From 66cf7c3a3b30a0441cb2c660bcb19bbb2c2a9943 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Oct 2025 01:07:48 -0500 Subject: [PATCH 0001/1145] [lvgl] Fix nested lambdas in automations unable to access parameters (#11583) Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/lvgl/defines.py | 22 +++++++++++++-- esphome/components/lvgl/lv_validation.py | 21 ++++++++++++--- esphome/components/lvgl/lvcode.py | 13 ++++++--- esphome/components/lvgl/sensor/__init__.py | 3 +-- tests/components/lvgl/common.yaml | 31 ++++++++++++++++++++++ tests/components/lvgl/lvgl-package.yaml | 23 ++++++++++++++++ 6 files changed, 103 insertions(+), 10 deletions(-) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 6464824c64..7fbb6de071 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -5,6 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i """ import logging +from typing import TYPE_CHECKING, Any from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_ITEMS @@ -12,6 +13,7 @@ from esphome.core import ID, Lambda from esphome.cpp_generator import LambdaExpression, MockObj from esphome.cpp_types import uint32 from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor +from esphome.types import Expression, SafeExpType from .helpers import requires_component @@ -42,7 +44,13 @@ def static_cast(type, value): def call_lambda(lamb: LambdaExpression): expr = lamb.content.strip() if expr.startswith("return") and expr.endswith(";"): - return expr[6:][:-1].strip() + return expr[6:-1].strip() + # If lambda has parameters, call it with those parameter names + # Parameter names come from hardcoded component code (like "x", "it", "event") + # not from user input, so they're safe to use directly + if lamb.parameters and lamb.parameters.parameters: + param_names = ", ".join(str(param.id) for param in lamb.parameters.parameters) + return f"{lamb}({param_names})" return f"{lamb}()" @@ -65,10 +73,20 @@ class LValidator: return cv.returning_lambda(value) return self.validator(value) - async def process(self, value, args=()): + async def process( + self, value: Any, args: list[tuple[SafeExpType, str]] | None = None + ) -> Expression: if value is None: return None if isinstance(value, Lambda): + # Local import to avoid circular import + from .lvcode import CodeContext, LambdaContext + + if TYPE_CHECKING: + # CodeContext does not have get_automation_parameters + # so we need to assert the type here + assert isinstance(CodeContext.code_context, LambdaContext) + args = args or CodeContext.code_context.get_automation_parameters() return cg.RawExpression( call_lambda( await cg.process_lambda(value, args, return_type=self.rtype) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index d345ac70f3..6f95a32a18 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -1,3 +1,5 @@ +from typing import TYPE_CHECKING, Any + import esphome.codegen as cg from esphome.components import image from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw @@ -17,6 +19,7 @@ from esphome.cpp_generator import MockObj from esphome.cpp_types import ESPTime, int32, uint32 from esphome.helpers import cpp_string_escape from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor +from esphome.types import Expression, SafeExpType from . import types as ty from .defines import ( @@ -388,11 +391,23 @@ class TextValidator(LValidator): return value return super().__call__(value) - async def process(self, value, args=()): + async def process( + self, value: Any, args: list[tuple[SafeExpType, str]] | None = None + ) -> Expression: + # Local import to avoid circular import at module level + + from .lvcode import CodeContext, LambdaContext + + if TYPE_CHECKING: + # CodeContext does not have get_automation_parameters + # so we need to assert the type here + assert isinstance(CodeContext.code_context, LambdaContext) + args = args or CodeContext.code_context.get_automation_parameters() + if isinstance(value, dict): if format_str := value.get(CONF_FORMAT): - args = [str(x) for x in value[CONF_ARGS]] - arg_expr = cg.RawExpression(",".join(args)) + str_args = [str(x) for x in value[CONF_ARGS]] + arg_expr = cg.RawExpression(",".join(str_args)) format_str = cpp_string_escape(format_str) return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()") if time_format := value.get(CONF_TIME_FORMAT): diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index 7a5c35f896..ea38845c07 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -164,6 +164,9 @@ class LambdaContext(CodeContext): code_text.append(text) return code_text + def get_automation_parameters(self) -> list[tuple[SafeExpType, str]]: + return self.parameters + async def __aenter__(self): await super().__aenter__() add_line_marks(self.where) @@ -178,9 +181,8 @@ class LvContext(LambdaContext): added_lambda_count = 0 - def __init__(self, args=None): - self.args = args or LVGL_COMP_ARG - super().__init__(parameters=self.args) + def __init__(self): + super().__init__(parameters=LVGL_COMP_ARG) async def __aexit__(self, exc_type, exc_val, exc_tb): await super().__aexit__(exc_type, exc_val, exc_tb) @@ -189,6 +191,11 @@ class LvContext(LambdaContext): cg.add(expression) return expression + def get_automation_parameters(self) -> list[tuple[SafeExpType, str]]: + # When generating automations, we don't want the `lv_component` parameter to be passed + # to the lambda. + return [] + def __call__(self, *args): return self.add(*args) diff --git a/esphome/components/lvgl/sensor/__init__.py b/esphome/components/lvgl/sensor/__init__.py index 03b2638ed0..167af9c6e1 100644 --- a/esphome/components/lvgl/sensor/__init__.py +++ b/esphome/components/lvgl/sensor/__init__.py @@ -5,7 +5,6 @@ from ..defines import CONF_WIDGET from ..lvcode import ( API_EVENT, EVENT_ARG, - LVGL_COMP_ARG, UPDATE_EVENT, LambdaContext, LvContext, @@ -30,7 +29,7 @@ async def to_code(config): await wait_for_widgets() async with LambdaContext(EVENT_ARG) as lamb: lv_add(sensor.publish_state(widget.get_value())) - async with LvContext(LVGL_COMP_ARG): + async with LvContext(): lv_add( lvgl_static.add_event_cb( widget.obj, diff --git a/tests/components/lvgl/common.yaml b/tests/components/lvgl/common.yaml index d9b7013a1e..c70dd7568d 100644 --- a/tests/components/lvgl/common.yaml +++ b/tests/components/lvgl/common.yaml @@ -52,6 +52,19 @@ number: widget: spinbox_id id: lvgl_spinbox_number name: LVGL Spinbox Number + - platform: template + id: test_brightness + name: "Test Brightness" + min_value: 0 + max_value: 255 + step: 1 + optimistic: true + # Test lambda in automation accessing x parameter directly + # This is a real-world pattern from user configs + on_value: + - lambda: !lambda |- + // Direct use of x parameter in automation + ESP_LOGD("test", "Brightness: %.0f", x); light: - platform: lvgl @@ -110,3 +123,21 @@ text: platform: lvgl widget: hello_label mode: text + +text_sensor: + - platform: template + id: test_text_sensor + name: "Test Text Sensor" + # Test nested lambdas in LVGL actions can access automation parameters + on_value: + - lvgl.label.update: + id: hello_label + text: !lambda return x.c_str(); + - lvgl.label.update: + id: hello_label + text: !lambda |- + // Test complex lambda with conditionals accessing x parameter + if (x == "*") { + return "WILDCARD"; + } + return x.c_str(); diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 582531e943..14241a1669 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -257,7 +257,30 @@ lvgl: text: "Hello shiny day" text_color: 0xFFFFFF align: bottom_mid + - label: + id: setup_lambda_label + # Test lambda in widget property during setup (LvContext) + # Should NOT receive lv_component parameter + text: !lambda |- + char buf[32]; + snprintf(buf, sizeof(buf), "Setup: %d", 42); + return std::string(buf); + align: top_mid text_font: space16 + - label: + id: chip_info_label + # Test complex setup lambda (real-world pattern) + # Should NOT receive lv_component parameter + text: !lambda |- + // Test conditional compilation and string formatting + char buf[64]; + #ifdef USE_ESP_IDF + snprintf(buf, sizeof(buf), "IDF: v%d.%d", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR); + #else + snprintf(buf, sizeof(buf), "Arduino"); + #endif + return std::string(buf); + align: top_left - obj: align: center arc_opa: COVER From 6fb490f49b35d4610677e2f91b2230f5e5a012c4 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 29 Oct 2025 12:40:22 -0400 Subject: [PATCH 0002/1145] [remote_transmitter] Add non-blocking mode (#11524) --- .../components/remote_transmitter/__init__.py | 20 +++++++++++++ .../remote_transmitter/remote_transmitter.h | 3 ++ .../remote_transmitter_esp32.cpp | 30 +++++++++++++++---- .../remote_transmitter/esp32-common.yaml | 1 + 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/esphome/components/remote_transmitter/__init__.py b/esphome/components/remote_transmitter/__init__.py index cb98c017f1..faa6c827f7 100644 --- a/esphome/components/remote_transmitter/__init__.py +++ b/esphome/components/remote_transmitter/__init__.py @@ -1,3 +1,5 @@ +import logging + from esphome import automation, pins import esphome.codegen as cg from esphome.components import esp32, esp32_rmt, remote_base @@ -18,9 +20,12 @@ from esphome.const import ( ) from esphome.core import CORE +_LOGGER = logging.getLogger(__name__) + AUTO_LOAD = ["remote_base"] CONF_EOT_LEVEL = "eot_level" +CONF_NON_BLOCKING = "non_blocking" CONF_ON_TRANSMIT = "on_transmit" CONF_ON_COMPLETE = "on_complete" CONF_TRANSMITTER_ID = remote_base.CONF_TRANSMITTER_ID @@ -65,11 +70,25 @@ CONFIG_SCHEMA = cv.Schema( esp32_c6=48, esp32_h2=48, ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), + cv.Optional(CONF_NON_BLOCKING): cv.All(cv.only_on_esp32, cv.boolean), cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True), cv.Optional(CONF_ON_COMPLETE): automation.validate_automation(single=True), } ).extend(cv.COMPONENT_SCHEMA) + +def _validate_non_blocking(config): + if CORE.is_esp32 and CONF_NON_BLOCKING not in config: + _LOGGER.warning( + "'non_blocking' is not set for 'remote_transmitter' and will default to 'true'.\n" + "The default behavior changed in 2025.11.0; previously blocking mode was used.\n" + "To silence this warning, explicitly set 'non_blocking: true' (or 'false')." + ) + config[CONF_NON_BLOCKING] = True + + +FINAL_VALIDATE_SCHEMA = _validate_non_blocking + DIGITAL_WRITE_ACTION_SCHEMA = cv.maybe_simple_value( { cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterComponent), @@ -95,6 +114,7 @@ async def to_code(config): if CORE.is_esp32: var = cg.new_Pvariable(config[CONF_ID], pin) cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) + cg.add(var.set_non_blocking(config[CONF_NON_BLOCKING])) if CONF_CLOCK_RESOLUTION in config: cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION])) if CONF_USE_DMA in config: diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index b5d8e8d83f..cc3b82ad61 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -54,6 +54,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, #if defined(USE_ESP32) void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; } void set_eot_level(bool eot_level) { this->eot_level_ = eot_level; } + void set_non_blocking(bool non_blocking) { this->non_blocking_ = non_blocking; } #endif Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; }; @@ -74,6 +75,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, #ifdef USE_ESP32 void configure_rmt_(); + void wait_for_rmt_(); #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) RemoteTransmitterComponentStore store_{}; @@ -90,6 +92,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, esp_err_t error_code_{ESP_OK}; std::string error_string_{""}; bool inverted_{false}; + bool non_blocking_{false}; #endif uint8_t carrier_duty_percent_; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 27bbf3c210..59c85c99a8 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -196,12 +196,29 @@ void RemoteTransmitterComponent::configure_rmt_() { } } +void RemoteTransmitterComponent::wait_for_rmt_() { + esp_err_t error = rmt_tx_wait_all_done(this->channel_, -1); + if (error != ESP_OK) { + ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); + this->status_set_warning(); + } + + this->complete_trigger_->trigger(); +} + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { + uint64_t total_duration = 0; + if (this->is_failed()) { return; } + // if the timeout was cancelled, block until the tx is complete + if (this->non_blocking_ && this->cancel_timeout("complete")) { + this->wait_for_rmt_(); + } + if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) { this->current_carrier_frequency_ = this->temp_.get_carrier_frequency(); this->configure_rmt_(); @@ -212,6 +229,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen // encode any delay at the start of the buffer to simplify the encoder callback // this will be skipped the first time around + total_duration += send_wait * (send_times - 1); send_wait = this->from_microseconds_(static_cast(send_wait)); while (send_wait > 0) { int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX)); @@ -229,6 +247,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen if (!level) { value = -value; } + total_duration += value * send_times; value = this->from_microseconds_(static_cast(value)); while (value > 0) { int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX)); @@ -260,13 +279,12 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen } else { this->status_clear_warning(); } - error = rmt_tx_wait_all_done(this->channel_, -1); - if (error != ESP_OK) { - ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); - this->status_set_warning(); - } - this->complete_trigger_->trigger(); + if (this->non_blocking_) { + this->set_timeout("complete", total_duration / 1000, [this]() { this->wait_for_rmt_(); }); + } else { + this->wait_for_rmt_(); + } } #else void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { diff --git a/tests/components/remote_transmitter/esp32-common.yaml b/tests/components/remote_transmitter/esp32-common.yaml index 8b26c45149..79fd47ae21 100644 --- a/tests/components/remote_transmitter/esp32-common.yaml +++ b/tests/components/remote_transmitter/esp32-common.yaml @@ -2,6 +2,7 @@ remote_transmitter: - id: xmitr pin: ${pin} carrier_duty_percent: 50% + non_blocking: true clock_resolution: ${clock_resolution} rmt_symbols: ${rmt_symbols} From f18c70a256328ecb9037ad45ff405a67f796565c Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Wed, 29 Oct 2025 19:06:55 +0100 Subject: [PATCH 0003/1145] [core] Fix substitution id redefinition false positive (#11603) --- esphome/config.py | 3 +++ tests/unit_tests/core/test_config.py | 11 +++++++++++ .../core/config/id_collision_with_substitution.yaml | 12 ++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 tests/unit_tests/fixtures/core/config/id_collision_with_substitution.yaml diff --git a/esphome/config.py b/esphome/config.py index 634dba8dad..e508ca585b 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -319,6 +319,9 @@ def iter_ids(config, path=None): yield from iter_ids(item, path + [i]) elif isinstance(config, dict): for key, value in config.items(): + if len(path) == 0 and key == CONF_SUBSTITUTIONS: + # Ignore IDs in substitution definitions. + continue if isinstance(key, core.ID): yield key, path yield from iter_ids(value, path + [key]) diff --git a/tests/unit_tests/core/test_config.py b/tests/unit_tests/core/test_config.py index a1e4627dc9..90b2f5edba 100644 --- a/tests/unit_tests/core/test_config.py +++ b/tests/unit_tests/core/test_config.py @@ -261,6 +261,17 @@ def test_device_duplicate_id( assert "ID duplicate_device redefined!" in captured.out +def test_substitution_with_id( + yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] +) -> None: + """Test that a ids coming from substitutions do not cause false positive ID redefinition.""" + load_config_from_fixture( + yaml_file, "id_collision_with_substitution.yaml", FIXTURES_DIR + ) + captured = capsys.readouterr() + assert "ID some_switch_id redefined!" not in captured.out + + def test_add_platform_defines_priority() -> None: """Test that _add_platform_defines runs after globals. diff --git a/tests/unit_tests/fixtures/core/config/id_collision_with_substitution.yaml b/tests/unit_tests/fixtures/core/config/id_collision_with_substitution.yaml new file mode 100644 index 0000000000..840d9ac925 --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/id_collision_with_substitution.yaml @@ -0,0 +1,12 @@ +esphome: + name: test + +host: + +substitutions: + support_switches: + - platform: gpio + id: some_switch_id + pin: 12 + +switch: $support_switches From 287f65cbaf4ab31fcab7a0502ff5ddbeee51d0e4 Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Wed, 29 Oct 2025 13:27:31 -0700 Subject: [PATCH 0004/1145] [lvgl] fix typo from previous refactor (#11596) --- esphome/components/image/image.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/components/image/image.cpp b/esphome/components/image/image.cpp index 7b65c4d0cb..90e021467f 100644 --- a/esphome/components/image/image.cpp +++ b/esphome/components/image/image.cpp @@ -125,7 +125,7 @@ lv_img_dsc_t *Image::get_lv_img_dsc() { case IMAGE_TYPE_RGB: #if LV_COLOR_DEPTH == 32 - switch (this->transparent_) { + switch (this->transparency_) { case TRANSPARENCY_ALPHA_CHANNEL: this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA; break; @@ -156,7 +156,8 @@ lv_img_dsc_t *Image::get_lv_img_dsc() { break; } #else - this->dsc_.header.cf = this->transparent_ == TRANSPARENCY_ALPHA_CHANNEL ? LV_IMG_CF_RGB565A8 : LV_IMG_CF_RGB565; + this->dsc_.header.cf = + this->transparency_ == TRANSPARENCY_ALPHA_CHANNEL ? LV_IMG_CF_RGB565A8 : LV_IMG_CF_RGB565; #endif break; } From 918650f15adb10d87ae6d0a4f04accdb27b97b73 Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Wed, 29 Oct 2025 14:06:45 -0700 Subject: [PATCH 0005/1145] [lvgl] memset canvas buffer to prevent display of random garbage (#11582) Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/lvgl/widgets/canvas.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/esphome/components/lvgl/widgets/canvas.py b/esphome/components/lvgl/widgets/canvas.py index 217e8935f1..f0a9cd35ba 100644 --- a/esphome/components/lvgl/widgets/canvas.py +++ b/esphome/components/lvgl/widgets/canvas.py @@ -33,7 +33,7 @@ from ..lv_validation import ( pixels, size, ) -from ..lvcode import LocalVariable, lv, lv_assign +from ..lvcode import LocalVariable, lv, lv_assign, lv_expr from ..schemas import STYLE_PROPS, STYLE_REMAP, TEXT_SCHEMA, point_schema from ..types import LvType, ObjUpdateAction, WidgetType from . import Widget, get_widgets @@ -70,15 +70,18 @@ class CanvasType(WidgetType): width = config[CONF_WIDTH] height = config[CONF_HEIGHT] use_alpha = "_ALPHA" if config[CONF_TRANSPARENT] else "" - lv.canvas_set_buffer( - w.obj, - lv.custom_mem_alloc( - literal(f"LV_CANVAS_BUF_SIZE_TRUE_COLOR{use_alpha}({width}, {height})") - ), - width, - height, - literal(f"LV_IMG_CF_TRUE_COLOR{use_alpha}"), + buf_size = literal( + f"LV_CANVAS_BUF_SIZE_TRUE_COLOR{use_alpha}({width}, {height})" ) + with LocalVariable("buf", cg.void, lv_expr.custom_mem_alloc(buf_size)) as buf: + cg.add(cg.RawExpression(f"memset({buf}, 0, {buf_size});")) + lv.canvas_set_buffer( + w.obj, + buf, + width, + height, + literal(f"LV_IMG_CF_TRUE_COLOR{use_alpha}"), + ) canvas_spec = CanvasType() From 03fd1143710d58243a93f5901dc49e677413b981 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Oct 2025 20:26:37 -0500 Subject: [PATCH 0006/1145] [ci] Restore parallel execution for clang-tidy split mode (#11613) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd45adb78b..a2b1f84ca8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -458,7 +458,7 @@ jobs: GH_TOKEN: ${{ github.token }} strategy: fail-fast: false - max-parallel: 1 + max-parallel: 2 matrix: include: - id: clang-tidy From 08aae39ea47d65dd4ff4af655fd967f2a64167f7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Oct 2025 20:27:28 -0500 Subject: [PATCH 0007/1145] [ci] Consolidate component splitting into determine-jobs (#11614) --- .github/workflows/ci.yml | 46 ++--------------------------- script/determine-jobs.py | 24 +++++++++++++++ script/split_components_for_ci.py | 4 +++ tests/script/test_determine_jobs.py | 11 +++++++ 4 files changed, 42 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2b1f84ca8..1756d5b765 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -180,6 +180,7 @@ jobs: memory_impact: ${{ steps.determine.outputs.memory-impact }} cpp-unit-tests-run-all: ${{ steps.determine.outputs.cpp-unit-tests-run-all }} cpp-unit-tests-components: ${{ steps.determine.outputs.cpp-unit-tests-components }} + component-test-batches: ${{ steps.determine.outputs.component-test-batches }} steps: - name: Check out code from GitHub uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -214,6 +215,7 @@ jobs: echo "memory-impact=$(echo "$output" | jq -c '.memory_impact')" >> $GITHUB_OUTPUT echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT + echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT integration-tests: name: Run integration tests @@ -536,59 +538,18 @@ jobs: run: script/ci-suggest-changes if: always() - test-build-components-splitter: - name: Split components for intelligent grouping (40 weighted per batch) - runs-on: ubuntu-24.04 - needs: - - common - - determine-jobs - if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0 - outputs: - matrix: ${{ steps.split.outputs.components }} - steps: - - name: Check out code from GitHub - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Restore Python - uses: ./.github/actions/restore-python - with: - python-version: ${{ env.DEFAULT_PYTHON }} - cache-key: ${{ needs.common.outputs.cache-key }} - - name: Split components intelligently based on bus configurations - id: split - run: | - . venv/bin/activate - - # Use intelligent splitter that groups components with same bus configs - components='${{ needs.determine-jobs.outputs.changed-components-with-tests }}' - - # Only isolate directly changed components when targeting dev branch - # For beta/release branches, group everything for faster CI - if [[ "${{ github.base_ref }}" == beta* ]] || [[ "${{ github.base_ref }}" == release* ]]; then - directly_changed='[]' - echo "Target branch: ${{ github.base_ref }} - grouping all components" - else - directly_changed='${{ needs.determine-jobs.outputs.directly-changed-components-with-tests }}' - echo "Target branch: ${{ github.base_ref }} - isolating directly changed components" - fi - - echo "Splitting components intelligently..." - output=$(python3 script/split_components_for_ci.py --components "$components" --directly-changed "$directly_changed" --batch-size 40 --output github) - - echo "$output" >> $GITHUB_OUTPUT - test-build-components-split: name: Test components batch (${{ matrix.components }}) runs-on: ubuntu-24.04 needs: - common - determine-jobs - - test-build-components-splitter if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0 strategy: fail-fast: false max-parallel: ${{ (startsWith(github.base_ref, 'beta') || startsWith(github.base_ref, 'release')) && 8 || 4 }} matrix: - components: ${{ fromJson(needs.test-build-components-splitter.outputs.matrix) }} + components: ${{ fromJson(needs.determine-jobs.outputs.component-test-batches) }} steps: - name: Show disk space run: | @@ -980,7 +941,6 @@ jobs: - clang-tidy-nosplit - clang-tidy-split - determine-jobs - - test-build-components-splitter - test-build-components-split - pre-commit-ci-lite - memory-impact-target-branch diff --git a/script/determine-jobs.py b/script/determine-jobs.py index 21eb529f33..4a0edebb0d 100755 --- a/script/determine-jobs.py +++ b/script/determine-jobs.py @@ -43,12 +43,14 @@ from enum import StrEnum from functools import cache import json import os +from pathlib import Path import subprocess import sys from typing import Any from helpers import ( CPP_FILE_EXTENSIONS, + ESPHOME_TESTS_COMPONENTS_PATH, PYTHON_FILE_EXTENSIONS, changed_files, core_changed, @@ -65,12 +67,17 @@ from helpers import ( parse_test_filename, root_path, ) +from split_components_for_ci import create_intelligent_batches # Threshold for splitting clang-tidy jobs # For small PRs (< 65 files), use nosplit for faster CI # For large PRs (>= 65 files), use split for better parallelization CLANG_TIDY_SPLIT_THRESHOLD = 65 +# Component test batch size (weighted) +# Isolated components count as 10x, groupable components count as 1x +COMPONENT_TEST_BATCH_SIZE = 40 + class Platform(StrEnum): """Platform identifiers for memory impact analysis.""" @@ -686,6 +693,22 @@ def main() -> None: # Determine which C++ unit tests to run cpp_run_all, cpp_components = determine_cpp_unit_tests(args.branch) + # Split components into batches for CI testing + # This intelligently groups components with similar bus configurations + component_test_batches: list[str] + if changed_components_with_tests: + tests_dir = Path(root_path) / ESPHOME_TESTS_COMPONENTS_PATH + batches, _ = create_intelligent_batches( + components=changed_components_with_tests, + tests_dir=tests_dir, + batch_size=COMPONENT_TEST_BATCH_SIZE, + directly_changed=directly_changed_with_tests, + ) + # Convert batches to space-separated strings for CI matrix + component_test_batches = [" ".join(batch) for batch in batches] + else: + component_test_batches = [] + output: dict[str, Any] = { "integration_tests": run_integration, "clang_tidy": run_clang_tidy, @@ -703,6 +726,7 @@ def main() -> None: "memory_impact": memory_impact, "cpp_unit_tests_run_all": cpp_run_all, "cpp_unit_tests_components": cpp_components, + "component_test_batches": component_test_batches, } # Output as JSON diff --git a/script/split_components_for_ci.py b/script/split_components_for_ci.py index 87da540d43..65d09efb9b 100755 --- a/script/split_components_for_ci.py +++ b/script/split_components_for_ci.py @@ -62,6 +62,10 @@ def create_intelligent_batches( ) -> tuple[list[list[str]], dict[tuple[str, str], list[str]]]: """Create batches optimized for component grouping. + IMPORTANT: This function is called from both split_components_for_ci.py (standalone script) + and determine-jobs.py (integrated into job determination). Be careful when refactoring + to ensure changes work in both contexts. + Args: components: List of component names to batch tests_dir: Path to tests/components directory diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py index c8ef76184f..e73c134151 100644 --- a/tests/script/test_determine_jobs.py +++ b/tests/script/test_determine_jobs.py @@ -152,6 +152,14 @@ def test_main_all_tests_should_run( assert output["memory_impact"]["should_run"] == "false" assert output["cpp_unit_tests_run_all"] is False assert output["cpp_unit_tests_components"] == ["wifi", "api", "sensor"] + # component_test_batches should be present and be a list of space-separated strings + assert "component_test_batches" in output + assert isinstance(output["component_test_batches"], list) + # Each batch should be a space-separated string of component names + for batch in output["component_test_batches"]: + assert isinstance(batch, str) + # Should contain at least one component (no empty batches) + assert len(batch) > 0 def test_main_no_tests_should_run( @@ -209,6 +217,9 @@ def test_main_no_tests_should_run( assert output["memory_impact"]["should_run"] == "false" assert output["cpp_unit_tests_run_all"] is False assert output["cpp_unit_tests_components"] == [] + # component_test_batches should be empty list + assert "component_test_batches" in output + assert output["component_test_batches"] == [] def test_main_with_branch_argument( From 29ed3c20af179fcdb2fd641566f7167fe79c863a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Oct 2025 20:28:38 -0500 Subject: [PATCH 0008/1145] [gpio] Skip set_use_interrupt call when using default value (#11612) --- esphome/components/gpio/binary_sensor/__init__.py | 4 +++- tests/component_tests/gpio/test_gpio_binary_sensor.py | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/esphome/components/gpio/binary_sensor/__init__.py b/esphome/components/gpio/binary_sensor/__init__.py index 8372bc7e08..ca4dc43e9c 100644 --- a/esphome/components/gpio/binary_sensor/__init__.py +++ b/esphome/components/gpio/binary_sensor/__init__.py @@ -94,6 +94,8 @@ async def to_code(config): ) use_interrupt = False - cg.add(var.set_use_interrupt(use_interrupt)) if use_interrupt: cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE])) + else: + # Only generate call when disabling interrupts (default is true) + cg.add(var.set_use_interrupt(use_interrupt)) diff --git a/tests/component_tests/gpio/test_gpio_binary_sensor.py b/tests/component_tests/gpio/test_gpio_binary_sensor.py index 74fa2ab1c1..73665dc45d 100644 --- a/tests/component_tests/gpio/test_gpio_binary_sensor.py +++ b/tests/component_tests/gpio/test_gpio_binary_sensor.py @@ -18,7 +18,8 @@ def test_gpio_binary_sensor_basic_setup( assert "new gpio::GPIOBinarySensor();" in main_cpp assert "App.register_binary_sensor" in main_cpp - assert "bs_gpio->set_use_interrupt(true);" in main_cpp + # set_use_interrupt(true) should NOT be generated (uses C++ default) + assert "bs_gpio->set_use_interrupt(true);" not in main_cpp assert "bs_gpio->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp @@ -51,8 +52,8 @@ def test_gpio_binary_sensor_esp8266_other_pins_use_interrupt( "tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml" ) - # GPIO5 should still use interrupts - assert "bs_gpio5->set_use_interrupt(true);" in main_cpp + # GPIO5 should still use interrupts (default, so no setter call) + assert "bs_gpio5->set_use_interrupt(true);" not in main_cpp assert "bs_gpio5->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp From 58235049e3c7a023a37441aed6b8440abaec0626 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Oct 2025 21:06:21 -0500 Subject: [PATCH 0009/1145] [template] Eliminate optional wrapper to save 4 bytes RAM per instance (#11610) --- .../binary_sensor/template_binary_sensor.cpp | 14 +++-- .../binary_sensor/template_binary_sensor.h | 5 +- .../template/cover/template_cover.cpp | 33 ++++++------ .../template/cover/template_cover.h | 9 ++-- .../template/datetime/template_date.cpp | 15 +++--- .../template/datetime/template_date.h | 5 +- .../template/datetime/template_datetime.cpp | 21 ++++---- .../template/datetime/template_datetime.h | 5 +- .../template/datetime/template_time.cpp | 15 +++--- .../template/datetime/template_time.h | 5 +- .../template/lock/template_lock.cpp | 15 +++--- .../components/template/lock/template_lock.h | 6 ++- .../template/number/template_number.cpp | 9 ++-- .../template/number/template_number.h | 5 +- .../template/select/template_select.cpp | 16 +++--- .../template/select/template_select.h | 5 +- .../template/sensor/template_sensor.cpp | 5 +- .../template/sensor/template_sensor.h | 5 +- .../template/switch/template_switch.cpp | 15 +++--- .../template/switch/template_switch.h | 5 +- .../template/text/template_text.cpp | 18 +++---- .../components/template/text/template_text.h | 5 +- .../text_sensor/template_text_sensor.cpp | 5 +- .../text_sensor/template_text_sensor.h | 5 +- .../template/valve/template_valve.cpp | 17 +++---- .../template/valve/template_valve.h | 5 +- esphome/core/template_lambda.h | 51 +++++++++++++++++++ tests/components/template/common-base.yaml | 8 +++ tests/integration/fixtures/runtime_stats.yaml | 1 + 29 files changed, 196 insertions(+), 132 deletions(-) create mode 100644 esphome/core/template_lambda.h diff --git a/esphome/components/template/binary_sensor/template_binary_sensor.cpp b/esphome/components/template/binary_sensor/template_binary_sensor.cpp index 8543dff4dc..806aed49b1 100644 --- a/esphome/components/template/binary_sensor/template_binary_sensor.cpp +++ b/esphome/components/template/binary_sensor/template_binary_sensor.cpp @@ -6,17 +6,21 @@ namespace template_ { static const char *const TAG = "template.binary_sensor"; -void TemplateBinarySensor::setup() { this->loop(); } +void TemplateBinarySensor::setup() { + if (!this->f_.has_value()) { + this->disable_loop(); + } else { + this->loop(); + } +} void TemplateBinarySensor::loop() { - if (!this->f_.has_value()) - return; - - auto s = (*this->f_)(); + auto s = this->f_(); if (s.has_value()) { this->publish_state(*s); } } + void TemplateBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Template Binary Sensor", this); } } // namespace template_ diff --git a/esphome/components/template/binary_sensor/template_binary_sensor.h b/esphome/components/template/binary_sensor/template_binary_sensor.h index 2e0b216eb4..bc591391b9 100644 --- a/esphome/components/template/binary_sensor/template_binary_sensor.h +++ b/esphome/components/template/binary_sensor/template_binary_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/template_lambda.h" #include "esphome/components/binary_sensor/binary_sensor.h" namespace esphome { @@ -8,7 +9,7 @@ namespace template_ { class TemplateBinarySensor : public Component, public binary_sensor::BinarySensor { public: - void set_template(optional (*f)()) { this->f_ = f; } + template void set_template(F &&f) { this->f_.set(std::forward(f)); } void setup() override; void loop() override; @@ -17,7 +18,7 @@ class TemplateBinarySensor : public Component, public binary_sensor::BinarySenso float get_setup_priority() const override { return setup_priority::HARDWARE; } protected: - optional (*)()> f_; + TemplateLambda f_; }; } // namespace template_ diff --git a/esphome/components/template/cover/template_cover.cpp b/esphome/components/template/cover/template_cover.cpp index bed3931e78..a87f28ccec 100644 --- a/esphome/components/template/cover/template_cover.cpp +++ b/esphome/components/template/cover/template_cover.cpp @@ -33,28 +33,27 @@ void TemplateCover::setup() { break; } } + if (!this->state_f_.has_value() && !this->tilt_f_.has_value()) + this->disable_loop(); } void TemplateCover::loop() { bool changed = false; - if (this->state_f_.has_value()) { - auto s = (*this->state_f_)(); - if (s.has_value()) { - auto pos = clamp(*s, 0.0f, 1.0f); - if (pos != this->position) { - this->position = pos; - changed = true; - } + auto s = this->state_f_(); + if (s.has_value()) { + auto pos = clamp(*s, 0.0f, 1.0f); + if (pos != this->position) { + this->position = pos; + changed = true; } } - if (this->tilt_f_.has_value()) { - auto s = (*this->tilt_f_)(); - if (s.has_value()) { - auto tilt = clamp(*s, 0.0f, 1.0f); - if (tilt != this->tilt) { - this->tilt = tilt; - changed = true; - } + + auto tilt = this->tilt_f_(); + if (tilt.has_value()) { + auto tilt_val = clamp(*tilt, 0.0f, 1.0f); + if (tilt_val != this->tilt) { + this->tilt = tilt_val; + changed = true; } } @@ -63,7 +62,6 @@ void TemplateCover::loop() { } void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } -void TemplateCover::set_state_lambda(optional (*f)()) { this->state_f_ = f; } float TemplateCover::get_setup_priority() const { return setup_priority::HARDWARE; } Trigger<> *TemplateCover::get_open_trigger() const { return this->open_trigger_; } Trigger<> *TemplateCover::get_close_trigger() const { return this->close_trigger_; } @@ -124,7 +122,6 @@ CoverTraits TemplateCover::get_traits() { } Trigger *TemplateCover::get_position_trigger() const { return this->position_trigger_; } Trigger *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; } -void TemplateCover::set_tilt_lambda(optional (*tilt_f)()) { this->tilt_f_ = tilt_f; } void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; } void TemplateCover::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; } void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; } diff --git a/esphome/components/template/cover/template_cover.h b/esphome/components/template/cover/template_cover.h index ed1ebf4e43..faff69f867 100644 --- a/esphome/components/template/cover/template_cover.h +++ b/esphome/components/template/cover/template_cover.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/template_lambda.h" #include "esphome/components/cover/cover.h" namespace esphome { @@ -17,7 +18,8 @@ class TemplateCover : public cover::Cover, public Component { public: TemplateCover(); - void set_state_lambda(optional (*f)()); + template void set_state_lambda(F &&f) { this->state_f_.set(std::forward(f)); } + template void set_tilt_lambda(F &&f) { this->tilt_f_.set(std::forward(f)); } Trigger<> *get_open_trigger() const; Trigger<> *get_close_trigger() const; Trigger<> *get_stop_trigger() const; @@ -26,7 +28,6 @@ class TemplateCover : public cover::Cover, public Component { Trigger *get_tilt_trigger() const; void set_optimistic(bool optimistic); void set_assumed_state(bool assumed_state); - void set_tilt_lambda(optional (*tilt_f)()); void set_has_stop(bool has_stop); void set_has_position(bool has_position); void set_has_tilt(bool has_tilt); @@ -45,8 +46,8 @@ class TemplateCover : public cover::Cover, public Component { void stop_prev_trigger_(); TemplateCoverRestoreMode restore_mode_{COVER_RESTORE}; - optional (*)()> state_f_; - optional (*)()> tilt_f_; + TemplateLambda state_f_; + TemplateLambda tilt_f_; bool assumed_state_{false}; bool optimistic_{false}; Trigger<> *open_trigger_; diff --git a/esphome/components/template/datetime/template_date.cpp b/esphome/components/template/datetime/template_date.cpp index 2fa8016802..3f6626e847 100644 --- a/esphome/components/template/datetime/template_date.cpp +++ b/esphome/components/template/datetime/template_date.cpp @@ -40,14 +40,13 @@ void TemplateDate::update() { if (!this->f_.has_value()) return; - auto val = (*this->f_)(); - if (!val.has_value()) - return; - - this->year_ = val->year; - this->month_ = val->month; - this->day_ = val->day_of_month; - this->publish_state(); + auto val = this->f_(); + if (val.has_value()) { + this->year_ = val->year; + this->month_ = val->month; + this->day_ = val->day_of_month; + this->publish_state(); + } } void TemplateDate::control(const datetime::DateCall &call) { diff --git a/esphome/components/template/datetime/template_date.h b/esphome/components/template/datetime/template_date.h index 2a0967fc94..7fed704d0e 100644 --- a/esphome/components/template/datetime/template_date.h +++ b/esphome/components/template/datetime/template_date.h @@ -9,13 +9,14 @@ #include "esphome/core/component.h" #include "esphome/core/preferences.h" #include "esphome/core/time.h" +#include "esphome/core/template_lambda.h" namespace esphome { namespace template_ { class TemplateDate : public datetime::DateEntity, public PollingComponent { public: - void set_template(optional (*f)()) { this->f_ = f; } + template void set_template(F &&f) { this->f_.set(std::forward(f)); } void setup() override; void update() override; @@ -35,7 +36,7 @@ class TemplateDate : public datetime::DateEntity, public PollingComponent { ESPTime initial_value_{}; bool restore_value_{false}; Trigger *set_trigger_ = new Trigger(); - optional (*)()> f_; + TemplateLambda f_; ESPPreferenceObject pref_; }; diff --git a/esphome/components/template/datetime/template_datetime.cpp b/esphome/components/template/datetime/template_datetime.cpp index a4a4e47d65..62f842a7ad 100644 --- a/esphome/components/template/datetime/template_datetime.cpp +++ b/esphome/components/template/datetime/template_datetime.cpp @@ -43,17 +43,16 @@ void TemplateDateTime::update() { if (!this->f_.has_value()) return; - auto val = (*this->f_)(); - if (!val.has_value()) - return; - - this->year_ = val->year; - this->month_ = val->month; - this->day_ = val->day_of_month; - this->hour_ = val->hour; - this->minute_ = val->minute; - this->second_ = val->second; - this->publish_state(); + auto val = this->f_(); + if (val.has_value()) { + this->year_ = val->year; + this->month_ = val->month; + this->day_ = val->day_of_month; + this->hour_ = val->hour; + this->minute_ = val->minute; + this->second_ = val->second; + this->publish_state(); + } } void TemplateDateTime::control(const datetime::DateTimeCall &call) { diff --git a/esphome/components/template/datetime/template_datetime.h b/esphome/components/template/datetime/template_datetime.h index d917015b67..ec45bf0326 100644 --- a/esphome/components/template/datetime/template_datetime.h +++ b/esphome/components/template/datetime/template_datetime.h @@ -9,13 +9,14 @@ #include "esphome/core/component.h" #include "esphome/core/preferences.h" #include "esphome/core/time.h" +#include "esphome/core/template_lambda.h" namespace esphome { namespace template_ { class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponent { public: - void set_template(optional (*f)()) { this->f_ = f; } + template void set_template(F &&f) { this->f_.set(std::forward(f)); } void setup() override; void update() override; @@ -35,7 +36,7 @@ class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponen ESPTime initial_value_{}; bool restore_value_{false}; Trigger *set_trigger_ = new Trigger(); - optional (*)()> f_; + TemplateLambda f_; ESPPreferenceObject pref_; }; diff --git a/esphome/components/template/datetime/template_time.cpp b/esphome/components/template/datetime/template_time.cpp index 349700f187..dab28d01cc 100644 --- a/esphome/components/template/datetime/template_time.cpp +++ b/esphome/components/template/datetime/template_time.cpp @@ -40,14 +40,13 @@ void TemplateTime::update() { if (!this->f_.has_value()) return; - auto val = (*this->f_)(); - if (!val.has_value()) - return; - - this->hour_ = val->hour; - this->minute_ = val->minute; - this->second_ = val->second; - this->publish_state(); + auto val = this->f_(); + if (val.has_value()) { + this->hour_ = val->hour; + this->minute_ = val->minute; + this->second_ = val->second; + this->publish_state(); + } } void TemplateTime::control(const datetime::TimeCall &call) { diff --git a/esphome/components/template/datetime/template_time.h b/esphome/components/template/datetime/template_time.h index 2f05ba0737..ea7474c0ba 100644 --- a/esphome/components/template/datetime/template_time.h +++ b/esphome/components/template/datetime/template_time.h @@ -9,13 +9,14 @@ #include "esphome/core/component.h" #include "esphome/core/preferences.h" #include "esphome/core/time.h" +#include "esphome/core/template_lambda.h" namespace esphome { namespace template_ { class TemplateTime : public datetime::TimeEntity, public PollingComponent { public: - void set_template(optional (*f)()) { this->f_ = f; } + template void set_template(F &&f) { this->f_.set(std::forward(f)); } void setup() override; void update() override; @@ -35,7 +36,7 @@ class TemplateTime : public datetime::TimeEntity, public PollingComponent { ESPTime initial_value_{}; bool restore_value_{false}; Trigger *set_trigger_ = new Trigger(); - optional (*)()> f_; + TemplateLambda f_; ESPPreferenceObject pref_; }; diff --git a/esphome/components/template/lock/template_lock.cpp b/esphome/components/template/lock/template_lock.cpp index c2e227c26d..8ed87b9736 100644 --- a/esphome/components/template/lock/template_lock.cpp +++ b/esphome/components/template/lock/template_lock.cpp @@ -11,14 +11,16 @@ static const char *const TAG = "template.lock"; TemplateLock::TemplateLock() : lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {} -void TemplateLock::loop() { +void TemplateLock::setup() { if (!this->f_.has_value()) - return; - auto val = (*this->f_)(); - if (!val.has_value()) - return; + this->disable_loop(); +} - this->publish_state(*val); +void TemplateLock::loop() { + auto val = this->f_(); + if (val.has_value()) { + this->publish_state(*val); + } } void TemplateLock::control(const lock::LockCall &call) { if (this->prev_trigger_ != nullptr) { @@ -45,7 +47,6 @@ void TemplateLock::open_latch() { this->open_trigger_->trigger(); } void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } -void TemplateLock::set_state_lambda(optional (*f)()) { this->f_ = f; } float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; } Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; } Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; } diff --git a/esphome/components/template/lock/template_lock.h b/esphome/components/template/lock/template_lock.h index 428744a66f..14fca4635e 100644 --- a/esphome/components/template/lock/template_lock.h +++ b/esphome/components/template/lock/template_lock.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/template_lambda.h" #include "esphome/components/lock/lock.h" namespace esphome { @@ -11,9 +12,10 @@ class TemplateLock : public lock::Lock, public Component { public: TemplateLock(); + void setup() override; void dump_config() override; - void set_state_lambda(optional (*f)()); + template void set_state_lambda(F &&f) { this->f_.set(std::forward(f)); } Trigger<> *get_lock_trigger() const; Trigger<> *get_unlock_trigger() const; Trigger<> *get_open_trigger() const; @@ -26,7 +28,7 @@ class TemplateLock : public lock::Lock, public Component { void control(const lock::LockCall &call) override; void open_latch() override; - optional (*)()> f_; + TemplateLambda f_; bool optimistic_{false}; Trigger<> *lock_trigger_; Trigger<> *unlock_trigger_; diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index 187f426273..145a89a2f7 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -30,11 +30,10 @@ void TemplateNumber::update() { if (!this->f_.has_value()) return; - auto val = (*this->f_)(); - if (!val.has_value()) - return; - - this->publish_state(*val); + auto val = this->f_(); + if (val.has_value()) { + this->publish_state(*val); + } } void TemplateNumber::control(float value) { diff --git a/esphome/components/template/number/template_number.h b/esphome/components/template/number/template_number.h index e77b181d25..a9307e9246 100644 --- a/esphome/components/template/number/template_number.h +++ b/esphome/components/template/number/template_number.h @@ -4,13 +4,14 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/preferences.h" +#include "esphome/core/template_lambda.h" namespace esphome { namespace template_ { class TemplateNumber : public number::Number, public PollingComponent { public: - void set_template(optional (*f)()) { this->f_ = f; } + template void set_template(F &&f) { this->f_.set(std::forward(f)); } void setup() override; void update() override; @@ -28,7 +29,7 @@ class TemplateNumber : public number::Number, public PollingComponent { float initial_value_{NAN}; bool restore_value_{false}; Trigger *set_trigger_ = new Trigger(); - optional (*)()> f_; + TemplateLambda f_; ESPPreferenceObject pref_; }; diff --git a/esphome/components/template/select/template_select.cpp b/esphome/components/template/select/template_select.cpp index c7a1d8a344..3ea34c3c7c 100644 --- a/esphome/components/template/select/template_select.cpp +++ b/esphome/components/template/select/template_select.cpp @@ -31,16 +31,14 @@ void TemplateSelect::update() { if (!this->f_.has_value()) return; - auto val = (*this->f_)(); - if (!val.has_value()) - return; - - if (!this->has_option(*val)) { - ESP_LOGE(TAG, "Lambda returned an invalid option: %s", (*val).c_str()); - return; + auto val = this->f_(); + if (val.has_value()) { + if (!this->has_option(*val)) { + ESP_LOGE(TAG, "Lambda returned an invalid option: %s", (*val).c_str()); + return; + } + this->publish_state(*val); } - - this->publish_state(*val); } void TemplateSelect::control(const std::string &value) { diff --git a/esphome/components/template/select/template_select.h b/esphome/components/template/select/template_select.h index e77e4d8f14..1c33153872 100644 --- a/esphome/components/template/select/template_select.h +++ b/esphome/components/template/select/template_select.h @@ -4,13 +4,14 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/preferences.h" +#include "esphome/core/template_lambda.h" namespace esphome { namespace template_ { class TemplateSelect : public select::Select, public PollingComponent { public: - void set_template(optional (*f)()) { this->f_ = f; } + template void set_template(F &&f) { this->f_.set(std::forward(f)); } void setup() override; void update() override; @@ -28,7 +29,7 @@ class TemplateSelect : public select::Select, public PollingComponent { size_t initial_option_index_{0}; bool restore_value_ = false; Trigger *set_trigger_ = new Trigger(); - optional (*)()> f_; + TemplateLambda f_; ESPPreferenceObject pref_; }; diff --git a/esphome/components/template/sensor/template_sensor.cpp b/esphome/components/template/sensor/template_sensor.cpp index 65f2417670..1558ea9b15 100644 --- a/esphome/components/template/sensor/template_sensor.cpp +++ b/esphome/components/template/sensor/template_sensor.cpp @@ -11,13 +11,14 @@ void TemplateSensor::update() { if (!this->f_.has_value()) return; - auto val = (*this->f_)(); + auto val = this->f_(); if (val.has_value()) { this->publish_state(*val); } } + float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; } -void TemplateSensor::set_template(optional (*f)()) { this->f_ = f; } + void TemplateSensor::dump_config() { LOG_SENSOR("", "Template Sensor", this); LOG_UPDATE_INTERVAL(this); diff --git a/esphome/components/template/sensor/template_sensor.h b/esphome/components/template/sensor/template_sensor.h index 369313d607..793d754a0f 100644 --- a/esphome/components/template/sensor/template_sensor.h +++ b/esphome/components/template/sensor/template_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/template_lambda.h" #include "esphome/components/sensor/sensor.h" namespace esphome { @@ -8,7 +9,7 @@ namespace template_ { class TemplateSensor : public sensor::Sensor, public PollingComponent { public: - void set_template(optional (*f)()); + template void set_template(F &&f) { this->f_.set(std::forward(f)); } void update() override; @@ -17,7 +18,7 @@ class TemplateSensor : public sensor::Sensor, public PollingComponent { float get_setup_priority() const override; protected: - optional (*)()> f_; + TemplateLambda f_; }; } // namespace template_ diff --git a/esphome/components/template/switch/template_switch.cpp b/esphome/components/template/switch/template_switch.cpp index 5aaf514b2a..95e8692da5 100644 --- a/esphome/components/template/switch/template_switch.cpp +++ b/esphome/components/template/switch/template_switch.cpp @@ -9,13 +9,10 @@ static const char *const TAG = "template.switch"; TemplateSwitch::TemplateSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {} void TemplateSwitch::loop() { - if (!this->f_.has_value()) - return; - auto s = (*this->f_)(); - if (!s.has_value()) - return; - - this->publish_state(*s); + auto s = this->f_(); + if (s.has_value()) { + this->publish_state(*s); + } } void TemplateSwitch::write_state(bool state) { if (this->prev_trigger_ != nullptr) { @@ -35,11 +32,13 @@ void TemplateSwitch::write_state(bool state) { } void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } bool TemplateSwitch::assumed_state() { return this->assumed_state_; } -void TemplateSwitch::set_state_lambda(optional (*f)()) { this->f_ = f; } float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; } Trigger<> *TemplateSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; } Trigger<> *TemplateSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; } void TemplateSwitch::setup() { + if (!this->f_.has_value()) + this->disable_loop(); + optional initial_state = this->get_initial_state_with_restore_mode(); if (initial_state.has_value()) { diff --git a/esphome/components/template/switch/template_switch.h b/esphome/components/template/switch/template_switch.h index 0fba66b9bd..18a374df35 100644 --- a/esphome/components/template/switch/template_switch.h +++ b/esphome/components/template/switch/template_switch.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/template_lambda.h" #include "esphome/components/switch/switch.h" namespace esphome { @@ -14,7 +15,7 @@ class TemplateSwitch : public switch_::Switch, public Component { void setup() override; void dump_config() override; - void set_state_lambda(optional (*f)()); + template void set_state_lambda(F &&f) { this->f_.set(std::forward(f)); } Trigger<> *get_turn_on_trigger() const; Trigger<> *get_turn_off_trigger() const; void set_optimistic(bool optimistic); @@ -28,7 +29,7 @@ class TemplateSwitch : public switch_::Switch, public Component { void write_state(bool state) override; - optional (*)()> f_; + TemplateLambda f_; bool optimistic_{false}; bool assumed_state_{false}; Trigger<> *turn_on_trigger_; diff --git a/esphome/components/template/text/template_text.cpp b/esphome/components/template/text/template_text.cpp index d8e840ba7e..a917c72a14 100644 --- a/esphome/components/template/text/template_text.cpp +++ b/esphome/components/template/text/template_text.cpp @@ -7,10 +7,8 @@ namespace template_ { static const char *const TAG = "template.text"; void TemplateText::setup() { - if (!(this->f_ == nullptr)) { - if (this->f_.has_value()) - return; - } + if (this->f_.has_value()) + return; std::string value = this->initial_value_; if (!this->pref_) { ESP_LOGD(TAG, "State from initial: %s", value.c_str()); @@ -26,17 +24,13 @@ void TemplateText::setup() { } void TemplateText::update() { - if (this->f_ == nullptr) - return; - if (!this->f_.has_value()) return; - auto val = (*this->f_)(); - if (!val.has_value()) - return; - - this->publish_state(*val); + auto val = this->f_(); + if (val.has_value()) { + this->publish_state(*val); + } } void TemplateText::control(const std::string &value) { diff --git a/esphome/components/template/text/template_text.h b/esphome/components/template/text/template_text.h index 6c17d2016a..c12021f80e 100644 --- a/esphome/components/template/text/template_text.h +++ b/esphome/components/template/text/template_text.h @@ -4,6 +4,7 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/preferences.h" +#include "esphome/core/template_lambda.h" namespace esphome { namespace template_ { @@ -61,7 +62,7 @@ template class TextSaver : public TemplateTextSaverBase { class TemplateText : public text::Text, public PollingComponent { public: - void set_template(optional (*f)()) { this->f_ = f; } + template void set_template(F &&f) { this->f_.set(std::forward(f)); } void setup() override; void update() override; @@ -78,7 +79,7 @@ class TemplateText : public text::Text, public PollingComponent { bool optimistic_ = false; std::string initial_value_; Trigger *set_trigger_ = new Trigger(); - optional (*)()> f_{nullptr}; + TemplateLambda f_{}; TemplateTextSaverBase *pref_ = nullptr; }; diff --git a/esphome/components/template/text_sensor/template_text_sensor.cpp b/esphome/components/template/text_sensor/template_text_sensor.cpp index 2b0297d62f..024d0093a2 100644 --- a/esphome/components/template/text_sensor/template_text_sensor.cpp +++ b/esphome/components/template/text_sensor/template_text_sensor.cpp @@ -10,13 +10,14 @@ void TemplateTextSensor::update() { if (!this->f_.has_value()) return; - auto val = (*this->f_)(); + auto val = this->f_(); if (val.has_value()) { this->publish_state(*val); } } + float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; } -void TemplateTextSensor::set_template(optional (*f)()) { this->f_ = f; } + void TemplateTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Template Sensor", this); } } // namespace template_ diff --git a/esphome/components/template/text_sensor/template_text_sensor.h b/esphome/components/template/text_sensor/template_text_sensor.h index 48e40c2493..0d01c72023 100644 --- a/esphome/components/template/text_sensor/template_text_sensor.h +++ b/esphome/components/template/text_sensor/template_text_sensor.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/template_lambda.h" #include "esphome/components/text_sensor/text_sensor.h" namespace esphome { @@ -9,7 +10,7 @@ namespace template_ { class TemplateTextSensor : public text_sensor::TextSensor, public PollingComponent { public: - void set_template(optional (*f)()); + template void set_template(F &&f) { this->f_.set(std::forward(f)); } void update() override; @@ -18,7 +19,7 @@ class TemplateTextSensor : public text_sensor::TextSensor, public PollingCompone void dump_config() override; protected: - optional (*)()> f_{}; + TemplateLambda f_{}; }; } // namespace template_ diff --git a/esphome/components/template/valve/template_valve.cpp b/esphome/components/template/valve/template_valve.cpp index b27cc00968..b91b32473e 100644 --- a/esphome/components/template/valve/template_valve.cpp +++ b/esphome/components/template/valve/template_valve.cpp @@ -33,19 +33,19 @@ void TemplateValve::setup() { break; } } + if (!this->state_f_.has_value()) + this->disable_loop(); } void TemplateValve::loop() { bool changed = false; - if (this->state_f_.has_value()) { - auto s = (*this->state_f_)(); - if (s.has_value()) { - auto pos = clamp(*s, 0.0f, 1.0f); - if (pos != this->position) { - this->position = pos; - changed = true; - } + auto s = this->state_f_(); + if (s.has_value()) { + auto pos = clamp(*s, 0.0f, 1.0f); + if (pos != this->position) { + this->position = pos; + changed = true; } } @@ -55,7 +55,6 @@ void TemplateValve::loop() { void TemplateValve::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void TemplateValve::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } -void TemplateValve::set_state_lambda(optional (*f)()) { this->state_f_ = f; } float TemplateValve::get_setup_priority() const { return setup_priority::HARDWARE; } Trigger<> *TemplateValve::get_open_trigger() const { return this->open_trigger_; } diff --git a/esphome/components/template/valve/template_valve.h b/esphome/components/template/valve/template_valve.h index 92c32f3487..d6235f8e5c 100644 --- a/esphome/components/template/valve/template_valve.h +++ b/esphome/components/template/valve/template_valve.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/template_lambda.h" #include "esphome/components/valve/valve.h" namespace esphome { @@ -17,7 +18,7 @@ class TemplateValve : public valve::Valve, public Component { public: TemplateValve(); - void set_state_lambda(optional (*f)()); + template void set_state_lambda(F &&f) { this->state_f_.set(std::forward(f)); } Trigger<> *get_open_trigger() const; Trigger<> *get_close_trigger() const; Trigger<> *get_stop_trigger() const; @@ -42,7 +43,7 @@ class TemplateValve : public valve::Valve, public Component { void stop_prev_trigger_(); TemplateValveRestoreMode restore_mode_{VALVE_NO_RESTORE}; - optional (*)()> state_f_; + TemplateLambda state_f_; bool assumed_state_{false}; bool optimistic_{false}; Trigger<> *open_trigger_; diff --git a/esphome/core/template_lambda.h b/esphome/core/template_lambda.h new file mode 100644 index 0000000000..7b8f4374aa --- /dev/null +++ b/esphome/core/template_lambda.h @@ -0,0 +1,51 @@ +#pragma once + +#include "esphome/core/optional.h" + +namespace esphome { + +/** Lightweight wrapper for template platform lambdas (stateless function pointers only). + * + * This optimizes template platforms by storing only a function pointer (4 bytes on ESP32) + * instead of std::function (16-32 bytes). + * + * IMPORTANT: This only supports stateless lambdas (no captures). The set_template() method + * is an internal API used by YAML codegen, not intended for external use. + * + * Lambdas must return optional to support the pattern: + * return {}; // Don't publish a value + * return 42.0; // Publish this value + * + * operator() returns optional, returning nullopt when no lambda is set (nullptr check). + * + * @tparam T The return type (e.g., float for sensor values) + * @tparam Args Optional arguments for the lambda + */ +template class TemplateLambda { + public: + TemplateLambda() : f_(nullptr) {} + + /** Set the lambda function pointer. + * INTERNAL API: Only for use by YAML codegen. + * Only stateless lambdas (no captures) are supported. + */ + void set(optional (*f)(Args...)) { this->f_ = f; } + + /** Check if a lambda is set */ + bool has_value() const { return this->f_ != nullptr; } + + /** Call the lambda, returning nullopt if no lambda is set */ + optional operator()(Args &&...args) { + if (this->f_ == nullptr) + return nullopt; + return this->f_(std::forward(args)...); + } + + /** Alias for operator() for compatibility */ + optional call(Args &&...args) { return (*this)(std::forward(args)...); } + + protected: + optional (*f_)(Args...); // Function pointer (4 bytes on ESP32) +}; + +} // namespace esphome diff --git a/tests/components/template/common-base.yaml b/tests/components/template/common-base.yaml index b873af5207..f101eea942 100644 --- a/tests/components/template/common-base.yaml +++ b/tests/components/template/common-base.yaml @@ -9,6 +9,13 @@ esphome: id: template_sens state: !lambda "return 42.0;" + # Test C++ API: set_template() with stateless lambda (no captures) + # NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break. + - lambda: |- + id(template_sens).set_template([]() -> esphome::optional { + return 123.0f; + }); + - datetime.date.set: id: test_date date: @@ -215,6 +222,7 @@ cover: number: - platform: template + id: template_number name: "Template number" optimistic: true min_value: 0 diff --git a/tests/integration/fixtures/runtime_stats.yaml b/tests/integration/fixtures/runtime_stats.yaml index aad1c275fb..fd34cdb939 100644 --- a/tests/integration/fixtures/runtime_stats.yaml +++ b/tests/integration/fixtures/runtime_stats.yaml @@ -32,6 +32,7 @@ switch: name: "Test Switch" id: test_switch optimistic: true + lambda: return false; interval: - interval: 0.5s From bd87e56bc7bbdb2a1d7178536bd9f1c5b9232d45 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Oct 2025 21:14:03 -0500 Subject: [PATCH 0010/1145] [e131] Replace std::set with std::vector to reduce flash usage (#11598) --- esphome/components/e131/e131.cpp | 13 +++++++++---- esphome/components/e131/e131.h | 4 +--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/esphome/components/e131/e131.cpp b/esphome/components/e131/e131.cpp index d18d945cec..c10c88faf2 100644 --- a/esphome/components/e131/e131.cpp +++ b/esphome/components/e131/e131.cpp @@ -3,6 +3,8 @@ #include "e131_addressable_light_effect.h" #include "esphome/core/log.h" +#include + namespace esphome { namespace e131 { @@ -76,14 +78,14 @@ void E131Component::loop() { } void E131Component::add_effect(E131AddressableLightEffect *light_effect) { - if (light_effects_.count(light_effect)) { + if (std::find(light_effects_.begin(), light_effects_.end(), light_effect) != light_effects_.end()) { return; } ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(), light_effect->get_last_universe()); - light_effects_.insert(light_effect); + light_effects_.push_back(light_effect); for (auto universe = light_effect->get_first_universe(); universe <= light_effect->get_last_universe(); ++universe) { join_(universe); @@ -91,14 +93,17 @@ void E131Component::add_effect(E131AddressableLightEffect *light_effect) { } void E131Component::remove_effect(E131AddressableLightEffect *light_effect) { - if (!light_effects_.count(light_effect)) { + auto it = std::find(light_effects_.begin(), light_effects_.end(), light_effect); + if (it == light_effects_.end()) { return; } ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(), light_effect->get_last_universe()); - light_effects_.erase(light_effect); + // Swap with last element and pop for O(1) removal (order doesn't matter) + *it = light_effects_.back(); + light_effects_.pop_back(); for (auto universe = light_effect->get_first_universe(); universe <= light_effect->get_last_universe(); ++universe) { leave_(universe); diff --git a/esphome/components/e131/e131.h b/esphome/components/e131/e131.h index d0e38fa98c..831138a545 100644 --- a/esphome/components/e131/e131.h +++ b/esphome/components/e131/e131.h @@ -7,7 +7,6 @@ #include #include #include -#include #include namespace esphome { @@ -47,9 +46,8 @@ class E131Component : public esphome::Component { E131ListenMethod listen_method_{E131_MULTICAST}; std::unique_ptr socket_; - std::set light_effects_; + std::vector light_effects_; std::map universe_consumers_; - std::map universe_packets_; }; } // namespace e131 From 077cce9848303422581a0616b875d53044369a8c Mon Sep 17 00:00:00 2001 From: Markus <974709+Links2004@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:08:08 +0100 Subject: [PATCH 0011/1145] [core] .local addresses are only resolvable if mDNS is enabled (#11508) --- esphome/__main__.py | 16 +++++++++++++--- tests/unit_tests/test_main.py | 6 +++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 26e5ae7424..b0c081a34f 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -207,14 +207,14 @@ def choose_upload_log_host( if has_mqtt_logging(): resolved.append("MQTT") - if has_api() and has_non_ip_address(): + if has_api() and has_non_ip_address() and has_resolvable_address(): resolved.extend(_resolve_with_cache(CORE.address, purpose)) elif purpose == Purpose.UPLOADING: if has_ota() and has_mqtt_ip_lookup(): resolved.append("MQTTIP") - if has_ota() and has_non_ip_address(): + if has_ota() and has_non_ip_address() and has_resolvable_address(): resolved.extend(_resolve_with_cache(CORE.address, purpose)) else: resolved.append(device) @@ -318,7 +318,17 @@ def has_resolvable_address() -> bool: """Check if CORE.address is resolvable (via mDNS, DNS, or is an IP address).""" # Any address (IP, mDNS hostname, or regular DNS hostname) is resolvable # The resolve_ip_address() function in helpers.py handles all types via AsyncResolver - return CORE.address is not None + if CORE.address is None: + return False + + if has_ip_address(): + return True + + if has_mdns(): + return True + + # .local mDNS hostnames are only resolvable if mDNS is enabled + return not CORE.address.endswith(".local") def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str): diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index 9119c88502..9e5f399381 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -744,7 +744,7 @@ def test_choose_upload_log_host_ota_local_all_options() -> None: check_default=None, purpose=Purpose.UPLOADING, ) - assert result == ["MQTTIP", "test.local"] + assert result == ["MQTTIP"] @pytest.mark.usefixtures("mock_serial_ports") @@ -794,7 +794,7 @@ def test_choose_upload_log_host_ota_local_all_options_logging() -> None: check_default=None, purpose=Purpose.LOGGING, ) - assert result == ["MQTTIP", "MQTT", "test.local"] + assert result == ["MQTTIP", "MQTT"] @pytest.mark.usefixtures("mock_no_mqtt_logging") @@ -1564,7 +1564,7 @@ def test_has_resolvable_address() -> None: setup_core( config={CONF_MDNS: {CONF_DISABLED: True}}, address="esphome-device.local" ) - assert has_resolvable_address() is True + assert has_resolvable_address() is False # Test with mDNS disabled and regular DNS hostname (resolvable) setup_core(config={CONF_MDNS: {CONF_DISABLED: True}}, address="device.example.com") From fd64585f99fe47e749606823231020915d6fa75e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:50:06 -0500 Subject: [PATCH 0012/1145] Bump github/codeql-action from 4.31.0 to 4.31.2 (#11626) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6b940eed8a..ab938b3436 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0 + uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -86,6 +86,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0 + uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 with: category: "/language:${{matrix.language}}" From 6d0527ff2ad406603067a939d0b3f703db33fc0c Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Fri, 31 Oct 2025 20:04:55 +0100 Subject: [PATCH 0013/1145] [substitutions] fix jinja parsing strings that look like sets as sets (#11611) --- esphome/components/substitutions/jinja.py | 10 ++++++++-- .../fixtures/substitutions/00-simple_var.approved.yaml | 1 + .../fixtures/substitutions/00-simple_var.input.yaml | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/esphome/components/substitutions/jinja.py b/esphome/components/substitutions/jinja.py index cb3c6dfac5..fb9f843da2 100644 --- a/esphome/components/substitutions/jinja.py +++ b/esphome/components/substitutions/jinja.py @@ -138,6 +138,7 @@ def _concat_nodes_override(values: Iterator[Any]) -> Any: values = chain(head, values) raw = "".join([str(v) for v in values]) + result = None try: # Attempt to parse the concatenated string into a Python literal. # This allows expressions like "1 + 2" to be evaluated to the integer 3. @@ -145,11 +146,16 @@ def _concat_nodes_override(values: Iterator[Any]) -> Any: # fall back to returning the raw string. This is consistent with # Home Assistant's behavior when evaluating templates result = literal_eval(raw) + except (ValueError, SyntaxError, MemoryError, TypeError): + pass + else: + if isinstance(result, set): + # Sets are not supported, return raw string + return raw + if not isinstance(result, str): return result - except (ValueError, SyntaxError, MemoryError, TypeError): - pass return raw diff --git a/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml b/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml index 795a788f62..6f3bae1ac4 100644 --- a/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml +++ b/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml @@ -33,3 +33,4 @@ test_list: {{{ "x", "79"}, { "y", "82"}}} - '{{{"AA"}}}' - '"HELLO"' + - '{ 79, 82 }' diff --git a/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml b/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml index 722e116d36..306119b753 100644 --- a/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml +++ b/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml @@ -34,3 +34,4 @@ test_list: {{{ "x", "${ position.x }"}, { "y", "${ position.y }"}}} - ${ '{{{"AA"}}}' } - ${ '"HELLO"' } + - '{ ${position.x}, ${position.y} }' From 292abd1187f8de0b4941e26974e05581177abfe0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 19:46:50 +0000 Subject: [PATCH 0014/1145] Bump ruff from 0.14.2 to 0.14.3 (#11633) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f7e4a688e0..5356bffd96 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.2 + rev: v0.14.3 hooks: # Run the linter. - id: ruff diff --git a/requirements_test.txt b/requirements_test.txt index a11992b0fd..11367172b1 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==4.0.2 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.14.2 # also change in .pre-commit-config.yaml when updating +ruff==0.14.3 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.0 # also change in .pre-commit-config.yaml when updating pre-commit From 30f2a4395fd737bb723c1f82a56ec6f0ba4e41a0 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sat, 1 Nov 2025 10:08:28 +1000 Subject: [PATCH 0015/1145] [image] Catch and report svg load errors (#11619) --- esphome/components/image/__init__.py | 31 +++++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index f880b5f736..bf25a7cd92 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -671,18 +671,33 @@ async def write_image(config, all_frames=False): resize = config.get(CONF_RESIZE) if is_svg_file(path): # Local import so use of non-SVG files needn't require cairosvg installed + from pyexpat import ExpatError + from xml.etree.ElementTree import ParseError + from cairosvg import svg2png + from cairosvg.helpers import PointError if not resize: resize = (None, None) - with open(path, "rb") as file: - image = svg2png( - file_obj=file, - output_width=resize[0], - output_height=resize[1], - ) - image = Image.open(io.BytesIO(image)) - width, height = image.size + try: + with open(path, "rb") as file: + image = svg2png( + file_obj=file, + output_width=resize[0], + output_height=resize[1], + ) + image = Image.open(io.BytesIO(image)) + width, height = image.size + except ( + ValueError, + ParseError, + IndexError, + ExpatError, + AttributeError, + TypeError, + PointError, + ) as e: + raise core.EsphomeError(f"Could not load SVG image {path}: {e}") from e else: image = Image.open(path) width, height = image.size From d9d2d2f6b936369324ce271a509efa88e48aff1c Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sat, 1 Nov 2025 14:17:23 +1000 Subject: [PATCH 0016/1145] [automations] Update error message (#11640) --- esphome/automation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/automation.py b/esphome/automation.py index cfe0af1b59..99be12451e 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -182,7 +182,7 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False): value = cv.Schema([extra_validators])(value) if single: if len(value) != 1: - raise cv.Invalid("Cannot have more than 1 automation for templates") + raise cv.Invalid("This trigger allows only a single automation") return value[0] return value From 5a5894eaa3049a174f44c9af185bcd3c080cae5d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 1 Nov 2025 01:05:26 -0500 Subject: [PATCH 0017/1145] [ruff] Remove deprecated UP038 rule from ignore list (#11646) --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 49598d434d..d6aa584237 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,6 @@ ignore = [ "PLW1641", # Object does not implement `__hash__` method "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable "PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target - "UP038", # https://github.com/astral-sh/ruff/issues/7871 https://github.com/astral-sh/ruff/pull/16681 ] [tool.ruff.lint.isort] From 8df5a3a6308a1b01c0a4137a7b68f0769d55e2a5 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:27:28 +1000 Subject: [PATCH 0018/1145] [lvgl] Trigger improvements and additions (#11628) --- esphome/components/lvgl/__init__.py | 52 +++++++++++++---------- esphome/components/lvgl/automation.py | 3 +- esphome/components/lvgl/defines.py | 2 + esphome/components/lvgl/lvgl_esphome.cpp | 33 ++++++++++---- esphome/components/lvgl/lvgl_esphome.h | 27 +++++++----- esphome/components/lvgl/types.py | 6 ++- tests/components/lvgl/test.esp32-idf.yaml | 8 ++++ 7 files changed, 86 insertions(+), 45 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 5af61300da..aa6935c5fc 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -58,7 +58,7 @@ from .types import ( FontEngine, IdleTrigger, ObjUpdateAction, - PauseTrigger, + PlainTrigger, lv_font_t, lv_group_t, lv_style_t, @@ -151,6 +151,13 @@ for w_type in WIDGET_TYPES.values(): create_modify_schema(w_type), )(update_to_code) +SIMPLE_TRIGGERS = ( + df.CONF_ON_PAUSE, + df.CONF_ON_RESUME, + df.CONF_ON_DRAW_START, + df.CONF_ON_DRAW_END, +) + def as_macro(macro, value): if value is None: @@ -244,9 +251,9 @@ def final_validation(configs): for w in refreshed_widgets: path = global_config.get_path_for_id(w) widget_conf = global_config.get_config_for_path(path[:-1]) - if not any(isinstance(v, Lambda) for v in widget_conf.values()): + if not any(isinstance(v, (Lambda, dict)) for v in widget_conf.values()): raise cv.Invalid( - f"Widget '{w}' does not have any templated properties to refresh", + f"Widget '{w}' does not have any dynamic properties to refresh", ) @@ -366,16 +373,16 @@ async def to_code(configs): conf[CONF_TRIGGER_ID], lv_component, templ ) await build_automation(idle_trigger, [], conf) - for conf in config.get(df.CONF_ON_PAUSE, ()): - pause_trigger = cg.new_Pvariable( - conf[CONF_TRIGGER_ID], lv_component, True - ) - await build_automation(pause_trigger, [], conf) - for conf in config.get(df.CONF_ON_RESUME, ()): - resume_trigger = cg.new_Pvariable( - conf[CONF_TRIGGER_ID], lv_component, False - ) - await build_automation(resume_trigger, [], conf) + for trigger_name in SIMPLE_TRIGGERS: + if conf := config.get(trigger_name): + trigger_var = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + await build_automation(trigger_var, [], conf) + cg.add( + getattr( + lv_component, + f"set_{trigger_name.removeprefix('on_')}_trigger", + )(trigger_var) + ) await add_on_boot_triggers(config.get(CONF_ON_BOOT, ())) # This must be done after all widgets are created @@ -443,16 +450,15 @@ LVGL_SCHEMA = cv.All( ), } ), - cv.Optional(df.CONF_ON_PAUSE): validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PauseTrigger), - } - ), - cv.Optional(df.CONF_ON_RESUME): validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PauseTrigger), - } - ), + **{ + cv.Optional(x): validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PlainTrigger), + }, + single=True, + ) + for x in SIMPLE_TRIGGERS + }, cv.Exclusive(df.CONF_WIDGETS, CONF_PAGES): cv.ensure_list( WIDGET_SCHEMA ), diff --git a/esphome/components/lvgl/automation.py b/esphome/components/lvgl/automation.py index fc70b0f682..593c8c67bb 100644 --- a/esphome/components/lvgl/automation.py +++ b/esphome/components/lvgl/automation.py @@ -400,7 +400,8 @@ async def obj_refresh_to_code(config, action_id, template_arg, args): # must pass all widget-specific options here, even if not templated, but only do so if at least one is # templated. First filter out common style properties. config = {k: v for k, v in widget.config.items() if k not in ALL_STYLES} - if any(isinstance(v, Lambda) for v in config.values()): + # Check if v is a Lambda or a dict, implying it is dynamic + if any(isinstance(v, (Lambda, dict)) for v in config.values()): await widget.type.to_code(widget, config) if ( widget.type.w_type.value_property is not None diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 7fbb6de071..3241ba9c3f 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -483,6 +483,8 @@ CONF_MSGBOXES = "msgboxes" CONF_OBJ = "obj" CONF_ONE_CHECKED = "one_checked" CONF_ONE_LINE = "one_line" +CONF_ON_DRAW_START = "on_draw_start" +CONF_ON_DRAW_END = "on_draw_end" CONF_ON_PAUSE = "on_pause" CONF_ON_RESUME = "on_resume" CONF_ON_SELECT = "on_select" diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 7a32691b53..947342089c 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -82,6 +82,18 @@ static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) { area->y2 = (area->y2 + draw_rounding) / draw_rounding * draw_rounding - 1; } +void LvglComponent::monitor_cb(lv_disp_drv_t *disp_drv, uint32_t time, uint32_t px) { + ESP_LOGVV(TAG, "Draw end: %" PRIu32 " pixels in %" PRIu32 " ms", px, time); + auto *comp = static_cast(disp_drv->user_data); + comp->draw_end_(); +} + +void LvglComponent::render_start_cb(lv_disp_drv_t *disp_drv) { + ESP_LOGVV(TAG, "Draw start"); + auto *comp = static_cast(disp_drv->user_data); + comp->draw_start_(); +} + lv_event_code_t lv_api_event; // NOLINT lv_event_code_t lv_update_event; // NOLINT void LvglComponent::dump_config() { @@ -101,7 +113,10 @@ void LvglComponent::set_paused(bool paused, bool show_snow) { lv_disp_trig_activity(this->disp_); // resets the inactivity time lv_obj_invalidate(lv_scr_act()); } - this->pause_callbacks_.call(paused); + if (paused && this->pause_callback_ != nullptr) + this->pause_callback_->trigger(); + if (!paused && this->resume_callback_ != nullptr) + this->resume_callback_->trigger(); } void LvglComponent::esphome_lvgl_init() { @@ -225,13 +240,6 @@ IdleTrigger::IdleTrigger(LvglComponent *parent, TemplatableValue timeo }); } -PauseTrigger::PauseTrigger(LvglComponent *parent, TemplatableValue paused) : paused_(std::move(paused)) { - parent->add_on_pause_callback([this](bool pausing) { - if (this->paused_.value() == pausing) - this->trigger(); - }); -} - #ifdef USE_LVGL_TOUCHSCREEN LVTouchListener::LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time, LvglComponent *parent) { this->set_parent(parent); @@ -474,6 +482,12 @@ void LvglComponent::setup() { return; } } + if (this->draw_start_callback_ != nullptr) { + this->disp_drv_.render_start_cb = render_start_cb; + } + if (this->draw_end_callback_ != nullptr) { + this->disp_drv_.monitor_cb = monitor_cb; + } #if LV_USE_LOG lv_log_register_print_cb([](const char *buf) { auto next = strchr(buf, ')'); @@ -502,8 +516,9 @@ void LvglComponent::loop() { if (this->paused_) { if (this->show_snow_) this->write_random_(); + } else { + lv_timer_handler_run_in_period(5); } - lv_timer_handler_run_in_period(5); } #ifdef USE_LVGL_ANIMIMG diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index d3dc8fac5a..ea58fdb85b 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -171,7 +171,9 @@ class LvglComponent : public PollingComponent { void add_on_idle_callback(std::function &&callback) { this->idle_callbacks_.add(std::move(callback)); } - void add_on_pause_callback(std::function &&callback) { this->pause_callbacks_.add(std::move(callback)); } + + static void monitor_cb(lv_disp_drv_t *disp_drv, uint32_t time, uint32_t px); + static void render_start_cb(lv_disp_drv_t *disp_drv); void dump_config() override; bool is_idle(uint32_t idle_ms) { return lv_disp_get_inactive_time(this->disp_) > idle_ms; } lv_disp_t *get_disp() { return this->disp_; } @@ -213,12 +215,20 @@ class LvglComponent : public PollingComponent { size_t draw_rounding{2}; display::DisplayRotation rotation{display::DISPLAY_ROTATION_0_DEGREES}; + void set_pause_trigger(Trigger<> *trigger) { this->pause_callback_ = trigger; } + void set_resume_trigger(Trigger<> *trigger) { this->resume_callback_ = trigger; } + void set_draw_start_trigger(Trigger<> *trigger) { this->draw_start_callback_ = trigger; } + void set_draw_end_trigger(Trigger<> *trigger) { this->draw_end_callback_ = trigger; } protected: + // these functions are never called unless the callbacks are non-null since the + // LVGL callbacks that call them are not set unless the start/end callbacks are non-null + void draw_start_() const { this->draw_start_callback_->trigger(); } + void draw_end_() const { this->draw_end_callback_->trigger(); } + void write_random_(); void draw_buffer_(const lv_area_t *area, lv_color_t *ptr); void flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p); - std::vector displays_{}; size_t buffer_frac_{1}; bool full_refresh_{}; @@ -235,7 +245,10 @@ class LvglComponent : public PollingComponent { std::map focus_marks_{}; CallbackManager idle_callbacks_{}; - CallbackManager pause_callbacks_{}; + Trigger<> *pause_callback_{}; + Trigger<> *resume_callback_{}; + Trigger<> *draw_start_callback_{}; + Trigger<> *draw_end_callback_{}; lv_color_t *rotate_buf_{}; }; @@ -248,14 +261,6 @@ class IdleTrigger : public Trigger<> { bool is_idle_{}; }; -class PauseTrigger : public Trigger<> { - public: - explicit PauseTrigger(LvglComponent *parent, TemplatableValue paused); - - protected: - TemplatableValue paused_; -}; - template class LvglAction : public Action, public Parented { public: explicit LvglAction(std::function &&lamb) : action_(std::move(lamb)) {} diff --git a/esphome/components/lvgl/types.py b/esphome/components/lvgl/types.py index c19c89401a..9955b530aa 100644 --- a/esphome/components/lvgl/types.py +++ b/esphome/components/lvgl/types.py @@ -3,6 +3,7 @@ import sys from esphome import automation, codegen as cg from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_TEXT, CONF_VALUE from esphome.cpp_generator import MockObj, MockObjClass +from esphome.cpp_types import esphome_ns from .defines import lvgl_ns from .lvcode import lv_expr @@ -42,8 +43,11 @@ lv_event_code_t = cg.global_ns.enum("lv_event_code_t") lv_indev_type_t = cg.global_ns.enum("lv_indev_type_t") lv_key_t = cg.global_ns.enum("lv_key_t") FontEngine = lvgl_ns.class_("FontEngine") +PlainTrigger = esphome_ns.class_("Trigger<>", automation.Trigger.template()) +DrawEndTrigger = esphome_ns.class_( + "Trigger", automation.Trigger.template(cg.uint32, cg.uint32) +) IdleTrigger = lvgl_ns.class_("IdleTrigger", automation.Trigger.template()) -PauseTrigger = lvgl_ns.class_("PauseTrigger", automation.Trigger.template()) ObjUpdateAction = lvgl_ns.class_("ObjUpdateAction", automation.Action) LvglCondition = lvgl_ns.class_("LvglCondition", automation.Condition) LvglAction = lvgl_ns.class_("LvglAction", automation.Action) diff --git a/tests/components/lvgl/test.esp32-idf.yaml b/tests/components/lvgl/test.esp32-idf.yaml index 6170b0f4fb..2450d28eb8 100644 --- a/tests/components/lvgl/test.esp32-idf.yaml +++ b/tests/components/lvgl/test.esp32-idf.yaml @@ -68,5 +68,13 @@ lvgl: enter_button: pushbutton group: general initial_focus: lv_roller + on_draw_start: + - logger.log: draw started + on_draw_end: + - logger.log: draw ended + - lvgl.pause: + - component.update: tft_display + - delay: 60s + - lvgl.resume: <<: !include common.yaml From 4d1d37a9112a6b54467722531438349d672d0f5a Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:37:07 +1000 Subject: [PATCH 0019/1145] [lvgl] Fix event for binary sensor (#11636) --- esphome/components/lvgl/binary_sensor/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/lvgl/binary_sensor/__init__.py b/esphome/components/lvgl/binary_sensor/__init__.py index ffbdc977b2..f9df7d23fa 100644 --- a/esphome/components/lvgl/binary_sensor/__init__.py +++ b/esphome/components/lvgl/binary_sensor/__init__.py @@ -31,7 +31,7 @@ async def to_code(config): lvgl_static.add_event_cb( widget.obj, await pressed_ctx.get_lambda(), - LV_EVENT.PRESSING, + LV_EVENT.PRESSED, LV_EVENT.RELEASED, ) ) From 0b4d445794f1d804b072d73d9ae0952139a48524 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:45:42 +1000 Subject: [PATCH 0020/1145] [sdl] Fix keymappings (#11635) --- esphome/components/sdl/binary_sensor.py | 485 ++++++++++++------------ tests/components/sdl/common.yaml | 6 +- 2 files changed, 253 insertions(+), 238 deletions(-) diff --git a/esphome/components/sdl/binary_sensor.py b/esphome/components/sdl/binary_sensor.py index 3ea6c2d218..e19a488800 100644 --- a/esphome/components/sdl/binary_sensor.py +++ b/esphome/components/sdl/binary_sensor.py @@ -12,241 +12,256 @@ CODEOWNERS = ["@bdm310"] STATE_ARG = "state" -SDL_KEYMAP = { - "SDLK_UNKNOWN": 0, - "SDLK_FIRST": 0, - "SDLK_BACKSPACE": 8, - "SDLK_TAB": 9, - "SDLK_CLEAR": 12, - "SDLK_RETURN": 13, - "SDLK_PAUSE": 19, - "SDLK_ESCAPE": 27, - "SDLK_SPACE": 32, - "SDLK_EXCLAIM": 33, - "SDLK_QUOTEDBL": 34, - "SDLK_HASH": 35, - "SDLK_DOLLAR": 36, - "SDLK_AMPERSAND": 38, - "SDLK_QUOTE": 39, - "SDLK_LEFTPAREN": 40, - "SDLK_RIGHTPAREN": 41, - "SDLK_ASTERISK": 42, - "SDLK_PLUS": 43, - "SDLK_COMMA": 44, - "SDLK_MINUS": 45, - "SDLK_PERIOD": 46, - "SDLK_SLASH": 47, - "SDLK_0": 48, - "SDLK_1": 49, - "SDLK_2": 50, - "SDLK_3": 51, - "SDLK_4": 52, - "SDLK_5": 53, - "SDLK_6": 54, - "SDLK_7": 55, - "SDLK_8": 56, - "SDLK_9": 57, - "SDLK_COLON": 58, - "SDLK_SEMICOLON": 59, - "SDLK_LESS": 60, - "SDLK_EQUALS": 61, - "SDLK_GREATER": 62, - "SDLK_QUESTION": 63, - "SDLK_AT": 64, - "SDLK_LEFTBRACKET": 91, - "SDLK_BACKSLASH": 92, - "SDLK_RIGHTBRACKET": 93, - "SDLK_CARET": 94, - "SDLK_UNDERSCORE": 95, - "SDLK_BACKQUOTE": 96, - "SDLK_a": 97, - "SDLK_b": 98, - "SDLK_c": 99, - "SDLK_d": 100, - "SDLK_e": 101, - "SDLK_f": 102, - "SDLK_g": 103, - "SDLK_h": 104, - "SDLK_i": 105, - "SDLK_j": 106, - "SDLK_k": 107, - "SDLK_l": 108, - "SDLK_m": 109, - "SDLK_n": 110, - "SDLK_o": 111, - "SDLK_p": 112, - "SDLK_q": 113, - "SDLK_r": 114, - "SDLK_s": 115, - "SDLK_t": 116, - "SDLK_u": 117, - "SDLK_v": 118, - "SDLK_w": 119, - "SDLK_x": 120, - "SDLK_y": 121, - "SDLK_z": 122, - "SDLK_DELETE": 127, - "SDLK_WORLD_0": 160, - "SDLK_WORLD_1": 161, - "SDLK_WORLD_2": 162, - "SDLK_WORLD_3": 163, - "SDLK_WORLD_4": 164, - "SDLK_WORLD_5": 165, - "SDLK_WORLD_6": 166, - "SDLK_WORLD_7": 167, - "SDLK_WORLD_8": 168, - "SDLK_WORLD_9": 169, - "SDLK_WORLD_10": 170, - "SDLK_WORLD_11": 171, - "SDLK_WORLD_12": 172, - "SDLK_WORLD_13": 173, - "SDLK_WORLD_14": 174, - "SDLK_WORLD_15": 175, - "SDLK_WORLD_16": 176, - "SDLK_WORLD_17": 177, - "SDLK_WORLD_18": 178, - "SDLK_WORLD_19": 179, - "SDLK_WORLD_20": 180, - "SDLK_WORLD_21": 181, - "SDLK_WORLD_22": 182, - "SDLK_WORLD_23": 183, - "SDLK_WORLD_24": 184, - "SDLK_WORLD_25": 185, - "SDLK_WORLD_26": 186, - "SDLK_WORLD_27": 187, - "SDLK_WORLD_28": 188, - "SDLK_WORLD_29": 189, - "SDLK_WORLD_30": 190, - "SDLK_WORLD_31": 191, - "SDLK_WORLD_32": 192, - "SDLK_WORLD_33": 193, - "SDLK_WORLD_34": 194, - "SDLK_WORLD_35": 195, - "SDLK_WORLD_36": 196, - "SDLK_WORLD_37": 197, - "SDLK_WORLD_38": 198, - "SDLK_WORLD_39": 199, - "SDLK_WORLD_40": 200, - "SDLK_WORLD_41": 201, - "SDLK_WORLD_42": 202, - "SDLK_WORLD_43": 203, - "SDLK_WORLD_44": 204, - "SDLK_WORLD_45": 205, - "SDLK_WORLD_46": 206, - "SDLK_WORLD_47": 207, - "SDLK_WORLD_48": 208, - "SDLK_WORLD_49": 209, - "SDLK_WORLD_50": 210, - "SDLK_WORLD_51": 211, - "SDLK_WORLD_52": 212, - "SDLK_WORLD_53": 213, - "SDLK_WORLD_54": 214, - "SDLK_WORLD_55": 215, - "SDLK_WORLD_56": 216, - "SDLK_WORLD_57": 217, - "SDLK_WORLD_58": 218, - "SDLK_WORLD_59": 219, - "SDLK_WORLD_60": 220, - "SDLK_WORLD_61": 221, - "SDLK_WORLD_62": 222, - "SDLK_WORLD_63": 223, - "SDLK_WORLD_64": 224, - "SDLK_WORLD_65": 225, - "SDLK_WORLD_66": 226, - "SDLK_WORLD_67": 227, - "SDLK_WORLD_68": 228, - "SDLK_WORLD_69": 229, - "SDLK_WORLD_70": 230, - "SDLK_WORLD_71": 231, - "SDLK_WORLD_72": 232, - "SDLK_WORLD_73": 233, - "SDLK_WORLD_74": 234, - "SDLK_WORLD_75": 235, - "SDLK_WORLD_76": 236, - "SDLK_WORLD_77": 237, - "SDLK_WORLD_78": 238, - "SDLK_WORLD_79": 239, - "SDLK_WORLD_80": 240, - "SDLK_WORLD_81": 241, - "SDLK_WORLD_82": 242, - "SDLK_WORLD_83": 243, - "SDLK_WORLD_84": 244, - "SDLK_WORLD_85": 245, - "SDLK_WORLD_86": 246, - "SDLK_WORLD_87": 247, - "SDLK_WORLD_88": 248, - "SDLK_WORLD_89": 249, - "SDLK_WORLD_90": 250, - "SDLK_WORLD_91": 251, - "SDLK_WORLD_92": 252, - "SDLK_WORLD_93": 253, - "SDLK_WORLD_94": 254, - "SDLK_WORLD_95": 255, - "SDLK_KP0": 256, - "SDLK_KP1": 257, - "SDLK_KP2": 258, - "SDLK_KP3": 259, - "SDLK_KP4": 260, - "SDLK_KP5": 261, - "SDLK_KP6": 262, - "SDLK_KP7": 263, - "SDLK_KP8": 264, - "SDLK_KP9": 265, - "SDLK_KP_PERIOD": 266, - "SDLK_KP_DIVIDE": 267, - "SDLK_KP_MULTIPLY": 268, - "SDLK_KP_MINUS": 269, - "SDLK_KP_PLUS": 270, - "SDLK_KP_ENTER": 271, - "SDLK_KP_EQUALS": 272, - "SDLK_UP": 273, - "SDLK_DOWN": 274, - "SDLK_RIGHT": 275, - "SDLK_LEFT": 276, - "SDLK_INSERT": 277, - "SDLK_HOME": 278, - "SDLK_END": 279, - "SDLK_PAGEUP": 280, - "SDLK_PAGEDOWN": 281, - "SDLK_F1": 282, - "SDLK_F2": 283, - "SDLK_F3": 284, - "SDLK_F4": 285, - "SDLK_F5": 286, - "SDLK_F6": 287, - "SDLK_F7": 288, - "SDLK_F8": 289, - "SDLK_F9": 290, - "SDLK_F10": 291, - "SDLK_F11": 292, - "SDLK_F12": 293, - "SDLK_F13": 294, - "SDLK_F14": 295, - "SDLK_F15": 296, - "SDLK_NUMLOCK": 300, - "SDLK_CAPSLOCK": 301, - "SDLK_SCROLLOCK": 302, - "SDLK_RSHIFT": 303, - "SDLK_LSHIFT": 304, - "SDLK_RCTRL": 305, - "SDLK_LCTRL": 306, - "SDLK_RALT": 307, - "SDLK_LALT": 308, - "SDLK_RMETA": 309, - "SDLK_LMETA": 310, - "SDLK_LSUPER": 311, - "SDLK_RSUPER": 312, - "SDLK_MODE": 313, - "SDLK_COMPOSE": 314, - "SDLK_HELP": 315, - "SDLK_PRINT": 316, - "SDLK_SYSREQ": 317, - "SDLK_BREAK": 318, - "SDLK_MENU": 319, - "SDLK_POWER": 320, - "SDLK_EURO": 321, - "SDLK_UNDO": 322, -} +SDL_KeyCode = cg.global_ns.enum("SDL_KeyCode") + +SDL_KEYS = ( + "SDLK_UNKNOWN", + "SDLK_RETURN", + "SDLK_ESCAPE", + "SDLK_BACKSPACE", + "SDLK_TAB", + "SDLK_SPACE", + "SDLK_EXCLAIM", + "SDLK_QUOTEDBL", + "SDLK_HASH", + "SDLK_PERCENT", + "SDLK_DOLLAR", + "SDLK_AMPERSAND", + "SDLK_QUOTE", + "SDLK_LEFTPAREN", + "SDLK_RIGHTPAREN", + "SDLK_ASTERISK", + "SDLK_PLUS", + "SDLK_COMMA", + "SDLK_MINUS", + "SDLK_PERIOD", + "SDLK_SLASH", + "SDLK_0", + "SDLK_1", + "SDLK_2", + "SDLK_3", + "SDLK_4", + "SDLK_5", + "SDLK_6", + "SDLK_7", + "SDLK_8", + "SDLK_9", + "SDLK_COLON", + "SDLK_SEMICOLON", + "SDLK_LESS", + "SDLK_EQUALS", + "SDLK_GREATER", + "SDLK_QUESTION", + "SDLK_AT", + "SDLK_LEFTBRACKET", + "SDLK_BACKSLASH", + "SDLK_RIGHTBRACKET", + "SDLK_CARET", + "SDLK_UNDERSCORE", + "SDLK_BACKQUOTE", + "SDLK_a", + "SDLK_b", + "SDLK_c", + "SDLK_d", + "SDLK_e", + "SDLK_f", + "SDLK_g", + "SDLK_h", + "SDLK_i", + "SDLK_j", + "SDLK_k", + "SDLK_l", + "SDLK_m", + "SDLK_n", + "SDLK_o", + "SDLK_p", + "SDLK_q", + "SDLK_r", + "SDLK_s", + "SDLK_t", + "SDLK_u", + "SDLK_v", + "SDLK_w", + "SDLK_x", + "SDLK_y", + "SDLK_z", + "SDLK_CAPSLOCK", + "SDLK_F1", + "SDLK_F2", + "SDLK_F3", + "SDLK_F4", + "SDLK_F5", + "SDLK_F6", + "SDLK_F7", + "SDLK_F8", + "SDLK_F9", + "SDLK_F10", + "SDLK_F11", + "SDLK_F12", + "SDLK_PRINTSCREEN", + "SDLK_SCROLLLOCK", + "SDLK_PAUSE", + "SDLK_INSERT", + "SDLK_HOME", + "SDLK_PAGEUP", + "SDLK_DELETE", + "SDLK_END", + "SDLK_PAGEDOWN", + "SDLK_RIGHT", + "SDLK_LEFT", + "SDLK_DOWN", + "SDLK_UP", + "SDLK_NUMLOCKCLEAR", + "SDLK_KP_DIVIDE", + "SDLK_KP_MULTIPLY", + "SDLK_KP_MINUS", + "SDLK_KP_PLUS", + "SDLK_KP_ENTER", + "SDLK_KP_1", + "SDLK_KP_2", + "SDLK_KP_3", + "SDLK_KP_4", + "SDLK_KP_5", + "SDLK_KP_6", + "SDLK_KP_7", + "SDLK_KP_8", + "SDLK_KP_9", + "SDLK_KP_0", + "SDLK_KP_PERIOD", + "SDLK_APPLICATION", + "SDLK_POWER", + "SDLK_KP_EQUALS", + "SDLK_F13", + "SDLK_F14", + "SDLK_F15", + "SDLK_F16", + "SDLK_F17", + "SDLK_F18", + "SDLK_F19", + "SDLK_F20", + "SDLK_F21", + "SDLK_F22", + "SDLK_F23", + "SDLK_F24", + "SDLK_EXECUTE", + "SDLK_HELP", + "SDLK_MENU", + "SDLK_SELECT", + "SDLK_STOP", + "SDLK_AGAIN", + "SDLK_UNDO", + "SDLK_CUT", + "SDLK_COPY", + "SDLK_PASTE", + "SDLK_FIND", + "SDLK_MUTE", + "SDLK_VOLUMEUP", + "SDLK_VOLUMEDOWN", + "SDLK_KP_COMMA", + "SDLK_KP_EQUALSAS400", + "SDLK_ALTERASE", + "SDLK_SYSREQ", + "SDLK_CANCEL", + "SDLK_CLEAR", + "SDLK_PRIOR", + "SDLK_RETURN2", + "SDLK_SEPARATOR", + "SDLK_OUT", + "SDLK_OPER", + "SDLK_CLEARAGAIN", + "SDLK_CRSEL", + "SDLK_EXSEL", + "SDLK_KP_00", + "SDLK_KP_000", + "SDLK_THOUSANDSSEPARATOR", + "SDLK_DECIMALSEPARATOR", + "SDLK_CURRENCYUNIT", + "SDLK_CURRENCYSUBUNIT", + "SDLK_KP_LEFTPAREN", + "SDLK_KP_RIGHTPAREN", + "SDLK_KP_LEFTBRACE", + "SDLK_KP_RIGHTBRACE", + "SDLK_KP_TAB", + "SDLK_KP_BACKSPACE", + "SDLK_KP_A", + "SDLK_KP_B", + "SDLK_KP_C", + "SDLK_KP_D", + "SDLK_KP_E", + "SDLK_KP_F", + "SDLK_KP_XOR", + "SDLK_KP_POWER", + "SDLK_KP_PERCENT", + "SDLK_KP_LESS", + "SDLK_KP_GREATER", + "SDLK_KP_AMPERSAND", + "SDLK_KP_DBLAMPERSAND", + "SDLK_KP_VERTICALBAR", + "SDLK_KP_DBLVERTICALBAR", + "SDLK_KP_COLON", + "SDLK_KP_HASH", + "SDLK_KP_SPACE", + "SDLK_KP_AT", + "SDLK_KP_EXCLAM", + "SDLK_KP_MEMSTORE", + "SDLK_KP_MEMRECALL", + "SDLK_KP_MEMCLEAR", + "SDLK_KP_MEMADD", + "SDLK_KP_MEMSUBTRACT", + "SDLK_KP_MEMMULTIPLY", + "SDLK_KP_MEMDIVIDE", + "SDLK_KP_PLUSMINUS", + "SDLK_KP_CLEAR", + "SDLK_KP_CLEARENTRY", + "SDLK_KP_BINARY", + "SDLK_KP_OCTAL", + "SDLK_KP_DECIMAL", + "SDLK_KP_HEXADECIMAL", + "SDLK_LCTRL", + "SDLK_LSHIFT", + "SDLK_LALT", + "SDLK_LGUI", + "SDLK_RCTRL", + "SDLK_RSHIFT", + "SDLK_RALT", + "SDLK_RGUI", + "SDLK_MODE", + "SDLK_AUDIONEXT", + "SDLK_AUDIOPREV", + "SDLK_AUDIOSTOP", + "SDLK_AUDIOPLAY", + "SDLK_AUDIOMUTE", + "SDLK_MEDIASELECT", + "SDLK_WWW", + "SDLK_MAIL", + "SDLK_CALCULATOR", + "SDLK_COMPUTER", + "SDLK_AC_SEARCH", + "SDLK_AC_HOME", + "SDLK_AC_BACK", + "SDLK_AC_FORWARD", + "SDLK_AC_STOP", + "SDLK_AC_REFRESH", + "SDLK_AC_BOOKMARKS", + "SDLK_BRIGHTNESSDOWN", + "SDLK_BRIGHTNESSUP", + "SDLK_DISPLAYSWITCH", + "SDLK_KBDILLUMTOGGLE", + "SDLK_KBDILLUMDOWN", + "SDLK_KBDILLUMUP", + "SDLK_EJECT", + "SDLK_SLEEP", + "SDLK_APP1", + "SDLK_APP2", + "SDLK_AUDIOREWIND", + "SDLK_AUDIOFASTFORWARD", + "SDLK_SOFTLEFT", + "SDLK_SOFTRIGHT", + "SDLK_CALL", + "SDLK_ENDCALL", +) + +SDL_KEYMAP = {key: getattr(SDL_KeyCode, key) for key in SDL_KEYS} CONFIG_SCHEMA = ( binary_sensor.binary_sensor_schema(BinarySensor) diff --git a/tests/components/sdl/common.yaml b/tests/components/sdl/common.yaml index 50fa4a5990..52991d595c 100644 --- a/tests/components/sdl/common.yaml +++ b/tests/components/sdl/common.yaml @@ -14,10 +14,10 @@ display: binary_sensor: - platform: sdl id: key_up - key: SDLK_a + key: SDLK_UP - platform: sdl id: key_down - key: SDLK_d + key: SDLK_DOWN - platform: sdl id: key_enter - key: SDLK_s + key: SDLK_RETURN From e28c15229811ed6731ab446cdf880f0929555ac9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 1 Nov 2025 04:48:58 -0500 Subject: [PATCH 0021/1145] [cpp_generator] Align isinstance() with codebase style (tuple vs PEP 604) (#11645) --- esphome/cpp_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index a2da424e5a..6f1af01a5b 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -350,7 +350,7 @@ def safe_exp(obj: SafeExpType) -> Expression: return IntLiteral(int(obj.total_seconds)) if isinstance(obj, TimePeriodMinutes): return IntLiteral(int(obj.total_minutes)) - if isinstance(obj, tuple | list): + if isinstance(obj, (tuple, list)): return ArrayInitializer(*[safe_exp(o) for o in obj]) if obj is bool: return bool_ From c662697ca77cca55e6b63a4515476f5404d5810f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 1 Nov 2025 11:18:10 -0500 Subject: [PATCH 0022/1145] [json] Fix component test compilation errors (#11647) --- tests/components/json/common.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/components/json/common.yaml b/tests/components/json/common.yaml index f4074e1172..c36c7f2a5a 100644 --- a/tests/components/json/common.yaml +++ b/tests/components/json/common.yaml @@ -14,12 +14,14 @@ interval: // Test parse_json bool parse_ok = esphome::json::parse_json(json_str, [](JsonObject root) { - if (root.containsKey("sensor") && root.containsKey("value")) { + if (root["sensor"].is() && root["value"].is()) { const char* sensor = root["sensor"]; float value = root["value"]; ESP_LOGD("test", "Parsed: sensor=%s, value=%.1f", sensor, value); + return true; } else { ESP_LOGD("test", "Parsed JSON missing required keys"); + return false; } }); ESP_LOGD("test", "Parse result (JSON syntax only): %s", parse_ok ? "success" : "failed"); From 55af8186294b900d52cd31eabd20407f30563286 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Sat, 1 Nov 2025 17:18:38 +0100 Subject: [PATCH 0023/1145] [nrf52] fix compilation warning (#11656) --- esphome/components/zephyr/core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/zephyr/core.cpp b/esphome/components/zephyr/core.cpp index ad7a148cdb..365b6b8ed2 100644 --- a/esphome/components/zephyr/core.cpp +++ b/esphome/components/zephyr/core.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include "esphome/core/hal.h" #include "esphome/core/helpers.h" From d25121a55c100da3aa3ddce112beac9e43f5b2e3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 1 Nov 2025 22:43:08 -0500 Subject: [PATCH 0024/1145] [core] Remove redundant fd bounds check in yield_with_select_() (#11666) --- esphome/core/application.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index c745aa0ae5..61cfcc7585 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -576,10 +576,11 @@ void Application::yield_with_select_(uint32_t delay_ms) { // Update fd_set if socket list has changed if (this->socket_fds_changed_) { FD_ZERO(&this->base_read_fds_); + // fd bounds are already validated in register_socket_fd() or guaranteed by platform design: + // - ESP32: LwIP guarantees fd < FD_SETSIZE by design (LWIP_SOCKET_OFFSET = FD_SETSIZE - CONFIG_LWIP_MAX_SOCKETS) + // - Other platforms: register_socket_fd() validates fd < FD_SETSIZE for (int fd : this->socket_fds_) { - if (fd >= 0 && fd < FD_SETSIZE) { - FD_SET(fd, &this->base_read_fds_); - } + FD_SET(fd, &this->base_read_fds_); } this->socket_fds_changed_ = false; } From 1fc3165b582a290f65b5be68a52ee45d75721ca5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 1 Nov 2025 22:43:39 -0500 Subject: [PATCH 0025/1145] [api] Remove unnecessary intermediate variable in frame helpers (#11668) --- esphome/components/api/api_frame_helper_noise.cpp | 3 +-- esphome/components/api/api_frame_helper_plaintext.cpp | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index e952ea670b..633b07a7fa 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -434,8 +434,7 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st return APIError::OK; } - std::vector *raw_buffer = buffer.get_buffer(); - uint8_t *buffer_data = raw_buffer->data(); // Cache buffer pointer + uint8_t *buffer_data = buffer.get_buffer()->data(); this->reusable_iovs_.clear(); this->reusable_iovs_.reserve(packets.size()); diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index 471e6c5404..dcbd35aa32 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -230,8 +230,7 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer return APIError::OK; } - std::vector *raw_buffer = buffer.get_buffer(); - uint8_t *buffer_data = raw_buffer->data(); // Cache buffer pointer + uint8_t *buffer_data = buffer.get_buffer()->data(); this->reusable_iovs_.clear(); this->reusable_iovs_.reserve(packets.size()); From edde2fc94c584cb07365c16c6fe7a9d0b20488a1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 08:18:17 -0600 Subject: [PATCH 0026/1145] Add basic tests for web_server_idf (#11659) --- tests/components/web_server_idf/common.yaml | 29 +++++++++++++++++++ .../web_server_idf/test.esp32-idf.yaml | 3 ++ 2 files changed, 32 insertions(+) create mode 100644 tests/components/web_server_idf/common.yaml create mode 100644 tests/components/web_server_idf/test.esp32-idf.yaml diff --git a/tests/components/web_server_idf/common.yaml b/tests/components/web_server_idf/common.yaml new file mode 100644 index 0000000000..b1885af266 --- /dev/null +++ b/tests/components/web_server_idf/common.yaml @@ -0,0 +1,29 @@ +esphome: + name: test-web-server-idf + +esp32: + board: esp32dev + framework: + type: esp-idf + +network: + +# Add some entities to test SSE event formatting +sensor: + - platform: template + name: "Test Sensor" + id: test_sensor + update_interval: 60s + lambda: "return 42.5;" + +binary_sensor: + - platform: template + name: "Test Binary Sensor" + id: test_binary_sensor + lambda: "return true;" + +switch: + - platform: template + name: "Test Switch" + id: test_switch + optimistic: true diff --git a/tests/components/web_server_idf/test.esp32-idf.yaml b/tests/components/web_server_idf/test.esp32-idf.yaml new file mode 100644 index 0000000000..c3b85178ef --- /dev/null +++ b/tests/components/web_server_idf/test.esp32-idf.yaml @@ -0,0 +1,3 @@ +<<: !include common.yaml + +web_server: From f6946c0b9aed3b15986079c16c98c0e51438311a Mon Sep 17 00:00:00 2001 From: Kjell Braden Date: Sun, 2 Nov 2025 22:08:45 +0100 Subject: [PATCH 0027/1145] add integration test for script re-entry argument issue (#11652) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- .../fixtures/action_concurrent_reentry.yaml | 105 ++++++++++++++++++ .../test_action_concurrent_reentry.py | 93 ++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 tests/integration/fixtures/action_concurrent_reentry.yaml create mode 100644 tests/integration/test_action_concurrent_reentry.py diff --git a/tests/integration/fixtures/action_concurrent_reentry.yaml b/tests/integration/fixtures/action_concurrent_reentry.yaml new file mode 100644 index 0000000000..68d36d1510 --- /dev/null +++ b/tests/integration/fixtures/action_concurrent_reentry.yaml @@ -0,0 +1,105 @@ +esphome: + name: action-concurrent-reentry + on_boot: + - priority: -100 + then: + - repeat: + count: 5 + then: + - lambda: id(handler_wait_until)->execute(id(global_counter)); + - lambda: id(handler_repeat)->execute(id(global_counter)); + - lambda: id(handler_while)->execute(id(global_counter)); + - lambda: id(handler_script_wait)->execute(id(global_counter)); + - delay: 50ms + - lambda: id(global_counter)++; + - delay: 50ms + +host: + +api: + +globals: + - id: global_counter + type: int + +script: + - id: handler_wait_until + + mode: parallel + + parameters: + arg: int + + then: + - wait_until: + condition: + lambda: return id(global_counter) == 5; + + - logger.log: + format: "AFTER wait_until ARG %d" + args: + - arg + + - id: handler_script_wait + + mode: parallel + + parameters: + arg: int + + then: + - script.wait: handler_wait_until + + - logger.log: + format: "AFTER script.wait ARG %d" + args: + - arg + + - id: handler_repeat + + mode: parallel + + parameters: + arg: int + + then: + - repeat: + count: 3 + then: + - logger.log: + format: "IN repeat %d ARG %d" + args: + - iteration + - arg + - delay: 100ms + + - logger.log: + format: "AFTER repeat ARG %d" + args: + - arg + + - id: handler_while + + mode: parallel + + parameters: + arg: int + + then: + - while: + condition: + lambda: return id(global_counter) != 5; + then: + - logger.log: + format: "IN while ARG %d" + args: + - arg + - delay: 100ms + + - logger.log: + format: "AFTER while ARG %d" + args: + - arg + +logger: + level: DEBUG diff --git a/tests/integration/test_action_concurrent_reentry.py b/tests/integration/test_action_concurrent_reentry.py new file mode 100644 index 0000000000..ba67e4c798 --- /dev/null +++ b/tests/integration/test_action_concurrent_reentry.py @@ -0,0 +1,93 @@ +"""Integration test for API conditional memory optimization with triggers and services.""" + +from __future__ import annotations + +import asyncio +import collections +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.xfail(reason="https://github.com/esphome/issues/issues/6534") +@pytest.mark.asyncio +async def test_action_concurrent_reentry( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """ + This test runs a script in parallel with varying arguments and verifies if + each script keeps its original argument throughout its execution + """ + test_complete = asyncio.Event() + expected = {0, 1, 2, 3, 4} + + # Patterns to match in logs + after_wait_until_pattern = re.compile(r"AFTER wait_until ARG (\d+)") + after_script_wait_pattern = re.compile(r"AFTER script\.wait ARG (\d+)") + after_repeat_pattern = re.compile(r"AFTER repeat ARG (\d+)") + in_repeat_pattern = re.compile(r"IN repeat (\d+) ARG (\d+)") + after_while_pattern = re.compile(r"AFTER while ARG (\d+)") + in_while_pattern = re.compile(r"IN while ARG (\d+)") + + after_wait_until_args = [] + after_script_wait_args = [] + after_while_args = [] + in_while_args = [] + after_repeat_args = [] + in_repeat_args = collections.defaultdict(list) + + def check_output(line: str) -> None: + """Check log output for expected messages.""" + if test_complete.is_set(): + return + + if mo := after_wait_until_pattern.search(line): + after_wait_until_args.append(int(mo.group(1))) + elif mo := after_script_wait_pattern.search(line): + after_script_wait_args.append(int(mo.group(1))) + elif mo := in_while_pattern.search(line): + in_while_args.append(int(mo.group(1))) + elif mo := after_while_pattern.search(line): + after_while_args.append(int(mo.group(1))) + elif mo := in_repeat_pattern.search(line): + in_repeat_args[int(mo.group(1))].append(int(mo.group(2))) + elif mo := after_repeat_pattern.search(line): + after_repeat_args.append(int(mo.group(1))) + if len(after_repeat_args) == len(expected): + test_complete.set() + + # Run with log monitoring + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "action-concurrent-reentry" + + # Wait for tests to complete with timeout + try: + await asyncio.wait_for(test_complete.wait(), timeout=8.0) + except TimeoutError: + pytest.fail("test timed out") + + # order may change, but all args must be present + for args in in_repeat_args.values(): + assert set(args) == expected + assert set(in_repeat_args.keys()) == {0, 1, 2} + assert set(after_wait_until_args) == expected, after_wait_until_args + assert set(after_script_wait_args) == expected, after_script_wait_args + assert set(after_repeat_args) == expected, after_repeat_args + assert set(after_while_args) == expected, after_while_args + assert dict(collections.Counter(in_while_args)) == { + 0: 5, + 1: 4, + 2: 3, + 3: 2, + 4: 1, + }, in_while_args From 425c88ee9434f35fcb093488f08a5651c4df57cf Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Sun, 2 Nov 2025 23:06:13 +0100 Subject: [PATCH 0028/1145] [nextion] Send `auto_wake_on_touch` as part of startup commands on loop (#11670) --- esphome/components/nextion/nextion.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index fc152ece1e..d77af510d7 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -323,6 +323,8 @@ void Nextion::loop() { this->set_touch_sleep_timeout(this->touch_sleep_timeout_); } + this->set_auto_wake_on_touch(this->connection_state_.auto_wake_on_touch_); + this->connection_state_.ignore_is_setup_ = false; } From 338190abeca6cd7cd29ca6065065d549c490857e Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Sun, 2 Nov 2025 19:11:02 -0300 Subject: [PATCH 0029/1145] ESP32 Pin loopTask to CORE 1 (#11669) --- esphome/components/esp32/core.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 3427c96e70..1c8f29fa95 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -96,7 +96,11 @@ void loop_task(void *pv_params) { extern "C" void app_main() { esp32::setup_preferences(); +#if CONFIG_FREERTOS_UNICORE xTaskCreate(loop_task, "loopTask", 8192, nullptr, 1, &loop_task_handle); +#else + xTaskCreatePinnedToCore(loop_task, "loopTask", 8192, nullptr, 1, &loop_task_handle, 1); +#endif } #endif // USE_ESP_IDF From 70ea3af578e1440fc49751dff420bc41f195af20 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Sun, 2 Nov 2025 23:19:28 +0100 Subject: [PATCH 0030/1145] [nrf52,gpio] switch input gpio to polling mode (#11664) --- esphome/components/gpio/binary_sensor/__init__.py | 3 ++- esphome/components/zephyr/gpio.cpp | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/gpio/binary_sensor/__init__.py b/esphome/components/gpio/binary_sensor/__init__.py index ca4dc43e9c..3c2021d40e 100644 --- a/esphome/components/gpio/binary_sensor/__init__.py +++ b/esphome/components/gpio/binary_sensor/__init__.py @@ -39,6 +39,7 @@ CONFIG_SCHEMA = ( # due to hardware limitations or lack of reliable interrupt support. This ensures # stable operation on these platforms. Future maintainers should verify platform # capabilities before changing this default behavior. + # nrf52 has no gpio interrupts implemented yet cv.SplitDefault( CONF_USE_INTERRUPT, bk72xx=False, @@ -46,7 +47,7 @@ CONFIG_SCHEMA = ( esp8266=True, host=True, ln882x=False, - nrf52=True, + nrf52=False, rp2040=True, rtl87xx=False, ): cv.boolean, diff --git a/esphome/components/zephyr/gpio.cpp b/esphome/components/zephyr/gpio.cpp index 4b84910368..41b983535c 100644 --- a/esphome/components/zephyr/gpio.cpp +++ b/esphome/components/zephyr/gpio.cpp @@ -8,8 +8,8 @@ namespace zephyr { static const char *const TAG = "zephyr"; -static int flags_to_mode(gpio::Flags flags, bool inverted, bool value) { - int ret = 0; +static gpio_flags_t flags_to_mode(gpio::Flags flags, bool inverted, bool value) { + gpio_flags_t ret = 0; if (flags & gpio::FLAG_INPUT) { ret |= GPIO_INPUT; } @@ -79,7 +79,10 @@ void ZephyrGPIOPin::pin_mode(gpio::Flags flags) { if (nullptr == this->gpio_) { return; } - gpio_pin_configure(this->gpio_, this->pin_ % 32, flags_to_mode(flags, this->inverted_, this->value_)); + auto ret = gpio_pin_configure(this->gpio_, this->pin_ % 32, flags_to_mode(flags, this->inverted_, this->value_)); + if (ret != 0) { + ESP_LOGE(TAG, "gpio %u cannot be configured %d.", this->pin_, ret); + } } std::string ZephyrGPIOPin::dump_summary() const { From 50e7ce55e7370f4f015488e0e61e2746134d7075 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Sun, 2 Nov 2025 23:20:30 +0100 Subject: [PATCH 0031/1145] [nrf52] enable nrf52 test (#11379) --- esphome/components/nrf52/__init__.py | 1 + tests/components/nrf52/test-bootloader.nrf52-xiao-ble.yaml | 3 +++ tests/components/nrf52/test.nrf52-mcumgr.yaml | 0 3 files changed, 4 insertions(+) create mode 100644 tests/components/nrf52/test-bootloader.nrf52-xiao-ble.yaml create mode 100644 tests/components/nrf52/test.nrf52-mcumgr.yaml diff --git a/esphome/components/nrf52/__init__.py b/esphome/components/nrf52/__init__.py index 27e1246744..ace324c1f5 100644 --- a/esphome/components/nrf52/__init__.py +++ b/esphome/components/nrf52/__init__.py @@ -290,6 +290,7 @@ def show_logs(config: ConfigType, args, devices: list[str]) -> bool: address = ble_device.address else: return True + if is_mac_address(address): asyncio.run(logger_connect(address)) return True diff --git a/tests/components/nrf52/test-bootloader.nrf52-xiao-ble.yaml b/tests/components/nrf52/test-bootloader.nrf52-xiao-ble.yaml new file mode 100644 index 0000000000..022ab9c753 --- /dev/null +++ b/tests/components/nrf52/test-bootloader.nrf52-xiao-ble.yaml @@ -0,0 +1,3 @@ +nrf52: + # it is not correct bootloader for the board + bootloader: adafruit_nrf52_sd140_v6 diff --git a/tests/components/nrf52/test.nrf52-mcumgr.yaml b/tests/components/nrf52/test.nrf52-mcumgr.yaml new file mode 100644 index 0000000000..e69de29bb2 From c822ec152f45528949dc9b220165774286cf7456 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Sun, 2 Nov 2025 23:22:49 +0100 Subject: [PATCH 0032/1145] Enable IPv6 for host (#11630) --- esphome/components/network/__init__.py | 2 ++ tests/components/network/test-ipv6.host.yaml | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 tests/components/network/test-ipv6.host.yaml diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index 502803da1e..1d62b661ca 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -55,6 +55,7 @@ CONFIG_SCHEMA = cv.Schema( esp32=False, rp2040=False, bk72xx=False, + host=False, ): cv.All( cv.boolean, cv.Any( @@ -64,6 +65,7 @@ CONFIG_SCHEMA = cv.Schema( esp8266_arduino=cv.Version(0, 0, 0), rp2040_arduino=cv.Version(0, 0, 0), bk72xx_arduino=cv.Version(1, 7, 0), + host=cv.Version(0, 0, 0), ), cv.boolean_false, ), diff --git a/tests/components/network/test-ipv6.host.yaml b/tests/components/network/test-ipv6.host.yaml new file mode 100644 index 0000000000..d9eeab89ea --- /dev/null +++ b/tests/components/network/test-ipv6.host.yaml @@ -0,0 +1,2 @@ +network: + enable_ipv6: true From 79378a930e8d10871a765bce6a144b7868b0e50b Mon Sep 17 00:00:00 2001 From: Juan Antonio Aldea Date: Sun, 2 Nov 2025 23:26:20 +0100 Subject: [PATCH 0033/1145] Use lists inits initialization instead of std::fill (#11532) --- esphome/components/remote_base/abbwelcome_protocol.h | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/esphome/components/remote_base/abbwelcome_protocol.h b/esphome/components/remote_base/abbwelcome_protocol.h index f2d0f5b547..b258bd920b 100644 --- a/esphome/components/remote_base/abbwelcome_protocol.h +++ b/esphome/components/remote_base/abbwelcome_protocol.h @@ -33,19 +33,13 @@ Message Format: class ABBWelcomeData { public: // Make default - ABBWelcomeData() { - std::fill(std::begin(this->data_), std::end(this->data_), 0); - this->data_[0] = 0x55; - this->data_[1] = 0xff; - } + ABBWelcomeData() : data_{0x55, 0xff} {} // Make from initializer_list - ABBWelcomeData(std::initializer_list data) { - std::fill(std::begin(this->data_), std::end(this->data_), 0); + ABBWelcomeData(std::initializer_list data) : data_{} { std::copy_n(data.begin(), std::min(data.size(), this->data_.size()), this->data_.begin()); } // Make from vector - ABBWelcomeData(const std::vector &data) { - std::fill(std::begin(this->data_), std::end(this->data_), 0); + ABBWelcomeData(const std::vector &data) : data_{} { std::copy_n(data.begin(), std::min(data.size(), this->data_.size()), this->data_.begin()); } // Default copy constructor From 8a8a80e1071a3f71938fdd278fca20a5eb5157a1 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Sun, 2 Nov 2025 23:44:52 +0100 Subject: [PATCH 0034/1145] [nrf52, zigbee] OnlyWith support list of components (#11533) Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- esphome/config_validation.py | 31 ++++- tests/unit_tests/test_config_validation.py | 129 ++++++++++++++++++++- 2 files changed, 154 insertions(+), 6 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 359b257992..a3fd271a86 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -2,6 +2,7 @@ from __future__ import annotations +from collections.abc import Callable from contextlib import contextmanager, suppress from dataclasses import dataclass from datetime import datetime @@ -18,6 +19,7 @@ import logging from pathlib import Path import re from string import ascii_letters, digits +import typing import uuid as uuid_ import voluptuous as vol @@ -1763,16 +1765,37 @@ class SplitDefault(Optional): class OnlyWith(Optional): - """Set the default value only if the given component is loaded.""" + """Set the default value only if the given component(s) is/are loaded. - def __init__(self, key, component, default=None): + This validator allows configuration keys to have defaults that are only applied + when specific component(s) are loaded. Supports both single component names and + lists of components. + + Args: + key: Configuration key + component: Single component name (str) or list of component names. + For lists, ALL components must be loaded for the default to apply. + default: Default value to use when condition is met + + Example: + # Single component + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(MQTTComponent) + + # Multiple components (all must be loaded) + cv.OnlyWith(CONF_ZIGBEE_ID, ["zigbee", "nrf52"]): cv.use_id(Zigbee) + """ + + def __init__(self, key, component: str | list[str], default=None) -> None: super().__init__(key) self._component = component self._default = vol.default_factory(default) @property - def default(self): - if self._component in CORE.loaded_integrations: + def default(self) -> Callable[[], typing.Any] | vol.Undefined: + if isinstance(self._component, list): + if all(c in CORE.loaded_integrations for c in self._component): + return self._default + elif self._component in CORE.loaded_integrations: return self._default return vol.UNDEFINED diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 2928c5c83a..104cdc2b7a 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -3,6 +3,7 @@ import string from hypothesis import example, given from hypothesis.strategies import builds, integers, ip_addresses, one_of, text import pytest +import voluptuous as vol from esphome import config_validation from esphome.components.esp32.const import ( @@ -301,8 +302,6 @@ def test_split_default(framework, platform, variant, full, idf, arduino, simple) ], ) def test_require_framework_version(framework, platform, message): - import voluptuous as vol - from esphome.const import ( KEY_CORE, KEY_FRAMEWORK_VERSION, @@ -377,3 +376,129 @@ def test_require_framework_version(framework, platform, message): config_validation.require_framework_version( extra_message="test 5", )("test") + + +def test_only_with_single_component_loaded() -> None: + """Test OnlyWith with single component when component is loaded.""" + CORE.loaded_integrations = {"mqtt"} + + schema = config_validation.Schema( + { + config_validation.OnlyWith("mqtt_id", "mqtt", default="test_mqtt"): str, + } + ) + + result = schema({}) + assert result.get("mqtt_id") == "test_mqtt" + + +def test_only_with_single_component_not_loaded() -> None: + """Test OnlyWith with single component when component is not loaded.""" + CORE.loaded_integrations = set() + + schema = config_validation.Schema( + { + config_validation.OnlyWith("mqtt_id", "mqtt", default="test_mqtt"): str, + } + ) + + result = schema({}) + assert "mqtt_id" not in result + + +def test_only_with_list_all_components_loaded() -> None: + """Test OnlyWith with list when all components are loaded.""" + CORE.loaded_integrations = {"zigbee", "nrf52"} + + schema = config_validation.Schema( + { + config_validation.OnlyWith( + "zigbee_id", ["zigbee", "nrf52"], default="test_zigbee" + ): str, + } + ) + + result = schema({}) + assert result.get("zigbee_id") == "test_zigbee" + + +def test_only_with_list_partial_components_loaded() -> None: + """Test OnlyWith with list when only some components are loaded.""" + CORE.loaded_integrations = {"zigbee"} # Only zigbee, not nrf52 + + schema = config_validation.Schema( + { + config_validation.OnlyWith( + "zigbee_id", ["zigbee", "nrf52"], default="test_zigbee" + ): str, + } + ) + + result = schema({}) + assert "zigbee_id" not in result + + +def test_only_with_list_no_components_loaded() -> None: + """Test OnlyWith with list when no components are loaded.""" + CORE.loaded_integrations = set() + + schema = config_validation.Schema( + { + config_validation.OnlyWith( + "zigbee_id", ["zigbee", "nrf52"], default="test_zigbee" + ): str, + } + ) + + result = schema({}) + assert "zigbee_id" not in result + + +def test_only_with_list_multiple_components() -> None: + """Test OnlyWith with list requiring three components.""" + CORE.loaded_integrations = {"comp1", "comp2", "comp3"} + + schema = config_validation.Schema( + { + config_validation.OnlyWith( + "test_id", ["comp1", "comp2", "comp3"], default="test_value" + ): str, + } + ) + + result = schema({}) + assert result.get("test_id") == "test_value" + + # Test with one missing + CORE.loaded_integrations = {"comp1", "comp2"} + result = schema({}) + assert "test_id" not in result + + +def test_only_with_empty_list() -> None: + """Test OnlyWith with empty list (edge case).""" + CORE.loaded_integrations = set() + + schema = config_validation.Schema( + { + config_validation.OnlyWith("test_id", [], default="test_value"): str, + } + ) + + # all([]) returns True, so default should be applied + result = schema({}) + assert result.get("test_id") == "test_value" + + +def test_only_with_user_value_overrides_default() -> None: + """Test OnlyWith respects user-provided values over defaults.""" + CORE.loaded_integrations = {"mqtt"} + + schema = config_validation.Schema( + { + config_validation.OnlyWith("mqtt_id", "mqtt", default="default_id"): str, + } + ) + + result = schema({"mqtt_id": "custom_id"}) + assert result.get("mqtt_id") == "custom_id" From 86402be9e31e7f983011797a1ce684b76c124441 Mon Sep 17 00:00:00 2001 From: Kjell Braden Date: Mon, 3 Nov 2025 00:02:13 +0100 Subject: [PATCH 0035/1145] actions: fix loop re-entry (#7972) Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston --- esphome/components/script/script.h | 38 ++++- esphome/core/base_automation.h | 80 +++++++---- .../fixtures/automation_wait_actions.yaml | 130 ++++++++++++++++++ .../test_action_concurrent_reentry.py | 1 - .../test_automation_wait_actions.py | 104 ++++++++++++++ 5 files changed, 321 insertions(+), 32 deletions(-) create mode 100644 tests/integration/fixtures/automation_wait_actions.yaml create mode 100644 tests/integration/test_automation_wait_actions.py diff --git a/esphome/components/script/script.h b/esphome/components/script/script.h index 58fb67a3ea..870a623f32 100644 --- a/esphome/components/script/script.h +++ b/esphome/components/script/script.h @@ -2,6 +2,7 @@ #include #include +#include #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -264,10 +265,22 @@ template class IsRunningCondition : public Condition class ScriptWaitAction : public Action, public Component { public: ScriptWaitAction(C *script) : script_(script) {} + void setup() override { + // Start with loop disabled - only enable when there's work to do + this->disable_loop(); + } + void play_complex(Ts... x) override { this->num_running_++; // Check if we can continue immediately. @@ -275,7 +288,11 @@ template class ScriptWaitAction : public Action, this->play_next_(x...); return; } - this->var_ = std::make_tuple(x...); + + // Store parameters for later execution + this->param_queue_.emplace_front(x...); + // Enable loop now that we have work to do + this->enable_loop(); this->loop(); } @@ -286,15 +303,30 @@ template class ScriptWaitAction : public Action, if (this->script_->is_running()) return; - this->play_next_tuple_(this->var_); + while (!this->param_queue_.empty()) { + auto ¶ms = this->param_queue_.front(); + this->play_next_tuple_(params, typename gens::type()); + this->param_queue_.pop_front(); + } + // Queue is now empty - disable loop until next play_complex + this->disable_loop(); } void play(Ts... x) override { /* ignore - see play_complex */ } + void stop() override { + this->param_queue_.clear(); + this->disable_loop(); + } + protected: + template void play_next_tuple_(const std::tuple &tuple, seq /*unused*/) { + this->play_next_(std::get(tuple)...); + } + C *script_; - std::tuple var_{}; + std::forward_list> param_queue_; }; } // namespace script diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 1c60dd1c7a..e668a1782a 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -10,6 +10,7 @@ #include "esphome/core/helpers.h" #include +#include namespace esphome { @@ -268,32 +269,28 @@ template class WhileAction : public Action { void add_then(const std::initializer_list *> &actions) { this->then_.add_actions(actions); this->then_.add_action(new LambdaAction([this](Ts... x) { - if (this->num_running_ > 0 && this->condition_->check_tuple(this->var_)) { + if (this->num_running_ > 0 && this->condition_->check(x...)) { // play again - if (this->num_running_ > 0) { - this->then_.play_tuple(this->var_); - } + this->then_.play(x...); } else { // condition false, play next - this->play_next_tuple_(this->var_); + this->play_next_(x...); } })); } void play_complex(Ts... x) override { this->num_running_++; - // Store loop parameters - this->var_ = std::make_tuple(x...); // Initial condition check - if (!this->condition_->check_tuple(this->var_)) { + if (!this->condition_->check(x...)) { // If new condition check failed, stop loop if running this->then_.stop(); - this->play_next_tuple_(this->var_); + this->play_next_(x...); return; } if (this->num_running_ > 0) { - this->then_.play_tuple(this->var_); + this->then_.play(x...); } } @@ -305,7 +302,6 @@ template class WhileAction : public Action { protected: Condition *condition_; ActionList then_; - std::tuple var_{}; }; template class RepeatAction : public Action { @@ -317,7 +313,7 @@ template class RepeatAction : public Action { this->then_.add_action(new LambdaAction([this](uint32_t iteration, Ts... x) { iteration++; if (iteration >= this->count_.value(x...)) { - this->play_next_tuple_(this->var_); + this->play_next_(x...); } else { this->then_.play(iteration, x...); } @@ -326,11 +322,10 @@ template class RepeatAction : public Action { void play_complex(Ts... x) override { this->num_running_++; - this->var_ = std::make_tuple(x...); if (this->count_.value(x...) > 0) { this->then_.play(0, x...); } else { - this->play_next_tuple_(this->var_); + this->play_next_(x...); } } @@ -341,15 +336,26 @@ template class RepeatAction : public Action { protected: ActionList then_; - std::tuple var_; }; +/** Wait until a condition is true to continue execution. + * + * Uses queue-based storage to safely handle concurrent executions. + * While concurrent execution from the same trigger is uncommon, it's possible + * (e.g., rapid button presses, high-frequency sensor updates), so we use + * queue-based storage for correctness. + */ template class WaitUntilAction : public Action, public Component { public: WaitUntilAction(Condition *condition) : condition_(condition) {} TEMPLATABLE_VALUE(uint32_t, timeout_value) + void setup() override { + // Start with loop disabled - only enable when there's work to do + this->disable_loop(); + } + void play_complex(Ts... x) override { this->num_running_++; // Check if we can continue immediately. @@ -359,13 +365,14 @@ template class WaitUntilAction : public Action, public Co } return; } - this->var_ = std::make_tuple(x...); - if (this->timeout_value_.has_value()) { - auto f = std::bind(&WaitUntilAction::play_next_, this, x...); - this->set_timeout("timeout", this->timeout_value_.value(x...), f); - } + // Store for later processing + auto now = millis(); + auto timeout = this->timeout_value_.optional_value(x...); + this->var_queue_.emplace_front(now, timeout, std::make_tuple(x...)); + // Enable loop now that we have work to do + this->enable_loop(); this->loop(); } @@ -373,13 +380,32 @@ template class WaitUntilAction : public Action, public Co if (this->num_running_ == 0) return; - if (!this->condition_->check_tuple(this->var_)) { - return; + auto now = millis(); + + this->var_queue_.remove_if([&](auto &queued) { + auto start = std::get(queued); + auto timeout = std::get>(queued); + auto &var = std::get>(queued); + + auto expired = timeout && (now - start) >= *timeout; + + if (!expired && !this->condition_->check_tuple(var)) { + return false; + } + + this->play_next_tuple_(var); + return true; + }); + + // If queue is now empty, disable loop until next play_complex + if (this->var_queue_.empty()) { + this->disable_loop(); } + } - this->cancel_timeout("timeout"); - - this->play_next_tuple_(this->var_); + void stop() override { + this->var_queue_.clear(); + this->disable_loop(); } float get_setup_priority() const override { return setup_priority::DATA; } @@ -387,11 +413,9 @@ template class WaitUntilAction : public Action, public Co void play(Ts... x) override { /* ignore - see play_complex */ } - void stop() override { this->cancel_timeout("timeout"); } - protected: Condition *condition_; - std::tuple var_{}; + std::forward_list, std::tuple>> var_queue_{}; }; template class UpdateComponentAction : public Action { diff --git a/tests/integration/fixtures/automation_wait_actions.yaml b/tests/integration/fixtures/automation_wait_actions.yaml new file mode 100644 index 0000000000..65a61be14f --- /dev/null +++ b/tests/integration/fixtures/automation_wait_actions.yaml @@ -0,0 +1,130 @@ +esphome: + name: test-automation-wait-actions + +host: + +api: + actions: + # Test 1: Trigger wait_until automation 5 times rapidly + - action: test_wait_until + then: + - logger.log: "=== TEST 1: Triggering wait_until automation 5 times ===" + # Publish 5 different values to trigger the on_value automation 5 times + - sensor.template.publish: + id: wait_until_sensor + state: 1 + - sensor.template.publish: + id: wait_until_sensor + state: 2 + - sensor.template.publish: + id: wait_until_sensor + state: 3 + - sensor.template.publish: + id: wait_until_sensor + state: 4 + - sensor.template.publish: + id: wait_until_sensor + state: 5 + # Wait then satisfy the condition so all 5 waiting actions complete + - delay: 100ms + - globals.set: + id: test_flag + value: 'true' + + # Test 2: Trigger script.wait automation 5 times rapidly + - action: test_script_wait + then: + - logger.log: "=== TEST 2: Triggering script.wait automation 5 times ===" + # Start a long-running script + - script.execute: blocking_script + # Publish 5 different values to trigger the on_value automation 5 times + - sensor.template.publish: + id: script_wait_sensor + state: 1 + - sensor.template.publish: + id: script_wait_sensor + state: 2 + - sensor.template.publish: + id: script_wait_sensor + state: 3 + - sensor.template.publish: + id: script_wait_sensor + state: 4 + - sensor.template.publish: + id: script_wait_sensor + state: 5 + + # Test 3: Trigger wait_until timeout automation 5 times rapidly + - action: test_wait_timeout + then: + - logger.log: "=== TEST 3: Triggering timeout automation 5 times ===" + # Publish 5 different values (condition will never be true, all will timeout) + - sensor.template.publish: + id: timeout_sensor + state: 1 + - sensor.template.publish: + id: timeout_sensor + state: 2 + - sensor.template.publish: + id: timeout_sensor + state: 3 + - sensor.template.publish: + id: timeout_sensor + state: 4 + - sensor.template.publish: + id: timeout_sensor + state: 5 + +logger: + level: DEBUG + +globals: + - id: test_flag + type: bool + restore_value: false + initial_value: 'false' + + - id: timeout_flag + type: bool + restore_value: false + initial_value: 'false' + +# Sensors with wait_until/script.wait in their on_value automations +sensor: + # Test 1: on_value automation with wait_until + - platform: template + id: wait_until_sensor + on_value: + # This wait_until will be hit 5 times before any complete + - wait_until: + condition: + lambda: return id(test_flag); + - logger.log: "wait_until automation completed" + + # Test 2: on_value automation with script.wait + - platform: template + id: script_wait_sensor + on_value: + # This script.wait will be hit 5 times before any complete + - script.wait: blocking_script + - logger.log: "script.wait automation completed" + + # Test 3: on_value automation with wait_until timeout + - platform: template + id: timeout_sensor + on_value: + # This wait_until will be hit 5 times, all will timeout + - wait_until: + condition: + lambda: return id(timeout_flag); + timeout: 200ms + - logger.log: "timeout automation completed" + +script: + # Blocking script for script.wait test + - id: blocking_script + mode: single + then: + - logger.log: "Blocking script: START" + - delay: 200ms + - logger.log: "Blocking script: END" diff --git a/tests/integration/test_action_concurrent_reentry.py b/tests/integration/test_action_concurrent_reentry.py index ba67e4c798..aa5801ca2b 100644 --- a/tests/integration/test_action_concurrent_reentry.py +++ b/tests/integration/test_action_concurrent_reentry.py @@ -11,7 +11,6 @@ import pytest from .types import APIClientConnectedFactory, RunCompiledFunction -@pytest.mark.xfail(reason="https://github.com/esphome/issues/issues/6534") @pytest.mark.asyncio async def test_action_concurrent_reentry( yaml_config: str, diff --git a/tests/integration/test_automation_wait_actions.py b/tests/integration/test_automation_wait_actions.py new file mode 100644 index 0000000000..adcb8ba487 --- /dev/null +++ b/tests/integration/test_automation_wait_actions.py @@ -0,0 +1,104 @@ +"""Test concurrent execution of wait_until and script.wait in direct automation actions.""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_automation_wait_actions( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """ + Test that wait_until and script.wait correctly handle concurrent executions + when automation actions (not scripts) are triggered multiple times rapidly. + + This tests sensor.on_value automations being triggered 5 times before any complete. + """ + loop = asyncio.get_running_loop() + + # Track completion counts + test_results = { + "wait_until": 0, + "script_wait": 0, + "wait_until_timeout": 0, + } + + # Patterns for log messages + wait_until_complete = re.compile(r"wait_until automation completed") + script_wait_complete = re.compile(r"script\.wait automation completed") + timeout_complete = re.compile(r"timeout automation completed") + + # Test completion futures + test1_complete = loop.create_future() + test2_complete = loop.create_future() + test3_complete = loop.create_future() + + def check_output(line: str) -> None: + """Check log output for completion messages.""" + # Test 1: wait_until concurrent execution + if wait_until_complete.search(line): + test_results["wait_until"] += 1 + if test_results["wait_until"] == 5 and not test1_complete.done(): + test1_complete.set_result(True) + + # Test 2: script.wait concurrent execution + if script_wait_complete.search(line): + test_results["script_wait"] += 1 + if test_results["script_wait"] == 5 and not test2_complete.done(): + test2_complete.set_result(True) + + # Test 3: wait_until with timeout + if timeout_complete.search(line): + test_results["wait_until_timeout"] += 1 + if test_results["wait_until_timeout"] == 5 and not test3_complete.done(): + test3_complete.set_result(True) + + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Get services + _, services = await client.list_entities_services() + + # Test 1: wait_until in automation - trigger 5 times rapidly + test_service = next((s for s in services if s.name == "test_wait_until"), None) + assert test_service is not None, "test_wait_until service not found" + client.execute_service(test_service, {}) + await asyncio.wait_for(test1_complete, timeout=3.0) + + # Verify Test 1: All 5 triggers should complete + assert test_results["wait_until"] == 5, ( + f"Test 1: Expected 5 wait_until completions, got {test_results['wait_until']}" + ) + + # Test 2: script.wait in automation - trigger 5 times rapidly + test_service = next((s for s in services if s.name == "test_script_wait"), None) + assert test_service is not None, "test_script_wait service not found" + client.execute_service(test_service, {}) + await asyncio.wait_for(test2_complete, timeout=3.0) + + # Verify Test 2: All 5 triggers should complete + assert test_results["script_wait"] == 5, ( + f"Test 2: Expected 5 script.wait completions, got {test_results['script_wait']}" + ) + + # Test 3: wait_until with timeout in automation - trigger 5 times rapidly + test_service = next( + (s for s in services if s.name == "test_wait_timeout"), None + ) + assert test_service is not None, "test_wait_timeout service not found" + client.execute_service(test_service, {}) + await asyncio.wait_for(test3_complete, timeout=3.0) + + # Verify Test 3: All 5 triggers should timeout and complete + assert test_results["wait_until_timeout"] == 5, ( + f"Test 3: Expected 5 timeout completions, got {test_results['wait_until_timeout']}" + ) From 19e275dc02894792327001d9df1bd8c863fe4c5c Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:33:44 +1000 Subject: [PATCH 0036/1145] [component] Add is_idle method and condition (#11651) --- esphome/automation.py | 26 +++++++++++++++++++++++++- esphome/components/lvgl/automation.py | 6 +++++- esphome/components/lvgl/lvgl_esphome.h | 1 - esphome/core/component.cpp | 1 + esphome/core/component.h | 8 ++++++++ tests/components/esphome/common.yaml | 7 ++++++- 6 files changed, 45 insertions(+), 4 deletions(-) diff --git a/esphome/automation.py b/esphome/automation.py index 99be12451e..2439b1ddc4 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -15,7 +15,7 @@ from esphome.const import ( CONF_TYPE_ID, CONF_UPDATE_INTERVAL, ) -from esphome.core import ID +from esphome.core import ID, Lambda from esphome.cpp_generator import ( LambdaExpression, MockObj, @@ -310,6 +310,30 @@ async def for_condition_to_code( return var +@register_condition( + "component.is_idle", + LambdaCondition, + maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(cg.Component), + } + ), +) +async def component_is_idle_condition_to_code( + config: ConfigType, + condition_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: + comp = await cg.get_variable(config[CONF_ID]) + lambda_ = await cg.process_lambda( + Lambda(f"return {comp}->is_idle();"), args, return_type=bool + ) + return new_lambda_pvariable( + condition_id, lambda_, StatelessLambdaCondition, template_arg + ) + + @register_action( "delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds) ) diff --git a/esphome/components/lvgl/automation.py b/esphome/components/lvgl/automation.py index 593c8c67bb..9b58727f2a 100644 --- a/esphome/components/lvgl/automation.py +++ b/esphome/components/lvgl/automation.py @@ -137,7 +137,11 @@ async def lvgl_is_idle(config, condition_id, template_arg, args): lvgl = config[CONF_LVGL_ID] timeout = await lv_milliseconds.process(config[CONF_TIMEOUT]) async with LambdaContext(LVGL_COMP_ARG, return_type=cg.bool_) as context: - lv_add(ReturnStatement(lvgl_comp.is_idle(timeout))) + lv_add( + ReturnStatement( + lv_expr.disp_get_inactive_time(lvgl_comp.get_disp()) > timeout + ) + ) var = cg.new_Pvariable( condition_id, TemplateArguments(LvglComponent, *template_arg), diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index ea58fdb85b..50d192fde3 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -175,7 +175,6 @@ class LvglComponent : public PollingComponent { static void monitor_cb(lv_disp_drv_t *disp_drv, uint32_t time, uint32_t px); static void render_start_cb(lv_disp_drv_t *disp_drv); void dump_config() override; - bool is_idle(uint32_t idle_ms) { return lv_disp_get_inactive_time(this->disp_) > idle_ms; } lv_disp_t *get_disp() { return this->disp_; } lv_obj_t *get_scr_act() { return lv_disp_get_scr_act(this->disp_); } // Pause or resume the display. diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 11d9501bb8..de3dd99d0c 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -284,6 +284,7 @@ bool Component::is_ready() const { (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE || (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_SETUP; } +bool Component::is_idle() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE; } bool Component::can_proceed() { return true; } bool Component::status_has_warning() const { return this->component_state_ & STATUS_LED_WARNING; } bool Component::status_has_error() const { return this->component_state_ & STATUS_LED_ERROR; } diff --git a/esphome/core/component.h b/esphome/core/component.h index e97941374d..462e0e301c 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -141,6 +141,14 @@ class Component { */ bool is_in_loop_state() const; + /** Check if this component is idle. + * Being idle means being in LOOP_DONE state. + * This means the component has completed setup, is not failed, but its loop is currently disabled. + * + * @return True if the component is idle + */ + bool is_idle() const; + /** Mark this component as failed. Any future timeouts/intervals/setup/loop will no longer be called. * * This might be useful if a component wants to indicate that a connection to its peripheral failed. diff --git a/tests/components/esphome/common.yaml b/tests/components/esphome/common.yaml index a4b309b69d..b2d7bccaa5 100644 --- a/tests/components/esphome/common.yaml +++ b/tests/components/esphome/common.yaml @@ -10,7 +10,11 @@ esphome: on_shutdown: logger.log: on_shutdown on_loop: - logger.log: on_loop + if: + condition: + component.is_idle: binary_sensor_id + then: + logger.log: on_loop - sensor idle compile_process_limit: 1 min_version: "2025.1" name_add_mac_suffix: true @@ -34,5 +38,6 @@ esphome: binary_sensor: - platform: template + id: binary_sensor_id name: Other device sensor device_id: other_device From 3e17767f6a310d10e33e74849e61786bfabeab78 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:50:15 +1000 Subject: [PATCH 0037/1145] [font][image] Use ESPHome urls for remote images (#11675) --- tests/components/font/common.yaml | 4 ++-- tests/components/font/test.host.yaml | 4 ++-- tests/components/image/common.yaml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/components/font/common.yaml b/tests/components/font/common.yaml index 6ba52e3d97..c156b4aea1 100644 --- a/tests/components/font/common.yaml +++ b/tests/components/font/common.yaml @@ -21,12 +21,12 @@ font: id: roboto_greek size: 20 glyphs: ["\u0300", "\u00C5", "\U000000C7"] - - file: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf" + - file: "https://media.esphome.io/tests/fonts/Monocraft.ttf" id: monocraft size: 20 - file: type: web - url: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf" + url: "https://media.esphome.io/tests/fonts/Monocraft.ttf" id: monocraft2 size: 24 - file: $component_dir/Monocraft.ttf diff --git a/tests/components/font/test.host.yaml b/tests/components/font/test.host.yaml index c5399f2826..387ea47335 100644 --- a/tests/components/font/test.host.yaml +++ b/tests/components/font/test.host.yaml @@ -21,12 +21,12 @@ font: id: roboto_greek size: 20 glyphs: ["\u0300", "\u00C5", "\U000000C7"] - - file: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf" + - file: "https://media.esphome.io/tests/fonts/Monocraft.ttf" id: monocraft size: 20 - file: type: web - url: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf" + url: "https://media.esphome.io/tests/fonts/Monocraft.ttf" id: monocraft2 size: 24 - file: $component_dir/Monocraft.ttf diff --git a/tests/components/image/common.yaml b/tests/components/image/common.yaml index 864ca41c44..9819068970 100644 --- a/tests/components/image/common.yaml +++ b/tests/components/image/common.yaml @@ -50,16 +50,16 @@ image: transparency: opaque - id: web_svg_image - file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg + file: https://media.esphome.io/logo/logo.svg resize: 256x48 type: BINARY transparency: chroma_key - id: web_tiff_image - file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff + file: https://media.esphome.io/tests/images/SIPI_Jelly_Beans_4.1.07.tiff type: RGB resize: 48x48 - id: web_redirect_image - file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 + file: https://media.esphome.io/logo/logo.png type: RGB resize: 48x48 - id: mdi_alert From 1509ed8d2391f0cdd16e65b703e2979006515200 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 19:04:06 -0600 Subject: [PATCH 0038/1145] [esphome][ota] Add write_byte_() helper to reduce code duplication (#11511) --- .../components/esphome/ota/ota_esphome.cpp | 28 ++++++------------- esphome/components/esphome/ota/ota_esphome.h | 1 + 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 569268ea15..b85d660272 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -281,19 +281,15 @@ void ESPHomeOTAComponent::handle_data_() { #endif // Acknowledge auth OK - 1 byte - buf[0] = ota::OTA_RESPONSE_AUTH_OK; - this->writeall_(buf, 1); + this->write_byte_(ota::OTA_RESPONSE_AUTH_OK); // Read size, 4 bytes MSB first if (!this->readall_(buf, 4)) { this->log_read_error_(LOG_STR("size")); goto error; // NOLINT(cppcoreguidelines-avoid-goto) } - ota_size = 0; - for (uint8_t i = 0; i < 4; i++) { - ota_size <<= 8; - ota_size |= buf[i]; - } + ota_size = (static_cast(buf[0]) << 24) | (static_cast(buf[1]) << 16) | + (static_cast(buf[2]) << 8) | buf[3]; ESP_LOGV(TAG, "Size is %u bytes", ota_size); // Now that we've passed authentication and are actually @@ -313,8 +309,7 @@ void ESPHomeOTAComponent::handle_data_() { update_started = true; // Acknowledge prepare OK - 1 byte - buf[0] = ota::OTA_RESPONSE_UPDATE_PREPARE_OK; - this->writeall_(buf, 1); + this->write_byte_(ota::OTA_RESPONSE_UPDATE_PREPARE_OK); // Read binary MD5, 32 bytes if (!this->readall_(buf, 32)) { @@ -326,8 +321,7 @@ void ESPHomeOTAComponent::handle_data_() { this->backend_->set_update_md5(sbuf); // Acknowledge MD5 OK - 1 byte - buf[0] = ota::OTA_RESPONSE_BIN_MD5_OK; - this->writeall_(buf, 1); + this->write_byte_(ota::OTA_RESPONSE_BIN_MD5_OK); while (total < ota_size) { // TODO: timeout check @@ -354,8 +348,7 @@ void ESPHomeOTAComponent::handle_data_() { total += read; #if USE_OTA_VERSION == 2 while (size_acknowledged + OTA_BLOCK_SIZE <= total || (total == ota_size && size_acknowledged < ota_size)) { - buf[0] = ota::OTA_RESPONSE_CHUNK_OK; - this->writeall_(buf, 1); + this->write_byte_(ota::OTA_RESPONSE_CHUNK_OK); size_acknowledged += OTA_BLOCK_SIZE; } #endif @@ -374,8 +367,7 @@ void ESPHomeOTAComponent::handle_data_() { } // Acknowledge receive OK - 1 byte - buf[0] = ota::OTA_RESPONSE_RECEIVE_OK; - this->writeall_(buf, 1); + this->write_byte_(ota::OTA_RESPONSE_RECEIVE_OK); error_code = this->backend_->end(); if (error_code != ota::OTA_RESPONSE_OK) { @@ -384,8 +376,7 @@ void ESPHomeOTAComponent::handle_data_() { } // Acknowledge Update end OK - 1 byte - buf[0] = ota::OTA_RESPONSE_UPDATE_END_OK; - this->writeall_(buf, 1); + this->write_byte_(ota::OTA_RESPONSE_UPDATE_END_OK); // Read ACK if (!this->readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) { @@ -404,8 +395,7 @@ void ESPHomeOTAComponent::handle_data_() { App.safe_reboot(); error: - buf[0] = static_cast(error_code); - this->writeall_(buf, 1); + this->write_byte_(static_cast(error_code)); this->cleanup_connection_(); if (this->backend_ != nullptr && update_started) { diff --git a/esphome/components/esphome/ota/ota_esphome.h b/esphome/components/esphome/ota/ota_esphome.h index d4a8410d35..057461e6a4 100644 --- a/esphome/components/esphome/ota/ota_esphome.h +++ b/esphome/components/esphome/ota/ota_esphome.h @@ -53,6 +53,7 @@ class ESPHomeOTAComponent : public ota::OTAComponent { #endif // USE_OTA_PASSWORD bool readall_(uint8_t *buf, size_t len); bool writeall_(const uint8_t *buf, size_t len); + inline bool write_byte_(uint8_t byte) { return this->writeall_(&byte, 1); } bool try_read_(size_t to_read, const LogString *desc); bool try_write_(size_t to_write, const LogString *desc); From 17ab20ef6108bcf4c3c2f67d2f022cbe71d9c00d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 19:05:36 -0600 Subject: [PATCH 0039/1145] [esp32_ble] Optimize loop() to reduce flash usage by ~104 bytes (#11627) --- esphome/components/esp32_ble/ble.cpp | 120 +++++++++++++-------------- 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 5bbd5fe9ed..69e317ff6d 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -31,6 +31,26 @@ namespace esphome::esp32_ble { static const char *const TAG = "esp32_ble"; +// GAP event groups for deduplication across gap_event_handler and dispatch_gap_event_ +#define GAP_SCAN_COMPLETE_EVENTS \ + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: \ + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: \ + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT + +#define GAP_ADV_COMPLETE_EVENTS \ + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: \ + case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: \ + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: \ + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: \ + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT + +#define GAP_SECURITY_EVENTS \ + case ESP_GAP_BLE_AUTH_CMPL_EVT: \ + case ESP_GAP_BLE_SEC_REQ_EVT: \ + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: \ + case ESP_GAP_BLE_PASSKEY_REQ_EVT: \ + case ESP_GAP_BLE_NC_REQ_EVT + void ESP32BLE::setup() { global_ble = this; if (!ble_pre_setup_()) { @@ -414,60 +434,48 @@ void ESP32BLE::loop() { break; // Scan complete events - case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: - case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: - case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: - // All three scan complete events have the same structure with just status - // The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe - // This is verified at compile-time by static_assert checks in ble_event.h - // The struct already contains our copy of the status (copied in BLEEvent constructor) - ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); -#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT - for (auto *gap_handler : this->gap_event_handlers_) { - gap_handler->gap_event_handler( - gap_event, reinterpret_cast(&ble_event->event_.gap.scan_complete)); - } -#endif - break; - + GAP_SCAN_COMPLETE_EVENTS: // Advertising complete events - case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: - case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: - case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: - case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: - case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: - // All advertising complete events have the same structure with just status - ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); -#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT - for (auto *gap_handler : this->gap_event_handlers_) { - gap_handler->gap_event_handler( - gap_event, reinterpret_cast(&ble_event->event_.gap.adv_complete)); - } -#endif - break; - + GAP_ADV_COMPLETE_EVENTS: // RSSI complete event case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: - ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); -#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT - for (auto *gap_handler : this->gap_event_handlers_) { - gap_handler->gap_event_handler( - gap_event, reinterpret_cast(&ble_event->event_.gap.read_rssi_complete)); - } -#endif - break; - // Security events - case ESP_GAP_BLE_AUTH_CMPL_EVT: - case ESP_GAP_BLE_SEC_REQ_EVT: - case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: - case ESP_GAP_BLE_PASSKEY_REQ_EVT: - case ESP_GAP_BLE_NC_REQ_EVT: + GAP_SECURITY_EVENTS: ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); #ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT - for (auto *gap_handler : this->gap_event_handlers_) { - gap_handler->gap_event_handler( - gap_event, reinterpret_cast(&ble_event->event_.gap.security)); + { + esp_ble_gap_cb_param_t *param; + // clang-format off + switch (gap_event) { + // All three scan complete events have the same structure with just status + // The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe + // This is verified at compile-time by static_assert checks in ble_event.h + // The struct already contains our copy of the status (copied in BLEEvent constructor) + GAP_SCAN_COMPLETE_EVENTS: + param = reinterpret_cast(&ble_event->event_.gap.scan_complete); + break; + + // All advertising complete events have the same structure with just status + GAP_ADV_COMPLETE_EVENTS: + param = reinterpret_cast(&ble_event->event_.gap.adv_complete); + break; + + case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: + param = reinterpret_cast(&ble_event->event_.gap.read_rssi_complete); + break; + + GAP_SECURITY_EVENTS: + param = reinterpret_cast(&ble_event->event_.gap.security); + break; + + default: + break; + } + // clang-format on + // Dispatch to all registered handlers + for (auto *gap_handler : this->gap_event_handlers_) { + gap_handler->gap_event_handler(gap_event, param); + } } #endif break; @@ -547,23 +555,13 @@ void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_pa // Queue GAP events that components need to handle // Scanning events - used by esp32_ble_tracker case ESP_GAP_BLE_SCAN_RESULT_EVT: - case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: - case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: - case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + GAP_SCAN_COMPLETE_EVENTS: // Advertising events - used by esp32_ble_beacon and esp32_ble server - case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: - case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: - case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: - case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: - case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + GAP_ADV_COMPLETE_EVENTS: // Connection events - used by ble_client case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: // Security events - used by ble_client and bluetooth_proxy - case ESP_GAP_BLE_AUTH_CMPL_EVT: - case ESP_GAP_BLE_SEC_REQ_EVT: - case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: - case ESP_GAP_BLE_PASSKEY_REQ_EVT: - case ESP_GAP_BLE_NC_REQ_EVT: + GAP_SECURITY_EVENTS: enqueue_ble_event(event, param); return; From 01ae86145a7e15f8c537798a6f6f3050596bcf99 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 19:06:40 -0600 Subject: [PATCH 0040/1145] [ble_client] Fix premature disconnections by reading characteristics immediately after service discovery (#11410) --- esphome/components/ble_client/sensor/ble_sensor.cpp | 3 +++ esphome/components/ble_client/text_sensor/ble_text_sensor.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index 6d293528c6..61685c0566 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -77,6 +77,9 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga } } else { this->node_state = espbt::ClientState::ESTABLISHED; + // For non-notify characteristics, trigger an immediate read after service discovery + // to avoid peripherals disconnecting due to inactivity + this->update(); } break; } diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index e7da297fa0..b7a6d154db 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -79,6 +79,9 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } } else { this->node_state = espbt::ClientState::ESTABLISHED; + // For non-notify characteristics, trigger an immediate read after service discovery + // to avoid peripherals disconnecting due to inactivity + this->update(); } break; } From 40f919eaa6f3fb60d461f745220d414e66ca80bf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 19:07:03 -0600 Subject: [PATCH 0041/1145] Add action continuation tests (#11674) --- tests/components/api/common-base.yaml | 96 +++++++ .../fixtures/continuation_actions.yaml | 174 +++++++++++++ .../integration/test_continuation_actions.py | 235 ++++++++++++++++++ 3 files changed, 505 insertions(+) create mode 100644 tests/integration/fixtures/continuation_actions.yaml create mode 100644 tests/integration/test_continuation_actions.py diff --git a/tests/components/api/common-base.yaml b/tests/components/api/common-base.yaml index 6483d5a997..c90fa4dfef 100644 --- a/tests/components/api/common-base.yaml +++ b/tests/components/api/common-base.yaml @@ -87,3 +87,99 @@ api: - float_arr.size() - string_arr[0].c_str() - string_arr.size() + # Test ContinuationAction (IfAction with then/else branches) + - action: test_if_action + variables: + condition: bool + value: int + then: + - if: + condition: + lambda: 'return condition;' + then: + - logger.log: + format: "Condition true, value: %d" + args: ['value'] + else: + - logger.log: + format: "Condition false, value: %d" + args: ['value'] + - logger.log: "After if/else" + # Test nested IfAction (multiple ContinuationAction instances) + - action: test_nested_if + variables: + outer: bool + inner: bool + then: + - if: + condition: + lambda: 'return outer;' + then: + - if: + condition: + lambda: 'return inner;' + then: + - logger.log: "Both true" + else: + - logger.log: "Outer true, inner false" + else: + - logger.log: "Outer false" + - logger.log: "After nested if" + # Test WhileLoopContinuation (WhileAction) + - action: test_while_action + variables: + max_count: int + then: + - lambda: 'id(api_continuation_test_counter) = 0;' + - while: + condition: + lambda: 'return id(api_continuation_test_counter) < max_count;' + then: + - logger.log: + format: "While loop iteration: %d" + args: ['id(api_continuation_test_counter)'] + - lambda: 'id(api_continuation_test_counter)++;' + - logger.log: "After while loop" + # Test RepeatLoopContinuation (RepeatAction) + - action: test_repeat_action + variables: + count: int + then: + - repeat: + count: !lambda 'return count;' + then: + - logger.log: + format: "Repeat iteration: %d" + args: ['iteration'] + - logger.log: "After repeat" + # Test combined continuations (if + while + repeat) + - action: test_combined_continuations + variables: + do_loop: bool + loop_count: int + then: + - if: + condition: + lambda: 'return do_loop;' + then: + - repeat: + count: !lambda 'return loop_count;' + then: + - lambda: 'id(api_continuation_test_counter) = iteration;' + - while: + condition: + lambda: 'return id(api_continuation_test_counter) > 0;' + then: + - logger.log: + format: "Combined: repeat=%d, while=%d" + args: ['iteration', 'id(api_continuation_test_counter)'] + - lambda: 'id(api_continuation_test_counter)--;' + else: + - logger.log: "Skipped loops" + - logger.log: "After combined test" + +globals: + - id: api_continuation_test_counter + type: int + restore_value: false + initial_value: '0' diff --git a/tests/integration/fixtures/continuation_actions.yaml b/tests/integration/fixtures/continuation_actions.yaml new file mode 100644 index 0000000000..bdfe149cb7 --- /dev/null +++ b/tests/integration/fixtures/continuation_actions.yaml @@ -0,0 +1,174 @@ +esphome: + name: test-continuation-actions + +host: + +api: + actions: + # Test 1: IfAction with ContinuationAction (then/else branches) + - action: test_if_action + variables: + condition: bool + value: int + then: + - logger.log: + format: "Test if: condition=%s, value=%d" + args: ['YESNO(condition)', 'value'] + - if: + condition: + lambda: 'return condition;' + then: + - logger.log: + format: "if-then executed: value=%d" + args: ['value'] + else: + - logger.log: + format: "if-else executed: value=%d" + args: ['value'] + - logger.log: "if completed" + + # Test 2: Nested IfAction (multiple ContinuationAction instances) + - action: test_nested_if + variables: + outer: bool + inner: bool + then: + - logger.log: + format: "Test nested if: outer=%s, inner=%s" + args: ['YESNO(outer)', 'YESNO(inner)'] + - if: + condition: + lambda: 'return outer;' + then: + - if: + condition: + lambda: 'return inner;' + then: + - logger.log: "nested-both-true" + else: + - logger.log: "nested-outer-true-inner-false" + else: + - logger.log: "nested-outer-false" + - logger.log: "nested if completed" + + # Test 3: WhileAction with WhileLoopContinuation + - action: test_while_action + variables: + max_count: int + then: + - logger.log: + format: "Test while: max_count=%d" + args: ['max_count'] + - globals.set: + id: continuation_test_counter + value: !lambda 'return 0;' + - while: + condition: + lambda: 'return id(continuation_test_counter) < max_count;' + then: + - logger.log: + format: "while-iteration-%d" + args: ['id(continuation_test_counter)'] + - globals.set: + id: continuation_test_counter + value: !lambda 'return id(continuation_test_counter) + 1;' + - logger.log: "while completed" + + # Test 4: RepeatAction with RepeatLoopContinuation + - action: test_repeat_action + variables: + count: int + then: + - logger.log: + format: "Test repeat: count=%d" + args: ['count'] + - repeat: + count: !lambda 'return count;' + then: + - logger.log: + format: "repeat-iteration-%d" + args: ['iteration'] + - logger.log: "repeat completed" + + # Test 5: Combined continuations (if + while + repeat) + - action: test_combined + variables: + do_loop: bool + loop_count: int + then: + - logger.log: + format: "Test combined: do_loop=%s, loop_count=%d" + args: ['YESNO(do_loop)', 'loop_count'] + - if: + condition: + lambda: 'return do_loop;' + then: + - repeat: + count: !lambda 'return loop_count;' + then: + - globals.set: + id: continuation_test_counter + value: !lambda 'return iteration;' + - while: + condition: + lambda: 'return id(continuation_test_counter) > 0;' + then: + - logger.log: + format: "combined-repeat%d-while%d" + args: ['iteration', 'id(continuation_test_counter)'] + - globals.set: + id: continuation_test_counter + value: !lambda 'return id(continuation_test_counter) - 1;' + else: + - logger.log: "combined-skipped" + - logger.log: "combined completed" + + # Test 6: Rapid triggers to verify memory efficiency + - action: test_rapid_if + then: + - logger.log: "=== Rapid if test start ===" + - sensor.template.publish: + id: rapid_sensor + state: 1 + - sensor.template.publish: + id: rapid_sensor + state: 2 + - sensor.template.publish: + id: rapid_sensor + state: 3 + - sensor.template.publish: + id: rapid_sensor + state: 4 + - sensor.template.publish: + id: rapid_sensor + state: 5 + - logger.log: "=== Rapid if test published 5 values ===" + +logger: + level: DEBUG + +globals: + - id: continuation_test_counter + type: int + restore_value: false + initial_value: '0' + +# Sensor to test rapid automation triggers with if/else (ContinuationAction) +sensor: + - platform: template + id: rapid_sensor + on_value: + - if: + condition: + lambda: 'return x > 2;' + then: + - logger.log: + format: "rapid-if-then: value=%d" + args: ['(int)x'] + else: + - logger.log: + format: "rapid-if-else: value=%d" + args: ['(int)x'] + - logger.log: + format: "rapid-if-completed: value=%d" + args: ['(int)x'] diff --git a/tests/integration/test_continuation_actions.py b/tests/integration/test_continuation_actions.py new file mode 100644 index 0000000000..1069ee7581 --- /dev/null +++ b/tests/integration/test_continuation_actions.py @@ -0,0 +1,235 @@ +"""Test continuation actions (ContinuationAction, WhileLoopContinuation, RepeatLoopContinuation).""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_continuation_actions( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """ + Test that continuation actions work correctly for if/while/repeat. + + These continuation classes replace LambdaAction with simple parent pointers, + saving 32-36 bytes per instance and eliminating std::function overhead. + """ + loop = asyncio.get_running_loop() + + # Track test completions + test_results = { + "if_then": False, + "if_else": False, + "if_complete": False, + "nested_both_true": False, + "nested_outer_true_inner_false": False, + "nested_outer_false": False, + "nested_complete": False, + "while_iterations": 0, + "while_complete": False, + "repeat_iterations": 0, + "repeat_complete": False, + "combined_iterations": 0, + "combined_complete": False, + "rapid_then": 0, + "rapid_else": 0, + "rapid_complete": 0, + } + + # Patterns for log messages + if_then_pattern = re.compile(r"if-then executed: value=(\d+)") + if_else_pattern = re.compile(r"if-else executed: value=(\d+)") + if_complete_pattern = re.compile(r"if completed") + nested_both_true_pattern = re.compile(r"nested-both-true") + nested_outer_true_inner_false_pattern = re.compile(r"nested-outer-true-inner-false") + nested_outer_false_pattern = re.compile(r"nested-outer-false") + nested_complete_pattern = re.compile(r"nested if completed") + while_iteration_pattern = re.compile(r"while-iteration-(\d+)") + while_complete_pattern = re.compile(r"while completed") + repeat_iteration_pattern = re.compile(r"repeat-iteration-(\d+)") + repeat_complete_pattern = re.compile(r"repeat completed") + combined_pattern = re.compile(r"combined-repeat(\d+)-while(\d+)") + combined_complete_pattern = re.compile(r"combined completed") + rapid_then_pattern = re.compile(r"rapid-if-then: value=(\d+)") + rapid_else_pattern = re.compile(r"rapid-if-else: value=(\d+)") + rapid_complete_pattern = re.compile(r"rapid-if-completed: value=(\d+)") + + # Test completion futures + test1_complete = loop.create_future() # if action + test2_complete = loop.create_future() # nested if + test3_complete = loop.create_future() # while + test4_complete = loop.create_future() # repeat + test5_complete = loop.create_future() # combined + test6_complete = loop.create_future() # rapid + + def check_output(line: str) -> None: + """Check log output for test messages.""" + # Test 1: IfAction + if if_then_pattern.search(line): + test_results["if_then"] = True + if if_else_pattern.search(line): + test_results["if_else"] = True + if if_complete_pattern.search(line): + test_results["if_complete"] = True + if not test1_complete.done(): + test1_complete.set_result(True) + + # Test 2: Nested IfAction + if nested_both_true_pattern.search(line): + test_results["nested_both_true"] = True + if nested_outer_true_inner_false_pattern.search(line): + test_results["nested_outer_true_inner_false"] = True + if nested_outer_false_pattern.search(line): + test_results["nested_outer_false"] = True + if nested_complete_pattern.search(line): + test_results["nested_complete"] = True + if not test2_complete.done(): + test2_complete.set_result(True) + + # Test 3: WhileAction + if match := while_iteration_pattern.search(line): + test_results["while_iterations"] = max( + test_results["while_iterations"], int(match.group(1)) + 1 + ) + if while_complete_pattern.search(line): + test_results["while_complete"] = True + if not test3_complete.done(): + test3_complete.set_result(True) + + # Test 4: RepeatAction + if match := repeat_iteration_pattern.search(line): + test_results["repeat_iterations"] = max( + test_results["repeat_iterations"], int(match.group(1)) + 1 + ) + if repeat_complete_pattern.search(line): + test_results["repeat_complete"] = True + if not test4_complete.done(): + test4_complete.set_result(True) + + # Test 5: Combined + if combined_pattern.search(line): + test_results["combined_iterations"] += 1 + if combined_complete_pattern.search(line): + test_results["combined_complete"] = True + if not test5_complete.done(): + test5_complete.set_result(True) + + # Test 6: Rapid triggers + if rapid_then_pattern.search(line): + test_results["rapid_then"] += 1 + if rapid_else_pattern.search(line): + test_results["rapid_else"] += 1 + if rapid_complete_pattern.search(line): + test_results["rapid_complete"] += 1 + if test_results["rapid_complete"] == 5 and not test6_complete.done(): + test6_complete.set_result(True) + + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Get services + _, services = await client.list_entities_services() + + # Test 1: IfAction with then branch + test_service = next((s for s in services if s.name == "test_if_action"), None) + assert test_service is not None, "test_if_action service not found" + client.execute_service(test_service, {"condition": True, "value": 42}) + await asyncio.wait_for(test1_complete, timeout=2.0) + assert test_results["if_then"], "IfAction then branch not executed" + assert test_results["if_complete"], "IfAction did not complete" + + # Test 1b: IfAction with else branch + test1_complete = loop.create_future() + test_results["if_complete"] = False + client.execute_service(test_service, {"condition": False, "value": 99}) + await asyncio.wait_for(test1_complete, timeout=2.0) + assert test_results["if_else"], "IfAction else branch not executed" + assert test_results["if_complete"], "IfAction did not complete" + + # Test 2: Nested IfAction - test all branches + test_service = next((s for s in services if s.name == "test_nested_if"), None) + assert test_service is not None, "test_nested_if service not found" + + # Both true + client.execute_service(test_service, {"outer": True, "inner": True}) + await asyncio.wait_for(test2_complete, timeout=2.0) + assert test_results["nested_both_true"], "Nested both true not executed" + + # Outer true, inner false + test2_complete = loop.create_future() + test_results["nested_complete"] = False + client.execute_service(test_service, {"outer": True, "inner": False}) + await asyncio.wait_for(test2_complete, timeout=2.0) + assert test_results["nested_outer_true_inner_false"], ( + "Nested outer true inner false not executed" + ) + + # Outer false + test2_complete = loop.create_future() + test_results["nested_complete"] = False + client.execute_service(test_service, {"outer": False, "inner": True}) + await asyncio.wait_for(test2_complete, timeout=2.0) + assert test_results["nested_outer_false"], "Nested outer false not executed" + + # Test 3: WhileAction + test_service = next( + (s for s in services if s.name == "test_while_action"), None + ) + assert test_service is not None, "test_while_action service not found" + client.execute_service(test_service, {"max_count": 3}) + await asyncio.wait_for(test3_complete, timeout=2.0) + assert test_results["while_iterations"] == 3, ( + f"WhileAction expected 3 iterations, got {test_results['while_iterations']}" + ) + assert test_results["while_complete"], "WhileAction did not complete" + + # Test 4: RepeatAction + test_service = next( + (s for s in services if s.name == "test_repeat_action"), None + ) + assert test_service is not None, "test_repeat_action service not found" + client.execute_service(test_service, {"count": 5}) + await asyncio.wait_for(test4_complete, timeout=2.0) + assert test_results["repeat_iterations"] == 5, ( + f"RepeatAction expected 5 iterations, got {test_results['repeat_iterations']}" + ) + assert test_results["repeat_complete"], "RepeatAction did not complete" + + # Test 5: Combined (if + repeat + while) + test_service = next((s for s in services if s.name == "test_combined"), None) + assert test_service is not None, "test_combined service not found" + client.execute_service(test_service, {"do_loop": True, "loop_count": 2}) + await asyncio.wait_for(test5_complete, timeout=2.0) + # Should execute: repeat 2 times, each iteration does while from iteration down to 0 + # iteration 0: while 0 times = 0 + # iteration 1: while 1 time = 1 + # Total: 1 combined log + assert test_results["combined_iterations"] >= 1, ( + f"Combined expected >=1 iterations, got {test_results['combined_iterations']}" + ) + assert test_results["combined_complete"], "Combined did not complete" + + # Test 6: Rapid triggers (tests memory efficiency of ContinuationAction) + test_service = next((s for s in services if s.name == "test_rapid_if"), None) + assert test_service is not None, "test_rapid_if service not found" + client.execute_service(test_service, {}) + await asyncio.wait_for(test6_complete, timeout=2.0) + # Values 1, 2 should hit else (<=2), values 3, 4, 5 should hit then (>2) + assert test_results["rapid_else"] == 2, ( + f"Rapid test expected 2 else, got {test_results['rapid_else']}" + ) + assert test_results["rapid_then"] == 3, ( + f"Rapid test expected 3 then, got {test_results['rapid_then']}" + ) + assert test_results["rapid_complete"] == 5, ( + f"Rapid test expected 5 completions, got {test_results['rapid_complete']}" + ) From 7a1297ec8428025075b48d52f3d32c1ccd8de8e3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 19:08:12 -0600 Subject: [PATCH 0042/1145] [web_server] Remove redundant assignment in deq_push_back_with_dedup_ (#11642) --- esphome/components/web_server/web_server.cpp | 3 +-- esphome/components/web_server_idf/web_server_idf.cpp | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 1d08ef5a35..61951e2600 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -111,8 +111,7 @@ void DeferredUpdateEventSource::deq_push_back_with_dedup_(void *source, message_ // Use range-based for loop instead of std::find_if to reduce template instantiation overhead and binary size for (auto &event : this->deferred_queue_) { if (event == item) { - event = item; - return; + return; // Already in queue, no need to update since items are equal } } this->deferred_queue_.push_back(item); diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index c3ba7ddc2b..ac0b5bad83 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -494,8 +494,7 @@ void AsyncEventSourceResponse::deq_push_back_with_dedup_(void *source, message_g // Use range-based for loop instead of std::find_if to reduce template instantiation overhead and binary size for (auto &event : this->deferred_queue_) { if (event == item) { - event = item; - return; + return; // Already in queue, no need to update since items are equal } } this->deferred_queue_.push_back(item); From 712421b82b6dc241329f25993d5500656e500f1b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 19:10:18 -0600 Subject: [PATCH 0043/1145] [web_server] Eliminate nested lambdas in DeferredUpdateEventSourceList (#11641) --- esphome/components/web_server/web_server.cpp | 61 ++++++++++---------- esphome/components/web_server/web_server.h | 2 +- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 61951e2600..cc62b019c3 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -219,50 +219,51 @@ void DeferredUpdateEventSourceList::add_new_client(WebServer *ws, AsyncWebServer DeferredUpdateEventSource *es = new DeferredUpdateEventSource(ws, "/events"); this->push_back(es); - es->onConnect([this, ws, es](AsyncEventSourceClient *client) { - ws->defer([this, ws, es]() { this->on_client_connect_(ws, es); }); - }); + es->onConnect([this, es](AsyncEventSourceClient *client) { this->on_client_connect_(es); }); - es->onDisconnect([this, ws, es](AsyncEventSourceClient *client) { - ws->defer([this, es]() { this->on_client_disconnect_((DeferredUpdateEventSource *) es); }); - }); + es->onDisconnect([this, es](AsyncEventSourceClient *client) { this->on_client_disconnect_(es); }); es->handleRequest(request); } -void DeferredUpdateEventSourceList::on_client_connect_(WebServer *ws, DeferredUpdateEventSource *source) { - // Configure reconnect timeout and send config - // this should always go through since the AsyncEventSourceClient event queue is empty on connect - std::string message = ws->get_config_json(); - source->try_send_nodefer(message.c_str(), "ping", millis(), 30000); +void DeferredUpdateEventSourceList::on_client_connect_(DeferredUpdateEventSource *source) { + WebServer *ws = source->web_server_; + ws->defer([ws, source]() { + // Configure reconnect timeout and send config + // this should always go through since the AsyncEventSourceClient event queue is empty on connect + std::string message = ws->get_config_json(); + source->try_send_nodefer(message.c_str(), "ping", millis(), 30000); #ifdef USE_WEBSERVER_SORTING - for (auto &group : ws->sorting_groups_) { - json::JsonBuilder builder; - JsonObject root = builder.root(); - root["name"] = group.second.name; - root["sorting_weight"] = group.second.weight; - message = builder.serialize(); + for (auto &group : ws->sorting_groups_) { + json::JsonBuilder builder; + JsonObject root = builder.root(); + root["name"] = group.second.name; + root["sorting_weight"] = group.second.weight; + message = builder.serialize(); - // up to 31 groups should be able to be queued initially without defer - source->try_send_nodefer(message.c_str(), "sorting_group"); - } + // up to 31 groups should be able to be queued initially without defer + source->try_send_nodefer(message.c_str(), "sorting_group"); + } #endif - source->entities_iterator_.begin(ws->include_internal_); + source->entities_iterator_.begin(ws->include_internal_); - // just dump them all up-front and take advantage of the deferred queue - // on second thought that takes too long, but leaving the commented code here for debug purposes - // while(!source->entities_iterator_.completed()) { - // source->entities_iterator_.advance(); - //} + // just dump them all up-front and take advantage of the deferred queue + // on second thought that takes too long, but leaving the commented code here for debug purposes + // while(!source->entities_iterator_.completed()) { + // source->entities_iterator_.advance(); + //} + }); } void DeferredUpdateEventSourceList::on_client_disconnect_(DeferredUpdateEventSource *source) { - // This method was called via WebServer->defer() and is no longer executing in the - // context of the network callback. The object is now dead and can be safely deleted. - this->remove(source); - delete source; // NOLINT + source->web_server_->defer([this, source]() { + // This method was called via WebServer->defer() and is no longer executing in the + // context of the network callback. The object is now dead and can be safely deleted. + this->remove(source); + delete source; // NOLINT + }); } #endif diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 2e5d58d375..c54f5558a9 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -141,7 +141,7 @@ class DeferredUpdateEventSource : public AsyncEventSource { class DeferredUpdateEventSourceList : public std::list { protected: - void on_client_connect_(WebServer *ws, DeferredUpdateEventSource *source); + void on_client_connect_(DeferredUpdateEventSource *source); void on_client_disconnect_(DeferredUpdateEventSource *source); public: From 4838eff3829831d2a6b1390de1c848e52be62fca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 19:12:56 -0600 Subject: [PATCH 0044/1145] [web_server] Use zero-copy entity ID comparison in request handlers (#11644) --- esphome/components/web_server/web_server.cpp | 52 +++++++++++--------- esphome/components/web_server/web_server.h | 11 ++++- esphome/core/entity_base.h | 5 ++ 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index cc62b019c3..fe0da14435 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -435,9 +435,10 @@ void WebServer::on_sensor_update(sensor::Sensor *obj, float state) { } void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (sensor::Sensor *obj : App.get_sensors()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + // Note: request->method() is always HTTP_GET here (canHandle ensures this) + if (match.method_empty()) { auto detail = get_request_detail(request); std::string data = this->sensor_json(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); @@ -477,9 +478,10 @@ void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s } void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (text_sensor::TextSensor *obj : App.get_text_sensors()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + // Note: request->method() is always HTTP_GET here (canHandle ensures this) + if (match.method_empty()) { auto detail = get_request_detail(request); std::string data = this->text_sensor_json(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); @@ -516,7 +518,7 @@ void WebServer::on_switch_update(switch_::Switch *obj, bool state) { } void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (switch_::Switch *obj : App.get_switches()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { @@ -585,7 +587,7 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail #ifdef USE_BUTTON void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (button::Button *obj : App.get_buttons()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); @@ -627,9 +629,10 @@ void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) { } void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + // Note: request->method() is always HTTP_GET here (canHandle ensures this) + if (match.method_empty()) { auto detail = get_request_detail(request); std::string data = this->binary_sensor_json(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); @@ -665,7 +668,7 @@ void WebServer::on_fan_update(fan::Fan *obj) { } void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (fan::Fan *obj : App.get_fans()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { @@ -739,7 +742,7 @@ void WebServer::on_light_update(light::LightState *obj) { } void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (light::LightState *obj : App.get_lights()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { @@ -812,7 +815,7 @@ void WebServer::on_cover_update(cover::Cover *obj) { } void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (cover::Cover *obj : App.get_covers()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { @@ -897,7 +900,7 @@ void WebServer::on_number_update(number::Number *obj, float state) { } void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_numbers()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { @@ -962,7 +965,7 @@ void WebServer::on_date_update(datetime::DateEntity *obj) { } void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_dates()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); @@ -1017,7 +1020,7 @@ void WebServer::on_time_update(datetime::TimeEntity *obj) { } void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_times()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); @@ -1071,7 +1074,7 @@ void WebServer::on_datetime_update(datetime::DateTimeEntity *obj) { } void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_datetimes()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); @@ -1126,7 +1129,7 @@ void WebServer::on_text_update(text::Text *obj, const std::string &state) { } void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_texts()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { @@ -1180,7 +1183,7 @@ void WebServer::on_select_update(select::Select *obj, const std::string &state, } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_selects()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { @@ -1236,7 +1239,7 @@ void WebServer::on_climate_update(climate::Climate *obj) { } void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_climates()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { @@ -1377,7 +1380,7 @@ void WebServer::on_lock_update(lock::Lock *obj) { } void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (lock::Lock *obj : App.get_locks()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { @@ -1448,7 +1451,7 @@ void WebServer::on_valve_update(valve::Valve *obj) { } void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (valve::Valve *obj : App.get_valves()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { @@ -1529,7 +1532,7 @@ void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP } void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (alarm_control_panel::AlarmControlPanel *obj : App.get_alarm_control_panels()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { @@ -1608,10 +1611,11 @@ void WebServer::on_event(event::Event *obj, const std::string &event_type) { void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (event::Event *obj : App.get_events()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + // Note: request->method() is always HTTP_GET here (canHandle ensures this) + if (match.method_empty()) { auto detail = get_request_detail(request); std::string data = this->event_json(obj, "", detail); request->send(200, "application/json", data.c_str()); @@ -1673,7 +1677,7 @@ void WebServer::on_update(update::UpdateEntity *obj) { } void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (update::UpdateEntity *obj : App.get_updates()) { - if (!match.id_equals(obj->get_object_id())) + if (!match.id_equals_entity(obj)) continue; if (request->method() == HTTP_GET && match.method_empty()) { diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index c54f5558a9..fb790483dc 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -48,8 +48,15 @@ struct UrlMatch { return domain && domain_len == strlen(str) && memcmp(domain, str, domain_len) == 0; } - bool id_equals(const std::string &str) const { - return id && id_len == str.length() && memcmp(id, str.c_str(), id_len) == 0; + bool id_equals_entity(EntityBase *entity) const { + // Zero-copy comparison using StringRef + StringRef static_ref = entity->get_object_id_ref_for_api_(); + if (!static_ref.empty()) { + return id && id_len == static_ref.size() && memcmp(id, static_ref.c_str(), id_len) == 0; + } + // Fallback to allocation (rare) + const auto &obj_id = entity->get_object_id(); + return id && id_len == obj_id.length() && memcmp(id, obj_id.c_str(), id_len) == 0; } bool method_equals(const char *str) const { diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 4a6460e708..80cd6b8e77 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -17,6 +17,10 @@ namespace api { class APIConnection; } // namespace api +namespace web_server { +struct UrlMatch; +} // namespace web_server + enum EntityCategory : uint8_t { ENTITY_CATEGORY_NONE = 0, ENTITY_CATEGORY_CONFIG = 1, @@ -116,6 +120,7 @@ class EntityBase { protected: friend class api::APIConnection; + friend struct web_server::UrlMatch; // Get object_id as StringRef when it's static (for API usage) // Returns empty StringRef if object_id is dynamic (needs allocation) From 34244afea1117c6042e786f5f99a1f026aa0c39c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 19:16:26 -0600 Subject: [PATCH 0045/1145] =?UTF-8?q?[esp32=5Fble]=20Reduce=20GATT=20event?= =?UTF-8?q?=20latency=20from=208ms=20to=2012=CE=BCs=20with=20notification?= =?UTF-8?q?=20socket=20(#11663)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- esphome/components/esp32_ble/__init__.py | 9 ++ esphome/components/esp32_ble/ble.cpp | 112 +++++++++++++++++++++++ esphome/components/esp32_ble/ble.h | 41 +++++++++ 3 files changed, 162 insertions(+) diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 411c2add71..1ae8df6f5e 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -7,6 +7,7 @@ from typing import Any from esphome import automation import esphome.codegen as cg +from esphome.components import socket from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant import esphome.config_validation as cv from esphome.const import ( @@ -481,6 +482,14 @@ async def to_code(config): cg.add(var.set_name(name)) await cg.register_component(var, config) + # BLE uses 1 UDP socket for event notification to wake up main loop from select() + # This enables low-latency (~12μs) BLE event processing instead of waiting for + # select() timeout (0-16ms). The socket is created in ble_setup_() and used to + # wake lwip_select() when BLE events arrive from the BLE thread. + # Note: Called during config generation, socket is created at runtime. In practice, + # always used since esp32_ble only runs on ESP32 which always has USE_SOCKET_SELECT_SUPPORT. + socket.consume_sockets(1, "esp32_ble")(config) + # Define max connections for use in C++ code (e.g., ble_server.h) max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS) cg.add_define("USE_ESP32_BLE_MAX_CONNECTIONS", max_connections) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 69e317ff6d..eef0db5347 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -27,6 +27,10 @@ extern "C" { #include #endif +#ifdef USE_SOCKET_SELECT_SUPPORT +#include +#endif + namespace esphome::esp32_ble { static const char *const TAG = "esp32_ble"; @@ -293,10 +297,21 @@ bool ESP32BLE::ble_setup_() { // BLE takes some time to be fully set up, 200ms should be more than enough delay(200); // NOLINT + // Set up notification socket to wake main loop for BLE events + // This enables low-latency (~12μs) event processing instead of waiting for select() timeout +#ifdef USE_SOCKET_SELECT_SUPPORT + this->setup_event_notification_(); +#endif + return true; } bool ESP32BLE::ble_dismantle_() { + // Clean up notification socket first before dismantling BLE stack +#ifdef USE_SOCKET_SELECT_SUPPORT + this->cleanup_event_notification_(); +#endif + esp_err_t err = esp_bluedroid_disable(); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err); @@ -394,6 +409,12 @@ void ESP32BLE::loop() { break; } +#ifdef USE_SOCKET_SELECT_SUPPORT + // Drain any notification socket events first + // This clears the socket so it doesn't stay "ready" in subsequent select() calls + this->drain_event_notifications_(); +#endif + BLEEvent *ble_event = this->ble_events_.pop(); while (ble_event != nullptr) { switch (ble_event->type_) { @@ -582,6 +603,10 @@ void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_pa void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { enqueue_ble_event(event, gatts_if, param); + // Wake up main loop to process GATT event immediately +#ifdef USE_SOCKET_SELECT_SUPPORT + global_ble->notify_main_loop_(); +#endif } #endif @@ -589,6 +614,10 @@ void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gat void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { enqueue_ble_event(event, gattc_if, param); + // Wake up main loop to process GATT event immediately +#ifdef USE_SOCKET_SELECT_SUPPORT + global_ble->notify_main_loop_(); +#endif } #endif @@ -628,6 +657,89 @@ void ESP32BLE::dump_config() { } } +#ifdef USE_SOCKET_SELECT_SUPPORT +void ESP32BLE::setup_event_notification_() { + // Create UDP socket for event notifications + this->notify_fd_ = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (this->notify_fd_ < 0) { + ESP_LOGW(TAG, "Event socket create failed: %d", errno); + return; + } + + // Bind to loopback with auto-assigned port + struct sockaddr_in addr = {}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = lwip_htonl(INADDR_LOOPBACK); + addr.sin_port = 0; // Auto-assign port + + if (lwip_bind(this->notify_fd_, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + ESP_LOGW(TAG, "Event socket bind failed: %d", errno); + lwip_close(this->notify_fd_); + this->notify_fd_ = -1; + return; + } + + // Get the assigned address and connect to it + // Connecting a UDP socket allows using send() instead of sendto() for better performance + struct sockaddr_in notify_addr; + socklen_t len = sizeof(notify_addr); + if (lwip_getsockname(this->notify_fd_, (struct sockaddr *) ¬ify_addr, &len) < 0) { + ESP_LOGW(TAG, "Event socket address failed: %d", errno); + lwip_close(this->notify_fd_); + this->notify_fd_ = -1; + return; + } + + // Connect to self (loopback) - allows using send() instead of sendto() + // After connect(), no need to store notify_addr - the socket remembers it + if (lwip_connect(this->notify_fd_, (struct sockaddr *) ¬ify_addr, sizeof(notify_addr)) < 0) { + ESP_LOGW(TAG, "Event socket connect failed: %d", errno); + lwip_close(this->notify_fd_); + this->notify_fd_ = -1; + return; + } + + // Set non-blocking mode + int flags = lwip_fcntl(this->notify_fd_, F_GETFL, 0); + lwip_fcntl(this->notify_fd_, F_SETFL, flags | O_NONBLOCK); + + // Register with application's select() loop + if (!App.register_socket_fd(this->notify_fd_)) { + ESP_LOGW(TAG, "Event socket register failed"); + lwip_close(this->notify_fd_); + this->notify_fd_ = -1; + return; + } + + ESP_LOGD(TAG, "Event socket ready"); +} + +void ESP32BLE::cleanup_event_notification_() { + if (this->notify_fd_ >= 0) { + App.unregister_socket_fd(this->notify_fd_); + lwip_close(this->notify_fd_); + this->notify_fd_ = -1; + ESP_LOGD(TAG, "Event socket closed"); + } +} + +void ESP32BLE::drain_event_notifications_() { + // Called from main loop to drain any pending notifications + // Must check is_socket_ready() to avoid blocking on empty socket + if (this->notify_fd_ >= 0 && App.is_socket_ready(this->notify_fd_)) { + char buffer[BLE_EVENT_NOTIFY_DRAIN_BUFFER_SIZE]; + // Drain all pending notifications with non-blocking reads + // Multiple BLE events may have triggered multiple writes, so drain until EWOULDBLOCK + // We control both ends of this loopback socket (always write 1 byte per event), + // so no error checking needed - any errors indicate catastrophic system failure + while (lwip_recvfrom(this->notify_fd_, buffer, sizeof(buffer), 0, nullptr, nullptr) > 0) { + // Just draining, no action needed - actual BLE events are already queued + } + } +} + +#endif // USE_SOCKET_SELECT_SUPPORT + uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) { uint64_t u = 0; u |= uint64_t(address[0] & 0xFF) << 40; diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index dc973f0e82..7c3195db6d 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -25,6 +25,10 @@ #include #include +#ifdef USE_SOCKET_SELECT_SUPPORT +#include +#endif + namespace esphome::esp32_ble { // Maximum size of the BLE event queue @@ -162,6 +166,13 @@ class ESP32BLE : public Component { void advertising_init_(); #endif +#ifdef USE_SOCKET_SELECT_SUPPORT + void setup_event_notification_(); // Create notification socket + void cleanup_event_notification_(); // Close and unregister socket + inline void notify_main_loop_(); // Wake up select() from BLE thread (hot path - inlined) + void drain_event_notifications_(); // Read pending notifications in main loop +#endif + private: template friend void enqueue_ble_event(Args... args); @@ -196,6 +207,13 @@ class ESP32BLE : public Component { esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; // 4 bytes (enum) uint32_t advertising_cycle_time_{}; // 4 bytes +#ifdef USE_SOCKET_SELECT_SUPPORT + // Event notification socket for waking up main loop from BLE thread + // Uses connected UDP loopback socket to wake lwip_select() with ~12μs latency vs 0-16ms timeout + // Socket is connected during setup, allowing use of send() instead of sendto() for efficiency + int notify_fd_{-1}; // 4 bytes (file descriptor) +#endif + // 2-byte aligned members uint16_t appearance_{0}; // 2 bytes @@ -207,6 +225,29 @@ class ESP32BLE : public Component { // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32BLE *global_ble; +#ifdef USE_SOCKET_SELECT_SUPPORT +// Inline implementations for hot-path functions +// These are called from BLE thread (notify) and main loop (drain) on every event + +// Small buffer for draining notification bytes (1 byte sent per BLE event) +// Size allows draining multiple notifications per recvfrom() without wasting stack +static constexpr size_t BLE_EVENT_NOTIFY_DRAIN_BUFFER_SIZE = 16; + +inline void ESP32BLE::notify_main_loop_() { + // Called from BLE thread context when events are queued + // Wakes up lwip_select() in main loop by writing to connected loopback socket + if (this->notify_fd_ >= 0) { + const char dummy = 1; + // Non-blocking send - if it fails (unlikely), select() will wake on timeout anyway + // No error checking needed: we control both ends of this loopback socket, and the + // BLE event is already queued. Notification is best-effort to reduce latency. + // This is safe to call from BLE thread - send() is thread-safe in lwip + // Socket is already connected to loopback address, so send() is faster than sendto() + lwip_send(this->notify_fd_, &dummy, 1, 0); + } +} +#endif // USE_SOCKET_SELECT_SUPPORT + template class BLEEnabledCondition : public Condition { public: bool check(Ts... x) override { return global_ble->is_active(); } From 3f05fd82e50d43ec878939049a7ef10119571ad2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 19:18:59 -0600 Subject: [PATCH 0046/1145] [fan] Use std::vector for preset modes, preserve config order (#11483) --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_pb2.cpp | 8 ++-- esphome/components/api/api_pb2.h | 2 +- esphome/components/fan/fan.cpp | 44 +++++++++++++------ esphome/components/fan/fan_traits.h | 38 +++++++--------- esphome/components/hbridge/fan/hbridge_fan.h | 6 +-- esphome/components/speed/fan/speed_fan.h | 6 +-- .../components/template/fan/template_fan.h | 6 +-- 9 files changed, 59 insertions(+), 55 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index f50944ffa4..20645fc47b 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -425,7 +425,7 @@ message ListEntitiesFanResponse { bool disabled_by_default = 9; string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON"]; EntityCategory entity_category = 11; - repeated string supported_preset_modes = 12 [(container_pointer) = "std::set"]; + repeated string supported_preset_modes = 12 [(container_pointer_no_template) = "std::vector"]; uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"]; } // Deprecated in API version 1.6 - only used in deprecated fields diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 382c4acc16..33d5072d9c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -423,7 +423,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con msg.supports_speed = traits.supports_speed(); msg.supports_direction = traits.supports_direction(); msg.supported_speed_count = traits.supported_speed_count(); - msg.supported_preset_modes = &traits.supported_preset_modes_for_api_(); + msg.supported_preset_modes = &traits.supported_preset_modes(); return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::fan_command(const FanCommandRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 3472707d3c..0673d35518 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -355,8 +355,8 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(10, this->icon_ref_); #endif buffer.encode_uint32(11, static_cast(this->entity_category)); - for (const auto &it : *this->supported_preset_modes) { - buffer.encode_string(12, it, true); + for (const char *it : *this->supported_preset_modes) { + buffer.encode_string(12, it, strlen(it), true); } #ifdef USE_DEVICES buffer.encode_uint32(13, this->device_id); @@ -376,8 +376,8 @@ void ListEntitiesFanResponse::calculate_size(ProtoSize &size) const { #endif size.add_uint32(1, static_cast(this->entity_category)); if (!this->supported_preset_modes->empty()) { - for (const auto &it : *this->supported_preset_modes) { - size.add_length_force(1, it.size()); + for (const char *it : *this->supported_preset_modes) { + size.add_length_force(1, strlen(it)); } } #ifdef USE_DEVICES diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index aa5c031ac7..89f16044d7 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -725,7 +725,7 @@ class ListEntitiesFanResponse final : public InfoResponseProtoMessage { bool supports_speed{false}; bool supports_direction{false}; int32_t supported_speed_count{0}; - const std::set *supported_preset_modes{}; + const std::vector *supported_preset_modes{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 26065ed644..5b4f437f99 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -51,7 +51,14 @@ void FanCall::validate_() { if (!this->preset_mode_.empty()) { const auto &preset_modes = traits.supported_preset_modes(); - if (preset_modes.find(this->preset_mode_) == preset_modes.end()) { + bool found = false; + for (const auto &mode : preset_modes) { + if (strcmp(mode, this->preset_mode_.c_str()) == 0) { + found = true; + break; + } + } + if (!found) { ESP_LOGW(TAG, "%s: Preset mode '%s' not supported", this->parent_.get_name().c_str(), this->preset_mode_.c_str()); this->preset_mode_.clear(); } @@ -92,11 +99,12 @@ FanCall FanRestoreState::to_call(Fan &fan) { call.set_speed(this->speed); call.set_direction(this->direction); - if (fan.get_traits().supports_preset_modes()) { + auto traits = fan.get_traits(); + if (traits.supports_preset_modes()) { // Use stored preset index to get preset name - const auto &preset_modes = fan.get_traits().supported_preset_modes(); + const auto &preset_modes = traits.supported_preset_modes(); if (this->preset_mode < preset_modes.size()) { - call.set_preset_mode(*std::next(preset_modes.begin(), this->preset_mode)); + call.set_preset_mode(preset_modes[this->preset_mode]); } } return call; @@ -107,11 +115,12 @@ void FanRestoreState::apply(Fan &fan) { fan.speed = this->speed; fan.direction = this->direction; - if (fan.get_traits().supports_preset_modes()) { + auto traits = fan.get_traits(); + if (traits.supports_preset_modes()) { // Use stored preset index to get preset name - const auto &preset_modes = fan.get_traits().supported_preset_modes(); + const auto &preset_modes = traits.supported_preset_modes(); if (this->preset_mode < preset_modes.size()) { - fan.preset_mode = *std::next(preset_modes.begin(), this->preset_mode); + fan.preset_mode = preset_modes[this->preset_mode]; } } fan.publish_state(); @@ -182,18 +191,25 @@ void Fan::save_state_() { return; } + auto traits = this->get_traits(); + FanRestoreState state{}; state.state = this->state; state.oscillating = this->oscillating; state.speed = this->speed; state.direction = this->direction; - if (this->get_traits().supports_preset_modes() && !this->preset_mode.empty()) { - const auto &preset_modes = this->get_traits().supported_preset_modes(); + if (traits.supports_preset_modes() && !this->preset_mode.empty()) { + const auto &preset_modes = traits.supported_preset_modes(); // Store index of current preset mode - auto preset_iterator = preset_modes.find(this->preset_mode); - if (preset_iterator != preset_modes.end()) - state.preset_mode = std::distance(preset_modes.begin(), preset_iterator); + size_t i = 0; + for (const auto &mode : preset_modes) { + if (strcmp(mode, this->preset_mode.c_str()) == 0) { + state.preset_mode = i; + break; + } + i++; + } } this->rtc_.save(&state); @@ -216,8 +232,8 @@ void Fan::dump_traits_(const char *tag, const char *prefix) { } if (traits.supports_preset_modes()) { ESP_LOGCONFIG(tag, "%s Supported presets:", prefix); - for (const std::string &s : traits.supported_preset_modes()) - ESP_LOGCONFIG(tag, "%s - %s", prefix, s.c_str()); + for (const char *s : traits.supported_preset_modes()) + ESP_LOGCONFIG(tag, "%s - %s", prefix, s); } } diff --git a/esphome/components/fan/fan_traits.h b/esphome/components/fan/fan_traits.h index 48509e5705..df345f9b04 100644 --- a/esphome/components/fan/fan_traits.h +++ b/esphome/components/fan/fan_traits.h @@ -1,15 +1,9 @@ -#include -#include - #pragma once -namespace esphome { +#include +#include -#ifdef USE_API -namespace api { -class APIConnection; -} // namespace api -#endif +namespace esphome { namespace fan { @@ -36,27 +30,27 @@ class FanTraits { /// Set whether this fan supports changing direction void set_direction(bool direction) { this->direction_ = direction; } /// Return the preset modes supported by the fan. - std::set supported_preset_modes() const { return this->preset_modes_; } - /// Set the preset modes supported by the fan. - void set_supported_preset_modes(const std::set &preset_modes) { this->preset_modes_ = preset_modes; } + const std::vector &supported_preset_modes() const { return this->preset_modes_; } + /// Set the preset modes supported by the fan (from initializer list). + void set_supported_preset_modes(std::initializer_list preset_modes) { + this->preset_modes_ = preset_modes; + } + /// Set the preset modes supported by the fan (from vector). + void set_supported_preset_modes(const std::vector &preset_modes) { this->preset_modes_ = preset_modes; } + + // Deleted overloads to catch incorrect std::string usage at compile time with clear error messages + void set_supported_preset_modes(const std::vector &preset_modes) = delete; + void set_supported_preset_modes(std::initializer_list preset_modes) = delete; + /// Return if preset modes are supported bool supports_preset_modes() const { return !this->preset_modes_.empty(); } protected: -#ifdef USE_API - // The API connection is a friend class to access internal methods - friend class api::APIConnection; - // This method returns a reference to the internal preset modes set. - // It is used by the API to avoid copying data when encoding messages. - // Warning: Do not use this method outside of the API connection code. - // It returns a reference to internal data that can be invalidated. - const std::set &supported_preset_modes_for_api_() const { return this->preset_modes_; } -#endif bool oscillation_{false}; bool speed_{false}; bool direction_{false}; int speed_count_{}; - std::set preset_modes_{}; + std::vector preset_modes_{}; }; } // namespace fan diff --git a/esphome/components/hbridge/fan/hbridge_fan.h b/esphome/components/hbridge/fan/hbridge_fan.h index 4234fccae3..143c7c1853 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.h +++ b/esphome/components/hbridge/fan/hbridge_fan.h @@ -1,7 +1,5 @@ #pragma once -#include - #include "esphome/core/automation.h" #include "esphome/components/output/binary_output.h" #include "esphome/components/output/float_output.h" @@ -22,7 +20,7 @@ class HBridgeFan : public Component, public fan::Fan { void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; } void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; } void set_enable_pin(output::FloatOutput *enable) { enable_ = enable; } - void set_preset_modes(const std::set &presets) { preset_modes_ = presets; } + void set_preset_modes(std::initializer_list presets) { preset_modes_ = presets; } void setup() override; void dump_config() override; @@ -38,7 +36,7 @@ class HBridgeFan : public Component, public fan::Fan { int speed_count_{}; DecayMode decay_mode_{DECAY_MODE_SLOW}; fan::FanTraits traits_; - std::set preset_modes_{}; + std::vector preset_modes_{}; void control(const fan::FanCall &call) override; void write_state_(); diff --git a/esphome/components/speed/fan/speed_fan.h b/esphome/components/speed/fan/speed_fan.h index 6537bce3f6..e9a389e0f3 100644 --- a/esphome/components/speed/fan/speed_fan.h +++ b/esphome/components/speed/fan/speed_fan.h @@ -1,7 +1,5 @@ #pragma once -#include - #include "esphome/core/component.h" #include "esphome/components/output/binary_output.h" #include "esphome/components/output/float_output.h" @@ -18,7 +16,7 @@ class SpeedFan : public Component, public fan::Fan { void set_output(output::FloatOutput *output) { this->output_ = output; } void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; } - void set_preset_modes(const std::set &presets) { this->preset_modes_ = presets; } + void set_preset_modes(std::initializer_list presets) { this->preset_modes_ = presets; } fan::FanTraits get_traits() override { return this->traits_; } protected: @@ -30,7 +28,7 @@ class SpeedFan : public Component, public fan::Fan { output::BinaryOutput *direction_{nullptr}; int speed_count_{}; fan::FanTraits traits_; - std::set preset_modes_{}; + std::vector preset_modes_{}; }; } // namespace speed diff --git a/esphome/components/template/fan/template_fan.h b/esphome/components/template/fan/template_fan.h index 7f5305ca48..b09352f4d4 100644 --- a/esphome/components/template/fan/template_fan.h +++ b/esphome/components/template/fan/template_fan.h @@ -1,7 +1,5 @@ #pragma once -#include - #include "esphome/core/component.h" #include "esphome/components/fan/fan.h" @@ -16,7 +14,7 @@ class TemplateFan : public Component, public fan::Fan { void set_has_direction(bool has_direction) { this->has_direction_ = has_direction; } void set_has_oscillating(bool has_oscillating) { this->has_oscillating_ = has_oscillating; } void set_speed_count(int count) { this->speed_count_ = count; } - void set_preset_modes(const std::set &presets) { this->preset_modes_ = presets; } + void set_preset_modes(std::initializer_list presets) { this->preset_modes_ = presets; } fan::FanTraits get_traits() override { return this->traits_; } protected: @@ -26,7 +24,7 @@ class TemplateFan : public Component, public fan::Fan { bool has_direction_{false}; int speed_count_{0}; fan::FanTraits traits_; - std::set preset_modes_{}; + std::vector preset_modes_{}; }; } // namespace template_ From cf76c3a74769f18254e24c9e6173974581966802 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 19:23:03 -0600 Subject: [PATCH 0047/1145] [web_server_idf] Reduce flash by eliminating temporary string allocations in event formatting (#11658) Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> --- .../web_server_idf/web_server_idf.cpp | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index ac0b5bad83..0dab5e7e8c 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -245,8 +246,8 @@ void AsyncWebServerRequest::redirect(const std::string &url) { } void AsyncWebServerRequest::init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type) { - // Set status code - use constants for common codes to avoid string allocation - const char *status = nullptr; + // Set status code - use constants for common codes, default to 500 for unknown codes + const char *status; switch (code) { case 200: status = HTTPD_200; @@ -258,9 +259,10 @@ void AsyncWebServerRequest::init_response_(AsyncWebServerResponse *rsp, int code status = HTTPD_409; break; default: + status = HTTPD_500; break; } - httpd_resp_set_status(*this, status == nullptr ? to_string(code).c_str() : status); + httpd_resp_set_status(*this, status); if (content_type && *content_type) { httpd_resp_set_type(*this, content_type); @@ -348,7 +350,13 @@ void AsyncWebServerResponse::addHeader(const char *name, const char *value) { httpd_resp_set_hdr(*this->req_, name, value); } -void AsyncResponseStream::print(float value) { this->print(to_string(value)); } +void AsyncResponseStream::print(float value) { + // Use stack buffer to avoid temporary string allocation + // Size: sign (1) + digits (10) + decimal (1) + precision (6) + exponent (5) + null (1) = 24, use 32 for safety + char buf[32]; + int len = snprintf(buf, sizeof(buf), "%f", value); + this->content_.append(buf, len); +} void AsyncResponseStream::printf(const char *fmt, ...) { va_list args; @@ -593,16 +601,19 @@ bool AsyncEventSourceResponse::try_send_nodefer(const char *message, const char event_buffer_.append(chunk_len_header); + // Use stack buffer for formatting numeric fields to avoid temporary string allocations + // Size: "retry: " (7) + max uint32 (10 digits) + CRLF (2) + null (1) = 20 bytes, use 32 for safety + constexpr size_t num_buf_size = 32; + char num_buf[num_buf_size]; + if (reconnect) { - event_buffer_.append("retry: ", sizeof("retry: ") - 1); - event_buffer_.append(to_string(reconnect)); - event_buffer_.append(CRLF_STR, CRLF_LEN); + int len = snprintf(num_buf, num_buf_size, "retry: %" PRIu32 CRLF_STR, reconnect); + event_buffer_.append(num_buf, len); } if (id) { - event_buffer_.append("id: ", sizeof("id: ") - 1); - event_buffer_.append(to_string(id)); - event_buffer_.append(CRLF_STR, CRLF_LEN); + int len = snprintf(num_buf, num_buf_size, "id: %" PRIu32 CRLF_STR, id); + event_buffer_.append(num_buf, len); } if (event && *event) { From 4a5e6576c8eac98dc9120e250325af2394e8ea8b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 19:29:29 -0600 Subject: [PATCH 0048/1145] [scheduler] Refactor call() for improved code organization (#11643) --- esphome/core/scheduler.cpp | 105 +++++++++++-------------------------- esphome/core/scheduler.h | 58 ++++++++++++++++++++ 2 files changed, 88 insertions(+), 75 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 0d4715f621..11d59c2499 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -316,59 +316,37 @@ optional HOT Scheduler::next_schedule_in(uint32_t now) { return 0; return next_exec - now_64; } + +void Scheduler::full_cleanup_removed_items_() { + // We hold the lock for the entire cleanup operation because: + // 1. We're rebuilding the entire items_ list, so we need exclusive access throughout + // 2. Other threads must see either the old state or the new state, not intermediate states + // 3. The operation is already expensive (O(n)), so lock overhead is negligible + // 4. No operations inside can block or take other locks, so no deadlock risk + LockGuard guard{this->lock_}; + + std::vector> valid_items; + + // Move all non-removed items to valid_items, recycle removed ones + for (auto &item : this->items_) { + if (!is_item_removed_(item.get())) { + valid_items.push_back(std::move(item)); + } else { + // Recycle removed items + this->recycle_item_(std::move(item)); + } + } + + // Replace items_ with the filtered list + this->items_ = std::move(valid_items); + // Rebuild the heap structure since items are no longer in heap order + std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp); + this->to_remove_ = 0; +} + void HOT Scheduler::call(uint32_t now) { #ifndef ESPHOME_THREAD_SINGLE - // Process defer queue first to guarantee FIFO execution order for deferred items. - // Previously, defer() used the heap which gave undefined order for equal timestamps, - // causing race conditions on multi-core systems (ESP32, BK7200). - // With the defer queue: - // - Deferred items (delay=0) go directly to defer_queue_ in set_timer_common_ - // - Items execute in exact order they were deferred (FIFO guarantee) - // - No deferred items exist in to_add_, so processing order doesn't affect correctness - // Single-core platforms don't use this queue and fall back to the heap-based approach. - // - // Note: Items cancelled via cancel_item_locked_() are marked with remove=true but still - // processed here. They are skipped during execution by should_skip_item_(). - // This is intentional - no memory leak occurs. - // - // We use an index (defer_queue_front_) to track the read position instead of calling - // erase() on every pop, which would be O(n). The queue is processed once per loop - - // any items added during processing are left for the next loop iteration. - - // Snapshot the queue end point - only process items that existed at loop start - // Items added during processing (by callbacks or other threads) run next loop - // No lock needed: single consumer (main loop), stale read just means we process less this iteration - size_t defer_queue_end = this->defer_queue_.size(); - - while (this->defer_queue_front_ < defer_queue_end) { - std::unique_ptr item; - { - LockGuard lock(this->lock_); - // SAFETY: Moving out the unique_ptr leaves a nullptr in the vector at defer_queue_front_. - // This is intentional and safe because: - // 1. The vector is only cleaned up by cleanup_defer_queue_locked_() at the end of this function - // 2. Any code iterating defer_queue_ MUST check for nullptr items (see mark_matching_items_removed_ - // and has_cancelled_timeout_in_container_ in scheduler.h) - // 3. The lock protects concurrent access, but the nullptr remains until cleanup - item = std::move(this->defer_queue_[this->defer_queue_front_]); - this->defer_queue_front_++; - } - - // Execute callback without holding lock to prevent deadlocks - // if the callback tries to call defer() again - if (!this->should_skip_item_(item.get())) { - now = this->execute_item_(item.get(), now); - } - // Recycle the defer item after execution - this->recycle_item_(std::move(item)); - } - - // If we've consumed all items up to the snapshot point, clean up the dead space - // Single consumer (main loop), so no lock needed for this check - if (this->defer_queue_front_ >= defer_queue_end) { - LockGuard lock(this->lock_); - this->cleanup_defer_queue_locked_(); - } + this->process_defer_queue_(now); #endif /* not ESPHOME_THREAD_SINGLE */ // Convert the fresh timestamp from main loop to 64-bit for scheduler operations @@ -429,30 +407,7 @@ void HOT Scheduler::call(uint32_t now) { // If we still have too many cancelled items, do a full cleanup // This only happens if cancelled items are stuck in the middle/bottom of the heap if (this->to_remove_ >= MAX_LOGICALLY_DELETED_ITEMS) { - // We hold the lock for the entire cleanup operation because: - // 1. We're rebuilding the entire items_ list, so we need exclusive access throughout - // 2. Other threads must see either the old state or the new state, not intermediate states - // 3. The operation is already expensive (O(n)), so lock overhead is negligible - // 4. No operations inside can block or take other locks, so no deadlock risk - LockGuard guard{this->lock_}; - - std::vector> valid_items; - - // Move all non-removed items to valid_items, recycle removed ones - for (auto &item : this->items_) { - if (!is_item_removed_(item.get())) { - valid_items.push_back(std::move(item)); - } else { - // Recycle removed items - this->recycle_item_(std::move(item)); - } - } - - // Replace items_ with the filtered list - this->items_ = std::move(valid_items); - // Rebuild the heap structure since items are no longer in heap order - std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp); - this->to_remove_ = 0; + this->full_cleanup_removed_items_(); } while (!this->items_.empty()) { // Don't copy-by value yet diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index df0be0e4ce..f6ec07294d 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -263,7 +263,65 @@ class Scheduler { // Helper to recycle a SchedulerItem void recycle_item_(std::unique_ptr item); + // Helper to perform full cleanup when too many items are cancelled + void full_cleanup_removed_items_(); + #ifndef ESPHOME_THREAD_SINGLE + // Helper to process defer queue - inline for performance in hot path + inline void process_defer_queue_(uint32_t &now) { + // Process defer queue first to guarantee FIFO execution order for deferred items. + // Previously, defer() used the heap which gave undefined order for equal timestamps, + // causing race conditions on multi-core systems (ESP32, BK7200). + // With the defer queue: + // - Deferred items (delay=0) go directly to defer_queue_ in set_timer_common_ + // - Items execute in exact order they were deferred (FIFO guarantee) + // - No deferred items exist in to_add_, so processing order doesn't affect correctness + // Single-core platforms don't use this queue and fall back to the heap-based approach. + // + // Note: Items cancelled via cancel_item_locked_() are marked with remove=true but still + // processed here. They are skipped during execution by should_skip_item_(). + // This is intentional - no memory leak occurs. + // + // We use an index (defer_queue_front_) to track the read position instead of calling + // erase() on every pop, which would be O(n). The queue is processed once per loop - + // any items added during processing are left for the next loop iteration. + + // Snapshot the queue end point - only process items that existed at loop start + // Items added during processing (by callbacks or other threads) run next loop + // No lock needed: single consumer (main loop), stale read just means we process less this iteration + size_t defer_queue_end = this->defer_queue_.size(); + + while (this->defer_queue_front_ < defer_queue_end) { + std::unique_ptr item; + { + LockGuard lock(this->lock_); + // SAFETY: Moving out the unique_ptr leaves a nullptr in the vector at defer_queue_front_. + // This is intentional and safe because: + // 1. The vector is only cleaned up by cleanup_defer_queue_locked_() at the end of this function + // 2. Any code iterating defer_queue_ MUST check for nullptr items (see mark_matching_items_removed_ + // and has_cancelled_timeout_in_container_ in scheduler.h) + // 3. The lock protects concurrent access, but the nullptr remains until cleanup + item = std::move(this->defer_queue_[this->defer_queue_front_]); + this->defer_queue_front_++; + } + + // Execute callback without holding lock to prevent deadlocks + // if the callback tries to call defer() again + if (!this->should_skip_item_(item.get())) { + now = this->execute_item_(item.get(), now); + } + // Recycle the defer item after execution + this->recycle_item_(std::move(item)); + } + + // If we've consumed all items up to the snapshot point, clean up the dead space + // Single consumer (main loop), so no lock needed for this check + if (this->defer_queue_front_ >= defer_queue_end) { + LockGuard lock(this->lock_); + this->cleanup_defer_queue_locked_(); + } + } + // Helper to cleanup defer_queue_ after processing // IMPORTANT: Caller must hold the scheduler lock before calling this function. inline void cleanup_defer_queue_locked_() { From 0f0cd1f706e31c35961578647f4d63c8e04dc768 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 19:40:13 -0600 Subject: [PATCH 0049/1145] [core] Avoid redundant millis() calls in base_automation loop methods (#11676) --- esphome/core/base_automation.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index e668a1782a..28af02a846 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -103,7 +103,7 @@ template class ForCondition : public Condition, public Co bool check_internal() { bool cond = this->condition_->check(); if (!cond) - this->last_inactive_ = millis(); + this->last_inactive_ = App.get_loop_component_start_time(); return cond; } @@ -380,7 +380,7 @@ template class WaitUntilAction : public Action, public Co if (this->num_running_ == 0) return; - auto now = millis(); + auto now = App.get_loop_component_start_time(); this->var_queue_.remove_if([&](auto &queued) { auto start = std::get(queued); From 4dd3c906635ae3964f6b12eab46bcbc7d44990f2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 20:55:17 -0600 Subject: [PATCH 0050/1145] [esp32_ble] Wake main loop for GAP security events (#11677) --- esphome/components/esp32_ble/ble.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index eef0db5347..d6f7e1ce43 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -581,9 +581,17 @@ void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_pa GAP_ADV_COMPLETE_EVENTS: // Connection events - used by ble_client case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: + enqueue_ble_event(event, param); + return; + // Security events - used by ble_client and bluetooth_proxy + // These are rare but interactive (pairing/bonding), so notify immediately GAP_SECURITY_EVENTS: enqueue_ble_event(event, param); + // Wake up main loop to process security event immediately +#ifdef USE_SOCKET_SELECT_SUPPORT + global_ble->notify_main_loop_(); +#endif return; // Ignore these GAP events as they are not relevant for our use case From a41c7b2b3c65aef775ae4d5ff70ce772e28d2c01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Nov 2025 22:16:38 -0600 Subject: [PATCH 0051/1145] Bump aioesphomeapi from 42.5.0 to 42.6.0 (#11682) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 660b18c933..33fa2b64eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==42.5.0 +aioesphomeapi==42.6.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.16 # dashboard_import From 42833c85f5fd50313f7dcf965434508aeb030c67 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Nov 2025 23:16:39 -0600 Subject: [PATCH 0052/1145] [climate] Replace std::vector with const char* for custom fan modes and presets (#11621) --- esphome/components/api/api.proto | 4 +- esphome/components/api/api_connection.cpp | 8 +- esphome/components/api/api_pb2.cpp | 16 +- esphome/components/api/api_pb2.h | 4 +- esphome/components/bedjet/bedjet_const.h | 1 - .../bedjet/climate/bedjet_climate.cpp | 68 +++--- .../bedjet/climate/bedjet_climate.h | 12 +- esphome/components/climate/climate.cpp | 223 ++++++++++++------ esphome/components/climate/climate.h | 64 ++++- esphome/components/climate/climate_traits.h | 85 +++++-- esphome/components/demo/demo_climate.h | 22 +- esphome/components/midea/ac_adapter.cpp | 18 +- esphome/components/midea/ac_adapter.h | 14 +- esphome/components/midea/air_conditioner.cpp | 14 +- esphome/components/midea/air_conditioner.h | 8 +- .../thermostat/thermostat_climate.cpp | 47 ++-- .../thermostat/thermostat_climate.h | 2 +- esphome/components/web_server/web_server.cpp | 10 +- script/api_protobuf/api_protobuf.py | 3 +- .../climate_custom_fan_modes_and_presets.yaml | 40 ++++ .../integration/test_climate_custom_modes.py | 42 ++++ 21 files changed, 475 insertions(+), 230 deletions(-) create mode 100644 tests/integration/fixtures/climate_custom_fan_modes_and_presets.yaml create mode 100644 tests/integration/test_climate_custom_modes.py diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 20645fc47b..7a50fa6b17 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1000,9 +1000,9 @@ message ListEntitiesClimateResponse { bool supports_action = 12; // Deprecated: use feature_flags repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer_no_template) = "climate::ClimateFanModeMask"]; repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer_no_template) = "climate::ClimateSwingModeMask"]; - repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::vector"]; + repeated string supported_custom_fan_modes = 15 [(container_pointer_no_template) = "std::vector"]; repeated ClimatePreset supported_presets = 16 [(container_pointer_no_template) = "climate::ClimatePresetMask"]; - repeated string supported_custom_presets = 17 [(container_pointer) = "std::vector"]; + repeated string supported_custom_presets = 17 [(container_pointer_no_template) = "std::vector"]; bool disabled_by_default = 18; string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"]; EntityCategory entity_category = 20; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 33d5072d9c..f8490b6ef7 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -637,14 +637,14 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection } if (traits.get_supports_fan_modes() && climate->fan_mode.has_value()) resp.fan_mode = static_cast(climate->fan_mode.value()); - if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) { - resp.set_custom_fan_mode(StringRef(climate->custom_fan_mode.value())); + if (!traits.get_supported_custom_fan_modes().empty() && climate->has_custom_fan_mode()) { + resp.set_custom_fan_mode(StringRef(climate->get_custom_fan_mode())); } if (traits.get_supports_presets() && climate->preset.has_value()) { resp.preset = static_cast(climate->preset.value()); } - if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) { - resp.set_custom_preset(StringRef(climate->custom_preset.value())); + if (!traits.get_supported_custom_presets().empty() && climate->has_custom_preset()) { + resp.set_custom_preset(StringRef(climate->get_custom_preset())); } if (traits.get_supports_swing_modes()) resp.swing_mode = static_cast(climate->swing_mode); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 0673d35518..dfa1a1320f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1179,14 +1179,14 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { for (const auto &it : *this->supported_swing_modes) { buffer.encode_uint32(14, static_cast(it), true); } - for (const auto &it : *this->supported_custom_fan_modes) { - buffer.encode_string(15, it, true); + for (const char *it : *this->supported_custom_fan_modes) { + buffer.encode_string(15, it, strlen(it), true); } for (const auto &it : *this->supported_presets) { buffer.encode_uint32(16, static_cast(it), true); } - for (const auto &it : *this->supported_custom_presets) { - buffer.encode_string(17, it, true); + for (const char *it : *this->supported_custom_presets) { + buffer.encode_string(17, it, strlen(it), true); } buffer.encode_bool(18, this->disabled_by_default); #ifdef USE_ENTITY_ICON @@ -1229,8 +1229,8 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const { } } if (!this->supported_custom_fan_modes->empty()) { - for (const auto &it : *this->supported_custom_fan_modes) { - size.add_length_force(1, it.size()); + for (const char *it : *this->supported_custom_fan_modes) { + size.add_length_force(1, strlen(it)); } } if (!this->supported_presets->empty()) { @@ -1239,8 +1239,8 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const { } } if (!this->supported_custom_presets->empty()) { - for (const auto &it : *this->supported_custom_presets) { - size.add_length_force(2, it.size()); + for (const char *it : *this->supported_custom_presets) { + size.add_length_force(2, strlen(it)); } } size.add_bool(2, this->disabled_by_default); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 89f16044d7..716f1a6e9b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1384,9 +1384,9 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage { bool supports_action{false}; const climate::ClimateFanModeMask *supported_fan_modes{}; const climate::ClimateSwingModeMask *supported_swing_modes{}; - const std::vector *supported_custom_fan_modes{}; + const std::vector *supported_custom_fan_modes{}; const climate::ClimatePresetMask *supported_presets{}; - const std::vector *supported_custom_presets{}; + const std::vector *supported_custom_presets{}; float visual_current_temperature_step{0.0f}; bool supports_current_humidity{false}; bool supports_target_humidity{false}; diff --git a/esphome/components/bedjet/bedjet_const.h b/esphome/components/bedjet/bedjet_const.h index 0693be1092..10f403dd1a 100644 --- a/esphome/components/bedjet/bedjet_const.h +++ b/esphome/components/bedjet/bedjet_const.h @@ -100,7 +100,6 @@ enum BedjetCommand : uint8_t { static const uint8_t BEDJET_FAN_SPEED_COUNT = 20; static constexpr const char *const BEDJET_FAN_STEP_NAMES[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_; -static const std::string BEDJET_FAN_STEP_NAME_STRINGS[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_; } // namespace bedjet } // namespace esphome diff --git a/esphome/components/bedjet/climate/bedjet_climate.cpp b/esphome/components/bedjet/climate/bedjet_climate.cpp index f22d312b5a..716d4d4241 100644 --- a/esphome/components/bedjet/climate/bedjet_climate.cpp +++ b/esphome/components/bedjet/climate/bedjet_climate.cpp @@ -8,15 +8,15 @@ namespace bedjet { using namespace esphome::climate; -static const std::string *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) { +static const char *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) { if (fan_step < BEDJET_FAN_SPEED_COUNT) - return &BEDJET_FAN_STEP_NAME_STRINGS[fan_step]; + return BEDJET_FAN_STEP_NAMES[fan_step]; return nullptr; } -static uint8_t bedjet_fan_speed_to_step(const std::string &fan_step_percent) { +static uint8_t bedjet_fan_speed_to_step(const char *fan_step_percent) { for (int i = 0; i < BEDJET_FAN_SPEED_COUNT; i++) { - if (fan_step_percent == BEDJET_FAN_STEP_NAME_STRINGS[i]) { + if (strcmp(BEDJET_FAN_STEP_NAMES[i], fan_step_percent) == 0) { return i; } } @@ -48,7 +48,7 @@ void BedJetClimate::dump_config() { ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(mode))); } for (const auto &mode : traits.get_supported_custom_fan_modes()) { - ESP_LOGCONFIG(TAG, " - %s (c)", mode.c_str()); + ESP_LOGCONFIG(TAG, " - %s (c)", mode); } ESP_LOGCONFIG(TAG, " Supported presets:"); @@ -56,7 +56,7 @@ void BedJetClimate::dump_config() { ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_preset_to_string(preset))); } for (const auto &preset : traits.get_supported_custom_presets()) { - ESP_LOGCONFIG(TAG, " - %s (c)", preset.c_str()); + ESP_LOGCONFIG(TAG, " - %s (c)", preset); } } @@ -79,7 +79,7 @@ void BedJetClimate::reset_state_() { this->target_temperature = NAN; this->current_temperature = NAN; this->preset.reset(); - this->custom_preset.reset(); + this->clear_custom_preset_(); this->publish_state(); } @@ -120,7 +120,7 @@ void BedJetClimate::control(const ClimateCall &call) { if (button_result) { this->mode = mode; // We're using (custom) preset for Turbo, EXT HT, & M1-3 presets, so changing climate mode will clear those - this->custom_preset.reset(); + this->clear_custom_preset_(); this->preset.reset(); } } @@ -144,8 +144,7 @@ void BedJetClimate::control(const ClimateCall &call) { if (result) { this->mode = CLIMATE_MODE_HEAT; - this->preset = CLIMATE_PRESET_BOOST; - this->custom_preset.reset(); + this->set_preset_(CLIMATE_PRESET_BOOST); } } else if (preset == CLIMATE_PRESET_NONE && this->preset.has_value()) { if (this->mode == CLIMATE_MODE_HEAT && this->preset == CLIMATE_PRESET_BOOST) { @@ -153,7 +152,7 @@ void BedJetClimate::control(const ClimateCall &call) { result = this->parent_->send_button(heat_button(this->heating_mode_)); if (result) { this->preset.reset(); - this->custom_preset.reset(); + this->clear_custom_preset_(); } } else { ESP_LOGD(TAG, "Ignoring preset '%s' call; with current mode '%s' and preset '%s'", @@ -164,28 +163,27 @@ void BedJetClimate::control(const ClimateCall &call) { ESP_LOGW(TAG, "Unsupported preset: %d", preset); return; } - } else if (call.get_custom_preset().has_value()) { - std::string preset = *call.get_custom_preset(); + } else if (call.has_custom_preset()) { + const char *preset = call.get_custom_preset(); bool result; - if (preset == "M1") { + if (strcmp(preset, "M1") == 0) { result = this->parent_->button_memory1(); - } else if (preset == "M2") { + } else if (strcmp(preset, "M2") == 0) { result = this->parent_->button_memory2(); - } else if (preset == "M3") { + } else if (strcmp(preset, "M3") == 0) { result = this->parent_->button_memory3(); - } else if (preset == "LTD HT") { + } else if (strcmp(preset, "LTD HT") == 0) { result = this->parent_->button_heat(); - } else if (preset == "EXT HT") { + } else if (strcmp(preset, "EXT HT") == 0) { result = this->parent_->button_ext_heat(); } else { - ESP_LOGW(TAG, "Unsupported preset: %s", preset.c_str()); + ESP_LOGW(TAG, "Unsupported preset: %s", preset); return; } if (result) { - this->custom_preset = preset; - this->preset.reset(); + this->set_custom_preset_(preset); } } @@ -207,19 +205,16 @@ void BedJetClimate::control(const ClimateCall &call) { } if (result) { - this->fan_mode = fan_mode; - this->custom_fan_mode.reset(); + this->set_fan_mode_(fan_mode); } - } else if (call.get_custom_fan_mode().has_value()) { - auto fan_mode = *call.get_custom_fan_mode(); + } else if (call.has_custom_fan_mode()) { + const char *fan_mode = call.get_custom_fan_mode(); auto fan_index = bedjet_fan_speed_to_step(fan_mode); if (fan_index <= 19) { - ESP_LOGV(TAG, "[%s] Converted fan mode %s to bedjet fan step %d", this->get_name().c_str(), fan_mode.c_str(), - fan_index); + ESP_LOGV(TAG, "[%s] Converted fan mode %s to bedjet fan step %d", this->get_name().c_str(), fan_mode, fan_index); bool result = this->parent_->set_fan_index(fan_index); if (result) { - this->custom_fan_mode = fan_mode; - this->fan_mode.reset(); + this->set_custom_fan_mode_(fan_mode); } } } @@ -245,7 +240,7 @@ void BedJetClimate::on_status(const BedjetStatusPacket *data) { const auto *fan_mode_name = bedjet_fan_step_to_fan_mode(data->fan_step); if (fan_mode_name != nullptr) { - this->custom_fan_mode = *fan_mode_name; + this->set_custom_fan_mode_(fan_mode_name); } // TODO: Get biorhythm data to determine which preset (M1-3) is running, if any. @@ -255,7 +250,7 @@ void BedJetClimate::on_status(const BedjetStatusPacket *data) { this->mode = CLIMATE_MODE_OFF; this->action = CLIMATE_ACTION_IDLE; this->fan_mode = CLIMATE_FAN_OFF; - this->custom_preset.reset(); + this->clear_custom_preset_(); this->preset.reset(); break; @@ -266,7 +261,7 @@ void BedJetClimate::on_status(const BedjetStatusPacket *data) { if (this->heating_mode_ == HEAT_MODE_EXTENDED) { this->set_custom_preset_("LTD HT"); } else { - this->custom_preset.reset(); + this->clear_custom_preset_(); } break; @@ -275,7 +270,7 @@ void BedJetClimate::on_status(const BedjetStatusPacket *data) { this->action = CLIMATE_ACTION_HEATING; this->preset.reset(); if (this->heating_mode_ == HEAT_MODE_EXTENDED) { - this->custom_preset.reset(); + this->clear_custom_preset_(); } else { this->set_custom_preset_("EXT HT"); } @@ -284,20 +279,19 @@ void BedJetClimate::on_status(const BedjetStatusPacket *data) { case MODE_COOL: this->mode = CLIMATE_MODE_FAN_ONLY; this->action = CLIMATE_ACTION_COOLING; - this->custom_preset.reset(); + this->clear_custom_preset_(); this->preset.reset(); break; case MODE_DRY: this->mode = CLIMATE_MODE_DRY; this->action = CLIMATE_ACTION_DRYING; - this->custom_preset.reset(); + this->clear_custom_preset_(); this->preset.reset(); break; case MODE_TURBO: - this->preset = CLIMATE_PRESET_BOOST; - this->custom_preset.reset(); + this->set_preset_(CLIMATE_PRESET_BOOST); this->mode = CLIMATE_MODE_HEAT; this->action = CLIMATE_ACTION_HEATING; break; diff --git a/esphome/components/bedjet/climate/bedjet_climate.h b/esphome/components/bedjet/climate/bedjet_climate.h index dbbb73aeae..05f4a849e0 100644 --- a/esphome/components/bedjet/climate/bedjet_climate.h +++ b/esphome/components/bedjet/climate/bedjet_climate.h @@ -50,21 +50,13 @@ class BedJetClimate : public climate::Climate, public BedJetClient, public Polli // Climate doesn't have a "TURBO" mode, but we can use the BOOST preset instead. climate::CLIMATE_PRESET_BOOST, }); + // String literals are stored in rodata and valid for program lifetime traits.set_supported_custom_presets({ - // We could fetch biodata from bedjet and set these names that way. - // But then we have to invert the lookup in order to send the right preset. - // For now, we can leave them as M1-3 to match the remote buttons. - // EXT HT added to match remote button. - "EXT HT", + this->heating_mode_ == HEAT_MODE_EXTENDED ? "LTD HT" : "EXT HT", "M1", "M2", "M3", }); - if (this->heating_mode_ == HEAT_MODE_EXTENDED) { - traits.add_supported_custom_preset("LTD HT"); - } else { - traits.add_supported_custom_preset("EXT HT"); - } traits.set_visual_min_temperature(19.0); traits.set_visual_max_temperature(43.0); traits.set_visual_temperature_step(1.0); diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 944934edbf..7df38758dc 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -50,21 +50,21 @@ void ClimateCall::perform() { const LogString *mode_s = climate_mode_to_string(*this->mode_); ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(mode_s)); } - if (this->custom_fan_mode_.has_value()) { + if (this->custom_fan_mode_ != nullptr) { this->fan_mode_.reset(); - ESP_LOGD(TAG, " Custom Fan: %s", this->custom_fan_mode_.value().c_str()); + ESP_LOGD(TAG, " Custom Fan: %s", this->custom_fan_mode_); } if (this->fan_mode_.has_value()) { - this->custom_fan_mode_.reset(); + this->custom_fan_mode_ = nullptr; const LogString *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_); ESP_LOGD(TAG, " Fan: %s", LOG_STR_ARG(fan_mode_s)); } - if (this->custom_preset_.has_value()) { + if (this->custom_preset_ != nullptr) { this->preset_.reset(); - ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_.value().c_str()); + ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_); } if (this->preset_.has_value()) { - this->custom_preset_.reset(); + this->custom_preset_ = nullptr; const LogString *preset_s = climate_preset_to_string(*this->preset_); ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(preset_s)); } @@ -96,11 +96,10 @@ void ClimateCall::validate_() { this->mode_.reset(); } } - if (this->custom_fan_mode_.has_value()) { - auto custom_fan_mode = *this->custom_fan_mode_; - if (!traits.supports_custom_fan_mode(custom_fan_mode)) { - ESP_LOGW(TAG, " Fan Mode %s not supported", custom_fan_mode.c_str()); - this->custom_fan_mode_.reset(); + if (this->custom_fan_mode_ != nullptr) { + if (!traits.supports_custom_fan_mode(this->custom_fan_mode_)) { + ESP_LOGW(TAG, " Fan Mode %s not supported", this->custom_fan_mode_); + this->custom_fan_mode_ = nullptr; } } else if (this->fan_mode_.has_value()) { auto fan_mode = *this->fan_mode_; @@ -109,11 +108,10 @@ void ClimateCall::validate_() { this->fan_mode_.reset(); } } - if (this->custom_preset_.has_value()) { - auto custom_preset = *this->custom_preset_; - if (!traits.supports_custom_preset(custom_preset)) { - ESP_LOGW(TAG, " Preset %s not supported", custom_preset.c_str()); - this->custom_preset_.reset(); + if (this->custom_preset_ != nullptr) { + if (!traits.supports_custom_preset(this->custom_preset_)) { + ESP_LOGW(TAG, " Preset %s not supported", this->custom_preset_); + this->custom_preset_ = nullptr; } } else if (this->preset_.has_value()) { auto preset = *this->preset_; @@ -186,26 +184,29 @@ ClimateCall &ClimateCall::set_mode(const std::string &mode) { ClimateCall &ClimateCall::set_fan_mode(ClimateFanMode fan_mode) { this->fan_mode_ = fan_mode; - this->custom_fan_mode_.reset(); + this->custom_fan_mode_ = nullptr; return *this; } -ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { +ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode) { + // Check if it's a standard enum mode first for (const auto &mode_entry : CLIMATE_FAN_MODES_BY_STR) { - if (str_equals_case_insensitive(fan_mode, mode_entry.str)) { - this->set_fan_mode(static_cast(mode_entry.value)); - return *this; + if (str_equals_case_insensitive(custom_fan_mode, mode_entry.str)) { + return this->set_fan_mode(static_cast(mode_entry.value)); } } - if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) { - this->custom_fan_mode_ = fan_mode; + // Find the matching pointer from parent climate device + if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode)) { + this->custom_fan_mode_ = mode_ptr; this->fan_mode_.reset(); - } else { - ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), fan_mode.c_str()); + return *this; } + ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), custom_fan_mode); return *this; } +ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { return this->set_fan_mode(fan_mode.c_str()); } + ClimateCall &ClimateCall::set_fan_mode(optional fan_mode) { if (fan_mode.has_value()) { this->set_fan_mode(fan_mode.value()); @@ -215,26 +216,29 @@ ClimateCall &ClimateCall::set_fan_mode(optional fan_mode) { ClimateCall &ClimateCall::set_preset(ClimatePreset preset) { this->preset_ = preset; - this->custom_preset_.reset(); + this->custom_preset_ = nullptr; return *this; } -ClimateCall &ClimateCall::set_preset(const std::string &preset) { +ClimateCall &ClimateCall::set_preset(const char *custom_preset) { + // Check if it's a standard enum preset first for (const auto &preset_entry : CLIMATE_PRESETS_BY_STR) { - if (str_equals_case_insensitive(preset, preset_entry.str)) { - this->set_preset(static_cast(preset_entry.value)); - return *this; + if (str_equals_case_insensitive(custom_preset, preset_entry.str)) { + return this->set_preset(static_cast(preset_entry.value)); } } - if (this->parent_->get_traits().supports_custom_preset(preset)) { - this->custom_preset_ = preset; + // Find the matching pointer from parent climate device + if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset)) { + this->custom_preset_ = preset_ptr; this->preset_.reset(); - } else { - ESP_LOGW(TAG, "'%s' - Unrecognized preset %s", this->parent_->get_name().c_str(), preset.c_str()); + return *this; } + ESP_LOGW(TAG, "'%s' - Unrecognized preset %s", this->parent_->get_name().c_str(), custom_preset); return *this; } +ClimateCall &ClimateCall::set_preset(const std::string &preset) { return this->set_preset(preset.c_str()); } + ClimateCall &ClimateCall::set_preset(optional preset) { if (preset.has_value()) { this->set_preset(preset.value()); @@ -287,8 +291,6 @@ const optional &ClimateCall::get_mode() const { return this->mode_; const optional &ClimateCall::get_fan_mode() const { return this->fan_mode_; } const optional &ClimateCall::get_swing_mode() const { return this->swing_mode_; } const optional &ClimateCall::get_preset() const { return this->preset_; } -const optional &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; } -const optional &ClimateCall::get_custom_preset() const { return this->custom_preset_; } ClimateCall &ClimateCall::set_target_temperature_high(optional target_temperature_high) { this->target_temperature_high_ = target_temperature_high; @@ -317,13 +319,13 @@ ClimateCall &ClimateCall::set_mode(optional mode) { ClimateCall &ClimateCall::set_fan_mode(optional fan_mode) { this->fan_mode_ = fan_mode; - this->custom_fan_mode_.reset(); + this->custom_fan_mode_ = nullptr; return *this; } ClimateCall &ClimateCall::set_preset(optional preset) { this->preset_ = preset; - this->custom_preset_.reset(); + this->custom_preset_ = nullptr; return *this; } @@ -382,13 +384,13 @@ void Climate::save_state_() { state.uses_custom_fan_mode = false; state.fan_mode = this->fan_mode.value(); } - if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) { + if (!traits.get_supported_custom_fan_modes().empty() && this->has_custom_fan_mode()) { state.uses_custom_fan_mode = true; const auto &supported = traits.get_supported_custom_fan_modes(); // std::vector maintains insertion order size_t i = 0; - for (const auto &mode : supported) { - if (mode == custom_fan_mode) { + for (const char *mode : supported) { + if (strcmp(mode, this->custom_fan_mode_) == 0) { state.custom_fan_mode = i; break; } @@ -399,13 +401,13 @@ void Climate::save_state_() { state.uses_custom_preset = false; state.preset = this->preset.value(); } - if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) { + if (!traits.get_supported_custom_presets().empty() && this->has_custom_preset()) { state.uses_custom_preset = true; const auto &supported = traits.get_supported_custom_presets(); // std::vector maintains insertion order size_t i = 0; - for (const auto &preset : supported) { - if (preset == custom_preset) { + for (const char *preset : supported) { + if (strcmp(preset, this->custom_preset_) == 0) { state.custom_preset = i; break; } @@ -430,14 +432,14 @@ void Climate::publish_state() { if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) { ESP_LOGD(TAG, " Fan Mode: %s", LOG_STR_ARG(climate_fan_mode_to_string(this->fan_mode.value()))); } - if (!traits.get_supported_custom_fan_modes().empty() && this->custom_fan_mode.has_value()) { - ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode.value().c_str()); + if (!traits.get_supported_custom_fan_modes().empty() && this->has_custom_fan_mode()) { + ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode_); } if (traits.get_supports_presets() && this->preset.has_value()) { ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(climate_preset_to_string(this->preset.value()))); } - if (!traits.get_supported_custom_presets().empty() && this->custom_preset.has_value()) { - ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset.value().c_str()); + if (!traits.get_supported_custom_presets().empty() && this->has_custom_preset()) { + ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_); } if (traits.get_supports_swing_modes()) { ESP_LOGD(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode))); @@ -527,7 +529,7 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) { if (this->uses_custom_fan_mode) { if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) { call.fan_mode_.reset(); - call.custom_fan_mode_ = *std::next(traits.get_supported_custom_fan_modes().cbegin(), this->custom_fan_mode); + call.custom_fan_mode_ = traits.get_supported_custom_fan_modes()[this->custom_fan_mode]; } } else if (traits.supports_fan_mode(this->fan_mode)) { call.set_fan_mode(this->fan_mode); @@ -535,7 +537,7 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) { if (this->uses_custom_preset) { if (this->custom_preset < traits.get_supported_custom_presets().size()) { call.preset_.reset(); - call.custom_preset_ = *std::next(traits.get_supported_custom_presets().cbegin(), this->custom_preset); + call.custom_preset_ = traits.get_supported_custom_presets()[this->custom_preset]; } } else if (traits.supports_preset(this->preset)) { call.set_preset(this->preset); @@ -562,20 +564,20 @@ void ClimateDeviceRestoreState::apply(Climate *climate) { if (this->uses_custom_fan_mode) { if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) { climate->fan_mode.reset(); - climate->custom_fan_mode = *std::next(traits.get_supported_custom_fan_modes().cbegin(), this->custom_fan_mode); + climate->custom_fan_mode_ = traits.get_supported_custom_fan_modes()[this->custom_fan_mode]; } } else if (traits.supports_fan_mode(this->fan_mode)) { climate->fan_mode = this->fan_mode; - climate->custom_fan_mode.reset(); + climate->clear_custom_fan_mode_(); } if (this->uses_custom_preset) { if (this->custom_preset < traits.get_supported_custom_presets().size()) { climate->preset.reset(); - climate->custom_preset = *std::next(traits.get_supported_custom_presets().cbegin(), this->custom_preset); + climate->custom_preset_ = traits.get_supported_custom_presets()[this->custom_preset]; } } else if (traits.supports_preset(this->preset)) { climate->preset = this->preset; - climate->custom_preset.reset(); + climate->clear_custom_preset_(); } if (traits.supports_swing_mode(this->swing_mode)) { climate->swing_mode = this->swing_mode; @@ -583,28 +585,107 @@ void ClimateDeviceRestoreState::apply(Climate *climate) { climate->publish_state(); } -template bool set_alternative(optional &dst, optional &alt, const T1 &src) { - bool is_changed = alt.has_value(); - alt.reset(); - if (is_changed || dst != src) { - dst = src; - is_changed = true; +/** Template helper for setting primary modes (fan_mode, preset) with mutual exclusion. + * + * Climate devices have mutually exclusive mode pairs: + * - fan_mode (enum) vs custom_fan_mode_ (const char*) + * - preset (enum) vs custom_preset_ (const char*) + * + * Only one mode in each pair can be active at a time. This helper ensures setting a primary + * mode automatically clears its corresponding custom mode. + * + * Example state transitions: + * Before: custom_fan_mode_="Turbo", fan_mode=nullopt + * Call: set_fan_mode_(CLIMATE_FAN_HIGH) + * After: custom_fan_mode_=nullptr, fan_mode=CLIMATE_FAN_HIGH + * + * @param primary The primary mode optional (fan_mode or preset) + * @param custom_ptr Reference to the custom mode pointer (custom_fan_mode_ or custom_preset_) + * @param value The new primary mode value to set + * @return true if state changed, false if already set to this value + */ +template bool set_primary_mode(optional &primary, const char *&custom_ptr, T value) { + // Clear the custom mode (mutual exclusion) + bool changed = custom_ptr != nullptr; + custom_ptr = nullptr; + // Set the primary mode + if (changed || !primary.has_value() || primary.value() != value) { + primary = value; + return true; } - return is_changed; + return false; +} + +/** Template helper for setting custom modes (custom_fan_mode_, custom_preset_) with mutual exclusion. + * + * This helper ensures setting a custom mode automatically clears its corresponding primary mode. + * It also validates that the custom mode exists in the device's supported modes (lifetime safety). + * + * Example state transitions: + * Before: fan_mode=CLIMATE_FAN_HIGH, custom_fan_mode_=nullptr + * Call: set_custom_fan_mode_("Turbo") + * After: fan_mode=nullopt, custom_fan_mode_="Turbo" (pointer from traits) + * + * Lifetime Safety: + * - found_ptr must come from traits.find_custom_*_mode_() + * - Only pointers found in traits are stored, ensuring they remain valid + * - Prevents dangling pointers from temporary strings + * + * @param custom_ptr Reference to the custom mode pointer to set + * @param primary The primary mode optional to clear + * @param found_ptr The validated pointer from traits (nullptr if not found) + * @param has_custom Whether a custom mode is currently active + * @return true if state changed, false otherwise + */ +template +bool set_custom_mode(const char *&custom_ptr, optional &primary, const char *found_ptr, bool has_custom) { + if (found_ptr != nullptr) { + // Clear the primary mode (mutual exclusion) + bool changed = primary.has_value(); + primary.reset(); + // Set the custom mode (pointer is validated by caller from traits) + if (changed || custom_ptr != found_ptr) { + custom_ptr = found_ptr; + return true; + } + return false; + } + // Mode not found in supported modes, clear it if currently set + if (has_custom) { + custom_ptr = nullptr; + return true; + } + return false; } bool Climate::set_fan_mode_(ClimateFanMode mode) { - return set_alternative(this->fan_mode, this->custom_fan_mode, mode); + return set_primary_mode(this->fan_mode, this->custom_fan_mode_, mode); } -bool Climate::set_custom_fan_mode_(const std::string &mode) { - return set_alternative(this->custom_fan_mode, this->fan_mode, mode); +bool Climate::set_custom_fan_mode_(const char *mode) { + auto traits = this->get_traits(); + return set_custom_mode(this->custom_fan_mode_, this->fan_mode, traits.find_custom_fan_mode_(mode), + this->has_custom_fan_mode()); } -bool Climate::set_preset_(ClimatePreset preset) { return set_alternative(this->preset, this->custom_preset, preset); } +void Climate::clear_custom_fan_mode_() { this->custom_fan_mode_ = nullptr; } -bool Climate::set_custom_preset_(const std::string &preset) { - return set_alternative(this->custom_preset, this->preset, preset); +bool Climate::set_preset_(ClimatePreset preset) { return set_primary_mode(this->preset, this->custom_preset_, preset); } + +bool Climate::set_custom_preset_(const char *preset) { + auto traits = this->get_traits(); + return set_custom_mode(this->custom_preset_, this->preset, traits.find_custom_preset_(preset), + this->has_custom_preset()); +} + +void Climate::clear_custom_preset_() { this->custom_preset_ = nullptr; } + +const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode) { + return this->get_traits().find_custom_fan_mode_(custom_fan_mode); +} + +const char *Climate::find_custom_preset_(const char *custom_preset) { + return this->get_traits().find_custom_preset_(custom_preset); } void Climate::dump_traits_(const char *tag) { @@ -656,8 +737,8 @@ void Climate::dump_traits_(const char *tag) { } if (!traits.get_supported_custom_fan_modes().empty()) { ESP_LOGCONFIG(tag, " Supported custom fan modes:"); - for (const std::string &s : traits.get_supported_custom_fan_modes()) - ESP_LOGCONFIG(tag, " - %s", s.c_str()); + for (const char *s : traits.get_supported_custom_fan_modes()) + ESP_LOGCONFIG(tag, " - %s", s); } if (!traits.get_supported_presets().empty()) { ESP_LOGCONFIG(tag, " Supported presets:"); @@ -666,8 +747,8 @@ void Climate::dump_traits_(const char *tag) { } if (!traits.get_supported_custom_presets().empty()) { ESP_LOGCONFIG(tag, " Supported custom presets:"); - for (const std::string &s : traits.get_supported_custom_presets()) - ESP_LOGCONFIG(tag, " - %s", s.c_str()); + for (const char *s : traits.get_supported_custom_presets()) + ESP_LOGCONFIG(tag, " - %s", s); } if (!traits.get_supported_swing_modes().empty()) { ESP_LOGCONFIG(tag, " Supported swing modes:"); diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 0c3e3ebe16..b277877c3e 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -77,6 +77,8 @@ class ClimateCall { ClimateCall &set_fan_mode(const std::string &fan_mode); /// Set the fan mode of the climate device based on a string. ClimateCall &set_fan_mode(optional fan_mode); + /// Set the custom fan mode of the climate device. + ClimateCall &set_fan_mode(const char *custom_fan_mode); /// Set the swing mode of the climate device. ClimateCall &set_swing_mode(ClimateSwingMode swing_mode); /// Set the swing mode of the climate device. @@ -91,6 +93,8 @@ class ClimateCall { ClimateCall &set_preset(const std::string &preset); /// Set the preset of the climate device based on a string. ClimateCall &set_preset(optional preset); + /// Set the custom preset of the climate device. + ClimateCall &set_preset(const char *custom_preset); void perform(); @@ -103,8 +107,10 @@ class ClimateCall { const optional &get_fan_mode() const; const optional &get_swing_mode() const; const optional &get_preset() const; - const optional &get_custom_fan_mode() const; - const optional &get_custom_preset() const; + const char *get_custom_fan_mode() const { return this->custom_fan_mode_; } + const char *get_custom_preset() const { return this->custom_preset_; } + bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; } + bool has_custom_preset() const { return this->custom_preset_ != nullptr; } protected: void validate_(); @@ -118,8 +124,10 @@ class ClimateCall { optional fan_mode_; optional swing_mode_; optional preset_; - optional custom_fan_mode_; - optional custom_preset_; + + private: + const char *custom_fan_mode_{nullptr}; + const char *custom_preset_{nullptr}; }; /// Struct used to save the state of the climate device in restore memory. @@ -212,6 +220,12 @@ class Climate : public EntityBase { void set_visual_min_humidity_override(float visual_min_humidity_override); void set_visual_max_humidity_override(float visual_max_humidity_override); + /// Check if a custom fan mode is currently active. + bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; } + + /// Check if a custom preset is currently active. + bool has_custom_preset() const { return this->custom_preset_ != nullptr; } + /// The current temperature of the climate device, as reported from the integration. float current_temperature{NAN}; @@ -238,12 +252,6 @@ class Climate : public EntityBase { /// The active preset of the climate device. optional preset; - /// The active custom fan mode of the climate device. - optional custom_fan_mode; - - /// The active custom preset mode of the climate device. - optional custom_preset; - /// The active mode of the climate device. ClimateMode mode{CLIMATE_MODE_OFF}; @@ -253,20 +261,37 @@ class Climate : public EntityBase { /// The active swing mode of the climate device. ClimateSwingMode swing_mode{CLIMATE_SWING_OFF}; + /// Get the active custom fan mode (read-only access). + const char *get_custom_fan_mode() const { return this->custom_fan_mode_; } + + /// Get the active custom preset (read-only access). + const char *get_custom_preset() const { return this->custom_preset_; } + protected: friend ClimateCall; + friend struct ClimateDeviceRestoreState; /// Set fan mode. Reset custom fan mode. Return true if fan mode has been changed. bool set_fan_mode_(ClimateFanMode mode); /// Set custom fan mode. Reset primary fan mode. Return true if fan mode has been changed. - bool set_custom_fan_mode_(const std::string &mode); + bool set_custom_fan_mode_(const char *mode); + /// Clear custom fan mode. + void clear_custom_fan_mode_(); /// Set preset. Reset custom preset. Return true if preset has been changed. bool set_preset_(ClimatePreset preset); /// Set custom preset. Reset primary preset. Return true if preset has been changed. - bool set_custom_preset_(const std::string &preset); + bool set_custom_preset_(const char *preset); + /// Clear custom preset. + void clear_custom_preset_(); + + /// Find and return the matching custom fan mode pointer from traits, or nullptr if not found. + const char *find_custom_fan_mode_(const char *custom_fan_mode); + + /// Find and return the matching custom preset pointer from traits, or nullptr if not found. + const char *find_custom_preset_(const char *custom_preset); /** Get the default traits of this climate device. * @@ -303,6 +328,21 @@ class Climate : public EntityBase { optional visual_current_temperature_step_override_{}; optional visual_min_humidity_override_{}; optional visual_max_humidity_override_{}; + + private: + /** The active custom fan mode (private - enforces use of safe setters). + * + * Points to an entry in traits.supported_custom_fan_modes_ or nullptr. + * Use get_custom_fan_mode() to read, set_custom_fan_mode_() to modify. + */ + const char *custom_fan_mode_{nullptr}; + + /** The active custom preset (private - enforces use of safe setters). + * + * Points to an entry in traits.supported_custom_presets_ or nullptr. + * Use get_custom_preset() to read, set_custom_preset_() to modify. + */ + const char *custom_preset_{nullptr}; }; } // namespace climate diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index 1161a54f4e..0eecf9789f 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "climate_mode.h" #include "esphome/core/finite_set_mask.h" @@ -18,16 +19,25 @@ using ClimateSwingModeMask = FiniteSetMask>; using ClimatePresetMask = FiniteSetMask>; -// Lightweight linear search for small vectors (1-20 items) +// Lightweight linear search for small vectors (1-20 items) of const char* pointers // Avoids std::find template overhead -template inline bool vector_contains(const std::vector &vec, const T &value) { - for (const auto &item : vec) { - if (item == value) +inline bool vector_contains(const std::vector &vec, const char *value) { + for (const char *item : vec) { + if (strcmp(item, value) == 0) return true; } return false; } +// Find and return matching pointer from vector, or nullptr if not found +inline const char *vector_find(const std::vector &vec, const char *value) { + for (const char *item : vec) { + if (strcmp(item, value) == 0) + return item; + } + return nullptr; +} + /** This class contains all static data for climate devices. * * All climate devices must support these features: @@ -55,7 +65,11 @@ template inline bool vector_contains(const std::vector &vec, cons * - temperature step - the step with which to increase/decrease target temperature. * This also affects with how many decimal places the temperature is shown */ +class Climate; // Forward declaration + class ClimateTraits { + friend class Climate; // Allow Climate to access protected find methods + public: /// Get/set feature flags (see ClimateFeatures enum in climate_mode.h) uint32_t get_feature_flags() const { return this->feature_flags_; } @@ -128,47 +142,61 @@ class ClimateTraits { void set_supported_fan_modes(ClimateFanModeMask modes) { this->supported_fan_modes_ = modes; } void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); } - void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.push_back(mode); } bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); } bool get_supports_fan_modes() const { return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty(); } const ClimateFanModeMask &get_supported_fan_modes() const { return this->supported_fan_modes_; } - void set_supported_custom_fan_modes(std::vector supported_custom_fan_modes) { - this->supported_custom_fan_modes_ = std::move(supported_custom_fan_modes); + void set_supported_custom_fan_modes(std::initializer_list modes) { + this->supported_custom_fan_modes_ = modes; } - void set_supported_custom_fan_modes(std::initializer_list modes) { + void set_supported_custom_fan_modes(const std::vector &modes) { this->supported_custom_fan_modes_ = modes; } template void set_supported_custom_fan_modes(const char *const (&modes)[N]) { this->supported_custom_fan_modes_.assign(modes, modes + N); } - const std::vector &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; } - bool supports_custom_fan_mode(const std::string &custom_fan_mode) const { + + // Deleted overloads to catch incorrect std::string usage at compile time with clear error messages + void set_supported_custom_fan_modes(const std::vector &modes) = delete; + void set_supported_custom_fan_modes(std::initializer_list modes) = delete; + + const std::vector &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; } + bool supports_custom_fan_mode(const char *custom_fan_mode) const { return vector_contains(this->supported_custom_fan_modes_, custom_fan_mode); } + bool supports_custom_fan_mode(const std::string &custom_fan_mode) const { + return this->supports_custom_fan_mode(custom_fan_mode.c_str()); + } void set_supported_presets(ClimatePresetMask presets) { this->supported_presets_ = presets; } void add_supported_preset(ClimatePreset preset) { this->supported_presets_.insert(preset); } - void add_supported_custom_preset(const std::string &preset) { this->supported_custom_presets_.push_back(preset); } bool supports_preset(ClimatePreset preset) const { return this->supported_presets_.count(preset); } bool get_supports_presets() const { return !this->supported_presets_.empty(); } const ClimatePresetMask &get_supported_presets() const { return this->supported_presets_; } - void set_supported_custom_presets(std::vector supported_custom_presets) { - this->supported_custom_presets_ = std::move(supported_custom_presets); + void set_supported_custom_presets(std::initializer_list presets) { + this->supported_custom_presets_ = presets; } - void set_supported_custom_presets(std::initializer_list presets) { + void set_supported_custom_presets(const std::vector &presets) { this->supported_custom_presets_ = presets; } template void set_supported_custom_presets(const char *const (&presets)[N]) { this->supported_custom_presets_.assign(presets, presets + N); } - const std::vector &get_supported_custom_presets() const { return this->supported_custom_presets_; } - bool supports_custom_preset(const std::string &custom_preset) const { + + // Deleted overloads to catch incorrect std::string usage at compile time with clear error messages + void set_supported_custom_presets(const std::vector &presets) = delete; + void set_supported_custom_presets(std::initializer_list presets) = delete; + + const std::vector &get_supported_custom_presets() const { return this->supported_custom_presets_; } + bool supports_custom_preset(const char *custom_preset) const { return vector_contains(this->supported_custom_presets_, custom_preset); } + bool supports_custom_preset(const std::string &custom_preset) const { + return this->supports_custom_preset(custom_preset.c_str()); + } void set_supported_swing_modes(ClimateSwingModeMask modes) { this->supported_swing_modes_ = modes; } void add_supported_swing_mode(ClimateSwingMode mode) { this->supported_swing_modes_.insert(mode); } @@ -227,6 +255,18 @@ class ClimateTraits { } } + /// Find and return the matching custom fan mode pointer from supported modes, or nullptr if not found + /// This is protected as it's an implementation detail - use Climate::find_custom_fan_mode_() instead + const char *find_custom_fan_mode_(const char *custom_fan_mode) const { + return vector_find(this->supported_custom_fan_modes_, custom_fan_mode); + } + + /// Find and return the matching custom preset pointer from supported presets, or nullptr if not found + /// This is protected as it's an implementation detail - use Climate::find_custom_preset_() instead + const char *find_custom_preset_(const char *custom_preset) const { + return vector_find(this->supported_custom_presets_, custom_preset); + } + uint32_t feature_flags_{0}; float visual_min_temperature_{10}; float visual_max_temperature_{30}; @@ -239,8 +279,17 @@ class ClimateTraits { climate::ClimateFanModeMask supported_fan_modes_; climate::ClimateSwingModeMask supported_swing_modes_; climate::ClimatePresetMask supported_presets_; - std::vector supported_custom_fan_modes_; - std::vector supported_custom_presets_; + + /** Custom mode storage using const char* pointers to eliminate std::string overhead. + * + * Pointers must remain valid for the ClimateTraits lifetime. Safe patterns: + * - String literals: set_supported_custom_fan_modes({"Turbo", "Silent"}) + * - Static const data: static const char* MODE = "Eco"; + * + * Climate class setters validate pointers are from these vectors before storing. + */ + std::vector supported_custom_fan_modes_; + std::vector supported_custom_presets_; }; } // namespace climate diff --git a/esphome/components/demo/demo_climate.h b/esphome/components/demo/demo_climate.h index 84b16e7ec5..e2dfb0142b 100644 --- a/esphome/components/demo/demo_climate.h +++ b/esphome/components/demo/demo_climate.h @@ -28,16 +28,16 @@ class DemoClimate : public climate::Climate, public Component { this->mode = climate::CLIMATE_MODE_AUTO; this->action = climate::CLIMATE_ACTION_COOLING; this->fan_mode = climate::CLIMATE_FAN_HIGH; - this->custom_preset = {"My Preset"}; + this->set_custom_preset_("My Preset"); break; case DemoClimateType::TYPE_3: this->current_temperature = 21.5; this->target_temperature_low = 21.0; this->target_temperature_high = 22.5; this->mode = climate::CLIMATE_MODE_HEAT_COOL; - this->custom_fan_mode = {"Auto Low"}; + this->set_custom_fan_mode_("Auto Low"); this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - this->preset = climate::CLIMATE_PRESET_AWAY; + this->set_preset_(climate::CLIMATE_PRESET_AWAY); break; } this->publish_state(); @@ -58,23 +58,19 @@ class DemoClimate : public climate::Climate, public Component { this->target_temperature_high = *call.get_target_temperature_high(); } if (call.get_fan_mode().has_value()) { - this->fan_mode = *call.get_fan_mode(); - this->custom_fan_mode.reset(); + this->set_fan_mode_(*call.get_fan_mode()); } if (call.get_swing_mode().has_value()) { this->swing_mode = *call.get_swing_mode(); } - if (call.get_custom_fan_mode().has_value()) { - this->custom_fan_mode = *call.get_custom_fan_mode(); - this->fan_mode.reset(); + if (call.has_custom_fan_mode()) { + this->set_custom_fan_mode_(call.get_custom_fan_mode()); } if (call.get_preset().has_value()) { - this->preset = *call.get_preset(); - this->custom_preset.reset(); + this->set_preset_(*call.get_preset()); } - if (call.get_custom_preset().has_value()) { - this->custom_preset = *call.get_custom_preset(); - this->preset.reset(); + if (call.has_custom_preset()) { + this->set_custom_preset_(call.get_custom_preset()); } this->publish_state(); } diff --git a/esphome/components/midea/ac_adapter.cpp b/esphome/components/midea/ac_adapter.cpp index 2837713c35..d903db4a1b 100644 --- a/esphome/components/midea/ac_adapter.cpp +++ b/esphome/components/midea/ac_adapter.cpp @@ -8,9 +8,9 @@ namespace midea { namespace ac { const char *const Constants::TAG = "midea"; -const std::string Constants::FREEZE_PROTECTION = "freeze protection"; -const std::string Constants::SILENT = "silent"; -const std::string Constants::TURBO = "turbo"; +const char *const Constants::FREEZE_PROTECTION = "freeze protection"; +const char *const Constants::SILENT = "silent"; +const char *const Constants::TURBO = "turbo"; ClimateMode Converters::to_climate_mode(MideaMode mode) { switch (mode) { @@ -108,7 +108,7 @@ bool Converters::is_custom_midea_fan_mode(MideaFanMode mode) { } } -const std::string &Converters::to_custom_climate_fan_mode(MideaFanMode mode) { +const char *Converters::to_custom_climate_fan_mode(MideaFanMode mode) { switch (mode) { case MideaFanMode::FAN_SILENT: return Constants::SILENT; @@ -117,8 +117,8 @@ const std::string &Converters::to_custom_climate_fan_mode(MideaFanMode mode) { } } -MideaFanMode Converters::to_midea_fan_mode(const std::string &mode) { - if (mode == Constants::SILENT) +MideaFanMode Converters::to_midea_fan_mode(const char *mode) { + if (strcmp(mode, Constants::SILENT) == 0) return MideaFanMode::FAN_SILENT; return MideaFanMode::FAN_TURBO; } @@ -151,9 +151,9 @@ ClimatePreset Converters::to_climate_preset(MideaPreset preset) { bool Converters::is_custom_midea_preset(MideaPreset preset) { return preset == MideaPreset::PRESET_FREEZE_PROTECTION; } -const std::string &Converters::to_custom_climate_preset(MideaPreset preset) { return Constants::FREEZE_PROTECTION; } +const char *Converters::to_custom_climate_preset(MideaPreset preset) { return Constants::FREEZE_PROTECTION; } -MideaPreset Converters::to_midea_preset(const std::string &preset) { return MideaPreset::PRESET_FREEZE_PROTECTION; } +MideaPreset Converters::to_midea_preset(const char *preset) { return MideaPreset::PRESET_FREEZE_PROTECTION; } void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities) { if (capabilities.supportAutoMode()) @@ -169,7 +169,7 @@ void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea:: if (capabilities.supportEcoPreset()) traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_ECO); if (capabilities.supportFrostProtectionPreset()) - traits.add_supported_custom_preset(Constants::FREEZE_PROTECTION); + traits.set_supported_custom_presets({Constants::FREEZE_PROTECTION}); } } // namespace ac diff --git a/esphome/components/midea/ac_adapter.h b/esphome/components/midea/ac_adapter.h index c17894ae31..b0589a37f9 100644 --- a/esphome/components/midea/ac_adapter.h +++ b/esphome/components/midea/ac_adapter.h @@ -20,9 +20,9 @@ using MideaPreset = dudanov::midea::ac::Preset; class Constants { public: static const char *const TAG; - static const std::string FREEZE_PROTECTION; - static const std::string SILENT; - static const std::string TURBO; + static const char *const FREEZE_PROTECTION; + static const char *const SILENT; + static const char *const TURBO; }; class Converters { @@ -32,15 +32,15 @@ class Converters { static MideaSwingMode to_midea_swing_mode(ClimateSwingMode mode); static ClimateSwingMode to_climate_swing_mode(MideaSwingMode mode); static MideaPreset to_midea_preset(ClimatePreset preset); - static MideaPreset to_midea_preset(const std::string &preset); + static MideaPreset to_midea_preset(const char *preset); static bool is_custom_midea_preset(MideaPreset preset); static ClimatePreset to_climate_preset(MideaPreset preset); - static const std::string &to_custom_climate_preset(MideaPreset preset); + static const char *to_custom_climate_preset(MideaPreset preset); static MideaFanMode to_midea_fan_mode(ClimateFanMode fan_mode); - static MideaFanMode to_midea_fan_mode(const std::string &fan_mode); + static MideaFanMode to_midea_fan_mode(const char *fan_mode); static bool is_custom_midea_fan_mode(MideaFanMode fan_mode); static ClimateFanMode to_climate_fan_mode(MideaFanMode fan_mode); - static const std::string &to_custom_climate_fan_mode(MideaFanMode fan_mode); + static const char *to_custom_climate_fan_mode(MideaFanMode fan_mode); static void to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities); }; diff --git a/esphome/components/midea/air_conditioner.cpp b/esphome/components/midea/air_conditioner.cpp index 0ad26ebd51..a6a8d52549 100644 --- a/esphome/components/midea/air_conditioner.cpp +++ b/esphome/components/midea/air_conditioner.cpp @@ -64,13 +64,13 @@ void AirConditioner::control(const ClimateCall &call) { ctrl.mode = Converters::to_midea_mode(call.get_mode().value()); if (call.get_preset().has_value()) { ctrl.preset = Converters::to_midea_preset(call.get_preset().value()); - } else if (call.get_custom_preset().has_value()) { - ctrl.preset = Converters::to_midea_preset(call.get_custom_preset().value()); + } else if (call.has_custom_preset()) { + ctrl.preset = Converters::to_midea_preset(call.get_custom_preset()); } if (call.get_fan_mode().has_value()) { ctrl.fanMode = Converters::to_midea_fan_mode(call.get_fan_mode().value()); - } else if (call.get_custom_fan_mode().has_value()) { - ctrl.fanMode = Converters::to_midea_fan_mode(call.get_custom_fan_mode().value()); + } else if (call.has_custom_fan_mode()) { + ctrl.fanMode = Converters::to_midea_fan_mode(call.get_custom_fan_mode()); } this->base_.control(ctrl); } @@ -84,8 +84,10 @@ ClimateTraits AirConditioner::traits() { traits.set_supported_modes(this->supported_modes_); traits.set_supported_swing_modes(this->supported_swing_modes_); traits.set_supported_presets(this->supported_presets_); - traits.set_supported_custom_presets(this->supported_custom_presets_); - traits.set_supported_custom_fan_modes(this->supported_custom_fan_modes_); + if (!this->supported_custom_presets_.empty()) + traits.set_supported_custom_presets(this->supported_custom_presets_); + if (!this->supported_custom_fan_modes_.empty()) + traits.set_supported_custom_fan_modes(this->supported_custom_fan_modes_); /* + MINIMAL SET OF CAPABILITIES */ traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO); traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW); diff --git a/esphome/components/midea/air_conditioner.h b/esphome/components/midea/air_conditioner.h index 6c2401efe7..70833b8bcc 100644 --- a/esphome/components/midea/air_conditioner.h +++ b/esphome/components/midea/air_conditioner.h @@ -46,8 +46,8 @@ class AirConditioner : public ApplianceBase, void set_supported_modes(ClimateModeMask modes) { this->supported_modes_ = modes; } void set_supported_swing_modes(ClimateSwingModeMask modes) { this->supported_swing_modes_ = modes; } void set_supported_presets(ClimatePresetMask presets) { this->supported_presets_ = presets; } - void set_custom_presets(const std::vector &presets) { this->supported_custom_presets_ = presets; } - void set_custom_fan_modes(const std::vector &modes) { this->supported_custom_fan_modes_ = modes; } + void set_custom_presets(std::initializer_list presets) { this->supported_custom_presets_ = presets; } + void set_custom_fan_modes(std::initializer_list modes) { this->supported_custom_fan_modes_ = modes; } protected: void control(const ClimateCall &call) override; @@ -55,8 +55,8 @@ class AirConditioner : public ApplianceBase, ClimateModeMask supported_modes_{}; ClimateSwingModeMask supported_swing_modes_{}; ClimatePresetMask supported_presets_{}; - std::vector supported_custom_presets_{}; - std::vector supported_custom_fan_modes_{}; + std::vector supported_custom_presets_{}; + std::vector supported_custom_fan_modes_{}; Sensor *outdoor_sensor_{nullptr}; Sensor *humidity_sensor_{nullptr}; Sensor *power_sensor_{nullptr}; diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 18efe3984e..d533ef93ec 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -54,7 +54,7 @@ void ThermostatClimate::setup() { if (this->default_preset_ != climate::ClimatePreset::CLIMATE_PRESET_NONE) { this->change_preset_(this->default_preset_); } else if (!this->default_custom_preset_.empty()) { - this->change_custom_preset_(this->default_custom_preset_); + this->change_custom_preset_(this->default_custom_preset_.c_str()); } } @@ -218,12 +218,13 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { this->preset = call.get_preset().value(); } } - if (call.get_custom_preset().has_value()) { + if (call.has_custom_preset()) { // setup_complete_ blocks modifying/resetting the temps immediately after boot if (this->setup_complete_) { - this->change_custom_preset_(call.get_custom_preset().value()); + this->change_custom_preset_(call.get_custom_preset()); } else { - this->custom_preset = call.get_custom_preset().value(); + // Use the base class method which handles pointer lookup internally + this->set_custom_preset_(call.get_custom_preset()); } } @@ -321,9 +322,17 @@ climate::ClimateTraits ThermostatClimate::traits() { for (auto &it : this->preset_config_) { traits.add_supported_preset(it.first); } - for (auto &it : this->custom_preset_config_) { - traits.add_supported_custom_preset(it.first); + + // Extract custom preset names from the custom_preset_config_ map + if (!this->custom_preset_config_.empty()) { + std::vector custom_preset_names; + custom_preset_names.reserve(this->custom_preset_config_.size()); + for (const auto &it : this->custom_preset_config_) { + custom_preset_names.push_back(it.first.c_str()); + } + traits.set_supported_custom_presets(custom_preset_names); } + return traits; } @@ -1153,7 +1162,7 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { this->preset.value() != preset) { // Fire any preset changed trigger if defined Trigger<> *trig = this->preset_change_trigger_; - this->preset = preset; + this->set_preset_(preset); if (trig != nullptr) { trig->trigger(); } @@ -1163,36 +1172,36 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { } else { ESP_LOGI(TAG, "No changes required to apply preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset))); } - this->custom_preset.reset(); - this->preset = preset; } else { ESP_LOGW(TAG, "Preset %s not configured; ignoring", LOG_STR_ARG(climate::climate_preset_to_string(preset))); } } -void ThermostatClimate::change_custom_preset_(const std::string &custom_preset) { +void ThermostatClimate::change_custom_preset_(const char *custom_preset) { auto config = this->custom_preset_config_.find(custom_preset); if (config != this->custom_preset_config_.end()) { - ESP_LOGV(TAG, "Custom preset %s requested", custom_preset.c_str()); - if (this->change_preset_internal_(config->second) || (!this->custom_preset.has_value()) || - this->custom_preset.value() != custom_preset) { + ESP_LOGV(TAG, "Custom preset %s requested", custom_preset); + if (this->change_preset_internal_(config->second) || !this->has_custom_preset() || + strcmp(this->get_custom_preset(), custom_preset) != 0) { // Fire any preset changed trigger if defined Trigger<> *trig = this->preset_change_trigger_; - this->custom_preset = custom_preset; + // Use the base class method which handles pointer lookup and preset reset internally + this->set_custom_preset_(custom_preset); if (trig != nullptr) { trig->trigger(); } this->refresh(); - ESP_LOGI(TAG, "Custom preset %s applied", custom_preset.c_str()); + ESP_LOGI(TAG, "Custom preset %s applied", custom_preset); } else { - ESP_LOGI(TAG, "No changes required to apply custom preset %s", custom_preset.c_str()); + ESP_LOGI(TAG, "No changes required to apply custom preset %s", custom_preset); + // Note: set_custom_preset_() above handles preset.reset() and custom_preset_ assignment internally. + // The old code had these lines here unconditionally, which was a bug (double assignment, state modification + // even when no changes were needed). Now properly handled by the protected setter with mutual exclusion. } - this->preset.reset(); - this->custom_preset = custom_preset; } else { - ESP_LOGW(TAG, "Custom preset %s not configured; ignoring", custom_preset.c_str()); + ESP_LOGW(TAG, "Custom preset %s not configured; ignoring", custom_preset); } } diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 42adab7751..c9795d9666 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -199,7 +199,7 @@ class ThermostatClimate : public climate::Climate, public Component { /// Change to a provided preset setting; will reset temperature, mode, fan, and swing modes accordingly void change_preset_(climate::ClimatePreset preset); /// Change to a provided custom preset setting; will reset temperature, mode, fan, and swing modes accordingly - void change_custom_preset_(const std::string &custom_preset); + void change_custom_preset_(const char *custom_preset); /// Applies the temperature, mode, fan, and swing modes of the provided config. /// This is agnostic of custom vs built in preset diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index fe0da14435..465356db80 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1315,7 +1315,7 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf for (climate::ClimatePreset m : traits.get_supported_presets()) opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m))); } - if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) { + if (!traits.get_supported_custom_presets().empty() && obj->has_custom_preset()) { JsonArray opt = root["custom_presets"].to(); for (auto const &custom_preset : traits.get_supported_custom_presets()) opt.add(custom_preset); @@ -1336,14 +1336,14 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf if (traits.get_supports_fan_modes() && obj->fan_mode.has_value()) { root["fan_mode"] = PSTR_LOCAL(climate_fan_mode_to_string(obj->fan_mode.value())); } - if (!traits.get_supported_custom_fan_modes().empty() && obj->custom_fan_mode.has_value()) { - root["custom_fan_mode"] = obj->custom_fan_mode.value().c_str(); + if (!traits.get_supported_custom_fan_modes().empty() && obj->has_custom_fan_mode()) { + root["custom_fan_mode"] = obj->get_custom_fan_mode(); } if (traits.get_supports_presets() && obj->preset.has_value()) { root["preset"] = PSTR_LOCAL(climate_preset_to_string(obj->preset.value())); } - if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) { - root["custom_preset"] = obj->custom_preset.value().c_str(); + if (!traits.get_supported_custom_presets().empty() && obj->has_custom_preset()) { + root["custom_preset"] = obj->get_custom_preset(); } if (traits.get_supports_swing_modes()) { root["swing_mode"] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode)); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 394e92b9a7..3b756095a1 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -1610,8 +1610,9 @@ class RepeatedTypeInfo(TypeInfo): # Other types need the actual value # Special handling for const char* elements if self._use_pointer and "const char" in self._container_no_template: + field_id_size = self.calculate_field_id_size() o += f" for (const char *it : {container_ref}) {{\n" - o += " size.add_length_force(1, strlen(it));\n" + o += f" size.add_length_force({field_id_size}, strlen(it));\n" else: auto_ref = "" if self._ti_is_bool else "&" o += f" for (const auto {auto_ref}it : {container_ref}) {{\n" diff --git a/tests/integration/fixtures/climate_custom_fan_modes_and_presets.yaml b/tests/integration/fixtures/climate_custom_fan_modes_and_presets.yaml new file mode 100644 index 0000000000..bf4ef9eafd --- /dev/null +++ b/tests/integration/fixtures/climate_custom_fan_modes_and_presets.yaml @@ -0,0 +1,40 @@ +esphome: + name: climate-custom-modes-test +host: +api: +logger: + +sensor: + - platform: template + id: thermostat_sensor + lambda: "return 22.0;" + +climate: + - platform: thermostat + id: test_thermostat + name: Test Thermostat Custom Modes + sensor: thermostat_sensor + preset: + - name: Away + default_target_temperature_low: 16°C + default_target_temperature_high: 20°C + - name: Eco Plus + default_target_temperature_low: 18°C + default_target_temperature_high: 22°C + - name: Super Saver + default_target_temperature_low: 20°C + default_target_temperature_high: 24°C + - name: Vacation Mode + default_target_temperature_low: 15°C + default_target_temperature_high: 18°C + idle_action: + - logger.log: idle_action + cool_action: + - logger.log: cool_action + heat_action: + - logger.log: heat_action + min_cooling_off_time: 10s + min_cooling_run_time: 10s + min_heating_off_time: 10s + min_heating_run_time: 10s + min_idle_time: 10s diff --git a/tests/integration/test_climate_custom_modes.py b/tests/integration/test_climate_custom_modes.py new file mode 100644 index 0000000000..ce34959d88 --- /dev/null +++ b/tests/integration/test_climate_custom_modes.py @@ -0,0 +1,42 @@ +"""Integration test for climate custom presets.""" + +from __future__ import annotations + +from aioesphomeapi import ClimateInfo, ClimatePreset +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_climate_custom_fan_modes_and_presets( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that custom presets are properly exposed via API.""" + async with run_compiled(yaml_config), api_client_connected() as client: + # Get entities and services + entities, services = await client.list_entities_services() + climate_infos = [e for e in entities if isinstance(e, ClimateInfo)] + assert len(climate_infos) == 1, "Expected exactly 1 climate entity" + + test_climate = climate_infos[0] + + # Verify enum presets are exposed (from preset: config map) + assert ClimatePreset.AWAY in test_climate.supported_presets, ( + "Expected AWAY in enum presets" + ) + + # Verify custom string presets are exposed (non-standard preset names from preset map) + custom_presets = test_climate.supported_custom_presets + assert len(custom_presets) == 3, ( + f"Expected 3 custom presets, got {len(custom_presets)}: {custom_presets}" + ) + assert "Eco Plus" in custom_presets, "Expected 'Eco Plus' in custom presets" + assert "Super Saver" in custom_presets, ( + "Expected 'Super Saver' in custom presets" + ) + assert "Vacation Mode" in custom_presets, ( + "Expected 'Vacation Mode' in custom presets" + ) From 0e792d07912346c85dc09e41d6081b8861c2f8c2 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Sun, 2 Nov 2025 21:20:08 -0800 Subject: [PATCH 0053/1145] [nrf52,debug] fix status of nRESET pin, add extra registry from UICR (#11667) Co-authored-by: J. Nick Koston --- esphome/components/debug/debug_zephyr.cpp | 33 ++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/esphome/components/debug/debug_zephyr.cpp b/esphome/components/debug/debug_zephyr.cpp index 231b39a711..62fa391e5f 100644 --- a/esphome/components/debug/debug_zephyr.cpp +++ b/esphome/components/debug/debug_zephyr.cpp @@ -8,8 +8,7 @@ #define BOOTLOADER_VERSION_REGISTER NRF_TIMER2->CC[0] -namespace esphome { -namespace debug { +namespace esphome::debug { static const char *const TAG = "debug"; constexpr std::uintptr_t MBR_PARAM_PAGE_ADDR = 0xFFC; @@ -281,14 +280,18 @@ void DebugComponent::get_device_info_(std::string &device_info) { NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE)); ESP_LOGD(TAG, "RAM: %ukB, Flash: %ukB, production test: %sdone", NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH, (NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not ")); + bool n_reset_enabled = NRF_UICR->PSELRESET[0] == NRF_UICR->PSELRESET[1] && + (NRF_UICR->PSELRESET[0] & UICR_PSELRESET_CONNECT_Msk) == UICR_PSELRESET_CONNECT_Connected + << UICR_PSELRESET_CONNECT_Pos; ESP_LOGD( TAG, "GPIO as NFC pins: %s, GPIO as nRESET pin: %s", YESNO((NRF_UICR->NFCPINS & UICR_NFCPINS_PROTECT_Msk) == (UICR_NFCPINS_PROTECT_NFC << UICR_NFCPINS_PROTECT_Pos)), - YESNO(((NRF_UICR->PSELRESET[0] & UICR_PSELRESET_CONNECT_Msk) != - (UICR_PSELRESET_CONNECT_Connected << UICR_PSELRESET_CONNECT_Pos)) || - ((NRF_UICR->PSELRESET[1] & UICR_PSELRESET_CONNECT_Msk) != - (UICR_PSELRESET_CONNECT_Connected << UICR_PSELRESET_CONNECT_Pos)))); - + YESNO(n_reset_enabled)); + if (n_reset_enabled) { + uint8_t port = (NRF_UICR->PSELRESET[0] & UICR_PSELRESET_PORT_Msk) >> UICR_PSELRESET_PORT_Pos; + uint8_t pin = (NRF_UICR->PSELRESET[0] & UICR_PSELRESET_PIN_Msk) >> UICR_PSELRESET_PIN_Pos; + ESP_LOGD(TAG, "nRESET port P%u.%02u", port, pin); + } #ifdef USE_BOOTLOADER_MCUBOOT ESP_LOGD(TAG, "bootloader: mcuboot"); #else @@ -322,10 +325,22 @@ void DebugComponent::get_device_info_(std::string &device_info) { #endif } #endif + auto uicr = [](volatile uint32_t *data, uint8_t size) { + std::string res; + char buf[sizeof(uint32_t) * 2 + 1]; + for (size_t i = 0; i < size; i++) { + if (i > 0) { + res += ' '; + } + res += format_hex_pretty(data[i], '\0', false); + } + return res; + }; + ESP_LOGD(TAG, "NRFFW %s", uicr(NRF_UICR->NRFFW, 13).c_str()); + ESP_LOGD(TAG, "NRFHW %s", uicr(NRF_UICR->NRFHW, 12).c_str()); } void DebugComponent::update_platform_() {} -} // namespace debug -} // namespace esphome +} // namespace esphome::debug #endif From 7e1cea8e6941f0e5d7df17a23c1213a352331b14 Mon Sep 17 00:00:00 2001 From: Kent Gibson Date: Mon, 3 Nov 2025 22:05:33 +0800 Subject: [PATCH 0054/1145] [template] alarm_control_panel more ESP_LOGCONFIG reductions (#11691) --- .../template_alarm_control_panel.cpp | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp index f7e9872ce1..af662a05a0 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp @@ -42,23 +42,21 @@ static const LogString *sensor_type_to_string(AlarmSensorType type) { #endif void TemplateAlarmControlPanel::dump_config() { - ESP_LOGCONFIG(TAG, "TemplateAlarmControlPanel:"); ESP_LOGCONFIG(TAG, + "TemplateAlarmControlPanel:\n" " Current State: %s\n" - " Number of Codes: %u", - LOG_STR_ARG(alarm_control_panel_state_to_string(this->current_state_)), this->codes_.size()); - if (!this->codes_.empty()) - ESP_LOGCONFIG(TAG, " Requires Code To Arm: %s", YESNO(this->requires_code_to_arm_)); - ESP_LOGCONFIG(TAG, " Arming Away Time: %" PRIu32 "s", (this->arming_away_time_ / 1000)); - if (this->arming_home_time_ != 0) - ESP_LOGCONFIG(TAG, " Arming Home Time: %" PRIu32 "s", (this->arming_home_time_ / 1000)); - if (this->arming_night_time_ != 0) - ESP_LOGCONFIG(TAG, " Arming Night Time: %" PRIu32 "s", (this->arming_night_time_ / 1000)); - ESP_LOGCONFIG(TAG, + " Number of Codes: %u\n" + " Requires Code To Arm: %s\n" + " Arming Away Time: %" PRIu32 "s\n" + " Arming Home Time: %" PRIu32 "s\n" + " Arming Night Time: %" PRIu32 "s\n" " Pending Time: %" PRIu32 "s\n" " Trigger Time: %" PRIu32 "s\n" " Supported Features: %" PRIu32, - (this->pending_time_ / 1000), (this->trigger_time_ / 1000), this->get_supported_features()); + LOG_STR_ARG(alarm_control_panel_state_to_string(this->current_state_)), this->codes_.size(), + YESNO(!this->codes_.empty() && this->requires_code_to_arm_), (this->arming_away_time_ / 1000), + (this->arming_home_time_ / 1000), (this->arming_night_time_ / 1000), (this->pending_time_ / 1000), + (this->trigger_time_ / 1000), this->get_supported_features()); #ifdef USE_BINARY_SENSOR for (auto const &[sensor, info] : this->sensor_map_) { ESP_LOGCONFIG(TAG, From 1ec1692c774173dd73d91bbe6ededc402207274a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Nov 2025 08:23:04 -0600 Subject: [PATCH 0055/1145] [mqtt] Fix climate custom fan mode and preset compilation errors (#11692) --- esphome/components/mqtt/mqtt_climate.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index a6f4e0a201..aee2b38942 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -357,8 +357,8 @@ bool MQTTClimateComponent::publish_state_() { payload = "unknown"; } } - if (this->device_->custom_preset.has_value()) - payload = this->device_->custom_preset.value(); + if (this->device_->has_custom_preset()) + payload = this->device_->get_custom_preset(); if (!this->publish(this->get_preset_state_topic(), payload)) success = false; } @@ -429,8 +429,8 @@ bool MQTTClimateComponent::publish_state_() { payload = "unknown"; } } - if (this->device_->custom_fan_mode.has_value()) - payload = this->device_->custom_fan_mode.value(); + if (this->device_->has_custom_fan_mode()) + payload = this->device_->get_custom_fan_mode(); if (!this->publish(this->get_fan_mode_state_topic(), payload)) success = false; } From f05f45af74f2fc02e30445ca67f449c672fee6b4 Mon Sep 17 00:00:00 2001 From: Nathan Bernard <3122054+pixelatedmirror@users.noreply.github.com> Date: Mon, 3 Nov 2025 07:17:28 -0800 Subject: [PATCH 0056/1145] Add support for Mopeka standard check alternate ID (#10907) Co-authored-by: J. Nick Koston --- esphome/components/mopeka_std_check/mopeka_std_check.cpp | 2 +- esphome/components/mopeka_std_check/mopeka_std_check.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/mopeka_std_check/mopeka_std_check.cpp b/esphome/components/mopeka_std_check/mopeka_std_check.cpp index 6685a23c41..0d8340f95f 100644 --- a/esphome/components/mopeka_std_check/mopeka_std_check.cpp +++ b/esphome/components/mopeka_std_check/mopeka_std_check.cpp @@ -72,7 +72,7 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const u_int8_t hardware_id = mopeka_data->data_1 & 0xCF; if (static_cast(hardware_id) != STANDARD && static_cast(hardware_id) != XL && - static_cast(hardware_id) != ETRAILER) { + static_cast(hardware_id) != ETRAILER && static_cast(hardware_id) != STANDARD_ALT) { ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", device.address_str().c_str(), hardware_id); return false; } diff --git a/esphome/components/mopeka_std_check/mopeka_std_check.h b/esphome/components/mopeka_std_check/mopeka_std_check.h index b92445df34..897b5414ed 100644 --- a/esphome/components/mopeka_std_check/mopeka_std_check.h +++ b/esphome/components/mopeka_std_check/mopeka_std_check.h @@ -15,6 +15,7 @@ namespace mopeka_std_check { enum SensorType { STANDARD = 0x02, XL = 0x03, + STANDARD_ALT = 0x44, ETRAILER = 0x46, }; From cb039b42aa235042fb9c51373eadcf2d98af2108 Mon Sep 17 00:00:00 2001 From: Paul Strawder Date: Mon, 3 Nov 2025 17:34:53 +0100 Subject: [PATCH 0057/1145] [esp32] Make the loop task's stack size configurable (#10564) Co-authored-by: Paul Strawder Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/esp32/__init__.py | 12 ++++++++++++ esphome/components/esp32/core.cpp | 5 +++-- esphome/core/defines.h | 1 + .../components/esp32/test-stack_size.esp32-idf.yaml | 6 ++++++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 tests/components/esp32/test-stack_size.esp32-idf.yaml diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index ddb8dbb1f0..6981662d77 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -558,6 +558,7 @@ CONF_DISABLE_LIBC_LOCKS_IN_IRAM = "disable_libc_locks_in_iram" CONF_DISABLE_VFS_SUPPORT_TERMIOS = "disable_vfs_support_termios" CONF_DISABLE_VFS_SUPPORT_SELECT = "disable_vfs_support_select" CONF_DISABLE_VFS_SUPPORT_DIR = "disable_vfs_support_dir" +CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size" # VFS requirement tracking # Components that need VFS features can call require_vfs_select() or require_vfs_dir() @@ -654,6 +655,9 @@ FRAMEWORK_SCHEMA = cv.All( ): cv.boolean, cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean, cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean, + cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range( + min=8192, max=32768 + ), } ), cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( @@ -926,6 +930,10 @@ async def to_code(config): f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})" ), ) + add_idf_sdkconfig_option( + "CONFIG_ARDUINO_LOOP_STACK_SIZE", + conf[CONF_ADVANCED][CONF_LOOP_TASK_STACK_SIZE], + ) add_idf_sdkconfig_option("CONFIG_AUTOSTART_ARDUINO", True) add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True) add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True) @@ -1071,6 +1079,10 @@ async def to_code(config): ) add_idf_sdkconfig_option("CONFIG_IDF_EXPERIMENTAL_FEATURES", True) + cg.add_define( + "ESPHOME_LOOP_TASK_STACK_SIZE", advanced.get(CONF_LOOP_TASK_STACK_SIZE) + ) + cg.add_define( "USE_ESP_IDF_VERSION_CODE", cg.RawExpression( diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 1c8f29fa95..6215ff862f 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -1,5 +1,6 @@ #ifdef USE_ESP32 +#include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "preferences.h" @@ -97,9 +98,9 @@ void loop_task(void *pv_params) { extern "C" void app_main() { esp32::setup_preferences(); #if CONFIG_FREERTOS_UNICORE - xTaskCreate(loop_task, "loopTask", 8192, nullptr, 1, &loop_task_handle); + xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle); #else - xTaskCreatePinnedToCore(loop_task, "loopTask", 8192, nullptr, 1, &loop_task_handle, 1); + xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle, 1); #endif } #endif // USE_ESP_IDF diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 868df6e254..dc37dcbc0e 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -155,6 +155,7 @@ // IDF-specific feature flags #ifdef USE_ESP_IDF #define USE_MQTT_IDF_ENQUEUE +#define ESPHOME_LOOP_TASK_STACK_SIZE 8192 #endif // ESP32-specific feature flags diff --git a/tests/components/esp32/test-stack_size.esp32-idf.yaml b/tests/components/esp32/test-stack_size.esp32-idf.yaml new file mode 100644 index 0000000000..4953588035 --- /dev/null +++ b/tests/components/esp32/test-stack_size.esp32-idf.yaml @@ -0,0 +1,6 @@ +esp32: + board: esp32dev + framework: + type: esp-idf + advanced: + loop_task_stack_size: 16384 From 06d0787ee06e0780606c6e7010d599f08adaaef2 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Mon, 3 Nov 2025 08:42:49 -0800 Subject: [PATCH 0058/1145] [nrf52, i2c] i2c support for nrf52 (#8150) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: Ludovic BOUÉ Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- esphome/components/i2c/__init__.py | 70 ++++++++- esphome/components/i2c/i2c.cpp | 3 + esphome/components/i2c/i2c_bus_zephyr.cpp | 133 ++++++++++++++++++ esphome/components/i2c/i2c_bus_zephyr.h | 38 +++++ esphome/components/zephyr/__init__.py | 3 +- .../ads1115/test.nrf52-adafruit.yaml | 4 + .../components/ads1115/test.nrf52-mcumgr.yaml | 4 + .../components/aht10/test.nrf52-adafruit.yaml | 4 + tests/components/i2c/test.nrf52-adafruit.yaml | 4 + tests/components/i2c/test.nrf52-mcumgr.yaml | 4 + tests/components/i2c/test.nrf52-xiao-ble.yaml | 4 + .../common/i2c/nrf52.yaml | 11 ++ 12 files changed, 276 insertions(+), 6 deletions(-) create mode 100644 esphome/components/i2c/i2c_bus_zephyr.cpp create mode 100644 esphome/components/i2c/i2c_bus_zephyr.h create mode 100644 tests/components/ads1115/test.nrf52-adafruit.yaml create mode 100644 tests/components/ads1115/test.nrf52-mcumgr.yaml create mode 100644 tests/components/aht10/test.nrf52-adafruit.yaml create mode 100644 tests/components/i2c/test.nrf52-adafruit.yaml create mode 100644 tests/components/i2c/test.nrf52-mcumgr.yaml create mode 100644 tests/components/i2c/test.nrf52-xiao-ble.yaml create mode 100644 tests/test_build_components/common/i2c/nrf52.yaml diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 3cfec1e94d..6308923759 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -2,11 +2,18 @@ import logging from esphome import pins import esphome.codegen as cg +from esphome.components.zephyr import ( + zephyr_add_overlay, + zephyr_add_prj_conf, + zephyr_data, +) +from esphome.components.zephyr.const import KEY_BOARD from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_ADDRESS, CONF_FREQUENCY, + CONF_I2C, CONF_I2C_ID, CONF_ID, CONF_SCAN, @@ -15,10 +22,12 @@ from esphome.const import ( CONF_TIMEOUT, PLATFORM_ESP32, PLATFORM_ESP8266, + PLATFORM_NRF52, PLATFORM_RP2040, PlatformFramework, ) from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.cpp_generator import MockObj import esphome.final_validate as fv LOGGER = logging.getLogger(__name__) @@ -28,6 +37,7 @@ I2CBus = i2c_ns.class_("I2CBus") InternalI2CBus = i2c_ns.class_("InternalI2CBus", I2CBus) ArduinoI2CBus = i2c_ns.class_("ArduinoI2CBus", InternalI2CBus, cg.Component) IDFI2CBus = i2c_ns.class_("IDFI2CBus", InternalI2CBus, cg.Component) +ZephyrI2CBus = i2c_ns.class_("ZephyrI2CBus", I2CBus, cg.Component) I2CDevice = i2c_ns.class_("I2CDevice") @@ -41,6 +51,8 @@ def _bus_declare_type(value): return cv.declare_id(ArduinoI2CBus)(value) if CORE.using_esp_idf: return cv.declare_id(IDFI2CBus)(value) + if CORE.using_zephyr: + return cv.declare_id(ZephyrI2CBus)(value) raise NotImplementedError @@ -62,23 +74,70 @@ CONFIG_SCHEMA = cv.All( cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All( cv.only_with_esp_idf, cv.boolean ), - cv.Optional(CONF_FREQUENCY, default="50kHz"): cv.All( - cv.frequency, cv.Range(min=0, min_included=False) + cv.SplitDefault( + CONF_FREQUENCY, + esp32="50kHz", + esp8266="50kHz", + rp2040="50kHz", + nrf52="100kHz", + ): cv.All( + cv.frequency, + cv.Range(min=0, min_included=False), + ), + cv.Optional(CONF_TIMEOUT): cv.All( + cv.only_with_framework(["arduino", "esp-idf"]), + cv.positive_time_period, ), - cv.Optional(CONF_TIMEOUT): cv.positive_time_period, cv.Optional(CONF_SCAN, default=True): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA), - cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]), + cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_NRF52]), validate_config, ) +def _final_validate(config): + full_config = fv.full_config.get()[CONF_I2C] + if CORE.using_zephyr and len(full_config) > 1: + raise cv.Invalid("Second i2c is not implemented on Zephyr yet") + + +FINAL_VALIDATE_SCHEMA = _final_validate + + @coroutine_with_priority(CoroPriority.BUS) async def to_code(config): cg.add_global(i2c_ns.using) cg.add_define("USE_I2C") - var = cg.new_Pvariable(config[CONF_ID]) + if CORE.using_zephyr: + zephyr_add_prj_conf("I2C", True) + i2c = "i2c0" + if zephyr_data()[KEY_BOARD] in ["xiao_ble"]: + i2c = "i2c1" + zephyr_add_overlay( + f""" + &pinctrl {{ + {i2c}_default: {i2c}_default {{ + group1 {{ + psels = , + ; + }}; + }}; + {i2c}_sleep: {i2c}_sleep {{ + group1 {{ + psels = , + ; + low-power-enable; + }}; + }}; + }}; + """ + ) + var = cg.new_Pvariable( + config[CONF_ID], MockObj(f"DEVICE_DT_GET(DT_NODELABEL({i2c}))") + ) + else: + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) cg.add(var.set_sda_pin(config[CONF_SDA])) @@ -197,5 +256,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( PlatformFramework.LN882X_ARDUINO, }, "i2c_bus_esp_idf.cpp": {PlatformFramework.ESP32_IDF}, + "i2c_bus_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR}, } ) diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index 31c21f398c..f8c7a1b40b 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -1,6 +1,7 @@ #include "i2c.h" #include "esphome/core/defines.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include @@ -23,6 +24,8 @@ void I2CBus::i2c_scan_() { } else if (err == ERROR_UNKNOWN) { scan_results_.emplace_back(address, false); } + // it takes 16sec to scan on nrf52. It prevents board reset. + arch_feed_wdt(); } #if defined(USE_ESP32) && defined(USE_LOGGER) esp_log_level_set("*", previous); diff --git a/esphome/components/i2c/i2c_bus_zephyr.cpp b/esphome/components/i2c/i2c_bus_zephyr.cpp new file mode 100644 index 0000000000..658dcee35c --- /dev/null +++ b/esphome/components/i2c/i2c_bus_zephyr.cpp @@ -0,0 +1,133 @@ +#ifdef USE_ZEPHYR + +#include "i2c_bus_zephyr.h" +#include +#include "esphome/core/log.h" + +namespace esphome::i2c { + +static const char *const TAG = "i2c.zephyr"; + +void ZephyrI2CBus::setup() { + if (!device_is_ready(this->i2c_dev_)) { + ESP_LOGE(TAG, "I2C dev is not ready."); + mark_failed(); + return; + } + + int ret = i2c_configure(this->i2c_dev_, this->dev_config_); + if (ret < 0) { + ESP_LOGE(TAG, "I2C: Failed to configure device"); + } + + this->recovery_result_ = i2c_recover_bus(this->i2c_dev_); + if (this->recovery_result_ != 0) { + ESP_LOGE(TAG, "I2C recover bus failed, err %d", this->recovery_result_); + } + if (this->scan_) { + ESP_LOGV(TAG, "Scanning I2C bus for active devices..."); + this->i2c_scan_(); + } +} + +void ZephyrI2CBus::dump_config() { + auto get_speed = [](uint32_t dev_config) { + switch (I2C_SPEED_GET(dev_config)) { + case I2C_SPEED_STANDARD: + return "100 kHz"; + case I2C_SPEED_FAST: + return "400 kHz"; + case I2C_SPEED_FAST_PLUS: + return "1 MHz"; + case I2C_SPEED_HIGH: + return "3.4 MHz"; + case I2C_SPEED_ULTRA: + return "5 MHz"; + } + return "unknown"; + }; + ESP_LOGCONFIG(TAG, + "I2C Bus:\n" + " SDA Pin: GPIO%u\n" + " SCL Pin: GPIO%u\n" + " Frequency: %s\n" + " Name: %s", + this->sda_pin_, this->scl_pin_, get_speed(this->dev_config_), this->i2c_dev_->name); + + if (this->recovery_result_ != 0) { + ESP_LOGCONFIG(TAG, " Recovery: failed, err %d", this->recovery_result_); + } else { + ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered"); + } + if (this->scan_) { + ESP_LOGI(TAG, "Results from I2C bus scan:"); + if (scan_results_.empty()) { + ESP_LOGI(TAG, "Found no I2C devices!"); + } else { + for (const auto &s : scan_results_) { + if (s.second) { + ESP_LOGI(TAG, "Found I2C device at address 0x%02X", s.first); + } else { + ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first); + } + } + } + } +} + +ErrorCode ZephyrI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, + uint8_t *read_buffer, size_t read_count) { + if (!device_is_ready(this->i2c_dev_)) { + return ERROR_NOT_INITIALIZED; + } + + i2c_msg msgs[2]{}; + size_t cnt = 0; + uint8_t dst = 0x00; // dummy data to not use random value + + if (read_count == 0 && write_count == 0) { + msgs[cnt].buf = &dst; + msgs[cnt].len = 0U; + msgs[cnt++].flags = I2C_MSG_WRITE; + } else { + if (write_count) { + // the same struct is used for read/write — const cast is fine; data isn't modified + msgs[cnt].buf = const_cast(write_buffer); + msgs[cnt].len = write_count; + msgs[cnt++].flags = I2C_MSG_WRITE; + } + if (read_count) { + msgs[cnt].buf = const_cast(read_buffer); + msgs[cnt].len = read_count; + msgs[cnt++].flags = I2C_MSG_READ | I2C_MSG_RESTART; + } + } + + msgs[cnt - 1].flags |= I2C_MSG_STOP; + + auto err = i2c_transfer(this->i2c_dev_, msgs, cnt, address); + + if (err == -EIO) { + return ERROR_NOT_ACKNOWLEDGED; + } + + if (err != 0) { + ESP_LOGE(TAG, "i2c transfer error %d", err); + return ERROR_UNKNOWN; + } + + return ERROR_OK; +} + +void ZephyrI2CBus::set_frequency(uint32_t frequency) { + this->dev_config_ &= ~I2C_SPEED_MASK; + if (frequency >= 400000) { + this->dev_config_ |= I2C_SPEED_SET(I2C_SPEED_FAST); + } else { + this->dev_config_ |= I2C_SPEED_SET(I2C_SPEED_STANDARD); + } +} + +} // namespace esphome::i2c + +#endif diff --git a/esphome/components/i2c/i2c_bus_zephyr.h b/esphome/components/i2c/i2c_bus_zephyr.h new file mode 100644 index 0000000000..49cac5b992 --- /dev/null +++ b/esphome/components/i2c/i2c_bus_zephyr.h @@ -0,0 +1,38 @@ +#pragma once + +#ifdef USE_ZEPHYR + +#include "i2c_bus.h" +#include "esphome/core/component.h" + +struct device; + +namespace esphome::i2c { + +class ZephyrI2CBus : public InternalI2CBus, public Component { + public: + explicit ZephyrI2CBus(const device *i2c_dev) : i2c_dev_(i2c_dev) {} + void setup() override; + void dump_config() override; + ErrorCode write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer, + size_t read_count) override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void set_scan(bool scan) { scan_ = scan; } + void set_sda_pin(uint8_t sda_pin) { this->sda_pin_ = sda_pin; } + void set_scl_pin(uint8_t scl_pin) { this->scl_pin_ = scl_pin; } + void set_frequency(uint32_t frequency); + + int get_port() const override { return 0; } + + protected: + const device *i2c_dev_; + int recovery_result_ = 0; + uint8_t sda_pin_{}; + uint8_t scl_pin_{}; + uint32_t dev_config_{}; +}; + +} // namespace esphome::i2c + +#endif diff --git a/esphome/components/zephyr/__init__.py b/esphome/components/zephyr/__init__.py index a2fb12a5e2..0381fbcba9 100644 --- a/esphome/components/zephyr/__init__.py +++ b/esphome/components/zephyr/__init__.py @@ -1,4 +1,5 @@ from pathlib import Path +import textwrap from typing import TypedDict import esphome.codegen as cg @@ -90,7 +91,7 @@ def zephyr_add_prj_conf( def zephyr_add_overlay(content): - zephyr_data()[KEY_OVERLAY] += content + zephyr_data()[KEY_OVERLAY] += textwrap.dedent(content) def add_extra_build_file(filename: str, path: Path) -> bool: diff --git a/tests/components/ads1115/test.nrf52-adafruit.yaml b/tests/components/ads1115/test.nrf52-adafruit.yaml new file mode 100644 index 0000000000..2a0de6241c --- /dev/null +++ b/tests/components/ads1115/test.nrf52-adafruit.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/nrf52.yaml + +<<: !include common.yaml diff --git a/tests/components/ads1115/test.nrf52-mcumgr.yaml b/tests/components/ads1115/test.nrf52-mcumgr.yaml new file mode 100644 index 0000000000..2a0de6241c --- /dev/null +++ b/tests/components/ads1115/test.nrf52-mcumgr.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/nrf52.yaml + +<<: !include common.yaml diff --git a/tests/components/aht10/test.nrf52-adafruit.yaml b/tests/components/aht10/test.nrf52-adafruit.yaml new file mode 100644 index 0000000000..2a0de6241c --- /dev/null +++ b/tests/components/aht10/test.nrf52-adafruit.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/nrf52.yaml + +<<: !include common.yaml diff --git a/tests/components/i2c/test.nrf52-adafruit.yaml b/tests/components/i2c/test.nrf52-adafruit.yaml new file mode 100644 index 0000000000..2a0de6241c --- /dev/null +++ b/tests/components/i2c/test.nrf52-adafruit.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/nrf52.yaml + +<<: !include common.yaml diff --git a/tests/components/i2c/test.nrf52-mcumgr.yaml b/tests/components/i2c/test.nrf52-mcumgr.yaml new file mode 100644 index 0000000000..2a0de6241c --- /dev/null +++ b/tests/components/i2c/test.nrf52-mcumgr.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/nrf52.yaml + +<<: !include common.yaml diff --git a/tests/components/i2c/test.nrf52-xiao-ble.yaml b/tests/components/i2c/test.nrf52-xiao-ble.yaml new file mode 100644 index 0000000000..2a0de6241c --- /dev/null +++ b/tests/components/i2c/test.nrf52-xiao-ble.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/nrf52.yaml + +<<: !include common.yaml diff --git a/tests/test_build_components/common/i2c/nrf52.yaml b/tests/test_build_components/common/i2c/nrf52.yaml new file mode 100644 index 0000000000..b86cdf0d69 --- /dev/null +++ b/tests/test_build_components/common/i2c/nrf52.yaml @@ -0,0 +1,11 @@ +# Common I2C configuration for NRF52 tests + +substitutions: + scl_pin: P0.04 + sda_pin: P0.05 + +i2c: + - id: i2c_bus + scl: ${scl_pin} + sda: ${sda_pin} + scan: true From 3f12630a6b0227ee6787f92709205692313a629e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Nov 2025 13:13:37 -0600 Subject: [PATCH 0059/1145] [core][esp32_ble][socket] Add wake_loop_threadsafe() helper for background thread wakeups (#11681) --- esphome/components/esp32_ble/__init__.py | 10 +- esphome/components/esp32_ble/ble.cpp | 112 +----------------- esphome/components/esp32_ble/ble.h | 40 +------ esphome/components/socket/__init__.py | 27 +++++ esphome/core/application.cpp | 79 ++++++++++++ esphome/core/application.h | 42 +++++++ esphome/core/defines.h | 1 + tests/components/socket/conftest.py | 12 ++ .../socket/test_wake_loop_threadsafe.py | 42 +++++++ 9 files changed, 217 insertions(+), 148 deletions(-) create mode 100644 tests/components/socket/conftest.py create mode 100644 tests/components/socket/test_wake_loop_threadsafe.py diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 1ae8df6f5e..ced7e3fec9 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -22,6 +22,7 @@ from esphome.core import CORE, CoroPriority, TimePeriod, coroutine_with_priority import esphome.final_validate as fv DEPENDENCIES = ["esp32"] +AUTO_LOAD = ["socket"] CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"] DOMAIN = "esp32_ble" @@ -482,13 +483,10 @@ async def to_code(config): cg.add(var.set_name(name)) await cg.register_component(var, config) - # BLE uses 1 UDP socket for event notification to wake up main loop from select() + # BLE uses the socket wake_loop_threadsafe() mechanism to wake the main loop from BLE tasks # This enables low-latency (~12μs) BLE event processing instead of waiting for - # select() timeout (0-16ms). The socket is created in ble_setup_() and used to - # wake lwip_select() when BLE events arrive from the BLE thread. - # Note: Called during config generation, socket is created at runtime. In practice, - # always used since esp32_ble only runs on ESP32 which always has USE_SOCKET_SELECT_SUPPORT. - socket.consume_sockets(1, "esp32_ble")(config) + # select() timeout (0-16ms). The wake socket is shared across all components. + socket.require_wake_loop_threadsafe() # Define max connections for use in C++ code (e.g., ble_server.h) max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index d6f7e1ce43..fc26a7fc21 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -297,21 +297,10 @@ bool ESP32BLE::ble_setup_() { // BLE takes some time to be fully set up, 200ms should be more than enough delay(200); // NOLINT - // Set up notification socket to wake main loop for BLE events - // This enables low-latency (~12μs) event processing instead of waiting for select() timeout -#ifdef USE_SOCKET_SELECT_SUPPORT - this->setup_event_notification_(); -#endif - return true; } bool ESP32BLE::ble_dismantle_() { - // Clean up notification socket first before dismantling BLE stack -#ifdef USE_SOCKET_SELECT_SUPPORT - this->cleanup_event_notification_(); -#endif - esp_err_t err = esp_bluedroid_disable(); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err); @@ -409,12 +398,6 @@ void ESP32BLE::loop() { break; } -#ifdef USE_SOCKET_SELECT_SUPPORT - // Drain any notification socket events first - // This clears the socket so it doesn't stay "ready" in subsequent select() calls - this->drain_event_notifications_(); -#endif - BLEEvent *ble_event = this->ble_events_.pop(); while (ble_event != nullptr) { switch (ble_event->type_) { @@ -589,8 +572,8 @@ void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_pa GAP_SECURITY_EVENTS: enqueue_ble_event(event, param); // Wake up main loop to process security event immediately -#ifdef USE_SOCKET_SELECT_SUPPORT - global_ble->notify_main_loop_(); +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + App.wake_loop_threadsafe(); #endif return; @@ -612,8 +595,8 @@ void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gat esp_ble_gatts_cb_param_t *param) { enqueue_ble_event(event, gatts_if, param); // Wake up main loop to process GATT event immediately -#ifdef USE_SOCKET_SELECT_SUPPORT - global_ble->notify_main_loop_(); +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + App.wake_loop_threadsafe(); #endif } #endif @@ -623,8 +606,8 @@ void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gat esp_ble_gattc_cb_param_t *param) { enqueue_ble_event(event, gattc_if, param); // Wake up main loop to process GATT event immediately -#ifdef USE_SOCKET_SELECT_SUPPORT - global_ble->notify_main_loop_(); +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + App.wake_loop_threadsafe(); #endif } #endif @@ -665,89 +648,6 @@ void ESP32BLE::dump_config() { } } -#ifdef USE_SOCKET_SELECT_SUPPORT -void ESP32BLE::setup_event_notification_() { - // Create UDP socket for event notifications - this->notify_fd_ = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (this->notify_fd_ < 0) { - ESP_LOGW(TAG, "Event socket create failed: %d", errno); - return; - } - - // Bind to loopback with auto-assigned port - struct sockaddr_in addr = {}; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = lwip_htonl(INADDR_LOOPBACK); - addr.sin_port = 0; // Auto-assign port - - if (lwip_bind(this->notify_fd_, (struct sockaddr *) &addr, sizeof(addr)) < 0) { - ESP_LOGW(TAG, "Event socket bind failed: %d", errno); - lwip_close(this->notify_fd_); - this->notify_fd_ = -1; - return; - } - - // Get the assigned address and connect to it - // Connecting a UDP socket allows using send() instead of sendto() for better performance - struct sockaddr_in notify_addr; - socklen_t len = sizeof(notify_addr); - if (lwip_getsockname(this->notify_fd_, (struct sockaddr *) ¬ify_addr, &len) < 0) { - ESP_LOGW(TAG, "Event socket address failed: %d", errno); - lwip_close(this->notify_fd_); - this->notify_fd_ = -1; - return; - } - - // Connect to self (loopback) - allows using send() instead of sendto() - // After connect(), no need to store notify_addr - the socket remembers it - if (lwip_connect(this->notify_fd_, (struct sockaddr *) ¬ify_addr, sizeof(notify_addr)) < 0) { - ESP_LOGW(TAG, "Event socket connect failed: %d", errno); - lwip_close(this->notify_fd_); - this->notify_fd_ = -1; - return; - } - - // Set non-blocking mode - int flags = lwip_fcntl(this->notify_fd_, F_GETFL, 0); - lwip_fcntl(this->notify_fd_, F_SETFL, flags | O_NONBLOCK); - - // Register with application's select() loop - if (!App.register_socket_fd(this->notify_fd_)) { - ESP_LOGW(TAG, "Event socket register failed"); - lwip_close(this->notify_fd_); - this->notify_fd_ = -1; - return; - } - - ESP_LOGD(TAG, "Event socket ready"); -} - -void ESP32BLE::cleanup_event_notification_() { - if (this->notify_fd_ >= 0) { - App.unregister_socket_fd(this->notify_fd_); - lwip_close(this->notify_fd_); - this->notify_fd_ = -1; - ESP_LOGD(TAG, "Event socket closed"); - } -} - -void ESP32BLE::drain_event_notifications_() { - // Called from main loop to drain any pending notifications - // Must check is_socket_ready() to avoid blocking on empty socket - if (this->notify_fd_ >= 0 && App.is_socket_ready(this->notify_fd_)) { - char buffer[BLE_EVENT_NOTIFY_DRAIN_BUFFER_SIZE]; - // Drain all pending notifications with non-blocking reads - // Multiple BLE events may have triggered multiple writes, so drain until EWOULDBLOCK - // We control both ends of this loopback socket (always write 1 byte per event), - // so no error checking needed - any errors indicate catastrophic system failure - while (lwip_recvfrom(this->notify_fd_, buffer, sizeof(buffer), 0, nullptr, nullptr) > 0) { - // Just draining, no action needed - actual BLE events are already queued - } - } -} - -#endif // USE_SOCKET_SELECT_SUPPORT - uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) { uint64_t u = 0; u |= uint64_t(address[0] & 0xFF) << 40; diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 7c3195db6d..3be6a7048d 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -166,12 +166,10 @@ class ESP32BLE : public Component { void advertising_init_(); #endif -#ifdef USE_SOCKET_SELECT_SUPPORT - void setup_event_notification_(); // Create notification socket - void cleanup_event_notification_(); // Close and unregister socket - inline void notify_main_loop_(); // Wake up select() from BLE thread (hot path - inlined) - void drain_event_notifications_(); // Read pending notifications in main loop -#endif + // BLE uses the core wake_loop_threadsafe() mechanism to wake the main event loop + // from BLE tasks. This enables low-latency (~12μs) event processing instead of + // waiting for select() timeout (0-16ms). The wake socket is shared with other + // components that need this functionality. private: template friend void enqueue_ble_event(Args... args); @@ -207,13 +205,6 @@ class ESP32BLE : public Component { esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; // 4 bytes (enum) uint32_t advertising_cycle_time_{}; // 4 bytes -#ifdef USE_SOCKET_SELECT_SUPPORT - // Event notification socket for waking up main loop from BLE thread - // Uses connected UDP loopback socket to wake lwip_select() with ~12μs latency vs 0-16ms timeout - // Socket is connected during setup, allowing use of send() instead of sendto() for efficiency - int notify_fd_{-1}; // 4 bytes (file descriptor) -#endif - // 2-byte aligned members uint16_t appearance_{0}; // 2 bytes @@ -225,29 +216,6 @@ class ESP32BLE : public Component { // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32BLE *global_ble; -#ifdef USE_SOCKET_SELECT_SUPPORT -// Inline implementations for hot-path functions -// These are called from BLE thread (notify) and main loop (drain) on every event - -// Small buffer for draining notification bytes (1 byte sent per BLE event) -// Size allows draining multiple notifications per recvfrom() without wasting stack -static constexpr size_t BLE_EVENT_NOTIFY_DRAIN_BUFFER_SIZE = 16; - -inline void ESP32BLE::notify_main_loop_() { - // Called from BLE thread context when events are queued - // Wakes up lwip_select() in main loop by writing to connected loopback socket - if (this->notify_fd_ >= 0) { - const char dummy = 1; - // Non-blocking send - if it fails (unlikely), select() will wake on timeout anyway - // No error checking needed: we control both ends of this loopback socket, and the - // BLE event is already queued. Notification is best-effort to reduce latency. - // This is safe to call from BLE thread - send() is thread-safe in lwip - // Socket is already connected to loopback address, so send() is faster than sendto() - lwip_send(this->notify_fd_, &dummy, 1, 0); - } -} -#endif // USE_SOCKET_SELECT_SUPPORT - template class BLEEnabledCondition : public Condition { public: bool check(Ts... x) override { return global_ble->is_active(); } diff --git a/esphome/components/socket/__init__.py b/esphome/components/socket/__init__.py index e6a4cfc07f..49e074a6ee 100644 --- a/esphome/components/socket/__init__.py +++ b/esphome/components/socket/__init__.py @@ -15,6 +15,9 @@ IMPLEMENTATION_BSD_SOCKETS = "bsd_sockets" # Components register their socket needs and platforms read this to configure appropriately KEY_SOCKET_CONSUMERS = "socket_consumers" +# Wake loop threadsafe support tracking +KEY_WAKE_LOOP_THREADSAFE_REQUIRED = "wake_loop_threadsafe_required" + def consume_sockets( value: int, consumer: str @@ -37,6 +40,30 @@ def consume_sockets( return _consume_sockets +def require_wake_loop_threadsafe() -> None: + """Mark that wake_loop_threadsafe support is required by a component. + + Call this from components that need to wake the main event loop from background threads. + This enables the shared UDP loopback socket mechanism (~208 bytes RAM). + The socket is shared across all components that use this feature. + + IMPORTANT: This is for background thread context only, NOT ISR context. + Socket operations are not safe to call from ISR handlers. + + Example: + from esphome.components import socket + + async def to_code(config): + socket.require_wake_loop_threadsafe() + """ + # Only set up once (idempotent - multiple components can call this) + if not CORE.data.get(KEY_WAKE_LOOP_THREADSAFE_REQUIRED, False): + CORE.data[KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True + cg.add_define("USE_WAKE_LOOP_THREADSAFE") + # Consume 1 socket for the shared wake notification socket + consume_sockets(1, "socket.wake_loop_threadsafe")({}) + + CONFIG_SCHEMA = cv.Schema( { cv.SplitDefault( diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 61cfcc7585..75814ae253 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -122,6 +122,11 @@ void Application::setup() { // Clear setup priority overrides to free memory clear_setup_priority_overrides(); +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + // Set up wake socket for waking main loop from tasks + this->setup_wake_loop_threadsafe_(); +#endif + this->schedule_dump_config(); } void Application::loop() { @@ -472,6 +477,11 @@ void Application::enable_pending_loops_() { } void Application::before_loop_tasks_(uint32_t loop_start_time) { +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + // Drain wake notifications first to clear socket for next wake + this->drain_wake_notifications_(); +#endif + // Process scheduled tasks this->scheduler.call(loop_start_time); @@ -625,4 +635,73 @@ void Application::yield_with_select_(uint32_t delay_ms) { Application App; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) +void Application::setup_wake_loop_threadsafe_() { + // Create UDP socket for wake notifications + this->wake_socket_fd_ = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (this->wake_socket_fd_ < 0) { + ESP_LOGW(TAG, "Wake socket create failed: %d", errno); + return; + } + + // Bind to loopback with auto-assigned port + struct sockaddr_in addr = {}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = lwip_htonl(INADDR_LOOPBACK); + addr.sin_port = 0; // Auto-assign port + + if (lwip_bind(this->wake_socket_fd_, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + ESP_LOGW(TAG, "Wake socket bind failed: %d", errno); + lwip_close(this->wake_socket_fd_); + this->wake_socket_fd_ = -1; + return; + } + + // Get the assigned address and connect to it + // Connecting a UDP socket allows using send() instead of sendto() for better performance + struct sockaddr_in wake_addr; + socklen_t len = sizeof(wake_addr); + if (lwip_getsockname(this->wake_socket_fd_, (struct sockaddr *) &wake_addr, &len) < 0) { + ESP_LOGW(TAG, "Wake socket address failed: %d", errno); + lwip_close(this->wake_socket_fd_); + this->wake_socket_fd_ = -1; + return; + } + + // Connect to self (loopback) - allows using send() instead of sendto() + // After connect(), no need to store wake_addr - the socket remembers it + if (lwip_connect(this->wake_socket_fd_, (struct sockaddr *) &wake_addr, sizeof(wake_addr)) < 0) { + ESP_LOGW(TAG, "Wake socket connect failed: %d", errno); + lwip_close(this->wake_socket_fd_); + this->wake_socket_fd_ = -1; + return; + } + + // Set non-blocking mode + int flags = lwip_fcntl(this->wake_socket_fd_, F_GETFL, 0); + lwip_fcntl(this->wake_socket_fd_, F_SETFL, flags | O_NONBLOCK); + + // Register with application's select() loop + if (!this->register_socket_fd(this->wake_socket_fd_)) { + ESP_LOGW(TAG, "Wake socket register failed"); + lwip_close(this->wake_socket_fd_); + this->wake_socket_fd_ = -1; + return; + } +} + +void Application::wake_loop_threadsafe() { + // Called from FreeRTOS task context when events need immediate processing + // Wakes up lwip_select() in main loop by writing to connected loopback socket + if (this->wake_socket_fd_ >= 0) { + const char dummy = 1; + // Non-blocking send - if it fails (unlikely), select() will wake on timeout anyway + // No error checking needed: we control both ends of this loopback socket. + // This is safe to call from FreeRTOS tasks - send() is thread-safe in lwip + // Socket is already connected to loopback address, so send() is faster than sendto() + lwip_send(this->wake_socket_fd_, &dummy, 1, 0); + } +} +#endif // defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + } // namespace esphome diff --git a/esphome/core/application.h b/esphome/core/application.h index 29a734f000..dae44d8902 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -21,7 +21,10 @@ #ifdef USE_SOCKET_SELECT_SUPPORT #include +#ifdef USE_WAKE_LOOP_THREADSAFE +#include #endif +#endif // USE_SOCKET_SELECT_SUPPORT #ifdef USE_BINARY_SENSOR #include "esphome/components/binary_sensor/binary_sensor.h" @@ -429,6 +432,13 @@ class Application { /// Check if there's data available on a socket without blocking /// This function is thread-safe for reading, but should be called after select() has run bool is_socket_ready(int fd) const; + +#ifdef USE_WAKE_LOOP_THREADSAFE + /// Wake the main event loop from a FreeRTOS task + /// Thread-safe, can be called from task context to immediately wake select() + /// IMPORTANT: NOT safe to call from ISR context (socket operations not ISR-safe) + void wake_loop_threadsafe(); +#endif #endif protected: @@ -454,6 +464,11 @@ class Application { /// Perform a delay while also monitoring socket file descriptors for readiness void yield_with_select_(uint32_t delay_ms); +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + void setup_wake_loop_threadsafe_(); // Create wake notification socket + inline void drain_wake_notifications_(); // Read pending wake notifications in main loop (hot path - inlined) +#endif + // === Member variables ordered by size to minimize padding === // Pointer-sized members first @@ -481,6 +496,9 @@ class Application { FixedVector looping_components_{}; #ifdef USE_SOCKET_SELECT_SUPPORT std::vector socket_fds_; // Vector of all monitored socket file descriptors +#ifdef USE_WAKE_LOOP_THREADSAFE + int wake_socket_fd_{-1}; // Shared wake notification socket for waking main loop from tasks +#endif #endif // std::string members (typically 24-32 bytes each) @@ -597,4 +615,28 @@ class Application { /// Global storage of Application pointer - only one Application can exist. extern Application App; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) +// Inline implementations for hot-path functions +// drain_wake_notifications_() is called on every loop iteration + +// Small buffer for draining wake notification bytes (1 byte sent per wake) +// Size allows draining multiple notifications per recvfrom() without wasting stack +static constexpr size_t WAKE_NOTIFY_DRAIN_BUFFER_SIZE = 16; + +inline void Application::drain_wake_notifications_() { + // Called from main loop to drain any pending wake notifications + // Must check is_socket_ready() to avoid blocking on empty socket + if (this->wake_socket_fd_ >= 0 && this->is_socket_ready(this->wake_socket_fd_)) { + char buffer[WAKE_NOTIFY_DRAIN_BUFFER_SIZE]; + // Drain all pending notifications with non-blocking reads + // Multiple wake events may have triggered multiple writes, so drain until EWOULDBLOCK + // We control both ends of this loopback socket (always write 1 byte per wake), + // so no error checking needed - any errors indicate catastrophic system failure + while (lwip_recvfrom(this->wake_socket_fd_, buffer, sizeof(buffer), 0, nullptr, nullptr) > 0) { + // Just draining, no action needed - wake has already occurred + } + } +} +#endif // defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + } // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index dc37dcbc0e..2be32058ea 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -196,6 +196,7 @@ #define USE_PSRAM #define USE_SOCKET_IMPL_BSD_SOCKETS #define USE_SOCKET_SELECT_SUPPORT +#define USE_WAKE_LOOP_THREADSAFE #define USE_SPEAKER #define USE_SPI #define USE_VOICE_ASSISTANT diff --git a/tests/components/socket/conftest.py b/tests/components/socket/conftest.py new file mode 100644 index 0000000000..5d93cac232 --- /dev/null +++ b/tests/components/socket/conftest.py @@ -0,0 +1,12 @@ +"""Configuration file for socket component tests.""" + +import pytest + +from esphome.core import CORE + + +@pytest.fixture(autouse=True) +def reset_core(): + """Reset CORE after each test.""" + yield + CORE.reset() diff --git a/tests/components/socket/test_wake_loop_threadsafe.py b/tests/components/socket/test_wake_loop_threadsafe.py new file mode 100644 index 0000000000..45e5ea2211 --- /dev/null +++ b/tests/components/socket/test_wake_loop_threadsafe.py @@ -0,0 +1,42 @@ +from esphome.components import socket +from esphome.core import CORE + + +def test_require_wake_loop_threadsafe__first_call() -> None: + """Test that first call sets up define and consumes socket.""" + socket.require_wake_loop_threadsafe() + + # Verify CORE.data was updated + assert CORE.data[socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED] is True + + # Verify the define was added + assert any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines) + + +def test_require_wake_loop_threadsafe__idempotent() -> None: + """Test that subsequent calls are idempotent.""" + # Set up initial state as if already called + CORE.data[socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True + + # Call again - should not raise or fail + socket.require_wake_loop_threadsafe() + + # Verify state is still True + assert CORE.data[socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED] is True + + # Define should not be added since flag was already True + assert not any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines) + + +def test_require_wake_loop_threadsafe__multiple_calls() -> None: + """Test that multiple calls only set up once.""" + # Call three times + socket.require_wake_loop_threadsafe() + socket.require_wake_loop_threadsafe() + socket.require_wake_loop_threadsafe() + + # Verify CORE.data was set + assert CORE.data[socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED] is True + + # Verify the define was added (only once, but we can just check it exists) + assert any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines) From fb7dbc99103fa1225a88df22833f433d093cc623 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Nov 2025 13:50:39 -0600 Subject: [PATCH 0060/1145] [usb_host] Add wake_loop_threadsafe() for low-latency USB event processing (#11683) --- esphome/components/usb_host/__init__.py | 8 +++++++- esphome/components/usb_host/usb_host_client.cpp | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/esphome/components/usb_host/__init__.py b/esphome/components/usb_host/__init__.py index d452e0e9fa..cccabcf646 100644 --- a/esphome/components/usb_host/__init__.py +++ b/esphome/components/usb_host/__init__.py @@ -1,4 +1,5 @@ import esphome.codegen as cg +from esphome.components import socket from esphome.components.esp32 import ( VARIANT_ESP32P4, VARIANT_ESP32S2, @@ -11,7 +12,7 @@ from esphome.const import CONF_DEVICES, CONF_ID from esphome.cpp_types import Component from esphome.types import ConfigType -AUTO_LOAD = ["bytebuffer"] +AUTO_LOAD = ["bytebuffer", "socket"] CODEOWNERS = ["@clydebarrow"] DEPENDENCIES = ["esp32"] usb_host_ns = cg.esphome_ns.namespace("usb_host") @@ -71,6 +72,11 @@ async def to_code(config: ConfigType) -> None: max_requests = config[CONF_MAX_TRANSFER_REQUESTS] cg.add_define("USB_HOST_MAX_REQUESTS", max_requests) + # USB uses the socket wake_loop_threadsafe() mechanism to wake the main loop from USB task + # This enables low-latency (~12μs) USB event processing instead of waiting for + # select() timeout (0-16ms). The wake socket is shared across all components. + socket.require_wake_loop_threadsafe() + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) for device in config.get(CONF_DEVICES) or (): diff --git a/esphome/components/usb_host/usb_host_client.cpp b/esphome/components/usb_host/usb_host_client.cpp index 2139ed869a..0dda36b9d7 100644 --- a/esphome/components/usb_host/usb_host_client.cpp +++ b/esphome/components/usb_host/usb_host_client.cpp @@ -3,6 +3,7 @@ #include "usb_host.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" +#include "esphome/core/application.h" #include "esphome/components/bytebuffer/bytebuffer.h" #include @@ -174,6 +175,11 @@ static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void * // Push to lock-free queue (always succeeds since pool size == queue size) client->event_queue.push(event); + + // Wake main loop immediately to process USB event instead of waiting for select() timeout +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + App.wake_loop_threadsafe(); +#endif } void USBClient::setup() { usb_host_client_config_t config{.is_synchronous = false, From 9c7cb30ae597a4d132b2242cc1fca55067a6e4b3 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:08:50 -0500 Subject: [PATCH 0061/1145] [esp32_hosted] Initial OTA implementation (#11562) --- CODEOWNERS | 1 + .../esp32_hosted/update/__init__.py | 78 +++++++++ .../update/esp32_hosted_update.cpp | 164 ++++++++++++++++++ .../esp32_hosted/update/esp32_hosted_update.h | 32 ++++ esphome/idf_component.yml | 6 +- script/ci-custom.py | 1 + tests/components/esp32_hosted/.gitattributes | 1 + .../esp32_hosted/test.esp32-p4-idf.yaml | 6 + .../components/esp32_hosted/test_firmware.bin | Bin 0 -> 65536 bytes 9 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 esphome/components/esp32_hosted/update/__init__.py create mode 100644 esphome/components/esp32_hosted/update/esp32_hosted_update.cpp create mode 100644 esphome/components/esp32_hosted/update/esp32_hosted_update.h create mode 100644 tests/components/esp32_hosted/.gitattributes create mode 100644 tests/components/esp32_hosted/test_firmware.bin diff --git a/CODEOWNERS b/CODEOWNERS index 667a44fc03..fee0e98f46 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -155,6 +155,7 @@ esphome/components/esp32_ble_tracker/* @bdraco esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_can/* @Sympatron esphome/components/esp32_hosted/* @swoboda1337 +esphome/components/esp32_hosted/update/* @swoboda1337 esphome/components/esp32_improv/* @jesserockz esphome/components/esp32_rmt/* @jesserockz esphome/components/esp32_rmt_led_strip/* @jesserockz diff --git a/esphome/components/esp32_hosted/update/__init__.py b/esphome/components/esp32_hosted/update/__init__.py new file mode 100644 index 0000000000..040f989a64 --- /dev/null +++ b/esphome/components/esp32_hosted/update/__init__.py @@ -0,0 +1,78 @@ +import hashlib +from typing import Any + +import esphome.codegen as cg +from esphome.components import esp32, update +import esphome.config_validation as cv +from esphome.const import CONF_PATH, CONF_RAW_DATA_ID +from esphome.core import CORE, HexInt + +CODEOWNERS = ["@swoboda1337"] +AUTO_LOAD = ["sha256", "watchdog"] +DEPENDENCIES = ["esp32_hosted"] + +CONF_SHA256 = "sha256" + +esp32_hosted_ns = cg.esphome_ns.namespace("esp32_hosted") +Esp32HostedUpdate = esp32_hosted_ns.class_( + "Esp32HostedUpdate", update.UpdateEntity, cg.Component +) + + +def _validate_sha256(value: Any) -> str: + value = cv.string_strict(value) + if len(value) != 64: + raise cv.Invalid("SHA256 must be 64 hexadecimal characters") + try: + bytes.fromhex(value) + except ValueError as e: + raise cv.Invalid(f"SHA256 must be valid hexadecimal: {e}") from e + return value + + +CONFIG_SCHEMA = cv.All( + update.update_schema(Esp32HostedUpdate, device_class="firmware").extend( + { + cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), + cv.Required(CONF_PATH): cv.file_, + cv.Required(CONF_SHA256): _validate_sha256, + } + ), + esp32.only_on_variant( + supported=[ + esp32.const.VARIANT_ESP32H2, + esp32.const.VARIANT_ESP32P4, + ] + ), +) + + +def _validate_firmware(config: dict[str, Any]) -> None: + path = CORE.relative_config_path(config[CONF_PATH]) + with open(path, "rb") as f: + firmware_data = f.read() + calculated = hashlib.sha256(firmware_data).hexdigest() + expected = config[CONF_SHA256].lower() + if calculated != expected: + raise cv.Invalid( + f"SHA256 mismatch for {config[CONF_PATH]}: expected {expected}, got {calculated}" + ) + + +FINAL_VALIDATE_SCHEMA = _validate_firmware + + +async def to_code(config: dict[str, Any]) -> None: + var = await update.new_update(config) + + path = config[CONF_PATH] + with open(CORE.relative_config_path(path), "rb") as f: + firmware_data = f.read() + rhs = [HexInt(x) for x in firmware_data] + prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) + + sha256_bytes = bytes.fromhex(config[CONF_SHA256]) + cg.add(var.set_firmware_sha256([HexInt(b) for b in sha256_bytes])) + cg.add(var.set_firmware_data(prog_arr)) + cg.add(var.set_firmware_size(len(firmware_data))) + await cg.register_component(var, config) diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp new file mode 100644 index 0000000000..adbcc5bf11 --- /dev/null +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp @@ -0,0 +1,164 @@ +#if defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) +#include "esp32_hosted_update.h" +#include "esphome/components/watchdog/watchdog.h" +#include "esphome/components/sha256/sha256.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include +#include +#include + +extern "C" { +#include +} + +namespace esphome::esp32_hosted { + +static const char *const TAG = "esp32_hosted.update"; + +// older coprocessor firmware versions have a 1500-byte limit per RPC call +constexpr size_t CHUNK_SIZE = 1500; + +void Esp32HostedUpdate::setup() { + this->update_info_.title = "ESP32 Hosted Coprocessor"; + + // get coprocessor version + esp_hosted_coprocessor_fwver_t ver_info; + if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) { + this->update_info_.current_version = str_sprintf("%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1); + } else { + this->update_info_.current_version = "unknown"; + } + ESP_LOGD(TAG, "Coprocessor version: %s", this->update_info_.current_version.c_str()); + + // get image version + const int app_desc_offset = sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t); + if (this->firmware_size_ >= app_desc_offset + sizeof(esp_app_desc_t)) { + esp_app_desc_t *app_desc = (esp_app_desc_t *) (this->firmware_data_ + app_desc_offset); + if (app_desc->magic_word == ESP_APP_DESC_MAGIC_WORD) { + ESP_LOGD(TAG, "Firmware version: %s", app_desc->version); + ESP_LOGD(TAG, "Project name: %s", app_desc->project_name); + ESP_LOGD(TAG, "Build date: %s", app_desc->date); + ESP_LOGD(TAG, "Build time: %s", app_desc->time); + ESP_LOGD(TAG, "IDF version: %s", app_desc->idf_ver); + this->update_info_.latest_version = app_desc->version; + if (this->update_info_.latest_version != this->update_info_.current_version) { + this->state_ = update::UPDATE_STATE_AVAILABLE; + } else { + this->state_ = update::UPDATE_STATE_NO_UPDATE; + } + } else { + ESP_LOGW(TAG, "Invalid app description magic word: 0x%08x (expected 0x%08x)", app_desc->magic_word, + ESP_APP_DESC_MAGIC_WORD); + this->state_ = update::UPDATE_STATE_NO_UPDATE; + } + } else { + ESP_LOGW(TAG, "Firmware too small to contain app description"); + this->state_ = update::UPDATE_STATE_NO_UPDATE; + } + + // publish state + this->status_clear_error(); + this->publish_state(); +} + +void Esp32HostedUpdate::dump_config() { + ESP_LOGCONFIG(TAG, + "ESP32 Hosted Update:\n" + " Current Version: %s\n" + " Latest Version: %s\n" + " Latest Size: %zu bytes", + this->update_info_.current_version.c_str(), this->update_info_.latest_version.c_str(), + this->firmware_size_); +} + +void Esp32HostedUpdate::perform(bool force) { + if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) { + ESP_LOGW(TAG, "Update not available"); + return; + } + + if (this->firmware_data_ == nullptr || this->firmware_size_ == 0) { + ESP_LOGE(TAG, "No firmware data available"); + return; + } + + sha256::SHA256 hasher; + hasher.init(); + hasher.add(this->firmware_data_, this->firmware_size_); + hasher.calculate(); + if (!hasher.equals_bytes(this->firmware_sha256_.data())) { + this->status_set_error("SHA256 verification failed"); + this->publish_state(); + return; + } + + ESP_LOGI(TAG, "Starting OTA update (%zu bytes)", this->firmware_size_); + + watchdog::WatchdogManager watchdog(20000); + update::UpdateState prev_state = this->state_; + this->state_ = update::UPDATE_STATE_INSTALLING; + this->update_info_.has_progress = false; + this->publish_state(); + + esp_err_t err = esp_hosted_slave_ota_begin(); // NOLINT + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err)); + this->state_ = prev_state; + this->status_set_error("Failed to begin OTA"); + this->publish_state(); + return; + } + + uint8_t chunk[CHUNK_SIZE]; + const uint8_t *data_ptr = this->firmware_data_; + size_t remaining = this->firmware_size_; + while (remaining > 0) { + size_t chunk_size = std::min(remaining, static_cast(CHUNK_SIZE)); + memcpy(chunk, data_ptr, chunk_size); + err = esp_hosted_slave_ota_write(chunk, chunk_size); // NOLINT + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err)); + esp_hosted_slave_ota_end(); // NOLINT + this->state_ = prev_state; + this->status_set_error("Failed to write OTA data"); + this->publish_state(); + return; + } + data_ptr += chunk_size; + remaining -= chunk_size; + App.feed_wdt(); + } + + err = esp_hosted_slave_ota_end(); // NOLINT + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err)); + this->state_ = prev_state; + this->status_set_error("Failed to end OTA"); + this->publish_state(); + return; + } + + // activate new firmware + err = esp_hosted_slave_ota_activate(); // NOLINT + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to activate OTA: %s", esp_err_to_name(err)); + this->state_ = prev_state; + this->status_set_error("Failed to activate OTA"); + this->publish_state(); + return; + } + + // update state + ESP_LOGI(TAG, "OTA update successful"); + this->state_ = update::UPDATE_STATE_NO_UPDATE; + this->status_clear_error(); + this->publish_state(); + + // schedule a restart to ensure everything is in sync + ESP_LOGI(TAG, "Restarting in 1 second"); + this->set_timeout(1000, []() { App.safe_reboot(); }); +} + +} // namespace esphome::esp32_hosted +#endif diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.h b/esphome/components/esp32_hosted/update/esp32_hosted_update.h new file mode 100644 index 0000000000..9c087bf72a --- /dev/null +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.h @@ -0,0 +1,32 @@ +#pragma once + +#if defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) + +#include "esphome/core/component.h" +#include "esphome/components/update/update_entity.h" +#include + +namespace esphome::esp32_hosted { + +class Esp32HostedUpdate : public update::UpdateEntity, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + + void perform(bool force) override; + void check() override {} + + void set_firmware_data(const uint8_t *data) { this->firmware_data_ = data; } + void set_firmware_size(size_t size) { this->firmware_size_ = size; } + void set_firmware_sha256(const std::array &sha256) { this->firmware_sha256_ = sha256; } + + protected: + const uint8_t *firmware_data_{nullptr}; + size_t firmware_size_{0}; + std::array firmware_sha256_; +}; + +} // namespace esphome::esp32_hosted + +#endif diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 1a6dc8b97d..31112caf0a 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -6,15 +6,15 @@ dependencies: espressif/mdns: version: 1.8.2 espressif/esp_wifi_remote: - version: 0.10.2 + version: 1.1.5 rules: - if: "target in [esp32h2, esp32p4]" espressif/eppp_link: - version: 0.2.0 + version: 1.1.3 rules: - if: "target in [esp32h2, esp32p4]" espressif/esp_hosted: - version: 2.0.11 + version: 2.6.1 rules: - if: "target in [esp32h2, esp32p4]" zorxx/multipart-parser: diff --git a/script/ci-custom.py b/script/ci-custom.py index 6b01623d92..106aa438fe 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -71,6 +71,7 @@ ignore_types = ( ".apng", ".gif", ".webp", + ".bin", ) LINT_FILE_CHECKS = [] diff --git a/tests/components/esp32_hosted/.gitattributes b/tests/components/esp32_hosted/.gitattributes new file mode 100644 index 0000000000..6abdc56117 --- /dev/null +++ b/tests/components/esp32_hosted/.gitattributes @@ -0,0 +1 @@ +*.bin -text diff --git a/tests/components/esp32_hosted/test.esp32-p4-idf.yaml b/tests/components/esp32_hosted/test.esp32-p4-idf.yaml index dade44d145..2a76f17e2f 100644 --- a/tests/components/esp32_hosted/test.esp32-p4-idf.yaml +++ b/tests/components/esp32_hosted/test.esp32-p4-idf.yaml @@ -1 +1,7 @@ <<: !include common.yaml + +update: + - platform: esp32_hosted + name: "Coprocessor Firmware Update" + path: $component_dir/test_firmware.bin + sha256: de2f256064a0af797747c2b97505dc0b9f3df0de4f489eac731c23ae9ca9cc31 diff --git a/tests/components/esp32_hosted/test_firmware.bin b/tests/components/esp32_hosted/test_firmware.bin new file mode 100644 index 0000000000000000000000000000000000000000..c97c12f9b0a24bfc19c74a2b265a97c924137775 GIT binary patch literal 65536 zcmeIufdBvi0Dz$VsTV1P3IhfV7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ u0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFi-;k4*&rG literal 0 HcmV?d00001 From 8aa8bb8f9898760dfb169c57706b91e9833da785 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 4 Nov 2025 07:45:32 +1000 Subject: [PATCH 0062/1145] [epaper_spi] Refactoring (#11540) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/display/display.h | 2 +- esphome/components/epaper_spi/display.py | 124 ++++++++-- esphome/components/epaper_spi/epaper_spi.cpp | 210 ++++++++++------- esphome/components/epaper_spi/epaper_spi.h | 104 ++++++--- .../epaper_spi_model_7p3in_spectra_e6.cpp | 42 ---- .../epaper_spi_model_7p3in_spectra_e6.h | 45 ---- .../epaper_spi/epaper_spi_spectra_e6.cpp | 221 ++++++++++-------- .../epaper_spi/epaper_spi_spectra_e6.h | 15 +- .../components/epaper_spi/models/__init__.py | 65 ++++++ .../epaper_spi/models/spectra_e6.py | 51 ++++ esphome/components/mipi/__init__.py | 23 +- .../components/split_buffer/split_buffer.cpp | 43 ++-- .../components/split_buffer/split_buffer.h | 10 +- .../epaper_spi/test.esp32-s3-idf.yaml | 8 +- 14 files changed, 619 insertions(+), 344 deletions(-) delete mode 100644 esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.cpp delete mode 100644 esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.h create mode 100644 esphome/components/epaper_spi/models/__init__.py create mode 100644 esphome/components/epaper_spi/models/spectra_e6.py diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index f2d79d12d9..14205da853 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -210,7 +210,7 @@ class Display : public PollingComponent { /// Fill the entire screen with the given color. virtual void fill(Color color); /// Clear the entire screen by filling it with OFF pixels. - void clear(); + virtual void clear(); /// Get the calculated width of the display in pixels with rotation applied. virtual int get_width() { return this->get_width_internal(); } diff --git a/esphome/components/epaper_spi/display.py b/esphome/components/epaper_spi/display.py index 20549f049d..9ff393b397 100644 --- a/esphome/components/epaper_spi/display.py +++ b/esphome/components/epaper_spi/display.py @@ -1,21 +1,35 @@ +import importlib +import pkgutil + from esphome import core, pins import esphome.codegen as cg from esphome.components import display, spi +from esphome.components.mipi import flatten_sequence, map_sequence import esphome.config_validation as cv from esphome.const import ( CONF_BUSY_PIN, + CONF_CS_PIN, + CONF_DATA_RATE, CONF_DC_PIN, + CONF_DIMENSIONS, + CONF_ENABLE_PIN, + CONF_HEIGHT, CONF_ID, + CONF_INIT_SEQUENCE, CONF_LAMBDA, CONF_MODEL, - CONF_PAGES, CONF_RESET_DURATION, CONF_RESET_PIN, + CONF_WIDTH, ) +from . import models + AUTO_LOAD = ["split_buffer"] DEPENDENCIES = ["spi"] +CONF_INIT_SEQUENCE_ID = "init_sequence_id" + epaper_spi_ns = cg.esphome_ns.namespace("epaper_spi") EPaperBase = epaper_spi_ns.class_( "EPaperBase", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer @@ -24,30 +38,79 @@ EPaperBase = epaper_spi_ns.class_( EPaperSpectraE6 = epaper_spi_ns.class_("EPaperSpectraE6", EPaperBase) EPaper7p3InSpectraE6 = epaper_spi_ns.class_("EPaper7p3InSpectraE6", EPaperSpectraE6) -MODELS = { - "7.3in-spectra-e6": EPaper7p3InSpectraE6, -} +# Import all models dynamically from the models package +for module_info in pkgutil.iter_modules(models.__path__): + importlib.import_module(f".models.{module_info.name}", package=__package__) -CONFIG_SCHEMA = cv.All( - display.FULL_DISPLAY_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(EPaperBase), - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True, space="-"), - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema, - cv.Optional(CONF_RESET_DURATION): cv.All( - cv.positive_time_period_milliseconds, - cv.Range(max=core.TimePeriod(milliseconds=500)), - ), - } - ) - .extend(cv.polling_component_schema("60s")) - .extend(spi.spi_device_schema()), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +MODELS = models.EpaperModel.models + +DIMENSION_SCHEMA = cv.Schema( + { + cv.Required(CONF_WIDTH): cv.int_, + cv.Required(CONF_HEIGHT): cv.int_, + } ) + +def model_schema(config): + model = MODELS[config[CONF_MODEL]] + class_name = epaper_spi_ns.class_(model.class_name, EPaperBase) + cv_dimensions = cv.Optional if model.get_default(CONF_WIDTH) else cv.Required + return ( + display.FULL_DISPLAY_SCHEMA.extend( + spi.spi_device_schema( + cs_pin_required=False, + default_mode="MODE0", + default_data_rate=model.get_default(CONF_DATA_RATE, 10_000_000), + ) + ) + .extend( + { + model.option(pin): pins.gpio_output_pin_schema + for pin in (CONF_RESET_PIN, CONF_CS_PIN, CONF_BUSY_PIN) + } + ) + .extend( + { + cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True), + model.option(CONF_DC_PIN, fallback=None): pins.gpio_output_pin_schema, + cv.GenerateID(): cv.declare_id(class_name), + cv.GenerateID(CONF_INIT_SEQUENCE_ID): cv.declare_id(cg.uint8), + cv_dimensions(CONF_DIMENSIONS): DIMENSION_SCHEMA, + model.option(CONF_ENABLE_PIN): cv.ensure_list( + pins.gpio_output_pin_schema + ), + model.option(CONF_INIT_SEQUENCE, cv.UNDEFINED): cv.ensure_list( + map_sequence + ), + model.option(CONF_RESET_DURATION, cv.UNDEFINED): cv.All( + cv.positive_time_period_milliseconds, + cv.Range(max=core.TimePeriod(milliseconds=500)), + ), + } + ) + ) + + +def customise_schema(config): + """ + Create a customised config schema for a specific model and validate the configuration. + :param config: The configuration dictionary to validate + :return: The validated configuration dictionary + :raises cv.Invalid: If the configuration is invalid + """ + config = cv.Schema( + { + cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True), + }, + extra=cv.ALLOW_EXTRA, + )(config) + return model_schema(config)(config) + + +CONFIG_SCHEMA = customise_schema + FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( "epaper_spi", require_miso=False, require_mosi=True ) @@ -56,8 +119,23 @@ FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( async def to_code(config): model = MODELS[config[CONF_MODEL]] - rhs = model.new() - var = cg.Pvariable(config[CONF_ID], rhs, model) + init_sequence = config.get(CONF_INIT_SEQUENCE) + if init_sequence is None: + init_sequence = model.get_init_sequence(config) + init_sequence = flatten_sequence(init_sequence) + init_sequence_length = len(init_sequence) + init_sequence_id = cg.static_const_array( + config[CONF_INIT_SEQUENCE_ID], init_sequence + ) + width, height = model.get_dimensions(config) + var = cg.new_Pvariable( + config[CONF_ID], + model.name, + width, + height, + init_sequence_id, + init_sequence_length, + ) await display.register_display(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/epaper_spi/epaper_spi.cpp b/esphome/components/epaper_spi/epaper_spi.cpp index 9630ea7f8b..cf6a0b0c3d 100644 --- a/esphome/components/epaper_spi/epaper_spi.cpp +++ b/esphome/components/epaper_spi/epaper_spi.cpp @@ -8,33 +8,20 @@ namespace esphome::epaper_spi { static const char *const TAG = "epaper_spi"; -static const LogString *epaper_state_to_string(EPaperState state) { - switch (state) { - case EPaperState::IDLE: - return LOG_STR("IDLE"); - case EPaperState::UPDATE: - return LOG_STR("UPDATE"); - case EPaperState::RESET: - return LOG_STR("RESET"); - case EPaperState::INITIALISE: - return LOG_STR("INITIALISE"); - case EPaperState::TRANSFER_DATA: - return LOG_STR("TRANSFER_DATA"); - case EPaperState::POWER_ON: - return LOG_STR("POWER_ON"); - case EPaperState::REFRESH_SCREEN: - return LOG_STR("REFRESH_SCREEN"); - case EPaperState::POWER_OFF: - return LOG_STR("POWER_OFF"); - case EPaperState::DEEP_SLEEP: - return LOG_STR("DEEP_SLEEP"); - default: - return LOG_STR("UNKNOWN"); - } +static constexpr const char *const EPAPER_STATE_STRINGS[] = { + "IDLE", "UPDATE", "RESET", "RESET_END", + + "SHOULD_WAIT", "INITIALISE", "TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP", +}; + +const char *EPaperBase::epaper_state_to_string_() { + if (auto idx = static_cast(this->state_); idx < std::size(EPAPER_STATE_STRINGS)) + return EPAPER_STATE_STRINGS[idx]; + return "Unknown"; } void EPaperBase::setup() { - if (!this->init_buffer_(this->get_buffer_length())) { + if (!this->init_buffer_(this->buffer_length_)) { this->mark_failed("Failed to initialise buffer"); return; } @@ -50,7 +37,7 @@ bool EPaperBase::init_buffer_(size_t buffer_length) { return true; } -void EPaperBase::setup_pins_() { +void EPaperBase::setup_pins_() const { this->dc_pin_->setup(); // OUTPUT this->dc_pin_->digital_write(false); @@ -81,11 +68,7 @@ void EPaperBase::data(uint8_t value) { // write a command followed by zero or more bytes of data. // The command is the first byte, length is the length of data only in the second byte, followed by the data. // [COMMAND, LENGTH, DATA...] -void EPaperBase::cmd_data(const uint8_t *data) { - const uint8_t command = data[0]; - const uint8_t length = data[1]; - const uint8_t *ptr = data + 2; - +void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) { ESP_LOGVV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length, format_hex_pretty(ptr, length, '.', false).c_str()); @@ -99,91 +82,146 @@ void EPaperBase::cmd_data(const uint8_t *data) { this->disable(); } -bool EPaperBase::is_idle_() { +bool EPaperBase::is_idle_() const { if (this->busy_pin_ == nullptr) { return true; } - return this->busy_pin_->digital_read(); + return !this->busy_pin_->digital_read(); } -void EPaperBase::reset() { +bool EPaperBase::reset_() const { if (this->reset_pin_ != nullptr) { - this->reset_pin_->digital_write(false); - this->disable_loop(); - this->set_timeout(this->reset_duration_, [this] { - this->reset_pin_->digital_write(true); - this->set_timeout(20, [this] { this->enable_loop(); }); - }); + if (this->state_ == EPaperState::RESET) { + this->reset_pin_->digital_write(false); + return false; + } + this->reset_pin_->digital_write(true); } + return true; } void EPaperBase::update() { - if (!this->state_queue_.empty()) { - ESP_LOGE(TAG, "Display update already in progress - %s", - LOG_STR_ARG(epaper_state_to_string(this->state_queue_.front()))); + if (this->state_ != EPaperState::IDLE) { + ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_()); return; } - - this->state_queue_.push(EPaperState::UPDATE); - this->state_queue_.push(EPaperState::RESET); - this->state_queue_.push(EPaperState::INITIALISE); - this->state_queue_.push(EPaperState::TRANSFER_DATA); - this->state_queue_.push(EPaperState::POWER_ON); - this->state_queue_.push(EPaperState::REFRESH_SCREEN); - this->state_queue_.push(EPaperState::POWER_OFF); - this->state_queue_.push(EPaperState::DEEP_SLEEP); - this->state_queue_.push(EPaperState::IDLE); - + this->set_state_(EPaperState::RESET); this->enable_loop(); } +void EPaperBase::wait_for_idle_(bool should_wait) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + if (should_wait) { + this->waiting_for_idle_start_ = millis(); + this->waiting_for_idle_last_print_ = this->waiting_for_idle_start_; + } +#endif + this->waiting_for_idle_ = should_wait; +} + +/** + * Called during the loop task. + * First defer for any pending delays, then check if we are waiting for the display to become idle. + * If not waiting for idle, process the state machine. + */ + void EPaperBase::loop() { + auto now = millis(); + if (this->delay_until_ != 0) { + // using modulus arithmetic to handle wrap-around + int diff = now - this->delay_until_; + if (diff < 0) { + return; + } + this->delay_until_ = 0; + } if (this->waiting_for_idle_) { if (this->is_idle_()) { this->waiting_for_idle_ = false; + ESP_LOGV(TAG, "Screen now idle after %u ms", (unsigned) (millis() - this->waiting_for_idle_start_)); } else { - if (App.get_loop_component_start_time() - this->waiting_for_idle_last_print_ >= 1000) { - ESP_LOGV(TAG, "Waiting for idle"); - this->waiting_for_idle_last_print_ = App.get_loop_component_start_time(); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + if (now - this->waiting_for_idle_last_print_ >= 1000) { + ESP_LOGV(TAG, "Waiting for idle in state %s", this->epaper_state_to_string_()); + this->waiting_for_idle_last_print_ = millis(); } +#endif return; } } + this->process_state_(); +} - auto state = this->state_queue_.front(); - - switch (state) { +/** + * Process the state machine. + * Typical state sequence: + * IDLE -> RESET -> RESET_END -> UPDATE -> INITIALISE -> TRANSFER_DATA -> POWER_ON -> REFRESH_SCREEN -> POWER_OFF -> + * DEEP_SLEEP -> IDLE + * + * Should a subclassed class need to override this, the method will need to be made virtual. + */ +void EPaperBase::process_state_() { + ESP_LOGV(TAG, "Process state entered in state %s", epaper_state_to_string_()); + switch (this->state_) { + default: + ESP_LOGD(TAG, "Display is in unhandled state %s", epaper_state_to_string_()); + this->disable_loop(); + break; case EPaperState::IDLE: this->disable_loop(); break; + case EPaperState::RESET: + case EPaperState::RESET_END: + if (this->reset_()) { + this->set_state_(EPaperState::UPDATE); + } else { + this->set_state_(EPaperState::RESET_END); + } + break; case EPaperState::UPDATE: this->do_update_(); // Calls ESPHome (current page) lambda - break; - case EPaperState::RESET: - this->reset(); + this->set_state_(EPaperState::INITIALISE); break; case EPaperState::INITIALISE: this->initialise_(); + this->set_state_(EPaperState::TRANSFER_DATA); break; case EPaperState::TRANSFER_DATA: if (!this->transfer_data()) { return; // Not done yet, come back next loop } + this->set_state_(EPaperState::POWER_ON); break; case EPaperState::POWER_ON: this->power_on(); + this->set_state_(EPaperState::REFRESH_SCREEN); break; case EPaperState::REFRESH_SCREEN: this->refresh_screen(); + this->set_state_(EPaperState::POWER_OFF); break; case EPaperState::POWER_OFF: this->power_off(); + this->set_state_(EPaperState::DEEP_SLEEP); break; case EPaperState::DEEP_SLEEP: this->deep_sleep(); + this->set_state_(EPaperState::IDLE); break; } - this->state_queue_.pop(); +} + +void EPaperBase::set_state_(EPaperState state, uint16_t delay) { + ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_()); + this->state_ = state; + this->wait_for_idle_(state > EPaperState::SHOULD_WAIT); + if (delay != 0) { + this->delay_until_ = millis() + delay; + } else { + this->delay_until_ = 0; + } + ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay, + TRUEFALSE(this->waiting_for_idle_)); } void EPaperBase::start_command_() { @@ -203,25 +241,39 @@ void EPaperBase::on_safe_shutdown() { this->deep_sleep(); } void EPaperBase::initialise_() { size_t index = 0; - const auto &sequence = this->init_sequence_; - const size_t sequence_size = this->init_sequence_length_; - while (index != sequence_size) { - if (sequence_size - index < 2) { - this->mark_failed("Malformed init sequence"); - return; - } - const auto *ptr = sequence + index; - const uint8_t length = ptr[1]; - if (sequence_size - index < length + 2) { - this->mark_failed("Malformed init sequence"); - return; - } - this->cmd_data(ptr); - index += length + 2; + auto *sequence = this->init_sequence_; + auto length = this->init_sequence_length_; + while (index != length) { + if (length - index < 2) { + this->mark_failed("Malformed init sequence"); + return; + } + const uint8_t cmd = sequence[index++]; + if (const uint8_t x = sequence[index++]; x == DELAY_FLAG) { + ESP_LOGV(TAG, "Delay %dms", cmd); + delay(cmd); + } else { + const uint8_t num_args = x & 0x7F; + if (length - index < num_args) { + ESP_LOGE(TAG, "Malformed init sequence, cmd = %X, num_args = %u", cmd, num_args); + this->mark_failed(); + return; + } + ESP_LOGV(TAG, "Command %02X, length %d", cmd, num_args); + this->cmd_data(cmd, sequence + index, num_args); + index += num_args; + } } +} - this->power_on(); +void EPaperBase::dump_config() { + LOG_DISPLAY("", "E-Paper SPI", this); + ESP_LOGCONFIG(TAG, " Model: %s", this->name_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); } } // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/epaper_spi.h b/esphome/components/epaper_spi/epaper_spi.h index f6b2d41c65..4745ec7339 100644 --- a/esphome/components/epaper_spi/epaper_spi.h +++ b/esphome/components/epaper_spi/epaper_spi.h @@ -8,36 +8,48 @@ #include namespace esphome::epaper_spi { +using namespace display; enum class EPaperState : uint8_t { - IDLE, - UPDATE, - RESET, - INITIALISE, - TRANSFER_DATA, - POWER_ON, - REFRESH_SCREEN, - POWER_OFF, - DEEP_SLEEP, + IDLE, // not doing anything + UPDATE, // update the buffer + RESET, // drive reset low (active) + RESET_END, // drive reset high (inactive) + + SHOULD_WAIT, // states higher than this should wait for the display to be not busy + INITIALISE, // send the init sequence + TRANSFER_DATA, // transfer data to the display + POWER_ON, // power on the display + REFRESH_SCREEN, // send refresh command + POWER_OFF, // power off the display + DEEP_SLEEP, // deep sleep the display }; -static const uint8_t MAX_TRANSFER_TIME = 10; // Transfer in 10ms blocks to allow the loop to run +static constexpr uint8_t MAX_TRANSFER_TIME = 10; // Transfer in 10ms blocks to allow the loop to run +static constexpr uint8_t DELAY_FLAG = 0xFF; -class EPaperBase : public display::DisplayBuffer, +class EPaperBase : public DisplayBuffer, public spi::SPIDevice { public: - EPaperBase(const uint8_t *init_sequence, const size_t init_sequence_length) - : init_sequence_length_(init_sequence_length), init_sequence_(init_sequence) {} + EPaperBase(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence, + size_t init_sequence_length, DisplayType display_type = DISPLAY_TYPE_BINARY) + : name_(name), + width_(width), + height_(height), + init_sequence_(init_sequence), + init_sequence_length_(init_sequence_length), + display_type_(display_type) {} void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } float get_setup_priority() const override; void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; } void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; } + void dump_config() override; void command(uint8_t value); void data(uint8_t value); - void cmd_data(const uint8_t *data); + void cmd_data(uint8_t command, const uint8_t *ptr, size_t length); void update() override; void loop() override; @@ -46,48 +58,84 @@ class EPaperBase : public display::DisplayBuffer, void on_safe_shutdown() override; + DisplayType get_display_type() override { return this->display_type_; }; + protected: - bool is_idle_(); - void setup_pins_(); - virtual void reset(); + int get_height_internal() override { return this->height_; }; + int get_width_internal() override { return this->width_; }; + void process_state_(); + + const char *epaper_state_to_string_(); + bool is_idle_() const; + void setup_pins_() const; + bool reset_() const; void initialise_(); + void wait_for_idle_(bool should_wait); bool init_buffer_(size_t buffer_length); virtual int get_width_controller() { return this->get_width_internal(); }; - virtual void deep_sleep() = 0; + + /** + * Methods that must be implemented by concrete classes to control the display + */ /** * Send data to the device via SPI - * @return true if done, false if should be called next loop + * @return true if done, false if it should be called next loop */ virtual bool transfer_data() = 0; + /** + * Refresh the screen after data transfer + */ virtual void refresh_screen() = 0; + /** + * Power the display on + */ virtual void power_on() = 0; + /** + * Power the display off + */ virtual void power_off() = 0; - virtual uint32_t get_buffer_length() = 0; + + /** + * Place the display into deep sleep + */ + virtual void deep_sleep() = 0; + + void set_state_(EPaperState state, uint16_t delay = 0); void start_command_(); void end_command_(); void start_data_(); void end_data_(); - const size_t init_sequence_length_{0}; + // properties initialised in the constructor + const char *name_; + uint16_t width_; + uint16_t height_; + const uint8_t *init_sequence_; + size_t init_sequence_length_; + DisplayType display_type_; - size_t current_data_index_{0}; + size_t buffer_length_{}; + size_t current_data_index_{0}; // used by data transfer to track progress uint32_t reset_duration_{200}; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + uint32_t transfer_start_time_{}; uint32_t waiting_for_idle_last_print_{0}; + uint32_t waiting_for_idle_start_{0}; +#endif - GPIOPin *dc_pin_; - GPIOPin *busy_pin_{nullptr}; - GPIOPin *reset_pin_{nullptr}; - - const uint8_t *init_sequence_{nullptr}; + GPIOPin *dc_pin_{}; + GPIOPin *busy_pin_{}; + GPIOPin *reset_pin_{}; bool waiting_for_idle_{false}; + uint32_t delay_until_{0}; split_buffer::SplitBuffer buffer_; - std::queue state_queue_{{EPaperState::IDLE}}; + EPaperState state_{EPaperState::IDLE}; }; } // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.cpp b/esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.cpp deleted file mode 100644 index f6273b392f..0000000000 --- a/esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "epaper_spi_model_7p3in_spectra_e6.h" - -namespace esphome::epaper_spi { - -static constexpr const char *const TAG = "epaper_spi.7.3in-spectra-e6"; - -void EPaper7p3InSpectraE6::power_on() { - ESP_LOGI(TAG, "Power on"); - this->command(0x04); - this->waiting_for_idle_ = true; -} - -void EPaper7p3InSpectraE6::power_off() { - ESP_LOGI(TAG, "Power off"); - this->command(0x02); - this->data(0x00); - this->waiting_for_idle_ = true; -} - -void EPaper7p3InSpectraE6::refresh_screen() { - ESP_LOGI(TAG, "Refresh"); - this->command(0x12); - this->data(0x00); - this->waiting_for_idle_ = true; -} - -void EPaper7p3InSpectraE6::deep_sleep() { - ESP_LOGI(TAG, "Deep sleep"); - this->command(0x07); - this->data(0xA5); -} - -void EPaper7p3InSpectraE6::dump_config() { - LOG_DISPLAY("", "E-Paper SPI", this); - ESP_LOGCONFIG(TAG, " Model: 7.3in Spectra E6"); - LOG_PIN(" Reset Pin: ", this->reset_pin_); - LOG_PIN(" DC Pin: ", this->dc_pin_); - LOG_PIN(" Busy Pin: ", this->busy_pin_); - LOG_UPDATE_INTERVAL(this); -} - -} // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.h b/esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.h deleted file mode 100644 index 6e850085ac..0000000000 --- a/esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "epaper_spi_spectra_e6.h" - -namespace esphome::epaper_spi { - -class EPaper7p3InSpectraE6 : public EPaperSpectraE6 { - static constexpr const uint16_t WIDTH = 800; - static constexpr const uint16_t HEIGHT = 480; - // clang-format off - - // Command, data length, data - static constexpr uint8_t INIT_SEQUENCE[] = { - 0xAA, 6, 0x49, 0x55, 0x20, 0x08, 0x09, 0x18, - 0x01, 1, 0x3F, - 0x00, 2, 0x5F, 0x69, - 0x03, 4, 0x00, 0x54, 0x00, 0x44, - 0x05, 4, 0x40, 0x1F, 0x1F, 0x2C, - 0x06, 4, 0x6F, 0x1F, 0x17, 0x49, - 0x08, 4, 0x6F, 0x1F, 0x1F, 0x22, - 0x30, 1, 0x03, - 0x50, 1, 0x3F, - 0x60, 2, 0x02, 0x00, - 0x61, 4, WIDTH / 256, WIDTH % 256, HEIGHT / 256, HEIGHT % 256, - 0x84, 1, 0x01, - 0xE3, 1, 0x2F, - }; - // clang-format on - - public: - EPaper7p3InSpectraE6() : EPaperSpectraE6(INIT_SEQUENCE, sizeof(INIT_SEQUENCE)) {} - - void dump_config() override; - - protected: - int get_width_internal() override { return WIDTH; }; - int get_height_internal() override { return HEIGHT; }; - - void refresh_screen() override; - void power_on() override; - void power_off() override; - void deep_sleep() override; -}; - -} // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp b/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp index dccc691252..8e4cbdde2a 100644 --- a/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp +++ b/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp @@ -1,135 +1,166 @@ #include "epaper_spi_spectra_e6.h" +#include + #include "esphome/core/log.h" namespace esphome::epaper_spi { - static constexpr const char *const TAG = "epaper_spi.6c"; +static constexpr size_t MAX_TRANSFER_SIZE = 128; +static constexpr unsigned char GRAY_THRESHOLD = 50; -static inline uint8_t color_to_hex(Color color) { - if (color.red > 127) { - if (color.green > 170) { - if (color.blue > 127) { - return 0x1; // White - } else { - return 0x2; // Yellow - } - } else { - return 0x3; // Red (or Magenta) - } - } else { - if (color.green > 127) { - if (color.blue > 127) { - return 0x5; // Cyan -> Blue - } else { - return 0x6; // Green - } - } else { - if (color.blue > 127) { - return 0x5; // Blue - } else { - return 0x0; // Black - } +enum E6Color { + BLACK, + WHITE, + YELLOW, + RED, + SKIP_1, + BLUE, + GREEN, + CYAN, + SKIP_2, +}; + +static uint8_t color_to_hex(Color color) { + // --- Step 1: Check for Grayscale (Black or White) --- + // We define "grayscale" as a color where the min and max components + // are close to each other. + unsigned char max_rgb = std::max({color.r, color.g, color.b}); + unsigned char min_rgb = std::min({color.r, color.g, color.b}); + + if ((max_rgb - min_rgb) < GRAY_THRESHOLD) { + // It's a shade of gray. Map to BLACK or WHITE. + // We split the luminance at the halfway point (382 = (255*3)/2) + if ((static_cast(color.r) + color.g + color.b) > 382) { + return WHITE; } + return BLACK; } + // --- Step 2: Check for Primary/Secondary Colors --- + // If it's not gray, it's a color. We check which components are + // "on" (over 128) vs "off". This divides the RGB cube into 8 corners. + bool r_on = (color.r > 128); + bool g_on = (color.g > 128); + bool b_on = (color.b > 128); + + if (r_on && g_on && !b_on) { + return YELLOW; + } + if (r_on && !g_on && !b_on) { + return RED; + } + if (!r_on && g_on && !b_on) { + return GREEN; + } + if (!r_on && !g_on && b_on) { + return BLUE; + } + // Handle "impure" colors (Cyan, Magenta) + if (!r_on && g_on && b_on) { + // Cyan (G+B) -> Closest is Green or Blue. Pick Green. + return GREEN; + } + if (r_on && !g_on) { + // Magenta (R+B) -> Closest is Red or Blue. Pick Red. + return RED; + } + // Handle the remaining corners (White-ish, Black-ish) + if (r_on) { + // All high (but not gray) -> White + return WHITE; + } + // !r_on && !g_on && !b_on + // All low (but not gray) -> Black + return BLACK; +} + +void EPaperSpectraE6::power_on() { + ESP_LOGD(TAG, "Power on"); + this->command(0x04); +} + +void EPaperSpectraE6::power_off() { + ESP_LOGD(TAG, "Power off"); + this->command(0x02); + this->data(0x00); +} + +void EPaperSpectraE6::refresh_screen() { + ESP_LOGD(TAG, "Refresh"); + this->command(0x12); + this->data(0x00); +} + +void EPaperSpectraE6::deep_sleep() { + ESP_LOGD(TAG, "Deep sleep"); + this->command(0x07); + this->data(0xA5); } void EPaperSpectraE6::fill(Color color) { - uint8_t pixel_color; - if (color.is_on()) { - pixel_color = color_to_hex(color); - } else { - pixel_color = 0x1; - } + auto pixel_color = color_to_hex(color); - // We store 8 bitset<3> in 3 bytes - // | byte 1 | byte 2 | byte 3 | - // |aaabbbaa|abbbaaab|bbaaabbb| - uint8_t byte_1 = pixel_color << 5 | pixel_color << 2 | pixel_color >> 1; - uint8_t byte_2 = pixel_color << 7 | pixel_color << 4 | pixel_color << 1 | pixel_color >> 2; - uint8_t byte_3 = pixel_color << 6 | pixel_color << 3 | pixel_color << 0; - - const size_t buffer_length = this->get_buffer_length(); - for (size_t i = 0; i < buffer_length; i += 3) { - this->buffer_[i + 0] = byte_1; - this->buffer_[i + 1] = byte_2; - this->buffer_[i + 2] = byte_3; - } + // We store 2 pixels per byte + this->buffer_.fill(pixel_color + (pixel_color << 4)); } -uint32_t EPaperSpectraE6::get_buffer_length() { - // 6 colors buffer, 1 pixel = 3 bits, we will store 8 pixels in 24 bits = 3 bytes - return this->get_width_controller() * this->get_height_internal() / 8u * 3u; +void EPaperSpectraE6::clear() { + // clear buffer to white, just like real paper. + this->fill(COLOR_ON); } void HOT EPaperSpectraE6::draw_absolute_pixel_internal(int x, int y, Color color) { - if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) + if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0) return; - uint8_t pixel_bits = color_to_hex(color); + auto pixel_bits = color_to_hex(color); uint32_t pixel_position = x + y * this->get_width_controller(); - uint32_t first_bit_position = pixel_position * 3; - uint32_t byte_position = first_bit_position / 8u; - uint32_t byte_subposition = first_bit_position % 8u; - - if (byte_subposition <= 5) { - this->buffer_[byte_position] = (this->buffer_[byte_position] & (0xFF ^ (0b111 << (5 - byte_subposition)))) | - (pixel_bits << (5 - byte_subposition)); + uint32_t byte_position = pixel_position / 2; + auto original = this->buffer_[byte_position]; + if ((pixel_position & 1) != 0) { + this->buffer_[byte_position] = (original & 0xF0) | pixel_bits; } else { - this->buffer_[byte_position] = (this->buffer_[byte_position] & (0xFF ^ (0b111 >> (byte_subposition - 5)))) | - (pixel_bits >> (byte_subposition - 5)); - - this->buffer_[byte_position + 1] = - (this->buffer_[byte_position + 1] & (0xFF ^ (0xFF & (0b111 << (13 - byte_subposition))))) | - (pixel_bits << (13 - byte_subposition)); + this->buffer_[byte_position] = (original & 0x0F) | (pixel_bits << 4); } } bool HOT EPaperSpectraE6::transfer_data() { const uint32_t start_time = App.get_loop_component_start_time(); + const size_t buffer_length = this->buffer_length_; if (this->current_data_index_ == 0) { - ESP_LOGV(TAG, "Sending data"); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + this->transfer_start_time_ = millis(); +#endif + ESP_LOGV(TAG, "Start sending data at %ums", (unsigned) millis()); this->command(0x10); } - uint8_t bytes_to_send[4]{0}; - const size_t buffer_length = this->get_buffer_length(); - for (size_t i = this->current_data_index_; i < buffer_length; i += 3) { - const uint32_t triplet = encode_uint24(this->buffer_[i + 0], this->buffer_[i + 1], this->buffer_[i + 2]); - // 8 pixels are stored in 3 bytes - // |aaabbbaa|abbbaaab|bbaaabbb| - // | byte 1 | byte 2 | byte 3 | - bytes_to_send[0] = ((triplet >> 17) & 0b01110000) | ((triplet >> 18) & 0b00000111); - bytes_to_send[1] = ((triplet >> 11) & 0b01110000) | ((triplet >> 12) & 0b00000111); - bytes_to_send[2] = ((triplet >> 5) & 0b01110000) | ((triplet >> 6) & 0b00000111); - bytes_to_send[3] = ((triplet << 1) & 0b01110000) | ((triplet << 0) & 0b00000111); + size_t buf_idx = 0; + uint8_t bytes_to_send[MAX_TRANSFER_SIZE]; + while (this->current_data_index_ != buffer_length) { + bytes_to_send[buf_idx++] = this->buffer_[this->current_data_index_++]; - this->start_data_(); - this->write_array(bytes_to_send, sizeof(bytes_to_send)); - this->end_data_(); + if (buf_idx == sizeof bytes_to_send) { + this->start_data_(); + this->write_array(bytes_to_send, buf_idx); + this->end_data_(); + ESP_LOGV(TAG, "Wrote %d bytes at %ums", buf_idx, (unsigned) millis()); + buf_idx = 0; - if (millis() - start_time > MAX_TRANSFER_TIME) { - // Let the main loop run and come back next loop - this->current_data_index_ = i + 3; - return false; + if (millis() - start_time > MAX_TRANSFER_TIME) { + // Let the main loop run and come back next loop + return false; + } } } // Finished the entire dataset + if (buf_idx != 0) { + this->start_data_(); + this->write_array(bytes_to_send, buf_idx); + this->end_data_(); + } this->current_data_index_ = 0; + ESP_LOGV(TAG, "Sent data in %" PRIu32 " ms", millis() - this->transfer_start_time_); return true; } - -void EPaperSpectraE6::reset() { - if (this->reset_pin_ != nullptr) { - this->disable_loop(); - this->reset_pin_->digital_write(true); - this->set_timeout(20, [this] { - this->reset_pin_->digital_write(false); - delay(2); - this->reset_pin_->digital_write(true); - this->set_timeout(20, [this] { this->enable_loop(); }); - }); - } -} - } // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/epaper_spi_spectra_e6.h b/esphome/components/epaper_spi/epaper_spi_spectra_e6.h index 9f0652f79d..48356ad74b 100644 --- a/esphome/components/epaper_spi/epaper_spi_spectra_e6.h +++ b/esphome/components/epaper_spi/epaper_spi_spectra_e6.h @@ -6,18 +6,23 @@ namespace esphome::epaper_spi { class EPaperSpectraE6 : public EPaperBase { public: - EPaperSpectraE6(const uint8_t *init_sequence, const size_t init_sequence_length) - : EPaperBase(init_sequence, init_sequence_length) {} + EPaperSpectraE6(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence, + size_t init_sequence_length) + : EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_COLOR) { + this->buffer_length_ = width * height / 2; // 2 pixels per byte + } - display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } void fill(Color color) override; + void clear() override; protected: + void refresh_screen() override; + void power_on() override; + void power_off() override; + void deep_sleep() override; void draw_absolute_pixel_internal(int x, int y, Color color) override; - uint32_t get_buffer_length() override; bool transfer_data() override; - void reset() override; }; } // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/models/__init__.py b/esphome/components/epaper_spi/models/__init__.py new file mode 100644 index 0000000000..019eb31d18 --- /dev/null +++ b/esphome/components/epaper_spi/models/__init__.py @@ -0,0 +1,65 @@ +from typing import Any, Self + +import esphome.config_validation as cv +from esphome.const import CONF_DIMENSIONS, CONF_HEIGHT, CONF_WIDTH + + +class EpaperModel: + models: dict[str, Self] = {} + + def __init__( + self, + name: str, + class_name: str, + initsequence=None, + **defaults, + ): + name = name.upper() + self.name = name + self.class_name = class_name + self.initsequence = initsequence + self.defaults = defaults + EpaperModel.models[name] = self + + def get_default(self, key, fallback: Any = False) -> Any: + return self.defaults.get(key, fallback) + + def get_init_sequence(self, config: dict): + return self.initsequence + + def option(self, name, fallback=cv.UNDEFINED) -> cv.Optional | cv.Required: + if fallback is None and self.get_default(name, None) is None: + return cv.Required(name) + return cv.Optional(name, default=self.get_default(name, fallback)) + + def get_dimensions(self, config) -> tuple[int, int]: + if CONF_DIMENSIONS in config: + # Explicit dimensions, just use as is + dimensions = config[CONF_DIMENSIONS] + if isinstance(dimensions, dict): + width = dimensions[CONF_WIDTH] + height = dimensions[CONF_HEIGHT] + else: + (width, height) = dimensions + + else: + # Default dimensions, use model defaults + width = self.get_default(CONF_WIDTH) + height = self.get_default(CONF_HEIGHT) + return width, height + + def extend(self, name, **kwargs) -> "EpaperModel": + """ + Extend the current model with additional parameters or a modified init sequence. + Parameters supplied here will override the defaults of the current model. + if the initsequence is not provided, the current model's initsequence will be used. + If add_init_sequence is provided, it will be appended to the current initsequence. + :param name: + :param kwargs: + :return: + """ + initsequence = list(kwargs.pop("initsequence", self.initsequence) or ()) + initsequence.extend(kwargs.pop("add_init_sequence", ())) + defaults = self.defaults.copy() + defaults.update(kwargs) + return self.__class__(name, initsequence=tuple(initsequence), **defaults) diff --git a/esphome/components/epaper_spi/models/spectra_e6.py b/esphome/components/epaper_spi/models/spectra_e6.py new file mode 100644 index 0000000000..9f0b673d69 --- /dev/null +++ b/esphome/components/epaper_spi/models/spectra_e6.py @@ -0,0 +1,51 @@ +from typing import Any + +from . import EpaperModel + + +class SpectraE6(EpaperModel): + def __init__(self, name, class_name="EPaperSpectraE6", **kwargs): + super().__init__(name, class_name, **kwargs) + + # fmt: off + def get_init_sequence(self, config: dict): + width, height = self.get_dimensions(config) + return ( + (0xAA, 0x49, 0x55, 0x20, 0x08, 0x09, 0x18,), + (0x01, 0x3F,), + (0x00, 0x5F, 0x69,), + (0x03, 0x00, 0x54, 0x00, 0x44,), + (0x05, 0x40, 0x1F, 0x1F, 0x2C,), + (0x06, 0x6F, 0x1F, 0x17, 0x49,), + (0x08, 0x6F, 0x1F, 0x1F, 0x22,), + (0x30, 0x03,), + (0x50, 0x3F,), + (0x60, 0x02, 0x00,), + (0x61, width // 256, width % 256, height // 256, height % 256,), + (0x84, 0x01,), + (0xE3, 0x2F,), + ) + + def get_default(self, key, fallback: Any = False) -> Any: + return self.defaults.get(key, fallback) + + +spectra_e6 = SpectraE6("spectra-e6") + +spectra_e6.extend( + "Seeed-reTerminal-E1002", + width=800, + height=480, + data_rate="20MHz", + cs_pin=10, + dc_pin=11, + reset_pin=12, + busy_pin={ + "number": 13, + "inverted": True, + "mode": { + "input": True, + "pullup": True, + }, + }, +) diff --git a/esphome/components/mipi/__init__.py b/esphome/components/mipi/__init__.py index 93d1750cd6..4dbc81caa2 100644 --- a/esphome/components/mipi/__init__.py +++ b/esphome/components/mipi/__init__.py @@ -218,6 +218,21 @@ def map_sequence(value): return tuple(value) +def flatten_sequence(sequence: tuple | list): + """ + Flatten an init sequence into a single list of bytes. + :param sequence: The list of tuples + :return: a list of bytes + """ + return sum( + tuple( + (x[1], 0xFF) if x[0] == DELAY_FLAG else (x[0], len(x) - 1) + x[1:] + for x in sequence + ), + (), + ) + + def delay(ms): return DELAY_FLAG, ms @@ -456,13 +471,7 @@ class DriverChip: # Flatten the sequence into a list of bytes, with the length of each command # or the delay flag inserted where needed - return sum( - tuple( - (x[1], 0xFF) if x[0] == DELAY_FLAG else (x[0], len(x) - 1) + x[1:] - for x in sequence - ), - (), - ), madctl + return flatten_sequence(sequence), madctl def requires_buffer(config) -> bool: diff --git a/esphome/components/split_buffer/split_buffer.cpp b/esphome/components/split_buffer/split_buffer.cpp index a710670a5d..526a19c71c 100644 --- a/esphome/components/split_buffer/split_buffer.cpp +++ b/esphome/components/split_buffer/split_buffer.cpp @@ -4,7 +4,6 @@ #include "esphome/core/log.h" namespace esphome::split_buffer { - static constexpr const char *const TAG = "split_buffer"; SplitBuffer::~SplitBuffer() { this->free(); } @@ -102,32 +101,44 @@ void SplitBuffer::free() { this->total_length_ = 0; } -uint8_t &SplitBuffer::operator[](size_t index) { +const uint8_t &SplitBuffer::operator[](size_t index) const { if (index >= this->total_length_) { ESP_LOGE(TAG, "Out of bounds - %zu >= %zu", index, this->total_length_); - // Return reference to a static dummy byte to avoid crash + // Return reference to a static dummy byte since we can't throw exceptions. + // the byte is non-const since it will also be used by the non-const [] overload. static uint8_t dummy = 0; return dummy; } - size_t buffer_index = index / this->buffer_size_; - size_t offset_in_buffer = index - this->buffer_size_ * buffer_index; + const auto buffer_index = index / this->buffer_size_; + const auto offset_in_buffer = index % this->buffer_size_; return this->buffers_[buffer_index][offset_in_buffer]; } -const uint8_t &SplitBuffer::operator[](size_t index) const { - if (index >= this->total_length_) { - ESP_LOGE(TAG, "Out of bounds - %zu >= %zu", index, this->total_length_); - // Return reference to a static dummy byte to avoid crash - static const uint8_t DUMMY = 0; - return DUMMY; +// non-const version of operator[] for write access +uint8_t &SplitBuffer::operator[](size_t index) { + // avoid code duplication. These casts are safe since we know the object is not const. + return const_cast(static_cast(this)->operator[](index)); +} + +/** + * Fill the entire buffer with a single byte value + * @param value Fill value + */ +void SplitBuffer::fill(uint8_t value) const { + if (this->buffer_count_ == 0) + return; + // clear all the full sized buffers + size_t i = 0; + for (; i != this->buffer_count_ - 1; i++) { + memset(this->buffers_[i], value, this->buffer_size_); } - - size_t buffer_index = index / this->buffer_size_; - size_t offset_in_buffer = index - this->buffer_size_ * buffer_index; - - return this->buffers_[buffer_index][offset_in_buffer]; + // clear the last, potentially short, buffer. + // `i` is guaranteed to equal the last index since the loop terminates at that value. + // where all buffers are the same size, the modulus must return the size, not 0. + auto size_last = ((this->total_length_ - 1) % this->buffer_size_) + 1; + memset(this->buffers_[i], value, size_last); } } // namespace esphome::split_buffer diff --git a/esphome/components/split_buffer/split_buffer.h b/esphome/components/split_buffer/split_buffer.h index c3490f3d6e..b615ddce74 100644 --- a/esphome/components/split_buffer/split_buffer.h +++ b/esphome/components/split_buffer/split_buffer.h @@ -4,7 +4,13 @@ #include namespace esphome::split_buffer { - +/** + * A SplitBuffer allocates a large memory buffer potentially as multiple smaller buffers + * to facilitate allocation of large buffers on devices with fragmented memory spaces. + * Each sub-buffer is the same size, except for the last one which may be smaller. + * Standard array indexing using `[]` is possible on the buffer, but, since the buffer may not be contiguous in memory, + * there is no easy way to access the buffer as a single array, i.e. no `.data()` access like a vector. + */ class SplitBuffer { public: SplitBuffer() = default; @@ -19,13 +25,13 @@ class SplitBuffer { // Access operators uint8_t &operator[](size_t index); const uint8_t &operator[](size_t index) const; + void fill(uint8_t value) const; // Get the total length size_t size() const { return this->total_length_; } // Get buffer information size_t get_buffer_count() const { return this->buffer_count_; } - size_t get_buffer_size() const { return this->buffer_size_; } // Check if successfully initialized bool is_valid() const { return this->buffers_ != nullptr && this->buffer_count_ > 0; } diff --git a/tests/components/epaper_spi/test.esp32-s3-idf.yaml b/tests/components/epaper_spi/test.esp32-s3-idf.yaml index 34aefb82b4..cff1f51897 100644 --- a/tests/components/epaper_spi/test.esp32-s3-idf.yaml +++ b/tests/components/epaper_spi/test.esp32-s3-idf.yaml @@ -4,7 +4,10 @@ packages: display: - platform: epaper_spi spi_id: spi_bus - model: 7.3in-spectra-e6 + model: spectra-e6 + dimensions: + width: 800 + height: 480 cs_pin: GPIO5 dc_pin: GPIO17 reset_pin: GPIO16 @@ -13,3 +16,6 @@ display: update_interval: 60s lambda: |- it.circle(64, 64, 50, Color::BLACK); + + - platform: epaper_spi + model: seeed-reterminal-e1002 From 57f2e32b00026c372124c5124e3c67eb6765ca1f Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 27 Oct 2025 17:09:45 -0500 Subject: [PATCH 0063/1145] [uart] Fix order of initialization calls (#11510) --- .../uart/uart_component_esp_idf.cpp | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index cffa3308eb..73813d2d5b 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -99,10 +99,26 @@ void IDFUARTComponent::setup() { } void IDFUARTComponent::load_settings(bool dump_config) { - uart_config_t uart_config = this->get_config_(); - esp_err_t err = uart_param_config(this->uart_num_, &uart_config); + esp_err_t err; + + if (uart_is_driver_installed(this->uart_num_)) { + err = uart_driver_delete(this->uart_num_); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + } + err = uart_driver_install(this->uart_num_, // UART number + this->rx_buffer_size_, // RX ring buffer size + 0, // TX ring buffer size. If zero, driver will not use a TX buffer and TX function will + // block task until all data has been sent out + 20, // event queue size/depth + &this->uart_event_queue_, // event queue + 0 // Flags used to allocate the interrupt + ); if (err != ESP_OK) { - ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err)); + ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } @@ -119,10 +135,12 @@ void IDFUARTComponent::load_settings(bool dump_config) { int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1; uint32_t invert = 0; - if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) + if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) { invert |= UART_SIGNAL_TXD_INV; - if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) + } + if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) { invert |= UART_SIGNAL_RXD_INV; + } err = uart_set_line_inverse(this->uart_num_, invert); if (err != ESP_OK) { @@ -138,26 +156,6 @@ void IDFUARTComponent::load_settings(bool dump_config) { return; } - if (uart_is_driver_installed(this->uart_num_)) { - uart_driver_delete(this->uart_num_); - if (err != ESP_OK) { - ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err)); - this->mark_failed(); - return; - } - } - err = uart_driver_install(this->uart_num_, /* UART RX ring buffer size. */ this->rx_buffer_size_, - /* UART TX ring buffer size. If set to zero, driver will not use TX buffer, TX function will - block task until all data have been sent out.*/ - 0, - /* UART event queue size/depth. */ 20, &(this->uart_event_queue_), - /* Flags used to allocate the interrupt. */ 0); - if (err != ESP_OK) { - ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err)); - this->mark_failed(); - return; - } - err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err)); @@ -173,24 +171,32 @@ void IDFUARTComponent::load_settings(bool dump_config) { } auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART; - err = uart_set_mode(this->uart_num_, mode); + err = uart_set_mode(this->uart_num_, mode); // per docs, must be called only after uart_driver_install() if (err != ESP_OK) { ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } + uart_config_t uart_config = this->get_config_(); + err = uart_param_config(this->uart_num_, &uart_config); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + if (dump_config) { - ESP_LOGCONFIG(TAG, "UART %u was reloaded.", this->uart_num_); + ESP_LOGCONFIG(TAG, "Reloaded UART %u", this->uart_num_); this->dump_config(); } } void IDFUARTComponent::dump_config() { ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_); - LOG_PIN(" TX Pin: ", tx_pin_); - LOG_PIN(" RX Pin: ", rx_pin_); - LOG_PIN(" Flow Control Pin: ", flow_control_pin_); + LOG_PIN(" TX Pin: ", this->tx_pin_); + LOG_PIN(" RX Pin: ", this->rx_pin_); + LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_); if (this->rx_pin_ != nullptr) { ESP_LOGCONFIG(TAG, " RX Buffer Size: %u\n" From 641dd24b214537486337eb82c574278b4428201d Mon Sep 17 00:00:00 2001 From: Anton Sergunov Date: Wed, 29 Oct 2025 07:33:16 +0600 Subject: [PATCH 0064/1145] Fix the LiberTiny bug with UART pin setup (#11518) --- .../uart/uart_component_libretiny.cpp | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/esphome/components/uart/uart_component_libretiny.cpp b/esphome/components/uart/uart_component_libretiny.cpp index 9c065fe5df..8d1d28fce4 100644 --- a/esphome/components/uart/uart_component_libretiny.cpp +++ b/esphome/components/uart/uart_component_libretiny.cpp @@ -46,40 +46,58 @@ uint16_t LibreTinyUARTComponent::get_config() { } void LibreTinyUARTComponent::setup() { - if (this->rx_pin_) { - this->rx_pin_->setup(); - } - if (this->tx_pin_ && this->rx_pin_ != this->tx_pin_) { - this->tx_pin_->setup(); - } - int8_t tx_pin = tx_pin_ == nullptr ? -1 : tx_pin_->get_pin(); int8_t rx_pin = rx_pin_ == nullptr ? -1 : rx_pin_->get_pin(); bool tx_inverted = tx_pin_ != nullptr && tx_pin_->is_inverted(); bool rx_inverted = rx_pin_ != nullptr && rx_pin_->is_inverted(); + auto shouldFallbackToSoftwareSerial = [&]() -> bool { + auto hasFlags = [](InternalGPIOPin *pin, const gpio::Flags mask) -> bool { + return pin && pin->get_flags() & mask != gpio::Flags::FLAG_NONE; + }; + if (hasFlags(this->tx_pin_, gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN) || + hasFlags(this->rx_pin_, gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN)) { +#if LT_ARD_HAS_SOFTSERIAL + ESP_LOGI(TAG, "Pins has flags set. Using Software Serial"); + return true; +#else + ESP_LOGW(TAG, "Pin flags are set but not supported for hardware serial. Ignoring"); +#endif + } + return false; + }; + if (false) return; #if LT_HW_UART0 - else if ((tx_pin == -1 || tx_pin == PIN_SERIAL0_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL0_RX)) { + else if ((tx_pin == -1 || tx_pin == PIN_SERIAL0_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL0_RX) && + !shouldFallbackToSoftwareSerial()) { this->serial_ = &Serial0; this->hardware_idx_ = 0; } #endif #if LT_HW_UART1 - else if ((tx_pin == -1 || tx_pin == PIN_SERIAL1_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL1_RX)) { + else if ((tx_pin == -1 || tx_pin == PIN_SERIAL1_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL1_RX) && + !shouldFallbackToSoftwareSerial()) { this->serial_ = &Serial1; this->hardware_idx_ = 1; } #endif #if LT_HW_UART2 - else if ((tx_pin == -1 || tx_pin == PIN_SERIAL2_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL2_RX)) { + else if ((tx_pin == -1 || tx_pin == PIN_SERIAL2_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL2_RX) && + !shouldFallbackToSoftwareSerial()) { this->serial_ = &Serial2; this->hardware_idx_ = 2; } #endif else { #if LT_ARD_HAS_SOFTSERIAL + if (this->rx_pin_) { + this->rx_pin_->setup(); + } + if (this->tx_pin_ && this->rx_pin_ != this->tx_pin_) { + this->tx_pin_->setup(); + } this->serial_ = new SoftwareSerial(rx_pin, tx_pin, rx_inverted || tx_inverted); #else this->serial_ = &Serial; From db395a662de2f33f64aec4f91c54cc6af717e177 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:37:14 +1000 Subject: [PATCH 0065/1145] [mipi_rgb] Fix rotation with custom model (#11585) --- esphome/components/mipi/__init__.py | 12 ++++++++ esphome/components/mipi_rgb/display.py | 38 ++++++++++++++------------ esphome/components/mipi_spi/display.py | 15 +--------- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/esphome/components/mipi/__init__.py b/esphome/components/mipi/__init__.py index 4dff1af62a..93d1750cd6 100644 --- a/esphome/components/mipi/__init__.py +++ b/esphome/components/mipi/__init__.py @@ -384,6 +384,18 @@ class DriverChip: transform[CONF_TRANSFORM] = True return transform + def swap_xy_schema(self): + uses_swap = self.get_default(CONF_SWAP_XY, None) != cv.UNDEFINED + + def validator(value): + if value: + raise cv.Invalid("Axis swapping not supported by this model") + return cv.boolean(value) + + if uses_swap: + return {cv.Required(CONF_SWAP_XY): cv.boolean} + return {cv.Optional(CONF_SWAP_XY, default=False): validator} + def add_madctl(self, sequence: list, config: dict): # Add the MADCTL command to the sequence based on the configuration. use_flip = config.get(CONF_USE_AXIS_FLIPS) diff --git a/esphome/components/mipi_rgb/display.py b/esphome/components/mipi_rgb/display.py index 3001d33980..9d6b1fa729 100644 --- a/esphome/components/mipi_rgb/display.py +++ b/esphome/components/mipi_rgb/display.py @@ -46,6 +46,7 @@ from esphome.const import ( CONF_DATA_RATE, CONF_DC_PIN, CONF_DIMENSIONS, + CONF_DISABLED, CONF_ENABLE_PIN, CONF_GREEN, CONF_HSYNC_PIN, @@ -117,16 +118,16 @@ def data_pin_set(length): def model_schema(config): model = MODELS[config[CONF_MODEL].upper()] - if transforms := model.transforms: - transform = cv.Schema({cv.Required(x): cv.boolean for x in transforms}) - for x in (CONF_SWAP_XY, CONF_MIRROR_X, CONF_MIRROR_Y): - if x not in transforms: - transform = transform.extend( - {cv.Optional(x): cv.invalid(f"{x} not supported by this model")} - ) - else: - transform = cv.invalid("This model does not support transforms") - + transform = cv.Any( + cv.Schema( + { + cv.Required(CONF_MIRROR_X): cv.boolean, + cv.Required(CONF_MIRROR_Y): cv.boolean, + **model.swap_xy_schema(), + } + ), + cv.one_of(CONF_DISABLED, lower=True), + ) # RPI model does not use an init sequence, indicates with empty list if model.initsequence is None: # Custom model requires an init sequence @@ -135,12 +136,16 @@ def model_schema(config): else: iseqconf = cv.Optional(CONF_INIT_SEQUENCE) uses_spi = CONF_INIT_SEQUENCE in config or len(model.initsequence) != 0 - swap_xy = config.get(CONF_TRANSFORM, {}).get(CONF_SWAP_XY, False) - - # Dimensions are optional if the model has a default width and the swap_xy transform is not overridden - cv_dimensions = ( - cv.Optional if model.get_default(CONF_WIDTH) and not swap_xy else cv.Required + # Dimensions are optional if the model has a default width and the x-y transform is not overridden + transform_config = config.get(CONF_TRANSFORM, {}) + is_swapped = ( + isinstance(transform_config, dict) + and transform_config.get(CONF_SWAP_XY, False) is True ) + cv_dimensions = ( + cv.Optional if model.get_default(CONF_WIDTH) and not is_swapped else cv.Required + ) + pixel_modes = (PIXEL_MODE_16BIT, PIXEL_MODE_18BIT, "16", "18") schema = display.FULL_DISPLAY_SCHEMA.extend( { @@ -157,7 +162,7 @@ def model_schema(config): model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.one_of( *pixel_modes, lower=True ), - model.option(CONF_TRANSFORM, cv.UNDEFINED): transform, + cv.Optional(CONF_TRANSFORM): transform, cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True), model.option(CONF_INVERT_COLORS, False): cv.boolean, model.option(CONF_USE_AXIS_FLIPS, True): cv.boolean, @@ -270,7 +275,6 @@ async def to_code(config): cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH])) cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED])) cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY])) - index = 0 dpins = [] if CONF_RED in config[CONF_DATA_PINS]: red_pins = config[CONF_DATA_PINS][CONF_RED] diff --git a/esphome/components/mipi_spi/display.py b/esphome/components/mipi_spi/display.py index 891c8b42ff..50ea826eab 100644 --- a/esphome/components/mipi_spi/display.py +++ b/esphome/components/mipi_spi/display.py @@ -131,19 +131,6 @@ def denominator(config): ) from StopIteration -def swap_xy_schema(model): - uses_swap = model.get_default(CONF_SWAP_XY, None) != cv.UNDEFINED - - def validator(value): - if value: - raise cv.Invalid("Axis swapping not supported by this model") - return cv.boolean(value) - - if uses_swap: - return {cv.Required(CONF_SWAP_XY): cv.boolean} - return {cv.Optional(CONF_SWAP_XY, default=False): validator} - - def model_schema(config): model = MODELS[config[CONF_MODEL]] bus_mode = config[CONF_BUS_MODE] @@ -152,7 +139,7 @@ def model_schema(config): { cv.Required(CONF_MIRROR_X): cv.boolean, cv.Required(CONF_MIRROR_Y): cv.boolean, - **swap_xy_schema(model), + **model.swap_xy_schema(), } ), cv.one_of(CONF_DISABLED, lower=True), From fecc8399a557a577a40d98a110d95d0a2a2c01b3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Oct 2025 01:07:48 -0500 Subject: [PATCH 0066/1145] [lvgl] Fix nested lambdas in automations unable to access parameters (#11583) Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/lvgl/defines.py | 22 +++++++++++++-- esphome/components/lvgl/lv_validation.py | 21 ++++++++++++--- esphome/components/lvgl/lvcode.py | 13 ++++++--- esphome/components/lvgl/sensor/__init__.py | 3 +-- tests/components/lvgl/common.yaml | 31 ++++++++++++++++++++++ tests/components/lvgl/lvgl-package.yaml | 23 ++++++++++++++++ 6 files changed, 103 insertions(+), 10 deletions(-) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index baee403b57..d2b0977e89 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -5,6 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i """ import logging +from typing import TYPE_CHECKING, Any from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_ITEMS @@ -12,6 +13,7 @@ from esphome.core import ID, Lambda from esphome.cpp_generator import LambdaExpression, MockObj from esphome.cpp_types import uint32 from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor +from esphome.types import Expression, SafeExpType from .helpers import requires_component @@ -42,7 +44,13 @@ def static_cast(type, value): def call_lambda(lamb: LambdaExpression): expr = lamb.content.strip() if expr.startswith("return") and expr.endswith(";"): - return expr[6:][:-1].strip() + return expr[6:-1].strip() + # If lambda has parameters, call it with those parameter names + # Parameter names come from hardcoded component code (like "x", "it", "event") + # not from user input, so they're safe to use directly + if lamb.parameters and lamb.parameters.parameters: + param_names = ", ".join(str(param.id) for param in lamb.parameters.parameters) + return f"{lamb}({param_names})" return f"{lamb}()" @@ -65,10 +73,20 @@ class LValidator: return cv.returning_lambda(value) return self.validator(value) - async def process(self, value, args=()): + async def process( + self, value: Any, args: list[tuple[SafeExpType, str]] | None = None + ) -> Expression: if value is None: return None if isinstance(value, Lambda): + # Local import to avoid circular import + from .lvcode import CodeContext, LambdaContext + + if TYPE_CHECKING: + # CodeContext does not have get_automation_parameters + # so we need to assert the type here + assert isinstance(CodeContext.code_context, LambdaContext) + args = args or CodeContext.code_context.get_automation_parameters() return cg.RawExpression( call_lambda( await cg.process_lambda(value, args, return_type=self.rtype) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index d345ac70f3..6f95a32a18 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -1,3 +1,5 @@ +from typing import TYPE_CHECKING, Any + import esphome.codegen as cg from esphome.components import image from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw @@ -17,6 +19,7 @@ from esphome.cpp_generator import MockObj from esphome.cpp_types import ESPTime, int32, uint32 from esphome.helpers import cpp_string_escape from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor +from esphome.types import Expression, SafeExpType from . import types as ty from .defines import ( @@ -388,11 +391,23 @@ class TextValidator(LValidator): return value return super().__call__(value) - async def process(self, value, args=()): + async def process( + self, value: Any, args: list[tuple[SafeExpType, str]] | None = None + ) -> Expression: + # Local import to avoid circular import at module level + + from .lvcode import CodeContext, LambdaContext + + if TYPE_CHECKING: + # CodeContext does not have get_automation_parameters + # so we need to assert the type here + assert isinstance(CodeContext.code_context, LambdaContext) + args = args or CodeContext.code_context.get_automation_parameters() + if isinstance(value, dict): if format_str := value.get(CONF_FORMAT): - args = [str(x) for x in value[CONF_ARGS]] - arg_expr = cg.RawExpression(",".join(args)) + str_args = [str(x) for x in value[CONF_ARGS]] + arg_expr = cg.RawExpression(",".join(str_args)) format_str = cpp_string_escape(format_str) return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()") if time_format := value.get(CONF_TIME_FORMAT): diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index 7a5c35f896..ea38845c07 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -164,6 +164,9 @@ class LambdaContext(CodeContext): code_text.append(text) return code_text + def get_automation_parameters(self) -> list[tuple[SafeExpType, str]]: + return self.parameters + async def __aenter__(self): await super().__aenter__() add_line_marks(self.where) @@ -178,9 +181,8 @@ class LvContext(LambdaContext): added_lambda_count = 0 - def __init__(self, args=None): - self.args = args or LVGL_COMP_ARG - super().__init__(parameters=self.args) + def __init__(self): + super().__init__(parameters=LVGL_COMP_ARG) async def __aexit__(self, exc_type, exc_val, exc_tb): await super().__aexit__(exc_type, exc_val, exc_tb) @@ -189,6 +191,11 @@ class LvContext(LambdaContext): cg.add(expression) return expression + def get_automation_parameters(self) -> list[tuple[SafeExpType, str]]: + # When generating automations, we don't want the `lv_component` parameter to be passed + # to the lambda. + return [] + def __call__(self, *args): return self.add(*args) diff --git a/esphome/components/lvgl/sensor/__init__.py b/esphome/components/lvgl/sensor/__init__.py index 03b2638ed0..167af9c6e1 100644 --- a/esphome/components/lvgl/sensor/__init__.py +++ b/esphome/components/lvgl/sensor/__init__.py @@ -5,7 +5,6 @@ from ..defines import CONF_WIDGET from ..lvcode import ( API_EVENT, EVENT_ARG, - LVGL_COMP_ARG, UPDATE_EVENT, LambdaContext, LvContext, @@ -30,7 +29,7 @@ async def to_code(config): await wait_for_widgets() async with LambdaContext(EVENT_ARG) as lamb: lv_add(sensor.publish_state(widget.get_value())) - async with LvContext(LVGL_COMP_ARG): + async with LvContext(): lv_add( lvgl_static.add_event_cb( widget.obj, diff --git a/tests/components/lvgl/common.yaml b/tests/components/lvgl/common.yaml index d9b7013a1e..c70dd7568d 100644 --- a/tests/components/lvgl/common.yaml +++ b/tests/components/lvgl/common.yaml @@ -52,6 +52,19 @@ number: widget: spinbox_id id: lvgl_spinbox_number name: LVGL Spinbox Number + - platform: template + id: test_brightness + name: "Test Brightness" + min_value: 0 + max_value: 255 + step: 1 + optimistic: true + # Test lambda in automation accessing x parameter directly + # This is a real-world pattern from user configs + on_value: + - lambda: !lambda |- + // Direct use of x parameter in automation + ESP_LOGD("test", "Brightness: %.0f", x); light: - platform: lvgl @@ -110,3 +123,21 @@ text: platform: lvgl widget: hello_label mode: text + +text_sensor: + - platform: template + id: test_text_sensor + name: "Test Text Sensor" + # Test nested lambdas in LVGL actions can access automation parameters + on_value: + - lvgl.label.update: + id: hello_label + text: !lambda return x.c_str(); + - lvgl.label.update: + id: hello_label + text: !lambda |- + // Test complex lambda with conditionals accessing x parameter + if (x == "*") { + return "WILDCARD"; + } + return x.c_str(); diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 582531e943..14241a1669 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -257,7 +257,30 @@ lvgl: text: "Hello shiny day" text_color: 0xFFFFFF align: bottom_mid + - label: + id: setup_lambda_label + # Test lambda in widget property during setup (LvContext) + # Should NOT receive lv_component parameter + text: !lambda |- + char buf[32]; + snprintf(buf, sizeof(buf), "Setup: %d", 42); + return std::string(buf); + align: top_mid text_font: space16 + - label: + id: chip_info_label + # Test complex setup lambda (real-world pattern) + # Should NOT receive lv_component parameter + text: !lambda |- + // Test conditional compilation and string formatting + char buf[64]; + #ifdef USE_ESP_IDF + snprintf(buf, sizeof(buf), "IDF: v%d.%d", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR); + #else + snprintf(buf, sizeof(buf), "Arduino"); + #endif + return std::string(buf); + align: top_left - obj: align: center arc_opa: COVER From 51745d1d5ef080493666e592b6abe777cf2a9338 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sat, 1 Nov 2025 10:08:28 +1000 Subject: [PATCH 0067/1145] [image] Catch and report svg load errors (#11619) --- esphome/components/image/__init__.py | 31 +++++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index f880b5f736..bf25a7cd92 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -671,18 +671,33 @@ async def write_image(config, all_frames=False): resize = config.get(CONF_RESIZE) if is_svg_file(path): # Local import so use of non-SVG files needn't require cairosvg installed + from pyexpat import ExpatError + from xml.etree.ElementTree import ParseError + from cairosvg import svg2png + from cairosvg.helpers import PointError if not resize: resize = (None, None) - with open(path, "rb") as file: - image = svg2png( - file_obj=file, - output_width=resize[0], - output_height=resize[1], - ) - image = Image.open(io.BytesIO(image)) - width, height = image.size + try: + with open(path, "rb") as file: + image = svg2png( + file_obj=file, + output_width=resize[0], + output_height=resize[1], + ) + image = Image.open(io.BytesIO(image)) + width, height = image.size + except ( + ValueError, + ParseError, + IndexError, + ExpatError, + AttributeError, + TypeError, + PointError, + ) as e: + raise core.EsphomeError(f"Could not load SVG image {path}: {e}") from e else: image = Image.open(path) width, height = image.size From 2f5f1da16f0e37f1e774e61c8acdb3d0d4d71730 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:37:07 +1000 Subject: [PATCH 0068/1145] [lvgl] Fix event for binary sensor (#11636) --- esphome/components/lvgl/binary_sensor/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/lvgl/binary_sensor/__init__.py b/esphome/components/lvgl/binary_sensor/__init__.py index ffbdc977b2..f9df7d23fa 100644 --- a/esphome/components/lvgl/binary_sensor/__init__.py +++ b/esphome/components/lvgl/binary_sensor/__init__.py @@ -31,7 +31,7 @@ async def to_code(config): lvgl_static.add_event_cb( widget.obj, await pressed_ctx.get_lambda(), - LV_EVENT.PRESSING, + LV_EVENT.PRESSED, LV_EVENT.RELEASED, ) ) From 0f6fd9130430d976613f60fc933bc69a5162d7aa Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:45:42 +1000 Subject: [PATCH 0069/1145] [sdl] Fix keymappings (#11635) --- esphome/components/sdl/binary_sensor.py | 485 ++++++++++++------------ tests/components/sdl/common.yaml | 6 +- 2 files changed, 253 insertions(+), 238 deletions(-) diff --git a/esphome/components/sdl/binary_sensor.py b/esphome/components/sdl/binary_sensor.py index 3ea6c2d218..e19a488800 100644 --- a/esphome/components/sdl/binary_sensor.py +++ b/esphome/components/sdl/binary_sensor.py @@ -12,241 +12,256 @@ CODEOWNERS = ["@bdm310"] STATE_ARG = "state" -SDL_KEYMAP = { - "SDLK_UNKNOWN": 0, - "SDLK_FIRST": 0, - "SDLK_BACKSPACE": 8, - "SDLK_TAB": 9, - "SDLK_CLEAR": 12, - "SDLK_RETURN": 13, - "SDLK_PAUSE": 19, - "SDLK_ESCAPE": 27, - "SDLK_SPACE": 32, - "SDLK_EXCLAIM": 33, - "SDLK_QUOTEDBL": 34, - "SDLK_HASH": 35, - "SDLK_DOLLAR": 36, - "SDLK_AMPERSAND": 38, - "SDLK_QUOTE": 39, - "SDLK_LEFTPAREN": 40, - "SDLK_RIGHTPAREN": 41, - "SDLK_ASTERISK": 42, - "SDLK_PLUS": 43, - "SDLK_COMMA": 44, - "SDLK_MINUS": 45, - "SDLK_PERIOD": 46, - "SDLK_SLASH": 47, - "SDLK_0": 48, - "SDLK_1": 49, - "SDLK_2": 50, - "SDLK_3": 51, - "SDLK_4": 52, - "SDLK_5": 53, - "SDLK_6": 54, - "SDLK_7": 55, - "SDLK_8": 56, - "SDLK_9": 57, - "SDLK_COLON": 58, - "SDLK_SEMICOLON": 59, - "SDLK_LESS": 60, - "SDLK_EQUALS": 61, - "SDLK_GREATER": 62, - "SDLK_QUESTION": 63, - "SDLK_AT": 64, - "SDLK_LEFTBRACKET": 91, - "SDLK_BACKSLASH": 92, - "SDLK_RIGHTBRACKET": 93, - "SDLK_CARET": 94, - "SDLK_UNDERSCORE": 95, - "SDLK_BACKQUOTE": 96, - "SDLK_a": 97, - "SDLK_b": 98, - "SDLK_c": 99, - "SDLK_d": 100, - "SDLK_e": 101, - "SDLK_f": 102, - "SDLK_g": 103, - "SDLK_h": 104, - "SDLK_i": 105, - "SDLK_j": 106, - "SDLK_k": 107, - "SDLK_l": 108, - "SDLK_m": 109, - "SDLK_n": 110, - "SDLK_o": 111, - "SDLK_p": 112, - "SDLK_q": 113, - "SDLK_r": 114, - "SDLK_s": 115, - "SDLK_t": 116, - "SDLK_u": 117, - "SDLK_v": 118, - "SDLK_w": 119, - "SDLK_x": 120, - "SDLK_y": 121, - "SDLK_z": 122, - "SDLK_DELETE": 127, - "SDLK_WORLD_0": 160, - "SDLK_WORLD_1": 161, - "SDLK_WORLD_2": 162, - "SDLK_WORLD_3": 163, - "SDLK_WORLD_4": 164, - "SDLK_WORLD_5": 165, - "SDLK_WORLD_6": 166, - "SDLK_WORLD_7": 167, - "SDLK_WORLD_8": 168, - "SDLK_WORLD_9": 169, - "SDLK_WORLD_10": 170, - "SDLK_WORLD_11": 171, - "SDLK_WORLD_12": 172, - "SDLK_WORLD_13": 173, - "SDLK_WORLD_14": 174, - "SDLK_WORLD_15": 175, - "SDLK_WORLD_16": 176, - "SDLK_WORLD_17": 177, - "SDLK_WORLD_18": 178, - "SDLK_WORLD_19": 179, - "SDLK_WORLD_20": 180, - "SDLK_WORLD_21": 181, - "SDLK_WORLD_22": 182, - "SDLK_WORLD_23": 183, - "SDLK_WORLD_24": 184, - "SDLK_WORLD_25": 185, - "SDLK_WORLD_26": 186, - "SDLK_WORLD_27": 187, - "SDLK_WORLD_28": 188, - "SDLK_WORLD_29": 189, - "SDLK_WORLD_30": 190, - "SDLK_WORLD_31": 191, - "SDLK_WORLD_32": 192, - "SDLK_WORLD_33": 193, - "SDLK_WORLD_34": 194, - "SDLK_WORLD_35": 195, - "SDLK_WORLD_36": 196, - "SDLK_WORLD_37": 197, - "SDLK_WORLD_38": 198, - "SDLK_WORLD_39": 199, - "SDLK_WORLD_40": 200, - "SDLK_WORLD_41": 201, - "SDLK_WORLD_42": 202, - "SDLK_WORLD_43": 203, - "SDLK_WORLD_44": 204, - "SDLK_WORLD_45": 205, - "SDLK_WORLD_46": 206, - "SDLK_WORLD_47": 207, - "SDLK_WORLD_48": 208, - "SDLK_WORLD_49": 209, - "SDLK_WORLD_50": 210, - "SDLK_WORLD_51": 211, - "SDLK_WORLD_52": 212, - "SDLK_WORLD_53": 213, - "SDLK_WORLD_54": 214, - "SDLK_WORLD_55": 215, - "SDLK_WORLD_56": 216, - "SDLK_WORLD_57": 217, - "SDLK_WORLD_58": 218, - "SDLK_WORLD_59": 219, - "SDLK_WORLD_60": 220, - "SDLK_WORLD_61": 221, - "SDLK_WORLD_62": 222, - "SDLK_WORLD_63": 223, - "SDLK_WORLD_64": 224, - "SDLK_WORLD_65": 225, - "SDLK_WORLD_66": 226, - "SDLK_WORLD_67": 227, - "SDLK_WORLD_68": 228, - "SDLK_WORLD_69": 229, - "SDLK_WORLD_70": 230, - "SDLK_WORLD_71": 231, - "SDLK_WORLD_72": 232, - "SDLK_WORLD_73": 233, - "SDLK_WORLD_74": 234, - "SDLK_WORLD_75": 235, - "SDLK_WORLD_76": 236, - "SDLK_WORLD_77": 237, - "SDLK_WORLD_78": 238, - "SDLK_WORLD_79": 239, - "SDLK_WORLD_80": 240, - "SDLK_WORLD_81": 241, - "SDLK_WORLD_82": 242, - "SDLK_WORLD_83": 243, - "SDLK_WORLD_84": 244, - "SDLK_WORLD_85": 245, - "SDLK_WORLD_86": 246, - "SDLK_WORLD_87": 247, - "SDLK_WORLD_88": 248, - "SDLK_WORLD_89": 249, - "SDLK_WORLD_90": 250, - "SDLK_WORLD_91": 251, - "SDLK_WORLD_92": 252, - "SDLK_WORLD_93": 253, - "SDLK_WORLD_94": 254, - "SDLK_WORLD_95": 255, - "SDLK_KP0": 256, - "SDLK_KP1": 257, - "SDLK_KP2": 258, - "SDLK_KP3": 259, - "SDLK_KP4": 260, - "SDLK_KP5": 261, - "SDLK_KP6": 262, - "SDLK_KP7": 263, - "SDLK_KP8": 264, - "SDLK_KP9": 265, - "SDLK_KP_PERIOD": 266, - "SDLK_KP_DIVIDE": 267, - "SDLK_KP_MULTIPLY": 268, - "SDLK_KP_MINUS": 269, - "SDLK_KP_PLUS": 270, - "SDLK_KP_ENTER": 271, - "SDLK_KP_EQUALS": 272, - "SDLK_UP": 273, - "SDLK_DOWN": 274, - "SDLK_RIGHT": 275, - "SDLK_LEFT": 276, - "SDLK_INSERT": 277, - "SDLK_HOME": 278, - "SDLK_END": 279, - "SDLK_PAGEUP": 280, - "SDLK_PAGEDOWN": 281, - "SDLK_F1": 282, - "SDLK_F2": 283, - "SDLK_F3": 284, - "SDLK_F4": 285, - "SDLK_F5": 286, - "SDLK_F6": 287, - "SDLK_F7": 288, - "SDLK_F8": 289, - "SDLK_F9": 290, - "SDLK_F10": 291, - "SDLK_F11": 292, - "SDLK_F12": 293, - "SDLK_F13": 294, - "SDLK_F14": 295, - "SDLK_F15": 296, - "SDLK_NUMLOCK": 300, - "SDLK_CAPSLOCK": 301, - "SDLK_SCROLLOCK": 302, - "SDLK_RSHIFT": 303, - "SDLK_LSHIFT": 304, - "SDLK_RCTRL": 305, - "SDLK_LCTRL": 306, - "SDLK_RALT": 307, - "SDLK_LALT": 308, - "SDLK_RMETA": 309, - "SDLK_LMETA": 310, - "SDLK_LSUPER": 311, - "SDLK_RSUPER": 312, - "SDLK_MODE": 313, - "SDLK_COMPOSE": 314, - "SDLK_HELP": 315, - "SDLK_PRINT": 316, - "SDLK_SYSREQ": 317, - "SDLK_BREAK": 318, - "SDLK_MENU": 319, - "SDLK_POWER": 320, - "SDLK_EURO": 321, - "SDLK_UNDO": 322, -} +SDL_KeyCode = cg.global_ns.enum("SDL_KeyCode") + +SDL_KEYS = ( + "SDLK_UNKNOWN", + "SDLK_RETURN", + "SDLK_ESCAPE", + "SDLK_BACKSPACE", + "SDLK_TAB", + "SDLK_SPACE", + "SDLK_EXCLAIM", + "SDLK_QUOTEDBL", + "SDLK_HASH", + "SDLK_PERCENT", + "SDLK_DOLLAR", + "SDLK_AMPERSAND", + "SDLK_QUOTE", + "SDLK_LEFTPAREN", + "SDLK_RIGHTPAREN", + "SDLK_ASTERISK", + "SDLK_PLUS", + "SDLK_COMMA", + "SDLK_MINUS", + "SDLK_PERIOD", + "SDLK_SLASH", + "SDLK_0", + "SDLK_1", + "SDLK_2", + "SDLK_3", + "SDLK_4", + "SDLK_5", + "SDLK_6", + "SDLK_7", + "SDLK_8", + "SDLK_9", + "SDLK_COLON", + "SDLK_SEMICOLON", + "SDLK_LESS", + "SDLK_EQUALS", + "SDLK_GREATER", + "SDLK_QUESTION", + "SDLK_AT", + "SDLK_LEFTBRACKET", + "SDLK_BACKSLASH", + "SDLK_RIGHTBRACKET", + "SDLK_CARET", + "SDLK_UNDERSCORE", + "SDLK_BACKQUOTE", + "SDLK_a", + "SDLK_b", + "SDLK_c", + "SDLK_d", + "SDLK_e", + "SDLK_f", + "SDLK_g", + "SDLK_h", + "SDLK_i", + "SDLK_j", + "SDLK_k", + "SDLK_l", + "SDLK_m", + "SDLK_n", + "SDLK_o", + "SDLK_p", + "SDLK_q", + "SDLK_r", + "SDLK_s", + "SDLK_t", + "SDLK_u", + "SDLK_v", + "SDLK_w", + "SDLK_x", + "SDLK_y", + "SDLK_z", + "SDLK_CAPSLOCK", + "SDLK_F1", + "SDLK_F2", + "SDLK_F3", + "SDLK_F4", + "SDLK_F5", + "SDLK_F6", + "SDLK_F7", + "SDLK_F8", + "SDLK_F9", + "SDLK_F10", + "SDLK_F11", + "SDLK_F12", + "SDLK_PRINTSCREEN", + "SDLK_SCROLLLOCK", + "SDLK_PAUSE", + "SDLK_INSERT", + "SDLK_HOME", + "SDLK_PAGEUP", + "SDLK_DELETE", + "SDLK_END", + "SDLK_PAGEDOWN", + "SDLK_RIGHT", + "SDLK_LEFT", + "SDLK_DOWN", + "SDLK_UP", + "SDLK_NUMLOCKCLEAR", + "SDLK_KP_DIVIDE", + "SDLK_KP_MULTIPLY", + "SDLK_KP_MINUS", + "SDLK_KP_PLUS", + "SDLK_KP_ENTER", + "SDLK_KP_1", + "SDLK_KP_2", + "SDLK_KP_3", + "SDLK_KP_4", + "SDLK_KP_5", + "SDLK_KP_6", + "SDLK_KP_7", + "SDLK_KP_8", + "SDLK_KP_9", + "SDLK_KP_0", + "SDLK_KP_PERIOD", + "SDLK_APPLICATION", + "SDLK_POWER", + "SDLK_KP_EQUALS", + "SDLK_F13", + "SDLK_F14", + "SDLK_F15", + "SDLK_F16", + "SDLK_F17", + "SDLK_F18", + "SDLK_F19", + "SDLK_F20", + "SDLK_F21", + "SDLK_F22", + "SDLK_F23", + "SDLK_F24", + "SDLK_EXECUTE", + "SDLK_HELP", + "SDLK_MENU", + "SDLK_SELECT", + "SDLK_STOP", + "SDLK_AGAIN", + "SDLK_UNDO", + "SDLK_CUT", + "SDLK_COPY", + "SDLK_PASTE", + "SDLK_FIND", + "SDLK_MUTE", + "SDLK_VOLUMEUP", + "SDLK_VOLUMEDOWN", + "SDLK_KP_COMMA", + "SDLK_KP_EQUALSAS400", + "SDLK_ALTERASE", + "SDLK_SYSREQ", + "SDLK_CANCEL", + "SDLK_CLEAR", + "SDLK_PRIOR", + "SDLK_RETURN2", + "SDLK_SEPARATOR", + "SDLK_OUT", + "SDLK_OPER", + "SDLK_CLEARAGAIN", + "SDLK_CRSEL", + "SDLK_EXSEL", + "SDLK_KP_00", + "SDLK_KP_000", + "SDLK_THOUSANDSSEPARATOR", + "SDLK_DECIMALSEPARATOR", + "SDLK_CURRENCYUNIT", + "SDLK_CURRENCYSUBUNIT", + "SDLK_KP_LEFTPAREN", + "SDLK_KP_RIGHTPAREN", + "SDLK_KP_LEFTBRACE", + "SDLK_KP_RIGHTBRACE", + "SDLK_KP_TAB", + "SDLK_KP_BACKSPACE", + "SDLK_KP_A", + "SDLK_KP_B", + "SDLK_KP_C", + "SDLK_KP_D", + "SDLK_KP_E", + "SDLK_KP_F", + "SDLK_KP_XOR", + "SDLK_KP_POWER", + "SDLK_KP_PERCENT", + "SDLK_KP_LESS", + "SDLK_KP_GREATER", + "SDLK_KP_AMPERSAND", + "SDLK_KP_DBLAMPERSAND", + "SDLK_KP_VERTICALBAR", + "SDLK_KP_DBLVERTICALBAR", + "SDLK_KP_COLON", + "SDLK_KP_HASH", + "SDLK_KP_SPACE", + "SDLK_KP_AT", + "SDLK_KP_EXCLAM", + "SDLK_KP_MEMSTORE", + "SDLK_KP_MEMRECALL", + "SDLK_KP_MEMCLEAR", + "SDLK_KP_MEMADD", + "SDLK_KP_MEMSUBTRACT", + "SDLK_KP_MEMMULTIPLY", + "SDLK_KP_MEMDIVIDE", + "SDLK_KP_PLUSMINUS", + "SDLK_KP_CLEAR", + "SDLK_KP_CLEARENTRY", + "SDLK_KP_BINARY", + "SDLK_KP_OCTAL", + "SDLK_KP_DECIMAL", + "SDLK_KP_HEXADECIMAL", + "SDLK_LCTRL", + "SDLK_LSHIFT", + "SDLK_LALT", + "SDLK_LGUI", + "SDLK_RCTRL", + "SDLK_RSHIFT", + "SDLK_RALT", + "SDLK_RGUI", + "SDLK_MODE", + "SDLK_AUDIONEXT", + "SDLK_AUDIOPREV", + "SDLK_AUDIOSTOP", + "SDLK_AUDIOPLAY", + "SDLK_AUDIOMUTE", + "SDLK_MEDIASELECT", + "SDLK_WWW", + "SDLK_MAIL", + "SDLK_CALCULATOR", + "SDLK_COMPUTER", + "SDLK_AC_SEARCH", + "SDLK_AC_HOME", + "SDLK_AC_BACK", + "SDLK_AC_FORWARD", + "SDLK_AC_STOP", + "SDLK_AC_REFRESH", + "SDLK_AC_BOOKMARKS", + "SDLK_BRIGHTNESSDOWN", + "SDLK_BRIGHTNESSUP", + "SDLK_DISPLAYSWITCH", + "SDLK_KBDILLUMTOGGLE", + "SDLK_KBDILLUMDOWN", + "SDLK_KBDILLUMUP", + "SDLK_EJECT", + "SDLK_SLEEP", + "SDLK_APP1", + "SDLK_APP2", + "SDLK_AUDIOREWIND", + "SDLK_AUDIOFASTFORWARD", + "SDLK_SOFTLEFT", + "SDLK_SOFTRIGHT", + "SDLK_CALL", + "SDLK_ENDCALL", +) + +SDL_KEYMAP = {key: getattr(SDL_KeyCode, key) for key in SDL_KEYS} CONFIG_SCHEMA = ( binary_sensor.binary_sensor_schema(BinarySensor) diff --git a/tests/components/sdl/common.yaml b/tests/components/sdl/common.yaml index 50fa4a5990..52991d595c 100644 --- a/tests/components/sdl/common.yaml +++ b/tests/components/sdl/common.yaml @@ -14,10 +14,10 @@ display: binary_sensor: - platform: sdl id: key_up - key: SDLK_a + key: SDLK_UP - platform: sdl id: key_down - key: SDLK_d + key: SDLK_DOWN - platform: sdl id: key_enter - key: SDLK_s + key: SDLK_RETURN From a3583da17d1a59d53db1917fedb151ca5fae1360 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:25:33 +1300 Subject: [PATCH 0070/1145] Bump version to 2025.10.4 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index f770defaf2..4f72970e24 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.10.3 +PROJECT_NUMBER = 2025.10.4 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 712dc85221..9e8ec487b5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.10.3" +__version__ = "2025.10.4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 99ce989eaedd59fdad9ea28a0468d63d31568d95 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Nov 2025 16:30:35 -0600 Subject: [PATCH 0071/1145] [micro_wake_word] Add wake_loop_threadsafe() for low-latency wake word detection (#11698) --- esphome/components/micro_wake_word/__init__.py | 7 ++++++- esphome/components/micro_wake_word/micro_wake_word.cpp | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/esphome/components/micro_wake_word/__init__.py b/esphome/components/micro_wake_word/__init__.py index 8cd7115368..575fb97799 100644 --- a/esphome/components/micro_wake_word/__init__.py +++ b/esphome/components/micro_wake_word/__init__.py @@ -7,7 +7,7 @@ from urllib.parse import urljoin from esphome import automation, external_files, git from esphome.automation import register_action, register_condition import esphome.codegen as cg -from esphome.components import esp32, microphone +from esphome.components import esp32, microphone, socket import esphome.config_validation as cv from esphome.const import ( CONF_FILE, @@ -32,6 +32,7 @@ _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@kahrendt", "@jesserockz"] DEPENDENCIES = ["microphone"] +AUTO_LOAD = ["socket"] DOMAIN = "micro_wake_word" @@ -443,6 +444,10 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + # Enable wake_loop_threadsafe() for low-latency wake word detection + # The inference task queues detection events that need immediate processing + socket.require_wake_loop_threadsafe() + mic_source = await microphone.microphone_source_to_code(config[CONF_MICROPHONE]) cg.add(var.set_microphone_source(mic_source)) diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp index 6fca48a5bd..a0547b158e 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.cpp +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -2,6 +2,7 @@ #ifdef USE_ESP_IDF +#include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -426,6 +427,12 @@ void MicroWakeWord::process_probabilities_() { if (vad_state.detected) { #endif xQueueSend(this->detection_queue_, &wake_word_state, portMAX_DELAY); + + // Wake main loop immediately to process wake word detection +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + App.wake_loop_threadsafe(); +#endif + model->reset_probabilities(); #ifdef USE_MICRO_WAKE_WORD_VAD } else { From 99d1a9cf6ef89fae36d749d3f640891ad1265471 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 4 Nov 2025 09:23:45 +1000 Subject: [PATCH 0072/1145] [usb_uart] Fixes for transfer queue allocation (#11548) --- esphome/components/usb_host/usb_host.h | 12 ++--- .../components/usb_host/usb_host_client.cpp | 54 +++++++++---------- esphome/components/usb_uart/usb_uart.cpp | 22 +++++--- tests/components/usb_uart/common.yaml | 3 ++ 4 files changed, 51 insertions(+), 40 deletions(-) diff --git a/esphome/components/usb_host/usb_host.h b/esphome/components/usb_host/usb_host.h index 43b24a54a5..31bdde2df8 100644 --- a/esphome/components/usb_host/usb_host.h +++ b/esphome/components/usb_host/usb_host.h @@ -55,7 +55,7 @@ static const uint8_t USB_DIR_IN = 1 << 7; static const uint8_t USB_DIR_OUT = 0; static const size_t SETUP_PACKET_SIZE = 8; -static const size_t MAX_REQUESTS = USB_HOST_MAX_REQUESTS; // maximum number of outstanding requests possible. +static constexpr size_t MAX_REQUESTS = USB_HOST_MAX_REQUESTS; // maximum number of outstanding requests possible. static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be between 1 and 32"); // Select appropriate bitmask type for tracking allocation of TransferRequest slots. @@ -65,6 +65,7 @@ static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be bet // This is tied to the static_assert above, which enforces MAX_REQUESTS is between 1 and 32. // If MAX_REQUESTS is increased above 32, this logic and the static_assert must be updated. using trq_bitmask_t = std::conditional<(MAX_REQUESTS <= 16), uint16_t, uint32_t>::type; +static constexpr trq_bitmask_t ALL_REQUESTS_IN_USE = MAX_REQUESTS == 32 ? ~0 : (1 << MAX_REQUESTS) - 1; static constexpr size_t USB_EVENT_QUEUE_SIZE = 32; // Size of event queue between USB task and main loop static constexpr size_t USB_TASK_STACK_SIZE = 4096; // Stack size for USB task (same as ESP-IDF USB examples) @@ -133,11 +134,11 @@ class USBClient : public Component { float get_setup_priority() const override { return setup_priority::IO; } void on_opened(uint8_t addr); void on_removed(usb_device_handle_t handle); - void control_transfer_callback(const usb_transfer_t *xfer) const; - void transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length); - void transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length); + bool transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length); + bool transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length); void dump_config() override; void release_trq(TransferRequest *trq); + trq_bitmask_t get_trq_in_use() const { return trq_in_use_; } bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback, const std::vector &data = {}); @@ -147,7 +148,6 @@ class USBClient : public Component { EventPool event_pool; protected: - bool register_(); TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe) virtual void disconnect(); virtual void on_connected() {} @@ -158,7 +158,7 @@ class USBClient : public Component { // USB task management static void usb_task_fn(void *arg); - void usb_task_loop(); + [[noreturn]] void usb_task_loop() const; TaskHandle_t usb_task_handle_{nullptr}; diff --git a/esphome/components/usb_host/usb_host_client.cpp b/esphome/components/usb_host/usb_host_client.cpp index 0dda36b9d7..4c09cf8a49 100644 --- a/esphome/components/usb_host/usb_host_client.cpp +++ b/esphome/components/usb_host/usb_host_client.cpp @@ -194,9 +194,9 @@ void USBClient::setup() { } // Pre-allocate USB transfer buffers for all slots at startup // This avoids any dynamic allocation during runtime - for (size_t i = 0; i < MAX_REQUESTS; i++) { - usb_host_transfer_alloc(64, 0, &this->requests_[i].transfer); - this->requests_[i].client = this; // Set once, never changes + for (auto &request : this->requests_) { + usb_host_transfer_alloc(64, 0, &request.transfer); + request.client = this; // Set once, never changes } // Create and start USB task @@ -216,8 +216,7 @@ void USBClient::usb_task_fn(void *arg) { auto *client = static_cast(arg); client->usb_task_loop(); } - -void USBClient::usb_task_loop() { +void USBClient::usb_task_loop() const { while (true) { usb_host_client_handle_events(this->handle_, portMAX_DELAY); } @@ -340,22 +339,23 @@ static void control_callback(const usb_transfer_t *xfer) { // This multi-threaded access is intentional for performance - USB task can // immediately restart transfers without waiting for main loop scheduling. TransferRequest *USBClient::get_trq_() { - trq_bitmask_t mask = this->trq_in_use_.load(std::memory_order_relaxed); + trq_bitmask_t mask = this->trq_in_use_.load(std::memory_order_acquire); // Find first available slot (bit = 0) and try to claim it atomically // We use a while loop to allow retrying the same slot after CAS failure - size_t i = 0; - while (i != MAX_REQUESTS) { - if (mask & (static_cast(1) << i)) { - // Slot is in use, move to next slot - i++; - continue; + for (;;) { + if (mask == ALL_REQUESTS_IN_USE) { + ESP_LOGE(TAG, "All %zu transfer slots in use", MAX_REQUESTS); + return nullptr; } + // find the least significant zero bit + trq_bitmask_t lsb = ~mask & (mask + 1); // Slot i appears available, try to claim it atomically - trq_bitmask_t desired = mask | (static_cast(1) << i); // Set bit i to mark as in-use + trq_bitmask_t desired = mask | lsb; - if (this->trq_in_use_.compare_exchange_weak(mask, desired, std::memory_order_acquire, std::memory_order_relaxed)) { + if (this->trq_in_use_.compare_exchange_weak(mask, desired, std::memory_order::acquire)) { + auto i = __builtin_ctz(lsb); // count trailing zeroes // Successfully claimed slot i - prepare the TransferRequest auto *trq = &this->requests_[i]; trq->transfer->context = trq; @@ -364,13 +364,9 @@ TransferRequest *USBClient::get_trq_() { } // CAS failed - another thread modified the bitmask // mask was already updated by compare_exchange_weak with the current value - // No need to reload - the CAS already did that for us - i = 0; } - - ESP_LOGE(TAG, "All %zu transfer slots in use", MAX_REQUESTS); - return nullptr; } + void USBClient::disconnect() { this->on_disconnected(); auto err = usb_host_device_close(this->handle_, this->device_handle_); @@ -452,11 +448,11 @@ static void transfer_callback(usb_transfer_t *xfer) { * * @throws None. */ -void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) { +bool USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) { auto *trq = this->get_trq_(); if (trq == nullptr) { ESP_LOGE(TAG, "Too many requests queued"); - return; + return false; } trq->callback = callback; trq->transfer->callback = transfer_callback; @@ -466,7 +462,9 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err); this->release_trq(trq); + return false; } + return true; } /** @@ -482,11 +480,11 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u * * @throws None. */ -void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) { +bool USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) { auto *trq = this->get_trq_(); if (trq == nullptr) { ESP_LOGE(TAG, "Too many requests queued"); - return; + return false; } trq->callback = callback; trq->transfer->callback = transfer_callback; @@ -497,7 +495,9 @@ void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err); this->release_trq(trq); + return false; } + return true; } void USBClient::dump_config() { ESP_LOGCONFIG(TAG, @@ -511,7 +511,7 @@ void USBClient::dump_config() { // - Main loop: When transfer submission fails // // THREAD SAFETY: Lock-free using atomic AND to clear bit -// Thread-safe atomic operation allows multi-threaded deallocation +// Thread-safe atomic operation allows multithreaded deallocation void USBClient::release_trq(TransferRequest *trq) { if (trq == nullptr) return; @@ -523,10 +523,10 @@ void USBClient::release_trq(TransferRequest *trq) { return; } - // Atomically clear bit i to mark slot as available + // Atomically clear the bit to mark slot as available // fetch_and with inverted bitmask clears the bit atomically - trq_bitmask_t bit = static_cast(1) << index; - this->trq_in_use_.fetch_and(static_cast(~bit), std::memory_order_release); + trq_bitmask_t mask = ~(static_cast(1) << index); + this->trq_in_use_.fetch_and(mask, std::memory_order_release); } } // namespace usb_host diff --git a/esphome/components/usb_uart/usb_uart.cpp b/esphome/components/usb_uart/usb_uart.cpp index 29003e071e..c24fffb11d 100644 --- a/esphome/components/usb_uart/usb_uart.cpp +++ b/esphome/components/usb_uart/usb_uart.cpp @@ -214,7 +214,7 @@ void USBUartComponent::dump_config() { } } void USBUartComponent::start_input(USBUartChannel *channel) { - if (!channel->initialised_.load() || channel->input_started_.load()) + if (!channel->initialised_.load()) return; // THREAD CONTEXT: Called from both USB task and main loop threads // - USB task: Immediate restart after successful transfer for continuous data flow @@ -226,12 +226,18 @@ void USBUartComponent::start_input(USBUartChannel *channel) { // // The underlying transfer_in() uses lock-free atomic allocation from the // TransferRequest pool, making this multi-threaded access safe + + // if already started, don't restart. A spurious failure in compare_exchange_weak + // is not a problem, as it will be retried on the next read_array() + auto started = false; + if (!channel->input_started_.compare_exchange_weak(started, true)) + return; const auto *ep = channel->cdc_dev_.in_ep; // CALLBACK CONTEXT: This lambda is executed in USB task via transfer_callback auto callback = [this, channel](const usb_host::TransferStatus &status) { ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code); if (!status.success) { - ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code)); + ESP_LOGE(TAG, "Input transfer failed, status=%s", esp_err_to_name(status.error_code)); // On failure, don't restart - let next read_array() trigger it channel->input_started_.store(false); return; @@ -263,8 +269,9 @@ void USBUartComponent::start_input(USBUartChannel *channel) { channel->input_started_.store(false); this->start_input(channel); }; - channel->input_started_.store(true); - this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize); + if (!this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize)) { + channel->input_started_.store(false); + } } void USBUartComponent::start_output(USBUartChannel *channel) { @@ -357,11 +364,12 @@ void USBUartTypeCdcAcm::on_disconnected() { usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress); } usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number); - channel->initialised_.store(false); - channel->input_started_.store(false); - channel->output_started_.store(false); + // Reset the input and output started flags to their initial state to avoid the possibility of spurious restarts + channel->input_started_.store(true); + channel->output_started_.store(true); channel->input_buffer_.clear(); channel->output_buffer_.clear(); + channel->initialised_.store(false); } USBClient::on_disconnected(); } diff --git a/tests/components/usb_uart/common.yaml b/tests/components/usb_uart/common.yaml index 46ad6291f9..474c3f5c8d 100644 --- a/tests/components/usb_uart/common.yaml +++ b/tests/components/usb_uart/common.yaml @@ -1,3 +1,6 @@ +usb_host: + max_transfer_requests: 32 + usb_uart: - id: uart_0 type: cdc_acm From 266e4ae91fb2414528163109e708af6246b57952 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 3 Nov 2025 17:30:37 -0600 Subject: [PATCH 0073/1145] [helpers] Add `get_mac_address_into_buffer()` (#11700) --- esphome/core/helpers.cpp | 6 ++++++ esphome/core/helpers.h | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index fb8b220b2f..568acb9f1b 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -643,6 +643,12 @@ std::string get_mac_address_pretty() { return format_mac_address_pretty(mac); } +void get_mac_address_into_buffer(std::span buf) { + uint8_t mac[6]; + get_mac_address_raw(mac); + format_mac_addr_lower_no_sep(mac, buf.data()); +} + #ifndef USE_ESP32 bool has_custom_mac_address() { return false; } #endif diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index cf21ddc16d..91ddc70afa 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -1027,6 +1028,10 @@ std::string get_mac_address(); /// Get the device MAC address as a string, in colon-separated uppercase hex notation. std::string get_mac_address_pretty(); +/// Get the device MAC address into the given buffer, in lowercase hex notation. +/// Assumes buffer length is 13 (12 digits for hexadecimal representation followed by null terminator). +void get_mac_address_into_buffer(std::span buf); + #ifdef USE_ESP32 /// Set the MAC address to use from the provided byte array (6 bytes). void set_mac_address(uint8_t *mac); From 59326f137ed7c6a932bb63f38a10fe4fa7baaf9b Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 3 Nov 2025 18:29:30 -0600 Subject: [PATCH 0074/1145] [tinyusb] New component (#11678) --- CODEOWNERS | 1 + esphome/components/tinyusb/__init__.py | 60 ++++++++++++++++ .../components/tinyusb/tinyusb_component.cpp | 44 ++++++++++++ .../components/tinyusb/tinyusb_component.h | 72 +++++++++++++++++++ esphome/idf_component.yml | 4 ++ tests/components/tinyusb/common.yaml | 8 +++ .../components/tinyusb/test.esp32-p4-idf.yaml | 1 + .../components/tinyusb/test.esp32-s2-idf.yaml | 1 + .../components/tinyusb/test.esp32-s3-idf.yaml | 1 + 9 files changed, 192 insertions(+) create mode 100644 esphome/components/tinyusb/__init__.py create mode 100644 esphome/components/tinyusb/tinyusb_component.cpp create mode 100644 esphome/components/tinyusb/tinyusb_component.h create mode 100644 tests/components/tinyusb/common.yaml create mode 100644 tests/components/tinyusb/test.esp32-p4-idf.yaml create mode 100644 tests/components/tinyusb/test.esp32-s2-idf.yaml create mode 100644 tests/components/tinyusb/test.esp32-s3-idf.yaml diff --git a/CODEOWNERS b/CODEOWNERS index fee0e98f46..b8a4df6a85 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -480,6 +480,7 @@ esphome/components/template/fan/* @ssieb esphome/components/text/* @mauritskorse esphome/components/thermostat/* @kbx81 esphome/components/time/* @esphome/core +esphome/components/tinyusb/* @kbx81 esphome/components/tlc5947/* @rnauber esphome/components/tlc5971/* @IJIJI esphome/components/tm1621/* @Philippe12 diff --git a/esphome/components/tinyusb/__init__.py b/esphome/components/tinyusb/__init__.py new file mode 100644 index 0000000000..72afc18387 --- /dev/null +++ b/esphome/components/tinyusb/__init__.py @@ -0,0 +1,60 @@ +import esphome.codegen as cg +from esphome.components import esp32 +from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option +from esphome.components.esp32.const import ( + VARIANT_ESP32P4, + VARIANT_ESP32S2, + VARIANT_ESP32S3, +) +import esphome.config_validation as cv +from esphome.const import CONF_ID + +CODEOWNERS = ["@kbx81"] +CONFLICTS_WITH = ["usb_host"] + +CONF_USB_LANG_ID = "usb_lang_id" +CONF_USB_MANUFACTURER_STR = "usb_manufacturer_str" +CONF_USB_PRODUCT_ID = "usb_product_id" +CONF_USB_PRODUCT_STR = "usb_product_str" +CONF_USB_SERIAL_STR = "usb_serial_str" +CONF_USB_VENDOR_ID = "usb_vendor_id" + +tinyusb_ns = cg.esphome_ns.namespace("tinyusb") +TinyUSB = tinyusb_ns.class_("TinyUSB", cg.Component) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(TinyUSB), + cv.Optional(CONF_USB_PRODUCT_ID, default=0x4001): cv.uint16_t, + cv.Optional(CONF_USB_VENDOR_ID, default=0x303A): cv.uint16_t, + cv.Optional(CONF_USB_LANG_ID, default=0x0409): cv.uint16_t, + cv.Optional(CONF_USB_MANUFACTURER_STR, default="ESPHome"): cv.string, + cv.Optional(CONF_USB_PRODUCT_STR, default="ESPHome"): cv.string, + cv.Optional(CONF_USB_SERIAL_STR, default=""): cv.string, + } + ).extend(cv.COMPONENT_SCHEMA), + esp32.only_on_variant( + supported=[VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3], + ), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + # Set USB device descriptor properties + cg.add(var.set_usb_desc_product_id(config[CONF_USB_PRODUCT_ID])) + cg.add(var.set_usb_desc_vendor_id(config[CONF_USB_VENDOR_ID])) + cg.add(var.set_usb_desc_lang_id(config[CONF_USB_LANG_ID])) + cg.add(var.set_usb_desc_manufacturer(config[CONF_USB_MANUFACTURER_STR])) + cg.add(var.set_usb_desc_product(config[CONF_USB_PRODUCT_STR])) + if config[CONF_USB_SERIAL_STR]: + cg.add(var.set_usb_desc_serial(config[CONF_USB_SERIAL_STR])) + + add_idf_component(name="espressif/esp_tinyusb", ref="1.7.6~1") + + add_idf_sdkconfig_option("CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID", False) + add_idf_sdkconfig_option("CONFIG_TINYUSB_DESC_USE_DEFAULT_PID", False) + add_idf_sdkconfig_option("CONFIG_TINYUSB_DESC_BCD_DEVICE", 0x0100) diff --git a/esphome/components/tinyusb/tinyusb_component.cpp b/esphome/components/tinyusb/tinyusb_component.cpp new file mode 100644 index 0000000000..a2057c90ce --- /dev/null +++ b/esphome/components/tinyusb/tinyusb_component.cpp @@ -0,0 +1,44 @@ +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#include "tinyusb_component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome::tinyusb { + +static const char *TAG = "tinyusb"; + +void TinyUSB::setup() { + // Use the device's MAC address as its serial number if no serial number is defined + if (this->string_descriptor_[SERIAL_NUMBER] == nullptr) { + static char mac_addr_buf[13]; + get_mac_address_into_buffer(mac_addr_buf); + this->string_descriptor_[SERIAL_NUMBER] = mac_addr_buf; + } + + this->tusb_cfg_ = { + .descriptor = &this->usb_descriptor_, + .string_descriptor = this->string_descriptor_, + .string_descriptor_count = SIZE, + .external_phy = false, + }; + + esp_err_t result = tinyusb_driver_install(&this->tusb_cfg_); + if (result != ESP_OK) { + this->mark_failed(); + } +} + +void TinyUSB::dump_config() { + ESP_LOGCONFIG(TAG, + "TinyUSB:\n" + " Product ID: 0x%04X\n" + " Vendor ID: 0x%04X\n" + " Manufacturer: '%s'\n" + " Product: '%s'\n" + " Serial: '%s'\n", + this->usb_descriptor_.idProduct, this->usb_descriptor_.idVendor, this->string_descriptor_[MANUFACTURER], + this->string_descriptor_[PRODUCT], this->string_descriptor_[SERIAL_NUMBER]); +} + +} // namespace esphome::tinyusb +#endif diff --git a/esphome/components/tinyusb/tinyusb_component.h b/esphome/components/tinyusb/tinyusb_component.h new file mode 100644 index 0000000000..56c286f455 --- /dev/null +++ b/esphome/components/tinyusb/tinyusb_component.h @@ -0,0 +1,72 @@ +#pragma once +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#include "esphome/core/component.h" + +#include "tinyusb.h" +#include "tusb.h" + +namespace esphome::tinyusb { + +enum USBDStringDescriptor : uint8_t { + LANGUAGE_ID = 0, + MANUFACTURER = 1, + PRODUCT = 2, + SERIAL_NUMBER = 3, + INTERFACE = 4, + TERMINATOR = 5, + SIZE = 6, +}; + +static const char *DEFAULT_USB_STR = "ESPHome"; + +class TinyUSB : public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void set_usb_desc_product_id(uint16_t product_id) { this->usb_descriptor_.idProduct = product_id; } + void set_usb_desc_vendor_id(uint16_t vendor_id) { this->usb_descriptor_.idVendor = vendor_id; } + void set_usb_desc_lang_id(uint16_t lang_id) { + this->usb_desc_lang_id_[0] = lang_id & 0xFF; + this->usb_desc_lang_id_[1] = lang_id >> 8; + } + void set_usb_desc_manufacturer(const char *usb_desc_manufacturer) { + this->string_descriptor_[MANUFACTURER] = usb_desc_manufacturer; + } + void set_usb_desc_product(const char *usb_desc_product) { this->string_descriptor_[PRODUCT] = usb_desc_product; } + void set_usb_desc_serial(const char *usb_desc_serial) { this->string_descriptor_[SERIAL_NUMBER] = usb_desc_serial; } + + protected: + char usb_desc_lang_id_[2] = {0x09, 0x04}; // defaults to english + + const char *string_descriptor_[SIZE] = { + this->usb_desc_lang_id_, // 0: supported language is English (0x0409) + DEFAULT_USB_STR, // 1: Manufacturer + DEFAULT_USB_STR, // 2: Product + nullptr, // 3: Serial Number + nullptr, // 4: Interface + nullptr, // 5: Terminator + }; + + tinyusb_config_t tusb_cfg_{}; + tusb_desc_device_t usb_descriptor_{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = 0x303A, + .idProduct = 0x4001, + .bcdDevice = CONFIG_TINYUSB_DESC_BCD_DEVICE, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigurations = 1, + }; +}; + +} // namespace esphome::tinyusb +#endif diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 31112caf0a..fcb3a4f438 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -23,3 +23,7 @@ dependencies: version: "2.0.0" rules: - if: "target in [esp32, esp32p4]" + espressif/esp_tinyusb: + version: "1.7.6~1" + rules: + - if: "target in [esp32s2, esp32s3, esp32p4]" diff --git a/tests/components/tinyusb/common.yaml b/tests/components/tinyusb/common.yaml new file mode 100644 index 0000000000..cb3f48836a --- /dev/null +++ b/tests/components/tinyusb/common.yaml @@ -0,0 +1,8 @@ +tinyusb: + id: tinyusb_test + usb_lang_id: 0x0123 + usb_manufacturer_str: ESPHomeTestManufacturer + usb_product_id: 0x1234 + usb_product_str: ESPHomeTestProduct + usb_serial_str: ESPHomeTestSerialNumber + usb_vendor_id: 0x2345 diff --git a/tests/components/tinyusb/test.esp32-p4-idf.yaml b/tests/components/tinyusb/test.esp32-p4-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/tinyusb/test.esp32-p4-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tinyusb/test.esp32-s2-idf.yaml b/tests/components/tinyusb/test.esp32-s2-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/tinyusb/test.esp32-s2-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tinyusb/test.esp32-s3-idf.yaml b/tests/components/tinyusb/test.esp32-s3-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/tinyusb/test.esp32-s3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml From 6220084fe684f357c99f4261832f09c8cf7a76c9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Nov 2025 19:23:04 -0600 Subject: [PATCH 0075/1145] [ci] Fix memory impact analysis to filter incompatible platform components (#11706) --- script/determine-jobs.py | 33 ++++++++- tests/script/test_determine_jobs.py | 108 ++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/script/determine-jobs.py b/script/determine-jobs.py index 4a0edebb0d..6f908b7150 100755 --- a/script/determine-jobs.py +++ b/script/determine-jobs.py @@ -94,6 +94,22 @@ class Platform(StrEnum): MEMORY_IMPACT_FALLBACK_COMPONENT = "api" # Representative component for core changes MEMORY_IMPACT_FALLBACK_PLATFORM = Platform.ESP32_IDF # Most representative platform +# Platform-specific components that can only be built on their respective platforms +# These components contain platform-specific code and cannot be cross-compiled +# Regular components (wifi, logger, api, etc.) are cross-platform and not listed here +PLATFORM_SPECIFIC_COMPONENTS = frozenset( + { + "esp32", # ESP32 platform implementation + "esp8266", # ESP8266 platform implementation + "rp2040", # Raspberry Pi Pico / RP2040 platform implementation + "bk72xx", # Beken BK72xx platform implementation (uses LibreTiny) + "rtl87xx", # Realtek RTL87xx platform implementation (uses LibreTiny) + "ln882x", # Winner Micro LN882x platform implementation (uses LibreTiny) + "host", # Host platform (for testing on development machine) + "nrf52", # Nordic nRF52 platform implementation + } +) + # Platform preference order for memory impact analysis # This order is used when no platform-specific hints are detected from filenames # Priority rationale: @@ -568,6 +584,20 @@ def detect_memory_impact_config( ) platform = _select_platform_by_count(platform_counts) + # Filter out platform-specific components that are incompatible with selected platform + # Platform components (esp32, esp8266, rp2040, etc.) can only build on their own platform + # Other components (wifi, logger, etc.) are cross-platform and can build anywhere + compatible_components = [ + component + for component in components_with_tests + if component not in PLATFORM_SPECIFIC_COMPONENTS + or platform in component_platforms_map.get(component, set()) + ] + + # If no components are compatible with the selected platform, don't run + if not compatible_components: + return {"should_run": "false"} + # Debug output print("Memory impact analysis:", file=sys.stderr) print(f" Changed components: {sorted(changed_component_set)}", file=sys.stderr) @@ -579,10 +609,11 @@ def detect_memory_impact_config( print(f" Platform hints from filenames: {platform_hints}", file=sys.stderr) print(f" Common platforms: {sorted(common_platforms)}", file=sys.stderr) print(f" Selected platform: {platform}", file=sys.stderr) + print(f" Compatible components: {compatible_components}", file=sys.stderr) return { "should_run": "true", - "components": components_with_tests, + "components": compatible_components, "platform": platform, "use_merged_config": "true", } diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py index e73c134151..a33eca5b19 100644 --- a/tests/script/test_determine_jobs.py +++ b/tests/script/test_determine_jobs.py @@ -1130,3 +1130,111 @@ def test_main_core_files_changed_still_detects_components( assert "select" in output["changed_components"] assert "api" in output["changed_components"] assert len(output["changed_components"]) > 0 + + +def test_detect_memory_impact_config_filters_incompatible_esp32_on_esp8266( + tmp_path: Path, +) -> None: + """Test that ESP32 components are filtered out when ESP8266 platform is selected. + + This test verifies the fix for the issue where ESP32 components were being included + when ESP8266 was selected as the platform, causing build failures in PR 10387. + """ + # Create test directory structure + tests_dir = tmp_path / "tests" / "components" + + # esp32 component only has esp32-idf tests (NOT compatible with esp8266) + esp32_dir = tests_dir / "esp32" + esp32_dir.mkdir(parents=True) + (esp32_dir / "test.esp32-idf.yaml").write_text("test: esp32") + (esp32_dir / "test.esp32-s3-idf.yaml").write_text("test: esp32") + + # esp8266 component only has esp8266-ard test (NOT compatible with esp32) + esp8266_dir = tests_dir / "esp8266" + esp8266_dir.mkdir(parents=True) + (esp8266_dir / "test.esp8266-ard.yaml").write_text("test: esp8266") + + # Mock changed_files to return both esp32 and esp8266 component changes + # Include esp8266-specific filename to trigger esp8266 platform hint + with ( + patch.object(determine_jobs, "root_path", str(tmp_path)), + patch.object(helpers, "root_path", str(tmp_path)), + patch.object(determine_jobs, "changed_files") as mock_changed_files, + ): + mock_changed_files.return_value = [ + "tests/components/esp32/common.yaml", + "tests/components/esp8266/test.esp8266-ard.yaml", + "esphome/core/helpers_esp8266.h", # ESP8266-specific file to hint platform + ] + determine_jobs._component_has_tests.cache_clear() + + result = determine_jobs.detect_memory_impact_config() + + # Memory impact should run + assert result["should_run"] == "true" + + # Platform should be esp8266-ard (due to ESP8266 filename hint) + assert result["platform"] == "esp8266-ard" + + # CRITICAL: Only esp8266 component should be included, not esp32 + # This prevents trying to build ESP32 components on ESP8266 platform + assert result["components"] == ["esp8266"], ( + "When esp8266-ard platform is selected, only esp8266 component should be included, " + "not esp32. This prevents trying to build ESP32 components on ESP8266 platform." + ) + + assert result["use_merged_config"] == "true" + + +def test_detect_memory_impact_config_filters_incompatible_esp8266_on_esp32( + tmp_path: Path, +) -> None: + """Test that ESP8266 components are filtered out when ESP32 platform is selected. + + This is the inverse of the ESP8266 test - ensures filtering works both ways. + """ + # Create test directory structure + tests_dir = tmp_path / "tests" / "components" + + # esp32 component only has esp32-idf tests (NOT compatible with esp8266) + esp32_dir = tests_dir / "esp32" + esp32_dir.mkdir(parents=True) + (esp32_dir / "test.esp32-idf.yaml").write_text("test: esp32") + (esp32_dir / "test.esp32-s3-idf.yaml").write_text("test: esp32") + + # esp8266 component only has esp8266-ard test (NOT compatible with esp32) + esp8266_dir = tests_dir / "esp8266" + esp8266_dir.mkdir(parents=True) + (esp8266_dir / "test.esp8266-ard.yaml").write_text("test: esp8266") + + # Mock changed_files to return both esp32 and esp8266 component changes + # Include MORE esp32-specific filenames to ensure esp32-idf wins the hint count + with ( + patch.object(determine_jobs, "root_path", str(tmp_path)), + patch.object(helpers, "root_path", str(tmp_path)), + patch.object(determine_jobs, "changed_files") as mock_changed_files, + ): + mock_changed_files.return_value = [ + "tests/components/esp32/common.yaml", + "tests/components/esp8266/test.esp8266-ard.yaml", + "esphome/components/wifi/wifi_component_esp_idf.cpp", # ESP-IDF hint + "esphome/components/ethernet/ethernet_esp32.cpp", # ESP32 hint + ] + determine_jobs._component_has_tests.cache_clear() + + result = determine_jobs.detect_memory_impact_config() + + # Memory impact should run + assert result["should_run"] == "true" + + # Platform should be esp32-idf (due to more ESP32-IDF hints) + assert result["platform"] == "esp32-idf" + + # CRITICAL: Only esp32 component should be included, not esp8266 + # This prevents trying to build ESP8266 components on ESP32 platform + assert result["components"] == ["esp32"], ( + "When esp32-idf platform is selected, only esp32 component should be included, " + "not esp8266. This prevents trying to build ESP8266 components on ESP32 platform." + ) + + assert result["use_merged_config"] == "true" From 326975ccad0d8c819042981e801b20e79c6c3567 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:09:34 -0500 Subject: [PATCH 0076/1145] [core] Fix ESPTime crash (#11705) --- esphome/core/time.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/core/time.h b/esphome/core/time.h index ffcfced418..68826dabdc 100644 --- a/esphome/core/time.h +++ b/esphome/core/time.h @@ -84,6 +84,9 @@ struct ESPTime { */ static ESPTime from_epoch_local(time_t epoch) { struct tm *c_tm = ::localtime(&epoch); + if (c_tm == nullptr) { + return ESPTime{}; // Return an invalid ESPTime + } return ESPTime::from_c_tm(c_tm, epoch); } /** Convert an UTC epoch timestamp to a UTC time ESPTime instance. @@ -93,6 +96,9 @@ struct ESPTime { */ static ESPTime from_epoch_utc(time_t epoch) { struct tm *c_tm = ::gmtime(&epoch); + if (c_tm == nullptr) { + return ESPTime{}; // Return an invalid ESPTime + } return ESPTime::from_c_tm(c_tm, epoch); } From 758ac583431af02f9482de3240de2c6a806ac8e6 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:38:43 +1000 Subject: [PATCH 0077/1145] [psram] Require mode for S3 (#11470) Co-authored-by: clydeps --- esphome/components/esp32_camera/__init__.py | 18 ++++++--- esphome/components/inkplate/display.py | 3 +- esphome/components/psram/__init__.py | 12 ++++++ .../speaker/media_player/__init__.py | 40 +++++++++---------- tests/component_tests/psram/test_psram.py | 12 ++++-- tests/components/inkplate/test.esp32-idf.yaml | 3 ++ .../mipi_spi/test-lvgl.esp32-s3-idf.yaml | 1 + .../common/i2c_camera/esp32-idf.yaml | 2 + 8 files changed, 60 insertions(+), 31 deletions(-) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index d8ba098645..d9d9bc0a56 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -4,6 +4,7 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import i2c from esphome.components.esp32 import add_idf_component +from esphome.components.psram import DOMAIN as psram_domain import esphome.config_validation as cv from esphome.const import ( CONF_BRIGHTNESS, @@ -26,10 +27,9 @@ import esphome.final_validate as fv _LOGGER = logging.getLogger(__name__) +AUTO_LOAD = ["camera"] DEPENDENCIES = ["esp32"] -AUTO_LOAD = ["camera", "psram"] - esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) ESP32CameraImageData = esp32_camera_ns.struct("CameraImageData") @@ -163,6 +163,14 @@ CONF_ON_IMAGE = "on_image" camera_range_param = cv.int_range(min=-2, max=2) + +def validate_fb_location_(value): + validator = cv.enum(ENUM_FB_LOCATION, upper=True) + if value.lower() == psram_domain: + validator = cv.All(validator, cv.requires_component(psram_domain)) + return validator(value) + + CONFIG_SCHEMA = cv.All( cv.ENTITY_BASE_SCHEMA.extend( { @@ -236,9 +244,9 @@ CONFIG_SCHEMA = cv.All( cv.framerate, cv.Range(min=0, max=1) ), cv.Optional(CONF_FRAME_BUFFER_COUNT, default=1): cv.int_range(min=1, max=2), - cv.Optional(CONF_FRAME_BUFFER_LOCATION, default="PSRAM"): cv.enum( - ENUM_FB_LOCATION, upper=True - ), + cv.Optional( + CONF_FRAME_BUFFER_LOCATION, default="PSRAM" + ): validate_fb_location_, cv.Optional(CONF_ON_STREAM_START): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( diff --git a/esphome/components/inkplate/display.py b/esphome/components/inkplate/display.py index a0b0265cf1..89518dcfab 100644 --- a/esphome/components/inkplate/display.py +++ b/esphome/components/inkplate/display.py @@ -20,8 +20,7 @@ import esphome.final_validate as fv from .const import INKPLATE_10_CUSTOM_WAVEFORMS, WAVEFORMS -DEPENDENCIES = ["i2c", "esp32"] -AUTO_LOAD = ["psram"] +DEPENDENCIES = ["i2c", "esp32", "psram"] CONF_DISPLAY_DATA_0_PIN = "display_data_0_pin" CONF_DISPLAY_DATA_1_PIN = "display_data_1_pin" diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index 8e4f9d7eac..df49e08879 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -1,4 +1,5 @@ import logging +import textwrap import esphome.codegen as cg from esphome.components.esp32 import ( @@ -104,6 +105,17 @@ def get_config_schema(config): if not speeds: raise cv.Invalid("PSRAM is not supported on this chip") modes = SPIRAM_MODES[variant] + if CONF_MODE not in config and len(modes) != 1: + raise ( + cv.Invalid( + textwrap.dedent( + f""" + {variant} requires PSRAM mode selection; one of {", ".join(modes)} + Selection of the wrong mode for the board will cause a runtime failure to initialise PSRAM + """ + ) + ) + ) return cv.Schema( { cv.GenerateID(): cv.declare_id(PsramComponent), diff --git a/esphome/components/speaker/media_player/__init__.py b/esphome/components/speaker/media_player/__init__.py index 7537a61e4e..e50656e723 100644 --- a/esphome/components/speaker/media_player/__init__.py +++ b/esphome/components/speaker/media_player/__init__.py @@ -26,21 +26,12 @@ from esphome.const import ( from esphome.core import CORE, HexInt from esphome.core.entity_helpers import inherit_property_from from esphome.external_files import download_content -from esphome.types import ConfigType +from esphome.final_validate import full_config _LOGGER = logging.getLogger(__name__) -def AUTO_LOAD(config: ConfigType) -> list[str]: - load = ["audio"] - if ( - not config - or config.get(CONF_TASK_STACK_IN_PSRAM) - or config.get(CONF_CODEC_SUPPORT_ENABLED) - ): - return load + ["psram"] - return load - +AUTO_LOAD = ["audio"] CODEOWNERS = ["@kahrendt", "@synesthesiam"] DOMAIN = "media_player" @@ -226,12 +217,19 @@ def _validate_repeated_speaker(config): return config -def _validate_supported_local_file(config): +def _final_validate(config): + # Default to using codec if psram is enabled + if (use_codec := config.get(CONF_CODEC_SUPPORT_ENABLED)) is None: + use_codec = psram.DOMAIN in full_config.get() + conf_id = config[CONF_ID].id + core_data = CORE.data.setdefault(DOMAIN, {conf_id: {}}) + core_data[conf_id][CONF_CODEC_SUPPORT_ENABLED] = use_codec + for file_config in config.get(CONF_FILES, []): _, media_file_type = _read_audio_file_and_type(file_config) if str(media_file_type) == str(audio.AUDIO_FILE_TYPE_ENUM["NONE"]): raise cv.Invalid("Unsupported local media file") - if not config[CONF_CODEC_SUPPORT_ENABLED] and str(media_file_type) != str( + if not use_codec and str(media_file_type) != str( audio.AUDIO_FILE_TYPE_ENUM["WAV"] ): # Only wav files are supported @@ -290,11 +288,11 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_BUFFER_SIZE, default=1000000): cv.int_range( min=4000, max=4000000 ), - cv.Optional( - CONF_CODEC_SUPPORT_ENABLED, default=psram.supported() - ): cv.boolean, + cv.Optional(CONF_CODEC_SUPPORT_ENABLED): cv.boolean, cv.Optional(CONF_FILES): cv.ensure_list(MEDIA_FILE_TYPE_SCHEMA), - cv.Optional(CONF_TASK_STACK_IN_PSRAM, default=False): cv.boolean, + cv.Optional(CONF_TASK_STACK_IN_PSRAM): cv.All( + cv.boolean, cv.requires_component(psram.DOMAIN) + ), cv.Optional(CONF_VOLUME_INCREMENT, default=0.05): cv.percentage, cv.Optional(CONF_VOLUME_INITIAL, default=0.5): cv.percentage, cv.Optional(CONF_VOLUME_MAX, default=1.0): cv.percentage, @@ -317,12 +315,12 @@ FINAL_VALIDATE_SCHEMA = cv.All( }, extra=cv.ALLOW_EXTRA, ), - _validate_supported_local_file, + _final_validate, ) async def to_code(config): - if config[CONF_CODEC_SUPPORT_ENABLED]: + if CORE.data[DOMAIN][config[CONF_ID].id][CONF_CODEC_SUPPORT_ENABLED]: # Compile all supported audio codecs and optimize the wifi settings cg.add_define("USE_AUDIO_FLAC_SUPPORT", True) @@ -352,8 +350,8 @@ async def to_code(config): cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE])) - cg.add(var.set_task_stack_in_psram(config[CONF_TASK_STACK_IN_PSRAM])) - if config[CONF_TASK_STACK_IN_PSRAM]: + if config.get(CONF_TASK_STACK_IN_PSRAM): + cg.add(var.set_task_stack_in_psram(True)) esp32.add_idf_sdkconfig_option( "CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True ) diff --git a/tests/component_tests/psram/test_psram.py b/tests/component_tests/psram/test_psram.py index 3e40a8d192..f8ad013689 100644 --- a/tests/component_tests/psram/test_psram.py +++ b/tests/component_tests/psram/test_psram.py @@ -34,6 +34,12 @@ SUPPORTED_PSRAM_VARIANTS = [ VARIANT_ESP32S3, VARIANT_ESP32P4, ] +SUPPORTED_PSRAM_MODES = { + VARIANT_ESP32: ["quad"], + VARIANT_ESP32S2: ["quad"], + VARIANT_ESP32S3: ["quad", "octal"], + VARIANT_ESP32P4: ["hex"], +} @pytest.mark.parametrize( @@ -86,7 +92,7 @@ def test_psram_configuration_valid_supported_variants( from esphome.components.psram import CONFIG_SCHEMA, FINAL_VALIDATE_SCHEMA # This should not raise an exception - config = CONFIG_SCHEMA({}) + config = CONFIG_SCHEMA({"mode": SUPPORTED_PSRAM_MODES[variant][0]}) FINAL_VALIDATE_SCHEMA(config) @@ -122,7 +128,7 @@ def _setup_psram_final_validation_test( ("config", "esp32_config", "expect_error", "error_match"), [ pytest.param( - {"speed": "120MHz"}, + {"mode": "quad", "speed": "120MHz"}, {"cpu_frequency": "160MHz"}, True, r"PSRAM 120MHz requires 240MHz CPU frequency", @@ -143,7 +149,7 @@ def _setup_psram_final_validation_test( id="ecc_only_in_octal_mode", ), pytest.param( - {"speed": "120MHZ"}, + {"mode": "quad", "speed": "120MHZ"}, {"cpu_frequency": "240MHZ"}, False, None, diff --git a/tests/components/inkplate/test.esp32-idf.yaml b/tests/components/inkplate/test.esp32-idf.yaml index b47e39c389..17e58ce390 100644 --- a/tests/components/inkplate/test.esp32-idf.yaml +++ b/tests/components/inkplate/test.esp32-idf.yaml @@ -1,4 +1,7 @@ packages: i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml +psram: + mode: quad + <<: !include common.yaml diff --git a/tests/components/mipi_spi/test-lvgl.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-lvgl.esp32-s3-idf.yaml index 48f34f3449..14f864d326 100644 --- a/tests/components/mipi_spi/test-lvgl.esp32-s3-idf.yaml +++ b/tests/components/mipi_spi/test-lvgl.esp32-s3-idf.yaml @@ -9,3 +9,4 @@ display: lvgl: psram: + mode: quad diff --git a/tests/test_build_components/common/i2c_camera/esp32-idf.yaml b/tests/test_build_components/common/i2c_camera/esp32-idf.yaml index a6e7c264cb..443ebbebd9 100644 --- a/tests/test_build_components/common/i2c_camera/esp32-idf.yaml +++ b/tests/test_build_components/common/i2c_camera/esp32-idf.yaml @@ -1,4 +1,6 @@ # I2C bus for camera sensor +psram: + i2c: - id: i2c_camera_bus sda: 25 From 0b04361fc0be415a28bf5ffb9c223bf2a262f5b0 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:39:27 +1000 Subject: [PATCH 0078/1145] [lvgl] Layout improvements (#10149) Co-authored-by: clydeps Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/lvgl/__init__.py | 25 +- esphome/components/lvgl/defines.py | 3 + esphome/components/lvgl/layout.py | 357 +++++++++++++++++++ esphome/components/lvgl/lv_validation.py | 3 + esphome/components/lvgl/lvcode.py | 5 +- esphome/components/lvgl/schemas.py | 259 ++++---------- esphome/components/lvgl/types.py | 31 +- esphome/components/lvgl/widgets/__init__.py | 7 +- esphome/components/lvgl/widgets/canvas.py | 60 ++-- esphome/components/lvgl/widgets/checkbox.py | 9 +- esphome/components/lvgl/widgets/container.py | 39 ++ esphome/components/lvgl/widgets/label.py | 11 +- esphome/components/lvgl/widgets/qrcode.py | 13 +- esphome/components/lvgl/widgets/textarea.py | 17 +- tests/components/lvgl/lvgl-package.yaml | 22 +- 15 files changed, 572 insertions(+), 289 deletions(-) create mode 100644 esphome/components/lvgl/layout.py create mode 100644 esphome/components/lvgl/widgets/container.py diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index aa6935c5fc..861999d0b7 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -41,10 +41,7 @@ from .lv_validation import lv_bool, lv_images_used from .lvcode import LvContext, LvglComponent, lvgl_static from .schemas import ( DISP_BG_SCHEMA, - FLEX_OBJ_SCHEMA, FULL_STYLE_SCHEMA, - GRID_CELL_SCHEMA, - LAYOUT_SCHEMAS, WIDGET_TYPES, any_widget_schema, container_schema, @@ -78,6 +75,7 @@ from .widgets.button import button_spec from .widgets.buttonmatrix import buttonmatrix_spec from .widgets.canvas import canvas_spec from .widgets.checkbox import checkbox_spec +from .widgets.container import container_spec from .widgets.dropdown import dropdown_spec from .widgets.img import img_spec from .widgets.keyboard import keyboard_spec @@ -130,20 +128,10 @@ for w_type in ( tileview_spec, qr_code_spec, canvas_spec, + container_spec, ): WIDGET_TYPES[w_type.name] = w_type -WIDGET_SCHEMA = any_widget_schema() - -LAYOUT_SCHEMAS[df.TYPE_GRID] = { - cv.Optional(df.CONF_WIDGETS): cv.ensure_list(any_widget_schema(GRID_CELL_SCHEMA)) -} -LAYOUT_SCHEMAS[df.TYPE_FLEX] = { - cv.Optional(df.CONF_WIDGETS): cv.ensure_list(any_widget_schema(FLEX_OBJ_SCHEMA)) -} -LAYOUT_SCHEMAS[df.TYPE_NONE] = { - cv.Optional(df.CONF_WIDGETS): cv.ensure_list(any_widget_schema()) -} for w_type in WIDGET_TYPES.values(): register_action( f"lvgl.{w_type.name}.update", @@ -410,7 +398,7 @@ def display_schema(config): def add_hello_world(config): if df.CONF_WIDGETS not in config and CONF_PAGES not in config: LOGGER.info("No pages or widgets configured, creating default hello_world page") - config[df.CONF_WIDGETS] = cv.ensure_list(WIDGET_SCHEMA)(get_hello_world()) + config[df.CONF_WIDGETS] = any_widget_schema()(get_hello_world()) return config @@ -450,6 +438,7 @@ LVGL_SCHEMA = cv.All( ), } ), + cv.Optional(CONF_PAGES): cv.ensure_list(container_schema(page_spec)), **{ cv.Optional(x): validate_automation( { @@ -459,12 +448,6 @@ LVGL_SCHEMA = cv.All( ) for x in SIMPLE_TRIGGERS }, - cv.Exclusive(df.CONF_WIDGETS, CONF_PAGES): cv.ensure_list( - WIDGET_SCHEMA - ), - cv.Exclusive(CONF_PAGES, CONF_PAGES): cv.ensure_list( - container_schema(page_spec) - ), cv.Optional(df.CONF_MSGBOXES): cv.ensure_list(MSGBOX_SCHEMA), cv.Optional(df.CONF_PAGE_WRAP, default=True): lv_bool, cv.Optional(df.CONF_TOP_LAYER): container_schema(obj_spec), diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 3241ba9c3f..f2bcb6cc06 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -394,6 +394,8 @@ LV_FLEX_ALIGNMENTS = LvConstant( "SPACE_BETWEEN", ) +LV_FLEX_CROSS_ALIGNMENTS = LV_FLEX_ALIGNMENTS.extend("STRETCH") + LV_MENU_MODES = LvConstant( "LV_MENU_HEADER_", "TOP_FIXED", @@ -436,6 +438,7 @@ CONF_BUTTONS = "buttons" CONF_BYTE_ORDER = "byte_order" CONF_CHANGE_RATE = "change_rate" CONF_CLOSE_BUTTON = "close_button" +CONF_CONTAINER = "container" CONF_CONTROL = "control" CONF_DEFAULT_FONT = "default_font" CONF_DEFAULT_GROUP = "default_group" diff --git a/esphome/components/lvgl/layout.py b/esphome/components/lvgl/layout.py new file mode 100644 index 0000000000..0aed525e16 --- /dev/null +++ b/esphome/components/lvgl/layout.py @@ -0,0 +1,357 @@ +import re + +import esphome.config_validation as cv +from esphome.const import CONF_HEIGHT, CONF_TYPE, CONF_WIDTH + +from .defines import ( + CONF_FLEX_ALIGN_CROSS, + CONF_FLEX_ALIGN_MAIN, + CONF_FLEX_ALIGN_TRACK, + CONF_FLEX_FLOW, + CONF_FLEX_GROW, + CONF_GRID_CELL_COLUMN_POS, + CONF_GRID_CELL_COLUMN_SPAN, + CONF_GRID_CELL_ROW_POS, + CONF_GRID_CELL_ROW_SPAN, + CONF_GRID_CELL_X_ALIGN, + CONF_GRID_CELL_Y_ALIGN, + CONF_GRID_COLUMN_ALIGN, + CONF_GRID_COLUMNS, + CONF_GRID_ROW_ALIGN, + CONF_GRID_ROWS, + CONF_LAYOUT, + CONF_PAD_COLUMN, + CONF_PAD_ROW, + CONF_WIDGETS, + FLEX_FLOWS, + LV_CELL_ALIGNMENTS, + LV_FLEX_ALIGNMENTS, + LV_FLEX_CROSS_ALIGNMENTS, + LV_GRID_ALIGNMENTS, + TYPE_FLEX, + TYPE_GRID, + TYPE_NONE, + LvConstant, +) +from .lv_validation import padding, size + +cell_alignments = LV_CELL_ALIGNMENTS.one_of +grid_alignments = LV_GRID_ALIGNMENTS.one_of +flex_alignments = LV_FLEX_ALIGNMENTS.one_of + +FLEX_LAYOUT_SCHEMA = { + cv.Required(CONF_TYPE): cv.one_of(TYPE_FLEX, lower=True), + cv.Optional(CONF_FLEX_FLOW, default="row_wrap"): FLEX_FLOWS.one_of, + cv.Optional(CONF_FLEX_ALIGN_MAIN, default="start"): flex_alignments, + cv.Optional( + CONF_FLEX_ALIGN_CROSS, default="start" + ): LV_FLEX_CROSS_ALIGNMENTS.one_of, + cv.Optional(CONF_FLEX_ALIGN_TRACK, default="start"): flex_alignments, + cv.Optional(CONF_PAD_ROW): padding, + cv.Optional(CONF_PAD_COLUMN): padding, + cv.Optional(CONF_FLEX_GROW): cv.int_, +} + +FLEX_HV_STYLE = { + CONF_FLEX_ALIGN_MAIN: "LV_FLEX_ALIGN_SPACE_EVENLY", + CONF_FLEX_ALIGN_TRACK: "LV_FLEX_ALIGN_CENTER", + CONF_FLEX_ALIGN_CROSS: "LV_FLEX_ALIGN_CENTER", + CONF_TYPE: TYPE_FLEX, +} + +FLEX_OBJ_SCHEMA = { + cv.Optional(CONF_FLEX_GROW): cv.int_, +} + + +def flex_hv_schema(dir): + dir = CONF_HEIGHT if dir == "horizontal" else CONF_WIDTH + return { + cv.Optional(CONF_FLEX_GROW, default=1): cv.int_, + cv.Optional(dir, default="100%"): size, + } + + +def grid_free_space(value): + value = cv.Upper(value) + if value.startswith("FR(") and value.endswith(")"): + value = value.removesuffix(")").removeprefix("FR(") + return f"LV_GRID_FR({cv.positive_int(value)})" + raise cv.Invalid("must be a size in pixels, CONTENT or FR(nn)") + + +grid_spec = cv.Any(size, LvConstant("LV_GRID_", "CONTENT").one_of, grid_free_space) + +GRID_CELL_SCHEMA = { + cv.Optional(CONF_GRID_CELL_ROW_POS): cv.positive_int, + cv.Optional(CONF_GRID_CELL_COLUMN_POS): cv.positive_int, + cv.Optional(CONF_GRID_CELL_ROW_SPAN, default=1): cv.positive_int, + cv.Optional(CONF_GRID_CELL_COLUMN_SPAN, default=1): cv.positive_int, + cv.Optional(CONF_GRID_CELL_X_ALIGN): grid_alignments, + cv.Optional(CONF_GRID_CELL_Y_ALIGN): grid_alignments, +} + + +class Layout: + """ + Define properties for a layout + The base class is layout "none" + """ + + def get_type(self): + return TYPE_NONE + + def get_layout_schemas(self, config: dict) -> tuple: + """ + Get the layout and child schema for a given widget based on its layout type. + """ + return None, {} + + def validate(self, config): + """ + Validate the layout configuration. This is called late in the schema validation + :param config: The input configuration + :return: The validated configuration + """ + return config + + +class FlexLayout(Layout): + def get_type(self): + return TYPE_FLEX + + def get_layout_schemas(self, config: dict) -> tuple: + layout = config.get(CONF_LAYOUT) + if not isinstance(layout, dict) or layout.get(CONF_TYPE) != TYPE_FLEX: + return None, {} + child_schema = FLEX_OBJ_SCHEMA + if grow := layout.get(CONF_FLEX_GROW): + child_schema = {cv.Optional(CONF_FLEX_GROW, default=grow): cv.int_} + # Polyfill to implement stretch alignment for flex containers + # LVGL does not support this natively, so we add a 100% size property to the children in the cross-axis + if layout.get(CONF_FLEX_ALIGN_CROSS) == "LV_FLEX_ALIGN_STRETCH": + dimension = ( + CONF_WIDTH + if "COLUMN" in layout[CONF_FLEX_FLOW].upper() + else CONF_HEIGHT + ) + child_schema[cv.Optional(dimension, default="100%")] = size + return FLEX_LAYOUT_SCHEMA, child_schema + + def validate(self, config): + """ + Perform validation on the container and its children for this layout + :param config: + :return: + """ + return config + + +class DirectionalLayout(FlexLayout): + def __init__(self, direction: str, flow): + """ + :param direction: "horizontal" or "vertical" + :param flow: "row" or "column" + """ + super().__init__() + self.direction = direction + self.flow = flow + + def get_type(self): + return self.direction + + def get_layout_schemas(self, config: dict) -> tuple: + if config.get(CONF_LAYOUT, "").lower() != self.direction: + return None, {} + return cv.one_of(self.direction, lower=True), flex_hv_schema(self.direction) + + def validate(self, config): + assert config[CONF_LAYOUT].lower() == self.direction + config[CONF_LAYOUT] = { + **FLEX_HV_STYLE, + CONF_FLEX_FLOW: "LV_FLEX_FLOW_" + self.flow.upper(), + } + return config + + +class GridLayout(Layout): + _GRID_LAYOUT_REGEX = re.compile(r"^\s*(\d+)\s*x\s*(\d+)\s*$") + + def get_type(self): + return TYPE_GRID + + def get_layout_schemas(self, config: dict) -> tuple: + layout = config.get(CONF_LAYOUT) + if isinstance(layout, str): + if GridLayout._GRID_LAYOUT_REGEX.match(layout): + return ( + cv.string, + { + cv.Optional(CONF_GRID_CELL_ROW_POS): cv.positive_int, + cv.Optional(CONF_GRID_CELL_COLUMN_POS): cv.positive_int, + cv.Optional( + CONF_GRID_CELL_ROW_SPAN, default=1 + ): cv.positive_int, + cv.Optional( + CONF_GRID_CELL_COLUMN_SPAN, default=1 + ): cv.positive_int, + cv.Optional( + CONF_GRID_CELL_X_ALIGN, default="center" + ): grid_alignments, + cv.Optional( + CONF_GRID_CELL_Y_ALIGN, default="center" + ): grid_alignments, + }, + ) + # Not a valid grid layout string + return None, {} + + if not isinstance(layout, dict) or layout.get(CONF_TYPE) != TYPE_GRID: + return None, {} + return ( + { + cv.Required(CONF_TYPE): cv.one_of(TYPE_GRID, lower=True), + cv.Required(CONF_GRID_ROWS): [grid_spec], + cv.Required(CONF_GRID_COLUMNS): [grid_spec], + cv.Optional(CONF_GRID_COLUMN_ALIGN): grid_alignments, + cv.Optional(CONF_GRID_ROW_ALIGN): grid_alignments, + cv.Optional(CONF_PAD_ROW): padding, + cv.Optional(CONF_PAD_COLUMN): padding, + }, + { + cv.Optional(CONF_GRID_CELL_ROW_POS): cv.positive_int, + cv.Optional(CONF_GRID_CELL_COLUMN_POS): cv.positive_int, + cv.Optional(CONF_GRID_CELL_ROW_SPAN, default=1): cv.positive_int, + cv.Optional(CONF_GRID_CELL_COLUMN_SPAN, default=1): cv.positive_int, + cv.Optional(CONF_GRID_CELL_X_ALIGN): grid_alignments, + cv.Optional(CONF_GRID_CELL_Y_ALIGN): grid_alignments, + }, + ) + + def validate(self, config: dict): + """ + Validate the grid layout. + The `layout:` key may be a dictionary with `rows` and `columns` keys, or a string in the format "rows x columns". + Either all cells must have a row and column, + or none, in which case the grid layout is auto-generated. + :param config: + :return: The config updated with auto-generated values + """ + layout = config.get(CONF_LAYOUT) + if isinstance(layout, str): + # If the layout is a string, assume it is in the format "rows x columns", implying + # a grid layout with the specified number of rows and columns each with CONTENT sizing. + layout = layout.strip() + match = GridLayout._GRID_LAYOUT_REGEX.match(layout) + if match: + rows = int(match.group(1)) + cols = int(match.group(2)) + layout = { + CONF_TYPE: TYPE_GRID, + CONF_GRID_ROWS: ["LV_GRID_FR(1)"] * rows, + CONF_GRID_COLUMNS: ["LV_GRID_FR(1)"] * cols, + } + config[CONF_LAYOUT] = layout + else: + raise cv.Invalid( + f"Invalid grid layout format: {config}, expected 'rows x columns'", + [CONF_LAYOUT], + ) + # should be guaranteed to be a dict at this point + assert isinstance(layout, dict) + assert layout.get(CONF_TYPE) == TYPE_GRID + rows = len(layout[CONF_GRID_ROWS]) + columns = len(layout[CONF_GRID_COLUMNS]) + used_cells = [[None] * columns for _ in range(rows)] + for index, widget in enumerate(config.get(CONF_WIDGETS, [])): + _, w = next(iter(widget.items())) + if (CONF_GRID_CELL_COLUMN_POS in w) != (CONF_GRID_CELL_ROW_POS in w): + raise cv.Invalid( + "Both row and column positions must be specified, or both omitted", + [CONF_WIDGETS, index], + ) + if CONF_GRID_CELL_ROW_POS in w: + row = w[CONF_GRID_CELL_ROW_POS] + column = w[CONF_GRID_CELL_COLUMN_POS] + else: + try: + row, column = next( + (r_idx, c_idx) + for r_idx, row in enumerate(used_cells) + for c_idx, value in enumerate(row) + if value is None + ) + except StopIteration: + raise cv.Invalid( + "No free cells available in grid layout", [CONF_WIDGETS, index] + ) from None + w[CONF_GRID_CELL_ROW_POS] = row + w[CONF_GRID_CELL_COLUMN_POS] = column + + for i in range(w[CONF_GRID_CELL_ROW_SPAN]): + for j in range(w[CONF_GRID_CELL_COLUMN_SPAN]): + if row + i >= rows or column + j >= columns: + raise cv.Invalid( + f"Cell at {row}/{column} span {w[CONF_GRID_CELL_ROW_SPAN]}x{w[CONF_GRID_CELL_COLUMN_SPAN]} " + f"exceeds grid size {rows}x{columns}", + [CONF_WIDGETS, index], + ) + if used_cells[row + i][column + j] is not None: + raise cv.Invalid( + f"Cell span {row + i}/{column + j} already occupied by widget at index {used_cells[row + i][column + j]}", + [CONF_WIDGETS, index], + ) + used_cells[row + i][column + j] = index + + return config + + +LAYOUT_CLASSES = ( + FlexLayout(), + GridLayout(), + DirectionalLayout("horizontal", "row"), + DirectionalLayout("vertical", "column"), +) +LAYOUT_CHOICES = [x.get_type() for x in LAYOUT_CLASSES] + + +def append_layout_schema(schema, config: dict): + """ + Get the child layout schema for a given widget based on its layout type. + :param config: The config to check + :return: A schema for the layout including a widgets key + """ + # Local import to avoid circular dependencies + if CONF_WIDGETS not in config: + if CONF_LAYOUT in config: + raise cv.Invalid( + f"Layout {config[CONF_LAYOUT]} requires a {CONF_WIDGETS} key", + [CONF_LAYOUT], + ) + return schema + + from .schemas import any_widget_schema + + if CONF_LAYOUT not in config: + # If no layout is specified, return the schema as is + return schema.extend({cv.Optional(CONF_WIDGETS): any_widget_schema()}) + + for layout_class in LAYOUT_CLASSES: + layout_schema, child_schema = layout_class.get_layout_schemas(config) + if layout_schema: + layout_schema = cv.Schema( + { + cv.Required(CONF_LAYOUT): layout_schema, + cv.Required(CONF_WIDGETS): any_widget_schema(child_schema), + } + ) + layout_schema.add_extra(layout_class.validate) + return layout_schema.extend(schema) + + # If no layout class matched, return a default schema + return cv.Schema( + { + cv.Optional(CONF_LAYOUT): cv.one_of(*LAYOUT_CHOICES, lower=True), + cv.Optional(CONF_WIDGETS): any_widget_schema(), + } + ) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index 6f95a32a18..9fe72128ce 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -1,3 +1,4 @@ +import re from typing import TYPE_CHECKING, Any import esphome.codegen as cg @@ -246,6 +247,8 @@ def pixels_or_percent_validator(value): return ["pixels", "..%"] if isinstance(value, str) and value.lower().endswith("px"): value = cv.int_(value[:-2]) + if isinstance(value, str) and re.match(r"^lv_pct\((\d+)\)$", value): + return value value = cv.Any(cv.int_, cv.percentage)(value) if isinstance(value, int): return value diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index ea38845c07..c11597131f 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -299,6 +299,7 @@ class LvExpr(MockLv): # Top level mock for generic lv_ calls to be recorded lv = MockLv("lv_") +LV = MockLv("LV_") # Just generate an expression lv_expr = LvExpr("lv_") # Mock for lv_obj_ calls @@ -327,7 +328,7 @@ def lv_assign(target, expression): lv_add(AssignmentExpression("", "", target, expression)) -def lv_Pvariable(type, name): +def lv_Pvariable(type, name) -> MockObj: """ Create but do not initialise a pointer variable :param type: Type of the variable target @@ -343,7 +344,7 @@ def lv_Pvariable(type, name): return var -def lv_variable(type, name): +def lv_variable(type, name) -> MockObj: """ Create but do not initialise a variable :param type: Type of the variable target diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 959d203c41..dd248d0b94 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -12,17 +12,21 @@ from esphome.const import ( CONF_TEXT, CONF_TIME, CONF_TRIGGER_ID, - CONF_TYPE, CONF_X, CONF_Y, ) from esphome.core import TimePeriod from esphome.core.config import StartupTrigger -from esphome.schema_extractors import SCHEMA_EXTRACT from . import defines as df, lv_validation as lvalid -from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR, TYPE_GRID -from .helpers import add_lv_use, requires_component, validate_printf +from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR +from .helpers import requires_component, validate_printf +from .layout import ( + FLEX_OBJ_SCHEMA, + GRID_CELL_SCHEMA, + append_layout_schema, + grid_alignments, +) from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity from .lvcode import LvglComponent, lv_event_t_ptr from .types import ( @@ -72,11 +76,9 @@ def _validate_text(value): # A schema for text properties -TEXT_SCHEMA = cv.Schema( - { - cv.Optional(CONF_TEXT): _validate_text, - } -) +TEXT_SCHEMA = { + cv.Optional(CONF_TEXT): _validate_text, +} LIST_ACTION_SCHEMA = cv.ensure_list( cv.maybe_simple_value( @@ -136,7 +138,7 @@ STYLE_PROPS = { "arc_opa": lvalid.opacity, "arc_color": lvalid.lv_color, "arc_rounded": lvalid.lv_bool, - "arc_width": lvalid.lv_positive_int, + "arc_width": lvalid.pixels, "anim_time": lvalid.lv_milliseconds, "bg_color": lvalid.lv_color, "bg_grad": lv_gradient, @@ -223,10 +225,6 @@ STYLE_REMAP = { "image_recolor_opa": "img_recolor_opa", } -cell_alignments = df.LV_CELL_ALIGNMENTS.one_of -grid_alignments = df.LV_GRID_ALIGNMENTS.one_of -flex_alignments = df.LV_FLEX_ALIGNMENTS.one_of - # Complete object style schema STYLE_SCHEMA = cv.Schema({cv.Optional(k): v for k, v in STYLE_PROPS.items()}).extend( { @@ -266,10 +264,8 @@ def part_schema(parts): :param parts: The parts to include :return: The schema """ - return ( - cv.Schema({cv.Optional(part): STATE_SCHEMA for part in parts}) - .extend(STATE_SCHEMA) - .extend(FLAG_SCHEMA) + return STATE_SCHEMA.extend(FLAG_SCHEMA).extend( + {cv.Optional(part): STATE_SCHEMA for part in parts} ) @@ -277,10 +273,10 @@ def automation_schema(typ: LvType): events = df.LV_EVENT_TRIGGERS + df.SWIPE_TRIGGERS if typ.has_on_value: events = events + (CONF_ON_VALUE,) - args = typ.get_arg_type() if isinstance(typ, LvType) else [] + args = typ.get_arg_type() args.append(lv_event_t_ptr) - return cv.Schema( - { + return { + **{ cv.Optional(event): validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -289,14 +285,11 @@ def automation_schema(typ: LvType): } ) for event in events - } - ).extend( - { - cv.Optional(CONF_ON_BOOT): validate_automation( - {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger)} - ) - } - ) + }, + cv.Optional(CONF_ON_BOOT): validate_automation( + {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger)} + ), + } def base_update_schema(widget_type, parts): @@ -335,75 +328,17 @@ def obj_schema(widget_type: WidgetType): """ return ( part_schema(widget_type.parts) - .extend(LAYOUT_SCHEMA) .extend(ALIGN_TO_SCHEMA) .extend(automation_schema(widget_type.w_type)) .extend( - cv.Schema( - { - cv.Optional(CONF_STATE): SET_STATE_SCHEMA, - cv.Optional(CONF_GROUP): cv.use_id(lv_group_t), - } - ) + { + cv.Optional(CONF_STATE): SET_STATE_SCHEMA, + cv.Optional(CONF_GROUP): cv.use_id(lv_group_t), + } ) ) -def _validate_grid_layout(config): - layout = config[df.CONF_LAYOUT] - rows = len(layout[df.CONF_GRID_ROWS]) - columns = len(layout[df.CONF_GRID_COLUMNS]) - used_cells = [[None] * columns for _ in range(rows)] - for index, widget in enumerate(config[df.CONF_WIDGETS]): - _, w = next(iter(widget.items())) - if (df.CONF_GRID_CELL_COLUMN_POS in w) != (df.CONF_GRID_CELL_ROW_POS in w): - # pylint: disable=raise-missing-from - raise cv.Invalid( - "Both row and column positions must be specified, or both omitted", - [df.CONF_WIDGETS, index], - ) - if df.CONF_GRID_CELL_ROW_POS in w: - row = w[df.CONF_GRID_CELL_ROW_POS] - column = w[df.CONF_GRID_CELL_COLUMN_POS] - else: - try: - row, column = next( - (r_idx, c_idx) - for r_idx, row in enumerate(used_cells) - for c_idx, value in enumerate(row) - if value is None - ) - except StopIteration: - # pylint: disable=raise-missing-from - raise cv.Invalid( - "No free cells available in grid layout", [df.CONF_WIDGETS, index] - ) - w[df.CONF_GRID_CELL_ROW_POS] = row - w[df.CONF_GRID_CELL_COLUMN_POS] = column - - for i in range(w[df.CONF_GRID_CELL_ROW_SPAN]): - for j in range(w[df.CONF_GRID_CELL_COLUMN_SPAN]): - if row + i >= rows or column + j >= columns: - # pylint: disable=raise-missing-from - raise cv.Invalid( - f"Cell at {row}/{column} span {w[df.CONF_GRID_CELL_ROW_SPAN]}x{w[df.CONF_GRID_CELL_COLUMN_SPAN]} " - f"exceeds grid size {rows}x{columns}", - [df.CONF_WIDGETS, index], - ) - if used_cells[row + i][column + j] is not None: - # pylint: disable=raise-missing-from - raise cv.Invalid( - f"Cell span {row + i}/{column + j} already occupied by widget at index {used_cells[row + i][column + j]}", - [df.CONF_WIDGETS, index], - ) - used_cells[row + i][column + j] = index - - return config - - -LAYOUT_SCHEMAS = {} -LAYOUT_VALIDATORS = {TYPE_GRID: _validate_grid_layout} - ALIGN_TO_SCHEMA = { cv.Optional(df.CONF_ALIGN_TO): cv.Schema( { @@ -416,57 +351,6 @@ ALIGN_TO_SCHEMA = { } -def grid_free_space(value): - value = cv.Upper(value) - if value.startswith("FR(") and value.endswith(")"): - value = value.removesuffix(")").removeprefix("FR(") - return f"LV_GRID_FR({cv.positive_int(value)})" - raise cv.Invalid("must be a size in pixels, CONTENT or FR(nn)") - - -grid_spec = cv.Any( - lvalid.size, df.LvConstant("LV_GRID_", "CONTENT").one_of, grid_free_space -) - -LAYOUT_SCHEMA = { - cv.Optional(df.CONF_LAYOUT): cv.typed_schema( - { - df.TYPE_GRID: { - cv.Required(df.CONF_GRID_ROWS): [grid_spec], - cv.Required(df.CONF_GRID_COLUMNS): [grid_spec], - cv.Optional(df.CONF_GRID_COLUMN_ALIGN): grid_alignments, - cv.Optional(df.CONF_GRID_ROW_ALIGN): grid_alignments, - cv.Optional(df.CONF_PAD_ROW): lvalid.padding, - cv.Optional(df.CONF_PAD_COLUMN): lvalid.padding, - }, - df.TYPE_FLEX: { - cv.Optional( - df.CONF_FLEX_FLOW, default="row_wrap" - ): df.FLEX_FLOWS.one_of, - cv.Optional(df.CONF_FLEX_ALIGN_MAIN, default="start"): flex_alignments, - cv.Optional(df.CONF_FLEX_ALIGN_CROSS, default="start"): flex_alignments, - cv.Optional(df.CONF_FLEX_ALIGN_TRACK, default="start"): flex_alignments, - cv.Optional(df.CONF_PAD_ROW): lvalid.padding, - cv.Optional(df.CONF_PAD_COLUMN): lvalid.padding, - }, - }, - lower=True, - ) -} - -GRID_CELL_SCHEMA = { - cv.Optional(df.CONF_GRID_CELL_ROW_POS): cv.positive_int, - cv.Optional(df.CONF_GRID_CELL_COLUMN_POS): cv.positive_int, - cv.Optional(df.CONF_GRID_CELL_ROW_SPAN, default=1): cv.positive_int, - cv.Optional(df.CONF_GRID_CELL_COLUMN_SPAN, default=1): cv.positive_int, - cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments, - cv.Optional(df.CONF_GRID_CELL_Y_ALIGN): grid_alignments, -} - -FLEX_OBJ_SCHEMA = { - cv.Optional(df.CONF_FLEX_GROW): cv.int_, -} - DISP_BG_SCHEMA = cv.Schema( { cv.Optional(df.CONF_DISP_BG_IMAGE): cv.Any( @@ -498,48 +382,11 @@ ALL_STYLES = { } -def container_validator(schema, widget_type: WidgetType): - """ - Create a validator for a container given the widget type - :param schema: Base schema to extend - :param widget_type: - :return: - """ - - def validator(value): - if w_sch := widget_type.schema: - if isinstance(w_sch, dict): - w_sch = cv.Schema(w_sch) - # order is important here to preserve extras - result = w_sch.extend(schema) - else: - result = schema - ltype = df.TYPE_NONE - if value and (layout := value.get(df.CONF_LAYOUT)): - if not isinstance(layout, dict): - raise cv.Invalid("Layout value must be a dict") - ltype = layout.get(CONF_TYPE) - if not ltype: - raise (cv.Invalid("Layout schema requires type:")) - add_lv_use(ltype) - if value == SCHEMA_EXTRACT: - return result - result = result.extend( - LAYOUT_SCHEMAS.get(ltype.lower(), LAYOUT_SCHEMAS[df.TYPE_NONE]) - ) - value = result(value) - if layout_validator := LAYOUT_VALIDATORS.get(ltype): - value = layout_validator(value) - return value - - return validator - - def container_schema(widget_type: WidgetType, extras=None): """ Create a schema for a container widget of a given type. All obj properties are available, plus the extras passed in, plus any defined for the specific widget being specified. - :param widget_type: The widget type, e.g. "img" + :param widget_type: The widget type, e.g. "image" :param extras: Additional options to be made available, e.g. layout properties for children :return: The schema for this type of widget. """ @@ -549,31 +396,49 @@ def container_schema(widget_type: WidgetType, extras=None): if extras: schema = schema.extend(extras) # Delayed evaluation for recursion - return container_validator(schema, widget_type) + schema = schema.extend(widget_type.schema) -def widget_schema(widget_type: WidgetType, extras=None): - """ - Create a schema for a given widget type - :param widget_type: The name of the widget - :param extras: - :return: - """ - validator = container_schema(widget_type, extras=extras) - if required := widget_type.required_component: - validator = cv.All(validator, requires_component(required)) - return cv.Exclusive(widget_type.name, df.CONF_WIDGETS), validator + def validator(value): + return append_layout_schema(schema, value)(value) - -# All widget schemas must be defined before this is called. + return validator def any_widget_schema(extras=None): """ - Generate schemas for all possible LVGL widgets. This is what implements the ability to have a list of any kind of + Dynamically generate schemas for all possible LVGL widgets. This is what implements the ability to have a list of any kind of widget under the widgets: key. :param extras: Additional schema to be applied to each generated one - :return: + :return: A validator for the Widgets key """ - return cv.Any(dict(widget_schema(wt, extras) for wt in WIDGET_TYPES.values())) + + def validator(value): + if isinstance(value, dict): + # Convert to list + value = [{k: v} for k, v in value.items()] + if not isinstance(value, list): + raise cv.Invalid("Expected a list of widgets") + result = [] + for index, entry in enumerate(value): + if not isinstance(entry, dict) or len(entry) != 1: + raise cv.Invalid( + "Each widget must be a dictionary with a single key", path=[index] + ) + [(key, value)] = entry.items() + # Validate the widget against its schema + widget_type = WIDGET_TYPES.get(key) + if not widget_type: + raise cv.Invalid(f"Unknown widget type: {key}", path=[index]) + container_validator = container_schema(widget_type, extras=extras) + if required := widget_type.required_component: + container_validator = cv.All( + container_validator, requires_component(required) + ) + # Apply custom validation + value = widget_type.validate(value or {}) + result.append({key: container_validator(value)}) + return result + + return validator diff --git a/esphome/components/lvgl/types.py b/esphome/components/lvgl/types.py index 9955b530aa..8c33e13934 100644 --- a/esphome/components/lvgl/types.py +++ b/esphome/components/lvgl/types.py @@ -1,6 +1,7 @@ import sys from esphome import automation, codegen as cg +from esphome.config_validation import Schema from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_TEXT, CONF_VALUE from esphome.cpp_generator import MockObj, MockObjClass from esphome.cpp_types import esphome_ns @@ -135,14 +136,14 @@ class WidgetType: self.lv_name = lv_name or name self.w_type = w_type self.parts = parts - if schema is None: - self.schema = {} - else: - self.schema = schema + if not isinstance(schema, Schema): + schema = Schema(schema or {}) + self.schema = schema if modify_schema is None: - self.modify_schema = self.schema - else: - self.modify_schema = modify_schema + modify_schema = schema + if not isinstance(modify_schema, Schema): + modify_schema = Schema(modify_schema) + self.modify_schema = modify_schema self.mock_obj = MockObj(f"lv_{self.lv_name}", "_") @property @@ -163,7 +164,6 @@ class WidgetType: :param config: Its configuration :return: Generated code as a list of text lines """ - return [] async def obj_creator(self, parent: MockObjClass, config: dict): """ @@ -174,6 +174,13 @@ class WidgetType: """ return lv_expr.call(f"{self.lv_name}_create", parent) + def on_create(self, var: MockObj, config: dict): + """ + Called from to_code when the widget is created, to set up any initial properties + :param var: The variable representing the widget + :param config: Its configuration + """ + def get_uses(self): """ Get a list of other widgets used by this one @@ -193,6 +200,14 @@ class WidgetType: def get_scale(self, config: dict): return 1.0 + def validate(self, value): + """ + Provides an opportunity for custom validation for a given widget type + :param value: + :return: + """ + return value + class NumberType(WidgetType): def get_max(self, config: dict): diff --git a/esphome/components/lvgl/widgets/__init__.py b/esphome/components/lvgl/widgets/__init__.py index 1f9cdde0a0..7d9f9cb7de 100644 --- a/esphome/components/lvgl/widgets/__init__.py +++ b/esphome/components/lvgl/widgets/__init__.py @@ -339,7 +339,10 @@ async def set_obj_properties(w: Widget, config): if layout_type == TYPE_FLEX: lv_obj.set_flex_flow(w.obj, literal(layout[CONF_FLEX_FLOW])) main = literal(layout[CONF_FLEX_ALIGN_MAIN]) - cross = literal(layout[CONF_FLEX_ALIGN_CROSS]) + cross = layout[CONF_FLEX_ALIGN_CROSS] + if cross == "LV_FLEX_ALIGN_STRETCH": + cross = "LV_FLEX_ALIGN_CENTER" + cross = literal(cross) track = literal(layout[CONF_FLEX_ALIGN_TRACK]) lv_obj.set_flex_align(w.obj, main, cross, track) parts = collect_parts(config) @@ -446,9 +449,11 @@ async def widget_to_code(w_cnfig, w_type: WidgetType, parent): if spec.is_compound(): var = cg.new_Pvariable(wid) lv_add(var.set_obj(creator)) + spec.on_create(var.obj, w_cnfig) else: var = lv_Pvariable(lv_obj_t, wid) lv_assign(var, creator) + spec.on_create(var, w_cnfig) w = Widget.create(wid, var, spec, w_cnfig) if theme := theme_widget_map.get(w_type): diff --git a/esphome/components/lvgl/widgets/canvas.py b/esphome/components/lvgl/widgets/canvas.py index f0a9cd35ba..ead352aa77 100644 --- a/esphome/components/lvgl/widgets/canvas.py +++ b/esphome/components/lvgl/widgets/canvas.py @@ -159,18 +159,15 @@ async def canvas_set_pixel(config, action_id, template_arg, args): ) -DRAW_SCHEMA = cv.Schema( - { - cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t), - cv.Required(CONF_X): pixels, - cv.Required(CONF_Y): pixels, - } -) -DRAW_OPA_SCHEMA = DRAW_SCHEMA.extend( - { - cv.Optional(CONF_OPA): opacity, - } -) +DRAW_SCHEMA = { + cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t), + cv.Required(CONF_X): pixels, + cv.Required(CONF_Y): pixels, +} +DRAW_OPA_SCHEMA = { + **DRAW_SCHEMA, + cv.Optional(CONF_OPA): opacity, +} async def draw_to_code(config, dsc_type, props, do_draw, action_id, template_arg, args): @@ -224,12 +221,14 @@ RECT_PROPS = { @automation.register_action( "lvgl.canvas.draw_rectangle", ObjUpdateAction, - DRAW_SCHEMA.extend( + cv.Schema( { + **DRAW_OPA_SCHEMA, cv.Required(CONF_WIDTH): cv.templatable(cv.int_), cv.Required(CONF_HEIGHT): cv.templatable(cv.int_), - }, - ).extend({cv.Optional(prop): STYLE_PROPS[prop] for prop in RECT_PROPS}), + **{cv.Optional(prop): STYLE_PROPS[prop] for prop in RECT_PROPS}, + } + ), ) async def canvas_draw_rect(config, action_id, template_arg, args): width = await pixels.process(config[CONF_WIDTH]) @@ -261,13 +260,14 @@ TEXT_PROPS = { @automation.register_action( "lvgl.canvas.draw_text", ObjUpdateAction, - TEXT_SCHEMA.extend(DRAW_OPA_SCHEMA) - .extend( + cv.Schema( { + **TEXT_SCHEMA, + **DRAW_OPA_SCHEMA, cv.Required(CONF_MAX_WIDTH): cv.templatable(cv.int_), + **{cv.Optional(prop): STYLE_PROPS[f"text_{prop}"] for prop in TEXT_PROPS}, }, - ) - .extend({cv.Optional(prop): STYLE_PROPS[f"text_{prop}"] for prop in TEXT_PROPS}), + ), ) async def canvas_draw_text(config, action_id, template_arg, args): text = await lv_text.process(config[CONF_TEXT]) @@ -293,13 +293,15 @@ IMG_PROPS = { @automation.register_action( "lvgl.canvas.draw_image", ObjUpdateAction, - DRAW_OPA_SCHEMA.extend( + cv.Schema( { + **DRAW_OPA_SCHEMA, cv.Required(CONF_SRC): lv_image, cv.Optional(CONF_PIVOT_X, default=0): pixels, cv.Optional(CONF_PIVOT_Y, default=0): pixels, - }, - ).extend({cv.Optional(prop): validator for prop, validator in IMG_PROPS.items()}), + **{cv.Optional(prop): validator for prop, validator in IMG_PROPS.items()}, + } + ), ) async def canvas_draw_image(config, action_id, template_arg, args): src = await lv_image.process(config[CONF_SRC]) @@ -336,8 +338,9 @@ LINE_PROPS = { cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t), cv.Optional(CONF_OPA): opacity, cv.Required(CONF_POINTS): cv.ensure_list(point_schema), - }, - ).extend({cv.Optional(prop): validator for prop, validator in LINE_PROPS.items()}), + **{cv.Optional(prop): validator for prop, validator in LINE_PROPS.items()}, + } + ), ) async def canvas_draw_line(config, action_id, template_arg, args): points = [ @@ -363,8 +366,9 @@ async def canvas_draw_line(config, action_id, template_arg, args): { cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t), cv.Required(CONF_POINTS): cv.ensure_list(point_schema), + **{cv.Optional(prop): STYLE_PROPS[prop] for prop in RECT_PROPS}, }, - ).extend({cv.Optional(prop): STYLE_PROPS[prop] for prop in RECT_PROPS}), + ), ) async def canvas_draw_polygon(config, action_id, template_arg, args): points = [ @@ -395,13 +399,15 @@ ARC_PROPS = { @automation.register_action( "lvgl.canvas.draw_arc", ObjUpdateAction, - DRAW_OPA_SCHEMA.extend( + cv.Schema( { + **DRAW_OPA_SCHEMA, cv.Required(CONF_RADIUS): pixels, cv.Required(CONF_START_ANGLE): lv_angle_degrees, cv.Required(CONF_END_ANGLE): lv_angle_degrees, + **{cv.Optional(prop): validator for prop, validator in ARC_PROPS.items()}, } - ).extend({cv.Optional(prop): validator for prop, validator in ARC_PROPS.items()}), + ), ) async def canvas_draw_arc(config, action_id, template_arg, args): radius = await size.process(config[CONF_RADIUS]) diff --git a/esphome/components/lvgl/widgets/checkbox.py b/esphome/components/lvgl/widgets/checkbox.py index c344fbfe75..ca97e2d843 100644 --- a/esphome/components/lvgl/widgets/checkbox.py +++ b/esphome/components/lvgl/widgets/checkbox.py @@ -17,11 +17,10 @@ class CheckboxType(WidgetType): CONF_CHECKBOX, LvBoolean("lv_checkbox_t"), (CONF_MAIN, CONF_INDICATOR), - TEXT_SCHEMA.extend( - { - Optional(CONF_PAD_COLUMN): padding, - } - ), + { + **TEXT_SCHEMA, + Optional(CONF_PAD_COLUMN): padding, + }, ) async def to_code(self, w: Widget, config): diff --git a/esphome/components/lvgl/widgets/container.py b/esphome/components/lvgl/widgets/container.py new file mode 100644 index 0000000000..2ac1a3b244 --- /dev/null +++ b/esphome/components/lvgl/widgets/container.py @@ -0,0 +1,39 @@ +import esphome.config_validation as cv +from esphome.const import CONF_HEIGHT, CONF_WIDTH +from esphome.cpp_generator import MockObj + +from ..defines import CONF_CONTAINER, CONF_MAIN, CONF_OBJ, CONF_SCROLLBAR +from ..lv_validation import size +from ..lvcode import lv +from ..types import WidgetType, lv_obj_t + +CONTAINER_SCHEMA = cv.Schema( + { + cv.Optional(CONF_HEIGHT, default="100%"): size, + cv.Optional(CONF_WIDTH, default="100%"): size, + } +) + + +class ContainerType(WidgetType): + """ + A simple container widget that can hold other widgets and which defaults to a 100% size. + Made from an obj with all styles removed + """ + + def __init__(self): + super().__init__( + CONF_CONTAINER, + lv_obj_t, + (CONF_MAIN, CONF_SCROLLBAR), + schema=CONTAINER_SCHEMA, + modify_schema={}, + lv_name=CONF_OBJ, + ) + self.styles = {} + + def on_create(self, var: MockObj, config: dict): + lv.obj_remove_style_all(var) + + +container_spec = ContainerType() diff --git a/esphome/components/lvgl/widgets/label.py b/esphome/components/lvgl/widgets/label.py index 6b04235674..3a3a997737 100644 --- a/esphome/components/lvgl/widgets/label.py +++ b/esphome/components/lvgl/widgets/label.py @@ -23,12 +23,11 @@ class LabelType(WidgetType): CONF_LABEL, LvText("lv_label_t"), (CONF_MAIN, CONF_SCROLLBAR, CONF_SELECTED), - TEXT_SCHEMA.extend( - { - cv.Optional(CONF_RECOLOR): lv_bool, - cv.Optional(CONF_LONG_MODE): LV_LONG_MODES.one_of, - } - ), + { + **TEXT_SCHEMA, + cv.Optional(CONF_RECOLOR): lv_bool, + cv.Optional(CONF_LONG_MODE): LV_LONG_MODES.one_of, + }, ) async def to_code(self, w: Widget, config): diff --git a/esphome/components/lvgl/widgets/qrcode.py b/esphome/components/lvgl/widgets/qrcode.py index 028a81b449..ad46f67c6b 100644 --- a/esphome/components/lvgl/widgets/qrcode.py +++ b/esphome/components/lvgl/widgets/qrcode.py @@ -14,13 +14,12 @@ CONF_QRCODE = "qrcode" CONF_DARK_COLOR = "dark_color" CONF_LIGHT_COLOR = "light_color" -QRCODE_SCHEMA = TEXT_SCHEMA.extend( - { - cv.Optional(CONF_DARK_COLOR, default="black"): lv_color, - cv.Optional(CONF_LIGHT_COLOR, default="white"): lv_color, - cv.Required(CONF_SIZE): cv.int_, - } -) +QRCODE_SCHEMA = { + **TEXT_SCHEMA, + cv.Optional(CONF_DARK_COLOR, default="black"): lv_color, + cv.Optional(CONF_LIGHT_COLOR, default="white"): lv_color, + cv.Required(CONF_SIZE): cv.int_, +} class QrCodeType(WidgetType): diff --git a/esphome/components/lvgl/widgets/textarea.py b/esphome/components/lvgl/widgets/textarea.py index 23d50b3894..e5ab884685 100644 --- a/esphome/components/lvgl/widgets/textarea.py +++ b/esphome/components/lvgl/widgets/textarea.py @@ -21,15 +21,14 @@ CONF_TEXTAREA = "textarea" lv_textarea_t = LvText("lv_textarea_t") -TEXTAREA_SCHEMA = TEXT_SCHEMA.extend( - { - cv.Optional(CONF_PLACEHOLDER_TEXT): lv_text, - cv.Optional(CONF_ACCEPTED_CHARS): lv_text, - cv.Optional(CONF_ONE_LINE): lv_bool, - cv.Optional(CONF_PASSWORD_MODE): lv_bool, - cv.Optional(CONF_MAX_LENGTH): lv_int, - } -) +TEXTAREA_SCHEMA = { + **TEXT_SCHEMA, + cv.Optional(CONF_PLACEHOLDER_TEXT): lv_text, + cv.Optional(CONF_ACCEPTED_CHARS): lv_text, + cv.Optional(CONF_ONE_LINE): lv_bool, + cv.Optional(CONF_PASSWORD_MODE): lv_bool, + cv.Optional(CONF_MAX_LENGTH): lv_int, +} class TextareaType(WidgetType): diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 14241a1669..ca669c16e4 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -113,9 +113,10 @@ lvgl: title: Messagebox bg_color: 0xffff widgets: - - label: - text: Hello Msgbox - id: msgbox_label + # Test single widget without list + label: + text: Hello Msgbox + id: msgbox_label body: text: This is a sample messagebox bg_color: 0x808080 @@ -281,7 +282,7 @@ lvgl: #endif return std::string(buf); align: top_left - - obj: + - container: align: center arc_opa: COVER arc_color: 0xFF0000 @@ -414,6 +415,7 @@ lvgl: - buttons: - id: button_e - button: + layout: 2x1 id: button_button width: 20% height: 10% @@ -430,8 +432,13 @@ lvgl: checked: bg_color: 0x000000 widgets: - - label: - text: Button + # Test parse a dict instead of list + label: + text: Button + align: bottom_right + image: + src: cat_image + align: top_left on_click: - lvgl.widget.focus: spin_up - lvgl.widget.focus: next @@ -539,6 +546,7 @@ lvgl: - logger.log: "tile 1 is now showing" tiles: - id: tile_1 + layout: vertical row: 0 column: 0 dir: ALL @@ -554,6 +562,7 @@ lvgl: bg_color: 0x000000 - id: page2 + layout: vertical widgets: - canvas: id: canvas_id @@ -1005,6 +1014,7 @@ lvgl: r_mod: -20 opa: 0% - id: page3 + layout: horizontal widgets: - keyboard: id: lv_keyboard From 3e086c2127eb51ab4c7af219cb75be5fff00bd76 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:43:27 +1000 Subject: [PATCH 0079/1145] [lvgl] Fix rotation with unusual width (#11680) --- esphome/components/const/__init__.py | 1 + esphome/components/lvgl/lvgl_esphome.cpp | 24 ++++++++++--------- .../components/lvgl/widgets/buttonmatrix.py | 3 ++- esphome/components/matrix_keypad/__init__.py | 3 ++- esphome/const.py | 1 - 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/esphome/components/const/__init__.py b/esphome/components/const/__init__.py index 19924f0da7..2b88bb43a8 100644 --- a/esphome/components/const/__init__.py +++ b/esphome/components/const/__init__.py @@ -11,4 +11,5 @@ CONF_DRAW_ROUNDING = "draw_rounding" CONF_ON_RECEIVE = "on_receive" CONF_ON_STATE_CHANGE = "on_state_change" CONF_REQUEST_HEADERS = "request_headers" +CONF_ROWS = "rows" CONF_USE_PSRAM = "use_psram" diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 947342089c..05005b0217 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -171,6 +171,7 @@ bool LvPageType::is_showing() const { return this->parent_->get_current_page() = void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) { auto width = lv_area_get_width(area); auto height = lv_area_get_height(area); + auto height_rounded = (height + this->draw_rounding - 1) / this->draw_rounding * this->draw_rounding; auto x1 = area->x1; auto y1 = area->y1; lv_color_t *dst = this->rotate_buf_; @@ -178,13 +179,13 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) { case display::DISPLAY_ROTATION_90_DEGREES: for (lv_coord_t x = height; x-- != 0;) { for (lv_coord_t y = 0; y != width; y++) { - dst[y * height + x] = *ptr++; + dst[y * height_rounded + x] = *ptr++; } } y1 = x1; x1 = this->disp_drv_.ver_res - area->y1 - height; - width = height; - height = lv_area_get_width(area); + height = width; + width = height_rounded; break; case display::DISPLAY_ROTATION_180_DEGREES: @@ -200,13 +201,13 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) { case display::DISPLAY_ROTATION_270_DEGREES: for (lv_coord_t x = 0; x != height; x++) { for (lv_coord_t y = width; y-- != 0;) { - dst[y * height + x] = *ptr++; + dst[y * height_rounded + x] = *ptr++; } } x1 = y1; y1 = this->disp_drv_.hor_res - area->x1 - width; - width = height; - height = lv_area_get_width(area); + height = width; + width = height_rounded; break; default: @@ -443,8 +444,10 @@ LvglComponent::LvglComponent(std::vector displays, float buf void LvglComponent::setup() { auto *display = this->displays_[0]; - auto width = display->get_width(); - auto height = display->get_height(); + auto rounding = this->draw_rounding; + // cater for displays with dimensions that don't divide by the required rounding + auto width = (display->get_width() + rounding - 1) / rounding * rounding; + auto height = (display->get_height() + rounding - 1) / rounding * rounding; auto frac = this->buffer_frac_; if (frac == 0) frac = 1; @@ -469,9 +472,8 @@ void LvglComponent::setup() { } this->buffer_frac_ = frac; lv_disp_draw_buf_init(&this->draw_buf_, buffer, nullptr, buffer_pixels); - this->disp_drv_.hor_res = width; - this->disp_drv_.ver_res = height; - // this->setup_driver_(display->get_width(), display->get_height()); + this->disp_drv_.hor_res = display->get_width(); + this->disp_drv_.ver_res = display->get_height(); lv_disp_drv_update(this->disp_, &this->disp_drv_); this->rotation = display->get_rotation(); if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) { diff --git a/esphome/components/lvgl/widgets/buttonmatrix.py b/esphome/components/lvgl/widgets/buttonmatrix.py index baeb1c8e3e..fe421aa477 100644 --- a/esphome/components/lvgl/widgets/buttonmatrix.py +++ b/esphome/components/lvgl/widgets/buttonmatrix.py @@ -1,8 +1,9 @@ from esphome import automation import esphome.codegen as cg +from esphome.components.const import CONF_ROWS from esphome.components.key_provider import KeyProvider import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_ITEMS, CONF_ROWS, CONF_TEXT, CONF_WIDTH +from esphome.const import CONF_ID, CONF_ITEMS, CONF_TEXT, CONF_WIDTH from esphome.cpp_generator import MockObj from ..automation import action_to_code diff --git a/esphome/components/matrix_keypad/__init__.py b/esphome/components/matrix_keypad/__init__.py index 2e123323a0..868b149211 100644 --- a/esphome/components/matrix_keypad/__init__.py +++ b/esphome/components/matrix_keypad/__init__.py @@ -1,8 +1,9 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import key_provider +from esphome.components.const import CONF_ROWS import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_ROWS, CONF_TRIGGER_ID +from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_TRIGGER_ID CODEOWNERS = ["@ssieb"] diff --git a/esphome/const.py b/esphome/const.py index 3bbc6b8b3f..d0d94ed283 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -838,7 +838,6 @@ CONF_RMT_CHANNEL = "rmt_channel" CONF_RMT_SYMBOLS = "rmt_symbols" CONF_ROTATION = "rotation" CONF_ROW = "row" -CONF_ROWS = "rows" CONF_RS_PIN = "rs_pin" CONF_RTD_NOMINAL_RESISTANCE = "rtd_nominal_resistance" CONF_RTD_WIRES = "rtd_wires" From 525790049587e58a2cc72dd06b59ba6be350f019 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Nov 2025 21:45:20 -0600 Subject: [PATCH 0080/1145] [mqtt] Add wake_loop_threadsafe() for low-latency event processing on ESP32 (#11695) --- esphome/components/mqtt/__init__.py | 12 +++++++++--- esphome/components/mqtt/mqtt_backend_esp32.cpp | 5 +++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 641c70a367..1fc0c30db1 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -3,7 +3,7 @@ import re from esphome import automation from esphome.automation import Condition import esphome.codegen as cg -from esphome.components import logger +from esphome.components import logger, socket from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv @@ -66,6 +66,9 @@ DEPENDENCIES = ["network"] def AUTO_LOAD(): if CORE.is_esp8266 or CORE.is_libretiny: return ["async_tcp", "json"] + # ESP32 needs socket for wake_loop_threadsafe() + if CORE.is_esp32: + return ["json", "socket"] return ["json"] @@ -213,8 +216,6 @@ def validate_fingerprint(value): def _consume_mqtt_sockets(config: ConfigType) -> ConfigType: """Register socket needs for MQTT component.""" - from esphome.components import socket - # MQTT needs 1 socket for the broker connection socket.consume_sockets(1, "mqtt")(config) return config @@ -341,6 +342,11 @@ async def to_code(config): # https://github.com/heman/async-mqtt-client/blob/master/library.json cg.add_library("heman/AsyncMqttClient-esphome", "2.0.0") + # MQTT on ESP32 uses wake_loop_threadsafe() to wake the main loop from the MQTT event handler + # This enables low-latency MQTT event processing instead of waiting for select() timeout + if CORE.is_esp32: + socket.require_wake_loop_threadsafe() + cg.add_define("USE_MQTT") cg.add_global(mqtt_ns.using) diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index 623206a0cd..dcc51ed60e 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -190,6 +190,11 @@ void MQTTBackendESP32::mqtt_event_handler(void *handler_args, esp_event_base_t b if (instance) { auto event = *static_cast(event_data); instance->mqtt_events_.emplace(event); + + // Wake main loop immediately to process MQTT event instead of waiting for select() timeout +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + App.wake_loop_threadsafe(); +#endif } } From 4c31cb57eaf1d1411c071ef74c190fffdb25be04 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Nov 2025 21:45:57 -0600 Subject: [PATCH 0081/1145] [espnow] Add wake_loop_threadsafe() for low-latency event processing (#11696) --- esphome/components/espnow/__init__.py | 7 ++++++- esphome/components/espnow/espnow_component.cpp | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/esphome/components/espnow/__init__.py b/esphome/components/espnow/__init__.py index 9d2f17440c..cc2c02d4c0 100644 --- a/esphome/components/espnow/__init__.py +++ b/esphome/components/espnow/__init__.py @@ -1,6 +1,6 @@ from esphome import automation, core import esphome.codegen as cg -from esphome.components import wifi +from esphome.components import socket, wifi from esphome.components.udp import CONF_ON_RECEIVE import esphome.config_validation as cv from esphome.const import ( @@ -17,6 +17,7 @@ from esphome.core import CORE, HexInt from esphome.types import ConfigType CODEOWNERS = ["@jesserockz"] +AUTO_LOAD = ["socket"] byte_vector = cg.std_vector.template(cg.uint8) peer_address_t = cg.std_ns.class_("array").template(cg.uint8, 6) @@ -120,6 +121,10 @@ async def to_code(config): if CORE.using_arduino: cg.add_library("WiFi", None) + # ESP-NOW uses wake_loop_threadsafe() to wake the main loop from ESP-NOW callbacks + # This enables low-latency event processing instead of waiting for select() timeout + socket.require_wake_loop_threadsafe() + cg.add_define("USE_ESPNOW") if wifi_channel := config.get(CONF_CHANNEL): cg.add(var.set_wifi_channel(wifi_channel)) diff --git a/esphome/components/espnow/espnow_component.cpp b/esphome/components/espnow/espnow_component.cpp index b0d5938dba..d2f136d1c7 100644 --- a/esphome/components/espnow/espnow_component.cpp +++ b/esphome/components/espnow/espnow_component.cpp @@ -4,6 +4,7 @@ #include "espnow_err.h" +#include "esphome/core/application.h" #include "esphome/core/defines.h" #include "esphome/core/log.h" @@ -97,6 +98,11 @@ void on_send_report(const uint8_t *mac_addr, esp_now_send_status_t status) // Push the packet to the queue global_esp_now->receive_packet_queue_.push(packet); // Push always because we're the only producer and the pool ensures we never exceed queue size + + // Wake main loop immediately to process ESP-NOW send event instead of waiting for select() timeout +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + App.wake_loop_threadsafe(); +#endif } void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) { @@ -114,6 +120,11 @@ void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int // Push the packet to the queue global_esp_now->receive_packet_queue_.push(packet); // Push always because we're the only producer and the pool ensures we never exceed queue size + + // Wake main loop immediately to process ESP-NOW receive event instead of waiting for select() timeout +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + App.wake_loop_threadsafe(); +#endif } ESPNowComponent::ESPNowComponent() { global_esp_now = this; } From 4d2f9db8617d542021ed20aa2fea993e8ffeb384 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Nov 2025 21:46:34 -0600 Subject: [PATCH 0082/1145] [esp32_ble] Remove leftover lwip/sockets.h include (#11702) --- esphome/components/esp32_ble/ble.cpp | 4 ---- esphome/components/esp32_ble/ble.h | 4 ---- 2 files changed, 8 deletions(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index fc26a7fc21..8bbb21e3ca 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -27,10 +27,6 @@ extern "C" { #include #endif -#ifdef USE_SOCKET_SELECT_SUPPORT -#include -#endif - namespace esphome::esp32_ble { static const char *const TAG = "esp32_ble"; diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 3be6a7048d..c3e1ec2ce6 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -25,10 +25,6 @@ #include #include -#ifdef USE_SOCKET_SELECT_SUPPORT -#include -#endif - namespace esphome::esp32_ble { // Maximum size of the BLE event queue From 980098ca77dfe249da4d45943ddf5488d4532ae2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Nov 2025 21:47:11 -0600 Subject: [PATCH 0083/1145] [ci] Fix non-component files incorrectly detected as components (#11701) --- script/helpers.py | 6 +++++- tests/script/test_helpers.py | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/script/helpers.py b/script/helpers.py index 447d54fa54..33f95d6f8a 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -101,7 +101,11 @@ def get_component_from_path(file_path: str) -> str | None: ): parts = file_path.split("/") if len(parts) >= 3 and parts[2]: - return parts[2] + # Verify that parts[2] is actually a component directory, not a file + # like .gitignore or README.md in the components directory itself + component_name = parts[2] + if "." not in component_name: + return component_name return None diff --git a/tests/script/test_helpers.py b/tests/script/test_helpers.py index 1046512a14..5eb55c0722 100644 --- a/tests/script/test_helpers.py +++ b/tests/script/test_helpers.py @@ -1093,6 +1093,11 @@ def test_parse_list_components_output(output: str, expected: list[str]) -> None: ("tests/components/", None), # No component name ("esphome/components", None), # No trailing slash ("tests/components", None), # No trailing slash + # Files in component directories that are not components + ("tests/components/.gitignore", None), # Hidden file + ("tests/components/README.md", None), # Documentation file + ("esphome/components/__init__.py", None), # Python init file + ("tests/components/main.cpp", None), # File with extension ], ) def test_get_component_from_path( From 060bb4159f6c638d3ce03a1e3169607a751ec1d1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Nov 2025 22:38:57 -0600 Subject: [PATCH 0084/1145] [ci] Cache component dependency graph for up to 3.4x faster determine-jobs (#11648) --- .github/workflows/ci.yml | 11 ++ script/helpers.py | 71 +++++++- tests/script/test_determine_jobs.py | 2 + tests/script/test_helpers.py | 260 ++++++++++++++++++++++++++++ 4 files changed, 341 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1756d5b765..16837b3186 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -192,6 +192,11 @@ jobs: with: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} + - name: Restore components graph cache + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: .temp/components_graph.json + key: components-graph-${{ hashFiles('esphome/components/**/*.py') }} - name: Determine which tests to run id: determine env: @@ -216,6 +221,12 @@ jobs: echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT + - name: Save components graph cache + if: github.ref == 'refs/heads/dev' + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: .temp/components_graph.json + key: components-graph-${{ hashFiles('esphome/components/**/*.py') }} integration-tests: name: Run integration tests diff --git a/script/helpers.py b/script/helpers.py index 33f95d6f8a..5b2fe6cd06 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -2,6 +2,7 @@ from __future__ import annotations from collections.abc import Callable from functools import cache +import hashlib import json import os import os.path @@ -52,6 +53,10 @@ BASE_BUS_COMPONENTS = { "remote_receiver", } +# Cache version for components graph +# Increment this when the cache format or graph building logic changes +COMPONENTS_GRAPH_CACHE_VERSION = 1 + def parse_list_components_output(output: str) -> list[str]: """Parse the output from list-components.py script. @@ -756,20 +761,71 @@ def resolve_auto_load( return auto_load() +@cache +def get_components_graph_cache_key() -> str: + """Generate cache key based on all component Python file hashes. + + Uses git ls-files with sha1 hashes to generate a stable cache key that works + across different machines and CI runs. This is faster and more reliable than + reading file contents or using modification times. + + Returns: + SHA256 hex string uniquely identifying the current component state + """ + + # Use git ls-files -s to get sha1 hashes of all component Python files + # Format: + # This is fast and works consistently across CI and local dev + # We hash all .py files because AUTO_LOAD, DEPENDENCIES, etc. can be defined + # in any Python file, not just __init__.py + cmd = ["git", "ls-files", "-s", "esphome/components/**/*.py"] + result = subprocess.run( + cmd, capture_output=True, text=True, check=True, cwd=root_path, close_fds=False + ) + + # Hash the git output (includes file paths and their sha1 hashes) + # This changes only when component Python files actually change + hasher = hashlib.sha256() + hasher.update(result.stdout.encode()) + + return hasher.hexdigest() + + def create_components_graph() -> dict[str, list[str]]: - """Create a graph of component dependencies. + """Create a graph of component dependencies (cached). + + This function is expensive (5-6 seconds) because it imports all ESPHome components + to extract their DEPENDENCIES and AUTO_LOAD metadata. The result is cached based + on component file modification times, so unchanged components don't trigger a rebuild. Returns: Dictionary mapping parent components to their children (dependencies) """ - from pathlib import Path + # Check cache first - use fixed filename since GitHub Actions cache doesn't support wildcards + cache_file = Path(temp_folder) / "components_graph.json" + + if cache_file.exists(): + try: + cached_data = json.loads(cache_file.read_text()) + except (OSError, json.JSONDecodeError): + # Cache file corrupted or unreadable, rebuild + pass + else: + # Verify cache version matches + if cached_data.get("_version") == COMPONENTS_GRAPH_CACHE_VERSION: + # Verify cache is for current component state + cache_key = get_components_graph_cache_key() + if cached_data.get("_cache_key") == cache_key: + return cached_data.get("graph", {}) + # Cache key mismatch - stale cache, rebuild + # Cache version mismatch - incompatible format, rebuild from esphome import const from esphome.core import CORE from esphome.loader import ComponentManifest, get_component, get_platform # The root directory of the repo - root = Path(__file__).parent.parent + root = Path(root_path) components_dir = root / ESPHOME_COMPONENTS_PATH # Fake some directory so that get_component works CORE.config_path = root @@ -846,6 +902,15 @@ def create_components_graph() -> dict[str, list[str]]: # restore config CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0] + # Save to cache with version and cache key for validation + cache_data = { + "_version": COMPONENTS_GRAPH_CACHE_VERSION, + "_cache_key": get_components_graph_cache_key(), + "graph": components_graph, + } + cache_file.parent.mkdir(exist_ok=True) + cache_file.write_text(json.dumps(cache_data)) + return components_graph diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py index a33eca5b19..e084e2e398 100644 --- a/tests/script/test_determine_jobs.py +++ b/tests/script/test_determine_jobs.py @@ -543,6 +543,7 @@ def test_main_filters_components_without_tests( with ( patch.object(determine_jobs, "root_path", str(tmp_path)), patch.object(helpers, "root_path", str(tmp_path)), + patch.object(helpers, "create_components_graph", return_value={}), patch("sys.argv", ["determine-jobs.py"]), patch.object( determine_jobs, @@ -640,6 +641,7 @@ def test_main_detects_components_with_variant_tests( with ( patch.object(determine_jobs, "root_path", str(tmp_path)), patch.object(helpers, "root_path", str(tmp_path)), + patch.object(helpers, "create_components_graph", return_value={}), patch("sys.argv", ["determine-jobs.py"]), patch.object( determine_jobs, diff --git a/tests/script/test_helpers.py b/tests/script/test_helpers.py index 5eb55c0722..1bfffef51c 100644 --- a/tests/script/test_helpers.py +++ b/tests/script/test_helpers.py @@ -1,5 +1,6 @@ """Unit tests for script/helpers.py module.""" +from collections.abc import Generator import json import os from pathlib import Path @@ -1106,3 +1107,262 @@ def test_get_component_from_path( """Test extraction of component names from file paths.""" result = helpers.get_component_from_path(file_path) assert result == expected_component + + +# Components graph cache tests + + +@pytest.fixture +def mock_git_output() -> str: + """Fixture for mock git ls-files output with realistic component files. + + Includes examples of AUTO_LOAD in sensor.py and binary_sensor.py files, + which is why we need to hash all .py files, not just __init__.py. + """ + return ( + "100644 abc123... 0 esphome/components/wifi/__init__.py\n" + "100644 def456... 0 esphome/components/api/__init__.py\n" + "100644 ghi789... 0 esphome/components/xiaomi_lywsd03mmc/__init__.py\n" + "100644 jkl012... 0 esphome/components/xiaomi_lywsd03mmc/sensor.py\n" + "100644 mno345... 0 esphome/components/xiaomi_cgpr1/__init__.py\n" + "100644 pqr678... 0 esphome/components/xiaomi_cgpr1/binary_sensor.py\n" + ) + + +@pytest.fixture +def mock_cache_file(tmp_path: Path) -> Path: + """Fixture for a temporary cache file path.""" + return tmp_path / "components_graph.json" + + +@pytest.fixture(autouse=True) +def clear_cache_key_cache() -> None: + """Clear the components graph cache key cache before each test.""" + helpers.get_components_graph_cache_key.cache_clear() + + +@pytest.fixture +def mock_subprocess_run() -> Generator[Mock, None, None]: + """Fixture to mock subprocess.run for git commands.""" + with patch("subprocess.run") as mock_run: + yield mock_run + + +def test_cache_key_generation(mock_git_output: str, mock_subprocess_run: Mock) -> None: + """Test that cache key is generated based on git file hashes.""" + mock_result = Mock() + mock_result.stdout = mock_git_output + mock_subprocess_run.return_value = mock_result + + key = helpers.get_components_graph_cache_key() + + # Should be a 64-character hex string (SHA256) + assert len(key) == 64 + assert all(c in "0123456789abcdef" for c in key) + + +def test_cache_key_consistent_for_same_files( + mock_git_output: str, mock_subprocess_run: Mock +) -> None: + """Test that same git output produces same cache key.""" + mock_result = Mock() + mock_result.stdout = mock_git_output + mock_subprocess_run.return_value = mock_result + + key1 = helpers.get_components_graph_cache_key() + key2 = helpers.get_components_graph_cache_key() + + assert key1 == key2 + + +def test_cache_key_different_for_changed_files(mock_subprocess_run: Mock) -> None: + """Test that different git output produces different cache key. + + This test demonstrates that changes to any .py file (not just __init__.py) + will invalidate the cache, which is important because AUTO_LOAD can be + defined in sensor.py, binary_sensor.py, etc. + """ + mock_result1 = Mock() + mock_result1.stdout = ( + "100644 abc123... 0 esphome/components/xiaomi_lywsd03mmc/sensor.py\n" + ) + + mock_result2 = Mock() + # Same file, different hash - simulates a change to AUTO_LOAD + mock_result2.stdout = ( + "100644 xyz789... 0 esphome/components/xiaomi_lywsd03mmc/sensor.py\n" + ) + + mock_subprocess_run.return_value = mock_result1 + key1 = helpers.get_components_graph_cache_key() + + helpers.get_components_graph_cache_key.cache_clear() + mock_subprocess_run.return_value = mock_result2 + key2 = helpers.get_components_graph_cache_key() + + assert key1 != key2 + + +def test_cache_key_uses_git_ls_files( + mock_git_output: str, mock_subprocess_run: Mock +) -> None: + """Test that git ls-files command is called correctly.""" + mock_result = Mock() + mock_result.stdout = mock_git_output + mock_subprocess_run.return_value = mock_result + + helpers.get_components_graph_cache_key() + + # Verify git ls-files was called with correct arguments + mock_subprocess_run.assert_called_once() + call_args = mock_subprocess_run.call_args + assert call_args[0][0] == [ + "git", + "ls-files", + "-s", + "esphome/components/**/*.py", + ] + assert call_args[1]["capture_output"] is True + assert call_args[1]["text"] is True + assert call_args[1]["check"] is True + assert call_args[1]["close_fds"] is False + + +def test_cache_hit_returns_cached_graph( + tmp_path: Path, mock_git_output: str, mock_subprocess_run: Mock +) -> None: + """Test that cache hit returns cached data without rebuilding.""" + mock_graph = {"wifi": ["network"], "api": ["socket"]} + cache_key = "a" * 64 + cache_data = { + "_version": helpers.COMPONENTS_GRAPH_CACHE_VERSION, + "_cache_key": cache_key, + "graph": mock_graph, + } + + # Write cache file + cache_file = tmp_path / "components_graph.json" + cache_file.write_text(json.dumps(cache_data)) + + mock_result = Mock() + mock_result.stdout = mock_git_output + mock_subprocess_run.return_value = mock_result + + with ( + patch("helpers.get_components_graph_cache_key", return_value=cache_key), + patch("helpers.temp_folder", str(tmp_path)), + ): + result = helpers.create_components_graph() + assert result == mock_graph + + +def test_cache_miss_no_cache_file( + tmp_path: Path, mock_git_output: str, mock_subprocess_run: Mock +) -> None: + """Test that cache miss rebuilds graph when no cache file exists.""" + mock_result = Mock() + mock_result.stdout = mock_git_output + mock_subprocess_run.return_value = mock_result + + # Create minimal components directory structure + components_dir = tmp_path / "esphome" / "components" + components_dir.mkdir(parents=True) + + with ( + patch("helpers.root_path", str(tmp_path)), + patch("helpers.temp_folder", str(tmp_path / ".temp")), + patch("helpers.get_components_graph_cache_key", return_value="test_key"), + ): + result = helpers.create_components_graph() + # Should return empty graph for empty components directory + assert result == {} + + +def test_cache_miss_version_mismatch( + tmp_path: Path, mock_git_output: str, mock_subprocess_run: Mock +) -> None: + """Test that cache miss rebuilds graph when version doesn't match.""" + cache_data = { + "_version": 999, # Wrong version + "_cache_key": "test_key", + "graph": {"old": ["data"]}, + } + + cache_file = tmp_path / ".temp" / "components_graph.json" + cache_file.parent.mkdir(parents=True) + cache_file.write_text(json.dumps(cache_data)) + + mock_result = Mock() + mock_result.stdout = mock_git_output + mock_subprocess_run.return_value = mock_result + + # Create minimal components directory structure + components_dir = tmp_path / "esphome" / "components" + components_dir.mkdir(parents=True) + + with ( + patch("helpers.root_path", str(tmp_path)), + patch("helpers.temp_folder", str(tmp_path / ".temp")), + patch("helpers.get_components_graph_cache_key", return_value="test_key"), + ): + result = helpers.create_components_graph() + # Should rebuild and return empty graph, not use cached data + assert result == {} + + +def test_cache_miss_key_mismatch( + tmp_path: Path, mock_git_output: str, mock_subprocess_run: Mock +) -> None: + """Test that cache miss rebuilds graph when cache key doesn't match.""" + cache_data = { + "_version": helpers.COMPONENTS_GRAPH_CACHE_VERSION, + "_cache_key": "old_key", + "graph": {"old": ["data"]}, + } + + cache_file = tmp_path / ".temp" / "components_graph.json" + cache_file.parent.mkdir(parents=True) + cache_file.write_text(json.dumps(cache_data)) + + mock_result = Mock() + mock_result.stdout = mock_git_output + mock_subprocess_run.return_value = mock_result + + # Create minimal components directory structure + components_dir = tmp_path / "esphome" / "components" + components_dir.mkdir(parents=True) + + with ( + patch("helpers.root_path", str(tmp_path)), + patch("helpers.temp_folder", str(tmp_path / ".temp")), + patch("helpers.get_components_graph_cache_key", return_value="new_key"), + ): + result = helpers.create_components_graph() + # Should rebuild and return empty graph, not use cached data with old key + assert result == {} + + +def test_cache_miss_corrupted_json( + tmp_path: Path, mock_git_output: str, mock_subprocess_run: Mock +) -> None: + """Test that cache miss rebuilds graph when cache file has invalid JSON.""" + cache_file = tmp_path / ".temp" / "components_graph.json" + cache_file.parent.mkdir(parents=True) + cache_file.write_text("{invalid json") + + mock_result = Mock() + mock_result.stdout = mock_git_output + mock_subprocess_run.return_value = mock_result + + # Create minimal components directory structure + components_dir = tmp_path / "esphome" / "components" + components_dir.mkdir(parents=True) + + with ( + patch("helpers.root_path", str(tmp_path)), + patch("helpers.temp_folder", str(tmp_path / ".temp")), + patch("helpers.get_components_graph_cache_key", return_value="test_key"), + ): + result = helpers.create_components_graph() + # Should handle corruption gracefully and rebuild + assert result == {} From 13e3c03a6144ea215612640e43784080af5634f1 Mon Sep 17 00:00:00 2001 From: leejoow Date: Tue, 4 Nov 2025 07:30:53 +0100 Subject: [PATCH 0085/1145] [dallas_temp] add support for index (#11346) --- esphome/components/dallas_temp/dallas_temp.cpp | 2 +- esphome/components/one_wire/__init__.py | 7 +++++-- esphome/components/one_wire/one_wire.cpp | 12 +++++++++++- esphome/components/one_wire/one_wire.h | 7 ++++++- tests/components/dallas_temp/common.yaml | 3 +++ 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/esphome/components/dallas_temp/dallas_temp.cpp b/esphome/components/dallas_temp/dallas_temp.cpp index a518c96489..a3969e081e 100644 --- a/esphome/components/dallas_temp/dallas_temp.cpp +++ b/esphome/components/dallas_temp/dallas_temp.cpp @@ -70,7 +70,7 @@ bool DallasTemperatureSensor::read_scratch_pad_() { } void DallasTemperatureSensor::setup() { - if (!this->check_address_()) + if (!this->check_address_or_index_()) return; if (!this->read_scratch_pad_()) return; diff --git a/esphome/components/one_wire/__init__.py b/esphome/components/one_wire/__init__.py index 6d95b8fd33..e12cca3e27 100644 --- a/esphome/components/one_wire/__init__.py +++ b/esphome/components/one_wire/__init__.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ADDRESS +from esphome.const import CONF_ADDRESS, CONF_INDEX CODEOWNERS = ["@ssieb"] @@ -21,7 +21,8 @@ def one_wire_device_schema(): return cv.Schema( { cv.GenerateID(CONF_ONE_WIRE_ID): cv.use_id(OneWireBus), - cv.Optional(CONF_ADDRESS): cv.hex_uint64_t, + cv.Exclusive(CONF_ADDRESS, "index_or_address"): cv.hex_uint64_t, + cv.Exclusive(CONF_INDEX, "index_or_address"): cv.uint8_t, } ) @@ -37,3 +38,5 @@ async def register_one_wire_device(var, config): cg.add(var.set_one_wire_bus(parent)) if (address := config.get(CONF_ADDRESS)) is not None: cg.add(var.set_address(address)) + if (index := config.get(CONF_INDEX)) is not None: + cg.add(var.set_index(index)) diff --git a/esphome/components/one_wire/one_wire.cpp b/esphome/components/one_wire/one_wire.cpp index 96e6145f63..fd139d0ddc 100644 --- a/esphome/components/one_wire/one_wire.cpp +++ b/esphome/components/one_wire/one_wire.cpp @@ -18,10 +18,20 @@ bool OneWireDevice::send_command_(uint8_t cmd) { return true; } -bool OneWireDevice::check_address_() { +bool OneWireDevice::check_address_or_index_() { if (this->address_ != 0) return true; auto devices = this->bus_->get_devices(); + + if (this->index_ != INDEX_NOT_SET) { + if (this->index_ >= devices.size()) { + ESP_LOGE(TAG, "Index %d out of range, only %d devices found", this->index_, devices.size()); + return false; + } + this->address_ = devices[this->index_]; + return true; + } + if (devices.empty()) { ESP_LOGE(TAG, "No devices, can't auto-select address"); return false; diff --git a/esphome/components/one_wire/one_wire.h b/esphome/components/one_wire/one_wire.h index e83c6e81e8..f6a956a92c 100644 --- a/esphome/components/one_wire/one_wire.h +++ b/esphome/components/one_wire/one_wire.h @@ -17,6 +17,8 @@ class OneWireDevice { /// @param address of the device void set_address(uint64_t address) { this->address_ = address; } + void set_index(uint8_t index) { this->index_ = index; } + /// @brief store the pointer to the OneWireBus to use /// @param bus pointer to the OneWireBus object void set_one_wire_bus(OneWireBus *bus) { this->bus_ = bus; } @@ -25,13 +27,16 @@ class OneWireDevice { const std::string &get_address_name(); protected: + static constexpr uint8_t INDEX_NOT_SET = 255; + uint64_t address_{0}; + uint8_t index_{INDEX_NOT_SET}; OneWireBus *bus_{nullptr}; ///< pointer to OneWireBus instance std::string address_name_; /// @brief find an address if necessary /// should be called from setup - bool check_address_(); + bool check_address_or_index_(); /// @brief send command on the bus /// @param cmd command to send diff --git a/tests/components/dallas_temp/common.yaml b/tests/components/dallas_temp/common.yaml index 6d865d8e93..abd8e0cfa3 100644 --- a/tests/components/dallas_temp/common.yaml +++ b/tests/components/dallas_temp/common.yaml @@ -9,3 +9,6 @@ sensor: resolution: 9 - platform: dallas_temp name: Dallas Temperature 2 + - platform: dallas_temp + name: Dallas Temperature 3 + index: 2 From 84f7cacef95ae53bc1a993914453abffe35406e6 Mon Sep 17 00:00:00 2001 From: Chaser Huang Date: Tue, 4 Nov 2025 10:41:30 -0500 Subject: [PATCH 0086/1145] [sgp30] Fix reading from preexisting stored baseline even with `store_baseline:false` (#7922) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/sgp30/sgp30.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 0e3aeff812..9e8d6b332c 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -78,7 +78,7 @@ void SGP30Component::setup() { uint32_t hash = fnv1_hash(App.get_compilation_time() + std::to_string(this->serial_number_)); this->pref_ = global_preferences->make_preference(hash, true); - if (this->pref_.load(&this->baselines_storage_)) { + if (this->store_baseline_ && this->pref_.load(&this->baselines_storage_)) { ESP_LOGI(TAG, "Loaded eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", this->baselines_storage_.eco2, baselines_storage_.tvoc); this->eco2_baseline_ = this->baselines_storage_.eco2; From 71fa88c9d41efe5a3fbf179e28b8a2aa2584e9a2 Mon Sep 17 00:00:00 2001 From: Cameron Steel Date: Wed, 5 Nov 2025 03:32:23 +1100 Subject: [PATCH 0087/1145] =?UTF-8?q?[max7219digit]=20support=20`flip=5Fx`?= =?UTF-8?q?=20when=20`rotate=5Fchip`=20is=2090=C2=B0=20or=20270=C2=B0=20(#?= =?UTF-8?q?6109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/max7219digit/max7219digit.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index 6df3c4d7c8..cdceafad50 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -271,7 +271,11 @@ void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) { } } } else if (this->orientation_ == 1) { - b = pixels[col]; + if (this->flip_x_) { + b = pixels[7 - col]; + } else { + b = pixels[col]; + } } else if (this->orientation_ == 2) { for (uint8_t i = 0; i < 8; i++) { if (this->flip_x_) { @@ -282,7 +286,11 @@ void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) { } } else { for (uint8_t i = 0; i < 8; i++) { - b |= ((pixels[7 - col] >> i) & 1) << (7 - i); + if (this->flip_x_) { + b |= ((pixels[col] >> i) & 1) << (7 - i); + } else { + b |= ((pixels[7 - col] >> i) & 1) << (7 - i); + } } } // send this byte to display at selected chip From 968df6cb3fd625a87118e4d72ea2970641e9efb9 Mon Sep 17 00:00:00 2001 From: SeByDocKy Date: Tue, 4 Nov 2025 18:16:11 +0100 Subject: [PATCH 0088/1145] [gp8403] Add gp8413 (15 bits) DAC model (#7726) Co-authored-by: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- CODEOWNERS | 2 +- esphome/components/gp8403/__init__.py | 17 +++++--- esphome/components/gp8403/gp8403.cpp | 40 +++++++++++++++++-- esphome/components/gp8403/gp8403.h | 13 +++++- esphome/components/gp8403/output/__init__.py | 4 +- .../gp8403/output/gp8403_output.cpp | 10 +---- .../components/gp8403/output/gp8403_output.h | 4 +- tests/components/gp8403/common.yaml | 9 +++++ 8 files changed, 73 insertions(+), 26 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index b8a4df6a85..4d458eceb8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -181,7 +181,7 @@ esphome/components/gdk101/* @Szewcson esphome/components/gl_r01_i2c/* @pkejval esphome/components/globals/* @esphome/core esphome/components/gp2y1010au0f/* @zry98 -esphome/components/gp8403/* @jesserockz +esphome/components/gp8403/* @jesserockz @sebydocky esphome/components/gpio/* @esphome/core esphome/components/gpio/one_wire/* @ssieb esphome/components/gps/* @coogle @ximex diff --git a/esphome/components/gp8403/__init__.py b/esphome/components/gp8403/__init__.py index 96f1807688..83859a4030 100644 --- a/esphome/components/gp8403/__init__.py +++ b/esphome/components/gp8403/__init__.py @@ -1,19 +1,25 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_VOLTAGE +from esphome.const import CONF_ID, CONF_MODEL, CONF_VOLTAGE -CODEOWNERS = ["@jesserockz"] +CODEOWNERS = ["@jesserockz", "@sebydocky"] DEPENDENCIES = ["i2c"] MULTI_CONF = True gp8403_ns = cg.esphome_ns.namespace("gp8403") -GP8403 = gp8403_ns.class_("GP8403", cg.Component, i2c.I2CDevice) +GP8403Component = gp8403_ns.class_("GP8403Component", cg.Component, i2c.I2CDevice) GP8403Voltage = gp8403_ns.enum("GP8403Voltage") +GP8403Model = gp8403_ns.enum("GP8403Model") CONF_GP8403_ID = "gp8403_id" +MODELS = { + "GP8403": GP8403Model.GP8403, + "GP8413": GP8403Model.GP8413, +} + VOLTAGES = { "5V": GP8403Voltage.GP8403_VOLTAGE_5V, "10V": GP8403Voltage.GP8403_VOLTAGE_10V, @@ -22,7 +28,8 @@ VOLTAGES = { CONFIG_SCHEMA = ( cv.Schema( { - cv.GenerateID(): cv.declare_id(GP8403), + cv.GenerateID(): cv.declare_id(GP8403Component), + cv.Optional(CONF_MODEL, default="GP8403"): cv.enum(MODELS, upper=True), cv.Required(CONF_VOLTAGE): cv.enum(VOLTAGES, upper=True), } ) @@ -35,5 +42,5 @@ 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) - + cg.add(var.set_model(config[CONF_MODEL])) cg.add(var.set_voltage(config[CONF_VOLTAGE])) diff --git a/esphome/components/gp8403/gp8403.cpp b/esphome/components/gp8403/gp8403.cpp index 5107e96dee..11c2f9a7c0 100644 --- a/esphome/components/gp8403/gp8403.cpp +++ b/esphome/components/gp8403/gp8403.cpp @@ -8,16 +8,48 @@ namespace gp8403 { static const char *const TAG = "gp8403"; static const uint8_t RANGE_REGISTER = 0x01; +static const uint8_t OUTPUT_REGISTER = 0x02; -void GP8403::setup() { this->write_register(RANGE_REGISTER, (uint8_t *) (&this->voltage_), 1); } +const LogString *model_to_string(GP8403Model model) { + switch (model) { + case GP8403Model::GP8403: + return LOG_STR("GP8403"); + case GP8403Model::GP8413: + return LOG_STR("GP8413"); + } + return LOG_STR("Unknown"); +}; -void GP8403::dump_config() { +void GP8403Component::setup() { this->write_register(RANGE_REGISTER, (uint8_t *) (&this->voltage_), 1); } + +void GP8403Component::dump_config() { ESP_LOGCONFIG(TAG, "GP8403:\n" - " Voltage: %dV", - this->voltage_ == GP8403_VOLTAGE_5V ? 5 : 10); + " Voltage: %dV\n" + " Model: %s", + this->voltage_ == GP8403_VOLTAGE_5V ? 5 : 10, LOG_STR_ARG(model_to_string(this->model_))); LOG_I2C_DEVICE(this); } +void GP8403Component::write_state(float state, uint8_t channel) { + uint16_t val = 0; + switch (this->model_) { + case GP8403Model::GP8403: + val = ((uint16_t) (4095 * state)) << 4; + break; + case GP8403Model::GP8413: + val = ((uint16_t) (32767 * state)) << 1; + break; + default: + ESP_LOGE(TAG, "Unknown model %s", LOG_STR_ARG(model_to_string(this->model_))); + return; + } + ESP_LOGV(TAG, "Calculated DAC value: %" PRIu16, val); + i2c::ErrorCode err = this->write_register(OUTPUT_REGISTER + (2 * channel), (uint8_t *) &val, 2); + if (err != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Error writing to %s, code %d", LOG_STR_ARG(model_to_string(this->model_)), err); + } +} + } // namespace gp8403 } // namespace esphome diff --git a/esphome/components/gp8403/gp8403.h b/esphome/components/gp8403/gp8403.h index 9f493d39e3..6613187b20 100644 --- a/esphome/components/gp8403/gp8403.h +++ b/esphome/components/gp8403/gp8403.h @@ -11,15 +11,24 @@ enum GP8403Voltage { GP8403_VOLTAGE_10V = 0x11, }; -class GP8403 : public Component, public i2c::I2CDevice { +enum GP8403Model { + GP8403, + GP8413, +}; + +class GP8403Component : public Component, public i2c::I2CDevice { public: void setup() override; void dump_config() override; - + float get_setup_priority() const override { return setup_priority::DATA; } + void set_model(GP8403Model model) { this->model_ = model; } void set_voltage(gp8403::GP8403Voltage voltage) { this->voltage_ = voltage; } + void write_state(float state, uint8_t channel); + protected: GP8403Voltage voltage_; + GP8403Model model_{GP8403Model::GP8403}; }; } // namespace gp8403 diff --git a/esphome/components/gp8403/output/__init__.py b/esphome/components/gp8403/output/__init__.py index dc57833f4a..5245c405db 100644 --- a/esphome/components/gp8403/output/__init__.py +++ b/esphome/components/gp8403/output/__init__.py @@ -3,7 +3,7 @@ from esphome.components import i2c, output import esphome.config_validation as cv from esphome.const import CONF_CHANNEL, CONF_ID -from .. import CONF_GP8403_ID, GP8403, gp8403_ns +from .. import CONF_GP8403_ID, GP8403Component, gp8403_ns DEPENDENCIES = ["gp8403"] @@ -14,7 +14,7 @@ GP8403Output = gp8403_ns.class_( CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(GP8403Output), - cv.GenerateID(CONF_GP8403_ID): cv.use_id(GP8403), + cv.GenerateID(CONF_GP8403_ID): cv.use_id(GP8403Component), cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=1), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/gp8403/output/gp8403_output.cpp b/esphome/components/gp8403/output/gp8403_output.cpp index edb6972184..dfdc2d6ccb 100644 --- a/esphome/components/gp8403/output/gp8403_output.cpp +++ b/esphome/components/gp8403/output/gp8403_output.cpp @@ -7,8 +7,6 @@ namespace gp8403 { static const char *const TAG = "gp8403.output"; -static const uint8_t OUTPUT_REGISTER = 0x02; - void GP8403Output::dump_config() { ESP_LOGCONFIG(TAG, "GP8403 Output:\n" @@ -16,13 +14,7 @@ void GP8403Output::dump_config() { this->channel_); } -void GP8403Output::write_state(float state) { - uint16_t value = ((uint16_t) (state * 4095)) << 4; - i2c::ErrorCode err = this->parent_->write_register(OUTPUT_REGISTER + (2 * this->channel_), (uint8_t *) &value, 2); - if (err != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Error writing to GP8403, code %d", err); - } -} +void GP8403Output::write_state(float state) { this->parent_->write_state(state, this->channel_); } } // namespace gp8403 } // namespace esphome diff --git a/esphome/components/gp8403/output/gp8403_output.h b/esphome/components/gp8403/output/gp8403_output.h index 71e5efb1cb..c0d6650500 100644 --- a/esphome/components/gp8403/output/gp8403_output.h +++ b/esphome/components/gp8403/output/gp8403_output.h @@ -8,13 +8,11 @@ namespace esphome { namespace gp8403 { -class GP8403Output : public Component, public output::FloatOutput, public Parented { +class GP8403Output : public Component, public output::FloatOutput, public Parented { public: void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA - 1; } - void set_channel(uint8_t channel) { this->channel_ = channel; } - void write_state(float state) override; protected: diff --git a/tests/components/gp8403/common.yaml b/tests/components/gp8403/common.yaml index b04fbc4fbc..1147316b02 100644 --- a/tests/components/gp8403/common.yaml +++ b/tests/components/gp8403/common.yaml @@ -4,6 +4,11 @@ gp8403: voltage: 5V - id: gp8403_10v i2c_id: i2c_bus + model: GP8403 + voltage: 10V + - id: gp8413 + i2c_id: i2c_bus + model: GP8413 voltage: 10V output: @@ -15,3 +20,7 @@ output: gp8403_id: gp8403_10v id: gp8403_output_1 channel: 1 + - platform: gp8403 + gp8403_id: gp8413 + id: gp8413_output_2 + channel: 1 From 191a88c2dc50dd44f8a9dc3bcb2fbc70bff4fb35 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Tue, 4 Nov 2025 19:38:59 +0100 Subject: [PATCH 0089/1145] [gt911] Fix gt911 touchscreen with reset pin not initializing when loglevel is set to NONE (#11715) --- esphome/components/gt911/touchscreen/gt911_touchscreen.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp index 4810867d4b..992a86cc21 100644 --- a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp +++ b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp @@ -34,8 +34,8 @@ void GT911Touchscreen::setup() { this->interrupt_pin_->digital_write(false); } delay(2); - this->reset_pin_->digital_write(true); // wait 50ms after reset - this->set_timeout(50, [this] { this->setup_internal_(); }); + this->reset_pin_->digital_write(true); // wait at least T3+T4 ms as per the datasheet + this->set_timeout(5 + 50 + 1, [this] { this->setup_internal_(); }); return; } this->setup_internal_(); From aed7505f53c48bcda7206d0ac6f8db6cdfad433d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 4 Nov 2025 15:48:20 -0600 Subject: [PATCH 0090/1145] [automations] Reduce memory usage in if/while/repeat actions (32-36 bytes per instance) (#11650) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/core/automation.h | 1 + esphome/core/base_automation.h | 91 +++++++++++++++++++++++++++------- 2 files changed, 73 insertions(+), 19 deletions(-) diff --git a/esphome/core/automation.h b/esphome/core/automation.h index aace7889f0..c22b3ca0e3 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -220,6 +220,7 @@ template class Action { protected: friend ActionList; + template friend class ContinuationAction; virtual void play(Ts... x) = 0; void play_next_(Ts... x) { diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 28af02a846..083bb3ae31 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -216,18 +216,46 @@ template class StatelessLambdaAction : public Action { void (*f_)(Ts...); }; +/// Simple continuation action that calls play_next_ on a parent action. +/// Used internally by IfAction, WhileAction, RepeatAction, etc. to chain actions. +/// Memory: 4-8 bytes (parent pointer) vs 40 bytes (LambdaAction with std::function). +template class ContinuationAction : public Action { + public: + explicit ContinuationAction(Action *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->play_next_(x...); } + + protected: + Action *parent_; +}; + +// Forward declaration for WhileLoopContinuation +template class WhileAction; + +/// Loop continuation for WhileAction that checks condition and repeats or continues. +/// Memory: 4-8 bytes (parent pointer) vs 40 bytes (LambdaAction with std::function). +template class WhileLoopContinuation : public Action { + public: + explicit WhileLoopContinuation(WhileAction *parent) : parent_(parent) {} + + void play(Ts... x) override; + + protected: + WhileAction *parent_; +}; + template class IfAction : public Action { public: explicit IfAction(Condition *condition) : condition_(condition) {} void add_then(const std::initializer_list *> &actions) { this->then_.add_actions(actions); - this->then_.add_action(new LambdaAction([this](Ts... x) { this->play_next_(x...); })); + this->then_.add_action(new ContinuationAction(this)); } void add_else(const std::initializer_list *> &actions) { this->else_.add_actions(actions); - this->else_.add_action(new LambdaAction([this](Ts... x) { this->play_next_(x...); })); + this->else_.add_action(new ContinuationAction(this)); } void play_complex(Ts... x) override { @@ -268,17 +296,11 @@ template class WhileAction : public Action { void add_then(const std::initializer_list *> &actions) { this->then_.add_actions(actions); - this->then_.add_action(new LambdaAction([this](Ts... x) { - if (this->num_running_ > 0 && this->condition_->check(x...)) { - // play again - this->then_.play(x...); - } else { - // condition false, play next - this->play_next_(x...); - } - })); + this->then_.add_action(new WhileLoopContinuation(this)); } + friend class WhileLoopContinuation; + void play_complex(Ts... x) override { this->num_running_++; // Initial condition check @@ -304,22 +326,43 @@ template class WhileAction : public Action { ActionList then_; }; +// Implementation of WhileLoopContinuation::play +template void WhileLoopContinuation::play(Ts... x) { + if (this->parent_->num_running_ > 0 && this->parent_->condition_->check(x...)) { + // play again + this->parent_->then_.play(x...); + } else { + // condition false, play next + this->parent_->play_next_(x...); + } +} + +// Forward declaration for RepeatLoopContinuation +template class RepeatAction; + +/// Loop continuation for RepeatAction that increments iteration and repeats or continues. +/// Memory: 4-8 bytes (parent pointer) vs 40 bytes (LambdaAction with std::function). +template class RepeatLoopContinuation : public Action { + public: + explicit RepeatLoopContinuation(RepeatAction *parent) : parent_(parent) {} + + void play(uint32_t iteration, Ts... x) override; + + protected: + RepeatAction *parent_; +}; + template class RepeatAction : public Action { public: TEMPLATABLE_VALUE(uint32_t, count) void add_then(const std::initializer_list *> &actions) { this->then_.add_actions(actions); - this->then_.add_action(new LambdaAction([this](uint32_t iteration, Ts... x) { - iteration++; - if (iteration >= this->count_.value(x...)) { - this->play_next_(x...); - } else { - this->then_.play(iteration, x...); - } - })); + this->then_.add_action(new RepeatLoopContinuation(this)); } + friend class RepeatLoopContinuation; + void play_complex(Ts... x) override { this->num_running_++; if (this->count_.value(x...) > 0) { @@ -338,6 +381,16 @@ template class RepeatAction : public Action { ActionList then_; }; +// Implementation of RepeatLoopContinuation::play +template void RepeatLoopContinuation::play(uint32_t iteration, Ts... x) { + iteration++; + if (iteration >= this->parent_->count_.value(x...)) { + this->parent_->play_next_(x...); + } else { + this->parent_->then_.play(iteration, x...); + } +} + /** Wait until a condition is true to continue execution. * * Uses queue-based storage to safely handle concurrent executions. From 531b27582a4422700f166c16b8e2b2bd39647eaa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 4 Nov 2025 15:52:10 -0600 Subject: [PATCH 0091/1145] [network] Store use_address in RODATA to save RAM (#11707) --- esphome/components/api/api_server.cpp | 2 +- esphome/components/esphome/ota/ota_esphome.cpp | 2 +- esphome/components/ethernet/ethernet_component.cpp | 4 ++-- esphome/components/ethernet/ethernet_component.h | 10 +++++++--- esphome/components/network/util.cpp | 5 ++--- esphome/components/network/util.h | 2 +- esphome/components/openthread/openthread.cpp | 4 ++-- esphome/components/openthread/openthread.h | 10 +++++++--- esphome/components/web_server/web_server.cpp | 2 +- esphome/components/wifi/wifi_component.cpp | 4 ++-- esphome/components/wifi/wifi_component.h | 10 +++++++--- 11 files changed, 33 insertions(+), 22 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index e618610a75..e5f0d9795e 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -224,7 +224,7 @@ void APIServer::dump_config() { " Address: %s:%u\n" " Listen backlog: %u\n" " Max connections: %u", - network::get_use_address().c_str(), this->port_, this->listen_backlog_, this->max_connections_); + network::get_use_address(), this->port_, this->listen_backlog_, this->max_connections_); #ifdef USE_API_NOISE ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk())); if (!this->noise_ctx_->has_psk()) { diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index b85d660272..eb6c61a69b 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -94,7 +94,7 @@ void ESPHomeOTAComponent::dump_config() { "Over-The-Air updates:\n" " Address: %s:%u\n" " Version: %d", - network::get_use_address().c_str(), this->port_, USE_OTA_VERSION); + network::get_use_address(), this->port_, USE_OTA_VERSION); #ifdef USE_OTA_PASSWORD if (!this->password_.empty()) { ESP_LOGCONFIG(TAG, " Password configured"); diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 24b6e8154b..893d0285be 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -691,9 +691,9 @@ void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ // set_use_address() is guaranteed to be called during component setup by Python code generation, // so use_address_ will always be valid when get_use_address() is called - no fallback needed. -const std::string &EthernetComponent::get_use_address() const { return this->use_address_; } +const char *EthernetComponent::get_use_address() const { return this->use_address_; } -void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; } +void EthernetComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; } void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) { esp_err_t err; diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index d5dda3e3ae..31f9fa360a 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -88,8 +88,8 @@ class EthernetComponent : public Component { network::IPAddresses get_ip_addresses(); network::IPAddress get_dns_address(uint8_t num); - const std::string &get_use_address() const; - void set_use_address(const std::string &use_address); + const char *get_use_address() const; + void set_use_address(const char *use_address); void get_eth_mac_address_raw(uint8_t *mac); std::string get_eth_mac_address_pretty(); eth_duplex_t get_duplex_mode(); @@ -114,7 +114,6 @@ class EthernetComponent : public Component { /// @brief Set arbitratry PHY registers from config. void write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data); - std::string use_address_; #ifdef USE_ETHERNET_SPI uint8_t clk_pin_; uint8_t miso_pin_; @@ -158,6 +157,11 @@ class EthernetComponent : public Component { esp_eth_handle_t eth_handle_; esp_eth_phy_t *phy_{nullptr}; optional> fixed_mac_; + + private: + // Stores a pointer to a string literal (static storage duration). + // ONLY set from Python-generated code with string literals - never dynamic strings. + const char *use_address_{""}; }; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/network/util.cpp b/esphome/components/network/util.cpp index cb8f8569ad..5e741fd244 100644 --- a/esphome/components/network/util.cpp +++ b/esphome/components/network/util.cpp @@ -85,7 +85,7 @@ network::IPAddresses get_ip_addresses() { return {}; } -const std::string &get_use_address() { +const char *get_use_address() { // Global component pointers are guaranteed to be set by component constructors when USE_* is defined #ifdef USE_ETHERNET return ethernet::global_eth_component->get_use_address(); @@ -105,8 +105,7 @@ const std::string &get_use_address() { #if !defined(USE_ETHERNET) && !defined(USE_MODEM) && !defined(USE_WIFI) && !defined(USE_OPENTHREAD) // Fallback when no network component is defined (e.g., host platform) - static const std::string empty; - return empty; + return ""; #endif } diff --git a/esphome/components/network/util.h b/esphome/components/network/util.h index b4a92f8bee..3dc12232aa 100644 --- a/esphome/components/network/util.h +++ b/esphome/components/network/util.h @@ -12,7 +12,7 @@ bool is_connected(); /// Return whether the network is disabled (only wifi for now) bool is_disabled(); /// Get the active network hostname -const std::string &get_use_address(); +const char *get_use_address(); IPAddresses get_ip_addresses(); } // namespace network diff --git a/esphome/components/openthread/openthread.cpp b/esphome/components/openthread/openthread.cpp index db909e6b1f..d7fb1e1d42 100644 --- a/esphome/components/openthread/openthread.cpp +++ b/esphome/components/openthread/openthread.cpp @@ -254,9 +254,9 @@ void OpenThreadComponent::on_factory_reset(std::function callback) { // set_use_address() is guaranteed to be called during component setup by Python code generation, // so use_address_ will always be valid when get_use_address() is called - no fallback needed. -const std::string &OpenThreadComponent::get_use_address() const { return this->use_address_; } +const char *OpenThreadComponent::get_use_address() const { return this->use_address_; } -void OpenThreadComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; } +void OpenThreadComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; } } // namespace openthread } // namespace esphome diff --git a/esphome/components/openthread/openthread.h b/esphome/components/openthread/openthread.h index 19dbeb4628..3132e41696 100644 --- a/esphome/components/openthread/openthread.h +++ b/esphome/components/openthread/openthread.h @@ -33,15 +33,19 @@ class OpenThreadComponent : public Component { void on_factory_reset(std::function callback); void defer_factory_reset_external_callback(); - const std::string &get_use_address() const; - void set_use_address(const std::string &use_address); + const char *get_use_address() const; + void set_use_address(const char *use_address); protected: std::optional get_omr_address_(InstanceLock &lock); bool teardown_started_{false}; bool teardown_complete_{false}; std::function factory_reset_external_callback_; - std::string use_address_; + + private: + // Stores a pointer to a string literal (static storage duration). + // ONLY set from Python-generated code with string literals - never dynamic strings. + const char *use_address_{""}; }; extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 465356db80..acc0f33e61 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -324,7 +324,7 @@ void WebServer::dump_config() { ESP_LOGCONFIG(TAG, "Web Server:\n" " Address: %s:%u", - network::get_use_address().c_str(), this->base_->get_port()); + network::get_use_address(), this->base_->get_port()); } float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; } diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index b278e5a386..51b5756f29 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -273,8 +273,8 @@ network::IPAddress WiFiComponent::get_dns_address(int num) { } // set_use_address() is guaranteed to be called during component setup by Python code generation, // so use_address_ will always be valid when get_use_address() is called - no fallback needed. -const std::string &WiFiComponent::get_use_address() const { return this->use_address_; } -void WiFiComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; } +const char *WiFiComponent::get_use_address() const { return this->use_address_; } +void WiFiComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; } #ifdef USE_WIFI_AP void WiFiComponent::setup_ap_config_() { diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 42f78dbfac..89b7b1fa41 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -283,8 +283,8 @@ class WiFiComponent : public Component { network::IPAddress get_dns_address(int num); network::IPAddresses get_ip_addresses(); - const std::string &get_use_address() const; - void set_use_address(const std::string &use_address); + const char *get_use_address() const; + void set_use_address(const char *use_address); const wifi_scan_vector_t &get_scan_result() const { return scan_result_; } @@ -393,7 +393,6 @@ class WiFiComponent : public Component { void wifi_scan_done_callback_(); #endif - std::string use_address_; FixedVector sta_; std::vector sta_priorities_; wifi_scan_vector_t scan_result_; @@ -445,6 +444,11 @@ class WiFiComponent : public Component { // Pointers at the end (naturally aligned) Trigger<> *connect_trigger_{new Trigger<>()}; Trigger<> *disconnect_trigger_{new Trigger<>()}; + + private: + // Stores a pointer to a string literal (static storage duration). + // ONLY set from Python-generated code with string literals - never dynamic strings. + const char *use_address_{""}; }; extern WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) From 885508775f6c1a556410c462b95f247c6b249d05 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 4 Nov 2025 15:55:37 -0600 Subject: [PATCH 0092/1145] [fan] Remove duplicate preset mode storage to save RAM (#11632) --- esphome/components/api/api_connection.cpp | 4 +- esphome/components/copy/fan/copy_fan.cpp | 6 +- esphome/components/fan/automation.h | 9 +- esphome/components/fan/fan.cpp | 90 ++++++++++++------- esphome/components/fan/fan.h | 32 +++++-- esphome/components/fan/fan_traits.h | 12 +++ .../components/hbridge/fan/hbridge_fan.cpp | 2 +- esphome/components/speed/fan/speed_fan.cpp | 2 +- .../components/template/fan/template_fan.cpp | 2 +- 9 files changed, 109 insertions(+), 50 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index f8490b6ef7..3f1a076007 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -410,8 +410,8 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co } if (traits.supports_direction()) msg.direction = static_cast(fan->direction); - if (traits.supports_preset_modes()) - msg.set_preset_mode(StringRef(fan->preset_mode)); + if (traits.supports_preset_modes() && fan->has_preset_mode()) + msg.set_preset_mode(StringRef(fan->get_preset_mode())); return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, diff --git a/esphome/components/copy/fan/copy_fan.cpp b/esphome/components/copy/fan/copy_fan.cpp index 15a7f5e025..d35ece950b 100644 --- a/esphome/components/copy/fan/copy_fan.cpp +++ b/esphome/components/copy/fan/copy_fan.cpp @@ -12,7 +12,7 @@ void CopyFan::setup() { this->oscillating = source_->oscillating; this->speed = source_->speed; this->direction = source_->direction; - this->preset_mode = source_->preset_mode; + this->set_preset_mode_(source_->get_preset_mode()); this->publish_state(); }); @@ -20,7 +20,7 @@ void CopyFan::setup() { this->oscillating = source_->oscillating; this->speed = source_->speed; this->direction = source_->direction; - this->preset_mode = source_->preset_mode; + this->set_preset_mode_(source_->get_preset_mode()); this->publish_state(); } @@ -49,7 +49,7 @@ void CopyFan::control(const fan::FanCall &call) { call2.set_speed(*call.get_speed()); if (call.get_direction().has_value()) call2.set_direction(*call.get_direction()); - if (!call.get_preset_mode().empty()) + if (call.has_preset_mode()) call2.set_preset_mode(call.get_preset_mode()); call2.perform(); } diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index 90661c307c..ae0af1a9bd 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -212,18 +212,19 @@ class FanPresetSetTrigger : public Trigger { public: FanPresetSetTrigger(Fan *state) { state->add_on_state_callback([this, state]() { - auto preset_mode = state->preset_mode; + const auto *preset_mode = state->get_preset_mode(); auto should_trigger = preset_mode != this->last_preset_mode_; this->last_preset_mode_ = preset_mode; if (should_trigger) { - this->trigger(preset_mode); + // Trigger with empty string when nullptr to maintain backward compatibility + this->trigger(preset_mode != nullptr ? preset_mode : ""); } }); - this->last_preset_mode_ = state->preset_mode; + this->last_preset_mode_ = state->get_preset_mode(); } protected: - std::string last_preset_mode_; + const char *last_preset_mode_{nullptr}; }; } // namespace fan diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 5b4f437f99..959572e9d9 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -17,6 +17,27 @@ const LogString *fan_direction_to_string(FanDirection direction) { } } +FanCall &FanCall::set_preset_mode(const std::string &preset_mode) { return this->set_preset_mode(preset_mode.c_str()); } + +FanCall &FanCall::set_preset_mode(const char *preset_mode) { + if (preset_mode == nullptr || strlen(preset_mode) == 0) { + this->preset_mode_ = nullptr; + return *this; + } + + // Find and validate pointer from traits immediately + auto traits = this->parent_.get_traits(); + const char *validated_mode = traits.find_preset_mode(preset_mode); + if (validated_mode != nullptr) { + this->preset_mode_ = validated_mode; // Store pointer from traits + } else { + // Preset mode not found in traits - log warning and don't set + ESP_LOGW(TAG, "%s: Preset mode '%s' not supported", this->parent_.get_name().c_str(), preset_mode); + this->preset_mode_ = nullptr; + } + return *this; +} + void FanCall::perform() { ESP_LOGD(TAG, "'%s' - Setting:", this->parent_.get_name().c_str()); this->validate_(); @@ -32,8 +53,8 @@ void FanCall::perform() { if (this->direction_.has_value()) { ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_))); } - if (!this->preset_mode_.empty()) { - ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_.c_str()); + if (this->has_preset_mode()) { + ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_); } this->parent_.control(*this); } @@ -46,30 +67,15 @@ void FanCall::validate_() { // https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes // "Manually setting a speed must disable any set preset mode" - this->preset_mode_.clear(); - } - - if (!this->preset_mode_.empty()) { - const auto &preset_modes = traits.supported_preset_modes(); - bool found = false; - for (const auto &mode : preset_modes) { - if (strcmp(mode, this->preset_mode_.c_str()) == 0) { - found = true; - break; - } - } - if (!found) { - ESP_LOGW(TAG, "%s: Preset mode '%s' not supported", this->parent_.get_name().c_str(), this->preset_mode_.c_str()); - this->preset_mode_.clear(); - } + this->preset_mode_ = nullptr; } // when turning on... if (!this->parent_.state && this->binary_state_.has_value() && *this->binary_state_ // ..,and no preset mode will be active... - && this->preset_mode_.empty() && - this->parent_.preset_mode.empty() + && !this->has_preset_mode() && + this->parent_.get_preset_mode() == nullptr // ...and neither current nor new speed is available... && traits.supports_speed() && this->parent_.speed == 0 && !this->speed_.has_value()) { // ...set speed to 100% @@ -117,12 +123,13 @@ void FanRestoreState::apply(Fan &fan) { auto traits = fan.get_traits(); if (traits.supports_preset_modes()) { - // Use stored preset index to get preset name + // Use stored preset index to get preset name from traits const auto &preset_modes = traits.supported_preset_modes(); if (this->preset_mode < preset_modes.size()) { - fan.preset_mode = preset_modes[this->preset_mode]; + fan.set_preset_mode_(preset_modes[this->preset_mode]); } } + fan.publish_state(); } @@ -131,6 +138,29 @@ FanCall Fan::turn_off() { return this->make_call().set_state(false); } FanCall Fan::toggle() { return this->make_call().set_state(!this->state); } FanCall Fan::make_call() { return FanCall(*this); } +const char *Fan::find_preset_mode_(const char *preset_mode) { return this->get_traits().find_preset_mode(preset_mode); } + +bool Fan::set_preset_mode_(const char *preset_mode) { + if (preset_mode == nullptr) { + // Treat nullptr as clearing the preset mode + if (this->preset_mode_ == nullptr) { + return false; // No change + } + this->clear_preset_mode_(); + return true; + } + const char *validated = this->find_preset_mode_(preset_mode); + if (validated == nullptr || this->preset_mode_ == validated) { + return false; // Preset mode not supported or no change + } + this->preset_mode_ = validated; + return true; +} + +bool Fan::set_preset_mode_(const std::string &preset_mode) { return this->set_preset_mode_(preset_mode.c_str()); } + +void Fan::clear_preset_mode_() { this->preset_mode_ = nullptr; } + void Fan::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } void Fan::publish_state() { auto traits = this->get_traits(); @@ -146,8 +176,9 @@ void Fan::publish_state() { if (traits.supports_direction()) { ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction))); } - if (traits.supports_preset_modes() && !this->preset_mode.empty()) { - ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode.c_str()); + const char *preset = this->get_preset_mode(); + if (preset != nullptr) { + ESP_LOGD(TAG, " Preset Mode: %s", preset); } this->state_callback_.call(); this->save_state_(); @@ -199,16 +230,15 @@ void Fan::save_state_() { state.speed = this->speed; state.direction = this->direction; - if (traits.supports_preset_modes() && !this->preset_mode.empty()) { + const char *preset = this->get_preset_mode(); + if (preset != nullptr) { const auto &preset_modes = traits.supported_preset_modes(); - // Store index of current preset mode - size_t i = 0; - for (const auto &mode : preset_modes) { - if (strcmp(mode, this->preset_mode.c_str()) == 0) { + // Find index of current preset mode (pointer comparison is safe since preset is from traits) + for (size_t i = 0; i < preset_modes.size(); i++) { + if (preset_modes[i] == preset) { state.preset_mode = i; break; } - i++; } } diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index 3739de29a2..e38a80dbbe 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -70,11 +70,10 @@ class FanCall { return *this; } optional get_direction() const { return this->direction_; } - FanCall &set_preset_mode(const std::string &preset_mode) { - this->preset_mode_ = preset_mode; - return *this; - } - std::string get_preset_mode() const { return this->preset_mode_; } + FanCall &set_preset_mode(const std::string &preset_mode); + FanCall &set_preset_mode(const char *preset_mode); + const char *get_preset_mode() const { return this->preset_mode_; } + bool has_preset_mode() const { return this->preset_mode_ != nullptr; } void perform(); @@ -86,7 +85,7 @@ class FanCall { optional oscillating_; optional speed_; optional direction_{}; - std::string preset_mode_{}; + const char *preset_mode_{nullptr}; // Pointer to string in traits (after validation) }; struct FanRestoreState { @@ -112,8 +111,6 @@ class Fan : public EntityBase { int speed{0}; /// The current direction of the fan FanDirection direction{FanDirection::FORWARD}; - // The current preset mode of the fan - std::string preset_mode{}; FanCall turn_on(); FanCall turn_off(); @@ -130,8 +127,15 @@ class Fan : public EntityBase { /// Set the restore mode of this fan. void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } + /// Get the current preset mode (returns pointer to string stored in traits, or nullptr if not set) + const char *get_preset_mode() const { return this->preset_mode_; } + + /// Check if a preset mode is currently active + bool has_preset_mode() const { return this->preset_mode_ != nullptr; } + protected: friend FanCall; + friend struct FanRestoreState; virtual void control(const FanCall &call) = 0; @@ -140,9 +144,21 @@ class Fan : public EntityBase { void dump_traits_(const char *tag, const char *prefix); + /// Set the preset mode (finds and stores pointer from traits). Returns true if changed. + bool set_preset_mode_(const char *preset_mode); + /// Set the preset mode (finds and stores pointer from traits). Returns true if changed. + bool set_preset_mode_(const std::string &preset_mode); + /// Clear the preset mode + void clear_preset_mode_(); + /// Find and return the matching preset mode pointer from traits, or nullptr if not found. + const char *find_preset_mode_(const char *preset_mode); + CallbackManager state_callback_{}; ESPPreferenceObject rtc_; FanRestoreMode restore_mode_; + + private: + const char *preset_mode_{nullptr}; }; } // namespace fan diff --git a/esphome/components/fan/fan_traits.h b/esphome/components/fan/fan_traits.h index df345f9b04..24987fe984 100644 --- a/esphome/components/fan/fan_traits.h +++ b/esphome/components/fan/fan_traits.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -44,6 +45,17 @@ class FanTraits { /// Return if preset modes are supported bool supports_preset_modes() const { return !this->preset_modes_.empty(); } + /// Find and return the matching preset mode pointer from supported modes, or nullptr if not found. + const char *find_preset_mode(const char *preset_mode) const { + if (preset_mode == nullptr) + return nullptr; + for (const char *mode : this->preset_modes_) { + if (strcmp(mode, preset_mode) == 0) { + return mode; // Return pointer from traits + } + } + return nullptr; + } protected: bool oscillation_{false}; diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp index 605a9d4ef3..488208b725 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.cpp +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -57,7 +57,7 @@ void HBridgeFan::control(const fan::FanCall &call) { this->oscillating = *call.get_oscillating(); if (call.get_direction().has_value()) this->direction = *call.get_direction(); - this->preset_mode = call.get_preset_mode(); + this->set_preset_mode_(call.get_preset_mode()); this->write_state_(); this->publish_state(); diff --git a/esphome/components/speed/fan/speed_fan.cpp b/esphome/components/speed/fan/speed_fan.cpp index 57bd795416..801593c2ac 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -29,7 +29,7 @@ void SpeedFan::control(const fan::FanCall &call) { this->oscillating = *call.get_oscillating(); if (call.get_direction().has_value()) this->direction = *call.get_direction(); - this->preset_mode = call.get_preset_mode(); + this->set_preset_mode_(call.get_preset_mode()); this->write_state_(); this->publish_state(); diff --git a/esphome/components/template/fan/template_fan.cpp b/esphome/components/template/fan/template_fan.cpp index 5f4a2ae8f7..eba4c673b5 100644 --- a/esphome/components/template/fan/template_fan.cpp +++ b/esphome/components/template/fan/template_fan.cpp @@ -29,7 +29,7 @@ void TemplateFan::control(const fan::FanCall &call) { this->oscillating = *call.get_oscillating(); if (call.get_direction().has_value() && this->has_direction_) this->direction = *call.get_direction(); - this->preset_mode = call.get_preset_mode(); + this->set_preset_mode_(call.get_preset_mode()); this->publish_state(); } From c5e5609e926d4ebb9144638aeaef638b78c17105 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 5 Nov 2025 08:00:12 +1000 Subject: [PATCH 0093/1145] [lvgl] Fix case sensitivity in flex layout (#11717) --- esphome/components/lvgl/layout.py | 39 +++++++++++++++++++------ tests/components/lvgl/lvgl-package.yaml | 6 ++-- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/esphome/components/lvgl/layout.py b/esphome/components/lvgl/layout.py index 0aed525e16..a6aa816fda 100644 --- a/esphome/components/lvgl/layout.py +++ b/esphome/components/lvgl/layout.py @@ -1,4 +1,5 @@ import re +import textwrap import esphome.config_validation as cv from esphome.const import CONF_HEIGHT, CONF_TYPE, CONF_WIDTH @@ -122,7 +123,7 @@ class FlexLayout(Layout): def get_layout_schemas(self, config: dict) -> tuple: layout = config.get(CONF_LAYOUT) - if not isinstance(layout, dict) or layout.get(CONF_TYPE) != TYPE_FLEX: + if not isinstance(layout, dict) or layout.get(CONF_TYPE).lower() != TYPE_FLEX: return None, {} child_schema = FLEX_OBJ_SCHEMA if grow := layout.get(CONF_FLEX_GROW): @@ -161,6 +162,8 @@ class DirectionalLayout(FlexLayout): return self.direction def get_layout_schemas(self, config: dict) -> tuple: + if not isinstance(config.get(CONF_LAYOUT), str): + return None, {} if config.get(CONF_LAYOUT, "").lower() != self.direction: return None, {} return cv.one_of(self.direction, lower=True), flex_hv_schema(self.direction) @@ -206,7 +209,7 @@ class GridLayout(Layout): # Not a valid grid layout string return None, {} - if not isinstance(layout, dict) or layout.get(CONF_TYPE) != TYPE_GRID: + if not isinstance(layout, dict) or layout.get(CONF_TYPE).lower() != TYPE_GRID: return None, {} return ( { @@ -259,7 +262,7 @@ class GridLayout(Layout): ) # should be guaranteed to be a dict at this point assert isinstance(layout, dict) - assert layout.get(CONF_TYPE) == TYPE_GRID + assert layout.get(CONF_TYPE).lower() == TYPE_GRID rows = len(layout[CONF_GRID_ROWS]) columns = len(layout[CONF_GRID_COLUMNS]) used_cells = [[None] * columns for _ in range(rows)] @@ -335,6 +338,17 @@ def append_layout_schema(schema, config: dict): if CONF_LAYOUT not in config: # If no layout is specified, return the schema as is return schema.extend({cv.Optional(CONF_WIDGETS): any_widget_schema()}) + layout = config[CONF_LAYOUT] + # Sanity check the layout to avoid redundant checks in each type + if not isinstance(layout, str) and not isinstance(layout, dict): + raise cv.Invalid( + "The 'layout' option must be a string or a dictionary", [CONF_LAYOUT] + ) + if isinstance(layout, dict) and not isinstance(layout.get(CONF_TYPE), str): + raise cv.Invalid( + "Invalid layout type; must be a string ('flex' or 'grid')", + [CONF_LAYOUT, CONF_TYPE], + ) for layout_class in LAYOUT_CLASSES: layout_schema, child_schema = layout_class.get_layout_schemas(config) @@ -348,10 +362,17 @@ def append_layout_schema(schema, config: dict): layout_schema.add_extra(layout_class.validate) return layout_schema.extend(schema) - # If no layout class matched, return a default schema - return cv.Schema( - { - cv.Optional(CONF_LAYOUT): cv.one_of(*LAYOUT_CHOICES, lower=True), - cv.Optional(CONF_WIDGETS): any_widget_schema(), - } + if isinstance(layout, dict): + raise cv.Invalid( + "Invalid layout type; must be 'flex' or 'grid'", [CONF_LAYOUT, CONF_TYPE] + ) + raise cv.Invalid( + textwrap.dedent( + """ + Invalid 'layout' value + layout choices are 'horizontal', 'vertical', 'x', + or a dictionary with a 'type' key + """ + ), + [CONF_LAYOUT], ) diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index ca669c16e4..8ac9a60e2d 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -191,7 +191,7 @@ lvgl: args: ['lv_event_code_name_for(event->code).c_str()'] skip: true layout: - type: flex + type: Flex pad_row: 4 pad_column: 4px flex_align_main: center @@ -863,7 +863,7 @@ lvgl: width: 100% pad_all: 8 layout: - type: grid + type: GRid grid_row_align: end grid_rows: [25px, fr(1), content] grid_columns: [40, fr(1), fr(1)] @@ -1014,7 +1014,7 @@ lvgl: r_mod: -20 opa: 0% - id: page3 - layout: horizontal + layout: Horizontal widgets: - keyboard: id: lv_keyboard From c7ae42461331d1fdeb73c9a042fabc1ab6c954a6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 4 Nov 2025 16:14:54 -0600 Subject: [PATCH 0094/1145] [display] Optimize display writers with function pointers for stateless lambdas (#11629) --- esphome/components/display/display.h | 114 +++++++++++++++++- .../components/lcd_gpio/gpio_lcd_display.h | 9 +- .../components/lcd_pcf8574/pcf8574_display.h | 9 +- esphome/components/max7219/max7219.h | 5 +- .../components/max7219digit/max7219digit.h | 4 +- esphome/components/nextion/nextion.h | 5 +- .../pvvx_mithermometer/display/pvvx_display.h | 5 +- esphome/components/st7920/st7920.h | 4 +- esphome/components/tm1621/tm1621.h | 5 +- esphome/components/tm1637/tm1637.h | 5 +- esphome/components/tm1638/tm1638.h | 5 +- tests/components/image/test.host.yaml | 1 + tests/components/sdl/common.yaml | 3 + 13 files changed, 152 insertions(+), 22 deletions(-) diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index 14205da853..97b0a4e8d7 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -176,7 +176,117 @@ class Display; class DisplayPage; class DisplayOnPageChangeTrigger; -using display_writer_t = std::function; +/** Optimized display writer that uses function pointers for stateless lambdas. + * + * Similar to TemplatableValue but specialized for display writer callbacks. + * Saves ~8 bytes per stateless lambda on 32-bit platforms (16 bytes std::function → ~8 bytes discriminator+pointer). + * + * Supports both: + * - Stateless lambdas (from YAML) → function pointer (4 bytes) + * - Stateful lambdas/std::function (from C++ code) → std::function* (heap allocated) + * + * @tparam T The display type (e.g., Display, Nextion, GPIOLCDDisplay) + */ +template class DisplayWriter { + public: + DisplayWriter() : type_(NONE) {} + + // For stateless lambdas (convertible to function pointer): use function pointer (4 bytes) + template + DisplayWriter(F f) requires std::invocable && std::convertible_to + : type_(STATELESS_LAMBDA) { + this->stateless_f_ = f; // Implicit conversion to function pointer + } + + // For stateful lambdas and std::function (not convertible to function pointer): use std::function* (heap allocated) + // This handles backwards compatibility with external components + template + DisplayWriter(F f) requires std::invocable &&(!std::convertible_to) : type_(LAMBDA) { + this->f_ = new std::function(std::move(f)); + } + + // Copy constructor + DisplayWriter(const DisplayWriter &other) : type_(other.type_) { + if (type_ == LAMBDA) { + this->f_ = new std::function(*other.f_); + } else if (type_ == STATELESS_LAMBDA) { + this->stateless_f_ = other.stateless_f_; + } + } + + // Move constructor + DisplayWriter(DisplayWriter &&other) noexcept : type_(other.type_) { + if (type_ == LAMBDA) { + this->f_ = other.f_; + other.f_ = nullptr; + } else if (type_ == STATELESS_LAMBDA) { + this->stateless_f_ = other.stateless_f_; + } + other.type_ = NONE; + } + + // Assignment operators + DisplayWriter &operator=(const DisplayWriter &other) { + if (this != &other) { + this->~DisplayWriter(); + new (this) DisplayWriter(other); + } + return *this; + } + + DisplayWriter &operator=(DisplayWriter &&other) noexcept { + if (this != &other) { + this->~DisplayWriter(); + new (this) DisplayWriter(std::move(other)); + } + return *this; + } + + ~DisplayWriter() { + if (type_ == LAMBDA) { + delete this->f_; + } + // STATELESS_LAMBDA/NONE: no cleanup needed (function pointer or empty) + } + + bool has_value() const { return this->type_ != NONE; } + + void call(T &display) const { + switch (this->type_) { + case STATELESS_LAMBDA: + this->stateless_f_(display); // Direct function pointer call + break; + case LAMBDA: + (*this->f_)(display); // std::function call + break; + case NONE: + default: + break; + } + } + + // Operator() for convenience + void operator()(T &display) const { this->call(display); } + + // Operator* for backwards compatibility with (*writer_)(*this) pattern + DisplayWriter &operator*() { return *this; } + const DisplayWriter &operator*() const { return *this; } + + protected: + enum : uint8_t { + NONE, + LAMBDA, + STATELESS_LAMBDA, + } type_; + + union { + std::function *f_; + void (*stateless_f_)(T &); + }; +}; + +// Type alias for Display writer - uses optimized DisplayWriter instead of std::function +using display_writer_t = DisplayWriter; #define LOG_DISPLAY(prefix, type, obj) \ if ((obj) != nullptr) { \ @@ -678,7 +788,7 @@ class Display : public PollingComponent { void sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3); DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES}; - optional writer_{}; + display_writer_t writer_{}; DisplayPage *page_{nullptr}; DisplayPage *previous_page_{nullptr}; std::vector on_page_change_triggers_; diff --git a/esphome/components/lcd_gpio/gpio_lcd_display.h b/esphome/components/lcd_gpio/gpio_lcd_display.h index aba254a90a..81e4dc51a0 100644 --- a/esphome/components/lcd_gpio/gpio_lcd_display.h +++ b/esphome/components/lcd_gpio/gpio_lcd_display.h @@ -2,13 +2,18 @@ #include "esphome/core/hal.h" #include "esphome/components/lcd_base/lcd_display.h" +#include "esphome/components/display/display.h" namespace esphome { namespace lcd_gpio { +class GPIOLCDDisplay; + +using gpio_lcd_writer_t = display::DisplayWriter; + class GPIOLCDDisplay : public lcd_base::LCDDisplay { public: - void set_writer(std::function &&writer) { this->writer_ = std::move(writer); } + void set_writer(gpio_lcd_writer_t &&writer) { this->writer_ = std::move(writer); } void setup() override; void set_data_pins(GPIOPin *d0, GPIOPin *d1, GPIOPin *d2, GPIOPin *d3) { this->data_pins_[0] = d0; @@ -43,7 +48,7 @@ class GPIOLCDDisplay : public lcd_base::LCDDisplay { GPIOPin *rw_pin_{nullptr}; GPIOPin *enable_pin_{nullptr}; GPIOPin *data_pins_[8]{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; - std::function writer_; + gpio_lcd_writer_t writer_; }; } // namespace lcd_gpio diff --git a/esphome/components/lcd_pcf8574/pcf8574_display.h b/esphome/components/lcd_pcf8574/pcf8574_display.h index 4db3afb9b0..672b609036 100644 --- a/esphome/components/lcd_pcf8574/pcf8574_display.h +++ b/esphome/components/lcd_pcf8574/pcf8574_display.h @@ -3,13 +3,18 @@ #include "esphome/core/component.h" #include "esphome/components/lcd_base/lcd_display.h" #include "esphome/components/i2c/i2c.h" +#include "esphome/components/display/display.h" namespace esphome { namespace lcd_pcf8574 { +class PCF8574LCDDisplay; + +using pcf8574_lcd_writer_t = display::DisplayWriter; + class PCF8574LCDDisplay : public lcd_base::LCDDisplay, public i2c::I2CDevice { public: - void set_writer(std::function &&writer) { this->writer_ = std::move(writer); } + void set_writer(pcf8574_lcd_writer_t &&writer) { this->writer_ = std::move(writer); } void setup() override; void dump_config() override; void backlight(); @@ -24,7 +29,7 @@ class PCF8574LCDDisplay : public lcd_base::LCDDisplay, public i2c::I2CDevice { // Stores the current state of the backlight. uint8_t backlight_value_; - std::function writer_; + pcf8574_lcd_writer_t writer_; }; } // namespace lcd_pcf8574 diff --git a/esphome/components/max7219/max7219.h b/esphome/components/max7219/max7219.h index 270edf3282..58d871d54c 100644 --- a/esphome/components/max7219/max7219.h +++ b/esphome/components/max7219/max7219.h @@ -4,13 +4,14 @@ #include "esphome/core/time.h" #include "esphome/components/spi/spi.h" +#include "esphome/components/display/display.h" namespace esphome { namespace max7219 { class MAX7219Component; -using max7219_writer_t = std::function; +using max7219_writer_t = display::DisplayWriter; class MAX7219Component : public PollingComponent, public spi::SPIDevice writer_{}; + max7219_writer_t writer_{}; }; } // namespace max7219 diff --git a/esphome/components/max7219digit/max7219digit.h b/esphome/components/max7219digit/max7219digit.h index ead8033803..af419b9b38 100644 --- a/esphome/components/max7219digit/max7219digit.h +++ b/esphome/components/max7219digit/max7219digit.h @@ -23,7 +23,7 @@ enum ScrollMode { class MAX7219Component; -using max7219_writer_t = std::function; +using max7219_writer_t = display::DisplayWriter; class MAX7219Component : public display::DisplayBuffer, public spi::SPIDevice writer_local_{}; + max7219_writer_t writer_local_{}; }; } // namespace max7219digit diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index c078ab9d56..61068b52fc 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -9,6 +9,7 @@ #include "esphome/components/uart/uart.h" #include "nextion_base.h" #include "nextion_component.h" +#include "esphome/components/display/display.h" #include "esphome/components/display/display_color_utils.h" #ifdef USE_NEXTION_TFT_UPLOAD @@ -31,7 +32,7 @@ namespace nextion { class Nextion; class NextionComponentBase; -using nextion_writer_t = std::function; +using nextion_writer_t = display::DisplayWriter; static const std::string COMMAND_DELIMITER{static_cast(255), static_cast(255), static_cast(255)}; @@ -1471,7 +1472,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe CallbackManager touch_callback_{}; CallbackManager buffer_overflow_callback_{}; - optional writer_; + nextion_writer_t writer_; optional brightness_; #ifdef USE_NEXTION_CONFIG_DUMP_DEVICE_INFO diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.h b/esphome/components/pvvx_mithermometer/display/pvvx_display.h index c7fc523420..8637506bae 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.h +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/display/display.h" #include @@ -29,7 +30,7 @@ enum UNIT { UNIT_DEG_E, ///< show "°E" }; -using pvvx_writer_t = std::function; +using pvvx_writer_t = display::DisplayWriter; class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent { public: @@ -126,7 +127,7 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent { esp32_ble_tracker::ESPBTUUID char_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw("00001f1f-0000-1000-8000-00805f9b34fb"); - optional writer_{}; + pvvx_writer_t writer_{}; }; } // namespace pvvx_mithermometer diff --git a/esphome/components/st7920/st7920.h b/esphome/components/st7920/st7920.h index c9fdad454d..c48fe8cc1c 100644 --- a/esphome/components/st7920/st7920.h +++ b/esphome/components/st7920/st7920.h @@ -9,7 +9,7 @@ namespace st7920 { class ST7920; -using st7920_writer_t = std::function; +using st7920_writer_t = display::DisplayWriter; class ST7920 : public display::DisplayBuffer, public spi::SPIDevice writer_local_{}; + st7920_writer_t writer_local_{}; }; } // namespace st7920 diff --git a/esphome/components/tm1621/tm1621.h b/esphome/components/tm1621/tm1621.h index b9f330e96e..fe923417a6 100644 --- a/esphome/components/tm1621/tm1621.h +++ b/esphome/components/tm1621/tm1621.h @@ -3,13 +3,14 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/hal.h" +#include "esphome/components/display/display.h" namespace esphome { namespace tm1621 { class TM1621Display; -using tm1621_writer_t = std::function; +using tm1621_writer_t = display::DisplayWriter; class TM1621Display : public PollingComponent { public: @@ -59,7 +60,7 @@ class TM1621Display : public PollingComponent { GPIOPin *cs_pin_; GPIOPin *read_pin_; GPIOPin *write_pin_; - optional writer_{}; + tm1621_writer_t writer_{}; char row_[2][12]; uint8_t state_; uint8_t device_; diff --git a/esphome/components/tm1637/tm1637.h b/esphome/components/tm1637/tm1637.h index d44680c623..b9e96119e9 100644 --- a/esphome/components/tm1637/tm1637.h +++ b/esphome/components/tm1637/tm1637.h @@ -4,6 +4,7 @@ #include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/time.h" +#include "esphome/components/display/display.h" #include @@ -19,7 +20,7 @@ class TM1637Display; class TM1637Key; #endif -using tm1637_writer_t = std::function; +using tm1637_writer_t = display::DisplayWriter; class TM1637Display : public PollingComponent { public: @@ -78,7 +79,7 @@ class TM1637Display : public PollingComponent { uint8_t length_; bool inverted_; bool on_{true}; - optional writer_{}; + tm1637_writer_t writer_{}; uint8_t buffer_[6] = {0}; #ifdef USE_BINARY_SENSOR std::vector tm1637_keys_{}; diff --git a/esphome/components/tm1638/tm1638.h b/esphome/components/tm1638/tm1638.h index add72cfdf3..f6b2922ecf 100644 --- a/esphome/components/tm1638/tm1638.h +++ b/esphome/components/tm1638/tm1638.h @@ -5,6 +5,7 @@ #include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/time.h" +#include "esphome/components/display/display.h" #include @@ -18,7 +19,7 @@ class KeyListener { class TM1638Component; -using tm1638_writer_t = std::function; +using tm1638_writer_t = display::DisplayWriter; class TM1638Component : public PollingComponent { public: @@ -70,7 +71,7 @@ class TM1638Component : public PollingComponent { GPIOPin *stb_pin_; GPIOPin *dio_pin_; uint8_t *buffer_ = new uint8_t[8]; - optional writer_{}; + tm1638_writer_t writer_{}; std::vector listeners_{}; }; diff --git a/tests/components/image/test.host.yaml b/tests/components/image/test.host.yaml index 0411195e2a..aa45497088 100644 --- a/tests/components/image/test.host.yaml +++ b/tests/components/image/test.host.yaml @@ -1,5 +1,6 @@ display: - platform: sdl + id: image_display auto_clear_enabled: false dimensions: width: 480 diff --git a/tests/components/sdl/common.yaml b/tests/components/sdl/common.yaml index 52991d595c..66f93915b6 100644 --- a/tests/components/sdl/common.yaml +++ b/tests/components/sdl/common.yaml @@ -13,11 +13,14 @@ display: binary_sensor: - platform: sdl + sdl_id: sdl_display id: key_up key: SDLK_UP - platform: sdl + sdl_id: sdl_display id: key_down key: SDLK_DOWN - platform: sdl + sdl_id: sdl_display id: key_enter key: SDLK_RETURN From 6f7e54c3f3ba979eee8f4200a8b298a7dfd61b68 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 4 Nov 2025 16:33:01 -0600 Subject: [PATCH 0095/1145] [select] Refactor to index-based operations for immediate and future RAM savings (#11623) --- esphome/components/api/api_connection.cpp | 2 +- .../components/copy/select/copy_select.cpp | 8 +- esphome/components/copy/select/copy_select.h | 2 +- .../display_menu_base/menu_item.cpp | 2 +- esphome/components/ld2410/ld2410.cpp | 14 +- esphome/components/ld2410/ld2410.h | 4 +- .../ld2410/select/baud_rate_select.cpp | 6 +- .../ld2410/select/baud_rate_select.h | 2 +- .../select/distance_resolution_select.cpp | 6 +- .../select/distance_resolution_select.h | 2 +- .../select/light_out_control_select.cpp | 4 +- .../ld2410/select/light_out_control_select.h | 2 +- esphome/components/ld2412/ld2412.cpp | 14 +- esphome/components/ld2412/ld2412.h | 4 +- .../ld2412/select/baud_rate_select.cpp | 6 +- .../ld2412/select/baud_rate_select.h | 2 +- .../select/distance_resolution_select.cpp | 6 +- .../select/distance_resolution_select.h | 2 +- .../select/light_out_control_select.cpp | 4 +- .../ld2412/select/light_out_control_select.h | 2 +- esphome/components/ld2420/ld2420.cpp | 6 +- esphome/components/ld2420/ld2420.h | 2 +- .../ld2420/select/operating_mode_select.cpp | 6 +- .../ld2420/select/operating_mode_select.h | 2 +- esphome/components/ld2450/ld2450.cpp | 12 +- esphome/components/ld2450/ld2450.h | 4 +- .../ld2450/select/baud_rate_select.cpp | 6 +- .../ld2450/select/baud_rate_select.h | 2 +- .../ld2450/select/zone_type_select.cpp | 6 +- .../ld2450/select/zone_type_select.h | 2 +- .../logger/select/logger_level_select.cpp | 13 +- .../logger/select/logger_level_select.h | 2 +- esphome/components/mqtt/mqtt_select.cpp | 5 +- .../seeed_mr24hpc1/seeed_mr24hpc1.cpp | 10 +- .../seeed_mr60fda2/seeed_mr60fda2.cpp | 6 +- esphome/components/select/select.cpp | 49 ++++-- esphome/components/select/select.h | 49 +++++- esphome/components/select/select_call.cpp | 159 +++++++++--------- esphome/components/select/select_call.h | 6 +- .../template/select/template_select.cpp | 14 +- .../template/select/template_select.h | 2 +- .../components/tuya/select/tuya_select.cpp | 3 +- esphome/components/web_server/web_server.cpp | 10 +- esphome/components/web_server/web_server.h | 2 +- 44 files changed, 268 insertions(+), 204 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 3f1a076007..5ab8a6eb05 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -877,7 +877,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection bool is_single) { auto *select = static_cast(entity); SelectStateResponse resp; - resp.set_state(StringRef(select->state)); + resp.set_state(StringRef(select->current_option())); resp.missing_state = !select->has_state(); return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } diff --git a/esphome/components/copy/select/copy_select.cpp b/esphome/components/copy/select/copy_select.cpp index bdcbd0b42c..e45338e785 100644 --- a/esphome/components/copy/select/copy_select.cpp +++ b/esphome/components/copy/select/copy_select.cpp @@ -7,19 +7,19 @@ namespace copy { static const char *const TAG = "copy.select"; void CopySelect::setup() { - source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(value); }); + source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(index); }); traits.set_options(source_->traits.get_options()); if (source_->has_state()) - this->publish_state(source_->state); + this->publish_state(source_->active_index().value()); } void CopySelect::dump_config() { LOG_SELECT("", "Copy Select", this); } -void CopySelect::control(const std::string &value) { +void CopySelect::control(size_t index) { auto call = source_->make_call(); - call.set_option(value); + call.set_index(index); call.perform(); } diff --git a/esphome/components/copy/select/copy_select.h b/esphome/components/copy/select/copy_select.h index fb0aee86f6..bd74a93e82 100644 --- a/esphome/components/copy/select/copy_select.h +++ b/esphome/components/copy/select/copy_select.h @@ -13,7 +13,7 @@ class CopySelect : public select::Select, public Component { void dump_config() override; protected: - void control(const std::string &value) override; + void control(size_t index) override; select::Select *source_; }; diff --git a/esphome/components/display_menu_base/menu_item.cpp b/esphome/components/display_menu_base/menu_item.cpp index 2c7f34c493..8224adf3fe 100644 --- a/esphome/components/display_menu_base/menu_item.cpp +++ b/esphome/components/display_menu_base/menu_item.cpp @@ -42,7 +42,7 @@ std::string MenuItemSelect::get_value_text() const { result = this->value_getter_.value()(this); } else { if (this->select_var_ != nullptr) { - result = this->select_var_->state; + result = this->select_var_->current_option(); } } diff --git a/esphome/components/ld2410/ld2410.cpp b/esphome/components/ld2410/ld2410.cpp index 5c3af54ad8..608882565f 100644 --- a/esphome/components/ld2410/ld2410.cpp +++ b/esphome/components/ld2410/ld2410.cpp @@ -121,9 +121,9 @@ constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[] = { }; // Helper functions for lookups -template uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) { +template uint8_t find_uint8(const StringToUint8 (&arr)[N], const char *str) { for (const auto &entry : arr) { - if (str == entry.str) + if (strcmp(str, entry.str) == 0) return entry.value; } return 0xFF; // Not found @@ -441,7 +441,7 @@ bool LD2410Component::handle_ack_data_() { ESP_LOGV(TAG, "Baud rate change"); #ifdef USE_SELECT if (this->baud_rate_select_ != nullptr) { - ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->state.c_str()); + ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->current_option()); } #endif break; @@ -626,14 +626,14 @@ void LD2410Component::set_bluetooth(bool enable) { this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); } -void LD2410Component::set_distance_resolution(const std::string &state) { +void LD2410Component::set_distance_resolution(const char *state) { this->set_config_mode_(true); const uint8_t cmd_value[2] = {find_uint8(DISTANCE_RESOLUTIONS_BY_STR, state), 0x00}; this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, sizeof(cmd_value)); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); } -void LD2410Component::set_baud_rate(const std::string &state) { +void LD2410Component::set_baud_rate(const char *state) { this->set_config_mode_(true); const uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00}; this->send_command_(CMD_SET_BAUD_RATE, cmd_value, sizeof(cmd_value)); @@ -759,10 +759,10 @@ void LD2410Component::set_light_out_control() { #endif #ifdef USE_SELECT if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) { - this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->state); + this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option()); } if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->has_state()) { - this->out_pin_level_ = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->state); + this->out_pin_level_ = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option()); } #endif this->set_config_mode_(true); diff --git a/esphome/components/ld2410/ld2410.h b/esphome/components/ld2410/ld2410.h index 54fe1ce14d..52cf76b5b6 100644 --- a/esphome/components/ld2410/ld2410.h +++ b/esphome/components/ld2410/ld2410.h @@ -98,8 +98,8 @@ class LD2410Component : public Component, public uart::UARTDevice { void read_all_info(); void restart_and_read_all_info(); void set_bluetooth(bool enable); - void set_distance_resolution(const std::string &state); - void set_baud_rate(const std::string &state); + void set_distance_resolution(const char *state); + void set_baud_rate(const char *state); void factory_reset(); protected: diff --git a/esphome/components/ld2410/select/baud_rate_select.cpp b/esphome/components/ld2410/select/baud_rate_select.cpp index f4e0b90e2e..6da7c1d5f5 100644 --- a/esphome/components/ld2410/select/baud_rate_select.cpp +++ b/esphome/components/ld2410/select/baud_rate_select.cpp @@ -3,9 +3,9 @@ namespace esphome { namespace ld2410 { -void BaudRateSelect::control(const std::string &value) { - this->publish_state(value); - this->parent_->set_baud_rate(state); +void BaudRateSelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_baud_rate(this->option_at(index)); } } // namespace ld2410 diff --git a/esphome/components/ld2410/select/baud_rate_select.h b/esphome/components/ld2410/select/baud_rate_select.h index 3827b6a48a..9385c8cf7e 100644 --- a/esphome/components/ld2410/select/baud_rate_select.h +++ b/esphome/components/ld2410/select/baud_rate_select.h @@ -11,7 +11,7 @@ class BaudRateSelect : public select::Select, public Parented { BaudRateSelect() = default; protected: - void control(const std::string &value) override; + void control(size_t index) override; }; } // namespace ld2410 diff --git a/esphome/components/ld2410/select/distance_resolution_select.cpp b/esphome/components/ld2410/select/distance_resolution_select.cpp index eef34bda63..4fc4c5af02 100644 --- a/esphome/components/ld2410/select/distance_resolution_select.cpp +++ b/esphome/components/ld2410/select/distance_resolution_select.cpp @@ -3,9 +3,9 @@ namespace esphome { namespace ld2410 { -void DistanceResolutionSelect::control(const std::string &value) { - this->publish_state(value); - this->parent_->set_distance_resolution(state); +void DistanceResolutionSelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_distance_resolution(this->option_at(index)); } } // namespace ld2410 diff --git a/esphome/components/ld2410/select/distance_resolution_select.h b/esphome/components/ld2410/select/distance_resolution_select.h index d6affb1020..1a04f843a6 100644 --- a/esphome/components/ld2410/select/distance_resolution_select.h +++ b/esphome/components/ld2410/select/distance_resolution_select.h @@ -11,7 +11,7 @@ class DistanceResolutionSelect : public select::Select, public Parentedpublish_state(value); +void LightOutControlSelect::control(size_t index) { + this->publish_state(index); this->parent_->set_light_out_control(); } diff --git a/esphome/components/ld2410/select/light_out_control_select.h b/esphome/components/ld2410/select/light_out_control_select.h index 5d72e1774e..e8cd8f1d6a 100644 --- a/esphome/components/ld2410/select/light_out_control_select.h +++ b/esphome/components/ld2410/select/light_out_control_select.h @@ -11,7 +11,7 @@ class LightOutControlSelect : public select::Select, public Parented uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) { +template uint8_t find_uint8(const StringToUint8 (&arr)[N], const char *str) { for (const auto &entry : arr) { - if (str == entry.str) { + if (strcmp(str, entry.str) == 0) { return entry.value; } } @@ -485,7 +485,7 @@ bool LD2412Component::handle_ack_data_() { ESP_LOGV(TAG, "Baud rate change"); #ifdef USE_SELECT if (this->baud_rate_select_ != nullptr) { - ESP_LOGW(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->state.c_str()); + ESP_LOGW(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->current_option()); } #endif break; @@ -699,14 +699,14 @@ void LD2412Component::set_bluetooth(bool enable) { this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); } -void LD2412Component::set_distance_resolution(const std::string &state) { +void LD2412Component::set_distance_resolution(const char *state) { this->set_config_mode_(true); const uint8_t cmd_value[6] = {find_uint8(DISTANCE_RESOLUTIONS_BY_STR, state), 0x00, 0x00, 0x00, 0x00, 0x00}; this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, sizeof(cmd_value)); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); } -void LD2412Component::set_baud_rate(const std::string &state) { +void LD2412Component::set_baud_rate(const char *state) { this->set_config_mode_(true); const uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00}; this->send_command_(CMD_SET_BAUD_RATE, cmd_value, sizeof(cmd_value)); @@ -783,7 +783,7 @@ void LD2412Component::set_basic_config() { 1, TOTAL_GATES, DEFAULT_PRESENCE_TIMEOUT, 0, #endif #ifdef USE_SELECT - find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->state), + find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option()), #else 0x01, // Default value if not using select #endif @@ -837,7 +837,7 @@ void LD2412Component::set_light_out_control() { #endif #ifdef USE_SELECT if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) { - this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->state); + this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option()); } #endif uint8_t value[2] = {this->light_function_, this->light_threshold_}; diff --git a/esphome/components/ld2412/ld2412.h b/esphome/components/ld2412/ld2412.h index 41f96ab301..2bed34bdd8 100644 --- a/esphome/components/ld2412/ld2412.h +++ b/esphome/components/ld2412/ld2412.h @@ -99,8 +99,8 @@ class LD2412Component : public Component, public uart::UARTDevice { void read_all_info(); void restart_and_read_all_info(); void set_bluetooth(bool enable); - void set_distance_resolution(const std::string &state); - void set_baud_rate(const std::string &state); + void set_distance_resolution(const char *state); + void set_baud_rate(const char *state); void factory_reset(); void start_dynamic_background_correction(); diff --git a/esphome/components/ld2412/select/baud_rate_select.cpp b/esphome/components/ld2412/select/baud_rate_select.cpp index 2291a81896..7bc4683853 100644 --- a/esphome/components/ld2412/select/baud_rate_select.cpp +++ b/esphome/components/ld2412/select/baud_rate_select.cpp @@ -3,9 +3,9 @@ namespace esphome { namespace ld2412 { -void BaudRateSelect::control(const std::string &value) { - this->publish_state(value); - this->parent_->set_baud_rate(state); +void BaudRateSelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_baud_rate(this->option_at(index)); } } // namespace ld2412 diff --git a/esphome/components/ld2412/select/baud_rate_select.h b/esphome/components/ld2412/select/baud_rate_select.h index 2ae33551fb..ffe0329341 100644 --- a/esphome/components/ld2412/select/baud_rate_select.h +++ b/esphome/components/ld2412/select/baud_rate_select.h @@ -11,7 +11,7 @@ class BaudRateSelect : public select::Select, public Parented { BaudRateSelect() = default; protected: - void control(const std::string &value) override; + void control(size_t index) override; }; } // namespace ld2412 diff --git a/esphome/components/ld2412/select/distance_resolution_select.cpp b/esphome/components/ld2412/select/distance_resolution_select.cpp index a282215fbd..5a6f46a071 100644 --- a/esphome/components/ld2412/select/distance_resolution_select.cpp +++ b/esphome/components/ld2412/select/distance_resolution_select.cpp @@ -3,9 +3,9 @@ namespace esphome { namespace ld2412 { -void DistanceResolutionSelect::control(const std::string &value) { - this->publish_state(value); - this->parent_->set_distance_resolution(state); +void DistanceResolutionSelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_distance_resolution(this->option_at(index)); } } // namespace ld2412 diff --git a/esphome/components/ld2412/select/distance_resolution_select.h b/esphome/components/ld2412/select/distance_resolution_select.h index 0658f5d1a7..842f63b7b1 100644 --- a/esphome/components/ld2412/select/distance_resolution_select.h +++ b/esphome/components/ld2412/select/distance_resolution_select.h @@ -11,7 +11,7 @@ class DistanceResolutionSelect : public select::Select, public Parentedpublish_state(value); +void LightOutControlSelect::control(size_t index) { + this->publish_state(index); this->parent_->set_light_out_control(); } diff --git a/esphome/components/ld2412/select/light_out_control_select.h b/esphome/components/ld2412/select/light_out_control_select.h index a71bab1e14..7a50970d0d 100644 --- a/esphome/components/ld2412/select/light_out_control_select.h +++ b/esphome/components/ld2412/select/light_out_control_select.h @@ -11,7 +11,7 @@ class LightOutControlSelect : public select::Select, public Parentedtotal_sample_number_counter); } -void LD2420Component::set_operating_mode(const std::string &state) { +void LD2420Component::set_operating_mode(const char *state) { // If unsupported firmware ignore mode select if (ld2420::get_firmware_int(firmware_ver_) >= CALIBRATE_VERSION_MIN) { this->current_operating_mode = find_uint8(OP_MODE_BY_STR, state); diff --git a/esphome/components/ld2420/ld2420.h b/esphome/components/ld2420/ld2420.h index 812c408cfd..128baab604 100644 --- a/esphome/components/ld2420/ld2420.h +++ b/esphome/components/ld2420/ld2420.h @@ -107,7 +107,7 @@ class LD2420Component : public Component, public uart::UARTDevice { int send_cmd_from_array(CmdFrameT cmd_frame); void report_gate_data(); void handle_cmd_error(uint8_t error); - void set_operating_mode(const std::string &state); + void set_operating_mode(const char *state); void auto_calibrate_sensitivity(); void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number); uint8_t set_config_mode(bool enable); diff --git a/esphome/components/ld2420/select/operating_mode_select.cpp b/esphome/components/ld2420/select/operating_mode_select.cpp index 2d576e7cc6..5bf80b33c9 100644 --- a/esphome/components/ld2420/select/operating_mode_select.cpp +++ b/esphome/components/ld2420/select/operating_mode_select.cpp @@ -7,9 +7,9 @@ namespace ld2420 { static const char *const TAG = "ld2420.select"; -void LD2420Select::control(const std::string &value) { - this->publish_state(value); - this->parent_->set_operating_mode(value); +void LD2420Select::control(size_t index) { + this->publish_state(index); + this->parent_->set_operating_mode(this->option_at(index)); } } // namespace ld2420 diff --git a/esphome/components/ld2420/select/operating_mode_select.h b/esphome/components/ld2420/select/operating_mode_select.h index 317b2af8c0..f59eb33432 100644 --- a/esphome/components/ld2420/select/operating_mode_select.h +++ b/esphome/components/ld2420/select/operating_mode_select.h @@ -11,7 +11,7 @@ class LD2420Select : public Component, public select::Select, public Parentedset_config_mode_(false); #ifdef USE_SELECT const auto baud_rate = std::to_string(this->parent_->get_baud_rate()); - if (this->baud_rate_select_ != nullptr && this->baud_rate_select_->state != baud_rate) { + if (this->baud_rate_select_ != nullptr && strcmp(this->baud_rate_select_->current_option(), baud_rate.c_str()) != 0) { this->baud_rate_select_->publish_state(baud_rate); } this->publish_zone_type(); @@ -635,7 +635,7 @@ bool LD2450Component::handle_ack_data_() { ESP_LOGV(TAG, "Baud rate change"); #ifdef USE_SELECT if (this->baud_rate_select_ != nullptr) { - ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->state.c_str()); + ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->current_option()); } #endif break; @@ -716,7 +716,7 @@ bool LD2450Component::handle_ack_data_() { this->publish_zone_type(); #ifdef USE_SELECT if (this->zone_type_select_ != nullptr) { - ESP_LOGV(TAG, "Change zone type to: %s", this->zone_type_select_->state.c_str()); + ESP_LOGV(TAG, "Change zone type to: %s", this->zone_type_select_->current_option()); } #endif if (this->buffer_data_[10] == 0x00) { @@ -790,7 +790,7 @@ void LD2450Component::set_bluetooth(bool enable) { } // Set Baud rate -void LD2450Component::set_baud_rate(const std::string &state) { +void LD2450Component::set_baud_rate(const char *state) { this->set_config_mode_(true); const uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00}; this->send_command_(CMD_SET_BAUD_RATE, cmd_value, sizeof(cmd_value)); @@ -798,8 +798,8 @@ void LD2450Component::set_baud_rate(const std::string &state) { } // Set Zone Type - one of: Disabled, Detection, Filter -void LD2450Component::set_zone_type(const std::string &state) { - ESP_LOGV(TAG, "Set zone type: %s", state.c_str()); +void LD2450Component::set_zone_type(const char *state) { + ESP_LOGV(TAG, "Set zone type: %s", state); uint8_t zone_type = find_uint8(ZONE_TYPE_BY_STR, state); this->zone_type_ = zone_type; this->send_set_zone_command_(); diff --git a/esphome/components/ld2450/ld2450.h b/esphome/components/ld2450/ld2450.h index 9faa189019..44b63be444 100644 --- a/esphome/components/ld2450/ld2450.h +++ b/esphome/components/ld2450/ld2450.h @@ -115,8 +115,8 @@ class LD2450Component : public Component, public uart::UARTDevice { void restart_and_read_all_info(); void set_bluetooth(bool enable); void set_multi_target(bool enable); - void set_baud_rate(const std::string &state); - void set_zone_type(const std::string &state); + void set_baud_rate(const char *state); + void set_zone_type(const char *state); void publish_zone_type(); void factory_reset(); #ifdef USE_TEXT_SENSOR diff --git a/esphome/components/ld2450/select/baud_rate_select.cpp b/esphome/components/ld2450/select/baud_rate_select.cpp index 06439aaa75..754972214e 100644 --- a/esphome/components/ld2450/select/baud_rate_select.cpp +++ b/esphome/components/ld2450/select/baud_rate_select.cpp @@ -3,9 +3,9 @@ namespace esphome { namespace ld2450 { -void BaudRateSelect::control(const std::string &value) { - this->publish_state(value); - this->parent_->set_baud_rate(state); +void BaudRateSelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_baud_rate(this->option_at(index)); } } // namespace ld2450 diff --git a/esphome/components/ld2450/select/baud_rate_select.h b/esphome/components/ld2450/select/baud_rate_select.h index 04fe65b4fd..22810d5f13 100644 --- a/esphome/components/ld2450/select/baud_rate_select.h +++ b/esphome/components/ld2450/select/baud_rate_select.h @@ -11,7 +11,7 @@ class BaudRateSelect : public select::Select, public Parented { BaudRateSelect() = default; protected: - void control(const std::string &value) override; + void control(size_t index) override; }; } // namespace ld2450 diff --git a/esphome/components/ld2450/select/zone_type_select.cpp b/esphome/components/ld2450/select/zone_type_select.cpp index a9f6155142..1111428c7c 100644 --- a/esphome/components/ld2450/select/zone_type_select.cpp +++ b/esphome/components/ld2450/select/zone_type_select.cpp @@ -3,9 +3,9 @@ namespace esphome { namespace ld2450 { -void ZoneTypeSelect::control(const std::string &value) { - this->publish_state(value); - this->parent_->set_zone_type(state); +void ZoneTypeSelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_zone_type(this->option_at(index)); } } // namespace ld2450 diff --git a/esphome/components/ld2450/select/zone_type_select.h b/esphome/components/ld2450/select/zone_type_select.h index 8aafeb6beb..fc95ec1021 100644 --- a/esphome/components/ld2450/select/zone_type_select.h +++ b/esphome/components/ld2450/select/zone_type_select.h @@ -11,7 +11,7 @@ class ZoneTypeSelect : public select::Select, public Parented { ZoneTypeSelect() = default; protected: - void control(const std::string &value) override; + void control(size_t index) override; }; } // namespace ld2450 diff --git a/esphome/components/logger/select/logger_level_select.cpp b/esphome/components/logger/select/logger_level_select.cpp index 6d60a3ae47..e2ec28a390 100644 --- a/esphome/components/logger/select/logger_level_select.cpp +++ b/esphome/components/logger/select/logger_level_select.cpp @@ -3,10 +3,10 @@ namespace esphome::logger { void LoggerLevelSelect::publish_state(int level) { - const auto &option = this->at(level_to_index(level)); - if (!option) + auto index = level_to_index(level); + if (!this->has_index(index)) return; - Select::publish_state(option.value()); + Select::publish_state(index); } void LoggerLevelSelect::setup() { @@ -14,11 +14,6 @@ void LoggerLevelSelect::setup() { this->publish_state(this->parent_->get_log_level()); } -void LoggerLevelSelect::control(const std::string &value) { - const auto index = this->index_of(value); - if (!index) - return; - this->parent_->set_log_level(index_to_level(index.value())); -} +void LoggerLevelSelect::control(size_t index) { this->parent_->set_log_level(index_to_level(index)); } } // namespace esphome::logger diff --git a/esphome/components/logger/select/logger_level_select.h b/esphome/components/logger/select/logger_level_select.h index 0631eca45d..950edd29ac 100644 --- a/esphome/components/logger/select/logger_level_select.h +++ b/esphome/components/logger/select/logger_level_select.h @@ -9,7 +9,7 @@ class LoggerLevelSelect : public Component, public select::Select, public Parent public: void publish_state(int level); void setup() override; - void control(const std::string &value) override; + void control(size_t index) override; protected: // Convert log level to option index (skip CONFIG at level 4) diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index b851348306..e1660b07ea 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -21,7 +21,8 @@ void MQTTSelectComponent::setup() { call.set_option(state); call.perform(); }); - this->select_->add_on_state_callback([this](const std::string &state, size_t index) { this->publish_state(state); }); + this->select_->add_on_state_callback( + [this](const std::string &state, size_t index) { this->publish_state(this->select_->option_at(index)); }); } void MQTTSelectComponent::dump_config() { @@ -44,7 +45,7 @@ void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon } bool MQTTSelectComponent::send_initial_state() { if (this->select_->has_state()) { - return this->publish_state(this->select_->state); + return this->publish_state(this->select_->current_option()); } else { return true; } diff --git a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp index 76523ce5c0..4c0416d727 100644 --- a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp +++ b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp @@ -435,12 +435,12 @@ void MR24HPC1Component::r24_frame_parse_open_underlying_information_(uint8_t *da } else if ((this->existence_boundary_select_ != nullptr) && ((data[FRAME_COMMAND_WORD_INDEX] == 0x0a) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8a))) { if (this->existence_boundary_select_->has_index(data[FRAME_DATA_INDEX] - 1)) { - this->existence_boundary_select_->publish_state(S_BOUNDARY_STR[data[FRAME_DATA_INDEX] - 1]); + this->existence_boundary_select_->publish_state(data[FRAME_DATA_INDEX] - 1); } } else if ((this->motion_boundary_select_ != nullptr) && ((data[FRAME_COMMAND_WORD_INDEX] == 0x0b) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8b))) { if (this->motion_boundary_select_->has_index(data[FRAME_DATA_INDEX] - 1)) { - this->motion_boundary_select_->publish_state(S_BOUNDARY_STR[data[FRAME_DATA_INDEX] - 1]); + this->motion_boundary_select_->publish_state(data[FRAME_DATA_INDEX] - 1); } } else if ((this->motion_trigger_number_ != nullptr) && ((data[FRAME_COMMAND_WORD_INDEX] == 0x0c) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8c))) { @@ -515,7 +515,7 @@ void MR24HPC1Component::r24_frame_parse_work_status_(uint8_t *data) { ESP_LOGD(TAG, "Reply: get radar init status 0x%02X", data[FRAME_DATA_INDEX]); } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x07) { if ((this->scene_mode_select_ != nullptr) && (this->scene_mode_select_->has_index(data[FRAME_DATA_INDEX]))) { - this->scene_mode_select_->publish_state(S_SCENE_STR[data[FRAME_DATA_INDEX]]); + this->scene_mode_select_->publish_state(data[FRAME_DATA_INDEX]); } else { ESP_LOGD(TAG, "Select has index offset %d Error", data[FRAME_DATA_INDEX]); } @@ -538,7 +538,7 @@ void MR24HPC1Component::r24_frame_parse_work_status_(uint8_t *data) { ESP_LOGD(TAG, "Reply: get radar init status 0x%02X", data[FRAME_DATA_INDEX]); } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x87) { if ((this->scene_mode_select_ != nullptr) && (this->scene_mode_select_->has_index(data[FRAME_DATA_INDEX]))) { - this->scene_mode_select_->publish_state(S_SCENE_STR[data[FRAME_DATA_INDEX]]); + this->scene_mode_select_->publish_state(data[FRAME_DATA_INDEX]); } else { ESP_LOGD(TAG, "Select has index offset %d Error", data[FRAME_DATA_INDEX]); } @@ -581,7 +581,7 @@ void MR24HPC1Component::r24_frame_parse_human_information_(uint8_t *data) { ((data[FRAME_COMMAND_WORD_INDEX] == 0x0A) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8A))) { // none:0x00 1s:0x01 30s:0x02 1min:0x03 2min:0x04 5min:0x05 10min:0x06 30min:0x07 1hour:0x08 if (data[FRAME_DATA_INDEX] < 9) { - this->unman_time_select_->publish_state(S_UNMANNED_TIME_STR[data[FRAME_DATA_INDEX]]); + this->unman_time_select_->publish_state(data[FRAME_DATA_INDEX]); } } else if ((this->keep_away_text_sensor_ != nullptr) && ((data[FRAME_COMMAND_WORD_INDEX] == 0x0B) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8B))) { diff --git a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp index dea7976578..7f8bd6a43c 100644 --- a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp +++ b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp @@ -292,7 +292,7 @@ void MR60FDA2Component::process_frame_() { install_height_float = bit_cast(current_install_height_int); uint32_t select_index = find_nearest_index(install_height_float, INSTALL_HEIGHT, 7); - this->install_height_select_->publish_state(this->install_height_select_->at(select_index).value()); + this->install_height_select_->publish_state(select_index); } if (this->height_threshold_select_ != nullptr) { @@ -301,7 +301,7 @@ void MR60FDA2Component::process_frame_() { height_threshold_float = bit_cast(current_height_threshold_int); size_t select_index = find_nearest_index(height_threshold_float, HEIGHT_THRESHOLD, 7); - this->height_threshold_select_->publish_state(this->height_threshold_select_->at(select_index).value()); + this->height_threshold_select_->publish_state(select_index); } if (this->sensitivity_select_ != nullptr) { @@ -309,7 +309,7 @@ void MR60FDA2Component::process_frame_() { encode_uint32(current_data_buf_[11], current_data_buf_[10], current_data_buf_[9], current_data_buf_[8]); uint32_t select_index = find_nearest_index(current_sensitivity, SENSITIVITY, 3); - this->sensitivity_select_->publish_state(this->sensitivity_select_->at(select_index).value()); + this->sensitivity_select_->publish_state(select_index); } ESP_LOGD(TAG, "Mounting height: %.2f, Height threshold: %.2f, Sensitivity: %" PRIu32, install_height_float, diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 5e30be3c13..6bb01ba6e2 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -7,24 +7,43 @@ namespace select { static const char *const TAG = "select"; -void Select::publish_state(const std::string &state) { +void Select::publish_state(const std::string &state) { this->publish_state(state.c_str()); } + +void Select::publish_state(const char *state) { auto index = this->index_of(state); - const auto *name = this->get_name().c_str(); if (index.has_value()) { - this->set_has_state(true); - this->state = state; - ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", name, state.c_str(), index.value()); - this->state_callback_.call(state, index.value()); + this->publish_state(index.value()); } else { - ESP_LOGE(TAG, "'%s': invalid state for publish_state(): %s", name, state.c_str()); + ESP_LOGE(TAG, "'%s': Invalid option %s", this->get_name().c_str(), state); } } +void Select::publish_state(size_t index) { + if (!this->has_index(index)) { + ESP_LOGE(TAG, "'%s': Invalid index %zu", this->get_name().c_str(), index); + return; + } + const char *option = this->option_at(index); + this->set_has_state(true); + this->active_index_ = index; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + this->state = option; // Update deprecated member for backward compatibility +#pragma GCC diagnostic pop + ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", this->get_name().c_str(), option, index); + // Callback signature requires std::string, create temporary for compatibility + this->state_callback_.call(std::string(option), index); +} + +const char *Select::current_option() const { return this->has_state() ? this->option_at(this->active_index_) : ""; } + void Select::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } -bool Select::has_option(const std::string &option) const { return this->index_of(option).has_value(); } +bool Select::has_option(const std::string &option) const { return this->index_of(option.c_str()).has_value(); } + +bool Select::has_option(const char *option) const { return this->index_of(option).has_value(); } bool Select::has_index(size_t index) const { return index < this->size(); } @@ -33,10 +52,12 @@ size_t Select::size() const { return options.size(); } -optional Select::index_of(const std::string &option) const { +optional Select::index_of(const std::string &option) const { return this->index_of(option.c_str()); } + +optional Select::index_of(const char *option) const { const auto &options = traits.get_options(); for (size_t i = 0; i < options.size(); i++) { - if (strcmp(options[i], option.c_str()) == 0) { + if (strcmp(options[i], option) == 0) { return i; } } @@ -45,19 +66,17 @@ optional Select::index_of(const std::string &option) const { optional Select::active_index() const { if (this->has_state()) { - return this->index_of(this->state); - } else { - return {}; + return this->active_index_; } + return {}; } optional Select::at(size_t index) const { if (this->has_index(index)) { const auto &options = traits.get_options(); return std::string(options.at(index)); - } else { - return {}; } + return {}; } const char *Select::option_at(size_t index) const { return traits.get_options().at(index); } diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index eabb39898b..f859594cd1 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -30,16 +30,31 @@ namespace select { */ class Select : public EntityBase { public: - std::string state; SelectTraits traits; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + /// @deprecated Use current_option() instead. This member will be removed in ESPHome 2026.5.0. + __attribute__((deprecated("Use current_option() instead of .state. Will be removed in 2026.5.0"))) + std::string state{}; + + Select() = default; + ~Select() = default; +#pragma GCC diagnostic pop + void publish_state(const std::string &state); + void publish_state(const char *state); + void publish_state(size_t index); + + /// Return the currently selected option (as const char* from flash). + const char *current_option() const; /// Instantiate a SelectCall object to modify this select component's state. SelectCall make_call() { return SelectCall(this); } /// Return whether this select component contains the provided option. bool has_option(const std::string &option) const; + bool has_option(const char *option) const; /// Return whether this select component contains the provided index offset. bool has_index(size_t index) const; @@ -49,6 +64,7 @@ class Select : public EntityBase { /// Find the (optional) index offset of the provided option value. optional index_of(const std::string &option) const; + optional index_of(const char *option) const; /// Return the (optional) index offset of the currently active option. optional active_index() const; @@ -64,13 +80,36 @@ class Select : public EntityBase { protected: friend class SelectCall; - /** Set the value of the select, this is a virtual method that each select integration must implement. + size_t active_index_{0}; + + /** Set the value of the select by index, this is an optional virtual method. * - * This method is called by the SelectCall. + * IMPORTANT: At least ONE of the two control() methods must be overridden by derived classes. + * Overriding this index-based version is PREFERRED as it avoids string conversions. * - * @param value The value as validated by the SelectCall. + * This method is called by the SelectCall when the index is already known. + * Default implementation converts to string and calls control(const std::string&). + * + * @param index The index as validated by the SelectCall. */ - virtual void control(const std::string &value) = 0; + virtual void control(size_t index) { this->control(this->option_at(index)); } + + /** Set the value of the select, this is a virtual method that each select integration can implement. + * + * IMPORTANT: At least ONE of the two control() methods must be overridden by derived classes. + * Overriding control(size_t) is PREFERRED as it avoids string conversions. + * + * This method is called by control(size_t) when not overridden, or directly by external code. + * Default implementation converts to index and calls control(size_t). + * + * @param value The value as validated by the caller. + */ + virtual void control(const std::string &value) { + auto index = this->index_of(value); + if (index.has_value()) { + this->control(index.value()); + } + } CallbackManager state_callback_; }; diff --git a/esphome/components/select/select_call.cpp b/esphome/components/select/select_call.cpp index dd398b4052..aa7559e24e 100644 --- a/esphome/components/select/select_call.cpp +++ b/esphome/components/select/select_call.cpp @@ -7,19 +7,21 @@ namespace select { static const char *const TAG = "select"; -SelectCall &SelectCall::set_option(const std::string &option) { - return with_operation(SELECT_OP_SET).with_option(option); +SelectCall &SelectCall::set_option(const std::string &option) { return this->with_option(option); } + +SelectCall &SelectCall::set_option(const char *option) { return this->with_option(option); } + +SelectCall &SelectCall::set_index(size_t index) { return this->with_index(index); } + +SelectCall &SelectCall::select_next(bool cycle) { return this->with_operation(SELECT_OP_NEXT).with_cycle(cycle); } + +SelectCall &SelectCall::select_previous(bool cycle) { + return this->with_operation(SELECT_OP_PREVIOUS).with_cycle(cycle); } -SelectCall &SelectCall::set_index(size_t index) { return with_operation(SELECT_OP_SET_INDEX).with_index(index); } +SelectCall &SelectCall::select_first() { return this->with_operation(SELECT_OP_FIRST); } -SelectCall &SelectCall::select_next(bool cycle) { return with_operation(SELECT_OP_NEXT).with_cycle(cycle); } - -SelectCall &SelectCall::select_previous(bool cycle) { return with_operation(SELECT_OP_PREVIOUS).with_cycle(cycle); } - -SelectCall &SelectCall::select_first() { return with_operation(SELECT_OP_FIRST); } - -SelectCall &SelectCall::select_last() { return with_operation(SELECT_OP_LAST); } +SelectCall &SelectCall::select_last() { return this->with_operation(SELECT_OP_LAST); } SelectCall &SelectCall::with_operation(SelectOperation operation) { this->operation_ = operation; @@ -31,89 +33,96 @@ SelectCall &SelectCall::with_cycle(bool cycle) { return *this; } -SelectCall &SelectCall::with_option(const std::string &option) { - this->option_ = option; +SelectCall &SelectCall::with_option(const std::string &option) { return this->with_option(option.c_str()); } + +SelectCall &SelectCall::with_option(const char *option) { + this->operation_ = SELECT_OP_SET; + // Find the option index - this validates the option exists + this->index_ = this->parent_->index_of(option); return *this; } SelectCall &SelectCall::with_index(size_t index) { - this->index_ = index; + this->operation_ = SELECT_OP_SET; + if (index >= this->parent_->size()) { + ESP_LOGW(TAG, "'%s' - Index value %zu out of bounds", this->parent_->get_name().c_str(), index); + this->index_ = {}; // Store nullopt for invalid index + } else { + this->index_ = index; + } return *this; } +optional SelectCall::calculate_target_index_(const char *name) { + const auto &options = this->parent_->traits.get_options(); + if (options.empty()) { + ESP_LOGW(TAG, "'%s' - Select has no options", name); + return {}; + } + + if (this->operation_ == SELECT_OP_FIRST) { + return 0; + } + + if (this->operation_ == SELECT_OP_LAST) { + return options.size() - 1; + } + + if (this->operation_ == SELECT_OP_SET) { + ESP_LOGD(TAG, "'%s' - Setting", name); + if (!this->index_.has_value()) { + ESP_LOGW(TAG, "'%s' - No option set", name); + return {}; + } + return this->index_.value(); + } + + // SELECT_OP_NEXT or SELECT_OP_PREVIOUS + ESP_LOGD(TAG, "'%s' - Selecting %s, with%s cycling", name, + this->operation_ == SELECT_OP_NEXT ? LOG_STR_LITERAL("next") : LOG_STR_LITERAL("previous"), + this->cycle_ ? LOG_STR_LITERAL("") : LOG_STR_LITERAL("out")); + + const auto size = options.size(); + if (!this->parent_->has_state()) { + return this->operation_ == SELECT_OP_NEXT ? 0 : size - 1; + } + + // Use cached active_index_ instead of index_of() lookup + const auto active_index = this->parent_->active_index_; + if (this->cycle_) { + return (size + active_index + (this->operation_ == SELECT_OP_NEXT ? +1 : -1)) % size; + } + + if (this->operation_ == SELECT_OP_PREVIOUS && active_index > 0) { + return active_index - 1; + } + + if (this->operation_ == SELECT_OP_NEXT && active_index < size - 1) { + return active_index + 1; + } + + return {}; // Can't navigate further without cycling +} + void SelectCall::perform() { auto *parent = this->parent_; const auto *name = parent->get_name().c_str(); - const auto &traits = parent->traits; - const auto &options = traits.get_options(); if (this->operation_ == SELECT_OP_NONE) { ESP_LOGW(TAG, "'%s' - SelectCall performed without selecting an operation", name); return; } - if (options.empty()) { - ESP_LOGW(TAG, "'%s' - Cannot perform SelectCall, select has no options", name); + + // Calculate target index (with_index() and with_option() already validate bounds/existence) + auto target_index = this->calculate_target_index_(name); + if (!target_index.has_value()) { return; } - std::string target_value; - - if (this->operation_ == SELECT_OP_SET) { - ESP_LOGD(TAG, "'%s' - Setting", name); - if (!this->option_.has_value()) { - ESP_LOGW(TAG, "'%s' - No option value set for SelectCall", name); - return; - } - target_value = this->option_.value(); - } else if (this->operation_ == SELECT_OP_SET_INDEX) { - if (!this->index_.has_value()) { - ESP_LOGW(TAG, "'%s' - No index value set for SelectCall", name); - return; - } - if (this->index_.value() >= options.size()) { - ESP_LOGW(TAG, "'%s' - Index value %zu out of bounds", name, this->index_.value()); - return; - } - target_value = options[this->index_.value()]; - } else if (this->operation_ == SELECT_OP_FIRST) { - target_value = options.front(); - } else if (this->operation_ == SELECT_OP_LAST) { - target_value = options.back(); - } else if (this->operation_ == SELECT_OP_NEXT || this->operation_ == SELECT_OP_PREVIOUS) { - auto cycle = this->cycle_; - ESP_LOGD(TAG, "'%s' - Selecting %s, with%s cycling", name, this->operation_ == SELECT_OP_NEXT ? "next" : "previous", - cycle ? "" : "out"); - if (!parent->has_state()) { - target_value = this->operation_ == SELECT_OP_NEXT ? options.front() : options.back(); - } else { - auto index = parent->index_of(parent->state); - if (index.has_value()) { - auto size = options.size(); - if (cycle) { - auto use_index = (size + index.value() + (this->operation_ == SELECT_OP_NEXT ? +1 : -1)) % size; - target_value = options[use_index]; - } else { - if (this->operation_ == SELECT_OP_PREVIOUS && index.value() > 0) { - target_value = options[index.value() - 1]; - } else if (this->operation_ == SELECT_OP_NEXT && index.value() < options.size() - 1) { - target_value = options[index.value() + 1]; - } else { - return; - } - } - } else { - target_value = this->operation_ == SELECT_OP_NEXT ? options.front() : options.back(); - } - } - } - - if (!parent->has_option(target_value)) { - ESP_LOGW(TAG, "'%s' - Option %s is not a valid option", name, target_value.c_str()); - return; - } - - ESP_LOGD(TAG, "'%s' - Set selected option to: %s", name, target_value.c_str()); - parent->control(target_value); + auto idx = target_index.value(); + // All operations use indices, call control() by index to avoid string conversion + ESP_LOGD(TAG, "'%s' - Set selected option to: %s", name, parent->option_at(idx)); + parent->control(idx); } } // namespace select diff --git a/esphome/components/select/select_call.h b/esphome/components/select/select_call.h index efc9a982ec..eae7d3de1d 100644 --- a/esphome/components/select/select_call.h +++ b/esphome/components/select/select_call.h @@ -10,7 +10,6 @@ class Select; enum SelectOperation { SELECT_OP_NONE, SELECT_OP_SET, - SELECT_OP_SET_INDEX, SELECT_OP_NEXT, SELECT_OP_PREVIOUS, SELECT_OP_FIRST, @@ -23,6 +22,7 @@ class SelectCall { void perform(); SelectCall &set_option(const std::string &option); + SelectCall &set_option(const char *option); SelectCall &set_index(size_t index); SelectCall &select_next(bool cycle); @@ -33,11 +33,13 @@ class SelectCall { SelectCall &with_operation(SelectOperation operation); SelectCall &with_cycle(bool cycle); SelectCall &with_option(const std::string &option); + SelectCall &with_option(const char *option); SelectCall &with_index(size_t index); protected: + __attribute__((always_inline)) inline optional calculate_target_index_(const char *name); + Select *const parent_; - optional option_; optional index_; SelectOperation operation_{SELECT_OP_NONE}; bool cycle_; diff --git a/esphome/components/template/select/template_select.cpp b/esphome/components/template/select/template_select.cpp index 3ea34c3c7c..112f24e919 100644 --- a/esphome/components/template/select/template_select.cpp +++ b/esphome/components/template/select/template_select.cpp @@ -24,7 +24,7 @@ void TemplateSelect::setup() { ESP_LOGD(TAG, "State from initial: %s", this->option_at(index)); } - this->publish_state(this->at(index).value()); + this->publish_state(index); } void TemplateSelect::update() { @@ -41,16 +41,14 @@ void TemplateSelect::update() { } } -void TemplateSelect::control(const std::string &value) { - this->set_trigger_->trigger(value); +void TemplateSelect::control(size_t index) { + this->set_trigger_->trigger(std::string(this->option_at(index))); if (this->optimistic_) - this->publish_state(value); + this->publish_state(index); - if (this->restore_value_) { - auto index = this->index_of(value); - this->pref_.save(&index.value()); - } + if (this->restore_value_) + this->pref_.save(&index); } void TemplateSelect::dump_config() { diff --git a/esphome/components/template/select/template_select.h b/esphome/components/template/select/template_select.h index 1c33153872..2dad059ade 100644 --- a/esphome/components/template/select/template_select.h +++ b/esphome/components/template/select/template_select.h @@ -24,7 +24,7 @@ class TemplateSelect : public select::Select, public PollingComponent { void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } protected: - void control(const std::string &value) override; + void control(size_t index) override; bool optimistic_ = false; size_t initial_option_index_{0}; bool restore_value_ = false; diff --git a/esphome/components/tuya/select/tuya_select.cpp b/esphome/components/tuya/select/tuya_select.cpp index 7c1cd09d06..07e3ce44ee 100644 --- a/esphome/components/tuya/select/tuya_select.cpp +++ b/esphome/components/tuya/select/tuya_select.cpp @@ -17,8 +17,7 @@ void TuyaSelect::setup() { return; } size_t mapping_idx = std::distance(mappings.cbegin(), it); - auto value = this->at(mapping_idx); - this->publish_state(value.value()); + this->publish_state(mapping_idx); }); } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index acc0f33e61..f1d1a75875 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1188,7 +1188,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->select_json(obj, obj->state, detail); + std::string data = this->select_json(obj, obj->has_state() ? obj->current_option() : "", detail); request->send(200, "application/json", data.c_str()); return; } @@ -1208,12 +1208,14 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM request->send(404); } std::string WebServer::select_state_json_generator(WebServer *web_server, void *source) { - return web_server->select_json((select::Select *) (source), ((select::Select *) (source))->state, DETAIL_STATE); + auto *obj = (select::Select *) (source); + return web_server->select_json(obj, obj->has_state() ? obj->current_option() : "", DETAIL_STATE); } std::string WebServer::select_all_json_generator(WebServer *web_server, void *source) { - return web_server->select_json((select::Select *) (source), ((select::Select *) (source))->state, DETAIL_ALL); + auto *obj = (select::Select *) (source); + return web_server->select_json(obj, obj->has_state() ? obj->current_option() : "", DETAIL_ALL); } -std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) { +std::string WebServer::select_json(select::Select *obj, const char *value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index fb790483dc..328140cfae 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -410,7 +410,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { static std::string select_state_json_generator(WebServer *web_server, void *source); static std::string select_all_json_generator(WebServer *web_server, void *source); /// Dump the select state with its value as a JSON string. - std::string select_json(select::Select *obj, const std::string &value, JsonDetail start_config); + std::string select_json(select::Select *obj, const char *value, JsonDetail start_config); #endif #ifdef USE_CLIMATE From 64f8963566092f382f2b8c1556a201d0f48252be Mon Sep 17 00:00:00 2001 From: Gnuspice Date: Wed, 5 Nov 2025 12:46:06 +1300 Subject: [PATCH 0096/1145] [const] Move `CONF_ENABLED` to const.py (#11719) --- esphome/components/const/__init__.py | 1 + esphome/components/modbus_controller/__init__.py | 2 +- esphome/components/modbus_controller/const.py | 1 - esphome/components/wireguard/binary_sensor.py | 3 +-- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/components/const/__init__.py b/esphome/components/const/__init__.py index 2b88bb43a8..0c22b2d27e 100644 --- a/esphome/components/const/__init__.py +++ b/esphome/components/const/__init__.py @@ -8,6 +8,7 @@ BYTE_ORDER_BIG = "big_endian" CONF_COLOR_DEPTH = "color_depth" CONF_DRAW_ROUNDING = "draw_rounding" +CONF_ENABLED = "enabled" CONF_ON_RECEIVE = "on_receive" CONF_ON_STATE_CHANGE = "on_state_change" CONF_REQUEST_HEADERS = "request_headers" diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 28f3326c47..1c23783ce3 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -3,6 +3,7 @@ import binascii from esphome import automation import esphome.codegen as cg from esphome.components import modbus +from esphome.components.const import CONF_ENABLED import esphome.config_validation as cv from esphome.const import ( CONF_ADDRESS, @@ -20,7 +21,6 @@ from .const import ( CONF_BYTE_OFFSET, CONF_COMMAND_THROTTLE, CONF_CUSTOM_COMMAND, - CONF_ENABLED, CONF_FORCE_NEW_RANGE, CONF_MAX_CMD_RETRIES, CONF_MODBUS_CONTROLLER_ID, diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py index ee0b5fc633..c689d84576 100644 --- a/esphome/components/modbus_controller/const.py +++ b/esphome/components/modbus_controller/const.py @@ -2,7 +2,6 @@ CONF_ALLOW_DUPLICATE_COMMANDS = "allow_duplicate_commands" CONF_BITMASK = "bitmask" CONF_BYTE_OFFSET = "byte_offset" CONF_COMMAND_THROTTLE = "command_throttle" -CONF_ENABLED = "enabled" CONF_OFFLINE_SKIP_UPDATES = "offline_skip_updates" CONF_CUSTOM_COMMAND = "custom_command" CONF_FORCE_NEW_RANGE = "force_new_range" diff --git a/esphome/components/wireguard/binary_sensor.py b/esphome/components/wireguard/binary_sensor.py index 02c4862e8d..2ba59d4c39 100644 --- a/esphome/components/wireguard/binary_sensor.py +++ b/esphome/components/wireguard/binary_sensor.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import binary_sensor +from esphome.components.const import CONF_ENABLED import esphome.config_validation as cv from esphome.const import ( CONF_STATUS, @@ -9,8 +10,6 @@ from esphome.const import ( from . import CONF_WIREGUARD_ID, Wireguard -CONF_ENABLED = "enabled" - DEPENDENCIES = ["wireguard"] CONFIG_SCHEMA = { From 1446e7174ac96a7158d9d51fb21020e965d72873 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 4 Nov 2025 19:23:24 -0600 Subject: [PATCH 0097/1145] [core] Reduce action framework argument copies by 83% (#11704) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ags10/ags10.h | 4 +- esphome/components/aic3204/automation.h | 2 +- .../alarm_control_panel/automation.h | 14 ++--- esphome/components/animation/animation.h | 6 +- esphome/components/api/api_server.h | 2 +- .../components/api/homeassistant_service.h | 2 +- esphome/components/at581x/automation.h | 4 +- esphome/components/audio_adc/automation.h | 2 +- esphome/components/audio_dac/automation.h | 6 +- esphome/components/binary_sensor/automation.h | 6 +- esphome/components/bl0906/bl0906.h | 2 +- esphome/components/ble_client/automation.h | 18 +++--- esphome/components/button/automation.h | 2 +- esphome/components/canbus/canbus.h | 2 +- esphome/components/climate/automation.h | 2 +- esphome/components/cm1106/cm1106.h | 2 +- esphome/components/cover/automation.h | 16 ++--- esphome/components/cs5460a/cs5460a.h | 2 +- esphome/components/datetime/date_entity.h | 2 +- esphome/components/datetime/datetime_entity.h | 2 +- esphome/components/datetime/time_entity.h | 2 +- .../deep_sleep/deep_sleep_component.h | 6 +- esphome/components/dfplayer/dfplayer.h | 16 ++--- .../components/dfrobot_sen0395/automation.h | 4 +- esphome/components/display/display.h | 8 +-- .../components/display_menu_base/automation.h | 18 +++--- esphome/components/ds1307/ds1307.h | 4 +- .../components/duty_time/duty_time_sensor.h | 8 +-- esphome/components/esp32_ble/ble.h | 6 +- .../esp32_ble_server/ble_server_automations.h | 6 +- .../components/esp32_ble_tracker/automation.h | 4 +- esphome/components/esp8266_pwm/esp8266_pwm.h | 2 +- esphome/components/esp_ldo/esp_ldo.h | 2 +- esphome/components/espnow/automation.h | 10 ++-- esphome/components/event/automation.h | 2 +- esphome/components/ezo/automation.h | 10 ++-- esphome/components/ezo_pmp/ezo_pmp.h | 24 ++++---- esphome/components/fan/automation.h | 12 ++-- .../fingerprint_grow/fingerprint_grow.h | 12 ++-- .../components/globals/globals_component.h | 2 +- .../grove_tb6612fng/grove_tb6612fng.h | 12 ++-- esphome/components/haier/automation.h | 26 ++++---- esphome/components/hbridge/fan/hbridge_fan.h | 2 +- .../components/http_request/http_request.h | 6 +- .../components/http_request/ota/automation.h | 2 +- esphome/components/htu21d/htu21d.h | 4 +- .../integration/integration_sensor.h | 2 +- .../components/key_collector/key_collector.h | 4 +- esphome/components/ld2410/automation.h | 2 +- esphome/components/ledc/ledc_output.h | 2 +- .../components/libretiny_pwm/libretiny_pwm.h | 2 +- esphome/components/light/automation.h | 12 ++-- esphome/components/lightwaverf/lightwaverf.h | 2 +- esphome/components/lock/automation.h | 8 +-- esphome/components/lvgl/lvgl_esphome.h | 6 +- esphome/components/max17043/automation.h | 2 +- esphome/components/max6956/automation.h | 4 +- esphome/components/max7219digit/automation.h | 8 +-- esphome/components/media_player/automation.h | 20 ++++--- esphome/components/mhz19/mhz19.h | 6 +- .../components/micro_wake_word/automation.h | 12 ++-- esphome/components/microphone/automation.h | 12 ++-- esphome/components/midea/ac_automations.h | 16 ++--- esphome/components/mixer/speaker/automation.h | 2 +- esphome/components/mqtt/mqtt_client.h | 10 ++-- esphome/components/nau7802/nau7802.h | 6 +- esphome/components/nextion/automation.h | 8 +-- esphome/components/number/automation.h | 6 +- .../components/online_image/online_image.h | 4 +- esphome/components/output/automation.h | 10 ++-- esphome/components/pcf85063/pcf85063.h | 4 +- esphome/components/pcf8563/pcf8563.h | 4 +- esphome/components/pid/pid_climate.h | 6 +- .../pipsolar/output/pipsolar_output.h | 2 +- esphome/components/pmwcs3/pmwcs3.h | 6 +- esphome/components/pn532/pn532.h | 2 +- esphome/components/pn7150/automation.h | 22 +++---- esphome/components/pn7160/automation.h | 22 +++---- esphome/components/pulse_counter/automation.h | 2 +- esphome/components/pulse_meter/automation.h | 2 +- esphome/components/pzemac/pzemac.h | 2 +- esphome/components/pzemdc/pzemdc.h | 2 +- esphome/components/remote_base/remote_base.h | 2 +- .../remote_transmitter/automation.h | 2 +- esphome/components/rf_bridge/rf_bridge.h | 16 ++--- .../rotary_encoder/rotary_encoder.h | 2 +- esphome/components/rp2040_pwm/rp2040_pwm.h | 2 +- esphome/components/rtttl/rtttl.h | 6 +- esphome/components/scd30/automation.h | 2 +- esphome/components/scd4x/automation.h | 4 +- esphome/components/script/script.h | 10 ++-- esphome/components/select/automation.h | 6 +- esphome/components/sen5x/automation.h | 2 +- esphome/components/senseair/senseair.h | 10 ++-- esphome/components/sensor/automation.h | 4 +- esphome/components/servo/servo.h | 4 +- esphome/components/sim800l/sim800l.h | 10 ++-- esphome/components/sound_level/sound_level.h | 4 +- esphome/components/speaker/automation.h | 16 ++--- .../speaker/media_player/automation.h | 2 +- esphome/components/sprinkler/automation.h | 30 +++++----- esphome/components/sps30/automation.h | 2 +- esphome/components/stepper/stepper.h | 10 ++-- esphome/components/sun/sun.h | 2 +- esphome/components/switch/automation.h | 12 ++-- esphome/components/sx126x/automation.h | 12 ++-- esphome/components/sx127x/automation.h | 12 ++-- esphome/components/template/lock/automation.h | 2 +- .../components/template/valve/automation.h | 2 +- esphome/components/text/automation.h | 2 +- esphome/components/text_sensor/automation.h | 4 +- esphome/components/time/real_time_clock.h | 2 +- esphome/components/tm1651/tm1651.h | 10 ++-- esphome/components/uart/automation.h | 2 +- esphome/components/udp/automation.h | 2 +- esphome/components/ufire_ec/ufire_ec.h | 4 +- esphome/components/ufire_ise/ufire_ise.h | 6 +- esphome/components/update/automation.h | 4 +- esphome/components/valve/automation.h | 14 ++--- .../voice_assistant/voice_assistant.h | 10 ++-- esphome/components/wifi/wifi_component.h | 10 ++-- esphome/components/wireguard/wireguard.h | 8 +-- esphome/core/automation.h | 14 ++--- esphome/core/base_automation.h | 60 +++++++++---------- .../loop_test_component/loop_test_component.h | 4 +- 125 files changed, 448 insertions(+), 446 deletions(-) diff --git a/esphome/components/ags10/ags10.h b/esphome/components/ags10/ags10.h index e0975f14bc..9e034b20cb 100644 --- a/esphome/components/ags10/ags10.h +++ b/esphome/components/ags10/ags10.h @@ -105,7 +105,7 @@ template class AGS10NewI2cAddressAction : public Action, public: TEMPLATABLE_VALUE(uint8_t, new_address) - void play(Ts... x) override { this->parent_->new_i2c_address(this->new_address_.value(x...)); } + void play(const Ts &...x) override { this->parent_->new_i2c_address(this->new_address_.value(x...)); } }; enum AGS10SetZeroPointActionMode { @@ -122,7 +122,7 @@ template class AGS10SetZeroPointAction : public Action, p TEMPLATABLE_VALUE(uint16_t, value) TEMPLATABLE_VALUE(AGS10SetZeroPointActionMode, mode) - void play(Ts... x) override { + void play(const Ts &...x) override { switch (this->mode_.value(x...)) { case FACTORY_DEFAULT: this->parent_->set_zero_point_with_factory_defaults(); diff --git a/esphome/components/aic3204/automation.h b/esphome/components/aic3204/automation.h index 416a88fa12..851ff930f8 100644 --- a/esphome/components/aic3204/automation.h +++ b/esphome/components/aic3204/automation.h @@ -13,7 +13,7 @@ template class SetAutoMuteAction : public Action { TEMPLATABLE_VALUE(uint8_t, auto_mute_mode) - void play(Ts... x) override { this->aic3204_->set_auto_mute_mode(this->auto_mute_mode_.value(x...)); } + void play(const Ts &...x) override { this->aic3204_->set_auto_mute_mode(this->auto_mute_mode_.value(x...)); } protected: AIC3204 *aic3204_; diff --git a/esphome/components/alarm_control_panel/automation.h b/esphome/components/alarm_control_panel/automation.h index 2177fb710f..db2ef78158 100644 --- a/esphome/components/alarm_control_panel/automation.h +++ b/esphome/components/alarm_control_panel/automation.h @@ -89,7 +89,7 @@ template class ArmAwayAction : public Action { TEMPLATABLE_VALUE(std::string, code) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->alarm_control_panel_->make_call(); auto code = this->code_.optional_value(x...); if (code.has_value()) { @@ -109,7 +109,7 @@ template class ArmHomeAction : public Action { TEMPLATABLE_VALUE(std::string, code) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->alarm_control_panel_->make_call(); auto code = this->code_.optional_value(x...); if (code.has_value()) { @@ -129,7 +129,7 @@ template class ArmNightAction : public Action { TEMPLATABLE_VALUE(std::string, code) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->alarm_control_panel_->make_call(); auto code = this->code_.optional_value(x...); if (code.has_value()) { @@ -149,7 +149,7 @@ template class DisarmAction : public Action { TEMPLATABLE_VALUE(std::string, code) - void play(Ts... x) override { this->alarm_control_panel_->disarm(this->code_.optional_value(x...)); } + void play(const Ts &...x) override { this->alarm_control_panel_->disarm(this->code_.optional_value(x...)); } protected: AlarmControlPanel *alarm_control_panel_; @@ -159,7 +159,7 @@ template class PendingAction : public Action { public: explicit PendingAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {} - void play(Ts... x) override { this->alarm_control_panel_->make_call().pending().perform(); } + void play(const Ts &...x) override { this->alarm_control_panel_->make_call().pending().perform(); } protected: AlarmControlPanel *alarm_control_panel_; @@ -169,7 +169,7 @@ template class TriggeredAction : public Action { public: explicit TriggeredAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {} - void play(Ts... x) override { this->alarm_control_panel_->make_call().triggered().perform(); } + void play(const Ts &...x) override { this->alarm_control_panel_->make_call().triggered().perform(); } protected: AlarmControlPanel *alarm_control_panel_; @@ -178,7 +178,7 @@ template class TriggeredAction : public Action { template class AlarmControlPanelCondition : public Condition { public: AlarmControlPanelCondition(AlarmControlPanel *parent) : parent_(parent) {} - bool check(Ts... x) override { + bool check(const Ts &...x) override { return this->parent_->is_state_armed(this->parent_->get_state()) || this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_TRIGGERED; } diff --git a/esphome/components/animation/animation.h b/esphome/components/animation/animation.h index c44e0060af..b33254df30 100644 --- a/esphome/components/animation/animation.h +++ b/esphome/components/animation/animation.h @@ -39,7 +39,7 @@ class Animation : public image::Image { template class AnimationNextFrameAction : public Action { public: AnimationNextFrameAction(Animation *parent) : parent_(parent) {} - void play(Ts... x) override { this->parent_->next_frame(); } + void play(const Ts &...x) override { this->parent_->next_frame(); } protected: Animation *parent_; @@ -48,7 +48,7 @@ template class AnimationNextFrameAction : public Action { template class AnimationPrevFrameAction : public Action { public: AnimationPrevFrameAction(Animation *parent) : parent_(parent) {} - void play(Ts... x) override { this->parent_->prev_frame(); } + void play(const Ts &...x) override { this->parent_->prev_frame(); } protected: Animation *parent_; @@ -58,7 +58,7 @@ template class AnimationSetFrameAction : public Action { public: AnimationSetFrameAction(Animation *parent) : parent_(parent) {} TEMPLATABLE_VALUE(uint16_t, frame) - void play(Ts... x) override { this->parent_->set_frame(this->frame_.value(x...)); } + void play(const Ts &...x) override { this->parent_->set_frame(this->frame_.value(x...)); } protected: Animation *parent_; diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index d29181250e..f1f44a266d 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -237,7 +237,7 @@ extern APIServer *global_api_server; // NOLINT(cppcoreguidelines-avoid-non-cons template class APIConnectedCondition : public Condition { public: - bool check(Ts... x) override { return global_api_server->is_connected(); } + bool check(const Ts &...x) override { return global_api_server->is_connected(); } }; } // namespace esphome::api diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 4343fcd0bb..d00e9e6257 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -133,7 +133,7 @@ template class HomeAssistantServiceCallAction : public Action *get_error_trigger() const { return this->error_trigger_; } #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES - void play(Ts... x) override { + void play(const Ts &...x) override { HomeassistantActionRequest resp; std::string service_value = this->service_.value(x...); resp.set_service(StringRef(service_value)); diff --git a/esphome/components/at581x/automation.h b/esphome/components/at581x/automation.h index 4863a87565..b1611a6758 100644 --- a/esphome/components/at581x/automation.h +++ b/esphome/components/at581x/automation.h @@ -10,7 +10,7 @@ namespace at581x { template class AT581XResetAction : public Action, public Parented { public: - void play(Ts... x) { this->parent_->reset_hardware_frontend(); } + void play(const Ts &...x) { this->parent_->reset_hardware_frontend(); } }; template class AT581XSettingsAction : public Action, public Parented { @@ -25,7 +25,7 @@ template class AT581XSettingsAction : public Action, publ TEMPLATABLE_VALUE(int, trigger_keep) TEMPLATABLE_VALUE(int, stage_gain) - void play(Ts... x) { + void play(const Ts &...x) { if (this->frequency_.has_value()) { int v = this->frequency_.value(x...); this->parent_->set_frequency(v); diff --git a/esphome/components/audio_adc/automation.h b/esphome/components/audio_adc/automation.h index 1b0bc2a6ad..0c42468479 100644 --- a/esphome/components/audio_adc/automation.h +++ b/esphome/components/audio_adc/automation.h @@ -13,7 +13,7 @@ template class SetMicGainAction : public Action { TEMPLATABLE_VALUE(float, mic_gain) - void play(Ts... x) override { this->audio_adc_->set_mic_gain(this->mic_gain_.value(x...)); } + void play(const Ts &...x) override { this->audio_adc_->set_mic_gain(this->mic_gain_.value(x...)); } protected: AudioAdc *audio_adc_; diff --git a/esphome/components/audio_dac/automation.h b/esphome/components/audio_dac/automation.h index b6cf2acaf4..3eb3441f3d 100644 --- a/esphome/components/audio_dac/automation.h +++ b/esphome/components/audio_dac/automation.h @@ -11,7 +11,7 @@ template class MuteOffAction : public Action { public: explicit MuteOffAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {} - void play(Ts... x) override { this->audio_dac_->set_mute_off(); } + void play(const Ts &...x) override { this->audio_dac_->set_mute_off(); } protected: AudioDac *audio_dac_; @@ -21,7 +21,7 @@ template class MuteOnAction : public Action { public: explicit MuteOnAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {} - void play(Ts... x) override { this->audio_dac_->set_mute_on(); } + void play(const Ts &...x) override { this->audio_dac_->set_mute_on(); } protected: AudioDac *audio_dac_; @@ -33,7 +33,7 @@ template class SetVolumeAction : public Action { TEMPLATABLE_VALUE(float, volume) - void play(Ts... x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); } + void play(const Ts &...x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); } protected: AudioDac *audio_dac_; diff --git a/esphome/components/binary_sensor/automation.h b/esphome/components/binary_sensor/automation.h index 0bc7b9acb3..f6971a2fc4 100644 --- a/esphome/components/binary_sensor/automation.h +++ b/esphome/components/binary_sensor/automation.h @@ -141,7 +141,7 @@ class StateChangeTrigger : public Trigger, optional > { template class BinarySensorCondition : public Condition { public: BinarySensorCondition(BinarySensor *parent, bool state) : parent_(parent), state_(state) {} - bool check(Ts... x) override { return this->parent_->state == this->state_; } + bool check(const Ts &...x) override { return this->parent_->state == this->state_; } protected: BinarySensor *parent_; @@ -153,7 +153,7 @@ template class BinarySensorPublishAction : public Action explicit BinarySensorPublishAction(BinarySensor *sensor) : sensor_(sensor) {} TEMPLATABLE_VALUE(bool, state) - void play(Ts... x) override { + void play(const Ts &...x) override { auto val = this->state_.value(x...); this->sensor_->publish_state(val); } @@ -166,7 +166,7 @@ template class BinarySensorInvalidateAction : public Actionsensor_->invalidate_state(); } + void play(const Ts &...x) override { this->sensor_->invalidate_state(); } protected: BinarySensor *sensor_; diff --git a/esphome/components/bl0906/bl0906.h b/esphome/components/bl0906/bl0906.h index 5a9ad0f028..493b645c89 100644 --- a/esphome/components/bl0906/bl0906.h +++ b/esphome/components/bl0906/bl0906.h @@ -89,7 +89,7 @@ class BL0906 : public PollingComponent, public uart::UARTDevice { template class ResetEnergyAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); } + void play(const Ts &...x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); } }; } // namespace bl0906 diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 55f1cb2f46..ce534501f3 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -123,9 +123,9 @@ template class BLEClientWriteAction : public Action, publ this->has_simple_value_ = true; } - void play(Ts... x) override {} + void play(const Ts &...x) override {} - void play_complex(Ts... x) override { + void play_complex(const Ts &...x) override { this->num_running_++; this->var_ = std::make_tuple(x...); auto value = this->has_simple_value_ ? this->value_.simple : this->value_.template_func(x...); @@ -229,7 +229,7 @@ template class BLEClientPasskeyReplyAction : public Actionvalue_.simple; @@ -266,7 +266,7 @@ template class BLEClientNumericComparisonReplyAction : public Ac public: BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; } - void play(Ts... x) override { + void play(const Ts &...x) override { esp_bd_addr_t remote_bda; memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); if (has_simple_value_) { @@ -299,7 +299,7 @@ template class BLEClientRemoveBondAction : public Action public: BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; } - void play(Ts... x) override { + void play(const Ts &...x) override { esp_bd_addr_t remote_bda; memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); esp_ble_remove_bond_device(remote_bda); @@ -334,9 +334,9 @@ template class BLEClientConnectAction : public Action, pu } // not used since we override play_complex_ - void play(Ts... x) override {} + void play(const Ts &...x) override {} - void play_complex(Ts... x) override { + void play_complex(const Ts &...x) override { // it makes no sense to have multiple instances of this running at the same time. // this would occur only if the same automation was re-triggered while still // running. So just cancel the second chain if this is detected. @@ -379,9 +379,9 @@ template class BLEClientDisconnectAction : public Action, } // not used since we override play_complex_ - void play(Ts... x) override {} + void play(const Ts &...x) override {} - void play_complex(Ts... x) override { + void play_complex(const Ts &...x) override { this->num_running_++; if (this->node_state == espbt::ClientState::IDLE) { this->play_next_(x...); diff --git a/esphome/components/button/automation.h b/esphome/components/button/automation.h index a5fb9f35b7..3b792eb5d7 100644 --- a/esphome/components/button/automation.h +++ b/esphome/components/button/automation.h @@ -11,7 +11,7 @@ template class PressAction : public Action { public: explicit PressAction(Button *button) : button_(button) {} - void play(Ts... x) override { this->button_->press(); } + void play(const Ts &...x) override { this->button_->press(); } protected: Button *button_; diff --git a/esphome/components/canbus/canbus.h b/esphome/components/canbus/canbus.h index 51d7c0830a..029eb278c0 100644 --- a/esphome/components/canbus/canbus.h +++ b/esphome/components/canbus/canbus.h @@ -129,7 +129,7 @@ template class CanbusSendAction : public Action, public P this->remote_transmission_request_ = remote_transmission_request; } - void play(Ts... x) override { + void play(const Ts &...x) override { auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_; auto use_extended_id = this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_; diff --git a/esphome/components/climate/automation.h b/esphome/components/climate/automation.h index a4d13ade58..36cc8f4f21 100644 --- a/esphome/components/climate/automation.h +++ b/esphome/components/climate/automation.h @@ -22,7 +22,7 @@ template class ControlAction : public Action { TEMPLATABLE_VALUE(std::string, custom_preset) TEMPLATABLE_VALUE(ClimateSwingMode, swing_mode) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->climate_->make_call(); call.set_mode(this->mode_.optional_value(x...)); call.set_target_temperature(this->target_temperature_.optional_value(x...)); diff --git a/esphome/components/cm1106/cm1106.h b/esphome/components/cm1106/cm1106.h index 3b78e17cf4..ad089bbe7d 100644 --- a/esphome/components/cm1106/cm1106.h +++ b/esphome/components/cm1106/cm1106.h @@ -30,7 +30,7 @@ template class CM1106CalibrateZeroAction : public Action public: CM1106CalibrateZeroAction(CM1106Component *cm1106) : cm1106_(cm1106) {} - void play(Ts... x) override { this->cm1106_->calibrate_zero(400); } + void play(const Ts &...x) override { this->cm1106_->calibrate_zero(400); } protected: CM1106Component *cm1106_; diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 6406ba52cb..752e0398c1 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -11,7 +11,7 @@ template class OpenAction : public Action { public: explicit OpenAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->make_call().set_command_open().perform(); } + void play(const Ts &...x) override { this->cover_->make_call().set_command_open().perform(); } protected: Cover *cover_; @@ -21,7 +21,7 @@ template class CloseAction : public Action { public: explicit CloseAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->make_call().set_command_close().perform(); } + void play(const Ts &...x) override { this->cover_->make_call().set_command_close().perform(); } protected: Cover *cover_; @@ -31,7 +31,7 @@ template class StopAction : public Action { public: explicit StopAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->make_call().set_command_stop().perform(); } + void play(const Ts &...x) override { this->cover_->make_call().set_command_stop().perform(); } protected: Cover *cover_; @@ -41,7 +41,7 @@ template class ToggleAction : public Action { public: explicit ToggleAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->make_call().set_command_toggle().perform(); } + void play(const Ts &...x) override { this->cover_->make_call().set_command_toggle().perform(); } protected: Cover *cover_; @@ -55,7 +55,7 @@ template class ControlAction : public Action { TEMPLATABLE_VALUE(float, position) TEMPLATABLE_VALUE(float, tilt) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->cover_->make_call(); if (this->stop_.has_value()) call.set_stop(this->stop_.value(x...)); @@ -77,7 +77,7 @@ template class CoverPublishAction : public Action { TEMPLATABLE_VALUE(float, tilt) TEMPLATABLE_VALUE(CoverOperation, current_operation) - void play(Ts... x) override { + void play(const Ts &...x) override { if (this->position_.has_value()) this->cover_->position = this->position_.value(x...); if (this->tilt_.has_value()) @@ -94,7 +94,7 @@ template class CoverPublishAction : public Action { template class CoverIsOpenCondition : public Condition { public: CoverIsOpenCondition(Cover *cover) : cover_(cover) {} - bool check(Ts... x) override { return this->cover_->is_fully_open(); } + bool check(const Ts &...x) override { return this->cover_->is_fully_open(); } protected: Cover *cover_; @@ -103,7 +103,7 @@ template class CoverIsOpenCondition : public Condition { template class CoverIsClosedCondition : public Condition { public: CoverIsClosedCondition(Cover *cover) : cover_(cover) {} - bool check(Ts... x) override { return this->cover_->is_fully_closed(); } + bool check(const Ts &...x) override { return this->cover_->is_fully_closed(); } protected: Cover *cover_; diff --git a/esphome/components/cs5460a/cs5460a.h b/esphome/components/cs5460a/cs5460a.h index 15ae04f3c6..11b13f5851 100644 --- a/esphome/components/cs5460a/cs5460a.h +++ b/esphome/components/cs5460a/cs5460a.h @@ -114,7 +114,7 @@ template class CS5460ARestartAction : public Action { public: CS5460ARestartAction(CS5460AComponent *cs5460a) : cs5460a_(cs5460a) {} - void play(Ts... x) override { cs5460a_->restart(); } + void play(const Ts &...x) override { cs5460a_->restart(); } protected: CS5460AComponent *cs5460a_; diff --git a/esphome/components/datetime/date_entity.h b/esphome/components/datetime/date_entity.h index fcbb46cf17..ba2edb127a 100644 --- a/esphome/components/datetime/date_entity.h +++ b/esphome/components/datetime/date_entity.h @@ -101,7 +101,7 @@ template class DateSetAction : public Action, public Pare public: TEMPLATABLE_VALUE(ESPTime, date) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->parent_->make_call(); if (this->date_.has_value()) { diff --git a/esphome/components/datetime/datetime_entity.h b/esphome/components/datetime/datetime_entity.h index 275eedfd3b..43bff5a181 100644 --- a/esphome/components/datetime/datetime_entity.h +++ b/esphome/components/datetime/datetime_entity.h @@ -124,7 +124,7 @@ template class DateTimeSetAction : public Action, public public: TEMPLATABLE_VALUE(ESPTime, datetime) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->parent_->make_call(); if (this->datetime_.has_value()) { diff --git a/esphome/components/datetime/time_entity.h b/esphome/components/datetime/time_entity.h index e79b8c225d..c5cbeb52da 100644 --- a/esphome/components/datetime/time_entity.h +++ b/esphome/components/datetime/time_entity.h @@ -103,7 +103,7 @@ template class TimeSetAction : public Action, public Pare public: TEMPLATABLE_VALUE(ESPTime, time) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->parent_->make_call(); if (this->time_.has_value()) { diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 38744163c7..80381e767c 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -148,7 +148,7 @@ template class EnterDeepSleepAction : public Action { void set_time(time::RealTimeClock *time) { this->time_ = time; } #endif - void play(Ts... x) override { + void play(const Ts &...x) override { if (this->sleep_duration_.has_value()) { this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...)); } @@ -207,12 +207,12 @@ template class EnterDeepSleepAction : public Action { template class PreventDeepSleepAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->prevent_deep_sleep(); } + void play(const Ts &...x) override { this->parent_->prevent_deep_sleep(); } }; template class AllowDeepSleepAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->allow_deep_sleep(); } + void play(const Ts &...x) override { this->parent_->allow_deep_sleep(); } }; } // namespace deep_sleep diff --git a/esphome/components/dfplayer/dfplayer.h b/esphome/components/dfplayer/dfplayer.h index d2ec0a2310..03d2230ca6 100644 --- a/esphome/components/dfplayer/dfplayer.h +++ b/esphome/components/dfplayer/dfplayer.h @@ -77,7 +77,7 @@ class DFPlayer : public uart::UARTDevice, public Component { class ACTION_CLASS : /* NOLINT */ \ public Action, \ public Parented { \ - void play(Ts... x) override { this->parent_->ACTION_METHOD(); } \ + void play(const Ts &...x) override { this->parent_->ACTION_METHOD(); } \ }; DFPLAYER_SIMPLE_ACTION(NextAction, next) @@ -87,7 +87,7 @@ template class PlayMp3Action : public Action, public Pare public: TEMPLATABLE_VALUE(uint16_t, file) - void play(Ts... x) override { + void play(const Ts &...x) override { auto file = this->file_.value(x...); this->parent_->play_mp3(file); } @@ -98,7 +98,7 @@ template class PlayFileAction : public Action, public Par TEMPLATABLE_VALUE(uint16_t, file) TEMPLATABLE_VALUE(bool, loop) - void play(Ts... x) override { + void play(const Ts &...x) override { auto file = this->file_.value(x...); auto loop = this->loop_.value(x...); if (loop) { @@ -115,7 +115,7 @@ template class PlayFolderAction : public Action, public P TEMPLATABLE_VALUE(uint16_t, file) TEMPLATABLE_VALUE(bool, loop) - void play(Ts... x) override { + void play(const Ts &...x) override { auto folder = this->folder_.value(x...); auto file = this->file_.value(x...); auto loop = this->loop_.value(x...); @@ -131,7 +131,7 @@ template class SetDeviceAction : public Action, public Pa public: TEMPLATABLE_VALUE(Device, device) - void play(Ts... x) override { + void play(const Ts &...x) override { auto device = this->device_.value(x...); this->parent_->set_device(device); } @@ -141,7 +141,7 @@ template class SetVolumeAction : public Action, public Pa public: TEMPLATABLE_VALUE(uint8_t, volume) - void play(Ts... x) override { + void play(const Ts &...x) override { auto volume = this->volume_.value(x...); this->parent_->set_volume(volume); } @@ -151,7 +151,7 @@ template class SetEqAction : public Action, public Parent public: TEMPLATABLE_VALUE(EqPreset, eq) - void play(Ts... x) override { + void play(const Ts &...x) override { auto eq = this->eq_.value(x...); this->parent_->set_eq(eq); } @@ -168,7 +168,7 @@ DFPLAYER_SIMPLE_ACTION(VolumeDownAction, volume_down) template class DFPlayerIsPlayingCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->is_playing(); } + bool check(const Ts &...x) override { return this->parent_->is_playing(); } }; class DFPlayerFinishedPlaybackTrigger : public Trigger<> { diff --git a/esphome/components/dfrobot_sen0395/automation.h b/esphome/components/dfrobot_sen0395/automation.h index 3f69e482b7..422555d6eb 100644 --- a/esphome/components/dfrobot_sen0395/automation.h +++ b/esphome/components/dfrobot_sen0395/automation.h @@ -11,7 +11,7 @@ namespace dfrobot_sen0395 { template class DfrobotSen0395ResetAction : public Action, public Parented { public: - void play(Ts... x) { this->parent_->enqueue(make_unique()); } + void play(const Ts &...x) { this->parent_->enqueue(make_unique()); } }; template @@ -33,7 +33,7 @@ class DfrobotSen0395SettingsAction : public Action, public Parentedparent_->enqueue(make_unique(0)); if (this->factory_reset_.has_value() && this->factory_reset_.value(x...) == true) { this->parent_->enqueue(make_unique()); diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index 97b0a4e8d7..47d40915aa 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -819,7 +819,7 @@ template class DisplayPageShowAction : public Action { public: TEMPLATABLE_VALUE(DisplayPage *, page) - void play(Ts... x) override { + void play(const Ts &...x) override { auto *page = this->page_.value(x...); if (page != nullptr) { page->show(); @@ -831,7 +831,7 @@ template class DisplayPageShowNextAction : public Action public: DisplayPageShowNextAction(Display *buffer) : buffer_(buffer) {} - void play(Ts... x) override { this->buffer_->show_next_page(); } + void play(const Ts &...x) override { this->buffer_->show_next_page(); } Display *buffer_; }; @@ -840,7 +840,7 @@ template class DisplayPageShowPrevAction : public Action public: DisplayPageShowPrevAction(Display *buffer) : buffer_(buffer) {} - void play(Ts... x) override { this->buffer_->show_prev_page(); } + void play(const Ts &...x) override { this->buffer_->show_prev_page(); } Display *buffer_; }; @@ -850,7 +850,7 @@ template class DisplayIsDisplayingPageCondition : public Conditi DisplayIsDisplayingPageCondition(Display *parent) : parent_(parent) {} void set_page(DisplayPage *page) { this->page_ = page; } - bool check(Ts... x) override { return this->parent_->get_active_page() == this->page_; } + bool check(const Ts &...x) override { return this->parent_->get_active_page() == this->page_; } protected: Display *parent_; diff --git a/esphome/components/display_menu_base/automation.h b/esphome/components/display_menu_base/automation.h index d5394a1e0c..9c64794cef 100644 --- a/esphome/components/display_menu_base/automation.h +++ b/esphome/components/display_menu_base/automation.h @@ -10,7 +10,7 @@ template class UpAction : public Action { public: explicit UpAction(DisplayMenuComponent *menu) : menu_(menu) {} - void play(Ts... x) override { this->menu_->up(); } + void play(const Ts &...x) override { this->menu_->up(); } protected: DisplayMenuComponent *menu_; @@ -20,7 +20,7 @@ template class DownAction : public Action { public: explicit DownAction(DisplayMenuComponent *menu) : menu_(menu) {} - void play(Ts... x) override { this->menu_->down(); } + void play(const Ts &...x) override { this->menu_->down(); } protected: DisplayMenuComponent *menu_; @@ -30,7 +30,7 @@ template class LeftAction : public Action { public: explicit LeftAction(DisplayMenuComponent *menu) : menu_(menu) {} - void play(Ts... x) override { this->menu_->left(); } + void play(const Ts &...x) override { this->menu_->left(); } protected: DisplayMenuComponent *menu_; @@ -40,7 +40,7 @@ template class RightAction : public Action { public: explicit RightAction(DisplayMenuComponent *menu) : menu_(menu) {} - void play(Ts... x) override { this->menu_->right(); } + void play(const Ts &...x) override { this->menu_->right(); } protected: DisplayMenuComponent *menu_; @@ -50,7 +50,7 @@ template class EnterAction : public Action { public: explicit EnterAction(DisplayMenuComponent *menu) : menu_(menu) {} - void play(Ts... x) override { this->menu_->enter(); } + void play(const Ts &...x) override { this->menu_->enter(); } protected: DisplayMenuComponent *menu_; @@ -60,7 +60,7 @@ template class ShowAction : public Action { public: explicit ShowAction(DisplayMenuComponent *menu) : menu_(menu) {} - void play(Ts... x) override { this->menu_->show(); } + void play(const Ts &...x) override { this->menu_->show(); } protected: DisplayMenuComponent *menu_; @@ -70,7 +70,7 @@ template class HideAction : public Action { public: explicit HideAction(DisplayMenuComponent *menu) : menu_(menu) {} - void play(Ts... x) override { this->menu_->hide(); } + void play(const Ts &...x) override { this->menu_->hide(); } protected: DisplayMenuComponent *menu_; @@ -80,7 +80,7 @@ template class ShowMainAction : public Action { public: explicit ShowMainAction(DisplayMenuComponent *menu) : menu_(menu) {} - void play(Ts... x) override { this->menu_->show_main(); } + void play(const Ts &...x) override { this->menu_->show_main(); } protected: DisplayMenuComponent *menu_; @@ -88,7 +88,7 @@ template class ShowMainAction : public Action { template class IsActiveCondition : public Condition { public: explicit IsActiveCondition(DisplayMenuComponent *menu) : menu_(menu) {} - bool check(Ts... x) override { return this->menu_->is_active(); } + bool check(const Ts &...x) override { return this->menu_->is_active(); } protected: DisplayMenuComponent *menu_; diff --git a/esphome/components/ds1307/ds1307.h b/esphome/components/ds1307/ds1307.h index 2e9ac2275c..f7f06253b7 100644 --- a/esphome/components/ds1307/ds1307.h +++ b/esphome/components/ds1307/ds1307.h @@ -59,12 +59,12 @@ class DS1307Component : public time::RealTimeClock, public i2c::I2CDevice { template class WriteAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->write_time(); } + void play(const Ts &...x) override { this->parent_->write_time(); } }; template class ReadAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->read_time(); } + void play(const Ts &...x) override { this->parent_->read_time(); } }; } // namespace ds1307 } // namespace esphome diff --git a/esphome/components/duty_time/duty_time_sensor.h b/esphome/components/duty_time/duty_time_sensor.h index 18280f8e21..d9fb2a6d60 100644 --- a/esphome/components/duty_time/duty_time_sensor.h +++ b/esphome/components/duty_time/duty_time_sensor.h @@ -51,15 +51,15 @@ class DutyTimeSensor : public sensor::Sensor, public PollingComponent { template class BaseAction : public Action, public Parented {}; template class StartAction : public BaseAction { - void play(Ts... x) override { this->parent_->start(); } + void play(const Ts &...x) override { this->parent_->start(); } }; template class StopAction : public BaseAction { - void play(Ts... x) override { this->parent_->stop(); } + void play(const Ts &...x) override { this->parent_->stop(); } }; template class ResetAction : public BaseAction { - void play(Ts... x) override { this->parent_->reset(); } + void play(const Ts &...x) override { this->parent_->reset(); } }; template class RunningCondition : public Condition, public Parented { @@ -67,7 +67,7 @@ template class RunningCondition : public Condition, publi explicit RunningCondition(DutyTimeSensor *parent, bool state) : Parented(parent), state_(state) {} protected: - bool check(Ts... x) override { return this->parent_->is_running() == this->state_; } + bool check(const Ts &...x) override { return this->parent_->is_running() == this->state_; } bool state_; }; diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index c3e1ec2ce6..2fb60bb822 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -214,17 +214,17 @@ extern ESP32BLE *global_ble; template class BLEEnabledCondition : public Condition { public: - bool check(Ts... x) override { return global_ble->is_active(); } + bool check(const Ts &...x) override { return global_ble->is_active(); } }; template class BLEEnableAction : public Action { public: - void play(Ts... x) override { global_ble->enable(); } + void play(const Ts &...x) override { global_ble->enable(); } }; template class BLEDisableAction : public Action { public: - void play(Ts... x) override { global_ble->disable(); } + void play(const Ts &...x) override { global_ble->disable(); } }; } // namespace esphome::esp32_ble diff --git a/esphome/components/esp32_ble_server/ble_server_automations.h b/esphome/components/esp32_ble_server/ble_server_automations.h index 543b1153fc..fe18600280 100644 --- a/esphome/components/esp32_ble_server/ble_server_automations.h +++ b/esphome/components/esp32_ble_server/ble_server_automations.h @@ -71,7 +71,7 @@ template class BLECharacteristicSetValueAction : public Action, buffer) void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); } - void play(Ts... x) override { + void play(const Ts &...x) override { // If the listener is already set, do nothing if (BLECharacteristicSetValueActionManager::get_instance()->has_listener(this->parent_)) return; @@ -96,7 +96,7 @@ template class BLECharacteristicSetValueAction : public Action class BLECharacteristicNotifyAction : public Action { public: BLECharacteristicNotifyAction(BLECharacteristic *characteristic) : parent_(characteristic) {} - void play(Ts... x) override { + void play(const Ts &...x) override { #ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION // Call the pre-notify event BLECharacteristicSetValueActionManager::get_instance()->emit_pre_notify(this->parent_); @@ -116,7 +116,7 @@ template class BLEDescriptorSetValueAction : public Action, buffer) void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); } - void play(Ts... x) override { this->parent_->set_value(this->buffer_.value(x...)); } + void play(const Ts &...x) override { this->parent_->set_value(this->buffer_.value(x...)); } protected: BLEDescriptor *parent_; diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index 784f2eaaa2..054cbaa7df 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -96,7 +96,7 @@ template class ESP32BLEStartScanAction : public Action { public: ESP32BLEStartScanAction(ESP32BLETracker *parent) : parent_(parent) {} TEMPLATABLE_VALUE(bool, continuous) - void play(Ts... x) override { + void play(const Ts &...x) override { this->parent_->set_scan_continuous(this->continuous_.value(x...)); this->parent_->start_scan(); } @@ -107,7 +107,7 @@ template class ESP32BLEStartScanAction : public Action { template class ESP32BLEStopScanAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->stop_scan(); } + void play(const Ts &...x) override { this->parent_->stop_scan(); } }; } // namespace esphome::esp32_ble_tracker diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.h b/esphome/components/esp8266_pwm/esp8266_pwm.h index 79530aacd4..4b021fc462 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.h +++ b/esphome/components/esp8266_pwm/esp8266_pwm.h @@ -40,7 +40,7 @@ template class SetFrequencyAction : public Action { SetFrequencyAction(ESP8266PWM *parent) : parent_(parent) {} TEMPLATABLE_VALUE(float, frequency); - void play(Ts... x) { + void play(const Ts &...x) { float freq = this->frequency_.value(x...); this->parent_->update_frequency(freq); } diff --git a/esphome/components/esp_ldo/esp_ldo.h b/esphome/components/esp_ldo/esp_ldo.h index bafa32db6b..9edd303e16 100644 --- a/esphome/components/esp_ldo/esp_ldo.h +++ b/esphome/components/esp_ldo/esp_ldo.h @@ -34,7 +34,7 @@ template class AdjustAction : public Action { TEMPLATABLE_VALUE(float, voltage) - void play(Ts... x) override { this->ldo_->adjust_voltage(this->voltage_.value(x...)); } + void play(const Ts &...x) override { this->ldo_->adjust_voltage(this->voltage_.value(x...)); } protected: EspLdo *ldo_; diff --git a/esphome/components/espnow/automation.h b/esphome/components/espnow/automation.h index 5415b088fd..0b26681400 100644 --- a/esphome/components/espnow/automation.h +++ b/esphome/components/espnow/automation.h @@ -36,7 +36,7 @@ template class SendAction : public Action, public Parente void set_wait_for_sent(bool wait_for_sent) { this->flags_.wait_for_sent = wait_for_sent; } void set_continue_on_error(bool continue_on_error) { this->flags_.continue_on_error = continue_on_error; } - void play_complex(Ts... x) override { + void play_complex(const Ts &...x) override { this->num_running_++; send_callback_t send_callback = [this, x...](esp_err_t status) { if (status == ESP_OK) { @@ -67,7 +67,7 @@ template class SendAction : public Action, public Parente } } - void play(Ts... x) override { /* ignore - see play_complex */ + void play(const Ts &...x) override { /* ignore - see play_complex */ } void stop() override { @@ -90,7 +90,7 @@ template class AddPeerAction : public Action, public Pare TEMPLATABLE_VALUE(peer_address_t, address); public: - void play(Ts... x) override { + void play(const Ts &...x) override { peer_address_t address = this->address_.value(x...); this->parent_->add_peer(address.data()); } @@ -100,7 +100,7 @@ template class DeletePeerAction : public Action, public P TEMPLATABLE_VALUE(peer_address_t, address); public: - void play(Ts... x) override { + void play(const Ts &...x) override { peer_address_t address = this->address_.value(x...); this->parent_->del_peer(address.data()); } @@ -109,7 +109,7 @@ template class DeletePeerAction : public Action, public P template class SetChannelAction : public Action, public Parented { public: TEMPLATABLE_VALUE(uint8_t, channel) - void play(Ts... x) override { + void play(const Ts &...x) override { if (this->parent_->is_wifi_enabled()) { return; } diff --git a/esphome/components/event/automation.h b/esphome/components/event/automation.h index 9ebcb654a0..5bdba18687 100644 --- a/esphome/components/event/automation.h +++ b/esphome/components/event/automation.h @@ -11,7 +11,7 @@ template class TriggerEventAction : public Action, public public: TEMPLATABLE_VALUE(std::string, event_type) - void play(Ts... x) override { this->parent_->trigger(this->event_type_.value(x...)); } + void play(const Ts &...x) override { this->parent_->trigger(this->event_type_.value(x...)); } }; class EventTrigger : public Trigger { diff --git a/esphome/components/ezo/automation.h b/esphome/components/ezo/automation.h index 19427b9159..a4a6fa3014 100644 --- a/esphome/components/ezo/automation.h +++ b/esphome/components/ezo/automation.h @@ -17,35 +17,35 @@ class LedTrigger : public Trigger { class CustomTrigger : public Trigger { public: explicit CustomTrigger(EZOSensor *ezo) { - ezo->add_custom_callback([this](std::string value) { this->trigger(std::move(value)); }); + ezo->add_custom_callback([this](const std::string &value) { this->trigger(value); }); } }; class TTrigger : public Trigger { public: explicit TTrigger(EZOSensor *ezo) { - ezo->add_t_callback([this](std::string value) { this->trigger(std::move(value)); }); + ezo->add_t_callback([this](const std::string &value) { this->trigger(value); }); } }; class CalibrationTrigger : public Trigger { public: explicit CalibrationTrigger(EZOSensor *ezo) { - ezo->add_calibration_callback([this](std::string value) { this->trigger(std::move(value)); }); + ezo->add_calibration_callback([this](const std::string &value) { this->trigger(value); }); } }; class SlopeTrigger : public Trigger { public: explicit SlopeTrigger(EZOSensor *ezo) { - ezo->add_slope_callback([this](std::string value) { this->trigger(std::move(value)); }); + ezo->add_slope_callback([this](const std::string &value) { this->trigger(value); }); } }; class DeviceInformationTrigger : public Trigger { public: explicit DeviceInformationTrigger(EZOSensor *ezo) { - ezo->add_device_infomation_callback([this](std::string value) { this->trigger(std::move(value)); }); + ezo->add_device_infomation_callback([this](const std::string &value) { this->trigger(value); }); } }; diff --git a/esphome/components/ezo_pmp/ezo_pmp.h b/esphome/components/ezo_pmp/ezo_pmp.h index 671e124810..d4917e7f4b 100644 --- a/esphome/components/ezo_pmp/ezo_pmp.h +++ b/esphome/components/ezo_pmp/ezo_pmp.h @@ -119,7 +119,7 @@ template class EzoPMPFindAction : public Action { public: EzoPMPFindAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} - void play(Ts... x) override { this->ezopmp_->find(); } + void play(const Ts &...x) override { this->ezopmp_->find(); } protected: EzoPMP *ezopmp_; @@ -129,7 +129,7 @@ template class EzoPMPDoseContinuouslyAction : public Actionezopmp_->dose_continuously(); } + void play(const Ts &...x) override { this->ezopmp_->dose_continuously(); } protected: EzoPMP *ezopmp_; @@ -139,7 +139,7 @@ template class EzoPMPDoseVolumeAction : public Action { public: EzoPMPDoseVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} - void play(Ts... x) override { this->ezopmp_->dose_volume(this->volume_.value(x...)); } + void play(const Ts &...x) override { this->ezopmp_->dose_volume(this->volume_.value(x...)); } TEMPLATABLE_VALUE(double, volume) protected: @@ -150,7 +150,7 @@ template class EzoPMPDoseVolumeOverTimeAction : public Actionezopmp_->dose_volume_over_time(this->volume_.value(x...), this->duration_.value(x...)); } TEMPLATABLE_VALUE(double, volume) @@ -164,7 +164,7 @@ template class EzoPMPDoseWithConstantFlowRateAction : public Act public: EzoPMPDoseWithConstantFlowRateAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} - void play(Ts... x) override { + void play(const Ts &...x) override { this->ezopmp_->dose_with_constant_flow_rate(this->volume_.value(x...), this->duration_.value(x...)); } TEMPLATABLE_VALUE(double, volume) @@ -178,7 +178,7 @@ template class EzoPMPSetCalibrationVolumeAction : public Action< public: EzoPMPSetCalibrationVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} - void play(Ts... x) override { this->ezopmp_->set_calibration_volume(this->volume_.value(x...)); } + void play(const Ts &...x) override { this->ezopmp_->set_calibration_volume(this->volume_.value(x...)); } TEMPLATABLE_VALUE(double, volume) protected: @@ -189,7 +189,7 @@ template class EzoPMPClearTotalVolumeDispensedAction : public Ac public: EzoPMPClearTotalVolumeDispensedAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} - void play(Ts... x) override { this->ezopmp_->clear_total_volume_dosed(); } + void play(const Ts &...x) override { this->ezopmp_->clear_total_volume_dosed(); } protected: EzoPMP *ezopmp_; @@ -199,7 +199,7 @@ template class EzoPMPClearCalibrationAction : public Actionezopmp_->clear_calibration(); } + void play(const Ts &...x) override { this->ezopmp_->clear_calibration(); } protected: EzoPMP *ezopmp_; @@ -209,7 +209,7 @@ template class EzoPMPPauseDosingAction : public Action { public: EzoPMPPauseDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} - void play(Ts... x) override { this->ezopmp_->pause_dosing(); } + void play(const Ts &...x) override { this->ezopmp_->pause_dosing(); } protected: EzoPMP *ezopmp_; @@ -219,7 +219,7 @@ template class EzoPMPStopDosingAction : public Action { public: EzoPMPStopDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} - void play(Ts... x) override { this->ezopmp_->stop_dosing(); } + void play(const Ts &...x) override { this->ezopmp_->stop_dosing(); } protected: EzoPMP *ezopmp_; @@ -229,7 +229,7 @@ template class EzoPMPChangeI2CAddressAction : public Actionezopmp_->change_i2c_address(this->address_.value(x...)); } + void play(const Ts &...x) override { this->ezopmp_->change_i2c_address(this->address_.value(x...)); } TEMPLATABLE_VALUE(int, address) protected: @@ -240,7 +240,7 @@ template class EzoPMPArbitraryCommandAction : public Actionezopmp_->exec_arbitrary_command(this->command_.value(x...)); } + void play(const Ts &...x) override { this->ezopmp_->exec_arbitrary_command(this->command_.value(x...)); } TEMPLATABLE_VALUE(std::string, command) protected: diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index ae0af1a9bd..ce1db6fc64 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -15,7 +15,7 @@ template class TurnOnAction : public Action { TEMPLATABLE_VALUE(int, speed) TEMPLATABLE_VALUE(FanDirection, direction) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->state_->turn_on(); if (this->oscillating_.has_value()) { call.set_oscillating(this->oscillating_.value(x...)); @@ -36,7 +36,7 @@ template class TurnOffAction : public Action { public: explicit TurnOffAction(Fan *state) : state_(state) {} - void play(Ts... x) override { this->state_->turn_off().perform(); } + void play(const Ts &...x) override { this->state_->turn_off().perform(); } Fan *state_; }; @@ -45,7 +45,7 @@ template class ToggleAction : public Action { public: explicit ToggleAction(Fan *state) : state_(state) {} - void play(Ts... x) override { this->state_->toggle().perform(); } + void play(const Ts &...x) override { this->state_->toggle().perform(); } Fan *state_; }; @@ -56,7 +56,7 @@ template class CycleSpeedAction : public Action { TEMPLATABLE_VALUE(bool, no_off_cycle) - void play(Ts... x) override { + void play(const Ts &...x) override { // check to see if fan supports speeds and is on if (this->state_->get_traits().supported_speed_count()) { if (this->state_->state) { @@ -97,7 +97,7 @@ template class CycleSpeedAction : public Action { template class FanIsOnCondition : public Condition { public: explicit FanIsOnCondition(Fan *state) : state_(state) {} - bool check(Ts... x) override { return this->state_->state; } + bool check(const Ts &...x) override { return this->state_->state; } protected: Fan *state_; @@ -105,7 +105,7 @@ template class FanIsOnCondition : public Condition { template class FanIsOffCondition : public Condition { public: explicit FanIsOffCondition(Fan *state) : state_(state) {} - bool check(Ts... x) override { return !this->state_->state; } + bool check(const Ts &...x) override { return !this->state_->state; } protected: Fan *state_; diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.h b/esphome/components/fingerprint_grow/fingerprint_grow.h index 590c709c22..370b26f56a 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.h +++ b/esphome/components/fingerprint_grow/fingerprint_grow.h @@ -273,7 +273,7 @@ template class EnrollmentAction : public Action, public P TEMPLATABLE_VALUE(uint16_t, finger_id) TEMPLATABLE_VALUE(uint8_t, num_scans) - void play(Ts... x) override { + void play(const Ts &...x) override { auto finger_id = this->finger_id_.value(x...); auto num_scans = this->num_scans_.value(x...); if (num_scans) { @@ -287,14 +287,14 @@ template class EnrollmentAction : public Action, public P template class CancelEnrollmentAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->finish_enrollment(1); } + void play(const Ts &...x) override { this->parent_->finish_enrollment(1); } }; template class DeleteAction : public Action, public Parented { public: TEMPLATABLE_VALUE(uint16_t, finger_id) - void play(Ts... x) override { + void play(const Ts &...x) override { auto finger_id = this->finger_id_.value(x...); this->parent_->delete_fingerprint(finger_id); } @@ -302,14 +302,14 @@ template class DeleteAction : public Action, public Paren template class DeleteAllAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->delete_all_fingerprints(); } + void play(const Ts &...x) override { this->parent_->delete_all_fingerprints(); } }; template class LEDControlAction : public Action, public Parented { public: TEMPLATABLE_VALUE(bool, state) - void play(Ts... x) override { + void play(const Ts &...x) override { auto state = this->state_.value(x...); this->parent_->led_control(state); } @@ -322,7 +322,7 @@ template class AuraLEDControlAction : public Action, publ TEMPLATABLE_VALUE(uint8_t, color) TEMPLATABLE_VALUE(uint8_t, count) - void play(Ts... x) override { + void play(const Ts &...x) override { auto state = this->state_.value(x...); auto speed = this->speed_.value(x...); auto color = this->color_.value(x...); diff --git a/esphome/components/globals/globals_component.h b/esphome/components/globals/globals_component.h index 4c6a12aa72..1d2a08937e 100644 --- a/esphome/components/globals/globals_component.h +++ b/esphome/components/globals/globals_component.h @@ -134,7 +134,7 @@ template class GlobalVarSetAction : public Actionparent_->value() = this->value_.value(x...); } + void play(const Ts &...x) override { this->parent_->value() = this->value_.value(x...); } protected: C *parent_; diff --git a/esphome/components/grove_tb6612fng/grove_tb6612fng.h b/esphome/components/grove_tb6612fng/grove_tb6612fng.h index 68281117e7..a36cb85cff 100644 --- a/esphome/components/grove_tb6612fng/grove_tb6612fng.h +++ b/esphome/components/grove_tb6612fng/grove_tb6612fng.h @@ -168,7 +168,7 @@ class GROVETB6612FNGMotorRunAction : public Action, public Parentedchannel_.value(x...); auto speed = this->speed_.value(x...); this->parent_->dc_motor_run(channel, speed); @@ -180,7 +180,7 @@ class GROVETB6612FNGMotorBrakeAction : public Action, public Parentedparent_->dc_motor_brake(this->channel_.value(x...)); } + void play(const Ts &...x) override { this->parent_->dc_motor_brake(this->channel_.value(x...)); } }; template @@ -188,19 +188,19 @@ class GROVETB6612FNGMotorStopAction : public Action, public Parentedparent_->dc_motor_stop(this->channel_.value(x...)); } + void play(const Ts &...x) override { this->parent_->dc_motor_stop(this->channel_.value(x...)); } }; template class GROVETB6612FNGMotorStandbyAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->standby(); } + void play(const Ts &...x) override { this->parent_->standby(); } }; template class GROVETB6612FNGMotorNoStandbyAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->not_standby(); } + void play(const Ts &...x) override { this->parent_->not_standby(); } }; template @@ -208,7 +208,7 @@ class GROVETB6612FNGMotorChangeAddressAction : public Action, public Pare public: TEMPLATABLE_VALUE(uint8_t, address) - void play(Ts... x) override { this->parent_->set_i2c_addr(this->address_.value(x...)); } + void play(const Ts &...x) override { this->parent_->set_i2c_addr(this->address_.value(x...)); } }; } // namespace grove_tb6612fng diff --git a/esphome/components/haier/automation.h b/esphome/components/haier/automation.h index 55df7ecc1d..c1ce7c01ea 100644 --- a/esphome/components/haier/automation.h +++ b/esphome/components/haier/automation.h @@ -10,7 +10,7 @@ namespace haier { template class DisplayOnAction : public Action { public: DisplayOnAction(HaierClimateBase *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->set_display_state(true); } + void play(const Ts &...x) { this->parent_->set_display_state(true); } protected: HaierClimateBase *parent_; @@ -19,7 +19,7 @@ template class DisplayOnAction : public Action { template class DisplayOffAction : public Action { public: DisplayOffAction(HaierClimateBase *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->set_display_state(false); } + void play(const Ts &...x) { this->parent_->set_display_state(false); } protected: HaierClimateBase *parent_; @@ -28,7 +28,7 @@ template class DisplayOffAction : public Action { template class BeeperOnAction : public Action { public: BeeperOnAction(HonClimate *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->set_beeper_state(true); } + void play(const Ts &...x) { this->parent_->set_beeper_state(true); } protected: HonClimate *parent_; @@ -37,7 +37,7 @@ template class BeeperOnAction : public Action { template class BeeperOffAction : public Action { public: BeeperOffAction(HonClimate *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->set_beeper_state(false); } + void play(const Ts &...x) { this->parent_->set_beeper_state(false); } protected: HonClimate *parent_; @@ -47,7 +47,7 @@ template class VerticalAirflowAction : public Action { public: VerticalAirflowAction(HonClimate *parent) : parent_(parent) {} TEMPLATABLE_VALUE(hon_protocol::VerticalSwingMode, direction) - void play(Ts... x) { this->parent_->set_vertical_airflow(this->direction_.value(x...)); } + void play(const Ts &...x) { this->parent_->set_vertical_airflow(this->direction_.value(x...)); } protected: HonClimate *parent_; @@ -57,7 +57,7 @@ template class HorizontalAirflowAction : public Action { public: HorizontalAirflowAction(HonClimate *parent) : parent_(parent) {} TEMPLATABLE_VALUE(hon_protocol::HorizontalSwingMode, direction) - void play(Ts... x) { this->parent_->set_horizontal_airflow(this->direction_.value(x...)); } + void play(const Ts &...x) { this->parent_->set_horizontal_airflow(this->direction_.value(x...)); } protected: HonClimate *parent_; @@ -66,7 +66,7 @@ template class HorizontalAirflowAction : public Action { template class HealthOnAction : public Action { public: HealthOnAction(HaierClimateBase *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->set_health_mode(true); } + void play(const Ts &...x) { this->parent_->set_health_mode(true); } protected: HaierClimateBase *parent_; @@ -75,7 +75,7 @@ template class HealthOnAction : public Action { template class HealthOffAction : public Action { public: HealthOffAction(HaierClimateBase *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->set_health_mode(false); } + void play(const Ts &...x) { this->parent_->set_health_mode(false); } protected: HaierClimateBase *parent_; @@ -84,7 +84,7 @@ template class HealthOffAction : public Action { template class StartSelfCleaningAction : public Action { public: StartSelfCleaningAction(HonClimate *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->start_self_cleaning(); } + void play(const Ts &...x) { this->parent_->start_self_cleaning(); } protected: HonClimate *parent_; @@ -93,7 +93,7 @@ template class StartSelfCleaningAction : public Action { template class StartSteriCleaningAction : public Action { public: StartSteriCleaningAction(HonClimate *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->start_steri_cleaning(); } + void play(const Ts &...x) { this->parent_->start_steri_cleaning(); } protected: HonClimate *parent_; @@ -102,7 +102,7 @@ template class StartSteriCleaningAction : public Action { template class PowerOnAction : public Action { public: PowerOnAction(HaierClimateBase *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->send_power_on_command(); } + void play(const Ts &...x) { this->parent_->send_power_on_command(); } protected: HaierClimateBase *parent_; @@ -111,7 +111,7 @@ template class PowerOnAction : public Action { template class PowerOffAction : public Action { public: PowerOffAction(HaierClimateBase *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->send_power_off_command(); } + void play(const Ts &...x) { this->parent_->send_power_off_command(); } protected: HaierClimateBase *parent_; @@ -120,7 +120,7 @@ template class PowerOffAction : public Action { template class PowerToggleAction : public Action { public: PowerToggleAction(HaierClimateBase *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->toggle_power(); } + void play(const Ts &...x) { this->parent_->toggle_power(); } protected: HaierClimateBase *parent_; diff --git a/esphome/components/hbridge/fan/hbridge_fan.h b/esphome/components/hbridge/fan/hbridge_fan.h index 143c7c1853..ec1e8ada0e 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.h +++ b/esphome/components/hbridge/fan/hbridge_fan.h @@ -49,7 +49,7 @@ template class BrakeAction : public Action { public: explicit BrakeAction(HBridgeFan *parent) : parent_(parent) {} - void play(Ts... x) override { this->parent_->brake(); } + void play(const Ts &...x) override { this->parent_->brake(); } HBridgeFan *parent_; }; diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 482cd2da44..8a82a44d7d 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -113,8 +113,8 @@ class HttpContainer : public Parented { class HttpRequestResponseTrigger : public Trigger, std::string &> { public: - void process(std::shared_ptr container, std::string &response_body) { - this->trigger(std::move(container), response_body); + void process(const std::shared_ptr &container, std::string &response_body) { + this->trigger(container, response_body); } }; @@ -210,7 +210,7 @@ template class HttpRequestSendAction : public Action { this->max_response_buffer_size_ = max_response_buffer_size; } - void play(Ts... x) override { + void play(const Ts &...x) override { std::string body; if (this->body_.has_value()) { body = this->body_.value(x...); diff --git a/esphome/components/http_request/ota/automation.h b/esphome/components/http_request/ota/automation.h index d4c21f1c72..6c50bb9b0d 100644 --- a/esphome/components/http_request/ota/automation.h +++ b/esphome/components/http_request/ota/automation.h @@ -15,7 +15,7 @@ template class OtaHttpRequestComponentFlashAction : public Actio TEMPLATABLE_VALUE(std::string, url) TEMPLATABLE_VALUE(std::string, username) - void play(Ts... x) override { + void play(const Ts &...x) override { if (this->md5_url_.has_value()) { this->parent_->set_md5_url(this->md5_url_.value(x...)); } diff --git a/esphome/components/htu21d/htu21d.h b/esphome/components/htu21d/htu21d.h index 9b3831b784..277c6ca3e5 100644 --- a/esphome/components/htu21d/htu21d.h +++ b/esphome/components/htu21d/htu21d.h @@ -41,7 +41,7 @@ template class SetHeaterLevelAction : public Action, publ public: TEMPLATABLE_VALUE(uint8_t, level) - void play(Ts... x) override { + void play(const Ts &...x) override { auto level = this->level_.value(x...); this->parent_->set_heater_level(level); @@ -52,7 +52,7 @@ template class SetHeaterAction : public Action, public Pa public: TEMPLATABLE_VALUE(bool, status) - void play(Ts... x) override { + void play(const Ts &...x) override { auto status = this->status_.value(x...); this->parent_->set_heater(status); diff --git a/esphome/components/integration/integration_sensor.h b/esphome/components/integration/integration_sensor.h index d9f2f5e50f..f075d163fe 100644 --- a/esphome/components/integration/integration_sensor.h +++ b/esphome/components/integration/integration_sensor.h @@ -75,7 +75,7 @@ template class ResetAction : public Action { public: explicit ResetAction(IntegrationSensor *parent) : parent_(parent) {} - void play(Ts... x) override { this->parent_->reset(); } + void play(const Ts &...x) override { this->parent_->reset(); } protected: IntegrationSensor *parent_; diff --git a/esphome/components/key_collector/key_collector.h b/esphome/components/key_collector/key_collector.h index 35e8141ce5..735f396809 100644 --- a/esphome/components/key_collector/key_collector.h +++ b/esphome/components/key_collector/key_collector.h @@ -52,11 +52,11 @@ class KeyCollector : public Component { }; template class EnableAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->set_enabled(true); } + void play(const Ts &...x) override { this->parent_->set_enabled(true); } }; template class DisableAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->set_enabled(false); } + void play(const Ts &...x) override { this->parent_->set_enabled(false); } }; } // namespace key_collector diff --git a/esphome/components/ld2410/automation.h b/esphome/components/ld2410/automation.h index 7cb9855f84..f4f1c197b2 100644 --- a/esphome/components/ld2410/automation.h +++ b/esphome/components/ld2410/automation.h @@ -12,7 +12,7 @@ template class BluetoothPasswordSetAction : public Action explicit BluetoothPasswordSetAction(LD2410Component *ld2410_comp) : ld2410_comp_(ld2410_comp) {} TEMPLATABLE_VALUE(std::string, password) - void play(Ts... x) override { this->ld2410_comp_->set_bluetooth_password(this->password_.value(x...)); } + void play(const Ts &...x) override { this->ld2410_comp_->set_bluetooth_password(this->password_.value(x...)); } protected: LD2410Component *ld2410_comp_; diff --git a/esphome/components/ledc/ledc_output.h b/esphome/components/ledc/ledc_output.h index f04543bc5b..b24e3cfdb2 100644 --- a/esphome/components/ledc/ledc_output.h +++ b/esphome/components/ledc/ledc_output.h @@ -47,7 +47,7 @@ template class SetFrequencyAction : public Action { SetFrequencyAction(LEDCOutput *parent) : parent_(parent) {} TEMPLATABLE_VALUE(float, frequency); - void play(Ts... x) { + void play(const Ts &...x) { float freq = this->frequency_.value(x...); this->parent_->update_frequency(freq); } diff --git a/esphome/components/libretiny_pwm/libretiny_pwm.h b/esphome/components/libretiny_pwm/libretiny_pwm.h index 42ce40ca39..f911709054 100644 --- a/esphome/components/libretiny_pwm/libretiny_pwm.h +++ b/esphome/components/libretiny_pwm/libretiny_pwm.h @@ -40,7 +40,7 @@ template class SetFrequencyAction : public Action { SetFrequencyAction(LibreTinyPWM *parent) : parent_(parent) {} TEMPLATABLE_VALUE(float, frequency); - void play(Ts... x) { + void play(const Ts &...x) { float freq = this->frequency_.value(x...); this->parent_->update_frequency(freq); } diff --git a/esphome/components/light/automation.h b/esphome/components/light/automation.h index 6e055741da..8899db8bba 100644 --- a/esphome/components/light/automation.h +++ b/esphome/components/light/automation.h @@ -15,7 +15,7 @@ template class ToggleAction : public Action { TEMPLATABLE_VALUE(uint32_t, transition_length) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->state_->toggle(); call.set_transition_length(this->transition_length_.optional_value(x...)); call.perform(); @@ -44,7 +44,7 @@ template class LightControlAction : public Action { TEMPLATABLE_VALUE(float, warm_white) TEMPLATABLE_VALUE(std::string, effect) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->parent_->make_call(); call.set_color_mode(this->color_mode_.optional_value(x...)); call.set_state(this->state_.optional_value(x...)); @@ -74,7 +74,7 @@ template class DimRelativeAction : public Action { TEMPLATABLE_VALUE(float, relative_brightness) TEMPLATABLE_VALUE(uint32_t, transition_length) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->parent_->make_call(); float rel = this->relative_brightness_.value(x...); float cur; @@ -107,7 +107,7 @@ template class DimRelativeAction : public Action { template class LightIsOnCondition : public Condition { public: explicit LightIsOnCondition(LightState *state) : state_(state) {} - bool check(Ts... x) override { return this->state_->current_values.is_on(); } + bool check(const Ts &...x) override { return this->state_->current_values.is_on(); } protected: LightState *state_; @@ -115,7 +115,7 @@ template class LightIsOnCondition : public Condition { template class LightIsOffCondition : public Condition { public: explicit LightIsOffCondition(LightState *state) : state_(state) {} - bool check(Ts... x) override { return !this->state_->current_values.is_on(); } + bool check(const Ts &...x) override { return !this->state_->current_values.is_on(); } protected: LightState *state_; @@ -179,7 +179,7 @@ template class AddressableSet : public Action { TEMPLATABLE_VALUE(float, blue) TEMPLATABLE_VALUE(float, white) - void play(Ts... x) override { + void play(const Ts &...x) override { auto *out = (AddressableLight *) this->parent_->get_output(); int32_t range_from = interpret_index(this->range_from_.value_or(x..., 0), out->size()); if (range_from < 0 || range_from >= out->size()) diff --git a/esphome/components/lightwaverf/lightwaverf.h b/esphome/components/lightwaverf/lightwaverf.h index b9f2abfcb3..ee4e91e9d1 100644 --- a/esphome/components/lightwaverf/lightwaverf.h +++ b/esphome/components/lightwaverf/lightwaverf.h @@ -51,7 +51,7 @@ template class SendRawAction : public Action { void set_pulse_length(const int &data) { pulse_length_ = data; } void set_data(const std::vector &data) { code_ = data; } - void play(Ts... x) { + void play(const Ts &...x) { int repeats = this->repeat_.value(x...); int inverted = this->inverted_.value(x...); int pulse_length = this->pulse_length_.value(x...); diff --git a/esphome/components/lock/automation.h b/esphome/components/lock/automation.h index 8cb3b64ffe..0f596ef5e6 100644 --- a/esphome/components/lock/automation.h +++ b/esphome/components/lock/automation.h @@ -11,7 +11,7 @@ template class LockAction : public Action { public: explicit LockAction(Lock *a_lock) : lock_(a_lock) {} - void play(Ts... x) override { this->lock_->lock(); } + void play(const Ts &...x) override { this->lock_->lock(); } protected: Lock *lock_; @@ -21,7 +21,7 @@ template class UnlockAction : public Action { public: explicit UnlockAction(Lock *a_lock) : lock_(a_lock) {} - void play(Ts... x) override { this->lock_->unlock(); } + void play(const Ts &...x) override { this->lock_->unlock(); } protected: Lock *lock_; @@ -31,7 +31,7 @@ template class OpenAction : public Action { public: explicit OpenAction(Lock *a_lock) : lock_(a_lock) {} - void play(Ts... x) override { this->lock_->open(); } + void play(const Ts &...x) override { this->lock_->open(); } protected: Lock *lock_; @@ -40,7 +40,7 @@ template class OpenAction : public Action { template class LockCondition : public Condition { public: LockCondition(Lock *parent, bool state) : parent_(parent), state_(state) {} - bool check(Ts... x) override { + bool check(const Ts &...x) override { auto check_state = this->state_ ? LockState::LOCK_STATE_LOCKED : LockState::LOCK_STATE_UNLOCKED; return this->parent_->state == check_state; } diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 50d192fde3..1ae05f933f 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -129,7 +129,7 @@ template class ObjUpdateAction : public Action { public: explicit ObjUpdateAction(std::function &&lamb) : lamb_(std::move(lamb)) {} - void play(Ts... x) override { this->lamb_(x...); } + void play(const Ts &...x) override { this->lamb_(x...); } protected: std::function lamb_; @@ -263,7 +263,7 @@ class IdleTrigger : public Trigger<> { template class LvglAction : public Action, public Parented { public: explicit LvglAction(std::function &&lamb) : action_(std::move(lamb)) {} - void play(Ts... x) override { this->action_(this->parent_); } + void play(const Ts &...x) override { this->action_(this->parent_); } protected: std::function action_{}; @@ -272,7 +272,7 @@ template class LvglAction : public Action, public Parente template class LvglCondition : public Condition, public Parented { public: LvglCondition(std::function &&condition_lambda) : condition_lambda_(std::move(condition_lambda)) {} - bool check(Ts... x) override { return this->condition_lambda_(this->parent_); } + bool check(const Ts &...x) override { return this->condition_lambda_(this->parent_); } protected: std::function condition_lambda_{}; diff --git a/esphome/components/max17043/automation.h b/esphome/components/max17043/automation.h index 44729d119b..ac201a7309 100644 --- a/esphome/components/max17043/automation.h +++ b/esphome/components/max17043/automation.h @@ -10,7 +10,7 @@ template class SleepAction : public Action { public: explicit SleepAction(MAX17043Component *max17043) : max17043_(max17043) {} - void play(Ts... x) override { this->max17043_->sleep_mode(); } + void play(const Ts &...x) override { this->max17043_->sleep_mode(); } protected: MAX17043Component *max17043_; diff --git a/esphome/components/max6956/automation.h b/esphome/components/max6956/automation.h index c0b491dc7f..ca2c3e3ce4 100644 --- a/esphome/components/max6956/automation.h +++ b/esphome/components/max6956/automation.h @@ -13,7 +13,7 @@ template class SetCurrentGlobalAction : public Action { TEMPLATABLE_VALUE(uint8_t, brightness_global) - void play(Ts... x) override { + void play(const Ts &...x) override { this->max6956_->set_brightness_global(this->brightness_global_.value(x...)); this->max6956_->write_brightness_global(); } @@ -28,7 +28,7 @@ template class SetCurrentModeAction : public Action { TEMPLATABLE_VALUE(max6956::MAX6956CURRENTMODE, brightness_mode) - void play(Ts... x) override { + void play(const Ts &...x) override { this->max6956_->set_brightness_mode(this->brightness_mode_.value(x...)); this->max6956_->write_brightness_mode(); } diff --git a/esphome/components/max7219digit/automation.h b/esphome/components/max7219digit/automation.h index 02acebb109..be8245d14d 100644 --- a/esphome/components/max7219digit/automation.h +++ b/esphome/components/max7219digit/automation.h @@ -12,7 +12,7 @@ template class DisplayInvertAction : public Action, publi public: TEMPLATABLE_VALUE(bool, state) - void play(Ts... x) override { + void play(const Ts &...x) override { bool state = this->state_.value(x...); this->parent_->invert_on_off(state); } @@ -22,7 +22,7 @@ template class DisplayVisibilityAction : public Action, p public: TEMPLATABLE_VALUE(bool, state) - void play(Ts... x) override { + void play(const Ts &...x) override { bool state = this->state_.value(x...); this->parent_->turn_on_off(state); } @@ -32,7 +32,7 @@ template class DisplayReverseAction : public Action, publ public: TEMPLATABLE_VALUE(bool, state) - void play(Ts... x) override { + void play(const Ts &...x) override { bool state = this->state_.value(x...); this->parent_->set_reverse(state); } @@ -42,7 +42,7 @@ template class DisplayIntensityAction : public Action, pu public: TEMPLATABLE_VALUE(uint8_t, state) - void play(Ts... x) override { + void play(const Ts &...x) override { uint8_t state = this->state_.value(x...); this->parent_->set_intensity(state); } diff --git a/esphome/components/media_player/automation.h b/esphome/components/media_player/automation.h index 3af5959f32..50e7693cb5 100644 --- a/esphome/components/media_player/automation.h +++ b/esphome/components/media_player/automation.h @@ -11,7 +11,7 @@ template class MediaPlayerCommandAction : public Action, public Parented { public: TEMPLATABLE_VALUE(bool, announcement); - void play(Ts... x) override { + void play(const Ts &...x) override { this->parent_->make_call().set_command(Command).set_announcement(this->announcement_.value(x...)).perform(); } }; @@ -36,7 +36,7 @@ using TurnOffAction = MediaPlayerCommandAction class PlayMediaAction : public Action, public Parented { TEMPLATABLE_VALUE(std::string, media_url) TEMPLATABLE_VALUE(bool, announcement) - void play(Ts... x) override { + void play(const Ts &...x) override { this->parent_->make_call() .set_media_url(this->media_url_.value(x...)) .set_announcement(this->announcement_.value(x...)) @@ -46,7 +46,7 @@ template class PlayMediaAction : public Action, public Pa template class VolumeSetAction : public Action, public Parented { TEMPLATABLE_VALUE(float, volume) - void play(Ts... x) override { this->parent_->make_call().set_volume(this->volume_.value(x...)).perform(); } + void play(const Ts &...x) override { this->parent_->make_call().set_volume(this->volume_.value(x...)).perform(); } }; class StateTrigger : public Trigger<> { @@ -75,32 +75,34 @@ using OffTrigger = MediaPlayerStateTrigger class IsIdleCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_IDLE; } + bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_IDLE; } }; template class IsPlayingCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING; } + bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING; } }; template class IsPausedCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PAUSED; } + bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PAUSED; } }; template class IsAnnouncingCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING; } + bool check(const Ts &...x) override { + return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING; + } }; template class IsOnCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ON; } + bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ON; } }; template class IsOffCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_OFF; } + bool check(const Ts &...x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_OFF; } }; } // namespace media_player diff --git a/esphome/components/mhz19/mhz19.h b/esphome/components/mhz19/mhz19.h index ec38f2cd2f..be36886d62 100644 --- a/esphome/components/mhz19/mhz19.h +++ b/esphome/components/mhz19/mhz19.h @@ -40,7 +40,7 @@ template class MHZ19CalibrateZeroAction : public Action { public: MHZ19CalibrateZeroAction(MHZ19Component *mhz19) : mhz19_(mhz19) {} - void play(Ts... x) override { this->mhz19_->calibrate_zero(); } + void play(const Ts &...x) override { this->mhz19_->calibrate_zero(); } protected: MHZ19Component *mhz19_; @@ -50,7 +50,7 @@ template class MHZ19ABCEnableAction : public Action { public: MHZ19ABCEnableAction(MHZ19Component *mhz19) : mhz19_(mhz19) {} - void play(Ts... x) override { this->mhz19_->abc_enable(); } + void play(const Ts &...x) override { this->mhz19_->abc_enable(); } protected: MHZ19Component *mhz19_; @@ -60,7 +60,7 @@ template class MHZ19ABCDisableAction : public Action { public: MHZ19ABCDisableAction(MHZ19Component *mhz19) : mhz19_(mhz19) {} - void play(Ts... x) override { this->mhz19_->abc_disable(); } + void play(const Ts &...x) override { this->mhz19_->abc_disable(); } protected: MHZ19Component *mhz19_; diff --git a/esphome/components/micro_wake_word/automation.h b/esphome/components/micro_wake_word/automation.h index f10a4ed347..e1795a7e64 100644 --- a/esphome/components/micro_wake_word/automation.h +++ b/esphome/components/micro_wake_word/automation.h @@ -9,23 +9,23 @@ namespace micro_wake_word { template class StartAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->start(); } + void play(const Ts &...x) override { this->parent_->start(); } }; template class StopAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->stop(); } + void play(const Ts &...x) override { this->parent_->stop(); } }; template class IsRunningCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->is_running(); } + bool check(const Ts &...x) override { return this->parent_->is_running(); } }; template class EnableModelAction : public Action { public: explicit EnableModelAction(WakeWordModel *wake_word_model) : wake_word_model_(wake_word_model) {} - void play(Ts... x) override { this->wake_word_model_->enable(); } + void play(const Ts &...x) override { this->wake_word_model_->enable(); } protected: WakeWordModel *wake_word_model_; @@ -34,7 +34,7 @@ template class EnableModelAction : public Action { template class DisableModelAction : public Action { public: explicit DisableModelAction(WakeWordModel *wake_word_model) : wake_word_model_(wake_word_model) {} - void play(Ts... x) override { this->wake_word_model_->disable(); } + void play(const Ts &...x) override { this->wake_word_model_->disable(); } protected: WakeWordModel *wake_word_model_; @@ -43,7 +43,7 @@ template class DisableModelAction : public Action { template class ModelIsEnabledCondition : public Condition { public: explicit ModelIsEnabledCondition(WakeWordModel *wake_word_model) : wake_word_model_(wake_word_model) {} - bool check(Ts... x) override { return this->wake_word_model_->is_enabled(); } + bool check(const Ts &...x) override { return this->wake_word_model_->is_enabled(); } protected: WakeWordModel *wake_word_model_; diff --git a/esphome/components/microphone/automation.h b/esphome/components/microphone/automation.h index 5745909c46..a6c4bdae66 100644 --- a/esphome/components/microphone/automation.h +++ b/esphome/components/microphone/automation.h @@ -9,18 +9,18 @@ namespace esphome { namespace microphone { template class CaptureAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->start(); } + void play(const Ts &...x) override { this->parent_->start(); } }; template class StopCaptureAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->stop(); } + void play(const Ts &...x) override { this->parent_->stop(); } }; template class MuteAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->set_mute_state(true); } + void play(const Ts &...x) override { this->parent_->set_mute_state(true); } }; template class UnmuteAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->set_mute_state(false); } + void play(const Ts &...x) override { this->parent_->set_mute_state(false); } }; class DataTrigger : public Trigger &> { @@ -32,12 +32,12 @@ class DataTrigger : public Trigger &> { template class IsCapturingCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->is_running(); } + bool check(const Ts &...x) override { return this->parent_->is_running(); } }; template class IsMutedCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->get_mute_state(); } + bool check(const Ts &...x) override { return this->parent_->get_mute_state(); } }; } // namespace microphone diff --git a/esphome/components/midea/ac_automations.h b/esphome/components/midea/ac_automations.h index e6fffa2511..760737be87 100644 --- a/esphome/components/midea/ac_automations.h +++ b/esphome/components/midea/ac_automations.h @@ -22,7 +22,7 @@ template class FollowMeAction : public MideaActionBase { TEMPLATABLE_VALUE(bool, use_fahrenheit) TEMPLATABLE_VALUE(bool, beeper) - void play(Ts... x) override { + void play(const Ts &...x) override { this->parent_->do_follow_me(this->temperature_.value(x...), this->use_fahrenheit_.value(x...), this->beeper_.value(x...)); } @@ -30,37 +30,37 @@ template class FollowMeAction : public MideaActionBase { template class SwingStepAction : public MideaActionBase { public: - void play(Ts... x) override { this->parent_->do_swing_step(); } + void play(const Ts &...x) override { this->parent_->do_swing_step(); } }; template class DisplayToggleAction : public MideaActionBase { public: - void play(Ts... x) override { this->parent_->do_display_toggle(); } + void play(const Ts &...x) override { this->parent_->do_display_toggle(); } }; template class BeeperOnAction : public MideaActionBase { public: - void play(Ts... x) override { this->parent_->do_beeper_on(); } + void play(const Ts &...x) override { this->parent_->do_beeper_on(); } }; template class BeeperOffAction : public MideaActionBase { public: - void play(Ts... x) override { this->parent_->do_beeper_off(); } + void play(const Ts &...x) override { this->parent_->do_beeper_off(); } }; template class PowerOnAction : public MideaActionBase { public: - void play(Ts... x) override { this->parent_->do_power_on(); } + void play(const Ts &...x) override { this->parent_->do_power_on(); } }; template class PowerOffAction : public MideaActionBase { public: - void play(Ts... x) override { this->parent_->do_power_off(); } + void play(const Ts &...x) override { this->parent_->do_power_off(); } }; template class PowerToggleAction : public MideaActionBase { public: - void play(Ts... x) override { this->parent_->do_power_toggle(); } + void play(const Ts &...x) override { this->parent_->do_power_toggle(); } }; } // namespace ac diff --git a/esphome/components/mixer/speaker/automation.h b/esphome/components/mixer/speaker/automation.h index b688fa2c1e..2234936628 100644 --- a/esphome/components/mixer/speaker/automation.h +++ b/esphome/components/mixer/speaker/automation.h @@ -9,7 +9,7 @@ namespace mixer_speaker { template class DuckingApplyAction : public Action, public Parented { TEMPLATABLE_VALUE(uint8_t, decibel_reduction) TEMPLATABLE_VALUE(uint32_t, duration) - void play(Ts... x) override { + void play(const Ts &...x) override { this->parent_->apply_ducking(this->decibel_reduction_.value(x...), this->duration_.value(x...)); } }; diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 325ca56f4b..79383ee857 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -389,7 +389,7 @@ template class MQTTPublishAction : public Action { TEMPLATABLE_VALUE(uint8_t, qos) TEMPLATABLE_VALUE(bool, retain) - void play(Ts... x) override { + void play(const Ts &...x) override { this->parent_->publish(this->topic_.value(x...), this->payload_.value(x...), this->qos_.value(x...), this->retain_.value(x...)); } @@ -407,7 +407,7 @@ template class MQTTPublishJsonAction : public Action { void set_payload(std::function payload) { this->payload_ = payload; } - void play(Ts... x) override { + void play(const Ts &...x) override { auto f = std::bind(&MQTTPublishJsonAction::encode_, this, x..., std::placeholders::_1); auto topic = this->topic_.value(x...); auto qos = this->qos_.value(x...); @@ -424,7 +424,7 @@ template class MQTTPublishJsonAction : public Action { template class MQTTConnectedCondition : public Condition { public: MQTTConnectedCondition(MQTTClientComponent *parent) : parent_(parent) {} - bool check(Ts... x) override { return this->parent_->is_connected(); } + bool check(const Ts &...x) override { return this->parent_->is_connected(); } protected: MQTTClientComponent *parent_; @@ -434,7 +434,7 @@ template class MQTTEnableAction : public Action { public: MQTTEnableAction(MQTTClientComponent *parent) : parent_(parent) {} - void play(Ts... x) override { this->parent_->enable(); } + void play(const Ts &...x) override { this->parent_->enable(); } protected: MQTTClientComponent *parent_; @@ -444,7 +444,7 @@ template class MQTTDisableAction : public Action { public: MQTTDisableAction(MQTTClientComponent *parent) : parent_(parent) {} - void play(Ts... x) override { this->parent_->disable(); } + void play(const Ts &...x) override { this->parent_->disable(); } protected: MQTTClientComponent *parent_; diff --git a/esphome/components/nau7802/nau7802.h b/esphome/components/nau7802/nau7802.h index 17e426ccc6..05452851ca 100644 --- a/esphome/components/nau7802/nau7802.h +++ b/esphome/components/nau7802/nau7802.h @@ -101,18 +101,18 @@ class NAU7802Sensor : public sensor::Sensor, public PollingComponent, public i2c template class NAU7802CalbrateExternalOffsetAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->calibrate_external_offset(); } + void play(const Ts &...x) override { this->parent_->calibrate_external_offset(); } }; template class NAU7802CalbrateInternalOffsetAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->calibrate_internal_offset(); } + void play(const Ts &...x) override { this->parent_->calibrate_internal_offset(); } }; template class NAU7802CalbrateGainAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->calibrate_gain(); } + void play(const Ts &...x) override { this->parent_->calibrate_gain(); } }; } // namespace nau7802 diff --git a/esphome/components/nextion/automation.h b/esphome/components/nextion/automation.h index c718355af8..8e85e15823 100644 --- a/esphome/components/nextion/automation.h +++ b/esphome/components/nextion/automation.h @@ -55,7 +55,7 @@ template class NextionSetBrightnessAction : public Action TEMPLATABLE_VALUE(float, brightness) - void play(Ts... x) override { + void play(const Ts &...x) override { this->component_->set_brightness(this->brightness_.value(x...)); this->component_->set_backlight_brightness(this->brightness_.value(x...)); } @@ -74,7 +74,7 @@ template class NextionPublishFloatAction : public Action TEMPLATABLE_VALUE(bool, publish_state) TEMPLATABLE_VALUE(bool, send_to_nextion) - void play(Ts... x) override { + void play(const Ts &...x) override { this->component_->set_state(this->state_.value(x...), this->publish_state_.value(x...), this->send_to_nextion_.value(x...)); } @@ -97,7 +97,7 @@ template class NextionPublishTextAction : public Action { TEMPLATABLE_VALUE(bool, publish_state) TEMPLATABLE_VALUE(bool, send_to_nextion) - void play(Ts... x) override { + void play(const Ts &...x) override { this->component_->set_state(this->state_.value(x...), this->publish_state_.value(x...), this->send_to_nextion_.value(x...)); } @@ -120,7 +120,7 @@ template class NextionPublishBoolAction : public Action { TEMPLATABLE_VALUE(bool, publish_state) TEMPLATABLE_VALUE(bool, send_to_nextion) - void play(Ts... x) override { + void play(const Ts &...x) override { this->component_->set_state(this->state_.value(x...), this->publish_state_.value(x...), this->send_to_nextion_.value(x...)); } diff --git a/esphome/components/number/automation.h b/esphome/components/number/automation.h index 33f0f9727e..79eba883c4 100644 --- a/esphome/components/number/automation.h +++ b/esphome/components/number/automation.h @@ -19,7 +19,7 @@ template class NumberSetAction : public Action { NumberSetAction(Number *number) : number_(number) {} TEMPLATABLE_VALUE(float, value) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->number_->make_call(); call.set_value(this->value_.value(x...)); call.perform(); @@ -35,7 +35,7 @@ template class NumberOperationAction : public Action { TEMPLATABLE_VALUE(NumberOperation, operation) TEMPLATABLE_VALUE(bool, cycle) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->number_->make_call(); call.with_operation(this->operation_.value(x...)); if (this->cycle_.has_value()) { @@ -74,7 +74,7 @@ template class NumberInRangeCondition : public Condition void set_min(float min) { this->min_ = min; } void set_max(float max) { this->max_ = max; } - bool check(Ts... x) override { + bool check(const Ts &...x) override { const float state = this->parent_->state; if (std::isnan(this->min_)) { return state <= this->max_; diff --git a/esphome/components/online_image/online_image.h b/esphome/components/online_image/online_image.h index 3326cbe8d6..12d409ca29 100644 --- a/esphome/components/online_image/online_image.h +++ b/esphome/components/online_image/online_image.h @@ -207,7 +207,7 @@ template class OnlineImageSetUrlAction : public Action { OnlineImageSetUrlAction(OnlineImage *parent) : parent_(parent) {} TEMPLATABLE_VALUE(std::string, url) TEMPLATABLE_VALUE(bool, update) - void play(Ts... x) override { + void play(const Ts &...x) override { this->parent_->set_url(this->url_.value(x...)); if (this->update_.value(x...)) { this->parent_->update(); @@ -221,7 +221,7 @@ template class OnlineImageSetUrlAction : public Action { template class OnlineImageReleaseAction : public Action { public: OnlineImageReleaseAction(OnlineImage *parent) : parent_(parent) {} - void play(Ts... x) override { this->parent_->release(); } + void play(const Ts &...x) override { this->parent_->release(); } protected: OnlineImage *parent_; diff --git a/esphome/components/output/automation.h b/esphome/components/output/automation.h index de84bb91ca..3279378129 100644 --- a/esphome/components/output/automation.h +++ b/esphome/components/output/automation.h @@ -12,7 +12,7 @@ template class TurnOffAction : public Action { public: TurnOffAction(BinaryOutput *output) : output_(output) {} - void play(Ts... x) override { this->output_->turn_off(); } + void play(const Ts &...x) override { this->output_->turn_off(); } protected: BinaryOutput *output_; @@ -22,7 +22,7 @@ template class TurnOnAction : public Action { public: TurnOnAction(BinaryOutput *output) : output_(output) {} - void play(Ts... x) override { this->output_->turn_on(); } + void play(const Ts &...x) override { this->output_->turn_on(); } protected: BinaryOutput *output_; @@ -34,7 +34,7 @@ template class SetLevelAction : public Action { TEMPLATABLE_VALUE(float, level) - void play(Ts... x) override { this->output_->set_level(this->level_.value(x...)); } + void play(const Ts &...x) override { this->output_->set_level(this->level_.value(x...)); } protected: FloatOutput *output_; @@ -46,7 +46,7 @@ template class SetMinPowerAction : public Action { TEMPLATABLE_VALUE(float, min_power) - void play(Ts... x) override { this->output_->set_min_power(this->min_power_.value(x...)); } + void play(const Ts &...x) override { this->output_->set_min_power(this->min_power_.value(x...)); } protected: FloatOutput *output_; @@ -58,7 +58,7 @@ template class SetMaxPowerAction : public Action { TEMPLATABLE_VALUE(float, max_power) - void play(Ts... x) override { this->output_->set_max_power(this->max_power_.value(x...)); } + void play(const Ts &...x) override { this->output_->set_max_power(this->max_power_.value(x...)); } protected: FloatOutput *output_; diff --git a/esphome/components/pcf85063/pcf85063.h b/esphome/components/pcf85063/pcf85063.h index 1a3fd704e5..b7034d4f00 100644 --- a/esphome/components/pcf85063/pcf85063.h +++ b/esphome/components/pcf85063/pcf85063.h @@ -85,12 +85,12 @@ class PCF85063Component : public time::RealTimeClock, public i2c::I2CDevice { template class WriteAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->write_time(); } + void play(const Ts &...x) override { this->parent_->write_time(); } }; template class ReadAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->read_time(); } + void play(const Ts &...x) override { this->parent_->read_time(); } }; } // namespace pcf85063 } // namespace esphome diff --git a/esphome/components/pcf8563/pcf8563.h b/esphome/components/pcf8563/pcf8563.h index b6832efe72..81aa816b42 100644 --- a/esphome/components/pcf8563/pcf8563.h +++ b/esphome/components/pcf8563/pcf8563.h @@ -113,12 +113,12 @@ class PCF8563Component : public time::RealTimeClock, public i2c::I2CDevice { template class WriteAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->write_time(); } + void play(const Ts &...x) override { this->parent_->write_time(); } }; template class ReadAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->read_time(); } + void play(const Ts &...x) override { this->parent_->read_time(); } }; } // namespace pcf8563 } // namespace esphome diff --git a/esphome/components/pid/pid_climate.h b/esphome/components/pid/pid_climate.h index 1a09ffdd20..dc0a92efed 100644 --- a/esphome/components/pid/pid_climate.h +++ b/esphome/components/pid/pid_climate.h @@ -109,7 +109,7 @@ template class PIDAutotuneAction : public Action { void set_positive_output(float positive_output) { positive_output_ = positive_output; } void set_negative_output(float negative_output) { negative_output_ = negative_output; } - void play(Ts... x) { + void play(const Ts &...x) { auto tuner = make_unique(); tuner->set_noiseband(this->noiseband_); tuner->set_output_negative(this->negative_output_); @@ -128,7 +128,7 @@ template class PIDResetIntegralTermAction : public Action public: PIDResetIntegralTermAction(PIDClimate *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->reset_integral_term(); } + void play(const Ts &...x) { this->parent_->reset_integral_term(); } protected: PIDClimate *parent_; @@ -138,7 +138,7 @@ template class PIDSetControlParametersAction : public Actionkp_.value(x...); auto ki = this->ki_.value(x...); auto kd = this->kd_.value(x...); diff --git a/esphome/components/pipsolar/output/pipsolar_output.h b/esphome/components/pipsolar/output/pipsolar_output.h index 29b2d116f2..b4b8000962 100644 --- a/esphome/components/pipsolar/output/pipsolar_output.h +++ b/esphome/components/pipsolar/output/pipsolar_output.h @@ -32,7 +32,7 @@ template class SetOutputAction : public Action { TEMPLATABLE_VALUE(float, level) - void play(Ts... x) override { this->output_->set_value(this->level_.value(x...)); } + void play(const Ts &...x) override { this->output_->set_value(this->level_.value(x...)); } protected: PipsolarOutput *output_; diff --git a/esphome/components/pmwcs3/pmwcs3.h b/esphome/components/pmwcs3/pmwcs3.h index d60f9d1f61..d63c516586 100644 --- a/esphome/components/pmwcs3/pmwcs3.h +++ b/esphome/components/pmwcs3/pmwcs3.h @@ -38,7 +38,7 @@ template class PMWCS3AirCalibrationAction : public Action public: PMWCS3AirCalibrationAction(PMWCS3Component *parent) : parent_(parent) {} - void play(Ts... x) override { this->parent_->air_calibration(); } + void play(const Ts &...x) override { this->parent_->air_calibration(); } protected: PMWCS3Component *parent_; @@ -48,7 +48,7 @@ template class PMWCS3WaterCalibrationAction : public Actionparent_->water_calibration(); } + void play(const Ts &...x) override { this->parent_->water_calibration(); } protected: PMWCS3Component *parent_; @@ -59,7 +59,7 @@ template class PMWCS3NewI2cAddressAction : public Action PMWCS3NewI2cAddressAction(PMWCS3Component *parent) : parent_(parent) {} TEMPLATABLE_VALUE(int, new_address) - void play(Ts... x) override { this->parent_->new_i2c_address(this->new_address_.value(x...)); } + void play(const Ts &...x) override { this->parent_->new_i2c_address(this->new_address_.value(x...)); } protected: PMWCS3Component *parent_; diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index c8e9a40008..eeb15648fb 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -143,7 +143,7 @@ class PN532OnFinishedWriteTrigger : public Trigger<> { template class PN532IsWritingCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->is_writing(); } + bool check(const Ts &...x) override { return this->parent_->is_writing(); } }; } // namespace pn532 diff --git a/esphome/components/pn7150/automation.h b/esphome/components/pn7150/automation.h index aebb1b7573..21329a998a 100644 --- a/esphome/components/pn7150/automation.h +++ b/esphome/components/pn7150/automation.h @@ -23,42 +23,42 @@ class PN7150OnFinishedWriteTrigger : public Trigger<> { template class PN7150IsWritingCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->is_writing(); } + bool check(const Ts &...x) override { return this->parent_->is_writing(); } }; template class EmulationOffAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->set_tag_emulation_off(); } + void play(const Ts &...x) override { this->parent_->set_tag_emulation_off(); } }; template class EmulationOnAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->set_tag_emulation_on(); } + void play(const Ts &...x) override { this->parent_->set_tag_emulation_on(); } }; template class PollingOffAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->set_polling_off(); } + void play(const Ts &...x) override { this->parent_->set_polling_off(); } }; template class PollingOnAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->set_polling_on(); } + void play(const Ts &...x) override { this->parent_->set_polling_on(); } }; template class SetCleanModeAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->clean_mode(); } + void play(const Ts &...x) override { this->parent_->clean_mode(); } }; template class SetFormatModeAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->format_mode(); } + void play(const Ts &...x) override { this->parent_->format_mode(); } }; template class SetReadModeAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->read_mode(); } + void play(const Ts &...x) override { this->parent_->read_mode(); } }; template class SetEmulationMessageAction : public Action, public Parented { TEMPLATABLE_VALUE(std::string, message) TEMPLATABLE_VALUE(bool, include_android_app_record) - void play(Ts... x) override { + void play(const Ts &...x) override { this->parent_->set_tag_emulation_message(this->message_.optional_value(x...), this->include_android_app_record_.optional_value(x...)); } @@ -68,14 +68,14 @@ template class SetWriteMessageAction : public Action, pub TEMPLATABLE_VALUE(std::string, message) TEMPLATABLE_VALUE(bool, include_android_app_record) - void play(Ts... x) override { + void play(const Ts &...x) override { this->parent_->set_tag_write_message(this->message_.optional_value(x...), this->include_android_app_record_.optional_value(x...)); } }; template class SetWriteModeAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->write_mode(); } + void play(const Ts &...x) override { this->parent_->write_mode(); } }; } // namespace pn7150 diff --git a/esphome/components/pn7160/automation.h b/esphome/components/pn7160/automation.h index 854fb11684..08148c2311 100644 --- a/esphome/components/pn7160/automation.h +++ b/esphome/components/pn7160/automation.h @@ -23,42 +23,42 @@ class PN7160OnFinishedWriteTrigger : public Trigger<> { template class PN7160IsWritingCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->is_writing(); } + bool check(const Ts &...x) override { return this->parent_->is_writing(); } }; template class EmulationOffAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->set_tag_emulation_off(); } + void play(const Ts &...x) override { this->parent_->set_tag_emulation_off(); } }; template class EmulationOnAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->set_tag_emulation_on(); } + void play(const Ts &...x) override { this->parent_->set_tag_emulation_on(); } }; template class PollingOffAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->set_polling_off(); } + void play(const Ts &...x) override { this->parent_->set_polling_off(); } }; template class PollingOnAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->set_polling_on(); } + void play(const Ts &...x) override { this->parent_->set_polling_on(); } }; template class SetCleanModeAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->clean_mode(); } + void play(const Ts &...x) override { this->parent_->clean_mode(); } }; template class SetFormatModeAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->format_mode(); } + void play(const Ts &...x) override { this->parent_->format_mode(); } }; template class SetReadModeAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->read_mode(); } + void play(const Ts &...x) override { this->parent_->read_mode(); } }; template class SetEmulationMessageAction : public Action, public Parented { TEMPLATABLE_VALUE(std::string, message) TEMPLATABLE_VALUE(bool, include_android_app_record) - void play(Ts... x) override { + void play(const Ts &...x) override { this->parent_->set_tag_emulation_message(this->message_.optional_value(x...), this->include_android_app_record_.optional_value(x...)); } @@ -68,14 +68,14 @@ template class SetWriteMessageAction : public Action, pub TEMPLATABLE_VALUE(std::string, message) TEMPLATABLE_VALUE(bool, include_android_app_record) - void play(Ts... x) override { + void play(const Ts &...x) override { this->parent_->set_tag_write_message(this->message_.optional_value(x...), this->include_android_app_record_.optional_value(x...)); } }; template class SetWriteModeAction : public Action, public Parented { - void play(Ts... x) override { this->parent_->write_mode(); } + void play(const Ts &...x) override { this->parent_->write_mode(); } }; } // namespace pn7160 diff --git a/esphome/components/pulse_counter/automation.h b/esphome/components/pulse_counter/automation.h index d749540a95..0c0dc2552d 100644 --- a/esphome/components/pulse_counter/automation.h +++ b/esphome/components/pulse_counter/automation.h @@ -14,7 +14,7 @@ template class SetTotalPulsesAction : public Action { TEMPLATABLE_VALUE(uint32_t, total_pulses) - void play(Ts... x) override { this->pulse_counter_->set_total_pulses(this->total_pulses_.value(x...)); } + void play(const Ts &...x) override { this->pulse_counter_->set_total_pulses(this->total_pulses_.value(x...)); } protected: PulseCounterSensor *pulse_counter_; diff --git a/esphome/components/pulse_meter/automation.h b/esphome/components/pulse_meter/automation.h index 3112ded680..bf0768b7af 100644 --- a/esphome/components/pulse_meter/automation.h +++ b/esphome/components/pulse_meter/automation.h @@ -14,7 +14,7 @@ template class SetTotalPulsesAction : public Action { TEMPLATABLE_VALUE(uint32_t, total_pulses) - void play(Ts... x) override { this->pulse_meter_->set_total_pulses(this->total_pulses_.value(x...)); } + void play(const Ts &...x) override { this->pulse_meter_->set_total_pulses(this->total_pulses_.value(x...)); } protected: PulseMeterSensor *pulse_meter_; diff --git a/esphome/components/pzemac/pzemac.h b/esphome/components/pzemac/pzemac.h index 7a229b49ce..e5b96115f9 100644 --- a/esphome/components/pzemac/pzemac.h +++ b/esphome/components/pzemac/pzemac.h @@ -43,7 +43,7 @@ template class ResetEnergyAction : public Action { public: ResetEnergyAction(PZEMAC *pzemac) : pzemac_(pzemac) {} - void play(Ts... x) override { this->pzemac_->reset_energy_(); } + void play(const Ts &...x) override { this->pzemac_->reset_energy_(); } protected: PZEMAC *pzemac_; diff --git a/esphome/components/pzemdc/pzemdc.h b/esphome/components/pzemdc/pzemdc.h index b91ab4c0a5..2e6c26a10c 100644 --- a/esphome/components/pzemdc/pzemdc.h +++ b/esphome/components/pzemdc/pzemdc.h @@ -36,7 +36,7 @@ template class ResetEnergyAction : public Action { public: ResetEnergyAction(PZEMDC *pzemdc) : pzemdc_(pzemdc) {} - void play(Ts... x) override { this->pzemdc_->reset_energy(); } + void play(const Ts &...x) override { this->pzemdc_->reset_energy(); } protected: PZEMDC *pzemdc_; diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index b740ba8085..2cb79bf571 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -276,7 +276,7 @@ template class RemoteTransmitterActionBase : public RemoteTransm TEMPLATABLE_VALUE(uint32_t, send_wait) protected: - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->transmitter_->transmit(); this->encode(call.get_data(), x...); call.set_send_times(this->send_times_.value_or(x..., 1)); diff --git a/esphome/components/remote_transmitter/automation.h b/esphome/components/remote_transmitter/automation.h index 75b017ec61..bee1d0be8a 100644 --- a/esphome/components/remote_transmitter/automation.h +++ b/esphome/components/remote_transmitter/automation.h @@ -11,7 +11,7 @@ namespace remote_transmitter { template class DigitalWriteAction : public Action, public Parented { public: TEMPLATABLE_VALUE(bool, value) - void play(Ts... x) override { this->parent_->digital_write(this->value_.value(x...)); } + void play(const Ts &...x) override { this->parent_->digital_write(this->value_.value(x...)); } }; } // namespace remote_transmitter diff --git a/esphome/components/rf_bridge/rf_bridge.h b/esphome/components/rf_bridge/rf_bridge.h index fe6dd96b38..d2f75c819d 100644 --- a/esphome/components/rf_bridge/rf_bridge.h +++ b/esphome/components/rf_bridge/rf_bridge.h @@ -98,7 +98,7 @@ template class RFBridgeSendCodeAction : public Action { TEMPLATABLE_VALUE(uint16_t, high) TEMPLATABLE_VALUE(uint32_t, code) - void play(Ts... x) { + void play(const Ts &...x) { RFBridgeData data{}; data.sync = this->sync_.value(x...); data.low = this->low_.value(x...); @@ -118,7 +118,7 @@ template class RFBridgeSendAdvancedCodeAction : public Actionlength_.value(x...); data.protocol = this->protocol_.value(x...); @@ -134,7 +134,7 @@ template class RFBridgeLearnAction : public Action { public: RFBridgeLearnAction(RFBridgeComponent *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->learn(); } + void play(const Ts &...x) { this->parent_->learn(); } protected: RFBridgeComponent *parent_; @@ -144,7 +144,7 @@ template class RFBridgeStartAdvancedSniffingAction : public Acti public: RFBridgeStartAdvancedSniffingAction(RFBridgeComponent *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->start_advanced_sniffing(); } + void play(const Ts &...x) { this->parent_->start_advanced_sniffing(); } protected: RFBridgeComponent *parent_; @@ -154,7 +154,7 @@ template class RFBridgeStopAdvancedSniffingAction : public Actio public: RFBridgeStopAdvancedSniffingAction(RFBridgeComponent *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->stop_advanced_sniffing(); } + void play(const Ts &...x) { this->parent_->stop_advanced_sniffing(); } protected: RFBridgeComponent *parent_; @@ -164,7 +164,7 @@ template class RFBridgeStartBucketSniffingAction : public Action public: RFBridgeStartBucketSniffingAction(RFBridgeComponent *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->start_bucket_sniffing(); } + void play(const Ts &...x) { this->parent_->start_bucket_sniffing(); } protected: RFBridgeComponent *parent_; @@ -175,7 +175,7 @@ template class RFBridgeSendRawAction : public Action { RFBridgeSendRawAction(RFBridgeComponent *parent) : parent_(parent) {} TEMPLATABLE_VALUE(std::string, raw) - void play(Ts... x) { this->parent_->send_raw(this->raw_.value(x...)); } + void play(const Ts &...x) { this->parent_->send_raw(this->raw_.value(x...)); } protected: RFBridgeComponent *parent_; @@ -186,7 +186,7 @@ template class RFBridgeBeepAction : public Action { RFBridgeBeepAction(RFBridgeComponent *parent) : parent_(parent) {} TEMPLATABLE_VALUE(uint16_t, duration) - void play(Ts... x) { this->parent_->beep(this->duration_.value(x...)); } + void play(const Ts &...x) { this->parent_->beep(this->duration_.value(x...)); } protected: RFBridgeComponent *parent_; diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index e88ee9152a..14442f0565 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -114,7 +114,7 @@ template class RotaryEncoderSetValueAction : public Actionencoder_->set_value(this->value_.value(x...)); } + void play(const Ts &...x) override { this->encoder_->set_value(this->value_.value(x...)); } protected: RotaryEncoderSensor *encoder_; diff --git a/esphome/components/rp2040_pwm/rp2040_pwm.h b/esphome/components/rp2040_pwm/rp2040_pwm.h index e499e72b06..b82765b1c0 100644 --- a/esphome/components/rp2040_pwm/rp2040_pwm.h +++ b/esphome/components/rp2040_pwm/rp2040_pwm.h @@ -45,7 +45,7 @@ template class SetFrequencyAction : public Action { SetFrequencyAction(RP2040PWM *parent) : parent_(parent) {} TEMPLATABLE_VALUE(float, frequency); - void play(Ts... x) { + void play(const Ts &...x) { float freq = this->frequency_.value(x...); this->parent_->update_frequency(freq); } diff --git a/esphome/components/rtttl/rtttl.h b/esphome/components/rtttl/rtttl.h index d536c6c08e..1e924a897c 100644 --- a/esphome/components/rtttl/rtttl.h +++ b/esphome/components/rtttl/rtttl.h @@ -122,7 +122,7 @@ template class PlayAction : public Action { PlayAction(Rtttl *rtttl) : rtttl_(rtttl) {} TEMPLATABLE_VALUE(std::string, value) - void play(Ts... x) override { this->rtttl_->play(this->value_.value(x...)); } + void play(const Ts &...x) override { this->rtttl_->play(this->value_.value(x...)); } protected: Rtttl *rtttl_; @@ -130,12 +130,12 @@ template class PlayAction : public Action { template class StopAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->stop(); } + void play(const Ts &...x) override { this->parent_->stop(); } }; template class IsPlayingCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->is_playing(); } + bool check(const Ts &...x) override { return this->parent_->is_playing(); } }; class FinishedPlaybackTrigger : public Trigger<> { diff --git a/esphome/components/scd30/automation.h b/esphome/components/scd30/automation.h index 37b3bc1674..1f89e7c815 100644 --- a/esphome/components/scd30/automation.h +++ b/esphome/components/scd30/automation.h @@ -9,7 +9,7 @@ namespace scd30 { template class ForceRecalibrationWithReference : public Action, public Parented { public: - void play(Ts... x) override { + void play(const Ts &...x) override { if (this->value_.has_value()) { this->parent_->force_recalibration_with_reference(this->value_.value(x...)); } diff --git a/esphome/components/scd4x/automation.h b/esphome/components/scd4x/automation.h index dc43e9eb56..6ce1468577 100644 --- a/esphome/components/scd4x/automation.h +++ b/esphome/components/scd4x/automation.h @@ -9,7 +9,7 @@ namespace scd4x { template class PerformForcedCalibrationAction : public Action, public Parented { public: - void play(Ts... x) override { + void play(const Ts &...x) override { if (this->value_.has_value()) { this->parent_->perform_forced_calibration(this->value_.value(x...)); } @@ -21,7 +21,7 @@ template class PerformForcedCalibrationAction : public Action class FactoryResetAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->factory_reset(); } + void play(const Ts &...x) override { this->parent_->factory_reset(); } }; } // namespace scd4x diff --git a/esphome/components/script/script.h b/esphome/components/script/script.h index 870a623f32..51cece01e4 100644 --- a/esphome/components/script/script.h +++ b/esphome/components/script/script.h @@ -215,7 +215,7 @@ template class ScriptExecuteAction, T template void set_args(F... x) { args_ = Args{x...}; } - void play(Ts... x) override { this->script_->execute_tuple(this->eval_args_(x...)); } + void play(const Ts &...x) override { this->script_->execute_tuple(this->eval_args_(x...)); } protected: // NOTE: @@ -249,7 +249,7 @@ template class ScriptStopAction : public Action public: ScriptStopAction(C *script) : script_(script) {} - void play(Ts... x) override { this->script_->stop(); } + void play(const Ts &...x) override { this->script_->stop(); } protected: C *script_; @@ -259,7 +259,7 @@ template class IsRunningCondition : public Conditionparent_->is_running(); } + bool check(const Ts &...x) override { return this->parent_->is_running(); } protected: C *parent_; @@ -281,7 +281,7 @@ template class ScriptWaitAction : public Action, this->disable_loop(); } - void play_complex(Ts... x) override { + void play_complex(const Ts &...x) override { this->num_running_++; // Check if we can continue immediately. if (!this->script_->is_running()) { @@ -312,7 +312,7 @@ template class ScriptWaitAction : public Action, this->disable_loop(); } - void play(Ts... x) override { /* ignore - see play_complex */ + void play(const Ts &...x) override { /* ignore - see play_complex */ } void stop() override { diff --git a/esphome/components/select/automation.h b/esphome/components/select/automation.h index 1250665188..3e42eaf98a 100644 --- a/esphome/components/select/automation.h +++ b/esphome/components/select/automation.h @@ -19,7 +19,7 @@ template class SelectSetAction : public Action { explicit SelectSetAction(Select *select) : select_(select) {} TEMPLATABLE_VALUE(std::string, option) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->select_->make_call(); call.set_option(this->option_.value(x...)); call.perform(); @@ -34,7 +34,7 @@ template class SelectSetIndexAction : public Action { explicit SelectSetIndexAction(Select *select) : select_(select) {} TEMPLATABLE_VALUE(size_t, index) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->select_->make_call(); call.set_index(this->index_.value(x...)); call.perform(); @@ -50,7 +50,7 @@ template class SelectOperationAction : public Action { TEMPLATABLE_VALUE(bool, cycle) TEMPLATABLE_VALUE(SelectOperation, operation) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->select_->make_call(); call.with_operation(this->operation_.value(x...)); if (this->cycle_.has_value()) { diff --git a/esphome/components/sen5x/automation.h b/esphome/components/sen5x/automation.h index 423b942000..558ea46e47 100644 --- a/esphome/components/sen5x/automation.h +++ b/esphome/components/sen5x/automation.h @@ -11,7 +11,7 @@ template class StartFanAction : public Action { public: explicit StartFanAction(SEN5XComponent *sen5x) : sen5x_(sen5x) {} - void play(Ts... x) override { this->sen5x_->start_fan_cleaning(); } + void play(const Ts &...x) override { this->sen5x_->start_fan_cleaning(); } protected: SEN5XComponent *sen5x_; diff --git a/esphome/components/senseair/senseair.h b/esphome/components/senseair/senseair.h index 5b66860f1a..9db849075d 100644 --- a/esphome/components/senseair/senseair.h +++ b/esphome/components/senseair/senseair.h @@ -42,7 +42,7 @@ template class SenseAirBackgroundCalibrationAction : public Acti public: SenseAirBackgroundCalibrationAction(SenseAirComponent *senseair) : senseair_(senseair) {} - void play(Ts... x) override { this->senseair_->background_calibration(); } + void play(const Ts &...x) override { this->senseair_->background_calibration(); } protected: SenseAirComponent *senseair_; @@ -52,7 +52,7 @@ template class SenseAirBackgroundCalibrationResultAction : publi public: SenseAirBackgroundCalibrationResultAction(SenseAirComponent *senseair) : senseair_(senseair) {} - void play(Ts... x) override { this->senseair_->background_calibration_result(); } + void play(const Ts &...x) override { this->senseair_->background_calibration_result(); } protected: SenseAirComponent *senseair_; @@ -62,7 +62,7 @@ template class SenseAirABCEnableAction : public Action { public: SenseAirABCEnableAction(SenseAirComponent *senseair) : senseair_(senseair) {} - void play(Ts... x) override { this->senseair_->abc_enable(); } + void play(const Ts &...x) override { this->senseair_->abc_enable(); } protected: SenseAirComponent *senseair_; @@ -72,7 +72,7 @@ template class SenseAirABCDisableAction : public Action { public: SenseAirABCDisableAction(SenseAirComponent *senseair) : senseair_(senseair) {} - void play(Ts... x) override { this->senseair_->abc_disable(); } + void play(const Ts &...x) override { this->senseair_->abc_disable(); } protected: SenseAirComponent *senseair_; @@ -82,7 +82,7 @@ template class SenseAirABCGetPeriodAction : public Action public: SenseAirABCGetPeriodAction(SenseAirComponent *senseair) : senseair_(senseair) {} - void play(Ts... x) override { this->senseair_->abc_get_period(); } + void play(const Ts &...x) override { this->senseair_->abc_get_period(); } protected: SenseAirComponent *senseair_; diff --git a/esphome/components/sensor/automation.h b/esphome/components/sensor/automation.h index 4f34c35023..df7d31a0c9 100644 --- a/esphome/components/sensor/automation.h +++ b/esphome/components/sensor/automation.h @@ -26,7 +26,7 @@ template class SensorPublishAction : public Action { SensorPublishAction(Sensor *sensor) : sensor_(sensor) {} TEMPLATABLE_VALUE(float, state) - void play(Ts... x) override { this->sensor_->publish_state(this->state_.value(x...)); } + void play(const Ts &...x) override { this->sensor_->publish_state(this->state_.value(x...)); } protected: Sensor *sensor_; @@ -90,7 +90,7 @@ template class SensorInRangeCondition : public Condition void set_min(float min) { this->min_ = min; } void set_max(float max) { this->max_ = max; } - bool check(Ts... x) override { + bool check(const Ts &...x) override { const float state = this->parent_->state; if (std::isnan(this->min_)) { return state <= this->max_; diff --git a/esphome/components/servo/servo.h b/esphome/components/servo/servo.h index ff1708dc53..3d15aefefe 100644 --- a/esphome/components/servo/servo.h +++ b/esphome/components/servo/servo.h @@ -57,7 +57,7 @@ template class ServoWriteAction : public Action { ServoWriteAction(Servo *servo) : servo_(servo) {} TEMPLATABLE_VALUE(float, value) - void play(Ts... x) override { this->servo_->write(this->value_.value(x...)); } + void play(const Ts &...x) override { this->servo_->write(this->value_.value(x...)); } protected: Servo *servo_; @@ -67,7 +67,7 @@ template class ServoDetachAction : public Action { public: ServoDetachAction(Servo *servo) : servo_(servo) {} - void play(Ts... x) override { this->servo_->detach(); } + void play(const Ts &...x) override { this->servo_->detach(); } protected: Servo *servo_; diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h index bf7efd6915..a2da686ce1 100644 --- a/esphome/components/sim800l/sim800l.h +++ b/esphome/components/sim800l/sim800l.h @@ -162,7 +162,7 @@ template class Sim800LSendSmsAction : public Action { TEMPLATABLE_VALUE(std::string, recipient) TEMPLATABLE_VALUE(std::string, message) - void play(Ts... x) { + void play(const Ts &...x) { auto recipient = this->recipient_.value(x...); auto message = this->message_.value(x...); this->parent_->send_sms(recipient, message); @@ -177,7 +177,7 @@ template class Sim800LSendUssdAction : public Action { Sim800LSendUssdAction(Sim800LComponent *parent) : parent_(parent) {} TEMPLATABLE_VALUE(std::string, ussd) - void play(Ts... x) { + void play(const Ts &...x) { auto ussd_code = this->ussd_.value(x...); this->parent_->send_ussd(ussd_code); } @@ -191,7 +191,7 @@ template class Sim800LDialAction : public Action { Sim800LDialAction(Sim800LComponent *parent) : parent_(parent) {} TEMPLATABLE_VALUE(std::string, recipient) - void play(Ts... x) { + void play(const Ts &...x) { auto recipient = this->recipient_.value(x...); this->parent_->dial(recipient); } @@ -203,7 +203,7 @@ template class Sim800LConnectAction : public Action { public: Sim800LConnectAction(Sim800LComponent *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->connect(); } + void play(const Ts &...x) { this->parent_->connect(); } protected: Sim800LComponent *parent_; @@ -213,7 +213,7 @@ template class Sim800LDisconnectAction : public Action { public: Sim800LDisconnectAction(Sim800LComponent *parent) : parent_(parent) {} - void play(Ts... x) { this->parent_->disconnect(); } + void play(const Ts &...x) { this->parent_->disconnect(); } protected: Sim800LComponent *parent_; diff --git a/esphome/components/sound_level/sound_level.h b/esphome/components/sound_level/sound_level.h index 6a80a60ac7..dc35f69fe2 100644 --- a/esphome/components/sound_level/sound_level.h +++ b/esphome/components/sound_level/sound_level.h @@ -60,12 +60,12 @@ class SoundLevelComponent : public Component { template class StartAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->start(); } + void play(const Ts &...x) override { this->parent_->start(); } }; template class StopAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->stop(); } + void play(const Ts &...x) override { this->parent_->stop(); } }; } // namespace sound_level diff --git a/esphome/components/speaker/automation.h b/esphome/components/speaker/automation.h index c083796eea..80bba25030 100644 --- a/esphome/components/speaker/automation.h +++ b/esphome/components/speaker/automation.h @@ -19,7 +19,7 @@ template class PlayAction : public Action, public Parente this->static_ = true; } - void play(Ts... x) override { + void play(const Ts &...x) override { if (this->static_) { this->parent_->play(this->data_static_); } else { @@ -36,14 +36,14 @@ template class PlayAction : public Action, public Parente template class VolumeSetAction : public Action, public Parented { TEMPLATABLE_VALUE(float, volume) - void play(Ts... x) override { this->parent_->set_volume(this->volume_.value(x...)); } + void play(const Ts &...x) override { this->parent_->set_volume(this->volume_.value(x...)); } }; template class MuteOnAction : public Action { public: explicit MuteOnAction(Speaker *speaker) : speaker_(speaker) {} - void play(Ts... x) override { this->speaker_->set_mute_state(true); } + void play(const Ts &...x) override { this->speaker_->set_mute_state(true); } protected: Speaker *speaker_; @@ -53,7 +53,7 @@ template class MuteOffAction : public Action { public: explicit MuteOffAction(Speaker *speaker) : speaker_(speaker) {} - void play(Ts... x) override { this->speaker_->set_mute_state(false); } + void play(const Ts &...x) override { this->speaker_->set_mute_state(false); } protected: Speaker *speaker_; @@ -61,22 +61,22 @@ template class MuteOffAction : public Action { template class StopAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->stop(); } + void play(const Ts &...x) override { this->parent_->stop(); } }; template class FinishAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->finish(); } + void play(const Ts &...x) override { this->parent_->finish(); } }; template class IsPlayingCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->is_running(); } + bool check(const Ts &...x) override { return this->parent_->is_running(); } }; template class IsStoppedCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->is_stopped(); } + bool check(const Ts &...x) override { return this->parent_->is_stopped(); } }; } // namespace speaker diff --git a/esphome/components/speaker/media_player/automation.h b/esphome/components/speaker/media_player/automation.h index d1a01aabc4..fdf3db07f9 100644 --- a/esphome/components/speaker/media_player/automation.h +++ b/esphome/components/speaker/media_player/automation.h @@ -14,7 +14,7 @@ template class PlayOnDeviceMediaAction : public Action, p TEMPLATABLE_VALUE(audio::AudioFile *, audio_file) TEMPLATABLE_VALUE(bool, announcement) TEMPLATABLE_VALUE(bool, enqueue) - void play(Ts... x) override { + void play(const Ts &...x) override { this->parent_->play_file(this->audio_file_.value(x...), this->announcement_.value(x...), this->enqueue_.value(x...)); } diff --git a/esphome/components/sprinkler/automation.h b/esphome/components/sprinkler/automation.h index 59c6cd50e1..d6c877ae90 100644 --- a/esphome/components/sprinkler/automation.h +++ b/esphome/components/sprinkler/automation.h @@ -13,7 +13,7 @@ template class SetDividerAction : public Action { TEMPLATABLE_VALUE(uint32_t, divider) - void play(Ts... x) override { this->sprinkler_->set_divider(this->divider_.optional_value(x...)); } + void play(const Ts &...x) override { this->sprinkler_->set_divider(this->divider_.optional_value(x...)); } protected: Sprinkler *sprinkler_; @@ -25,7 +25,7 @@ template class SetMultiplierAction : public Action { TEMPLATABLE_VALUE(float, multiplier) - void play(Ts... x) override { this->sprinkler_->set_multiplier(this->multiplier_.optional_value(x...)); } + void play(const Ts &...x) override { this->sprinkler_->set_multiplier(this->multiplier_.optional_value(x...)); } protected: Sprinkler *sprinkler_; @@ -38,7 +38,7 @@ template class QueueValveAction : public Action { TEMPLATABLE_VALUE(size_t, valve_number) TEMPLATABLE_VALUE(uint32_t, valve_run_duration) - void play(Ts... x) override { + void play(const Ts &...x) override { this->sprinkler_->queue_valve(this->valve_number_.optional_value(x...), this->valve_run_duration_.optional_value(x...)); } @@ -51,7 +51,7 @@ template class ClearQueuedValvesAction : public Action { public: explicit ClearQueuedValvesAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {} - void play(Ts... x) override { this->sprinkler_->clear_queued_valves(); } + void play(const Ts &...x) override { this->sprinkler_->clear_queued_valves(); } protected: Sprinkler *sprinkler_; @@ -63,7 +63,7 @@ template class SetRepeatAction : public Action { TEMPLATABLE_VALUE(uint32_t, repeat) - void play(Ts... x) override { this->sprinkler_->set_repeat(this->repeat_.optional_value(x...)); } + void play(const Ts &...x) override { this->sprinkler_->set_repeat(this->repeat_.optional_value(x...)); } protected: Sprinkler *sprinkler_; @@ -76,7 +76,7 @@ template class SetRunDurationAction : public Action { TEMPLATABLE_VALUE(size_t, valve_number) TEMPLATABLE_VALUE(uint32_t, valve_run_duration) - void play(Ts... x) override { + void play(const Ts &...x) override { this->sprinkler_->set_valve_run_duration(this->valve_number_.optional_value(x...), this->valve_run_duration_.optional_value(x...)); } @@ -89,7 +89,7 @@ template class StartFromQueueAction : public Action { public: explicit StartFromQueueAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {} - void play(Ts... x) override { this->sprinkler_->start_from_queue(); } + void play(const Ts &...x) override { this->sprinkler_->start_from_queue(); } protected: Sprinkler *sprinkler_; @@ -99,7 +99,7 @@ template class StartFullCycleAction : public Action { public: explicit StartFullCycleAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {} - void play(Ts... x) override { this->sprinkler_->start_full_cycle(); } + void play(const Ts &...x) override { this->sprinkler_->start_full_cycle(); } protected: Sprinkler *sprinkler_; @@ -112,7 +112,7 @@ template class StartSingleValveAction : public Action { TEMPLATABLE_VALUE(size_t, valve_to_start) TEMPLATABLE_VALUE(uint32_t, valve_run_duration) - void play(Ts... x) override { + void play(const Ts &...x) override { this->sprinkler_->start_single_valve(this->valve_to_start_.optional_value(x...), this->valve_run_duration_.optional_value(x...)); } @@ -125,7 +125,7 @@ template class ShutdownAction : public Action { public: explicit ShutdownAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {} - void play(Ts... x) override { this->sprinkler_->shutdown(); } + void play(const Ts &...x) override { this->sprinkler_->shutdown(); } protected: Sprinkler *sprinkler_; @@ -135,7 +135,7 @@ template class NextValveAction : public Action { public: explicit NextValveAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {} - void play(Ts... x) override { this->sprinkler_->next_valve(); } + void play(const Ts &...x) override { this->sprinkler_->next_valve(); } protected: Sprinkler *sprinkler_; @@ -145,7 +145,7 @@ template class PreviousValveAction : public Action { public: explicit PreviousValveAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {} - void play(Ts... x) override { this->sprinkler_->previous_valve(); } + void play(const Ts &...x) override { this->sprinkler_->previous_valve(); } protected: Sprinkler *sprinkler_; @@ -155,7 +155,7 @@ template class PauseAction : public Action { public: explicit PauseAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {} - void play(Ts... x) override { this->sprinkler_->pause(); } + void play(const Ts &...x) override { this->sprinkler_->pause(); } protected: Sprinkler *sprinkler_; @@ -165,7 +165,7 @@ template class ResumeAction : public Action { public: explicit ResumeAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {} - void play(Ts... x) override { this->sprinkler_->resume(); } + void play(const Ts &...x) override { this->sprinkler_->resume(); } protected: Sprinkler *sprinkler_; @@ -175,7 +175,7 @@ template class ResumeOrStartAction : public Action { public: explicit ResumeOrStartAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {} - void play(Ts... x) override { this->sprinkler_->resume_or_start_full_cycle(); } + void play(const Ts &...x) override { this->sprinkler_->resume_or_start_full_cycle(); } protected: Sprinkler *sprinkler_; diff --git a/esphome/components/sps30/automation.h b/esphome/components/sps30/automation.h index 443aafb575..67af813687 100644 --- a/esphome/components/sps30/automation.h +++ b/esphome/components/sps30/automation.h @@ -11,7 +11,7 @@ template class StartFanAction : public Action { public: explicit StartFanAction(SPS30Component *sps30) : sps30_(sps30) {} - void play(Ts... x) override { this->sps30_->start_fan_cleaning(); } + void play(const Ts &...x) override { this->sps30_->start_fan_cleaning(); } protected: SPS30Component *sps30_; diff --git a/esphome/components/stepper/stepper.h b/esphome/components/stepper/stepper.h index 1cf4830b1f..2bad672494 100644 --- a/esphome/components/stepper/stepper.h +++ b/esphome/components/stepper/stepper.h @@ -44,7 +44,7 @@ template class SetTargetAction : public Action { TEMPLATABLE_VALUE(int32_t, target) - void play(Ts... x) override { this->parent_->set_target(this->target_.value(x...)); } + void play(const Ts &...x) override { this->parent_->set_target(this->target_.value(x...)); } protected: Stepper *parent_; @@ -56,7 +56,7 @@ template class ReportPositionAction : public Action { TEMPLATABLE_VALUE(int32_t, position) - void play(Ts... x) override { this->parent_->report_position(this->position_.value(x...)); } + void play(const Ts &...x) override { this->parent_->report_position(this->position_.value(x...)); } protected: Stepper *parent_; @@ -68,7 +68,7 @@ template class SetSpeedAction : public Action { TEMPLATABLE_VALUE(float, speed); - void play(Ts... x) override { + void play(const Ts &...x) override { float speed = this->speed_.value(x...); this->parent_->set_max_speed(speed); this->parent_->on_update_speed(); @@ -84,7 +84,7 @@ template class SetAccelerationAction : public Action { TEMPLATABLE_VALUE(float, acceleration); - void play(Ts... x) override { + void play(const Ts &...x) override { float acceleration = this->acceleration_.value(x...); this->parent_->set_acceleration(acceleration); } @@ -99,7 +99,7 @@ template class SetDecelerationAction : public Action { TEMPLATABLE_VALUE(float, deceleration); - void play(Ts... x) override { + void play(const Ts &...x) override { float deceleration = this->deceleration_.value(x...); this->parent_->set_deceleration(deceleration); } diff --git a/esphome/components/sun/sun.h b/esphome/components/sun/sun.h index 77d62d34c3..67a0306a37 100644 --- a/esphome/components/sun/sun.h +++ b/esphome/components/sun/sun.h @@ -115,7 +115,7 @@ template class SunCondition : public Condition, public Pa TEMPLATABLE_VALUE(double, elevation); void set_above(bool above) { above_ = above; } - bool check(Ts... x) override { + bool check(const Ts &...x) override { double elevation = this->elevation_.value(x...); double current = this->parent_->elevation(); if (this->above_) { diff --git a/esphome/components/switch/automation.h b/esphome/components/switch/automation.h index b8cbc9b976..27d3474c97 100644 --- a/esphome/components/switch/automation.h +++ b/esphome/components/switch/automation.h @@ -11,7 +11,7 @@ template class TurnOnAction : public Action { public: explicit TurnOnAction(Switch *a_switch) : switch_(a_switch) {} - void play(Ts... x) override { this->switch_->turn_on(); } + void play(const Ts &...x) override { this->switch_->turn_on(); } protected: Switch *switch_; @@ -21,7 +21,7 @@ template class TurnOffAction : public Action { public: explicit TurnOffAction(Switch *a_switch) : switch_(a_switch) {} - void play(Ts... x) override { this->switch_->turn_off(); } + void play(const Ts &...x) override { this->switch_->turn_off(); } protected: Switch *switch_; @@ -31,7 +31,7 @@ template class ToggleAction : public Action { public: explicit ToggleAction(Switch *a_switch) : switch_(a_switch) {} - void play(Ts... x) override { this->switch_->toggle(); } + void play(const Ts &...x) override { this->switch_->toggle(); } protected: Switch *switch_; @@ -43,7 +43,7 @@ template class ControlAction : public Action { TEMPLATABLE_VALUE(bool, state) - void play(Ts... x) override { + void play(const Ts &...x) override { auto state = this->state_.optional_value(x...); if (state.has_value()) { this->switch_->control(*state); @@ -57,7 +57,7 @@ template class ControlAction : public Action { template class SwitchCondition : public Condition { public: SwitchCondition(Switch *parent, bool state) : parent_(parent), state_(state) {} - bool check(Ts... x) override { return this->parent_->state == this->state_; } + bool check(const Ts &...x) override { return this->parent_->state == this->state_; } protected: Switch *parent_; @@ -98,7 +98,7 @@ template class SwitchPublishAction : public Action { SwitchPublishAction(Switch *a_switch) : switch_(a_switch) {} TEMPLATABLE_VALUE(bool, state) - void play(Ts... x) override { this->switch_->publish_state(this->state_.value(x...)); } + void play(const Ts &...x) override { this->switch_->publish_state(this->state_.value(x...)); } protected: Switch *switch_; diff --git a/esphome/components/sx126x/automation.h b/esphome/components/sx126x/automation.h index 520ef99718..6b2371e253 100644 --- a/esphome/components/sx126x/automation.h +++ b/esphome/components/sx126x/automation.h @@ -9,7 +9,7 @@ namespace sx126x { template class RunImageCalAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->run_image_cal(); } + void play(const Ts &...x) override { this->parent_->run_image_cal(); } }; template class SendPacketAction : public Action, public Parented { @@ -24,7 +24,7 @@ template class SendPacketAction : public Action, public P this->static_ = true; } - void play(Ts... x) override { + void play(const Ts &...x) override { if (this->static_) { this->parent_->transmit_packet(this->data_static_); } else { @@ -40,22 +40,22 @@ template class SendPacketAction : public Action, public P template class SetModeTxAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->set_mode_tx(); } + void play(const Ts &...x) override { this->parent_->set_mode_tx(); } }; template class SetModeRxAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->set_mode_rx(); } + void play(const Ts &...x) override { this->parent_->set_mode_rx(); } }; template class SetModeSleepAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->set_mode_sleep(); } + void play(const Ts &...x) override { this->parent_->set_mode_sleep(); } }; template class SetModeStandbyAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->set_mode_standby(STDBY_XOSC); } + void play(const Ts &...x) override { this->parent_->set_mode_standby(STDBY_XOSC); } }; } // namespace sx126x diff --git a/esphome/components/sx127x/automation.h b/esphome/components/sx127x/automation.h index 2b9c261de1..eae16c11fa 100644 --- a/esphome/components/sx127x/automation.h +++ b/esphome/components/sx127x/automation.h @@ -9,7 +9,7 @@ namespace sx127x { template class RunImageCalAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->run_image_cal(); } + void play(const Ts &...x) override { this->parent_->run_image_cal(); } }; template class SendPacketAction : public Action, public Parented { @@ -24,7 +24,7 @@ template class SendPacketAction : public Action, public P this->static_ = true; } - void play(Ts... x) override { + void play(const Ts &...x) override { if (this->static_) { this->parent_->transmit_packet(this->data_static_); } else { @@ -40,22 +40,22 @@ template class SendPacketAction : public Action, public P template class SetModeTxAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->set_mode_tx(); } + void play(const Ts &...x) override { this->parent_->set_mode_tx(); } }; template class SetModeRxAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->set_mode_rx(); } + void play(const Ts &...x) override { this->parent_->set_mode_rx(); } }; template class SetModeSleepAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->set_mode_sleep(); } + void play(const Ts &...x) override { this->parent_->set_mode_sleep(); } }; template class SetModeStandbyAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->set_mode_standby(); } + void play(const Ts &...x) override { this->parent_->set_mode_standby(); } }; } // namespace sx127x diff --git a/esphome/components/template/lock/automation.h b/esphome/components/template/lock/automation.h index 6124546592..bd110b7b0c 100644 --- a/esphome/components/template/lock/automation.h +++ b/esphome/components/template/lock/automation.h @@ -11,7 +11,7 @@ template class TemplateLockPublishAction : public Action, public: TEMPLATABLE_VALUE(lock::LockState, state) - void play(Ts... x) override { this->parent_->publish_state(this->state_.value(x...)); } + void play(const Ts &...x) override { this->parent_->publish_state(this->state_.value(x...)); } }; } // namespace template_ diff --git a/esphome/components/template/valve/automation.h b/esphome/components/template/valve/automation.h index af9b070c60..e3f394ac7c 100644 --- a/esphome/components/template/valve/automation.h +++ b/esphome/components/template/valve/automation.h @@ -11,7 +11,7 @@ template class TemplateValvePublishAction : public Action TEMPLATABLE_VALUE(float, position) TEMPLATABLE_VALUE(valve::ValveOperation, current_operation) - void play(Ts... x) override { + void play(const Ts &...x) override { if (this->position_.has_value()) this->parent_->position = this->position_.value(x...); if (this->current_operation_.has_value()) diff --git a/esphome/components/text/automation.h b/esphome/components/text/automation.h index f20a4f433b..e7667fe491 100644 --- a/esphome/components/text/automation.h +++ b/esphome/components/text/automation.h @@ -19,7 +19,7 @@ template class TextSetAction : public Action { explicit TextSetAction(Text *text) : text_(text) {} TEMPLATABLE_VALUE(std::string, value) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->text_->make_call(); call.set_value(this->value_.value(x...)); call.perform(); diff --git a/esphome/components/text_sensor/automation.h b/esphome/components/text_sensor/automation.h index d7286845e0..709c54c140 100644 --- a/esphome/components/text_sensor/automation.h +++ b/esphome/components/text_sensor/automation.h @@ -29,7 +29,7 @@ template class TextSensorStateCondition : public Conditionparent_->state == this->state_.value(x...); } + bool check(const Ts &...x) override { return this->parent_->state == this->state_.value(x...); } protected: TextSensor *parent_; @@ -40,7 +40,7 @@ template class TextSensorPublishAction : public Action { TextSensorPublishAction(TextSensor *sensor) : sensor_(sensor) {} TEMPLATABLE_VALUE(std::string, state) - void play(Ts... x) override { this->sensor_->publish_state(this->state_.value(x...)); } + void play(const Ts &...x) override { this->sensor_->publish_state(this->state_.value(x...)); } protected: TextSensor *sensor_; diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index 7e60bbd234..bbcecaa628 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -67,7 +67,7 @@ class RealTimeClock : public PollingComponent { template class TimeHasTimeCondition : public Condition { public: TimeHasTimeCondition(RealTimeClock *parent) : parent_(parent) {} - bool check(Ts... x) override { return this->parent_->now().is_valid(); } + bool check(const Ts &...x) override { return this->parent_->now().is_valid(); } protected: RealTimeClock *parent_; diff --git a/esphome/components/tm1651/tm1651.h b/esphome/components/tm1651/tm1651.h index 7079910adf..83e74c5f33 100644 --- a/esphome/components/tm1651/tm1651.h +++ b/esphome/components/tm1651/tm1651.h @@ -61,7 +61,7 @@ template class SetBrightnessAction : public Action, publi public: TEMPLATABLE_VALUE(uint8_t, brightness) - void play(Ts... x) override { + void play(const Ts &...x) override { auto brightness = this->brightness_.value(x...); this->parent_->set_brightness(brightness); } @@ -71,7 +71,7 @@ template class SetLevelAction : public Action, public Par public: TEMPLATABLE_VALUE(uint8_t, level) - void play(Ts... x) override { + void play(const Ts &...x) override { auto level = this->level_.value(x...); this->parent_->set_level(level); } @@ -81,7 +81,7 @@ template class SetLevelPercentAction : public Action, pub public: TEMPLATABLE_VALUE(uint8_t, level_percent) - void play(Ts... x) override { + void play(const Ts &...x) override { auto level_percent = this->level_percent_.value(x...); this->parent_->set_level_percent(level_percent); } @@ -89,12 +89,12 @@ template class SetLevelPercentAction : public Action, pub template class TurnOnAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->turn_on(); } + void play(const Ts &...x) override { this->parent_->turn_on(); } }; template class TurnOffAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->turn_off(); } + void play(const Ts &...x) override { this->parent_->turn_off(); } }; } // namespace tm1651 diff --git a/esphome/components/uart/automation.h b/esphome/components/uart/automation.h index 9c599253de..ad2c4d2bf1 100644 --- a/esphome/components/uart/automation.h +++ b/esphome/components/uart/automation.h @@ -23,7 +23,7 @@ template class UARTWriteAction : public Action, public Pa this->static_ = true; } - void play(Ts... x) override { + void play(const Ts &...x) override { if (this->static_) { this->parent_->write_array(this->data_static_); } else { diff --git a/esphome/components/udp/automation.h b/esphome/components/udp/automation.h index f75e6d35bf..c5e5e2eae8 100644 --- a/esphome/components/udp/automation.h +++ b/esphome/components/udp/automation.h @@ -20,7 +20,7 @@ template class UDPWriteAction : public Action, public Par this->static_ = true; } - void play(Ts... x) override { + void play(const Ts &...x) override { if (this->static_) { this->parent_->send_packet(this->data_static_); } else { diff --git a/esphome/components/ufire_ec/ufire_ec.h b/esphome/components/ufire_ec/ufire_ec.h index 3d436555a2..bfbed1b43e 100644 --- a/esphome/components/ufire_ec/ufire_ec.h +++ b/esphome/components/ufire_ec/ufire_ec.h @@ -65,7 +65,7 @@ template class UFireECCalibrateProbeAction : public Actionparent_->calibrate_probe(this->solution_.value(x...), this->temperature_.value(x...)); } @@ -77,7 +77,7 @@ template class UFireECResetAction : public Action { public: UFireECResetAction(UFireECComponent *parent) : parent_(parent) {} - void play(Ts... x) override { this->parent_->reset_board(); } + void play(const Ts &...x) override { this->parent_->reset_board(); } protected: UFireECComponent *parent_; diff --git a/esphome/components/ufire_ise/ufire_ise.h b/esphome/components/ufire_ise/ufire_ise.h index 01efdcdb55..fe9a6dfb9c 100644 --- a/esphome/components/ufire_ise/ufire_ise.h +++ b/esphome/components/ufire_ise/ufire_ise.h @@ -64,7 +64,7 @@ template class UFireISECalibrateProbeLowAction : public Actionparent_->calibrate_probe_low(this->solution_.value(x...)); } + void play(const Ts &...x) override { this->parent_->calibrate_probe_low(this->solution_.value(x...)); } protected: UFireISEComponent *parent_; @@ -75,7 +75,7 @@ template class UFireISECalibrateProbeHighAction : public Action< UFireISECalibrateProbeHighAction(UFireISEComponent *parent) : parent_(parent) {} TEMPLATABLE_VALUE(float, solution) - void play(Ts... x) override { this->parent_->calibrate_probe_high(this->solution_.value(x...)); } + void play(const Ts &...x) override { this->parent_->calibrate_probe_high(this->solution_.value(x...)); } protected: UFireISEComponent *parent_; @@ -85,7 +85,7 @@ template class UFireISEResetAction : public Action { public: UFireISEResetAction(UFireISEComponent *parent) : parent_(parent) {} - void play(Ts... x) override { this->parent_->reset_board(); } + void play(const Ts &...x) override { this->parent_->reset_board(); } protected: UFireISEComponent *parent_; diff --git a/esphome/components/update/automation.h b/esphome/components/update/automation.h index df50f86a0c..8563b855fe 100644 --- a/esphome/components/update/automation.h +++ b/esphome/components/update/automation.h @@ -11,12 +11,12 @@ template class PerformAction : public Action, public Pare TEMPLATABLE_VALUE(bool, force) public: - void play(Ts... x) override { this->parent_->perform(this->force_.value(x...)); } + void play(const Ts &...x) override { this->parent_->perform(this->force_.value(x...)); } }; template class IsAvailableCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->state == UPDATE_STATE_AVAILABLE; } + bool check(const Ts &...x) override { return this->parent_->state == UPDATE_STATE_AVAILABLE; } }; } // namespace update diff --git a/esphome/components/valve/automation.h b/esphome/components/valve/automation.h index f2c06270c0..87e9cde088 100644 --- a/esphome/components/valve/automation.h +++ b/esphome/components/valve/automation.h @@ -11,7 +11,7 @@ template class OpenAction : public Action { public: explicit OpenAction(Valve *valve) : valve_(valve) {} - void play(Ts... x) override { this->valve_->make_call().set_command_open().perform(); } + void play(const Ts &...x) override { this->valve_->make_call().set_command_open().perform(); } protected: Valve *valve_; @@ -21,7 +21,7 @@ template class CloseAction : public Action { public: explicit CloseAction(Valve *valve) : valve_(valve) {} - void play(Ts... x) override { this->valve_->make_call().set_command_close().perform(); } + void play(const Ts &...x) override { this->valve_->make_call().set_command_close().perform(); } protected: Valve *valve_; @@ -31,7 +31,7 @@ template class StopAction : public Action { public: explicit StopAction(Valve *valve) : valve_(valve) {} - void play(Ts... x) override { this->valve_->make_call().set_command_stop().perform(); } + void play(const Ts &...x) override { this->valve_->make_call().set_command_stop().perform(); } protected: Valve *valve_; @@ -41,7 +41,7 @@ template class ToggleAction : public Action { public: explicit ToggleAction(Valve *valve) : valve_(valve) {} - void play(Ts... x) override { this->valve_->make_call().set_command_toggle().perform(); } + void play(const Ts &...x) override { this->valve_->make_call().set_command_toggle().perform(); } protected: Valve *valve_; @@ -54,7 +54,7 @@ template class ControlAction : public Action { TEMPLATABLE_VALUE(bool, stop) TEMPLATABLE_VALUE(float, position) - void play(Ts... x) override { + void play(const Ts &...x) override { auto call = this->valve_->make_call(); if (this->stop_.has_value()) call.set_stop(this->stop_.value(x...)); @@ -70,7 +70,7 @@ template class ControlAction : public Action { template class ValveIsOpenCondition : public Condition { public: ValveIsOpenCondition(Valve *valve) : valve_(valve) {} - bool check(Ts... x) override { return this->valve_->is_fully_open(); } + bool check(const Ts &...x) override { return this->valve_->is_fully_open(); } protected: Valve *valve_; @@ -79,7 +79,7 @@ template class ValveIsOpenCondition : public Condition { template class ValveIsClosedCondition : public Condition { public: ValveIsClosedCondition(Valve *valve) : valve_(valve) {} - bool check(Ts... x) override { return this->valve_->is_fully_closed(); } + bool check(const Ts &...x) override { return this->valve_->is_fully_closed(); } protected: Valve *valve_; diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 95f77dbf09..8d3d3497ec 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -324,7 +324,7 @@ template class StartAction : public Action, public Parent TEMPLATABLE_VALUE(std::string, wake_word); public: - void play(Ts... x) override { + void play(const Ts &...x) override { this->parent_->set_wake_word(this->wake_word_.value(x...)); this->parent_->request_start(false, this->silence_detection_); } @@ -337,22 +337,22 @@ template class StartAction : public Action, public Parent template class StartContinuousAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->request_start(true, true); } + void play(const Ts &...x) override { this->parent_->request_start(true, true); } }; template class StopAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->request_stop(); } + void play(const Ts &...x) override { this->parent_->request_stop(); } }; template class IsRunningCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->is_running() || this->parent_->is_continuous(); } + bool check(const Ts &...x) override { return this->parent_->is_running() || this->parent_->is_continuous(); } }; template class ConnectedCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->get_api_connection() != nullptr; } + bool check(const Ts &...x) override { return this->parent_->get_api_connection() != nullptr; } }; extern VoiceAssistant *global_voice_assistant; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 89b7b1fa41..ac63e0eb0c 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -455,22 +455,22 @@ extern WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid- template class WiFiConnectedCondition : public Condition { public: - bool check(Ts... x) override { return global_wifi_component->is_connected(); } + bool check(const Ts &...x) override { return global_wifi_component->is_connected(); } }; template class WiFiEnabledCondition : public Condition { public: - bool check(Ts... x) override { return !global_wifi_component->is_disabled(); } + bool check(const Ts &...x) override { return !global_wifi_component->is_disabled(); } }; template class WiFiEnableAction : public Action { public: - void play(Ts... x) override { global_wifi_component->enable(); } + void play(const Ts &...x) override { global_wifi_component->enable(); } }; template class WiFiDisableAction : public Action { public: - void play(Ts... x) override { global_wifi_component->disable(); } + void play(const Ts &...x) override { global_wifi_component->disable(); } }; template class WiFiConfigureAction : public Action, public Component { @@ -480,7 +480,7 @@ template class WiFiConfigureAction : public Action, publi TEMPLATABLE_VALUE(bool, save) TEMPLATABLE_VALUE(uint32_t, connection_timeout) - void play(Ts... x) override { + void play(const Ts &...x) override { auto ssid = this->ssid_.value(x...); auto password = this->password_.value(x...); // Avoid multiple calls diff --git a/esphome/components/wireguard/wireguard.h b/esphome/components/wireguard/wireguard.h index 5db9a48c90..f8f79b835d 100644 --- a/esphome/components/wireguard/wireguard.h +++ b/esphome/components/wireguard/wireguard.h @@ -148,25 +148,25 @@ std::string mask_key(const std::string &key); /// Condition to check if remote peer is online. template class WireguardPeerOnlineCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->is_peer_up(); } + bool check(const Ts &...x) override { return this->parent_->is_peer_up(); } }; /// Condition to check if Wireguard component is enabled. template class WireguardEnabledCondition : public Condition, public Parented { public: - bool check(Ts... x) override { return this->parent_->is_enabled(); } + bool check(const Ts &...x) override { return this->parent_->is_enabled(); } }; /// Action to enable Wireguard component. template class WireguardEnableAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->enable(); } + void play(const Ts &...x) override { this->parent_->enable(); } }; /// Action to disable Wireguard component. template class WireguardDisableAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->disable(); } + void play(const Ts &...x) override { this->parent_->disable(); } }; } // namespace wireguard diff --git a/esphome/core/automation.h b/esphome/core/automation.h index c22b3ca0e3..33e08c9c1c 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -148,7 +148,7 @@ template class TemplatableValue { template class Condition { public: /// Check whether this condition passes. This condition check must be instant, and not cause any delays. - virtual bool check(Ts... x) = 0; + virtual bool check(const Ts &...x) = 0; /// Call check with a tuple of values as parameter. bool check_tuple(const std::tuple &tuple) { @@ -166,7 +166,7 @@ template class Automation; template class Trigger { public: /// Inform the parent automation that the event has triggered. - void trigger(Ts... x) { + void trigger(const Ts &...x) { if (this->automation_parent_ == nullptr) return; this->automation_parent_->trigger(x...); @@ -194,7 +194,7 @@ template class ActionList; template class Action { public: - virtual void play_complex(Ts... x) { + virtual void play_complex(const Ts &...x) { this->num_running_++; this->play(x...); this->play_next_(x...); @@ -222,8 +222,8 @@ template class Action { friend ActionList; template friend class ContinuationAction; - virtual void play(Ts... x) = 0; - void play_next_(Ts... x) { + virtual void play(const Ts &...x) = 0; + void play_next_(const Ts &...x) { if (this->num_running_ > 0) { this->num_running_--; if (this->next_ != nullptr) { @@ -273,7 +273,7 @@ template class ActionList { this->add_action(action); } } - void play(Ts... x) { + void play(const Ts &...x) { if (this->actions_begin_ != nullptr) this->actions_begin_->play_complex(x...); } @@ -315,7 +315,7 @@ template class Automation { void stop() { this->actions_.stop(); } - void trigger(Ts... x) { this->actions_.play(x...); } + void trigger(const Ts &...x) { this->actions_.play(x...); } bool is_running() { return this->actions_.is_running(); } diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 083bb3ae31..128a2d4b08 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -17,7 +17,7 @@ namespace esphome { template class AndCondition : public Condition { public: explicit AndCondition(std::initializer_list *> conditions) : conditions_(conditions) {} - bool check(Ts... x) override { + bool check(const Ts &...x) override { for (auto *condition : this->conditions_) { if (!condition->check(x...)) return false; @@ -33,7 +33,7 @@ template class AndCondition : public Condition { template class OrCondition : public Condition { public: explicit OrCondition(std::initializer_list *> conditions) : conditions_(conditions) {} - bool check(Ts... x) override { + bool check(const Ts &...x) override { for (auto *condition : this->conditions_) { if (condition->check(x...)) return true; @@ -49,7 +49,7 @@ template class OrCondition : public Condition { template class NotCondition : public Condition { public: explicit NotCondition(Condition *condition) : condition_(condition) {} - bool check(Ts... x) override { return !this->condition_->check(x...); } + bool check(const Ts &...x) override { return !this->condition_->check(x...); } protected: Condition *condition_; @@ -58,7 +58,7 @@ template class NotCondition : public Condition { template class XorCondition : public Condition { public: explicit XorCondition(std::initializer_list *> conditions) : conditions_(conditions) {} - bool check(Ts... x) override { + bool check(const Ts &...x) override { size_t result = 0; for (auto *condition : this->conditions_) { result += condition->check(x...); @@ -74,7 +74,7 @@ template class XorCondition : public Condition { template class LambdaCondition : public Condition { public: explicit LambdaCondition(std::function &&f) : f_(std::move(f)) {} - bool check(Ts... x) override { return this->f_(x...); } + bool check(const Ts &...x) override { return this->f_(x...); } protected: std::function f_; @@ -86,7 +86,7 @@ template class LambdaCondition : public Condition { template class StatelessLambdaCondition : public Condition { public: explicit StatelessLambdaCondition(bool (*f)(Ts...)) : f_(f) {} - bool check(Ts... x) override { return this->f_(x...); } + bool check(const Ts &...x) override { return this->f_(x...); } protected: bool (*f_)(Ts...); @@ -107,7 +107,7 @@ template class ForCondition : public Condition, public Co return cond; } - bool check(Ts... x) override { + bool check(const Ts &...x) override { if (!this->check_internal()) return false; return millis() - this->last_inactive_ >= this->time_.value(x...); @@ -171,7 +171,7 @@ template class DelayAction : public Action, public Compon TEMPLATABLE_VALUE(uint32_t, delay) - void play_complex(Ts... x) override { + void play_complex(const Ts &...x) override { auto f = std::bind(&DelayAction::play_next_, this, x...); this->num_running_++; @@ -187,7 +187,7 @@ template class DelayAction : public Action, public Compon } float get_setup_priority() const override { return setup_priority::HARDWARE; } - void play(Ts... x) override { /* ignore - see play_complex */ + void play(const Ts &...x) override { /* ignore - see play_complex */ } void stop() override { this->cancel_timeout("delay"); } @@ -197,7 +197,7 @@ template class LambdaAction : public Action { public: explicit LambdaAction(std::function &&f) : f_(std::move(f)) {} - void play(Ts... x) override { this->f_(x...); } + void play(const Ts &...x) override { this->f_(x...); } protected: std::function f_; @@ -210,7 +210,7 @@ template class StatelessLambdaAction : public Action { public: explicit StatelessLambdaAction(void (*f)(Ts...)) : f_(f) {} - void play(Ts... x) override { this->f_(x...); } + void play(const Ts &...x) override { this->f_(x...); } protected: void (*f_)(Ts...); @@ -223,7 +223,7 @@ template class ContinuationAction : public Action { public: explicit ContinuationAction(Action *parent) : parent_(parent) {} - void play(Ts... x) override { this->parent_->play_next_(x...); } + void play(const Ts &...x) override { this->parent_->play_next_(x...); } protected: Action *parent_; @@ -238,7 +238,7 @@ template class WhileLoopContinuation : public Action { public: explicit WhileLoopContinuation(WhileAction *parent) : parent_(parent) {} - void play(Ts... x) override; + void play(const Ts &...x) override; protected: WhileAction *parent_; @@ -258,7 +258,7 @@ template class IfAction : public Action { this->else_.add_action(new ContinuationAction(this)); } - void play_complex(Ts... x) override { + void play_complex(const Ts &...x) override { this->num_running_++; bool res = this->condition_->check(x...); if (res) { @@ -276,7 +276,7 @@ template class IfAction : public Action { } } - void play(Ts... x) override { /* ignore - see play_complex */ + void play(const Ts &...x) override { /* ignore - see play_complex */ } void stop() override { @@ -301,7 +301,7 @@ template class WhileAction : public Action { friend class WhileLoopContinuation; - void play_complex(Ts... x) override { + void play_complex(const Ts &...x) override { this->num_running_++; // Initial condition check if (!this->condition_->check(x...)) { @@ -316,7 +316,7 @@ template class WhileAction : public Action { } } - void play(Ts... x) override { /* ignore - see play_complex */ + void play(const Ts &...x) override { /* ignore - see play_complex */ } void stop() override { this->then_.stop(); } @@ -327,7 +327,7 @@ template class WhileAction : public Action { }; // Implementation of WhileLoopContinuation::play -template void WhileLoopContinuation::play(Ts... x) { +template void WhileLoopContinuation::play(const Ts &...x) { if (this->parent_->num_running_ > 0 && this->parent_->condition_->check(x...)) { // play again this->parent_->then_.play(x...); @@ -346,7 +346,7 @@ template class RepeatLoopContinuation : public Action *parent) : parent_(parent) {} - void play(uint32_t iteration, Ts... x) override; + void play(const uint32_t &iteration, const Ts &...x) override; protected: RepeatAction *parent_; @@ -363,7 +363,7 @@ template class RepeatAction : public Action { friend class RepeatLoopContinuation; - void play_complex(Ts... x) override { + void play_complex(const Ts &...x) override { this->num_running_++; if (this->count_.value(x...) > 0) { this->then_.play(0, x...); @@ -372,7 +372,7 @@ template class RepeatAction : public Action { } } - void play(Ts... x) override { /* ignore - see play_complex */ + void play(const Ts &...x) override { /* ignore - see play_complex */ } void stop() override { this->then_.stop(); } @@ -382,12 +382,12 @@ template class RepeatAction : public Action { }; // Implementation of RepeatLoopContinuation::play -template void RepeatLoopContinuation::play(uint32_t iteration, Ts... x) { - iteration++; - if (iteration >= this->parent_->count_.value(x...)) { +template void RepeatLoopContinuation::play(const uint32_t &iteration, const Ts &...x) { + uint32_t next_iteration = iteration + 1; + if (next_iteration >= this->parent_->count_.value(x...)) { this->parent_->play_next_(x...); } else { - this->parent_->then_.play(iteration, x...); + this->parent_->then_.play(next_iteration, x...); } } @@ -409,7 +409,7 @@ template class WaitUntilAction : public Action, public Co this->disable_loop(); } - void play_complex(Ts... x) override { + void play_complex(const Ts &...x) override { this->num_running_++; // Check if we can continue immediately. if (this->condition_->check(x...)) { @@ -463,7 +463,7 @@ template class WaitUntilAction : public Action, public Co float get_setup_priority() const override { return setup_priority::DATA; } - void play(Ts... x) override { /* ignore - see play_complex */ + void play(const Ts &...x) override { /* ignore - see play_complex */ } protected: @@ -475,7 +475,7 @@ template class UpdateComponentAction : public Action { public: UpdateComponentAction(PollingComponent *component) : component_(component) {} - void play(Ts... x) override { + void play(const Ts &...x) override { if (!this->component_->is_ready()) return; this->component_->update(); @@ -489,7 +489,7 @@ template class SuspendComponentAction : public Action { public: SuspendComponentAction(PollingComponent *component) : component_(component) {} - void play(Ts... x) override { + void play(const Ts &...x) override { if (!this->component_->is_ready()) return; this->component_->stop_poller(); @@ -504,7 +504,7 @@ template class ResumeComponentAction : public Action { ResumeComponentAction(PollingComponent *component) : component_(component) {} TEMPLATABLE_VALUE(uint32_t, update_interval) - void play(Ts... x) override { + void play(const Ts &...x) override { if (!this->component_->is_ready()) { return; } diff --git a/tests/integration/fixtures/external_components/loop_test_component/loop_test_component.h b/tests/integration/fixtures/external_components/loop_test_component/loop_test_component.h index cdc04d491b..3dca2da2e9 100644 --- a/tests/integration/fixtures/external_components/loop_test_component/loop_test_component.h +++ b/tests/integration/fixtures/external_components/loop_test_component/loop_test_component.h @@ -39,7 +39,7 @@ template class EnableAction : public Action { public: EnableAction(LoopTestComponent *parent) : parent_(parent) {} - void play(Ts... x) override { this->parent_->service_enable(); } + void play(const Ts &...x) override { this->parent_->service_enable(); } protected: LoopTestComponent *parent_; @@ -49,7 +49,7 @@ template class DisableAction : public Action { public: DisableAction(LoopTestComponent *parent) : parent_(parent) {} - void play(Ts... x) override { this->parent_->service_disable(); } + void play(const Ts &...x) override { this->parent_->service_disable(); } protected: LoopTestComponent *parent_; From 32975c9d8be215e06795e1f7cd64d59b22a44386 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 4 Nov 2025 19:49:27 -0600 Subject: [PATCH 0098/1145] [select][lvgl] Fix FixedVector size() returning 0 when using operator[] after init() (#11721) --- esphome/components/lvgl/select/lvgl_select.h | 4 ++-- esphome/components/select/select_traits.cpp | 4 ++-- esphome/core/helpers.h | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/lvgl/select/lvgl_select.h b/esphome/components/lvgl/select/lvgl_select.h index 3b1fd67d68..d4c9631073 100644 --- a/esphome/components/lvgl/select/lvgl_select.h +++ b/esphome/components/lvgl/select/lvgl_select.h @@ -59,8 +59,8 @@ class LVGLSelect : public select::Select, public Component { const auto &opts = this->widget_->get_options(); FixedVector opt_ptrs; opt_ptrs.init(opts.size()); - for (size_t i = 0; i < opts.size(); i++) { - opt_ptrs[i] = opts[i].c_str(); + for (const auto &opt : opts) { + opt_ptrs.push_back(opt.c_str()); } this->traits.set_options(opt_ptrs); } diff --git a/esphome/components/select/select_traits.cpp b/esphome/components/select/select_traits.cpp index c6ded98ebf..e5e12bdc7a 100644 --- a/esphome/components/select/select_traits.cpp +++ b/esphome/components/select/select_traits.cpp @@ -7,8 +7,8 @@ void SelectTraits::set_options(const std::initializer_list &option void SelectTraits::set_options(const FixedVector &options) { this->options_.init(options.size()); - for (size_t i = 0; i < options.size(); i++) { - this->options_[i] = options[i]; + for (const auto &opt : options) { + this->options_.push_back(opt); } } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 91ddc70afa..660874ed1a 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -248,6 +248,8 @@ template class FixedVector { } // Allocate capacity - can be called multiple times to reinit + // IMPORTANT: After calling init(), you MUST use push_back() to add elements. + // Direct assignment via operator[] does NOT update the size counter. void init(size_t n) { cleanup_(); reset_(); From 6b522dfee6880df49d6826734310ca6ab7f3d25c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 09:14:21 -0600 Subject: [PATCH 0099/1145] [wifi_info] Reduce heap usage by up to 1.7KB in scan_results sensor (#11723) --- esphome/components/wifi_info/wifi_info_text_sensor.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index 2cb96123a0..04889d6bb3 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -10,6 +10,8 @@ namespace esphome { namespace wifi_info { +static constexpr size_t MAX_STATE_LENGTH = 255; + class IPAddressWiFiInfo : public PollingComponent, public text_sensor::TextSensor { public: void update() override { @@ -71,11 +73,14 @@ class ScanResultsWiFiInfo : public PollingComponent, public text_sensor::TextSen scan_results += "dB\n"; } + // There's a limit of 255 characters per state. + // Longer states just don't get sent so we truncate it. + if (scan_results.length() > MAX_STATE_LENGTH) { + scan_results.resize(MAX_STATE_LENGTH); + } if (this->last_scan_results_ != scan_results) { this->last_scan_results_ = scan_results; - // There's a limit of 255 characters per state. - // Longer states just don't get sent so we truncate it. - this->publish_state(scan_results.substr(0, 255)); + this->publish_state(scan_results); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } From 6e2dbbf636dbe98665904618553caddb0eb46315 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 09:15:05 -0600 Subject: [PATCH 0100/1145] [voice_assistant] Eliminate substr() allocations in text truncation (#11725) --- esphome/components/voice_assistant/voice_assistant.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 7ece73994f..fd35dc7d09 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -657,7 +657,8 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { ESP_LOGW(TAG, "No text in STT_END event"); return; } else if (text.length() > 500) { - text = text.substr(0, 497) + "..."; + text.resize(497); + text += "..."; } ESP_LOGD(TAG, "Speech recognised as: \"%s\"", text.c_str()); this->defer([this, text]() { this->stt_end_trigger_->trigger(text); }); @@ -714,7 +715,8 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { return; } if (text.length() > 500) { - text = text.substr(0, 497) + "..."; + text.resize(497); + text += "..."; } ESP_LOGD(TAG, "Response: \"%s\"", text.c_str()); this->defer([this, text]() { From 479f8dd85c75b7ca961a7c031330eb5ca4ea41a3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 09:17:28 -0600 Subject: [PATCH 0101/1145] [rtttl] Reduce flash usage by eliminating substr() allocations (#11722) --- esphome/components/rtttl/rtttl.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index b79f27e2e5..65fcc207d4 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -35,9 +35,9 @@ void Rtttl::dump_config() { void Rtttl::play(std::string rtttl) { if (this->state_ != State::STATE_STOPPED && this->state_ != State::STATE_STOPPING) { - int pos = this->rtttl_.find(':'); - auto name = this->rtttl_.substr(0, pos); - ESP_LOGW(TAG, "Already playing: %s", name.c_str()); + size_t pos = this->rtttl_.find(':'); + size_t len = (pos != std::string::npos) ? pos : this->rtttl_.length(); + ESP_LOGW(TAG, "Already playing: %.*s", (int) len, this->rtttl_.c_str()); return; } @@ -59,8 +59,7 @@ void Rtttl::play(std::string rtttl) { return; } - auto name = this->rtttl_.substr(0, this->position_); - ESP_LOGD(TAG, "Playing song %s", name.c_str()); + ESP_LOGD(TAG, "Playing song %.*s", (int) this->position_, this->rtttl_.c_str()); // get default duration this->position_ = this->rtttl_.find("d=", this->position_); From b7838671ae53b501d829a5fc7d50e04f7dc7790d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 10:57:20 -0600 Subject: [PATCH 0102/1145] [ld2420] Eliminate substr() allocation in firmware version parsing (#11724) --- esphome/components/ld2420/ld2420.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index b48c336d4e..f544acc112 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -174,7 +174,7 @@ static uint8_t calc_checksum(void *data, size_t size) { static int get_firmware_int(const char *version_string) { std::string version_str = version_string; if (version_str[0] == 'v') { - version_str = version_str.substr(1); + version_str.erase(0, 1); } version_str.erase(remove(version_str.begin(), version_str.end(), '.'), version_str.end()); int version_integer = stoi(version_str); From df53ff7afed11a1fc18ad03484d77c9c64bf1024 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 12:13:12 -0600 Subject: [PATCH 0103/1145] [scheduler] Extract helper functions to improve code readability (#11730) --- esphome/core/scheduler.cpp | 45 ++++++++++++++++++++------------------ esphome/core/scheduler.h | 34 ++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 11d59c2499..d285af2d0e 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -117,12 +117,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type item->set_name(name_cstr, !is_static_string); item->type = type; item->callback = std::move(func); - // Initialize remove to false (though it should already be from constructor) -#ifdef ESPHOME_THREAD_MULTI_ATOMICS - item->remove.store(false, std::memory_order_relaxed); -#else - item->remove = false; -#endif + // Reset remove flag - recycled items may have been cancelled (remove=true) in previous use + this->set_item_removed_(item.get(), false); item->is_retry = is_retry; #ifndef ESPHOME_THREAD_SINGLE @@ -153,21 +149,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type } #ifdef ESPHOME_DEBUG_SCHEDULER - // Validate static strings in debug mode - if (is_static_string && name_cstr != nullptr) { - validate_static_string(name_cstr); - } - - // Debug logging - const char *type_str = (type == SchedulerItem::TIMEOUT) ? "timeout" : "interval"; - if (type == SchedulerItem::TIMEOUT) { - ESP_LOGD(TAG, "set_%s(name='%s/%s', %s=%" PRIu32 ")", type_str, LOG_STR_ARG(item->get_source()), - name_cstr ? name_cstr : "(null)", type_str, delay); - } else { - ESP_LOGD(TAG, "set_%s(name='%s/%s', %s=%" PRIu32 ", offset=%" PRIu32 ")", type_str, LOG_STR_ARG(item->get_source()), - name_cstr ? name_cstr : "(null)", type_str, delay, - static_cast(item->get_next_execution() - now)); - } + this->debug_log_timer_(item.get(), is_static_string, name_cstr, type, delay, now); #endif /* ESPHOME_DEBUG_SCHEDULER */ // For retries, check if there's a cancelled timeout first @@ -787,4 +769,25 @@ void Scheduler::recycle_item_(std::unique_ptr item) { // else: unique_ptr will delete the item when it goes out of scope } +#ifdef ESPHOME_DEBUG_SCHEDULER +void Scheduler::debug_log_timer_(const SchedulerItem *item, bool is_static_string, const char *name_cstr, + SchedulerItem::Type type, uint32_t delay, uint64_t now) { + // Validate static strings in debug mode + if (is_static_string && name_cstr != nullptr) { + validate_static_string(name_cstr); + } + + // Debug logging + const char *type_str = (type == SchedulerItem::TIMEOUT) ? "timeout" : "interval"; + if (type == SchedulerItem::TIMEOUT) { + ESP_LOGD(TAG, "set_%s(name='%s/%s', %s=%" PRIu32 ")", type_str, LOG_STR_ARG(item->get_source()), + name_cstr ? name_cstr : "(null)", type_str, delay); + } else { + ESP_LOGD(TAG, "set_%s(name='%s/%s', %s=%" PRIu32 ", offset=%" PRIu32 ")", type_str, LOG_STR_ARG(item->get_source()), + name_cstr ? name_cstr : "(null)", type_str, delay, + static_cast(item->get_next_execution() - now)); + } +} +#endif /* ESPHOME_DEBUG_SCHEDULER */ + } // namespace esphome diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index f6ec07294d..fd16840240 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -266,6 +266,12 @@ class Scheduler { // Helper to perform full cleanup when too many items are cancelled void full_cleanup_removed_items_(); +#ifdef ESPHOME_DEBUG_SCHEDULER + // Helper for debug logging in set_timer_common_ - extracted to reduce code size + void debug_log_timer_(const SchedulerItem *item, bool is_static_string, const char *name_cstr, + SchedulerItem::Type type, uint32_t delay, uint64_t now); +#endif /* ESPHOME_DEBUG_SCHEDULER */ + #ifndef ESPHOME_THREAD_SINGLE // Helper to process defer queue - inline for performance in hot path inline void process_defer_queue_(uint32_t &now) { @@ -367,6 +373,24 @@ class Scheduler { #endif } + // Helper to set item removal flag (platform-specific) + // For ESPHOME_THREAD_MULTI_NO_ATOMICS platforms, the caller must hold the scheduler lock before calling this + // function. Uses memory_order_release when setting to true (for cancellation synchronization), + // and memory_order_relaxed when setting to false (for initialization). + void set_item_removed_(SchedulerItem *item, bool removed) { +#ifdef ESPHOME_THREAD_MULTI_ATOMICS + // Multi-threaded with atomics: use atomic store with appropriate ordering + // Release ordering when setting to true ensures cancellation is visible to other threads + // Relaxed ordering when setting to false is sufficient for initialization + item->remove.store(removed, removed ? std::memory_order_release : std::memory_order_relaxed); +#else + // Single-threaded (ESPHOME_THREAD_SINGLE) or + // multi-threaded without atomics (ESPHOME_THREAD_MULTI_NO_ATOMICS): direct write + // For ESPHOME_THREAD_MULTI_NO_ATOMICS, caller MUST hold lock! + item->remove = removed; +#endif + } + // Helper to mark matching items in a container as removed // Returns the number of items marked for removal // IMPORTANT: Caller must hold the scheduler lock before calling this function. @@ -383,15 +407,7 @@ class Scheduler { continue; if (this->matches_item_(item, component, name_cstr, type, match_retry)) { // Mark item for removal (platform-specific) -#ifdef ESPHOME_THREAD_MULTI_ATOMICS - // Multi-threaded with atomics: use atomic store - item->remove.store(true, std::memory_order_release); -#else - // Single-threaded (ESPHOME_THREAD_SINGLE) or - // multi-threaded without atomics (ESPHOME_THREAD_MULTI_NO_ATOMICS): direct write - // For ESPHOME_THREAD_MULTI_NO_ATOMICS, caller MUST hold lock! - item->remove = true; -#endif + this->set_item_removed_(item.get(), true); count++; } } From d36ef050a90d09a87e22b2cc5866b52007e9c875 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 12:15:50 -0600 Subject: [PATCH 0104/1145] [template] Mark all component classes as final (#11733) --- .../alarm_control_panel/template_alarm_control_panel.h | 2 +- .../template/binary_sensor/template_binary_sensor.h | 2 +- esphome/components/template/button/template_button.h | 2 +- esphome/components/template/cover/template_cover.h | 2 +- esphome/components/template/datetime/template_date.h | 2 +- esphome/components/template/datetime/template_datetime.h | 2 +- esphome/components/template/datetime/template_time.h | 2 +- esphome/components/template/event/template_event.h | 2 +- esphome/components/template/fan/template_fan.h | 2 +- esphome/components/template/lock/template_lock.h | 2 +- esphome/components/template/number/template_number.h | 2 +- esphome/components/template/output/template_output.h | 4 ++-- esphome/components/template/select/template_select.h | 2 +- esphome/components/template/sensor/template_sensor.h | 2 +- esphome/components/template/switch/template_switch.h | 2 +- esphome/components/template/text/template_text.h | 2 +- .../components/template/text_sensor/template_text_sensor.h | 2 +- esphome/components/template/valve/template_valve.h | 2 +- 18 files changed, 19 insertions(+), 19 deletions(-) diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h index 40a79004da..202dc7c13f 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h @@ -49,7 +49,7 @@ struct SensorInfo { uint8_t store_index; }; -class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, public Component { +class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControlPanel, public Component { public: TemplateAlarmControlPanel(); void dump_config() override; diff --git a/esphome/components/template/binary_sensor/template_binary_sensor.h b/esphome/components/template/binary_sensor/template_binary_sensor.h index bc591391b9..0af709b097 100644 --- a/esphome/components/template/binary_sensor/template_binary_sensor.h +++ b/esphome/components/template/binary_sensor/template_binary_sensor.h @@ -7,7 +7,7 @@ namespace esphome { namespace template_ { -class TemplateBinarySensor : public Component, public binary_sensor::BinarySensor { +class TemplateBinarySensor final : public Component, public binary_sensor::BinarySensor { public: template void set_template(F &&f) { this->f_.set(std::forward(f)); } diff --git a/esphome/components/template/button/template_button.h b/esphome/components/template/button/template_button.h index 68e976f64b..5bda82c58f 100644 --- a/esphome/components/template/button/template_button.h +++ b/esphome/components/template/button/template_button.h @@ -5,7 +5,7 @@ namespace esphome { namespace template_ { -class TemplateButton : public button::Button { +class TemplateButton final : public button::Button { public: // Implements the abstract `press_action` but the `on_press` trigger already handles the press. void press_action() override{}; diff --git a/esphome/components/template/cover/template_cover.h b/esphome/components/template/cover/template_cover.h index faff69f867..125c67bb86 100644 --- a/esphome/components/template/cover/template_cover.h +++ b/esphome/components/template/cover/template_cover.h @@ -14,7 +14,7 @@ enum TemplateCoverRestoreMode { COVER_RESTORE_AND_CALL, }; -class TemplateCover : public cover::Cover, public Component { +class TemplateCover final : public cover::Cover, public Component { public: TemplateCover(); diff --git a/esphome/components/template/datetime/template_date.h b/esphome/components/template/datetime/template_date.h index 7fed704d0e..fe64b0ba14 100644 --- a/esphome/components/template/datetime/template_date.h +++ b/esphome/components/template/datetime/template_date.h @@ -14,7 +14,7 @@ namespace esphome { namespace template_ { -class TemplateDate : public datetime::DateEntity, public PollingComponent { +class TemplateDate final : public datetime::DateEntity, public PollingComponent { public: template void set_template(F &&f) { this->f_.set(std::forward(f)); } diff --git a/esphome/components/template/datetime/template_datetime.h b/esphome/components/template/datetime/template_datetime.h index ec45bf0326..c44bd85265 100644 --- a/esphome/components/template/datetime/template_datetime.h +++ b/esphome/components/template/datetime/template_datetime.h @@ -14,7 +14,7 @@ namespace esphome { namespace template_ { -class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponent { +class TemplateDateTime final : public datetime::DateTimeEntity, public PollingComponent { public: template void set_template(F &&f) { this->f_.set(std::forward(f)); } diff --git a/esphome/components/template/datetime/template_time.h b/esphome/components/template/datetime/template_time.h index ea7474c0ba..0c95330d27 100644 --- a/esphome/components/template/datetime/template_time.h +++ b/esphome/components/template/datetime/template_time.h @@ -14,7 +14,7 @@ namespace esphome { namespace template_ { -class TemplateTime : public datetime::TimeEntity, public PollingComponent { +class TemplateTime final : public datetime::TimeEntity, public PollingComponent { public: template void set_template(F &&f) { this->f_.set(std::forward(f)); } diff --git a/esphome/components/template/event/template_event.h b/esphome/components/template/event/template_event.h index 251ae9299b..5467a64141 100644 --- a/esphome/components/template/event/template_event.h +++ b/esphome/components/template/event/template_event.h @@ -6,7 +6,7 @@ namespace esphome { namespace template_ { -class TemplateEvent : public Component, public event::Event {}; +class TemplateEvent final : public Component, public event::Event {}; } // namespace template_ } // namespace esphome diff --git a/esphome/components/template/fan/template_fan.h b/esphome/components/template/fan/template_fan.h index b09352f4d4..052b385b93 100644 --- a/esphome/components/template/fan/template_fan.h +++ b/esphome/components/template/fan/template_fan.h @@ -6,7 +6,7 @@ namespace esphome { namespace template_ { -class TemplateFan : public Component, public fan::Fan { +class TemplateFan final : public Component, public fan::Fan { public: TemplateFan() {} void setup() override; diff --git a/esphome/components/template/lock/template_lock.h b/esphome/components/template/lock/template_lock.h index 14fca4635e..ac10794e4d 100644 --- a/esphome/components/template/lock/template_lock.h +++ b/esphome/components/template/lock/template_lock.h @@ -8,7 +8,7 @@ namespace esphome { namespace template_ { -class TemplateLock : public lock::Lock, public Component { +class TemplateLock final : public lock::Lock, public Component { public: TemplateLock(); diff --git a/esphome/components/template/number/template_number.h b/esphome/components/template/number/template_number.h index a9307e9246..876ec96b3b 100644 --- a/esphome/components/template/number/template_number.h +++ b/esphome/components/template/number/template_number.h @@ -9,7 +9,7 @@ namespace esphome { namespace template_ { -class TemplateNumber : public number::Number, public PollingComponent { +class TemplateNumber final : public number::Number, public PollingComponent { public: template void set_template(F &&f) { this->f_.set(std::forward(f)); } diff --git a/esphome/components/template/output/template_output.h b/esphome/components/template/output/template_output.h index 90de801a5c..9ecfc446b9 100644 --- a/esphome/components/template/output/template_output.h +++ b/esphome/components/template/output/template_output.h @@ -7,7 +7,7 @@ namespace esphome { namespace template_ { -class TemplateBinaryOutput : public output::BinaryOutput { +class TemplateBinaryOutput final : public output::BinaryOutput { public: Trigger *get_trigger() const { return trigger_; } @@ -17,7 +17,7 @@ class TemplateBinaryOutput : public output::BinaryOutput { Trigger *trigger_ = new Trigger(); }; -class TemplateFloatOutput : public output::FloatOutput { +class TemplateFloatOutput final : public output::FloatOutput { public: Trigger *get_trigger() const { return trigger_; } diff --git a/esphome/components/template/select/template_select.h b/esphome/components/template/select/template_select.h index 2dad059ade..cb5b546976 100644 --- a/esphome/components/template/select/template_select.h +++ b/esphome/components/template/select/template_select.h @@ -9,7 +9,7 @@ namespace esphome { namespace template_ { -class TemplateSelect : public select::Select, public PollingComponent { +class TemplateSelect final : public select::Select, public PollingComponent { public: template void set_template(F &&f) { this->f_.set(std::forward(f)); } diff --git a/esphome/components/template/sensor/template_sensor.h b/esphome/components/template/sensor/template_sensor.h index 793d754a0f..3ca965dde3 100644 --- a/esphome/components/template/sensor/template_sensor.h +++ b/esphome/components/template/sensor/template_sensor.h @@ -7,7 +7,7 @@ namespace esphome { namespace template_ { -class TemplateSensor : public sensor::Sensor, public PollingComponent { +class TemplateSensor final : public sensor::Sensor, public PollingComponent { public: template void set_template(F &&f) { this->f_.set(std::forward(f)); } diff --git a/esphome/components/template/switch/template_switch.h b/esphome/components/template/switch/template_switch.h index 18a374df35..35c18af448 100644 --- a/esphome/components/template/switch/template_switch.h +++ b/esphome/components/template/switch/template_switch.h @@ -8,7 +8,7 @@ namespace esphome { namespace template_ { -class TemplateSwitch : public switch_::Switch, public Component { +class TemplateSwitch final : public switch_::Switch, public Component { public: TemplateSwitch(); diff --git a/esphome/components/template/text/template_text.h b/esphome/components/template/text/template_text.h index c12021f80e..1a0a66ed5b 100644 --- a/esphome/components/template/text/template_text.h +++ b/esphome/components/template/text/template_text.h @@ -60,7 +60,7 @@ template class TextSaver : public TemplateTextSaverBase { } }; -class TemplateText : public text::Text, public PollingComponent { +class TemplateText final : public text::Text, public PollingComponent { public: template void set_template(F &&f) { this->f_.set(std::forward(f)); } diff --git a/esphome/components/template/text_sensor/template_text_sensor.h b/esphome/components/template/text_sensor/template_text_sensor.h index 0d01c72023..da5c518c7f 100644 --- a/esphome/components/template/text_sensor/template_text_sensor.h +++ b/esphome/components/template/text_sensor/template_text_sensor.h @@ -8,7 +8,7 @@ namespace esphome { namespace template_ { -class TemplateTextSensor : public text_sensor::TextSensor, public PollingComponent { +class TemplateTextSensor final : public text_sensor::TextSensor, public PollingComponent { public: template void set_template(F &&f) { this->f_.set(std::forward(f)); } diff --git a/esphome/components/template/valve/template_valve.h b/esphome/components/template/valve/template_valve.h index d6235f8e5c..c452648193 100644 --- a/esphome/components/template/valve/template_valve.h +++ b/esphome/components/template/valve/template_valve.h @@ -14,7 +14,7 @@ enum TemplateValveRestoreMode { VALVE_RESTORE_AND_CALL, }; -class TemplateValve : public valve::Valve, public Component { +class TemplateValve final : public valve::Valve, public Component { public: TemplateValve(); From b08419fa473533e28a7cd86f98b2b3f98f1b7f27 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 12:30:45 -0600 Subject: [PATCH 0105/1145] [mqtt] Use StringRef to avoid string copies in discovery (#11731) --- .../mqtt/mqtt_alarm_control_panel.cpp | 2 +- .../components/mqtt/mqtt_binary_sensor.cpp | 9 ++++--- esphome/components/mqtt/mqtt_button.cpp | 7 ++--- esphome/components/mqtt/mqtt_component.cpp | 26 +++++++++++-------- esphome/components/mqtt/mqtt_component.h | 8 +++--- esphome/components/mqtt/mqtt_cover.cpp | 9 ++++--- esphome/components/mqtt/mqtt_event.cpp | 8 ++++-- esphome/components/mqtt/mqtt_fan.cpp | 14 +++++----- esphome/components/mqtt/mqtt_lock.cpp | 2 +- esphome/components/mqtt/mqtt_number.cpp | 14 +++++++--- esphome/components/mqtt/mqtt_sensor.cpp | 14 ++++++---- esphome/components/mqtt/mqtt_switch.cpp | 2 +- esphome/components/mqtt/mqtt_text_sensor.cpp | 8 +++--- esphome/components/mqtt/mqtt_update.cpp | 2 +- esphome/components/mqtt/mqtt_valve.cpp | 8 +++--- 15 files changed, 81 insertions(+), 52 deletions(-) diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index 94460c31a7..dd3df5f8aa 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -36,7 +36,7 @@ void MQTTAlarmControlPanelComponent::setup() { } else if (strcasecmp(payload.c_str(), "TRIGGERED") == 0) { call.triggered(); } else { - ESP_LOGW(TAG, "'%s': Received unknown command payload %s", this->friendly_name().c_str(), payload.c_str()); + ESP_LOGW(TAG, "'%s': Received unknown command payload %s", this->friendly_name_().c_str(), payload.c_str()); } call.perform(); }); diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 2ce4928574..479cee205a 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -30,9 +30,12 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor } void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - if (!this->binary_sensor_->get_device_class().empty()) - root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class(); + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + const auto device_class = this->binary_sensor_->get_device_class_ref(); + if (!device_class.empty()) { + root[MQTT_DEVICE_CLASS] = device_class; + } + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) if (this->binary_sensor_->is_status_binary_sensor()) root[MQTT_PAYLOAD_ON] = mqtt::global_mqtt_client->get_availability().payload_available; if (this->binary_sensor_->is_status_binary_sensor()) diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index b3435edf38..f8eb0eab2d 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -20,7 +20,7 @@ void MQTTButtonComponent::setup() { if (payload == "PRESS") { this->button_->press(); } else { - ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str()); + ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str()); this->status_momentary_warning("state", 5000); } }); @@ -33,8 +33,9 @@ void MQTTButtonComponent::dump_config() { void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson config.state_topic = false; - if (!this->button_->get_device_class().empty()) { - root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); + const auto device_class = this->button_->get_device_class_ref(); + if (!device_class.empty()) { + root[MQTT_DEVICE_CLASS] = device_class; } // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index eb6114008a..1cd818964e 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -64,11 +64,11 @@ bool MQTTComponent::send_discovery_() { const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info(); if (discovery_info.clean) { - ESP_LOGV(TAG, "'%s': Cleaning discovery", this->friendly_name().c_str()); + ESP_LOGV(TAG, "'%s': Cleaning discovery", this->friendly_name_().c_str()); return global_mqtt_client->publish(this->get_discovery_topic_(discovery_info), "", 0, this->qos_, true); } - ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name().c_str()); + ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name_().c_str()); // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return global_mqtt_client->publish_json( @@ -85,12 +85,16 @@ bool MQTTComponent::send_discovery_() { } // Fields from EntityBase - root[MQTT_NAME] = this->get_entity()->has_own_name() ? this->friendly_name() : ""; + root[MQTT_NAME] = this->get_entity()->has_own_name() ? this->friendly_name_() : ""; - if (this->is_disabled_by_default()) + if (this->is_disabled_by_default_()) root[MQTT_ENABLED_BY_DEFAULT] = false; - if (!this->get_icon().empty()) - root[MQTT_ICON] = this->get_icon(); + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + const auto icon_ref = this->get_icon_ref_(); + if (!icon_ref.empty()) { + root[MQTT_ICON] = icon_ref; + } + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) const auto entity_category = this->get_entity()->get_entity_category(); switch (entity_category) { @@ -122,7 +126,7 @@ bool MQTTComponent::send_discovery_() { const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info(); if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) { char friendly_name_hash[9]; - sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name())); + sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name_())); friendly_name_hash[8] = 0; // ensure the hash-string ends with null root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash; } else { @@ -184,7 +188,7 @@ bool MQTTComponent::is_discovery_enabled() const { } std::string MQTTComponent::get_default_object_id_() const { - return str_sanitize(str_snake_case(this->friendly_name())); + return str_sanitize(str_snake_case(this->friendly_name_())); } void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) { @@ -268,9 +272,9 @@ void MQTTComponent::schedule_resend_state() { this->resend_state_ = true; } bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connected(); } // Pull these properties from EntityBase if not overridden -std::string MQTTComponent::friendly_name() const { return this->get_entity()->get_name(); } -std::string MQTTComponent::get_icon() const { return this->get_entity()->get_icon(); } -bool MQTTComponent::is_disabled_by_default() const { return this->get_entity()->is_disabled_by_default(); } +std::string MQTTComponent::friendly_name_() const { return this->get_entity()->get_name(); } +StringRef MQTTComponent::get_icon_ref_() const { return this->get_entity()->get_icon_ref(); } +bool MQTTComponent::is_disabled_by_default_() const { return this->get_entity()->is_disabled_by_default(); } bool MQTTComponent::is_internal() { if (this->has_custom_state_topic_) { // If the custom state_topic is null, return true as it is internal and should not publish diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 851fdd842c..2f8dfcf64e 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -165,13 +165,13 @@ class MQTTComponent : public Component { virtual const EntityBase *get_entity() const = 0; /// Get the friendly name of this MQTT component. - virtual std::string friendly_name() const; + std::string friendly_name_() const; - /// Get the icon field of this component - virtual std::string get_icon() const; + /// Get the icon field of this component as StringRef + StringRef get_icon_ref_() const; /// Get whether the underlying Entity is disabled by default - virtual bool is_disabled_by_default() const; + bool is_disabled_by_default_() const; /// Get the MQTT topic that new states will be shared to. std::string get_state_topic_() const; diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 6fb61ee469..b63aa66d29 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -67,9 +67,12 @@ void MQTTCoverComponent::dump_config() { } } void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - if (!this->cover_->get_device_class().empty()) - root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class(); + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + const auto device_class = this->cover_->get_device_class_ref(); + if (!device_class.empty()) { + root[MQTT_DEVICE_CLASS] = device_class; + } + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) auto traits = this->cover_->get_traits(); if (traits.get_is_assumed_state()) { diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp index f972d545c6..e206335446 100644 --- a/esphome/components/mqtt/mqtt_event.cpp +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -21,8 +21,12 @@ void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConf for (const auto &event_type : this->event_->get_event_types()) event_types.add(event_type); - if (!this->event_->get_device_class().empty()) - root[MQTT_DEVICE_CLASS] = this->event_->get_device_class(); + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + const auto device_class = this->event_->get_device_class_ref(); + if (!device_class.empty()) { + root[MQTT_DEVICE_CLASS] = device_class; + } + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) config.command_topic = false; } diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 70e1ae3b4a..2aefc3a4db 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -24,15 +24,15 @@ void MQTTFanComponent::setup() { auto val = parse_on_off(payload.c_str()); switch (val) { case PARSE_ON: - ESP_LOGD(TAG, "'%s' Turning Fan ON.", this->friendly_name().c_str()); + ESP_LOGD(TAG, "'%s' Turning Fan ON.", this->friendly_name_().c_str()); this->state_->turn_on().perform(); break; case PARSE_OFF: - ESP_LOGD(TAG, "'%s' Turning Fan OFF.", this->friendly_name().c_str()); + ESP_LOGD(TAG, "'%s' Turning Fan OFF.", this->friendly_name_().c_str()); this->state_->turn_off().perform(); break; case PARSE_TOGGLE: - ESP_LOGD(TAG, "'%s' Toggling Fan.", this->friendly_name().c_str()); + ESP_LOGD(TAG, "'%s' Toggling Fan.", this->friendly_name_().c_str()); this->state_->toggle().perform(); break; case PARSE_NONE: @@ -48,11 +48,11 @@ void MQTTFanComponent::setup() { auto val = parse_on_off(payload.c_str(), "forward", "reverse"); switch (val) { case PARSE_ON: - ESP_LOGD(TAG, "'%s': Setting direction FORWARD", this->friendly_name().c_str()); + ESP_LOGD(TAG, "'%s': Setting direction FORWARD", this->friendly_name_().c_str()); this->state_->make_call().set_direction(fan::FanDirection::FORWARD).perform(); break; case PARSE_OFF: - ESP_LOGD(TAG, "'%s': Setting direction REVERSE", this->friendly_name().c_str()); + ESP_LOGD(TAG, "'%s': Setting direction REVERSE", this->friendly_name_().c_str()); this->state_->make_call().set_direction(fan::FanDirection::REVERSE).perform(); break; case PARSE_TOGGLE: @@ -75,11 +75,11 @@ void MQTTFanComponent::setup() { auto val = parse_on_off(payload.c_str(), "oscillate_on", "oscillate_off"); switch (val) { case PARSE_ON: - ESP_LOGD(TAG, "'%s': Setting oscillating ON", this->friendly_name().c_str()); + ESP_LOGD(TAG, "'%s': Setting oscillating ON", this->friendly_name_().c_str()); this->state_->make_call().set_oscillating(true).perform(); break; case PARSE_OFF: - ESP_LOGD(TAG, "'%s': Setting oscillating OFF", this->friendly_name().c_str()); + ESP_LOGD(TAG, "'%s': Setting oscillating OFF", this->friendly_name_().c_str()); this->state_->make_call().set_oscillating(false).perform(); break; case PARSE_TOGGLE: diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index 0412624983..0e15377ba4 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -24,7 +24,7 @@ void MQTTLockComponent::setup() { } else if (strcasecmp(payload.c_str(), "OPEN") == 0) { this->lock_->open(); } else { - ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str()); + ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str()); this->status_momentary_warning("state", 5000); } }); diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index a44632ff30..f419eac130 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -44,8 +44,11 @@ void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon root[MQTT_MIN] = traits.get_min_value(); root[MQTT_MAX] = traits.get_max_value(); root[MQTT_STEP] = traits.get_step(); - if (!this->number_->traits.get_unit_of_measurement().empty()) - root[MQTT_UNIT_OF_MEASUREMENT] = this->number_->traits.get_unit_of_measurement(); + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + const auto unit_of_measurement = this->number_->traits.get_unit_of_measurement_ref(); + if (!unit_of_measurement.empty()) { + root[MQTT_UNIT_OF_MEASUREMENT] = unit_of_measurement; + } switch (this->number_->traits.get_mode()) { case NUMBER_MODE_AUTO: break; @@ -56,8 +59,11 @@ void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon root[MQTT_MODE] = "slider"; break; } - if (!this->number_->traits.get_device_class().empty()) - root[MQTT_DEVICE_CLASS] = this->number_->traits.get_device_class(); + const auto device_class = this->number_->traits.get_device_class_ref(); + if (!device_class.empty()) { + root[MQTT_DEVICE_CLASS] = device_class; + } + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) config.command_topic = true; } diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 9e61f6ef3b..010ac3013e 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -44,13 +44,17 @@ void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - if (!this->sensor_->get_device_class().empty()) { - root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + const auto device_class = this->sensor_->get_device_class_ref(); + if (!device_class.empty()) { + root[MQTT_DEVICE_CLASS] = device_class; } - if (!this->sensor_->get_unit_of_measurement().empty()) - root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement(); + const auto unit_of_measurement = this->sensor_->get_unit_of_measurement_ref(); + if (!unit_of_measurement.empty()) { + root[MQTT_UNIT_OF_MEASUREMENT] = unit_of_measurement; + } + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) if (this->get_expire_after() > 0) root[MQTT_EXPIRE_AFTER] = this->get_expire_after() / 1000; diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index 8b1323bdb2..b3a35420b9 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -29,7 +29,7 @@ void MQTTSwitchComponent::setup() { break; case PARSE_NONE: default: - ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str()); + ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str()); this->status_momentary_warning("state", 5000); break; } diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index 42260ed2a8..e6e7cf04e8 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -15,10 +15,12 @@ using namespace esphome::text_sensor; MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {} void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - if (!this->sensor_->get_device_class().empty()) { - root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + const auto device_class = this->sensor_->get_device_class_ref(); + if (!device_class.empty()) { + root[MQTT_DEVICE_CLASS] = device_class; } + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) config.command_topic = false; } void MQTTTextSensor::setup() { diff --git a/esphome/components/mqtt/mqtt_update.cpp b/esphome/components/mqtt/mqtt_update.cpp index 5d4807c7f3..20f3a69a9e 100644 --- a/esphome/components/mqtt/mqtt_update.cpp +++ b/esphome/components/mqtt/mqtt_update.cpp @@ -20,7 +20,7 @@ void MQTTUpdateComponent::setup() { if (payload == "INSTALL") { this->update_->perform(); } else { - ESP_LOGW(TAG, "'%s': Received unknown update payload: %s", this->friendly_name().c_str(), payload.c_str()); + ESP_LOGW(TAG, "'%s': Received unknown update payload: %s", this->friendly_name_().c_str(), payload.c_str()); this->status_momentary_warning("state", 5000); } }); diff --git a/esphome/components/mqtt/mqtt_valve.cpp b/esphome/components/mqtt/mqtt_valve.cpp index 551398cf42..ae60670748 100644 --- a/esphome/components/mqtt/mqtt_valve.cpp +++ b/esphome/components/mqtt/mqtt_valve.cpp @@ -49,10 +49,12 @@ void MQTTValveComponent::dump_config() { } } void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - if (!this->valve_->get_device_class().empty()) { - root[MQTT_DEVICE_CLASS] = this->valve_->get_device_class(); + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + const auto device_class = this->valve_->get_device_class_ref(); + if (!device_class.empty()) { + root[MQTT_DEVICE_CLASS] = device_class; } + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) auto traits = this->valve_->get_traits(); if (traits.get_is_assumed_state()) { From be006ecaddad5df7c2be5cfa3ffa2499dda5c203 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 12:31:19 -0600 Subject: [PATCH 0106/1145] [mdns] Eliminate redundant hostname copy to save heap memory (#11734) --- esphome/components/mdns/mdns_component.cpp | 4 +--- esphome/components/mdns/mdns_component.h | 1 - esphome/components/mdns/mdns_esp32.cpp | 6 ++++-- esphome/components/mdns/mdns_esp8266.cpp | 3 ++- esphome/components/mdns/mdns_libretiny.cpp | 3 ++- esphome/components/mdns/mdns_rp2040.cpp | 3 ++- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index d476136554..2c3150ff5d 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -37,8 +37,6 @@ MDNS_STATIC_CONST_CHAR(SERVICE_TCP, "_tcp"); MDNS_STATIC_CONST_CHAR(VALUE_VERSION, ESPHOME_VERSION); void MDNSComponent::compile_records_(StaticVector &services) { - this->hostname_ = App.get_name(); - // IMPORTANT: The #ifdef blocks below must match COMPONENTS_WITH_MDNS_SERVICES // in mdns/__init__.py. If you add a new service here, update both locations. @@ -179,7 +177,7 @@ void MDNSComponent::dump_config() { ESP_LOGCONFIG(TAG, "mDNS:\n" " Hostname: %s", - this->hostname_.c_str()); + App.get_name().c_str()); #ifdef USE_MDNS_STORE_SERVICES ESP_LOGV(TAG, " Services:"); for (const auto &service : this->services_) { diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index 35371fd739..f4237d5a69 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -76,7 +76,6 @@ class MDNSComponent : public Component { #ifdef USE_MDNS_STORE_SERVICES StaticVector services_{}; #endif - std::string hostname_; void compile_records_(StaticVector &services); }; diff --git a/esphome/components/mdns/mdns_esp32.cpp b/esphome/components/mdns/mdns_esp32.cpp index c02bfcbadb..ecdc926cc9 100644 --- a/esphome/components/mdns/mdns_esp32.cpp +++ b/esphome/components/mdns/mdns_esp32.cpp @@ -2,6 +2,7 @@ #if defined(USE_ESP32) && defined(USE_MDNS) #include +#include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" #include "mdns_component.h" @@ -27,8 +28,9 @@ void MDNSComponent::setup() { return; } - mdns_hostname_set(this->hostname_.c_str()); - mdns_instance_name_set(this->hostname_.c_str()); + const char *hostname = App.get_name().c_str(); + mdns_hostname_set(hostname); + mdns_instance_name_set(hostname); for (const auto &service : services) { auto txt_records = std::make_unique(service.txt_records.size()); diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index 25a3defa7b..9bbb406070 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -4,6 +4,7 @@ #include #include "esphome/components/network/ip_address.h" #include "esphome/components/network/util.h" +#include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" #include "mdns_component.h" @@ -20,7 +21,7 @@ void MDNSComponent::setup() { this->compile_records_(services); #endif - MDNS.begin(this->hostname_.c_str()); + MDNS.begin(App.get_name().c_str()); for (const auto &service : services) { // Strip the leading underscore from the proto and service_type. While it is diff --git a/esphome/components/mdns/mdns_libretiny.cpp b/esphome/components/mdns/mdns_libretiny.cpp index a3e317a2bf..fb2088f719 100644 --- a/esphome/components/mdns/mdns_libretiny.cpp +++ b/esphome/components/mdns/mdns_libretiny.cpp @@ -3,6 +3,7 @@ #include "esphome/components/network/ip_address.h" #include "esphome/components/network/util.h" +#include "esphome/core/application.h" #include "esphome/core/log.h" #include "mdns_component.h" @@ -20,7 +21,7 @@ void MDNSComponent::setup() { this->compile_records_(services); #endif - MDNS.begin(this->hostname_.c_str()); + MDNS.begin(App.get_name().c_str()); for (const auto &service : services) { // Strip the leading underscore from the proto and service_type. While it is diff --git a/esphome/components/mdns/mdns_rp2040.cpp b/esphome/components/mdns/mdns_rp2040.cpp index 791fa3934d..a9f5349f14 100644 --- a/esphome/components/mdns/mdns_rp2040.cpp +++ b/esphome/components/mdns/mdns_rp2040.cpp @@ -3,6 +3,7 @@ #include "esphome/components/network/ip_address.h" #include "esphome/components/network/util.h" +#include "esphome/core/application.h" #include "esphome/core/log.h" #include "mdns_component.h" @@ -20,7 +21,7 @@ void MDNSComponent::setup() { this->compile_records_(services); #endif - MDNS.begin(this->hostname_.c_str()); + MDNS.begin(App.get_name().c_str()); for (const auto &service : services) { // Strip the leading underscore from the proto and service_type. While it is From 00c0854323a79663a27243f05db911be9ad956e1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 12:50:35 -0600 Subject: [PATCH 0107/1145] [core] Deprecate get_icon(), get_device_class(), get_unit_of_measurement() and fix remaining non-MQTT usages (#11732) --- esphome/components/graph/graph.cpp | 4 ++-- esphome/components/prometheus/prometheus_handler.cpp | 2 +- esphome/components/sprinkler/sprinkler.cpp | 4 ++-- esphome/core/entity_base.h | 12 +++++++++--- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index ac6ace96ee..88bb306408 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -235,7 +235,7 @@ void GraphLegend::init(Graph *g) { std::string valstr = value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals()); if (this->units_) { - valstr += trace->sensor_->get_unit_of_measurement(); + valstr += trace->sensor_->get_unit_of_measurement_ref(); } this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh); if (fw > valw) @@ -371,7 +371,7 @@ void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_of std::string valstr = value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals()); if (legend_->units_) { - valstr += trace->sensor_->get_unit_of_measurement(); + valstr += trace->sensor_->get_unit_of_measurement_ref(); } buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr.c_str()); ESP_LOGV(TAG, " value: %s", valstr.c_str()); diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 6e7ed6f79f..5cfcacf0cb 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -158,7 +158,7 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor stream->print(ESPHOME_F("\",name=\"")); stream->print(relabel_name_(obj).c_str()); stream->print(ESPHOME_F("\",unit=\"")); - stream->print(obj->get_unit_of_measurement().c_str()); + stream->print(obj->get_unit_of_measurement_ref().c_str()); stream->print(ESPHOME_F("\"} ")); stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str()); stream->print(ESPHOME_F("\n")); diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index 7676e17468..8edb240a41 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -650,7 +650,7 @@ void Sprinkler::set_valve_run_duration(const optional valve_number, cons return; } auto call = this->valve_[valve_number.value()].run_duration_number->make_call(); - if (this->valve_[valve_number.value()].run_duration_number->traits.get_unit_of_measurement() == MIN_STR) { + if (this->valve_[valve_number.value()].run_duration_number->traits.get_unit_of_measurement_ref() == MIN_STR) { call.set_value(run_duration.value() / 60.0); } else { call.set_value(run_duration.value()); @@ -732,7 +732,7 @@ uint32_t Sprinkler::valve_run_duration(const size_t valve_number) { return 0; } if (this->valve_[valve_number].run_duration_number != nullptr) { - if (this->valve_[valve_number].run_duration_number->traits.get_unit_of_measurement() == MIN_STR) { + if (this->valve_[valve_number].run_duration_number->traits.get_unit_of_measurement_ref() == MIN_STR) { return static_cast(roundf(this->valve_[valve_number].run_duration_number->state * 60)); } else { return static_cast(roundf(this->valve_[valve_number].run_duration_number->state)); diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 80cd6b8e77..6e5362464f 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -61,7 +61,9 @@ class EntityBase { } // Get/set this entity's icon - std::string get_icon() const; + [[deprecated("Use get_icon_ref() instead for better performance (avoids string copy). Will stop working in ESPHome " + "2026.5.0")]] std::string + get_icon() const; void set_icon(const char *icon); StringRef get_icon_ref() const { static constexpr auto EMPTY_STRING = StringRef::from_lit(""); @@ -158,7 +160,9 @@ class EntityBase { class EntityBase_DeviceClass { // NOLINT(readability-identifier-naming) public: /// Get the device class, using the manual override if set. - std::string get_device_class(); + [[deprecated("Use get_device_class_ref() instead for better performance (avoids string copy). Will stop working in " + "ESPHome 2026.5.0")]] std::string + get_device_class(); /// Manually set the device class. void set_device_class(const char *device_class); /// Get the device class as StringRef @@ -174,7 +178,9 @@ class EntityBase_DeviceClass { // NOLINT(readability-identifier-naming) class EntityBase_UnitOfMeasurement { // NOLINT(readability-identifier-naming) public: /// Get the unit of measurement, using the manual override if set. - std::string get_unit_of_measurement(); + [[deprecated("Use get_unit_of_measurement_ref() instead for better performance (avoids string copy). Will stop " + "working in ESPHome 2026.5.0")]] std::string + get_unit_of_measurement(); /// Manually set the unit of measurement. void set_unit_of_measurement(const char *unit_of_measurement); /// Get the unit of measurement as StringRef From aa5795c019c055e08807f9943633d3a8582582e4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 13:17:34 -0600 Subject: [PATCH 0108/1145] [tests] Fix ID collision between bl0940 and nau7802 component tests (#11739) --- tests/components/bl0940/common.yaml | 6 +++--- tests/components/nau7802/common.yaml | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/components/bl0940/common.yaml b/tests/components/bl0940/common.yaml index 0b73fd6d55..e476ba10c0 100644 --- a/tests/components/bl0940/common.yaml +++ b/tests/components/bl0940/common.yaml @@ -1,11 +1,11 @@ button: - platform: bl0940 - bl0940_id: test_id + bl0940_id: bl0940_test_id name: Cal Reset sensor: - platform: bl0940 - id: test_id + id: bl0940_test_id voltage: name: BL0940 Voltage current: @@ -22,7 +22,7 @@ sensor: number: - platform: bl0940 id: bl0940_number_id - bl0940_id: test_id + bl0940_id: bl0940_test_id current_calibration: name: Cal Current min_value: -5 diff --git a/tests/components/nau7802/common.yaml b/tests/components/nau7802/common.yaml index 5c52c33dad..5251910df9 100644 --- a/tests/components/nau7802/common.yaml +++ b/tests/components/nau7802/common.yaml @@ -1,13 +1,13 @@ sensor: - platform: nau7802 i2c_id: i2c_bus - id: test_id + id: nau7802_test_id name: weight gain: 32 ldo_voltage: "3.0v" samples_per_second: 10 on_value: then: - - nau7802.calibrate_external_offset: test_id - - nau7802.calibrate_internal_offset: test_id - - nau7802.calibrate_gain: test_id + - nau7802.calibrate_external_offset: nau7802_test_id + - nau7802.calibrate_internal_offset: nau7802_test_id + - nau7802.calibrate_gain: nau7802_test_id From ce5e6088638d6b5cdc2becc34196205d3e03b2dc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 14:32:45 -0600 Subject: [PATCH 0109/1145] [ci] Skip memory impact analysis for release and beta branches (#11740) --- script/determine-jobs.py | 15 +++++++ script/helpers.py | 45 ++++++++++++++++--- tests/script/test_determine_jobs.py | 70 +++++++++++++++++++++++++++++ tests/script/test_helpers.py | 7 +++ 4 files changed, 130 insertions(+), 7 deletions(-) diff --git a/script/determine-jobs.py b/script/determine-jobs.py index 6f908b7150..e9d17d8fe5 100755 --- a/script/determine-jobs.py +++ b/script/determine-jobs.py @@ -63,6 +63,7 @@ from helpers import ( get_components_from_integration_fixtures, get_components_with_dependencies, get_cpp_changed_components, + get_target_branch, git_ls_files, parse_test_filename, root_path, @@ -471,6 +472,20 @@ def detect_memory_impact_config( - platform: platform name for the merged build - use_merged_config: "true" (always use merged config) """ + # Skip memory impact analysis for release* or beta* branches + # These branches typically contain many merged changes from dev, and building + # all components at once would produce nonsensical memory impact results. + # Memory impact analysis is most useful for focused PRs targeting dev. + target_branch = get_target_branch() + if target_branch and ( + target_branch.startswith("release") or target_branch.startswith("beta") + ): + print( + f"Memory impact: Skipping analysis for target branch {target_branch} " + f"(would try to build all components at once, giving nonsensical results)", + file=sys.stderr, + ) + return {"should_run": "false"} # Get actually changed files (not dependencies) files = changed_files(branch) diff --git a/script/helpers.py b/script/helpers.py index 5b2fe6cd06..1039ef39ac 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -196,6 +196,20 @@ def splitlines_no_ends(string: str) -> list[str]: return [s.strip() for s in string.splitlines()] +@cache +def _get_github_event_data() -> dict | None: + """Read and parse GitHub event file (cached). + + Returns: + Parsed event data dictionary, or None if not available + """ + github_event_path = os.environ.get("GITHUB_EVENT_PATH") + if github_event_path and os.path.exists(github_event_path): + with open(github_event_path) as f: + return json.load(f) + return None + + def _get_pr_number_from_github_env() -> str | None: """Extract PR number from GitHub environment variables. @@ -208,13 +222,30 @@ def _get_pr_number_from_github_env() -> str | None: return github_ref.split("/pull/")[1].split("/")[0] # Fallback to GitHub event file - github_event_path = os.environ.get("GITHUB_EVENT_PATH") - if github_event_path and os.path.exists(github_event_path): - with open(github_event_path) as f: - event_data = json.load(f) - pr_data = event_data.get("pull_request", {}) - if pr_number := pr_data.get("number"): - return str(pr_number) + if event_data := _get_github_event_data(): + pr_data = event_data.get("pull_request", {}) + if pr_number := pr_data.get("number"): + return str(pr_number) + + return None + + +def get_target_branch() -> str | None: + """Get the target branch from GitHub environment variables. + + Returns: + Target branch name (e.g., "dev", "release", "beta"), or None if not in PR context + """ + # First try GITHUB_BASE_REF (set for pull_request events) + if base_ref := os.environ.get("GITHUB_BASE_REF"): + return base_ref + + # Fallback to GitHub event file + if event_data := _get_github_event_data(): + pr_data = event_data.get("pull_request", {}) + base_data = pr_data.get("base", {}) + if ref := base_data.get("ref"): + return ref return None diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py index e084e2e398..9f12d7ffcf 100644 --- a/tests/script/test_determine_jobs.py +++ b/tests/script/test_determine_jobs.py @@ -1240,3 +1240,73 @@ def test_detect_memory_impact_config_filters_incompatible_esp8266_on_esp32( ) assert result["use_merged_config"] == "true" + + +def test_detect_memory_impact_config_skips_release_branch(tmp_path: Path) -> None: + """Test that memory impact analysis is skipped for release* branches.""" + # Create test directory structure with components that have tests + tests_dir = tmp_path / "tests" / "components" + wifi_dir = tests_dir / "wifi" + wifi_dir.mkdir(parents=True) + (wifi_dir / "test.esp32-idf.yaml").write_text("test: wifi") + + with ( + patch.object(determine_jobs, "root_path", str(tmp_path)), + patch.object(helpers, "root_path", str(tmp_path)), + patch.object(determine_jobs, "changed_files") as mock_changed_files, + patch.object(determine_jobs, "get_target_branch", return_value="release"), + ): + mock_changed_files.return_value = ["esphome/components/wifi/wifi.cpp"] + determine_jobs._component_has_tests.cache_clear() + + result = determine_jobs.detect_memory_impact_config() + + # Memory impact should be skipped for release branch + assert result["should_run"] == "false" + + +def test_detect_memory_impact_config_skips_beta_branch(tmp_path: Path) -> None: + """Test that memory impact analysis is skipped for beta* branches.""" + # Create test directory structure with components that have tests + tests_dir = tmp_path / "tests" / "components" + wifi_dir = tests_dir / "wifi" + wifi_dir.mkdir(parents=True) + (wifi_dir / "test.esp32-idf.yaml").write_text("test: wifi") + + with ( + patch.object(determine_jobs, "root_path", str(tmp_path)), + patch.object(helpers, "root_path", str(tmp_path)), + patch.object(determine_jobs, "changed_files") as mock_changed_files, + patch.object(determine_jobs, "get_target_branch", return_value="beta"), + ): + mock_changed_files.return_value = ["esphome/components/wifi/wifi.cpp"] + determine_jobs._component_has_tests.cache_clear() + + result = determine_jobs.detect_memory_impact_config() + + # Memory impact should be skipped for beta branch + assert result["should_run"] == "false" + + +def test_detect_memory_impact_config_runs_for_dev_branch(tmp_path: Path) -> None: + """Test that memory impact analysis runs for dev branch.""" + # Create test directory structure with components that have tests + tests_dir = tmp_path / "tests" / "components" + wifi_dir = tests_dir / "wifi" + wifi_dir.mkdir(parents=True) + (wifi_dir / "test.esp32-idf.yaml").write_text("test: wifi") + + with ( + patch.object(determine_jobs, "root_path", str(tmp_path)), + patch.object(helpers, "root_path", str(tmp_path)), + patch.object(determine_jobs, "changed_files") as mock_changed_files, + patch.object(determine_jobs, "get_target_branch", return_value="dev"), + ): + mock_changed_files.return_value = ["esphome/components/wifi/wifi.cpp"] + determine_jobs._component_has_tests.cache_clear() + + result = determine_jobs.detect_memory_impact_config() + + # Memory impact should run for dev branch + assert result["should_run"] == "true" + assert result["components"] == ["wifi"] diff --git a/tests/script/test_helpers.py b/tests/script/test_helpers.py index 1bfffef51c..c51273f298 100644 --- a/tests/script/test_helpers.py +++ b/tests/script/test_helpers.py @@ -31,6 +31,13 @@ print_file_list = helpers.print_file_list get_all_dependencies = helpers.get_all_dependencies +@pytest.fixture(autouse=True) +def clear_helpers_cache() -> None: + """Clear cached functions before each test.""" + helpers._get_github_event_data.cache_clear() + helpers._get_changed_files_github_actions.cache_clear() + + @pytest.mark.parametrize( ("github_ref", "expected_pr_number"), [ From 20b6e0d5c239d949a89f61a528733772f5c3166b Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:37:38 +1000 Subject: [PATCH 0110/1145] [lvgl] Allow text substitution for NaN (#11712) --- esphome/components/lvgl/helpers.py | 39 ++++++++++++++++++++---- esphome/components/lvgl/lv_validation.py | 16 ++++++++-- esphome/components/lvgl/schemas.py | 3 +- tests/components/lvgl/lvgl-package.yaml | 6 ++++ 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/esphome/components/lvgl/helpers.py b/esphome/components/lvgl/helpers.py index 8d5b6354bb..c2bd58f71c 100644 --- a/esphome/components/lvgl/helpers.py +++ b/esphome/components/lvgl/helpers.py @@ -3,6 +3,8 @@ import re from esphome import config_validation as cv from esphome.const import CONF_ARGS, CONF_FORMAT +CONF_IF_NAN = "if_nan" + lv_uses = { "USER_DATA", "LOG", @@ -21,23 +23,48 @@ lv_fonts_used = set() esphome_fonts_used = set() lvgl_components_required = set() - -def validate_printf(value): - cfmt = r""" +# noqa +f_regex = re.compile( + r""" ( # start of capture group 1 % # literal "%" - (?:[-+0 #]{0,5}) # optional flags + [-+0 #]{0,5} # optional flags + (?:\d+|\*)? # width + (?:\.(?:\d+|\*))? # precision + (?:h|l|ll|w|I|I32|I64)? # size + f # type + ) + """, + flags=re.VERBOSE, +) +# noqa +c_regex = re.compile( + r""" + ( # start of capture group 1 + % # literal "%" + [-+0 #]{0,5} # optional flags (?:\d+|\*)? # width (?:\.(?:\d+|\*))? # precision (?:h|l|ll|w|I|I32|I64)? # size [cCdiouxXeEfgGaAnpsSZ] # type ) - """ # noqa - matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.VERBOSE) + """, + flags=re.VERBOSE, +) + + +def validate_printf(value): + format_string = value[CONF_FORMAT] + matches = c_regex.findall(format_string) if len(matches) != len(value[CONF_ARGS]): raise cv.Invalid( f"Found {len(matches)} printf-patterns ({', '.join(matches)}), but {len(value[CONF_ARGS])} args were given!" ) + + if value.get(CONF_IF_NAN) and len(f_regex.findall(format_string)) != 1: + raise cv.Invalid( + "Use of 'if_nan' requires a single valid printf-pattern of type %f" + ) return value diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index 9fe72128ce..045258555c 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -33,7 +33,13 @@ from .defines import ( call_lambda, literal, ) -from .helpers import add_lv_use, esphome_fonts_used, lv_fonts_used, requires_component +from .helpers import ( + CONF_IF_NAN, + add_lv_use, + esphome_fonts_used, + lv_fonts_used, + requires_component, +) from .types import lv_font_t, lv_gradient_t opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") @@ -412,7 +418,13 @@ class TextValidator(LValidator): str_args = [str(x) for x in value[CONF_ARGS]] arg_expr = cg.RawExpression(",".join(str_args)) format_str = cpp_string_escape(format_str) - return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()") + sprintf_str = f"str_sprintf({format_str}, {arg_expr}).c_str()" + if nanval := value.get(CONF_IF_NAN): + nanval = cpp_string_escape(nanval) + return literal( + f"(std::isfinite({arg_expr}) ? {sprintf_str} : {nanval})" + ) + return literal(sprintf_str) if time_format := value.get(CONF_TIME_FORMAT): source = value[CONF_TIME] if isinstance(source, Lambda): diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index dd248d0b94..0dcf420f24 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -20,7 +20,7 @@ from esphome.core.config import StartupTrigger from . import defines as df, lv_validation as lvalid from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR -from .helpers import requires_component, validate_printf +from .helpers import CONF_IF_NAN, requires_component, validate_printf from .layout import ( FLEX_OBJ_SCHEMA, GRID_CELL_SCHEMA, @@ -54,6 +54,7 @@ PRINTF_TEXT_SCHEMA = cv.All( { cv.Required(CONF_FORMAT): cv.string, cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_), + cv.Optional(CONF_IF_NAN): cv.string, }, ), validate_printf, diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 8ac9a60e2d..b122d10f04 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -726,6 +726,12 @@ lvgl: - logger.log: format: "Spinbox value is %f" args: [x] + - lvgl.label.update: + id: hello_label + text: + format: "value is %.1f now" + args: [x] + if_nan: "Value unknown" - button: styles: spin_button id: spin_down From bdfd88441a3666ce6f89773bb9bbfbc384eee4af Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 19:31:23 -0600 Subject: [PATCH 0111/1145] [ci] Skip memory impact analysis when more than 40 components changed (#11741) --- script/determine-jobs.py | 12 +++++ tests/script/test_determine_jobs.py | 77 ++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 17 deletions(-) diff --git a/script/determine-jobs.py b/script/determine-jobs.py index e9d17d8fe5..39a7571fbe 100755 --- a/script/determine-jobs.py +++ b/script/determine-jobs.py @@ -94,6 +94,7 @@ class Platform(StrEnum): # Memory impact analysis constants MEMORY_IMPACT_FALLBACK_COMPONENT = "api" # Representative component for core changes MEMORY_IMPACT_FALLBACK_PLATFORM = Platform.ESP32_IDF # Most representative platform +MEMORY_IMPACT_MAX_COMPONENTS = 40 # Max components before results become nonsensical # Platform-specific components that can only be built on their respective platforms # These components contain platform-specific code and cannot be cross-compiled @@ -555,6 +556,17 @@ def detect_memory_impact_config( if not components_with_tests: return {"should_run": "false"} + # Skip memory impact analysis if too many components changed + # Building 40+ components at once produces nonsensical memory impact results + # This typically happens with large refactorings or batch updates + if len(components_with_tests) > MEMORY_IMPACT_MAX_COMPONENTS: + print( + f"Memory impact: Skipping analysis for {len(components_with_tests)} components " + f"(limit is {MEMORY_IMPACT_MAX_COMPONENTS}, would give nonsensical results)", + file=sys.stderr, + ) + return {"should_run": "false"} + # Find common platforms supported by ALL components # This ensures we can build all components together in a merged config common_platforms = set(MEMORY_IMPACT_PLATFORM_PREFERENCE) diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py index 9f12d7ffcf..4894a5e28a 100644 --- a/tests/script/test_determine_jobs.py +++ b/tests/script/test_determine_jobs.py @@ -71,9 +71,10 @@ def mock_changed_files() -> Generator[Mock, None, None]: @pytest.fixture(autouse=True) -def clear_clang_tidy_cache() -> None: - """Clear the clang-tidy full scan cache before each test.""" +def clear_determine_jobs_caches() -> None: + """Clear all cached functions before each test.""" determine_jobs._is_clang_tidy_full_scan.cache_clear() + determine_jobs._component_has_tests.cache_clear() def test_main_all_tests_should_run( @@ -565,7 +566,6 @@ def test_main_filters_components_without_tests( patch.object(determine_jobs, "changed_files", return_value=[]), ): # Clear the cache since we're mocking root_path - determine_jobs._component_has_tests.cache_clear() determine_jobs.main() # Check output @@ -665,7 +665,6 @@ def test_main_detects_components_with_variant_tests( patch.object(determine_jobs, "changed_files", return_value=[]), ): # Clear the cache since we're mocking root_path - determine_jobs._component_has_tests.cache_clear() determine_jobs.main() # Check output @@ -714,7 +713,6 @@ def test_detect_memory_impact_config_with_common_platform(tmp_path: Path) -> Non "esphome/components/wifi/wifi.cpp", "esphome/components/api/api.cpp", ] - determine_jobs._component_has_tests.cache_clear() result = determine_jobs.detect_memory_impact_config() @@ -744,7 +742,6 @@ def test_detect_memory_impact_config_core_only_changes(tmp_path: Path) -> None: "esphome/core/application.cpp", "esphome/core/component.h", ] - determine_jobs._component_has_tests.cache_clear() result = determine_jobs.detect_memory_impact_config() @@ -775,7 +772,6 @@ def test_detect_memory_impact_config_core_python_only_changes(tmp_path: Path) -> "esphome/config.py", "esphome/core/config.py", ] - determine_jobs._component_has_tests.cache_clear() result = determine_jobs.detect_memory_impact_config() @@ -808,7 +804,6 @@ def test_detect_memory_impact_config_no_common_platform(tmp_path: Path) -> None: "esphome/components/wifi/wifi.cpp", "esphome/components/logger/logger.cpp", ] - determine_jobs._component_has_tests.cache_clear() result = determine_jobs.detect_memory_impact_config() @@ -830,7 +825,6 @@ def test_detect_memory_impact_config_no_changes(tmp_path: Path) -> None: patch.object(determine_jobs, "changed_files") as mock_changed_files, ): mock_changed_files.return_value = [] - determine_jobs._component_has_tests.cache_clear() result = determine_jobs.detect_memory_impact_config() @@ -855,7 +849,6 @@ def test_detect_memory_impact_config_no_components_with_tests(tmp_path: Path) -> mock_changed_files.return_value = [ "esphome/components/my_custom_component/component.cpp", ] - determine_jobs._component_has_tests.cache_clear() result = determine_jobs.detect_memory_impact_config() @@ -895,7 +888,6 @@ def test_detect_memory_impact_config_includes_base_bus_components( "esphome/components/uart/automation.h", # Header file with inline code "esphome/components/wifi/wifi.cpp", ] - determine_jobs._component_has_tests.cache_clear() result = determine_jobs.detect_memory_impact_config() @@ -938,7 +930,6 @@ def test_detect_memory_impact_config_with_variant_tests(tmp_path: Path) -> None: "esphome/components/improv_serial/improv_serial.cpp", "esphome/components/ethernet/ethernet.cpp", ] - determine_jobs._component_has_tests.cache_clear() result = determine_jobs.detect_memory_impact_config() @@ -1168,7 +1159,6 @@ def test_detect_memory_impact_config_filters_incompatible_esp32_on_esp8266( "tests/components/esp8266/test.esp8266-ard.yaml", "esphome/core/helpers_esp8266.h", # ESP8266-specific file to hint platform ] - determine_jobs._component_has_tests.cache_clear() result = determine_jobs.detect_memory_impact_config() @@ -1222,7 +1212,6 @@ def test_detect_memory_impact_config_filters_incompatible_esp8266_on_esp32( "esphome/components/wifi/wifi_component_esp_idf.cpp", # ESP-IDF hint "esphome/components/ethernet/ethernet_esp32.cpp", # ESP32 hint ] - determine_jobs._component_has_tests.cache_clear() result = determine_jobs.detect_memory_impact_config() @@ -1257,7 +1246,6 @@ def test_detect_memory_impact_config_skips_release_branch(tmp_path: Path) -> Non patch.object(determine_jobs, "get_target_branch", return_value="release"), ): mock_changed_files.return_value = ["esphome/components/wifi/wifi.cpp"] - determine_jobs._component_has_tests.cache_clear() result = determine_jobs.detect_memory_impact_config() @@ -1280,7 +1268,6 @@ def test_detect_memory_impact_config_skips_beta_branch(tmp_path: Path) -> None: patch.object(determine_jobs, "get_target_branch", return_value="beta"), ): mock_changed_files.return_value = ["esphome/components/wifi/wifi.cpp"] - determine_jobs._component_has_tests.cache_clear() result = determine_jobs.detect_memory_impact_config() @@ -1303,10 +1290,66 @@ def test_detect_memory_impact_config_runs_for_dev_branch(tmp_path: Path) -> None patch.object(determine_jobs, "get_target_branch", return_value="dev"), ): mock_changed_files.return_value = ["esphome/components/wifi/wifi.cpp"] - determine_jobs._component_has_tests.cache_clear() result = determine_jobs.detect_memory_impact_config() # Memory impact should run for dev branch assert result["should_run"] == "true" assert result["components"] == ["wifi"] + + +def test_detect_memory_impact_config_skips_too_many_components( + tmp_path: Path, +) -> None: + """Test that memory impact analysis is skipped when more than 40 components changed.""" + # Create test directory structure with 41 components + tests_dir = tmp_path / "tests" / "components" + component_names = [f"component_{i}" for i in range(41)] + + for component_name in component_names: + comp_dir = tests_dir / component_name + comp_dir.mkdir(parents=True) + (comp_dir / "test.esp32-idf.yaml").write_text(f"test: {component_name}") + + with ( + patch.object(determine_jobs, "root_path", str(tmp_path)), + patch.object(helpers, "root_path", str(tmp_path)), + patch.object(determine_jobs, "changed_files") as mock_changed_files, + patch.object(determine_jobs, "get_target_branch", return_value="dev"), + ): + mock_changed_files.return_value = [ + f"esphome/components/{name}/{name}.cpp" for name in component_names + ] + + result = determine_jobs.detect_memory_impact_config() + + # Memory impact should be skipped for too many components (41 > 40) + assert result["should_run"] == "false" + + +def test_detect_memory_impact_config_runs_at_component_limit(tmp_path: Path) -> None: + """Test that memory impact analysis runs with exactly 40 components (at limit).""" + # Create test directory structure with exactly 40 components + tests_dir = tmp_path / "tests" / "components" + component_names = [f"component_{i}" for i in range(40)] + + for component_name in component_names: + comp_dir = tests_dir / component_name + comp_dir.mkdir(parents=True) + (comp_dir / "test.esp32-idf.yaml").write_text(f"test: {component_name}") + + with ( + patch.object(determine_jobs, "root_path", str(tmp_path)), + patch.object(helpers, "root_path", str(tmp_path)), + patch.object(determine_jobs, "changed_files") as mock_changed_files, + patch.object(determine_jobs, "get_target_branch", return_value="dev"), + ): + mock_changed_files.return_value = [ + f"esphome/components/{name}/{name}.cpp" for name in component_names + ] + + result = determine_jobs.detect_memory_impact_config() + + # Memory impact should run at exactly 40 components (at limit but not over) + assert result["should_run"] == "true" + assert len(result["components"]) == 40 From 5eea7bdb44fcd58b99f6738264122e9528ebcbb0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 19:45:48 -0600 Subject: [PATCH 0112/1145] Update AI instructions with C++ style guidelines from developers docs (#11743) --- .ai/instructions.md | 74 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/.ai/instructions.md b/.ai/instructions.md index 5f314a0dc9..9309c67c65 100644 --- a/.ai/instructions.md +++ b/.ai/instructions.md @@ -51,7 +51,79 @@ This document provides essential context for AI models interacting with this pro * **Naming Conventions:** * **Python:** Follows PEP 8. Use clear, descriptive names following snake_case. - * **C++:** Follows the Google C++ Style Guide. + * **C++:** Follows the Google C++ Style Guide with these specifics (following clang-tidy conventions): + - Function, method, and variable names: `lower_snake_case` + - Class/struct/enum names: `UpperCamelCase` + - Top-level constants (global/namespace scope): `UPPER_SNAKE_CASE` + - Function-local constants: `lower_snake_case` + - Protected/private fields: `lower_snake_case_with_trailing_underscore_` + - Favor descriptive names over abbreviations + +* **C++ Field Visibility:** + * **Prefer `protected`:** Use `protected` for most class fields to enable extensibility and testing. Fields should be `lower_snake_case_with_trailing_underscore_`. + * **Use `private` for safety-critical cases:** Use `private` visibility when direct field access could introduce bugs or violate invariants: + 1. **Pointer lifetime issues:** When setters validate and store pointers from known lists to prevent dangling references. + ```cpp + // Helper to find matching string in vector and return its pointer + inline const char *vector_find(const std::vector &vec, const char *value) { + for (const char *item : vec) { + if (strcmp(item, value) == 0) + return item; + } + return nullptr; + } + + class ClimateDevice { + public: + void set_custom_fan_modes(std::initializer_list modes) { + this->custom_fan_modes_ = modes; + this->active_custom_fan_mode_ = nullptr; // Reset when modes change + } + bool set_custom_fan_mode(const char *mode) { + // Find mode in supported list and store that pointer (not the input pointer) + const char *validated_mode = vector_find(this->custom_fan_modes_, mode); + if (validated_mode != nullptr) { + this->active_custom_fan_mode_ = validated_mode; + return true; + } + return false; + } + private: + std::vector custom_fan_modes_; // Pointers to string literals in flash + const char *active_custom_fan_mode_{nullptr}; // Must point to entry in custom_fan_modes_ + }; + ``` + 2. **Invariant coupling:** When multiple fields must remain synchronized to prevent buffer overflows or data corruption. + ```cpp + class Buffer { + public: + void resize(size_t new_size) { + auto new_data = std::make_unique(new_size); + if (this->data_) { + std::memcpy(new_data.get(), this->data_.get(), std::min(this->size_, new_size)); + } + this->data_ = std::move(new_data); + this->size_ = new_size; // Must stay in sync with data_ + } + private: + std::unique_ptr data_; + size_t size_{0}; // Must match allocated size of data_ + }; + ``` + 3. **Resource management:** When setters perform cleanup or registration operations that derived classes might skip. + * **Provide `protected` accessor methods:** When derived classes need controlled access to `private` members. + +* **C++ Preprocessor Directives:** + * **Avoid `#define` for constants:** Using `#define` for constants is discouraged and should be replaced with `const` variables or enums. + * **Use `#define` only for:** + - Conditional compilation (`#ifdef`, `#ifndef`) + - Compile-time sizes calculated during Python code generation (e.g., configuring `std::array` or `StaticVector` dimensions via `cg.add_define()`) + +* **C++ Additional Conventions:** + * **Member access:** Prefix all class member access with `this->` (e.g., `this->value_` not `value_`) + * **Indentation:** Use spaces (two per indentation level), not tabs + * **Type aliases:** Prefer `using type_t = int;` over `typedef int type_t;` + * **Line length:** Wrap lines at no more than 120 characters * **Component Structure:** * **Standard Files:** From 83f30a64ed54f9285b0d68ba22b8654cdc75c5be Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 20:31:59 -0600 Subject: [PATCH 0113/1145] [api] Store YAML service names in flash instead of heap (#11744) --- esphome/components/api/custom_api_device.h | 4 +- esphome/components/api/user_services.h | 60 +++++++++++++++++-- .../fixtures/api_custom_services.yaml | 22 +++++++ tests/integration/test_api_custom_services.py | 60 ++++++++++++++++++- 4 files changed, 136 insertions(+), 10 deletions(-) diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index d34ccfa0ce..43ea644f0c 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -9,11 +9,11 @@ namespace esphome::api { #ifdef USE_API_SERVICES -template class CustomAPIDeviceService : public UserServiceBase { +template class CustomAPIDeviceService : public UserServiceDynamic { public: CustomAPIDeviceService(const std::string &name, const std::array &arg_names, T *obj, void (T::*callback)(Ts...)) - : UserServiceBase(name, arg_names), obj_(obj), callback_(callback) {} + : UserServiceDynamic(name, arg_names), obj_(obj), callback_(callback) {} protected: void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT diff --git a/esphome/components/api/user_services.h b/esphome/components/api/user_services.h index 9ca5e1093e..2a887fc52d 100644 --- a/esphome/components/api/user_services.h +++ b/esphome/components/api/user_services.h @@ -23,11 +23,13 @@ template T get_execute_arg_value(const ExecuteServiceArgument &arg); template enums::ServiceArgType to_service_arg_type(); +// Base class for YAML-defined services (most common case) +// Stores only pointers to string literals in flash - no heap allocation template class UserServiceBase : public UserServiceDescriptor { public: - UserServiceBase(std::string name, const std::array &arg_names) - : name_(std::move(name)), arg_names_(arg_names) { - this->key_ = fnv1_hash(this->name_); + UserServiceBase(const char *name, const std::array &arg_names) + : name_(name), arg_names_(arg_names) { + this->key_ = fnv1_hash(name); } ListEntitiesServicesResponse encode_list_service_response() override { @@ -47,7 +49,7 @@ template class UserServiceBase : public UserServiceDescriptor { bool execute_service(const ExecuteServiceRequest &req) override { if (req.key != this->key_) return false; - if (req.args.size() != this->arg_names_.size()) + if (req.args.size() != sizeof...(Ts)) return false; this->execute_(req.args, typename gens::type()); return true; @@ -59,14 +61,60 @@ template class UserServiceBase : public UserServiceDescriptor { this->execute((get_execute_arg_value(args[S]))...); } - std::string name_; + // Pointers to string literals in flash - no heap allocation + const char *name_; + std::array arg_names_; uint32_t key_{0}; +}; + +// Separate class for custom_api_device services (rare case) +// Stores copies of runtime-generated names +template class UserServiceDynamic : public UserServiceDescriptor { + public: + UserServiceDynamic(std::string name, const std::array &arg_names) + : name_(std::move(name)), arg_names_(arg_names) { + this->key_ = fnv1_hash(this->name_.c_str()); + } + + ListEntitiesServicesResponse encode_list_service_response() override { + ListEntitiesServicesResponse msg; + msg.set_name(StringRef(this->name_)); + msg.key = this->key_; + std::array arg_types = {to_service_arg_type()...}; + msg.args.init(sizeof...(Ts)); + for (size_t i = 0; i < sizeof...(Ts); i++) { + auto &arg = msg.args.emplace_back(); + arg.type = arg_types[i]; + arg.set_name(StringRef(this->arg_names_[i])); + } + return msg; + } + + bool execute_service(const ExecuteServiceRequest &req) override { + if (req.key != this->key_) + return false; + if (req.args.size() != sizeof...(Ts)) + return false; + this->execute_(req.args, typename gens::type()); + return true; + } + + protected: + virtual void execute(Ts... x) = 0; + template void execute_(const ArgsContainer &args, seq type) { + this->execute((get_execute_arg_value(args[S]))...); + } + + // Heap-allocated strings for runtime-generated names + std::string name_; std::array arg_names_; + uint32_t key_{0}; }; template class UserServiceTrigger : public UserServiceBase, public Trigger { public: - UserServiceTrigger(const std::string &name, const std::array &arg_names) + // Constructor for static names (YAML-defined services - used by code generator) + UserServiceTrigger(const char *name, const std::array &arg_names) : UserServiceBase(name, arg_names) {} protected: diff --git a/tests/integration/fixtures/api_custom_services.yaml b/tests/integration/fixtures/api_custom_services.yaml index 41efc95b85..a597c74126 100644 --- a/tests/integration/fixtures/api_custom_services.yaml +++ b/tests/integration/fixtures/api_custom_services.yaml @@ -11,6 +11,28 @@ api: then: - logger.log: "YAML service called" + # Test YAML service with arguments (tests UserServiceBase with const char* array) + - action: test_yaml_service_with_args + variables: + my_int: int + my_string: string + then: + - logger.log: + format: "YAML service with args: %d, %s" + args: [my_int, my_string.c_str()] + + # Test YAML service with multiple arguments + - action: test_yaml_service_many_args + variables: + arg1: int + arg2: float + arg3: bool + arg4: string + then: + - logger.log: + format: "YAML service many args: %d, %.2f, %d, %s" + args: [arg1, arg2, arg3, arg4.c_str()] + logger: level: DEBUG diff --git a/tests/integration/test_api_custom_services.py b/tests/integration/test_api_custom_services.py index 9ae4cdcb5d..967c504112 100644 --- a/tests/integration/test_api_custom_services.py +++ b/tests/integration/test_api_custom_services.py @@ -33,12 +33,16 @@ async def test_api_custom_services( # Track log messages yaml_service_future = loop.create_future() + yaml_args_future = loop.create_future() + yaml_many_args_future = loop.create_future() custom_service_future = loop.create_future() custom_args_future = loop.create_future() custom_arrays_future = loop.create_future() # Patterns to match in logs yaml_service_pattern = re.compile(r"YAML service called") + yaml_args_pattern = re.compile(r"YAML service with args: 123, test_value") + yaml_many_args_pattern = re.compile(r"YAML service many args: 42, 3\.14, 1, hello") custom_service_pattern = re.compile(r"Custom test service called!") custom_args_pattern = re.compile( r"Custom service called with: test_string, 456, 1, 78\.90" @@ -51,6 +55,10 @@ async def test_api_custom_services( """Check log output for expected messages.""" if not yaml_service_future.done() and yaml_service_pattern.search(line): yaml_service_future.set_result(True) + elif not yaml_args_future.done() and yaml_args_pattern.search(line): + yaml_args_future.set_result(True) + elif not yaml_many_args_future.done() and yaml_many_args_pattern.search(line): + yaml_many_args_future.set_result(True) elif not custom_service_future.done() and custom_service_pattern.search(line): custom_service_future.set_result(True) elif not custom_args_future.done() and custom_args_pattern.search(line): @@ -71,11 +79,13 @@ async def test_api_custom_services( # List services _, services = await client.list_entities_services() - # Should have 4 services: 1 YAML + 3 CustomAPIDevice - assert len(services) == 4, f"Expected 4 services, found {len(services)}" + # Should have 6 services: 3 YAML + 3 CustomAPIDevice + assert len(services) == 6, f"Expected 6 services, found {len(services)}" # Find our services yaml_service: UserService | None = None + yaml_args_service: UserService | None = None + yaml_many_args_service: UserService | None = None custom_service: UserService | None = None custom_args_service: UserService | None = None custom_arrays_service: UserService | None = None @@ -83,6 +93,10 @@ async def test_api_custom_services( for service in services: if service.name == "test_yaml_service": yaml_service = service + elif service.name == "test_yaml_service_with_args": + yaml_args_service = service + elif service.name == "test_yaml_service_many_args": + yaml_many_args_service = service elif service.name == "custom_test_service": custom_service = service elif service.name == "custom_service_with_args": @@ -91,6 +105,10 @@ async def test_api_custom_services( custom_arrays_service = service assert yaml_service is not None, "test_yaml_service not found" + assert yaml_args_service is not None, "test_yaml_service_with_args not found" + assert yaml_many_args_service is not None, ( + "test_yaml_service_many_args not found" + ) assert custom_service is not None, "custom_test_service not found" assert custom_args_service is not None, "custom_service_with_args not found" assert custom_arrays_service is not None, "custom_service_with_arrays not found" @@ -99,6 +117,44 @@ async def test_api_custom_services( client.execute_service(yaml_service, {}) await asyncio.wait_for(yaml_service_future, timeout=5.0) + # Verify YAML service with args arguments + assert len(yaml_args_service.args) == 2 + yaml_args_types = {arg.name: arg.type for arg in yaml_args_service.args} + assert yaml_args_types["my_int"] == UserServiceArgType.INT + assert yaml_args_types["my_string"] == UserServiceArgType.STRING + + # Test YAML service with arguments + client.execute_service( + yaml_args_service, + { + "my_int": 123, + "my_string": "test_value", + }, + ) + await asyncio.wait_for(yaml_args_future, timeout=5.0) + + # Verify YAML service with many args arguments + assert len(yaml_many_args_service.args) == 4 + yaml_many_args_types = { + arg.name: arg.type for arg in yaml_many_args_service.args + } + assert yaml_many_args_types["arg1"] == UserServiceArgType.INT + assert yaml_many_args_types["arg2"] == UserServiceArgType.FLOAT + assert yaml_many_args_types["arg3"] == UserServiceArgType.BOOL + assert yaml_many_args_types["arg4"] == UserServiceArgType.STRING + + # Test YAML service with many arguments + client.execute_service( + yaml_many_args_service, + { + "arg1": 42, + "arg2": 3.14, + "arg3": True, + "arg4": "hello", + }, + ) + await asyncio.wait_for(yaml_many_args_future, timeout=5.0) + # Test simple CustomAPIDevice service client.execute_service(custom_service, {}) await asyncio.wait_for(custom_service_future, timeout=5.0) From ab5d8f67aee0c631b3fdf7d6cae94aa71fb4b60c Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:48:02 +1000 Subject: [PATCH 0114/1145] [core] Add helper functions for clamp_at_... (#10387) --- esphome/core/helpers.h | 16 +++++++++++++++- tests/components/esp32/common.yaml | 13 +++++++++++++ tests/components/esp8266/test.esp8266-ard.yaml | 13 +++++++++++++ tests/components/host/common.yaml | 7 +++++++ tests/components/libretiny/test.bk72xx-ard.yaml | 13 +++++++++++++ tests/components/nrf52/test.nrf52-adafruit.yaml | 10 ++++++++++ tests/components/rp2040/test.rp2040-ard.yaml | 13 +++++++++++++ 7 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tests/components/esp32/common.yaml create mode 100644 tests/components/esp8266/test.esp8266-ard.yaml create mode 100644 tests/components/libretiny/test.bk72xx-ard.yaml create mode 100644 tests/components/rp2040/test.rp2040-ard.yaml diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 660874ed1a..48af7f674a 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -12,6 +12,7 @@ #include #include #include +#include #include "esphome/core/optional.h" @@ -1169,7 +1170,20 @@ template class RAMAllocator { template using ExternalRAMAllocator = RAMAllocator; -/// @} +/** + * Functions to constrain the range of arithmetic values. + */ + +template T clamp_at_least(T value, T min) { + if (value < min) + return min; + return value; +} +template T clamp_at_most(T value, T max) { + if (value > max) + return max; + return value; +} /// @name Internal functions ///@{ diff --git a/tests/components/esp32/common.yaml b/tests/components/esp32/common.yaml new file mode 100644 index 0000000000..039a261016 --- /dev/null +++ b/tests/components/esp32/common.yaml @@ -0,0 +1,13 @@ +logger: + level: VERBOSE + +esphome: + on_boot: + - lambda: |- + int x = 100; + x = clamp(x, 50, 90); + assert(x == 90); + x = clamp_at_least(x, 95); + assert(x == 95); + x = clamp_at_most(x, 40); + assert(x == 40); diff --git a/tests/components/esp8266/test.esp8266-ard.yaml b/tests/components/esp8266/test.esp8266-ard.yaml new file mode 100644 index 0000000000..039a261016 --- /dev/null +++ b/tests/components/esp8266/test.esp8266-ard.yaml @@ -0,0 +1,13 @@ +logger: + level: VERBOSE + +esphome: + on_boot: + - lambda: |- + int x = 100; + x = clamp(x, 50, 90); + assert(x == 90); + x = clamp_at_least(x, 95); + assert(x == 95); + x = clamp_at_most(x, 40); + assert(x == 40); diff --git a/tests/components/host/common.yaml b/tests/components/host/common.yaml index 5c329c8245..d5c8446ae8 100644 --- a/tests/components/host/common.yaml +++ b/tests/components/host/common.yaml @@ -15,3 +15,10 @@ esphome: static const uint8_t my_addr[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; if (!mac_address_is_valid(my_addr)) ESP_LOGD("test", "Invalid mac address %X", my_addr[0]); // etc. + int x = 100; + x = clamp(x, 50, 90); + assert(x == 90); + x = clamp_at_least(x, 95); + assert(x == 95); + x = clamp_at_most(x, 40); + assert(x == 40); diff --git a/tests/components/libretiny/test.bk72xx-ard.yaml b/tests/components/libretiny/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..039a261016 --- /dev/null +++ b/tests/components/libretiny/test.bk72xx-ard.yaml @@ -0,0 +1,13 @@ +logger: + level: VERBOSE + +esphome: + on_boot: + - lambda: |- + int x = 100; + x = clamp(x, 50, 90); + assert(x == 90); + x = clamp_at_least(x, 95); + assert(x == 95); + x = clamp_at_most(x, 40); + assert(x == 40); diff --git a/tests/components/nrf52/test.nrf52-adafruit.yaml b/tests/components/nrf52/test.nrf52-adafruit.yaml index 3fe80209b6..cf704ecceb 100644 --- a/tests/components/nrf52/test.nrf52-adafruit.yaml +++ b/tests/components/nrf52/test.nrf52-adafruit.yaml @@ -1,3 +1,13 @@ +esphome: + on_boot: + - lambda: |- + int x = 100; + x = clamp(x, 50, 90); + assert(x == 90); + x = clamp_at_least(x, 95); + assert(x == 95); + x = clamp_at_most(x, 40); + assert(x == 40); nrf52: dfu: reset_pin: diff --git a/tests/components/rp2040/test.rp2040-ard.yaml b/tests/components/rp2040/test.rp2040-ard.yaml new file mode 100644 index 0000000000..039a261016 --- /dev/null +++ b/tests/components/rp2040/test.rp2040-ard.yaml @@ -0,0 +1,13 @@ +logger: + level: VERBOSE + +esphome: + on_boot: + - lambda: |- + int x = 100; + x = clamp(x, 50, 90); + assert(x == 90); + x = clamp_at_least(x, 95); + assert(x == 95); + x = clamp_at_most(x, 40); + assert(x == 40); From 822eacfd77878067484601ec2fe58edde7fae9e4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 20:49:24 -0600 Subject: [PATCH 0115/1145] [core] Fix wait_until and for_condition timing regression in automation chains (#11716) --- esphome/core/base_automation.h | 82 +++++++------ .../fixtures/wait_until_mid_loop_timing.yaml | 109 +++++++++++++++++ .../test_wait_until_mid_loop_timing.py | 112 ++++++++++++++++++ 3 files changed, 269 insertions(+), 34 deletions(-) create mode 100644 tests/integration/fixtures/wait_until_mid_loop_timing.yaml create mode 100644 tests/integration/test_wait_until_mid_loop_timing.py diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 128a2d4b08..6f392c8959 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -98,22 +98,28 @@ template class ForCondition : public Condition, public Co TEMPLATABLE_VALUE(uint32_t, time); - void loop() override { this->check_internal(); } - float get_setup_priority() const override { return setup_priority::DATA; } - bool check_internal() { - bool cond = this->condition_->check(); - if (!cond) - this->last_inactive_ = App.get_loop_component_start_time(); - return cond; + void loop() override { + // Safe to use cached time - only called from Application::loop() + this->check_internal_(App.get_loop_component_start_time()); } + float get_setup_priority() const override { return setup_priority::DATA; } + bool check(const Ts &...x) override { - if (!this->check_internal()) + auto now = millis(); + if (!this->check_internal_(now)) return false; - return millis() - this->last_inactive_ >= this->time_.value(x...); + return now - this->last_inactive_ >= this->time_.value(x...); } protected: + bool check_internal_(uint32_t now) { + bool cond = this->condition_->check(); + if (!cond) + this->last_inactive_ = now; + return cond; + } + Condition<> *condition_; uint32_t last_inactive_{0}; }; @@ -424,34 +430,17 @@ template class WaitUntilAction : public Action, public Co auto timeout = this->timeout_value_.optional_value(x...); this->var_queue_.emplace_front(now, timeout, std::make_tuple(x...)); - // Enable loop now that we have work to do - this->enable_loop(); - this->loop(); + // Do immediate check with fresh timestamp + if (this->process_queue_(now)) { + // Only enable loop if we still have pending items + this->enable_loop(); + } } void loop() override { - if (this->num_running_ == 0) - return; - - auto now = App.get_loop_component_start_time(); - - this->var_queue_.remove_if([&](auto &queued) { - auto start = std::get(queued); - auto timeout = std::get>(queued); - auto &var = std::get>(queued); - - auto expired = timeout && (now - start) >= *timeout; - - if (!expired && !this->condition_->check_tuple(var)) { - return false; - } - - this->play_next_tuple_(var); - return true; - }); - - // If queue is now empty, disable loop until next play_complex - if (this->var_queue_.empty()) { + // Safe to use cached time - only called from Application::loop() + if (this->num_running_ > 0 && !this->process_queue_(App.get_loop_component_start_time())) { + // If queue is now empty, disable loop until next play_complex this->disable_loop(); } } @@ -467,6 +456,31 @@ template class WaitUntilAction : public Action, public Co } protected: + // Helper: Process queue, triggering completed items and removing them + // Returns true if queue still has pending items + bool process_queue_(uint32_t now) { + // Process each queued wait_until and remove completed ones + this->var_queue_.remove_if([&](auto &queued) { + auto start = std::get(queued); + auto timeout = std::get>(queued); + auto &var = std::get>(queued); + + // Check if timeout has expired + auto expired = timeout && (now - start) >= *timeout; + + // Keep waiting if not expired and condition not met + if (!expired && !this->condition_->check_tuple(var)) { + return false; + } + + // Condition met or timed out - trigger next action + this->play_next_tuple_(var); + return true; + }); + + return !this->var_queue_.empty(); + } + Condition *condition_; std::forward_list, std::tuple>> var_queue_{}; }; diff --git a/tests/integration/fixtures/wait_until_mid_loop_timing.yaml b/tests/integration/fixtures/wait_until_mid_loop_timing.yaml new file mode 100644 index 0000000000..32f59e81a1 --- /dev/null +++ b/tests/integration/fixtures/wait_until_mid_loop_timing.yaml @@ -0,0 +1,109 @@ +# Test for PR #11676 bug: wait_until timeout when triggered mid-component-loop +# This demonstrates that App.get_loop_component_start_time() is stale when +# wait_until is triggered partway through a component's loop execution + +esphome: + name: wait-mid-loop + +host: + +api: + actions: + - action: test_mid_loop_timeout + then: + - logger.log: "=== Test: wait_until triggered mid-loop should timeout correctly ===" + + # Reset test state + - globals.set: + id: test_complete + value: 'false' + + # Trigger the slow script that will call wait_until mid-execution + - script.execute: slow_script + + # Wait for test to complete (should take ~300ms: 100ms delay + 200ms timeout) + - wait_until: + condition: + lambda: return id(test_complete); + timeout: 2s + + - if: + condition: + lambda: return id(test_complete); + then: + - logger.log: "✓ Test PASSED: wait_until timed out correctly" + else: + - logger.log: "✗ Test FAILED: wait_until did not complete properly" + +logger: + level: DEBUG + +globals: + - id: test_complete + type: bool + restore_value: false + initial_value: 'false' + + - id: test_condition + type: bool + restore_value: false + initial_value: 'false' + + - id: timeout_start_time + type: uint32_t + restore_value: false + initial_value: '0' + + - id: timeout_end_time + type: uint32_t + restore_value: false + initial_value: '0' + +script: + # This script simulates a component that takes time during its execution + # When wait_until is triggered mid-script, the loop_component_start_time + # will be stale (from when the script's component loop started) + - id: slow_script + then: + - logger.log: "Script: Starting, about to do some work..." + + # Simulate component doing work for 100ms + # This represents time spent in a component's loop() before triggering wait_until + - delay: 100ms + + - logger.log: "Script: 100ms elapsed, now starting wait_until with 200ms timeout" + - lambda: |- + // Record when timeout starts + id(timeout_start_time) = millis(); + id(test_condition) = false; + + # At this point: + # - Script component's loop started 100ms ago + # - App.loop_component_start_time_ = time from 100ms ago (stale!) + # - wait_until will capture millis() NOW (fresh) + # - BUG: loop() will use stale loop_component_start_time, causing immediate timeout + + - wait_until: + condition: + lambda: return id(test_condition); + timeout: 200ms + + - lambda: |- + // Record when timeout completes + id(timeout_end_time) = millis(); + uint32_t elapsed = id(timeout_end_time) - id(timeout_start_time); + + ESP_LOGD("TEST", "wait_until completed after %u ms (expected ~200ms)", elapsed); + + // Check if timeout took approximately correct time + // Should be ~200ms, not <50ms (immediate timeout) + if (elapsed >= 150 && elapsed <= 250) { + ESP_LOGD("TEST", "✓ Timeout duration correct: %u ms", elapsed); + id(test_complete) = true; + } else { + ESP_LOGE("TEST", "✗ Timeout duration WRONG: %u ms (expected 150-250ms)", elapsed); + if (elapsed < 50) { + ESP_LOGE("TEST", " → Likely BUG: Immediate timeout due to stale loop_component_start_time"); + } + id(test_complete) = false; + } diff --git a/tests/integration/test_wait_until_mid_loop_timing.py b/tests/integration/test_wait_until_mid_loop_timing.py new file mode 100644 index 0000000000..01cad747ae --- /dev/null +++ b/tests/integration/test_wait_until_mid_loop_timing.py @@ -0,0 +1,112 @@ +"""Integration test for PR #11676 mid-loop timing bug. + +This test validates that wait_until timeouts work correctly when triggered +mid-component-loop, where App.get_loop_component_start_time() is stale. + +The bug: When wait_until is triggered partway through a component's loop execution +(e.g., from a script or automation), the cached loop_component_start_time_ is stale +relative to when the action was actually triggered. This causes timeout calculations +to underflow and timeout immediately instead of waiting the specified duration. +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_wait_until_mid_loop_timing( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that wait_until timeout works when triggered mid-component-loop. + + This test: + 1. Executes a script that delays 100ms (simulating component work) + 2. Then starts wait_until with 200ms timeout + 3. Verifies timeout takes ~200ms, not <50ms (immediate timeout bug) + """ + loop = asyncio.get_running_loop() + + # Track test results + test_results = { + "timeout_duration": None, + "passed": False, + "failed": False, + "bug_detected": False, + } + + # Patterns for log messages + timeout_duration = re.compile(r"wait_until completed after (\d+) ms") + test_pass = re.compile(r"✓ Timeout duration correct") + test_fail = re.compile(r"✗ Timeout duration WRONG") + bug_pattern = re.compile(r"Likely BUG: Immediate timeout") + test_passed = re.compile(r"✓ Test PASSED") + test_failed = re.compile(r"✗ Test FAILED") + + test_complete = loop.create_future() + + def check_output(line: str) -> None: + """Check log output for test results.""" + # Extract timeout duration + match = timeout_duration.search(line) + if match: + test_results["timeout_duration"] = int(match.group(1)) + + if test_pass.search(line): + test_results["passed"] = True + if test_fail.search(line): + test_results["failed"] = True + if bug_pattern.search(line): + test_results["bug_detected"] = True + + # Final test result + if ( + test_passed.search(line) + or test_failed.search(line) + and not test_complete.done() + ): + test_complete.set_result(True) + + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Get the test service + _, services = await client.list_entities_services() + test_service = next( + (s for s in services if s.name == "test_mid_loop_timeout"), None + ) + assert test_service is not None, "test_mid_loop_timeout service not found" + + # Execute the test + client.execute_service(test_service, {}) + + # Wait for test to complete (100ms delay + 200ms timeout + margins = ~500ms) + await asyncio.wait_for(test_complete, timeout=5.0) + + # Verify results + assert test_results["timeout_duration"] is not None, ( + "Timeout duration not reported" + ) + assert test_results["passed"], ( + f"Test failed: wait_until took {test_results['timeout_duration']}ms, expected ~200ms. " + f"Bug detected: {test_results['bug_detected']}" + ) + assert not test_results["bug_detected"], ( + f"BUG DETECTED: wait_until timed out immediately ({test_results['timeout_duration']}ms) " + "instead of waiting 200ms. This indicates stale loop_component_start_time." + ) + + # Additional validation: timeout should be ~200ms (150-250ms range) + duration = test_results["timeout_duration"] + assert 150 <= duration <= 250, ( + f"Timeout duration {duration}ms outside expected range (150-250ms). " + f"This suggests timing regression from PR #11676." + ) From 74187845b7625efba82b1adf753335eb1713022a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Nov 2025 20:55:26 -0600 Subject: [PATCH 0116/1145] [select] Convert remaining components to use index-based control() (#11693) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/demo/demo_select.h | 2 +- .../es8388/select/adc_input_mic_select.cpp | 6 ++--- .../es8388/select/adc_input_mic_select.h | 2 +- .../es8388/select/dac_output_select.cpp | 6 ++--- .../es8388/select/dac_output_select.h | 2 +- esphome/components/lvgl/select/lvgl_select.h | 8 +++---- .../select/modbus_select.cpp | 22 +++++++++---------- .../modbus_controller/select/modbus_select.h | 2 +- .../select/existence_boundary_select.cpp | 9 +++----- .../select/existence_boundary_select.h | 2 +- .../select/motion_boundary_select.cpp | 9 +++----- .../select/motion_boundary_select.h | 2 +- .../select/scene_mode_select.cpp | 9 +++----- .../seeed_mr24hpc1/select/scene_mode_select.h | 2 +- .../select/unman_time_select.cpp | 9 +++----- .../seeed_mr24hpc1/select/unman_time_select.h | 2 +- .../select/height_threshold_select.cpp | 9 +++----- .../select/height_threshold_select.h | 2 +- .../select/install_height_select.cpp | 9 +++----- .../select/install_height_select.h | 2 +- .../select/sensitivity_select.cpp | 9 +++----- .../select/sensitivity_select.h | 2 +- .../components/tuya/select/tuya_select.cpp | 22 +++++++------------ esphome/components/tuya/select/tuya_select.h | 2 +- 24 files changed, 61 insertions(+), 90 deletions(-) diff --git a/esphome/components/demo/demo_select.h b/esphome/components/demo/demo_select.h index 1951a684a2..1a5df13eda 100644 --- a/esphome/components/demo/demo_select.h +++ b/esphome/components/demo/demo_select.h @@ -8,7 +8,7 @@ namespace demo { class DemoSelect : public select::Select, public Component { protected: - void control(const std::string &value) override { this->publish_state(value); } + void control(size_t index) override { this->publish_state(index); } }; } // namespace demo diff --git a/esphome/components/es8388/select/adc_input_mic_select.cpp b/esphome/components/es8388/select/adc_input_mic_select.cpp index 5fab5b8a92..2e47534296 100644 --- a/esphome/components/es8388/select/adc_input_mic_select.cpp +++ b/esphome/components/es8388/select/adc_input_mic_select.cpp @@ -3,9 +3,9 @@ namespace esphome { namespace es8388 { -void ADCInputMicSelect::control(const std::string &value) { - this->publish_state(value); - this->parent_->set_adc_input_mic(static_cast(this->index_of(value).value())); +void ADCInputMicSelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_adc_input_mic(static_cast(index)); } } // namespace es8388 diff --git a/esphome/components/es8388/select/adc_input_mic_select.h b/esphome/components/es8388/select/adc_input_mic_select.h index 8d035525ef..f0fa840d00 100644 --- a/esphome/components/es8388/select/adc_input_mic_select.h +++ b/esphome/components/es8388/select/adc_input_mic_select.h @@ -8,7 +8,7 @@ namespace es8388 { class ADCInputMicSelect : public select::Select, public Parented { protected: - void control(const std::string &value) override; + void control(size_t index) override; }; } // namespace es8388 diff --git a/esphome/components/es8388/select/dac_output_select.cpp b/esphome/components/es8388/select/dac_output_select.cpp index 268b5f290c..9af288a721 100644 --- a/esphome/components/es8388/select/dac_output_select.cpp +++ b/esphome/components/es8388/select/dac_output_select.cpp @@ -3,9 +3,9 @@ namespace esphome { namespace es8388 { -void DacOutputSelect::control(const std::string &value) { - this->publish_state(value); - this->parent_->set_dac_output(static_cast(this->index_of(value).value())); +void DacOutputSelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_dac_output(static_cast(index)); } } // namespace es8388 diff --git a/esphome/components/es8388/select/dac_output_select.h b/esphome/components/es8388/select/dac_output_select.h index fccae9fc19..40d8a66553 100644 --- a/esphome/components/es8388/select/dac_output_select.h +++ b/esphome/components/es8388/select/dac_output_select.h @@ -8,7 +8,7 @@ namespace es8388 { class DacOutputSelect : public select::Select, public Parented { protected: - void control(const std::string &value) override; + void control(size_t index) override; }; } // namespace es8388 diff --git a/esphome/components/lvgl/select/lvgl_select.h b/esphome/components/lvgl/select/lvgl_select.h index d4c9631073..70bb3e7bcb 100644 --- a/esphome/components/lvgl/select/lvgl_select.h +++ b/esphome/components/lvgl/select/lvgl_select.h @@ -41,16 +41,16 @@ class LVGLSelect : public select::Select, public Component { } void publish() { - this->publish_state(this->widget_->get_selected_text()); + auto index = this->widget_->get_selected_index(); + this->publish_state(index); if (this->restore_) { - auto index = this->widget_->get_selected_index(); this->pref_.save(&index); } } protected: - void control(const std::string &value) override { - this->widget_->set_selected_text(value, this->anim_); + void control(size_t index) override { + this->widget_->set_selected_index(index, this->anim_); this->publish(); } void set_options_() { diff --git a/esphome/components/modbus_controller/select/modbus_select.cpp b/esphome/components/modbus_controller/select/modbus_select.cpp index 48bf2835f2..853f4215c3 100644 --- a/esphome/components/modbus_controller/select/modbus_select.cpp +++ b/esphome/components/modbus_controller/select/modbus_select.cpp @@ -28,8 +28,9 @@ void ModbusSelect::parse_and_publish(const std::vector &data) { if (map_it != this->mapping_.cend()) { size_t idx = std::distance(this->mapping_.cbegin(), map_it); - new_state = std::string(this->option_at(idx)); - ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value); + ESP_LOGV(TAG, "Found option %s for value %lld", this->option_at(idx), value); + this->publish_state(idx); + return; } else { ESP_LOGE(TAG, "No option found for mapping %lld", value); } @@ -40,19 +41,16 @@ void ModbusSelect::parse_and_publish(const std::vector &data) { } } -void ModbusSelect::control(const std::string &value) { - auto idx = this->index_of(value); - if (!idx.has_value()) { - ESP_LOGW(TAG, "Invalid option '%s'", value.c_str()); - return; - } - optional mapval = this->mapping_[idx.value()]; - ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str()); +void ModbusSelect::control(size_t index) { + optional mapval = this->mapping_[index]; + const char *option = this->option_at(index); + ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, option); std::vector data; if (this->write_transform_func_.has_value()) { - auto val = (*this->write_transform_func_)(this, value, *mapval, data); + // Transform func requires string parameter for backward compatibility + auto val = (*this->write_transform_func_)(this, std::string(option), *mapval, data); if (val.has_value()) { mapval = *val; ESP_LOGV(TAG, "write_lambda returned mapping value %lld", *mapval); @@ -85,7 +83,7 @@ void ModbusSelect::control(const std::string &value) { this->parent_->queue_command(write_cmd); if (this->optimistic_) - this->publish_state(value); + this->publish_state(index); } } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/select/modbus_select.h b/esphome/components/modbus_controller/select/modbus_select.h index e6b98aead2..fde441f2bc 100644 --- a/esphome/components/modbus_controller/select/modbus_select.h +++ b/esphome/components/modbus_controller/select/modbus_select.h @@ -38,7 +38,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem void dump_config() override; void parse_and_publish(const std::vector &data) override; - void control(const std::string &value) override; + void control(size_t index) override; protected: std::vector mapping_{}; diff --git a/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.cpp b/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.cpp index 03c2ec4745..81543055a4 100644 --- a/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.cpp +++ b/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.cpp @@ -3,12 +3,9 @@ namespace esphome { namespace seeed_mr24hpc1 { -void ExistenceBoundarySelect::control(const std::string &value) { - this->publish_state(value); - auto index = this->index_of(value); - if (index.has_value()) { - this->parent_->set_existence_boundary(index.value()); - } +void ExistenceBoundarySelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_existence_boundary(index); } } // namespace seeed_mr24hpc1 diff --git a/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.h b/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.h index ad770a7296..933279dd13 100644 --- a/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.h +++ b/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.h @@ -11,7 +11,7 @@ class ExistenceBoundarySelect : public select::Select, public Parentedpublish_state(value); - auto index = this->index_of(value); - if (index.has_value()) { - this->parent_->set_motion_boundary(index.value()); - } +void MotionBoundarySelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_motion_boundary(index); } } // namespace seeed_mr24hpc1 diff --git a/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.h b/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.h index 9058e3130b..b0051ae6b1 100644 --- a/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.h +++ b/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.h @@ -11,7 +11,7 @@ class MotionBoundarySelect : public select::Select, public Parentedpublish_state(value); - auto index = this->index_of(value); - if (index.has_value()) { - this->parent_->set_scene_mode(index.value()); - } +void SceneModeSelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_scene_mode(index); } } // namespace seeed_mr24hpc1 diff --git a/esphome/components/seeed_mr24hpc1/select/scene_mode_select.h b/esphome/components/seeed_mr24hpc1/select/scene_mode_select.h index 95508d49b0..f478ea5b66 100644 --- a/esphome/components/seeed_mr24hpc1/select/scene_mode_select.h +++ b/esphome/components/seeed_mr24hpc1/select/scene_mode_select.h @@ -11,7 +11,7 @@ class SceneModeSelect : public select::Select, public Parentedpublish_state(value); - auto index = this->index_of(value); - if (index.has_value()) { - this->parent_->set_unman_time(index.value()); - } +void UnmanTimeSelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_unman_time(index); } } // namespace seeed_mr24hpc1 diff --git a/esphome/components/seeed_mr24hpc1/select/unman_time_select.h b/esphome/components/seeed_mr24hpc1/select/unman_time_select.h index 7131988cda..a64ff4b840 100644 --- a/esphome/components/seeed_mr24hpc1/select/unman_time_select.h +++ b/esphome/components/seeed_mr24hpc1/select/unman_time_select.h @@ -11,7 +11,7 @@ class UnmanTimeSelect : public select::Select, public Parentedpublish_state(value); - auto index = this->index_of(value); - if (index.has_value()) { - this->parent_->set_height_threshold(index.value()); - } +void HeightThresholdSelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_height_threshold(index); } } // namespace seeed_mr60fda2 diff --git a/esphome/components/seeed_mr60fda2/select/height_threshold_select.h b/esphome/components/seeed_mr60fda2/select/height_threshold_select.h index b856dbc89a..f5707c7a88 100644 --- a/esphome/components/seeed_mr60fda2/select/height_threshold_select.h +++ b/esphome/components/seeed_mr60fda2/select/height_threshold_select.h @@ -11,7 +11,7 @@ class HeightThresholdSelect : public select::Select, public Parentedpublish_state(value); - auto index = this->index_of(value); - if (index.has_value()) { - this->parent_->set_install_height(index.value()); - } +void InstallHeightSelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_install_height(index); } } // namespace seeed_mr60fda2 diff --git a/esphome/components/seeed_mr60fda2/select/install_height_select.h b/esphome/components/seeed_mr60fda2/select/install_height_select.h index 7430da3493..470d96c50c 100644 --- a/esphome/components/seeed_mr60fda2/select/install_height_select.h +++ b/esphome/components/seeed_mr60fda2/select/install_height_select.h @@ -11,7 +11,7 @@ class InstallHeightSelect : public select::Select, public Parentedpublish_state(value); - auto index = this->index_of(value); - if (index.has_value()) { - this->parent_->set_sensitivity(index.value()); - } +void SensitivitySelect::control(size_t index) { + this->publish_state(index); + this->parent_->set_sensitivity(index); } } // namespace seeed_mr60fda2 diff --git a/esphome/components/seeed_mr60fda2/select/sensitivity_select.h b/esphome/components/seeed_mr60fda2/select/sensitivity_select.h index d1accc1b5b..82ed4c5d79 100644 --- a/esphome/components/seeed_mr60fda2/select/sensitivity_select.h +++ b/esphome/components/seeed_mr60fda2/select/sensitivity_select.h @@ -11,7 +11,7 @@ class SensitivitySelect : public select::Select, public Parentedoptimistic_) - this->publish_state(value); + this->publish_state(index); - auto idx = this->index_of(value); - if (idx.has_value()) { - uint8_t mapping = this->mappings_.at(idx.value()); - ESP_LOGV(TAG, "Setting %u datapoint value to %u:%s", this->select_id_, mapping, value.c_str()); - if (this->is_int_) { - this->parent_->set_integer_datapoint_value(this->select_id_, mapping); - } else { - this->parent_->set_enum_datapoint_value(this->select_id_, mapping); - } - return; + uint8_t mapping = this->mappings_.at(index); + ESP_LOGV(TAG, "Setting %u datapoint value to %u:%s", this->select_id_, mapping, this->option_at(index)); + if (this->is_int_) { + this->parent_->set_integer_datapoint_value(this->select_id_, mapping); + } else { + this->parent_->set_enum_datapoint_value(this->select_id_, mapping); } - - ESP_LOGW(TAG, "Invalid value %s", value.c_str()); } void TuyaSelect::dump_config() { diff --git a/esphome/components/tuya/select/tuya_select.h b/esphome/components/tuya/select/tuya_select.h index 12d7b507d4..24505c9910 100644 --- a/esphome/components/tuya/select/tuya_select.h +++ b/esphome/components/tuya/select/tuya_select.h @@ -23,7 +23,7 @@ class TuyaSelect : public select::Select, public Component { void set_select_mappings(std::vector mappings) { this->mappings_ = std::move(mappings); } protected: - void control(const std::string &value) override; + void control(size_t index) override; Tuya *parent_; bool optimistic_ = false; From 895d76ca030927210f5736161e19e1d7cfd23a54 Mon Sep 17 00:00:00 2001 From: Szewcson Date: Thu, 6 Nov 2025 04:19:29 +0100 Subject: [PATCH 0117/1145] [gdk101] Fix fw version reporting (#11029) Signed-off-by: szewcu Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/gdk101/gdk101.cpp | 15 +++++++++------ esphome/components/gdk101/gdk101.h | 7 ++++++- esphome/components/gdk101/sensor.py | 9 ++------- esphome/components/gdk101/text_sensor.py | 23 +++++++++++++++++++++++ tests/components/gdk101/common.yaml | 7 +++++-- 5 files changed, 45 insertions(+), 16 deletions(-) create mode 100644 esphome/components/gdk101/text_sensor.py diff --git a/esphome/components/gdk101/gdk101.cpp b/esphome/components/gdk101/gdk101.cpp index 4c156ab24b..6c218f03d9 100644 --- a/esphome/components/gdk101/gdk101.cpp +++ b/esphome/components/gdk101/gdk101.cpp @@ -62,7 +62,6 @@ void GDK101Component::dump_config() { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); } #ifdef USE_SENSOR - LOG_SENSOR(" ", "Firmware Version", this->fw_version_sensor_); LOG_SENSOR(" ", "Average Radaition Dose per 1 minute", this->rad_1m_sensor_); LOG_SENSOR(" ", "Average Radaition Dose per 10 minutes", this->rad_10m_sensor_); LOG_SENSOR(" ", "Status", this->status_sensor_); @@ -72,6 +71,10 @@ void GDK101Component::dump_config() { #ifdef USE_BINARY_SENSOR LOG_BINARY_SENSOR(" ", "Vibration Status", this->vibration_binary_sensor_); #endif // USE_BINARY_SENSOR + +#ifdef USE_TEXT_SENSOR + LOG_TEXT_SENSOR(" ", "Firmware Version", this->fw_version_text_sensor_); +#endif // USE_TEXT_SENSOR } float GDK101Component::get_setup_priority() const { return setup_priority::DATA; } @@ -153,18 +156,18 @@ bool GDK101Component::read_status_(uint8_t *data) { } bool GDK101Component::read_fw_version_(uint8_t *data) { -#ifdef USE_SENSOR - if (this->fw_version_sensor_ != nullptr) { +#ifdef USE_TEXT_SENSOR + if (this->fw_version_text_sensor_ != nullptr) { if (!this->read_bytes(GDK101_REG_READ_FIRMWARE, data, 2)) { ESP_LOGE(TAG, "Updating GDK101 failed!"); return false; } - const float fw_version = data[0] + (data[1] / 10.0f); + const std::string fw_version_str = str_sprintf("%d.%d", data[0], data[1]); - this->fw_version_sensor_->publish_state(fw_version); + this->fw_version_text_sensor_->publish_state(fw_version_str); } -#endif // USE_SENSOR +#endif // USE_TEXT_SENSOR return true; } diff --git a/esphome/components/gdk101/gdk101.h b/esphome/components/gdk101/gdk101.h index 460e72ac89..f250a42a54 100644 --- a/esphome/components/gdk101/gdk101.h +++ b/esphome/components/gdk101/gdk101.h @@ -8,6 +8,9 @@ #ifdef USE_BINARY_SENSOR #include "esphome/components/binary_sensor/binary_sensor.h" #endif // USE_BINARY_SENSOR +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif // USE_TEXT_SENSOR #include "esphome/components/i2c/i2c.h" namespace esphome { @@ -25,12 +28,14 @@ class GDK101Component : public PollingComponent, public i2c::I2CDevice { SUB_SENSOR(rad_1m) SUB_SENSOR(rad_10m) SUB_SENSOR(status) - SUB_SENSOR(fw_version) SUB_SENSOR(measurement_duration) #endif // USE_SENSOR #ifdef USE_BINARY_SENSOR SUB_BINARY_SENSOR(vibration) #endif // USE_BINARY_SENSOR +#ifdef USE_TEXT_SENSOR + SUB_TEXT_SENSOR(fw_version) +#endif // USE_TEXT_SENSOR public: void setup() override; diff --git a/esphome/components/gdk101/sensor.py b/esphome/components/gdk101/sensor.py index d04e0b8367..6cf89e0fd4 100644 --- a/esphome/components/gdk101/sensor.py +++ b/esphome/components/gdk101/sensor.py @@ -40,9 +40,8 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_EMPTY, state_class=STATE_CLASS_MEASUREMENT, ), - cv.Optional(CONF_VERSION): sensor.sensor_schema( - entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - accuracy_decimals=1, + cv.Optional(CONF_VERSION): cv.invalid( + "The 'version' option has been moved to the `text_sensor` component." ), cv.Optional(CONF_STATUS): sensor.sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, @@ -71,10 +70,6 @@ async def to_code(config): sens = await sensor.new_sensor(radiation_dose_per_10m) cg.add(hub.set_rad_10m_sensor(sens)) - if version_config := config.get(CONF_VERSION): - sens = await sensor.new_sensor(version_config) - cg.add(hub.set_fw_version_sensor(sens)) - if status_config := config.get(CONF_STATUS): sens = await sensor.new_sensor(status_config) cg.add(hub.set_status_sensor(sens)) diff --git a/esphome/components/gdk101/text_sensor.py b/esphome/components/gdk101/text_sensor.py new file mode 100644 index 0000000000..703e68493a --- /dev/null +++ b/esphome/components/gdk101/text_sensor.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +from esphome.components import text_sensor +import esphome.config_validation as cv +from esphome.const import CONF_VERSION, ENTITY_CATEGORY_DIAGNOSTIC, ICON_CHIP + +from . import CONF_GDK101_ID, GDK101Component + +DEPENDENCIES = ["gdk101"] + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_GDK101_ID): cv.use_id(GDK101Component), + cv.Required(CONF_VERSION): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP + ), + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_GDK101_ID]) + var = await text_sensor.new_text_sensor(config[CONF_VERSION]) + cg.add(hub.set_fw_version_text_sensor(var)) diff --git a/tests/components/gdk101/common.yaml b/tests/components/gdk101/common.yaml index 4eb5586ade..b9b93d760f 100644 --- a/tests/components/gdk101/common.yaml +++ b/tests/components/gdk101/common.yaml @@ -11,8 +11,6 @@ sensor: name: Radiation Dose @ 10 min status: name: Status - version: - name: FW Version measurement_duration: name: Measuring Time @@ -21,3 +19,8 @@ binary_sensor: gdk101_id: my_gdk101 vibrations: name: Vibrations + +text_sensor: + - platform: gdk101 + version: + name: FW Version From 26607713bb1ba0848100384b9ddecd6d4345ba4d Mon Sep 17 00:00:00 2001 From: rwrozelle Date: Wed, 5 Nov 2025 22:57:31 -0500 Subject: [PATCH 0118/1145] [openthread] add poll period for mtd devices (#11374) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/openthread/__init__.py | 19 ++++++++++++-- esphome/components/openthread/const.py | 1 + esphome/components/openthread/openthread.cpp | 17 ++++++++++++ esphome/components/openthread/openthread.h | 7 +++++ .../components/openthread/openthread_esp.cpp | 26 +++++++++++++++++++ .../openthread/test.esp32-c6-idf.yaml | 4 ++- 6 files changed, 71 insertions(+), 3 deletions(-) diff --git a/esphome/components/openthread/__init__.py b/esphome/components/openthread/__init__.py index 572ec144d4..e3ad3ed76c 100644 --- a/esphome/components/openthread/__init__.py +++ b/esphome/components/openthread/__init__.py @@ -9,7 +9,7 @@ from esphome.components.esp32 import ( from esphome.components.mdns import MDNSComponent, enable_mdns_storage import esphome.config_validation as cv from esphome.const import CONF_CHANNEL, CONF_ENABLE_IPV6, CONF_ID, CONF_USE_ADDRESS -from esphome.core import CORE +from esphome.core import CORE, TimePeriodMilliseconds import esphome.final_validate as fv from esphome.types import ConfigType @@ -22,6 +22,7 @@ from .const import ( CONF_NETWORK_KEY, CONF_NETWORK_NAME, CONF_PAN_ID, + CONF_POLL_PERIOD, CONF_PSKC, CONF_SRP_ID, CONF_TLV, @@ -89,7 +90,7 @@ def set_sdkconfig_options(config): add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", True) add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5) - # TODO: Add suport for sleepy end devices + # TODO: Add suport for synchronized sleepy end devices (SSED) add_idf_sdkconfig_option(f"CONFIG_OPENTHREAD_{config.get(CONF_DEVICE_TYPE)}", True) @@ -113,6 +114,17 @@ _CONNECTION_SCHEMA = cv.Schema( def _validate(config: ConfigType) -> ConfigType: if CONF_USE_ADDRESS not in config: config[CONF_USE_ADDRESS] = f"{CORE.name}.local" + device_type = config.get(CONF_DEVICE_TYPE) + poll_period = config.get(CONF_POLL_PERIOD) + if ( + device_type == "FTD" + and poll_period + and poll_period > TimePeriodMilliseconds(milliseconds=0) + ): + raise cv.Invalid( + f"{CONF_POLL_PERIOD} can only be used with {CONF_DEVICE_TYPE}: MTD" + ) + return config @@ -135,6 +147,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_FORCE_DATASET): cv.boolean, cv.Optional(CONF_TLV): cv.string_strict, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, + cv.Optional(CONF_POLL_PERIOD): cv.positive_time_period_milliseconds, } ).extend(_CONNECTION_SCHEMA), cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV), @@ -167,6 +180,8 @@ async def to_code(config): ot = cg.new_Pvariable(config[CONF_ID]) cg.add(ot.set_use_address(config[CONF_USE_ADDRESS])) await cg.register_component(ot, config) + if (poll_period := config.get(CONF_POLL_PERIOD)) is not None: + cg.add(ot.set_poll_period(poll_period)) srp = cg.new_Pvariable(config[CONF_SRP_ID]) mdns_component = await cg.get_variable(config[CONF_MDNS_ID]) diff --git a/esphome/components/openthread/const.py b/esphome/components/openthread/const.py index 7a6ffb2df4..f0274a8c9e 100644 --- a/esphome/components/openthread/const.py +++ b/esphome/components/openthread/const.py @@ -6,6 +6,7 @@ CONF_MESH_LOCAL_PREFIX = "mesh_local_prefix" CONF_NETWORK_NAME = "network_name" CONF_NETWORK_KEY = "network_key" CONF_PAN_ID = "pan_id" +CONF_POLL_PERIOD = "poll_period" CONF_PSKC = "pskc" CONF_SRP_ID = "srp_id" CONF_TLV = "tlv" diff --git a/esphome/components/openthread/openthread.cpp b/esphome/components/openthread/openthread.cpp index d7fb1e1d42..721ab89326 100644 --- a/esphome/components/openthread/openthread.cpp +++ b/esphome/components/openthread/openthread.cpp @@ -29,6 +29,23 @@ OpenThreadComponent *global_openthread_component = // NOLINT(cppcoreguidelines- OpenThreadComponent::OpenThreadComponent() { global_openthread_component = this; } +void OpenThreadComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Open Thread:"); +#if CONFIG_OPENTHREAD_FTD + ESP_LOGCONFIG(TAG, " Device Type: FTD"); +#elif CONFIG_OPENTHREAD_MTD + ESP_LOGCONFIG(TAG, " Device Type: MTD"); + // TBD: Synchronized Sleepy End Device + if (this->poll_period > 0) { + ESP_LOGCONFIG(TAG, " Device is configured as Sleepy End Device (SED)"); + uint32_t duration = this->poll_period / 1000; + ESP_LOGCONFIG(TAG, " Poll Period: %" PRIu32 "s", duration); + } else { + ESP_LOGCONFIG(TAG, " Device is configured as Minimal End Device (MED)"); + } +#endif +} + bool OpenThreadComponent::is_connected() { auto lock = InstanceLock::try_acquire(100); if (!lock) { diff --git a/esphome/components/openthread/openthread.h b/esphome/components/openthread/openthread.h index 3132e41696..546128b366 100644 --- a/esphome/components/openthread/openthread.h +++ b/esphome/components/openthread/openthread.h @@ -22,6 +22,7 @@ class OpenThreadComponent : public Component { public: OpenThreadComponent(); ~OpenThreadComponent(); + void dump_config() override; void setup() override; bool teardown() override; float get_setup_priority() const override { return setup_priority::WIFI; } @@ -35,6 +36,9 @@ class OpenThreadComponent : public Component { const char *get_use_address() const; void set_use_address(const char *use_address); +#if CONFIG_OPENTHREAD_MTD + void set_poll_period(uint32_t poll_period) { this->poll_period = poll_period; } +#endif protected: std::optional get_omr_address_(InstanceLock &lock); @@ -46,6 +50,9 @@ class OpenThreadComponent : public Component { // Stores a pointer to a string literal (static storage duration). // ONLY set from Python-generated code with string literals - never dynamic strings. const char *use_address_{""}; +#if CONFIG_OPENTHREAD_MTD + uint32_t poll_period{0}; +#endif }; extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/openthread/openthread_esp.cpp b/esphome/components/openthread/openthread_esp.cpp index b11b7ad34a..72dc521091 100644 --- a/esphome/components/openthread/openthread_esp.cpp +++ b/esphome/components/openthread/openthread_esp.cpp @@ -105,6 +105,32 @@ void OpenThreadComponent::ot_main() { esp_cli_custom_command_init(); #endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION + otLinkModeConfig link_mode_config = {0}; +#if CONFIG_OPENTHREAD_FTD + link_mode_config.mRxOnWhenIdle = true; + link_mode_config.mDeviceType = true; + link_mode_config.mNetworkData = true; +#elif CONFIG_OPENTHREAD_MTD + if (this->poll_period > 0) { + if (otLinkSetPollPeriod(esp_openthread_get_instance(), this->poll_period) != OT_ERROR_NONE) { + ESP_LOGE(TAG, "Failed to set OpenThread pollperiod."); + } + uint32_t link_polling_period = otLinkGetPollPeriod(esp_openthread_get_instance()); + ESP_LOGD(TAG, "Link Polling Period: %d", link_polling_period); + } + link_mode_config.mRxOnWhenIdle = this->poll_period == 0; + link_mode_config.mDeviceType = false; + link_mode_config.mNetworkData = false; +#endif + + if (otThreadSetLinkMode(esp_openthread_get_instance(), link_mode_config) != OT_ERROR_NONE) { + ESP_LOGE(TAG, "Failed to set OpenThread linkmode."); + } + link_mode_config = otThreadGetLinkMode(esp_openthread_get_instance()); + ESP_LOGD(TAG, "Link Mode Device Type: %s", link_mode_config.mDeviceType ? "true" : "false"); + ESP_LOGD(TAG, "Link Mode Network Data: %s", link_mode_config.mNetworkData ? "true" : "false"); + ESP_LOGD(TAG, "Link Mode RX On When Idle: %s", link_mode_config.mRxOnWhenIdle ? "true" : "false"); + // Run the main loop #if CONFIG_OPENTHREAD_CLI esp_openthread_cli_create_task(); diff --git a/tests/components/openthread/test.esp32-c6-idf.yaml b/tests/components/openthread/test.esp32-c6-idf.yaml index da5339fb39..9df63b2f29 100644 --- a/tests/components/openthread/test.esp32-c6-idf.yaml +++ b/tests/components/openthread/test.esp32-c6-idf.yaml @@ -2,7 +2,7 @@ network: enable_ipv6: true openthread: - device_type: FTD + device_type: MTD channel: 13 network_name: OpenThread-8f28 network_key: 0xdfd34f0f05cad978ec4e32b0413038ff @@ -11,3 +11,5 @@ openthread: pskc: 0xc23a76e98f1a6483639b1ac1271e2e27 mesh_local_prefix: fd53:145f:ed22:ad81::/64 force_dataset: true + use_address: open-thread-test.local + poll_period: 20sec From 5cdb891b580882842c420b458ddc141775162031 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 6 Nov 2025 19:21:58 -0600 Subject: [PATCH 0119/1145] [socket] Deduplicate IP formatting in LWIP raw TCP implementation (#11747) --- .../components/socket/lwip_raw_tcp_impl.cpp | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 4dedeffb6a..e0d93d8e2f 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -172,16 +172,7 @@ class LWIPRawImpl : public Socket { errno = ECONNRESET; return ""; } - char buffer[50] = {}; - if (IP_IS_V4_VAL(pcb_->remote_ip)) { - inet_ntoa_r(pcb_->remote_ip, buffer, sizeof(buffer)); - } -#if LWIP_IPV6 - else if (IP_IS_V6_VAL(pcb_->remote_ip)) { - inet6_ntoa_r(pcb_->remote_ip, buffer, sizeof(buffer)); - } -#endif - return std::string(buffer); + return this->format_ip_address_(pcb_->remote_ip); } int getsockname(struct sockaddr *name, socklen_t *addrlen) override { if (pcb_ == nullptr) { @@ -199,16 +190,7 @@ class LWIPRawImpl : public Socket { errno = ECONNRESET; return ""; } - char buffer[50] = {}; - if (IP_IS_V4_VAL(pcb_->local_ip)) { - inet_ntoa_r(pcb_->local_ip, buffer, sizeof(buffer)); - } -#if LWIP_IPV6 - else if (IP_IS_V6_VAL(pcb_->local_ip)) { - inet6_ntoa_r(pcb_->local_ip, buffer, sizeof(buffer)); - } -#endif - return std::string(buffer); + return this->format_ip_address_(pcb_->local_ip); } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { if (pcb_ == nullptr) { @@ -499,6 +481,19 @@ class LWIPRawImpl : public Socket { } protected: + std::string format_ip_address_(const ip_addr_t &ip) { + char buffer[50] = {}; + if (IP_IS_V4_VAL(ip)) { + inet_ntoa_r(ip, buffer, sizeof(buffer)); + } +#if LWIP_IPV6 + else if (IP_IS_V6_VAL(ip)) { + inet6_ntoa_r(ip, buffer, sizeof(buffer)); + } +#endif + return std::string(buffer); + } + int ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) { if (family_ == AF_INET) { if (*addrlen < sizeof(struct sockaddr_in)) { From ba5fa7c10a97c0c62f4d6126f254e165d71cccc6 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Thu, 6 Nov 2025 20:22:50 -0500 Subject: [PATCH 0120/1145] [psram] Add option to disable ignore not found sdkconfig setting (#11411) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/const/__init__.py | 1 + esphome/components/psram/__init__.py | 6 +++++- tests/components/psram/test.esp32-s3-idf.yaml | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/const/__init__.py b/esphome/components/const/__init__.py index 0c22b2d27e..12a69551f5 100644 --- a/esphome/components/const/__init__.py +++ b/esphome/components/const/__init__.py @@ -9,6 +9,7 @@ BYTE_ORDER_BIG = "big_endian" CONF_COLOR_DEPTH = "color_depth" CONF_DRAW_ROUNDING = "draw_rounding" CONF_ENABLED = "enabled" +CONF_IGNORE_NOT_FOUND = "ignore_not_found" CONF_ON_RECEIVE = "on_receive" CONF_ON_STATE_CHANGE = "on_state_change" CONF_REQUEST_HEADERS = "request_headers" diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index df49e08879..11c238c1bf 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -2,6 +2,7 @@ import logging import textwrap import esphome.codegen as cg +from esphome.components.const import CONF_IGNORE_NOT_FOUND from esphome.components.esp32 import ( CONF_CPU_FREQUENCY, CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES, @@ -123,6 +124,7 @@ def get_config_schema(config): cv.Optional(CONF_ENABLE_ECC, default=False): cv.boolean, cv.Optional(CONF_SPEED, default=speeds[0]): cv.one_of(*speeds, upper=True), cv.Optional(CONF_DISABLED, default=False): cv.boolean, + cv.Optional(CONF_IGNORE_NOT_FOUND, default=True): cv.boolean, } )(config) @@ -147,7 +149,9 @@ async def to_code(config): add_idf_sdkconfig_option("CONFIG_SPIRAM", True) add_idf_sdkconfig_option("CONFIG_SPIRAM_USE", True) add_idf_sdkconfig_option("CONFIG_SPIRAM_USE_CAPS_ALLOC", True) - add_idf_sdkconfig_option("CONFIG_SPIRAM_IGNORE_NOTFOUND", True) + add_idf_sdkconfig_option( + "CONFIG_SPIRAM_IGNORE_NOTFOUND", config[CONF_IGNORE_NOT_FOUND] + ) add_idf_sdkconfig_option(f"CONFIG_SPIRAM_MODE_{SDK_MODES[config[CONF_MODE]]}", True) diff --git a/tests/components/psram/test.esp32-s3-idf.yaml b/tests/components/psram/test.esp32-s3-idf.yaml index 75d4ee539c..548b8324d0 100644 --- a/tests/components/psram/test.esp32-s3-idf.yaml +++ b/tests/components/psram/test.esp32-s3-idf.yaml @@ -9,3 +9,4 @@ psram: mode: octal speed: 120MHz enable_ecc: true + ignore_not_found: false From 5d20e3a3b4ab7981d0d17a822493532eef267b75 Mon Sep 17 00:00:00 2001 From: philippderdiedas <56478008+philippderdiedas@users.noreply.github.com> Date: Fri, 7 Nov 2025 02:25:14 +0100 Subject: [PATCH 0121/1145] Add MCP3221 i2c A-D-Converter (#7764) --- CODEOWNERS | 1 + esphome/components/mcp3221/__init__.py | 1 + esphome/components/mcp3221/mcp3221_sensor.cpp | 31 ++++++++++++ esphome/components/mcp3221/mcp3221_sensor.h | 28 +++++++++++ esphome/components/mcp3221/sensor.py | 49 +++++++++++++++++++ tests/components/mcp3221/common.yaml | 6 +++ .../components/mcp3221/test.esp32-c3-idf.yaml | 4 ++ tests/components/mcp3221/test.esp32-idf.yaml | 4 ++ .../components/mcp3221/test.esp32-s3-idf.yaml | 4 ++ .../components/mcp3221/test.esp8266-ard.yaml | 4 ++ tests/components/mcp3221/test.rp2040-ard.yaml | 4 ++ 11 files changed, 136 insertions(+) create mode 100644 esphome/components/mcp3221/__init__.py create mode 100644 esphome/components/mcp3221/mcp3221_sensor.cpp create mode 100644 esphome/components/mcp3221/mcp3221_sensor.h create mode 100644 esphome/components/mcp3221/sensor.py create mode 100644 tests/components/mcp3221/common.yaml create mode 100644 tests/components/mcp3221/test.esp32-c3-idf.yaml create mode 100644 tests/components/mcp3221/test.esp32-idf.yaml create mode 100644 tests/components/mcp3221/test.esp32-s3-idf.yaml create mode 100644 tests/components/mcp3221/test.esp8266-ard.yaml create mode 100644 tests/components/mcp3221/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 4d458eceb8..7e785db451 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -290,6 +290,7 @@ esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp3204/* @rsumner +esphome/components/mcp3221/* @philippderdiedas esphome/components/mcp4461/* @p1ngb4ck esphome/components/mcp4728/* @berfenger esphome/components/mcp47a1/* @jesserockz diff --git a/esphome/components/mcp3221/__init__.py b/esphome/components/mcp3221/__init__.py new file mode 100644 index 0000000000..677bb78c35 --- /dev/null +++ b/esphome/components/mcp3221/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@philippderdiedas"] diff --git a/esphome/components/mcp3221/mcp3221_sensor.cpp b/esphome/components/mcp3221/mcp3221_sensor.cpp new file mode 100644 index 0000000000..c04b1c0b93 --- /dev/null +++ b/esphome/components/mcp3221/mcp3221_sensor.cpp @@ -0,0 +1,31 @@ +#include "mcp3221_sensor.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp3221 { + +static const char *const TAG = "mcp3221"; + +float MCP3221Sensor::sample() { + uint8_t data[2]; + if (this->read(data, 2) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Read failed"); + this->status_set_warning(); + return NAN; + } + this->status_clear_warning(); + + uint16_t value = encode_uint16(data[0], data[1]); + float voltage = value * this->reference_voltage_ / 4096.0f; + + return voltage; +} + +void MCP3221Sensor::update() { + float v = this->sample(); + this->publish_state(v); +} + +} // namespace mcp3221 +} // namespace esphome diff --git a/esphome/components/mcp3221/mcp3221_sensor.h b/esphome/components/mcp3221/mcp3221_sensor.h new file mode 100644 index 0000000000..c83caccabf --- /dev/null +++ b/esphome/components/mcp3221/mcp3221_sensor.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/voltage_sampler/voltage_sampler.h" + +#include + +namespace esphome { +namespace mcp3221 { + +class MCP3221Sensor : public sensor::Sensor, + public PollingComponent, + public voltage_sampler::VoltageSampler, + public i2c::I2CDevice { + public: + void set_reference_voltage(float reference_voltage) { this->reference_voltage_ = reference_voltage; } + void update() override; + float sample() override; + + protected: + float reference_voltage_; +}; + +} // namespace mcp3221 +} // namespace esphome diff --git a/esphome/components/mcp3221/sensor.py b/esphome/components/mcp3221/sensor.py new file mode 100644 index 0000000000..993876c2c8 --- /dev/null +++ b/esphome/components/mcp3221/sensor.py @@ -0,0 +1,49 @@ +import esphome.codegen as cg +from esphome.components import i2c, sensor, voltage_sampler +import esphome.config_validation as cv +from esphome.const import ( + CONF_REFERENCE_VOLTAGE, + DEVICE_CLASS_VOLTAGE, + ICON_SCALE, + STATE_CLASS_MEASUREMENT, + UNIT_VOLT, +) + +AUTO_LOAD = ["voltage_sampler"] +DEPENDENCIES = ["i2c"] + + +mcp3221_ns = cg.esphome_ns.namespace("mcp3221") +MCP3221Sensor = mcp3221_ns.class_( + "MCP3221Sensor", + sensor.Sensor, + voltage_sampler.VoltageSampler, + cg.PollingComponent, + i2c.I2CDevice, +) + + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + MCP3221Sensor, + icon=ICON_SCALE, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + device_class=DEVICE_CLASS_VOLTAGE, + unit_of_measurement=UNIT_VOLT, + ) + .extend( + { + cv.Optional(CONF_REFERENCE_VOLTAGE, default="3.3V"): cv.voltage, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x48)) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + cg.add(var.set_reference_voltage(config[CONF_REFERENCE_VOLTAGE])) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) diff --git a/tests/components/mcp3221/common.yaml b/tests/components/mcp3221/common.yaml new file mode 100644 index 0000000000..cc3eadbf4f --- /dev/null +++ b/tests/components/mcp3221/common.yaml @@ -0,0 +1,6 @@ +sensor: + - platform: mcp3221 + id: test_id + name: voltage + i2c_id: i2c_bus + reference_voltage: 3.3V diff --git a/tests/components/mcp3221/test.esp32-c3-idf.yaml b/tests/components/mcp3221/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..9990d96d29 --- /dev/null +++ b/tests/components/mcp3221/test.esp32-c3-idf.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/mcp3221/test.esp32-idf.yaml b/tests/components/mcp3221/test.esp32-idf.yaml new file mode 100644 index 0000000000..b47e39c389 --- /dev/null +++ b/tests/components/mcp3221/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/mcp3221/test.esp32-s3-idf.yaml b/tests/components/mcp3221/test.esp32-s3-idf.yaml new file mode 100644 index 0000000000..0fd8684a2c --- /dev/null +++ b/tests/components/mcp3221/test.esp32-s3-idf.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/mcp3221/test.esp8266-ard.yaml b/tests/components/mcp3221/test.esp8266-ard.yaml new file mode 100644 index 0000000000..4a98b9388a --- /dev/null +++ b/tests/components/mcp3221/test.esp8266-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/mcp3221/test.rp2040-ard.yaml b/tests/components/mcp3221/test.rp2040-ard.yaml new file mode 100644 index 0000000000..319a7c71a6 --- /dev/null +++ b/tests/components/mcp3221/test.rp2040-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml + +<<: !include common.yaml From d0b399d77167d5ab48e5aeb8fd275eef151ccfb5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 6 Nov 2025 20:44:01 -0600 Subject: [PATCH 0122/1145] [ci] Reduce release time by removing 468 redundant ESP32-C3 IDF tests (#11737) --- .../components/a01nyub/test.esp32-c3-idf.yaml | 8 ------- .../components/a02yyuw/test.esp32-c3-idf.yaml | 8 ------- tests/components/a4988/test.esp32-c3-idf.yaml | 6 ------ .../absolute_humidity/test.esp32-c3-idf.yaml | 1 - .../adc128s102/test.esp32-c3-idf.yaml | 7 ------- .../components/ade7880/test.esp32-c3-idf.yaml | 9 -------- .../ade7953_i2c/test.esp32-c3-idf.yaml | 7 ------- .../ade7953_spi/test.esp32-c3-idf.yaml | 7 ------- .../components/ads1115/test.esp32-c3-idf.yaml | 4 ---- tests/components/ags10/test.esp32-c3-idf.yaml | 4 ---- tests/components/aht10/test.esp32-c3-idf.yaml | 4 ---- .../components/aic3204/test.esp32-c3-idf.yaml | 4 ---- .../test.esp32-c3-idf.yaml | 4 ---- .../test.esp32-c3-idf.yaml | 4 ---- .../test.esp32-c3-idf.yaml | 1 - .../components/alpha3/test.esp32-c3-idf.yaml | 4 ---- .../components/am2315c/test.esp32-c3-idf.yaml | 4 ---- .../components/am2320/test.esp32-c3-idf.yaml | 4 ---- tests/components/am43/test.esp32-c3-idf.yaml | 4 ---- .../analog_threshold/test.esp32-c3-idf.yaml | 1 - .../animation/test.esp32-c3-idf.yaml | 13 ------------ tests/components/anova/test.esp32-c3-idf.yaml | 4 ---- .../apds9306/test.esp32-c3-idf.yaml | 4 ---- .../apds9960/test.esp32-c3-idf.yaml | 4 ---- tests/components/api/test.esp32-c3-idf.yaml | 5 ----- .../as3935_i2c/test.esp32-c3-idf.yaml | 7 ------- .../as3935_spi/test.esp32-c3-idf.yaml | 7 ------- .../components/as5600/test.esp32-c3-idf.yaml | 7 ------- .../components/as7341/test.esp32-c3-idf.yaml | 4 ---- .../components/at581x/test.esp32-c3-idf.yaml | 4 ---- .../atc_mithermometer/test.esp32-c3-idf.yaml | 4 ---- .../atm90e26/test.esp32-c3-idf.yaml | 6 ------ .../atm90e32/test.esp32-c3-idf.yaml | 6 ------ .../axs15231/test.esp32-c3-idf.yaml | 4 ---- .../b_parasite/test.esp32-c3-idf.yaml | 4 ---- .../bang_bang/test.esp32-c3-idf.yaml | 1 - .../components/bedjet/test.esp32-c3-idf.yaml | 4 ---- .../components/bh1750/test.esp32-c3-idf.yaml | 4 ---- .../bh1900nux/test.esp32-c3-idf.yaml | 4 ---- .../binary_sensor/test.esp32-c3-idf.yaml | 2 -- .../binary_sensor_map/test.esp32-c3-idf.yaml | 1 - .../components/bl0906/test.esp32-c3-idf.yaml | 4 ---- .../components/bl0939/test.esp32-c3-idf.yaml | 5 ----- .../components/bl0940/test.esp32-c3-idf.yaml | 5 ----- .../components/bl0942/test.esp32-c3-idf.yaml | 5 ----- .../ble_client/test.esp32-c3-idf.yaml | 4 ---- .../ble_presence/test.esp32-c3-idf.yaml | 4 ---- .../ble_rssi/test.esp32-c3-idf.yaml | 4 ---- .../ble_scanner/test.esp32-c3-idf.yaml | 4 ---- .../bme280_i2c/test.esp32-c3-idf.yaml | 4 ---- .../bme280_spi/test.esp32-c3-idf.yaml | 6 ------ .../components/bme680/test.esp32-c3-idf.yaml | 4 ---- .../bme68x_bsec2_i2c/test.esp32-c3-idf.yaml | 4 ---- .../components/bmi160/test.esp32-c3-idf.yaml | 4 ---- .../components/bmp085/test.esp32-c3-idf.yaml | 4 ---- .../bmp280_i2c/test.esp32-c3-idf.yaml | 4 ---- .../bmp280_spi/test.esp32-c3-idf.yaml | 6 ------ .../bmp3xx_i2c/test.esp32-c3-idf.yaml | 4 ---- .../bmp3xx_spi/test.esp32-c3-idf.yaml | 6 ------ .../components/bmp581/test.esp32-c3-idf.yaml | 4 ---- .../bp1658cj/test.esp32-c3-idf.yaml | 5 ----- .../components/bp5758d/test.esp32-c3-idf.yaml | 5 ----- .../components/button/test.esp32-c3-idf.yaml | 1 - .../bytebuffer/test.esp32-c3-idf.yaml | 1 - .../components/cap1188/test.esp32-c3-idf.yaml | 7 ------- .../captive_portal/test.esp32-c3-idf.yaml | 1 - .../components/ccs811/test.esp32-c3-idf.yaml | 4 ---- .../cd74hc4067/test.esp32-c3-idf.yaml | 8 ------- .../components/ch422g/test.esp32-c3-idf.yaml | 4 ---- .../components/chsc6x/test.esp32-c3-idf.yaml | 20 ------------------ .../climate_ir_lg/test.esp32-c3-idf.yaml | 4 ---- .../components/cm1106/test.esp32-c3-idf.yaml | 5 ----- tests/components/color/test.esp32-c3-idf.yaml | 1 - .../color_temperature/test.esp32-c3-idf.yaml | 6 ------ .../combination/test.esp32-c3-idf.yaml | 1 - .../components/coolix/test.esp32-c3-idf.yaml | 4 ---- tests/components/copy/test.esp32-c3-idf.yaml | 5 ----- .../components/cs5460a/test.esp32-c3-idf.yaml | 6 ------ .../components/cse7761/test.esp32-c3-idf.yaml | 5 ----- .../components/cse7766/test.esp32-c3-idf.yaml | 5 ----- .../components/cst226/test.esp32-c3-idf.yaml | 11 ---------- .../components/cst816/test.esp32-c3-idf.yaml | 11 ---------- .../ct_clamp/test.esp32-c3-idf.yaml | 4 ---- .../current_based/test.esp32-c3-idf.yaml | 7 ------- tests/components/cwww/test.esp32-c3-idf.yaml | 17 --------------- .../components/dac7678/test.esp32-c3-idf.yaml | 4 ---- .../daikin_brc/test.esp32-c3-idf.yaml | 4 ---- .../dallas_temp/test.esp32-c3-idf.yaml | 7 ------- .../daly_bms/test.esp32-c3-idf.yaml | 5 ----- tests/components/debug/test.esp32-c3-idf.yaml | 1 - .../delonghi/test.esp32-c3-idf.yaml | 4 ---- .../dfplayer/test.esp32-c3-idf.yaml | 5 ----- .../dfrobot_sen0395/test.esp32-c3-idf.yaml | 5 ----- tests/components/dht/test.esp32-c3-idf.yaml | 1 - tests/components/dht12/test.esp32-c3-idf.yaml | 4 ---- .../components/dps310/test.esp32-c3-idf.yaml | 4 ---- .../components/ds1307/test.esp32-c3-idf.yaml | 4 ---- .../components/ds2484/test.esp32-c3-idf.yaml | 4 ---- .../duty_cycle/test.esp32-c3-idf.yaml | 1 - .../duty_time/test.esp32-c3-idf.yaml | 1 - tests/components/e131/test.esp32-c3-idf.yaml | 5 ----- tests/components/ee895/test.esp32-c3-idf.yaml | 4 ---- .../ektf2232/test.esp32-c3-idf.yaml | 9 -------- .../components/emc2101/test.esp32-c3-idf.yaml | 4 ---- .../components/endstop/test.esp32-c3-idf.yaml | 1 - .../ens160_i2c/test.esp32-c3-idf.yaml | 4 ---- .../ens160_spi/test.esp32-c3-idf.yaml | 6 ------ .../components/ens210/test.esp32-c3-idf.yaml | 4 ---- .../components/es7210/test.esp32-c3-idf.yaml | 4 ---- .../components/es7243e/test.esp32-c3-idf.yaml | 4 ---- .../components/es8156/test.esp32-c3-idf.yaml | 4 ---- .../components/es8311/test.esp32-c3-idf.yaml | 4 ---- .../components/es8388/test.esp32-c3-idf.yaml | 4 ---- .../components/esphome/test.esp32-c3-idf.yaml | 1 - tests/components/event/test.esp32-c3-idf.yaml | 1 - .../test.esp32-c3-idf.yaml | 4 ---- .../test.esp32-c3-idf.yaml | 1 - tests/components/ezo/test.esp32-c3-idf.yaml | 4 ---- .../components/ezo_pmp/test.esp32-c3-idf.yaml | 4 ---- .../factory_reset/test.esp32-c3-idf.yaml | 1 - .../feedback/test.esp32-c3-idf.yaml | 1 - .../fingerprint_grow/test.esp32-c3-idf.yaml | 6 ------ tests/components/font/test.esp32-c3-idf.yaml | 7 ------- .../components/fs3000/test.esp32-c3-idf.yaml | 4 ---- .../components/ft5x06/test.esp32-c3-idf.yaml | 7 ------- .../components/ft63x6/test.esp32-c3-idf.yaml | 8 ------- .../fujitsu_general/test.esp32-c3-idf.yaml | 4 ---- tests/components/gcja5/test.esp32-c3-idf.yaml | 4 ---- .../gl_r01_i2c/test.esp32-c3-idf.yaml | 4 ---- .../components/globals/test.esp32-c3-idf.yaml | 1 - .../gp2y1010au0f/test.esp32-c3-idf.yaml | 5 ----- .../components/gp8403/test.esp32-c3-idf.yaml | 4 ---- tests/components/gps/test.esp32-c3-idf.yaml | 5 ----- tests/components/graph/test.esp32-c3-idf.yaml | 7 ------- .../test.esp32-c3-idf.yaml | 7 ------- tests/components/gree/test.esp32-c3-idf.yaml | 4 ---- .../grove_gas_mc_v2/test.esp32-c3-idf.yaml | 4 ---- .../grove_tb6612fng/test.esp32-c3-idf.yaml | 4 ---- .../growatt_solar/test.esp32-c3-idf.yaml | 6 ------ tests/components/gt911/test.esp32-c3-idf.yaml | 9 -------- tests/components/haier/test.esp32-c3-idf.yaml | 5 ----- .../havells_solar/test.esp32-c3-idf.yaml | 6 ------ .../components/hbridge/test.esp32-c3-idf.yaml | 17 --------------- .../components/hdc1080/test.esp32-c3-idf.yaml | 4 ---- .../components/hdc2010/test.esp32-c3-idf.yaml | 4 ---- tests/components/he60r/test.esp32-c3-idf.yaml | 4 ---- .../hitachi_ac344/test.esp32-c3-idf.yaml | 4 ---- .../hitachi_ac424/test.esp32-c3-idf.yaml | 4 ---- .../components/hlw8012/test.esp32-c3-idf.yaml | 6 ------ .../components/hm3301/test.esp32-c3-idf.yaml | 4 ---- .../hmc5883l/test.esp32-c3-idf.yaml | 4 ---- .../homeassistant/test.esp32-c3-idf.yaml | 2 -- .../honeywell_hih_i2c/test.esp32-c3-idf.yaml | 4 ---- .../honeywellabp/test.esp32-c3-idf.yaml | 6 ------ .../honeywellabp2_i2c/test.esp32-c3-idf.yaml | 4 ---- .../hrxl_maxsonar_wr/test.esp32-c3-idf.yaml | 5 ----- .../components/hte501/test.esp32-c3-idf.yaml | 4 ---- .../http_request/test.esp32-c3-idf.yaml | 4 ---- .../components/htu21d/test.esp32-c3-idf.yaml | 4 ---- .../components/htu31d/test.esp32-c3-idf.yaml | 4 ---- tests/components/hx711/test.esp32-c3-idf.yaml | 5 ----- .../hydreon_rgxx/test.esp32-c3-idf.yaml | 5 ----- .../components/hyt271/test.esp32-c3-idf.yaml | 4 ---- tests/components/i2c/test.esp32-c3-idf.yaml | 4 ---- .../i2c_device/test.esp32-c3-idf.yaml | 4 ---- .../components/iaqcore/test.esp32-c3-idf.yaml | 4 ---- .../components/ili9xxx/test.esp32-c3-idf.yaml | 11 ---------- .../components/ina219/test.esp32-c3-idf.yaml | 4 ---- .../components/ina226/test.esp32-c3-idf.yaml | 4 ---- .../components/ina260/test.esp32-c3-idf.yaml | 4 ---- .../ina2xx_i2c/test.esp32-c3-idf.yaml | 4 ---- .../ina2xx_spi/test.esp32-c3-idf.yaml | 6 ------ .../components/ina3221/test.esp32-c3-idf.yaml | 4 ---- .../test.esp32-c3-idf.yaml | 4 ---- .../integration/test.esp32-c3-idf.yaml | 4 ---- .../interval/test.esp32-c3-idf.yaml | 1 - .../jsn_sr04t/test.esp32-c3-idf.yaml | 5 ----- .../key_collector/test.esp32-c3-idf.yaml | 7 ------- .../kmeteriso/test.esp32-c3-idf.yaml | 4 ---- .../components/kuntze/test.esp32-c3-idf.yaml | 6 ------ .../lc709203f/test.esp32-c3-idf.yaml | 4 ---- .../lcd_gpio/test.esp32-c3-idf.yaml | 9 -------- .../lcd_menu/test.esp32-c3-idf.yaml | 9 -------- .../lcd_pcf8574/test.esp32-c3-idf.yaml | 4 ---- .../components/ld2410/test.esp32-c3-idf.yaml | 8 ------- .../components/ld2412/test.esp32-c3-idf.yaml | 8 ------- .../components/ld2420/test.esp32-c3-idf.yaml | 5 ----- .../components/ld2450/test.esp32-c3-idf.yaml | 8 ------- tests/components/light/test.esp32-c3-idf.yaml | 21 ------------------- .../lilygo_t5_47/test.esp32-c3-idf.yaml | 8 ------- tests/components/lm75b/test.esp32-c3-idf.yaml | 4 ---- tests/components/lock/test.esp32-c3-idf.yaml | 1 - tests/components/lps22/test.esp32-c3-idf.yaml | 4 ---- .../components/ltr390/test.esp32-c3-idf.yaml | 4 ---- .../components/ltr501/test.esp32-c3-idf.yaml | 4 ---- .../ltr_als_ps/test.esp32-c3-idf.yaml | 4 ---- .../m5stack_8angle/test.esp32-c3-idf.yaml | 4 ---- .../components/mapping/test.esp32-c3-idf.yaml | 13 ------------ .../matrix_keypad/test.esp32-c3-idf.yaml | 15 ------------- .../max17043/test.esp32-c3-idf.yaml | 4 ---- .../max31855/test.esp32-c3-idf.yaml | 6 ------ .../max31856/test.esp32-c3-idf.yaml | 6 ------ .../max31865/test.esp32-c3-idf.yaml | 6 ------ .../max44009/test.esp32-c3-idf.yaml | 4 ---- .../components/max6675/test.esp32-c3-idf.yaml | 6 ------ .../components/max6956/test.esp32-c3-idf.yaml | 4 ---- .../components/max7219/test.esp32-c3-idf.yaml | 6 ------ .../max7219digit/test.esp32-c3-idf.yaml | 6 ------ .../components/max9611/test.esp32-c3-idf.yaml | 4 ---- .../mcp23008/test.esp32-c3-idf.yaml | 4 ---- .../mcp23016/test.esp32-c3-idf.yaml | 4 ---- .../mcp23017/test.esp32-c3-idf.yaml | 4 ---- .../mcp23s08/test.esp32-c3-idf.yaml | 7 ------- .../mcp23s17/test.esp32-c3-idf.yaml | 7 ------- .../components/mcp2515/test.esp32-c3-idf.yaml | 6 ------ .../components/mcp3008/test.esp32-c3-idf.yaml | 6 ------ .../components/mcp3204/test.esp32-c3-idf.yaml | 6 ------ .../components/mcp4461/test.esp32-c3-idf.yaml | 4 ---- .../components/mcp4725/test.esp32-c3-idf.yaml | 4 ---- .../components/mcp4728/test.esp32-c3-idf.yaml | 4 ---- .../components/mcp47a1/test.esp32-c3-idf.yaml | 4 ---- .../components/mcp9600/test.esp32-c3-idf.yaml | 4 ---- .../components/mcp9808/test.esp32-c3-idf.yaml | 4 ---- .../mdns/test-enabled.esp32-c3-idf.yaml | 1 - tests/components/mhz19/test.esp32-c3-idf.yaml | 5 ----- .../micronova/test.esp32-c3-idf.yaml | 7 ------- .../microphone/test.esp32-c3-idf.yaml | 7 ------- .../mics_4514/test.esp32-c3-idf.yaml | 4 ---- .../midea_ir/test.esp32-c3-idf.yaml | 4 ---- .../mitsubishi/test.esp32-c3-idf.yaml | 4 ---- tests/components/mixer/test.esp32-c3-idf.yaml | 7 ------- .../mlx90393/test.esp32-c3-idf.yaml | 4 ---- .../mlx90614/test.esp32-c3-idf.yaml | 4 ---- .../components/mmc5603/test.esp32-c3-idf.yaml | 4 ---- .../components/mmc5983/test.esp32-c3-idf.yaml | 4 ---- .../components/modbus/test.esp32-c3-idf.yaml | 6 ------ .../modbus_controller/test.esp32-c3-idf.yaml | 4 ---- .../monochromatic/test.esp32-c3-idf.yaml | 5 ----- .../mopeka_ble/test.esp32-c3-idf.yaml | 4 ---- .../mopeka_pro_check/test.esp32-c3-idf.yaml | 4 ---- .../mopeka_std_check/test.esp32-c3-idf.yaml | 4 ---- .../mpl3115a2/test.esp32-c3-idf.yaml | 4 ---- .../components/mpr121/test.esp32-c3-idf.yaml | 8 ------- .../components/mpu6050/test.esp32-c3-idf.yaml | 4 ---- .../components/mpu6886/test.esp32-c3-idf.yaml | 4 ---- tests/components/mqtt/test.esp32-c3-idf.yaml | 3 --- .../mqtt_subscribe/test.esp32-c3-idf.yaml | 1 - .../components/ms5611/test.esp32-c3-idf.yaml | 8 ------- .../components/msa3xx/test.esp32-c3-idf.yaml | 4 ---- .../components/my9231/test.esp32-c3-idf.yaml | 1 - .../components/nau7802/test.esp32-c3-idf.yaml | 4 ---- .../network/test-ipv6.esp32-c3-idf.yaml | 4 ---- .../components/network/test.esp32-c3-idf.yaml | 1 - .../components/nextion/test.esp32-c3-idf.yaml | 7 ------- .../components/noblex/test.esp32-c3-idf.yaml | 5 ----- tests/components/ntc/test.esp32-c3-idf.yaml | 4 ---- .../components/opt3001/test.esp32-c3-idf.yaml | 4 ---- tests/components/ota/test.esp32-c3-idf.yaml | 1 - .../components/output/test.esp32-c3-idf.yaml | 5 ----- .../packet_transport/test.esp32-c3-idf.yaml | 4 ---- .../pca6416a/test.esp32-c3-idf.yaml | 4 ---- .../components/pca9554/test.esp32-c3-idf.yaml | 4 ---- .../components/pca9685/test.esp32-c3-idf.yaml | 4 ---- .../components/pcd8544/test.esp32-c3-idf.yaml | 8 ------- .../pcf85063/test.esp32-c3-idf.yaml | 4 ---- .../components/pcf8563/test.esp32-c3-idf.yaml | 4 ---- .../components/pcf8574/test.esp32-c3-idf.yaml | 4 ---- tests/components/pid/test.esp32-c3-idf.yaml | 1 - .../pipsolar/test.esp32-c3-idf.yaml | 5 ----- .../components/pm1006/test.esp32-c3-idf.yaml | 5 ----- .../components/pm2005/test.esp32-c3-idf.yaml | 4 ---- .../pmsa003i/test.esp32-c3-idf.yaml | 4 ---- .../components/pmsx003/test.esp32-c3-idf.yaml | 5 ----- .../components/pmwcs3/test.esp32-c3-idf.yaml | 4 ---- .../pn532_i2c/test.esp32-c3-idf.yaml | 4 ---- .../pn532_spi/test.esp32-c3-idf.yaml | 6 ------ .../pn7150_i2c/test.esp32-c3-idf.yaml | 8 ------- .../pn7160_i2c/test.esp32-c3-idf.yaml | 8 ------- .../pn7160_spi/test.esp32-c3-idf.yaml | 8 ------- .../power_supply/test.esp32-c3-idf.yaml | 1 - .../prometheus/test.esp32-c3-idf.yaml | 7 ------- .../pulse_meter/test.esp32-c3-idf.yaml | 1 - .../pulse_width/test.esp32-c3-idf.yaml | 1 - .../pvvx_mithermometer/test.esp32-c3-idf.yaml | 4 ---- .../pylontech/test.esp32-c3-idf.yaml | 5 ----- .../pzem004t/test.esp32-c3-idf.yaml | 5 ----- .../components/pzemac/test.esp32-c3-idf.yaml | 5 ----- .../components/pzemdc/test.esp32-c3-idf.yaml | 5 ----- .../qmc5883l/test.esp32-c3-idf.yaml | 7 ------- .../components/qmp6988/test.esp32-c3-idf.yaml | 4 ---- .../components/qr_code/test.esp32-c3-idf.yaml | 8 ------- .../qwiic_pir/test.esp32-c3-idf.yaml | 4 ---- .../radon_eye_ble/test.esp32-c3-idf.yaml | 4 ---- .../radon_eye_rd200/test.esp32-c3-idf.yaml | 4 ---- .../rc522_i2c/test.esp32-c3-idf.yaml | 4 ---- .../rc522_spi/test.esp32-c3-idf.yaml | 6 ------ .../components/rdm6300/test.esp32-c3-idf.yaml | 5 ----- .../resampler/test.esp32-c3-idf.yaml | 7 ------- .../resistance/test.esp32-c3-idf.yaml | 4 ---- .../components/restart/test.esp32-c3-idf.yaml | 1 - .../rf_bridge/test.esp32-c3-idf.yaml | 4 ---- tests/components/rgb/test.esp32-c3-idf.yaml | 7 ------- tests/components/rgbct/test.esp32-c3-idf.yaml | 9 -------- tests/components/rgbw/test.esp32-c3-idf.yaml | 8 ------- tests/components/rgbww/test.esp32-c3-idf.yaml | 9 -------- .../rotary_encoder/test.esp32-c3-idf.yaml | 6 ------ tests/components/rtttl/test.esp32-c3-idf.yaml | 5 ----- .../ruuvi_ble/test.esp32-c3-idf.yaml | 4 ---- .../ruuvitag/test.esp32-c3-idf.yaml | 4 ---- .../safe_mode/test-enabled.esp32-c3-idf.yaml | 1 - tests/components/scd30/test.esp32-c3-idf.yaml | 4 ---- tests/components/scd4x/test.esp32-c3-idf.yaml | 4 ---- .../components/script/test.esp32-c3-idf.yaml | 1 - .../sdm_meter/test.esp32-c3-idf.yaml | 5 ----- tests/components/sdp3x/test.esp32-c3-idf.yaml | 4 ---- .../components/sds011/test.esp32-c3-idf.yaml | 5 ----- .../seeed_mr24hpc1/test.esp32-c3-idf.yaml | 4 ---- .../seeed_mr60bha2/test.esp32-c3-idf.yaml | 4 ---- .../seeed_mr60fda2/test.esp32-c3-idf.yaml | 4 ---- .../selec_meter/test.esp32-c3-idf.yaml | 7 ------- .../components/sen0321/test.esp32-c3-idf.yaml | 4 ---- .../sen21231/test.esp32-c3-idf.yaml | 4 ---- tests/components/sen5x/test.esp32-c3-idf.yaml | 4 ---- .../senseair/test.esp32-c3-idf.yaml | 5 ----- tests/components/servo/test.esp32-c3-idf.yaml | 5 ----- tests/components/sfa30/test.esp32-c3-idf.yaml | 4 ---- tests/components/sgp30/test.esp32-c3-idf.yaml | 4 ---- tests/components/sgp4x/test.esp32-c3-idf.yaml | 4 ---- .../components/sht3xd/test.esp32-c3-idf.yaml | 4 ---- tests/components/sht4x/test.esp32-c3-idf.yaml | 4 ---- tests/components/shtcx/test.esp32-c3-idf.yaml | 4 ---- .../shutdown/test.esp32-c3-idf.yaml | 1 - .../components/sim800l/test.esp32-c3-idf.yaml | 5 ----- .../slow_pwm/test.esp32-c3-idf.yaml | 1 - .../components/sm16716/test.esp32-c3-idf.yaml | 5 ----- .../components/sm2135/test.esp32-c3-idf.yaml | 5 ----- .../components/sm2235/test.esp32-c3-idf.yaml | 5 ----- .../components/sm2335/test.esp32-c3-idf.yaml | 5 ----- .../components/sm300d2/test.esp32-c3-idf.yaml | 5 ----- tests/components/sml/test.esp32-c3-idf.yaml | 5 ----- .../components/smt100/test.esp32-c3-idf.yaml | 5 ----- .../sn74hc165/test.esp32-c3-idf.yaml | 7 ------- .../sn74hc595/test.esp32-c3-idf.yaml | 12 ----------- tests/components/sntp/test.esp32-c3-idf.yaml | 1 - .../sonoff_d1/test.esp32-c3-idf.yaml | 4 ---- .../sound_level/test.esp32-c3-idf.yaml | 6 ------ .../speaker/audio_dac.esp32-c3-idf.yaml | 10 --------- .../components/speaker/test.esp32-c3-idf.yaml | 12 ----------- tests/components/speed/test.esp32-c3-idf.yaml | 5 ----- .../spi_device/test.esp32-c3-idf.yaml | 4 ---- .../spi_led_strip/test.esp32-c3-idf.yaml | 4 ---- .../sprinkler/test.esp32-c3-idf.yaml | 1 - tests/components/sps30/test.esp32-c3-idf.yaml | 4 ---- .../ssd1306_i2c/test.esp32-c3-idf.yaml | 7 ------- .../ssd1306_spi/test.esp32-c3-idf.yaml | 8 ------- .../ssd1322_spi/test.esp32-c3-idf.yaml | 8 ------- .../ssd1325_spi/test.esp32-c3-idf.yaml | 8 ------- .../ssd1327_i2c/test.esp32-c3-idf.yaml | 7 ------- .../ssd1327_spi/test.esp32-c3-idf.yaml | 8 ------- .../ssd1331_spi/test.esp32-c3-idf.yaml | 8 ------- .../ssd1351_spi/test.esp32-c3-idf.yaml | 8 ------- .../st7567_i2c/test.esp32-c3-idf.yaml | 7 ------- .../st7567_spi/test.esp32-c3-idf.yaml | 8 ------- .../components/st7735/test.esp32-c3-idf.yaml | 8 ------- .../components/st7789v/test.esp32-c3-idf.yaml | 10 --------- .../components/st7920/test.esp32-c3-idf.yaml | 6 ------ .../components/statsD/test.esp32-c3-idf.yaml | 2 -- .../components/status/test.esp32-c3-idf.yaml | 1 - .../status_led/test.esp32-c3-idf.yaml | 1 - .../components/stepper/test.esp32-c3-idf.yaml | 1 - tests/components/sts3x/test.esp32-c3-idf.yaml | 4 ---- tests/components/sun/test.esp32-c3-idf.yaml | 1 - .../sun_gtil2/test.esp32-c3-idf.yaml | 5 ----- .../components/switch/test.esp32-c3-idf.yaml | 2 -- .../components/sx126x/test.esp32-c3-idf.yaml | 10 --------- .../components/sx127x/test.esp32-c3-idf.yaml | 8 ------- .../components/sx1509/test.esp32-c3-idf.yaml | 4 ---- .../components/syslog/test.esp32-c3-idf.yaml | 1 - tests/components/t6615/test.esp32-c3-idf.yaml | 4 ---- tests/components/tc74/test.esp32-c3-idf.yaml | 4 ---- .../tca9548a/test.esp32-c3-idf.yaml | 4 ---- .../components/tca9555/test.esp32-c3-idf.yaml | 4 ---- .../components/tcl112/test.esp32-c3-idf.yaml | 4 ---- .../tcs34725/test.esp32-c3-idf.yaml | 4 ---- .../components/tee501/test.esp32-c3-idf.yaml | 4 ---- .../teleinfo/test.esp32-c3-idf.yaml | 5 ----- .../template/test.esp32-c3-idf.yaml | 2 -- .../thermostat/test.esp32-c3-idf.yaml | 1 - tests/components/time/test.esp32-c3-idf.yaml | 1 - .../time_based/test.esp32-c3-idf.yaml | 1 - .../tlc59208f/test.esp32-c3-idf.yaml | 4 ---- .../components/tlc5947/test.esp32-c3-idf.yaml | 7 ------- .../components/tlc5971/test.esp32-c3-idf.yaml | 6 ------ .../components/tm1621/test.esp32-c3-idf.yaml | 7 ------- .../components/tm1637/test.esp32-c3-idf.yaml | 5 ----- .../components/tm1638/test.esp32-c3-idf.yaml | 1 - .../components/tm1651/test.esp32-c3-idf.yaml | 1 - .../components/tmp102/test.esp32-c3-idf.yaml | 4 ---- .../components/tmp1075/test.esp32-c3-idf.yaml | 4 ---- .../components/tmp117/test.esp32-c3-idf.yaml | 4 ---- .../tof10120/test.esp32-c3-idf.yaml | 4 ---- .../tormatic/test.esp32-c3-idf.yaml | 5 ----- .../components/toshiba/test.esp32-c3-idf.yaml | 4 ---- .../toshiba/test_ras2819t.esp32-c3-idf.yaml | 5 ----- .../total_daily_energy/test.esp32-c3-idf.yaml | 6 ------ .../components/tsl2561/test.esp32-c3-idf.yaml | 4 ---- .../components/tsl2591/test.esp32-c3-idf.yaml | 4 ---- .../components/tt21100/test.esp32-c3-idf.yaml | 9 -------- .../ttp229_bsf/test.esp32-c3-idf.yaml | 8 ------- .../ttp229_lsf/test.esp32-c3-idf.yaml | 4 ---- tests/components/tuya/test.esp32-c3-idf.yaml | 6 ------ tests/components/tx20/test.esp32-c3-idf.yaml | 1 - tests/components/udp/test.esp32-c3-idf.yaml | 4 ---- .../ufire_ec/test.esp32-c3-idf.yaml | 4 ---- .../ufire_ise/test.esp32-c3-idf.yaml | 4 ---- .../components/uln2003/test.esp32-c3-idf.yaml | 7 ------- .../ultrasonic/test.esp32-c3-idf.yaml | 1 - .../uponor_smatrix/test.esp32-c3-idf.yaml | 5 ----- .../components/uptime/test.esp32-c3-idf.yaml | 1 - tests/components/vbus/test.esp32-c3-idf.yaml | 4 ---- .../veml3235/test.esp32-c3-idf.yaml | 4 ---- .../veml7700/test.esp32-c3-idf.yaml | 4 ---- .../components/version/test.esp32-c3-idf.yaml | 1 - .../components/vl53l0x/test.esp32-c3-idf.yaml | 4 ---- .../voice_assistant/test.esp32-c3-idf.yaml | 8 ------- .../wake_on_lan/test.esp32-c3-idf.yaml | 4 ---- .../waveshare_epaper/test.esp32-c3-idf.yaml | 9 -------- .../web_server/test.esp32-c3-idf.yaml | 1 - .../whirlpool/test.esp32-c3-idf.yaml | 4 ---- .../components/whynter/test.esp32-c3-idf.yaml | 4 ---- .../components/wiegand/test.esp32-c3-idf.yaml | 1 - tests/components/wifi/test.esp32-c3-idf.yaml | 1 - .../wifi_info/test.esp32-c3-idf.yaml | 4 ---- .../wifi_signal/test.esp32-c3-idf.yaml | 1 - .../wireguard/test.esp32-c3-idf.yaml | 4 ---- .../components/wl_134/test.esp32-c3-idf.yaml | 5 ----- tests/components/wts01/test.esp32-c3-idf.yaml | 5 ----- tests/components/x9c/test.esp32-c3-idf.yaml | 6 ------ .../xgzp68xx/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_ble/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_cgd1/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_cgdk2/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_cgg1/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_cgpr1/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_gcls002/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_hhccjcy01/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_hhccpot002/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_jqjcy01ym/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_lywsd02/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_lywsd02mmc/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_lywsd03mmc/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_lywsdcgq/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_mhoc303/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_mhoc401/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_miscale/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_mjyd02yla/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_mue4094rt/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_rtcgq02lm/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_wx08zm/test.esp32-c3-idf.yaml | 4 ---- .../xiaomi_xmwsdj04mmc/test.esp32-c3-idf.yaml | 4 ---- .../components/xl9535/test.esp32-c3-idf.yaml | 4 ---- .../components/xpt2046/test.esp32-c3-idf.yaml | 10 --------- .../components/yashima/test.esp32-c3-idf.yaml | 4 ---- .../components/zhlt01/test.esp32-c3-idf.yaml | 4 ---- .../zio_ultrasonic/test.esp32-c3-idf.yaml | 4 ---- .../zwave_proxy/test.esp32-c3-idf.yaml | 5 ----- .../components/zyaura/test.esp32-c3-idf.yaml | 5 ----- 467 files changed, 2212 deletions(-) delete mode 100644 tests/components/a01nyub/test.esp32-c3-idf.yaml delete mode 100644 tests/components/a02yyuw/test.esp32-c3-idf.yaml delete mode 100644 tests/components/a4988/test.esp32-c3-idf.yaml delete mode 100644 tests/components/absolute_humidity/test.esp32-c3-idf.yaml delete mode 100644 tests/components/adc128s102/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ade7880/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ade7953_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ade7953_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ads1115/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ags10/test.esp32-c3-idf.yaml delete mode 100644 tests/components/aht10/test.esp32-c3-idf.yaml delete mode 100644 tests/components/aic3204/test.esp32-c3-idf.yaml delete mode 100644 tests/components/airthings_wave_mini/test.esp32-c3-idf.yaml delete mode 100644 tests/components/airthings_wave_plus/test.esp32-c3-idf.yaml delete mode 100644 tests/components/alarm_control_panel/test.esp32-c3-idf.yaml delete mode 100644 tests/components/alpha3/test.esp32-c3-idf.yaml delete mode 100644 tests/components/am2315c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/am2320/test.esp32-c3-idf.yaml delete mode 100644 tests/components/am43/test.esp32-c3-idf.yaml delete mode 100644 tests/components/analog_threshold/test.esp32-c3-idf.yaml delete mode 100644 tests/components/animation/test.esp32-c3-idf.yaml delete mode 100644 tests/components/anova/test.esp32-c3-idf.yaml delete mode 100644 tests/components/apds9306/test.esp32-c3-idf.yaml delete mode 100644 tests/components/apds9960/test.esp32-c3-idf.yaml delete mode 100644 tests/components/api/test.esp32-c3-idf.yaml delete mode 100644 tests/components/as3935_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/as3935_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/as5600/test.esp32-c3-idf.yaml delete mode 100644 tests/components/as7341/test.esp32-c3-idf.yaml delete mode 100644 tests/components/at581x/test.esp32-c3-idf.yaml delete mode 100644 tests/components/atc_mithermometer/test.esp32-c3-idf.yaml delete mode 100644 tests/components/atm90e26/test.esp32-c3-idf.yaml delete mode 100644 tests/components/atm90e32/test.esp32-c3-idf.yaml delete mode 100644 tests/components/axs15231/test.esp32-c3-idf.yaml delete mode 100644 tests/components/b_parasite/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bang_bang/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bedjet/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bh1750/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bh1900nux/test.esp32-c3-idf.yaml delete mode 100644 tests/components/binary_sensor/test.esp32-c3-idf.yaml delete mode 100644 tests/components/binary_sensor_map/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bl0906/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bl0939/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bl0940/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bl0942/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ble_client/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ble_presence/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ble_rssi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ble_scanner/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bme280_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bme280_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bme680/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bme68x_bsec2_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bmi160/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bmp085/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bmp280_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bmp280_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bmp3xx_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bmp3xx_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bmp581/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bp1658cj/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bp5758d/test.esp32-c3-idf.yaml delete mode 100644 tests/components/button/test.esp32-c3-idf.yaml delete mode 100644 tests/components/bytebuffer/test.esp32-c3-idf.yaml delete mode 100644 tests/components/cap1188/test.esp32-c3-idf.yaml delete mode 100644 tests/components/captive_portal/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ccs811/test.esp32-c3-idf.yaml delete mode 100644 tests/components/cd74hc4067/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ch422g/test.esp32-c3-idf.yaml delete mode 100644 tests/components/chsc6x/test.esp32-c3-idf.yaml delete mode 100644 tests/components/climate_ir_lg/test.esp32-c3-idf.yaml delete mode 100644 tests/components/cm1106/test.esp32-c3-idf.yaml delete mode 100644 tests/components/color/test.esp32-c3-idf.yaml delete mode 100644 tests/components/color_temperature/test.esp32-c3-idf.yaml delete mode 100644 tests/components/combination/test.esp32-c3-idf.yaml delete mode 100644 tests/components/coolix/test.esp32-c3-idf.yaml delete mode 100644 tests/components/copy/test.esp32-c3-idf.yaml delete mode 100644 tests/components/cs5460a/test.esp32-c3-idf.yaml delete mode 100644 tests/components/cse7761/test.esp32-c3-idf.yaml delete mode 100644 tests/components/cse7766/test.esp32-c3-idf.yaml delete mode 100644 tests/components/cst226/test.esp32-c3-idf.yaml delete mode 100644 tests/components/cst816/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ct_clamp/test.esp32-c3-idf.yaml delete mode 100644 tests/components/current_based/test.esp32-c3-idf.yaml delete mode 100644 tests/components/cwww/test.esp32-c3-idf.yaml delete mode 100644 tests/components/dac7678/test.esp32-c3-idf.yaml delete mode 100644 tests/components/daikin_brc/test.esp32-c3-idf.yaml delete mode 100644 tests/components/dallas_temp/test.esp32-c3-idf.yaml delete mode 100644 tests/components/daly_bms/test.esp32-c3-idf.yaml delete mode 100644 tests/components/debug/test.esp32-c3-idf.yaml delete mode 100644 tests/components/delonghi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/dfplayer/test.esp32-c3-idf.yaml delete mode 100644 tests/components/dfrobot_sen0395/test.esp32-c3-idf.yaml delete mode 100644 tests/components/dht/test.esp32-c3-idf.yaml delete mode 100644 tests/components/dht12/test.esp32-c3-idf.yaml delete mode 100644 tests/components/dps310/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ds1307/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ds2484/test.esp32-c3-idf.yaml delete mode 100644 tests/components/duty_cycle/test.esp32-c3-idf.yaml delete mode 100644 tests/components/duty_time/test.esp32-c3-idf.yaml delete mode 100644 tests/components/e131/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ee895/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ektf2232/test.esp32-c3-idf.yaml delete mode 100644 tests/components/emc2101/test.esp32-c3-idf.yaml delete mode 100644 tests/components/endstop/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ens160_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ens160_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ens210/test.esp32-c3-idf.yaml delete mode 100644 tests/components/es7210/test.esp32-c3-idf.yaml delete mode 100644 tests/components/es7243e/test.esp32-c3-idf.yaml delete mode 100644 tests/components/es8156/test.esp32-c3-idf.yaml delete mode 100644 tests/components/es8311/test.esp32-c3-idf.yaml delete mode 100644 tests/components/es8388/test.esp32-c3-idf.yaml delete mode 100644 tests/components/esphome/test.esp32-c3-idf.yaml delete mode 100644 tests/components/event/test.esp32-c3-idf.yaml delete mode 100644 tests/components/exposure_notifications/test.esp32-c3-idf.yaml delete mode 100644 tests/components/external_components/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ezo/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ezo_pmp/test.esp32-c3-idf.yaml delete mode 100644 tests/components/factory_reset/test.esp32-c3-idf.yaml delete mode 100644 tests/components/feedback/test.esp32-c3-idf.yaml delete mode 100644 tests/components/fingerprint_grow/test.esp32-c3-idf.yaml delete mode 100644 tests/components/font/test.esp32-c3-idf.yaml delete mode 100644 tests/components/fs3000/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ft5x06/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ft63x6/test.esp32-c3-idf.yaml delete mode 100644 tests/components/fujitsu_general/test.esp32-c3-idf.yaml delete mode 100644 tests/components/gcja5/test.esp32-c3-idf.yaml delete mode 100644 tests/components/gl_r01_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/globals/test.esp32-c3-idf.yaml delete mode 100644 tests/components/gp2y1010au0f/test.esp32-c3-idf.yaml delete mode 100644 tests/components/gp8403/test.esp32-c3-idf.yaml delete mode 100644 tests/components/gps/test.esp32-c3-idf.yaml delete mode 100644 tests/components/graph/test.esp32-c3-idf.yaml delete mode 100644 tests/components/graphical_display_menu/test.esp32-c3-idf.yaml delete mode 100644 tests/components/gree/test.esp32-c3-idf.yaml delete mode 100644 tests/components/grove_gas_mc_v2/test.esp32-c3-idf.yaml delete mode 100644 tests/components/grove_tb6612fng/test.esp32-c3-idf.yaml delete mode 100644 tests/components/growatt_solar/test.esp32-c3-idf.yaml delete mode 100644 tests/components/gt911/test.esp32-c3-idf.yaml delete mode 100644 tests/components/haier/test.esp32-c3-idf.yaml delete mode 100644 tests/components/havells_solar/test.esp32-c3-idf.yaml delete mode 100644 tests/components/hbridge/test.esp32-c3-idf.yaml delete mode 100644 tests/components/hdc1080/test.esp32-c3-idf.yaml delete mode 100644 tests/components/hdc2010/test.esp32-c3-idf.yaml delete mode 100644 tests/components/he60r/test.esp32-c3-idf.yaml delete mode 100644 tests/components/hitachi_ac344/test.esp32-c3-idf.yaml delete mode 100644 tests/components/hitachi_ac424/test.esp32-c3-idf.yaml delete mode 100644 tests/components/hlw8012/test.esp32-c3-idf.yaml delete mode 100644 tests/components/hm3301/test.esp32-c3-idf.yaml delete mode 100644 tests/components/hmc5883l/test.esp32-c3-idf.yaml delete mode 100644 tests/components/homeassistant/test.esp32-c3-idf.yaml delete mode 100644 tests/components/honeywell_hih_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/honeywellabp/test.esp32-c3-idf.yaml delete mode 100644 tests/components/honeywellabp2_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/hrxl_maxsonar_wr/test.esp32-c3-idf.yaml delete mode 100644 tests/components/hte501/test.esp32-c3-idf.yaml delete mode 100644 tests/components/http_request/test.esp32-c3-idf.yaml delete mode 100644 tests/components/htu21d/test.esp32-c3-idf.yaml delete mode 100644 tests/components/htu31d/test.esp32-c3-idf.yaml delete mode 100644 tests/components/hx711/test.esp32-c3-idf.yaml delete mode 100644 tests/components/hydreon_rgxx/test.esp32-c3-idf.yaml delete mode 100644 tests/components/hyt271/test.esp32-c3-idf.yaml delete mode 100644 tests/components/i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/i2c_device/test.esp32-c3-idf.yaml delete mode 100644 tests/components/iaqcore/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ili9xxx/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ina219/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ina226/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ina260/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ina2xx_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ina2xx_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ina3221/test.esp32-c3-idf.yaml delete mode 100644 tests/components/inkbird_ibsth1_mini/test.esp32-c3-idf.yaml delete mode 100644 tests/components/integration/test.esp32-c3-idf.yaml delete mode 100644 tests/components/interval/test.esp32-c3-idf.yaml delete mode 100644 tests/components/jsn_sr04t/test.esp32-c3-idf.yaml delete mode 100644 tests/components/key_collector/test.esp32-c3-idf.yaml delete mode 100644 tests/components/kmeteriso/test.esp32-c3-idf.yaml delete mode 100644 tests/components/kuntze/test.esp32-c3-idf.yaml delete mode 100644 tests/components/lc709203f/test.esp32-c3-idf.yaml delete mode 100644 tests/components/lcd_gpio/test.esp32-c3-idf.yaml delete mode 100644 tests/components/lcd_menu/test.esp32-c3-idf.yaml delete mode 100644 tests/components/lcd_pcf8574/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ld2410/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ld2412/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ld2420/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ld2450/test.esp32-c3-idf.yaml delete mode 100644 tests/components/light/test.esp32-c3-idf.yaml delete mode 100644 tests/components/lilygo_t5_47/test.esp32-c3-idf.yaml delete mode 100644 tests/components/lm75b/test.esp32-c3-idf.yaml delete mode 100644 tests/components/lock/test.esp32-c3-idf.yaml delete mode 100644 tests/components/lps22/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ltr390/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ltr501/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ltr_als_ps/test.esp32-c3-idf.yaml delete mode 100644 tests/components/m5stack_8angle/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mapping/test.esp32-c3-idf.yaml delete mode 100644 tests/components/matrix_keypad/test.esp32-c3-idf.yaml delete mode 100644 tests/components/max17043/test.esp32-c3-idf.yaml delete mode 100644 tests/components/max31855/test.esp32-c3-idf.yaml delete mode 100644 tests/components/max31856/test.esp32-c3-idf.yaml delete mode 100644 tests/components/max31865/test.esp32-c3-idf.yaml delete mode 100644 tests/components/max44009/test.esp32-c3-idf.yaml delete mode 100644 tests/components/max6675/test.esp32-c3-idf.yaml delete mode 100644 tests/components/max6956/test.esp32-c3-idf.yaml delete mode 100644 tests/components/max7219/test.esp32-c3-idf.yaml delete mode 100644 tests/components/max7219digit/test.esp32-c3-idf.yaml delete mode 100644 tests/components/max9611/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp23008/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp23016/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp23017/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp23s08/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp23s17/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp2515/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp3008/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp3204/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp4461/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp4725/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp4728/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp47a1/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp9600/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mcp9808/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mdns/test-enabled.esp32-c3-idf.yaml delete mode 100644 tests/components/mhz19/test.esp32-c3-idf.yaml delete mode 100644 tests/components/micronova/test.esp32-c3-idf.yaml delete mode 100644 tests/components/microphone/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mics_4514/test.esp32-c3-idf.yaml delete mode 100644 tests/components/midea_ir/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mitsubishi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mixer/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mlx90393/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mlx90614/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mmc5603/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mmc5983/test.esp32-c3-idf.yaml delete mode 100644 tests/components/modbus/test.esp32-c3-idf.yaml delete mode 100644 tests/components/modbus_controller/test.esp32-c3-idf.yaml delete mode 100644 tests/components/monochromatic/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mopeka_ble/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mopeka_pro_check/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mopeka_std_check/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mpl3115a2/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mpr121/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mpu6050/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mpu6886/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mqtt/test.esp32-c3-idf.yaml delete mode 100644 tests/components/mqtt_subscribe/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ms5611/test.esp32-c3-idf.yaml delete mode 100644 tests/components/msa3xx/test.esp32-c3-idf.yaml delete mode 100644 tests/components/my9231/test.esp32-c3-idf.yaml delete mode 100644 tests/components/nau7802/test.esp32-c3-idf.yaml delete mode 100644 tests/components/network/test-ipv6.esp32-c3-idf.yaml delete mode 100644 tests/components/network/test.esp32-c3-idf.yaml delete mode 100644 tests/components/nextion/test.esp32-c3-idf.yaml delete mode 100644 tests/components/noblex/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ntc/test.esp32-c3-idf.yaml delete mode 100644 tests/components/opt3001/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ota/test.esp32-c3-idf.yaml delete mode 100644 tests/components/output/test.esp32-c3-idf.yaml delete mode 100644 tests/components/packet_transport/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pca6416a/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pca9554/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pca9685/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pcd8544/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pcf85063/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pcf8563/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pcf8574/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pid/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pipsolar/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pm1006/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pm2005/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pmsa003i/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pmsx003/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pmwcs3/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pn532_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pn532_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pn7150_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pn7160_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pn7160_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/power_supply/test.esp32-c3-idf.yaml delete mode 100644 tests/components/prometheus/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pulse_meter/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pulse_width/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pvvx_mithermometer/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pylontech/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pzem004t/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pzemac/test.esp32-c3-idf.yaml delete mode 100644 tests/components/pzemdc/test.esp32-c3-idf.yaml delete mode 100644 tests/components/qmc5883l/test.esp32-c3-idf.yaml delete mode 100644 tests/components/qmp6988/test.esp32-c3-idf.yaml delete mode 100644 tests/components/qr_code/test.esp32-c3-idf.yaml delete mode 100644 tests/components/qwiic_pir/test.esp32-c3-idf.yaml delete mode 100644 tests/components/radon_eye_ble/test.esp32-c3-idf.yaml delete mode 100644 tests/components/radon_eye_rd200/test.esp32-c3-idf.yaml delete mode 100644 tests/components/rc522_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/rc522_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/rdm6300/test.esp32-c3-idf.yaml delete mode 100644 tests/components/resampler/test.esp32-c3-idf.yaml delete mode 100644 tests/components/resistance/test.esp32-c3-idf.yaml delete mode 100644 tests/components/restart/test.esp32-c3-idf.yaml delete mode 100644 tests/components/rf_bridge/test.esp32-c3-idf.yaml delete mode 100644 tests/components/rgb/test.esp32-c3-idf.yaml delete mode 100644 tests/components/rgbct/test.esp32-c3-idf.yaml delete mode 100644 tests/components/rgbw/test.esp32-c3-idf.yaml delete mode 100644 tests/components/rgbww/test.esp32-c3-idf.yaml delete mode 100644 tests/components/rotary_encoder/test.esp32-c3-idf.yaml delete mode 100644 tests/components/rtttl/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ruuvi_ble/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ruuvitag/test.esp32-c3-idf.yaml delete mode 100644 tests/components/safe_mode/test-enabled.esp32-c3-idf.yaml delete mode 100644 tests/components/scd30/test.esp32-c3-idf.yaml delete mode 100644 tests/components/scd4x/test.esp32-c3-idf.yaml delete mode 100644 tests/components/script/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sdm_meter/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sdp3x/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sds011/test.esp32-c3-idf.yaml delete mode 100644 tests/components/seeed_mr24hpc1/test.esp32-c3-idf.yaml delete mode 100644 tests/components/seeed_mr60bha2/test.esp32-c3-idf.yaml delete mode 100644 tests/components/seeed_mr60fda2/test.esp32-c3-idf.yaml delete mode 100644 tests/components/selec_meter/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sen0321/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sen21231/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sen5x/test.esp32-c3-idf.yaml delete mode 100644 tests/components/senseair/test.esp32-c3-idf.yaml delete mode 100644 tests/components/servo/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sfa30/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sgp30/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sgp4x/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sht3xd/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sht4x/test.esp32-c3-idf.yaml delete mode 100644 tests/components/shtcx/test.esp32-c3-idf.yaml delete mode 100644 tests/components/shutdown/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sim800l/test.esp32-c3-idf.yaml delete mode 100644 tests/components/slow_pwm/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sm16716/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sm2135/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sm2235/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sm2335/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sm300d2/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sml/test.esp32-c3-idf.yaml delete mode 100644 tests/components/smt100/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sn74hc165/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sn74hc595/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sntp/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sonoff_d1/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sound_level/test.esp32-c3-idf.yaml delete mode 100644 tests/components/speaker/audio_dac.esp32-c3-idf.yaml delete mode 100644 tests/components/speaker/test.esp32-c3-idf.yaml delete mode 100644 tests/components/speed/test.esp32-c3-idf.yaml delete mode 100644 tests/components/spi_device/test.esp32-c3-idf.yaml delete mode 100644 tests/components/spi_led_strip/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sprinkler/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sps30/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ssd1306_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ssd1306_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ssd1322_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ssd1325_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ssd1327_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ssd1327_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ssd1331_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ssd1351_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/st7567_i2c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/st7567_spi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/st7735/test.esp32-c3-idf.yaml delete mode 100644 tests/components/st7789v/test.esp32-c3-idf.yaml delete mode 100644 tests/components/st7920/test.esp32-c3-idf.yaml delete mode 100644 tests/components/statsD/test.esp32-c3-idf.yaml delete mode 100644 tests/components/status/test.esp32-c3-idf.yaml delete mode 100644 tests/components/status_led/test.esp32-c3-idf.yaml delete mode 100644 tests/components/stepper/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sts3x/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sun/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sun_gtil2/test.esp32-c3-idf.yaml delete mode 100644 tests/components/switch/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sx126x/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sx127x/test.esp32-c3-idf.yaml delete mode 100644 tests/components/sx1509/test.esp32-c3-idf.yaml delete mode 100644 tests/components/syslog/test.esp32-c3-idf.yaml delete mode 100644 tests/components/t6615/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tc74/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tca9548a/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tca9555/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tcl112/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tcs34725/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tee501/test.esp32-c3-idf.yaml delete mode 100644 tests/components/teleinfo/test.esp32-c3-idf.yaml delete mode 100644 tests/components/template/test.esp32-c3-idf.yaml delete mode 100644 tests/components/thermostat/test.esp32-c3-idf.yaml delete mode 100644 tests/components/time/test.esp32-c3-idf.yaml delete mode 100644 tests/components/time_based/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tlc59208f/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tlc5947/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tlc5971/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tm1621/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tm1637/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tm1638/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tm1651/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tmp102/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tmp1075/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tmp117/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tof10120/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tormatic/test.esp32-c3-idf.yaml delete mode 100644 tests/components/toshiba/test.esp32-c3-idf.yaml delete mode 100644 tests/components/toshiba/test_ras2819t.esp32-c3-idf.yaml delete mode 100644 tests/components/total_daily_energy/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tsl2561/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tsl2591/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tt21100/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ttp229_bsf/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ttp229_lsf/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tuya/test.esp32-c3-idf.yaml delete mode 100644 tests/components/tx20/test.esp32-c3-idf.yaml delete mode 100644 tests/components/udp/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ufire_ec/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ufire_ise/test.esp32-c3-idf.yaml delete mode 100644 tests/components/uln2003/test.esp32-c3-idf.yaml delete mode 100644 tests/components/ultrasonic/test.esp32-c3-idf.yaml delete mode 100644 tests/components/uponor_smatrix/test.esp32-c3-idf.yaml delete mode 100644 tests/components/uptime/test.esp32-c3-idf.yaml delete mode 100644 tests/components/vbus/test.esp32-c3-idf.yaml delete mode 100644 tests/components/veml3235/test.esp32-c3-idf.yaml delete mode 100644 tests/components/veml7700/test.esp32-c3-idf.yaml delete mode 100644 tests/components/version/test.esp32-c3-idf.yaml delete mode 100644 tests/components/vl53l0x/test.esp32-c3-idf.yaml delete mode 100644 tests/components/voice_assistant/test.esp32-c3-idf.yaml delete mode 100644 tests/components/wake_on_lan/test.esp32-c3-idf.yaml delete mode 100644 tests/components/waveshare_epaper/test.esp32-c3-idf.yaml delete mode 100644 tests/components/web_server/test.esp32-c3-idf.yaml delete mode 100644 tests/components/whirlpool/test.esp32-c3-idf.yaml delete mode 100644 tests/components/whynter/test.esp32-c3-idf.yaml delete mode 100644 tests/components/wiegand/test.esp32-c3-idf.yaml delete mode 100644 tests/components/wifi/test.esp32-c3-idf.yaml delete mode 100644 tests/components/wifi_info/test.esp32-c3-idf.yaml delete mode 100644 tests/components/wifi_signal/test.esp32-c3-idf.yaml delete mode 100644 tests/components/wireguard/test.esp32-c3-idf.yaml delete mode 100644 tests/components/wl_134/test.esp32-c3-idf.yaml delete mode 100644 tests/components/wts01/test.esp32-c3-idf.yaml delete mode 100644 tests/components/x9c/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xgzp68xx/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_ble/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_cgd1/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_cgdk2/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_cgg1/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_cgpr1/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_gcls002/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_hhccjcy01/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_hhccpot002/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_jqjcy01ym/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_lywsd02/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_lywsd02mmc/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_lywsd03mmc/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_lywsdcgq/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_mhoc303/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_mhoc401/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_miscale/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_mjyd02yla/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_mue4094rt/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_rtcgq02lm/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_wx08zm/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xiaomi_xmwsdj04mmc/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xl9535/test.esp32-c3-idf.yaml delete mode 100644 tests/components/xpt2046/test.esp32-c3-idf.yaml delete mode 100644 tests/components/yashima/test.esp32-c3-idf.yaml delete mode 100644 tests/components/zhlt01/test.esp32-c3-idf.yaml delete mode 100644 tests/components/zio_ultrasonic/test.esp32-c3-idf.yaml delete mode 100644 tests/components/zwave_proxy/test.esp32-c3-idf.yaml delete mode 100644 tests/components/zyaura/test.esp32-c3-idf.yaml diff --git a/tests/components/a01nyub/test.esp32-c3-idf.yaml b/tests/components/a01nyub/test.esp32-c3-idf.yaml deleted file mode 100644 index 2cda8deaf9..0000000000 --- a/tests/components/a01nyub/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -substitutions: - tx_pin: GPIO4 - rx_pin: GPIO5 - -<<: !include common.yaml diff --git a/tests/components/a02yyuw/test.esp32-c3-idf.yaml b/tests/components/a02yyuw/test.esp32-c3-idf.yaml deleted file mode 100644 index 2cda8deaf9..0000000000 --- a/tests/components/a02yyuw/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -substitutions: - tx_pin: GPIO4 - rx_pin: GPIO5 - -<<: !include common.yaml diff --git a/tests/components/a4988/test.esp32-c3-idf.yaml b/tests/components/a4988/test.esp32-c3-idf.yaml deleted file mode 100644 index 25caba75b5..0000000000 --- a/tests/components/a4988/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - step_pin: GPIO2 - dir_pin: GPIO3 - sleep_pin: GPIO5 - -<<: !include common.yaml diff --git a/tests/components/absolute_humidity/test.esp32-c3-idf.yaml b/tests/components/absolute_humidity/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/absolute_humidity/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/adc128s102/test.esp32-c3-idf.yaml b/tests/components/adc128s102/test.esp32-c3-idf.yaml deleted file mode 100644 index a60568a736..0000000000 --- a/tests/components/adc128s102/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - cs_pin: GPIO2 - -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ade7880/test.esp32-c3-idf.yaml b/tests/components/ade7880/test.esp32-c3-idf.yaml deleted file mode 100644 index 7d5b41fc5a..0000000000 --- a/tests/components/ade7880/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - irq0_pin: GPIO6 - irq1_pin: GPIO7 - reset_pin: GPIO9 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ade7953_i2c/test.esp32-c3-idf.yaml b/tests/components/ade7953_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index 59296a1e6e..0000000000 --- a/tests/components/ade7953_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - irq_pin: GPIO6 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ade7953_spi/test.esp32-c3-idf.yaml b/tests/components/ade7953_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index 5e7e2dc82c..0000000000 --- a/tests/components/ade7953_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - irq_pin: GPIO9 - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ads1115/test.esp32-c3-idf.yaml b/tests/components/ads1115/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ads1115/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ags10/test.esp32-c3-idf.yaml b/tests/components/ags10/test.esp32-c3-idf.yaml deleted file mode 100644 index 72703301a1..0000000000 --- a/tests/components/ags10/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c_low_freq: !include ../../test_build_components/common/i2c_low_freq/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/aht10/test.esp32-c3-idf.yaml b/tests/components/aht10/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/aht10/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/aic3204/test.esp32-c3-idf.yaml b/tests/components/aic3204/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/aic3204/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/airthings_wave_mini/test.esp32-c3-idf.yaml b/tests/components/airthings_wave_mini/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/airthings_wave_mini/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/airthings_wave_plus/test.esp32-c3-idf.yaml b/tests/components/airthings_wave_plus/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/airthings_wave_plus/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/alarm_control_panel/test.esp32-c3-idf.yaml b/tests/components/alarm_control_panel/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/alarm_control_panel/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/alpha3/test.esp32-c3-idf.yaml b/tests/components/alpha3/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/alpha3/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/am2315c/test.esp32-c3-idf.yaml b/tests/components/am2315c/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/am2315c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/am2320/test.esp32-c3-idf.yaml b/tests/components/am2320/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/am2320/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/am43/test.esp32-c3-idf.yaml b/tests/components/am43/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/am43/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/analog_threshold/test.esp32-c3-idf.yaml b/tests/components/analog_threshold/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/analog_threshold/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/animation/test.esp32-c3-idf.yaml b/tests/components/animation/test.esp32-c3-idf.yaml deleted file mode 100644 index a08a683333..0000000000 --- a/tests/components/animation/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,13 +0,0 @@ -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - animation: !include common.yaml - -display: - - platform: ili9xxx - id: main_lcd - spi_id: spi_bus - model: ili9342 - cs_pin: 8 - dc_pin: 9 - reset_pin: 10 - invert_colors: false diff --git a/tests/components/anova/test.esp32-c3-idf.yaml b/tests/components/anova/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/anova/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/apds9306/test.esp32-c3-idf.yaml b/tests/components/apds9306/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/apds9306/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/apds9960/test.esp32-c3-idf.yaml b/tests/components/apds9960/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/apds9960/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/api/test.esp32-c3-idf.yaml b/tests/components/api/test.esp32-c3-idf.yaml deleted file mode 100644 index 46c01d926f..0000000000 --- a/tests/components/api/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -<<: !include common.yaml - -wifi: - ssid: MySSID - password: password1 diff --git a/tests/components/as3935_i2c/test.esp32-c3-idf.yaml b/tests/components/as3935_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index 59296a1e6e..0000000000 --- a/tests/components/as3935_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - irq_pin: GPIO6 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/as3935_spi/test.esp32-c3-idf.yaml b/tests/components/as3935_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index 5e7e2dc82c..0000000000 --- a/tests/components/as3935_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - irq_pin: GPIO9 - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/as5600/test.esp32-c3-idf.yaml b/tests/components/as5600/test.esp32-c3-idf.yaml deleted file mode 100644 index 03a87ed6c4..0000000000 --- a/tests/components/as5600/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - dir_pin: GPIO6 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/as7341/test.esp32-c3-idf.yaml b/tests/components/as7341/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/as7341/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/at581x/test.esp32-c3-idf.yaml b/tests/components/at581x/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/at581x/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/atc_mithermometer/test.esp32-c3-idf.yaml b/tests/components/atc_mithermometer/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/atc_mithermometer/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/atm90e26/test.esp32-c3-idf.yaml b/tests/components/atm90e26/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/atm90e26/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/atm90e32/test.esp32-c3-idf.yaml b/tests/components/atm90e32/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/atm90e32/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/axs15231/test.esp32-c3-idf.yaml b/tests/components/axs15231/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/axs15231/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/b_parasite/test.esp32-c3-idf.yaml b/tests/components/b_parasite/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/b_parasite/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bang_bang/test.esp32-c3-idf.yaml b/tests/components/bang_bang/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/bang_bang/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/bedjet/test.esp32-c3-idf.yaml b/tests/components/bedjet/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/bedjet/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bh1750/test.esp32-c3-idf.yaml b/tests/components/bh1750/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/bh1750/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bh1900nux/test.esp32-c3-idf.yaml b/tests/components/bh1900nux/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/bh1900nux/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/binary_sensor/test.esp32-c3-idf.yaml b/tests/components/binary_sensor/test.esp32-c3-idf.yaml deleted file mode 100644 index 25cb37a0b4..0000000000 --- a/tests/components/binary_sensor/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - common: !include common.yaml diff --git a/tests/components/binary_sensor_map/test.esp32-c3-idf.yaml b/tests/components/binary_sensor_map/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/binary_sensor_map/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/bl0906/test.esp32-c3-idf.yaml b/tests/components/bl0906/test.esp32-c3-idf.yaml deleted file mode 100644 index 147d967dd4..0000000000 --- a/tests/components/bl0906/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - uart_19200: !include ../../test_build_components/common/uart_19200/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bl0939/test.esp32-c3-idf.yaml b/tests/components/bl0939/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/bl0939/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bl0940/test.esp32-c3-idf.yaml b/tests/components/bl0940/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/bl0940/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bl0942/test.esp32-c3-idf.yaml b/tests/components/bl0942/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/bl0942/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ble_client/test.esp32-c3-idf.yaml b/tests/components/ble_client/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/ble_client/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ble_presence/test.esp32-c3-idf.yaml b/tests/components/ble_presence/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/ble_presence/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ble_rssi/test.esp32-c3-idf.yaml b/tests/components/ble_rssi/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/ble_rssi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ble_scanner/test.esp32-c3-idf.yaml b/tests/components/ble_scanner/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/ble_scanner/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bme280_i2c/test.esp32-c3-idf.yaml b/tests/components/bme280_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/bme280_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bme280_spi/test.esp32-c3-idf.yaml b/tests/components/bme280_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/bme280_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bme680/test.esp32-c3-idf.yaml b/tests/components/bme680/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/bme680/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bme68x_bsec2_i2c/test.esp32-c3-idf.yaml b/tests/components/bme68x_bsec2_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/bme68x_bsec2_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bmi160/test.esp32-c3-idf.yaml b/tests/components/bmi160/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/bmi160/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bmp085/test.esp32-c3-idf.yaml b/tests/components/bmp085/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/bmp085/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !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 deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/bmp280_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !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 deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/bmp280_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bmp3xx_i2c/test.esp32-c3-idf.yaml b/tests/components/bmp3xx_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/bmp3xx_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bmp3xx_spi/test.esp32-c3-idf.yaml b/tests/components/bmp3xx_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/bmp3xx_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bmp581/test.esp32-c3-idf.yaml b/tests/components/bmp581/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/bmp581/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/bp1658cj/test.esp32-c3-idf.yaml b/tests/components/bp1658cj/test.esp32-c3-idf.yaml deleted file mode 100644 index 7808481215..0000000000 --- a/tests/components/bp1658cj/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - clock_pin: GPIO5 - data_pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/bp5758d/test.esp32-c3-idf.yaml b/tests/components/bp5758d/test.esp32-c3-idf.yaml deleted file mode 100644 index 7808481215..0000000000 --- a/tests/components/bp5758d/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - clock_pin: GPIO5 - data_pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/button/test.esp32-c3-idf.yaml b/tests/components/button/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/button/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/bytebuffer/test.esp32-c3-idf.yaml b/tests/components/bytebuffer/test.esp32-c3-idf.yaml deleted file mode 100644 index 380ca87628..0000000000 --- a/tests/components/bytebuffer/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -!include common.yaml diff --git a/tests/components/cap1188/test.esp32-c3-idf.yaml b/tests/components/cap1188/test.esp32-c3-idf.yaml deleted file mode 100644 index c97f30d52c..0000000000 --- a/tests/components/cap1188/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - reset_pin: GPIO6 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/captive_portal/test.esp32-c3-idf.yaml b/tests/components/captive_portal/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/captive_portal/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/ccs811/test.esp32-c3-idf.yaml b/tests/components/ccs811/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ccs811/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/cd74hc4067/test.esp32-c3-idf.yaml b/tests/components/cd74hc4067/test.esp32-c3-idf.yaml deleted file mode 100644 index 5e8784c1fc..0000000000 --- a/tests/components/cd74hc4067/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - pin_s0: GPIO2 - pin_s1: GPIO3 - pin_s2: GPIO4 - pin_s3: GPIO5 - pin: GPIO0 - -<<: !include common.yaml diff --git a/tests/components/ch422g/test.esp32-c3-idf.yaml b/tests/components/ch422g/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ch422g/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/chsc6x/test.esp32-c3-idf.yaml b/tests/components/chsc6x/test.esp32-c3-idf.yaml deleted file mode 100644 index f0de4107d7..0000000000 --- a/tests/components/chsc6x/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,20 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -display: - - platform: ili9xxx - id: ili9xxx_display - model: GC9A01A - invert_colors: True - cs_pin: 11 - dc_pin: 7 - pages: - - id: page1 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - -touchscreen: - - platform: chsc6x - display: ili9xxx_display - interrupt_pin: 20 diff --git a/tests/components/climate_ir_lg/test.esp32-c3-idf.yaml b/tests/components/climate_ir_lg/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/climate_ir_lg/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/cm1106/test.esp32-c3-idf.yaml b/tests/components/cm1106/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/cm1106/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/color/test.esp32-c3-idf.yaml b/tests/components/color/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/color/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/color_temperature/test.esp32-c3-idf.yaml b/tests/components/color_temperature/test.esp32-c3-idf.yaml deleted file mode 100644 index 016f315d9f..0000000000 --- a/tests/components/color_temperature/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - light_platform: ledc - pin_o1: GPIO6 - pin_o2: GPIO7 - -<<: !include common.yaml diff --git a/tests/components/combination/test.esp32-c3-idf.yaml b/tests/components/combination/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/combination/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/coolix/test.esp32-c3-idf.yaml b/tests/components/coolix/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/coolix/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/copy/test.esp32-c3-idf.yaml b/tests/components/copy/test.esp32-c3-idf.yaml deleted file mode 100644 index 76272beb77..0000000000 --- a/tests/components/copy/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - pwm_platform: ledc - pin: GPIO2 - -<<: !include common.yaml diff --git a/tests/components/cs5460a/test.esp32-c3-idf.yaml b/tests/components/cs5460a/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/cs5460a/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/cse7761/test.esp32-c3-idf.yaml b/tests/components/cse7761/test.esp32-c3-idf.yaml deleted file mode 100644 index 4e11c6e7cb..0000000000 --- a/tests/components/cse7761/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart_38400: !include ../../test_build_components/common/uart_38400/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/cse7766/test.esp32-c3-idf.yaml b/tests/components/cse7766/test.esp32-c3-idf.yaml deleted file mode 100644 index dc95c985c7..0000000000 --- a/tests/components/cse7766/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart_4800_even: !include ../../test_build_components/common/uart_4800_even/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/cst226/test.esp32-c3-idf.yaml b/tests/components/cst226/test.esp32-c3-idf.yaml deleted file mode 100644 index ffc12867d0..0000000000 --- a/tests/components/cst226/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,11 +0,0 @@ -substitutions: - cs_pin: GPIO7 - dc_pin: GPIO9 - disp_reset_pin: GPIO18 - interrupt_pin: GPIO2 - reset_pin: GPIO3 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/cst816/test.esp32-c3-idf.yaml b/tests/components/cst816/test.esp32-c3-idf.yaml deleted file mode 100644 index ffc12867d0..0000000000 --- a/tests/components/cst816/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,11 +0,0 @@ -substitutions: - cs_pin: GPIO7 - dc_pin: GPIO9 - disp_reset_pin: GPIO18 - interrupt_pin: GPIO2 - reset_pin: GPIO3 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ct_clamp/test.esp32-c3-idf.yaml b/tests/components/ct_clamp/test.esp32-c3-idf.yaml deleted file mode 100644 index a8f29c98ae..0000000000 --- a/tests/components/ct_clamp/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -substitutions: - pin: GPIO0 - -<<: !include common.yaml diff --git a/tests/components/current_based/test.esp32-c3-idf.yaml b/tests/components/current_based/test.esp32-c3-idf.yaml deleted file mode 100644 index 59296a1e6e..0000000000 --- a/tests/components/current_based/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - irq_pin: GPIO6 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/cwww/test.esp32-c3-idf.yaml b/tests/components/cwww/test.esp32-c3-idf.yaml deleted file mode 100644 index 51571b34cf..0000000000 --- a/tests/components/cwww/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,17 +0,0 @@ -substitutions: - light_platform: ledc - pin_o1: GPIO6 - pin_o2: GPIO7 - -output: - - platform: ${light_platform} - id: light_output_1 - pin: ${pin_o1} - channel: 0 - - platform: ${light_platform} - id: light_output_2 - pin: ${pin_o2} - channel: 1 - phase_angle: 180° - -<<: !include common.yaml diff --git a/tests/components/dac7678/test.esp32-c3-idf.yaml b/tests/components/dac7678/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/dac7678/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/daikin_brc/test.esp32-c3-idf.yaml b/tests/components/daikin_brc/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/daikin_brc/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/dallas_temp/test.esp32-c3-idf.yaml b/tests/components/dallas_temp/test.esp32-c3-idf.yaml deleted file mode 100644 index 49bf988eb4..0000000000 --- a/tests/components/dallas_temp/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - one_wire_pin: "4" - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/daly_bms/test.esp32-c3-idf.yaml b/tests/components/daly_bms/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/daly_bms/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/debug/test.esp32-c3-idf.yaml b/tests/components/debug/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/debug/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/delonghi/test.esp32-c3-idf.yaml b/tests/components/delonghi/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/delonghi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/dfplayer/test.esp32-c3-idf.yaml b/tests/components/dfplayer/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/dfplayer/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/dfrobot_sen0395/test.esp32-c3-idf.yaml b/tests/components/dfrobot_sen0395/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/dfrobot_sen0395/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/dht/test.esp32-c3-idf.yaml b/tests/components/dht/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/dht/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/dht12/test.esp32-c3-idf.yaml b/tests/components/dht12/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/dht12/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/dps310/test.esp32-c3-idf.yaml b/tests/components/dps310/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/dps310/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ds1307/test.esp32-c3-idf.yaml b/tests/components/ds1307/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ds1307/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ds2484/test.esp32-c3-idf.yaml b/tests/components/ds2484/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ds2484/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/duty_cycle/test.esp32-c3-idf.yaml b/tests/components/duty_cycle/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/duty_cycle/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/duty_time/test.esp32-c3-idf.yaml b/tests/components/duty_time/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/duty_time/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/e131/test.esp32-c3-idf.yaml b/tests/components/e131/test.esp32-c3-idf.yaml deleted file mode 100644 index d9dc4f6804..0000000000 --- a/tests/components/e131/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - light_platform: esp32_rmt_led_strip - pin: GPIO2 - -<<: !include common-idf.yaml diff --git a/tests/components/ee895/test.esp32-c3-idf.yaml b/tests/components/ee895/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ee895/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ektf2232/test.esp32-c3-idf.yaml b/tests/components/ektf2232/test.esp32-c3-idf.yaml deleted file mode 100644 index 708d352a59..0000000000 --- a/tests/components/ektf2232/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - display_reset_pin: GPIO3 - interrupt_pin: GPIO6 - touch_reset_pin: GPIO7 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/emc2101/test.esp32-c3-idf.yaml b/tests/components/emc2101/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/emc2101/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/endstop/test.esp32-c3-idf.yaml b/tests/components/endstop/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/endstop/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/ens160_i2c/test.esp32-c3-idf.yaml b/tests/components/ens160_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ens160_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ens160_spi/test.esp32-c3-idf.yaml b/tests/components/ens160_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/ens160_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ens210/test.esp32-c3-idf.yaml b/tests/components/ens210/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ens210/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/es7210/test.esp32-c3-idf.yaml b/tests/components/es7210/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/es7210/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/es7243e/test.esp32-c3-idf.yaml b/tests/components/es7243e/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/es7243e/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/es8156/test.esp32-c3-idf.yaml b/tests/components/es8156/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/es8156/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/es8311/test.esp32-c3-idf.yaml b/tests/components/es8311/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/es8311/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/es8388/test.esp32-c3-idf.yaml b/tests/components/es8388/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/es8388/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/esphome/test.esp32-c3-idf.yaml b/tests/components/esphome/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/esphome/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/event/test.esp32-c3-idf.yaml b/tests/components/event/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/event/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/exposure_notifications/test.esp32-c3-idf.yaml b/tests/components/exposure_notifications/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/exposure_notifications/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/external_components/test.esp32-c3-idf.yaml b/tests/components/external_components/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/external_components/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/ezo/test.esp32-c3-idf.yaml b/tests/components/ezo/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ezo/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ezo_pmp/test.esp32-c3-idf.yaml b/tests/components/ezo_pmp/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ezo_pmp/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/factory_reset/test.esp32-c3-idf.yaml b/tests/components/factory_reset/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/factory_reset/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/feedback/test.esp32-c3-idf.yaml b/tests/components/feedback/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/feedback/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/fingerprint_grow/test.esp32-c3-idf.yaml b/tests/components/fingerprint_grow/test.esp32-c3-idf.yaml deleted file mode 100644 index dff4f7fd92..0000000000 --- a/tests/components/fingerprint_grow/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - sensing_pin: GPIO6 -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/font/test.esp32-c3-idf.yaml b/tests/components/font/test.esp32-c3-idf.yaml deleted file mode 100644 index 2090db7e6d..0000000000 --- a/tests/components/font/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - display_reset_pin: GPIO3 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/fs3000/test.esp32-c3-idf.yaml b/tests/components/fs3000/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/fs3000/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ft5x06/test.esp32-c3-idf.yaml b/tests/components/ft5x06/test.esp32-c3-idf.yaml deleted file mode 100644 index c97f30d52c..0000000000 --- a/tests/components/ft5x06/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - reset_pin: GPIO6 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ft63x6/test.esp32-c3-idf.yaml b/tests/components/ft63x6/test.esp32-c3-idf.yaml deleted file mode 100644 index febf38d8e1..0000000000 --- a/tests/components/ft63x6/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - interrupt_pin: GPIO2 - reset_pin: GPIO3 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/fujitsu_general/test.esp32-c3-idf.yaml b/tests/components/fujitsu_general/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/fujitsu_general/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/gcja5/test.esp32-c3-idf.yaml b/tests/components/gcja5/test.esp32-c3-idf.yaml deleted file mode 100644 index 236529042f..0000000000 --- a/tests/components/gcja5/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - uart_9600_even: !include ../../test_build_components/common/uart_9600_even/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/gl_r01_i2c/test.esp32-c3-idf.yaml b/tests/components/gl_r01_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/gl_r01_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/globals/test.esp32-c3-idf.yaml b/tests/components/globals/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/globals/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/gp2y1010au0f/test.esp32-c3-idf.yaml b/tests/components/gp2y1010au0f/test.esp32-c3-idf.yaml deleted file mode 100644 index 0e331c893c..0000000000 --- a/tests/components/gp2y1010au0f/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - adc_pin: GPIO0 - output_pin: GPIO1 - -<<: !include common.yaml diff --git a/tests/components/gp8403/test.esp32-c3-idf.yaml b/tests/components/gp8403/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/gp8403/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/gps/test.esp32-c3-idf.yaml b/tests/components/gps/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/gps/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/graph/test.esp32-c3-idf.yaml b/tests/components/graph/test.esp32-c3-idf.yaml deleted file mode 100644 index c97f30d52c..0000000000 --- a/tests/components/graph/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - reset_pin: GPIO6 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/graphical_display_menu/test.esp32-c3-idf.yaml b/tests/components/graphical_display_menu/test.esp32-c3-idf.yaml deleted file mode 100644 index c97f30d52c..0000000000 --- a/tests/components/graphical_display_menu/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - reset_pin: GPIO6 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/gree/test.esp32-c3-idf.yaml b/tests/components/gree/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/gree/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/grove_gas_mc_v2/test.esp32-c3-idf.yaml b/tests/components/grove_gas_mc_v2/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/grove_gas_mc_v2/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/grove_tb6612fng/test.esp32-c3-idf.yaml b/tests/components/grove_tb6612fng/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/grove_tb6612fng/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/growatt_solar/test.esp32-c3-idf.yaml b/tests/components/growatt_solar/test.esp32-c3-idf.yaml deleted file mode 100644 index 17940aafcf..0000000000 --- a/tests/components/growatt_solar/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - flow_control_pin: GPIO3 -packages: - modbus: !include ../../test_build_components/common/modbus/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/gt911/test.esp32-c3-idf.yaml b/tests/components/gt911/test.esp32-c3-idf.yaml deleted file mode 100644 index 5e15963b7e..0000000000 --- a/tests/components/gt911/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - display_reset_pin: "18" - interrupt_pin: "20" - reset_pin: "21" - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/haier/test.esp32-c3-idf.yaml b/tests/components/haier/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/haier/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/havells_solar/test.esp32-c3-idf.yaml b/tests/components/havells_solar/test.esp32-c3-idf.yaml deleted file mode 100644 index 17940aafcf..0000000000 --- a/tests/components/havells_solar/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - flow_control_pin: GPIO3 -packages: - modbus: !include ../../test_build_components/common/modbus/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/hbridge/test.esp32-c3-idf.yaml b/tests/components/hbridge/test.esp32-c3-idf.yaml deleted file mode 100644 index 93a6cb5818..0000000000 --- a/tests/components/hbridge/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,17 +0,0 @@ -substitutions: - pwm_platform: "ledc" - output1_pin: "4" - output2_pin: "5" - output3_pin: "6" - output4_pin: "7" - hbridge_on_pin: "2" - hbridge_off_pin: "3" - -<<: !include common.yaml - -switch: - - platform: hbridge - id: switch_hbridge - on_pin: ${hbridge_on_pin} - off_pin: ${hbridge_off_pin} - pulse_length: 60ms diff --git a/tests/components/hdc1080/test.esp32-c3-idf.yaml b/tests/components/hdc1080/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/hdc1080/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/hdc2010/test.esp32-c3-idf.yaml b/tests/components/hdc2010/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/hdc2010/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/he60r/test.esp32-c3-idf.yaml b/tests/components/he60r/test.esp32-c3-idf.yaml deleted file mode 100644 index b0c8c5de3d..0000000000 --- a/tests/components/he60r/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - uart_1200_even: !include ../../test_build_components/common/uart_1200_even/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/hitachi_ac344/test.esp32-c3-idf.yaml b/tests/components/hitachi_ac344/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/hitachi_ac344/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/hitachi_ac424/test.esp32-c3-idf.yaml b/tests/components/hitachi_ac424/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/hitachi_ac424/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/hlw8012/test.esp32-c3-idf.yaml b/tests/components/hlw8012/test.esp32-c3-idf.yaml deleted file mode 100644 index 8b0d069ce2..0000000000 --- a/tests/components/hlw8012/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - sel_pin: GPIO2 - cf_pin: GPIO3 - cf1_pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/hm3301/test.esp32-c3-idf.yaml b/tests/components/hm3301/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/hm3301/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/hmc5883l/test.esp32-c3-idf.yaml b/tests/components/hmc5883l/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/hmc5883l/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/homeassistant/test.esp32-c3-idf.yaml b/tests/components/homeassistant/test.esp32-c3-idf.yaml deleted file mode 100644 index 25cb37a0b4..0000000000 --- a/tests/components/homeassistant/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - common: !include common.yaml diff --git a/tests/components/honeywell_hih_i2c/test.esp32-c3-idf.yaml b/tests/components/honeywell_hih_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/honeywell_hih_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/honeywellabp/test.esp32-c3-idf.yaml b/tests/components/honeywellabp/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/honeywellabp/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/honeywellabp2_i2c/test.esp32-c3-idf.yaml b/tests/components/honeywellabp2_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/honeywellabp2_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/hrxl_maxsonar_wr/test.esp32-c3-idf.yaml b/tests/components/hrxl_maxsonar_wr/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/hrxl_maxsonar_wr/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/hte501/test.esp32-c3-idf.yaml b/tests/components/hte501/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/hte501/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/http_request/test.esp32-c3-idf.yaml b/tests/components/http_request/test.esp32-c3-idf.yaml deleted file mode 100644 index ee2f5aa59b..0000000000 --- a/tests/components/http_request/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -substitutions: - verify_ssl: "true" - -<<: !include common.yaml diff --git a/tests/components/htu21d/test.esp32-c3-idf.yaml b/tests/components/htu21d/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/htu21d/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/htu31d/test.esp32-c3-idf.yaml b/tests/components/htu31d/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/htu31d/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/hx711/test.esp32-c3-idf.yaml b/tests/components/hx711/test.esp32-c3-idf.yaml deleted file mode 100644 index defef165e3..0000000000 --- a/tests/components/hx711/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - clk_pin: GPIO4 - dout_pin: GPIO5 - -<<: !include common.yaml diff --git a/tests/components/hydreon_rgxx/test.esp32-c3-idf.yaml b/tests/components/hydreon_rgxx/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/hydreon_rgxx/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/hyt271/test.esp32-c3-idf.yaml b/tests/components/hyt271/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/hyt271/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/i2c/test.esp32-c3-idf.yaml b/tests/components/i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/i2c_device/test.esp32-c3-idf.yaml b/tests/components/i2c_device/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/i2c_device/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/iaqcore/test.esp32-c3-idf.yaml b/tests/components/iaqcore/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/iaqcore/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ili9xxx/test.esp32-c3-idf.yaml b/tests/components/ili9xxx/test.esp32-c3-idf.yaml deleted file mode 100644 index 1eea1e85f7..0000000000 --- a/tests/components/ili9xxx/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,11 +0,0 @@ -substitutions: - cs_pin1: GPIO8 - dc_pin1: GPIO9 - reset_pin1: GPIO10 - cs_pin2: GPIO2 - dc_pin2: GPIO3 - reset_pin2: GPIO7 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ina219/test.esp32-c3-idf.yaml b/tests/components/ina219/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ina219/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ina226/test.esp32-c3-idf.yaml b/tests/components/ina226/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ina226/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ina260/test.esp32-c3-idf.yaml b/tests/components/ina260/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ina260/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ina2xx_i2c/test.esp32-c3-idf.yaml b/tests/components/ina2xx_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ina2xx_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ina2xx_spi/test.esp32-c3-idf.yaml b/tests/components/ina2xx_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/ina2xx_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ina3221/test.esp32-c3-idf.yaml b/tests/components/ina3221/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ina3221/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/inkbird_ibsth1_mini/test.esp32-c3-idf.yaml b/tests/components/inkbird_ibsth1_mini/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/inkbird_ibsth1_mini/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/integration/test.esp32-c3-idf.yaml b/tests/components/integration/test.esp32-c3-idf.yaml deleted file mode 100644 index 5105e645f3..0000000000 --- a/tests/components/integration/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -substitutions: - pin: GPIO1 - -<<: !include common-esp32.yaml diff --git a/tests/components/interval/test.esp32-c3-idf.yaml b/tests/components/interval/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/interval/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/jsn_sr04t/test.esp32-c3-idf.yaml b/tests/components/jsn_sr04t/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/jsn_sr04t/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/key_collector/test.esp32-c3-idf.yaml b/tests/components/key_collector/test.esp32-c3-idf.yaml deleted file mode 100644 index b580ab7843..0000000000 --- a/tests/components/key_collector/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - pin_r0: GPIO2 - pin_r1: GPIO3 - pin_c0: GPIO4 - pin_c1: GPIO5 - -<<: !include common.yaml diff --git a/tests/components/kmeteriso/test.esp32-c3-idf.yaml b/tests/components/kmeteriso/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/kmeteriso/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/kuntze/test.esp32-c3-idf.yaml b/tests/components/kuntze/test.esp32-c3-idf.yaml deleted file mode 100644 index 17940aafcf..0000000000 --- a/tests/components/kuntze/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - flow_control_pin: GPIO3 -packages: - modbus: !include ../../test_build_components/common/modbus/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/lc709203f/test.esp32-c3-idf.yaml b/tests/components/lc709203f/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/lc709203f/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/lcd_gpio/test.esp32-c3-idf.yaml b/tests/components/lcd_gpio/test.esp32-c3-idf.yaml deleted file mode 100644 index b6b05f3ab4..0000000000 --- a/tests/components/lcd_gpio/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - d0_pin: GPIO1 - d1_pin: GPIO2 - d2_pin: GPIO3 - d3_pin: GPIO4 - enable_pin: GPIO5 - rs_pin: GPIO6 - -<<: !include common.yaml diff --git a/tests/components/lcd_menu/test.esp32-c3-idf.yaml b/tests/components/lcd_menu/test.esp32-c3-idf.yaml deleted file mode 100644 index b6b05f3ab4..0000000000 --- a/tests/components/lcd_menu/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - d0_pin: GPIO1 - d1_pin: GPIO2 - d2_pin: GPIO3 - d3_pin: GPIO4 - enable_pin: GPIO5 - rs_pin: GPIO6 - -<<: !include common.yaml diff --git a/tests/components/lcd_pcf8574/test.esp32-c3-idf.yaml b/tests/components/lcd_pcf8574/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/lcd_pcf8574/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ld2410/test.esp32-c3-idf.yaml b/tests/components/ld2410/test.esp32-c3-idf.yaml deleted file mode 100644 index 7a8f790ed8..0000000000 --- a/tests/components/ld2410/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - tx_pin: GPIO4 - rx_pin: GPIO5 - -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ld2412/test.esp32-c3-idf.yaml b/tests/components/ld2412/test.esp32-c3-idf.yaml deleted file mode 100644 index 7a8f790ed8..0000000000 --- a/tests/components/ld2412/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - tx_pin: GPIO4 - rx_pin: GPIO5 - -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ld2420/test.esp32-c3-idf.yaml b/tests/components/ld2420/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/ld2420/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ld2450/test.esp32-c3-idf.yaml b/tests/components/ld2450/test.esp32-c3-idf.yaml deleted file mode 100644 index 7a8f790ed8..0000000000 --- a/tests/components/ld2450/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - tx_pin: GPIO4 - rx_pin: GPIO5 - -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/light/test.esp32-c3-idf.yaml b/tests/components/light/test.esp32-c3-idf.yaml deleted file mode 100644 index 317d5748a3..0000000000 --- a/tests/components/light/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,21 +0,0 @@ -output: - - platform: gpio - id: test_binary - pin: 0 - - platform: ledc - id: test_ledc_1 - pin: 1 - - platform: ledc - id: test_ledc_2 - pin: 2 - - platform: ledc - id: test_ledc_3 - pin: 3 - - platform: ledc - id: test_ledc_4 - pin: 4 - - platform: ledc - id: test_ledc_5 - pin: 5 - -<<: !include common.yaml diff --git a/tests/components/lilygo_t5_47/test.esp32-c3-idf.yaml b/tests/components/lilygo_t5_47/test.esp32-c3-idf.yaml deleted file mode 100644 index febf38d8e1..0000000000 --- a/tests/components/lilygo_t5_47/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - interrupt_pin: GPIO2 - reset_pin: GPIO3 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/lm75b/test.esp32-c3-idf.yaml b/tests/components/lm75b/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/lm75b/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/lock/test.esp32-c3-idf.yaml b/tests/components/lock/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/lock/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/lps22/test.esp32-c3-idf.yaml b/tests/components/lps22/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/lps22/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ltr390/test.esp32-c3-idf.yaml b/tests/components/ltr390/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ltr390/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ltr501/test.esp32-c3-idf.yaml b/tests/components/ltr501/test.esp32-c3-idf.yaml deleted file mode 100644 index 72703301a1..0000000000 --- a/tests/components/ltr501/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c_low_freq: !include ../../test_build_components/common/i2c_low_freq/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ltr_als_ps/test.esp32-c3-idf.yaml b/tests/components/ltr_als_ps/test.esp32-c3-idf.yaml deleted file mode 100644 index 72703301a1..0000000000 --- a/tests/components/ltr_als_ps/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c_low_freq: !include ../../test_build_components/common/i2c_low_freq/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/m5stack_8angle/test.esp32-c3-idf.yaml b/tests/components/m5stack_8angle/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/m5stack_8angle/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mapping/test.esp32-c3-idf.yaml b/tests/components/mapping/test.esp32-c3-idf.yaml deleted file mode 100644 index 7911eb7edc..0000000000 --- a/tests/components/mapping/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,13 +0,0 @@ -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - map: !include common.yaml - -display: - spi_id: spi_bus - platform: ili9xxx - id: main_lcd - model: ili9342 - cs_pin: 8 - dc_pin: 9 - reset_pin: 10 - invert_colors: false diff --git a/tests/components/matrix_keypad/test.esp32-c3-idf.yaml b/tests/components/matrix_keypad/test.esp32-c3-idf.yaml deleted file mode 100644 index 75d9c0b263..0000000000 --- a/tests/components/matrix_keypad/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,15 +0,0 @@ -packages: - common: !include common.yaml - -matrix_keypad: - id: keypad - rows: - - pin: 1 - - pin: 2 - columns: - - pin: 3 - - pin: 4 - keys: "1234" - has_pulldowns: true - on_key: - - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/max17043/test.esp32-c3-idf.yaml b/tests/components/max17043/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/max17043/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/max31855/test.esp32-c3-idf.yaml b/tests/components/max31855/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/max31855/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/max31856/test.esp32-c3-idf.yaml b/tests/components/max31856/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/max31856/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/max31865/test.esp32-c3-idf.yaml b/tests/components/max31865/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/max31865/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/max44009/test.esp32-c3-idf.yaml b/tests/components/max44009/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/max44009/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/max6675/test.esp32-c3-idf.yaml b/tests/components/max6675/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/max6675/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/max6956/test.esp32-c3-idf.yaml b/tests/components/max6956/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/max6956/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/max7219/test.esp32-c3-idf.yaml b/tests/components/max7219/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/max7219/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/max7219digit/test.esp32-c3-idf.yaml b/tests/components/max7219digit/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/max7219digit/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/max9611/test.esp32-c3-idf.yaml b/tests/components/max9611/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/max9611/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp23008/test.esp32-c3-idf.yaml b/tests/components/mcp23008/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mcp23008/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp23016/test.esp32-c3-idf.yaml b/tests/components/mcp23016/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mcp23016/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp23017/test.esp32-c3-idf.yaml b/tests/components/mcp23017/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mcp23017/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp23s08/test.esp32-c3-idf.yaml b/tests/components/mcp23s08/test.esp32-c3-idf.yaml deleted file mode 100644 index b11ec9cdc6..0000000000 --- a/tests/components/mcp23s08/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - cs_pin: GPIO8 - -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp23s17/test.esp32-c3-idf.yaml b/tests/components/mcp23s17/test.esp32-c3-idf.yaml deleted file mode 100644 index b11ec9cdc6..0000000000 --- a/tests/components/mcp23s17/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - cs_pin: GPIO8 - -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp2515/test.esp32-c3-idf.yaml b/tests/components/mcp2515/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/mcp2515/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp3008/test.esp32-c3-idf.yaml b/tests/components/mcp3008/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/mcp3008/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp3204/test.esp32-c3-idf.yaml b/tests/components/mcp3204/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/mcp3204/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp4461/test.esp32-c3-idf.yaml b/tests/components/mcp4461/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mcp4461/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp4725/test.esp32-c3-idf.yaml b/tests/components/mcp4725/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mcp4725/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp4728/test.esp32-c3-idf.yaml b/tests/components/mcp4728/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mcp4728/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp47a1/test.esp32-c3-idf.yaml b/tests/components/mcp47a1/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mcp47a1/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp9600/test.esp32-c3-idf.yaml b/tests/components/mcp9600/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mcp9600/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mcp9808/test.esp32-c3-idf.yaml b/tests/components/mcp9808/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mcp9808/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mdns/test-enabled.esp32-c3-idf.yaml b/tests/components/mdns/test-enabled.esp32-c3-idf.yaml deleted file mode 100644 index 97fd63d70e..0000000000 --- a/tests/components/mdns/test-enabled.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-enabled.yaml diff --git a/tests/components/mhz19/test.esp32-c3-idf.yaml b/tests/components/mhz19/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/mhz19/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/micronova/test.esp32-c3-idf.yaml b/tests/components/micronova/test.esp32-c3-idf.yaml deleted file mode 100644 index eed876ae74..0000000000 --- a/tests/components/micronova/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - enable_rx_pin: GPIO3 - -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/microphone/test.esp32-c3-idf.yaml b/tests/components/microphone/test.esp32-c3-idf.yaml deleted file mode 100644 index c28dc553f5..0000000000 --- a/tests/components/microphone/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - i2s_bclk_pin: GPIO6 - i2s_lrclk_pin: GPIO7 - i2s_mclk_pin: GPIO8 - i2s_din_pin: GPIO3 - -<<: !include common.yaml diff --git a/tests/components/mics_4514/test.esp32-c3-idf.yaml b/tests/components/mics_4514/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mics_4514/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/midea_ir/test.esp32-c3-idf.yaml b/tests/components/midea_ir/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/midea_ir/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mitsubishi/test.esp32-c3-idf.yaml b/tests/components/mitsubishi/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/mitsubishi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mixer/test.esp32-c3-idf.yaml b/tests/components/mixer/test.esp32-c3-idf.yaml deleted file mode 100644 index f1721f0862..0000000000 --- a/tests/components/mixer/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - lrclk_pin: GPIO4 - bclk_pin: GPIO5 - mclk_pin: GPIO6 - dout_pin: GPIO7 - -<<: !include common.yaml diff --git a/tests/components/mlx90393/test.esp32-c3-idf.yaml b/tests/components/mlx90393/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mlx90393/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mlx90614/test.esp32-c3-idf.yaml b/tests/components/mlx90614/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mlx90614/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mmc5603/test.esp32-c3-idf.yaml b/tests/components/mmc5603/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mmc5603/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mmc5983/test.esp32-c3-idf.yaml b/tests/components/mmc5983/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mmc5983/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/modbus/test.esp32-c3-idf.yaml b/tests/components/modbus/test.esp32-c3-idf.yaml deleted file mode 100644 index 430c6818cb..0000000000 --- a/tests/components/modbus/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - flow_control_pin: GPIO3 -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/modbus_controller/test.esp32-c3-idf.yaml b/tests/components/modbus_controller/test.esp32-c3-idf.yaml deleted file mode 100644 index db826676ee..0000000000 --- a/tests/components/modbus_controller/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - modbus: !include ../../test_build_components/common/modbus/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/monochromatic/test.esp32-c3-idf.yaml b/tests/components/monochromatic/test.esp32-c3-idf.yaml deleted file mode 100644 index feabf013fd..0000000000 --- a/tests/components/monochromatic/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - light_platform: ledc - pin: GPIO2 - -<<: !include common.yaml diff --git a/tests/components/mopeka_ble/test.esp32-c3-idf.yaml b/tests/components/mopeka_ble/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/mopeka_ble/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mopeka_pro_check/test.esp32-c3-idf.yaml b/tests/components/mopeka_pro_check/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/mopeka_pro_check/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mopeka_std_check/test.esp32-c3-idf.yaml b/tests/components/mopeka_std_check/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/mopeka_std_check/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mpl3115a2/test.esp32-c3-idf.yaml b/tests/components/mpl3115a2/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mpl3115a2/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mpr121/test.esp32-c3-idf.yaml b/tests/components/mpr121/test.esp32-c3-idf.yaml deleted file mode 100644 index d1abb03369..0000000000 --- a/tests/components/mpr121/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - i2c_scl: GPIO5 - i2c_sda: GPIO4 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mpu6050/test.esp32-c3-idf.yaml b/tests/components/mpu6050/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mpu6050/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mpu6886/test.esp32-c3-idf.yaml b/tests/components/mpu6886/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/mpu6886/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mqtt/test.esp32-c3-idf.yaml b/tests/components/mqtt/test.esp32-c3-idf.yaml deleted file mode 100644 index d19609b55e..0000000000 --- a/tests/components/mqtt/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,3 +0,0 @@ -packages: - common: !include common.yaml - update: !include common-update.yaml diff --git a/tests/components/mqtt_subscribe/test.esp32-c3-idf.yaml b/tests/components/mqtt_subscribe/test.esp32-c3-idf.yaml deleted file mode 100644 index 2cb6d82536..0000000000 --- a/tests/components/mqtt_subscribe/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-idf.yaml diff --git a/tests/components/ms5611/test.esp32-c3-idf.yaml b/tests/components/ms5611/test.esp32-c3-idf.yaml deleted file mode 100644 index d1abb03369..0000000000 --- a/tests/components/ms5611/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - i2c_scl: GPIO5 - i2c_sda: GPIO4 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/msa3xx/test.esp32-c3-idf.yaml b/tests/components/msa3xx/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/msa3xx/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/my9231/test.esp32-c3-idf.yaml b/tests/components/my9231/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/my9231/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/nau7802/test.esp32-c3-idf.yaml b/tests/components/nau7802/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/nau7802/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/network/test-ipv6.esp32-c3-idf.yaml b/tests/components/network/test-ipv6.esp32-c3-idf.yaml deleted file mode 100644 index da1324b17e..0000000000 --- a/tests/components/network/test-ipv6.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -substitutions: - network_enable_ipv6: "true" - -<<: !include common.yaml diff --git a/tests/components/network/test.esp32-c3-idf.yaml b/tests/components/network/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/network/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/nextion/test.esp32-c3-idf.yaml b/tests/components/nextion/test.esp32-c3-idf.yaml deleted file mode 100644 index 888693f909..0000000000 --- a/tests/components/nextion/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - base: !include common.yaml - -display: - - id: !extend main_lcd - tft_url: http://esphome.io/default35.tft diff --git a/tests/components/noblex/test.esp32-c3-idf.yaml b/tests/components/noblex/test.esp32-c3-idf.yaml deleted file mode 100644 index fe77c44eed..0000000000 --- a/tests/components/noblex/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - remote_receiver: !include ../../test_build_components/common/remote_receiver/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ntc/test.esp32-c3-idf.yaml b/tests/components/ntc/test.esp32-c3-idf.yaml deleted file mode 100644 index 37fb325f4a..0000000000 --- a/tests/components/ntc/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -substitutions: - pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/opt3001/test.esp32-c3-idf.yaml b/tests/components/opt3001/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/opt3001/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ota/test.esp32-c3-idf.yaml b/tests/components/ota/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/ota/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/output/test.esp32-c3-idf.yaml b/tests/components/output/test.esp32-c3-idf.yaml deleted file mode 100644 index 2227643703..0000000000 --- a/tests/components/output/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - output_platform: ledc - pin: GPIO1 - -<<: !include common.yaml diff --git a/tests/components/packet_transport/test.esp32-c3-idf.yaml b/tests/components/packet_transport/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/packet_transport/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pca6416a/test.esp32-c3-idf.yaml b/tests/components/pca6416a/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/pca6416a/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pca9554/test.esp32-c3-idf.yaml b/tests/components/pca9554/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/pca9554/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pca9685/test.esp32-c3-idf.yaml b/tests/components/pca9685/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/pca9685/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pcd8544/test.esp32-c3-idf.yaml b/tests/components/pcd8544/test.esp32-c3-idf.yaml deleted file mode 100644 index b112cf4c31..0000000000 --- a/tests/components/pcd8544/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - cs_pin: GPIO8 - dc_pin: GPIO9 - reset_pin: GPIO10 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pcf85063/test.esp32-c3-idf.yaml b/tests/components/pcf85063/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/pcf85063/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pcf8563/test.esp32-c3-idf.yaml b/tests/components/pcf8563/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/pcf8563/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pcf8574/test.esp32-c3-idf.yaml b/tests/components/pcf8574/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/pcf8574/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pid/test.esp32-c3-idf.yaml b/tests/components/pid/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/pid/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/pipsolar/test.esp32-c3-idf.yaml b/tests/components/pipsolar/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/pipsolar/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pm1006/test.esp32-c3-idf.yaml b/tests/components/pm1006/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/pm1006/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pm2005/test.esp32-c3-idf.yaml b/tests/components/pm2005/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/pm2005/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pmsa003i/test.esp32-c3-idf.yaml b/tests/components/pmsa003i/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/pmsa003i/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pmsx003/test.esp32-c3-idf.yaml b/tests/components/pmsx003/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/pmsx003/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pmwcs3/test.esp32-c3-idf.yaml b/tests/components/pmwcs3/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/pmwcs3/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pn532_i2c/test.esp32-c3-idf.yaml b/tests/components/pn532_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/pn532_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pn532_spi/test.esp32-c3-idf.yaml b/tests/components/pn532_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/pn532_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pn7150_i2c/test.esp32-c3-idf.yaml b/tests/components/pn7150_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index cdf8445263..0000000000 --- a/tests/components/pn7150_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - irq_pin: GPIO6 - ven_pin: GPIO7 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pn7160_i2c/test.esp32-c3-idf.yaml b/tests/components/pn7160_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index cdf8445263..0000000000 --- a/tests/components/pn7160_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - irq_pin: GPIO6 - ven_pin: GPIO7 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pn7160_spi/test.esp32-c3-idf.yaml b/tests/components/pn7160_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index ac18bfff5c..0000000000 --- a/tests/components/pn7160_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - cs_pin: GPIO8 - irq_pin: GPIO9 - ven_pin: GPIO10 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/power_supply/test.esp32-c3-idf.yaml b/tests/components/power_supply/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/power_supply/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/prometheus/test.esp32-c3-idf.yaml b/tests/components/prometheus/test.esp32-c3-idf.yaml deleted file mode 100644 index fedeaf822a..0000000000 --- a/tests/components/prometheus/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - verify_ssl: "false" - -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pulse_meter/test.esp32-c3-idf.yaml b/tests/components/pulse_meter/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/pulse_meter/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/pulse_width/test.esp32-c3-idf.yaml b/tests/components/pulse_width/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/pulse_width/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/pvvx_mithermometer/test.esp32-c3-idf.yaml b/tests/components/pvvx_mithermometer/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/pvvx_mithermometer/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pylontech/test.esp32-c3-idf.yaml b/tests/components/pylontech/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/pylontech/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pzem004t/test.esp32-c3-idf.yaml b/tests/components/pzem004t/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/pzem004t/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pzemac/test.esp32-c3-idf.yaml b/tests/components/pzemac/test.esp32-c3-idf.yaml deleted file mode 100644 index 6c6e95488f..0000000000 --- a/tests/components/pzemac/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - modbus: !include ../../test_build_components/common/modbus/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/pzemdc/test.esp32-c3-idf.yaml b/tests/components/pzemdc/test.esp32-c3-idf.yaml deleted file mode 100644 index 6c6e95488f..0000000000 --- a/tests/components/pzemdc/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - modbus: !include ../../test_build_components/common/modbus/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/qmc5883l/test.esp32-c3-idf.yaml b/tests/components/qmc5883l/test.esp32-c3-idf.yaml deleted file mode 100644 index 854ddc25e7..0000000000 --- a/tests/components/qmc5883l/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - drdy_pin: GPIO6 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/qmp6988/test.esp32-c3-idf.yaml b/tests/components/qmp6988/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/qmp6988/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/qr_code/test.esp32-c3-idf.yaml b/tests/components/qr_code/test.esp32-c3-idf.yaml deleted file mode 100644 index b112cf4c31..0000000000 --- a/tests/components/qr_code/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - cs_pin: GPIO8 - dc_pin: GPIO9 - reset_pin: GPIO10 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/qwiic_pir/test.esp32-c3-idf.yaml b/tests/components/qwiic_pir/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/qwiic_pir/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/radon_eye_ble/test.esp32-c3-idf.yaml b/tests/components/radon_eye_ble/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/radon_eye_ble/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/radon_eye_rd200/test.esp32-c3-idf.yaml b/tests/components/radon_eye_rd200/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/radon_eye_rd200/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/rc522_i2c/test.esp32-c3-idf.yaml b/tests/components/rc522_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/rc522_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/rc522_spi/test.esp32-c3-idf.yaml b/tests/components/rc522_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/rc522_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/rdm6300/test.esp32-c3-idf.yaml b/tests/components/rdm6300/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/rdm6300/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/resampler/test.esp32-c3-idf.yaml b/tests/components/resampler/test.esp32-c3-idf.yaml deleted file mode 100644 index f1721f0862..0000000000 --- a/tests/components/resampler/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - lrclk_pin: GPIO4 - bclk_pin: GPIO5 - mclk_pin: GPIO6 - dout_pin: GPIO7 - -<<: !include common.yaml diff --git a/tests/components/resistance/test.esp32-c3-idf.yaml b/tests/components/resistance/test.esp32-c3-idf.yaml deleted file mode 100644 index 37fb325f4a..0000000000 --- a/tests/components/resistance/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -substitutions: - pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/restart/test.esp32-c3-idf.yaml b/tests/components/restart/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/restart/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/rf_bridge/test.esp32-c3-idf.yaml b/tests/components/rf_bridge/test.esp32-c3-idf.yaml deleted file mode 100644 index a19013bf54..0000000000 --- a/tests/components/rf_bridge/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/rgb/test.esp32-c3-idf.yaml b/tests/components/rgb/test.esp32-c3-idf.yaml deleted file mode 100644 index 1fe4a4bb90..0000000000 --- a/tests/components/rgb/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - light_platform: ledc - pin1: GPIO2 - pin2: GPIO3 - pin3: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/rgbct/test.esp32-c3-idf.yaml b/tests/components/rgbct/test.esp32-c3-idf.yaml deleted file mode 100644 index 27a1fbca4d..0000000000 --- a/tests/components/rgbct/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - light_platform: ledc - pin1: GPIO2 - pin2: GPIO3 - pin3: GPIO4 - pin4: GPIO5 - pin5: GPIO6 - -<<: !include common.yaml diff --git a/tests/components/rgbw/test.esp32-c3-idf.yaml b/tests/components/rgbw/test.esp32-c3-idf.yaml deleted file mode 100644 index b44734344e..0000000000 --- a/tests/components/rgbw/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - light_platform: ledc - pin1: GPIO2 - pin2: GPIO3 - pin3: GPIO4 - pin4: GPIO5 - -<<: !include common.yaml diff --git a/tests/components/rgbww/test.esp32-c3-idf.yaml b/tests/components/rgbww/test.esp32-c3-idf.yaml deleted file mode 100644 index 27a1fbca4d..0000000000 --- a/tests/components/rgbww/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - light_platform: ledc - pin1: GPIO2 - pin2: GPIO3 - pin3: GPIO4 - pin4: GPIO5 - pin5: GPIO6 - -<<: !include common.yaml diff --git a/tests/components/rotary_encoder/test.esp32-c3-idf.yaml b/tests/components/rotary_encoder/test.esp32-c3-idf.yaml deleted file mode 100644 index b71a454bdd..0000000000 --- a/tests/components/rotary_encoder/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - pin_a: GPIO2 - pin_b: GPIO3 - pin_reset: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/rtttl/test.esp32-c3-idf.yaml b/tests/components/rtttl/test.esp32-c3-idf.yaml deleted file mode 100644 index 7476963591..0000000000 --- a/tests/components/rtttl/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - output_platform: ledc - pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/ruuvi_ble/test.esp32-c3-idf.yaml b/tests/components/ruuvi_ble/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/ruuvi_ble/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ruuvitag/test.esp32-c3-idf.yaml b/tests/components/ruuvitag/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/ruuvitag/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/safe_mode/test-enabled.esp32-c3-idf.yaml b/tests/components/safe_mode/test-enabled.esp32-c3-idf.yaml deleted file mode 100644 index 97fd63d70e..0000000000 --- a/tests/components/safe_mode/test-enabled.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-enabled.yaml diff --git a/tests/components/scd30/test.esp32-c3-idf.yaml b/tests/components/scd30/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/scd30/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/scd4x/test.esp32-c3-idf.yaml b/tests/components/scd4x/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/scd4x/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/script/test.esp32-c3-idf.yaml b/tests/components/script/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/script/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/sdm_meter/test.esp32-c3-idf.yaml b/tests/components/sdm_meter/test.esp32-c3-idf.yaml deleted file mode 100644 index 6c6e95488f..0000000000 --- a/tests/components/sdm_meter/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - modbus: !include ../../test_build_components/common/modbus/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sdp3x/test.esp32-c3-idf.yaml b/tests/components/sdp3x/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/sdp3x/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sds011/test.esp32-c3-idf.yaml b/tests/components/sds011/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/sds011/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/seeed_mr24hpc1/test.esp32-c3-idf.yaml b/tests/components/seeed_mr24hpc1/test.esp32-c3-idf.yaml deleted file mode 100644 index a19013bf54..0000000000 --- a/tests/components/seeed_mr24hpc1/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/seeed_mr60bha2/test.esp32-c3-idf.yaml b/tests/components/seeed_mr60bha2/test.esp32-c3-idf.yaml deleted file mode 100644 index fea89f5768..0000000000 --- a/tests/components/seeed_mr60bha2/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - uart_115200: !include ../../test_build_components/common/uart_115200/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/seeed_mr60fda2/test.esp32-c3-idf.yaml b/tests/components/seeed_mr60fda2/test.esp32-c3-idf.yaml deleted file mode 100644 index fea89f5768..0000000000 --- a/tests/components/seeed_mr60fda2/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - uart_115200: !include ../../test_build_components/common/uart_115200/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/selec_meter/test.esp32-c3-idf.yaml b/tests/components/selec_meter/test.esp32-c3-idf.yaml deleted file mode 100644 index beb90e1471..0000000000 --- a/tests/components/selec_meter/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - flow_control_pin: GPIO10 - -packages: - modbus: !include ../../test_build_components/common/modbus/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sen0321/test.esp32-c3-idf.yaml b/tests/components/sen0321/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/sen0321/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sen21231/test.esp32-c3-idf.yaml b/tests/components/sen21231/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/sen21231/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sen5x/test.esp32-c3-idf.yaml b/tests/components/sen5x/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/sen5x/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/senseair/test.esp32-c3-idf.yaml b/tests/components/senseair/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/senseair/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/servo/test.esp32-c3-idf.yaml b/tests/components/servo/test.esp32-c3-idf.yaml deleted file mode 100644 index 7476963591..0000000000 --- a/tests/components/servo/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - output_platform: ledc - pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/sfa30/test.esp32-c3-idf.yaml b/tests/components/sfa30/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/sfa30/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sgp30/test.esp32-c3-idf.yaml b/tests/components/sgp30/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/sgp30/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sgp4x/test.esp32-c3-idf.yaml b/tests/components/sgp4x/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/sgp4x/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sht3xd/test.esp32-c3-idf.yaml b/tests/components/sht3xd/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/sht3xd/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sht4x/test.esp32-c3-idf.yaml b/tests/components/sht4x/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/sht4x/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/shtcx/test.esp32-c3-idf.yaml b/tests/components/shtcx/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/shtcx/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/shutdown/test.esp32-c3-idf.yaml b/tests/components/shutdown/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/shutdown/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/sim800l/test.esp32-c3-idf.yaml b/tests/components/sim800l/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/sim800l/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/slow_pwm/test.esp32-c3-idf.yaml b/tests/components/slow_pwm/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/slow_pwm/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/sm16716/test.esp32-c3-idf.yaml b/tests/components/sm16716/test.esp32-c3-idf.yaml deleted file mode 100644 index 7808481215..0000000000 --- a/tests/components/sm16716/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - clock_pin: GPIO5 - data_pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/sm2135/test.esp32-c3-idf.yaml b/tests/components/sm2135/test.esp32-c3-idf.yaml deleted file mode 100644 index 7808481215..0000000000 --- a/tests/components/sm2135/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - clock_pin: GPIO5 - data_pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/sm2235/test.esp32-c3-idf.yaml b/tests/components/sm2235/test.esp32-c3-idf.yaml deleted file mode 100644 index 7808481215..0000000000 --- a/tests/components/sm2235/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - clock_pin: GPIO5 - data_pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/sm2335/test.esp32-c3-idf.yaml b/tests/components/sm2335/test.esp32-c3-idf.yaml deleted file mode 100644 index 7808481215..0000000000 --- a/tests/components/sm2335/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - clock_pin: GPIO5 - data_pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/sm300d2/test.esp32-c3-idf.yaml b/tests/components/sm300d2/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/sm300d2/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sml/test.esp32-c3-idf.yaml b/tests/components/sml/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/sml/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/smt100/test.esp32-c3-idf.yaml b/tests/components/smt100/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/smt100/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sn74hc165/test.esp32-c3-idf.yaml b/tests/components/sn74hc165/test.esp32-c3-idf.yaml deleted file mode 100644 index 0a3db917b7..0000000000 --- a/tests/components/sn74hc165/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - clock_pin: GPIO3 - data_pin: GPIO4 - load_pin: GPIO5 - clock_inhibit_pin: GPIO6 - -<<: !include common.yaml diff --git a/tests/components/sn74hc595/test.esp32-c3-idf.yaml b/tests/components/sn74hc595/test.esp32-c3-idf.yaml deleted file mode 100644 index 74b5e855fa..0000000000 --- a/tests/components/sn74hc595/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,12 +0,0 @@ -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -substitutions: - clock_pin: GPIO7 - data_pin: GPIO10 - latch_pin1: GPIO1 - oe_pin1: GPIO2 - latch_pin2: GPIO3 - oe_pin2: GPIO9 - -<<: !include common.yaml diff --git a/tests/components/sntp/test.esp32-c3-idf.yaml b/tests/components/sntp/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/sntp/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/sonoff_d1/test.esp32-c3-idf.yaml b/tests/components/sonoff_d1/test.esp32-c3-idf.yaml deleted file mode 100644 index a19013bf54..0000000000 --- a/tests/components/sonoff_d1/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sound_level/test.esp32-c3-idf.yaml b/tests/components/sound_level/test.esp32-c3-idf.yaml deleted file mode 100644 index aeb7d9f0af..0000000000 --- a/tests/components/sound_level/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - i2s_bclk_pin: GPIO6 - i2s_lrclk_pin: GPIO7 - i2s_dout_pin: GPIO8 - -<<: !include common.yaml diff --git a/tests/components/speaker/audio_dac.esp32-c3-idf.yaml b/tests/components/speaker/audio_dac.esp32-c3-idf.yaml deleted file mode 100644 index 30900f1920..0000000000 --- a/tests/components/speaker/audio_dac.esp32-c3-idf.yaml +++ /dev/null @@ -1,10 +0,0 @@ -substitutions: - i2s_bclk_pin: GPIO7 - i2s_lrclk_pin: GPIO6 - i2s_mclk_pin: GPIO9 - i2s_dout_pin: GPIO8 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common-audio_dac.yaml diff --git a/tests/components/speaker/test.esp32-c3-idf.yaml b/tests/components/speaker/test.esp32-c3-idf.yaml deleted file mode 100644 index 9d1a1cca3b..0000000000 --- a/tests/components/speaker/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,12 +0,0 @@ -substitutions: - scl_pin: GPIO5 - sda_pin: GPIO4 - i2s_bclk_pin: GPIO7 - i2s_lrclk_pin: GPIO6 - i2s_mclk_pin: GPIO9 - i2s_dout_pin: GPIO8 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/speed/test.esp32-c3-idf.yaml b/tests/components/speed/test.esp32-c3-idf.yaml deleted file mode 100644 index 7476963591..0000000000 --- a/tests/components/speed/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - output_platform: ledc - pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/spi_device/test.esp32-c3-idf.yaml b/tests/components/spi_device/test.esp32-c3-idf.yaml deleted file mode 100644 index 6d64c2b23b..0000000000 --- a/tests/components/spi_device/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/spi_led_strip/test.esp32-c3-idf.yaml b/tests/components/spi_led_strip/test.esp32-c3-idf.yaml deleted file mode 100644 index 6d64c2b23b..0000000000 --- a/tests/components/spi_led_strip/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sprinkler/test.esp32-c3-idf.yaml b/tests/components/sprinkler/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/sprinkler/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/sps30/test.esp32-c3-idf.yaml b/tests/components/sps30/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/sps30/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ssd1306_i2c/test.esp32-c3-idf.yaml b/tests/components/ssd1306_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index f8bfab2319..0000000000 --- a/tests/components/ssd1306_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - reset_pin: GPIO3 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ssd1306_spi/test.esp32-c3-idf.yaml b/tests/components/ssd1306_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index b112cf4c31..0000000000 --- a/tests/components/ssd1306_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - cs_pin: GPIO8 - dc_pin: GPIO9 - reset_pin: GPIO10 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ssd1322_spi/test.esp32-c3-idf.yaml b/tests/components/ssd1322_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index b112cf4c31..0000000000 --- a/tests/components/ssd1322_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - cs_pin: GPIO8 - dc_pin: GPIO9 - reset_pin: GPIO10 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ssd1325_spi/test.esp32-c3-idf.yaml b/tests/components/ssd1325_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index b112cf4c31..0000000000 --- a/tests/components/ssd1325_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - cs_pin: GPIO8 - dc_pin: GPIO9 - reset_pin: GPIO10 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ssd1327_i2c/test.esp32-c3-idf.yaml b/tests/components/ssd1327_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index f8bfab2319..0000000000 --- a/tests/components/ssd1327_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - reset_pin: GPIO3 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ssd1327_spi/test.esp32-c3-idf.yaml b/tests/components/ssd1327_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index b112cf4c31..0000000000 --- a/tests/components/ssd1327_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - cs_pin: GPIO8 - dc_pin: GPIO9 - reset_pin: GPIO10 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ssd1331_spi/test.esp32-c3-idf.yaml b/tests/components/ssd1331_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index b112cf4c31..0000000000 --- a/tests/components/ssd1331_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - cs_pin: GPIO8 - dc_pin: GPIO9 - reset_pin: GPIO10 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ssd1351_spi/test.esp32-c3-idf.yaml b/tests/components/ssd1351_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index b112cf4c31..0000000000 --- a/tests/components/ssd1351_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - cs_pin: GPIO8 - dc_pin: GPIO9 - reset_pin: GPIO10 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/st7567_i2c/test.esp32-c3-idf.yaml b/tests/components/st7567_i2c/test.esp32-c3-idf.yaml deleted file mode 100644 index f8bfab2319..0000000000 --- a/tests/components/st7567_i2c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - reset_pin: GPIO3 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/st7567_spi/test.esp32-c3-idf.yaml b/tests/components/st7567_spi/test.esp32-c3-idf.yaml deleted file mode 100644 index b112cf4c31..0000000000 --- a/tests/components/st7567_spi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - cs_pin: GPIO8 - dc_pin: GPIO9 - reset_pin: GPIO10 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/st7735/test.esp32-c3-idf.yaml b/tests/components/st7735/test.esp32-c3-idf.yaml deleted file mode 100644 index b112cf4c31..0000000000 --- a/tests/components/st7735/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - cs_pin: GPIO8 - dc_pin: GPIO9 - reset_pin: GPIO10 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/st7789v/test.esp32-c3-idf.yaml b/tests/components/st7789v/test.esp32-c3-idf.yaml deleted file mode 100644 index b4d70edb31..0000000000 --- a/tests/components/st7789v/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,10 +0,0 @@ -substitutions: - cs_pin: GPIO8 - dc_pin: GPIO9 - reset_pin: GPIO10 - backlight_pin: GPIO7 - -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/st7920/test.esp32-c3-idf.yaml b/tests/components/st7920/test.esp32-c3-idf.yaml deleted file mode 100644 index 15d986e157..0000000000 --- a/tests/components/st7920/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO8 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/statsD/test.esp32-c3-idf.yaml b/tests/components/statsD/test.esp32-c3-idf.yaml deleted file mode 100644 index 25cb37a0b4..0000000000 --- a/tests/components/statsD/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - common: !include common.yaml diff --git a/tests/components/status/test.esp32-c3-idf.yaml b/tests/components/status/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/status/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/status_led/test.esp32-c3-idf.yaml b/tests/components/status_led/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/status_led/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/stepper/test.esp32-c3-idf.yaml b/tests/components/stepper/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/stepper/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/sts3x/test.esp32-c3-idf.yaml b/tests/components/sts3x/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/sts3x/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sun/test.esp32-c3-idf.yaml b/tests/components/sun/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/sun/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/sun_gtil2/test.esp32-c3-idf.yaml b/tests/components/sun_gtil2/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/sun_gtil2/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/switch/test.esp32-c3-idf.yaml b/tests/components/switch/test.esp32-c3-idf.yaml deleted file mode 100644 index 25cb37a0b4..0000000000 --- a/tests/components/switch/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - common: !include common.yaml diff --git a/tests/components/sx126x/test.esp32-c3-idf.yaml b/tests/components/sx126x/test.esp32-c3-idf.yaml deleted file mode 100644 index e27f11032e..0000000000 --- a/tests/components/sx126x/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,10 +0,0 @@ -substitutions: - cs_pin: GPIO1 - rst_pin: GPIO2 - busy_pin: GPIO7 - dio1_pin: GPIO3 - -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sx127x/test.esp32-c3-idf.yaml b/tests/components/sx127x/test.esp32-c3-idf.yaml deleted file mode 100644 index dfee192545..0000000000 --- a/tests/components/sx127x/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - cs_pin: GPIO1 - rst_pin: GPIO2 - dio0_pin: GPIO3 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/sx1509/test.esp32-c3-idf.yaml b/tests/components/sx1509/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/sx1509/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/syslog/test.esp32-c3-idf.yaml b/tests/components/syslog/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/syslog/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/t6615/test.esp32-c3-idf.yaml b/tests/components/t6615/test.esp32-c3-idf.yaml deleted file mode 100644 index 147d967dd4..0000000000 --- a/tests/components/t6615/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - uart_19200: !include ../../test_build_components/common/uart_19200/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tc74/test.esp32-c3-idf.yaml b/tests/components/tc74/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/tc74/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tca9548a/test.esp32-c3-idf.yaml b/tests/components/tca9548a/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/tca9548a/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tca9555/test.esp32-c3-idf.yaml b/tests/components/tca9555/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/tca9555/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tcl112/test.esp32-c3-idf.yaml b/tests/components/tcl112/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/tcl112/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tcs34725/test.esp32-c3-idf.yaml b/tests/components/tcs34725/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/tcs34725/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tee501/test.esp32-c3-idf.yaml b/tests/components/tee501/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/tee501/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/teleinfo/test.esp32-c3-idf.yaml b/tests/components/teleinfo/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/teleinfo/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/template/test.esp32-c3-idf.yaml b/tests/components/template/test.esp32-c3-idf.yaml deleted file mode 100644 index 25cb37a0b4..0000000000 --- a/tests/components/template/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - common: !include common.yaml diff --git a/tests/components/thermostat/test.esp32-c3-idf.yaml b/tests/components/thermostat/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/thermostat/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/time/test.esp32-c3-idf.yaml b/tests/components/time/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/time/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/time_based/test.esp32-c3-idf.yaml b/tests/components/time_based/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/time_based/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/tlc59208f/test.esp32-c3-idf.yaml b/tests/components/tlc59208f/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/tlc59208f/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tlc5947/test.esp32-c3-idf.yaml b/tests/components/tlc5947/test.esp32-c3-idf.yaml deleted file mode 100644 index 4694c43642..0000000000 --- a/tests/components/tlc5947/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - clock_pin: GPIO5 - data_pin: GPIO4 - lat_pin: GPIO3 - -packages: - common: !include common.yaml diff --git a/tests/components/tlc5971/test.esp32-c3-idf.yaml b/tests/components/tlc5971/test.esp32-c3-idf.yaml deleted file mode 100644 index d898a21d46..0000000000 --- a/tests/components/tlc5971/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - clock_pin: GPIO5 - data_pin: GPIO4 - -packages: - common: !include common.yaml diff --git a/tests/components/tm1621/test.esp32-c3-idf.yaml b/tests/components/tm1621/test.esp32-c3-idf.yaml deleted file mode 100644 index 562ced7485..0000000000 --- a/tests/components/tm1621/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - cs_pin: GPIO6 - data_pin: GPIO7 - read_pin: GPIO2 - write_pin: GPIO3 - -<<: !include common.yaml diff --git a/tests/components/tm1637/test.esp32-c3-idf.yaml b/tests/components/tm1637/test.esp32-c3-idf.yaml deleted file mode 100644 index 0c4d4a9a7a..0000000000 --- a/tests/components/tm1637/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - clk_pin: GPIO7 - dio_pin: GPIO3 - -<<: !include common.yaml diff --git a/tests/components/tm1638/test.esp32-c3-idf.yaml b/tests/components/tm1638/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/tm1638/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/tm1651/test.esp32-c3-idf.yaml b/tests/components/tm1651/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/tm1651/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/tmp102/test.esp32-c3-idf.yaml b/tests/components/tmp102/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/tmp102/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tmp1075/test.esp32-c3-idf.yaml b/tests/components/tmp1075/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/tmp1075/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tmp117/test.esp32-c3-idf.yaml b/tests/components/tmp117/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/tmp117/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tof10120/test.esp32-c3-idf.yaml b/tests/components/tof10120/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/tof10120/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tormatic/test.esp32-c3-idf.yaml b/tests/components/tormatic/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/tormatic/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/toshiba/test.esp32-c3-idf.yaml b/tests/components/toshiba/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/toshiba/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/toshiba/test_ras2819t.esp32-c3-idf.yaml b/tests/components/toshiba/test_ras2819t.esp32-c3-idf.yaml deleted file mode 100644 index 00805baa01..0000000000 --- a/tests/components/toshiba/test_ras2819t.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - tx_pin: GPIO5 - rx_pin: GPIO4 - -<<: !include common_ras2819t.yaml diff --git a/tests/components/total_daily_energy/test.esp32-c3-idf.yaml b/tests/components/total_daily_energy/test.esp32-c3-idf.yaml deleted file mode 100644 index 8b0d069ce2..0000000000 --- a/tests/components/total_daily_energy/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - sel_pin: GPIO2 - cf_pin: GPIO3 - cf1_pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/tsl2561/test.esp32-c3-idf.yaml b/tests/components/tsl2561/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/tsl2561/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tsl2591/test.esp32-c3-idf.yaml b/tests/components/tsl2591/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/tsl2591/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tt21100/test.esp32-c3-idf.yaml b/tests/components/tt21100/test.esp32-c3-idf.yaml deleted file mode 100644 index a7265e10b2..0000000000 --- a/tests/components/tt21100/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - disp_reset_pin: GPIO7 - interrupt_pin: GPIO2 - reset_pin: GPIO3 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ttp229_bsf/test.esp32-c3-idf.yaml b/tests/components/ttp229_bsf/test.esp32-c3-idf.yaml deleted file mode 100644 index ad1c58b40e..0000000000 --- a/tests/components/ttp229_bsf/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - ttp229_scl_pin: GPIO7 - ttp229_sdo_pin: GPIO4 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ttp229_lsf/test.esp32-c3-idf.yaml b/tests/components/ttp229_lsf/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ttp229_lsf/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tuya/test.esp32-c3-idf.yaml b/tests/components/tuya/test.esp32-c3-idf.yaml deleted file mode 100644 index 43c28ba7b3..0000000000 --- a/tests/components/tuya/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - status_pin: GPIO2 -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/tx20/test.esp32-c3-idf.yaml b/tests/components/tx20/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/tx20/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/udp/test.esp32-c3-idf.yaml b/tests/components/udp/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/udp/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ufire_ec/test.esp32-c3-idf.yaml b/tests/components/ufire_ec/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ufire_ec/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ufire_ise/test.esp32-c3-idf.yaml b/tests/components/ufire_ise/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/ufire_ise/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/uln2003/test.esp32-c3-idf.yaml b/tests/components/uln2003/test.esp32-c3-idf.yaml deleted file mode 100644 index 11d16a4d5d..0000000000 --- a/tests/components/uln2003/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - pin_a: GPIO0 - pin_b: GPIO1 - pin_c: GPIO2 - pin_d: GPIO3 - -<<: !include common.yaml diff --git a/tests/components/ultrasonic/test.esp32-c3-idf.yaml b/tests/components/ultrasonic/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/ultrasonic/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/uponor_smatrix/test.esp32-c3-idf.yaml b/tests/components/uponor_smatrix/test.esp32-c3-idf.yaml deleted file mode 100644 index cd26c783c2..0000000000 --- a/tests/components/uponor_smatrix/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -packages: - uart_19200: !include ../../test_build_components/common/uart_19200/esp32-c3-idf.yaml - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/uptime/test.esp32-c3-idf.yaml b/tests/components/uptime/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/uptime/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/vbus/test.esp32-c3-idf.yaml b/tests/components/vbus/test.esp32-c3-idf.yaml deleted file mode 100644 index a19013bf54..0000000000 --- a/tests/components/vbus/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/veml3235/test.esp32-c3-idf.yaml b/tests/components/veml3235/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/veml3235/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/veml7700/test.esp32-c3-idf.yaml b/tests/components/veml7700/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/veml7700/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/version/test.esp32-c3-idf.yaml b/tests/components/version/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/version/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/vl53l0x/test.esp32-c3-idf.yaml b/tests/components/vl53l0x/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/vl53l0x/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/voice_assistant/test.esp32-c3-idf.yaml b/tests/components/voice_assistant/test.esp32-c3-idf.yaml deleted file mode 100644 index 46745e4308..0000000000 --- a/tests/components/voice_assistant/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - i2s_lrclk_pin: GPIO6 - i2s_bclk_pin: GPIO7 - i2s_mclk_pin: GPIO5 - i2s_din_pin: GPIO3 - i2s_dout_pin: GPIO2 - -<<: !include common-idf.yaml diff --git a/tests/components/wake_on_lan/test.esp32-c3-idf.yaml b/tests/components/wake_on_lan/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/wake_on_lan/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml b/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml deleted file mode 100644 index bdd7e1d350..0000000000 --- a/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - cs_pin: GPIO7 - dc_pin: GPIO1 - busy_pin: GPIO2 - reset_pin: GPIO3 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/web_server/test.esp32-c3-idf.yaml b/tests/components/web_server/test.esp32-c3-idf.yaml deleted file mode 100644 index 7e6658e20e..0000000000 --- a/tests/components/web_server/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common_v2.yaml diff --git a/tests/components/whirlpool/test.esp32-c3-idf.yaml b/tests/components/whirlpool/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/whirlpool/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/whynter/test.esp32-c3-idf.yaml b/tests/components/whynter/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/whynter/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wiegand/test.esp32-c3-idf.yaml b/tests/components/wiegand/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/wiegand/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/wifi/test.esp32-c3-idf.yaml b/tests/components/wifi/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/wifi/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/wifi_info/test.esp32-c3-idf.yaml b/tests/components/wifi_info/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/wifi_info/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wifi_signal/test.esp32-c3-idf.yaml b/tests/components/wifi_signal/test.esp32-c3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/wifi_signal/test.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/wireguard/test.esp32-c3-idf.yaml b/tests/components/wireguard/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/wireguard/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wl_134/test.esp32-c3-idf.yaml b/tests/components/wl_134/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/wl_134/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wts01/test.esp32-c3-idf.yaml b/tests/components/wts01/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/wts01/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/x9c/test.esp32-c3-idf.yaml b/tests/components/x9c/test.esp32-c3-idf.yaml deleted file mode 100644 index b06e15a98c..0000000000 --- a/tests/components/x9c/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,6 +0,0 @@ -substitutions: - cs_pin: GPIO3 - inc_pin: GPIO4 - ud_pin: GPIO5 - -<<: !include common.yaml diff --git a/tests/components/xgzp68xx/test.esp32-c3-idf.yaml b/tests/components/xgzp68xx/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/xgzp68xx/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_ble/test.esp32-c3-idf.yaml b/tests/components/xiaomi_ble/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_ble/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_cgd1/test.esp32-c3-idf.yaml b/tests/components/xiaomi_cgd1/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_cgd1/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_cgdk2/test.esp32-c3-idf.yaml b/tests/components/xiaomi_cgdk2/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_cgdk2/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_cgg1/test.esp32-c3-idf.yaml b/tests/components/xiaomi_cgg1/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_cgg1/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_cgpr1/test.esp32-c3-idf.yaml b/tests/components/xiaomi_cgpr1/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_cgpr1/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_gcls002/test.esp32-c3-idf.yaml b/tests/components/xiaomi_gcls002/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_gcls002/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_hhccjcy01/test.esp32-c3-idf.yaml b/tests/components/xiaomi_hhccjcy01/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_hhccjcy01/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_hhccpot002/test.esp32-c3-idf.yaml b/tests/components/xiaomi_hhccpot002/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_hhccpot002/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_jqjcy01ym/test.esp32-c3-idf.yaml b/tests/components/xiaomi_jqjcy01ym/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_jqjcy01ym/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsd02/test.esp32-c3-idf.yaml b/tests/components/xiaomi_lywsd02/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_lywsd02/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsd02mmc/test.esp32-c3-idf.yaml b/tests/components/xiaomi_lywsd02mmc/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_lywsd02mmc/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsd03mmc/test.esp32-c3-idf.yaml b/tests/components/xiaomi_lywsd03mmc/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_lywsd03mmc/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsdcgq/test.esp32-c3-idf.yaml b/tests/components/xiaomi_lywsdcgq/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_lywsdcgq/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_mhoc303/test.esp32-c3-idf.yaml b/tests/components/xiaomi_mhoc303/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_mhoc303/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_mhoc401/test.esp32-c3-idf.yaml b/tests/components/xiaomi_mhoc401/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_mhoc401/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_miscale/test.esp32-c3-idf.yaml b/tests/components/xiaomi_miscale/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_miscale/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_mjyd02yla/test.esp32-c3-idf.yaml b/tests/components/xiaomi_mjyd02yla/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_mjyd02yla/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_mue4094rt/test.esp32-c3-idf.yaml b/tests/components/xiaomi_mue4094rt/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_mue4094rt/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_rtcgq02lm/test.esp32-c3-idf.yaml b/tests/components/xiaomi_rtcgq02lm/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_rtcgq02lm/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_wx08zm/test.esp32-c3-idf.yaml b/tests/components/xiaomi_wx08zm/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_wx08zm/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xiaomi_xmwsdj04mmc/test.esp32-c3-idf.yaml b/tests/components/xiaomi_xmwsdj04mmc/test.esp32-c3-idf.yaml deleted file mode 100644 index 9f2634f967..0000000000 --- a/tests/components/xiaomi_xmwsdj04mmc/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - ble: !include ../../test_build_components/common/ble/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xl9535/test.esp32-c3-idf.yaml b/tests/components/xl9535/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/xl9535/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/xpt2046/test.esp32-c3-idf.yaml b/tests/components/xpt2046/test.esp32-c3-idf.yaml deleted file mode 100644 index ff7a32c26c..0000000000 --- a/tests/components/xpt2046/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,10 +0,0 @@ -substitutions: - dc_pin: GPIO7 - cs_pin: GPIO0 - disp_cs_pin: GPIO1 - interrupt_pin: GPIO3 - reset_pin: GPIO10 -packages: - spi: !include ../../test_build_components/common/spi/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/yashima/test.esp32-c3-idf.yaml b/tests/components/yashima/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/yashima/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/zhlt01/test.esp32-c3-idf.yaml b/tests/components/zhlt01/test.esp32-c3-idf.yaml deleted file mode 100644 index 43d5343715..0000000000 --- a/tests/components/zhlt01/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/zio_ultrasonic/test.esp32-c3-idf.yaml b/tests/components/zio_ultrasonic/test.esp32-c3-idf.yaml deleted file mode 100644 index 9990d96d29..0000000000 --- a/tests/components/zio_ultrasonic/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/zwave_proxy/test.esp32-c3-idf.yaml b/tests/components/zwave_proxy/test.esp32-c3-idf.yaml deleted file mode 100644 index 4b7c8351a7..0000000000 --- a/tests/components/zwave_proxy/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: -packages: - uart: !include ../../test_build_components/common/uart/esp32-c3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/zyaura/test.esp32-c3-idf.yaml b/tests/components/zyaura/test.esp32-c3-idf.yaml deleted file mode 100644 index 7808481215..0000000000 --- a/tests/components/zyaura/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - clock_pin: GPIO5 - data_pin: GPIO4 - -<<: !include common.yaml From 182e106bfa31c08024f6e9de702651c4c9f4bc31 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 6 Nov 2025 20:44:40 -0600 Subject: [PATCH 0123/1145] [wifi] Guard AP-related members with USE_WIFI_AP to save RAM (#11753) --- esphome/components/wifi/wifi_component.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index ac63e0eb0c..ef595e9891 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -233,6 +233,7 @@ class WiFiComponent : public Component { */ void set_ap(const WiFiAP &ap); WiFiAP get_ap() { return this->ap_; } + void set_ap_timeout(uint32_t ap_timeout) { ap_timeout_ = ap_timeout; } #endif // USE_WIFI_AP void enable(); @@ -241,7 +242,6 @@ class WiFiComponent : public Component { void start_scanning(); void check_scanning_finished(); void start_connecting(const WiFiAP &ap, bool two); - void set_ap_timeout(uint32_t ap_timeout) { ap_timeout_ = ap_timeout; } void check_connecting_finished(); @@ -397,7 +397,9 @@ class WiFiComponent : public Component { std::vector sta_priorities_; wifi_scan_vector_t scan_result_; WiFiAP selected_ap_; +#ifdef USE_WIFI_AP WiFiAP ap_; +#endif optional output_power_; ESPPreferenceObject pref_; #ifdef USE_WIFI_FAST_CONNECT @@ -408,7 +410,9 @@ class WiFiComponent : public Component { uint32_t action_started_; uint32_t last_connected_{0}; uint32_t reboot_timeout_{}; +#ifdef USE_WIFI_AP uint32_t ap_timeout_{}; +#endif // Group all 8-bit values together WiFiComponentState state_{WIFI_COMPONENT_STATE_OFF}; From 7c30d57391ff082e610c575b547298255b4090b0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 6 Nov 2025 21:26:53 -0600 Subject: [PATCH 0124/1145] [wifi] Refactor AP selection to use index instead of copy (saves 88 bytes) (#11749) --- esphome/components/wifi/__init__.py | 8 +- esphome/components/wifi/wifi_component.cpp | 219 +++++++++++++-------- esphome/components/wifi/wifi_component.h | 41 +++- 3 files changed, 177 insertions(+), 91 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index b980bab4aa..5f4190a933 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -54,6 +54,10 @@ AUTO_LOAD = ["network"] NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2, const.VARIANT_ESP32P4] CONF_SAVE = "save" +# Maximum number of WiFi networks that can be configured +# Limited to 127 because selected_sta_index_ is int8_t in C++ +MAX_WIFI_NETWORKS = 127 + wifi_ns = cg.esphome_ns.namespace("wifi") EAPAuth = wifi_ns.struct("EAPAuth") ManualIP = wifi_ns.struct("ManualIP") @@ -260,7 +264,9 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(WiFiComponent), - cv.Optional(CONF_NETWORKS): cv.ensure_list(WIFI_NETWORK_STA), + cv.Optional(CONF_NETWORKS): cv.All( + cv.ensure_list(WIFI_NETWORK_STA), cv.Length(max=MAX_WIFI_NETWORKS) + ), cv.Optional(CONF_SSID): cv.ssid, cv.Optional(CONF_PASSWORD): validate_password, cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA, diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 51b5756f29..789c22bae1 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1,5 +1,6 @@ #include "wifi_component.h" #ifdef USE_WIFI +#include #include #ifdef USE_ESP32 @@ -109,12 +110,15 @@ void WiFiComponent::start() { } #ifdef USE_WIFI_FAST_CONNECT - this->trying_loaded_ap_ = this->load_fast_connect_settings_(); + WiFiAP params; + this->trying_loaded_ap_ = this->load_fast_connect_settings_(params); if (!this->trying_loaded_ap_) { - this->ap_index_ = 0; - this->selected_ap_ = this->sta_[this->ap_index_]; + // FAST CONNECT FALLBACK: No saved settings available + // Use first config (will use SSID from config) + this->selected_sta_index_ = 0; + params = this->build_wifi_ap_from_selected_(); } - this->start_connecting(this->selected_ap_, false); + this->start_connecting(params, false); #else this->start_scanning(); #endif @@ -169,15 +173,16 @@ void WiFiComponent::loop() { this->status_set_warning(LOG_STR("waiting to reconnect")); if (millis() - this->action_started_ > 5000) { #ifdef USE_WIFI_FAST_CONNECT - // NOTE: This check may not make sense here as it could interfere with AP cycling - if (!this->selected_ap_.get_bssid().has_value()) - this->selected_ap_ = this->sta_[0]; - this->start_connecting(this->selected_ap_, false); + // Safety check: Ensure selected_sta_index_ is valid before retrying + // (should already be set by retry_connect(), but check for robustness) + this->reset_selected_ap_to_first_if_invalid_(); + WiFiAP params = this->build_wifi_ap_from_selected_(); + this->start_connecting(params, false); #else if (this->retry_hidden_) { - if (!this->selected_ap_.get_bssid().has_value()) - this->selected_ap_ = this->sta_[0]; - this->start_connecting(this->selected_ap_, false); + this->reset_selected_ap_to_first_if_invalid_(); + WiFiAP params = this->build_wifi_ap_from_selected_(); + this->start_connecting(params, false); } else { this->start_scanning(); } @@ -336,8 +341,42 @@ void WiFiComponent::set_sta(const WiFiAP &ap) { this->clear_sta(); this->init_sta(1); this->add_sta(ap); + this->selected_sta_index_ = 0; +} + +WiFiAP WiFiComponent::build_wifi_ap_from_selected_() const { + // PRECONDITION: selected_sta_index_ must be valid (ensured by all callers) + const WiFiAP *config = this->get_selected_sta_(); + assert(config != nullptr); + WiFiAP params = *config; + + // SYNCHRONIZATION: selected_sta_index_ and scan_result_[0] are kept in sync after wifi_scan_done(): + // - wifi_scan_done() sorts all scan results by priority/RSSI (best first) + // - It then finds which sta_[i] config matches scan_result_[0] + // - Sets selected_sta_index_ = i to record that matching config + // This sync holds until scan_result_ is cleared (e.g., after connection or in reset_for_next_ap_attempt_()) + if (!this->scan_result_.empty()) { + // Override with scan data - network is visible + const WiFiScanResult &scan = this->scan_result_[0]; + params.set_hidden(false); + params.set_ssid(scan.get_ssid()); + params.set_bssid(scan.get_bssid()); + params.set_channel(scan.get_channel()); + } else if (params.get_hidden()) { + // Hidden network - clear BSSID and channel even if set in config + // There might be multiple hidden networks with same SSID but we can't know which is correct + // Rely on probe-req with just SSID. Empty channel triggers ALL_CHANNEL_SCAN. + params.set_bssid(optional{}); + params.set_channel(optional{}); + } + + return params; +} + +WiFiAP WiFiComponent::get_sta() const { + const WiFiAP *config = this->get_selected_sta_(); + return config ? *config : WiFiAP{}; } -void WiFiComponent::clear_sta() { this->sta_.clear(); } void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &password) { SavedWifiSettings save{}; // zero-initialized - all bytes set to \0, guaranteeing null termination strncpy(save.ssid, ssid.c_str(), sizeof(save.ssid) - 1); // max 32 chars, byte 32 remains \0 @@ -485,8 +524,8 @@ void WiFiComponent::print_connect_params_() { LOG_STR_ARG(get_signal_bars(rssi)), get_wifi_channel(), wifi_subnet_mask_().str().c_str(), wifi_gateway_ip_().str().c_str(), wifi_dns_ip_(0).str().c_str(), wifi_dns_ip_(1).str().c_str()); #ifdef ESPHOME_LOG_HAS_VERBOSE - if (this->selected_ap_.get_bssid().has_value()) { - ESP_LOGV(TAG, " Priority: %.1f", this->get_sta_priority(*this->selected_ap_.get_bssid())); + if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_bssid().has_value()) { + ESP_LOGV(TAG, " Priority: %.1f", this->get_sta_priority(*config->get_bssid())); } #endif #ifdef USE_WIFI_11KV_SUPPORT @@ -633,55 +672,38 @@ void WiFiComponent::check_scanning_finished() { log_scan_result(res); } - if (!this->scan_result_[0].get_matches()) { + // SYNCHRONIZATION POINT: Establish link between scan_result_[0] and selected_sta_index_ + // After sorting, scan_result_[0] contains the best network. Now find which sta_[i] config + // matches that network and record it in selected_sta_index_. This keeps the two indices + // synchronized so build_wifi_ap_from_selected_() can safely use both to build connection parameters. + const WiFiScanResult &scan_res = this->scan_result_[0]; + if (!scan_res.get_matches()) { ESP_LOGW(TAG, "No matching network found"); this->retry_connect(); return; } - // Build connection params directly into selected_ap_ to avoid extra copy - const WiFiScanResult &scan_res = this->scan_result_[0]; - WiFiAP &selected = this->selected_ap_; - for (auto &config : this->sta_) { - // search for matching STA config, at least one will match (from checks before) - if (!scan_res.matches(config)) { - continue; + bool found_match = false; + for (size_t i = 0; i < this->sta_.size(); i++) { + if (scan_res.matches(this->sta_[i])) { + // Safe cast: sta_.size() limited to MAX_WIFI_NETWORKS (127) in __init__.py validation + // No overflow check needed - YAML validation prevents >127 networks + this->selected_sta_index_ = static_cast(i); // Links scan_result_[0] with sta_[i] + found_match = true; + break; } + } - if (config.get_hidden()) { - // selected network is hidden, we use the data from the config - selected.set_hidden(true); - selected.set_ssid(config.get_ssid()); - // Clear channel and BSSID for hidden networks - there might be multiple hidden networks - // but we can't know which one is the correct one. Rely on probe-req with just SSID. - selected.set_channel(0); - selected.set_bssid(optional{}); - } else { - // selected network is visible, we use the data from the scan - // limit the connect params to only connect to exactly this network - // (network selection is done during scan phase). - selected.set_hidden(false); - selected.set_ssid(scan_res.get_ssid()); - selected.set_channel(scan_res.get_channel()); - selected.set_bssid(scan_res.get_bssid()); - } - // copy manual IP (if set) - selected.set_manual_ip(config.get_manual_ip()); - -#ifdef USE_WIFI_WPA2_EAP - // copy EAP parameters (if set) - selected.set_eap(config.get_eap()); -#endif - - // copy password (if set) - selected.set_password(config.get_password()); - - break; + if (!found_match) { + ESP_LOGW(TAG, "No matching network found"); + this->retry_connect(); + return; } yield(); - this->start_connecting(this->selected_ap_, false); + WiFiAP params = this->build_wifi_ap_from_selected_(); + this->start_connecting(params, false); } void WiFiComponent::dump_config() { @@ -700,9 +722,12 @@ void WiFiComponent::check_connecting_finished() { } ESP_LOGI(TAG, "Connected"); - // We won't retry hidden networks unless a reconnect fails more than three times again - if (this->retry_hidden_ && !this->selected_ap_.get_hidden()) - ESP_LOGW(TAG, "Network '%s' should be marked as hidden", this->selected_ap_.get_ssid().c_str()); + // Warn if we had to retry with hidden network mode for a network that's not marked hidden + // Only warn if we actually connected without scan data (SSID only), not if scan succeeded on retry + if (const WiFiAP *config = this->get_selected_sta_(); + this->retry_hidden_ && config && !config->get_hidden() && this->scan_result_.empty()) { + ESP_LOGW(TAG, "Network '%s' should be marked as hidden", config->get_ssid().c_str()); + } this->retry_hidden_ = false; this->print_connect_params_(); @@ -725,16 +750,16 @@ void WiFiComponent::check_connecting_finished() { this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED; this->num_retried_ = 0; +#ifdef USE_WIFI_FAST_CONNECT + this->save_fast_connect_settings_(); +#endif + // Free scan results memory unless a component needs them if (!this->keep_scan_results_) { this->scan_result_.clear(); this->scan_result_.shrink_to_fit(); } -#ifdef USE_WIFI_FAST_CONNECT - this->save_fast_connect_settings_(); -#endif - return; } @@ -772,8 +797,8 @@ void WiFiComponent::check_connecting_finished() { } void WiFiComponent::retry_connect() { - if (this->selected_ap_.get_bssid()) { - auto bssid = *this->selected_ap_.get_bssid(); + if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_bssid()) { + auto bssid = *config->get_bssid(); float priority = this->get_sta_priority(bssid); this->set_sta_priority(bssid, priority - 1.0f); } @@ -782,19 +807,26 @@ void WiFiComponent::retry_connect() { if (!this->is_captive_portal_active_() && !this->is_esp32_improv_active_() && (this->num_retried_ > 3 || this->error_from_callback_)) { #ifdef USE_WIFI_FAST_CONNECT + // No empty check needed - YAML validation requires at least one network for fast_connect if (this->trying_loaded_ap_) { this->trying_loaded_ap_ = false; - this->ap_index_ = 0; // Retry from the first configured AP - } else if (this->ap_index_ >= this->sta_.size() - 1) { + this->selected_sta_index_ = 0; // Retry from the first configured AP + this->reset_for_next_ap_attempt_(); + } else if (this->selected_sta_index_ >= static_cast(this->sta_.size()) - 1) { + // Safe cast: sta_.size() limited to MAX_WIFI_NETWORKS (127) in __init__.py validation + // Exhausted all configured APs, restart adapter and cycle back to first + // Restart clears any stuck WiFi driver state + // Each AP is tried with config data only (SSID + optional BSSID/channel if user configured them) + // Typically SSID only, which triggers ESP-IDF internal scanning ESP_LOGW(TAG, "No more APs to try"); - this->ap_index_ = 0; + this->selected_sta_index_ = 0; + this->reset_for_next_ap_attempt_(); this->restart_adapter(); } else { // Try next AP - this->ap_index_++; + this->selected_sta_index_++; + this->reset_for_next_ap_attempt_(); } - this->num_retried_ = 0; - this->selected_ap_ = this->sta_[this->ap_index_]; #else if (this->num_retried_ > 5) { // If retry failed for more than 5 times, let's restart STA @@ -813,7 +845,8 @@ void WiFiComponent::retry_connect() { if (this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTING) { yield(); this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING_2; - this->start_connecting(this->selected_ap_, true); + WiFiAP params = this->build_wifi_ap_from_selected_(); + this->start_connecting(params, true); return; } @@ -852,16 +885,29 @@ bool WiFiComponent::is_esp32_improv_active_() { } #ifdef USE_WIFI_FAST_CONNECT -bool WiFiComponent::load_fast_connect_settings_() { +bool WiFiComponent::load_fast_connect_settings_(WiFiAP ¶ms) { SavedWifiFastConnectSettings fast_connect_save{}; if (this->fast_connect_pref_.load(&fast_connect_save)) { + // Validate saved AP index + if (fast_connect_save.ap_index < 0 || static_cast(fast_connect_save.ap_index) >= this->sta_.size()) { + ESP_LOGW(TAG, "AP index out of bounds"); + return false; + } + + // Set selected index for future operations (save, retry, etc) + this->selected_sta_index_ = fast_connect_save.ap_index; + + // Copy entire config, then override with fast connect data + params = this->sta_[fast_connect_save.ap_index]; + + // Override with saved BSSID/channel from fast connect (SSID/password/etc already copied from config) bssid_t bssid{}; std::copy(fast_connect_save.bssid, fast_connect_save.bssid + 6, bssid.begin()); - this->ap_index_ = fast_connect_save.ap_index; - this->selected_ap_ = this->sta_[this->ap_index_]; - this->selected_ap_.set_bssid(bssid); - this->selected_ap_.set_channel(fast_connect_save.channel); + params.set_bssid(bssid); + params.set_channel(fast_connect_save.channel); + // Fast connect uses specific BSSID+channel, not hidden network probe (even if config has hidden: true) + params.set_hidden(false); ESP_LOGD(TAG, "Loaded fast_connect settings"); return true; @@ -873,18 +919,25 @@ bool WiFiComponent::load_fast_connect_settings_() { void WiFiComponent::save_fast_connect_settings_() { bssid_t bssid = wifi_bssid(); uint8_t channel = get_wifi_channel(); + // selected_sta_index_ is always valid here (called only after successful connection) + // Fallback to 0 is defensive programming for robustness + int8_t ap_index = this->selected_sta_index_ >= 0 ? this->selected_sta_index_ : 0; - if (bssid != this->selected_ap_.get_bssid() || channel != this->selected_ap_.get_channel()) { - SavedWifiFastConnectSettings fast_connect_save{}; - - memcpy(fast_connect_save.bssid, bssid.data(), 6); - fast_connect_save.channel = channel; - fast_connect_save.ap_index = this->ap_index_; - - this->fast_connect_pref_.save(&fast_connect_save); - - ESP_LOGD(TAG, "Saved fast_connect settings"); + // Skip save if settings haven't changed (compare with previously saved settings to reduce flash wear) + SavedWifiFastConnectSettings previous_save{}; + if (this->fast_connect_pref_.load(&previous_save) && memcmp(previous_save.bssid, bssid.data(), 6) == 0 && + previous_save.channel == channel && previous_save.ap_index == ap_index) { + return; // No change, nothing to save } + + SavedWifiFastConnectSettings fast_connect_save{}; + memcpy(fast_connect_save.bssid, bssid.data(), 6); + fast_connect_save.channel = channel; + fast_connect_save.ap_index = ap_index; + + this->fast_connect_pref_.save(&fast_connect_save); + + ESP_LOGD(TAG, "Saved fast_connect settings"); } #endif diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index ef595e9891..cb75edf5a0 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -218,10 +218,14 @@ class WiFiComponent : public Component { WiFiComponent(); void set_sta(const WiFiAP &ap); - WiFiAP get_sta() { return this->selected_ap_; } + // Returns a copy of the currently selected AP configuration + WiFiAP get_sta() const; void init_sta(size_t count); void add_sta(const WiFiAP &ap); - void clear_sta(); + void clear_sta() { + this->sta_.clear(); + this->selected_sta_index_ = -1; + } #ifdef USE_WIFI_AP /** Setup an Access Point that should be created if no connection to a station can be made. @@ -337,6 +341,29 @@ class WiFiComponent : public Component { #endif // USE_WIFI_AP void print_connect_params_(); + WiFiAP build_wifi_ap_from_selected_() const; + + const WiFiAP *get_selected_sta_() const { + if (this->selected_sta_index_ >= 0 && static_cast(this->selected_sta_index_) < this->sta_.size()) { + return &this->sta_[this->selected_sta_index_]; + } + return nullptr; + } + + void reset_selected_ap_to_first_if_invalid_() { + if (this->selected_sta_index_ < 0 || static_cast(this->selected_sta_index_) >= this->sta_.size()) { + this->selected_sta_index_ = this->sta_.empty() ? -1 : 0; + } + } + +#ifdef USE_WIFI_FAST_CONNECT + // Reset state for next fast connect AP attempt + // Clears old scan data so the new AP is tried with config only (SSID without specific BSSID/channel) + void reset_for_next_ap_attempt_() { + this->num_retried_ = 0; + this->scan_result_.clear(); + } +#endif void wifi_loop_(); bool wifi_mode_(optional sta, optional ap); @@ -365,7 +392,7 @@ class WiFiComponent : public Component { bool is_esp32_improv_active_(); #ifdef USE_WIFI_FAST_CONNECT - bool load_fast_connect_settings_(); + bool load_fast_connect_settings_(WiFiAP ¶ms); void save_fast_connect_settings_(); #endif @@ -396,7 +423,6 @@ class WiFiComponent : public Component { FixedVector sta_; std::vector sta_priorities_; wifi_scan_vector_t scan_result_; - WiFiAP selected_ap_; #ifdef USE_WIFI_AP WiFiAP ap_; #endif @@ -418,9 +444,10 @@ class WiFiComponent : public Component { WiFiComponentState state_{WIFI_COMPONENT_STATE_OFF}; WiFiPowerSaveMode power_save_{WIFI_POWER_SAVE_NONE}; uint8_t num_retried_{0}; -#ifdef USE_WIFI_FAST_CONNECT - uint8_t ap_index_{0}; -#endif + // Index into sta_ array for the currently selected AP configuration (-1 = none selected) + // Used to access password, manual_ip, priority, EAP settings, and hidden flag + // int8_t limits to 127 APs (enforced in __init__.py via MAX_WIFI_NETWORKS) + int8_t selected_sta_index_{-1}; #if USE_NETWORK_IPV6 uint8_t num_ipv6_addresses_{0}; #endif /* USE_NETWORK_IPV6 */ From 3c41e080c578f33f4b6d1fcecd5e024b2f30dbab Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 6 Nov 2025 21:37:02 -0600 Subject: [PATCH 0125/1145] [core] Use ESPDEPRECATED macro for deprecation warnings (#11755) --- esphome/components/select/select.h | 2 +- esphome/core/entity_base.h | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index f859594cd1..7459c9d146 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -35,7 +35,7 @@ class Select : public EntityBase { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" /// @deprecated Use current_option() instead. This member will be removed in ESPHome 2026.5.0. - __attribute__((deprecated("Use current_option() instead of .state. Will be removed in 2026.5.0"))) + ESPDEPRECATED("Use current_option() instead of .state. Will be removed in 2026.5.0", "2025.11.0") std::string state{}; Select() = default; diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 6e5362464f..1486ff5360 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -61,9 +61,10 @@ class EntityBase { } // Get/set this entity's icon - [[deprecated("Use get_icon_ref() instead for better performance (avoids string copy). Will stop working in ESPHome " - "2026.5.0")]] std::string - get_icon() const; + ESPDEPRECATED( + "Use get_icon_ref() instead for better performance (avoids string copy). Will be removed in ESPHome 2026.5.0", + "2025.11.0") + std::string get_icon() const; void set_icon(const char *icon); StringRef get_icon_ref() const { static constexpr auto EMPTY_STRING = StringRef::from_lit(""); @@ -160,9 +161,10 @@ class EntityBase { class EntityBase_DeviceClass { // NOLINT(readability-identifier-naming) public: /// Get the device class, using the manual override if set. - [[deprecated("Use get_device_class_ref() instead for better performance (avoids string copy). Will stop working in " - "ESPHome 2026.5.0")]] std::string - get_device_class(); + ESPDEPRECATED("Use get_device_class_ref() instead for better performance (avoids string copy). Will be removed in " + "ESPHome 2026.5.0", + "2025.11.0") + std::string get_device_class(); /// Manually set the device class. void set_device_class(const char *device_class); /// Get the device class as StringRef @@ -178,9 +180,10 @@ class EntityBase_DeviceClass { // NOLINT(readability-identifier-naming) class EntityBase_UnitOfMeasurement { // NOLINT(readability-identifier-naming) public: /// Get the unit of measurement, using the manual override if set. - [[deprecated("Use get_unit_of_measurement_ref() instead for better performance (avoids string copy). Will stop " - "working in ESPHome 2026.5.0")]] std::string - get_unit_of_measurement(); + ESPDEPRECATED("Use get_unit_of_measurement_ref() instead for better performance (avoids string copy). Will be " + "removed in ESPHome 2026.5.0", + "2025.11.0") + std::string get_unit_of_measurement(); /// Manually set the unit of measurement. void set_unit_of_measurement(const char *unit_of_measurement); /// Get the unit of measurement as StringRef From 4f08f0750a66299d4de5e562b4b406ce14bf22f6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 6 Nov 2025 22:34:53 -0600 Subject: [PATCH 0126/1145] [ai_instructions] Add public API and breaking changes guidelines (#11756) --- .ai/instructions.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/.ai/instructions.md b/.ai/instructions.md index 9309c67c65..8d81c6cf0f 100644 --- a/.ai/instructions.md +++ b/.ai/instructions.md @@ -440,3 +440,45 @@ This document provides essential context for AI models interacting with this pro * **Python:** When adding a new Python dependency, add it to the appropriate `requirements*.txt` file and `pyproject.toml`. * **C++ / PlatformIO:** When adding a new C++ dependency, add it to `platformio.ini` and use `cg.add_library`. * **Build Flags:** Use `cg.add_build_flag(...)` to add compiler flags. + +## 8. Public API and Breaking Changes + +* **Public C++ API:** + * **Components**: Only documented features at [esphome.io](https://esphome.io) are public API. Undocumented `public` members are internal. + * **Core/Base Classes** (`esphome/core/`, `Component`, `Sensor`, etc.): All `public` members are public API. + * **Components with Global Accessors** (`global_api_server`, etc.): All `public` members are public API (except config setters). + +* **Public Python API:** + * All documented configuration options at [esphome.io](https://esphome.io) are public API. + * Python code in `esphome/core/` actively used by existing core components is considered stable API. + * Other Python code is internal unless explicitly documented for external component use. + +* **Breaking Changes Policy:** + * Aim for **6-month deprecation window** when possible + * Clean breaks allowed for: signature changes, deep refactorings, resource constraints + * Must document migration path in PR description (generates release notes) + * Blog post required for core/base class changes or significant architectural changes + * Full details: https://developers.esphome.io/contributing/code/#public-api-and-breaking-changes + +* **Breaking Change Checklist:** + - [ ] Clear justification (RAM/flash savings, architectural improvement) + - [ ] Explored non-breaking alternatives + - [ ] Added deprecation warnings if possible (use `ESPDEPRECATED` macro for C++) + - [ ] Documented migration path in PR description with before/after examples + - [ ] Updated all internal usage and esphome-docs + - [ ] Tested backward compatibility during deprecation period + +* **Deprecation Pattern (C++):** + ```cpp + // Remove before 2026.6.0 + ESPDEPRECATED("Use new_method() instead. Removed in 2026.6.0", "2025.12.0") + void old_method() { this->new_method(); } + ``` + +* **Deprecation Pattern (Python):** + ```python + # Remove before 2026.6.0 + if CONF_OLD_KEY in config: + _LOGGER.warning(f"'{CONF_OLD_KEY}' deprecated, use '{CONF_NEW_KEY}'. Removed in 2026.6.0") + config[CONF_NEW_KEY] = config.pop(CONF_OLD_KEY) # Auto-migrate + ``` From 85d2565f25057cba2ca362fa2c40db5122f212b1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 01:18:43 -0600 Subject: [PATCH 0127/1145] [tests] Fix determine_jobs tests failing when target branch is beta (#11758) --- tests/script/test_determine_jobs.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py index 4894a5e28a..cadc7f9cd7 100644 --- a/tests/script/test_determine_jobs.py +++ b/tests/script/test_determine_jobs.py @@ -70,6 +70,13 @@ def mock_changed_files() -> Generator[Mock, None, None]: yield mock +@pytest.fixture +def mock_target_branch_dev() -> Generator[Mock, None, None]: + """Mock get_target_branch to return 'dev' for memory impact tests.""" + with patch.object(determine_jobs, "get_target_branch", return_value="dev") as mock: + yield mock + + @pytest.fixture(autouse=True) def clear_determine_jobs_caches() -> None: """Clear all cached functions before each test.""" @@ -688,6 +695,7 @@ def test_main_detects_components_with_variant_tests( # Tests for detect_memory_impact_config function +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_with_common_platform(tmp_path: Path) -> None: """Test memory impact detection when components share a common platform.""" # Create test directory structure @@ -722,6 +730,7 @@ def test_detect_memory_impact_config_with_common_platform(tmp_path: Path) -> Non assert result["use_merged_config"] == "true" +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_core_only_changes(tmp_path: Path) -> None: """Test memory impact detection with core C++ changes (no component changes).""" # Create test directory structure with fallback component @@ -779,6 +788,7 @@ def test_detect_memory_impact_config_core_python_only_changes(tmp_path: Path) -> assert result["should_run"] == "false" +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_no_common_platform(tmp_path: Path) -> None: """Test memory impact detection when components have no common platform.""" # Create test directory structure @@ -855,6 +865,7 @@ def test_detect_memory_impact_config_no_components_with_tests(tmp_path: Path) -> assert result["should_run"] == "false" +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_includes_base_bus_components( tmp_path: Path, ) -> None: @@ -897,6 +908,7 @@ def test_detect_memory_impact_config_includes_base_bus_components( assert result["platform"] == "esp32-idf" # Common platform +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_with_variant_tests(tmp_path: Path) -> None: """Test memory impact detection for components with only variant test files. @@ -1125,6 +1137,7 @@ def test_main_core_files_changed_still_detects_components( assert len(output["changed_components"]) > 0 +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_filters_incompatible_esp32_on_esp8266( tmp_path: Path, ) -> None: @@ -1178,6 +1191,7 @@ def test_detect_memory_impact_config_filters_incompatible_esp32_on_esp8266( assert result["use_merged_config"] == "true" +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_filters_incompatible_esp8266_on_esp32( tmp_path: Path, ) -> None: From a5bf55b6acbf6c1715f20487c167a7ff2593ed2a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 01:19:45 -0600 Subject: [PATCH 0128/1145] =?UTF-8?q?[ci]=20Fix=20component=20batching=20f?= =?UTF-8?q?or=20beta/release=20branches=20(3-4=20=E2=86=92=2040=20per=20ba?= =?UTF-8?q?tch)=20(#11759)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/determine-jobs.py | 18 +++- tests/script/test_determine_jobs.py | 148 ++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 1 deletion(-) diff --git a/script/determine-jobs.py b/script/determine-jobs.py index 39a7571fbe..5cc3f2570a 100755 --- a/script/determine-jobs.py +++ b/script/determine-jobs.py @@ -756,11 +756,27 @@ def main() -> None: component_test_batches: list[str] if changed_components_with_tests: tests_dir = Path(root_path) / ESPHOME_TESTS_COMPONENTS_PATH + + # For beta/release branches, group all components for faster CI + # (no isolation, all components are groupable) + target_branch = get_target_branch() + is_release_branch = target_branch and ( + target_branch.startswith("release") or target_branch.startswith("beta") + ) + + if is_release_branch: + # For beta/release: Don't isolate any components - group everything + # This allows components to be merged into single builds + batch_directly_changed = set() # Empty set - no isolation + else: + # Normal PR: only directly changed components are isolated + batch_directly_changed = directly_changed_with_tests + batches, _ = create_intelligent_batches( components=changed_components_with_tests, tests_dir=tests_dir, batch_size=COMPONENT_TEST_BATCH_SIZE, - directly_changed=directly_changed_with_tests, + directly_changed=batch_directly_changed, ) # Convert batches to space-separated strings for CI matrix component_test_batches = [" ".join(batch) for batch in batches] diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py index cadc7f9cd7..291a23967b 100644 --- a/tests/script/test_determine_jobs.py +++ b/tests/script/test_determine_jobs.py @@ -19,6 +19,8 @@ sys.path.insert(0, script_dir) # Import helpers module for patching import helpers # noqa: E402 +import script.helpers # noqa: E402 + spec = importlib.util.spec_from_file_location( "determine_jobs", os.path.join(script_dir, "determine-jobs.py") ) @@ -132,6 +134,16 @@ def test_main_all_tests_should_run( ["wifi", "api"] if not deps else ["wifi", "api", "sensor"] ), ), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), + patch.object( + determine_jobs, + "create_intelligent_batches", + return_value=([["wifi", "api", "sensor"]], {}), + ), ): determine_jobs.main() @@ -203,6 +215,16 @@ def test_main_no_tests_should_run( patch.object( determine_jobs, "get_components_with_dependencies", return_value=[] ), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), + patch.object( + determine_jobs, + "create_intelligent_batches", + return_value=([], {}), + ), ): determine_jobs.main() @@ -266,6 +288,16 @@ def test_main_with_branch_argument( patch.object( determine_jobs, "get_components_with_dependencies", return_value=["mqtt"] ), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), + patch.object( + determine_jobs, + "create_intelligent_batches", + return_value=([["mqtt"]], {}), + ), ): determine_jobs.main() @@ -571,6 +603,11 @@ def test_main_filters_components_without_tests( ), ), patch.object(determine_jobs, "changed_files", return_value=[]), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), ): # Clear the cache since we're mocking root_path determine_jobs.main() @@ -670,6 +707,11 @@ def test_main_detects_components_with_variant_tests( ), ), patch.object(determine_jobs, "changed_files", return_value=[]), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), ): # Clear the cache since we're mocking root_path determine_jobs.main() @@ -1124,6 +1166,16 @@ def test_main_core_files_changed_still_detects_components( else ["select", "api", "bluetooth_proxy", "logger"] ), ), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), + patch.object( + determine_jobs, + "create_intelligent_batches", + return_value=([["select", "api", "bluetooth_proxy", "logger"]], {}), + ), ): determine_jobs.main() @@ -1367,3 +1419,99 @@ def test_detect_memory_impact_config_runs_at_component_limit(tmp_path: Path) -> # Memory impact should run at exactly 40 components (at limit but not over) assert result["should_run"] == "true" assert len(result["components"]) == 40 + + +def test_component_batching_beta_branch_40_per_batch( + tmp_path: Path, + mock_should_run_integration_tests: Mock, + mock_should_run_clang_tidy: Mock, + mock_should_run_clang_format: Mock, + mock_should_run_python_linters: Mock, + mock_changed_files: Mock, + mock_determine_cpp_unit_tests: Mock, + capsys: pytest.CaptureFixture[str], +) -> None: + """Test that beta/release branches create batches with 40 actual components each. + + For beta/release branches, all components should be groupable (not isolated), + and each batch should contain 40 actual components with weight 1 each. + This matches the original behavior before consolidation. + """ + # Create 120 test components with test files + component_names = [f"comp_{i:03d}" for i in range(120)] + tests_dir = tmp_path / "tests" / "components" + + for comp in component_names: + comp_dir = tests_dir / comp + comp_dir.mkdir(parents=True) + (comp_dir / "test.esp32-idf.yaml").write_text(f"# Test for {comp}") + + # Setup mocks + mock_should_run_integration_tests.return_value = False + mock_should_run_clang_tidy.return_value = False + mock_should_run_clang_format.return_value = False + mock_should_run_python_linters.return_value = False + mock_determine_cpp_unit_tests.return_value = (False, []) + + # Mock changed_files to return all component files + changed_files = [ + f"esphome/components/{comp}/{comp}.cpp" for comp in component_names + ] + mock_changed_files.return_value = changed_files + + # Run main function with beta branch + # Don't mock create_intelligent_batches - that's what we're testing! + with ( + patch("sys.argv", ["determine-jobs.py", "--branch", "beta"]), + patch.object(determine_jobs, "root_path", str(tmp_path)), + patch.object(helpers, "root_path", str(tmp_path)), + patch.object(script.helpers, "root_path", str(tmp_path)), + patch.object(determine_jobs, "get_target_branch", return_value="beta"), + patch.object(determine_jobs, "_is_clang_tidy_full_scan", return_value=False), + patch.object( + determine_jobs, + "get_changed_components", + return_value=component_names, + ), + patch.object( + determine_jobs, + "filter_component_and_test_files", + side_effect=lambda f: f.startswith("esphome/components/"), + ), + patch.object( + determine_jobs, + "get_components_with_dependencies", + side_effect=lambda files, deps: component_names, + ), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), + ): + determine_jobs.main() + + # Check output + captured = capsys.readouterr() + output = json.loads(captured.out) + + # Verify batches are present and properly sized + assert "component_test_batches" in output + batches = output["component_test_batches"] + + # Should have 3 batches (120 components / 40 per batch = 3) + assert len(batches) == 3, f"Expected 3 batches, got {len(batches)}" + + # Each batch should have approximately 40 components (all weight=1, groupable) + for i, batch_str in enumerate(batches): + batch_components = batch_str.split() + assert len(batch_components) == 40, ( + f"Batch {i} should have 40 components, got {len(batch_components)}" + ) + + # Verify all 120 components are in batches + all_components = [] + for batch_str in batches: + all_components.extend(batch_str.split()) + assert len(all_components) == 120 + assert set(all_components) == set(component_names) From 79d1a558af778df183764b656a838ec650d9fb47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 20:12:15 +0000 Subject: [PATCH 0129/1145] Bump ruff from 0.14.3 to 0.14.4 (#11768) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5356bffd96..dab660b03f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.3 + rev: v0.14.4 hooks: # Run the linter. - id: ruff diff --git a/requirements_test.txt b/requirements_test.txt index 11367172b1..81cb711eec 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==4.0.2 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.14.3 # also change in .pre-commit-config.yaml when updating +ruff==0.14.4 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.0 # also change in .pre-commit-config.yaml when updating pre-commit From c77bb3b269bf7fba517fb359535c59cccef49152 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 15:46:16 -0600 Subject: [PATCH 0130/1145] [event] Store event types in flash memory (#11767) --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_connection.cpp | 3 +- esphome/components/api/api_pb2.cpp | 10 +++--- esphome/components/api/api_pb2.h | 2 +- esphome/components/api/api_pb2_dump.cpp | 2 +- esphome/components/event/event.cpp | 30 +++++++++++++---- esphome/components/event/event.h | 35 +++++++++++++++++--- esphome/components/mqtt/mqtt_event.cpp | 4 +-- esphome/components/web_server/web_server.cpp | 5 +-- 9 files changed, 67 insertions(+), 26 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 7a50fa6b17..e115e4630d 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -2147,7 +2147,7 @@ message ListEntitiesEventResponse { EntityCategory entity_category = 7; string device_class = 8; - repeated string event_types = 9; + repeated string event_types = 9 [(container_pointer_no_template) = "FixedVector"]; uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"]; } message EventResponse { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5ab8a6eb05..8c293b41a2 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1310,8 +1310,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c auto *event = static_cast(entity); ListEntitiesEventResponse msg; msg.set_device_class(event->get_device_class_ref()); - for (const auto &event_type : event->get_event_types()) - msg.event_types.push_back(event_type); + msg.event_types = &event->get_event_types(); return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index dfa1a1320f..0a073fb662 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2877,8 +2877,8 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_string(8, this->device_class_ref_); - for (auto &it : this->event_types) { - buffer.encode_string(9, it, true); + for (const char *it : *this->event_types) { + buffer.encode_string(9, it, strlen(it), true); } #ifdef USE_DEVICES buffer.encode_uint32(10, this->device_id); @@ -2894,9 +2894,9 @@ void ListEntitiesEventResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); size.add_length(1, this->device_class_ref_.size()); - if (!this->event_types.empty()) { - for (const auto &it : this->event_types) { - size.add_length_force(1, it.size()); + if (!this->event_types->empty()) { + for (const char *it : *this->event_types) { + size.add_length_force(1, strlen(it)); } } #ifdef USE_DEVICES diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 716f1a6e9b..358049026e 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -2788,7 +2788,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage { #endif StringRef device_class_ref_{}; void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } - std::vector event_types{}; + const FixedVector *event_types{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index d94ceaaa9c..d9662483bf 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -2053,7 +2053,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const { dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); dump_field(out, "device_class", this->device_class_ref_); - for (const auto &it : this->event_types) { + for (const auto &it : *this->event_types) { dump_field(out, "event_types", it, 4); } #ifdef USE_DEVICES diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp index 20549ad0a5..a14afbd7f5 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -8,11 +8,11 @@ namespace event { static const char *const TAG = "event"; void Event::trigger(const std::string &event_type) { - // Linear search - faster than std::set for small datasets (1-5 items typical) - const std::string *found = nullptr; - for (const auto &type : this->types_) { - if (type == event_type) { - found = &type; + // Linear search with strcmp - faster than std::set for small datasets (1-5 items typical) + const char *found = nullptr; + for (const char *type : this->types_) { + if (strcmp(type, event_type.c_str()) == 0) { + found = type; break; } } @@ -20,11 +20,27 @@ void Event::trigger(const std::string &event_type) { ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str()); return; } - last_event_type = found; - ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str()); + this->last_event_type_ = found; + ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), this->last_event_type_); this->event_callback_.call(event_type); } +void Event::set_event_types(const FixedVector &event_types) { + this->types_.init(event_types.size()); + for (const char *type : event_types) { + this->types_.push_back(type); + } + this->last_event_type_ = nullptr; // Reset when types change +} + +void Event::set_event_types(const std::vector &event_types) { + this->types_.init(event_types.size()); + for (const char *type : event_types) { + this->types_.push_back(type); + } + this->last_event_type_ = nullptr; // Reset when types change +} + void Event::add_on_event_callback(std::function &&callback) { this->event_callback_.add(std::move(callback)); } diff --git a/esphome/components/event/event.h b/esphome/components/event/event.h index 2f6267a200..e4b2e0b845 100644 --- a/esphome/components/event/event.h +++ b/esphome/components/event/event.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include "esphome/core/component.h" #include "esphome/core/entity_base.h" @@ -22,16 +24,39 @@ namespace event { class Event : public EntityBase, public EntityBase_DeviceClass { public: - const std::string *last_event_type; - void trigger(const std::string &event_type); - void set_event_types(const std::initializer_list &event_types) { this->types_ = event_types; } - const FixedVector &get_event_types() const { return this->types_; } + + /// Set the event types supported by this event (from initializer list). + void set_event_types(std::initializer_list event_types) { + this->types_ = event_types; + this->last_event_type_ = nullptr; // Reset when types change + } + /// Set the event types supported by this event (from FixedVector). + void set_event_types(const FixedVector &event_types); + /// Set the event types supported by this event (from vector). + void set_event_types(const std::vector &event_types); + + // Deleted overloads to catch incorrect std::string usage at compile time with clear error messages + void set_event_types(std::initializer_list event_types) = delete; + void set_event_types(const FixedVector &event_types) = delete; + void set_event_types(const std::vector &event_types) = delete; + + /// Return the event types supported by this event. + const FixedVector &get_event_types() const { return this->types_; } + + /// Return the last triggered event type (pointer to string in types_), or nullptr if no event triggered yet. + const char *get_last_event_type() const { return this->last_event_type_; } + void add_on_event_callback(std::function &&callback); protected: CallbackManager event_callback_; - FixedVector types_; + FixedVector types_; + + private: + /// Last triggered event type - must point to entry in types_ to ensure valid lifetime. + /// Set by trigger() after validation, reset to nullptr when types_ changes. + const char *last_event_type_{nullptr}; }; } // namespace event diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp index e206335446..fd095ea041 100644 --- a/esphome/components/mqtt/mqtt_event.cpp +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -38,8 +38,8 @@ void MQTTEventComponent::setup() { void MQTTEventComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT Event '%s': ", this->event_->get_name().c_str()); ESP_LOGCONFIG(TAG, "Event Types: "); - for (const auto &event_type : this->event_->get_event_types()) { - ESP_LOGCONFIG(TAG, "- %s", event_type.c_str()); + for (const char *event_type : this->event_->get_event_types()) { + ESP_LOGCONFIG(TAG, "- %s", event_type); } LOG_MQTT_COMPONENT(true, true); } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index f1d1a75875..91ca076474 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1628,7 +1628,8 @@ void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMa } static std::string get_event_type(event::Event *event) { - return (event && event->last_event_type) ? *event->last_event_type : ""; + const char *last_type = event ? event->get_last_event_type() : nullptr; + return last_type ? last_type : ""; } std::string WebServer::event_state_json_generator(WebServer *web_server, void *source) { @@ -1649,7 +1650,7 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty } if (start_config == DETAIL_ALL) { JsonArray event_types = root["event_types"].to(); - for (auto const &event_type : obj->get_event_types()) { + for (const char *event_type : obj->get_event_types()) { event_types.add(event_type); } root["device_class"] = obj->get_device_class_ref(); From f55c87218075a63bdf39629a38aa34cab3443e40 Mon Sep 17 00:00:00 2001 From: optimusprimespace <62800678+optimusprimespace@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:56:51 +0200 Subject: [PATCH 0131/1145] Updated AQI calculation for HM3301 to the new standard (#9442) Co-authored-by: J. Nick Koston --- esphome/components/hm3301/aqi_calculator.h | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/hm3301/aqi_calculator.h index c1b47826a2..aa01060d2c 100644 --- a/esphome/components/hm3301/aqi_calculator.h +++ b/esphome/components/hm3301/aqi_calculator.h @@ -1,7 +1,7 @@ #pragma once - +#include #include "abstract_aqi_calculator.h" -// https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf +// https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf namespace esphome { namespace hm3301 { @@ -16,16 +16,15 @@ class AQICalculator : public AbstractAQICalculator { } protected: - static const int AMOUNT_OF_LEVELS = 7; + static const int AMOUNT_OF_LEVELS = 6; - int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, - {201, 300}, {301, 400}, {401, 500}}; + int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}}; - int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 35}, {36, 55}, {56, 150}, - {151, 250}, {251, 350}, {351, 500}}; + int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, + {56, 125}, {126, 225}, {226, INT_MAX}}; - int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, {255, 354}, - {355, 424}, {425, 504}, {505, 604}}; + int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, + {255, 354}, {355, 424}, {425, INT_MAX}}; int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { int grid_index = get_grid_index_(value, array); From b61027607f53f9344b924a5827f34e1513e44257 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Nov 2025 15:22:40 -0600 Subject: [PATCH 0132/1145] Bump aioesphomeapi from 42.6.0 to 42.7.0 (#11771) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 33fa2b64eb..40802422f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==42.6.0 +aioesphomeapi==42.7.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.16 # dashboard_import From a290b88cd6fa86566068370b3ee7e0723ecdd967 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:09:03 -0600 Subject: [PATCH 0133/1145] Expand uart.write tests (#11785) --- tests/components/uart/test.esp32-idf.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/components/uart/test.esp32-idf.yaml b/tests/components/uart/test.esp32-idf.yaml index 9744a48409..6ffd0d7282 100644 --- a/tests/components/uart/test.esp32-idf.yaml +++ b/tests/components/uart/test.esp32-idf.yaml @@ -3,6 +3,8 @@ esphome: then: - uart.write: 'Hello World' - uart.write: [0x00, 0x20, 0x42] + - uart.write: !lambda |- + return {0xAA, 0xBB, 0xCC}; uart: - id: uart_uart @@ -46,6 +48,15 @@ switch: turn_on: "TURN_ON" turn_off: "TURN_OFF" +number: + - platform: template + name: "Test Number" + id: test_number + optimistic: true + min_value: 0 + max_value: 100 + step: 1 + button: # Test uart button with array data - platform: uart @@ -57,3 +68,10 @@ button: name: "UART Button String" uart_id: uart_uart data: "BUTTON_PRESS" + # Test uart button with lambda (function pointer) + - platform: template + name: "UART Lambda Test" + on_press: + - uart.write: !lambda |- + std::string cmd = "VALUE=" + str_sprintf("%.0f", id(test_number).state) + "\r\n"; + return std::vector(cmd.begin(), cmd.end()); From b49619d9bf17eef24f6029af9d2dfa9c5be722a7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:09:25 -0600 Subject: [PATCH 0134/1145] Add ble_client lambda compile tests (#11787) --- tests/components/ble_client/common.yaml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/components/ble_client/common.yaml b/tests/components/ble_client/common.yaml index aa4b639463..4ea1dd60f3 100644 --- a/tests/components/ble_client/common.yaml +++ b/tests/components/ble_client/common.yaml @@ -52,3 +52,25 @@ sensor: name: "BLE Sensor without Lambda" service_uuid: "abcd1234-abcd-1234-abcd-abcd12345678" characteristic_uuid: "abcd1237-abcd-1234-abcd-abcd12345678" + +number: + - platform: template + name: "Test Number" + id: test_number + optimistic: true + min_value: 0 + max_value: 255 + step: 1 + +button: + # Test ble_write with lambda that references a component (function pointer) + - platform: template + name: "BLE Write Lambda Test" + on_press: + - ble_client.ble_write: + id: test_blec + service_uuid: "abcd1234-abcd-1234-abcd-abcd12345678" + characteristic_uuid: "abcd1235-abcd-1234-abcd-abcd12345678" + value: !lambda |- + uint8_t val = (uint8_t)id(test_number).state; + return std::vector{0xAA, val, 0xBB}; From 783dbd1e6b6f462317ed5881df871c87903ea442 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:09:46 -0600 Subject: [PATCH 0135/1145] Add additional compile time tests for canbus (#11789) --- tests/components/canbus/common.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/components/canbus/common.yaml b/tests/components/canbus/common.yaml index fd146cc3a3..8bddeb7409 100644 --- a/tests/components/canbus/common.yaml +++ b/tests/components/canbus/common.yaml @@ -37,6 +37,15 @@ canbus: break; } +number: + - platform: template + name: "Test Number" + id: test_number + optimistic: true + min_value: 0 + max_value: 255 + step: 1 + button: - platform: template name: Canbus Actions @@ -44,3 +53,7 @@ button: - canbus.send: "abc" - canbus.send: [0, 1, 2] - canbus.send: !lambda return {0, 1, 2}; + # Test canbus.send with lambda that references a component (function pointer) + - canbus.send: !lambda |- + uint8_t val = (uint8_t)id(test_number).state; + return std::vector{0xAA, val, 0xBB}; From 4c078dea2c035fec594c1abfed51c0d03e51de2d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:10:31 -0600 Subject: [PATCH 0136/1145] Add additional sx126x lambda tests (#11791) --- tests/components/sx126x/common.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/components/sx126x/common.yaml b/tests/components/sx126x/common.yaml index 3f540a4bae..659550cc01 100644 --- a/tests/components/sx126x/common.yaml +++ b/tests/components/sx126x/common.yaml @@ -26,6 +26,15 @@ sx126x: - lambda: |- ESP_LOGD("lambda", "packet %.2f %.2f %s", rssi, snr, format_hex(x).c_str()); +number: + - platform: template + name: "SX126x Number" + id: my_number + optimistic: true + min_value: 0 + max_value: 100 + step: 1 + button: - platform: template name: "SX126x Button" @@ -37,3 +46,5 @@ button: - sx126x.set_mode_rx - sx126x.send_packet: data: [0xC5, 0x51, 0x78, 0x82, 0xB7, 0xF9, 0x9C, 0x5C] + - sx126x.send_packet: !lambda |- + return {0x01, 0x02, (uint8_t)id(my_number).state}; From e468ca48812ef2ab649ecc4f8649da6975dabc4d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:11:31 -0600 Subject: [PATCH 0137/1145] Add additional sx127x lambda tests (#11793) --- tests/components/sx127x/common.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/components/sx127x/common.yaml b/tests/components/sx127x/common.yaml index 540381fc08..6e48952fcc 100644 --- a/tests/components/sx127x/common.yaml +++ b/tests/components/sx127x/common.yaml @@ -26,6 +26,15 @@ sx127x: - sx127x.send_packet: data: [0xC5, 0x51, 0x78, 0x82, 0xB7, 0xF9, 0x9C, 0x5C] +number: + - platform: template + name: "SX127x Number" + id: my_number + optimistic: true + min_value: 0 + max_value: 100 + step: 1 + button: - platform: template name: "SX127x Button" @@ -38,3 +47,5 @@ button: - sx127x.set_mode_rx - sx127x.send_packet: data: [0xC5, 0x51, 0x78, 0x82, 0xB7, 0xF9, 0x9C, 0x5C] + - sx127x.send_packet: !lambda |- + return {0x01, 0x02, (uint8_t)id(my_number).state}; From 55853552637893cbb4ca4e8be25f2314b6614b21 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:15:50 -0600 Subject: [PATCH 0138/1145] Add additional speaker lambda tests (#11797) --- tests/components/speaker/common.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/components/speaker/common.yaml b/tests/components/speaker/common.yaml index c04674ee29..fa54fa7e39 100644 --- a/tests/components/speaker/common.yaml +++ b/tests/components/speaker/common.yaml @@ -1,3 +1,12 @@ +number: + - platform: template + name: "Speaker Number" + id: my_number + optimistic: true + min_value: 0 + max_value: 100 + step: 1 + esphome: on_boot: then: @@ -14,6 +23,15 @@ esphome: - speaker.finish: - speaker.stop: +button: + - platform: template + name: "Speaker Button" + on_press: + then: + - speaker.play: [0x10, 0x20, 0x30, 0x40] + - speaker.play: !lambda |- + return {0x01, 0x02, (uint8_t)id(my_number).state}; + i2s_audio: i2s_lrclk_pin: ${i2s_bclk_pin} i2s_bclk_pin: ${i2s_lrclk_pin} From eb0558ca3fb71c10516c96f366d2880f1e325717 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:16:09 -0600 Subject: [PATCH 0139/1145] Add additional udp lambda tests (#11795) --- tests/components/udp/common.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/components/udp/common.yaml b/tests/components/udp/common.yaml index 96224d0d1f..98546d49ef 100644 --- a/tests/components/udp/common.yaml +++ b/tests/components/udp/common.yaml @@ -17,3 +17,22 @@ udp: id: my_udp data: !lambda |- return std::vector{1,3,4,5,6}; + +number: + - platform: template + name: "UDP Number" + id: my_number + optimistic: true + min_value: 0 + max_value: 100 + step: 1 + +button: + - platform: template + name: "UDP Button" + on_press: + then: + - udp.write: + data: [0x01, 0x02, 0x03] + - udp.write: !lambda |- + return {0x10, 0x20, (uint8_t)id(my_number).state}; From f7179d42557b926f1f30adc1c4a6acd433491715 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:16:53 -0600 Subject: [PATCH 0140/1145] Add additonal abbwelcome remote_base tests (#11799) --- .../remote_transmitter/common-buttons.yaml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/components/remote_transmitter/common-buttons.yaml b/tests/components/remote_transmitter/common-buttons.yaml index e9593cc97c..101d60a893 100644 --- a/tests/components/remote_transmitter/common-buttons.yaml +++ b/tests/components/remote_transmitter/common-buttons.yaml @@ -1,3 +1,11 @@ +number: + - platform: template + id: test_number + optimistic: true + min_value: 0 + max_value: 255 + step: 1 + button: - platform: template name: Beo4 audio mute @@ -217,6 +225,23 @@ button: command: 0xEC rc_code_1: 0x0D rc_code_2: 0x0D + - platform: template + name: ABBWelcome static + on_press: + remote_transmitter.transmit_abbwelcome: + source_address: 0x1234 + destination_address: 0x5678 + message_type: 0x01 + data: [0x10, 0x20, 0x30] + - platform: template + name: ABBWelcome lambda + on_press: + remote_transmitter.transmit_abbwelcome: + source_address: 0x1234 + destination_address: 0x5678 + message_type: 0x01 + data: !lambda |- + return {(uint8_t)id(test_number).state, 0x20, 0x30}; - platform: template name: Digital Write on_press: From 5f9c7a70ff8721d0f80c739827810e166b870cb6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:17:14 -0600 Subject: [PATCH 0141/1145] Add additional tests for remote_transmitter raw (#11801) --- tests/components/remote_transmitter/common-buttons.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/components/remote_transmitter/common-buttons.yaml b/tests/components/remote_transmitter/common-buttons.yaml index 101d60a893..d48d36bd54 100644 --- a/tests/components/remote_transmitter/common-buttons.yaml +++ b/tests/components/remote_transmitter/common-buttons.yaml @@ -136,10 +136,16 @@ button: address: 0x00 command: 0x0B - platform: template - name: RC5 Raw + name: RC5 Raw static on_press: remote_transmitter.transmit_raw: code: [1000, -1000] + - platform: template + name: RC5 Raw lambda + on_press: + remote_transmitter.transmit_raw: + code: !lambda |- + return {(int32_t)id(test_number).state * 100, -1000}; - platform: template name: AEHA id: eaha_hitachi_climate_power_on From 870b2c4f848b6bc0ad123cb907a7012300db02e9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:21:25 -0600 Subject: [PATCH 0142/1145] [ble_client] Optimize ble_write memory usage - store static data in flash (#11786) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ble_client/__init__.py | 8 +++- esphome/components/ble_client/automation.h | 43 +++++++++------------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 768a345213..37db181584 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -15,6 +15,7 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_VALUE, ) +from esphome.core import ID AUTO_LOAD = ["esp32_ble_client"] CODEOWNERS = ["@buxtronix", "@clydebarrow"] @@ -198,7 +199,12 @@ async def ble_write_to_code(config, action_id, template_arg, args): templ = await cg.templatable(value, args, cg.std_vector.template(cg.uint8)) cg.add(var.set_value_template(templ)) else: - cg.add(var.set_value_simple(value)) + # Generate static array in flash to avoid RAM copy + if isinstance(value, bytes): + value = list(value) + arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) + arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*value)) + cg.add(var.set_value_simple(arr, len(value))) if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): cg.add( diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index ce534501f3..9c5646b3d1 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -96,11 +96,8 @@ template class BLEClientWriteAction : public Action, publ BLEClientWriteAction(BLEClient *ble_client) { ble_client->register_ble_node(this); ble_client_ = ble_client; - this->construct_simple_value_(); } - ~BLEClientWriteAction() { this->destroy_simple_value_(); } - void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } @@ -110,17 +107,14 @@ template class BLEClientWriteAction : public Action, publ void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } void set_value_template(std::vector (*func)(Ts...)) { - this->destroy_simple_value_(); - this->value_.template_func = func; - this->has_simple_value_ = false; + this->value_.func = func; + this->len_ = -1; // Sentinel value indicates template mode } - void set_value_simple(const std::vector &value) { - if (!this->has_simple_value_) { - this->construct_simple_value_(); - } - this->value_.simple = value; - this->has_simple_value_ = true; + // Store pointer to static data in flash (no RAM copy) + void set_value_simple(const uint8_t *data, size_t len) { + this->value_.data = data; + this->len_ = len; // Length >= 0 indicates static mode } void play(const Ts &...x) override {} @@ -128,7 +122,14 @@ template class BLEClientWriteAction : public Action, publ void play_complex(const Ts &...x) override { this->num_running_++; this->var_ = std::make_tuple(x...); - auto value = this->has_simple_value_ ? this->value_.simple : this->value_.template_func(x...); + std::vector value; + if (this->len_ >= 0) { + // Static mode: copy from flash to vector + value.assign(this->value_.data, this->value_.data + this->len_); + } else { + // Template mode: call function + value = this->value_.func(x...); + } // on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work. if (!write(value)) this->play_next_(x...); @@ -201,21 +202,11 @@ template class BLEClientWriteAction : public Action, publ } private: - void construct_simple_value_() { new (&this->value_.simple) std::vector(); } - - void destroy_simple_value_() { - if (this->has_simple_value_) { - this->value_.simple.~vector(); - } - } - BLEClient *ble_client_; - bool has_simple_value_ = true; + ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length union Value { - std::vector simple; - std::vector (*template_func)(Ts...); - Value() {} // trivial constructor - ~Value() {} // trivial destructor - we manage lifetime via discriminator + std::vector (*func)(Ts...); // Function pointer (stateless lambdas) + const uint8_t *data; // Pointer to static data in flash } value_; espbt::ESPBTUUID service_uuid_; espbt::ESPBTUUID char_uuid_; From 3bcbfe8d9756ef97a47dff0ed4e641919022b0fb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:22:15 -0600 Subject: [PATCH 0143/1145] [canbus] Optimize canbus.send memory usage - store static data in flash (#11788) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/canbus/__init__.py | 7 ++++-- esphome/components/canbus/canbus.h | 34 +++++++++++++++++---------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index e1de1eb2f2..7b51c2c45c 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -4,7 +4,7 @@ from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_DATA, CONF_ID, CONF_TRIGGER_ID -from esphome.core import CORE +from esphome.core import CORE, ID CODEOWNERS = ["@mvturnho", "@danielschramm"] IS_PLATFORM_COMPONENT = True @@ -176,5 +176,8 @@ async def canbus_action_to_code(config, action_id, template_arg, args): else: if isinstance(data, bytes): data = [int(x) for x in data] - cg.add(var.set_data_static(data)) + # Generate static array in flash to avoid RAM copy + arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) + arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data)) + cg.add(var.set_data_static(arr, len(data))) return var diff --git a/esphome/components/canbus/canbus.h b/esphome/components/canbus/canbus.h index 029eb278c0..f7b84111bd 100644 --- a/esphome/components/canbus/canbus.h +++ b/esphome/components/canbus/canbus.h @@ -112,13 +112,16 @@ class Canbus : public Component { template class CanbusSendAction : public Action, public Parented { public: - void set_data_template(const std::function(Ts...)> func) { - this->data_func_ = func; - this->static_ = false; + void set_data_template(std::vector (*func)(Ts...)) { + // Stateless lambdas (generated by ESPHome) implicitly convert to function pointers + this->data_.func = func; + this->len_ = -1; // Sentinel value indicates template mode } - void set_data_static(const std::vector &data) { - this->data_static_ = data; - this->static_ = true; + + // Store pointer to static data in flash (no RAM copy) + void set_data_static(const uint8_t *data, size_t len) { + this->data_.data = data; + this->len_ = len; // Length >= 0 indicates static mode } void set_can_id(uint32_t can_id) { this->can_id_ = can_id; } @@ -133,21 +136,26 @@ template class CanbusSendAction : public Action, public P auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_; auto use_extended_id = this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_; - if (this->static_) { - this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, this->data_static_); + std::vector data; + if (this->len_ >= 0) { + // Static mode: copy from flash to vector + data.assign(this->data_.data, this->data_.data + this->len_); } else { - auto val = this->data_func_(x...); - this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, val); + // Template mode: call function + data = this->data_.func(x...); } + this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, data); } protected: optional can_id_{}; optional use_extended_id_{}; bool remote_transmission_request_{false}; - bool static_{false}; - std::function(Ts...)> data_func_{}; - std::vector data_static_{}; + ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length + union Data { + std::vector (*func)(Ts...); // Function pointer (stateless lambdas) + const uint8_t *data; // Pointer to static data in flash + } data_; }; class CanbusTrigger : public Trigger, uint32_t, bool>, public Component { From 26a3ec41d6037f44b4479ee35983aab9f789688f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:23:33 -0600 Subject: [PATCH 0144/1145] [sx126x] Optimize send_packet action memory usage - store static data in flash (#11790) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/sx126x/__init__.py | 7 ++++-- esphome/components/sx126x/automation.h | 30 +++++++++++++++----------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/esphome/components/sx126x/__init__.py b/esphome/components/sx126x/__init__.py index 370cd102d4..f8f3b9d104 100644 --- a/esphome/components/sx126x/__init__.py +++ b/esphome/components/sx126x/__init__.py @@ -3,7 +3,7 @@ import esphome.codegen as cg from esphome.components import spi import esphome.config_validation as cv from esphome.const import CONF_BUSY_PIN, CONF_DATA, CONF_FREQUENCY, CONF_ID -from esphome.core import TimePeriod +from esphome.core import ID, TimePeriod MULTI_CONF = True CODEOWNERS = ["@swoboda1337"] @@ -329,5 +329,8 @@ async def send_packet_action_to_code(config, action_id, template_arg, args): templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) cg.add(var.set_data_template(templ)) else: - cg.add(var.set_data_static(data)) + # Generate static array in flash to avoid RAM copy + arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) + arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data)) + cg.add(var.set_data_static(arr, len(data))) return var diff --git a/esphome/components/sx126x/automation.h b/esphome/components/sx126x/automation.h index 6b2371e253..2282c583cb 100644 --- a/esphome/components/sx126x/automation.h +++ b/esphome/components/sx126x/automation.h @@ -14,28 +14,34 @@ template class RunImageCalAction : public Action, public template class SendPacketAction : public Action, public Parented { public: - void set_data_template(std::function(Ts...)> func) { - this->data_func_ = func; - this->static_ = false; + void set_data_template(std::vector (*func)(Ts...)) { + this->data_.func = func; + this->len_ = -1; // Sentinel value indicates template mode } - void set_data_static(const std::vector &data) { - this->data_static_ = data; - this->static_ = true; + void set_data_static(const uint8_t *data, size_t len) { + this->data_.data = data; + this->len_ = len; // Length >= 0 indicates static mode } void play(const Ts &...x) override { - if (this->static_) { - this->parent_->transmit_packet(this->data_static_); + std::vector data; + if (this->len_ >= 0) { + // Static mode: copy from flash to vector + data.assign(this->data_.data, this->data_.data + this->len_); } else { - this->parent_->transmit_packet(this->data_func_(x...)); + // Template mode: call function + data = this->data_.func(x...); } + this->parent_->transmit_packet(data); } protected: - bool static_{false}; - std::function(Ts...)> data_func_{}; - std::vector data_static_{}; + ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length + union Data { + std::vector (*func)(Ts...); // Function pointer (stateless lambdas) + const uint8_t *data; // Pointer to static data in flash + } data_; }; template class SetModeTxAction : public Action, public Parented { From 77ab096b59311c49679899b3f0c3b56e02e50dd6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:25:16 -0600 Subject: [PATCH 0145/1145] [remote_base] Optimize raw transmit action memory usage - use function pointers (#11800) --- esphome/components/remote_base/raw_protocol.h | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/esphome/components/remote_base/raw_protocol.h b/esphome/components/remote_base/raw_protocol.h index 9b671e611f..941b6aab42 100644 --- a/esphome/components/remote_base/raw_protocol.h +++ b/esphome/components/remote_base/raw_protocol.h @@ -42,17 +42,20 @@ class RawTrigger : public Trigger, public Component, public RemoteRe template class RawAction : public RemoteTransmitterActionBase { public: - void set_code_template(std::function func) { this->code_func_ = func; } + void set_code_template(RawTimings (*func)(Ts...)) { + this->code_.func = func; + this->len_ = -1; // Sentinel value indicates template mode + } void set_code_static(const int32_t *code, size_t len) { - this->code_static_ = code; - this->code_static_len_ = len; + this->code_.data = code; + this->len_ = len; // Length >= 0 indicates static mode } TEMPLATABLE_VALUE(uint32_t, carrier_frequency); void encode(RemoteTransmitData *dst, Ts... x) override { - if (this->code_static_ != nullptr) { - for (size_t i = 0; i < this->code_static_len_; i++) { - auto val = this->code_static_[i]; + if (this->len_ >= 0) { + for (size_t i = 0; i < static_cast(this->len_); i++) { + auto val = this->code_.data[i]; if (val < 0) { dst->space(static_cast(-val)); } else { @@ -60,15 +63,17 @@ template class RawAction : public RemoteTransmitterActionBaseset_data(this->code_func_(x...)); + dst->set_data(this->code_.func(x...)); } dst->set_carrier_frequency(this->carrier_frequency_.value(x...)); } protected: - std::function code_func_{nullptr}; - const int32_t *code_static_{nullptr}; - int32_t code_static_len_{0}; + ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length + union Code { + RawTimings (*func)(Ts...); + const int32_t *data; + } code_; }; class RawDumper : public RemoteReceiverDumperBase { From 7705a5de063a80b763cb81104142fb3371410f53 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:25:40 -0600 Subject: [PATCH 0146/1145] [sx127x] Optimize send_packet action memory usage - store static data in flash (#11792) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/sx127x/__init__.py | 6 +++++- esphome/components/sx127x/automation.h | 30 +++++++++++++++----------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/esphome/components/sx127x/__init__.py b/esphome/components/sx127x/__init__.py index 33b556db07..77cb61f7f8 100644 --- a/esphome/components/sx127x/__init__.py +++ b/esphome/components/sx127x/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import spi import esphome.config_validation as cv from esphome.const import CONF_DATA, CONF_FREQUENCY, CONF_ID +from esphome.core import ID MULTI_CONF = True CODEOWNERS = ["@swoboda1337"] @@ -321,5 +322,8 @@ async def send_packet_action_to_code(config, action_id, template_arg, args): templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) cg.add(var.set_data_template(templ)) else: - cg.add(var.set_data_static(data)) + # Generate static array in flash to avoid RAM copy + arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) + arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data)) + cg.add(var.set_data_static(arr, len(data))) return var diff --git a/esphome/components/sx127x/automation.h b/esphome/components/sx127x/automation.h index eae16c11fa..fb0367fcca 100644 --- a/esphome/components/sx127x/automation.h +++ b/esphome/components/sx127x/automation.h @@ -14,28 +14,34 @@ template class RunImageCalAction : public Action, public template class SendPacketAction : public Action, public Parented { public: - void set_data_template(std::function(Ts...)> func) { - this->data_func_ = func; - this->static_ = false; + void set_data_template(std::vector (*func)(Ts...)) { + this->data_.func = func; + this->len_ = -1; // Sentinel value indicates template mode } - void set_data_static(const std::vector &data) { - this->data_static_ = data; - this->static_ = true; + void set_data_static(const uint8_t *data, size_t len) { + this->data_.data = data; + this->len_ = len; // Length >= 0 indicates static mode } void play(const Ts &...x) override { - if (this->static_) { - this->parent_->transmit_packet(this->data_static_); + std::vector data; + if (this->len_ >= 0) { + // Static mode: copy from flash to vector + data.assign(this->data_.data, this->data_.data + this->len_); } else { - this->parent_->transmit_packet(this->data_func_(x...)); + // Template mode: call function + data = this->data_.func(x...); } + this->parent_->transmit_packet(data); } protected: - bool static_{false}; - std::function(Ts...)> data_func_{}; - std::vector data_static_{}; + ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length + union Data { + std::vector (*func)(Ts...); // Function pointer (stateless lambdas) + const uint8_t *data; // Pointer to static data in flash + } data_; }; template class SetModeTxAction : public Action, public Parented { From e7ff56f1cd377128c9123a4cb4ca8c6f7f8ca8bb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:27:09 -0600 Subject: [PATCH 0147/1145] [remote_base] Eliminate substr() allocations in Pronto dump logging (#11726) --- .../remote_base/pronto_protocol.cpp | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index 35fd782248..9fbc9e85ba 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -71,6 +71,7 @@ static const uint16_t FALLBACK_FREQUENCY = 64767U; // To use with frequency = 0 static const uint32_t MICROSECONDS_IN_SECONDS = 1000000UL; static const uint16_t PRONTO_DEFAULT_GAP = 45000; static const uint16_t MARK_EXCESS_MICROS = 20; +static constexpr size_t PRONTO_LOG_CHUNK_SIZE = 230; static uint16_t to_frequency_k_hz(uint16_t code) { if (code == 0) @@ -225,18 +226,18 @@ optional ProntoProtocol::decode(RemoteReceiveData src) { } void ProntoProtocol::dump(const ProntoData &data) { - std::string rest; - - rest = data.data; ESP_LOGI(TAG, "Received Pronto: data="); - while (true) { - ESP_LOGI(TAG, "%s", rest.substr(0, 230).c_str()); - if (rest.size() > 230) { - rest = rest.substr(230); - } else { - break; - } - } + + const char *ptr = data.data.c_str(); + size_t remaining = data.data.size(); + + // Log in chunks, always logging at least once (even for empty string) + do { + size_t chunk_size = remaining < PRONTO_LOG_CHUNK_SIZE ? remaining : PRONTO_LOG_CHUNK_SIZE; + ESP_LOGI(TAG, "%.*s", (int) chunk_size, ptr); + ptr += chunk_size; + remaining -= chunk_size; + } while (remaining > 0); } } // namespace remote_base From cbb98c40502be70cb50ffa8ae741dc18be324f3c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:27:56 -0600 Subject: [PATCH 0148/1145] [bl0940] Fix calibration number preference hash for multi-device configs (#11769) --- esphome/components/bl0940/number/calibration_number.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/bl0940/number/calibration_number.cpp b/esphome/components/bl0940/number/calibration_number.cpp index cdb26cd298..e83c3add1f 100644 --- a/esphome/components/bl0940/number/calibration_number.cpp +++ b/esphome/components/bl0940/number/calibration_number.cpp @@ -9,7 +9,7 @@ static const char *const TAG = "bl0940.number"; void CalibrationNumber::setup() { float value = 0.0f; if (this->restore_value_) { - this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); + this->pref_ = global_preferences->make_preference(this->get_preference_hash()); if (!this->pref_.load(&value)) { value = 0.0f; } From 8b9600b930d74339a9c47035bc641d147bf0d2e9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:33:29 -0600 Subject: [PATCH 0149/1145] [speaker] Optimize speaker.play action memory usage - store static data in flash (#11796) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/speaker/__init__.py | 7 ++++-- esphome/components/speaker/automation.h | 29 +++++++++++++++---------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/esphome/components/speaker/__init__.py b/esphome/components/speaker/__init__.py index 5f1ba94ee6..18e1d9782c 100644 --- a/esphome/components/speaker/__init__.py +++ b/esphome/components/speaker/__init__.py @@ -3,7 +3,7 @@ import esphome.codegen as cg from esphome.components import audio, audio_dac import esphome.config_validation as cv from esphome.const import CONF_DATA, CONF_ID, CONF_VOLUME -from esphome.core import CORE +from esphome.core import CORE, ID from esphome.coroutine import CoroPriority, coroutine_with_priority AUTO_LOAD = ["audio"] @@ -90,7 +90,10 @@ async def speaker_play_action(config, action_id, template_arg, args): templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) cg.add(var.set_data_template(templ)) else: - cg.add(var.set_data_static(data)) + # Generate static array in flash to avoid RAM copy + arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) + arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data)) + cg.add(var.set_data_static(arr, len(data))) return var diff --git a/esphome/components/speaker/automation.h b/esphome/components/speaker/automation.h index 80bba25030..391c9e4c62 100644 --- a/esphome/components/speaker/automation.h +++ b/esphome/components/speaker/automation.h @@ -10,28 +10,33 @@ namespace speaker { template class PlayAction : public Action, public Parented { public: - void set_data_template(std::function(Ts...)> func) { - this->data_func_ = func; - this->static_ = false; + void set_data_template(std::vector (*func)(Ts...)) { + this->data_.func = func; + this->len_ = -1; // Sentinel value indicates template mode } - void set_data_static(const std::vector &data) { - this->data_static_ = data; - this->static_ = true; + + void set_data_static(const uint8_t *data, size_t len) { + this->data_.data = data; + this->len_ = len; // Length >= 0 indicates static mode } void play(const Ts &...x) override { - if (this->static_) { - this->parent_->play(this->data_static_); + if (this->len_ >= 0) { + // Static mode: pass pointer directly to play(const uint8_t *, size_t) + this->parent_->play(this->data_.data, static_cast(this->len_)); } else { - auto val = this->data_func_(x...); + // Template mode: call function and pass vector to play(const std::vector &) + auto val = this->data_.func(x...); this->parent_->play(val); } } protected: - bool static_{false}; - std::function(Ts...)> data_func_{}; - std::vector data_static_{}; + ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length + union Data { + std::vector (*func)(Ts...); // Function pointer (stateless lambdas) + const uint8_t *data; // Pointer to static data in flash + } data_; }; template class VolumeSetAction : public Action, public Parented { From fb1c67490ad170fd92bdeea357242fae9fb847b0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:33:56 -0600 Subject: [PATCH 0150/1145] [udp] Optimize udp.write action memory usage - store static data in flash (#11794) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/udp/__init__.py | 7 +++++-- esphome/components/udp/automation.h | 29 +++++++++++++++++------------ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/esphome/components/udp/__init__.py b/esphome/components/udp/__init__.py index 6b1e4f8ed8..69abf4b989 100644 --- a/esphome/components/udp/__init__.py +++ b/esphome/components/udp/__init__.py @@ -12,7 +12,7 @@ from esphome.components.packet_transport import ( ) import esphome.config_validation as cv from esphome.const import CONF_DATA, CONF_ID, CONF_PORT, CONF_TRIGGER_ID -from esphome.core import Lambda +from esphome.core import ID, Lambda from esphome.cpp_generator import ExpressionStatement, MockObj CODEOWNERS = ["@clydebarrow"] @@ -158,5 +158,8 @@ async def udp_write_to_code(config, action_id, template_arg, args): templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) cg.add(var.set_data_template(templ)) else: - cg.add(var.set_data_static(data)) + # Generate static array in flash to avoid RAM copy + arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) + arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data)) + cg.add(var.set_data_static(arr, len(data))) return var diff --git a/esphome/components/udp/automation.h b/esphome/components/udp/automation.h index c5e5e2eae8..b66c2a9892 100644 --- a/esphome/components/udp/automation.h +++ b/esphome/components/udp/automation.h @@ -11,28 +11,33 @@ namespace udp { template class UDPWriteAction : public Action, public Parented { public: - void set_data_template(std::function(Ts...)> func) { - this->data_func_ = func; - this->static_ = false; + void set_data_template(std::vector (*func)(Ts...)) { + this->data_.func = func; + this->len_ = -1; // Sentinel value indicates template mode } - void set_data_static(const std::vector &data) { - this->data_static_ = data; - this->static_ = true; + + void set_data_static(const uint8_t *data, size_t len) { + this->data_.data = data; + this->len_ = len; // Length >= 0 indicates static mode } void play(const Ts &...x) override { - if (this->static_) { - this->parent_->send_packet(this->data_static_); + if (this->len_ >= 0) { + // Static mode: pass pointer directly to send_packet(const uint8_t *, size_t) + this->parent_->send_packet(this->data_.data, static_cast(this->len_)); } else { - auto val = this->data_func_(x...); + // Template mode: call function and pass vector to send_packet(const std::vector &) + auto val = this->data_.func(x...); this->parent_->send_packet(val); } } protected: - bool static_{false}; - std::function(Ts...)> data_func_{}; - std::vector data_static_{}; + ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length + union Data { + std::vector (*func)(Ts...); // Function pointer (stateless lambdas) + const uint8_t *data; // Pointer to static data in flash + } data_; }; } // namespace udp From d516627957778427af22ce57c2d5c1294e9948d5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:37:14 -0600 Subject: [PATCH 0151/1145] [uart] Store static data in flash and use function pointers for lambdas (#11784) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/uart/__init__.py | 7 ++++-- esphome/components/uart/automation.h | 35 +++++++++++++++------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index eb911ed007..cbc11d0db0 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -31,7 +31,7 @@ from esphome.const import ( PLATFORM_HOST, PlatformFramework, ) -from esphome.core import CORE +from esphome.core import CORE, ID import esphome.final_validate as fv from esphome.yaml_util import make_data_base @@ -446,7 +446,10 @@ async def uart_write_to_code(config, action_id, template_arg, args): templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) cg.add(var.set_data_template(templ)) else: - cg.add(var.set_data_static(cg.ArrayInitializer(*data))) + # Generate static array in flash to avoid RAM copy + arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) + arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data)) + cg.add(var.set_data_static(arr, len(data))) return var diff --git a/esphome/components/uart/automation.h b/esphome/components/uart/automation.h index ad2c4d2bf1..c2eb308eb8 100644 --- a/esphome/components/uart/automation.h +++ b/esphome/components/uart/automation.h @@ -10,32 +10,35 @@ namespace uart { template class UARTWriteAction : public Action, public Parented { public: - void set_data_template(std::function(Ts...)> func) { - this->data_func_ = func; - this->static_ = false; + void set_data_template(std::vector (*func)(Ts...)) { + // Stateless lambdas (generated by ESPHome) implicitly convert to function pointers + this->code_.func = func; + this->len_ = -1; // Sentinel value indicates template mode } - void set_data_static(std::vector &&data) { - this->data_static_ = std::move(data); - this->static_ = true; - } - void set_data_static(std::initializer_list data) { - this->data_static_ = std::vector(data); - this->static_ = true; + + // Store pointer to static data in flash (no RAM copy) + void set_data_static(const uint8_t *data, size_t len) { + this->code_.data = data; + this->len_ = len; // Length >= 0 indicates static mode } void play(const Ts &...x) override { - if (this->static_) { - this->parent_->write_array(this->data_static_); + if (this->len_ >= 0) { + // Static mode: use pointer and length + this->parent_->write_array(this->code_.data, static_cast(this->len_)); } else { - auto val = this->data_func_(x...); + // Template mode: call function + auto val = this->code_.func(x...); this->parent_->write_array(val); } } protected: - bool static_{false}; - std::function(Ts...)> data_func_{}; - std::vector data_static_{}; + ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length + union Code { + std::vector (*func)(Ts...); // Function pointer (stateless lambdas) + const uint8_t *data; // Pointer to static data in flash + } code_; }; } // namespace uart From 7b86e1feb0f71e23dc8a7db9663f8bebb4d8075b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:39:27 -0600 Subject: [PATCH 0152/1145] [core] Remove deprecated EntityBase::hash_base() method (#11783) --- esphome/core/entity_base.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 1486ff5360..2b52d66f76 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -129,9 +129,6 @@ class EntityBase { // Returns empty StringRef if object_id is dynamic (needs allocation) StringRef get_object_id_ref_for_api_() const; - /// The hash_base() function has been deprecated. It is kept in this - /// class for now, to prevent external components from not compiling. - virtual uint32_t hash_base() { return 0L; } void calc_object_id_(); /// Check if the object_id is dynamic (changes with MAC suffix) From 0d735dc259b8338b35464ddddca6c1533c331bd4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 16:46:01 -0600 Subject: [PATCH 0153/1145] [remote_base] Optimize abbwelcome action memory usage - store static data in flash (#11798) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/remote_base/__init__.py | 6 ++-- .../remote_base/abbwelcome_protocol.h | 29 ++++++++++++------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 8d735ea563..d24d24b000 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -39,7 +39,7 @@ from esphome.const import ( CONF_WAND_ID, CONF_ZERO, ) -from esphome.core import coroutine +from esphome.core import ID, coroutine from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.util import Registry, SimpleRegistry @@ -2104,7 +2104,9 @@ async def abbwelcome_action(var, config, args): ) cg.add(var.set_data_template(template_)) else: - cg.add(var.set_data_static(data_)) + arr_id = ID(f"{var.base}_data", is_declaration=True, type=cg.uint8) + arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data_)) + cg.add(var.set_data_static(arr, len(data_))) # Mirage diff --git a/esphome/components/remote_base/abbwelcome_protocol.h b/esphome/components/remote_base/abbwelcome_protocol.h index b258bd920b..4b922eb2f1 100644 --- a/esphome/components/remote_base/abbwelcome_protocol.h +++ b/esphome/components/remote_base/abbwelcome_protocol.h @@ -214,10 +214,13 @@ template class ABBWelcomeAction : public RemoteTransmitterAction TEMPLATABLE_VALUE(uint8_t, message_type) TEMPLATABLE_VALUE(uint8_t, message_id) TEMPLATABLE_VALUE(bool, auto_message_id) - void set_data_static(std::vector data) { data_static_ = std::move(data); } - void set_data_template(std::function(Ts...)> func) { - this->data_func_ = func; - has_data_func_ = true; + void set_data_template(std::vector (*func)(Ts...)) { + this->data_.func = func; + this->len_ = -1; // Sentinel value indicates template mode + } + void set_data_static(const uint8_t *data, size_t len) { + this->data_.data = data; + this->len_ = len; // Length >= 0 indicates static mode } void encode(RemoteTransmitData *dst, Ts... x) override { ABBWelcomeData data; @@ -228,19 +231,25 @@ template class ABBWelcomeAction : public RemoteTransmitterAction data.set_message_type(this->message_type_.value(x...)); data.set_message_id(this->message_id_.value(x...)); data.auto_message_id = this->auto_message_id_.value(x...); - if (has_data_func_) { - data.set_data(this->data_func_(x...)); + std::vector data_vec; + if (this->len_ >= 0) { + // Static mode: copy from flash to vector + data_vec.assign(this->data_.data, this->data_.data + this->len_); } else { - data.set_data(this->data_static_); + // Template mode: call function + data_vec = this->data_.func(x...); } + data.set_data(data_vec); data.finalize(); ABBWelcomeProtocol().encode(dst, data); } protected: - std::function(Ts...)> data_func_{}; - std::vector data_static_{}; - bool has_data_func_{false}; + ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length + union Data { + std::vector (*func)(Ts...); // Function pointer (stateless lambdas) + const uint8_t *data; // Pointer to static data in flash + } data_; }; } // namespace remote_base From 1dabe83d048601b36d29c8b33be70acc84540a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Sun, 9 Nov 2025 23:48:33 +0100 Subject: [PATCH 0154/1145] [nrf52] api (#11751) --- esphome/components/api/__init__.py | 1 + esphome/components/api/api_connection.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 449572c0e5..7f69a9fda1 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -227,6 +227,7 @@ CONFIG_SCHEMA = cv.All( esp32=8, # More RAM, can buffer more rp2040=5, # Limited RAM bk72xx=8, # Moderate RAM + nrf52=8, # Moderate RAM rtl87xx=8, # Moderate RAM host=16, # Abundant resources ln882x=8, # Moderate RAM diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8c293b41a2..7eb61f08b6 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1467,6 +1467,8 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { static constexpr auto MANUFACTURER = StringRef::from_lit("Beken"); #elif defined(USE_LN882X) static constexpr auto MANUFACTURER = StringRef::from_lit("Lightning"); +#elif defined(USE_NRF52) + static constexpr auto MANUFACTURER = StringRef::from_lit("Nordic Semiconductor"); #elif defined(USE_RTL87XX) static constexpr auto MANUFACTURER = StringRef::from_lit("Realtek"); #elif defined(USE_HOST) From 7abb6d499870ef418b27bbd63e3281aff85eb11c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 17:34:08 -0600 Subject: [PATCH 0155/1145] [core] Implement Global Controller Registry to reduce RAM usage (#11772) --- .../alarm_control_panel.cpp | 9 +- esphome/components/api/__init__.py | 3 + esphome/components/api/api_server.cpp | 33 +-- esphome/components/api/api_server.h | 14 +- .../binary_sensor/binary_sensor.cpp | 5 + esphome/components/climate/climate.cpp | 5 + esphome/components/cover/cover.cpp | 7 + esphome/components/datetime/date_entity.cpp | 6 +- .../components/datetime/datetime_entity.cpp | 6 +- esphome/components/datetime/time_entity.cpp | 6 +- esphome/components/event/event.cpp | 6 +- esphome/components/fan/fan.cpp | 5 + esphome/components/light/light_state.cpp | 14 +- esphome/components/lock/lock.cpp | 5 + .../components/media_player/media_player.cpp | 10 +- esphome/components/number/number.cpp | 5 + esphome/components/select/select.cpp | 5 + esphome/components/sensor/sensor.cpp | 5 + esphome/components/switch/switch.cpp | 5 + esphome/components/text/text.cpp | 5 + .../components/text_sensor/text_sensor.cpp | 5 + esphome/components/update/update_entity.cpp | 6 +- esphome/components/valve/valve.cpp | 5 + esphome/components/web_server/__init__.py | 3 + esphome/components/web_server/web_server.cpp | 54 +++- esphome/components/web_server/web_server.h | 14 +- esphome/core/__init__.py | 8 + esphome/core/config.py | 17 +- esphome/core/controller.cpp | 134 ---------- esphome/core/controller.h | 15 +- esphome/core/controller_registry.cpp | 114 ++++++++ esphome/core/controller_registry.h | 245 ++++++++++++++++++ esphome/core/defines.h | 2 + 33 files changed, 583 insertions(+), 198 deletions(-) delete mode 100644 esphome/core/controller.cpp create mode 100644 esphome/core/controller_registry.cpp create mode 100644 esphome/core/controller_registry.h diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index 9f1485ee90..c29e02c8ef 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -1,6 +1,8 @@ -#include - #include "alarm_control_panel.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" + +#include #include "esphome/core/application.h" #include "esphome/core/helpers.h" @@ -34,6 +36,9 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) { LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state))); this->current_state_ = state; this->state_callback_.call(); +#if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_alarm_control_panel_update(this); +#endif if (state == ACP_STATE_TRIGGERED) { this->triggered_callback_.call(); } else if (state == ACP_STATE_ARMING) { diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 7f69a9fda1..a9286c531f 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -245,6 +245,9 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + # Track controller registration for StaticVector sizing + CORE.register_controller() + cg.add(var.set_port(config[CONF_PORT])) if config[CONF_PASSWORD]: cg.add_define("USE_API_PASSWORD") diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index e5f0d9795e..18601d74ff 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -5,6 +5,7 @@ #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/util.h" @@ -34,7 +35,7 @@ APIServer::APIServer() { } void APIServer::setup() { - this->setup_controller(); + ControllerRegistry::register_controller(this); #ifdef USE_API_NOISE uint32_t hash = 88491486UL; @@ -269,7 +270,7 @@ bool APIServer::check_password(const uint8_t *password_data, size_t password_len void APIServer::handle_disconnect(APIConnection *conn) {} -// Macro for entities without extra parameters +// Macro for controller update dispatch #define API_DISPATCH_UPDATE(entity_type, entity_name) \ void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \ if (obj->is_internal()) \ @@ -278,15 +279,6 @@ void APIServer::handle_disconnect(APIConnection *conn) {} c->send_##entity_name##_state(obj); \ } -// Macro for entities with extra parameters (but parameters not used in send) -#define API_DISPATCH_UPDATE_IGNORE_PARAMS(entity_type, entity_name, ...) \ - void APIServer::on_##entity_name##_update(entity_type *obj, __VA_ARGS__) { /* NOLINT(bugprone-macro-parentheses) */ \ - if (obj->is_internal()) \ - return; \ - for (auto &c : this->clients_) \ - c->send_##entity_name##_state(obj); \ - } - #ifdef USE_BINARY_SENSOR API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor) #endif @@ -304,15 +296,15 @@ API_DISPATCH_UPDATE(light::LightState, light) #endif #ifdef USE_SENSOR -API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state) +API_DISPATCH_UPDATE(sensor::Sensor, sensor) #endif #ifdef USE_SWITCH -API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state) +API_DISPATCH_UPDATE(switch_::Switch, switch) #endif #ifdef USE_TEXT_SENSOR -API_DISPATCH_UPDATE_IGNORE_PARAMS(text_sensor::TextSensor, text_sensor, const std::string &state) +API_DISPATCH_UPDATE(text_sensor::TextSensor, text_sensor) #endif #ifdef USE_CLIMATE @@ -320,7 +312,7 @@ API_DISPATCH_UPDATE(climate::Climate, climate) #endif #ifdef USE_NUMBER -API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state) +API_DISPATCH_UPDATE(number::Number, number) #endif #ifdef USE_DATETIME_DATE @@ -336,11 +328,11 @@ API_DISPATCH_UPDATE(datetime::DateTimeEntity, datetime) #endif #ifdef USE_TEXT -API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state) +API_DISPATCH_UPDATE(text::Text, text) #endif #ifdef USE_SELECT -API_DISPATCH_UPDATE_IGNORE_PARAMS(select::Select, select, const std::string &state, size_t index) +API_DISPATCH_UPDATE(select::Select, select) #endif #ifdef USE_LOCK @@ -356,12 +348,13 @@ API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player) #endif #ifdef USE_EVENT -// Event is a special case - it's the only entity that passes extra parameters to the send method -void APIServer::on_event(event::Event *obj, const std::string &event_type) { +// Event is a special case - unlike other entities with simple state fields, +// events store their state in a member accessed via obj->get_last_event_type() +void APIServer::on_event(event::Event *obj) { if (obj->is_internal()) return; for (auto &c : this->clients_) - c->send_event(obj, event_type); + c->send_event(obj, obj->get_last_event_type()); } #endif diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index f1f44a266d..2d58063d6c 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -72,19 +72,19 @@ class APIServer : public Component, public Controller { void on_light_update(light::LightState *obj) override; #endif #ifdef USE_SENSOR - void on_sensor_update(sensor::Sensor *obj, float state) override; + void on_sensor_update(sensor::Sensor *obj) override; #endif #ifdef USE_SWITCH - void on_switch_update(switch_::Switch *obj, bool state) override; + void on_switch_update(switch_::Switch *obj) override; #endif #ifdef USE_TEXT_SENSOR - void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override; + void on_text_sensor_update(text_sensor::TextSensor *obj) override; #endif #ifdef USE_CLIMATE void on_climate_update(climate::Climate *obj) override; #endif #ifdef USE_NUMBER - void on_number_update(number::Number *obj, float state) override; + void on_number_update(number::Number *obj) override; #endif #ifdef USE_DATETIME_DATE void on_date_update(datetime::DateEntity *obj) override; @@ -96,10 +96,10 @@ class APIServer : public Component, public Controller { void on_datetime_update(datetime::DateTimeEntity *obj) override; #endif #ifdef USE_TEXT - void on_text_update(text::Text *obj, const std::string &state) override; + void on_text_update(text::Text *obj) override; #endif #ifdef USE_SELECT - void on_select_update(select::Select *obj, const std::string &state, size_t index) override; + void on_select_update(select::Select *obj) override; #endif #ifdef USE_LOCK void on_lock_update(lock::Lock *obj) override; @@ -141,7 +141,7 @@ class APIServer : public Component, public Controller { void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override; #endif #ifdef USE_EVENT - void on_event(event::Event *obj, const std::string &event_type) override; + void on_event(event::Event *obj) override; #endif #ifdef USE_UPDATE void on_update(update::UpdateEntity *obj) override; diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 33b3de6d72..220ed685db 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -1,4 +1,6 @@ #include "binary_sensor.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -37,6 +39,9 @@ void BinarySensor::send_state_internal(bool new_state) { // Note that set_state_ de-dups and will only trigger callbacks if the state has actually changed if (this->set_state_(new_state)) { ESP_LOGD(TAG, "'%s': New state is %s", this->get_name().c_str(), ONOFF(new_state)); +#if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_binary_sensor_update(this); +#endif } } diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 7df38758dc..82b75660ba 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -1,4 +1,6 @@ #include "climate.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/macros.h" namespace esphome { @@ -463,6 +465,9 @@ void Climate::publish_state() { // Send state to frontend this->state_callback_.call(*this); +#if defined(USE_CLIMATE) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_climate_update(this); +#endif // Save state this->save_state_(); } diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 654bb956a5..3062dba28a 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -1,5 +1,9 @@ #include "cover.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" + #include + #include "esphome/core/log.h" namespace esphome { @@ -169,6 +173,9 @@ void Cover::publish_state(bool save) { ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation)); this->state_callback_.call(); +#if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_cover_update(this); +#endif if (save) { CoverRestoreState restore{}; diff --git a/esphome/components/datetime/date_entity.cpp b/esphome/components/datetime/date_entity.cpp index c164a98b2e..2c2775ecf4 100644 --- a/esphome/components/datetime/date_entity.cpp +++ b/esphome/components/datetime/date_entity.cpp @@ -1,5 +1,6 @@ #include "date_entity.h" - +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #ifdef USE_DATETIME_DATE #include "esphome/core/log.h" @@ -32,6 +33,9 @@ void DateEntity::publish_state() { this->set_has_state(true); ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_); this->state_callback_.call(); +#if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_date_update(this); +#endif } DateCall DateEntity::make_call() { return DateCall(this); } diff --git a/esphome/components/datetime/datetime_entity.cpp b/esphome/components/datetime/datetime_entity.cpp index 4e3b051eb3..8606a47fa7 100644 --- a/esphome/components/datetime/datetime_entity.cpp +++ b/esphome/components/datetime/datetime_entity.cpp @@ -1,5 +1,6 @@ #include "datetime_entity.h" - +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #ifdef USE_DATETIME_DATETIME #include "esphome/core/log.h" @@ -48,6 +49,9 @@ void DateTimeEntity::publish_state() { ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_, this->month_, this->day_, this->hour_, this->minute_, this->second_); this->state_callback_.call(); +#if defined(USE_DATETIME_DATETIME) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_datetime_update(this); +#endif } DateTimeCall DateTimeEntity::make_call() { return DateTimeCall(this); } diff --git a/esphome/components/datetime/time_entity.cpp b/esphome/components/datetime/time_entity.cpp index 9b05c2124f..469be077ea 100644 --- a/esphome/components/datetime/time_entity.cpp +++ b/esphome/components/datetime/time_entity.cpp @@ -1,5 +1,6 @@ #include "time_entity.h" - +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #ifdef USE_DATETIME_TIME #include "esphome/core/log.h" @@ -29,6 +30,9 @@ void TimeEntity::publish_state() { ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_, this->second_); this->state_callback_.call(); +#if defined(USE_DATETIME_TIME) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_time_update(this); +#endif } TimeCall TimeEntity::make_call() { return TimeCall(this); } diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp index a14afbd7f5..4c74a11388 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -1,5 +1,6 @@ #include "event.h" - +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -23,6 +24,9 @@ void Event::trigger(const std::string &event_type) { this->last_event_type_ = found; ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), this->last_event_type_); this->event_callback_.call(event_type); +#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_event(this); +#endif } void Event::set_event_types(const FixedVector &event_types) { diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 959572e9d9..d37825a651 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -1,4 +1,6 @@ #include "fan.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -181,6 +183,9 @@ void Fan::publish_state() { ESP_LOGD(TAG, " Preset Mode: %s", preset); } this->state_callback_.call(); +#if defined(USE_FAN) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_fan_update(this); +#endif this->save_state_(); } diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 7b0a698bb8..4c253ec5a8 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -1,7 +1,8 @@ -#include "esphome/core/log.h" - -#include "light_output.h" #include "light_state.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" +#include "esphome/core/log.h" +#include "light_output.h" #include "transformers.h" namespace esphome { @@ -137,7 +138,12 @@ void LightState::loop() { float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } -void LightState::publish_state() { this->remote_values_callback_.call(); } +void LightState::publish_state() { + this->remote_values_callback_.call(); +#if defined(USE_LIGHT) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_light_update(this); +#endif +} LightOutput *LightState::get_output() const { return this->output_; } diff --git a/esphome/components/lock/lock.cpp b/esphome/components/lock/lock.cpp index ddc5445349..54fefe8745 100644 --- a/esphome/components/lock/lock.cpp +++ b/esphome/components/lock/lock.cpp @@ -1,4 +1,6 @@ #include "lock.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -53,6 +55,9 @@ void Lock::publish_state(LockState state) { this->rtc_.save(&this->state); ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), lock_state_to_string(state)); this->state_callback_.call(); +#if defined(USE_LOCK) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_lock_update(this); +#endif } void Lock::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } diff --git a/esphome/components/media_player/media_player.cpp b/esphome/components/media_player/media_player.cpp index 3f274bf73b..b46ec39d30 100644 --- a/esphome/components/media_player/media_player.cpp +++ b/esphome/components/media_player/media_player.cpp @@ -1,5 +1,6 @@ #include "media_player.h" - +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -148,7 +149,12 @@ void MediaPlayer::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } -void MediaPlayer::publish_state() { this->state_callback_.call(); } +void MediaPlayer::publish_state() { + this->state_callback_.call(); +#if defined(USE_MEDIA_PLAYER) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_media_player_update(this); +#endif +} } // namespace media_player } // namespace esphome diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index da08faf655..f12e0e9e1e 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -1,4 +1,6 @@ #include "number.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -32,6 +34,9 @@ void Number::publish_state(float state) { this->state = state; ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state); this->state_callback_.call(state); +#if defined(USE_NUMBER) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_number_update(this); +#endif } void Number::add_on_state_callback(std::function &&callback) { diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 6bb01ba6e2..9fe7a52422 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -1,4 +1,6 @@ #include "select.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" #include @@ -33,6 +35,9 @@ void Select::publish_state(size_t index) { ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", this->get_name().c_str(), option, index); // Callback signature requires std::string, create temporary for compatibility this->state_callback_.call(std::string(option), index); +#if defined(USE_SELECT) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_select_update(this); +#endif } const char *Select::current_option() const { return this->has_state() ? this->option_at(this->active_index_) : ""; } diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 92da4345b7..df6bd644e8 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -1,4 +1,6 @@ #include "sensor.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -131,6 +133,9 @@ void Sensor::internal_send_state_to_frontend(float state) { ESP_LOGD(TAG, "'%s': Sending state %.5f %s with %d decimals of accuracy", this->get_name().c_str(), state, this->get_unit_of_measurement_ref().c_str(), this->get_accuracy_decimals()); this->callback_.call(state); +#if defined(USE_SENSOR) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_sensor_update(this); +#endif } } // namespace sensor diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index 02cee91a76..3c3a437ff3 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -1,4 +1,6 @@ #include "switch.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -62,6 +64,9 @@ void Switch::publish_state(bool state) { ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), ONOFF(this->state)); this->state_callback_.call(this->state); +#if defined(USE_SWITCH) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_switch_update(this); +#endif } bool Switch::assumed_state() { return false; } diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index 654893d4e4..933d82c85c 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -1,4 +1,6 @@ #include "text.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -16,6 +18,9 @@ void Text::publish_state(const std::string &state) { ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state.c_str()); } this->state_callback_.call(state); +#if defined(USE_TEXT) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_text_update(this); +#endif } void Text::add_on_state_callback(std::function &&callback) { diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 0294d65861..a7bcf19967 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -1,4 +1,6 @@ #include "text_sensor.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -84,6 +86,9 @@ void TextSensor::internal_send_state_to_frontend(const std::string &state) { this->set_has_state(true); ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str()); this->callback_.call(state); +#if defined(USE_TEXT_SENSOR) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_text_sensor_update(this); +#endif } } // namespace text_sensor diff --git a/esphome/components/update/update_entity.cpp b/esphome/components/update/update_entity.cpp index ce97fb1b77..567fc9fc8e 100644 --- a/esphome/components/update/update_entity.cpp +++ b/esphome/components/update/update_entity.cpp @@ -1,5 +1,6 @@ #include "update_entity.h" - +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -32,6 +33,9 @@ void UpdateEntity::publish_state() { this->set_has_state(true); this->state_callback_.call(); +#if defined(USE_UPDATE) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_update(this); +#endif } } // namespace update diff --git a/esphome/components/valve/valve.cpp b/esphome/components/valve/valve.cpp index b041fe8449..381d9061de 100644 --- a/esphome/components/valve/valve.cpp +++ b/esphome/components/valve/valve.cpp @@ -1,4 +1,6 @@ #include "valve.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" #include @@ -147,6 +149,9 @@ void Valve::publish_state(bool save) { ESP_LOGD(TAG, " Current Operation: %s", valve_operation_to_str(this->current_operation)); this->state_callback_.call(); +#if defined(USE_VALVE) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_valve_update(this); +#endif if (save) { ValveRestoreState restore{}; diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index a7fdf30eef..17ad496f30 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -289,6 +289,9 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID], paren) await cg.register_component(var, config) + # Track controller registration for StaticVector sizing + CORE.register_controller() + version = config[CONF_VERSION] cg.add(paren.set_port(config[CONF_PORT])) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 91ca076474..5a8128ba43 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -3,6 +3,8 @@ #include "esphome/components/json/json_util.h" #include "esphome/components/network/util.h" #include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -294,7 +296,7 @@ std::string WebServer::get_config_json() { } void WebServer::setup() { - this->setup_controller(this->include_internal_); + ControllerRegistry::register_controller(this); this->base_->init(); #ifdef USE_LOGGER @@ -430,7 +432,9 @@ static JsonDetail get_request_detail(AsyncWebServerRequest *request) { } #ifdef USE_SENSOR -void WebServer::on_sensor_update(sensor::Sensor *obj, float state) { +void WebServer::on_sensor_update(sensor::Sensor *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", sensor_state_json_generator); } void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -473,7 +477,9 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail #endif #ifdef USE_TEXT_SENSOR -void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) { +void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", text_sensor_state_json_generator); } void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -513,7 +519,9 @@ std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std: #endif #ifdef USE_SWITCH -void WebServer::on_switch_update(switch_::Switch *obj, bool state) { +void WebServer::on_switch_update(switch_::Switch *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", switch_state_json_generator); } void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -625,6 +633,8 @@ std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) #ifdef USE_BINARY_SENSOR void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", binary_sensor_state_json_generator); } void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -664,6 +674,8 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool #ifdef USE_FAN void WebServer::on_fan_update(fan::Fan *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", fan_state_json_generator); } void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -738,6 +750,8 @@ std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { #ifdef USE_LIGHT void WebServer::on_light_update(light::LightState *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", light_state_json_generator); } void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -811,6 +825,8 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi #ifdef USE_COVER void WebServer::on_cover_update(cover::Cover *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", cover_state_json_generator); } void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -895,7 +911,9 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { #endif #ifdef USE_NUMBER -void WebServer::on_number_update(number::Number *obj, float state) { +void WebServer::on_number_update(number::Number *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", number_state_json_generator); } void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -961,6 +979,8 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail #ifdef USE_DATETIME_DATE void WebServer::on_date_update(datetime::DateEntity *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", date_state_json_generator); } void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1016,6 +1036,8 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con #ifdef USE_DATETIME_TIME void WebServer::on_time_update(datetime::TimeEntity *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", time_state_json_generator); } void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1070,6 +1092,8 @@ std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_con #ifdef USE_DATETIME_DATETIME void WebServer::on_datetime_update(datetime::DateTimeEntity *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", datetime_state_json_generator); } void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1124,7 +1148,9 @@ std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail s #endif // USE_DATETIME_DATETIME #ifdef USE_TEXT -void WebServer::on_text_update(text::Text *obj, const std::string &state) { +void WebServer::on_text_update(text::Text *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", text_state_json_generator); } void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1178,7 +1204,9 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json #endif #ifdef USE_SELECT -void WebServer::on_select_update(select::Select *obj, const std::string &state, size_t index) { +void WebServer::on_select_update(select::Select *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", select_state_json_generator); } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1237,6 +1265,8 @@ std::string WebServer::select_json(select::Select *obj, const char *value, JsonD #ifdef USE_CLIMATE void WebServer::on_climate_update(climate::Climate *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", climate_state_json_generator); } void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1378,6 +1408,8 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf #ifdef USE_LOCK void WebServer::on_lock_update(lock::Lock *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", lock_state_json_generator); } void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1449,6 +1481,8 @@ std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDet #ifdef USE_VALVE void WebServer::on_valve_update(valve::Valve *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", valve_state_json_generator); } void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1530,6 +1564,8 @@ std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) { #ifdef USE_ALARM_CONTROL_PANEL void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", alarm_control_panel_state_json_generator); } void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1607,7 +1643,9 @@ std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmContro #endif #ifdef USE_EVENT -void WebServer::on_event(event::Event *obj, const std::string &event_type) { +void WebServer::on_event(event::Event *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", event_state_json_generator); } diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 328140cfae..7e1af88645 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -255,7 +255,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_SENSOR - void on_sensor_update(sensor::Sensor *obj, float state) override; + void on_sensor_update(sensor::Sensor *obj) override; /// Handle a sensor request under '/sensor/'. void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -266,7 +266,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_SWITCH - void on_switch_update(switch_::Switch *obj, bool state) override; + void on_switch_update(switch_::Switch *obj) override; /// Handle a switch request under '/switch//'. void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -324,7 +324,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_TEXT_SENSOR - void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override; + void on_text_sensor_update(text_sensor::TextSensor *obj) override; /// Handle a text sensor request under '/text_sensor/'. void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -348,7 +348,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_NUMBER - void on_number_update(number::Number *obj, float state) override; + void on_number_update(number::Number *obj) override; /// Handle a number request under '/number/'. void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -392,7 +392,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_TEXT - void on_text_update(text::Text *obj, const std::string &state) override; + void on_text_update(text::Text *obj) override; /// Handle a text input request under '/text/'. void handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -403,7 +403,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_SELECT - void on_select_update(select::Select *obj, const std::string &state, size_t index) override; + void on_select_update(select::Select *obj) override; /// Handle a select request under '/select/'. void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -462,7 +462,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_EVENT - void on_event(event::Event *obj, const std::string &event_type) override; + void on_event(event::Event *obj) override; static std::string event_state_json_generator(WebServer *web_server, void *source); static std::string event_all_json_generator(WebServer *web_server, void *source); diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index fed5265d6b..08753b0f2d 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -48,6 +48,9 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) +# Key for tracking controller count in CORE.data for ControllerRegistry StaticVector sizing +KEY_CONTROLLER_REGISTRY_COUNT = "controller_registry_count" + class EsphomeError(Exception): """General ESPHome exception occurred.""" @@ -910,6 +913,11 @@ class EsphomeCore: """ self.platform_counts[platform_name] += 1 + def register_controller(self) -> None: + """Track registration of a Controller for ControllerRegistry StaticVector sizing.""" + controller_count = self.data.setdefault(KEY_CONTROLLER_REGISTRY_COUNT, 0) + self.data[KEY_CONTROLLER_REGISTRY_COUNT] = controller_count + 1 + @property def cpp_main_section(self): from esphome.cpp_generator import statement diff --git a/esphome/core/config.py b/esphome/core/config.py index 2740453808..763f9ebd9f 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -40,7 +40,12 @@ from esphome.const import ( PlatformFramework, __version__ as ESPHOME_VERSION, ) -from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.core import ( + CORE, + KEY_CONTROLLER_REGISTRY_COUNT, + CoroPriority, + coroutine_with_priority, +) from esphome.helpers import ( copy_file_if_changed, fnv1a_32bit_hash, @@ -462,6 +467,15 @@ async def _add_platform_defines() -> None: cg.add_define(f"USE_{platform_name.upper()}") +@coroutine_with_priority(CoroPriority.FINAL) +async def _add_controller_registry_define() -> None: + # Generate StaticVector size for ControllerRegistry + controller_count = CORE.data.get(KEY_CONTROLLER_REGISTRY_COUNT, 0) + if controller_count > 0: + cg.add_define("USE_CONTROLLER_REGISTRY") + cg.add_define("CONTROLLER_REGISTRY_MAX", controller_count) + + @coroutine_with_priority(CoroPriority.CORE) async def to_code(config: ConfigType) -> None: cg.add_global(cg.global_ns.namespace("esphome").using) @@ -483,6 +497,7 @@ async def to_code(config: ConfigType) -> None: cg.add_define("ESPHOME_COMPONENT_COUNT", len(CORE.component_ids)) CORE.add_job(_add_platform_defines) + CORE.add_job(_add_controller_registry_define) CORE.add_job(_add_automations, config) diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp deleted file mode 100644 index f7ff5a9734..0000000000 --- a/esphome/core/controller.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include "controller.h" -#include "esphome/core/application.h" -#include "esphome/core/log.h" - -namespace esphome { - -void Controller::setup_controller(bool include_internal) { -#ifdef USE_BINARY_SENSOR - for (auto *obj : App.get_binary_sensors()) { - if (include_internal || !obj->is_internal()) { - obj->add_full_state_callback( - [this, obj](optional previous, optional state) { this->on_binary_sensor_update(obj); }); - } - } -#endif -#ifdef USE_FAN - for (auto *obj : App.get_fans()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_fan_update(obj); }); - } -#endif -#ifdef USE_LIGHT - for (auto *obj : App.get_lights()) { - if (include_internal || !obj->is_internal()) - obj->add_new_remote_values_callback([this, obj]() { this->on_light_update(obj); }); - } -#endif -#ifdef USE_SENSOR - for (auto *obj : App.get_sensors()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](float state) { this->on_sensor_update(obj, state); }); - } -#endif -#ifdef USE_SWITCH - for (auto *obj : App.get_switches()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](bool state) { this->on_switch_update(obj, state); }); - } -#endif -#ifdef USE_COVER - for (auto *obj : App.get_covers()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_cover_update(obj); }); - } -#endif -#ifdef USE_TEXT_SENSOR - for (auto *obj : App.get_text_sensors()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](const std::string &state) { this->on_text_sensor_update(obj, state); }); - } -#endif -#ifdef USE_CLIMATE - for (auto *obj : App.get_climates()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](climate::Climate & /*unused*/) { this->on_climate_update(obj); }); - } -#endif -#ifdef USE_NUMBER - for (auto *obj : App.get_numbers()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](float state) { this->on_number_update(obj, state); }); - } -#endif -#ifdef USE_DATETIME_DATE - for (auto *obj : App.get_dates()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_date_update(obj); }); - } -#endif -#ifdef USE_DATETIME_TIME - for (auto *obj : App.get_times()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_time_update(obj); }); - } -#endif -#ifdef USE_DATETIME_DATETIME - for (auto *obj : App.get_datetimes()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_datetime_update(obj); }); - } -#endif -#ifdef USE_TEXT - for (auto *obj : App.get_texts()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](const std::string &state) { this->on_text_update(obj, state); }); - } -#endif -#ifdef USE_SELECT - for (auto *obj : App.get_selects()) { - if (include_internal || !obj->is_internal()) { - obj->add_on_state_callback( - [this, obj](const std::string &state, size_t index) { this->on_select_update(obj, state, index); }); - } - } -#endif -#ifdef USE_LOCK - for (auto *obj : App.get_locks()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_lock_update(obj); }); - } -#endif -#ifdef USE_VALVE - for (auto *obj : App.get_valves()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_valve_update(obj); }); - } -#endif -#ifdef USE_MEDIA_PLAYER - for (auto *obj : App.get_media_players()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_media_player_update(obj); }); - } -#endif -#ifdef USE_ALARM_CONTROL_PANEL - for (auto *obj : App.get_alarm_control_panels()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_alarm_control_panel_update(obj); }); - } -#endif -#ifdef USE_EVENT - for (auto *obj : App.get_events()) { - if (include_internal || !obj->is_internal()) - obj->add_on_event_callback([this, obj](const std::string &event_type) { this->on_event(obj, event_type); }); - } -#endif -#ifdef USE_UPDATE - for (auto *obj : App.get_updates()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_update(obj); }); - } -#endif -} - -} // namespace esphome diff --git a/esphome/core/controller.h b/esphome/core/controller.h index b475e326ee..697017217d 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -69,7 +69,6 @@ namespace esphome { class Controller { public: - void setup_controller(bool include_internal = false); #ifdef USE_BINARY_SENSOR virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj){}; #endif @@ -80,22 +79,22 @@ class Controller { virtual void on_light_update(light::LightState *obj){}; #endif #ifdef USE_SENSOR - virtual void on_sensor_update(sensor::Sensor *obj, float state){}; + virtual void on_sensor_update(sensor::Sensor *obj){}; #endif #ifdef USE_SWITCH - virtual void on_switch_update(switch_::Switch *obj, bool state){}; + virtual void on_switch_update(switch_::Switch *obj){}; #endif #ifdef USE_COVER virtual void on_cover_update(cover::Cover *obj){}; #endif #ifdef USE_TEXT_SENSOR - virtual void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state){}; + virtual void on_text_sensor_update(text_sensor::TextSensor *obj){}; #endif #ifdef USE_CLIMATE virtual void on_climate_update(climate::Climate *obj){}; #endif #ifdef USE_NUMBER - virtual void on_number_update(number::Number *obj, float state){}; + virtual void on_number_update(number::Number *obj){}; #endif #ifdef USE_DATETIME_DATE virtual void on_date_update(datetime::DateEntity *obj){}; @@ -107,10 +106,10 @@ class Controller { virtual void on_datetime_update(datetime::DateTimeEntity *obj){}; #endif #ifdef USE_TEXT - virtual void on_text_update(text::Text *obj, const std::string &state){}; + virtual void on_text_update(text::Text *obj){}; #endif #ifdef USE_SELECT - virtual void on_select_update(select::Select *obj, const std::string &state, size_t index){}; + virtual void on_select_update(select::Select *obj){}; #endif #ifdef USE_LOCK virtual void on_lock_update(lock::Lock *obj){}; @@ -125,7 +124,7 @@ class Controller { virtual void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj){}; #endif #ifdef USE_EVENT - virtual void on_event(event::Event *obj, const std::string &event_type){}; + virtual void on_event(event::Event *obj){}; #endif #ifdef USE_UPDATE virtual void on_update(update::UpdateEntity *obj){}; diff --git a/esphome/core/controller_registry.cpp b/esphome/core/controller_registry.cpp new file mode 100644 index 0000000000..0a84bb0d0d --- /dev/null +++ b/esphome/core/controller_registry.cpp @@ -0,0 +1,114 @@ +#include "esphome/core/controller_registry.h" + +#ifdef USE_CONTROLLER_REGISTRY + +#include "esphome/core/controller.h" + +namespace esphome { + +StaticVector ControllerRegistry::controllers; + +void ControllerRegistry::register_controller(Controller *controller) { controllers.push_back(controller); } + +// Macro for standard registry notification dispatch - calls on__update() +#define CONTROLLER_REGISTRY_NOTIFY(entity_type, entity_name) \ + void ControllerRegistry::notify_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \ + for (auto *controller : controllers) { \ + controller->on_##entity_name##_update(obj); \ + } \ + } + +// Macro for entities where controller method has no "_update" suffix (Event, Update) +#define CONTROLLER_REGISTRY_NOTIFY_NO_UPDATE_SUFFIX(entity_type, entity_name) \ + void ControllerRegistry::notify_##entity_name(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \ + for (auto *controller : controllers) { \ + controller->on_##entity_name(obj); \ + } \ + } + +#ifdef USE_BINARY_SENSOR +CONTROLLER_REGISTRY_NOTIFY(binary_sensor::BinarySensor, binary_sensor) +#endif + +#ifdef USE_FAN +CONTROLLER_REGISTRY_NOTIFY(fan::Fan, fan) +#endif + +#ifdef USE_LIGHT +CONTROLLER_REGISTRY_NOTIFY(light::LightState, light) +#endif + +#ifdef USE_SENSOR +CONTROLLER_REGISTRY_NOTIFY(sensor::Sensor, sensor) +#endif + +#ifdef USE_SWITCH +CONTROLLER_REGISTRY_NOTIFY(switch_::Switch, switch) +#endif + +#ifdef USE_COVER +CONTROLLER_REGISTRY_NOTIFY(cover::Cover, cover) +#endif + +#ifdef USE_TEXT_SENSOR +CONTROLLER_REGISTRY_NOTIFY(text_sensor::TextSensor, text_sensor) +#endif + +#ifdef USE_CLIMATE +CONTROLLER_REGISTRY_NOTIFY(climate::Climate, climate) +#endif + +#ifdef USE_NUMBER +CONTROLLER_REGISTRY_NOTIFY(number::Number, number) +#endif + +#ifdef USE_DATETIME_DATE +CONTROLLER_REGISTRY_NOTIFY(datetime::DateEntity, date) +#endif + +#ifdef USE_DATETIME_TIME +CONTROLLER_REGISTRY_NOTIFY(datetime::TimeEntity, time) +#endif + +#ifdef USE_DATETIME_DATETIME +CONTROLLER_REGISTRY_NOTIFY(datetime::DateTimeEntity, datetime) +#endif + +#ifdef USE_TEXT +CONTROLLER_REGISTRY_NOTIFY(text::Text, text) +#endif + +#ifdef USE_SELECT +CONTROLLER_REGISTRY_NOTIFY(select::Select, select) +#endif + +#ifdef USE_LOCK +CONTROLLER_REGISTRY_NOTIFY(lock::Lock, lock) +#endif + +#ifdef USE_VALVE +CONTROLLER_REGISTRY_NOTIFY(valve::Valve, valve) +#endif + +#ifdef USE_MEDIA_PLAYER +CONTROLLER_REGISTRY_NOTIFY(media_player::MediaPlayer, media_player) +#endif + +#ifdef USE_ALARM_CONTROL_PANEL +CONTROLLER_REGISTRY_NOTIFY(alarm_control_panel::AlarmControlPanel, alarm_control_panel) +#endif + +#ifdef USE_EVENT +CONTROLLER_REGISTRY_NOTIFY_NO_UPDATE_SUFFIX(event::Event, event) +#endif + +#ifdef USE_UPDATE +CONTROLLER_REGISTRY_NOTIFY_NO_UPDATE_SUFFIX(update::UpdateEntity, update) +#endif + +#undef CONTROLLER_REGISTRY_NOTIFY +#undef CONTROLLER_REGISTRY_NOTIFY_NO_UPDATE_SUFFIX + +} // namespace esphome + +#endif // USE_CONTROLLER_REGISTRY diff --git a/esphome/core/controller_registry.h b/esphome/core/controller_registry.h new file mode 100644 index 0000000000..640a276a0a --- /dev/null +++ b/esphome/core/controller_registry.h @@ -0,0 +1,245 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_CONTROLLER_REGISTRY + +#include "esphome/core/helpers.h" + +// Forward declarations +namespace esphome { + +class Controller; + +#ifdef USE_BINARY_SENSOR +namespace binary_sensor { +class BinarySensor; +} +#endif + +#ifdef USE_FAN +namespace fan { +class Fan; +} +#endif + +#ifdef USE_LIGHT +namespace light { +class LightState; +} +#endif + +#ifdef USE_SENSOR +namespace sensor { +class Sensor; +} +#endif + +#ifdef USE_SWITCH +namespace switch_ { +class Switch; +} +#endif + +#ifdef USE_COVER +namespace cover { +class Cover; +} +#endif + +#ifdef USE_TEXT_SENSOR +namespace text_sensor { +class TextSensor; +} +#endif + +#ifdef USE_CLIMATE +namespace climate { +class Climate; +} +#endif + +#ifdef USE_NUMBER +namespace number { +class Number; +} +#endif + +#ifdef USE_DATETIME_DATE +namespace datetime { +class DateEntity; +} +#endif + +#ifdef USE_DATETIME_TIME +namespace datetime { +class TimeEntity; +} +#endif + +#ifdef USE_DATETIME_DATETIME +namespace datetime { +class DateTimeEntity; +} +#endif + +#ifdef USE_TEXT +namespace text { +class Text; +} +#endif + +#ifdef USE_SELECT +namespace select { +class Select; +} +#endif + +#ifdef USE_LOCK +namespace lock { +class Lock; +} +#endif + +#ifdef USE_VALVE +namespace valve { +class Valve; +} +#endif + +#ifdef USE_MEDIA_PLAYER +namespace media_player { +class MediaPlayer; +} +#endif + +#ifdef USE_ALARM_CONTROL_PANEL +namespace alarm_control_panel { +class AlarmControlPanel; +} +#endif + +#ifdef USE_EVENT +namespace event { +class Event; +} +#endif + +#ifdef USE_UPDATE +namespace update { +class UpdateEntity; +} +#endif + +/** Global registry for Controllers to receive entity state updates. + * + * This singleton registry allows Controllers (APIServer, WebServer) to receive + * entity state change notifications without storing per-entity callbacks. + * + * Instead of each entity maintaining controller callbacks (32 bytes overhead per entity), + * entities call ControllerRegistry::notify_*_update() which iterates the small list + * of registered controllers (typically 2: API and WebServer). + * + * Controllers read state directly from entities using existing accessors (obj->state, etc.) + * rather than receiving it as callback parameters that were being ignored anyway. + * + * Memory savings: 32 bytes per entity (2 controllers × 16 bytes std::function overhead) + * Typical config (25 entities): ~780 bytes saved + * Large config (80 entities): ~2,540 bytes saved + */ +class ControllerRegistry { + public: + /** Register a controller to receive entity state updates. + * + * Controllers should call this in their setup() method. + * Typically only APIServer and WebServer register. + */ + static void register_controller(Controller *controller); + +#ifdef USE_BINARY_SENSOR + static void notify_binary_sensor_update(binary_sensor::BinarySensor *obj); +#endif + +#ifdef USE_FAN + static void notify_fan_update(fan::Fan *obj); +#endif + +#ifdef USE_LIGHT + static void notify_light_update(light::LightState *obj); +#endif + +#ifdef USE_SENSOR + static void notify_sensor_update(sensor::Sensor *obj); +#endif + +#ifdef USE_SWITCH + static void notify_switch_update(switch_::Switch *obj); +#endif + +#ifdef USE_COVER + static void notify_cover_update(cover::Cover *obj); +#endif + +#ifdef USE_TEXT_SENSOR + static void notify_text_sensor_update(text_sensor::TextSensor *obj); +#endif + +#ifdef USE_CLIMATE + static void notify_climate_update(climate::Climate *obj); +#endif + +#ifdef USE_NUMBER + static void notify_number_update(number::Number *obj); +#endif + +#ifdef USE_DATETIME_DATE + static void notify_date_update(datetime::DateEntity *obj); +#endif + +#ifdef USE_DATETIME_TIME + static void notify_time_update(datetime::TimeEntity *obj); +#endif + +#ifdef USE_DATETIME_DATETIME + static void notify_datetime_update(datetime::DateTimeEntity *obj); +#endif + +#ifdef USE_TEXT + static void notify_text_update(text::Text *obj); +#endif + +#ifdef USE_SELECT + static void notify_select_update(select::Select *obj); +#endif + +#ifdef USE_LOCK + static void notify_lock_update(lock::Lock *obj); +#endif + +#ifdef USE_VALVE + static void notify_valve_update(valve::Valve *obj); +#endif + +#ifdef USE_MEDIA_PLAYER + static void notify_media_player_update(media_player::MediaPlayer *obj); +#endif + +#ifdef USE_ALARM_CONTROL_PANEL + static void notify_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj); +#endif + +#ifdef USE_EVENT + static void notify_event(event::Event *obj); +#endif + +#ifdef USE_UPDATE + static void notify_update(update::UpdateEntity *obj); +#endif + + protected: + static StaticVector controllers; +}; + +} // namespace esphome + +#endif // USE_CONTROLLER_REGISTRY diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 2be32058ea..8230518071 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -28,6 +28,7 @@ #define USE_BUTTON #define USE_CAMERA #define USE_CLIMATE +#define USE_CONTROLLER_REGISTRY #define USE_COVER #define USE_DATETIME #define USE_DATETIME_DATE @@ -296,6 +297,7 @@ #define USE_DASHBOARD_IMPORT // Default counts for static analysis +#define CONTROLLER_REGISTRY_MAX 2 #define ESPHOME_COMPONENT_COUNT 50 #define ESPHOME_DEVICE_COUNT 10 #define ESPHOME_AREA_COUNT 10 From fbbdad75f6ad8b10b6c8926abd7a82a6b1c55345 Mon Sep 17 00:00:00 2001 From: Paul Schulz Date: Mon, 10 Nov 2025 11:56:02 +1030 Subject: [PATCH 0156/1145] [sx126x] Change BUSY, RST, DIO1 pins to general GPIO (from internal) (#11782) --- esphome/components/sx126x/__init__.py | 6 +++--- esphome/components/sx126x/sx126x.h | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/esphome/components/sx126x/__init__.py b/esphome/components/sx126x/__init__.py index f8f3b9d104..1eb83b7a33 100644 --- a/esphome/components/sx126x/__init__.py +++ b/esphome/components/sx126x/__init__.py @@ -189,7 +189,7 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(SX126x), cv.Optional(CONF_BANDWIDTH, default="125_0kHz"): cv.enum(BW), cv.Optional(CONF_BITRATE, default=4800): cv.int_range(min=600, max=300000), - cv.Required(CONF_BUSY_PIN): pins.internal_gpio_input_pin_schema, + cv.Required(CONF_BUSY_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_CODING_RATE, default="CR_4_5"): cv.enum(CODING_RATE), cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean, cv.Optional(CONF_CRC_INVERTED, default=True): cv.boolean, @@ -201,7 +201,7 @@ CONFIG_SCHEMA = ( cv.hex_int, cv.Range(min=0, max=0xFFFF) ), cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000), - cv.Required(CONF_DIO1_PIN): pins.internal_gpio_input_pin_schema, + cv.Required(CONF_DIO1_PIN): pins.gpio_input_pin_schema, cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000), cv.Required(CONF_HW_VERSION): cv.one_of( "sx1261", "sx1262", "sx1268", "llcc68", lower=True @@ -213,7 +213,7 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=256), cv.Optional(CONF_PREAMBLE_DETECT, default=2): cv.int_range(min=0, max=4), cv.Optional(CONF_PREAMBLE_SIZE, default=8): cv.int_range(min=1, max=65535), - cv.Required(CONF_RST_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_RST_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RX_START, default=True): cv.boolean, cv.Required(CONF_RF_SWITCH): cv.boolean, cv.Optional(CONF_SHAPING, default="NONE"): cv.enum(SHAPING), diff --git a/esphome/components/sx126x/sx126x.h b/esphome/components/sx126x/sx126x.h index 47d6449738..850d7d4c77 100644 --- a/esphome/components/sx126x/sx126x.h +++ b/esphome/components/sx126x/sx126x.h @@ -64,7 +64,7 @@ class SX126x : public Component, void dump_config() override; void set_bandwidth(SX126xBw bandwidth) { this->bandwidth_ = bandwidth; } void set_bitrate(uint32_t bitrate) { this->bitrate_ = bitrate; } - void set_busy_pin(InternalGPIOPin *busy_pin) { this->busy_pin_ = busy_pin; } + void set_busy_pin(GPIOPin *busy_pin) { this->busy_pin_ = busy_pin; } void set_coding_rate(uint8_t coding_rate) { this->coding_rate_ = coding_rate; } void set_crc_enable(bool crc_enable) { this->crc_enable_ = crc_enable; } void set_crc_inverted(bool crc_inverted) { this->crc_inverted_ = crc_inverted; } @@ -72,7 +72,7 @@ class SX126x : public Component, void set_crc_polynomial(uint16_t crc_polynomial) { this->crc_polynomial_ = crc_polynomial; } void set_crc_initial(uint16_t crc_initial) { this->crc_initial_ = crc_initial; } void set_deviation(uint32_t deviation) { this->deviation_ = deviation; } - void set_dio1_pin(InternalGPIOPin *dio1_pin) { this->dio1_pin_ = dio1_pin; } + void set_dio1_pin(GPIOPin *dio1_pin) { this->dio1_pin_ = dio1_pin; } void set_frequency(uint32_t frequency) { this->frequency_ = frequency; } void set_hw_version(const std::string &hw_version) { this->hw_version_ = hw_version; } void set_mode_rx(); @@ -85,7 +85,7 @@ class SX126x : public Component, void set_payload_length(uint8_t payload_length) { this->payload_length_ = payload_length; } void set_preamble_detect(uint16_t preamble_detect) { this->preamble_detect_ = preamble_detect; } void set_preamble_size(uint16_t preamble_size) { this->preamble_size_ = preamble_size; } - void set_rst_pin(InternalGPIOPin *rst_pin) { this->rst_pin_ = rst_pin; } + void set_rst_pin(GPIOPin *rst_pin) { this->rst_pin_ = rst_pin; } void set_rx_start(bool rx_start) { this->rx_start_ = rx_start; } void set_rf_switch(bool rf_switch) { this->rf_switch_ = rf_switch; } void set_shaping(uint8_t shaping) { this->shaping_ = shaping; } @@ -115,9 +115,9 @@ class SX126x : public Component, std::vector listeners_; std::vector packet_; std::vector sync_value_; - InternalGPIOPin *busy_pin_{nullptr}; - InternalGPIOPin *dio1_pin_{nullptr}; - InternalGPIOPin *rst_pin_{nullptr}; + GPIOPin *busy_pin_{nullptr}; + GPIOPin *dio1_pin_{nullptr}; + GPIOPin *rst_pin_{nullptr}; std::string hw_version_; char version_[16]; SX126xBw bandwidth_{SX126X_BW_125000}; From c17a31a8f836503b742a63f87125b6532f3db4ee Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 19:28:49 -0600 Subject: [PATCH 0157/1145] Ensure event paths are enabled in api compile tests (#11776) --- tests/components/api/common-base.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/components/api/common-base.yaml b/tests/components/api/common-base.yaml index c90fa4dfef..fc53b8ac7e 100644 --- a/tests/components/api/common-base.yaml +++ b/tests/components/api/common-base.yaml @@ -178,6 +178,14 @@ api: - logger.log: "Skipped loops" - logger.log: "After combined test" +event: + - platform: template + name: Test Event + id: test_event + event_types: + - single_click + - double_click + globals: - id: api_continuation_test_counter type: int From b47e89a7d593d3b38a9cadf0b75ea605c666cd0a Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Mon, 10 Nov 2025 03:21:38 +0100 Subject: [PATCH 0158/1145] [nrf52,watchdog] do not disable watchog if it is not nesesery (#11686) --- esphome/components/debug/__init__.py | 1 + esphome/components/zephyr/core.cpp | 10 +++++++++- esphome/core/defines.h | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index 6b4205a545..dc032f442e 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -59,6 +59,7 @@ async def to_code(config): zephyr_add_prj_conf("SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL", True) var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + cg.add_define("USE_DEBUG") FILTER_SOURCE_FILES = filter_source_files_from_platform( diff --git a/esphome/components/zephyr/core.cpp b/esphome/components/zephyr/core.cpp index 365b6b8ed2..d5427a0ebf 100644 --- a/esphome/components/zephyr/core.cpp +++ b/esphome/components/zephyr/core.cpp @@ -6,6 +6,7 @@ #include #include "esphome/core/hal.h" #include "esphome/core/helpers.h" +#include "esphome/core/defines.h" namespace esphome { @@ -25,7 +26,14 @@ void arch_init() { wdt_config.window.max = 2000; wdt_channel_id = wdt_install_timeout(WDT, &wdt_config); if (wdt_channel_id >= 0) { - wdt_setup(WDT, WDT_OPT_PAUSE_HALTED_BY_DBG | WDT_OPT_PAUSE_IN_SLEEP); + uint8_t options = 0; +#ifdef USE_DEBUG + options |= WDT_OPT_PAUSE_HALTED_BY_DBG; +#endif +#ifdef USE_DEEP_SLEEP + options |= WDT_OPT_PAUSE_IN_SLEEP; +#endif + wdt_setup(WDT, options); } } } diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 8230518071..ac725fbca9 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -34,6 +34,7 @@ #define USE_DATETIME_DATE #define USE_DATETIME_DATETIME #define USE_DATETIME_TIME +#define USE_DEBUG #define USE_DEEP_SLEEP #define USE_DEVICES #define USE_DISPLAY From 2a166536426c63c3ef588e76841bbbcf24ec4f6e Mon Sep 17 00:00:00 2001 From: On Freund Date: Mon, 10 Nov 2025 15:44:27 +0200 Subject: [PATCH 0159/1145] HLK-FM22X Face Recognition module component (#8059) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- CODEOWNERS | 1 + esphome/components/hlk_fm22x/__init__.py | 247 +++++++++++++ esphome/components/hlk_fm22x/binary_sensor.py | 21 ++ esphome/components/hlk_fm22x/hlk_fm22x.cpp | 325 ++++++++++++++++++ esphome/components/hlk_fm22x/hlk_fm22x.h | 224 ++++++++++++ esphome/components/hlk_fm22x/sensor.py | 47 +++ esphome/components/hlk_fm22x/text_sensor.py | 42 +++ .../components/hlk_fm22x/test.esp32-idf.yaml | 47 +++ .../hlk_fm22x/test.esp8266-ard.yaml | 47 +++ .../components/hlk_fm22x/test.rp2040-ard.yaml | 47 +++ 10 files changed, 1048 insertions(+) create mode 100644 esphome/components/hlk_fm22x/__init__.py create mode 100644 esphome/components/hlk_fm22x/binary_sensor.py create mode 100644 esphome/components/hlk_fm22x/hlk_fm22x.cpp create mode 100644 esphome/components/hlk_fm22x/hlk_fm22x.h create mode 100644 esphome/components/hlk_fm22x/sensor.py create mode 100644 esphome/components/hlk_fm22x/text_sensor.py create mode 100644 tests/components/hlk_fm22x/test.esp32-idf.yaml create mode 100644 tests/components/hlk_fm22x/test.esp8266-ard.yaml create mode 100644 tests/components/hlk_fm22x/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 7e785db451..393774372f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -206,6 +206,7 @@ esphome/components/hdc2010/* @optimusprimespace @ssieb esphome/components/he60r/* @clydebarrow esphome/components/heatpumpir/* @rob-deutsch esphome/components/hitachi_ac424/* @sourabhjaiswal +esphome/components/hlk_fm22x/* @OnFreund esphome/components/hm3301/* @freekode esphome/components/hmac_md5/* @dwmw2 esphome/components/homeassistant/* @esphome/core @OttoWinter diff --git a/esphome/components/hlk_fm22x/__init__.py b/esphome/components/hlk_fm22x/__init__.py new file mode 100644 index 0000000000..efd64b6513 --- /dev/null +++ b/esphome/components/hlk_fm22x/__init__.py @@ -0,0 +1,247 @@ +from esphome import automation +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import ( + CONF_DIRECTION, + CONF_ID, + CONF_NAME, + CONF_ON_ENROLLMENT_DONE, + CONF_ON_ENROLLMENT_FAILED, + CONF_TRIGGER_ID, +) + +CODEOWNERS = ["@OnFreund"] +DEPENDENCIES = ["uart"] +AUTO_LOAD = ["binary_sensor", "sensor", "text_sensor"] +MULTI_CONF = True + +CONF_HLK_FM22X_ID = "hlk_fm22x_id" +CONF_FACE_ID = "face_id" +CONF_ON_FACE_SCAN_MATCHED = "on_face_scan_matched" +CONF_ON_FACE_SCAN_UNMATCHED = "on_face_scan_unmatched" +CONF_ON_FACE_SCAN_INVALID = "on_face_scan_invalid" +CONF_ON_FACE_INFO = "on_face_info" + +hlk_fm22x_ns = cg.esphome_ns.namespace("hlk_fm22x") +HlkFm22xComponent = hlk_fm22x_ns.class_( + "HlkFm22xComponent", cg.PollingComponent, uart.UARTDevice +) + +FaceScanMatchedTrigger = hlk_fm22x_ns.class_( + "FaceScanMatchedTrigger", automation.Trigger.template(cg.int16, cg.std_string) +) + +FaceScanUnmatchedTrigger = hlk_fm22x_ns.class_( + "FaceScanUnmatchedTrigger", automation.Trigger.template() +) + +FaceScanInvalidTrigger = hlk_fm22x_ns.class_( + "FaceScanInvalidTrigger", automation.Trigger.template(cg.uint8) +) + +FaceInfoTrigger = hlk_fm22x_ns.class_( + "FaceInfoTrigger", + automation.Trigger.template( + cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16 + ), +) + +EnrollmentDoneTrigger = hlk_fm22x_ns.class_( + "EnrollmentDoneTrigger", automation.Trigger.template(cg.int16, cg.uint8) +) + +EnrollmentFailedTrigger = hlk_fm22x_ns.class_( + "EnrollmentFailedTrigger", automation.Trigger.template(cg.uint8) +) + +EnrollmentAction = hlk_fm22x_ns.class_("EnrollmentAction", automation.Action) +DeleteAction = hlk_fm22x_ns.class_("DeleteAction", automation.Action) +DeleteAllAction = hlk_fm22x_ns.class_("DeleteAllAction", automation.Action) +ScanAction = hlk_fm22x_ns.class_("ScanAction", automation.Action) +ResetAction = hlk_fm22x_ns.class_("ResetAction", automation.Action) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HlkFm22xComponent), + cv.Optional(CONF_ON_FACE_SCAN_MATCHED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FaceScanMatchedTrigger + ), + } + ), + cv.Optional(CONF_ON_FACE_SCAN_UNMATCHED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FaceScanUnmatchedTrigger + ), + } + ), + cv.Optional(CONF_ON_FACE_SCAN_INVALID): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FaceScanInvalidTrigger + ), + } + ), + cv.Optional(CONF_ON_FACE_INFO): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FaceInfoTrigger), + } + ), + cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + EnrollmentDoneTrigger + ), + } + ), + cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + EnrollmentFailedTrigger + ), + } + ), + } + ) + .extend(cv.polling_component_schema("50ms")) + .extend(uart.UART_DEVICE_SCHEMA), +) + + +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) + + for conf in config.get(CONF_ON_FACE_SCAN_MATCHED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(cg.int16, "face_id"), (cg.std_string, "name")], conf + ) + + for conf in config.get(CONF_ON_FACE_SCAN_UNMATCHED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_FACE_SCAN_INVALID, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.uint8, "error")], conf) + + for conf in config.get(CONF_ON_FACE_INFO, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, + [ + (cg.int16, "status"), + (cg.int16, "left"), + (cg.int16, "top"), + (cg.int16, "right"), + (cg.int16, "bottom"), + (cg.int16, "yaw"), + (cg.int16, "pitch"), + (cg.int16, "roll"), + ], + conf, + ) + + for conf in config.get(CONF_ON_ENROLLMENT_DONE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(cg.int16, "face_id"), (cg.uint8, "direction")], conf + ) + + for conf in config.get(CONF_ON_ENROLLMENT_FAILED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.uint8, "error")], conf) + + +@automation.register_action( + "hlk_fm22x.enroll", + EnrollmentAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(HlkFm22xComponent), + cv.Required(CONF_NAME): cv.templatable(cv.string), + cv.Required(CONF_DIRECTION): cv.templatable(cv.uint8_t), + }, + key=CONF_NAME, + ), +) +async def hlk_fm22x_enroll_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + + template_ = await cg.templatable(config[CONF_NAME], args, cg.std_string) + cg.add(var.set_name(template_)) + template_ = await cg.templatable(config[CONF_DIRECTION], args, cg.uint8) + cg.add(var.set_direction(template_)) + return var + + +@automation.register_action( + "hlk_fm22x.delete", + DeleteAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(HlkFm22xComponent), + cv.Required(CONF_FACE_ID): cv.templatable(cv.uint16_t), + }, + key=CONF_FACE_ID, + ), +) +async def hlk_fm22x_delete_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + + template_ = await cg.templatable(config[CONF_FACE_ID], args, cg.int16) + cg.add(var.set_face_id(template_)) + return var + + +@automation.register_action( + "hlk_fm22x.delete_all", + DeleteAllAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(HlkFm22xComponent), + } + ), +) +async def hlk_fm22x_delete_all_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +@automation.register_action( + "hlk_fm22x.scan", + ScanAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(HlkFm22xComponent), + } + ), +) +async def hlk_fm22x_scan_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +@automation.register_action( + "hlk_fm22x.reset", + ResetAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(HlkFm22xComponent), + } + ), +) +async def hlk_fm22x_reset_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var diff --git a/esphome/components/hlk_fm22x/binary_sensor.py b/esphome/components/hlk_fm22x/binary_sensor.py new file mode 100644 index 0000000000..3620f33ac0 --- /dev/null +++ b/esphome/components/hlk_fm22x/binary_sensor.py @@ -0,0 +1,21 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import CONF_ICON, ICON_KEY_PLUS + +from . import CONF_HLK_FM22X_ID, HlkFm22xComponent + +DEPENDENCIES = ["hlk_fm22x"] + +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend( + { + cv.GenerateID(CONF_HLK_FM22X_ID): cv.use_id(HlkFm22xComponent), + cv.Optional(CONF_ICON, default=ICON_KEY_PLUS): cv.icon, + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_HLK_FM22X_ID]) + var = await binary_sensor.new_binary_sensor(config) + cg.add(hub.set_enrolling_binary_sensor(var)) diff --git a/esphome/components/hlk_fm22x/hlk_fm22x.cpp b/esphome/components/hlk_fm22x/hlk_fm22x.cpp new file mode 100644 index 0000000000..ab15a2340d --- /dev/null +++ b/esphome/components/hlk_fm22x/hlk_fm22x.cpp @@ -0,0 +1,325 @@ +#include "hlk_fm22x.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include +#include + +namespace esphome::hlk_fm22x { + +static const char *const TAG = "hlk_fm22x"; + +void HlkFm22xComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up HLK-FM22X..."); + this->set_enrolling_(false); + while (this->available()) { + this->read(); + } + this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_STATUS); }); +} + +void HlkFm22xComponent::update() { + if (this->active_command_ != HlkFm22xCommand::NONE) { + if (this->wait_cycles_ > 600) { + ESP_LOGE(TAG, "Command 0x%.2X timed out", this->active_command_); + if (HlkFm22xCommand::RESET == this->active_command_) { + this->mark_failed(); + } else { + this->reset(); + } + } + } + this->recv_command_(); +} + +void HlkFm22xComponent::enroll_face(const std::string &name, HlkFm22xFaceDirection direction) { + if (name.length() > 31) { + ESP_LOGE(TAG, "enroll_face(): name too long '%s'", name.c_str()); + return; + } + ESP_LOGI(TAG, "Starting enrollment for %s", name.c_str()); + std::array data{}; + data[0] = 0; // admin + std::copy(name.begin(), name.end(), data.begin() + 1); + // Remaining bytes are already zero-initialized + data[33] = (uint8_t) direction; + data[34] = 10; // timeout + this->send_command_(HlkFm22xCommand::ENROLL, data.data(), data.size()); + this->set_enrolling_(true); +} + +void HlkFm22xComponent::scan_face() { + ESP_LOGI(TAG, "Verify face"); + static const uint8_t DATA[] = {0, 0}; + this->send_command_(HlkFm22xCommand::VERIFY, DATA, sizeof(DATA)); +} + +void HlkFm22xComponent::delete_face(int16_t face_id) { + ESP_LOGI(TAG, "Deleting face in slot %d", face_id); + const uint8_t data[] = {(uint8_t) (face_id >> 8), (uint8_t) (face_id & 0xFF)}; + this->send_command_(HlkFm22xCommand::DELETE_FACE, data, sizeof(data)); +} + +void HlkFm22xComponent::delete_all_faces() { + ESP_LOGI(TAG, "Deleting all stored faces"); + this->send_command_(HlkFm22xCommand::DELETE_ALL_FACES); +} + +void HlkFm22xComponent::get_face_count_() { + ESP_LOGD(TAG, "Getting face count"); + this->send_command_(HlkFm22xCommand::GET_ALL_FACE_IDS); +} + +void HlkFm22xComponent::reset() { + ESP_LOGI(TAG, "Resetting module"); + this->active_command_ = HlkFm22xCommand::NONE; + this->wait_cycles_ = 0; + this->set_enrolling_(false); + this->send_command_(HlkFm22xCommand::RESET); +} + +void HlkFm22xComponent::send_command_(HlkFm22xCommand command, const uint8_t *data, size_t size) { + ESP_LOGV(TAG, "Send command: 0x%.2X", command); + if (this->active_command_ != HlkFm22xCommand::NONE) { + ESP_LOGW(TAG, "Command 0x%.2X already active", this->active_command_); + return; + } + this->wait_cycles_ = 0; + this->active_command_ = command; + while (this->available()) + this->read(); + this->write((uint8_t) (START_CODE >> 8)); + this->write((uint8_t) (START_CODE & 0xFF)); + this->write((uint8_t) command); + uint16_t data_size = size; + this->write((uint8_t) (data_size >> 8)); + this->write((uint8_t) (data_size & 0xFF)); + + uint8_t checksum = 0; + checksum ^= (uint8_t) command; + checksum ^= (data_size >> 8); + checksum ^= (data_size & 0xFF); + for (size_t i = 0; i < size; i++) { + this->write(data[i]); + checksum ^= data[i]; + } + + this->write(checksum); + this->active_command_ = command; + this->wait_cycles_ = 0; +} + +void HlkFm22xComponent::recv_command_() { + uint8_t byte, checksum = 0; + uint16_t length = 0; + + if (this->available() < 7) { + ++this->wait_cycles_; + return; + } + this->wait_cycles_ = 0; + + if ((this->read() != (uint8_t) (START_CODE >> 8)) || (this->read() != (uint8_t) (START_CODE & 0xFF))) { + ESP_LOGE(TAG, "Invalid start code"); + return; + } + + byte = this->read(); + checksum ^= byte; + HlkFm22xResponseType response_type = (HlkFm22xResponseType) byte; + + byte = this->read(); + checksum ^= byte; + length = byte << 8; + byte = this->read(); + checksum ^= byte; + length |= byte; + + std::vector data; + data.reserve(length); + for (uint16_t idx = 0; idx < length; ++idx) { + byte = this->read(); + checksum ^= byte; + data.push_back(byte); + } + + ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type, format_hex_pretty(data).c_str()); + + byte = this->read(); + if (byte != checksum) { + ESP_LOGE(TAG, "Invalid checksum for data. Calculated: 0x%.2X, Received: 0x%.2X", checksum, byte); + return; + } + switch (response_type) { + case HlkFm22xResponseType::NOTE: + this->handle_note_(data); + break; + case HlkFm22xResponseType::REPLY: + this->handle_reply_(data); + break; + default: + ESP_LOGW(TAG, "Unexpected response type: 0x%.2X", response_type); + break; + } +} + +void HlkFm22xComponent::handle_note_(const std::vector &data) { + switch (data[0]) { + case HlkFm22xNoteType::FACE_STATE: + if (data.size() < 17) { + ESP_LOGE(TAG, "Invalid face note data size: %u", data.size()); + break; + } + { + int16_t info[8]; + uint8_t offset = 1; + for (int16_t &i : info) { + i = ((int16_t) data[offset + 1] << 8) | data[offset]; + offset += 2; + } + ESP_LOGV(TAG, "Face state: status: %d, left: %d, top: %d, right: %d, bottom: %d, yaw: %d, pitch: %d, roll: %d", + info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[7]); + this->face_info_callback_.call(info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[7]); + } + break; + case HlkFm22xNoteType::READY: + ESP_LOGE(TAG, "Command 0x%.2X timed out", this->active_command_); + switch (this->active_command_) { + case HlkFm22xCommand::ENROLL: + this->set_enrolling_(false); + this->enrollment_failed_callback_.call(HlkFm22xResult::FAILED4_TIMEOUT); + break; + case HlkFm22xCommand::VERIFY: + this->face_scan_invalid_callback_.call(HlkFm22xResult::FAILED4_TIMEOUT); + break; + default: + break; + } + this->active_command_ = HlkFm22xCommand::NONE; + this->wait_cycles_ = 0; + break; + default: + ESP_LOGW(TAG, "Unhandled note: 0x%.2X", data[0]); + break; + } +} + +void HlkFm22xComponent::handle_reply_(const std::vector &data) { + auto expected = this->active_command_; + this->active_command_ = HlkFm22xCommand::NONE; + if (data[0] != (uint8_t) expected) { + ESP_LOGE(TAG, "Unexpected response command. Expected: 0x%.2X, Received: 0x%.2X", expected, data[0]); + return; + } + + if (data[1] != HlkFm22xResult::SUCCESS) { + ESP_LOGE(TAG, "Command <0x%.2X> failed. Error: 0x%.2X", data[0], data[1]); + switch (expected) { + case HlkFm22xCommand::ENROLL: + this->set_enrolling_(false); + this->enrollment_failed_callback_.call(data[1]); + break; + case HlkFm22xCommand::VERIFY: + if (data[1] == HlkFm22xResult::REJECTED) { + this->face_scan_unmatched_callback_.call(); + } else { + this->face_scan_invalid_callback_.call(data[1]); + } + break; + default: + break; + } + return; + } + switch (expected) { + case HlkFm22xCommand::VERIFY: { + int16_t face_id = ((int16_t) data[2] << 8) | data[3]; + std::string name(data.begin() + 4, data.begin() + 36); + ESP_LOGD(TAG, "Face verified. ID: %d, name: %s", face_id, name.c_str()); + if (this->last_face_id_sensor_ != nullptr) { + this->last_face_id_sensor_->publish_state(face_id); + } + if (this->last_face_name_text_sensor_ != nullptr) { + this->last_face_name_text_sensor_->publish_state(name); + } + this->face_scan_matched_callback_.call(face_id, name); + break; + } + case HlkFm22xCommand::ENROLL: { + int16_t face_id = ((int16_t) data[2] << 8) | data[3]; + HlkFm22xFaceDirection direction = (HlkFm22xFaceDirection) data[4]; + ESP_LOGI(TAG, "Face enrolled. ID: %d, Direction: 0x%.2X", face_id, direction); + this->enrollment_done_callback_.call(face_id, (uint8_t) direction); + this->set_enrolling_(false); + this->defer([this]() { this->get_face_count_(); }); + break; + } + case HlkFm22xCommand::GET_STATUS: + if (this->status_sensor_ != nullptr) { + this->status_sensor_->publish_state(data[2]); + } + this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_VERSION); }); + break; + case HlkFm22xCommand::GET_VERSION: + if (this->version_text_sensor_ != nullptr) { + std::string version(data.begin() + 2, data.end()); + this->version_text_sensor_->publish_state(version); + } + this->defer([this]() { this->get_face_count_(); }); + break; + case HlkFm22xCommand::GET_ALL_FACE_IDS: + if (this->face_count_sensor_ != nullptr) { + this->face_count_sensor_->publish_state(data[2]); + } + break; + case HlkFm22xCommand::DELETE_FACE: + ESP_LOGI(TAG, "Deleted face"); + break; + case HlkFm22xCommand::DELETE_ALL_FACES: + ESP_LOGI(TAG, "Deleted all faces"); + break; + case HlkFm22xCommand::RESET: + ESP_LOGI(TAG, "Module reset"); + this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_STATUS); }); + break; + default: + ESP_LOGW(TAG, "Unhandled command: 0x%.2X", this->active_command_); + break; + } +} + +void HlkFm22xComponent::set_enrolling_(bool enrolling) { + if (this->enrolling_binary_sensor_ != nullptr) { + this->enrolling_binary_sensor_->publish_state(enrolling); + } +} + +void HlkFm22xComponent::dump_config() { + ESP_LOGCONFIG(TAG, "HLK_FM22X:"); + LOG_UPDATE_INTERVAL(this); + if (this->version_text_sensor_) { + LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %s", this->version_text_sensor_->get_state().c_str()); + } + if (this->enrolling_binary_sensor_) { + LOG_BINARY_SENSOR(" ", "Enrolling", this->enrolling_binary_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %s", this->enrolling_binary_sensor_->state ? "ON" : "OFF"); + } + if (this->face_count_sensor_) { + LOG_SENSOR(" ", "Face Count", this->face_count_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->face_count_sensor_->get_state()); + } + if (this->status_sensor_) { + LOG_SENSOR(" ", "Status", this->status_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->status_sensor_->get_state()); + } + if (this->last_face_id_sensor_) { + LOG_SENSOR(" ", "Last Face ID", this->last_face_id_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %u", (int16_t) this->last_face_id_sensor_->get_state()); + } + if (this->last_face_name_text_sensor_) { + LOG_TEXT_SENSOR(" ", "Last Face Name", this->last_face_name_text_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %s", this->last_face_name_text_sensor_->get_state().c_str()); + } +} + +} // namespace esphome::hlk_fm22x diff --git a/esphome/components/hlk_fm22x/hlk_fm22x.h b/esphome/components/hlk_fm22x/hlk_fm22x.h new file mode 100644 index 0000000000..5ecc715ea1 --- /dev/null +++ b/esphome/components/hlk_fm22x/hlk_fm22x.h @@ -0,0 +1,224 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/uart/uart.h" + +#include +#include + +namespace esphome::hlk_fm22x { + +static const uint16_t START_CODE = 0xEFAA; +enum HlkFm22xCommand { + NONE = 0x00, + RESET = 0x10, + GET_STATUS = 0x11, + VERIFY = 0x12, + ENROLL = 0x13, + DELETE_FACE = 0x20, + DELETE_ALL_FACES = 0x21, + GET_ALL_FACE_IDS = 0x24, + GET_VERSION = 0x30, + GET_SERIAL_NUMBER = 0x93, +}; + +enum HlkFm22xResponseType { + REPLY = 0x00, + NOTE = 0x01, + IMAGE = 0x02, +}; + +enum HlkFm22xNoteType { + READY = 0x00, + FACE_STATE = 0x01, +}; + +enum HlkFm22xResult { + SUCCESS = 0x00, + REJECTED = 0x01, + ABORTED = 0x02, + FAILED4_CAMERA = 0x04, + FAILED4_UNKNOWNREASON = 0x05, + FAILED4_INVALIDPARAM = 0x06, + FAILED4_NOMEMORY = 0x07, + FAILED4_UNKNOWNUSER = 0x08, + FAILED4_MAXUSER = 0x09, + FAILED4_FACEENROLLED = 0x0A, + FAILED4_LIVENESSCHECK = 0x0C, + FAILED4_TIMEOUT = 0x0D, + FAILED4_AUTHORIZATION = 0x0E, + FAILED4_READ_FILE = 0x13, + FAILED4_WRITE_FILE = 0x14, + FAILED4_NO_ENCRYPT = 0x15, + FAILED4_NO_RGBIMAGE = 0x17, + FAILED4_JPGPHOTO_LARGE = 0x18, + FAILED4_JPGPHOTO_SMALL = 0x19, +}; + +enum HlkFm22xFaceDirection { + FACE_DIRECTION_UNDEFINED = 0x00, + FACE_DIRECTION_MIDDLE = 0x01, + FACE_DIRECTION_RIGHT = 0x02, + FACE_DIRECTION_LEFT = 0x04, + FACE_DIRECTION_DOWN = 0x08, + FACE_DIRECTION_UP = 0x10, +}; + +class HlkFm22xComponent : public PollingComponent, public uart::UARTDevice { + public: + void setup() override; + void update() override; + void dump_config() override; + + void set_face_count_sensor(sensor::Sensor *face_count_sensor) { this->face_count_sensor_ = face_count_sensor; } + void set_status_sensor(sensor::Sensor *status_sensor) { this->status_sensor_ = status_sensor; } + void set_last_face_id_sensor(sensor::Sensor *last_face_id_sensor) { + this->last_face_id_sensor_ = last_face_id_sensor; + } + void set_last_face_name_text_sensor(text_sensor::TextSensor *last_face_name_text_sensor) { + this->last_face_name_text_sensor_ = last_face_name_text_sensor; + } + void set_enrolling_binary_sensor(binary_sensor::BinarySensor *enrolling_binary_sensor) { + this->enrolling_binary_sensor_ = enrolling_binary_sensor; + } + void set_version_text_sensor(text_sensor::TextSensor *version_text_sensor) { + this->version_text_sensor_ = version_text_sensor; + } + void add_on_face_scan_matched_callback(std::function callback) { + this->face_scan_matched_callback_.add(std::move(callback)); + } + void add_on_face_scan_unmatched_callback(std::function callback) { + this->face_scan_unmatched_callback_.add(std::move(callback)); + } + void add_on_face_scan_invalid_callback(std::function callback) { + this->face_scan_invalid_callback_.add(std::move(callback)); + } + void add_on_face_info_callback( + std::function callback) { + this->face_info_callback_.add(std::move(callback)); + } + void add_on_enrollment_done_callback(std::function callback) { + this->enrollment_done_callback_.add(std::move(callback)); + } + void add_on_enrollment_failed_callback(std::function callback) { + this->enrollment_failed_callback_.add(std::move(callback)); + } + + void enroll_face(const std::string &name, HlkFm22xFaceDirection direction); + void scan_face(); + void delete_face(int16_t face_id); + void delete_all_faces(); + void reset(); + + protected: + void get_face_count_(); + void send_command_(HlkFm22xCommand command, const uint8_t *data = nullptr, size_t size = 0); + void recv_command_(); + void handle_note_(const std::vector &data); + void handle_reply_(const std::vector &data); + void set_enrolling_(bool enrolling); + + HlkFm22xCommand active_command_ = HlkFm22xCommand::NONE; + uint16_t wait_cycles_ = 0; + sensor::Sensor *face_count_sensor_{nullptr}; + sensor::Sensor *status_sensor_{nullptr}; + sensor::Sensor *last_face_id_sensor_{nullptr}; + binary_sensor::BinarySensor *enrolling_binary_sensor_{nullptr}; + text_sensor::TextSensor *last_face_name_text_sensor_{nullptr}; + text_sensor::TextSensor *version_text_sensor_{nullptr}; + CallbackManager face_scan_invalid_callback_; + CallbackManager face_scan_matched_callback_; + CallbackManager face_scan_unmatched_callback_; + CallbackManager face_info_callback_; + CallbackManager enrollment_done_callback_; + CallbackManager enrollment_failed_callback_; +}; + +class FaceScanMatchedTrigger : public Trigger { + public: + explicit FaceScanMatchedTrigger(HlkFm22xComponent *parent) { + parent->add_on_face_scan_matched_callback( + [this](int16_t face_id, const std::string &name) { this->trigger(face_id, name); }); + } +}; + +class FaceScanUnmatchedTrigger : public Trigger<> { + public: + explicit FaceScanUnmatchedTrigger(HlkFm22xComponent *parent) { + parent->add_on_face_scan_unmatched_callback([this]() { this->trigger(); }); + } +}; + +class FaceScanInvalidTrigger : public Trigger { + public: + explicit FaceScanInvalidTrigger(HlkFm22xComponent *parent) { + parent->add_on_face_scan_invalid_callback([this](uint8_t error) { this->trigger(error); }); + } +}; + +class FaceInfoTrigger : public Trigger { + public: + explicit FaceInfoTrigger(HlkFm22xComponent *parent) { + parent->add_on_face_info_callback( + [this](int16_t status, int16_t left, int16_t top, int16_t right, int16_t bottom, int16_t yaw, int16_t pitch, + int16_t roll) { this->trigger(status, left, top, right, bottom, yaw, pitch, roll); }); + } +}; + +class EnrollmentDoneTrigger : public Trigger { + public: + explicit EnrollmentDoneTrigger(HlkFm22xComponent *parent) { + parent->add_on_enrollment_done_callback( + [this](int16_t face_id, uint8_t direction) { this->trigger(face_id, direction); }); + } +}; + +class EnrollmentFailedTrigger : public Trigger { + public: + explicit EnrollmentFailedTrigger(HlkFm22xComponent *parent) { + parent->add_on_enrollment_failed_callback([this](uint8_t error) { this->trigger(error); }); + } +}; + +template class EnrollmentAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(std::string, name) + TEMPLATABLE_VALUE(uint8_t, direction) + + void play(Ts... x) override { + auto name = this->name_.value(x...); + auto direction = (HlkFm22xFaceDirection) this->direction_.value(x...); + this->parent_->enroll_face(name, direction); + } +}; + +template class DeleteAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(int16_t, face_id) + + void play(Ts... x) override { + auto face_id = this->face_id_.value(x...); + this->parent_->delete_face(face_id); + } +}; + +template class DeleteAllAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->delete_all_faces(); } +}; + +template class ScanAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->scan_face(); } +}; + +template class ResetAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->reset(); } +}; + +} // namespace esphome::hlk_fm22x diff --git a/esphome/components/hlk_fm22x/sensor.py b/esphome/components/hlk_fm22x/sensor.py new file mode 100644 index 0000000000..e14b45599f --- /dev/null +++ b/esphome/components/hlk_fm22x/sensor.py @@ -0,0 +1,47 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import CONF_STATUS, ENTITY_CATEGORY_DIAGNOSTIC, ICON_ACCOUNT + +from . import CONF_HLK_FM22X_ID, HlkFm22xComponent + +DEPENDENCIES = ["hlk_fm22x"] + +CONF_FACE_COUNT = "face_count" +CONF_LAST_FACE_ID = "last_face_id" +ICON_FACE = "mdi:face-recognition" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_HLK_FM22X_ID): cv.use_id(HlkFm22xComponent), + cv.Optional(CONF_FACE_COUNT): sensor.sensor_schema( + icon=ICON_FACE, + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_STATUS): sensor.sensor_schema( + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_LAST_FACE_ID): sensor.sensor_schema( + icon=ICON_ACCOUNT, + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_HLK_FM22X_ID]) + + for key in [ + CONF_FACE_COUNT, + CONF_STATUS, + CONF_LAST_FACE_ID, + ]: + if key not in config: + continue + conf = config[key] + sens = await sensor.new_sensor(conf) + cg.add(getattr(hub, f"set_{key}_sensor")(sens)) diff --git a/esphome/components/hlk_fm22x/text_sensor.py b/esphome/components/hlk_fm22x/text_sensor.py new file mode 100644 index 0000000000..06da61c8b3 --- /dev/null +++ b/esphome/components/hlk_fm22x/text_sensor.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +from esphome.components import text_sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_VERSION, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_ACCOUNT, + ICON_RESTART, +) + +from . import CONF_HLK_FM22X_ID, HlkFm22xComponent + +DEPENDENCIES = ["hlk_fm22x"] + +CONF_LAST_FACE_NAME = "last_face_name" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_HLK_FM22X_ID): cv.use_id(HlkFm22xComponent), + cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema( + icon=ICON_RESTART, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_LAST_FACE_NAME): text_sensor.text_sensor_schema( + icon=ICON_ACCOUNT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_HLK_FM22X_ID]) + for key in [ + CONF_VERSION, + CONF_LAST_FACE_NAME, + ]: + if key not in config: + continue + conf = config[key] + sens = await text_sensor.new_text_sensor(conf) + cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) diff --git a/tests/components/hlk_fm22x/test.esp32-idf.yaml b/tests/components/hlk_fm22x/test.esp32-idf.yaml new file mode 100644 index 0000000000..5e7cbde664 --- /dev/null +++ b/tests/components/hlk_fm22x/test.esp32-idf.yaml @@ -0,0 +1,47 @@ +esphome: + on_boot: + then: + - hlk_fm22x.enroll: + name: "Test" + direction: 1 + - hlk_fm22x.delete_all: + +uart: + - id: uart_hlk_fm22x + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +hlk_fm22x: + on_face_scan_matched: + - logger.log: test_hlk_22x_face_scan_matched + on_face_scan_unmatched: + - logger.log: test_hlk_22x_face_scan_unmatched + on_face_scan_invalid: + - logger.log: test_hlk_22x_face_scan_invalid + on_face_info: + - logger.log: test_hlk_22x_face_info + on_enrollment_done: + - logger.log: test_hlk_22x_enrollment_done + on_enrollment_failed: + - logger.log: test_hlk_22x_enrollment_failed + +sensor: + - platform: hlk_fm22x + face_count: + name: "Face Count" + last_face_id: + name: "Last Face ID" + status: + name: "Face Status" + +binary_sensor: + - platform: hlk_fm22x + name: "Face Enrolling" + +text_sensor: + - platform: hlk_fm22x + version: + name: "HLK Version" + last_face_name: + name: "Last Face Name" diff --git a/tests/components/hlk_fm22x/test.esp8266-ard.yaml b/tests/components/hlk_fm22x/test.esp8266-ard.yaml new file mode 100644 index 0000000000..680047834c --- /dev/null +++ b/tests/components/hlk_fm22x/test.esp8266-ard.yaml @@ -0,0 +1,47 @@ +esphome: + on_boot: + then: + - hlk_fm22x.enroll: + name: "Test" + direction: 1 + - hlk_fm22x.delete_all: + +uart: + - id: uart_hlk_fm22x + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +hlk_fm22x: + on_face_scan_matched: + - logger.log: test_hlk_22x_face_scan_matched + on_face_scan_unmatched: + - logger.log: test_hlk_22x_face_scan_unmatched + on_face_scan_invalid: + - logger.log: test_hlk_22x_face_scan_invalid + on_face_info: + - logger.log: test_hlk_22x_face_info + on_enrollment_done: + - logger.log: test_hlk_22x_enrollment_done + on_enrollment_failed: + - logger.log: test_hlk_22x_enrollment_failed + +sensor: + - platform: hlk_fm22x + face_count: + name: "Face Count" + last_face_id: + name: "Last Face ID" + status: + name: "Face Status" + +binary_sensor: + - platform: hlk_fm22x + name: "Face Enrolling" + +text_sensor: + - platform: hlk_fm22x + version: + name: "HLK Version" + last_face_name: + name: "Last Face Name" diff --git a/tests/components/hlk_fm22x/test.rp2040-ard.yaml b/tests/components/hlk_fm22x/test.rp2040-ard.yaml new file mode 100644 index 0000000000..680047834c --- /dev/null +++ b/tests/components/hlk_fm22x/test.rp2040-ard.yaml @@ -0,0 +1,47 @@ +esphome: + on_boot: + then: + - hlk_fm22x.enroll: + name: "Test" + direction: 1 + - hlk_fm22x.delete_all: + +uart: + - id: uart_hlk_fm22x + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +hlk_fm22x: + on_face_scan_matched: + - logger.log: test_hlk_22x_face_scan_matched + on_face_scan_unmatched: + - logger.log: test_hlk_22x_face_scan_unmatched + on_face_scan_invalid: + - logger.log: test_hlk_22x_face_scan_invalid + on_face_info: + - logger.log: test_hlk_22x_face_info + on_enrollment_done: + - logger.log: test_hlk_22x_enrollment_done + on_enrollment_failed: + - logger.log: test_hlk_22x_enrollment_failed + +sensor: + - platform: hlk_fm22x + face_count: + name: "Face Count" + last_face_id: + name: "Last Face ID" + status: + name: "Face Status" + +binary_sensor: + - platform: hlk_fm22x + name: "Face Enrolling" + +text_sensor: + - platform: hlk_fm22x + version: + name: "HLK Version" + last_face_name: + name: "Last Face Name" From f32b69b8f15bb0e4208854c75f8283c51b515ac1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 10 Nov 2025 10:00:42 -0600 Subject: [PATCH 0160/1145] [tests] Add unit test coverage for web_port property (#11811) --- tests/unit_tests/test_core.py | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index 92b60efd93..e52cb24831 100644 --- a/tests/unit_tests/test_core.py +++ b/tests/unit_tests/test_core.py @@ -670,3 +670,51 @@ class TestEsphomeCore: os.environ.pop("ESPHOME_IS_HA_ADDON", None) os.environ.pop("ESPHOME_DATA_DIR", None) assert target.data_dir == Path(expected_default) + + def test_web_port__none(self, target): + """Test web_port returns None when web_server is not configured.""" + target.config = {} + assert target.web_port is None + + def test_web_port__explicit_web_server_default_port(self, target): + """Test web_port returns 80 when web_server is explicitly configured without port.""" + target.config = {const.CONF_WEB_SERVER: {}} + assert target.web_port == 80 + + def test_web_port__explicit_web_server_custom_port(self, target): + """Test web_port returns custom port when web_server is configured with port.""" + target.config = {const.CONF_WEB_SERVER: {const.CONF_PORT: 8080}} + assert target.web_port == 8080 + + def test_web_port__ota_web_server_platform_only(self, target): + """ + Test web_port returns None when ota.web_server platform is explicitly configured. + + This is a critical test for Dashboard Issue #766: + https://github.com/esphome/dashboard/issues/766 + + When ota: platform: web_server is explicitly configured (or auto-loaded by captive_portal): + - "web_server" appears in loaded_integrations (platform name added to integrations) + - "ota/web_server" appears in loaded_platforms + - But CONF_WEB_SERVER is NOT in config (only the platform is loaded, not the component) + - web_port MUST return None (no web UI available) + - Dashboard should NOT show VISIT button + + This test ensures web_port only checks CONF_WEB_SERVER in config, not loaded_integrations. + """ + # Simulate config with ota.web_server platform but no web_server component + # This happens when: + # 1. User explicitly configures: ota: - platform: web_server + # 2. OR captive_portal auto-loads ota.web_server + target.config = { + const.CONF_OTA: [ + { + "platform": "web_server", + # OTA web_server platform config would be here + } + ], + # Note: CONF_WEB_SERVER is NOT in config - only the OTA platform + } + # Even though "web_server" is in loaded_integrations due to the platform, + # web_port must return None because the full web_server component is not configured + assert target.web_port is None From 43eafbccb3f6a397e2107a005b37b8098f1880b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 13:28:14 -0600 Subject: [PATCH 0161/1145] Bump pytest-asyncio from 1.2.0 to 1.3.0 (#11815) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 81cb711eec..f845c47fc0 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,7 +8,7 @@ pre-commit pytest==8.4.2 pytest-cov==7.0.0 pytest-mock==3.15.1 -pytest-asyncio==1.2.0 +pytest-asyncio==1.3.0 pytest-xdist==3.8.0 asyncmock==0.4.2 hypothesis==6.92.1 From 8c5b9647223f0fe5a83b0820c98c0c05aa89252d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 13:28:25 -0600 Subject: [PATCH 0162/1145] Bump pyupgrade from 3.21.0 to 3.21.1 (#11816) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index f845c47fc0..169037753b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,7 @@ pylint==4.0.2 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating ruff==0.14.4 # also change in .pre-commit-config.yaml when updating -pyupgrade==3.21.0 # also change in .pre-commit-config.yaml when updating +pyupgrade==3.21.1 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests From e46300828e661b2b88bd3a568a7d46b0092c5c31 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 13:45:56 -0600 Subject: [PATCH 0163/1145] Bump pytest from 8.4.2 to 9.0.0 (#11817) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 169037753b..35010ad52f 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==3.21.1 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==8.4.2 +pytest==9.0.0 pytest-cov==7.0.0 pytest-mock==3.15.1 pytest-asyncio==1.3.0 From 40e2976ba2f46a58bb0fe220ce0c312d74a6586e Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Tue, 11 Nov 2025 00:33:34 +0100 Subject: [PATCH 0164/1145] [ai] simplify namespace syntax (#11824) --- .ai/instructions.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.ai/instructions.md b/.ai/instructions.md index 8d81c6cf0f..681829bae6 100644 --- a/.ai/instructions.md +++ b/.ai/instructions.md @@ -172,8 +172,7 @@ This document provides essential context for AI models interacting with this pro * **C++ Class Pattern:** ```cpp - namespace esphome { - namespace my_component { + namespace esphome::my_component { class MyComponent : public Component { public: @@ -189,8 +188,7 @@ This document provides essential context for AI models interacting with this pro int param_{0}; }; - } // namespace my_component - } // namespace esphome + } // namespace esphome::my_component ``` * **Common Component Examples:** From 0f8332fe3cd0680e9c98a04803f6b5884fe5ae47 Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Mon, 10 Nov 2025 16:04:03 -0800 Subject: [PATCH 0165/1145] [lvgl] Automatically register widget types (#11394) Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/lvgl/__init__.py | 103 ++++++-------------- esphome/components/lvgl/schemas.py | 4 + esphome/components/lvgl/types.py | 23 ++++- esphome/components/lvgl/widgets/__init__.py | 7 +- esphome/components/lvgl/widgets/spinbox.py | 16 +-- tests/components/lvgl/lvgl-package.yaml | 4 + 6 files changed, 64 insertions(+), 93 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 861999d0b7..4df68a6386 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -1,6 +1,8 @@ +import importlib import logging +import pkgutil -from esphome.automation import build_automation, register_action, validate_automation +from esphome.automation import build_automation, validate_automation import esphome.codegen as cg from esphome.components.const import CONF_COLOR_DEPTH, CONF_DRAW_ROUNDING from esphome.components.display import Display @@ -25,8 +27,8 @@ from esphome.cpp_generator import MockObj 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, focused_widgets, refreshed_widgets, update_to_code +from . import defines as df, helpers, lv_validation as lvalid, widgets +from .automation import disp_update, focused_widgets, refreshed_widgets from .defines import add_define from .encoders import ( ENCODERS_CONFIG, @@ -45,7 +47,6 @@ from .schemas import ( WIDGET_TYPES, any_widget_schema, container_schema, - create_modify_schema, obj_schema, ) from .styles import add_top_layer, styles_to_code, theme_to_code @@ -54,7 +55,6 @@ from .trigger import add_on_boot_triggers, generate_triggers from .types import ( FontEngine, IdleTrigger, - ObjUpdateAction, PlainTrigger, lv_font_t, lv_group_t, @@ -69,33 +69,23 @@ from .widgets import ( set_obj_properties, styles_used, ) -from .widgets.animimg import animimg_spec -from .widgets.arc import arc_spec -from .widgets.button import button_spec -from .widgets.buttonmatrix import buttonmatrix_spec -from .widgets.canvas import canvas_spec -from .widgets.checkbox import checkbox_spec -from .widgets.container import container_spec -from .widgets.dropdown import dropdown_spec -from .widgets.img import img_spec -from .widgets.keyboard import keyboard_spec -from .widgets.label import label_spec -from .widgets.led import led_spec -from .widgets.line import line_spec -from .widgets.lv_bar import bar_spec -from .widgets.meter import meter_spec + +# Import only what we actually use directly in this file from .widgets.msgbox import MSGBOX_SCHEMA, msgboxes_to_code -from .widgets.obj import obj_spec -from .widgets.page import add_pages, generate_page_triggers, page_spec -from .widgets.qrcode import qr_code_spec -from .widgets.roller import roller_spec -from .widgets.slider import slider_spec -from .widgets.spinbox import spinbox_spec -from .widgets.spinner import spinner_spec -from .widgets.switch import switch_spec -from .widgets.tabview import tabview_spec -from .widgets.textarea import textarea_spec -from .widgets.tileview import tileview_spec +from .widgets.obj import obj_spec # Used in LVGL_SCHEMA +from .widgets.page import ( # page_spec used in LVGL_SCHEMA + add_pages, + generate_page_triggers, + page_spec, +) + +# Widget registration happens via WidgetType.__init__ in individual widget files +# The imports below trigger creation of the widget types +# Action registration (lvgl.{widget}.update) happens automatically +# in the WidgetType.__init__ method + +for module_info in pkgutil.iter_modules(widgets.__path__): + importlib.import_module(f".widgets.{module_info.name}", package=__package__) DOMAIN = "lvgl" DEPENDENCIES = ["display"] @@ -103,41 +93,6 @@ AUTO_LOAD = ["key_provider"] CODEOWNERS = ["@clydebarrow"] LOGGER = logging.getLogger(__name__) -for w_type in ( - label_spec, - obj_spec, - button_spec, - bar_spec, - slider_spec, - arc_spec, - line_spec, - spinner_spec, - led_spec, - animimg_spec, - checkbox_spec, - img_spec, - switch_spec, - tabview_spec, - buttonmatrix_spec, - meter_spec, - dropdown_spec, - roller_spec, - textarea_spec, - spinbox_spec, - keyboard_spec, - tileview_spec, - qr_code_spec, - canvas_spec, - container_spec, -): - WIDGET_TYPES[w_type.name] = w_type - -for w_type in WIDGET_TYPES.values(): - register_action( - f"lvgl.{w_type.name}.update", - ObjUpdateAction, - create_modify_schema(w_type), - )(update_to_code) SIMPLE_TRIGGERS = ( df.CONF_ON_PAUSE, @@ -402,6 +357,15 @@ def add_hello_world(config): return config +def _theme_schema(value): + return cv.Schema( + { + cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA) + for name, w in WIDGET_TYPES.items() + } + )(value) + + FINAL_VALIDATE_SCHEMA = final_validation LVGL_SCHEMA = cv.All( @@ -454,12 +418,7 @@ LVGL_SCHEMA = cv.All( cv.Optional( df.CONF_TRANSPARENCY_KEY, default=0x000400 ): lvalid.lv_color, - cv.Optional(df.CONF_THEME): cv.Schema( - { - cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA) - for name, w in WIDGET_TYPES.items() - } - ), + cv.Optional(df.CONF_THEME): _theme_schema, cv.Optional(df.CONF_GRADIENTS): GRADIENT_SCHEMA, cv.Optional(df.CONF_TOUCHSCREENS, default=None): touchscreen_schema, cv.Optional(df.CONF_ENCODERS, default=None): ENCODERS_CONFIG, diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 0dcf420f24..6b77f66abb 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -411,6 +411,10 @@ def any_widget_schema(extras=None): Dynamically generate schemas for all possible LVGL widgets. This is what implements the ability to have a list of any kind of widget under the widgets: key. + This uses lazy evaluation - the schema is built when called during validation, + not at import time. This allows external components to register widgets + before schema validation begins. + :param extras: Additional schema to be applied to each generated one :return: A validator for the Widgets key """ diff --git a/esphome/components/lvgl/types.py b/esphome/components/lvgl/types.py index 8c33e13934..035320b6ac 100644 --- a/esphome/components/lvgl/types.py +++ b/esphome/components/lvgl/types.py @@ -1,8 +1,10 @@ import sys from esphome import automation, codegen as cg +from esphome.automation import register_action from esphome.config_validation import Schema from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_TEXT, CONF_VALUE +from esphome.core import EsphomeError from esphome.cpp_generator import MockObj, MockObjClass from esphome.cpp_types import esphome_ns @@ -124,13 +126,16 @@ class WidgetType: schema=None, modify_schema=None, lv_name=None, + is_mock: bool = False, ): """ :param name: The widget name, e.g. "bar" :param w_type: The C type of the widget :param parts: What parts this widget supports :param schema: The config schema for defining a widget - :param modify_schema: A schema to update the widget + :param modify_schema: A schema to update the widget, defaults to the same as the schema + :param lv_name: The name of the LVGL widget in the LVGL library, if different from the name + :param is_mock: Whether this widget is a mock widget, i.e. not a real LVGL widget """ self.name = name self.lv_name = lv_name or name @@ -146,6 +151,22 @@ class WidgetType: self.modify_schema = modify_schema self.mock_obj = MockObj(f"lv_{self.lv_name}", "_") + # Local import to avoid circular import + from .automation import update_to_code + from .schemas import WIDGET_TYPES, create_modify_schema + + if not is_mock: + if self.name in WIDGET_TYPES: + raise EsphomeError(f"Duplicate definition of widget type '{self.name}'") + WIDGET_TYPES[self.name] = self + + # Register the update action automatically + register_action( + f"lvgl.{self.name}.update", + ObjUpdateAction, + create_modify_schema(self), + )(update_to_code) + @property def animated(self): return False diff --git a/esphome/components/lvgl/widgets/__init__.py b/esphome/components/lvgl/widgets/__init__.py index 7d9f9cb7de..187b5828c2 100644 --- a/esphome/components/lvgl/widgets/__init__.py +++ b/esphome/components/lvgl/widgets/__init__.py @@ -213,17 +213,14 @@ class LvScrActType(WidgetType): """ def __init__(self): - super().__init__("lv_scr_act()", lv_obj_t, ()) + super().__init__("lv_scr_act()", lv_obj_t, (), is_mock=True) async def to_code(self, w, config: dict): return [] -lv_scr_act_spec = LvScrActType() - - def get_scr_act(lv_comp: MockObj) -> Widget: - return Widget.create(None, lv_comp.get_scr_act(), lv_scr_act_spec, {}) + return Widget.create(None, lv_comp.get_scr_act(), LvScrActType(), {}) def get_widget_generator(wid): diff --git a/esphome/components/lvgl/widgets/spinbox.py b/esphome/components/lvgl/widgets/spinbox.py index 26ad149c6f..ac23ded723 100644 --- a/esphome/components/lvgl/widgets/spinbox.py +++ b/esphome/components/lvgl/widgets/spinbox.py @@ -2,7 +2,7 @@ from esphome import automation import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_RANGE_FROM, CONF_RANGE_TO, CONF_STEP, CONF_VALUE -from ..automation import action_to_code, update_to_code +from ..automation import action_to_code from ..defines import ( CONF_CURSOR, CONF_DECIMAL_PLACES, @@ -171,17 +171,3 @@ async def spinbox_decrement(config, action_id, template_arg, args): lv.spinbox_decrement(w.obj) return await action_to_code(widgets, do_increment, action_id, template_arg, args) - - -@automation.register_action( - "lvgl.spinbox.update", - ObjUpdateAction, - cv.Schema( - { - cv.Required(CONF_ID): cv.use_id(lv_spinbox_t), - cv.Required(CONF_VALUE): lv_float, - } - ), -) -async def spinbox_update_to_code(config, action_id, template_arg, args): - return await update_to_code(config, action_id, template_arg, args) diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index b122d10f04..d7c342b16e 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -700,6 +700,10 @@ lvgl: width: 100% height: 10% align: top_mid + on_value: + - lvgl.spinbox.update: + id: spinbox_id + value: !lambda return x; - button: styles: spin_button id: spin_up From 855aa32f542d5296a7febf9b388819c20a19ecf9 Mon Sep 17 00:00:00 2001 From: Beormund <75735592+Beormund@users.noreply.github.com> Date: Tue, 11 Nov 2025 00:32:59 +0000 Subject: [PATCH 0166/1145] Add support for RX8130 RTC Chip (#10511) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/rx8130/__init__.py | 0 esphome/components/rx8130/rx8130.cpp | 127 ++++++++++++++++++ esphome/components/rx8130/rx8130.h | 35 +++++ esphome/components/rx8130/time.py | 56 ++++++++ tests/components/rx8130/common.yaml | 8 ++ tests/components/rx8130/test.esp32-idf.yaml | 4 + tests/components/rx8130/test.esp8266-ard.yaml | 4 + tests/components/rx8130/test.rp2040-ard.yaml | 4 + 9 files changed, 239 insertions(+) create mode 100644 esphome/components/rx8130/__init__.py create mode 100644 esphome/components/rx8130/rx8130.cpp create mode 100644 esphome/components/rx8130/rx8130.h create mode 100644 esphome/components/rx8130/time.py create mode 100644 tests/components/rx8130/common.yaml create mode 100644 tests/components/rx8130/test.esp32-idf.yaml create mode 100644 tests/components/rx8130/test.esp8266-ard.yaml create mode 100644 tests/components/rx8130/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 393774372f..e6970af47c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -396,6 +396,7 @@ esphome/components/rpi_dpi_rgb/* @clydebarrow esphome/components/rtl87xx/* @kuba2k2 esphome/components/rtttl/* @glmnet esphome/components/runtime_stats/* @bdraco +esphome/components/rx8130/* @beormund esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti esphome/components/scd4x/* @martgras @sjtrny esphome/components/script/* @esphome/core diff --git a/esphome/components/rx8130/__init__.py b/esphome/components/rx8130/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/rx8130/rx8130.cpp b/esphome/components/rx8130/rx8130.cpp new file mode 100644 index 0000000000..cf6ea3e6e6 --- /dev/null +++ b/esphome/components/rx8130/rx8130.cpp @@ -0,0 +1,127 @@ +#include "rx8130.h" +#include "esphome/core/log.h" + +// https://download.epsondevice.com/td/pdf/app/RX8130CE_en.pdf + +namespace esphome { +namespace rx8130 { + +static const uint8_t RX8130_REG_SEC = 0x10; +static const uint8_t RX8130_REG_MIN = 0x11; +static const uint8_t RX8130_REG_HOUR = 0x12; +static const uint8_t RX8130_REG_WDAY = 0x13; +static const uint8_t RX8130_REG_MDAY = 0x14; +static const uint8_t RX8130_REG_MONTH = 0x15; +static const uint8_t RX8130_REG_YEAR = 0x16; +static const uint8_t RX8130_REG_EXTEN = 0x1C; +static const uint8_t RX8130_REG_FLAG = 0x1D; +static const uint8_t RX8130_REG_CTRL0 = 0x1E; +static const uint8_t RX8130_REG_CTRL1 = 0x1F; +static const uint8_t RX8130_REG_DIG_OFFSET = 0x30; +static const uint8_t RX8130_BIT_CTRL_STOP = 0x40; +static const uint8_t RX8130_BAT_FLAGS = 0x30; +static const uint8_t RX8130_CLEAR_FLAGS = 0x00; + +static const char *const TAG = "rx8130"; + +constexpr uint8_t bcd2dec(uint8_t val) { return (val >> 4) * 10 + (val & 0x0f); } +constexpr uint8_t dec2bcd(uint8_t val) { return ((val / 10) << 4) + (val % 10); } + +void RX8130Component::setup() { + // Set digital offset to disabled with no offset + if (this->write_register(RX8130_REG_DIG_OFFSET, &RX8130_CLEAR_FLAGS, 1) != i2c::ERROR_OK) { + this->mark_failed(); + return; + } + // Disable wakeup timers + if (this->write_register(RX8130_REG_EXTEN, &RX8130_CLEAR_FLAGS, 1) != i2c::ERROR_OK) { + this->mark_failed(); + return; + } + // Clear VLF flag in case there has been data loss + if (this->write_register(RX8130_REG_FLAG, &RX8130_CLEAR_FLAGS, 1) != i2c::ERROR_OK) { + this->mark_failed(); + return; + } + // Clear test flag and disable interrupts + if (this->write_register(RX8130_REG_CTRL0, &RX8130_CLEAR_FLAGS, 1) != i2c::ERROR_OK) { + this->mark_failed(); + return; + } + // Enable battery charging and switching + if (this->write_register(RX8130_REG_CTRL1, &RX8130_BAT_FLAGS, 1) != i2c::ERROR_OK) { + this->mark_failed(); + return; + } + // Clear STOP bit + this->stop_(false); +} + +void RX8130Component::update() { this->read_time(); } + +void RX8130Component::dump_config() { + ESP_LOGCONFIG(TAG, "RX8130:"); + LOG_I2C_DEVICE(this); +} + +void RX8130Component::read_time() { + uint8_t date[7]; + if (this->read_register(RX8130_REG_SEC, date, 7) != i2c::ERROR_OK) { + this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); + return; + } + ESPTime rtc_time{ + .second = bcd2dec(date[0] & 0x7f), + .minute = bcd2dec(date[1] & 0x7f), + .hour = bcd2dec(date[2] & 0x3f), + .day_of_week = bcd2dec(date[3] & 0x7f), + .day_of_month = bcd2dec(date[4] & 0x3f), + .day_of_year = 1, // ignored by recalc_timestamp_utc(false) + .month = bcd2dec(date[5] & 0x1f), + .year = static_cast(bcd2dec(date[6]) + 2000), + .is_dst = false, // not used + .timestamp = 0 // overwritten by recalc_timestamp_utc(false) + }; + rtc_time.recalc_timestamp_utc(false); + if (!rtc_time.is_valid()) { + ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock."); + return; + } + ESP_LOGD(TAG, "Read UTC time: %04d-%02d-%02d %02d:%02d:%02d", rtc_time.year, rtc_time.month, rtc_time.day_of_month, + rtc_time.hour, rtc_time.minute, rtc_time.second); + time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp); +} + +void RX8130Component::write_time() { + auto now = time::RealTimeClock::utcnow(); + if (!now.is_valid()) { + ESP_LOGE(TAG, "Invalid system time, not syncing to RTC."); + return; + } + uint8_t buff[7]; + buff[0] = dec2bcd(now.second); + buff[1] = dec2bcd(now.minute); + buff[2] = dec2bcd(now.hour); + buff[3] = dec2bcd(now.day_of_week); + buff[4] = dec2bcd(now.day_of_month); + buff[5] = dec2bcd(now.month); + buff[6] = dec2bcd(now.year % 100); + this->stop_(true); + if (this->write_register(RX8130_REG_SEC, buff, 7) != i2c::ERROR_OK) { + this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); + } else { + ESP_LOGD(TAG, "Wrote UTC time: %04d-%02d-%02d %02d:%02d:%02d", now.year, now.month, now.day_of_month, now.hour, + now.minute, now.second); + } + this->stop_(false); +} + +void RX8130Component::stop_(bool stop) { + const uint8_t data = stop ? RX8130_BIT_CTRL_STOP : RX8130_CLEAR_FLAGS; + if (this->write_register(RX8130_REG_CTRL0, &data, 1) != i2c::ERROR_OK) { + this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); + } +} + +} // namespace rx8130 +} // namespace esphome diff --git a/esphome/components/rx8130/rx8130.h b/esphome/components/rx8130/rx8130.h new file mode 100644 index 0000000000..6694c763cd --- /dev/null +++ b/esphome/components/rx8130/rx8130.h @@ -0,0 +1,35 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/time/real_time_clock.h" + +namespace esphome { +namespace rx8130 { + +class RX8130Component : public time::RealTimeClock, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + void dump_config() override; + void read_time(); + void write_time(); + /// Ensure RTC is initialized at the correct time in the setup sequence + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void stop_(bool stop); +}; + +template class WriteAction : public Action, public Parented { + public: + void play(const Ts... x) override { this->parent_->write_time(); } +}; + +template class ReadAction : public Action, public Parented { + public: + void play(const Ts... x) override { this->parent_->read_time(); } +}; + +} // namespace rx8130 +} // namespace esphome diff --git a/esphome/components/rx8130/time.py b/esphome/components/rx8130/time.py new file mode 100644 index 0000000000..cb0402bd32 --- /dev/null +++ b/esphome/components/rx8130/time.py @@ -0,0 +1,56 @@ +from esphome import automation +import esphome.codegen as cg +from esphome.components import i2c, time +import esphome.config_validation as cv +from esphome.const import CONF_ID + +CODEOWNERS = ["@beormund"] +DEPENDENCIES = ["i2c"] +rx8130_ns = cg.esphome_ns.namespace("rx8130") +RX8130Component = rx8130_ns.class_("RX8130Component", time.RealTimeClock, i2c.I2CDevice) +WriteAction = rx8130_ns.class_("WriteAction", automation.Action) +ReadAction = rx8130_ns.class_("ReadAction", automation.Action) + + +CONFIG_SCHEMA = time.TIME_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(RX8130Component), + } +).extend(i2c.i2c_device_schema(0x32)) + + +@automation.register_action( + "rx8130.write_time", + WriteAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(RX8130Component), + } + ), +) +async def rx8130_write_time_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +@automation.register_action( + "rx8130.read_time", + ReadAction, + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(RX8130Component), + } + ), +) +async def rx8130_read_time_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +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) + await time.register_time(var, config) diff --git a/tests/components/rx8130/common.yaml b/tests/components/rx8130/common.yaml new file mode 100644 index 0000000000..e6b849e25b --- /dev/null +++ b/tests/components/rx8130/common.yaml @@ -0,0 +1,8 @@ +esphome: + on_boot: + - rx8130.read_time + - rx8130.write_time + +time: + - platform: rx8130 + i2c_id: i2c_bus diff --git a/tests/components/rx8130/test.esp32-idf.yaml b/tests/components/rx8130/test.esp32-idf.yaml new file mode 100644 index 0000000000..b47e39c389 --- /dev/null +++ b/tests/components/rx8130/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/rx8130/test.esp8266-ard.yaml b/tests/components/rx8130/test.esp8266-ard.yaml new file mode 100644 index 0000000000..4a98b9388a --- /dev/null +++ b/tests/components/rx8130/test.esp8266-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/rx8130/test.rp2040-ard.yaml b/tests/components/rx8130/test.rp2040-ard.yaml new file mode 100644 index 0000000000..319a7c71a6 --- /dev/null +++ b/tests/components/rx8130/test.rp2040-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml + +<<: !include common.yaml From 1cccfdd2b92ffb12c7f6a578258481f8c232351e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 10 Nov 2025 18:40:23 -0600 Subject: [PATCH 0167/1145] [wifi] Fix mesh network failover and improve retry logic reliability (#11805) --- esphome/components/wifi/wifi_component.cpp | 847 +++++++++++++++++---- esphome/components/wifi/wifi_component.h | 70 +- 2 files changed, 776 insertions(+), 141 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 789c22bae1..885288f46a 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -42,6 +42,258 @@ namespace wifi { static const char *const TAG = "wifi"; +/// WiFi Retry Logic - Priority-Based BSSID Selection +/// +/// The WiFi component uses a state machine with priority degradation to handle connection failures +/// and automatically cycle through different BSSIDs in mesh networks or multiple configured networks. +/// +/// Connection Flow: +/// ┌──────────────────────────────────────────────────────────────────────┐ +/// │ Fast Connect Path (Optional) │ +/// ├──────────────────────────────────────────────────────────────────────┤ +/// │ Entered if: configuration has 'fast_connect: true' │ +/// │ Optimization to skip scanning when possible: │ +/// │ │ +/// │ 1. INITIAL_CONNECT → Try one of: │ +/// │ a) Saved BSSID+channel (from previous boot) │ +/// │ b) First configured non-hidden network (any BSSID) │ +/// │ ↓ │ +/// │ [FAILED] → Check if more configured networks available │ +/// │ ↓ │ +/// │ 2. FAST_CONNECT_CYCLING_APS → Try remaining configured networks │ +/// │ (1 attempt each, any BSSID) │ +/// │ ↓ │ +/// │ [All Failed] → Fall through to explicit hidden or scanning │ +/// │ │ +/// │ Note: Fast connect data saved from previous successful connection │ +/// └──────────────────────────────────────────────────────────────────────┘ +/// ↓ +/// ┌──────────────────────────────────────────────────────────────────────┐ +/// │ Explicit Hidden Networks Path (Optional) │ +/// ├──────────────────────────────────────────────────────────────────────┤ +/// │ Entered if: first configured network has 'hidden: true' │ +/// │ │ +/// │ 1. EXPLICIT_HIDDEN → Try consecutive hidden networks (1 attempt) │ +/// │ Stop when visible network reached │ +/// │ ↓ │ +/// │ Example: Hidden1, Hidden2, Visible1, Hidden3, Visible2 │ +/// │ Try: Hidden1, Hidden2 (stop at Visible1) │ +/// │ ↓ │ +/// │ [All Failed] → Fall back to scan-based connection │ +/// │ │ +/// │ Note: Fast connect saves BSSID after first successful connection, │ +/// │ so subsequent boots use fast path instead of hidden mode │ +/// └──────────────────────────────────────────────────────────────────────┘ +/// ↓ +/// ┌──────────────────────────────────────────────────────────────────────┐ +/// │ Scan-Based Connection Path │ +/// ├──────────────────────────────────────────────────────────────────────┤ +/// │ │ +/// │ 1. SCAN → Sort by priority (highest first), then RSSI │ +/// │ ┌─────────────────────────────────────────────────┐ │ +/// │ │ scan_result_[0] = Best BSSID (highest priority) │ │ +/// │ │ scan_result_[1] = Second best │ │ +/// │ │ scan_result_[2] = Third best │ │ +/// │ └─────────────────────────────────────────────────┘ │ +/// │ ↓ │ +/// │ 2. SCAN_CONNECTING → Try scan_result_[0] (2 attempts) │ +/// │ (Visible1, Visible2 from example above) │ +/// │ ↓ │ +/// │ 3. FAILED → Decrease priority: 0.0 → -1.0 → -2.0 │ +/// │ (stored in persistent sta_priorities_) │ +/// │ ↓ │ +/// │ 4. Check for hidden networks: │ +/// │ - If found → RETRY_HIDDEN (try SSIDs not in scan, 1 attempt) │ +/// │ Skip hidden networks before first visible one │ +/// │ (Skip Hidden1/Hidden2, try Hidden3 from example) │ +/// │ - If none → Skip RETRY_HIDDEN, go to step 5 │ +/// │ ↓ │ +/// │ 5. FAILED → RESTARTING_ADAPTER (skipped if AP/improv active) │ +/// │ ↓ │ +/// │ 6. Loop back to start: │ +/// │ - If first network is hidden → EXPLICIT_HIDDEN (retry cycle) │ +/// │ - Otherwise → SCAN_CONNECTING (rescan) │ +/// │ ↓ │ +/// │ 7. RESCAN → Apply stored priorities, sort again │ +/// │ ┌─────────────────────────────────────────────────┐ │ +/// │ │ scan_result_[0] = BSSID B (priority 0.0) ← NEW │ │ +/// │ │ scan_result_[1] = BSSID C (priority 0.0) │ │ +/// │ │ scan_result_[2] = BSSID A (priority -2.0) ← OLD │ │ +/// │ └─────────────────────────────────────────────────┘ │ +/// │ ↓ │ +/// │ 8. SCAN_CONNECTING → Try scan_result_[0] (next best) │ +/// │ │ +/// │ Key: Priority system cycles through BSSIDs ACROSS scan cycles │ +/// │ Full retry cycle: EXPLICIT_HIDDEN → SCAN → RETRY_HIDDEN │ +/// │ Always try best available BSSID (scan_result_[0]) │ +/// └──────────────────────────────────────────────────────────────────────┘ +/// +/// Retry Phases: +/// - INITIAL_CONNECT: Try saved BSSID+channel (fast_connect), or fall back to normal flow +/// - FAST_CONNECT_CYCLING_APS: Cycle through remaining configured networks (1 attempt each, fast_connect only) +/// - EXPLICIT_HIDDEN: Try consecutive networks marked hidden:true before scanning (1 attempt per SSID) +/// - SCAN_CONNECTING: Connect using scan results (2 attempts per BSSID) +/// - RETRY_HIDDEN: Try networks not found in scan (1 attempt per SSID, skipped if none found) +/// - RESTARTING_ADAPTER: Restart WiFi adapter to clear stuck state +/// +/// Hidden Network Handling: +/// - Networks marked 'hidden: true' before first non-hidden → Tried in EXPLICIT_HIDDEN phase +/// - Networks marked 'hidden: true' after first non-hidden → Tried in RETRY_HIDDEN phase +/// - After successful connection, fast_connect saves BSSID → subsequent boots use fast path +/// - Networks not in scan results → Tried in RETRY_HIDDEN phase +/// - Networks visible in scan + not marked hidden → Skipped in RETRY_HIDDEN phase +/// - Networks marked 'hidden: true' always use hidden mode, even if broadcasting SSID + +static const LogString *retry_phase_to_log_string(WiFiRetryPhase phase) { + switch (phase) { + case WiFiRetryPhase::INITIAL_CONNECT: + return LOG_STR("INITIAL_CONNECT"); +#ifdef USE_WIFI_FAST_CONNECT + case WiFiRetryPhase::FAST_CONNECT_CYCLING_APS: + return LOG_STR("FAST_CONNECT_CYCLING"); +#endif + case WiFiRetryPhase::EXPLICIT_HIDDEN: + return LOG_STR("EXPLICIT_HIDDEN"); + case WiFiRetryPhase::SCAN_CONNECTING: + return LOG_STR("SCAN_CONNECTING"); + case WiFiRetryPhase::RETRY_HIDDEN: + return LOG_STR("RETRY_HIDDEN"); + case WiFiRetryPhase::RESTARTING_ADAPTER: + return LOG_STR("RESTARTING"); + default: + return LOG_STR("UNKNOWN"); + } +} + +bool WiFiComponent::went_through_explicit_hidden_phase_() const { + // If first configured network is marked hidden, we went through EXPLICIT_HIDDEN phase + // This means those networks were already tried and should be skipped in RETRY_HIDDEN + return !this->sta_.empty() && this->sta_[0].get_hidden(); +} + +int8_t WiFiComponent::find_first_non_hidden_index_() const { + // Find the first network that is NOT marked hidden:true + // This is where EXPLICIT_HIDDEN phase would have stopped + for (size_t i = 0; i < this->sta_.size(); i++) { + if (!this->sta_[i].get_hidden()) { + return static_cast(i); + } + } + return -1; // All networks are hidden +} + +// 2 attempts per BSSID in SCAN_CONNECTING phase +// Rationale: This is the ONLY phase where we decrease BSSID priority, so we must be very sure. +// Auth failures are common immediately after scan due to WiFi stack state transitions. +// Trying twice filters out false positives and prevents unnecessarily marking a good BSSID as bad. +// After 2 genuine failures, priority degradation ensures we skip this BSSID on subsequent scans. +static constexpr uint8_t WIFI_RETRY_COUNT_PER_BSSID = 2; + +// 1 attempt per SSID in RETRY_HIDDEN phase +// Rationale: Try hidden mode once, then rescan to get next best BSSID via priority system +static constexpr uint8_t WIFI_RETRY_COUNT_PER_SSID = 1; + +// 1 attempt per AP in fast_connect mode (INITIAL_CONNECT and FAST_CONNECT_CYCLING_APS) +// Rationale: Fast connect prioritizes speed - try each AP once to find a working one quickly +static constexpr uint8_t WIFI_RETRY_COUNT_PER_AP = 1; + +static constexpr uint8_t get_max_retries_for_phase(WiFiRetryPhase phase) { + switch (phase) { + case WiFiRetryPhase::INITIAL_CONNECT: +#ifdef USE_WIFI_FAST_CONNECT + case WiFiRetryPhase::FAST_CONNECT_CYCLING_APS: +#endif + // INITIAL_CONNECT and FAST_CONNECT_CYCLING_APS both use 1 attempt per AP (fast_connect mode) + return WIFI_RETRY_COUNT_PER_AP; + case WiFiRetryPhase::EXPLICIT_HIDDEN: + // Explicitly hidden network: 1 attempt (user marked as hidden, try once then scan) + return WIFI_RETRY_COUNT_PER_SSID; + case WiFiRetryPhase::SCAN_CONNECTING: + // Scan-based phase: 2 attempts per BSSID (handles transient auth failures after scan) + return WIFI_RETRY_COUNT_PER_BSSID; + case WiFiRetryPhase::RETRY_HIDDEN: + // Hidden network mode: 1 attempt per SSID + return WIFI_RETRY_COUNT_PER_SSID; + default: + return WIFI_RETRY_COUNT_PER_BSSID; + } +} + +static void apply_scan_result_to_params(WiFiAP ¶ms, const WiFiScanResult &scan) { + params.set_hidden(false); + params.set_ssid(scan.get_ssid()); + params.set_bssid(scan.get_bssid()); + params.set_channel(scan.get_channel()); +} + +bool WiFiComponent::needs_scan_results_() const { + // Only SCAN_CONNECTING phase needs scan results + if (this->retry_phase_ != WiFiRetryPhase::SCAN_CONNECTING) { + return false; + } + // Need scan if we have no results or no matching networks + return this->scan_result_.empty() || !this->scan_result_[0].get_matches(); +} + +bool WiFiComponent::ssid_was_seen_in_scan_(const std::string &ssid) const { + // Check if this SSID is configured as hidden + // If explicitly marked hidden, we should always try hidden mode regardless of scan results + for (const auto &conf : this->sta_) { + if (conf.get_ssid() == ssid && conf.get_hidden()) { + return false; // Treat as not seen - force hidden mode attempt + } + } + + // Otherwise, check if we saw it in scan results + for (const auto &scan : this->scan_result_) { + if (scan.get_ssid() == ssid) { + return true; + } + } + return false; +} + +int8_t WiFiComponent::find_next_hidden_sta_(int8_t start_index, bool include_explicit_hidden) { + // Find next SSID that wasn't in scan results (might be hidden) + // Start searching from start_index + 1 + for (size_t i = start_index + 1; i < this->sta_.size(); i++) { + const auto &sta = this->sta_[i]; + + // Skip networks that were already tried in EXPLICIT_HIDDEN phase + // Those are: networks marked hidden:true that appear before the first non-hidden network + if (!include_explicit_hidden && sta.get_hidden()) { + int8_t first_non_hidden_idx = this->find_first_non_hidden_index_(); + if (first_non_hidden_idx >= 0 && static_cast(i) < first_non_hidden_idx) { + ESP_LOGD(TAG, "Skipping " LOG_SECRET("'%s'") " (explicit hidden, already tried)", sta.get_ssid().c_str()); + continue; + } + } + + if (!this->ssid_was_seen_in_scan_(sta.get_ssid())) { + ESP_LOGD(TAG, "Hidden candidate " LOG_SECRET("'%s'") " at index %d", sta.get_ssid().c_str(), static_cast(i)); + return static_cast(i); + } + ESP_LOGD(TAG, "Skipping " LOG_SECRET("'%s'") " (visible in scan)", sta.get_ssid().c_str()); + } + // No hidden SSIDs found + return -1; +} + +void WiFiComponent::start_initial_connection_() { + // If first network (highest priority) is explicitly marked hidden, try it first before scanning + // This respects user's priority order when they explicitly configure hidden networks + if (!this->sta_.empty() && this->sta_[0].get_hidden()) { + ESP_LOGI(TAG, "Starting with explicit hidden network (highest priority)"); + this->selected_sta_index_ = 0; + this->retry_phase_ = WiFiRetryPhase::EXPLICIT_HIDDEN; + WiFiAP params = this->build_params_for_current_phase_(); + this->start_connecting(params, false); + } else { + ESP_LOGI(TAG, "Starting scan"); + this->start_scanning(); + } +} + #if defined(USE_ESP32) && defined(USE_WIFI_WPA2_EAP) && ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE static const char *eap_phase2_to_str(esp_eap_ttls_phase2_types type) { switch (type) { @@ -109,18 +361,28 @@ void WiFiComponent::start() { ESP_LOGV(TAG, "Setting Power Save Option failed"); } + this->transition_to_phase_(WiFiRetryPhase::INITIAL_CONNECT); #ifdef USE_WIFI_FAST_CONNECT WiFiAP params; - this->trying_loaded_ap_ = this->load_fast_connect_settings_(params); - if (!this->trying_loaded_ap_) { - // FAST CONNECT FALLBACK: No saved settings available - // Use first config (will use SSID from config) + bool loaded_fast_connect = this->load_fast_connect_settings_(params); + // Fast connect optimization: only use when we have saved BSSID+channel data + // Without saved data, try first configured network or use normal flow + if (loaded_fast_connect) { + ESP_LOGI(TAG, "Starting fast_connect (saved) " LOG_SECRET("'%s'"), params.get_ssid().c_str()); + this->start_connecting(params, false); + } else if (!this->sta_.empty() && !this->sta_[0].get_hidden()) { + // No saved data, but have configured networks - try first non-hidden network + ESP_LOGI(TAG, "Starting fast_connect (config) " LOG_SECRET("'%s'"), this->sta_[0].get_ssid().c_str()); this->selected_sta_index_ = 0; - params = this->build_wifi_ap_from_selected_(); + params = this->build_params_for_current_phase_(); + this->start_connecting(params, false); + } else { + // No saved data and (no networks OR first is hidden) - use normal flow + this->start_initial_connection_(); } - this->start_connecting(params, false); #else - this->start_scanning(); + // Without fast_connect: go straight to scanning (or hidden mode if all networks are hidden) + this->start_initial_connection_(); #endif #ifdef USE_WIFI_AP } else if (this->has_ap()) { @@ -150,8 +412,7 @@ void WiFiComponent::restart_adapter() { ESP_LOGW(TAG, "Restarting adapter"); this->wifi_mode_(false, {}); delay(100); // NOLINT - this->num_retried_ = 0; - this->retry_hidden_ = false; + // Don't set retry_phase_ or num_retried_ here - state machine handles transitions } void WiFiComponent::loop() { @@ -172,21 +433,19 @@ void WiFiComponent::loop() { case WIFI_COMPONENT_STATE_COOLDOWN: { this->status_set_warning(LOG_STR("waiting to reconnect")); if (millis() - this->action_started_ > 5000) { -#ifdef USE_WIFI_FAST_CONNECT - // Safety check: Ensure selected_sta_index_ is valid before retrying - // (should already be set by retry_connect(), but check for robustness) + // After cooldown, connect based on current retry phase this->reset_selected_ap_to_first_if_invalid_(); - WiFiAP params = this->build_wifi_ap_from_selected_(); - this->start_connecting(params, false); -#else - if (this->retry_hidden_) { - this->reset_selected_ap_to_first_if_invalid_(); - WiFiAP params = this->build_wifi_ap_from_selected_(); - this->start_connecting(params, false); - } else { + + // Check if we need to trigger a scan first + if (this->needs_scan_results_() && !this->all_networks_hidden_()) { + // Need scan results or no matching networks found - scan/rescan + ESP_LOGD(TAG, "Scanning required for phase %s", LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_))); this->start_scanning(); + } else { + // Have everything we need to connect (or all networks are hidden, skip scanning) + WiFiAP params = this->build_params_for_current_phase_(); + this->start_connecting(params, false); } -#endif } break; } @@ -344,30 +603,44 @@ void WiFiComponent::set_sta(const WiFiAP &ap) { this->selected_sta_index_ = 0; } -WiFiAP WiFiComponent::build_wifi_ap_from_selected_() const { - // PRECONDITION: selected_sta_index_ must be valid (ensured by all callers) +WiFiAP WiFiComponent::build_params_for_current_phase_() { const WiFiAP *config = this->get_selected_sta_(); - assert(config != nullptr); + if (config == nullptr) { + ESP_LOGE(TAG, "No valid network config (selected_sta_index_=%d, sta_.size()=%zu)", + static_cast(this->selected_sta_index_), this->sta_.size()); + // Return empty params - caller should handle this gracefully + return WiFiAP(); + } + WiFiAP params = *config; - // SYNCHRONIZATION: selected_sta_index_ and scan_result_[0] are kept in sync after wifi_scan_done(): - // - wifi_scan_done() sorts all scan results by priority/RSSI (best first) - // - It then finds which sta_[i] config matches scan_result_[0] - // - Sets selected_sta_index_ = i to record that matching config - // This sync holds until scan_result_ is cleared (e.g., after connection or in reset_for_next_ap_attempt_()) - if (!this->scan_result_.empty()) { - // Override with scan data - network is visible - const WiFiScanResult &scan = this->scan_result_[0]; - params.set_hidden(false); - params.set_ssid(scan.get_ssid()); - params.set_bssid(scan.get_bssid()); - params.set_channel(scan.get_channel()); - } else if (params.get_hidden()) { - // Hidden network - clear BSSID and channel even if set in config - // There might be multiple hidden networks with same SSID but we can't know which is correct - // Rely on probe-req with just SSID. Empty channel triggers ALL_CHANNEL_SCAN. - params.set_bssid(optional{}); - params.set_channel(optional{}); + switch (this->retry_phase_) { + case WiFiRetryPhase::INITIAL_CONNECT: +#ifdef USE_WIFI_FAST_CONNECT + case WiFiRetryPhase::FAST_CONNECT_CYCLING_APS: +#endif + // Fast connect phases: use config-only (no scan results) + // BSSID/channel from config if user specified them, otherwise empty + break; + + case WiFiRetryPhase::EXPLICIT_HIDDEN: + case WiFiRetryPhase::RETRY_HIDDEN: + // Hidden network mode: clear BSSID/channel to trigger probe request + // (both explicit hidden and retry hidden use same behavior) + params.set_bssid(optional{}); + params.set_channel(optional{}); + break; + + case WiFiRetryPhase::SCAN_CONNECTING: + // Scan-based phase: always use best scan result (index 0 - highest priority after sorting) + if (!this->scan_result_.empty()) { + apply_scan_result_to_params(params, this->scan_result_[0]); + } + break; + + case WiFiRetryPhase::RESTARTING_ADAPTER: + // Should not be building params during restart + break; } return params; @@ -392,7 +665,21 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa } void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { - ESP_LOGI(TAG, "Connecting to '%s'", ap.get_ssid().c_str()); + // Log connection attempt at INFO level with priority + std::string bssid_formatted; + float priority = 0.0f; + + if (ap.get_bssid().has_value()) { + bssid_formatted = format_mac_address_pretty(ap.get_bssid().value().data()); + priority = this->get_sta_priority(ap.get_bssid().value()); + } + + ESP_LOGI(TAG, + "Connecting to " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") " (priority %.1f, attempt %u/%u in phase %s)...", + ap.get_ssid().c_str(), ap.get_bssid().has_value() ? bssid_formatted.c_str() : LOG_STR_LITERAL("any"), + priority, this->num_retried_ + 1, get_max_retries_for_phase(this->retry_phase_), + LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_))); + #ifdef ESPHOME_LOG_HAS_VERBOSE ESP_LOGV(TAG, "Connection Params:"); ESP_LOGV(TAG, " SSID: '%s'", ap.get_ssid().c_str()); @@ -565,8 +852,39 @@ void WiFiComponent::start_scanning() { this->state_ = WIFI_COMPONENT_STATE_STA_SCANNING; } -// Helper function for WiFi scan result comparison -// Returns true if 'a' should be placed before 'b' in the sorted order +/// Comparator for WiFi scan result sorting - determines which network should be tried first +/// Returns true if 'a' should be placed before 'b' in the sorted order (a is "better" than b) +/// +/// Sorting logic (in priority order): +/// 1. Matching networks always ranked before non-matching networks +/// 2. For matching networks: Priority first (CRITICAL - tracks failure history) +/// 3. RSSI as tiebreaker for equal priority or non-matching networks +/// +/// WHY PRIORITY MUST BE CHECKED FIRST: +/// The priority field tracks connection failure history via priority degradation: +/// - Initial priority: 0.0 (from config or default) +/// - Each connection failure: priority -= 1.0 (becomes -1.0, -2.0, -3.0, etc.) +/// - Failed BSSIDs sorted lower → naturally try different BSSID on next scan +/// +/// This enables automatic BSSID cycling for various real-world failure scenarios: +/// - Crashed/hung AP (visible but not responding) +/// - Misconfigured mesh node (accepts auth but no DHCP/routing) +/// - Capacity limits (AP refuses new clients) +/// - Rogue AP (same SSID, wrong password or malicious) +/// - Intermittent hardware issues (flaky radio, overheating) +/// +/// Example mesh network: 3 APs with same SSID "home", all at priority 0.0 initially +/// - Try strongest BSSID A (sorted by RSSI) → fails → priority A becomes -1.0 +/// - Next scan: BSSID B and C (priority 0.0) sorted BEFORE A (priority -1.0) +/// - Try next strongest BSSID B → succeeds or fails and gets deprioritized +/// - System naturally cycles through all BSSIDs via priority degradation +/// - Eventually finds working AP or tries all options before restarting adapter +/// +/// If we checked RSSI first (Bug in PR #9963): +/// - Same failed BSSID would keep being selected if it has strongest signal +/// - Device stuck connecting to crashed AP with -30dBm while working AP at -50dBm ignored +/// - Priority degradation would be useless +/// - Mesh networks would never recover from single AP failure [[nodiscard]] inline static bool wifi_scan_result_is_better(const WiFiScanResult &a, const WiFiScanResult &b) { // Matching networks always come before non-matching if (a.get_matches() && !b.get_matches()) @@ -574,21 +892,13 @@ void WiFiComponent::start_scanning() { if (!a.get_matches() && b.get_matches()) return false; - if (a.get_matches() && b.get_matches()) { - // For APs with the same SSID, always prefer stronger signal - // This helps with mesh networks and multiple APs - if (a.get_ssid() == b.get_ssid()) { - return a.get_rssi() > b.get_rssi(); - } - - // For different SSIDs, check priority first - if (a.get_priority() != b.get_priority()) - return a.get_priority() > b.get_priority(); - // If priorities are equal, prefer stronger signal - return a.get_rssi() > b.get_rssi(); + // Both matching: check priority first (tracks connection failures via priority degradation) + // Priority is decreased when a BSSID fails to connect, so lower priority = previously failed + if (a.get_matches() && b.get_matches() && a.get_priority() != b.get_priority()) { + return a.get_priority() > b.get_priority(); } - // Both don't match - sort by signal strength + // Use RSSI as tiebreaker (for equal-priority matching networks or all non-matching networks) return a.get_rssi() > b.get_rssi(); } @@ -623,10 +933,8 @@ __attribute__((noinline)) static void log_scan_result(const WiFiScanResult &res) ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), res.get_is_hidden() ? LOG_STR_LITERAL("(HIDDEN) ") : LOG_STR_LITERAL(""), bssid_s, LOG_STR_ARG(get_signal_bars(res.get_rssi()))); - ESP_LOGD(TAG, - " Channel: %u\n" - " RSSI: %d dB", - res.get_channel(), res.get_rssi()); + ESP_LOGD(TAG, " Channel: %2u, RSSI: %3d dB, Priority: %4.1f", res.get_channel(), res.get_rssi(), + res.get_priority()); } else { ESP_LOGD(TAG, "- " LOG_SECRET("'%s'") " " LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), bssid_s, LOG_STR_ARG(get_signal_bars(res.get_rssi()))); @@ -675,34 +983,36 @@ void WiFiComponent::check_scanning_finished() { // SYNCHRONIZATION POINT: Establish link between scan_result_[0] and selected_sta_index_ // After sorting, scan_result_[0] contains the best network. Now find which sta_[i] config // matches that network and record it in selected_sta_index_. This keeps the two indices - // synchronized so build_wifi_ap_from_selected_() can safely use both to build connection parameters. + // synchronized so build_params_for_current_phase_() can safely use both to build connection parameters. const WiFiScanResult &scan_res = this->scan_result_[0]; - if (!scan_res.get_matches()) { - ESP_LOGW(TAG, "No matching network found"); - this->retry_connect(); - return; - } - bool found_match = false; - for (size_t i = 0; i < this->sta_.size(); i++) { - if (scan_res.matches(this->sta_[i])) { - // Safe cast: sta_.size() limited to MAX_WIFI_NETWORKS (127) in __init__.py validation - // No overflow check needed - YAML validation prevents >127 networks - this->selected_sta_index_ = static_cast(i); // Links scan_result_[0] with sta_[i] - found_match = true; - break; + if (scan_res.get_matches()) { + for (size_t i = 0; i < this->sta_.size(); i++) { + if (scan_res.matches(this->sta_[i])) { + // Safe cast: sta_.size() limited to MAX_WIFI_NETWORKS (127) in __init__.py validation + // No overflow check needed - YAML validation prevents >127 networks + this->selected_sta_index_ = static_cast(i); // Links scan_result_[0] with sta_[i] + found_match = true; + break; + } } } if (!found_match) { ESP_LOGW(TAG, "No matching network found"); - this->retry_connect(); - return; + // No scan results matched our configured networks - transition directly to hidden mode + // Don't call retry_connect() since we never attempted a connection (no BSSID to penalize) + this->transition_to_phase_(WiFiRetryPhase::RETRY_HIDDEN); + // Now start connection attempt in hidden mode + } else if (this->transition_to_phase_(WiFiRetryPhase::SCAN_CONNECTING)) { + return; // scan started, wait for next loop iteration } yield(); - WiFiAP params = this->build_wifi_ap_from_selected_(); + WiFiAP params = this->build_params_for_current_phase_(); + // Ensure we're in SCAN_CONNECTING phase when connecting with scan results + // (needed when scan was started directly without transition_to_phase_, e.g., initial scan) this->start_connecting(params, false); } @@ -724,11 +1034,14 @@ void WiFiComponent::check_connecting_finished() { ESP_LOGI(TAG, "Connected"); // Warn if we had to retry with hidden network mode for a network that's not marked hidden // Only warn if we actually connected without scan data (SSID only), not if scan succeeded on retry - if (const WiFiAP *config = this->get_selected_sta_(); - this->retry_hidden_ && config && !config->get_hidden() && this->scan_result_.empty()) { - ESP_LOGW(TAG, "Network '%s' should be marked as hidden", config->get_ssid().c_str()); + if (const WiFiAP *config = this->get_selected_sta_(); this->retry_phase_ == WiFiRetryPhase::RETRY_HIDDEN && + config && !config->get_hidden() && + this->scan_result_.empty()) { + ESP_LOGW(TAG, LOG_SECRET("'%s'") " should be marked hidden", config->get_ssid().c_str()); } - this->retry_hidden_ = false; + // Reset to initial phase on successful connection (don't log transition, just reset state) + this->retry_phase_ = WiFiRetryPhase::INITIAL_CONNECT; + this->num_retried_ = 0; this->print_connect_params_(); @@ -796,58 +1109,334 @@ void WiFiComponent::check_connecting_finished() { this->retry_connect(); } -void WiFiComponent::retry_connect() { - if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_bssid()) { - auto bssid = *config->get_bssid(); - float priority = this->get_sta_priority(bssid); - this->set_sta_priority(bssid, priority - 1.0f); +/// Determine the next retry phase based on current state and failure conditions +/// This function examines the current retry phase, number of retries, and failure reasons +/// to decide what phase to move to next. It does not modify any state - it only returns +/// the recommended next phase. +/// +/// @return The next WiFiRetryPhase to transition to (may be same as current phase if should retry) +WiFiRetryPhase WiFiComponent::determine_next_phase_() { + switch (this->retry_phase_) { + case WiFiRetryPhase::INITIAL_CONNECT: +#ifdef USE_WIFI_FAST_CONNECT + case WiFiRetryPhase::FAST_CONNECT_CYCLING_APS: + // INITIAL_CONNECT and FAST_CONNECT_CYCLING_APS: no retries, try next AP or fall back to scan + if (this->selected_sta_index_ < static_cast(this->sta_.size()) - 1) { + return WiFiRetryPhase::FAST_CONNECT_CYCLING_APS; // Move to next AP + } +#endif + // No more APs to try, fall back to scan + return WiFiRetryPhase::SCAN_CONNECTING; + + case WiFiRetryPhase::EXPLICIT_HIDDEN: { + // Try all explicitly hidden networks before scanning + if (this->num_retried_ + 1 < WIFI_RETRY_COUNT_PER_SSID) { + return WiFiRetryPhase::EXPLICIT_HIDDEN; // Keep retrying same SSID + } + + // Exhausted retries on current SSID - check for more explicitly hidden networks + // Stop when we reach a visible network (proceed to scanning) + size_t next_index = this->selected_sta_index_ + 1; + if (next_index < this->sta_.size() && this->sta_[next_index].get_hidden()) { + // Found another explicitly hidden network + return WiFiRetryPhase::EXPLICIT_HIDDEN; + } + + // No more consecutive explicitly hidden networks - proceed to scanning + return WiFiRetryPhase::SCAN_CONNECTING; + } + + case WiFiRetryPhase::SCAN_CONNECTING: + // If scan found no matching networks, skip to hidden network mode + if (!this->scan_result_.empty() && !this->scan_result_[0].get_matches()) { + return WiFiRetryPhase::RETRY_HIDDEN; + } + + if (this->num_retried_ + 1 < WIFI_RETRY_COUNT_PER_BSSID) { + return WiFiRetryPhase::SCAN_CONNECTING; // Keep retrying same BSSID + } + + // Exhausted retries on current BSSID (scan_result_[0]) + // Its priority has been decreased, so on next scan it will be sorted lower + // and we'll try the next best BSSID. + // Check if there are any potentially hidden networks to try + if (this->find_next_hidden_sta_(-1, !this->went_through_explicit_hidden_phase_()) >= 0) { + return WiFiRetryPhase::RETRY_HIDDEN; // Found hidden networks to try + } + // No hidden networks - skip directly to restart/rescan + if (this->is_captive_portal_active_() || this->is_esp32_improv_active_()) { + return this->went_through_explicit_hidden_phase_() ? WiFiRetryPhase::EXPLICIT_HIDDEN + : WiFiRetryPhase::SCAN_CONNECTING; + } + return WiFiRetryPhase::RESTARTING_ADAPTER; + + case WiFiRetryPhase::RETRY_HIDDEN: + // If no hidden SSIDs to try (selected_sta_index_ == -1), skip directly to rescan + if (this->selected_sta_index_ >= 0) { + if (this->num_retried_ + 1 < WIFI_RETRY_COUNT_PER_SSID) { + return WiFiRetryPhase::RETRY_HIDDEN; // Keep retrying same SSID + } + + // Exhausted retries on current SSID - check if there are more potentially hidden SSIDs to try + if (this->selected_sta_index_ < static_cast(this->sta_.size()) - 1) { + // More SSIDs available - stay in RETRY_HIDDEN, advance will happen in retry_connect() + return WiFiRetryPhase::RETRY_HIDDEN; + } + } + // Exhausted all potentially hidden SSIDs - rescan to try next BSSID + // If captive portal/improv is active, skip adapter restart and go back to start + // Otherwise restart adapter to clear any stuck state + if (this->is_captive_portal_active_() || this->is_esp32_improv_active_()) { + // Go back to explicit hidden if we went through it initially, otherwise scan + return this->went_through_explicit_hidden_phase_() ? WiFiRetryPhase::EXPLICIT_HIDDEN + : WiFiRetryPhase::SCAN_CONNECTING; + } + + // Restart adapter + return WiFiRetryPhase::RESTARTING_ADAPTER; + + case WiFiRetryPhase::RESTARTING_ADAPTER: + // After restart, go back to explicit hidden if we went through it initially, otherwise scan + return this->went_through_explicit_hidden_phase_() ? WiFiRetryPhase::EXPLICIT_HIDDEN + : WiFiRetryPhase::SCAN_CONNECTING; } - delay(10); - if (!this->is_captive_portal_active_() && !this->is_esp32_improv_active_() && - (this->num_retried_ > 3 || this->error_from_callback_)) { -#ifdef USE_WIFI_FAST_CONNECT - // No empty check needed - YAML validation requires at least one network for fast_connect - if (this->trying_loaded_ap_) { - this->trying_loaded_ap_ = false; - this->selected_sta_index_ = 0; // Retry from the first configured AP - this->reset_for_next_ap_attempt_(); - } else if (this->selected_sta_index_ >= static_cast(this->sta_.size()) - 1) { - // Safe cast: sta_.size() limited to MAX_WIFI_NETWORKS (127) in __init__.py validation - // Exhausted all configured APs, restart adapter and cycle back to first - // Restart clears any stuck WiFi driver state - // Each AP is tried with config data only (SSID + optional BSSID/channel if user configured them) - // Typically SSID only, which triggers ESP-IDF internal scanning - ESP_LOGW(TAG, "No more APs to try"); - this->selected_sta_index_ = 0; - this->reset_for_next_ap_attempt_(); - this->restart_adapter(); - } else { - // Try next AP - this->selected_sta_index_++; - this->reset_for_next_ap_attempt_(); - } -#else - if (this->num_retried_ > 5) { - // If retry failed for more than 5 times, let's restart STA - this->restart_adapter(); - } else { - // Try hidden networks after 3 failed retries - ESP_LOGD(TAG, "Retrying with hidden networks"); - this->retry_hidden_ = true; - this->num_retried_++; - } -#endif - } else { - this->num_retried_++; + // Should never reach here + return WiFiRetryPhase::SCAN_CONNECTING; +} + +/// Transition from current retry phase to a new phase with logging and phase-specific setup +/// This function handles the actual state change, including: +/// - Logging the phase transition +/// - Resetting the retry counter +/// - Performing phase-specific initialization (e.g., advancing AP index, starting scans) +/// +/// @param new_phase The phase we're transitioning TO +/// @return true if an async scan was started (caller should wait for completion) +/// false if no scan started (caller can proceed with connection attempt) +bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) { + WiFiRetryPhase old_phase = this->retry_phase_; + + // No-op if staying in same phase + if (old_phase == new_phase) { + return false; } + + ESP_LOGD(TAG, "Retry phase: %s → %s", LOG_STR_ARG(retry_phase_to_log_string(old_phase)), + LOG_STR_ARG(retry_phase_to_log_string(new_phase))); + + this->retry_phase_ = new_phase; + this->num_retried_ = 0; // Reset retry counter on phase change + + // Phase-specific setup + switch (new_phase) { +#ifdef USE_WIFI_FAST_CONNECT + case WiFiRetryPhase::FAST_CONNECT_CYCLING_APS: + // Move to next configured AP - clear old scan data so new AP is tried with config only + this->selected_sta_index_++; + this->scan_result_.clear(); + break; +#endif + + case WiFiRetryPhase::EXPLICIT_HIDDEN: + // Starting explicit hidden phase - reset to first network + this->selected_sta_index_ = 0; + break; + + case WiFiRetryPhase::SCAN_CONNECTING: + // Transitioning to scan-based connection +#ifdef USE_WIFI_FAST_CONNECT + if (old_phase == WiFiRetryPhase::FAST_CONNECT_CYCLING_APS) { + ESP_LOGI(TAG, "Fast connect exhausted, falling back to scan"); + } +#endif + // Trigger scan if we don't have scan results OR if transitioning from phases that need fresh scan + if (this->scan_result_.empty() || old_phase == WiFiRetryPhase::EXPLICIT_HIDDEN || + old_phase == WiFiRetryPhase::RETRY_HIDDEN || old_phase == WiFiRetryPhase::RESTARTING_ADAPTER) { + this->selected_sta_index_ = -1; // Will be set after scan completes + this->start_scanning(); + return true; // Started scan, wait for completion + } + // Already have scan results - selected_sta_index_ should already be synchronized + // (set in check_scanning_finished() when scan completed) + // No need to reset it here + break; + + case WiFiRetryPhase::RETRY_HIDDEN: + // Starting hidden mode - find first SSID that wasn't in scan results + if (old_phase == WiFiRetryPhase::SCAN_CONNECTING) { + // Keep scan results so we can skip SSIDs that were visible in the scan + // Don't clear scan_result_ - we need it to know which SSIDs are NOT hidden + + // If first network is marked hidden, we went through EXPLICIT_HIDDEN phase + // In that case, skip networks marked hidden:true (already tried) + // Otherwise, include them (they haven't been tried yet) + this->selected_sta_index_ = this->find_next_hidden_sta_(-1, !this->went_through_explicit_hidden_phase_()); + + if (this->selected_sta_index_ == -1) { + ESP_LOGD(TAG, "All SSIDs visible or already tried, skipping hidden mode"); + } + } + break; + + case WiFiRetryPhase::RESTARTING_ADAPTER: + this->restart_adapter(); + // Return true to indicate we should wait (go to COOLDOWN) instead of immediately connecting + return true; + + default: + break; + } + + return false; // Did not start scan, can proceed with connection +} + +/// Log failed connection attempt and decrease BSSID priority to avoid repeated failures +/// This function identifies which BSSID was attempted (from scan results or config), +/// decreases its priority by 1.0 to discourage future attempts, and logs the change. +/// +/// The priority degradation system ensures that failed BSSIDs are automatically sorted +/// lower in subsequent scans, naturally cycling through different APs without explicit +/// BSSID tracking within a scan cycle. +/// +/// Priority sources: +/// - SCAN_CONNECTING phase: Uses BSSID from scan_result_[0] (best match after sorting) +/// - Other phases: Uses BSSID from config if explicitly specified by user or fast_connect +/// +/// If no BSSID is available (SSID-only connection), priority adjustment is skipped. +void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { + // Determine which BSSID we tried to connect to + optional failed_bssid; + + if (this->retry_phase_ == WiFiRetryPhase::SCAN_CONNECTING && !this->scan_result_.empty()) { + // Scan-based phase: always use best result (index 0) + failed_bssid = this->scan_result_[0].get_bssid(); + } else if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_bssid()) { + // Config has specific BSSID (fast_connect or user-specified) + failed_bssid = *config->get_bssid(); + } + + if (!failed_bssid.has_value()) { + return; // No BSSID to penalize + } + + // Decrease priority to avoid repeatedly trying the same failed BSSID + float old_priority = this->get_sta_priority(failed_bssid.value()); + float new_priority = old_priority - 1.0f; + this->set_sta_priority(failed_bssid.value(), new_priority); + + // Get SSID for logging + std::string ssid; + if (this->retry_phase_ == WiFiRetryPhase::SCAN_CONNECTING && !this->scan_result_.empty()) { + ssid = this->scan_result_[0].get_ssid(); + } else if (const WiFiAP *config = this->get_selected_sta_()) { + ssid = config->get_ssid(); + } + + ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %.1f → %.1f", ssid.c_str(), + format_mac_address_pretty(failed_bssid.value().data()).c_str(), old_priority, new_priority); +} + +/// Handle target advancement or retry counter increment when staying in the same phase +/// This function is called when a connection attempt fails and determine_next_phase_() indicates +/// we should stay in the current phase. It decides whether to: +/// - Advance to the next target (AP in fast_connect, SSID in hidden mode) +/// - Or increment the retry counter to try the same target again +/// +/// Phase-specific behavior: +/// - FAST_CONNECT_CYCLING_APS: Always advance to next AP (no retries per AP) +/// - RETRY_HIDDEN: Advance to next SSID after exhausting retries on current SSID +/// - Other phases: Increment retry counter (will retry same target) +void WiFiComponent::advance_to_next_target_or_increment_retry_() { + WiFiRetryPhase current_phase = this->retry_phase_; + + // Check if we need to advance to next AP/SSID within the same phase +#ifdef USE_WIFI_FAST_CONNECT + if (current_phase == WiFiRetryPhase::FAST_CONNECT_CYCLING_APS) { + // Fast connect: always advance to next AP (no retries per AP) + this->selected_sta_index_++; + this->num_retried_ = 0; + ESP_LOGD(TAG, "Next AP in %s", LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_))); + return; + } +#endif + + if (current_phase == WiFiRetryPhase::EXPLICIT_HIDDEN && this->num_retried_ + 1 >= WIFI_RETRY_COUNT_PER_SSID) { + // Explicit hidden: exhausted retries on current SSID, find next explicitly hidden network + // Stop when we reach a visible network (proceed to scanning) + size_t next_index = this->selected_sta_index_ + 1; + if (next_index < this->sta_.size() && this->sta_[next_index].get_hidden()) { + this->selected_sta_index_ = static_cast(next_index); + this->num_retried_ = 0; + ESP_LOGD(TAG, "Next explicit hidden network at index %d", static_cast(next_index)); + return; + } + // No more consecutive explicit hidden networks found - fall through to trigger phase change + } + + if (current_phase == WiFiRetryPhase::RETRY_HIDDEN && this->num_retried_ + 1 >= WIFI_RETRY_COUNT_PER_SSID) { + // Hidden mode: exhausted retries on current SSID, find next potentially hidden SSID + // If first network is marked hidden, we went through EXPLICIT_HIDDEN phase + // In that case, skip networks marked hidden:true (already tried) + // Otherwise, include them (they haven't been tried yet) + int8_t next_index = + this->find_next_hidden_sta_(this->selected_sta_index_, !this->went_through_explicit_hidden_phase_()); + if (next_index != -1) { + // Found another potentially hidden SSID + this->selected_sta_index_ = next_index; + this->num_retried_ = 0; + return; + } + // No more potentially hidden SSIDs - set selected_sta_index_ to -1 to trigger phase change + // This ensures determine_next_phase_() will skip the RETRY_HIDDEN logic and transition out + this->selected_sta_index_ = -1; + // Return early - phase change will happen on next wifi_loop() iteration + return; + } + + // Don't increment retry counter if we're in a scan phase with no valid targets + if (this->needs_scan_results_()) { + return; + } + + // Increment retry counter to try the same target again + this->num_retried_++; + ESP_LOGD(TAG, "Retry attempt %u/%u in phase %s", this->num_retried_ + 1, + get_max_retries_for_phase(this->retry_phase_), LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_))); +} + +void WiFiComponent::retry_connect() { + this->log_and_adjust_priority_for_failed_connect_(); + + delay(10); + + // Determine next retry phase based on current state + WiFiRetryPhase current_phase = this->retry_phase_; + WiFiRetryPhase next_phase = this->determine_next_phase_(); + + // Handle phase transitions (transition_to_phase_ handles same-phase no-op internally) + if (this->transition_to_phase_(next_phase)) { + return; // Wait for scan to complete + } + + if (next_phase == current_phase) { + this->advance_to_next_target_or_increment_retry_(); + } + this->error_from_callback_ = false; + if (this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTING) { yield(); - this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING_2; - WiFiAP params = this->build_wifi_ap_from_selected_(); - this->start_connecting(params, true); - return; + // Check if we have a valid target before building params + // After exhausting all networks in a phase, selected_sta_index_ may be -1 + // In that case, skip connection and let next wifi_loop() handle phase transition + if (this->selected_sta_index_ >= 0) { + this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING_2; + WiFiAP params = this->build_params_for_current_phase_(); + this->start_connecting(params, true); + return; + } + // No valid target - fall through to set state to allow phase transition } this->state_ = WIFI_COMPONENT_STATE_COOLDOWN; diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index cb75edf5a0..1cdf3234c7 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -94,6 +94,24 @@ enum class WiFiSTAConnectStatus : int { ERROR_CONNECT_FAILED, }; +/// Tracks the current retry strategy/phase for WiFi connection attempts +enum class WiFiRetryPhase : uint8_t { + /// Initial connection attempt (varies based on fast_connect setting) + INITIAL_CONNECT, +#ifdef USE_WIFI_FAST_CONNECT + /// Fast connect mode: cycling through configured APs (config-only, no scan) + FAST_CONNECT_CYCLING_APS, +#endif + /// Explicitly hidden networks (user marked as hidden, try before scanning) + EXPLICIT_HIDDEN, + /// Scan-based: connecting to best AP from scan results + SCAN_CONNECTING, + /// Retry networks not found in scan (might be hidden) + RETRY_HIDDEN, + /// Restarting WiFi adapter to clear stuck state + RESTARTING_ADAPTER, +}; + /// Struct for setting static IPs in WiFiComponent. struct ManualIP { network::IPAddress static_ip; @@ -341,8 +359,37 @@ class WiFiComponent : public Component { #endif // USE_WIFI_AP void print_connect_params_(); - WiFiAP build_wifi_ap_from_selected_() const; + WiFiAP build_params_for_current_phase_(); + /// Determine next retry phase based on current state and failure conditions + WiFiRetryPhase determine_next_phase_(); + /// Transition to a new retry phase with logging + /// Returns true if a scan was started (caller should wait), false otherwise + bool transition_to_phase_(WiFiRetryPhase new_phase); + /// Check if we need valid scan results for the current phase but don't have any + /// Returns true if the phase requires scan results but they're missing or don't match + bool needs_scan_results_() const; + /// Check if we went through EXPLICIT_HIDDEN phase (first network is marked hidden) + /// Used in RETRY_HIDDEN to determine whether to skip explicitly hidden networks + bool went_through_explicit_hidden_phase_() const; + /// Find the index of the first non-hidden network + /// Returns where EXPLICIT_HIDDEN phase would have stopped, or -1 if all networks are hidden + int8_t find_first_non_hidden_index_() const; + /// Check if an SSID was seen in the most recent scan results + /// Used to skip hidden mode for SSIDs we know are visible + bool ssid_was_seen_in_scan_(const std::string &ssid) const; + /// Find next SSID that wasn't in scan results (might be hidden) + /// Returns index of next potentially hidden SSID, or -1 if none found + /// @param start_index Start searching from index after this (-1 to start from beginning) + /// @param include_explicit_hidden If true, include SSIDs marked hidden:true. If false, only find truly hidden SSIDs. + int8_t find_next_hidden_sta_(int8_t start_index, bool include_explicit_hidden = true); + /// Log failed connection and decrease BSSID priority to avoid repeated attempts + void log_and_adjust_priority_for_failed_connect_(); + /// Advance to next target (AP/SSID) within current phase, or increment retry counter + /// Called when staying in the same phase after a failed connection attempt + void advance_to_next_target_or_increment_retry_(); + /// Start initial connection - either scan or connect directly to hidden networks + void start_initial_connection_(); const WiFiAP *get_selected_sta_() const { if (this->selected_sta_index_ >= 0 && static_cast(this->selected_sta_index_) < this->sta_.size()) { return &this->sta_[this->selected_sta_index_]; @@ -356,14 +403,15 @@ class WiFiComponent : public Component { } } -#ifdef USE_WIFI_FAST_CONNECT - // Reset state for next fast connect AP attempt - // Clears old scan data so the new AP is tried with config only (SSID without specific BSSID/channel) - void reset_for_next_ap_attempt_() { - this->num_retried_ = 0; - this->scan_result_.clear(); + bool all_networks_hidden_() const { + if (this->sta_.empty()) + return false; + for (const auto &ap : this->sta_) { + if (!ap.get_hidden()) + return false; + } + return true; } -#endif void wifi_loop_(); bool wifi_mode_(optional sta, optional ap); @@ -443,20 +491,18 @@ class WiFiComponent : public Component { // Group all 8-bit values together WiFiComponentState state_{WIFI_COMPONENT_STATE_OFF}; WiFiPowerSaveMode power_save_{WIFI_POWER_SAVE_NONE}; + WiFiRetryPhase retry_phase_{WiFiRetryPhase::INITIAL_CONNECT}; uint8_t num_retried_{0}; // Index into sta_ array for the currently selected AP configuration (-1 = none selected) // Used to access password, manual_ip, priority, EAP settings, and hidden flag // int8_t limits to 127 APs (enforced in __init__.py via MAX_WIFI_NETWORKS) int8_t selected_sta_index_{-1}; + #if USE_NETWORK_IPV6 uint8_t num_ipv6_addresses_{0}; #endif /* USE_NETWORK_IPV6 */ // Group all boolean values together -#ifdef USE_WIFI_FAST_CONNECT - bool trying_loaded_ap_{false}; -#endif - bool retry_hidden_{false}; bool has_ap_{false}; bool handled_connected_state_{false}; bool error_from_callback_{false}; From 82692d7053ec4713a4eadb802ec33619d91ce57f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 10 Nov 2025 19:00:54 -0600 Subject: [PATCH 0168/1145] [tests] Migrate components to shared packages and fix ID ambiguity (#11819) --- script/analyze_component_buses.py | 1 + tests/components/chsc6x/test.rp2040-ard.yaml | 1 + tests/components/hlk_fm22x/common.yaml | 41 ++++++++++++++++ .../components/hlk_fm22x/test.esp32-idf.yaml | 49 ++----------------- .../hlk_fm22x/test.esp8266-ard.yaml | 49 ++----------------- .../components/hlk_fm22x/test.rp2040-ard.yaml | 49 ++----------------- tests/components/speaker/common.yaml | 30 +++++++++--- tests/components/toshiba/common_ras2819t.yaml | 8 --- .../toshiba/test_ras2819t.esp32-ard.yaml | 6 +-- .../toshiba/test_ras2819t.esp32-c3-ard.yaml | 6 +-- .../toshiba/test_ras2819t.esp32-idf.yaml | 6 +-- .../toshiba/test_ras2819t.esp8266-ard.yaml | 6 +-- .../common/remote_receiver/esp32-ard.yaml | 12 +++++ .../common/remote_receiver/esp32-c3-ard.yaml | 12 +++++ 14 files changed, 111 insertions(+), 165 deletions(-) create mode 100644 tests/components/hlk_fm22x/common.yaml create mode 100644 tests/test_build_components/common/remote_receiver/esp32-ard.yaml create mode 100644 tests/test_build_components/common/remote_receiver/esp32-c3-ard.yaml diff --git a/script/analyze_component_buses.py b/script/analyze_component_buses.py index 38d1f8c2b7..27a36f889f 100755 --- a/script/analyze_component_buses.py +++ b/script/analyze_component_buses.py @@ -86,6 +86,7 @@ ISOLATED_COMPONENTS = { "modbus_controller": "Defines multiple modbus buses for testing client/server functionality - conflicts with package modbus bus", "neopixelbus": "RMT type conflict with ESP32 Arduino/ESP-IDF headers (enum vs struct rmt_channel_t)", "packages": "cannot merge packages", + "tinyusb": "Conflicts with usb_host component - cannot be used together", } diff --git a/tests/components/chsc6x/test.rp2040-ard.yaml b/tests/components/chsc6x/test.rp2040-ard.yaml index 89cc1b7477..2e3613a4a3 100644 --- a/tests/components/chsc6x/test.rp2040-ard.yaml +++ b/tests/components/chsc6x/test.rp2040-ard.yaml @@ -16,5 +16,6 @@ display: touchscreen: - platform: chsc6x + i2c_id: i2c_bus display: ili9xxx_display interrupt_pin: 22 diff --git a/tests/components/hlk_fm22x/common.yaml b/tests/components/hlk_fm22x/common.yaml new file mode 100644 index 0000000000..6fcd9af594 --- /dev/null +++ b/tests/components/hlk_fm22x/common.yaml @@ -0,0 +1,41 @@ +esphome: + on_boot: + then: + - hlk_fm22x.enroll: + name: "Test" + direction: 1 + - hlk_fm22x.delete_all: + +hlk_fm22x: + on_face_scan_matched: + - logger.log: test_hlk_22x_face_scan_matched + on_face_scan_unmatched: + - logger.log: test_hlk_22x_face_scan_unmatched + on_face_scan_invalid: + - logger.log: test_hlk_22x_face_scan_invalid + on_face_info: + - logger.log: test_hlk_22x_face_info + on_enrollment_done: + - logger.log: test_hlk_22x_enrollment_done + on_enrollment_failed: + - logger.log: test_hlk_22x_enrollment_failed + +sensor: + - platform: hlk_fm22x + face_count: + name: "Face Count" + last_face_id: + name: "Last Face ID" + status: + name: "Face Status" + +binary_sensor: + - platform: hlk_fm22x + name: "Face Enrolling" + +text_sensor: + - platform: hlk_fm22x + version: + name: "HLK Version" + last_face_name: + name: "Last Face Name" diff --git a/tests/components/hlk_fm22x/test.esp32-idf.yaml b/tests/components/hlk_fm22x/test.esp32-idf.yaml index 5e7cbde664..2d29656c94 100644 --- a/tests/components/hlk_fm22x/test.esp32-idf.yaml +++ b/tests/components/hlk_fm22x/test.esp32-idf.yaml @@ -1,47 +1,4 @@ -esphome: - on_boot: - then: - - hlk_fm22x.enroll: - name: "Test" - direction: 1 - - hlk_fm22x.delete_all: +packages: + uart: !include ../../test_build_components/common/uart/esp32-idf.yaml -uart: - - id: uart_hlk_fm22x - tx_pin: 17 - rx_pin: 16 - baud_rate: 115200 - -hlk_fm22x: - on_face_scan_matched: - - logger.log: test_hlk_22x_face_scan_matched - on_face_scan_unmatched: - - logger.log: test_hlk_22x_face_scan_unmatched - on_face_scan_invalid: - - logger.log: test_hlk_22x_face_scan_invalid - on_face_info: - - logger.log: test_hlk_22x_face_info - on_enrollment_done: - - logger.log: test_hlk_22x_enrollment_done - on_enrollment_failed: - - logger.log: test_hlk_22x_enrollment_failed - -sensor: - - platform: hlk_fm22x - face_count: - name: "Face Count" - last_face_id: - name: "Last Face ID" - status: - name: "Face Status" - -binary_sensor: - - platform: hlk_fm22x - name: "Face Enrolling" - -text_sensor: - - platform: hlk_fm22x - version: - name: "HLK Version" - last_face_name: - name: "Last Face Name" +<<: !include common.yaml diff --git a/tests/components/hlk_fm22x/test.esp8266-ard.yaml b/tests/components/hlk_fm22x/test.esp8266-ard.yaml index 680047834c..5a05efa259 100644 --- a/tests/components/hlk_fm22x/test.esp8266-ard.yaml +++ b/tests/components/hlk_fm22x/test.esp8266-ard.yaml @@ -1,47 +1,4 @@ -esphome: - on_boot: - then: - - hlk_fm22x.enroll: - name: "Test" - direction: 1 - - hlk_fm22x.delete_all: +packages: + uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml -uart: - - id: uart_hlk_fm22x - tx_pin: 4 - rx_pin: 5 - baud_rate: 115200 - -hlk_fm22x: - on_face_scan_matched: - - logger.log: test_hlk_22x_face_scan_matched - on_face_scan_unmatched: - - logger.log: test_hlk_22x_face_scan_unmatched - on_face_scan_invalid: - - logger.log: test_hlk_22x_face_scan_invalid - on_face_info: - - logger.log: test_hlk_22x_face_info - on_enrollment_done: - - logger.log: test_hlk_22x_enrollment_done - on_enrollment_failed: - - logger.log: test_hlk_22x_enrollment_failed - -sensor: - - platform: hlk_fm22x - face_count: - name: "Face Count" - last_face_id: - name: "Last Face ID" - status: - name: "Face Status" - -binary_sensor: - - platform: hlk_fm22x - name: "Face Enrolling" - -text_sensor: - - platform: hlk_fm22x - version: - name: "HLK Version" - last_face_name: - name: "Last Face Name" +<<: !include common.yaml diff --git a/tests/components/hlk_fm22x/test.rp2040-ard.yaml b/tests/components/hlk_fm22x/test.rp2040-ard.yaml index 680047834c..f1df2daf83 100644 --- a/tests/components/hlk_fm22x/test.rp2040-ard.yaml +++ b/tests/components/hlk_fm22x/test.rp2040-ard.yaml @@ -1,47 +1,4 @@ -esphome: - on_boot: - then: - - hlk_fm22x.enroll: - name: "Test" - direction: 1 - - hlk_fm22x.delete_all: +packages: + uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml -uart: - - id: uart_hlk_fm22x - tx_pin: 4 - rx_pin: 5 - baud_rate: 115200 - -hlk_fm22x: - on_face_scan_matched: - - logger.log: test_hlk_22x_face_scan_matched - on_face_scan_unmatched: - - logger.log: test_hlk_22x_face_scan_unmatched - on_face_scan_invalid: - - logger.log: test_hlk_22x_face_scan_invalid - on_face_info: - - logger.log: test_hlk_22x_face_info - on_enrollment_done: - - logger.log: test_hlk_22x_enrollment_done - on_enrollment_failed: - - logger.log: test_hlk_22x_enrollment_failed - -sensor: - - platform: hlk_fm22x - face_count: - name: "Face Count" - last_face_id: - name: "Last Face ID" - status: - name: "Face Status" - -binary_sensor: - - platform: hlk_fm22x - name: "Face Enrolling" - -text_sensor: - - platform: hlk_fm22x - version: - name: "HLK Version" - last_face_name: - name: "Last Face Name" +<<: !include common.yaml diff --git a/tests/components/speaker/common.yaml b/tests/components/speaker/common.yaml index fa54fa7e39..9aaf639162 100644 --- a/tests/components/speaker/common.yaml +++ b/tests/components/speaker/common.yaml @@ -11,26 +11,42 @@ esphome: on_boot: then: - speaker.mute_on: + id: speaker_id - speaker.mute_off: + id: speaker_id - if: - condition: speaker.is_stopped + condition: + speaker.is_stopped: + id: speaker_id then: - - speaker.play: [0, 1, 2, 3] - - speaker.volume_set: 0.9 + - speaker.play: + id: speaker_id + data: [0, 1, 2, 3] + - speaker.volume_set: + id: speaker_id + volume: 0.9 - if: - condition: speaker.is_playing + condition: + speaker.is_playing: + id: speaker_id then: - speaker.finish: + id: speaker_id - speaker.stop: + id: speaker_id button: - platform: template name: "Speaker Button" on_press: then: - - speaker.play: [0x10, 0x20, 0x30, 0x40] - - speaker.play: !lambda |- - return {0x01, 0x02, (uint8_t)id(my_number).state}; + - speaker.play: + id: speaker_id + data: [0x10, 0x20, 0x30, 0x40] + - speaker.play: + id: speaker_id + data: !lambda |- + return {0x01, 0x02, (uint8_t)id(my_number).state}; i2s_audio: i2s_lrclk_pin: ${i2s_bclk_pin} diff --git a/tests/components/toshiba/common_ras2819t.yaml b/tests/components/toshiba/common_ras2819t.yaml index 32081fca98..157456ba81 100644 --- a/tests/components/toshiba/common_ras2819t.yaml +++ b/tests/components/toshiba/common_ras2819t.yaml @@ -1,11 +1,3 @@ -remote_transmitter: - pin: ${tx_pin} - carrier_duty_percent: 50% - -remote_receiver: - id: rcvr - pin: ${rx_pin} - climate: - platform: toshiba name: "RAS-2819T Climate" diff --git a/tests/components/toshiba/test_ras2819t.esp32-ard.yaml b/tests/components/toshiba/test_ras2819t.esp32-ard.yaml index 00805baa01..d82ba54897 100644 --- a/tests/components/toshiba/test_ras2819t.esp32-ard.yaml +++ b/tests/components/toshiba/test_ras2819t.esp32-ard.yaml @@ -1,5 +1,5 @@ -substitutions: - tx_pin: GPIO5 - rx_pin: GPIO4 +packages: + remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-ard.yaml + remote_receiver: !include ../../test_build_components/common/remote_receiver/esp32-ard.yaml <<: !include common_ras2819t.yaml diff --git a/tests/components/toshiba/test_ras2819t.esp32-c3-ard.yaml b/tests/components/toshiba/test_ras2819t.esp32-c3-ard.yaml index 00805baa01..6858dd587f 100644 --- a/tests/components/toshiba/test_ras2819t.esp32-c3-ard.yaml +++ b/tests/components/toshiba/test_ras2819t.esp32-c3-ard.yaml @@ -1,5 +1,5 @@ -substitutions: - tx_pin: GPIO5 - rx_pin: GPIO4 +packages: + remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-ard.yaml + remote_receiver: !include ../../test_build_components/common/remote_receiver/esp32-c3-ard.yaml <<: !include common_ras2819t.yaml diff --git a/tests/components/toshiba/test_ras2819t.esp32-idf.yaml b/tests/components/toshiba/test_ras2819t.esp32-idf.yaml index 00805baa01..3facc5bbb3 100644 --- a/tests/components/toshiba/test_ras2819t.esp32-idf.yaml +++ b/tests/components/toshiba/test_ras2819t.esp32-idf.yaml @@ -1,5 +1,5 @@ -substitutions: - tx_pin: GPIO5 - rx_pin: GPIO4 +packages: + remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-idf.yaml + remote_receiver: !include ../../test_build_components/common/remote_receiver/esp32-idf.yaml <<: !include common_ras2819t.yaml diff --git a/tests/components/toshiba/test_ras2819t.esp8266-ard.yaml b/tests/components/toshiba/test_ras2819t.esp8266-ard.yaml index 00805baa01..3976dcc739 100644 --- a/tests/components/toshiba/test_ras2819t.esp8266-ard.yaml +++ b/tests/components/toshiba/test_ras2819t.esp8266-ard.yaml @@ -1,5 +1,5 @@ -substitutions: - tx_pin: GPIO5 - rx_pin: GPIO4 +packages: + remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp8266-ard.yaml + remote_receiver: !include ../../test_build_components/common/remote_receiver/esp8266-ard.yaml <<: !include common_ras2819t.yaml diff --git a/tests/test_build_components/common/remote_receiver/esp32-ard.yaml b/tests/test_build_components/common/remote_receiver/esp32-ard.yaml new file mode 100644 index 0000000000..af5c2f2409 --- /dev/null +++ b/tests/test_build_components/common/remote_receiver/esp32-ard.yaml @@ -0,0 +1,12 @@ +# Common remote_receiver configuration for ESP32 Arduino tests +# Provides a shared remote receiver that all components can use +# Components will auto-use this receiver if they don't specify receiver_id + +substitutions: + remote_receiver_pin: GPIO32 + +remote_receiver: + - id: rcvr + pin: ${remote_receiver_pin} + dump: all + tolerance: 25% diff --git a/tests/test_build_components/common/remote_receiver/esp32-c3-ard.yaml b/tests/test_build_components/common/remote_receiver/esp32-c3-ard.yaml new file mode 100644 index 0000000000..26b288b427 --- /dev/null +++ b/tests/test_build_components/common/remote_receiver/esp32-c3-ard.yaml @@ -0,0 +1,12 @@ +# Common remote_receiver configuration for ESP32-C3 Arduino tests +# Provides a shared remote receiver that all components can use +# Components will auto-use this receiver if they don't specify receiver_id + +substitutions: + remote_receiver_pin: GPIO10 + +remote_receiver: + - id: rcvr + pin: ${remote_receiver_pin} + dump: all + tolerance: 25% From 463a00b1aca9fd7a44bb91a50418ce5a0bef48a3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:10:29 +1300 Subject: [PATCH 0169/1145] [CI] Don't request codeowners review in forks (#11827) --- .github/workflows/codeowner-review-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeowner-review-request.yml b/.github/workflows/codeowner-review-request.yml index 563d55f42b..6f4351b298 100644 --- a/.github/workflows/codeowner-review-request.yml +++ b/.github/workflows/codeowner-review-request.yml @@ -21,7 +21,7 @@ permissions: jobs: request-codeowner-reviews: name: Run - if: ${{ !github.event.pull_request.draft }} + if: ${{ github.repository == 'esphome/esphome' && !github.event.pull_request.draft }} runs-on: ubuntu-latest steps: - name: Request reviews from component codeowners From 1539b4307469b59fe654b940920a2863dad44d8a Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 11 Nov 2025 11:17:16 +1000 Subject: [PATCH 0170/1145] [wifi][ethernet] Don't block setup until connected (#9823) Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- esphome/components/ethernet/ethernet_component.cpp | 2 -- esphome/components/ethernet/ethernet_component.h | 1 - esphome/components/wifi/wifi_component.cpp | 6 ------ esphome/components/wifi/wifi_component.h | 2 -- 4 files changed, 11 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 893d0285be..5888ddce60 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -418,8 +418,6 @@ void EthernetComponent::dump_config() { float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } -bool EthernetComponent::can_proceed() { return this->is_connected(); } - network::IPAddresses EthernetComponent::get_ip_addresses() { network::IPAddresses addresses; esp_netif_ip_info_t ip; diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 31f9fa360a..f1f0ac9cb8 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -58,7 +58,6 @@ class EthernetComponent : public Component { void loop() override; void dump_config() override; float get_setup_priority() const override; - bool can_proceed() override; void on_powerdown() override { powerdown(); } bool is_connected(); diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 885288f46a..7279e0c783 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1443,12 +1443,6 @@ void WiFiComponent::retry_connect() { this->action_started_ = millis(); } -bool WiFiComponent::can_proceed() { - if (!this->has_sta() || this->state_ == WIFI_COMPONENT_STATE_DISABLED || this->ap_setup_) { - return true; - } - return this->is_connected(); -} void WiFiComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } bool WiFiComponent::is_connected() { return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED && diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 1cdf3234c7..ed049544cf 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -269,8 +269,6 @@ class WiFiComponent : public Component { void retry_connect(); - bool can_proceed() override; - void set_reboot_timeout(uint32_t reboot_timeout); bool is_connected(); From 7a700ca0779367b6558489af364915f7bfd4eb9f Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 11 Nov 2025 12:15:44 +1000 Subject: [PATCH 0171/1145] [core] Update clamp functions to allow mixed but comparable types (#11828) --- esphome/core/helpers.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 48af7f674a..52a0746057 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1174,12 +1174,18 @@ template using ExternalRAMAllocator = RAMAllocator; * Functions to constrain the range of arithmetic values. */ -template T clamp_at_least(T value, T min) { +template +concept comparable_with = requires(T a, U b) { + { a > b } -> std::convertible_to; + { a < b } -> std::convertible_to; +}; + +template U> T clamp_at_least(T value, U min) { if (value < min) return min; return value; } -template T clamp_at_most(T value, T max) { +template U> T clamp_at_most(T value, U max) { if (value > max) return max; return value; From a6b7c1f18c933a8f9d0dc2b94ea492cfff70ef39 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Tue, 11 Nov 2025 16:17:25 +0100 Subject: [PATCH 0172/1145] [nrf52,gpio] add gpio levels for high voltage mode (#9858) Co-authored-by: J. Nick Koston --- esphome/components/nrf52/__init__.py | 26 +++++ esphome/components/nrf52/uicr.cpp | 110 ++++++++++++++++++ esphome/core/defines.h | 2 + .../components/nrf52/test.nrf52-adafruit.yaml | 3 + tests/components/nrf52/test.nrf52-mcumgr.yaml | 4 + .../components/nrf52/test.nrf52-xiao-ble.yaml | 2 + 6 files changed, 147 insertions(+) create mode 100644 esphome/components/nrf52/uicr.cpp diff --git a/esphome/components/nrf52/__init__.py b/esphome/components/nrf52/__init__.py index ace324c1f5..9566263c7c 100644 --- a/esphome/components/nrf52/__init__.py +++ b/esphome/components/nrf52/__init__.py @@ -25,6 +25,7 @@ from esphome.const import ( CONF_FRAMEWORK, CONF_ID, CONF_RESET_PIN, + CONF_VOLTAGE, KEY_CORE, KEY_FRAMEWORK_VERSION, KEY_TARGET_FRAMEWORK, @@ -102,6 +103,11 @@ nrf52_ns = cg.esphome_ns.namespace("nrf52") DeviceFirmwareUpdate = nrf52_ns.class_("DeviceFirmwareUpdate", cg.Component) CONF_DFU = "dfu" +CONF_REG0 = "reg0" +CONF_UICR_ERASE = "uicr_erase" + +VOLTAGE_LEVELS = [1.8, 2.1, 2.4, 2.7, 3.0, 3.3] +DEFAULT_VOLTAGE_LEVEL = "default" CONFIG_SCHEMA = cv.All( _detect_bootloader, @@ -116,6 +122,18 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema, } ), + cv.Optional(CONF_REG0): cv.Schema( + { + cv.Required(CONF_VOLTAGE): cv.Any( + cv.All( + cv.voltage, + cv.one_of(*VOLTAGE_LEVELS, float=True), + ), + cv.one_of(*[DEFAULT_VOLTAGE_LEVEL], lower=True), + ), + cv.Optional(CONF_UICR_ERASE, default=False): cv.boolean, + } + ), } ), ) @@ -183,6 +201,14 @@ async def to_code(config: ConfigType) -> None: if dfu_config := config.get(CONF_DFU): CORE.add_job(_dfu_to_code, dfu_config) + if reg0_config := config.get(CONF_REG0): + value = 7 # DEFAULT_VOLTAGE_LEVEL + if reg0_config[CONF_VOLTAGE] in VOLTAGE_LEVELS: + value = VOLTAGE_LEVELS.index(reg0_config[CONF_VOLTAGE]) + cg.add_define("USE_NRF52_REG0_VOUT", value) + if reg0_config[CONF_UICR_ERASE]: + cg.add_define("USE_NRF52_UICR_ERASE") + @coroutine_with_priority(CoroPriority.DIAGNOSTICS) async def _dfu_to_code(dfu_config): diff --git a/esphome/components/nrf52/uicr.cpp b/esphome/components/nrf52/uicr.cpp new file mode 100644 index 0000000000..22714b7e50 --- /dev/null +++ b/esphome/components/nrf52/uicr.cpp @@ -0,0 +1,110 @@ +#include "esphome/core/defines.h" + +#ifdef USE_NRF52_REG0_VOUT +#include +#include +#include + +extern "C" { +void nvmc_config(uint32_t mode); +void nvmc_wait(); +nrfx_err_t nrfx_nvmc_uicr_erase(); +} + +namespace esphome::nrf52 { + +enum class StatusFlags : uint8_t { + OK = 0x00, + NEED_RESET = 0x01, + NEED_ERASE = 0x02, +}; + +constexpr StatusFlags &operator|=(StatusFlags &a, StatusFlags b) { + a = static_cast(static_cast(a) | static_cast(b)); + return a; +} + +constexpr bool operator&(StatusFlags a, StatusFlags b) { + return (static_cast(a) & static_cast(b)) != 0; +} + +static bool regout0_ok() { + return (NRF_UICR->REGOUT0 & UICR_REGOUT0_VOUT_Msk) == (USE_NRF52_REG0_VOUT << UICR_REGOUT0_VOUT_Pos); +} + +static StatusFlags set_regout0() { + /* If the board is powered from USB (high voltage mode), + * GPIO output voltage is set to 1.8 volts by default. + */ + if (!regout0_ok()) { + nvmc_config(NVMC_CONFIG_WEN_Wen); + NRF_UICR->REGOUT0 = + (NRF_UICR->REGOUT0 & ~((uint32_t) UICR_REGOUT0_VOUT_Msk)) | (USE_NRF52_REG0_VOUT << UICR_REGOUT0_VOUT_Pos); + nvmc_wait(); + nvmc_config(NVMC_CONFIG_WEN_Ren); + return regout0_ok() ? StatusFlags::NEED_RESET : StatusFlags::NEED_ERASE; + } + return StatusFlags::OK; +} + +#ifndef USE_BOOTLOADER_MCUBOOT +// https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/6a9a6a3e6d0f86918e9286188426a279976645bd/lib/sdk11/components/libraries/bootloader_dfu/dfu_types.h#L61 +constexpr uint32_t BOOTLOADER_REGION_START = 0x000F4000; +constexpr uint32_t BOOTLOADER_MBR_PARAMS_PAGE_ADDRESS = 0x000FE000; + +static bool bootloader_ok() { + return NRF_UICR->NRFFW[0] == BOOTLOADER_REGION_START && NRF_UICR->NRFFW[1] == BOOTLOADER_MBR_PARAMS_PAGE_ADDRESS; +} + +static StatusFlags fix_bootloader() { + if (!bootloader_ok()) { + nvmc_config(NVMC_CONFIG_WEN_Wen); + NRF_UICR->NRFFW[0] = BOOTLOADER_REGION_START; + NRF_UICR->NRFFW[1] = BOOTLOADER_MBR_PARAMS_PAGE_ADDRESS; + nvmc_wait(); + nvmc_config(NVMC_CONFIG_WEN_Ren); + return bootloader_ok() ? StatusFlags::NEED_RESET : StatusFlags::NEED_ERASE; + } + return StatusFlags::OK; +} +#endif + +static StatusFlags set_uicr() { + StatusFlags status = StatusFlags::OK; + status |= set_regout0(); +#ifndef USE_BOOTLOADER_MCUBOOT + status |= fix_bootloader(); +#endif + return status; +} + +static int board_esphome_init() { + StatusFlags status = set_uicr(); + +#ifdef USE_NRF52_UICR_ERASE + if (status & StatusFlags::NEED_ERASE) { + nrfx_err_t ret = nrfx_nvmc_uicr_erase(); + if (ret != NRFX_SUCCESS) { +#ifdef CONFIG_PRINTK + printk("nrfx_nvmc_uicr_erase failed %d\n", ret); +#endif + } else { + status |= set_uicr(); + } + } +#endif + + if (status & StatusFlags::NEED_RESET) { + /* a reset is required for changes to take effect */ + NVIC_SystemReset(); + } + + return 0; +} +} // namespace esphome::nrf52 + +static int board_esphome_init() { return esphome::nrf52::board_esphome_init(); } + +SYS_INIT(board_esphome_init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); + +#endif diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ac725fbca9..c522a8ec62 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -287,6 +287,8 @@ #ifdef USE_NRF52 #define USE_NRF52_DFU +#define USE_NRF52_REG0_VOUT 5 +#define USE_NRF52_UICR_ERASE #define USE_SOFTDEVICE_ID 7 #define USE_SOFTDEVICE_VERSION 1 #endif diff --git a/tests/components/nrf52/test.nrf52-adafruit.yaml b/tests/components/nrf52/test.nrf52-adafruit.yaml index cf704ecceb..72fd015953 100644 --- a/tests/components/nrf52/test.nrf52-adafruit.yaml +++ b/tests/components/nrf52/test.nrf52-adafruit.yaml @@ -15,3 +15,6 @@ nrf52: inverted: true mode: output: true + reg0: + voltage: 2.1V + uicr_erase: true diff --git a/tests/components/nrf52/test.nrf52-mcumgr.yaml b/tests/components/nrf52/test.nrf52-mcumgr.yaml index e69de29bb2..89ec637db6 100644 --- a/tests/components/nrf52/test.nrf52-mcumgr.yaml +++ b/tests/components/nrf52/test.nrf52-mcumgr.yaml @@ -0,0 +1,4 @@ +nrf52: + reg0: + voltage: 3.3V + uicr_erase: true diff --git a/tests/components/nrf52/test.nrf52-xiao-ble.yaml b/tests/components/nrf52/test.nrf52-xiao-ble.yaml index 3fe80209b6..c3c44902f0 100644 --- a/tests/components/nrf52/test.nrf52-xiao-ble.yaml +++ b/tests/components/nrf52/test.nrf52-xiao-ble.yaml @@ -5,3 +5,5 @@ nrf52: inverted: true mode: output: true + reg0: + voltage: default From a6b905e1488fb5bd9eefef426d45ddfd2b38700c Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Tue, 11 Nov 2025 18:50:07 +0100 Subject: [PATCH 0173/1145] [nrf52,pcf8563] fix build error (#11846) Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- esphome/components/ds1307/ds1307.cpp | 2 +- .../components/homeassistant/time/homeassistant_time.cpp | 6 ++---- esphome/components/pcf85063/pcf85063.cpp | 2 +- esphome/components/pcf8563/pcf8563.cpp | 2 +- esphome/components/rx8130/rx8130.cpp | 1 + esphome/components/sntp/sntp_component.cpp | 1 + esphome/components/time/real_time_clock.cpp | 7 +++++++ esphome/components/time/real_time_clock.h | 2 ++ tests/components/ds1307/test.nrf52-adafruit.yaml | 4 ++++ tests/components/pcf85063/test.nrf52-adafruit.yaml | 4 ++++ tests/components/pcf8563/test.nrf52-adafruit.yaml | 4 ++++ tests/components/rx8130/test.nrf52-adafruit.yaml | 4 ++++ 12 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 tests/components/ds1307/test.nrf52-adafruit.yaml create mode 100644 tests/components/pcf85063/test.nrf52-adafruit.yaml create mode 100644 tests/components/pcf8563/test.nrf52-adafruit.yaml create mode 100644 tests/components/rx8130/test.nrf52-adafruit.yaml diff --git a/esphome/components/ds1307/ds1307.cpp b/esphome/components/ds1307/ds1307.cpp index 077db497b1..adbd7b5487 100644 --- a/esphome/components/ds1307/ds1307.cpp +++ b/esphome/components/ds1307/ds1307.cpp @@ -23,7 +23,7 @@ void DS1307Component::dump_config() { if (this->is_failed()) { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); } - ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); + RealTimeClock::dump_config(); } float DS1307Component::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/homeassistant/time/homeassistant_time.cpp b/esphome/components/homeassistant/time/homeassistant_time.cpp index 0a91a2f63d..e72c5a21f5 100644 --- a/esphome/components/homeassistant/time/homeassistant_time.cpp +++ b/esphome/components/homeassistant/time/homeassistant_time.cpp @@ -7,10 +7,8 @@ namespace homeassistant { static const char *const TAG = "homeassistant.time"; void HomeassistantTime::dump_config() { - ESP_LOGCONFIG(TAG, - "Home Assistant Time:\n" - " Timezone: '%s'", - this->timezone_.c_str()); + ESP_LOGCONFIG(TAG, "Home Assistant Time"); + RealTimeClock::dump_config(); } float HomeassistantTime::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/pcf85063/pcf85063.cpp b/esphome/components/pcf85063/pcf85063.cpp index cb987c6129..f38b60b55d 100644 --- a/esphome/components/pcf85063/pcf85063.cpp +++ b/esphome/components/pcf85063/pcf85063.cpp @@ -23,7 +23,7 @@ void PCF85063Component::dump_config() { if (this->is_failed()) { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); } - ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); + RealTimeClock::dump_config(); } float PCF85063Component::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/pcf8563/pcf8563.cpp b/esphome/components/pcf8563/pcf8563.cpp index 27020378a6..2090936bb6 100644 --- a/esphome/components/pcf8563/pcf8563.cpp +++ b/esphome/components/pcf8563/pcf8563.cpp @@ -23,7 +23,7 @@ void PCF8563Component::dump_config() { if (this->is_failed()) { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); } - ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); + RealTimeClock::dump_config(); } float PCF8563Component::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/rx8130/rx8130.cpp b/esphome/components/rx8130/rx8130.cpp index cf6ea3e6e6..ba092a4834 100644 --- a/esphome/components/rx8130/rx8130.cpp +++ b/esphome/components/rx8130/rx8130.cpp @@ -62,6 +62,7 @@ void RX8130Component::update() { this->read_time(); } void RX8130Component::dump_config() { ESP_LOGCONFIG(TAG, "RX8130:"); LOG_I2C_DEVICE(this); + RealTimeClock::dump_config(); } void RX8130Component::read_time() { diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 331a9b3509..c4d78b6e0b 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -61,6 +61,7 @@ void SNTPComponent::dump_config() { for (auto &server : this->servers_) { ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server); } + RealTimeClock::dump_config(); } void SNTPComponent::update() { #if !defined(USE_ESP32) diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 42c564659f..175cee0c1f 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -23,6 +23,13 @@ namespace time { static const char *const TAG = "time"; RealTimeClock::RealTimeClock() = default; + +void RealTimeClock::dump_config() { +#ifdef USE_TIME_TIMEZONE + ESP_LOGCONFIG(TAG, "Timezone: '%s'", this->timezone_.c_str()); +#endif +} + void RealTimeClock::synchronize_epoch_(uint32_t epoch) { ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch); // Update UTC epoch time. diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index bbcecaa628..2f17bd86d6 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -52,6 +52,8 @@ class RealTimeClock : public PollingComponent { this->time_sync_callback_.add(std::move(callback)); }; + void dump_config() override; + protected: /// Report a unix epoch as current time. void synchronize_epoch_(uint32_t epoch); diff --git a/tests/components/ds1307/test.nrf52-adafruit.yaml b/tests/components/ds1307/test.nrf52-adafruit.yaml new file mode 100644 index 0000000000..2a0de6241c --- /dev/null +++ b/tests/components/ds1307/test.nrf52-adafruit.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/nrf52.yaml + +<<: !include common.yaml diff --git a/tests/components/pcf85063/test.nrf52-adafruit.yaml b/tests/components/pcf85063/test.nrf52-adafruit.yaml new file mode 100644 index 0000000000..2a0de6241c --- /dev/null +++ b/tests/components/pcf85063/test.nrf52-adafruit.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/nrf52.yaml + +<<: !include common.yaml diff --git a/tests/components/pcf8563/test.nrf52-adafruit.yaml b/tests/components/pcf8563/test.nrf52-adafruit.yaml new file mode 100644 index 0000000000..2a0de6241c --- /dev/null +++ b/tests/components/pcf8563/test.nrf52-adafruit.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/nrf52.yaml + +<<: !include common.yaml diff --git a/tests/components/rx8130/test.nrf52-adafruit.yaml b/tests/components/rx8130/test.nrf52-adafruit.yaml new file mode 100644 index 0000000000..2a0de6241c --- /dev/null +++ b/tests/components/rx8130/test.nrf52-adafruit.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/nrf52.yaml + +<<: !include common.yaml From 661920c51edcaaf3c8c62d86499f79388a063d8d Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Tue, 11 Nov 2025 19:18:17 +0100 Subject: [PATCH 0174/1145] [nrf52,ssd1306_i2c] fix build error (#11847) --- esphome/core/macros.h | 4 ++++ tests/components/ssd1306_i2c/test.nrf52-xiao-ble.yaml | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 tests/components/ssd1306_i2c/test.nrf52-xiao-ble.yaml diff --git a/esphome/core/macros.h b/esphome/core/macros.h index 8b2383321b..2e47453c40 100644 --- a/esphome/core/macros.h +++ b/esphome/core/macros.h @@ -6,3 +6,7 @@ #ifdef USE_ARDUINO #include #endif + +#ifdef USE_ZEPHYR +#define M_PI 3.14159265358979323846 +#endif diff --git a/tests/components/ssd1306_i2c/test.nrf52-xiao-ble.yaml b/tests/components/ssd1306_i2c/test.nrf52-xiao-ble.yaml new file mode 100644 index 0000000000..28254e4af5 --- /dev/null +++ b/tests/components/ssd1306_i2c/test.nrf52-xiao-ble.yaml @@ -0,0 +1,7 @@ +substitutions: + reset_pin: P0.10 + +packages: + i2c: !include ../../test_build_components/common/i2c/nrf52.yaml + +<<: !include common.yaml From 7a92565a0c4d0290f55a63f3cfdad9da9ec5fe17 Mon Sep 17 00:00:00 2001 From: CzBiX Date: Wed, 12 Nov 2025 03:24:52 +0800 Subject: [PATCH 0175/1145] [lvgl] Fix compile when using transform_zoom (#11845) --- esphome/components/lvgl/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 4df68a6386..2a24f343c3 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -331,7 +331,7 @@ async def to_code(configs): # This must be done after all widgets are created for comp in helpers.lvgl_components_required: cg.add_define(f"USE_LVGL_{comp.upper()}") - if "transform_angle" in styles_used: + if {"transform_angle", "transform_zoom"} & styles_used: add_define("LV_COLOR_SCREEN_TRANSP", "1") for use in helpers.lv_uses: add_define(f"LV_USE_{use.upper()}") From 80a7c6d3c31b172a34ba0203785b128b146d92a6 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Tue, 11 Nov 2025 21:52:41 +0100 Subject: [PATCH 0176/1145] [nrf52,debug] add partition dump (#11839) Co-authored-by: J. Nick Koston --- esphome/components/debug/debug_component.cpp | 6 ++-- esphome/components/debug/debug_component.h | 8 ++--- esphome/components/debug/debug_zephyr.cpp | 32 +++++++++++++++++++ .../components/debug/test.nrf52-xiao-ble.yaml | 1 + 4 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 tests/components/debug/test.nrf52-xiao-ble.yaml diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index ade0968e08..f54bf82eae 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -49,9 +49,9 @@ void DebugComponent::dump_config() { } #endif // USE_TEXT_SENSOR -#ifdef USE_ESP32 - this->log_partition_info_(); // Log partition information for ESP32 -#endif // USE_ESP32 +#if defined(USE_ESP32) || defined(USE_ZEPHYR) + this->log_partition_info_(); // Log partition information +#endif } void DebugComponent::loop() { diff --git a/esphome/components/debug/debug_component.h b/esphome/components/debug/debug_component.h index efd0dafab0..96306f7cdf 100644 --- a/esphome/components/debug/debug_component.h +++ b/esphome/components/debug/debug_component.h @@ -62,19 +62,19 @@ class DebugComponent : public PollingComponent { sensor::Sensor *cpu_frequency_sensor_{nullptr}; #endif // USE_SENSOR -#ifdef USE_ESP32 +#if defined(USE_ESP32) || defined(USE_ZEPHYR) /** * @brief Logs information about the device's partition table. * - * This function iterates through the ESP32's partition table and logs details + * This function iterates through the partition table and logs details * about each partition, including its name, type, subtype, starting address, * and size. The information is useful for diagnosing issues related to flash * memory or verifying the partition configuration dynamically at runtime. * - * Only available when compiled for ESP32 platforms. + * Only available when compiled for ESP32 and ZEPHYR platforms. */ void log_partition_info_(); -#endif // USE_ESP32 +#endif #ifdef USE_TEXT_SENSOR text_sensor::TextSensor *device_info_{nullptr}; diff --git a/esphome/components/debug/debug_zephyr.cpp b/esphome/components/debug/debug_zephyr.cpp index 62fa391e5f..c888c41a78 100644 --- a/esphome/components/debug/debug_zephyr.cpp +++ b/esphome/components/debug/debug_zephyr.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #define BOOTLOADER_VERSION_REGISTER NRF_TIMER2->CC[0] @@ -86,6 +87,37 @@ std::string DebugComponent::get_reset_reason_() { uint32_t DebugComponent::get_free_heap_() { return INT_MAX; } +static void fa_cb(const struct flash_area *fa, void *user_data) { +#if CONFIG_FLASH_MAP_LABELS + const char *fa_label = flash_area_label(fa); + + if (fa_label == nullptr) { + fa_label = "-"; + } + ESP_LOGCONFIG(TAG, "%2d 0x%0*" PRIxPTR " %-26s %-24.24s 0x%-10x 0x%-12x", (int) fa->fa_id, + sizeof(uintptr_t) * 2, (uintptr_t) fa->fa_dev, fa->fa_dev->name, fa_label, (uint32_t) fa->fa_off, + fa->fa_size); +#else + ESP_LOGCONFIG(TAG, "%2d 0x%0*" PRIxPTR " %-26s 0x%-10x 0x%-12x", (int) fa->fa_id, sizeof(uintptr_t) * 2, + (uintptr_t) fa->fa_dev, fa->fa_dev->name, (uint32_t) fa->fa_off, fa->fa_size); +#endif +} + +void DebugComponent::log_partition_info_() { +#if CONFIG_FLASH_MAP_LABELS + ESP_LOGCONFIG(TAG, "ID | Device | Device Name " + "| Label | Offset | Size"); + ESP_LOGCONFIG(TAG, "--------------------------------------------" + "-----------------------------------------------"); +#else + ESP_LOGCONFIG(TAG, "ID | Device | Device Name " + "| Offset | Size"); + ESP_LOGCONFIG(TAG, "-----------------------------------------" + "------------------------------"); +#endif + flash_area_foreach(fa_cb, nullptr); +} + void DebugComponent::get_device_info_(std::string &device_info) { std::string supply = "Main supply status: "; if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) { diff --git a/tests/components/debug/test.nrf52-xiao-ble.yaml b/tests/components/debug/test.nrf52-xiao-ble.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/debug/test.nrf52-xiao-ble.yaml @@ -0,0 +1 @@ +<<: !include common.yaml From 2f91e7bd47e96a9542e73764b67d4b77ad48f29e Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Tue, 11 Nov 2025 22:33:53 +0100 Subject: [PATCH 0177/1145] [nrf52] fix boot loop (#11854) --- esphome/components/nrf52/__init__.py | 14 ++++---------- esphome/components/nrf52/uicr.cpp | 13 ++++++++++++- tests/components/nrf52/test.nrf52-xiao-ble.yaml | 2 +- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/esphome/components/nrf52/__init__.py b/esphome/components/nrf52/__init__.py index 9566263c7c..a3b79bf139 100644 --- a/esphome/components/nrf52/__init__.py +++ b/esphome/components/nrf52/__init__.py @@ -107,7 +107,6 @@ CONF_REG0 = "reg0" CONF_UICR_ERASE = "uicr_erase" VOLTAGE_LEVELS = [1.8, 2.1, 2.4, 2.7, 3.0, 3.3] -DEFAULT_VOLTAGE_LEVEL = "default" CONFIG_SCHEMA = cv.All( _detect_bootloader, @@ -124,12 +123,9 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_REG0): cv.Schema( { - cv.Required(CONF_VOLTAGE): cv.Any( - cv.All( - cv.voltage, - cv.one_of(*VOLTAGE_LEVELS, float=True), - ), - cv.one_of(*[DEFAULT_VOLTAGE_LEVEL], lower=True), + cv.Required(CONF_VOLTAGE): cv.All( + cv.voltage, + cv.one_of(*VOLTAGE_LEVELS, float=True), ), cv.Optional(CONF_UICR_ERASE, default=False): cv.boolean, } @@ -202,9 +198,7 @@ async def to_code(config: ConfigType) -> None: CORE.add_job(_dfu_to_code, dfu_config) if reg0_config := config.get(CONF_REG0): - value = 7 # DEFAULT_VOLTAGE_LEVEL - if reg0_config[CONF_VOLTAGE] in VOLTAGE_LEVELS: - value = VOLTAGE_LEVELS.index(reg0_config[CONF_VOLTAGE]) + value = VOLTAGE_LEVELS.index(reg0_config[CONF_VOLTAGE]) cg.add_define("USE_NRF52_REG0_VOUT", value) if reg0_config[CONF_UICR_ERASE]: cg.add_define("USE_NRF52_UICR_ERASE") diff --git a/esphome/components/nrf52/uicr.cpp b/esphome/components/nrf52/uicr.cpp index 22714b7e50..4c0beeb503 100644 --- a/esphome/components/nrf52/uicr.cpp +++ b/esphome/components/nrf52/uicr.cpp @@ -69,9 +69,20 @@ static StatusFlags fix_bootloader() { } #endif +#define BOOTLOADER_VERSION_REGISTER NRF_TIMER2->CC[0] + static StatusFlags set_uicr() { StatusFlags status = StatusFlags::OK; - status |= set_regout0(); +#ifndef USE_BOOTLOADER_MCUBOOT + if (BOOTLOADER_VERSION_REGISTER <= 0x902) { +#ifdef CONFIG_PRINTK + printk("cannot control regout0 for %#x\n", BOOTLOADER_VERSION_REGISTER); +#endif + } else +#endif + { + status |= set_regout0(); + } #ifndef USE_BOOTLOADER_MCUBOOT status |= fix_bootloader(); #endif diff --git a/tests/components/nrf52/test.nrf52-xiao-ble.yaml b/tests/components/nrf52/test.nrf52-xiao-ble.yaml index c3c44902f0..d53c692001 100644 --- a/tests/components/nrf52/test.nrf52-xiao-ble.yaml +++ b/tests/components/nrf52/test.nrf52-xiao-ble.yaml @@ -6,4 +6,4 @@ nrf52: mode: output: true reg0: - voltage: default + voltage: 1.8V From a2ec7f622c861f7e21843d26a10b573515be43f6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 Nov 2025 16:04:37 -0600 Subject: [PATCH 0178/1145] [wifi] Fix infinite retry loop when no hidden networks and captive portal active (#11831) --- esphome/components/wifi/wifi_component.cpp | 28 ++++++++++------------ 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 7279e0c783..49e433b468 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1163,11 +1163,9 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() { if (this->find_next_hidden_sta_(-1, !this->went_through_explicit_hidden_phase_()) >= 0) { return WiFiRetryPhase::RETRY_HIDDEN; // Found hidden networks to try } - // No hidden networks - skip directly to restart/rescan - if (this->is_captive_portal_active_() || this->is_esp32_improv_active_()) { - return this->went_through_explicit_hidden_phase_() ? WiFiRetryPhase::EXPLICIT_HIDDEN - : WiFiRetryPhase::SCAN_CONNECTING; - } + // No hidden networks - always go through RESTARTING_ADAPTER phase + // This ensures num_retried_ gets reset and a fresh scan is triggered + // The actual adapter restart will be skipped if captive portal/improv is active return WiFiRetryPhase::RESTARTING_ADAPTER; case WiFiRetryPhase::RETRY_HIDDEN: @@ -1183,16 +1181,9 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() { return WiFiRetryPhase::RETRY_HIDDEN; } } - // Exhausted all potentially hidden SSIDs - rescan to try next BSSID - // If captive portal/improv is active, skip adapter restart and go back to start - // Otherwise restart adapter to clear any stuck state - if (this->is_captive_portal_active_() || this->is_esp32_improv_active_()) { - // Go back to explicit hidden if we went through it initially, otherwise scan - return this->went_through_explicit_hidden_phase_() ? WiFiRetryPhase::EXPLICIT_HIDDEN - : WiFiRetryPhase::SCAN_CONNECTING; - } - - // Restart adapter + // Exhausted all potentially hidden SSIDs - always go through RESTARTING_ADAPTER + // This ensures num_retried_ gets reset and a fresh scan is triggered + // The actual adapter restart will be skipped if captive portal/improv is active return WiFiRetryPhase::RESTARTING_ADAPTER; case WiFiRetryPhase::RESTARTING_ADAPTER: @@ -1280,7 +1271,12 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) { break; case WiFiRetryPhase::RESTARTING_ADAPTER: - this->restart_adapter(); + // Skip actual adapter restart if captive portal/improv is active + // This allows state machine to reset num_retried_ and trigger fresh scan + // without disrupting the captive portal/improv connection + if (!this->is_captive_portal_active_() && !this->is_esp32_improv_active_()) { + this->restart_adapter(); + } // Return true to indicate we should wait (go to COOLDOWN) instead of immediately connecting return true; From ef04903a7a5e4665c13a2b4208e99ef976db1ab6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 Nov 2025 16:10:17 -0600 Subject: [PATCH 0179/1145] [wifi] Change priority type from float to int8_t (#11830) --- esphome/components/wifi/__init__.py | 2 +- esphome/components/wifi/wifi_component.cpp | 53 ++++++++++++++++++---- esphome/components/wifi/wifi_component.h | 24 +++++----- tests/components/wifi/common.yaml | 5 ++ 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 5f4190a933..358f920c2c 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -174,7 +174,7 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend( { cv.Optional(CONF_BSSID): cv.mac_address, cv.Optional(CONF_HIDDEN): cv.boolean, - cv.Optional(CONF_PRIORITY, default=0.0): cv.float_, + cv.Optional(CONF_PRIORITY, default=0): cv.int_range(min=-128, max=127), cv.Optional(CONF_EAP): EAP_AUTH_SCHEMA, } ) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 49e433b468..681555431e 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -667,7 +667,7 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { // Log connection attempt at INFO level with priority std::string bssid_formatted; - float priority = 0.0f; + int8_t priority = 0; if (ap.get_bssid().has_value()) { bssid_formatted = format_mac_address_pretty(ap.get_bssid().value().data()); @@ -675,7 +675,7 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { } ESP_LOGI(TAG, - "Connecting to " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") " (priority %.1f, attempt %u/%u in phase %s)...", + "Connecting to " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") " (priority %d, attempt %u/%u in phase %s)...", ap.get_ssid().c_str(), ap.get_bssid().has_value() ? bssid_formatted.c_str() : LOG_STR_LITERAL("any"), priority, this->num_retried_ + 1, get_max_retries_for_phase(this->retry_phase_), LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_))); @@ -812,7 +812,7 @@ void WiFiComponent::print_connect_params_() { wifi_gateway_ip_().str().c_str(), wifi_dns_ip_(0).str().c_str(), wifi_dns_ip_(1).str().c_str()); #ifdef ESPHOME_LOG_HAS_VERBOSE if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_bssid().has_value()) { - ESP_LOGV(TAG, " Priority: %.1f", this->get_sta_priority(*config->get_bssid())); + ESP_LOGV(TAG, " Priority: %d", this->get_sta_priority(*config->get_bssid())); } #endif #ifdef USE_WIFI_11KV_SUPPORT @@ -933,8 +933,7 @@ __attribute__((noinline)) static void log_scan_result(const WiFiScanResult &res) ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), res.get_is_hidden() ? LOG_STR_LITERAL("(HIDDEN) ") : LOG_STR_LITERAL(""), bssid_s, LOG_STR_ARG(get_signal_bars(res.get_rssi()))); - ESP_LOGD(TAG, " Channel: %2u, RSSI: %3d dB, Priority: %4.1f", res.get_channel(), res.get_rssi(), - res.get_priority()); + ESP_LOGD(TAG, " Channel: %2u, RSSI: %3d dB, Priority: %4d", res.get_channel(), res.get_rssi(), res.get_priority()); } else { ESP_LOGD(TAG, "- " LOG_SECRET("'%s'") " " LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), bssid_s, LOG_STR_ARG(get_signal_bars(res.get_rssi()))); @@ -1063,6 +1062,9 @@ void WiFiComponent::check_connecting_finished() { this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED; this->num_retried_ = 0; + // Clear priority tracking if all priorities are at minimum + this->clear_priorities_if_all_min_(); + #ifdef USE_WIFI_FAST_CONNECT this->save_fast_connect_settings_(); #endif @@ -1287,6 +1289,34 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) { return false; // Did not start scan, can proceed with connection } +/// Clear BSSID priority tracking if all priorities are at minimum (saves memory) +/// At minimum priority, all BSSIDs are equally bad, so priority tracking is useless +/// Called after successful connection or after failed connection attempts +void WiFiComponent::clear_priorities_if_all_min_() { + if (this->sta_priorities_.empty()) { + return; + } + + int8_t first_priority = this->sta_priorities_[0].priority; + + // Only clear if all priorities have been decremented to the minimum value + // At this point, all BSSIDs have been equally penalized and priority info is useless + if (first_priority != std::numeric_limits::min()) { + return; + } + + for (const auto &pri : this->sta_priorities_) { + if (pri.priority != first_priority) { + return; // Not all same, nothing to do + } + } + + // All priorities are at minimum - clear the vector to save memory and reset + ESP_LOGD(TAG, "Clearing BSSID priorities (all at minimum)"); + this->sta_priorities_.clear(); + this->sta_priorities_.shrink_to_fit(); +} + /// Log failed connection attempt and decrease BSSID priority to avoid repeated failures /// This function identifies which BSSID was attempted (from scan results or config), /// decreases its priority by 1.0 to discourage future attempts, and logs the change. @@ -1317,8 +1347,9 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { } // Decrease priority to avoid repeatedly trying the same failed BSSID - float old_priority = this->get_sta_priority(failed_bssid.value()); - float new_priority = old_priority - 1.0f; + int8_t old_priority = this->get_sta_priority(failed_bssid.value()); + int8_t new_priority = + (old_priority > std::numeric_limits::min()) ? (old_priority - 1) : std::numeric_limits::min(); this->set_sta_priority(failed_bssid.value(), new_priority); // Get SSID for logging @@ -1329,8 +1360,12 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { ssid = config->get_ssid(); } - ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %.1f → %.1f", ssid.c_str(), + ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", ssid.c_str(), format_mac_address_pretty(failed_bssid.value().data()).c_str(), old_priority, new_priority); + + // After adjusting priority, check if all priorities are now at minimum + // If so, clear the vector to save memory and reset for fresh start + this->clear_priorities_if_all_min_(); } /// Handle target advancement or retry counter increment when staying in the same phase @@ -1543,9 +1578,9 @@ bool WiFiAP::get_hidden() const { return this->hidden_; } WiFiScanResult::WiFiScanResult(const bssid_t &bssid, std::string ssid, uint8_t channel, int8_t rssi, bool with_auth, bool is_hidden) : bssid_(bssid), - ssid_(std::move(ssid)), channel_(channel), rssi_(rssi), + ssid_(std::move(ssid)), with_auth_(with_auth), is_hidden_(is_hidden) {} bool WiFiScanResult::matches(const WiFiAP &config) const { diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index ed049544cf..b8223e8dc8 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -157,7 +157,7 @@ class WiFiAP { void set_eap(optional eap_auth); #endif // USE_WIFI_WPA2_EAP void set_channel(optional channel); - void set_priority(float priority) { priority_ = priority; } + void set_priority(int8_t priority) { priority_ = priority; } void set_manual_ip(optional manual_ip); void set_hidden(bool hidden); const std::string &get_ssid() const; @@ -167,7 +167,7 @@ class WiFiAP { const optional &get_eap() const; #endif // USE_WIFI_WPA2_EAP const optional &get_channel() const; - float get_priority() const { return priority_; } + int8_t get_priority() const { return priority_; } const optional &get_manual_ip() const; bool get_hidden() const; @@ -179,8 +179,8 @@ class WiFiAP { optional eap_; #endif // USE_WIFI_WPA2_EAP optional manual_ip_; - float priority_{0}; optional channel_; + int8_t priority_{0}; bool hidden_{false}; }; @@ -198,17 +198,17 @@ class WiFiScanResult { int8_t get_rssi() const; bool get_with_auth() const; bool get_is_hidden() const; - float get_priority() const { return priority_; } - void set_priority(float priority) { priority_ = priority; } + int8_t get_priority() const { return priority_; } + void set_priority(int8_t priority) { priority_ = priority; } bool operator==(const WiFiScanResult &rhs) const; protected: bssid_t bssid_; - std::string ssid_; - float priority_{0.0f}; uint8_t channel_; int8_t rssi_; + std::string ssid_; + int8_t priority_{0}; bool matches_{false}; bool with_auth_; bool is_hidden_; @@ -216,7 +216,7 @@ class WiFiScanResult { struct WiFiSTAPriority { bssid_t bssid; - float priority; + int8_t priority; }; enum WiFiPowerSaveMode : uint8_t { @@ -317,14 +317,14 @@ class WiFiComponent : public Component { } return false; } - float get_sta_priority(const bssid_t bssid) { + int8_t get_sta_priority(const bssid_t bssid) { for (auto &it : this->sta_priorities_) { if (it.bssid == bssid) return it.priority; } - return 0.0f; + return 0; } - void set_sta_priority(const bssid_t bssid, float priority) { + void set_sta_priority(const bssid_t bssid, int8_t priority) { for (auto &it : this->sta_priorities_) { if (it.bssid == bssid) { it.priority = priority; @@ -383,6 +383,8 @@ class WiFiComponent : public Component { int8_t find_next_hidden_sta_(int8_t start_index, bool include_explicit_hidden = true); /// Log failed connection and decrease BSSID priority to avoid repeated attempts void log_and_adjust_priority_for_failed_connect_(); + /// Clear BSSID priority tracking if all priorities are at minimum (saves memory) + void clear_priorities_if_all_min_(); /// Advance to next target (AP/SSID) within current phase, or increment retry counter /// Called when staying in the same phase after a failed connection attempt void advance_to_next_target_or_increment_retry_(); diff --git a/tests/components/wifi/common.yaml b/tests/components/wifi/common.yaml index af27f85092..5d9973cbc8 100644 --- a/tests/components/wifi/common.yaml +++ b/tests/components/wifi/common.yaml @@ -15,5 +15,10 @@ wifi: networks: - ssid: MySSID password: password1 + priority: 10 - ssid: MySSID2 password: password2 + priority: 5 + - ssid: MySSID3 + password: password3 + priority: 0 From 00c71b7236a1ce891f8cc6721240b72897757a46 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 Nov 2025 16:33:37 -0600 Subject: [PATCH 0180/1145] [wifi] Fix all-hidden networks duplicate attempts and scan skipping (#11848) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/wifi/wifi_component.cpp | 39 ++++++++++++++++------ esphome/components/wifi/wifi_component.h | 3 +- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 681555431e..d75ac971eb 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -253,17 +253,19 @@ bool WiFiComponent::ssid_was_seen_in_scan_(const std::string &ssid) const { return false; } -int8_t WiFiComponent::find_next_hidden_sta_(int8_t start_index, bool include_explicit_hidden) { +int8_t WiFiComponent::find_next_hidden_sta_(int8_t start_index) { // Find next SSID that wasn't in scan results (might be hidden) + bool include_explicit_hidden = !this->went_through_explicit_hidden_phase_(); // Start searching from start_index + 1 for (size_t i = start_index + 1; i < this->sta_.size(); i++) { const auto &sta = this->sta_[i]; // Skip networks that were already tried in EXPLICIT_HIDDEN phase // Those are: networks marked hidden:true that appear before the first non-hidden network + // If all networks are hidden (first_non_hidden_idx == -1), skip all of them if (!include_explicit_hidden && sta.get_hidden()) { int8_t first_non_hidden_idx = this->find_first_non_hidden_index_(); - if (first_non_hidden_idx >= 0 && static_cast(i) < first_non_hidden_idx) { + if (first_non_hidden_idx < 0 || static_cast(i) < first_non_hidden_idx) { ESP_LOGD(TAG, "Skipping " LOG_SECRET("'%s'") " (explicit hidden, already tried)", sta.get_ssid().c_str()); continue; } @@ -1002,6 +1004,12 @@ void WiFiComponent::check_scanning_finished() { // No scan results matched our configured networks - transition directly to hidden mode // Don't call retry_connect() since we never attempted a connection (no BSSID to penalize) this->transition_to_phase_(WiFiRetryPhase::RETRY_HIDDEN); + // If no hidden networks to try, skip connection attempt (will be handled on next loop) + if (this->selected_sta_index_ == -1) { + this->state_ = WIFI_COMPONENT_STATE_COOLDOWN; + this->action_started_ = millis(); + return; + } // Now start connection attempt in hidden mode } else if (this->transition_to_phase_(WiFiRetryPhase::SCAN_CONNECTING)) { return; // scan started, wait for next loop iteration @@ -1144,7 +1152,12 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() { return WiFiRetryPhase::EXPLICIT_HIDDEN; } - // No more consecutive explicitly hidden networks - proceed to scanning + // No more consecutive explicitly hidden networks + // If ALL networks are hidden, skip scanning and go directly to restart + if (this->find_first_non_hidden_index_() < 0) { + return WiFiRetryPhase::RESTARTING_ADAPTER; + } + // Otherwise proceed to scanning for non-hidden networks return WiFiRetryPhase::SCAN_CONNECTING; } @@ -1162,7 +1175,7 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() { // Its priority has been decreased, so on next scan it will be sorted lower // and we'll try the next best BSSID. // Check if there are any potentially hidden networks to try - if (this->find_next_hidden_sta_(-1, !this->went_through_explicit_hidden_phase_()) >= 0) { + if (this->find_next_hidden_sta_(-1) >= 0) { return WiFiRetryPhase::RETRY_HIDDEN; // Found hidden networks to try } // No hidden networks - always go through RESTARTING_ADAPTER phase @@ -1179,8 +1192,13 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() { // Exhausted retries on current SSID - check if there are more potentially hidden SSIDs to try if (this->selected_sta_index_ < static_cast(this->sta_.size()) - 1) { - // More SSIDs available - stay in RETRY_HIDDEN, advance will happen in retry_connect() - return WiFiRetryPhase::RETRY_HIDDEN; + // Check if find_next_hidden_sta_() would actually find another hidden SSID + // as it might have been seen in the scan results and we want to skip those + // otherwise we will get stuck in RETRY_HIDDEN phase + if (this->find_next_hidden_sta_(this->selected_sta_index_) != -1) { + // More hidden SSIDs available - stay in RETRY_HIDDEN, advance will happen in retry_connect() + return WiFiRetryPhase::RETRY_HIDDEN; + } } } // Exhausted all potentially hidden SSIDs - always go through RESTARTING_ADAPTER @@ -1205,8 +1223,8 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() { /// - Performing phase-specific initialization (e.g., advancing AP index, starting scans) /// /// @param new_phase The phase we're transitioning TO -/// @return true if an async scan was started (caller should wait for completion) -/// false if no scan started (caller can proceed with connection attempt) +/// @return true if connection attempt should be skipped (scan started or no networks to try) +/// false if caller can proceed with connection attempt bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) { WiFiRetryPhase old_phase = this->retry_phase_; @@ -1264,7 +1282,7 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) { // If first network is marked hidden, we went through EXPLICIT_HIDDEN phase // In that case, skip networks marked hidden:true (already tried) // Otherwise, include them (they haven't been tried yet) - this->selected_sta_index_ = this->find_next_hidden_sta_(-1, !this->went_through_explicit_hidden_phase_()); + this->selected_sta_index_ = this->find_next_hidden_sta_(-1); if (this->selected_sta_index_ == -1) { ESP_LOGD(TAG, "All SSIDs visible or already tried, skipping hidden mode"); @@ -1410,8 +1428,7 @@ void WiFiComponent::advance_to_next_target_or_increment_retry_() { // If first network is marked hidden, we went through EXPLICIT_HIDDEN phase // In that case, skip networks marked hidden:true (already tried) // Otherwise, include them (they haven't been tried yet) - int8_t next_index = - this->find_next_hidden_sta_(this->selected_sta_index_, !this->went_through_explicit_hidden_phase_()); + int8_t next_index = this->find_next_hidden_sta_(this->selected_sta_index_); if (next_index != -1) { // Found another potentially hidden SSID this->selected_sta_index_ = next_index; diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index b8223e8dc8..e786708b08 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -379,8 +379,7 @@ class WiFiComponent : public Component { /// Find next SSID that wasn't in scan results (might be hidden) /// Returns index of next potentially hidden SSID, or -1 if none found /// @param start_index Start searching from index after this (-1 to start from beginning) - /// @param include_explicit_hidden If true, include SSIDs marked hidden:true. If false, only find truly hidden SSIDs. - int8_t find_next_hidden_sta_(int8_t start_index, bool include_explicit_hidden = true); + int8_t find_next_hidden_sta_(int8_t start_index); /// Log failed connection and decrease BSSID priority to avoid repeated attempts void log_and_adjust_priority_for_failed_connect_(); /// Clear BSSID priority tracking if all priorities are at minimum (saves memory) From 65a303d48f92c1706dc6f464f88c41fbc9aa5c9f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 Nov 2025 16:39:55 -0600 Subject: [PATCH 0181/1145] [wifi] Add min_auth_mode configuration option (#11814) --- esphome/components/wifi/__init__.py | 42 +++++++++++++++++++ esphome/components/wifi/wifi_component.h | 8 ++++ .../wifi/wifi_component_esp8266.cpp | 13 +++++- .../wifi/wifi_component_esp_idf.cpp | 15 +++++-- tests/components/wifi/test.esp32-idf.yaml | 1 + tests/components/wifi/test.esp8266-ard.yaml | 6 ++- 6 files changed, 79 insertions(+), 6 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 358f920c2c..c42af23252 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -1,3 +1,5 @@ +import logging + from esphome import automation from esphome.automation import Condition import esphome.codegen as cg @@ -42,6 +44,7 @@ from esphome.const import ( CONF_TTLS_PHASE_2, CONF_USE_ADDRESS, CONF_USERNAME, + Platform, PlatformFramework, ) from esphome.core import CORE, CoroPriority, HexInt, coroutine_with_priority @@ -49,10 +52,13 @@ import esphome.final_validate as fv from . import wpa2_eap +_LOGGER = logging.getLogger(__name__) + AUTO_LOAD = ["network"] NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2, const.VARIANT_ESP32P4] CONF_SAVE = "save" +CONF_MIN_AUTH_MODE = "min_auth_mode" # Maximum number of WiFi networks that can be configured # Limited to 127 because selected_sta_index_ is int8_t in C++ @@ -70,6 +76,14 @@ WIFI_POWER_SAVE_MODES = { "LIGHT": WiFiPowerSaveMode.WIFI_POWER_SAVE_LIGHT, "HIGH": WiFiPowerSaveMode.WIFI_POWER_SAVE_HIGH, } + +WifiMinAuthMode = wifi_ns.enum("WifiMinAuthMode") +WIFI_MIN_AUTH_MODES = { + "WPA": WifiMinAuthMode.WIFI_MIN_AUTH_MODE_WPA, + "WPA2": WifiMinAuthMode.WIFI_MIN_AUTH_MODE_WPA2, + "WPA3": WifiMinAuthMode.WIFI_MIN_AUTH_MODE_WPA3, +} +VALIDATE_WIFI_MIN_AUTH_MODE = cv.enum(WIFI_MIN_AUTH_MODES, upper=True) WiFiConnectedCondition = wifi_ns.class_("WiFiConnectedCondition", Condition) WiFiEnabledCondition = wifi_ns.class_("WiFiEnabledCondition", Condition) WiFiEnableAction = wifi_ns.class_("WiFiEnableAction", automation.Action) @@ -187,6 +201,27 @@ def validate_variant(_): raise cv.Invalid(f"WiFi requires component esp32_hosted on {variant}") +def _apply_min_auth_mode_default(config): + """Apply platform-specific default for min_auth_mode and warn ESP8266 users.""" + # Only apply defaults for platforms that support min_auth_mode + if CONF_MIN_AUTH_MODE not in config and (CORE.is_esp8266 or CORE.is_esp32): + if CORE.is_esp8266: + _LOGGER.warning( + "The minimum WiFi authentication mode (wifi -> min_auth_mode) is not set. " + "This controls the weakest encryption your device will accept when connecting to WiFi. " + "Currently defaults to WPA (less secure), but will change to WPA2 (more secure) in 2026.6.0. " + "WPA uses TKIP encryption which has known security vulnerabilities and should be avoided. " + "WPA2 uses AES encryption which is significantly more secure. " + "To silence this warning, explicitly set min_auth_mode under 'wifi:'. " + "If your router supports WPA2 or WPA3, set 'min_auth_mode: WPA2'. " + "If your router only supports WPA, set 'min_auth_mode: WPA'." + ) + config[CONF_MIN_AUTH_MODE] = VALIDATE_WIFI_MIN_AUTH_MODE("WPA") + elif CORE.is_esp32: + config[CONF_MIN_AUTH_MODE] = VALIDATE_WIFI_MIN_AUTH_MODE("WPA2") + return config + + def final_validate(config): has_sta = bool(config.get(CONF_NETWORKS, True)) has_ap = CONF_AP in config @@ -287,6 +322,10 @@ CONFIG_SCHEMA = cv.All( ): cv.enum(WIFI_POWER_SAVE_MODES, upper=True), cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, + cv.Optional(CONF_MIN_AUTH_MODE): cv.All( + VALIDATE_WIFI_MIN_AUTH_MODE, + cv.only_on([Platform.ESP32, Platform.ESP8266]), + ), cv.SplitDefault(CONF_OUTPUT_POWER, esp8266=20.0): cv.All( cv.decibel, cv.float_range(min=8.5, max=20.5) ), @@ -311,6 +350,7 @@ CONFIG_SCHEMA = cv.All( ), } ), + _apply_min_auth_mode_default, _validate, ) @@ -420,6 +460,8 @@ async def to_code(config): cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE])) + if CONF_MIN_AUTH_MODE in config: + cg.add(var.set_min_auth_mode(config[CONF_MIN_AUTH_MODE])) if config[CONF_FAST_CONNECT]: cg.add_define("USE_WIFI_FAST_CONNECT") cg.add(var.set_passive_scan(config[CONF_PASSIVE_SCAN])) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index e786708b08..02d6d984f1 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -225,6 +225,12 @@ enum WiFiPowerSaveMode : uint8_t { WIFI_POWER_SAVE_HIGH, }; +enum WifiMinAuthMode : uint8_t { + WIFI_MIN_AUTH_MODE_WPA = 0, + WIFI_MIN_AUTH_MODE_WPA2, + WIFI_MIN_AUTH_MODE_WPA3, +}; + #ifdef USE_ESP32 struct IDFWiFiEvent; #endif @@ -274,6 +280,7 @@ class WiFiComponent : public Component { bool is_connected(); void set_power_save_mode(WiFiPowerSaveMode power_save); + void set_min_auth_mode(WifiMinAuthMode min_auth_mode) { min_auth_mode_ = min_auth_mode; } void set_output_power(float output_power) { output_power_ = output_power; } void set_passive_scan(bool passive); @@ -490,6 +497,7 @@ class WiFiComponent : public Component { // Group all 8-bit values together WiFiComponentState state_{WIFI_COMPONENT_STATE_OFF}; WiFiPowerSaveMode power_save_{WIFI_POWER_SAVE_NONE}; + WifiMinAuthMode min_auth_mode_{WIFI_MIN_AUTH_MODE_WPA2}; WiFiRetryPhase retry_phase_{WiFiRetryPhase::INITIAL_CONNECT}; uint8_t num_retried_{0}; // Index into sta_ array for the currently selected AP configuration (-1 = none selected) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 4e17c42f41..56e071404b 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -258,8 +258,17 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { if (ap.get_password().empty()) { conf.threshold.authmode = AUTH_OPEN; } else { - // Only allow auth modes with at least WPA - conf.threshold.authmode = AUTH_WPA_PSK; + // Set threshold based on configured minimum auth mode + // Note: ESP8266 doesn't support WPA3 + switch (this->min_auth_mode_) { + case WIFI_MIN_AUTH_MODE_WPA: + conf.threshold.authmode = AUTH_WPA_PSK; + break; + case WIFI_MIN_AUTH_MODE_WPA2: + case WIFI_MIN_AUTH_MODE_WPA3: // Fall back to WPA2 for ESP8266 + conf.threshold.authmode = AUTH_WPA2_PSK; + break; + } } conf.threshold.rssi = -127; #endif diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 08ecba3598..d3088c9a10 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -308,7 +308,18 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { if (ap.get_password().empty()) { conf.sta.threshold.authmode = WIFI_AUTH_OPEN; } else { - conf.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK; + // Set threshold based on configured minimum auth mode + switch (this->min_auth_mode_) { + case WIFI_MIN_AUTH_MODE_WPA: + conf.sta.threshold.authmode = WIFI_AUTH_WPA_PSK; + break; + case WIFI_MIN_AUTH_MODE_WPA2: + conf.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; + break; + case WIFI_MIN_AUTH_MODE_WPA3: + conf.sta.threshold.authmode = WIFI_AUTH_WPA3_PSK; + break; + } } #ifdef USE_WIFI_WPA2_EAP @@ -347,8 +358,6 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // The minimum rssi to accept in the fast scan mode conf.sta.threshold.rssi = -127; - conf.sta.threshold.authmode = WIFI_AUTH_OPEN; - wifi_config_t current_conf; esp_err_t err; err = esp_wifi_get_config(WIFI_IF_STA, ¤t_conf); diff --git a/tests/components/wifi/test.esp32-idf.yaml b/tests/components/wifi/test.esp32-idf.yaml index 91e235b9ce..827e4b17f7 100644 --- a/tests/components/wifi/test.esp32-idf.yaml +++ b/tests/components/wifi/test.esp32-idf.yaml @@ -2,6 +2,7 @@ psram: wifi: use_psram: true + min_auth_mode: WPA packages: - !include common.yaml diff --git a/tests/components/wifi/test.esp8266-ard.yaml b/tests/components/wifi/test.esp8266-ard.yaml index dade44d145..9cb0e3cf48 100644 --- a/tests/components/wifi/test.esp8266-ard.yaml +++ b/tests/components/wifi/test.esp8266-ard.yaml @@ -1 +1,5 @@ -<<: !include common.yaml +wifi: + min_auth_mode: WPA2 + +packages: + - !include common.yaml From 5dafaaced465413fb363f1896d79ea392ff0abc4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 Nov 2025 17:12:10 -0600 Subject: [PATCH 0182/1145] [wifi] Fix scan and connection failures after adapter restart (#11851) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../esp32_improv/esp32_improv_component.cpp | 2 +- .../improv_serial/improv_serial_component.cpp | 2 +- esphome/components/wifi/wifi_component.cpp | 85 ++++++++----------- esphome/components/wifi/wifi_component.h | 10 +-- 4 files changed, 39 insertions(+), 60 deletions(-) diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 2fa9d8f523..398b1d4251 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -336,7 +336,7 @@ void ESP32ImprovComponent::process_incoming_data_() { this->connecting_sta_ = sta; wifi::global_wifi_component->set_sta(sta); - wifi::global_wifi_component->start_connecting(sta, false); + wifi::global_wifi_component->start_connecting(sta); this->set_state_(improv::STATE_PROVISIONING); ESP_LOGD(TAG, "Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), command.password.c_str()); diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index 9d080ea98e..70260eeab3 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -231,7 +231,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command this->connecting_sta_ = sta; wifi::global_wifi_component->set_sta(sta); - wifi::global_wifi_component->start_connecting(sta, false); + wifi::global_wifi_component->start_connecting(sta); this->set_state_(improv::STATE_PROVISIONING); ESP_LOGD(TAG, "Received settings: SSID=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), command.password.c_str()); diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index d75ac971eb..ddba0558b4 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -197,6 +197,10 @@ static constexpr uint8_t WIFI_RETRY_COUNT_PER_SSID = 1; // Rationale: Fast connect prioritizes speed - try each AP once to find a working one quickly static constexpr uint8_t WIFI_RETRY_COUNT_PER_AP = 1; +/// Cooldown duration in milliseconds after adapter restart or repeated failures +/// Allows WiFi hardware to stabilize before next connection attempt +static constexpr uint32_t WIFI_COOLDOWN_DURATION_MS = 1000; + static constexpr uint8_t get_max_retries_for_phase(WiFiRetryPhase phase) { switch (phase) { case WiFiRetryPhase::INITIAL_CONNECT: @@ -275,7 +279,7 @@ int8_t WiFiComponent::find_next_hidden_sta_(int8_t start_index) { ESP_LOGD(TAG, "Hidden candidate " LOG_SECRET("'%s'") " at index %d", sta.get_ssid().c_str(), static_cast(i)); return static_cast(i); } - ESP_LOGD(TAG, "Skipping " LOG_SECRET("'%s'") " (visible in scan)", sta.get_ssid().c_str()); + ESP_LOGD(TAG, "Skipping hidden retry for visible network " LOG_SECRET("'%s'"), sta.get_ssid().c_str()); } // No hidden SSIDs found return -1; @@ -289,7 +293,7 @@ void WiFiComponent::start_initial_connection_() { this->selected_sta_index_ = 0; this->retry_phase_ = WiFiRetryPhase::EXPLICIT_HIDDEN; WiFiAP params = this->build_params_for_current_phase_(); - this->start_connecting(params, false); + this->start_connecting(params); } else { ESP_LOGI(TAG, "Starting scan"); this->start_scanning(); @@ -371,13 +375,13 @@ void WiFiComponent::start() { // Without saved data, try first configured network or use normal flow if (loaded_fast_connect) { ESP_LOGI(TAG, "Starting fast_connect (saved) " LOG_SECRET("'%s'"), params.get_ssid().c_str()); - this->start_connecting(params, false); + this->start_connecting(params); } else if (!this->sta_.empty() && !this->sta_[0].get_hidden()) { // No saved data, but have configured networks - try first non-hidden network ESP_LOGI(TAG, "Starting fast_connect (config) " LOG_SECRET("'%s'"), this->sta_[0].get_ssid().c_str()); this->selected_sta_index_ = 0; params = this->build_params_for_current_phase_(); - this->start_connecting(params, false); + this->start_connecting(params); } else { // No saved data and (no networks OR first is hidden) - use normal flow this->start_initial_connection_(); @@ -413,8 +417,11 @@ void WiFiComponent::start() { void WiFiComponent::restart_adapter() { ESP_LOGW(TAG, "Restarting adapter"); this->wifi_mode_(false, {}); - delay(100); // NOLINT + // Enter cooldown state to allow WiFi hardware to stabilize after restart // Don't set retry_phase_ or num_retried_ here - state machine handles transitions + this->state_ = WIFI_COMPONENT_STATE_COOLDOWN; + this->action_started_ = millis(); + this->error_from_callback_ = false; } void WiFiComponent::loop() { @@ -434,20 +441,12 @@ void WiFiComponent::loop() { switch (this->state_) { case WIFI_COMPONENT_STATE_COOLDOWN: { this->status_set_warning(LOG_STR("waiting to reconnect")); - if (millis() - this->action_started_ > 5000) { - // After cooldown, connect based on current retry phase - this->reset_selected_ap_to_first_if_invalid_(); - - // Check if we need to trigger a scan first - if (this->needs_scan_results_() && !this->all_networks_hidden_()) { - // Need scan results or no matching networks found - scan/rescan - ESP_LOGD(TAG, "Scanning required for phase %s", LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_))); - this->start_scanning(); - } else { - // Have everything we need to connect (or all networks are hidden, skip scanning) - WiFiAP params = this->build_params_for_current_phase_(); - this->start_connecting(params, false); - } + if (now - this->action_started_ > WIFI_COOLDOWN_DURATION_MS) { + // After cooldown we either restarted the adapter because of + // a failure, or something tried to connect over and over + // so we entered cooldown. In both cases we call + // check_connecting_finished to continue the state machine. + this->check_connecting_finished(); } break; } @@ -456,8 +455,7 @@ void WiFiComponent::loop() { this->check_scanning_finished(); break; } - case WIFI_COMPONENT_STATE_STA_CONNECTING: - case WIFI_COMPONENT_STATE_STA_CONNECTING_2: { + case WIFI_COMPONENT_STATE_STA_CONNECTING: { this->status_set_warning(LOG_STR("associating to network")); this->check_connecting_finished(); break; @@ -666,7 +664,7 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa this->set_sta(sta); } -void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { +void WiFiComponent::start_connecting(const WiFiAP &ap) { // Log connection attempt at INFO level with priority std::string bssid_formatted; int8_t priority = 0; @@ -730,14 +728,11 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { if (!this->wifi_sta_connect_(ap)) { ESP_LOGE(TAG, "wifi_sta_connect_ failed"); - this->retry_connect(); - return; - } - - if (!two) { - this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING; + // Enter cooldown to allow WiFi hardware to stabilize + // (immediate failure suggests hardware not ready, different from connection timeout) + this->state_ = WIFI_COMPONENT_STATE_COOLDOWN; } else { - this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING_2; + this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING; } this->action_started_ = millis(); } @@ -1006,8 +1001,6 @@ void WiFiComponent::check_scanning_finished() { this->transition_to_phase_(WiFiRetryPhase::RETRY_HIDDEN); // If no hidden networks to try, skip connection attempt (will be handled on next loop) if (this->selected_sta_index_ == -1) { - this->state_ = WIFI_COMPONENT_STATE_COOLDOWN; - this->action_started_ = millis(); return; } // Now start connection attempt in hidden mode @@ -1020,7 +1013,7 @@ void WiFiComponent::check_scanning_finished() { WiFiAP params = this->build_params_for_current_phase_(); // Ensure we're in SCAN_CONNECTING phase when connecting with scan results // (needed when scan was started directly without transition_to_phase_, e.g., initial scan) - this->start_connecting(params, false); + this->start_connecting(params); } void WiFiComponent::dump_config() { @@ -1094,7 +1087,7 @@ void WiFiComponent::check_connecting_finished() { } if (this->error_from_callback_) { - ESP_LOGW(TAG, "Connecting to network failed"); + ESP_LOGW(TAG, "Connecting to network failed (callback)"); this->retry_connect(); return; } @@ -1456,15 +1449,13 @@ void WiFiComponent::advance_to_next_target_or_increment_retry_() { void WiFiComponent::retry_connect() { this->log_and_adjust_priority_for_failed_connect_(); - delay(10); - // Determine next retry phase based on current state WiFiRetryPhase current_phase = this->retry_phase_; WiFiRetryPhase next_phase = this->determine_next_phase_(); // Handle phase transitions (transition_to_phase_ handles same-phase no-op internally) if (this->transition_to_phase_(next_phase)) { - return; // Wait for scan to complete + return; // Scan started or adapter restarted (which sets its own state) } if (next_phase == current_phase) { @@ -1473,22 +1464,14 @@ void WiFiComponent::retry_connect() { this->error_from_callback_ = false; - if (this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTING) { - yield(); - // Check if we have a valid target before building params - // After exhausting all networks in a phase, selected_sta_index_ may be -1 - // In that case, skip connection and let next wifi_loop() handle phase transition - if (this->selected_sta_index_ >= 0) { - this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING_2; - WiFiAP params = this->build_params_for_current_phase_(); - this->start_connecting(params, true); - return; - } - // No valid target - fall through to set state to allow phase transition + yield(); + // Check if we have a valid target before building params + // After exhausting all networks in a phase, selected_sta_index_ may be -1 + // In that case, skip connection and let next wifi_loop() handle phase transition + if (this->selected_sta_index_ >= 0) { + WiFiAP params = this->build_params_for_current_phase_(); + this->start_connecting(params); } - - this->state_ = WIFI_COMPONENT_STATE_COOLDOWN; - this->action_started_ = millis(); } void WiFiComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 02d6d984f1..ef0372535a 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -74,12 +74,6 @@ enum WiFiComponentState : uint8_t { WIFI_COMPONENT_STATE_STA_SCANNING, /** WiFi is in STA(+AP) mode and currently connecting to an AP. */ WIFI_COMPONENT_STATE_STA_CONNECTING, - /** WiFi is in STA(+AP) mode and currently connecting to an AP a second time. - * - * This is required because for some reason ESPs don't like to connect to WiFi APs directly after - * a scan. - * */ - WIFI_COMPONENT_STATE_STA_CONNECTING_2, /** WiFi is in STA(+AP) mode and successfully connected. */ WIFI_COMPONENT_STATE_STA_CONNECTED, /** WiFi is in AP-only mode and internal AP is already enabled. */ @@ -269,7 +263,9 @@ class WiFiComponent : public Component { bool is_disabled(); void start_scanning(); void check_scanning_finished(); - void start_connecting(const WiFiAP &ap, bool two); + void start_connecting(const WiFiAP &ap); + // Backward compatibility overload - ignores 'two' parameter + void start_connecting(const WiFiAP &ap, bool /* two */) { this->start_connecting(ap); } void check_connecting_finished(); From 572fae5c7d49c479f0006420e8bb656d89948480 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 Nov 2025 17:12:53 -0600 Subject: [PATCH 0183/1145] [wifi] Restore two-attempt BSSID filtering for mesh networks (#11844) --- esphome/components/wifi/wifi_component.cpp | 26 +++++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index ddba0558b4..e79d821ba7 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1341,6 +1341,11 @@ void WiFiComponent::clear_priorities_if_all_min_() { /// - Other phases: Uses BSSID from config if explicitly specified by user or fast_connect /// /// If no BSSID is available (SSID-only connection), priority adjustment is skipped. +/// +/// IMPORTANT: Priority is only decreased on the LAST attempt for a BSSID in SCAN_CONNECTING phase. +/// This prevents false positives from transient WiFi stack state issues after scanning. +/// Single failures don't necessarily mean the AP is bad - two genuine failures provide +/// higher confidence before degrading priority and skipping the BSSID in future scans. void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { // Determine which BSSID we tried to connect to optional failed_bssid; @@ -1357,12 +1362,6 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { return; // No BSSID to penalize } - // Decrease priority to avoid repeatedly trying the same failed BSSID - int8_t old_priority = this->get_sta_priority(failed_bssid.value()); - int8_t new_priority = - (old_priority > std::numeric_limits::min()) ? (old_priority - 1) : std::numeric_limits::min(); - this->set_sta_priority(failed_bssid.value(), new_priority); - // Get SSID for logging std::string ssid; if (this->retry_phase_ == WiFiRetryPhase::SCAN_CONNECTING && !this->scan_result_.empty()) { @@ -1371,6 +1370,21 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { ssid = config->get_ssid(); } + // Only decrease priority on the last attempt for this phase + // This prevents false positives from transient WiFi stack issues + uint8_t max_retries = get_max_retries_for_phase(this->retry_phase_); + bool is_last_attempt = (this->num_retried_ + 1 >= max_retries); + + // Decrease priority only on last attempt to avoid false positives from transient failures + int8_t old_priority = this->get_sta_priority(failed_bssid.value()); + int8_t new_priority = old_priority; + + if (is_last_attempt) { + // Decrease priority, but clamp to int8_t::min to prevent overflow + new_priority = + (old_priority > std::numeric_limits::min()) ? (old_priority - 1) : std::numeric_limits::min(); + this->set_sta_priority(failed_bssid.value(), new_priority); + } ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", ssid.c_str(), format_mac_address_pretty(failed_bssid.value().data()).c_str(), old_priority, new_priority); From 79a44449283407456694354e7dd3f4c8d22773ad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 Nov 2025 17:27:08 -0600 Subject: [PATCH 0184/1145] [wifi] Conditionally compile manual_ip to save 24-72 bytes RAM (#11833) --- esphome/components/wifi/__init__.py | 10 ++++++++++ esphome/components/wifi/wifi_component.cpp | 11 ++++++++++- esphome/components/wifi/wifi_component.h | 6 ++++++ .../components/wifi/wifi_component_esp8266.cpp | 13 +++++++++++++ .../components/wifi/wifi_component_esp_idf.cpp | 13 +++++++++++++ .../components/wifi/wifi_component_libretiny.cpp | 13 +++++++++++++ esphome/components/wifi/wifi_component_pico_w.cpp | 12 ++++++++++++ esphome/core/defines.h | 1 + tests/components/wifi/test.esp32-idf.yaml | 15 +++++++++++++++ 9 files changed, 93 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index c42af23252..28db698a43 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -425,6 +425,8 @@ async def to_code(config): # Track if any network uses Enterprise authentication has_eap = False + # Track if any network uses manual IP + has_manual_ip = False # Initialize FixedVector with the count of networks networks = config.get(CONF_NETWORKS, []) @@ -438,11 +440,15 @@ async def to_code(config): for network in networks: if CONF_EAP in network: has_eap = True + if network.get(CONF_MANUAL_IP) or config.get(CONF_MANUAL_IP): + has_manual_ip = True cg.with_local_variable(network[CONF_ID], WiFiAP(), add_sta, network) if CONF_AP in config: conf = config[CONF_AP] ip_config = conf.get(CONF_MANUAL_IP) + if ip_config: + has_manual_ip = True cg.with_local_variable( conf[CONF_ID], WiFiAP(), @@ -458,6 +464,10 @@ async def to_code(config): if CORE.is_esp32: add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT", has_eap) + # Only define USE_WIFI_MANUAL_IP if any AP uses manual IP + if has_manual_ip: + cg.add_define("USE_WIFI_MANUAL_IP") + cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE])) if CONF_MIN_AUTH_MODE in config: diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e79d821ba7..817419107f 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -569,6 +569,7 @@ void WiFiComponent::setup_ap_config_() { " IP Address: %s", this->ap_.get_ssid().c_str(), this->ap_.get_password().c_str(), ip_address.c_str()); +#ifdef USE_WIFI_MANUAL_IP auto manual_ip = this->ap_.get_manual_ip(); if (manual_ip.has_value()) { ESP_LOGCONFIG(TAG, @@ -578,6 +579,7 @@ void WiFiComponent::setup_ap_config_() { manual_ip->static_ip.str().c_str(), manual_ip->gateway.str().c_str(), manual_ip->subnet.str().c_str()); } +#endif if (!this->has_sta()) { this->state_ = WIFI_COMPONENT_STATE_AP; @@ -716,11 +718,14 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { } else { ESP_LOGV(TAG, " Channel not set"); } +#ifdef USE_WIFI_MANUAL_IP if (ap.get_manual_ip().has_value()) { ManualIP m = *ap.get_manual_ip(); ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.str().c_str(), m.gateway.str().c_str(), m.subnet.str().c_str(), m.dns1.str().c_str(), m.dns2.str().c_str()); - } else { + } else +#endif + { ESP_LOGV(TAG, " Using DHCP IP"); } ESP_LOGV(TAG, " Hidden: %s", YESNO(ap.get_hidden())); @@ -1577,7 +1582,9 @@ void WiFiAP::set_password(const std::string &password) { this->password_ = passw void WiFiAP::set_eap(optional eap_auth) { this->eap_ = std::move(eap_auth); } #endif void WiFiAP::set_channel(optional channel) { this->channel_ = channel; } +#ifdef USE_WIFI_MANUAL_IP void WiFiAP::set_manual_ip(optional manual_ip) { this->manual_ip_ = manual_ip; } +#endif void WiFiAP::set_hidden(bool hidden) { this->hidden_ = hidden; } const std::string &WiFiAP::get_ssid() const { return this->ssid_; } const optional &WiFiAP::get_bssid() const { return this->bssid_; } @@ -1586,7 +1593,9 @@ const std::string &WiFiAP::get_password() const { return this->password_; } const optional &WiFiAP::get_eap() const { return this->eap_; } #endif const optional &WiFiAP::get_channel() const { return this->channel_; } +#ifdef USE_WIFI_MANUAL_IP const optional &WiFiAP::get_manual_ip() const { return this->manual_ip_; } +#endif bool WiFiAP::get_hidden() const { return this->hidden_; } WiFiScanResult::WiFiScanResult(const bssid_t &bssid, std::string ssid, uint8_t channel, int8_t rssi, bool with_auth, diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index ef0372535a..713e6f223f 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -152,7 +152,9 @@ class WiFiAP { #endif // USE_WIFI_WPA2_EAP void set_channel(optional channel); void set_priority(int8_t priority) { priority_ = priority; } +#ifdef USE_WIFI_MANUAL_IP void set_manual_ip(optional manual_ip); +#endif void set_hidden(bool hidden); const std::string &get_ssid() const; const optional &get_bssid() const; @@ -162,7 +164,9 @@ class WiFiAP { #endif // USE_WIFI_WPA2_EAP const optional &get_channel() const; int8_t get_priority() const { return priority_; } +#ifdef USE_WIFI_MANUAL_IP const optional &get_manual_ip() const; +#endif bool get_hidden() const; protected: @@ -172,7 +176,9 @@ class WiFiAP { #ifdef USE_WIFI_WPA2_EAP optional eap_; #endif // USE_WIFI_WPA2_EAP +#ifdef USE_WIFI_MANUAL_IP optional manual_ip_; +#endif optional channel_; int8_t priority_{0}; bool hidden_{false}; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 56e071404b..bcb5dc4cf7 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -282,9 +282,15 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { return false; } +#ifdef USE_WIFI_MANUAL_IP if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) { return false; } +#else + if (!this->wifi_sta_ip_config_({})) { + return false; + } +#endif // setup enterprise authentication if required #ifdef USE_WIFI_WPA2_EAP @@ -832,10 +838,17 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return false; } +#ifdef USE_WIFI_MANUAL_IP if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) { ESP_LOGV(TAG, "wifi_ap_ip_config_ failed"); return false; } +#else + if (!this->wifi_ap_ip_config_({})) { + ESP_LOGV(TAG, "wifi_ap_ip_config_ failed"); + return false; + } +#endif return true; } diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index d3088c9a10..fd7e85fb6b 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -380,9 +380,15 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { return false; } +#ifdef USE_WIFI_MANUAL_IP if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) { return false; } +#else + if (!this->wifi_sta_ip_config_({})) { + return false; + } +#endif // setup enterprise authentication if required #ifdef USE_WIFI_WPA2_EAP @@ -994,10 +1000,17 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return false; } +#ifdef USE_WIFI_MANUAL_IP if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) { ESP_LOGE(TAG, "wifi_ap_ip_config_ failed:"); return false; } +#else + if (!this->wifi_ap_ip_config_({})) { + ESP_LOGE(TAG, "wifi_ap_ip_config_ failed:"); + return false; + } +#endif return true; } diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 45e2fba82a..2946b9e831 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -112,9 +112,15 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { WiFi.disconnect(); } +#ifdef USE_WIFI_MANUAL_IP if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) { return false; } +#else + if (!this->wifi_sta_ip_config_({})) { + return false; + } +#endif this->wifi_apply_hostname_(); @@ -445,10 +451,17 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { if (!this->wifi_mode_({}, true)) return false; +#ifdef USE_WIFI_MANUAL_IP if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) { ESP_LOGV(TAG, "wifi_ap_ip_config_ failed"); return false; } +#else + if (!this->wifi_ap_ip_config_({})) { + ESP_LOGV(TAG, "wifi_ap_ip_config_ failed"); + return false; + } +#endif yield(); diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index bf15892cd5..7025ba16bd 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -55,8 +55,13 @@ bool WiFiComponent::wifi_apply_power_save_() { bool WiFiComponent::wifi_apply_output_power_(float output_power) { return true; } bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { +#ifdef USE_WIFI_MANUAL_IP if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) return false; +#else + if (!this->wifi_sta_ip_config_({})) + return false; +#endif auto ret = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().c_str()); if (ret != WL_CONNECTED) @@ -161,10 +166,17 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { if (!this->wifi_mode_({}, true)) return false; +#ifdef USE_WIFI_MANUAL_IP if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) { ESP_LOGV(TAG, "wifi_ap_ip_config_ failed"); return false; } +#else + if (!this->wifi_ap_ip_config_({})) { + ESP_LOGV(TAG, "wifi_ap_ip_config_ failed"); + return false; + } +#endif WiFi.beginAP(ap.get_ssid().c_str(), ap.get_password().c_str(), ap.get_channel().value_or(1)); diff --git a/esphome/core/defines.h b/esphome/core/defines.h index c522a8ec62..41f4b28cd5 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -144,6 +144,7 @@ #define USE_TIME_TIMEZONE #define USE_WIFI #define USE_WIFI_AP +#define USE_WIFI_MANUAL_IP #define USE_WIREGUARD #endif diff --git a/tests/components/wifi/test.esp32-idf.yaml b/tests/components/wifi/test.esp32-idf.yaml index 827e4b17f7..6b3ef20963 100644 --- a/tests/components/wifi/test.esp32-idf.yaml +++ b/tests/components/wifi/test.esp32-idf.yaml @@ -3,6 +3,21 @@ psram: wifi: use_psram: true min_auth_mode: WPA + manual_ip: + static_ip: 192.168.1.100 + gateway: 192.168.1.1 + subnet: 255.255.255.0 + dns1: 1.1.1.1 + dns2: 8.8.8.8 + ap: + ssid: Fallback AP + password: fallback_password + manual_ip: + static_ip: 192.168.4.1 + gateway: 192.168.4.1 + subnet: 255.255.255.0 + +captive_portal: packages: - !include common.yaml From d7fa131a8a31b3dab42acfe9685532b4b72ec141 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Tue, 11 Nov 2025 19:43:06 -0500 Subject: [PATCH 0185/1145] [network, psram, speaker wifi] Use CORE.data to enable high performance networking (#11812) --- esphome/components/network/__init__.py | 123 ++++++++++++++++++ esphome/components/psram/__init__.py | 37 +++++- .../speaker/media_player/__init__.py | 36 +++-- esphome/components/wifi/__init__.py | 58 ++++++++- tests/components/network/test.esp32-idf.yaml | 3 + 5 files changed, 235 insertions(+), 22 deletions(-) diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index 1d62b661ca..d7a51fb0c6 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -1,7 +1,9 @@ import ipaddress +import logging import esphome.codegen as cg from esphome.components.esp32 import add_idf_sdkconfig_option +from esphome.components.psram import is_guaranteed as psram_is_guaranteed import esphome.config_validation as cv from esphome.const import CONF_ENABLE_IPV6, CONF_MIN_IPV6_ADDR_COUNT from esphome.core import CORE, CoroPriority, coroutine_with_priority @@ -9,6 +11,13 @@ from esphome.core import CORE, CoroPriority, coroutine_with_priority CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["mdns"] +_LOGGER = logging.getLogger(__name__) + +# High performance networking tracking infrastructure +# Components can request high performance networking and this configures lwip and WiFi settings +KEY_HIGH_PERFORMANCE_NETWORKING = "high_performance_networking" +CONF_ENABLE_HIGH_PERFORMANCE = "enable_high_performance" + network_ns = cg.esphome_ns.namespace("network") IPAddress = network_ns.class_("IPAddress") @@ -47,6 +56,55 @@ def ip_address_literal(ip: str | int | None) -> cg.MockObj: return IPAddress(str(ip)) +def require_high_performance_networking() -> None: + """Request high performance networking for network and WiFi. + + Call this from components that need optimized network performance for streaming + or high-throughput data transfer. This enables high performance mode which + configures both lwip TCP settings and WiFi driver settings for improved + network performance. + + Settings applied (ESP-IDF only): + - lwip: Larger TCP buffers, windows, and mailbox sizes + - WiFi: Increased RX/TX buffers, AMPDU aggregation, PSRAM allocation (set by wifi component) + + Configuration is PSRAM-aware: + - With PSRAM guaranteed: Aggressive settings (512 RX buffers, 512KB TCP windows) + - Without PSRAM: Conservative optimized settings (64 buffers, 65KB TCP windows) + + Example: + from esphome.components import network + + def _request_high_performance_networking(config): + network.require_high_performance_networking() + return config + + CONFIG_SCHEMA = cv.All( + ..., + _request_high_performance_networking, + ) + """ + # Only set up once (idempotent - multiple components can call this) + if not CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False): + CORE.data[KEY_HIGH_PERFORMANCE_NETWORKING] = True + + +def has_high_performance_networking() -> bool: + """Check if high performance networking mode is enabled. + + Returns True when high performance networking has been requested by a + component or explicitly enabled in the network configuration. This indicates + that lwip and WiFi will use optimized buffer sizes and settings. + + This function should be called during code generation (to_code phase) by + components that need to apply performance-related settings. + + Returns: + bool: True if high performance networking is enabled, False otherwise + """ + return CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False) + + CONFIG_SCHEMA = cv.Schema( { cv.SplitDefault( @@ -71,6 +129,7 @@ CONFIG_SCHEMA = cv.Schema( ), ), cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int, + cv.Optional(CONF_ENABLE_HIGH_PERFORMANCE): cv.All(cv.boolean, cv.only_on_esp32), } ) @@ -80,6 +139,70 @@ async def to_code(config): cg.add_define("USE_NETWORK") if CORE.using_arduino and CORE.is_esp32: cg.add_library("Networking", None) + + # Apply high performance networking settings + # Config can explicitly enable/disable, or default to component-driven behavior + enable_high_perf = config.get(CONF_ENABLE_HIGH_PERFORMANCE) + component_requested = CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False) + + # Explicit config overrides component request + should_enable = ( + enable_high_perf if enable_high_perf is not None else component_requested + ) + + # Log when user explicitly disables but a component requested it + if enable_high_perf is False and component_requested: + _LOGGER.info( + "High performance networking disabled by user configuration (overriding component request)" + ) + + if CORE.is_esp32 and CORE.using_esp_idf and should_enable: + # Check if PSRAM is guaranteed (set by psram component during final validation) + psram_guaranteed = psram_is_guaranteed() + + if psram_guaranteed: + _LOGGER.info( + "Applying high-performance lwip settings (PSRAM guaranteed): 512KB TCP windows, 512 mailbox sizes" + ) + # PSRAM is guaranteed - use aggressive settings + # Higher maximum values are allowed because CONFIG_LWIP_WND_SCALE is set to true + # CONFIG_LWIP_WND_SCALE can only be enabled if CONFIG_SPIRAM_IGNORE_NOTFOUND isn't set + # Based on https://github.com/espressif/esp-adf/issues/297#issuecomment-783811702 + + # Enable window scaling for much larger TCP windows + add_idf_sdkconfig_option("CONFIG_LWIP_WND_SCALE", True) + add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RCV_SCALE", 3) + + # Large TCP buffers and windows (requires PSRAM) + add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", 65534) + add_idf_sdkconfig_option("CONFIG_LWIP_TCP_WND_DEFAULT", 512000) + + # Large mailboxes for high throughput + add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 512) + add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 512) + + # TCP connection limits + add_idf_sdkconfig_option("CONFIG_LWIP_MAX_ACTIVE_TCP", 16) + add_idf_sdkconfig_option("CONFIG_LWIP_MAX_LISTENING_TCP", 16) + + # TCP optimizations + add_idf_sdkconfig_option("CONFIG_LWIP_TCP_MAXRTX", 12) + add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SYNMAXRTX", 6) + add_idf_sdkconfig_option("CONFIG_LWIP_TCP_MSS", 1436) + add_idf_sdkconfig_option("CONFIG_LWIP_TCP_MSL", 60000) + add_idf_sdkconfig_option("CONFIG_LWIP_TCP_OVERSIZE_MSS", True) + add_idf_sdkconfig_option("CONFIG_LWIP_TCP_QUEUE_OOSEQ", True) + else: + _LOGGER.info( + "Applying optimized lwip settings: 65KB TCP windows, 64 mailbox sizes" + ) + # PSRAM not guaranteed - use more conservative, but still optimized settings + # Based on https://github.com/espressif/esp-idf/blob/release/v5.4/examples/wifi/iperf/sdkconfig.defaults.esp32 + add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", 65534) + add_idf_sdkconfig_option("CONFIG_LWIP_TCP_WND_DEFAULT", 65534) + add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 64) + add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 64) + if (enable_ipv6 := config.get(CONF_ENABLE_IPV6, None)) is not None: cg.add_define("USE_NETWORK_IPV6", enable_ipv6) if enable_ipv6: diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index 11c238c1bf..c50c599855 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -35,6 +35,9 @@ DOMAIN = "psram" DEPENDENCIES = [PLATFORM_ESP32] +# PSRAM availability tracking for cross-component coordination +KEY_PSRAM_GUARANTEED = "psram_guaranteed" + _LOGGER = logging.getLogger(__name__) psram_ns = cg.esphome_ns.namespace(DOMAIN) @@ -71,6 +74,23 @@ def supported() -> bool: return variant in SPIRAM_MODES +def is_guaranteed() -> bool: + """Check if PSRAM is guaranteed to be available. + + Returns True when PSRAM is configured with both 'disabled: false' and + 'ignore_not_found: false', meaning the device will fail to boot if PSRAM + is not found. This ensures safe use of high buffer configurations that + depend on PSRAM. + + This function should be called during code generation (to_code phase) by + components that need to know PSRAM availability for configuration decisions. + + Returns: + bool: True if PSRAM is guaranteed, False otherwise + """ + return CORE.data.get(KEY_PSRAM_GUARANTEED, False) + + def validate_psram_mode(config): esp32_config = fv.full_config.get()[PLATFORM_ESP32] if config[CONF_SPEED] == "120MHZ": @@ -131,7 +151,22 @@ def get_config_schema(config): CONFIG_SCHEMA = get_config_schema -FINAL_VALIDATE_SCHEMA = validate_psram_mode + +def _store_psram_guaranteed(config): + """Store PSRAM guaranteed status in CORE.data for other components. + + PSRAM is "guaranteed" when it will fail if not found, ensuring safe use + of high buffer configurations in network/wifi components. + + Called during final validation to ensure the flag is available + before any to_code() functions run. + """ + psram_guaranteed = not config[CONF_DISABLED] and not config[CONF_IGNORE_NOT_FOUND] + CORE.data[KEY_PSRAM_GUARANTEED] = psram_guaranteed + return config + + +FINAL_VALIDATE_SCHEMA = cv.All(validate_psram_mode, _store_psram_guaranteed) async def to_code(config): diff --git a/esphome/components/speaker/media_player/__init__.py b/esphome/components/speaker/media_player/__init__.py index e50656e723..062bff92f8 100644 --- a/esphome/components/speaker/media_player/__init__.py +++ b/esphome/components/speaker/media_player/__init__.py @@ -6,7 +6,7 @@ from pathlib import Path from esphome import automation, external_files import esphome.codegen as cg -from esphome.components import audio, esp32, media_player, psram, speaker +from esphome.components import audio, esp32, media_player, network, psram, speaker import esphome.config_validation as cv from esphome.const import ( CONF_BUFFER_SIZE, @@ -32,6 +32,7 @@ _LOGGER = logging.getLogger(__name__) AUTO_LOAD = ["audio"] +DEPENDENCIES = ["network"] CODEOWNERS = ["@kahrendt", "@synesthesiam"] DOMAIN = "media_player" @@ -280,6 +281,18 @@ PIPELINE_SCHEMA = cv.Schema( } ) + +def _request_high_performance_networking(config): + """Request high performance networking for streaming media. + + Speaker media player streams audio data, so it always benefits from + optimized WiFi and lwip settings regardless of codec support. + Called during config validation to ensure flags are set before to_code(). + """ + network.require_high_performance_networking() + return config + + CONFIG_SCHEMA = cv.All( media_player.media_player_schema(SpeakerMediaPlayer).extend( { @@ -304,6 +317,7 @@ CONFIG_SCHEMA = cv.All( ), cv.only_with_esp_idf, _validate_repeated_speaker, + _request_high_performance_networking, ) @@ -321,28 +335,10 @@ FINAL_VALIDATE_SCHEMA = cv.All( async def to_code(config): if CORE.data[DOMAIN][config[CONF_ID].id][CONF_CODEC_SUPPORT_ENABLED]: - # Compile all supported audio codecs and optimize the wifi settings - + # Compile all supported audio codecs cg.add_define("USE_AUDIO_FLAC_SUPPORT", True) cg.add_define("USE_AUDIO_MP3_SUPPORT", True) - # Based on https://github.com/espressif/esp-idf/blob/release/v5.4/examples/wifi/iperf/sdkconfig.defaults.esp32 - esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM", 16) - esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM", 64) - esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM", 64) - esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_TX_ENABLED", True) - esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BA_WIN", 32) - esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_RX_ENABLED", True) - esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_RX_BA_WIN", 32) - - esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", 65534) - esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_WND_DEFAULT", 65534) - esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 64) - esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 64) - - # Allocate wifi buffers in PSRAM - esp32.add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True) - var = await media_player.new_media_player(config) await cg.register_component(var, config) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 28db698a43..4dbb425e4b 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -5,7 +5,11 @@ from esphome.automation import Condition import esphome.codegen as cg from esphome.components.const import CONF_USE_PSRAM from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant -from esphome.components.network import ip_address_literal +from esphome.components.network import ( + has_high_performance_networking, + ip_address_literal, +) +from esphome.components.psram import is_guaranteed as psram_is_guaranteed from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.config_validation import only_with_esp_idf @@ -56,6 +60,8 @@ _LOGGER = logging.getLogger(__name__) AUTO_LOAD = ["network"] +_LOGGER = logging.getLogger(__name__) + NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2, const.VARIANT_ESP32P4] CONF_SAVE = "save" CONF_MIN_AUTH_MODE = "min_auth_mode" @@ -496,6 +502,56 @@ async def to_code(config): if config.get(CONF_USE_PSRAM): add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True) + + # Apply high performance WiFi settings if high performance networking is enabled + if CORE.is_esp32 and CORE.using_esp_idf and has_high_performance_networking(): + # Check if PSRAM is guaranteed (set by psram component during final validation) + psram_guaranteed = psram_is_guaranteed() + + # Always allocate WiFi buffers in PSRAM if available + add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True) + + if psram_guaranteed: + _LOGGER.info( + "Applying high-performance WiFi settings (PSRAM guaranteed): 512 RX buffers, 32 TX buffers" + ) + # PSRAM is guaranteed - use aggressive settings + # Higher maximum values are allowed because CONFIG_LWIP_WND_SCALE is set to true in networking component + # Based on https://github.com/espressif/esp-adf/issues/297#issuecomment-783811702 + + # Large dynamic RX buffers (requires PSRAM) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM", 16) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM", 512) + + # Static TX buffers for better performance + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_TX_BUFFER", True) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BUFFER_TYPE", 0) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_CACHE_TX_BUFFER_NUM", 32) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM", 8) + + # AMPDU settings optimized for PSRAM + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_TX_ENABLED", True) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BA_WIN", 16) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_RX_ENABLED", True) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_RX_BA_WIN", 32) + else: + _LOGGER.info( + "Applying optimized WiFi settings: 64 RX buffers, 64 TX buffers" + ) + # PSRAM not guaranteed - use more conservative, but still optimized settings + # Based on https://github.com/espressif/esp-idf/blob/release/v5.4/examples/wifi/iperf/sdkconfig.defaults.esp32 + + # Standard buffer counts + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM", 16) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM", 64) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM", 64) + + # Standard AMPDU settings + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_TX_ENABLED", True) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BA_WIN", 32) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_RX_ENABLED", True) + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_RX_BA_WIN", 32) + cg.add_define("USE_WIFI") # must register before OTA safe mode check diff --git a/tests/components/network/test.esp32-idf.yaml b/tests/components/network/test.esp32-idf.yaml index dade44d145..7c01bafa0d 100644 --- a/tests/components/network/test.esp32-idf.yaml +++ b/tests/components/network/test.esp32-idf.yaml @@ -1 +1,4 @@ <<: !include common.yaml + +network: + enable_high_performance: true From a93887a79041b7f66806e27e944c90c8a2a0a860 Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Tue, 14 Oct 2025 21:29:41 -0700 Subject: [PATCH 0186/1145] [const] Add CONF_ROWS (#11249) --- esphome/components/lvgl/defines.py | 1 - esphome/components/lvgl/widgets/buttonmatrix.py | 3 +-- esphome/components/matrix_keypad/__init__.py | 3 +-- esphome/const.py | 1 + 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index d2b0977e89..7fbb6de071 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -504,7 +504,6 @@ CONF_RESUME_ON_INPUT = "resume_on_input" CONF_RIGHT_BUTTON = "right_button" CONF_ROLLOVER = "rollover" CONF_ROOT_BACK_BTN = "root_back_btn" -CONF_ROWS = "rows" CONF_SCALE_LINES = "scale_lines" CONF_SCROLLBAR_MODE = "scrollbar_mode" CONF_SELECTED_INDEX = "selected_index" diff --git a/esphome/components/lvgl/widgets/buttonmatrix.py b/esphome/components/lvgl/widgets/buttonmatrix.py index c6b6d2440f..baeb1c8e3e 100644 --- a/esphome/components/lvgl/widgets/buttonmatrix.py +++ b/esphome/components/lvgl/widgets/buttonmatrix.py @@ -2,7 +2,7 @@ from esphome import automation import esphome.codegen as cg from esphome.components.key_provider import KeyProvider import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_ITEMS, CONF_TEXT, CONF_WIDTH +from esphome.const import CONF_ID, CONF_ITEMS, CONF_ROWS, CONF_TEXT, CONF_WIDTH from esphome.cpp_generator import MockObj from ..automation import action_to_code @@ -15,7 +15,6 @@ from ..defines import ( CONF_ONE_CHECKED, CONF_PAD_COLUMN, CONF_PAD_ROW, - CONF_ROWS, CONF_SELECTED, ) from ..helpers import lvgl_components_required diff --git a/esphome/components/matrix_keypad/__init__.py b/esphome/components/matrix_keypad/__init__.py index f7a1d622a1..2e123323a0 100644 --- a/esphome/components/matrix_keypad/__init__.py +++ b/esphome/components/matrix_keypad/__init__.py @@ -2,7 +2,7 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import key_provider import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_TRIGGER_ID +from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_ROWS, CONF_TRIGGER_ID CODEOWNERS = ["@ssieb"] @@ -19,7 +19,6 @@ MatrixKeyTrigger = matrix_keypad_ns.class_( ) CONF_KEYPAD_ID = "keypad_id" -CONF_ROWS = "rows" CONF_COLUMNS = "columns" CONF_KEYS = "keys" CONF_DEBOUNCE_TIME = "debounce_time" diff --git a/esphome/const.py b/esphome/const.py index 9e8ec487b5..bfd772c539 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -837,6 +837,7 @@ CONF_RMT_CHANNEL = "rmt_channel" CONF_RMT_SYMBOLS = "rmt_symbols" CONF_ROTATION = "rotation" CONF_ROW = "row" +CONF_ROWS = "rows" CONF_RS_PIN = "rs_pin" CONF_RTD_NOMINAL_RESISTANCE = "rtd_nominal_resistance" CONF_RTD_WIRES = "rtd_wires" From 9326d78439b4ceec645ba85423abafab610d5e19 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 26 Oct 2025 09:00:08 -0400 Subject: [PATCH 0187/1145] [core] Don't allow python 3.14 (#11527) --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b7b4a48d7e..49598d434d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,9 @@ classifiers = [ "Programming Language :: Python :: 3", "Topic :: Home Automation", ] -requires-python = ">=3.11.0" + +# Python 3.14 is currently not supported by IDF <= 5.5.1, see https://github.com/esphome/esphome/issues/11502 +requires-python = ">=3.11.0,<3.14" dynamic = ["dependencies", "optional-dependencies", "version"] From 87f79290ba2fea8d0c58de18d7a44c2adf6ee389 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 4 Nov 2025 09:23:45 +1000 Subject: [PATCH 0188/1145] [usb_uart] Fixes for transfer queue allocation (#11548) --- esphome/components/usb_host/usb_host.h | 12 ++--- .../components/usb_host/usb_host_client.cpp | 54 +++++++++---------- esphome/components/usb_uart/usb_uart.cpp | 22 +++++--- tests/components/usb_uart/common.yaml | 3 ++ 4 files changed, 51 insertions(+), 40 deletions(-) diff --git a/esphome/components/usb_host/usb_host.h b/esphome/components/usb_host/usb_host.h index 43b24a54a5..31bdde2df8 100644 --- a/esphome/components/usb_host/usb_host.h +++ b/esphome/components/usb_host/usb_host.h @@ -55,7 +55,7 @@ static const uint8_t USB_DIR_IN = 1 << 7; static const uint8_t USB_DIR_OUT = 0; static const size_t SETUP_PACKET_SIZE = 8; -static const size_t MAX_REQUESTS = USB_HOST_MAX_REQUESTS; // maximum number of outstanding requests possible. +static constexpr size_t MAX_REQUESTS = USB_HOST_MAX_REQUESTS; // maximum number of outstanding requests possible. static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be between 1 and 32"); // Select appropriate bitmask type for tracking allocation of TransferRequest slots. @@ -65,6 +65,7 @@ static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be bet // This is tied to the static_assert above, which enforces MAX_REQUESTS is between 1 and 32. // If MAX_REQUESTS is increased above 32, this logic and the static_assert must be updated. using trq_bitmask_t = std::conditional<(MAX_REQUESTS <= 16), uint16_t, uint32_t>::type; +static constexpr trq_bitmask_t ALL_REQUESTS_IN_USE = MAX_REQUESTS == 32 ? ~0 : (1 << MAX_REQUESTS) - 1; static constexpr size_t USB_EVENT_QUEUE_SIZE = 32; // Size of event queue between USB task and main loop static constexpr size_t USB_TASK_STACK_SIZE = 4096; // Stack size for USB task (same as ESP-IDF USB examples) @@ -133,11 +134,11 @@ class USBClient : public Component { float get_setup_priority() const override { return setup_priority::IO; } void on_opened(uint8_t addr); void on_removed(usb_device_handle_t handle); - void control_transfer_callback(const usb_transfer_t *xfer) const; - void transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length); - void transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length); + bool transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length); + bool transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length); void dump_config() override; void release_trq(TransferRequest *trq); + trq_bitmask_t get_trq_in_use() const { return trq_in_use_; } bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback, const std::vector &data = {}); @@ -147,7 +148,6 @@ class USBClient : public Component { EventPool event_pool; protected: - bool register_(); TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe) virtual void disconnect(); virtual void on_connected() {} @@ -158,7 +158,7 @@ class USBClient : public Component { // USB task management static void usb_task_fn(void *arg); - void usb_task_loop(); + [[noreturn]] void usb_task_loop() const; TaskHandle_t usb_task_handle_{nullptr}; diff --git a/esphome/components/usb_host/usb_host_client.cpp b/esphome/components/usb_host/usb_host_client.cpp index 2139ed869a..dc216a209d 100644 --- a/esphome/components/usb_host/usb_host_client.cpp +++ b/esphome/components/usb_host/usb_host_client.cpp @@ -188,9 +188,9 @@ void USBClient::setup() { } // Pre-allocate USB transfer buffers for all slots at startup // This avoids any dynamic allocation during runtime - for (size_t i = 0; i < MAX_REQUESTS; i++) { - usb_host_transfer_alloc(64, 0, &this->requests_[i].transfer); - this->requests_[i].client = this; // Set once, never changes + for (auto &request : this->requests_) { + usb_host_transfer_alloc(64, 0, &request.transfer); + request.client = this; // Set once, never changes } // Create and start USB task @@ -210,8 +210,7 @@ void USBClient::usb_task_fn(void *arg) { auto *client = static_cast(arg); client->usb_task_loop(); } - -void USBClient::usb_task_loop() { +void USBClient::usb_task_loop() const { while (true) { usb_host_client_handle_events(this->handle_, portMAX_DELAY); } @@ -334,22 +333,23 @@ static void control_callback(const usb_transfer_t *xfer) { // This multi-threaded access is intentional for performance - USB task can // immediately restart transfers without waiting for main loop scheduling. TransferRequest *USBClient::get_trq_() { - trq_bitmask_t mask = this->trq_in_use_.load(std::memory_order_relaxed); + trq_bitmask_t mask = this->trq_in_use_.load(std::memory_order_acquire); // Find first available slot (bit = 0) and try to claim it atomically // We use a while loop to allow retrying the same slot after CAS failure - size_t i = 0; - while (i != MAX_REQUESTS) { - if (mask & (static_cast(1) << i)) { - // Slot is in use, move to next slot - i++; - continue; + for (;;) { + if (mask == ALL_REQUESTS_IN_USE) { + ESP_LOGE(TAG, "All %zu transfer slots in use", MAX_REQUESTS); + return nullptr; } + // find the least significant zero bit + trq_bitmask_t lsb = ~mask & (mask + 1); // Slot i appears available, try to claim it atomically - trq_bitmask_t desired = mask | (static_cast(1) << i); // Set bit i to mark as in-use + trq_bitmask_t desired = mask | lsb; - if (this->trq_in_use_.compare_exchange_weak(mask, desired, std::memory_order_acquire, std::memory_order_relaxed)) { + if (this->trq_in_use_.compare_exchange_weak(mask, desired, std::memory_order::acquire)) { + auto i = __builtin_ctz(lsb); // count trailing zeroes // Successfully claimed slot i - prepare the TransferRequest auto *trq = &this->requests_[i]; trq->transfer->context = trq; @@ -358,13 +358,9 @@ TransferRequest *USBClient::get_trq_() { } // CAS failed - another thread modified the bitmask // mask was already updated by compare_exchange_weak with the current value - // No need to reload - the CAS already did that for us - i = 0; } - - ESP_LOGE(TAG, "All %zu transfer slots in use", MAX_REQUESTS); - return nullptr; } + void USBClient::disconnect() { this->on_disconnected(); auto err = usb_host_device_close(this->handle_, this->device_handle_); @@ -446,11 +442,11 @@ static void transfer_callback(usb_transfer_t *xfer) { * * @throws None. */ -void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) { +bool USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) { auto *trq = this->get_trq_(); if (trq == nullptr) { ESP_LOGE(TAG, "Too many requests queued"); - return; + return false; } trq->callback = callback; trq->transfer->callback = transfer_callback; @@ -460,7 +456,9 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err); this->release_trq(trq); + return false; } + return true; } /** @@ -476,11 +474,11 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u * * @throws None. */ -void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) { +bool USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) { auto *trq = this->get_trq_(); if (trq == nullptr) { ESP_LOGE(TAG, "Too many requests queued"); - return; + return false; } trq->callback = callback; trq->transfer->callback = transfer_callback; @@ -491,7 +489,9 @@ void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err); this->release_trq(trq); + return false; } + return true; } void USBClient::dump_config() { ESP_LOGCONFIG(TAG, @@ -505,7 +505,7 @@ void USBClient::dump_config() { // - Main loop: When transfer submission fails // // THREAD SAFETY: Lock-free using atomic AND to clear bit -// Thread-safe atomic operation allows multi-threaded deallocation +// Thread-safe atomic operation allows multithreaded deallocation void USBClient::release_trq(TransferRequest *trq) { if (trq == nullptr) return; @@ -517,10 +517,10 @@ void USBClient::release_trq(TransferRequest *trq) { return; } - // Atomically clear bit i to mark slot as available + // Atomically clear the bit to mark slot as available // fetch_and with inverted bitmask clears the bit atomically - trq_bitmask_t bit = static_cast(1) << index; - this->trq_in_use_.fetch_and(static_cast(~bit), std::memory_order_release); + trq_bitmask_t mask = ~(static_cast(1) << index); + this->trq_in_use_.fetch_and(mask, std::memory_order_release); } } // namespace usb_host diff --git a/esphome/components/usb_uart/usb_uart.cpp b/esphome/components/usb_uart/usb_uart.cpp index 29003e071e..c24fffb11d 100644 --- a/esphome/components/usb_uart/usb_uart.cpp +++ b/esphome/components/usb_uart/usb_uart.cpp @@ -214,7 +214,7 @@ void USBUartComponent::dump_config() { } } void USBUartComponent::start_input(USBUartChannel *channel) { - if (!channel->initialised_.load() || channel->input_started_.load()) + if (!channel->initialised_.load()) return; // THREAD CONTEXT: Called from both USB task and main loop threads // - USB task: Immediate restart after successful transfer for continuous data flow @@ -226,12 +226,18 @@ void USBUartComponent::start_input(USBUartChannel *channel) { // // The underlying transfer_in() uses lock-free atomic allocation from the // TransferRequest pool, making this multi-threaded access safe + + // if already started, don't restart. A spurious failure in compare_exchange_weak + // is not a problem, as it will be retried on the next read_array() + auto started = false; + if (!channel->input_started_.compare_exchange_weak(started, true)) + return; const auto *ep = channel->cdc_dev_.in_ep; // CALLBACK CONTEXT: This lambda is executed in USB task via transfer_callback auto callback = [this, channel](const usb_host::TransferStatus &status) { ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code); if (!status.success) { - ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code)); + ESP_LOGE(TAG, "Input transfer failed, status=%s", esp_err_to_name(status.error_code)); // On failure, don't restart - let next read_array() trigger it channel->input_started_.store(false); return; @@ -263,8 +269,9 @@ void USBUartComponent::start_input(USBUartChannel *channel) { channel->input_started_.store(false); this->start_input(channel); }; - channel->input_started_.store(true); - this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize); + if (!this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize)) { + channel->input_started_.store(false); + } } void USBUartComponent::start_output(USBUartChannel *channel) { @@ -357,11 +364,12 @@ void USBUartTypeCdcAcm::on_disconnected() { usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress); } usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number); - channel->initialised_.store(false); - channel->input_started_.store(false); - channel->output_started_.store(false); + // Reset the input and output started flags to their initial state to avoid the possibility of spurious restarts + channel->input_started_.store(true); + channel->output_started_.store(true); channel->input_buffer_.clear(); channel->output_buffer_.clear(); + channel->initialised_.store(false); } USBClient::on_disconnected(); } diff --git a/tests/components/usb_uart/common.yaml b/tests/components/usb_uart/common.yaml index 46ad6291f9..474c3f5c8d 100644 --- a/tests/components/usb_uart/common.yaml +++ b/tests/components/usb_uart/common.yaml @@ -1,3 +1,6 @@ +usb_host: + max_transfer_requests: 32 + usb_uart: - id: uart_0 type: cdc_acm From 58ad4759f0b9f0e924bd093eba77e7d0db7b27a3 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:43:27 +1000 Subject: [PATCH 0189/1145] [lvgl] Fix rotation with unusual width (#11680) --- esphome/components/const/__init__.py | 1 + esphome/components/lvgl/lvgl_esphome.cpp | 24 ++++++++++--------- .../components/lvgl/widgets/buttonmatrix.py | 3 ++- esphome/components/matrix_keypad/__init__.py | 3 ++- esphome/const.py | 1 - 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/esphome/components/const/__init__.py b/esphome/components/const/__init__.py index 19924f0da7..2b88bb43a8 100644 --- a/esphome/components/const/__init__.py +++ b/esphome/components/const/__init__.py @@ -11,4 +11,5 @@ CONF_DRAW_ROUNDING = "draw_rounding" CONF_ON_RECEIVE = "on_receive" CONF_ON_STATE_CHANGE = "on_state_change" CONF_REQUEST_HEADERS = "request_headers" +CONF_ROWS = "rows" CONF_USE_PSRAM = "use_psram" diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 7a32691b53..6e18adccfe 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -156,6 +156,7 @@ bool LvPageType::is_showing() const { return this->parent_->get_current_page() = void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) { auto width = lv_area_get_width(area); auto height = lv_area_get_height(area); + auto height_rounded = (height + this->draw_rounding - 1) / this->draw_rounding * this->draw_rounding; auto x1 = area->x1; auto y1 = area->y1; lv_color_t *dst = this->rotate_buf_; @@ -163,13 +164,13 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) { case display::DISPLAY_ROTATION_90_DEGREES: for (lv_coord_t x = height; x-- != 0;) { for (lv_coord_t y = 0; y != width; y++) { - dst[y * height + x] = *ptr++; + dst[y * height_rounded + x] = *ptr++; } } y1 = x1; x1 = this->disp_drv_.ver_res - area->y1 - height; - width = height; - height = lv_area_get_width(area); + height = width; + width = height_rounded; break; case display::DISPLAY_ROTATION_180_DEGREES: @@ -185,13 +186,13 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) { case display::DISPLAY_ROTATION_270_DEGREES: for (lv_coord_t x = 0; x != height; x++) { for (lv_coord_t y = width; y-- != 0;) { - dst[y * height + x] = *ptr++; + dst[y * height_rounded + x] = *ptr++; } } x1 = y1; y1 = this->disp_drv_.hor_res - area->x1 - width; - width = height; - height = lv_area_get_width(area); + height = width; + width = height_rounded; break; default: @@ -435,8 +436,10 @@ LvglComponent::LvglComponent(std::vector displays, float buf void LvglComponent::setup() { auto *display = this->displays_[0]; - auto width = display->get_width(); - auto height = display->get_height(); + auto rounding = this->draw_rounding; + // cater for displays with dimensions that don't divide by the required rounding + auto width = (display->get_width() + rounding - 1) / rounding * rounding; + auto height = (display->get_height() + rounding - 1) / rounding * rounding; auto frac = this->buffer_frac_; if (frac == 0) frac = 1; @@ -461,9 +464,8 @@ void LvglComponent::setup() { } this->buffer_frac_ = frac; lv_disp_draw_buf_init(&this->draw_buf_, buffer, nullptr, buffer_pixels); - this->disp_drv_.hor_res = width; - this->disp_drv_.ver_res = height; - // this->setup_driver_(display->get_width(), display->get_height()); + this->disp_drv_.hor_res = display->get_width(); + this->disp_drv_.ver_res = display->get_height(); lv_disp_drv_update(this->disp_, &this->disp_drv_); this->rotation = display->get_rotation(); if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) { diff --git a/esphome/components/lvgl/widgets/buttonmatrix.py b/esphome/components/lvgl/widgets/buttonmatrix.py index baeb1c8e3e..fe421aa477 100644 --- a/esphome/components/lvgl/widgets/buttonmatrix.py +++ b/esphome/components/lvgl/widgets/buttonmatrix.py @@ -1,8 +1,9 @@ from esphome import automation import esphome.codegen as cg +from esphome.components.const import CONF_ROWS from esphome.components.key_provider import KeyProvider import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_ITEMS, CONF_ROWS, CONF_TEXT, CONF_WIDTH +from esphome.const import CONF_ID, CONF_ITEMS, CONF_TEXT, CONF_WIDTH from esphome.cpp_generator import MockObj from ..automation import action_to_code diff --git a/esphome/components/matrix_keypad/__init__.py b/esphome/components/matrix_keypad/__init__.py index 2e123323a0..868b149211 100644 --- a/esphome/components/matrix_keypad/__init__.py +++ b/esphome/components/matrix_keypad/__init__.py @@ -1,8 +1,9 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import key_provider +from esphome.components.const import CONF_ROWS import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_ROWS, CONF_TRIGGER_ID +from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_TRIGGER_ID CODEOWNERS = ["@ssieb"] diff --git a/esphome/const.py b/esphome/const.py index bfd772c539..9e8ec487b5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -837,7 +837,6 @@ CONF_RMT_CHANNEL = "rmt_channel" CONF_RMT_SYMBOLS = "rmt_symbols" CONF_ROTATION = "rotation" CONF_ROW = "row" -CONF_ROWS = "rows" CONF_RS_PIN = "rs_pin" CONF_RTD_NOMINAL_RESISTANCE = "rtd_nominal_resistance" CONF_RTD_WIRES = "rtd_wires" From a59888224c62ad1c0435f147baf360ba7f2269f9 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 11 Nov 2025 19:44:37 -0500 Subject: [PATCH 0190/1145] Bump version to 2025.10.5 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 4f72970e24..1390761da5 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.10.4 +PROJECT_NUMBER = 2025.10.5 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 9e8ec487b5..ddf9f28618 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.10.4" +__version__ = "2025.10.5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 7806eb980f3caff0c1a7d73d8ec7bce4b0e2286d Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 11 Nov 2025 19:50:47 -0500 Subject: [PATCH 0191/1145] Bump version to 2025.12.0-dev --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 034fa3fa37..a19120b9da 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.11.0-dev +PROJECT_NUMBER = 2025.12.0-dev # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index d0d94ed283..a25114d80e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.11.0-dev" +__version__ = "2025.12.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 298813d4fab29c05767ec15914bbafb6f53b32ee Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 11 Nov 2025 22:14:22 -0500 Subject: [PATCH 0192/1145] Bump version to 2025.11.0b1 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index a19120b9da..8025c71c19 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.0-dev +PROJECT_NUMBER = 2025.11.0b1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index a25114d80e..00975753c2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.0-dev" +__version__ = "2025.11.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 398dba4fc871efed35873a98a5aa44cb29217be0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 Nov 2025 21:44:19 -0600 Subject: [PATCH 0193/1145] [ci] Reduce release time by removing 21 redundant ESP32-S3 IDF tests (#11850) --- .../binary_sensor/test.esp32-s3-idf.yaml | 2 - .../bme68x_bsec2_i2c/test.esp32-s3-idf.yaml | 4 -- tests/components/debug/test.esp32-s3-idf.yaml | 1 - .../matrix_keypad/test.esp32-s3-idf.yaml | 15 ------ .../components/mcp3221/test.esp32-s3-idf.yaml | 4 -- .../mlx90393/test.esp32-s3-idf.yaml | 4 -- tests/components/npi19/test.esp32-s3-idf.yaml | 4 -- tests/components/ntc/test.esp32-s3-idf.yaml | 4 -- .../resistance/test.esp32-s3-idf.yaml | 4 -- .../components/switch/test.esp32-s3-idf.yaml | 2 - .../components/tem3200/test.esp32-s3-idf.yaml | 8 ---- .../template/test.esp32-s3-idf.yaml | 2 - ...max_with_usb_serial_jtag.esp32-s3-idf.yaml | 48 ------------------- .../wk2132_i2c/test.esp32-s3-idf.yaml | 9 ---- .../wk2132_spi/test.esp32-s3-idf.yaml | 11 ----- .../wk2168_i2c/test.esp32-s3-idf.yaml | 9 ---- .../wk2168_spi/test.esp32-s3-idf.yaml | 11 ----- .../wk2204_i2c/test.esp32-s3-idf.yaml | 9 ---- .../wk2204_spi/test.esp32-s3-idf.yaml | 11 ----- .../wk2212_i2c/test.esp32-s3-idf.yaml | 9 ---- .../wk2212_spi/test.esp32-s3-idf.yaml | 11 ----- 21 files changed, 182 deletions(-) delete mode 100644 tests/components/binary_sensor/test.esp32-s3-idf.yaml delete mode 100644 tests/components/bme68x_bsec2_i2c/test.esp32-s3-idf.yaml delete mode 100644 tests/components/debug/test.esp32-s3-idf.yaml delete mode 100644 tests/components/matrix_keypad/test.esp32-s3-idf.yaml delete mode 100644 tests/components/mcp3221/test.esp32-s3-idf.yaml delete mode 100644 tests/components/mlx90393/test.esp32-s3-idf.yaml delete mode 100644 tests/components/npi19/test.esp32-s3-idf.yaml delete mode 100644 tests/components/ntc/test.esp32-s3-idf.yaml delete mode 100644 tests/components/resistance/test.esp32-s3-idf.yaml delete mode 100644 tests/components/switch/test.esp32-s3-idf.yaml delete mode 100644 tests/components/tem3200/test.esp32-s3-idf.yaml delete mode 100644 tests/components/template/test.esp32-s3-idf.yaml delete mode 100644 tests/components/uart/test-uart_max_with_usb_serial_jtag.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2132_i2c/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2132_spi/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2168_i2c/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2168_spi/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2204_i2c/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2204_spi/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2212_i2c/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2212_spi/test.esp32-s3-idf.yaml diff --git a/tests/components/binary_sensor/test.esp32-s3-idf.yaml b/tests/components/binary_sensor/test.esp32-s3-idf.yaml deleted file mode 100644 index 25cb37a0b4..0000000000 --- a/tests/components/binary_sensor/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - common: !include common.yaml diff --git a/tests/components/bme68x_bsec2_i2c/test.esp32-s3-idf.yaml b/tests/components/bme68x_bsec2_i2c/test.esp32-s3-idf.yaml deleted file mode 100644 index 0fd8684a2c..0000000000 --- a/tests/components/bme68x_bsec2_i2c/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/debug/test.esp32-s3-idf.yaml b/tests/components/debug/test.esp32-s3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/debug/test.esp32-s3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/matrix_keypad/test.esp32-s3-idf.yaml b/tests/components/matrix_keypad/test.esp32-s3-idf.yaml deleted file mode 100644 index a491f2ed59..0000000000 --- a/tests/components/matrix_keypad/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,15 +0,0 @@ -packages: - common: !include common.yaml - -matrix_keypad: - id: keypad - rows: - - pin: 10 - - pin: 11 - columns: - - pin: 12 - - pin: 13 - keys: "1234" - has_pulldowns: true - on_key: - - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/mcp3221/test.esp32-s3-idf.yaml b/tests/components/mcp3221/test.esp32-s3-idf.yaml deleted file mode 100644 index 0fd8684a2c..0000000000 --- a/tests/components/mcp3221/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mlx90393/test.esp32-s3-idf.yaml b/tests/components/mlx90393/test.esp32-s3-idf.yaml deleted file mode 100644 index 0fd8684a2c..0000000000 --- a/tests/components/mlx90393/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/npi19/test.esp32-s3-idf.yaml b/tests/components/npi19/test.esp32-s3-idf.yaml deleted file mode 100644 index 0fd8684a2c..0000000000 --- a/tests/components/npi19/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ntc/test.esp32-s3-idf.yaml b/tests/components/ntc/test.esp32-s3-idf.yaml deleted file mode 100644 index 37fb325f4a..0000000000 --- a/tests/components/ntc/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -substitutions: - pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/resistance/test.esp32-s3-idf.yaml b/tests/components/resistance/test.esp32-s3-idf.yaml deleted file mode 100644 index 1910f325ae..0000000000 --- a/tests/components/resistance/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -substitutions: - pin: GPIO1 - -<<: !include common.yaml diff --git a/tests/components/switch/test.esp32-s3-idf.yaml b/tests/components/switch/test.esp32-s3-idf.yaml deleted file mode 100644 index 25cb37a0b4..0000000000 --- a/tests/components/switch/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - common: !include common.yaml diff --git a/tests/components/tem3200/test.esp32-s3-idf.yaml b/tests/components/tem3200/test.esp32-s3-idf.yaml deleted file mode 100644 index e9d826aa7c..0000000000 --- a/tests/components/tem3200/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - scl_pin: GPIO40 - sda_pin: GPIO41 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/template/test.esp32-s3-idf.yaml b/tests/components/template/test.esp32-s3-idf.yaml deleted file mode 100644 index 25cb37a0b4..0000000000 --- a/tests/components/template/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - common: !include common.yaml diff --git a/tests/components/uart/test-uart_max_with_usb_serial_jtag.esp32-s3-idf.yaml b/tests/components/uart/test-uart_max_with_usb_serial_jtag.esp32-s3-idf.yaml deleted file mode 100644 index 88a806eb92..0000000000 --- a/tests/components/uart/test-uart_max_with_usb_serial_jtag.esp32-s3-idf.yaml +++ /dev/null @@ -1,48 +0,0 @@ -<<: !include ../logger/common-usb_serial_jtag.yaml - -esphome: - on_boot: - then: - - uart.write: - id: uart_1 - data: 'Hello World' - - uart.write: - id: uart_1 - data: [0x00, 0x20, 0x42] - -uart: - - id: uart_1 - tx_pin: 4 - rx_pin: 5 - flow_control_pin: 6 - baud_rate: 9600 - data_bits: 8 - rx_buffer_size: 512 - rx_full_threshold: 10 - rx_timeout: 1 - parity: EVEN - stop_bits: 2 - - - id: uart_2 - tx_pin: 7 - rx_pin: 8 - flow_control_pin: 9 - baud_rate: 9600 - data_bits: 8 - rx_buffer_size: 512 - rx_full_threshold: 10 - rx_timeout: 1 - parity: EVEN - stop_bits: 2 - - - id: uart_3 - tx_pin: 10 - rx_pin: 11 - flow_control_pin: 12 - baud_rate: 9600 - data_bits: 8 - rx_buffer_size: 512 - rx_full_threshold: 10 - rx_timeout: 1 - parity: EVEN - stop_bits: 2 diff --git a/tests/components/wk2132_i2c/test.esp32-s3-idf.yaml b/tests/components/wk2132_i2c/test.esp32-s3-idf.yaml deleted file mode 100644 index d7b149a6fd..0000000000 --- a/tests/components/wk2132_i2c/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - scl_pin: GPIO40 - sda_pin: GPIO41 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - uart_bridge_2: !include ../../test_build_components/common/uart_bridge_2/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2132_spi/test.esp32-s3-idf.yaml b/tests/components/wk2132_spi/test.esp32-s3-idf.yaml deleted file mode 100644 index 9c7d36996e..0000000000 --- a/tests/components/wk2132_spi/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,11 +0,0 @@ -substitutions: - clk_pin: GPIO40 - miso_pin: GPIO41 - mosi_pin: GPIO6 - cs_pin: GPIO19 - -packages: - spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml - uart_bridge_2: !include ../../test_build_components/common/uart_bridge_2/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2168_i2c/test.esp32-s3-idf.yaml b/tests/components/wk2168_i2c/test.esp32-s3-idf.yaml deleted file mode 100644 index 115812be97..0000000000 --- a/tests/components/wk2168_i2c/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - scl_pin: GPIO40 - sda_pin: GPIO41 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2168_spi/test.esp32-s3-idf.yaml b/tests/components/wk2168_spi/test.esp32-s3-idf.yaml deleted file mode 100644 index 374fe64d16..0000000000 --- a/tests/components/wk2168_spi/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,11 +0,0 @@ -substitutions: - clk_pin: GPIO40 - miso_pin: GPIO41 - mosi_pin: GPIO6 - cs_pin: GPIO19 - -packages: - spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml - uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2204_i2c/test.esp32-s3-idf.yaml b/tests/components/wk2204_i2c/test.esp32-s3-idf.yaml deleted file mode 100644 index 115812be97..0000000000 --- a/tests/components/wk2204_i2c/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - scl_pin: GPIO40 - sda_pin: GPIO41 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2204_spi/test.esp32-s3-idf.yaml b/tests/components/wk2204_spi/test.esp32-s3-idf.yaml deleted file mode 100644 index 374fe64d16..0000000000 --- a/tests/components/wk2204_spi/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,11 +0,0 @@ -substitutions: - clk_pin: GPIO40 - miso_pin: GPIO41 - mosi_pin: GPIO6 - cs_pin: GPIO19 - -packages: - spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml - uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2212_i2c/test.esp32-s3-idf.yaml b/tests/components/wk2212_i2c/test.esp32-s3-idf.yaml deleted file mode 100644 index 115812be97..0000000000 --- a/tests/components/wk2212_i2c/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - scl_pin: GPIO40 - sda_pin: GPIO41 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2212_spi/test.esp32-s3-idf.yaml b/tests/components/wk2212_spi/test.esp32-s3-idf.yaml deleted file mode 100644 index 374fe64d16..0000000000 --- a/tests/components/wk2212_spi/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,11 +0,0 @@ -substitutions: - clk_pin: GPIO40 - miso_pin: GPIO41 - mosi_pin: GPIO6 - cs_pin: GPIO19 - -packages: - spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml - uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml - -<<: !include common.yaml From 748aee584a819dcab58dc07645f18af1d615cdfe Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 12 Nov 2025 10:41:22 -0500 Subject: [PATCH 0194/1145] [esp32] Update the recommended platform to 55.03.31-2 (#11865) --- esphome/components/esp32/__init__.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 6981662d77..61511cba0c 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -334,12 +334,14 @@ def _is_framework_url(source: str) -> str: # - https://github.com/espressif/arduino-esp32/releases ARDUINO_FRAMEWORK_VERSION_LOOKUP = { "recommended": cv.Version(3, 3, 2), - "latest": cv.Version(3, 3, 2), - "dev": cv.Version(3, 3, 2), + "latest": cv.Version(3, 3, 4), + "dev": cv.Version(3, 3, 4), } ARDUINO_PLATFORM_VERSION_LOOKUP = { - cv.Version(3, 3, 2): cv.Version(55, 3, 31, "1"), - cv.Version(3, 3, 1): cv.Version(55, 3, 31, "1"), + cv.Version(3, 3, 4): cv.Version(55, 3, 31, "2"), + cv.Version(3, 3, 3): cv.Version(55, 3, 31, "2"), + cv.Version(3, 3, 2): cv.Version(55, 3, 31, "2"), + cv.Version(3, 3, 1): cv.Version(55, 3, 31, "2"), cv.Version(3, 3, 0): cv.Version(55, 3, 30, "2"), cv.Version(3, 2, 1): cv.Version(54, 3, 21, "2"), cv.Version(3, 2, 0): cv.Version(54, 3, 20), @@ -357,8 +359,8 @@ ESP_IDF_FRAMEWORK_VERSION_LOOKUP = { "dev": cv.Version(5, 5, 1), } ESP_IDF_PLATFORM_VERSION_LOOKUP = { - cv.Version(5, 5, 1): cv.Version(55, 3, 31, "1"), - cv.Version(5, 5, 0): cv.Version(55, 3, 31, "1"), + cv.Version(5, 5, 1): cv.Version(55, 3, 31, "2"), + cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"), cv.Version(5, 4, 3): cv.Version(55, 3, 32), cv.Version(5, 4, 2): cv.Version(54, 3, 21, "2"), cv.Version(5, 4, 1): cv.Version(54, 3, 21, "2"), @@ -373,9 +375,9 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = { # The platform-espressif32 version # - https://github.com/pioarduino/platform-espressif32/releases PLATFORM_VERSION_LOOKUP = { - "recommended": cv.Version(55, 3, 31, "1"), - "latest": cv.Version(55, 3, 31, "1"), - "dev": cv.Version(55, 3, 31, "1"), + "recommended": cv.Version(55, 3, 31, "2"), + "latest": cv.Version(55, 3, 31, "2"), + "dev": cv.Version(55, 3, 31, "2"), } From 9de80b635a9c127488557604fc693d34549f8e64 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 11:56:19 -0600 Subject: [PATCH 0195/1145] [core] Fix wait_until hanging when used in on_boot automations (#11869) --- esphome/core/base_automation.h | 7 +- .../fixtures/wait_until_on_boot.yaml | 47 ++++++++++ tests/integration/test_wait_until_on_boot.py | 91 +++++++++++++++++++ 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 tests/integration/fixtures/wait_until_on_boot.yaml create mode 100644 tests/integration/test_wait_until_on_boot.py diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 6f392c8959..a5e6139182 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -412,7 +412,12 @@ template class WaitUntilAction : public Action, public Co void setup() override { // Start with loop disabled - only enable when there's work to do - this->disable_loop(); + // IMPORTANT: Only disable if num_running_ is 0, otherwise play_complex() was already + // called before our setup() (e.g., from on_boot trigger at same priority level) + // and we must not undo its enable_loop() call + if (this->num_running_ == 0) { + this->disable_loop(); + } } void play_complex(const Ts &...x) override { diff --git a/tests/integration/fixtures/wait_until_on_boot.yaml b/tests/integration/fixtures/wait_until_on_boot.yaml new file mode 100644 index 0000000000..358bef971b --- /dev/null +++ b/tests/integration/fixtures/wait_until_on_boot.yaml @@ -0,0 +1,47 @@ +# Test for wait_until in on_boot automation +# Reproduces bug where wait_until in on_boot would hang forever +# because WaitUntilAction::setup() would disable_loop() after +# play_complex() had already enabled it. + +esphome: + name: wait-until-on-boot + on_boot: + then: + - logger.log: "on_boot: Starting wait_until test" + - globals.set: + id: on_boot_started + value: 'true' + - wait_until: + condition: + lambda: return id(test_flag); + timeout: 5s + - logger.log: "on_boot: wait_until completed successfully" + +host: + +logger: + level: DEBUG + +globals: + - id: on_boot_started + type: bool + initial_value: 'false' + - id: test_flag + type: bool + initial_value: 'false' + +api: + actions: + - action: set_test_flag + then: + - globals.set: + id: test_flag + value: 'true' + - action: check_on_boot_started + then: + - lambda: |- + if (id(on_boot_started)) { + ESP_LOGI("test", "on_boot has started"); + } else { + ESP_LOGI("test", "on_boot has NOT started"); + } diff --git a/tests/integration/test_wait_until_on_boot.py b/tests/integration/test_wait_until_on_boot.py new file mode 100644 index 0000000000..b42c530c54 --- /dev/null +++ b/tests/integration/test_wait_until_on_boot.py @@ -0,0 +1,91 @@ +"""Integration test for wait_until in on_boot automation. + +This test validates that wait_until works correctly when triggered from on_boot, +which runs at the same setup priority as WaitUntilAction itself. This was broken +before the fix because WaitUntilAction::setup() would unconditionally disable_loop(), +even if play_complex() had already been called and enabled the loop. + +The bug: on_boot fires during StartupTrigger::setup(), which calls WaitUntilAction::play_complex() +before WaitUntilAction::setup() has run. Then when WaitUntilAction::setup() runs, it calls +disable_loop(), undoing the enable_loop() from play_complex(), causing wait_until to hang forever. +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_wait_until_on_boot( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that wait_until works in on_boot automation with a condition that becomes true later.""" + loop = asyncio.get_running_loop() + + on_boot_started = False + on_boot_completed = False + + on_boot_started_pattern = re.compile(r"on_boot: Starting wait_until test") + on_boot_complete_pattern = re.compile(r"on_boot: wait_until completed successfully") + + on_boot_started_future = loop.create_future() + on_boot_complete_future = loop.create_future() + + def check_output(line: str) -> None: + """Check log output for test progress.""" + nonlocal on_boot_started, on_boot_completed + + if on_boot_started_pattern.search(line): + on_boot_started = True + if not on_boot_started_future.done(): + on_boot_started_future.set_result(True) + + if on_boot_complete_pattern.search(line): + on_boot_completed = True + if not on_boot_complete_future.done(): + on_boot_complete_future.set_result(True) + + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Wait for on_boot to start + await asyncio.wait_for(on_boot_started_future, timeout=10.0) + assert on_boot_started, "on_boot did not start" + + # At this point, on_boot is blocked in wait_until waiting for test_flag to become true + # If the bug exists, wait_until's loop is disabled and it will never complete + # even after we set the flag + + # Give a moment for setup to complete + await asyncio.sleep(0.5) + + # Now set the flag that wait_until is waiting for + _, services = await client.list_entities_services() + set_flag_service = next( + (s for s in services if s.name == "set_test_flag"), None + ) + assert set_flag_service is not None, "set_test_flag service not found" + + client.execute_service(set_flag_service, {}) + + # If the fix works, wait_until's loop() will check the condition and proceed + # If the bug exists, wait_until is stuck with disabled loop and will timeout + try: + await asyncio.wait_for(on_boot_complete_future, timeout=2.0) + assert on_boot_completed, ( + "on_boot wait_until did not complete after flag was set" + ) + except TimeoutError: + pytest.fail( + "wait_until in on_boot did not complete within 2s after condition became true. " + "This indicates the bug where WaitUntilAction::setup() disables the loop " + "after play_complex() has already enabled it." + ) From 5d613ada8319e95d0d233beccdd322745d42e176 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:00:50 -0600 Subject: [PATCH 0196/1145] Bump pytest from 9.0.0 to 9.0.1 (#11874) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 35010ad52f..5c7cccaf25 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==3.21.1 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==9.0.0 +pytest==9.0.1 pytest-cov==7.0.0 pytest-mock==3.15.1 pytest-asyncio==1.3.0 From 3872a2fd919fb8b33fe0829001d0dce50a54001d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 14:01:07 -0600 Subject: [PATCH 0197/1145] [captive_portal] Warn when enabled without WiFi AP configured (#11856) --- esphome/components/captive_portal/__init__.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 99acb76bcf..9bd3ef8a05 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -1,9 +1,12 @@ +import logging + import esphome.codegen as cg from esphome.components import web_server_base from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( + CONF_AP, CONF_ID, PLATFORM_BK72XX, PLATFORM_ESP32, @@ -14,6 +17,10 @@ from esphome.const import ( ) from esphome.core import CORE, coroutine_with_priority from esphome.coroutine import CoroPriority +import esphome.final_validate as fv +from esphome.types import ConfigType + +_LOGGER = logging.getLogger(__name__) def AUTO_LOAD() -> list[str]: @@ -50,6 +57,27 @@ CONFIG_SCHEMA = cv.All( ) +def _final_validate(config: ConfigType) -> ConfigType: + full_config = fv.full_config.get() + wifi_conf = full_config.get("wifi") + + if wifi_conf is None: + # This shouldn't happen due to DEPENDENCIES = ["wifi"], but check anyway + raise cv.Invalid("Captive portal requires the wifi component to be configured") + + if CONF_AP not in wifi_conf: + _LOGGER.warning( + "Captive portal is enabled but no WiFi AP is configured. " + "The captive portal will not be accessible. " + "Add 'ap:' to your WiFi configuration to enable the captive portal." + ) + + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate + + @coroutine_with_priority(CoroPriority.CAPTIVE_PORTAL) async def to_code(config): paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) From 4b58cb4ce61cb07a3b8e1d97c7274e82e98b8db5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 14:01:19 -0600 Subject: [PATCH 0198/1145] [wifi] Pass ManualIP by const reference to reduce stack usage (#11858) --- esphome/components/network/ip_address.h | 8 ++++---- esphome/components/wifi/wifi_component.h | 4 ++-- esphome/components/wifi/wifi_component_esp8266.cpp | 4 ++-- esphome/components/wifi/wifi_component_esp_idf.cpp | 4 ++-- esphome/components/wifi/wifi_component_libretiny.cpp | 4 ++-- esphome/components/wifi/wifi_component_pico_w.cpp | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index 5e6b0dbd96..5ec6450cce 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -118,10 +118,10 @@ struct IPAddress { operator arduino_ns::IPAddress() const { return ip_addr_get_ip4_u32(&ip_addr_); } #endif - bool is_set() { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr) - bool is_ip4() { return IP_IS_V4(&ip_addr_); } - bool is_ip6() { return IP_IS_V6(&ip_addr_); } - bool is_multicast() { return ip_addr_ismulticast(&ip_addr_); } + bool is_set() const { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr) + bool is_ip4() const { return IP_IS_V4(&ip_addr_); } + bool is_ip6() const { return IP_IS_V6(&ip_addr_); } + bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); } std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); } bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); } bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); } diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 713e6f223f..d37367b88c 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -426,7 +426,7 @@ class WiFiComponent : public Component { bool wifi_sta_pre_setup_(); bool wifi_apply_output_power_(float output_power); bool wifi_apply_power_save_(); - bool wifi_sta_ip_config_(optional manual_ip); + bool wifi_sta_ip_config_(const optional &manual_ip); bool wifi_apply_hostname_(); bool wifi_sta_connect_(const WiFiAP &ap); void wifi_pre_setup_(); @@ -434,7 +434,7 @@ class WiFiComponent : public Component { bool wifi_scan_start_(bool passive); #ifdef USE_WIFI_AP - bool wifi_ap_ip_config_(optional manual_ip); + bool wifi_ap_ip_config_(const optional &manual_ip); bool wifi_start_ap_(const WiFiAP &ap); #endif // USE_WIFI_AP diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index bcb5dc4cf7..b787446a39 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -117,7 +117,7 @@ void netif_set_addr(struct netif *netif, const ip4_addr_t *ip, const ip4_addr_t }; #endif -bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { +bool WiFiComponent::wifi_sta_ip_config_(const optional &manual_ip) { // enable STA if (!this->wifi_mode_(true, {})) return false; @@ -730,7 +730,7 @@ void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) { } #ifdef USE_WIFI_AP -bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { +bool WiFiComponent::wifi_ap_ip_config_(const optional &manual_ip) { // enable AP if (!this->wifi_mode_({}, true)) return false; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index fd7e85fb6b..824adb5cf5 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -487,7 +487,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { return true; } -bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { +bool WiFiComponent::wifi_sta_ip_config_(const optional &manual_ip) { // enable STA if (!this->wifi_mode_(true, {})) return false; @@ -884,7 +884,7 @@ bool WiFiComponent::wifi_scan_start_(bool passive) { } #ifdef USE_WIFI_AP -bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { +bool WiFiComponent::wifi_ap_ip_config_(const optional &manual_ip) { esp_err_t err; // enable AP diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 2946b9e831..eea7a7e933 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -68,7 +68,7 @@ bool WiFiComponent::wifi_sta_pre_setup_() { return true; } bool WiFiComponent::wifi_apply_power_save_() { return WiFi.setSleep(this->power_save_ != WIFI_POWER_SAVE_NONE); } -bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { +bool WiFiComponent::wifi_sta_ip_config_(const optional &manual_ip) { // enable STA if (!this->wifi_mode_(true, {})) return false; @@ -434,7 +434,7 @@ void WiFiComponent::wifi_scan_done_callback_() { } #ifdef USE_WIFI_AP -bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { +bool WiFiComponent::wifi_ap_ip_config_(const optional &manual_ip) { // enable AP if (!this->wifi_mode_({}, true)) return false; diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 7025ba16bd..54f03f803d 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -72,7 +72,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { bool WiFiComponent::wifi_sta_pre_setup_() { return this->wifi_mode_(true, {}); } -bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { +bool WiFiComponent::wifi_sta_ip_config_(const optional &manual_ip) { if (!manual_ip.has_value()) { return true; } @@ -146,7 +146,7 @@ bool WiFiComponent::wifi_scan_start_(bool passive) { } #ifdef USE_WIFI_AP -bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { +bool WiFiComponent::wifi_ap_ip_config_(const optional &manual_ip) { esphome::network::IPAddress ip_address, gateway, subnet, dns; if (manual_ip.has_value()) { ip_address = manual_ip->static_ip; From 5a550cc579bee3f7c07ef7c9ccdc707ac776d836 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 14:26:36 -0600 Subject: [PATCH 0199/1145] [api] Eliminate heap allocations when transmitting Event types (#11773) --- esphome/components/api/api_connection.cpp | 14 +++---- esphome/components/api/api_connection.h | 48 ++++------------------- 2 files changed, 14 insertions(+), 48 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 7eb61f08b6..ca9ddaedf4 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1294,11 +1294,11 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe #endif #ifdef USE_EVENT -void APIConnection::send_event(event::Event *event, const std::string &event_type) { +void APIConnection::send_event(event::Event *event, const char *event_type) { this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE, EventResponse::ESTIMATED_SIZE); } -uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn, +uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn, uint32_t remaining_size, bool is_single) { EventResponse resp; resp.set_event_type(StringRef(event_type)); @@ -1650,9 +1650,7 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c // O(n) but optimized for RAM and not performance. for (auto &item : items) { if (item.entity == entity && item.message_type == message_type) { - // Clean up old creator before replacing - item.creator.cleanup(message_type); - // Move assign the new creator + // Replace with new creator item.creator = std::move(creator); return; } @@ -1822,7 +1820,7 @@ void APIConnection::process_batch_() { // Handle remaining items more efficiently if (items_processed < this->deferred_batch_.size()) { - // Remove processed items from the beginning with proper cleanup + // Remove processed items from the beginning this->deferred_batch_.remove_front(items_processed); // Reschedule for remaining items this->schedule_batch_(); @@ -1835,10 +1833,10 @@ void APIConnection::process_batch_() { uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, uint8_t message_type) const { #ifdef USE_EVENT - // Special case: EventResponse uses string pointer + // Special case: EventResponse uses const char * pointer if (message_type == EventResponse::MESSAGE_TYPE) { auto *e = static_cast(entity); - return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single); + return APIConnection::try_send_event_response(e, data_.const_char_ptr, conn, remaining_size, is_single); } #endif diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 284fa11a95..a77c93a2d5 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -177,7 +177,7 @@ class APIConnection final : public APIServerConnection { #endif #ifdef USE_EVENT - void send_event(event::Event *event, const std::string &event_type); + void send_event(event::Event *event, const char *event_type); #endif #ifdef USE_UPDATE @@ -450,7 +450,7 @@ class APIConnection final : public APIServerConnection { bool is_single); #endif #ifdef USE_EVENT - static uint16_t try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn, + static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn, uint32_t remaining_size, bool is_single); static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single); #endif @@ -508,10 +508,8 @@ class APIConnection final : public APIServerConnection { // Constructor for function pointer MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; } - // Constructor for string state capture - explicit MessageCreator(const std::string &str_value) { data_.string_ptr = new std::string(str_value); } - - // No destructor - cleanup must be called explicitly with message_type + // Constructor for const char * (Event types - no allocation needed) + explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; } // Delete copy operations - MessageCreator should only be moved MessageCreator(const MessageCreator &other) = delete; @@ -523,8 +521,6 @@ class APIConnection final : public APIServerConnection { // Move assignment MessageCreator &operator=(MessageCreator &&other) noexcept { if (this != &other) { - // IMPORTANT: Caller must ensure cleanup() was called if this contains a string! - // In our usage, this happens in add_item() deduplication and vector::erase() data_ = other.data_; other.data_.function_ptr = nullptr; } @@ -535,20 +531,10 @@ class APIConnection final : public APIServerConnection { uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, uint8_t message_type) const; - // Manual cleanup method - must be called before destruction for string types - void cleanup(uint8_t message_type) { -#ifdef USE_EVENT - if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) { - delete data_.string_ptr; - data_.string_ptr = nullptr; - } -#endif - } - private: union Data { MessageCreatorPtr function_ptr; - std::string *string_ptr; + const char *const_char_ptr; } data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before }; @@ -568,42 +554,24 @@ class APIConnection final : public APIServerConnection { std::vector items; uint32_t batch_start_time{0}; - private: - // Helper to cleanup items from the beginning - void cleanup_items_(size_t count) { - for (size_t i = 0; i < count; i++) { - items[i].creator.cleanup(items[i].message_type); - } - } - - public: DeferredBatch() { // Pre-allocate capacity for typical batch sizes to avoid reallocation items.reserve(8); } - ~DeferredBatch() { - // Ensure cleanup of any remaining items - clear(); - } - // Add item to the batch void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size); // Add item to the front of the batch (for high priority messages like ping) void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size); - // Clear all items with proper cleanup + // Clear all items void clear() { - cleanup_items_(items.size()); items.clear(); batch_start_time = 0; } - // Remove processed items from the front with proper cleanup - void remove_front(size_t count) { - cleanup_items_(count); - items.erase(items.begin(), items.begin() + count); - } + // Remove processed items from the front + void remove_front(size_t count) { items.erase(items.begin(), items.begin() + count); } bool empty() const { return items.empty(); } size_t size() const { return items.size(); } From 2f39b10baa9348c68d54413ad60177a6d0050995 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 14:26:46 -0600 Subject: [PATCH 0200/1145] [esp32_ble_tracker] Use initializer_list to eliminate compiler warning and reduce flash usage (#11861) --- esphome/components/esp32_ble_tracker/automation.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index 054cbaa7df..bbf7992fa4 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -10,7 +10,7 @@ namespace esphome::esp32_ble_tracker { class ESPBTAdvertiseTrigger : public Trigger, public ESPBTDeviceListener { public: explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } - void set_addresses(const std::vector &addresses) { this->address_vec_ = addresses; } + void set_addresses(std::initializer_list addresses) { this->address_vec_ = addresses; } bool parse_device(const ESPBTDevice &device) override { uint64_t u64_addr = device.address_uint64(); From 5f0fa68d732cd672fc83a249148bcde130bb812a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 14:26:57 -0600 Subject: [PATCH 0201/1145] [esp32_ble] Use stack allocation for MAC formatting in dump_config (#11860) --- esphome/components/esp32_ble/ble.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 8bbb21e3ca..d0bfb6f843 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -634,11 +634,13 @@ void ESP32BLE::dump_config() { io_capability_s = "invalid"; break; } + char mac_s[18]; + format_mac_addr_upper(mac_address, mac_s); ESP_LOGCONFIG(TAG, "BLE:\n" " MAC address: %s\n" " IO Capability: %s", - format_mac_address_pretty(mac_address).c_str(), io_capability_s); + mac_s, io_capability_s); } else { ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled"); } From 29a50da6355b2ad936dc78a19efb4cc4eeedc57e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 14:27:06 -0600 Subject: [PATCH 0202/1145] [wifi] Use stack allocation for BSSID formatting in logging (#11859) --- esphome/components/wifi/wifi_component.cpp | 24 +++++++++++-------- .../wifi/wifi_component_esp8266.cpp | 6 +++-- .../wifi/wifi_component_esp_idf.cpp | 6 +++-- .../wifi/wifi_component_libretiny.cpp | 6 +++-- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 817419107f..e33cd7cf2d 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -668,25 +668,25 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa void WiFiComponent::start_connecting(const WiFiAP &ap) { // Log connection attempt at INFO level with priority - std::string bssid_formatted; + char bssid_s[18]; int8_t priority = 0; if (ap.get_bssid().has_value()) { - bssid_formatted = format_mac_address_pretty(ap.get_bssid().value().data()); + format_mac_addr_upper(ap.get_bssid().value().data(), bssid_s); priority = this->get_sta_priority(ap.get_bssid().value()); } ESP_LOGI(TAG, "Connecting to " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") " (priority %d, attempt %u/%u in phase %s)...", - ap.get_ssid().c_str(), ap.get_bssid().has_value() ? bssid_formatted.c_str() : LOG_STR_LITERAL("any"), - priority, this->num_retried_ + 1, get_max_retries_for_phase(this->retry_phase_), + ap.get_ssid().c_str(), ap.get_bssid().has_value() ? bssid_s : LOG_STR_LITERAL("any"), priority, + this->num_retried_ + 1, get_max_retries_for_phase(this->retry_phase_), LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_))); #ifdef ESPHOME_LOG_HAS_VERBOSE ESP_LOGV(TAG, "Connection Params:"); ESP_LOGV(TAG, " SSID: '%s'", ap.get_ssid().c_str()); if (ap.get_bssid().has_value()) { - ESP_LOGV(TAG, " BSSID: %s", format_mac_address_pretty(ap.get_bssid()->data()).c_str()); + ESP_LOGV(TAG, " BSSID: %s", bssid_s); } else { ESP_LOGV(TAG, " BSSID: Not Set"); } @@ -787,6 +787,8 @@ const LogString *get_signal_bars(int8_t rssi) { void WiFiComponent::print_connect_params_() { bssid_t bssid = wifi_bssid(); + char bssid_s[18]; + format_mac_addr_upper(bssid.data(), bssid_s); ESP_LOGCONFIG(TAG, " Local MAC: %s", get_mac_address_pretty().c_str()); if (this->is_disabled()) { @@ -809,9 +811,9 @@ void WiFiComponent::print_connect_params_() { " Gateway: %s\n" " DNS1: %s\n" " DNS2: %s", - wifi_ssid().c_str(), format_mac_address_pretty(bssid.data()).c_str(), App.get_name().c_str(), rssi, - LOG_STR_ARG(get_signal_bars(rssi)), get_wifi_channel(), wifi_subnet_mask_().str().c_str(), - wifi_gateway_ip_().str().c_str(), wifi_dns_ip_(0).str().c_str(), wifi_dns_ip_(1).str().c_str()); + wifi_ssid().c_str(), bssid_s, App.get_name().c_str(), rssi, LOG_STR_ARG(get_signal_bars(rssi)), + get_wifi_channel(), wifi_subnet_mask_().str().c_str(), wifi_gateway_ip_().str().c_str(), + wifi_dns_ip_(0).str().c_str(), wifi_dns_ip_(1).str().c_str()); #ifdef ESPHOME_LOG_HAS_VERBOSE if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_bssid().has_value()) { ESP_LOGV(TAG, " Priority: %d", this->get_sta_priority(*config->get_bssid())); @@ -1390,8 +1392,10 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { (old_priority > std::numeric_limits::min()) ? (old_priority - 1) : std::numeric_limits::min(); this->set_sta_priority(failed_bssid.value(), new_priority); } - ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", ssid.c_str(), - format_mac_address_pretty(failed_bssid.value().data()).c_str(), old_priority, new_priority); + char bssid_s[18]; + format_mac_addr_upper(failed_bssid.value().data(), bssid_s); + ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", ssid.c_str(), bssid_s, + old_priority, new_priority); // After adjusting priority, check if all priorities are now at minimum // If so, clear the vector to save memory and reset for fresh start diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index b787446a39..78f336ab15 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -525,8 +525,10 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); s_sta_connect_not_found = true; } else { - ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, - format_mac_address_pretty(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason))); + char bssid_s[18]; + format_mac_addr_upper(it.bssid, bssid_s); + ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s, + LOG_STR_ARG(get_disconnect_reason_str(it.reason))); s_sta_connect_error = true; } s_sta_connected = false; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 824adb5cf5..df29565554 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -746,8 +746,10 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { ESP_LOGI(TAG, "Disconnected ssid='%s' reason='Station Roaming'", buf); return; } else { - ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, - format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); + char bssid_s[18]; + format_mac_addr_upper(it.bssid, bssid_s); + ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s, + get_disconnect_reason_str(it.reason)); s_sta_connect_error = true; } s_sta_connected = false; diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index eea7a7e933..7f0c35c8c8 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -299,8 +299,10 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ if (it.reason == WIFI_REASON_NO_AP_FOUND) { ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); } else { - ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, - format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); + char bssid_s[18]; + format_mac_addr_upper(it.bssid, bssid_s); + ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s, + get_disconnect_reason_str(it.reason)); } uint8_t reason = it.reason; From 859101ddc9fa55db7f31dfbb4a9411f1d4c736b8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 17:42:50 -0600 Subject: [PATCH 0203/1145] [api][event] Send events immediately to prevent loss during rapid triggers (#11777) --- esphome/components/api/api_connection.cpp | 4 +- esphome/components/api/api_connection.h | 54 ++++++++++++++++++----- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index ca9ddaedf4..b4230576de 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1295,8 +1295,8 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe #ifdef USE_EVENT void APIConnection::send_event(event::Event *event, const char *event_type) { - this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE, - EventResponse::ESTIMATED_SIZE); + this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE, + EventResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn, uint32_t remaining_size, bool is_single) { diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index a77c93a2d5..6cfd108927 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -650,21 +650,30 @@ class APIConnection final : public APIServerConnection { } #endif + // Helper to check if a message type should bypass batching + // Returns true if: + // 1. It's an UpdateStateResponse (always send immediately to handle cases where + // the main loop is blocked, e.g., during OTA updates) + // 2. It's an EventResponse (events are edge-triggered - every occurrence matters) + // 3. OR: User has opted into immediate sending (should_try_send_immediately = true + // AND batch_delay = 0) + inline bool should_send_immediately_(uint8_t message_type) const { + return ( +#ifdef USE_UPDATE + message_type == UpdateStateResponse::MESSAGE_TYPE || +#endif +#ifdef USE_EVENT + message_type == EventResponse::MESSAGE_TYPE || +#endif + (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0)); + } + // Helper method to send a message either immediately or via batching + // Tries immediate send if should_send_immediately_() returns true and buffer has space + // Falls back to batching if immediate send fails or isn't applicable bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type, uint8_t estimated_size) { - // Try to send immediately if: - // 1. It's an UpdateStateResponse (always send immediately to handle cases where - // the main loop is blocked, e.g., during OTA updates) - // 2. OR: We should try to send immediately (should_try_send_immediately = true) - // AND Batch delay is 0 (user has opted in to immediate sending) - // 3. AND: Buffer has space available - if (( -#ifdef USE_UPDATE - message_type == UpdateStateResponse::MESSAGE_TYPE || -#endif - (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0)) && - this->helper_->can_write_without_blocking()) { + if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) { // Now actually encode and send if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) && this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) { @@ -682,6 +691,27 @@ class APIConnection final : public APIServerConnection { return this->schedule_message_(entity, creator, message_type, estimated_size); } + // Overload for MessageCreator (used by events which need to capture event_type) + bool send_message_smart_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) { + // Try to send immediately if message type should bypass batching and buffer has space + if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) { + // Now actually encode and send + if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type) && + this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) { +#ifdef HAS_PROTO_MESSAGE_DUMP + // Log the message in verbose mode + this->log_proto_message_(entity, creator, message_type); +#endif + return true; + } + + // If immediate send failed, fall through to batching + } + + // Fall back to scheduled batching + return this->schedule_message_(entity, std::move(creator), message_type, estimated_size); + } + // Helper function to schedule a deferred message with known message type bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) { this->deferred_batch_.add_item(entity, std::move(creator), message_type, estimated_size); From 3a5b3ad77d35f9b1b1cceade282038100fb00603 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 17:55:06 -0600 Subject: [PATCH 0204/1145] [thermostat] Replace std::map with FixedVector, reduce flash usage (#11875) --- esphome/components/thermostat/climate.py | 38 ++++++++- .../thermostat/thermostat_climate.cpp | 82 +++++++++++------- .../thermostat/thermostat_climate.h | 33 +++++-- .../climate_custom_fan_modes_and_presets.yaml | 1 + .../integration/test_climate_custom_modes.py | 85 ++++++++++++++++++- 5 files changed, 195 insertions(+), 44 deletions(-) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index a928d208f3..a3c155aac0 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -945,6 +945,10 @@ async def to_code(config): cg.add(var.set_humidity_hysteresis(config[CONF_HUMIDITY_HYSTERESIS])) if CONF_PRESET in config: + # Separate standard and custom presets, and build preset config variables + standard_presets: list[tuple[cg.MockObj, cg.MockObj]] = [] + custom_presets: list[tuple[str, cg.MockObj]] = [] + for preset_config in config[CONF_PRESET]: name = preset_config[CONF_NAME] standard_preset = None @@ -987,9 +991,39 @@ async def to_code(config): ) if standard_preset is not None: - cg.add(var.set_preset_config(standard_preset, preset_target_variable)) + standard_presets.append((standard_preset, preset_target_variable)) else: - cg.add(var.set_custom_preset_config(name, preset_target_variable)) + custom_presets.append((name, preset_target_variable)) + + # Build initializer list for standard presets + if standard_presets: + cg.add( + var.set_preset_config( + [ + cg.StructInitializer( + thermostat_ns.struct("ThermostatPresetEntry"), + ("preset", preset), + ("config", preset_var), + ) + for preset, preset_var in standard_presets + ] + ) + ) + + # Build initializer list for custom presets + if custom_presets: + cg.add( + var.set_custom_preset_config( + [ + cg.StructInitializer( + thermostat_ns.struct("ThermostatCustomPresetEntry"), + ("name", cg.RawExpression(f'"{name}"')), + ("config", preset_var), + ) + for name, preset_var in custom_presets + ] + ) + ) if CONF_DEFAULT_PRESET in config: default_preset_name = config[CONF_DEFAULT_PRESET] diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index d533ef93ec..2b51f58f4f 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -53,8 +53,8 @@ void ThermostatClimate::setup() { if (use_default_preset) { if (this->default_preset_ != climate::ClimatePreset::CLIMATE_PRESET_NONE) { this->change_preset_(this->default_preset_); - } else if (!this->default_custom_preset_.empty()) { - this->change_custom_preset_(this->default_custom_preset_.c_str()); + } else if (this->default_custom_preset_ != nullptr) { + this->change_custom_preset_(this->default_custom_preset_); } } @@ -319,16 +319,16 @@ climate::ClimateTraits ThermostatClimate::traits() { if (this->supports_swing_mode_vertical_) traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL); - for (auto &it : this->preset_config_) { - traits.add_supported_preset(it.first); + for (const auto &entry : this->preset_config_) { + traits.add_supported_preset(entry.preset); } - // Extract custom preset names from the custom_preset_config_ map + // Extract custom preset names from the custom_preset_config_ vector if (!this->custom_preset_config_.empty()) { std::vector custom_preset_names; custom_preset_names.reserve(this->custom_preset_config_.size()); - for (const auto &it : this->custom_preset_config_) { - custom_preset_names.push_back(it.first.c_str()); + for (const auto &entry : this->custom_preset_config_) { + custom_preset_names.push_back(entry.name); } traits.set_supported_custom_presets(custom_preset_names); } @@ -1154,12 +1154,18 @@ void ThermostatClimate::dump_preset_config_(const char *preset_name, const Therm } void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { - auto config = this->preset_config_.find(preset); + // Linear search through preset configurations + const ThermostatClimateTargetTempConfig *config = nullptr; + for (const auto &entry : this->preset_config_) { + if (entry.preset == preset) { + config = &entry.config; + break; + } + } - if (config != this->preset_config_.end()) { + if (config != nullptr) { ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset))); - if (this->change_preset_internal_(config->second) || (!this->preset.has_value()) || - this->preset.value() != preset) { + if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) { // Fire any preset changed trigger if defined Trigger<> *trig = this->preset_change_trigger_; this->set_preset_(preset); @@ -1178,11 +1184,18 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { } void ThermostatClimate::change_custom_preset_(const char *custom_preset) { - auto config = this->custom_preset_config_.find(custom_preset); + // Linear search through custom preset configurations + const ThermostatClimateTargetTempConfig *config = nullptr; + for (const auto &entry : this->custom_preset_config_) { + if (strcmp(entry.name, custom_preset) == 0) { + config = &entry.config; + break; + } + } - if (config != this->custom_preset_config_.end()) { + if (config != nullptr) { ESP_LOGV(TAG, "Custom preset %s requested", custom_preset); - if (this->change_preset_internal_(config->second) || !this->has_custom_preset() || + if (this->change_preset_internal_(*config) || !this->has_custom_preset() || strcmp(this->get_custom_preset(), custom_preset) != 0) { // Fire any preset changed trigger if defined Trigger<> *trig = this->preset_change_trigger_; @@ -1247,14 +1260,12 @@ bool ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTem return something_changed; } -void ThermostatClimate::set_preset_config(climate::ClimatePreset preset, - const ThermostatClimateTargetTempConfig &config) { - this->preset_config_[preset] = config; +void ThermostatClimate::set_preset_config(std::initializer_list presets) { + this->preset_config_ = presets; } -void ThermostatClimate::set_custom_preset_config(const std::string &name, - const ThermostatClimateTargetTempConfig &config) { - this->custom_preset_config_[name] = config; +void ThermostatClimate::set_custom_preset_config(std::initializer_list presets) { + this->custom_preset_config_ = presets; } ThermostatClimate::ThermostatClimate() @@ -1293,8 +1304,16 @@ ThermostatClimate::ThermostatClimate() humidity_control_humidify_action_trigger_(new Trigger<>()), humidity_control_off_action_trigger_(new Trigger<>()) {} -void ThermostatClimate::set_default_preset(const std::string &custom_preset) { - this->default_custom_preset_ = custom_preset; +void ThermostatClimate::set_default_preset(const char *custom_preset) { + // Find the preset in custom_preset_config_ and store pointer from there + for (const auto &entry : this->custom_preset_config_) { + if (strcmp(entry.name, custom_preset) == 0) { + this->default_custom_preset_ = entry.name; + return; + } + } + // If not found, it will be caught during validation + this->default_custom_preset_ = nullptr; } void ThermostatClimate::set_default_preset(climate::ClimatePreset preset) { this->default_preset_ = preset; } @@ -1605,19 +1624,22 @@ void ThermostatClimate::dump_config() { if (!this->preset_config_.empty()) { ESP_LOGCONFIG(TAG, " Supported PRESETS:"); - for (auto &it : this->preset_config_) { - const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(it.first)); - ESP_LOGCONFIG(TAG, " %s:%s", preset_name, it.first == this->default_preset_ ? " (default)" : ""); - this->dump_preset_config_(preset_name, it.second); + for (const auto &entry : this->preset_config_) { + const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(entry.preset)); + ESP_LOGCONFIG(TAG, " %s:%s", preset_name, entry.preset == this->default_preset_ ? " (default)" : ""); + this->dump_preset_config_(preset_name, entry.config); } } if (!this->custom_preset_config_.empty()) { ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS:"); - for (auto &it : this->custom_preset_config_) { - const auto *preset_name = it.first.c_str(); - ESP_LOGCONFIG(TAG, " %s:%s", preset_name, it.first == this->default_custom_preset_ ? " (default)" : ""); - this->dump_preset_config_(preset_name, it.second); + for (const auto &entry : this->custom_preset_config_) { + const auto *preset_name = entry.name; + ESP_LOGCONFIG(TAG, " %s:%s", preset_name, + (this->default_custom_preset_ != nullptr && strcmp(entry.name, this->default_custom_preset_) == 0) + ? " (default)" + : ""); + this->dump_preset_config_(preset_name, entry.config); } } } diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index c9795d9666..76391f800c 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -3,12 +3,12 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" #include "esphome/components/climate/climate.h" #include "esphome/components/sensor/sensor.h" #include #include -#include namespace esphome { namespace thermostat { @@ -72,14 +72,29 @@ struct ThermostatClimateTargetTempConfig { optional mode_{}; }; +/// Entry for standard preset lookup +struct ThermostatPresetEntry { + climate::ClimatePreset preset; + ThermostatClimateTargetTempConfig config; +}; + +/// Entry for custom preset lookup +struct ThermostatCustomPresetEntry { + const char *name; + ThermostatClimateTargetTempConfig config; +}; + class ThermostatClimate : public climate::Climate, public Component { public: + using PresetEntry = ThermostatPresetEntry; + using CustomPresetEntry = ThermostatCustomPresetEntry; + ThermostatClimate(); void setup() override; void dump_config() override; void loop() override; - void set_default_preset(const std::string &custom_preset); + void set_default_preset(const char *custom_preset); void set_default_preset(climate::ClimatePreset preset); void set_on_boot_restore_from(OnBootRestoreFrom on_boot_restore_from); void set_set_point_minimum_differential(float differential); @@ -131,8 +146,8 @@ class ThermostatClimate : public climate::Climate, public Component { void set_supports_humidification(bool supports_humidification); void set_supports_two_points(bool supports_two_points); - void set_preset_config(climate::ClimatePreset preset, const ThermostatClimateTargetTempConfig &config); - void set_custom_preset_config(const std::string &name, const ThermostatClimateTargetTempConfig &config); + void set_preset_config(std::initializer_list presets); + void set_custom_preset_config(std::initializer_list presets); Trigger<> *get_cool_action_trigger() const; Trigger<> *get_supplemental_cool_action_trigger() const; @@ -516,9 +531,6 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *prev_swing_mode_trigger_{nullptr}; Trigger<> *prev_humidity_control_trigger_{nullptr}; - /// Default custom preset to use on start up - std::string default_custom_preset_{}; - /// Climate action timers std::array timer_{ ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)), @@ -534,9 +546,12 @@ class ThermostatClimate : public climate::Climate, public Component { }; /// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc) - std::map preset_config_{}; + FixedVector preset_config_{}; /// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset") - std::map custom_preset_config_{}; + FixedVector custom_preset_config_{}; + /// Default custom preset to use on start up (pointer to entry in custom_preset_config_) + private: + const char *default_custom_preset_{nullptr}; }; } // namespace thermostat diff --git a/tests/integration/fixtures/climate_custom_fan_modes_and_presets.yaml b/tests/integration/fixtures/climate_custom_fan_modes_and_presets.yaml index bf4ef9eafd..3996d0f169 100644 --- a/tests/integration/fixtures/climate_custom_fan_modes_and_presets.yaml +++ b/tests/integration/fixtures/climate_custom_fan_modes_and_presets.yaml @@ -14,6 +14,7 @@ climate: id: test_thermostat name: Test Thermostat Custom Modes sensor: thermostat_sensor + default_preset: "Eco Plus" preset: - name: Away default_target_temperature_low: 16°C diff --git a/tests/integration/test_climate_custom_modes.py b/tests/integration/test_climate_custom_modes.py index ce34959d88..67a7b0581a 100644 --- a/tests/integration/test_climate_custom_modes.py +++ b/tests/integration/test_climate_custom_modes.py @@ -2,9 +2,13 @@ from __future__ import annotations -from aioesphomeapi import ClimateInfo, ClimatePreset +import asyncio + +import aioesphomeapi +from aioesphomeapi import ClimateInfo, ClimatePreset, EntityState import pytest +from .state_utils import InitialStateHelper from .types import APIClientConnectedFactory, RunCompiledFunction @@ -14,15 +18,50 @@ async def test_climate_custom_fan_modes_and_presets( run_compiled: RunCompiledFunction, api_client_connected: APIClientConnectedFactory, ) -> None: - """Test that custom presets are properly exposed via API.""" + """Test that custom presets are properly exposed and can be changed.""" + loop = asyncio.get_running_loop() async with run_compiled(yaml_config), api_client_connected() as client: - # Get entities and services + states: dict[int, EntityState] = {} + super_saver_future: asyncio.Future[EntityState] = loop.create_future() + vacation_future: asyncio.Future[EntityState] = loop.create_future() + + def on_state(state: EntityState) -> None: + states[state.key] = state + if isinstance(state, aioesphomeapi.ClimateState): + # Wait for Super Saver preset + if ( + state.custom_preset == "Super Saver" + and state.target_temperature_low == 20.0 + and state.target_temperature_high == 24.0 + and not super_saver_future.done() + ): + super_saver_future.set_result(state) + # Wait for Vacation Mode preset + elif ( + state.custom_preset == "Vacation Mode" + and state.target_temperature_low == 15.0 + and state.target_temperature_high == 18.0 + and not vacation_future.done() + ): + vacation_future.set_result(state) + + # Get entities and set up state synchronization entities, services = await client.list_entities_services() + initial_state_helper = InitialStateHelper(entities) climate_infos = [e for e in entities if isinstance(e, ClimateInfo)] assert len(climate_infos) == 1, "Expected exactly 1 climate entity" test_climate = climate_infos[0] + # Subscribe with the wrapper that filters initial states + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + # Wait for all initial states to be broadcast + try: + await initial_state_helper.wait_for_initial_states() + except TimeoutError: + pytest.fail("Timeout waiting for initial states") + # Verify enum presets are exposed (from preset: config map) assert ClimatePreset.AWAY in test_climate.supported_presets, ( "Expected AWAY in enum presets" @@ -40,3 +79,43 @@ async def test_climate_custom_fan_modes_and_presets( assert "Vacation Mode" in custom_presets, ( "Expected 'Vacation Mode' in custom presets" ) + + # Get initial state and verify default preset + initial_state = initial_state_helper.initial_states.get(test_climate.key) + assert initial_state is not None, "Climate initial state not found" + assert isinstance(initial_state, aioesphomeapi.ClimateState) + assert initial_state.custom_preset == "Eco Plus", ( + f"Expected default preset 'Eco Plus', got '{initial_state.custom_preset}'" + ) + assert initial_state.target_temperature_low == 18.0, ( + f"Expected low temp 18.0, got {initial_state.target_temperature_low}" + ) + assert initial_state.target_temperature_high == 22.0, ( + f"Expected high temp 22.0, got {initial_state.target_temperature_high}" + ) + + # Test changing to "Super Saver" custom preset + client.climate_command(test_climate.key, custom_preset="Super Saver") + + try: + super_saver_state = await asyncio.wait_for(super_saver_future, timeout=5.0) + except TimeoutError: + pytest.fail("Super Saver preset change not received within 5 seconds") + + assert isinstance(super_saver_state, aioesphomeapi.ClimateState) + assert super_saver_state.custom_preset == "Super Saver" + assert super_saver_state.target_temperature_low == 20.0 + assert super_saver_state.target_temperature_high == 24.0 + + # Test changing to "Vacation Mode" custom preset + client.climate_command(test_climate.key, custom_preset="Vacation Mode") + + try: + vacation_state = await asyncio.wait_for(vacation_future, timeout=5.0) + except TimeoutError: + pytest.fail("Vacation Mode preset change not received within 5 seconds") + + assert isinstance(vacation_state, aioesphomeapi.ClimateState) + assert vacation_state.custom_preset == "Vacation Mode" + assert vacation_state.target_temperature_low == 15.0 + assert vacation_state.target_temperature_high == 18.0 From 769137fc09873de801f2a3e90ce38dc35573c2be Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 18:40:26 -0600 Subject: [PATCH 0205/1145] [mqtt] Fix crash with empty broker during upload/logs (#11866) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/mqtt.py | 13 ++++- tests/unit_tests/test_main.py | 50 +++++++++++++++++++ tests/unit_tests/test_mqtt.py | 91 +++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 tests/unit_tests/test_mqtt.py diff --git a/esphome/mqtt.py b/esphome/mqtt.py index 093ee64df4..0d50edbc2c 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -30,6 +30,7 @@ from esphome.const import ( from esphome.core import CORE, EsphomeError from esphome.helpers import get_int_env, get_str_env from esphome.log import AnsiFore, color +from esphome.types import ConfigType from esphome.util import safe_print _LOGGER = logging.getLogger(__name__) @@ -154,8 +155,12 @@ def show_discover(config, username=None, password=None, client_id=None): def get_esphome_device_ip( - config, username=None, password=None, client_id=None, timeout=25 -): + config: ConfigType, + username: str | None = None, + password: str | None = None, + client_id: str | None = None, + timeout: int | float = 25, +) -> list[str]: if CONF_MQTT not in config: raise EsphomeError( "Cannot discover IP via MQTT as the config does not include the mqtt: " @@ -166,6 +171,10 @@ def get_esphome_device_ip( "Cannot discover IP via MQTT as the config does not include the device name: " "component" ) + if not config[CONF_MQTT].get(CONF_BROKER): + raise EsphomeError( + "Cannot discover IP via MQTT as the broker is not configured" + ) dev_name = config[CONF_ESPHOME][CONF_NAME] dev_ip = None diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index 9e5f399381..ccbc5a1306 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -1166,6 +1166,56 @@ def test_upload_program_ota_with_mqtt_resolution( ) +def test_upload_program_ota_with_mqtt_empty_broker( + mock_mqtt_get_ip: Mock, + mock_is_ip_address: Mock, + mock_run_ota: Mock, + tmp_path: Path, + caplog: CaptureFixture, +) -> None: + """Test upload_program with OTA when MQTT broker is empty (issue #11653).""" + setup_core(address="192.168.1.50", platform=PLATFORM_ESP32, tmp_path=tmp_path) + + mock_is_ip_address.return_value = True + mock_mqtt_get_ip.side_effect = EsphomeError( + "Cannot discover IP via MQTT as the broker is not configured" + ) + mock_run_ota.return_value = (0, "192.168.1.50") + + config = { + CONF_OTA: [ + { + CONF_PLATFORM: CONF_ESPHOME, + CONF_PORT: 3232, + } + ], + CONF_MQTT: { + CONF_BROKER: "", + }, + CONF_MDNS: { + CONF_DISABLED: True, + }, + } + args = MockArgs(username="user", password="pass", client_id="client") + devices = ["MQTTIP", "192.168.1.50"] + + exit_code, host = upload_program(config, args, devices) + + assert exit_code == 0 + assert host == "192.168.1.50" + # Verify MQTT was attempted but failed gracefully + mock_mqtt_get_ip.assert_called_once_with(config, "user", "pass", "client") + # Verify we fell back to the IP address + expected_firmware = ( + tmp_path / ".esphome" / "build" / "test" / ".pioenvs" / "test" / "firmware.bin" + ) + mock_run_ota.assert_called_once_with( + ["192.168.1.50"], 3232, None, expected_firmware + ) + # Verify warning was logged + assert "MQTT IP discovery failed" in caplog.text + + @patch("esphome.__main__.importlib.import_module") def test_upload_program_platform_specific_handler( mock_import: Mock, diff --git a/tests/unit_tests/test_mqtt.py b/tests/unit_tests/test_mqtt.py new file mode 100644 index 0000000000..4c2c34dff1 --- /dev/null +++ b/tests/unit_tests/test_mqtt.py @@ -0,0 +1,91 @@ +"""Unit tests for esphome.mqtt module.""" + +from __future__ import annotations + +import pytest + +from esphome.const import CONF_BROKER, CONF_ESPHOME, CONF_MQTT, CONF_NAME +from esphome.core import EsphomeError +from esphome.mqtt import get_esphome_device_ip + + +def test_get_esphome_device_ip_empty_broker() -> None: + """Test that get_esphome_device_ip raises EsphomeError when broker is empty.""" + config = { + CONF_MQTT: { + CONF_BROKER: "", + }, + CONF_ESPHOME: { + CONF_NAME: "test-device", + }, + } + + with pytest.raises( + EsphomeError, + match="Cannot discover IP via MQTT as the broker is not configured", + ): + get_esphome_device_ip(config) + + +def test_get_esphome_device_ip_none_broker() -> None: + """Test that get_esphome_device_ip raises EsphomeError when broker is None.""" + config = { + CONF_MQTT: { + CONF_BROKER: None, + }, + CONF_ESPHOME: { + CONF_NAME: "test-device", + }, + } + + with pytest.raises( + EsphomeError, + match="Cannot discover IP via MQTT as the broker is not configured", + ): + get_esphome_device_ip(config) + + +def test_get_esphome_device_ip_missing_mqtt() -> None: + """Test that get_esphome_device_ip raises EsphomeError when mqtt config is missing.""" + config = { + CONF_ESPHOME: { + CONF_NAME: "test-device", + }, + } + + with pytest.raises( + EsphomeError, + match="Cannot discover IP via MQTT as the config does not include the mqtt:", + ): + get_esphome_device_ip(config) + + +def test_get_esphome_device_ip_missing_esphome() -> None: + """Test that get_esphome_device_ip raises EsphomeError when esphome config is missing.""" + config = { + CONF_MQTT: { + CONF_BROKER: "mqtt.local", + }, + } + + with pytest.raises( + EsphomeError, + match="Cannot discover IP via MQTT as the config does not include the device name:", + ): + get_esphome_device_ip(config) + + +def test_get_esphome_device_ip_missing_name() -> None: + """Test that get_esphome_device_ip raises EsphomeError when device name is missing.""" + config = { + CONF_MQTT: { + CONF_BROKER: "mqtt.local", + }, + CONF_ESPHOME: {}, + } + + with pytest.raises( + EsphomeError, + match="Cannot discover IP via MQTT as the config does not include the device name:", + ): + get_esphome_device_ip(config) From 735bf9930aa12335bfef6d939439521150b82109 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 18:41:28 -0600 Subject: [PATCH 0206/1145] [light] Fix dangling reference in compute_color_mode causing memory corruption (#11868) --- esphome/components/api/api_connection.cpp | 3 ++- esphome/components/light/light_call.cpp | 2 +- esphome/components/light/light_traits.h | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b4230576de..4acd2fc15c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -476,8 +476,9 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c auto *light = static_cast(entity); ListEntitiesLightResponse msg; auto traits = light->get_traits(); + auto supported_modes = traits.get_supported_color_modes(); // Pass pointer to ColorModeMask so the iterator can encode actual ColorMode enum values - msg.supported_color_modes = &traits.get_supported_color_modes(); + msg.supported_color_modes = &supported_modes; if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) || traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) { msg.min_mireds = traits.get_min_mireds(); diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index df17f53adc..8365ac77cd 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -406,7 +406,7 @@ void LightCall::transform_parameters_() { } } ColorMode LightCall::compute_color_mode_() { - const auto &supported_modes = this->parent_->get_traits().get_supported_color_modes(); + auto supported_modes = this->parent_->get_traits().get_supported_color_modes(); int supported_count = supported_modes.size(); // Some lights don't support any color modes (e.g. monochromatic light), leave it at unknown. diff --git a/esphome/components/light/light_traits.h b/esphome/components/light/light_traits.h index 294b0cad1d..c3bb27a964 100644 --- a/esphome/components/light/light_traits.h +++ b/esphome/components/light/light_traits.h @@ -18,7 +18,8 @@ class LightTraits { public: LightTraits() = default; - const ColorModeMask &get_supported_color_modes() const { return this->supported_color_modes_; } + // Return by value to avoid dangling reference when get_traits() returns a temporary + ColorModeMask get_supported_color_modes() const { return this->supported_color_modes_; } void set_supported_color_modes(ColorModeMask supported_color_modes) { this->supported_color_modes_ = supported_color_modes; } From 47fe84e92208f3afab6fb871ec9842423caf3584 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 18:43:51 -0600 Subject: [PATCH 0207/1145] [wifi][ethernet] Fix spurious warnings and unclear status after PR #9823 (#11871) --- esphome/components/ethernet/ethernet_component.cpp | 5 ++++- esphome/components/wifi/wifi_component.cpp | 13 ++++++++++++- esphome/components/wifi/wifi_component.h | 3 +++ esphome/components/wifi/wifi_component_esp8266.cpp | 2 +- esphome/components/wifi/wifi_component_esp_idf.cpp | 11 +++++++---- .../components/wifi/wifi_component_libretiny.cpp | 2 +- esphome/components/wifi/wifi_component_pico_w.cpp | 2 +- 7 files changed, 29 insertions(+), 9 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 5888ddce60..cad963b299 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -381,7 +381,10 @@ void EthernetComponent::dump_config() { break; } - ESP_LOGCONFIG(TAG, "Ethernet:"); + ESP_LOGCONFIG(TAG, + "Ethernet:\n" + " Connected: %s", + YESNO(this->is_connected())); this->dump_connect_params_(); #ifdef USE_ETHERNET_SPI ESP_LOGCONFIG(TAG, diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e33cd7cf2d..7d239349ff 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -743,6 +743,14 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { } const LogString *get_signal_bars(int8_t rssi) { + // Check for disconnected sentinel value first + if (rssi == WIFI_RSSI_DISCONNECTED) { + // MULTIPLICATION SIGN + // Unicode: U+00D7, UTF-8: C3 97 + return LOG_STR("\033[0;31m" // red + "\xc3\x97\xc3\x97\xc3\x97\xc3\x97" + "\033[0m"); + } // LOWER ONE QUARTER BLOCK // Unicode: U+2582, UTF-8: E2 96 82 // LOWER HALF BLOCK @@ -1024,7 +1032,10 @@ void WiFiComponent::check_scanning_finished() { } void WiFiComponent::dump_config() { - ESP_LOGCONFIG(TAG, "WiFi:"); + ESP_LOGCONFIG(TAG, + "WiFi:\n" + " Connected: %s", + YESNO(this->is_connected())); this->print_connect_params_(); } diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index d37367b88c..2fd7fa6cd4 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -52,6 +52,9 @@ extern "C" { namespace esphome { namespace wifi { +/// Sentinel value for RSSI when WiFi is not connected +static constexpr int8_t WIFI_RSSI_DISCONNECTED = -127; + struct SavedWifiSettings { char ssid[33]; char password[65]; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 78f336ab15..a543628e27 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -872,7 +872,7 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } -int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } +int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; } diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index df29565554..4aac03885a 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -1031,7 +1031,8 @@ bssid_t WiFiComponent::wifi_bssid() { wifi_ap_record_t info; esp_err_t err = esp_wifi_sta_get_ap_info(&info); if (err != ESP_OK) { - ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); + // Very verbose only: this is expected during dump_config() before connection is established (PR #9823) + ESP_LOGVV(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); return bssid; } std::copy(info.bssid, info.bssid + 6, bssid.begin()); @@ -1041,7 +1042,8 @@ std::string WiFiComponent::wifi_ssid() { wifi_ap_record_t info{}; esp_err_t err = esp_wifi_sta_get_ap_info(&info); if (err != ESP_OK) { - ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); + // Very verbose only: this is expected during dump_config() before connection is established (PR #9823) + ESP_LOGVV(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); return ""; } auto *ssid_s = reinterpret_cast(info.ssid); @@ -1052,8 +1054,9 @@ int8_t WiFiComponent::wifi_rssi() { wifi_ap_record_t info; esp_err_t err = esp_wifi_sta_get_ap_info(&info); if (err != ESP_OK) { - ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); - return 0; + // Very verbose only: this is expected during dump_config() before connection is established (PR #9823) + ESP_LOGVV(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); + return WIFI_RSSI_DISCONNECTED; } return info.rssi; } diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 7f0c35c8c8..98cbfddb1d 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -486,7 +486,7 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } -int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } +int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 54f03f803d..91766e8ab5 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -200,7 +200,7 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } -int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } +int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { From 2d6618da3c2f1410a8a66f19ab15f8529412e9d0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 18:44:22 -0600 Subject: [PATCH 0208/1145] [wifi] Fix slow reconnection after connection loss for all network types (#11873) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/wifi/wifi_component.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 7d239349ff..51a5a47323 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -465,6 +465,8 @@ void WiFiComponent::loop() { if (!this->is_connected()) { ESP_LOGW(TAG, "Connection lost; reconnecting"); this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING; + // Clear error flag before reconnecting so first attempt is not seen as immediate failure + this->error_from_callback_ = false; this->retry_connect(); } else { this->status_clear_warning(); @@ -1060,6 +1062,10 @@ void WiFiComponent::check_connecting_finished() { // Reset to initial phase on successful connection (don't log transition, just reset state) this->retry_phase_ = WiFiRetryPhase::INITIAL_CONNECT; this->num_retried_ = 0; + // Ensure next connection attempt does not inherit error state + // so when WiFi disconnects later we start fresh and don't see + // the first connection as a failure. + this->error_from_callback_ = false; this->print_connect_params_(); @@ -1146,6 +1152,11 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() { return WiFiRetryPhase::FAST_CONNECT_CYCLING_APS; // Move to next AP } #endif + // Check if we should try explicit hidden networks before scanning + // This handles reconnection after connection loss where first network is hidden + if (!this->sta_.empty() && this->sta_[0].get_hidden()) { + return WiFiRetryPhase::EXPLICIT_HIDDEN; + } // No more APs to try, fall back to scan return WiFiRetryPhase::SCAN_CONNECTING; From d869108416ed030caa93729b419256ac45e4667b Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Thu, 13 Nov 2025 03:06:20 +0100 Subject: [PATCH 0209/1145] [nrf52] add settings for dcdc converter (#11841) --- esphome/components/nrf52/__init__.py | 3 +++ tests/components/nrf52/test.nrf52-adafruit.yaml | 1 + 2 files changed, 4 insertions(+) diff --git a/esphome/components/nrf52/__init__.py b/esphome/components/nrf52/__init__.py index a3b79bf139..03927e8ea2 100644 --- a/esphome/components/nrf52/__init__.py +++ b/esphome/components/nrf52/__init__.py @@ -103,6 +103,7 @@ nrf52_ns = cg.esphome_ns.namespace("nrf52") DeviceFirmwareUpdate = nrf52_ns.class_("DeviceFirmwareUpdate", cg.Component) CONF_DFU = "dfu" +CONF_DCDC = "dcdc" CONF_REG0 = "reg0" CONF_UICR_ERASE = "uicr_erase" @@ -121,6 +122,7 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema, } ), + cv.Optional(CONF_DCDC, default=True): cv.boolean, cv.Optional(CONF_REG0): cv.Schema( { cv.Required(CONF_VOLTAGE): cv.All( @@ -196,6 +198,7 @@ async def to_code(config: ConfigType) -> None: if dfu_config := config.get(CONF_DFU): CORE.add_job(_dfu_to_code, dfu_config) + zephyr_add_prj_conf("BOARD_ENABLE_DCDC", config[CONF_DCDC]) if reg0_config := config.get(CONF_REG0): value = VOLTAGE_LEVELS.index(reg0_config[CONF_VOLTAGE]) diff --git a/tests/components/nrf52/test.nrf52-adafruit.yaml b/tests/components/nrf52/test.nrf52-adafruit.yaml index 72fd015953..5fa0d6e88f 100644 --- a/tests/components/nrf52/test.nrf52-adafruit.yaml +++ b/tests/components/nrf52/test.nrf52-adafruit.yaml @@ -15,6 +15,7 @@ nrf52: inverted: true mode: output: true + dcdc: False reg0: voltage: 2.1V uicr_erase: true From a1ab19d127f3aeb28a22fbc401beff00c2799529 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 Nov 2025 21:44:19 -0600 Subject: [PATCH 0210/1145] [ci] Reduce release time by removing 21 redundant ESP32-S3 IDF tests (#11850) --- .../binary_sensor/test.esp32-s3-idf.yaml | 2 - .../bme68x_bsec2_i2c/test.esp32-s3-idf.yaml | 4 -- tests/components/debug/test.esp32-s3-idf.yaml | 1 - .../matrix_keypad/test.esp32-s3-idf.yaml | 15 ------ .../components/mcp3221/test.esp32-s3-idf.yaml | 4 -- .../mlx90393/test.esp32-s3-idf.yaml | 4 -- tests/components/npi19/test.esp32-s3-idf.yaml | 4 -- tests/components/ntc/test.esp32-s3-idf.yaml | 4 -- .../resistance/test.esp32-s3-idf.yaml | 4 -- .../components/switch/test.esp32-s3-idf.yaml | 2 - .../components/tem3200/test.esp32-s3-idf.yaml | 8 ---- .../template/test.esp32-s3-idf.yaml | 2 - ...max_with_usb_serial_jtag.esp32-s3-idf.yaml | 48 ------------------- .../wk2132_i2c/test.esp32-s3-idf.yaml | 9 ---- .../wk2132_spi/test.esp32-s3-idf.yaml | 11 ----- .../wk2168_i2c/test.esp32-s3-idf.yaml | 9 ---- .../wk2168_spi/test.esp32-s3-idf.yaml | 11 ----- .../wk2204_i2c/test.esp32-s3-idf.yaml | 9 ---- .../wk2204_spi/test.esp32-s3-idf.yaml | 11 ----- .../wk2212_i2c/test.esp32-s3-idf.yaml | 9 ---- .../wk2212_spi/test.esp32-s3-idf.yaml | 11 ----- 21 files changed, 182 deletions(-) delete mode 100644 tests/components/binary_sensor/test.esp32-s3-idf.yaml delete mode 100644 tests/components/bme68x_bsec2_i2c/test.esp32-s3-idf.yaml delete mode 100644 tests/components/debug/test.esp32-s3-idf.yaml delete mode 100644 tests/components/matrix_keypad/test.esp32-s3-idf.yaml delete mode 100644 tests/components/mcp3221/test.esp32-s3-idf.yaml delete mode 100644 tests/components/mlx90393/test.esp32-s3-idf.yaml delete mode 100644 tests/components/npi19/test.esp32-s3-idf.yaml delete mode 100644 tests/components/ntc/test.esp32-s3-idf.yaml delete mode 100644 tests/components/resistance/test.esp32-s3-idf.yaml delete mode 100644 tests/components/switch/test.esp32-s3-idf.yaml delete mode 100644 tests/components/tem3200/test.esp32-s3-idf.yaml delete mode 100644 tests/components/template/test.esp32-s3-idf.yaml delete mode 100644 tests/components/uart/test-uart_max_with_usb_serial_jtag.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2132_i2c/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2132_spi/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2168_i2c/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2168_spi/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2204_i2c/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2204_spi/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2212_i2c/test.esp32-s3-idf.yaml delete mode 100644 tests/components/wk2212_spi/test.esp32-s3-idf.yaml diff --git a/tests/components/binary_sensor/test.esp32-s3-idf.yaml b/tests/components/binary_sensor/test.esp32-s3-idf.yaml deleted file mode 100644 index 25cb37a0b4..0000000000 --- a/tests/components/binary_sensor/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - common: !include common.yaml diff --git a/tests/components/bme68x_bsec2_i2c/test.esp32-s3-idf.yaml b/tests/components/bme68x_bsec2_i2c/test.esp32-s3-idf.yaml deleted file mode 100644 index 0fd8684a2c..0000000000 --- a/tests/components/bme68x_bsec2_i2c/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/debug/test.esp32-s3-idf.yaml b/tests/components/debug/test.esp32-s3-idf.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/debug/test.esp32-s3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/matrix_keypad/test.esp32-s3-idf.yaml b/tests/components/matrix_keypad/test.esp32-s3-idf.yaml deleted file mode 100644 index a491f2ed59..0000000000 --- a/tests/components/matrix_keypad/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,15 +0,0 @@ -packages: - common: !include common.yaml - -matrix_keypad: - id: keypad - rows: - - pin: 10 - - pin: 11 - columns: - - pin: 12 - - pin: 13 - keys: "1234" - has_pulldowns: true - on_key: - - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/mcp3221/test.esp32-s3-idf.yaml b/tests/components/mcp3221/test.esp32-s3-idf.yaml deleted file mode 100644 index 0fd8684a2c..0000000000 --- a/tests/components/mcp3221/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/mlx90393/test.esp32-s3-idf.yaml b/tests/components/mlx90393/test.esp32-s3-idf.yaml deleted file mode 100644 index 0fd8684a2c..0000000000 --- a/tests/components/mlx90393/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/npi19/test.esp32-s3-idf.yaml b/tests/components/npi19/test.esp32-s3-idf.yaml deleted file mode 100644 index 0fd8684a2c..0000000000 --- a/tests/components/npi19/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/ntc/test.esp32-s3-idf.yaml b/tests/components/ntc/test.esp32-s3-idf.yaml deleted file mode 100644 index 37fb325f4a..0000000000 --- a/tests/components/ntc/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -substitutions: - pin: GPIO4 - -<<: !include common.yaml diff --git a/tests/components/resistance/test.esp32-s3-idf.yaml b/tests/components/resistance/test.esp32-s3-idf.yaml deleted file mode 100644 index 1910f325ae..0000000000 --- a/tests/components/resistance/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -substitutions: - pin: GPIO1 - -<<: !include common.yaml diff --git a/tests/components/switch/test.esp32-s3-idf.yaml b/tests/components/switch/test.esp32-s3-idf.yaml deleted file mode 100644 index 25cb37a0b4..0000000000 --- a/tests/components/switch/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - common: !include common.yaml diff --git a/tests/components/tem3200/test.esp32-s3-idf.yaml b/tests/components/tem3200/test.esp32-s3-idf.yaml deleted file mode 100644 index e9d826aa7c..0000000000 --- a/tests/components/tem3200/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -substitutions: - scl_pin: GPIO40 - sda_pin: GPIO41 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/template/test.esp32-s3-idf.yaml b/tests/components/template/test.esp32-s3-idf.yaml deleted file mode 100644 index 25cb37a0b4..0000000000 --- a/tests/components/template/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - common: !include common.yaml diff --git a/tests/components/uart/test-uart_max_with_usb_serial_jtag.esp32-s3-idf.yaml b/tests/components/uart/test-uart_max_with_usb_serial_jtag.esp32-s3-idf.yaml deleted file mode 100644 index 88a806eb92..0000000000 --- a/tests/components/uart/test-uart_max_with_usb_serial_jtag.esp32-s3-idf.yaml +++ /dev/null @@ -1,48 +0,0 @@ -<<: !include ../logger/common-usb_serial_jtag.yaml - -esphome: - on_boot: - then: - - uart.write: - id: uart_1 - data: 'Hello World' - - uart.write: - id: uart_1 - data: [0x00, 0x20, 0x42] - -uart: - - id: uart_1 - tx_pin: 4 - rx_pin: 5 - flow_control_pin: 6 - baud_rate: 9600 - data_bits: 8 - rx_buffer_size: 512 - rx_full_threshold: 10 - rx_timeout: 1 - parity: EVEN - stop_bits: 2 - - - id: uart_2 - tx_pin: 7 - rx_pin: 8 - flow_control_pin: 9 - baud_rate: 9600 - data_bits: 8 - rx_buffer_size: 512 - rx_full_threshold: 10 - rx_timeout: 1 - parity: EVEN - stop_bits: 2 - - - id: uart_3 - tx_pin: 10 - rx_pin: 11 - flow_control_pin: 12 - baud_rate: 9600 - data_bits: 8 - rx_buffer_size: 512 - rx_full_threshold: 10 - rx_timeout: 1 - parity: EVEN - stop_bits: 2 diff --git a/tests/components/wk2132_i2c/test.esp32-s3-idf.yaml b/tests/components/wk2132_i2c/test.esp32-s3-idf.yaml deleted file mode 100644 index d7b149a6fd..0000000000 --- a/tests/components/wk2132_i2c/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - scl_pin: GPIO40 - sda_pin: GPIO41 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - uart_bridge_2: !include ../../test_build_components/common/uart_bridge_2/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2132_spi/test.esp32-s3-idf.yaml b/tests/components/wk2132_spi/test.esp32-s3-idf.yaml deleted file mode 100644 index 9c7d36996e..0000000000 --- a/tests/components/wk2132_spi/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,11 +0,0 @@ -substitutions: - clk_pin: GPIO40 - miso_pin: GPIO41 - mosi_pin: GPIO6 - cs_pin: GPIO19 - -packages: - spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml - uart_bridge_2: !include ../../test_build_components/common/uart_bridge_2/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2168_i2c/test.esp32-s3-idf.yaml b/tests/components/wk2168_i2c/test.esp32-s3-idf.yaml deleted file mode 100644 index 115812be97..0000000000 --- a/tests/components/wk2168_i2c/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - scl_pin: GPIO40 - sda_pin: GPIO41 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2168_spi/test.esp32-s3-idf.yaml b/tests/components/wk2168_spi/test.esp32-s3-idf.yaml deleted file mode 100644 index 374fe64d16..0000000000 --- a/tests/components/wk2168_spi/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,11 +0,0 @@ -substitutions: - clk_pin: GPIO40 - miso_pin: GPIO41 - mosi_pin: GPIO6 - cs_pin: GPIO19 - -packages: - spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml - uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2204_i2c/test.esp32-s3-idf.yaml b/tests/components/wk2204_i2c/test.esp32-s3-idf.yaml deleted file mode 100644 index 115812be97..0000000000 --- a/tests/components/wk2204_i2c/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - scl_pin: GPIO40 - sda_pin: GPIO41 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2204_spi/test.esp32-s3-idf.yaml b/tests/components/wk2204_spi/test.esp32-s3-idf.yaml deleted file mode 100644 index 374fe64d16..0000000000 --- a/tests/components/wk2204_spi/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,11 +0,0 @@ -substitutions: - clk_pin: GPIO40 - miso_pin: GPIO41 - mosi_pin: GPIO6 - cs_pin: GPIO19 - -packages: - spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml - uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2212_i2c/test.esp32-s3-idf.yaml b/tests/components/wk2212_i2c/test.esp32-s3-idf.yaml deleted file mode 100644 index 115812be97..0000000000 --- a/tests/components/wk2212_i2c/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - scl_pin: GPIO40 - sda_pin: GPIO41 - -packages: - i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml - uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml - -<<: !include common.yaml diff --git a/tests/components/wk2212_spi/test.esp32-s3-idf.yaml b/tests/components/wk2212_spi/test.esp32-s3-idf.yaml deleted file mode 100644 index 374fe64d16..0000000000 --- a/tests/components/wk2212_spi/test.esp32-s3-idf.yaml +++ /dev/null @@ -1,11 +0,0 @@ -substitutions: - clk_pin: GPIO40 - miso_pin: GPIO41 - mosi_pin: GPIO6 - cs_pin: GPIO19 - -packages: - spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml - uart_bridge_4: !include ../../test_build_components/common/uart_bridge_4/esp32-s3-idf.yaml - -<<: !include common.yaml From 4f088c93c9f9135cb948dd6317b8024eeeeb888a Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 12 Nov 2025 10:41:22 -0500 Subject: [PATCH 0211/1145] [esp32] Update the recommended platform to 55.03.31-2 (#11865) --- esphome/components/esp32/__init__.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 6981662d77..61511cba0c 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -334,12 +334,14 @@ def _is_framework_url(source: str) -> str: # - https://github.com/espressif/arduino-esp32/releases ARDUINO_FRAMEWORK_VERSION_LOOKUP = { "recommended": cv.Version(3, 3, 2), - "latest": cv.Version(3, 3, 2), - "dev": cv.Version(3, 3, 2), + "latest": cv.Version(3, 3, 4), + "dev": cv.Version(3, 3, 4), } ARDUINO_PLATFORM_VERSION_LOOKUP = { - cv.Version(3, 3, 2): cv.Version(55, 3, 31, "1"), - cv.Version(3, 3, 1): cv.Version(55, 3, 31, "1"), + cv.Version(3, 3, 4): cv.Version(55, 3, 31, "2"), + cv.Version(3, 3, 3): cv.Version(55, 3, 31, "2"), + cv.Version(3, 3, 2): cv.Version(55, 3, 31, "2"), + cv.Version(3, 3, 1): cv.Version(55, 3, 31, "2"), cv.Version(3, 3, 0): cv.Version(55, 3, 30, "2"), cv.Version(3, 2, 1): cv.Version(54, 3, 21, "2"), cv.Version(3, 2, 0): cv.Version(54, 3, 20), @@ -357,8 +359,8 @@ ESP_IDF_FRAMEWORK_VERSION_LOOKUP = { "dev": cv.Version(5, 5, 1), } ESP_IDF_PLATFORM_VERSION_LOOKUP = { - cv.Version(5, 5, 1): cv.Version(55, 3, 31, "1"), - cv.Version(5, 5, 0): cv.Version(55, 3, 31, "1"), + cv.Version(5, 5, 1): cv.Version(55, 3, 31, "2"), + cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"), cv.Version(5, 4, 3): cv.Version(55, 3, 32), cv.Version(5, 4, 2): cv.Version(54, 3, 21, "2"), cv.Version(5, 4, 1): cv.Version(54, 3, 21, "2"), @@ -373,9 +375,9 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = { # The platform-espressif32 version # - https://github.com/pioarduino/platform-espressif32/releases PLATFORM_VERSION_LOOKUP = { - "recommended": cv.Version(55, 3, 31, "1"), - "latest": cv.Version(55, 3, 31, "1"), - "dev": cv.Version(55, 3, 31, "1"), + "recommended": cv.Version(55, 3, 31, "2"), + "latest": cv.Version(55, 3, 31, "2"), + "dev": cv.Version(55, 3, 31, "2"), } From a859ecaad1cb64d7648c5293771c93109f58c2c5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 11:56:19 -0600 Subject: [PATCH 0212/1145] [core] Fix wait_until hanging when used in on_boot automations (#11869) --- esphome/core/base_automation.h | 7 +- .../fixtures/wait_until_on_boot.yaml | 47 ++++++++++ tests/integration/test_wait_until_on_boot.py | 91 +++++++++++++++++++ 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 tests/integration/fixtures/wait_until_on_boot.yaml create mode 100644 tests/integration/test_wait_until_on_boot.py diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 6f392c8959..a5e6139182 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -412,7 +412,12 @@ template class WaitUntilAction : public Action, public Co void setup() override { // Start with loop disabled - only enable when there's work to do - this->disable_loop(); + // IMPORTANT: Only disable if num_running_ is 0, otherwise play_complex() was already + // called before our setup() (e.g., from on_boot trigger at same priority level) + // and we must not undo its enable_loop() call + if (this->num_running_ == 0) { + this->disable_loop(); + } } void play_complex(const Ts &...x) override { diff --git a/tests/integration/fixtures/wait_until_on_boot.yaml b/tests/integration/fixtures/wait_until_on_boot.yaml new file mode 100644 index 0000000000..358bef971b --- /dev/null +++ b/tests/integration/fixtures/wait_until_on_boot.yaml @@ -0,0 +1,47 @@ +# Test for wait_until in on_boot automation +# Reproduces bug where wait_until in on_boot would hang forever +# because WaitUntilAction::setup() would disable_loop() after +# play_complex() had already enabled it. + +esphome: + name: wait-until-on-boot + on_boot: + then: + - logger.log: "on_boot: Starting wait_until test" + - globals.set: + id: on_boot_started + value: 'true' + - wait_until: + condition: + lambda: return id(test_flag); + timeout: 5s + - logger.log: "on_boot: wait_until completed successfully" + +host: + +logger: + level: DEBUG + +globals: + - id: on_boot_started + type: bool + initial_value: 'false' + - id: test_flag + type: bool + initial_value: 'false' + +api: + actions: + - action: set_test_flag + then: + - globals.set: + id: test_flag + value: 'true' + - action: check_on_boot_started + then: + - lambda: |- + if (id(on_boot_started)) { + ESP_LOGI("test", "on_boot has started"); + } else { + ESP_LOGI("test", "on_boot has NOT started"); + } diff --git a/tests/integration/test_wait_until_on_boot.py b/tests/integration/test_wait_until_on_boot.py new file mode 100644 index 0000000000..b42c530c54 --- /dev/null +++ b/tests/integration/test_wait_until_on_boot.py @@ -0,0 +1,91 @@ +"""Integration test for wait_until in on_boot automation. + +This test validates that wait_until works correctly when triggered from on_boot, +which runs at the same setup priority as WaitUntilAction itself. This was broken +before the fix because WaitUntilAction::setup() would unconditionally disable_loop(), +even if play_complex() had already been called and enabled the loop. + +The bug: on_boot fires during StartupTrigger::setup(), which calls WaitUntilAction::play_complex() +before WaitUntilAction::setup() has run. Then when WaitUntilAction::setup() runs, it calls +disable_loop(), undoing the enable_loop() from play_complex(), causing wait_until to hang forever. +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_wait_until_on_boot( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that wait_until works in on_boot automation with a condition that becomes true later.""" + loop = asyncio.get_running_loop() + + on_boot_started = False + on_boot_completed = False + + on_boot_started_pattern = re.compile(r"on_boot: Starting wait_until test") + on_boot_complete_pattern = re.compile(r"on_boot: wait_until completed successfully") + + on_boot_started_future = loop.create_future() + on_boot_complete_future = loop.create_future() + + def check_output(line: str) -> None: + """Check log output for test progress.""" + nonlocal on_boot_started, on_boot_completed + + if on_boot_started_pattern.search(line): + on_boot_started = True + if not on_boot_started_future.done(): + on_boot_started_future.set_result(True) + + if on_boot_complete_pattern.search(line): + on_boot_completed = True + if not on_boot_complete_future.done(): + on_boot_complete_future.set_result(True) + + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Wait for on_boot to start + await asyncio.wait_for(on_boot_started_future, timeout=10.0) + assert on_boot_started, "on_boot did not start" + + # At this point, on_boot is blocked in wait_until waiting for test_flag to become true + # If the bug exists, wait_until's loop is disabled and it will never complete + # even after we set the flag + + # Give a moment for setup to complete + await asyncio.sleep(0.5) + + # Now set the flag that wait_until is waiting for + _, services = await client.list_entities_services() + set_flag_service = next( + (s for s in services if s.name == "set_test_flag"), None + ) + assert set_flag_service is not None, "set_test_flag service not found" + + client.execute_service(set_flag_service, {}) + + # If the fix works, wait_until's loop() will check the condition and proceed + # If the bug exists, wait_until is stuck with disabled loop and will timeout + try: + await asyncio.wait_for(on_boot_complete_future, timeout=2.0) + assert on_boot_completed, ( + "on_boot wait_until did not complete after flag was set" + ) + except TimeoutError: + pytest.fail( + "wait_until in on_boot did not complete within 2s after condition became true. " + "This indicates the bug where WaitUntilAction::setup() disables the loop " + "after play_complex() has already enabled it." + ) From 6df0264d51d7b0be1cecb2870cf076b55e0b9092 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 14:26:36 -0600 Subject: [PATCH 0213/1145] [api] Eliminate heap allocations when transmitting Event types (#11773) --- esphome/components/api/api_connection.cpp | 14 +++---- esphome/components/api/api_connection.h | 48 ++++------------------- 2 files changed, 14 insertions(+), 48 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 7eb61f08b6..ca9ddaedf4 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1294,11 +1294,11 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe #endif #ifdef USE_EVENT -void APIConnection::send_event(event::Event *event, const std::string &event_type) { +void APIConnection::send_event(event::Event *event, const char *event_type) { this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE, EventResponse::ESTIMATED_SIZE); } -uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn, +uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn, uint32_t remaining_size, bool is_single) { EventResponse resp; resp.set_event_type(StringRef(event_type)); @@ -1650,9 +1650,7 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c // O(n) but optimized for RAM and not performance. for (auto &item : items) { if (item.entity == entity && item.message_type == message_type) { - // Clean up old creator before replacing - item.creator.cleanup(message_type); - // Move assign the new creator + // Replace with new creator item.creator = std::move(creator); return; } @@ -1822,7 +1820,7 @@ void APIConnection::process_batch_() { // Handle remaining items more efficiently if (items_processed < this->deferred_batch_.size()) { - // Remove processed items from the beginning with proper cleanup + // Remove processed items from the beginning this->deferred_batch_.remove_front(items_processed); // Reschedule for remaining items this->schedule_batch_(); @@ -1835,10 +1833,10 @@ void APIConnection::process_batch_() { uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, uint8_t message_type) const { #ifdef USE_EVENT - // Special case: EventResponse uses string pointer + // Special case: EventResponse uses const char * pointer if (message_type == EventResponse::MESSAGE_TYPE) { auto *e = static_cast(entity); - return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single); + return APIConnection::try_send_event_response(e, data_.const_char_ptr, conn, remaining_size, is_single); } #endif diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 284fa11a95..a77c93a2d5 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -177,7 +177,7 @@ class APIConnection final : public APIServerConnection { #endif #ifdef USE_EVENT - void send_event(event::Event *event, const std::string &event_type); + void send_event(event::Event *event, const char *event_type); #endif #ifdef USE_UPDATE @@ -450,7 +450,7 @@ class APIConnection final : public APIServerConnection { bool is_single); #endif #ifdef USE_EVENT - static uint16_t try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn, + static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn, uint32_t remaining_size, bool is_single); static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single); #endif @@ -508,10 +508,8 @@ class APIConnection final : public APIServerConnection { // Constructor for function pointer MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; } - // Constructor for string state capture - explicit MessageCreator(const std::string &str_value) { data_.string_ptr = new std::string(str_value); } - - // No destructor - cleanup must be called explicitly with message_type + // Constructor for const char * (Event types - no allocation needed) + explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; } // Delete copy operations - MessageCreator should only be moved MessageCreator(const MessageCreator &other) = delete; @@ -523,8 +521,6 @@ class APIConnection final : public APIServerConnection { // Move assignment MessageCreator &operator=(MessageCreator &&other) noexcept { if (this != &other) { - // IMPORTANT: Caller must ensure cleanup() was called if this contains a string! - // In our usage, this happens in add_item() deduplication and vector::erase() data_ = other.data_; other.data_.function_ptr = nullptr; } @@ -535,20 +531,10 @@ class APIConnection final : public APIServerConnection { uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, uint8_t message_type) const; - // Manual cleanup method - must be called before destruction for string types - void cleanup(uint8_t message_type) { -#ifdef USE_EVENT - if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) { - delete data_.string_ptr; - data_.string_ptr = nullptr; - } -#endif - } - private: union Data { MessageCreatorPtr function_ptr; - std::string *string_ptr; + const char *const_char_ptr; } data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before }; @@ -568,42 +554,24 @@ class APIConnection final : public APIServerConnection { std::vector items; uint32_t batch_start_time{0}; - private: - // Helper to cleanup items from the beginning - void cleanup_items_(size_t count) { - for (size_t i = 0; i < count; i++) { - items[i].creator.cleanup(items[i].message_type); - } - } - - public: DeferredBatch() { // Pre-allocate capacity for typical batch sizes to avoid reallocation items.reserve(8); } - ~DeferredBatch() { - // Ensure cleanup of any remaining items - clear(); - } - // Add item to the batch void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size); // Add item to the front of the batch (for high priority messages like ping) void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size); - // Clear all items with proper cleanup + // Clear all items void clear() { - cleanup_items_(items.size()); items.clear(); batch_start_time = 0; } - // Remove processed items from the front with proper cleanup - void remove_front(size_t count) { - cleanup_items_(count); - items.erase(items.begin(), items.begin() + count); - } + // Remove processed items from the front + void remove_front(size_t count) { items.erase(items.begin(), items.begin() + count); } bool empty() const { return items.empty(); } size_t size() const { return items.size(); } From 799cfe1de4353022304af4e9c403c69119735c0c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 14:26:46 -0600 Subject: [PATCH 0214/1145] [esp32_ble_tracker] Use initializer_list to eliminate compiler warning and reduce flash usage (#11861) --- esphome/components/esp32_ble_tracker/automation.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index 054cbaa7df..bbf7992fa4 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -10,7 +10,7 @@ namespace esphome::esp32_ble_tracker { class ESPBTAdvertiseTrigger : public Trigger, public ESPBTDeviceListener { public: explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } - void set_addresses(const std::vector &addresses) { this->address_vec_ = addresses; } + void set_addresses(std::initializer_list addresses) { this->address_vec_ = addresses; } bool parse_device(const ESPBTDevice &device) override { uint64_t u64_addr = device.address_uint64(); From 5a2e6697e0ed9a11494eb52a776238b878b69e4b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 17:42:50 -0600 Subject: [PATCH 0215/1145] [api][event] Send events immediately to prevent loss during rapid triggers (#11777) --- esphome/components/api/api_connection.cpp | 4 +- esphome/components/api/api_connection.h | 54 ++++++++++++++++++----- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index ca9ddaedf4..b4230576de 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1295,8 +1295,8 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe #ifdef USE_EVENT void APIConnection::send_event(event::Event *event, const char *event_type) { - this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE, - EventResponse::ESTIMATED_SIZE); + this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE, + EventResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn, uint32_t remaining_size, bool is_single) { diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index a77c93a2d5..6cfd108927 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -650,21 +650,30 @@ class APIConnection final : public APIServerConnection { } #endif + // Helper to check if a message type should bypass batching + // Returns true if: + // 1. It's an UpdateStateResponse (always send immediately to handle cases where + // the main loop is blocked, e.g., during OTA updates) + // 2. It's an EventResponse (events are edge-triggered - every occurrence matters) + // 3. OR: User has opted into immediate sending (should_try_send_immediately = true + // AND batch_delay = 0) + inline bool should_send_immediately_(uint8_t message_type) const { + return ( +#ifdef USE_UPDATE + message_type == UpdateStateResponse::MESSAGE_TYPE || +#endif +#ifdef USE_EVENT + message_type == EventResponse::MESSAGE_TYPE || +#endif + (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0)); + } + // Helper method to send a message either immediately or via batching + // Tries immediate send if should_send_immediately_() returns true and buffer has space + // Falls back to batching if immediate send fails or isn't applicable bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type, uint8_t estimated_size) { - // Try to send immediately if: - // 1. It's an UpdateStateResponse (always send immediately to handle cases where - // the main loop is blocked, e.g., during OTA updates) - // 2. OR: We should try to send immediately (should_try_send_immediately = true) - // AND Batch delay is 0 (user has opted in to immediate sending) - // 3. AND: Buffer has space available - if (( -#ifdef USE_UPDATE - message_type == UpdateStateResponse::MESSAGE_TYPE || -#endif - (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0)) && - this->helper_->can_write_without_blocking()) { + if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) { // Now actually encode and send if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) && this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) { @@ -682,6 +691,27 @@ class APIConnection final : public APIServerConnection { return this->schedule_message_(entity, creator, message_type, estimated_size); } + // Overload for MessageCreator (used by events which need to capture event_type) + bool send_message_smart_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) { + // Try to send immediately if message type should bypass batching and buffer has space + if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) { + // Now actually encode and send + if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type) && + this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) { +#ifdef HAS_PROTO_MESSAGE_DUMP + // Log the message in verbose mode + this->log_proto_message_(entity, creator, message_type); +#endif + return true; + } + + // If immediate send failed, fall through to batching + } + + // Fall back to scheduled batching + return this->schedule_message_(entity, std::move(creator), message_type, estimated_size); + } + // Helper function to schedule a deferred message with known message type bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) { this->deferred_batch_.add_item(entity, std::move(creator), message_type, estimated_size); From 72da3d0f1e3e202f7546299316bf242fbc81b439 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 17:55:06 -0600 Subject: [PATCH 0216/1145] [thermostat] Replace std::map with FixedVector, reduce flash usage (#11875) --- esphome/components/thermostat/climate.py | 38 ++++++++- .../thermostat/thermostat_climate.cpp | 82 +++++++++++------- .../thermostat/thermostat_climate.h | 33 +++++-- .../climate_custom_fan_modes_and_presets.yaml | 1 + .../integration/test_climate_custom_modes.py | 85 ++++++++++++++++++- 5 files changed, 195 insertions(+), 44 deletions(-) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index a928d208f3..a3c155aac0 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -945,6 +945,10 @@ async def to_code(config): cg.add(var.set_humidity_hysteresis(config[CONF_HUMIDITY_HYSTERESIS])) if CONF_PRESET in config: + # Separate standard and custom presets, and build preset config variables + standard_presets: list[tuple[cg.MockObj, cg.MockObj]] = [] + custom_presets: list[tuple[str, cg.MockObj]] = [] + for preset_config in config[CONF_PRESET]: name = preset_config[CONF_NAME] standard_preset = None @@ -987,9 +991,39 @@ async def to_code(config): ) if standard_preset is not None: - cg.add(var.set_preset_config(standard_preset, preset_target_variable)) + standard_presets.append((standard_preset, preset_target_variable)) else: - cg.add(var.set_custom_preset_config(name, preset_target_variable)) + custom_presets.append((name, preset_target_variable)) + + # Build initializer list for standard presets + if standard_presets: + cg.add( + var.set_preset_config( + [ + cg.StructInitializer( + thermostat_ns.struct("ThermostatPresetEntry"), + ("preset", preset), + ("config", preset_var), + ) + for preset, preset_var in standard_presets + ] + ) + ) + + # Build initializer list for custom presets + if custom_presets: + cg.add( + var.set_custom_preset_config( + [ + cg.StructInitializer( + thermostat_ns.struct("ThermostatCustomPresetEntry"), + ("name", cg.RawExpression(f'"{name}"')), + ("config", preset_var), + ) + for name, preset_var in custom_presets + ] + ) + ) if CONF_DEFAULT_PRESET in config: default_preset_name = config[CONF_DEFAULT_PRESET] diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index d533ef93ec..2b51f58f4f 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -53,8 +53,8 @@ void ThermostatClimate::setup() { if (use_default_preset) { if (this->default_preset_ != climate::ClimatePreset::CLIMATE_PRESET_NONE) { this->change_preset_(this->default_preset_); - } else if (!this->default_custom_preset_.empty()) { - this->change_custom_preset_(this->default_custom_preset_.c_str()); + } else if (this->default_custom_preset_ != nullptr) { + this->change_custom_preset_(this->default_custom_preset_); } } @@ -319,16 +319,16 @@ climate::ClimateTraits ThermostatClimate::traits() { if (this->supports_swing_mode_vertical_) traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL); - for (auto &it : this->preset_config_) { - traits.add_supported_preset(it.first); + for (const auto &entry : this->preset_config_) { + traits.add_supported_preset(entry.preset); } - // Extract custom preset names from the custom_preset_config_ map + // Extract custom preset names from the custom_preset_config_ vector if (!this->custom_preset_config_.empty()) { std::vector custom_preset_names; custom_preset_names.reserve(this->custom_preset_config_.size()); - for (const auto &it : this->custom_preset_config_) { - custom_preset_names.push_back(it.first.c_str()); + for (const auto &entry : this->custom_preset_config_) { + custom_preset_names.push_back(entry.name); } traits.set_supported_custom_presets(custom_preset_names); } @@ -1154,12 +1154,18 @@ void ThermostatClimate::dump_preset_config_(const char *preset_name, const Therm } void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { - auto config = this->preset_config_.find(preset); + // Linear search through preset configurations + const ThermostatClimateTargetTempConfig *config = nullptr; + for (const auto &entry : this->preset_config_) { + if (entry.preset == preset) { + config = &entry.config; + break; + } + } - if (config != this->preset_config_.end()) { + if (config != nullptr) { ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset))); - if (this->change_preset_internal_(config->second) || (!this->preset.has_value()) || - this->preset.value() != preset) { + if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) { // Fire any preset changed trigger if defined Trigger<> *trig = this->preset_change_trigger_; this->set_preset_(preset); @@ -1178,11 +1184,18 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { } void ThermostatClimate::change_custom_preset_(const char *custom_preset) { - auto config = this->custom_preset_config_.find(custom_preset); + // Linear search through custom preset configurations + const ThermostatClimateTargetTempConfig *config = nullptr; + for (const auto &entry : this->custom_preset_config_) { + if (strcmp(entry.name, custom_preset) == 0) { + config = &entry.config; + break; + } + } - if (config != this->custom_preset_config_.end()) { + if (config != nullptr) { ESP_LOGV(TAG, "Custom preset %s requested", custom_preset); - if (this->change_preset_internal_(config->second) || !this->has_custom_preset() || + if (this->change_preset_internal_(*config) || !this->has_custom_preset() || strcmp(this->get_custom_preset(), custom_preset) != 0) { // Fire any preset changed trigger if defined Trigger<> *trig = this->preset_change_trigger_; @@ -1247,14 +1260,12 @@ bool ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTem return something_changed; } -void ThermostatClimate::set_preset_config(climate::ClimatePreset preset, - const ThermostatClimateTargetTempConfig &config) { - this->preset_config_[preset] = config; +void ThermostatClimate::set_preset_config(std::initializer_list presets) { + this->preset_config_ = presets; } -void ThermostatClimate::set_custom_preset_config(const std::string &name, - const ThermostatClimateTargetTempConfig &config) { - this->custom_preset_config_[name] = config; +void ThermostatClimate::set_custom_preset_config(std::initializer_list presets) { + this->custom_preset_config_ = presets; } ThermostatClimate::ThermostatClimate() @@ -1293,8 +1304,16 @@ ThermostatClimate::ThermostatClimate() humidity_control_humidify_action_trigger_(new Trigger<>()), humidity_control_off_action_trigger_(new Trigger<>()) {} -void ThermostatClimate::set_default_preset(const std::string &custom_preset) { - this->default_custom_preset_ = custom_preset; +void ThermostatClimate::set_default_preset(const char *custom_preset) { + // Find the preset in custom_preset_config_ and store pointer from there + for (const auto &entry : this->custom_preset_config_) { + if (strcmp(entry.name, custom_preset) == 0) { + this->default_custom_preset_ = entry.name; + return; + } + } + // If not found, it will be caught during validation + this->default_custom_preset_ = nullptr; } void ThermostatClimate::set_default_preset(climate::ClimatePreset preset) { this->default_preset_ = preset; } @@ -1605,19 +1624,22 @@ void ThermostatClimate::dump_config() { if (!this->preset_config_.empty()) { ESP_LOGCONFIG(TAG, " Supported PRESETS:"); - for (auto &it : this->preset_config_) { - const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(it.first)); - ESP_LOGCONFIG(TAG, " %s:%s", preset_name, it.first == this->default_preset_ ? " (default)" : ""); - this->dump_preset_config_(preset_name, it.second); + for (const auto &entry : this->preset_config_) { + const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(entry.preset)); + ESP_LOGCONFIG(TAG, " %s:%s", preset_name, entry.preset == this->default_preset_ ? " (default)" : ""); + this->dump_preset_config_(preset_name, entry.config); } } if (!this->custom_preset_config_.empty()) { ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS:"); - for (auto &it : this->custom_preset_config_) { - const auto *preset_name = it.first.c_str(); - ESP_LOGCONFIG(TAG, " %s:%s", preset_name, it.first == this->default_custom_preset_ ? " (default)" : ""); - this->dump_preset_config_(preset_name, it.second); + for (const auto &entry : this->custom_preset_config_) { + const auto *preset_name = entry.name; + ESP_LOGCONFIG(TAG, " %s:%s", preset_name, + (this->default_custom_preset_ != nullptr && strcmp(entry.name, this->default_custom_preset_) == 0) + ? " (default)" + : ""); + this->dump_preset_config_(preset_name, entry.config); } } } diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index c9795d9666..76391f800c 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -3,12 +3,12 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" #include "esphome/components/climate/climate.h" #include "esphome/components/sensor/sensor.h" #include #include -#include namespace esphome { namespace thermostat { @@ -72,14 +72,29 @@ struct ThermostatClimateTargetTempConfig { optional mode_{}; }; +/// Entry for standard preset lookup +struct ThermostatPresetEntry { + climate::ClimatePreset preset; + ThermostatClimateTargetTempConfig config; +}; + +/// Entry for custom preset lookup +struct ThermostatCustomPresetEntry { + const char *name; + ThermostatClimateTargetTempConfig config; +}; + class ThermostatClimate : public climate::Climate, public Component { public: + using PresetEntry = ThermostatPresetEntry; + using CustomPresetEntry = ThermostatCustomPresetEntry; + ThermostatClimate(); void setup() override; void dump_config() override; void loop() override; - void set_default_preset(const std::string &custom_preset); + void set_default_preset(const char *custom_preset); void set_default_preset(climate::ClimatePreset preset); void set_on_boot_restore_from(OnBootRestoreFrom on_boot_restore_from); void set_set_point_minimum_differential(float differential); @@ -131,8 +146,8 @@ class ThermostatClimate : public climate::Climate, public Component { void set_supports_humidification(bool supports_humidification); void set_supports_two_points(bool supports_two_points); - void set_preset_config(climate::ClimatePreset preset, const ThermostatClimateTargetTempConfig &config); - void set_custom_preset_config(const std::string &name, const ThermostatClimateTargetTempConfig &config); + void set_preset_config(std::initializer_list presets); + void set_custom_preset_config(std::initializer_list presets); Trigger<> *get_cool_action_trigger() const; Trigger<> *get_supplemental_cool_action_trigger() const; @@ -516,9 +531,6 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *prev_swing_mode_trigger_{nullptr}; Trigger<> *prev_humidity_control_trigger_{nullptr}; - /// Default custom preset to use on start up - std::string default_custom_preset_{}; - /// Climate action timers std::array timer_{ ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)), @@ -534,9 +546,12 @@ class ThermostatClimate : public climate::Climate, public Component { }; /// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc) - std::map preset_config_{}; + FixedVector preset_config_{}; /// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset") - std::map custom_preset_config_{}; + FixedVector custom_preset_config_{}; + /// Default custom preset to use on start up (pointer to entry in custom_preset_config_) + private: + const char *default_custom_preset_{nullptr}; }; } // namespace thermostat diff --git a/tests/integration/fixtures/climate_custom_fan_modes_and_presets.yaml b/tests/integration/fixtures/climate_custom_fan_modes_and_presets.yaml index bf4ef9eafd..3996d0f169 100644 --- a/tests/integration/fixtures/climate_custom_fan_modes_and_presets.yaml +++ b/tests/integration/fixtures/climate_custom_fan_modes_and_presets.yaml @@ -14,6 +14,7 @@ climate: id: test_thermostat name: Test Thermostat Custom Modes sensor: thermostat_sensor + default_preset: "Eco Plus" preset: - name: Away default_target_temperature_low: 16°C diff --git a/tests/integration/test_climate_custom_modes.py b/tests/integration/test_climate_custom_modes.py index ce34959d88..67a7b0581a 100644 --- a/tests/integration/test_climate_custom_modes.py +++ b/tests/integration/test_climate_custom_modes.py @@ -2,9 +2,13 @@ from __future__ import annotations -from aioesphomeapi import ClimateInfo, ClimatePreset +import asyncio + +import aioesphomeapi +from aioesphomeapi import ClimateInfo, ClimatePreset, EntityState import pytest +from .state_utils import InitialStateHelper from .types import APIClientConnectedFactory, RunCompiledFunction @@ -14,15 +18,50 @@ async def test_climate_custom_fan_modes_and_presets( run_compiled: RunCompiledFunction, api_client_connected: APIClientConnectedFactory, ) -> None: - """Test that custom presets are properly exposed via API.""" + """Test that custom presets are properly exposed and can be changed.""" + loop = asyncio.get_running_loop() async with run_compiled(yaml_config), api_client_connected() as client: - # Get entities and services + states: dict[int, EntityState] = {} + super_saver_future: asyncio.Future[EntityState] = loop.create_future() + vacation_future: asyncio.Future[EntityState] = loop.create_future() + + def on_state(state: EntityState) -> None: + states[state.key] = state + if isinstance(state, aioesphomeapi.ClimateState): + # Wait for Super Saver preset + if ( + state.custom_preset == "Super Saver" + and state.target_temperature_low == 20.0 + and state.target_temperature_high == 24.0 + and not super_saver_future.done() + ): + super_saver_future.set_result(state) + # Wait for Vacation Mode preset + elif ( + state.custom_preset == "Vacation Mode" + and state.target_temperature_low == 15.0 + and state.target_temperature_high == 18.0 + and not vacation_future.done() + ): + vacation_future.set_result(state) + + # Get entities and set up state synchronization entities, services = await client.list_entities_services() + initial_state_helper = InitialStateHelper(entities) climate_infos = [e for e in entities if isinstance(e, ClimateInfo)] assert len(climate_infos) == 1, "Expected exactly 1 climate entity" test_climate = climate_infos[0] + # Subscribe with the wrapper that filters initial states + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + # Wait for all initial states to be broadcast + try: + await initial_state_helper.wait_for_initial_states() + except TimeoutError: + pytest.fail("Timeout waiting for initial states") + # Verify enum presets are exposed (from preset: config map) assert ClimatePreset.AWAY in test_climate.supported_presets, ( "Expected AWAY in enum presets" @@ -40,3 +79,43 @@ async def test_climate_custom_fan_modes_and_presets( assert "Vacation Mode" in custom_presets, ( "Expected 'Vacation Mode' in custom presets" ) + + # Get initial state and verify default preset + initial_state = initial_state_helper.initial_states.get(test_climate.key) + assert initial_state is not None, "Climate initial state not found" + assert isinstance(initial_state, aioesphomeapi.ClimateState) + assert initial_state.custom_preset == "Eco Plus", ( + f"Expected default preset 'Eco Plus', got '{initial_state.custom_preset}'" + ) + assert initial_state.target_temperature_low == 18.0, ( + f"Expected low temp 18.0, got {initial_state.target_temperature_low}" + ) + assert initial_state.target_temperature_high == 22.0, ( + f"Expected high temp 22.0, got {initial_state.target_temperature_high}" + ) + + # Test changing to "Super Saver" custom preset + client.climate_command(test_climate.key, custom_preset="Super Saver") + + try: + super_saver_state = await asyncio.wait_for(super_saver_future, timeout=5.0) + except TimeoutError: + pytest.fail("Super Saver preset change not received within 5 seconds") + + assert isinstance(super_saver_state, aioesphomeapi.ClimateState) + assert super_saver_state.custom_preset == "Super Saver" + assert super_saver_state.target_temperature_low == 20.0 + assert super_saver_state.target_temperature_high == 24.0 + + # Test changing to "Vacation Mode" custom preset + client.climate_command(test_climate.key, custom_preset="Vacation Mode") + + try: + vacation_state = await asyncio.wait_for(vacation_future, timeout=5.0) + except TimeoutError: + pytest.fail("Vacation Mode preset change not received within 5 seconds") + + assert isinstance(vacation_state, aioesphomeapi.ClimateState) + assert vacation_state.custom_preset == "Vacation Mode" + assert vacation_state.target_temperature_low == 15.0 + assert vacation_state.target_temperature_high == 18.0 From ff107a0674091d24e51a9f2c3fd04857d857b90a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 18:40:26 -0600 Subject: [PATCH 0217/1145] [mqtt] Fix crash with empty broker during upload/logs (#11866) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/mqtt.py | 13 ++++- tests/unit_tests/test_main.py | 50 +++++++++++++++++++ tests/unit_tests/test_mqtt.py | 91 +++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 tests/unit_tests/test_mqtt.py diff --git a/esphome/mqtt.py b/esphome/mqtt.py index 093ee64df4..0d50edbc2c 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -30,6 +30,7 @@ from esphome.const import ( from esphome.core import CORE, EsphomeError from esphome.helpers import get_int_env, get_str_env from esphome.log import AnsiFore, color +from esphome.types import ConfigType from esphome.util import safe_print _LOGGER = logging.getLogger(__name__) @@ -154,8 +155,12 @@ def show_discover(config, username=None, password=None, client_id=None): def get_esphome_device_ip( - config, username=None, password=None, client_id=None, timeout=25 -): + config: ConfigType, + username: str | None = None, + password: str | None = None, + client_id: str | None = None, + timeout: int | float = 25, +) -> list[str]: if CONF_MQTT not in config: raise EsphomeError( "Cannot discover IP via MQTT as the config does not include the mqtt: " @@ -166,6 +171,10 @@ def get_esphome_device_ip( "Cannot discover IP via MQTT as the config does not include the device name: " "component" ) + if not config[CONF_MQTT].get(CONF_BROKER): + raise EsphomeError( + "Cannot discover IP via MQTT as the broker is not configured" + ) dev_name = config[CONF_ESPHOME][CONF_NAME] dev_ip = None diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index 9e5f399381..ccbc5a1306 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -1166,6 +1166,56 @@ def test_upload_program_ota_with_mqtt_resolution( ) +def test_upload_program_ota_with_mqtt_empty_broker( + mock_mqtt_get_ip: Mock, + mock_is_ip_address: Mock, + mock_run_ota: Mock, + tmp_path: Path, + caplog: CaptureFixture, +) -> None: + """Test upload_program with OTA when MQTT broker is empty (issue #11653).""" + setup_core(address="192.168.1.50", platform=PLATFORM_ESP32, tmp_path=tmp_path) + + mock_is_ip_address.return_value = True + mock_mqtt_get_ip.side_effect = EsphomeError( + "Cannot discover IP via MQTT as the broker is not configured" + ) + mock_run_ota.return_value = (0, "192.168.1.50") + + config = { + CONF_OTA: [ + { + CONF_PLATFORM: CONF_ESPHOME, + CONF_PORT: 3232, + } + ], + CONF_MQTT: { + CONF_BROKER: "", + }, + CONF_MDNS: { + CONF_DISABLED: True, + }, + } + args = MockArgs(username="user", password="pass", client_id="client") + devices = ["MQTTIP", "192.168.1.50"] + + exit_code, host = upload_program(config, args, devices) + + assert exit_code == 0 + assert host == "192.168.1.50" + # Verify MQTT was attempted but failed gracefully + mock_mqtt_get_ip.assert_called_once_with(config, "user", "pass", "client") + # Verify we fell back to the IP address + expected_firmware = ( + tmp_path / ".esphome" / "build" / "test" / ".pioenvs" / "test" / "firmware.bin" + ) + mock_run_ota.assert_called_once_with( + ["192.168.1.50"], 3232, None, expected_firmware + ) + # Verify warning was logged + assert "MQTT IP discovery failed" in caplog.text + + @patch("esphome.__main__.importlib.import_module") def test_upload_program_platform_specific_handler( mock_import: Mock, diff --git a/tests/unit_tests/test_mqtt.py b/tests/unit_tests/test_mqtt.py new file mode 100644 index 0000000000..4c2c34dff1 --- /dev/null +++ b/tests/unit_tests/test_mqtt.py @@ -0,0 +1,91 @@ +"""Unit tests for esphome.mqtt module.""" + +from __future__ import annotations + +import pytest + +from esphome.const import CONF_BROKER, CONF_ESPHOME, CONF_MQTT, CONF_NAME +from esphome.core import EsphomeError +from esphome.mqtt import get_esphome_device_ip + + +def test_get_esphome_device_ip_empty_broker() -> None: + """Test that get_esphome_device_ip raises EsphomeError when broker is empty.""" + config = { + CONF_MQTT: { + CONF_BROKER: "", + }, + CONF_ESPHOME: { + CONF_NAME: "test-device", + }, + } + + with pytest.raises( + EsphomeError, + match="Cannot discover IP via MQTT as the broker is not configured", + ): + get_esphome_device_ip(config) + + +def test_get_esphome_device_ip_none_broker() -> None: + """Test that get_esphome_device_ip raises EsphomeError when broker is None.""" + config = { + CONF_MQTT: { + CONF_BROKER: None, + }, + CONF_ESPHOME: { + CONF_NAME: "test-device", + }, + } + + with pytest.raises( + EsphomeError, + match="Cannot discover IP via MQTT as the broker is not configured", + ): + get_esphome_device_ip(config) + + +def test_get_esphome_device_ip_missing_mqtt() -> None: + """Test that get_esphome_device_ip raises EsphomeError when mqtt config is missing.""" + config = { + CONF_ESPHOME: { + CONF_NAME: "test-device", + }, + } + + with pytest.raises( + EsphomeError, + match="Cannot discover IP via MQTT as the config does not include the mqtt:", + ): + get_esphome_device_ip(config) + + +def test_get_esphome_device_ip_missing_esphome() -> None: + """Test that get_esphome_device_ip raises EsphomeError when esphome config is missing.""" + config = { + CONF_MQTT: { + CONF_BROKER: "mqtt.local", + }, + } + + with pytest.raises( + EsphomeError, + match="Cannot discover IP via MQTT as the config does not include the device name:", + ): + get_esphome_device_ip(config) + + +def test_get_esphome_device_ip_missing_name() -> None: + """Test that get_esphome_device_ip raises EsphomeError when device name is missing.""" + config = { + CONF_MQTT: { + CONF_BROKER: "mqtt.local", + }, + CONF_ESPHOME: {}, + } + + with pytest.raises( + EsphomeError, + match="Cannot discover IP via MQTT as the config does not include the device name:", + ): + get_esphome_device_ip(config) From afed58107967d519c8a62779fbce809cdbe79af0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 18:41:28 -0600 Subject: [PATCH 0218/1145] [light] Fix dangling reference in compute_color_mode causing memory corruption (#11868) --- esphome/components/api/api_connection.cpp | 3 ++- esphome/components/light/light_call.cpp | 2 +- esphome/components/light/light_traits.h | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b4230576de..4acd2fc15c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -476,8 +476,9 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c auto *light = static_cast(entity); ListEntitiesLightResponse msg; auto traits = light->get_traits(); + auto supported_modes = traits.get_supported_color_modes(); // Pass pointer to ColorModeMask so the iterator can encode actual ColorMode enum values - msg.supported_color_modes = &traits.get_supported_color_modes(); + msg.supported_color_modes = &supported_modes; if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) || traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) { msg.min_mireds = traits.get_min_mireds(); diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index df17f53adc..8365ac77cd 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -406,7 +406,7 @@ void LightCall::transform_parameters_() { } } ColorMode LightCall::compute_color_mode_() { - const auto &supported_modes = this->parent_->get_traits().get_supported_color_modes(); + auto supported_modes = this->parent_->get_traits().get_supported_color_modes(); int supported_count = supported_modes.size(); // Some lights don't support any color modes (e.g. monochromatic light), leave it at unknown. diff --git a/esphome/components/light/light_traits.h b/esphome/components/light/light_traits.h index 294b0cad1d..c3bb27a964 100644 --- a/esphome/components/light/light_traits.h +++ b/esphome/components/light/light_traits.h @@ -18,7 +18,8 @@ class LightTraits { public: LightTraits() = default; - const ColorModeMask &get_supported_color_modes() const { return this->supported_color_modes_; } + // Return by value to avoid dangling reference when get_traits() returns a temporary + ColorModeMask get_supported_color_modes() const { return this->supported_color_modes_; } void set_supported_color_modes(ColorModeMask supported_color_modes) { this->supported_color_modes_ = supported_color_modes; } From 1d8b08dcce066b876d055a44b7f7ad192571985f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 18:43:51 -0600 Subject: [PATCH 0219/1145] [wifi][ethernet] Fix spurious warnings and unclear status after PR #9823 (#11871) --- esphome/components/ethernet/ethernet_component.cpp | 5 ++++- esphome/components/wifi/wifi_component.cpp | 13 ++++++++++++- esphome/components/wifi/wifi_component.h | 3 +++ esphome/components/wifi/wifi_component_esp8266.cpp | 2 +- esphome/components/wifi/wifi_component_esp_idf.cpp | 11 +++++++---- .../components/wifi/wifi_component_libretiny.cpp | 2 +- esphome/components/wifi/wifi_component_pico_w.cpp | 2 +- 7 files changed, 29 insertions(+), 9 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 5888ddce60..cad963b299 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -381,7 +381,10 @@ void EthernetComponent::dump_config() { break; } - ESP_LOGCONFIG(TAG, "Ethernet:"); + ESP_LOGCONFIG(TAG, + "Ethernet:\n" + " Connected: %s", + YESNO(this->is_connected())); this->dump_connect_params_(); #ifdef USE_ETHERNET_SPI ESP_LOGCONFIG(TAG, diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 817419107f..33aa6c8139 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -743,6 +743,14 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { } const LogString *get_signal_bars(int8_t rssi) { + // Check for disconnected sentinel value first + if (rssi == WIFI_RSSI_DISCONNECTED) { + // MULTIPLICATION SIGN + // Unicode: U+00D7, UTF-8: C3 97 + return LOG_STR("\033[0;31m" // red + "\xc3\x97\xc3\x97\xc3\x97\xc3\x97" + "\033[0m"); + } // LOWER ONE QUARTER BLOCK // Unicode: U+2582, UTF-8: E2 96 82 // LOWER HALF BLOCK @@ -1022,7 +1030,10 @@ void WiFiComponent::check_scanning_finished() { } void WiFiComponent::dump_config() { - ESP_LOGCONFIG(TAG, "WiFi:"); + ESP_LOGCONFIG(TAG, + "WiFi:\n" + " Connected: %s", + YESNO(this->is_connected())); this->print_connect_params_(); } diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 713e6f223f..5023cf3428 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -52,6 +52,9 @@ extern "C" { namespace esphome { namespace wifi { +/// Sentinel value for RSSI when WiFi is not connected +static constexpr int8_t WIFI_RSSI_DISCONNECTED = -127; + struct SavedWifiSettings { char ssid[33]; char password[65]; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index bcb5dc4cf7..bdaae5382a 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -870,7 +870,7 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } -int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } +int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; } diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index fd7e85fb6b..8c27fe92db 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -1029,7 +1029,8 @@ bssid_t WiFiComponent::wifi_bssid() { wifi_ap_record_t info; esp_err_t err = esp_wifi_sta_get_ap_info(&info); if (err != ESP_OK) { - ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); + // Very verbose only: this is expected during dump_config() before connection is established (PR #9823) + ESP_LOGVV(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); return bssid; } std::copy(info.bssid, info.bssid + 6, bssid.begin()); @@ -1039,7 +1040,8 @@ std::string WiFiComponent::wifi_ssid() { wifi_ap_record_t info{}; esp_err_t err = esp_wifi_sta_get_ap_info(&info); if (err != ESP_OK) { - ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); + // Very verbose only: this is expected during dump_config() before connection is established (PR #9823) + ESP_LOGVV(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); return ""; } auto *ssid_s = reinterpret_cast(info.ssid); @@ -1050,8 +1052,9 @@ int8_t WiFiComponent::wifi_rssi() { wifi_ap_record_t info; esp_err_t err = esp_wifi_sta_get_ap_info(&info); if (err != ESP_OK) { - ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); - return 0; + // Very verbose only: this is expected during dump_config() before connection is established (PR #9823) + ESP_LOGVV(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); + return WIFI_RSSI_DISCONNECTED; } return info.rssi; } diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 2946b9e831..8c6c28ac75 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -484,7 +484,7 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } -int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } +int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 7025ba16bd..073b752886 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -200,7 +200,7 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } -int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } +int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { From 1675408161b8e1631b3b1b53909580cfd7a1fb99 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 18:44:22 -0600 Subject: [PATCH 0220/1145] [wifi] Fix slow reconnection after connection loss for all network types (#11873) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/wifi/wifi_component.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 33aa6c8139..880928c3e3 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -465,6 +465,8 @@ void WiFiComponent::loop() { if (!this->is_connected()) { ESP_LOGW(TAG, "Connection lost; reconnecting"); this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING; + // Clear error flag before reconnecting so first attempt is not seen as immediate failure + this->error_from_callback_ = false; this->retry_connect(); } else { this->status_clear_warning(); @@ -1058,6 +1060,10 @@ void WiFiComponent::check_connecting_finished() { // Reset to initial phase on successful connection (don't log transition, just reset state) this->retry_phase_ = WiFiRetryPhase::INITIAL_CONNECT; this->num_retried_ = 0; + // Ensure next connection attempt does not inherit error state + // so when WiFi disconnects later we start fresh and don't see + // the first connection as a failure. + this->error_from_callback_ = false; this->print_connect_params_(); @@ -1144,6 +1150,11 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() { return WiFiRetryPhase::FAST_CONNECT_CYCLING_APS; // Move to next AP } #endif + // Check if we should try explicit hidden networks before scanning + // This handles reconnection after connection loss where first network is hidden + if (!this->sta_.empty() && this->sta_[0].get_hidden()) { + return WiFiRetryPhase::EXPLICIT_HIDDEN; + } // No more APs to try, fall back to scan return WiFiRetryPhase::SCAN_CONNECTING; From 382483b0635353b70cd1975af6eaaa4118f2091e Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 12 Nov 2025 21:56:11 -0500 Subject: [PATCH 0221/1145] Bump version to 2025.11.0b2 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 8025c71c19..8766b8f00c 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.11.0b1 +PROJECT_NUMBER = 2025.11.0b2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 00975753c2..8c6020a868 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.11.0b1" +__version__ = "2025.11.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From ed7e5cd325f8d376f01704f36fba2557dbfd69a7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 13 Nov 2025 17:00:47 +1300 Subject: [PATCH 0222/1145] Bump version to 2025.12.0-dev --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 8766b8f00c..a19120b9da 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.11.0b2 +PROJECT_NUMBER = 2025.12.0-dev # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 8c6020a868..a25114d80e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.11.0b2" +__version__ = "2025.12.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 952bdfaac238181819ef8d8f85267623dd00873b Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 14 Nov 2025 00:55:48 +1000 Subject: [PATCH 0223/1145] [esp32] Make esp-idf default framework for P4 (#11884) --- esphome/components/esp32/__init__.py | 163 ++++++++++++--------------- 1 file changed, 74 insertions(+), 89 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 61511cba0c..9741dc76a1 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -381,8 +381,9 @@ PLATFORM_VERSION_LOOKUP = { } -def _check_versions(value): - value = value.copy() +def _check_versions(config): + config = config.copy() + value = config[CONF_FRAMEWORK] if value[CONF_VERSION] in PLATFORM_VERSION_LOOKUP: if CONF_SOURCE in value or CONF_PLATFORM_VERSION in value: @@ -447,7 +448,7 @@ def _check_versions(value): "If there are connectivity or build issues please remove the manual version." ) - return value + return config def _parse_platform_version(value): @@ -598,89 +599,72 @@ def _validate_idf_component(config: ConfigType) -> ConfigType: FRAMEWORK_ESP_IDF = "esp-idf" FRAMEWORK_ARDUINO = "arduino" -FRAMEWORK_SCHEMA = cv.All( - cv.Schema( - { - cv.Optional(CONF_TYPE, default=FRAMEWORK_ARDUINO): cv.one_of( - FRAMEWORK_ESP_IDF, FRAMEWORK_ARDUINO - ), - cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, - cv.Optional(CONF_RELEASE): cv.string_strict, - cv.Optional(CONF_SOURCE): cv.string_strict, - cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, - cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { - cv.string_strict: cv.string_strict - }, - cv.Optional(CONF_LOG_LEVEL, default="ERROR"): cv.one_of( - *LOG_LEVELS_IDF, upper=True - ), - cv.Optional(CONF_ADVANCED, default={}): cv.Schema( - { - cv.Optional(CONF_ASSERTION_LEVEL): cv.one_of( - *ASSERTION_LEVELS, upper=True - ), - cv.Optional(CONF_COMPILER_OPTIMIZATION, default="SIZE"): cv.one_of( - *COMPILER_OPTIMIZATIONS, upper=True - ), - cv.Optional(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES): cv.boolean, - cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean, - cv.Optional( - CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False - ): cv.boolean, - cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC): cv.boolean, - # DHCP server is needed for WiFi AP mode. When WiFi component is used, - # it will handle disabling DHCP server when AP is not configured. - # Default to false (disabled) when WiFi is not used. - cv.OnlyWithout( - CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False - ): cv.boolean, - cv.Optional( - CONF_ENABLE_LWIP_MDNS_QUERIES, default=True - ): cv.boolean, - cv.Optional( - CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False - ): cv.boolean, - cv.Optional( - CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, default=True - ): cv.boolean, - cv.Optional( - CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, default=True - ): cv.boolean, - cv.Optional( - CONF_DISABLE_LIBC_LOCKS_IN_IRAM, default=True - ): cv.boolean, - cv.Optional( - CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True - ): cv.boolean, - cv.Optional( - CONF_DISABLE_VFS_SUPPORT_SELECT, default=True - ): cv.boolean, - cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean, - cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean, - cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range( - min=8192, max=32768 - ), - } - ), - cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( - cv.All( - cv.Schema( - { - cv.Required(CONF_NAME): cv.string_strict, - cv.Optional(CONF_SOURCE): cv.git_ref, - cv.Optional(CONF_REF): cv.string, - cv.Optional(CONF_PATH): cv.string, - cv.Optional(CONF_REFRESH): cv.All( - cv.string, cv.source_refresh - ), - } - ), - _validate_idf_component, - ) - ), - } - ), - _check_versions, +FRAMEWORK_SCHEMA = cv.Schema( + { + cv.Optional(CONF_TYPE): cv.one_of(FRAMEWORK_ESP_IDF, FRAMEWORK_ARDUINO), + cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, + cv.Optional(CONF_RELEASE): cv.string_strict, + cv.Optional(CONF_SOURCE): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, + cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { + cv.string_strict: cv.string_strict + }, + cv.Optional(CONF_LOG_LEVEL, default="ERROR"): cv.one_of( + *LOG_LEVELS_IDF, upper=True + ), + cv.Optional(CONF_ADVANCED, default={}): cv.Schema( + { + cv.Optional(CONF_ASSERTION_LEVEL): cv.one_of( + *ASSERTION_LEVELS, upper=True + ), + cv.Optional(CONF_COMPILER_OPTIMIZATION, default="SIZE"): cv.one_of( + *COMPILER_OPTIMIZATIONS, upper=True + ), + cv.Optional(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES): cv.boolean, + cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean, + cv.Optional(CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False): cv.boolean, + cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC): cv.boolean, + # DHCP server is needed for WiFi AP mode. When WiFi component is used, + # it will handle disabling DHCP server when AP is not configured. + # Default to false (disabled) when WiFi is not used. + cv.OnlyWithout( + CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False + ): cv.boolean, + cv.Optional(CONF_ENABLE_LWIP_MDNS_QUERIES, default=True): cv.boolean, + cv.Optional( + CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False + ): cv.boolean, + cv.Optional( + CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, default=True + ): cv.boolean, + cv.Optional( + CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, default=True + ): cv.boolean, + cv.Optional(CONF_DISABLE_LIBC_LOCKS_IN_IRAM, default=True): cv.boolean, + cv.Optional(CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True): cv.boolean, + cv.Optional(CONF_DISABLE_VFS_SUPPORT_SELECT, default=True): cv.boolean, + cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean, + cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean, + cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range( + min=8192, max=32768 + ), + } + ), + cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( + cv.All( + cv.Schema( + { + cv.Required(CONF_NAME): cv.string_strict, + cv.Optional(CONF_SOURCE): cv.git_ref, + cv.Optional(CONF_REF): cv.string, + cv.Optional(CONF_PATH): cv.string, + cv.Optional(CONF_REFRESH): cv.All(cv.string, cv.source_refresh), + } + ), + _validate_idf_component, + ) + ), + } ) @@ -743,11 +727,11 @@ def _show_framework_migration_message(name: str, variant: str) -> None: def _set_default_framework(config): + config = config.copy() if CONF_FRAMEWORK not in config: - config = config.copy() - - variant = config[CONF_VARIANT] config[CONF_FRAMEWORK] = FRAMEWORK_SCHEMA({}) + if CONF_TYPE not in config[CONF_FRAMEWORK]: + variant = config[CONF_VARIANT] if variant in ARDUINO_ALLOWED_VARIANTS: config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO _show_framework_migration_message( @@ -787,6 +771,7 @@ CONFIG_SCHEMA = cv.All( ), _detect_variant, _set_default_framework, + _check_versions, set_core_data, cv.has_at_least_one_key(CONF_BOARD, CONF_VARIANT), ) From 0afcf67c32bcc22d60579a38fd42e3823c7072b4 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 14 Nov 2025 01:52:08 +1000 Subject: [PATCH 0224/1145] [esp32] Add sdkconfig flag to make OTA work for 32MB flash (#11883) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/esp32/__init__.py | 81 +++++++++---------- tests/components/esp32/test.esp32-p4-idf.yaml | 27 +++++++ 2 files changed, 67 insertions(+), 41 deletions(-) create mode 100644 tests/components/esp32/test.esp32-p4-idf.yaml diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 9741dc76a1..0f85e585f7 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -498,6 +498,8 @@ def final_validate(config): from esphome.components.psram import DOMAIN as PSRAM_DOMAIN errs = [] + conf_fw = config[CONF_FRAMEWORK] + advanced = conf_fw[CONF_ADVANCED] full_config = fv.full_config.get() if pio_options := full_config[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS): pio_flash_size_key = "board_upload.flash_size" @@ -514,22 +516,14 @@ def final_validate(config): f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only" ) ) - if ( - config[CONF_VARIANT] != VARIANT_ESP32 - and CONF_ADVANCED in (conf_fw := config[CONF_FRAMEWORK]) - and CONF_IGNORE_EFUSE_MAC_CRC in conf_fw[CONF_ADVANCED] - ): + if config[CONF_VARIANT] != VARIANT_ESP32 and advanced[CONF_IGNORE_EFUSE_MAC_CRC]: errs.append( cv.Invalid( f"'{CONF_IGNORE_EFUSE_MAC_CRC}' is not supported on {config[CONF_VARIANT]}", path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_IGNORE_EFUSE_MAC_CRC], ) ) - if ( - config.get(CONF_FRAMEWORK, {}) - .get(CONF_ADVANCED, {}) - .get(CONF_EXECUTE_FROM_PSRAM) - ): + if advanced[CONF_EXECUTE_FROM_PSRAM]: if config[CONF_VARIANT] != VARIANT_ESP32S3: errs.append( cv.Invalid( @@ -545,6 +539,17 @@ def final_validate(config): ) ) + if ( + config[CONF_FLASH_SIZE] == "32MB" + and "ota" in full_config + and not advanced[CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES] + ): + errs.append( + cv.Invalid( + f"OTA with 32MB flash requires '{CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES}' to be set in the '{CONF_ADVANCED}' section of the esp32 configuration", + path=[CONF_FLASH_SIZE], + ) + ) if errs: raise cv.MultipleInvalid(errs) @@ -620,10 +625,12 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_COMPILER_OPTIMIZATION, default="SIZE"): cv.one_of( *COMPILER_OPTIMIZATIONS, upper=True ), - cv.Optional(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES): cv.boolean, + cv.Optional( + CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES, default=False + ): cv.boolean, cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean, cv.Optional(CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False): cv.boolean, - cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC): cv.boolean, + cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean, # DHCP server is needed for WiFi AP mode. When WiFi component is used, # it will handle disabling DHCP server when AP is not configured. # Default to false (disabled) when WiFi is not used. @@ -644,7 +651,7 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True): cv.boolean, cv.Optional(CONF_DISABLE_VFS_SUPPORT_SELECT, default=True): cv.boolean, cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean, - cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean, + cv.Optional(CONF_EXECUTE_FROM_PSRAM, default=False): cv.boolean, cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range( min=8192, max=32768 ), @@ -790,9 +797,7 @@ def _configure_lwip_max_sockets(conf: dict) -> None: from esphome.components.socket import KEY_SOCKET_CONSUMERS # Check if user manually specified CONFIG_LWIP_MAX_SOCKETS - user_max_sockets = conf.get(CONF_SDKCONFIG_OPTIONS, {}).get( - "CONFIG_LWIP_MAX_SOCKETS" - ) + user_max_sockets = conf[CONF_SDKCONFIG_OPTIONS].get("CONFIG_LWIP_MAX_SOCKETS") socket_consumers: dict[str, int] = CORE.data.get(KEY_SOCKET_CONSUMERS, {}) total_sockets = sum(socket_consumers.values()) @@ -962,23 +967,18 @@ async def to_code(config): # WiFi component handles its own optimization when AP mode is not used # When using Arduino with Ethernet, DHCP server functions must be available # for the Network library to compile, even if not actively used - if ( - CONF_ENABLE_LWIP_DHCP_SERVER in advanced - and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER] - and not ( - conf[CONF_TYPE] == FRAMEWORK_ARDUINO - and "ethernet" in CORE.loaded_integrations - ) + if advanced.get(CONF_ENABLE_LWIP_DHCP_SERVER) is False and not ( + conf[CONF_TYPE] == FRAMEWORK_ARDUINO and "ethernet" in CORE.loaded_integrations ): add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False) - if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, True): + if not advanced[CONF_ENABLE_LWIP_MDNS_QUERIES]: add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False) - if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False): + if not advanced[CONF_ENABLE_LWIP_BRIDGE_INTERFACE]: add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0) _configure_lwip_max_sockets(conf) - if advanced.get(CONF_EXECUTE_FROM_PSRAM, False): + if advanced[CONF_EXECUTE_FROM_PSRAM]: add_idf_sdkconfig_option("CONFIG_SPIRAM_FETCH_INSTRUCTIONS", True) add_idf_sdkconfig_option("CONFIG_SPIRAM_RODATA", True) @@ -989,23 +989,22 @@ async def to_code(config): # - select() on 4 sockets: ~190μs (Arduino/core locking) vs ~235μs (ESP-IDF default) # - Up to 200% slower under load when all operations queue through tcpip_thread # Enabling this makes ESP-IDF socket performance match Arduino framework. - if advanced.get(CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, True): + if advanced[CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING]: add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_CORE_LOCKING", True) - if advanced.get(CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, True): + if advanced[CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY]: add_idf_sdkconfig_option("CONFIG_LWIP_CHECK_THREAD_SAFETY", True) # Disable placing libc locks in IRAM to save RAM # This is safe for ESPHome since no IRAM ISRs (interrupts that run while cache is disabled) # use libc lock APIs. Saves approximately 1.3KB (1,356 bytes) of IRAM. - if advanced.get(CONF_DISABLE_LIBC_LOCKS_IN_IRAM, True): + if advanced[CONF_DISABLE_LIBC_LOCKS_IN_IRAM]: add_idf_sdkconfig_option("CONFIG_LIBC_LOCKS_PLACE_IN_IRAM", False) # Disable VFS support for termios (terminal I/O functions) # ESPHome doesn't use termios functions on ESP32 (only used in host UART driver). # Saves approximately 1.8KB of flash when disabled (default). add_idf_sdkconfig_option( - "CONFIG_VFS_SUPPORT_TERMIOS", - not advanced.get(CONF_DISABLE_VFS_SUPPORT_TERMIOS, True), + "CONFIG_VFS_SUPPORT_TERMIOS", not advanced[CONF_DISABLE_VFS_SUPPORT_TERMIOS] ) # Disable VFS support for select() with file descriptors @@ -1019,8 +1018,7 @@ async def to_code(config): else: # No component needs it - allow user to control (default: disabled) add_idf_sdkconfig_option( - "CONFIG_VFS_SUPPORT_SELECT", - not advanced.get(CONF_DISABLE_VFS_SUPPORT_SELECT, True), + "CONFIG_VFS_SUPPORT_SELECT", not advanced[CONF_DISABLE_VFS_SUPPORT_SELECT] ) # Disable VFS support for directory functions (opendir, readdir, mkdir, etc.) @@ -1033,8 +1031,7 @@ async def to_code(config): else: # No component needs it - allow user to control (default: disabled) add_idf_sdkconfig_option( - "CONFIG_VFS_SUPPORT_DIR", - not advanced.get(CONF_DISABLE_VFS_SUPPORT_DIR, True), + "CONFIG_VFS_SUPPORT_DIR", not advanced[CONF_DISABLE_VFS_SUPPORT_DIR] ) cg.add_platformio_option("board_build.partitions", "partitions.csv") @@ -1048,7 +1045,7 @@ async def to_code(config): add_idf_sdkconfig_option(flag, assertion_level == key) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) - compiler_optimization = advanced.get(CONF_COMPILER_OPTIMIZATION) + compiler_optimization = advanced[CONF_COMPILER_OPTIMIZATION] for key, flag in COMPILER_OPTIMIZATIONS.items(): add_idf_sdkconfig_option(flag, compiler_optimization == key) @@ -1057,18 +1054,20 @@ async def to_code(config): conf[CONF_ADVANCED][CONF_ENABLE_LWIP_ASSERT], ) - if advanced.get(CONF_IGNORE_EFUSE_MAC_CRC): + if advanced[CONF_IGNORE_EFUSE_MAC_CRC]: add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True) add_idf_sdkconfig_option("CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False) - if advanced.get(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES): + if advanced[CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES]: _LOGGER.warning( "Using experimental features in ESP-IDF may result in unexpected failures." ) add_idf_sdkconfig_option("CONFIG_IDF_EXPERIMENTAL_FEATURES", True) + if config[CONF_FLASH_SIZE] == "32MB": + add_idf_sdkconfig_option( + "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH", True + ) - cg.add_define( - "ESPHOME_LOOP_TASK_STACK_SIZE", advanced.get(CONF_LOOP_TASK_STACK_SIZE) - ) + cg.add_define("ESPHOME_LOOP_TASK_STACK_SIZE", advanced[CONF_LOOP_TASK_STACK_SIZE]) cg.add_define( "USE_ESP_IDF_VERSION_CODE", diff --git a/tests/components/esp32/test.esp32-p4-idf.yaml b/tests/components/esp32/test.esp32-p4-idf.yaml new file mode 100644 index 0000000000..a4c930f236 --- /dev/null +++ b/tests/components/esp32/test.esp32-p4-idf.yaml @@ -0,0 +1,27 @@ +esp32: + variant: esp32p4 + flash_size: 32MB + cpu_frequency: 400MHz + framework: + type: esp-idf + advanced: + enable_idf_experimental_features: yes + +ota: + platform: esphome + +wifi: + ssid: MySSID + password: password1 + +esp32_hosted: + variant: ESP32C6 + slot: 1 + active_high: true + reset_pin: GPIO15 + cmd_pin: GPIO13 + clk_pin: GPIO12 + d0_pin: GPIO11 + d1_pin: GPIO10 + d2_pin: GPIO9 + d3_pin: GPIO8 From 2290eb0dd2cac9d8b044aa011629dd1c134f2975 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Thu, 13 Nov 2025 19:08:06 +0100 Subject: [PATCH 0225/1145] [light] Fix missing `ColorMode::BRIGHTNESS` case in logging (#11836) --- esphome/components/light/light_call.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 8365ac77cd..b15ff84b97 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -52,8 +52,10 @@ static void log_invalid_parameter(const char *name, const LogString *message) { } static const LogString *color_mode_to_human(ColorMode color_mode) { - if (color_mode == ColorMode::UNKNOWN) - return LOG_STR("Unknown"); + if (color_mode == ColorMode::ON_OFF) + return LOG_STR("On/Off"); + if (color_mode == ColorMode::BRIGHTNESS) + return LOG_STR("Brightness"); if (color_mode == ColorMode::WHITE) return LOG_STR("White"); if (color_mode == ColorMode::COLOR_TEMPERATURE) @@ -68,7 +70,7 @@ static const LogString *color_mode_to_human(ColorMode color_mode) { return LOG_STR("RGB + cold/warm white"); if (color_mode == ColorMode::RGB_COLOR_TEMPERATURE) return LOG_STR("RGB + color temperature"); - return LOG_STR(""); + return LOG_STR("Unknown"); } // Helper to log percentage values From 67524e14eec345fecf60daf7251e67be7ff9a364 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 19:05:02 +0000 Subject: [PATCH 0226/1145] Bump pylint from 4.0.2 to 4.0.3 (#11894) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 5c7cccaf25..8d4e0ca246 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==4.0.2 +pylint==4.0.3 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating ruff==0.14.4 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.1 # also change in .pre-commit-config.yaml when updating From e49a943cf7cd93c5123ba4b4e10fc861d8b8f4a1 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:13:48 +0100 Subject: [PATCH 0227/1145] [wifi] Allow `use_psram` with Arduino (#11902) --- esphome/components/wifi/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 4dbb425e4b..11bd7798e2 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -12,7 +12,6 @@ from esphome.components.network import ( from esphome.components.psram import is_guaranteed as psram_is_guaranteed from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv -from esphome.config_validation import only_with_esp_idf from esphome.const import ( CONF_AP, CONF_BSSID, @@ -352,7 +351,7 @@ CONFIG_SCHEMA = cv.All( single=True ), cv.Optional(CONF_USE_PSRAM): cv.All( - only_with_esp_idf, cv.requires_component("psram"), cv.boolean + cv.only_on_esp32, cv.requires_component("psram"), cv.boolean ), } ), From 2bf6d48fcf06cce35a8e4d3211e0bc77c433d341 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:06:08 -0500 Subject: [PATCH 0228/1145] [uart] Improve error handling and validate buffer size (#11895) Co-authored-by: J. Nick Koston --- esphome/components/uart/__init__.py | 19 ++++++++++ .../uart/uart_component_esp_idf.cpp | 35 +++++++++++++++---- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index cbc11d0db0..7b0d9726b8 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -1,3 +1,4 @@ +from logging import getLogger import math import re @@ -35,6 +36,8 @@ from esphome.core import CORE, ID import esphome.final_validate as fv from esphome.yaml_util import make_data_base +_LOGGER = getLogger(__name__) + CODEOWNERS = ["@esphome/core"] uart_ns = cg.esphome_ns.namespace("uart") UARTComponent = uart_ns.class_("UARTComponent") @@ -130,6 +133,21 @@ def validate_host_config(config): return config +def validate_rx_buffer_size(config): + if CORE.is_esp32: + # ESP32 UART hardware FIFO is 128 bytes (LP UART is 16 bytes, but we use 128 as safe minimum) + # rx_buffer_size must be greater than the hardware FIFO length + min_buffer_size = 128 + if config[CONF_RX_BUFFER_SIZE] <= min_buffer_size: + _LOGGER.warning( + "UART rx_buffer_size (%d bytes) is too small and must be greater than the hardware " + "FIFO size (%d bytes). The buffer size will be automatically adjusted at runtime.", + config[CONF_RX_BUFFER_SIZE], + min_buffer_size, + ) + return config + + def _uart_declare_type(value): if CORE.is_esp8266: return cv.declare_id(ESP8266UartComponent)(value) @@ -247,6 +265,7 @@ CONFIG_SCHEMA = cv.All( ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN, CONF_PORT), validate_host_config, + validate_rx_buffer_size, ) diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 73813d2d5b..70a13c9e37 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -91,6 +91,16 @@ void IDFUARTComponent::setup() { this->uart_num_ = static_cast(next_uart_num++); this->lock_ = xSemaphoreCreateMutex(); +#if (SOC_UART_LP_NUM >= 1) + size_t fifo_len = ((this->uart_num_ < SOC_UART_HP_NUM) ? SOC_UART_FIFO_LEN : SOC_LP_UART_FIFO_LEN); +#else + size_t fifo_len = SOC_UART_FIFO_LEN; +#endif + if (this->rx_buffer_size_ <= fifo_len) { + ESP_LOGW(TAG, "rx_buffer_size is too small, must be greater than %zu", fifo_len); + this->rx_buffer_size_ = fifo_len * 2; + } + xSemaphoreTake(this->lock_, portMAX_DELAY); this->load_settings(false); @@ -237,8 +247,12 @@ void IDFUARTComponent::set_rx_timeout(size_t rx_timeout) { void IDFUARTComponent::write_array(const uint8_t *data, size_t len) { xSemaphoreTake(this->lock_, portMAX_DELAY); - uart_write_bytes(this->uart_num_, data, len); + int32_t write_len = uart_write_bytes(this->uart_num_, data, len); xSemaphoreGive(this->lock_); + if (write_len != (int32_t) len) { + ESP_LOGW(TAG, "uart_write_bytes failed: %d != %zu", write_len, len); + this->mark_failed(); + } #ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { this->debug_callback_.call(UART_DIRECTION_TX, data[i]); @@ -267,6 +281,7 @@ bool IDFUARTComponent::peek_byte(uint8_t *data) { bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { size_t length_to_read = len; + int32_t read_len = 0; if (!this->check_read_timeout_(len)) return false; xSemaphoreTake(this->lock_, portMAX_DELAY); @@ -277,25 +292,31 @@ bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { this->has_peek_ = false; } if (length_to_read > 0) - uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_PERIOD_MS); + read_len = uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_PERIOD_MS); xSemaphoreGive(this->lock_); #ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { this->debug_callback_.call(UART_DIRECTION_RX, data[i]); } #endif - return true; + return read_len == (int32_t) length_to_read; } int IDFUARTComponent::available() { - size_t available; + size_t available = 0; + esp_err_t err; xSemaphoreTake(this->lock_, portMAX_DELAY); - uart_get_buffered_data_len(this->uart_num_, &available); - if (this->has_peek_) - available++; + err = uart_get_buffered_data_len(this->uart_num_, &available); xSemaphoreGive(this->lock_); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_get_buffered_data_len failed: %s", esp_err_to_name(err)); + this->mark_failed(); + } + if (this->has_peek_) { + available++; + } return available; } From c32891ec025f05fd387cabd26cf18413f7881411 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:09:59 -0600 Subject: [PATCH 0229/1145] Bump github/codeql-action from 4.31.2 to 4.31.3 (#11911) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ab938b3436..2273975328 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 + uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -86,6 +86,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 + uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3 with: category: "/language:${{matrix.language}}" From 1df996601dce60fca1218f272d42b5974644afb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 19:14:07 +0000 Subject: [PATCH 0230/1145] Bump ruff from 0.14.4 to 0.14.5 (#11910) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dab660b03f..b86d00f2aa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.4 + rev: v0.14.5 hooks: # Run the linter. - id: ruff diff --git a/requirements_test.txt b/requirements_test.txt index 8d4e0ca246..e238faa77e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==4.0.3 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.14.4 # also change in .pre-commit-config.yaml when updating +ruff==0.14.5 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.1 # also change in .pre-commit-config.yaml when updating pre-commit From eb759efb3d6503dd76f8e7e414250775b533b19b Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sun, 16 Nov 2025 11:48:02 +1000 Subject: [PATCH 0231/1145] [font] Store glyph data in flash only (#11926) --- esphome/components/font/__init__.py | 29 ++++++---------- esphome/components/font/font.cpp | 46 ++++++++++++-------------- esphome/components/font/font.h | 44 ++++++++++++------------ esphome/components/lvgl/font.cpp | 4 +-- esphome/components/lvgl/lvgl_esphome.h | 4 +-- esphome/core/helpers.h | 17 ++++++++++ 6 files changed, 75 insertions(+), 69 deletions(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index ddcee14635..32e803f405 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -36,7 +36,6 @@ from esphome.const import ( CONF_WEIGHT, ) from esphome.core import CORE, HexInt -from esphome.helpers import cpp_string_escape from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) @@ -50,7 +49,6 @@ font_ns = cg.esphome_ns.namespace("font") Font = font_ns.class_("Font") Glyph = font_ns.class_("Glyph") -GlyphData = font_ns.struct("GlyphData") CONF_BPP = "bpp" CONF_EXTRAS = "extras" @@ -463,7 +461,7 @@ FONT_SCHEMA = cv.Schema( ) ), cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), - cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(GlyphData), + cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(Glyph), }, ) @@ -583,22 +581,15 @@ async def to_code(config): # Create the glyph table that points to data in the above array. glyph_initializer = [ - cg.StructInitializer( - GlyphData, - ( - "a_char", - cg.RawExpression(f"(const uint8_t *){cpp_string_escape(x.glyph)}"), - ), - ( - "data", - cg.RawExpression(f"{str(prog_arr)} + {str(y - len(x.bitmap_data))}"), - ), - ("advance", x.advance), - ("offset_x", x.offset_x), - ("offset_y", x.offset_y), - ("width", x.width), - ("height", x.height), - ) + [ + x.glyph, + prog_arr + (y - len(x.bitmap_data)), + x.advance, + x.offset_x, + x.offset_y, + x.width, + x.height, + ] for (x, y) in zip( glyph_args, list(accumulate([len(x.bitmap_data) for x in glyph_args])) ) diff --git a/esphome/components/font/font.cpp b/esphome/components/font/font.cpp index 8b2420ac07..add403fe98 100644 --- a/esphome/components/font/font.cpp +++ b/esphome/components/font/font.cpp @@ -9,20 +9,19 @@ namespace font { static const char *const TAG = "font"; -const uint8_t *Glyph::get_char() const { return this->glyph_data_->a_char; } // Compare the char at the string position with this char. // Return true if this char is less than or equal the other. bool Glyph::compare_to(const uint8_t *str) const { // 1 -> this->char_ // 2 -> str for (uint32_t i = 0;; i++) { - if (this->glyph_data_->a_char[i] == '\0') + if (this->a_char[i] == '\0') return true; if (str[i] == '\0') return false; - if (this->glyph_data_->a_char[i] > str[i]) + if (this->a_char[i] > str[i]) return false; - if (this->glyph_data_->a_char[i] < str[i]) + if (this->a_char[i] < str[i]) return true; } // this should not happen @@ -30,35 +29,32 @@ bool Glyph::compare_to(const uint8_t *str) const { } int Glyph::match_length(const uint8_t *str) const { for (uint32_t i = 0;; i++) { - if (this->glyph_data_->a_char[i] == '\0') + if (this->a_char[i] == '\0') return i; - if (str[i] != this->glyph_data_->a_char[i]) + if (str[i] != this->a_char[i]) return 0; } // this should not happen return 0; } void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const { - *x1 = this->glyph_data_->offset_x; - *y1 = this->glyph_data_->offset_y; - *width = this->glyph_data_->width; - *height = this->glyph_data_->height; + *x1 = this->offset_x; + *y1 = this->offset_y; + *width = this->width; + *height = this->height; } -Font::Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight, +Font::Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight, uint8_t bpp) - : baseline_(baseline), + : glyphs_(ConstVector(data, data_nr)), + baseline_(baseline), height_(height), descender_(descender), linegap_(height - baseline - descender), xheight_(xheight), capheight_(capheight), - bpp_(bpp) { - glyphs_.reserve(data_nr); - for (int i = 0; i < data_nr; ++i) - glyphs_.emplace_back(&data[i]); -} -int Font::match_next_glyph(const uint8_t *str, int *match_length) { + bpp_(bpp) {} +int Font::match_next_glyph(const uint8_t *str, int *match_length) const { int lo = 0; int hi = this->glyphs_.size() - 1; while (lo != hi) { @@ -88,18 +84,18 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in if (glyph_n < 0) { // Unknown char, skip if (!this->get_glyphs().empty()) - x += this->get_glyphs()[0].glyph_data_->advance; + x += this->get_glyphs()[0].advance; i++; continue; } const Glyph &glyph = this->glyphs_[glyph_n]; if (!has_char) { - min_x = glyph.glyph_data_->offset_x; + min_x = glyph.offset_x; } else { - min_x = std::min(min_x, x + glyph.glyph_data_->offset_x); + min_x = std::min(min_x, x + glyph.offset_x); } - x += glyph.glyph_data_->advance; + x += glyph.advance; i += match_length; has_char = true; @@ -118,7 +114,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo // Unknown char, skip ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]); if (!this->get_glyphs().empty()) { - uint8_t glyph_width = this->get_glyphs()[0].glyph_data_->advance; + uint8_t glyph_width = this->get_glyphs()[0].advance; display->filled_rectangle(x_at, y_start, glyph_width, this->height_, color); x_at += glyph_width; } @@ -130,7 +126,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo const Glyph &glyph = this->get_glyphs()[glyph_n]; glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); - const uint8_t *data = glyph.glyph_data_->data; + const uint8_t *data = glyph.data; const int max_x = x_at + scan_x1 + scan_width; const int max_y = y_start + scan_y1 + scan_height; @@ -168,7 +164,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo } } } - x_at += glyph.glyph_data_->advance; + x_at += glyph.advance; i += match_length; } diff --git a/esphome/components/font/font.h b/esphome/components/font/font.h index 28832d647d..cb6cc89137 100644 --- a/esphome/components/font/font.h +++ b/esphome/components/font/font.h @@ -12,21 +12,19 @@ namespace font { class Font; -struct GlyphData { - const uint8_t *a_char; - const uint8_t *data; - int advance; - int offset_x; - int offset_y; - int width; - int height; -}; - class Glyph { public: - Glyph(const GlyphData *data) : glyph_data_(data) {} + constexpr Glyph(const char *a_char, const uint8_t *data, int advance, int offset_x, int offset_y, int width, + int height) + : a_char(a_char), + data(data), + advance(advance), + offset_x(offset_x), + offset_y(offset_y), + width(width), + height(height) {} - const uint8_t *get_char() const; + const uint8_t *get_char() const { return reinterpret_cast(this->a_char); } bool compare_to(const uint8_t *str) const; @@ -34,12 +32,16 @@ class Glyph { void scan_area(int *x1, int *y1, int *width, int *height) const; - const GlyphData *get_glyph_data() const { return this->glyph_data_; } + const char *a_char; + const uint8_t *data; + int advance; + int offset_x; + int offset_y; + int width; + int height; protected: friend Font; - - const GlyphData *glyph_data_; }; class Font @@ -50,8 +52,8 @@ class Font public: /** Construct the font with the given glyphs. * - * @param data A vector of glyphs, must be sorted lexicographically. - * @param data_nr The number of glyphs in data. + * @param data A list of glyphs, must be sorted lexicographically. + * @param data_nr The number of glyphs * @param baseline The y-offset from the top of the text to the baseline. * @param height The y-offset from the top of the text to the bottom. * @param descender The y-offset from the baseline to the lowest stroke in the font (e.g. from letters like g or p). @@ -59,10 +61,10 @@ class Font * @param capheight The height of capital letters, usually measured at the "X" glyph. * @param bpp The bits per pixel used for this font. Used to read data out of the glyph bitmaps. */ - Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight, + Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight, uint8_t bpp = 1); - int match_next_glyph(const uint8_t *str, int *match_length); + int match_next_glyph(const uint8_t *str, int *match_length) const; #ifdef USE_DISPLAY void print(int x_start, int y_start, display::Display *display, Color color, const char *text, @@ -78,10 +80,10 @@ class Font inline int get_capheight() { return this->capheight_; } inline int get_bpp() { return this->bpp_; } - const std::vector> &get_glyphs() const { return glyphs_; } + const ConstVector &get_glyphs() const { return glyphs_; } protected: - std::vector> glyphs_; + ConstVector glyphs_; int baseline_; int height_; int descender_; diff --git a/esphome/components/lvgl/font.cpp b/esphome/components/lvgl/font.cpp index a0d5127570..1976fb9608 100644 --- a/esphome/components/lvgl/font.cpp +++ b/esphome/components/lvgl/font.cpp @@ -43,7 +43,7 @@ FontEngine::FontEngine(font::Font *esp_font) : font_(esp_font) { const lv_font_t *FontEngine::get_lv_font() { return &this->lv_font_; } -const font::GlyphData *FontEngine::get_glyph_data(uint32_t unicode_letter) { +const font::Glyph *FontEngine::get_glyph_data(uint32_t unicode_letter) { if (unicode_letter == last_letter_) return this->last_data_; uint8_t unicode[5]; @@ -67,7 +67,7 @@ const font::GlyphData *FontEngine::get_glyph_data(uint32_t unicode_letter) { int glyph_n = this->font_->match_next_glyph(unicode, &match_length); if (glyph_n < 0) return nullptr; - this->last_data_ = this->font_->get_glyphs()[glyph_n].get_glyph_data(); + this->last_data_ = &this->font_->get_glyphs()[glyph_n]; this->last_letter_ = unicode_letter; return this->last_data_; } diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 1ae05f933f..196a0d1cb4 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -140,7 +140,7 @@ class FontEngine { FontEngine(font::Font *esp_font); const lv_font_t *get_lv_font(); - const font::GlyphData *get_glyph_data(uint32_t unicode_letter); + const font::Glyph *get_glyph_data(uint32_t unicode_letter); uint16_t baseline{}; uint16_t height{}; uint8_t bpp{}; @@ -148,7 +148,7 @@ class FontEngine { protected: font::Font *font_{}; uint32_t last_letter_{}; - const font::GlyphData *last_data_{}; + const font::Glyph *last_data_{}; lv_font_t lv_font_{}; }; #endif // USE_LVGL_FONT diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 52a0746057..16eab8b8f6 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -111,6 +111,23 @@ template<> constexpr int64_t byteswap(int64_t n) { return __builtin_bswap64(n); /// @name Container utilities ///@{ +/// Lightweight read-only view over a const array stored in RODATA (will typically be in flash memory) +/// Avoids copying data from flash to RAM by keeping a pointer to the flash data. +/// Similar to std::span but with minimal overhead for embedded systems. + +template class ConstVector { + public: + constexpr ConstVector(const T *data, size_t size) : data_(data), size_(size) {} + + const constexpr T &operator[](size_t i) const { return data_[i]; } + constexpr size_t size() const { return size_; } + constexpr bool empty() const { return size_ == 0; } + + protected: + const T *data_; + size_t size_; +}; + /// Minimal static vector - saves memory by avoiding std::vector overhead template class StaticVector { public: From 5710cab972485a60af1488a62b71dca775002fb9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 15 Nov 2025 22:03:43 -0600 Subject: [PATCH 0232/1145] [ld2412] Fix stuck targets by adding timeout filter (#11919) --- esphome/components/ld2412/sensor.py | 76 ++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/esphome/components/ld2412/sensor.py b/esphome/components/ld2412/sensor.py index abb823faad..0bfbd9bf1d 100644 --- a/esphome/components/ld2412/sensor.py +++ b/esphome/components/ld2412/sensor.py @@ -31,36 +31,84 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component), cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_LIGHT): sensor.sensor_schema( device_class=DEVICE_CLASS_ILLUMINANCE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_LIGHTBULB, unit_of_measurement=UNIT_EMPTY, # No standard unit for this light sensor ), cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema( - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_MOTION_SENSOR, unit_of_measurement=UNIT_PERCENT, ), cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_FLASH, unit_of_measurement=UNIT_PERCENT, ), @@ -74,7 +122,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend( cv.Optional(CONF_MOVE_ENERGY): sensor.sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, filters=[ - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)} + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, ], icon=ICON_MOTION_SENSOR, unit_of_measurement=UNIT_PERCENT, @@ -82,7 +136,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend( cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, filters=[ - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)} + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, ], icon=ICON_FLASH, unit_of_measurement=UNIT_PERCENT, From 6b158e760d379033b811cd7bf73be0f95b33a09c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 15 Nov 2025 22:04:25 -0600 Subject: [PATCH 0233/1145] [ld2410] Add timeout filter to prevent stuck targets (#11920) --- esphome/components/ld2410/sensor.py | 76 ++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/esphome/components/ld2410/sensor.py b/esphome/components/ld2410/sensor.py index fca2b2ceca..3bd34963bc 100644 --- a/esphome/components/ld2410/sensor.py +++ b/esphome/components/ld2410/sensor.py @@ -31,35 +31,83 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema( - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_MOTION_SENSOR, unit_of_measurement=UNIT_PERCENT, ), cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_FLASH, unit_of_measurement=UNIT_PERCENT, ), cv.Optional(CONF_LIGHT): sensor.sensor_schema( device_class=DEVICE_CLASS_ILLUMINANCE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_LIGHTBULB, ), cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), @@ -73,7 +121,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend( cv.Optional(CONF_MOVE_ENERGY): sensor.sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, filters=[ - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)} + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, ], icon=ICON_MOTION_SENSOR, unit_of_measurement=UNIT_PERCENT, @@ -81,7 +135,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend( cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, filters=[ - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)} + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, ], icon=ICON_FLASH, unit_of_measurement=UNIT_PERCENT, From fc546ca3f6b63aaa43e208fe472c12eaa9c194b7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 15 Nov 2025 22:20:57 -0600 Subject: [PATCH 0234/1145] [scheduler] Fix timing breakage after 49 days of uptime on ESP8266/RP2040 (#11924) --- esphome/core/scheduler.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index d285af2d0e..d2e0f0dab4 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -609,13 +609,12 @@ uint64_t Scheduler::millis_64_(uint32_t now) { if (now < last && (last - now) > HALF_MAX_UINT32) { this->millis_major_++; major++; + this->last_millis_ = now; #ifdef ESPHOME_DEBUG_SCHEDULER ESP_LOGD(TAG, "Detected true 32-bit rollover at %" PRIu32 "ms (was %" PRIu32 ")", now, last); #endif /* ESPHOME_DEBUG_SCHEDULER */ - } - - // Only update if time moved forward - if (now > last) { + } else if (now > last) { + // Only update if time moved forward this->last_millis_ = now; } From ea2b4c3e2500a21d01d28a255a755c34391e237a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 15 Nov 2025 22:21:06 -0600 Subject: [PATCH 0235/1145] [binary_sensor] Modernize to C++17 nested namespaces and remove redundant qualifications (#11929) --- .../components/binary_sensor/automation.cpp | 18 ++++++++---------- esphome/components/binary_sensor/automation.h | 6 ++---- .../components/binary_sensor/binary_sensor.cpp | 8 ++------ .../components/binary_sensor/binary_sensor.h | 7 ++----- esphome/components/binary_sensor/filter.cpp | 8 ++------ esphome/components/binary_sensor/filter.h | 8 ++------ 6 files changed, 18 insertions(+), 37 deletions(-) diff --git a/esphome/components/binary_sensor/automation.cpp b/esphome/components/binary_sensor/automation.cpp index 64a0d3db8d..66d8d6e90f 100644 --- a/esphome/components/binary_sensor/automation.cpp +++ b/esphome/components/binary_sensor/automation.cpp @@ -1,12 +1,11 @@ #include "automation.h" #include "esphome/core/log.h" -namespace esphome { -namespace binary_sensor { +namespace esphome::binary_sensor { static const char *const TAG = "binary_sensor.automation"; -void binary_sensor::MultiClickTrigger::on_state_(bool state) { +void MultiClickTrigger::on_state_(bool state) { // Handle duplicate events if (state == this->last_state_) { return; @@ -67,7 +66,7 @@ void binary_sensor::MultiClickTrigger::on_state_(bool state) { *this->at_index_ = *this->at_index_ + 1; } -void binary_sensor::MultiClickTrigger::schedule_cooldown_() { +void MultiClickTrigger::schedule_cooldown_() { ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_); this->is_in_cooldown_ = true; this->set_timeout("cooldown", this->invalid_cooldown_, [this]() { @@ -79,7 +78,7 @@ void binary_sensor::MultiClickTrigger::schedule_cooldown_() { this->cancel_timeout("is_valid"); this->cancel_timeout("is_not_valid"); } -void binary_sensor::MultiClickTrigger::schedule_is_valid_(uint32_t min_length) { +void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) { if (min_length == 0) { this->is_valid_ = true; return; @@ -90,19 +89,19 @@ void binary_sensor::MultiClickTrigger::schedule_is_valid_(uint32_t min_length) { this->is_valid_ = true; }); } -void binary_sensor::MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) { +void MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) { this->set_timeout("is_not_valid", max_length, [this]() { ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS"); this->is_valid_ = false; this->schedule_cooldown_(); }); } -void binary_sensor::MultiClickTrigger::cancel() { +void MultiClickTrigger::cancel() { ESP_LOGV(TAG, "Multi Click: Sequence explicitly cancelled."); this->is_valid_ = false; this->schedule_cooldown_(); } -void binary_sensor::MultiClickTrigger::trigger_() { +void MultiClickTrigger::trigger_() { ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!"); this->at_index_.reset(); this->cancel_timeout("trigger"); @@ -118,5 +117,4 @@ bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length) { return length >= min_length && length <= max_length; } } -} // namespace binary_sensor -} // namespace esphome +} // namespace esphome::binary_sensor diff --git a/esphome/components/binary_sensor/automation.h b/esphome/components/binary_sensor/automation.h index f6971a2fc4..f8b130e08a 100644 --- a/esphome/components/binary_sensor/automation.h +++ b/esphome/components/binary_sensor/automation.h @@ -9,8 +9,7 @@ #include "esphome/core/helpers.h" #include "esphome/components/binary_sensor/binary_sensor.h" -namespace esphome { -namespace binary_sensor { +namespace esphome::binary_sensor { struct MultiClickTriggerEvent { bool state; @@ -172,5 +171,4 @@ template class BinarySensorInvalidateAction : public Action filters) { } bool BinarySensor::is_status_binary_sensor() const { return false; } -} // namespace binary_sensor - -} // namespace esphome +} // namespace esphome::binary_sensor diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index c1661d710f..0dca3e1520 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -6,9 +6,7 @@ #include -namespace esphome { - -namespace binary_sensor { +namespace esphome::binary_sensor { class BinarySensor; void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj); @@ -70,5 +68,4 @@ class BinarySensorInitiallyOff : public BinarySensor { bool has_state() const override { return true; } }; -} // namespace binary_sensor -} // namespace esphome +} // namespace esphome::binary_sensor diff --git a/esphome/components/binary_sensor/filter.cpp b/esphome/components/binary_sensor/filter.cpp index 8f31cf6fc2..9c7238f6d7 100644 --- a/esphome/components/binary_sensor/filter.cpp +++ b/esphome/components/binary_sensor/filter.cpp @@ -2,9 +2,7 @@ #include "binary_sensor.h" -namespace esphome { - -namespace binary_sensor { +namespace esphome::binary_sensor { static const char *const TAG = "sensor.filter"; @@ -132,6 +130,4 @@ optional SettleFilter::new_value(bool value) { float SettleFilter::get_setup_priority() const { return setup_priority::HARDWARE; } -} // namespace binary_sensor - -} // namespace esphome +} // namespace esphome::binary_sensor diff --git a/esphome/components/binary_sensor/filter.h b/esphome/components/binary_sensor/filter.h index 2d473c3b64..59bc43eeba 100644 --- a/esphome/components/binary_sensor/filter.h +++ b/esphome/components/binary_sensor/filter.h @@ -4,9 +4,7 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" -namespace esphome { - -namespace binary_sensor { +namespace esphome::binary_sensor { class BinarySensor; @@ -139,6 +137,4 @@ class SettleFilter : public Filter, public Component { bool steady_{true}; }; -} // namespace binary_sensor - -} // namespace esphome +} // namespace esphome::binary_sensor From 6f4042f401a27e8f6c46fa4b52f0e4f3c406be84 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 15 Nov 2025 22:21:38 -0600 Subject: [PATCH 0236/1145] Add tests for sensor timeout filters (#11923) --- .../fixtures/sensor_timeout_filter.yaml | 150 ++++++++++++++ .../integration/test_sensor_timeout_filter.py | 185 ++++++++++++++++++ 2 files changed, 335 insertions(+) create mode 100644 tests/integration/fixtures/sensor_timeout_filter.yaml create mode 100644 tests/integration/test_sensor_timeout_filter.py diff --git a/tests/integration/fixtures/sensor_timeout_filter.yaml b/tests/integration/fixtures/sensor_timeout_filter.yaml new file mode 100644 index 0000000000..dbd4db3242 --- /dev/null +++ b/tests/integration/fixtures/sensor_timeout_filter.yaml @@ -0,0 +1,150 @@ +esphome: + name: test-timeout-filters + +host: +api: + batch_delay: 0ms # Disable batching to receive all state updates +logger: + level: DEBUG + +# Template sensors that we'll use to publish values +sensor: + - platform: template + name: "Source Timeout Last" + id: source_timeout_last + accuracy_decimals: 1 + + - platform: template + name: "Source Timeout Reset" + id: source_timeout_reset + accuracy_decimals: 1 + + - platform: template + name: "Source Timeout Static" + id: source_timeout_static + accuracy_decimals: 1 + + - platform: template + name: "Source Timeout Lambda" + id: source_timeout_lambda + accuracy_decimals: 1 + + # Test 1: TimeoutFilter - "last" mode (outputs last received value) + - platform: copy + source_id: source_timeout_last + name: "Timeout Last Sensor" + id: timeout_last_sensor + filters: + - timeout: + timeout: 100ms + value: last # Explicitly specify "last" mode to use TimeoutFilter class + + # Test 2: TimeoutFilter - reset behavior (same filter, different source) + - platform: copy + source_id: source_timeout_reset + name: "Timeout Reset Sensor" + id: timeout_reset_sensor + filters: + - timeout: + timeout: 100ms + value: last # Explicitly specify "last" mode + + # Test 3: TimeoutFilterConfigured - static value mode + - platform: copy + source_id: source_timeout_static + name: "Timeout Static Sensor" + id: timeout_static_sensor + filters: + - timeout: + timeout: 100ms + value: 99.9 + + # Test 4: TimeoutFilterConfigured - lambda mode + - platform: copy + source_id: source_timeout_lambda + name: "Timeout Lambda Sensor" + id: timeout_lambda_sensor + filters: + - timeout: + timeout: 100ms + value: !lambda "return -1.0;" + +# Scripts to publish values with controlled timing +script: + # Test 1: Single value followed by timeout + - id: test_timeout_last_script + then: + # Publish initial value + - sensor.template.publish: + id: source_timeout_last + state: 42.0 + # Wait for timeout to fire (100ms + margin) + - delay: 150ms + + # Test 2: Multiple values before timeout (should reset timer) + - id: test_timeout_reset_script + then: + # Publish first value + - sensor.template.publish: + id: source_timeout_reset + state: 10.0 + # Wait 50ms (halfway to timeout) + - delay: 50ms + # Publish second value (resets timeout) + - sensor.template.publish: + id: source_timeout_reset + state: 20.0 + # Wait 50ms (halfway to timeout again) + - delay: 50ms + # Publish third value (resets timeout) + - sensor.template.publish: + id: source_timeout_reset + state: 30.0 + # Wait for timeout to fire (100ms + margin) + - delay: 150ms + + # Test 3: Static value timeout + - id: test_timeout_static_script + then: + # Publish initial value + - sensor.template.publish: + id: source_timeout_static + state: 55.5 + # Wait for timeout to fire + - delay: 150ms + + # Test 4: Lambda value timeout + - id: test_timeout_lambda_script + then: + # Publish initial value + - sensor.template.publish: + id: source_timeout_lambda + state: 77.7 + # Wait for timeout to fire + - delay: 150ms + +# Buttons to trigger each test scenario +button: + - platform: template + name: "Test Timeout Last Button" + id: test_timeout_last_button + on_press: + - script.execute: test_timeout_last_script + + - platform: template + name: "Test Timeout Reset Button" + id: test_timeout_reset_button + on_press: + - script.execute: test_timeout_reset_script + + - platform: template + name: "Test Timeout Static Button" + id: test_timeout_static_button + on_press: + - script.execute: test_timeout_static_script + + - platform: template + name: "Test Timeout Lambda Button" + id: test_timeout_lambda_button + on_press: + - script.execute: test_timeout_lambda_script diff --git a/tests/integration/test_sensor_timeout_filter.py b/tests/integration/test_sensor_timeout_filter.py new file mode 100644 index 0000000000..9b4704bb7b --- /dev/null +++ b/tests/integration/test_sensor_timeout_filter.py @@ -0,0 +1,185 @@ +"""Test sensor timeout filter functionality.""" + +from __future__ import annotations + +import asyncio + +from aioesphomeapi import EntityState, SensorState +import pytest + +from .state_utils import InitialStateHelper, build_key_to_entity_mapping +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_sensor_timeout_filter( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test TimeoutFilter and TimeoutFilterConfigured with all modes.""" + loop = asyncio.get_running_loop() + + # Track state changes for all sensors + timeout_last_states: list[float] = [] + timeout_reset_states: list[float] = [] + timeout_static_states: list[float] = [] + timeout_lambda_states: list[float] = [] + + # Futures for each test scenario + test1_complete = loop.create_future() # TimeoutFilter - last mode + test2_complete = loop.create_future() # TimeoutFilter - reset behavior + test3_complete = loop.create_future() # TimeoutFilterConfigured - static value + test4_complete = loop.create_future() # TimeoutFilterConfigured - lambda + + def on_state(state: EntityState) -> None: + """Track sensor state updates.""" + if not isinstance(state, SensorState): + return + + if state.missing_state: + return + + sensor_name = key_to_sensor.get(state.key) + + # Test 1: TimeoutFilter - last mode + if sensor_name == "timeout_last_sensor": + timeout_last_states.append(state.state) + # Expect 2 values: initial 42.0 + timeout fires with 42.0 + if len(timeout_last_states) >= 2 and not test1_complete.done(): + test1_complete.set_result(True) + + # Test 2: TimeoutFilter - reset behavior + elif sensor_name == "timeout_reset_sensor": + timeout_reset_states.append(state.state) + # Expect 4 values: 10.0, 20.0, 30.0, then timeout fires with 30.0 + if len(timeout_reset_states) >= 4 and not test2_complete.done(): + test2_complete.set_result(True) + + # Test 3: TimeoutFilterConfigured - static value + elif sensor_name == "timeout_static_sensor": + timeout_static_states.append(state.state) + # Expect 2 values: initial 55.5 + timeout fires with 99.9 + if len(timeout_static_states) >= 2 and not test3_complete.done(): + test3_complete.set_result(True) + + # Test 4: TimeoutFilterConfigured - lambda + elif sensor_name == "timeout_lambda_sensor": + timeout_lambda_states.append(state.state) + # Expect 2 values: initial 77.7 + timeout fires with -1.0 + if len(timeout_lambda_states) >= 2 and not test4_complete.done(): + test4_complete.set_result(True) + + async with ( + run_compiled(yaml_config), + api_client_connected() as client, + ): + entities, services = await client.list_entities_services() + + key_to_sensor = build_key_to_entity_mapping( + entities, + [ + "timeout_last_sensor", + "timeout_reset_sensor", + "timeout_static_sensor", + "timeout_lambda_sensor", + ], + ) + + initial_state_helper = InitialStateHelper(entities) + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + try: + await initial_state_helper.wait_for_initial_states() + except TimeoutError: + pytest.fail("Timeout waiting for initial states") + + # Helper to find buttons by object_id substring + def find_button(object_id_substring: str) -> int: + """Find a button by object_id substring and return its key.""" + button = next( + (e for e in entities if object_id_substring in e.object_id.lower()), + None, + ) + assert button is not None, f"Button '{object_id_substring}' not found" + return button.key + + # Find all test buttons + test1_button_key = find_button("test_timeout_last_button") + test2_button_key = find_button("test_timeout_reset_button") + test3_button_key = find_button("test_timeout_static_button") + test4_button_key = find_button("test_timeout_lambda_button") + + # === Test 1: TimeoutFilter - last mode === + client.button_command(test1_button_key) + try: + await asyncio.wait_for(test1_complete, timeout=2.0) + except TimeoutError: + pytest.fail(f"Test 1 timeout. Received states: {timeout_last_states}") + + assert len(timeout_last_states) == 2, ( + f"Test 1: Should have 2 states, got {len(timeout_last_states)}: {timeout_last_states}" + ) + assert timeout_last_states[0] == pytest.approx(42.0), ( + f"Test 1: First state should be 42.0, got {timeout_last_states[0]}" + ) + assert timeout_last_states[1] == pytest.approx(42.0), ( + f"Test 1: Timeout should output last value (42.0), got {timeout_last_states[1]}" + ) + + # === Test 2: TimeoutFilter - reset behavior === + client.button_command(test2_button_key) + try: + await asyncio.wait_for(test2_complete, timeout=2.0) + except TimeoutError: + pytest.fail(f"Test 2 timeout. Received states: {timeout_reset_states}") + + assert len(timeout_reset_states) == 4, ( + f"Test 2: Should have 4 states, got {len(timeout_reset_states)}: {timeout_reset_states}" + ) + assert timeout_reset_states[0] == pytest.approx(10.0), ( + f"Test 2: First state should be 10.0, got {timeout_reset_states[0]}" + ) + assert timeout_reset_states[1] == pytest.approx(20.0), ( + f"Test 2: Second state should be 20.0, got {timeout_reset_states[1]}" + ) + assert timeout_reset_states[2] == pytest.approx(30.0), ( + f"Test 2: Third state should be 30.0, got {timeout_reset_states[2]}" + ) + assert timeout_reset_states[3] == pytest.approx(30.0), ( + f"Test 2: Timeout should output last value (30.0), got {timeout_reset_states[3]}" + ) + + # === Test 3: TimeoutFilterConfigured - static value === + client.button_command(test3_button_key) + try: + await asyncio.wait_for(test3_complete, timeout=2.0) + except TimeoutError: + pytest.fail(f"Test 3 timeout. Received states: {timeout_static_states}") + + assert len(timeout_static_states) == 2, ( + f"Test 3: Should have 2 states, got {len(timeout_static_states)}: {timeout_static_states}" + ) + assert timeout_static_states[0] == pytest.approx(55.5), ( + f"Test 3: First state should be 55.5, got {timeout_static_states[0]}" + ) + assert timeout_static_states[1] == pytest.approx(99.9), ( + f"Test 3: Timeout should output configured value (99.9), got {timeout_static_states[1]}" + ) + + # === Test 4: TimeoutFilterConfigured - lambda === + client.button_command(test4_button_key) + try: + await asyncio.wait_for(test4_complete, timeout=2.0) + except TimeoutError: + pytest.fail(f"Test 4 timeout. Received states: {timeout_lambda_states}") + + assert len(timeout_lambda_states) == 2, ( + f"Test 4: Should have 2 states, got {len(timeout_lambda_states)}: {timeout_lambda_states}" + ) + assert timeout_lambda_states[0] == pytest.approx(77.7), ( + f"Test 4: First state should be 77.7, got {timeout_lambda_states[0]}" + ) + assert timeout_lambda_states[1] == pytest.approx(-1.0), ( + f"Test 4: Timeout should evaluate lambda (-1.0), got {timeout_lambda_states[1]}" + ) From 4fc4da6ed2d69d9cdb47d777abc24e0e5e0c29c4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 16 Nov 2025 07:35:31 -0600 Subject: [PATCH 0237/1145] [analyze-memory] Show all core symbols > 100 B instead of top 15 (#11909) --- esphome/analyze_memory/cli.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/esphome/analyze_memory/cli.py b/esphome/analyze_memory/cli.py index 718f42330d..44ade221f8 100644 --- a/esphome/analyze_memory/cli.py +++ b/esphome/analyze_memory/cli.py @@ -15,6 +15,11 @@ from . import ( class MemoryAnalyzerCLI(MemoryAnalyzer): """Memory analyzer with CLI-specific report generation.""" + # Symbol size threshold for detailed analysis + SYMBOL_SIZE_THRESHOLD: int = ( + 100 # Show symbols larger than this in detailed analysis + ) + # Column width constants COL_COMPONENT: int = 29 COL_FLASH_TEXT: int = 14 @@ -191,14 +196,21 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): f"{len(symbols):>{self.COL_CORE_COUNT}} | {percentage:>{self.COL_CORE_PERCENT - 1}.1f}%" ) - # Top 15 largest core symbols + # All core symbols above threshold lines.append("") - lines.append(f"Top 15 Largest {_COMPONENT_CORE} Symbols:") sorted_core_symbols = sorted( self._esphome_core_symbols, key=lambda x: x[2], reverse=True ) + large_core_symbols = [ + (symbol, demangled, size) + for symbol, demangled, size in sorted_core_symbols + if size > self.SYMBOL_SIZE_THRESHOLD + ] - for i, (symbol, demangled, size) in enumerate(sorted_core_symbols[:15]): + lines.append( + f"{_COMPONENT_CORE} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_core_symbols)} symbols):" + ) + for i, (symbol, demangled, size) in enumerate(large_core_symbols): lines.append(f"{i + 1}. {demangled} ({size:,} B)") lines.append("=" * self.TABLE_WIDTH) @@ -268,13 +280,15 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): lines.append(f"Total size: {comp_mem.flash_total:,} B") lines.append("") - # Show all symbols > 100 bytes for better visibility + # Show all symbols above threshold for better visibility large_symbols = [ - (sym, dem, size) for sym, dem, size in sorted_symbols if size > 100 + (sym, dem, size) + for sym, dem, size in sorted_symbols + if size > self.SYMBOL_SIZE_THRESHOLD ] lines.append( - f"{comp_name} Symbols > 100 B ({len(large_symbols)} symbols):" + f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_symbols)} symbols):" ) for i, (symbol, demangled, size) in enumerate(large_symbols): lines.append(f"{i + 1}. {demangled} ({size:,} B)") From 320120883cab070fa21ba187e13f0f3c6447c3b8 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 17 Nov 2025 07:47:54 +1000 Subject: [PATCH 0238/1145] [lvgl] Migrate lv_font creation into Font class and optimise (#11915) --- esphome/components/font/__init__.py | 2 + esphome/components/font/font.cpp | 263 ++++++++++++++++------- esphome/components/font/font.h | 33 +-- esphome/components/lvgl/__init__.py | 14 +- esphome/components/lvgl/font.cpp | 76 ------- esphome/components/lvgl/lv_validation.py | 5 +- esphome/components/lvgl/lvgl_esphome.h | 26 +-- esphome/components/lvgl/types.py | 1 - tests/components/lvgl/lvgl-package.yaml | 4 +- tests/components/lvgl/test.host.yaml | 6 + 10 files changed, 232 insertions(+), 198 deletions(-) delete mode 100644 esphome/components/lvgl/font.cpp diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 32e803f405..2667dbdbdf 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -486,6 +486,8 @@ class GlyphInfo: def glyph_to_glyphinfo(glyph, font, size, bpp): + # Convert to 32 bit unicode codepoint + glyph = ord(glyph) scale = 256 // (1 << bpp) if not font.is_scalable: sizes = [pt_to_px(x.size) for x in font.available_sizes] diff --git a/esphome/components/font/font.cpp b/esphome/components/font/font.cpp index add403fe98..5e3bf1dd20 100644 --- a/esphome/components/font/font.cpp +++ b/esphome/components/font/font.cpp @@ -6,42 +6,147 @@ namespace esphome { namespace font { - static const char *const TAG = "font"; -// Compare the char at the string position with this char. -// Return true if this char is less than or equal the other. -bool Glyph::compare_to(const uint8_t *str) const { - // 1 -> this->char_ - // 2 -> str - for (uint32_t i = 0;; i++) { - if (this->a_char[i] == '\0') - return true; - if (str[i] == '\0') - return false; - if (this->a_char[i] > str[i]) - return false; - if (this->a_char[i] < str[i]) - return true; +#ifdef USE_LVGL_FONT +const uint8_t *Font::get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter) { + auto *fe = (Font *) font->dsc; + const auto *gd = fe->get_glyph_data_(unicode_letter); + if (gd == nullptr) { + return nullptr; } - // this should not happen - return false; + return gd->data; } -int Glyph::match_length(const uint8_t *str) const { - for (uint32_t i = 0;; i++) { - if (this->a_char[i] == '\0') - return i; - if (str[i] != this->a_char[i]) + +bool Font::get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) { + auto *fe = (Font *) font->dsc; + const auto *gd = fe->get_glyph_data_(unicode_letter); + if (gd == nullptr) { + return false; + } + dsc->adv_w = gd->advance; + dsc->ofs_x = gd->offset_x; + dsc->ofs_y = fe->height_ - gd->height - gd->offset_y - fe->lv_font_.base_line; + dsc->box_w = gd->width; + dsc->box_h = gd->height; + dsc->is_placeholder = 0; + dsc->bpp = fe->get_bpp(); + return true; +} + +const Glyph *Font::get_glyph_data_(uint32_t unicode_letter) { + if (unicode_letter == this->last_letter_ && this->last_letter_ != 0) + return this->last_data_; + auto *glyph = this->find_glyph(unicode_letter); + if (glyph == nullptr) { + return nullptr; + } + this->last_data_ = glyph; + this->last_letter_ = unicode_letter; + return glyph; +} +#endif + +/** + * Attempt to extract a 32 bit Unicode codepoint from a UTF-8 string. + * If successful, return the codepoint and set the length to the number of bytes read. + * If the end of the string has been reached and a valid codepoint has not been found, return 0 and set the length to + * 0. + * + * @param utf8_str The input string + * @param length Pointer to length storage + * @return The extracted code point + */ +static uint32_t extract_unicode_codepoint(const char *utf8_str, size_t *length) { + // Safely cast to uint8_t* for correct bitwise operations on bytes + const uint8_t *current = reinterpret_cast(utf8_str); + uint32_t code_point = 0; + uint8_t c1 = *current++; + + // check for end of string + if (c1 == 0) { + *length = 0; + return 0; + } + + // --- 1-Byte Sequence: 0xxxxxxx (ASCII) --- + if (c1 < 0x80) { + // Valid ASCII byte. + code_point = c1; + // Optimization: No need to check for continuation bytes. + } + // --- 2-Byte Sequence: 110xxxxx 10xxxxxx --- + else if ((c1 & 0xE0) == 0xC0) { + uint8_t c2 = *current++; + + // Error Check 1: Check if c2 is a valid continuation byte (10xxxxxx) + if ((c2 & 0xC0) != 0x80) { + *length = 0; return 0; + } + + code_point = (c1 & 0x1F) << 6; + code_point |= (c2 & 0x3F); + + // Error Check 2: Overlong check (2-byte must be > 0x7F) + if (code_point <= 0x7F) { + *length = 0; + return 0; + } } - // this should not happen - return 0; -} -void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const { - *x1 = this->offset_x; - *y1 = this->offset_y; - *width = this->width; - *height = this->height; + // --- 3-Byte Sequence: 1110xxxx 10xxxxxx 10xxxxxx --- + else if ((c1 & 0xF0) == 0xE0) { + uint8_t c2 = *current++; + uint8_t c3 = *current++; + + // Error Check 1: Check continuation bytes + if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) { + *length = 0; + return 0; + } + + code_point = (c1 & 0x0F) << 12; + code_point |= (c2 & 0x3F) << 6; + code_point |= (c3 & 0x3F); + + // Error Check 2: Overlong check (3-byte must be > 0x7FF) + // Also check for surrogates (0xD800-0xDFFF) + if (code_point <= 0x7FF || (code_point >= 0xD800 && code_point <= 0xDFFF)) { + *length = 0; + return 0; + } + } + // --- 4-Byte Sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx --- + else if ((c1 & 0xF8) == 0xF0) { + uint8_t c2 = *current++; + uint8_t c3 = *current++; + uint8_t c4 = *current++; + + // Error Check 1: Check continuation bytes + if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80) || ((c4 & 0xC0) != 0x80)) { + *length = 0; + return 0; + } + + code_point = (c1 & 0x07) << 18; + code_point |= (c2 & 0x3F) << 12; + code_point |= (c3 & 0x3F) << 6; + code_point |= (c4 & 0x3F); + + // Error Check 2: Overlong check (4-byte must be > 0xFFFF) + // Also check for valid Unicode range (must be <= 0x10FFFF) + if (code_point <= 0xFFFF || code_point > 0x10FFFF) { + *length = 0; + return 0; + } + } + // --- Invalid leading byte (e.g., 10xxxxxx or 11111xxx) --- + else { + *length = 0; + return 0; + } + *length = current - reinterpret_cast(utf8_str); + return code_point; } Font::Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight, @@ -53,82 +158,93 @@ Font::Font(const Glyph *data, int data_nr, int baseline, int height, int descend linegap_(height - baseline - descender), xheight_(xheight), capheight_(capheight), - bpp_(bpp) {} -int Font::match_next_glyph(const uint8_t *str, int *match_length) const { + bpp_(bpp) { +#ifdef USE_LVGL_FONT + this->lv_font_.dsc = this; + this->lv_font_.line_height = this->get_height(); + this->lv_font_.base_line = this->lv_font_.line_height - this->get_baseline(); + this->lv_font_.get_glyph_dsc = get_glyph_dsc_cb; + this->lv_font_.get_glyph_bitmap = get_glyph_bitmap; + this->lv_font_.subpx = LV_FONT_SUBPX_NONE; + this->lv_font_.underline_position = -1; + this->lv_font_.underline_thickness = 1; +#endif +} + +const Glyph *Font::find_glyph(uint32_t codepoint) const { int lo = 0; int hi = this->glyphs_.size() - 1; while (lo != hi) { int mid = (lo + hi + 1) / 2; - if (this->glyphs_[mid].compare_to(str)) { + if (this->glyphs_[mid].is_less_or_equal(codepoint)) { lo = mid; } else { hi = mid - 1; } } - *match_length = this->glyphs_[lo].match_length(str); - if (*match_length <= 0) - return -1; - return lo; + auto *result = &this->glyphs_[lo]; + if (result->code_point == codepoint) + return result; + return nullptr; } + #ifdef USE_DISPLAY void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) { *baseline = this->baseline_; *height = this->height_; - int i = 0; int min_x = 0; bool has_char = false; int x = 0; - while (str[i] != '\0') { - int match_length; - int glyph_n = this->match_next_glyph((const uint8_t *) str + i, &match_length); - if (glyph_n < 0) { + for (;;) { + size_t length; + auto code_point = extract_unicode_codepoint(str, &length); + if (length == 0) + break; + str += length; + auto *glyph = this->find_glyph(code_point); + if (glyph == nullptr) { // Unknown char, skip - if (!this->get_glyphs().empty()) - x += this->get_glyphs()[0].advance; - i++; + if (!this->glyphs_.empty()) + x += this->glyphs_[0].advance; continue; } - const Glyph &glyph = this->glyphs_[glyph_n]; if (!has_char) { - min_x = glyph.offset_x; + min_x = glyph->offset_x; } else { - min_x = std::min(min_x, x + glyph.offset_x); + min_x = std::min(min_x, x + glyph->offset_x); } - x += glyph.advance; + x += glyph->advance; - i += match_length; has_char = true; } *x_offset = min_x; *width = x - min_x; } + void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text, Color background) { - int i = 0; int x_at = x_start; - int scan_x1, scan_y1, scan_width, scan_height; - while (text[i] != '\0') { - int match_length; - int glyph_n = this->match_next_glyph((const uint8_t *) text + i, &match_length); - if (glyph_n < 0) { + for (;;) { + size_t length; + auto code_point = extract_unicode_codepoint(text, &length); + if (length == 0) + break; + text += length; + auto *glyph = this->find_glyph(code_point); + if (glyph == nullptr) { // Unknown char, skip - ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]); - if (!this->get_glyphs().empty()) { - uint8_t glyph_width = this->get_glyphs()[0].advance; - display->filled_rectangle(x_at, y_start, glyph_width, this->height_, color); + ESP_LOGW(TAG, "Codepoint 0x%08" PRIx32 " not found in font", code_point); + if (!this->glyphs_.empty()) { + uint8_t glyph_width = this->glyphs_[0].advance; + display->rectangle(x_at, y_start, glyph_width, this->height_, color); x_at += glyph_width; } - - i++; continue; } - const Glyph &glyph = this->get_glyphs()[glyph_n]; - glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); - - const uint8_t *data = glyph.data; - const int max_x = x_at + scan_x1 + scan_width; - const int max_y = y_start + scan_y1 + scan_height; + const uint8_t *data = glyph->data; + const int max_x = x_at + glyph->offset_x + glyph->width; + const int max_y = y_start + glyph->offset_y + glyph->height; uint8_t bitmask = 0; uint8_t pixel_data = 0; @@ -141,10 +257,10 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo auto b_g = (float) background.g; auto b_b = (float) background.b; auto b_w = (float) background.w; - for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) { - for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) { + for (int glyph_y = y_start + glyph->offset_y; glyph_y != max_y; glyph_y++) { + for (int glyph_x = x_at + glyph->offset_x; glyph_x != max_x; glyph_x++) { uint8_t pixel = 0; - for (int bit_num = 0; bit_num != this->bpp_; bit_num++) { + for (uint8_t bit_num = 0; bit_num != this->bpp_; bit_num++) { if (bitmask == 0) { pixel_data = progmem_read_byte(data++); bitmask = 0x80; @@ -164,12 +280,9 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo } } } - x_at += glyph.advance; - - i += match_length; + x_at += glyph->advance; } } #endif - } // namespace font } // namespace esphome diff --git a/esphome/components/font/font.h b/esphome/components/font/font.h index cb6cc89137..262ded3be4 100644 --- a/esphome/components/font/font.h +++ b/esphome/components/font/font.h @@ -6,6 +6,9 @@ #ifdef USE_DISPLAY #include "esphome/components/display/display.h" #endif +#ifdef USE_LVGL_FONT +#include +#endif namespace esphome { namespace font { @@ -14,9 +17,9 @@ class Font; class Glyph { public: - constexpr Glyph(const char *a_char, const uint8_t *data, int advance, int offset_x, int offset_y, int width, + constexpr Glyph(uint32_t code_point, const uint8_t *data, int advance, int offset_x, int offset_y, int width, int height) - : a_char(a_char), + : code_point(code_point), data(data), advance(advance), offset_x(offset_x), @@ -24,24 +27,15 @@ class Glyph { width(width), height(height) {} - const uint8_t *get_char() const { return reinterpret_cast(this->a_char); } + bool is_less_or_equal(uint32_t other) const { return this->code_point <= other; } - bool compare_to(const uint8_t *str) const; - - int match_length(const uint8_t *str) const; - - void scan_area(int *x1, int *y1, int *width, int *height) const; - - const char *a_char; + const uint32_t code_point; const uint8_t *data; int advance; int offset_x; int offset_y; int width; int height; - - protected: - friend Font; }; class Font @@ -64,7 +58,7 @@ class Font Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight, uint8_t bpp = 1); - int match_next_glyph(const uint8_t *str, int *match_length) const; + const Glyph *find_glyph(uint32_t codepoint) const; #ifdef USE_DISPLAY void print(int x_start, int y_start, display::Display *display, Color color, const char *text, @@ -79,6 +73,9 @@ class Font inline int get_xheight() { return this->xheight_; } inline int get_capheight() { return this->capheight_; } inline int get_bpp() { return this->bpp_; } +#ifdef USE_LVGL_FONT + const lv_font_t *get_lv_font() const { return &this->lv_font_; } +#endif const ConstVector &get_glyphs() const { return glyphs_; } @@ -91,6 +88,14 @@ class Font int xheight_; int capheight_; uint8_t bpp_; // bits per pixel +#ifdef USE_LVGL_FONT + lv_font_t lv_font_{}; + static const uint8_t *get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter); + static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next); + const Glyph *get_glyph_data_(uint32_t unicode_letter); + uint32_t last_letter_{}; + const Glyph *last_data_{}; +#endif }; } // namespace font diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 2a24f343c3..eaa37b54dd 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -52,15 +52,7 @@ from .schemas import ( from .styles import add_top_layer, styles_to_code, theme_to_code from .touchscreens import touchscreen_schema, touchscreens_to_code from .trigger import add_on_boot_triggers, generate_triggers -from .types import ( - FontEngine, - IdleTrigger, - PlainTrigger, - lv_font_t, - lv_group_t, - lv_style_t, - lvgl_ns, -) +from .types import IdleTrigger, PlainTrigger, lv_font_t, lv_group_t, lv_style_t, lvgl_ns from .widgets import ( LvScrActType, Widget, @@ -244,7 +236,6 @@ async def to_code(configs): cg.add_global(lvgl_ns.using) for font in helpers.esphome_fonts_used: await cg.get_variable(font) - cg.new_Pvariable(ID(f"{font}_engine", True, type=FontEngine), MockObj(font)) default_font = config_0[df.CONF_DEFAULT_FONT] if not lvalid.is_lv_font(default_font): add_define( @@ -256,7 +247,8 @@ async def to_code(configs): type=lv_font_t.operator("ptr").operator("const"), ) cg.new_variable( - globfont_id, MockObj(await lvalid.lv_font.process(default_font)) + globfont_id, + MockObj(await lvalid.lv_font.process(default_font), "->").get_lv_font(), ) add_define("LV_FONT_DEFAULT", df.DEFAULT_ESPHOME_FONT) else: diff --git a/esphome/components/lvgl/font.cpp b/esphome/components/lvgl/font.cpp deleted file mode 100644 index 1976fb9608..0000000000 --- a/esphome/components/lvgl/font.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "lvgl_esphome.h" - -#ifdef USE_LVGL_FONT -namespace esphome { -namespace lvgl { - -static const uint8_t *get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter) { - auto *fe = (FontEngine *) font->dsc; - const auto *gd = fe->get_glyph_data(unicode_letter); - if (gd == nullptr) - return nullptr; - // esph_log_d(TAG, "Returning bitmap @ %X", (uint32_t)gd->data); - - return gd->data; -} - -static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) { - auto *fe = (FontEngine *) font->dsc; - const auto *gd = fe->get_glyph_data(unicode_letter); - if (gd == nullptr) - return false; - dsc->adv_w = gd->advance; - dsc->ofs_x = gd->offset_x; - dsc->ofs_y = fe->height - gd->height - gd->offset_y - fe->baseline; - dsc->box_w = gd->width; - dsc->box_h = gd->height; - dsc->is_placeholder = 0; - dsc->bpp = fe->bpp; - return true; -} - -FontEngine::FontEngine(font::Font *esp_font) : font_(esp_font) { - this->bpp = esp_font->get_bpp(); - this->lv_font_.dsc = this; - this->lv_font_.line_height = this->height = esp_font->get_height(); - this->lv_font_.base_line = this->baseline = this->lv_font_.line_height - esp_font->get_baseline(); - this->lv_font_.get_glyph_dsc = get_glyph_dsc_cb; - this->lv_font_.get_glyph_bitmap = get_glyph_bitmap; - this->lv_font_.subpx = LV_FONT_SUBPX_NONE; - this->lv_font_.underline_position = -1; - this->lv_font_.underline_thickness = 1; -} - -const lv_font_t *FontEngine::get_lv_font() { return &this->lv_font_; } - -const font::Glyph *FontEngine::get_glyph_data(uint32_t unicode_letter) { - if (unicode_letter == last_letter_) - return this->last_data_; - uint8_t unicode[5]; - memset(unicode, 0, sizeof unicode); - if (unicode_letter > 0xFFFF) { - unicode[0] = 0xF0 + ((unicode_letter >> 18) & 0x7); - unicode[1] = 0x80 + ((unicode_letter >> 12) & 0x3F); - unicode[2] = 0x80 + ((unicode_letter >> 6) & 0x3F); - unicode[3] = 0x80 + (unicode_letter & 0x3F); - } else if (unicode_letter > 0x7FF) { - unicode[0] = 0xE0 + ((unicode_letter >> 12) & 0xF); - unicode[1] = 0x80 + ((unicode_letter >> 6) & 0x3F); - unicode[2] = 0x80 + (unicode_letter & 0x3F); - } else if (unicode_letter > 0x7F) { - unicode[0] = 0xC0 + ((unicode_letter >> 6) & 0x1F); - unicode[1] = 0x80 + (unicode_letter & 0x3F); - } else { - unicode[0] = unicode_letter; - } - int match_length; - int glyph_n = this->font_->match_next_glyph(unicode, &match_length); - if (glyph_n < 0) - return nullptr; - this->last_data_ = &this->font_->get_glyphs()[glyph_n]; - this->last_letter_ = unicode_letter; - return this->last_data_; -} -} // namespace lvgl -} // namespace esphome -#endif // USES_LVGL_FONT diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index 045258555c..23c322c31f 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -493,6 +493,7 @@ class LvFont(LValidator): return LV_FONTS if is_lv_font(value): return lv_builtin_font(value) + add_lv_use("font") fontval = cv.use_id(Font)(value) esphome_fonts_used.add(fontval) return requires_component("font")(fontval) @@ -502,7 +503,9 @@ class LvFont(LValidator): async def process(self, value, args=()): if is_lv_font(value): return literal(f"&lv_font_{value}") - return literal(f"{value}_engine->get_lv_font()") + if isinstance(value, str): + return literal(f"{value}") + return await super().process(value, args) lv_font = LvFont() diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 196a0d1cb4..bd6f1fdb61 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -50,6 +50,14 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_332; #endif // LV_COLOR_DEPTH +#ifdef USE_LVGL_FONT +inline void lv_obj_set_style_text_font(lv_obj_t *obj, const font::Font *font, lv_style_selector_t part) { + lv_obj_set_style_text_font(obj, font->get_lv_font(), part); +} +inline void lv_style_set_text_font(lv_style_t *style, const font::Font *font) { + lv_style_set_text_font(style, font->get_lv_font()); +} +#endif #ifdef USE_LVGL_IMAGE // Shortcut / overload, so that the source of an image can easily be updated // from within a lambda. @@ -134,24 +142,6 @@ template class ObjUpdateAction : public Action { protected: std::function lamb_; }; -#ifdef USE_LVGL_FONT -class FontEngine { - public: - FontEngine(font::Font *esp_font); - const lv_font_t *get_lv_font(); - - const font::Glyph *get_glyph_data(uint32_t unicode_letter); - uint16_t baseline{}; - uint16_t height{}; - uint8_t bpp{}; - - protected: - font::Font *font_{}; - uint32_t last_letter_{}; - const font::Glyph *last_data_{}; - lv_font_t lv_font_{}; -}; -#endif // USE_LVGL_FONT #ifdef USE_LVGL_ANIMIMG void lv_animimg_stop(lv_obj_t *obj); #endif // USE_LVGL_ANIMIMG diff --git a/esphome/components/lvgl/types.py b/esphome/components/lvgl/types.py index 035320b6ac..b99c0ad5a3 100644 --- a/esphome/components/lvgl/types.py +++ b/esphome/components/lvgl/types.py @@ -45,7 +45,6 @@ lv_coord_t = cg.global_ns.namespace("lv_coord_t") lv_event_code_t = cg.global_ns.enum("lv_event_code_t") lv_indev_type_t = cg.global_ns.enum("lv_indev_type_t") lv_key_t = cg.global_ns.enum("lv_key_t") -FontEngine = lvgl_ns.class_("FontEngine") PlainTrigger = esphome_ns.class_("Trigger<>", automation.Trigger.template()) DrawEndTrigger = esphome_ns.class_( "Trigger", automation.Trigger.template(cg.uint32, cg.uint32) diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index d7c342b16e..e42a813b40 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -76,7 +76,7 @@ lvgl: line_width: 8 line_rounded: true - id: date_style - text_font: roboto10 + text_font: !lambda return id(roboto10); align: center text_color: !lambda return color_id2; bg_opa: cover @@ -267,7 +267,7 @@ lvgl: snprintf(buf, sizeof(buf), "Setup: %d", 42); return std::string(buf); align: top_mid - text_font: space16 + text_font: !lambda return id(space16); - label: id: chip_info_label # Test complex setup lambda (real-world pattern) diff --git a/tests/components/lvgl/test.host.yaml b/tests/components/lvgl/test.host.yaml index 39d9a0ebf3..00a8cd8c01 100644 --- a/tests/components/lvgl/test.host.yaml +++ b/tests/components/lvgl/test.host.yaml @@ -18,6 +18,7 @@ touchscreen: lvgl: - id: lvgl_0 + default_font: space16 displays: sdl0 - id: lvgl_1 displays: sdl1 @@ -39,3 +40,8 @@ lvgl: text: Click ME on_click: logger.log: Clicked + +font: + - file: "gfonts://Roboto" + id: space16 + bpp: 4 From 986d3c8f137aad0a6097606fae49b98a22949258 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 16 Nov 2025 18:38:38 -0600 Subject: [PATCH 0239/1145] [sntp] Merge multiple instances to fix crash and undefined behavior (#11904) --- esphome/components/sntp/time.py | 68 +++++ tests/component_tests/sntp/__init__.py | 1 + .../sntp/config/sntp_test.yaml | 22 ++ tests/component_tests/sntp/test_init.py | 238 ++++++++++++++++++ 4 files changed, 329 insertions(+) create mode 100644 tests/component_tests/sntp/__init__.py create mode 100644 tests/component_tests/sntp/config/sntp_test.yaml create mode 100644 tests/component_tests/sntp/test_init.py diff --git a/esphome/components/sntp/time.py b/esphome/components/sntp/time.py index d27fc9991d..69a2436d3d 100644 --- a/esphome/components/sntp/time.py +++ b/esphome/components/sntp/time.py @@ -1,9 +1,14 @@ +import logging + import esphome.codegen as cg from esphome.components import time as time_ +from esphome.config_helpers import merge_config import esphome.config_validation as cv from esphome.const import ( CONF_ID, + CONF_PLATFORM, CONF_SERVERS, + CONF_TIME, PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, @@ -12,13 +17,74 @@ from esphome.const import ( PLATFORM_RTL87XX, ) from esphome.core import CORE +import esphome.final_validate as fv +from esphome.types import ConfigType + +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ["network"] + +CONF_SNTP = "sntp" + sntp_ns = cg.esphome_ns.namespace("sntp") SNTPComponent = sntp_ns.class_("SNTPComponent", time_.RealTimeClock) DEFAULT_SERVERS = ["0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org"] + +def _sntp_final_validate(config: ConfigType) -> None: + """Merge multiple SNTP instances into one, similar to OTA merging behavior.""" + full_conf = fv.full_config.get() + time_confs = full_conf.get(CONF_TIME, []) + + sntp_configs: list[ConfigType] = [] + other_time_configs: list[ConfigType] = [] + + for time_conf in time_confs: + if time_conf.get(CONF_PLATFORM) == CONF_SNTP: + sntp_configs.append(time_conf) + else: + other_time_configs.append(time_conf) + + if len(sntp_configs) <= 1: + return + + # Merge all SNTP configs into the first one + merged = sntp_configs[0] + for sntp_conf in sntp_configs[1:]: + # Validate that IDs are consistent if manually specified + if merged[CONF_ID].is_manual and sntp_conf[CONF_ID].is_manual: + raise cv.Invalid( + f"Found multiple SNTP configurations but {CONF_ID} is inconsistent" + ) + merged = merge_config(merged, sntp_conf) + + # Deduplicate servers while preserving order + servers = merged[CONF_SERVERS] + unique_servers = list(dict.fromkeys(servers)) + + # Warn if we're dropping servers due to 3-server limit + if len(unique_servers) > 3: + dropped = unique_servers[3:] + unique_servers = unique_servers[:3] + _LOGGER.warning( + "SNTP supports maximum 3 servers. Dropped excess server(s): %s", + dropped, + ) + + merged[CONF_SERVERS] = unique_servers + + _LOGGER.warning( + "Found and merged %d SNTP time configurations into one instance", + len(sntp_configs), + ) + + # Replace time configs with merged SNTP + other time platforms + other_time_configs.append(merged) + full_conf[CONF_TIME] = other_time_configs + fv.full_config.set(full_conf) + + CONFIG_SCHEMA = cv.All( time_.TIME_SCHEMA.extend( { @@ -40,6 +106,8 @@ CONFIG_SCHEMA = cv.All( ), ) +FINAL_VALIDATE_SCHEMA = _sntp_final_validate + async def to_code(config): servers = config[CONF_SERVERS] diff --git a/tests/component_tests/sntp/__init__.py b/tests/component_tests/sntp/__init__.py new file mode 100644 index 0000000000..7d323a4980 --- /dev/null +++ b/tests/component_tests/sntp/__init__.py @@ -0,0 +1 @@ +"""Tests for SNTP component.""" diff --git a/tests/component_tests/sntp/config/sntp_test.yaml b/tests/component_tests/sntp/config/sntp_test.yaml new file mode 100644 index 0000000000..3942c9606b --- /dev/null +++ b/tests/component_tests/sntp/config/sntp_test.yaml @@ -0,0 +1,22 @@ +esphome: + name: sntp-test + +esp32: + board: esp32dev + framework: + type: esp-idf + +wifi: + ssid: "testssid" + password: "testpassword" + +# Test multiple SNTP instances that should be merged +time: + - platform: sntp + servers: + - 192.168.1.1 + - pool.ntp.org + - platform: sntp + servers: + - pool.ntp.org + - 192.168.1.2 diff --git a/tests/component_tests/sntp/test_init.py b/tests/component_tests/sntp/test_init.py new file mode 100644 index 0000000000..9197ff55d0 --- /dev/null +++ b/tests/component_tests/sntp/test_init.py @@ -0,0 +1,238 @@ +"""Tests for SNTP time configuration validation.""" + +from __future__ import annotations + +import logging +from typing import Any + +import pytest + +from esphome import config_validation as cv +from esphome.components.sntp.time import CONF_SNTP, _sntp_final_validate +from esphome.const import CONF_ID, CONF_PLATFORM, CONF_SERVERS, CONF_TIME +from esphome.core import ID +import esphome.final_validate as fv + + +@pytest.mark.parametrize( + ("time_configs", "expected_count", "expected_servers", "warning_messages"), + [ + pytest.param( + [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time", is_manual=False), + CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"], + } + ], + 1, + ["192.168.1.1", "pool.ntp.org"], + [], + id="single_instance_no_merge", + ), + pytest.param( + [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_1", is_manual=False), + CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"], + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_2", is_manual=False), + CONF_SERVERS: ["192.168.1.2"], + }, + ], + 1, + ["192.168.1.1", "pool.ntp.org", "192.168.1.2"], + ["Found and merged 2 SNTP time configurations into one instance"], + id="two_instances_merged", + ), + pytest.param( + [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_1", is_manual=False), + CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"], + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_2", is_manual=False), + CONF_SERVERS: ["pool.ntp.org", "192.168.1.2"], + }, + ], + 1, + ["192.168.1.1", "pool.ntp.org", "192.168.1.2"], + ["Found and merged 2 SNTP time configurations into one instance"], + id="deduplication_preserves_order", + ), + pytest.param( + [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_1", is_manual=False), + CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"], + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_2", is_manual=False), + CONF_SERVERS: ["192.168.1.2", "pool2.ntp.org"], + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_3", is_manual=False), + CONF_SERVERS: ["pool3.ntp.org"], + }, + ], + 1, + ["192.168.1.1", "pool.ntp.org", "192.168.1.2"], + [ + "SNTP supports maximum 3 servers. Dropped excess server(s): ['pool2.ntp.org', 'pool3.ntp.org']", + "Found and merged 3 SNTP time configurations into one instance", + ], + id="three_instances_drops_excess_servers", + ), + pytest.param( + [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_1", is_manual=False), + CONF_SERVERS: [ + "192.168.1.1", + "pool.ntp.org", + "pool.ntp.org", + "192.168.1.1", + ], + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_2", is_manual=False), + CONF_SERVERS: ["pool.ntp.org", "192.168.1.2"], + }, + ], + 1, + ["192.168.1.1", "pool.ntp.org", "192.168.1.2"], + ["Found and merged 2 SNTP time configurations into one instance"], + id="deduplication_multiple_duplicates", + ), + ], +) +def test_sntp_instance_merging( + time_configs: list[dict[str, Any]], + expected_count: int, + expected_servers: list[str], + warning_messages: list[str], + caplog: pytest.LogCaptureFixture, +) -> None: + """Test SNTP instance merging behavior.""" + # Create a mock full config with time configs + full_conf = {CONF_TIME: time_configs.copy()} + + # Set the context var + token = fv.full_config.set(full_conf) + try: + with caplog.at_level(logging.WARNING): + _sntp_final_validate({}) + + # Get the updated config + updated_conf = fv.full_config.get() + + # Check if merging occurred + if len(time_configs) > 1: + # Verify only one SNTP instance remains + sntp_instances = [ + tc + for tc in updated_conf[CONF_TIME] + if tc.get(CONF_PLATFORM) == CONF_SNTP + ] + assert len(sntp_instances) == expected_count + + # Verify server list + assert sntp_instances[0][CONF_SERVERS] == expected_servers + + # Verify warnings + for expected_msg in warning_messages: + assert any( + expected_msg in record.message for record in caplog.records + ), f"Expected warning message '{expected_msg}' not found in log" + else: + # Single instance should not trigger merging or warnings + assert len(caplog.records) == 0 + # Config should be unchanged + assert updated_conf[CONF_TIME] == time_configs + finally: + fv.full_config.reset(token) + + +def test_sntp_inconsistent_manual_ids() -> None: + """Test that inconsistent manual IDs raise an error.""" + # Create configs with manual IDs that are inconsistent + time_configs = [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_1", is_manual=True), + CONF_SERVERS: ["192.168.1.1"], + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_2", is_manual=True), + CONF_SERVERS: ["192.168.1.2"], + }, + ] + + full_conf = {CONF_TIME: time_configs} + + token = fv.full_config.set(full_conf) + try: + with pytest.raises( + cv.Invalid, + match="Found multiple SNTP configurations but id is inconsistent", + ): + _sntp_final_validate({}) + finally: + fv.full_config.reset(token) + + +def test_sntp_with_other_time_platforms(caplog: pytest.LogCaptureFixture) -> None: + """Test that SNTP merging doesn't affect other time platforms.""" + time_configs = [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_1", is_manual=False), + CONF_SERVERS: ["192.168.1.1"], + }, + { + CONF_PLATFORM: "homeassistant", + CONF_ID: ID("homeassistant_time", is_manual=False), + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_2", is_manual=False), + CONF_SERVERS: ["192.168.1.2"], + }, + ] + + full_conf = {CONF_TIME: time_configs.copy()} + + token = fv.full_config.set(full_conf) + try: + with caplog.at_level(logging.WARNING): + _sntp_final_validate({}) + + updated_conf = fv.full_config.get() + + # Should have 2 time platforms: 1 merged SNTP + 1 homeassistant + assert len(updated_conf[CONF_TIME]) == 2 + + # Find the platforms + platforms = {tc[CONF_PLATFORM] for tc in updated_conf[CONF_TIME]} + assert platforms == {CONF_SNTP, "homeassistant"} + + # Verify SNTP was merged + sntp_instances = [ + tc for tc in updated_conf[CONF_TIME] if tc[CONF_PLATFORM] == CONF_SNTP + ] + assert len(sntp_instances) == 1 + assert sntp_instances[0][CONF_SERVERS] == ["192.168.1.1", "192.168.1.2"] + finally: + fv.full_config.reset(token) From 96ee38759d7f5a6dd0e9ea7ca4331007ef49e936 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 16 Nov 2025 18:38:52 -0600 Subject: [PATCH 0240/1145] [web_server.ota] Merge multiple instances to prevent undefined behavior (#11905) --- esphome/components/web_server/ota/__init__.py | 58 ++++++- .../ota/test_web_server_ota.py | 153 ++++++++++++++++++ 2 files changed, 210 insertions(+), 1 deletion(-) diff --git a/esphome/components/web_server/ota/__init__.py b/esphome/components/web_server/ota/__init__.py index 4a98db8877..260e6aea6d 100644 --- a/esphome/components/web_server/ota/__init__.py +++ b/esphome/components/web_server/ota/__init__.py @@ -1,10 +1,17 @@ +import logging + import esphome.codegen as cg from esphome.components.esp32 import add_idf_component from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code +from esphome.config_helpers import merge_config import esphome.config_validation as cv -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_OTA, CONF_PLATFORM, CONF_WEB_SERVER from esphome.core import CORE, coroutine_with_priority from esphome.coroutine import CoroPriority +import esphome.final_validate as fv +from esphome.types import ConfigType + +_LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network", "web_server_base"] @@ -12,6 +19,53 @@ DEPENDENCIES = ["network", "web_server_base"] web_server_ns = cg.esphome_ns.namespace("web_server") WebServerOTAComponent = web_server_ns.class_("WebServerOTAComponent", OTAComponent) + +def _web_server_ota_final_validate(config: ConfigType) -> None: + """Merge multiple web_server OTA instances into one. + + Multiple web_server OTA instances register duplicate HTTP handlers for /update, + causing undefined behavior. Merge them into a single instance. + """ + full_conf = fv.full_config.get() + ota_confs = full_conf.get(CONF_OTA, []) + + web_server_ota_configs: list[ConfigType] = [] + other_ota_configs: list[ConfigType] = [] + + for ota_conf in ota_confs: + if ota_conf.get(CONF_PLATFORM) == CONF_WEB_SERVER: + web_server_ota_configs.append(ota_conf) + else: + other_ota_configs.append(ota_conf) + + if len(web_server_ota_configs) <= 1: + return + + # Merge all web_server OTA configs into the first one + merged = web_server_ota_configs[0] + for ota_conf in web_server_ota_configs[1:]: + # Validate that IDs are consistent if manually specified + if ( + merged[CONF_ID].is_manual + and ota_conf[CONF_ID].is_manual + and merged[CONF_ID] != ota_conf[CONF_ID] + ): + raise cv.Invalid( + f"Found multiple web_server OTA configurations but {CONF_ID} is inconsistent" + ) + merged = merge_config(merged, ota_conf) + + _LOGGER.warning( + "Found and merged %d web_server OTA configurations into one instance", + len(web_server_ota_configs), + ) + + # Replace OTA configs with merged web_server + other OTA platforms + other_ota_configs.append(merged) + full_conf[CONF_OTA] = other_ota_configs + fv.full_config.set(full_conf) + + CONFIG_SCHEMA = ( cv.Schema( { @@ -22,6 +76,8 @@ CONFIG_SCHEMA = ( .extend(cv.COMPONENT_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = _web_server_ota_final_validate + @coroutine_with_priority(CoroPriority.WEB_SERVER_OTA) async def to_code(config): diff --git a/tests/component_tests/ota/test_web_server_ota.py b/tests/component_tests/ota/test_web_server_ota.py index 0d8ff6f134..794eaac9be 100644 --- a/tests/component_tests/ota/test_web_server_ota.py +++ b/tests/component_tests/ota/test_web_server_ota.py @@ -1,6 +1,18 @@ """Tests for the web_server OTA platform.""" +from __future__ import annotations + from collections.abc import Callable +import logging +from typing import Any + +import pytest + +from esphome import config_validation as cv +from esphome.components.web_server.ota import _web_server_ota_final_validate +from esphome.const import CONF_ID, CONF_OTA, CONF_PLATFORM, CONF_WEB_SERVER +from esphome.core import ID +import esphome.final_validate as fv def test_web_server_ota_generated(generate_main: Callable[[str], str]) -> None: @@ -100,3 +112,144 @@ def test_web_server_ota_esp8266(generate_main: Callable[[str], str]) -> None: # Check web server OTA component is present assert "WebServerOTAComponent" in main_cpp assert "web_server::WebServerOTAComponent" in main_cpp + + +@pytest.mark.parametrize( + ("ota_configs", "expected_count", "warning_expected"), + [ + pytest.param( + [ + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web", is_manual=False), + } + ], + 1, + False, + id="single_instance_no_merge", + ), + pytest.param( + [ + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web_1", is_manual=False), + }, + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web_2", is_manual=False), + }, + ], + 1, + True, + id="two_instances_merged", + ), + pytest.param( + [ + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web_1", is_manual=False), + }, + { + CONF_PLATFORM: "esphome", + CONF_ID: ID("ota_esphome", is_manual=False), + }, + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web_2", is_manual=False), + }, + ], + 2, + True, + id="mixed_platforms_web_server_merged", + ), + ], +) +def test_web_server_ota_instance_merging( + ota_configs: list[dict[str, Any]], + expected_count: int, + warning_expected: bool, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test web_server OTA instance merging behavior.""" + full_conf = {CONF_OTA: ota_configs.copy()} + + token = fv.full_config.set(full_conf) + try: + with caplog.at_level(logging.WARNING): + _web_server_ota_final_validate({}) + + updated_conf = fv.full_config.get() + + # Verify total number of OTA platforms + assert len(updated_conf[CONF_OTA]) == expected_count + + # Verify warning + if warning_expected: + assert any( + "Found and merged" in record.message + and "web_server OTA" in record.message + for record in caplog.records + ), "Expected merge warning not found in log" + else: + assert len(caplog.records) == 0, "Unexpected warnings logged" + finally: + fv.full_config.reset(token) + + +def test_web_server_ota_consistent_manual_ids( + caplog: pytest.LogCaptureFixture, +) -> None: + """Test that consistent manual IDs can be merged successfully.""" + ota_configs = [ + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web", is_manual=True), + }, + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web", is_manual=True), + }, + ] + + full_conf = {CONF_OTA: ota_configs} + + token = fv.full_config.set(full_conf) + try: + with caplog.at_level(logging.WARNING): + _web_server_ota_final_validate({}) + + updated_conf = fv.full_config.get() + assert len(updated_conf[CONF_OTA]) == 1 + assert updated_conf[CONF_OTA][0][CONF_ID].id == "ota_web" + assert any( + "Found and merged" in record.message and "web_server OTA" in record.message + for record in caplog.records + ) + finally: + fv.full_config.reset(token) + + +def test_web_server_ota_inconsistent_manual_ids() -> None: + """Test that inconsistent manual IDs raise an error.""" + ota_configs = [ + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web_1", is_manual=True), + }, + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web_2", is_manual=True), + }, + ] + + full_conf = {CONF_OTA: ota_configs} + + token = fv.full_config.set(full_conf) + try: + with pytest.raises( + cv.Invalid, + match="Found multiple web_server OTA configurations but id is inconsistent", + ): + _web_server_ota_final_validate({}) + finally: + fv.full_config.reset(token) From 3b860e784c462ee07c74b79a0393d29f635d876d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 16 Nov 2025 18:39:01 -0600 Subject: [PATCH 0241/1145] [web_server_idf] Fix lwIP assertion crash by shutting down sockets on connection close (#11937) --- .../components/web_server_idf/web_server_idf.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 0dab5e7e8c..ce91569de2 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -489,10 +489,18 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest * void AsyncEventSourceResponse::destroy(void *ptr) { auto *rsp = static_cast(ptr); - ESP_LOGD(TAG, "Event source connection closed (fd: %d)", rsp->fd_.load()); - // Mark as dead by setting fd to 0 - will be cleaned up in the main loop - rsp->fd_.store(0); - // Note: We don't delete or remove from set here to avoid race conditions + int fd = rsp->fd_.exchange(0); // Atomically get and clear fd + + if (fd > 0) { + ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd); + // Immediately shut down the socket to prevent lwIP from delivering more data + // This prevents "recv_tcp: recv for wrong pcb!" assertions when the TCP stack + // tries to deliver queued data after the session is marked as dead + // See: https://github.com/esphome/esphome/issues/11936 + shutdown(fd, SHUT_RDWR); + // Note: We don't close() the socket - httpd owns it and will close it + } + // Session will be cleaned up in the main loop to avoid race conditions } // helper for allowing only unique entries in the queue From aa097a2fe6ba533a23b1598d44290c677d03ef04 Mon Sep 17 00:00:00 2001 From: Anton Sergunov Date: Mon, 17 Nov 2025 07:25:00 +0600 Subject: [PATCH 0242/1145] [uart] Setup uart pins only if flags are set (#11914) Co-authored-by: J. Nick Koston --- .../components/uart/uart_component_esp8266.cpp | 18 +++++++++++++----- .../components/uart/uart_component_esp_idf.cpp | 18 +++++++++++++----- .../uart/uart_component_libretiny.cpp | 2 +- .../components/uart/uart_component_rp2040.cpp | 18 +++++++++++++----- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 7a453dbb50..c84a877ef4 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -56,11 +56,19 @@ uint32_t ESP8266UartComponent::get_config() { } void ESP8266UartComponent::setup() { - if (this->rx_pin_) { - this->rx_pin_->setup(); - } - if (this->tx_pin_ && this->rx_pin_ != this->tx_pin_) { - this->tx_pin_->setup(); + auto setup_pin_if_needed = [](InternalGPIOPin *pin) { + if (!pin) { + return; + } + const auto mask = gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN; + if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) { + pin->setup(); + } + }; + + setup_pin_if_needed(this->rx_pin_); + if (this->rx_pin_ != this->tx_pin_) { + setup_pin_if_needed(this->tx_pin_); } // Use Arduino HardwareSerial UARTs if all used pins match the ones diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 70a13c9e37..61ca8c1c0c 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -133,11 +133,19 @@ void IDFUARTComponent::load_settings(bool dump_config) { return; } - if (this->rx_pin_) { - this->rx_pin_->setup(); - } - if (this->tx_pin_ && this->rx_pin_ != this->tx_pin_) { - this->tx_pin_->setup(); + auto setup_pin_if_needed = [](InternalGPIOPin *pin) { + if (!pin) { + return; + } + const auto mask = gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN; + if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) { + pin->setup(); + } + }; + + setup_pin_if_needed(this->rx_pin_); + if (this->rx_pin_ != this->tx_pin_) { + setup_pin_if_needed(this->tx_pin_); } int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; diff --git a/esphome/components/uart/uart_component_libretiny.cpp b/esphome/components/uart/uart_component_libretiny.cpp index 8d1d28fce4..1e408b169b 100644 --- a/esphome/components/uart/uart_component_libretiny.cpp +++ b/esphome/components/uart/uart_component_libretiny.cpp @@ -53,7 +53,7 @@ void LibreTinyUARTComponent::setup() { auto shouldFallbackToSoftwareSerial = [&]() -> bool { auto hasFlags = [](InternalGPIOPin *pin, const gpio::Flags mask) -> bool { - return pin && pin->get_flags() & mask != gpio::Flags::FLAG_NONE; + return pin && (pin->get_flags() & mask) != gpio::Flags::FLAG_NONE; }; if (hasFlags(this->tx_pin_, gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN) || hasFlags(this->rx_pin_, gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN)) { diff --git a/esphome/components/uart/uart_component_rp2040.cpp b/esphome/components/uart/uart_component_rp2040.cpp index c78691653d..cd3905b5c1 100644 --- a/esphome/components/uart/uart_component_rp2040.cpp +++ b/esphome/components/uart/uart_component_rp2040.cpp @@ -52,11 +52,19 @@ uint16_t RP2040UartComponent::get_config() { } void RP2040UartComponent::setup() { - if (this->rx_pin_) { - this->rx_pin_->setup(); - } - if (this->tx_pin_ && this->rx_pin_ != this->tx_pin_) { - this->tx_pin_->setup(); + auto setup_pin_if_needed = [](InternalGPIOPin *pin) { + if (!pin) { + return; + } + const auto mask = gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN; + if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) { + pin->setup(); + } + }; + + setup_pin_if_needed(this->rx_pin_); + if (this->rx_pin_ != this->tx_pin_) { + setup_pin_if_needed(this->tx_pin_); } uint16_t config = get_config(); From aed80732f965ec9dc93fb1bde0dc5d9e603f866e Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 14 Nov 2025 00:55:48 +1000 Subject: [PATCH 0243/1145] [esp32] Make esp-idf default framework for P4 (#11884) --- esphome/components/esp32/__init__.py | 163 ++++++++++++--------------- 1 file changed, 74 insertions(+), 89 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 61511cba0c..9741dc76a1 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -381,8 +381,9 @@ PLATFORM_VERSION_LOOKUP = { } -def _check_versions(value): - value = value.copy() +def _check_versions(config): + config = config.copy() + value = config[CONF_FRAMEWORK] if value[CONF_VERSION] in PLATFORM_VERSION_LOOKUP: if CONF_SOURCE in value or CONF_PLATFORM_VERSION in value: @@ -447,7 +448,7 @@ def _check_versions(value): "If there are connectivity or build issues please remove the manual version." ) - return value + return config def _parse_platform_version(value): @@ -598,89 +599,72 @@ def _validate_idf_component(config: ConfigType) -> ConfigType: FRAMEWORK_ESP_IDF = "esp-idf" FRAMEWORK_ARDUINO = "arduino" -FRAMEWORK_SCHEMA = cv.All( - cv.Schema( - { - cv.Optional(CONF_TYPE, default=FRAMEWORK_ARDUINO): cv.one_of( - FRAMEWORK_ESP_IDF, FRAMEWORK_ARDUINO - ), - cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, - cv.Optional(CONF_RELEASE): cv.string_strict, - cv.Optional(CONF_SOURCE): cv.string_strict, - cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, - cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { - cv.string_strict: cv.string_strict - }, - cv.Optional(CONF_LOG_LEVEL, default="ERROR"): cv.one_of( - *LOG_LEVELS_IDF, upper=True - ), - cv.Optional(CONF_ADVANCED, default={}): cv.Schema( - { - cv.Optional(CONF_ASSERTION_LEVEL): cv.one_of( - *ASSERTION_LEVELS, upper=True - ), - cv.Optional(CONF_COMPILER_OPTIMIZATION, default="SIZE"): cv.one_of( - *COMPILER_OPTIMIZATIONS, upper=True - ), - cv.Optional(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES): cv.boolean, - cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean, - cv.Optional( - CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False - ): cv.boolean, - cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC): cv.boolean, - # DHCP server is needed for WiFi AP mode. When WiFi component is used, - # it will handle disabling DHCP server when AP is not configured. - # Default to false (disabled) when WiFi is not used. - cv.OnlyWithout( - CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False - ): cv.boolean, - cv.Optional( - CONF_ENABLE_LWIP_MDNS_QUERIES, default=True - ): cv.boolean, - cv.Optional( - CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False - ): cv.boolean, - cv.Optional( - CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, default=True - ): cv.boolean, - cv.Optional( - CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, default=True - ): cv.boolean, - cv.Optional( - CONF_DISABLE_LIBC_LOCKS_IN_IRAM, default=True - ): cv.boolean, - cv.Optional( - CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True - ): cv.boolean, - cv.Optional( - CONF_DISABLE_VFS_SUPPORT_SELECT, default=True - ): cv.boolean, - cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean, - cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean, - cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range( - min=8192, max=32768 - ), - } - ), - cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( - cv.All( - cv.Schema( - { - cv.Required(CONF_NAME): cv.string_strict, - cv.Optional(CONF_SOURCE): cv.git_ref, - cv.Optional(CONF_REF): cv.string, - cv.Optional(CONF_PATH): cv.string, - cv.Optional(CONF_REFRESH): cv.All( - cv.string, cv.source_refresh - ), - } - ), - _validate_idf_component, - ) - ), - } - ), - _check_versions, +FRAMEWORK_SCHEMA = cv.Schema( + { + cv.Optional(CONF_TYPE): cv.one_of(FRAMEWORK_ESP_IDF, FRAMEWORK_ARDUINO), + cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, + cv.Optional(CONF_RELEASE): cv.string_strict, + cv.Optional(CONF_SOURCE): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, + cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { + cv.string_strict: cv.string_strict + }, + cv.Optional(CONF_LOG_LEVEL, default="ERROR"): cv.one_of( + *LOG_LEVELS_IDF, upper=True + ), + cv.Optional(CONF_ADVANCED, default={}): cv.Schema( + { + cv.Optional(CONF_ASSERTION_LEVEL): cv.one_of( + *ASSERTION_LEVELS, upper=True + ), + cv.Optional(CONF_COMPILER_OPTIMIZATION, default="SIZE"): cv.one_of( + *COMPILER_OPTIMIZATIONS, upper=True + ), + cv.Optional(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES): cv.boolean, + cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean, + cv.Optional(CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False): cv.boolean, + cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC): cv.boolean, + # DHCP server is needed for WiFi AP mode. When WiFi component is used, + # it will handle disabling DHCP server when AP is not configured. + # Default to false (disabled) when WiFi is not used. + cv.OnlyWithout( + CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False + ): cv.boolean, + cv.Optional(CONF_ENABLE_LWIP_MDNS_QUERIES, default=True): cv.boolean, + cv.Optional( + CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False + ): cv.boolean, + cv.Optional( + CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, default=True + ): cv.boolean, + cv.Optional( + CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, default=True + ): cv.boolean, + cv.Optional(CONF_DISABLE_LIBC_LOCKS_IN_IRAM, default=True): cv.boolean, + cv.Optional(CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True): cv.boolean, + cv.Optional(CONF_DISABLE_VFS_SUPPORT_SELECT, default=True): cv.boolean, + cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean, + cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean, + cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range( + min=8192, max=32768 + ), + } + ), + cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( + cv.All( + cv.Schema( + { + cv.Required(CONF_NAME): cv.string_strict, + cv.Optional(CONF_SOURCE): cv.git_ref, + cv.Optional(CONF_REF): cv.string, + cv.Optional(CONF_PATH): cv.string, + cv.Optional(CONF_REFRESH): cv.All(cv.string, cv.source_refresh), + } + ), + _validate_idf_component, + ) + ), + } ) @@ -743,11 +727,11 @@ def _show_framework_migration_message(name: str, variant: str) -> None: def _set_default_framework(config): + config = config.copy() if CONF_FRAMEWORK not in config: - config = config.copy() - - variant = config[CONF_VARIANT] config[CONF_FRAMEWORK] = FRAMEWORK_SCHEMA({}) + if CONF_TYPE not in config[CONF_FRAMEWORK]: + variant = config[CONF_VARIANT] if variant in ARDUINO_ALLOWED_VARIANTS: config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO _show_framework_migration_message( @@ -787,6 +771,7 @@ CONFIG_SCHEMA = cv.All( ), _detect_variant, _set_default_framework, + _check_versions, set_core_data, cv.has_at_least_one_key(CONF_BOARD, CONF_VARIANT), ) From fe00e209fff4f9c2e3e3b4c4664a300503488e94 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 14 Nov 2025 01:52:08 +1000 Subject: [PATCH 0244/1145] [esp32] Add sdkconfig flag to make OTA work for 32MB flash (#11883) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/esp32/__init__.py | 81 +++++++++---------- tests/components/esp32/test.esp32-p4-idf.yaml | 27 +++++++ 2 files changed, 67 insertions(+), 41 deletions(-) create mode 100644 tests/components/esp32/test.esp32-p4-idf.yaml diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 9741dc76a1..0f85e585f7 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -498,6 +498,8 @@ def final_validate(config): from esphome.components.psram import DOMAIN as PSRAM_DOMAIN errs = [] + conf_fw = config[CONF_FRAMEWORK] + advanced = conf_fw[CONF_ADVANCED] full_config = fv.full_config.get() if pio_options := full_config[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS): pio_flash_size_key = "board_upload.flash_size" @@ -514,22 +516,14 @@ def final_validate(config): f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only" ) ) - if ( - config[CONF_VARIANT] != VARIANT_ESP32 - and CONF_ADVANCED in (conf_fw := config[CONF_FRAMEWORK]) - and CONF_IGNORE_EFUSE_MAC_CRC in conf_fw[CONF_ADVANCED] - ): + if config[CONF_VARIANT] != VARIANT_ESP32 and advanced[CONF_IGNORE_EFUSE_MAC_CRC]: errs.append( cv.Invalid( f"'{CONF_IGNORE_EFUSE_MAC_CRC}' is not supported on {config[CONF_VARIANT]}", path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_IGNORE_EFUSE_MAC_CRC], ) ) - if ( - config.get(CONF_FRAMEWORK, {}) - .get(CONF_ADVANCED, {}) - .get(CONF_EXECUTE_FROM_PSRAM) - ): + if advanced[CONF_EXECUTE_FROM_PSRAM]: if config[CONF_VARIANT] != VARIANT_ESP32S3: errs.append( cv.Invalid( @@ -545,6 +539,17 @@ def final_validate(config): ) ) + if ( + config[CONF_FLASH_SIZE] == "32MB" + and "ota" in full_config + and not advanced[CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES] + ): + errs.append( + cv.Invalid( + f"OTA with 32MB flash requires '{CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES}' to be set in the '{CONF_ADVANCED}' section of the esp32 configuration", + path=[CONF_FLASH_SIZE], + ) + ) if errs: raise cv.MultipleInvalid(errs) @@ -620,10 +625,12 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_COMPILER_OPTIMIZATION, default="SIZE"): cv.one_of( *COMPILER_OPTIMIZATIONS, upper=True ), - cv.Optional(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES): cv.boolean, + cv.Optional( + CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES, default=False + ): cv.boolean, cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean, cv.Optional(CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False): cv.boolean, - cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC): cv.boolean, + cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean, # DHCP server is needed for WiFi AP mode. When WiFi component is used, # it will handle disabling DHCP server when AP is not configured. # Default to false (disabled) when WiFi is not used. @@ -644,7 +651,7 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True): cv.boolean, cv.Optional(CONF_DISABLE_VFS_SUPPORT_SELECT, default=True): cv.boolean, cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean, - cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean, + cv.Optional(CONF_EXECUTE_FROM_PSRAM, default=False): cv.boolean, cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range( min=8192, max=32768 ), @@ -790,9 +797,7 @@ def _configure_lwip_max_sockets(conf: dict) -> None: from esphome.components.socket import KEY_SOCKET_CONSUMERS # Check if user manually specified CONFIG_LWIP_MAX_SOCKETS - user_max_sockets = conf.get(CONF_SDKCONFIG_OPTIONS, {}).get( - "CONFIG_LWIP_MAX_SOCKETS" - ) + user_max_sockets = conf[CONF_SDKCONFIG_OPTIONS].get("CONFIG_LWIP_MAX_SOCKETS") socket_consumers: dict[str, int] = CORE.data.get(KEY_SOCKET_CONSUMERS, {}) total_sockets = sum(socket_consumers.values()) @@ -962,23 +967,18 @@ async def to_code(config): # WiFi component handles its own optimization when AP mode is not used # When using Arduino with Ethernet, DHCP server functions must be available # for the Network library to compile, even if not actively used - if ( - CONF_ENABLE_LWIP_DHCP_SERVER in advanced - and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER] - and not ( - conf[CONF_TYPE] == FRAMEWORK_ARDUINO - and "ethernet" in CORE.loaded_integrations - ) + if advanced.get(CONF_ENABLE_LWIP_DHCP_SERVER) is False and not ( + conf[CONF_TYPE] == FRAMEWORK_ARDUINO and "ethernet" in CORE.loaded_integrations ): add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False) - if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, True): + if not advanced[CONF_ENABLE_LWIP_MDNS_QUERIES]: add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False) - if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False): + if not advanced[CONF_ENABLE_LWIP_BRIDGE_INTERFACE]: add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0) _configure_lwip_max_sockets(conf) - if advanced.get(CONF_EXECUTE_FROM_PSRAM, False): + if advanced[CONF_EXECUTE_FROM_PSRAM]: add_idf_sdkconfig_option("CONFIG_SPIRAM_FETCH_INSTRUCTIONS", True) add_idf_sdkconfig_option("CONFIG_SPIRAM_RODATA", True) @@ -989,23 +989,22 @@ async def to_code(config): # - select() on 4 sockets: ~190μs (Arduino/core locking) vs ~235μs (ESP-IDF default) # - Up to 200% slower under load when all operations queue through tcpip_thread # Enabling this makes ESP-IDF socket performance match Arduino framework. - if advanced.get(CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, True): + if advanced[CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING]: add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_CORE_LOCKING", True) - if advanced.get(CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, True): + if advanced[CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY]: add_idf_sdkconfig_option("CONFIG_LWIP_CHECK_THREAD_SAFETY", True) # Disable placing libc locks in IRAM to save RAM # This is safe for ESPHome since no IRAM ISRs (interrupts that run while cache is disabled) # use libc lock APIs. Saves approximately 1.3KB (1,356 bytes) of IRAM. - if advanced.get(CONF_DISABLE_LIBC_LOCKS_IN_IRAM, True): + if advanced[CONF_DISABLE_LIBC_LOCKS_IN_IRAM]: add_idf_sdkconfig_option("CONFIG_LIBC_LOCKS_PLACE_IN_IRAM", False) # Disable VFS support for termios (terminal I/O functions) # ESPHome doesn't use termios functions on ESP32 (only used in host UART driver). # Saves approximately 1.8KB of flash when disabled (default). add_idf_sdkconfig_option( - "CONFIG_VFS_SUPPORT_TERMIOS", - not advanced.get(CONF_DISABLE_VFS_SUPPORT_TERMIOS, True), + "CONFIG_VFS_SUPPORT_TERMIOS", not advanced[CONF_DISABLE_VFS_SUPPORT_TERMIOS] ) # Disable VFS support for select() with file descriptors @@ -1019,8 +1018,7 @@ async def to_code(config): else: # No component needs it - allow user to control (default: disabled) add_idf_sdkconfig_option( - "CONFIG_VFS_SUPPORT_SELECT", - not advanced.get(CONF_DISABLE_VFS_SUPPORT_SELECT, True), + "CONFIG_VFS_SUPPORT_SELECT", not advanced[CONF_DISABLE_VFS_SUPPORT_SELECT] ) # Disable VFS support for directory functions (opendir, readdir, mkdir, etc.) @@ -1033,8 +1031,7 @@ async def to_code(config): else: # No component needs it - allow user to control (default: disabled) add_idf_sdkconfig_option( - "CONFIG_VFS_SUPPORT_DIR", - not advanced.get(CONF_DISABLE_VFS_SUPPORT_DIR, True), + "CONFIG_VFS_SUPPORT_DIR", not advanced[CONF_DISABLE_VFS_SUPPORT_DIR] ) cg.add_platformio_option("board_build.partitions", "partitions.csv") @@ -1048,7 +1045,7 @@ async def to_code(config): add_idf_sdkconfig_option(flag, assertion_level == key) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) - compiler_optimization = advanced.get(CONF_COMPILER_OPTIMIZATION) + compiler_optimization = advanced[CONF_COMPILER_OPTIMIZATION] for key, flag in COMPILER_OPTIMIZATIONS.items(): add_idf_sdkconfig_option(flag, compiler_optimization == key) @@ -1057,18 +1054,20 @@ async def to_code(config): conf[CONF_ADVANCED][CONF_ENABLE_LWIP_ASSERT], ) - if advanced.get(CONF_IGNORE_EFUSE_MAC_CRC): + if advanced[CONF_IGNORE_EFUSE_MAC_CRC]: add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True) add_idf_sdkconfig_option("CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False) - if advanced.get(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES): + if advanced[CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES]: _LOGGER.warning( "Using experimental features in ESP-IDF may result in unexpected failures." ) add_idf_sdkconfig_option("CONFIG_IDF_EXPERIMENTAL_FEATURES", True) + if config[CONF_FLASH_SIZE] == "32MB": + add_idf_sdkconfig_option( + "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH", True + ) - cg.add_define( - "ESPHOME_LOOP_TASK_STACK_SIZE", advanced.get(CONF_LOOP_TASK_STACK_SIZE) - ) + cg.add_define("ESPHOME_LOOP_TASK_STACK_SIZE", advanced[CONF_LOOP_TASK_STACK_SIZE]) cg.add_define( "USE_ESP_IDF_VERSION_CODE", diff --git a/tests/components/esp32/test.esp32-p4-idf.yaml b/tests/components/esp32/test.esp32-p4-idf.yaml new file mode 100644 index 0000000000..a4c930f236 --- /dev/null +++ b/tests/components/esp32/test.esp32-p4-idf.yaml @@ -0,0 +1,27 @@ +esp32: + variant: esp32p4 + flash_size: 32MB + cpu_frequency: 400MHz + framework: + type: esp-idf + advanced: + enable_idf_experimental_features: yes + +ota: + platform: esphome + +wifi: + ssid: MySSID + password: password1 + +esp32_hosted: + variant: ESP32C6 + slot: 1 + active_high: true + reset_pin: GPIO15 + cmd_pin: GPIO13 + clk_pin: GPIO12 + d0_pin: GPIO11 + d1_pin: GPIO10 + d2_pin: GPIO9 + d3_pin: GPIO8 From eb54c0026dfcab03fbb841ce5fd374554fa61960 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Thu, 13 Nov 2025 19:08:06 +0100 Subject: [PATCH 0245/1145] [light] Fix missing `ColorMode::BRIGHTNESS` case in logging (#11836) --- esphome/components/light/light_call.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 8365ac77cd..b15ff84b97 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -52,8 +52,10 @@ static void log_invalid_parameter(const char *name, const LogString *message) { } static const LogString *color_mode_to_human(ColorMode color_mode) { - if (color_mode == ColorMode::UNKNOWN) - return LOG_STR("Unknown"); + if (color_mode == ColorMode::ON_OFF) + return LOG_STR("On/Off"); + if (color_mode == ColorMode::BRIGHTNESS) + return LOG_STR("Brightness"); if (color_mode == ColorMode::WHITE) return LOG_STR("White"); if (color_mode == ColorMode::COLOR_TEMPERATURE) @@ -68,7 +70,7 @@ static const LogString *color_mode_to_human(ColorMode color_mode) { return LOG_STR("RGB + cold/warm white"); if (color_mode == ColorMode::RGB_COLOR_TEMPERATURE) return LOG_STR("RGB + color temperature"); - return LOG_STR(""); + return LOG_STR("Unknown"); } // Helper to log percentage values From 7ce94c27feeade6a50082ed9f14165c056fb2a71 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:13:48 +0100 Subject: [PATCH 0246/1145] [wifi] Allow `use_psram` with Arduino (#11902) --- esphome/components/wifi/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 4dbb425e4b..11bd7798e2 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -12,7 +12,6 @@ from esphome.components.network import ( from esphome.components.psram import is_guaranteed as psram_is_guaranteed from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv -from esphome.config_validation import only_with_esp_idf from esphome.const import ( CONF_AP, CONF_BSSID, @@ -352,7 +351,7 @@ CONFIG_SCHEMA = cv.All( single=True ), cv.Optional(CONF_USE_PSRAM): cv.All( - only_with_esp_idf, cv.requires_component("psram"), cv.boolean + cv.only_on_esp32, cv.requires_component("psram"), cv.boolean ), } ), From 97c4914573b5b7444f44c526e1fb11d13cdcfe38 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:06:08 -0500 Subject: [PATCH 0247/1145] [uart] Improve error handling and validate buffer size (#11895) Co-authored-by: J. Nick Koston --- esphome/components/uart/__init__.py | 19 ++++++++++ .../uart/uart_component_esp_idf.cpp | 35 +++++++++++++++---- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index cbc11d0db0..7b0d9726b8 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -1,3 +1,4 @@ +from logging import getLogger import math import re @@ -35,6 +36,8 @@ from esphome.core import CORE, ID import esphome.final_validate as fv from esphome.yaml_util import make_data_base +_LOGGER = getLogger(__name__) + CODEOWNERS = ["@esphome/core"] uart_ns = cg.esphome_ns.namespace("uart") UARTComponent = uart_ns.class_("UARTComponent") @@ -130,6 +133,21 @@ def validate_host_config(config): return config +def validate_rx_buffer_size(config): + if CORE.is_esp32: + # ESP32 UART hardware FIFO is 128 bytes (LP UART is 16 bytes, but we use 128 as safe minimum) + # rx_buffer_size must be greater than the hardware FIFO length + min_buffer_size = 128 + if config[CONF_RX_BUFFER_SIZE] <= min_buffer_size: + _LOGGER.warning( + "UART rx_buffer_size (%d bytes) is too small and must be greater than the hardware " + "FIFO size (%d bytes). The buffer size will be automatically adjusted at runtime.", + config[CONF_RX_BUFFER_SIZE], + min_buffer_size, + ) + return config + + def _uart_declare_type(value): if CORE.is_esp8266: return cv.declare_id(ESP8266UartComponent)(value) @@ -247,6 +265,7 @@ CONFIG_SCHEMA = cv.All( ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN, CONF_PORT), validate_host_config, + validate_rx_buffer_size, ) diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 73813d2d5b..70a13c9e37 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -91,6 +91,16 @@ void IDFUARTComponent::setup() { this->uart_num_ = static_cast(next_uart_num++); this->lock_ = xSemaphoreCreateMutex(); +#if (SOC_UART_LP_NUM >= 1) + size_t fifo_len = ((this->uart_num_ < SOC_UART_HP_NUM) ? SOC_UART_FIFO_LEN : SOC_LP_UART_FIFO_LEN); +#else + size_t fifo_len = SOC_UART_FIFO_LEN; +#endif + if (this->rx_buffer_size_ <= fifo_len) { + ESP_LOGW(TAG, "rx_buffer_size is too small, must be greater than %zu", fifo_len); + this->rx_buffer_size_ = fifo_len * 2; + } + xSemaphoreTake(this->lock_, portMAX_DELAY); this->load_settings(false); @@ -237,8 +247,12 @@ void IDFUARTComponent::set_rx_timeout(size_t rx_timeout) { void IDFUARTComponent::write_array(const uint8_t *data, size_t len) { xSemaphoreTake(this->lock_, portMAX_DELAY); - uart_write_bytes(this->uart_num_, data, len); + int32_t write_len = uart_write_bytes(this->uart_num_, data, len); xSemaphoreGive(this->lock_); + if (write_len != (int32_t) len) { + ESP_LOGW(TAG, "uart_write_bytes failed: %d != %zu", write_len, len); + this->mark_failed(); + } #ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { this->debug_callback_.call(UART_DIRECTION_TX, data[i]); @@ -267,6 +281,7 @@ bool IDFUARTComponent::peek_byte(uint8_t *data) { bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { size_t length_to_read = len; + int32_t read_len = 0; if (!this->check_read_timeout_(len)) return false; xSemaphoreTake(this->lock_, portMAX_DELAY); @@ -277,25 +292,31 @@ bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { this->has_peek_ = false; } if (length_to_read > 0) - uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_PERIOD_MS); + read_len = uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_PERIOD_MS); xSemaphoreGive(this->lock_); #ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { this->debug_callback_.call(UART_DIRECTION_RX, data[i]); } #endif - return true; + return read_len == (int32_t) length_to_read; } int IDFUARTComponent::available() { - size_t available; + size_t available = 0; + esp_err_t err; xSemaphoreTake(this->lock_, portMAX_DELAY); - uart_get_buffered_data_len(this->uart_num_, &available); - if (this->has_peek_) - available++; + err = uart_get_buffered_data_len(this->uart_num_, &available); xSemaphoreGive(this->lock_); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_get_buffered_data_len failed: %s", esp_err_to_name(err)); + this->mark_failed(); + } + if (this->has_peek_) { + available++; + } return available; } From 6440b5fbf5d05e7b572d9272c8ea8865b59ed513 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 15 Nov 2025 22:03:43 -0600 Subject: [PATCH 0248/1145] [ld2412] Fix stuck targets by adding timeout filter (#11919) --- esphome/components/ld2412/sensor.py | 76 ++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/esphome/components/ld2412/sensor.py b/esphome/components/ld2412/sensor.py index abb823faad..0bfbd9bf1d 100644 --- a/esphome/components/ld2412/sensor.py +++ b/esphome/components/ld2412/sensor.py @@ -31,36 +31,84 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component), cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_LIGHT): sensor.sensor_schema( device_class=DEVICE_CLASS_ILLUMINANCE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_LIGHTBULB, unit_of_measurement=UNIT_EMPTY, # No standard unit for this light sensor ), cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema( - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_MOTION_SENSOR, unit_of_measurement=UNIT_PERCENT, ), cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_FLASH, unit_of_measurement=UNIT_PERCENT, ), @@ -74,7 +122,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend( cv.Optional(CONF_MOVE_ENERGY): sensor.sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, filters=[ - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)} + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, ], icon=ICON_MOTION_SENSOR, unit_of_measurement=UNIT_PERCENT, @@ -82,7 +136,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend( cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, filters=[ - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)} + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, ], icon=ICON_FLASH, unit_of_measurement=UNIT_PERCENT, From d559f9f52e26cc779528fdb5c015cb73560536f1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 15 Nov 2025 22:04:25 -0600 Subject: [PATCH 0249/1145] [ld2410] Add timeout filter to prevent stuck targets (#11920) --- esphome/components/ld2410/sensor.py | 76 ++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/esphome/components/ld2410/sensor.py b/esphome/components/ld2410/sensor.py index fca2b2ceca..3bd34963bc 100644 --- a/esphome/components/ld2410/sensor.py +++ b/esphome/components/ld2410/sensor.py @@ -31,35 +31,83 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema( - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_MOTION_SENSOR, unit_of_measurement=UNIT_PERCENT, ), cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_FLASH, unit_of_measurement=UNIT_PERCENT, ), cv.Optional(CONF_LIGHT): sensor.sensor_schema( device_class=DEVICE_CLASS_ILLUMINANCE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_LIGHTBULB, ), cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}], + filters=[ + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, + ], icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), @@ -73,7 +121,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend( cv.Optional(CONF_MOVE_ENERGY): sensor.sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, filters=[ - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)} + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, ], icon=ICON_MOTION_SENSOR, unit_of_measurement=UNIT_PERCENT, @@ -81,7 +135,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend( cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, filters=[ - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)} + { + "timeout": { + "timeout": cv.TimePeriod(milliseconds=1000), + "value": "last", + } + }, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, ], icon=ICON_FLASH, unit_of_measurement=UNIT_PERCENT, From 36868ee7b173628e838951d3fb3c899e66b74866 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 15 Nov 2025 22:20:57 -0600 Subject: [PATCH 0250/1145] [scheduler] Fix timing breakage after 49 days of uptime on ESP8266/RP2040 (#11924) --- esphome/core/scheduler.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index d285af2d0e..d2e0f0dab4 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -609,13 +609,12 @@ uint64_t Scheduler::millis_64_(uint32_t now) { if (now < last && (last - now) > HALF_MAX_UINT32) { this->millis_major_++; major++; + this->last_millis_ = now; #ifdef ESPHOME_DEBUG_SCHEDULER ESP_LOGD(TAG, "Detected true 32-bit rollover at %" PRIu32 "ms (was %" PRIu32 ")", now, last); #endif /* ESPHOME_DEBUG_SCHEDULER */ - } - - // Only update if time moved forward - if (now > last) { + } else if (now > last) { + // Only update if time moved forward this->last_millis_ = now; } From f19296ac7f59c4b2610b127cd1663e11648cf645 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 16 Nov 2025 07:35:31 -0600 Subject: [PATCH 0251/1145] [analyze-memory] Show all core symbols > 100 B instead of top 15 (#11909) --- esphome/analyze_memory/cli.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/esphome/analyze_memory/cli.py b/esphome/analyze_memory/cli.py index 718f42330d..44ade221f8 100644 --- a/esphome/analyze_memory/cli.py +++ b/esphome/analyze_memory/cli.py @@ -15,6 +15,11 @@ from . import ( class MemoryAnalyzerCLI(MemoryAnalyzer): """Memory analyzer with CLI-specific report generation.""" + # Symbol size threshold for detailed analysis + SYMBOL_SIZE_THRESHOLD: int = ( + 100 # Show symbols larger than this in detailed analysis + ) + # Column width constants COL_COMPONENT: int = 29 COL_FLASH_TEXT: int = 14 @@ -191,14 +196,21 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): f"{len(symbols):>{self.COL_CORE_COUNT}} | {percentage:>{self.COL_CORE_PERCENT - 1}.1f}%" ) - # Top 15 largest core symbols + # All core symbols above threshold lines.append("") - lines.append(f"Top 15 Largest {_COMPONENT_CORE} Symbols:") sorted_core_symbols = sorted( self._esphome_core_symbols, key=lambda x: x[2], reverse=True ) + large_core_symbols = [ + (symbol, demangled, size) + for symbol, demangled, size in sorted_core_symbols + if size > self.SYMBOL_SIZE_THRESHOLD + ] - for i, (symbol, demangled, size) in enumerate(sorted_core_symbols[:15]): + lines.append( + f"{_COMPONENT_CORE} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_core_symbols)} symbols):" + ) + for i, (symbol, demangled, size) in enumerate(large_core_symbols): lines.append(f"{i + 1}. {demangled} ({size:,} B)") lines.append("=" * self.TABLE_WIDTH) @@ -268,13 +280,15 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): lines.append(f"Total size: {comp_mem.flash_total:,} B") lines.append("") - # Show all symbols > 100 bytes for better visibility + # Show all symbols above threshold for better visibility large_symbols = [ - (sym, dem, size) for sym, dem, size in sorted_symbols if size > 100 + (sym, dem, size) + for sym, dem, size in sorted_symbols + if size > self.SYMBOL_SIZE_THRESHOLD ] lines.append( - f"{comp_name} Symbols > 100 B ({len(large_symbols)} symbols):" + f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_symbols)} symbols):" ) for i, (symbol, demangled, size) in enumerate(large_symbols): lines.append(f"{i + 1}. {demangled} ({size:,} B)") From 91514894817fa318186b29dec48af397f9222ca9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 16 Nov 2025 18:38:38 -0600 Subject: [PATCH 0252/1145] [sntp] Merge multiple instances to fix crash and undefined behavior (#11904) --- esphome/components/sntp/time.py | 68 +++++ tests/component_tests/sntp/__init__.py | 1 + .../sntp/config/sntp_test.yaml | 22 ++ tests/component_tests/sntp/test_init.py | 238 ++++++++++++++++++ 4 files changed, 329 insertions(+) create mode 100644 tests/component_tests/sntp/__init__.py create mode 100644 tests/component_tests/sntp/config/sntp_test.yaml create mode 100644 tests/component_tests/sntp/test_init.py diff --git a/esphome/components/sntp/time.py b/esphome/components/sntp/time.py index d27fc9991d..69a2436d3d 100644 --- a/esphome/components/sntp/time.py +++ b/esphome/components/sntp/time.py @@ -1,9 +1,14 @@ +import logging + import esphome.codegen as cg from esphome.components import time as time_ +from esphome.config_helpers import merge_config import esphome.config_validation as cv from esphome.const import ( CONF_ID, + CONF_PLATFORM, CONF_SERVERS, + CONF_TIME, PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, @@ -12,13 +17,74 @@ from esphome.const import ( PLATFORM_RTL87XX, ) from esphome.core import CORE +import esphome.final_validate as fv +from esphome.types import ConfigType + +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ["network"] + +CONF_SNTP = "sntp" + sntp_ns = cg.esphome_ns.namespace("sntp") SNTPComponent = sntp_ns.class_("SNTPComponent", time_.RealTimeClock) DEFAULT_SERVERS = ["0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org"] + +def _sntp_final_validate(config: ConfigType) -> None: + """Merge multiple SNTP instances into one, similar to OTA merging behavior.""" + full_conf = fv.full_config.get() + time_confs = full_conf.get(CONF_TIME, []) + + sntp_configs: list[ConfigType] = [] + other_time_configs: list[ConfigType] = [] + + for time_conf in time_confs: + if time_conf.get(CONF_PLATFORM) == CONF_SNTP: + sntp_configs.append(time_conf) + else: + other_time_configs.append(time_conf) + + if len(sntp_configs) <= 1: + return + + # Merge all SNTP configs into the first one + merged = sntp_configs[0] + for sntp_conf in sntp_configs[1:]: + # Validate that IDs are consistent if manually specified + if merged[CONF_ID].is_manual and sntp_conf[CONF_ID].is_manual: + raise cv.Invalid( + f"Found multiple SNTP configurations but {CONF_ID} is inconsistent" + ) + merged = merge_config(merged, sntp_conf) + + # Deduplicate servers while preserving order + servers = merged[CONF_SERVERS] + unique_servers = list(dict.fromkeys(servers)) + + # Warn if we're dropping servers due to 3-server limit + if len(unique_servers) > 3: + dropped = unique_servers[3:] + unique_servers = unique_servers[:3] + _LOGGER.warning( + "SNTP supports maximum 3 servers. Dropped excess server(s): %s", + dropped, + ) + + merged[CONF_SERVERS] = unique_servers + + _LOGGER.warning( + "Found and merged %d SNTP time configurations into one instance", + len(sntp_configs), + ) + + # Replace time configs with merged SNTP + other time platforms + other_time_configs.append(merged) + full_conf[CONF_TIME] = other_time_configs + fv.full_config.set(full_conf) + + CONFIG_SCHEMA = cv.All( time_.TIME_SCHEMA.extend( { @@ -40,6 +106,8 @@ CONFIG_SCHEMA = cv.All( ), ) +FINAL_VALIDATE_SCHEMA = _sntp_final_validate + async def to_code(config): servers = config[CONF_SERVERS] diff --git a/tests/component_tests/sntp/__init__.py b/tests/component_tests/sntp/__init__.py new file mode 100644 index 0000000000..7d323a4980 --- /dev/null +++ b/tests/component_tests/sntp/__init__.py @@ -0,0 +1 @@ +"""Tests for SNTP component.""" diff --git a/tests/component_tests/sntp/config/sntp_test.yaml b/tests/component_tests/sntp/config/sntp_test.yaml new file mode 100644 index 0000000000..3942c9606b --- /dev/null +++ b/tests/component_tests/sntp/config/sntp_test.yaml @@ -0,0 +1,22 @@ +esphome: + name: sntp-test + +esp32: + board: esp32dev + framework: + type: esp-idf + +wifi: + ssid: "testssid" + password: "testpassword" + +# Test multiple SNTP instances that should be merged +time: + - platform: sntp + servers: + - 192.168.1.1 + - pool.ntp.org + - platform: sntp + servers: + - pool.ntp.org + - 192.168.1.2 diff --git a/tests/component_tests/sntp/test_init.py b/tests/component_tests/sntp/test_init.py new file mode 100644 index 0000000000..9197ff55d0 --- /dev/null +++ b/tests/component_tests/sntp/test_init.py @@ -0,0 +1,238 @@ +"""Tests for SNTP time configuration validation.""" + +from __future__ import annotations + +import logging +from typing import Any + +import pytest + +from esphome import config_validation as cv +from esphome.components.sntp.time import CONF_SNTP, _sntp_final_validate +from esphome.const import CONF_ID, CONF_PLATFORM, CONF_SERVERS, CONF_TIME +from esphome.core import ID +import esphome.final_validate as fv + + +@pytest.mark.parametrize( + ("time_configs", "expected_count", "expected_servers", "warning_messages"), + [ + pytest.param( + [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time", is_manual=False), + CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"], + } + ], + 1, + ["192.168.1.1", "pool.ntp.org"], + [], + id="single_instance_no_merge", + ), + pytest.param( + [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_1", is_manual=False), + CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"], + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_2", is_manual=False), + CONF_SERVERS: ["192.168.1.2"], + }, + ], + 1, + ["192.168.1.1", "pool.ntp.org", "192.168.1.2"], + ["Found and merged 2 SNTP time configurations into one instance"], + id="two_instances_merged", + ), + pytest.param( + [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_1", is_manual=False), + CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"], + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_2", is_manual=False), + CONF_SERVERS: ["pool.ntp.org", "192.168.1.2"], + }, + ], + 1, + ["192.168.1.1", "pool.ntp.org", "192.168.1.2"], + ["Found and merged 2 SNTP time configurations into one instance"], + id="deduplication_preserves_order", + ), + pytest.param( + [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_1", is_manual=False), + CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"], + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_2", is_manual=False), + CONF_SERVERS: ["192.168.1.2", "pool2.ntp.org"], + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_3", is_manual=False), + CONF_SERVERS: ["pool3.ntp.org"], + }, + ], + 1, + ["192.168.1.1", "pool.ntp.org", "192.168.1.2"], + [ + "SNTP supports maximum 3 servers. Dropped excess server(s): ['pool2.ntp.org', 'pool3.ntp.org']", + "Found and merged 3 SNTP time configurations into one instance", + ], + id="three_instances_drops_excess_servers", + ), + pytest.param( + [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_1", is_manual=False), + CONF_SERVERS: [ + "192.168.1.1", + "pool.ntp.org", + "pool.ntp.org", + "192.168.1.1", + ], + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_2", is_manual=False), + CONF_SERVERS: ["pool.ntp.org", "192.168.1.2"], + }, + ], + 1, + ["192.168.1.1", "pool.ntp.org", "192.168.1.2"], + ["Found and merged 2 SNTP time configurations into one instance"], + id="deduplication_multiple_duplicates", + ), + ], +) +def test_sntp_instance_merging( + time_configs: list[dict[str, Any]], + expected_count: int, + expected_servers: list[str], + warning_messages: list[str], + caplog: pytest.LogCaptureFixture, +) -> None: + """Test SNTP instance merging behavior.""" + # Create a mock full config with time configs + full_conf = {CONF_TIME: time_configs.copy()} + + # Set the context var + token = fv.full_config.set(full_conf) + try: + with caplog.at_level(logging.WARNING): + _sntp_final_validate({}) + + # Get the updated config + updated_conf = fv.full_config.get() + + # Check if merging occurred + if len(time_configs) > 1: + # Verify only one SNTP instance remains + sntp_instances = [ + tc + for tc in updated_conf[CONF_TIME] + if tc.get(CONF_PLATFORM) == CONF_SNTP + ] + assert len(sntp_instances) == expected_count + + # Verify server list + assert sntp_instances[0][CONF_SERVERS] == expected_servers + + # Verify warnings + for expected_msg in warning_messages: + assert any( + expected_msg in record.message for record in caplog.records + ), f"Expected warning message '{expected_msg}' not found in log" + else: + # Single instance should not trigger merging or warnings + assert len(caplog.records) == 0 + # Config should be unchanged + assert updated_conf[CONF_TIME] == time_configs + finally: + fv.full_config.reset(token) + + +def test_sntp_inconsistent_manual_ids() -> None: + """Test that inconsistent manual IDs raise an error.""" + # Create configs with manual IDs that are inconsistent + time_configs = [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_1", is_manual=True), + CONF_SERVERS: ["192.168.1.1"], + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_2", is_manual=True), + CONF_SERVERS: ["192.168.1.2"], + }, + ] + + full_conf = {CONF_TIME: time_configs} + + token = fv.full_config.set(full_conf) + try: + with pytest.raises( + cv.Invalid, + match="Found multiple SNTP configurations but id is inconsistent", + ): + _sntp_final_validate({}) + finally: + fv.full_config.reset(token) + + +def test_sntp_with_other_time_platforms(caplog: pytest.LogCaptureFixture) -> None: + """Test that SNTP merging doesn't affect other time platforms.""" + time_configs = [ + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_1", is_manual=False), + CONF_SERVERS: ["192.168.1.1"], + }, + { + CONF_PLATFORM: "homeassistant", + CONF_ID: ID("homeassistant_time", is_manual=False), + }, + { + CONF_PLATFORM: CONF_SNTP, + CONF_ID: ID("sntp_time_2", is_manual=False), + CONF_SERVERS: ["192.168.1.2"], + }, + ] + + full_conf = {CONF_TIME: time_configs.copy()} + + token = fv.full_config.set(full_conf) + try: + with caplog.at_level(logging.WARNING): + _sntp_final_validate({}) + + updated_conf = fv.full_config.get() + + # Should have 2 time platforms: 1 merged SNTP + 1 homeassistant + assert len(updated_conf[CONF_TIME]) == 2 + + # Find the platforms + platforms = {tc[CONF_PLATFORM] for tc in updated_conf[CONF_TIME]} + assert platforms == {CONF_SNTP, "homeassistant"} + + # Verify SNTP was merged + sntp_instances = [ + tc for tc in updated_conf[CONF_TIME] if tc[CONF_PLATFORM] == CONF_SNTP + ] + assert len(sntp_instances) == 1 + assert sntp_instances[0][CONF_SERVERS] == ["192.168.1.1", "192.168.1.2"] + finally: + fv.full_config.reset(token) From 3fd58f1a917f3d69af101a544f2688d70ec69228 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 16 Nov 2025 18:38:52 -0600 Subject: [PATCH 0253/1145] [web_server.ota] Merge multiple instances to prevent undefined behavior (#11905) --- esphome/components/web_server/ota/__init__.py | 58 ++++++- .../ota/test_web_server_ota.py | 153 ++++++++++++++++++ 2 files changed, 210 insertions(+), 1 deletion(-) diff --git a/esphome/components/web_server/ota/__init__.py b/esphome/components/web_server/ota/__init__.py index 4a98db8877..260e6aea6d 100644 --- a/esphome/components/web_server/ota/__init__.py +++ b/esphome/components/web_server/ota/__init__.py @@ -1,10 +1,17 @@ +import logging + import esphome.codegen as cg from esphome.components.esp32 import add_idf_component from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code +from esphome.config_helpers import merge_config import esphome.config_validation as cv -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_OTA, CONF_PLATFORM, CONF_WEB_SERVER from esphome.core import CORE, coroutine_with_priority from esphome.coroutine import CoroPriority +import esphome.final_validate as fv +from esphome.types import ConfigType + +_LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network", "web_server_base"] @@ -12,6 +19,53 @@ DEPENDENCIES = ["network", "web_server_base"] web_server_ns = cg.esphome_ns.namespace("web_server") WebServerOTAComponent = web_server_ns.class_("WebServerOTAComponent", OTAComponent) + +def _web_server_ota_final_validate(config: ConfigType) -> None: + """Merge multiple web_server OTA instances into one. + + Multiple web_server OTA instances register duplicate HTTP handlers for /update, + causing undefined behavior. Merge them into a single instance. + """ + full_conf = fv.full_config.get() + ota_confs = full_conf.get(CONF_OTA, []) + + web_server_ota_configs: list[ConfigType] = [] + other_ota_configs: list[ConfigType] = [] + + for ota_conf in ota_confs: + if ota_conf.get(CONF_PLATFORM) == CONF_WEB_SERVER: + web_server_ota_configs.append(ota_conf) + else: + other_ota_configs.append(ota_conf) + + if len(web_server_ota_configs) <= 1: + return + + # Merge all web_server OTA configs into the first one + merged = web_server_ota_configs[0] + for ota_conf in web_server_ota_configs[1:]: + # Validate that IDs are consistent if manually specified + if ( + merged[CONF_ID].is_manual + and ota_conf[CONF_ID].is_manual + and merged[CONF_ID] != ota_conf[CONF_ID] + ): + raise cv.Invalid( + f"Found multiple web_server OTA configurations but {CONF_ID} is inconsistent" + ) + merged = merge_config(merged, ota_conf) + + _LOGGER.warning( + "Found and merged %d web_server OTA configurations into one instance", + len(web_server_ota_configs), + ) + + # Replace OTA configs with merged web_server + other OTA platforms + other_ota_configs.append(merged) + full_conf[CONF_OTA] = other_ota_configs + fv.full_config.set(full_conf) + + CONFIG_SCHEMA = ( cv.Schema( { @@ -22,6 +76,8 @@ CONFIG_SCHEMA = ( .extend(cv.COMPONENT_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = _web_server_ota_final_validate + @coroutine_with_priority(CoroPriority.WEB_SERVER_OTA) async def to_code(config): diff --git a/tests/component_tests/ota/test_web_server_ota.py b/tests/component_tests/ota/test_web_server_ota.py index 0d8ff6f134..794eaac9be 100644 --- a/tests/component_tests/ota/test_web_server_ota.py +++ b/tests/component_tests/ota/test_web_server_ota.py @@ -1,6 +1,18 @@ """Tests for the web_server OTA platform.""" +from __future__ import annotations + from collections.abc import Callable +import logging +from typing import Any + +import pytest + +from esphome import config_validation as cv +from esphome.components.web_server.ota import _web_server_ota_final_validate +from esphome.const import CONF_ID, CONF_OTA, CONF_PLATFORM, CONF_WEB_SERVER +from esphome.core import ID +import esphome.final_validate as fv def test_web_server_ota_generated(generate_main: Callable[[str], str]) -> None: @@ -100,3 +112,144 @@ def test_web_server_ota_esp8266(generate_main: Callable[[str], str]) -> None: # Check web server OTA component is present assert "WebServerOTAComponent" in main_cpp assert "web_server::WebServerOTAComponent" in main_cpp + + +@pytest.mark.parametrize( + ("ota_configs", "expected_count", "warning_expected"), + [ + pytest.param( + [ + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web", is_manual=False), + } + ], + 1, + False, + id="single_instance_no_merge", + ), + pytest.param( + [ + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web_1", is_manual=False), + }, + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web_2", is_manual=False), + }, + ], + 1, + True, + id="two_instances_merged", + ), + pytest.param( + [ + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web_1", is_manual=False), + }, + { + CONF_PLATFORM: "esphome", + CONF_ID: ID("ota_esphome", is_manual=False), + }, + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web_2", is_manual=False), + }, + ], + 2, + True, + id="mixed_platforms_web_server_merged", + ), + ], +) +def test_web_server_ota_instance_merging( + ota_configs: list[dict[str, Any]], + expected_count: int, + warning_expected: bool, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test web_server OTA instance merging behavior.""" + full_conf = {CONF_OTA: ota_configs.copy()} + + token = fv.full_config.set(full_conf) + try: + with caplog.at_level(logging.WARNING): + _web_server_ota_final_validate({}) + + updated_conf = fv.full_config.get() + + # Verify total number of OTA platforms + assert len(updated_conf[CONF_OTA]) == expected_count + + # Verify warning + if warning_expected: + assert any( + "Found and merged" in record.message + and "web_server OTA" in record.message + for record in caplog.records + ), "Expected merge warning not found in log" + else: + assert len(caplog.records) == 0, "Unexpected warnings logged" + finally: + fv.full_config.reset(token) + + +def test_web_server_ota_consistent_manual_ids( + caplog: pytest.LogCaptureFixture, +) -> None: + """Test that consistent manual IDs can be merged successfully.""" + ota_configs = [ + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web", is_manual=True), + }, + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web", is_manual=True), + }, + ] + + full_conf = {CONF_OTA: ota_configs} + + token = fv.full_config.set(full_conf) + try: + with caplog.at_level(logging.WARNING): + _web_server_ota_final_validate({}) + + updated_conf = fv.full_config.get() + assert len(updated_conf[CONF_OTA]) == 1 + assert updated_conf[CONF_OTA][0][CONF_ID].id == "ota_web" + assert any( + "Found and merged" in record.message and "web_server OTA" in record.message + for record in caplog.records + ) + finally: + fv.full_config.reset(token) + + +def test_web_server_ota_inconsistent_manual_ids() -> None: + """Test that inconsistent manual IDs raise an error.""" + ota_configs = [ + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web_1", is_manual=True), + }, + { + CONF_PLATFORM: CONF_WEB_SERVER, + CONF_ID: ID("ota_web_2", is_manual=True), + }, + ] + + full_conf = {CONF_OTA: ota_configs} + + token = fv.full_config.set(full_conf) + try: + with pytest.raises( + cv.Invalid, + match="Found multiple web_server OTA configurations but id is inconsistent", + ): + _web_server_ota_final_validate({}) + finally: + fv.full_config.reset(token) From 9e02e3191798010befd85006f17ebebc4a03b360 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 16 Nov 2025 18:39:01 -0600 Subject: [PATCH 0254/1145] [web_server_idf] Fix lwIP assertion crash by shutting down sockets on connection close (#11937) --- .../components/web_server_idf/web_server_idf.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 0dab5e7e8c..ce91569de2 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -489,10 +489,18 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest * void AsyncEventSourceResponse::destroy(void *ptr) { auto *rsp = static_cast(ptr); - ESP_LOGD(TAG, "Event source connection closed (fd: %d)", rsp->fd_.load()); - // Mark as dead by setting fd to 0 - will be cleaned up in the main loop - rsp->fd_.store(0); - // Note: We don't delete or remove from set here to avoid race conditions + int fd = rsp->fd_.exchange(0); // Atomically get and clear fd + + if (fd > 0) { + ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd); + // Immediately shut down the socket to prevent lwIP from delivering more data + // This prevents "recv_tcp: recv for wrong pcb!" assertions when the TCP stack + // tries to deliver queued data after the session is marked as dead + // See: https://github.com/esphome/esphome/issues/11936 + shutdown(fd, SHUT_RDWR); + // Note: We don't close() the socket - httpd owns it and will close it + } + // Session will be cleaned up in the main loop to avoid race conditions } // helper for allowing only unique entries in the queue From 6c6b03bda05a32519b0690e11b12706f1e2df24d Mon Sep 17 00:00:00 2001 From: Anton Sergunov Date: Mon, 17 Nov 2025 07:25:00 +0600 Subject: [PATCH 0255/1145] [uart] Setup uart pins only if flags are set (#11914) Co-authored-by: J. Nick Koston --- .../components/uart/uart_component_esp8266.cpp | 18 +++++++++++++----- .../components/uart/uart_component_esp_idf.cpp | 18 +++++++++++++----- .../uart/uart_component_libretiny.cpp | 2 +- .../components/uart/uart_component_rp2040.cpp | 18 +++++++++++++----- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 7a453dbb50..c84a877ef4 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -56,11 +56,19 @@ uint32_t ESP8266UartComponent::get_config() { } void ESP8266UartComponent::setup() { - if (this->rx_pin_) { - this->rx_pin_->setup(); - } - if (this->tx_pin_ && this->rx_pin_ != this->tx_pin_) { - this->tx_pin_->setup(); + auto setup_pin_if_needed = [](InternalGPIOPin *pin) { + if (!pin) { + return; + } + const auto mask = gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN; + if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) { + pin->setup(); + } + }; + + setup_pin_if_needed(this->rx_pin_); + if (this->rx_pin_ != this->tx_pin_) { + setup_pin_if_needed(this->tx_pin_); } // Use Arduino HardwareSerial UARTs if all used pins match the ones diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 70a13c9e37..61ca8c1c0c 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -133,11 +133,19 @@ void IDFUARTComponent::load_settings(bool dump_config) { return; } - if (this->rx_pin_) { - this->rx_pin_->setup(); - } - if (this->tx_pin_ && this->rx_pin_ != this->tx_pin_) { - this->tx_pin_->setup(); + auto setup_pin_if_needed = [](InternalGPIOPin *pin) { + if (!pin) { + return; + } + const auto mask = gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN; + if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) { + pin->setup(); + } + }; + + setup_pin_if_needed(this->rx_pin_); + if (this->rx_pin_ != this->tx_pin_) { + setup_pin_if_needed(this->tx_pin_); } int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; diff --git a/esphome/components/uart/uart_component_libretiny.cpp b/esphome/components/uart/uart_component_libretiny.cpp index 8d1d28fce4..1e408b169b 100644 --- a/esphome/components/uart/uart_component_libretiny.cpp +++ b/esphome/components/uart/uart_component_libretiny.cpp @@ -53,7 +53,7 @@ void LibreTinyUARTComponent::setup() { auto shouldFallbackToSoftwareSerial = [&]() -> bool { auto hasFlags = [](InternalGPIOPin *pin, const gpio::Flags mask) -> bool { - return pin && pin->get_flags() & mask != gpio::Flags::FLAG_NONE; + return pin && (pin->get_flags() & mask) != gpio::Flags::FLAG_NONE; }; if (hasFlags(this->tx_pin_, gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN) || hasFlags(this->rx_pin_, gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN)) { diff --git a/esphome/components/uart/uart_component_rp2040.cpp b/esphome/components/uart/uart_component_rp2040.cpp index c78691653d..cd3905b5c1 100644 --- a/esphome/components/uart/uart_component_rp2040.cpp +++ b/esphome/components/uart/uart_component_rp2040.cpp @@ -52,11 +52,19 @@ uint16_t RP2040UartComponent::get_config() { } void RP2040UartComponent::setup() { - if (this->rx_pin_) { - this->rx_pin_->setup(); - } - if (this->tx_pin_ && this->rx_pin_ != this->tx_pin_) { - this->tx_pin_->setup(); + auto setup_pin_if_needed = [](InternalGPIOPin *pin) { + if (!pin) { + return; + } + const auto mask = gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN; + if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) { + pin->setup(); + } + }; + + setup_pin_if_needed(this->rx_pin_); + if (this->rx_pin_ != this->tx_pin_) { + setup_pin_if_needed(this->tx_pin_); } uint16_t config = get_config(); From a38c4e0c6e4c4e4bf960db6f1e75f74afdfcb574 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:32:09 +1300 Subject: [PATCH 0256/1145] Bump version to 2025.11.0b3 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 8766b8f00c..04046d9ce6 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.11.0b2 +PROJECT_NUMBER = 2025.11.0b3 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 8c6020a868..2ca0018fdb 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.11.0b2" +__version__ = "2025.11.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 10bdb47eae14bd3fd1cd761737b0633cd9c9894b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 16 Nov 2025 20:37:06 -0600 Subject: [PATCH 0257/1145] [cover] Modernize to C++17 nested namespaces (#11935) --- esphome/components/cover/automation.h | 6 ++---- esphome/components/cover/cover.cpp | 6 ++---- esphome/components/cover/cover.h | 6 ++---- esphome/components/cover/cover_traits.h | 6 ++---- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 752e0398c1..c0345a7cc6 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -4,8 +4,7 @@ #include "esphome/core/automation.h" #include "cover.h" -namespace esphome { -namespace cover { +namespace esphome::cover { template class OpenAction : public Action { public: @@ -131,5 +130,4 @@ class CoverClosedTrigger : public Trigger<> { } }; -} // namespace cover -} // namespace esphome +} // namespace esphome::cover diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 3062dba28a..8f735982f1 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -6,8 +6,7 @@ #include "esphome/core/log.h" -namespace esphome { -namespace cover { +namespace esphome::cover { static const char *const TAG = "cover"; @@ -212,5 +211,4 @@ void CoverRestoreState::apply(Cover *cover) { cover->publish_state(); } -} // namespace cover -} // namespace esphome +} // namespace esphome::cover diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index d5db6cfb4f..6c69c05e71 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -7,8 +7,7 @@ #include "cover_traits.h" -namespace esphome { -namespace cover { +namespace esphome::cover { const extern float COVER_OPEN; const extern float COVER_CLOSED; @@ -157,5 +156,4 @@ class Cover : public EntityBase, public EntityBase_DeviceClass { ESPPreferenceObject rtc_; }; -} // namespace cover -} // namespace esphome +} // namespace esphome::cover diff --git a/esphome/components/cover/cover_traits.h b/esphome/components/cover/cover_traits.h index 79001c3b03..723516318b 100644 --- a/esphome/components/cover/cover_traits.h +++ b/esphome/components/cover/cover_traits.h @@ -1,7 +1,6 @@ #pragma once -namespace esphome { -namespace cover { +namespace esphome::cover { class CoverTraits { public: @@ -26,5 +25,4 @@ class CoverTraits { bool supports_stop_{false}; }; -} // namespace cover -} // namespace esphome +} // namespace esphome::cover From 9e1f8d83f884b8fb9c168c1ea3909ef3fc365c21 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Mon, 17 Nov 2025 08:03:11 +0100 Subject: [PATCH 0258/1145] [config] Support !remove and !extend with LVGL-style configs (#11534) --- esphome/config.py | 59 +++++++++++-------- .../05-extend-remove.approved.yaml | 24 ++++++++ .../substitutions/05-extend-remove.input.yaml | 44 ++++++++++++++ 3 files changed, 104 insertions(+), 23 deletions(-) diff --git a/esphome/config.py b/esphome/config.py index e508ca585b..4c8019de75 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -338,21 +338,44 @@ def check_replaceme(value): ) -def _build_list_index(lst): +def _get_item_id(item: Any) -> str | Extend | Remove | None: + """Attempts to get a list item's ID""" + if not isinstance(item, dict): + return None # not a dict, can't have ID + # 1.- Check regular case: + # - id: my_id + item_id = item.get(CONF_ID) + if item_id is None and len(item) == 1: + # 2.- Check single-key dict case: + # - obj: + # id: my_id + item = next(iter(item.values())) + if isinstance(item, dict): + item_id = item.get(CONF_ID) + if isinstance(item_id, Extend): + # Remove instances of Extend so they don't overwrite the original item when merging: + del item[CONF_ID] + return item_id + + +def _build_list_index( + lst: list[Any], +) -> tuple[ + OrderedDict[str | Extend | Remove, Any], list[tuple[int, str, Any]], set[str] +]: index = OrderedDict() extensions, removals = [], set() - for item in lst: + for pos, item in enumerate(lst): if item is None: removals.add(None) continue - item_id = None - if isinstance(item, dict) and (item_id := item.get(CONF_ID)): - if isinstance(item_id, Extend): - extensions.append(item) - continue - if isinstance(item_id, Remove): - removals.add(item_id.value) - continue + item_id = _get_item_id(item) + if isinstance(item_id, Extend): + extensions.append((pos, item_id.value, item)) + continue + if isinstance(item_id, Remove): + removals.add(item_id.value) + continue if not item_id or item_id in index: # no id or duplicate -> pass through with identity-based key item_id = id(item) @@ -360,7 +383,7 @@ def _build_list_index(lst): return index, extensions, removals -def resolve_extend_remove(value, is_key=None): +def resolve_extend_remove(value: Any, is_key: bool = False) -> None: if isinstance(value, ESPLiteralValue): return # do not check inside literal blocks if isinstance(value, list): @@ -368,26 +391,16 @@ def resolve_extend_remove(value, is_key=None): if extensions or removals: # Rebuild the original list after # processing all extensions and removals - for item in extensions: - item_id = item[CONF_ID].value + for pos, item_id, item in extensions: if item_id in removals: continue old = index.get(item_id) if old is None: # Failed to find source for extension - # Find index of item to show error at correct position - i = next( - ( - i - for i, d in enumerate(value) - if d.get(CONF_ID) == item[CONF_ID] - ) - ) - with cv.prepend_path(i): + with cv.prepend_path(pos): raise cv.Invalid( f"Source for extension of ID '{item_id}' was not found." ) - item[CONF_ID] = item_id index[item_id] = merge_config(old, item) for item_id in removals: index.pop(item_id, None) diff --git a/tests/unit_tests/fixtures/substitutions/05-extend-remove.approved.yaml b/tests/unit_tests/fixtures/substitutions/05-extend-remove.approved.yaml index a479370f4b..35e3e6258f 100644 --- a/tests/unit_tests/fixtures/substitutions/05-extend-remove.approved.yaml +++ b/tests/unit_tests/fixtures/substitutions/05-extend-remove.approved.yaml @@ -7,3 +7,27 @@ some_component: value: 2 - id: component2 value: 5 +lvgl: + pages: + - id: page1 + widgets: + - obj: + id: object1 + x: 3 + y: 2 + width: 4 + - obj: + id: object3 + x: 6 + y: 12 + widgets: + - obj: + id: object4 + x: 14 + y: 9 + width: 15 + height: 13 + - obj: + id: object5 + x: 10 + y: 11 diff --git a/tests/unit_tests/fixtures/substitutions/05-extend-remove.input.yaml b/tests/unit_tests/fixtures/substitutions/05-extend-remove.input.yaml index 2e0e60798d..617f09c31c 100644 --- a/tests/unit_tests/fixtures/substitutions/05-extend-remove.input.yaml +++ b/tests/unit_tests/fixtures/substitutions/05-extend-remove.input.yaml @@ -13,6 +13,30 @@ packages: value: 5 - id: component3 value: 6 + - lvgl: + pages: + - id: page1 + widgets: + - obj: + id: object1 + x: 1 + y: 2 + - obj: + id: object2 + x: 5 + - obj: + id: object3 + x: 6 + y: 7 + widgets: + - obj: + id: object4 + x: 8 + y: 9 + - obj: + id: object5 + x: 10 + y: 11 some_component: - id: !extend ${A} @@ -20,3 +44,23 @@ some_component: - id: component2 value: 3 - id: !remove ${C} + +lvgl: + pages: + - id: !extend page1 + widgets: + - obj: + id: !extend object1 + x: 3 + width: 4 + - obj: + id: !remove object2 + - obj: + id: !extend object3 + y: 12 + height: 13 + widgets: + - obj: + id: !extend object4 + x: 14 + width: 15 From 3d6c361037098f2a7990d1c41c0b4b50788afb64 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:32:08 -0500 Subject: [PATCH 0259/1145] [core] Add support for setting environment variables (#11953) --- esphome/const.py | 1 + esphome/core/config.py | 15 +++++++++++++++ tests/components/esphome/common.yaml | 3 +++ 3 files changed, 19 insertions(+) diff --git a/esphome/const.py b/esphome/const.py index a25114d80e..b4cd3cfd1c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -336,6 +336,7 @@ CONF_ENERGY = "energy" CONF_ENTITY_CATEGORY = "entity_category" CONF_ENTITY_ID = "entity_id" CONF_ENUM_DATAPOINT = "enum_datapoint" +CONF_ENVIRONMENT_VARIABLES = "environment_variables" CONF_EQUATION = "equation" CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support" CONF_ESPHOME = "esphome" diff --git a/esphome/core/config.py b/esphome/core/config.py index 763f9ebd9f..0a239c5f5e 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -17,6 +17,7 @@ from esphome.const import ( CONF_COMPILE_PROCESS_LIMIT, CONF_DEBUG_SCHEDULER, CONF_DEVICES, + CONF_ENVIRONMENT_VARIABLES, CONF_ESPHOME, CONF_FRIENDLY_NAME, CONF_ID, @@ -215,6 +216,11 @@ CONFIG_SCHEMA = cv.All( cv.string_strict: cv.Any([cv.string], cv.string), } ), + cv.Optional(CONF_ENVIRONMENT_VARIABLES, default={}): cv.Schema( + { + cv.string_strict: cv.string, + } + ), cv.Optional(CONF_ON_BOOT): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger), @@ -426,6 +432,12 @@ async def _add_platformio_options(pio_options): cg.add_platformio_option(key, val) +@coroutine_with_priority(CoroPriority.FINAL) +async def _add_environment_variables(env_vars: dict[str, str]) -> None: + # Set environment variables for the build process + os.environ.update(env_vars) + + @coroutine_with_priority(CoroPriority.AUTOMATION) async def _add_automations(config): for conf in config.get(CONF_ON_BOOT, []): @@ -563,6 +575,9 @@ async def to_code(config: ConfigType) -> None: if config[CONF_PLATFORMIO_OPTIONS]: CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) + if config[CONF_ENVIRONMENT_VARIABLES]: + CORE.add_job(_add_environment_variables, config[CONF_ENVIRONMENT_VARIABLES]) + # Process areas all_areas: list[dict[str, str | core.ID]] = [] if CONF_AREA in config: diff --git a/tests/components/esphome/common.yaml b/tests/components/esphome/common.yaml index b2d7bccaa5..db75b08b38 100644 --- a/tests/components/esphome/common.yaml +++ b/tests/components/esphome/common.yaml @@ -2,6 +2,9 @@ esphome: debug_scheduler: true platformio_options: board_build.flash_mode: dio + environment_variables: + TEST_ENV_VAR: "test_value" + BUILD_NUMBER: "12345" area: id: testing_area name: Testing Area From 7a238028a7de73b37a6edd8abf0083cdca00aeb7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:38:44 -0600 Subject: [PATCH 0260/1145] Bump ruamel-yaml-clib from 0.2.14 to 0.2.15 (#11956) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 40802422f2..6ae050b35b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,7 @@ aioesphomeapi==42.7.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.16 # dashboard_import -ruamel.yaml.clib==0.2.14 # dashboard_import +ruamel.yaml.clib==0.2.15 # dashboard_import esphome-glyphsets==0.2.0 pillow==11.3.0 cairosvg==2.8.2 From 23f85162d0dd2216001812d79b60786a12f8a130 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:39:01 -0600 Subject: [PATCH 0261/1145] Bump actions/checkout from 5.0.0 to 5.0.1 (#11957) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto-label-pr.yml | 2 +- .github/workflows/ci-api-proto.yml | 2 +- .github/workflows/ci-clang-tidy-hash.yml | 2 +- .github/workflows/ci-docker.yml | 2 +- .../workflows/ci-memory-impact-comment.yml | 2 +- .github/workflows/ci.yml | 30 +++++++++---------- .github/workflows/codeql.yml | 2 +- .github/workflows/release.yml | 8 ++--- .github/workflows/sync-device-classes.yml | 4 +-- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/auto-label-pr.yml b/.github/workflows/auto-label-pr.yml index dd1bc29d83..fb284c9d8c 100644 --- a/.github/workflows/auto-label-pr.yml +++ b/.github/workflows/auto-label-pr.yml @@ -22,7 +22,7 @@ jobs: if: github.event.action != 'labeled' || github.event.sender.type != 'Bot' steps: - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Generate a token id: generate-token diff --git a/.github/workflows/ci-api-proto.yml b/.github/workflows/ci-api-proto.yml index 400373679f..be5af1aff1 100644 --- a/.github/workflows/ci-api-proto.yml +++ b/.github/workflows/ci-api-proto.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: diff --git a/.github/workflows/ci-clang-tidy-hash.yml b/.github/workflows/ci-clang-tidy-hash.yml index 78d1c2b87f..aebf07949d 100644 --- a/.github/workflows/ci-clang-tidy-hash.yml +++ b/.github/workflows/ci-clang-tidy-hash.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 7111c61dda..9bb983b993 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -43,7 +43,7 @@ jobs: - "docker" # - "lint" steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: diff --git a/.github/workflows/ci-memory-impact-comment.yml b/.github/workflows/ci-memory-impact-comment.yml index eea1d2c148..c82ae30f55 100644 --- a/.github/workflows/ci-memory-impact-comment.yml +++ b/.github/workflows/ci-memory-impact-comment.yml @@ -49,7 +49,7 @@ jobs: - name: Check out code from base repository if: steps.pr.outputs.skip != 'true' - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: # Always check out from the base repository (esphome/esphome), never from forks # Use the PR's target branch to ensure we run trusted code from the main repo diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16837b3186..5293c62d34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: cache-key: ${{ steps.cache-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Generate cache-key id: cache-key run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT @@ -70,7 +70,7 @@ jobs: if: needs.determine-jobs.outputs.python-linters == 'true' steps: - name: Check out code from GitHub - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -91,7 +91,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -132,7 +132,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore Python id: restore-python uses: ./.github/actions/restore-python @@ -183,7 +183,7 @@ jobs: component-test-batches: ${{ steps.determine.outputs.component-test-batches }} steps: - name: Check out code from GitHub - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: # Fetch enough history to find the merge base fetch-depth: 2 @@ -237,7 +237,7 @@ jobs: if: needs.determine-jobs.outputs.integration-tests == 'true' steps: - name: Check out code from GitHub - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up Python 3.13 id: python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 @@ -273,7 +273,7 @@ jobs: if: github.event_name == 'pull_request' && (needs.determine-jobs.outputs.cpp-unit-tests-run-all == 'true' || needs.determine-jobs.outputs.cpp-unit-tests-components != '[]') steps: - name: Check out code from GitHub - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore Python uses: ./.github/actions/restore-python @@ -321,7 +321,7 @@ jobs: steps: - name: Check out code from GitHub - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: # Need history for HEAD~1 to work for checking changed files fetch-depth: 2 @@ -400,7 +400,7 @@ jobs: GH_TOKEN: ${{ github.token }} steps: - name: Check out code from GitHub - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: # Need history for HEAD~1 to work for checking changed files fetch-depth: 2 @@ -489,7 +489,7 @@ jobs: steps: - name: Check out code from GitHub - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: # Need history for HEAD~1 to work for checking changed files fetch-depth: 2 @@ -577,7 +577,7 @@ jobs: version: 1.0 - name: Check out code from GitHub - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -662,7 +662,7 @@ jobs: if: github.event_name == 'pull_request' && !startsWith(github.base_ref, 'beta') && !startsWith(github.base_ref, 'release') steps: - name: Check out code from GitHub - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -688,7 +688,7 @@ jobs: skip: ${{ steps.check-script.outputs.skip }} steps: - name: Check out target branch - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.base_ref }} @@ -840,7 +840,7 @@ jobs: flash_usage: ${{ steps.extract.outputs.flash_usage }} steps: - name: Check out PR branch - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -908,7 +908,7 @@ jobs: GH_TOKEN: ${{ github.token }} steps: - name: Check out code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore Python uses: ./.github/actions/restore-python with: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2273975328..adb2a9d79f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -54,7 +54,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75d88abf29..a064f6ef3a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: branch_build: ${{ steps.tag.outputs.branch_build }} deploy_env: ${{ steps.tag.outputs.deploy_env }} steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Get tag id: tag # yamllint disable rule:line-length @@ -60,7 +60,7 @@ jobs: contents: read id-token: write steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: @@ -92,7 +92,7 @@ jobs: os: "ubuntu-24.04-arm" steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: @@ -168,7 +168,7 @@ jobs: - ghcr - dockerhub steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Download digests uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index 9479645ccc..4fc287b067 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -13,10 +13,10 @@ jobs: if: github.repository == 'esphome/esphome' steps: - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Checkout Home Assistant - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: repository: home-assistant/core path: lib/home-assistant From 1a73f49cd23080a500be9fd9d98de3ebccd02fbe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 17 Nov 2025 17:20:18 -0600 Subject: [PATCH 0262/1145] [number] Modernize to C++17 nested namespaces (#11945) --- esphome/components/number/automation.cpp | 6 ++---- esphome/components/number/automation.h | 6 ++---- esphome/components/number/number.cpp | 6 ++---- esphome/components/number/number.h | 6 ++---- esphome/components/number/number_call.cpp | 6 ++---- esphome/components/number/number_call.h | 6 ++---- esphome/components/number/number_traits.cpp | 6 ++---- esphome/components/number/number_traits.h | 6 ++---- 8 files changed, 16 insertions(+), 32 deletions(-) diff --git a/esphome/components/number/automation.cpp b/esphome/components/number/automation.cpp index bfc59d0465..78ffc255fe 100644 --- a/esphome/components/number/automation.cpp +++ b/esphome/components/number/automation.cpp @@ -1,8 +1,7 @@ #include "automation.h" #include "esphome/core/log.h" -namespace esphome { -namespace number { +namespace esphome::number { static const char *const TAG = "number.automation"; @@ -52,5 +51,4 @@ void ValueRangeTrigger::on_state_(float state) { this->rtc_.save(&in_range); } -} // namespace number -} // namespace esphome +} // namespace esphome::number diff --git a/esphome/components/number/automation.h b/esphome/components/number/automation.h index 79eba883c4..a7cd04f083 100644 --- a/esphome/components/number/automation.h +++ b/esphome/components/number/automation.h @@ -4,8 +4,7 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" -namespace esphome { -namespace number { +namespace esphome::number { class NumberStateTrigger : public Trigger { public: @@ -91,5 +90,4 @@ template class NumberInRangeCondition : public Condition float max_{NAN}; }; -} // namespace number -} // namespace esphome +} // namespace esphome::number diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index f12e0e9e1e..992100ead0 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -3,8 +3,7 @@ #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" -namespace esphome { -namespace number { +namespace esphome::number { static const char *const TAG = "number"; @@ -43,5 +42,4 @@ void Number::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } -} // namespace number -} // namespace esphome +} // namespace esphome::number diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index da91d70d53..472e06ad61 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -6,8 +6,7 @@ #include "number_call.h" #include "number_traits.h" -namespace esphome { -namespace number { +namespace esphome::number { class Number; void log_number(const char *tag, const char *prefix, const char *type, Number *obj); @@ -53,5 +52,4 @@ class Number : public EntityBase { CallbackManager state_callback_; }; -} // namespace number -} // namespace esphome +} // namespace esphome::number diff --git a/esphome/components/number/number_call.cpp b/esphome/components/number/number_call.cpp index 669dd65184..27a857c112 100644 --- a/esphome/components/number/number_call.cpp +++ b/esphome/components/number/number_call.cpp @@ -2,8 +2,7 @@ #include "number.h" #include "esphome/core/log.h" -namespace esphome { -namespace number { +namespace esphome::number { static const char *const TAG = "number"; @@ -125,5 +124,4 @@ void NumberCall::perform() { this->parent_->control(target_value); } -} // namespace number -} // namespace esphome +} // namespace esphome::number diff --git a/esphome/components/number/number_call.h b/esphome/components/number/number_call.h index 807207f0ec..0f6889dcb6 100644 --- a/esphome/components/number/number_call.h +++ b/esphome/components/number/number_call.h @@ -4,8 +4,7 @@ #include "esphome/core/log.h" #include "number_traits.h" -namespace esphome { -namespace number { +namespace esphome::number { class Number; @@ -44,5 +43,4 @@ class NumberCall { bool cycle_; }; -} // namespace number -} // namespace esphome +} // namespace esphome::number diff --git a/esphome/components/number/number_traits.cpp b/esphome/components/number/number_traits.cpp index 89035661f5..1e4239ceca 100644 --- a/esphome/components/number/number_traits.cpp +++ b/esphome/components/number/number_traits.cpp @@ -1,10 +1,8 @@ #include "esphome/core/log.h" #include "number_traits.h" -namespace esphome { -namespace number { +namespace esphome::number { static const char *const TAG = "number"; -} // namespace number -} // namespace esphome +} // namespace esphome::number diff --git a/esphome/components/number/number_traits.h b/esphome/components/number/number_traits.h index fa68c2390a..5ccbb9ba48 100644 --- a/esphome/components/number/number_traits.h +++ b/esphome/components/number/number_traits.h @@ -3,8 +3,7 @@ #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" -namespace esphome { -namespace number { +namespace esphome::number { enum NumberMode : uint8_t { NUMBER_MODE_AUTO = 0, @@ -35,5 +34,4 @@ class NumberTraits : public EntityBase_DeviceClass, public EntityBase_UnitOfMeas NumberMode mode_{NUMBER_MODE_AUTO}; }; -} // namespace number -} // namespace esphome +} // namespace esphome::number From fdc7ae776071c0638774f122d3f216f2ed45af48 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 17 Nov 2025 17:20:32 -0600 Subject: [PATCH 0263/1145] [wifi] Skip redundant setter calls for default values (#11943) --- esphome/components/wifi/__init__.py | 9 ++++++--- esphome/components/wifi/wifi_component.h | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 11bd7798e2..f543d972c9 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -479,11 +479,14 @@ async def to_code(config): cg.add(var.set_min_auth_mode(config[CONF_MIN_AUTH_MODE])) if config[CONF_FAST_CONNECT]: cg.add_define("USE_WIFI_FAST_CONNECT") - cg.add(var.set_passive_scan(config[CONF_PASSIVE_SCAN])) + # passive_scan defaults to false in C++ - only set if true + if config[CONF_PASSIVE_SCAN]: + cg.add(var.set_passive_scan(True)) if CONF_OUTPUT_POWER in config: cg.add(var.set_output_power(config[CONF_OUTPUT_POWER])) - - cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) + # enable_on_boot defaults to true in C++ - only set if false + if not config[CONF_ENABLE_ON_BOOT]: + cg.add(var.set_enable_on_boot(False)) if CORE.is_esp8266: cg.add_library("ESP8266WiFi", None) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 2fd7fa6cd4..66e2ccf1cb 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -526,7 +526,7 @@ class WiFiComponent : public Component { bool btm_{false}; bool rrm_{false}; #endif - bool enable_on_boot_; + bool enable_on_boot_{true}; bool got_ipv4_address_{false}; bool keep_scan_results_{false}; From 0923bcd2ca1a77ff8777bd71749a403b37ce00d4 Mon Sep 17 00:00:00 2001 From: strange_v Date: Tue, 18 Nov 2025 02:32:17 +0100 Subject: [PATCH 0264/1145] [mipi_rgb] Fix GUITION-4848S040 colors (#11709) --- esphome/components/mipi_rgb/mipi_rgb.cpp | 15 ++++++++------- esphome/components/mipi_rgb/models/guition.py | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/esphome/components/mipi_rgb/mipi_rgb.cpp b/esphome/components/mipi_rgb/mipi_rgb.cpp index 00c9c8cbff..080fb08c09 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.cpp +++ b/esphome/components/mipi_rgb/mipi_rgb.cpp @@ -350,6 +350,7 @@ void MipiRgb::dump_config() { "\n Width: %u" "\n Height: %u" "\n Rotation: %d degrees" + "\n PCLK Inverted: %s" "\n HSync Pulse Width: %u" "\n HSync Back Porch: %u" "\n HSync Front Porch: %u" @@ -357,18 +358,18 @@ void MipiRgb::dump_config() { "\n VSync Back Porch: %u" "\n VSync Front Porch: %u" "\n Invert Colors: %s" - "\n Pixel Clock: %dMHz" + "\n Pixel Clock: %uMHz" "\n Reset Pin: %s" "\n DE Pin: %s" "\n PCLK Pin: %s" "\n HSYNC Pin: %s" "\n VSYNC Pin: %s", - this->model_, this->width_, this->height_, this->rotation_, this->hsync_pulse_width_, - this->hsync_back_porch_, this->hsync_front_porch_, this->vsync_pulse_width_, this->vsync_back_porch_, - this->vsync_front_porch_, YESNO(this->invert_colors_), this->pclk_frequency_ / 1000000, - get_pin_name(this->reset_pin_).c_str(), get_pin_name(this->de_pin_).c_str(), - get_pin_name(this->pclk_pin_).c_str(), get_pin_name(this->hsync_pin_).c_str(), - get_pin_name(this->vsync_pin_).c_str()); + this->model_, this->width_, this->height_, this->rotation_, YESNO(this->pclk_inverted_), + this->hsync_pulse_width_, this->hsync_back_porch_, this->hsync_front_porch_, this->vsync_pulse_width_, + this->vsync_back_porch_, this->vsync_front_porch_, YESNO(this->invert_colors_), + (unsigned) (this->pclk_frequency_ / 1000000), get_pin_name(this->reset_pin_).c_str(), + get_pin_name(this->de_pin_).c_str(), get_pin_name(this->pclk_pin_).c_str(), + get_pin_name(this->hsync_pin_).c_str(), get_pin_name(this->vsync_pin_).c_str()); if (this->madctl_ & MADCTL_BGR) { this->dump_pins_(8, 13, "Blue", 0); diff --git a/esphome/components/mipi_rgb/models/guition.py b/esphome/components/mipi_rgb/models/guition.py index da433e686e..915b8beda0 100644 --- a/esphome/components/mipi_rgb/models/guition.py +++ b/esphome/components/mipi_rgb/models/guition.py @@ -11,6 +11,7 @@ st7701s.extend( vsync_pin=17, pclk_pin=21, pclk_frequency="12MHz", + pclk_inverted=False, pixel_mode="18bit", mirror_x=True, mirror_y=True, From 0d6c9623ce38f8aed4692006f52135f7e80d490e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 17 Nov 2025 20:02:16 -0600 Subject: [PATCH 0265/1145] [dashboard_import] Store package import URL in .rodata instead of RAM (#11951) --- esphome/components/dashboard_import/dashboard_import.cpp | 6 +++--- esphome/components/dashboard_import/dashboard_import.h | 6 ++---- esphome/components/mdns/mdns_component.cpp | 3 +-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/esphome/components/dashboard_import/dashboard_import.cpp b/esphome/components/dashboard_import/dashboard_import.cpp index c04696fd53..d4a95b81f6 100644 --- a/esphome/components/dashboard_import/dashboard_import.cpp +++ b/esphome/components/dashboard_import/dashboard_import.cpp @@ -3,10 +3,10 @@ namespace esphome { namespace dashboard_import { -static std::string g_package_import_url; // NOLINT +static const char *g_package_import_url = ""; // NOLINT -const std::string &get_package_import_url() { return g_package_import_url; } -void set_package_import_url(std::string url) { g_package_import_url = std::move(url); } +const char *get_package_import_url() { return g_package_import_url; } +void set_package_import_url(const char *url) { g_package_import_url = url; } } // namespace dashboard_import } // namespace esphome diff --git a/esphome/components/dashboard_import/dashboard_import.h b/esphome/components/dashboard_import/dashboard_import.h index edcda6b803..488bf80a2e 100644 --- a/esphome/components/dashboard_import/dashboard_import.h +++ b/esphome/components/dashboard_import/dashboard_import.h @@ -1,12 +1,10 @@ #pragma once -#include - namespace esphome { namespace dashboard_import { -const std::string &get_package_import_url(); -void set_package_import_url(std::string url); +const char *get_package_import_url(); +void set_package_import_url(const char *url); } // namespace dashboard_import } // namespace esphome diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 2c3150ff5d..b66129404e 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -135,8 +135,7 @@ void MDNSComponent::compile_records_(StaticVector Date: Tue, 18 Nov 2025 14:11:49 +1000 Subject: [PATCH 0266/1145] [build] Don't clear pio cache unless requested (#11966) --- esphome/writer.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/writer.py b/esphome/writer.py index 8eee445cf1..b866a804b3 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -121,7 +121,7 @@ def update_storage_json() -> None: ) else: _LOGGER.info("Core config or version changed, cleaning build files...") - clean_build() + clean_build(clear_pio_cache=False) elif storage_should_update_cmake_cache(old, new): _LOGGER.info("Integrations changed, cleaning cmake cache...") clean_cmake_cache() @@ -301,7 +301,7 @@ def clean_cmake_cache(): pioenvs_cmake_path.unlink() -def clean_build(): +def clean_build(clear_pio_cache: bool = True): import shutil # Allow skipping cache cleaning for integration tests @@ -322,6 +322,9 @@ def clean_build(): _LOGGER.info("Deleting %s", dependencies_lock) dependencies_lock.unlink() + if not clear_pio_cache: + return + # Clean PlatformIO cache to resolve CMake compiler detection issues # This helps when toolchain paths change or get corrupted try: From 11d0d4d1288c28aeb03b7bd27c36a857f9b308b4 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 18 Nov 2025 14:27:50 +1000 Subject: [PATCH 0267/1145] [lvgl] Apply scale to spinbox value (#11946) --- esphome/components/lvgl/widgets/spinbox.py | 5 ++++- tests/components/lvgl/lvgl-package.yaml | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/esphome/components/lvgl/widgets/spinbox.py b/esphome/components/lvgl/widgets/spinbox.py index ac23ded723..c6f25e9587 100644 --- a/esphome/components/lvgl/widgets/spinbox.py +++ b/esphome/components/lvgl/widgets/spinbox.py @@ -1,6 +1,7 @@ from esphome import automation import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_RANGE_FROM, CONF_RANGE_TO, CONF_STEP, CONF_VALUE +from esphome.cpp_generator import MockObj from ..automation import action_to_code from ..defines import ( @@ -114,7 +115,9 @@ class SpinboxType(WidgetType): w.obj, digits, digits - config[CONF_DECIMAL_PLACES] ) if (value := config.get(CONF_VALUE)) is not None: - lv.spinbox_set_value(w.obj, await lv_float.process(value)) + lv.spinbox_set_value( + w.obj, MockObj(await lv_float.process(value)) * w.get_scale() + ) def get_scale(self, config): return 10 ** config[CONF_DECIMAL_PLACES] diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index e42a813b40..eabceff9d9 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -703,7 +703,9 @@ lvgl: on_value: - lvgl.spinbox.update: id: spinbox_id - value: !lambda return x; + value: !lambda |- + static float yyy = 83.0; + return yyy + .8; - button: styles: spin_button id: spin_up From 33983b051bf9a979e929c983ed4661d57ebbed1f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 18 Nov 2025 10:51:47 -0600 Subject: [PATCH 0268/1145] [ld24xx] Use stack allocation for MAC and version formatting (#11961) --- esphome/components/ld2410/ld2410.cpp | 26 ++++++++++++-------------- esphome/components/ld2412/ld2412.cpp | 26 ++++++++++++-------------- esphome/components/ld2450/ld2450.cpp | 26 ++++++++++++-------------- esphome/components/ld24xx/ld24xx.h | 24 +++++++++++++++++++++++- 4 files changed, 59 insertions(+), 43 deletions(-) diff --git a/esphome/components/ld2410/ld2410.cpp b/esphome/components/ld2410/ld2410.cpp index 608882565f..391f2024cd 100644 --- a/esphome/components/ld2410/ld2410.cpp +++ b/esphome/components/ld2410/ld2410.cpp @@ -13,8 +13,6 @@ namespace esphome { namespace ld2410 { static const char *const TAG = "ld2410"; -static const char *const UNKNOWN_MAC = "unknown"; -static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X"; enum BaudRate : uint8_t { BAUD_RATE_9600 = 1, @@ -181,15 +179,15 @@ static inline bool validate_header_footer(const uint8_t *header_footer, const ui } void LD2410Component::dump_config() { - std::string mac_str = - mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC; - std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5], - this->version_[4], this->version_[3], this->version_[2]); + char mac_s[18]; + char version_s[20]; + const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s); + ld24xx::format_version_str(this->version_, version_s); ESP_LOGCONFIG(TAG, "LD2410:\n" " Firmware version: %s\n" " MAC address: %s", - version.c_str(), mac_str.c_str()); + version_s, mac_str); #ifdef USE_BINARY_SENSOR ESP_LOGCONFIG(TAG, "Binary Sensors:"); LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_); @@ -448,12 +446,12 @@ bool LD2410Component::handle_ack_data_() { case CMD_QUERY_VERSION: { std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_)); - std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5], - this->version_[4], this->version_[3], this->version_[2]); - ESP_LOGV(TAG, "Firmware version: %s", version.c_str()); + char version_s[20]; + ld24xx::format_version_str(this->version_, version_s); + ESP_LOGV(TAG, "Firmware version: %s", version_s); #ifdef USE_TEXT_SENSOR if (this->version_text_sensor_ != nullptr) { - this->version_text_sensor_->publish_state(version); + this->version_text_sensor_->publish_state(version_s); } #endif break; @@ -506,9 +504,9 @@ bool LD2410Component::handle_ack_data_() { std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_)); } - std::string mac_str = - mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC; - ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str()); + char mac_s[18]; + const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s); + ESP_LOGV(TAG, "MAC address: %s", mac_str); #ifdef USE_TEXT_SENSOR if (this->mac_text_sensor_ != nullptr) { this->mac_text_sensor_->publish_state(mac_str); diff --git a/esphome/components/ld2412/ld2412.cpp b/esphome/components/ld2412/ld2412.cpp index 5323a9a658..4f2fd7c2bd 100644 --- a/esphome/components/ld2412/ld2412.cpp +++ b/esphome/components/ld2412/ld2412.cpp @@ -14,8 +14,6 @@ namespace esphome { namespace ld2412 { static const char *const TAG = "ld2412"; -static const char *const UNKNOWN_MAC = "unknown"; -static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X"; enum BaudRate : uint8_t { BAUD_RATE_9600 = 1, @@ -200,15 +198,15 @@ static inline bool validate_header_footer(const uint8_t *header_footer, const ui } void LD2412Component::dump_config() { - std::string mac_str = - mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC; - std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5], - this->version_[4], this->version_[3], this->version_[2]); + char mac_s[18]; + char version_s[20]; + const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s); + ld24xx::format_version_str(this->version_, version_s); ESP_LOGCONFIG(TAG, "LD2412:\n" " Firmware version: %s\n" " MAC address: %s", - version.c_str(), mac_str.c_str()); + version_s, mac_str); #ifdef USE_BINARY_SENSOR ESP_LOGCONFIG(TAG, "Binary Sensors:"); LOG_BINARY_SENSOR(" ", "DynamicBackgroundCorrectionStatus", @@ -492,12 +490,12 @@ bool LD2412Component::handle_ack_data_() { case CMD_QUERY_VERSION: { std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_)); - std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5], - this->version_[4], this->version_[3], this->version_[2]); - ESP_LOGV(TAG, "Firmware version: %s", version.c_str()); + char version_s[20]; + ld24xx::format_version_str(this->version_, version_s); + ESP_LOGV(TAG, "Firmware version: %s", version_s); #ifdef USE_TEXT_SENSOR if (this->version_text_sensor_ != nullptr) { - this->version_text_sensor_->publish_state(version); + this->version_text_sensor_->publish_state(version_s); } #endif break; @@ -544,9 +542,9 @@ bool LD2412Component::handle_ack_data_() { std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_)); } - std::string mac_str = - mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC; - ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str()); + char mac_s[18]; + const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s); + ESP_LOGV(TAG, "MAC address: %s", mac_str); #ifdef USE_TEXT_SENSOR if (this->mac_text_sensor_ != nullptr) { this->mac_text_sensor_->publish_state(mac_str); diff --git a/esphome/components/ld2450/ld2450.cpp b/esphome/components/ld2450/ld2450.cpp index c9d4da47a4..8e5287aec7 100644 --- a/esphome/components/ld2450/ld2450.cpp +++ b/esphome/components/ld2450/ld2450.cpp @@ -17,8 +17,6 @@ namespace esphome { namespace ld2450 { static const char *const TAG = "ld2450"; -static const char *const UNKNOWN_MAC = "unknown"; -static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X"; enum BaudRate : uint8_t { BAUD_RATE_9600 = 1, @@ -192,15 +190,15 @@ void LD2450Component::setup() { } void LD2450Component::dump_config() { - std::string mac_str = - mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC; - std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5], - this->version_[4], this->version_[3], this->version_[2]); + char mac_s[18]; + char version_s[20]; + const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s); + ld24xx::format_version_str(this->version_, version_s); ESP_LOGCONFIG(TAG, "LD2450:\n" " Firmware version: %s\n" " MAC address: %s", - version.c_str(), mac_str.c_str()); + version_s, mac_str); #ifdef USE_BINARY_SENSOR ESP_LOGCONFIG(TAG, "Binary Sensors:"); LOG_BINARY_SENSOR(" ", "MovingTarget", this->moving_target_binary_sensor_); @@ -642,12 +640,12 @@ bool LD2450Component::handle_ack_data_() { case CMD_QUERY_VERSION: { std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_)); - std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5], - this->version_[4], this->version_[3], this->version_[2]); - ESP_LOGV(TAG, "Firmware version: %s", version.c_str()); + char version_s[20]; + ld24xx::format_version_str(this->version_, version_s); + ESP_LOGV(TAG, "Firmware version: %s", version_s); #ifdef USE_TEXT_SENSOR if (this->version_text_sensor_ != nullptr) { - this->version_text_sensor_->publish_state(version); + this->version_text_sensor_->publish_state(version_s); } #endif break; @@ -663,9 +661,9 @@ bool LD2450Component::handle_ack_data_() { std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_)); } - std::string mac_str = - mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC; - ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str()); + char mac_s[18]; + const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s); + ESP_LOGV(TAG, "MAC address: %s", mac_str); #ifdef USE_TEXT_SENSOR if (this->mac_text_sensor_ != nullptr) { this->mac_text_sensor_->publish_state(mac_str); diff --git a/esphome/components/ld24xx/ld24xx.h b/esphome/components/ld24xx/ld24xx.h index 1cd5e01163..e695b00705 100644 --- a/esphome/components/ld24xx/ld24xx.h +++ b/esphome/components/ld24xx/ld24xx.h @@ -1,11 +1,12 @@ #pragma once #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include +#include #ifdef USE_SENSOR -#include "esphome/core/helpers.h" #include "esphome/components/sensor/sensor.h" #define SUB_SENSOR_WITH_DEDUP(name, dedup_type) \ @@ -39,6 +40,27 @@ namespace esphome { namespace ld24xx { +static const char *const UNKNOWN_MAC = "unknown"; +static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X"; + +// Helper function to format MAC address with stack allocation +// Returns pointer to UNKNOWN_MAC constant or formatted buffer +// Buffer must be exactly 18 bytes (17 for "XX:XX:XX:XX:XX:XX" + null terminator) +inline const char *format_mac_str(const uint8_t *mac_address, std::span buffer) { + if (mac_address_is_valid(mac_address)) { + format_mac_addr_upper(mac_address, buffer.data()); + return buffer.data(); + } + return UNKNOWN_MAC; +} + +// Helper function to format firmware version with stack allocation +// Buffer must be exactly 20 bytes (format: "x.xxXXXXXX" fits in 11 + null terminator, 20 for safety) +inline void format_version_str(const uint8_t *version, std::span buffer) { + snprintf(buffer.data(), buffer.size(), VERSION_FMT, version[1], version[0], version[5], version[4], version[3], + version[2]); +} + #ifdef USE_SENSOR // Helper class to store a sensor with a deduplicator & publish state only when the value changes template class SensorWithDedup { From c59af222170518a7b85154dba9b26c53a8db0d33 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:40:31 -0500 Subject: [PATCH 0269/1145] [esp32] Fix Arduino build on some ESP32 S2 boards (#11972) --- esphome/components/esp32/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 0f85e585f7..6f577d2926 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -931,6 +931,12 @@ async def to_code(config): add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True) add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True) + # ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency + if get_esp32_variant() == VARIANT_ESP32S2: + cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=1") + cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=0") + cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=0") + cg.add_build_flag("-Wno-nonnull-compare") add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True) From 1888f5ffd582e3afdbfdff8faf9efbac8202ada4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 18 Nov 2025 12:16:18 -0600 Subject: [PATCH 0270/1145] [scheduler] Add defensive nullptr checks and explicit locking requirements (#11974) --- esphome/core/scheduler.cpp | 14 ++++++++------ esphome/core/scheduler.h | 35 +++++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index d2e0f0dab4..09d50ee7c8 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -154,8 +154,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type // For retries, check if there's a cancelled timeout first if (is_retry && name_cstr != nullptr && type == SchedulerItem::TIMEOUT && - (has_cancelled_timeout_in_container_(this->items_, component, name_cstr, /* match_retry= */ true) || - has_cancelled_timeout_in_container_(this->to_add_, component, name_cstr, /* match_retry= */ true))) { + (has_cancelled_timeout_in_container_locked_(this->items_, component, name_cstr, /* match_retry= */ true) || + has_cancelled_timeout_in_container_locked_(this->to_add_, component, name_cstr, /* match_retry= */ true))) { // Skip scheduling - the retry was cancelled #ifdef ESPHOME_DEBUG_SCHEDULER ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item", name_cstr); @@ -556,7 +556,8 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c #ifndef ESPHOME_THREAD_SINGLE // Mark items in defer queue as cancelled (they'll be skipped when processed) if (type == SchedulerItem::TIMEOUT) { - total_cancelled += this->mark_matching_items_removed_(this->defer_queue_, component, name_cstr, type, match_retry); + total_cancelled += + this->mark_matching_items_removed_locked_(this->defer_queue_, component, name_cstr, type, match_retry); } #endif /* not ESPHOME_THREAD_SINGLE */ @@ -565,19 +566,20 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c // (removing the last element doesn't break heap structure) if (!this->items_.empty()) { auto &last_item = this->items_.back(); - if (this->matches_item_(last_item, component, name_cstr, type, match_retry)) { + if (this->matches_item_locked_(last_item, component, name_cstr, type, match_retry)) { this->recycle_item_(std::move(this->items_.back())); this->items_.pop_back(); total_cancelled++; } // For other items in heap, we can only mark for removal (can't remove from middle of heap) - size_t heap_cancelled = this->mark_matching_items_removed_(this->items_, component, name_cstr, type, match_retry); + size_t heap_cancelled = + this->mark_matching_items_removed_locked_(this->items_, component, name_cstr, type, match_retry); total_cancelled += heap_cancelled; this->to_remove_ += heap_cancelled; // Track removals for heap items } // Cancel items in to_add_ - total_cancelled += this->mark_matching_items_removed_(this->to_add_, component, name_cstr, type, match_retry); + total_cancelled += this->mark_matching_items_removed_locked_(this->to_add_, component, name_cstr, type, match_retry); return total_cancelled > 0; } diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index fd16840240..bea1503df0 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -243,8 +243,18 @@ class Scheduler { } // Helper function to check if item matches criteria for cancellation - inline bool HOT matches_item_(const std::unique_ptr &item, Component *component, const char *name_cstr, - SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const { + // IMPORTANT: Must be called with scheduler lock held + inline bool HOT matches_item_locked_(const std::unique_ptr &item, Component *component, + const char *name_cstr, SchedulerItem::Type type, bool match_retry, + bool skip_removed = true) const { + // THREAD SAFETY: Check for nullptr first to prevent LoadProhibited crashes. On multi-threaded + // platforms, items can be moved out of defer_queue_ during processing, leaving nullptr entries. + // PR #11305 added nullptr checks in callers (mark_matching_items_removed_locked_() and + // has_cancelled_timeout_in_container_locked_()), but this check provides defense-in-depth: helper + // functions should be safe regardless of caller behavior. + // Fixes: https://github.com/esphome/esphome/issues/11940 + if (!item) + return false; if (item->component != component || item->type != type || (skip_removed && item->remove) || (match_retry && !item->is_retry)) { return false; @@ -304,8 +314,8 @@ class Scheduler { // SAFETY: Moving out the unique_ptr leaves a nullptr in the vector at defer_queue_front_. // This is intentional and safe because: // 1. The vector is only cleaned up by cleanup_defer_queue_locked_() at the end of this function - // 2. Any code iterating defer_queue_ MUST check for nullptr items (see mark_matching_items_removed_ - // and has_cancelled_timeout_in_container_ in scheduler.h) + // 2. Any code iterating defer_queue_ MUST check for nullptr items (see mark_matching_items_removed_locked_ + // and has_cancelled_timeout_in_container_locked_ in scheduler.h) // 3. The lock protects concurrent access, but the nullptr remains until cleanup item = std::move(this->defer_queue_[this->defer_queue_front_]); this->defer_queue_front_++; @@ -393,10 +403,10 @@ class Scheduler { // Helper to mark matching items in a container as removed // Returns the number of items marked for removal - // IMPORTANT: Caller must hold the scheduler lock before calling this function. + // IMPORTANT: Must be called with scheduler lock held template - size_t mark_matching_items_removed_(Container &container, Component *component, const char *name_cstr, - SchedulerItem::Type type, bool match_retry) { + size_t mark_matching_items_removed_locked_(Container &container, Component *component, const char *name_cstr, + SchedulerItem::Type type, bool match_retry) { size_t count = 0; for (auto &item : container) { // Skip nullptr items (can happen in defer_queue_ when items are being processed) @@ -405,7 +415,7 @@ class Scheduler { // the vector can still contain nullptr items from the processing loop. This check prevents crashes. if (!item) continue; - if (this->matches_item_(item, component, name_cstr, type, match_retry)) { + if (this->matches_item_locked_(item, component, name_cstr, type, match_retry)) { // Mark item for removal (platform-specific) this->set_item_removed_(item.get(), true); count++; @@ -415,9 +425,10 @@ class Scheduler { } // Template helper to check if any item in a container matches our criteria + // IMPORTANT: Must be called with scheduler lock held template - bool has_cancelled_timeout_in_container_(const Container &container, Component *component, const char *name_cstr, - bool match_retry) const { + bool has_cancelled_timeout_in_container_locked_(const Container &container, Component *component, + const char *name_cstr, bool match_retry) const { for (const auto &item : container) { // Skip nullptr items (can happen in defer_queue_ when items are being processed) // The defer_queue_ uses index-based processing: items are std::moved out but left in the @@ -426,8 +437,8 @@ class Scheduler { if (!item) continue; if (is_item_removed_(item.get()) && - this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry, - /* skip_removed= */ false)) { + this->matches_item_locked_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry, + /* skip_removed= */ false)) { return true; } } From fe2befcec2c50704afb816f5434da1871393522d Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:18:09 -0500 Subject: [PATCH 0271/1145] [bme68x] Print error when no sensors are configured (#11976) --- esphome/components/bme68x_bsec2/bme68x_bsec2.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp b/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp index f5dcfd65a1..91383c8d45 100644 --- a/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp +++ b/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp @@ -70,6 +70,9 @@ void BME68xBSEC2Component::dump_config() { if (this->is_failed()) { ESP_LOGE(TAG, "Communication failed (BSEC2 status: %d, BME68X status: %d)", this->bsec_status_, this->bme68x_status_); + if (this->bsec_status_ == BSEC_I_SU_SUBSCRIBEDOUTPUTGATES) { + ESP_LOGE(TAG, "No sensors, add at least one sensor to the config"); + } } if (this->algorithm_output_ != ALGORITHM_OUTPUT_IAQ) { From 72e4b16a5b58d0a2f542d619daad19873f2e5020 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:29:40 -0500 Subject: [PATCH 0272/1145] [sfa30] Fix negative temperature values (#11973) --- esphome/components/sfa30/sfa30.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/sfa30/sfa30.cpp b/esphome/components/sfa30/sfa30.cpp index 99709d5fbb..bbe3bcd7d2 100644 --- a/esphome/components/sfa30/sfa30.cpp +++ b/esphome/components/sfa30/sfa30.cpp @@ -73,17 +73,17 @@ void SFA30Component::update() { } if (this->formaldehyde_sensor_ != nullptr) { - const float formaldehyde = raw_data[0] / 5.0f; + const float formaldehyde = static_cast(raw_data[0]) / 5.0f; this->formaldehyde_sensor_->publish_state(formaldehyde); } if (this->humidity_sensor_ != nullptr) { - const float humidity = raw_data[1] / 100.0f; + const float humidity = static_cast(raw_data[1]) / 100.0f; this->humidity_sensor_->publish_state(humidity); } if (this->temperature_sensor_ != nullptr) { - const float temperature = raw_data[2] / 200.0f; + const float temperature = static_cast(raw_data[2]) / 200.0f; this->temperature_sensor_->publish_state(temperature); } From 81fe5deaa9f376764facd3cd5f26c04e9d4e8aac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:12:42 +1300 Subject: [PATCH 0273/1145] Bump github/codeql-action from 4.31.3 to 4.31.4 (#11977) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index adb2a9d79f..21fff10c95 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3 + uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -86,6 +86,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3 + uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 with: category: "/language:${{matrix.language}}" From 70ed9c7c4db30952faabdd8b7c6373cc28e41419 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 18 Nov 2025 13:17:21 -0600 Subject: [PATCH 0274/1145] [wifi] Fix captive portal unusable when WiFi credentials are wrong (#11965) --- esphome/components/captive_portal/__init__.py | 10 +++ .../captive_portal/captive_portal.cpp | 10 ++- .../captive_portal/captive_portal.h | 4 ++ esphome/components/esp32_improv/__init__.py | 6 +- .../esp32_improv/esp32_improv_component.cpp | 1 + .../esp32_improv/esp32_improv_component.h | 1 + .../web_server_idf/web_server_idf.cpp | 15 +++++ .../web_server_idf/web_server_idf.h | 4 ++ esphome/components/wifi/__init__.py | 8 ++- esphome/components/wifi/wifi_component.cpp | 62 +++++++++++++++---- esphome/components/wifi/wifi_component.h | 5 ++ 11 files changed, 111 insertions(+), 15 deletions(-) diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 9bd3ef8a05..25d0a22083 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -72,6 +72,16 @@ def _final_validate(config: ConfigType) -> ConfigType: "Add 'ap:' to your WiFi configuration to enable the captive portal." ) + # Register socket needs for DNS server and additional HTTP connections + # - 1 UDP socket for DNS server + # - 3 additional TCP sockets for captive portal detection probes + configuration requests + # OS captive portal detection makes multiple probe requests that stay in TIME_WAIT. + # Need headroom for actual user configuration requests. + # LRU purging will reclaim idle sockets to prevent exhaustion from repeated attempts. + from esphome.components import socket + + socket.consume_sockets(4, "captive_portal")(config) + return config diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 30438747f2..459ac557c8 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -50,8 +50,8 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { ESP_LOGI(TAG, "Requested WiFi Settings Change:"); ESP_LOGI(TAG, " SSID='%s'", ssid.c_str()); ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str()); - wifi::global_wifi_component->save_wifi_sta(ssid, psk); - wifi::global_wifi_component->start_scanning(); + // Defer save to main loop thread to avoid NVS operations from HTTP thread + this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid, psk); }); request->redirect(ESPHOME_F("/?save")); } @@ -63,6 +63,12 @@ void CaptivePortal::start() { this->base_->init(); if (!this->initialized_) { this->base_->add_handler(this); +#ifdef USE_ESP32 + // Enable LRU socket purging to handle captive portal detection probe bursts + // OS captive portal detection makes many simultaneous HTTP requests which can + // exhaust sockets. LRU purging automatically closes oldest idle connections. + this->base_->get_server()->set_lru_purge_enable(true); +#endif } network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index f48c286f0c..ae9b9dfba0 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -40,6 +40,10 @@ class CaptivePortal : public AsyncWebHandler, public Component { void end() { this->active_ = false; this->disable_loop(); // Stop processing DNS requests +#ifdef USE_ESP32 + // Disable LRU socket purging now that captive portal is done + this->base_->get_server()->set_lru_purge_enable(false); +#endif this->base_->deinit(); if (this->dns_server_ != nullptr) { this->dns_server_->stop(); diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 1a7194da81..2e69d400ca 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -20,6 +20,10 @@ CONF_ON_STOP = "on_stop" CONF_STATUS_INDICATOR = "status_indicator" CONF_WIFI_TIMEOUT = "wifi_timeout" +# Default WiFi timeout - aligned with WiFi component ap_timeout +# Allows sufficient time to try all BSSIDs before starting provisioning mode +DEFAULT_WIFI_TIMEOUT = "90s" + improv_ns = cg.esphome_ns.namespace("improv") Error = improv_ns.enum("Error") @@ -59,7 +63,7 @@ CONFIG_SCHEMA = ( CONF_AUTHORIZED_DURATION, default="1min" ): cv.positive_time_period_milliseconds, cv.Optional( - CONF_WIFI_TIMEOUT, default="1min" + CONF_WIFI_TIMEOUT, default=DEFAULT_WIFI_TIMEOUT ): cv.positive_time_period_milliseconds, cv.Optional(CONF_ON_PROVISIONED): automation.validate_automation( { diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 398b1d4251..0ad54bbb15 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -127,6 +127,7 @@ void ESP32ImprovComponent::loop() { // Set initial state based on whether we have an authorizer this->set_state_(this->get_initial_state_(), false); this->set_error_(improv::ERROR_NONE); + this->should_start_ = false; // Clear flag after starting ESP_LOGD(TAG, "Service started!"); } } diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 989552ea56..8f4cfd7958 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -45,6 +45,7 @@ class ESP32ImprovComponent : public Component, public improv_base::ImprovBase { void start(); void stop(); bool is_active() const { return this->state_ != improv::STATE_STOPPED; } + bool should_start() const { return this->should_start_; } #ifdef USE_ESP32_IMPROV_STATE_CALLBACK void add_on_state_callback(std::function &&callback) { diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index ce91569de2..f5a66f6bd9 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -94,6 +94,18 @@ void AsyncWebServer::end() { } } +void AsyncWebServer::set_lru_purge_enable(bool enable) { + if (this->lru_purge_enable_ == enable) { + return; // No change needed + } + this->lru_purge_enable_ = enable; + // If server is already running, restart it with new config + if (this->server_) { + this->end(); + this->begin(); + } +} + void AsyncWebServer::begin() { if (this->server_) { this->end(); @@ -101,6 +113,8 @@ void AsyncWebServer::begin() { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.server_port = this->port_; config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; }; + // Enable LRU purging if requested (e.g., by captive portal to handle probe bursts) + config.lru_purge_enable = this->lru_purge_enable_; if (httpd_start(&this->server_, &config) == ESP_OK) { const httpd_uri_t handler_get = { .uri = "", @@ -242,6 +256,7 @@ void AsyncWebServerRequest::send(int code, const char *content_type, const char void AsyncWebServerRequest::redirect(const std::string &url) { httpd_resp_set_status(*this, "302 Found"); httpd_resp_set_hdr(*this, "Location", url.c_str()); + httpd_resp_set_hdr(*this, "Connection", "close"); httpd_resp_send(*this, nullptr, 0); } diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index 5ec6fec009..b9f690b462 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -199,9 +199,13 @@ class AsyncWebServer { return *handler; } + void set_lru_purge_enable(bool enable); + httpd_handle_t get_server() { return this->server_; } + protected: uint16_t port_{}; httpd_handle_t server_{}; + bool lru_purge_enable_{false}; static esp_err_t request_handler(httpd_req_t *r); static esp_err_t request_post_handler(httpd_req_t *r); esp_err_t request_handler_(AsyncWebServerRequest *request) const; diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index f543d972c9..2b21478f30 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -69,6 +69,12 @@ CONF_MIN_AUTH_MODE = "min_auth_mode" # Limited to 127 because selected_sta_index_ is int8_t in C++ MAX_WIFI_NETWORKS = 127 +# Default AP timeout - allows sufficient time to try all BSSIDs during initial connection +# After AP starts, WiFi scanning is skipped to avoid disrupting the AP, so we only +# get best-effort connection attempts. Longer timeout ensures we exhaust all options +# before falling back to AP mode. Aligned with improv wifi_timeout default. +DEFAULT_AP_TIMEOUT = "90s" + wifi_ns = cg.esphome_ns.namespace("wifi") EAPAuth = wifi_ns.struct("EAPAuth") ManualIP = wifi_ns.struct("ManualIP") @@ -177,7 +183,7 @@ CONF_AP_TIMEOUT = "ap_timeout" WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend( { cv.Optional( - CONF_AP_TIMEOUT, default="1min" + CONF_AP_TIMEOUT, default=DEFAULT_AP_TIMEOUT ): cv.positive_time_period_milliseconds, } ) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 51a5a47323..30340601fb 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -199,7 +199,12 @@ static constexpr uint8_t WIFI_RETRY_COUNT_PER_AP = 1; /// Cooldown duration in milliseconds after adapter restart or repeated failures /// Allows WiFi hardware to stabilize before next connection attempt -static constexpr uint32_t WIFI_COOLDOWN_DURATION_MS = 1000; +static constexpr uint32_t WIFI_COOLDOWN_DURATION_MS = 500; + +/// Cooldown duration when fallback AP is active and captive portal may be running +/// Longer interval gives users time to configure WiFi without constant connection attempts +/// While connecting, WiFi can't beacon the AP properly, so needs longer cooldown +static constexpr uint32_t WIFI_COOLDOWN_WITH_AP_ACTIVE_MS = 30000; static constexpr uint8_t get_max_retries_for_phase(WiFiRetryPhase phase) { switch (phase) { @@ -275,7 +280,9 @@ int8_t WiFiComponent::find_next_hidden_sta_(int8_t start_index) { } } - if (!this->ssid_was_seen_in_scan_(sta.get_ssid())) { + // If we didn't scan this cycle, treat all networks as potentially hidden + // Otherwise, only retry networks that weren't seen in the scan + if (!this->did_scan_this_cycle_ || !this->ssid_was_seen_in_scan_(sta.get_ssid())) { ESP_LOGD(TAG, "Hidden candidate " LOG_SECRET("'%s'") " at index %d", sta.get_ssid().c_str(), static_cast(i)); return static_cast(i); } @@ -417,10 +424,6 @@ void WiFiComponent::start() { void WiFiComponent::restart_adapter() { ESP_LOGW(TAG, "Restarting adapter"); this->wifi_mode_(false, {}); - // Enter cooldown state to allow WiFi hardware to stabilize after restart - // Don't set retry_phase_ or num_retried_ here - state machine handles transitions - this->state_ = WIFI_COMPONENT_STATE_COOLDOWN; - this->action_started_ = millis(); this->error_from_callback_ = false; } @@ -441,7 +444,16 @@ void WiFiComponent::loop() { switch (this->state_) { case WIFI_COMPONENT_STATE_COOLDOWN: { this->status_set_warning(LOG_STR("waiting to reconnect")); - if (now - this->action_started_ > WIFI_COOLDOWN_DURATION_MS) { + // Skip cooldown if new credentials were provided while connecting + if (this->skip_cooldown_next_cycle_) { + this->skip_cooldown_next_cycle_ = false; + this->check_connecting_finished(); + break; + } + // Use longer cooldown when captive portal/improv is active to avoid disrupting user config + bool portal_active = this->is_captive_portal_active_() || this->is_esp32_improv_active_(); + uint32_t cooldown_duration = portal_active ? WIFI_COOLDOWN_WITH_AP_ACTIVE_MS : WIFI_COOLDOWN_DURATION_MS; + if (now - this->action_started_ > cooldown_duration) { // After cooldown we either restarted the adapter because of // a failure, or something tried to connect over and over // so we entered cooldown. In both cases we call @@ -495,7 +507,8 @@ void WiFiComponent::loop() { #endif // USE_WIFI_AP #ifdef USE_IMPROV - if (esp32_improv::global_improv_component != nullptr && !esp32_improv::global_improv_component->is_active()) { + if (esp32_improv::global_improv_component != nullptr && !esp32_improv::global_improv_component->is_active() && + !esp32_improv::global_improv_component->should_start()) { if (now - this->last_connected_ > esp32_improv::global_improv_component->get_wifi_timeout()) { if (this->wifi_mode_(true, {})) esp32_improv::global_improv_component->start(); @@ -605,6 +618,8 @@ void WiFiComponent::set_sta(const WiFiAP &ap) { this->init_sta(1); this->add_sta(ap); this->selected_sta_index_ = 0; + // When new credentials are set (e.g., from improv), skip cooldown to retry immediately + this->skip_cooldown_next_cycle_ = true; } WiFiAP WiFiComponent::build_params_for_current_phase_() { @@ -666,6 +681,17 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa sta.set_ssid(ssid); sta.set_password(password); this->set_sta(sta); + + // Trigger connection attempt (exits cooldown if needed, no-op if already connecting/connected) + this->connect_soon_(); +} + +void WiFiComponent::connect_soon_() { + // Only trigger retry if we're in cooldown - if already connecting/connected, do nothing + if (this->state_ == WIFI_COMPONENT_STATE_COOLDOWN) { + ESP_LOGD(TAG, "Exiting cooldown early due to new WiFi credentials"); + this->retry_connect(); + } } void WiFiComponent::start_connecting(const WiFiAP &ap) { @@ -963,6 +989,7 @@ void WiFiComponent::check_scanning_finished() { return; } this->scan_done_ = false; + this->did_scan_this_cycle_ = true; if (this->scan_result_.empty()) { ESP_LOGW(TAG, "No networks found"); @@ -1229,9 +1256,16 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() { return WiFiRetryPhase::RESTARTING_ADAPTER; case WiFiRetryPhase::RESTARTING_ADAPTER: - // After restart, go back to explicit hidden if we went through it initially, otherwise scan - return this->went_through_explicit_hidden_phase_() ? WiFiRetryPhase::EXPLICIT_HIDDEN - : WiFiRetryPhase::SCAN_CONNECTING; + // After restart, go back to explicit hidden if we went through it initially + if (this->went_through_explicit_hidden_phase_()) { + return WiFiRetryPhase::EXPLICIT_HIDDEN; + } + // Skip scanning when captive portal/improv is active to avoid disrupting AP + // Even passive scans can cause brief AP disconnections on ESP32 + if (this->is_captive_portal_active_() || this->is_esp32_improv_active_()) { + return WiFiRetryPhase::RETRY_HIDDEN; + } + return WiFiRetryPhase::SCAN_CONNECTING; } // Should never reach here @@ -1319,6 +1353,12 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) { if (!this->is_captive_portal_active_() && !this->is_esp32_improv_active_()) { this->restart_adapter(); } + // Clear scan flag - we're starting a new retry cycle + this->did_scan_this_cycle_ = false; + // Always enter cooldown after restart (or skip-restart) to allow stabilization + // Use extended cooldown when AP is active to avoid constant scanning that blocks DNS + this->state_ = WIFI_COMPONENT_STATE_COOLDOWN; + this->action_started_ = millis(); // Return true to indicate we should wait (go to COOLDOWN) instead of immediately connecting return true; diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 66e2ccf1cb..b3548078bc 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -291,6 +291,7 @@ class WiFiComponent : public Component { void set_passive_scan(bool passive); void save_wifi_sta(const std::string &ssid, const std::string &password); + // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) /// Setup WiFi interface. @@ -424,6 +425,8 @@ class WiFiComponent : public Component { return true; } + void connect_soon_(); + void wifi_loop_(); bool wifi_mode_(optional sta, optional ap); bool wifi_sta_pre_setup_(); @@ -529,6 +532,8 @@ class WiFiComponent : public Component { bool enable_on_boot_{true}; bool got_ipv4_address_{false}; bool keep_scan_results_{false}; + bool did_scan_this_cycle_{false}; + bool skip_cooldown_next_cycle_{false}; // Pointers at the end (naturally aligned) Trigger<> *connect_trigger_{new Trigger<>()}; From 6c8577678c63c01c12734f9c0cbc0966268bbffa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Nov 2025 14:01:07 -0600 Subject: [PATCH 0275/1145] [captive_portal] Warn when enabled without WiFi AP configured (#11856) --- esphome/components/captive_portal/__init__.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 99acb76bcf..9bd3ef8a05 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -1,9 +1,12 @@ +import logging + import esphome.codegen as cg from esphome.components import web_server_base from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( + CONF_AP, CONF_ID, PLATFORM_BK72XX, PLATFORM_ESP32, @@ -14,6 +17,10 @@ from esphome.const import ( ) from esphome.core import CORE, coroutine_with_priority from esphome.coroutine import CoroPriority +import esphome.final_validate as fv +from esphome.types import ConfigType + +_LOGGER = logging.getLogger(__name__) def AUTO_LOAD() -> list[str]: @@ -50,6 +57,27 @@ CONFIG_SCHEMA = cv.All( ) +def _final_validate(config: ConfigType) -> ConfigType: + full_config = fv.full_config.get() + wifi_conf = full_config.get("wifi") + + if wifi_conf is None: + # This shouldn't happen due to DEPENDENCIES = ["wifi"], but check anyway + raise cv.Invalid("Captive portal requires the wifi component to be configured") + + if CONF_AP not in wifi_conf: + _LOGGER.warning( + "Captive portal is enabled but no WiFi AP is configured. " + "The captive portal will not be accessible. " + "Add 'ap:' to your WiFi configuration to enable the captive portal." + ) + + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate + + @coroutine_with_priority(CoroPriority.CAPTIVE_PORTAL) async def to_code(config): paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) From 3b25fdbc5f458067494a87ddb8f59e4669ae85ad Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:32:08 -0500 Subject: [PATCH 0276/1145] [core] Add support for setting environment variables (#11953) --- esphome/const.py | 1 + esphome/core/config.py | 15 +++++++++++++++ tests/components/esphome/common.yaml | 3 +++ 3 files changed, 19 insertions(+) diff --git a/esphome/const.py b/esphome/const.py index 2ca0018fdb..8360531bff 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -336,6 +336,7 @@ CONF_ENERGY = "energy" CONF_ENTITY_CATEGORY = "entity_category" CONF_ENTITY_ID = "entity_id" CONF_ENUM_DATAPOINT = "enum_datapoint" +CONF_ENVIRONMENT_VARIABLES = "environment_variables" CONF_EQUATION = "equation" CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support" CONF_ESPHOME = "esphome" diff --git a/esphome/core/config.py b/esphome/core/config.py index 763f9ebd9f..0a239c5f5e 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -17,6 +17,7 @@ from esphome.const import ( CONF_COMPILE_PROCESS_LIMIT, CONF_DEBUG_SCHEDULER, CONF_DEVICES, + CONF_ENVIRONMENT_VARIABLES, CONF_ESPHOME, CONF_FRIENDLY_NAME, CONF_ID, @@ -215,6 +216,11 @@ CONFIG_SCHEMA = cv.All( cv.string_strict: cv.Any([cv.string], cv.string), } ), + cv.Optional(CONF_ENVIRONMENT_VARIABLES, default={}): cv.Schema( + { + cv.string_strict: cv.string, + } + ), cv.Optional(CONF_ON_BOOT): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger), @@ -426,6 +432,12 @@ async def _add_platformio_options(pio_options): cg.add_platformio_option(key, val) +@coroutine_with_priority(CoroPriority.FINAL) +async def _add_environment_variables(env_vars: dict[str, str]) -> None: + # Set environment variables for the build process + os.environ.update(env_vars) + + @coroutine_with_priority(CoroPriority.AUTOMATION) async def _add_automations(config): for conf in config.get(CONF_ON_BOOT, []): @@ -563,6 +575,9 @@ async def to_code(config: ConfigType) -> None: if config[CONF_PLATFORMIO_OPTIONS]: CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) + if config[CONF_ENVIRONMENT_VARIABLES]: + CORE.add_job(_add_environment_variables, config[CONF_ENVIRONMENT_VARIABLES]) + # Process areas all_areas: list[dict[str, str | core.ID]] = [] if CONF_AREA in config: diff --git a/tests/components/esphome/common.yaml b/tests/components/esphome/common.yaml index b2d7bccaa5..db75b08b38 100644 --- a/tests/components/esphome/common.yaml +++ b/tests/components/esphome/common.yaml @@ -2,6 +2,9 @@ esphome: debug_scheduler: true platformio_options: board_build.flash_mode: dio + environment_variables: + TEST_ENV_VAR: "test_value" + BUILD_NUMBER: "12345" area: id: testing_area name: Testing Area From e8998a79c71309164c0d4f4ab8cb0cce6c1b25c0 Mon Sep 17 00:00:00 2001 From: strange_v Date: Tue, 18 Nov 2025 02:32:17 +0100 Subject: [PATCH 0277/1145] [mipi_rgb] Fix GUITION-4848S040 colors (#11709) --- esphome/components/mipi_rgb/mipi_rgb.cpp | 15 ++++++++------- esphome/components/mipi_rgb/models/guition.py | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/esphome/components/mipi_rgb/mipi_rgb.cpp b/esphome/components/mipi_rgb/mipi_rgb.cpp index 00c9c8cbff..080fb08c09 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.cpp +++ b/esphome/components/mipi_rgb/mipi_rgb.cpp @@ -350,6 +350,7 @@ void MipiRgb::dump_config() { "\n Width: %u" "\n Height: %u" "\n Rotation: %d degrees" + "\n PCLK Inverted: %s" "\n HSync Pulse Width: %u" "\n HSync Back Porch: %u" "\n HSync Front Porch: %u" @@ -357,18 +358,18 @@ void MipiRgb::dump_config() { "\n VSync Back Porch: %u" "\n VSync Front Porch: %u" "\n Invert Colors: %s" - "\n Pixel Clock: %dMHz" + "\n Pixel Clock: %uMHz" "\n Reset Pin: %s" "\n DE Pin: %s" "\n PCLK Pin: %s" "\n HSYNC Pin: %s" "\n VSYNC Pin: %s", - this->model_, this->width_, this->height_, this->rotation_, this->hsync_pulse_width_, - this->hsync_back_porch_, this->hsync_front_porch_, this->vsync_pulse_width_, this->vsync_back_porch_, - this->vsync_front_porch_, YESNO(this->invert_colors_), this->pclk_frequency_ / 1000000, - get_pin_name(this->reset_pin_).c_str(), get_pin_name(this->de_pin_).c_str(), - get_pin_name(this->pclk_pin_).c_str(), get_pin_name(this->hsync_pin_).c_str(), - get_pin_name(this->vsync_pin_).c_str()); + this->model_, this->width_, this->height_, this->rotation_, YESNO(this->pclk_inverted_), + this->hsync_pulse_width_, this->hsync_back_porch_, this->hsync_front_porch_, this->vsync_pulse_width_, + this->vsync_back_porch_, this->vsync_front_porch_, YESNO(this->invert_colors_), + (unsigned) (this->pclk_frequency_ / 1000000), get_pin_name(this->reset_pin_).c_str(), + get_pin_name(this->de_pin_).c_str(), get_pin_name(this->pclk_pin_).c_str(), + get_pin_name(this->hsync_pin_).c_str(), get_pin_name(this->vsync_pin_).c_str()); if (this->madctl_ & MADCTL_BGR) { this->dump_pins_(8, 13, "Blue", 0); diff --git a/esphome/components/mipi_rgb/models/guition.py b/esphome/components/mipi_rgb/models/guition.py index da433e686e..915b8beda0 100644 --- a/esphome/components/mipi_rgb/models/guition.py +++ b/esphome/components/mipi_rgb/models/guition.py @@ -11,6 +11,7 @@ st7701s.extend( vsync_pin=17, pclk_pin=21, pclk_frequency="12MHz", + pclk_inverted=False, pixel_mode="18bit", mirror_x=True, mirror_y=True, From 70aa94b8a40845b23f573d425845e11376f24fe3 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 18 Nov 2025 14:27:50 +1000 Subject: [PATCH 0278/1145] [lvgl] Apply scale to spinbox value (#11946) --- esphome/components/lvgl/widgets/spinbox.py | 5 ++++- tests/components/lvgl/lvgl-package.yaml | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/esphome/components/lvgl/widgets/spinbox.py b/esphome/components/lvgl/widgets/spinbox.py index ac23ded723..c6f25e9587 100644 --- a/esphome/components/lvgl/widgets/spinbox.py +++ b/esphome/components/lvgl/widgets/spinbox.py @@ -1,6 +1,7 @@ from esphome import automation import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_RANGE_FROM, CONF_RANGE_TO, CONF_STEP, CONF_VALUE +from esphome.cpp_generator import MockObj from ..automation import action_to_code from ..defines import ( @@ -114,7 +115,9 @@ class SpinboxType(WidgetType): w.obj, digits, digits - config[CONF_DECIMAL_PLACES] ) if (value := config.get(CONF_VALUE)) is not None: - lv.spinbox_set_value(w.obj, await lv_float.process(value)) + lv.spinbox_set_value( + w.obj, MockObj(await lv_float.process(value)) * w.get_scale() + ) def get_scale(self, config): return 10 ** config[CONF_DECIMAL_PLACES] diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index d7c342b16e..fba860a407 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -703,7 +703,9 @@ lvgl: on_value: - lvgl.spinbox.update: id: spinbox_id - value: !lambda return x; + value: !lambda |- + static float yyy = 83.0; + return yyy + .8; - button: styles: spin_button id: spin_up From 93215f17375679b1c847b2a359bfe19533a9dff0 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:40:31 -0500 Subject: [PATCH 0279/1145] [esp32] Fix Arduino build on some ESP32 S2 boards (#11972) --- esphome/components/esp32/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 0f85e585f7..6f577d2926 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -931,6 +931,12 @@ async def to_code(config): add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True) add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True) + # ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency + if get_esp32_variant() == VARIANT_ESP32S2: + cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=1") + cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=0") + cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=0") + cg.add_build_flag("-Wno-nonnull-compare") add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True) From 6db73df649ce1d63c66fd1ead771a31bd5c5f912 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 18 Nov 2025 12:16:18 -0600 Subject: [PATCH 0280/1145] [scheduler] Add defensive nullptr checks and explicit locking requirements (#11974) --- esphome/core/scheduler.cpp | 14 ++++++++------ esphome/core/scheduler.h | 35 +++++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index d2e0f0dab4..09d50ee7c8 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -154,8 +154,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type // For retries, check if there's a cancelled timeout first if (is_retry && name_cstr != nullptr && type == SchedulerItem::TIMEOUT && - (has_cancelled_timeout_in_container_(this->items_, component, name_cstr, /* match_retry= */ true) || - has_cancelled_timeout_in_container_(this->to_add_, component, name_cstr, /* match_retry= */ true))) { + (has_cancelled_timeout_in_container_locked_(this->items_, component, name_cstr, /* match_retry= */ true) || + has_cancelled_timeout_in_container_locked_(this->to_add_, component, name_cstr, /* match_retry= */ true))) { // Skip scheduling - the retry was cancelled #ifdef ESPHOME_DEBUG_SCHEDULER ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item", name_cstr); @@ -556,7 +556,8 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c #ifndef ESPHOME_THREAD_SINGLE // Mark items in defer queue as cancelled (they'll be skipped when processed) if (type == SchedulerItem::TIMEOUT) { - total_cancelled += this->mark_matching_items_removed_(this->defer_queue_, component, name_cstr, type, match_retry); + total_cancelled += + this->mark_matching_items_removed_locked_(this->defer_queue_, component, name_cstr, type, match_retry); } #endif /* not ESPHOME_THREAD_SINGLE */ @@ -565,19 +566,20 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c // (removing the last element doesn't break heap structure) if (!this->items_.empty()) { auto &last_item = this->items_.back(); - if (this->matches_item_(last_item, component, name_cstr, type, match_retry)) { + if (this->matches_item_locked_(last_item, component, name_cstr, type, match_retry)) { this->recycle_item_(std::move(this->items_.back())); this->items_.pop_back(); total_cancelled++; } // For other items in heap, we can only mark for removal (can't remove from middle of heap) - size_t heap_cancelled = this->mark_matching_items_removed_(this->items_, component, name_cstr, type, match_retry); + size_t heap_cancelled = + this->mark_matching_items_removed_locked_(this->items_, component, name_cstr, type, match_retry); total_cancelled += heap_cancelled; this->to_remove_ += heap_cancelled; // Track removals for heap items } // Cancel items in to_add_ - total_cancelled += this->mark_matching_items_removed_(this->to_add_, component, name_cstr, type, match_retry); + total_cancelled += this->mark_matching_items_removed_locked_(this->to_add_, component, name_cstr, type, match_retry); return total_cancelled > 0; } diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index fd16840240..bea1503df0 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -243,8 +243,18 @@ class Scheduler { } // Helper function to check if item matches criteria for cancellation - inline bool HOT matches_item_(const std::unique_ptr &item, Component *component, const char *name_cstr, - SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const { + // IMPORTANT: Must be called with scheduler lock held + inline bool HOT matches_item_locked_(const std::unique_ptr &item, Component *component, + const char *name_cstr, SchedulerItem::Type type, bool match_retry, + bool skip_removed = true) const { + // THREAD SAFETY: Check for nullptr first to prevent LoadProhibited crashes. On multi-threaded + // platforms, items can be moved out of defer_queue_ during processing, leaving nullptr entries. + // PR #11305 added nullptr checks in callers (mark_matching_items_removed_locked_() and + // has_cancelled_timeout_in_container_locked_()), but this check provides defense-in-depth: helper + // functions should be safe regardless of caller behavior. + // Fixes: https://github.com/esphome/esphome/issues/11940 + if (!item) + return false; if (item->component != component || item->type != type || (skip_removed && item->remove) || (match_retry && !item->is_retry)) { return false; @@ -304,8 +314,8 @@ class Scheduler { // SAFETY: Moving out the unique_ptr leaves a nullptr in the vector at defer_queue_front_. // This is intentional and safe because: // 1. The vector is only cleaned up by cleanup_defer_queue_locked_() at the end of this function - // 2. Any code iterating defer_queue_ MUST check for nullptr items (see mark_matching_items_removed_ - // and has_cancelled_timeout_in_container_ in scheduler.h) + // 2. Any code iterating defer_queue_ MUST check for nullptr items (see mark_matching_items_removed_locked_ + // and has_cancelled_timeout_in_container_locked_ in scheduler.h) // 3. The lock protects concurrent access, but the nullptr remains until cleanup item = std::move(this->defer_queue_[this->defer_queue_front_]); this->defer_queue_front_++; @@ -393,10 +403,10 @@ class Scheduler { // Helper to mark matching items in a container as removed // Returns the number of items marked for removal - // IMPORTANT: Caller must hold the scheduler lock before calling this function. + // IMPORTANT: Must be called with scheduler lock held template - size_t mark_matching_items_removed_(Container &container, Component *component, const char *name_cstr, - SchedulerItem::Type type, bool match_retry) { + size_t mark_matching_items_removed_locked_(Container &container, Component *component, const char *name_cstr, + SchedulerItem::Type type, bool match_retry) { size_t count = 0; for (auto &item : container) { // Skip nullptr items (can happen in defer_queue_ when items are being processed) @@ -405,7 +415,7 @@ class Scheduler { // the vector can still contain nullptr items from the processing loop. This check prevents crashes. if (!item) continue; - if (this->matches_item_(item, component, name_cstr, type, match_retry)) { + if (this->matches_item_locked_(item, component, name_cstr, type, match_retry)) { // Mark item for removal (platform-specific) this->set_item_removed_(item.get(), true); count++; @@ -415,9 +425,10 @@ class Scheduler { } // Template helper to check if any item in a container matches our criteria + // IMPORTANT: Must be called with scheduler lock held template - bool has_cancelled_timeout_in_container_(const Container &container, Component *component, const char *name_cstr, - bool match_retry) const { + bool has_cancelled_timeout_in_container_locked_(const Container &container, Component *component, + const char *name_cstr, bool match_retry) const { for (const auto &item : container) { // Skip nullptr items (can happen in defer_queue_ when items are being processed) // The defer_queue_ uses index-based processing: items are std::moved out but left in the @@ -426,8 +437,8 @@ class Scheduler { if (!item) continue; if (is_item_removed_(item.get()) && - this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry, - /* skip_removed= */ false)) { + this->matches_item_locked_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry, + /* skip_removed= */ false)) { return true; } } From f18bc626909d68efde5156f59f85a817bc866971 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:29:40 -0500 Subject: [PATCH 0281/1145] [sfa30] Fix negative temperature values (#11973) --- esphome/components/sfa30/sfa30.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/sfa30/sfa30.cpp b/esphome/components/sfa30/sfa30.cpp index 99709d5fbb..bbe3bcd7d2 100644 --- a/esphome/components/sfa30/sfa30.cpp +++ b/esphome/components/sfa30/sfa30.cpp @@ -73,17 +73,17 @@ void SFA30Component::update() { } if (this->formaldehyde_sensor_ != nullptr) { - const float formaldehyde = raw_data[0] / 5.0f; + const float formaldehyde = static_cast(raw_data[0]) / 5.0f; this->formaldehyde_sensor_->publish_state(formaldehyde); } if (this->humidity_sensor_ != nullptr) { - const float humidity = raw_data[1] / 100.0f; + const float humidity = static_cast(raw_data[1]) / 100.0f; this->humidity_sensor_->publish_state(humidity); } if (this->temperature_sensor_ != nullptr) { - const float temperature = raw_data[2] / 200.0f; + const float temperature = static_cast(raw_data[2]) / 200.0f; this->temperature_sensor_->publish_state(temperature); } From f436f6ee2e3de361cdb5441aa0accf60986abb69 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 18 Nov 2025 13:17:21 -0600 Subject: [PATCH 0282/1145] [wifi] Fix captive portal unusable when WiFi credentials are wrong (#11965) --- esphome/components/captive_portal/__init__.py | 10 +++ .../captive_portal/captive_portal.cpp | 10 ++- .../captive_portal/captive_portal.h | 4 ++ esphome/components/esp32_improv/__init__.py | 6 +- .../esp32_improv/esp32_improv_component.cpp | 1 + .../esp32_improv/esp32_improv_component.h | 1 + .../web_server_idf/web_server_idf.cpp | 15 +++++ .../web_server_idf/web_server_idf.h | 4 ++ esphome/components/wifi/__init__.py | 8 ++- esphome/components/wifi/wifi_component.cpp | 62 +++++++++++++++---- esphome/components/wifi/wifi_component.h | 5 ++ 11 files changed, 111 insertions(+), 15 deletions(-) diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 9bd3ef8a05..25d0a22083 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -72,6 +72,16 @@ def _final_validate(config: ConfigType) -> ConfigType: "Add 'ap:' to your WiFi configuration to enable the captive portal." ) + # Register socket needs for DNS server and additional HTTP connections + # - 1 UDP socket for DNS server + # - 3 additional TCP sockets for captive portal detection probes + configuration requests + # OS captive portal detection makes multiple probe requests that stay in TIME_WAIT. + # Need headroom for actual user configuration requests. + # LRU purging will reclaim idle sockets to prevent exhaustion from repeated attempts. + from esphome.components import socket + + socket.consume_sockets(4, "captive_portal")(config) + return config diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 30438747f2..459ac557c8 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -50,8 +50,8 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { ESP_LOGI(TAG, "Requested WiFi Settings Change:"); ESP_LOGI(TAG, " SSID='%s'", ssid.c_str()); ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str()); - wifi::global_wifi_component->save_wifi_sta(ssid, psk); - wifi::global_wifi_component->start_scanning(); + // Defer save to main loop thread to avoid NVS operations from HTTP thread + this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid, psk); }); request->redirect(ESPHOME_F("/?save")); } @@ -63,6 +63,12 @@ void CaptivePortal::start() { this->base_->init(); if (!this->initialized_) { this->base_->add_handler(this); +#ifdef USE_ESP32 + // Enable LRU socket purging to handle captive portal detection probe bursts + // OS captive portal detection makes many simultaneous HTTP requests which can + // exhaust sockets. LRU purging automatically closes oldest idle connections. + this->base_->get_server()->set_lru_purge_enable(true); +#endif } network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index f48c286f0c..ae9b9dfba0 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -40,6 +40,10 @@ class CaptivePortal : public AsyncWebHandler, public Component { void end() { this->active_ = false; this->disable_loop(); // Stop processing DNS requests +#ifdef USE_ESP32 + // Disable LRU socket purging now that captive portal is done + this->base_->get_server()->set_lru_purge_enable(false); +#endif this->base_->deinit(); if (this->dns_server_ != nullptr) { this->dns_server_->stop(); diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 1a7194da81..2e69d400ca 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -20,6 +20,10 @@ CONF_ON_STOP = "on_stop" CONF_STATUS_INDICATOR = "status_indicator" CONF_WIFI_TIMEOUT = "wifi_timeout" +# Default WiFi timeout - aligned with WiFi component ap_timeout +# Allows sufficient time to try all BSSIDs before starting provisioning mode +DEFAULT_WIFI_TIMEOUT = "90s" + improv_ns = cg.esphome_ns.namespace("improv") Error = improv_ns.enum("Error") @@ -59,7 +63,7 @@ CONFIG_SCHEMA = ( CONF_AUTHORIZED_DURATION, default="1min" ): cv.positive_time_period_milliseconds, cv.Optional( - CONF_WIFI_TIMEOUT, default="1min" + CONF_WIFI_TIMEOUT, default=DEFAULT_WIFI_TIMEOUT ): cv.positive_time_period_milliseconds, cv.Optional(CONF_ON_PROVISIONED): automation.validate_automation( { diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 398b1d4251..0ad54bbb15 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -127,6 +127,7 @@ void ESP32ImprovComponent::loop() { // Set initial state based on whether we have an authorizer this->set_state_(this->get_initial_state_(), false); this->set_error_(improv::ERROR_NONE); + this->should_start_ = false; // Clear flag after starting ESP_LOGD(TAG, "Service started!"); } } diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 989552ea56..8f4cfd7958 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -45,6 +45,7 @@ class ESP32ImprovComponent : public Component, public improv_base::ImprovBase { void start(); void stop(); bool is_active() const { return this->state_ != improv::STATE_STOPPED; } + bool should_start() const { return this->should_start_; } #ifdef USE_ESP32_IMPROV_STATE_CALLBACK void add_on_state_callback(std::function &&callback) { diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index ce91569de2..f5a66f6bd9 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -94,6 +94,18 @@ void AsyncWebServer::end() { } } +void AsyncWebServer::set_lru_purge_enable(bool enable) { + if (this->lru_purge_enable_ == enable) { + return; // No change needed + } + this->lru_purge_enable_ = enable; + // If server is already running, restart it with new config + if (this->server_) { + this->end(); + this->begin(); + } +} + void AsyncWebServer::begin() { if (this->server_) { this->end(); @@ -101,6 +113,8 @@ void AsyncWebServer::begin() { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.server_port = this->port_; config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; }; + // Enable LRU purging if requested (e.g., by captive portal to handle probe bursts) + config.lru_purge_enable = this->lru_purge_enable_; if (httpd_start(&this->server_, &config) == ESP_OK) { const httpd_uri_t handler_get = { .uri = "", @@ -242,6 +256,7 @@ void AsyncWebServerRequest::send(int code, const char *content_type, const char void AsyncWebServerRequest::redirect(const std::string &url) { httpd_resp_set_status(*this, "302 Found"); httpd_resp_set_hdr(*this, "Location", url.c_str()); + httpd_resp_set_hdr(*this, "Connection", "close"); httpd_resp_send(*this, nullptr, 0); } diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index 5ec6fec009..b9f690b462 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -199,9 +199,13 @@ class AsyncWebServer { return *handler; } + void set_lru_purge_enable(bool enable); + httpd_handle_t get_server() { return this->server_; } + protected: uint16_t port_{}; httpd_handle_t server_{}; + bool lru_purge_enable_{false}; static esp_err_t request_handler(httpd_req_t *r); static esp_err_t request_post_handler(httpd_req_t *r); esp_err_t request_handler_(AsyncWebServerRequest *request) const; diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 11bd7798e2..5b3b30e0e9 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -69,6 +69,12 @@ CONF_MIN_AUTH_MODE = "min_auth_mode" # Limited to 127 because selected_sta_index_ is int8_t in C++ MAX_WIFI_NETWORKS = 127 +# Default AP timeout - allows sufficient time to try all BSSIDs during initial connection +# After AP starts, WiFi scanning is skipped to avoid disrupting the AP, so we only +# get best-effort connection attempts. Longer timeout ensures we exhaust all options +# before falling back to AP mode. Aligned with improv wifi_timeout default. +DEFAULT_AP_TIMEOUT = "90s" + wifi_ns = cg.esphome_ns.namespace("wifi") EAPAuth = wifi_ns.struct("EAPAuth") ManualIP = wifi_ns.struct("ManualIP") @@ -177,7 +183,7 @@ CONF_AP_TIMEOUT = "ap_timeout" WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend( { cv.Optional( - CONF_AP_TIMEOUT, default="1min" + CONF_AP_TIMEOUT, default=DEFAULT_AP_TIMEOUT ): cv.positive_time_period_milliseconds, } ) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 880928c3e3..e31d7bbf32 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -199,7 +199,12 @@ static constexpr uint8_t WIFI_RETRY_COUNT_PER_AP = 1; /// Cooldown duration in milliseconds after adapter restart or repeated failures /// Allows WiFi hardware to stabilize before next connection attempt -static constexpr uint32_t WIFI_COOLDOWN_DURATION_MS = 1000; +static constexpr uint32_t WIFI_COOLDOWN_DURATION_MS = 500; + +/// Cooldown duration when fallback AP is active and captive portal may be running +/// Longer interval gives users time to configure WiFi without constant connection attempts +/// While connecting, WiFi can't beacon the AP properly, so needs longer cooldown +static constexpr uint32_t WIFI_COOLDOWN_WITH_AP_ACTIVE_MS = 30000; static constexpr uint8_t get_max_retries_for_phase(WiFiRetryPhase phase) { switch (phase) { @@ -275,7 +280,9 @@ int8_t WiFiComponent::find_next_hidden_sta_(int8_t start_index) { } } - if (!this->ssid_was_seen_in_scan_(sta.get_ssid())) { + // If we didn't scan this cycle, treat all networks as potentially hidden + // Otherwise, only retry networks that weren't seen in the scan + if (!this->did_scan_this_cycle_ || !this->ssid_was_seen_in_scan_(sta.get_ssid())) { ESP_LOGD(TAG, "Hidden candidate " LOG_SECRET("'%s'") " at index %d", sta.get_ssid().c_str(), static_cast(i)); return static_cast(i); } @@ -417,10 +424,6 @@ void WiFiComponent::start() { void WiFiComponent::restart_adapter() { ESP_LOGW(TAG, "Restarting adapter"); this->wifi_mode_(false, {}); - // Enter cooldown state to allow WiFi hardware to stabilize after restart - // Don't set retry_phase_ or num_retried_ here - state machine handles transitions - this->state_ = WIFI_COMPONENT_STATE_COOLDOWN; - this->action_started_ = millis(); this->error_from_callback_ = false; } @@ -441,7 +444,16 @@ void WiFiComponent::loop() { switch (this->state_) { case WIFI_COMPONENT_STATE_COOLDOWN: { this->status_set_warning(LOG_STR("waiting to reconnect")); - if (now - this->action_started_ > WIFI_COOLDOWN_DURATION_MS) { + // Skip cooldown if new credentials were provided while connecting + if (this->skip_cooldown_next_cycle_) { + this->skip_cooldown_next_cycle_ = false; + this->check_connecting_finished(); + break; + } + // Use longer cooldown when captive portal/improv is active to avoid disrupting user config + bool portal_active = this->is_captive_portal_active_() || this->is_esp32_improv_active_(); + uint32_t cooldown_duration = portal_active ? WIFI_COOLDOWN_WITH_AP_ACTIVE_MS : WIFI_COOLDOWN_DURATION_MS; + if (now - this->action_started_ > cooldown_duration) { // After cooldown we either restarted the adapter because of // a failure, or something tried to connect over and over // so we entered cooldown. In both cases we call @@ -495,7 +507,8 @@ void WiFiComponent::loop() { #endif // USE_WIFI_AP #ifdef USE_IMPROV - if (esp32_improv::global_improv_component != nullptr && !esp32_improv::global_improv_component->is_active()) { + if (esp32_improv::global_improv_component != nullptr && !esp32_improv::global_improv_component->is_active() && + !esp32_improv::global_improv_component->should_start()) { if (now - this->last_connected_ > esp32_improv::global_improv_component->get_wifi_timeout()) { if (this->wifi_mode_(true, {})) esp32_improv::global_improv_component->start(); @@ -605,6 +618,8 @@ void WiFiComponent::set_sta(const WiFiAP &ap) { this->init_sta(1); this->add_sta(ap); this->selected_sta_index_ = 0; + // When new credentials are set (e.g., from improv), skip cooldown to retry immediately + this->skip_cooldown_next_cycle_ = true; } WiFiAP WiFiComponent::build_params_for_current_phase_() { @@ -666,6 +681,17 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa sta.set_ssid(ssid); sta.set_password(password); this->set_sta(sta); + + // Trigger connection attempt (exits cooldown if needed, no-op if already connecting/connected) + this->connect_soon_(); +} + +void WiFiComponent::connect_soon_() { + // Only trigger retry if we're in cooldown - if already connecting/connected, do nothing + if (this->state_ == WIFI_COMPONENT_STATE_COOLDOWN) { + ESP_LOGD(TAG, "Exiting cooldown early due to new WiFi credentials"); + this->retry_connect(); + } } void WiFiComponent::start_connecting(const WiFiAP &ap) { @@ -961,6 +987,7 @@ void WiFiComponent::check_scanning_finished() { return; } this->scan_done_ = false; + this->did_scan_this_cycle_ = true; if (this->scan_result_.empty()) { ESP_LOGW(TAG, "No networks found"); @@ -1227,9 +1254,16 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() { return WiFiRetryPhase::RESTARTING_ADAPTER; case WiFiRetryPhase::RESTARTING_ADAPTER: - // After restart, go back to explicit hidden if we went through it initially, otherwise scan - return this->went_through_explicit_hidden_phase_() ? WiFiRetryPhase::EXPLICIT_HIDDEN - : WiFiRetryPhase::SCAN_CONNECTING; + // After restart, go back to explicit hidden if we went through it initially + if (this->went_through_explicit_hidden_phase_()) { + return WiFiRetryPhase::EXPLICIT_HIDDEN; + } + // Skip scanning when captive portal/improv is active to avoid disrupting AP + // Even passive scans can cause brief AP disconnections on ESP32 + if (this->is_captive_portal_active_() || this->is_esp32_improv_active_()) { + return WiFiRetryPhase::RETRY_HIDDEN; + } + return WiFiRetryPhase::SCAN_CONNECTING; } // Should never reach here @@ -1317,6 +1351,12 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) { if (!this->is_captive_portal_active_() && !this->is_esp32_improv_active_()) { this->restart_adapter(); } + // Clear scan flag - we're starting a new retry cycle + this->did_scan_this_cycle_ = false; + // Always enter cooldown after restart (or skip-restart) to allow stabilization + // Use extended cooldown when AP is active to avoid constant scanning that blocks DNS + this->state_ = WIFI_COMPONENT_STATE_COOLDOWN; + this->action_started_ = millis(); // Return true to indicate we should wait (go to COOLDOWN) instead of immediately connecting return true; diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 5023cf3428..2e0a9816c6 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -291,6 +291,7 @@ class WiFiComponent : public Component { void set_passive_scan(bool passive); void save_wifi_sta(const std::string &ssid, const std::string &password); + // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) /// Setup WiFi interface. @@ -424,6 +425,8 @@ class WiFiComponent : public Component { return true; } + void connect_soon_(); + void wifi_loop_(); bool wifi_mode_(optional sta, optional ap); bool wifi_sta_pre_setup_(); @@ -529,6 +532,8 @@ class WiFiComponent : public Component { bool enable_on_boot_; bool got_ipv4_address_{false}; bool keep_scan_results_{false}; + bool did_scan_this_cycle_{false}; + bool skip_cooldown_next_cycle_{false}; // Pointers at the end (naturally aligned) Trigger<> *connect_trigger_{new Trigger<>()}; From 2681a14d05cbc1df1f434b2d5270b5b326e54140 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 19 Nov 2025 09:17:33 +1300 Subject: [PATCH 0283/1145] Bump version to 2025.11.0b4 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 04046d9ce6..c7b2187964 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.11.0b3 +PROJECT_NUMBER = 2025.11.0b4 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 8360531bff..7a3a79f270 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.11.0b3" +__version__ = "2025.11.0b4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 29374837c68d8643c61b35803130b829eca84268 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 18 Nov 2025 17:06:34 -0600 Subject: [PATCH 0284/1145] [wifi, captive_portal, web_server, wifi_info] Use stack allocation for MAC address formatting (#11963) --- esphome/components/captive_portal/captive_portal.cpp | 6 ++++-- esphome/components/web_server/web_server.cpp | 4 ++-- esphome/components/wifi/wifi_component.cpp | 6 ++++-- esphome/components/wifi_info/wifi_info_text_sensor.h | 5 ++++- esphome/core/helpers.cpp | 12 +++++++++--- esphome/core/helpers.h | 5 +++++ 6 files changed, 28 insertions(+), 10 deletions(-) diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 459ac557c8..4eb00835b1 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -13,14 +13,16 @@ static const char *const TAG = "captive_portal"; void CaptivePortal::handle_config(AsyncWebServerRequest *request) { AsyncResponseStream *stream = request->beginResponseStream(ESPHOME_F("application/json")); stream->addHeader(ESPHOME_F("cache-control"), ESPHOME_F("public, max-age=0, must-revalidate")); + char mac_s[18]; + const char *mac_str = get_mac_address_pretty_into_buffer(mac_s); #ifdef USE_ESP8266 stream->print(ESPHOME_F("{\"mac\":\"")); - stream->print(get_mac_address_pretty().c_str()); + stream->print(mac_str); stream->print(ESPHOME_F("\",\"name\":\"")); stream->print(App.get_name().c_str()); stream->print(ESPHOME_F("\",\"aps\":[{}")); #else - stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", get_mac_address_pretty().c_str(), App.get_name().c_str()); + stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", mac_str, App.get_name().c_str()); #endif for (auto &scan : wifi::global_wifi_component->get_scan_result()) { diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 5a8128ba43..cc51463fe7 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -359,8 +359,8 @@ void WebServer::handle_pna_cors_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse(200, ""); response->addHeader(HEADER_CORS_ALLOW_PNA, "true"); response->addHeader(HEADER_PNA_NAME, App.get_name().c_str()); - std::string mac = get_mac_address_pretty(); - response->addHeader(HEADER_PNA_ID, mac.c_str()); + char mac_s[18]; + response->addHeader(HEADER_PNA_ID, get_mac_address_pretty_into_buffer(mac_s)); request->send(response); } #endif diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 30340601fb..6f698bc2a8 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -341,10 +341,11 @@ void WiFiComponent::setup() { } void WiFiComponent::start() { + char mac_s[18]; ESP_LOGCONFIG(TAG, "Starting\n" " Local MAC: %s", - get_mac_address_pretty().c_str()); + get_mac_address_pretty_into_buffer(mac_s)); this->last_connected_ = millis(); uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time()) : 88491487UL; @@ -826,7 +827,8 @@ void WiFiComponent::print_connect_params_() { char bssid_s[18]; format_mac_addr_upper(bssid.data(), bssid_s); - ESP_LOGCONFIG(TAG, " Local MAC: %s", get_mac_address_pretty().c_str()); + char mac_s[18]; + ESP_LOGCONFIG(TAG, " Local MAC: %s", get_mac_address_pretty_into_buffer(mac_s)); if (this->is_disabled()) { ESP_LOGCONFIG(TAG, " Disabled"); return; diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index 04889d6bb3..0814336c43 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -126,7 +126,10 @@ class BSSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor { class MacAddressWifiInfo : public Component, public text_sensor::TextSensor { public: - void setup() override { this->publish_state(get_mac_address_pretty()); } + void setup() override { + char mac_s[18]; + this->publish_state(get_mac_address_pretty_into_buffer(mac_s)); + } void dump_config() override; }; diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 568acb9f1b..50af71649c 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -638,9 +638,8 @@ std::string get_mac_address() { } std::string get_mac_address_pretty() { - uint8_t mac[6]; - get_mac_address_raw(mac); - return format_mac_address_pretty(mac); + char buf[18]; + return std::string(get_mac_address_pretty_into_buffer(buf)); } void get_mac_address_into_buffer(std::span buf) { @@ -649,6 +648,13 @@ void get_mac_address_into_buffer(std::span buf) { format_mac_addr_lower_no_sep(mac, buf.data()); } +const char *get_mac_address_pretty_into_buffer(std::span buf) { + uint8_t mac[6]; + get_mac_address_raw(mac); + format_mac_addr_upper(mac, buf.data()); + return buf.data(); +} + #ifndef USE_ESP32 bool has_custom_mac_address() { return false; } #endif diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 16eab8b8f6..d8c1f4647e 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1052,6 +1052,11 @@ std::string get_mac_address_pretty(); /// Assumes buffer length is 13 (12 digits for hexadecimal representation followed by null terminator). void get_mac_address_into_buffer(std::span buf); +/// Get the device MAC address into the given buffer, in colon-separated uppercase hex notation. +/// Buffer must be exactly 18 bytes (17 for "XX:XX:XX:XX:XX:XX" + null terminator). +/// Returns pointer to the buffer for convenience. +const char *get_mac_address_pretty_into_buffer(std::span buf); + #ifdef USE_ESP32 /// Set the MAC address to use from the provided byte array (6 bytes). void set_mac_address(uint8_t *mac); From 45c994e4de71c99bc0fb8051c8ca9ea484348ee8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 18 Nov 2025 21:56:23 -0600 Subject: [PATCH 0285/1145] [light] Modernize namespace declarations to C++17 syntax (#11986) --- esphome/components/light/addressable_light.cpp | 6 ++---- esphome/components/light/addressable_light.h | 6 ++---- esphome/components/light/addressable_light_effect.h | 6 ++---- esphome/components/light/addressable_light_wrapper.h | 6 ++---- esphome/components/light/automation.cpp | 6 ++---- esphome/components/light/automation.h | 6 ++---- esphome/components/light/base_light_effects.h | 6 ++---- esphome/components/light/color_mode.h | 6 ++---- esphome/components/light/esp_color_correction.cpp | 6 ++---- esphome/components/light/esp_color_correction.h | 6 ++---- esphome/components/light/esp_color_view.h | 6 ++---- esphome/components/light/esp_hsv_color.cpp | 6 ++---- esphome/components/light/esp_hsv_color.h | 6 ++---- esphome/components/light/esp_range_view.cpp | 6 ++---- esphome/components/light/esp_range_view.h | 6 ++---- esphome/components/light/light_call.cpp | 6 ++---- esphome/components/light/light_color_values.h | 6 ++---- esphome/components/light/light_effect.cpp | 6 ++---- esphome/components/light/light_effect.h | 6 ++---- esphome/components/light/light_json_schema.cpp | 6 ++---- esphome/components/light/light_json_schema.h | 6 ++---- esphome/components/light/light_output.cpp | 6 ++---- esphome/components/light/light_output.h | 6 ++---- esphome/components/light/light_state.cpp | 6 ++---- esphome/components/light/light_state.h | 6 ++---- esphome/components/light/light_transformer.h | 6 ++---- esphome/components/light/transformers.h | 6 ++---- 27 files changed, 54 insertions(+), 108 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 5cbdcb0e86..2f6ffc9a38 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -1,8 +1,7 @@ #include "addressable_light.h" #include "esphome/core/log.h" -namespace esphome { -namespace light { +namespace esphome::light { static const char *const TAG = "light.addressable"; @@ -112,5 +111,4 @@ optional AddressableLightTransformer::apply() { return {}; } -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 393cc679bc..2e4b984ce4 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -14,8 +14,7 @@ #include "esphome/components/power_supply/power_supply.h" #endif -namespace esphome { -namespace light { +namespace esphome::light { /// Convert the color information from a `LightColorValues` object to a `Color` object (does not apply brightness). Color color_from_light_color_values(LightColorValues val); @@ -116,5 +115,4 @@ class AddressableLightTransformer : public LightTransformer { Color target_color_{}; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 0847db3770..a85ea4661d 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -7,8 +7,7 @@ #include "esphome/components/light/light_state.h" #include "esphome/components/light/addressable_light.h" -namespace esphome { -namespace light { +namespace esphome::light { inline static int16_t sin16_c(uint16_t theta) { static const uint16_t BASE[] = {0, 6393, 12539, 18204, 23170, 27245, 30273, 32137}; @@ -371,5 +370,4 @@ class AddressableFlickerEffect : public AddressableLightEffect { uint8_t intensity_{13}; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/addressable_light_wrapper.h b/esphome/components/light/addressable_light_wrapper.h index d358502430..8665e62a79 100644 --- a/esphome/components/light/addressable_light_wrapper.h +++ b/esphome/components/light/addressable_light_wrapper.h @@ -3,8 +3,7 @@ #include "esphome/core/component.h" #include "addressable_light.h" -namespace esphome { -namespace light { +namespace esphome::light { class AddressableLightWrapper : public light::AddressableLight { public: @@ -123,5 +122,4 @@ class AddressableLightWrapper : public light::AddressableLight { ColorMode color_mode_{ColorMode::UNKNOWN}; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/automation.cpp b/esphome/components/light/automation.cpp index 8c1785f061..ddac2f9341 100644 --- a/esphome/components/light/automation.cpp +++ b/esphome/components/light/automation.cpp @@ -1,8 +1,7 @@ #include "automation.h" #include "esphome/core/log.h" -namespace esphome { -namespace light { +namespace esphome::light { static const char *const TAG = "light.automation"; @@ -11,5 +10,4 @@ void addressableset_warn_about_scale(const char *field) { field); } -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/automation.h b/esphome/components/light/automation.h index 8899db8bba..9893c15e0c 100644 --- a/esphome/components/light/automation.h +++ b/esphome/components/light/automation.h @@ -4,8 +4,7 @@ #include "light_state.h" #include "addressable_light.h" -namespace esphome { -namespace light { +namespace esphome::light { enum class LimitMode { CLAMP, DO_NOTHING }; @@ -216,5 +215,4 @@ template class AddressableSet : public Action { } }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index 515afc5c59..2eeae574e7 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -6,8 +6,7 @@ #include "esphome/core/helpers.h" #include "light_effect.h" -namespace esphome { -namespace light { +namespace esphome::light { inline static float random_cubic_float() { const float r = random_float() * 2.0f - 1.0f; @@ -235,5 +234,4 @@ class FlickerLightEffect : public LightEffect { float alpha_{}; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/color_mode.h b/esphome/components/light/color_mode.h index aa3448c145..0750ae250d 100644 --- a/esphome/components/light/color_mode.h +++ b/esphome/components/light/color_mode.h @@ -3,8 +3,7 @@ #include #include "esphome/core/finite_set_mask.h" -namespace esphome { -namespace light { +namespace esphome::light { /// Color capabilities are the various outputs that a light has and that can be independently controlled by the user. enum class ColorCapability : uint8_t { @@ -210,5 +209,4 @@ inline bool has_capability(const ColorModeMask &mask, ColorCapability capability return (mask.get_mask() & CAPABILITY_BITMASKS[capability_to_index(capability)]) != 0; } -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/esp_color_correction.cpp b/esphome/components/light/esp_color_correction.cpp index e5e68264cc..1b511a94b2 100644 --- a/esphome/components/light/esp_color_correction.cpp +++ b/esphome/components/light/esp_color_correction.cpp @@ -2,8 +2,7 @@ #include "light_color_values.h" #include "esphome/core/log.h" -namespace esphome { -namespace light { +namespace esphome::light { void ESPColorCorrection::calculate_gamma_table(float gamma) { for (uint16_t i = 0; i < 256; i++) { @@ -23,5 +22,4 @@ void ESPColorCorrection::calculate_gamma_table(float gamma) { } } -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/esp_color_correction.h b/esphome/components/light/esp_color_correction.h index 14c065058c..d275e045b7 100644 --- a/esphome/components/light/esp_color_correction.h +++ b/esphome/components/light/esp_color_correction.h @@ -2,8 +2,7 @@ #include "esphome/core/color.h" -namespace esphome { -namespace light { +namespace esphome::light { class ESPColorCorrection { public: @@ -73,5 +72,4 @@ class ESPColorCorrection { uint8_t local_brightness_{255}; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/esp_color_view.h b/esphome/components/light/esp_color_view.h index 35117e7dd8..440a23e9c9 100644 --- a/esphome/components/light/esp_color_view.h +++ b/esphome/components/light/esp_color_view.h @@ -4,8 +4,7 @@ #include "esp_hsv_color.h" #include "esp_color_correction.h" -namespace esphome { -namespace light { +namespace esphome::light { class ESPColorSettable { public: @@ -106,5 +105,4 @@ class ESPColorView : public ESPColorSettable { const ESPColorCorrection *color_correction_; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/esp_hsv_color.cpp b/esphome/components/light/esp_hsv_color.cpp index 450c2e11ce..07205ea6d0 100644 --- a/esphome/components/light/esp_hsv_color.cpp +++ b/esphome/components/light/esp_hsv_color.cpp @@ -1,7 +1,6 @@ #include "esp_hsv_color.h" -namespace esphome { -namespace light { +namespace esphome::light { Color ESPHSVColor::to_rgb() const { // based on FastLED's hsv rainbow to rgb @@ -70,5 +69,4 @@ Color ESPHSVColor::to_rgb() const { return rgb; } -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/esp_hsv_color.h b/esphome/components/light/esp_hsv_color.h index cdde91c71c..4b54039258 100644 --- a/esphome/components/light/esp_hsv_color.h +++ b/esphome/components/light/esp_hsv_color.h @@ -3,8 +3,7 @@ #include "esphome/core/color.h" #include "esphome/core/helpers.h" -namespace esphome { -namespace light { +namespace esphome::light { struct ESPHSVColor { union { @@ -32,5 +31,4 @@ struct ESPHSVColor { Color to_rgb() const; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/esp_range_view.cpp b/esphome/components/light/esp_range_view.cpp index e1f0a507bd..58d552031a 100644 --- a/esphome/components/light/esp_range_view.cpp +++ b/esphome/components/light/esp_range_view.cpp @@ -1,8 +1,7 @@ #include "esp_range_view.h" #include "addressable_light.h" -namespace esphome { -namespace light { +namespace esphome::light { int32_t HOT interpret_index(int32_t index, int32_t size) { if (index < 0) @@ -92,5 +91,4 @@ ESPRangeView &ESPRangeView::operator=(const ESPRangeView &rhs) { // NOLINT ESPColorView ESPRangeIterator::operator*() const { return this->range_.parent_->get(this->i_); } -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/esp_range_view.h b/esphome/components/light/esp_range_view.h index 07d18af79f..f5e4ebb83f 100644 --- a/esphome/components/light/esp_range_view.h +++ b/esphome/components/light/esp_range_view.h @@ -3,8 +3,7 @@ #include "esp_color_view.h" #include "esp_hsv_color.h" -namespace esphome { -namespace light { +namespace esphome::light { int32_t interpret_index(int32_t index, int32_t size); @@ -76,5 +75,4 @@ class ESPRangeIterator { int32_t i_; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index b15ff84b97..b3bdb16c73 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -4,8 +4,7 @@ #include "esphome/core/log.h" #include "esphome/core/optional.h" -namespace esphome { -namespace light { +namespace esphome::light { static const char *const TAG = "light"; @@ -647,5 +646,4 @@ LightCall &LightCall::set_rgbw(float red, float green, float blue, float white) return *this; } -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index 04d7d1e7d8..bedfad2c35 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -4,8 +4,7 @@ #include "color_mode.h" #include -namespace esphome { -namespace light { +namespace esphome::light { inline static uint8_t to_uint8_scale(float x) { return static_cast(roundf(x * 255.0f)); } @@ -310,5 +309,4 @@ class LightColorValues { ColorMode color_mode_; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/light_effect.cpp b/esphome/components/light/light_effect.cpp index a210b48e5b..81b923f7f9 100644 --- a/esphome/components/light/light_effect.cpp +++ b/esphome/components/light/light_effect.cpp @@ -1,8 +1,7 @@ #include "light_effect.h" #include "light_state.h" -namespace esphome { -namespace light { +namespace esphome::light { uint32_t LightEffect::get_index() const { if (this->state_ == nullptr) { @@ -32,5 +31,4 @@ uint32_t LightEffect::get_index_in_parent_() const { return 0; // Not found } -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/light_effect.h b/esphome/components/light/light_effect.h index d4c2dc3582..aa1f6f7899 100644 --- a/esphome/components/light/light_effect.h +++ b/esphome/components/light/light_effect.h @@ -2,8 +2,7 @@ #include "esphome/core/component.h" -namespace esphome { -namespace light { +namespace esphome::light { class LightState; @@ -55,5 +54,4 @@ class LightEffect { uint32_t get_index_in_parent_() const; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index e754c453b5..1c9b92f504 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -3,8 +3,7 @@ #ifdef USE_JSON -namespace esphome { -namespace light { +namespace esphome::light { // See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema @@ -169,7 +168,6 @@ void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject } } -} // namespace light -} // namespace esphome +} // namespace esphome::light #endif diff --git a/esphome/components/light/light_json_schema.h b/esphome/components/light/light_json_schema.h index c92dd7b655..dac81e32e3 100644 --- a/esphome/components/light/light_json_schema.h +++ b/esphome/components/light/light_json_schema.h @@ -8,8 +8,7 @@ #include "light_call.h" #include "light_state.h" -namespace esphome { -namespace light { +namespace esphome::light { class LightJSONSchema { public: @@ -22,7 +21,6 @@ class LightJSONSchema { static void parse_color_json(LightState &state, LightCall &call, JsonObject root); }; -} // namespace light -} // namespace esphome +} // namespace esphome::light #endif diff --git a/esphome/components/light/light_output.cpp b/esphome/components/light/light_output.cpp index e805a0b694..a86e8e5bf1 100644 --- a/esphome/components/light/light_output.cpp +++ b/esphome/components/light/light_output.cpp @@ -1,12 +1,10 @@ #include "light_output.h" #include "transformers.h" -namespace esphome { -namespace light { +namespace esphome::light { std::unique_ptr LightOutput::create_default_transition() { return make_unique(); } -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/light_output.h b/esphome/components/light/light_output.h index 73ba0371cd..c82d270be8 100644 --- a/esphome/components/light/light_output.h +++ b/esphome/components/light/light_output.h @@ -5,8 +5,7 @@ #include "light_state.h" #include "light_transformer.h" -namespace esphome { -namespace light { +namespace esphome::light { /// Interface to write LightStates to hardware. class LightOutput { @@ -29,5 +28,4 @@ class LightOutput { virtual void write_state(LightState *state) = 0; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 4c253ec5a8..36b2af03a5 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -5,8 +5,7 @@ #include "light_output.h" #include "transformers.h" -namespace esphome { -namespace light { +namespace esphome::light { static const char *const TAG = "light"; @@ -304,5 +303,4 @@ void LightState::save_remote_values_() { this->rtc_.save(&saved); } -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index bf63c0ec27..06519cdc14 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -15,8 +15,7 @@ #include #include -namespace esphome { -namespace light { +namespace esphome::light { class LightOutput; @@ -298,5 +297,4 @@ class LightState : public EntityBase, public Component { LightRestoreMode restore_mode_; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/light_transformer.h b/esphome/components/light/light_transformer.h index a84183c03c..079c2d2ae0 100644 --- a/esphome/components/light/light_transformer.h +++ b/esphome/components/light/light_transformer.h @@ -4,8 +4,7 @@ #include "esphome/core/helpers.h" #include "light_color_values.h" -namespace esphome { -namespace light { +namespace esphome::light { /// Base class for all light color transformers, such as transitions or flashes. class LightTransformer { @@ -59,5 +58,4 @@ class LightTransformer { LightColorValues target_values_; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index 71d41a66d3..a26713b723 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -6,8 +6,7 @@ #include "light_state.h" #include "light_transformer.h" -namespace esphome { -namespace light { +namespace esphome::light { class LightTransitionTransformer : public LightTransformer { public: @@ -118,5 +117,4 @@ class LightFlashTransformer : public LightTransformer { bool begun_lightstate_restore_; }; -} // namespace light -} // namespace esphome +} // namespace esphome::light From b3ef05e5e137fd47ec4f781050af9a571f7689b1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 18 Nov 2025 22:00:39 -0600 Subject: [PATCH 0286/1145] [ld24xx] Modernize namespace declarations to C++17 syntax (#11988) --- esphome/components/ld2410/automation.h | 6 ++---- esphome/components/ld2410/button/factory_reset_button.cpp | 6 ++---- esphome/components/ld2410/button/factory_reset_button.h | 6 ++---- esphome/components/ld2410/button/query_button.cpp | 6 ++---- esphome/components/ld2410/button/query_button.h | 6 ++---- esphome/components/ld2410/button/restart_button.cpp | 6 ++---- esphome/components/ld2410/button/restart_button.h | 6 ++---- esphome/components/ld2410/ld2410.cpp | 6 ++---- esphome/components/ld2410/ld2410.h | 6 ++---- esphome/components/ld2410/number/gate_threshold_number.cpp | 6 ++---- esphome/components/ld2410/number/gate_threshold_number.h | 6 ++---- esphome/components/ld2410/number/light_threshold_number.cpp | 6 ++---- esphome/components/ld2410/number/light_threshold_number.h | 6 ++---- .../ld2410/number/max_distance_timeout_number.cpp | 6 ++---- .../components/ld2410/number/max_distance_timeout_number.h | 6 ++---- esphome/components/ld2410/select/baud_rate_select.cpp | 6 ++---- esphome/components/ld2410/select/baud_rate_select.h | 6 ++---- .../components/ld2410/select/distance_resolution_select.cpp | 6 ++---- .../components/ld2410/select/distance_resolution_select.h | 6 ++---- .../components/ld2410/select/light_out_control_select.cpp | 6 ++---- esphome/components/ld2410/select/light_out_control_select.h | 6 ++---- esphome/components/ld2410/switch/bluetooth_switch.cpp | 6 ++---- esphome/components/ld2410/switch/bluetooth_switch.h | 6 ++---- .../components/ld2410/switch/engineering_mode_switch.cpp | 6 ++---- esphome/components/ld2410/switch/engineering_mode_switch.h | 6 ++---- esphome/components/ld2412/button/factory_reset_button.cpp | 6 ++---- esphome/components/ld2412/button/factory_reset_button.h | 6 ++---- esphome/components/ld2412/button/query_button.cpp | 6 ++---- esphome/components/ld2412/button/query_button.h | 6 ++---- esphome/components/ld2412/button/restart_button.cpp | 6 ++---- esphome/components/ld2412/button/restart_button.h | 6 ++---- .../button/start_dynamic_background_correction_button.cpp | 6 ++---- .../button/start_dynamic_background_correction_button.h | 6 ++---- esphome/components/ld2412/ld2412.cpp | 6 ++---- esphome/components/ld2412/ld2412.h | 6 ++---- esphome/components/ld2412/number/gate_threshold_number.cpp | 6 ++---- esphome/components/ld2412/number/gate_threshold_number.h | 6 ++---- esphome/components/ld2412/number/light_threshold_number.cpp | 6 ++---- esphome/components/ld2412/number/light_threshold_number.h | 6 ++---- .../ld2412/number/max_distance_timeout_number.cpp | 6 ++---- .../components/ld2412/number/max_distance_timeout_number.h | 6 ++---- esphome/components/ld2412/select/baud_rate_select.cpp | 6 ++---- esphome/components/ld2412/select/baud_rate_select.h | 6 ++---- .../components/ld2412/select/distance_resolution_select.cpp | 6 ++---- .../components/ld2412/select/distance_resolution_select.h | 6 ++---- .../components/ld2412/select/light_out_control_select.cpp | 6 ++---- esphome/components/ld2412/select/light_out_control_select.h | 6 ++---- esphome/components/ld2412/switch/bluetooth_switch.cpp | 6 ++---- esphome/components/ld2412/switch/bluetooth_switch.h | 6 ++---- .../components/ld2412/switch/engineering_mode_switch.cpp | 6 ++---- esphome/components/ld2412/switch/engineering_mode_switch.h | 6 ++---- .../ld2420/binary_sensor/ld2420_binary_sensor.cpp | 6 ++---- .../components/ld2420/binary_sensor/ld2420_binary_sensor.h | 6 ++---- esphome/components/ld2420/button/reconfig_buttons.cpp | 6 ++---- esphome/components/ld2420/button/reconfig_buttons.h | 6 ++---- esphome/components/ld2420/ld2420.cpp | 6 ++---- esphome/components/ld2420/ld2420.h | 6 ++---- esphome/components/ld2420/number/gate_config_number.cpp | 6 ++---- esphome/components/ld2420/number/gate_config_number.h | 6 ++---- esphome/components/ld2420/select/operating_mode_select.cpp | 6 ++---- esphome/components/ld2420/select/operating_mode_select.h | 6 ++---- esphome/components/ld2420/sensor/ld2420_sensor.cpp | 6 ++---- esphome/components/ld2420/sensor/ld2420_sensor.h | 6 ++---- .../components/ld2420/text_sensor/ld2420_text_sensor.cpp | 6 ++---- esphome/components/ld2420/text_sensor/ld2420_text_sensor.h | 6 ++---- esphome/components/ld2450/button/factory_reset_button.cpp | 6 ++---- esphome/components/ld2450/button/factory_reset_button.h | 6 ++---- esphome/components/ld2450/button/restart_button.cpp | 6 ++---- esphome/components/ld2450/button/restart_button.h | 6 ++---- esphome/components/ld2450/ld2450.cpp | 6 ++---- esphome/components/ld2450/ld2450.h | 6 ++---- .../components/ld2450/number/presence_timeout_number.cpp | 6 ++---- esphome/components/ld2450/number/presence_timeout_number.h | 6 ++---- esphome/components/ld2450/number/zone_coordinate_number.cpp | 6 ++---- esphome/components/ld2450/number/zone_coordinate_number.h | 6 ++---- esphome/components/ld2450/select/baud_rate_select.cpp | 6 ++---- esphome/components/ld2450/select/baud_rate_select.h | 6 ++---- esphome/components/ld2450/select/zone_type_select.cpp | 6 ++---- esphome/components/ld2450/select/zone_type_select.h | 6 ++---- esphome/components/ld2450/switch/bluetooth_switch.cpp | 6 ++---- esphome/components/ld2450/switch/bluetooth_switch.h | 6 ++---- esphome/components/ld2450/switch/multi_target_switch.cpp | 6 ++---- esphome/components/ld2450/switch/multi_target_switch.h | 6 ++---- esphome/components/ld24xx/ld24xx.h | 6 ++---- 84 files changed, 168 insertions(+), 336 deletions(-) diff --git a/esphome/components/ld2410/automation.h b/esphome/components/ld2410/automation.h index f4f1c197b2..614453b575 100644 --- a/esphome/components/ld2410/automation.h +++ b/esphome/components/ld2410/automation.h @@ -4,8 +4,7 @@ #include "esphome/core/component.h" #include "ld2410.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { template class BluetoothPasswordSetAction : public Action { public: @@ -18,5 +17,4 @@ template class BluetoothPasswordSetAction : public Action LD2410Component *ld2410_comp_; }; -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/button/factory_reset_button.cpp b/esphome/components/ld2410/button/factory_reset_button.cpp index a848b02a9d..0223df7086 100644 --- a/esphome/components/ld2410/button/factory_reset_button.cpp +++ b/esphome/components/ld2410/button/factory_reset_button.cpp @@ -1,9 +1,7 @@ #include "factory_reset_button.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { void FactoryResetButton::press_action() { this->parent_->factory_reset(); } -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/button/factory_reset_button.h b/esphome/components/ld2410/button/factory_reset_button.h index 45bf979033..715a8c4056 100644 --- a/esphome/components/ld2410/button/factory_reset_button.h +++ b/esphome/components/ld2410/button/factory_reset_button.h @@ -3,8 +3,7 @@ #include "esphome/components/button/button.h" #include "../ld2410.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { class FactoryResetButton : public button::Button, public Parented { public: @@ -14,5 +13,4 @@ class FactoryResetButton : public button::Button, public Parentedparent_->read_all_info(); } -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/button/query_button.h b/esphome/components/ld2410/button/query_button.h index c7a47e32d8..7a786901ae 100644 --- a/esphome/components/ld2410/button/query_button.h +++ b/esphome/components/ld2410/button/query_button.h @@ -3,8 +3,7 @@ #include "esphome/components/button/button.h" #include "../ld2410.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { class QueryButton : public button::Button, public Parented { public: @@ -14,5 +13,4 @@ class QueryButton : public button::Button, public Parented { void press_action() override; }; -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/button/restart_button.cpp b/esphome/components/ld2410/button/restart_button.cpp index de0d36c1ef..0d5002d3c6 100644 --- a/esphome/components/ld2410/button/restart_button.cpp +++ b/esphome/components/ld2410/button/restart_button.cpp @@ -1,9 +1,7 @@ #include "restart_button.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); } -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/button/restart_button.h b/esphome/components/ld2410/button/restart_button.h index d00dc05a53..9bf8639a8c 100644 --- a/esphome/components/ld2410/button/restart_button.h +++ b/esphome/components/ld2410/button/restart_button.h @@ -3,8 +3,7 @@ #include "esphome/components/button/button.h" #include "../ld2410.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { class RestartButton : public button::Button, public Parented { public: @@ -14,5 +13,4 @@ class RestartButton : public button::Button, public Parented { void press_action() override; }; -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/ld2410.cpp b/esphome/components/ld2410/ld2410.cpp index 391f2024cd..bb2e4e2f4c 100644 --- a/esphome/components/ld2410/ld2410.cpp +++ b/esphome/components/ld2410/ld2410.cpp @@ -9,8 +9,7 @@ #include "esphome/core/application.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { static const char *const TAG = "ld2410"; @@ -782,5 +781,4 @@ void LD2410Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) { } #endif -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/ld2410.h b/esphome/components/ld2410/ld2410.h index 52cf76b5b6..efe585fb76 100644 --- a/esphome/components/ld2410/ld2410.h +++ b/esphome/components/ld2410/ld2410.h @@ -29,8 +29,7 @@ #include -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { using namespace ld24xx; @@ -133,5 +132,4 @@ class LD2410Component : public Component, public uart::UARTDevice { #endif }; -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/number/gate_threshold_number.cpp b/esphome/components/ld2410/number/gate_threshold_number.cpp index 5d040554d7..65e864a4d7 100644 --- a/esphome/components/ld2410/number/gate_threshold_number.cpp +++ b/esphome/components/ld2410/number/gate_threshold_number.cpp @@ -1,7 +1,6 @@ #include "gate_threshold_number.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { GateThresholdNumber::GateThresholdNumber(uint8_t gate) : gate_(gate) {} @@ -10,5 +9,4 @@ void GateThresholdNumber::control(float value) { this->parent_->set_gate_threshold(this->gate_); } -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/number/gate_threshold_number.h b/esphome/components/ld2410/number/gate_threshold_number.h index 2806ecce63..63491f18d3 100644 --- a/esphome/components/ld2410/number/gate_threshold_number.h +++ b/esphome/components/ld2410/number/gate_threshold_number.h @@ -3,8 +3,7 @@ #include "esphome/components/number/number.h" #include "../ld2410.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { class GateThresholdNumber : public number::Number, public Parented { public: @@ -15,5 +14,4 @@ class GateThresholdNumber : public number::Number, public Parentedpublish_state(value); this->parent_->set_light_out_control(); } -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/number/light_threshold_number.h b/esphome/components/ld2410/number/light_threshold_number.h index 8f014373c0..3c5e433416 100644 --- a/esphome/components/ld2410/number/light_threshold_number.h +++ b/esphome/components/ld2410/number/light_threshold_number.h @@ -3,8 +3,7 @@ #include "esphome/components/number/number.h" #include "../ld2410.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { class LightThresholdNumber : public number::Number, public Parented { public: @@ -14,5 +13,4 @@ class LightThresholdNumber : public number::Number, public Parentedpublish_state(value); this->parent_->set_max_distances_timeout(); } -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/number/max_distance_timeout_number.h b/esphome/components/ld2410/number/max_distance_timeout_number.h index 7d91b4b5fe..35f4cbbfae 100644 --- a/esphome/components/ld2410/number/max_distance_timeout_number.h +++ b/esphome/components/ld2410/number/max_distance_timeout_number.h @@ -3,8 +3,7 @@ #include "esphome/components/number/number.h" #include "../ld2410.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { class MaxDistanceTimeoutNumber : public number::Number, public Parented { public: @@ -14,5 +13,4 @@ class MaxDistanceTimeoutNumber : public number::Number, public Parentedpublish_state(index); this->parent_->set_baud_rate(this->option_at(index)); } -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/select/baud_rate_select.h b/esphome/components/ld2410/select/baud_rate_select.h index 9385c8cf7e..fb1d016b1f 100644 --- a/esphome/components/ld2410/select/baud_rate_select.h +++ b/esphome/components/ld2410/select/baud_rate_select.h @@ -3,8 +3,7 @@ #include "esphome/components/select/select.h" #include "../ld2410.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { class BaudRateSelect : public select::Select, public Parented { public: @@ -14,5 +13,4 @@ class BaudRateSelect : public select::Select, public Parented { void control(size_t index) override; }; -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/select/distance_resolution_select.cpp b/esphome/components/ld2410/select/distance_resolution_select.cpp index 4fc4c5af02..635bf206d3 100644 --- a/esphome/components/ld2410/select/distance_resolution_select.cpp +++ b/esphome/components/ld2410/select/distance_resolution_select.cpp @@ -1,12 +1,10 @@ #include "distance_resolution_select.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { void DistanceResolutionSelect::control(size_t index) { this->publish_state(index); this->parent_->set_distance_resolution(this->option_at(index)); } -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/select/distance_resolution_select.h b/esphome/components/ld2410/select/distance_resolution_select.h index 1a04f843a6..be2389d36e 100644 --- a/esphome/components/ld2410/select/distance_resolution_select.h +++ b/esphome/components/ld2410/select/distance_resolution_select.h @@ -3,8 +3,7 @@ #include "esphome/components/select/select.h" #include "../ld2410.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { class DistanceResolutionSelect : public select::Select, public Parented { public: @@ -14,5 +13,4 @@ class DistanceResolutionSelect : public select::Select, public Parentedpublish_state(index); this->parent_->set_light_out_control(); } -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/select/light_out_control_select.h b/esphome/components/ld2410/select/light_out_control_select.h index e8cd8f1d6a..608c311af4 100644 --- a/esphome/components/ld2410/select/light_out_control_select.h +++ b/esphome/components/ld2410/select/light_out_control_select.h @@ -3,8 +3,7 @@ #include "esphome/components/select/select.h" #include "../ld2410.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { class LightOutControlSelect : public select::Select, public Parented { public: @@ -14,5 +13,4 @@ class LightOutControlSelect : public select::Select, public Parentedpublish_state(state); this->parent_->set_bluetooth(state); } -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/switch/bluetooth_switch.h b/esphome/components/ld2410/switch/bluetooth_switch.h index 35ae1ec0c9..07804e2292 100644 --- a/esphome/components/ld2410/switch/bluetooth_switch.h +++ b/esphome/components/ld2410/switch/bluetooth_switch.h @@ -3,8 +3,7 @@ #include "esphome/components/switch/switch.h" #include "../ld2410.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { class BluetoothSwitch : public switch_::Switch, public Parented { public: @@ -14,5 +13,4 @@ class BluetoothSwitch : public switch_::Switch, public Parented void write_state(bool state) override; }; -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/switch/engineering_mode_switch.cpp b/esphome/components/ld2410/switch/engineering_mode_switch.cpp index 967c87c887..4f2f08b03e 100644 --- a/esphome/components/ld2410/switch/engineering_mode_switch.cpp +++ b/esphome/components/ld2410/switch/engineering_mode_switch.cpp @@ -1,12 +1,10 @@ #include "engineering_mode_switch.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { void EngineeringModeSwitch::write_state(bool state) { this->publish_state(state); this->parent_->set_engineering_mode(state); } -} // namespace ld2410 -} // namespace esphome +} // namespace esphome::ld2410 diff --git a/esphome/components/ld2410/switch/engineering_mode_switch.h b/esphome/components/ld2410/switch/engineering_mode_switch.h index e521200cd6..4dd8e16653 100644 --- a/esphome/components/ld2410/switch/engineering_mode_switch.h +++ b/esphome/components/ld2410/switch/engineering_mode_switch.h @@ -3,8 +3,7 @@ #include "esphome/components/switch/switch.h" #include "../ld2410.h" -namespace esphome { -namespace ld2410 { +namespace esphome::ld2410 { class EngineeringModeSwitch : public switch_::Switch, public Parented { public: @@ -14,5 +13,4 @@ class EngineeringModeSwitch : public switch_::Switch, public Parentedparent_->factory_reset(); } -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/button/factory_reset_button.h b/esphome/components/ld2412/button/factory_reset_button.h index 36a3fffcd5..1ef6b23b80 100644 --- a/esphome/components/ld2412/button/factory_reset_button.h +++ b/esphome/components/ld2412/button/factory_reset_button.h @@ -3,8 +3,7 @@ #include "esphome/components/button/button.h" #include "../ld2412.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { class FactoryResetButton : public button::Button, public Parented { public: @@ -14,5 +13,4 @@ class FactoryResetButton : public button::Button, public Parentedparent_->read_all_info(); } -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/button/query_button.h b/esphome/components/ld2412/button/query_button.h index 595ef6d1e9..373e135802 100644 --- a/esphome/components/ld2412/button/query_button.h +++ b/esphome/components/ld2412/button/query_button.h @@ -3,8 +3,7 @@ #include "esphome/components/button/button.h" #include "../ld2412.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { class QueryButton : public button::Button, public Parented { public: @@ -14,5 +13,4 @@ class QueryButton : public button::Button, public Parented { void press_action() override; }; -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/button/restart_button.cpp b/esphome/components/ld2412/button/restart_button.cpp index aca0d17841..430f6c998f 100644 --- a/esphome/components/ld2412/button/restart_button.cpp +++ b/esphome/components/ld2412/button/restart_button.cpp @@ -1,9 +1,7 @@ #include "restart_button.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); } -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/button/restart_button.h b/esphome/components/ld2412/button/restart_button.h index 5cd582e2a3..80c79f5e7d 100644 --- a/esphome/components/ld2412/button/restart_button.h +++ b/esphome/components/ld2412/button/restart_button.h @@ -3,8 +3,7 @@ #include "esphome/components/button/button.h" #include "../ld2412.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { class RestartButton : public button::Button, public Parented { public: @@ -14,5 +13,4 @@ class RestartButton : public button::Button, public Parented { void press_action() override; }; -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/button/start_dynamic_background_correction_button.cpp b/esphome/components/ld2412/button/start_dynamic_background_correction_button.cpp index 9b37243b82..8ba41a03fb 100644 --- a/esphome/components/ld2412/button/start_dynamic_background_correction_button.cpp +++ b/esphome/components/ld2412/button/start_dynamic_background_correction_button.cpp @@ -2,10 +2,8 @@ #include "restart_button.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { void StartDynamicBackgroundCorrectionButton::press_action() { this->parent_->start_dynamic_background_correction(); } -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/button/start_dynamic_background_correction_button.h b/esphome/components/ld2412/button/start_dynamic_background_correction_button.h index 3af0a8a149..b1f2127896 100644 --- a/esphome/components/ld2412/button/start_dynamic_background_correction_button.h +++ b/esphome/components/ld2412/button/start_dynamic_background_correction_button.h @@ -3,8 +3,7 @@ #include "esphome/components/button/button.h" #include "../ld2412.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { class StartDynamicBackgroundCorrectionButton : public button::Button, public Parented { public: @@ -14,5 +13,4 @@ class StartDynamicBackgroundCorrectionButton : public button::Button, public Par void press_action() override; }; -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/ld2412.cpp b/esphome/components/ld2412/ld2412.cpp index 4f2fd7c2bd..0f6fe62d30 100644 --- a/esphome/components/ld2412/ld2412.cpp +++ b/esphome/components/ld2412/ld2412.cpp @@ -10,8 +10,7 @@ #include "esphome/core/application.h" #include "esphome/core/helpers.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { static const char *const TAG = "ld2412"; @@ -855,5 +854,4 @@ void LD2412Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) { } #endif -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/ld2412.h b/esphome/components/ld2412/ld2412.h index 2bed34bdd8..5dd5e7bcde 100644 --- a/esphome/components/ld2412/ld2412.h +++ b/esphome/components/ld2412/ld2412.h @@ -29,8 +29,7 @@ #include -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { using namespace ld24xx; @@ -137,5 +136,4 @@ class LD2412Component : public Component, public uart::UARTDevice { #endif }; -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/number/gate_threshold_number.cpp b/esphome/components/ld2412/number/gate_threshold_number.cpp index 47f8cd9107..8d12bad115 100644 --- a/esphome/components/ld2412/number/gate_threshold_number.cpp +++ b/esphome/components/ld2412/number/gate_threshold_number.cpp @@ -1,7 +1,6 @@ #include "gate_threshold_number.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { GateThresholdNumber::GateThresholdNumber(uint8_t gate) : gate_(gate) {} @@ -10,5 +9,4 @@ void GateThresholdNumber::control(float value) { this->parent_->set_gate_threshold(); } -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/number/gate_threshold_number.h b/esphome/components/ld2412/number/gate_threshold_number.h index 61d9945a0a..78c2e54d82 100644 --- a/esphome/components/ld2412/number/gate_threshold_number.h +++ b/esphome/components/ld2412/number/gate_threshold_number.h @@ -3,8 +3,7 @@ #include "esphome/components/number/number.h" #include "../ld2412.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { class GateThresholdNumber : public number::Number, public Parented { public: @@ -15,5 +14,4 @@ class GateThresholdNumber : public number::Number, public Parentedpublish_state(value); this->parent_->set_light_out_control(); } -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/number/light_threshold_number.h b/esphome/components/ld2412/number/light_threshold_number.h index d8727d3c98..81fd73111c 100644 --- a/esphome/components/ld2412/number/light_threshold_number.h +++ b/esphome/components/ld2412/number/light_threshold_number.h @@ -3,8 +3,7 @@ #include "esphome/components/number/number.h" #include "../ld2412.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { class LightThresholdNumber : public number::Number, public Parented { public: @@ -14,5 +13,4 @@ class LightThresholdNumber : public number::Number, public Parentedpublish_state(value); this->parent_->set_basic_config(); } -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/number/max_distance_timeout_number.h b/esphome/components/ld2412/number/max_distance_timeout_number.h index af0dcf68c5..c1e947fa19 100644 --- a/esphome/components/ld2412/number/max_distance_timeout_number.h +++ b/esphome/components/ld2412/number/max_distance_timeout_number.h @@ -3,8 +3,7 @@ #include "esphome/components/number/number.h" #include "../ld2412.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { class MaxDistanceTimeoutNumber : public number::Number, public Parented { public: @@ -14,5 +13,4 @@ class MaxDistanceTimeoutNumber : public number::Number, public Parentedpublish_state(index); this->parent_->set_baud_rate(this->option_at(index)); } -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/select/baud_rate_select.h b/esphome/components/ld2412/select/baud_rate_select.h index ffe0329341..4666dd2fa0 100644 --- a/esphome/components/ld2412/select/baud_rate_select.h +++ b/esphome/components/ld2412/select/baud_rate_select.h @@ -3,8 +3,7 @@ #include "esphome/components/select/select.h" #include "../ld2412.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { class BaudRateSelect : public select::Select, public Parented { public: @@ -14,5 +13,4 @@ class BaudRateSelect : public select::Select, public Parented { void control(size_t index) override; }; -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/select/distance_resolution_select.cpp b/esphome/components/ld2412/select/distance_resolution_select.cpp index 5a6f46a071..95b80f87fb 100644 --- a/esphome/components/ld2412/select/distance_resolution_select.cpp +++ b/esphome/components/ld2412/select/distance_resolution_select.cpp @@ -1,12 +1,10 @@ #include "distance_resolution_select.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { void DistanceResolutionSelect::control(size_t index) { this->publish_state(index); this->parent_->set_distance_resolution(this->option_at(index)); } -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/select/distance_resolution_select.h b/esphome/components/ld2412/select/distance_resolution_select.h index 842f63b7b1..d3b7fad2f9 100644 --- a/esphome/components/ld2412/select/distance_resolution_select.h +++ b/esphome/components/ld2412/select/distance_resolution_select.h @@ -3,8 +3,7 @@ #include "esphome/components/select/select.h" #include "../ld2412.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { class DistanceResolutionSelect : public select::Select, public Parented { public: @@ -14,5 +13,4 @@ class DistanceResolutionSelect : public select::Select, public Parentedpublish_state(index); this->parent_->set_light_out_control(); } -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/select/light_out_control_select.h b/esphome/components/ld2412/select/light_out_control_select.h index 7a50970d0d..9f86189878 100644 --- a/esphome/components/ld2412/select/light_out_control_select.h +++ b/esphome/components/ld2412/select/light_out_control_select.h @@ -3,8 +3,7 @@ #include "esphome/components/select/select.h" #include "../ld2412.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { class LightOutControlSelect : public select::Select, public Parented { public: @@ -14,5 +13,4 @@ class LightOutControlSelect : public select::Select, public Parentedpublish_state(state); this->parent_->set_bluetooth(state); } -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/switch/bluetooth_switch.h b/esphome/components/ld2412/switch/bluetooth_switch.h index 730d338d87..0c0d1fa550 100644 --- a/esphome/components/ld2412/switch/bluetooth_switch.h +++ b/esphome/components/ld2412/switch/bluetooth_switch.h @@ -3,8 +3,7 @@ #include "esphome/components/switch/switch.h" #include "../ld2412.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { class BluetoothSwitch : public switch_::Switch, public Parented { public: @@ -14,5 +13,4 @@ class BluetoothSwitch : public switch_::Switch, public Parented void write_state(bool state) override; }; -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/switch/engineering_mode_switch.cpp b/esphome/components/ld2412/switch/engineering_mode_switch.cpp index 29ca0c22a8..28b4e5d9e6 100644 --- a/esphome/components/ld2412/switch/engineering_mode_switch.cpp +++ b/esphome/components/ld2412/switch/engineering_mode_switch.cpp @@ -1,12 +1,10 @@ #include "engineering_mode_switch.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { void EngineeringModeSwitch::write_state(bool state) { this->publish_state(state); this->parent_->set_engineering_mode(state); } -} // namespace ld2412 -} // namespace esphome +} // namespace esphome::ld2412 diff --git a/esphome/components/ld2412/switch/engineering_mode_switch.h b/esphome/components/ld2412/switch/engineering_mode_switch.h index aaa404c673..4e75a8a185 100644 --- a/esphome/components/ld2412/switch/engineering_mode_switch.h +++ b/esphome/components/ld2412/switch/engineering_mode_switch.h @@ -3,8 +3,7 @@ #include "esphome/components/switch/switch.h" #include "../ld2412.h" -namespace esphome { -namespace ld2412 { +namespace esphome::ld2412 { class EngineeringModeSwitch : public switch_::Switch, public Parented { public: @@ -14,5 +13,4 @@ class EngineeringModeSwitch : public switch_::Switch, public Parentedpresence_bsensor_); } -} // namespace ld2420 -} // namespace esphome +} // namespace esphome::ld2420 diff --git a/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.h b/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.h index ee06439090..ec52312f92 100644 --- a/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.h +++ b/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.h @@ -3,8 +3,7 @@ #include "../ld2420.h" #include "esphome/components/binary_sensor/binary_sensor.h" -namespace esphome { -namespace ld2420 { +namespace esphome::ld2420 { class LD2420BinarySensor : public LD2420Listener, public Component, binary_sensor::BinarySensor { public: @@ -21,5 +20,4 @@ class LD2420BinarySensor : public LD2420Listener, public Component, binary_senso binary_sensor::BinarySensor *presence_bsensor_{nullptr}; }; -} // namespace ld2420 -} // namespace esphome +} // namespace esphome::ld2420 diff --git a/esphome/components/ld2420/button/reconfig_buttons.cpp b/esphome/components/ld2420/button/reconfig_buttons.cpp index fb8ec2b5a6..1e748e59b8 100644 --- a/esphome/components/ld2420/button/reconfig_buttons.cpp +++ b/esphome/components/ld2420/button/reconfig_buttons.cpp @@ -4,13 +4,11 @@ static const char *const TAG = "ld2420.button"; -namespace esphome { -namespace ld2420 { +namespace esphome::ld2420 { void LD2420ApplyConfigButton::press_action() { this->parent_->apply_config_action(); } void LD2420RevertConfigButton::press_action() { this->parent_->revert_config_action(); } void LD2420RestartModuleButton::press_action() { this->parent_->restart_module_action(); } void LD2420FactoryResetButton::press_action() { this->parent_->factory_reset_action(); } -} // namespace ld2420 -} // namespace esphome +} // namespace esphome::ld2420 diff --git a/esphome/components/ld2420/button/reconfig_buttons.h b/esphome/components/ld2420/button/reconfig_buttons.h index 4e9e7a3692..72171ef386 100644 --- a/esphome/components/ld2420/button/reconfig_buttons.h +++ b/esphome/components/ld2420/button/reconfig_buttons.h @@ -3,8 +3,7 @@ #include "esphome/components/button/button.h" #include "../ld2420.h" -namespace esphome { -namespace ld2420 { +namespace esphome::ld2420 { class LD2420ApplyConfigButton : public button::Button, public Parented { public: @@ -38,5 +37,4 @@ class LD2420FactoryResetButton : public button::Button, public Parented listeners_{}; }; -} // namespace ld2420 -} // namespace esphome +} // namespace esphome::ld2420 diff --git a/esphome/components/ld2420/number/gate_config_number.cpp b/esphome/components/ld2420/number/gate_config_number.cpp index a373753770..998eed2188 100644 --- a/esphome/components/ld2420/number/gate_config_number.cpp +++ b/esphome/components/ld2420/number/gate_config_number.cpp @@ -4,8 +4,7 @@ static const char *const TAG = "ld2420.number"; -namespace esphome { -namespace ld2420 { +namespace esphome::ld2420 { void LD2420TimeoutNumber::control(float timeout) { this->publish_state(timeout); @@ -69,5 +68,4 @@ void LD2420StillThresholdNumbers::control(float still_threshold) { } } -} // namespace ld2420 -} // namespace esphome +} // namespace esphome::ld2420 diff --git a/esphome/components/ld2420/number/gate_config_number.h b/esphome/components/ld2420/number/gate_config_number.h index 459a8026e3..8a8b9c61b1 100644 --- a/esphome/components/ld2420/number/gate_config_number.h +++ b/esphome/components/ld2420/number/gate_config_number.h @@ -3,8 +3,7 @@ #include "esphome/components/number/number.h" #include "../ld2420.h" -namespace esphome { -namespace ld2420 { +namespace esphome::ld2420 { class LD2420TimeoutNumber : public number::Number, public Parented { public: @@ -74,5 +73,4 @@ class LD2420MoveThresholdNumbers : public number::Number, public Parentedparent_->set_operating_mode(this->option_at(index)); } -} // namespace ld2420 -} // namespace esphome +} // namespace esphome::ld2420 diff --git a/esphome/components/ld2420/select/operating_mode_select.h b/esphome/components/ld2420/select/operating_mode_select.h index f59eb33432..c1b8e0b11b 100644 --- a/esphome/components/ld2420/select/operating_mode_select.h +++ b/esphome/components/ld2420/select/operating_mode_select.h @@ -3,8 +3,7 @@ #include "../ld2420.h" #include "esphome/components/select/select.h" -namespace esphome { -namespace ld2420 { +namespace esphome::ld2420 { class LD2420Select : public Component, public select::Select, public Parented { public: @@ -14,5 +13,4 @@ class LD2420Select : public Component, public select::Select, public Parenteddistance_sensor_); } -} // namespace ld2420 -} // namespace esphome +} // namespace esphome::ld2420 diff --git a/esphome/components/ld2420/sensor/ld2420_sensor.h b/esphome/components/ld2420/sensor/ld2420_sensor.h index 82730d60e3..4849cfa047 100644 --- a/esphome/components/ld2420/sensor/ld2420_sensor.h +++ b/esphome/components/ld2420/sensor/ld2420_sensor.h @@ -3,8 +3,7 @@ #include "../ld2420.h" #include "esphome/components/sensor/sensor.h" -namespace esphome { -namespace ld2420 { +namespace esphome::ld2420 { class LD2420Sensor : public LD2420Listener, public Component, sensor::Sensor { public: @@ -30,5 +29,4 @@ class LD2420Sensor : public LD2420Listener, public Component, sensor::Sensor { std::vector energy_sensors_ = std::vector(TOTAL_GATES); }; -} // namespace ld2420 -} // namespace esphome +} // namespace esphome::ld2420 diff --git a/esphome/components/ld2420/text_sensor/ld2420_text_sensor.cpp b/esphome/components/ld2420/text_sensor/ld2420_text_sensor.cpp index f647a36936..f7b016c9d9 100644 --- a/esphome/components/ld2420/text_sensor/ld2420_text_sensor.cpp +++ b/esphome/components/ld2420/text_sensor/ld2420_text_sensor.cpp @@ -2,8 +2,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace ld2420 { +namespace esphome::ld2420 { static const char *const TAG = "ld2420.text_sensor"; @@ -12,5 +11,4 @@ void LD2420TextSensor::dump_config() { LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_); } -} // namespace ld2420 -} // namespace esphome +} // namespace esphome::ld2420 diff --git a/esphome/components/ld2420/text_sensor/ld2420_text_sensor.h b/esphome/components/ld2420/text_sensor/ld2420_text_sensor.h index 073ddd5d0f..1932eaaf69 100644 --- a/esphome/components/ld2420/text_sensor/ld2420_text_sensor.h +++ b/esphome/components/ld2420/text_sensor/ld2420_text_sensor.h @@ -3,8 +3,7 @@ #include "../ld2420.h" #include "esphome/components/text_sensor/text_sensor.h" -namespace esphome { -namespace ld2420 { +namespace esphome::ld2420 { class LD2420TextSensor : public LD2420Listener, public Component, text_sensor::TextSensor { public: @@ -20,5 +19,4 @@ class LD2420TextSensor : public LD2420Listener, public Component, text_sensor::T text_sensor::TextSensor *fw_version_text_sensor_{nullptr}; }; -} // namespace ld2420 -} // namespace esphome +} // namespace esphome::ld2420 diff --git a/esphome/components/ld2450/button/factory_reset_button.cpp b/esphome/components/ld2450/button/factory_reset_button.cpp index bcac7ada2f..7a8eb5b0dd 100644 --- a/esphome/components/ld2450/button/factory_reset_button.cpp +++ b/esphome/components/ld2450/button/factory_reset_button.cpp @@ -1,9 +1,7 @@ #include "factory_reset_button.h" -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { void FactoryResetButton::press_action() { this->parent_->factory_reset(); } -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/button/factory_reset_button.h b/esphome/components/ld2450/button/factory_reset_button.h index 8e80347119..392fc67ffd 100644 --- a/esphome/components/ld2450/button/factory_reset_button.h +++ b/esphome/components/ld2450/button/factory_reset_button.h @@ -3,8 +3,7 @@ #include "esphome/components/button/button.h" #include "../ld2450.h" -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { class FactoryResetButton : public button::Button, public Parented { public: @@ -14,5 +13,4 @@ class FactoryResetButton : public button::Button, public Parentedparent_->restart_and_read_all_info(); } -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/button/restart_button.h b/esphome/components/ld2450/button/restart_button.h index a44ae5a4d2..9219011f8b 100644 --- a/esphome/components/ld2450/button/restart_button.h +++ b/esphome/components/ld2450/button/restart_button.h @@ -3,8 +3,7 @@ #include "esphome/components/button/button.h" #include "../ld2450.h" -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { class RestartButton : public button::Button, public Parented { public: @@ -14,5 +13,4 @@ class RestartButton : public button::Button, public Parented { void press_action() override; }; -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/ld2450.cpp b/esphome/components/ld2450/ld2450.cpp index 8e5287aec7..e69ef31d4f 100644 --- a/esphome/components/ld2450/ld2450.cpp +++ b/esphome/components/ld2450/ld2450.cpp @@ -13,8 +13,7 @@ #include #include -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { static const char *const TAG = "ld2450"; @@ -939,5 +938,4 @@ float LD2450Component::restore_from_flash_() { } #endif -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/ld2450.h b/esphome/components/ld2450/ld2450.h index 44b63be444..b94c3cac37 100644 --- a/esphome/components/ld2450/ld2450.h +++ b/esphome/components/ld2450/ld2450.h @@ -31,8 +31,7 @@ #include -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { using namespace ld24xx; @@ -193,5 +192,4 @@ class LD2450Component : public Component, public uart::UARTDevice { #endif }; -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/number/presence_timeout_number.cpp b/esphome/components/ld2450/number/presence_timeout_number.cpp index ecfe71f484..19a1ada0d7 100644 --- a/esphome/components/ld2450/number/presence_timeout_number.cpp +++ b/esphome/components/ld2450/number/presence_timeout_number.cpp @@ -1,12 +1,10 @@ #include "presence_timeout_number.h" -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { void PresenceTimeoutNumber::control(float value) { this->publish_state(value); this->parent_->set_presence_timeout(); } -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/number/presence_timeout_number.h b/esphome/components/ld2450/number/presence_timeout_number.h index b18699792f..09c8afca55 100644 --- a/esphome/components/ld2450/number/presence_timeout_number.h +++ b/esphome/components/ld2450/number/presence_timeout_number.h @@ -3,8 +3,7 @@ #include "esphome/components/number/number.h" #include "../ld2450.h" -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { class PresenceTimeoutNumber : public number::Number, public Parented { public: @@ -14,5 +13,4 @@ class PresenceTimeoutNumber : public number::Number, public Parentedparent_->set_zone_coordinate(this->zone_); } -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/number/zone_coordinate_number.h b/esphome/components/ld2450/number/zone_coordinate_number.h index 72b83889c4..f5a389d712 100644 --- a/esphome/components/ld2450/number/zone_coordinate_number.h +++ b/esphome/components/ld2450/number/zone_coordinate_number.h @@ -3,8 +3,7 @@ #include "esphome/components/number/number.h" #include "../ld2450.h" -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { class ZoneCoordinateNumber : public number::Number, public Parented { public: @@ -15,5 +14,4 @@ class ZoneCoordinateNumber : public number::Number, public Parentedpublish_state(index); this->parent_->set_baud_rate(this->option_at(index)); } -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/select/baud_rate_select.h b/esphome/components/ld2450/select/baud_rate_select.h index 22810d5f13..cb53118170 100644 --- a/esphome/components/ld2450/select/baud_rate_select.h +++ b/esphome/components/ld2450/select/baud_rate_select.h @@ -3,8 +3,7 @@ #include "esphome/components/select/select.h" #include "../ld2450.h" -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { class BaudRateSelect : public select::Select, public Parented { public: @@ -14,5 +13,4 @@ class BaudRateSelect : public select::Select, public Parented { void control(size_t index) override; }; -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/select/zone_type_select.cpp b/esphome/components/ld2450/select/zone_type_select.cpp index 1111428c7c..39642b99ad 100644 --- a/esphome/components/ld2450/select/zone_type_select.cpp +++ b/esphome/components/ld2450/select/zone_type_select.cpp @@ -1,12 +1,10 @@ #include "zone_type_select.h" -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { void ZoneTypeSelect::control(size_t index) { this->publish_state(index); this->parent_->set_zone_type(this->option_at(index)); } -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/select/zone_type_select.h b/esphome/components/ld2450/select/zone_type_select.h index fc95ec1021..566346eb48 100644 --- a/esphome/components/ld2450/select/zone_type_select.h +++ b/esphome/components/ld2450/select/zone_type_select.h @@ -3,8 +3,7 @@ #include "esphome/components/select/select.h" #include "../ld2450.h" -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { class ZoneTypeSelect : public select::Select, public Parented { public: @@ -14,5 +13,4 @@ class ZoneTypeSelect : public select::Select, public Parented { void control(size_t index) override; }; -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/switch/bluetooth_switch.cpp b/esphome/components/ld2450/switch/bluetooth_switch.cpp index fa0d4fb06a..0e19a3e6c6 100644 --- a/esphome/components/ld2450/switch/bluetooth_switch.cpp +++ b/esphome/components/ld2450/switch/bluetooth_switch.cpp @@ -1,12 +1,10 @@ #include "bluetooth_switch.h" -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { void BluetoothSwitch::write_state(bool state) { this->publish_state(state); this->parent_->set_bluetooth(state); } -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/switch/bluetooth_switch.h b/esphome/components/ld2450/switch/bluetooth_switch.h index 3c1c4f755c..3d48a89b57 100644 --- a/esphome/components/ld2450/switch/bluetooth_switch.h +++ b/esphome/components/ld2450/switch/bluetooth_switch.h @@ -3,8 +3,7 @@ #include "esphome/components/switch/switch.h" #include "../ld2450.h" -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { class BluetoothSwitch : public switch_::Switch, public Parented { public: @@ -14,5 +13,4 @@ class BluetoothSwitch : public switch_::Switch, public Parented void write_state(bool state) override; }; -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/switch/multi_target_switch.cpp b/esphome/components/ld2450/switch/multi_target_switch.cpp index a163e29fc5..0b1cb04a68 100644 --- a/esphome/components/ld2450/switch/multi_target_switch.cpp +++ b/esphome/components/ld2450/switch/multi_target_switch.cpp @@ -1,12 +1,10 @@ #include "multi_target_switch.h" -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { void MultiTargetSwitch::write_state(bool state) { this->publish_state(state); this->parent_->set_multi_target(state); } -} // namespace ld2450 -} // namespace esphome +} // namespace esphome::ld2450 diff --git a/esphome/components/ld2450/switch/multi_target_switch.h b/esphome/components/ld2450/switch/multi_target_switch.h index ca6253588d..739f308cce 100644 --- a/esphome/components/ld2450/switch/multi_target_switch.h +++ b/esphome/components/ld2450/switch/multi_target_switch.h @@ -3,8 +3,7 @@ #include "esphome/components/switch/switch.h" #include "../ld2450.h" -namespace esphome { -namespace ld2450 { +namespace esphome::ld2450 { class MultiTargetSwitch : public switch_::Switch, public Parented { public: @@ -14,5 +13,4 @@ class MultiTargetSwitch : public switch_::Switch, public Parented> 8) #define lowbyte(val) (uint8_t)((val) &0xff) -namespace esphome { -namespace ld24xx { +namespace esphome::ld24xx { static const char *const UNKNOWN_MAC = "unknown"; static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X"; @@ -83,5 +82,4 @@ template class SensorWithDedup { Deduplicator publish_dedup; }; #endif -} // namespace ld24xx -} // namespace esphome +} // namespace esphome::ld24xx From 100ea46f03eb45b10beed6f8d36133f9d2635e70 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 18 Nov 2025 23:19:54 -0600 Subject: [PATCH 0287/1145] [tests] Fix SNTP time ID conflicts in component tests for grouped testing (#11990) --- tests/components/lvgl/common.yaml | 4 ++-- tests/components/lvgl/lvgl-package.yaml | 6 +++--- tests/components/mqtt/common.yaml | 1 + tests/components/uptime/common.yaml | 1 + tests/components/wireguard/common.yaml | 2 ++ 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/components/lvgl/common.yaml b/tests/components/lvgl/common.yaml index c70dd7568d..652ae7e7a1 100644 --- a/tests/components/lvgl/common.yaml +++ b/tests/components/lvgl/common.yaml @@ -115,8 +115,8 @@ wifi: password: PASSWORD123 time: - platform: sntp - id: time_id + - platform: sntp + id: sntp_time text: - id: lvgl_text diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index eabceff9d9..5839643638 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -478,19 +478,19 @@ lvgl: id: hello_label text: time_format: "%c" - time: time_id + time: sntp_time - lvgl.label.update: id: hello_label text: time_format: "%c" - time: !lambda return id(time_id).now(); + time: !lambda return id(sntp_time).now(); - lvgl.label.update: id: hello_label text: time_format: "%c" time: !lambda |- ESP_LOGD("label", "multi-line lambda"); - return id(time_id).now(); + return id(sntp_time).now(); on_value: logger.log: format: "state now %d" diff --git a/tests/components/mqtt/common.yaml b/tests/components/mqtt/common.yaml index 3f1b83bb01..284ac30337 100644 --- a/tests/components/mqtt/common.yaml +++ b/tests/components/mqtt/common.yaml @@ -4,6 +4,7 @@ wifi: time: - platform: sntp + id: sntp_time mqtt: broker: "192.168.178.84" diff --git a/tests/components/uptime/common.yaml b/tests/components/uptime/common.yaml index 86b764e7ff..279258c670 100644 --- a/tests/components/uptime/common.yaml +++ b/tests/components/uptime/common.yaml @@ -3,6 +3,7 @@ wifi: time: - platform: sntp + id: sntp_time sensor: - platform: uptime diff --git a/tests/components/wireguard/common.yaml b/tests/components/wireguard/common.yaml index cd7ab1075e..342ffa32f6 100644 --- a/tests/components/wireguard/common.yaml +++ b/tests/components/wireguard/common.yaml @@ -4,8 +4,10 @@ wifi: time: - platform: sntp + id: sntp_time wireguard: + time_id: sntp_time address: 172.16.34.100 netmask: 255.255.255.0 # NEVER use the following keys for your VPN -- they are now public! From f2b10ad132dcc4118cd6e0ecbead9da746863b77 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:12:34 -0500 Subject: [PATCH 0288/1145] [text_sensor] Fix infinite loop in substitute filter (#11989) Co-authored-by: J. Nick Koston --- esphome/components/text_sensor/filter.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp index a242b43b1c..40a37febee 100644 --- a/esphome/components/text_sensor/filter.cpp +++ b/esphome/components/text_sensor/filter.cpp @@ -66,10 +66,14 @@ SubstituteFilter::SubstituteFilter(const std::initializer_list &su : substitutions_(substitutions) {} optional SubstituteFilter::new_value(std::string value) { - std::size_t pos; for (const auto &sub : this->substitutions_) { - while ((pos = value.find(sub.from)) != std::string::npos) + std::size_t pos = 0; + while ((pos = value.find(sub.from, pos)) != std::string::npos) { value.replace(pos, sub.from.size(), sub.to); + // Advance past the replacement to avoid infinite loop when + // the replacement contains the search pattern (e.g., f -> foo) + pos += sub.to.size(); + } } return value; } From 73bc5252a1467a7530b921eb9be16ab6819f3038 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:12:57 -0500 Subject: [PATCH 0289/1145] [wifi] Fix positive RSSI values on 8266 (#11994) --- esphome/components/wifi/wifi_component_esp8266.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index a543628e27..274a505db2 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -872,7 +872,13 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } -int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } +int8_t WiFiComponent::wifi_rssi() { + if (WiFi.status() != WL_CONNECTED) + return WIFI_RSSI_DISCONNECTED; + int8_t rssi = WiFi.RSSI(); + // Values >= 31 are error codes per NONOS SDK API, not valid RSSI readings + return rssi >= 31 ? WIFI_RSSI_DISCONNECTED : rssi; +} int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; } From 61cef0a75c4b46df6e0d7761c78dbedfdb8aee72 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:58:47 -0500 Subject: [PATCH 0290/1145] [api] Fix format warnings in dump (#11999) --- esphome/components/api/api_pb2_dump.cpp | 2 +- script/api_protobuf/api_protobuf.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index d9662483bf..127ef44cd8 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -66,7 +66,7 @@ static void dump_field(std::string &out, const char *field_name, float value, in static void dump_field(std::string &out, const char *field_name, uint64_t value, int indent = 2) { char buffer[64]; append_field_prefix(out, field_name, indent); - snprintf(buffer, 64, "%llu", value); + snprintf(buffer, 64, "%" PRIu64, value); append_with_newline(out, buffer); } diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 3b756095a1..b07a249c8d 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -462,7 +462,7 @@ class Int64Type(TypeInfo): wire_type = WireType.VARINT # Uses wire type 0 def dump(self, name: str) -> str: - o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%" PRId64, {name});\n' o += "out.append(buffer);" return o @@ -482,7 +482,7 @@ class UInt64Type(TypeInfo): wire_type = WireType.VARINT # Uses wire type 0 def dump(self, name: str) -> str: - o = f'snprintf(buffer, sizeof(buffer), "%llu", {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%" PRIu64, {name});\n' o += "out.append(buffer);" return o @@ -522,7 +522,7 @@ class Fixed64Type(TypeInfo): wire_type = WireType.FIXED64 # Uses wire type 1 def dump(self, name: str) -> str: - o = f'snprintf(buffer, sizeof(buffer), "%llu", {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%" PRIu64, {name});\n' o += "out.append(buffer);" return o @@ -1106,7 +1106,7 @@ class SFixed64Type(TypeInfo): wire_type = WireType.FIXED64 # Uses wire type 1 def dump(self, name: str) -> str: - o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%" PRId64, {name});\n' o += "out.append(buffer);" return o @@ -1150,7 +1150,7 @@ class SInt64Type(TypeInfo): wire_type = WireType.VARINT # Uses wire type 0 def dump(self, name: str) -> str: - o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%" PRId64, {name});\n' o += "out.append(buffer);" return o @@ -2546,7 +2546,7 @@ static void dump_field(std::string &out, const char *field_name, float value, in static void dump_field(std::string &out, const char *field_name, uint64_t value, int indent = 2) { char buffer[64]; append_field_prefix(out, field_name, indent); - snprintf(buffer, 64, "%llu", value); + snprintf(buffer, 64, "%" PRIu64, value); append_with_newline(out, buffer); } From 8804bc28154abb1601cd029971cc0abe12a2f49a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 19 Nov 2025 12:58:33 -0600 Subject: [PATCH 0291/1145] [web_server_idf] Fix pbuf_free crash by moving shutdown before close (#11995) --- .../web_server_idf/web_server_idf.cpp | 41 ++++++++++++++----- .../web_server_idf/web_server_idf.h | 1 + 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index f5a66f6bd9..c910ed06c5 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -87,6 +87,29 @@ int nonblocking_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_ } } // namespace +void AsyncWebServer::safe_close_with_shutdown(httpd_handle_t hd, int sockfd) { + // CRITICAL: Shut down receive BEFORE closing to prevent lwIP race conditions + // + // The race condition occurs because close() initiates lwIP teardown while + // the TCP/IP thread can still receive packets, causing assertions when + // recv_tcp() sees partially-torn-down state. + // + // By shutting down receive first, we tell lwIP to stop accepting new data BEFORE + // the teardown begins, eliminating the race window. We only shutdown RD (not RDWR) + // to allow the FIN packet to be sent cleanly during close(). + // + // Note: This function may be called with an already-closed socket if the network + // stack closed it. In that case, shutdown() will fail but close() is safe to call. + // + // See: https://github.com/esphome/esphome-webserver/issues/163 + + // Attempt shutdown - ignore errors as socket may already be closed + shutdown(sockfd, SHUT_RD); + + // Always close - safe even if socket is already closed by network stack + close(sockfd); +} + void AsyncWebServer::end() { if (this->server_) { httpd_stop(this->server_); @@ -115,6 +138,8 @@ void AsyncWebServer::begin() { config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; }; // Enable LRU purging if requested (e.g., by captive portal to handle probe bursts) config.lru_purge_enable = this->lru_purge_enable_; + // Use custom close function that shuts down before closing to prevent lwIP race conditions + config.close_fn = AsyncWebServer::safe_close_with_shutdown; if (httpd_start(&this->server_, &config) == ESP_OK) { const httpd_uri_t handler_get = { .uri = "", @@ -505,17 +530,11 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest * void AsyncEventSourceResponse::destroy(void *ptr) { auto *rsp = static_cast(ptr); int fd = rsp->fd_.exchange(0); // Atomically get and clear fd - - if (fd > 0) { - ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd); - // Immediately shut down the socket to prevent lwIP from delivering more data - // This prevents "recv_tcp: recv for wrong pcb!" assertions when the TCP stack - // tries to deliver queued data after the session is marked as dead - // See: https://github.com/esphome/esphome/issues/11936 - shutdown(fd, SHUT_RDWR); - // Note: We don't close() the socket - httpd owns it and will close it - } - // Session will be cleaned up in the main loop to avoid race conditions + ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd); + // Mark as dead - will be cleaned up in the main loop + // Note: We don't delete or remove from set here to avoid race conditions + // httpd will call our custom close_fn (safe_close_with_shutdown) which handles + // shutdown() before close() to prevent lwIP race conditions } // helper for allowing only unique entries in the queue diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index b9f690b462..a139e9e4df 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -209,6 +209,7 @@ class AsyncWebServer { static esp_err_t request_handler(httpd_req_t *r); static esp_err_t request_post_handler(httpd_req_t *r); esp_err_t request_handler_(AsyncWebServerRequest *request) const; + static void safe_close_with_shutdown(httpd_handle_t hd, int sockfd); #ifdef USE_WEBSERVER_OTA esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type); #endif From b02b07ffafaf7dbf0529460638608f69d095a82b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:11:45 +1300 Subject: [PATCH 0292/1145] [epaper_spi] Add basic `7.3in-Spectra-E6` model (#12001) --- esphome/components/epaper_spi/display.py | 2 +- esphome/components/epaper_spi/models/spectra_e6.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/esphome/components/epaper_spi/display.py b/esphome/components/epaper_spi/display.py index 9ff393b397..182c37ba40 100644 --- a/esphome/components/epaper_spi/display.py +++ b/esphome/components/epaper_spi/display.py @@ -102,7 +102,7 @@ def customise_schema(config): """ config = cv.Schema( { - cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True), + cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True, space="-"), }, extra=cv.ALLOW_EXTRA, )(config) diff --git a/esphome/components/epaper_spi/models/spectra_e6.py b/esphome/components/epaper_spi/models/spectra_e6.py index 9f0b673d69..42a5a7da72 100644 --- a/esphome/components/epaper_spi/models/spectra_e6.py +++ b/esphome/components/epaper_spi/models/spectra_e6.py @@ -32,11 +32,15 @@ class SpectraE6(EpaperModel): spectra_e6 = SpectraE6("spectra-e6") -spectra_e6.extend( - "Seeed-reTerminal-E1002", +spectra_e6_7p3 = spectra_e6.extend( + "7.3in-Spectra-E6", width=800, height=480, data_rate="20MHz", +) + +spectra_e6_7p3.extend( + "Seeed-reTerminal-E1002", cs_pin=10, dc_pin=11, reset_pin=12, From 13b875c763b990a6d56104bd619f391c09ccceec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 18 Nov 2025 23:19:54 -0600 Subject: [PATCH 0293/1145] [tests] Fix SNTP time ID conflicts in component tests for grouped testing (#11990) --- tests/components/lvgl/common.yaml | 4 ++-- tests/components/lvgl/lvgl-package.yaml | 6 +++--- tests/components/mqtt/common.yaml | 1 + tests/components/uptime/common.yaml | 1 + tests/components/wireguard/common.yaml | 2 ++ 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/components/lvgl/common.yaml b/tests/components/lvgl/common.yaml index c70dd7568d..652ae7e7a1 100644 --- a/tests/components/lvgl/common.yaml +++ b/tests/components/lvgl/common.yaml @@ -115,8 +115,8 @@ wifi: password: PASSWORD123 time: - platform: sntp - id: time_id + - platform: sntp + id: sntp_time text: - id: lvgl_text diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index fba860a407..cb5b6f59b1 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -478,19 +478,19 @@ lvgl: id: hello_label text: time_format: "%c" - time: time_id + time: sntp_time - lvgl.label.update: id: hello_label text: time_format: "%c" - time: !lambda return id(time_id).now(); + time: !lambda return id(sntp_time).now(); - lvgl.label.update: id: hello_label text: time_format: "%c" time: !lambda |- ESP_LOGD("label", "multi-line lambda"); - return id(time_id).now(); + return id(sntp_time).now(); on_value: logger.log: format: "state now %d" diff --git a/tests/components/mqtt/common.yaml b/tests/components/mqtt/common.yaml index 3f1b83bb01..284ac30337 100644 --- a/tests/components/mqtt/common.yaml +++ b/tests/components/mqtt/common.yaml @@ -4,6 +4,7 @@ wifi: time: - platform: sntp + id: sntp_time mqtt: broker: "192.168.178.84" diff --git a/tests/components/uptime/common.yaml b/tests/components/uptime/common.yaml index 86b764e7ff..279258c670 100644 --- a/tests/components/uptime/common.yaml +++ b/tests/components/uptime/common.yaml @@ -3,6 +3,7 @@ wifi: time: - platform: sntp + id: sntp_time sensor: - platform: uptime diff --git a/tests/components/wireguard/common.yaml b/tests/components/wireguard/common.yaml index cd7ab1075e..342ffa32f6 100644 --- a/tests/components/wireguard/common.yaml +++ b/tests/components/wireguard/common.yaml @@ -4,8 +4,10 @@ wifi: time: - platform: sntp + id: sntp_time wireguard: + time_id: sntp_time address: 172.16.34.100 netmask: 255.255.255.0 # NEVER use the following keys for your VPN -- they are now public! From 7ef4b4f3d9d6cb8ce5414985ae59538647a3d438 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:12:34 -0500 Subject: [PATCH 0294/1145] [text_sensor] Fix infinite loop in substitute filter (#11989) Co-authored-by: J. Nick Koston --- esphome/components/text_sensor/filter.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp index a242b43b1c..40a37febee 100644 --- a/esphome/components/text_sensor/filter.cpp +++ b/esphome/components/text_sensor/filter.cpp @@ -66,10 +66,14 @@ SubstituteFilter::SubstituteFilter(const std::initializer_list &su : substitutions_(substitutions) {} optional SubstituteFilter::new_value(std::string value) { - std::size_t pos; for (const auto &sub : this->substitutions_) { - while ((pos = value.find(sub.from)) != std::string::npos) + std::size_t pos = 0; + while ((pos = value.find(sub.from, pos)) != std::string::npos) { value.replace(pos, sub.from.size(), sub.to); + // Advance past the replacement to avoid infinite loop when + // the replacement contains the search pattern (e.g., f -> foo) + pos += sub.to.size(); + } } return value; } From 0a224f919b3889651db230fbd047cf05f4996cdf Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:12:57 -0500 Subject: [PATCH 0295/1145] [wifi] Fix positive RSSI values on 8266 (#11994) --- esphome/components/wifi/wifi_component_esp8266.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index bdaae5382a..0134fcaed8 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -870,7 +870,13 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } -int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } +int8_t WiFiComponent::wifi_rssi() { + if (WiFi.status() != WL_CONNECTED) + return WIFI_RSSI_DISCONNECTED; + int8_t rssi = WiFi.RSSI(); + // Values >= 31 are error codes per NONOS SDK API, not valid RSSI readings + return rssi >= 31 ? WIFI_RSSI_DISCONNECTED : rssi; +} int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; } From 71dc2d374d18f236c7a5fdae1eee61cbe78c0320 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 19 Nov 2025 12:58:33 -0600 Subject: [PATCH 0296/1145] [web_server_idf] Fix pbuf_free crash by moving shutdown before close (#11995) --- .../web_server_idf/web_server_idf.cpp | 41 ++++++++++++++----- .../web_server_idf/web_server_idf.h | 1 + 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index f5a66f6bd9..c910ed06c5 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -87,6 +87,29 @@ int nonblocking_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_ } } // namespace +void AsyncWebServer::safe_close_with_shutdown(httpd_handle_t hd, int sockfd) { + // CRITICAL: Shut down receive BEFORE closing to prevent lwIP race conditions + // + // The race condition occurs because close() initiates lwIP teardown while + // the TCP/IP thread can still receive packets, causing assertions when + // recv_tcp() sees partially-torn-down state. + // + // By shutting down receive first, we tell lwIP to stop accepting new data BEFORE + // the teardown begins, eliminating the race window. We only shutdown RD (not RDWR) + // to allow the FIN packet to be sent cleanly during close(). + // + // Note: This function may be called with an already-closed socket if the network + // stack closed it. In that case, shutdown() will fail but close() is safe to call. + // + // See: https://github.com/esphome/esphome-webserver/issues/163 + + // Attempt shutdown - ignore errors as socket may already be closed + shutdown(sockfd, SHUT_RD); + + // Always close - safe even if socket is already closed by network stack + close(sockfd); +} + void AsyncWebServer::end() { if (this->server_) { httpd_stop(this->server_); @@ -115,6 +138,8 @@ void AsyncWebServer::begin() { config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; }; // Enable LRU purging if requested (e.g., by captive portal to handle probe bursts) config.lru_purge_enable = this->lru_purge_enable_; + // Use custom close function that shuts down before closing to prevent lwIP race conditions + config.close_fn = AsyncWebServer::safe_close_with_shutdown; if (httpd_start(&this->server_, &config) == ESP_OK) { const httpd_uri_t handler_get = { .uri = "", @@ -505,17 +530,11 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest * void AsyncEventSourceResponse::destroy(void *ptr) { auto *rsp = static_cast(ptr); int fd = rsp->fd_.exchange(0); // Atomically get and clear fd - - if (fd > 0) { - ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd); - // Immediately shut down the socket to prevent lwIP from delivering more data - // This prevents "recv_tcp: recv for wrong pcb!" assertions when the TCP stack - // tries to deliver queued data after the session is marked as dead - // See: https://github.com/esphome/esphome/issues/11936 - shutdown(fd, SHUT_RDWR); - // Note: We don't close() the socket - httpd owns it and will close it - } - // Session will be cleaned up in the main loop to avoid race conditions + ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd); + // Mark as dead - will be cleaned up in the main loop + // Note: We don't delete or remove from set here to avoid race conditions + // httpd will call our custom close_fn (safe_close_with_shutdown) which handles + // shutdown() before close() to prevent lwIP race conditions } // helper for allowing only unique entries in the queue diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index b9f690b462..a139e9e4df 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -209,6 +209,7 @@ class AsyncWebServer { static esp_err_t request_handler(httpd_req_t *r); static esp_err_t request_post_handler(httpd_req_t *r); esp_err_t request_handler_(AsyncWebServerRequest *request) const; + static void safe_close_with_shutdown(httpd_handle_t hd, int sockfd); #ifdef USE_WEBSERVER_OTA esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type); #endif From 1157b4aee8b9c6a991854928a05f3169715fd0a6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:11:45 +1300 Subject: [PATCH 0297/1145] [epaper_spi] Add basic `7.3in-Spectra-E6` model (#12001) --- esphome/components/epaper_spi/display.py | 2 +- esphome/components/epaper_spi/models/spectra_e6.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/esphome/components/epaper_spi/display.py b/esphome/components/epaper_spi/display.py index 9ff393b397..182c37ba40 100644 --- a/esphome/components/epaper_spi/display.py +++ b/esphome/components/epaper_spi/display.py @@ -102,7 +102,7 @@ def customise_schema(config): """ config = cv.Schema( { - cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True), + cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True, space="-"), }, extra=cv.ALLOW_EXTRA, )(config) diff --git a/esphome/components/epaper_spi/models/spectra_e6.py b/esphome/components/epaper_spi/models/spectra_e6.py index 9f0b673d69..42a5a7da72 100644 --- a/esphome/components/epaper_spi/models/spectra_e6.py +++ b/esphome/components/epaper_spi/models/spectra_e6.py @@ -32,11 +32,15 @@ class SpectraE6(EpaperModel): spectra_e6 = SpectraE6("spectra-e6") -spectra_e6.extend( - "Seeed-reTerminal-E1002", +spectra_e6_7p3 = spectra_e6.extend( + "7.3in-Spectra-E6", width=800, height=480, data_rate="20MHz", +) + +spectra_e6_7p3.extend( + "Seeed-reTerminal-E1002", cs_pin=10, dc_pin=11, reset_pin=12, From c75abfb8949ede2182765abcdffab807565fbd05 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:17:03 -0500 Subject: [PATCH 0298/1145] Bump version to 2025.11.0b5 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index c7b2187964..56373c5f69 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.11.0b4 +PROJECT_NUMBER = 2025.11.0b5 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 7a3a79f270..ddd36f69d7 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.11.0b4" +__version__ = "2025.11.0b5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 2c3417062ae82512f51b85847ef5fe4deba41ebc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:47:40 -0600 Subject: [PATCH 0299/1145] Bump pyupgrade from 3.21.1 to 3.21.2 (#12002) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index e238faa77e..7f6d3f8e26 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,7 @@ pylint==4.0.3 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating ruff==0.14.5 # also change in .pre-commit-config.yaml when updating -pyupgrade==3.21.1 # also change in .pre-commit-config.yaml when updating +pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests From 1e9c7d3c6dbc8e9e4da1367aaacf70c73dc3712b Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:02:52 -0500 Subject: [PATCH 0300/1145] Bump version to 2025.11.0 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 56373c5f69..1448fd010d 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.11.0b5 +PROJECT_NUMBER = 2025.11.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index ddd36f69d7..3505ad169b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.11.0b5" +__version__ = "2025.11.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 4398fd84d2ba21a3a4099946d554a6d70cc0e7ad Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:09:22 -0500 Subject: [PATCH 0301/1145] [graph] Fix legend border (#12000) --- esphome/components/graph/graph.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index 88bb306408..e3b9119108 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -337,7 +337,7 @@ void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_of return; /// Plot border - if (this->border_) { + if (legend_->border_) { int w = legend_->width_; int h = legend_->height_; buff->horizontal_line(x_offset, y_offset, w, color); From da25951f6e714fe483f5c382a6188225b62509da Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Thu, 20 Nov 2025 03:01:32 +0000 Subject: [PATCH 0302/1145] [socket] Fix IPv6 address parsing for BSD sockets (#11996) --- esphome/components/socket/socket.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp index 1c8e72b8fd..cc9232d21a 100644 --- a/esphome/components/socket/socket.cpp +++ b/esphome/components/socket/socket.cpp @@ -61,9 +61,18 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::stri server->sin6_family = AF_INET6; server->sin6_port = htons(port); +#ifdef USE_SOCKET_IMPL_BSD_SOCKETS + // Use standard inet_pton for BSD sockets + if (inet_pton(AF_INET6, ip_address.c_str(), &server->sin6_addr) != 1) { + errno = EINVAL; + return 0; + } +#else + // Use LWIP-specific functions ip6_addr_t ip6; inet6_aton(ip_address.c_str(), &ip6); memcpy(server->sin6_addr.un.u32_addr, ip6.addr, sizeof(ip6.addr)); +#endif return sizeof(sockaddr_in6); } #endif /* USE_NETWORK_IPV6 */ From 83307684a3dcc3e183de98e060c9324206a7a4ab Mon Sep 17 00:00:00 2001 From: B48D81EFCC <111175947+B48D81EFCC@users.noreply.github.com> Date: Thu, 20 Nov 2025 04:58:39 +0100 Subject: [PATCH 0303/1145] [stts22h] Add support for STTS22H temperature sensor (#11778) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/stts22h/__init__.py | 1 + esphome/components/stts22h/sensor.py | 33 ++++++ esphome/components/stts22h/stts22h.cpp | 101 ++++++++++++++++++ esphome/components/stts22h/stts22h.h | 21 ++++ tests/components/stts22h/common.yaml | 4 + tests/components/stts22h/test.esp32-idf.yaml | 4 + .../components/stts22h/test.esp8266-ard.yaml | 4 + tests/components/stts22h/test.nrf52.yaml | 4 + tests/components/stts22h/test.rp2040-ard.yaml | 4 + 10 files changed, 177 insertions(+) create mode 100644 esphome/components/stts22h/__init__.py create mode 100644 esphome/components/stts22h/sensor.py create mode 100644 esphome/components/stts22h/stts22h.cpp create mode 100644 esphome/components/stts22h/stts22h.h create mode 100644 tests/components/stts22h/common.yaml create mode 100644 tests/components/stts22h/test.esp32-idf.yaml create mode 100644 tests/components/stts22h/test.esp8266-ard.yaml create mode 100644 tests/components/stts22h/test.nrf52.yaml create mode 100644 tests/components/stts22h/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index e6970af47c..250fbbd4d4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -460,6 +460,7 @@ esphome/components/st7735/* @SenexCrenshaw esphome/components/st7789v/* @kbx81 esphome/components/st7920/* @marsjan155 esphome/components/statsd/* @Links2004 +esphome/components/stts22h/* @B48D81EFCC esphome/components/substitutions/* @esphome/core esphome/components/sun/* @OttoWinter esphome/components/sun_gtil2/* @Mat931 diff --git a/esphome/components/stts22h/__init__.py b/esphome/components/stts22h/__init__.py new file mode 100644 index 0000000000..a33c0b554b --- /dev/null +++ b/esphome/components/stts22h/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@B48D81EFCC"] diff --git a/esphome/components/stts22h/sensor.py b/esphome/components/stts22h/sensor.py new file mode 100644 index 0000000000..094c233361 --- /dev/null +++ b/esphome/components/stts22h/sensor.py @@ -0,0 +1,33 @@ +import esphome.codegen as cg +from esphome.components import i2c, sensor +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, +) + +DEPENDENCIES = ["i2c"] + +sensor_ns = cg.esphome_ns.namespace("stts22h") +stts22h = sensor_ns.class_( + "STTS22HComponent", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + stts22h, + accuracy_decimals=2, + unit_of_measurement=UNIT_CELSIUS, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x3C)) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/stts22h/stts22h.cpp b/esphome/components/stts22h/stts22h.cpp new file mode 100644 index 0000000000..614dc1da8b --- /dev/null +++ b/esphome/components/stts22h/stts22h.cpp @@ -0,0 +1,101 @@ +#include "esphome/core/log.h" +#include "stts22h.h" + +namespace esphome::stts22h { + +static const char *const TAG = "stts22h"; + +static const uint8_t WHOAMI_REG = 0x01; +static const uint8_t CTRL_REG = 0x04; +static const uint8_t TEMPERATURE_REG = 0x06; + +// CTRL_REG flags +static const uint8_t LOW_ODR_CTRL_ENABLE_FLAG = 0x80; // Flag to enable low ODR mode in CTRL_REG +static const uint8_t FREERUN_CTRL_ENABLE_FLAG = 0x04; // Flag to enable FREERUN mode in CTRL_REG +static const uint8_t ADD_INC_ENABLE_FLAG = 0x08; // Flag to enable ADD_INC (IF_ADD_INC) mode in CTRL_REG + +static const uint8_t WHOAMI_STTS22H_IDENTIFICATION = 0xA0; // ID value of STTS22H in WHOAMI_REG + +static const float SENSOR_SCALE = 0.01f; // Sensor resolution in degrees Celsius + +void STTS22HComponent::setup() { + // Check if device is a STTS22H + if (!this->is_stts22h_sensor_()) { + this->mark_failed("Device is not a STTS22H sensor"); + return; + } + + this->initialize_sensor_(); +} + +void STTS22HComponent::update() { + if (this->is_failed()) { + return; + } + + this->publish_state(this->read_temperature_()); +} + +void STTS22HComponent::dump_config() { + LOG_SENSOR("", "STTS22H", this); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); + if (this->is_failed()) { + ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); + } +} + +float STTS22HComponent::read_temperature_() { + uint8_t temp_reg_value[2]; + if (this->read_register(TEMPERATURE_REG, temp_reg_value, 2) != i2c::NO_ERROR) { + ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); + return NAN; + } + + // Combine the two bytes into a single 16-bit signed integer + // The STTS22H temperature data is in two's complement format + int16_t temp_raw_value = static_cast(encode_uint16(temp_reg_value[1], temp_reg_value[0])); + return temp_raw_value * SENSOR_SCALE; // Apply sensor resolution +} + +bool STTS22HComponent::is_stts22h_sensor_() { + uint8_t whoami_value; + if (this->read_register(WHOAMI_REG, &whoami_value, 1) != i2c::NO_ERROR) { + this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + return false; + } + + if (whoami_value != WHOAMI_STTS22H_IDENTIFICATION) { + this->mark_failed("Unexpected WHOAMI identifier. Sensor is not a STTS22H"); + return false; + } + + return true; +} + +void STTS22HComponent::initialize_sensor_() { + // Read current CTRL_REG configuration + uint8_t ctrl_value; + if (this->read_register(CTRL_REG, &ctrl_value, 1) != i2c::NO_ERROR) { + this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + return; + } + + // Enable low ODR mode and enable ADD_INC + // Before low ODR mode can be used, + // FREERUN bit must be cleared (see sensor documentation) + ctrl_value &= ~FREERUN_CTRL_ENABLE_FLAG; // Clear FREERUN bit + if (this->write_register(CTRL_REG, &ctrl_value, 1) != i2c::NO_ERROR) { + this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + return; + } + + // Enable LOW ODR mode and ADD_INC + ctrl_value |= LOW_ODR_CTRL_ENABLE_FLAG | ADD_INC_ENABLE_FLAG; // Set LOW ODR bit and ADD_INC bit + if (this->write_register(CTRL_REG, &ctrl_value, 1) != i2c::NO_ERROR) { + this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + return; + } +} + +} // namespace esphome::stts22h diff --git a/esphome/components/stts22h/stts22h.h b/esphome/components/stts22h/stts22h.h new file mode 100644 index 0000000000..442a263e49 --- /dev/null +++ b/esphome/components/stts22h/stts22h.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome::stts22h { + +class STTS22HComponent : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + void dump_config() override; + + protected: + void initialize_sensor_(); + bool is_stts22h_sensor_(); + float read_temperature_(); +}; + +} // namespace esphome::stts22h diff --git a/tests/components/stts22h/common.yaml b/tests/components/stts22h/common.yaml new file mode 100644 index 0000000000..802afe2065 --- /dev/null +++ b/tests/components/stts22h/common.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: stts22h + name: Temperature + update_interval: 15s diff --git a/tests/components/stts22h/test.esp32-idf.yaml b/tests/components/stts22h/test.esp32-idf.yaml new file mode 100644 index 0000000000..b47e39c389 --- /dev/null +++ b/tests/components/stts22h/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/stts22h/test.esp8266-ard.yaml b/tests/components/stts22h/test.esp8266-ard.yaml new file mode 100644 index 0000000000..4a98b9388a --- /dev/null +++ b/tests/components/stts22h/test.esp8266-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/stts22h/test.nrf52.yaml b/tests/components/stts22h/test.nrf52.yaml new file mode 100644 index 0000000000..2a0de6241c --- /dev/null +++ b/tests/components/stts22h/test.nrf52.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/nrf52.yaml + +<<: !include common.yaml diff --git a/tests/components/stts22h/test.rp2040-ard.yaml b/tests/components/stts22h/test.rp2040-ard.yaml new file mode 100644 index 0000000000..319a7c71a6 --- /dev/null +++ b/tests/components/stts22h/test.rp2040-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml + +<<: !include common.yaml From b346666a5264294afea8ce002cb5cf955b99339b Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Thu, 20 Nov 2025 10:05:22 +0100 Subject: [PATCH 0304/1145] [st7701s] Add explanatory comment (#12014) --- esphome/components/mipi_rgb/models/st7701s.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/mipi_rgb/models/st7701s.py b/esphome/components/mipi_rgb/models/st7701s.py index bfd1c9aa3f..0b0a9548ca 100644 --- a/esphome/components/mipi_rgb/models/st7701s.py +++ b/esphome/components/mipi_rgb/models/st7701s.py @@ -24,6 +24,8 @@ class ST7701S(DriverChip): sdir = 0 if transform.get(CONF_MIRROR_X): sdir |= 0x04 + # XFLIP doesn't do anything in the ST7701S, + # it's set in the madctl byte just so it can be reported at runtime by logconfig madctl |= MADCTL_XFLIP sequence.append((SDIR_CMD, sdir)) return madctl From 4825da8e9ca27c1b7d9c88bd094da5a620b53a23 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Nov 2025 07:57:04 -0600 Subject: [PATCH 0305/1145] [select] Modernize namespace declarations to C++17 syntax (#12007) --- esphome/components/select/automation.h | 6 ++---- esphome/components/select/select.cpp | 6 ++---- esphome/components/select/select.h | 6 ++---- esphome/components/select/select_call.cpp | 6 ++---- esphome/components/select/select_call.h | 6 ++---- esphome/components/select/select_traits.cpp | 6 ++---- esphome/components/select/select_traits.h | 6 ++---- 7 files changed, 14 insertions(+), 28 deletions(-) diff --git a/esphome/components/select/automation.h b/esphome/components/select/automation.h index 3e42eaf98a..768f2621f7 100644 --- a/esphome/components/select/automation.h +++ b/esphome/components/select/automation.h @@ -4,8 +4,7 @@ #include "esphome/core/component.h" #include "select.h" -namespace esphome { -namespace select { +namespace esphome::select { class SelectStateTrigger : public Trigger { public: @@ -63,5 +62,4 @@ template class SelectOperationAction : public Action { Select *select_; }; -} // namespace select -} // namespace esphome +} // namespace esphome::select diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 9fe7a52422..3ec413f167 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -4,8 +4,7 @@ #include "esphome/core/log.h" #include -namespace esphome { -namespace select { +namespace esphome::select { static const char *const TAG = "select"; @@ -86,5 +85,4 @@ optional Select::at(size_t index) const { const char *Select::option_at(size_t index) const { return traits.get_options().at(index); } -} // namespace select -} // namespace esphome +} // namespace esphome::select diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index 7459c9d146..c4d7412d50 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -6,8 +6,7 @@ #include "select_call.h" #include "select_traits.h" -namespace esphome { -namespace select { +namespace esphome::select { #define LOG_SELECT(prefix, type, obj) \ if ((obj) != nullptr) { \ @@ -114,5 +113,4 @@ class Select : public EntityBase { CallbackManager state_callback_; }; -} // namespace select -} // namespace esphome +} // namespace esphome::select diff --git a/esphome/components/select/select_call.cpp b/esphome/components/select/select_call.cpp index aa7559e24e..aecfed0d64 100644 --- a/esphome/components/select/select_call.cpp +++ b/esphome/components/select/select_call.cpp @@ -2,8 +2,7 @@ #include "select.h" #include "esphome/core/log.h" -namespace esphome { -namespace select { +namespace esphome::select { static const char *const TAG = "select"; @@ -125,5 +124,4 @@ void SelectCall::perform() { parent->control(idx); } -} // namespace select -} // namespace esphome +} // namespace esphome::select diff --git a/esphome/components/select/select_call.h b/esphome/components/select/select_call.h index eae7d3de1d..b31d890ef6 100644 --- a/esphome/components/select/select_call.h +++ b/esphome/components/select/select_call.h @@ -2,8 +2,7 @@ #include "esphome/core/helpers.h" -namespace esphome { -namespace select { +namespace esphome::select { class Select; @@ -45,5 +44,4 @@ class SelectCall { bool cycle_; }; -} // namespace select -} // namespace esphome +} // namespace esphome::select diff --git a/esphome/components/select/select_traits.cpp b/esphome/components/select/select_traits.cpp index e5e12bdc7a..ff52c0d85b 100644 --- a/esphome/components/select/select_traits.cpp +++ b/esphome/components/select/select_traits.cpp @@ -1,7 +1,6 @@ #include "select_traits.h" -namespace esphome { -namespace select { +namespace esphome::select { void SelectTraits::set_options(const std::initializer_list &options) { this->options_ = options; } @@ -14,5 +13,4 @@ void SelectTraits::set_options(const FixedVector &options) { const FixedVector &SelectTraits::get_options() const { return this->options_; } -} // namespace select -} // namespace esphome +} // namespace esphome::select diff --git a/esphome/components/select/select_traits.h b/esphome/components/select/select_traits.h index ee59a030ad..78a83e5944 100644 --- a/esphome/components/select/select_traits.h +++ b/esphome/components/select/select_traits.h @@ -3,8 +3,7 @@ #include "esphome/core/helpers.h" #include -namespace esphome { -namespace select { +namespace esphome::select { class SelectTraits { public: @@ -16,5 +15,4 @@ class SelectTraits { FixedVector options_; }; -} // namespace select -} // namespace esphome +} // namespace esphome::select From 507147376710e8d5e214d78ab51ece81f7a33339 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Nov 2025 07:57:33 -0600 Subject: [PATCH 0306/1145] [mdns] Modernize to C++17 nested namespace syntax (#11983) --- esphome/components/mdns/mdns_component.cpp | 6 ++---- esphome/components/mdns/mdns_component.h | 6 ++---- esphome/components/mdns/mdns_esp32.cpp | 6 ++---- esphome/components/mdns/mdns_esp8266.cpp | 6 ++---- esphome/components/mdns/mdns_host.cpp | 6 ++---- esphome/components/mdns/mdns_libretiny.cpp | 6 ++---- esphome/components/mdns/mdns_rp2040.cpp | 6 ++---- 7 files changed, 14 insertions(+), 28 deletions(-) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index b66129404e..c81defd19f 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -21,8 +21,7 @@ #include "esphome/components/dashboard_import/dashboard_import.h" #endif -namespace esphome { -namespace mdns { +namespace esphome::mdns { static const char *const TAG = "mdns"; @@ -189,6 +188,5 @@ void MDNSComponent::dump_config() { #endif } -} // namespace mdns -} // namespace esphome +} // namespace esphome::mdns #endif diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index f4237d5a69..691c45b7df 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -6,8 +6,7 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" -namespace esphome { -namespace mdns { +namespace esphome::mdns { // Helper struct that identifies strings that may be stored in flash storage (similar to LogString) struct MDNSString; @@ -79,6 +78,5 @@ class MDNSComponent : public Component { void compile_records_(StaticVector &services); }; -} // namespace mdns -} // namespace esphome +} // namespace esphome::mdns #endif diff --git a/esphome/components/mdns/mdns_esp32.cpp b/esphome/components/mdns/mdns_esp32.cpp index ecdc926cc9..5547a2524b 100644 --- a/esphome/components/mdns/mdns_esp32.cpp +++ b/esphome/components/mdns/mdns_esp32.cpp @@ -7,8 +7,7 @@ #include "esphome/core/log.h" #include "mdns_component.h" -namespace esphome { -namespace mdns { +namespace esphome::mdns { static const char *const TAG = "mdns"; @@ -56,7 +55,6 @@ void MDNSComponent::on_shutdown() { delay(40); // Allow the mdns packets announcing service removal to be sent } -} // namespace mdns -} // namespace esphome +} // namespace esphome::mdns #endif // USE_ESP32 diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index 9bbb406070..06f905884c 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -9,8 +9,7 @@ #include "esphome/core/log.h" #include "mdns_component.h" -namespace esphome { -namespace mdns { +namespace esphome::mdns { void MDNSComponent::setup() { #ifdef USE_MDNS_STORE_SERVICES @@ -52,7 +51,6 @@ void MDNSComponent::on_shutdown() { delay(10); } -} // namespace mdns -} // namespace esphome +} // namespace esphome::mdns #endif diff --git a/esphome/components/mdns/mdns_host.cpp b/esphome/components/mdns/mdns_host.cpp index f645d8d068..64b8c8f54b 100644 --- a/esphome/components/mdns/mdns_host.cpp +++ b/esphome/components/mdns/mdns_host.cpp @@ -6,8 +6,7 @@ #include "esphome/core/log.h" #include "mdns_component.h" -namespace esphome { -namespace mdns { +namespace esphome::mdns { void MDNSComponent::setup() { // Host platform doesn't have actual mDNS implementation @@ -15,7 +14,6 @@ void MDNSComponent::setup() { void MDNSComponent::on_shutdown() {} -} // namespace mdns -} // namespace esphome +} // namespace esphome::mdns #endif diff --git a/esphome/components/mdns/mdns_libretiny.cpp b/esphome/components/mdns/mdns_libretiny.cpp index fb2088f719..a049fe2109 100644 --- a/esphome/components/mdns/mdns_libretiny.cpp +++ b/esphome/components/mdns/mdns_libretiny.cpp @@ -9,8 +9,7 @@ #include -namespace esphome { -namespace mdns { +namespace esphome::mdns { void MDNSComponent::setup() { #ifdef USE_MDNS_STORE_SERVICES @@ -46,7 +45,6 @@ void MDNSComponent::setup() { void MDNSComponent::on_shutdown() {} -} // namespace mdns -} // namespace esphome +} // namespace esphome::mdns #endif diff --git a/esphome/components/mdns/mdns_rp2040.cpp b/esphome/components/mdns/mdns_rp2040.cpp index a9f5349f14..a102e0b6c3 100644 --- a/esphome/components/mdns/mdns_rp2040.cpp +++ b/esphome/components/mdns/mdns_rp2040.cpp @@ -9,8 +9,7 @@ #include -namespace esphome { -namespace mdns { +namespace esphome::mdns { void MDNSComponent::setup() { #ifdef USE_MDNS_STORE_SERVICES @@ -51,7 +50,6 @@ void MDNSComponent::on_shutdown() { delay(40); } -} // namespace mdns -} // namespace esphome +} // namespace esphome::mdns #endif From 24a6ad148c6a6fe130e1ca74859032a2b787df3f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Nov 2025 07:57:49 -0600 Subject: [PATCH 0307/1145] [lock] Modernize to C++17 nested namespaces (#11982) --- esphome/components/lock/automation.h | 6 ++---- esphome/components/lock/lock.cpp | 6 ++---- esphome/components/lock/lock.h | 6 ++---- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/esphome/components/lock/automation.h b/esphome/components/lock/automation.h index 0f596ef5e6..cba2c3fdda 100644 --- a/esphome/components/lock/automation.h +++ b/esphome/components/lock/automation.h @@ -4,8 +4,7 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" -namespace esphome { -namespace lock { +namespace esphome::lock { template class LockAction : public Action { public: @@ -72,5 +71,4 @@ class LockUnlockTrigger : public Trigger<> { } }; -} // namespace lock -} // namespace esphome +} // namespace esphome::lock diff --git a/esphome/components/lock/lock.cpp b/esphome/components/lock/lock.cpp index 54fefe8745..b8f0fbe011 100644 --- a/esphome/components/lock/lock.cpp +++ b/esphome/components/lock/lock.cpp @@ -3,8 +3,7 @@ #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" -namespace esphome { -namespace lock { +namespace esphome::lock { static const char *const TAG = "lock"; @@ -108,5 +107,4 @@ LockCall &LockCall::set_state(const std::string &state) { } const optional &LockCall::get_state() const { return this->state_; } -} // namespace lock -} // namespace esphome +} // namespace esphome::lock diff --git a/esphome/components/lock/lock.h b/esphome/components/lock/lock.h index 9737569921..8a906ef9fc 100644 --- a/esphome/components/lock/lock.h +++ b/esphome/components/lock/lock.h @@ -7,8 +7,7 @@ #include "esphome/core/preferences.h" #include -namespace esphome { -namespace lock { +namespace esphome::lock { class Lock; @@ -177,5 +176,4 @@ class Lock : public EntityBase { ESPPreferenceObject rtc_; }; -} // namespace lock -} // namespace esphome +} // namespace esphome::lock From a2321edf3c7dcb7b38adbc77816bcf23dcd44595 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Nov 2025 07:59:16 -0600 Subject: [PATCH 0308/1145] [network] Fix IPAddress constructor causing comparison failures and garbage output (#12005) --- esphome/components/network/ip_address.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index 5ec6450cce..3d8b062d0b 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -81,7 +81,12 @@ struct IPAddress { ip_addr_.type = IPADDR_TYPE_V6; } #endif /* LWIP_IPV6 */ - IPAddress(esp_ip4_addr_t *other_ip) { memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(esp_ip4_addr_t)); } + IPAddress(esp_ip4_addr_t *other_ip) { + memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(esp_ip4_addr_t)); +#if LWIP_IPV6 + ip_addr_.type = IPADDR_TYPE_V4; +#endif + } IPAddress(esp_ip_addr_t *other_ip) { #if LWIP_IPV6 memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip_addr_)); From b62053812b79f9fb9acc6965e7060c05951fc681 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Nov 2025 08:06:28 -0600 Subject: [PATCH 0309/1145] [core] Document threading model rationale in ThreadModel enum (#11979) --- esphome/components/libretiny/__init__.py | 4 ++++ esphome/const.py | 25 +++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index c63d6d7faa..93b66888da 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -261,6 +261,10 @@ async def component_to_code(config): cg.add_build_flag(f"-DUSE_LIBRETINY_VARIANT_{config[CONF_FAMILY]}") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_define("ESPHOME_VARIANT", FAMILY_FRIENDLY[config[CONF_FAMILY]]) + # LibreTiny uses MULTI_NO_ATOMICS because platforms like BK7231N (ARM968E-S) lack + # exclusive load/store (no LDREX/STREX). std::atomic RMW operations require libatomic, + # which is not linked to save flash (4-8KB). Even if linked, libatomic would use locks + # (ATOMIC_INT_LOCK_FREE=1), so explicit FreeRTOS mutexes are simpler and equivalent. cg.add_define(ThreadModel.MULTI_NO_ATOMICS) # force using arduino framework diff --git a/esphome/const.py b/esphome/const.py index b4cd3cfd1c..2b6b60d395 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -36,7 +36,30 @@ class Framework(StrEnum): class ThreadModel(StrEnum): - """Threading model identifiers for ESPHome scheduler.""" + """Threading model identifiers for ESPHome scheduler. + + ESPHome currently uses three threading models based on platform capabilities: + + SINGLE: + - Single-threaded platforms (ESP8266, RP2040) + - No RTOS task switching + - No concurrent access to scheduler data structures + - No atomics or locks required + - Minimal overhead + + MULTI_NO_ATOMICS: + - Multi-threaded platforms without hardware atomic RMW support (e.g. LibreTiny BK7231N) + - Uses FreeRTOS or another RTOS with multiple tasks + - CPU lacks exclusive load/store instructions (ARM968E-S has no LDREX/STREX) + - std::atomic cannot provide lock-free RMW; libatomic is avoided to save flash (4–8 KB) + - Scheduler uses explicit FreeRTOS mutexes for synchronization + + MULTI_ATOMICS: + - Multi-threaded platforms with hardware atomic RMW support (ESP32, Cortex-M, Host) + - CPU provides native atomic instructions (ESP32 S32C1I, ARM LDREX/STREX) + - std::atomic is used for lock-free synchronization + - Reduced contention and better performance + """ SINGLE = "ESPHOME_THREAD_SINGLE" MULTI_NO_ATOMICS = "ESPHOME_THREAD_MULTI_NO_ATOMICS" From 5d883c6e06d649651db138807ffd6cfd880c44ac Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Thu, 20 Nov 2025 15:06:40 +0100 Subject: [PATCH 0310/1145] [nrf52,i2c] fix review comment (#11931) --- esphome/components/i2c/i2c_bus_zephyr.cpp | 31 ++++++++++++----------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/esphome/components/i2c/i2c_bus_zephyr.cpp b/esphome/components/i2c/i2c_bus_zephyr.cpp index 658dcee35c..1eb9944dcb 100644 --- a/esphome/components/i2c/i2c_bus_zephyr.cpp +++ b/esphome/components/i2c/i2c_bus_zephyr.cpp @@ -8,6 +8,22 @@ namespace esphome::i2c { static const char *const TAG = "i2c.zephyr"; +static const char *get_speed(uint32_t dev_config) { + switch (I2C_SPEED_GET(dev_config)) { + case I2C_SPEED_STANDARD: + return "100 kHz"; + case I2C_SPEED_FAST: + return "400 kHz"; + case I2C_SPEED_FAST_PLUS: + return "1 MHz"; + case I2C_SPEED_HIGH: + return "3.4 MHz"; + case I2C_SPEED_ULTRA: + return "5 MHz"; + } + return "unknown"; +} + void ZephyrI2CBus::setup() { if (!device_is_ready(this->i2c_dev_)) { ESP_LOGE(TAG, "I2C dev is not ready."); @@ -31,21 +47,6 @@ void ZephyrI2CBus::setup() { } void ZephyrI2CBus::dump_config() { - auto get_speed = [](uint32_t dev_config) { - switch (I2C_SPEED_GET(dev_config)) { - case I2C_SPEED_STANDARD: - return "100 kHz"; - case I2C_SPEED_FAST: - return "400 kHz"; - case I2C_SPEED_FAST_PLUS: - return "1 MHz"; - case I2C_SPEED_HIGH: - return "3.4 MHz"; - case I2C_SPEED_ULTRA: - return "5 MHz"; - } - return "unknown"; - }; ESP_LOGCONFIG(TAG, "I2C Bus:\n" " SDA Pin: GPIO%u\n" From 06bef148f432c5521c8d91743a0134f672ae4be5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Nov 2025 08:06:52 -0600 Subject: [PATCH 0311/1145] [core] Optimize DelayAction for no-argument case using if constexpr (#11913) --- esphome/core/base_automation.h | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index a5e6139182..c2519da839 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -178,7 +178,6 @@ template class DelayAction : public Action, public Compon TEMPLATABLE_VALUE(uint32_t, delay) void play_complex(const Ts &...x) override { - auto f = std::bind(&DelayAction::play_next_, this, x...); this->num_running_++; // If num_running_ > 1, we have multiple instances running in parallel @@ -187,9 +186,22 @@ template class DelayAction : public Action, public Compon // WARNING: This can accumulate delays if scripts are triggered faster than they complete! // Users should set max_runs on parallel scripts to limit concurrent executions. // Issue #10264: This is a workaround for parallel script delays interfering with each other. - App.scheduler.set_timer_common_(this, Scheduler::SchedulerItem::TIMEOUT, - /* is_static_string= */ true, "delay", this->delay_.value(x...), std::move(f), - /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1); + + // Optimization: For no-argument delays (most common case), use direct lambda + // instead of std::bind to avoid bind overhead (~16 bytes heap + faster execution) + if constexpr (sizeof...(Ts) == 0) { + App.scheduler.set_timer_common_( + this, Scheduler::SchedulerItem::TIMEOUT, + /* is_static_string= */ true, "delay", this->delay_.value(), [this]() { this->play_next_(); }, + /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1); + } else { + // For delays with arguments, use std::bind to preserve argument values + // Arguments must be copied because original references may be invalid after delay + auto f = std::bind(&DelayAction::play_next_, this, x...); + App.scheduler.set_timer_common_(this, Scheduler::SchedulerItem::TIMEOUT, + /* is_static_string= */ true, "delay", this->delay_.value(x...), std::move(f), + /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1); + } } float get_setup_priority() const override { return setup_priority::HARDWARE; } From 3c86f3894b9f331f9df9d49b50725e9e7337df28 Mon Sep 17 00:00:00 2001 From: omartijn <44672243+omartijn@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:24:45 +0100 Subject: [PATCH 0312/1145] [hc8] Add support for HC8 CO2 sensor (#11872) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/hc8/__init__.py | 1 + esphome/components/hc8/hc8.cpp | 99 ++++++++++++++++++++++ esphome/components/hc8/hc8.h | 37 ++++++++ esphome/components/hc8/sensor.py | 79 +++++++++++++++++ tests/components/hc8/common.yaml | 13 +++ tests/components/hc8/test.esp32-idf.yaml | 4 + tests/components/hc8/test.esp8266-ard.yaml | 4 + tests/components/hc8/test.rp2040-ard.yaml | 4 + 9 files changed, 242 insertions(+) create mode 100644 esphome/components/hc8/__init__.py create mode 100644 esphome/components/hc8/hc8.cpp create mode 100644 esphome/components/hc8/hc8.h create mode 100644 esphome/components/hc8/sensor.py create mode 100644 tests/components/hc8/common.yaml create mode 100644 tests/components/hc8/test.esp32-idf.yaml create mode 100644 tests/components/hc8/test.esp8266-ard.yaml create mode 100644 tests/components/hc8/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 250fbbd4d4..c3d8f4350f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -202,6 +202,7 @@ esphome/components/havells_solar/* @sourabhjaiswal esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/light/* @DotNetDann esphome/components/hbridge/switch/* @dwmw2 +esphome/components/hc8/* @omartijn esphome/components/hdc2010/* @optimusprimespace @ssieb esphome/components/he60r/* @clydebarrow esphome/components/heatpumpir/* @rob-deutsch diff --git a/esphome/components/hc8/__init__.py b/esphome/components/hc8/__init__.py new file mode 100644 index 0000000000..e1028456b0 --- /dev/null +++ b/esphome/components/hc8/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@omartijn"] diff --git a/esphome/components/hc8/hc8.cpp b/esphome/components/hc8/hc8.cpp new file mode 100644 index 0000000000..5b649c2735 --- /dev/null +++ b/esphome/components/hc8/hc8.cpp @@ -0,0 +1,99 @@ +#include "hc8.h" +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include + +namespace esphome::hc8 { + +static const char *const TAG = "hc8"; +static const std::array HC8_COMMAND_GET_PPM{0x64, 0x69, 0x03, 0x5E, 0x4E}; +static const std::array HC8_COMMAND_CALIBRATE_PREAMBLE{0x11, 0x03, 0x03}; + +void HC8Component::setup() { + // send an initial query to the device, this will + // get it out of "active output mode", where it + // generates data every second + this->write_array(HC8_COMMAND_GET_PPM); + this->flush(); + + // ensure the buffer is empty + while (this->available()) + this->read(); +} + +void HC8Component::update() { + uint32_t now_ms = App.get_loop_component_start_time(); + uint32_t warmup_ms = this->warmup_seconds_ * 1000; + if (now_ms < warmup_ms) { + ESP_LOGW(TAG, "HC8 warming up, %" PRIu32 " s left", (warmup_ms - now_ms) / 1000); + this->status_set_warning(); + return; + } + + while (this->available()) + this->read(); + + this->write_array(HC8_COMMAND_GET_PPM); + this->flush(); + + // the sensor is a bit slow in responding, so trying to + // read immediately after sending a query will timeout + this->set_timeout(50, [this]() { + std::array response; + if (!this->read_array(response.data(), response.size())) { + ESP_LOGW(TAG, "Reading data from HC8 failed!"); + this->status_set_warning(); + return; + } + + if (response[0] != 0x64 || response[1] != 0x69) { + ESP_LOGW(TAG, "Invalid preamble from HC8!"); + this->status_set_warning(); + return; + } + + if (crc16(response.data(), 12) != encode_uint16(response[13], response[12])) { + ESP_LOGW(TAG, "HC8 Checksum mismatch"); + this->status_set_warning(); + return; + } + + this->status_clear_warning(); + + const uint16_t ppm = encode_uint16(response[5], response[4]); + ESP_LOGD(TAG, "HC8 Received CO₂=%uppm", ppm); + if (this->co2_sensor_ != nullptr) + this->co2_sensor_->publish_state(ppm); + }); +} + +void HC8Component::calibrate(uint16_t baseline) { + ESP_LOGD(TAG, "HC8 Calibrating baseline to %uppm", baseline); + + std::array command{}; + std::copy(begin(HC8_COMMAND_CALIBRATE_PREAMBLE), end(HC8_COMMAND_CALIBRATE_PREAMBLE), begin(command)); + command[3] = baseline >> 8; + command[4] = baseline; + command[5] = 0; + + // the last byte is a checksum over the data + for (uint8_t i = 0; i < 5; ++i) + command[5] -= command[i]; + + this->write_array(command); + this->flush(); +} + +float HC8Component::get_setup_priority() const { return setup_priority::DATA; } + +void HC8Component::dump_config() { + ESP_LOGCONFIG(TAG, "HC8:"); + LOG_SENSOR(" ", "CO2", this->co2_sensor_); + this->check_uart_settings(9600); + + ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_); +} + +} // namespace esphome::hc8 diff --git a/esphome/components/hc8/hc8.h b/esphome/components/hc8/hc8.h new file mode 100644 index 0000000000..7711fb8c97 --- /dev/null +++ b/esphome/components/hc8/hc8.h @@ -0,0 +1,37 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" + +#include + +namespace esphome::hc8 { + +class HC8Component : public PollingComponent, public uart::UARTDevice { + public: + float get_setup_priority() const override; + + void setup() override; + void update() override; + void dump_config() override; + + void calibrate(uint16_t baseline); + + void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; } + void set_warmup_seconds(uint32_t seconds) { warmup_seconds_ = seconds; } + + protected: + sensor::Sensor *co2_sensor_{nullptr}; + uint32_t warmup_seconds_{0}; +}; + +template class HC8CalibrateAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(uint16_t, baseline) + + void play(const Ts &...x) override { this->parent_->calibrate(this->baseline_.value(x...)); } +}; + +} // namespace esphome::hc8 diff --git a/esphome/components/hc8/sensor.py b/esphome/components/hc8/sensor.py new file mode 100644 index 0000000000..90698b2661 --- /dev/null +++ b/esphome/components/hc8/sensor.py @@ -0,0 +1,79 @@ +from esphome import automation +import esphome.codegen as cg +from esphome.components import sensor, uart +import esphome.config_validation as cv +from esphome.const import ( + CONF_BASELINE, + CONF_CO2, + CONF_ID, + DEVICE_CLASS_CARBON_DIOXIDE, + ICON_MOLECULE_CO2, + STATE_CLASS_MEASUREMENT, + UNIT_PARTS_PER_MILLION, +) + +DEPENDENCIES = ["uart"] + +CONF_WARMUP_TIME = "warmup_time" + +hc8_ns = cg.esphome_ns.namespace("hc8") +HC8Component = hc8_ns.class_("HC8Component", cg.PollingComponent, uart.UARTDevice) +HC8CalibrateAction = hc8_ns.class_("HC8CalibrateAction", automation.Action) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HC8Component), + cv.Optional(CONF_CO2): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_MILLION, + icon=ICON_MOLECULE_CO2, + accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + CONF_WARMUP_TIME, default="75s" + ): cv.positive_time_period_seconds, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "hc8", + baud_rate=9600, + require_rx=True, + require_tx=True, +) + + +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 co2 := config.get(CONF_CO2): + sens = await sensor.new_sensor(co2) + cg.add(var.set_co2_sensor(sens)) + + cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME])) + + +CALIBRATION_ACTION_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(HC8Component), + cv.Required(CONF_BASELINE): cv.templatable(cv.uint16_t), + } +) + + +@automation.register_action( + "hc8.calibrate", HC8CalibrateAction, CALIBRATION_ACTION_SCHEMA +) +async def hc8_calibration_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + template_ = await cg.templatable(config[CONF_BASELINE], args, cg.uint16) + cg.add(var.set_baseline(template_)) + return var diff --git a/tests/components/hc8/common.yaml b/tests/components/hc8/common.yaml new file mode 100644 index 0000000000..ac3b454315 --- /dev/null +++ b/tests/components/hc8/common.yaml @@ -0,0 +1,13 @@ +esphome: + on_boot: + then: + - hc8.calibrate: + id: hc8_sensor + baseline: 420 + +sensor: + - platform: hc8 + id: hc8_sensor + co2: + name: HC8 CO2 Value + update_interval: 15s diff --git a/tests/components/hc8/test.esp32-idf.yaml b/tests/components/hc8/test.esp32-idf.yaml new file mode 100644 index 0000000000..2d29656c94 --- /dev/null +++ b/tests/components/hc8/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/hc8/test.esp8266-ard.yaml b/tests/components/hc8/test.esp8266-ard.yaml new file mode 100644 index 0000000000..5a05efa259 --- /dev/null +++ b/tests/components/hc8/test.esp8266-ard.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/hc8/test.rp2040-ard.yaml b/tests/components/hc8/test.rp2040-ard.yaml new file mode 100644 index 0000000000..f1df2daf83 --- /dev/null +++ b/tests/components/hc8/test.rp2040-ard.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml + +<<: !include common.yaml From 59cd6dbf70235fa89c41eeedffa610a9c823ac76 Mon Sep 17 00:00:00 2001 From: damib Date: Thu, 20 Nov 2025 15:28:14 +0100 Subject: [PATCH 0313/1145] [climate_ir] Add optional humidity sensor (#9805) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/climate_ir/__init__.py | 11 ++++++++++- esphome/components/climate_ir/climate_ir.cpp | 15 ++++++++++++--- esphome/components/climate_ir/climate_ir.h | 2 ++ tests/components/climate_ir_lg/common.yaml | 12 ++++++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/esphome/components/climate_ir/__init__.py b/esphome/components/climate_ir/__init__.py index 6d66abf4cd..5315be3db6 100644 --- a/esphome/components/climate_ir/__init__.py +++ b/esphome/components/climate_ir/__init__.py @@ -3,7 +3,12 @@ import logging import esphome.codegen as cg from esphome.components import climate, remote_base, sensor import esphome.config_validation as cv -from esphome.const import CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT +from esphome.const import ( + CONF_HUMIDITY_SENSOR, + CONF_SENSOR, + CONF_SUPPORTS_COOL, + CONF_SUPPORTS_HEAT, +) from esphome.cpp_generator import MockObjClass _LOGGER = logging.getLogger(__name__) @@ -32,6 +37,7 @@ def climate_ir_schema( cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor), } ) .extend(cv.COMPONENT_SCHEMA) @@ -61,6 +67,9 @@ async def register_climate_ir(var, config): if sensor_id := config.get(CONF_SENSOR): sens = await cg.get_variable(sensor_id) cg.add(var.set_sensor(sens)) + if sensor_id := config.get(CONF_HUMIDITY_SENSOR): + sens = await cg.get_variable(sensor_id) + cg.add(var.set_humidity_sensor(sens)) async def new_climate_ir(config, *args): diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index 2b95792a6c..50c8d459b0 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -11,7 +11,9 @@ climate::ClimateTraits ClimateIR::traits() { if (this->sensor_ != nullptr) { traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE); } - + if (this->humidity_sensor_ != nullptr) { + traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY); + } traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); if (this->supports_cool_) traits.add_supported_mode(climate::CLIMATE_MODE_COOL); @@ -39,9 +41,16 @@ void ClimateIR::setup() { this->publish_state(); }); this->current_temperature = this->sensor_->state; - } else { - this->current_temperature = NAN; } + if (this->humidity_sensor_ != nullptr) { + this->humidity_sensor_->add_on_state_callback([this](float state) { + this->current_humidity = state; + // current humidity changed, publish state + this->publish_state(); + }); + this->current_humidity = this->humidity_sensor_->state; + } + // restore set points auto restore = this->restore_state_(); if (restore.has_value()) { diff --git a/esphome/components/climate_ir/climate_ir.h b/esphome/components/climate_ir/climate_ir.h index 62a43f0b2d..ac76d33853 100644 --- a/esphome/components/climate_ir/climate_ir.h +++ b/esphome/components/climate_ir/climate_ir.h @@ -43,6 +43,7 @@ class ClimateIR : public Component, void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } + void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; } protected: float minimum_temperature_, maximum_temperature_, temperature_step_; @@ -67,6 +68,7 @@ class ClimateIR : public Component, climate::ClimatePresetMask presets_{}; sensor::Sensor *sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace climate_ir diff --git a/tests/components/climate_ir_lg/common.yaml b/tests/components/climate_ir_lg/common.yaml index da0d656b21..37011b16ee 100644 --- a/tests/components/climate_ir_lg/common.yaml +++ b/tests/components/climate_ir_lg/common.yaml @@ -1,4 +1,16 @@ +sensor: + - platform: template + id: temp_sensor + lambda: return 22.0; + update_interval: 60s + - platform: template + id: humidity_sensor + lambda: return 50.0; + update_interval: 60s + climate: - platform: climate_ir_lg name: LG Climate transmitter_id: xmitr + sensor: temp_sensor + humidity_sensor: humidity_sensor From 1accb4ff3488c1763da426faea38f73408714332 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 20 Nov 2025 10:58:21 -0500 Subject: [PATCH 0314/1145] [ltr501][ltr_als_ps] Rename enum to avoid collision with lwip defines (#12017) --- esphome/components/ltr501/ltr501.cpp | 10 +++++----- esphome/components/ltr501/ltr501.h | 4 ++-- esphome/components/ltr_als_ps/ltr_als_ps.cpp | 12 ++++++------ esphome/components/ltr_als_ps/ltr_als_ps.h | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/esphome/components/ltr501/ltr501.cpp b/esphome/components/ltr501/ltr501.cpp index be5a4ddccf..04de91e362 100644 --- a/esphome/components/ltr501/ltr501.cpp +++ b/esphome/components/ltr501/ltr501.cpp @@ -174,7 +174,7 @@ void LTRAlsPs501Component::loop() { break; case State::WAITING_FOR_DATA: - if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) { + if (this->is_als_data_ready_(this->als_readings_) == LtrDataAvail::LTR_DATA_OK) { tries = 0; ESP_LOGV(TAG, "Reading sensor data assuming gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain), get_itime_ms(this->als_readings_.integration_time)); @@ -379,18 +379,18 @@ void LTRAlsPs501Component::configure_integration_time_(IntegrationTime501 time) } } -DataAvail LTRAlsPs501Component::is_als_data_ready_(AlsReadings &data) { +LtrDataAvail LTRAlsPs501Component::is_als_data_ready_(AlsReadings &data) { AlsPsStatusRegister als_status{0}; als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get(); if (!als_status.als_new_data) - return DataAvail::NO_DATA; + return LtrDataAvail::LTR_NO_DATA; ESP_LOGV(TAG, "Data ready, reported gain is %.0fx", get_gain_coeff(als_status.gain)); if (data.gain != als_status.gain) { ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain)); - return DataAvail::BAD_DATA; + return LtrDataAvail::LTR_BAD_DATA; } data.gain = als_status.gain; - return DataAvail::DATA_OK; + return LtrDataAvail::LTR_DATA_OK; } void LTRAlsPs501Component::read_sensor_data_(AlsReadings &data) { diff --git a/esphome/components/ltr501/ltr501.h b/esphome/components/ltr501/ltr501.h index 849ff6bc23..02c025da30 100644 --- a/esphome/components/ltr501/ltr501.h +++ b/esphome/components/ltr501/ltr501.h @@ -11,7 +11,7 @@ namespace esphome { namespace ltr501 { -enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK }; +enum LtrDataAvail : uint8_t { LTR_NO_DATA, LTR_BAD_DATA, LTR_DATA_OK }; enum LtrType : uint8_t { LTR_TYPE_UNKNOWN = 0, @@ -106,7 +106,7 @@ class LTRAlsPs501Component : public PollingComponent, public i2c::I2CDevice { void configure_als_(); void configure_integration_time_(IntegrationTime501 time); void configure_gain_(AlsGain501 gain); - DataAvail is_als_data_ready_(AlsReadings &data); + LtrDataAvail is_als_data_ready_(AlsReadings &data); void read_sensor_data_(AlsReadings &data); bool are_adjustments_required_(AlsReadings &data); void apply_lux_calculation_(AlsReadings &data); diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.cpp b/esphome/components/ltr_als_ps/ltr_als_ps.cpp index c3ea5848c8..f9c1474c85 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.cpp +++ b/esphome/components/ltr_als_ps/ltr_als_ps.cpp @@ -165,7 +165,7 @@ void LTRAlsPsComponent::loop() { break; case State::WAITING_FOR_DATA: - if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) { + if (this->is_als_data_ready_(this->als_readings_) == LtrDataAvail::LTR_DATA_OK) { tries = 0; ESP_LOGV(TAG, "Reading sensor data having gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain), get_itime_ms(this->als_readings_.integration_time)); @@ -376,23 +376,23 @@ void LTRAlsPsComponent::configure_integration_time_(IntegrationTime time) { } } -DataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) { +LtrDataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) { AlsPsStatusRegister als_status{0}; als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get(); if (!als_status.als_new_data) - return DataAvail::NO_DATA; + return LtrDataAvail::LTR_NO_DATA; if (als_status.data_invalid) { ESP_LOGW(TAG, "Data available but not valid"); - return DataAvail::BAD_DATA; + return LtrDataAvail::LTR_BAD_DATA; } ESP_LOGV(TAG, "Data ready, reported gain is %.0f", get_gain_coeff(als_status.gain)); if (data.gain != als_status.gain) { ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain)); - return DataAvail::BAD_DATA; + return LtrDataAvail::LTR_BAD_DATA; } - return DataAvail::DATA_OK; + return LtrDataAvail::LTR_DATA_OK; } void LTRAlsPsComponent::read_sensor_data_(AlsReadings &data) { diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.h b/esphome/components/ltr_als_ps/ltr_als_ps.h index 2c768009ab..c6052300de 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.h +++ b/esphome/components/ltr_als_ps/ltr_als_ps.h @@ -11,7 +11,7 @@ namespace esphome { namespace ltr_als_ps { -enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK }; +enum LtrDataAvail : uint8_t { LTR_NO_DATA, LTR_BAD_DATA, LTR_DATA_OK }; enum LtrType : uint8_t { LTR_TYPE_UNKNOWN = 0, @@ -106,7 +106,7 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { void configure_als_(); void configure_integration_time_(IntegrationTime time); void configure_gain_(AlsGain gain); - DataAvail is_als_data_ready_(AlsReadings &data); + LtrDataAvail is_als_data_ready_(AlsReadings &data); void read_sensor_data_(AlsReadings &data); bool are_adjustments_required_(AlsReadings &data); void apply_lux_calculation_(AlsReadings &data); From a1e507baf817f99d81593130796dff92b25b948d Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 20 Nov 2025 12:10:28 -0500 Subject: [PATCH 0315/1145] [cst816][packet_transport][udp][wake_on_lan] Fix error messages (#12019) --- .../cst816/touchscreen/cst816_touchscreen.cpp | 2 +- .../components/packet_transport/packet_transport.cpp | 2 +- esphome/components/udp/udp_component.cpp | 10 +++++----- esphome/components/wake_on_lan/wake_on_lan.cpp | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp index 0ba2d9df94..8ed9fa3f87 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp @@ -19,8 +19,8 @@ void CST816Touchscreen::continue_setup_() { case CST816T_CHIP_ID: break; default: - this->mark_failed(); this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_).c_str()); + this->mark_failed(); return; } this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION); diff --git a/esphome/components/packet_transport/packet_transport.cpp b/esphome/components/packet_transport/packet_transport.cpp index 8bde4ee505..857b40ca0e 100644 --- a/esphome/components/packet_transport/packet_transport.cpp +++ b/esphome/components/packet_transport/packet_transport.cpp @@ -195,8 +195,8 @@ static void add(std::vector &vec, const char *str) { void PacketTransport::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"); + this->mark_failed(); return; } this->resend_ping_key_ = this->ping_pong_enable_; diff --git a/esphome/components/udp/udp_component.cpp b/esphome/components/udp/udp_component.cpp index 8a9ce612b4..7714793e1c 100644 --- a/esphome/components/udp/udp_component.cpp +++ b/esphome/components/udp/udp_component.cpp @@ -21,8 +21,8 @@ void UDPComponent::setup() { if (this->should_broadcast_) { 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"); + this->mark_failed(); return; } int enable = 1; @@ -41,15 +41,15 @@ void UDPComponent::setup() { 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"); + this->mark_failed(); 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"); + this->mark_failed(); return; } int enable = 1; @@ -73,8 +73,8 @@ void UDPComponent::setup() { err = this->listen_socket_->setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq)); if (err < 0) { ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno); - this->mark_failed(); this->status_set_error("Failed to set IP_ADD_MEMBERSHIP"); + this->mark_failed(); return; } } @@ -82,8 +82,8 @@ void UDPComponent::setup() { 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"); + this->mark_failed(); return; } } diff --git a/esphome/components/wake_on_lan/wake_on_lan.cpp b/esphome/components/wake_on_lan/wake_on_lan.cpp index adf5a080e5..7993abd7e7 100644 --- a/esphome/components/wake_on_lan/wake_on_lan.cpp +++ b/esphome/components/wake_on_lan/wake_on_lan.cpp @@ -67,8 +67,8 @@ void WakeOnLanButton::setup() { #if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) 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"); + this->mark_failed(); return; } int enable = 1; From 01addeae080b0cce517ed296d6ea4a998d454ae6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:11:41 -0600 Subject: [PATCH 0316/1145] Bump actions/checkout from 5.0.1 to 6.0.0 (#12022) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto-label-pr.yml | 2 +- .github/workflows/ci-api-proto.yml | 2 +- .github/workflows/ci-clang-tidy-hash.yml | 2 +- .github/workflows/ci-docker.yml | 2 +- .../workflows/ci-memory-impact-comment.yml | 2 +- .github/workflows/ci.yml | 30 +++++++++---------- .github/workflows/codeql.yml | 2 +- .github/workflows/release.yml | 8 ++--- .github/workflows/sync-device-classes.yml | 4 +-- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/auto-label-pr.yml b/.github/workflows/auto-label-pr.yml index fb284c9d8c..8d8e08a5fc 100644 --- a/.github/workflows/auto-label-pr.yml +++ b/.github/workflows/auto-label-pr.yml @@ -22,7 +22,7 @@ jobs: if: github.event.action != 'labeled' || github.event.sender.type != 'Bot' steps: - name: Checkout - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Generate a token id: generate-token diff --git a/.github/workflows/ci-api-proto.yml b/.github/workflows/ci-api-proto.yml index be5af1aff1..b377ca76d8 100644 --- a/.github/workflows/ci-api-proto.yml +++ b/.github/workflows/ci-api-proto.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: diff --git a/.github/workflows/ci-clang-tidy-hash.yml b/.github/workflows/ci-clang-tidy-hash.yml index aebf07949d..9556b99015 100644 --- a/.github/workflows/ci-clang-tidy-hash.yml +++ b/.github/workflows/ci-clang-tidy-hash.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 9bb983b993..5287d92b10 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -43,7 +43,7 @@ jobs: - "docker" # - "lint" steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: diff --git a/.github/workflows/ci-memory-impact-comment.yml b/.github/workflows/ci-memory-impact-comment.yml index c82ae30f55..6ca58e252e 100644 --- a/.github/workflows/ci-memory-impact-comment.yml +++ b/.github/workflows/ci-memory-impact-comment.yml @@ -49,7 +49,7 @@ jobs: - name: Check out code from base repository if: steps.pr.outputs.skip != 'true' - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: # Always check out from the base repository (esphome/esphome), never from forks # Use the PR's target branch to ensure we run trusted code from the main repo diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5293c62d34..9c2fab0912 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: cache-key: ${{ steps.cache-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Generate cache-key id: cache-key run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT @@ -70,7 +70,7 @@ jobs: if: needs.determine-jobs.outputs.python-linters == 'true' steps: - name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -91,7 +91,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -132,7 +132,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Restore Python id: restore-python uses: ./.github/actions/restore-python @@ -183,7 +183,7 @@ jobs: component-test-batches: ${{ steps.determine.outputs.component-test-batches }} steps: - name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: # Fetch enough history to find the merge base fetch-depth: 2 @@ -237,7 +237,7 @@ jobs: if: needs.determine-jobs.outputs.integration-tests == 'true' steps: - name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python 3.13 id: python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 @@ -273,7 +273,7 @@ jobs: if: github.event_name == 'pull_request' && (needs.determine-jobs.outputs.cpp-unit-tests-run-all == 'true' || needs.determine-jobs.outputs.cpp-unit-tests-components != '[]') steps: - name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Restore Python uses: ./.github/actions/restore-python @@ -321,7 +321,7 @@ jobs: steps: - name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: # Need history for HEAD~1 to work for checking changed files fetch-depth: 2 @@ -400,7 +400,7 @@ jobs: GH_TOKEN: ${{ github.token }} steps: - name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: # Need history for HEAD~1 to work for checking changed files fetch-depth: 2 @@ -489,7 +489,7 @@ jobs: steps: - name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: # Need history for HEAD~1 to work for checking changed files fetch-depth: 2 @@ -577,7 +577,7 @@ jobs: version: 1.0 - name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -662,7 +662,7 @@ jobs: if: github.event_name == 'pull_request' && !startsWith(github.base_ref, 'beta') && !startsWith(github.base_ref, 'release') steps: - name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -688,7 +688,7 @@ jobs: skip: ${{ steps.check-script.outputs.skip }} steps: - name: Check out target branch - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: ref: ${{ github.base_ref }} @@ -840,7 +840,7 @@ jobs: flash_usage: ${{ steps.extract.outputs.flash_usage }} steps: - name: Check out PR branch - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -908,7 +908,7 @@ jobs: GH_TOKEN: ${{ github.token }} steps: - name: Check out code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 21fff10c95..80fab8819a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -54,7 +54,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a064f6ef3a..497ecd29e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: branch_build: ${{ steps.tag.outputs.branch_build }} deploy_env: ${{ steps.tag.outputs.deploy_env }} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Get tag id: tag # yamllint disable rule:line-length @@ -60,7 +60,7 @@ jobs: contents: read id-token: write steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: @@ -92,7 +92,7 @@ jobs: os: "ubuntu-24.04-arm" steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: @@ -168,7 +168,7 @@ jobs: - ghcr - dockerhub steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Download digests uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index 4fc287b067..2e36dc517d 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -13,10 +13,10 @@ jobs: if: github.repository == 'esphome/esphome' steps: - name: Checkout - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Checkout Home Assistant - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: repository: home-assistant/core path: lib/home-assistant From 0dea7a23e3d0a035cdee7ccfeea303eb083da072 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 21 Nov 2025 07:39:59 -0500 Subject: [PATCH 0317/1145] [jsn_sr04t] Fix model AJ_SR04M (#11992) --- esphome/components/jsn_sr04t/jsn_sr04t.cpp | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/esphome/components/jsn_sr04t/jsn_sr04t.cpp b/esphome/components/jsn_sr04t/jsn_sr04t.cpp index 077d4e58ea..84181dac48 100644 --- a/esphome/components/jsn_sr04t/jsn_sr04t.cpp +++ b/esphome/components/jsn_sr04t/jsn_sr04t.cpp @@ -10,7 +10,7 @@ namespace jsn_sr04t { static const char *const TAG = "jsn_sr04t.sensor"; void Jsnsr04tComponent::update() { - this->write_byte(0x55); + this->write_byte((this->model_ == AJ_SR04M) ? 0x01 : 0x55); ESP_LOGV(TAG, "Request read out from sensor"); } @@ -31,19 +31,10 @@ void Jsnsr04tComponent::loop() { } void Jsnsr04tComponent::check_buffer_() { - uint8_t checksum = 0; - switch (this->model_) { - case JSN_SR04T: - checksum = this->buffer_[0] + this->buffer_[1] + this->buffer_[2]; - break; - case AJ_SR04M: - checksum = this->buffer_[1] + this->buffer_[2]; - break; - } - + uint8_t checksum = this->buffer_[0] + this->buffer_[1] + this->buffer_[2]; if (this->buffer_[3] == checksum) { uint16_t distance = encode_uint16(this->buffer_[1], this->buffer_[2]); - if (distance > 250) { + if (distance > ((this->model_ == AJ_SR04M) ? 200 : 250)) { float meters = distance / 1000.0f; ESP_LOGV(TAG, "Distance from sensor: %umm, %.3fm", distance, meters); this->publish_state(meters); From 150e26dc2bfbf2155459c39c5f05873301254968 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 21 Nov 2025 06:41:48 -0600 Subject: [PATCH 0318/1145] [cst816][http_request] Fix status_set_error() dangling pointer bugs (#12033) --- .../cst816/touchscreen/cst816_touchscreen.cpp | 3 ++- .../http_request/update/http_request_update.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp index 8ed9fa3f87..0560f1b475 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp @@ -19,7 +19,8 @@ void CST816Touchscreen::continue_setup_() { case CST816T_CHIP_ID: break; default: - this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_).c_str()); + ESP_LOGE(TAG, "Unknown chip ID: 0x%02X", this->chip_id_); + this->status_set_error("Unknown chip ID"); this->mark_failed(); return; } diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 06aa6da6a4..9dbf8d181a 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -49,18 +49,18 @@ void HttpRequestUpdate::update_task(void *params) { auto container = this_update->request_parent_->get(this_update->source_url_); if (container == nullptr || container->status_code != HTTP_STATUS_OK) { - std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str()); + ESP_LOGE(TAG, "Failed to fetch manifest from %s", this_update->source_url_.c_str()); // Defer to main loop to avoid race condition on component_state_ read-modify-write - this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); }); + this_update->defer([this_update]() { this_update->status_set_error("Failed to fetch manifest"); }); UPDATE_RETURN; } RAMAllocator allocator; uint8_t *data = allocator.allocate(container->content_length); if (data == nullptr) { - std::string msg = str_sprintf("Failed to allocate %zu bytes for manifest", container->content_length); + ESP_LOGE(TAG, "Failed to allocate %zu bytes for manifest", container->content_length); // Defer to main loop to avoid race condition on component_state_ read-modify-write - this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); }); + this_update->defer([this_update]() { this_update->status_set_error("Failed to allocate memory for manifest"); }); container->end(); UPDATE_RETURN; } @@ -121,9 +121,9 @@ void HttpRequestUpdate::update_task(void *params) { } if (!valid) { - std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str()); + ESP_LOGE(TAG, "Failed to parse JSON from %s", this_update->source_url_.c_str()); // Defer to main loop to avoid race condition on component_state_ read-modify-write - this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); }); + this_update->defer([this_update]() { this_update->status_set_error("Failed to parse manifest JSON"); }); UPDATE_RETURN; } From 972b7e84fe800ea9dbd6b69d6f4bcc023b89e026 Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Fri, 21 Nov 2025 14:38:44 +0100 Subject: [PATCH 0319/1145] [tests] Fix mipi_spi test board (#12031) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- tests/component_tests/mipi_spi/test_init.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/component_tests/mipi_spi/test_init.py b/tests/component_tests/mipi_spi/test_init.py index e68f6fbfba..56a52df2ab 100644 --- a/tests/component_tests/mipi_spi/test_init.py +++ b/tests/component_tests/mipi_spi/test_init.py @@ -220,7 +220,7 @@ def test_esp32s3_specific_errors( set_core_config( PlatformFramework.ESP32_IDF, - platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32S3}, + platform_data={KEY_BOARD: "esp32-s3-devkitc-1", KEY_VARIANT: VARIANT_ESP32S3}, ) with pytest.raises(cv.Invalid, match=error_match): @@ -250,7 +250,7 @@ def test_custom_model_with_all_options( """Test custom model configuration with all available options.""" set_core_config( PlatformFramework.ESP32_IDF, - platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32S3}, + platform_data={KEY_BOARD: "esp32-s3-devkitc-1", KEY_VARIANT: VARIANT_ESP32S3}, ) run_schema_validation( @@ -293,7 +293,7 @@ def test_all_predefined_models( """Test all predefined display models validate successfully with appropriate defaults.""" set_core_config( PlatformFramework.ESP32_IDF, - platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32S3}, + platform_data={KEY_BOARD: "esp32-s3-devkitc-1", KEY_VARIANT: VARIANT_ESP32S3}, ) # Enable PSRAM which is required for some models From 782aee92a77a2a3d991c97f671c259b1b961883e Mon Sep 17 00:00:00 2001 From: Marko Draca Date: Fri, 21 Nov 2025 20:50:07 +0100 Subject: [PATCH 0320/1145] [mcp3204] differential mode support (#7436) Co-authored-by: marko Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/mcp3204/mcp3204.cpp | 21 ++++++++++++------- esphome/components/mcp3204/mcp3204.h | 2 +- esphome/components/mcp3204/sensor/__init__.py | 3 +++ .../mcp3204/sensor/mcp3204_sensor.cpp | 5 ++--- .../mcp3204/sensor/mcp3204_sensor.h | 3 ++- tests/components/mcp3204/common.yaml | 16 +++++++++++++- 6 files changed, 36 insertions(+), 14 deletions(-) diff --git a/esphome/components/mcp3204/mcp3204.cpp b/esphome/components/mcp3204/mcp3204.cpp index 4bb0cbed76..f0dd171a14 100644 --- a/esphome/components/mcp3204/mcp3204.cpp +++ b/esphome/components/mcp3204/mcp3204.cpp @@ -16,16 +16,21 @@ void MCP3204::dump_config() { ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_); } -float MCP3204::read_data(uint8_t pin) { - uint8_t adc_primary_config = 0b00000110 | (pin >> 2); - uint8_t adc_secondary_config = pin << 6; +float MCP3204::read_data(uint8_t pin, bool differential) { + uint8_t command, b0, b1; + + command = (1 << 6) | // start bit + ((differential ? 0 : 1) << 5) | // single or differential bit + ((pin & 0x07) << 2); // pin + this->enable(); - this->transfer_byte(adc_primary_config); - uint8_t adc_primary_byte = this->transfer_byte(adc_secondary_config); - uint8_t adc_secondary_byte = this->transfer_byte(0x00); + this->transfer_byte(command); + b0 = this->transfer_byte(0x00); + b1 = this->transfer_byte(0x00); this->disable(); - uint16_t digital_value = (adc_primary_byte << 8 | adc_secondary_byte) & 0b111111111111; - return float(digital_value) / 4096.000 * this->reference_voltage_; + + uint16_t digital_value = encode_uint16(b0, b1) >> 4; + return float(digital_value) / 4096.000 * this->reference_voltage_; // in V } } // namespace mcp3204 diff --git a/esphome/components/mcp3204/mcp3204.h b/esphome/components/mcp3204/mcp3204.h index 27261aa373..6287263a2a 100644 --- a/esphome/components/mcp3204/mcp3204.h +++ b/esphome/components/mcp3204/mcp3204.h @@ -18,7 +18,7 @@ class MCP3204 : public Component, void setup() override; void dump_config() override; float get_setup_priority() const override; - float read_data(uint8_t pin); + float read_data(uint8_t pin, bool differential); protected: float reference_voltage_; diff --git a/esphome/components/mcp3204/sensor/__init__.py b/esphome/components/mcp3204/sensor/__init__.py index a4b177cbcf..5f9aa9fdb6 100644 --- a/esphome/components/mcp3204/sensor/__init__.py +++ b/esphome/components/mcp3204/sensor/__init__.py @@ -13,6 +13,7 @@ MCP3204Sensor = mcp3204_ns.class_( "MCP3204Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler ) CONF_MCP3204_ID = "mcp3204_id" +CONF_DIFF_MODE = "diff_mode" CONFIG_SCHEMA = ( sensor.sensor_schema(MCP3204Sensor) @@ -20,6 +21,7 @@ CONFIG_SCHEMA = ( { cv.GenerateID(CONF_MCP3204_ID): cv.use_id(MCP3204), cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7), + cv.Optional(CONF_DIFF_MODE, default=False): cv.boolean, } ) .extend(cv.polling_component_schema("60s")) @@ -30,6 +32,7 @@ async def to_code(config): var = cg.new_Pvariable( config[CONF_ID], config[CONF_NUMBER], + config[CONF_DIFF_MODE], ) await cg.register_parented(var, config[CONF_MCP3204_ID]) await cg.register_component(var, config) diff --git a/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp b/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp index ce0fd25462..4c4abef4a7 100644 --- a/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp +++ b/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp @@ -7,16 +7,15 @@ namespace mcp3204 { static const char *const TAG = "mcp3204.sensor"; -MCP3204Sensor::MCP3204Sensor(uint8_t pin) : pin_(pin) {} - float MCP3204Sensor::get_setup_priority() const { return setup_priority::DATA; } void MCP3204Sensor::dump_config() { LOG_SENSOR("", "MCP3204 Sensor", this); ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); + ESP_LOGCONFIG(TAG, " Differential Mode: %s", YESNO(this->differential_mode_)); LOG_UPDATE_INTERVAL(this); } -float MCP3204Sensor::sample() { return this->parent_->read_data(this->pin_); } +float MCP3204Sensor::sample() { return this->parent_->read_data(this->pin_, this->differential_mode_); } void MCP3204Sensor::update() { this->publish_state(this->sample()); } } // namespace mcp3204 diff --git a/esphome/components/mcp3204/sensor/mcp3204_sensor.h b/esphome/components/mcp3204/sensor/mcp3204_sensor.h index 21c45590ab..5665b80b98 100644 --- a/esphome/components/mcp3204/sensor/mcp3204_sensor.h +++ b/esphome/components/mcp3204/sensor/mcp3204_sensor.h @@ -15,7 +15,7 @@ class MCP3204Sensor : public PollingComponent, public sensor::Sensor, public voltage_sampler::VoltageSampler { public: - MCP3204Sensor(uint8_t pin); + MCP3204Sensor(uint8_t pin, bool differential_mode) : pin_(pin), differential_mode_(differential_mode) {} void update() override; void dump_config() override; @@ -24,6 +24,7 @@ class MCP3204Sensor : public PollingComponent, protected: uint8_t pin_; + bool differential_mode_; }; } // namespace mcp3204 diff --git a/tests/components/mcp3204/common.yaml b/tests/components/mcp3204/common.yaml index eca6ec44f4..9750f0af8e 100644 --- a/tests/components/mcp3204/common.yaml +++ b/tests/components/mcp3204/common.yaml @@ -4,7 +4,21 @@ mcp3204: sensor: - platform: mcp3204 - id: mcp3204_sensor + id: mcp3204_default_single_0 mcp3204_id: mcp3204_hub number: 0 update_interval: 5s + + - platform: mcp3204 + id: mcp3204_single_0 + mcp3204_id: mcp3204_hub + number: 0 + diff_mode: false + update_interval: 5s + + - platform: mcp3204 + id: mcp3204_diff_0_1 + mcp3204_id: mcp3204_hub + number: 0 + diff_mode: true + update_interval: 5s From 3f6f2d7d650feee79896b13903c7a26e98559d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Fri, 21 Nov 2025 21:28:42 +0100 Subject: [PATCH 0321/1145] [bm8563] Add bm8563 component (#11616) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/bm8563/__init__.py | 1 + esphome/components/bm8563/bm8563.cpp | 198 ++++++++++++++++++ esphome/components/bm8563/bm8563.h | 57 +++++ esphome/components/bm8563/time.py | 80 +++++++ tests/components/bm8563/common.yaml | 10 + tests/components/bm8563/test.esp32-ard.yaml | 4 + tests/components/bm8563/test.esp32-idf.yaml | 4 + tests/components/bm8563/test.esp8266-ard.yaml | 4 + tests/components/bm8563/test.rp2040-ard.yaml | 4 + 10 files changed, 363 insertions(+) create mode 100644 esphome/components/bm8563/__init__.py create mode 100644 esphome/components/bm8563/bm8563.cpp create mode 100644 esphome/components/bm8563/bm8563.h create mode 100644 esphome/components/bm8563/time.py create mode 100644 tests/components/bm8563/common.yaml create mode 100644 tests/components/bm8563/test.esp32-ard.yaml create mode 100644 tests/components/bm8563/test.esp32-idf.yaml create mode 100644 tests/components/bm8563/test.esp8266-ard.yaml create mode 100644 tests/components/bm8563/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index c3d8f4350f..d6ec7b882e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -72,6 +72,7 @@ esphome/components/bl0942/* @dbuezas @dwmw2 esphome/components/ble_client/* @buxtronix @clydebarrow esphome/components/ble_nus/* @tomaszduda23 esphome/components/bluetooth_proxy/* @bdraco @jesserockz +esphome/components/bm8563/* @abmantis esphome/components/bme280_base/* @esphome/core esphome/components/bme280_spi/* @apbodrov esphome/components/bme680_bsec/* @trvrnrth diff --git a/esphome/components/bm8563/__init__.py b/esphome/components/bm8563/__init__.py new file mode 100644 index 0000000000..20254a8b00 --- /dev/null +++ b/esphome/components/bm8563/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@abmantis"] diff --git a/esphome/components/bm8563/bm8563.cpp b/esphome/components/bm8563/bm8563.cpp new file mode 100644 index 0000000000..07831485c1 --- /dev/null +++ b/esphome/components/bm8563/bm8563.cpp @@ -0,0 +1,198 @@ +#include "bm8563.h" +#include "esphome/core/log.h" + +namespace esphome::bm8563 { + +static const char *const TAG = "bm8563"; + +static constexpr uint8_t CONTROL_STATUS_1_REG = 0x00; +static constexpr uint8_t CONTROL_STATUS_2_REG = 0x01; +static constexpr uint8_t TIME_FIRST_REG = 0x02; // Time uses reg 2, 3, 4 +static constexpr uint8_t DATE_FIRST_REG = 0x05; // Date uses reg 5, 6, 7, 8 +static constexpr uint8_t TIMER_CONTROL_REG = 0x0E; +static constexpr uint8_t TIMER_VALUE_REG = 0x0F; +static constexpr uint8_t CLOCK_1_HZ = 0x82; +static constexpr uint8_t CLOCK_1_60_HZ = 0x83; +// Maximum duration: 255 minutes (at 1/60 Hz) = 15300 seconds +static constexpr uint32_t MAX_TIMER_DURATION_S = 255 * 60; + +void BM8563::setup() { + if (!this->write_byte_16(CONTROL_STATUS_1_REG, 0)) { + this->mark_failed(); + return; + } +} + +void BM8563::update() { this->read_time(); } + +void BM8563::dump_config() { + ESP_LOGCONFIG(TAG, "BM8563:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); + } +} + +void BM8563::start_timer(uint32_t duration_s) { + this->clear_irq_(); + this->set_timer_irq_(duration_s); +} + +void BM8563::write_time() { + auto now = time::RealTimeClock::utcnow(); + if (!now.is_valid()) { + ESP_LOGE(TAG, "Invalid system time, not syncing to RTC."); + return; + } + + ESP_LOGD(TAG, "Writing time: %i-%i-%i %i, %i:%i:%i", now.year, now.month, now.day_of_month, now.day_of_week, now.hour, + now.minute, now.second); + + this->set_time_(now); + this->set_date_(now); +} + +void BM8563::read_time() { + ESPTime rtc_time; + this->get_time_(rtc_time); + this->get_date_(rtc_time); + rtc_time.day_of_year = 1; // unused by recalc_timestamp_utc, but needs to be valid + ESP_LOGD(TAG, "Read time: %i-%i-%i %i, %i:%i:%i", rtc_time.year, rtc_time.month, rtc_time.day_of_month, + rtc_time.day_of_week, rtc_time.hour, rtc_time.minute, rtc_time.second); + + rtc_time.recalc_timestamp_utc(false); + if (!rtc_time.is_valid()) { + ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock."); + return; + } + time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp); +} + +uint8_t BM8563::bcd2_to_byte_(uint8_t value) { + const uint8_t tmp = ((value & 0xF0) >> 0x4) * 10; + return tmp + (value & 0x0F); +} + +uint8_t BM8563::byte_to_bcd2_(uint8_t value) { + const uint8_t bcdhigh = value / 10; + value -= bcdhigh * 10; + return (bcdhigh << 4) | value; +} + +void BM8563::get_time_(ESPTime &time) { + uint8_t buf[3] = {0}; + this->read_register(TIME_FIRST_REG, buf, 3); + + time.second = this->bcd2_to_byte_(buf[0] & 0x7f); + time.minute = this->bcd2_to_byte_(buf[1] & 0x7f); + time.hour = this->bcd2_to_byte_(buf[2] & 0x3f); +} + +void BM8563::set_time_(const ESPTime &time) { + uint8_t buf[3] = {this->byte_to_bcd2_(time.second), this->byte_to_bcd2_(time.minute), this->byte_to_bcd2_(time.hour)}; + this->write_register_(TIME_FIRST_REG, buf, 3); +} + +void BM8563::get_date_(ESPTime &time) { + uint8_t buf[4] = {0}; + this->read_register(DATE_FIRST_REG, buf, sizeof(buf)); + + time.day_of_month = this->bcd2_to_byte_(buf[0] & 0x3f); + time.day_of_week = this->bcd2_to_byte_(buf[1] & 0x07); + time.month = this->bcd2_to_byte_(buf[2] & 0x1f); + + uint8_t year_byte = this->bcd2_to_byte_(buf[3] & 0xff); + + if (buf[2] & 0x80) { + time.year = 1900 + year_byte; + } else { + time.year = 2000 + year_byte; + } +} + +void BM8563::set_date_(const ESPTime &time) { + uint8_t buf[4] = { + this->byte_to_bcd2_(time.day_of_month), + this->byte_to_bcd2_(time.day_of_week), + this->byte_to_bcd2_(time.month), + this->byte_to_bcd2_(time.year % 100), + }; + + if (time.year < 2000) { + buf[2] = buf[2] | 0x80; + } + + this->write_register_(DATE_FIRST_REG, buf, 4); +} + +void BM8563::write_byte_(uint8_t reg, uint8_t value) { + if (!this->write_byte(reg, value)) { + ESP_LOGE(TAG, "Failed to write byte 0x%02X with value 0x%02X", reg, value); + } +} + +void BM8563::write_register_(uint8_t reg, const uint8_t *data, size_t len) { + if (auto error = this->write_register(reg, data, len); error != i2c::ErrorCode::NO_ERROR) { + ESP_LOGE(TAG, "Failed to write register 0x%02X with %zu bytes", reg, len); + } +} + +optional BM8563::read_register_(uint8_t reg) { + uint8_t data; + if (auto error = this->read_register(reg, &data, 1); error != i2c::ErrorCode::NO_ERROR) { + ESP_LOGE(TAG, "Failed to read register 0x%02X", reg); + return {}; + } + return data; +} + +void BM8563::set_timer_irq_(uint32_t duration_s) { + ESP_LOGI(TAG, "Timer Duration: %u s", duration_s); + + if (duration_s > MAX_TIMER_DURATION_S) { + ESP_LOGW(TAG, "Timer duration %u s exceeds maximum %u s", duration_s, MAX_TIMER_DURATION_S); + return; + } + + if (duration_s > 255) { + uint8_t duration_minutes = duration_s / 60; + this->write_byte_(TIMER_VALUE_REG, duration_minutes); + this->write_byte_(TIMER_CONTROL_REG, CLOCK_1_60_HZ); + } else { + this->write_byte_(TIMER_VALUE_REG, duration_s); + this->write_byte_(TIMER_CONTROL_REG, CLOCK_1_HZ); + } + + auto maybe_ctrl_status_2 = this->read_register_(CONTROL_STATUS_2_REG); + if (!maybe_ctrl_status_2.has_value()) { + ESP_LOGE(TAG, "Failed to read CONTROL_STATUS_2_REG"); + return; + } + uint8_t ctrl_status_2_reg_value = maybe_ctrl_status_2.value(); + ctrl_status_2_reg_value |= (1 << 0); + ctrl_status_2_reg_value &= ~(1 << 7); + this->write_byte_(CONTROL_STATUS_2_REG, ctrl_status_2_reg_value); +} + +void BM8563::clear_irq_() { + auto maybe_data = this->read_register_(CONTROL_STATUS_2_REG); + if (!maybe_data.has_value()) { + ESP_LOGE(TAG, "Failed to read CONTROL_STATUS_2_REG"); + return; + } + uint8_t data = maybe_data.value(); + this->write_byte_(CONTROL_STATUS_2_REG, data & 0xf3); +} + +void BM8563::disable_irq_() { + this->clear_irq_(); + auto maybe_data = this->read_register_(CONTROL_STATUS_2_REG); + if (!maybe_data.has_value()) { + ESP_LOGE(TAG, "Failed to read CONTROL_STATUS_2_REG"); + return; + } + uint8_t data = maybe_data.value(); + this->write_byte_(CONTROL_STATUS_2_REG, data & 0xfc); +} + +} // namespace esphome::bm8563 diff --git a/esphome/components/bm8563/bm8563.h b/esphome/components/bm8563/bm8563.h new file mode 100644 index 0000000000..eda2d1b3c0 --- /dev/null +++ b/esphome/components/bm8563/bm8563.h @@ -0,0 +1,57 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/time/real_time_clock.h" + +namespace esphome::bm8563 { + +class BM8563 : public time::RealTimeClock, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + void dump_config() override; + + void write_time(); + void read_time(); + void start_timer(uint32_t duration_s); + + private: + void get_time_(ESPTime &time); + void get_date_(ESPTime &time); + + void set_time_(const ESPTime &time); + void set_date_(const ESPTime &time); + + void set_timer_irq_(uint32_t duration_s); + void clear_irq_(); + void disable_irq_(); + + void write_byte_(uint8_t reg, uint8_t value); + void write_register_(uint8_t reg, const uint8_t *data, size_t len); + optional read_register_(uint8_t reg); + + uint8_t bcd2_to_byte_(uint8_t value); + uint8_t byte_to_bcd2_(uint8_t value); +}; + +template class WriteAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->write_time(); } +}; + +template class ReadAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->read_time(); } +}; + +template class TimerAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(uint32_t, duration) + + void play(const Ts &...x) override { + auto duration = this->duration_.value(x...); + this->parent_->start_timer(duration); + } +}; + +} // namespace esphome::bm8563 diff --git a/esphome/components/bm8563/time.py b/esphome/components/bm8563/time.py new file mode 100644 index 0000000000..2785315af2 --- /dev/null +++ b/esphome/components/bm8563/time.py @@ -0,0 +1,80 @@ +from esphome import automation +import esphome.codegen as cg +from esphome.components import i2c, time +import esphome.config_validation as cv +from esphome.const import CONF_DURATION, CONF_ID + +DEPENDENCIES = ["i2c"] + +I2C_ADDR = 0x51 + +bm8563_ns = cg.esphome_ns.namespace("bm8563") +BM8563 = bm8563_ns.class_("BM8563", time.RealTimeClock, i2c.I2CDevice) +WriteAction = bm8563_ns.class_("WriteAction", automation.Action) +ReadAction = bm8563_ns.class_("ReadAction", automation.Action) +TimerAction = bm8563_ns.class_("TimerAction", automation.Action) + +CONFIG_SCHEMA = ( + time.TIME_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(BM8563), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(I2C_ADDR)) +) + + +@automation.register_action( + "bm8563.write_time", + WriteAction, + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(BM8563), + } + ), +) +async def bm8563_write_time_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +@automation.register_action( + "bm8563.start_timer", + TimerAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(BM8563), + cv.Required(CONF_DURATION): cv.templatable(cv.positive_time_period_seconds), + } + ), +) +async def bm8563_start_timer_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + template_ = await cg.templatable(config[CONF_DURATION], args, cg.uint32) + cg.add(var.set_duration(template_)) + return var + + +@automation.register_action( + "bm8563.read_time", + ReadAction, + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(BM8563), + } + ), +) +async def bm8563_read_time_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +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) + await time.register_time(var, config) diff --git a/tests/components/bm8563/common.yaml b/tests/components/bm8563/common.yaml new file mode 100644 index 0000000000..ec3fdd1518 --- /dev/null +++ b/tests/components/bm8563/common.yaml @@ -0,0 +1,10 @@ +esphome: + on_boot: + - bm8563.read_time + - bm8563.write_time + - bm8563.start_timer: + duration: 300s + +time: + - platform: bm8563 + i2c_id: i2c_bus diff --git a/tests/components/bm8563/test.esp32-ard.yaml b/tests/components/bm8563/test.esp32-ard.yaml new file mode 100644 index 0000000000..7c503b0ccb --- /dev/null +++ b/tests/components/bm8563/test.esp32-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/bm8563/test.esp32-idf.yaml b/tests/components/bm8563/test.esp32-idf.yaml new file mode 100644 index 0000000000..b47e39c389 --- /dev/null +++ b/tests/components/bm8563/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/bm8563/test.esp8266-ard.yaml b/tests/components/bm8563/test.esp8266-ard.yaml new file mode 100644 index 0000000000..4a98b9388a --- /dev/null +++ b/tests/components/bm8563/test.esp8266-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/bm8563/test.rp2040-ard.yaml b/tests/components/bm8563/test.rp2040-ard.yaml new file mode 100644 index 0000000000..319a7c71a6 --- /dev/null +++ b/tests/components/bm8563/test.rp2040-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml + +<<: !include common.yaml From a5751b294f06448375878dcb620ac6c4ec054893 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 24 Nov 2025 08:13:23 +1300 Subject: [PATCH 0322/1145] [api] Rename `USE_API_SERVICES` to `USE_API_USER_DEFINED_ACTIONS` (#12029) --- esphome/components/api/__init__.py | 4 ++-- esphome/components/api/api.proto | 8 ++++---- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_connection.h | 2 +- esphome/components/api/api_pb2.cpp | 2 +- esphome/components/api/api_pb2.h | 4 ++-- esphome/components/api/api_pb2_dump.cpp | 4 ++-- esphome/components/api/api_pb2_service.cpp | 4 ++-- esphome/components/api/api_pb2_service.h | 6 +++--- esphome/components/api/api_server.h | 8 ++++---- esphome/components/api/custom_api_device.h | 10 +++++----- esphome/components/api/list_entities.cpp | 2 +- esphome/components/api/list_entities.h | 2 +- esphome/components/api/user_services.h | 4 ++-- esphome/core/component_iterator.cpp | 6 +++--- esphome/core/component_iterator.h | 6 +++--- esphome/core/defines.h | 2 +- 17 files changed, 38 insertions(+), 38 deletions(-) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index a9286c531f..7f84f2f247 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -260,9 +260,9 @@ async def to_code(config): cg.add(var.set_max_connections(config[CONF_MAX_CONNECTIONS])) cg.add_define("API_MAX_SEND_QUEUE", config[CONF_MAX_SEND_QUEUE]) - # Set USE_API_SERVICES if any services are enabled + # Set USE_API_USER_DEFINED_ACTIONS if any services are enabled if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]: - cg.add_define("USE_API_SERVICES") + cg.add_define("USE_API_USER_DEFINED_ACTIONS") # Set USE_API_CUSTOM_SERVICES if external components need dynamic service registration if config[CONF_CUSTOM_SERVICES]: diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index e115e4630d..26d1fa6876 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -855,21 +855,21 @@ enum ServiceArgType { SERVICE_ARG_TYPE_STRING_ARRAY = 7; } message ListEntitiesServicesArgument { - option (ifdef) = "USE_API_SERVICES"; + option (ifdef) = "USE_API_USER_DEFINED_ACTIONS"; string name = 1; ServiceArgType type = 2; } message ListEntitiesServicesResponse { option (id) = 41; option (source) = SOURCE_SERVER; - option (ifdef) = "USE_API_SERVICES"; + option (ifdef) = "USE_API_USER_DEFINED_ACTIONS"; string name = 1; fixed32 key = 2; repeated ListEntitiesServicesArgument args = 3 [(fixed_vector) = true]; } message ExecuteServiceArgument { - option (ifdef) = "USE_API_SERVICES"; + option (ifdef) = "USE_API_USER_DEFINED_ACTIONS"; bool bool_ = 1; int32 legacy_int = 2; float float_ = 3; @@ -885,7 +885,7 @@ message ExecuteServiceRequest { option (id) = 42; option (source) = SOURCE_CLIENT; option (no_delay) = true; - option (ifdef) = "USE_API_SERVICES"; + option (ifdef) = "USE_API_USER_DEFINED_ACTIONS"; fixed32 key = 1; repeated ExecuteServiceArgument args = 2 [(fixed_vector) = true]; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 4acd2fc15c..c60680ae43 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1541,7 +1541,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes } } #endif -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS void APIConnection::execute_service(const ExecuteServiceRequest &msg) { bool found = false; for (auto *service : this->parent_->get_user_services()) { diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 6cfd108927..af3a19909f 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -221,7 +221,7 @@ class APIConnection final : public APIServerConnection { #ifdef USE_API_HOMEASSISTANT_STATES void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override; #endif -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS void execute_service(const ExecuteServiceRequest &msg) override; #endif #ifdef USE_API_NOISE diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 0a073fb662..d52135a566 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -995,7 +995,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { } return true; } -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->name_ref_); buffer.encode_uint32(2, static_cast(this->type)); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 358049026e..b19e92d4ff 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -63,7 +63,7 @@ enum LogLevel : uint32_t { LOG_LEVEL_VERBOSE = 6, LOG_LEVEL_VERY_VERBOSE = 7, }; -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS enum ServiceArgType : uint32_t { SERVICE_ARG_TYPE_BOOL = 0, SERVICE_ARG_TYPE_INT = 1, @@ -1239,7 +1239,7 @@ class GetTimeResponse final : public ProtoDecodableMessage { bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS class ListEntitiesServicesArgument final : public ProtoMessage { public: StringRef name_ref_{}; diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 127ef44cd8..ea752ba3ba 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -206,7 +206,7 @@ template<> const char *proto_enum_to_string(enums::LogLevel val return "UNKNOWN"; } } -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS template<> const char *proto_enum_to_string(enums::ServiceArgType value) { switch (value) { case enums::SERVICE_ARG_TYPE_BOOL: @@ -1177,7 +1177,7 @@ void GetTimeResponse::dump_to(std::string &out) const { out.append(format_hex_pretty(this->timezone, this->timezone_len)); out.append("\n"); } -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS void ListEntitiesServicesArgument::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesServicesArgument"); dump_field(out, "name", this->name_ref_); diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 9d227af0a3..3d28a137c8 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -193,7 +193,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, break; } #endif -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS case ExecuteServiceRequest::MESSAGE_TYPE: { ExecuteServiceRequest msg; msg.decode(msg_data, msg_size); @@ -670,7 +670,7 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc this->subscribe_home_assistant_states(msg); } #endif -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { this->execute_service(msg); } #endif #ifdef USE_API_NOISE diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 549b00ee6a..827b89e23c 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -79,7 +79,7 @@ class APIServerConnectionBase : public ProtoService { virtual void on_get_time_response(const GetTimeResponse &value){}; -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS virtual void on_execute_service_request(const ExecuteServiceRequest &value){}; #endif @@ -239,7 +239,7 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_API_HOMEASSISTANT_STATES virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; #endif -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS virtual void execute_service(const ExecuteServiceRequest &msg) = 0; #endif #ifdef USE_API_NOISE @@ -368,7 +368,7 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_API_HOMEASSISTANT_STATES void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; #endif -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS void on_execute_service_request(const ExecuteServiceRequest &msg) override; #endif #ifdef USE_API_NOISE diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 2d58063d6c..a3a082e165 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -12,7 +12,7 @@ #include "esphome/core/log.h" #include "list_entities.h" #include "subscribe_state.h" -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS #include "user_services.h" #endif @@ -124,7 +124,7 @@ class APIServer : public Component, public Controller { #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_SERVICES -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS void initialize_user_services(std::initializer_list services) { this->user_services_.assign(services); } @@ -166,7 +166,7 @@ class APIServer : public Component, public Controller { std::function f); const std::vector &get_state_subs() const; #endif -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS const std::vector &get_user_services() const { return this->user_services_; } #endif @@ -206,7 +206,7 @@ class APIServer : public Component, public Controller { #ifdef USE_API_HOMEASSISTANT_STATES std::vector state_subs_; #endif -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS std::vector user_services_; #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index 43ea644f0c..1006d07533 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -3,12 +3,12 @@ #include #include "api_server.h" #ifdef USE_API -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS #include "user_services.h" #endif namespace esphome::api { -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS template class CustomAPIDeviceService : public UserServiceDynamic { public: CustomAPIDeviceService(const std::string &name, const std::array &arg_names, T *obj, @@ -21,7 +21,7 @@ template class CustomAPIDeviceService : public UserS T *obj_; void (T::*callback_)(Ts...); }; -#endif // USE_API_SERVICES +#endif // USE_API_USER_DEFINED_ACTIONS class CustomAPIDevice { public: @@ -49,7 +49,7 @@ class CustomAPIDevice { * @param name The name of the service to register. * @param arg_names The name of the arguments for the service, must match the arguments of the function. */ -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS template void register_service(void (T::*callback)(Ts...), const std::string &name, const std::array &arg_names) { @@ -90,7 +90,7 @@ class CustomAPIDevice { * @param callback The member function to call when the service is triggered. * @param name The name of the arguments for the service, must match the arguments of the function. */ -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS template void register_service(void (T::*callback)(), const std::string &name) { #ifdef USE_API_CUSTOM_SERVICES auto *service = new CustomAPIDeviceService(name, {}, (T *) this, callback); // NOLINT diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index da4800a45e..e18fc17801 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -82,7 +82,7 @@ bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done( ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {} -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { auto resp = service->encode_list_service_response(); return this->client_->send_message(resp, ListEntitiesServicesResponse::MESSAGE_TYPE); diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index 769d7b9b6e..4c90dbbad8 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -43,7 +43,7 @@ class ListEntitiesIterator : public ComponentIterator { #ifdef USE_TEXT_SENSOR bool on_text_sensor(text_sensor::TextSensor *entity) override; #endif -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS bool on_service(UserServiceDescriptor *service) override; #endif #ifdef USE_CAMERA diff --git a/esphome/components/api/user_services.h b/esphome/components/api/user_services.h index 2a887fc52d..501b702e6b 100644 --- a/esphome/components/api/user_services.h +++ b/esphome/components/api/user_services.h @@ -7,7 +7,7 @@ #include "esphome/core/automation.h" #include "api_pb2.h" -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS namespace esphome::api { class UserServiceDescriptor { @@ -122,4 +122,4 @@ template class UserServiceTrigger : public UserServiceBaseprocess_platform_item_(api::global_api_server->get_user_services(), &ComponentIterator::on_service); break; @@ -185,7 +185,7 @@ void ComponentIterator::advance() { bool ComponentIterator::on_end() { return true; } bool ComponentIterator::on_begin() { return true; } -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS bool ComponentIterator::on_service(api::UserServiceDescriptor *service) { return true; } #endif #ifdef USE_CAMERA diff --git a/esphome/core/component_iterator.h b/esphome/core/component_iterator.h index 641d42898a..1b1bd80ac5 100644 --- a/esphome/core/component_iterator.h +++ b/esphome/core/component_iterator.h @@ -10,7 +10,7 @@ namespace esphome { -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS namespace api { class UserServiceDescriptor; } // namespace api @@ -45,7 +45,7 @@ class ComponentIterator { #ifdef USE_TEXT_SENSOR virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; #endif -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS virtual bool on_service(api::UserServiceDescriptor *service); #endif #ifdef USE_CAMERA @@ -122,7 +122,7 @@ class ComponentIterator { #ifdef USE_TEXT_SENSOR TEXT_SENSOR, #endif -#ifdef USE_API_SERVICES +#ifdef USE_API_USER_DEFINED_ACTIONS SERVICE, #endif #ifdef USE_CAMERA diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 41f4b28cd5..03362ce07a 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -124,7 +124,7 @@ #define USE_API_HOMEASSISTANT_STATES #define USE_API_NOISE #define USE_API_PLAINTEXT -#define USE_API_SERVICES +#define USE_API_USER_DEFINED_ACTIONS #define USE_API_CUSTOM_SERVICES #define API_MAX_SEND_QUEUE 8 #define USE_MD5 From f42b806889f51f81f31db8810443b5462c58e8e6 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Sun, 23 Nov 2025 22:03:13 +0100 Subject: [PATCH 0323/1145] [core] Fix error on invalid id extend/remove (#12064) --- esphome/config.py | 2 ++ .../fixtures/substitutions/05-extend-remove.approved.yaml | 6 ++++++ .../fixtures/substitutions/05-extend-remove.input.yaml | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/esphome/config.py b/esphome/config.py index 4c8019de75..1c4cdd93c6 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -355,6 +355,8 @@ def _get_item_id(item: Any) -> str | Extend | Remove | None: if isinstance(item_id, Extend): # Remove instances of Extend so they don't overwrite the original item when merging: del item[CONF_ID] + elif not isinstance(item_id, (str, Remove)): + return None return item_id diff --git a/tests/unit_tests/fixtures/substitutions/05-extend-remove.approved.yaml b/tests/unit_tests/fixtures/substitutions/05-extend-remove.approved.yaml index 35e3e6258f..773a124f25 100644 --- a/tests/unit_tests/fixtures/substitutions/05-extend-remove.approved.yaml +++ b/tests/unit_tests/fixtures/substitutions/05-extend-remove.approved.yaml @@ -31,3 +31,9 @@ lvgl: id: object5 x: 10 y: 11 + - obj: + id: + - Invalid ID + - obj: + id: + invalid: id diff --git a/tests/unit_tests/fixtures/substitutions/05-extend-remove.input.yaml b/tests/unit_tests/fixtures/substitutions/05-extend-remove.input.yaml index 617f09c31c..e6d46d6dc4 100644 --- a/tests/unit_tests/fixtures/substitutions/05-extend-remove.input.yaml +++ b/tests/unit_tests/fixtures/substitutions/05-extend-remove.input.yaml @@ -37,6 +37,10 @@ packages: id: object5 x: 10 y: 11 + - obj: + id: ["Invalid ID"] + - obj: + id: {"invalid": "id"} some_component: - id: !extend ${A} From c91a9495e611d4d1b94cffc6e88bd40f1d1843fd Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 23 Nov 2025 16:19:26 -0500 Subject: [PATCH 0324/1145] [ci] Fix filename (#12065) --- .../stts22h/{test.nrf52.yaml => test.nrf52-adafruit.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/components/stts22h/{test.nrf52.yaml => test.nrf52-adafruit.yaml} (100%) diff --git a/tests/components/stts22h/test.nrf52.yaml b/tests/components/stts22h/test.nrf52-adafruit.yaml similarity index 100% rename from tests/components/stts22h/test.nrf52.yaml rename to tests/components/stts22h/test.nrf52-adafruit.yaml From 5750f7fccbcf22c1df62d363cd0adb7d173be55d Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 23 Nov 2025 22:25:24 -0500 Subject: [PATCH 0325/1145] [ci] Fix test grouping (#12067) --- tests/components/stts22h/common.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/stts22h/common.yaml b/tests/components/stts22h/common.yaml index 802afe2065..2e332f9276 100644 --- a/tests/components/stts22h/common.yaml +++ b/tests/components/stts22h/common.yaml @@ -1,4 +1,5 @@ sensor: - platform: stts22h + i2c_id: i2c_bus name: Temperature update_interval: 15s From 60d687c2c6c9bb8961763958d1b0ad78fee2b772 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 23 Nov 2025 23:31:14 -0500 Subject: [PATCH 0326/1145] [esp32] Fix C2 builds (#12050) --- esphome/components/esp32/__init__.py | 6 ++++++ esphome/components/esp32/pre_build.py.script | 9 +++++++++ 2 files changed, 15 insertions(+) create mode 100644 esphome/components/esp32/pre_build.py.script diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 6f577d2926..59c6029334 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -883,6 +883,12 @@ async def to_code(config): CORE.relative_internal_path(".espressif") ) + add_extra_script( + "pre", + "pre_build.py", + Path(__file__).parent / "pre_build.py.script", + ) + add_extra_script( "post", "post_build.py", diff --git a/esphome/components/esp32/pre_build.py.script b/esphome/components/esp32/pre_build.py.script new file mode 100644 index 0000000000..af12275a0b --- /dev/null +++ b/esphome/components/esp32/pre_build.py.script @@ -0,0 +1,9 @@ +Import("env") # noqa: F821 + +# Remove custom_sdkconfig from the board config as it causes +# pioarduino to enable some strange hybrid build mode that breaks IDF +board = env.BoardConfig() +if "espidf.custom_sdkconfig" in board: + del board._manifest["espidf"]["custom_sdkconfig"] + if not board._manifest["espidf"]: + del board._manifest["espidf"] From b4b98505baed1fea37c1e2e5da11c2c8ea7d2e26 Mon Sep 17 00:00:00 2001 From: James <23900@qq.com> Date: Mon, 24 Nov 2025 23:05:02 +1300 Subject: [PATCH 0327/1145] [mipi_dsi] add guition JC4880P443 display (#12068) --- esphome/components/mipi_dsi/models/guition.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/esphome/components/mipi_dsi/models/guition.py b/esphome/components/mipi_dsi/models/guition.py index 5f7db4ebda..cd566633f9 100644 --- a/esphome/components/mipi_dsi/models/guition.py +++ b/esphome/components/mipi_dsi/models/guition.py @@ -35,3 +35,70 @@ DriverChip( (0x10, 0x0C), (0x11, 0x0C), (0x12, 0x0C), (0x13, 0x0C), (0x30, 0x00), ], ) + + +# JC4880P443 Driver Configuration (ST7701) +# Using parameters from esp_lcd_st7701.h and the working full init sequence +# ---------------------------------------------------------------------------------------------------------------------- +# * Resolution: 480x800 +# * PCLK Frequency: 34 MHz +# * DSI Lane Bit Rate: 500 Mbps (using 2-Lane DSI configuration) +# * Horizontal Timing (hsync_pulse_width=12, hsync_back_porch=42, hsync_front_porch=42) +# * Vertical Timing (vsync_pulse_width=2, vsync_back_porch=8, vsync_front_porch=166) +# ---------------------------------------------------------------------------------------------------------------------- +DriverChip( + "JC4880P443", + width=480, + height=800, + hsync_back_porch=42, + hsync_pulse_width=12, + hsync_front_porch=42, + vsync_back_porch=8, + vsync_pulse_width=2, + vsync_front_porch=166, + pclk_frequency="34MHz", + lane_bit_rate="500Mbps", + swap_xy=cv.UNDEFINED, + color_order="RGB", + reset_pin=5, + initsequence=[ + (0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), + (0xEF, 0x08), + (0xFF, 0x77, 0x01, 0x00, 0x00, 0x10), + (0xC0, 0x63, 0x00), + (0xC1, 0x0D, 0x02), + (0xC2, 0x10, 0x08), + (0xCC, 0x10), + (0xB0, 0x80, 0x09, 0x53, 0x0C, 0xD0, 0x07, 0x0C, 0x09, 0x09, 0x28, 0x06, 0xD4, 0x13, 0x69, 0x2B, 0x71), + (0xB1, 0x80, 0x94, 0x5A, 0x10, 0xD3, 0x06, 0x0A, 0x08, 0x08, 0x25, 0x03, 0xD3, 0x12, 0x66, 0x6A, 0x0D), + (0xFF, 0x77, 0x01, 0x00, 0x00, 0x11), + (0xB0, 0x5D), + (0xB1, 0x58), + (0xB2, 0x87), + (0xB3, 0x80), + (0xB5, 0x4E), + (0xB7, 0x85), + (0xB8, 0x21), + (0xB9, 0x10, 0x1F), + (0xBB, 0x03), + (0xBC, 0x00), + (0xC1, 0x78), + (0xC2, 0x78), + (0xD0, 0x88), + (0xE0, 0x00, 0x3A, 0x02), + (0xE1, 0x04, 0xA0, 0x00, 0xA0, 0x05, 0xA0, 0x00, 0xA0, 0x00, 0x40, 0x40), + (0xE2, 0x30, 0x00, 0x40, 0x40, 0x32, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00), + (0xE3, 0x00, 0x00, 0x33, 0x33), + (0xE4, 0x44, 0x44), + (0xE5, 0x09, 0x2E, 0xA0, 0xA0, 0x0B, 0x30, 0xA0, 0xA0, 0x05, 0x2A, 0xA0, 0xA0, 0x07, 0x2C, 0xA0, 0xA0), + (0xE6, 0x00, 0x00, 0x33, 0x33), + (0xE7, 0x44, 0x44), + (0xE8, 0x08, 0x2D, 0xA0, 0xA0, 0x0A, 0x2F, 0xA0, 0xA0, 0x04, 0x29, 0xA0, 0xA0, 0x06, 0x2B, 0xA0, 0xA0), + (0xEB, 0x00, 0x00, 0x4E, 0x4E, 0x00, 0x00, 0x00), + (0xEC, 0x08, 0x01), + (0xED, 0xB0, 0x2B, 0x98, 0xA4, 0x56, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x65, 0x4A, 0x89, 0xB2, 0x0B), + (0xEF, 0x08, 0x08, 0x08, 0x45, 0x3F, 0x54), + (0xFF, 0x77, 0x01, 0x00, 0x00, 0x00), + ] +) +# fmt: on From 8607a0881d4f3d3b6fe064287710f6af3f3a16b6 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:10:24 -0500 Subject: [PATCH 0328/1145] [core] Add support for passing yaml files to clean-all (#12039) --- esphome/__main__.py | 2 +- esphome/writer.py | 8 +++++++- tests/unit_tests/test_writer.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index b0c081a34f..f8fb678cb2 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -1319,7 +1319,7 @@ def parse_args(argv): "clean-all", help="Clean all build and platform files." ) parser_clean_all.add_argument( - "configuration", help="Your YAML configuration directory.", nargs="*" + "configuration", help="Your YAML file or configuration directory.", nargs="*" ) parser_dashboard = subparsers.add_parser( diff --git a/esphome/writer.py b/esphome/writer.py index b866a804b3..3124e9e12c 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -343,7 +343,13 @@ def clean_build(clear_pio_cache: bool = True): def clean_all(configuration: list[str]): import shutil - data_dirs = [Path(dir) / ".esphome" for dir in configuration] + data_dirs = [] + for config in configuration: + item = Path(config) + if item.is_file() and item.suffix in (".yaml", ".yml"): + data_dirs.append(item.parent / ".esphome") + else: + data_dirs.append(item / ".esphome") if is_ha_addon(): data_dirs.append(Path("/data")) if "ESPHOME_DATA_DIR" in os.environ: diff --git a/tests/unit_tests/test_writer.py b/tests/unit_tests/test_writer.py index a4490fbbc0..a2a358f4d3 100644 --- a/tests/unit_tests/test_writer.py +++ b/tests/unit_tests/test_writer.py @@ -737,6 +737,37 @@ def test_write_cpp_with_duplicate_markers( write_cpp("// New code") +@patch("esphome.writer.CORE") +def test_clean_all_with_yaml_file( + mock_core: MagicMock, + tmp_path: Path, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test clean_all with a .yaml file uses parent directory.""" + # Create config directory with yaml file + config_dir = tmp_path / "config" + config_dir.mkdir() + yaml_file = config_dir / "test.yaml" + yaml_file.write_text("esphome:\n name: test\n") + + build_dir = config_dir / ".esphome" + build_dir.mkdir() + (build_dir / "dummy.txt").write_text("x") + + from esphome.writer import clean_all + + with caplog.at_level("INFO"): + clean_all([str(yaml_file)]) + + # Verify .esphome directory still exists but contents cleaned + assert build_dir.exists() + assert not (build_dir / "dummy.txt").exists() + + # Verify logging mentions the build dir + assert "Cleaning" in caplog.text + assert str(build_dir) in caplog.text + + @patch("esphome.writer.CORE") def test_clean_all( mock_core: MagicMock, From 1f0a5e1eeab2d86031934a3f9c9e182458d60a5e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 10:21:32 -0600 Subject: [PATCH 0329/1145] [logger] Reduce UART overhead on ESP32/ESP8266 and fix buffer truncation (#11927) --- esphome/components/logger/__init__.py | 2 + esphome/components/logger/logger.cpp | 21 ++++---- esphome/components/logger/logger.h | 53 +++++++++++++++++-- esphome/components/logger/logger_esp32.cpp | 32 ++++++----- esphome/components/logger/logger_esp8266.cpp | 5 +- esphome/components/logger/logger_host.cpp | 2 +- .../components/logger/logger_libretiny.cpp | 2 +- esphome/components/logger/logger_rp2040.cpp | 2 +- esphome/components/logger/logger_zephyr.cpp | 2 +- 9 files changed, 84 insertions(+), 37 deletions(-) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index cf78e6ae63..39877030e9 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -365,8 +365,10 @@ async def to_code(config): if CORE.is_esp32: if config[CONF_HARDWARE_UART] == USB_CDC: add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True) + cg.add_define("USE_LOGGER_UART_SELECTION_USB_CDC") elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG: add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True) + cg.add_define("USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG") try: uart_selection(USB_SERIAL_JTAG) cg.add_define("USE_LOGGER_USB_SERIAL_JTAG") diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 9a9bf89fe3..9803bf528c 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -65,7 +65,9 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch uint16_t buffer_at = 0; // Initialize buffer position this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE); - this->write_msg_(console_buffer); + // Add newline if platform needs it (ESP32 doesn't add via write_msg_) + this->add_newline_to_buffer_if_needed_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE); + this->write_msg_(console_buffer, buffer_at); } // Reset the recursion guard for this task @@ -131,18 +133,19 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas // Save the offset before calling format_log_to_buffer_with_terminator_ // since it will increment tx_buffer_at_ to the end of the formatted string - uint32_t msg_start = this->tx_buffer_at_; + uint16_t msg_start = this->tx_buffer_at_; this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); - // Write to console and send callback starting at the msg_start - if (this->baud_rate_ > 0) { - this->write_msg_(this->tx_buffer_ + msg_start); - } - size_t msg_length = + uint16_t msg_length = this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position + + // Callbacks get message first (before console write) this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start, msg_length); + // Write to console starting at the msg_start + this->write_tx_buffer_to_console_(msg_start, &msg_length); + global_recursion_guard_ = false; } #endif // USE_STORE_LOG_STR_IN_FLASH @@ -209,9 +212,7 @@ void Logger::process_messages_() { // This ensures all log messages appear on the console in a clean, serialized manner // Note: Messages may appear slightly out of order due to async processing, but // this is preferred over corrupted/interleaved console output - if (this->baud_rate_ > 0) { - this->write_msg_(this->tx_buffer_); - } + this->write_tx_buffer_to_console_(); } } else { // No messages to process, disable loop if appropriate diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index dc8e06e0c9..8ba3dacacb 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -71,6 +71,17 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128; // "0x" + 2 hex digits per byte + '\0' static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; +// Platform-specific: does write_msg_ add its own newline? +// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266) +// Allows single write call with newline included for efficiency +// true: write_msg_ adds newline itself via puts()/println() (other platforms) +// Newline should NOT be added to buffer +#if defined(USE_ESP32) || defined(USE_ESP8266) +static constexpr bool WRITE_MSG_ADDS_NEWLINE = false; +#else +static constexpr bool WRITE_MSG_ADDS_NEWLINE = true; +#endif + #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) /** Enum for logging UART selection * @@ -173,7 +184,7 @@ class Logger : public Component { protected: void process_messages_(); - void write_msg_(const char *msg); + void write_msg_(const char *msg, size_t len); // Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator // It's the caller's responsibility to initialize buffer_at (typically to 0) @@ -200,6 +211,35 @@ class Logger : public Component { } } + // Helper to add newline to buffer for platforms that need it + // Modifies buffer_at to include the newline + inline void HOT add_newline_to_buffer_if_needed_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { + if constexpr (!WRITE_MSG_ADDS_NEWLINE) { + // Add newline - don't need to maintain null termination + // write_msg_ now always receives explicit length, so we can safely overwrite the null terminator + // This is safe because: + // 1. Callbacks already received the message (before we add newline) + // 2. write_msg_ receives the length explicitly (doesn't need null terminator) + if (*buffer_at < buffer_size) { + buffer[(*buffer_at)++] = '\n'; + } else if (buffer_size > 0) { + // Buffer was full - replace last char with newline to ensure it's visible + buffer[buffer_size - 1] = '\n'; + *buffer_at = buffer_size; + } + } + } + + // Helper to write tx_buffer_ to console if logging is enabled + // INTERNAL USE ONLY - offset > 0 requires length parameter to be non-null + inline void HOT write_tx_buffer_to_console_(uint16_t offset = 0, uint16_t *length = nullptr) { + if (this->baud_rate_ > 0) { + uint16_t *len_ptr = length ? length : &this->tx_buffer_at_; + this->add_newline_to_buffer_if_needed_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset); + this->write_msg_(this->tx_buffer_ + offset, *len_ptr); + } + } + // Helper to format and send a log message to both console and callbacks inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format, va_list args) { @@ -208,10 +248,11 @@ class Logger : public Component { this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); - if (this->baud_rate_ > 0) { - this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console - } + // Callbacks get message WITHOUT newline (for API/MQTT/syslog) this->log_callback_.call(level, tag, this->tx_buffer_, this->tx_buffer_at_); + + // Console gets message WITH newline (if platform needs it) + this->write_tx_buffer_to_console_(); } // Write the body of the log message to the buffer @@ -425,7 +466,9 @@ class Logger : public Component { } // Update buffer_at with the formatted length (handle truncation) - uint16_t formatted_len = (ret >= remaining) ? remaining : ret; + // When vsnprintf truncates (ret >= remaining), it writes (remaining - 1) chars + null terminator + // When it doesn't truncate (ret < remaining), it writes ret chars + null terminator + uint16_t formatted_len = (ret >= remaining) ? (remaining - 1) : ret; *buffer_at += formatted_len; // Remove all trailing newlines right after formatting diff --git a/esphome/components/logger/logger_esp32.cpp b/esphome/components/logger/logger_esp32.cpp index 7fc79e6f54..32ef752462 100644 --- a/esphome/components/logger/logger_esp32.cpp +++ b/esphome/components/logger/logger_esp32.cpp @@ -121,25 +121,23 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg) { - if ( -#if defined(USE_LOGGER_USB_CDC) && !defined(USE_LOGGER_USB_SERIAL_JTAG) - this->uart_ == UART_SELECTION_USB_CDC -#elif defined(USE_LOGGER_USB_SERIAL_JTAG) && !defined(USE_LOGGER_USB_CDC) - this->uart_ == UART_SELECTION_USB_SERIAL_JTAG -#elif defined(USE_LOGGER_USB_CDC) && defined(USE_LOGGER_USB_SERIAL_JTAG) - this->uart_ == UART_SELECTION_USB_CDC || this->uart_ == UART_SELECTION_USB_SERIAL_JTAG +void HOT Logger::write_msg_(const char *msg, size_t len) { + // Length is now always passed explicitly - no strlen() fallback needed + +#if defined(USE_LOGGER_UART_SELECTION_USB_CDC) || defined(USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG) + // USB CDC/JTAG - single write including newline (already in buffer) + // Use fwrite to stdout which goes through VFS to USB console + // + // Note: These defines indicate the user's YAML configuration choice (hardware_uart: USB_CDC/USB_SERIAL_JTAG). + // They are ONLY defined when the user explicitly selects USB as the logger output in their config. + // This is compile-time selection, not runtime detection - if USB is configured, it's always used. + // There is no fallback to regular UART if "USB isn't connected" - that's the user's responsibility + // to configure correctly for their hardware. This approach eliminates runtime overhead. + fwrite(msg, 1, len, stdout); #else - /* DISABLES CODE */ (false) // NOLINT + // Regular UART - single write including newline (already in buffer) + uart_write_bytes(this->uart_num_, msg, len); #endif - ) { - puts(msg); - } else { - // Use tx_buffer_at_ if msg points to tx_buffer_, otherwise fall back to strlen - size_t len = (msg == this->tx_buffer_) ? this->tx_buffer_at_ : strlen(msg); - uart_write_bytes(this->uart_num_, msg, len); - uart_write_bytes(this->uart_num_, "\n", 1); - } } const LogString *Logger::get_uart_selection_() { diff --git a/esphome/components/logger/logger_esp8266.cpp b/esphome/components/logger/logger_esp8266.cpp index 5063d88b92..0fc73b747a 100644 --- a/esphome/components/logger/logger_esp8266.cpp +++ b/esphome/components/logger/logger_esp8266.cpp @@ -33,7 +33,10 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } +void HOT Logger::write_msg_(const char *msg, size_t len) { + // Single write with newline already in buffer (added by caller) + this->hw_serial_->write(msg, len); +} const LogString *Logger::get_uart_selection_() { switch (this->uart_) { diff --git a/esphome/components/logger/logger_host.cpp b/esphome/components/logger/logger_host.cpp index 4abe92286a..c5e1e6f865 100644 --- a/esphome/components/logger/logger_host.cpp +++ b/esphome/components/logger/logger_host.cpp @@ -3,7 +3,7 @@ namespace esphome::logger { -void HOT Logger::write_msg_(const char *msg) { +void HOT Logger::write_msg_(const char *msg, size_t) { time_t rawtime; struct tm *timeinfo; char buffer[80]; diff --git a/esphome/components/logger/logger_libretiny.cpp b/esphome/components/logger/logger_libretiny.cpp index 3edfa74480..b8017b841d 100644 --- a/esphome/components/logger/logger_libretiny.cpp +++ b/esphome/components/logger/logger_libretiny.cpp @@ -49,7 +49,7 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } +void HOT Logger::write_msg_(const char *msg, size_t) { this->hw_serial_->println(msg); } const LogString *Logger::get_uart_selection_() { switch (this->uart_) { diff --git a/esphome/components/logger/logger_rp2040.cpp b/esphome/components/logger/logger_rp2040.cpp index 63727c2cda..4a8535c8e4 100644 --- a/esphome/components/logger/logger_rp2040.cpp +++ b/esphome/components/logger/logger_rp2040.cpp @@ -27,7 +27,7 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } +void HOT Logger::write_msg_(const char *msg, size_t) { this->hw_serial_->println(msg); } const LogString *Logger::get_uart_selection_() { switch (this->uart_) { diff --git a/esphome/components/logger/logger_zephyr.cpp b/esphome/components/logger/logger_zephyr.cpp index fb0c7dcca3..ec2ff3013c 100644 --- a/esphome/components/logger/logger_zephyr.cpp +++ b/esphome/components/logger/logger_zephyr.cpp @@ -62,7 +62,7 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg) { +void HOT Logger::write_msg_(const char *msg, size_t) { #ifdef CONFIG_PRINTK printk("%s\n", msg); #endif From 056b4375ebe238250675081613dcb00389e3254e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 10:21:47 -0600 Subject: [PATCH 0330/1145] [api] Reduce heap allocations in DeviceInfoResponse (#11952) --- esphome/components/api/api_connection.cpp | 12 ++++++++---- esphome/components/bluetooth_proxy/bluetooth_proxy.h | 6 ++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index c60680ae43..04221a237b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1451,8 +1451,11 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { #ifdef USE_AREAS resp.set_suggested_area(StringRef(App.get_area())); #endif - // mac_address must store temporary string - will be valid during send_message call - std::string mac_address = get_mac_address_pretty(); + // Stack buffer for MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes) + char mac_address[18]; + uint8_t mac[6]; + get_mac_address_raw(mac); + format_mac_addr_upper(mac, mac_address); resp.set_mac_address(StringRef(mac_address)); resp.set_esphome_version(ESPHOME_VERSION_REF); @@ -1493,8 +1496,9 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { #endif #ifdef USE_BLUETOOTH_PROXY resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags(); - // bt_mac must store temporary string - will be valid during send_message call - std::string bluetooth_mac = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(); + // Stack buffer for Bluetooth MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes) + char bluetooth_mac[18]; + bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(bluetooth_mac); resp.set_bluetooth_mac_address(StringRef(bluetooth_mac)); #endif #ifdef USE_VOICE_ASSISTANT diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index a5f0fbe32f..4de541fac2 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -130,11 +130,9 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ return flags; } - std::string get_bluetooth_mac_address_pretty() { + void get_bluetooth_mac_address_pretty(std::span output) { const uint8_t *mac = esp_bt_dev_get_address(); - char buf[18]; - format_mac_addr_upper(mac, buf); - return std::string(buf); + format_mac_addr_upper(mac, output.data()); } protected: From 426734beef724112b37037641c7ea7c20f044082 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 10:22:01 -0600 Subject: [PATCH 0331/1145] [web_server_base] Replace shared_ptr with unique_ptr for AsyncWebServer (#11984) --- esphome/components/web_server_base/web_server_base.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 039a452d64..fbf0d00c06 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -111,7 +111,7 @@ class WebServerBase : public Component { this->initialized_++; return; } - this->server_ = std::make_shared(this->port_); + this->server_ = std::make_unique(this->port_); // All content is controlled and created by user - so allowing all origins is fine here. DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); this->server_->begin(); @@ -127,7 +127,7 @@ class WebServerBase : public Component { this->server_ = nullptr; } } - std::shared_ptr get_server() const { return server_; } + AsyncWebServer *get_server() const { return this->server_.get(); } float get_setup_priority() const override; #ifdef USE_WEBSERVER_AUTH @@ -143,7 +143,7 @@ class WebServerBase : public Component { protected: int initialized_{0}; uint16_t port_{80}; - std::shared_ptr server_{nullptr}; + std::unique_ptr server_{nullptr}; std::vector handlers_; #ifdef USE_WEBSERVER_AUTH internal::Credentials credentials_; From 3c48e13c9f5cf391e775174748c7da77928e9b8d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 10:22:13 -0600 Subject: [PATCH 0332/1145] [ethernet] Conditionally compile manual_ip to save 24 bytes RAM (#11832) --- esphome/components/ethernet/__init__.py | 1 + esphome/components/ethernet/ethernet_component.cpp | 12 ++++++++++-- esphome/components/ethernet/ethernet_component.h | 4 ++++ esphome/core/defines.h | 1 + 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 2f02d227d7..b4d67635c1 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -383,6 +383,7 @@ async def to_code(config): cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) if CONF_MANUAL_IP in config: + cg.add_define("USE_ETHERNET_MANUAL_IP") cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) # Add compile-time define for PHY types with specific code diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index cad963b299..9a46aa2687 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -553,11 +553,14 @@ void EthernetComponent::start_connect_() { } esp_netif_ip_info_t info; +#ifdef USE_ETHERNET_MANUAL_IP if (this->manual_ip_.has_value()) { info.ip = this->manual_ip_->static_ip; info.gw = this->manual_ip_->gateway; info.netmask = this->manual_ip_->subnet; - } else { + } else +#endif + { info.ip.addr = 0; info.gw.addr = 0; info.netmask.addr = 0; @@ -578,6 +581,7 @@ void EthernetComponent::start_connect_() { err = esp_netif_set_ip_info(this->eth_netif_, &info); ESPHL_ERROR_CHECK(err, "DHCPC set IP info error"); +#ifdef USE_ETHERNET_MANUAL_IP if (this->manual_ip_.has_value()) { LwIPLock lock; if (this->manual_ip_->dns1.is_set()) { @@ -590,7 +594,9 @@ void EthernetComponent::start_connect_() { d = this->manual_ip_->dns2; dns_setserver(1, &d); } - } else { + } else +#endif + { err = esp_netif_dhcpc_start(this->eth_netif_); if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) { ESPHL_ERROR_CHECK(err, "DHCPC start error"); @@ -688,7 +694,9 @@ void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->cl void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); } #endif void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } +#ifdef USE_ETHERNET_MANUAL_IP void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } +#endif // set_use_address() is guaranteed to be called during component setup by Python code generation, // so use_address_ will always be valid when get_use_address() is called - no fallback needed. diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index f1f0ac9cb8..bffed4dc4a 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -82,7 +82,9 @@ class EthernetComponent : public Component { void add_phy_register(PHYRegister register_value); #endif void set_type(EthernetType type); +#ifdef USE_ETHERNET_MANUAL_IP void set_manual_ip(const ManualIP &manual_ip); +#endif void set_fixed_mac(const std::array &mac) { this->fixed_mac_ = mac; } network::IPAddresses get_ip_addresses(); @@ -137,7 +139,9 @@ class EthernetComponent : public Component { uint8_t mdc_pin_{23}; uint8_t mdio_pin_{18}; #endif +#ifdef USE_ETHERNET_MANUAL_IP optional manual_ip_{}; +#endif uint32_t connect_begin_; // Group all uint8_t types together (enums and bools) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 03362ce07a..5e7f51e04c 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -216,6 +216,7 @@ #define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 3, 2) #define USE_ETHERNET #define USE_ETHERNET_KSZ8081 +#define USE_ETHERNET_MANUAL_IP #endif #ifdef USE_ESP_IDF From 737f23a0bdb8a1c09bb0dee9709d09cb4c5403c4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 10:23:11 -0600 Subject: [PATCH 0333/1145] [light] Dynamically disable loop when idle to reduce CPU overhead (#11881) --- esphome/components/light/light_state.cpp | 24 ++++++++++++++++++++++++ esphome/components/light/light_state.h | 3 +++ 2 files changed, 27 insertions(+) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 36b2af03a5..9cde9077da 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -23,6 +23,9 @@ void LightState::setup() { effect->init_internal(this); } + // Start with loop disabled if idle - respects any effects/transitions set up during initialization + this->disable_loop_if_idle_(); + // When supported color temperature range is known, initialize color temperature setting within bounds. auto traits = this->get_traits(); float min_mireds = traits.get_min_mireds(); @@ -125,6 +128,9 @@ void LightState::loop() { this->is_transformer_active_ = false; this->transformer_ = nullptr; this->target_state_reached_callback_.call(); + + // Disable loop if idle (no transformer and no effect) + this->disable_loop_if_idle_(); } } @@ -132,6 +138,8 @@ void LightState::loop() { if (this->next_write_) { this->next_write_ = false; this->output_->write_state(this); + // Disable loop if idle (no transformer and no effect) + this->disable_loop_if_idle_(); } } @@ -227,6 +235,8 @@ void LightState::start_effect_(uint32_t effect_index) { this->active_effect_index_ = effect_index; auto *effect = this->get_active_effect_(); effect->start_internal(); + // Enable loop while effect is active + this->enable_loop(); } LightEffect *LightState::get_active_effect_() { if (this->active_effect_index_ == 0) { @@ -241,6 +251,8 @@ void LightState::stop_effect_() { effect->stop(); } this->active_effect_index_ = 0; + // Disable loop if idle (no effect and no transformer) + this->disable_loop_if_idle_(); } void LightState::start_transition_(const LightColorValues &target, uint32_t length, bool set_remote_values) { @@ -250,6 +262,8 @@ void LightState::start_transition_(const LightColorValues &target, uint32_t leng if (set_remote_values) { this->remote_values = target; } + // Enable loop while transition is active + this->enable_loop(); } void LightState::start_flash_(const LightColorValues &target, uint32_t length, bool set_remote_values) { @@ -265,6 +279,8 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b if (set_remote_values) { this->remote_values = target; }; + // Enable loop while flash is active + this->enable_loop(); } void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) { @@ -276,6 +292,14 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot } this->output_->update_state(this); this->next_write_ = true; + this->enable_loop(); +} + +void LightState::disable_loop_if_idle_() { + // Only disable loop if both transformer and effect are inactive, and no pending writes + if (this->transformer_ == nullptr && this->get_active_effect_() == nullptr && !this->next_write_) { + this->disable_loop(); + } } void LightState::save_remote_values_() { diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index 06519cdc14..ad8922b46f 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -255,6 +255,9 @@ class LightState : public EntityBase, public Component { /// Internal method to save the current remote_values to the preferences void save_remote_values_(); + /// Disable loop if neither transformer nor effect is active + void disable_loop_if_idle_(); + /// Store the output to allow effects to have more access. LightOutput *output_; /// The currently active transformer for this light (transition/flash). From 04ec6a69995adb0e6277e7abf2e49c05486a0e37 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 10:23:31 -0600 Subject: [PATCH 0334/1145] [api] Use stack buffer for MAC address in Noise handshake (#12072) --- esphome/components/api/api_frame_helper_noise.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index 633b07a7fa..8bcec0f9f3 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -239,12 +239,13 @@ APIError APINoiseFrameHelper::state_action_() { } if (state_ == State::SERVER_HELLO) { // send server hello + constexpr size_t mac_len = 13; // 12 hex chars + null terminator const std::string &name = App.get_name(); - const std::string &mac = get_mac_address(); + char mac[mac_len]; + get_mac_address_into_buffer(mac); // Calculate positions and sizes size_t name_len = name.size() + 1; // including null terminator - size_t mac_len = mac.size() + 1; // including null terminator size_t name_offset = 1; size_t mac_offset = name_offset + name_len; size_t total_size = 1 + name_len + mac_len; @@ -257,7 +258,7 @@ APIError APINoiseFrameHelper::state_action_() { // node name, terminated by null byte std::memcpy(msg.get() + name_offset, name.c_str(), name_len); // node mac, terminated by null byte - std::memcpy(msg.get() + mac_offset, mac.c_str(), mac_len); + std::memcpy(msg.get() + mac_offset, mac, mac_len); aerr = write_frame_(msg.get(), total_size); if (aerr != APIError::OK) From 06815fe177e5980ef049ec859c3fc8d26a28290d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 10:41:24 -0600 Subject: [PATCH 0335/1145] [script][wait_until] Fix FIFO ordering and reentrancy bugs (#12049) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/script/script.h | 20 +-- esphome/core/base_automation.h | 9 +- .../fixtures/script_delay_with_params.yaml | 131 ++++++++++++++++++ .../fixtures/wait_until_fifo_ordering.yaml | 82 +++++++++++ tests/integration/test_script_delay_params.py | 121 ++++++++++++++++ tests/integration/test_wait_until_ordering.py | 90 ++++++++++++ 6 files changed, 441 insertions(+), 12 deletions(-) create mode 100644 tests/integration/fixtures/script_delay_with_params.yaml create mode 100644 tests/integration/fixtures/wait_until_fifo_ordering.yaml create mode 100644 tests/integration/test_script_delay_params.py create mode 100644 tests/integration/test_wait_until_ordering.py diff --git a/esphome/components/script/script.h b/esphome/components/script/script.h index 51cece01e4..d60ed657f7 100644 --- a/esphome/components/script/script.h +++ b/esphome/components/script/script.h @@ -1,8 +1,8 @@ #pragma once +#include #include #include -#include #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -290,10 +290,10 @@ template class ScriptWaitAction : public Action, } // Store parameters for later execution - this->param_queue_.emplace_front(x...); - // Enable loop now that we have work to do + this->param_queue_.emplace_back(x...); + // Enable loop now that we have work to do - don't call loop() synchronously! + // Let the event loop call it to avoid reentrancy issues this->enable_loop(); - this->loop(); } void loop() override { @@ -303,13 +303,17 @@ template class ScriptWaitAction : public Action, if (this->script_->is_running()) return; - while (!this->param_queue_.empty()) { + // Only process ONE queued item per loop iteration + // Processing all items in a while loop causes infinite loops because + // play_next_() can trigger more items to be queued + if (!this->param_queue_.empty()) { auto ¶ms = this->param_queue_.front(); this->play_next_tuple_(params, typename gens::type()); this->param_queue_.pop_front(); + } else { + // Queue is now empty - disable loop until next play_complex + this->disable_loop(); } - // Queue is now empty - disable loop until next play_complex - this->disable_loop(); } void play(const Ts &...x) override { /* ignore - see play_complex */ @@ -326,7 +330,7 @@ template class ScriptWaitAction : public Action, } C *script_; - std::forward_list> param_queue_; + std::list> param_queue_; }; } // namespace script diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index c2519da839..e8878ac251 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -9,8 +9,8 @@ #include "esphome/core/application.h" #include "esphome/core/helpers.h" +#include #include -#include namespace esphome { @@ -445,9 +445,10 @@ template class WaitUntilAction : public Action, public Co // Store for later processing auto now = millis(); auto timeout = this->timeout_value_.optional_value(x...); - this->var_queue_.emplace_front(now, timeout, std::make_tuple(x...)); + this->var_queue_.emplace_back(now, timeout, std::make_tuple(x...)); - // Do immediate check with fresh timestamp + // Do immediate check with fresh timestamp - don't call loop() synchronously! + // Let the event loop call it to avoid reentrancy issues if (this->process_queue_(now)) { // Only enable loop if we still have pending items this->enable_loop(); @@ -499,7 +500,7 @@ template class WaitUntilAction : public Action, public Co } Condition *condition_; - std::forward_list, std::tuple>> var_queue_{}; + std::list, std::tuple>> var_queue_{}; }; template class UpdateComponentAction : public Action { diff --git a/tests/integration/fixtures/script_delay_with_params.yaml b/tests/integration/fixtures/script_delay_with_params.yaml new file mode 100644 index 0000000000..2a0f16d9fe --- /dev/null +++ b/tests/integration/fixtures/script_delay_with_params.yaml @@ -0,0 +1,131 @@ +esphome: + name: test-script-delay-params + +host: + +api: + actions: + # Test case from issue #12044: parent script with repeat calling child with delay + - action: test_repeat_with_delay + then: + - logger.log: "=== TEST: Repeat loop calling script with delay and parameters ===" + - script.execute: father_script + + # Test case from issue #12043: script.wait with delayed child script + - action: test_script_wait + then: + - logger.log: "=== TEST: script.wait with delayed child script ===" + - script.execute: show_start_page + - script.wait: show_start_page + - logger.log: "After wait: script completed successfully" + + # Test: Delay with different parameter types + - action: test_delay_param_types + then: + - logger.log: "=== TEST: Delay with various parameter types ===" + - script.execute: + id: delay_with_int + val: 42 + - delay: 50ms + - script.execute: + id: delay_with_string + msg: "test message" + - delay: 50ms + - script.execute: + id: delay_with_float + num: 3.14 + +logger: + level: DEBUG + +script: + # Reproduces issue #12044: child script with conditional delay + - id: son_script + mode: single + parameters: + iteration: int + then: + - logger.log: + format: "Son script started with iteration %d" + args: ['iteration'] + - if: + condition: + lambda: 'return iteration >= 5;' + then: + - logger.log: + format: "Son script delaying for iteration %d" + args: ['iteration'] + - delay: 100ms + - logger.log: + format: "Son script finished with iteration %d" + args: ['iteration'] + + # Reproduces issue #12044: parent script with repeat loop + - id: father_script + mode: single + then: + - repeat: + count: 10 + then: + - logger.log: + format: "Father iteration %d: calling son" + args: ['iteration'] + - script.execute: + id: son_script + iteration: !lambda 'return iteration;' + - script.wait: son_script + - logger.log: + format: "Father iteration %d: son finished, wait returned" + args: ['iteration'] + + # Reproduces issue #12043: script.wait hangs + - id: show_start_page + mode: single + then: + - logger.log: "Start page: beginning" + - delay: 100ms + - logger.log: "Start page: after delay" + - delay: 100ms + - logger.log: "Start page: completed" + + # Test delay with int parameter + - id: delay_with_int + mode: single + parameters: + val: int + then: + - logger.log: + format: "Int test: before delay, val=%d" + args: ['val'] + - delay: 50ms + - logger.log: + format: "Int test: after delay, val=%d" + args: ['val'] + + # Test delay with string parameter + - id: delay_with_string + mode: single + parameters: + msg: string + then: + - logger.log: + format: "String test: before delay, msg=%s" + args: ['msg.c_str()'] + - delay: 50ms + - logger.log: + format: "String test: after delay, msg=%s" + args: ['msg.c_str()'] + + # Test delay with float parameter + - id: delay_with_float + mode: single + parameters: + num: float + then: + - logger.log: + format: "Float test: before delay, num=%.2f" + args: ['num'] + - delay: 50ms + - logger.log: + format: "Float test: after delay, num=%.2f" + args: ['num'] diff --git a/tests/integration/fixtures/wait_until_fifo_ordering.yaml b/tests/integration/fixtures/wait_until_fifo_ordering.yaml new file mode 100644 index 0000000000..5dd60c8755 --- /dev/null +++ b/tests/integration/fixtures/wait_until_fifo_ordering.yaml @@ -0,0 +1,82 @@ +esphome: + name: test-wait-until-ordering + +host: + +api: + actions: + - action: test_wait_until_fifo + then: + - logger.log: "=== TEST: wait_until should execute in FIFO order ===" + - globals.set: + id: gate_open + value: 'false' + - delay: 100ms + # Start multiple parallel executions of coordinator script + # Each will call the shared waiter script, queueing in same wait_until + - script.execute: coordinator_0 + - script.execute: coordinator_1 + - script.execute: coordinator_2 + - script.execute: coordinator_3 + - script.execute: coordinator_4 + # Give scripts time to reach wait_until and queue + - delay: 200ms + - logger.log: "Opening gate - all wait_until should complete now" + - globals.set: + id: gate_open + value: 'true' + - delay: 500ms + - logger.log: "Test complete" + +globals: + - id: gate_open + type: bool + initial_value: 'false' + +script: + # Shared waiter with single wait_until action (all coordinators call this) + - id: waiter + mode: parallel + parameters: + iter: int + then: + - lambda: 'ESP_LOGD("main", "Queueing iteration %d", iter);' + - wait_until: + condition: + lambda: 'return id(gate_open);' + timeout: 5s + - lambda: 'ESP_LOGD("main", "Completed iteration %d", iter);' + + # Coordinator scripts - each calls shared waiter with different iteration number + - id: coordinator_0 + then: + - script.execute: + id: waiter + iter: 0 + + - id: coordinator_1 + then: + - script.execute: + id: waiter + iter: 1 + + - id: coordinator_2 + then: + - script.execute: + id: waiter + iter: 2 + + - id: coordinator_3 + then: + - script.execute: + id: waiter + iter: 3 + + - id: coordinator_4 + then: + - script.execute: + id: waiter + iter: 4 + +logger: + level: DEBUG diff --git a/tests/integration/test_script_delay_params.py b/tests/integration/test_script_delay_params.py new file mode 100644 index 0000000000..1b5d70863b --- /dev/null +++ b/tests/integration/test_script_delay_params.py @@ -0,0 +1,121 @@ +"""Integration test for script.wait FIFO ordering (issues #12043, #12044). + +This test verifies that ScriptWaitAction processes queued items in FIFO order. + +PR #7972 introduced bugs in ScriptWaitAction: +- Used emplace_front() causing LIFO ordering instead of FIFO +- Called loop() synchronously causing reentrancy issues +- Used while loop processing entire queue causing infinite loops + +These bugs manifested as: +- Scripts becoming "zombies" (stuck in running state) +- script.wait hanging forever +- Incorrect execution order +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_script_delay_with_params( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that script.wait processes queued items in FIFO order. + + This reproduces issues #12043 and #12044 where scripts would hang or become + zombies due to LIFO ordering bugs in ScriptWaitAction from PR #7972. + """ + test_complete = asyncio.Event() + + # Patterns to match in logs + father_calling_pattern = re.compile(r"Father iteration (\d+): calling son") + son_started_pattern = re.compile(r"Son script started with iteration (\d+)") + son_delaying_pattern = re.compile(r"Son script delaying for iteration (\d+)") + son_finished_pattern = re.compile(r"Son script finished with iteration (\d+)") + father_wait_returned_pattern = re.compile( + r"Father iteration (\d+): son finished, wait returned" + ) + + # Track which iterations completed + father_calling = set() + son_started = set() + son_delaying = set() + son_finished = set() + wait_returned = set() + + def check_output(line: str) -> None: + """Check log output for expected messages.""" + if test_complete.is_set(): + return + + if mo := father_calling_pattern.search(line): + father_calling.add(int(mo.group(1))) + elif mo := son_started_pattern.search(line): + son_started.add(int(mo.group(1))) + elif mo := son_delaying_pattern.search(line): + son_delaying.add(int(mo.group(1))) + elif mo := son_finished_pattern.search(line): + son_finished.add(int(mo.group(1))) + elif mo := father_wait_returned_pattern.search(line): + iteration = int(mo.group(1)) + wait_returned.add(iteration) + # Test completes when iteration 9 finishes + if iteration == 9: + test_complete.set() + + # Run with log monitoring + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "test-script-delay-params" + + # Get services + _, services = await client.list_entities_services() + test_service = next( + (s for s in services if s.name == "test_repeat_with_delay"), None + ) + assert test_service is not None, "test_repeat_with_delay service not found" + + # Execute the test + client.execute_service(test_service, {}) + + # Wait for test to complete (10 iterations * ~100ms each + margin) + try: + await asyncio.wait_for(test_complete.wait(), timeout=5.0) + except TimeoutError: + pytest.fail( + f"Test timed out. Completed iterations: {sorted(wait_returned)}. " + f"This likely indicates the script became a zombie (issue #12044)." + ) + + # Verify all 10 iterations completed successfully + expected_iterations = set(range(10)) + assert father_calling == expected_iterations, "Not all iterations started" + assert son_started == expected_iterations, ( + "Son script not started for all iterations" + ) + assert son_finished == expected_iterations, ( + "Son script not finished for all iterations" + ) + assert wait_returned == expected_iterations, ( + "script.wait did not return for all iterations" + ) + + # Verify delays were triggered for iterations >= 5 + expected_delays = set(range(5, 10)) + assert son_delaying == expected_delays, ( + "Delays not triggered for iterations >= 5" + ) diff --git a/tests/integration/test_wait_until_ordering.py b/tests/integration/test_wait_until_ordering.py new file mode 100644 index 0000000000..7c39913e5a --- /dev/null +++ b/tests/integration/test_wait_until_ordering.py @@ -0,0 +1,90 @@ +"""Integration test for wait_until FIFO ordering. + +This test verifies that when multiple wait_until actions are queued, +they execute in FIFO (First In First Out) order, not LIFO. + +PR #7972 introduced a bug where emplace_front() was used, causing +LIFO ordering which is incorrect. +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_wait_until_fifo_ordering( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that wait_until executes queued items in FIFO order. + + With the bug (using emplace_front), the order would be 4,3,2,1,0 (LIFO). + With the fix (using emplace_back), the order should be 0,1,2,3,4 (FIFO). + """ + test_complete = asyncio.Event() + + # Track completion order + completed_order = [] + + # Patterns to match + queuing_pattern = re.compile(r"Queueing iteration (\d+)") + completed_pattern = re.compile(r"Completed iteration (\d+)") + + def check_output(line: str) -> None: + """Check log output for completion order.""" + if test_complete.is_set(): + return + + if mo := queuing_pattern.search(line): + iteration = int(mo.group(1)) + + elif mo := completed_pattern.search(line): + iteration = int(mo.group(1)) + completed_order.append(iteration) + + # Test completes when all 5 have completed + if len(completed_order) == 5: + test_complete.set() + + # Run with log monitoring + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "test-wait-until-ordering" + + # Get services + _, services = await client.list_entities_services() + test_service = next( + (s for s in services if s.name == "test_wait_until_fifo"), None + ) + assert test_service is not None, "test_wait_until_fifo service not found" + + # Execute the test + client.execute_service(test_service, {}) + + # Wait for test to complete + try: + await asyncio.wait_for(test_complete.wait(), timeout=5.0) + except TimeoutError: + pytest.fail( + f"Test timed out. Completed order: {completed_order}. " + f"Expected 5 completions but got {len(completed_order)}." + ) + + # Verify FIFO order + expected_order = [0, 1, 2, 3, 4] + assert completed_order == expected_order, ( + f"Unexpected order: {completed_order}. " + f"Expected FIFO order: {expected_order}" + ) From 0764f4da86dee9a325237ca1fa2e5f3d71d0f6c7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 11:02:24 -0600 Subject: [PATCH 0336/1145] [esp_ldo,mipi_dsi,mipi_rgb] Fix dangling pointer bugs in mark_failed() (#12077) --- esphome/components/esp_ldo/esp_ldo.cpp | 4 ++-- esphome/components/mipi_dsi/mipi_dsi.cpp | 6 ++++++ esphome/components/mipi_dsi/mipi_dsi.h | 5 +---- esphome/components/mipi_rgb/mipi_rgb.cpp | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/esphome/components/esp_ldo/esp_ldo.cpp b/esphome/components/esp_ldo/esp_ldo.cpp index eb04670d7e..9ea7000b70 100644 --- a/esphome/components/esp_ldo/esp_ldo.cpp +++ b/esphome/components/esp_ldo/esp_ldo.cpp @@ -14,8 +14,8 @@ void EspLdo::setup() { config.flags.adjustable = this->adjustable_; auto err = esp_ldo_acquire_channel(&config, &this->handle_); if (err != ESP_OK) { - auto msg = str_sprintf("Failed to acquire LDO channel %d with voltage %fV", this->channel_, this->voltage_); - this->mark_failed(msg.c_str()); + ESP_LOGE(TAG, "Failed to acquire LDO channel %d with voltage %fV", this->channel_, this->voltage_); + this->mark_failed("Failed to acquire LDO channel"); } else { ESP_LOGD(TAG, "Acquired LDO channel %d with voltage %fV", this->channel_, this->voltage_); } diff --git a/esphome/components/mipi_dsi/mipi_dsi.cpp b/esphome/components/mipi_dsi/mipi_dsi.cpp index fbe251de41..7305435e4b 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.cpp +++ b/esphome/components/mipi_dsi/mipi_dsi.cpp @@ -11,6 +11,12 @@ static bool notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel xSemaphoreGiveFromISR(sem, &need_yield); return (need_yield == pdTRUE); } + +void MIPI_DSI::smark_failed(const char *message, esp_err_t err) { + ESP_LOGE(TAG, "%s: %s", message, esp_err_to_name(err)); + this->mark_failed(message); +} + void MIPI_DSI::setup() { ESP_LOGCONFIG(TAG, "Running Setup"); diff --git a/esphome/components/mipi_dsi/mipi_dsi.h b/esphome/components/mipi_dsi/mipi_dsi.h index ce8a2a2236..98ee092ed1 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.h +++ b/esphome/components/mipi_dsi/mipi_dsi.h @@ -62,10 +62,7 @@ class MIPI_DSI : public display::Display { void set_lanes(uint8_t lanes) { this->lanes_ = lanes; } void set_madctl(uint8_t madctl) { this->madctl_ = madctl; } - void smark_failed(const char *message, esp_err_t err) { - auto str = str_sprintf("Setup failed: %s: %s", message, esp_err_to_name(err)); - this->mark_failed(str.c_str()); - } + void smark_failed(const char *message, esp_err_t err); void update() override; diff --git a/esphome/components/mipi_rgb/mipi_rgb.cpp b/esphome/components/mipi_rgb/mipi_rgb.cpp index 080fb08c09..4c687724cf 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.cpp +++ b/esphome/components/mipi_rgb/mipi_rgb.cpp @@ -164,8 +164,8 @@ void MipiRgb::common_setup_() { if (err == ESP_OK) err = esp_lcd_panel_init(this->handle_); if (err != ESP_OK) { - auto msg = str_sprintf("lcd setup failed: %s", esp_err_to_name(err)); - this->mark_failed(msg.c_str()); + ESP_LOGE(TAG, "lcd setup failed: %s", esp_err_to_name(err)); + this->mark_failed("lcd setup failed"); } ESP_LOGCONFIG(TAG, "MipiRgb setup complete"); } From 66cda0466469531a7a9428db33251c6ad985c9bd Mon Sep 17 00:00:00 2001 From: Flo Date: Mon, 24 Nov 2025 18:19:38 +0100 Subject: [PATCH 0337/1145] [wifi] ap_active condition (#11852) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- esphome/components/wifi/__init__.py | 6 ++++++ esphome/components/wifi/wifi_component.cpp | 1 + esphome/components/wifi/wifi_component.h | 6 ++++++ tests/components/wifi/common.yaml | 4 ++++ 4 files changed, 17 insertions(+) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 2b21478f30..b9c0fa28a7 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -97,6 +97,7 @@ WIFI_MIN_AUTH_MODES = { VALIDATE_WIFI_MIN_AUTH_MODE = cv.enum(WIFI_MIN_AUTH_MODES, upper=True) WiFiConnectedCondition = wifi_ns.class_("WiFiConnectedCondition", Condition) WiFiEnabledCondition = wifi_ns.class_("WiFiEnabledCondition", Condition) +WiFiAPActiveCondition = wifi_ns.class_("WiFiAPActiveCondition", Condition) WiFiEnableAction = wifi_ns.class_("WiFiEnableAction", automation.Action) WiFiDisableAction = wifi_ns.class_("WiFiDisableAction", automation.Action) WiFiConfigureAction = wifi_ns.class_( @@ -590,6 +591,11 @@ async def wifi_enabled_to_code(config, condition_id, template_arg, args): return cg.new_Pvariable(condition_id, template_arg) +@automation.register_condition("wifi.ap_active", WiFiAPActiveCondition, cv.Schema({})) +async def wifi_ap_active_to_code(config, condition_id, template_arg, args): + return cg.new_Pvariable(condition_id, template_arg) + + @automation.register_action("wifi.enable", WiFiEnableAction, cv.Schema({})) async def wifi_enable_to_code(config, action_id, template_arg, args): return cg.new_Pvariable(action_id, template_arg) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 6f698bc2a8..23a4020453 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -530,6 +530,7 @@ void WiFiComponent::loop() { WiFiComponent::WiFiComponent() { global_wifi_component = this; } bool WiFiComponent::has_ap() const { return this->has_ap_; } +bool WiFiComponent::is_ap_active() const { return this->state_ == WIFI_COMPONENT_STATE_AP; } bool WiFiComponent::has_sta() const { return !this->sta_.empty(); } #ifdef USE_WIFI_11KV_SUPPORT void WiFiComponent::set_btm(bool btm) { this->btm_ = btm; } diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index b3548078bc..441606a2c1 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -308,6 +308,7 @@ class WiFiComponent : public Component { bool has_sta() const; bool has_ap() const; + bool is_ap_active() const; #ifdef USE_WIFI_11KV_SUPPORT void set_btm(bool btm); @@ -557,6 +558,11 @@ template class WiFiEnabledCondition : public Condition { bool check(const Ts &...x) override { return !global_wifi_component->is_disabled(); } }; +template class WiFiAPActiveCondition : public Condition { + public: + bool check(const Ts &...x) override { return global_wifi_component->is_ap_active(); } +}; + template class WiFiEnableAction : public Action { public: void play(const Ts &...x) override { global_wifi_component->enable(); } diff --git a/tests/components/wifi/common.yaml b/tests/components/wifi/common.yaml index 5d9973cbc8..7ce74ab00d 100644 --- a/tests/components/wifi/common.yaml +++ b/tests/components/wifi/common.yaml @@ -10,6 +10,10 @@ esphome: - logger.log: "Connected to WiFi!" on_error: - logger.log: "Failed to connect to WiFi!" + - if: + condition: wifi.ap_active + then: + - logger.log: "WiFi AP is active!" wifi: networks: From d7a197b3a3444d996dcdd2b249ccaa7e88aa1421 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 24 Nov 2025 12:27:09 -0500 Subject: [PATCH 0338/1145] [esp32] Use the IDF I2C implementation on Arduino (#12076) --- esphome/components/i2c/__init__.py | 26 +++++++++++++--------- esphome/components/i2c/i2c_bus_arduino.cpp | 24 +++++--------------- esphome/components/i2c/i2c_bus_arduino.h | 7 +++--- esphome/components/i2c/i2c_bus_esp_idf.cpp | 4 ++-- esphome/components/i2c/i2c_bus_esp_idf.h | 4 ++-- 5 files changed, 27 insertions(+), 38 deletions(-) diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 6308923759..738568cd3c 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -47,18 +47,20 @@ MULTI_CONF = True def _bus_declare_type(value): + if CORE.is_esp32: + return cv.declare_id(IDFI2CBus)(value) if CORE.using_arduino: return cv.declare_id(ArduinoI2CBus)(value) - if CORE.using_esp_idf: - return cv.declare_id(IDFI2CBus)(value) if CORE.using_zephyr: return cv.declare_id(ZephyrI2CBus)(value) raise NotImplementedError def validate_config(config): - if CORE.using_esp_idf: - return cv.require_framework_version(esp_idf=cv.Version(5, 4, 2))(config) + if CORE.is_esp32: + return cv.require_framework_version( + esp_idf=cv.Version(5, 4, 2), esp32_arduino=cv.Version(3, 2, 1) + )(config) return config @@ -67,12 +69,12 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): _bus_declare_type, cv.Optional(CONF_SDA, default="SDA"): pins.internal_gpio_pin_number, - cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32_idf=True): cv.All( - cv.only_with_esp_idf, cv.boolean + cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32=True): cv.All( + cv.only_on_esp32, cv.boolean ), cv.Optional(CONF_SCL, default="SCL"): pins.internal_gpio_pin_number, - cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All( - cv.only_with_esp_idf, cv.boolean + cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32=True): cv.All( + cv.only_on_esp32, cv.boolean ), cv.SplitDefault( CONF_FREQUENCY, @@ -151,7 +153,7 @@ async def to_code(config): cg.add(var.set_scan(config[CONF_SCAN])) if CONF_TIMEOUT in config: cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds))) - if CORE.using_arduino: + if CORE.using_arduino and not CORE.is_esp32: cg.add_library("Wire", None) @@ -248,14 +250,16 @@ def final_validate_device_schema( FILTER_SOURCE_FILES = filter_source_files_from_platform( { "i2c_bus_arduino.cpp": { - PlatformFramework.ESP32_ARDUINO, PlatformFramework.ESP8266_ARDUINO, PlatformFramework.RP2040_ARDUINO, PlatformFramework.BK72XX_ARDUINO, PlatformFramework.RTL87XX_ARDUINO, PlatformFramework.LN882X_ARDUINO, }, - "i2c_bus_esp_idf.cpp": {PlatformFramework.ESP32_IDF}, + "i2c_bus_esp_idf.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, "i2c_bus_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR}, } ) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 221423418b..1579020c9b 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -1,4 +1,4 @@ -#ifdef USE_ARDUINO +#if defined(USE_ARDUINO) && !defined(USE_ESP32) #include "i2c_bus_arduino.h" #include @@ -15,16 +15,7 @@ static const char *const TAG = "i2c.arduino"; void ArduinoI2CBus::setup() { recover_(); -#if defined(USE_ESP32) - static uint8_t next_bus_num = 0; - if (next_bus_num == 0) { - wire_ = &Wire; - } else { - wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory) - } - this->port_ = next_bus_num; - next_bus_num++; -#elif defined(USE_ESP8266) +#if defined(USE_ESP8266) wire_ = new TwoWire(); // NOLINT(cppcoreguidelines-owning-memory) #elif defined(USE_RP2040) static bool first = true; @@ -54,10 +45,7 @@ void ArduinoI2CBus::set_pins_and_clock_() { wire_->begin(static_cast(sda_pin_), static_cast(scl_pin_)); #endif if (timeout_ > 0) { // if timeout specified in yaml -#if defined(USE_ESP32) - // https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/src/Wire.cpp - wire_->setTimeOut(timeout_ / 1000); // unit: ms -#elif defined(USE_ESP8266) +#if defined(USE_ESP8266) // https://github.com/esp8266/Arduino/blob/master/libraries/Wire/Wire.h wire_->setClockStretchLimit(timeout_); // unit: us #elif defined(USE_RP2040) @@ -76,9 +64,7 @@ void ArduinoI2CBus::dump_config() { " Frequency: %u Hz", this->sda_pin_, this->scl_pin_, this->frequency_); if (timeout_ > 0) { -#if defined(USE_ESP32) - ESP_LOGCONFIG(TAG, " Timeout: %u ms", this->timeout_ / 1000); -#elif defined(USE_ESP8266) +#if defined(USE_ESP8266) ESP_LOGCONFIG(TAG, " Timeout: %u us", this->timeout_); #elif defined(USE_RP2040) ESP_LOGCONFIG(TAG, " Timeout: %u ms", this->timeout_ / 1000); @@ -275,4 +261,4 @@ void ArduinoI2CBus::recover_() { } // namespace i2c } // namespace esphome -#endif // USE_ESP_IDF +#endif // defined(USE_ARDUINO) && !defined(USE_ESP32) diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index b441828353..2d69e7684c 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ARDUINO +#if defined(USE_ARDUINO) && !defined(USE_ESP32) #include #include "esphome/core/component.h" @@ -29,7 +29,7 @@ class ArduinoI2CBus : public InternalI2CBus, public Component { void set_frequency(uint32_t frequency) { frequency_ = frequency; } void set_timeout(uint32_t timeout) { timeout_ = timeout; } - int get_port() const override { return this->port_; } + int get_port() const override { return 0; } private: void recover_(); @@ -37,7 +37,6 @@ class ArduinoI2CBus : public InternalI2CBus, public Component { RecoveryCode recovery_result_; protected: - int8_t port_{-1}; TwoWire *wire_; uint8_t sda_pin_; uint8_t scl_pin_; @@ -49,4 +48,4 @@ class ArduinoI2CBus : public InternalI2CBus, public Component { } // namespace i2c } // namespace esphome -#endif // USE_ARDUINO +#endif // defined(USE_ARDUINO) && !defined(USE_ESP32) diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index bf50ea0586..c22db51c68 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -1,4 +1,4 @@ -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "i2c_bus_esp_idf.h" @@ -299,4 +299,4 @@ void IDFI2CBus::recover_() { } // namespace i2c } // namespace esphome -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index f565be4535..63fe8b701c 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/component.h" #include "i2c_bus.h" @@ -53,4 +53,4 @@ class IDFI2CBus : public InternalI2CBus, public Component { } // namespace i2c } // namespace esphome -#endif // USE_ESP_IDF +#endif // USE_ESP32 From d7da55988552ca5a044e57b84a5c284763efb66c Mon Sep 17 00:00:00 2001 From: Sascha Ittner Date: Mon, 24 Nov 2025 18:31:26 +0100 Subject: [PATCH 0339/1145] [thermopro_ble] Add thermopro ble support (#11835) Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- CODEOWNERS | 1 + esphome/components/thermopro_ble/__init__.py | 0 esphome/components/thermopro_ble/sensor.py | 97 +++++++++ .../thermopro_ble/thermopro_ble.cpp | 204 ++++++++++++++++++ .../components/thermopro_ble/thermopro_ble.h | 49 +++++ tests/components/thermopro_ble/common.yaml | 13 ++ .../thermopro_ble/test.esp32-idf.yaml | 4 + 7 files changed, 368 insertions(+) create mode 100644 esphome/components/thermopro_ble/__init__.py create mode 100644 esphome/components/thermopro_ble/sensor.py create mode 100644 esphome/components/thermopro_ble/thermopro_ble.cpp create mode 100644 esphome/components/thermopro_ble/thermopro_ble.h create mode 100644 tests/components/thermopro_ble/common.yaml create mode 100644 tests/components/thermopro_ble/test.esp32-idf.yaml diff --git a/CODEOWNERS b/CODEOWNERS index d6ec7b882e..c6332e3933 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -484,6 +484,7 @@ esphome/components/template/datetime/* @rfdarter esphome/components/template/event/* @nohat esphome/components/template/fan/* @ssieb esphome/components/text/* @mauritskorse +esphome/components/thermopro_ble/* @sittner esphome/components/thermostat/* @kbx81 esphome/components/time/* @esphome/core esphome/components/tinyusb/* @kbx81 diff --git a/esphome/components/thermopro_ble/__init__.py b/esphome/components/thermopro_ble/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/thermopro_ble/sensor.py b/esphome/components/thermopro_ble/sensor.py new file mode 100644 index 0000000000..de63229621 --- /dev/null +++ b/esphome/components/thermopro_ble/sensor.py @@ -0,0 +1,97 @@ +import esphome.codegen as cg +from esphome.components import esp32_ble_tracker, sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_EXTERNAL_TEMPERATURE, + CONF_HUMIDITY, + CONF_ID, + CONF_MAC_ADDRESS, + CONF_SIGNAL_STRENGTH, + CONF_TEMPERATURE, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_DECIBEL_MILLIWATT, + UNIT_PERCENT, +) + +CODEOWNERS = ["@sittner"] + +DEPENDENCIES = ["esp32_ble_tracker"] + +thermopro_ble_ns = cg.esphome_ns.namespace("thermopro_ble") +ThermoProBLE = thermopro_ble_ns.class_( + "ThermoProBLE", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ThermoProBLE), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + 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, + ), + cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_SIGNAL_STRENGTH): sensor.sensor_schema( + unit_of_measurement=UNIT_DECIBEL_MILLIWATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await esp32_ble_tracker.register_ble_device(var, config) + + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature(sens)) + if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE): + sens = await sensor.new_sensor(external_temperature_config) + cg.add(var.set_external_temperature(sens)) + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity(sens)) + if battery_level_config := config.get(CONF_BATTERY_LEVEL): + sens = await sensor.new_sensor(battery_level_config) + cg.add(var.set_battery_level(sens)) + if signal_strength_config := config.get(CONF_SIGNAL_STRENGTH): + sens = await sensor.new_sensor(signal_strength_config) + cg.add(var.set_signal_strength(sens)) diff --git a/esphome/components/thermopro_ble/thermopro_ble.cpp b/esphome/components/thermopro_ble/thermopro_ble.cpp new file mode 100644 index 0000000000..4b43c9b39e --- /dev/null +++ b/esphome/components/thermopro_ble/thermopro_ble.cpp @@ -0,0 +1,204 @@ +#include "thermopro_ble.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome::thermopro_ble { + +// this size must be large enough to hold the largest data frame +// of all supported devices +static constexpr std::size_t MAX_DATA_SIZE = 24; + +struct DeviceParserMapping { + const char *prefix; + DeviceParser parser; +}; + +static float tp96_battery(uint16_t voltage); + +static optional parse_tp972(const uint8_t *data, std::size_t data_size); +static optional parse_tp96(const uint8_t *data, std::size_t data_size); +static optional parse_tp3(const uint8_t *data, std::size_t data_size); + +static const char *const TAG = "thermopro_ble"; + +static const struct DeviceParserMapping DEVICE_PARSER_MAP[] = { + {"TP972", parse_tp972}, {"TP970", parse_tp96}, {"TP96", parse_tp96}, {"TP3", parse_tp3}}; + +void ThermoProBLE::dump_config() { + ESP_LOGCONFIG(TAG, "ThermoPro BLE"); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "External temperature", this->external_temperature_); + LOG_SENSOR(" ", "Humidity", this->humidity_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); +} + +bool ThermoProBLE::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + // check for matching mac address + if (device.address_uint64() != this->address_) { + ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); + return false; + } + + // check for valid device type + update_device_type_(device.get_name()); + if (this->device_parser_ == nullptr) { + ESP_LOGVV(TAG, "parse_device(): invalid device type."); + return false; + } + + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + + // publish signal strength + float signal_strength = float(device.get_rssi()); + if (this->signal_strength_ != nullptr) + this->signal_strength_->publish_state(signal_strength); + + bool success = false; + for (auto &service_data : device.get_manufacturer_datas()) { + // check maximum data size + std::size_t data_size = service_data.data.size() + 2; + if (data_size > MAX_DATA_SIZE) { + ESP_LOGVV(TAG, "parse_device(): maximum data size exceeded!"); + continue; + } + + // reconstruct whole record from 2 byte uuid and data + esp_bt_uuid_t uuid = service_data.uuid.get_uuid(); + uint8_t data[MAX_DATA_SIZE] = {static_cast(uuid.uuid.uuid16), static_cast(uuid.uuid.uuid16 >> 8)}; + std::copy(service_data.data.begin(), service_data.data.end(), std::begin(data) + 2); + + // dispatch data to parser + optional result = this->device_parser_(data, data_size); + if (!result.has_value()) { + continue; + } + + // publish sensor values + if (result->temperature.has_value() && this->temperature_ != nullptr) + this->temperature_->publish_state(*result->temperature); + if (result->external_temperature.has_value() && this->external_temperature_ != nullptr) + this->external_temperature_->publish_state(*result->external_temperature); + if (result->humidity.has_value() && this->humidity_ != nullptr) + this->humidity_->publish_state(*result->humidity); + if (result->battery_level.has_value() && this->battery_level_ != nullptr) + this->battery_level_->publish_state(*result->battery_level); + + success = true; + } + + return success; +} + +void ThermoProBLE::update_device_type_(const std::string &device_name) { + // check for changed device name (should only happen on initial call) + if (this->device_name_ == device_name) { + return; + } + + // remember device name + this->device_name_ = device_name; + + // try to find device parser + for (const auto &mapping : DEVICE_PARSER_MAP) { + if (device_name.starts_with(mapping.prefix)) { + this->device_parser_ = mapping.parser; + return; + } + } + + // device type unknown + this->device_parser_ = nullptr; + ESP_LOGVV(TAG, "update_device_type_(): unknown device type %s.", device_name.c_str()); +} + +static inline uint16_t read_uint16(const uint8_t *data, std::size_t offset) { + return static_cast(data[offset + 0]) | (static_cast(data[offset + 1]) << 8); +} + +static inline int16_t read_int16(const uint8_t *data, std::size_t offset) { + return static_cast(read_uint16(data, offset)); +} + +static inline uint32_t read_uint32(const uint8_t *data, std::size_t offset) { + return static_cast(data[offset + 0]) | (static_cast(data[offset + 1]) << 8) | + (static_cast(data[offset + 2]) << 16) | (static_cast(data[offset + 3]) << 24); +} + +// Battery calculation used with permission from: +// https://github.com/Bluetooth-Devices/thermopro-ble/blob/main/src/thermopro_ble/parser.py +// +// TP96x battery values appear to be a voltage reading, probably in millivolts. +// This means that calculating battery life from it is a non-linear function. +// Examining the curve, it looked fairly close to a curve from the tanh function. +// So, I created a script to use Tensorflow to optimize an equation in the format +// A*tanh(B*x+C)+D +// Where A,B,C,D are the variables to optimize for. This yielded the below function +static float tp96_battery(uint16_t voltage) { + float level = 52.317286f * tanh(static_cast(voltage) / 273.624277936f - 8.76485439394f) + 51.06925f; + return std::max(0.0f, std::min(level, 100.0f)); +} + +static optional parse_tp972(const uint8_t *data, std::size_t data_size) { + if (data_size != 23) { + ESP_LOGVV(TAG, "parse_tp972(): payload has wrong size of %d (!= 23)!", data_size); + return {}; + } + + ParseResult result; + + // ambient temperature, 2 bytes, 16-bit unsigned integer, -54 °C offset + result.external_temperature = static_cast(read_uint16(data, 1)) - 54.0f; + + // battery level, 2 bytes, 16-bit unsigned integer, voltage (convert to percentage) + result.battery_level = tp96_battery(read_uint16(data, 3)); + + // internal temperature, 4 bytes, float, -54 °C offset + result.temperature = static_cast(read_uint32(data, 9)) - 54.0f; + + return result; +} + +static optional parse_tp96(const uint8_t *data, std::size_t data_size) { + if (data_size != 7) { + ESP_LOGVV(TAG, "parse_tp96(): payload has wrong size of %d (!= 7)!", data_size); + return {}; + } + + ParseResult result; + + // internal temperature, 2 bytes, 16-bit unsigned integer, -30 °C offset + result.temperature = static_cast(read_uint16(data, 1)) - 30.0f; + + // battery level, 2 bytes, 16-bit unsigned integer, voltage (convert to percentage) + result.battery_level = tp96_battery(read_uint16(data, 3)); + + // ambient temperature, 2 bytes, 16-bit unsigned integer, -30 °C offset + result.external_temperature = static_cast(read_uint16(data, 5)) - 30.0f; + + return result; +} + +static optional parse_tp3(const uint8_t *data, std::size_t data_size) { + if (data_size < 6) { + ESP_LOGVV(TAG, "parse_tp3(): payload has wrong size of %d (< 6)!", data_size); + return {}; + } + + ParseResult result; + + // temperature, 2 bytes, 16-bit signed integer, 0.1 °C + result.temperature = static_cast(read_int16(data, 1)) * 0.1f; + + // humidity, 1 byte, 8-bit unsigned integer, 1.0 % + result.humidity = static_cast(data[3]); + + // battery level, 2 bits (0-2) + result.battery_level = static_cast(data[4] & 0x3) * 50.0; + + return result; +} + +} // namespace esphome::thermopro_ble + +#endif diff --git a/esphome/components/thermopro_ble/thermopro_ble.h b/esphome/components/thermopro_ble/thermopro_ble.h new file mode 100644 index 0000000000..38bed82102 --- /dev/null +++ b/esphome/components/thermopro_ble/thermopro_ble.h @@ -0,0 +1,49 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef USE_ESP32 + +namespace esphome::thermopro_ble { + +struct ParseResult { + optional temperature; + optional external_temperature; + optional humidity; + optional battery_level; +}; + +using DeviceParser = optional (*)(const uint8_t *data, std::size_t data_size); + +class ThermoProBLE : public Component, public esp32_ble_tracker::ESPBTDeviceListener { + public: + void set_address(uint64_t address) { this->address_ = address; }; + + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + void dump_config() override; + void set_signal_strength(sensor::Sensor *signal_strength) { this->signal_strength_ = signal_strength; } + void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; } + void set_external_temperature(sensor::Sensor *external_temperature) { + this->external_temperature_ = external_temperature; + } + void set_humidity(sensor::Sensor *humidity) { this->humidity_ = humidity; } + void set_battery_level(sensor::Sensor *battery_level) { this->battery_level_ = battery_level; } + + protected: + uint64_t address_; + std::string device_name_; + DeviceParser device_parser_{nullptr}; + sensor::Sensor *signal_strength_{nullptr}; + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *external_temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; + + void update_device_type_(const std::string &device_name); +}; + +} // namespace esphome::thermopro_ble + +#endif diff --git a/tests/components/thermopro_ble/common.yaml b/tests/components/thermopro_ble/common.yaml new file mode 100644 index 0000000000..297725e1c3 --- /dev/null +++ b/tests/components/thermopro_ble/common.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: thermopro_ble + mac_address: FE:74:B8:6A:97:B7 + temperature: + name: "ThermoPro Temperature" + humidity: + name: "ThermoPro Humidity" + battery_level: + name: "ThermoPro Battery Level" + signal_strength: + name: "ThermoPro Signal Strength" diff --git a/tests/components/thermopro_ble/test.esp32-idf.yaml b/tests/components/thermopro_ble/test.esp32-idf.yaml new file mode 100644 index 0000000000..7a6541ae76 --- /dev/null +++ b/tests/components/thermopro_ble/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + ble: !include ../../test_build_components/common/ble/esp32-idf.yaml + +<<: !include common.yaml From b820e676161295d7d135128ff38a99016ecb0e5e Mon Sep 17 00:00:00 2001 From: Jordan Zucker Date: Mon, 24 Nov 2025 09:42:07 -0800 Subject: [PATCH 0340/1145] [prometheus] Add event and text base components metrics (#10240) Co-authored-by: Jordan Zucker Co-authored-by: J. Nick Koston --- .../prometheus/prometheus_handler.cpp | 106 ++++++++++++++++++ .../prometheus/prometheus_handler.h | 16 +++ tests/components/prometheus/common.yaml | 28 +++++ 3 files changed, 150 insertions(+) diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 5cfcacf0cb..6b57a3f718 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -53,6 +53,18 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) { this->lock_row_(stream, obj, area, node, friendly_name); #endif +#ifdef USE_EVENT + this->event_type_(stream); + for (auto *obj : App.get_events()) + this->event_row_(stream, obj, area, node, friendly_name); +#endif + +#ifdef USE_TEXT + this->text_type_(stream); + for (auto *obj : App.get_texts()) + this->text_row_(stream, obj, area, node, friendly_name); +#endif + #ifdef USE_TEXT_SENSOR this->text_sensor_type_(stream); for (auto *obj : App.get_text_sensors()) @@ -547,6 +559,100 @@ void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_senso } #endif +// Type-specific implementation +#ifdef USE_TEXT +void PrometheusHandler::text_type_(AsyncResponseStream *stream) { + stream->print(ESPHOME_F("#TYPE esphome_text_value gauge\n")); + stream->print(ESPHOME_F("#TYPE esphome_text_failed gauge\n")); +} +void PrometheusHandler::text_row_(AsyncResponseStream *stream, text::Text *obj, std::string &area, std::string &node, + std::string &friendly_name) { + if (obj->is_internal() && !this->include_internal_) + return; + if (obj->has_state()) { + // We have a valid value, output this value + stream->print(ESPHOME_F("esphome_text_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(ESPHOME_F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(ESPHOME_F("\"} 0\n")); + // Data itself + stream->print(ESPHOME_F("esphome_text_value{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(ESPHOME_F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(ESPHOME_F("\",value=\"")); + stream->print(obj->state.c_str()); + stream->print(ESPHOME_F("\"} ")); + stream->print(ESPHOME_F("1.0")); + stream->print(ESPHOME_F("\n")); + } else { + // Invalid state + stream->print(ESPHOME_F("esphome_text_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(ESPHOME_F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(ESPHOME_F("\"} 1\n")); + } +} +#endif + +// Type-specific implementation +#ifdef USE_EVENT +void PrometheusHandler::event_type_(AsyncResponseStream *stream) { + stream->print(ESPHOME_F("#TYPE esphome_event_value gauge\n")); + stream->print(ESPHOME_F("#TYPE esphome_event_failed gauge\n")); +} +void PrometheusHandler::event_row_(AsyncResponseStream *stream, event::Event *obj, std::string &area, std::string &node, + std::string &friendly_name) { + if (obj->is_internal() && !this->include_internal_) + return; + if (obj->get_last_event_type() != nullptr) { + // We have a valid event type, output this value + stream->print(ESPHOME_F("esphome_event_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(ESPHOME_F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(ESPHOME_F("\"} 0\n")); + // Data itself + stream->print(ESPHOME_F("esphome_event_value{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(ESPHOME_F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(ESPHOME_F("\",last_event_type=\"")); + stream->print(obj->get_last_event_type()); + stream->print(ESPHOME_F("\"} ")); + stream->print(ESPHOME_F("1.0")); + stream->print(ESPHOME_F("\n")); + } else { + // No event triggered yet + stream->print(ESPHOME_F("esphome_event_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(ESPHOME_F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(ESPHOME_F("\"} 1\n")); + } +} +#endif + // Type-specific implementation #ifdef USE_NUMBER void PrometheusHandler::number_type_(AsyncResponseStream *stream) { diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index c4598f44b0..45cc81b899 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -123,6 +123,22 @@ class PrometheusHandler : public AsyncWebHandler, public Component { std::string &friendly_name); #endif +#ifdef USE_EVENT + /// Return the type for prometheus + void event_type_(AsyncResponseStream *stream); + /// Return the event values state as prometheus data point + void event_row_(AsyncResponseStream *stream, event::Event *obj, std::string &area, std::string &node, + std::string &friendly_name); +#endif + +#ifdef USE_TEXT + /// Return the type for prometheus + void text_type_(AsyncResponseStream *stream); + /// Return the text values state as prometheus data point + void text_row_(AsyncResponseStream *stream, text::Text *obj, std::string &area, std::string &node, + std::string &friendly_name); +#endif + #ifdef USE_TEXT_SENSOR /// Return the type for prometheus void text_sensor_type_(AsyncResponseStream *stream); diff --git a/tests/components/prometheus/common.yaml b/tests/components/prometheus/common.yaml index cf46e882a7..0b90d614dd 100644 --- a/tests/components/prometheus/common.yaml +++ b/tests/components/prometheus/common.yaml @@ -39,6 +39,15 @@ sensor: return 0.0; update_interval: 60s +text: + - platform: template + name: "Template text" + optimistic: true + min_length: 0 + max_length: 100 + mode: text + initial_value: "Hello World" + text_sensor: - platform: version name: "ESPHome Version" @@ -52,6 +61,25 @@ text_sensor: return {"Goodbye (cruel) World"}; update_interval: 60s +event: + - platform: template + name: "Template Event" + id: template_event1 + event_types: + - "custom_event_1" + - "custom_event_2" + +button: + - platform: template + name: "Template Event Button" + on_press: + - logger.log: "Template Event Button pressed" + - lambda: |- + ESP_LOGD("template_event_button", "Template Event Button pressed"); + - event.trigger: + id: template_event1 + event_type: custom_event_1 + binary_sensor: - platform: template id: template_binary_sensor1 From 09f3f6219493ec28ed12fe3495e7c85c608620e1 Mon Sep 17 00:00:00 2001 From: Flo Date: Mon, 24 Nov 2025 18:49:16 +0100 Subject: [PATCH 0341/1145] [api] Connected Condition - state_subscription_only flag (#11906) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston --- esphome/components/api/__init__.py | 20 ++++++++++++++++++-- esphome/components/api/api_server.cpp | 13 ++++++++++++- esphome/components/api/api_server.h | 7 +++++-- tests/components/api/common-base.yaml | 4 ++++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 7f84f2f247..2910643dfb 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -85,6 +85,7 @@ CONF_HOMEASSISTANT_SERVICES = "homeassistant_services" CONF_HOMEASSISTANT_STATES = "homeassistant_states" CONF_LISTEN_BACKLOG = "listen_backlog" CONF_MAX_SEND_QUEUE = "max_send_queue" +CONF_STATE_SUBSCRIPTION_ONLY = "state_subscription_only" def validate_encryption_key(value): @@ -537,9 +538,24 @@ async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, arg return var -@automation.register_condition("api.connected", APIConnectedCondition, {}) +API_CONNECTED_CONDITION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(APIServer), + cv.Optional(CONF_STATE_SUBSCRIPTION_ONLY, default=False): cv.templatable( + cv.boolean + ), + } +) + + +@automation.register_condition( + "api.connected", APIConnectedCondition, API_CONNECTED_CONDITION_SCHEMA +) async def api_connected_to_code(config, condition_id, template_arg, args): - return cg.new_Pvariable(condition_id, template_arg) + var = cg.new_Pvariable(condition_id, template_arg) + templ = await cg.templatable(config[CONF_STATE_SUBSCRIPTION_ONLY], args, cg.bool_) + cg.add(var.set_state_subscription_only(templ)) + return var def FILTER_SOURCE_FILES() -> list[str]: diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 18601d74ff..d33c98abc9 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -528,7 +528,18 @@ void APIServer::request_time() { } #endif -bool APIServer::is_connected() const { return !this->clients_.empty(); } +bool APIServer::is_connected(bool state_subscription_only) const { + if (!state_subscription_only) { + return !this->clients_.empty(); + } + + for (const auto &client : this->clients_) { + if (client->flags_.state_subscription) { + return true; + } + } + return false; +} void APIServer::on_shutdown() { this->shutting_down_ = true; diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index a3a082e165..786cd63f44 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -150,7 +150,7 @@ class APIServer : public Component, public Controller { void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg); #endif - bool is_connected() const; + bool is_connected(bool state_subscription_only = false) const; #ifdef USE_API_HOMEASSISTANT_STATES struct HomeAssistantStateSubscription { @@ -236,8 +236,11 @@ class APIServer : public Component, public Controller { extern APIServer *global_api_server; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) template class APIConnectedCondition : public Condition { + TEMPLATABLE_VALUE(bool, state_subscription_only) public: - bool check(const Ts &...x) override { return global_api_server->is_connected(); } + bool check(const Ts &...x) override { + return global_api_server->is_connected(this->state_subscription_only_.value(x...)); + } }; } // namespace esphome::api diff --git a/tests/components/api/common-base.yaml b/tests/components/api/common-base.yaml index fc53b8ac7e..0416cebf9b 100644 --- a/tests/components/api/common-base.yaml +++ b/tests/components/api/common-base.yaml @@ -1,6 +1,10 @@ esphome: on_boot: then: + - wait_until: + condition: + api.connected: + state_subscription_only: true - homeassistant.event: event: esphome.button_pressed data: From c888becfa7369396c96fd1d4e8f807ebde57cc7b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 11:52:15 -0600 Subject: [PATCH 0342/1145] [api] Optimize APINoiseContext memory usage by removing shared_ptr overhead (#11981) --- esphome/components/api/api_connection.cpp | 4 ++-- esphome/components/api/api_frame_helper_noise.cpp | 2 +- esphome/components/api/api_frame_helper_noise.h | 9 ++++----- esphome/components/api/api_server.cpp | 6 +++--- esphome/components/api/api_server.h | 6 +++--- esphome/components/mdns/mdns_component.cpp | 2 +- esphome/components/mqtt/mqtt_client.cpp | 2 +- 7 files changed, 15 insertions(+), 16 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 04221a237b..ebfc641537 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -90,8 +90,8 @@ static const int CAMERA_STOP_STREAM = 5000; APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent) : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { #if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE) - auto noise_ctx = parent->get_noise_ctx(); - if (noise_ctx->has_psk()) { + auto &noise_ctx = parent->get_noise_ctx(); + if (noise_ctx.has_psk()) { this->helper_ = std::unique_ptr{new APINoiseFrameHelper(std::move(sock), noise_ctx, &this->client_info_)}; } else { diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index 8bcec0f9f3..f1028fa299 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -528,7 +528,7 @@ APIError APINoiseFrameHelper::init_handshake_() { if (aerr != APIError::OK) return aerr; - const auto &psk = ctx_->get_psk(); + const auto &psk = this->ctx_.get_psk(); err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size()); aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"), APIError::HANDSHAKESTATE_SETUP_FAILED); diff --git a/esphome/components/api/api_frame_helper_noise.h b/esphome/components/api/api_frame_helper_noise.h index e3243e4fa5..7eb01058db 100644 --- a/esphome/components/api/api_frame_helper_noise.h +++ b/esphome/components/api/api_frame_helper_noise.h @@ -9,9 +9,8 @@ namespace esphome::api { class APINoiseFrameHelper final : public APIFrameHelper { public: - APINoiseFrameHelper(std::unique_ptr socket, std::shared_ptr ctx, - const ClientInfo *client_info) - : APIFrameHelper(std::move(socket), client_info), ctx_(std::move(ctx)) { + APINoiseFrameHelper(std::unique_ptr socket, APINoiseContext &ctx, const ClientInfo *client_info) + : APIFrameHelper(std::move(socket), client_info), ctx_(ctx) { // Noise header structure: // Pos 0: indicator (0x01) // Pos 1-2: encrypted payload size (16-bit big-endian) @@ -41,8 +40,8 @@ class APINoiseFrameHelper final : public APIFrameHelper { NoiseCipherState *send_cipher_{nullptr}; NoiseCipherState *recv_cipher_{nullptr}; - // Shared pointer (8 bytes on 32-bit = 4 bytes control block pointer + 4 bytes object pointer) - std::shared_ptr ctx_; + // Reference to noise context (4 bytes on 32-bit) + APINoiseContext &ctx_; // Vector (12 bytes on 32-bit) std::vector prologue_; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index d33c98abc9..64f8751c35 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -227,8 +227,8 @@ void APIServer::dump_config() { " Max connections: %u", network::get_use_address(), this->port_, this->listen_backlog_, this->max_connections_); #ifdef USE_API_NOISE - ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk())); - if (!this->noise_ctx_->has_psk()) { + ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_.has_psk())); + if (!this->noise_ctx_.has_psk()) { ESP_LOGCONFIG(TAG, " Supports encryption: YES"); } #else @@ -493,7 +493,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) { ESP_LOGW(TAG, "Key set in YAML"); return false; #else - auto &old_psk = this->noise_ctx_->get_psk(); + auto &old_psk = this->noise_ctx_.get_psk(); if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) { ESP_LOGW(TAG, "New PSK matches old"); return true; diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 786cd63f44..428429418a 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -54,8 +54,8 @@ class APIServer : public Component, public Controller { #ifdef USE_API_NOISE bool save_noise_psk(psk_t psk, bool make_active = true); bool clear_noise_psk(bool make_active = true); - void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); } - std::shared_ptr get_noise_ctx() { return noise_ctx_; } + void set_noise_psk(psk_t psk) { this->noise_ctx_.set_psk(psk); } + APINoiseContext &get_noise_ctx() { return this->noise_ctx_; } #endif // USE_API_NOISE void handle_disconnect(APIConnection *conn); @@ -228,7 +228,7 @@ class APIServer : public Component, public Controller { // 7 bytes used, 1 byte padding #ifdef USE_API_NOISE - std::shared_ptr noise_ctx_ = std::make_shared(); + APINoiseContext noise_ctx_; ESPPreferenceObject noise_pref_; #endif // USE_API_NOISE }; diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index c81defd19f..4655907983 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -118,7 +118,7 @@ void MDNSComponent::compile_records_(StaticVectorget_noise_ctx()->has_psk(); + bool has_psk = api::global_api_server->get_noise_ctx().has_psk(); const char *encryption_key = has_psk ? TXT_API_ENCRYPTION : TXT_API_ENCRYPTION_SUPPORTED; txt_records.push_back({MDNS_STR(encryption_key), MDNS_STR(NOISE_ENCRYPTION)}); #endif diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 9055b4421e..a810d98adf 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -140,7 +140,7 @@ void MQTTClientComponent::send_device_info_() { #endif #ifdef USE_API_NOISE - root[api::global_api_server->get_noise_ctx()->has_psk() ? "api_encryption" : "api_encryption_supported"] = + root[api::global_api_server->get_noise_ctx().has_psk() ? "api_encryption" : "api_encryption_supported"] = "Noise_NNpsk0_25519_ChaChaPoly_SHA256"; #endif }, From c146d924255eebc958e4f5b8fc5706c2021af494 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 11:53:42 -0600 Subject: [PATCH 0343/1145] [api] Remove redundant socket pointer from APIFrameHelper (#11985) --- esphome/components/api/api_frame_helper.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 9aaada3cf7..d931a6e3a9 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -84,9 +84,7 @@ class APIFrameHelper { public: APIFrameHelper() = default; explicit APIFrameHelper(std::unique_ptr socket, const ClientInfo *client_info) - : socket_owned_(std::move(socket)), client_info_(client_info) { - socket_ = socket_owned_.get(); - } + : socket_(std::move(socket)), client_info_(client_info) {} virtual ~APIFrameHelper() = default; virtual APIError init() = 0; virtual APIError loop(); @@ -149,9 +147,8 @@ class APIFrameHelper { APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector &tx_buf, const std::string &info, StateEnum &state, StateEnum failed_state); - // Pointers first (4 bytes each) - socket::Socket *socket_{nullptr}; - std::unique_ptr socket_owned_; + // Socket ownership (4 bytes on 32-bit, 8 bytes on 64-bit) + std::unique_ptr socket_; // Common state enum for all frame helpers // Note: Not all states are used by all implementations From d1a1bb446b9014ff4e591580102dfc07931099d9 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 24 Nov 2025 12:55:04 -0500 Subject: [PATCH 0344/1145] [wifi] Add runtime power saving mode control (#11478) Co-authored-by: J. Nick Koston --- esphome/components/wifi/__init__.py | 18 ++++- esphome/components/wifi/wifi_component.cpp | 90 +++++++++++++++++++++- esphome/components/wifi/wifi_component.h | 42 ++++++++++ esphome/core/defines.h | 1 + tests/components/wifi/test.esp32-idf.yaml | 11 +++ 5 files changed, 160 insertions(+), 2 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index b9c0fa28a7..8a5e5329f1 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -607,6 +607,7 @@ async def wifi_disable_to_code(config, action_id, template_arg, args): KEEP_SCAN_RESULTS_KEY = "wifi_keep_scan_results" +RUNTIME_POWER_SAVE_KEY = "wifi_runtime_power_save" def request_wifi_scan_results(): @@ -619,13 +620,28 @@ def request_wifi_scan_results(): CORE.data[KEEP_SCAN_RESULTS_KEY] = True +def enable_runtime_power_save_control(): + """Enable runtime WiFi power save control. + + Components that need to dynamically switch WiFi power saving on/off for latency + performance (e.g., audio streaming, large data transfers) should call this + function during their code generation. This enables the request_high_performance() + and release_high_performance() APIs. + + Only supported on ESP32. + """ + CORE.data[RUNTIME_POWER_SAVE_KEY] = True + + @coroutine_with_priority(CoroPriority.FINAL) async def final_step(): - """Final code generation step to configure scan result retention.""" + """Final code generation step to configure optional WiFi features.""" if CORE.data.get(KEEP_SCAN_RESULTS_KEY, False): cg.add( cg.RawExpression("wifi::global_wifi_component->set_keep_scan_results(true)") ) + if CORE.data.get(RUNTIME_POWER_SAVE_KEY, False): + cg.add_define("USE_WIFI_RUNTIME_POWER_SAVE") @automation.register_action( diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 23a4020453..41931a7785 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -330,6 +330,19 @@ float WiFiComponent::get_setup_priority() const { return setup_priority::WIFI; } void WiFiComponent::setup() { this->wifi_pre_setup_(); + +#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE) + // Create semaphore for high-performance mode requests + // Start at 0, increment on request, decrement on release + this->high_performance_semaphore_ = xSemaphoreCreateCounting(UINT32_MAX, 0); + if (this->high_performance_semaphore_ == nullptr) { + ESP_LOGE(TAG, "Failed semaphore"); + } + + // Store the configured power save mode as baseline + this->configured_power_save_ = this->power_save_; +#endif + if (this->enable_on_boot_) { this->start(); } else { @@ -371,6 +384,19 @@ void WiFiComponent::start() { ESP_LOGV(TAG, "Setting Output Power Option failed"); } +#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE) + // Synchronize power_save_ with semaphore state before applying + if (this->high_performance_semaphore_ != nullptr) { + UBaseType_t semaphore_count = uxSemaphoreGetCount(this->high_performance_semaphore_); + if (semaphore_count > 0) { + this->power_save_ = WIFI_POWER_SAVE_NONE; + this->is_high_performance_mode_ = true; + } else { + this->power_save_ = this->configured_power_save_; + this->is_high_performance_mode_ = false; + } + } +#endif if (!this->wifi_apply_power_save_()) { ESP_LOGV(TAG, "Setting Power Save Option failed"); } @@ -525,6 +551,31 @@ void WiFiComponent::loop() { } } } + +#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE) + // Check if power save mode needs to be updated based on high-performance requests + if (this->high_performance_semaphore_ != nullptr) { + // Semaphore count directly represents active requests (starts at 0, increments on request) + UBaseType_t semaphore_count = uxSemaphoreGetCount(this->high_performance_semaphore_); + + if (semaphore_count > 0 && !this->is_high_performance_mode_) { + // Transition to high-performance mode (no power save) + ESP_LOGV(TAG, "Switching to high-performance mode (%" PRIu32 " active %s)", (uint32_t) semaphore_count, + semaphore_count == 1 ? "request" : "requests"); + this->power_save_ = WIFI_POWER_SAVE_NONE; + if (this->wifi_apply_power_save_()) { + this->is_high_performance_mode_ = true; + } + } else if (semaphore_count == 0 && this->is_high_performance_mode_) { + // Restore to configured power save mode + ESP_LOGV(TAG, "Restoring power save mode to configured setting"); + this->power_save_ = this->configured_power_save_; + if (this->wifi_apply_power_save_()) { + this->is_high_performance_mode_ = false; + } + } + } +#endif } WiFiComponent::WiFiComponent() { global_wifi_component = this; } @@ -1567,7 +1618,12 @@ bool WiFiComponent::is_connected() { return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED && this->wifi_sta_connect_status_() == WiFiSTAConnectStatus::CONNECTED && !this->error_from_callback_; } -void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->power_save_ = power_save; } +void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { + this->power_save_ = power_save; +#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE) + this->configured_power_save_ = power_save; +#endif +} void WiFiComponent::set_passive_scan(bool passive) { this->passive_scan_ = passive; } @@ -1586,6 +1642,38 @@ bool WiFiComponent::is_esp32_improv_active_() { #endif } +#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE) +bool WiFiComponent::request_high_performance() { + // Already configured for high performance - request satisfied + if (this->configured_power_save_ == WIFI_POWER_SAVE_NONE) { + return true; + } + + // Semaphore initialization failed + if (this->high_performance_semaphore_ == nullptr) { + return false; + } + + // Give the semaphore (non-blocking). This increments the count. + return xSemaphoreGive(this->high_performance_semaphore_) == pdTRUE; +} + +bool WiFiComponent::release_high_performance() { + // Already configured for high performance - nothing to release + if (this->configured_power_save_ == WIFI_POWER_SAVE_NONE) { + return true; + } + + // Semaphore initialization failed + if (this->high_performance_semaphore_ == nullptr) { + return false; + } + + // Take the semaphore (non-blocking). This decrements the count. + return xSemaphoreTake(this->high_performance_semaphore_, 0) == pdTRUE; +} +#endif // USE_ESP32 && USE_WIFI_RUNTIME_POWER_SAVE + #ifdef USE_WIFI_FAST_CONNECT bool WiFiComponent::load_fast_connect_settings_(WiFiAP ¶ms) { SavedWifiFastConnectSettings fast_connect_save{}; diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 441606a2c1..0dac80ad21 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -49,6 +49,11 @@ extern "C" { #include #endif +#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE) +#include +#include +#endif + namespace esphome { namespace wifi { @@ -365,6 +370,37 @@ class WiFiComponent : public Component { int32_t get_wifi_channel(); +#ifdef USE_WIFI_RUNTIME_POWER_SAVE + /** Request high-performance mode (no power saving) for improved WiFi latency. + * + * Components that need maximum WiFi performance (e.g., audio streaming, large data transfers) + * can call this method to temporarily disable WiFi power saving. Multiple components can + * request high performance simultaneously using a counting semaphore. + * + * Power saving will be restored to the YAML-configured mode when all components have + * called release_high_performance(). + * + * Note: Only supported on ESP32. + * + * @return true if request was satisfied (high-performance mode active or already configured), + * false if operation failed (semaphore error) + */ + bool request_high_performance(); + + /** Release a high-performance mode request. + * + * Should be called when a component no longer needs maximum WiFi latency. + * When all requests are released (semaphore count reaches zero), WiFi power saving + * is restored to the YAML-configured mode. + * + * Note: Only supported on ESP32. + * + * @return true if release was successful (or already in high-performance config), + * false if operation failed (semaphore error) + */ + bool release_high_performance(); +#endif // USE_WIFI_RUNTIME_POWER_SAVE + protected: #ifdef USE_WIFI_AP void setup_ap_config_(); @@ -535,6 +571,12 @@ class WiFiComponent : public Component { bool keep_scan_results_{false}; bool did_scan_this_cycle_{false}; bool skip_cooldown_next_cycle_{false}; +#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE) + WiFiPowerSaveMode configured_power_save_{WIFI_POWER_SAVE_NONE}; + bool is_high_performance_mode_{false}; + + SemaphoreHandle_t high_performance_semaphore_{nullptr}; +#endif // Pointers at the end (naturally aligned) Trigger<> *connect_trigger_{new Trigger<>()}; diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 5e7f51e04c..4b24c395b9 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -210,6 +210,7 @@ #define USE_WEBSERVER_SORTING #define USE_WIFI_11KV_SUPPORT #define USE_WIFI_FAST_CONNECT +#define USE_WIFI_RUNTIME_POWER_SAVE #define USB_HOST_MAX_REQUESTS 16 #ifdef USE_ARDUINO diff --git a/tests/components/wifi/test.esp32-idf.yaml b/tests/components/wifi/test.esp32-idf.yaml index 6b3ef20963..3e01d7f990 100644 --- a/tests/components/wifi/test.esp32-idf.yaml +++ b/tests/components/wifi/test.esp32-idf.yaml @@ -1,5 +1,16 @@ psram: +# Tests the high performance request and release; requires the USE_WIFI_RUNTIME_POWER_SAVE define +esphome: + platformio_options: + build_flags: + - "-DUSE_WIFI_RUNTIME_POWER_SAVE" + on_boot: + - then: + - lambda: |- + esphome::wifi::global_wifi_component->request_high_performance(); + esphome::wifi::global_wifi_component->release_high_performance(); + wifi: use_psram: true min_auth_mode: WPA From 7a73a524b94008c31cdcd895feaa5131335af975 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 12:21:09 -0600 Subject: [PATCH 0345/1145] [logger] Eliminate strlen overhead on LibreTiny (#11938) --- esphome/components/logger/logger.h | 4 ++-- esphome/components/logger/logger_libretiny.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 8ba3dacacb..6a8b640331 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -72,11 +72,11 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128; static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; // Platform-specific: does write_msg_ add its own newline? -// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266) +// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, LibreTiny) // Allows single write call with newline included for efficiency // true: write_msg_ adds newline itself via puts()/println() (other platforms) // Newline should NOT be added to buffer -#if defined(USE_ESP32) || defined(USE_ESP8266) +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_LIBRETINY) static constexpr bool WRITE_MSG_ADDS_NEWLINE = false; #else static constexpr bool WRITE_MSG_ADDS_NEWLINE = true; diff --git a/esphome/components/logger/logger_libretiny.cpp b/esphome/components/logger/logger_libretiny.cpp index b8017b841d..cdf55e710c 100644 --- a/esphome/components/logger/logger_libretiny.cpp +++ b/esphome/components/logger/logger_libretiny.cpp @@ -49,7 +49,7 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg, size_t) { this->hw_serial_->println(msg); } +void HOT Logger::write_msg_(const char *msg, size_t len) { this->hw_serial_->write(msg, len); } const LogString *Logger::get_uart_selection_() { switch (this->uart_) { From 0dd842744a1c5ab50a0bae97fa51766f82bbd506 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:44:09 -0600 Subject: [PATCH 0346/1145] Bump github/codeql-action from 4.31.4 to 4.31.5 (#12080) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 80fab8819a..d10c8bf267 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 + uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -86,6 +86,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 + uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 with: category: "/language:${{matrix.language}}" From 378fc4120ae7621bd03ecebc8a51808dc890b535 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:44:27 -0600 Subject: [PATCH 0347/1145] Bump peter-evans/create-pull-request from 7.0.8 to 7.0.9 (#12082) 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 2e36dc517d..8f95fa68ee 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -41,7 +41,7 @@ jobs: python script/run-in-env.py pre-commit run --all-files - name: Commit changes - uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9 with: commit-message: "Synchronise Device Classes from Home Assistant" committer: esphomebot From e2cd0ccd0e05a43fff011c008425cb6bcfa457c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:44:43 -0600 Subject: [PATCH 0348/1145] Bump actions/create-github-app-token from 2.1.4 to 2.2.0 (#12081) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto-label-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-label-pr.yml b/.github/workflows/auto-label-pr.yml index 8d8e08a5fc..998f3315c6 100644 --- a/.github/workflows/auto-label-pr.yml +++ b/.github/workflows/auto-label-pr.yml @@ -26,7 +26,7 @@ jobs: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2 + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2 with: app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} From a0440603b7ea6585680a28de05bc169dbead0739 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 13:45:06 -0600 Subject: [PATCH 0349/1145] [wifi] Use ESP-IDF IP formatting macros directly to eliminate heap allocations (#12078) --- esphome/components/wifi/wifi_component_esp_idf.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 4aac03885a..e6e914c0b4 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -603,10 +603,6 @@ const char *get_auth_mode_str(uint8_t mode) { } } -std::string format_ip4_addr(const esp_ip4_addr_t &ip) { return str_snprintf(IPSTR, 15, IP2STR(&ip)); } -#if LWIP_IPV6 -std::string format_ip6_addr(const esp_ip6_addr_t &ip) { return str_snprintf(IPV6STR, 39, IPV62STR(ip)); } -#endif /* LWIP_IPV6 */ const char *get_disconnect_reason_str(uint8_t reason) { switch (reason) { case WIFI_REASON_AUTH_EXPIRE: @@ -761,14 +757,13 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { #if USE_NETWORK_IPV6 esp_netif_create_ip6_linklocal(s_sta_netif); #endif /* USE_NETWORK_IPV6 */ - ESP_LOGV(TAG, "static_ip=%s gateway=%s", format_ip4_addr(it.ip_info.ip).c_str(), - format_ip4_addr(it.ip_info.gw).c_str()); + ESP_LOGV(TAG, "static_ip=" IPSTR " gateway=" IPSTR, IP2STR(&it.ip_info.ip), IP2STR(&it.ip_info.gw)); this->got_ipv4_address_ = true; #if USE_NETWORK_IPV6 } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_GOT_IP6) { const auto &it = data->data.ip_got_ip6; - ESP_LOGV(TAG, "IPv6 address=%s", format_ip6_addr(it.ip6_info.ip).c_str()); + ESP_LOGV(TAG, "IPv6 address=" IPV6STR, IPV62STR(it.ip6_info.ip)); this->num_ipv6_addresses_++; #endif /* USE_NETWORK_IPV6 */ @@ -832,7 +827,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_AP_STAIPASSIGNED) { const auto &it = data->data.ip_ap_staipassigned; - ESP_LOGV(TAG, "AP client assigned IP %s", format_ip4_addr(it.ip).c_str()); + ESP_LOGV(TAG, "AP client assigned IP " IPSTR, IP2STR(&it.ip)); } } From 909baf5e7a8daee99775775220367cf994a377db Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 13:45:29 -0600 Subject: [PATCH 0350/1145] [prometheus] Use current_option() instead of deprecated .state for select entities (#12079) --- esphome/components/prometheus/prometheus_handler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 6b57a3f718..812b547860 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -726,7 +726,7 @@ void PrometheusHandler::select_row_(AsyncResponseStream *stream, select::Select stream->print(ESPHOME_F("\",name=\"")); stream->print(relabel_name_(obj).c_str()); stream->print(ESPHOME_F("\",value=\"")); - stream->print(obj->state.c_str()); + stream->print(obj->current_option()); stream->print(ESPHOME_F("\"} ")); stream->print(ESPHOME_F("1.0")); stream->print(ESPHOME_F("\n")); From 97ba67f4eee3a85e0e416565cba3023900f66f0d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 13:45:56 -0600 Subject: [PATCH 0351/1145] [core] Deprecate unsafe const char* APIs in mark_failed() and status_set_error(), add LogString* overloads (#12021) --- .../absolute_humidity/absolute_humidity.cpp | 2 +- esphome/components/aht10/aht10.cpp | 2 +- esphome/components/bh1900nux/bh1900nux.cpp | 2 +- .../components/bme280_base/bme280_base.cpp | 18 +++--- .../components/bmp280_base/bmp280_base.cpp | 16 +++--- esphome/components/camera/camera.cpp | 2 +- .../cst816/touchscreen/cst816_touchscreen.cpp | 4 +- esphome/components/epaper_spi/epaper_spi.cpp | 4 +- .../update/esp32_hosted_update.cpp | 10 ++-- esphome/components/esp_ldo/esp_ldo.cpp | 2 +- esphome/components/gdk101/gdk101.cpp | 6 +- .../gt911/touchscreen/gt911_touchscreen.cpp | 4 +- .../update/http_request_update.cpp | 9 +-- esphome/components/lvgl/lvgl_esphome.cpp | 4 +- esphome/components/max17043/max17043.cpp | 4 +- esphome/components/mipi_dsi/mipi_dsi.cpp | 22 ++++---- esphome/components/mipi_dsi/mipi_dsi.h | 2 +- esphome/components/mipi_rgb/mipi_rgb.cpp | 8 +-- esphome/components/mipi_spi/mipi_spi.h | 2 +- .../mixer/speaker/mixer_speaker.cpp | 13 +++-- esphome/components/nau7802/nau7802.cpp | 2 +- .../packet_transport/packet_transport.cpp | 2 +- esphome/components/qmp6988/qmp6988.cpp | 2 +- .../resampler/speaker/resampler_speaker.cpp | 12 ++-- esphome/components/sht4x/sht4x.cpp | 2 +- esphome/components/stts22h/stts22h.cpp | 12 ++-- esphome/components/udp/udp_component.cpp | 10 ++-- .../components/usb_host/usb_host_client.cpp | 2 +- .../usb_host/usb_host_component.cpp | 2 +- esphome/components/usb_uart/usb_uart.cpp | 4 +- .../voice_assistant/voice_assistant.cpp | 2 +- .../components/wake_on_lan/wake_on_lan.cpp | 2 +- esphome/core/component.cpp | 55 ++++++++++++++----- esphome/core/component.h | 21 ++++++- 34 files changed, 157 insertions(+), 109 deletions(-) diff --git a/esphome/components/absolute_humidity/absolute_humidity.cpp b/esphome/components/absolute_humidity/absolute_humidity.cpp index 2c5603ee3d..d16a024d86 100644 --- a/esphome/components/absolute_humidity/absolute_humidity.cpp +++ b/esphome/components/absolute_humidity/absolute_humidity.cpp @@ -87,7 +87,7 @@ void AbsoluteHumidityComponent::loop() { break; default: this->publish_state(NAN); - this->status_set_error("Invalid saturation vapor pressure equation selection!"); + this->status_set_error(LOG_STR("Invalid saturation vapor pressure equation selection!")); return; } ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es); diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index 53c712a7a7..03d9d9cd9e 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -83,7 +83,7 @@ void AHT10Component::setup() { void AHT10Component::restart_read_() { if (this->read_count_ == AHT10_ATTEMPTS) { this->read_count_ = 0; - this->status_set_error("Reading timed out"); + this->status_set_error(LOG_STR("Reading timed out")); return; } this->read_count_++; diff --git a/esphome/components/bh1900nux/bh1900nux.cpp b/esphome/components/bh1900nux/bh1900nux.cpp index 96a06adaa0..0e71bd6532 100644 --- a/esphome/components/bh1900nux/bh1900nux.cpp +++ b/esphome/components/bh1900nux/bh1900nux.cpp @@ -23,7 +23,7 @@ void BH1900NUXSensor::setup() { i2c::ErrorCode result_code = this->write_register(SOFT_RESET_REG, &SOFT_RESET_PAYLOAD, 1); // Software Reset to check communication if (result_code != i2c::ERROR_OK) { - this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL)); return; } } diff --git a/esphome/components/bme280_base/bme280_base.cpp b/esphome/components/bme280_base/bme280_base.cpp index 86b65d361d..c5d4c9c0a5 100644 --- a/esphome/components/bme280_base/bme280_base.cpp +++ b/esphome/components/bme280_base/bme280_base.cpp @@ -100,18 +100,18 @@ void BME280Component::setup() { if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) { this->error_code_ = COMMUNICATION_FAILED; - this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL)); return; } if (chip_id != 0x60) { this->error_code_ = WRONG_CHIP_ID; - this->mark_failed(BME280_ERROR_WRONG_CHIP_ID); + this->mark_failed(LOG_STR(BME280_ERROR_WRONG_CHIP_ID)); return; } // Send a soft reset. if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) { - this->mark_failed("Reset failed"); + this->mark_failed(LOG_STR("Reset failed")); return; } // Wait until the NVM data has finished loading. @@ -120,12 +120,12 @@ void BME280Component::setup() { do { // NOLINT delay(2); if (!this->read_byte(BME280_REGISTER_STATUS, &status)) { - this->mark_failed("Error reading status register"); + this->mark_failed(LOG_STR("Error reading status register")); return; } } while ((status & BME280_STATUS_IM_UPDATE) && (--retry)); if (status & BME280_STATUS_IM_UPDATE) { - this->mark_failed("Timeout loading NVM"); + this->mark_failed(LOG_STR("Timeout loading NVM")); return; } @@ -153,26 +153,26 @@ void BME280Component::setup() { uint8_t humid_control_val = 0; if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) { - this->mark_failed("Read humidity control"); + this->mark_failed(LOG_STR("Read humidity control")); return; } humid_control_val &= ~0b00000111; humid_control_val |= this->humidity_oversampling_ & 0b111; if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) { - this->mark_failed("Write humidity control"); + this->mark_failed(LOG_STR("Write humidity control")); return; } uint8_t config_register = 0; if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) { - this->mark_failed("Read config"); + this->mark_failed(LOG_STR("Read config")); return; } config_register &= ~0b11111100; config_register |= 0b101 << 5; // 1000 ms standby time config_register |= (this->iir_filter_ & 0b111) << 2; if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) { - this->mark_failed("Write config"); + this->mark_failed(LOG_STR("Write config")); return; } } diff --git a/esphome/components/bmp280_base/bmp280_base.cpp b/esphome/components/bmp280_base/bmp280_base.cpp index 39654f5875..728eead521 100644 --- a/esphome/components/bmp280_base/bmp280_base.cpp +++ b/esphome/components/bmp280_base/bmp280_base.cpp @@ -65,23 +65,23 @@ void BMP280Component::setup() { // https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855 if (!this->bmp_read_byte(0xD0, &chip_id)) { this->error_code_ = COMMUNICATION_FAILED; - this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL)); return; } if (!this->bmp_read_byte(0xD0, &chip_id)) { this->error_code_ = COMMUNICATION_FAILED; - this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL)); return; } if (chip_id != 0x58) { this->error_code_ = WRONG_CHIP_ID; - this->mark_failed(BMP280_ERROR_WRONG_CHIP_ID); + this->mark_failed(LOG_STR(BMP280_ERROR_WRONG_CHIP_ID)); return; } // Send a soft reset. if (!this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) { - this->mark_failed("Reset failed"); + this->mark_failed(LOG_STR("Reset failed")); return; } // Wait until the NVM data has finished loading. @@ -90,12 +90,12 @@ void BMP280Component::setup() { do { delay(2); if (!this->bmp_read_byte(BMP280_REGISTER_STATUS, &status)) { - this->mark_failed("Error reading status register"); + this->mark_failed(LOG_STR("Error reading status register")); return; } } while ((status & BMP280_STATUS_IM_UPDATE) && (--retry)); if (status & BMP280_STATUS_IM_UPDATE) { - this->mark_failed("Timeout loading NVM"); + this->mark_failed(LOG_STR("Timeout loading NVM")); return; } @@ -116,14 +116,14 @@ void BMP280Component::setup() { uint8_t config_register = 0; if (!this->bmp_read_byte(BMP280_REGISTER_CONFIG, &config_register)) { - this->mark_failed("Read config"); + this->mark_failed(LOG_STR("Read config")); return; } config_register &= ~0b11111100; config_register |= 0b000 << 5; // 0.5 ms standby time config_register |= (this->iir_filter_ & 0b111) << 2; if (!this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register)) { - this->mark_failed("Write config"); + this->mark_failed(LOG_STR("Write config")); return; } } diff --git a/esphome/components/camera/camera.cpp b/esphome/components/camera/camera.cpp index 3bd632af5c..66b8138f38 100644 --- a/esphome/components/camera/camera.cpp +++ b/esphome/components/camera/camera.cpp @@ -8,7 +8,7 @@ Camera *Camera::global_camera = nullptr; Camera::Camera() { if (global_camera != nullptr) { - this->status_set_error("Multiple cameras are configured, but only one is supported."); + this->status_set_error(LOG_STR("Multiple cameras are configured, but only one is supported.")); this->mark_failed(); return; } diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp index 0560f1b475..f6280a75a1 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp @@ -20,13 +20,13 @@ void CST816Touchscreen::continue_setup_() { break; default: ESP_LOGE(TAG, "Unknown chip ID: 0x%02X", this->chip_id_); - this->status_set_error("Unknown chip ID"); + this->status_set_error(LOG_STR("Unknown chip ID")); this->mark_failed(); return; } this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION); } else if (!this->skip_probe_) { - this->status_set_error("Failed to read chip id"); + this->status_set_error(LOG_STR("Failed to read chip id")); this->mark_failed(); return; } diff --git a/esphome/components/epaper_spi/epaper_spi.cpp b/esphome/components/epaper_spi/epaper_spi.cpp index cf6a0b0c3d..39959cd743 100644 --- a/esphome/components/epaper_spi/epaper_spi.cpp +++ b/esphome/components/epaper_spi/epaper_spi.cpp @@ -22,7 +22,7 @@ const char *EPaperBase::epaper_state_to_string_() { void EPaperBase::setup() { if (!this->init_buffer_(this->buffer_length_)) { - this->mark_failed("Failed to initialise buffer"); + this->mark_failed(LOG_STR("Failed to initialise buffer")); return; } this->setup_pins_(); @@ -246,7 +246,7 @@ void EPaperBase::initialise_() { auto length = this->init_sequence_length_; while (index != length) { if (length - index < 2) { - this->mark_failed("Malformed init sequence"); + this->mark_failed(LOG_STR("Malformed init sequence")); return; } const uint8_t cmd = sequence[index++]; diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp index adbcc5bf11..f34a0ae10e 100644 --- a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp @@ -88,7 +88,7 @@ void Esp32HostedUpdate::perform(bool force) { hasher.add(this->firmware_data_, this->firmware_size_); hasher.calculate(); if (!hasher.equals_bytes(this->firmware_sha256_.data())) { - this->status_set_error("SHA256 verification failed"); + this->status_set_error(LOG_STR("SHA256 verification failed")); this->publish_state(); return; } @@ -105,7 +105,7 @@ void Esp32HostedUpdate::perform(bool force) { if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err)); this->state_ = prev_state; - this->status_set_error("Failed to begin OTA"); + this->status_set_error(LOG_STR("Failed to begin OTA")); this->publish_state(); return; } @@ -121,7 +121,7 @@ void Esp32HostedUpdate::perform(bool force) { ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err)); esp_hosted_slave_ota_end(); // NOLINT this->state_ = prev_state; - this->status_set_error("Failed to write OTA data"); + this->status_set_error(LOG_STR("Failed to write OTA data")); this->publish_state(); return; } @@ -134,7 +134,7 @@ void Esp32HostedUpdate::perform(bool force) { if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err)); this->state_ = prev_state; - this->status_set_error("Failed to end OTA"); + this->status_set_error(LOG_STR("Failed to end OTA")); this->publish_state(); return; } @@ -144,7 +144,7 @@ void Esp32HostedUpdate::perform(bool force) { if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to activate OTA: %s", esp_err_to_name(err)); this->state_ = prev_state; - this->status_set_error("Failed to activate OTA"); + this->status_set_error(LOG_STR("Failed to activate OTA")); this->publish_state(); return; } diff --git a/esphome/components/esp_ldo/esp_ldo.cpp b/esphome/components/esp_ldo/esp_ldo.cpp index 9ea7000b70..5e3d4159f3 100644 --- a/esphome/components/esp_ldo/esp_ldo.cpp +++ b/esphome/components/esp_ldo/esp_ldo.cpp @@ -15,7 +15,7 @@ void EspLdo::setup() { auto err = esp_ldo_acquire_channel(&config, &this->handle_); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to acquire LDO channel %d with voltage %fV", this->channel_, this->voltage_); - this->mark_failed("Failed to acquire LDO channel"); + this->mark_failed(LOG_STR("Failed to acquire LDO channel")); } else { ESP_LOGD(TAG, "Acquired LDO channel %d with voltage %fV", this->channel_, this->voltage_); } diff --git a/esphome/components/gdk101/gdk101.cpp b/esphome/components/gdk101/gdk101.cpp index 6c218f03d9..617e2138fb 100644 --- a/esphome/components/gdk101/gdk101.cpp +++ b/esphome/components/gdk101/gdk101.cpp @@ -36,20 +36,20 @@ void GDK101Component::setup() { uint8_t data[2]; // first, reset the sensor if (!this->reset_sensor_(data)) { - this->status_set_error("Reset failed!"); + this->status_set_error(LOG_STR("Reset failed!")); this->mark_failed(); return; } // sensor should acknowledge success of the reset procedure if (data[0] != 1) { - this->status_set_error("Reset not acknowledged!"); + this->status_set_error(LOG_STR("Reset not acknowledged!")); this->mark_failed(); return; } delay(10); // read firmware version if (!this->read_fw_version_(data)) { - this->status_set_error("Failed to read firmware version"); + this->status_set_error(LOG_STR("Failed to read firmware version")); this->mark_failed(); return; } diff --git a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp index 992a86cc21..b11880a042 100644 --- a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp +++ b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp @@ -79,13 +79,13 @@ void GT911Touchscreen::setup_internal_() { } } if (err != i2c::ERROR_OK) { - this->mark_failed("Calibration error"); + this->mark_failed(LOG_STR("Calibration error")); return; } } if (err != i2c::ERROR_OK) { - this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL)); return; } this->setup_done_ = true; diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 9dbf8d181a..c91b0eba73 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -29,7 +29,7 @@ void HttpRequestUpdate::setup() { this->publish_state(); } else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) { this->state_ = update::UPDATE_STATE_AVAILABLE; - this->status_set_error("Failed to install firmware"); + this->status_set_error(LOG_STR("Failed to install firmware")); this->publish_state(); } }); @@ -51,7 +51,7 @@ void HttpRequestUpdate::update_task(void *params) { if (container == nullptr || container->status_code != HTTP_STATUS_OK) { ESP_LOGE(TAG, "Failed to fetch manifest from %s", this_update->source_url_.c_str()); // Defer to main loop to avoid race condition on component_state_ read-modify-write - this_update->defer([this_update]() { this_update->status_set_error("Failed to fetch manifest"); }); + this_update->defer([this_update]() { this_update->status_set_error(LOG_STR("Failed to fetch manifest")); }); UPDATE_RETURN; } @@ -60,7 +60,8 @@ void HttpRequestUpdate::update_task(void *params) { if (data == nullptr) { ESP_LOGE(TAG, "Failed to allocate %zu bytes for manifest", container->content_length); // Defer to main loop to avoid race condition on component_state_ read-modify-write - this_update->defer([this_update]() { this_update->status_set_error("Failed to allocate memory for manifest"); }); + this_update->defer( + [this_update]() { this_update->status_set_error(LOG_STR("Failed to allocate memory for manifest")); }); container->end(); UPDATE_RETURN; } @@ -123,7 +124,7 @@ void HttpRequestUpdate::update_task(void *params) { if (!valid) { ESP_LOGE(TAG, "Failed to parse JSON from %s", this_update->source_url_.c_str()); // Defer to main loop to avoid race condition on component_state_ read-modify-write - this_update->defer([this_update]() { this_update->status_set_error("Failed to parse manifest JSON"); }); + this_update->defer([this_update]() { this_update->status_set_error(LOG_STR("Failed to parse manifest JSON")); }); UPDATE_RETURN; } diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 05005b0217..fbcd68378c 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -466,7 +466,7 @@ void LvglComponent::setup() { buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT } if (buffer == nullptr) { - this->status_set_error("Memory allocation failure"); + this->status_set_error(LOG_STR("Memory allocation failure")); this->mark_failed(); return; } @@ -479,7 +479,7 @@ void LvglComponent::setup() { if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) { this->rotate_buf_ = static_cast(lv_custom_mem_alloc(buf_bytes)); // NOLINT if (this->rotate_buf_ == nullptr) { - this->status_set_error("Memory allocation failure"); + this->status_set_error(LOG_STR("Memory allocation failure")); this->mark_failed(); return; } diff --git a/esphome/components/max17043/max17043.cpp b/esphome/components/max17043/max17043.cpp index f605fb1324..e8cf4d5ab1 100644 --- a/esphome/components/max17043/max17043.cpp +++ b/esphome/components/max17043/max17043.cpp @@ -57,14 +57,14 @@ void MAX17043Component::setup() { if (config_reg != MAX17043_CONFIG_POWER_UP_DEFAULT) { ESP_LOGE(TAG, "Device does not appear to be a MAX17043"); - this->status_set_error("unrecognised"); + this->status_set_error(LOG_STR("unrecognised")); this->mark_failed(); return; } // need to write back to config register to reset the sleep bit if (!this->write_byte_16(MAX17043_CONFIG, MAX17043_CONFIG_POWER_UP_DEFAULT)) { - this->status_set_error("sleep reset failed"); + this->status_set_error(LOG_STR("sleep reset failed")); this->mark_failed(); return; } diff --git a/esphome/components/mipi_dsi/mipi_dsi.cpp b/esphome/components/mipi_dsi/mipi_dsi.cpp index 7305435e4b..cae8647398 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.cpp +++ b/esphome/components/mipi_dsi/mipi_dsi.cpp @@ -12,8 +12,8 @@ static bool notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel return (need_yield == pdTRUE); } -void MIPI_DSI::smark_failed(const char *message, esp_err_t err) { - ESP_LOGE(TAG, "%s: %s", message, esp_err_to_name(err)); +void MIPI_DSI::smark_failed(const LogString *message, esp_err_t err) { + ESP_LOGE(TAG, "%s: %s", LOG_STR_ARG(message), esp_err_to_name(err)); this->mark_failed(message); } @@ -37,7 +37,7 @@ void MIPI_DSI::setup() { }; auto err = esp_lcd_new_dsi_bus(&bus_config, &this->bus_handle_); if (err != ESP_OK) { - this->smark_failed("lcd_new_dsi_bus failed", err); + this->smark_failed(LOG_STR("lcd_new_dsi_bus failed"), err); return; } esp_lcd_dbi_io_config_t dbi_config = { @@ -47,7 +47,7 @@ void MIPI_DSI::setup() { }; err = esp_lcd_new_panel_io_dbi(this->bus_handle_, &dbi_config, &this->io_handle_); if (err != ESP_OK) { - this->smark_failed("new_panel_io_dbi failed", err); + this->smark_failed(LOG_STR("new_panel_io_dbi failed"), err); return; } auto pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565; @@ -75,7 +75,7 @@ void MIPI_DSI::setup() { }}; err = esp_lcd_new_panel_dpi(this->bus_handle_, &dpi_config, &this->handle_); if (err != ESP_OK) { - this->smark_failed("esp_lcd_new_panel_dpi failed", err); + this->smark_failed(LOG_STR("esp_lcd_new_panel_dpi failed"), err); return; } if (this->reset_pin_ != nullptr) { @@ -92,14 +92,14 @@ void MIPI_DSI::setup() { auto when = millis() + 120; err = esp_lcd_panel_init(this->handle_); if (err != ESP_OK) { - this->smark_failed("esp_lcd_init failed", err); + this->smark_failed(LOG_STR("esp_lcd_init failed"), err); return; } size_t index = 0; auto &vec = this->init_sequence_; while (index != vec.size()) { if (vec.size() - index < 2) { - this->mark_failed("Malformed init sequence"); + this->mark_failed(LOG_STR("Malformed init sequence")); return; } uint8_t cmd = vec[index++]; @@ -110,7 +110,7 @@ void MIPI_DSI::setup() { } else { uint8_t num_args = x & 0x7F; if (vec.size() - index < num_args) { - this->mark_failed("Malformed init sequence"); + this->mark_failed(LOG_STR("Malformed init sequence")); return; } if (cmd == SLEEP_OUT) { @@ -125,7 +125,7 @@ void MIPI_DSI::setup() { format_hex_pretty(ptr, num_args, '.', false).c_str()); err = esp_lcd_panel_io_tx_param(this->io_handle_, cmd, ptr, num_args); if (err != ESP_OK) { - this->smark_failed("lcd_panel_io_tx_param failed", err); + this->smark_failed(LOG_STR("lcd_panel_io_tx_param failed"), err); return; } index += num_args; @@ -140,7 +140,7 @@ void MIPI_DSI::setup() { err = (esp_lcd_dpi_panel_register_event_callbacks(this->handle_, &cbs, this->io_lock_)); if (err != ESP_OK) { - this->smark_failed("Failed to register callbacks", err); + this->smark_failed(LOG_STR("Failed to register callbacks"), err); return; } @@ -222,7 +222,7 @@ bool MIPI_DSI::check_buffer_() { RAMAllocator allocator; this->buffer_ = allocator.allocate(this->height_ * this->width_ * bytes_per_pixel); if (this->buffer_ == nullptr) { - this->mark_failed("Could not allocate buffer for display!"); + this->mark_failed(LOG_STR("Could not allocate buffer for display!")); return false; } return true; diff --git a/esphome/components/mipi_dsi/mipi_dsi.h b/esphome/components/mipi_dsi/mipi_dsi.h index 98ee092ed1..1cffe3b178 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.h +++ b/esphome/components/mipi_dsi/mipi_dsi.h @@ -62,7 +62,7 @@ class MIPI_DSI : public display::Display { void set_lanes(uint8_t lanes) { this->lanes_ = lanes; } void set_madctl(uint8_t madctl) { this->madctl_ = madctl; } - void smark_failed(const char *message, esp_err_t err); + void smark_failed(const LogString *message, esp_err_t err); void update() override; diff --git a/esphome/components/mipi_rgb/mipi_rgb.cpp b/esphome/components/mipi_rgb/mipi_rgb.cpp index 4c687724cf..74eedae4f4 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.cpp +++ b/esphome/components/mipi_rgb/mipi_rgb.cpp @@ -73,7 +73,7 @@ void MipiRgbSpi::write_init_sequence_() { auto &vec = this->init_sequence_; while (index != vec.size()) { if (vec.size() - index < 2) { - this->mark_failed("Malformed init sequence"); + this->mark_failed(LOG_STR("Malformed init sequence")); return; } uint8_t cmd = vec[index++]; @@ -84,7 +84,7 @@ void MipiRgbSpi::write_init_sequence_() { } else { uint8_t num_args = x & 0x7F; if (vec.size() - index < num_args) { - this->mark_failed("Malformed init sequence"); + this->mark_failed(LOG_STR("Malformed init sequence")); return; } if (cmd == SLEEP_OUT) { @@ -165,7 +165,7 @@ void MipiRgb::common_setup_() { err = esp_lcd_panel_init(this->handle_); if (err != ESP_OK) { ESP_LOGE(TAG, "lcd setup failed: %s", esp_err_to_name(err)); - this->mark_failed("lcd setup failed"); + this->mark_failed(LOG_STR("lcd setup failed")); } ESP_LOGCONFIG(TAG, "MipiRgb setup complete"); } @@ -249,7 +249,7 @@ bool MipiRgb::check_buffer_() { RAMAllocator allocator; this->buffer_ = allocator.allocate(this->height_ * this->width_); if (this->buffer_ == nullptr) { - this->mark_failed("Could not allocate buffer for display!"); + this->mark_failed(LOG_STR("Could not allocate buffer for display!")); return false; } return true; diff --git a/esphome/components/mipi_spi/mipi_spi.h b/esphome/components/mipi_spi/mipi_spi.h index 7e597d1c61..1953aef035 100644 --- a/esphome/components/mipi_spi/mipi_spi.h +++ b/esphome/components/mipi_spi/mipi_spi.h @@ -478,7 +478,7 @@ class MipiSpiBuffer : public MipiSpi allocator{}; this->buffer_ = allocator.allocate(BUFFER_WIDTH * BUFFER_HEIGHT / FRACTION); if (this->buffer_ == nullptr) { - this->mark_failed("Buffer allocation failed"); + this->mark_failed(LOG_STR("Buffer allocation failed")); } } diff --git a/esphome/components/mixer/speaker/mixer_speaker.cpp b/esphome/components/mixer/speaker/mixer_speaker.cpp index b0b64f5709..043b629cf1 100644 --- a/esphome/components/mixer/speaker/mixer_speaker.cpp +++ b/esphome/components/mixer/speaker/mixer_speaker.cpp @@ -78,19 +78,20 @@ void SourceSpeaker::loop() { } else { switch (err) { case ESP_ERR_NO_MEM: - this->status_set_error("Failed to start mixer: not enough memory"); + this->status_set_error(LOG_STR("Failed to start mixer: not enough memory")); break; case ESP_ERR_NOT_SUPPORTED: - this->status_set_error("Failed to start mixer: unsupported bits per sample"); + this->status_set_error(LOG_STR("Failed to start mixer: unsupported bits per sample")); break; case ESP_ERR_INVALID_ARG: - this->status_set_error("Failed to start mixer: audio stream isn't compatible with the other audio stream."); + this->status_set_error( + LOG_STR("Failed to start mixer: audio stream isn't compatible with the other audio stream.")); break; case ESP_ERR_INVALID_STATE: - this->status_set_error("Failed to start mixer: mixer task failed to start"); + this->status_set_error(LOG_STR("Failed to start mixer: mixer task failed to start")); break; default: - this->status_set_error("Failed to start mixer"); + this->status_set_error(LOG_STR("Failed to start mixer")); break; } @@ -317,7 +318,7 @@ void MixerSpeaker::loop() { xEventGroupClearBits(this->event_group_, MixerEventGroupBits::STATE_STARTING); } if (event_group_bits & MixerEventGroupBits::ERR_ESP_NO_MEM) { - this->status_set_error("Failed to allocate the mixer's internal buffer"); + this->status_set_error(LOG_STR("Failed to allocate the mixer's internal buffer")); xEventGroupClearBits(this->event_group_, MixerEventGroupBits::ERR_ESP_NO_MEM); } if (event_group_bits & MixerEventGroupBits::STATE_RUNNING) { diff --git a/esphome/components/nau7802/nau7802.cpp b/esphome/components/nau7802/nau7802.cpp index 6a31b754f7..11f63a9a33 100644 --- a/esphome/components/nau7802/nau7802.cpp +++ b/esphome/components/nau7802/nau7802.cpp @@ -278,7 +278,7 @@ void NAU7802Sensor::loop() { this->set_calibration_failure_(true); this->state_ = CalibrationState::INACTIVE; ESP_LOGE(TAG, "Failed to calibrate sensor"); - this->status_set_error("Calibration Failed"); + this->status_set_error(LOG_STR("Calibration Failed")); return; } diff --git a/esphome/components/packet_transport/packet_transport.cpp b/esphome/components/packet_transport/packet_transport.cpp index 857b40ca0e..37e5f3d9e1 100644 --- a/esphome/components/packet_transport/packet_transport.cpp +++ b/esphome/components/packet_transport/packet_transport.cpp @@ -195,7 +195,7 @@ static void add(std::vector &vec, const char *str) { void PacketTransport::setup() { this->name_ = App.get_name().c_str(); if (strlen(this->name_) > 255) { - this->status_set_error("Device name exceeds 255 chars"); + this->status_set_error(LOG_STR("Device name exceeds 255 chars")); this->mark_failed(); return; } diff --git a/esphome/components/qmp6988/qmp6988.cpp b/esphome/components/qmp6988/qmp6988.cpp index 61fde186d7..57f54b6432 100644 --- a/esphome/components/qmp6988/qmp6988.cpp +++ b/esphome/components/qmp6988/qmp6988.cpp @@ -310,7 +310,7 @@ void QMP6988Component::calculate_pressure_() { void QMP6988Component::setup() { if (!this->device_check_()) { - this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL)); return; } diff --git a/esphome/components/resampler/speaker/resampler_speaker.cpp b/esphome/components/resampler/speaker/resampler_speaker.cpp index 5e5615cbb9..ad61aca084 100644 --- a/esphome/components/resampler/speaker/resampler_speaker.cpp +++ b/esphome/components/resampler/speaker/resampler_speaker.cpp @@ -66,17 +66,17 @@ void ResamplerSpeaker::loop() { } if (event_group_bits & ResamplingEventGroupBits::ERR_ESP_NO_MEM) { - this->status_set_error("Resampler task failed to allocate the internal buffers"); + this->status_set_error(LOG_STR("Resampler task failed to allocate the internal buffers")); xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_NO_MEM); this->state_ = speaker::STATE_STOPPING; } if (event_group_bits & ResamplingEventGroupBits::ERR_ESP_NOT_SUPPORTED) { - this->status_set_error("Cannot resample due to an unsupported audio stream"); + this->status_set_error(LOG_STR("Cannot resample due to an unsupported audio stream")); xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_NOT_SUPPORTED); this->state_ = speaker::STATE_STOPPING; } if (event_group_bits & ResamplingEventGroupBits::ERR_ESP_FAIL) { - this->status_set_error("Resampler task failed"); + this->status_set_error(LOG_STR("Resampler task failed")); xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_FAIL); this->state_ = speaker::STATE_STOPPING; } @@ -106,12 +106,12 @@ void ResamplerSpeaker::loop() { } else { switch (err) { case ESP_ERR_INVALID_STATE: - this->status_set_error("Failed to start resampler: resampler task failed to start"); + this->status_set_error(LOG_STR("Failed to start resampler: resampler task failed to start")); break; case ESP_ERR_NO_MEM: - this->status_set_error("Failed to start resampler: not enough memory for task stack"); + this->status_set_error(LOG_STR("Failed to start resampler: not enough memory for task stack")); default: - this->status_set_error("Failed to start resampler"); + this->status_set_error(LOG_STR("Failed to start resampler")); break; } diff --git a/esphome/components/sht4x/sht4x.cpp b/esphome/components/sht4x/sht4x.cpp index 62b8717ded..617b19ef3e 100644 --- a/esphome/components/sht4x/sht4x.cpp +++ b/esphome/components/sht4x/sht4x.cpp @@ -13,7 +13,7 @@ void SHT4XComponent::start_heater_() { ESP_LOGD(TAG, "Heater turning on"); if (this->write(cmd, 1) != i2c::ERROR_OK) { - this->status_set_error("Failed to turn on heater"); + this->status_set_error(LOG_STR("Failed to turn on heater")); } } diff --git a/esphome/components/stts22h/stts22h.cpp b/esphome/components/stts22h/stts22h.cpp index 614dc1da8b..2b2559c843 100644 --- a/esphome/components/stts22h/stts22h.cpp +++ b/esphome/components/stts22h/stts22h.cpp @@ -21,7 +21,7 @@ static const float SENSOR_SCALE = 0.01f; // Sensor resolution in degrees Celsiu void STTS22HComponent::setup() { // Check if device is a STTS22H if (!this->is_stts22h_sensor_()) { - this->mark_failed("Device is not a STTS22H sensor"); + this->mark_failed(LOG_STR("Device is not a STTS22H sensor")); return; } @@ -61,12 +61,12 @@ float STTS22HComponent::read_temperature_() { bool STTS22HComponent::is_stts22h_sensor_() { uint8_t whoami_value; if (this->read_register(WHOAMI_REG, &whoami_value, 1) != i2c::NO_ERROR) { - this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL)); return false; } if (whoami_value != WHOAMI_STTS22H_IDENTIFICATION) { - this->mark_failed("Unexpected WHOAMI identifier. Sensor is not a STTS22H"); + this->mark_failed(LOG_STR("Unexpected WHOAMI identifier. Sensor is not a STTS22H")); return false; } @@ -77,7 +77,7 @@ void STTS22HComponent::initialize_sensor_() { // Read current CTRL_REG configuration uint8_t ctrl_value; if (this->read_register(CTRL_REG, &ctrl_value, 1) != i2c::NO_ERROR) { - this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL)); return; } @@ -86,14 +86,14 @@ void STTS22HComponent::initialize_sensor_() { // FREERUN bit must be cleared (see sensor documentation) ctrl_value &= ~FREERUN_CTRL_ENABLE_FLAG; // Clear FREERUN bit if (this->write_register(CTRL_REG, &ctrl_value, 1) != i2c::NO_ERROR) { - this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL)); return; } // Enable LOW ODR mode and ADD_INC ctrl_value |= LOW_ODR_CTRL_ENABLE_FLAG | ADD_INC_ENABLE_FLAG; // Set LOW ODR bit and ADD_INC bit if (this->write_register(CTRL_REG, &ctrl_value, 1) != i2c::NO_ERROR) { - this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL)); return; } } diff --git a/esphome/components/udp/udp_component.cpp b/esphome/components/udp/udp_component.cpp index 7714793e1c..9105ced21e 100644 --- a/esphome/components/udp/udp_component.cpp +++ b/esphome/components/udp/udp_component.cpp @@ -21,7 +21,7 @@ void UDPComponent::setup() { if (this->should_broadcast_) { this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); if (this->broadcast_socket_ == nullptr) { - this->status_set_error("Could not create socket"); + this->status_set_error(LOG_STR("Could not create socket")); this->mark_failed(); return; } @@ -41,14 +41,14 @@ void UDPComponent::setup() { if (this->should_listen_) { this->listen_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); if (this->listen_socket_ == nullptr) { - this->status_set_error("Could not create socket"); + this->status_set_error(LOG_STR("Could not create socket")); this->mark_failed(); return; } auto err = this->listen_socket_->setblocking(false); if (err < 0) { ESP_LOGE(TAG, "Unable to set nonblocking: errno %d", errno); - this->status_set_error("Unable to set nonblocking"); + this->status_set_error(LOG_STR("Unable to set nonblocking")); this->mark_failed(); return; } @@ -73,7 +73,7 @@ void UDPComponent::setup() { err = this->listen_socket_->setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq)); if (err < 0) { ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno); - this->status_set_error("Failed to set IP_ADD_MEMBERSHIP"); + this->status_set_error(LOG_STR("Failed to set IP_ADD_MEMBERSHIP")); this->mark_failed(); return; } @@ -82,7 +82,7 @@ void UDPComponent::setup() { err = this->listen_socket_->bind((struct sockaddr *) &server, sizeof(server)); if (err != 0) { ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); - this->status_set_error("Unable to bind socket"); + this->status_set_error(LOG_STR("Unable to bind socket")); this->mark_failed(); return; } diff --git a/esphome/components/usb_host/usb_host_client.cpp b/esphome/components/usb_host/usb_host_client.cpp index 4c09cf8a49..fe61353b5d 100644 --- a/esphome/components/usb_host/usb_host_client.cpp +++ b/esphome/components/usb_host/usb_host_client.cpp @@ -188,7 +188,7 @@ void USBClient::setup() { auto err = usb_host_client_register(&config, &this->handle_); if (err != ESP_OK) { ESP_LOGE(TAG, "client register failed: %s", esp_err_to_name(err)); - this->status_set_error("Client register failed"); + this->status_set_error(LOG_STR("Client register failed")); this->mark_failed(); return; } diff --git a/esphome/components/usb_host/usb_host_component.cpp b/esphome/components/usb_host/usb_host_component.cpp index fb19239c73..1e70c289df 100644 --- a/esphome/components/usb_host/usb_host_component.cpp +++ b/esphome/components/usb_host/usb_host_component.cpp @@ -11,7 +11,7 @@ void USBHost::setup() { usb_host_config_t config{}; if (usb_host_install(&config) != ESP_OK) { - this->status_set_error("usb_host_install failed"); + this->status_set_error(LOG_STR("usb_host_install failed")); this->mark_failed(); return; } diff --git a/esphome/components/usb_uart/usb_uart.cpp b/esphome/components/usb_uart/usb_uart.cpp index c24fffb11d..6720c1e690 100644 --- a/esphome/components/usb_uart/usb_uart.cpp +++ b/esphome/components/usb_uart/usb_uart.cpp @@ -320,7 +320,7 @@ static void fix_mps(const usb_ep_desc_t *ep) { void USBUartTypeCdcAcm::on_connected() { auto cdc_devs = this->parse_descriptors(this->device_handle_); if (cdc_devs.empty()) { - this->status_set_error("No CDC-ACM device found"); + this->status_set_error(LOG_STR("No CDC-ACM device found")); this->disconnect(); return; } @@ -341,7 +341,7 @@ void USBUartTypeCdcAcm::on_connected() { if (err != ESP_OK) { ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_, channel->cdc_dev_.bulk_interface_number); - this->status_set_error("usb_host_interface_claim failed"); + this->status_set_error(LOG_STR("usb_host_interface_claim failed")); this->disconnect(); return; } diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index fd35dc7d09..551f0370f2 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -206,7 +206,7 @@ void VoiceAssistant::loop() { case State::START_MICROPHONE: { ESP_LOGD(TAG, "Starting Microphone"); if (!this->allocate_buffers_()) { - this->status_set_error("Failed to allocate buffers"); + this->status_set_error(LOG_STR("Failed to allocate buffers")); return; } if (this->status_has_error()) { diff --git a/esphome/components/wake_on_lan/wake_on_lan.cpp b/esphome/components/wake_on_lan/wake_on_lan.cpp index 7993abd7e7..8c5bdac54b 100644 --- a/esphome/components/wake_on_lan/wake_on_lan.cpp +++ b/esphome/components/wake_on_lan/wake_on_lan.cpp @@ -67,7 +67,7 @@ void WakeOnLanButton::setup() { #if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); if (this->broadcast_socket_ == nullptr) { - this->status_set_error("Could not create socket"); + this->status_set_error(LOG_STR("Could not create socket")); this->mark_failed(); return; } diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index de3dd99d0c..5e6ace8873 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -36,6 +36,9 @@ namespace { struct ComponentErrorMessage { const Component *component; const char *message; + // Track if message is flash pointer (needs LOG_STR_ARG) or RAM pointer + // Remove before 2026.6.0 when deprecated const char* API is removed + bool is_flash_ptr; }; struct ComponentPriorityOverride { @@ -49,6 +52,25 @@ std::unique_ptr> component_error_messages; // Setup priority overrides - freed after setup completes // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) std::unique_ptr> setup_priority_overrides; + +// Helper to store error messages - reduces duplication between deprecated and new API +// Remove before 2026.6.0 when deprecated const char* API is removed +void store_component_error_message(const Component *component, const char *message, bool is_flash_ptr) { + // Lazy allocate the error messages vector if needed + if (!component_error_messages) { + component_error_messages = std::make_unique>(); + } + // Check if this component already has an error message + for (auto &entry : *component_error_messages) { + if (entry.component == component) { + entry.message = message; + entry.is_flash_ptr = is_flash_ptr; + return; + } + } + // Add new error message + component_error_messages->emplace_back(ComponentErrorMessage{component, message, is_flash_ptr}); +} } // namespace namespace setup_priority { @@ -143,16 +165,20 @@ void Component::call_dump_config() { if (this->is_failed()) { // Look up error message from global vector const char *error_msg = nullptr; + bool is_flash_ptr = false; if (component_error_messages) { for (const auto &entry : *component_error_messages) { if (entry.component == this) { error_msg = entry.message; + is_flash_ptr = entry.is_flash_ptr; break; } } } + // Log with appropriate format based on pointer type ESP_LOGE(TAG, " %s is marked FAILED: %s", LOG_STR_ARG(this->get_component_log_str()), - error_msg ? error_msg : LOG_STR_LITERAL("unspecified")); + error_msg ? (is_flash_ptr ? LOG_STR_ARG((const LogString *) error_msg) : error_msg) + : LOG_STR_LITERAL("unspecified")); } } @@ -307,6 +333,7 @@ void Component::status_set_warning(const LogString *message) { ESP_LOGW(TAG, "%s set Warning flag: %s", LOG_STR_ARG(this->get_component_log_str()), message ? LOG_STR_ARG(message) : LOG_STR_LITERAL("unspecified")); } +void Component::status_set_error() { this->status_set_error((const LogString *) nullptr); } void Component::status_set_error(const char *message) { if ((this->component_state_ & STATUS_LED_ERROR) != 0) return; @@ -315,19 +342,19 @@ void Component::status_set_error(const char *message) { ESP_LOGE(TAG, "%s set Error flag: %s", LOG_STR_ARG(this->get_component_log_str()), message ? message : LOG_STR_LITERAL("unspecified")); if (message != nullptr) { - // Lazy allocate the error messages vector if needed - if (!component_error_messages) { - component_error_messages = std::make_unique>(); - } - // Check if this component already has an error message - for (auto &entry : *component_error_messages) { - if (entry.component == this) { - entry.message = message; - return; - } - } - // Add new error message - component_error_messages->emplace_back(ComponentErrorMessage{this, message}); + store_component_error_message(this, message, false); + } +} +void Component::status_set_error(const LogString *message) { + if ((this->component_state_ & STATUS_LED_ERROR) != 0) + return; + this->component_state_ |= STATUS_LED_ERROR; + App.app_state_ |= STATUS_LED_ERROR; + ESP_LOGE(TAG, "%s set Error flag: %s", LOG_STR_ARG(this->get_component_log_str()), + message ? LOG_STR_ARG(message) : LOG_STR_LITERAL("unspecified")); + if (message != nullptr) { + // Store the LogString pointer directly (safe because LogString is always in flash/static memory) + store_component_error_message(this, LOG_STR_ARG(message), true); } } void Component::status_clear_warning() { diff --git a/esphome/core/component.h b/esphome/core/component.h index 462e0e301c..51a9290e8b 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -5,6 +5,7 @@ #include #include +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/optional.h" @@ -157,7 +158,19 @@ class Component { */ virtual void mark_failed(); + // Remove before 2026.6.0 + ESPDEPRECATED("Use mark_failed(LOG_STR(\"static string literal\")) instead. Do NOT use .c_str() from temporary " + "strings. Will stop working in 2026.6.0", + "2025.12.0") void mark_failed(const char *message) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + this->status_set_error(message); +#pragma GCC diagnostic pop + this->mark_failed(); + } + + void mark_failed(const LogString *message) { this->status_set_error(message); this->mark_failed(); } @@ -216,7 +229,13 @@ class Component { void status_set_warning(const char *message = nullptr); void status_set_warning(const LogString *message); - void status_set_error(const char *message = nullptr); + void status_set_error(); // Set error flag without message + // Remove before 2026.6.0 + ESPDEPRECATED("Use status_set_error(LOG_STR(\"static string literal\")) instead. Do NOT use .c_str() from temporary " + "strings. Will stop working in 2026.6.0", + "2025.12.0") + void status_set_error(const char *message); + void status_set_error(const LogString *message); void status_clear_warning(); From eeb373fca98681ed576b767450044d2fb5cefedf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:15:30 +1300 Subject: [PATCH 0352/1145] [online_image] Fix some large PNGs causing watchdog timeout (#12025) Co-authored-by: guillempages --- esphome/components/online_image/png_image.cpp | 9 +++++++++ esphome/components/online_image/png_image.h | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/esphome/components/online_image/png_image.cpp b/esphome/components/online_image/png_image.cpp index 2038d09ed0..ce9d3bdc91 100644 --- a/esphome/components/online_image/png_image.cpp +++ b/esphome/components/online_image/png_image.cpp @@ -2,6 +2,7 @@ #ifdef USE_ONLINE_IMAGE_PNG_SUPPORT #include "esphome/components/display/display_buffer.h" +#include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -38,6 +39,14 @@ static void draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, ui PngDecoder *decoder = (PngDecoder *) pngle_get_user_data(pngle); Color color(rgba[0], rgba[1], rgba[2], rgba[3]); decoder->draw(x, y, w, h, color); + + // Feed watchdog periodically to avoid triggering during long decode operations. + // Feed every 1024 pixels to balance efficiency and responsiveness. + uint32_t pixels = w * h; + decoder->increment_pixels_decoded(pixels); + if ((decoder->get_pixels_decoded() % 1024) < pixels) { + App.feed_wdt(); + } } PngDecoder::PngDecoder(OnlineImage *image) : ImageDecoder(image) { diff --git a/esphome/components/online_image/png_image.h b/esphome/components/online_image/png_image.h index 46519f8ef4..40e85dde33 100644 --- a/esphome/components/online_image/png_image.h +++ b/esphome/components/online_image/png_image.h @@ -25,9 +25,13 @@ class PngDecoder : public ImageDecoder { int prepare(size_t download_size) override; int HOT decode(uint8_t *buffer, size_t size) override; + void increment_pixels_decoded(uint32_t count) { this->pixels_decoded_ += count; } + uint32_t get_pixels_decoded() const { return this->pixels_decoded_; } + protected: RAMAllocator allocator_; pngle_t *pngle_; + uint32_t pixels_decoded_{0}; }; } // namespace online_image From e09656f20e1abdd7984ee8353a49a1d108ef299d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:21:03 -0600 Subject: [PATCH 0353/1145] Bump bleak from 1.1.1 to 2.0.0 (#12083) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6ae050b35b..df036eeccc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ pillow==11.3.0 cairosvg==2.8.2 freetype-py==2.5.1 jinja2==3.1.6 -bleak==1.1.1 +bleak==2.0.0 # esp-idf >= 5.0 requires this pyparsing >= 3.0 From fbe091f167850844e1aad34aee4906e17025aa00 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:09:22 -0500 Subject: [PATCH 0354/1145] [graph] Fix legend border (#12000) --- esphome/components/graph/graph.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index 88bb306408..e3b9119108 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -337,7 +337,7 @@ void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_of return; /// Plot border - if (this->border_) { + if (legend_->border_) { int w = legend_->width_; int h = legend_->height_; buff->horizontal_line(x_offset, y_offset, w, color); From 45b8c1e267b398eda2367d480f141dbf0d4d8695 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Nov 2025 07:59:16 -0600 Subject: [PATCH 0355/1145] [network] Fix IPAddress constructor causing comparison failures and garbage output (#12005) --- esphome/components/network/ip_address.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index 5e6b0dbd96..b9364a1f81 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -81,7 +81,12 @@ struct IPAddress { ip_addr_.type = IPADDR_TYPE_V6; } #endif /* LWIP_IPV6 */ - IPAddress(esp_ip4_addr_t *other_ip) { memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(esp_ip4_addr_t)); } + IPAddress(esp_ip4_addr_t *other_ip) { + memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(esp_ip4_addr_t)); +#if LWIP_IPV6 + ip_addr_.type = IPADDR_TYPE_V4; +#endif + } IPAddress(esp_ip_addr_t *other_ip) { #if LWIP_IPV6 memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip_addr_)); From 89ee37a2d58ff0b23fbb4eac5c1462ac80949a52 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 20 Nov 2025 10:58:21 -0500 Subject: [PATCH 0356/1145] [ltr501][ltr_als_ps] Rename enum to avoid collision with lwip defines (#12017) --- esphome/components/ltr501/ltr501.cpp | 10 +++++----- esphome/components/ltr501/ltr501.h | 4 ++-- esphome/components/ltr_als_ps/ltr_als_ps.cpp | 12 ++++++------ esphome/components/ltr_als_ps/ltr_als_ps.h | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/esphome/components/ltr501/ltr501.cpp b/esphome/components/ltr501/ltr501.cpp index be5a4ddccf..04de91e362 100644 --- a/esphome/components/ltr501/ltr501.cpp +++ b/esphome/components/ltr501/ltr501.cpp @@ -174,7 +174,7 @@ void LTRAlsPs501Component::loop() { break; case State::WAITING_FOR_DATA: - if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) { + if (this->is_als_data_ready_(this->als_readings_) == LtrDataAvail::LTR_DATA_OK) { tries = 0; ESP_LOGV(TAG, "Reading sensor data assuming gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain), get_itime_ms(this->als_readings_.integration_time)); @@ -379,18 +379,18 @@ void LTRAlsPs501Component::configure_integration_time_(IntegrationTime501 time) } } -DataAvail LTRAlsPs501Component::is_als_data_ready_(AlsReadings &data) { +LtrDataAvail LTRAlsPs501Component::is_als_data_ready_(AlsReadings &data) { AlsPsStatusRegister als_status{0}; als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get(); if (!als_status.als_new_data) - return DataAvail::NO_DATA; + return LtrDataAvail::LTR_NO_DATA; ESP_LOGV(TAG, "Data ready, reported gain is %.0fx", get_gain_coeff(als_status.gain)); if (data.gain != als_status.gain) { ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain)); - return DataAvail::BAD_DATA; + return LtrDataAvail::LTR_BAD_DATA; } data.gain = als_status.gain; - return DataAvail::DATA_OK; + return LtrDataAvail::LTR_DATA_OK; } void LTRAlsPs501Component::read_sensor_data_(AlsReadings &data) { diff --git a/esphome/components/ltr501/ltr501.h b/esphome/components/ltr501/ltr501.h index 849ff6bc23..02c025da30 100644 --- a/esphome/components/ltr501/ltr501.h +++ b/esphome/components/ltr501/ltr501.h @@ -11,7 +11,7 @@ namespace esphome { namespace ltr501 { -enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK }; +enum LtrDataAvail : uint8_t { LTR_NO_DATA, LTR_BAD_DATA, LTR_DATA_OK }; enum LtrType : uint8_t { LTR_TYPE_UNKNOWN = 0, @@ -106,7 +106,7 @@ class LTRAlsPs501Component : public PollingComponent, public i2c::I2CDevice { void configure_als_(); void configure_integration_time_(IntegrationTime501 time); void configure_gain_(AlsGain501 gain); - DataAvail is_als_data_ready_(AlsReadings &data); + LtrDataAvail is_als_data_ready_(AlsReadings &data); void read_sensor_data_(AlsReadings &data); bool are_adjustments_required_(AlsReadings &data); void apply_lux_calculation_(AlsReadings &data); diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.cpp b/esphome/components/ltr_als_ps/ltr_als_ps.cpp index c3ea5848c8..f9c1474c85 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.cpp +++ b/esphome/components/ltr_als_ps/ltr_als_ps.cpp @@ -165,7 +165,7 @@ void LTRAlsPsComponent::loop() { break; case State::WAITING_FOR_DATA: - if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) { + if (this->is_als_data_ready_(this->als_readings_) == LtrDataAvail::LTR_DATA_OK) { tries = 0; ESP_LOGV(TAG, "Reading sensor data having gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain), get_itime_ms(this->als_readings_.integration_time)); @@ -376,23 +376,23 @@ void LTRAlsPsComponent::configure_integration_time_(IntegrationTime time) { } } -DataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) { +LtrDataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) { AlsPsStatusRegister als_status{0}; als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get(); if (!als_status.als_new_data) - return DataAvail::NO_DATA; + return LtrDataAvail::LTR_NO_DATA; if (als_status.data_invalid) { ESP_LOGW(TAG, "Data available but not valid"); - return DataAvail::BAD_DATA; + return LtrDataAvail::LTR_BAD_DATA; } ESP_LOGV(TAG, "Data ready, reported gain is %.0f", get_gain_coeff(als_status.gain)); if (data.gain != als_status.gain) { ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain)); - return DataAvail::BAD_DATA; + return LtrDataAvail::LTR_BAD_DATA; } - return DataAvail::DATA_OK; + return LtrDataAvail::LTR_DATA_OK; } void LTRAlsPsComponent::read_sensor_data_(AlsReadings &data) { diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.h b/esphome/components/ltr_als_ps/ltr_als_ps.h index 2c768009ab..c6052300de 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.h +++ b/esphome/components/ltr_als_ps/ltr_als_ps.h @@ -11,7 +11,7 @@ namespace esphome { namespace ltr_als_ps { -enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK }; +enum LtrDataAvail : uint8_t { LTR_NO_DATA, LTR_BAD_DATA, LTR_DATA_OK }; enum LtrType : uint8_t { LTR_TYPE_UNKNOWN = 0, @@ -106,7 +106,7 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { void configure_als_(); void configure_integration_time_(IntegrationTime time); void configure_gain_(AlsGain gain); - DataAvail is_als_data_ready_(AlsReadings &data); + LtrDataAvail is_als_data_ready_(AlsReadings &data); void read_sensor_data_(AlsReadings &data); bool are_adjustments_required_(AlsReadings &data); void apply_lux_calculation_(AlsReadings &data); From 11ba6440d7124f8895e3e7fef900147f60b4c39e Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 20 Nov 2025 12:10:28 -0500 Subject: [PATCH 0357/1145] [cst816][packet_transport][udp][wake_on_lan] Fix error messages (#12019) --- .../cst816/touchscreen/cst816_touchscreen.cpp | 2 +- .../components/packet_transport/packet_transport.cpp | 2 +- esphome/components/udp/udp_component.cpp | 10 +++++----- esphome/components/wake_on_lan/wake_on_lan.cpp | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp index 0ba2d9df94..8ed9fa3f87 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp @@ -19,8 +19,8 @@ void CST816Touchscreen::continue_setup_() { case CST816T_CHIP_ID: break; default: - this->mark_failed(); this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_).c_str()); + this->mark_failed(); return; } this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION); diff --git a/esphome/components/packet_transport/packet_transport.cpp b/esphome/components/packet_transport/packet_transport.cpp index 8bde4ee505..857b40ca0e 100644 --- a/esphome/components/packet_transport/packet_transport.cpp +++ b/esphome/components/packet_transport/packet_transport.cpp @@ -195,8 +195,8 @@ static void add(std::vector &vec, const char *str) { void PacketTransport::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"); + this->mark_failed(); return; } this->resend_ping_key_ = this->ping_pong_enable_; diff --git a/esphome/components/udp/udp_component.cpp b/esphome/components/udp/udp_component.cpp index 8a9ce612b4..7714793e1c 100644 --- a/esphome/components/udp/udp_component.cpp +++ b/esphome/components/udp/udp_component.cpp @@ -21,8 +21,8 @@ void UDPComponent::setup() { if (this->should_broadcast_) { 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"); + this->mark_failed(); return; } int enable = 1; @@ -41,15 +41,15 @@ void UDPComponent::setup() { 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"); + this->mark_failed(); 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"); + this->mark_failed(); return; } int enable = 1; @@ -73,8 +73,8 @@ void UDPComponent::setup() { err = this->listen_socket_->setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq)); if (err < 0) { ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno); - this->mark_failed(); this->status_set_error("Failed to set IP_ADD_MEMBERSHIP"); + this->mark_failed(); return; } } @@ -82,8 +82,8 @@ void UDPComponent::setup() { 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"); + this->mark_failed(); return; } } diff --git a/esphome/components/wake_on_lan/wake_on_lan.cpp b/esphome/components/wake_on_lan/wake_on_lan.cpp index adf5a080e5..7993abd7e7 100644 --- a/esphome/components/wake_on_lan/wake_on_lan.cpp +++ b/esphome/components/wake_on_lan/wake_on_lan.cpp @@ -67,8 +67,8 @@ void WakeOnLanButton::setup() { #if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) 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"); + this->mark_failed(); return; } int enable = 1; From d698083ede17444ac1f2fe31d75a8ab2290732dd Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 21 Nov 2025 07:39:59 -0500 Subject: [PATCH 0358/1145] [jsn_sr04t] Fix model AJ_SR04M (#11992) --- esphome/components/jsn_sr04t/jsn_sr04t.cpp | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/esphome/components/jsn_sr04t/jsn_sr04t.cpp b/esphome/components/jsn_sr04t/jsn_sr04t.cpp index 077d4e58ea..84181dac48 100644 --- a/esphome/components/jsn_sr04t/jsn_sr04t.cpp +++ b/esphome/components/jsn_sr04t/jsn_sr04t.cpp @@ -10,7 +10,7 @@ namespace jsn_sr04t { static const char *const TAG = "jsn_sr04t.sensor"; void Jsnsr04tComponent::update() { - this->write_byte(0x55); + this->write_byte((this->model_ == AJ_SR04M) ? 0x01 : 0x55); ESP_LOGV(TAG, "Request read out from sensor"); } @@ -31,19 +31,10 @@ void Jsnsr04tComponent::loop() { } void Jsnsr04tComponent::check_buffer_() { - uint8_t checksum = 0; - switch (this->model_) { - case JSN_SR04T: - checksum = this->buffer_[0] + this->buffer_[1] + this->buffer_[2]; - break; - case AJ_SR04M: - checksum = this->buffer_[1] + this->buffer_[2]; - break; - } - + uint8_t checksum = this->buffer_[0] + this->buffer_[1] + this->buffer_[2]; if (this->buffer_[3] == checksum) { uint16_t distance = encode_uint16(this->buffer_[1], this->buffer_[2]); - if (distance > 250) { + if (distance > ((this->model_ == AJ_SR04M) ? 200 : 250)) { float meters = distance / 1000.0f; ESP_LOGV(TAG, "Distance from sensor: %umm, %.3fm", distance, meters); this->publish_state(meters); From f8efefffaa9023a12529197d03a38f1986535a49 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 21 Nov 2025 06:41:48 -0600 Subject: [PATCH 0359/1145] [cst816][http_request] Fix status_set_error() dangling pointer bugs (#12033) --- .../cst816/touchscreen/cst816_touchscreen.cpp | 3 ++- .../http_request/update/http_request_update.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp index 8ed9fa3f87..0560f1b475 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp @@ -19,7 +19,8 @@ void CST816Touchscreen::continue_setup_() { case CST816T_CHIP_ID: break; default: - this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_).c_str()); + ESP_LOGE(TAG, "Unknown chip ID: 0x%02X", this->chip_id_); + this->status_set_error("Unknown chip ID"); this->mark_failed(); return; } diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 06aa6da6a4..9dbf8d181a 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -49,18 +49,18 @@ void HttpRequestUpdate::update_task(void *params) { auto container = this_update->request_parent_->get(this_update->source_url_); if (container == nullptr || container->status_code != HTTP_STATUS_OK) { - std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str()); + ESP_LOGE(TAG, "Failed to fetch manifest from %s", this_update->source_url_.c_str()); // Defer to main loop to avoid race condition on component_state_ read-modify-write - this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); }); + this_update->defer([this_update]() { this_update->status_set_error("Failed to fetch manifest"); }); UPDATE_RETURN; } RAMAllocator allocator; uint8_t *data = allocator.allocate(container->content_length); if (data == nullptr) { - std::string msg = str_sprintf("Failed to allocate %zu bytes for manifest", container->content_length); + ESP_LOGE(TAG, "Failed to allocate %zu bytes for manifest", container->content_length); // Defer to main loop to avoid race condition on component_state_ read-modify-write - this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); }); + this_update->defer([this_update]() { this_update->status_set_error("Failed to allocate memory for manifest"); }); container->end(); UPDATE_RETURN; } @@ -121,9 +121,9 @@ void HttpRequestUpdate::update_task(void *params) { } if (!valid) { - std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str()); + ESP_LOGE(TAG, "Failed to parse JSON from %s", this_update->source_url_.c_str()); // Defer to main loop to avoid race condition on component_state_ read-modify-write - this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); }); + this_update->defer([this_update]() { this_update->status_set_error("Failed to parse manifest JSON"); }); UPDATE_RETURN; } From f31f023c891f906a6762a69d50978433859a1b9e Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 23 Nov 2025 23:31:14 -0500 Subject: [PATCH 0360/1145] [esp32] Fix C2 builds (#12050) --- esphome/components/esp32/__init__.py | 6 ++++++ esphome/components/esp32/pre_build.py.script | 9 +++++++++ 2 files changed, 15 insertions(+) create mode 100644 esphome/components/esp32/pre_build.py.script diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 6f577d2926..59c6029334 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -883,6 +883,12 @@ async def to_code(config): CORE.relative_internal_path(".espressif") ) + add_extra_script( + "pre", + "pre_build.py", + Path(__file__).parent / "pre_build.py.script", + ) + add_extra_script( "post", "post_build.py", diff --git a/esphome/components/esp32/pre_build.py.script b/esphome/components/esp32/pre_build.py.script new file mode 100644 index 0000000000..af12275a0b --- /dev/null +++ b/esphome/components/esp32/pre_build.py.script @@ -0,0 +1,9 @@ +Import("env") # noqa: F821 + +# Remove custom_sdkconfig from the board config as it causes +# pioarduino to enable some strange hybrid build mode that breaks IDF +board = env.BoardConfig() +if "espidf.custom_sdkconfig" in board: + del board._manifest["espidf"]["custom_sdkconfig"] + if not board._manifest["espidf"]: + del board._manifest["espidf"] From 83525b7a926c0c51a50d4fe18259cdd6f5ca52f6 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:10:24 -0500 Subject: [PATCH 0361/1145] [core] Add support for passing yaml files to clean-all (#12039) --- esphome/__main__.py | 2 +- esphome/writer.py | 8 +++++++- tests/unit_tests/test_writer.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index b0c081a34f..f8fb678cb2 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -1319,7 +1319,7 @@ def parse_args(argv): "clean-all", help="Clean all build and platform files." ) parser_clean_all.add_argument( - "configuration", help="Your YAML configuration directory.", nargs="*" + "configuration", help="Your YAML file or configuration directory.", nargs="*" ) parser_dashboard = subparsers.add_parser( diff --git a/esphome/writer.py b/esphome/writer.py index 8eee445cf1..1e49a2c961 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -340,7 +340,13 @@ def clean_build(): def clean_all(configuration: list[str]): import shutil - data_dirs = [Path(dir) / ".esphome" for dir in configuration] + data_dirs = [] + for config in configuration: + item = Path(config) + if item.is_file() and item.suffix in (".yaml", ".yml"): + data_dirs.append(item.parent / ".esphome") + else: + data_dirs.append(item / ".esphome") if is_ha_addon(): data_dirs.append(Path("/data")) if "ESPHOME_DATA_DIR" in os.environ: diff --git a/tests/unit_tests/test_writer.py b/tests/unit_tests/test_writer.py index a4490fbbc0..a2a358f4d3 100644 --- a/tests/unit_tests/test_writer.py +++ b/tests/unit_tests/test_writer.py @@ -737,6 +737,37 @@ def test_write_cpp_with_duplicate_markers( write_cpp("// New code") +@patch("esphome.writer.CORE") +def test_clean_all_with_yaml_file( + mock_core: MagicMock, + tmp_path: Path, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test clean_all with a .yaml file uses parent directory.""" + # Create config directory with yaml file + config_dir = tmp_path / "config" + config_dir.mkdir() + yaml_file = config_dir / "test.yaml" + yaml_file.write_text("esphome:\n name: test\n") + + build_dir = config_dir / ".esphome" + build_dir.mkdir() + (build_dir / "dummy.txt").write_text("x") + + from esphome.writer import clean_all + + with caplog.at_level("INFO"): + clean_all([str(yaml_file)]) + + # Verify .esphome directory still exists but contents cleaned + assert build_dir.exists() + assert not (build_dir / "dummy.txt").exists() + + # Verify logging mentions the build dir + assert "Cleaning" in caplog.text + assert str(build_dir) in caplog.text + + @patch("esphome.writer.CORE") def test_clean_all( mock_core: MagicMock, From 3a7a0c66ab500e2b0ce618967e2f13064509009f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 10:41:24 -0600 Subject: [PATCH 0362/1145] [script][wait_until] Fix FIFO ordering and reentrancy bugs (#12049) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/script/script.h | 20 +-- esphome/core/base_automation.h | 9 +- .../fixtures/script_delay_with_params.yaml | 131 ++++++++++++++++++ .../fixtures/wait_until_fifo_ordering.yaml | 82 +++++++++++ tests/integration/test_script_delay_params.py | 121 ++++++++++++++++ tests/integration/test_wait_until_ordering.py | 90 ++++++++++++ 6 files changed, 441 insertions(+), 12 deletions(-) create mode 100644 tests/integration/fixtures/script_delay_with_params.yaml create mode 100644 tests/integration/fixtures/wait_until_fifo_ordering.yaml create mode 100644 tests/integration/test_script_delay_params.py create mode 100644 tests/integration/test_wait_until_ordering.py diff --git a/esphome/components/script/script.h b/esphome/components/script/script.h index 51cece01e4..d60ed657f7 100644 --- a/esphome/components/script/script.h +++ b/esphome/components/script/script.h @@ -1,8 +1,8 @@ #pragma once +#include #include #include -#include #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -290,10 +290,10 @@ template class ScriptWaitAction : public Action, } // Store parameters for later execution - this->param_queue_.emplace_front(x...); - // Enable loop now that we have work to do + this->param_queue_.emplace_back(x...); + // Enable loop now that we have work to do - don't call loop() synchronously! + // Let the event loop call it to avoid reentrancy issues this->enable_loop(); - this->loop(); } void loop() override { @@ -303,13 +303,17 @@ template class ScriptWaitAction : public Action, if (this->script_->is_running()) return; - while (!this->param_queue_.empty()) { + // Only process ONE queued item per loop iteration + // Processing all items in a while loop causes infinite loops because + // play_next_() can trigger more items to be queued + if (!this->param_queue_.empty()) { auto ¶ms = this->param_queue_.front(); this->play_next_tuple_(params, typename gens::type()); this->param_queue_.pop_front(); + } else { + // Queue is now empty - disable loop until next play_complex + this->disable_loop(); } - // Queue is now empty - disable loop until next play_complex - this->disable_loop(); } void play(const Ts &...x) override { /* ignore - see play_complex */ @@ -326,7 +330,7 @@ template class ScriptWaitAction : public Action, } C *script_; - std::forward_list> param_queue_; + std::list> param_queue_; }; } // namespace script diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index a5e6139182..e46e5d92a9 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -9,8 +9,8 @@ #include "esphome/core/application.h" #include "esphome/core/helpers.h" +#include #include -#include namespace esphome { @@ -433,9 +433,10 @@ template class WaitUntilAction : public Action, public Co // Store for later processing auto now = millis(); auto timeout = this->timeout_value_.optional_value(x...); - this->var_queue_.emplace_front(now, timeout, std::make_tuple(x...)); + this->var_queue_.emplace_back(now, timeout, std::make_tuple(x...)); - // Do immediate check with fresh timestamp + // Do immediate check with fresh timestamp - don't call loop() synchronously! + // Let the event loop call it to avoid reentrancy issues if (this->process_queue_(now)) { // Only enable loop if we still have pending items this->enable_loop(); @@ -487,7 +488,7 @@ template class WaitUntilAction : public Action, public Co } Condition *condition_; - std::forward_list, std::tuple>> var_queue_{}; + std::list, std::tuple>> var_queue_{}; }; template class UpdateComponentAction : public Action { diff --git a/tests/integration/fixtures/script_delay_with_params.yaml b/tests/integration/fixtures/script_delay_with_params.yaml new file mode 100644 index 0000000000..2a0f16d9fe --- /dev/null +++ b/tests/integration/fixtures/script_delay_with_params.yaml @@ -0,0 +1,131 @@ +esphome: + name: test-script-delay-params + +host: + +api: + actions: + # Test case from issue #12044: parent script with repeat calling child with delay + - action: test_repeat_with_delay + then: + - logger.log: "=== TEST: Repeat loop calling script with delay and parameters ===" + - script.execute: father_script + + # Test case from issue #12043: script.wait with delayed child script + - action: test_script_wait + then: + - logger.log: "=== TEST: script.wait with delayed child script ===" + - script.execute: show_start_page + - script.wait: show_start_page + - logger.log: "After wait: script completed successfully" + + # Test: Delay with different parameter types + - action: test_delay_param_types + then: + - logger.log: "=== TEST: Delay with various parameter types ===" + - script.execute: + id: delay_with_int + val: 42 + - delay: 50ms + - script.execute: + id: delay_with_string + msg: "test message" + - delay: 50ms + - script.execute: + id: delay_with_float + num: 3.14 + +logger: + level: DEBUG + +script: + # Reproduces issue #12044: child script with conditional delay + - id: son_script + mode: single + parameters: + iteration: int + then: + - logger.log: + format: "Son script started with iteration %d" + args: ['iteration'] + - if: + condition: + lambda: 'return iteration >= 5;' + then: + - logger.log: + format: "Son script delaying for iteration %d" + args: ['iteration'] + - delay: 100ms + - logger.log: + format: "Son script finished with iteration %d" + args: ['iteration'] + + # Reproduces issue #12044: parent script with repeat loop + - id: father_script + mode: single + then: + - repeat: + count: 10 + then: + - logger.log: + format: "Father iteration %d: calling son" + args: ['iteration'] + - script.execute: + id: son_script + iteration: !lambda 'return iteration;' + - script.wait: son_script + - logger.log: + format: "Father iteration %d: son finished, wait returned" + args: ['iteration'] + + # Reproduces issue #12043: script.wait hangs + - id: show_start_page + mode: single + then: + - logger.log: "Start page: beginning" + - delay: 100ms + - logger.log: "Start page: after delay" + - delay: 100ms + - logger.log: "Start page: completed" + + # Test delay with int parameter + - id: delay_with_int + mode: single + parameters: + val: int + then: + - logger.log: + format: "Int test: before delay, val=%d" + args: ['val'] + - delay: 50ms + - logger.log: + format: "Int test: after delay, val=%d" + args: ['val'] + + # Test delay with string parameter + - id: delay_with_string + mode: single + parameters: + msg: string + then: + - logger.log: + format: "String test: before delay, msg=%s" + args: ['msg.c_str()'] + - delay: 50ms + - logger.log: + format: "String test: after delay, msg=%s" + args: ['msg.c_str()'] + + # Test delay with float parameter + - id: delay_with_float + mode: single + parameters: + num: float + then: + - logger.log: + format: "Float test: before delay, num=%.2f" + args: ['num'] + - delay: 50ms + - logger.log: + format: "Float test: after delay, num=%.2f" + args: ['num'] diff --git a/tests/integration/fixtures/wait_until_fifo_ordering.yaml b/tests/integration/fixtures/wait_until_fifo_ordering.yaml new file mode 100644 index 0000000000..5dd60c8755 --- /dev/null +++ b/tests/integration/fixtures/wait_until_fifo_ordering.yaml @@ -0,0 +1,82 @@ +esphome: + name: test-wait-until-ordering + +host: + +api: + actions: + - action: test_wait_until_fifo + then: + - logger.log: "=== TEST: wait_until should execute in FIFO order ===" + - globals.set: + id: gate_open + value: 'false' + - delay: 100ms + # Start multiple parallel executions of coordinator script + # Each will call the shared waiter script, queueing in same wait_until + - script.execute: coordinator_0 + - script.execute: coordinator_1 + - script.execute: coordinator_2 + - script.execute: coordinator_3 + - script.execute: coordinator_4 + # Give scripts time to reach wait_until and queue + - delay: 200ms + - logger.log: "Opening gate - all wait_until should complete now" + - globals.set: + id: gate_open + value: 'true' + - delay: 500ms + - logger.log: "Test complete" + +globals: + - id: gate_open + type: bool + initial_value: 'false' + +script: + # Shared waiter with single wait_until action (all coordinators call this) + - id: waiter + mode: parallel + parameters: + iter: int + then: + - lambda: 'ESP_LOGD("main", "Queueing iteration %d", iter);' + - wait_until: + condition: + lambda: 'return id(gate_open);' + timeout: 5s + - lambda: 'ESP_LOGD("main", "Completed iteration %d", iter);' + + # Coordinator scripts - each calls shared waiter with different iteration number + - id: coordinator_0 + then: + - script.execute: + id: waiter + iter: 0 + + - id: coordinator_1 + then: + - script.execute: + id: waiter + iter: 1 + + - id: coordinator_2 + then: + - script.execute: + id: waiter + iter: 2 + + - id: coordinator_3 + then: + - script.execute: + id: waiter + iter: 3 + + - id: coordinator_4 + then: + - script.execute: + id: waiter + iter: 4 + +logger: + level: DEBUG diff --git a/tests/integration/test_script_delay_params.py b/tests/integration/test_script_delay_params.py new file mode 100644 index 0000000000..1b5d70863b --- /dev/null +++ b/tests/integration/test_script_delay_params.py @@ -0,0 +1,121 @@ +"""Integration test for script.wait FIFO ordering (issues #12043, #12044). + +This test verifies that ScriptWaitAction processes queued items in FIFO order. + +PR #7972 introduced bugs in ScriptWaitAction: +- Used emplace_front() causing LIFO ordering instead of FIFO +- Called loop() synchronously causing reentrancy issues +- Used while loop processing entire queue causing infinite loops + +These bugs manifested as: +- Scripts becoming "zombies" (stuck in running state) +- script.wait hanging forever +- Incorrect execution order +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_script_delay_with_params( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that script.wait processes queued items in FIFO order. + + This reproduces issues #12043 and #12044 where scripts would hang or become + zombies due to LIFO ordering bugs in ScriptWaitAction from PR #7972. + """ + test_complete = asyncio.Event() + + # Patterns to match in logs + father_calling_pattern = re.compile(r"Father iteration (\d+): calling son") + son_started_pattern = re.compile(r"Son script started with iteration (\d+)") + son_delaying_pattern = re.compile(r"Son script delaying for iteration (\d+)") + son_finished_pattern = re.compile(r"Son script finished with iteration (\d+)") + father_wait_returned_pattern = re.compile( + r"Father iteration (\d+): son finished, wait returned" + ) + + # Track which iterations completed + father_calling = set() + son_started = set() + son_delaying = set() + son_finished = set() + wait_returned = set() + + def check_output(line: str) -> None: + """Check log output for expected messages.""" + if test_complete.is_set(): + return + + if mo := father_calling_pattern.search(line): + father_calling.add(int(mo.group(1))) + elif mo := son_started_pattern.search(line): + son_started.add(int(mo.group(1))) + elif mo := son_delaying_pattern.search(line): + son_delaying.add(int(mo.group(1))) + elif mo := son_finished_pattern.search(line): + son_finished.add(int(mo.group(1))) + elif mo := father_wait_returned_pattern.search(line): + iteration = int(mo.group(1)) + wait_returned.add(iteration) + # Test completes when iteration 9 finishes + if iteration == 9: + test_complete.set() + + # Run with log monitoring + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "test-script-delay-params" + + # Get services + _, services = await client.list_entities_services() + test_service = next( + (s for s in services if s.name == "test_repeat_with_delay"), None + ) + assert test_service is not None, "test_repeat_with_delay service not found" + + # Execute the test + client.execute_service(test_service, {}) + + # Wait for test to complete (10 iterations * ~100ms each + margin) + try: + await asyncio.wait_for(test_complete.wait(), timeout=5.0) + except TimeoutError: + pytest.fail( + f"Test timed out. Completed iterations: {sorted(wait_returned)}. " + f"This likely indicates the script became a zombie (issue #12044)." + ) + + # Verify all 10 iterations completed successfully + expected_iterations = set(range(10)) + assert father_calling == expected_iterations, "Not all iterations started" + assert son_started == expected_iterations, ( + "Son script not started for all iterations" + ) + assert son_finished == expected_iterations, ( + "Son script not finished for all iterations" + ) + assert wait_returned == expected_iterations, ( + "script.wait did not return for all iterations" + ) + + # Verify delays were triggered for iterations >= 5 + expected_delays = set(range(5, 10)) + assert son_delaying == expected_delays, ( + "Delays not triggered for iterations >= 5" + ) diff --git a/tests/integration/test_wait_until_ordering.py b/tests/integration/test_wait_until_ordering.py new file mode 100644 index 0000000000..7c39913e5a --- /dev/null +++ b/tests/integration/test_wait_until_ordering.py @@ -0,0 +1,90 @@ +"""Integration test for wait_until FIFO ordering. + +This test verifies that when multiple wait_until actions are queued, +they execute in FIFO (First In First Out) order, not LIFO. + +PR #7972 introduced a bug where emplace_front() was used, causing +LIFO ordering which is incorrect. +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_wait_until_fifo_ordering( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that wait_until executes queued items in FIFO order. + + With the bug (using emplace_front), the order would be 4,3,2,1,0 (LIFO). + With the fix (using emplace_back), the order should be 0,1,2,3,4 (FIFO). + """ + test_complete = asyncio.Event() + + # Track completion order + completed_order = [] + + # Patterns to match + queuing_pattern = re.compile(r"Queueing iteration (\d+)") + completed_pattern = re.compile(r"Completed iteration (\d+)") + + def check_output(line: str) -> None: + """Check log output for completion order.""" + if test_complete.is_set(): + return + + if mo := queuing_pattern.search(line): + iteration = int(mo.group(1)) + + elif mo := completed_pattern.search(line): + iteration = int(mo.group(1)) + completed_order.append(iteration) + + # Test completes when all 5 have completed + if len(completed_order) == 5: + test_complete.set() + + # Run with log monitoring + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "test-wait-until-ordering" + + # Get services + _, services = await client.list_entities_services() + test_service = next( + (s for s in services if s.name == "test_wait_until_fifo"), None + ) + assert test_service is not None, "test_wait_until_fifo service not found" + + # Execute the test + client.execute_service(test_service, {}) + + # Wait for test to complete + try: + await asyncio.wait_for(test_complete.wait(), timeout=5.0) + except TimeoutError: + pytest.fail( + f"Test timed out. Completed order: {completed_order}. " + f"Expected 5 completions but got {len(completed_order)}." + ) + + # Verify FIFO order + expected_order = [0, 1, 2, 3, 4] + assert completed_order == expected_order, ( + f"Unexpected order: {completed_order}. " + f"Expected FIFO order: {expected_order}" + ) From 50d08a2ebae6a1eb125c92d545138277bbe12d1d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 11:02:24 -0600 Subject: [PATCH 0363/1145] [esp_ldo,mipi_dsi,mipi_rgb] Fix dangling pointer bugs in mark_failed() (#12077) --- esphome/components/esp_ldo/esp_ldo.cpp | 4 ++-- esphome/components/mipi_dsi/mipi_dsi.cpp | 6 ++++++ esphome/components/mipi_dsi/mipi_dsi.h | 5 +---- esphome/components/mipi_rgb/mipi_rgb.cpp | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/esphome/components/esp_ldo/esp_ldo.cpp b/esphome/components/esp_ldo/esp_ldo.cpp index eb04670d7e..9ea7000b70 100644 --- a/esphome/components/esp_ldo/esp_ldo.cpp +++ b/esphome/components/esp_ldo/esp_ldo.cpp @@ -14,8 +14,8 @@ void EspLdo::setup() { config.flags.adjustable = this->adjustable_; auto err = esp_ldo_acquire_channel(&config, &this->handle_); if (err != ESP_OK) { - auto msg = str_sprintf("Failed to acquire LDO channel %d with voltage %fV", this->channel_, this->voltage_); - this->mark_failed(msg.c_str()); + ESP_LOGE(TAG, "Failed to acquire LDO channel %d with voltage %fV", this->channel_, this->voltage_); + this->mark_failed("Failed to acquire LDO channel"); } else { ESP_LOGD(TAG, "Acquired LDO channel %d with voltage %fV", this->channel_, this->voltage_); } diff --git a/esphome/components/mipi_dsi/mipi_dsi.cpp b/esphome/components/mipi_dsi/mipi_dsi.cpp index fbe251de41..7305435e4b 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.cpp +++ b/esphome/components/mipi_dsi/mipi_dsi.cpp @@ -11,6 +11,12 @@ static bool notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel xSemaphoreGiveFromISR(sem, &need_yield); return (need_yield == pdTRUE); } + +void MIPI_DSI::smark_failed(const char *message, esp_err_t err) { + ESP_LOGE(TAG, "%s: %s", message, esp_err_to_name(err)); + this->mark_failed(message); +} + void MIPI_DSI::setup() { ESP_LOGCONFIG(TAG, "Running Setup"); diff --git a/esphome/components/mipi_dsi/mipi_dsi.h b/esphome/components/mipi_dsi/mipi_dsi.h index ce8a2a2236..98ee092ed1 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.h +++ b/esphome/components/mipi_dsi/mipi_dsi.h @@ -62,10 +62,7 @@ class MIPI_DSI : public display::Display { void set_lanes(uint8_t lanes) { this->lanes_ = lanes; } void set_madctl(uint8_t madctl) { this->madctl_ = madctl; } - void smark_failed(const char *message, esp_err_t err) { - auto str = str_sprintf("Setup failed: %s: %s", message, esp_err_to_name(err)); - this->mark_failed(str.c_str()); - } + void smark_failed(const char *message, esp_err_t err); void update() override; diff --git a/esphome/components/mipi_rgb/mipi_rgb.cpp b/esphome/components/mipi_rgb/mipi_rgb.cpp index 080fb08c09..4c687724cf 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.cpp +++ b/esphome/components/mipi_rgb/mipi_rgb.cpp @@ -164,8 +164,8 @@ void MipiRgb::common_setup_() { if (err == ESP_OK) err = esp_lcd_panel_init(this->handle_); if (err != ESP_OK) { - auto msg = str_sprintf("lcd setup failed: %s", esp_err_to_name(err)); - this->mark_failed(msg.c_str()); + ESP_LOGE(TAG, "lcd setup failed: %s", esp_err_to_name(err)); + this->mark_failed("lcd setup failed"); } ESP_LOGCONFIG(TAG, "MipiRgb setup complete"); } From 25bcd0ea25b979ffc064bd64a473fe900fd58c10 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:15:30 +1300 Subject: [PATCH 0364/1145] [online_image] Fix some large PNGs causing watchdog timeout (#12025) Co-authored-by: guillempages --- esphome/components/online_image/png_image.cpp | 9 +++++++++ esphome/components/online_image/png_image.h | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/esphome/components/online_image/png_image.cpp b/esphome/components/online_image/png_image.cpp index 2038d09ed0..ce9d3bdc91 100644 --- a/esphome/components/online_image/png_image.cpp +++ b/esphome/components/online_image/png_image.cpp @@ -2,6 +2,7 @@ #ifdef USE_ONLINE_IMAGE_PNG_SUPPORT #include "esphome/components/display/display_buffer.h" +#include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -38,6 +39,14 @@ static void draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, ui PngDecoder *decoder = (PngDecoder *) pngle_get_user_data(pngle); Color color(rgba[0], rgba[1], rgba[2], rgba[3]); decoder->draw(x, y, w, h, color); + + // Feed watchdog periodically to avoid triggering during long decode operations. + // Feed every 1024 pixels to balance efficiency and responsiveness. + uint32_t pixels = w * h; + decoder->increment_pixels_decoded(pixels); + if ((decoder->get_pixels_decoded() % 1024) < pixels) { + App.feed_wdt(); + } } PngDecoder::PngDecoder(OnlineImage *image) : ImageDecoder(image) { diff --git a/esphome/components/online_image/png_image.h b/esphome/components/online_image/png_image.h index 46519f8ef4..40e85dde33 100644 --- a/esphome/components/online_image/png_image.h +++ b/esphome/components/online_image/png_image.h @@ -25,9 +25,13 @@ class PngDecoder : public ImageDecoder { int prepare(size_t download_size) override; int HOT decode(uint8_t *buffer, size_t size) override; + void increment_pixels_decoded(uint32_t count) { this->pixels_decoded_ += count; } + uint32_t get_pixels_decoded() const { return this->pixels_decoded_; } + protected: RAMAllocator allocator_; pngle_t *pngle_; + uint32_t pixels_decoded_{0}; }; } // namespace online_image From 9186144dcdb9a21bc02012ad8de44ce67d8ec0ab Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 24 Nov 2025 16:24:38 -0500 Subject: [PATCH 0365/1145] Bump version to 2025.11.1 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 1448fd010d..a2b6efcfae 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.11.0 +PROJECT_NUMBER = 2025.11.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 3505ad169b..f4ddd01c09 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.11.0" +__version__ = "2025.11.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 88b898458b6a71a50ff63fed3aae2658e16a19a0 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 24 Nov 2025 15:25:49 -0600 Subject: [PATCH 0366/1145] [bluetooth_proxy] Fix crash due to null pointer (#12084) Co-authored-by: J. Nick Koston Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- esphome/components/bluetooth_proxy/bluetooth_proxy.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 4de541fac2..4363c508ec 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -132,7 +132,11 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ void get_bluetooth_mac_address_pretty(std::span output) { const uint8_t *mac = esp_bt_dev_get_address(); - format_mac_addr_upper(mac, output.data()); + if (mac != nullptr) { + format_mac_addr_upper(mac, output.data()); + } else { + output[0] = '\0'; + } } protected: From 7f1a9a611f7cb8d5d8d17b7b096eeb5595cc8957 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 02:09:02 +0000 Subject: [PATCH 0367/1145] Bump aioesphomeapi from 42.7.0 to 42.8.0 (#12092) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index df036eeccc..a5c919e95f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==42.7.0 +aioesphomeapi==42.8.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.16 # dashboard_import From 2bc8a4a77980493101aa66f089d9f213d8f5213b Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 24 Nov 2025 20:23:10 -0600 Subject: [PATCH 0368/1145] [wifi_info] Use callbacks instead of polling (#10748) Co-authored-by: J. Nick Koston --- esphome/components/wifi/__init__.py | 14 ++ esphome/components/wifi/automation.h | 116 +++++++++++++++ esphome/components/wifi/wifi_component.cpp | 6 +- esphome/components/wifi/wifi_component.h | 138 ++++-------------- .../wifi/wifi_component_esp8266.cpp | 22 ++- .../wifi/wifi_component_esp_idf.cpp | 22 ++- .../wifi/wifi_component_libretiny.cpp | 23 ++- .../components/wifi/wifi_component_pico_w.cpp | 61 +++++++- esphome/components/wifi_info/text_sensor.py | 37 +++-- .../wifi_info/wifi_info_text_sensor.cpp | 119 ++++++++++++++- .../wifi_info/wifi_info_text_sensor.h | 102 +++---------- esphome/core/defines.h | 1 + 12 files changed, 417 insertions(+), 244 deletions(-) create mode 100644 esphome/components/wifi/automation.h diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 8a5e5329f1..31d9ca0f70 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -608,6 +608,7 @@ async def wifi_disable_to_code(config, action_id, template_arg, args): KEEP_SCAN_RESULTS_KEY = "wifi_keep_scan_results" RUNTIME_POWER_SAVE_KEY = "wifi_runtime_power_save" +WIFI_CALLBACKS_KEY = "wifi_callbacks" def request_wifi_scan_results(): @@ -633,6 +634,17 @@ def enable_runtime_power_save_control(): CORE.data[RUNTIME_POWER_SAVE_KEY] = True +def request_wifi_callbacks() -> None: + """Request that WiFi callbacks be compiled in. + + Components that need to be notified about WiFi state changes (IP address changes, + scan results, connection state) should call this function during their code generation. + This enables the add_on_ip_state_callback(), add_on_wifi_scan_state_callback(), + and add_on_wifi_connect_state_callback() APIs. + """ + CORE.data[WIFI_CALLBACKS_KEY] = True + + @coroutine_with_priority(CoroPriority.FINAL) async def final_step(): """Final code generation step to configure optional WiFi features.""" @@ -642,6 +654,8 @@ async def final_step(): ) if CORE.data.get(RUNTIME_POWER_SAVE_KEY, False): cg.add_define("USE_WIFI_RUNTIME_POWER_SAVE") + if CORE.data.get(WIFI_CALLBACKS_KEY, False): + cg.add_define("USE_WIFI_CALLBACKS") @automation.register_action( diff --git a/esphome/components/wifi/automation.h b/esphome/components/wifi/automation.h new file mode 100644 index 0000000000..7997baff65 --- /dev/null +++ b/esphome/components/wifi/automation.h @@ -0,0 +1,116 @@ +#pragma once + +#include "esphome/core/defines.h" +#ifdef USE_WIFI +#include "wifi_component.h" + +namespace esphome::wifi { + +template class WiFiConnectedCondition : public Condition { + public: + bool check(const Ts &...x) override { return global_wifi_component->is_connected(); } +}; + +template class WiFiEnabledCondition : public Condition { + public: + bool check(const Ts &...x) override { return !global_wifi_component->is_disabled(); } +}; + +template class WiFiAPActiveCondition : public Condition { + public: + bool check(const Ts &...x) override { return global_wifi_component->is_ap_active(); } +}; + +template class WiFiEnableAction : public Action { + public: + void play(const Ts &...x) override { global_wifi_component->enable(); } +}; + +template class WiFiDisableAction : public Action { + public: + void play(const Ts &...x) override { global_wifi_component->disable(); } +}; + +template class WiFiConfigureAction : public Action, public Component { + public: + TEMPLATABLE_VALUE(std::string, ssid) + TEMPLATABLE_VALUE(std::string, password) + TEMPLATABLE_VALUE(bool, save) + TEMPLATABLE_VALUE(uint32_t, connection_timeout) + + void play(const Ts &...x) override { + auto ssid = this->ssid_.value(x...); + auto password = this->password_.value(x...); + // Avoid multiple calls + if (this->connecting_) + return; + // If already connected to the same AP, do nothing + if (global_wifi_component->wifi_ssid() == ssid) { + // Callback to notify the user that the connection was successful + this->connect_trigger_->trigger(); + return; + } + // Create a new WiFiAP object with the new SSID and password + this->new_sta_.set_ssid(ssid); + this->new_sta_.set_password(password); + // Save the current STA + this->old_sta_ = global_wifi_component->get_sta(); + // Disable WiFi + global_wifi_component->disable(); + // Set the state to connecting + this->connecting_ = true; + // Store the new STA so once the WiFi is enabled, it will connect to it + // This is necessary because the WiFiComponent will raise an error and fallback to the saved STA + // if trying to connect to a new STA while already connected to another one + if (this->save_.value(x...)) { + global_wifi_component->save_wifi_sta(new_sta_.get_ssid(), new_sta_.get_password()); + } else { + global_wifi_component->set_sta(new_sta_); + } + // Enable WiFi + global_wifi_component->enable(); + // Set timeout for the connection + this->set_timeout("wifi-connect-timeout", this->connection_timeout_.value(x...), [this, x...]() { + // If the timeout is reached, stop connecting and revert to the old AP + global_wifi_component->disable(); + global_wifi_component->save_wifi_sta(old_sta_.get_ssid(), old_sta_.get_password()); + global_wifi_component->enable(); + // Start a timeout for the fallback if the connection to the old AP fails + this->set_timeout("wifi-fallback-timeout", this->connection_timeout_.value(x...), [this]() { + this->connecting_ = false; + this->error_trigger_->trigger(); + }); + }); + } + + Trigger<> *get_connect_trigger() const { return this->connect_trigger_; } + Trigger<> *get_error_trigger() const { return this->error_trigger_; } + + void loop() override { + if (!this->connecting_) + return; + if (global_wifi_component->is_connected()) { + // The WiFi is connected, stop the timeout and reset the connecting flag + this->cancel_timeout("wifi-connect-timeout"); + this->cancel_timeout("wifi-fallback-timeout"); + this->connecting_ = false; + if (global_wifi_component->wifi_ssid() == this->new_sta_.get_ssid()) { + // Callback to notify the user that the connection was successful + this->connect_trigger_->trigger(); + } else { + // Callback to notify the user that the connection failed + this->error_trigger_->trigger(); + } + } + } + + protected: + bool connecting_{false}; + WiFiAP new_sta_; + WiFiAP old_sta_; + Trigger<> *connect_trigger_{new Trigger<>()}; + Trigger<> *error_trigger_{new Trigger<>()}; +}; + +} // namespace esphome::wifi +#endif diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 41931a7785..d53de83bd3 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -37,8 +37,7 @@ #include "esphome/components/esp32_improv/esp32_improv_component.h" #endif -namespace esphome { -namespace wifi { +namespace esphome::wifi { static const char *const TAG = "wifi"; @@ -1813,6 +1812,5 @@ bool WiFiScanResult::operator==(const WiFiScanResult &rhs) const { return this-> WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -} // namespace wifi -} // namespace esphome +} // namespace esphome::wifi #endif diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 0dac80ad21..b6b956a12d 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -54,8 +54,7 @@ extern "C" { #include #endif -namespace esphome { -namespace wifi { +namespace esphome::wifi { /// Sentinel value for RSSI when WiFi is not connected static constexpr int8_t WIFI_RSSI_DISCONNECTED = -127; @@ -370,6 +369,27 @@ class WiFiComponent : public Component { int32_t get_wifi_channel(); +#ifdef USE_WIFI_CALLBACKS + /// Add a callback that will be called on configuration changes (IP change, SSID change, etc.) + /// @param callback The callback to be called; template arguments are: + /// - IP addresses + /// - DNS address 1 + /// - DNS address 2 + void add_on_ip_state_callback( + std::function &&callback) { + this->ip_state_callback_.add(std::move(callback)); + } + /// - Wi-Fi scan results + void add_on_wifi_scan_state_callback(std::function &)> &&callback) { + this->wifi_scan_state_callback_.add(std::move(callback)); + } + /// - Wi-Fi SSID + /// - Wi-Fi BSSID + void add_on_wifi_connect_state_callback(std::function &&callback) { + this->wifi_connect_state_callback_.add(std::move(callback)); + } +#endif // USE_WIFI_CALLBACKS + #ifdef USE_WIFI_RUNTIME_POWER_SAVE /** Request high-performance mode (no power saving) for improved WiFi latency. * @@ -526,6 +546,11 @@ class WiFiComponent : public Component { WiFiAP ap_; #endif optional output_power_; +#ifdef USE_WIFI_CALLBACKS + CallbackManager ip_state_callback_; + CallbackManager &)> wifi_scan_state_callback_; + CallbackManager wifi_connect_state_callback_; +#endif // USE_WIFI_CALLBACKS ESPPreferenceObject pref_; #ifdef USE_WIFI_FAST_CONNECT ESPPreferenceObject fast_connect_pref_; @@ -590,112 +615,5 @@ class WiFiComponent : public Component { extern WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -template class WiFiConnectedCondition : public Condition { - public: - bool check(const Ts &...x) override { return global_wifi_component->is_connected(); } -}; - -template class WiFiEnabledCondition : public Condition { - public: - bool check(const Ts &...x) override { return !global_wifi_component->is_disabled(); } -}; - -template class WiFiAPActiveCondition : public Condition { - public: - bool check(const Ts &...x) override { return global_wifi_component->is_ap_active(); } -}; - -template class WiFiEnableAction : public Action { - public: - void play(const Ts &...x) override { global_wifi_component->enable(); } -}; - -template class WiFiDisableAction : public Action { - public: - void play(const Ts &...x) override { global_wifi_component->disable(); } -}; - -template class WiFiConfigureAction : public Action, public Component { - public: - TEMPLATABLE_VALUE(std::string, ssid) - TEMPLATABLE_VALUE(std::string, password) - TEMPLATABLE_VALUE(bool, save) - TEMPLATABLE_VALUE(uint32_t, connection_timeout) - - void play(const Ts &...x) override { - auto ssid = this->ssid_.value(x...); - auto password = this->password_.value(x...); - // Avoid multiple calls - if (this->connecting_) - return; - // If already connected to the same AP, do nothing - if (global_wifi_component->wifi_ssid() == ssid) { - // Callback to notify the user that the connection was successful - this->connect_trigger_->trigger(); - return; - } - // Create a new WiFiAP object with the new SSID and password - this->new_sta_.set_ssid(ssid); - this->new_sta_.set_password(password); - // Save the current STA - this->old_sta_ = global_wifi_component->get_sta(); - // Disable WiFi - global_wifi_component->disable(); - // Set the state to connecting - this->connecting_ = true; - // Store the new STA so once the WiFi is enabled, it will connect to it - // This is necessary because the WiFiComponent will raise an error and fallback to the saved STA - // if trying to connect to a new STA while already connected to another one - if (this->save_.value(x...)) { - global_wifi_component->save_wifi_sta(new_sta_.get_ssid(), new_sta_.get_password()); - } else { - global_wifi_component->set_sta(new_sta_); - } - // Enable WiFi - global_wifi_component->enable(); - // Set timeout for the connection - this->set_timeout("wifi-connect-timeout", this->connection_timeout_.value(x...), [this, x...]() { - // If the timeout is reached, stop connecting and revert to the old AP - global_wifi_component->disable(); - global_wifi_component->save_wifi_sta(old_sta_.get_ssid(), old_sta_.get_password()); - global_wifi_component->enable(); - // Start a timeout for the fallback if the connection to the old AP fails - this->set_timeout("wifi-fallback-timeout", this->connection_timeout_.value(x...), [this]() { - this->connecting_ = false; - this->error_trigger_->trigger(); - }); - }); - } - - Trigger<> *get_connect_trigger() const { return this->connect_trigger_; } - Trigger<> *get_error_trigger() const { return this->error_trigger_; } - - void loop() override { - if (!this->connecting_) - return; - if (global_wifi_component->is_connected()) { - // The WiFi is connected, stop the timeout and reset the connecting flag - this->cancel_timeout("wifi-connect-timeout"); - this->cancel_timeout("wifi-fallback-timeout"); - this->connecting_ = false; - if (global_wifi_component->wifi_ssid() == this->new_sta_.get_ssid()) { - // Callback to notify the user that the connection was successful - this->connect_trigger_->trigger(); - } else { - // Callback to notify the user that the connection failed - this->error_trigger_->trigger(); - } - } - } - - protected: - bool connecting_{false}; - WiFiAP new_sta_; - WiFiAP old_sta_; - Trigger<> *connect_trigger_{new Trigger<>()}; - Trigger<> *error_trigger_{new Trigger<>()}; -}; - -} // namespace wifi -} // namespace esphome +} // namespace esphome::wifi #endif diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 274a505db2..540ad3a585 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -38,8 +38,7 @@ extern "C" { #include "esphome/core/log.h" #include "esphome/core/util.h" -namespace esphome { -namespace wifi { +namespace esphome::wifi { static const char *const TAG = "wifi_esp8266"; @@ -514,6 +513,10 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(), it.channel); s_sta_connected = true; +#ifdef USE_WIFI_CALLBACKS + global_wifi_component->wifi_connect_state_callback_.call(global_wifi_component->wifi_ssid(), + global_wifi_component->wifi_bssid()); +#endif break; } case EVENT_STAMODE_DISCONNECTED: { @@ -533,6 +536,9 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { } s_sta_connected = false; s_sta_connecting = false; +#ifdef USE_WIFI_CALLBACKS + global_wifi_component->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0})); +#endif break; } case EVENT_STAMODE_AUTHMODE_CHANGE: { @@ -555,6 +561,11 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", format_ip_addr(it.ip).c_str(), format_ip_addr(it.gw).c_str(), format_ip_addr(it.mask).c_str()); s_sta_got_ip = true; +#ifdef USE_WIFI_CALLBACKS + global_wifi_component->ip_state_callback_.call(global_wifi_component->wifi_sta_ip_addresses(), + global_wifi_component->get_dns_address(0), + global_wifi_component->get_dns_address(1)); +#endif break; } case EVENT_STAMODE_DHCP_TIMEOUT: { @@ -729,6 +740,9 @@ void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) { it->is_hidden != 0); } this->scan_done_ = true; +#ifdef USE_WIFI_CALLBACKS + global_wifi_component->wifi_scan_state_callback_.call(global_wifi_component->scan_result_); +#endif } #ifdef USE_WIFI_AP @@ -885,8 +899,6 @@ network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {(const ip_addr_t *) WiFi.dnsIP(num)}; } void WiFiComponent::wifi_loop_() {} -} // namespace wifi -} // namespace esphome - +} // namespace esphome::wifi #endif #endif diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index e6e914c0b4..c20c96ced0 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -41,8 +41,7 @@ #include "esphome/core/log.h" #include "esphome/core/util.h" -namespace esphome { -namespace wifi { +namespace esphome::wifi { static const char *const TAG = "wifi_esp32"; @@ -728,6 +727,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); s_sta_connected = true; +#ifdef USE_WIFI_CALLBACKS + this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid()); +#endif } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) { const auto &it = data->data.sta_disconnected; @@ -751,6 +753,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { s_sta_connected = false; s_sta_connecting = false; error_from_callback_ = true; +#ifdef USE_WIFI_CALLBACKS + this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0})); +#endif } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) { const auto &it = data->data.ip_got_ip; @@ -759,12 +764,18 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { #endif /* USE_NETWORK_IPV6 */ ESP_LOGV(TAG, "static_ip=" IPSTR " gateway=" IPSTR, IP2STR(&it.ip_info.ip), IP2STR(&it.ip_info.gw)); this->got_ipv4_address_ = true; +#ifdef USE_WIFI_CALLBACKS + this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); +#endif #if USE_NETWORK_IPV6 } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_GOT_IP6) { const auto &it = data->data.ip_got_ip6; ESP_LOGV(TAG, "IPv6 address=" IPV6STR, IPV62STR(it.ip6_info.ip)); this->num_ipv6_addresses_++; +#ifdef USE_WIFI_CALLBACKS + this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); +#endif #endif /* USE_NETWORK_IPV6 */ } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_LOST_IP) { @@ -804,6 +815,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { scan_result_.emplace_back(bssid, ssid, record.primary, record.rssi, record.authmode != WIFI_AUTH_OPEN, ssid.empty()); } +#ifdef USE_WIFI_CALLBACKS + this->wifi_scan_state_callback_.call(this->scan_result_); +#endif } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_START) { ESP_LOGV(TAG, "AP start"); @@ -1088,8 +1102,6 @@ network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return network::IPAddress(dns_ip); } -} // namespace wifi -} // namespace esphome - +} // namespace esphome::wifi #endif // USE_ESP32 #endif diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 98cbfddb1d..04d0d4fa85 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -15,8 +15,7 @@ #include "esphome/core/log.h" #include "esphome/core/util.h" -namespace esphome { -namespace wifi { +namespace esphome::wifi { static const char *const TAG = "wifi_lt"; @@ -288,7 +287,9 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ buf[it.ssid_len] = '\0'; ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); - +#ifdef USE_WIFI_CALLBACKS + this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid()); +#endif break; } case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: { @@ -314,6 +315,9 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } s_sta_connecting = false; +#ifdef USE_WIFI_CALLBACKS + this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0})); +#endif break; } case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: { @@ -335,11 +339,17 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ ESP_LOGV(TAG, "static_ip=%s gateway=%s", format_ip4_addr(WiFi.localIP()).c_str(), format_ip4_addr(WiFi.gatewayIP()).c_str()); s_sta_connecting = false; +#ifdef USE_WIFI_CALLBACKS + this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); +#endif break; } case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: { // auto it = info.got_ip.ip_info; ESP_LOGV(TAG, "Got IPv6"); +#ifdef USE_WIFI_CALLBACKS + this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); +#endif break; } case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: { @@ -433,6 +443,9 @@ void WiFiComponent::wifi_scan_done_callback_() { } WiFi.scanDelete(); this->scan_done_ = true; +#ifdef USE_WIFI_CALLBACKS + this->wifi_scan_state_callback_.call(this->scan_result_); +#endif } #ifdef USE_WIFI_AP @@ -493,8 +506,6 @@ network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()} network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } void WiFiComponent::wifi_loop_() {} -} // namespace wifi -} // namespace esphome - +} // namespace esphome::wifi #endif // USE_LIBRETINY #endif diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 91766e8ab5..326883c0c4 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -1,4 +1,3 @@ - #include "wifi_component.h" #ifdef USE_WIFI @@ -15,11 +14,14 @@ #include "esphome/core/log.h" #include "esphome/core/util.h" -namespace esphome { -namespace wifi { +namespace esphome::wifi { static const char *const TAG = "wifi_pico_w"; +// Track previous state for detecting changes +static bool s_sta_was_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_had_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + bool WiFiComponent::wifi_mode_(optional sta, optional ap) { if (sta.has_value()) { if (sta.value()) { @@ -51,7 +53,7 @@ bool WiFiComponent::wifi_apply_power_save_() { return ret == 0; } -// TODO: The driver doesnt seem to have an API for this +// TODO: The driver doesn't seem to have an API for this bool WiFiComponent::wifi_apply_output_power_(float output_power) { return true; } bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { @@ -219,16 +221,61 @@ network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { } void WiFiComponent::wifi_loop_() { + // Handle scan completion if (this->state_ == WIFI_COMPONENT_STATE_STA_SCANNING && !cyw43_wifi_scan_active(&cyw43_state)) { this->scan_done_ = true; ESP_LOGV(TAG, "Scan done"); +#ifdef USE_WIFI_CALLBACKS + this->wifi_scan_state_callback_.call(this->scan_result_); +#endif + } + + // Poll for connection state changes + // The arduino-pico WiFi library doesn't have event callbacks like ESP8266/ESP32, + // so we need to poll the link status to detect state changes + auto status = cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_STA); + bool is_connected = (status == CYW43_LINK_UP); + + // Detect connection state change + if (is_connected && !s_sta_was_connected) { + // Just connected + s_sta_was_connected = true; + ESP_LOGV(TAG, "Connected"); +#ifdef USE_WIFI_CALLBACKS + this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid()); +#endif + } else if (!is_connected && s_sta_was_connected) { + // Just disconnected + s_sta_was_connected = false; + s_sta_had_ip = false; + ESP_LOGV(TAG, "Disconnected"); +#ifdef USE_WIFI_CALLBACKS + this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0})); +#endif + } + + // Detect IP address changes (only when connected) + if (is_connected) { + bool has_ip = false; + // Check for any IP address (IPv4 or IPv6) + for (auto addr : addrList) { + has_ip = true; + break; + } + + if (has_ip && !s_sta_had_ip) { + // Just got IP address + s_sta_had_ip = true; + ESP_LOGV(TAG, "Got IP address"); +#ifdef USE_WIFI_CALLBACKS + this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); +#endif + } } } void WiFiComponent::wifi_pre_setup_() {} -} // namespace wifi -} // namespace esphome - +} // namespace esphome::wifi #endif #endif diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index a4da582c55..0feee3d4a9 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -15,31 +15,27 @@ DEPENDENCIES = ["wifi"] wifi_info_ns = cg.esphome_ns.namespace("wifi_info") IPAddressWiFiInfo = wifi_info_ns.class_( - "IPAddressWiFiInfo", text_sensor.TextSensor, cg.PollingComponent + "IPAddressWiFiInfo", text_sensor.TextSensor, cg.Component ) ScanResultsWiFiInfo = wifi_info_ns.class_( - "ScanResultsWiFiInfo", text_sensor.TextSensor, cg.PollingComponent -) -SSIDWiFiInfo = wifi_info_ns.class_( - "SSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent + "ScanResultsWiFiInfo", text_sensor.TextSensor, cg.Component ) +SSIDWiFiInfo = wifi_info_ns.class_("SSIDWiFiInfo", text_sensor.TextSensor, cg.Component) BSSIDWiFiInfo = wifi_info_ns.class_( - "BSSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent + "BSSIDWiFiInfo", text_sensor.TextSensor, cg.Component ) MacAddressWifiInfo = wifi_info_ns.class_( "MacAddressWifiInfo", text_sensor.TextSensor, cg.Component ) DNSAddressWifiInfo = wifi_info_ns.class_( - "DNSAddressWifiInfo", text_sensor.TextSensor, cg.PollingComponent + "DNSAddressWifiInfo", text_sensor.TextSensor, cg.Component ) CONFIG_SCHEMA = cv.Schema( { cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( IPAddressWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ) - .extend(cv.polling_component_schema("1s")) - .extend( + ).extend( { cv.Optional(f"address_{x}"): text_sensor.text_sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, @@ -49,22 +45,31 @@ CONFIG_SCHEMA = cv.Schema( ), cv.Optional(CONF_SCAN_RESULTS): text_sensor.text_sensor_schema( ScanResultsWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ).extend(cv.polling_component_schema("60s")), + ), cv.Optional(CONF_SSID): text_sensor.text_sensor_schema( SSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ).extend(cv.polling_component_schema("1s")), + ), cv.Optional(CONF_BSSID): text_sensor.text_sensor_schema( BSSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ).extend(cv.polling_component_schema("1s")), + ), cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema( MacAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ), cv.Optional(CONF_DNS_ADDRESS): text_sensor.text_sensor_schema( DNSAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ).extend(cv.polling_component_schema("1s")), + ), } ) +# Keys that require WiFi callbacks +_NETWORK_INFO_KEYS = { + CONF_SSID, + CONF_BSSID, + CONF_IP_ADDRESS, + CONF_DNS_ADDRESS, + CONF_SCAN_RESULTS, +} + async def setup_conf(config, key): if key in config: @@ -74,6 +79,10 @@ async def setup_conf(config, key): async def to_code(config): + # Request WiFi callbacks for any sensor that needs them + if _NETWORK_INFO_KEYS.intersection(config): + wifi.request_wifi_callbacks() + await setup_conf(config, CONF_SSID) await setup_conf(config, CONF_BSSID) await setup_conf(config, CONF_MAC_ADDRESS) diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index 2612e4af8d..abd590b168 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -2,18 +2,121 @@ #ifdef USE_WIFI #include "esphome/core/log.h" -namespace esphome { -namespace wifi_info { +namespace esphome::wifi_info { static const char *const TAG = "wifi_info"; +static constexpr size_t MAX_STATE_LENGTH = 255; + +/******************** + * IPAddressWiFiInfo + *******************/ + +void IPAddressWiFiInfo::setup() { + wifi::global_wifi_component->add_on_ip_state_callback( + [this](const network::IPAddresses &ips, const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip) { + this->state_callback_(ips); + }); +} + void IPAddressWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "IP Address", this); } -void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "Scan Results", this); } -void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); } -void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "BSSID", this); } -void MacAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "MAC Address", this); } + +void IPAddressWiFiInfo::state_callback_(const network::IPAddresses &ips) { + this->publish_state(ips[0].str()); + uint8_t sensor = 0; + for (const auto &ip : ips) { + if (ip.is_set()) { + if (this->ip_sensors_[sensor] != nullptr) { + this->ip_sensors_[sensor]->publish_state(ip.str()); + } + sensor++; + } + } +} + +/********************* + * DNSAddressWifiInfo + ********************/ + +void DNSAddressWifiInfo::setup() { + wifi::global_wifi_component->add_on_ip_state_callback( + [this](const network::IPAddresses &ips, const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip) { + this->state_callback_(dns1_ip, dns2_ip); + }); +} + void DNSAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "DNS Address", this); } -} // namespace wifi_info -} // namespace esphome +void DNSAddressWifiInfo::state_callback_(const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip) { + std::string dns_results = dns1_ip.str() + " " + dns2_ip.str(); + this->publish_state(dns_results); +} + +/********************** + * ScanResultsWiFiInfo + *********************/ + +void ScanResultsWiFiInfo::setup() { + wifi::global_wifi_component->add_on_wifi_scan_state_callback( + [this](const wifi::wifi_scan_vector_t &results) { this->state_callback_(results); }); +} + +void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "Scan Results", this); } + +void ScanResultsWiFiInfo::state_callback_(const wifi::wifi_scan_vector_t &results) { + std::string scan_results; + for (const auto &scan : results) { + if (scan.get_is_hidden()) + continue; + + scan_results += scan.get_ssid(); + scan_results += ": "; + scan_results += esphome::to_string(scan.get_rssi()); + scan_results += "dB\n"; + } + // There's a limit of 255 characters per state; longer states just don't get sent so we truncate it + if (scan_results.length() > MAX_STATE_LENGTH) { + scan_results.resize(MAX_STATE_LENGTH); + } + this->publish_state(scan_results); +} + +/*************** + * SSIDWiFiInfo + **************/ + +void SSIDWiFiInfo::setup() { + wifi::global_wifi_component->add_on_wifi_connect_state_callback( + [this](const std::string &ssid, const wifi::bssid_t &bssid) { this->state_callback_(ssid); }); +} + +void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); } + +void SSIDWiFiInfo::state_callback_(const std::string &ssid) { this->publish_state(ssid); } + +/**************** + * BSSIDWiFiInfo + ***************/ + +void BSSIDWiFiInfo::setup() { + wifi::global_wifi_component->add_on_wifi_connect_state_callback( + [this](const std::string &ssid, const wifi::bssid_t &bssid) { this->state_callback_(bssid); }); +} + +void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "BSSID", this); } + +void BSSIDWiFiInfo::state_callback_(const wifi::bssid_t &bssid) { + char buf[18] = "unknown"; + if (mac_address_is_valid(bssid.data())) { + format_mac_addr_upper(bssid.data(), buf); + } + this->publish_state(buf); +} +/********************* + * MacAddressWifiInfo + ********************/ + +void MacAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "MAC Address", this); } + +} // namespace esphome::wifi_info #endif diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index 0814336c43..12666b4059 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -7,121 +7,54 @@ #ifdef USE_WIFI #include -namespace esphome { -namespace wifi_info { +namespace esphome::wifi_info { -static constexpr size_t MAX_STATE_LENGTH = 255; - -class IPAddressWiFiInfo : public PollingComponent, public text_sensor::TextSensor { +class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor { public: - void update() override { - auto ips = wifi::global_wifi_component->wifi_sta_ip_addresses(); - if (ips != this->last_ips_) { - this->last_ips_ = ips; - this->publish_state(ips[0].str()); - uint8_t sensor = 0; - for (auto &ip : ips) { - if (ip.is_set()) { - if (this->ip_sensors_[sensor] != nullptr) { - this->ip_sensors_[sensor]->publish_state(ip.str()); - } - sensor++; - } - } - } - } - float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + void setup() override; void dump_config() override; void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; } protected: - network::IPAddresses last_ips_; + void state_callback_(const network::IPAddresses &ips); std::array ip_sensors_; }; -class DNSAddressWifiInfo : public PollingComponent, public text_sensor::TextSensor { +class DNSAddressWifiInfo : public Component, public text_sensor::TextSensor { public: - void update() override { - auto dns_one = wifi::global_wifi_component->get_dns_address(0); - auto dns_two = wifi::global_wifi_component->get_dns_address(1); - - std::string dns_results = dns_one.str() + " " + dns_two.str(); - - if (dns_results != this->last_results_) { - this->last_results_ = dns_results; - this->publish_state(dns_results); - } - } - float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + void setup() override; void dump_config() override; protected: - std::string last_results_; + void state_callback_(const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip); }; -class ScanResultsWiFiInfo : public PollingComponent, public text_sensor::TextSensor { +class ScanResultsWiFiInfo : public Component, public text_sensor::TextSensor { public: - void update() override { - std::string scan_results; - for (auto &scan : wifi::global_wifi_component->get_scan_result()) { - if (scan.get_is_hidden()) - continue; - - scan_results += scan.get_ssid(); - scan_results += ": "; - scan_results += esphome::to_string(scan.get_rssi()); - scan_results += "dB\n"; - } - - // There's a limit of 255 characters per state. - // Longer states just don't get sent so we truncate it. - if (scan_results.length() > MAX_STATE_LENGTH) { - scan_results.resize(MAX_STATE_LENGTH); - } - if (this->last_scan_results_ != scan_results) { - this->last_scan_results_ = scan_results; - this->publish_state(scan_results); - } - } + void setup() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } void dump_config() override; protected: - std::string last_scan_results_; + void state_callback_(const wifi::wifi_scan_vector_t &results); }; -class SSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor { +class SSIDWiFiInfo : public Component, public text_sensor::TextSensor { public: - void update() override { - std::string ssid = wifi::global_wifi_component->wifi_ssid(); - if (this->last_ssid_ != ssid) { - this->last_ssid_ = ssid; - this->publish_state(this->last_ssid_); - } - } - float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + void setup() override; void dump_config() override; protected: - std::string last_ssid_; + void state_callback_(const std::string &ssid); }; -class BSSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor { +class BSSIDWiFiInfo : public Component, public text_sensor::TextSensor { public: - void update() override { - wifi::bssid_t bssid = wifi::global_wifi_component->wifi_bssid(); - if (memcmp(bssid.data(), last_bssid_.data(), 6) != 0) { - std::copy(bssid.begin(), bssid.end(), last_bssid_.begin()); - char buf[18]; - format_mac_addr_upper(bssid.data(), buf); - this->publish_state(buf); - } - } - float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + void setup() override; void dump_config() override; protected: - wifi::bssid_t last_bssid_; + void state_callback_(const wifi::bssid_t &bssid); }; class MacAddressWifiInfo : public Component, public text_sensor::TextSensor { @@ -133,6 +66,5 @@ class MacAddressWifiInfo : public Component, public text_sensor::TextSensor { void dump_config() override; }; -} // namespace wifi_info -} // namespace esphome +} // namespace esphome::wifi_info #endif diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 4b24c395b9..1373ea6366 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -210,6 +210,7 @@ #define USE_WEBSERVER_SORTING #define USE_WIFI_11KV_SUPPORT #define USE_WIFI_FAST_CONNECT +#define USE_WIFI_CALLBACKS #define USE_WIFI_RUNTIME_POWER_SAVE #define USB_HOST_MAX_REQUESTS 16 From 1c808a3375a824f37cfdb6bfb067f96975be733b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 21:19:18 -0600 Subject: [PATCH 0369/1145] [ble_client] Write static BLE data directly from flash without allocation (#11826) --- esphome/components/ble_client/automation.h | 27 +++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 9c5646b3d1..bbc2dd05e0 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -122,16 +122,19 @@ template class BLEClientWriteAction : public Action, publ void play_complex(const Ts &...x) override { this->num_running_++; this->var_ = std::make_tuple(x...); - std::vector value; + + bool result; if (this->len_ >= 0) { - // Static mode: copy from flash to vector - value.assign(this->value_.data, this->value_.data + this->len_); + // Static mode: write directly from flash pointer + result = this->write(this->value_.data, this->len_); } else { - // Template mode: call function - value = this->value_.func(x...); + // Template mode: call function and write the vector + std::vector value = this->value_.func(x...); + result = this->write(value); } + // on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work. - if (!write(value)) + if (!result) this->play_next_(x...); } @@ -144,15 +147,15 @@ template class BLEClientWriteAction : public Action, publ * errors. */ // initiate the write. Return true if all went well, will be followed by a WRITE_CHAR event. - bool write(const std::vector &value) { + bool write(const uint8_t *data, size_t len) { if (this->node_state != espbt::ClientState::ESTABLISHED) { esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected"); return false; } - esph_log_vv(Automation::TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str()); - esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), - this->char_handle_, value.size(), const_cast(value.data()), - this->write_type_, ESP_GATT_AUTH_REQ_NONE); + esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty(data, len).c_str()); + esp_err_t err = + esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len, + const_cast(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE); if (err != ESP_OK) { esph_log_e(Automation::TAG, "Error writing to characteristic: %s!", esp_err_to_name(err)); return false; @@ -160,6 +163,8 @@ template class BLEClientWriteAction : public Action, publ return true; } + bool write(const std::vector &value) { return this->write(value.data(), value.size()); } + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override { switch (event) { From 46a26560fd32eceedc547b154018ddad4deefd8a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Nov 2025 21:21:56 -0600 Subject: [PATCH 0370/1145] [template.alarm_control_panel] Replace std::map with FixedVector for heap and flash savings (#11893) --- .../template/alarm_control_panel/__init__.py | 6 +- .../template_alarm_control_panel.cpp | 35 +++-- .../template_alarm_control_panel.h | 20 ++- ...late_alarm_control_panel_many_sensors.yaml | 136 ++++++++++++++++++ ...mplate_alarm_control_panel_many_sensors.py | 118 +++++++++++++++ 5 files changed, 296 insertions(+), 19 deletions(-) create mode 100644 tests/integration/fixtures/template_alarm_control_panel_many_sensors.yaml create mode 100644 tests/integration/test_template_alarm_control_panel_many_sensors.py diff --git a/esphome/components/template/alarm_control_panel/__init__.py b/esphome/components/template/alarm_control_panel/__init__.py index 5d2421fcbc..256c7f276a 100644 --- a/esphome/components/template/alarm_control_panel/__init__.py +++ b/esphome/components/template/alarm_control_panel/__init__.py @@ -137,7 +137,11 @@ async def to_code(config): cg.add(var.set_arming_night_time(config[CONF_ARMING_NIGHT_TIME])) supports_arm_night = True - for sensor in config.get(CONF_BINARY_SENSORS, []): + if sensors := config.get(CONF_BINARY_SENSORS, []): + # Initialize FixedVector with the exact number of sensors + cg.add(var.init_sensors(len(sensors))) + + for sensor in sensors: bs = await cg.get_variable(sensor[CONF_INPUT]) flags = BinarySensorFlags[FLAG_NORMAL] diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp index af662a05a0..f025435261 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp @@ -20,10 +20,13 @@ void TemplateAlarmControlPanel::add_sensor(binary_sensor::BinarySensor *sensor, // Save the flags and type. Assign a store index for the per sensor data type. SensorDataStore sd; sd.last_chime_state = false; - this->sensor_map_[sensor].flags = flags; - this->sensor_map_[sensor].type = type; + AlarmSensor alarm_sensor; + alarm_sensor.sensor = sensor; + alarm_sensor.info.flags = flags; + alarm_sensor.info.type = type; + alarm_sensor.info.store_index = this->next_store_index_++; + this->sensors_.push_back(alarm_sensor); this->sensor_data_.push_back(sd); - this->sensor_map_[sensor].store_index = this->next_store_index_++; }; static const LogString *sensor_type_to_string(AlarmSensorType type) { @@ -45,7 +48,7 @@ void TemplateAlarmControlPanel::dump_config() { ESP_LOGCONFIG(TAG, "TemplateAlarmControlPanel:\n" " Current State: %s\n" - " Number of Codes: %u\n" + " Number of Codes: %zu\n" " Requires Code To Arm: %s\n" " Arming Away Time: %" PRIu32 "s\n" " Arming Home Time: %" PRIu32 "s\n" @@ -58,7 +61,8 @@ void TemplateAlarmControlPanel::dump_config() { (this->arming_home_time_ / 1000), (this->arming_night_time_ / 1000), (this->pending_time_ / 1000), (this->trigger_time_ / 1000), this->get_supported_features()); #ifdef USE_BINARY_SENSOR - for (auto const &[sensor, info] : this->sensor_map_) { + for (const auto &alarm_sensor : this->sensors_) { + const uint16_t flags = alarm_sensor.info.flags; ESP_LOGCONFIG(TAG, " Binary Sensor:\n" " Name: %s\n" @@ -67,11 +71,10 @@ void TemplateAlarmControlPanel::dump_config() { " Armed night bypass: %s\n" " Auto bypass: %s\n" " Chime mode: %s", - sensor->get_name().c_str(), LOG_STR_ARG(sensor_type_to_string(info.type)), - TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME), - TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT), - TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO), - TRUEFALSE(info.flags & BINARY_SENSOR_MODE_CHIME)); + alarm_sensor.sensor->get_name().c_str(), LOG_STR_ARG(sensor_type_to_string(alarm_sensor.info.type)), + TRUEFALSE(flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME), + TRUEFALSE(flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT), + TRUEFALSE(flags & BINARY_SENSOR_MODE_BYPASS_AUTO), TRUEFALSE(flags & BINARY_SENSOR_MODE_CHIME)); } #endif } @@ -121,7 +124,9 @@ void TemplateAlarmControlPanel::loop() { #ifdef USE_BINARY_SENSOR // Test all of the sensors regardless of the alarm panel state - for (auto const &[sensor, info] : this->sensor_map_) { + for (const auto &alarm_sensor : this->sensors_) { + const auto &info = alarm_sensor.info; + auto *sensor = alarm_sensor.sensor; // Check for chime zones if (info.flags & BINARY_SENSOR_MODE_CHIME) { // Look for the transition from closed to open @@ -242,11 +247,11 @@ void TemplateAlarmControlPanel::arm_(optional code, alarm_control_p void TemplateAlarmControlPanel::bypass_before_arming() { #ifdef USE_BINARY_SENSOR - for (auto const &[sensor, info] : this->sensor_map_) { + for (const auto &alarm_sensor : this->sensors_) { // Check for faulted bypass_auto sensors and remove them from monitoring - if ((info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (sensor->state)) { - ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", sensor->get_name().c_str()); - this->bypassed_sensor_indicies_.push_back(info.store_index); + if ((alarm_sensor.info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (alarm_sensor.sensor->state)) { + ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", alarm_sensor.sensor->get_name().c_str()); + this->bypassed_sensor_indicies_.push_back(alarm_sensor.info.store_index); } } #endif diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h index 202dc7c13f..80ce34b8ae 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h @@ -1,11 +1,12 @@ #pragma once #include -#include +#include #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include "esphome/components/alarm_control_panel/alarm_control_panel.h" @@ -49,6 +50,13 @@ struct SensorInfo { uint8_t store_index; }; +#ifdef USE_BINARY_SENSOR +struct AlarmSensor { + binary_sensor::BinarySensor *sensor; + SensorInfo info; +}; +#endif + class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControlPanel, public Component { public: TemplateAlarmControlPanel(); @@ -63,6 +71,12 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl void bypass_before_arming(); #ifdef USE_BINARY_SENSOR + /** Initialize the sensors vector with the specified capacity. + * + * @param capacity The number of sensors to allocate space for. + */ + void init_sensors(size_t capacity) { this->sensors_.init(capacity); } + /** Add a binary_sensor to the alarm_panel. * * @param sensor The BinarySensor instance. @@ -122,8 +136,8 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl protected: void control(const alarm_control_panel::AlarmControlPanelCall &call) override; #ifdef USE_BINARY_SENSOR - // This maps a binary sensor to its alarm specific info - std::map sensor_map_; + // List of binary sensors with their alarm-specific info + FixedVector sensors_; // a list of automatically bypassed sensors std::vector bypassed_sensor_indicies_; #endif diff --git a/tests/integration/fixtures/template_alarm_control_panel_many_sensors.yaml b/tests/integration/fixtures/template_alarm_control_panel_many_sensors.yaml new file mode 100644 index 0000000000..836d3f11d5 --- /dev/null +++ b/tests/integration/fixtures/template_alarm_control_panel_many_sensors.yaml @@ -0,0 +1,136 @@ +esphome: + name: template-alarm-many-sensors + friendly_name: "Template Alarm Control Panel with Many Sensors" + +logger: + +host: + +api: + +binary_sensor: + - platform: template + id: sensor1 + name: "Door 1" + - platform: template + id: sensor2 + name: "Door 2" + - platform: template + id: sensor3 + name: "Window 1" + - platform: template + id: sensor4 + name: "Window 2" + - platform: template + id: sensor5 + name: "Motion 1" + - platform: template + id: sensor6 + name: "Motion 2" + - platform: template + id: sensor7 + name: "Glass Break 1" + - platform: template + id: sensor8 + name: "Glass Break 2" + - platform: template + id: sensor9 + name: "Smoke Detector" + - platform: template + id: sensor10 + name: "CO Detector" + +alarm_control_panel: + - platform: template + id: test_alarm + name: "Test Alarm" + codes: + - "1234" + requires_code_to_arm: true + arming_away_time: 5s + arming_home_time: 3s + arming_night_time: 3s + pending_time: 10s + trigger_time: 300s + restore_mode: ALWAYS_DISARMED + binary_sensors: + - input: sensor1 + bypass_armed_home: false + bypass_armed_night: false + bypass_auto: true + chime: true + trigger_mode: DELAYED + - input: sensor2 + bypass_armed_home: false + bypass_armed_night: false + bypass_auto: true + chime: true + trigger_mode: DELAYED + - input: sensor3 + bypass_armed_home: true + bypass_armed_night: false + bypass_auto: false + chime: false + trigger_mode: DELAYED + - input: sensor4 + bypass_armed_home: true + bypass_armed_night: false + bypass_auto: false + chime: false + trigger_mode: DELAYED + - input: sensor5 + bypass_armed_home: false + bypass_armed_night: true + bypass_auto: false + chime: false + trigger_mode: INSTANT + - input: sensor6 + bypass_armed_home: false + bypass_armed_night: true + bypass_auto: false + chime: false + trigger_mode: INSTANT + - input: sensor7 + bypass_armed_home: false + bypass_armed_night: false + bypass_auto: false + chime: false + trigger_mode: INSTANT + - input: sensor8 + bypass_armed_home: false + bypass_armed_night: false + bypass_auto: false + chime: false + trigger_mode: INSTANT + - input: sensor9 + bypass_armed_home: false + bypass_armed_night: false + bypass_auto: false + chime: false + trigger_mode: INSTANT_ALWAYS + - input: sensor10 + bypass_armed_home: false + bypass_armed_night: false + bypass_auto: false + chime: false + trigger_mode: INSTANT_ALWAYS + on_disarmed: + - logger.log: "Alarm disarmed" + on_arming: + - logger.log: "Alarm arming" + on_armed_away: + - logger.log: "Alarm armed away" + on_armed_home: + - logger.log: "Alarm armed home" + on_armed_night: + - logger.log: "Alarm armed night" + on_pending: + - logger.log: "Alarm pending" + on_triggered: + - logger.log: "Alarm triggered" + on_cleared: + - logger.log: "Alarm cleared" + on_chime: + - logger.log: "Chime activated" + on_ready: + - logger.log: "Sensors ready state changed" diff --git a/tests/integration/test_template_alarm_control_panel_many_sensors.py b/tests/integration/test_template_alarm_control_panel_many_sensors.py new file mode 100644 index 0000000000..856815c731 --- /dev/null +++ b/tests/integration/test_template_alarm_control_panel_many_sensors.py @@ -0,0 +1,118 @@ +"""Integration test for template alarm control panel with many sensors.""" + +from __future__ import annotations + +import aioesphomeapi +from aioesphomeapi.model import APIIntEnum +import pytest + +from .state_utils import InitialStateHelper +from .types import APIClientConnectedFactory, RunCompiledFunction + + +class EspHomeACPFeatures(APIIntEnum): + """ESPHome AlarmControlPanel feature numbers.""" + + ARM_HOME = 1 + ARM_AWAY = 2 + ARM_NIGHT = 4 + TRIGGER = 8 + ARM_CUSTOM_BYPASS = 16 + ARM_VACATION = 32 + + +@pytest.mark.asyncio +async def test_template_alarm_control_panel_many_sensors( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test template alarm control panel with 10 binary sensors using FixedVector.""" + async with run_compiled(yaml_config), api_client_connected() as client: + # Get entity info first + entities, _ = await client.list_entities_services() + + # Find the alarm control panel and binary sensors + alarm_info: aioesphomeapi.AlarmControlPanelInfo | None = None + binary_sensors: list[aioesphomeapi.BinarySensorInfo] = [] + + for entity in entities: + if isinstance(entity, aioesphomeapi.AlarmControlPanelInfo): + alarm_info = entity + elif isinstance(entity, aioesphomeapi.BinarySensorInfo): + binary_sensors.append(entity) + + assert alarm_info is not None, "Alarm control panel entity info not found" + assert alarm_info.name == "Test Alarm" + assert alarm_info.requires_code is True + assert alarm_info.requires_code_to_arm is True + + # Verify we have 10 binary sensors + assert len(binary_sensors) == 10, ( + f"Expected 10 binary sensors, got {len(binary_sensors)}" + ) + + # Verify sensor names + expected_sensor_names = { + "Door 1", + "Door 2", + "Window 1", + "Window 2", + "Motion 1", + "Motion 2", + "Glass Break 1", + "Glass Break 2", + "Smoke Detector", + "CO Detector", + } + actual_sensor_names = {sensor.name for sensor in binary_sensors} + assert actual_sensor_names == expected_sensor_names, ( + f"Sensor names mismatch. Expected: {expected_sensor_names}, " + f"Got: {actual_sensor_names}" + ) + + # Use InitialStateHelper to wait for all initial states + state_helper = InitialStateHelper(entities) + + def on_state(state: aioesphomeapi.EntityState) -> None: + # We'll receive subsequent states here after initial states + pass + + client.subscribe_states(state_helper.on_state_wrapper(on_state)) + + # Wait for all initial states + await state_helper.wait_for_initial_states(timeout=5.0) + + # Verify the alarm state is disarmed initially + alarm_state = state_helper.initial_states.get(alarm_info.key) + assert alarm_state is not None, "Alarm control panel initial state not received" + assert isinstance(alarm_state, aioesphomeapi.AlarmControlPanelEntityState) + assert alarm_state.state == aioesphomeapi.AlarmControlPanelState.DISARMED, ( + f"Expected initial state DISARMED, got {alarm_state.state}" + ) + + # Verify all 10 binary sensors have initial states + binary_sensor_states = [ + state_helper.initial_states.get(sensor.key) for sensor in binary_sensors + ] + assert all(state is not None for state in binary_sensor_states), ( + "Not all binary sensors have initial states" + ) + + # Verify all binary sensor states are BinarySensorState type + for i, state in enumerate(binary_sensor_states): + assert isinstance(state, aioesphomeapi.BinarySensorState), ( + f"Binary sensor {i} state is not BinarySensorState: {type(state)}" + ) + + # Verify supported features + expected_features = ( + EspHomeACPFeatures.ARM_HOME + | EspHomeACPFeatures.ARM_AWAY + | EspHomeACPFeatures.ARM_NIGHT + | EspHomeACPFeatures.TRIGGER + ) + assert alarm_info.supported_features == expected_features, ( + f"Expected supported_features={expected_features} (ARM_HOME|ARM_AWAY|ARM_NIGHT|TRIGGER), " + f"got {alarm_info.supported_features}" + ) From 66a871840e1f0b6ba37b03f833b49e6bb73afaaf Mon Sep 17 00:00:00 2001 From: bdm310 Date: Mon, 24 Nov 2025 22:14:23 -0800 Subject: [PATCH 0371/1145] Add more lvgl arc update parameters (#12066) --- esphome/components/lvgl/widgets/arc.py | 54 +++++++++++++++++++------ tests/components/lvgl/lvgl-package.yaml | 12 ++++++ 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/esphome/components/lvgl/widgets/arc.py b/esphome/components/lvgl/widgets/arc.py index ef4da0d815..21530441f8 100644 --- a/esphome/components/lvgl/widgets/arc.py +++ b/esphome/components/lvgl/widgets/arc.py @@ -20,7 +20,13 @@ from ..defines import ( CONF_START_ANGLE, literal, ) -from ..lv_validation import get_start_value, lv_angle_degrees, lv_float, lv_int +from ..lv_validation import ( + get_start_value, + lv_angle_degrees, + lv_float, + lv_int, + lv_positive_int, +) from ..lvcode import lv, lv_expr, lv_obj from ..types import LvNumber, NumberType from . import Widget @@ -36,13 +42,20 @@ ARC_SCHEMA = cv.Schema( cv.Optional(CONF_ROTATION, default=0.0): lv_angle_degrees, cv.Optional(CONF_ADJUSTABLE, default=False): bool, cv.Optional(CONF_MODE, default="NORMAL"): ARC_MODES.one_of, - cv.Optional(CONF_CHANGE_RATE, default=720): cv.uint16_t, + cv.Optional(CONF_CHANGE_RATE, default=720): lv_positive_int, } ) ARC_MODIFY_SCHEMA = cv.Schema( { cv.Optional(CONF_VALUE): lv_float, + cv.Optional(CONF_MIN_VALUE): lv_int, + cv.Optional(CONF_MAX_VALUE): lv_int, + cv.Optional(CONF_START_ANGLE): lv_angle_degrees, + cv.Optional(CONF_END_ANGLE): lv_angle_degrees, + cv.Optional(CONF_ROTATION): lv_angle_degrees, + cv.Optional(CONF_MODE): ARC_MODES.one_of, + cv.Optional(CONF_CHANGE_RATE): lv_positive_int, } ) @@ -58,17 +71,34 @@ class ArcType(NumberType): ) async def to_code(self, w: Widget, config): - if CONF_MIN_VALUE in config: + if CONF_MIN_VALUE in config and CONF_MAX_VALUE in config: max_value = await lv_int.process(config[CONF_MAX_VALUE]) min_value = await lv_int.process(config[CONF_MIN_VALUE]) lv.arc_set_range(w.obj, min_value, max_value) - start = await lv_angle_degrees.process(config[CONF_START_ANGLE]) - end = await lv_angle_degrees.process(config[CONF_END_ANGLE]) - rotation = await lv_angle_degrees.process(config[CONF_ROTATION]) - lv.arc_set_bg_angles(w.obj, start, end) - lv.arc_set_rotation(w.obj, rotation) - lv.arc_set_mode(w.obj, literal(config[CONF_MODE])) - lv.arc_set_change_rate(w.obj, config[CONF_CHANGE_RATE]) + elif CONF_MIN_VALUE in config: + max_value = w.get_property(CONF_MAX_VALUE) + min_value = await lv_int.process(config[CONF_MIN_VALUE]) + lv.arc_set_range(w.obj, min_value, max_value) + elif CONF_MAX_VALUE in config: + max_value = await lv_int.process(config[CONF_MAX_VALUE]) + min_value = w.get_property(CONF_MIN_VALUE) + lv.arc_set_range(w.obj, min_value, max_value) + + await w.set_property( + CONF_START_ANGLE, + await lv_angle_degrees.process(config.get(CONF_START_ANGLE)), + ) + await w.set_property( + CONF_END_ANGLE, await lv_angle_degrees.process(config.get(CONF_END_ANGLE)) + ) + await w.set_property( + CONF_ROTATION, await lv_angle_degrees.process(config.get(CONF_ROTATION)) + ) + await w.set_property(CONF_MODE, config) + await w.set_property( + CONF_CHANGE_RATE, + await lv_positive_int.process(config.get(CONF_CHANGE_RATE)), + ) if CONF_ADJUSTABLE in config: if not config[CONF_ADJUSTABLE]: @@ -78,9 +108,7 @@ class ArcType(NumberType): # 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: - lv.arc_set_value(w.obj, value) + await w.set_property(CONF_VALUE, await get_start_value(config)) arc_spec = ArcType() diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 5839643638..d54aef8b4a 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -781,6 +781,18 @@ lvgl: arc_color: 0xFFFF00 focused: arc_color: 0x808080 + on_click: + then: + - lvgl.arc.update: + id: lv_arc_1 + value: !lambda return (int)((float)rand() / RAND_MAX * 100); + min_value: !lambda return (int)((float)rand() / RAND_MAX * 100); + max_value: !lambda return (int)((float)rand() / RAND_MAX * 100); + start_angle: !lambda return (int)((float)rand() / RAND_MAX * 100); + end_angle: !lambda return (int)((float)rand() / RAND_MAX * 100); + rotation: !lambda return (int)((float)rand() / RAND_MAX * 100); + change_rate: !lambda return (uint)((float)rand() / RAND_MAX * 100); + mode: NORMAL - bar: id: bar_id align: top_mid From 18c97a08c38193e4a2807c254a4ff52f265f2f28 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Nov 2025 01:47:06 -0600 Subject: [PATCH 0372/1145] [esp8266] Use C++17 nested namespaces and constexpr (#12096) --- esphome/components/esp8266/core.h | 4 +--- esphome/components/esp8266/gpio.cpp | 9 +++++---- esphome/components/esp8266/gpio.h | 6 ++---- esphome/components/esp8266/preferences.cpp | 20 ++++++++++---------- esphome/components/esp8266/preferences.h | 6 ++---- 5 files changed, 20 insertions(+), 25 deletions(-) diff --git a/esphome/components/esp8266/core.h b/esphome/components/esp8266/core.h index ac33305669..1abe67be86 100644 --- a/esphome/components/esp8266/core.h +++ b/esphome/components/esp8266/core.h @@ -7,8 +7,6 @@ extern const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16]; extern const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16]; -namespace esphome { -namespace esp8266 {} // namespace esp8266 -} // namespace esphome +namespace esphome::esp8266 {} // namespace esphome::esp8266 #endif // USE_ESP8266 diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index ee3683c67d..17a495bc1d 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -3,8 +3,7 @@ #include "gpio.h" #include "esphome/core/log.h" -namespace esphome { -namespace esp8266 { +namespace esphome::esp8266 { static const char *const TAG = "esp8266"; @@ -110,9 +109,11 @@ void ESP8266GPIOPin::digital_write(bool value) { } void ESP8266GPIOPin::detach_interrupt() const { detachInterrupt(pin_); } -} // namespace esp8266 +} // namespace esphome::esp8266 -using namespace esp8266; +namespace esphome { + +using esp8266::ISRPinArg; bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { auto *arg = reinterpret_cast(this->arg_); diff --git a/esphome/components/esp8266/gpio.h b/esphome/components/esp8266/gpio.h index a1b6d79b3b..213a5c54be 100644 --- a/esphome/components/esp8266/gpio.h +++ b/esphome/components/esp8266/gpio.h @@ -5,8 +5,7 @@ #include "esphome/core/hal.h" #include -namespace esphome { -namespace esp8266 { +namespace esphome::esp8266 { class ESP8266GPIOPin : public InternalGPIOPin { public: @@ -33,7 +32,6 @@ class ESP8266GPIOPin : public InternalGPIOPin { gpio::Flags flags_{}; }; -} // namespace esp8266 -} // namespace esphome +} // namespace esphome::esp8266 #endif // USE_ESP8266 diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index a26e9cc498..197d244dc4 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -15,24 +15,24 @@ extern "C" { #include #include -namespace esphome { -namespace esp8266 { +namespace esphome::esp8266 { static const char *const TAG = "esp8266.preferences"; -static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static uint32_t *s_flash_storage = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static const uint32_t ESP_RTC_USER_MEM_START = 0x60001200; +static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200; +static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; +static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; + #define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START) -static const uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; -static const uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; #ifdef USE_ESP8266_PREFERENCES_FLASH -static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 128; +static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 128; #else -static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 64; +static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 64; #endif static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) { @@ -284,10 +284,10 @@ void setup_preferences() { } void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; } -} // namespace esp8266 +} // namespace esphome::esp8266 +namespace esphome { ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome #endif // USE_ESP8266 diff --git a/esphome/components/esp8266/preferences.h b/esphome/components/esp8266/preferences.h index edec915794..16cf80a129 100644 --- a/esphome/components/esp8266/preferences.h +++ b/esphome/components/esp8266/preferences.h @@ -2,13 +2,11 @@ #ifdef USE_ESP8266 -namespace esphome { -namespace esp8266 { +namespace esphome::esp8266 { void setup_preferences(); void preferences_prevent_write(bool prevent); -} // namespace esp8266 -} // namespace esphome +} // namespace esphome::esp8266 #endif // USE_ESP8266 From 697c5f424ebf0aa9c07693cce2c1c79675b164f4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Nov 2025 02:17:53 -0600 Subject: [PATCH 0373/1145] [api] Use const char* pointers for light effects to eliminate heap allocations (#12090) --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_connection.cpp | 10 +++++++--- esphome/components/api/api_pb2.cpp | 10 +++++----- esphome/components/api/api_pb2.h | 2 +- esphome/components/api/api_pb2_dump.cpp | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 26d1fa6876..74a8e8ff7f 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -518,7 +518,7 @@ message ListEntitiesLightResponse { bool legacy_supports_color_temperature = 8 [deprecated=true]; float min_mireds = 9; float max_mireds = 10; - repeated string effects = 11; + repeated string effects = 11 [(container_pointer_no_template) = "FixedVector"]; bool disabled_by_default = 13; string icon = 14 [(field_ifdef) = "USE_ENTITY_ICON"]; EntityCategory entity_category = 15; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index ebfc641537..12cbbb991d 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -484,12 +484,16 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c msg.min_mireds = traits.get_min_mireds(); msg.max_mireds = traits.get_max_mireds(); } + FixedVector effects_list; if (light->supports_effects()) { - msg.effects.emplace_back("None"); - for (auto *effect : light->get_effects()) { - msg.effects.emplace_back(effect->get_name()); + auto &light_effects = light->get_effects(); + effects_list.init(light_effects.size() + 1); + effects_list.push_back("None"); + for (auto *effect : light_effects) { + effects_list.push_back(effect->get_name()); } } + msg.effects = &effects_list; return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index d52135a566..c131815456 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -476,8 +476,8 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_float(9, this->min_mireds); buffer.encode_float(10, this->max_mireds); - for (auto &it : this->effects) { - buffer.encode_string(11, it, true); + for (const char *it : *this->effects) { + buffer.encode_string(11, it, strlen(it), true); } buffer.encode_bool(13, this->disabled_by_default); #ifdef USE_ENTITY_ICON @@ -499,9 +499,9 @@ void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const { } size.add_float(1, this->min_mireds); size.add_float(1, this->max_mireds); - if (!this->effects.empty()) { - for (const auto &it : this->effects) { - size.add_length_force(1, it.size()); + if (!this->effects->empty()) { + for (const char *it : *this->effects) { + size.add_length_force(1, strlen(it)); } } size.add_bool(1, this->disabled_by_default); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index b19e92d4ff..93ece74d85 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -793,7 +793,7 @@ class ListEntitiesLightResponse final : public InfoResponseProtoMessage { const light::ColorModeMask *supported_color_modes{}; float min_mireds{0.0f}; float max_mireds{0.0f}; - std::vector effects{}; + const FixedVector *effects{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index ea752ba3ba..a985e052ac 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -924,7 +924,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { } dump_field(out, "min_mireds", this->min_mireds); dump_field(out, "max_mireds", this->max_mireds); - for (const auto &it : this->effects) { + for (const auto &it : *this->effects) { dump_field(out, "effects", it, 4); } dump_field(out, "disabled_by_default", this->disabled_by_default); From c30b92019347b681698551857e06eddac406a1d7 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Tue, 25 Nov 2025 13:48:32 +0100 Subject: [PATCH 0374/1145] [nextion] Do not set alternative baud rate when not specified or `<= 0` (#12097) --- esphome/components/nextion/nextion_upload_arduino.cpp | 3 +++ esphome/components/nextion/nextion_upload_idf.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/esphome/components/nextion/nextion_upload_arduino.cpp b/esphome/components/nextion/nextion_upload_arduino.cpp index b4d217d7aa..baea938729 100644 --- a/esphome/components/nextion/nextion_upload_arduino.cpp +++ b/esphome/components/nextion/nextion_upload_arduino.cpp @@ -174,6 +174,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { // Check if baud rate is supported this->original_baud_rate_ = this->parent_->get_baud_rate(); + if (baud_rate <= 0) { + baud_rate = this->original_baud_rate_; + } ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); // Define the configuration for the HTTP client diff --git a/esphome/components/nextion/nextion_upload_idf.cpp b/esphome/components/nextion/nextion_upload_idf.cpp index 3b0d65643d..942e3dd6c3 100644 --- a/esphome/components/nextion/nextion_upload_idf.cpp +++ b/esphome/components/nextion/nextion_upload_idf.cpp @@ -177,6 +177,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { // Check if baud rate is supported this->original_baud_rate_ = this->parent_->get_baud_rate(); + if (baud_rate <= 0) { + baud_rate = this->original_baud_rate_; + } ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); // Define the configuration for the HTTP client From cdf27f144766759efc1bb809a5f7631cc8a7cf85 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:14:53 -0500 Subject: [PATCH 0375/1145] [esp32] Fix platformio flash size print (#12099) Co-authored-by: J. Nick Koston --- esphome/components/esp32/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 59c6029334..d372af3e6a 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -854,6 +854,10 @@ def _configure_lwip_max_sockets(conf: dict) -> None: async def to_code(config): cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE]) + cg.add_platformio_option( + "board_upload.maximum_size", + int(config[CONF_FLASH_SIZE].removesuffix("MB")) * 1024 * 1024, + ) cg.set_cpp_standard("gnu++20") cg.add_build_flag("-DUSE_ESP32") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) From a571033b43f3418c54f42ef078b357b90b6f8bed Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Nov 2025 10:30:01 -0600 Subject: [PATCH 0376/1145] [script] Fix script.wait hanging when triggered from on_boot (#12102) --- esphome/components/script/script.h | 7 +- .../fixtures/script_wait_on_boot.yaml | 54 ++++++++ tests/integration/test_script_wait_on_boot.py | 130 ++++++++++++++++++ 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 tests/integration/fixtures/script_wait_on_boot.yaml create mode 100644 tests/integration/test_script_wait_on_boot.py diff --git a/esphome/components/script/script.h b/esphome/components/script/script.h index d60ed657f7..3a0823f3cc 100644 --- a/esphome/components/script/script.h +++ b/esphome/components/script/script.h @@ -278,7 +278,12 @@ template class ScriptWaitAction : public Action, void setup() override { // Start with loop disabled - only enable when there's work to do - this->disable_loop(); + // IMPORTANT: Only disable if num_running_ is 0, otherwise play_complex() was already + // called before our setup() (e.g., from on_boot trigger at same priority level) + // and we must not undo its enable_loop() call + if (this->num_running_ == 0) { + this->disable_loop(); + } } void play_complex(const Ts &...x) override { diff --git a/tests/integration/fixtures/script_wait_on_boot.yaml b/tests/integration/fixtures/script_wait_on_boot.yaml new file mode 100644 index 0000000000..8736b02294 --- /dev/null +++ b/tests/integration/fixtures/script_wait_on_boot.yaml @@ -0,0 +1,54 @@ +esphome: + name: test-script-wait-on-boot + on_boot: + # Use default priority (600.0) which is same as ScriptWaitAction's setup priority + # This tests the race condition where on_boot runs before ScriptWaitAction::setup() + then: + - logger.log: "=== on_boot: Starting boot sequence ===" + - script.execute: show_start_page + - script.wait: show_start_page + - logger.log: "=== on_boot: First script completed, starting second ===" + - script.execute: flip_thru_pages + - script.wait: flip_thru_pages + - logger.log: "=== on_boot: All boot scripts completed successfully ===" + +host: + +api: + actions: + # Manual trigger for additional testing + - action: test_script_wait + then: + - logger.log: "=== Manual test: Starting ===" + - script.execute: show_start_page + - script.wait: show_start_page + - logger.log: "=== Manual test: First script completed ===" + - script.execute: flip_thru_pages + - script.wait: flip_thru_pages + - logger.log: "=== Manual test: All completed ===" + +logger: + level: DEBUG + +script: + # First script - simulates display initialization + - id: show_start_page + mode: single + then: + - logger.log: "show_start_page: Starting" + - delay: 100ms + - logger.log: "show_start_page: After delay 1" + - delay: 100ms + - logger.log: "show_start_page: Completed" + + # Second script - simulates page flip sequence + - id: flip_thru_pages + mode: single + then: + - logger.log: "flip_thru_pages: Starting" + - delay: 50ms + - logger.log: "flip_thru_pages: Page 1" + - delay: 50ms + - logger.log: "flip_thru_pages: Page 2" + - delay: 50ms + - logger.log: "flip_thru_pages: Completed" diff --git a/tests/integration/test_script_wait_on_boot.py b/tests/integration/test_script_wait_on_boot.py new file mode 100644 index 0000000000..478090f782 --- /dev/null +++ b/tests/integration/test_script_wait_on_boot.py @@ -0,0 +1,130 @@ +"""Integration test for script.wait during on_boot (issue #12043). + +This test verifies that script.wait works correctly when triggered from on_boot. +The issue was that ScriptWaitAction::setup() unconditionally disabled the loop, +even if play_complex() had already been called (from an on_boot trigger at the +same priority level) and enabled it. + +The race condition occurs because: +1. on_boot's default priority is 600.0 (setup_priority::DATA) +2. ScriptWaitAction's default setup priority is also DATA (600.0) +3. When they have the same priority, if on_boot runs first and triggers a script, + ScriptWaitAction::play_complex() enables the loop +4. Then ScriptWaitAction::setup() runs and unconditionally disables the loop +5. The wait never completes because the loop is disabled + +The fix adds a conditional check (like WaitUntilAction has) to only disable the +loop in setup() if num_running_ is 0. +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_script_wait_on_boot( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that script.wait works correctly when triggered from on_boot. + + This reproduces issue #12043 where script.wait would hang forever when + triggered from on_boot due to a race condition in ScriptWaitAction::setup(). + """ + test_complete = asyncio.Event() + + # Track progress through the boot sequence + boot_started = False + first_script_started = False + first_script_completed = False + first_wait_returned = False + second_script_started = False + second_script_completed = False + all_completed = False + + # Patterns for boot sequence logs + boot_start_pattern = re.compile(r"on_boot: Starting boot sequence") + show_start_pattern = re.compile(r"show_start_page: Starting") + show_complete_pattern = re.compile(r"show_start_page: Completed") + first_wait_pattern = re.compile(r"on_boot: First script completed") + flip_start_pattern = re.compile(r"flip_thru_pages: Starting") + flip_complete_pattern = re.compile(r"flip_thru_pages: Completed") + all_complete_pattern = re.compile(r"on_boot: All boot scripts completed") + + def check_output(line: str) -> None: + """Check log output for boot sequence progress.""" + nonlocal boot_started, first_script_started, first_script_completed + nonlocal first_wait_returned, second_script_started, second_script_completed + nonlocal all_completed + + if boot_start_pattern.search(line): + boot_started = True + elif show_start_pattern.search(line): + first_script_started = True + elif show_complete_pattern.search(line): + first_script_completed = True + elif first_wait_pattern.search(line): + first_wait_returned = True + elif flip_start_pattern.search(line): + second_script_started = True + elif flip_complete_pattern.search(line): + second_script_completed = True + elif all_complete_pattern.search(line): + all_completed = True + test_complete.set() + + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "test-script-wait-on-boot" + + # Wait for on_boot sequence to complete + # The boot sequence should complete automatically + # Timeout is generous to allow for delays in the scripts + try: + await asyncio.wait_for(test_complete.wait(), timeout=5.0) + except TimeoutError: + # Build a detailed error message showing where the boot sequence got stuck + progress = [] + if boot_started: + progress.append("boot started") + if first_script_started: + progress.append("show_start_page started") + if first_script_completed: + progress.append("show_start_page completed") + if first_wait_returned: + progress.append("first script.wait returned") + if second_script_started: + progress.append("flip_thru_pages started") + if second_script_completed: + progress.append("flip_thru_pages completed") + + if not first_wait_returned and first_script_completed: + pytest.fail( + f"Test timed out - script.wait hung after show_start_page completed! " + f"This is the issue #12043 bug. Progress: {', '.join(progress)}" + ) + else: + pytest.fail( + f"Test timed out. Progress: {', '.join(progress) if progress else 'none'}" + ) + + # Verify the complete boot sequence executed in order + assert boot_started, "on_boot did not start" + assert first_script_started, "show_start_page did not start" + assert first_script_completed, "show_start_page did not complete" + assert first_wait_returned, "First script.wait did not return" + assert second_script_started, "flip_thru_pages did not start" + assert second_script_completed, "flip_thru_pages did not complete" + assert all_completed, "Boot sequence did not complete" From cf8c2056444bf5b6f02accf6a8480b913d0f1c67 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Nov 2025 12:15:45 -0600 Subject: [PATCH 0377/1145] [core] Reduce flash size by combining set_name() and set_object_id() calls (#11941) --- esphome/core/entity_base.cpp | 6 ++++++ esphome/core/entity_base.h | 3 +++ esphome/core/entity_helpers.py | 6 ++---- .../binary_sensor/test_binary_sensor.py | 2 +- tests/component_tests/button/test_button.py | 2 +- tests/component_tests/text/test_text.py | 2 +- .../text_sensor/test_text_sensor.py | 15 ++++++++++++--- tests/unit_tests/core/test_entity_helpers.py | 13 ++++++++++--- 8 files changed, 36 insertions(+), 13 deletions(-) diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 4883c72cf1..046f99d8cc 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -74,6 +74,12 @@ void EntityBase::set_object_id(const char *object_id) { this->calc_object_id_(); } +void EntityBase::set_name_and_object_id(const char *name, const char *object_id) { + this->set_name(name); + this->object_id_c_str_ = object_id; + this->calc_object_id_(); +} + // Calculate Object ID Hash from Entity Name void EntityBase::calc_object_id_() { this->object_id_hash_ = diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 2b52d66f76..aa9b92877a 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -41,6 +41,9 @@ class EntityBase { std::string get_object_id() const; void set_object_id(const char *object_id); + // Set both name and object_id in one call (reduces generated code size) + void set_name_and_object_id(const char *name, const char *object_id); + // Get the unique Object ID of this Entity uint32_t get_object_id_hash(); diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index 9b4786f835..f360b4d809 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -84,8 +84,6 @@ async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None: # Get device name for object ID calculation device_name = device_id_obj.id - add(var.set_name(config[CONF_NAME])) - # Calculate base object_id using the same logic as C++ # This must match the C++ behavior in esphome/core/entity_base.cpp base_object_id = get_base_entity_object_id( @@ -97,8 +95,8 @@ async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None: "Entity has empty name, using '%s' as object_id base", base_object_id ) - # Set the object ID - add(var.set_object_id(base_object_id)) + # Set both name and object_id in one call to reduce generated code size + add(var.set_name_and_object_id(config[CONF_NAME], base_object_id)) _LOGGER.debug( "Setting object_id '%s' for entity '%s' on platform '%s'", base_object_id, diff --git a/tests/component_tests/binary_sensor/test_binary_sensor.py b/tests/component_tests/binary_sensor/test_binary_sensor.py index 32d74027ba..86e0705023 100644 --- a/tests/component_tests/binary_sensor/test_binary_sensor.py +++ b/tests/component_tests/binary_sensor/test_binary_sensor.py @@ -29,7 +29,7 @@ def test_binary_sensor_sets_mandatory_fields(generate_main): ) # Then - assert 'bs_1->set_name("test bs1");' in main_cpp + assert 'bs_1->set_name_and_object_id("test bs1", "test_bs1");' in main_cpp assert "bs_1->set_pin(" in main_cpp diff --git a/tests/component_tests/button/test_button.py b/tests/component_tests/button/test_button.py index 512ef42b44..b21665288c 100644 --- a/tests/component_tests/button/test_button.py +++ b/tests/component_tests/button/test_button.py @@ -26,7 +26,7 @@ def test_button_sets_mandatory_fields(generate_main): main_cpp = generate_main("tests/component_tests/button/test_button.yaml") # Then - assert 'wol_1->set_name("wol_test_1");' in main_cpp + assert 'wol_1->set_name_and_object_id("wol_test_1", "wol_test_1");' in main_cpp assert "wol_2->set_macaddr(18, 52, 86, 120, 144, 171);" in main_cpp diff --git a/tests/component_tests/text/test_text.py b/tests/component_tests/text/test_text.py index 99ddd78ee7..bfc3131f6d 100644 --- a/tests/component_tests/text/test_text.py +++ b/tests/component_tests/text/test_text.py @@ -25,7 +25,7 @@ def test_text_sets_mandatory_fields(generate_main): main_cpp = generate_main("tests/component_tests/text/test_text.yaml") # Then - assert 'it_1->set_name("test 1 text");' in main_cpp + assert 'it_1->set_name_and_object_id("test 1 text", "test_1_text");' in main_cpp def test_text_config_value_internal_set(generate_main): diff --git a/tests/component_tests/text_sensor/test_text_sensor.py b/tests/component_tests/text_sensor/test_text_sensor.py index 1c4ef6633d..934ee67cef 100644 --- a/tests/component_tests/text_sensor/test_text_sensor.py +++ b/tests/component_tests/text_sensor/test_text_sensor.py @@ -25,9 +25,18 @@ def test_text_sensor_sets_mandatory_fields(generate_main): main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") # Then - assert 'ts_1->set_name("Template Text Sensor 1");' in main_cpp - assert 'ts_2->set_name("Template Text Sensor 2");' in main_cpp - assert 'ts_3->set_name("Template Text Sensor 3");' in main_cpp + assert ( + 'ts_1->set_name_and_object_id("Template Text Sensor 1", "template_text_sensor_1");' + in main_cpp + ) + assert ( + 'ts_2->set_name_and_object_id("Template Text Sensor 2", "template_text_sensor_2");' + in main_cpp + ) + assert ( + 'ts_3->set_name_and_object_id("Template Text Sensor 3", "template_text_sensor_3");' + in main_cpp + ) def test_text_sensor_config_value_internal_set(generate_main): diff --git a/tests/unit_tests/core/test_entity_helpers.py b/tests/unit_tests/core/test_entity_helpers.py index 9ba5367413..01de0f27f9 100644 --- a/tests/unit_tests/core/test_entity_helpers.py +++ b/tests/unit_tests/core/test_entity_helpers.py @@ -27,8 +27,13 @@ from esphome.helpers import sanitize, snake_case from .common import load_config_from_fixture -# Pre-compiled regex pattern for extracting object IDs from expressions +# Pre-compiled regex patterns for extracting object IDs from expressions +# Matches both old format: .set_object_id("obj_id") +# and new format: .set_name_and_object_id("name", "obj_id") OBJECT_ID_PATTERN = re.compile(r'\.set_object_id\(["\'](.*?)["\']\)') +COMBINED_PATTERN = re.compile( + r'\.set_name_and_object_id\(["\'].*?["\']\s*,\s*["\'](.*?)["\']\)' +) FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "entity_helpers" @@ -273,8 +278,10 @@ def setup_test_environment() -> Generator[list[str], None, None]: def extract_object_id_from_expressions(expressions: list[str]) -> str | None: """Extract the object ID that was set from the generated expressions.""" for expr in expressions: - # Look for set_object_id calls with regex to handle various formats - # Matches: var.set_object_id("temperature_2") or var.set_object_id('temperature_2') + # First try new combined format: .set_name_and_object_id("name", "obj_id") + if match := COMBINED_PATTERN.search(expr): + return match.group(1) + # Fall back to old format: .set_object_id("obj_id") if match := OBJECT_ID_PATTERN.search(expr): return match.group(1) return None From 8c5985f68a4cc8e14e84b90577e98255ac1a51e1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Nov 2025 12:16:02 -0600 Subject: [PATCH 0378/1145] [web_server] Consolidate turn_on/turn_off handlers to eliminate duplicate lambdas (#12094) --- esphome/components/web_server/web_server.cpp | 65 +++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index cc51463fe7..6bf6524fbc 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -690,8 +690,14 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc } else if (match.method_equals("toggle")) { this->defer([obj]() { obj->toggle().perform(); }); request->send(200); - } else if (match.method_equals("turn_on") || match.method_equals("turn_off")) { - auto call = match.method_equals("turn_on") ? obj->turn_on() : obj->turn_off(); + } else { + bool is_on = match.method_equals("turn_on"); + bool is_off = match.method_equals("turn_off"); + if (!is_on && !is_off) { + request->send(404); + return; + } + auto call = is_on ? obj->turn_on() : obj->turn_off(); parse_int_param_(request, "speed_level", call, &decltype(call)::set_speed); @@ -715,8 +721,6 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc } this->defer([call]() mutable { call.perform(); }); request->send(200); - } else { - request->send(404); } return; } @@ -766,32 +770,35 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa } else if (match.method_equals("toggle")) { this->defer([obj]() { obj->toggle().perform(); }); request->send(200); - } else if (match.method_equals("turn_on")) { - auto call = obj->turn_on(); - - // Parse color parameters - parse_light_param_(request, "brightness", call, &decltype(call)::set_brightness, 255.0f); - parse_light_param_(request, "r", call, &decltype(call)::set_red, 255.0f); - parse_light_param_(request, "g", call, &decltype(call)::set_green, 255.0f); - parse_light_param_(request, "b", call, &decltype(call)::set_blue, 255.0f); - parse_light_param_(request, "white_value", call, &decltype(call)::set_white, 255.0f); - parse_light_param_(request, "color_temp", call, &decltype(call)::set_color_temperature); - - // Parse timing parameters - parse_light_param_uint_(request, "flash", call, &decltype(call)::set_flash_length, 1000); - parse_light_param_uint_(request, "transition", call, &decltype(call)::set_transition_length, 1000); - - parse_string_param_(request, "effect", call, &decltype(call)::set_effect); - - this->defer([call]() mutable { call.perform(); }); - request->send(200); - } else if (match.method_equals("turn_off")) { - auto call = obj->turn_off(); - parse_light_param_uint_(request, "transition", call, &decltype(call)::set_transition_length, 1000); - this->defer([call]() mutable { call.perform(); }); - request->send(200); } else { - request->send(404); + bool is_on = match.method_equals("turn_on"); + bool is_off = match.method_equals("turn_off"); + if (!is_on && !is_off) { + request->send(404); + return; + } + auto call = is_on ? obj->turn_on() : obj->turn_off(); + + if (is_on) { + // Parse color parameters + parse_light_param_(request, "brightness", call, &decltype(call)::set_brightness, 255.0f); + parse_light_param_(request, "r", call, &decltype(call)::set_red, 255.0f); + parse_light_param_(request, "g", call, &decltype(call)::set_green, 255.0f); + parse_light_param_(request, "b", call, &decltype(call)::set_blue, 255.0f); + parse_light_param_(request, "white_value", call, &decltype(call)::set_white, 255.0f); + parse_light_param_(request, "color_temp", call, &decltype(call)::set_color_temperature); + + // Parse timing parameters + parse_light_param_uint_(request, "flash", call, &decltype(call)::set_flash_length, 1000); + } + parse_light_param_uint_(request, "transition", call, &decltype(call)::set_transition_length, 1000); + + if (is_on) { + parse_string_param_(request, "effect", call, &decltype(call)::set_effect); + } + + this->defer([call]() mutable { call.perform(); }); + request->send(200); } return; } From 310693467819523eea3a53b2a1681db50aec3e36 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Nov 2025 12:16:27 -0600 Subject: [PATCH 0379/1145] [esp32_ble] Optimize name storage to reduce RAM and eliminate heap allocations (#12071) --- esphome/components/esp32_ble/ble.cpp | 31 ++++++++++++++++++---------- esphome/components/esp32_ble/ble.h | 6 ++---- esphome/core/application.h | 8 ++++--- esphome/core/helpers.cpp | 10 ++++++--- esphome/core/helpers.h | 11 ++++++++++ 5 files changed, 45 insertions(+), 21 deletions(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index d0bfb6f843..a0ed9ee90c 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -256,29 +256,38 @@ bool ESP32BLE::ble_setup_() { } #endif - std::string name; - if (this->name_.has_value()) { - name = this->name_.value(); + const char *device_name; + std::string name_with_suffix; + + if (this->name_ != nullptr) { if (App.is_name_add_mac_suffix_enabled()) { + // MAC address length: 12 hex chars + null terminator + constexpr size_t mac_address_len = 13; // MAC address suffix length (last 6 characters of 12-char MAC address string) constexpr size_t mac_address_suffix_len = 6; - const std::string mac_addr = get_mac_address(); - const char *mac_suffix_ptr = mac_addr.c_str() + mac_address_suffix_len; - name = make_name_with_suffix(name, '-', mac_suffix_ptr, mac_address_suffix_len); + char mac_addr[mac_address_len]; + get_mac_address_into_buffer(mac_addr); + const char *mac_suffix_ptr = mac_addr + mac_address_suffix_len; + name_with_suffix = + make_name_with_suffix(this->name_, strlen(this->name_), '-', mac_suffix_ptr, mac_address_suffix_len); + device_name = name_with_suffix.c_str(); + } else { + device_name = this->name_; } } else { - name = App.get_name(); - if (name.length() > 20) { + name_with_suffix = App.get_name(); + if (name_with_suffix.length() > 20) { if (App.is_name_add_mac_suffix_enabled()) { // Keep first 13 chars and last 7 chars (MAC suffix), remove middle - name.erase(13, name.length() - 20); + name_with_suffix.erase(13, name_with_suffix.length() - 20); } else { - name.resize(20); + name_with_suffix.resize(20); } } + device_name = name_with_suffix.c_str(); } - err = esp_ble_gap_set_device_name(name.c_str()); + err = esp_ble_gap_set_device_name(device_name); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gap_set_device_name failed: %d", err); return false; diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 2fb60bb822..393ec2e911 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -112,7 +112,7 @@ class ESP32BLE : public Component { void loop() override; void dump_config() override; float get_setup_priority() const override; - void set_name(const std::string &name) { this->name_ = name; } + void set_name(const char *name) { this->name_ = name; } #ifdef USE_ESP32_BLE_ADVERTISING void advertising_start(); @@ -191,13 +191,11 @@ class ESP32BLE : public Component { esphome::LockFreeQueue ble_events_; esphome::EventPool ble_event_pool_; - // optional (typically 16+ bytes on 32-bit, aligned to 4 bytes) - optional name_; - // 4-byte aligned members #ifdef USE_ESP32_BLE_ADVERTISING BLEAdvertising *advertising_{}; // 4 bytes (pointer) #endif + const char *name_{nullptr}; // 4 bytes (pointer to string literal in flash) esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; // 4 bytes (enum) uint32_t advertising_cycle_time_{}; // 4 bytes diff --git a/esphome/core/application.h b/esphome/core/application.h index dae44d8902..14e800342e 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -105,11 +105,13 @@ class Application { arch_init(); this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { + // MAC address length: 12 hex chars + null terminator + constexpr size_t mac_address_len = 13; // MAC address suffix length (last 6 characters of 12-char MAC address string) constexpr size_t mac_address_suffix_len = 6; - const std::string mac_addr = get_mac_address(); - // Use pointer + offset to avoid substr() allocation - const char *mac_suffix_ptr = mac_addr.c_str() + mac_address_suffix_len; + char mac_addr[mac_address_len]; + get_mac_address_into_buffer(mac_addr); + const char *mac_suffix_ptr = mac_addr + mac_address_suffix_len; this->name_ = make_name_with_suffix(name, '-', mac_suffix_ptr, mac_address_suffix_len); if (!friendly_name.empty()) { this->friendly_name_ = make_name_with_suffix(friendly_name, ' ', mac_suffix_ptr, mac_address_suffix_len); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 50af71649c..1f675563c7 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -238,9 +238,9 @@ std::string str_sprintf(const char *fmt, ...) { // Maximum size for name with suffix: 120 (max friendly name) + 1 (separator) + 6 (MAC suffix) + 1 (null term) static constexpr size_t MAX_NAME_WITH_SUFFIX_SIZE = 128; -std::string make_name_with_suffix(const std::string &name, char sep, const char *suffix_ptr, size_t suffix_len) { +std::string make_name_with_suffix(const char *name, size_t name_len, char sep, const char *suffix_ptr, + size_t suffix_len) { char buffer[MAX_NAME_WITH_SUFFIX_SIZE]; - size_t name_len = name.size(); size_t total_len = name_len + 1 + suffix_len; // Silently truncate if needed: prioritize keeping the full suffix @@ -252,13 +252,17 @@ std::string make_name_with_suffix(const std::string &name, char sep, const char total_len = name_len + 1 + suffix_len; } - memcpy(buffer, name.c_str(), name_len); + memcpy(buffer, name, name_len); buffer[name_len] = sep; memcpy(buffer + name_len + 1, suffix_ptr, suffix_len); buffer[total_len] = '\0'; return std::string(buffer, total_len); } +std::string make_name_with_suffix(const std::string &name, char sep, const char *suffix_ptr, size_t suffix_len) { + return make_name_with_suffix(name.c_str(), name.size(), sep, suffix_ptr, suffix_len); +} + // Parsing & formatting size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index d8c1f4647e..a43c55e06b 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -512,6 +512,17 @@ std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, . /// @return The concatenated string: name + sep + suffix std::string make_name_with_suffix(const std::string &name, char sep, const char *suffix_ptr, size_t suffix_len); +/// Optimized string concatenation: name + separator + suffix (const char* overload) +/// Uses a fixed stack buffer to avoid heap allocations. +/// @param name The base name string +/// @param name_len Length of the name +/// @param sep Single character separator +/// @param suffix_ptr Pointer to the suffix characters +/// @param suffix_len Length of the suffix +/// @return The concatenated string: name + sep + suffix +std::string make_name_with_suffix(const char *name, size_t name_len, char sep, const char *suffix_ptr, + size_t suffix_len); + ///@} /// @name Parsing & formatting From 6ca0cd1e8b3f6b1622acfdcd0b8ed6a4c84d1a0b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Nov 2025 12:16:48 -0600 Subject: [PATCH 0380/1145] [ltr390] Simplify mode tracking with bitmask instead of vector/function (#12093) --- esphome/components/ltr390/ltr390.cpp | 55 ++++++++++++++-------------- esphome/components/ltr390/ltr390.h | 14 +++---- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/esphome/components/ltr390/ltr390.cpp b/esphome/components/ltr390/ltr390.cpp index c1885dcb6f..ba4a7ea5cb 100644 --- a/esphome/components/ltr390/ltr390.cpp +++ b/esphome/components/ltr390/ltr390.cpp @@ -104,12 +104,17 @@ void LTR390Component::read_uvs_() { } } -void LTR390Component::read_mode_(int mode_index) { - // Set mode - LTR390MODE mode = std::get<0>(this->mode_funcs_[mode_index]); - +void LTR390Component::standby_() { std::bitset<8> ctrl = this->reg(LTR390_MAIN_CTRL).get(); - ctrl[LTR390_CTRL_MODE] = mode; + ctrl[LTR390_CTRL_EN] = false; + this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong(); + this->reading_ = false; +} + +void LTR390Component::read_mode_(LTR390MODE mode) { + // Set mode + std::bitset<8> ctrl = this->reg(LTR390_MAIN_CTRL).get(); + ctrl[LTR390_CTRL_MODE] = (mode == LTR390_MODE_UVS); ctrl[LTR390_CTRL_EN] = true; this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong(); @@ -129,21 +134,18 @@ void LTR390Component::read_mode_(int mode_index) { } // After the sensor integration time do the following - this->set_timeout(int_time + LTR390_WAKEUP_TIME + LTR390_SETTLE_TIME, [this, mode_index]() { - // Read from the sensor - std::get<1>(this->mode_funcs_[mode_index])(); - - // If there are more modes to read then begin the next - // otherwise stop - if (mode_index + 1 < (int) this->mode_funcs_.size()) { - this->read_mode_(mode_index + 1); + this->set_timeout(int_time + LTR390_WAKEUP_TIME + LTR390_SETTLE_TIME, [this, mode]() { + // Read from the sensor and continue to next mode or standby + if (mode == LTR390_MODE_ALS) { + this->read_als_(); + if (this->enabled_modes_ & ENABLED_MODE_UVS) { + this->read_mode_(LTR390_MODE_UVS); + return; + } } else { - // put sensor in standby - std::bitset<8> ctrl = this->reg(LTR390_MAIN_CTRL).get(); - ctrl[LTR390_CTRL_EN] = false; - this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong(); - this->reading_ = false; + this->read_uvs_(); } + this->standby_(); }); } @@ -172,14 +174,12 @@ void LTR390Component::setup() { // Set sensor read state this->reading_ = false; - // If we need the light sensor then add to the list + // Determine which modes are enabled based on configured sensors if (this->light_sensor_ != nullptr || this->als_sensor_ != nullptr) { - this->mode_funcs_.emplace_back(LTR390_MODE_ALS, std::bind(<R390Component::read_als_, this)); + this->enabled_modes_ |= ENABLED_MODE_ALS; } - - // If we need the UV sensor then add to the list if (this->uvi_sensor_ != nullptr || this->uv_sensor_ != nullptr) { - this->mode_funcs_.emplace_back(LTR390_MODE_UVS, std::bind(<R390Component::read_uvs_, this)); + this->enabled_modes_ |= ENABLED_MODE_UVS; } } @@ -195,10 +195,11 @@ void LTR390Component::dump_config() { } void LTR390Component::update() { - if (!this->reading_ && !mode_funcs_.empty()) { - this->reading_ = true; - this->read_mode_(0); - } + if (this->reading_ || this->enabled_modes_ == 0) + return; + + this->reading_ = true; + this->read_mode_((this->enabled_modes_ & ENABLED_MODE_ALS) ? LTR390_MODE_ALS : LTR390_MODE_UVS); } } // namespace ltr390 diff --git a/esphome/components/ltr390/ltr390.h b/esphome/components/ltr390/ltr390.h index 7db73d68ff..47884b9166 100644 --- a/esphome/components/ltr390/ltr390.h +++ b/esphome/components/ltr390/ltr390.h @@ -1,7 +1,5 @@ #pragma once -#include -#include #include "esphome/components/i2c/i2c.h" #include "esphome/components/sensor/sensor.h" #include "esphome/core/component.h" @@ -60,17 +58,19 @@ class LTR390Component : public PollingComponent, public i2c::I2CDevice { void set_uv_sensor(sensor::Sensor *uv_sensor) { this->uv_sensor_ = uv_sensor; } protected: + static constexpr uint8_t ENABLED_MODE_ALS = 1 << 0; + static constexpr uint8_t ENABLED_MODE_UVS = 1 << 1; + optional read_sensor_data_(LTR390MODE mode); void read_als_(); void read_uvs_(); - void read_mode_(int mode_index); + void read_mode_(LTR390MODE mode); + void standby_(); - bool reading_; - - // a list of modes and corresponding read functions - std::vector>> mode_funcs_; + bool reading_{false}; + uint8_t enabled_modes_{0}; LTR390GAIN gain_als_; LTR390GAIN gain_uv_; From dec323e786314b9fc1d3cdddd8c8153ab237e490 Mon Sep 17 00:00:00 2001 From: Nikolai Ryzhkov Date: Tue, 25 Nov 2025 19:27:35 +0100 Subject: [PATCH 0381/1145] [sht4x] Read and store a serial number of SHT4x sensors (#12089) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: J. Nick Koston --- esphome/components/sht4x/sht4x.cpp | 23 ++++++++++++++++++++++- esphome/components/sht4x/sht4x.h | 2 ++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/esphome/components/sht4x/sht4x.cpp b/esphome/components/sht4x/sht4x.cpp index 617b19ef3e..9d29746f0b 100644 --- a/esphome/components/sht4x/sht4x.cpp +++ b/esphome/components/sht4x/sht4x.cpp @@ -7,6 +7,7 @@ namespace sht4x { static const char *const TAG = "sht4x"; static const uint8_t MEASURECOMMANDS[] = {0xFD, 0xF6, 0xE0}; +static const uint8_t SERIAL_NUMBER_COMMAND = 0x89; void SHT4XComponent::start_heater_() { uint8_t cmd[] = {MEASURECOMMANDS[this->heater_command_]}; @@ -17,6 +18,17 @@ void SHT4XComponent::start_heater_() { } } +void SHT4XComponent::read_serial_number_() { + uint16_t buffer[2]; + if (!this->get_8bit_register(SERIAL_NUMBER_COMMAND, buffer, 2, 1)) { + ESP_LOGE(TAG, "Get serial number failed"); + this->serial_number_ = 0; + return; + } + this->serial_number_ = (uint32_t(buffer[0]) << 16) | (uint32_t(buffer[1])); + ESP_LOGD(TAG, "Serial number: %08" PRIx32, this->serial_number_); +} + void SHT4XComponent::setup() { auto err = this->write(nullptr, 0); if (err != i2c::ERROR_OK) { @@ -24,6 +36,8 @@ void SHT4XComponent::setup() { return; } + this->read_serial_number_(); + if (std::isfinite(this->duty_cycle_) && this->duty_cycle_ > 0.0f) { uint32_t heater_interval = static_cast(static_cast(this->heater_time_) / this->duty_cycle_); ESP_LOGD(TAG, "Heater interval: %" PRIu32, heater_interval); @@ -54,11 +68,18 @@ void SHT4XComponent::setup() { } void SHT4XComponent::dump_config() { - ESP_LOGCONFIG(TAG, "SHT4x:"); + ESP_LOGCONFIG(TAG, + "SHT4x:\n" + " Serial number: %08" PRIx32, + this->serial_number_); + LOG_I2C_DEVICE(this); if (this->is_failed()) { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); } + if (this->serial_number_ == 0) { + ESP_LOGW(TAG, "Get serial number failed"); + } } void SHT4XComponent::update() { diff --git a/esphome/components/sht4x/sht4x.h b/esphome/components/sht4x/sht4x.h index accc7323be..aec0f3d7f8 100644 --- a/esphome/components/sht4x/sht4x.h +++ b/esphome/components/sht4x/sht4x.h @@ -36,7 +36,9 @@ class SHT4XComponent : public PollingComponent, public sensirion_common::Sensiri float duty_cycle_; void start_heater_(); + void read_serial_number_(); uint8_t heater_command_; + uint32_t serial_number_; sensor::Sensor *temp_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; From b6be5e3eda156568c114089b22a9ba1e989a6f12 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 26 Nov 2025 05:06:42 +1000 Subject: [PATCH 0382/1145] [lvgl] Allow multiple widgets per grid cell (#12091) --- esphome/components/lvgl/layout.py | 9 ++++++++- tests/components/lvgl/lvgl-package.yaml | 5 +++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/esphome/components/lvgl/layout.py b/esphome/components/lvgl/layout.py index a6aa816fda..caa503ef0d 100644 --- a/esphome/components/lvgl/layout.py +++ b/esphome/components/lvgl/layout.py @@ -36,6 +36,8 @@ from .defines import ( ) from .lv_validation import padding, size +CONF_MULTIPLE_WIDGETS_PER_CELL = "multiple_widgets_per_cell" + cell_alignments = LV_CELL_ALIGNMENTS.one_of grid_alignments = LV_GRID_ALIGNMENTS.one_of flex_alignments = LV_FLEX_ALIGNMENTS.one_of @@ -220,6 +222,7 @@ class GridLayout(Layout): cv.Optional(CONF_GRID_ROW_ALIGN): grid_alignments, cv.Optional(CONF_PAD_ROW): padding, cv.Optional(CONF_PAD_COLUMN): padding, + cv.Optional(CONF_MULTIPLE_WIDGETS_PER_CELL, default=False): cv.boolean, }, { cv.Optional(CONF_GRID_CELL_ROW_POS): cv.positive_int, @@ -263,6 +266,7 @@ class GridLayout(Layout): # should be guaranteed to be a dict at this point assert isinstance(layout, dict) assert layout.get(CONF_TYPE).lower() == TYPE_GRID + allow_multiple = layout.get(CONF_MULTIPLE_WIDGETS_PER_CELL, False) rows = len(layout[CONF_GRID_ROWS]) columns = len(layout[CONF_GRID_COLUMNS]) used_cells = [[None] * columns for _ in range(rows)] @@ -299,7 +303,10 @@ class GridLayout(Layout): f"exceeds grid size {rows}x{columns}", [CONF_WIDGETS, index], ) - if used_cells[row + i][column + j] is not None: + if ( + not allow_multiple + and used_cells[row + i][column + j] is not None + ): raise cv.Invalid( f"Cell span {row + i}/{column + j} already occupied by widget at index {used_cells[row + i][column + j]}", [CONF_WIDGETS, index], diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index d54aef8b4a..708dfa2cb1 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -893,6 +893,7 @@ lvgl: grid_columns: [40, fr(1), fr(1)] pad_row: 6px pad_column: 0 + multiple_widgets_per_cell: true widgets: - image: grid_cell_row_pos: 0 @@ -917,6 +918,10 @@ lvgl: grid_cell_row_pos: 1 grid_cell_column_pos: 0 text: "Grid cell 1/0" + - label: + grid_cell_row_pos: 1 + grid_cell_column_pos: 0 + text: "Duplicate for 1/0" - label: styles: bdr_style grid_cell_row_pos: 1 From 70df4ecaa93d7c6bacfada4980df9034dfecc06d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 13:35:40 -0600 Subject: [PATCH 0383/1145] Bump actions/setup-python from 6.0.0 to 6.1.0 (#12106) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-api-proto.yml | 2 +- .github/workflows/ci-clang-tidy-hash.yml | 2 +- .github/workflows/ci-docker.yml | 2 +- .github/workflows/ci.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- .github/workflows/sync-device-classes.yml | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci-api-proto.yml b/.github/workflows/ci-api-proto.yml index b377ca76d8..2bee5ed211 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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: "3.11" diff --git a/.github/workflows/ci-clang-tidy-hash.yml b/.github/workflows/ci-clang-tidy-hash.yml index 9556b99015..1826ed27cf 100644 --- a/.github/workflows/ci-clang-tidy-hash.yml +++ b/.github/workflows/ci-clang-tidy-hash.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: "3.11" diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 5287d92b10..c76d9cf2a5 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -45,7 +45,7 @@ jobs: steps: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: "3.11" - name: Set up Docker Buildx diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c2fab0912..9cfc02d5cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment @@ -240,7 +240,7 @@ jobs: uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python 3.13 id: python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: "3.13" - name: Restore Python virtual environment diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 497ecd29e7..1ff810d869 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,7 +62,7 @@ jobs: steps: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: "3.x" - name: Build @@ -94,7 +94,7 @@ jobs: steps: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: "3.11" diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index 8f95fa68ee..baaa29df2c 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@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: 3.13 From ae60b5e6a133b4df3266831078fc1df3976fa314 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:27:49 -0600 Subject: [PATCH 0384/1145] Bump actions/setup-python from 6.0.0 to 6.1.0 in /.github/actions/restore-python (#12108) 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 f314e79ad9..c4ac3d1a9e 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@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ inputs.python-version }} - name: Restore Python virtual environment From 50bdcdee0c851810940652913d12dfaa38330e6e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 26 Nov 2025 12:39:41 +1300 Subject: [PATCH 0385/1145] Add developer-breaking-change labelling (#12095) --- .github/PULL_REQUEST_TEMPLATE.md | 1 + .github/workflows/auto-label-pr.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 28437e6302..41dd02458e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,6 +7,7 @@ - [ ] Bugfix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Developer breaking change (an API change that could break external components) - [ ] Code quality improvements to existing code or addition of tests - [ ] Other diff --git a/.github/workflows/auto-label-pr.yml b/.github/workflows/auto-label-pr.yml index 998f3315c6..d09072d814 100644 --- a/.github/workflows/auto-label-pr.yml +++ b/.github/workflows/auto-label-pr.yml @@ -68,6 +68,7 @@ jobs: 'bugfix', 'new-feature', 'breaking-change', + 'developer-breaking-change', 'code-quality' ]; @@ -367,6 +368,7 @@ jobs: { pattern: /- \[x\] Bugfix \(non-breaking change which fixes an issue\)/i, label: 'bugfix' }, { pattern: /- \[x\] New feature \(non-breaking change which adds functionality\)/i, label: 'new-feature' }, { pattern: /- \[x\] Breaking change \(fix or feature that would cause existing functionality to not work as expected\)/i, label: 'breaking-change' }, + { pattern: /- \[x\] Developer breaking change \(an API change that could break external components\)/i, label: 'developer-breaking-change' }, { pattern: /- \[x\] Code quality improvements to existing code or addition of tests/i, label: 'code-quality' } ]; From ffae3501ab00c1fc06dd7d395dee64b95154fe0c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Nov 2025 17:44:50 -0600 Subject: [PATCH 0386/1145] [core] Replace seq<>/gens<> with std::index_sequence for code clarity (#11921) --- esphome/components/api/user_services.h | 10 ++++--- esphome/components/script/script.h | 12 ++++----- esphome/core/automation.h | 36 +++++++++++++++++++------- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/esphome/components/api/user_services.h b/esphome/components/api/user_services.h index 501b702e6b..d9c13c520b 100644 --- a/esphome/components/api/user_services.h +++ b/esphome/components/api/user_services.h @@ -51,13 +51,14 @@ template class UserServiceBase : public UserServiceDescriptor { return false; if (req.args.size() != sizeof...(Ts)) return false; - this->execute_(req.args, typename gens::type()); + this->execute_(req.args, std::make_index_sequence{}); return true; } protected: virtual void execute(Ts... x) = 0; - template void execute_(const ArgsContainer &args, seq type) { + template + void execute_(const ArgsContainer &args, std::index_sequence type) { this->execute((get_execute_arg_value(args[S]))...); } @@ -95,13 +96,14 @@ template class UserServiceDynamic : public UserServiceDescriptor return false; if (req.args.size() != sizeof...(Ts)) return false; - this->execute_(req.args, typename gens::type()); + this->execute_(req.args, std::make_index_sequence{}); return true; } protected: virtual void execute(Ts... x) = 0; - template void execute_(const ArgsContainer &args, seq type) { + template + void execute_(const ArgsContainer &args, std::index_sequence type) { this->execute((get_execute_arg_value(args[S]))...); } diff --git a/esphome/components/script/script.h b/esphome/components/script/script.h index 3a0823f3cc..cd1a084f16 100644 --- a/esphome/components/script/script.h +++ b/esphome/components/script/script.h @@ -46,14 +46,14 @@ template class Script : public ScriptLogger, public Trigger &tuple) { - this->execute_tuple_(tuple, typename gens::type()); + this->execute_tuple_(tuple, std::make_index_sequence{}); } // Internal function to give scripts readable names. void set_name(const LogString *name) { name_ = name; } protected: - template void execute_tuple_(const std::tuple &tuple, seq /*unused*/) { + template void execute_tuple_(const std::tuple &tuple, std::index_sequence /*unused*/) { this->execute(std::get(tuple)...); } @@ -157,7 +157,7 @@ template class QueueingScript : public Script, public Com const size_t queue_capacity = static_cast(this->max_runs_ - 1); auto tuple_ptr = std::move(this->var_queue_[this->queue_front_]); this->queue_front_ = (this->queue_front_ + 1) % queue_capacity; - this->trigger_tuple_(*tuple_ptr, typename gens::type()); + this->trigger_tuple_(*tuple_ptr, std::make_index_sequence{}); } } @@ -174,7 +174,7 @@ template class QueueingScript : public Script, public Com } } - template void trigger_tuple_(const std::tuple &tuple, seq /*unused*/) { + template void trigger_tuple_(const std::tuple &tuple, std::index_sequence /*unused*/) { this->trigger(std::get(tuple)...); } @@ -313,7 +313,7 @@ template class ScriptWaitAction : public Action, // play_next_() can trigger more items to be queued if (!this->param_queue_.empty()) { auto ¶ms = this->param_queue_.front(); - this->play_next_tuple_(params, typename gens::type()); + this->play_next_tuple_(params, std::make_index_sequence{}); this->param_queue_.pop_front(); } else { // Queue is now empty - disable loop until next play_complex @@ -330,7 +330,7 @@ template class ScriptWaitAction : public Action, } protected: - template void play_next_tuple_(const std::tuple &tuple, seq /*unused*/) { + template void play_next_tuple_(const std::tuple &tuple, std::index_sequence /*unused*/) { this->play_next_(std::get(tuple)...); } diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 33e08c9c1c..dacadd35e8 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -11,10 +11,26 @@ namespace esphome { +// C++20 std::index_sequence is now used for tuple unpacking +// Legacy seq<>/gens<> pattern deprecated but kept for backwards compatibility // https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971 -template struct seq {}; // NOLINT -template struct gens : gens {}; // NOLINT -template struct gens<0, S...> { using type = seq; }; // NOLINT +// Remove before 2026.6.0 +// NOLINTBEGIN(readability-identifier-naming) +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +template struct ESPDEPRECATED("Use std::index_sequence instead. Removed in 2026.6.0", "2025.12.0") seq {}; +template +struct ESPDEPRECATED("Use std::make_index_sequence instead. Removed in 2026.6.0", "2025.12.0") gens + : gens {}; +template struct gens<0, S...> { using type = seq; }; + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif +// NOLINTEND(readability-identifier-naming) #define TEMPLATABLE_VALUE_(type, name) \ protected: \ @@ -152,11 +168,11 @@ template class Condition { /// Call check with a tuple of values as parameter. bool check_tuple(const std::tuple &tuple) { - return this->check_tuple_(tuple, typename gens::type()); + return this->check_tuple_(tuple, std::make_index_sequence{}); } protected: - template bool check_tuple_(const std::tuple &tuple, seq /*unused*/) { + template bool check_tuple_(const std::tuple &tuple, std::index_sequence /*unused*/) { return this->check(std::get(tuple)...); } }; @@ -231,11 +247,11 @@ template class Action { } } } - template void play_next_tuple_(const std::tuple &tuple, seq /*unused*/) { + template void play_next_tuple_(const std::tuple &tuple, std::index_sequence /*unused*/) { this->play_next_(std::get(tuple)...); } void play_next_tuple_(const std::tuple &tuple) { - this->play_next_tuple_(tuple, typename gens::type()); + this->play_next_tuple_(tuple, std::make_index_sequence{}); } virtual void stop() {} @@ -277,7 +293,9 @@ template class ActionList { if (this->actions_begin_ != nullptr) this->actions_begin_->play_complex(x...); } - void play_tuple(const std::tuple &tuple) { this->play_tuple_(tuple, typename gens::type()); } + void play_tuple(const std::tuple &tuple) { + this->play_tuple_(tuple, std::make_index_sequence{}); + } void stop() { if (this->actions_begin_ != nullptr) this->actions_begin_->stop_complex(); @@ -298,7 +316,7 @@ template class ActionList { } protected: - template void play_tuple_(const std::tuple &tuple, seq /*unused*/) { + template void play_tuple_(const std::tuple &tuple, std::index_sequence /*unused*/) { this->play(std::get(tuple)...); } From bda17180df0c9ca735d25dc52def20b83b2465f6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Nov 2025 17:48:08 -0600 Subject: [PATCH 0387/1145] [core] Deduplicate identical stateless lambdas to reduce flash usage (#11918) --- esphome/cpp_generator.py | 177 ++++++++++++++- tests/component_tests/text/test_text.py | 19 +- tests/unit_tests/test_lambda_dedup.py | 286 ++++++++++++++++++++++++ 3 files changed, 471 insertions(+), 11 deletions(-) create mode 100644 tests/unit_tests/test_lambda_dedup.py diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 6f1af01a5b..4f91696ca1 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -19,11 +19,21 @@ from esphome.core import ( TimePeriodNanoseconds, TimePeriodSeconds, ) +from esphome.coroutine import CoroPriority, coroutine_with_priority from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last from esphome.types import Expression, SafeExpType, TemplateArgsType from esphome.util import OrderedDict from esphome.yaml_util import ESPHomeDataBase +# Keys for lambda deduplication storage in CORE.data +_KEY_LAMBDA_DEDUP = "lambda_dedup" +_KEY_LAMBDA_DEDUP_DECLARATIONS = "lambda_dedup_declarations" + +# Regex patterns for static variable detection (compiled once) +_RE_CPP_SINGLE_LINE_COMMENT = re.compile(r"//.*?$", re.MULTILINE) +_RE_CPP_MULTI_LINE_COMMENT = re.compile(r"/\*.*?\*/", re.DOTALL) +_RE_STATIC_VARIABLE = re.compile(r"\bstatic\s+(?!cast|assert|pointer_cast)\w+\s+\w+") + class RawExpression(Expression): __slots__ = ("text",) @@ -188,7 +198,7 @@ class LambdaExpression(Expression): def __init__( self, parts, parameters, capture: str = "=", return_type=None, source=None - ): + ) -> None: self.parts = parts if not isinstance(parameters, ParameterListExpression): parameters = ParameterListExpression(*parameters) @@ -197,16 +207,21 @@ class LambdaExpression(Expression): self.capture = capture self.return_type = safe_exp(return_type) if return_type is not None else None - def __str__(self): + def format_body(self) -> str: + """Format the lambda body with source directive and content.""" + body = "" + if self.source is not None: + body += f"{self.source.as_line_directive}\n" + body += self.content + return body + + def __str__(self) -> str: # Stateless lambdas (empty capture) implicitly convert to function pointers # when assigned to function pointer types - no unary + needed cpp = f"[{self.capture}]({self.parameters})" if self.return_type is not None: cpp += f" -> {self.return_type}" - cpp += " {\n" - if self.source is not None: - cpp += f"{self.source.as_line_directive}\n" - cpp += f"{self.content}\n}}" + cpp += f" {{\n{self.format_body()}\n}}" return indent_all_but_first_and_last(cpp) @property @@ -214,6 +229,37 @@ class LambdaExpression(Expression): return "".join(str(part) for part in self.parts) +class SharedFunctionLambdaExpression(LambdaExpression): + """A lambda expression that references a shared deduplicated function. + + This class wraps a function pointer but maintains the LambdaExpression + interface so calling code works unchanged. + """ + + __slots__ = ("_func_name",) + + def __init__( + self, + func_name: str, + parameters: TemplateArgsType, + return_type: SafeExpType | None = None, + ) -> None: + # Initialize parent with empty parts since we're just a function reference + super().__init__( + [], parameters, capture="", return_type=return_type, source=None + ) + self._func_name = func_name + + def __str__(self) -> str: + # Just return the function name - it's already a function pointer + return self._func_name + + @property + def content(self) -> str: + # No content, just a function reference + return "" + + # pylint: disable=abstract-method class Literal(Expression, metaclass=abc.ABCMeta): __slots__ = () @@ -583,6 +629,25 @@ def add_global(expression: SafeExpType | Statement, prepend: bool = False): CORE.add_global(expression, prepend) +@coroutine_with_priority(CoroPriority.FINAL) +async def flush_lambda_dedup_declarations() -> None: + """Flush all deferred lambda deduplication declarations to global scope. + + This is a coroutine that runs with FINAL priority (after all components) + to ensure all referenced variables are declared before the shared + lambda functions that use them. + """ + if _KEY_LAMBDA_DEDUP_DECLARATIONS not in CORE.data: + return + + declarations = CORE.data[_KEY_LAMBDA_DEDUP_DECLARATIONS] + for func_declaration in declarations: + add_global(RawStatement(func_declaration)) + + # Clear the list so we don't add them again + CORE.data[_KEY_LAMBDA_DEDUP_DECLARATIONS] = [] + + def add_library(name: str, version: str | None, repository: str | None = None): """Add a library to the codegen library storage. @@ -656,6 +721,93 @@ async def get_variable_with_full_id(id_: ID) -> tuple[ID, "MockObj"]: return await CORE.get_variable_with_full_id(id_) +def _has_static_variables(code: str) -> bool: + """Check if code contains static variable definitions. + + Static variables in lambdas should not be deduplicated because each lambda + instance should have its own static variable state. + + Args: + code: The lambda body code to check + + Returns: + True if code contains static variable definitions + """ + # Remove C++ comments to avoid false positives + # Remove single-line comments (// ...) + code_no_comments = _RE_CPP_SINGLE_LINE_COMMENT.sub("", code) + # Remove multi-line comments (/* ... */) + code_no_comments = _RE_CPP_MULTI_LINE_COMMENT.sub("", code_no_comments) + + # Match: static + # But not: static_cast, static_assert, static_pointer_cast + return bool(_RE_STATIC_VARIABLE.search(code_no_comments)) + + +def _get_shared_lambda_name(lambda_expr: LambdaExpression) -> str | None: + """Get the shared function name for a lambda expression. + + If an identical lambda was already generated, returns the existing shared + function name. Otherwise, creates a new shared function and returns its name. + + Lambdas with static variables are not deduplicated to preserve their + independent state. + + Args: + lambda_expr: The lambda expression to deduplicate + + Returns: + The name of the shared function for this lambda (either existing or newly created), + or None if the lambda should not be deduplicated (e.g., contains static variables) + """ + # Create a unique key from the lambda content, parameters, and return type + content = lambda_expr.content + + # Don't deduplicate lambdas with static variables - each instance needs its own state + if _has_static_variables(content): + return None + param_str = str(lambda_expr.parameters) + return_str = ( + str(lambda_expr.return_type) if lambda_expr.return_type is not None else "void" + ) + + # Use tuple of (content, params, return_type) as key + lambda_key = (content, param_str, return_str) + + # Initialize deduplication storage in CORE.data if not exists + if _KEY_LAMBDA_DEDUP not in CORE.data: + CORE.data[_KEY_LAMBDA_DEDUP] = {} + # Register the flush job to run after all components (FINAL priority) + # This ensures all variables are declared before shared lambda functions + CORE.add_job(flush_lambda_dedup_declarations) + + lambda_cache = CORE.data[_KEY_LAMBDA_DEDUP] + + # Check if we've seen this lambda before + if lambda_key in lambda_cache: + # Return name of existing shared function + return lambda_cache[lambda_key] + + # First occurrence - create a shared function + # Use the cache size as the function number + func_name = f"shared_lambda_{len(lambda_cache)}" + + # Build the function declaration using lambda's body formatting + func_declaration = ( + f"{return_str} {func_name}({param_str}) {{\n{lambda_expr.format_body()}\n}}" + ) + + # Store the declaration to be added later (after all variable declarations) + # We can't add it immediately because it might reference variables not yet declared + CORE.data.setdefault(_KEY_LAMBDA_DEDUP_DECLARATIONS, []).append(func_declaration) + + # Store in cache + lambda_cache[lambda_key] = func_name + + # Return the function name (this is the first occurrence, but we still generate shared function) + return func_name + + async def process_lambda( value: Lambda, parameters: TemplateArgsType, @@ -713,6 +865,19 @@ async def process_lambda( location.line += value.content_offset else: location = None + + # Lambda deduplication: Only deduplicate stateless lambdas (empty capture). + # Stateful lambdas cannot be shared as they capture different contexts. + # Lambdas with static variables are also not deduplicated to preserve independent state. + if capture == "": + lambda_expr = LambdaExpression( + parts, parameters, capture, return_type, location + ) + func_name = _get_shared_lambda_name(lambda_expr) + if func_name is not None: + # Return a shared function reference instead of inline lambda + return SharedFunctionLambdaExpression(func_name, parameters, return_type) + return LambdaExpression(parts, parameters, capture, return_type, location) diff --git a/tests/component_tests/text/test_text.py b/tests/component_tests/text/test_text.py index bfc3131f6d..56dee205b4 100644 --- a/tests/component_tests/text/test_text.py +++ b/tests/component_tests/text/test_text.py @@ -1,4 +1,6 @@ -"""Tests for the binary sensor component.""" +"""Tests for the text component.""" + +from esphome.core import CORE def test_text_is_setup(generate_main): @@ -56,15 +58,22 @@ def test_text_config_value_mode_set(generate_main): assert "it_3->traits.set_mode(text::TEXT_MODE_PASSWORD);" in main_cpp -def test_text_config_lamda_is_set(generate_main): +def test_text_config_lambda_is_set(generate_main) -> None: """ - Test if lambda is set for lambda mode (optimized with stateless lambda) + Test if lambda is set for lambda mode (optimized with stateless lambda and deduplication) """ # Given # When main_cpp = generate_main("tests/component_tests/text/test_text.yaml") + # Get both global and main sections to find the shared lambda definition + full_cpp = CORE.cpp_global_section + main_cpp + # Then - assert "it_4->set_template([]() -> esphome::optional {" in main_cpp - assert 'return std::string{"Hello"};' in main_cpp + # Lambda is deduplicated into a shared function (reference in main section) + assert "it_4->set_template(shared_lambda_" in main_cpp + # Lambda body should be in the code somewhere + assert 'return std::string{"Hello"};' in full_cpp + # Verify the shared lambda function is defined (in global section) + assert "esphome::optional shared_lambda_" in full_cpp diff --git a/tests/unit_tests/test_lambda_dedup.py b/tests/unit_tests/test_lambda_dedup.py new file mode 100644 index 0000000000..bbf5f02e6d --- /dev/null +++ b/tests/unit_tests/test_lambda_dedup.py @@ -0,0 +1,286 @@ +"""Tests for lambda deduplication in cpp_generator.""" + +from esphome import cpp_generator as cg +from esphome.core import CORE + + +def test_deduplicate_identical_lambdas() -> None: + """Test that identical stateless lambdas are deduplicated.""" + # Create two identical lambda expressions + lambda1 = cg.LambdaExpression( + parts=["return 42;"], + parameters=[], + capture="", + return_type=cg.RawExpression("int"), + ) + + lambda2 = cg.LambdaExpression( + parts=["return 42;"], + parameters=[], + capture="", + return_type=cg.RawExpression("int"), + ) + + # Try to deduplicate them + func_name1 = cg._get_shared_lambda_name(lambda1) + func_name2 = cg._get_shared_lambda_name(lambda2) + + # Both should get the same function name (deduplication happened) + assert func_name1 == func_name2 + assert func_name1 == "shared_lambda_0" + + +def test_different_lambdas_not_deduplicated() -> None: + """Test that different lambdas get different function names.""" + lambda1 = cg.LambdaExpression( + parts=["return 42;"], + parameters=[], + capture="", + return_type=cg.RawExpression("int"), + ) + + lambda2 = cg.LambdaExpression( + parts=["return 24;"], # Different content + parameters=[], + capture="", + return_type=cg.RawExpression("int"), + ) + + func_name1 = cg._get_shared_lambda_name(lambda1) + func_name2 = cg._get_shared_lambda_name(lambda2) + + # Different lambdas should get different function names + assert func_name1 != func_name2 + assert func_name1 == "shared_lambda_0" + assert func_name2 == "shared_lambda_1" + + +def test_different_return_types_not_deduplicated() -> None: + """Test that lambdas with different return types are not deduplicated.""" + lambda1 = cg.LambdaExpression( + parts=["return 42;"], + parameters=[], + capture="", + return_type=cg.RawExpression("int"), + ) + + lambda2 = cg.LambdaExpression( + parts=["return 42;"], # Same content + parameters=[], + capture="", + return_type=cg.RawExpression("float"), # Different return type + ) + + func_name1 = cg._get_shared_lambda_name(lambda1) + func_name2 = cg._get_shared_lambda_name(lambda2) + + # Different return types = different functions + assert func_name1 != func_name2 + + +def test_different_parameters_not_deduplicated() -> None: + """Test that lambdas with different parameters are not deduplicated.""" + lambda1 = cg.LambdaExpression( + parts=["return x;"], + parameters=[("int", "x")], + capture="", + return_type=cg.RawExpression("int"), + ) + + lambda2 = cg.LambdaExpression( + parts=["return x;"], # Same content + parameters=[("float", "x")], # Different parameter type + capture="", + return_type=cg.RawExpression("int"), + ) + + func_name1 = cg._get_shared_lambda_name(lambda1) + func_name2 = cg._get_shared_lambda_name(lambda2) + + # Different parameters = different functions + assert func_name1 != func_name2 + + +def test_flush_lambda_dedup_declarations() -> None: + """Test that deferred declarations are properly stored for later flushing.""" + # Create a lambda which will create a deferred declaration + lambda1 = cg.LambdaExpression( + parts=["return 42;"], + parameters=[], + capture="", + return_type=cg.RawExpression("int"), + ) + + cg._get_shared_lambda_name(lambda1) + + # Check that declaration was stored + assert cg._KEY_LAMBDA_DEDUP_DECLARATIONS in CORE.data + assert len(CORE.data[cg._KEY_LAMBDA_DEDUP_DECLARATIONS]) == 1 + + # Verify the declaration content is correct + declaration = CORE.data[cg._KEY_LAMBDA_DEDUP_DECLARATIONS][0] + assert "shared_lambda_0" in declaration + assert "return 42;" in declaration + + # Note: The actual flushing happens via CORE.add_job with FINAL priority + # during real code generation, so we don't test that here + + +def test_shared_function_lambda_expression() -> None: + """Test SharedFunctionLambdaExpression behaves correctly.""" + shared_lambda = cg.SharedFunctionLambdaExpression( + func_name="shared_lambda_0", + parameters=[], + return_type=cg.RawExpression("int"), + ) + + # Should output just the function name + assert str(shared_lambda) == "shared_lambda_0" + + # Should have empty capture (stateless) + assert shared_lambda.capture == "" + + # Should have empty content (just a reference) + assert shared_lambda.content == "" + + +def test_lambda_deduplication_counter() -> None: + """Test that lambda counter increments correctly.""" + # Create 3 different lambdas + for i in range(3): + lambda_expr = cg.LambdaExpression( + parts=[f"return {i};"], + parameters=[], + capture="", + return_type=cg.RawExpression("int"), + ) + func_name = cg._get_shared_lambda_name(lambda_expr) + assert func_name == f"shared_lambda_{i}" + + +def test_lambda_format_body() -> None: + """Test that format_body correctly formats lambda body with source.""" + # Without source + lambda1 = cg.LambdaExpression( + parts=["return 42;"], + parameters=[], + capture="", + return_type=None, + source=None, + ) + assert lambda1.format_body() == "return 42;" + + # With source would need a proper source object, skip for now + + +def test_stateful_lambdas_not_deduplicated() -> None: + """Test that stateful lambdas (non-empty capture) are not deduplicated.""" + # _get_shared_lambda_name is only called for stateless lambdas (capture == "") + # Stateful lambdas bypass deduplication entirely in process_lambda + + # Verify that a stateful lambda would NOT get deduplicated + # by checking it's not in the stateless dedup cache + stateful_lambda = cg.LambdaExpression( + parts=["return x + y;"], + parameters=[], + capture="=", # Non-empty capture means stateful + return_type=cg.RawExpression("int"), + ) + + # Stateful lambdas should NOT be passed to _get_shared_lambda_name + # This is enforced by the `if capture == ""` check in process_lambda + # We verify the lambda has a non-empty capture + assert stateful_lambda.capture != "" + assert stateful_lambda.capture == "=" + + +def test_static_variable_detection() -> None: + """Test detection of static variables in lambda code.""" + # Should detect static variables + assert cg._has_static_variables("static int counter = 0;") + assert cg._has_static_variables("static bool flag = false; return flag;") + assert cg._has_static_variables(" static float value = 1.0; ") + + # Should NOT detect static_cast, static_assert, etc. (with underscores) + assert not cg._has_static_variables("return static_cast(value);") + assert not cg._has_static_variables("static_assert(sizeof(int) == 4);") + assert not cg._has_static_variables("auto ptr = static_pointer_cast(bar);") + + # Edge case: 'cast', 'assert', 'pointer_cast' are NOT C++ keywords + # Someone could use them as type names, but we should NOT flag them + # because they're not actually static variables with state + # NOTE: These are valid C++ but extremely unlikely in ESPHome lambdas + assert not cg._has_static_variables("static cast obj;") # 'cast' as type name + assert not cg._has_static_variables("static assert value;") # 'assert' as type name + assert not cg._has_static_variables( + "static pointer_cast ptr;" + ) # 'pointer_cast' as type + + # Should NOT detect in comments + assert not cg._has_static_variables("// static int x = 0;\nreturn 42;") + assert not cg._has_static_variables("/* static int y = 0; */ return 42;") + + # Should detect even with comments elsewhere + assert cg._has_static_variables("// comment\nstatic int x = 0;\nreturn x;") + + # Should NOT detect non-static code + assert not cg._has_static_variables("int counter = 0; return counter++;") + assert not cg._has_static_variables("return 42;") + + # Should handle newlines between static and type/variable + assert cg._has_static_variables("static int\nfoo = 0;") + assert cg._has_static_variables("static\nint\nbar = 0;") + assert cg._has_static_variables( + "static int \n foo = 0;" + ) # Mixed spaces/newlines + + +def test_lambdas_with_static_not_deduplicated() -> None: + """Test that lambdas with static variables are not deduplicated.""" + # Two identical lambdas with static variables + lambda1 = cg.LambdaExpression( + parts=["static int counter = 0; return counter++;"], + parameters=[], + capture="", + return_type=cg.RawExpression("int"), + ) + + lambda2 = cg.LambdaExpression( + parts=["static int counter = 0; return counter++;"], + parameters=[], + capture="", + return_type=cg.RawExpression("int"), + ) + + # Should return None (not deduplicated) + func_name1 = cg._get_shared_lambda_name(lambda1) + func_name2 = cg._get_shared_lambda_name(lambda2) + + assert func_name1 is None + assert func_name2 is None + + +def test_lambdas_without_static_still_deduplicated() -> None: + """Test that lambdas without static variables are still deduplicated.""" + # Two identical lambdas WITHOUT static variables + lambda1 = cg.LambdaExpression( + parts=["int counter = 0; return counter++;"], # No static + parameters=[], + capture="", + return_type=cg.RawExpression("int"), + ) + + lambda2 = cg.LambdaExpression( + parts=["int counter = 0; return counter++;"], # No static + parameters=[], + capture="", + return_type=cg.RawExpression("int"), + ) + + # Should be deduplicated (same function name) + func_name1 = cg._get_shared_lambda_name(lambda1) + func_name2 = cg._get_shared_lambda_name(lambda2) + + assert func_name1 is not None + assert func_name2 is not None + assert func_name1 == func_name2 From 03a8ef71ff4224ad57315402bfedc897906d6038 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Nov 2025 18:37:49 -0600 Subject: [PATCH 0388/1145] [esp32_ble_client] Replace std::string with char[18] for BLE address storage (#12070) --- esphome/components/alpha3/alpha3.cpp | 18 ++--- .../components/am43/sensor/am43_sensor.cpp | 13 ++-- esphome/components/anova/anova.cpp | 9 +-- esphome/components/ble_client/automation.h | 2 +- esphome/components/ble_client/ble_client.cpp | 2 +- .../ble_client/output/ble_binary_output.cpp | 4 +- .../ble_client/sensor/ble_rssi_sensor.cpp | 6 +- .../ble_client/sensor/ble_sensor.cpp | 2 +- .../text_sensor/ble_text_sensor.cpp | 2 +- .../bluetooth_proxy/bluetooth_connection.cpp | 41 +++++----- .../bluetooth_proxy/bluetooth_proxy.cpp | 11 ++- .../esp32_ble_client/ble_characteristic.cpp | 6 +- .../esp32_ble_client/ble_client_base.cpp | 75 +++++++++---------- .../esp32_ble_client/ble_client_base.h | 16 ++-- .../esp32_ble_client/ble_service.cpp | 4 +- .../display/pvvx_display.cpp | 36 +++++---- 16 files changed, 115 insertions(+), 132 deletions(-) diff --git a/esphome/components/alpha3/alpha3.cpp b/esphome/components/alpha3/alpha3.cpp index 344f2d5a03..f22a8e2444 100644 --- a/esphome/components/alpha3/alpha3.cpp +++ b/esphome/components/alpha3/alpha3.cpp @@ -56,13 +56,13 @@ bool Alpha3::is_current_response_type_(const uint8_t *response_type) { void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) { if (this->response_offset_ >= this->response_length_) { - ESP_LOGD(TAG, "[%s] GENI response begin", this->parent_->address_str().c_str()); + ESP_LOGD(TAG, "[%s] GENI response begin", this->parent_->address_str()); if (length < GENI_RESPONSE_HEADER_LENGTH) { - ESP_LOGW(TAG, "[%s] response to short", this->parent_->address_str().c_str()); + ESP_LOGW(TAG, "[%s] response too short", this->parent_->address_str()); return; } if (response[0] != 36 || response[2] != 248 || response[3] != 231 || response[4] != 10) { - ESP_LOGW(TAG, "[%s] response bytes %d %d %d %d %d don't match GENI HEADER", this->parent_->address_str().c_str(), + ESP_LOGW(TAG, "[%s] response bytes %d %d %d %d %d don't match GENI HEADER", this->parent_->address_str(), response[0], response[1], response[2], response[3], response[4]); return; } @@ -77,11 +77,11 @@ void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) { }; if (this->is_current_response_type_(GENI_RESPONSE_TYPE_FLOW_HEAD)) { - ESP_LOGD(TAG, "[%s] FLOW HEAD Response", this->parent_->address_str().c_str()); + ESP_LOGD(TAG, "[%s] FLOW HEAD Response", this->parent_->address_str()); extract_publish_sensor_value(GENI_RESPONSE_FLOW_OFFSET, this->flow_sensor_, 3600.0F); extract_publish_sensor_value(GENI_RESPONSE_HEAD_OFFSET, this->head_sensor_, .0001F); } else if (this->is_current_response_type_(GENI_RESPONSE_TYPE_POWER)) { - ESP_LOGD(TAG, "[%s] POWER Response", this->parent_->address_str().c_str()); + ESP_LOGD(TAG, "[%s] POWER Response", this->parent_->address_str()); extract_publish_sensor_value(GENI_RESPONSE_POWER_OFFSET, this->power_sensor_, 1.0F); extract_publish_sensor_value(GENI_RESPONSE_CURRENT_OFFSET, this->current_sensor_, 1.0F); extract_publish_sensor_value(GENI_RESPONSE_MOTOR_SPEED_OFFSET, this->speed_sensor_, 1.0F); @@ -100,7 +100,7 @@ void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc if (param->open.status == ESP_GATT_OK) { this->response_offset_ = 0; this->response_length_ = 0; - ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str()); + ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str()); } break; } @@ -132,7 +132,7 @@ void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc case ESP_GATTC_SEARCH_CMPL_EVT: { auto *chr = this->parent_->get_characteristic(ALPHA3_GENI_SERVICE_UUID, ALPHA3_GENI_CHARACTERISTIC_UUID); if (chr == nullptr) { - ESP_LOGE(TAG, "[%s] No GENI service found at device, not an Alpha3..?", this->parent_->address_str().c_str()); + ESP_LOGE(TAG, "[%s] No GENI service found at device, not an Alpha3..?", this->parent_->address_str()); break; } auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(), @@ -164,12 +164,12 @@ void Alpha3::send_request_(uint8_t *request, size_t len) { esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->geni_handle_, len, request, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) - ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status); } void Alpha3::update() { if (this->node_state != espbt::ClientState::ESTABLISHED) { - ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str()); + ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str()); return; } diff --git a/esphome/components/am43/sensor/am43_sensor.cpp b/esphome/components/am43/sensor/am43_sensor.cpp index 4cc99001ae..b2bc3254e2 100644 --- a/esphome/components/am43/sensor/am43_sensor.cpp +++ b/esphome/components/am43/sensor/am43_sensor.cpp @@ -44,11 +44,9 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i auto *chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID); if (chr == nullptr) { if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) { - ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", - this->parent_->address_str().c_str()); + ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->parent_->address_str()); } else { - ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?", - this->parent_->address_str().c_str()); + ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?", this->parent_->address_str()); } break; } @@ -82,8 +80,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { - ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), - status); + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status); } } this->current_sensor_ = 0; @@ -97,7 +94,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i void Am43::update() { if (this->node_state != espbt::ClientState::ESTABLISHED) { - ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str()); + ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str()); return; } if (this->current_sensor_ == 0) { @@ -107,7 +104,7 @@ void Am43::update() { esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { - ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status); } } this->current_sensor_++; diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index d0e8f6827f..2693224a97 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -42,7 +42,7 @@ void Anova::control(const ClimateCall &call) { esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { - ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status); } } if (call.get_target_temperature().has_value()) { @@ -51,7 +51,7 @@ void Anova::control(const ClimateCall &call) { esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { - ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status); } } } @@ -124,8 +124,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { - ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), - status); + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status); } } } @@ -150,7 +149,7 @@ void Anova::update() { esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { - ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status); } this->current_request_++; } diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index bbc2dd05e0..788eac4a57 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -198,7 +198,7 @@ template class BLEClientWriteAction : public Action, publ } this->node_state = espbt::ClientState::ESTABLISHED; esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), - ble_client_->address_str().c_str()); + ble_client_->address_str()); break; } default: diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 5cf096c9d4..b8968fe4ba 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -39,7 +39,7 @@ void BLEClient::set_enabled(bool enabled) { return; this->enabled = enabled; if (!enabled) { - ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str()); + ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str()); this->disconnect(); } } diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp index ce67193be7..84558717f8 100644 --- a/esphome/components/ble_client/output/ble_binary_output.cpp +++ b/esphome/components/ble_client/output/ble_binary_output.cpp @@ -14,7 +14,7 @@ void BLEBinaryOutput::dump_config() { " MAC address : %s\n" " Service UUID : %s\n" " Characteristic UUID: %s", - this->parent_->address_str().c_str(), this->service_uuid_.to_string().c_str(), + this->parent_->address_str(), this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str()); LOG_BINARY_OUTPUT(this); } @@ -44,7 +44,7 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i } this->node_state = espbt::ClientState::ESTABLISHED; ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), - this->parent()->address_str().c_str()); + this->parent()->address_str()); this->node_state = espbt::ClientState::ESTABLISHED; break; } diff --git a/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp index 663c52ac10..4edcbd3877 100644 --- a/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp @@ -19,7 +19,7 @@ void BLEClientRSSISensor::loop() { void BLEClientRSSISensor::dump_config() { LOG_SENSOR("", "BLE Client RSSI Sensor", this); - ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str()); + ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str()); LOG_UPDATE_INTERVAL(this); } @@ -69,10 +69,10 @@ void BLEClientRSSISensor::update() { this->get_rssi_(); } void BLEClientRSSISensor::get_rssi_() { - ESP_LOGV(TAG, "requesting rssi from %s", this->parent()->address_str().c_str()); + ESP_LOGV(TAG, "requesting rssi from %s", this->parent()->address_str()); auto status = esp_ble_gap_read_rssi(this->parent()->get_remote_bda()); if (status != ESP_OK) { - ESP_LOGW(TAG, "esp_ble_gap_read_rssi error, address=%s, status=%d", this->parent()->address_str().c_str(), status); + ESP_LOGW(TAG, "esp_ble_gap_read_rssi error, address=%s, status=%d", this->parent()->address_str(), status); this->status_set_warning(); this->publish_state(NAN); } diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index 61685c0566..8e3e483003 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -25,7 +25,7 @@ void BLESensor::dump_config() { " Characteristic UUID: %s\n" " Descriptor UUID : %s\n" " Notifications : %s", - this->parent()->address_str().c_str(), this->service_uuid_.to_string().c_str(), + this->parent()->address_str(), this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_)); LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index b7a6d154db..bb771aed99 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -28,7 +28,7 @@ void BLETextSensor::dump_config() { " Characteristic UUID: %s\n" " Descriptor UUID : %s\n" " Notifications : %s", - this->parent()->address_str().c_str(), this->service_uuid_.to_string().c_str(), + this->parent()->address_str(), this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_)); LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index fcc344dda9..1d6f7e23b3 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -196,8 +196,8 @@ void BluetoothConnection::send_service_for_discovery_() { if (service_status != ESP_GATT_OK || service_count == 0) { ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service %s, status=%d, service_count=%d, offset=%d", - this->connection_index_, this->address_str().c_str(), - service_status != ESP_GATT_OK ? "error" : "missing", service_status, service_count, this->send_service_); + this->connection_index_, this->address_str(), service_status != ESP_GATT_OK ? "error" : "missing", + service_status, service_count, this->send_service_); this->send_service_ = DONE_SENDING_SERVICES; return; } @@ -312,13 +312,13 @@ void BluetoothConnection::send_service_for_discovery_() { if (resp.services.size() > 1) { resp.services.pop_back(); ESP_LOGD(TAG, "[%d] [%s] Service %d would exceed limit (current: %d + service: %d > %d), sending current batch", - this->connection_index_, this->address_str().c_str(), this->send_service_, current_size, service_size, + this->connection_index_, this->address_str(), this->send_service_, current_size, service_size, MAX_PACKET_SIZE); // Don't increment send_service_ - we'll retry this service in next batch } else { // This single service is too large, but we have to send it anyway ESP_LOGV(TAG, "[%d] [%s] Service %d is too large (%d bytes) but sending anyway", this->connection_index_, - this->address_str().c_str(), this->send_service_, service_size); + this->address_str(), this->send_service_, service_size); // Increment so we don't get stuck this->send_service_++; } @@ -337,21 +337,20 @@ void BluetoothConnection::send_service_for_discovery_() { } void BluetoothConnection::log_connection_error_(const char *operation, esp_gatt_status_t status) { - ESP_LOGE(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str().c_str(), operation, - status); + ESP_LOGE(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str(), operation, status); } void BluetoothConnection::log_connection_warning_(const char *operation, esp_err_t err) { - ESP_LOGW(TAG, "[%d] [%s] %s failed, err=%d", this->connection_index_, this->address_str().c_str(), operation, err); + ESP_LOGW(TAG, "[%d] [%s] %s failed, err=%d", this->connection_index_, this->address_str(), operation, err); } void BluetoothConnection::log_gatt_not_connected_(const char *action, const char *type) { - ESP_LOGW(TAG, "[%d] [%s] Cannot %s GATT %s, not connected.", this->connection_index_, this->address_str().c_str(), - action, type); + ESP_LOGW(TAG, "[%d] [%s] Cannot %s GATT %s, not connected.", this->connection_index_, this->address_str(), action, + type); } void BluetoothConnection::log_gatt_operation_error_(const char *operation, uint16_t handle, esp_gatt_status_t status) { - ESP_LOGW(TAG, "[%d] [%s] Error %s for handle 0x%2X, status=%d", this->connection_index_, this->address_str().c_str(), + ESP_LOGW(TAG, "[%d] [%s] Error %s for handle 0x%2X, status=%d", this->connection_index_, this->address_str(), operation, handle, status); } @@ -372,14 +371,14 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_DISCONNECT_EVT: { // Don't reset connection yet - wait for CLOSE_EVT to ensure controller has freed resources // This prevents race condition where we mark slot as free before controller cleanup is complete - ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_.c_str(), + ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_, param->disconnect.reason); // Send disconnection notification but don't free the slot yet this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason); break; } case ESP_GATTC_CLOSE_EVT: { - ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_.c_str(), + ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_, param->close.reason); // Now the GATT connection is fully closed and controller resources are freed // Safe to mark the connection slot as available @@ -463,7 +462,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga break; } case ESP_GATTC_NOTIFY_EVT: { - ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, this->address_str_.c_str(), + ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, this->address_str_, param->notify.handle); api::BluetoothGATTNotifyDataResponse resp; resp.address = this->address_; @@ -502,8 +501,7 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(), - handle); + ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic handle %d", this->connection_index_, this->address_str_, handle); esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE); return this->check_and_log_error_("esp_ble_gattc_read_char", err); @@ -515,8 +513,7 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const uint8 this->log_gatt_not_connected_("write", "characteristic"); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(), - handle); + ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_, handle); // ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data // The BTC layer immediately copies the data to its own buffer (see btc_gattc.c) @@ -532,8 +529,7 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) { this->log_gatt_not_connected_("read", "descriptor"); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(), - handle); + ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_, handle); esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE); return this->check_and_log_error_("esp_ble_gattc_read_char_descr", err); @@ -544,8 +540,7 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const uint8_t * this->log_gatt_not_connected_("write", "descriptor"); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(), - handle); + ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_, handle); // ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data // The BTC layer immediately copies the data to its own buffer (see btc_gattc.c) @@ -564,13 +559,13 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl if (enable) { ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->connection_index_, - this->address_str_.c_str(), handle); + this->address_str_, handle); esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle); return this->check_and_log_error_("esp_ble_gattc_register_for_notify", err); } ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", this->connection_index_, - this->address_str_.c_str(), handle); + this->address_str_, handle); esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle); return this->check_and_log_error_("esp_ble_gattc_unregister_for_notify", err); } diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 34e0aa93a3..71f8da75a7 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -47,12 +47,11 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta void BluetoothProxy::log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state) { ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, state: %s", connection->get_connection_index(), - connection->address_str().c_str(), espbt::client_state_to_string(state)); + connection->address_str(), espbt::client_state_to_string(state)); } void BluetoothProxy::log_connection_info_(BluetoothConnection *connection, const char *message) { - ESP_LOGI(TAG, "[%d] [%s] Connecting %s", connection->get_connection_index(), connection->address_str().c_str(), - message); + ESP_LOGI(TAG, "[%d] [%s] Connecting %s", connection->get_connection_index(), connection->address_str(), message); } void BluetoothProxy::log_not_connected_gatt_(const char *action, const char *type) { @@ -186,7 +185,7 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest } if (!msg.has_address_type) { ESP_LOGE(TAG, "[%d] [%s] Missing address type in connect request", connection->get_connection_index(), - connection->address_str().c_str()); + connection->address_str()); this->send_device_connection(msg.address, false); return; } @@ -199,7 +198,7 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest } else if (connection->state() == espbt::ClientState::CONNECTING) { if (connection->disconnect_pending()) { ESP_LOGW(TAG, "[%d] [%s] Connection request while pending disconnect, cancelling pending disconnect", - connection->get_connection_index(), connection->address_str().c_str()); + connection->get_connection_index(), connection->address_str()); connection->cancel_pending_disconnect(); return; } @@ -339,7 +338,7 @@ void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetSer return; } if (!connection->service_count_) { - ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str()); + ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str()); this->send_gatt_services_done(msg.address); return; } diff --git a/esphome/components/esp32_ble_client/ble_characteristic.cpp b/esphome/components/esp32_ble_client/ble_characteristic.cpp index 36229c23c3..e0d0174c57 100644 --- a/esphome/components/esp32_ble_client/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_client/ble_characteristic.cpp @@ -38,7 +38,7 @@ void BLECharacteristic::parse_descriptors() { } if (status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", - this->service->client->get_connection_index(), this->service->client->address_str().c_str(), status); + this->service->client->get_connection_index(), this->service->client->address_str(), status); break; } if (count == 0) { @@ -51,7 +51,7 @@ void BLECharacteristic::parse_descriptors() { desc->characteristic = this; this->descriptors.push_back(desc); ESP_LOGV(TAG, "[%d] [%s] descriptor %s, handle 0x%x", this->service->client->get_connection_index(), - this->service->client->address_str().c_str(), desc->uuid.to_string().c_str(), desc->handle); + this->service->client->address_str(), desc->uuid.to_string().c_str(), desc->handle); offset++; } } @@ -84,7 +84,7 @@ esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, new_val, write_type, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "[%d] [%s] Error sending write value to BLE gattc server, status=%d", - this->service->client->get_connection_index(), this->service->client->address_str().c_str(), status); + this->service->client->get_connection_index(), this->service->client->address_str(), status); } return status; } diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 18321ef91c..07e88c7528 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -41,7 +41,7 @@ void BLEClientBase::setup() { } void BLEClientBase::set_state(espbt::ClientState st) { - ESP_LOGV(TAG, "[%d] [%s] Set state %d", this->connection_index_, this->address_str_.c_str(), (int) st); + ESP_LOGV(TAG, "[%d] [%s] Set state %d", this->connection_index_, this->address_str_, (int) st); ESPBTClient::set_state(st); } @@ -71,7 +71,7 @@ void BLEClientBase::dump_config() { ESP_LOGCONFIG(TAG, " Address: %s\n" " Auto-Connect: %s", - this->address_str().c_str(), TRUEFALSE(this->auto_connect_)); + this->address_str(), TRUEFALSE(this->auto_connect_)); ESP_LOGCONFIG(TAG, " State: %s", espbt::client_state_to_string(this->state())); if (this->status_ == ESP_GATT_NO_RESOURCES) { ESP_LOGE(TAG, " Failed due to no resources. Try to reduce number of BLE clients in config."); @@ -104,12 +104,11 @@ void BLEClientBase::connect() { // Prevent duplicate connection attempts if (this->state_ == espbt::ClientState::CONNECTING || this->state_ == espbt::ClientState::CONNECTED || this->state_ == espbt::ClientState::ESTABLISHED) { - ESP_LOGW(TAG, "[%d] [%s] Connection already in progress, state=%s", this->connection_index_, - this->address_str_.c_str(), espbt::client_state_to_string(this->state_)); + ESP_LOGW(TAG, "[%d] [%s] Connection already in progress, state=%s", this->connection_index_, this->address_str_, + espbt::client_state_to_string(this->state_)); return; } - ESP_LOGI(TAG, "[%d] [%s] 0x%02x Connecting", this->connection_index_, this->address_str_.c_str(), - this->remote_addr_type_); + ESP_LOGI(TAG, "[%d] [%s] 0x%02x Connecting", this->connection_index_, this->address_str_, this->remote_addr_type_); this->paired_ = false; // Enable loop for state processing this->enable_loop(); @@ -135,13 +134,13 @@ esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda void BLEClientBase::disconnect() { if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING) { - ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already %s", this->connection_index_, this->address_str_.c_str(), + ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already %s", this->connection_index_, this->address_str_, espbt::client_state_to_string(this->state_)); return; } if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) { ESP_LOGD(TAG, "[%d] [%s] Disconnect before connected, disconnect scheduled", this->connection_index_, - this->address_str_.c_str()); + this->address_str_); this->want_disconnect_ = true; return; } @@ -150,8 +149,7 @@ void BLEClientBase::disconnect() { void BLEClientBase::unconditional_disconnect() { // Disconnect without checking the state. - ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_.c_str(), - this->conn_id_); + ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_, this->conn_id_); if (this->state_ == espbt::ClientState::DISCONNECTING) { this->log_error_("Already disconnecting"); return; @@ -192,24 +190,23 @@ void BLEClientBase::release_services() { } void BLEClientBase::log_event_(const char *name) { - ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), name); + ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_, name); } void BLEClientBase::log_gattc_event_(const char *name) { - ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_.c_str(), name); + ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_, name); } void BLEClientBase::log_gattc_warning_(const char *operation, esp_gatt_status_t status) { - ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_.c_str(), operation, - status); + ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_, operation, status); } void BLEClientBase::log_gattc_warning_(const char *operation, esp_err_t err) { - ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_.c_str(), operation, err); + ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_, operation, err); } void BLEClientBase::log_connection_params_(const char *param_type) { - ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_.c_str(), param_type); + ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_, param_type); } void BLEClientBase::handle_connection_result_(esp_err_t ret) { @@ -220,15 +217,15 @@ void BLEClientBase::handle_connection_result_(esp_err_t ret) { } void BLEClientBase::log_error_(const char *message) { - ESP_LOGE(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message); + ESP_LOGE(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_, message); } void BLEClientBase::log_error_(const char *message, int code) { - ESP_LOGE(TAG, "[%d] [%s] %s=%d", this->connection_index_, this->address_str_.c_str(), message, code); + ESP_LOGE(TAG, "[%d] [%s] %s=%d", this->connection_index_, this->address_str_, message, code); } void BLEClientBase::log_warning_(const char *message) { - ESP_LOGW(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message); + ESP_LOGW(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_, message); } void BLEClientBase::update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, @@ -264,13 +261,13 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_) return false; - ESP_LOGV(TAG, "[%d] [%s] gattc_event_handler: event=%d gattc_if=%d", this->connection_index_, - this->address_str_.c_str(), event, esp_gattc_if); + ESP_LOGV(TAG, "[%d] [%s] gattc_event_handler: event=%d gattc_if=%d", this->connection_index_, this->address_str_, + event, esp_gattc_if); switch (event) { case ESP_GATTC_REG_EVT: { if (param->reg.status == ESP_GATT_OK) { - ESP_LOGV(TAG, "[%d] [%s] gattc registered app id %d", this->connection_index_, this->address_str_.c_str(), + ESP_LOGV(TAG, "[%d] [%s] gattc registered app id %d", this->connection_index_, this->address_str_, this->app_id); this->gattc_if_ = esp_gattc_if; } else { @@ -292,7 +289,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ // arriving after we've already transitioned to IDLE state. if (this->state_ == espbt::ClientState::IDLE) { ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in IDLE state (status=%d), ignoring", this->connection_index_, - this->address_str_.c_str(), param->open.status); + this->address_str_, param->open.status); break; } @@ -301,7 +298,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ // because it means we have a bad assumption about how the // ESP BT stack works. ESP_LOGE(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->connection_index_, - this->address_str_.c_str(), espbt::client_state_to_string(this->state_), param->open.status); + this->address_str_, espbt::client_state_to_string(this->state_), param->open.status); } if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { this->log_gattc_warning_("Connection open", param->open.status); @@ -318,7 +315,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } // MTU negotiation already started in ESP_GATTC_CONNECT_EVT this->set_state(espbt::ClientState::CONNECTED); - ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str()); + ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_); if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { // Cached connections already connected with medium parameters, no update needed // only set our state, subclients might have more stuff to do yet. @@ -354,8 +351,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->state_ == espbt::ClientState::CONNECTED) { this->log_warning_("Remote closed during discovery"); } else { - ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_, - this->address_str_.c_str(), param->disconnect.reason); + ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_, this->address_str_, + param->disconnect.reason); } this->release_services(); this->set_state(espbt::ClientState::IDLE); @@ -366,12 +363,12 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ if (this->conn_id_ != param->cfg_mtu.conn_id) return false; if (param->cfg_mtu.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_, - this->address_str_.c_str(), param->cfg_mtu.mtu, param->cfg_mtu.status); + ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_, this->address_str_, + param->cfg_mtu.mtu, param->cfg_mtu.status); // No state change required here - disconnect event will follow if needed. break; } - ESP_LOGD(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_.c_str(), + ESP_LOGD(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_, param->cfg_mtu.status, param->cfg_mtu.mtu); this->mtu_ = param->cfg_mtu.mtu; break; @@ -415,14 +412,14 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } else if (this->connection_type_ != espbt::ConnectionType::V3_WITH_CACHE) { #ifdef USE_ESP32_BLE_DEVICE for (auto &svc : this->services_) { - ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(), + ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_, svc->uuid.to_string().c_str()); - ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_, - this->address_str_.c_str(), svc->start_handle, svc->end_handle); + ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_, this->address_str_, + svc->start_handle, svc->end_handle); } #endif } - ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_.c_str()); + ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_); this->state_ = espbt::ClientState::ESTABLISHED; break; } @@ -503,7 +500,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ default: // ideally would check all other events for matching conn_id - ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_.c_str(), event); + ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_, event); break; } return true; @@ -520,7 +517,7 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ case ESP_GAP_BLE_SEC_REQ_EVT: if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr)) return; - ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event); + ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_, event); esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); break; // This event is sent once authentication has completed @@ -529,13 +526,13 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ return; esp_bd_addr_t bd_addr; memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); - ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_.c_str(), + ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_, format_hex(bd_addr, 6).c_str()); if (!param->ble_security.auth_cmpl.success) { this->log_error_("auth fail reason", param->ble_security.auth_cmpl.fail_reason); } else { this->paired_ = true; - ESP_LOGD(TAG, "[%d] [%s] auth success type = %d mode = %d", this->connection_index_, this->address_str_.c_str(), + ESP_LOGD(TAG, "[%d] [%s] auth success type = %d mode = %d", this->connection_index_, this->address_str_, param->ble_security.auth_cmpl.addr_type, param->ble_security.auth_cmpl.auth_mode); } break; @@ -598,7 +595,7 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) { } } ESP_LOGW(TAG, "[%d] [%s] Cannot parse characteristic value of type 0x%x length %d", this->connection_index_, - this->address_str_.c_str(), value[0], length); + this->address_str_, value[0], length); return NAN; } diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index 7f0ae3b83e..7786495915 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -10,7 +10,6 @@ #endif #include -#include #include #include @@ -23,6 +22,7 @@ namespace esphome::esp32_ble_client { namespace espbt = esphome::esp32_ble_tracker; static const int UNSET_CONN_ID = 0xFFFF; +static constexpr size_t MAC_ADDR_STR_LEN = 18; // "AA:BB:CC:DD:EE:FF\0" class BLEClientBase : public espbt::ESPBTClient, public Component { public: @@ -58,14 +58,12 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { this->remote_bda_[4] = (address >> 8) & 0xFF; this->remote_bda_[5] = (address >> 0) & 0xFF; if (address == 0) { - this->address_str_ = ""; + this->address_str_[0] = '\0'; } else { - char buf[18]; - format_mac_addr_upper(this->remote_bda_, buf); - this->address_str_ = buf; + format_mac_addr_upper(this->remote_bda_, this->address_str_); } } - const std::string &address_str() const { return this->address_str_; } + const char *address_str() const { return this->address_str_; } #ifdef USE_ESP32_BLE_DEVICE BLEService *get_service(espbt::ESPBTUUID uuid); @@ -104,7 +102,6 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { uint64_t address_{0}; // Group 2: Container types (grouped for memory optimization) - std::string address_str_{}; #ifdef USE_ESP32_BLE_DEVICE std::vector services_; #endif @@ -113,8 +110,9 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { int gattc_if_; esp_gatt_status_t status_{ESP_GATT_OK}; - // Group 4: Arrays (6 bytes) - esp_bd_addr_t remote_bda_; + // Group 4: Arrays + char address_str_[MAC_ADDR_STR_LEN]{}; // 18 bytes: "AA:BB:CC:DD:EE:FF\0" + esp_bd_addr_t remote_bda_; // 6 bytes // Group 5: 2-byte types uint16_t conn_id_{UNSET_CONN_ID}; diff --git a/esphome/components/esp32_ble_client/ble_service.cpp b/esphome/components/esp32_ble_client/ble_service.cpp index accaad15e1..deaaa3de02 100644 --- a/esphome/components/esp32_ble_client/ble_service.cpp +++ b/esphome/components/esp32_ble_client/ble_service.cpp @@ -51,7 +51,7 @@ void BLEService::parse_characteristics() { } if (status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->client->get_connection_index(), - this->client->address_str().c_str(), status); + this->client->address_str(), status); break; } if (count == 0) { @@ -65,7 +65,7 @@ void BLEService::parse_characteristics() { characteristic->service = this; this->characteristics.push_back(characteristic); ESP_LOGV(TAG, "[%d] [%s] characteristic %s, handle 0x%x, properties 0x%x", this->client->get_connection_index(), - this->client->address_str().c_str(), characteristic->uuid.to_string().c_str(), characteristic->handle, + this->client->address_str(), characteristic->uuid.to_string().c_str(), characteristic->handle, characteristic->properties); offset++; } diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp index b6916ad68f..8436633619 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp @@ -14,7 +14,7 @@ void PVVXDisplay::dump_config() { " Service UUID : %s\n" " Characteristic UUID : %s\n" " Auto clear : %s", - this->parent_->address_str().c_str(), this->service_uuid_.to_string().c_str(), + this->parent_->address_str(), this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(), YESNO(this->auto_clear_enabled_)); #ifdef USE_TIME ESP_LOGCONFIG(TAG, " Set time on connection: %s", YESNO(this->time_ != nullptr)); @@ -28,12 +28,12 @@ void PVVXDisplay::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t switch (event) { case ESP_GATTC_OPEN_EVT: if (param->open.status == ESP_GATT_OK) { - ESP_LOGV(TAG, "[%s] Connected successfully!", this->parent_->address_str().c_str()); + ESP_LOGV(TAG, "[%s] Connected successfully!", this->parent_->address_str()); this->delayed_disconnect_(); } break; case ESP_GATTC_DISCONNECT_EVT: - ESP_LOGV(TAG, "[%s] Disconnected", this->parent_->address_str().c_str()); + ESP_LOGV(TAG, "[%s] Disconnected", this->parent_->address_str()); this->connection_established_ = false; this->cancel_timeout("disconnect"); this->char_handle_ = 0; @@ -41,7 +41,7 @@ void PVVXDisplay::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t case ESP_GATTC_SEARCH_CMPL_EVT: { auto *chr = this->parent_->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { - ESP_LOGW(TAG, "[%s] Characteristic not found.", this->parent_->address_str().c_str()); + ESP_LOGW(TAG, "[%s] Characteristic not found.", this->parent_->address_str()); break; } this->connection_established_ = true; @@ -66,11 +66,11 @@ void PVVXDisplay::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb return; if (param->ble_security.auth_cmpl.success) { - ESP_LOGD(TAG, "[%s] Authentication successful, performing writes.", this->parent_->address_str().c_str()); + ESP_LOGD(TAG, "[%s] Authentication successful, performing writes.", this->parent_->address_str()); // Now that pairing is complete, perform the pending writes this->sync_time_and_display_(); } else { - ESP_LOGW(TAG, "[%s] Authentication failed.", this->parent_->address_str().c_str()); + ESP_LOGW(TAG, "[%s] Authentication failed.", this->parent_->address_str()); } break; } @@ -89,22 +89,20 @@ void PVVXDisplay::update() { void PVVXDisplay::display() { if (!this->parent_->enabled) { - ESP_LOGD(TAG, "[%s] BLE client not enabled. Init connection.", this->parent_->address_str().c_str()); + ESP_LOGD(TAG, "[%s] BLE client not enabled. Init connection.", this->parent_->address_str()); this->parent_->set_enabled(true); return; } if (!this->connection_established_) { - ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.", - this->parent_->address_str().c_str()); + ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.", this->parent_->address_str()); return; } if (!this->char_handle_) { - ESP_LOGW(TAG, "[%s] No ble handle to BLE client. State update can not be written.", - this->parent_->address_str().c_str()); + ESP_LOGW(TAG, "[%s] No ble handle to BLE client. State update can not be written.", this->parent_->address_str()); return; } ESP_LOGD(TAG, "[%s] Send to display: bignum %d, smallnum: %d, cfg: 0x%02x, validity period: %u.", - this->parent_->address_str().c_str(), this->bignum_, this->smallnum_, this->cfg_, this->validity_period_); + this->parent_->address_str(), this->bignum_, this->smallnum_, this->cfg_, this->validity_period_); uint8_t blk[8] = {}; blk[0] = 0x22; blk[1] = this->bignum_ & 0xff; @@ -128,16 +126,16 @@ void PVVXDisplay::setcfgbit_(uint8_t bit, bool value) { void PVVXDisplay::send_to_setup_char_(uint8_t *blk, size_t size) { if (!this->connection_established_) { - ESP_LOGW(TAG, "[%s] Not connected to BLE client.", this->parent_->address_str().c_str()); + ESP_LOGW(TAG, "[%s] Not connected to BLE client.", this->parent_->address_str()); return; } auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, size, blk, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { - ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status); } else { - ESP_LOGV(TAG, "[%s] send %u bytes", this->parent_->address_str().c_str(), size); + ESP_LOGV(TAG, "[%s] send %u bytes", this->parent_->address_str(), size); this->delayed_disconnect_(); } } @@ -161,21 +159,21 @@ void PVVXDisplay::sync_time_() { if (this->time_ == nullptr) return; if (!this->connection_established_) { - ESP_LOGW(TAG, "[%s] Not connected to BLE client. Time can not be synced.", this->parent_->address_str().c_str()); + ESP_LOGW(TAG, "[%s] Not connected to BLE client. Time can not be synced.", this->parent_->address_str()); return; } if (!this->char_handle_) { - ESP_LOGW(TAG, "[%s] No ble handle to BLE client. Time can not be synced.", this->parent_->address_str().c_str()); + ESP_LOGW(TAG, "[%s] No ble handle to BLE client. Time can not be synced.", this->parent_->address_str()); return; } auto time = this->time_->now(); if (!time.is_valid()) { - ESP_LOGW(TAG, "[%s] Time is not yet valid. Time can not be synced.", this->parent_->address_str().c_str()); + ESP_LOGW(TAG, "[%s] Time is not yet valid. Time can not be synced.", this->parent_->address_str()); return; } time.recalc_timestamp_utc(true); // calculate timestamp of local time uint8_t blk[5] = {}; - ESP_LOGD(TAG, "[%s] Sync time with timestamp %" PRIu64 ".", this->parent_->address_str().c_str(), time.timestamp); + ESP_LOGD(TAG, "[%s] Sync time with timestamp %" PRIu64 ".", this->parent_->address_str(), time.timestamp); blk[0] = 0x23; blk[1] = time.timestamp & 0xff; blk[2] = (time.timestamp >> 8) & 0xff; From d443dbbf344626357cb2321c84cf189f0fc75bef Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Nov 2025 19:42:09 -0600 Subject: [PATCH 0389/1145] [lvgl] Fix lambda return types for coord and font validators (#12113) --- esphome/components/lvgl/lv_validation.py | 6 ++++-- esphome/components/lvgl/widgets/line.py | 6 ++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index 23c322c31f..9c1dd22085 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -40,7 +40,7 @@ from .helpers import ( lv_fonts_used, requires_component, ) -from .types import lv_font_t, lv_gradient_t +from .types import lv_gradient_t opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") @@ -498,7 +498,9 @@ class LvFont(LValidator): esphome_fonts_used.add(fontval) return requires_component("font")(fontval) - super().__init__(validator, lv_font_t) + # Use font::Font* as return type for lambdas returning ESPHome fonts + # The inline overloads in lvgl_esphome.h handle conversion to lv_font_t* + super().__init__(validator, Font.operator("ptr")) async def process(self, value, args=()): if is_lv_font(value): diff --git a/esphome/components/lvgl/widgets/line.py b/esphome/components/lvgl/widgets/line.py index bd90edbefc..57cb965737 100644 --- a/esphome/components/lvgl/widgets/line.py +++ b/esphome/components/lvgl/widgets/line.py @@ -6,7 +6,7 @@ from esphome.core import Lambda from ..defines import CONF_MAIN, call_lambda from ..lvcode import lv_add from ..schemas import point_schema -from ..types import LvCompound, LvType +from ..types import LvCompound, LvType, lv_coord_t from . import Widget, WidgetType CONF_LINE = "line" @@ -23,9 +23,7 @@ LINE_SCHEMA = { async def process_coord(coord): if isinstance(coord, Lambda): - coord = call_lambda( - await cg.process_lambda(coord, [], return_type="lv_coord_t") - ) + coord = call_lambda(await cg.process_lambda(coord, [], return_type=lv_coord_t)) if not coord.endswith("()"): coord = f"static_cast({coord})" return cg.RawExpression(coord) From f071b6232a2f9cd043b4870384a9070019221b2d Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:47:27 +1000 Subject: [PATCH 0390/1145] [lvgl] Fix position of errors in widget config (#12111) Co-authored-by: J. Nick Koston --- esphome/components/lvgl/schemas.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 6b77f66abb..b2d463c5fd 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -1,6 +1,7 @@ from esphome import config_validation as cv from esphome.automation import Trigger, validate_automation from esphome.components.time import RealTimeClock +from esphome.config_validation import prepend_path from esphome.const import ( CONF_ARGS, CONF_FORMAT, @@ -422,7 +423,10 @@ def any_widget_schema(extras=None): def validator(value): if isinstance(value, dict): # Convert to list + is_dict = True value = [{k: v} for k, v in value.items()] + else: + is_dict = False if not isinstance(value, list): raise cv.Invalid("Expected a list of widgets") result = [] @@ -443,7 +447,9 @@ def any_widget_schema(extras=None): ) # Apply custom validation value = widget_type.validate(value or {}) - result.append({key: container_validator(value)}) + path = [key] if is_dict else [index, key] + with prepend_path(path): + result.append({key: container_validator(value)}) return result return validator From e071380532a3082ac3ba7de0c52aae5250720226 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:49:47 +1000 Subject: [PATCH 0391/1145] [lvgl] Add missing obj scroll properties (#11901) Co-authored-by: J. Nick Koston --- esphome/components/lvgl/defines.py | 5 +++++ esphome/components/lvgl/schemas.py | 19 ++++++++++++++++++- esphome/components/lvgl/widgets/__init__.py | 6 +++--- tests/components/lvgl/lvgl-package.yaml | 5 +++++ 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index f2bcb6cc06..6b3b9c97ef 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -279,6 +279,8 @@ KEYBOARD_MODES = LvConstant( ) ROLLER_MODES = LvConstant("LV_ROLLER_MODE_", "NORMAL", "INFINITE") TILE_DIRECTIONS = DIRECTIONS.extend("HOR", "VER", "ALL") +SCROLL_DIRECTIONS = TILE_DIRECTIONS.extend("NONE") +SNAP_DIRECTIONS = LvConstant("LV_SCROLL_SNAP_", "NONE", "START", "END", "CENTER") CHILD_ALIGNMENTS = LvConstant( "LV_ALIGN_", "TOP_LEFT", @@ -511,6 +513,9 @@ CONF_ROLLOVER = "rollover" CONF_ROOT_BACK_BTN = "root_back_btn" CONF_SCALE_LINES = "scale_lines" CONF_SCROLLBAR_MODE = "scrollbar_mode" +CONF_SCROLL_DIR = "scroll_dir" +CONF_SCROLL_SNAP_X = "scroll_snap_x" +CONF_SCROLL_SNAP_Y = "scroll_snap_y" CONF_SELECTED_INDEX = "selected_index" CONF_SELECTED_TEXT = "selected_text" CONF_SHOW_SNOW = "show_snow" diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index b2d463c5fd..f2704f99de 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -20,7 +20,14 @@ from esphome.core import TimePeriod from esphome.core.config import StartupTrigger from . import defines as df, lv_validation as lvalid -from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR +from .defines import ( + CONF_SCROLL_DIR, + CONF_SCROLL_SNAP_X, + CONF_SCROLL_SNAP_Y, + CONF_SCROLLBAR_MODE, + CONF_TIME_FORMAT, + LV_GRAD_DIR, +) from .helpers import CONF_IF_NAN, requires_component, validate_printf from .layout import ( FLEX_OBJ_SCHEMA, @@ -234,9 +241,19 @@ STYLE_SCHEMA = cv.Schema({cv.Optional(k): v for k, v in STYLE_PROPS.items()}).ex cv.Optional(df.CONF_SCROLLBAR_MODE): df.LvConstant( "LV_SCROLLBAR_MODE_", "OFF", "ON", "ACTIVE", "AUTO" ).one_of, + cv.Optional(CONF_SCROLL_DIR): df.SCROLL_DIRECTIONS.one_of, + cv.Optional(CONF_SCROLL_SNAP_X): df.SNAP_DIRECTIONS.one_of, + cv.Optional(CONF_SCROLL_SNAP_Y): df.SNAP_DIRECTIONS.one_of, } ) +OBJ_PROPERTIES = { + CONF_SCROLL_SNAP_X, + CONF_SCROLL_SNAP_Y, + CONF_SCROLL_DIR, + CONF_SCROLLBAR_MODE, +} + # Also allow widget specific properties for use in style definitions FULL_STYLE_SCHEMA = STYLE_SCHEMA.extend( { diff --git a/esphome/components/lvgl/widgets/__init__.py b/esphome/components/lvgl/widgets/__init__.py index 187b5828c2..2e7948522e 100644 --- a/esphome/components/lvgl/widgets/__init__.py +++ b/esphome/components/lvgl/widgets/__init__.py @@ -21,7 +21,6 @@ from ..defines import ( CONF_MAIN, CONF_PAD_COLUMN, CONF_PAD_ROW, - CONF_SCROLLBAR_MODE, CONF_STYLES, CONF_WIDGETS, OBJ_FLAGS, @@ -45,7 +44,7 @@ from ..lvcode import ( lv_obj, lv_Pvariable, ) -from ..schemas import ALL_STYLES, STYLE_REMAP, WIDGET_TYPES +from ..schemas import ALL_STYLES, OBJ_PROPERTIES, STYLE_REMAP, WIDGET_TYPES from ..types import LV_STATE, LvType, WidgetType, lv_coord_t, lv_obj_t, lv_obj_t_ptr EVENT_LAMB = "event_lamb__" @@ -414,7 +413,8 @@ async def set_obj_properties(w: Widget, config): w.add_state(state) cond.else_() w.clear_state(state) - await w.set_property(CONF_SCROLLBAR_MODE, config, lv_name="obj") + for property in OBJ_PROPERTIES: + await w.set_property(property, config, lv_name="obj") async def add_widgets(parent: Widget, config: dict): diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 708dfa2cb1..eddcbe9fd5 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -537,6 +537,9 @@ lvgl: - tileview: id: tileview_id scrollbar_mode: active + scroll_dir: all + scroll_elastic: true + scroll_momentum: true on_value: then: - if: @@ -546,6 +549,8 @@ lvgl: - logger.log: "tile 1 is now showing" tiles: - id: tile_1 + scroll_snap_y: center + scroll_snap_x: start layout: vertical row: 0 column: 0 From 1207b9e99532733cb77597384c47876c3358a1f4 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:53:51 +1000 Subject: [PATCH 0392/1145] [lvgl] Automatically pad rows and columns (#11879) Co-authored-by: J. Nick Koston --- esphome/components/lvgl/layout.py | 6 +++++- tests/components/lvgl/lvgl-package.yaml | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/lvgl/layout.py b/esphome/components/lvgl/layout.py index caa503ef0d..b27a0b54a2 100644 --- a/esphome/components/lvgl/layout.py +++ b/esphome/components/lvgl/layout.py @@ -172,10 +172,14 @@ class DirectionalLayout(FlexLayout): def validate(self, config): assert config[CONF_LAYOUT].lower() == self.direction - config[CONF_LAYOUT] = { + layout = { **FLEX_HV_STYLE, CONF_FLEX_FLOW: "LV_FLEX_FLOW_" + self.flow.upper(), } + if pad_all := config.get("pad_all"): + layout[CONF_PAD_ROW] = pad_all + layout[CONF_PAD_COLUMN] = pad_all + config[CONF_LAYOUT] = layout return config diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index eddcbe9fd5..30866a603c 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -552,6 +552,7 @@ lvgl: scroll_snap_y: center scroll_snap_x: start layout: vertical + pad_all: 6px row: 0 column: 0 dir: ALL @@ -1049,6 +1050,7 @@ lvgl: opa: 0% - id: page3 layout: Horizontal + pad_all: 6px widgets: - keyboard: id: lv_keyboard From b328758634676b84839f13ad2f70469351b976dd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 26 Nov 2025 10:53:44 -0600 Subject: [PATCH 0393/1145] Revert "[core] Deduplicate identical stateless lambdas to reduce flash usage" (#12117) --- esphome/cpp_generator.py | 177 +-------------- tests/component_tests/text/test_text.py | 19 +- tests/unit_tests/test_lambda_dedup.py | 286 ------------------------ 3 files changed, 11 insertions(+), 471 deletions(-) delete mode 100644 tests/unit_tests/test_lambda_dedup.py diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 4f91696ca1..6f1af01a5b 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -19,21 +19,11 @@ from esphome.core import ( TimePeriodNanoseconds, TimePeriodSeconds, ) -from esphome.coroutine import CoroPriority, coroutine_with_priority from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last from esphome.types import Expression, SafeExpType, TemplateArgsType from esphome.util import OrderedDict from esphome.yaml_util import ESPHomeDataBase -# Keys for lambda deduplication storage in CORE.data -_KEY_LAMBDA_DEDUP = "lambda_dedup" -_KEY_LAMBDA_DEDUP_DECLARATIONS = "lambda_dedup_declarations" - -# Regex patterns for static variable detection (compiled once) -_RE_CPP_SINGLE_LINE_COMMENT = re.compile(r"//.*?$", re.MULTILINE) -_RE_CPP_MULTI_LINE_COMMENT = re.compile(r"/\*.*?\*/", re.DOTALL) -_RE_STATIC_VARIABLE = re.compile(r"\bstatic\s+(?!cast|assert|pointer_cast)\w+\s+\w+") - class RawExpression(Expression): __slots__ = ("text",) @@ -198,7 +188,7 @@ class LambdaExpression(Expression): def __init__( self, parts, parameters, capture: str = "=", return_type=None, source=None - ) -> None: + ): self.parts = parts if not isinstance(parameters, ParameterListExpression): parameters = ParameterListExpression(*parameters) @@ -207,21 +197,16 @@ class LambdaExpression(Expression): self.capture = capture self.return_type = safe_exp(return_type) if return_type is not None else None - def format_body(self) -> str: - """Format the lambda body with source directive and content.""" - body = "" - if self.source is not None: - body += f"{self.source.as_line_directive}\n" - body += self.content - return body - - def __str__(self) -> str: + def __str__(self): # Stateless lambdas (empty capture) implicitly convert to function pointers # when assigned to function pointer types - no unary + needed cpp = f"[{self.capture}]({self.parameters})" if self.return_type is not None: cpp += f" -> {self.return_type}" - cpp += f" {{\n{self.format_body()}\n}}" + cpp += " {\n" + if self.source is not None: + cpp += f"{self.source.as_line_directive}\n" + cpp += f"{self.content}\n}}" return indent_all_but_first_and_last(cpp) @property @@ -229,37 +214,6 @@ class LambdaExpression(Expression): return "".join(str(part) for part in self.parts) -class SharedFunctionLambdaExpression(LambdaExpression): - """A lambda expression that references a shared deduplicated function. - - This class wraps a function pointer but maintains the LambdaExpression - interface so calling code works unchanged. - """ - - __slots__ = ("_func_name",) - - def __init__( - self, - func_name: str, - parameters: TemplateArgsType, - return_type: SafeExpType | None = None, - ) -> None: - # Initialize parent with empty parts since we're just a function reference - super().__init__( - [], parameters, capture="", return_type=return_type, source=None - ) - self._func_name = func_name - - def __str__(self) -> str: - # Just return the function name - it's already a function pointer - return self._func_name - - @property - def content(self) -> str: - # No content, just a function reference - return "" - - # pylint: disable=abstract-method class Literal(Expression, metaclass=abc.ABCMeta): __slots__ = () @@ -629,25 +583,6 @@ def add_global(expression: SafeExpType | Statement, prepend: bool = False): CORE.add_global(expression, prepend) -@coroutine_with_priority(CoroPriority.FINAL) -async def flush_lambda_dedup_declarations() -> None: - """Flush all deferred lambda deduplication declarations to global scope. - - This is a coroutine that runs with FINAL priority (after all components) - to ensure all referenced variables are declared before the shared - lambda functions that use them. - """ - if _KEY_LAMBDA_DEDUP_DECLARATIONS not in CORE.data: - return - - declarations = CORE.data[_KEY_LAMBDA_DEDUP_DECLARATIONS] - for func_declaration in declarations: - add_global(RawStatement(func_declaration)) - - # Clear the list so we don't add them again - CORE.data[_KEY_LAMBDA_DEDUP_DECLARATIONS] = [] - - def add_library(name: str, version: str | None, repository: str | None = None): """Add a library to the codegen library storage. @@ -721,93 +656,6 @@ async def get_variable_with_full_id(id_: ID) -> tuple[ID, "MockObj"]: return await CORE.get_variable_with_full_id(id_) -def _has_static_variables(code: str) -> bool: - """Check if code contains static variable definitions. - - Static variables in lambdas should not be deduplicated because each lambda - instance should have its own static variable state. - - Args: - code: The lambda body code to check - - Returns: - True if code contains static variable definitions - """ - # Remove C++ comments to avoid false positives - # Remove single-line comments (// ...) - code_no_comments = _RE_CPP_SINGLE_LINE_COMMENT.sub("", code) - # Remove multi-line comments (/* ... */) - code_no_comments = _RE_CPP_MULTI_LINE_COMMENT.sub("", code_no_comments) - - # Match: static - # But not: static_cast, static_assert, static_pointer_cast - return bool(_RE_STATIC_VARIABLE.search(code_no_comments)) - - -def _get_shared_lambda_name(lambda_expr: LambdaExpression) -> str | None: - """Get the shared function name for a lambda expression. - - If an identical lambda was already generated, returns the existing shared - function name. Otherwise, creates a new shared function and returns its name. - - Lambdas with static variables are not deduplicated to preserve their - independent state. - - Args: - lambda_expr: The lambda expression to deduplicate - - Returns: - The name of the shared function for this lambda (either existing or newly created), - or None if the lambda should not be deduplicated (e.g., contains static variables) - """ - # Create a unique key from the lambda content, parameters, and return type - content = lambda_expr.content - - # Don't deduplicate lambdas with static variables - each instance needs its own state - if _has_static_variables(content): - return None - param_str = str(lambda_expr.parameters) - return_str = ( - str(lambda_expr.return_type) if lambda_expr.return_type is not None else "void" - ) - - # Use tuple of (content, params, return_type) as key - lambda_key = (content, param_str, return_str) - - # Initialize deduplication storage in CORE.data if not exists - if _KEY_LAMBDA_DEDUP not in CORE.data: - CORE.data[_KEY_LAMBDA_DEDUP] = {} - # Register the flush job to run after all components (FINAL priority) - # This ensures all variables are declared before shared lambda functions - CORE.add_job(flush_lambda_dedup_declarations) - - lambda_cache = CORE.data[_KEY_LAMBDA_DEDUP] - - # Check if we've seen this lambda before - if lambda_key in lambda_cache: - # Return name of existing shared function - return lambda_cache[lambda_key] - - # First occurrence - create a shared function - # Use the cache size as the function number - func_name = f"shared_lambda_{len(lambda_cache)}" - - # Build the function declaration using lambda's body formatting - func_declaration = ( - f"{return_str} {func_name}({param_str}) {{\n{lambda_expr.format_body()}\n}}" - ) - - # Store the declaration to be added later (after all variable declarations) - # We can't add it immediately because it might reference variables not yet declared - CORE.data.setdefault(_KEY_LAMBDA_DEDUP_DECLARATIONS, []).append(func_declaration) - - # Store in cache - lambda_cache[lambda_key] = func_name - - # Return the function name (this is the first occurrence, but we still generate shared function) - return func_name - - async def process_lambda( value: Lambda, parameters: TemplateArgsType, @@ -865,19 +713,6 @@ async def process_lambda( location.line += value.content_offset else: location = None - - # Lambda deduplication: Only deduplicate stateless lambdas (empty capture). - # Stateful lambdas cannot be shared as they capture different contexts. - # Lambdas with static variables are also not deduplicated to preserve independent state. - if capture == "": - lambda_expr = LambdaExpression( - parts, parameters, capture, return_type, location - ) - func_name = _get_shared_lambda_name(lambda_expr) - if func_name is not None: - # Return a shared function reference instead of inline lambda - return SharedFunctionLambdaExpression(func_name, parameters, return_type) - return LambdaExpression(parts, parameters, capture, return_type, location) diff --git a/tests/component_tests/text/test_text.py b/tests/component_tests/text/test_text.py index 56dee205b4..bfc3131f6d 100644 --- a/tests/component_tests/text/test_text.py +++ b/tests/component_tests/text/test_text.py @@ -1,6 +1,4 @@ -"""Tests for the text component.""" - -from esphome.core import CORE +"""Tests for the binary sensor component.""" def test_text_is_setup(generate_main): @@ -58,22 +56,15 @@ def test_text_config_value_mode_set(generate_main): assert "it_3->traits.set_mode(text::TEXT_MODE_PASSWORD);" in main_cpp -def test_text_config_lambda_is_set(generate_main) -> None: +def test_text_config_lamda_is_set(generate_main): """ - Test if lambda is set for lambda mode (optimized with stateless lambda and deduplication) + Test if lambda is set for lambda mode (optimized with stateless lambda) """ # Given # When main_cpp = generate_main("tests/component_tests/text/test_text.yaml") - # Get both global and main sections to find the shared lambda definition - full_cpp = CORE.cpp_global_section + main_cpp - # Then - # Lambda is deduplicated into a shared function (reference in main section) - assert "it_4->set_template(shared_lambda_" in main_cpp - # Lambda body should be in the code somewhere - assert 'return std::string{"Hello"};' in full_cpp - # Verify the shared lambda function is defined (in global section) - assert "esphome::optional shared_lambda_" in full_cpp + assert "it_4->set_template([]() -> esphome::optional {" in main_cpp + assert 'return std::string{"Hello"};' in main_cpp diff --git a/tests/unit_tests/test_lambda_dedup.py b/tests/unit_tests/test_lambda_dedup.py deleted file mode 100644 index bbf5f02e6d..0000000000 --- a/tests/unit_tests/test_lambda_dedup.py +++ /dev/null @@ -1,286 +0,0 @@ -"""Tests for lambda deduplication in cpp_generator.""" - -from esphome import cpp_generator as cg -from esphome.core import CORE - - -def test_deduplicate_identical_lambdas() -> None: - """Test that identical stateless lambdas are deduplicated.""" - # Create two identical lambda expressions - lambda1 = cg.LambdaExpression( - parts=["return 42;"], - parameters=[], - capture="", - return_type=cg.RawExpression("int"), - ) - - lambda2 = cg.LambdaExpression( - parts=["return 42;"], - parameters=[], - capture="", - return_type=cg.RawExpression("int"), - ) - - # Try to deduplicate them - func_name1 = cg._get_shared_lambda_name(lambda1) - func_name2 = cg._get_shared_lambda_name(lambda2) - - # Both should get the same function name (deduplication happened) - assert func_name1 == func_name2 - assert func_name1 == "shared_lambda_0" - - -def test_different_lambdas_not_deduplicated() -> None: - """Test that different lambdas get different function names.""" - lambda1 = cg.LambdaExpression( - parts=["return 42;"], - parameters=[], - capture="", - return_type=cg.RawExpression("int"), - ) - - lambda2 = cg.LambdaExpression( - parts=["return 24;"], # Different content - parameters=[], - capture="", - return_type=cg.RawExpression("int"), - ) - - func_name1 = cg._get_shared_lambda_name(lambda1) - func_name2 = cg._get_shared_lambda_name(lambda2) - - # Different lambdas should get different function names - assert func_name1 != func_name2 - assert func_name1 == "shared_lambda_0" - assert func_name2 == "shared_lambda_1" - - -def test_different_return_types_not_deduplicated() -> None: - """Test that lambdas with different return types are not deduplicated.""" - lambda1 = cg.LambdaExpression( - parts=["return 42;"], - parameters=[], - capture="", - return_type=cg.RawExpression("int"), - ) - - lambda2 = cg.LambdaExpression( - parts=["return 42;"], # Same content - parameters=[], - capture="", - return_type=cg.RawExpression("float"), # Different return type - ) - - func_name1 = cg._get_shared_lambda_name(lambda1) - func_name2 = cg._get_shared_lambda_name(lambda2) - - # Different return types = different functions - assert func_name1 != func_name2 - - -def test_different_parameters_not_deduplicated() -> None: - """Test that lambdas with different parameters are not deduplicated.""" - lambda1 = cg.LambdaExpression( - parts=["return x;"], - parameters=[("int", "x")], - capture="", - return_type=cg.RawExpression("int"), - ) - - lambda2 = cg.LambdaExpression( - parts=["return x;"], # Same content - parameters=[("float", "x")], # Different parameter type - capture="", - return_type=cg.RawExpression("int"), - ) - - func_name1 = cg._get_shared_lambda_name(lambda1) - func_name2 = cg._get_shared_lambda_name(lambda2) - - # Different parameters = different functions - assert func_name1 != func_name2 - - -def test_flush_lambda_dedup_declarations() -> None: - """Test that deferred declarations are properly stored for later flushing.""" - # Create a lambda which will create a deferred declaration - lambda1 = cg.LambdaExpression( - parts=["return 42;"], - parameters=[], - capture="", - return_type=cg.RawExpression("int"), - ) - - cg._get_shared_lambda_name(lambda1) - - # Check that declaration was stored - assert cg._KEY_LAMBDA_DEDUP_DECLARATIONS in CORE.data - assert len(CORE.data[cg._KEY_LAMBDA_DEDUP_DECLARATIONS]) == 1 - - # Verify the declaration content is correct - declaration = CORE.data[cg._KEY_LAMBDA_DEDUP_DECLARATIONS][0] - assert "shared_lambda_0" in declaration - assert "return 42;" in declaration - - # Note: The actual flushing happens via CORE.add_job with FINAL priority - # during real code generation, so we don't test that here - - -def test_shared_function_lambda_expression() -> None: - """Test SharedFunctionLambdaExpression behaves correctly.""" - shared_lambda = cg.SharedFunctionLambdaExpression( - func_name="shared_lambda_0", - parameters=[], - return_type=cg.RawExpression("int"), - ) - - # Should output just the function name - assert str(shared_lambda) == "shared_lambda_0" - - # Should have empty capture (stateless) - assert shared_lambda.capture == "" - - # Should have empty content (just a reference) - assert shared_lambda.content == "" - - -def test_lambda_deduplication_counter() -> None: - """Test that lambda counter increments correctly.""" - # Create 3 different lambdas - for i in range(3): - lambda_expr = cg.LambdaExpression( - parts=[f"return {i};"], - parameters=[], - capture="", - return_type=cg.RawExpression("int"), - ) - func_name = cg._get_shared_lambda_name(lambda_expr) - assert func_name == f"shared_lambda_{i}" - - -def test_lambda_format_body() -> None: - """Test that format_body correctly formats lambda body with source.""" - # Without source - lambda1 = cg.LambdaExpression( - parts=["return 42;"], - parameters=[], - capture="", - return_type=None, - source=None, - ) - assert lambda1.format_body() == "return 42;" - - # With source would need a proper source object, skip for now - - -def test_stateful_lambdas_not_deduplicated() -> None: - """Test that stateful lambdas (non-empty capture) are not deduplicated.""" - # _get_shared_lambda_name is only called for stateless lambdas (capture == "") - # Stateful lambdas bypass deduplication entirely in process_lambda - - # Verify that a stateful lambda would NOT get deduplicated - # by checking it's not in the stateless dedup cache - stateful_lambda = cg.LambdaExpression( - parts=["return x + y;"], - parameters=[], - capture="=", # Non-empty capture means stateful - return_type=cg.RawExpression("int"), - ) - - # Stateful lambdas should NOT be passed to _get_shared_lambda_name - # This is enforced by the `if capture == ""` check in process_lambda - # We verify the lambda has a non-empty capture - assert stateful_lambda.capture != "" - assert stateful_lambda.capture == "=" - - -def test_static_variable_detection() -> None: - """Test detection of static variables in lambda code.""" - # Should detect static variables - assert cg._has_static_variables("static int counter = 0;") - assert cg._has_static_variables("static bool flag = false; return flag;") - assert cg._has_static_variables(" static float value = 1.0; ") - - # Should NOT detect static_cast, static_assert, etc. (with underscores) - assert not cg._has_static_variables("return static_cast(value);") - assert not cg._has_static_variables("static_assert(sizeof(int) == 4);") - assert not cg._has_static_variables("auto ptr = static_pointer_cast(bar);") - - # Edge case: 'cast', 'assert', 'pointer_cast' are NOT C++ keywords - # Someone could use them as type names, but we should NOT flag them - # because they're not actually static variables with state - # NOTE: These are valid C++ but extremely unlikely in ESPHome lambdas - assert not cg._has_static_variables("static cast obj;") # 'cast' as type name - assert not cg._has_static_variables("static assert value;") # 'assert' as type name - assert not cg._has_static_variables( - "static pointer_cast ptr;" - ) # 'pointer_cast' as type - - # Should NOT detect in comments - assert not cg._has_static_variables("// static int x = 0;\nreturn 42;") - assert not cg._has_static_variables("/* static int y = 0; */ return 42;") - - # Should detect even with comments elsewhere - assert cg._has_static_variables("// comment\nstatic int x = 0;\nreturn x;") - - # Should NOT detect non-static code - assert not cg._has_static_variables("int counter = 0; return counter++;") - assert not cg._has_static_variables("return 42;") - - # Should handle newlines between static and type/variable - assert cg._has_static_variables("static int\nfoo = 0;") - assert cg._has_static_variables("static\nint\nbar = 0;") - assert cg._has_static_variables( - "static int \n foo = 0;" - ) # Mixed spaces/newlines - - -def test_lambdas_with_static_not_deduplicated() -> None: - """Test that lambdas with static variables are not deduplicated.""" - # Two identical lambdas with static variables - lambda1 = cg.LambdaExpression( - parts=["static int counter = 0; return counter++;"], - parameters=[], - capture="", - return_type=cg.RawExpression("int"), - ) - - lambda2 = cg.LambdaExpression( - parts=["static int counter = 0; return counter++;"], - parameters=[], - capture="", - return_type=cg.RawExpression("int"), - ) - - # Should return None (not deduplicated) - func_name1 = cg._get_shared_lambda_name(lambda1) - func_name2 = cg._get_shared_lambda_name(lambda2) - - assert func_name1 is None - assert func_name2 is None - - -def test_lambdas_without_static_still_deduplicated() -> None: - """Test that lambdas without static variables are still deduplicated.""" - # Two identical lambdas WITHOUT static variables - lambda1 = cg.LambdaExpression( - parts=["int counter = 0; return counter++;"], # No static - parameters=[], - capture="", - return_type=cg.RawExpression("int"), - ) - - lambda2 = cg.LambdaExpression( - parts=["int counter = 0; return counter++;"], # No static - parameters=[], - capture="", - return_type=cg.RawExpression("int"), - ) - - # Should be deduplicated (same function name) - func_name1 = cg._get_shared_lambda_name(lambda1) - func_name2 = cg._get_shared_lambda_name(lambda2) - - assert func_name1 is not None - assert func_name2 is not None - assert func_name1 == func_name2 From 12a51ff047641d7ecdc3ec1a5b532ba8bdf5e49a Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Wed, 26 Nov 2025 18:00:44 +0100 Subject: [PATCH 0394/1145] [packages] Fix package schema validation (#12116) Co-authored-by: J. Nick Koston --- esphome/components/packages/__init__.py | 70 ++++++++++++++----- .../component_tests/packages/test_packages.py | 39 +++++++++-- 2 files changed, 87 insertions(+), 22 deletions(-) diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 04057c07f2..41cde0391b 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -1,3 +1,4 @@ +import logging from pathlib import Path from esphome import git, yaml_util @@ -20,18 +21,41 @@ from esphome.const import ( ) from esphome.core import EsphomeError +_LOGGER = logging.getLogger(__name__) + DOMAIN = CONF_PACKAGES -def validate_git_package(config: dict): - if CONF_URL not in config: - return config - config = BASE_SCHEMA(config) - new_config = config +def valid_package_contents(package_config: dict): + """Validates that a package_config that will be merged looks as much as possible to a valid config + to fail early on obvious mistakes.""" + if isinstance(package_config, dict): + if CONF_URL in package_config: + # If a URL key is found, then make sure the config conforms to a remote package schema: + return REMOTE_PACKAGE_SCHEMA(package_config) + + # Validate manually since Voluptuous would regenerate dicts and lose metadata + # such as ESPHomeDataBase + for k, v in package_config.items(): + if not isinstance(k, str): + raise cv.Invalid("Package content keys must be strings") + if isinstance(v, (dict, list)): + continue # e.g. script: [] or logger: {level: debug} + if v is None: + continue # e.g. web_server: + raise cv.Invalid("Invalid component content in package definition") + return package_config + + raise cv.Invalid("Package contents must be a dict") + + +def expand_file_to_files(config: dict): if CONF_FILE in config: + new_config = config new_config[CONF_FILES] = [config[CONF_FILE]] del new_config[CONF_FILE] - return new_config + return new_config + return config def validate_yaml_filename(value): @@ -45,7 +69,7 @@ def validate_yaml_filename(value): def validate_source_shorthand(value): if not isinstance(value, str): - raise cv.Invalid("Shorthand only for strings") + raise cv.Invalid("Git URL shorthand only for strings") git_file = git.GitFile.from_shorthand(value) @@ -56,10 +80,17 @@ def validate_source_shorthand(value): if git_file.ref: conf[CONF_REF] = git_file.ref - return BASE_SCHEMA(conf) + return REMOTE_PACKAGE_SCHEMA(conf) -BASE_SCHEMA = cv.All( +def deprecate_single_package(config): + _LOGGER.warning( + "Including a single package under `packages:` is deprecated. Use a list instead." + ) + return config + + +REMOTE_PACKAGE_SCHEMA = cv.All( cv.Schema( { cv.Required(CONF_URL): cv.url, @@ -90,23 +121,30 @@ BASE_SCHEMA = cv.All( } ), cv.has_at_least_one_key(CONF_FILE, CONF_FILES), + expand_file_to_files, ) -PACKAGE_SCHEMA = cv.All( - cv.Any(validate_source_shorthand, BASE_SCHEMA, dict), validate_git_package +PACKAGE_SCHEMA = cv.Any( # A package definition is either: + validate_source_shorthand, # A git URL shorthand string that expands to a remote package schema, or + REMOTE_PACKAGE_SCHEMA, # a valid remote package schema, or + valid_package_contents, # Something that at least looks like an actual package, e.g. {wifi:{ssid: xxx}} + # which will have to be fully validated later as per each component's schema. ) -CONFIG_SCHEMA = cv.Any( +CONFIG_SCHEMA = cv.Any( # under `packages:` we can have either: cv.Schema( { - str: PACKAGE_SCHEMA, + str: PACKAGE_SCHEMA, # a named dict of package definitions, or } ), - [PACKAGE_SCHEMA], + [PACKAGE_SCHEMA], # a list of package definitions, or + cv.All( # a single package definition (deprecated) + cv.ensure_list(PACKAGE_SCHEMA), deprecate_single_package + ), ) -def _process_base_package(config: dict, skip_update: bool = False) -> dict: +def _process_remote_package(config: dict, skip_update: bool = False) -> dict: # When skip_update is True, use NEVER_REFRESH to prevent updates actual_refresh = git.NEVER_REFRESH if skip_update else config[CONF_REFRESH] repo_dir, revert = git.clone_or_update( @@ -185,7 +223,7 @@ def _process_base_package(config: dict, skip_update: bool = False) -> dict: def _process_package(package_config, config, skip_update: bool = False): recursive_package = package_config if CONF_URL in package_config: - package_config = _process_base_package(package_config, skip_update) + package_config = _process_remote_package(package_config, skip_update) if isinstance(package_config, dict): recursive_package = do_packages_pass(package_config, skip_update) return merge_config(recursive_package, config) diff --git a/tests/component_tests/packages/test_packages.py b/tests/component_tests/packages/test_packages.py index 1c4c91aa52..ac4e211fe6 100644 --- a/tests/component_tests/packages/test_packages.py +++ b/tests/component_tests/packages/test_packages.py @@ -95,7 +95,7 @@ def test_package_invalid_dict(basic_esphome, basic_wifi): @pytest.mark.parametrize( - "package", + "packages", [ {"package1": "github://esphome/non-existant-repo/file1.yml@main"}, {"package2": "github://esphome/non-existant-repo/file1.yml"}, @@ -107,12 +107,12 @@ def test_package_invalid_dict(basic_esphome, basic_wifi): ], ], ) -def test_package_shorthand(package): - CONFIG_SCHEMA(package) +def test_package_shorthand(packages): + CONFIG_SCHEMA(packages) @pytest.mark.parametrize( - "package", + "packages", [ # not github {"package1": "someplace://esphome/non-existant-repo/file1.yml@main"}, @@ -133,9 +133,9 @@ def test_package_shorthand(package): [3], ], ) -def test_package_invalid(package): +def test_package_invalid(packages): with pytest.raises(cv.Invalid): - CONFIG_SCHEMA(package) + CONFIG_SCHEMA(packages) def test_package_include(basic_wifi, basic_esphome): @@ -155,6 +155,33 @@ def test_package_include(basic_wifi, basic_esphome): assert actual == expected +def test_single_package( + basic_esphome, + basic_wifi, + caplog: pytest.LogCaptureFixture, +): + """ + Tests the simple case where a single package is added to the top-level config as is. + In this test, the CONF_WIFI config is expected to be simply added to the top-level config. + This tests the case where the user just put packages: !include package.yaml, not + part of a list or mapping of packages. + This behavior is deprecated, the test also checks if a warning is issued. + """ + config = {CONF_ESPHOME: basic_esphome, CONF_PACKAGES: {CONF_WIFI: basic_wifi}} + + expected = {CONF_ESPHOME: basic_esphome, CONF_WIFI: basic_wifi} + + with caplog.at_level("WARNING"): + actual = packages_pass(config) + + assert actual == expected + + assert ( + "Including a single package under `packages:` is deprecated. Use a list instead." + in caplog.text + ) + + def test_package_append(basic_wifi, basic_esphome): """ Tests the case where a key is present in both a package and top-level config. From 083886c4b05a95aa117ba4dfafac17b45908b681 Mon Sep 17 00:00:00 2001 From: Pawelo <81100874+pgolawsk@users.noreply.github.com> Date: Wed, 26 Nov 2025 19:06:51 +0100 Subject: [PATCH 0395/1145] [prometheus] Avoid generating unused light color metrics to reduce memory usage on ESP8266 (#9530) Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- .../prometheus/prometheus_handler.cpp | 135 ++++++++---------- .../prometheus/prometheus_handler.h | 8 ++ tests/components/prometheus/common.yaml | 27 ++++ 3 files changed, 92 insertions(+), 78 deletions(-) diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 812b547860..252b477400 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -141,6 +141,24 @@ void PrometheusHandler::add_friendly_name_label_(AsyncResponseStream *stream, st } } +#ifdef USE_ESP8266 +void PrometheusHandler::print_metric_labels_(AsyncResponseStream *stream, const __FlashStringHelper *metric_name, + EntityBase *obj, std::string &area, std::string &node, + std::string &friendly_name) { +#else +void PrometheusHandler::print_metric_labels_(AsyncResponseStream *stream, const char *metric_name, EntityBase *obj, + std::string &area, std::string &node, std::string &friendly_name) { +#endif + stream->print(metric_name); + stream->print(ESPHOME_F("{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(ESPHOME_F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); +} + // Type-specific implementation #ifdef USE_SENSOR void PrometheusHandler::sensor_type_(AsyncResponseStream *stream) { @@ -303,13 +321,7 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat if (obj->is_internal() && !this->include_internal_) return; // State - stream->print(ESPHOME_F("esphome_light_state{id=\"")); - stream->print(relabel_id_(obj).c_str()); - add_area_label_(stream, area); - add_node_label_(stream, node); - add_friendly_name_label_(stream, friendly_name); - stream->print(ESPHOME_F("\",name=\"")); - stream->print(relabel_name_(obj).c_str()); + print_metric_labels_(stream, ESPHOME_F("esphome_light_state"), obj, area, node, friendly_name); stream->print(ESPHOME_F("\"} ")); stream->print(obj->remote_values.is_on()); stream->print(ESPHOME_F("\n")); @@ -318,78 +330,45 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat float brightness, r, g, b, w; color.as_brightness(&brightness); color.as_rgbw(&r, &g, &b, &w); - stream->print(ESPHOME_F("esphome_light_color{id=\"")); - stream->print(relabel_id_(obj).c_str()); - add_area_label_(stream, area); - add_node_label_(stream, node); - add_friendly_name_label_(stream, friendly_name); - stream->print(ESPHOME_F("\",name=\"")); - stream->print(relabel_name_(obj).c_str()); - stream->print(ESPHOME_F("\",channel=\"brightness\"} ")); - stream->print(brightness); - stream->print(ESPHOME_F("\n")); - stream->print(ESPHOME_F("esphome_light_color{id=\"")); - stream->print(relabel_id_(obj).c_str()); - add_area_label_(stream, area); - add_node_label_(stream, node); - add_friendly_name_label_(stream, friendly_name); - stream->print(ESPHOME_F("\",name=\"")); - stream->print(relabel_name_(obj).c_str()); - stream->print(ESPHOME_F("\",channel=\"r\"} ")); - stream->print(r); - stream->print(ESPHOME_F("\n")); - stream->print(ESPHOME_F("esphome_light_color{id=\"")); - stream->print(relabel_id_(obj).c_str()); - add_area_label_(stream, area); - add_node_label_(stream, node); - add_friendly_name_label_(stream, friendly_name); - stream->print(ESPHOME_F("\",name=\"")); - stream->print(relabel_name_(obj).c_str()); - stream->print(ESPHOME_F("\",channel=\"g\"} ")); - stream->print(g); - stream->print(ESPHOME_F("\n")); - stream->print(ESPHOME_F("esphome_light_color{id=\"")); - stream->print(relabel_id_(obj).c_str()); - add_area_label_(stream, area); - add_node_label_(stream, node); - add_friendly_name_label_(stream, friendly_name); - stream->print(ESPHOME_F("\",name=\"")); - stream->print(relabel_name_(obj).c_str()); - stream->print(ESPHOME_F("\",channel=\"b\"} ")); - stream->print(b); - stream->print(ESPHOME_F("\n")); - stream->print(ESPHOME_F("esphome_light_color{id=\"")); - stream->print(relabel_id_(obj).c_str()); - add_area_label_(stream, area); - add_node_label_(stream, node); - add_friendly_name_label_(stream, friendly_name); - stream->print(ESPHOME_F("\",name=\"")); - stream->print(relabel_name_(obj).c_str()); - stream->print(ESPHOME_F("\",channel=\"w\"} ")); - stream->print(w); - stream->print(ESPHOME_F("\n")); - // Effect - std::string effect = obj->get_effect_name(); - if (effect == "None") { - stream->print(ESPHOME_F("esphome_light_effect_active{id=\"")); - stream->print(relabel_id_(obj).c_str()); - add_area_label_(stream, area); - add_node_label_(stream, node); - add_friendly_name_label_(stream, friendly_name); - stream->print(ESPHOME_F("\",name=\"")); - stream->print(relabel_name_(obj).c_str()); - stream->print(ESPHOME_F("\",effect=\"None\"} 0\n")); - } else { - stream->print(ESPHOME_F("esphome_light_effect_active{id=\"")); - stream->print(relabel_id_(obj).c_str()); - add_area_label_(stream, area); - add_node_label_(stream, node); - add_friendly_name_label_(stream, friendly_name); - stream->print(ESPHOME_F("\",name=\"")); - stream->print(relabel_name_(obj).c_str()); + if (obj->get_traits().supports_color_capability(light::ColorCapability::BRIGHTNESS)) { + print_metric_labels_(stream, ESPHOME_F("esphome_light_color"), obj, area, node, friendly_name); + stream->print(ESPHOME_F("\",channel=\"brightness\"} ")); + stream->print(brightness); + stream->print(ESPHOME_F("\n")); + } + if (obj->get_traits().supports_color_capability(light::ColorCapability::RGB)) { + print_metric_labels_(stream, ESPHOME_F("esphome_light_color"), obj, area, node, friendly_name); + stream->print(ESPHOME_F("\",channel=\"r\"} ")); + stream->print(r); + stream->print(ESPHOME_F("\n")); + print_metric_labels_(stream, ESPHOME_F("esphome_light_color"), obj, area, node, friendly_name); + stream->print(ESPHOME_F("\",channel=\"g\"} ")); + stream->print(g); + stream->print(ESPHOME_F("\n")); + print_metric_labels_(stream, ESPHOME_F("esphome_light_color"), obj, area, node, friendly_name); + stream->print(ESPHOME_F("\",channel=\"b\"} ")); + stream->print(b); + stream->print(ESPHOME_F("\n")); + } + if (obj->get_traits().supports_color_capability(light::ColorCapability::WHITE)) { + print_metric_labels_(stream, ESPHOME_F("esphome_light_color"), obj, area, node, friendly_name); + stream->print(ESPHOME_F("\",channel=\"w\"} ")); + stream->print(w); + stream->print(ESPHOME_F("\n")); + } + // Skip effect metrics if light has no effects + if (!obj->get_effects().empty()) { + // Effect + std::string effect = obj->get_effect_name(); + print_metric_labels_(stream, ESPHOME_F("esphome_light_effect_active"), obj, area, node, friendly_name); stream->print(ESPHOME_F("\",effect=\"")); - stream->print(effect.c_str()); - stream->print(ESPHOME_F("\"} 1\n")); + // Only vary based on effect + if (effect == "None") { + stream->print(ESPHOME_F("None\"} 0\n")); + } else { + stream->print(effect.c_str()); + stream->print(ESPHOME_F("\"} 1\n")); + } } } #endif diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 45cc81b899..24243c8c98 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -66,6 +66,14 @@ class PrometheusHandler : public AsyncWebHandler, public Component { void add_area_label_(AsyncResponseStream *stream, std::string &area); void add_node_label_(AsyncResponseStream *stream, std::string &node); void add_friendly_name_label_(AsyncResponseStream *stream, std::string &friendly_name); + /// Print metric name and common labels (id, area, node, friendly_name, name) +#ifdef USE_ESP8266 + void print_metric_labels_(AsyncResponseStream *stream, const __FlashStringHelper *metric_name, EntityBase *obj, + std::string &area, std::string &node, std::string &friendly_name); +#else + void print_metric_labels_(AsyncResponseStream *stream, const char *metric_name, EntityBase *obj, std::string &area, + std::string &node, std::string &friendly_name); +#endif #ifdef USE_SENSOR /// Return the type for prometheus diff --git a/tests/components/prometheus/common.yaml b/tests/components/prometheus/common.yaml index 0b90d614dd..7ff416dccb 100644 --- a/tests/components/prometheus/common.yaml +++ b/tests/components/prometheus/common.yaml @@ -112,6 +112,25 @@ cover: } return COVER_CLOSED; +light: + - platform: binary + name: "Binary Light" + output: test_output + - platform: monochromatic + name: "Brightness Light" + output: test_output + - platform: rgb + name: "RGB Light" + red: test_output + green: test_output + blue: test_output + - platform: rgbw + name: "RGBW Light" + red: test_output + green: test_output + blue: test_output + white: test_output + lock: - platform: template id: template_lock1 @@ -122,6 +141,14 @@ lock: return LOCK_STATE_UNLOCKED; optimistic: true +output: + - platform: template + id: test_output + type: float + write_action: + - lambda: |- + // no-op for CI/build tests + (void)state; select: - platform: template id: template_select1 From eb970cf44ec07ea45596f837c573e4fec7ff09d7 Mon Sep 17 00:00:00 2001 From: Jon Oberheide <506986+jonoberheide@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:56:22 -0500 Subject: [PATCH 0396/1145] make thermostat humidification_action public (#12132) --- esphome/components/thermostat/thermostat_climate.cpp | 8 ++++---- esphome/components/thermostat/thermostat_climate.h | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 2b51f58f4f..e79eed4055 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -654,7 +654,7 @@ void ThermostatClimate::trigger_supplemental_action_() { void ThermostatClimate::switch_to_humidity_control_action_(HumidificationAction action) { // setup_complete_ helps us ensure an action is called immediately after boot - if ((action == this->humidification_action_) && this->setup_complete_) { + if ((action == this->humidification_action) && this->setup_complete_) { // already in target mode return; } @@ -683,7 +683,7 @@ void ThermostatClimate::switch_to_humidity_control_action_(HumidificationAction this->prev_humidity_control_trigger_->stop_action(); this->prev_humidity_control_trigger_ = nullptr; } - this->humidification_action_ = action; + this->humidification_action = action; this->prev_humidity_control_trigger_ = trig; if (trig != nullptr) { trig->trigger(); @@ -1114,7 +1114,7 @@ bool ThermostatClimate::dehumidification_required_() { } // if we get here, the current humidity is between target + hysteresis and target - hysteresis, // so the action should not change - return this->humidification_action_ == THERMOSTAT_HUMIDITY_CONTROL_ACTION_DEHUMIDIFY; + return this->humidification_action == THERMOSTAT_HUMIDITY_CONTROL_ACTION_DEHUMIDIFY; } bool ThermostatClimate::humidification_required_() { @@ -1127,7 +1127,7 @@ bool ThermostatClimate::humidification_required_() { } // if we get here, the current humidity is between target - hysteresis and target + hysteresis, // so the action should not change - return this->humidification_action_ == THERMOSTAT_HUMIDITY_CONTROL_ACTION_HUMIDIFY; + return this->humidification_action == THERMOSTAT_HUMIDITY_CONTROL_ACTION_HUMIDIFY; } void ThermostatClimate::dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config) { diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 76391f800c..69d2307b1c 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -207,6 +207,9 @@ class ThermostatClimate : public climate::Climate, public Component { void validate_target_temperature_high(); void validate_target_humidity(); + /// The current humidification action + HumidificationAction humidification_action{THERMOSTAT_HUMIDITY_CONTROL_ACTION_NONE}; + protected: /// Override control to change settings of the climate device. void control(const climate::ClimateCall &call) override; @@ -301,9 +304,6 @@ class ThermostatClimate : public climate::Climate, public Component { /// The current supplemental action climate::ClimateAction supplemental_action_{climate::CLIMATE_ACTION_OFF}; - /// The current humidification action - HumidificationAction humidification_action_{THERMOSTAT_HUMIDITY_CONTROL_ACTION_NONE}; - /// Default standard preset to use on start up climate::ClimatePreset default_preset_{}; From caaa08d678f43ebcad29e837dfc25137f21d2776 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:05:45 +1000 Subject: [PATCH 0397/1145] [core] Fix for missing arguments to shared_lambda (#12115) --- esphome/components/lvgl/widgets/__init__.py | 4 ++-- esphome/cpp_generator.py | 8 +------- tests/components/lvgl/lvgl-package.yaml | 12 ++++++++++++ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/esphome/components/lvgl/widgets/__init__.py b/esphome/components/lvgl/widgets/__init__.py index 2e7948522e..b1d157325b 100644 --- a/esphome/components/lvgl/widgets/__init__.py +++ b/esphome/components/lvgl/widgets/__init__.py @@ -382,7 +382,7 @@ async def set_obj_properties(w: Widget, config): clrs = join_enums(flag_clr, "LV_OBJ_FLAG_") w.clear_flag(clrs) for key, value in lambs.items(): - lamb = await cg.process_lambda(value, [], return_type=cg.bool_) + lamb = await cg.process_lambda(value, [], capture="=", return_type=cg.bool_) flag = f"LV_OBJ_FLAG_{key.upper()}" with LvConditional(call_lambda(lamb)) as cond: w.add_flag(flag) @@ -407,7 +407,7 @@ async def set_obj_properties(w: Widget, config): clears = join_enums(clears, "LV_STATE_") w.clear_state(clears) for key, value in lambs.items(): - lamb = await cg.process_lambda(value, [], return_type=cg.bool_) + lamb = await cg.process_lambda(value, [], capture="=", return_type=cg.bool_) state = f"LV_STATE_{key.upper()}" with LvConditional(call_lambda(lamb)) as cond: w.add_state(state) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 6f1af01a5b..1a47b346b7 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -659,7 +659,7 @@ async def get_variable_with_full_id(id_: ID) -> tuple[ID, "MockObj"]: async def process_lambda( value: Lambda, parameters: TemplateArgsType, - capture: str = "=", + capture: str = "", return_type: SafeExpType = None, ) -> LambdaExpression | None: """Process the given lambda value into a LambdaExpression. @@ -702,12 +702,6 @@ async def process_lambda( parts[i * 3 + 1] = var parts[i * 3 + 2] = "" - # All id() references are global variables in generated C++ code. - # Global variables should not be captured - they're accessible everywhere. - # Use empty capture instead of capture-by-value. - if capture == "=": - capture = "" - if isinstance(value, ESPHomeDataBase) and value.esp_range is not None: location = value.esp_range.start_mark location.line += value.content_offset diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 30866a603c..a5714d5639 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -16,6 +16,18 @@ binary_sensor: platform: template - id: left_sensor platform: template + - platform: lvgl + id: button_checker + name: LVGL button + widget: button_button + on_state: + then: + - lvgl.checkbox.update: + id: checkbox_id + state: + checked: !lambda |- + auto y = x; // block inlining of one line return + return y; lvgl: log_level: debug From a2d9941c622388f827570077dc6ab45d1d80112f Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:06:32 +1000 Subject: [PATCH 0398/1145] [lvgl] Add option to sync updates with display (#11896) Co-authored-by: J. Nick Koston --- esphome/components/lvgl/__init__.py | 4 ++ esphome/components/lvgl/defines.py | 1 + esphome/components/lvgl/lvgl_esphome.cpp | 48 +++++++++++++++++++---- esphome/components/lvgl/lvgl_esphome.h | 13 +++--- tests/components/lvgl/test.esp32-idf.yaml | 1 + 5 files changed, 55 insertions(+), 12 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index eaa37b54dd..eeabf755a6 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -276,6 +276,7 @@ async def to_code(configs): config[df.CONF_FULL_REFRESH], config[CONF_DRAW_ROUNDING], config[df.CONF_RESUME_ON_INPUT], + config[df.CONF_UPDATE_WHEN_DISPLAY_IDLE], ) await cg.register_component(lv_component, config) Widget.create(config[CONF_ID], lv_component, LvScrActType(), config) @@ -373,6 +374,9 @@ LVGL_SCHEMA = cv.All( df.CONF_DEFAULT_FONT, default="montserrat_14" ): lvalid.lv_font, cv.Optional(df.CONF_FULL_REFRESH, default=False): cv.boolean, + cv.Optional( + df.CONF_UPDATE_WHEN_DISPLAY_IDLE, default=False + ): cv.boolean, cv.Optional(CONF_DRAW_ROUNDING, default=2): cv.positive_int, cv.Optional(CONF_BUFFER_SIZE, default=0): cv.percentage, cv.Optional(CONF_LOG_LEVEL, default="WARN"): cv.one_of( diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 6b3b9c97ef..1fce6fa458 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -542,6 +542,7 @@ CONF_TOUCHSCREENS = "touchscreens" CONF_TRANSPARENCY_KEY = "transparency_key" CONF_THEME = "theme" CONF_UPDATE_ON_RELEASE = "update_on_release" +CONF_UPDATE_WHEN_DISPLAY_IDLE = "update_when_display_idle" CONF_VISIBLE_ROW_COUNT = "visible_row_count" CONF_WIDGET = "widget" CONF_WIDGETS = "widgets" diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index fbcd68378c..18226a9f57 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -106,6 +106,7 @@ void LvglComponent::dump_config() { this->disp_drv_.hor_res, this->disp_drv_.ver_res, 100 / this->buffer_frac_, this->rotation, (int) this->draw_rounding); } + void LvglComponent::set_paused(bool paused, bool show_snow) { this->paused_ = paused; this->show_snow_ = show_snow; @@ -124,32 +125,38 @@ void LvglComponent::esphome_lvgl_init() { lv_update_event = static_cast(lv_event_register_id()); lv_api_event = static_cast(lv_event_register_id()); } + void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event) { lv_obj_add_event_cb(obj, callback, event, nullptr); } + void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2) { add_event_cb(obj, callback, event1); add_event_cb(obj, callback, event2); } + void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2, lv_event_code_t event3) { add_event_cb(obj, callback, event1); add_event_cb(obj, callback, event2); add_event_cb(obj, callback, event3); } + void LvglComponent::add_page(LvPageType *page) { this->pages_.push_back(page); page->set_parent(this); lv_disp_set_default(this->disp_); page->setup(this->pages_.size() - 1); } + void LvglComponent::show_page(size_t index, lv_scr_load_anim_t anim, uint32_t time) { if (index >= this->pages_.size()) return; this->current_page_ = index; lv_scr_load_anim(this->pages_[this->current_page_]->obj, anim, time, 0, false); } + void LvglComponent::show_next_page(lv_scr_load_anim_t anim, uint32_t time) { if (this->pages_.empty() || (this->current_page_ == this->pages_.size() - 1 && !this->page_wrap_)) return; @@ -158,6 +165,7 @@ void LvglComponent::show_next_page(lv_scr_load_anim_t anim, uint32_t time) { } while (this->pages_[this->current_page_]->skip); // skip empty pages() this->show_page(this->current_page_, anim, time); } + void LvglComponent::show_prev_page(lv_scr_load_anim_t anim, uint32_t time) { if (this->pages_.empty() || (this->current_page_ == 0 && !this->page_wrap_)) return; @@ -166,8 +174,10 @@ void LvglComponent::show_prev_page(lv_scr_load_anim_t anim, uint32_t time) { } while (this->pages_[this->current_page_]->skip); // skip empty pages() this->show_page(this->current_page_, anim, time); } + size_t LvglComponent::get_current_page() const { return this->current_page_; } bool LvPageType::is_showing() const { return this->parent_->get_current_page() == this->index; } + void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) { auto width = lv_area_get_width(area); auto height = lv_area_get_height(area); @@ -222,7 +232,7 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) { } void LvglComponent::flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { - if (!this->paused_) { + if (!this->is_paused()) { auto now = millis(); this->draw_buffer_(area, color_p); ESP_LOGVV(TAG, "flush_cb, area=%d/%d, %d/%d took %dms", area->x1, area->y1, lv_area_get_width(area), @@ -230,6 +240,7 @@ void LvglComponent::flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv } lv_disp_flush_ready(disp_drv); } + IdleTrigger::IdleTrigger(LvglComponent *parent, TemplatableValue timeout) : timeout_(std::move(timeout)) { parent->add_on_idle_callback([this](uint32_t idle_time) { if (!this->is_idle_ && idle_time > this->timeout_.value()) { @@ -377,6 +388,27 @@ void LvKeyboardType::set_obj(lv_obj_t *lv_obj) { } #endif // USE_LVGL_KEYBOARD +void LvglComponent::draw_end_() { + if (this->draw_end_callback_ != nullptr) + this->draw_end_callback_->trigger(); + if (this->update_when_display_idle_) { + for (auto *disp : this->displays_) + disp->update(); + } +} + +bool LvglComponent::is_paused() const { + if (this->paused_) + return true; + if (this->update_when_display_idle_) { + for (auto *disp : this->displays_) { + if (!disp->is_idle()) + return true; + } + } + return false; +} + void LvglComponent::write_random_() { int iterations = 6 - lv_disp_get_inactive_time(this->disp_) / 60000; if (iterations <= 0) @@ -426,12 +458,13 @@ void LvglComponent::write_random_() { * presses a key or clicks on the screen. */ LvglComponent::LvglComponent(std::vector displays, float buffer_frac, bool full_refresh, - int draw_rounding, bool resume_on_input) + int draw_rounding, bool resume_on_input, bool update_when_display_idle) : draw_rounding(draw_rounding), displays_(std::move(displays)), buffer_frac_(buffer_frac), full_refresh_(full_refresh), - resume_on_input_(resume_on_input) { + resume_on_input_(resume_on_input), + update_when_display_idle_(update_when_display_idle) { lv_disp_draw_buf_init(&this->draw_buf_, nullptr, nullptr, 0); lv_disp_drv_init(&this->disp_drv_); this->disp_drv_.draw_buf = &this->draw_buf_; @@ -487,7 +520,7 @@ void LvglComponent::setup() { if (this->draw_start_callback_ != nullptr) { this->disp_drv_.render_start_cb = render_start_cb; } - if (this->draw_end_callback_ != nullptr) { + if (this->draw_end_callback_ != nullptr || this->update_when_display_idle_) { this->disp_drv_.monitor_cb = monitor_cb; } #if LV_USE_LOG @@ -509,14 +542,15 @@ void LvglComponent::setup() { void LvglComponent::update() { // update indicators - if (this->paused_) { + if (this->is_paused()) { return; } this->idle_callbacks_.call(lv_disp_get_inactive_time(this->disp_)); } + void LvglComponent::loop() { - if (this->paused_) { - if (this->show_snow_) + if (this->is_paused()) { + if (this->paused_ && this->show_snow_) this->write_random_(); } else { lv_timer_handler_run_in_period(5); diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index bd6f1fdb61..9c82f3646b 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -151,7 +151,7 @@ class LvglComponent : public PollingComponent { public: LvglComponent(std::vector displays, float buffer_frac, bool full_refresh, int draw_rounding, - bool resume_on_input); + bool resume_on_input, bool update_when_display_idle); static void static_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p); float get_setup_priority() const override { return setup_priority::PROCESSOR; } @@ -171,7 +171,9 @@ class LvglComponent : public PollingComponent { // @param paused If true, pause the display. If false, resume the display. // @param show_snow If true, show the snow effect when paused. void set_paused(bool paused, bool show_snow); - bool is_paused() const { return this->paused_; } + + // Returns true if the display is explicitly paused, or a blocking display update is in progress. + bool is_paused() const; // If the display is paused and we have resume_on_input_ set to true, resume the display. void maybe_wakeup() { if (this->paused_ && this->resume_on_input_) { @@ -210,10 +212,10 @@ class LvglComponent : public PollingComponent { void set_draw_end_trigger(Trigger<> *trigger) { this->draw_end_callback_ = trigger; } protected: - // these functions are never called unless the callbacks are non-null since the - // LVGL callbacks that call them are not set unless the start/end callbacks are non-null + void draw_end_(); + // Not checking for non-null callback since the + // LVGL callback that calls it is not set in that case void draw_start_() const { this->draw_start_callback_->trigger(); } - void draw_end_() const { this->draw_end_callback_->trigger(); } void write_random_(); void draw_buffer_(const lv_area_t *area, lv_color_t *ptr); @@ -222,6 +224,7 @@ class LvglComponent : public PollingComponent { size_t buffer_frac_{1}; bool full_refresh_{}; bool resume_on_input_{}; + bool update_when_display_idle_{}; lv_disp_draw_buf_t draw_buf_{}; lv_disp_drv_t disp_drv_{}; diff --git a/tests/components/lvgl/test.esp32-idf.yaml b/tests/components/lvgl/test.esp32-idf.yaml index 2450d28eb8..e6025e17fc 100644 --- a/tests/components/lvgl/test.esp32-idf.yaml +++ b/tests/components/lvgl/test.esp32-idf.yaml @@ -60,6 +60,7 @@ display: update_interval: never lvgl: + update_when_display_idle: true displays: - tft_display - second_display From 927d3715c1ab16d5963ee404790b5ae142b1b613 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:06:40 +1000 Subject: [PATCH 0399/1145] [lvgl] Allow setting text directly on a button (#11964) Co-authored-by: J. Nick Koston --- esphome/components/lvgl/__init__.py | 18 +++++++--- esphome/components/lvgl/defines.py | 22 ++++++++++-- esphome/components/lvgl/schemas.py | 35 ++++++++++++++----- esphome/components/lvgl/types.py | 16 ++++++--- esphome/components/lvgl/widgets/button.py | 42 ++++++++++++++++++++--- tests/components/lvgl/lvgl-package.yaml | 8 +++++ 6 files changed, 115 insertions(+), 26 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index eeabf755a6..040661495c 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -108,7 +108,7 @@ LV_CONF_H_FORMAT = """\ def generate_lv_conf_h(): - definitions = [as_macro(m, v) for m, v in df.lv_defines.items()] + definitions = [as_macro(m, v) for m, v in df.get_data(df.KEY_LV_DEFINES).items()] definitions.sort() return LV_CONF_H_FORMAT.format("\n".join(definitions)) @@ -140,11 +140,11 @@ def multi_conf_validate(configs: list[dict]): ) -def final_validation(configs): - if len(configs) != 1: - multi_conf_validate(configs) +def final_validation(config_list): + if len(config_list) != 1: + multi_conf_validate(config_list) global_config = full_config.get() - for config in configs: + for config in config_list: if (pages := config.get(CONF_PAGES)) and all(p[df.CONF_SKIP] for p in pages): raise cv.Invalid("At least one page must not be skipped") for display_id in config[df.CONF_DISPLAYS]: @@ -190,6 +190,14 @@ def final_validation(configs): raise cv.Invalid( f"Widget '{w}' does not have any dynamic properties to refresh", ) + # Do per-widget type final validation for update actions + for widget_type, update_configs in df.get_data(df.KEY_UPDATED_WIDGETS).items(): + for conf in update_configs: + for id_conf in conf.get(CONF_ID, ()): + name = id_conf[CONF_ID] + path = global_config.get_path_for_id(name) + widget_conf = global_config.get_config_for_path(path[:-1]) + widget_type.final_validate(name, conf, widget_conf, path[1:]) async def to_code(configs): diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 1fce6fa458..1d528b2f73 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_ITEMS -from esphome.core import ID, Lambda +from esphome.core import CORE, ID, Lambda from esphome.cpp_generator import LambdaExpression, MockObj from esphome.cpp_types import uint32 from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor @@ -20,11 +20,27 @@ from .helpers import requires_component LOGGER = logging.getLogger(__name__) lvgl_ns = cg.esphome_ns.namespace("lvgl") -lv_defines = {} # Dict of #defines to provide as build flags +DOMAIN = "lvgl" +KEY_LV_DEFINES = "lv_defines" +KEY_UPDATED_WIDGETS = "updated_widgets" + + +def get_data(key, default=None): + """ + Get a data structure from the global data store by key + :param key: A key for the data + :param default: The default data - the default is an empty dict + :return: + """ + return CORE.data.setdefault(DOMAIN, {}).setdefault( + key, default if default is not None else {} + ) def add_define(macro, value="1"): - if macro in lv_defines and lv_defines[macro] != value: + lv_defines = get_data(KEY_LV_DEFINES) + value = str(value) + if lv_defines.setdefault(macro, value) != value: LOGGER.error( "Redefinition of %s - was %s now %s", macro, lv_defines[macro], value ) diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index f2704f99de..45d933c00e 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -1,3 +1,5 @@ +from collections.abc import Callable + from esphome import config_validation as cv from esphome.automation import Trigger, validate_automation from esphome.components.time import RealTimeClock @@ -311,19 +313,36 @@ def automation_schema(typ: LvType): } -def base_update_schema(widget_type, parts): +def _update_widget(widget_type: WidgetType) -> Callable[[dict], dict]: """ - Create a schema for updating a widgets style properties, states and flags + During validation of update actions, create a map of action types to affected widgets + for use in final validation. + :param widget_type: + :return: + """ + + def validator(value: dict) -> dict: + df.get_data(df.KEY_UPDATED_WIDGETS).setdefault(widget_type, []).append(value) + return value + + return validator + + +def base_update_schema(widget_type: WidgetType | LvType, parts): + """ + Create a schema for updating a widget's style properties, states and flags. :param widget_type: The type of the ID :param parts: The allowable parts to specify :return: """ - return part_schema(parts).extend( + + w_type = widget_type.w_type if isinstance(widget_type, WidgetType) else widget_type + schema = part_schema(parts).extend( { cv.Required(CONF_ID): cv.ensure_list( cv.maybe_simple_value( { - cv.Required(CONF_ID): cv.use_id(widget_type), + cv.Required(CONF_ID): cv.use_id(w_type), }, key=CONF_ID, ) @@ -332,11 +351,9 @@ def base_update_schema(widget_type, parts): } ) - -def create_modify_schema(widget_type): - return base_update_schema(widget_type.w_type, widget_type.parts).extend( - widget_type.modify_schema - ) + if isinstance(widget_type, WidgetType): + schema.add_extra(_update_widget(widget_type)) + return schema def obj_schema(widget_type: WidgetType): diff --git a/esphome/components/lvgl/types.py b/esphome/components/lvgl/types.py index b99c0ad5a3..9c92ca7e98 100644 --- a/esphome/components/lvgl/types.py +++ b/esphome/components/lvgl/types.py @@ -152,18 +152,18 @@ class WidgetType: # Local import to avoid circular import from .automation import update_to_code - from .schemas import WIDGET_TYPES, create_modify_schema + from .schemas import WIDGET_TYPES, base_update_schema if not is_mock: if self.name in WIDGET_TYPES: raise EsphomeError(f"Duplicate definition of widget type '{self.name}'") WIDGET_TYPES[self.name] = self - # Register the update action automatically + # Register the update action automatically, adding widget-specific properties register_action( f"lvgl.{self.name}.update", ObjUpdateAction, - create_modify_schema(self), + base_update_schema(self, self.parts).extend(self.modify_schema), )(update_to_code) @property @@ -182,7 +182,6 @@ class WidgetType: Generate code for a given widget :param w: The widget :param config: Its configuration - :return: Generated code as a list of text lines """ async def obj_creator(self, parent: MockObjClass, config: dict): @@ -228,6 +227,15 @@ class WidgetType: """ return value + def final_validate(self, widget, update_config, widget_config, path): + """ + Allow final validation for a given widget type update action + :param widget: A widget + :param update_config: The configuration for the update action + :param widget_config: The configuration for the widget itself + :param path: The path to the widget, for error reporting + """ + class NumberType(WidgetType): def get_max(self, config: dict): diff --git a/esphome/components/lvgl/widgets/button.py b/esphome/components/lvgl/widgets/button.py index b59884ee67..5f2910174f 100644 --- a/esphome/components/lvgl/widgets/button.py +++ b/esphome/components/lvgl/widgets/button.py @@ -1,20 +1,52 @@ -from esphome.const import CONF_BUTTON +from esphome import config_validation as cv +from esphome.const import CONF_BUTTON, CONF_TEXT +from esphome.cpp_generator import MockObj -from ..defines import CONF_MAIN +from ..defines import CONF_MAIN, CONF_WIDGETS +from ..helpers import add_lv_use +from ..lv_validation import lv_text +from ..lvcode import lv, lv_expr +from ..schemas import TEXT_SCHEMA from ..types import LvBoolean, WidgetType +from . import Widget +from .label import label_spec lv_button_t = LvBoolean("lv_btn_t") class ButtonType(WidgetType): def __init__(self): - super().__init__(CONF_BUTTON, lv_button_t, (CONF_MAIN,), lv_name="btn") + super().__init__( + CONF_BUTTON, lv_button_t, (CONF_MAIN,), schema=TEXT_SCHEMA, lv_name="btn" + ) + + def validate(self, value): + if CONF_TEXT in value: + if CONF_WIDGETS in value: + raise cv.Invalid("Cannot use both text and widgets in a button") + add_lv_use("label") + return value def get_uses(self): return ("btn",) - async def to_code(self, w, config): - return [] + def on_create(self, var: MockObj, config: dict): + if CONF_TEXT in config: + lv.label_create(var) + return var + + async def to_code(self, w: Widget, config): + if text := config.get(CONF_TEXT): + label_widget = Widget.create( + None, lv_expr.obj_get_child(w.obj, 0), label_spec + ) + await label_widget.set_property(CONF_TEXT, await lv_text.process(text)) + + def final_validate(self, widget, update_config, widget_config, path): + if CONF_TEXT in update_config and CONF_TEXT not in widget_config: + raise cv.Invalid( + "Button must have 'text:' configured to allow updating text", path + ) button_spec = ButtonType() diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index a5714d5639..65d629bcdf 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -426,6 +426,14 @@ lvgl: logger.log: Long pressed repeated - buttons: - id: button_e + - button: + id: button_with_text + text: Button + on_click: + lvgl.button.update: + id: button_with_text + text: Clicked + - button: layout: 2x1 id: button_button From b3955cd151d0f1b8ac488d75ab47dc468d9adde7 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:07:51 +1000 Subject: [PATCH 0400/1145] [epaper_spi] Add SSD1677 and Waveshare 4.26 (#11887) Co-authored-by: J. Nick Koston --- esphome/components/display/display.cpp | 128 +++++++++++++----- esphome/components/epaper_spi/display.py | 90 ++++++++++-- esphome/components/epaper_spi/epaper_spi.cpp | 107 ++++++++++++--- esphome/components/epaper_spi/epaper_spi.h | 80 ++++++++--- .../epaper_spi/epaper_spi_spectra_e6.cpp | 23 ++-- .../epaper_spi/epaper_spi_spectra_e6.h | 4 +- .../epaper_spi/epaper_spi_ssd1677.cpp | 86 ++++++++++++ .../epaper_spi/epaper_spi_ssd1677.h | 25 ++++ .../components/epaper_spi/models/ssd1677.py | 42 ++++++ .../epaper_spi/test.esp32-s3-idf.yaml | 5 + 10 files changed, 488 insertions(+), 102 deletions(-) create mode 100644 esphome/components/epaper_spi/epaper_spi_ssd1677.cpp create mode 100644 esphome/components/epaper_spi/epaper_spi_ssd1677.h create mode 100644 esphome/components/epaper_spi/models/ssd1677.py diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp index 1451d14e2e..ebc3c0a9f6 100644 --- a/esphome/components/display/display.cpp +++ b/esphome/components/display/display.cpp @@ -7,7 +7,6 @@ namespace esphome { namespace display { - static const char *const TAG = "display"; const Color COLOR_OFF(0, 0, 0, 0); @@ -16,6 +15,7 @@ const Color COLOR_ON(255, 255, 255, 255); void Display::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); } void Display::clear() { this->fill(COLOR_OFF); } void Display::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; } + void HOT Display::line(int x1, int y1, int x2, int y2, Color color) { const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1; const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1; @@ -91,23 +91,27 @@ void HOT Display::horizontal_line(int x, int y, int width, Color color) { for (int i = x; i < x + width; i++) this->draw_pixel_at(i, y, color); } + void HOT Display::vertical_line(int x, int y, int height, Color color) { // Future: Could be made more efficient by manipulating buffer directly in certain rotations. for (int i = y; i < y + height; i++) this->draw_pixel_at(x, i, color); } + void Display::rectangle(int x1, int y1, int width, int height, Color color) { this->horizontal_line(x1, y1, width, color); this->horizontal_line(x1, y1 + height - 1, width, color); this->vertical_line(x1, y1, height, color); this->vertical_line(x1 + width - 1, y1, height, color); } + void Display::filled_rectangle(int x1, int y1, int width, int height, Color color) { // Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses. for (int i = y1; i < y1 + height; i++) { this->horizontal_line(x1, i, width, color); } } + void HOT Display::circle(int center_x, int center_xy, int radius, Color color) { int dx = -radius; int dy = 0; @@ -131,6 +135,7 @@ void HOT Display::circle(int center_x, int center_xy, int radius, Color color) { } } while (dx <= 0); } + void Display::filled_circle(int center_x, int center_y, int radius, Color color) { int dx = -int32_t(radius); int dy = 0; @@ -157,6 +162,7 @@ void Display::filled_circle(int center_x, int center_y, int radius, Color color) } } while (dx <= 0); } + void Display::filled_ring(int center_x, int center_y, int radius1, int radius2, Color color) { int rmax = radius1 > radius2 ? radius1 : radius2; int rmin = radius1 < radius2 ? radius1 : radius2; @@ -213,6 +219,7 @@ void Display::filled_ring(int center_x, int center_y, int radius1, int radius2, } } while (dxmax <= 0); } + void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2, int progress, Color color) { int rmax = radius1 > radius2 ? radius1 : radius2; int rmin = radius1 < radius2 ? radius1 : radius2; @@ -228,7 +235,8 @@ void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2, // outer dots this->draw_pixel_at(center_x + dxmax, center_y - dymax, color); this->draw_pixel_at(center_x - dxmax, center_y - dymax, color); - if (dymin < rmin) { // side parts + if (dymin < rmin) { + // side parts int lhline_width = -(dxmax - dxmin) + 1; if (progress >= 50) { if (float(dymax) < float(-dxmax) * tan_a) { @@ -239,7 +247,8 @@ void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2, this->horizontal_line(center_x + dxmax, center_y - dymax, lhline_width, color); // left if (!dymax) this->horizontal_line(center_x - dxmin, center_y, lhline_width, color); // right horizontal border - if (upd_dxmax > -dxmin) { // right + if (upd_dxmax > -dxmin) { + // right int rhline_width = (upd_dxmax + dxmin) + 1; this->horizontal_line(center_x - dxmin, center_y - dymax, rhline_width > lhline_width ? lhline_width : rhline_width, color); @@ -256,7 +265,8 @@ void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2, if (lhline_width > 0) this->horizontal_line(center_x + dxmax, center_y - dymax, lhline_width, color); } - } else { // top part + } else { + // top part int hline_width = 2 * (-dxmax) + 1; if (progress >= 50) { if (dymax < float(-dxmax) * tan_a) { @@ -300,11 +310,13 @@ void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2, } } while (dxmax <= 0); } + void HOT Display::triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) { this->line(x1, y1, x2, y2, color); this->line(x1, y1, x3, y3, color); this->line(x2, y2, x3, y3, color); } + void Display::sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3) { if (*y1 > *y2) { int x_temp = *x1, y_temp = *y1; @@ -322,6 +334,7 @@ void Display::sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3 = x_temp, *y3 = y_temp; } } + void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color) { // y2 must be equal to y3 (same horizontal line) @@ -333,7 +346,8 @@ void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int s1_dy = abs(y2 - y1); int s1_sign_x = ((x2 - x1) >= 0) ? 1 : -1; int s1_sign_y = ((y2 - y1) >= 0) ? 1 : -1; - if (s1_dy > s1_dx) { // swap values + if (s1_dy > s1_dx) { + // swap values int tmp = s1_dx; s1_dx = s1_dy; s1_dy = tmp; @@ -349,7 +363,8 @@ void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int s2_dy = abs(y3 - y1); int s2_sign_x = ((x3 - x1) >= 0) ? 1 : -1; int s2_sign_y = ((y3 - y1) >= 0) ? 1 : -1; - if (s2_dy > s2_dx) { // swap values + if (s2_dy > s2_dx) { + // swap values int tmp = s2_dx; s2_dx = s2_dy; s2_dy = tmp; @@ -402,20 +417,25 @@ void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, } } } + void Display::filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) { // Sort the three points by y-coordinate ascending, so [x1,y1] is the topmost point this->sort_triangle_points_by_y_(&x1, &y1, &x2, &y2, &x3, &y3); - if (y2 == y3) { // Check for special case of a bottom-flat triangle + if (y2 == y3) { + // Check for special case of a bottom-flat triangle this->filled_flat_side_triangle_(x1, y1, x2, y2, x3, y3, color); - } else if (y1 == y2) { // Check for special case of a top-flat triangle + } else if (y1 == y2) { + // Check for special case of a top-flat triangle this->filled_flat_side_triangle_(x3, y3, x1, y1, x2, y2, color); - } else { // General case: split the no-flat-side triangle in a top-flat triangle and bottom-flat triangle + } else { + // General case: split the no-flat-side triangle in a top-flat triangle and bottom-flat triangle int x_temp = (int) (x1 + ((float) (y2 - y1) / (float) (y3 - y1)) * (x3 - x1)), y_temp = y2; this->filled_flat_side_triangle_(x1, y1, x2, y2, x_temp, y_temp, color); this->filled_flat_side_triangle_(x3, y3, x2, y2, x_temp, y_temp, color); } } + void HOT Display::get_regular_polygon_vertex(int vertex_id, int *vertex_x, int *vertex_y, int center_x, int center_y, int radius, int edges, RegularPolygonVariation variation, float rotation_degrees) { @@ -447,7 +467,8 @@ void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPo int current_vertex_x, current_vertex_y; get_regular_polygon_vertex(current_vertex_id, ¤t_vertex_x, ¤t_vertex_y, x, y, radius, edges, variation, rotation_degrees); - if (current_vertex_id > 0) { // Start drawing after the 2nd vertex coordinates has been calculated + if (current_vertex_id > 0) { + // Start drawing after the 2nd vertex coordinates has been calculated if (drawing == DRAWING_FILLED) { this->filled_triangle(x, y, previous_vertex_x, previous_vertex_y, current_vertex_x, current_vertex_y, color); } else if (drawing == DRAWING_OUTLINE) { @@ -459,21 +480,26 @@ void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPo } } } + void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color, RegularPolygonDrawing drawing) { regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, drawing); } + void HOT Display::regular_polygon(int x, int y, int radius, int edges, Color color, RegularPolygonDrawing drawing) { regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, drawing); } + void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, float rotation_degrees, Color color) { regular_polygon(x, y, radius, edges, variation, rotation_degrees, color, DRAWING_FILLED); } + void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color) { regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, DRAWING_FILLED); } + void Display::filled_regular_polygon(int x, int y, int radius, int edges, Color color) { regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, DRAWING_FILLED); } @@ -584,15 +610,19 @@ void Display::get_text_bounds(int x, int y, const char *text, BaseFont *font, Te break; } } + void Display::print(int x, int y, BaseFont *font, Color color, const char *text, Color background) { this->print(x, y, font, color, TextAlign::TOP_LEFT, text, background); } + void Display::print(int x, int y, BaseFont *font, TextAlign align, const char *text) { this->print(x, y, font, COLOR_ON, align, text); } + void Display::print(int x, int y, BaseFont *font, const char *text) { this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text); } + void Display::printf(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, ...) { va_list arg; @@ -600,31 +630,37 @@ void Display::printf(int x, int y, BaseFont *font, Color color, Color background this->vprintf_(x, y, font, color, background, align, format, arg); va_end(arg); } + void Display::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) { va_list arg; va_start(arg, format); this->vprintf_(x, y, font, color, COLOR_OFF, align, format, arg); va_end(arg); } + void Display::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) { va_list arg; va_start(arg, format); this->vprintf_(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, arg); va_end(arg); } + void Display::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) { va_list arg; va_start(arg, format); this->vprintf_(x, y, font, COLOR_ON, COLOR_OFF, align, format, arg); va_end(arg); } + void Display::printf(int x, int y, BaseFont *font, const char *format, ...) { va_list arg; va_start(arg, format); this->vprintf_(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, arg); va_end(arg); } + void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; } + void Display::set_pages(std::vector pages) { for (auto *page : pages) page->set_parent(this); @@ -637,6 +673,7 @@ void Display::set_pages(std::vector pages) { pages[pages.size() - 1]->set_next(pages[0]); this->show_page(pages[0]); } + void Display::show_page(DisplayPage *page) { this->previous_page_ = this->page_; this->page_ = page; @@ -645,8 +682,10 @@ void Display::show_page(DisplayPage *page) { t->process(this->previous_page_, this->page_); } } + void Display::show_next_page() { this->page_->show_next(); } void Display::show_prev_page() { this->page_->show_prev(); } + void Display::do_update_() { if (this->auto_clear_enabled_) { this->clear(); @@ -660,10 +699,12 @@ void Display::do_update_() { } this->clear_clipping_(); } + void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) { if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to)) this->trigger(from, to); } + void Display::strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, ESPTime time) { char buffer[64]; @@ -671,15 +712,19 @@ void Display::strftime(int x, int y, BaseFont *font, Color color, Color backgrou if (ret > 0) this->print(x, y, font, color, align, buffer, background); } + void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) { this->strftime(x, y, font, color, COLOR_OFF, align, format, time); } + void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) { this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time); } + void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) { this->strftime(x, y, font, COLOR_ON, COLOR_OFF, align, format, time); } + void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) { this->strftime(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, time); } @@ -691,6 +736,7 @@ void Display::start_clipping(Rect rect) { } this->clipping_rectangle_.push_back(rect); } + void Display::end_clipping() { if (this->clipping_rectangle_.empty()) { ESP_LOGE(TAG, "clear: Clipping is not set."); @@ -698,6 +744,7 @@ void Display::end_clipping() { this->clipping_rectangle_.pop_back(); } } + void Display::extend_clipping(Rect add_rect) { if (this->clipping_rectangle_.empty()) { ESP_LOGE(TAG, "add: Clipping is not set."); @@ -705,6 +752,7 @@ void Display::extend_clipping(Rect add_rect) { this->clipping_rectangle_.back().extend(add_rect); } } + void Display::shrink_clipping(Rect add_rect) { if (this->clipping_rectangle_.empty()) { ESP_LOGE(TAG, "add: Clipping is not set."); @@ -712,6 +760,7 @@ void Display::shrink_clipping(Rect add_rect) { this->clipping_rectangle_.back().shrink(add_rect); } } + Rect Display::get_clipping() const { if (this->clipping_rectangle_.empty()) { return Rect(); @@ -719,7 +768,9 @@ Rect Display::get_clipping() const { return this->clipping_rectangle_.back(); } } + void Display::clear_clipping_() { this->clipping_rectangle_.clear(); } + bool Display::clip(int x, int y) { if (x < 0 || x >= this->get_width() || y < 0 || y >= this->get_height()) return false; @@ -727,6 +778,7 @@ bool Display::clip(int x, int y) { return false; return true; } + bool Display::clamp_x_(int x, int w, int &min_x, int &max_x) { min_x = std::max(x, 0); max_x = std::min(x + w, this->get_width()); @@ -742,6 +794,7 @@ bool Display::clamp_x_(int x, int w, int &min_x, int &max_x) { return min_x < max_x; } + bool Display::clamp_y_(int y, int h, int &min_y, int &max_y) { min_y = std::max(y, 0); max_y = std::min(y + h, this->get_height()); @@ -766,15 +819,15 @@ void Display::test_card() { int w = get_width(), h = get_height(), image_w, image_h; this->clear(); this->show_test_card_ = false; + image_w = std::min(w - 20, 310); + image_h = std::min(h - 20, 255); + int shift_x = (w - image_w) / 2; + int shift_y = (h - image_h) / 2; + int line_w = (image_w - 6) / 6; + int image_c = image_w / 2; if (this->get_display_type() == DISPLAY_TYPE_COLOR) { Color r(255, 0, 0), g(0, 255, 0), b(0, 0, 255); - image_w = std::min(w - 20, 310); - image_h = std::min(h - 20, 255); - int shift_x = (w - image_w) / 2; - int shift_y = (h - image_h) / 2; - int line_w = (image_w - 6) / 6; - int image_c = image_w / 2; for (auto i = 0; i != image_h; i++) { int c = esp_scale(i, image_h); this->horizontal_line(shift_x + 0, shift_y + i, line_w, r.fade_to_white(c)); @@ -786,26 +839,26 @@ void Display::test_card() { this->horizontal_line(shift_x + image_w - (line_w * 2), shift_y + i, line_w, b.fade_to_white(c)); this->horizontal_line(shift_x + image_w - line_w, shift_y + i, line_w, b.fade_to_black(c)); } - this->rectangle(shift_x, shift_y, image_w, image_h, Color(127, 127, 0)); + } + this->rectangle(shift_x, shift_y, image_w, image_h, Color(127, 127, 0)); - uint16_t shift_r = shift_x + line_w - (8 * 3); - uint16_t shift_g = shift_x + image_c - (8 * 3); - uint16_t shift_b = shift_x + image_w - line_w - (8 * 3); - shift_y = h / 2 - (8 * 3); - for (auto i = 0; i < 8; i++) { - uint8_t ftr = progmem_read_byte(&TESTCARD_FONT[0][i]); - uint8_t ftg = progmem_read_byte(&TESTCARD_FONT[1][i]); - uint8_t ftb = progmem_read_byte(&TESTCARD_FONT[2][i]); - for (auto k = 0; k < 8; k++) { - if ((ftr & (1 << k)) != 0) { - this->filled_rectangle(shift_r + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF); - } - if ((ftg & (1 << k)) != 0) { - this->filled_rectangle(shift_g + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF); - } - if ((ftb & (1 << k)) != 0) { - this->filled_rectangle(shift_b + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF); - } + uint16_t shift_r = shift_x + line_w - (8 * 3); + uint16_t shift_g = shift_x + image_c - (8 * 3); + uint16_t shift_b = shift_x + image_w - line_w - (8 * 3); + shift_y = h / 2 - (8 * 3); + for (auto i = 0; i < 8; i++) { + uint8_t ftr = progmem_read_byte(&TESTCARD_FONT[0][i]); + uint8_t ftg = progmem_read_byte(&TESTCARD_FONT[1][i]); + uint8_t ftb = progmem_read_byte(&TESTCARD_FONT[2][i]); + for (auto k = 0; k < 8; k++) { + if ((ftr & (1 << k)) != 0) { + this->filled_rectangle(shift_r + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF); + } + if ((ftg & (1 << k)) != 0) { + this->filled_rectangle(shift_g + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF); + } + if ((ftb & (1 << k)) != 0) { + this->filled_rectangle(shift_b + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF); } } } @@ -818,7 +871,9 @@ void Display::test_card() { } DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {} + void DisplayPage::show() { this->parent_->show_page(this); } + void DisplayPage::show_next() { if (this->next_ == nullptr) { ESP_LOGE(TAG, "no next page"); @@ -826,6 +881,7 @@ void DisplayPage::show_next() { } this->next_->show(); } + void DisplayPage::show_prev() { if (this->prev_ == nullptr) { ESP_LOGE(TAG, "no previous page"); @@ -833,6 +889,7 @@ void DisplayPage::show_prev() { } this->prev_->show(); } + void DisplayPage::set_parent(Display *parent) { this->parent_ = parent; } void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; } void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; } @@ -868,6 +925,5 @@ const LogString *text_align_to_string(TextAlign textalign) { return LOG_STR("UNKNOWN"); } } - } // namespace display } // namespace esphome diff --git a/esphome/components/epaper_spi/display.py b/esphome/components/epaper_spi/display.py index 182c37ba40..ff5693c206 100644 --- a/esphome/components/epaper_spi/display.py +++ b/esphome/components/epaper_spi/display.py @@ -4,8 +4,10 @@ import pkgutil from esphome import core, pins import esphome.codegen as cg from esphome.components import display, spi +from esphome.components.display import CONF_SHOW_TEST_CARD, validate_rotation from esphome.components.mipi import flatten_sequence, map_sequence import esphome.config_validation as cv +from esphome.config_validation import update_interval from esphome.const import ( CONF_BUSY_PIN, CONF_CS_PIN, @@ -13,15 +15,25 @@ from esphome.const import ( CONF_DC_PIN, CONF_DIMENSIONS, CONF_ENABLE_PIN, + CONF_FULL_UPDATE_EVERY, CONF_HEIGHT, CONF_ID, CONF_INIT_SEQUENCE, CONF_LAMBDA, + CONF_MIRROR_X, + CONF_MIRROR_Y, CONF_MODEL, + CONF_PAGES, CONF_RESET_DURATION, CONF_RESET_PIN, + CONF_ROTATION, + CONF_SWAP_XY, + CONF_TRANSFORM, + CONF_UPDATE_INTERVAL, CONF_WIDTH, ) +from esphome.cpp_generator import RawExpression +from esphome.final_validate import full_config from . import models @@ -32,8 +44,9 @@ CONF_INIT_SEQUENCE_ID = "init_sequence_id" epaper_spi_ns = cg.esphome_ns.namespace("epaper_spi") EPaperBase = epaper_spi_ns.class_( - "EPaperBase", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer + "EPaperBase", cg.PollingComponent, spi.SPIDevice, display.Display ) +Transform = epaper_spi_ns.enum("Transform") EPaperSpectraE6 = epaper_spi_ns.class_("EPaperSpectraE6", EPaperBase) EPaper7p3InSpectraE6 = epaper_spi_ns.class_("EPaper7p3InSpectraE6", EPaperSpectraE6) @@ -52,6 +65,8 @@ DIMENSION_SCHEMA = cv.Schema( } ) +TRANSFORM_OPTIONS = {CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY} + def model_schema(config): model = MODELS[config[CONF_MODEL]] @@ -73,7 +88,18 @@ def model_schema(config): ) .extend( { + cv.Optional(CONF_ROTATION, default=0): validate_rotation, cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True), + cv.Optional( + CONF_UPDATE_INTERVAL, default=cv.UNDEFINED + ): update_interval, + cv.Optional(CONF_TRANSFORM): cv.Schema( + { + cv.Required(CONF_MIRROR_X): cv.boolean, + cv.Required(CONF_MIRROR_Y): cv.boolean, + } + ), + cv.Optional(CONF_FULL_UPDATE_EVERY, default=1): cv.int_range(1, 255), model.option(CONF_DC_PIN, fallback=None): pins.gpio_output_pin_schema, cv.GenerateID(): cv.declare_id(class_name), cv.GenerateID(CONF_INIT_SEQUENCE_ID): cv.declare_id(cg.uint8), @@ -111,9 +137,29 @@ def customise_schema(config): CONFIG_SCHEMA = customise_schema -FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( - "epaper_spi", require_miso=False, require_mosi=True -) + +def _final_validate(config): + spi.final_validate_device_schema( + "epaper_spi", require_miso=False, require_mosi=True + )(config) + + global_config = full_config.get() + from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN + + if CONF_LAMBDA not in config and CONF_PAGES not in config: + if LVGL_DOMAIN in global_config: + if CONF_UPDATE_INTERVAL not in config: + config[CONF_UPDATE_INTERVAL] = update_interval("never") + else: + # If no drawing methods are configured, and LVGL is not enabled, show a test card + config[CONF_SHOW_TEST_CARD] = True + config[CONF_UPDATE_INTERVAL] = core.TimePeriod( + seconds=60 + ).total_milliseconds + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate async def to_code(config): @@ -137,7 +183,9 @@ async def to_code(config): init_sequence_length, ) - await display.register_display(var, config) + # Rotation is handled by setting the transform + display_config = {k: v for k, v in config.items() if k != CONF_ROTATION} + await display.register_display(var, display_config) await spi.register_spi_device(var, config) dc = await cg.gpio_pin_expression(config[CONF_DC_PIN]) @@ -148,11 +196,35 @@ async def to_code(config): config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) - if CONF_RESET_PIN in config: - reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) + if reset_pin := config.get(CONF_RESET_PIN): + reset = await cg.gpio_pin_expression(reset_pin) cg.add(var.set_reset_pin(reset)) - if CONF_BUSY_PIN in config: - busy = await cg.gpio_pin_expression(config[CONF_BUSY_PIN]) + if busy_pin := config.get(CONF_BUSY_PIN): + busy = await cg.gpio_pin_expression(busy_pin) cg.add(var.set_busy_pin(busy)) + cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY])) if CONF_RESET_DURATION in config: cg.add(var.set_reset_duration(config[CONF_RESET_DURATION])) + if transform := config.get(CONF_TRANSFORM): + transform[CONF_SWAP_XY] = False + else: + transform = {x: model.get_default(x, False) for x in TRANSFORM_OPTIONS} + rotation = config[CONF_ROTATION] + if rotation == 180: + transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X] + transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y] + elif rotation == 90: + transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY] + transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X] + elif rotation == 270: + transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY] + transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y] + transform_str = "|".join( + { + str(getattr(Transform, x.upper())) + for x in TRANSFORM_OPTIONS + if transform.get(x) + } + ) + if transform_str: + cg.add(var.set_transform(RawExpression(transform_str))) diff --git a/esphome/components/epaper_spi/epaper_spi.cpp b/esphome/components/epaper_spi/epaper_spi.cpp index 39959cd743..f6313d33ef 100644 --- a/esphome/components/epaper_spi/epaper_spi.cpp +++ b/esphome/components/epaper_spi/epaper_spi.cpp @@ -9,9 +9,8 @@ namespace esphome::epaper_spi { static const char *const TAG = "epaper_spi"; static constexpr const char *const EPAPER_STATE_STRINGS[] = { - "IDLE", "UPDATE", "RESET", "RESET_END", - - "SHOULD_WAIT", "INITIALISE", "TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP", + "IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE", + "TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP", }; const char *EPaperBase::epaper_state_to_string_() { @@ -69,8 +68,8 @@ void EPaperBase::data(uint8_t value) { // The command is the first byte, length is the length of data only in the second byte, followed by the data. // [COMMAND, LENGTH, DATA...] void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) { - ESP_LOGVV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length, - format_hex_pretty(ptr, length, '.', false).c_str()); + ESP_LOGV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length, + format_hex_pretty(ptr, length, '.', false).c_str()); this->dc_pin_->digital_write(false); this->enable(); @@ -89,7 +88,7 @@ bool EPaperBase::is_idle_() const { return !this->busy_pin_->digital_read(); } -bool EPaperBase::reset_() const { +bool EPaperBase::reset() { if (this->reset_pin_ != nullptr) { if (this->state_ == EPaperState::RESET) { this->reset_pin_->digital_write(false); @@ -105,16 +104,16 @@ void EPaperBase::update() { ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_()); return; } - this->set_state_(EPaperState::RESET); + this->set_state_(EPaperState::UPDATE); this->enable_loop(); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG + this->update_start_time_ = millis(); +#endif } void EPaperBase::wait_for_idle_(bool should_wait) { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE - if (should_wait) { - this->waiting_for_idle_start_ = millis(); - this->waiting_for_idle_last_print_ = this->waiting_for_idle_start_; - } + this->waiting_for_idle_start_ = millis(); #endif this->waiting_for_idle_ = should_wait; } @@ -138,7 +137,9 @@ void EPaperBase::loop() { if (this->waiting_for_idle_) { if (this->is_idle_()) { this->waiting_for_idle_ = false; - ESP_LOGV(TAG, "Screen now idle after %u ms", (unsigned) (millis() - this->waiting_for_idle_start_)); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + ESP_LOGV(TAG, "Screen was busy for %u ms", (unsigned) (millis() - this->waiting_for_idle_start_)); +#endif } else { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE if (now - this->waiting_for_idle_last_print_ >= 1000) { @@ -164,23 +165,27 @@ void EPaperBase::process_state_() { ESP_LOGV(TAG, "Process state entered in state %s", epaper_state_to_string_()); switch (this->state_) { default: - ESP_LOGD(TAG, "Display is in unhandled state %s", epaper_state_to_string_()); - this->disable_loop(); + ESP_LOGE(TAG, "Display is in unhandled state %s", epaper_state_to_string_()); + this->set_state_(EPaperState::IDLE); break; case EPaperState::IDLE: this->disable_loop(); break; case EPaperState::RESET: case EPaperState::RESET_END: - if (this->reset_()) { - this->set_state_(EPaperState::UPDATE); + if (this->reset()) { + this->set_state_(EPaperState::INITIALISE); } else { - this->set_state_(EPaperState::RESET_END); + this->set_state_(EPaperState::RESET_END, this->reset_duration_); } break; case EPaperState::UPDATE: this->do_update_(); // Calls ESPHome (current page) lambda - this->set_state_(EPaperState::INITIALISE); + if (this->x_high_ < this->x_low_ || this->y_high_ < this->y_low_) { + this->set_state_(EPaperState::IDLE); + return; + } + this->set_state_(EPaperState::RESET); break; case EPaperState::INITIALISE: this->initialise_(); @@ -190,6 +195,10 @@ void EPaperBase::process_state_() { if (!this->transfer_data()) { return; // Not done yet, come back next loop } + this->x_low_ = this->width_; + this->x_high_ = 0; + this->y_low_ = this->height_; + this->y_high_ = 0; this->set_state_(EPaperState::POWER_ON); break; case EPaperState::POWER_ON: @@ -197,7 +206,8 @@ void EPaperBase::process_state_() { this->set_state_(EPaperState::REFRESH_SCREEN); break; case EPaperState::REFRESH_SCREEN: - this->refresh_screen(); + this->refresh_screen(this->update_count_ != 0); + this->update_count_ = (this->update_count_ + 1) % this->full_update_every_; this->set_state_(EPaperState::POWER_OFF); break; case EPaperState::POWER_OFF: @@ -207,6 +217,7 @@ void EPaperBase::process_state_() { case EPaperState::DEEP_SLEEP: this->deep_sleep(); this->set_state_(EPaperState::IDLE); + ESP_LOGD(TAG, "Display update took %" PRIu32 " ms", millis() - this->update_start_time_); break; } } @@ -222,6 +233,9 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) { } ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay, TRUEFALSE(this->waiting_for_idle_)); + if (state == EPaperState::IDLE) { + this->disable_loop(); + } } void EPaperBase::start_command_() { @@ -260,20 +274,73 @@ void EPaperBase::initialise_() { this->mark_failed(); return; } - ESP_LOGV(TAG, "Command %02X, length %d", cmd, num_args); this->cmd_data(cmd, sequence + index, num_args); index += num_args; } } } +/** + * Check and rotate coordinates based on the transform flags. + * @param x + * @param y + * @return false if the coordinates are out of bounds + */ +bool EPaperBase::rotate_coordinates_(int &x, int &y) const { + if (!this->get_clipping().inside(x, y)) + return false; + if (this->transform_ & SWAP_XY) + std::swap(x, y); + if (this->transform_ & MIRROR_X) + x = this->width_ - x - 1; + if (this->transform_ & MIRROR_Y) + y = this->height_ - y - 1; + if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0) + return false; + return true; +} + +/** + * Default implementation for monochrome displays where 8 pixels are packed to a byte. + * @param x + * @param y + * @param color + */ +void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) { + if (!rotate_coordinates_(x, y)) + return; + const size_t pixel_position = y * this->width_ + x; + const size_t byte_position = pixel_position / 8; + const uint8_t bit_position = pixel_position % 8; + const uint8_t pixel_bit = 0x80 >> bit_position; + const auto original = this->buffer_[byte_position]; + if ((color_to_bit(color) == 0)) { + this->buffer_[byte_position] = original & ~pixel_bit; + } else { + this->buffer_[byte_position] = original | pixel_bit; + } + this->x_low_ = clamp_at_most(this->x_low_, x); + this->x_high_ = clamp_at_least(this->x_high_, x + 1); + this->y_low_ = clamp_at_most(this->y_low_, y); + this->y_high_ = clamp_at_least(this->y_high_, y + 1); +} + void EPaperBase::dump_config() { LOG_DISPLAY("", "E-Paper SPI", this); ESP_LOGCONFIG(TAG, " Model: %s", this->name_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_PIN(" CS Pin: ", this->cs_); LOG_UPDATE_INTERVAL(this); + ESP_LOGCONFIG(TAG, + " SPI Data Rate: %uMHz\n" + " Full update every: %d\n" + " Swap X/Y: %s\n" + " Mirror X: %s\n" + " Mirror Y: %s", + (unsigned) (this->data_rate_ / 1000000), this->full_update_every_, YESNO(this->transform_ & SWAP_XY), + YESNO(this->transform_ & MIRROR_X), YESNO(this->transform_ & MIRROR_Y)); } } // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/epaper_spi.h b/esphome/components/epaper_spi/epaper_spi.h index 4745ec7339..544ea3e9ba 100644 --- a/esphome/components/epaper_spi/epaper_spi.h +++ b/esphome/components/epaper_spi/epaper_spi.h @@ -5,8 +5,6 @@ #include "esphome/components/split_buffer/split_buffer.h" #include "esphome/core/component.h" -#include - namespace esphome::epaper_spi { using namespace display; @@ -25,10 +23,16 @@ enum class EPaperState : uint8_t { DEEP_SLEEP, // deep sleep the display }; -static constexpr uint8_t MAX_TRANSFER_TIME = 10; // Transfer in 10ms blocks to allow the loop to run +static constexpr uint8_t NONE = 0; +static constexpr uint8_t MIRROR_X = 1; +static constexpr uint8_t MIRROR_Y = 2; +static constexpr uint8_t SWAP_XY = 4; + +static constexpr uint32_t MAX_TRANSFER_TIME = 10; // Transfer in 10ms blocks to allow the loop to run +static constexpr size_t MAX_TRANSFER_SIZE = 128; static constexpr uint8_t DELAY_FLAG = 0xFF; -class EPaperBase : public DisplayBuffer, +class EPaperBase : public Display, public spi::SPIDevice { public: @@ -45,6 +49,8 @@ class EPaperBase : public DisplayBuffer, void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; } void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; } + void set_transform(uint8_t transform) { this->transform_ = transform; } + void set_full_update_every(uint8_t full_update_every) { this->full_update_every_ = full_update_every; } void dump_config() override; void command(uint8_t value); @@ -60,20 +66,47 @@ class EPaperBase : public DisplayBuffer, DisplayType get_display_type() override { return this->display_type_; }; + // Default implementations for monochrome displays + static uint8_t color_to_bit(Color color) { + // It's always a shade of gray. Map to BLACK or WHITE. + // We split the luminance at a suitable point + if ((static_cast(color.r) + color.g + color.b) > 512) { + return 1; + } + return 0; + } + void fill(Color color) override { + auto pixel_color = color_to_bit(color) ? 0xFF : 0x00; + + // We store 8 pixels per byte + this->buffer_.fill(pixel_color); + this->x_high_ = this->width_; + this->y_high_ = this->height_; + this->x_low_ = 0; + this->y_low_ = 0; + } + + void clear() override { + // clear buffer to white, just like real paper. + this->fill(COLOR_ON); + } + protected: int get_height_internal() override { return this->height_; }; int get_width_internal() override { return this->width_; }; + int get_width() override { return this->transform_ & SWAP_XY ? this->height_ : this->width_; } + int get_height() override { return this->transform_ & SWAP_XY ? this->width_ : this->height_; } + void draw_pixel_at(int x, int y, Color color) override; void process_state_(); const char *epaper_state_to_string_(); bool is_idle_() const; void setup_pins_() const; - bool reset_() const; + virtual bool reset(); void initialise_(); void wait_for_idle_(bool should_wait); bool init_buffer_(size_t buffer_length); - - virtual int get_width_controller() { return this->get_width_internal(); }; + bool rotate_coordinates_(int &x, int &y) const; /** * Methods that must be implemented by concrete classes to control the display @@ -86,7 +119,7 @@ class EPaperBase : public DisplayBuffer, /** * Refresh the screen after data transfer */ - virtual void refresh_screen() = 0; + virtual void refresh_screen(bool partial) = 0; /** * Power the display on @@ -118,24 +151,31 @@ class EPaperBase : public DisplayBuffer, DisplayType display_type_; size_t buffer_length_{}; - size_t current_data_index_{0}; // used by data transfer to track progress - uint32_t reset_duration_{200}; -#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE - uint32_t transfer_start_time_{}; - uint32_t waiting_for_idle_last_print_{0}; - uint32_t waiting_for_idle_start_{0}; -#endif - + size_t current_data_index_{}; // used by data transfer to track progress + split_buffer::SplitBuffer buffer_{}; GPIOPin *dc_pin_{}; GPIOPin *busy_pin_{}; GPIOPin *reset_pin_{}; + bool waiting_for_idle_{}; + uint32_t delay_until_{}; + uint8_t transform_{}; + uint8_t update_count_{}; + // these values represent the bounds of the updated buffer. Note that x_high and y_high + // point to the pixel past the last one updated, i.e. may range up to width/height. + uint16_t x_low_{}, y_low_{}, x_high_{}, y_high_{}; - bool waiting_for_idle_{false}; - uint32_t delay_until_{0}; - - split_buffer::SplitBuffer buffer_; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + uint32_t waiting_for_idle_last_print_{}; + uint32_t waiting_for_idle_start_{}; +#endif +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG + uint32_t update_start_time_{}; +#endif + // properties with specific initialisers go last EPaperState state_{EPaperState::IDLE}; + uint32_t reset_duration_{10}; + uint8_t full_update_every_{1}; }; } // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp b/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp index 8e4cbdde2a..d0e68595d0 100644 --- a/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp +++ b/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp @@ -6,7 +6,6 @@ namespace esphome::epaper_spi { static constexpr const char *const TAG = "epaper_spi.6c"; -static constexpr size_t MAX_TRANSFER_SIZE = 128; static constexpr unsigned char GRAY_THRESHOLD = 50; enum E6Color { @@ -75,24 +74,24 @@ static uint8_t color_to_hex(Color color) { } void EPaperSpectraE6::power_on() { - ESP_LOGD(TAG, "Power on"); + ESP_LOGV(TAG, "Power on"); this->command(0x04); } void EPaperSpectraE6::power_off() { - ESP_LOGD(TAG, "Power off"); + ESP_LOGV(TAG, "Power off"); this->command(0x02); this->data(0x00); } -void EPaperSpectraE6::refresh_screen() { - ESP_LOGD(TAG, "Refresh"); +void EPaperSpectraE6::refresh_screen(bool partial) { + ESP_LOGV(TAG, "Refresh"); this->command(0x12); this->data(0x00); } void EPaperSpectraE6::deep_sleep() { - ESP_LOGD(TAG, "Deep sleep"); + ESP_LOGV(TAG, "Deep sleep"); this->command(0x07); this->data(0xA5); } @@ -109,12 +108,11 @@ void EPaperSpectraE6::clear() { this->fill(COLOR_ON); } -void HOT EPaperSpectraE6::draw_absolute_pixel_internal(int x, int y, Color color) { - if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0) +void HOT EPaperSpectraE6::draw_pixel_at(int x, int y, Color color) { + if (!this->rotate_coordinates_(x, y)) return; - auto pixel_bits = color_to_hex(color); - uint32_t pixel_position = x + y * this->get_width_controller(); + uint32_t pixel_position = x + y * this->get_width_internal(); uint32_t byte_position = pixel_position / 2; auto original = this->buffer_[byte_position]; if ((pixel_position & 1) != 0) { @@ -128,10 +126,6 @@ bool HOT EPaperSpectraE6::transfer_data() { const uint32_t start_time = App.get_loop_component_start_time(); const size_t buffer_length = this->buffer_length_; if (this->current_data_index_ == 0) { -#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE - this->transfer_start_time_ = millis(); -#endif - ESP_LOGV(TAG, "Start sending data at %ums", (unsigned) millis()); this->command(0x10); } @@ -160,7 +154,6 @@ bool HOT EPaperSpectraE6::transfer_data() { this->end_data_(); } this->current_data_index_ = 0; - ESP_LOGV(TAG, "Sent data in %" PRIu32 " ms", millis() - this->transfer_start_time_); return true; } } // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/epaper_spi_spectra_e6.h b/esphome/components/epaper_spi/epaper_spi_spectra_e6.h index 48356ad74b..b8dbf0b0c5 100644 --- a/esphome/components/epaper_spi/epaper_spi_spectra_e6.h +++ b/esphome/components/epaper_spi/epaper_spi_spectra_e6.h @@ -16,11 +16,11 @@ class EPaperSpectraE6 : public EPaperBase { void clear() override; protected: - void refresh_screen() override; + void refresh_screen(bool partial) override; void power_on() override; void power_off() override; void deep_sleep() override; - void draw_absolute_pixel_internal(int x, int y, Color color) override; + void draw_pixel_at(int x, int y, Color color) override; bool transfer_data() override; }; diff --git a/esphome/components/epaper_spi/epaper_spi_ssd1677.cpp b/esphome/components/epaper_spi/epaper_spi_ssd1677.cpp new file mode 100644 index 0000000000..e4f04657ad --- /dev/null +++ b/esphome/components/epaper_spi/epaper_spi_ssd1677.cpp @@ -0,0 +1,86 @@ +#include "epaper_spi_ssd1677.h" + +#include + +#include "esphome/core/log.h" + +namespace esphome::epaper_spi { +static constexpr const char *const TAG = "epaper_spi.ssd1677"; + +void EPaperSSD1677::refresh_screen(bool partial) { + ESP_LOGV(TAG, "Refresh screen"); + this->command(0x22); + this->data(partial ? 0xFF : 0xF7); + this->command(0x20); +} + +void EPaperSSD1677::deep_sleep() { + ESP_LOGV(TAG, "Deep sleep"); + this->command(0x10); +} + +bool EPaperSSD1677::reset() { + if (EPaperBase::reset()) { + this->command(0x12); + return true; + } + return false; +} + +bool HOT EPaperSSD1677::transfer_data() { + auto start_time = millis(); + if (this->current_data_index_ == 0) { + uint8_t data[4]{}; + // round to byte boundaries + this->x_low_ &= ~7; + this->y_low_ &= ~7; + this->x_high_ += 7; + this->x_high_ &= ~7; + this->y_high_ += 7; + this->y_high_ &= ~7; + data[0] = this->x_low_; + data[1] = this->x_low_ / 256; + data[2] = this->x_high_ - 1; + data[3] = (this->x_high_ - 1) / 256; + cmd_data(0x4E, data, 2); + cmd_data(0x44, data, sizeof(data)); + data[0] = this->y_low_; + data[1] = this->y_low_ / 256; + data[2] = this->y_high_ - 1; + data[3] = (this->y_high_ - 1) / 256; + cmd_data(0x4F, data, 2); + this->cmd_data(0x45, data, sizeof(data)); + // for monochrome, we still need to clear the red data buffer at least once to prevent it + // causing dirty pixels after partial refresh. + this->command(this->send_red_ ? 0x26 : 0x24); + this->current_data_index_ = this->y_low_; // actually current line + } + size_t row_length = (this->x_high_ - this->x_low_) / 8; + FixedVector bytes_to_send{}; + bytes_to_send.init(row_length); + ESP_LOGV(TAG, "Writing bytes at line %zu at %ums", this->current_data_index_, (unsigned) millis()); + this->start_data_(); + while (this->current_data_index_ != this->y_high_) { + size_t data_idx = (this->current_data_index_ * this->width_ + this->x_low_) / 8; + for (size_t i = 0; i != row_length; i++) { + bytes_to_send[i] = this->send_red_ ? 0 : this->buffer_[data_idx++]; + } + ++this->current_data_index_; + this->write_array(&bytes_to_send.front(), row_length); // NOLINT + if (millis() - start_time > MAX_TRANSFER_TIME) { + // Let the main loop run and come back next loop + this->end_data_(); + return false; + } + } + + this->end_data_(); + this->current_data_index_ = 0; + if (this->send_red_) { + this->send_red_ = false; + return false; + } + return true; +} + +} // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/epaper_spi_ssd1677.h b/esphome/components/epaper_spi/epaper_spi_ssd1677.h new file mode 100644 index 0000000000..47584d24c0 --- /dev/null +++ b/esphome/components/epaper_spi/epaper_spi_ssd1677.h @@ -0,0 +1,25 @@ +#pragma once + +#include "epaper_spi.h" + +namespace esphome::epaper_spi { + +class EPaperSSD1677 : public EPaperBase { + public: + EPaperSSD1677(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence, + size_t init_sequence_length) + : EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY) { + this->buffer_length_ = width * height / 8; // 8 pixels per byte + } + + protected: + void refresh_screen(bool partial) override; + void power_on() override {} + void power_off() override{}; + void deep_sleep() override; + bool reset() override; + bool transfer_data() override; + bool send_red_{true}; +}; + +} // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/models/ssd1677.py b/esphome/components/epaper_spi/models/ssd1677.py new file mode 100644 index 0000000000..3eb53d650e --- /dev/null +++ b/esphome/components/epaper_spi/models/ssd1677.py @@ -0,0 +1,42 @@ +from esphome.const import CONF_DATA_RATE + +from . import EpaperModel + + +class SSD1677(EpaperModel): + def __init__(self, name, class_name="EPaperSSD1677", **kwargs): + if CONF_DATA_RATE not in kwargs: + kwargs[CONF_DATA_RATE] = "20MHz" + super().__init__(name, class_name, **kwargs) + + # fmt: off + def get_init_sequence(self, config: dict): + width, _height = self.get_dimensions(config) + return ( + (0x18, 0x80), # Select internal Temp sensor + (0x0C, 0xAE, 0xC7, 0xC3, 0xC0, 0x80), # inrush current level 2 + (0x01, (width - 1) % 256, (width - 1) // 256, 0x02), # Set column gate limit + (0x3C, 0x01), # Set border waveform + (0x11, 3), # Set transform + ) + + +ssd1677 = SSD1677("ssd1677") + +ssd1677.extend( + "seeed-ee04-mono-4.26", + width=800, + height=480, + mirror_x=True, + cs_pin=44, + dc_pin=10, + reset_pin=38, + busy_pin={ + "number": 4, + "inverted": False, + "mode": { + "input": True, + "pulldown": True, + }, + }, +) diff --git a/tests/components/epaper_spi/test.esp32-s3-idf.yaml b/tests/components/epaper_spi/test.esp32-s3-idf.yaml index cff1f51897..d330b4127d 100644 --- a/tests/components/epaper_spi/test.esp32-s3-idf.yaml +++ b/tests/components/epaper_spi/test.esp32-s3-idf.yaml @@ -19,3 +19,8 @@ display: - platform: epaper_spi model: seeed-reterminal-e1002 + - platform: epaper_spi + model: seeed-ee04-mono-4.26 + # Override pins to avoid conflict with other display configs + busy_pin: 43 + dc_pin: 42 From 23e58c1c7b597b81f0634acfa3b907686de68333 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 27 Nov 2025 12:08:40 +1300 Subject: [PATCH 0401/1145] [inkplate] Ignore strapping pin warnings on default pins (#12110) --- esphome/components/inkplate/display.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/esphome/components/inkplate/display.py b/esphome/components/inkplate/display.py index 89518dcfab..47c8c898e5 100644 --- a/esphome/components/inkplate/display.py +++ b/esphome/components/inkplate/display.py @@ -6,10 +6,12 @@ import esphome.config_validation as cv from esphome.const import ( CONF_FULL_UPDATE_EVERY, CONF_ID, + CONF_IGNORE_STRAPPING_WARNING, CONF_LAMBDA, CONF_MIRROR_X, CONF_MIRROR_Y, CONF_MODEL, + CONF_NUMBER, CONF_OE_PIN, CONF_PAGES, CONF_TRANSFORM, @@ -101,14 +103,21 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_SPV_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_VCOM_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_WAKEUP_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_CL_PIN, default=0): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_LE_PIN, default=2): pins.internal_gpio_output_pin_schema, + cv.Optional( + CONF_CL_PIN, + default={CONF_NUMBER: 0, CONF_IGNORE_STRAPPING_WARNING: True}, + ): pins.internal_gpio_output_pin_schema, + cv.Optional( + CONF_LE_PIN, + default={CONF_NUMBER: 2, CONF_IGNORE_STRAPPING_WARNING: True}, + ): pins.internal_gpio_output_pin_schema, # Data pins cv.Optional( CONF_DISPLAY_DATA_0_PIN, default=4 ): pins.internal_gpio_output_pin_schema, cv.Optional( - CONF_DISPLAY_DATA_1_PIN, default=5 + CONF_DISPLAY_DATA_1_PIN, + default={CONF_NUMBER: 5, CONF_IGNORE_STRAPPING_WARNING: True}, ): pins.internal_gpio_output_pin_schema, cv.Optional( CONF_DISPLAY_DATA_2_PIN, default=18 From 9c85ec9182bbbbdf9ab057ec2ff931dbcffe4d7e Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 26 Nov 2025 20:01:35 -0500 Subject: [PATCH 0402/1145] [esp32] Fix hosted update when there is no wifi (#12123) --- .../components/esp32_hosted/update/esp32_hosted_update.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp index f34a0ae10e..de130ca71f 100644 --- a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp @@ -22,6 +22,11 @@ constexpr size_t CHUNK_SIZE = 1500; void Esp32HostedUpdate::setup() { this->update_info_.title = "ESP32 Hosted Coprocessor"; + // if wifi is not present, connect to the coprocessor +#ifndef USE_WIFI + esp_hosted_connect_to_slave(); // NOLINT +#endif + // get coprocessor version esp_hosted_coprocessor_fwver_t ver_info; if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) { From a7a5a0b9a23a048a63b8e40621f39c411b9db8c4 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 26 Nov 2025 22:46:17 -0500 Subject: [PATCH 0403/1145] [esp32] Improve IDF component support (#12127) --- esphome/components/esp32/__init__.py | 74 ++++++++++++++----- tests/components/esp32/test.esp32-p4-idf.yaml | 4 + 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index d372af3e6a..35ef76634b 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -37,6 +37,7 @@ from esphome.const import ( __version__, ) from esphome.core import CORE, HexInt, TimePeriod +from esphome.coroutine import CoroPriority, coroutine_with_priority import esphome.final_validate as fv from esphome.helpers import copy_file_if_changed, write_file_if_changed from esphome.types import ConfigType @@ -262,15 +263,32 @@ def add_idf_component( "deprecated and will be removed in ESPHome 2026.1. If you are seeing this, report " "an issue to the external_component author and ask them to update it." ) + components_registry = CORE.data[KEY_ESP32][KEY_COMPONENTS] if components: for comp in components: - CORE.data[KEY_ESP32][KEY_COMPONENTS][comp] = { + existing = components_registry.get(comp) + if existing and existing.get(KEY_REF) != ref: + _LOGGER.warning( + "IDF component %s version conflict %s replaced by %s", + comp, + existing.get(KEY_REF), + ref, + ) + components_registry[comp] = { KEY_REPO: repo, KEY_REF: ref, KEY_PATH: f"{path}/{comp}" if path else comp, } else: - CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = { + existing = components_registry.get(name) + if existing and existing.get(KEY_REF) != ref: + _LOGGER.warning( + "IDF component %s version conflict %s replaced by %s", + name, + existing.get(KEY_REF), + ref, + ) + components_registry[name] = { KEY_REPO: repo, KEY_REF: ref, KEY_PATH: path, @@ -592,6 +610,14 @@ def require_vfs_dir() -> None: CORE.data[KEY_VFS_DIR_REQUIRED] = True +def _parse_idf_component(value: str) -> ConfigType: + """Parse IDF component shorthand syntax like 'owner/component^version'""" + if "^" not in value: + raise cv.Invalid(f"Invalid IDF component shorthand '{value}'") + name, ref = value.split("^", 1) + return {CONF_NAME: name, CONF_REF: ref} + + def _validate_idf_component(config: ConfigType) -> ConfigType: """Validate IDF component config and warn about deprecated options.""" if CONF_REFRESH in config: @@ -659,14 +685,19 @@ FRAMEWORK_SCHEMA = cv.Schema( ), cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( cv.All( - cv.Schema( - { - cv.Required(CONF_NAME): cv.string_strict, - cv.Optional(CONF_SOURCE): cv.git_ref, - cv.Optional(CONF_REF): cv.string, - cv.Optional(CONF_PATH): cv.string, - cv.Optional(CONF_REFRESH): cv.All(cv.string, cv.source_refresh), - } + cv.Any( + cv.All(cv.string_strict, _parse_idf_component), + cv.Schema( + { + cv.Required(CONF_NAME): cv.string_strict, + cv.Optional(CONF_SOURCE): cv.git_ref, + cv.Optional(CONF_REF): cv.string, + cv.Optional(CONF_PATH): cv.string, + cv.Optional(CONF_REFRESH): cv.All( + cv.string, cv.source_refresh + ), + } + ), ), _validate_idf_component, ) @@ -851,6 +882,18 @@ def _configure_lwip_max_sockets(conf: dict) -> None: add_idf_sdkconfig_option("CONFIG_LWIP_MAX_SOCKETS", max_sockets) +@coroutine_with_priority(CoroPriority.FINAL) +async def _add_yaml_idf_components(components: list[ConfigType]): + """Add IDF components from YAML config with final priority to override code-added components.""" + for component in components: + add_idf_component( + name=component[CONF_NAME], + repo=component.get(CONF_SOURCE), + ref=component.get(CONF_REF), + path=component.get(CONF_PATH), + ) + + async def to_code(config): cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE]) @@ -1097,13 +1140,10 @@ async def to_code(config): for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) - for component in conf[CONF_COMPONENTS]: - add_idf_component( - name=component[CONF_NAME], - repo=component.get(CONF_SOURCE), - ref=component.get(CONF_REF), - path=component.get(CONF_PATH), - ) + # Components from YAML are added in a separate coroutine with FINAL priority + # Schedule it to run after all other components + if conf[CONF_COMPONENTS]: + CORE.add_job(_add_yaml_idf_components, conf[CONF_COMPONENTS]) APP_PARTITION_SIZES = { diff --git a/tests/components/esp32/test.esp32-p4-idf.yaml b/tests/components/esp32/test.esp32-p4-idf.yaml index a4c930f236..1c243ef459 100644 --- a/tests/components/esp32/test.esp32-p4-idf.yaml +++ b/tests/components/esp32/test.esp32-p4-idf.yaml @@ -4,6 +4,10 @@ esp32: cpu_frequency: 400MHz framework: type: esp-idf + components: + - espressif/mdns^1.8.2 + - name: espressif/esp_hosted + ref: 2.6.6 advanced: enable_idf_experimental_features: yes From 91df0548efc10423b5785d45d6c830178ed3dc56 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 28 Nov 2025 02:30:03 +1100 Subject: [PATCH 0404/1145] [wifi] Restore blocking setup until connected for RP2040 (#12142) --- esphome/components/wifi/wifi_component.cpp | 14 ++++++++++++++ esphome/components/wifi/wifi_component.h | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index d53de83bd3..e67493aa4d 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1612,6 +1612,20 @@ void WiFiComponent::retry_connect() { } } +#ifdef USE_RP2040 +// RP2040's mDNS library (LEAmDNS) relies on LwipIntf::stateUpCB() to restart +// mDNS when the network interface reconnects. However, this callback is disabled +// in the arduino-pico framework. As a workaround, we block component setup until +// WiFi is connected, ensuring mDNS.begin() is called with an active connection. + +bool WiFiComponent::can_proceed() { + if (!this->has_sta() || this->state_ == WIFI_COMPONENT_STATE_DISABLED || this->ap_setup_) { + return true; + } + return this->is_connected(); +} +#endif + void WiFiComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } bool WiFiComponent::is_connected() { return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED && diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index b6b956a12d..a9b03a8b8d 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -284,6 +284,10 @@ class WiFiComponent : public Component { void retry_connect(); +#ifdef USE_RP2040 + bool can_proceed() override; +#endif + void set_reboot_timeout(uint32_t reboot_timeout); bool is_connected(); From 1fadd1227d531ddb611ec58d9c189f0821cfe796 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 27 Nov 2025 10:50:21 -0600 Subject: [PATCH 0405/1145] [scheduler] Fix use-after-move crash in heap operations (#12124) --- esphome/core/scheduler.cpp | 26 +++++++++++++------------- esphome/core/scheduler.h | 4 +++- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 09d50ee7c8..352587bf10 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -359,8 +359,7 @@ void HOT Scheduler::call(uint32_t now) { std::unique_ptr item; { LockGuard guard{this->lock_}; - item = std::move(this->items_[0]); - this->pop_raw_(); + item = this->pop_raw_locked_(); } const char *name = item->get_name(); @@ -401,7 +400,7 @@ void HOT Scheduler::call(uint32_t now) { // Don't run on failed components if (item->component != nullptr && item->component->is_failed()) { LockGuard guard{this->lock_}; - this->pop_raw_(); + this->recycle_item_(this->pop_raw_locked_()); continue; } @@ -414,7 +413,7 @@ void HOT Scheduler::call(uint32_t now) { { LockGuard guard{this->lock_}; if (is_item_removed_(item.get())) { - this->pop_raw_(); + this->recycle_item_(this->pop_raw_locked_()); this->to_remove_--; continue; } @@ -423,7 +422,7 @@ void HOT Scheduler::call(uint32_t now) { // Single-threaded or multi-threaded with atomics: can check without lock if (is_item_removed_(item.get())) { LockGuard guard{this->lock_}; - this->pop_raw_(); + this->recycle_item_(this->pop_raw_locked_()); this->to_remove_--; continue; } @@ -443,14 +442,14 @@ void HOT Scheduler::call(uint32_t now) { LockGuard guard{this->lock_}; - auto executed_item = std::move(this->items_[0]); // Only pop after function call, this ensures we were reachable // during the function call and know if we were cancelled. - this->pop_raw_(); + auto executed_item = this->pop_raw_locked_(); if (executed_item->remove) { - // We were removed/cancelled in the function call, stop + // We were removed/cancelled in the function call, recycle and continue this->to_remove_--; + this->recycle_item_(std::move(executed_item)); continue; } @@ -497,7 +496,7 @@ size_t HOT Scheduler::cleanup_() { return this->items_.size(); // We must hold the lock for the entire cleanup operation because: - // 1. We're modifying items_ (via pop_raw_) which requires exclusive access + // 1. We're modifying items_ (via pop_raw_locked_) which requires exclusive access // 2. We're decrementing to_remove_ which is also modified by other threads // (though all modifications are already under lock) // 3. Other threads read items_ when searching for items to cancel in cancel_item_locked_() @@ -510,17 +509,18 @@ size_t HOT Scheduler::cleanup_() { if (!item->remove) break; this->to_remove_--; - this->pop_raw_(); + this->recycle_item_(this->pop_raw_locked_()); } return this->items_.size(); } -void HOT Scheduler::pop_raw_() { +std::unique_ptr HOT Scheduler::pop_raw_locked_() { std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp); - // Instead of destroying, recycle the item - this->recycle_item_(std::move(this->items_.back())); + // Move the item out before popping - this is the item that was at the front of the heap + auto item = std::move(this->items_.back()); this->items_.pop_back(); + return item; } // Helper to execute a scheduler item diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index bea1503df0..08e003c9fb 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -219,7 +219,9 @@ class Scheduler { // Returns the number of items remaining after cleanup // IMPORTANT: This method should only be called from the main thread (loop task). size_t cleanup_(); - void pop_raw_(); + // Remove and return the front item from the heap + // IMPORTANT: Caller must hold the scheduler lock before calling this function. + std::unique_ptr pop_raw_locked_(); private: // Helper to cancel items by name - must be called with lock held From 9289fc36f79222af346394b096ad629b683234d8 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Tue, 25 Nov 2025 13:48:32 +0100 Subject: [PATCH 0406/1145] [nextion] Do not set alternative baud rate when not specified or `<= 0` (#12097) --- esphome/components/nextion/nextion_upload_arduino.cpp | 3 +++ esphome/components/nextion/nextion_upload_idf.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/esphome/components/nextion/nextion_upload_arduino.cpp b/esphome/components/nextion/nextion_upload_arduino.cpp index b4d217d7aa..baea938729 100644 --- a/esphome/components/nextion/nextion_upload_arduino.cpp +++ b/esphome/components/nextion/nextion_upload_arduino.cpp @@ -174,6 +174,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { // Check if baud rate is supported this->original_baud_rate_ = this->parent_->get_baud_rate(); + if (baud_rate <= 0) { + baud_rate = this->original_baud_rate_; + } ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); // Define the configuration for the HTTP client diff --git a/esphome/components/nextion/nextion_upload_idf.cpp b/esphome/components/nextion/nextion_upload_idf.cpp index 3b0d65643d..942e3dd6c3 100644 --- a/esphome/components/nextion/nextion_upload_idf.cpp +++ b/esphome/components/nextion/nextion_upload_idf.cpp @@ -177,6 +177,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { // Check if baud rate is supported this->original_baud_rate_ = this->parent_->get_baud_rate(); + if (baud_rate <= 0) { + baud_rate = this->original_baud_rate_; + } ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); // Define the configuration for the HTTP client From acdcd56395fdcad51c652c10b6e289196403ef1b Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:14:53 -0500 Subject: [PATCH 0407/1145] [esp32] Fix platformio flash size print (#12099) Co-authored-by: J. Nick Koston --- esphome/components/esp32/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 59c6029334..d372af3e6a 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -854,6 +854,10 @@ def _configure_lwip_max_sockets(conf: dict) -> None: async def to_code(config): cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE]) + cg.add_platformio_option( + "board_upload.maximum_size", + int(config[CONF_FLASH_SIZE].removesuffix("MB")) * 1024 * 1024, + ) cg.set_cpp_standard("gnu++20") cg.add_build_flag("-DUSE_ESP32") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) From 278f12fb99fd83eefaf261e2ccab396c0cb38d67 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Nov 2025 10:30:01 -0600 Subject: [PATCH 0408/1145] [script] Fix script.wait hanging when triggered from on_boot (#12102) --- esphome/components/script/script.h | 7 +- .../fixtures/script_wait_on_boot.yaml | 54 ++++++++ tests/integration/test_script_wait_on_boot.py | 130 ++++++++++++++++++ 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 tests/integration/fixtures/script_wait_on_boot.yaml create mode 100644 tests/integration/test_script_wait_on_boot.py diff --git a/esphome/components/script/script.h b/esphome/components/script/script.h index d60ed657f7..3a0823f3cc 100644 --- a/esphome/components/script/script.h +++ b/esphome/components/script/script.h @@ -278,7 +278,12 @@ template class ScriptWaitAction : public Action, void setup() override { // Start with loop disabled - only enable when there's work to do - this->disable_loop(); + // IMPORTANT: Only disable if num_running_ is 0, otherwise play_complex() was already + // called before our setup() (e.g., from on_boot trigger at same priority level) + // and we must not undo its enable_loop() call + if (this->num_running_ == 0) { + this->disable_loop(); + } } void play_complex(const Ts &...x) override { diff --git a/tests/integration/fixtures/script_wait_on_boot.yaml b/tests/integration/fixtures/script_wait_on_boot.yaml new file mode 100644 index 0000000000..8736b02294 --- /dev/null +++ b/tests/integration/fixtures/script_wait_on_boot.yaml @@ -0,0 +1,54 @@ +esphome: + name: test-script-wait-on-boot + on_boot: + # Use default priority (600.0) which is same as ScriptWaitAction's setup priority + # This tests the race condition where on_boot runs before ScriptWaitAction::setup() + then: + - logger.log: "=== on_boot: Starting boot sequence ===" + - script.execute: show_start_page + - script.wait: show_start_page + - logger.log: "=== on_boot: First script completed, starting second ===" + - script.execute: flip_thru_pages + - script.wait: flip_thru_pages + - logger.log: "=== on_boot: All boot scripts completed successfully ===" + +host: + +api: + actions: + # Manual trigger for additional testing + - action: test_script_wait + then: + - logger.log: "=== Manual test: Starting ===" + - script.execute: show_start_page + - script.wait: show_start_page + - logger.log: "=== Manual test: First script completed ===" + - script.execute: flip_thru_pages + - script.wait: flip_thru_pages + - logger.log: "=== Manual test: All completed ===" + +logger: + level: DEBUG + +script: + # First script - simulates display initialization + - id: show_start_page + mode: single + then: + - logger.log: "show_start_page: Starting" + - delay: 100ms + - logger.log: "show_start_page: After delay 1" + - delay: 100ms + - logger.log: "show_start_page: Completed" + + # Second script - simulates page flip sequence + - id: flip_thru_pages + mode: single + then: + - logger.log: "flip_thru_pages: Starting" + - delay: 50ms + - logger.log: "flip_thru_pages: Page 1" + - delay: 50ms + - logger.log: "flip_thru_pages: Page 2" + - delay: 50ms + - logger.log: "flip_thru_pages: Completed" diff --git a/tests/integration/test_script_wait_on_boot.py b/tests/integration/test_script_wait_on_boot.py new file mode 100644 index 0000000000..478090f782 --- /dev/null +++ b/tests/integration/test_script_wait_on_boot.py @@ -0,0 +1,130 @@ +"""Integration test for script.wait during on_boot (issue #12043). + +This test verifies that script.wait works correctly when triggered from on_boot. +The issue was that ScriptWaitAction::setup() unconditionally disabled the loop, +even if play_complex() had already been called (from an on_boot trigger at the +same priority level) and enabled it. + +The race condition occurs because: +1. on_boot's default priority is 600.0 (setup_priority::DATA) +2. ScriptWaitAction's default setup priority is also DATA (600.0) +3. When they have the same priority, if on_boot runs first and triggers a script, + ScriptWaitAction::play_complex() enables the loop +4. Then ScriptWaitAction::setup() runs and unconditionally disables the loop +5. The wait never completes because the loop is disabled + +The fix adds a conditional check (like WaitUntilAction has) to only disable the +loop in setup() if num_running_ is 0. +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_script_wait_on_boot( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that script.wait works correctly when triggered from on_boot. + + This reproduces issue #12043 where script.wait would hang forever when + triggered from on_boot due to a race condition in ScriptWaitAction::setup(). + """ + test_complete = asyncio.Event() + + # Track progress through the boot sequence + boot_started = False + first_script_started = False + first_script_completed = False + first_wait_returned = False + second_script_started = False + second_script_completed = False + all_completed = False + + # Patterns for boot sequence logs + boot_start_pattern = re.compile(r"on_boot: Starting boot sequence") + show_start_pattern = re.compile(r"show_start_page: Starting") + show_complete_pattern = re.compile(r"show_start_page: Completed") + first_wait_pattern = re.compile(r"on_boot: First script completed") + flip_start_pattern = re.compile(r"flip_thru_pages: Starting") + flip_complete_pattern = re.compile(r"flip_thru_pages: Completed") + all_complete_pattern = re.compile(r"on_boot: All boot scripts completed") + + def check_output(line: str) -> None: + """Check log output for boot sequence progress.""" + nonlocal boot_started, first_script_started, first_script_completed + nonlocal first_wait_returned, second_script_started, second_script_completed + nonlocal all_completed + + if boot_start_pattern.search(line): + boot_started = True + elif show_start_pattern.search(line): + first_script_started = True + elif show_complete_pattern.search(line): + first_script_completed = True + elif first_wait_pattern.search(line): + first_wait_returned = True + elif flip_start_pattern.search(line): + second_script_started = True + elif flip_complete_pattern.search(line): + second_script_completed = True + elif all_complete_pattern.search(line): + all_completed = True + test_complete.set() + + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "test-script-wait-on-boot" + + # Wait for on_boot sequence to complete + # The boot sequence should complete automatically + # Timeout is generous to allow for delays in the scripts + try: + await asyncio.wait_for(test_complete.wait(), timeout=5.0) + except TimeoutError: + # Build a detailed error message showing where the boot sequence got stuck + progress = [] + if boot_started: + progress.append("boot started") + if first_script_started: + progress.append("show_start_page started") + if first_script_completed: + progress.append("show_start_page completed") + if first_wait_returned: + progress.append("first script.wait returned") + if second_script_started: + progress.append("flip_thru_pages started") + if second_script_completed: + progress.append("flip_thru_pages completed") + + if not first_wait_returned and first_script_completed: + pytest.fail( + f"Test timed out - script.wait hung after show_start_page completed! " + f"This is the issue #12043 bug. Progress: {', '.join(progress)}" + ) + else: + pytest.fail( + f"Test timed out. Progress: {', '.join(progress) if progress else 'none'}" + ) + + # Verify the complete boot sequence executed in order + assert boot_started, "on_boot did not start" + assert first_script_started, "show_start_page did not start" + assert first_script_completed, "show_start_page did not complete" + assert first_wait_returned, "First script.wait did not return" + assert second_script_started, "flip_thru_pages did not start" + assert second_script_completed, "flip_thru_pages did not complete" + assert all_completed, "Boot sequence did not complete" From 46ae6d35a237fecea76eb68a4398ac2d7765a5d5 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 26 Nov 2025 05:06:42 +1000 Subject: [PATCH 0409/1145] [lvgl] Allow multiple widgets per grid cell (#12091) --- esphome/components/lvgl/layout.py | 9 ++++++++- tests/components/lvgl/lvgl-package.yaml | 5 +++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/esphome/components/lvgl/layout.py b/esphome/components/lvgl/layout.py index a6aa816fda..caa503ef0d 100644 --- a/esphome/components/lvgl/layout.py +++ b/esphome/components/lvgl/layout.py @@ -36,6 +36,8 @@ from .defines import ( ) from .lv_validation import padding, size +CONF_MULTIPLE_WIDGETS_PER_CELL = "multiple_widgets_per_cell" + cell_alignments = LV_CELL_ALIGNMENTS.one_of grid_alignments = LV_GRID_ALIGNMENTS.one_of flex_alignments = LV_FLEX_ALIGNMENTS.one_of @@ -220,6 +222,7 @@ class GridLayout(Layout): cv.Optional(CONF_GRID_ROW_ALIGN): grid_alignments, cv.Optional(CONF_PAD_ROW): padding, cv.Optional(CONF_PAD_COLUMN): padding, + cv.Optional(CONF_MULTIPLE_WIDGETS_PER_CELL, default=False): cv.boolean, }, { cv.Optional(CONF_GRID_CELL_ROW_POS): cv.positive_int, @@ -263,6 +266,7 @@ class GridLayout(Layout): # should be guaranteed to be a dict at this point assert isinstance(layout, dict) assert layout.get(CONF_TYPE).lower() == TYPE_GRID + allow_multiple = layout.get(CONF_MULTIPLE_WIDGETS_PER_CELL, False) rows = len(layout[CONF_GRID_ROWS]) columns = len(layout[CONF_GRID_COLUMNS]) used_cells = [[None] * columns for _ in range(rows)] @@ -299,7 +303,10 @@ class GridLayout(Layout): f"exceeds grid size {rows}x{columns}", [CONF_WIDGETS, index], ) - if used_cells[row + i][column + j] is not None: + if ( + not allow_multiple + and used_cells[row + i][column + j] is not None + ): raise cv.Invalid( f"Cell span {row + i}/{column + j} already occupied by widget at index {used_cells[row + i][column + j]}", [CONF_WIDGETS, index], diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index cb5b6f59b1..70afd5b3d9 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -881,6 +881,7 @@ lvgl: grid_columns: [40, fr(1), fr(1)] pad_row: 6px pad_column: 0 + multiple_widgets_per_cell: true widgets: - image: grid_cell_row_pos: 0 @@ -905,6 +906,10 @@ lvgl: grid_cell_row_pos: 1 grid_cell_column_pos: 0 text: "Grid cell 1/0" + - label: + grid_cell_row_pos: 1 + grid_cell_column_pos: 0 + text: "Duplicate for 1/0" - label: styles: bdr_style grid_cell_row_pos: 1 From ae140f52e3dbfab83c516a9b5e7d1997fe7b3149 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:47:27 +1000 Subject: [PATCH 0410/1145] [lvgl] Fix position of errors in widget config (#12111) Co-authored-by: J. Nick Koston --- esphome/components/lvgl/schemas.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 6b77f66abb..b2d463c5fd 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -1,6 +1,7 @@ from esphome import config_validation as cv from esphome.automation import Trigger, validate_automation from esphome.components.time import RealTimeClock +from esphome.config_validation import prepend_path from esphome.const import ( CONF_ARGS, CONF_FORMAT, @@ -422,7 +423,10 @@ def any_widget_schema(extras=None): def validator(value): if isinstance(value, dict): # Convert to list + is_dict = True value = [{k: v} for k, v in value.items()] + else: + is_dict = False if not isinstance(value, list): raise cv.Invalid("Expected a list of widgets") result = [] @@ -443,7 +447,9 @@ def any_widget_schema(extras=None): ) # Apply custom validation value = widget_type.validate(value or {}) - result.append({key: container_validator(value)}) + path = [key] if is_dict else [index, key] + with prepend_path(path): + result.append({key: container_validator(value)}) return result return validator From 6645994700cd0c9b3bd25e2799bfdc4a2f606fb9 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 26 Nov 2025 20:01:35 -0500 Subject: [PATCH 0411/1145] [esp32] Fix hosted update when there is no wifi (#12123) --- .../components/esp32_hosted/update/esp32_hosted_update.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp index adbcc5bf11..6f91d1b3e6 100644 --- a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp @@ -22,6 +22,11 @@ constexpr size_t CHUNK_SIZE = 1500; void Esp32HostedUpdate::setup() { this->update_info_.title = "ESP32 Hosted Coprocessor"; + // if wifi is not present, connect to the coprocessor +#ifndef USE_WIFI + esp_hosted_connect_to_slave(); // NOLINT +#endif + // get coprocessor version esp_hosted_coprocessor_fwver_t ver_info; if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) { From b4b34aee13874eb4d3ce4062fa90ff50c1636cdd Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 28 Nov 2025 02:30:03 +1100 Subject: [PATCH 0412/1145] [wifi] Restore blocking setup until connected for RP2040 (#12142) --- esphome/components/wifi/wifi_component.cpp | 14 ++++++++++++++ esphome/components/wifi/wifi_component.h | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e31d7bbf32..abf62cb063 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1555,6 +1555,20 @@ void WiFiComponent::retry_connect() { } } +#ifdef USE_RP2040 +// RP2040's mDNS library (LEAmDNS) relies on LwipIntf::stateUpCB() to restart +// mDNS when the network interface reconnects. However, this callback is disabled +// in the arduino-pico framework. As a workaround, we block component setup until +// WiFi is connected, ensuring mDNS.begin() is called with an active connection. + +bool WiFiComponent::can_proceed() { + if (!this->has_sta() || this->state_ == WIFI_COMPONENT_STATE_DISABLED || this->ap_setup_) { + return true; + } + return this->is_connected(); +} +#endif + void WiFiComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } bool WiFiComponent::is_connected() { return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED && diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 2e0a9816c6..28eef211d3 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -280,6 +280,10 @@ class WiFiComponent : public Component { void retry_connect(); +#ifdef USE_RP2040 + bool can_proceed() override; +#endif + void set_reboot_timeout(uint32_t reboot_timeout); bool is_connected(); From d5e2543751105d90b31e4e0d6dc9bacd08ca9827 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 27 Nov 2025 10:50:21 -0600 Subject: [PATCH 0413/1145] [scheduler] Fix use-after-move crash in heap operations (#12124) --- esphome/core/scheduler.cpp | 26 +++++++++++++------------- esphome/core/scheduler.h | 4 +++- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 09d50ee7c8..352587bf10 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -359,8 +359,7 @@ void HOT Scheduler::call(uint32_t now) { std::unique_ptr item; { LockGuard guard{this->lock_}; - item = std::move(this->items_[0]); - this->pop_raw_(); + item = this->pop_raw_locked_(); } const char *name = item->get_name(); @@ -401,7 +400,7 @@ void HOT Scheduler::call(uint32_t now) { // Don't run on failed components if (item->component != nullptr && item->component->is_failed()) { LockGuard guard{this->lock_}; - this->pop_raw_(); + this->recycle_item_(this->pop_raw_locked_()); continue; } @@ -414,7 +413,7 @@ void HOT Scheduler::call(uint32_t now) { { LockGuard guard{this->lock_}; if (is_item_removed_(item.get())) { - this->pop_raw_(); + this->recycle_item_(this->pop_raw_locked_()); this->to_remove_--; continue; } @@ -423,7 +422,7 @@ void HOT Scheduler::call(uint32_t now) { // Single-threaded or multi-threaded with atomics: can check without lock if (is_item_removed_(item.get())) { LockGuard guard{this->lock_}; - this->pop_raw_(); + this->recycle_item_(this->pop_raw_locked_()); this->to_remove_--; continue; } @@ -443,14 +442,14 @@ void HOT Scheduler::call(uint32_t now) { LockGuard guard{this->lock_}; - auto executed_item = std::move(this->items_[0]); // Only pop after function call, this ensures we were reachable // during the function call and know if we were cancelled. - this->pop_raw_(); + auto executed_item = this->pop_raw_locked_(); if (executed_item->remove) { - // We were removed/cancelled in the function call, stop + // We were removed/cancelled in the function call, recycle and continue this->to_remove_--; + this->recycle_item_(std::move(executed_item)); continue; } @@ -497,7 +496,7 @@ size_t HOT Scheduler::cleanup_() { return this->items_.size(); // We must hold the lock for the entire cleanup operation because: - // 1. We're modifying items_ (via pop_raw_) which requires exclusive access + // 1. We're modifying items_ (via pop_raw_locked_) which requires exclusive access // 2. We're decrementing to_remove_ which is also modified by other threads // (though all modifications are already under lock) // 3. Other threads read items_ when searching for items to cancel in cancel_item_locked_() @@ -510,17 +509,18 @@ size_t HOT Scheduler::cleanup_() { if (!item->remove) break; this->to_remove_--; - this->pop_raw_(); + this->recycle_item_(this->pop_raw_locked_()); } return this->items_.size(); } -void HOT Scheduler::pop_raw_() { +std::unique_ptr HOT Scheduler::pop_raw_locked_() { std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp); - // Instead of destroying, recycle the item - this->recycle_item_(std::move(this->items_.back())); + // Move the item out before popping - this is the item that was at the front of the heap + auto item = std::move(this->items_.back()); this->items_.pop_back(); + return item; } // Helper to execute a scheduler item diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index bea1503df0..08e003c9fb 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -219,7 +219,9 @@ class Scheduler { // Returns the number of items remaining after cleanup // IMPORTANT: This method should only be called from the main thread (loop task). size_t cleanup_(); - void pop_raw_(); + // Remove and return the front item from the heap + // IMPORTANT: Caller must hold the scheduler lock before calling this function. + std::unique_ptr pop_raw_locked_(); private: // Helper to cancel items by name - must be called with lock held From 4115dd7222088bae60d3995c06c876be4a1b5e88 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:23:28 -0500 Subject: [PATCH 0414/1145] Bump version to 2025.11.2 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index a2b6efcfae..d30bd84257 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.11.1 +PROJECT_NUMBER = 2025.11.2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index f4ddd01c09..45b726e599 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.11.1" +__version__ = "2025.11.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 4c549798bc9faca98036748b220d122fe55eccd0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 27 Nov 2025 16:33:08 -0600 Subject: [PATCH 0415/1145] [usb_uart] Wake main loop immediately when USB data arrives (#12148) --- esphome/components/usb_uart/__init__.py | 7 ++++++- esphome/components/usb_uart/usb_uart.cpp | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/components/usb_uart/__init__.py b/esphome/components/usb_uart/__init__.py index a852e1f78b..d9bb58ae3a 100644 --- a/esphome/components/usb_uart/__init__.py +++ b/esphome/components/usb_uart/__init__.py @@ -1,4 +1,5 @@ import esphome.codegen as cg +from esphome.components import socket from esphome.components.uart import ( CONF_DATA_BITS, CONF_PARITY, @@ -17,7 +18,7 @@ from esphome.const import ( ) from esphome.cpp_types import Component -AUTO_LOAD = ["uart", "usb_host", "bytebuffer"] +AUTO_LOAD = ["uart", "usb_host", "bytebuffer", "socket"] CODEOWNERS = ["@clydebarrow"] usb_uart_ns = cg.esphome_ns.namespace("usb_uart") @@ -116,6 +117,10 @@ CONFIG_SCHEMA = cv.ensure_list( async def to_code(config): + # Enable wake_loop_threadsafe for low-latency USB data processing + # The USB task queues data events that need immediate processing + socket.require_wake_loop_threadsafe() + for device in config: var = await register_usb_client(device) for index, channel in enumerate(device[CONF_CHANNELS]): diff --git a/esphome/components/usb_uart/usb_uart.cpp b/esphome/components/usb_uart/usb_uart.cpp index 6720c1e690..fefccd3645 100644 --- a/esphome/components/usb_uart/usb_uart.cpp +++ b/esphome/components/usb_uart/usb_uart.cpp @@ -2,6 +2,7 @@ #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) #include "usb_uart.h" #include "esphome/core/log.h" +#include "esphome/core/application.h" #include "esphome/components/uart/uart_debugger.h" #include @@ -262,6 +263,11 @@ void USBUartComponent::start_input(USBUartChannel *channel) { // Push to lock-free queue for main loop processing // Push always succeeds because pool size == queue size this->usb_data_queue_.push(chunk); + + // Wake main loop immediately to process USB data instead of waiting for select() timeout +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + App.wake_loop_threadsafe(); +#endif } // On success, restart input immediately from USB task for performance From 71dc402a30190fa91af8d5d55d48f697a121ba64 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 27 Nov 2025 22:00:33 -0600 Subject: [PATCH 0416/1145] [logger] Replace std::function callbacks with LogListener interface (#12153) --- esphome/components/api/api_server.cpp | 29 ++++++----- esphome/components/api/api_server.h | 14 +++++- esphome/components/ble_nus/ble_nus.cpp | 18 ++++--- esphome/components/ble_nus/ble_nus.h | 13 ++++- esphome/components/logger/logger.cpp | 11 ++--- esphome/components/logger/logger.h | 51 ++++++++++++++------ esphome/components/mqtt/mqtt_client.cpp | 22 +++++---- esphome/components/mqtt/mqtt_client.h | 14 +++++- esphome/components/syslog/esphome_syslog.cpp | 9 ++-- esphome/components/syslog/esphome_syslog.h | 4 +- esphome/components/web_server/web_server.cpp | 17 ++++--- esphome/components/web_server/web_server.h | 16 +++++- 12 files changed, 153 insertions(+), 65 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 64f8751c35..de0c4b24c9 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -101,19 +101,7 @@ void APIServer::setup() { #ifdef USE_LOGGER if (logger::global_logger != nullptr) { - logger::global_logger->add_on_log_callback( - [this](int level, const char *tag, const char *message, size_t message_len) { - if (this->shutting_down_) { - // Don't try to send logs during shutdown - // as it could result in a recursion and - // we would be filling a buffer we are trying to clear - return; - } - for (auto &c : this->clients_) { - if (!c->flags_.remove && c->get_log_subscription_level() >= level) - c->try_send_log_message(level, tag, message, message_len); - } - }); + logger::global_logger->add_log_listener(this); } #endif @@ -541,6 +529,21 @@ bool APIServer::is_connected(bool state_subscription_only) const { return false; } +#ifdef USE_LOGGER +void APIServer::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) { + if (this->shutting_down_) { + // Don't try to send logs during shutdown + // as it could result in a recursion and + // we would be filling a buffer we are trying to clear + return; + } + for (auto &c : this->clients_) { + if (!c->flags_.remove && c->get_log_subscription_level() >= level) + c->try_send_log_message(level, tag, message, message_len); + } +} +#endif + void APIServer::on_shutdown() { this->shutting_down_ = true; diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 428429418a..57aea6ad0e 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -15,6 +15,9 @@ #ifdef USE_API_USER_DEFINED_ACTIONS #include "user_services.h" #endif +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif #include #include @@ -27,7 +30,13 @@ struct SavedNoisePsk { } PACKED; // NOLINT #endif -class APIServer : public Component, public Controller { +class APIServer : public Component, + public Controller +#ifdef USE_LOGGER + , + public logger::LogListener +#endif +{ public: APIServer(); void setup() override; @@ -37,6 +46,9 @@ class APIServer : public Component, public Controller { void dump_config() override; void on_shutdown() override; bool teardown() override; +#ifdef USE_LOGGER + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; +#endif #ifdef USE_API_PASSWORD bool check_password(const uint8_t *password_data, size_t password_len) const; void set_password(const std::string &password); diff --git a/esphome/components/ble_nus/ble_nus.cpp b/esphome/components/ble_nus/ble_nus.cpp index 9c4d0a3938..bd80592d89 100644 --- a/esphome/components/ble_nus/ble_nus.cpp +++ b/esphome/components/ble_nus/ble_nus.cpp @@ -87,17 +87,21 @@ void BLENUS::setup() { global_ble_nus = this; #ifdef USE_LOGGER if (logger::global_logger != nullptr && this->expose_log_) { - logger::global_logger->add_on_log_callback( - [this](int level, const char *tag, const char *message, size_t message_len) { - this->write_array(reinterpret_cast(message), message_len); - const char c = '\n'; - this->write_array(reinterpret_cast(&c), 1); - }); + logger::global_logger->add_log_listener(this); } - #endif } +#ifdef USE_LOGGER +void BLENUS::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) { + (void) level; + (void) tag; + this->write_array(reinterpret_cast(message), message_len); + const char c = '\n'; + this->write_array(reinterpret_cast(&c), 1); +} +#endif + void BLENUS::dump_config() { ESP_LOGCONFIG(TAG, "ble nus:"); ESP_LOGCONFIG(TAG, " log: %s", YESNO(this->expose_log_)); diff --git a/esphome/components/ble_nus/ble_nus.h b/esphome/components/ble_nus/ble_nus.h index e8cba32b4c..ef20fc5e5b 100644 --- a/esphome/components/ble_nus/ble_nus.h +++ b/esphome/components/ble_nus/ble_nus.h @@ -2,12 +2,20 @@ #ifdef USE_ZEPHYR #include "esphome/core/defines.h" #include "esphome/core/component.h" +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif #include #include namespace esphome::ble_nus { -class BLENUS : public Component { +class BLENUS : public Component +#ifdef USE_LOGGER + , + public logger::LogListener +#endif +{ enum TxStatus { TX_DISABLED, TX_ENABLED, @@ -20,6 +28,9 @@ class BLENUS : public Component { void loop() override; size_t write_array(const uint8_t *data, size_t len); void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; } +#ifdef USE_LOGGER + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; +#endif protected: static void send_enabled_callback(bt_nus_send_status status); diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 9803bf528c..f925e85e11 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -140,8 +140,9 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas uint16_t msg_length = this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position - // Callbacks get message first (before console write) - this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start, msg_length); + // Listeners get message first (before console write) + for (auto *listener : this->log_listeners_) + listener->on_log(level, tag, this->tx_buffer_ + msg_start, msg_length); // Write to console starting at the msg_start this->write_tx_buffer_to_console_(msg_start, &msg_length); @@ -203,7 +204,8 @@ void Logger::process_messages_() { this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); this->tx_buffer_[this->tx_buffer_at_] = '\0'; size_t msg_len = this->tx_buffer_at_; // We already know the length from tx_buffer_at_ - this->log_callback_.call(message->level, message->tag, this->tx_buffer_, msg_len); + for (auto *listener : this->log_listeners_) + listener->on_log(message->level, message->tag, this->tx_buffer_, msg_len); // At this point all the data we need from message has been transferred to the tx_buffer // so we can release the message to allow other tasks to use it as soon as possible. this->log_buffer_->release_message_main_loop(received_token); @@ -231,9 +233,6 @@ void Logger::set_log_level(const char *tag, uint8_t log_level) { this->log_level UARTSelection Logger::get_uart() const { return this->uart_; } #endif -void Logger::add_on_log_callback(std::function &&callback) { - this->log_callback_.add(std::move(callback)); -} float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; } #ifdef USE_STORE_LOG_STR_IN_FLASH diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 6a8b640331..a0024411d7 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -36,6 +36,28 @@ struct device; namespace esphome::logger { +/** Interface for receiving log messages without std::function overhead. + * + * Components can implement this interface instead of using lambdas with std::function + * to reduce flash usage from std::function type erasure machinery. + * + * Usage: + * class MyComponent : public Component, public LogListener { + * public: + * void setup() override { + * if (logger::global_logger != nullptr) + * logger::global_logger->add_log_listener(this); + * } + * void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { + * // Handle log message + * } + * }; + */ +class LogListener { + public: + virtual void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) = 0; +}; + #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS // Comparison function for const char* keys in log_levels_ map struct CStrCompare { @@ -168,8 +190,8 @@ class Logger : public Component { inline uint8_t level_for(const char *tag); - /// Register a callback that will be called for every log message sent - void add_on_log_callback(std::function &&callback); + /// Register a log listener to receive log messages + void add_log_listener(LogListener *listener) { this->log_listeners_.push_back(listener); } // add a listener for log level changes void add_listener(std::function &&callback) { this->level_callback_.add(std::move(callback)); } @@ -240,7 +262,7 @@ class Logger : public Component { } } - // Helper to format and send a log message to both console and callbacks + // Helper to format and send a log message to both console and listeners inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // Format to tx_buffer and prepare for output @@ -248,8 +270,9 @@ class Logger : public Component { this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); - // Callbacks get message WITHOUT newline (for API/MQTT/syslog) - this->log_callback_.call(level, tag, this->tx_buffer_, this->tx_buffer_at_); + // Listeners get message WITHOUT newline (for API/MQTT/syslog) + for (auto *listener : this->log_listeners_) + listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_); // Console gets message WITH newline (if platform needs it) this->write_tx_buffer_to_console_(); @@ -301,7 +324,7 @@ class Logger : public Component { #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS std::map log_levels_{}; #endif - CallbackManager log_callback_{}; + std::vector log_listeners_; // Log message listeners (API, MQTT, syslog, etc.) CallbackManager level_callback_{}; #ifdef USE_ESPHOME_TASK_LOG_BUFFER std::unique_ptr log_buffer_; // Will be initialized with init_log_buffer @@ -496,15 +519,15 @@ class Logger : public Component { }; extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -class LoggerMessageTrigger : public Trigger { +class LoggerMessageTrigger : public Trigger, public LogListener { public: - explicit LoggerMessageTrigger(Logger *parent, uint8_t level) { - this->level_ = level; - parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message, size_t message_len) { - if (level <= this->level_) { - this->trigger(level, tag, message); - } - }); + explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) { parent->add_log_listener(this); } + + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { + (void) message_len; + if (level <= this->level_) { + this->trigger(level, tag, message); + } } protected: diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index a810d98adf..ba701b90a3 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -57,15 +57,7 @@ void MQTTClientComponent::setup() { }); #ifdef USE_LOGGER if (this->is_log_message_enabled() && logger::global_logger != nullptr) { - logger::global_logger->add_on_log_callback( - [this](int level, const char *tag, const char *message, size_t message_len) { - if (level <= this->log_level_ && this->is_connected()) { - this->publish({.topic = this->log_message_.topic, - .payload = std::string(message, message_len), - .qos = this->log_message_.qos, - .retain = this->log_message_.retain}); - } - }); + logger::global_logger->add_log_listener(this); } #endif @@ -148,6 +140,18 @@ void MQTTClientComponent::send_device_info_() { // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } +#ifdef USE_LOGGER +void MQTTClientComponent::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) { + (void) tag; + if (level <= this->log_level_ && this->is_connected()) { + this->publish({.topic = this->log_message_.topic, + .payload = std::string(message, message_len), + .qos = this->log_message_.qos, + .retain = this->log_message_.retain}); + } +} +#endif + void MQTTClientComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT:\n" diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 79383ee857..8547fe337f 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -10,6 +10,9 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif #if defined(USE_ESP32) #include "mqtt_backend_esp32.h" #elif defined(USE_ESP8266) @@ -97,7 +100,12 @@ enum MQTTClientState { class MQTTComponent; -class MQTTClientComponent : public Component { +class MQTTClientComponent : public Component +#ifdef USE_LOGGER + , + public logger::LogListener +#endif +{ public: MQTTClientComponent(); @@ -238,6 +246,10 @@ class MQTTClientComponent : public Component { /// MQTT client setup priority float get_setup_priority() const override; +#ifdef USE_LOGGER + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; +#endif + void on_message(const std::string &topic, const std::string &payload); bool can_proceed() override; diff --git a/esphome/components/syslog/esphome_syslog.cpp b/esphome/components/syslog/esphome_syslog.cpp index 71468fa932..f5c20c891e 100644 --- a/esphome/components/syslog/esphome_syslog.cpp +++ b/esphome/components/syslog/esphome_syslog.cpp @@ -19,11 +19,10 @@ constexpr int LOG_LEVEL_TO_SYSLOG_SEVERITY[] = { 7 // VERY_VERBOSE }; -void Syslog::setup() { - logger::global_logger->add_on_log_callback( - [this](int level, const char *tag, const char *message, size_t message_len) { - this->log_(level, tag, message, message_len); - }); +void Syslog::setup() { logger::global_logger->add_log_listener(this); } + +void Syslog::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) { + this->log_(level, tag, message, message_len); } void Syslog::log_(const int level, const char *tag, const char *message, size_t message_len) const { diff --git a/esphome/components/syslog/esphome_syslog.h b/esphome/components/syslog/esphome_syslog.h index e3b2f7dae5..1010993265 100644 --- a/esphome/components/syslog/esphome_syslog.h +++ b/esphome/components/syslog/esphome_syslog.h @@ -2,16 +2,18 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/components/logger/logger.h" #include "esphome/components/udp/udp_component.h" #include "esphome/components/time/real_time_clock.h" #ifdef USE_NETWORK namespace esphome { namespace syslog { -class Syslog : public Component, public Parented { +class Syslog : public Component, public Parented, public logger::LogListener { public: Syslog(int level, time::RealTimeClock *time) : log_level_(level), time_(time) {} void setup() override; + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; void set_strip(bool strip) { this->strip_ = strip; } void set_facility(int facility) { this->facility_ = facility; } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 6bf6524fbc..f5ca674161 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -301,12 +301,7 @@ void WebServer::setup() { #ifdef USE_LOGGER if (logger::global_logger != nullptr && this->expose_log_) { - logger::global_logger->add_on_log_callback( - // logs are not deferred, the memory overhead would be too large - [this](int level, const char *tag, const char *message, size_t message_len) { - (void) message_len; - this->events_.try_send_nodefer(message, "log", millis()); - }); + logger::global_logger->add_log_listener(this); } #endif @@ -322,6 +317,16 @@ void WebServer::setup() { this->set_interval(10000, [this]() { this->events_.try_send_nodefer("", "ping", millis(), 30000); }); } void WebServer::loop() { this->events_.loop(); } + +#ifdef USE_LOGGER +void WebServer::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) { + (void) level; + (void) tag; + (void) message_len; + this->events_.try_send_nodefer(message, "log", millis()); +} +#endif + void WebServer::dump_config() { ESP_LOGCONFIG(TAG, "Web Server:\n" diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 7e1af88645..52cf0bedea 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -7,6 +7,9 @@ #include "esphome/core/component.h" #include "esphome/core/controller.h" #include "esphome/core/entity_base.h" +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif #include #include @@ -170,7 +173,14 @@ class DeferredUpdateEventSourceList : public std::list Date: Thu, 27 Nov 2025 22:09:27 -0600 Subject: [PATCH 0417/1145] [light] Replace sparse enum switch with linear search to save 156 bytes RAM (#12140) --- .../components/light/light_json_schema.cpp | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 1c9b92f504..41cb855630 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -7,30 +7,29 @@ namespace esphome::light { // See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema -// Lookup table for color mode strings -static constexpr const char *get_color_mode_json_str(ColorMode mode) { - switch (mode) { - case ColorMode::ON_OFF: - return "onoff"; - case ColorMode::BRIGHTNESS: - return "brightness"; - case ColorMode::WHITE: - return "white"; // not supported by HA in MQTT - case ColorMode::COLOR_TEMPERATURE: - return "color_temp"; - case ColorMode::COLD_WARM_WHITE: - return "cwww"; // not supported by HA - case ColorMode::RGB: - return "rgb"; - case ColorMode::RGB_WHITE: - return "rgbw"; - case ColorMode::RGB_COLOR_TEMPERATURE: - return "rgbct"; // not supported by HA - case ColorMode::RGB_COLD_WARM_WHITE: - return "rgbww"; - default: - return nullptr; +// Get JSON string for color mode using linear search (avoids large switch jump table) +static const char *get_color_mode_json_str(ColorMode mode) { + // Parallel arrays: mode values and their corresponding strings + // Uses less RAM than a switch jump table on sparse enum values + static constexpr ColorMode MODES[] = { + ColorMode::ON_OFF, + ColorMode::BRIGHTNESS, + ColorMode::WHITE, + ColorMode::COLOR_TEMPERATURE, + ColorMode::COLD_WARM_WHITE, + ColorMode::RGB, + ColorMode::RGB_WHITE, + ColorMode::RGB_COLOR_TEMPERATURE, + ColorMode::RGB_COLD_WARM_WHITE, + }; + static constexpr const char *STRINGS[] = { + "onoff", "brightness", "white", "color_temp", "cwww", "rgb", "rgbw", "rgbct", "rgbww", + }; + for (size_t i = 0; i < sizeof(MODES) / sizeof(MODES[0]); i++) { + if (MODES[i] == mode) + return STRINGS[i]; } + return nullptr; } void LightJSONSchema::dump_json(LightState &state, JsonObject root) { From e1ec6146c0d19d7d4dc021ed2afdd96d8d492518 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 27 Nov 2025 22:09:41 -0600 Subject: [PATCH 0418/1145] [wifi] Save 112 bytes BSS on ESP8266 by calling SDK directly for BSSID (#12137) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- esphome/components/wifi/wifi_component_esp8266.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 540ad3a585..2d1f979c83 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -878,10 +878,9 @@ network::IPAddress WiFiComponent::wifi_soft_ap_ip() { bssid_t WiFiComponent::wifi_bssid() { bssid_t bssid{}; - uint8_t *raw_bssid = WiFi.BSSID(); - if (raw_bssid != nullptr) { - for (size_t i = 0; i < bssid.size(); i++) - bssid[i] = raw_bssid[i]; + struct station_config conf {}; + if (wifi_station_get_config(&conf)) { + std::copy_n(conf.bssid, bssid.size(), bssid.begin()); } return bssid; } From 60ffa0e52ef4a83c23052467e53c9d5d3ab20b29 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Nov 2025 11:27:08 -0600 Subject: [PATCH 0419/1145] [esp32_ble_tracker] Replace scanner state callback with listener interface (#12156) --- .../bluetooth_proxy/bluetooth_proxy.cpp | 12 +++++++----- .../bluetooth_proxy/bluetooth_proxy.h | 7 ++++++- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 4 +++- .../esp32_ble_tracker/esp32_ble_tracker.h | 19 +++++++++++++++---- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 71f8da75a7..d45377b3f6 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -27,11 +27,13 @@ void BluetoothProxy::setup() { // Capture the configured scan mode from YAML before any API changes this->configured_scan_active_ = this->parent_->get_scan_active(); - this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) { - if (this->api_connection_ != nullptr) { - this->send_bluetooth_scanner_state_(state); - } - }); + this->parent_->add_scanner_state_listener(this); +} + +void BluetoothProxy::on_scanner_state(esp32_ble_tracker::ScannerState state) { + if (this->api_connection_ != nullptr) { + this->send_bluetooth_scanner_state_(state); + } } void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state) { diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 4363c508ec..ab9aee2d81 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -52,7 +52,9 @@ enum BluetoothProxySubscriptionFlag : uint32_t { SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0, }; -class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, public Component { +class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, + public esp32_ble_tracker::BLEScannerStateListener, + public Component { friend class BluetoothConnection; // Allow connection to update connections_free_response_ public: BluetoothProxy(); @@ -108,6 +110,9 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ void set_active(bool active) { this->active_ = active; } bool has_active() { return this->active_; } + /// BLEScannerStateListener interface + void on_scanner_state(esp32_ble_tracker::ScannerState state) override; + uint32_t get_legacy_version() const { if (this->active_) { return LEGACY_ACTIVE_CONNECTIONS_VERSION; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 8577f12a92..d3c5edfb94 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -373,7 +373,9 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i void ESP32BLETracker::set_scanner_state_(ScannerState state) { this->scanner_state_ = state; - this->scanner_state_callbacks_.call(state); + for (auto *listener : this->scanner_state_listeners_) { + listener->on_scanner_state(state); + } } #ifdef USE_ESP32_BLE_DEVICE diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index f80f3e2670..92d13a62ad 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -180,6 +180,16 @@ enum class ScannerState { STOPPING, }; +/** Listener interface for BLE scanner state changes. + * + * Components can implement this interface to receive scanner state updates + * without the overhead of std::function callbacks. + */ +class BLEScannerStateListener { + public: + virtual void on_scanner_state(ScannerState state) = 0; +}; + // Helper function to convert ClientState to string const char *client_state_to_string(ClientState state); @@ -264,8 +274,9 @@ class ESP32BLETracker : public Component, void gap_scan_event_handler(const BLEScanResult &scan_result) override; void ble_before_disabled_event_handler() override; - void add_scanner_state_callback(std::function &&callback) { - this->scanner_state_callbacks_.add(std::move(callback)); + /// Add a listener for scanner state changes + void add_scanner_state_listener(BLEScannerStateListener *listener) { + this->scanner_state_listeners_.push_back(listener); } ScannerState get_scanner_state() const { return this->scanner_state_; } @@ -322,14 +333,14 @@ class ESP32BLETracker : public Component, return counts; } - // Group 1: Large objects (12+ bytes) - vectors and callback manager + // Group 1: Large objects (12+ bytes) - vectors #ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT StaticVector listeners_; #endif #ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT StaticVector clients_; #endif - CallbackManager scanner_state_callbacks_; + std::vector scanner_state_listeners_; #ifdef USE_ESP32_BLE_DEVICE /// Vector of addresses that have already been printed in print_bt_device_info std::vector already_discovered_; From 26e979d3d5a1d80c479ba4729a37875d8580ec64 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Nov 2025 11:27:17 -0600 Subject: [PATCH 0420/1145] [wifi] Replace std::function callbacks with listener interfaces (#12155) --- esphome/components/wifi/__init__.py | 16 ++-- esphome/components/wifi/wifi_component.h | 73 +++++++++++++------ .../wifi/wifi_component_esp8266.cpp | 28 ++++--- .../wifi/wifi_component_esp_idf.cpp | 30 +++++--- .../wifi/wifi_component_libretiny.cpp | 30 +++++--- .../components/wifi/wifi_component_pico_w.cpp | 24 ++++-- esphome/components/wifi_info/text_sensor.py | 6 +- .../wifi_info/wifi_info_text_sensor.cpp | 46 ++++-------- .../wifi_info/wifi_info_text_sensor.h | 36 +++++---- esphome/core/defines.h | 2 +- 10 files changed, 172 insertions(+), 119 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 31d9ca0f70..2c10506011 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -608,7 +608,7 @@ async def wifi_disable_to_code(config, action_id, template_arg, args): KEEP_SCAN_RESULTS_KEY = "wifi_keep_scan_results" RUNTIME_POWER_SAVE_KEY = "wifi_runtime_power_save" -WIFI_CALLBACKS_KEY = "wifi_callbacks" +WIFI_LISTENERS_KEY = "wifi_listeners" def request_wifi_scan_results(): @@ -634,15 +634,15 @@ def enable_runtime_power_save_control(): CORE.data[RUNTIME_POWER_SAVE_KEY] = True -def request_wifi_callbacks() -> None: - """Request that WiFi callbacks be compiled in. +def request_wifi_listeners() -> None: + """Request that WiFi state listeners be compiled in. Components that need to be notified about WiFi state changes (IP address changes, scan results, connection state) should call this function during their code generation. - This enables the add_on_ip_state_callback(), add_on_wifi_scan_state_callback(), - and add_on_wifi_connect_state_callback() APIs. + This enables the add_ip_state_listener(), add_scan_results_listener(), + and add_connect_state_listener() APIs. """ - CORE.data[WIFI_CALLBACKS_KEY] = True + CORE.data[WIFI_LISTENERS_KEY] = True @coroutine_with_priority(CoroPriority.FINAL) @@ -654,8 +654,8 @@ async def final_step(): ) if CORE.data.get(RUNTIME_POWER_SAVE_KEY, False): cg.add_define("USE_WIFI_RUNTIME_POWER_SAVE") - if CORE.data.get(WIFI_CALLBACKS_KEY, False): - cg.add_define("USE_WIFI_CALLBACKS") + if CORE.data.get(WIFI_LISTENERS_KEY, False): + cg.add_define("USE_WIFI_LISTENERS") @automation.register_action( diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index a9b03a8b8d..97cc3961fe 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -242,6 +242,37 @@ enum WifiMinAuthMode : uint8_t { struct IDFWiFiEvent; #endif +/** Listener interface for WiFi IP state changes. + * + * Components can implement this interface to receive IP address updates + * without the overhead of std::function callbacks. + */ +class WiFiIPStateListener { + public: + virtual void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, + const network::IPAddress &dns2) = 0; +}; + +/** Listener interface for WiFi scan results. + * + * Components can implement this interface to receive scan results + * without the overhead of std::function callbacks. + */ +class WiFiScanResultsListener { + public: + virtual void on_wifi_scan_results(const wifi_scan_vector_t &results) = 0; +}; + +/** Listener interface for WiFi connection state changes. + * + * Components can implement this interface to receive connection updates + * without the overhead of std::function callbacks. + */ +class WiFiConnectStateListener { + public: + virtual void on_wifi_connect_state(const std::string &ssid, const bssid_t &bssid) = 0; +}; + /// This component is responsible for managing the ESP WiFi interface. class WiFiComponent : public Component { public: @@ -373,26 +404,22 @@ class WiFiComponent : public Component { int32_t get_wifi_channel(); -#ifdef USE_WIFI_CALLBACKS - /// Add a callback that will be called on configuration changes (IP change, SSID change, etc.) - /// @param callback The callback to be called; template arguments are: - /// - IP addresses - /// - DNS address 1 - /// - DNS address 2 - void add_on_ip_state_callback( - std::function &&callback) { - this->ip_state_callback_.add(std::move(callback)); +#ifdef USE_WIFI_LISTENERS + /** Add a listener for IP state changes. + * Listener receives: IP addresses, DNS address 1, DNS address 2 + */ + void add_ip_state_listener(WiFiIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); } + /// Add a listener for WiFi scan results + void add_scan_results_listener(WiFiScanResultsListener *listener) { + this->scan_results_listeners_.push_back(listener); } - /// - Wi-Fi scan results - void add_on_wifi_scan_state_callback(std::function &)> &&callback) { - this->wifi_scan_state_callback_.add(std::move(callback)); + /** Add a listener for WiFi connection state changes. + * Listener receives: SSID, BSSID + */ + void add_connect_state_listener(WiFiConnectStateListener *listener) { + this->connect_state_listeners_.push_back(listener); } - /// - Wi-Fi SSID - /// - Wi-Fi BSSID - void add_on_wifi_connect_state_callback(std::function &&callback) { - this->wifi_connect_state_callback_.add(std::move(callback)); - } -#endif // USE_WIFI_CALLBACKS +#endif // USE_WIFI_LISTENERS #ifdef USE_WIFI_RUNTIME_POWER_SAVE /** Request high-performance mode (no power saving) for improved WiFi latency. @@ -550,11 +577,11 @@ class WiFiComponent : public Component { WiFiAP ap_; #endif optional output_power_; -#ifdef USE_WIFI_CALLBACKS - CallbackManager ip_state_callback_; - CallbackManager &)> wifi_scan_state_callback_; - CallbackManager wifi_connect_state_callback_; -#endif // USE_WIFI_CALLBACKS +#ifdef USE_WIFI_LISTENERS + std::vector ip_state_listeners_; + std::vector scan_results_listeners_; + std::vector connect_state_listeners_; +#endif // USE_WIFI_LISTENERS ESPPreferenceObject pref_; #ifdef USE_WIFI_FAST_CONNECT ESPPreferenceObject fast_connect_pref_; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 2d1f979c83..701cae5f7c 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -513,9 +513,10 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(), it.channel); s_sta_connected = true; -#ifdef USE_WIFI_CALLBACKS - global_wifi_component->wifi_connect_state_callback_.call(global_wifi_component->wifi_ssid(), - global_wifi_component->wifi_bssid()); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : global_wifi_component->connect_state_listeners_) { + listener->on_wifi_connect_state(global_wifi_component->wifi_ssid(), global_wifi_component->wifi_bssid()); + } #endif break; } @@ -536,8 +537,10 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { } s_sta_connected = false; s_sta_connecting = false; -#ifdef USE_WIFI_CALLBACKS - global_wifi_component->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0})); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : global_wifi_component->connect_state_listeners_) { + listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + } #endif break; } @@ -561,10 +564,11 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", format_ip_addr(it.ip).c_str(), format_ip_addr(it.gw).c_str(), format_ip_addr(it.mask).c_str()); s_sta_got_ip = true; -#ifdef USE_WIFI_CALLBACKS - global_wifi_component->ip_state_callback_.call(global_wifi_component->wifi_sta_ip_addresses(), - global_wifi_component->get_dns_address(0), - global_wifi_component->get_dns_address(1)); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : global_wifi_component->ip_state_listeners_) { + listener->on_ip_state(global_wifi_component->wifi_sta_ip_addresses(), global_wifi_component->get_dns_address(0), + global_wifi_component->get_dns_address(1)); + } #endif break; } @@ -740,8 +744,10 @@ void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) { it->is_hidden != 0); } this->scan_done_ = true; -#ifdef USE_WIFI_CALLBACKS - global_wifi_component->wifi_scan_state_callback_.call(global_wifi_component->scan_result_); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : global_wifi_component->scan_results_listeners_) { + listener->on_wifi_scan_results(global_wifi_component->scan_result_); + } #endif } diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index c20c96ced0..3d25d2890f 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -727,8 +727,10 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); s_sta_connected = true; -#ifdef USE_WIFI_CALLBACKS - this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid()); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->connect_state_listeners_) { + listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); + } #endif } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) { @@ -753,8 +755,10 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { s_sta_connected = false; s_sta_connecting = false; error_from_callback_ = true; -#ifdef USE_WIFI_CALLBACKS - this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0})); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->connect_state_listeners_) { + listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + } #endif } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) { @@ -764,8 +768,10 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { #endif /* USE_NETWORK_IPV6 */ ESP_LOGV(TAG, "static_ip=" IPSTR " gateway=" IPSTR, IP2STR(&it.ip_info.ip), IP2STR(&it.ip_info.gw)); this->got_ipv4_address_ = true; -#ifdef USE_WIFI_CALLBACKS - this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } #endif #if USE_NETWORK_IPV6 @@ -773,8 +779,10 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { const auto &it = data->data.ip_got_ip6; ESP_LOGV(TAG, "IPv6 address=" IPV6STR, IPV62STR(it.ip6_info.ip)); this->num_ipv6_addresses_++; -#ifdef USE_WIFI_CALLBACKS - this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } #endif #endif /* USE_NETWORK_IPV6 */ @@ -815,8 +823,10 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { scan_result_.emplace_back(bssid, ssid, record.primary, record.rssi, record.authmode != WIFI_AUTH_OPEN, ssid.empty()); } -#ifdef USE_WIFI_CALLBACKS - this->wifi_scan_state_callback_.call(this->scan_result_); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->scan_results_listeners_) { + listener->on_wifi_scan_results(this->scan_result_); + } #endif } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_START) { diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 04d0d4fa85..f1405d3bef 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -287,8 +287,10 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ buf[it.ssid_len] = '\0'; ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); -#ifdef USE_WIFI_CALLBACKS - this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid()); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->connect_state_listeners_) { + listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); + } #endif break; } @@ -315,8 +317,10 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } s_sta_connecting = false; -#ifdef USE_WIFI_CALLBACKS - this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0})); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->connect_state_listeners_) { + listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + } #endif break; } @@ -339,16 +343,20 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ ESP_LOGV(TAG, "static_ip=%s gateway=%s", format_ip4_addr(WiFi.localIP()).c_str(), format_ip4_addr(WiFi.gatewayIP()).c_str()); s_sta_connecting = false; -#ifdef USE_WIFI_CALLBACKS - this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } #endif break; } case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: { // auto it = info.got_ip.ip_info; ESP_LOGV(TAG, "Got IPv6"); -#ifdef USE_WIFI_CALLBACKS - this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } #endif break; } @@ -443,8 +451,10 @@ void WiFiComponent::wifi_scan_done_callback_() { } WiFi.scanDelete(); this->scan_done_ = true; -#ifdef USE_WIFI_CALLBACKS - this->wifi_scan_state_callback_.call(this->scan_result_); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->scan_results_listeners_) { + listener->on_wifi_scan_results(this->scan_result_); + } #endif } diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 326883c0c4..1a8b75213c 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -225,8 +225,10 @@ void WiFiComponent::wifi_loop_() { if (this->state_ == WIFI_COMPONENT_STATE_STA_SCANNING && !cyw43_wifi_scan_active(&cyw43_state)) { this->scan_done_ = true; ESP_LOGV(TAG, "Scan done"); -#ifdef USE_WIFI_CALLBACKS - this->wifi_scan_state_callback_.call(this->scan_result_); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->scan_results_listeners_) { + listener->on_wifi_scan_results(this->scan_result_); + } #endif } @@ -241,16 +243,20 @@ void WiFiComponent::wifi_loop_() { // Just connected s_sta_was_connected = true; ESP_LOGV(TAG, "Connected"); -#ifdef USE_WIFI_CALLBACKS - this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid()); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->connect_state_listeners_) { + listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); + } #endif } else if (!is_connected && s_sta_was_connected) { // Just disconnected s_sta_was_connected = false; s_sta_had_ip = false; ESP_LOGV(TAG, "Disconnected"); -#ifdef USE_WIFI_CALLBACKS - this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0})); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->connect_state_listeners_) { + listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + } #endif } @@ -267,8 +273,10 @@ void WiFiComponent::wifi_loop_() { // Just got IP address s_sta_had_ip = true; ESP_LOGV(TAG, "Got IP address"); -#ifdef USE_WIFI_CALLBACKS - this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } #endif } } diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 0feee3d4a9..bc0c038f80 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -61,7 +61,7 @@ CONFIG_SCHEMA = cv.Schema( } ) -# Keys that require WiFi callbacks +# Keys that require WiFi listeners _NETWORK_INFO_KEYS = { CONF_SSID, CONF_BSSID, @@ -79,9 +79,9 @@ async def setup_conf(config, key): async def to_code(config): - # Request WiFi callbacks for any sensor that needs them + # Request WiFi listeners for any sensor that needs them if _NETWORK_INFO_KEYS.intersection(config): - wifi.request_wifi_callbacks() + wifi.request_wifi_listeners() await setup_conf(config, CONF_SSID) await setup_conf(config, CONF_BSSID) diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index abd590b168..92d3ea29f5 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -12,16 +12,12 @@ static constexpr size_t MAX_STATE_LENGTH = 255; * IPAddressWiFiInfo *******************/ -void IPAddressWiFiInfo::setup() { - wifi::global_wifi_component->add_on_ip_state_callback( - [this](const network::IPAddresses &ips, const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip) { - this->state_callback_(ips); - }); -} +void IPAddressWiFiInfo::setup() { wifi::global_wifi_component->add_ip_state_listener(this); } void IPAddressWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "IP Address", this); } -void IPAddressWiFiInfo::state_callback_(const network::IPAddresses &ips) { +void IPAddressWiFiInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, + const network::IPAddress &dns2) { this->publish_state(ips[0].str()); uint8_t sensor = 0; for (const auto &ip : ips) { @@ -38,17 +34,13 @@ void IPAddressWiFiInfo::state_callback_(const network::IPAddresses &ips) { * DNSAddressWifiInfo ********************/ -void DNSAddressWifiInfo::setup() { - wifi::global_wifi_component->add_on_ip_state_callback( - [this](const network::IPAddresses &ips, const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip) { - this->state_callback_(dns1_ip, dns2_ip); - }); -} +void DNSAddressWifiInfo::setup() { wifi::global_wifi_component->add_ip_state_listener(this); } void DNSAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "DNS Address", this); } -void DNSAddressWifiInfo::state_callback_(const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip) { - std::string dns_results = dns1_ip.str() + " " + dns2_ip.str(); +void DNSAddressWifiInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, + const network::IPAddress &dns2) { + std::string dns_results = dns1.str() + " " + dns2.str(); this->publish_state(dns_results); } @@ -56,14 +48,11 @@ void DNSAddressWifiInfo::state_callback_(const network::IPAddress &dns1_ip, cons * ScanResultsWiFiInfo *********************/ -void ScanResultsWiFiInfo::setup() { - wifi::global_wifi_component->add_on_wifi_scan_state_callback( - [this](const wifi::wifi_scan_vector_t &results) { this->state_callback_(results); }); -} +void ScanResultsWiFiInfo::setup() { wifi::global_wifi_component->add_scan_results_listener(this); } void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "Scan Results", this); } -void ScanResultsWiFiInfo::state_callback_(const wifi::wifi_scan_vector_t &results) { +void ScanResultsWiFiInfo::on_wifi_scan_results(const wifi::wifi_scan_vector_t &results) { std::string scan_results; for (const auto &scan : results) { if (scan.get_is_hidden()) @@ -85,33 +74,30 @@ void ScanResultsWiFiInfo::state_callback_(const wifi::wifi_scan_vector_tadd_on_wifi_connect_state_callback( - [this](const std::string &ssid, const wifi::bssid_t &bssid) { this->state_callback_(ssid); }); -} +void SSIDWiFiInfo::setup() { wifi::global_wifi_component->add_connect_state_listener(this); } void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); } -void SSIDWiFiInfo::state_callback_(const std::string &ssid) { this->publish_state(ssid); } +void SSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) { + this->publish_state(ssid); +} /**************** * BSSIDWiFiInfo ***************/ -void BSSIDWiFiInfo::setup() { - wifi::global_wifi_component->add_on_wifi_connect_state_callback( - [this](const std::string &ssid, const wifi::bssid_t &bssid) { this->state_callback_(bssid); }); -} +void BSSIDWiFiInfo::setup() { wifi::global_wifi_component->add_connect_state_listener(this); } void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "BSSID", this); } -void BSSIDWiFiInfo::state_callback_(const wifi::bssid_t &bssid) { +void BSSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) { char buf[18] = "unknown"; if (mac_address_is_valid(bssid.data())) { format_mac_addr_upper(bssid.data(), buf); } this->publish_state(buf); } + /********************* * MacAddressWifiInfo ********************/ diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index 12666b4059..74d951f922 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -9,55 +9,61 @@ namespace esphome::wifi_info { -class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor { +class IPAddressWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiIPStateListener { public: void setup() override; void dump_config() override; void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; } + // WiFiIPStateListener interface + void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, + const network::IPAddress &dns2) override; + protected: - void state_callback_(const network::IPAddresses &ips); std::array ip_sensors_; }; -class DNSAddressWifiInfo : public Component, public text_sensor::TextSensor { +class DNSAddressWifiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiIPStateListener { public: void setup() override; void dump_config() override; - protected: - void state_callback_(const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip); + // WiFiIPStateListener interface + void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, + const network::IPAddress &dns2) override; }; -class ScanResultsWiFiInfo : public Component, public text_sensor::TextSensor { +class ScanResultsWiFiInfo final : public Component, + public text_sensor::TextSensor, + public wifi::WiFiScanResultsListener { public: void setup() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } void dump_config() override; - protected: - void state_callback_(const wifi::wifi_scan_vector_t &results); + // WiFiScanResultsListener interface + void on_wifi_scan_results(const wifi::wifi_scan_vector_t &results) override; }; -class SSIDWiFiInfo : public Component, public text_sensor::TextSensor { +class SSIDWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiConnectStateListener { public: void setup() override; void dump_config() override; - protected: - void state_callback_(const std::string &ssid); + // WiFiConnectStateListener interface + void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override; }; -class BSSIDWiFiInfo : public Component, public text_sensor::TextSensor { +class BSSIDWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiConnectStateListener { public: void setup() override; void dump_config() override; - protected: - void state_callback_(const wifi::bssid_t &bssid); + // WiFiConnectStateListener interface + void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override; }; -class MacAddressWifiInfo : public Component, public text_sensor::TextSensor { +class MacAddressWifiInfo final : public Component, public text_sensor::TextSensor { public: void setup() override { char mac_s[18]; diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 1373ea6366..f4026aad96 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -210,7 +210,7 @@ #define USE_WEBSERVER_SORTING #define USE_WIFI_11KV_SUPPORT #define USE_WIFI_FAST_CONNECT -#define USE_WIFI_CALLBACKS +#define USE_WIFI_LISTENERS #define USE_WIFI_RUNTIME_POWER_SAVE #define USB_HOST_MAX_REQUESTS 16 From fb82362e9cbd97e09b5eb1cee5e4d5840e038ff0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Nov 2025 12:13:29 -0600 Subject: [PATCH 0421/1145] [api] Eliminate rx_buf heap churn and release buffers after initial sync (#12133) --- esphome/components/api/api_connection.cpp | 6 ++++-- esphome/components/api/api_connection.h | 15 +++++++++---- esphome/components/api/api_frame_helper.h | 21 ++++++++++++++++--- .../components/api/api_frame_helper_noise.cpp | 3 +-- .../api/api_frame_helper_plaintext.cpp | 3 +-- esphome/components/api/api_pb2_service.cpp | 4 ++-- esphome/components/api/api_pb2_service.h | 4 ++-- esphome/components/api/proto.h | 2 +- script/api_protobuf/api_protobuf.py | 8 +++---- 9 files changed, 44 insertions(+), 22 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 12cbbb991d..9ad45dc6b7 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -169,8 +169,7 @@ void APIConnection::loop() { } else { this->last_traffic_ = now; // read a packet - this->read_message(buffer.data_len, buffer.type, - buffer.data_len > 0 ? &buffer.container[buffer.data_offset] : nullptr); + this->read_message(buffer.data_len, buffer.type, buffer.data); if (this->flags_.remove) return; } @@ -195,6 +194,9 @@ void APIConnection::loop() { } // Now that everything is sent, enable immediate sending for future state changes this->flags_.should_try_send_immediately = true; + // Release excess memory from buffers that grew during initial sync + this->deferred_batch_.release_buffer(); + this->helper_->release_buffers(); } } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index af3a19909f..05af0ccde7 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -554,10 +554,8 @@ class APIConnection final : public APIServerConnection { std::vector items; uint32_t batch_start_time{0}; - DeferredBatch() { - // Pre-allocate capacity for typical batch sizes to avoid reallocation - items.reserve(8); - } + // No pre-allocation - log connections never use batching, and for + // connections that do, buffers are released after initial sync anyway // Add item to the batch void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size); @@ -576,6 +574,15 @@ class APIConnection final : public APIServerConnection { bool empty() const { return items.empty(); } size_t size() const { return items.size(); } const BatchItem &operator[](size_t index) const { return items[index]; } + // Release excess capacity - only releases if items already empty + void release_buffer() { + // Safe to call: batch is processed before release_buffer is called, + // and if any items remain (partial processing), we must not clear them. + // Use swap trick since shrink_to_fit() is non-binding and may be ignored. + if (items.empty()) { + std::vector().swap(items); + } + } }; // DeferredBatch here (16 bytes, 4-byte aligned) diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index d931a6e3a9..b582bcea9a 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -35,10 +35,9 @@ struct ClientInfo; class ProtoWriteBuffer; struct ReadPacketBuffer { - std::vector container; - uint16_t type; - uint16_t data_offset; + const uint8_t *data; // Points directly into frame helper's rx_buf_ (valid until next read_packet call) uint16_t data_len; + uint16_t type; }; // Packed packet info structure to minimize memory usage @@ -119,6 +118,22 @@ class APIFrameHelper { uint8_t frame_footer_size() const { return frame_footer_size_; } // Check if socket has data ready to read bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); } + // Release excess memory from internal buffers after initial sync + void release_buffers() { + // rx_buf_: Safe to clear only if no partial read in progress. + // rx_buf_len_ tracks bytes read so far; if non-zero, we're mid-frame + // and clearing would lose partially received data. + if (this->rx_buf_len_ == 0) { + // Use swap trick since shrink_to_fit() is non-binding and may be ignored + std::vector().swap(this->rx_buf_); + } + // reusable_iovs_: Safe to release unconditionally. + // Only used within write_protobuf_packets() calls - cleared at start, + // populated with pointers, used for writev(), then function returns. + // The iovecs contain stale pointers after the call (data was either sent + // or copied to tx_buf_), and are cleared on next write_protobuf_packets(). + std::vector().swap(this->reusable_iovs_); + } protected: // Buffer containing data to be sent diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index f1028fa299..ae69f0b673 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -407,8 +407,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { return APIError::BAD_DATA_PACKET; } - buffer->container = std::move(this->rx_buf_); - buffer->data_offset = 4; + buffer->data = msg_data + 4; // Skip 4-byte header (type + length) buffer->data_len = data_len; buffer->type = type; return APIError::OK; diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index dcbd35aa32..b5d90b2429 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -210,8 +210,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { return aerr; } - buffer->container = std::move(this->rx_buf_); - buffer->data_offset = 0; + buffer->data = this->rx_buf_.data(); buffer->data_len = this->rx_header_parsed_len_; buffer->type = this->rx_header_parsed_type_; return APIError::OK; diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 3d28a137c8..45f6ecd30e 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -13,7 +13,7 @@ void APIServerConnectionBase::log_send_message_(const char *name, const std::str } #endif -void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { +void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) { switch (msg_type) { case HelloRequest::MESSAGE_TYPE: { HelloRequest msg; @@ -827,7 +827,7 @@ void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { th void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); } #endif -void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { +void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) { // Check authentication/connection requirements for messages switch (msg_type) { case HelloRequest::MESSAGE_TYPE: // No setup required diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 827b89e23c..6d94046a23 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -218,7 +218,7 @@ class APIServerConnectionBase : public ProtoService { virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){}; #endif protected: - void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; + void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override; }; class APIServerConnection : public APIServerConnectionBase { @@ -480,7 +480,7 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_ZWAVE_PROXY void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override; #endif - void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; + void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override; }; } // namespace esphome::api diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index e7585924a5..83b6922be1 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -846,7 +846,7 @@ class ProtoService { */ virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0; virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0; - virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0; + virtual void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) = 0; // Optimized method that pre-allocates buffer based on message size bool send_message_(const ProtoMessage &msg, uint8_t message_type) { diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index b07a249c8d..3412fac5db 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -2769,8 +2769,8 @@ static const char *const TAG = "api.service"; cases = list(RECEIVE_CASES.items()) cases.sort() hpp += " protected:\n" - hpp += " void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n" - out = f"void {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n" + hpp += " void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;\n" + out = f"void {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {{\n" out += " switch (msg_type) {\n" for i, (case, ifdef, message_name) in cases: if ifdef is not None: @@ -2878,9 +2878,9 @@ static const char *const TAG = "api.service"; result += "#endif\n" return result - hpp_protected += " void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n" + hpp_protected += " void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;\n" - cpp += f"\nvoid {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n" + cpp += f"\nvoid {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {{\n" cpp += " // Check authentication/connection requirements for messages\n" cpp += " switch (msg_type) {\n" From e15f3a08aef0255b111db331c726128e4b68db8d Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Fri, 28 Nov 2025 19:15:55 +0100 Subject: [PATCH 0422/1145] [tests] Remote packages with substitutions (#12145) --- .../06-remote_packages.approved.yaml | 25 +++++++++++++ .../06-remote_packages.input.yaml | 37 +++++++++++++++++++ .../substitutions/remote_package_proxy.yaml | 6 +++ .../fixtures/substitutions/remotes/README.md | 3 ++ .../remotes/repo1/main/file1.yaml | 9 +++++ .../remotes/repo2/main/file2.yaml | 10 +++++ tests/unit_tests/test_substitutions.py | 33 ++++++++++++++++- 7 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/fixtures/substitutions/06-remote_packages.approved.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/06-remote_packages.input.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/remote_package_proxy.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/remotes/README.md create mode 100644 tests/unit_tests/fixtures/substitutions/remotes/repo1/main/file1.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/remotes/repo2/main/file2.yaml diff --git a/tests/unit_tests/fixtures/substitutions/06-remote_packages.approved.yaml b/tests/unit_tests/fixtures/substitutions/06-remote_packages.approved.yaml new file mode 100644 index 0000000000..4b5315013c --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/06-remote_packages.approved.yaml @@ -0,0 +1,25 @@ +substitutions: + x: 10 + y: 20 + z: 30 +values_from_repo1_main: + - package_name: package1 + x: 3 + y: 4 + z: 5 + volume: 60 + - package_name: package2 + x: 6 + y: 7 + z: 8 + volume: 336 + - package_name: default + x: 10 + y: 20 + z: 5 + volume: 1000 + - package_name: package4_from_repo2 + x: 9 + y: 10 + z: 11 + volume: 990 diff --git a/tests/unit_tests/fixtures/substitutions/06-remote_packages.input.yaml b/tests/unit_tests/fixtures/substitutions/06-remote_packages.input.yaml new file mode 100644 index 0000000000..a8128a7a07 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/06-remote_packages.input.yaml @@ -0,0 +1,37 @@ +substitutions: + x: 10 + y: 20 + z: 30 +packages: + package1: + url: https://github.com/esphome/repo1 + files: + - path: file1.yaml + vars: + package_name: package1 + x: 3 + y: 4 + ref: main + package2: !include # a package that just includes the given remote package + file: remote_package_proxy.yaml + vars: + url: https://github.com/esphome/repo1 + ref: main + files: + - path: file1.yaml + vars: + package_name: package2 + x: 6 + y: 7 + z: 8 + package3: github://esphome/repo1/file1.yaml@main # a package that uses the shorthand syntax + package4: # include repo2, which itself includes repo1 + url: https://github.com/esphome/repo2 + files: + - path: file2.yaml + vars: + package_name: package4 + a: 9 + b: 10 + c: 11 + ref: main diff --git a/tests/unit_tests/fixtures/substitutions/remote_package_proxy.yaml b/tests/unit_tests/fixtures/substitutions/remote_package_proxy.yaml new file mode 100644 index 0000000000..05da30acb4 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/remote_package_proxy.yaml @@ -0,0 +1,6 @@ +# acts as a proxy to be able to include a remote package +# in which the url/ref/files come from a substitution +packages: + - url: ${url} + ref: ${ref} + files: ${files} diff --git a/tests/unit_tests/fixtures/substitutions/remotes/README.md b/tests/unit_tests/fixtures/substitutions/remotes/README.md new file mode 100644 index 0000000000..09d9f38699 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/remotes/README.md @@ -0,0 +1,3 @@ +This folder contains fake repos for remote packages testing +These are used by `test_substitutions.py`. +To add repos, create a folder and add its path to the `REMOTES` constant in `test_substitutions.py`. diff --git a/tests/unit_tests/fixtures/substitutions/remotes/repo1/main/file1.yaml b/tests/unit_tests/fixtures/substitutions/remotes/repo1/main/file1.yaml new file mode 100644 index 0000000000..3830b1650f --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/remotes/repo1/main/file1.yaml @@ -0,0 +1,9 @@ +defaults: + z: 5 + package_name: default +values_from_repo1_main: + - package_name: ${package_name} + x: ${x} + y: ${y} + z: ${z} + volume: ${x*y*z} diff --git a/tests/unit_tests/fixtures/substitutions/remotes/repo2/main/file2.yaml b/tests/unit_tests/fixtures/substitutions/remotes/repo2/main/file2.yaml new file mode 100644 index 0000000000..7f62ab8926 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/remotes/repo2/main/file2.yaml @@ -0,0 +1,10 @@ +packages: + - url: https://github.com/esphome/repo1 + ref: main + files: + - path: file1.yaml + vars: + package_name: ${package_name}_from_repo2 + x: ${a} + y: ${b} + z: ${c} diff --git a/tests/unit_tests/test_substitutions.py b/tests/unit_tests/test_substitutions.py index 7d50b44506..c5e6618ea6 100644 --- a/tests/unit_tests/test_substitutions.py +++ b/tests/unit_tests/test_substitutions.py @@ -2,6 +2,7 @@ import glob import logging from pathlib import Path from typing import Any +from unittest.mock import patch from esphome import config as config_module, yaml_util from esphome.components import substitutions @@ -84,11 +85,41 @@ def verify_database(value: Any, path: str = "") -> str | None: return None -def test_substitutions_fixtures(fixture_path): +# Mapping of (url, ref) to local test repository path under fixtures/substitutions +REMOTES = { + ("https://github.com/esphome/repo1", "main"): "remotes/repo1/main", + ("https://github.com/esphome/repo2", "main"): "remotes/repo2/main", +} + + +@patch("esphome.git.clone_or_update") +def test_substitutions_fixtures(mock_clone_or_update, fixture_path): base_dir = fixture_path / "substitutions" sources = sorted(glob.glob(str(base_dir / "*.input.yaml"))) assert sources, f"No input YAML files found in {base_dir}" + def fake_clone_or_update( + *, + url: str, + ref: str | None = None, + refresh=None, + domain: str, + username: str | None = None, + password: str | None = None, + submodules: list[str] | None = None, + _recover_broken: bool = True, + ) -> tuple[Path, None]: + path = REMOTES.get((url, ref)) + if path is None: + path = REMOTES.get((url.rstrip(".git"), ref)) + if path is None: + raise RuntimeError( + f"Cannot find test repository for {url} @ {ref}. Check the REMOTES mapping in test_substitutions.py" + ) + return base_dir / path, None + + mock_clone_or_update.side_effect = fake_clone_or_update + failures = [] for source_path in sources: source_path = Path(source_path) From d6ca01775e5c19a48c58f7147c3dfc8e6cc00489 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Fri, 28 Nov 2025 19:24:09 +0100 Subject: [PATCH 0423/1145] [packages] Restore remote shorthand vars and !remove in early package contents validation (#12158) Co-authored-by: J. Nick Koston --- esphome/components/packages/__init__.py | 12 +++++++++--- .../substitutions/06-remote_packages.approved.yaml | 5 +++++ .../substitutions/06-remote_packages.input.yaml | 6 ++++++ .../substitutions/remote_package_shorthand.yaml | 4 ++++ 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 tests/unit_tests/fixtures/substitutions/remote_package_shorthand.yaml diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 41cde0391b..67fd2770e9 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -2,7 +2,8 @@ import logging from pathlib import Path from esphome import git, yaml_util -from esphome.config_helpers import merge_config +from esphome.components.substitutions.jinja import has_jinja +from esphome.config_helpers import Remove, merge_config import esphome.config_validation as cv from esphome.const import ( CONF_ESPHOME, @@ -39,10 +40,15 @@ def valid_package_contents(package_config: dict): for k, v in package_config.items(): if not isinstance(k, str): raise cv.Invalid("Package content keys must be strings") - if isinstance(v, (dict, list)): - continue # e.g. script: [] or logger: {level: debug} + if isinstance(v, (dict, list, Remove)): + continue # e.g. script: [], psram: !remove, logger: {level: debug} if v is None: continue # e.g. web_server: + if isinstance(v, str) and has_jinja(v): + # e.g: remote package shorthand: + # package_name: github://esphome/repo/file.yaml@${ branch } + continue + raise cv.Invalid("Invalid component content in package definition") return package_config diff --git a/tests/unit_tests/fixtures/substitutions/06-remote_packages.approved.yaml b/tests/unit_tests/fixtures/substitutions/06-remote_packages.approved.yaml index 4b5315013c..0fffbfb7cb 100644 --- a/tests/unit_tests/fixtures/substitutions/06-remote_packages.approved.yaml +++ b/tests/unit_tests/fixtures/substitutions/06-remote_packages.approved.yaml @@ -23,3 +23,8 @@ values_from_repo1_main: y: 10 z: 11 volume: 990 + - package_name: default + x: 10 + y: 20 + z: 5 + volume: 1000 diff --git a/tests/unit_tests/fixtures/substitutions/06-remote_packages.input.yaml b/tests/unit_tests/fixtures/substitutions/06-remote_packages.input.yaml index a8128a7a07..772860bf19 100644 --- a/tests/unit_tests/fixtures/substitutions/06-remote_packages.input.yaml +++ b/tests/unit_tests/fixtures/substitutions/06-remote_packages.input.yaml @@ -35,3 +35,9 @@ packages: b: 10 c: 11 ref: main + package5: !include + file: remote_package_shorthand.yaml + vars: + repo: repo1 + file: file1.yaml + ref: main diff --git a/tests/unit_tests/fixtures/substitutions/remote_package_shorthand.yaml b/tests/unit_tests/fixtures/substitutions/remote_package_shorthand.yaml new file mode 100644 index 0000000000..f49e85e038 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/remote_package_shorthand.yaml @@ -0,0 +1,4 @@ +# acts as a proxy to be able to include a remote package +# in which the shorthand comes from a substitution +packages: + - github://esphome/${repo}/${file}@${ref} From 2e5529664006c585692ba47310229e2fe3f3e52c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Nov 2025 14:43:11 -0600 Subject: [PATCH 0424/1145] [sensor] Replace timeout filter scheduler with loop-based implementation (#11922) --- esphome/components/sensor/__init__.py | 11 +++++-- esphome/components/sensor/filter.cpp | 43 ++++++++++++++++++++------ esphome/components/sensor/filter.h | 44 ++++++++++++++++++++++----- 3 files changed, 78 insertions(+), 20 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index e8fec222a1..f83226d10f 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -270,7 +270,9 @@ ThrottleFilter = sensor_ns.class_("ThrottleFilter", Filter) ThrottleWithPriorityFilter = sensor_ns.class_( "ThrottleWithPriorityFilter", ValueListFilter ) -TimeoutFilter = sensor_ns.class_("TimeoutFilter", Filter, cg.Component) +TimeoutFilterBase = sensor_ns.class_("TimeoutFilterBase", Filter, cg.Component) +TimeoutFilterLast = sensor_ns.class_("TimeoutFilterLast", TimeoutFilterBase) +TimeoutFilterConfigured = sensor_ns.class_("TimeoutFilterConfigured", TimeoutFilterBase) DebounceFilter = sensor_ns.class_("DebounceFilter", Filter, cg.Component) HeartbeatFilter = sensor_ns.class_("HeartbeatFilter", Filter, cg.Component) DeltaFilter = sensor_ns.class_("DeltaFilter", Filter) @@ -681,11 +683,16 @@ TIMEOUT_SCHEMA = cv.maybe_simple_value( ) -@FILTER_REGISTRY.register("timeout", TimeoutFilter, TIMEOUT_SCHEMA) +@FILTER_REGISTRY.register("timeout", TimeoutFilterBase, TIMEOUT_SCHEMA) async def timeout_filter_to_code(config, filter_id): + filter_id = filter_id.copy() if config[CONF_VALUE] == "last": + # Use TimeoutFilterLast for "last" mode (smaller, more common - LD2450, LD2412, etc.) + filter_id.type = TimeoutFilterLast var = cg.new_Pvariable(filter_id, config[CONF_TIMEOUT]) else: + # Use TimeoutFilterConfigured for configured value mode + filter_id.type = TimeoutFilterConfigured template_ = await cg.templatable(config[CONF_VALUE], [], float) var = cg.new_Pvariable(filter_id, config[CONF_TIMEOUT], template_) await cg.register_component(var, {}) diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 65d8dea31c..c8c6540112 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -339,20 +339,43 @@ void OrFilter::initialize(Sensor *parent, Filter *next) { this->phi_.initialize(parent, nullptr); } -// TimeoutFilter -optional TimeoutFilter::new_value(float value) { - if (this->value_.has_value()) { - this->set_timeout("timeout", this->time_period_, [this]() { this->output(this->value_.value().value()); }); - } else { - this->set_timeout("timeout", this->time_period_, [this, value]() { this->output(value); }); +// TimeoutFilterBase - shared loop logic +void TimeoutFilterBase::loop() { + // Check if timeout period has elapsed + // Use cached loop start time to avoid repeated millis() calls + const uint32_t now = App.get_loop_component_start_time(); + if (now - this->timeout_start_time_ >= this->time_period_) { + // Timeout fired - get output value from derived class and output it + this->output(this->get_output_value()); + + // Disable loop until next value arrives + this->disable_loop(); } +} + +float TimeoutFilterBase::get_setup_priority() const { return setup_priority::HARDWARE; } + +// TimeoutFilterLast - "last" mode implementation +optional TimeoutFilterLast::new_value(float value) { + // Store the value to output when timeout fires + this->pending_value_ = value; + + // Record when timeout started and enable loop + this->timeout_start_time_ = millis(); + this->enable_loop(); + return value; } -TimeoutFilter::TimeoutFilter(uint32_t time_period) : time_period_(time_period) {} -TimeoutFilter::TimeoutFilter(uint32_t time_period, const TemplatableValue &new_value) - : time_period_(time_period), value_(new_value) {} -float TimeoutFilter::get_setup_priority() const { return setup_priority::HARDWARE; } +// TimeoutFilterConfigured - configured value mode implementation +optional TimeoutFilterConfigured::new_value(float value) { + // Record when timeout started and enable loop + // Note: we don't store the incoming value since we have a configured value + this->timeout_start_time_ = millis(); + this->enable_loop(); + + return value; +} // DebounceFilter optional DebounceFilter::new_value(float value) { diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 75e28a1efe..92a9184c18 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -380,18 +380,46 @@ class ThrottleWithPriorityFilter : public ValueListFilter { uint32_t min_time_between_inputs_; }; -class TimeoutFilter : public Filter, public Component { +// Base class for timeout filters - contains common loop logic +class TimeoutFilterBase : public Filter, public Component { public: - explicit TimeoutFilter(uint32_t time_period); - explicit TimeoutFilter(uint32_t time_period, const TemplatableValue &new_value); - - optional new_value(float value) override; - + void loop() override; float get_setup_priority() const override; protected: - uint32_t time_period_; - optional> value_; + explicit TimeoutFilterBase(uint32_t time_period) : time_period_(time_period) { this->disable_loop(); } + virtual float get_output_value() = 0; + + uint32_t time_period_; // 4 bytes (timeout duration in ms) + uint32_t timeout_start_time_{0}; // 4 bytes (when the timeout was started) + // Total base: 8 bytes +}; + +// Timeout filter for "last" mode - outputs the last received value after timeout +class TimeoutFilterLast : public TimeoutFilterBase { + public: + explicit TimeoutFilterLast(uint32_t time_period) : TimeoutFilterBase(time_period) {} + + optional new_value(float value) override; + + protected: + float get_output_value() override { return this->pending_value_; } + float pending_value_{0}; // 4 bytes (value to output when timeout fires) + // Total: 8 (base) + 4 = 12 bytes + vtable ptr + Component overhead +}; + +// Timeout filter with configured value - evaluates TemplatableValue after timeout +class TimeoutFilterConfigured : public TimeoutFilterBase { + public: + explicit TimeoutFilterConfigured(uint32_t time_period, const TemplatableValue &new_value) + : TimeoutFilterBase(time_period), value_(new_value) {} + + optional new_value(float value) override; + + protected: + float get_output_value() override { return this->value_.value(); } + TemplatableValue value_; // 16 bytes (configured output value, can be lambda) + // Total: 8 (base) + 16 = 24 bytes + vtable ptr + Component overhead }; class DebounceFilter : public Filter, public Component { From ca599b25c2a817a963eb5f76de2a0fcd8cc73738 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Nov 2025 15:33:28 -0600 Subject: [PATCH 0425/1145] [espnow] Initialize LwIP stack when running without WiFi component (#12169) --- esphome/components/espnow/espnow_component.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/components/espnow/espnow_component.cpp b/esphome/components/espnow/espnow_component.cpp index d2f136d1c7..bc05833709 100644 --- a/esphome/components/espnow/espnow_component.cpp +++ b/esphome/components/espnow/espnow_component.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -157,6 +158,12 @@ bool ESPNowComponent::is_wifi_enabled() { } void ESPNowComponent::setup() { +#ifndef USE_WIFI + // Initialize LwIP stack for wake_loop_threadsafe() socket support + // When WiFi component is present, it handles esp_netif_init() + ESP_ERROR_CHECK(esp_netif_init()); +#endif + if (this->enable_on_boot_) { this->enable_(); } else { From bc50be6053493aa453ab68602be89c2c4c55ecc6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Nov 2025 16:14:00 -0600 Subject: [PATCH 0426/1145] [logger] Conditionally compile log level change listener (#12168) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/logger/__init__.py | 23 +++++++++++++ esphome/components/logger/logger.cpp | 5 ++- esphome/components/logger/logger.h | 34 +++++++++++++++++-- esphome/components/logger/select/__init__.py | 9 ++++- .../logger/select/logger_level_select.cpp | 6 ++-- .../logger/select/logger_level_select.h | 9 +++-- esphome/core/defines.h | 1 + 7 files changed, 77 insertions(+), 10 deletions(-) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 39877030e9..d9ca44d3c9 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -406,6 +406,8 @@ async def to_code(config): conf, ) + CORE.add_job(final_step) + def validate_printf(value): # https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python @@ -506,3 +508,24 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( }, } ) + +# Keys for CORE.data storage +DOMAIN = "logger" +KEY_LEVEL_LISTENERS = "level_listeners" + + +def request_logger_level_listeners() -> None: + """Request that logger level listeners be compiled in. + + Components that need to be notified about log level changes should call this + function during their code generation. This enables the add_level_listener() + method and compiles in the listener vector. + """ + CORE.data.setdefault(DOMAIN, {})[KEY_LEVEL_LISTENERS] = True + + +@coroutine_with_priority(CoroPriority.FINAL) +async def final_step(): + """Final code generation step to configure optional logger features.""" + if CORE.data.get(DOMAIN, {}).get(KEY_LEVEL_LISTENERS, False): + cg.add_define("USE_LOGGER_LEVEL_LISTENERS") diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index f925e85e11..21e2b44808 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -288,7 +288,10 @@ void Logger::set_log_level(uint8_t level) { ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_STR_ARG(LOG_LEVELS[ESPHOME_LOG_LEVEL])); } this->current_level_ = level; - this->level_callback_.call(level); +#ifdef USE_LOGGER_LEVEL_LISTENERS + for (auto *listener : this->level_listeners_) + listener->on_log_level_change(level); +#endif } Logger *global_logger = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index a0024411d7..8abc1196e1 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -58,6 +58,30 @@ class LogListener { virtual void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) = 0; }; +#ifdef USE_LOGGER_LEVEL_LISTENERS +/** Interface for receiving log level changes without std::function overhead. + * + * Components can implement this interface instead of using lambdas with std::function + * to reduce flash usage from std::function type erasure machinery. + * + * Usage: + * class MyComponent : public Component, public LoggerLevelListener { + * public: + * void setup() override { + * if (logger::global_logger != nullptr) + * logger::global_logger->add_logger_level_listener(this); + * } + * void on_log_level_change(uint8_t level) override { + * // Handle log level change + * } + * }; + */ +class LoggerLevelListener { + public: + virtual void on_log_level_change(uint8_t level) = 0; +}; +#endif + #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS // Comparison function for const char* keys in log_levels_ map struct CStrCompare { @@ -193,8 +217,10 @@ class Logger : public Component { /// Register a log listener to receive log messages void add_log_listener(LogListener *listener) { this->log_listeners_.push_back(listener); } - // add a listener for log level changes - void add_listener(std::function &&callback) { this->level_callback_.add(std::move(callback)); } +#ifdef USE_LOGGER_LEVEL_LISTENERS + /// Register a listener for log level changes + void add_level_listener(LoggerLevelListener *listener) { this->level_listeners_.push_back(listener); } +#endif float get_setup_priority() const override; @@ -325,7 +351,9 @@ class Logger : public Component { std::map log_levels_{}; #endif std::vector log_listeners_; // Log message listeners (API, MQTT, syslog, etc.) - CallbackManager level_callback_{}; +#ifdef USE_LOGGER_LEVEL_LISTENERS + std::vector level_listeners_; // Log level change listeners +#endif #ifdef USE_ESPHOME_TASK_LOG_BUFFER std::unique_ptr log_buffer_; // Will be initialized with init_log_buffer #endif diff --git a/esphome/components/logger/select/__init__.py b/esphome/components/logger/select/__init__.py index 2e83599eb4..6ce663978e 100644 --- a/esphome/components/logger/select/__init__.py +++ b/esphome/components/logger/select/__init__.py @@ -5,7 +5,13 @@ from esphome.const import CONF_LEVEL, CONF_LOGGER, ENTITY_CATEGORY_CONFIG, ICON_ from esphome.core import CORE from esphome.cpp_helpers import register_component, register_parented -from .. import CONF_LOGGER_ID, LOG_LEVELS, Logger, logger_ns +from .. import ( + CONF_LOGGER_ID, + LOG_LEVELS, + Logger, + logger_ns, + request_logger_level_listeners, +) CODEOWNERS = ["@clydebarrow"] @@ -21,6 +27,7 @@ CONFIG_SCHEMA = select.select_schema( async def to_code(config): + request_logger_level_listeners() parent = await cg.get_variable(config[CONF_LOGGER_ID]) levels = list(LOG_LEVELS) index = levels.index(CORE.data[CONF_LOGGER][CONF_LEVEL]) diff --git a/esphome/components/logger/select/logger_level_select.cpp b/esphome/components/logger/select/logger_level_select.cpp index e2ec28a390..3091ca1851 100644 --- a/esphome/components/logger/select/logger_level_select.cpp +++ b/esphome/components/logger/select/logger_level_select.cpp @@ -2,7 +2,7 @@ namespace esphome::logger { -void LoggerLevelSelect::publish_state(int level) { +void LoggerLevelSelect::on_log_level_change(uint8_t level) { auto index = level_to_index(level); if (!this->has_index(index)) return; @@ -10,8 +10,8 @@ void LoggerLevelSelect::publish_state(int level) { } void LoggerLevelSelect::setup() { - this->parent_->add_listener([this](int level) { this->publish_state(level); }); - this->publish_state(this->parent_->get_log_level()); + this->parent_->add_level_listener(this); + this->on_log_level_change(this->parent_->get_log_level()); } void LoggerLevelSelect::control(size_t index) { this->parent_->set_log_level(index_to_level(index)); } diff --git a/esphome/components/logger/select/logger_level_select.h b/esphome/components/logger/select/logger_level_select.h index 950edd29ac..6482114943 100644 --- a/esphome/components/logger/select/logger_level_select.h +++ b/esphome/components/logger/select/logger_level_select.h @@ -5,12 +5,17 @@ #include "esphome/components/logger/logger.h" namespace esphome::logger { -class LoggerLevelSelect : public Component, public select::Select, public Parented { +class LoggerLevelSelect final : public Component, + public select::Select, + public Parented, + public LoggerLevelListener { public: - void publish_state(int level); void setup() override; void control(size_t index) override; + // LoggerLevelListener interface + void on_log_level_change(uint8_t level) override; + protected: // Convert log level to option index (skip CONFIG at level 4) static uint8_t level_to_index(uint8_t level) { return (level > ESPHOME_LOG_LEVEL_CONFIG) ? level - 1 : level; } diff --git a/esphome/core/defines.h b/esphome/core/defines.h index f4026aad96..538d4e3d6e 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -51,6 +51,7 @@ #define USE_LIGHT #define USE_LOCK #define USE_LOGGER +#define USE_LOGGER_LEVEL_LISTENERS #define USE_LOGGER_RUNTIME_TAG_LEVELS #define USE_LVGL #define USE_LVGL_ANIMIMG From 5fa4ff754c4306083ddd320edc68c706d568e862 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Nov 2025 21:57:01 -0600 Subject: [PATCH 0427/1145] [ble_client] Convert to C++17 namespace style (#12176) --- esphome/components/ble_client/automation.cpp | 6 ++---- esphome/components/ble_client/automation.h | 6 ++---- esphome/components/ble_client/ble_client.cpp | 6 ++---- esphome/components/ble_client/ble_client.h | 6 ++---- esphome/components/ble_client/output/ble_binary_output.cpp | 6 ++---- esphome/components/ble_client/output/ble_binary_output.h | 6 ++---- esphome/components/ble_client/sensor/automation.h | 6 ++---- esphome/components/ble_client/sensor/ble_rssi_sensor.cpp | 6 ++---- esphome/components/ble_client/sensor/ble_rssi_sensor.h | 6 ++---- esphome/components/ble_client/sensor/ble_sensor.cpp | 6 ++---- esphome/components/ble_client/sensor/ble_sensor.h | 6 ++---- esphome/components/ble_client/switch/ble_switch.cpp | 6 ++---- esphome/components/ble_client/switch/ble_switch.h | 6 ++---- esphome/components/ble_client/text_sensor/automation.h | 6 ++---- .../components/ble_client/text_sensor/ble_text_sensor.cpp | 6 ++---- esphome/components/ble_client/text_sensor/ble_text_sensor.h | 6 ++---- 16 files changed, 32 insertions(+), 64 deletions(-) diff --git a/esphome/components/ble_client/automation.cpp b/esphome/components/ble_client/automation.cpp index 9a0233eb70..cd2802f617 100644 --- a/esphome/components/ble_client/automation.cpp +++ b/esphome/components/ble_client/automation.cpp @@ -2,12 +2,10 @@ #include "automation.h" -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { const char *const Automation::TAG = "ble_client.automation"; -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 788eac4a57..ccda894509 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -9,8 +9,7 @@ #include "esphome/components/ble_client/ble_client.h" #include "esphome/core/log.h" -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { // placeholder class for static TAG . class Automation { @@ -391,7 +390,6 @@ template class BLEClientDisconnectAction : public Action, BLEClient *ble_client_; std::tuple var_{}; }; -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index b8968fe4ba..d41fb17961 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -7,8 +7,7 @@ #ifdef USE_ESP32 -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { static const char *const TAG = "ble_client"; @@ -82,7 +81,6 @@ bool BLEClient::all_nodes_established_() { return true; } -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index e04f4a8042..ca523251ef 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -15,8 +15,7 @@ #include #include -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { namespace espbt = esphome::esp32_ble_tracker; @@ -75,7 +74,6 @@ class BLEClient : public BLEClientBase { std::vector nodes_; }; -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp index 84558717f8..1d874a65e4 100644 --- a/esphome/components/ble_client/output/ble_binary_output.cpp +++ b/esphome/components/ble_client/output/ble_binary_output.cpp @@ -3,8 +3,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #ifdef USE_ESP32 -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { static const char *const TAG = "ble_binary_output"; @@ -75,6 +74,5 @@ void BLEBinaryOutput::write_state(bool state) { ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_string().c_str(), err); } -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/output/ble_binary_output.h b/esphome/components/ble_client/output/ble_binary_output.h index 5e8bd6da62..299de9b860 100644 --- a/esphome/components/ble_client/output/ble_binary_output.h +++ b/esphome/components/ble_client/output/ble_binary_output.h @@ -7,8 +7,7 @@ #ifdef USE_ESP32 #include -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { namespace espbt = esphome::esp32_ble_tracker; @@ -36,7 +35,6 @@ class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, publi esp_gatt_write_type_t write_type_{}; }; -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/sensor/automation.h b/esphome/components/ble_client/sensor/automation.h index 56ab7ba4c9..84430cb7d9 100644 --- a/esphome/components/ble_client/sensor/automation.h +++ b/esphome/components/ble_client/sensor/automation.h @@ -5,8 +5,7 @@ #ifdef USE_ESP32 -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { class BLESensorNotifyTrigger : public Trigger, public BLESensor { public: @@ -35,7 +34,6 @@ class BLESensorNotifyTrigger : public Trigger, public BLESensor { BLESensor *sensor_; }; -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp index 4edcbd3877..dc032a7a98 100644 --- a/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp @@ -6,8 +6,7 @@ #ifdef USE_ESP32 -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { static const char *const TAG = "ble_rssi_sensor"; @@ -78,6 +77,5 @@ void BLEClientRSSISensor::get_rssi_() { } } -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/sensor/ble_rssi_sensor.h b/esphome/components/ble_client/sensor/ble_rssi_sensor.h index 76cd8345a6..570a5b423c 100644 --- a/esphome/components/ble_client/sensor/ble_rssi_sensor.h +++ b/esphome/components/ble_client/sensor/ble_rssi_sensor.h @@ -8,8 +8,7 @@ #ifdef USE_ESP32 #include -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { namespace espbt = esphome::esp32_ble_tracker; @@ -29,6 +28,5 @@ class BLEClientRSSISensor : public sensor::Sensor, public PollingComponent, publ bool should_update_{false}; }; -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index 8e3e483003..38d90faff0 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -6,8 +6,7 @@ #ifdef USE_ESP32 -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { static const char *const TAG = "ble_sensor"; @@ -147,6 +146,5 @@ void BLESensor::update() { } } -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/sensor/ble_sensor.h b/esphome/components/ble_client/sensor/ble_sensor.h index c6335d5836..fe5b5ecd53 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.h +++ b/esphome/components/ble_client/sensor/ble_sensor.h @@ -10,8 +10,7 @@ #ifdef USE_ESP32 #include -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { namespace espbt = esphome::esp32_ble_tracker; @@ -48,6 +47,5 @@ class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClie espbt::ESPBTUUID descr_uuid_; }; -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/switch/ble_switch.cpp b/esphome/components/ble_client/switch/ble_switch.cpp index 9d92b1b2b5..5baca2adcf 100644 --- a/esphome/components/ble_client/switch/ble_switch.cpp +++ b/esphome/components/ble_client/switch/ble_switch.cpp @@ -4,8 +4,7 @@ #ifdef USE_ESP32 -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { static const char *const TAG = "ble_switch"; @@ -31,6 +30,5 @@ void BLEClientSwitch::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i void BLEClientSwitch::dump_config() { LOG_SWITCH("", "BLE Client Switch", this); } -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/switch/ble_switch.h b/esphome/components/ble_client/switch/ble_switch.h index 9809f904e7..9be6d06b1c 100644 --- a/esphome/components/ble_client/switch/ble_switch.h +++ b/esphome/components/ble_client/switch/ble_switch.h @@ -8,8 +8,7 @@ #ifdef USE_ESP32 #include -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { namespace espbt = esphome::esp32_ble_tracker; @@ -24,6 +23,5 @@ class BLEClientSwitch : public switch_::Switch, public Component, public BLEClie void write_state(bool state) override; }; -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/text_sensor/automation.h b/esphome/components/ble_client/text_sensor/automation.h index c504c35a58..f7b077926b 100644 --- a/esphome/components/ble_client/text_sensor/automation.h +++ b/esphome/components/ble_client/text_sensor/automation.h @@ -5,8 +5,7 @@ #ifdef USE_ESP32 -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { class BLETextSensorNotifyTrigger : public Trigger, public BLETextSensor { public: @@ -33,7 +32,6 @@ class BLETextSensorNotifyTrigger : public Trigger, public BLETextSe BLETextSensor *sensor_; }; -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index bb771aed99..415981a1ba 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -7,8 +7,7 @@ #ifdef USE_ESP32 -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { static const char *const TAG = "ble_text_sensor"; @@ -138,6 +137,5 @@ void BLETextSensor::update() { } } -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.h b/esphome/components/ble_client/text_sensor/ble_text_sensor.h index c75a4df952..3fbd64389c 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.h +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.h @@ -8,8 +8,7 @@ #ifdef USE_ESP32 #include -namespace esphome { -namespace ble_client { +namespace esphome::ble_client { namespace espbt = esphome::esp32_ble_tracker; @@ -40,6 +39,5 @@ class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, p espbt::ESPBTUUID descr_uuid_; }; -} // namespace ble_client -} // namespace esphome +} // namespace esphome::ble_client #endif From 2174795b273ef3ad26f8606e1e7d2d9a28c41646 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Nov 2025 21:57:36 -0600 Subject: [PATCH 0428/1145] [number] Reduce NumberCall size by 4 bytes on 32-bit platforms (#12178) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/number/number_call.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/number/number_call.h b/esphome/components/number/number_call.h index 0f6889dcb6..584c13f413 100644 --- a/esphome/components/number/number_call.h +++ b/esphome/components/number/number_call.h @@ -8,7 +8,7 @@ namespace esphome::number { class Number; -enum NumberOperation { +enum NumberOperation : uint8_t { NUMBER_OP_NONE, NUMBER_OP_SET, NUMBER_OP_INCREMENT, @@ -38,9 +38,9 @@ class NumberCall { float limit); Number *const parent_; - NumberOperation operation_{NUMBER_OP_NONE}; optional value_; - bool cycle_; + NumberOperation operation_{NUMBER_OP_NONE}; + bool cycle_{false}; }; } // namespace esphome::number From b71d8010d25e0252262aff70b14a49857e4a4025 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Nov 2025 21:59:31 -0600 Subject: [PATCH 0429/1145] [light] Store log_percent parameter strings in flash on ESP8266 (#12174) --- esphome/components/light/light_call.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index b3bdb16c73..f523b4451b 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -74,11 +74,11 @@ static const LogString *color_mode_to_human(ColorMode color_mode) { // Helper to log percentage values #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG -static void log_percent(const char *name, const char *param, float value) { - ESP_LOGD(TAG, " %s: %.0f%%", param, value * 100.0f); +static void log_percent(const LogString *param, float value) { + ESP_LOGD(TAG, " %s: %.0f%%", LOG_STR_ARG(param), value * 100.0f); } #else -#define log_percent(name, param, value) +#define log_percent(param, value) #endif void LightCall::perform() { @@ -104,11 +104,11 @@ void LightCall::perform() { } if (this->has_brightness()) { - log_percent(name, "Brightness", v.get_brightness()); + log_percent(LOG_STR("Brightness"), v.get_brightness()); } if (this->has_color_brightness()) { - log_percent(name, "Color brightness", v.get_color_brightness()); + log_percent(LOG_STR("Color brightness"), v.get_color_brightness()); } if (this->has_red() || this->has_green() || this->has_blue()) { ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f, @@ -116,7 +116,7 @@ void LightCall::perform() { } if (this->has_white()) { - log_percent(name, "White", v.get_white()); + log_percent(LOG_STR("White"), v.get_white()); } if (this->has_color_temperature()) { ESP_LOGD(TAG, " Color temperature: %.1f mireds", v.get_color_temperature()); From c40e8e7f5c722cb70e44bf5a3c61eb7a18a55bb9 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sat, 29 Nov 2025 19:38:29 +1100 Subject: [PATCH 0430/1145] [helpers] Add conversion from FixedVector to std::vector (#12179) --- esphome/core/helpers.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index a43c55e06b..83a12b9bf0 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -242,6 +242,9 @@ template class FixedVector { other.reset_(); } + // Allow conversion to std::vector + operator std::vector() const { return {data_, data_ + size_}; } + FixedVector &operator=(FixedVector &&other) noexcept { if (this != &other) { // Delete our current data From cf444fc3b82e68e33715434f39f6b4762f2cf809 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Sat, 29 Nov 2025 09:40:13 +0100 Subject: [PATCH 0431/1145] [mipi_spi] add guition JC4827W543 C/R (#12034) --- esphome/components/mipi_spi/models/jc.py | 105 +++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/esphome/components/mipi_spi/models/jc.py b/esphome/components/mipi_spi/models/jc.py index 5dbf049ded..5b936fd956 100644 --- a/esphome/components/mipi_spi/models/jc.py +++ b/esphome/components/mipi_spi/models/jc.py @@ -484,4 +484,109 @@ DriverChip( ), ) +DriverChip( + "JC4827W543", + height=272, + width=480, + offset_height=0, + offset_width=0, + cs_pin={CONF_NUMBER: 45, CONF_IGNORE_STRAPPING_WARNING: True}, + invert_colors=True, + color_order=MODE_RGB, + bus_mode=TYPE_QUAD, + data_rate="20MHz", + initsequence=( + (0xFF, 0xA5), + (0x41, 0x03), + (0x44, 0x15), + (0x45, 0x15), + (0x7D, 0x03), + (0xC1, 0xBB), + (0xC2, 0x05), + (0xC3, 0x10), + (0xC6, 0x3E), + (0xC7, 0x25), + (0xC8, 0x11), + (0x7A, 0x5F), + (0x6F, 0x44), + (0x78, 0x70), + (0xC9, 0x00), + (0x67, 0x21), + (0x51, 0x0A), + (0x52, 0x76), + (0x53, 0x0A), + (0x54, 0x76), + (0x46, 0x0A), + (0x47, 0x2A), + (0x48, 0x0A), + (0x49, 0x1A), + (0x56, 0x43), + (0x57, 0x42), + (0x58, 0x3C), + (0x59, 0x64), + (0x5A, 0x41), + (0x5B, 0x3C), + (0x5C, 0x02), + (0x5D, 0x3C), + (0x5E, 0x1F), + (0x60, 0x80), + (0x61, 0x3F), + (0x62, 0x21), + (0x63, 0x07), + (0x64, 0xE0), + (0x65, 0x02), + (0xCA, 0x20), + (0xCB, 0x52), + (0xCC, 0x10), + (0xCD, 0x42), + (0xD0, 0x20), + (0xD1, 0x52), + (0xD2, 0x10), + (0xD3, 0x42), + (0xD4, 0x0A), + (0xD5, 0x32), + (0x80, 0x00), + (0xA0, 0x00), + (0x81, 0x07), + (0xA1, 0x06), + (0x82, 0x02), + (0xA2, 0x01), + (0x86, 0x11), + (0xA6, 0x10), + (0x87, 0x27), + (0xA7, 0x27), + (0x83, 0x37), + (0xA3, 0x37), + (0x84, 0x35), + (0xA4, 0x35), + (0x85, 0x3F), + (0xA5, 0x3F), + (0x88, 0x0B), + (0xA8, 0x0B), + (0x89, 0x14), + (0xA9, 0x14), + (0x8A, 0x1A), + (0xAA, 0x1A), + (0x8B, 0x0A), + (0xAB, 0x0A), + (0x8C, 0x14), + (0xAC, 0x08), + (0x8D, 0x17), + (0xAD, 0x07), + (0x8E, 0x16), + (0xAE, 0x06), + (0x8F, 0x1B), + (0xAF, 0x07), + (0x90, 0x04), + (0xB0, 0x04), + (0x91, 0x0A), + (0xB1, 0x0A), + (0x92, 0x16), + (0xB2, 0x15), + (0xFF, 0x00), + (0x11, 0x00), + (0x29, 0x00), + ), +) + models = {} From 1f47797007419e10046513b7eb49d18fa200d1e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C4=8Cerm=C3=A1k?= Date: Sat, 29 Nov 2025 23:26:25 +0100 Subject: [PATCH 0432/1145] Add MEASUREMENT_ANGLE to SensorStateClass (#12085) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_pb2.h | 1 + esphome/components/api/api_pb2_dump.cpp | 2 ++ esphome/components/sensor/__init__.py | 1 + esphome/components/sensor/sensor.cpp | 2 ++ esphome/components/sensor/sensor.h | 1 + esphome/const.py | 3 +++ tests/components/sensor/common.yaml | 7 +++++++ 8 files changed, 18 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 74a8e8ff7f..5450c2536c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -589,6 +589,7 @@ enum SensorStateClass { STATE_CLASS_MEASUREMENT = 1; STATE_CLASS_TOTAL_INCREASING = 2; STATE_CLASS_TOTAL = 3; + STATE_CLASS_MEASUREMENT_ANGLE = 4; } // Deprecated in API version 1.5 diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 93ece74d85..74d3834bf5 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -51,6 +51,7 @@ enum SensorStateClass : uint32_t { STATE_CLASS_MEASUREMENT = 1, STATE_CLASS_TOTAL_INCREASING = 2, STATE_CLASS_TOTAL = 3, + STATE_CLASS_MEASUREMENT_ANGLE = 4, }; #endif enum LogLevel : uint32_t { diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index a985e052ac..bea7fc53c4 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -179,6 +179,8 @@ template<> const char *proto_enum_to_string(enums::Sens return "STATE_CLASS_TOTAL_INCREASING"; case enums::STATE_CLASS_TOTAL: return "STATE_CLASS_TOTAL"; + case enums::STATE_CLASS_MEASUREMENT_ANGLE: + return "STATE_CLASS_MEASUREMENT_ANGLE"; default: return "UNKNOWN"; } diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index f83226d10f..027d9a69b8 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -182,6 +182,7 @@ STATE_CLASSES = { "measurement": StateClasses.STATE_CLASS_MEASUREMENT, "total_increasing": StateClasses.STATE_CLASS_TOTAL_INCREASING, "total": StateClasses.STATE_CLASS_TOTAL, + "measurement_angle": StateClasses.STATE_CLASS_MEASUREMENT_ANGLE, } validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_") diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index df6bd644e8..49dc56edaa 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -44,6 +44,8 @@ const LogString *state_class_to_string(StateClass state_class) { return LOG_STR("total_increasing"); case STATE_CLASS_TOTAL: return LOG_STR("total"); + case STATE_CLASS_MEASUREMENT_ANGLE: + return LOG_STR("measurement_angle"); case STATE_CLASS_NONE: default: return LOG_STR(""); diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index a4210e5e6c..5d387a1ad7 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -31,6 +31,7 @@ enum StateClass : uint8_t { STATE_CLASS_MEASUREMENT = 1, STATE_CLASS_TOTAL_INCREASING = 2, STATE_CLASS_TOTAL = 3, + STATE_CLASS_MEASUREMENT_ANGLE = 4 }; const LogString *state_class_to_string(StateClass state_class); diff --git a/esphome/const.py b/esphome/const.py index 2b6b60d395..59bf0e8b8a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1346,6 +1346,9 @@ STATE_CLASS_NONE = "" # The state represents a measurement in present time STATE_CLASS_MEASUREMENT = "measurement" +# The state represents a measurement in present time for angles measured in degrees (°) +STATE_CLASS_MEASUREMENT_ANGLE = "measurement_angle" + # The state represents a total that only increases, a decrease is considered a reset. STATE_CLASS_TOTAL_INCREASING = "total_increasing" diff --git a/tests/components/sensor/common.yaml b/tests/components/sensor/common.yaml index 2180f66da8..1961c98685 100644 --- a/tests/components/sensor/common.yaml +++ b/tests/components/sensor/common.yaml @@ -236,3 +236,10 @@ sensor: - multiply: 2.0 - offset: 10.0 - lambda: return x * 3.0; + + # Testing measurement_angle state class + - platform: template + name: "Angle Sensor" + lambda: return 42.0; + update_interval: 1s + state_class: "measurement_angle" From 46567c47161a53ea48262c0ffde665ba3224885b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Nov 2025 22:55:27 +0000 Subject: [PATCH 0433/1145] Bump aioesphomeapi from 42.8.0 to 42.9.0 (#12189) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a5c919e95f..45ae3d5925 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==42.8.0 +aioesphomeapi==42.9.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.16 # dashboard_import From ec88bf0cb12be0159a1aa1a8519fd8a770fc1c6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Nov 2025 22:56:26 +0000 Subject: [PATCH 0434/1145] Bump ruff from 0.14.5 to 0.14.7 (#12190) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b86d00f2aa..412a678d02 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.5 + rev: v0.14.7 hooks: # Run the linter. - id: ruff diff --git a/requirements_test.txt b/requirements_test.txt index 7f6d3f8e26..3aec877126 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==4.0.3 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.14.5 # also change in .pre-commit-config.yaml when updating +ruff==0.14.7 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating pre-commit From d82a92b406238162bbd01756cb301776f901a6d9 Mon Sep 17 00:00:00 2001 From: Darsey Litzenberger Date: Sat, 29 Nov 2025 16:41:47 -0700 Subject: [PATCH 0435/1145] [ade7953_base] Add missing CODEOWNERS (#12181) --- CODEOWNERS | 1 + esphome/components/ade7953_base/__init__.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index c6332e3933..7861871323 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -21,6 +21,7 @@ esphome/components/adc128s102/* @DeerMaximum esphome/components/addressable_light/* @justfalter esphome/components/ade7880/* @kpfleming esphome/components/ade7953/* @angelnu +esphome/components/ade7953_base/* @angelnu esphome/components/ade7953_i2c/* @angelnu esphome/components/ade7953_spi/* @angelnu esphome/components/ads1118/* @solomondg1 diff --git a/esphome/components/ade7953_base/__init__.py b/esphome/components/ade7953_base/__init__.py index 42b6c8ba24..4fc35352f9 100644 --- a/esphome/components/ade7953_base/__init__.py +++ b/esphome/components/ade7953_base/__init__.py @@ -24,6 +24,8 @@ from esphome.const import ( UNIT_WATT, ) +CODEOWNERS = ["@angelnu"] + CONF_CURRENT_A = "current_a" CONF_CURRENT_B = "current_b" CONF_ACTIVE_POWER_A = "active_power_a" From 77f5f2326f793c488dca9a190590d8d7e57e9484 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 29 Nov 2025 18:36:12 -0600 Subject: [PATCH 0436/1145] [hlk_fm22x] Fix Action::play method signatures (#12192) --- esphome/components/hlk_fm22x/hlk_fm22x.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/hlk_fm22x/hlk_fm22x.h b/esphome/components/hlk_fm22x/hlk_fm22x.h index 5ecc715ea1..9c981d3c44 100644 --- a/esphome/components/hlk_fm22x/hlk_fm22x.h +++ b/esphome/components/hlk_fm22x/hlk_fm22x.h @@ -189,7 +189,7 @@ template class EnrollmentAction : public Action, public P TEMPLATABLE_VALUE(std::string, name) TEMPLATABLE_VALUE(uint8_t, direction) - void play(Ts... x) override { + void play(const Ts &...x) override { auto name = this->name_.value(x...); auto direction = (HlkFm22xFaceDirection) this->direction_.value(x...); this->parent_->enroll_face(name, direction); @@ -200,7 +200,7 @@ template class DeleteAction : public Action, public Paren public: TEMPLATABLE_VALUE(int16_t, face_id) - void play(Ts... x) override { + void play(const Ts &...x) override { auto face_id = this->face_id_.value(x...); this->parent_->delete_face(face_id); } @@ -208,17 +208,17 @@ template class DeleteAction : public Action, public Paren template class DeleteAllAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->delete_all_faces(); } + void play(const Ts &...x) override { this->parent_->delete_all_faces(); } }; template class ScanAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->scan_face(); } + void play(const Ts &...x) override { this->parent_->scan_face(); } }; template class ResetAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->reset(); } + void play(const Ts &...x) override { this->parent_->reset(); } }; } // namespace esphome::hlk_fm22x From 042a08887f848e348c47b92055cd5957acf19807 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 29 Nov 2025 18:54:49 -0600 Subject: [PATCH 0437/1145] [climate] Use C++17 nested namespace syntax (#12194) --- esphome/components/climate/automation.h | 6 ++---- esphome/components/climate/climate.cpp | 6 ++---- esphome/components/climate/climate.h | 6 ++---- esphome/components/climate/climate_mode.cpp | 6 ++---- esphome/components/climate/climate_mode.h | 6 ++---- esphome/components/climate/climate_traits.cpp | 6 ++---- esphome/components/climate/climate_traits.h | 6 ++---- 7 files changed, 14 insertions(+), 28 deletions(-) diff --git a/esphome/components/climate/automation.h b/esphome/components/climate/automation.h index 36cc8f4f21..fac56d9d9e 100644 --- a/esphome/components/climate/automation.h +++ b/esphome/components/climate/automation.h @@ -3,8 +3,7 @@ #include "esphome/core/automation.h" #include "climate.h" -namespace esphome { -namespace climate { +namespace esphome::climate { template class ControlAction : public Action { public: @@ -58,5 +57,4 @@ class StateTrigger : public Trigger { } }; -} // namespace climate -} // namespace esphome +} // namespace esphome::climate diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 82b75660ba..b0fba6aa62 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -3,8 +3,7 @@ #include "esphome/core/controller_registry.h" #include "esphome/core/macros.h" -namespace esphome { -namespace climate { +namespace esphome::climate { static const char *const TAG = "climate"; @@ -762,5 +761,4 @@ void Climate::dump_traits_(const char *tag) { } } -} // namespace climate -} // namespace esphome +} // namespace esphome::climate diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index b277877c3e..28a73d8c05 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -8,8 +8,7 @@ #include "climate_mode.h" #include "climate_traits.h" -namespace esphome { -namespace climate { +namespace esphome::climate { #define LOG_CLIMATE(prefix, type, obj) \ if ((obj) != nullptr) { \ @@ -345,5 +344,4 @@ class Climate : public EntityBase { const char *custom_preset_{nullptr}; }; -} // namespace climate -} // namespace esphome +} // namespace esphome::climate diff --git a/esphome/components/climate/climate_mode.cpp b/esphome/components/climate/climate_mode.cpp index 794f45ccd6..b153ee0424 100644 --- a/esphome/components/climate/climate_mode.cpp +++ b/esphome/components/climate/climate_mode.cpp @@ -1,7 +1,6 @@ #include "climate_mode.h" -namespace esphome { -namespace climate { +namespace esphome::climate { const LogString *climate_mode_to_string(ClimateMode mode) { switch (mode) { @@ -107,5 +106,4 @@ const LogString *climate_preset_to_string(ClimatePreset preset) { } } -} // namespace climate -} // namespace esphome +} // namespace esphome::climate diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index 44423d2f22..c961c44248 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -3,8 +3,7 @@ #include #include "esphome/core/log.h" -namespace esphome { -namespace climate { +namespace esphome::climate { /// Enum for all modes a climate device can be in. /// NOTE: If adding values, update ClimateModeMask in climate_traits.h to use the new last value @@ -132,5 +131,4 @@ const LogString *climate_swing_mode_to_string(ClimateSwingMode mode); /// Convert the given PresetMode to a human-readable string. const LogString *climate_preset_to_string(ClimatePreset preset); -} // namespace climate -} // namespace esphome +} // namespace esphome::climate diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index 342dffaad6..9bf2d9acd3 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -1,7 +1,6 @@ #include "climate_traits.h" -namespace esphome { -namespace climate { +namespace esphome::climate { int8_t ClimateTraits::get_target_temperature_accuracy_decimals() const { return step_to_accuracy_decimals(this->visual_target_temperature_step_); @@ -11,5 +10,4 @@ int8_t ClimateTraits::get_current_temperature_accuracy_decimals() const { return step_to_accuracy_decimals(this->visual_current_temperature_step_); } -} // namespace climate -} // namespace esphome +} // namespace esphome::climate diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index 0eecf9789f..d358293475 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -6,8 +6,7 @@ #include "esphome/core/finite_set_mask.h" #include "esphome/core/helpers.h" -namespace esphome { -namespace climate { +namespace esphome::climate { // Type aliases for climate enum bitmasks // These replace std::set to eliminate red-black tree overhead @@ -292,5 +291,4 @@ class ClimateTraits { std::vector supported_custom_presets_; }; -} // namespace climate -} // namespace esphome +} // namespace esphome::climate From 7317bf4a5d0adbda61fd7078b00e35348a276ed1 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 30 Nov 2025 08:04:19 -0500 Subject: [PATCH 0438/1145] [esp32_can] Add P4 support (#12201) --- esphome/components/esp32_can/canbus.py | 3 +++ esphome/components/esp32_can/esp32_can.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_can/canbus.py b/esphome/components/esp32_can/canbus.py index dfa98b2eff..acc3785f22 100644 --- a/esphome/components/esp32_can/canbus.py +++ b/esphome/components/esp32_can/canbus.py @@ -10,6 +10,7 @@ from esphome.components.esp32.const import ( VARIANT_ESP32C3, VARIANT_ESP32C6, VARIANT_ESP32H2, + VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, ) @@ -59,6 +60,7 @@ CAN_SPEEDS_ESP32_S3 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_C3 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_C6 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_H2 = {**CAN_SPEEDS_ESP32_S2} +CAN_SPEEDS_ESP32_P4 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS = { VARIANT_ESP32: CAN_SPEEDS_ESP32, @@ -67,6 +69,7 @@ CAN_SPEEDS = { VARIANT_ESP32C3: CAN_SPEEDS_ESP32_C3, VARIANT_ESP32C6: CAN_SPEEDS_ESP32_C6, VARIANT_ESP32H2: CAN_SPEEDS_ESP32_H2, + VARIANT_ESP32P4: CAN_SPEEDS_ESP32_P4, } diff --git a/esphome/components/esp32_can/esp32_can.cpp b/esphome/components/esp32_can/esp32_can.cpp index cdef7b1930..f9b63b8ebc 100644 --- a/esphome/components/esp32_can/esp32_can.cpp +++ b/esphome/components/esp32_can/esp32_can.cpp @@ -17,7 +17,7 @@ static const char *const TAG = "esp32_can"; static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config) { switch (bitrate) { #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3) || \ - defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) case canbus::CAN_1KBPS: *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1KBITS(); return true; From e95ceafc175bc60fde8edc672cd2bb686934ec7b Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 30 Nov 2025 08:04:33 -0500 Subject: [PATCH 0439/1145] [mopeka_pro_check] Fix negative temperatures (#12198) Co-authored-by: Claude --- esphome/components/mopeka_pro_check/mopeka_pro_check.cpp | 4 ++-- esphome/components/mopeka_pro_check/mopeka_pro_check.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp index 9527f09f59..42d61f81a3 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp @@ -116,7 +116,7 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) // Get temperature of sensor if (this->temperature_ != nullptr) { - uint8_t temp_in_c = this->parse_temperature_(manu_data.data); + int8_t temp_in_c = this->parse_temperature_(manu_data.data); this->temperature_->publish_state(temp_in_c); } @@ -145,7 +145,7 @@ uint32_t MopekaProCheck::parse_distance_(const std::vector &message) { (MOPEKA_LPG_COEF[0] + MOPEKA_LPG_COEF[1] * raw_t + MOPEKA_LPG_COEF[2] * raw_t * raw_t)); } -uint8_t MopekaProCheck::parse_temperature_(const std::vector &message) { return (message[2] & 0x7F) - 40; } +int8_t MopekaProCheck::parse_temperature_(const std::vector &message) { return (message[2] & 0x7F) - 40; } SensorReadQuality MopekaProCheck::parse_read_quality_(const std::vector &message) { // Since a 8 bit value is being shifted and truncated to 2 bits all possible values are defined as enumeration diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.h b/esphome/components/mopeka_pro_check/mopeka_pro_check.h index 4cbe8f2afe..41fb312152 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.h +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.h @@ -61,7 +61,7 @@ class MopekaProCheck : public Component, public esp32_ble_tracker::ESPBTDeviceLi uint8_t parse_battery_level_(const std::vector &message); uint32_t parse_distance_(const std::vector &message); - uint8_t parse_temperature_(const std::vector &message); + int8_t parse_temperature_(const std::vector &message); SensorReadQuality parse_read_quality_(const std::vector &message); }; From 47c767fa5e0a139e9b7634c6dd36102b9afa0331 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 30 Nov 2025 08:04:45 -0500 Subject: [PATCH 0440/1145] [openthread] Add C5 support (#12200) --- esphome/components/openthread/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/openthread/__init__.py b/esphome/components/openthread/__init__.py index e3ad3ed76c..5b1abe4fb5 100644 --- a/esphome/components/openthread/__init__.py +++ b/esphome/components/openthread/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components.esp32 import ( + VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32H2, add_idf_sdkconfig_option, @@ -152,7 +153,7 @@ CONFIG_SCHEMA = cv.All( ).extend(_CONNECTION_SCHEMA), cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV), cv.only_with_esp_idf, - only_on_variant(supported=[VARIANT_ESP32C6, VARIANT_ESP32H2]), + only_on_variant(supported=[VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32H2]), _validate, _require_vfs_select, ) From 8308bc29111c47e83117e405887b9e5e6f51da99 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Sun, 30 Nov 2025 14:06:06 +0100 Subject: [PATCH 0441/1145] [mdns] Bump mDNS component to 1.9.1 (#12207) --- esphome/components/mdns/__init__.py | 2 +- esphome/idf_component.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index 4776bef22f..1daac93a2e 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -165,7 +165,7 @@ async def to_code(config): cg.add_library("LEAmDNS", None) if CORE.using_esp_idf: - add_idf_component(name="espressif/mdns", ref="1.8.2") + add_idf_component(name="espressif/mdns", ref="1.9.1") cg.add_define("USE_MDNS") diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index fcb3a4f438..b27b6b8ed1 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -4,7 +4,7 @@ dependencies: espressif/esp32-camera: version: 2.1.1 espressif/mdns: - version: 1.8.2 + version: 1.9.1 espressif/esp_wifi_remote: version: 1.1.5 rules: From 82e12383302f28af748a499ee2e79664e43254c3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 30 Nov 2025 17:09:02 -0600 Subject: [PATCH 0442/1145] [lock] Refactor trigger classes to template and add integration tests (#12193) --- esphome/components/lock/automation.h | 18 ++---- .../fixtures/lock_automations.yaml | 17 ++++++ tests/integration/test_lock_automations.py | 58 +++++++++++++++++++ 3 files changed, 80 insertions(+), 13 deletions(-) create mode 100644 tests/integration/fixtures/lock_automations.yaml create mode 100644 tests/integration/test_lock_automations.py diff --git a/esphome/components/lock/automation.h b/esphome/components/lock/automation.h index cba2c3fdda..011c6cc6af 100644 --- a/esphome/components/lock/automation.h +++ b/esphome/components/lock/automation.h @@ -49,26 +49,18 @@ template class LockCondition : public Condition { bool state_; }; -class LockLockTrigger : public Trigger<> { +template class LockStateTrigger : public Trigger<> { public: - LockLockTrigger(Lock *a_lock) { + explicit LockStateTrigger(Lock *a_lock) { a_lock->add_on_state_callback([this, a_lock]() { - if (a_lock->state == LockState::LOCK_STATE_LOCKED) { + if (a_lock->state == State) { this->trigger(); } }); } }; -class LockUnlockTrigger : public Trigger<> { - public: - LockUnlockTrigger(Lock *a_lock) { - a_lock->add_on_state_callback([this, a_lock]() { - if (a_lock->state == LockState::LOCK_STATE_UNLOCKED) { - this->trigger(); - } - }); - } -}; +using LockLockTrigger = LockStateTrigger; +using LockUnlockTrigger = LockStateTrigger; } // namespace esphome::lock diff --git a/tests/integration/fixtures/lock_automations.yaml b/tests/integration/fixtures/lock_automations.yaml new file mode 100644 index 0000000000..fe11e656fa --- /dev/null +++ b/tests/integration/fixtures/lock_automations.yaml @@ -0,0 +1,17 @@ +esphome: + name: lock-automations-test + +host: +api: # Port will be automatically injected +logger: + level: DEBUG + +lock: + - platform: template + id: test_lock + name: "Test Lock" + optimistic: true + on_lock: + - logger.log: "TRIGGER: on_lock fired" + on_unlock: + - logger.log: "TRIGGER: on_unlock fired" diff --git a/tests/integration/test_lock_automations.py b/tests/integration/test_lock_automations.py new file mode 100644 index 0000000000..e200a2eacd --- /dev/null +++ b/tests/integration/test_lock_automations.py @@ -0,0 +1,58 @@ +"""Integration test for lock automation triggers. + +Tests that on_lock and on_unlock triggers work correctly. +""" + +import asyncio + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_lock_automations( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test lock on_lock and on_unlock triggers.""" + loop = asyncio.get_running_loop() + + # Futures for log line detection + on_lock_future: asyncio.Future[bool] = loop.create_future() + on_unlock_future: asyncio.Future[bool] = loop.create_future() + + def check_output(line: str) -> None: + """Check log output for trigger messages.""" + if "TRIGGER: on_lock fired" in line and not on_lock_future.done(): + on_lock_future.set_result(True) + elif "TRIGGER: on_unlock fired" in line and not on_unlock_future.done(): + on_unlock_future.set_result(True) + + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Import here to avoid import errors when aioesphomeapi is not installed + from aioesphomeapi import LockCommand + + # Get entities + entities = await client.list_entities_services() + lock = next(e for e in entities[0] if e.object_id == "test_lock") + + # Test 1: Lock - should trigger on_lock + client.lock_command(key=lock.key, command=LockCommand.LOCK) + + try: + await asyncio.wait_for(on_lock_future, timeout=5.0) + except TimeoutError: + pytest.fail("on_lock trigger did not fire") + + # Test 2: Unlock - should trigger on_unlock + client.lock_command(key=lock.key, command=LockCommand.UNLOCK) + + try: + await asyncio.wait_for(on_unlock_future, timeout=5.0) + except TimeoutError: + pytest.fail("on_unlock trigger did not fire") From 2ca118f3718f18d76f07d43c7c7437953b5bb819 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 30 Nov 2025 17:25:46 -0600 Subject: [PATCH 0443/1145] [web_server] Replace routing table with if-else chain to save 116 bytes RAM (#12139) --- esphome/components/web_server/web_server.cpp | 107 ++++++++++++------- 1 file changed, 68 insertions(+), 39 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index f5ca674161..bc48793ba2 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1686,6 +1686,7 @@ std::string WebServer::event_state_json_generator(WebServer *web_server, void *s auto *event = static_cast(source); return web_server->event_json(event, get_event_type(event), DETAIL_STATE); } +// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson std::string WebServer::event_all_json_generator(WebServer *web_server, void *source) { auto *event = static_cast(source); return web_server->event_json(event, get_event_type(event), DETAIL_ALL); @@ -1709,6 +1710,7 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty return builder.serialize(); } +// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) #endif #ifdef USE_UPDATE @@ -1945,83 +1947,110 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { // Parse URL for component routing UrlMatch match = match_url(url.c_str(), url.length(), false); - // Component routing using minimal code repetition - struct ComponentRoute { - const char *domain; - void (WebServer::*handler)(AsyncWebServerRequest *, const UrlMatch &); - }; - - static const ComponentRoute ROUTES[] = { + // Route to appropriate handler based on domain + // NOLINTNEXTLINE(readability-simplify-boolean-expr) + if (false) { // Start chain for else-if macro pattern + } #ifdef USE_SENSOR - {"sensor", &WebServer::handle_sensor_request}, + else if (match.domain_equals("sensor")) { + this->handle_sensor_request(request, match); + } #endif #ifdef USE_SWITCH - {"switch", &WebServer::handle_switch_request}, + else if (match.domain_equals("switch")) { + this->handle_switch_request(request, match); + } #endif #ifdef USE_BUTTON - {"button", &WebServer::handle_button_request}, + else if (match.domain_equals("button")) { + this->handle_button_request(request, match); + } #endif #ifdef USE_BINARY_SENSOR - {"binary_sensor", &WebServer::handle_binary_sensor_request}, + else if (match.domain_equals("binary_sensor")) { + this->handle_binary_sensor_request(request, match); + } #endif #ifdef USE_FAN - {"fan", &WebServer::handle_fan_request}, + else if (match.domain_equals("fan")) { + this->handle_fan_request(request, match); + } #endif #ifdef USE_LIGHT - {"light", &WebServer::handle_light_request}, + else if (match.domain_equals("light")) { + this->handle_light_request(request, match); + } #endif #ifdef USE_TEXT_SENSOR - {"text_sensor", &WebServer::handle_text_sensor_request}, + else if (match.domain_equals("text_sensor")) { + this->handle_text_sensor_request(request, match); + } #endif #ifdef USE_COVER - {"cover", &WebServer::handle_cover_request}, + else if (match.domain_equals("cover")) { + this->handle_cover_request(request, match); + } #endif #ifdef USE_NUMBER - {"number", &WebServer::handle_number_request}, + else if (match.domain_equals("number")) { + this->handle_number_request(request, match); + } #endif #ifdef USE_DATETIME_DATE - {"date", &WebServer::handle_date_request}, + else if (match.domain_equals("date")) { + this->handle_date_request(request, match); + } #endif #ifdef USE_DATETIME_TIME - {"time", &WebServer::handle_time_request}, + else if (match.domain_equals("time")) { + this->handle_time_request(request, match); + } #endif #ifdef USE_DATETIME_DATETIME - {"datetime", &WebServer::handle_datetime_request}, + else if (match.domain_equals("datetime")) { + this->handle_datetime_request(request, match); + } #endif #ifdef USE_TEXT - {"text", &WebServer::handle_text_request}, + else if (match.domain_equals("text")) { + this->handle_text_request(request, match); + } #endif #ifdef USE_SELECT - {"select", &WebServer::handle_select_request}, + else if (match.domain_equals("select")) { + this->handle_select_request(request, match); + } #endif #ifdef USE_CLIMATE - {"climate", &WebServer::handle_climate_request}, + else if (match.domain_equals("climate")) { + this->handle_climate_request(request, match); + } #endif #ifdef USE_LOCK - {"lock", &WebServer::handle_lock_request}, + else if (match.domain_equals("lock")) { + this->handle_lock_request(request, match); + } #endif #ifdef USE_VALVE - {"valve", &WebServer::handle_valve_request}, + else if (match.domain_equals("valve")) { + this->handle_valve_request(request, match); + } #endif #ifdef USE_ALARM_CONTROL_PANEL - {"alarm_control_panel", &WebServer::handle_alarm_control_panel_request}, + else if (match.domain_equals("alarm_control_panel")) { + this->handle_alarm_control_panel_request(request, match); + } #endif #ifdef USE_UPDATE - {"update", &WebServer::handle_update_request}, -#endif - }; - - // Check each route - for (const auto &route : ROUTES) { - if (match.domain_equals(route.domain)) { - (this->*route.handler)(request, match); - return; - } + else if (match.domain_equals("update")) { + this->handle_update_request(request, match); + } +#endif + else { + // No matching handler found - send 404 + ESP_LOGV(TAG, "Request for unknown URL: %s", url.c_str()); + request->send(404, "text/plain", "Not Found"); } - - // No matching handler found - send 404 - ESP_LOGV(TAG, "Request for unknown URL: %s", url.c_str()); - request->send(404, "text/plain", "Not Found"); } bool WebServer::isRequestHandlerTrivial() const { return false; } From bf4ef36c3a7875716ff6a4301e2e6eeb415ce62c Mon Sep 17 00:00:00 2001 From: Darsey Litzenberger Date: Sun, 30 Nov 2025 17:17:50 -0700 Subject: [PATCH 0444/1145] [ade7953] Apply voltage_gain setting to both channels (#12180) --- esphome/components/ade7953_base/ade7953_base.cpp | 13 ++++++++----- esphome/components/ade7953_base/ade7953_base.h | 10 ++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/esphome/components/ade7953_base/ade7953_base.cpp b/esphome/components/ade7953_base/ade7953_base.cpp index 5f5fdd27ee..821e4a3105 100644 --- a/esphome/components/ade7953_base/ade7953_base.cpp +++ b/esphome/components/ade7953_base/ade7953_base.cpp @@ -25,7 +25,8 @@ void ADE7953::setup() { this->ade_write_8(PGA_V_8, pga_v_); this->ade_write_8(PGA_IA_8, pga_ia_); this->ade_write_8(PGA_IB_8, pga_ib_); - this->ade_write_32(AVGAIN_32, vgain_); + this->ade_write_32(AVGAIN_32, avgain_); + this->ade_write_32(BVGAIN_32, bvgain_); this->ade_write_32(AIGAIN_32, aigain_); this->ade_write_32(BIGAIN_32, bigain_); this->ade_write_32(AWGAIN_32, awgain_); @@ -34,7 +35,8 @@ void ADE7953::setup() { this->ade_read_8(PGA_V_8, &pga_v_); this->ade_read_8(PGA_IA_8, &pga_ia_); this->ade_read_8(PGA_IB_8, &pga_ib_); - this->ade_read_32(AVGAIN_32, &vgain_); + this->ade_read_32(AVGAIN_32, &avgain_); + this->ade_read_32(BVGAIN_32, &bvgain_); this->ade_read_32(AIGAIN_32, &aigain_); this->ade_read_32(BIGAIN_32, &bigain_); this->ade_read_32(AWGAIN_32, &awgain_); @@ -63,13 +65,14 @@ void ADE7953::dump_config() { " PGA_V_8: 0x%X\n" " PGA_IA_8: 0x%X\n" " PGA_IB_8: 0x%X\n" - " VGAIN_32: 0x%08jX\n" + " AVGAIN_32: 0x%08jX\n" + " BVGAIN_32: 0x%08jX\n" " AIGAIN_32: 0x%08jX\n" " BIGAIN_32: 0x%08jX\n" " AWGAIN_32: 0x%08jX\n" " BWGAIN_32: 0x%08jX", - this->use_acc_energy_regs_, pga_v_, pga_ia_, pga_ib_, (uintmax_t) vgain_, (uintmax_t) aigain_, - (uintmax_t) bigain_, (uintmax_t) awgain_, (uintmax_t) bwgain_); + this->use_acc_energy_regs_, pga_v_, pga_ia_, pga_ib_, (uintmax_t) avgain_, (uintmax_t) bvgain_, + (uintmax_t) aigain_, (uintmax_t) bigain_, (uintmax_t) awgain_, (uintmax_t) bwgain_); } #define ADE_PUBLISH_(name, val, factor) \ diff --git a/esphome/components/ade7953_base/ade7953_base.h b/esphome/components/ade7953_base/ade7953_base.h index d711a5c6be..bcafddca4e 100644 --- a/esphome/components/ade7953_base/ade7953_base.h +++ b/esphome/components/ade7953_base/ade7953_base.h @@ -46,7 +46,12 @@ class ADE7953 : public PollingComponent, public sensor::Sensor { void set_pga_ib(uint8_t pga_ib) { pga_ib_ = pga_ib; } // Set input gains - void set_vgain(uint32_t vgain) { vgain_ = vgain; } + void set_vgain(uint32_t vgain) { + // Datasheet says: "to avoid discrepancies in other registers, + // if AVGAIN is set then BVGAIN should be set to the same value." + avgain_ = vgain; + bvgain_ = vgain; + } void set_aigain(uint32_t aigain) { aigain_ = aigain; } void set_bigain(uint32_t bigain) { bigain_ = bigain; } void set_awgain(uint32_t awgain) { awgain_ = awgain; } @@ -100,7 +105,8 @@ class ADE7953 : public PollingComponent, public sensor::Sensor { uint8_t pga_v_; uint8_t pga_ia_; uint8_t pga_ib_; - uint32_t vgain_; + uint32_t avgain_; + uint32_t bvgain_; uint32_t aigain_; uint32_t bigain_; uint32_t awgain_; From 4335fcdb72cddb820b75e6ba20dc41b01739cce2 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 30 Nov 2025 23:27:10 -0500 Subject: [PATCH 0445/1145] [psram] Add C5 support (#12215) Co-authored-by: Claude --- esphome/components/psram/__init__.py | 3 +++ tests/component_tests/psram/test_psram.py | 7 ++++--- tests/components/psram/test.esp32-c5-idf.yaml | 8 ++++++++ .../build_components_base.esp32-c5-idf.yaml | 17 +++++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 tests/components/psram/test.esp32-c5-idf.yaml create mode 100644 tests/test_build_components/build_components_base.esp32-c5-idf.yaml diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index c50c599855..4ee4e97696 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -11,6 +11,7 @@ from esphome.components.esp32 import ( get_esp32_variant, ) from esphome.components.esp32.const import ( + VARIANT_ESP32C5, VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, @@ -55,6 +56,7 @@ SPIRAM_MODES = { VARIANT_ESP32: (TYPE_QUAD,), VARIANT_ESP32S2: (TYPE_QUAD,), VARIANT_ESP32S3: (TYPE_QUAD, TYPE_OCTAL), + VARIANT_ESP32C5: (TYPE_QUAD,), VARIANT_ESP32P4: (TYPE_HEX,), } @@ -63,6 +65,7 @@ SPIRAM_SPEEDS = { VARIANT_ESP32: (40, 80, 120), VARIANT_ESP32S2: (40, 80, 120), VARIANT_ESP32S3: (40, 80, 120), + VARIANT_ESP32C5: (40, 80, 120), VARIANT_ESP32P4: (20, 100, 200), } diff --git a/tests/component_tests/psram/test_psram.py b/tests/component_tests/psram/test_psram.py index f8ad013689..86bc29cc84 100644 --- a/tests/component_tests/psram/test_psram.py +++ b/tests/component_tests/psram/test_psram.py @@ -23,22 +23,23 @@ from tests.component_tests.types import SetCoreConfigCallable UNSUPPORTED_PSRAM_VARIANTS = [ VARIANT_ESP32C2, VARIANT_ESP32C3, - VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32H2, ] SUPPORTED_PSRAM_VARIANTS = [ VARIANT_ESP32, + VARIANT_ESP32C5, + VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, - VARIANT_ESP32P4, ] SUPPORTED_PSRAM_MODES = { VARIANT_ESP32: ["quad"], + VARIANT_ESP32C5: ["quad"], + VARIANT_ESP32P4: ["hex"], VARIANT_ESP32S2: ["quad"], VARIANT_ESP32S3: ["quad", "octal"], - VARIANT_ESP32P4: ["hex"], } diff --git a/tests/components/psram/test.esp32-c5-idf.yaml b/tests/components/psram/test.esp32-c5-idf.yaml new file mode 100644 index 0000000000..fbd0132e2d --- /dev/null +++ b/tests/components/psram/test.esp32-c5-idf.yaml @@ -0,0 +1,8 @@ +esp32: + cpu_frequency: 240MHz + framework: + type: esp-idf + +psram: + speed: 120MHz + ignore_not_found: false diff --git a/tests/test_build_components/build_components_base.esp32-c5-idf.yaml b/tests/test_build_components/build_components_base.esp32-c5-idf.yaml new file mode 100644 index 0000000000..6468297e9a --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-c5-idf.yaml @@ -0,0 +1,17 @@ +esphome: + name: componenttestesp32c5idf + friendly_name: $component_name + +esp32: + board: esp32-c5-devkitc-1 + framework: + type: esp-idf + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file From 161a18b3269032e95c883dc67bf0928a77b7581e Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 1 Dec 2025 00:33:23 -0600 Subject: [PATCH 0446/1145] [uart] Add `wake_loop_on_rx` flag for low latency processing (#12172) Co-authored-by: J. Nick Koston --- esphome/components/uart/__init__.py | 39 ++++++++++- .../uart/uart_component_esp_idf.cpp | 70 ++++++++++++++++++- .../components/uart/uart_component_esp_idf.h | 8 +++ esphome/core/defines.h | 1 + 4 files changed, 116 insertions(+), 2 deletions(-) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 7b0d9726b8..a1c78dd45c 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass from logging import getLogger import math import re @@ -32,13 +33,15 @@ from esphome.const import ( PLATFORM_HOST, PlatformFramework, ) -from esphome.core import CORE, ID +from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority import esphome.final_validate as fv from esphome.yaml_util import make_data_base _LOGGER = getLogger(__name__) CODEOWNERS = ["@esphome/core"] +DOMAIN = "uart" + uart_ns = cg.esphome_ns.namespace("uart") UARTComponent = uart_ns.class_("UARTComponent") @@ -52,6 +55,7 @@ LibreTinyUARTComponent = uart_ns.class_( ) HostUartComponent = uart_ns.class_("HostUartComponent", UARTComponent, cg.Component) + NATIVE_UART_CLASSES = ( str(IDFUARTComponent), str(ESP8266UartComponent), @@ -100,6 +104,30 @@ MULTI_CONF = True MULTI_CONF_NO_DEFAULT = True +@dataclass +class UARTData: + """State data for UART component configuration generation.""" + + wake_loop_on_rx: bool = False + + +def _get_data() -> UARTData: + """Get UART component data from CORE.data.""" + if DOMAIN not in CORE.data: + CORE.data[DOMAIN] = UARTData() + return CORE.data[DOMAIN] + + +def request_wake_loop_on_rx() -> None: + """Request that the UART wake the main loop when data is received. + + Components that need low-latency notification of incoming UART data + should call this function during their code generation. + This enables the RX event task which wakes the main loop when data arrives. + """ + _get_data().wake_loop_on_rx = True + + def validate_raw_data(value): if isinstance(value, str): return value.encode("utf-8") @@ -335,6 +363,8 @@ async def to_code(config): if CONF_DEBUG in config: await debug_to_code(config[CONF_DEBUG], var) + CORE.add_job(final_step) + # A schema to use for all UART devices, all UART integrations must extend this! UART_DEVICE_SCHEMA = cv.Schema( @@ -472,6 +502,13 @@ async def uart_write_to_code(config, action_id, template_arg, args): return var +@coroutine_with_priority(CoroPriority.FINAL) +async def final_step(): + """Final code generation step to configure optional UART features.""" + if _get_data().wake_loop_on_rx: + cg.add_define("USE_UART_WAKE_LOOP_ON_RX") + + FILTER_SOURCE_FILES = filter_source_files_from_platform( { "uart_component_esp_idf.cpp": { diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 61ca8c1c0c..c6efe862c4 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -112,6 +112,12 @@ void IDFUARTComponent::load_settings(bool dump_config) { esp_err_t err; if (uart_is_driver_installed(this->uart_num_)) { +#ifdef USE_UART_WAKE_LOOP_ON_RX + if (this->rx_event_task_handle_ != nullptr) { + vTaskDelete(this->rx_event_task_handle_); + this->rx_event_task_handle_ = nullptr; + } +#endif err = uart_driver_delete(this->uart_num_); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err)); @@ -204,6 +210,11 @@ void IDFUARTComponent::load_settings(bool dump_config) { return; } +#ifdef USE_UART_WAKE_LOOP_ON_RX + // Start the RX event task to enable low-latency data notifications + this->start_rx_event_task_(); +#endif // USE_UART_WAKE_LOOP_ON_RX + if (dump_config) { ESP_LOGCONFIG(TAG, "Reloaded UART %u", this->uart_num_); this->dump_config(); @@ -226,7 +237,11 @@ void IDFUARTComponent::dump_config() { " Baud Rate: %" PRIu32 " baud\n" " Data Bits: %u\n" " Parity: %s\n" - " Stop bits: %u", + " Stop bits: %u" +#ifdef USE_UART_WAKE_LOOP_ON_RX + "\n Wake on data RX: ENABLED" +#endif + , this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_); this->check_logger_conflict(); } @@ -337,6 +352,59 @@ void IDFUARTComponent::flush() { void IDFUARTComponent::check_logger_conflict() {} +#ifdef USE_UART_WAKE_LOOP_ON_RX +void IDFUARTComponent::start_rx_event_task_() { + // Create FreeRTOS task to monitor UART events + BaseType_t result = xTaskCreate(rx_event_task_func, // Task function + "uart_rx_evt", // Task name (max 16 chars) + 2240, // Stack size in bytes (~2.2KB); increase if needed for logging + this, // Task parameter (this pointer) + tskIDLE_PRIORITY + 1, // Priority (low, just above idle) + &this->rx_event_task_handle_ // Task handle + ); + + if (result != pdPASS) { + ESP_LOGE(TAG, "Failed to create RX event task"); + return; + } + + ESP_LOGV(TAG, "RX event task started"); +} + +void IDFUARTComponent::rx_event_task_func(void *param) { + auto *self = static_cast(param); + uart_event_t event; + + ESP_LOGV(TAG, "RX event task running"); + + // Run forever - task lifecycle matches component lifecycle + while (true) { + // Wait for UART events (blocks efficiently) + if (xQueueReceive(self->uart_event_queue_, &event, portMAX_DELAY) == pdTRUE) { + switch (event.type) { + case UART_DATA: + // Data available in UART RX buffer - wake the main loop + ESP_LOGVV(TAG, "Data event: %d bytes", event.size); + App.wake_loop_threadsafe(); + break; + + case UART_FIFO_OVF: + case UART_BUFFER_FULL: + ESP_LOGW(TAG, "FIFO overflow or ring buffer full - clearing"); + uart_flush_input(self->uart_num_); + App.wake_loop_threadsafe(); + break; + + default: + // Ignore other event types + ESP_LOGVV(TAG, "Event type: %d", event.type); + break; + } + } + } +} +#endif // USE_UART_WAKE_LOOP_ON_RX + } // namespace uart } // namespace esphome diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h index a2ba2aa968..e47a323979 100644 --- a/esphome/components/uart/uart_component_esp_idf.h +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -53,6 +53,14 @@ class IDFUARTComponent : public UARTComponent, public Component { bool has_peek_{false}; uint8_t peek_byte_; + +#ifdef USE_UART_WAKE_LOOP_ON_RX + // RX notification support + void start_rx_event_task_(); + static void rx_event_task_func(void *param); + + TaskHandle_t rx_event_task_handle_{nullptr}; +#endif // USE_UART_WAKE_LOOP_ON_RX }; } // namespace uart diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 538d4e3d6e..12dfdba5ce 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -107,6 +107,7 @@ #define USE_TIME #define USE_TOUCHSCREEN #define USE_UART_DEBUGGER +#define USE_UART_WAKE_LOOP_ON_RX #define USE_UPDATE #define USE_VALVE #define USE_ZWAVE_PROXY From dbc16ce46884dd54e5f62a8f37d15394eb6b8613 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 1 Dec 2025 02:48:47 -0600 Subject: [PATCH 0447/1145] [wifi_info] Fix compilation error when using only mac_address sensor, add tests (#12222) --- esphome/components/wifi_info/wifi_info_text_sensor.cpp | 4 ++++ esphome/components/wifi_info/wifi_info_text_sensor.h | 2 ++ tests/components/wifi_info/common-mac.yaml | 8 ++++++++ tests/components/wifi_info/common.yaml | 2 +- tests/components/wifi_info/test-mac.esp32-idf.yaml | 4 ++++ tests/components/wifi_info/test-mac.esp8266-ard.yaml | 4 ++++ tests/components/wifi_info/test-mac.rp2040-ard.yaml | 4 ++++ 7 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 tests/components/wifi_info/common-mac.yaml create mode 100644 tests/components/wifi_info/test-mac.esp32-idf.yaml create mode 100644 tests/components/wifi_info/test-mac.esp8266-ard.yaml create mode 100644 tests/components/wifi_info/test-mac.rp2040-ard.yaml diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index 92d3ea29f5..6c9d0c00e5 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -6,6 +6,8 @@ namespace esphome::wifi_info { static const char *const TAG = "wifi_info"; +#ifdef USE_WIFI_LISTENERS + static constexpr size_t MAX_STATE_LENGTH = 255; /******************** @@ -98,6 +100,8 @@ void BSSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::b this->publish_state(buf); } +#endif + /********************* * MacAddressWifiInfo ********************/ diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index 74d951f922..f1f85c114f 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -9,6 +9,7 @@ namespace esphome::wifi_info { +#ifdef USE_WIFI_LISTENERS class IPAddressWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiIPStateListener { public: void setup() override; @@ -62,6 +63,7 @@ class BSSIDWiFiInfo final : public Component, public text_sensor::TextSensor, pu // WiFiConnectStateListener interface void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override; }; +#endif class MacAddressWifiInfo final : public Component, public text_sensor::TextSensor { public: diff --git a/tests/components/wifi_info/common-mac.yaml b/tests/components/wifi_info/common-mac.yaml new file mode 100644 index 0000000000..3571cd08c4 --- /dev/null +++ b/tests/components/wifi_info/common-mac.yaml @@ -0,0 +1,8 @@ +wifi: + ssid: MySSID + password: password1 + +text_sensor: + - platform: wifi_info + mac_address: + name: MAC Address diff --git a/tests/components/wifi_info/common.yaml b/tests/components/wifi_info/common.yaml index cf5ea563ba..f87d381d0c 100644 --- a/tests/components/wifi_info/common.yaml +++ b/tests/components/wifi_info/common.yaml @@ -13,6 +13,6 @@ text_sensor: bssid: name: BSSID mac_address: - name: Mac Address + name: MAC Address dns_address: name: DNS ADdress diff --git a/tests/components/wifi_info/test-mac.esp32-idf.yaml b/tests/components/wifi_info/test-mac.esp32-idf.yaml new file mode 100644 index 0000000000..9d561ca2ce --- /dev/null +++ b/tests/components/wifi_info/test-mac.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml + +<<: !include common-mac.yaml diff --git a/tests/components/wifi_info/test-mac.esp8266-ard.yaml b/tests/components/wifi_info/test-mac.esp8266-ard.yaml new file mode 100644 index 0000000000..05f6344fb6 --- /dev/null +++ b/tests/components/wifi_info/test-mac.esp8266-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml + +<<: !include common-mac.yaml diff --git a/tests/components/wifi_info/test-mac.rp2040-ard.yaml b/tests/components/wifi_info/test-mac.rp2040-ard.yaml new file mode 100644 index 0000000000..d2d54def9d --- /dev/null +++ b/tests/components/wifi_info/test-mac.rp2040-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml + +<<: !include common-mac.yaml From 664881bc130f070d84cc88f59ed9a2baf7dc2654 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 1 Dec 2025 06:57:18 -0600 Subject: [PATCH 0448/1145] [uart] Convert to C++17 namespace style (#12220) --- esphome/components/uart/automation.h | 6 ++---- esphome/components/uart/button/uart_button.cpp | 6 ++---- esphome/components/uart/button/uart_button.h | 6 ++---- .../components/uart/packet_transport/uart_transport.cpp | 7 +++---- esphome/components/uart/packet_transport/uart_transport.h | 6 ++---- esphome/components/uart/switch/uart_switch.cpp | 6 ++---- esphome/components/uart/switch/uart_switch.h | 6 ++---- esphome/components/uart/uart.cpp | 6 ++---- esphome/components/uart/uart.h | 6 ++---- esphome/components/uart/uart_component.cpp | 6 ++---- esphome/components/uart/uart_component.h | 6 ++---- esphome/components/uart/uart_component_esp8266.cpp | 6 ++---- esphome/components/uart/uart_component_esp8266.h | 7 ++----- esphome/components/uart/uart_component_esp_idf.cpp | 8 +++----- esphome/components/uart/uart_component_esp_idf.h | 7 ++----- esphome/components/uart/uart_component_host.cpp | 7 ++----- esphome/components/uart/uart_component_host.h | 7 ++----- esphome/components/uart/uart_component_libretiny.cpp | 7 ++----- esphome/components/uart/uart_component_libretiny.h | 7 ++----- esphome/components/uart/uart_component_rp2040.cpp | 7 ++----- esphome/components/uart/uart_component_rp2040.h | 7 ++----- esphome/components/uart/uart_debugger.cpp | 6 ++---- esphome/components/uart/uart_debugger.h | 6 ++---- 23 files changed, 48 insertions(+), 101 deletions(-) diff --git a/esphome/components/uart/automation.h b/esphome/components/uart/automation.h index c2eb308eb8..c99caac97b 100644 --- a/esphome/components/uart/automation.h +++ b/esphome/components/uart/automation.h @@ -5,8 +5,7 @@ #include -namespace esphome { -namespace uart { +namespace esphome::uart { template class UARTWriteAction : public Action, public Parented { public: @@ -41,5 +40,4 @@ template class UARTWriteAction : public Action, public Pa } code_; }; -} // namespace uart -} // namespace esphome +} // namespace esphome::uart diff --git a/esphome/components/uart/button/uart_button.cpp b/esphome/components/uart/button/uart_button.cpp index dd228b9bb7..809ceaabb0 100644 --- a/esphome/components/uart/button/uart_button.cpp +++ b/esphome/components/uart/button/uart_button.cpp @@ -1,8 +1,7 @@ #include "uart_button.h" #include "esphome/core/log.h" -namespace esphome { -namespace uart { +namespace esphome::uart { static const char *const TAG = "uart.button"; @@ -13,5 +12,4 @@ void UARTButton::press_action() { void UARTButton::dump_config() { LOG_BUTTON("", "UART Button", this); } -} // namespace uart -} // namespace esphome +} // namespace esphome::uart diff --git a/esphome/components/uart/button/uart_button.h b/esphome/components/uart/button/uart_button.h index 8c7d762a05..2b530d3c4b 100644 --- a/esphome/components/uart/button/uart_button.h +++ b/esphome/components/uart/button/uart_button.h @@ -6,8 +6,7 @@ #include -namespace esphome { -namespace uart { +namespace esphome::uart { class UARTButton : public button::Button, public UARTDevice, public Component { public: @@ -21,5 +20,4 @@ class UARTButton : public button::Button, public UARTDevice, public Component { std::vector data_; }; -} // namespace uart -} // namespace esphome +} // namespace esphome::uart diff --git a/esphome/components/uart/packet_transport/uart_transport.cpp b/esphome/components/uart/packet_transport/uart_transport.cpp index 423b657532..4a9aa0fe47 100644 --- a/esphome/components/uart/packet_transport/uart_transport.cpp +++ b/esphome/components/uart/packet_transport/uart_transport.cpp @@ -2,8 +2,7 @@ #include "esphome/core/application.h" #include "uart_transport.h" -namespace esphome { -namespace uart { +namespace esphome::uart { static const char *const TAG = "uart_transport"; @@ -84,5 +83,5 @@ void UARTTransport::send_packet(const std::vector &buf) const { this->write_byte_(crc >> 8); this->parent_->write_byte(FLAG_BYTE); } -} // namespace uart -} // namespace esphome + +} // namespace esphome::uart diff --git a/esphome/components/uart/packet_transport/uart_transport.h b/esphome/components/uart/packet_transport/uart_transport.h index f1431e948c..e84bed95e6 100644 --- a/esphome/components/uart/packet_transport/uart_transport.h +++ b/esphome/components/uart/packet_transport/uart_transport.h @@ -5,8 +5,7 @@ #include #include "../uart.h" -namespace esphome { -namespace uart { +namespace esphome::uart { /** * A transport protocol for sending and receiving packets over a UART connection. @@ -37,5 +36,4 @@ class UARTTransport : public packet_transport::PacketTransport, public UARTDevic bool rx_control_{}; }; -} // namespace uart -} // namespace esphome +} // namespace esphome::uart diff --git a/esphome/components/uart/switch/uart_switch.cpp b/esphome/components/uart/switch/uart_switch.cpp index 4f5ff9fc99..642bd19772 100644 --- a/esphome/components/uart/switch/uart_switch.cpp +++ b/esphome/components/uart/switch/uart_switch.cpp @@ -2,8 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -namespace esphome { -namespace uart { +namespace esphome::uart { static const char *const TAG = "uart.switch"; @@ -58,5 +57,4 @@ void UARTSwitch::dump_config() { } } -} // namespace uart -} // namespace esphome +} // namespace esphome::uart diff --git a/esphome/components/uart/switch/uart_switch.h b/esphome/components/uart/switch/uart_switch.h index 909307d57e..5730fc9b4b 100644 --- a/esphome/components/uart/switch/uart_switch.h +++ b/esphome/components/uart/switch/uart_switch.h @@ -7,8 +7,7 @@ #include #include -namespace esphome { -namespace uart { +namespace esphome::uart { class UARTSwitch : public switch_::Switch, public UARTDevice, public Component { public: @@ -33,5 +32,4 @@ class UARTSwitch : public switch_::Switch, public UARTDevice, public Component { uint32_t last_transmission_; }; -} // namespace uart -} // namespace esphome +} // namespace esphome::uart diff --git a/esphome/components/uart/uart.cpp b/esphome/components/uart/uart.cpp index b18454bf9d..6cfd6537a5 100644 --- a/esphome/components/uart/uart.cpp +++ b/esphome/components/uart/uart.cpp @@ -5,8 +5,7 @@ #include "esphome/core/log.h" #include -namespace esphome { -namespace uart { +namespace esphome::uart { static const char *const TAG = "uart"; @@ -43,5 +42,4 @@ const LogString *parity_to_str(UARTParityOptions parity) { } } -} // namespace uart -} // namespace esphome +} // namespace esphome::uart diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index e2912db122..72c282f1c4 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -6,8 +6,7 @@ #include "esphome/core/log.h" #include "uart_component.h" -namespace esphome { -namespace uart { +namespace esphome::uart { class UARTDevice { public: @@ -74,5 +73,4 @@ class UARTDevice { UARTComponent *parent_{nullptr}; }; -} // namespace uart -} // namespace esphome +} // namespace esphome::uart diff --git a/esphome/components/uart/uart_component.cpp b/esphome/components/uart/uart_component.cpp index 8f670275d4..30fc208fc9 100644 --- a/esphome/components/uart/uart_component.cpp +++ b/esphome/components/uart/uart_component.cpp @@ -1,7 +1,6 @@ #include "uart_component.h" -namespace esphome { -namespace uart { +namespace esphome::uart { static const char *const TAG = "uart"; @@ -28,5 +27,4 @@ void UARTComponent::set_rx_full_threshold_ms(uint8_t time) { this->set_rx_full_threshold(val); } -} // namespace uart -} // namespace esphome +} // namespace esphome::uart diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index 452688b3e9..fd528e228f 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -11,8 +11,7 @@ #include "esphome/core/automation.h" #endif -namespace esphome { -namespace uart { +namespace esphome::uart { enum UARTParityOptions { UART_CONFIG_PARITY_NONE, @@ -199,5 +198,4 @@ class UARTComponent { #endif }; -} // namespace uart -} // namespace esphome +} // namespace esphome::uart diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index c84a877ef4..c78daa7462 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -9,8 +9,7 @@ #include "esphome/components/logger/logger.h" #endif -namespace esphome { -namespace uart { +namespace esphome::uart { static const char *const TAG = "uart.arduino_esp8266"; bool ESP8266UartComponent::serial0_in_use = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -331,6 +330,5 @@ int ESP8266SoftwareSerial::available() { return avail; } -} // namespace uart -} // namespace esphome +} // namespace esphome::uart #endif // USE_ESP8266 diff --git a/esphome/components/uart/uart_component_esp8266.h b/esphome/components/uart/uart_component_esp8266.h index 749dd4c61e..e33dd00644 100644 --- a/esphome/components/uart/uart_component_esp8266.h +++ b/esphome/components/uart/uart_component_esp8266.h @@ -9,8 +9,7 @@ #include "esphome/core/log.h" #include "uart_component.h" -namespace esphome { -namespace uart { +namespace esphome::uart { class ESP8266SoftwareSerial { public: @@ -88,7 +87,5 @@ class ESP8266UartComponent : public UARTComponent, public Component { static bool serial0_in_use; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) }; -} // namespace uart -} // namespace esphome - +} // namespace esphome::uart #endif // USE_ESP8266 diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index c6efe862c4..b438e4f7a6 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -14,8 +14,8 @@ #include "esphome/components/logger/logger.h" #endif -namespace esphome { -namespace uart { +namespace esphome::uart { + static const char *const TAG = "uart.idf"; uart_config_t IDFUARTComponent::get_config_() { @@ -405,7 +405,5 @@ void IDFUARTComponent::rx_event_task_func(void *param) { } #endif // USE_UART_WAKE_LOOP_ON_RX -} // namespace uart -} // namespace esphome - +} // namespace esphome::uart #endif // USE_ESP32 diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h index e47a323979..bd6d0c792e 100644 --- a/esphome/components/uart/uart_component_esp_idf.h +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -6,8 +6,7 @@ #include "esphome/core/component.h" #include "uart_component.h" -namespace esphome { -namespace uart { +namespace esphome::uart { class IDFUARTComponent : public UARTComponent, public Component { public: @@ -63,7 +62,5 @@ class IDFUARTComponent : public UARTComponent, public Component { #endif // USE_UART_WAKE_LOOP_ON_RX }; -} // namespace uart -} // namespace esphome - +} // namespace esphome::uart #endif // USE_ESP32 diff --git a/esphome/components/uart/uart_component_host.cpp b/esphome/components/uart/uart_component_host.cpp index adb11266c5..69b24607d1 100644 --- a/esphome/components/uart/uart_component_host.cpp +++ b/esphome/components/uart/uart_component_host.cpp @@ -96,8 +96,7 @@ speed_t get_baud(int baud) { } // namespace -namespace esphome { -namespace uart { +namespace esphome::uart { static const char *const TAG = "uart.host"; @@ -296,7 +295,5 @@ void HostUartComponent::update_error_(const std::string &error) { ESP_LOGE(TAG, "Port error: %s", error.c_str()); } -} // namespace uart -} // namespace esphome - +} // namespace esphome::uart #endif // USE_HOST diff --git a/esphome/components/uart/uart_component_host.h b/esphome/components/uart/uart_component_host.h index c1f1dd0d2c..a4a6946c0c 100644 --- a/esphome/components/uart/uart_component_host.h +++ b/esphome/components/uart/uart_component_host.h @@ -6,8 +6,7 @@ #include "esphome/core/log.h" #include "uart_component.h" -namespace esphome { -namespace uart { +namespace esphome::uart { class HostUartComponent : public UARTComponent, public Component { public: @@ -32,7 +31,5 @@ class HostUartComponent : public UARTComponent, public Component { uint8_t peek_byte_; }; -} // namespace uart -} // namespace esphome - +} // namespace esphome::uart #endif // USE_HOST diff --git a/esphome/components/uart/uart_component_libretiny.cpp b/esphome/components/uart/uart_component_libretiny.cpp index 1e408b169b..01c7063fe8 100644 --- a/esphome/components/uart/uart_component_libretiny.cpp +++ b/esphome/components/uart/uart_component_libretiny.cpp @@ -14,8 +14,7 @@ #include #endif -namespace esphome { -namespace uart { +namespace esphome::uart { static const char *const TAG = "uart.lt"; @@ -187,7 +186,5 @@ void LibreTinyUARTComponent::check_logger_conflict() { #endif } -} // namespace uart -} // namespace esphome - +} // namespace esphome::uart #endif // USE_LIBRETINY diff --git a/esphome/components/uart/uart_component_libretiny.h b/esphome/components/uart/uart_component_libretiny.h index 00982fd297..ec13e7da5a 100644 --- a/esphome/components/uart/uart_component_libretiny.h +++ b/esphome/components/uart/uart_component_libretiny.h @@ -8,8 +8,7 @@ #include "esphome/core/log.h" #include "uart_component.h" -namespace esphome { -namespace uart { +namespace esphome::uart { class LibreTinyUARTComponent : public UARTComponent, public Component { public: @@ -37,7 +36,5 @@ class LibreTinyUARTComponent : public UARTComponent, public Component { int8_t hardware_idx_{-1}; }; -} // namespace uart -} // namespace esphome - +} // namespace esphome::uart #endif // USE_LIBRETINY diff --git a/esphome/components/uart/uart_component_rp2040.cpp b/esphome/components/uart/uart_component_rp2040.cpp index cd3905b5c1..5799d26a54 100644 --- a/esphome/components/uart/uart_component_rp2040.cpp +++ b/esphome/components/uart/uart_component_rp2040.cpp @@ -11,8 +11,7 @@ #include "esphome/components/logger/logger.h" #endif -namespace esphome { -namespace uart { +namespace esphome::uart { static const char *const TAG = "uart.arduino_rp2040"; @@ -193,7 +192,5 @@ void RP2040UartComponent::flush() { this->serial_->flush(); } -} // namespace uart -} // namespace esphome - +} // namespace esphome::uart #endif // USE_RP2040 diff --git a/esphome/components/uart/uart_component_rp2040.h b/esphome/components/uart/uart_component_rp2040.h index f26c913cff..d626d11a2e 100644 --- a/esphome/components/uart/uart_component_rp2040.h +++ b/esphome/components/uart/uart_component_rp2040.h @@ -11,8 +11,7 @@ #include "esphome/core/log.h" #include "uart_component.h" -namespace esphome { -namespace uart { +namespace esphome::uart { class RP2040UartComponent : public UARTComponent, public Component { public: @@ -40,7 +39,5 @@ class RP2040UartComponent : public UARTComponent, public Component { HardwareSerial *serial_{nullptr}; }; -} // namespace uart -} // namespace esphome - +} // namespace esphome::uart #endif // USE_RP2040 diff --git a/esphome/components/uart/uart_debugger.cpp b/esphome/components/uart/uart_debugger.cpp index e2d92eac60..b51a57d68e 100644 --- a/esphome/components/uart/uart_debugger.cpp +++ b/esphome/components/uart/uart_debugger.cpp @@ -6,8 +6,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace uart { +namespace esphome::uart { static const char *const TAG = "uart_debug"; @@ -197,6 +196,5 @@ void UARTDebug::log_binary(UARTDirection direction, std::vector bytes, delay(10); } -} // namespace uart -} // namespace esphome +} // namespace esphome::uart #endif diff --git a/esphome/components/uart/uart_debugger.h b/esphome/components/uart/uart_debugger.h index 4f9b6d09df..df87655962 100644 --- a/esphome/components/uart/uart_debugger.h +++ b/esphome/components/uart/uart_debugger.h @@ -8,8 +8,7 @@ #include "uart.h" #include "uart_component.h" -namespace esphome { -namespace uart { +namespace esphome::uart { /// The UARTDebugger class adds debugging support to a UART bus. /// @@ -96,6 +95,5 @@ class UARTDebug { static void log_binary(UARTDirection direction, std::vector bytes, uint8_t separator); }; -} // namespace uart -} // namespace esphome +} // namespace esphome::uart #endif From 065c1bfc6a438230d10d4595a79570c1bb414f96 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Dec 2025 08:34:07 -0600 Subject: [PATCH 0449/1145] [core] Fix status_momentary API misuse and optimize parameter type (#12216) --- .../components/demo/demo_alarm_control_panel.h | 4 ++-- esphome/components/es8388/es8388.cpp | 4 ++-- esphome/components/esphome/ota/ota_esphome.cpp | 2 +- .../micro_wake_word/micro_wake_word.cpp | 5 ++--- esphome/components/sound_level/sound_level.cpp | 4 ++-- esphome/core/component.cpp | 4 ++-- esphome/core/component.h | 18 ++++++++++++++++-- 7 files changed, 27 insertions(+), 14 deletions(-) diff --git a/esphome/components/demo/demo_alarm_control_panel.h b/esphome/components/demo/demo_alarm_control_panel.h index 9902d27882..f59434830b 100644 --- a/esphome/components/demo/demo_alarm_control_panel.h +++ b/esphome/components/demo/demo_alarm_control_panel.h @@ -33,7 +33,7 @@ class DemoAlarmControlPanel : public AlarmControlPanel, public Component { case ACP_STATE_ARMED_AWAY: if (this->get_requires_code_to_arm() && call.get_code().has_value()) { if (call.get_code().value() != "1234") { - this->status_momentary_error("Invalid code", 5000); + this->status_momentary_error("invalid_code", 5000); return; } } @@ -42,7 +42,7 @@ class DemoAlarmControlPanel : public AlarmControlPanel, public Component { case ACP_STATE_DISARMED: if (this->get_requires_code() && call.get_code().has_value()) { if (call.get_code().value() != "1234") { - this->status_momentary_error("Invalid code", 5000); + this->status_momentary_error("invalid_code", 5000); return; } } diff --git a/esphome/components/es8388/es8388.cpp b/esphome/components/es8388/es8388.cpp index 69c16a9615..5abe7a5e5f 100644 --- a/esphome/components/es8388/es8388.cpp +++ b/esphome/components/es8388/es8388.cpp @@ -225,7 +225,7 @@ bool ES8388::set_dac_output(DacOutputLine line) { optional ES8388::get_dac_power() { uint8_t dac_power; if (!this->read_byte(ES8388_DACPOWER, &dac_power)) { - this->status_momentary_warning("Failed to read ES8388_DACPOWER"); + this->status_momentary_warning("dacpower_read"); return {}; } switch (dac_power) { @@ -268,7 +268,7 @@ bool ES8388::set_adc_input_mic(AdcInputMicLine line) { optional ES8388::get_mic_input() { uint8_t mic_input; if (!this->read_byte(ES8388_ADCCONTROL2, &mic_input)) { - this->status_momentary_warning("Failed to read ES8388_ADCCONTROL2"); + this->status_momentary_warning("adccontrol2_read"); return {}; } switch (mic_input) { diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index eb6c61a69b..852a50cc22 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -402,7 +402,7 @@ error: this->backend_->abort(); } - this->status_momentary_error("onerror", 5000); + this->status_momentary_error("err", 5000); #ifdef USE_OTA_STATE_CALLBACK this->state_callback_.call(ota::OTA_ERROR, 0.0f, static_cast(error_code)); #endif diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp index a0547b158e..ec8fa34da4 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.cpp +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -298,8 +298,7 @@ void MicroWakeWord::loop() { // uses floating point operations. if (!FrontendPopulateState(&this->frontend_config_, &this->frontend_state_, this->microphone_source_->get_audio_stream_info().get_sample_rate())) { - this->status_momentary_error( - "Failed to allocate buffers for spectrogram feature processor, attempting again in 1 second", 1000); + this->status_momentary_error("frontend_alloc", 1000); return; } @@ -308,7 +307,7 @@ void MicroWakeWord::loop() { if (this->inference_task_handle_ == nullptr) { FrontendFreeStateContents(&this->frontend_state_); // Deallocate frontend state - this->status_momentary_error("Task failed to start, attempting again in 1 second", 1000); + this->status_momentary_error("task_start", 1000); } } break; diff --git a/esphome/components/sound_level/sound_level.cpp b/esphome/components/sound_level/sound_level.cpp index db6b168bbc..2719172409 100644 --- a/esphome/components/sound_level/sound_level.cpp +++ b/esphome/components/sound_level/sound_level.cpp @@ -167,7 +167,7 @@ bool SoundLevelComponent::start_() { this->audio_buffer_ = audio::AudioSourceTransferBuffer::create( this->microphone_source_->get_audio_stream_info().ms_to_bytes(AUDIO_BUFFER_DURATION_MS)); if (this->audio_buffer_ == nullptr) { - this->status_momentary_error("Failed to allocate transfer buffer", 15000); + this->status_momentary_error("transfer_buffer", 15000); return false; } @@ -176,7 +176,7 @@ bool SoundLevelComponent::start_() { std::shared_ptr temp_ring_buffer = RingBuffer::create(this->microphone_source_->get_audio_stream_info().ms_to_bytes(RING_BUFFER_DURATION_MS)); if (temp_ring_buffer.use_count() == 0) { - this->status_momentary_error("Failed to allocate ring buffer", 15000); + this->status_momentary_error("ring_buffer", 15000); this->stop_(); return false; } else { diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 5e6ace8873..b7c0cedb76 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -369,11 +369,11 @@ void Component::status_clear_error() { this->component_state_ &= ~STATUS_LED_ERROR; ESP_LOGE(TAG, "%s cleared Error flag", LOG_STR_ARG(this->get_component_log_str())); } -void Component::status_momentary_warning(const std::string &name, uint32_t length) { +void Component::status_momentary_warning(const char *name, uint32_t length) { this->status_set_warning(); this->set_timeout(name, length, [this]() { this->status_clear_warning(); }); } -void Component::status_momentary_error(const std::string &name, uint32_t length) { +void Component::status_momentary_error(const char *name, uint32_t length) { this->status_set_error(); this->set_timeout(name, length, [this]() { this->status_clear_error(); }); } diff --git a/esphome/core/component.h b/esphome/core/component.h index 51a9290e8b..3d45a020c4 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -241,9 +241,23 @@ class Component { void status_clear_error(); - void status_momentary_warning(const std::string &name, uint32_t length = 5000); + /** Set warning status flag and automatically clear it after a timeout. + * + * @param name Identifier for the timeout (used to cancel/replace existing timeouts with the same name). + * Must be a static string literal (stored in flash/rodata), not a temporary or dynamic string. + * This is NOT a message to display - use status_set_warning() with a message if logging is needed. + * @param length Duration in milliseconds before the warning is automatically cleared. + */ + void status_momentary_warning(const char *name, uint32_t length = 5000); - void status_momentary_error(const std::string &name, uint32_t length = 5000); + /** Set error status flag and automatically clear it after a timeout. + * + * @param name Identifier for the timeout (used to cancel/replace existing timeouts with the same name). + * Must be a static string literal (stored in flash/rodata), not a temporary or dynamic string. + * This is NOT a message to display - use status_set_error() with a message if logging is needed. + * @param length Duration in milliseconds before the error is automatically cleared. + */ + void status_momentary_error(const char *name, uint32_t length = 5000); bool has_overridden_loop() const; From b322622ef17a08fe40b8d2b84ef57c9c3766982f Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Mon, 1 Dec 2025 16:47:00 +0100 Subject: [PATCH 0450/1145] [micronova] Convert to C++17 namespace style (#12229) --- esphome/components/micronova/button/micronova_button.cpp | 6 ++---- esphome/components/micronova/button/micronova_button.h | 6 ++---- esphome/components/micronova/micronova.cpp | 6 ++---- esphome/components/micronova/micronova.h | 6 ++---- esphome/components/micronova/number/micronova_number.cpp | 6 ++---- esphome/components/micronova/number/micronova_number.h | 6 ++---- esphome/components/micronova/sensor/micronova_sensor.cpp | 6 ++---- esphome/components/micronova/sensor/micronova_sensor.h | 6 ++---- esphome/components/micronova/switch/micronova_switch.cpp | 6 ++---- esphome/components/micronova/switch/micronova_switch.h | 6 ++---- .../micronova/text_sensor/micronova_text_sensor.cpp | 6 ++---- .../micronova/text_sensor/micronova_text_sensor.h | 6 ++---- 12 files changed, 24 insertions(+), 48 deletions(-) diff --git a/esphome/components/micronova/button/micronova_button.cpp b/esphome/components/micronova/button/micronova_button.cpp index c1903fd878..147fef37bd 100644 --- a/esphome/components/micronova/button/micronova_button.cpp +++ b/esphome/components/micronova/button/micronova_button.cpp @@ -1,7 +1,6 @@ #include "micronova_button.h" -namespace esphome { -namespace micronova { +namespace esphome::micronova { void MicroNovaButton::press_action() { switch (this->get_function()) { @@ -14,5 +13,4 @@ void MicroNovaButton::press_action() { this->micronova_->update(); } -} // namespace micronova -} // namespace esphome +} // namespace esphome::micronova diff --git a/esphome/components/micronova/button/micronova_button.h b/esphome/components/micronova/button/micronova_button.h index 77649051d6..5c1d7d8455 100644 --- a/esphome/components/micronova/button/micronova_button.h +++ b/esphome/components/micronova/button/micronova_button.h @@ -4,8 +4,7 @@ #include "esphome/core/component.h" #include "esphome/components/button/button.h" -namespace esphome { -namespace micronova { +namespace esphome::micronova { class MicroNovaButton : public Component, public button::Button, public MicroNovaButtonListener { public: @@ -19,5 +18,4 @@ class MicroNovaButton : public Component, public button::Button, public MicroNov void press_action() override; }; -} // namespace micronova -} // namespace esphome +} // namespace esphome::micronova diff --git a/esphome/components/micronova/micronova.cpp b/esphome/components/micronova/micronova.cpp index b96798ed12..52b719bff2 100644 --- a/esphome/components/micronova/micronova.cpp +++ b/esphome/components/micronova/micronova.cpp @@ -1,8 +1,7 @@ #include "micronova.h" #include "esphome/core/log.h" -namespace esphome { -namespace micronova { +namespace esphome::micronova { void MicroNova::setup() { if (this->enable_rx_pin_ != nullptr) { @@ -144,5 +143,4 @@ void MicroNova::write_address(uint8_t location, uint8_t address, uint8_t data) { } } -} // namespace micronova -} // namespace esphome +} // namespace esphome::micronova diff --git a/esphome/components/micronova/micronova.h b/esphome/components/micronova/micronova.h index fc68d941cf..acb85fad3c 100644 --- a/esphome/components/micronova/micronova.h +++ b/esphome/components/micronova/micronova.h @@ -8,8 +8,7 @@ #include -namespace esphome { -namespace micronova { +namespace esphome::micronova { static const char *const TAG = "micronova"; static const int STOVE_REPLY_DELAY = 60; @@ -160,5 +159,4 @@ class MicroNova : public PollingComponent, public uart::UARTDevice { MicroNovaSwitchListener *stove_switch_{nullptr}; }; -} // namespace micronova -} // namespace esphome +} // namespace esphome::micronova diff --git a/esphome/components/micronova/number/micronova_number.cpp b/esphome/components/micronova/number/micronova_number.cpp index 244eb7ee9f..b311c85b99 100644 --- a/esphome/components/micronova/number/micronova_number.cpp +++ b/esphome/components/micronova/number/micronova_number.cpp @@ -1,7 +1,6 @@ #include "micronova_number.h" -namespace esphome { -namespace micronova { +namespace esphome::micronova { void MicroNovaNumber::process_value_from_stove(int value_from_stove) { float new_sensor_value = 0; @@ -41,5 +40,4 @@ void MicroNovaNumber::control(float value) { this->micronova_->update(); } -} // namespace micronova -} // namespace esphome +} // namespace esphome::micronova diff --git a/esphome/components/micronova/number/micronova_number.h b/esphome/components/micronova/number/micronova_number.h index 49c6358255..79e59dbc28 100644 --- a/esphome/components/micronova/number/micronova_number.h +++ b/esphome/components/micronova/number/micronova_number.h @@ -3,8 +3,7 @@ #include "esphome/components/micronova/micronova.h" #include "esphome/components/number/number.h" -namespace esphome { -namespace micronova { +namespace esphome::micronova { class MicroNovaNumber : public number::Number, public MicroNovaSensorListener { public: @@ -24,5 +23,4 @@ class MicroNovaNumber : public number::Number, public MicroNovaSensorListener { uint8_t memory_write_location_ = 0; }; -} // namespace micronova -} // namespace esphome +} // namespace esphome::micronova diff --git a/esphome/components/micronova/sensor/micronova_sensor.cpp b/esphome/components/micronova/sensor/micronova_sensor.cpp index 3f0c0feaf8..9fd8832f29 100644 --- a/esphome/components/micronova/sensor/micronova_sensor.cpp +++ b/esphome/components/micronova/sensor/micronova_sensor.cpp @@ -1,7 +1,6 @@ #include "micronova_sensor.h" -namespace esphome { -namespace micronova { +namespace esphome::micronova { void MicroNovaSensor::process_value_from_stove(int value_from_stove) { if (value_from_stove == -1) { @@ -31,5 +30,4 @@ void MicroNovaSensor::process_value_from_stove(int value_from_stove) { this->publish_state(new_sensor_value); } -} // namespace micronova -} // namespace esphome +} // namespace esphome::micronova diff --git a/esphome/components/micronova/sensor/micronova_sensor.h b/esphome/components/micronova/sensor/micronova_sensor.h index 9d5ae96b87..081e68b09d 100644 --- a/esphome/components/micronova/sensor/micronova_sensor.h +++ b/esphome/components/micronova/sensor/micronova_sensor.h @@ -3,8 +3,7 @@ #include "esphome/components/micronova/micronova.h" #include "esphome/components/sensor/sensor.h" -namespace esphome { -namespace micronova { +namespace esphome::micronova { class MicroNovaSensor : public sensor::Sensor, public MicroNovaSensorListener { public: @@ -23,5 +22,4 @@ class MicroNovaSensor : public sensor::Sensor, public MicroNovaSensorListener { int fan_speed_offset_ = 0; }; -} // namespace micronova -} // namespace esphome +} // namespace esphome::micronova diff --git a/esphome/components/micronova/switch/micronova_switch.cpp b/esphome/components/micronova/switch/micronova_switch.cpp index 28674acd96..81d36adccb 100644 --- a/esphome/components/micronova/switch/micronova_switch.cpp +++ b/esphome/components/micronova/switch/micronova_switch.cpp @@ -1,7 +1,6 @@ #include "micronova_switch.h" -namespace esphome { -namespace micronova { +namespace esphome::micronova { void MicroNovaSwitch::write_state(bool state) { switch (this->get_function()) { @@ -31,5 +30,4 @@ void MicroNovaSwitch::write_state(bool state) { } } -} // namespace micronova -} // namespace esphome +} // namespace esphome::micronova diff --git a/esphome/components/micronova/switch/micronova_switch.h b/esphome/components/micronova/switch/micronova_switch.h index b0ca33b497..7019084355 100644 --- a/esphome/components/micronova/switch/micronova_switch.h +++ b/esphome/components/micronova/switch/micronova_switch.h @@ -4,8 +4,7 @@ #include "esphome/core/component.h" #include "esphome/components/switch/switch.h" -namespace esphome { -namespace micronova { +namespace esphome::micronova { class MicroNovaSwitch : public Component, public switch_::Switch, public MicroNovaSwitchListener { public: @@ -25,5 +24,4 @@ class MicroNovaSwitch : public Component, public switch_::Switch, public MicroNo void write_state(bool state) override; }; -} // namespace micronova -} // namespace esphome +} // namespace esphome::micronova diff --git a/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp b/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp index 03b192ffd1..b62fb1afce 100644 --- a/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp +++ b/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp @@ -1,7 +1,6 @@ #include "micronova_text_sensor.h" -namespace esphome { -namespace micronova { +namespace esphome::micronova { void MicroNovaTextSensor::process_value_from_stove(int value_from_stove) { if (value_from_stove == -1) { @@ -27,5 +26,4 @@ void MicroNovaTextSensor::process_value_from_stove(int value_from_stove) { } } -} // namespace micronova -} // namespace esphome +} // namespace esphome::micronova diff --git a/esphome/components/micronova/text_sensor/micronova_text_sensor.h b/esphome/components/micronova/text_sensor/micronova_text_sensor.h index b4e5de9bb3..352f049654 100644 --- a/esphome/components/micronova/text_sensor/micronova_text_sensor.h +++ b/esphome/components/micronova/text_sensor/micronova_text_sensor.h @@ -3,8 +3,7 @@ #include "esphome/components/micronova/micronova.h" #include "esphome/components/text_sensor/text_sensor.h" -namespace esphome { -namespace micronova { +namespace esphome::micronova { class MicroNovaTextSensor : public text_sensor::TextSensor, public MicroNovaSensorListener { public: @@ -16,5 +15,4 @@ class MicroNovaTextSensor : public text_sensor::TextSensor, public MicroNovaSens void process_value_from_stove(int value_from_stove) override; }; -} // namespace micronova -} // namespace esphome +} // namespace esphome::micronova From 6d336676a2390491827ea0920376cff73955f71f Mon Sep 17 00:00:00 2001 From: Juri Berlanda Date: Mon, 1 Dec 2025 18:09:58 +0100 Subject: [PATCH 0451/1145] [remote_transmitter, remote_receiver] Add RP2040 support (#12048) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/remote_receiver/__init__.py | 2 ++ .../components/remote_receiver/remote_receiver.cpp | 2 +- esphome/components/remote_receiver/remote_receiver.h | 4 ++-- esphome/components/remote_transmitter/__init__.py | 1 + .../remote_transmitter/remote_transmitter.cpp | 6 +++--- .../remote_transmitter/remote_transmitter.h | 2 +- .../components/remote_receiver/test.rp2040-ard.yaml | 12 ++++++++++++ .../remote_transmitter/test.rp2040-ard.yaml | 7 +++++++ 8 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 tests/components/remote_receiver/test.rp2040-ard.yaml create mode 100644 tests/components/remote_transmitter/test.rp2040-ard.yaml diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index cd2b440645..e79b3f91ed 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -114,6 +114,7 @@ CONFIG_SCHEMA = remote_base.validate_triggers( bk72xx="1000b", ln882x="1000b", rtl87xx="1000b", + rp2040="1000b", ): cv.validate_bytes, cv.Optional(CONF_FILTER, default="50us"): cv.All( cv.positive_time_period_microseconds, @@ -213,6 +214,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( PlatformFramework.BK72XX_ARDUINO, PlatformFramework.RTL87XX_ARDUINO, PlatformFramework.LN882X_ARDUINO, + PlatformFramework.RP2040_ARDUINO, }, } ) diff --git a/esphome/components/remote_receiver/remote_receiver.cpp b/esphome/components/remote_receiver/remote_receiver.cpp index a8438e20d7..53bfb0890f 100644 --- a/esphome/components/remote_receiver/remote_receiver.cpp +++ b/esphome/components/remote_receiver/remote_receiver.cpp @@ -3,7 +3,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#if defined(USE_LIBRETINY) || defined(USE_ESP8266) +#if defined(USE_LIBRETINY) || defined(USE_ESP8266) || defined(USE_RP2040) namespace esphome { namespace remote_receiver { diff --git a/esphome/components/remote_receiver/remote_receiver.h b/esphome/components/remote_receiver/remote_receiver.h index 3ddcf353c7..3d2f7f0ef9 100644 --- a/esphome/components/remote_receiver/remote_receiver.h +++ b/esphome/components/remote_receiver/remote_receiver.h @@ -12,7 +12,7 @@ namespace esphome { namespace remote_receiver { -#if defined(USE_ESP8266) || defined(USE_LIBRETINY) +#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040) struct RemoteReceiverComponentStore { static void gpio_intr(RemoteReceiverComponentStore *arg); @@ -84,7 +84,7 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, std::string error_string_{""}; #endif -#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_ESP32) +#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_ESP32) || defined(USE_RP2040) RemoteReceiverComponentStore store_; HighFrequencyLoopRequester high_freq_; #endif diff --git a/esphome/components/remote_transmitter/__init__.py b/esphome/components/remote_transmitter/__init__.py index faa6c827f7..ff055b959b 100644 --- a/esphome/components/remote_transmitter/__init__.py +++ b/esphome/components/remote_transmitter/__init__.py @@ -156,6 +156,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( PlatformFramework.BK72XX_ARDUINO, PlatformFramework.RTL87XX_ARDUINO, PlatformFramework.LN882X_ARDUINO, + PlatformFramework.RP2040_ARDUINO, }, } ) diff --git a/esphome/components/remote_transmitter/remote_transmitter.cpp b/esphome/components/remote_transmitter/remote_transmitter.cpp index 347e9d9d33..576143bcbc 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -#if defined(USE_LIBRETINY) || defined(USE_ESP8266) +#if defined(USE_LIBRETINY) || defined(USE_ESP8266) || defined(USE_RP2040) namespace esphome { namespace remote_transmitter { @@ -40,8 +40,8 @@ void RemoteTransmitterComponent::await_target_time_() { if (this->target_time_ == 0) { this->target_time_ = current_time; } else if ((int32_t) (this->target_time_ - current_time) > 0) { -#if defined(USE_LIBRETINY) - // busy loop for libretiny is required (see the comment inside micros() in wiring.c) +#if defined(USE_LIBRETINY) || defined(USE_RP2040) + // busy loop is required for libretiny and rp2040 as interrupts are disabled while ((int32_t) (this->target_time_ - micros()) > 0) ; #else diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index cc3b82ad61..dd6a849e4c 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -62,7 +62,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, protected: void send_internal(uint32_t send_times, uint32_t send_wait) override; -#if defined(USE_ESP8266) || defined(USE_LIBRETINY) +#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040) void calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period, uint32_t *off_time_period); void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec); diff --git a/tests/components/remote_receiver/test.rp2040-ard.yaml b/tests/components/remote_receiver/test.rp2040-ard.yaml new file mode 100644 index 0000000000..c9784ae003 --- /dev/null +++ b/tests/components/remote_receiver/test.rp2040-ard.yaml @@ -0,0 +1,12 @@ +remote_receiver: + id: rcvr + pin: GPIO5 + dump: all + <<: !include common-actions.yaml + +binary_sensor: + - platform: remote_receiver + name: Panasonic Remote Input + panasonic: + address: 0x4004 + command: 0x100BCBD diff --git a/tests/components/remote_transmitter/test.rp2040-ard.yaml b/tests/components/remote_transmitter/test.rp2040-ard.yaml new file mode 100644 index 0000000000..19759360f4 --- /dev/null +++ b/tests/components/remote_transmitter/test.rp2040-ard.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + id: xmitr + pin: GPIO5 + carrier_duty_percent: 50% + +packages: + buttons: !include common-buttons.yaml From 2b7695ba3fea894d9ff2787150e87c119ac42e46 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:40:56 -0500 Subject: [PATCH 0452/1145] [core] Fix clean all windows (#12217) Co-authored-by: Claude Co-authored-by: J. Nick Koston --- esphome/writer.py | 35 ++++++++--- tests/unit_tests/test_writer.py | 103 ++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 9 deletions(-) diff --git a/esphome/writer.py b/esphome/writer.py index 3124e9e12c..721db07f96 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -1,8 +1,12 @@ +from collections.abc import Callable import importlib import logging import os from pathlib import Path import re +import shutil +import stat +from types import TracebackType from esphome import loader from esphome.config import iter_component_configs, iter_components @@ -301,9 +305,24 @@ def clean_cmake_cache(): pioenvs_cmake_path.unlink() -def clean_build(clear_pio_cache: bool = True): - import shutil +def _rmtree_error_handler( + func: Callable[[str], object], + path: str, + exc_info: tuple[type[BaseException], BaseException, TracebackType | None], +) -> None: + """Error handler for shutil.rmtree to handle read-only files on Windows. + On Windows, git pack files and other files may be marked read-only, + causing shutil.rmtree to fail with "Access is denied". This handler + removes the read-only flag and retries the deletion. + """ + if os.access(path, os.W_OK): + raise exc_info[1].with_traceback(exc_info[2]) + os.chmod(path, stat.S_IWUSR | stat.S_IRUSR) + func(path) + + +def clean_build(clear_pio_cache: bool = True): # Allow skipping cache cleaning for integration tests if os.environ.get("ESPHOME_SKIP_CLEAN_BUILD"): _LOGGER.warning("Skipping build cleaning (ESPHOME_SKIP_CLEAN_BUILD set)") @@ -312,11 +331,11 @@ def clean_build(clear_pio_cache: bool = True): pioenvs = CORE.relative_pioenvs_path() if pioenvs.is_dir(): _LOGGER.info("Deleting %s", pioenvs) - shutil.rmtree(pioenvs) + shutil.rmtree(pioenvs, onerror=_rmtree_error_handler) piolibdeps = CORE.relative_piolibdeps_path() if piolibdeps.is_dir(): _LOGGER.info("Deleting %s", piolibdeps) - shutil.rmtree(piolibdeps) + shutil.rmtree(piolibdeps, onerror=_rmtree_error_handler) dependencies_lock = CORE.relative_build_path("dependencies.lock") if dependencies_lock.is_file(): _LOGGER.info("Deleting %s", dependencies_lock) @@ -337,12 +356,10 @@ def clean_build(clear_pio_cache: bool = True): cache_dir = Path(config.get("platformio", "cache_dir")) if cache_dir.is_dir(): _LOGGER.info("Deleting PlatformIO cache %s", cache_dir) - shutil.rmtree(cache_dir) + shutil.rmtree(cache_dir, onerror=_rmtree_error_handler) def clean_all(configuration: list[str]): - import shutil - data_dirs = [] for config in configuration: item = Path(config) @@ -364,7 +381,7 @@ def clean_all(configuration: list[str]): if item.is_file() and not item.name.endswith(".json"): item.unlink() elif item.is_dir() and item.name != "storage": - shutil.rmtree(item) + shutil.rmtree(item, onerror=_rmtree_error_handler) # Clean PlatformIO project files try: @@ -378,7 +395,7 @@ def clean_all(configuration: list[str]): path = Path(config.get("platformio", pio_dir)) if path.is_dir(): _LOGGER.info("Deleting PlatformIO %s %s", pio_dir, path) - shutil.rmtree(path) + shutil.rmtree(path, onerror=_rmtree_error_handler) GITIGNORE_CONTENT = """# Gitignore settings for ESPHome diff --git a/tests/unit_tests/test_writer.py b/tests/unit_tests/test_writer.py index a2a358f4d3..9fa60c06ec 100644 --- a/tests/unit_tests/test_writer.py +++ b/tests/unit_tests/test_writer.py @@ -1,7 +1,9 @@ """Test writer module functionality.""" from collections.abc import Callable +import os from pathlib import Path +import stat from typing import Any from unittest.mock import MagicMock, patch @@ -15,6 +17,7 @@ from esphome.writer import ( CPP_INCLUDE_BEGIN, CPP_INCLUDE_END, GITIGNORE_CONTENT, + clean_all, clean_build, clean_cmake_cache, storage_should_clean, @@ -1062,3 +1065,103 @@ def test_clean_all_preserves_json_files( # Verify logging mentions cleaning assert "Cleaning" in caplog.text assert str(build_dir) in caplog.text + + +@patch("esphome.writer.CORE") +def test_clean_build_handles_readonly_files( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test clean_build handles read-only files (e.g., git pack files on Windows).""" + # Create directory structure with read-only files + pioenvs_dir = tmp_path / ".pioenvs" + pioenvs_dir.mkdir() + git_dir = pioenvs_dir / ".git" / "objects" / "pack" + git_dir.mkdir(parents=True) + + # Create a read-only file (simulating git pack files on Windows) + readonly_file = git_dir / "pack-abc123.pack" + readonly_file.write_text("pack data") + os.chmod(readonly_file, stat.S_IRUSR) # Read-only + + # Setup mocks + mock_core.relative_pioenvs_path.return_value = pioenvs_dir + mock_core.relative_piolibdeps_path.return_value = tmp_path / ".piolibdeps" + mock_core.relative_build_path.return_value = tmp_path / "dependencies.lock" + + # Verify file is read-only + assert not os.access(readonly_file, os.W_OK) + + # Call the function - should not crash + clean_build() + + # Verify directory was removed despite read-only files + assert not pioenvs_dir.exists() + + +@patch("esphome.writer.CORE") +def test_clean_all_handles_readonly_files( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test clean_all handles read-only files.""" + # Create config directory + config_dir = tmp_path / "config" + config_dir.mkdir() + + build_dir = config_dir / ".esphome" + build_dir.mkdir() + + # Create a subdirectory with read-only files + subdir = build_dir / "subdir" + subdir.mkdir() + readonly_file = subdir / "readonly.txt" + readonly_file.write_text("content") + os.chmod(readonly_file, stat.S_IRUSR) # Read-only + + # Verify file is read-only + assert not os.access(readonly_file, os.W_OK) + + # Call the function - should not crash + clean_all([str(config_dir)]) + + # Verify directory was removed despite read-only files + assert not subdir.exists() + assert build_dir.exists() # .esphome dir itself is preserved + + +@patch("esphome.writer.CORE") +def test_clean_build_reraises_for_other_errors( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test clean_build re-raises errors that are not read-only permission issues.""" + # Create directory structure with a read-only subdirectory + # This prevents file deletion and triggers the error handler + pioenvs_dir = tmp_path / ".pioenvs" + pioenvs_dir.mkdir() + subdir = pioenvs_dir / "subdir" + subdir.mkdir() + test_file = subdir / "test.txt" + test_file.write_text("content") + + # Make subdir read-only so files inside can't be deleted + os.chmod(subdir, stat.S_IRUSR | stat.S_IXUSR) + + # Setup mocks + mock_core.relative_pioenvs_path.return_value = pioenvs_dir + mock_core.relative_piolibdeps_path.return_value = tmp_path / ".piolibdeps" + mock_core.relative_build_path.return_value = tmp_path / "dependencies.lock" + + try: + # Mock os.access in writer module to return True (writable) + # This simulates a case where the error is NOT due to read-only permissions + # so the error handler should re-raise instead of trying to fix permissions + with ( + patch("esphome.writer.os.access", return_value=True), + pytest.raises(PermissionError), + ): + clean_build() + finally: + # Cleanup - restore write permission so tmp_path cleanup works + os.chmod(subdir, stat.S_IRWXU) From 6a79ce8eff9cf6312ee4d0333df732ab948af518 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 1 Dec 2025 14:16:39 -0600 Subject: [PATCH 0453/1145] [uart] Automatically enable the socket wake infrastructure when RX wake requested (#12221) --- esphome/components/uart/__init__.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index a1c78dd45c..6494aaa286 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -42,6 +42,17 @@ _LOGGER = getLogger(__name__) CODEOWNERS = ["@esphome/core"] DOMAIN = "uart" + +def AUTO_LOAD() -> list[str]: + """Ideally, we would only auto-load socket only when wake_loop_on_rx is requested; + however, AUTO_LOAD is examined before wake_loop_on_rx is set, so instead, since ESP32 + always uses socket select support in the main app, we'll just ensure it's loaded here. + """ + if CORE.is_esp32: + return ["socket"] + return [] + + uart_ns = cg.esphome_ns.namespace("uart") UARTComponent = uart_ns.class_("UARTComponent") @@ -125,7 +136,15 @@ def request_wake_loop_on_rx() -> None: should call this function during their code generation. This enables the RX event task which wakes the main loop when data arrives. """ - _get_data().wake_loop_on_rx = True + data = _get_data() + if not data.wake_loop_on_rx: + data.wake_loop_on_rx = True + + # UART RX event task uses wake_loop_threadsafe() to notify the main loop + # Automatically enable the socket wake infrastructure when RX wake is requested + from esphome.components import socket + + socket.require_wake_loop_threadsafe() def validate_raw_data(value): From 52fe3de78f81bea1d44fb4f87ed90e51ddc8ac92 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 1 Dec 2025 14:27:20 -0600 Subject: [PATCH 0454/1145] [zwave_proxy] Use new socket wake infrastructure to reduce latency, convert to C++17 namespace style (#12135) Co-authored-by: J. Nick Koston --- esphome/components/zwave_proxy/__init__.py | 3 +++ esphome/components/zwave_proxy/zwave_proxy.cpp | 8 ++++---- esphome/components/zwave_proxy/zwave_proxy.h | 6 ++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/esphome/components/zwave_proxy/__init__.py b/esphome/components/zwave_proxy/__init__.py index d88f9f7041..5be05bb464 100644 --- a/esphome/components/zwave_proxy/__init__.py +++ b/esphome/components/zwave_proxy/__init__.py @@ -41,3 +41,6 @@ async def to_code(config): await cg.register_component(var, config) await uart.register_uart_device(var, config) cg.add_define("USE_ZWAVE_PROXY") + + # Request UART to wake the main loop when data arrives for low-latency processing + uart.request_wake_loop_on_rx() diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp index a26a9b2335..e0ca5529b8 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.cpp +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -5,8 +5,7 @@ #include "esphome/core/log.h" #include "esphome/core/util.h" -namespace esphome { -namespace zwave_proxy { +namespace esphome::zwave_proxy { static const char *const TAG = "zwave_proxy"; @@ -144,6 +143,7 @@ void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::en this->api_connection_ = api_connection; ESP_LOGV(TAG, "API connection is now subscribed"); break; + case api::enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE: if (this->api_connection_ != api_connection) { ESP_LOGV(TAG, "API connection is not subscribed"); @@ -151,6 +151,7 @@ void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::en } this->api_connection_ = nullptr; break; + default: ESP_LOGW(TAG, "Unknown request type: %d", type); break; @@ -342,5 +343,4 @@ bool ZWaveProxy::response_handler_() { ZWaveProxy *global_zwave_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -} // namespace zwave_proxy -} // namespace esphome +} // namespace esphome::zwave_proxy diff --git a/esphome/components/zwave_proxy/zwave_proxy.h b/esphome/components/zwave_proxy/zwave_proxy.h index 20d9090d98..e23e202bea 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.h +++ b/esphome/components/zwave_proxy/zwave_proxy.h @@ -8,8 +8,7 @@ #include -namespace esphome { -namespace zwave_proxy { +namespace esphome::zwave_proxy { static constexpr size_t MAX_ZWAVE_FRAME_SIZE = 257; // Maximum Z-Wave frame size @@ -89,5 +88,4 @@ class ZWaveProxy : public uart::UARTDevice, public Component { extern ZWaveProxy *global_zwave_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -} // namespace zwave_proxy -} // namespace esphome +} // namespace esphome::zwave_proxy From 78df884bb54feacb181ca66ac7907de551d08ab8 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 1 Dec 2025 16:03:00 -0500 Subject: [PATCH 0455/1145] [rtl87xx] Fix AsyncTCP compilation by upgrading FreeRTOS to 8.2.3 (#12230) Co-authored-by: Claude --- esphome/components/rtl87xx/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/rtl87xx/__init__.py b/esphome/components/rtl87xx/__init__.py index 109c986f75..d24ffcea3d 100644 --- a/esphome/components/rtl87xx/__init__.py +++ b/esphome/components/rtl87xx/__init__.py @@ -6,6 +6,7 @@ # in schema.py file in this directory. from esphome import pins +import esphome.codegen as cg from esphome.components import libretiny from esphome.components.libretiny.const import ( COMPONENT_RTL87XX, @@ -45,6 +46,9 @@ CONFIG_SCHEMA.prepend_extra(_set_core_data) async def to_code(config): + # Use FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake required by AsyncTCP 3.4.3+ + # https://github.com/esphome/esphome/issues/10220 + cg.add_platformio_option("custom_versions.freertos", "8.2.3") return await libretiny.component_to_code(config) From d4bd282bb4edc3f2f5c2d28e4e65b496edd0ef83 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 1 Dec 2025 16:08:49 -0600 Subject: [PATCH 0456/1145] [helpers] Fix unit tests following #12135 (#12237) --- script/helpers.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/script/helpers.py b/script/helpers.py index 1039ef39ac..06a50a3092 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -630,7 +630,12 @@ def get_all_dependencies(component_names: set[str]) -> set[str]: Returns: Set of all components including dependencies and auto-loaded components """ - from esphome.const import KEY_CORE + from esphome.const import ( + KEY_CORE, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, + PLATFORM_HOST, + ) from esphome.core import CORE from esphome.loader import get_component @@ -642,7 +647,10 @@ def get_all_dependencies(component_names: set[str]) -> set[str]: # Set up fake config path for component loading root = Path(__file__).parent.parent CORE.config_path = root - CORE.data[KEY_CORE] = {} + CORE.data[KEY_CORE] = { + KEY_TARGET_PLATFORM: PLATFORM_HOST, + KEY_TARGET_FRAMEWORK: "host-native", + } # Keep finding dependencies until no new ones are found while True: From d332edfacadc08f8b3d232a323dbe5cad824ae15 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Dec 2025 16:50:03 -0600 Subject: [PATCH 0457/1145] [datetime] Convert to C++17 nested namespace style (#12235) --- esphome/components/datetime/date_entity.cpp | 6 ++---- esphome/components/datetime/date_entity.h | 6 ++---- esphome/components/datetime/datetime_base.h | 6 ++---- esphome/components/datetime/datetime_entity.cpp | 6 ++---- esphome/components/datetime/datetime_entity.h | 6 ++---- esphome/components/datetime/time_entity.cpp | 6 ++---- esphome/components/datetime/time_entity.h | 6 ++---- 7 files changed, 14 insertions(+), 28 deletions(-) diff --git a/esphome/components/datetime/date_entity.cpp b/esphome/components/datetime/date_entity.cpp index 2c2775ecf4..c061bc81f7 100644 --- a/esphome/components/datetime/date_entity.cpp +++ b/esphome/components/datetime/date_entity.cpp @@ -5,8 +5,7 @@ #include "esphome/core/log.h" -namespace esphome { -namespace datetime { +namespace esphome::datetime { static const char *const TAG = "datetime.date_entity"; @@ -129,7 +128,6 @@ void DateEntityRestoreState::apply(DateEntity *date) { date->publish_state(); } -} // namespace datetime -} // namespace esphome +} // namespace esphome::datetime #endif // USE_DATETIME_DATE diff --git a/esphome/components/datetime/date_entity.h b/esphome/components/datetime/date_entity.h index ba2edb127a..069116d162 100644 --- a/esphome/components/datetime/date_entity.h +++ b/esphome/components/datetime/date_entity.h @@ -10,8 +10,7 @@ #include "datetime_base.h" -namespace esphome { -namespace datetime { +namespace esphome::datetime { #define LOG_DATETIME_DATE(prefix, type, obj) \ if ((obj) != nullptr) { \ @@ -111,7 +110,6 @@ template class DateSetAction : public Action, public Pare } }; -} // namespace datetime -} // namespace esphome +} // namespace esphome::datetime #endif // USE_DATETIME_DATE diff --git a/esphome/components/datetime/datetime_base.h b/esphome/components/datetime/datetime_base.h index b5f54ac96f..7b9b281ea4 100644 --- a/esphome/components/datetime/datetime_base.h +++ b/esphome/components/datetime/datetime_base.h @@ -8,8 +8,7 @@ #include "esphome/components/time/real_time_clock.h" #endif -namespace esphome { -namespace datetime { +namespace esphome::datetime { class DateTimeBase : public EntityBase { public: @@ -37,5 +36,4 @@ class DateTimeStateTrigger : public Trigger { } }; -} // namespace datetime -} // namespace esphome +} // namespace esphome::datetime diff --git a/esphome/components/datetime/datetime_entity.cpp b/esphome/components/datetime/datetime_entity.cpp index 8606a47fa7..694f9c5721 100644 --- a/esphome/components/datetime/datetime_entity.cpp +++ b/esphome/components/datetime/datetime_entity.cpp @@ -5,8 +5,7 @@ #include "esphome/core/log.h" -namespace esphome { -namespace datetime { +namespace esphome::datetime { static const char *const TAG = "datetime.datetime_entity"; @@ -250,7 +249,6 @@ bool OnDateTimeTrigger::matches_(const ESPTime &time) const { } #endif -} // namespace datetime -} // namespace esphome +} // namespace esphome::datetime #endif // USE_DATETIME_TIME diff --git a/esphome/components/datetime/datetime_entity.h b/esphome/components/datetime/datetime_entity.h index 43bff5a181..018346b34b 100644 --- a/esphome/components/datetime/datetime_entity.h +++ b/esphome/components/datetime/datetime_entity.h @@ -10,8 +10,7 @@ #include "datetime_base.h" -namespace esphome { -namespace datetime { +namespace esphome::datetime { #define LOG_DATETIME_DATETIME(prefix, type, obj) \ if ((obj) != nullptr) { \ @@ -146,7 +145,6 @@ class OnDateTimeTrigger : public Trigger<>, public Component, public Parented, public Component, public Parented Date: Mon, 1 Dec 2025 16:50:29 -0600 Subject: [PATCH 0458/1145] [button] Convert to C++17 nested namespace style (#12233) Co-authored-by: Keith Burzinski --- esphome/components/button/automation.h | 6 ++---- esphome/components/button/button.cpp | 6 ++---- esphome/components/button/button.h | 6 ++---- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/esphome/components/button/automation.h b/esphome/components/button/automation.h index 3b792eb5d7..6a54b141a3 100644 --- a/esphome/components/button/automation.h +++ b/esphome/components/button/automation.h @@ -4,8 +4,7 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" -namespace esphome { -namespace button { +namespace esphome::button { template class PressAction : public Action { public: @@ -24,5 +23,4 @@ class ButtonPressTrigger : public Trigger<> { } }; -} // namespace button -} // namespace esphome +} // namespace esphome::button diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index c968d31088..87a222776e 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -1,8 +1,7 @@ #include "button.h" #include "esphome/core/log.h" -namespace esphome { -namespace button { +namespace esphome::button { static const char *const TAG = "button"; @@ -26,5 +25,4 @@ void Button::press() { } void Button::add_on_press_callback(std::function &&callback) { this->press_callback_.add(std::move(callback)); } -} // namespace button -} // namespace esphome +} // namespace esphome::button diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index 75b76f9dcf..18122f6f2f 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -4,8 +4,7 @@ #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" -namespace esphome { -namespace button { +namespace esphome::button { class Button; void log_button(const char *tag, const char *prefix, const char *type, Button *obj); @@ -45,5 +44,4 @@ class Button : public EntityBase, public EntityBase_DeviceClass { CallbackManager press_callback_{}; }; -} // namespace button -} // namespace esphome +} // namespace esphome::button From e42cf9a4f452ed80ec43782b11fd73543d41c164 Mon Sep 17 00:00:00 2001 From: Peter Popovec Date: Tue, 2 Dec 2025 00:06:47 +0100 Subject: [PATCH 0459/1145] [mqtt] Enable support for the RTL87XX platform (#7697) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- esphome/components/mqtt/__init__.py | 3 ++- tests/components/mqtt/test.rtl87xx-ard.yaml | 2 ++ .../build_components_base.rtl87xx-ard.yaml | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/components/mqtt/test.rtl87xx-ard.yaml create mode 100644 tests/test_build_components/build_components_base.rtl87xx-ard.yaml diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 1fc0c30db1..237ed2ce38 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -55,6 +55,7 @@ from esphome.const import ( PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, + PLATFORM_RTL87XX, PlatformFramework, ) from esphome.core import CORE, CoroPriority, coroutine_with_priority @@ -316,7 +317,7 @@ CONFIG_SCHEMA = cv.All( } ), validate_config, - cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]), + cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]), _consume_mqtt_sockets, ) diff --git a/tests/components/mqtt/test.rtl87xx-ard.yaml b/tests/components/mqtt/test.rtl87xx-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/mqtt/test.rtl87xx-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/test_build_components/build_components_base.rtl87xx-ard.yaml b/tests/test_build_components/build_components_base.rtl87xx-ard.yaml new file mode 100644 index 0000000000..1720ef700d --- /dev/null +++ b/tests/test_build_components/build_components_base.rtl87xx-ard.yaml @@ -0,0 +1,15 @@ +esphome: + name: componenttestesprtl87xx + friendly_name: $component_name + +rtl87xx: + board: generic-rtl8710bn-2mb-788k + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file From df58e832e56dbae50baa2a6218166c92bef5c631 Mon Sep 17 00:00:00 2001 From: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com> Date: Tue, 2 Dec 2025 00:44:33 +0100 Subject: [PATCH 0460/1145] [esp8266] Allow IN&OUT pin config for ESP8266 (#12238) --- esphome/components/esp8266/gpio.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 17a495bc1d..124df39ce3 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -8,11 +8,13 @@ namespace esphome::esp8266 { static const char *const TAG = "esp8266"; static int flags_to_mode(gpio::Flags flags, uint8_t pin) { - if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone) - return INPUT; - } else if (flags == gpio::FLAG_OUTPUT) { + if (flags == gpio::FLAG_OUTPUT || flags == (gpio::FLAG_OUTPUT | gpio::FLAG_INPUT)) { return OUTPUT; - } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + } + if (flags == gpio::FLAG_INPUT) { + return INPUT; + } + if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { if (pin == 16) { // GPIO16 doesn't have a pullup, so pinMode would fail. // However, sometimes this method is called with pullup mode anyway @@ -21,13 +23,14 @@ static int flags_to_mode(gpio::Flags flags, uint8_t pin) { return INPUT; } return INPUT_PULLUP; - } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { - return INPUT_PULLDOWN_16; - } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { - return OUTPUT_OPEN_DRAIN; - } else { - return 0; } + if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + return INPUT_PULLDOWN_16; + } + if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return OUTPUT_OPEN_DRAIN; + } + return INPUT; } struct ISRPinArg { From 6dafc5137e074fe09484fa7df821d125e117ed41 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Dec 2025 21:24:08 -0600 Subject: [PATCH 0461/1145] [esp32] Place FreeRTOS functions in flash by default (prep for IDF 6.0) (#12182) --- esphome/components/esp32/__init__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 35ef76634b..c49fc89fbd 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -584,6 +584,7 @@ CONF_DISABLE_LIBC_LOCKS_IN_IRAM = "disable_libc_locks_in_iram" CONF_DISABLE_VFS_SUPPORT_TERMIOS = "disable_vfs_support_termios" CONF_DISABLE_VFS_SUPPORT_SELECT = "disable_vfs_support_select" CONF_DISABLE_VFS_SUPPORT_DIR = "disable_vfs_support_dir" +CONF_FREERTOS_IN_IRAM = "freertos_in_iram" CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size" # VFS requirement tracking @@ -677,6 +678,7 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True): cv.boolean, cv.Optional(CONF_DISABLE_VFS_SUPPORT_SELECT, default=True): cv.boolean, cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean, + cv.Optional(CONF_FREERTOS_IN_IRAM, default=False): cv.boolean, cv.Optional(CONF_EXECUTE_FROM_PSRAM, default=False): cv.boolean, cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range( min=8192, max=32768 @@ -1003,6 +1005,22 @@ async def to_code(config): # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) + # Place non-ISR FreeRTOS functions into flash instead of IRAM + # This saves up to 8KB of IRAM. ISR-safe functions (FromISR variants) stay in IRAM. + # In ESP-IDF 6.0 this becomes the default and CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH + # is removed (replaced by CONFIG_FREERTOS_IN_IRAM to restore old behavior). + # We enable this now to match IDF 6.0 behavior and catch any issues early. + # Users can set freertos_in_iram: true as an escape hatch if they encounter problems + # with code that incorrectly calls FreeRTOS functions from ISRs with cache disabled. + if conf[CONF_ADVANCED][CONF_FREERTOS_IN_IRAM]: + # IDF 5.x: don't set the flash option (keeps functions in IRAM) + # IDF 6.0+: will need CONFIG_FREERTOS_IN_IRAM=y to restore IRAM placement + add_idf_sdkconfig_option("CONFIG_FREERTOS_IN_IRAM", True) + else: + # IDF 5.x: explicitly place functions in flash + # IDF 6.0+: this is the default, option no longer exists + add_idf_sdkconfig_option("CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH", True) + # Setup watchdog add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True) add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True) From 69438031761225923e6e31546b18c856cec3864d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Dec 2025 21:26:13 -0600 Subject: [PATCH 0462/1145] [cover] Store cover state strings in flash on ESP8266 (#12196) --- esphome/components/cover/cover.cpp | 22 ++++++++++---------- esphome/components/cover/cover.h | 3 ++- esphome/components/web_server/web_server.cpp | 10 +++++---- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 8f735982f1..feac9823b9 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -13,25 +13,25 @@ static const char *const TAG = "cover"; const float COVER_OPEN = 1.0f; const float COVER_CLOSED = 0.0f; -const char *cover_command_to_str(float pos) { +const LogString *cover_command_to_str(float pos) { if (pos == COVER_OPEN) { - return "OPEN"; + return LOG_STR("OPEN"); } else if (pos == COVER_CLOSED) { - return "CLOSE"; + return LOG_STR("CLOSE"); } else { - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -const char *cover_operation_to_str(CoverOperation op) { +const LogString *cover_operation_to_str(CoverOperation op) { switch (op) { case COVER_OPERATION_IDLE: - return "IDLE"; + return LOG_STR("IDLE"); case COVER_OPERATION_OPENING: - return "OPENING"; + return LOG_STR("OPENING"); case COVER_OPERATION_CLOSING: - return "CLOSING"; + return LOG_STR("CLOSING"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } @@ -87,7 +87,7 @@ void CoverCall::perform() { if (traits.get_supports_position()) { ESP_LOGD(TAG, " Position: %.0f%%", *this->position_ * 100.0f); } else { - ESP_LOGD(TAG, " Command: %s", cover_command_to_str(*this->position_)); + ESP_LOGD(TAG, " Command: %s", LOG_STR_ARG(cover_command_to_str(*this->position_))); } } if (this->tilt_.has_value()) { @@ -169,7 +169,7 @@ void Cover::publish_state(bool save) { if (traits.get_supports_tilt()) { ESP_LOGD(TAG, " Tilt: %.0f%%", this->tilt * 100.0f); } - ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation)); + ESP_LOGD(TAG, " Current Operation: %s", LOG_STR_ARG(cover_operation_to_str(this->current_operation))); this->state_callback_.call(); #if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY) diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 6c69c05e71..d8c45ab2bd 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" +#include "esphome/core/log.h" #include "esphome/core/preferences.h" #include "cover_traits.h" @@ -86,7 +87,7 @@ enum CoverOperation : uint8_t { COVER_OPERATION_CLOSING, }; -const char *cover_operation_to_str(CoverOperation op); +const LogString *cover_operation_to_str(CoverOperation op); /** Base class for all cover devices. * diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index bc48793ba2..c752c00899 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -41,6 +41,10 @@ namespace web_server { static const char *const TAG = "web_server"; +// Longest: HORIZONTAL (10 chars + null terminator, rounded up) +static constexpr size_t PSTR_LOCAL_SIZE = 16; +#define PSTR_LOCAL(mode_s) ESPHOME_strncpy_P(buf, (ESPHOME_PGM_P) ((mode_s)), PSTR_LOCAL_SIZE - 1) + #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS static const char *const HEADER_PNA_NAME = "Private-Network-Access-Name"; static const char *const HEADER_PNA_ID = "Private-Network-Access-ID"; @@ -908,7 +912,8 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { set_json_icon_state_value(root, obj, "cover", obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position, start_config); - root["current_operation"] = cover::cover_operation_to_str(obj->current_operation); + char buf[PSTR_LOCAL_SIZE]; + root["current_operation"] = PSTR_LOCAL(cover::cover_operation_to_str(obj->current_operation)); if (obj->get_traits().get_supports_position()) root["position"] = obj->position; @@ -1272,9 +1277,6 @@ std::string WebServer::select_json(select::Select *obj, const char *value, JsonD } #endif -// Longest: HORIZONTAL -#define PSTR_LOCAL(mode_s) ESPHOME_strncpy_P(buf, (ESPHOME_PGM_P) ((mode_s)), 15) - #ifdef USE_CLIMATE void WebServer::on_climate_update(climate::Climate *obj) { if (!this->include_internal_ && obj->is_internal()) From 2903a4aa92fa8c81ba79ae5eb96a484de4457da6 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 1 Dec 2025 22:41:34 -0500 Subject: [PATCH 0463/1145] [ota] Use ESP-IDF OTA backend for all ESP32 builds (#12244) Co-authored-by: Claude Co-authored-by: J. Nick Koston --- .../components/esphome/ota/ota_esphome.cpp | 1 - .../http_request/ota/ota_http_request.cpp | 1 - esphome/components/ota/__init__.py | 9 ++- .../ota/ota_backend_arduino_esp32.cpp | 72 ------------------- .../ota/ota_backend_arduino_esp32.h | 27 ------- .../components/ota/ota_backend_esp_idf.cpp | 4 +- esphome/components/ota/ota_backend_esp_idf.h | 4 +- 7 files changed, 8 insertions(+), 110 deletions(-) delete mode 100644 esphome/components/ota/ota_backend_arduino_esp32.cpp delete mode 100644 esphome/components/ota/ota_backend_arduino_esp32.h diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 852a50cc22..6cfd543553 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -10,7 +10,6 @@ #endif #include "esphome/components/network/util.h" #include "esphome/components/ota/ota_backend.h" -#include "esphome/components/ota/ota_backend_arduino_esp32.h" #include "esphome/components/ota/ota_backend_arduino_esp8266.h" #include "esphome/components/ota/ota_backend_arduino_libretiny.h" #include "esphome/components/ota/ota_backend_arduino_rp2040.h" diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 4d9e868c74..4552fcc9df 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -7,7 +7,6 @@ #include "esphome/components/md5/md5.h" #include "esphome/components/watchdog/watchdog.h" #include "esphome/components/ota/ota_backend.h" -#include "esphome/components/ota/ota_backend_arduino_esp32.h" #include "esphome/components/ota/ota_backend_arduino_esp8266.h" #include "esphome/components/ota/ota_backend_arduino_rp2040.h" #include "esphome/components/ota/ota_backend_esp_idf.h" diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index eec39668db..be1b6da241 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -87,9 +87,6 @@ BASE_OTA_SCHEMA = cv.Schema( async def to_code(config): cg.add_define("USE_OTA") - if CORE.is_esp32 and CORE.using_arduino: - cg.add_library("Update", None) - if CORE.is_rp2040 and CORE.using_arduino: cg.add_library("Updater", None) @@ -127,8 +124,10 @@ async def ota_to_code(var, config): FILTER_SOURCE_FILES = filter_source_files_from_platform( { - "ota_backend_arduino_esp32.cpp": {PlatformFramework.ESP32_ARDUINO}, - "ota_backend_esp_idf.cpp": {PlatformFramework.ESP32_IDF}, + "ota_backend_esp_idf.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, "ota_backend_arduino_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, "ota_backend_arduino_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO}, "ota_backend_arduino_libretiny.cpp": { diff --git a/esphome/components/ota/ota_backend_arduino_esp32.cpp b/esphome/components/ota/ota_backend_arduino_esp32.cpp deleted file mode 100644 index 5c6230f2ce..0000000000 --- a/esphome/components/ota/ota_backend_arduino_esp32.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#ifdef USE_ESP32_FRAMEWORK_ARDUINO -#include "esphome/core/defines.h" -#include "esphome/core/log.h" - -#include "ota_backend.h" -#include "ota_backend_arduino_esp32.h" - -#include - -namespace esphome { -namespace ota { - -static const char *const TAG = "ota.arduino_esp32"; - -std::unique_ptr make_ota_backend() { return make_unique(); } - -OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) { - // Handle UPDATE_SIZE_UNKNOWN (0) which is used by web server OTA - // where the exact firmware size is unknown due to multipart encoding - if (image_size == 0) { - image_size = UPDATE_SIZE_UNKNOWN; - } - bool ret = Update.begin(image_size, U_FLASH); - if (ret) { - return OTA_RESPONSE_OK; - } - - uint8_t error = Update.getError(); - if (error == UPDATE_ERROR_SIZE) - return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; - - ESP_LOGE(TAG, "Begin error: %d", error); - - return OTA_RESPONSE_ERROR_UNKNOWN; -} - -void ArduinoESP32OTABackend::set_update_md5(const char *md5) { - Update.setMD5(md5); - this->md5_set_ = true; -} - -OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) { - size_t written = Update.write(data, len); - if (written == len) { - return OTA_RESPONSE_OK; - } - - uint8_t error = Update.getError(); - ESP_LOGE(TAG, "Write error: %d", error); - - return OTA_RESPONSE_ERROR_WRITING_FLASH; -} - -OTAResponseTypes ArduinoESP32OTABackend::end() { - // Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5 - // This matches the behavior of the old web_server OTA implementation - if (Update.end(!this->md5_set_)) { - return OTA_RESPONSE_OK; - } - - uint8_t error = Update.getError(); - ESP_LOGE(TAG, "End error: %d", error); - - return OTA_RESPONSE_ERROR_UPDATE_END; -} - -void ArduinoESP32OTABackend::abort() { Update.abort(); } - -} // namespace ota -} // namespace esphome - -#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ota/ota_backend_arduino_esp32.h b/esphome/components/ota/ota_backend_arduino_esp32.h deleted file mode 100644 index 6615cf3dc0..0000000000 --- a/esphome/components/ota/ota_backend_arduino_esp32.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#ifdef USE_ESP32_FRAMEWORK_ARDUINO -#include "ota_backend.h" - -#include "esphome/core/defines.h" -#include "esphome/core/helpers.h" - -namespace esphome { -namespace ota { - -class ArduinoESP32OTABackend : public OTABackend { - public: - OTAResponseTypes begin(size_t image_size) override; - void set_update_md5(const char *md5) override; - OTAResponseTypes write(uint8_t *data, size_t len) override; - OTAResponseTypes end() override; - void abort() override; - bool supports_compression() override { return false; } - - private: - bool md5_set_{false}; -}; - -} // namespace ota -} // namespace esphome - -#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp index 97aae09bd9..f278c3741f 100644 --- a/esphome/components/ota/ota_backend_esp_idf.cpp +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -1,4 +1,4 @@ -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "ota_backend_esp_idf.h" #include "esphome/components/md5/md5.h" @@ -107,4 +107,4 @@ void IDFOTABackend::abort() { } // namespace ota } // namespace esphome -#endif +#endif // USE_ESP32 diff --git a/esphome/components/ota/ota_backend_esp_idf.h b/esphome/components/ota/ota_backend_esp_idf.h index 6e93982131..764010e614 100644 --- a/esphome/components/ota/ota_backend_esp_idf.h +++ b/esphome/components/ota/ota_backend_esp_idf.h @@ -1,5 +1,5 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "ota_backend.h" #include "esphome/components/md5/md5.h" @@ -29,4 +29,4 @@ class IDFOTABackend : public OTABackend { } // namespace ota } // namespace esphome -#endif +#endif // USE_ESP32 From c45cd44bb897d6926cbf97d3e615a84ee76ee28a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:49:25 -0600 Subject: [PATCH 0464/1145] Bump github/codeql-action from 4.31.5 to 4.31.6 (#12234) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d10c8bf267..33f587a748 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 + uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -86,6 +86,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 + uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6 with: category: "/language:${{matrix.language}}" From 82a06c697e705199073568e5952dc75fe5c91460 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Dec 2025 21:57:41 -0600 Subject: [PATCH 0465/1145] [esp32] Place ring buffer functions in flash by default (prep for IDF 6.0) (#12184) --- esphome/components/esp32/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index c49fc89fbd..14db25fd46 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -585,6 +585,7 @@ CONF_DISABLE_VFS_SUPPORT_TERMIOS = "disable_vfs_support_termios" CONF_DISABLE_VFS_SUPPORT_SELECT = "disable_vfs_support_select" CONF_DISABLE_VFS_SUPPORT_DIR = "disable_vfs_support_dir" CONF_FREERTOS_IN_IRAM = "freertos_in_iram" +CONF_RINGBUF_IN_IRAM = "ringbuf_in_iram" CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size" # VFS requirement tracking @@ -679,6 +680,7 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_DISABLE_VFS_SUPPORT_SELECT, default=True): cv.boolean, cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean, cv.Optional(CONF_FREERTOS_IN_IRAM, default=False): cv.boolean, + cv.Optional(CONF_RINGBUF_IN_IRAM, default=False): cv.boolean, cv.Optional(CONF_EXECUTE_FROM_PSRAM, default=False): cv.boolean, cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range( min=8192, max=32768 @@ -1021,6 +1023,17 @@ async def to_code(config): # IDF 6.0+: this is the default, option no longer exists add_idf_sdkconfig_option("CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH", True) + # Place ring buffer functions into flash instead of IRAM by default + # This saves IRAM. In ESP-IDF 6.0 flash placement becomes the default. + # Users can set ringbuf_in_iram: true as an escape hatch if they encounter issues. + if conf[CONF_ADVANCED][CONF_RINGBUF_IN_IRAM]: + # User requests ring buffer in IRAM + # IDF 6.0+: will need CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH=n + add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH", False) + else: + # Place in flash to save IRAM (default) + add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH", True) + # Setup watchdog add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True) add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True) From 9a0731437a94c3fb9122249fa7e9154a2a3d2c28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 22:11:33 -0600 Subject: [PATCH 0466/1145] Bump aioesphomeapi from 42.9.0 to 42.10.0 (#12245) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 45ae3d5925..5d824a6859 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==42.9.0 +aioesphomeapi==42.10.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.16 # dashboard_import From 10ddebc737644c44124a9164953dcdaa190c07ad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Dec 2025 22:17:31 -0600 Subject: [PATCH 0467/1145] [text_sensor] Avoid duplicate string storage when no filters configured (#12205) --- .../components/text_sensor/text_sensor.cpp | 12 +- esphome/components/text_sensor/text_sensor.h | 5 +- .../fixtures/text_sensor_raw_state.yaml | 54 +++++++++ .../integration/test_text_sensor_raw_state.py | 114 ++++++++++++++++++ 4 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 tests/integration/fixtures/text_sensor_raw_state.yaml create mode 100644 tests/integration/test_text_sensor_raw_state.py diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index a7bcf19967..d984e78b2a 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -25,7 +25,11 @@ void log_text_sensor(const char *tag, const char *prefix, const char *type, Text } void TextSensor::publish_state(const std::string &state) { - this->raw_state = state; + // Only store raw_state_ separately when filters exist + // When no filters, raw_state == state, so we avoid the duplicate storage + if (this->filter_list_ != nullptr) { + this->raw_state_ = state; + } if (this->raw_callback_) { this->raw_callback_->call(state); } @@ -80,7 +84,11 @@ void TextSensor::add_on_raw_state_callback(std::function call } std::string TextSensor::get_state() const { return this->state; } -std::string TextSensor::get_raw_state() const { return this->raw_state; } +std::string TextSensor::get_raw_state() const { + // When no filters exist, raw_state == state, so return state to avoid + // requiring separate storage + return this->filter_list_ != nullptr ? this->raw_state_ : this->state; +} void TextSensor::internal_send_state_to_frontend(const std::string &state) { this->state = state; this->set_has_state(true); diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index db2e857ae3..fcfbed2fbc 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -50,7 +50,6 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { void add_on_raw_state_callback(std::function callback); std::string state; - std::string raw_state; // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) @@ -63,6 +62,10 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { CallbackManager callback_; ///< Storage for filtered state callbacks. Filter *filter_list_{nullptr}; ///< Store all active filters. + + /// Raw state (before filters). Only populated when filters are configured. + /// When no filters exist, get_raw_state() returns state directly. + std::string raw_state_; }; } // namespace text_sensor diff --git a/tests/integration/fixtures/text_sensor_raw_state.yaml b/tests/integration/fixtures/text_sensor_raw_state.yaml new file mode 100644 index 0000000000..03aece0a04 --- /dev/null +++ b/tests/integration/fixtures/text_sensor_raw_state.yaml @@ -0,0 +1,54 @@ +esphome: + name: test-text-sensor-raw-state + +host: +api: + batch_delay: 0ms # Disable batching to receive all state updates +logger: + level: DEBUG + +# Text sensor WITHOUT filters - get_raw_state() should return same as state +text_sensor: + - platform: template + name: "No Filter Sensor" + id: no_filter_sensor + + # Text sensor WITH filter - get_raw_state() should return original value + - platform: template + name: "With Filter Sensor" + id: with_filter_sensor + filters: + - to_upper + +# Button to publish values and log raw_state vs state +button: + - platform: template + name: "Test No Filter Button" + id: test_no_filter_button + on_press: + - text_sensor.template.publish: + id: no_filter_sensor + state: "hello world" + - delay: 50ms + # Log both state and get_raw_state() to verify they match + - logger.log: + format: "NO_FILTER: state='%s' raw_state='%s'" + args: + - id(no_filter_sensor).state.c_str() + - id(no_filter_sensor).get_raw_state().c_str() + + - platform: template + name: "Test With Filter Button" + id: test_with_filter_button + on_press: + - text_sensor.template.publish: + id: with_filter_sensor + state: "hello world" + - delay: 50ms + # Log both state and get_raw_state() to verify filter works + # state should be "HELLO WORLD" (filtered), raw_state should be "hello world" (original) + - logger.log: + format: "WITH_FILTER: state='%s' raw_state='%s'" + args: + - id(with_filter_sensor).state.c_str() + - id(with_filter_sensor).get_raw_state().c_str() diff --git a/tests/integration/test_text_sensor_raw_state.py b/tests/integration/test_text_sensor_raw_state.py new file mode 100644 index 0000000000..a53ec8c963 --- /dev/null +++ b/tests/integration/test_text_sensor_raw_state.py @@ -0,0 +1,114 @@ +"""Integration test for TextSensor get_raw_state() functionality. + +This tests the optimization in PR #12205 where raw_state is only stored +when filters are configured. When no filters exist, get_raw_state() should +return state directly. +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_text_sensor_raw_state( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that get_raw_state() works correctly with and without filters. + + Without filters: get_raw_state() should return the same value as state + With filters: get_raw_state() should return the original (unfiltered) value + """ + loop = asyncio.get_running_loop() + + # Futures to track log messages + no_filter_future: asyncio.Future[tuple[str, str]] = loop.create_future() + with_filter_future: asyncio.Future[tuple[str, str]] = loop.create_future() + + # Patterns to match log output + # NO_FILTER: state='hello world' raw_state='hello world' + no_filter_pattern = re.compile(r"NO_FILTER: state='([^']*)' raw_state='([^']*)'") + # WITH_FILTER: state='HELLO WORLD' raw_state='hello world' + with_filter_pattern = re.compile( + r"WITH_FILTER: state='([^']*)' raw_state='([^']*)'" + ) + + def check_output(line: str) -> None: + """Check log output for expected messages.""" + if not no_filter_future.done(): + match = no_filter_pattern.search(line) + if match: + no_filter_future.set_result((match.group(1), match.group(2))) + + if not with_filter_future.done(): + match = with_filter_pattern.search(line) + if match: + with_filter_future.set_result((match.group(1), match.group(2))) + + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "test-text-sensor-raw-state" + + # Get entities to find our buttons + entities, _ = await client.list_entities_services() + + # Find the test buttons + no_filter_button = next( + (e for e in entities if "test_no_filter_button" in e.object_id.lower()), + None, + ) + assert no_filter_button is not None, "Test No Filter Button not found" + + with_filter_button = next( + (e for e in entities if "test_with_filter_button" in e.object_id.lower()), + None, + ) + assert with_filter_button is not None, "Test With Filter Button not found" + + # Test 1: Text sensor without filters + # get_raw_state() should return the same as state + client.button_command(no_filter_button.key) + + try: + state, raw_state = await asyncio.wait_for(no_filter_future, timeout=5.0) + except TimeoutError: + pytest.fail("Timeout waiting for NO_FILTER log message") + + assert state == "hello world", f"Expected state='hello world', got '{state}'" + assert raw_state == "hello world", ( + f"Expected raw_state='hello world', got '{raw_state}'" + ) + assert state == raw_state, ( + f"Without filters, state and raw_state should be equal. " + f"state='{state}', raw_state='{raw_state}'" + ) + + # Test 2: Text sensor with to_upper filter + # state should be filtered (uppercase), raw_state should be original + client.button_command(with_filter_button.key) + + try: + state, raw_state = await asyncio.wait_for(with_filter_future, timeout=5.0) + except TimeoutError: + pytest.fail("Timeout waiting for WITH_FILTER log message") + + assert state == "HELLO WORLD", f"Expected state='HELLO WORLD', got '{state}'" + assert raw_state == "hello world", ( + f"Expected raw_state='hello world', got '{raw_state}'" + ) + assert state != raw_state, ( + f"With filters, state and raw_state should differ. " + f"state='{state}', raw_state='{raw_state}'" + ) From 29be1423f55a0d1502835d10ed2784b0f0839042 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 2 Dec 2025 08:59:50 -0500 Subject: [PATCH 0468/1145] [core] Filter noisy platformio log messages (#12218) Co-authored-by: Claude Co-authored-by: J. Nick Koston --- esphome/platformio_api.py | 28 ++++++- tests/unit_tests/test_platformio_api.py | 98 +++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index d59523a74a..4d795ea5d9 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -107,9 +107,24 @@ FILTER_PLATFORMIO_LINES = [ r"Warning: DEPRECATED: 'esptool.py' is deprecated. Please use 'esptool' instead. The '.py' suffix will be removed in a future major release.", r"Warning: esp-idf-size exited with code 2", r"esp_idf_size: error: unrecognized arguments: --ng", + r"Package configuration completed successfully", ] +class PlatformioLogFilter(logging.Filter): + """Filter to suppress noisy platformio log messages.""" + + _PATTERN = re.compile( + r"|".join(r"(?:" + pattern + r")" for pattern in FILTER_PLATFORMIO_LINES) + ) + + def filter(self, record: logging.LogRecord) -> bool: + # Only filter messages from platformio-related loggers + if "platformio" not in record.name.lower(): + return True + return self._PATTERN.match(record.getMessage()) is None + + def run_platformio_cli(*args, **kwargs) -> str | int: os.environ["PLATFORMIO_FORCE_COLOR"] = "true" os.environ["PLATFORMIO_BUILD_DIR"] = str(CORE.relative_pioenvs_path().absolute()) @@ -130,7 +145,18 @@ def run_platformio_cli(*args, **kwargs) -> str | int: patch_structhash() patch_file_downloader() - return run_external_command(platformio.__main__.main, *cmd, **kwargs) + + # Add log filter to suppress noisy platformio messages + log_filter = PlatformioLogFilter() if not CORE.verbose else None + if log_filter: + for handler in logging.getLogger().handlers: + handler.addFilter(log_filter) + try: + return run_external_command(platformio.__main__.main, *cmd, **kwargs) + finally: + if log_filter: + for handler in logging.getLogger().handlers: + handler.removeFilter(log_filter) def run_platformio_cli_run(config, verbose, *args, **kwargs) -> str | int: diff --git a/tests/unit_tests/test_platformio_api.py b/tests/unit_tests/test_platformio_api.py index 13ef3516e4..4d7b635e59 100644 --- a/tests/unit_tests/test_platformio_api.py +++ b/tests/unit_tests/test_platformio_api.py @@ -1,6 +1,7 @@ """Tests for platformio_api.py path functions.""" import json +import logging import os from pathlib import Path import shutil @@ -670,3 +671,100 @@ def test_process_stacktrace_bad_alloc( assert "Memory allocation of 512 bytes failed at 40201234" in caplog.text mock_decode_pc.assert_called_once_with(config, "40201234") assert state is False + + +def test_platformio_log_filter_allows_non_platformio_messages() -> None: + """Test that non-platformio logger messages are allowed through.""" + log_filter = platformio_api.PlatformioLogFilter() + record = logging.LogRecord( + name="esphome.core", + level=logging.INFO, + pathname="", + lineno=0, + msg="Some esphome message", + args=(), + exc_info=None, + ) + assert log_filter.filter(record) is True + + +@pytest.mark.parametrize( + "msg", + [ + "Verbose mode can be enabled via `-v, --verbose` option", + "Found 5 compatible libraries", + "Found 123 compatible libraries", + "Building in release mode", + "Building in debug mode", + "Merged 2 ELF section", + "esptool.py v4.7.0", + "esptool v4.8.1", + "PLATFORM: espressif32 @ 6.4.0", + "Using cache: /path/to/cache", + "Package configuration completed successfully", + "Scanning dependencies...", + "Installing dependencies", + "Library Manager: Already installed, built-in library", + "Memory Usage -> https://bit.ly/pio-memory-usage", + ], +) +def test_platformio_log_filter_blocks_noisy_messages(msg: str) -> None: + """Test that noisy platformio messages are filtered out.""" + log_filter = platformio_api.PlatformioLogFilter() + record = logging.LogRecord( + name="platformio.builder", + level=logging.INFO, + pathname="", + lineno=0, + msg=msg, + args=(), + exc_info=None, + ) + assert log_filter.filter(record) is False + + +@pytest.mark.parametrize( + "msg", + [ + "Compiling .pio/build/test/src/main.cpp.o", + "Linking .pio/build/test/firmware.elf", + "Error: something went wrong", + "warning: unused variable", + ], +) +def test_platformio_log_filter_allows_other_platformio_messages(msg: str) -> None: + """Test that non-noisy platformio messages are allowed through.""" + log_filter = platformio_api.PlatformioLogFilter() + record = logging.LogRecord( + name="platformio.builder", + level=logging.INFO, + pathname="", + lineno=0, + msg=msg, + args=(), + exc_info=None, + ) + assert log_filter.filter(record) is True + + +@pytest.mark.parametrize( + "logger_name", + [ + "PLATFORMIO.builder", + "PlatformIO.core", + "platformio.run", + ], +) +def test_platformio_log_filter_case_insensitive_logger_name(logger_name: str) -> None: + """Test that platformio logger name matching is case insensitive.""" + log_filter = platformio_api.PlatformioLogFilter() + record = logging.LogRecord( + name=logger_name, + level=logging.INFO, + pathname="", + lineno=0, + msg="Found 5 compatible libraries", + args=(), + exc_info=None, + ) + assert log_filter.filter(record) is False From deda7a1bf395a7b51ecafd86303cb31c3aa3179c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Dec 2025 09:59:05 -0600 Subject: [PATCH 0469/1145] [lock] Store lock state strings in flash on ESP8266 (#12163) --- esphome/components/lock/lock.cpp | 21 ++++++++++---------- esphome/components/lock/lock.h | 5 ++++- esphome/components/mqtt/mqtt_lock.cpp | 10 ++++++++-- esphome/components/web_server/web_server.cpp | 7 ++++--- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/esphome/components/lock/lock.cpp b/esphome/components/lock/lock.cpp index b8f0fbe011..018f5113e3 100644 --- a/esphome/components/lock/lock.cpp +++ b/esphome/components/lock/lock.cpp @@ -7,21 +7,21 @@ namespace esphome::lock { static const char *const TAG = "lock"; -const char *lock_state_to_string(LockState state) { +const LogString *lock_state_to_string(LockState state) { switch (state) { case LOCK_STATE_LOCKED: - return "LOCKED"; + return LOG_STR("LOCKED"); case LOCK_STATE_UNLOCKED: - return "UNLOCKED"; + return LOG_STR("UNLOCKED"); case LOCK_STATE_JAMMED: - return "JAMMED"; + return LOG_STR("JAMMED"); case LOCK_STATE_LOCKING: - return "LOCKING"; + return LOG_STR("LOCKING"); case LOCK_STATE_UNLOCKING: - return "UNLOCKING"; + return LOG_STR("UNLOCKING"); case LOCK_STATE_NONE: default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } @@ -52,7 +52,7 @@ void Lock::publish_state(LockState state) { this->state = state; this->rtc_.save(&this->state); - ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), lock_state_to_string(state)); + ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), LOG_STR_ARG(lock_state_to_string(state))); this->state_callback_.call(); #if defined(USE_LOCK) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_lock_update(this); @@ -65,8 +65,7 @@ void LockCall::perform() { ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); this->validate_(); if (this->state_.has_value()) { - const char *state_s = lock_state_to_string(*this->state_); - ESP_LOGD(TAG, " State: %s", state_s); + ESP_LOGD(TAG, " State: %s", LOG_STR_ARG(lock_state_to_string(*this->state_))); } this->parent_->control(*this); } @@ -74,7 +73,7 @@ void LockCall::validate_() { if (this->state_.has_value()) { auto state = *this->state_; if (!this->parent_->traits.supports_state(state)) { - ESP_LOGW(TAG, " State %s is not supported by this device!", lock_state_to_string(*this->state_)); + ESP_LOGW(TAG, " State %s is not supported by this device!", LOG_STR_ARG(lock_state_to_string(*this->state_))); this->state_.reset(); } } diff --git a/esphome/components/lock/lock.h b/esphome/components/lock/lock.h index 8a906ef9fc..4001a182b8 100644 --- a/esphome/components/lock/lock.h +++ b/esphome/components/lock/lock.h @@ -30,7 +30,10 @@ enum LockState : uint8_t { LOCK_STATE_LOCKING = 4, LOCK_STATE_UNLOCKING = 5 }; -const char *lock_state_to_string(LockState state); +const LogString *lock_state_to_string(LockState state); + +/// Maximum length of lock state string (including null terminator): "UNLOCKING" = 10 +static constexpr size_t LOCK_STATE_STR_SIZE = 10; class LockTraits { public: diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index 0e15377ba4..95efbf60e1 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -48,8 +48,14 @@ void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfi bool MQTTLockComponent::send_initial_state() { return this->publish_state(); } bool MQTTLockComponent::publish_state() { - std::string payload = lock_state_to_string(this->lock_->state); - return this->publish(this->get_state_topic_(), payload); +#ifdef USE_STORE_LOG_STR_IN_FLASH + char buf[LOCK_STATE_STR_SIZE]; + strncpy_P(buf, (PGM_P) lock_state_to_string(this->lock_->state), sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + return this->publish(this->get_state_topic_(), buf); +#else + return this->publish(this->get_state_topic_(), LOG_STR_ARG(lock_state_to_string(this->lock_->state))); +#endif } } // namespace mqtt diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index c752c00899..38fa54704a 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1334,7 +1334,7 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf const auto traits = obj->get_traits(); int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals(); int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals(); - char buf[16]; + char buf[PSTR_LOCAL_SIZE]; if (start_config == DETAIL_ALL) { JsonArray opt = root["modes"].to(); @@ -1484,7 +1484,8 @@ std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDet json::JsonBuilder builder; JsonObject root = builder.root(); - set_json_icon_state_value(root, obj, "lock", lock::lock_state_to_string(value), value, start_config); + char buf[PSTR_LOCAL_SIZE]; + set_json_icon_state_value(root, obj, "lock", PSTR_LOCAL(lock::lock_state_to_string(value)), value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); } @@ -1645,7 +1646,7 @@ std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmContro json::JsonBuilder builder; JsonObject root = builder.root(); - char buf[16]; + char buf[PSTR_LOCAL_SIZE]; set_json_icon_state_value(root, obj, "alarm-control-panel", PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config); if (start_config == DETAIL_ALL) { From f9ad832e7bef3c685273ccc971942814b4eda2dd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Dec 2025 09:59:32 -0600 Subject: [PATCH 0470/1145] [esp32_camera] Replace std::function callbacks with CameraListener interface (#12165) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/api/api_server.cpp | 16 ++++--- esphome/components/api/api_server.h | 10 +++++ esphome/components/camera/camera.h | 25 ++++++++--- .../components/esp32_camera/esp32_camera.cpp | 21 ++++----- .../components/esp32_camera/esp32_camera.h | 43 ++++++++----------- .../camera_web_server.cpp | 14 +++--- .../camera_web_server.h | 5 ++- 7 files changed, 78 insertions(+), 56 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index de0c4b24c9..4168761c74 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -107,12 +107,7 @@ void APIServer::setup() { #ifdef USE_CAMERA if (camera::Camera::instance() != nullptr && !camera::Camera::instance()->is_internal()) { - camera::Camera::instance()->add_image_callback([this](const std::shared_ptr &image) { - for (auto &c : this->clients_) { - if (!c->flags_.remove) - c->set_camera_state(image); - } - }); + camera::Camera::instance()->add_listener(this); } #endif } @@ -544,6 +539,15 @@ void APIServer::on_log(uint8_t level, const char *tag, const char *message, size } #endif +#ifdef USE_CAMERA +void APIServer::on_camera_image(const std::shared_ptr &image) { + for (auto &c : this->clients_) { + if (!c->flags_.remove) + c->set_camera_state(image); + } +} +#endif + void APIServer::on_shutdown() { this->shutting_down_ = true; diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 57aea6ad0e..3089bb1d35 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -18,6 +18,9 @@ #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" #endif +#ifdef USE_CAMERA +#include "esphome/components/camera/camera.h" +#endif #include #include @@ -36,6 +39,10 @@ class APIServer : public Component, , public logger::LogListener #endif +#ifdef USE_CAMERA + , + public camera::CameraListener +#endif { public: APIServer(); @@ -49,6 +56,9 @@ class APIServer : public Component, #ifdef USE_LOGGER void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; #endif +#ifdef USE_CAMERA + void on_camera_image(const std::shared_ptr &image) override; +#endif #ifdef USE_API_PASSWORD bool check_password(const uint8_t *password_data, size_t password_len) const; void set_password(const std::string &password); diff --git a/esphome/components/camera/camera.h b/esphome/components/camera/camera.h index c28a756a06..6e1fc8cc06 100644 --- a/esphome/components/camera/camera.h +++ b/esphome/components/camera/camera.h @@ -35,6 +35,21 @@ inline const char *to_string(PixelFormat format) { return "PIXEL_FORMAT_UNKNOWN"; } +// Forward declaration +class CameraImage; + +/** Listener interface for camera events. + * + * Components can implement this interface to receive camera notifications + * (new images, stream start/stop) without the overhead of std::function callbacks. + */ +class CameraListener { + public: + virtual void on_camera_image(const std::shared_ptr &image) {} + virtual void on_stream_start() {} + virtual void on_stream_stop() {} +}; + /** Abstract camera image base class. * Encapsulates the JPEG encoded data and it is shared among * all connected clients. @@ -87,12 +102,12 @@ struct CameraImageSpec { }; /** Abstract camera base class. Collaborates with API. - * 1) API server starts and installs callback (add_image_callback) - * which is called by the camera when a new image is available. + * 1) API server starts and registers as a listener (add_listener) + * to receive new images from the camera. * 2) New API client connects and creates a new image reader (create_image_reader). * 3) API connection receives protobuf CameraImageRequest and calls request_image. * 3.a) API connection receives protobuf CameraImageRequest and calls start_stream. - * 4) Camera implementation provides JPEG data in the CameraImage and calls callback. + * 4) Camera implementation provides JPEG data in the CameraImage and notifies listeners. * 5) API connection sets the image in the image reader. * 6) API connection consumes data from the image reader and returns the image when finished. * 7.a) Camera captures a new image and continues with 4) until start_stream is called. @@ -100,8 +115,8 @@ struct CameraImageSpec { class Camera : public EntityBase, public Component { public: Camera(); - // Camera implementation invokes callback to publish a new image. - virtual void add_image_callback(std::function)> &&callback) = 0; + /// Add a listener to receive camera events + virtual void add_listener(CameraListener *listener) = 0; /// Returns a new camera image reader that keeps track of the JPEG data in the camera image. virtual CameraImageReader *create_image_reader() = 0; // Connection, camera or web server requests one new JPEG image. diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 38bd8d5822..5080a6f32d 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -205,7 +205,9 @@ void ESP32Camera::loop() { this->current_image_ = std::make_shared(fb, this->single_requesters_ | this->stream_requesters_); ESP_LOGD(TAG, "Got Image: len=%u", fb->len); - this->new_image_callback_.call(this->current_image_); + for (auto *listener : this->listeners_) { + listener->on_camera_image(this->current_image_); + } this->last_update_ = now; this->single_requesters_ = 0; } @@ -357,21 +359,16 @@ void ESP32Camera::set_frame_buffer_location(camera_fb_location_t fb_location) { } /* ---------------- public API (specific) ---------------- */ -void ESP32Camera::add_image_callback(std::function)> &&callback) { - this->new_image_callback_.add(std::move(callback)); -} -void ESP32Camera::add_stream_start_callback(std::function &&callback) { - this->stream_start_callback_.add(std::move(callback)); -} -void ESP32Camera::add_stream_stop_callback(std::function &&callback) { - this->stream_stop_callback_.add(std::move(callback)); -} void ESP32Camera::start_stream(camera::CameraRequester requester) { - this->stream_start_callback_.call(); + for (auto *listener : this->listeners_) { + listener->on_stream_start(); + } this->stream_requesters_ |= (1U << requester); } void ESP32Camera::stop_stream(camera::CameraRequester requester) { - this->stream_stop_callback_.call(); + for (auto *listener : this->listeners_) { + listener->on_stream_stop(); + } this->stream_requesters_ &= ~(1U << requester); } void ESP32Camera::request_image(camera::CameraRequester requester) { this->single_requesters_ |= (1U << requester); } diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 0e7f7c0ea6..54a7d6064a 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -165,9 +165,8 @@ class ESP32Camera : public camera::Camera { void request_image(camera::CameraRequester requester) override; void update_camera_parameters(); - void add_image_callback(std::function)> &&callback) override; - void add_stream_start_callback(std::function &&callback); - void add_stream_stop_callback(std::function &&callback); + /// Add a listener to receive camera events + void add_listener(camera::CameraListener *listener) override { this->listeners_.push_back(listener); } camera::CameraImageReader *create_image_reader() override; protected: @@ -210,9 +209,7 @@ class ESP32Camera : public camera::Camera { uint8_t stream_requesters_{0}; QueueHandle_t framebuffer_get_queue_; QueueHandle_t framebuffer_return_queue_; - CallbackManager)> new_image_callback_{}; - CallbackManager stream_start_callback_{}; - CallbackManager stream_stop_callback_{}; + std::vector listeners_; uint32_t last_idle_request_{0}; uint32_t last_update_{0}; @@ -221,33 +218,27 @@ class ESP32Camera : public camera::Camera { #endif // USE_I2C }; -class ESP32CameraImageTrigger : public Trigger { +class ESP32CameraImageTrigger : public Trigger, public camera::CameraListener { public: - explicit ESP32CameraImageTrigger(ESP32Camera *parent) { - parent->add_image_callback([this](const std::shared_ptr &image) { - CameraImageData camera_image_data{}; - camera_image_data.length = image->get_data_length(); - camera_image_data.data = image->get_data_buffer(); - this->trigger(camera_image_data); - }); + explicit ESP32CameraImageTrigger(ESP32Camera *parent) { parent->add_listener(this); } + void on_camera_image(const std::shared_ptr &image) override { + CameraImageData camera_image_data{}; + camera_image_data.length = image->get_data_length(); + camera_image_data.data = image->get_data_buffer(); + this->trigger(camera_image_data); } }; -class ESP32CameraStreamStartTrigger : public Trigger<> { +class ESP32CameraStreamStartTrigger : public Trigger<>, public camera::CameraListener { public: - explicit ESP32CameraStreamStartTrigger(ESP32Camera *parent) { - parent->add_stream_start_callback([this]() { this->trigger(); }); - } - - protected: + explicit ESP32CameraStreamStartTrigger(ESP32Camera *parent) { parent->add_listener(this); } + void on_stream_start() override { this->trigger(); } }; -class ESP32CameraStreamStopTrigger : public Trigger<> { - public: - explicit ESP32CameraStreamStopTrigger(ESP32Camera *parent) { - parent->add_stream_stop_callback([this]() { this->trigger(); }); - } - protected: +class ESP32CameraStreamStopTrigger : public Trigger<>, public camera::CameraListener { + public: + explicit ESP32CameraStreamStopTrigger(ESP32Camera *parent) { parent->add_listener(this); } + void on_stream_stop() override { this->trigger(); } }; } // namespace esp32_camera diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp index 1b81989296..f49578c425 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -67,12 +67,14 @@ void CameraWebServer::setup() { httpd_register_uri_handler(this->httpd_, &uri); - camera::Camera::instance()->add_image_callback([this](std::shared_ptr image) { - if (this->running_ && image->was_requested_by(camera::WEB_REQUESTER)) { - this->image_ = std::move(image); - xSemaphoreGive(this->semaphore_); - } - }); + camera::Camera::instance()->add_listener(this); +} + +void CameraWebServer::on_camera_image(const std::shared_ptr &image) { + if (this->running_ && image->was_requested_by(camera::WEB_REQUESTER)) { + this->image_ = image; + xSemaphoreGive(this->semaphore_); + } } void CameraWebServer::on_shutdown() { diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.h b/esphome/components/esp32_camera_web_server/camera_web_server.h index e70246745c..ad7b29fb11 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.h +++ b/esphome/components/esp32_camera_web_server/camera_web_server.h @@ -18,7 +18,7 @@ namespace esp32_camera_web_server { enum Mode { STREAM, SNAPSHOT }; -class CameraWebServer : public Component { +class CameraWebServer : public Component, public camera::CameraListener { public: CameraWebServer(); ~CameraWebServer(); @@ -31,6 +31,9 @@ class CameraWebServer : public Component { void set_mode(Mode mode) { this->mode_ = mode; } void loop() override; + /// CameraListener interface + void on_camera_image(const std::shared_ptr &image) override; + protected: std::shared_ptr wait_for_image_(); esp_err_t handler_(struct httpd_req *req); From 5142ff372bb389c3df9d73830930db325272037c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Dec 2025 10:01:54 -0600 Subject: [PATCH 0471/1145] [light] Use listener pattern for state callbacks with lazy allocation (#12166) --- esphome/components/light/automation.h | 62 ++++++----- esphome/components/light/light_call.cpp | 6 +- esphome/components/light/light_state.cpp | 26 +++-- esphome/components/light/light_state.h | 58 +++++++--- esphome/components/mqtt/mqtt_light.cpp | 7 +- esphome/components/mqtt/mqtt_light.h | 5 +- .../fixtures/light_automations.yaml | 26 +++++ tests/integration/test_light_automations.py | 101 ++++++++++++++++++ 8 files changed, 236 insertions(+), 55 deletions(-) create mode 100644 tests/integration/fixtures/light_automations.yaml create mode 100644 tests/integration/test_light_automations.py diff --git a/esphome/components/light/automation.h b/esphome/components/light/automation.h index 9893c15e0c..c90d71c5df 100644 --- a/esphome/components/light/automation.h +++ b/esphome/components/light/automation.h @@ -120,46 +120,54 @@ template class LightIsOffCondition : public Condition { LightState *state_; }; -class LightTurnOnTrigger : public Trigger<> { +class LightTurnOnTrigger : public Trigger<>, public LightRemoteValuesListener { public: - LightTurnOnTrigger(LightState *a_light) { - a_light->add_new_remote_values_callback([this, a_light]() { - // using the remote value because of transitions we need to trigger as early as possible - auto is_on = a_light->remote_values.is_on(); - // only trigger when going from off to on - auto should_trigger = is_on && !this->last_on_; - // Set new state immediately so that trigger() doesn't devolve - // into infinite loop - this->last_on_ = is_on; - if (should_trigger) { - this->trigger(); - } - }); + explicit LightTurnOnTrigger(LightState *a_light) : light_(a_light) { + a_light->add_remote_values_listener(this); this->last_on_ = a_light->current_values.is_on(); } + void on_light_remote_values_update() override { + // using the remote value because of transitions we need to trigger as early as possible + auto is_on = this->light_->remote_values.is_on(); + // only trigger when going from off to on + auto should_trigger = is_on && !this->last_on_; + // Set new state immediately so that trigger() doesn't devolve + // into infinite loop + this->last_on_ = is_on; + if (should_trigger) { + this->trigger(); + } + } + protected: + LightState *light_; bool last_on_; }; -class LightTurnOffTrigger : public Trigger<> { +class LightTurnOffTrigger : public Trigger<>, public LightTargetStateReachedListener { public: - LightTurnOffTrigger(LightState *a_light) { - a_light->add_new_target_state_reached_callback([this, a_light]() { - auto is_on = a_light->current_values.is_on(); - // only trigger when going from on to off - if (!is_on) { - this->trigger(); - } - }); + explicit LightTurnOffTrigger(LightState *a_light) : light_(a_light) { + a_light->add_target_state_reached_listener(this); } + + void on_light_target_state_reached() override { + auto is_on = this->light_->current_values.is_on(); + // only trigger when going from on to off + if (!is_on) { + this->trigger(); + } + } + + protected: + LightState *light_; }; -class LightStateTrigger : public Trigger<> { +class LightStateTrigger : public Trigger<>, public LightRemoteValuesListener { public: - LightStateTrigger(LightState *a_light) { - a_light->add_new_remote_values_callback([this]() { this->trigger(); }); - } + explicit LightStateTrigger(LightState *a_light) { a_light->add_remote_values_listener(this); } + + void on_light_remote_values_update() override { this->trigger(); } }; // This is slightly ugly, but we can't log in headers, and can't make this a static method on AddressableSet diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index f523b4451b..dca5861734 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -174,8 +174,10 @@ void LightCall::perform() { this->parent_->set_immediately_(v, publish); } - if (!this->has_transition_()) { - this->parent_->target_state_reached_callback_.call(); + if (!this->has_transition_() && this->parent_->target_state_reached_listeners_) { + for (auto *listener : *this->parent_->target_state_reached_listeners_) { + listener->on_light_target_state_reached(); + } } if (publish) { this->parent_->publish_state(); diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 9cde9077da..af619a426a 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -127,7 +127,11 @@ void LightState::loop() { this->transformer_->stop(); this->is_transformer_active_ = false; this->transformer_ = nullptr; - this->target_state_reached_callback_.call(); + if (this->target_state_reached_listeners_) { + for (auto *listener : *this->target_state_reached_listeners_) { + listener->on_light_target_state_reached(); + } + } // Disable loop if idle (no transformer and no effect) this->disable_loop_if_idle_(); @@ -146,7 +150,11 @@ void LightState::loop() { float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } void LightState::publish_state() { - this->remote_values_callback_.call(); + if (this->remote_values_listeners_) { + for (auto *listener : *this->remote_values_listeners_) { + listener->on_light_remote_values_update(); + } + } #if defined(USE_LIGHT) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_light_update(this); #endif @@ -171,11 +179,17 @@ StringRef LightState::get_effect_name_ref() { return EFFECT_NONE_REF; } -void LightState::add_new_remote_values_callback(std::function &&send_callback) { - this->remote_values_callback_.add(std::move(send_callback)); +void LightState::add_remote_values_listener(LightRemoteValuesListener *listener) { + if (!this->remote_values_listeners_) { + this->remote_values_listeners_ = make_unique>(); + } + this->remote_values_listeners_->push_back(listener); } -void LightState::add_new_target_state_reached_callback(std::function &&send_callback) { - this->target_state_reached_callback_.add(std::move(send_callback)); +void LightState::add_target_state_reached_listener(LightTargetStateReachedListener *listener) { + if (!this->target_state_reached_listeners_) { + this->target_state_reached_listeners_ = make_unique>(); + } + this->target_state_reached_listeners_->push_back(listener); } void LightState::set_default_transition_length(uint32_t default_transition_length) { diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index ad8922b46f..7ea72306f9 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -18,6 +18,29 @@ namespace esphome::light { class LightOutput; +class LightState; + +/** Listener interface for light remote value changes. + * + * Components can implement this interface to receive notifications + * when the light's remote values change (state, brightness, color, etc.) + * without the overhead of std::function callbacks. + */ +class LightRemoteValuesListener { + public: + virtual void on_light_remote_values_update() = 0; +}; + +/** Listener interface for light target state reached. + * + * Components can implement this interface to receive notifications + * when the light finishes a transition and reaches its target state + * without the overhead of std::function callbacks. + */ +class LightTargetStateReachedListener { + public: + virtual void on_light_target_state_reached() = 0; +}; enum LightRestoreMode : uint8_t { LIGHT_RESTORE_DEFAULT_OFF, @@ -121,21 +144,17 @@ class LightState : public EntityBase, public Component { /// Return the name of the current effect as StringRef (for API usage) StringRef get_effect_name_ref(); - /** - * This lets front-end components subscribe to light change events. This callback is called once - * when the remote color values are changed. - * - * @param send_callback The callback. + /** Add a listener for remote values changes. + * Listener is notified when the light's remote values change (state, brightness, color, etc.) + * Lazily allocates the listener vector on first registration. */ - void add_new_remote_values_callback(std::function &&send_callback); + void add_remote_values_listener(LightRemoteValuesListener *listener); - /** - * The callback is called once the state of current_values and remote_values are equal (when the - * transition is finished). - * - * @param send_callback + /** Add a listener for target state reached. + * Listener is notified when the light finishes a transition and reaches its target state. + * Lazily allocates the listener vector on first registration. */ - void add_new_target_state_reached_callback(std::function &&send_callback); + void add_target_state_reached_listener(LightTargetStateReachedListener *listener); /// Set the default transition length, i.e. the transition length when no transition is provided. void set_default_transition_length(uint32_t default_transition_length); @@ -279,19 +298,24 @@ class LightState : public EntityBase, public Component { // for effects, true if a transformer (transition) is active. bool is_transformer_active_ = false; - /** Callback to call when new values for the frontend are available. + /** Listeners for remote values changes. * * "Remote values" are light color values that are reported to the frontend and have a lower * publish frequency than the "real" color values. For example, during transitions the current * color value may change continuously, but the remote values will be reported as the target values * starting with the beginning of the transition. + * + * Lazily allocated - only created when a listener is actually registered. */ - CallbackManager remote_values_callback_{}; + std::unique_ptr> remote_values_listeners_; - /** Callback to call when the state of current_values and remote_values are equal - * This should be called once the state of current_values changed and equals the state of remote_values + /** Listeners for target state reached. + * Notified when the state of current_values and remote_values are equal + * (when the transition is finished). + * + * Lazily allocated - only created when a listener is actually registered. */ - CallbackManager target_state_reached_callback_{}; + std::unique_ptr> target_state_reached_listeners_; /// Initial state of the light. optional initial_state_{}; diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index 883b67ffc6..fe911bfba2 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -25,8 +25,11 @@ void MQTTJSONLightComponent::setup() { call.perform(); }); - auto f = std::bind(&MQTTJSONLightComponent::publish_state_, this); - this->state_->add_new_remote_values_callback([this, f]() { this->defer("send", f); }); + this->state_->add_remote_values_listener(this); +} + +void MQTTJSONLightComponent::on_light_remote_values_update() { + this->defer("send", [this]() { this->publish_state_(); }); } MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : state_(state) {} diff --git a/esphome/components/mqtt/mqtt_light.h b/esphome/components/mqtt/mqtt_light.h index 3d1e770d4d..a105f3d7b8 100644 --- a/esphome/components/mqtt/mqtt_light.h +++ b/esphome/components/mqtt/mqtt_light.h @@ -11,7 +11,7 @@ namespace esphome { namespace mqtt { -class MQTTJSONLightComponent : public mqtt::MQTTComponent { +class MQTTJSONLightComponent : public mqtt::MQTTComponent, public light::LightRemoteValuesListener { public: explicit MQTTJSONLightComponent(light::LightState *state); @@ -25,6 +25,9 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent { bool send_initial_state() override; + // LightRemoteValuesListener interface + void on_light_remote_values_update() override; + protected: std::string component_type() const override; const EntityBase *get_entity() const override; diff --git a/tests/integration/fixtures/light_automations.yaml b/tests/integration/fixtures/light_automations.yaml new file mode 100644 index 0000000000..b5b88d95e7 --- /dev/null +++ b/tests/integration/fixtures/light_automations.yaml @@ -0,0 +1,26 @@ +esphome: + name: light-automations-test + +host: +api: # Port will be automatically injected +logger: + level: DEBUG + +output: + - platform: template + id: test_output + type: binary + write_action: + - lambda: "" + +light: + - platform: binary + id: test_light + name: "Test Light" + output: test_output + on_turn_on: + - logger.log: "TRIGGER: on_turn_on fired" + on_turn_off: + - logger.log: "TRIGGER: on_turn_off fired" + on_state: + - logger.log: "TRIGGER: on_state fired" diff --git a/tests/integration/test_light_automations.py b/tests/integration/test_light_automations.py new file mode 100644 index 0000000000..9ff334548a --- /dev/null +++ b/tests/integration/test_light_automations.py @@ -0,0 +1,101 @@ +"""Integration test for light automation triggers. + +Tests that on_turn_on, on_turn_off, and on_state triggers work correctly +with the listener interface pattern. +""" + +import asyncio + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_light_automations( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test light on_turn_on, on_turn_off, and on_state triggers.""" + loop = asyncio.get_running_loop() + + # Futures for log line detection + on_turn_on_future: asyncio.Future[bool] = loop.create_future() + on_turn_off_future: asyncio.Future[bool] = loop.create_future() + on_state_count = 0 + counting_enabled = False + on_state_futures: list[asyncio.Future[bool]] = [] + + def create_on_state_future() -> asyncio.Future[bool]: + """Create a new future for on_state trigger.""" + future: asyncio.Future[bool] = loop.create_future() + on_state_futures.append(future) + return future + + def check_output(line: str) -> None: + """Check log output for trigger messages.""" + nonlocal on_state_count + if "TRIGGER: on_turn_on fired" in line: + if not on_turn_on_future.done(): + on_turn_on_future.set_result(True) + elif "TRIGGER: on_turn_off fired" in line: + if not on_turn_off_future.done(): + on_turn_off_future.set_result(True) + elif "TRIGGER: on_state fired" in line: + # Only count on_state after we start testing + if counting_enabled: + on_state_count += 1 + # Complete any pending on_state futures + for future in on_state_futures: + if not future.done(): + future.set_result(True) + break + + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Get entities + entities = await client.list_entities_services() + light = next(e for e in entities[0] if e.object_id == "test_light") + + # Start counting on_state events now + counting_enabled = True + + # Test 1: Turn light on - should trigger on_turn_on and on_state + on_state_future_1 = create_on_state_future() + client.light_command(key=light.key, state=True) + + # Wait for on_turn_on trigger + try: + await asyncio.wait_for(on_turn_on_future, timeout=5.0) + except TimeoutError: + pytest.fail("on_turn_on trigger did not fire") + + # Wait for on_state trigger + try: + await asyncio.wait_for(on_state_future_1, timeout=5.0) + except TimeoutError: + pytest.fail("on_state trigger did not fire after turn on") + + # Test 2: Turn light off - should trigger on_turn_off and on_state + on_state_future_2 = create_on_state_future() + client.light_command(key=light.key, state=False) + + # Wait for on_turn_off trigger + try: + await asyncio.wait_for(on_turn_off_future, timeout=5.0) + except TimeoutError: + pytest.fail("on_turn_off trigger did not fire") + + # Wait for on_state trigger + try: + await asyncio.wait_for(on_state_future_2, timeout=5.0) + except TimeoutError: + pytest.fail("on_state trigger did not fire after turn off") + + # Verify on_state fired exactly twice (once for on, once for off) + assert on_state_count == 2, ( + f"on_state should have triggered exactly twice, got {on_state_count}" + ) From 101103c66639bba53309fab3caec4992128b89e4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Dec 2025 10:02:09 -0600 Subject: [PATCH 0472/1145] [core] Add RAM strings and symbols analysis to analyze-memory command (#12161) --- esphome/__main__.py | 22 +- esphome/analyze_memory/__init__.py | 173 +-------- esphome/analyze_memory/demangle.py | 182 ++++++++++ esphome/analyze_memory/ram_strings.py | 493 ++++++++++++++++++++++++++ esphome/analyze_memory/toolchain.py | 57 +++ tests/unit_tests/test_main.py | 25 +- 6 files changed, 779 insertions(+), 173 deletions(-) create mode 100644 esphome/analyze_memory/demangle.py create mode 100644 esphome/analyze_memory/ram_strings.py create mode 100644 esphome/analyze_memory/toolchain.py diff --git a/esphome/__main__.py b/esphome/__main__.py index f8fb678cb2..55fbbc6c8a 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -944,6 +944,7 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int: """ from esphome import platformio_api from esphome.analyze_memory.cli import MemoryAnalyzerCLI + from esphome.analyze_memory.ram_strings import RamStringsAnalyzer # Always compile to ensure fresh data (fast if no changes - just relinks) exit_code = write_cpp(config) @@ -966,7 +967,7 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int: external_components = detect_external_components(config) _LOGGER.debug("Detected external components: %s", external_components) - # Perform memory analysis + # Perform component memory analysis _LOGGER.info("Analyzing memory usage...") analyzer = MemoryAnalyzerCLI( str(firmware_elf), @@ -976,11 +977,28 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int: ) analyzer.analyze() - # Generate and display report + # Generate and display component report report = analyzer.generate_report() print() print(report) + # Perform RAM strings analysis + _LOGGER.info("Analyzing RAM strings...") + try: + ram_analyzer = RamStringsAnalyzer( + str(firmware_elf), + objdump_path=idedata.objdump_path, + platform=CORE.target_platform, + ) + ram_analyzer.analyze() + + # Generate and display RAM strings report + ram_report = ram_analyzer.generate_report() + print() + print(ram_report) + except Exception as e: # pylint: disable=broad-except + _LOGGER.warning("RAM strings analysis failed: %s", e) + return 0 diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index 71e86e3788..9632a68913 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -15,6 +15,7 @@ from .const import ( SECTION_TO_ATTR, SYMBOL_PATTERNS, ) +from .demangle import batch_demangle from .helpers import ( get_component_class_patterns, get_esphome_components, @@ -27,15 +28,6 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) -# GCC global constructor/destructor prefix annotations -_GCC_PREFIX_ANNOTATIONS = { - "_GLOBAL__sub_I_": "global constructor for", - "_GLOBAL__sub_D_": "global destructor for", -} - -# GCC optimization suffix pattern (e.g., $isra$0, $part$1, $constprop$2) -_GCC_OPTIMIZATION_SUFFIX_PATTERN = re.compile(r"(\$(?:isra|part|constprop)\$\d+)") - # C++ runtime patterns for categorization _CPP_RUNTIME_PATTERNS = frozenset(["vtable", "typeinfo", "thunk"]) @@ -312,168 +304,9 @@ class MemoryAnalyzer: if not symbols: return - # Try to find the appropriate c++filt for the platform - cppfilt_cmd = "c++filt" - _LOGGER.info("Demangling %d symbols", len(symbols)) - _LOGGER.debug("objdump_path = %s", self.objdump_path) - - # Check if we have a toolchain-specific c++filt - if self.objdump_path and self.objdump_path != "objdump": - # Replace objdump with c++filt in the path - potential_cppfilt = self.objdump_path.replace("objdump", "c++filt") - _LOGGER.info("Checking for toolchain c++filt at: %s", potential_cppfilt) - if Path(potential_cppfilt).exists(): - cppfilt_cmd = potential_cppfilt - _LOGGER.info("✓ Using toolchain c++filt: %s", cppfilt_cmd) - else: - _LOGGER.info( - "✗ Toolchain c++filt not found at %s, using system c++filt", - potential_cppfilt, - ) - else: - _LOGGER.info("✗ Using system c++filt (objdump_path=%s)", self.objdump_path) - - # Strip GCC optimization suffixes and prefixes before demangling - # Suffixes like $isra$0, $part$0, $constprop$0 confuse c++filt - # Prefixes like _GLOBAL__sub_I_ need to be removed and tracked - symbols_stripped: list[str] = [] - symbols_prefixes: list[str] = [] # Track removed prefixes - for symbol in symbols: - # Remove GCC optimization markers - stripped = _GCC_OPTIMIZATION_SUFFIX_PATTERN.sub("", symbol) - - # Handle GCC global constructor/initializer prefixes - # _GLOBAL__sub_I_ -> extract for demangling - prefix = "" - for gcc_prefix in _GCC_PREFIX_ANNOTATIONS: - if stripped.startswith(gcc_prefix): - prefix = gcc_prefix - stripped = stripped[len(prefix) :] - break - - symbols_stripped.append(stripped) - symbols_prefixes.append(prefix) - - try: - # Send all symbols to c++filt at once - result = subprocess.run( - [cppfilt_cmd], - input="\n".join(symbols_stripped), - capture_output=True, - text=True, - check=False, - ) - except (subprocess.SubprocessError, OSError, UnicodeDecodeError) as e: - # On error, cache originals - _LOGGER.warning("Failed to batch demangle symbols: %s", e) - for symbol in symbols: - self._demangle_cache[symbol] = symbol - return - - if result.returncode != 0: - _LOGGER.warning( - "c++filt exited with code %d: %s", - result.returncode, - result.stderr[:200] if result.stderr else "(no error output)", - ) - # Cache originals on failure - for symbol in symbols: - self._demangle_cache[symbol] = symbol - return - - # Process demangled output - self._process_demangled_output( - symbols, symbols_stripped, symbols_prefixes, result.stdout, cppfilt_cmd - ) - - def _process_demangled_output( - self, - symbols: list[str], - symbols_stripped: list[str], - symbols_prefixes: list[str], - demangled_output: str, - cppfilt_cmd: str, - ) -> None: - """Process demangled symbol output and populate cache. - - Args: - symbols: Original symbol names - symbols_stripped: Stripped symbol names sent to c++filt - symbols_prefixes: Removed prefixes to restore - demangled_output: Output from c++filt - cppfilt_cmd: Path to c++filt command (for logging) - """ - demangled_lines = demangled_output.strip().split("\n") - failed_count = 0 - - for original, stripped, prefix, demangled in zip( - symbols, symbols_stripped, symbols_prefixes, demangled_lines - ): - # Add back any prefix that was removed - demangled = self._restore_symbol_prefix(prefix, stripped, demangled) - - # If we stripped a suffix, add it back to the demangled name for clarity - if original != stripped and not prefix: - demangled = self._restore_symbol_suffix(original, demangled) - - self._demangle_cache[original] = demangled - - # Log symbols that failed to demangle (stayed the same as stripped version) - if stripped == demangled and stripped.startswith("_Z"): - failed_count += 1 - if failed_count <= 5: # Only log first 5 failures - _LOGGER.warning("Failed to demangle: %s", original) - - if failed_count == 0: - _LOGGER.info("Successfully demangled all %d symbols", len(symbols)) - return - - _LOGGER.warning( - "Failed to demangle %d/%d symbols using %s", - failed_count, - len(symbols), - cppfilt_cmd, - ) - - @staticmethod - def _restore_symbol_prefix(prefix: str, stripped: str, demangled: str) -> str: - """Restore prefix that was removed before demangling. - - Args: - prefix: Prefix that was removed (e.g., "_GLOBAL__sub_I_") - stripped: Stripped symbol name - demangled: Demangled symbol name - - Returns: - Demangled name with prefix restored/annotated - """ - if not prefix: - return demangled - - # Successfully demangled - add descriptive prefix - if demangled != stripped and ( - annotation := _GCC_PREFIX_ANNOTATIONS.get(prefix) - ): - return f"[{annotation}: {demangled}]" - - # Failed to demangle - restore original prefix - return prefix + demangled - - @staticmethod - def _restore_symbol_suffix(original: str, demangled: str) -> str: - """Restore GCC optimization suffix that was removed before demangling. - - Args: - original: Original symbol name with suffix - demangled: Demangled symbol name without suffix - - Returns: - Demangled name with suffix annotation - """ - if suffix_match := _GCC_OPTIMIZATION_SUFFIX_PATTERN.search(original): - return f"{demangled} [{suffix_match.group(1)}]" - return demangled + self._demangle_cache = batch_demangle(symbols, objdump_path=self.objdump_path) + _LOGGER.info("Successfully demangled %d symbols", len(self._demangle_cache)) def _demangle_symbol(self, symbol: str) -> str: """Get demangled C++ symbol name from cache.""" diff --git a/esphome/analyze_memory/demangle.py b/esphome/analyze_memory/demangle.py new file mode 100644 index 0000000000..8999108b51 --- /dev/null +++ b/esphome/analyze_memory/demangle.py @@ -0,0 +1,182 @@ +"""Symbol demangling utilities for memory analysis. + +This module provides functions for demangling C++ symbol names using c++filt. +""" + +from __future__ import annotations + +import logging +import re +import subprocess + +from .toolchain import find_tool + +_LOGGER = logging.getLogger(__name__) + +# GCC global constructor/destructor prefix annotations +GCC_PREFIX_ANNOTATIONS = { + "_GLOBAL__sub_I_": "global constructor for", + "_GLOBAL__sub_D_": "global destructor for", +} + +# GCC optimization suffix pattern (e.g., $isra$0, $part$1, $constprop$2) +GCC_OPTIMIZATION_SUFFIX_PATTERN = re.compile(r"(\$(?:isra|part|constprop)\$\d+)") + + +def _strip_gcc_annotations(symbol: str) -> tuple[str, str]: + """Strip GCC optimization suffixes and prefixes from a symbol. + + Args: + symbol: The mangled symbol name + + Returns: + Tuple of (stripped_symbol, removed_prefix) + """ + # Remove GCC optimization markers + stripped = GCC_OPTIMIZATION_SUFFIX_PATTERN.sub("", symbol) + + # Handle GCC global constructor/initializer prefixes + prefix = "" + for gcc_prefix in GCC_PREFIX_ANNOTATIONS: + if stripped.startswith(gcc_prefix): + prefix = gcc_prefix + stripped = stripped[len(prefix) :] + break + + return stripped, prefix + + +def _restore_symbol_prefix(prefix: str, stripped: str, demangled: str) -> str: + """Restore prefix that was removed before demangling. + + Args: + prefix: Prefix that was removed (e.g., "_GLOBAL__sub_I_") + stripped: Stripped symbol name + demangled: Demangled symbol name + + Returns: + Demangled name with prefix restored/annotated + """ + if not prefix: + return demangled + + # Successfully demangled - add descriptive prefix + if demangled != stripped and (annotation := GCC_PREFIX_ANNOTATIONS.get(prefix)): + return f"[{annotation}: {demangled}]" + + # Failed to demangle - restore original prefix + return prefix + demangled + + +def _restore_symbol_suffix(original: str, demangled: str) -> str: + """Restore GCC optimization suffix that was removed before demangling. + + Args: + original: Original symbol name with suffix + demangled: Demangled symbol name without suffix + + Returns: + Demangled name with suffix annotation + """ + if suffix_match := GCC_OPTIMIZATION_SUFFIX_PATTERN.search(original): + return f"{demangled} [{suffix_match.group(1)}]" + return demangled + + +def batch_demangle( + symbols: list[str], + cppfilt_path: str | None = None, + objdump_path: str | None = None, +) -> dict[str, str]: + """Batch demangle C++ symbol names. + + Args: + symbols: List of symbol names to demangle + cppfilt_path: Path to c++filt binary (auto-detected if not provided) + objdump_path: Path to objdump binary to derive c++filt path from + + Returns: + Dictionary mapping original symbol names to demangled names + """ + cache: dict[str, str] = {} + + if not symbols: + return cache + + # Find c++filt tool + cppfilt_cmd = cppfilt_path or find_tool("c++filt", objdump_path) + if not cppfilt_cmd: + _LOGGER.warning("Could not find c++filt, symbols will not be demangled") + return {s: s for s in symbols} + + _LOGGER.debug("Demangling %d symbols using %s", len(symbols), cppfilt_cmd) + + # Strip GCC optimization suffixes and prefixes before demangling + symbols_stripped: list[str] = [] + symbols_prefixes: list[str] = [] + for symbol in symbols: + stripped, prefix = _strip_gcc_annotations(symbol) + symbols_stripped.append(stripped) + symbols_prefixes.append(prefix) + + try: + result = subprocess.run( + [cppfilt_cmd], + input="\n".join(symbols_stripped), + capture_output=True, + text=True, + check=False, + ) + except (subprocess.SubprocessError, OSError, UnicodeDecodeError) as e: + _LOGGER.warning("Failed to batch demangle symbols: %s", e) + return {s: s for s in symbols} + + if result.returncode != 0: + _LOGGER.warning( + "c++filt exited with code %d: %s", + result.returncode, + result.stderr[:200] if result.stderr else "(no error output)", + ) + return {s: s for s in symbols} + + # Process demangled output + demangled_lines = result.stdout.strip().split("\n") + + # Check for output length mismatch + if len(demangled_lines) != len(symbols): + _LOGGER.warning( + "c++filt output mismatch: expected %d lines, got %d", + len(symbols), + len(demangled_lines), + ) + return {s: s for s in symbols} + + failed_count = 0 + + for original, stripped, prefix, demangled in zip( + symbols, symbols_stripped, symbols_prefixes, demangled_lines + ): + # Add back any prefix that was removed + demangled = _restore_symbol_prefix(prefix, stripped, demangled) + + # If we stripped a suffix, add it back to the demangled name for clarity + if original != stripped and not prefix: + demangled = _restore_symbol_suffix(original, demangled) + + cache[original] = demangled + + # Count symbols that failed to demangle + if stripped == demangled and stripped.startswith("_Z"): + failed_count += 1 + if failed_count <= 5: + _LOGGER.debug("Failed to demangle: %s", original) + + if failed_count > 0: + _LOGGER.debug( + "Failed to demangle %d/%d symbols using %s", + failed_count, + len(symbols), + cppfilt_cmd, + ) + + return cache diff --git a/esphome/analyze_memory/ram_strings.py b/esphome/analyze_memory/ram_strings.py new file mode 100644 index 0000000000..fbcbeeca61 --- /dev/null +++ b/esphome/analyze_memory/ram_strings.py @@ -0,0 +1,493 @@ +"""Analyzer for RAM-stored strings in ESP8266/ESP32 firmware ELF files. + +This module identifies strings that are stored in RAM sections (.data, .bss, .rodata) +rather than in flash sections (.irom0.text, .irom.text), which is important for +memory-constrained platforms like ESP8266. +""" + +from __future__ import annotations + +from collections import defaultdict +from dataclasses import dataclass +import logging +from pathlib import Path +import re +import subprocess + +from .demangle import batch_demangle +from .toolchain import find_tool + +_LOGGER = logging.getLogger(__name__) + +# ESP8266: .rodata is in RAM (DRAM), not flash +# ESP32: .rodata is in flash, mapped to data bus +ESP8266_RAM_SECTIONS = frozenset([".data", ".rodata", ".bss"]) +ESP8266_FLASH_SECTIONS = frozenset([".irom0.text", ".irom.text", ".text"]) + +# ESP32: .rodata is memory-mapped from flash +ESP32_RAM_SECTIONS = frozenset([".data", ".bss", ".dram0.data", ".dram0.bss"]) +ESP32_FLASH_SECTIONS = frozenset([".text", ".rodata", ".flash.text", ".flash.rodata"]) + +# nm symbol types for data symbols (D=global data, d=local data, R=rodata, B=bss) +DATA_SYMBOL_TYPES = frozenset(["D", "d", "R", "r", "B", "b"]) + + +@dataclass +class SectionInfo: + """Information about an ELF section.""" + + name: str + address: int + size: int + + +@dataclass +class RamString: + """A string found in RAM.""" + + section: str + address: int + content: str + + @property + def size(self) -> int: + """Size in bytes including null terminator.""" + return len(self.content) + 1 + + +@dataclass +class RamSymbol: + """A symbol found in RAM.""" + + name: str + sym_type: str + address: int + size: int + section: str + demangled: str = "" # Demangled name, set after batch demangling + + +class RamStringsAnalyzer: + """Analyzes ELF files to find strings stored in RAM.""" + + def __init__( + self, + elf_path: str, + objdump_path: str | None = None, + min_length: int = 8, + platform: str = "esp32", + ) -> None: + """Initialize the RAM strings analyzer. + + Args: + elf_path: Path to the ELF file to analyze + objdump_path: Path to objdump binary (used to find other tools) + min_length: Minimum string length to report (default: 8) + platform: Platform name ("esp8266", "esp32", etc.) for section mapping + """ + self.elf_path = Path(elf_path) + if not self.elf_path.exists(): + raise FileNotFoundError(f"ELF file not found: {elf_path}") + + self.objdump_path = objdump_path + self.min_length = min_length + self.platform = platform + + # Set RAM/flash sections based on platform + if self.platform == "esp8266": + self.ram_sections = ESP8266_RAM_SECTIONS + self.flash_sections = ESP8266_FLASH_SECTIONS + else: + # ESP32 and other platforms + self.ram_sections = ESP32_RAM_SECTIONS + self.flash_sections = ESP32_FLASH_SECTIONS + + self.sections: dict[str, SectionInfo] = {} + self.ram_strings: list[RamString] = [] + self.ram_symbols: list[RamSymbol] = [] + + def _run_command(self, cmd: list[str]) -> str: + """Run a command and return its output.""" + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return result.stdout + except subprocess.CalledProcessError as e: + _LOGGER.debug("Command failed: %s - %s", " ".join(cmd), e.stderr) + raise + except FileNotFoundError: + _LOGGER.warning("Command not found: %s", cmd[0]) + raise + + def analyze(self) -> None: + """Perform the full RAM analysis.""" + self._parse_sections() + self._extract_strings() + self._analyze_symbols() + self._demangle_symbols() + + def _parse_sections(self) -> None: + """Parse section headers from ELF file.""" + objdump = find_tool("objdump", self.objdump_path) + if not objdump: + _LOGGER.error("Could not find objdump command") + return + + try: + output = self._run_command([objdump, "-h", str(self.elf_path)]) + except (subprocess.CalledProcessError, FileNotFoundError): + return + + # Parse section headers + # Format: Idx Name Size VMA LMA File off Algn + section_pattern = re.compile( + r"^\s*\d+\s+(\S+)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)" + ) + + for line in output.split("\n"): + if match := section_pattern.match(line): + name = match.group(1) + size = int(match.group(2), 16) + vma = int(match.group(3), 16) + self.sections[name] = SectionInfo(name, vma, size) + + def _extract_strings(self) -> None: + """Extract strings from RAM sections.""" + objdump = find_tool("objdump", self.objdump_path) + if not objdump: + return + + for section_name in self.ram_sections: + if section_name not in self.sections: + continue + + try: + output = self._run_command( + [objdump, "-s", "-j", section_name, str(self.elf_path)] + ) + except subprocess.CalledProcessError: + # Section may exist but have no content (e.g., .bss) + continue + except FileNotFoundError: + continue + + strings = self._parse_hex_dump(output, section_name) + self.ram_strings.extend(strings) + + def _parse_hex_dump(self, output: str, section_name: str) -> list[RamString]: + """Parse hex dump output to extract strings. + + Args: + output: Output from objdump -s + section_name: Name of the section being parsed + + Returns: + List of RamString objects + """ + strings: list[RamString] = [] + current_string = bytearray() + string_start_addr = 0 + + for line in output.split("\n"): + # Lines look like: " 3ffef8a0 00000000 00000000 00000000 00000000 ................" + match = re.match(r"^\s+([0-9a-fA-F]+)\s+((?:[0-9a-fA-F]{2,8}\s*)+)", line) + if not match: + continue + + addr = int(match.group(1), 16) + hex_data = match.group(2).strip() + + # Convert hex to bytes + hex_bytes = hex_data.split() + byte_offset = 0 + for hex_chunk in hex_bytes: + # Handle both byte-by-byte and word formats + for i in range(0, len(hex_chunk), 2): + byte_val = int(hex_chunk[i : i + 2], 16) + if 0x20 <= byte_val <= 0x7E: # Printable ASCII + if not current_string: + string_start_addr = addr + byte_offset + current_string.append(byte_val) + else: + if byte_val == 0 and len(current_string) >= self.min_length: + # Found null terminator + strings.append( + RamString( + section=section_name, + address=string_start_addr, + content=current_string.decode( + "ascii", errors="ignore" + ), + ) + ) + current_string = bytearray() + byte_offset += 1 + + return strings + + def _analyze_symbols(self) -> None: + """Analyze symbols in RAM sections.""" + nm = find_tool("nm", self.objdump_path) + if not nm: + return + + try: + output = self._run_command([nm, "-S", "--size-sort", str(self.elf_path)]) + except (subprocess.CalledProcessError, FileNotFoundError): + return + + for line in output.split("\n"): + parts = line.split() + if len(parts) < 4: + continue + + try: + addr = int(parts[0], 16) + size = int(parts[1], 16) if parts[1] != "?" else 0 + except ValueError: + continue + + sym_type = parts[2] + name = " ".join(parts[3:]) + + # Filter for data symbols + if sym_type not in DATA_SYMBOL_TYPES: + continue + + # Check if symbol is in a RAM section + for section_name in self.ram_sections: + if section_name not in self.sections: + continue + + section = self.sections[section_name] + if section.address <= addr < section.address + section.size: + self.ram_symbols.append( + RamSymbol( + name=name, + sym_type=sym_type, + address=addr, + size=size, + section=section_name, + ) + ) + break + + def _demangle_symbols(self) -> None: + """Batch demangle all RAM symbol names.""" + if not self.ram_symbols: + return + + # Collect all symbol names and demangle them + symbol_names = [s.name for s in self.ram_symbols] + demangle_cache = batch_demangle(symbol_names, objdump_path=self.objdump_path) + + # Assign demangled names to symbols + for symbol in self.ram_symbols: + symbol.demangled = demangle_cache.get(symbol.name, symbol.name) + + def _get_sections_size(self, section_names: frozenset[str]) -> int: + """Get total size of specified sections.""" + return sum( + section.size + for name, section in self.sections.items() + if name in section_names + ) + + def get_total_ram_usage(self) -> int: + """Get total RAM usage from RAM sections.""" + return self._get_sections_size(self.ram_sections) + + def get_total_flash_usage(self) -> int: + """Get total flash usage from flash sections.""" + return self._get_sections_size(self.flash_sections) + + def get_total_string_bytes(self) -> int: + """Get total bytes used by strings in RAM.""" + return sum(s.size for s in self.ram_strings) + + def get_repeated_strings(self) -> list[tuple[str, int]]: + """Find strings that appear multiple times. + + Returns: + List of (string, count) tuples sorted by potential savings + """ + string_counts: dict[str, int] = defaultdict(int) + for ram_string in self.ram_strings: + string_counts[ram_string.content] += 1 + + return sorted( + [(s, c) for s, c in string_counts.items() if c > 1], + key=lambda x: x[1] * (len(x[0]) + 1), + reverse=True, + ) + + def get_long_strings(self, min_len: int = 20) -> list[RamString]: + """Get strings longer than the specified length. + + Args: + min_len: Minimum string length + + Returns: + List of RamString objects sorted by length + """ + return sorted( + [s for s in self.ram_strings if len(s.content) >= min_len], + key=lambda x: len(x.content), + reverse=True, + ) + + def get_largest_symbols(self, min_size: int = 100) -> list[RamSymbol]: + """Get RAM symbols larger than the specified size. + + Args: + min_size: Minimum symbol size in bytes + + Returns: + List of RamSymbol objects sorted by size + """ + return sorted( + [s for s in self.ram_symbols if s.size >= min_size], + key=lambda x: x.size, + reverse=True, + ) + + def generate_report(self, show_all_sections: bool = False) -> str: + """Generate a formatted RAM strings analysis report. + + Args: + show_all_sections: If True, show all sections, not just RAM + + Returns: + Formatted report string + """ + lines: list[str] = [] + table_width = 80 + + lines.append("=" * table_width) + lines.append( + f"RAM Strings Analysis ({self.platform.upper()})".center(table_width) + ) + lines.append("=" * table_width) + lines.append("") + + # Section Analysis + lines.append("SECTION ANALYSIS") + lines.append("-" * table_width) + lines.append(f"{'Section':<20} {'Address':<12} {'Size':<12} {'Location'}") + lines.append("-" * table_width) + + total_ram_usage = 0 + total_flash_usage = 0 + + for name, section in sorted(self.sections.items(), key=lambda x: x[1].address): + if name in self.ram_sections: + location = "RAM" + total_ram_usage += section.size + elif name in self.flash_sections: + location = "FLASH" + total_flash_usage += section.size + else: + location = "OTHER" + + if show_all_sections or name in self.ram_sections: + lines.append( + f"{name:<20} 0x{section.address:08x} {section.size:>8} B {location}" + ) + + lines.append("-" * table_width) + lines.append(f"Total RAM sections size: {total_ram_usage:,} bytes") + lines.append(f"Total Flash sections size: {total_flash_usage:,} bytes") + + # Strings in RAM + lines.append("") + lines.append("=" * table_width) + lines.append("STRINGS IN RAM SECTIONS") + lines.append("=" * table_width) + lines.append( + "Note: .bss sections contain uninitialized data (no strings to extract)" + ) + + # Group strings by section + strings_by_section: dict[str, list[RamString]] = defaultdict(list) + for ram_string in self.ram_strings: + strings_by_section[ram_string.section].append(ram_string) + + for section_name in sorted(strings_by_section.keys()): + section_strings = strings_by_section[section_name] + lines.append(f"\nSection: {section_name}") + lines.append("-" * 40) + for ram_string in sorted(section_strings, key=lambda x: x.address): + clean_string = ram_string.content[:100] + ( + "..." if len(ram_string.content) > 100 else "" + ) + lines.append( + f' 0x{ram_string.address:08x}: "{clean_string}" (len={len(ram_string.content)})' + ) + + # Large RAM symbols + lines.append("") + lines.append("=" * table_width) + lines.append("LARGE DATA SYMBOLS IN RAM (>= 50 bytes)") + lines.append("=" * table_width) + + largest_symbols = self.get_largest_symbols(50) + lines.append(f"\n{'Symbol':<50} {'Type':<6} {'Size':<10} {'Section'}") + lines.append("-" * table_width) + + for symbol in largest_symbols: + # Use demangled name if available, otherwise raw name + display_name = symbol.demangled or symbol.name + name_display = display_name[:49] if len(display_name) > 49 else display_name + lines.append( + f"{name_display:<50} {symbol.sym_type:<6} {symbol.size:>8} B {symbol.section}" + ) + + # Summary + lines.append("") + lines.append("=" * table_width) + lines.append("SUMMARY") + lines.append("=" * table_width) + lines.append(f"Total strings found in RAM: {len(self.ram_strings)}") + total_string_bytes = self.get_total_string_bytes() + lines.append(f"Total bytes used by strings: {total_string_bytes:,}") + + # Optimization targets + lines.append("") + lines.append("=" * table_width) + lines.append("POTENTIAL OPTIMIZATION TARGETS") + lines.append("=" * table_width) + + # Repeated strings + repeated = self.get_repeated_strings()[:10] + if repeated: + lines.append("\nRepeated strings (could be deduplicated):") + for string, count in repeated: + savings = (count - 1) * (len(string) + 1) + clean_string = string[:50] + ("..." if len(string) > 50 else "") + lines.append( + f' "{clean_string}" - appears {count} times (potential savings: {savings} bytes)' + ) + + # Long strings - platform-specific advice + long_strings = self.get_long_strings(20)[:10] + if long_strings: + if self.platform == "esp8266": + lines.append( + "\nLong strings that could be moved to PROGMEM (>= 20 chars):" + ) + else: + # ESP32: strings in DRAM are typically there for a reason + # (interrupt handlers, pre-flash-init code, etc.) + lines.append("\nLong strings in DRAM (>= 20 chars):") + lines.append( + "Note: ESP32 DRAM strings may be required for interrupt/early-boot contexts" + ) + for ram_string in long_strings: + clean_string = ram_string.content[:60] + ( + "..." if len(ram_string.content) > 60 else "" + ) + lines.append( + f' {ram_string.section} @ 0x{ram_string.address:08x}: "{clean_string}" ({len(ram_string.content)} bytes)' + ) + + lines.append("") + return "\n".join(lines) diff --git a/esphome/analyze_memory/toolchain.py b/esphome/analyze_memory/toolchain.py new file mode 100644 index 0000000000..e766252412 --- /dev/null +++ b/esphome/analyze_memory/toolchain.py @@ -0,0 +1,57 @@ +"""Toolchain utilities for memory analysis.""" + +from __future__ import annotations + +import logging +from pathlib import Path +import subprocess + +_LOGGER = logging.getLogger(__name__) + +# Platform-specific toolchain prefixes +TOOLCHAIN_PREFIXES = [ + "xtensa-lx106-elf-", # ESP8266 + "xtensa-esp32-elf-", # ESP32 + "xtensa-esp-elf-", # ESP32 (newer IDF) + "", # System default (no prefix) +] + + +def find_tool( + tool_name: str, + objdump_path: str | None = None, +) -> str | None: + """Find a toolchain tool by name. + + First tries to derive the tool path from objdump_path (if provided), + then falls back to searching for platform-specific tools. + + Args: + tool_name: Name of the tool (e.g., "objdump", "nm", "c++filt") + objdump_path: Path to objdump binary to derive other tool paths from + + Returns: + Path to the tool or None if not found + """ + # Try to derive from objdump path first (most reliable) + if objdump_path and objdump_path != "objdump": + objdump_file = Path(objdump_path) + # Replace just the filename portion, preserving any prefix (e.g., xtensa-esp32-elf-) + new_name = objdump_file.name.replace("objdump", tool_name) + potential_path = str(objdump_file.with_name(new_name)) + if Path(potential_path).exists(): + _LOGGER.debug("Found %s at: %s", tool_name, potential_path) + return potential_path + + # Try platform-specific tools + for prefix in TOOLCHAIN_PREFIXES: + cmd = f"{prefix}{tool_name}" + try: + subprocess.run([cmd, "--version"], capture_output=True, check=True) + _LOGGER.debug("Found %s: %s", tool_name, cmd) + return cmd + except (subprocess.CalledProcessError, FileNotFoundError): + continue + + _LOGGER.warning("Could not find %s tool", tool_name) + return None diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index ccbc5a1306..670d6c16fc 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -269,6 +269,16 @@ def mock_memory_analyzer_cli() -> Generator[Mock]: yield mock_class +@pytest.fixture +def mock_ram_strings_analyzer() -> Generator[Mock]: + """Mock RamStringsAnalyzer for testing.""" + with patch("esphome.analyze_memory.ram_strings.RamStringsAnalyzer") as mock_class: + mock_analyzer = MagicMock() + mock_analyzer.generate_report.return_value = "Mock RAM Strings Report" + mock_class.return_value = mock_analyzer + yield mock_class + + def test_choose_upload_log_host_with_string_default() -> None: """Test with a single string default device.""" setup_core() @@ -2424,6 +2434,7 @@ def test_command_analyze_memory_success( mock_get_idedata: Mock, mock_get_esphome_components: Mock, mock_memory_analyzer_cli: Mock, + mock_ram_strings_analyzer: Mock, ) -> None: """Test command_analyze_memory with successful compilation and analysis.""" setup_core(platform=PLATFORM_ESP32, tmp_path=tmp_path, name="test_device") @@ -2471,9 +2482,20 @@ def test_command_analyze_memory_success( mock_analyzer.analyze.assert_called_once() mock_analyzer.generate_report.assert_called_once() - # Verify report was printed + # Verify RAM strings analyzer was created and run + mock_ram_strings_analyzer.assert_called_once_with( + str(firmware_elf), + objdump_path="/path/to/objdump", + platform="esp32", + ) + mock_ram_analyzer = mock_ram_strings_analyzer.return_value + mock_ram_analyzer.analyze.assert_called_once() + mock_ram_analyzer.generate_report.assert_called_once() + + # Verify reports were printed captured = capfd.readouterr() assert "Mock Memory Report" in captured.out + assert "Mock RAM Strings Report" in captured.out def test_command_analyze_memory_with_external_components( @@ -2483,6 +2505,7 @@ def test_command_analyze_memory_with_external_components( mock_get_idedata: Mock, mock_get_esphome_components: Mock, mock_memory_analyzer_cli: Mock, + mock_ram_strings_analyzer: Mock, ) -> None: """Test command_analyze_memory detects external components.""" setup_core(platform=PLATFORM_ESP32, tmp_path=tmp_path, name="test_device") From d1583456e97af1a00ec80350223d685addac2eb0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Dec 2025 10:02:29 -0600 Subject: [PATCH 0473/1145] [web_server] Store update state strings in flash on ESP8266 (#12204) --- esphome/components/web_server/web_server.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 38fa54704a..b56d9ce698 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -41,8 +41,8 @@ namespace web_server { static const char *const TAG = "web_server"; -// Longest: HORIZONTAL (10 chars + null terminator, rounded up) -static constexpr size_t PSTR_LOCAL_SIZE = 16; +// Longest: UPDATE AVAILABLE (16 chars + null terminator, rounded up) +static constexpr size_t PSTR_LOCAL_SIZE = 18; #define PSTR_LOCAL(mode_s) ESPHOME_strncpy_P(buf, (ESPHOME_PGM_P) ((mode_s)), PSTR_LOCAL_SIZE - 1) #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS @@ -1717,16 +1717,16 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty #endif #ifdef USE_UPDATE -static const char *update_state_to_string(update::UpdateState state) { +static const LogString *update_state_to_string(update::UpdateState state) { switch (state) { case update::UPDATE_STATE_NO_UPDATE: - return "NO UPDATE"; + return LOG_STR("NO UPDATE"); case update::UPDATE_STATE_AVAILABLE: - return "UPDATE AVAILABLE"; + return LOG_STR("UPDATE AVAILABLE"); case update::UPDATE_STATE_INSTALLING: - return "INSTALLING"; + return LOG_STR("INSTALLING"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } @@ -1769,8 +1769,9 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c json::JsonBuilder builder; JsonObject root = builder.root(); - set_json_icon_state_value(root, obj, "update", update_state_to_string(obj->state), obj->update_info.latest_version, - start_config); + char buf[PSTR_LOCAL_SIZE]; + set_json_icon_state_value(root, obj, "update", PSTR_LOCAL(update_state_to_string(obj->state)), + obj->update_info.latest_version, start_config); if (start_config == DETAIL_ALL) { root["current_version"] = obj->update_info.current_version; root["title"] = obj->update_info.title; From 3f08cacf71e32e582b9c6612c445488a9372423c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Dec 2025 10:02:51 -0600 Subject: [PATCH 0474/1145] [valve] Store valve state strings in flash on ESP8266 (#12202) --- .../prometheus/prometheus_handler.cpp | 6 ++++- esphome/components/valve/valve.cpp | 22 +++++++++---------- esphome/components/valve/valve.h | 3 ++- esphome/components/web_server/web_server.cpp | 3 ++- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 252b477400..4b5d834ebf 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -895,7 +895,11 @@ void PrometheusHandler::valve_row_(AsyncResponseStream *stream, valve::Valve *ob stream->print(ESPHOME_F("\",name=\"")); stream->print(relabel_name_(obj).c_str()); stream->print(ESPHOME_F("\",operation=\"")); - stream->print(valve::valve_operation_to_str(obj->current_operation)); +#ifdef USE_STORE_LOG_STR_IN_FLASH + stream->print((const __FlashStringHelper *) valve::valve_operation_to_str(obj->current_operation)); +#else + stream->print((const char *) valve::valve_operation_to_str(obj->current_operation)); +#endif stream->print(ESPHOME_F("\"} ")); stream->print(ESPHOME_F("1.0")); stream->print(ESPHOME_F("\n")); diff --git a/esphome/components/valve/valve.cpp b/esphome/components/valve/valve.cpp index 381d9061de..fed113afc2 100644 --- a/esphome/components/valve/valve.cpp +++ b/esphome/components/valve/valve.cpp @@ -12,25 +12,25 @@ static const char *const TAG = "valve"; const float VALVE_OPEN = 1.0f; const float VALVE_CLOSED = 0.0f; -const char *valve_command_to_str(float pos) { +const LogString *valve_command_to_str(float pos) { if (pos == VALVE_OPEN) { - return "OPEN"; + return LOG_STR("OPEN"); } else if (pos == VALVE_CLOSED) { - return "CLOSE"; + return LOG_STR("CLOSE"); } else { - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -const char *valve_operation_to_str(ValveOperation op) { +const LogString *valve_operation_to_str(ValveOperation op) { switch (op) { case VALVE_OPERATION_IDLE: - return "IDLE"; + return LOG_STR("IDLE"); case VALVE_OPERATION_OPENING: - return "OPENING"; + return LOG_STR("OPENING"); case VALVE_OPERATION_CLOSING: - return "CLOSING"; + return LOG_STR("CLOSING"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } @@ -82,7 +82,7 @@ void ValveCall::perform() { if (traits.get_supports_position()) { ESP_LOGD(TAG, " Position: %.0f%%", *this->position_ * 100.0f); } else { - ESP_LOGD(TAG, " Command: %s", valve_command_to_str(*this->position_)); + ESP_LOGD(TAG, " Command: %s", LOG_STR_ARG(valve_command_to_str(*this->position_))); } } if (this->toggle_.has_value()) { @@ -146,7 +146,7 @@ void Valve::publish_state(bool save) { ESP_LOGD(TAG, " State: UNKNOWN"); } } - ESP_LOGD(TAG, " Current Operation: %s", valve_operation_to_str(this->current_operation)); + ESP_LOGD(TAG, " Current Operation: %s", LOG_STR_ARG(valve_operation_to_str(this->current_operation))); this->state_callback_.call(); #if defined(USE_VALVE) && defined(USE_CONTROLLER_REGISTRY) diff --git a/esphome/components/valve/valve.h b/esphome/components/valve/valve.h index ab7ff5abe1..2cb28e4b2f 100644 --- a/esphome/components/valve/valve.h +++ b/esphome/components/valve/valve.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" +#include "esphome/core/log.h" #include "esphome/core/preferences.h" #include "valve_traits.h" @@ -81,7 +82,7 @@ enum ValveOperation : uint8_t { VALVE_OPERATION_CLOSING, }; -const char *valve_operation_to_str(ValveOperation op); +const LogString *valve_operation_to_str(ValveOperation op); /** Base class for all valve devices. * diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index b56d9ce698..35f20f8609 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1565,7 +1565,8 @@ std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) { set_json_icon_state_value(root, obj, "valve", obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position, start_config); - root["current_operation"] = valve::valve_operation_to_str(obj->current_operation); + char buf[PSTR_LOCAL_SIZE]; + root["current_operation"] = PSTR_LOCAL(valve::valve_operation_to_str(obj->current_operation)); if (obj->get_traits().get_supports_position()) root["position"] = obj->position; From 77477bd3300827055b3574c79fe55aa630cabd17 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Dec 2025 10:03:29 -0600 Subject: [PATCH 0475/1145] [web_server_idf] Fix SSE multi-line message formatting (#12247) --- .../web_server_idf/web_server_idf.cpp | 87 +++++++++++++++++-- 1 file changed, 81 insertions(+), 6 deletions(-) diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index c910ed06c5..af99b85e53 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -664,17 +664,92 @@ bool AsyncEventSourceResponse::try_send_nodefer(const char *message, const char event_buffer_.append(CRLF_STR, CRLF_LEN); } - if (message && *message) { - event_buffer_.append("data: ", sizeof("data: ") - 1); - event_buffer_.append(message); - event_buffer_.append(CRLF_STR, CRLF_LEN); + // Match ESPAsyncWebServer: null message means no data lines and no terminating blank line + if (message) { + // SSE spec requires each line of a multi-line message to have its own "data:" prefix + // Handle \n, \r, and \r\n line endings (matching ESPAsyncWebServer behavior) + + // Fast path: check if message contains any newlines at all + // Most SSE messages (JSON state updates) have no newlines + const char *first_n = strchr(message, '\n'); + const char *first_r = strchr(message, '\r'); + + if (first_n == nullptr && first_r == nullptr) { + // No newlines - fast path (most common case) + event_buffer_.append("data: ", sizeof("data: ") - 1); + event_buffer_.append(message); + event_buffer_.append(CRLF_STR CRLF_STR, CRLF_LEN * 2); // data line + blank line terminator + } else { + // Has newlines - handle multi-line message + const char *line_start = message; + size_t msg_len = strlen(message); + const char *msg_end = message + msg_len; + + // Reuse the first search results + const char *next_n = first_n; + const char *next_r = first_r; + + while (line_start <= msg_end) { + const char *line_end; + const char *next_line; + + if (next_n == nullptr && next_r == nullptr) { + // No more line breaks - output remaining text as final line + event_buffer_.append("data: ", sizeof("data: ") - 1); + event_buffer_.append(line_start); + event_buffer_.append(CRLF_STR, CRLF_LEN); + break; + } + + // Determine line ending type and next line start + if (next_n != nullptr && next_r != nullptr) { + if (next_r + 1 == next_n) { + // \r\n sequence + line_end = next_r; + next_line = next_n + 1; + } else { + // Mixed \n and \r - use whichever comes first + line_end = (next_r < next_n) ? next_r : next_n; + next_line = line_end + 1; + } + } else if (next_n != nullptr) { + // Unix LF + line_end = next_n; + next_line = next_n + 1; + } else { + // Old Mac CR + line_end = next_r; + next_line = next_r + 1; + } + + // Output this line + event_buffer_.append("data: ", sizeof("data: ") - 1); + event_buffer_.append(line_start, line_end - line_start); + event_buffer_.append(CRLF_STR, CRLF_LEN); + + line_start = next_line; + + // Check if we've consumed all content + if (line_start >= msg_end) { + break; + } + + // Search for next newlines only in remaining string + next_n = strchr(line_start, '\n'); + next_r = strchr(line_start, '\r'); + } + + // Terminate message with blank line + event_buffer_.append(CRLF_STR, CRLF_LEN); + } } - if (event_buffer_.empty()) { + if (event_buffer_.size() == static_cast(chunk_len_header_len)) { + // Nothing was added, reset buffer + event_buffer_.resize(0); return true; } - event_buffer_.append(CRLF_STR, CRLF_LEN); event_buffer_.append(CRLF_STR, CRLF_LEN); // chunk length header itself and the final chunk terminating CRLF are not counted as part of the chunk From 6ce2a456915e16968eb0275bc7d6531dd03b7ca4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Dec 2025 10:03:58 -0600 Subject: [PATCH 0476/1145] [text_sensor] Add deprecation warning for raw_state member access (#12246) --- esphome/components/text_sensor/text_sensor.cpp | 18 ++++++++++-------- esphome/components/text_sensor/text_sensor.h | 11 +++++++---- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index d984e78b2a..51923ebd96 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -25,11 +25,11 @@ void log_text_sensor(const char *tag, const char *prefix, const char *type, Text } void TextSensor::publish_state(const std::string &state) { - // Only store raw_state_ separately when filters exist - // When no filters, raw_state == state, so we avoid the duplicate storage - if (this->filter_list_ != nullptr) { - this->raw_state_ = state; - } +// Suppress deprecation warning - we need to populate raw_state for backwards compatibility +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + this->raw_state = state; +#pragma GCC diagnostic pop if (this->raw_callback_) { this->raw_callback_->call(state); } @@ -85,9 +85,11 @@ void TextSensor::add_on_raw_state_callback(std::function call std::string TextSensor::get_state() const { return this->state; } std::string TextSensor::get_raw_state() const { - // When no filters exist, raw_state == state, so return state to avoid - // requiring separate storage - return this->filter_list_ != nullptr ? this->raw_state_ : this->state; +// Suppress deprecation warning - get_raw_state() is the replacement API +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + return this->raw_state; +#pragma GCC diagnostic pop } void TextSensor::internal_send_state_to_frontend(const std::string &state) { this->state = state; diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index fcfbed2fbc..7217806a55 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -51,6 +51,13 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { std::string state; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + /// @deprecated Use get_raw_state() instead. This member will be removed in ESPHome 2026.6.0. + ESPDEPRECATED("Use get_raw_state() instead of .raw_state. Will be removed in 2026.6.0", "2025.12.0") + std::string raw_state; +#pragma GCC diagnostic pop + // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) @@ -62,10 +69,6 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { CallbackManager callback_; ///< Storage for filtered state callbacks. Filter *filter_list_{nullptr}; ///< Store all active filters. - - /// Raw state (before filters). Only populated when filters are configured. - /// When no filters exist, get_raw_state() returns state directly. - std::string raw_state_; }; } // namespace text_sensor From 8f97f3b81f397c5204200bc4046c0ff7d634dec5 Mon Sep 17 00:00:00 2001 From: Flo Date: Tue, 2 Dec 2025 17:12:27 +0100 Subject: [PATCH 0477/1145] [wifi] Fix ap_active condition (#12227) --- esphome/components/wifi/wifi_component.cpp | 2 +- esphome/components/wifi/wifi_component.h | 1 + esphome/components/wifi/wifi_component_esp8266.cpp | 3 +++ esphome/components/wifi/wifi_component_esp_idf.cpp | 5 ++--- esphome/components/wifi/wifi_component_libretiny.cpp | 3 +++ esphome/components/wifi/wifi_component_pico_w.cpp | 4 ++++ 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e67493aa4d..317507f242 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -580,7 +580,7 @@ void WiFiComponent::loop() { WiFiComponent::WiFiComponent() { global_wifi_component = this; } bool WiFiComponent::has_ap() const { return this->has_ap_; } -bool WiFiComponent::is_ap_active() const { return this->state_ == WIFI_COMPONENT_STATE_AP; } +bool WiFiComponent::is_ap_active() const { return this->ap_started_; } bool WiFiComponent::has_sta() const { return !this->sta_.empty(); } #ifdef USE_WIFI_11KV_SUPPORT void WiFiComponent::set_btm(bool btm) { this->btm_ = btm; } diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 97cc3961fe..2148f2d4c7 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -616,6 +616,7 @@ class WiFiComponent : public Component { bool error_from_callback_{false}; bool scan_done_{false}; bool ap_setup_{false}; + bool ap_started_{false}; bool passive_scan_{false}; bool has_saved_wifi_settings_{false}; #ifdef USE_WIFI_11KV_SUPPORT diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 701cae5f7c..c1c0dd470f 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -82,8 +82,11 @@ bool WiFiComponent::wifi_mode_(optional sta, optional ap) { if (!ret) { ESP_LOGW(TAG, "Set mode failed"); + return false; } + this->ap_started_ = target_ap; + return ret; } bool WiFiComponent::wifi_apply_power_save_() { diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 3d25d2890f..e1f8108892 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -53,7 +53,6 @@ static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid- #endif // USE_WIFI_AP static bool s_sta_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static bool s_sta_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static bool s_ap_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static bool s_sta_connect_not_found = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static bool s_sta_connect_error = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -831,11 +830,11 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_START) { ESP_LOGV(TAG, "AP start"); - s_ap_started = true; + this->ap_started_ = true; } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STOP) { ESP_LOGV(TAG, "AP stop"); - s_ap_started = false; + this->ap_started_ = false; } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_PROBEREQRECVED) { const auto &it = data->data.ap_probe_req_rx; diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index f1405d3bef..0de7003899 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -50,8 +50,11 @@ bool WiFiComponent::wifi_mode_(optional sta, optional ap) { if (!ret) { ESP_LOGW(TAG, "Setting mode failed"); + return false; } + this->ap_started_ = enable_ap; + return ret; } bool WiFiComponent::wifi_apply_output_power_(float output_power) { diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 1a8b75213c..c7dc4120dd 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -28,11 +28,15 @@ bool WiFiComponent::wifi_mode_(optional sta, optional ap) { cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_STA, true, CYW43_COUNTRY_WORLDWIDE); } } + + bool ap_state = false; if (ap.has_value()) { if (ap.value()) { cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_AP, true, CYW43_COUNTRY_WORLDWIDE); + ap_state = true; } } + this->ap_started_ = ap_state; return true; } From 638c59e162cd7ae46b6ddb24f9738cb332ea51ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 10:13:20 -0600 Subject: [PATCH 0478/1145] Bump pylint from 4.0.3 to 4.0.4 (#12239) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 3aec877126..9d55d23272 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==4.0.3 +pylint==4.0.4 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating ruff==0.14.7 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating From a6a6f482e6661c5747ba475d3a170268f9a88e45 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Dec 2025 10:51:05 -0600 Subject: [PATCH 0479/1145] [core] Add PROGMEM macros and move web_server JSON keys to flash (#12214) --- .../components/light/light_json_schema.cpp | 94 ++++++------ esphome/components/web_server/web_server.cpp | 142 +++++++++--------- .../web_server_base/web_server_base.h | 15 +- esphome/core/progmem.h | 16 ++ 4 files changed, 137 insertions(+), 130 deletions(-) create mode 100644 esphome/core/progmem.h diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 41cb855630..3365d1f417 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -1,5 +1,6 @@ #include "light_json_schema.h" #include "light_output.h" +#include "esphome/core/progmem.h" #ifdef USE_JSON @@ -35,9 +36,9 @@ static const char *get_color_mode_json_str(ColorMode mode) { void LightJSONSchema::dump_json(LightState &state, JsonObject root) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (state.supports_effects()) { - root["effect"] = state.get_effect_name(); - root["effect_index"] = state.get_current_effect_index(); - root["effect_count"] = state.get_effect_count(); + root[ESPHOME_F("effect")] = state.get_effect_name(); + root[ESPHOME_F("effect_index")] = state.get_current_effect_index(); + root[ESPHOME_F("effect_count")] = state.get_effect_count(); } auto values = state.remote_values; @@ -45,39 +46,39 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) { const auto color_mode = values.get_color_mode(); const char *mode_str = get_color_mode_json_str(color_mode); if (mode_str != nullptr) { - root["color_mode"] = mode_str; + root[ESPHOME_F("color_mode")] = mode_str; } if (color_mode & ColorCapability::ON_OFF) - root["state"] = (values.get_state() != 0.0f) ? "ON" : "OFF"; + root[ESPHOME_F("state")] = (values.get_state() != 0.0f) ? "ON" : "OFF"; if (color_mode & ColorCapability::BRIGHTNESS) - root["brightness"] = to_uint8_scale(values.get_brightness()); + root[ESPHOME_F("brightness")] = to_uint8_scale(values.get_brightness()); - JsonObject color = root["color"].to(); + JsonObject color = root[ESPHOME_F("color")].to(); if (color_mode & ColorCapability::RGB) { float color_brightness = values.get_color_brightness(); - color["r"] = to_uint8_scale(color_brightness * values.get_red()); - color["g"] = to_uint8_scale(color_brightness * values.get_green()); - color["b"] = to_uint8_scale(color_brightness * values.get_blue()); + color[ESPHOME_F("r")] = to_uint8_scale(color_brightness * values.get_red()); + color[ESPHOME_F("g")] = to_uint8_scale(color_brightness * values.get_green()); + color[ESPHOME_F("b")] = to_uint8_scale(color_brightness * values.get_blue()); } if (color_mode & ColorCapability::WHITE) { uint8_t white_val = to_uint8_scale(values.get_white()); - color["w"] = white_val; - root["white_value"] = white_val; // legacy API + color[ESPHOME_F("w")] = white_val; + root[ESPHOME_F("white_value")] = white_val; // legacy API } if (color_mode & ColorCapability::COLOR_TEMPERATURE) { // this one isn't under the color subkey for some reason - root["color_temp"] = uint32_t(values.get_color_temperature()); + root[ESPHOME_F("color_temp")] = uint32_t(values.get_color_temperature()); } if (color_mode & ColorCapability::COLD_WARM_WHITE) { - color["c"] = to_uint8_scale(values.get_cold_white()); - color["w"] = to_uint8_scale(values.get_warm_white()); + color[ESPHOME_F("c")] = to_uint8_scale(values.get_cold_white()); + color[ESPHOME_F("w")] = to_uint8_scale(values.get_warm_white()); } } void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject root) { - if (root["state"].is()) { - auto val = parse_on_off(root["state"]); + if (root[ESPHOME_F("state")].is()) { + auto val = parse_on_off(root[ESPHOME_F("state")]); switch (val) { case PARSE_ON: call.set_state(true); @@ -93,76 +94,77 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO } } - if (root["brightness"].is()) { - call.set_brightness(float(root["brightness"]) / 255.0f); + if (root[ESPHOME_F("brightness")].is()) { + call.set_brightness(float(root[ESPHOME_F("brightness")]) / 255.0f); } - if (root["color"].is()) { - JsonObject color = root["color"]; + if (root[ESPHOME_F("color")].is()) { + JsonObject color = root[ESPHOME_F("color")]; // HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness. float max_rgb = 0.0f; - if (color["r"].is()) { - float r = float(color["r"]) / 255.0f; + if (color[ESPHOME_F("r")].is()) { + float r = float(color[ESPHOME_F("r")]) / 255.0f; max_rgb = fmaxf(max_rgb, r); call.set_red(r); } - if (color["g"].is()) { - float g = float(color["g"]) / 255.0f; + if (color[ESPHOME_F("g")].is()) { + float g = float(color[ESPHOME_F("g")]) / 255.0f; max_rgb = fmaxf(max_rgb, g); call.set_green(g); } - if (color["b"].is()) { - float b = float(color["b"]) / 255.0f; + if (color[ESPHOME_F("b")].is()) { + float b = float(color[ESPHOME_F("b")]) / 255.0f; max_rgb = fmaxf(max_rgb, b); call.set_blue(b); } - if (color["r"].is() || color["g"].is() || color["b"].is()) { + if (color[ESPHOME_F("r")].is() || color[ESPHOME_F("g")].is() || + color[ESPHOME_F("b")].is()) { call.set_color_brightness(max_rgb); } - if (color["c"].is()) { - call.set_cold_white(float(color["c"]) / 255.0f); + if (color[ESPHOME_F("c")].is()) { + call.set_cold_white(float(color[ESPHOME_F("c")]) / 255.0f); } - if (color["w"].is()) { + if (color[ESPHOME_F("w")].is()) { // the HA scheme is ambiguous here, the same key is used for white channel in RGBW and warm // white channel in RGBWW. - if (color["c"].is()) { - call.set_warm_white(float(color["w"]) / 255.0f); + if (color[ESPHOME_F("c")].is()) { + call.set_warm_white(float(color[ESPHOME_F("w")]) / 255.0f); } else { - call.set_white(float(color["w"]) / 255.0f); + call.set_white(float(color[ESPHOME_F("w")]) / 255.0f); } } } - if (root["white_value"].is()) { // legacy API - call.set_white(float(root["white_value"]) / 255.0f); + if (root[ESPHOME_F("white_value")].is()) { // legacy API + call.set_white(float(root[ESPHOME_F("white_value")]) / 255.0f); } - if (root["color_temp"].is()) { - call.set_color_temperature(float(root["color_temp"])); + if (root[ESPHOME_F("color_temp")].is()) { + call.set_color_temperature(float(root[ESPHOME_F("color_temp")])); } } void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) { LightJSONSchema::parse_color_json(state, call, root); - if (root["flash"].is()) { - auto length = uint32_t(float(root["flash"]) * 1000); + if (root[ESPHOME_F("flash")].is()) { + auto length = uint32_t(float(root[ESPHOME_F("flash")]) * 1000); call.set_flash_length(length); } - if (root["transition"].is()) { - auto length = uint32_t(float(root["transition"]) * 1000); + if (root[ESPHOME_F("transition")].is()) { + auto length = uint32_t(float(root[ESPHOME_F("transition")]) * 1000); call.set_transition_length(length); } - if (root["effect"].is()) { - const char *effect = root["effect"]; + if (root[ESPHOME_F("effect")].is()) { + const char *effect = root[ESPHOME_F("effect")]; call.set_effect(effect); } - if (root["effect_index"].is()) { - uint32_t effect_index = root["effect_index"]; + if (root[ESPHOME_F("effect_index")].is()) { + uint32_t effect_index = root[ESPHOME_F("effect_index")]; call.set_effect(effect_index); } } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 35f20f8609..1f3605a082 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -244,8 +244,8 @@ void DeferredUpdateEventSourceList::on_client_connect_(DeferredUpdateEventSource for (auto &group : ws->sorting_groups_) { json::JsonBuilder builder; JsonObject root = builder.root(); - root["name"] = group.second.name; - root["sorting_weight"] = group.second.weight; + root[ESPHOME_F("name")] = group.second.name; + root[ESPHOME_F("sorting_weight")] = group.second.weight; message = builder.serialize(); // up to 31 groups should be able to be queued initially without defer @@ -286,15 +286,15 @@ std::string WebServer::get_config_json() { json::JsonBuilder builder; JsonObject root = builder.root(); - root["title"] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name(); - root["comment"] = App.get_comment(); + root[ESPHOME_F("title")] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name(); + root[ESPHOME_F("comment")] = App.get_comment(); #if defined(USE_WEBSERVER_OTA_DISABLED) || !defined(USE_WEBSERVER_OTA) - root["ota"] = false; // Note: USE_WEBSERVER_OTA_DISABLED only affects web_server, not captive_portal + root[ESPHOME_F("ota")] = false; // Note: USE_WEBSERVER_OTA_DISABLED only affects web_server, not captive_portal #else - root["ota"] = true; + root[ESPHOME_F("ota")] = true; #endif - root["log"] = this->expose_log_; - root["lang"] = "en"; + root[ESPHOME_F("log")] = this->expose_log_; + root[ESPHOME_F("lang")] = "en"; return builder.serialize(); } @@ -407,14 +407,14 @@ static void set_json_id(JsonObject &root, EntityBase *obj, const char *prefix, J char id_buf[160]; // object_id can be up to 128 chars + prefix + dash + null const auto &object_id = obj->get_object_id(); snprintf(id_buf, sizeof(id_buf), "%s-%s", prefix, object_id.c_str()); - root["id"] = id_buf; + root[ESPHOME_F("id")] = id_buf; if (start_config == DETAIL_ALL) { - root["name"] = obj->get_name(); - root["icon"] = obj->get_icon_ref(); - root["entity_category"] = obj->get_entity_category(); + root[ESPHOME_F("name")] = obj->get_name(); + root[ESPHOME_F("icon")] = obj->get_icon_ref(); + root[ESPHOME_F("entity_category")] = obj->get_entity_category(); bool is_disabled = obj->is_disabled_by_default(); if (is_disabled) - root["is_disabled_by_default"] = is_disabled; + root[ESPHOME_F("is_disabled_by_default")] = is_disabled; } } @@ -424,14 +424,14 @@ template static void set_json_value(JsonObject &root, EntityBase *obj, const char *prefix, const T &value, JsonDetail start_config) { set_json_id(root, obj, prefix, start_config); - root["value"] = value; + root[ESPHOME_F("value")] = value; } template static void set_json_icon_state_value(JsonObject &root, EntityBase *obj, const char *prefix, const std::string &state, const T &value, JsonDetail start_config) { set_json_value(root, obj, prefix, value, start_config); - root["state"] = state; + root[ESPHOME_F("state")] = state; } // Helper to get request detail parameter @@ -478,7 +478,7 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); if (!uom_ref.empty()) - root["uom"] = uom_ref; + root[ESPHOME_F("uom")] = uom_ref; } return builder.serialize(); @@ -593,7 +593,7 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail set_json_icon_state_value(root, obj, "switch", value ? "ON" : "OFF", value, start_config); if (start_config == DETAIL_ALL) { - root["assumed_state"] = obj->assumed_state(); + root[ESPHOME_F("assumed_state")] = obj->assumed_state(); this->add_sorting_info_(root, obj); } @@ -748,11 +748,11 @@ std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { set_json_icon_state_value(root, obj, "fan", obj->state ? "ON" : "OFF", obj->state, start_config); const auto traits = obj->get_traits(); if (traits.supports_speed()) { - root["speed_level"] = obj->speed; - root["speed_count"] = traits.supported_speed_count(); + root[ESPHOME_F("speed_level")] = obj->speed; + root[ESPHOME_F("speed_count")] = traits.supported_speed_count(); } if (obj->get_traits().supports_oscillation()) - root["oscillation"] = obj->oscillating; + root[ESPHOME_F("oscillation")] = obj->oscillating; if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); } @@ -827,7 +827,7 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi light::LightJSONSchema::dump_json(*obj, root); if (start_config == DETAIL_ALL) { - JsonArray opt = root["effects"].to(); + JsonArray opt = root[ESPHOME_F("effects")].to(); opt.add("None"); for (auto const &option : obj->get_effects()) { opt.add(option->get_name()); @@ -913,12 +913,12 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { set_json_icon_state_value(root, obj, "cover", obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position, start_config); char buf[PSTR_LOCAL_SIZE]; - root["current_operation"] = PSTR_LOCAL(cover::cover_operation_to_str(obj->current_operation)); + root[ESPHOME_F("current_operation")] = PSTR_LOCAL(cover::cover_operation_to_str(obj->current_operation)); if (obj->get_traits().get_supports_position()) - root["position"] = obj->position; + root[ESPHOME_F("position")] = obj->position; if (obj->get_traits().get_supports_tilt()) - root["tilt"] = obj->tilt; + root[ESPHOME_F("tilt")] = obj->tilt; if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); } @@ -979,14 +979,15 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail value, step_to_accuracy_decimals(obj->traits.get_step()), uom_ref); set_json_icon_state_value(root, obj, "number", state_str, val_str, start_config); if (start_config == DETAIL_ALL) { - root["min_value"] = + root[ESPHOME_F("min_value")] = value_accuracy_to_string(obj->traits.get_min_value(), step_to_accuracy_decimals(obj->traits.get_step())); - root["max_value"] = + root[ESPHOME_F("max_value")] = value_accuracy_to_string(obj->traits.get_max_value(), step_to_accuracy_decimals(obj->traits.get_step())); - root["step"] = value_accuracy_to_string(obj->traits.get_step(), step_to_accuracy_decimals(obj->traits.get_step())); - root["mode"] = (int) obj->traits.get_mode(); + root[ESPHOME_F("step")] = + value_accuracy_to_string(obj->traits.get_step(), step_to_accuracy_decimals(obj->traits.get_step())); + root[ESPHOME_F("mode")] = (int) obj->traits.get_mode(); if (!uom_ref.empty()) - root["uom"] = uom_ref; + root[ESPHOME_F("uom")] = uom_ref; this->add_sorting_info_(root, obj); } @@ -1208,11 +1209,11 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json std::string state = obj->traits.get_mode() == text::TextMode::TEXT_MODE_PASSWORD ? "********" : value; set_json_icon_state_value(root, obj, "text", state, value, start_config); - root["min_length"] = obj->traits.get_min_length(); - root["max_length"] = obj->traits.get_max_length(); - root["pattern"] = obj->traits.get_pattern(); + root[ESPHOME_F("min_length")] = obj->traits.get_min_length(); + root[ESPHOME_F("max_length")] = obj->traits.get_max_length(); + root[ESPHOME_F("pattern")] = obj->traits.get_pattern(); if (start_config == DETAIL_ALL) { - root["mode"] = (int) obj->traits.get_mode(); + root[ESPHOME_F("mode")] = (int) obj->traits.get_mode(); this->add_sorting_info_(root, obj); } @@ -1266,7 +1267,7 @@ std::string WebServer::select_json(select::Select *obj, const char *value, JsonD set_json_icon_state_value(root, obj, "select", value, value, start_config); if (start_config == DETAIL_ALL) { - JsonArray opt = root["option"].to(); + JsonArray opt = root[ESPHOME_F("option")].to(); for (auto &option : obj->traits.get_options()) { opt.add(option); } @@ -1337,32 +1338,32 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf char buf[PSTR_LOCAL_SIZE]; if (start_config == DETAIL_ALL) { - JsonArray opt = root["modes"].to(); + JsonArray opt = root[ESPHOME_F("modes")].to(); for (climate::ClimateMode m : traits.get_supported_modes()) opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m))); if (!traits.get_supported_custom_fan_modes().empty()) { - JsonArray opt = root["fan_modes"].to(); + JsonArray opt = root[ESPHOME_F("fan_modes")].to(); for (climate::ClimateFanMode m : traits.get_supported_fan_modes()) opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m))); } if (!traits.get_supported_custom_fan_modes().empty()) { - JsonArray opt = root["custom_fan_modes"].to(); + JsonArray opt = root[ESPHOME_F("custom_fan_modes")].to(); for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) opt.add(custom_fan_mode); } if (traits.get_supports_swing_modes()) { - JsonArray opt = root["swing_modes"].to(); + JsonArray opt = root[ESPHOME_F("swing_modes")].to(); for (auto swing_mode : traits.get_supported_swing_modes()) opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode))); } if (traits.get_supports_presets() && obj->preset.has_value()) { - JsonArray opt = root["presets"].to(); + JsonArray opt = root[ESPHOME_F("presets")].to(); for (climate::ClimatePreset m : traits.get_supported_presets()) opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m))); } if (!traits.get_supported_custom_presets().empty() && obj->has_custom_preset()) { - JsonArray opt = root["custom_presets"].to(); + JsonArray opt = root[ESPHOME_F("custom_presets")].to(); for (auto const &custom_preset : traits.get_supported_custom_presets()) opt.add(custom_preset); } @@ -1370,49 +1371,50 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf } bool has_state = false; - root["mode"] = PSTR_LOCAL(climate_mode_to_string(obj->mode)); - root["max_temp"] = value_accuracy_to_string(traits.get_visual_max_temperature(), target_accuracy); - root["min_temp"] = value_accuracy_to_string(traits.get_visual_min_temperature(), target_accuracy); - root["step"] = traits.get_visual_target_temperature_step(); + root[ESPHOME_F("mode")] = PSTR_LOCAL(climate_mode_to_string(obj->mode)); + root[ESPHOME_F("max_temp")] = value_accuracy_to_string(traits.get_visual_max_temperature(), target_accuracy); + root[ESPHOME_F("min_temp")] = value_accuracy_to_string(traits.get_visual_min_temperature(), target_accuracy); + root[ESPHOME_F("step")] = traits.get_visual_target_temperature_step(); if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) { - root["action"] = PSTR_LOCAL(climate_action_to_string(obj->action)); - root["state"] = root["action"]; + root[ESPHOME_F("action")] = PSTR_LOCAL(climate_action_to_string(obj->action)); + root[ESPHOME_F("state")] = root[ESPHOME_F("action")]; has_state = true; } if (traits.get_supports_fan_modes() && obj->fan_mode.has_value()) { - root["fan_mode"] = PSTR_LOCAL(climate_fan_mode_to_string(obj->fan_mode.value())); + root[ESPHOME_F("fan_mode")] = PSTR_LOCAL(climate_fan_mode_to_string(obj->fan_mode.value())); } if (!traits.get_supported_custom_fan_modes().empty() && obj->has_custom_fan_mode()) { - root["custom_fan_mode"] = obj->get_custom_fan_mode(); + root[ESPHOME_F("custom_fan_mode")] = obj->get_custom_fan_mode(); } if (traits.get_supports_presets() && obj->preset.has_value()) { - root["preset"] = PSTR_LOCAL(climate_preset_to_string(obj->preset.value())); + root[ESPHOME_F("preset")] = PSTR_LOCAL(climate_preset_to_string(obj->preset.value())); } if (!traits.get_supported_custom_presets().empty() && obj->has_custom_preset()) { - root["custom_preset"] = obj->get_custom_preset(); + root[ESPHOME_F("custom_preset")] = obj->get_custom_preset(); } if (traits.get_supports_swing_modes()) { - root["swing_mode"] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode)); + root[ESPHOME_F("swing_mode")] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode)); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) { if (!std::isnan(obj->current_temperature)) { - root["current_temperature"] = value_accuracy_to_string(obj->current_temperature, current_accuracy); + root[ESPHOME_F("current_temperature")] = value_accuracy_to_string(obj->current_temperature, current_accuracy); } else { - root["current_temperature"] = "NA"; + root[ESPHOME_F("current_temperature")] = "NA"; } } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { - root["target_temperature_low"] = value_accuracy_to_string(obj->target_temperature_low, target_accuracy); - root["target_temperature_high"] = value_accuracy_to_string(obj->target_temperature_high, target_accuracy); + root[ESPHOME_F("target_temperature_low")] = value_accuracy_to_string(obj->target_temperature_low, target_accuracy); + root[ESPHOME_F("target_temperature_high")] = + value_accuracy_to_string(obj->target_temperature_high, target_accuracy); if (!has_state) { - root["state"] = value_accuracy_to_string((obj->target_temperature_high + obj->target_temperature_low) / 2.0f, - target_accuracy); + root[ESPHOME_F("state")] = value_accuracy_to_string( + (obj->target_temperature_high + obj->target_temperature_low) / 2.0f, target_accuracy); } } else { - root["target_temperature"] = value_accuracy_to_string(obj->target_temperature, target_accuracy); + root[ESPHOME_F("target_temperature")] = value_accuracy_to_string(obj->target_temperature, target_accuracy); if (!has_state) - root["state"] = root["target_temperature"]; + root[ESPHOME_F("state")] = root[ESPHOME_F("target_temperature")]; } return builder.serialize(); @@ -1566,10 +1568,10 @@ std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) { set_json_icon_state_value(root, obj, "valve", obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position, start_config); char buf[PSTR_LOCAL_SIZE]; - root["current_operation"] = PSTR_LOCAL(valve::valve_operation_to_str(obj->current_operation)); + root[ESPHOME_F("current_operation")] = PSTR_LOCAL(valve::valve_operation_to_str(obj->current_operation)); if (obj->get_traits().get_supports_position()) - root["position"] = obj->position; + root[ESPHOME_F("position")] = obj->position; if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); } @@ -1701,14 +1703,14 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty set_json_id(root, obj, "event", start_config); if (!event_type.empty()) { - root["event_type"] = event_type; + root[ESPHOME_F("event_type")] = event_type; } if (start_config == DETAIL_ALL) { - JsonArray event_types = root["event_types"].to(); + JsonArray event_types = root[ESPHOME_F("event_types")].to(); for (const char *event_type : obj->get_event_types()) { event_types.add(event_type); } - root["device_class"] = obj->get_device_class_ref(); + root[ESPHOME_F("device_class")] = obj->get_device_class_ref(); this->add_sorting_info_(root, obj); } @@ -1774,10 +1776,10 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c set_json_icon_state_value(root, obj, "update", PSTR_LOCAL(update_state_to_string(obj->state)), obj->update_info.latest_version, start_config); if (start_config == DETAIL_ALL) { - root["current_version"] = obj->update_info.current_version; - root["title"] = obj->update_info.title; - root["summary"] = obj->update_info.summary; - root["release_url"] = obj->update_info.release_url; + root[ESPHOME_F("current_version")] = obj->update_info.current_version; + root[ESPHOME_F("title")] = obj->update_info.title; + root[ESPHOME_F("summary")] = obj->update_info.summary; + root[ESPHOME_F("release_url")] = obj->update_info.release_url; this->add_sorting_info_(root, obj); } @@ -2063,9 +2065,9 @@ bool WebServer::isRequestHandlerTrivial() const { return false; } void WebServer::add_sorting_info_(JsonObject &root, EntityBase *entity) { #ifdef USE_WEBSERVER_SORTING if (this->sorting_entitys_.find(entity) != this->sorting_entitys_.end()) { - root["sorting_weight"] = this->sorting_entitys_[entity].weight; + root[ESPHOME_F("sorting_weight")] = this->sorting_entitys_[entity].weight; if (this->sorting_groups_.find(this->sorting_entitys_[entity].group_id) != this->sorting_groups_.end()) { - root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[entity].group_id].name; + root[ESPHOME_F("sorting_group")] = this->sorting_groups_[this->sorting_entitys_[entity].group_id].name; } } #endif diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index fbf0d00c06..54ec997671 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -6,20 +6,7 @@ #include #include "esphome/core/component.h" - -// Platform-agnostic macros for web server components -// On ESP32 (both Arduino and IDF): Use plain strings (no PROGMEM) -// On ESP8266: Use Arduino's F() macro for PROGMEM strings -#ifdef USE_ESP32 -#define ESPHOME_F(string_literal) (string_literal) -#define ESPHOME_PGM_P const char * -#define ESPHOME_strncpy_P strncpy -#else -// ESP8266 uses Arduino macros -#define ESPHOME_F(string_literal) F(string_literal) -#define ESPHOME_PGM_P PGM_P -#define ESPHOME_strncpy_P strncpy_P -#endif +#include "esphome/core/progmem.h" #if USE_ESP32 #include "esphome/core/hal.h" diff --git a/esphome/core/progmem.h b/esphome/core/progmem.h new file mode 100644 index 0000000000..67131fd113 --- /dev/null +++ b/esphome/core/progmem.h @@ -0,0 +1,16 @@ +#pragma once + +// Platform-agnostic macros for PROGMEM string handling +// On ESP32 (both Arduino and IDF): Use plain strings (no PROGMEM) +// On ESP8266/Arduino: Use Arduino's F() macro for PROGMEM strings + +#ifdef USE_ESP32 +#define ESPHOME_F(string_literal) (string_literal) +#define ESPHOME_PGM_P const char * +#define ESPHOME_strncpy_P strncpy +#else +// ESP8266 and other Arduino platforms use Arduino macros +#define ESPHOME_F(string_literal) F(string_literal) +#define ESPHOME_PGM_P PGM_P +#define ESPHOME_strncpy_P strncpy_P +#endif From 2f75962b19ed472604b1b4f938f8e70f5a879ea0 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:40:46 -0500 Subject: [PATCH 0480/1145] [analog_threshold] Fix oscillation when using invert filter (#12251) Co-authored-by: Claude --- .../analog_threshold_binary_sensor.cpp | 11 +++++++---- .../analog_threshold/analog_threshold_binary_sensor.h | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp b/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp index f83f2aff08..0b3bd0e472 100644 --- a/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp +++ b/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp @@ -12,10 +12,11 @@ void AnalogThresholdBinarySensor::setup() { // TRUE state is defined to be when sensor is >= threshold // so when undefined sensor value initialize to FALSE if (std::isnan(sensor_value)) { + this->raw_state_ = false; this->publish_initial_state(false); } else { - this->publish_initial_state(sensor_value >= - (this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f); + this->raw_state_ = sensor_value >= (this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f; + this->publish_initial_state(this->raw_state_); } } @@ -25,8 +26,10 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) { this->sensor_->add_on_state_callback([this](float sensor_value) { // if there is an invalid sensor reading, ignore the change and keep the current state if (!std::isnan(sensor_value)) { - this->publish_state(sensor_value >= - (this->state ? this->lower_threshold_.value() : this->upper_threshold_.value())); + // Use raw_state_ for hysteresis logic, not this->state which is post-filter + this->raw_state_ = + sensor_value >= (this->raw_state_ ? this->lower_threshold_.value() : this->upper_threshold_.value()); + this->publish_state(this->raw_state_); } }); } diff --git a/esphome/components/analog_threshold/analog_threshold_binary_sensor.h b/esphome/components/analog_threshold/analog_threshold_binary_sensor.h index 55d6b15c36..9ea95d8570 100644 --- a/esphome/components/analog_threshold/analog_threshold_binary_sensor.h +++ b/esphome/components/analog_threshold/analog_threshold_binary_sensor.h @@ -20,6 +20,7 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina sensor::Sensor *sensor_{nullptr}; TemplatableValue upper_threshold_{}; TemplatableValue lower_threshold_{}; + bool raw_state_{false}; // Pre-filter state for hysteresis logic }; } // namespace analog_threshold From 708496c10116dae0e6268983bcf368da7e6ad09e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:45:38 -0600 Subject: [PATCH 0481/1145] Bump actions/checkout from 6.0.0 to 6.0.1 (#12259) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto-label-pr.yml | 2 +- .github/workflows/ci-api-proto.yml | 2 +- .github/workflows/ci-clang-tidy-hash.yml | 2 +- .github/workflows/ci-docker.yml | 2 +- .../workflows/ci-memory-impact-comment.yml | 2 +- .github/workflows/ci.yml | 30 +++++++++---------- .github/workflows/codeql.yml | 2 +- .github/workflows/release.yml | 8 ++--- .github/workflows/sync-device-classes.yml | 4 +-- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/auto-label-pr.yml b/.github/workflows/auto-label-pr.yml index d09072d814..39164fc2ea 100644 --- a/.github/workflows/auto-label-pr.yml +++ b/.github/workflows/auto-label-pr.yml @@ -22,7 +22,7 @@ jobs: if: github.event.action != 'labeled' || github.event.sender.type != 'Bot' steps: - name: Checkout - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Generate a token id: generate-token diff --git a/.github/workflows/ci-api-proto.yml b/.github/workflows/ci-api-proto.yml index 2bee5ed211..a0c6568345 100644 --- a/.github/workflows/ci-api-proto.yml +++ b/.github/workflows/ci-api-proto.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Set up Python uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: diff --git a/.github/workflows/ci-clang-tidy-hash.yml b/.github/workflows/ci-clang-tidy-hash.yml index 1826ed27cf..94068c19d6 100644 --- a/.github/workflows/ci-clang-tidy-hash.yml +++ b/.github/workflows/ci-clang-tidy-hash.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Set up Python uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index c76d9cf2a5..bf7fa0c262 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -43,7 +43,7 @@ jobs: - "docker" # - "lint" steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Set up Python uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: diff --git a/.github/workflows/ci-memory-impact-comment.yml b/.github/workflows/ci-memory-impact-comment.yml index 6ca58e252e..7e81e1184d 100644 --- a/.github/workflows/ci-memory-impact-comment.yml +++ b/.github/workflows/ci-memory-impact-comment.yml @@ -49,7 +49,7 @@ jobs: - name: Check out code from base repository if: steps.pr.outputs.skip != 'true' - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: # Always check out from the base repository (esphome/esphome), never from forks # Use the PR's target branch to ensure we run trusted code from the main repo diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9cfc02d5cf..9ef6b4341c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: cache-key: ${{ steps.cache-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Generate cache-key id: cache-key run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT @@ -70,7 +70,7 @@ jobs: if: needs.determine-jobs.outputs.python-linters == 'true' steps: - name: Check out code from GitHub - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -91,7 +91,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -132,7 +132,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Restore Python id: restore-python uses: ./.github/actions/restore-python @@ -183,7 +183,7 @@ jobs: component-test-batches: ${{ steps.determine.outputs.component-test-batches }} steps: - name: Check out code from GitHub - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: # Fetch enough history to find the merge base fetch-depth: 2 @@ -237,7 +237,7 @@ jobs: if: needs.determine-jobs.outputs.integration-tests == 'true' steps: - name: Check out code from GitHub - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Set up Python 3.13 id: python uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 @@ -273,7 +273,7 @@ jobs: if: github.event_name == 'pull_request' && (needs.determine-jobs.outputs.cpp-unit-tests-run-all == 'true' || needs.determine-jobs.outputs.cpp-unit-tests-components != '[]') steps: - name: Check out code from GitHub - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Restore Python uses: ./.github/actions/restore-python @@ -321,7 +321,7 @@ jobs: steps: - name: Check out code from GitHub - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: # Need history for HEAD~1 to work for checking changed files fetch-depth: 2 @@ -400,7 +400,7 @@ jobs: GH_TOKEN: ${{ github.token }} steps: - name: Check out code from GitHub - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: # Need history for HEAD~1 to work for checking changed files fetch-depth: 2 @@ -489,7 +489,7 @@ jobs: steps: - name: Check out code from GitHub - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: # Need history for HEAD~1 to work for checking changed files fetch-depth: 2 @@ -577,7 +577,7 @@ jobs: version: 1.0 - name: Check out code from GitHub - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -662,7 +662,7 @@ jobs: if: github.event_name == 'pull_request' && !startsWith(github.base_ref, 'beta') && !startsWith(github.base_ref, 'release') steps: - name: Check out code from GitHub - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -688,7 +688,7 @@ jobs: skip: ${{ steps.check-script.outputs.skip }} steps: - name: Check out target branch - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.base_ref }} @@ -840,7 +840,7 @@ jobs: flash_usage: ${{ steps.extract.outputs.flash_usage }} steps: - name: Check out PR branch - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -908,7 +908,7 @@ jobs: GH_TOKEN: ${{ github.token }} steps: - name: Check out code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Restore Python uses: ./.github/actions/restore-python with: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 33f587a748..d9b6bcdcca 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -54,7 +54,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1ff810d869..d52595bbb3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: branch_build: ${{ steps.tag.outputs.branch_build }} deploy_env: ${{ steps.tag.outputs.deploy_env }} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Get tag id: tag # yamllint disable rule:line-length @@ -60,7 +60,7 @@ jobs: contents: read id-token: write steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Set up Python uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: @@ -92,7 +92,7 @@ jobs: os: "ubuntu-24.04-arm" steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Set up Python uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: @@ -168,7 +168,7 @@ jobs: - ghcr - dockerhub steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Download digests uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index baaa29df2c..ea81a1e013 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -13,10 +13,10 @@ jobs: if: github.repository == 'esphome/esphome' steps: - name: Checkout - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Checkout Home Assistant - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: home-assistant/core path: lib/home-assistant From ab60ae092d095277e6f360f401e8e4b4057d2f96 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Tue, 2 Dec 2025 23:17:24 +0100 Subject: [PATCH 0482/1145] [tests] Allow substitution tests to run independently for debugging (#12224) Co-authored-by: J. Nick Koston --- tests/unit_tests/test_substitutions.py | 119 ++++++++++++------------- 1 file changed, 59 insertions(+), 60 deletions(-) diff --git a/tests/unit_tests/test_substitutions.py b/tests/unit_tests/test_substitutions.py index c5e6618ea6..eb9ef5443c 100644 --- a/tests/unit_tests/test_substitutions.py +++ b/tests/unit_tests/test_substitutions.py @@ -2,13 +2,16 @@ import glob import logging from pathlib import Path from typing import Any -from unittest.mock import patch +from unittest.mock import MagicMock, patch + +import pytest from esphome import config as config_module, yaml_util from esphome.components import substitutions +from esphome.components.packages import do_packages_pass from esphome.config import resolve_extend_remove from esphome.config_helpers import merge_config -from esphome.const import CONF_PACKAGES, CONF_SUBSTITUTIONS +from esphome.const import CONF_SUBSTITUTIONS from esphome.core import CORE from esphome.util import OrderedDict @@ -91,13 +94,22 @@ REMOTES = { ("https://github.com/esphome/repo2", "main"): "remotes/repo2/main", } +# Collect all input YAML files for test_substitutions_fixtures parametrized tests: +HERE = Path(__file__).parent +BASE_DIR = HERE / "fixtures" / "substitutions" +SOURCES = sorted(glob.glob(str(BASE_DIR / "*.input.yaml"))) +assert SOURCES, f"test_substitutions_fixtures: No input YAML files found in {BASE_DIR}" + +@pytest.mark.parametrize( + "source_path", + [Path(p) for p in SOURCES], + ids=lambda p: p.name, +) @patch("esphome.git.clone_or_update") -def test_substitutions_fixtures(mock_clone_or_update, fixture_path): - base_dir = fixture_path / "substitutions" - sources = sorted(glob.glob(str(base_dir / "*.input.yaml"))) - assert sources, f"No input YAML files found in {base_dir}" - +def test_substitutions_fixtures( + mock_clone_or_update: MagicMock, source_path: Path +) -> None: def fake_clone_or_update( *, url: str, @@ -116,72 +128,59 @@ def test_substitutions_fixtures(mock_clone_or_update, fixture_path): raise RuntimeError( f"Cannot find test repository for {url} @ {ref}. Check the REMOTES mapping in test_substitutions.py" ) - return base_dir / path, None + return BASE_DIR / path, None mock_clone_or_update.side_effect = fake_clone_or_update - failures = [] - for source_path in sources: - source_path = Path(source_path) - try: - expected_path = source_path.with_suffix("").with_suffix(".approved.yaml") - test_case = source_path.with_suffix("").stem + expected_path = source_path.with_suffix("").with_suffix(".approved.yaml") + test_case = source_path.with_suffix("").stem - # Load using ESPHome's YAML loader - config = yaml_util.load_yaml(source_path) + # Load using ESPHome's YAML loader + config = yaml_util.load_yaml(source_path) - if CONF_PACKAGES in config: - from esphome.components.packages import do_packages_pass + config = do_packages_pass(config) - config = do_packages_pass(config) + substitutions.do_substitution_pass(config, None) - substitutions.do_substitution_pass(config, None) + resolve_extend_remove(config) + verify_database_result = verify_database(config) + if verify_database_result is not None: + raise AssertionError(verify_database_result) - resolve_extend_remove(config) - verify_database_result = verify_database(config) - if verify_database_result is not None: - raise AssertionError(verify_database_result) + # Also load expected using ESPHome's loader, or use {} if missing and DEV_MODE + if expected_path.is_file(): + expected = yaml_util.load_yaml(expected_path) + elif DEV_MODE: + expected = {} + else: + assert expected_path.is_file(), f"Expected file missing: {expected_path}" - # Also load expected using ESPHome's loader, or use {} if missing and DEV_MODE - if expected_path.is_file(): - expected = yaml_util.load_yaml(expected_path) - elif DEV_MODE: - expected = {} - else: - assert expected_path.is_file(), ( - f"Expected file missing: {expected_path}" - ) + # Sort dicts only (not lists) for comparison + got_sorted = sort_dicts(config) + expected_sorted = sort_dicts(expected) - # Sort dicts only (not lists) for comparison - got_sorted = sort_dicts(config) - expected_sorted = sort_dicts(expected) - - if got_sorted != expected_sorted: - diff = "\n".join(dict_diff(got_sorted, expected_sorted)) - msg = ( - f"Substitution result mismatch for {source_path.name}\n" - f"Diff:\n{diff}\n\n" - f"Got: {got_sorted}\n" - f"Expected: {expected_sorted}" - ) - # Write out the received file when test fails - if DEV_MODE: - received_path = source_path.with_name(f"{test_case}.received.yaml") - write_yaml(received_path, config) - print(msg) - failures.append(msg) - else: - raise AssertionError(msg) - except Exception as err: - _LOGGER.error("Error in test file %s", source_path) - raise err - - if DEV_MODE and failures: - print(f"\n{len(failures)} substitution test case(s) failed.") + if got_sorted != expected_sorted: + diff = "\n".join(dict_diff(got_sorted, expected_sorted)) + msg = ( + f"Substitution result mismatch for {source_path.name}\n" + f"Diff:\n{diff}\n\n" + f"Got: {got_sorted}\n" + f"Expected: {expected_sorted}" + ) + # Write out the received file when test fails + if DEV_MODE: + received_path = source_path.with_name(f"{test_case}.received.yaml") + write_yaml(received_path, config) + msg += f"\nWrote received file to {received_path}." + raise AssertionError(msg) if DEV_MODE: _LOGGER.error("Tests passed, but Dev mode is enabled.") - assert not DEV_MODE # make sure DEV_MODE is disabled after you are finished. + assert ( + not DEV_MODE # make sure DEV_MODE is disabled after you are finished. + ), ( + "Test passed but DEV_MODE must be disabled when running tests. Please set DEV_MODE=False." + ) def test_substitutions_with_command_line_maintains_ordered_dict() -> None: From 6f91c75f8605af0e0be3770755e788e94cdb2825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=2E=20=C3=81rkosi=20R=C3=B3bert?= Date: Wed, 3 Dec 2025 10:20:17 +0100 Subject: [PATCH 0483/1145] [gree] `turbo`, `light`, `health`, `xfan` switches (#12160) Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/gree/__init__.py | 3 + esphome/components/gree/climate.py | 6 +- esphome/components/gree/gree.cpp | 26 ++++- esphome/components/gree/gree.h | 102 +++++++++--------- esphome/components/gree/switch/__init__.py | 74 +++++++++++++ .../components/gree/switch/gree_switch.cpp | 24 +++++ esphome/components/gree/switch/gree_switch.h | 24 +++++ tests/components/gree/common.yaml | 15 ++- 9 files changed, 218 insertions(+), 57 deletions(-) create mode 100644 esphome/components/gree/switch/__init__.py create mode 100644 esphome/components/gree/switch/gree_switch.cpp create mode 100644 esphome/components/gree/switch/gree_switch.h diff --git a/CODEOWNERS b/CODEOWNERS index 7861871323..dbeeb56f8f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -190,6 +190,7 @@ esphome/components/gps/* @coogle @ximex esphome/components/graph/* @synco esphome/components/graphical_display_menu/* @MrMDavidson esphome/components/gree/* @orestismers +esphome/components/gree/switch/* @nagyrobi esphome/components/grove_gas_mc_v2/* @YorkshireIoT esphome/components/grove_tb6612fng/* @max246 esphome/components/growatt_solar/* @leeuwte diff --git a/esphome/components/gree/__init__.py b/esphome/components/gree/__init__.py index e69de29bb2..2dd9ac0f1c 100644 --- a/esphome/components/gree/__init__.py +++ b/esphome/components/gree/__init__.py @@ -0,0 +1,3 @@ +import esphome.codegen as cg + +gree_ns = cg.esphome_ns.namespace("gree") diff --git a/esphome/components/gree/climate.py b/esphome/components/gree/climate.py index 057ba67b94..0892155fd2 100644 --- a/esphome/components/gree/climate.py +++ b/esphome/components/gree/climate.py @@ -3,11 +3,11 @@ from esphome.components import climate_ir import esphome.config_validation as cv from esphome.const import CONF_MODEL +from . import gree_ns + CODEOWNERS = ["@orestismers"] AUTO_LOAD = ["climate_ir"] - -gree_ns = cg.esphome_ns.namespace("gree") GreeClimate = gree_ns.class_("GreeClimate", climate_ir.ClimateIR) Model = gree_ns.enum("Model") @@ -23,7 +23,7 @@ MODELS = { CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(GreeClimate).extend( { - cv.Required(CONF_MODEL): cv.enum(MODELS), + cv.Required(CONF_MODEL): cv.enum(MODELS, lower=True), } ) diff --git a/esphome/components/gree/gree.cpp b/esphome/components/gree/gree.cpp index e0cacb4f1e..b8cf8a39a8 100644 --- a/esphome/components/gree/gree.cpp +++ b/esphome/components/gree/gree.cpp @@ -16,13 +16,28 @@ void GreeClimate::set_model(Model model) { this->model_ = model; } +void GreeClimate::set_mode_bit(uint8_t bit_mask, bool enabled) { + if (enabled) { + this->mode_bits_ |= bit_mask; + } else { + this->mode_bits_ &= ~bit_mask; + } + this->transmit_state(); +} + void GreeClimate::transmit_state() { uint8_t remote_state[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00}; remote_state[0] = this->fan_speed_() | this->operation_mode_(); remote_state[1] = this->temperature_(); - if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF || this->model_ == GREE_YAG) { + if (this->model_ == GREE_YAN) { + remote_state[2] = 0x20; // bits 0..3 always 0000, bits 4..7 TURBO, LIGHT, HEALTH, X-FAN + remote_state[3] = 0x50; // bits 4..7 always 0101 + remote_state[4] = this->vertical_swing_(); + } + + if (this->model_ == GREE_YX1FF || this->model_ == GREE_YAG) { remote_state[2] = 0x60; remote_state[3] = 0x50; remote_state[4] = this->vertical_swing_(); @@ -41,7 +56,7 @@ void GreeClimate::transmit_state() { } if (this->model_ == GREE_YAA || this->model_ == GREE_YAC || this->model_ == GREE_YAC1FB9) { - remote_state[2] = 0x20; // bits 0..3 always 0000, bits 4..7 TURBO,LIGHT,HEALTH,X-FAN + remote_state[2] = 0x20; // bits 0..3 always 0000, bits 4..7 TURBO, LIGHT, HEALTH, X-FAN remote_state[3] = 0x50; // bits 4..7 always 0101 remote_state[6] = 0x20; // YAA1FB, FAA1FB1, YB1F2 bits 4..7 always 0010 @@ -52,6 +67,13 @@ void GreeClimate::transmit_state() { } } + if (this->model_ == GREE_YAN || this->model_ == GREE_YAA || this->model_ == GREE_YAC || + this->model_ == GREE_YAC1FB9) { + // Merge the mode bits into remote_state[2] + // Clear the mode bits (bits 4-7) and OR in the current mode_bits_ + remote_state[2] = (remote_state[2] & 0x0F) | this->mode_bits_; + } + if (this->model_ == GREE_YX1FF) { if (this->fan_speed_() == GREE_FAN_TURBO) { remote_state[2] |= GREE_FAN_TURBO_BIT; diff --git a/esphome/components/gree/gree.h b/esphome/components/gree/gree.h index f91d78cabd..24453750ae 100644 --- a/esphome/components/gree/gree.h +++ b/esphome/components/gree/gree.h @@ -2,80 +2,79 @@ #include "esphome/components/climate_ir/climate_ir.h" -namespace esphome { -namespace gree { +namespace esphome::gree { // Values for GREE IR Controllers // Temperature -const uint8_t GREE_TEMP_MIN = 16; // Celsius -const uint8_t GREE_TEMP_MAX = 30; // Celsius +static constexpr uint8_t GREE_TEMP_MIN = 16; // Celsius +static constexpr uint8_t GREE_TEMP_MAX = 30; // Celsius // Modes -const uint8_t GREE_MODE_AUTO = 0x00; -const uint8_t GREE_MODE_COOL = 0x01; -const uint8_t GREE_MODE_HEAT = 0x04; -const uint8_t GREE_MODE_DRY = 0x02; -const uint8_t GREE_MODE_FAN = 0x03; +static constexpr uint8_t GREE_MODE_AUTO = 0x00; +static constexpr uint8_t GREE_MODE_COOL = 0x01; +static constexpr uint8_t GREE_MODE_HEAT = 0x04; +static constexpr uint8_t GREE_MODE_DRY = 0x02; +static constexpr uint8_t GREE_MODE_FAN = 0x03; -const uint8_t GREE_MODE_OFF = 0x00; -const uint8_t GREE_MODE_ON = 0x08; +static constexpr uint8_t GREE_MODE_OFF = 0x00; +static constexpr uint8_t GREE_MODE_ON = 0x08; // Fan Speed -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; +static constexpr uint8_t GREE_FAN_AUTO = 0x00; +static constexpr uint8_t GREE_FAN_1 = 0x10; +static constexpr uint8_t GREE_FAN_2 = 0x20; +static constexpr uint8_t GREE_FAN_3 = 0x30; // IR Transmission -const uint32_t GREE_IR_FREQUENCY = 38000; -const uint32_t GREE_HEADER_MARK = 9000; -const uint32_t GREE_HEADER_SPACE = 4000; -const uint32_t GREE_BIT_MARK = 620; -const uint32_t GREE_ONE_SPACE = 1600; -const uint32_t GREE_ZERO_SPACE = 540; -const uint32_t GREE_MESSAGE_SPACE = 19000; +static constexpr uint32_t GREE_IR_FREQUENCY = 38000; +static constexpr uint32_t GREE_HEADER_MARK = 9000; +static constexpr uint32_t GREE_HEADER_SPACE = 4000; +static constexpr uint32_t GREE_BIT_MARK = 620; +static constexpr uint32_t GREE_ONE_SPACE = 1600; +static constexpr uint32_t GREE_ZERO_SPACE = 540; +static constexpr uint32_t GREE_MESSAGE_SPACE = 19000; // Timing specific for YAC features (I-Feel mode) -const uint32_t GREE_YAC_HEADER_MARK = 6000; -const uint32_t GREE_YAC_HEADER_SPACE = 3000; -const uint32_t GREE_YAC_BIT_MARK = 650; +static constexpr uint32_t GREE_YAC_HEADER_MARK = 6000; +static constexpr uint32_t GREE_YAC_HEADER_SPACE = 3000; +static constexpr uint32_t GREE_YAC_BIT_MARK = 650; // Timing specific to YAC1FB9 -const uint32_t GREE_YAC1FB9_HEADER_SPACE = 4500; -const uint32_t GREE_YAC1FB9_MESSAGE_SPACE = 19980; +static constexpr uint32_t GREE_YAC1FB9_HEADER_SPACE = 4500; +static constexpr uint32_t GREE_YAC1FB9_MESSAGE_SPACE = 19980; // State Frame size -const uint8_t GREE_STATE_FRAME_SIZE = 8; +static constexpr uint8_t GREE_STATE_FRAME_SIZE = 8; // Only available on YAN // Vertical air directions. Note that these cannot be set on all heat pumps -const uint8_t GREE_VDIR_AUTO = 0x00; -const uint8_t GREE_VDIR_MANUAL = 0x00; -const uint8_t GREE_VDIR_SWING = 0x01; -const uint8_t GREE_VDIR_UP = 0x02; -const uint8_t GREE_VDIR_MUP = 0x03; -const uint8_t GREE_VDIR_MIDDLE = 0x04; -const uint8_t GREE_VDIR_MDOWN = 0x05; -const uint8_t GREE_VDIR_DOWN = 0x06; +static constexpr uint8_t GREE_VDIR_AUTO = 0x00; +static constexpr uint8_t GREE_VDIR_MANUAL = 0x00; +static constexpr uint8_t GREE_VDIR_SWING = 0x01; +static constexpr uint8_t GREE_VDIR_UP = 0x02; +static constexpr uint8_t GREE_VDIR_MUP = 0x03; +static constexpr uint8_t GREE_VDIR_MIDDLE = 0x04; +static constexpr uint8_t GREE_VDIR_MDOWN = 0x05; +static constexpr uint8_t GREE_VDIR_DOWN = 0x06; // Only available on YAC/YAG // Horizontal air directions. Note that these cannot be set on all heat pumps -const uint8_t GREE_HDIR_AUTO = 0x00; -const uint8_t GREE_HDIR_MANUAL = 0x00; -const uint8_t GREE_HDIR_SWING = 0x01; -const uint8_t GREE_HDIR_LEFT = 0x02; -const uint8_t GREE_HDIR_MLEFT = 0x03; -const uint8_t GREE_HDIR_MIDDLE = 0x04; -const uint8_t GREE_HDIR_MRIGHT = 0x05; -const uint8_t GREE_HDIR_RIGHT = 0x06; +static constexpr uint8_t GREE_HDIR_AUTO = 0x00; +static constexpr uint8_t GREE_HDIR_MANUAL = 0x00; +static constexpr uint8_t GREE_HDIR_SWING = 0x01; +static constexpr uint8_t GREE_HDIR_LEFT = 0x02; +static constexpr uint8_t GREE_HDIR_MLEFT = 0x03; +static constexpr uint8_t GREE_HDIR_MIDDLE = 0x04; +static constexpr uint8_t GREE_HDIR_MRIGHT = 0x05; +static constexpr 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; +static constexpr uint8_t GREE_FAN_TURBO = 0x80; +static constexpr uint8_t GREE_FAN_TURBO_BIT = 0x10; +static constexpr uint8_t GREE_PRESET_NONE = 0x00; +static constexpr uint8_t GREE_PRESET_SLEEP = 0x01; +static constexpr uint8_t GREE_PRESET_SLEEP_BIT = 0x80; // Model codes enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9, GREE_YX1FF, GREE_YAG }; @@ -90,6 +89,7 @@ class GreeClimate : public climate_ir::ClimateIR { climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} void set_model(Model model); + void set_mode_bit(uint8_t bit_mask, bool enabled); protected: // Transmit via IR the state of this climate controller. @@ -103,7 +103,7 @@ class GreeClimate : public climate_ir::ClimateIR { uint8_t preset_(); Model model_{}; + uint8_t mode_bits_{0}; // Combined mode bits for remote_state[2] }; -} // namespace gree -} // namespace esphome +} // namespace esphome::gree diff --git a/esphome/components/gree/switch/__init__.py b/esphome/components/gree/switch/__init__.py new file mode 100644 index 0000000000..111fea65d2 --- /dev/null +++ b/esphome/components/gree/switch/__init__.py @@ -0,0 +1,74 @@ +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import CONF_LIGHT, DEVICE_CLASS_SWITCH, ENTITY_CATEGORY_CONFIG +import esphome.final_validate as fv + +from .. import gree_ns +from ..climate import CONF_MODEL, GreeClimate + +CODEOWNERS = ["@nagyrobi"] + +GreeModeBitSwitch = gree_ns.class_("GreeModeBitSwitch", switch.Switch, cg.Component) + +CONF_TURBO = "turbo" +CONF_HEALTH = "health" +CONF_XFAN = "xfan" +CONF_GREE_ID = "gree_id" + +# Switch configurations: (config_key, display_name, bit_mask, icon) +SWITCH_CONFIGS = ( + (CONF_TURBO, "Gree Turbo Switch", 0x10, "mdi:car-turbocharger"), + (CONF_LIGHT, "Gree Light Switch", 0x20, "mdi:led-outline"), + (CONF_HEALTH, "Gree Health Switch", 0x40, "mdi:pine-tree"), + (CONF_XFAN, "Gree X-FAN Switch", 0x80, "mdi:wall-sconce-flat"), +) + +SUPPORTED_MODELS = { + "yan", + "yaa", + "yac", + "yac1fb9", +} + +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_GREE_ID): cv.use_id(GreeClimate), + **{ + cv.Optional(key): switch.switch_schema( + GreeModeBitSwitch, + icon=icon, + default_restore_mode="RESTORE_DEFAULT_OFF", + device_class=DEVICE_CLASS_SWITCH, + entity_category=ENTITY_CATEGORY_CONFIG, + ) + for key, _, _, icon in SWITCH_CONFIGS + }, + } +) + + +def _validate_model(config): + full_config = fv.full_config.get() + climate_path = full_config.get_path_for_id(config[CONF_GREE_ID])[:-1] + climate_conf = full_config.get_config_for_path(climate_path) + if climate_conf[CONF_MODEL] not in SUPPORTED_MODELS: + raise cv.Invalid( + "Gree switches are only supported for the " + + ", ".join(SUPPORTED_MODELS) + + " models" + ) + + +FINAL_VALIDATE_SCHEMA = _validate_model + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_GREE_ID]) + + for conf_key, name, bit_mask, _ in SWITCH_CONFIGS: + if switch_conf := config.get(conf_key): + sw = cg.new_Pvariable(switch_conf[cv.CONF_ID], name, bit_mask) + await switch.register_switch(sw, switch_conf) + await cg.register_component(sw, switch_conf) + await cg.register_parented(sw, parent) diff --git a/esphome/components/gree/switch/gree_switch.cpp b/esphome/components/gree/switch/gree_switch.cpp new file mode 100644 index 0000000000..13f14e5453 --- /dev/null +++ b/esphome/components/gree/switch/gree_switch.cpp @@ -0,0 +1,24 @@ +#include "gree_switch.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace gree { + +static const char *const TAG = "gree.switch"; + +void GreeModeBitSwitch::setup() { + auto initial = this->get_initial_state_with_restore_mode(); + if (initial.has_value()) { + this->write_state(*initial); + } +} + +void GreeModeBitSwitch::dump_config() { log_switch(TAG, " ", this->name_, this); } + +void GreeModeBitSwitch::write_state(bool state) { + this->parent_->set_mode_bit(this->bit_mask_, state); + this->publish_state(state); +} + +} // namespace gree +} // namespace esphome diff --git a/esphome/components/gree/switch/gree_switch.h b/esphome/components/gree/switch/gree_switch.h new file mode 100644 index 0000000000..239ac4bf17 --- /dev/null +++ b/esphome/components/gree/switch/gree_switch.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +#include "esphome/components/gree/gree.h" + +namespace esphome { +namespace gree { + +class GreeModeBitSwitch : public switch_::Switch, public Component, public Parented { + public: + GreeModeBitSwitch(const char *name, uint8_t bit_mask) : name_(name), bit_mask_(bit_mask) {} + + void setup() override; + void dump_config() override; + void write_state(bool state) override; + + protected: + const char *name_; + uint8_t bit_mask_; +}; + +} // namespace gree +} // namespace esphome diff --git a/tests/components/gree/common.yaml b/tests/components/gree/common.yaml index e706076034..1ddce781bb 100644 --- a/tests/components/gree/common.yaml +++ b/tests/components/gree/common.yaml @@ -1,5 +1,18 @@ climate: - platform: gree name: GREE - model: generic + id: my_gree_ac + model: YAN transmitter_id: xmitr + +switch: + - platform: gree + gree_id: my_gree_ac + light: + name: "AC Lights" + turbo: + name: "AC Turbo" + health: + name: "AC Health" + xfan: + name: "AC X-Fan" From 669bcad4584f5f06b817ee53fbe91624232f26e2 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:31:12 -0500 Subject: [PATCH 0484/1145] [rtl87xx] Fix FreeRTOS version for RTL8720C boards (#12261) Co-authored-by: Claude --- esphome/components/rtl87xx/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/rtl87xx/__init__.py b/esphome/components/rtl87xx/__init__.py index d24ffcea3d..8f27544108 100644 --- a/esphome/components/rtl87xx/__init__.py +++ b/esphome/components/rtl87xx/__init__.py @@ -10,7 +10,9 @@ import esphome.codegen as cg from esphome.components import libretiny from esphome.components.libretiny.const import ( COMPONENT_RTL87XX, + FAMILY_RTL8710B, KEY_COMPONENT_DATA, + KEY_FAMILY, KEY_LIBRETINY, LibreTinyComponent, ) @@ -48,7 +50,9 @@ CONFIG_SCHEMA.prepend_extra(_set_core_data) async def to_code(config): # Use FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake required by AsyncTCP 3.4.3+ # https://github.com/esphome/esphome/issues/10220 - cg.add_platformio_option("custom_versions.freertos", "8.2.3") + # Only for RTL8710B (ambz) - RTL8720C (ambz2) requires FreeRTOS 10.x + if CORE.data[KEY_LIBRETINY][KEY_FAMILY] == FAMILY_RTL8710B: + cg.add_platformio_option("custom_versions.freertos", "8.2.3") return await libretiny.component_to_code(config) From 87ac4baf3ace99e1ccba9e773722974aff3eeb78 Mon Sep 17 00:00:00 2001 From: lygris Date: Wed, 3 Dec 2025 09:42:04 -0600 Subject: [PATCH 0485/1145] [cc1101] Add new cc1101 component (#11849) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/cc1101/__init__.py | 220 +++++++ esphome/components/cc1101/cc1101.cpp | 550 +++++++++++++++++ esphome/components/cc1101/cc1101.h | 110 ++++ esphome/components/cc1101/cc1101defs.h | 644 ++++++++++++++++++++ esphome/components/cc1101/cc1101pa.h | 174 ++++++ tests/components/cc1101/common.yaml | 20 + tests/components/cc1101/test.esp32-idf.yaml | 8 + tests/components/cc1101/test.esp8266.yaml | 8 + 9 files changed, 1735 insertions(+) create mode 100644 esphome/components/cc1101/__init__.py create mode 100644 esphome/components/cc1101/cc1101.cpp create mode 100644 esphome/components/cc1101/cc1101.h create mode 100644 esphome/components/cc1101/cc1101defs.h create mode 100644 esphome/components/cc1101/cc1101pa.h create mode 100644 tests/components/cc1101/common.yaml create mode 100644 tests/components/cc1101/test.esp32-idf.yaml create mode 100644 tests/components/cc1101/test.esp8266.yaml diff --git a/CODEOWNERS b/CODEOWNERS index dbeeb56f8f..65405f79d1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -97,6 +97,7 @@ esphome/components/camera_encoder/* @DT-art1 esphome/components/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @mreditor97 esphome/components/captive_portal/* @esphome/core +esphome/components/cc1101/* @gabest11 @lygris esphome/components/ccs811/* @habbie esphome/components/cd74hc4067/* @asoehlke esphome/components/ch422g/* @clydebarrow @jesterret diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py new file mode 100644 index 0000000000..0f5743d0cd --- /dev/null +++ b/esphome/components/cc1101/__init__.py @@ -0,0 +1,220 @@ +from esphome import automation +from esphome.automation import maybe_simple_id +import esphome.codegen as cg +from esphome.components import spi +import esphome.config_validation as cv +from esphome.const import CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, CONF_WAIT_TIME + +CODEOWNERS = ["@lygris", "@gabest11"] +DEPENDENCIES = ["spi"] +MULTI_CONF = True + +ns = cg.esphome_ns.namespace("cc1101") +CC1101Component = ns.class_("CC1101Component", cg.Component, spi.SPIDevice) + +# Config keys +CONF_OUTPUT_POWER = "output_power" +CONF_RX_ATTENUATION = "rx_attenuation" +CONF_DC_BLOCKING_FILTER = "dc_blocking_filter" +CONF_IF_FREQUENCY = "if_frequency" +CONF_FILTER_BANDWIDTH = "filter_bandwidth" +CONF_CHANNEL_SPACING = "channel_spacing" +CONF_FSK_DEVIATION = "fsk_deviation" +CONF_MSK_DEVIATION = "msk_deviation" +CONF_SYMBOL_RATE = "symbol_rate" +CONF_SYNC_MODE = "sync_mode" +CONF_CARRIER_SENSE_ABOVE_THRESHOLD = "carrier_sense_above_threshold" +CONF_MODULATION_TYPE = "modulation_type" +CONF_MANCHESTER = "manchester" +CONF_NUM_PREAMBLE = "num_preamble" +CONF_SYNC1 = "sync1" +CONF_SYNC0 = "sync0" +CONF_PKTLEN = "pktlen" +CONF_MAGN_TARGET = "magn_target" +CONF_MAX_LNA_GAIN = "max_lna_gain" +CONF_MAX_DVGA_GAIN = "max_dvga_gain" +CONF_CARRIER_SENSE_ABS_THR = "carrier_sense_abs_thr" +CONF_CARRIER_SENSE_REL_THR = "carrier_sense_rel_thr" +CONF_LNA_PRIORITY = "lna_priority" +CONF_FILTER_LENGTH_FSK_MSK = "filter_length_fsk_msk" +CONF_FILTER_LENGTH_ASK_OOK = "filter_length_ask_ook" +CONF_FREEZE = "freeze" +CONF_HYST_LEVEL = "hyst_level" + +# Enums +SyncMode = ns.enum("SyncMode", True) +SYNC_MODE = { + "None": SyncMode.SYNC_MODE_NONE, + "15/16": SyncMode.SYNC_MODE_15_16, + "16/16": SyncMode.SYNC_MODE_16_16, + "30/32": SyncMode.SYNC_MODE_30_32, +} + +Modulation = ns.enum("Modulation", True) +MODULATION = { + "2-FSK": Modulation.MODULATION_2_FSK, + "GFSK": Modulation.MODULATION_GFSK, + "ASK/OOK": Modulation.MODULATION_ASK_OOK, + "4-FSK": Modulation.MODULATION_4_FSK, + "MSK": Modulation.MODULATION_MSK, +} + +RxAttenuation = ns.enum("RxAttenuation", True) +RX_ATTENUATION = { + "0dB": RxAttenuation.RX_ATTENUATION_0DB, + "6dB": RxAttenuation.RX_ATTENUATION_6DB, + "12dB": RxAttenuation.RX_ATTENUATION_12DB, + "18dB": RxAttenuation.RX_ATTENUATION_18DB, +} + +MagnTarget = ns.enum("MagnTarget", True) +MAGN_TARGET = { + "24dB": MagnTarget.MAGN_TARGET_24DB, + "27dB": MagnTarget.MAGN_TARGET_27DB, + "30dB": MagnTarget.MAGN_TARGET_30DB, + "33dB": MagnTarget.MAGN_TARGET_33DB, + "36dB": MagnTarget.MAGN_TARGET_36DB, + "38dB": MagnTarget.MAGN_TARGET_38DB, + "40dB": MagnTarget.MAGN_TARGET_40DB, + "42dB": MagnTarget.MAGN_TARGET_42DB, +} + +MaxLnaGain = ns.enum("MaxLnaGain", True) +MAX_LNA_GAIN = { + "Default": MaxLnaGain.MAX_LNA_GAIN_DEFAULT, + "2.6dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_2P6DB, + "6.1dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_6P1DB, + "7.4dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_7P4DB, + "9.2dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_9P2DB, + "11.5dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_11P5DB, + "14.6dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_14P6DB, + "17.1dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_17P1DB, +} + +MaxDvgaGain = ns.enum("MaxDvgaGain", True) +MAX_DVGA_GAIN = { + "Default": MaxDvgaGain.MAX_DVGA_GAIN_DEFAULT, + "-1": MaxDvgaGain.MAX_DVGA_GAIN_MINUS_1, + "-2": MaxDvgaGain.MAX_DVGA_GAIN_MINUS_2, + "-3": MaxDvgaGain.MAX_DVGA_GAIN_MINUS_3, +} + +CarrierSenseRelThr = ns.enum("CarrierSenseRelThr", True) +CARRIER_SENSE_REL_THR = { + "Default": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_DEFAULT, + "+6dB": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_PLUS_6DB, + "+10dB": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_PLUS_10DB, + "+14dB": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_PLUS_14DB, +} + +FilterLengthFskMsk = ns.enum("FilterLengthFskMsk", True) +FILTER_LENGTH_FSK_MSK = { + "8": FilterLengthFskMsk.FILTER_LENGTH_8DB, + "16": FilterLengthFskMsk.FILTER_LENGTH_16DB, + "32": FilterLengthFskMsk.FILTER_LENGTH_32DB, + "64": FilterLengthFskMsk.FILTER_LENGTH_64DB, +} + +FilterLengthAskOok = ns.enum("FilterLengthAskOok", True) +FILTER_LENGTH_ASK_OOK = { + "4dB": FilterLengthAskOok.FILTER_LENGTH_4DB, + "8dB": FilterLengthAskOok.FILTER_LENGTH_8DB, + "12dB": FilterLengthAskOok.FILTER_LENGTH_12DB, + "16dB": FilterLengthAskOok.FILTER_LENGTH_16DB, +} + +Freeze = ns.enum("Freeze", True) +FREEZE = { + "Default": Freeze.FREEZE_DEFAULT, + "On Sync": Freeze.FREEZE_ON_SYNC, + "Analog Only": Freeze.FREEZE_ANALOG_ONLY, + "Analog And Digital": Freeze.FREEZE_ANALOG_AND_DIGITAL, +} + +WaitTime = ns.enum("WaitTime", True) +WAIT_TIME = { + "8": WaitTime.WAIT_TIME_8_SAMPLES, + "16": WaitTime.WAIT_TIME_16_SAMPLES, + "24": WaitTime.WAIT_TIME_24_SAMPLES, + "32": WaitTime.WAIT_TIME_32_SAMPLES, +} + +HystLevel = ns.enum("HystLevel", True) +HYST_LEVEL = { + "None": HystLevel.HYST_LEVEL_NONE, + "Low": HystLevel.HYST_LEVEL_LOW, + "Medium": HystLevel.HYST_LEVEL_MEDIUM, + "High": HystLevel.HYST_LEVEL_HIGH, +} + +# Config key -> Validator mapping +CONFIG_MAP = { + CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0), + CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False), + CONF_DC_BLOCKING_FILTER: cv.boolean, + CONF_FREQUENCY: cv.float_range(min=300000.0, max=928000.0), + CONF_IF_FREQUENCY: cv.float_range(min=25, max=788), + CONF_FILTER_BANDWIDTH: cv.float_range(min=58.0, max=812.0), + CONF_CHANNEL: cv.uint8_t, + CONF_CHANNEL_SPACING: cv.float_range(min=25, max=405), + CONF_FSK_DEVIATION: cv.float_range(min=1.5, max=381), + CONF_MSK_DEVIATION: cv.int_range(min=1, max=8), + CONF_SYMBOL_RATE: cv.float_range(min=600, max=500000), + CONF_SYNC_MODE: cv.enum(SYNC_MODE, upper=False), + CONF_CARRIER_SENSE_ABOVE_THRESHOLD: cv.boolean, + CONF_MODULATION_TYPE: cv.enum(MODULATION, upper=False), + CONF_MANCHESTER: cv.boolean, + CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7), + CONF_SYNC1: cv.hex_uint8_t, + CONF_SYNC0: cv.hex_uint8_t, + CONF_PKTLEN: cv.uint8_t, + CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False), + CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False), + CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False), + CONF_CARRIER_SENSE_ABS_THR: cv.int_range(min=-8, max=7), + CONF_CARRIER_SENSE_REL_THR: cv.enum(CARRIER_SENSE_REL_THR, upper=False), + CONF_LNA_PRIORITY: cv.boolean, + CONF_FILTER_LENGTH_FSK_MSK: cv.enum(FILTER_LENGTH_FSK_MSK, upper=False), + CONF_FILTER_LENGTH_ASK_OOK: cv.enum(FILTER_LENGTH_ASK_OOK, upper=False), + CONF_FREEZE: cv.enum(FREEZE, upper=False), + CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False), + CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False), +} + +CONFIG_SCHEMA = ( + cv.Schema({cv.GenerateID(): cv.declare_id(CC1101Component)}) + .extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()}) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema(cs_pin_required=True)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) + + for key in CONFIG_MAP: + if key in config: + cg.add(getattr(var, f"set_{key}")(config[key])) + + +# Actions +BeginTxAction = ns.class_("BeginTxAction", automation.Action) +BeginRxAction = ns.class_("BeginRxAction", automation.Action) +ResetAction = ns.class_("ResetAction", automation.Action) +SetIdleAction = ns.class_("SetIdleAction", automation.Action) + +CC1101_ACTION_SCHEMA = cv.Schema( + maybe_simple_id({cv.GenerateID(CONF_ID): cv.use_id(CC1101Component)}) +) + + +@automation.register_action("cc1101.begin_tx", BeginTxAction, CC1101_ACTION_SCHEMA) +@automation.register_action("cc1101.begin_rx", BeginRxAction, CC1101_ACTION_SCHEMA) +@automation.register_action("cc1101.reset", ResetAction, CC1101_ACTION_SCHEMA) +@automation.register_action("cc1101.set_idle", SetIdleAction, CC1101_ACTION_SCHEMA) +async def cc1101_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp new file mode 100644 index 0000000000..1a758e415a --- /dev/null +++ b/esphome/components/cc1101/cc1101.cpp @@ -0,0 +1,550 @@ +#include "cc1101.h" +#include "cc1101pa.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include + +namespace esphome::cc1101 { + +static const char *const TAG = "cc1101"; + +static void split_float(float value, int mbits, uint8_t &e, uint32_t &m) { + int e_tmp; + float m_tmp = std::frexp(value, &e_tmp); + if (e_tmp <= mbits) { + e = 0; + m = 0; + return; + } + e = static_cast(e_tmp - mbits - 1); + m = static_cast(((m_tmp * 2 - 1) * (1 << (mbits + 1))) + 1) >> 1; + if (m == (1UL << mbits)) { + e = e + 1; + m = 0; + } +} + +CC1101Component::CC1101Component() { + // Datasheet defaults + memset(&this->state_, 0, sizeof(this->state_)); + this->state_.GDO2_CFG = 0x0D; // Serial Data (for RX on GDO2) + this->state_.GDO1_CFG = 0x2E; + this->state_.GDO0_CFG = 0x0D; // Serial Data (for RX on GDO0 / TX Input) + this->state_.FIFO_THR = 7; + this->state_.SYNC1 = 0xD3; + this->state_.SYNC0 = 0x91; + this->state_.PKTLEN = 0xFF; + this->state_.APPEND_STATUS = 1; + this->state_.LENGTH_CONFIG = 1; + this->state_.CRC_EN = 1; + this->state_.WHITE_DATA = 1; + this->state_.FREQ_IF = 0x0F; + this->state_.FREQ2 = 0x1E; + this->state_.FREQ1 = 0xC4; + this->state_.FREQ0 = 0xEC; + this->state_.DRATE_E = 0x0C; + this->state_.CHANBW_E = 0x02; + this->state_.DRATE_M = 0x22; + this->state_.SYNC_MODE = 2; + this->state_.CHANSPC_E = 2; + this->state_.NUM_PREAMBLE = 2; + this->state_.CHANSPC_M = 0xF8; + this->state_.DEVIATION_M = 7; + this->state_.DEVIATION_E = 4; + this->state_.RX_TIME = 7; + this->state_.CCA_MODE = 3; + this->state_.PO_TIMEOUT = 1; + this->state_.FOC_LIMIT = 2; + this->state_.FOC_POST_K = 1; + this->state_.FOC_PRE_K = 2; + this->state_.FOC_BS_CS_GATE = 1; + this->state_.BS_POST_KP = 1; + this->state_.BS_POST_KI = 1; + this->state_.BS_PRE_KP = 2; + this->state_.BS_PRE_KI = 1; + this->state_.MAGN_TARGET = 3; + this->state_.AGC_LNA_PRIORITY = 1; + this->state_.FILTER_LENGTH = 1; + this->state_.WAIT_TIME = 1; + this->state_.HYST_LEVEL = 2; + this->state_.WOREVT1 = 0x87; + this->state_.WOREVT0 = 0x6B; + this->state_.RC_CAL = 1; + this->state_.EVENT1 = 7; + this->state_.RC_PD = 1; + this->state_.MIX_CURRENT = 2; + this->state_.LODIV_BUF_CURRENT_RX = 1; + this->state_.LNA2MIX_CURRENT = 1; + this->state_.LNA_CURRENT = 1; + this->state_.LODIV_BUF_CURRENT_TX = 1; + this->state_.FSCAL3_LO = 9; + this->state_.CHP_CURR_CAL_EN = 2; + this->state_.FSCAL3_HI = 2; + this->state_.FSCAL2 = 0x0A; + this->state_.FSCAL1 = 0x20; + this->state_.FSCAL0 = 0x0D; + this->state_.RCCTRL1 = 0x41; + this->state_.FSTEST = 0x59; + this->state_.PTEST = 0x7F; + this->state_.AGCTEST = 0x3F; + this->state_.TEST2 = 0x88; + this->state_.TEST1 = 0x31; + this->state_.TEST0_LO = 1; + this->state_.VCO_SEL_CAL_EN = 1; + this->state_.TEST0_HI = 2; + + // PKTCTRL0 + this->state_.PKT_FORMAT = 3; + this->state_.LENGTH_CONFIG = 2; + this->state_.FS_AUTOCAL = 1; + + // Default Settings + this->set_frequency(433920); + this->set_if_frequency(153); + this->set_filter_bandwidth(203); + this->set_channel(0); + this->set_channel_spacing(200); + this->set_symbol_rate(5000); + this->set_sync_mode(SyncMode::SYNC_MODE_NONE); + this->set_carrier_sense_above_threshold(true); + this->set_modulation_type(Modulation::MODULATION_ASK_OOK); + this->set_magn_target(MagnTarget::MAGN_TARGET_42DB); + this->set_max_lna_gain(MaxLnaGain::MAX_LNA_GAIN_DEFAULT); + this->set_max_dvga_gain(MaxDvgaGain::MAX_DVGA_GAIN_MINUS_3); + this->set_lna_priority(false); + this->set_wait_time(WaitTime::WAIT_TIME_32_SAMPLES); + + // CRITICAL: Initialize PA Table to avoid transmitting 0 power (Silence) + memset(this->pa_table_, 0, sizeof(this->pa_table_)); + this->set_output_power(10.0f); +} + +void CC1101Component::setup() { + this->spi_setup(); + this->cs_->digital_write(true); + delayMicroseconds(1); + this->cs_->digital_write(false); + delayMicroseconds(1); + this->cs_->digital_write(true); + delayMicroseconds(41); + this->cs_->digital_write(false); + delay(5); + + this->strobe_(Command::RES); + delay(5); + + this->read_(Register::PARTNUM); + this->read_(Register::VERSION); + this->chip_id_ = encode_uint16(this->state_.PARTNUM, this->state_.VERSION); + ESP_LOGD(TAG, "CC1101 found! Chip ID: 0x%04X", this->chip_id_); + if (this->state_.VERSION == 0 || this->state_.PARTNUM == 0xFF) { + ESP_LOGE(TAG, "Failed to verify CC1101."); + this->mark_failed(); + return; + } + + this->initialized_ = true; + + for (uint8_t i = 0; i <= static_cast(Register::TEST0); i++) { + if (i == static_cast(Register::FSTEST) || i == static_cast(Register::AGCTEST)) { + continue; + } + this->write_(static_cast(i)); + } + this->write_(Register::PATABLE, this->pa_table_, sizeof(this->pa_table_)); + this->strobe_(Command::RX); +} + +void CC1101Component::dump_config() { + static const char *const MODULATION_NAMES[] = {"2-FSK", "GFSK", "UNUSED", "ASK/OOK", + "4-FSK", "UNUSED", "UNUSED", "MSK"}; + int32_t freq = static_cast(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) * + XTAL_FREQUENCY / (1 << 16); + float symbol_rate = + (((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY * 1000.0f; + float bw = XTAL_FREQUENCY / (8.0f * (4 + this->state_.CHANBW_M) * (1 << this->state_.CHANBW_E)); + ESP_LOGCONFIG(TAG, "CC1101:"); + LOG_PIN(" CS Pin: ", this->cs_); + ESP_LOGCONFIG(TAG, + " Chip ID: 0x%04X\n" + " Frequency: %" PRId32 " kHz\n" + " Channel: %u\n" + " Modulation: %s\n" + " Symbol Rate: %.0f baud\n" + " Filter Bandwidth: %.1f kHz\n" + " Output Power: %.1f dBm", + this->chip_id_, freq, this->state_.CHANNR, MODULATION_NAMES[this->state_.MOD_FORMAT & 0x07], + symbol_rate, bw, this->output_power_effective_); +} + +void CC1101Component::begin_tx() { + // Ensure Packet Format is 3 (Async Serial), use GDO0 as input during TX + this->write_(Register::PKTCTRL0, 0x32); + ESP_LOGV(TAG, "Beginning TX sequence"); + this->strobe_(Command::TX); + if (!this->wait_for_state_(State::TX, 50)) { + ESP_LOGW(TAG, "Timed out waiting for TX state!"); + } +} + +void CC1101Component::begin_rx() { + ESP_LOGV(TAG, "Beginning RX sequence"); + this->strobe_(Command::RX); +} + +void CC1101Component::reset() { + this->strobe_(Command::RES); + this->setup(); +} + +void CC1101Component::set_idle() { + ESP_LOGV(TAG, "Setting IDLE state"); + this->enter_idle_(); +} + +void CC1101Component::set_gdo0_config(uint8_t value) { + this->state_.GDO0_CFG = value; + if (this->initialized_) { + this->write_(Register::IOCFG0); + } +} + +void CC1101Component::set_gdo2_config(uint8_t value) { + this->state_.GDO2_CFG = value; + if (this->initialized_) { + this->write_(Register::IOCFG2); + } +} + +bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) { + uint32_t start = millis(); + while (millis() - start < timeout_ms) { + this->read_(Register::MARCSTATE); + State s = static_cast(this->state_.MARC_STATE); + if (s == target_state) { + return true; + } + delayMicroseconds(100); + } + return false; +} + +void CC1101Component::enter_idle_() { + this->strobe_(Command::IDLE); + this->wait_for_state_(State::IDLE); +} + +uint8_t CC1101Component::strobe_(Command cmd) { + uint8_t index = static_cast(cmd); + if (cmd < Command::RES || cmd > Command::NOP) { + return 0xFF; + } + this->enable(); + uint8_t status_byte = this->transfer_byte(index); + this->disable(); + return status_byte; +} + +void CC1101Component::write_(Register reg) { + uint8_t index = static_cast(reg); + this->enable(); + this->write_byte(index); + this->write_array(&this->state_.regs()[index], 1); + this->disable(); +} + +void CC1101Component::write_(Register reg, uint8_t value) { + uint8_t index = static_cast(reg); + this->state_.regs()[index] = value; + this->write_(reg); +} + +void CC1101Component::write_(Register reg, const uint8_t *buffer, size_t length) { + uint8_t index = static_cast(reg); + this->enable(); + this->write_byte(index | BUS_WRITE | BUS_BURST); + this->write_array(buffer, length); + this->disable(); +} + +void CC1101Component::read_(Register reg) { + uint8_t index = static_cast(reg); + this->enable(); + this->write_byte(index | BUS_READ | BUS_BURST); + this->state_.regs()[index] = this->transfer_byte(0); + this->disable(); +} + +void CC1101Component::read_(Register reg, uint8_t *buffer, size_t length) { + uint8_t index = static_cast(reg); + this->enable(); + this->write_byte(index | BUS_READ | BUS_BURST); + this->read_array(buffer, length); + this->disable(); +} + +// Setters +void CC1101Component::set_output_power(float value) { + this->output_power_requested_ = value; + int32_t freq = static_cast(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) * + XTAL_FREQUENCY / (1 << 16); + uint8_t a = 0xC0; + if (freq >= 300000 && freq <= 348000) { + a = PowerTableItem::find(PA_TABLE_315, sizeof(PA_TABLE_315) / sizeof(PA_TABLE_315[0]), value); + } else if (freq >= 378000 && freq <= 464000) { + a = PowerTableItem::find(PA_TABLE_433, sizeof(PA_TABLE_433) / sizeof(PA_TABLE_433[0]), value); + } else if (freq >= 779000 && freq < 900000) { + a = PowerTableItem::find(PA_TABLE_868, sizeof(PA_TABLE_868) / sizeof(PA_TABLE_868[0]), value); + } else if (freq >= 900000 && freq <= 928000) { + a = PowerTableItem::find(PA_TABLE_915, sizeof(PA_TABLE_915) / sizeof(PA_TABLE_915[0]), value); + } + + if (static_cast(this->state_.MOD_FORMAT) == Modulation::MODULATION_ASK_OOK) { + this->pa_table_[0] = 0; + this->pa_table_[1] = a; + } else { + this->pa_table_[0] = a; + this->pa_table_[1] = 0; + } + this->output_power_effective_ = value; + if (this->initialized_) { + this->write_(Register::PATABLE, this->pa_table_, sizeof(this->pa_table_)); + } +} + +void CC1101Component::set_rx_attenuation(RxAttenuation value) { + this->state_.CLOSE_IN_RX = static_cast(value); + if (this->initialized_) { + this->write_(Register::FIFOTHR); + } +} + +void CC1101Component::set_dc_blocking_filter(bool value) { + this->state_.DEM_DCFILT_OFF = value ? 0 : 1; + if (this->initialized_) { + this->write_(Register::MDMCFG2); + } +} + +void CC1101Component::set_frequency(float value) { + int32_t freq = static_cast(value * (1 << 16) / XTAL_FREQUENCY); + this->state_.FREQ2 = static_cast(freq >> 16); + this->state_.FREQ1 = static_cast(freq >> 8); + this->state_.FREQ0 = static_cast(freq); + if (this->initialized_) { + this->enter_idle_(); + this->write_(Register::FREQ2); + this->write_(Register::FREQ1); + this->write_(Register::FREQ0); + this->strobe_(Command::RX); + } +} + +void CC1101Component::set_if_frequency(float value) { + this->state_.FREQ_IF = value * (1 << 10) / XTAL_FREQUENCY; + if (this->initialized_) { + this->write_(Register::FSCTRL1); + } +} + +void CC1101Component::set_filter_bandwidth(float value) { + uint8_t e; + uint32_t m; + split_float(XTAL_FREQUENCY / (value * 8), 2, e, m); + this->state_.CHANBW_E = e; + this->state_.CHANBW_M = static_cast(m); + if (this->initialized_) { + this->write_(Register::MDMCFG4); + } +} + +void CC1101Component::set_channel(uint8_t value) { + this->state_.CHANNR = value; + if (this->initialized_) { + this->enter_idle_(); + this->write_(Register::CHANNR); + this->strobe_(Command::RX); + } +} + +void CC1101Component::set_channel_spacing(float value) { + uint8_t e; + uint32_t m; + split_float(value * (1 << 18) / XTAL_FREQUENCY, 8, e, m); + this->state_.CHANSPC_E = e; + this->state_.CHANSPC_M = static_cast(m); + if (this->initialized_) { + this->write_(Register::MDMCFG1); + this->write_(Register::MDMCFG0); + } +} + +void CC1101Component::set_fsk_deviation(float value) { + uint8_t e; + uint32_t m; + split_float(value * (1 << 17) / XTAL_FREQUENCY, 3, e, m); + this->state_.DEVIATION_E = e; + this->state_.DEVIATION_M = static_cast(m); + if (this->initialized_) { + this->write_(Register::DEVIATN); + } +} + +void CC1101Component::set_msk_deviation(uint8_t value) { + this->state_.DEVIATION_E = 0; + this->state_.DEVIATION_M = value - 1; + if (this->initialized_) { + this->write_(Register::DEVIATN); + } +} + +void CC1101Component::set_symbol_rate(float value) { + uint8_t e; + uint32_t m; + split_float(value * (1 << 28) / (XTAL_FREQUENCY * 1000), 8, e, m); + this->state_.DRATE_E = e; + this->state_.DRATE_M = static_cast(m); + if (this->initialized_) { + this->write_(Register::MDMCFG4); + this->write_(Register::MDMCFG3); + } +} + +void CC1101Component::set_sync_mode(SyncMode value) { + this->state_.SYNC_MODE = static_cast(value); + if (this->initialized_) { + this->write_(Register::MDMCFG2); + } +} + +void CC1101Component::set_carrier_sense_above_threshold(bool value) { + this->state_.CARRIER_SENSE_ABOVE_THRESHOLD = value ? 1 : 0; + if (this->initialized_) { + this->write_(Register::MDMCFG2); + } +} + +void CC1101Component::set_modulation_type(Modulation value) { + this->state_.MOD_FORMAT = static_cast(value); + this->state_.PA_POWER = value == Modulation::MODULATION_ASK_OOK ? 1 : 0; + if (this->initialized_) { + this->enter_idle_(); + this->write_(Register::MDMCFG2); + this->write_(Register::FREND0); + this->strobe_(Command::RX); + } +} + +void CC1101Component::set_manchester(bool value) { + this->state_.MANCHESTER_EN = value ? 1 : 0; + if (this->initialized_) { + this->write_(Register::MDMCFG2); + } +} + +void CC1101Component::set_num_preamble(uint8_t value) { + this->state_.NUM_PREAMBLE = value; + if (this->initialized_) { + this->write_(Register::MDMCFG1); + } +} + +void CC1101Component::set_sync1(uint8_t value) { + this->state_.SYNC1 = value; + if (this->initialized_) { + this->write_(Register::SYNC1); + } +} + +void CC1101Component::set_sync0(uint8_t value) { + this->state_.SYNC0 = value; + if (this->initialized_) { + this->write_(Register::SYNC0); + } +} + +void CC1101Component::set_pktlen(uint8_t value) { + this->state_.PKTLEN = value; + if (this->initialized_) { + this->write_(Register::PKTLEN); + } +} + +void CC1101Component::set_magn_target(MagnTarget value) { + this->state_.MAGN_TARGET = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL2); + } +} + +void CC1101Component::set_max_lna_gain(MaxLnaGain value) { + this->state_.MAX_LNA_GAIN = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL2); + } +} + +void CC1101Component::set_max_dvga_gain(MaxDvgaGain value) { + this->state_.MAX_DVGA_GAIN = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL2); + } +} + +void CC1101Component::set_carrier_sense_abs_thr(int8_t value) { + this->state_.CARRIER_SENSE_ABS_THR = static_cast(value & 0b1111); + if (this->initialized_) { + this->write_(Register::AGCCTRL1); + } +} + +void CC1101Component::set_carrier_sense_rel_thr(CarrierSenseRelThr value) { + this->state_.CARRIER_SENSE_REL_THR = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL1); + } +} + +void CC1101Component::set_lna_priority(bool value) { + this->state_.AGC_LNA_PRIORITY = value ? 1 : 0; + if (this->initialized_) { + this->write_(Register::AGCCTRL1); + } +} + +void CC1101Component::set_filter_length_fsk_msk(FilterLengthFskMsk value) { + this->state_.FILTER_LENGTH = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL0); + } +} + +void CC1101Component::set_filter_length_ask_ook(FilterLengthAskOok value) { + this->state_.FILTER_LENGTH = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL0); + } +} + +void CC1101Component::set_freeze(Freeze value) { + this->state_.AGC_FREEZE = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL0); + } +} + +void CC1101Component::set_wait_time(WaitTime value) { + this->state_.WAIT_TIME = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL0); + } +} + +void CC1101Component::set_hyst_level(HystLevel value) { + this->state_.HYST_LEVEL = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL0); + } +} + +} // namespace esphome::cc1101 diff --git a/esphome/components/cc1101/cc1101.h b/esphome/components/cc1101/cc1101.h new file mode 100644 index 0000000000..65aeb2ea82 --- /dev/null +++ b/esphome/components/cc1101/cc1101.h @@ -0,0 +1,110 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/spi/spi.h" +#include "esphome/core/automation.h" +#include "cc1101defs.h" + +namespace esphome::cc1101 { + +class CC1101Component : public Component, + public spi::SPIDevice { + public: + CC1101Component(); + + void setup() override; + void dump_config() override; + + // Actions + void begin_tx(); + void begin_rx(); + void reset(); + void set_idle(); + + // GDO Pin Configuration + void set_gdo0_config(uint8_t value); + void set_gdo2_config(uint8_t value); + + // Configuration Setters + void set_output_power(float value); + void set_rx_attenuation(RxAttenuation value); + void set_dc_blocking_filter(bool value); + + // Tuner settings + void set_frequency(float value); + void set_if_frequency(float value); + void set_filter_bandwidth(float value); + void set_channel(uint8_t value); + void set_channel_spacing(float value); + void set_fsk_deviation(float value); + void set_msk_deviation(uint8_t value); + void set_symbol_rate(float value); + void set_sync_mode(SyncMode value); + void set_carrier_sense_above_threshold(bool value); + void set_modulation_type(Modulation value); + void set_manchester(bool value); + void set_num_preamble(uint8_t value); + void set_sync1(uint8_t value); + void set_sync0(uint8_t value); + void set_pktlen(uint8_t value); + + // AGC settings + void set_magn_target(MagnTarget value); + void set_max_lna_gain(MaxLnaGain value); + void set_max_dvga_gain(MaxDvgaGain value); + void set_carrier_sense_abs_thr(int8_t value); + void set_carrier_sense_rel_thr(CarrierSenseRelThr value); + void set_lna_priority(bool value); + void set_filter_length_fsk_msk(FilterLengthFskMsk value); + void set_filter_length_ask_ook(FilterLengthAskOok value); + void set_freeze(Freeze value); + void set_wait_time(WaitTime value); + void set_hyst_level(HystLevel value); + + protected: + uint16_t chip_id_{0}; + bool initialized_{false}; + + float output_power_requested_{10.0f}; + float output_power_effective_{10.0f}; + uint8_t pa_table_[PA_TABLE_SIZE]{}; + + CC1101State state_; + + // Low-level Helpers + uint8_t strobe_(Command cmd); + void write_(Register reg); + void write_(Register reg, uint8_t value); + void write_(Register reg, const uint8_t *buffer, size_t length); + void read_(Register reg); + void read_(Register reg, uint8_t *buffer, size_t length); + + // State Management + bool wait_for_state_(State target_state, uint32_t timeout_ms = 100); + void enter_idle_(); +}; + +// Action Wrappers +template class BeginTxAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->begin_tx(); } +}; + +template class BeginRxAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->begin_rx(); } +}; + +template class ResetAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->reset(); } +}; + +template class SetIdleAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->set_idle(); } +}; + +} // namespace esphome::cc1101 diff --git a/esphome/components/cc1101/cc1101defs.h b/esphome/components/cc1101/cc1101defs.h new file mode 100644 index 0000000000..52f15cb85a --- /dev/null +++ b/esphome/components/cc1101/cc1101defs.h @@ -0,0 +1,644 @@ +#pragma once + +#include + +namespace esphome::cc1101 { + +static constexpr float XTAL_FREQUENCY = 26000; + +static constexpr uint8_t BUS_BURST = 0x40; +static constexpr uint8_t BUS_READ = 0x80; +static constexpr uint8_t BUS_WRITE = 0x00; +static constexpr uint8_t BYTES_IN_RXFIFO = 0x7F; // byte number in RXfifo +static constexpr size_t PA_TABLE_SIZE = 8; + +enum class Register : uint8_t { + IOCFG2 = 0x00, // GDO2 output pin configuration + IOCFG1 = 0x01, // GDO1 output pin configuration + IOCFG0 = 0x02, // GDO0 output pin configuration + FIFOTHR = 0x03, // RX FIFO and TX FIFO thresholds + SYNC1 = 0x04, // Sync word, high INT8U + SYNC0 = 0x05, // Sync word, low INT8U + PKTLEN = 0x06, // Packet length + PKTCTRL1 = 0x07, // Packet automation control + PKTCTRL0 = 0x08, // Packet automation control + ADDR = 0x09, // Device address + CHANNR = 0x0A, // Channel number + FSCTRL1 = 0x0B, // Frequency synthesizer control + FSCTRL0 = 0x0C, // Frequency synthesizer control + FREQ2 = 0x0D, // Frequency control word, high INT8U + FREQ1 = 0x0E, // Frequency control word, middle INT8U + FREQ0 = 0x0F, // Frequency control word, low INT8U + MDMCFG4 = 0x10, // Modem configuration + MDMCFG3 = 0x11, // Modem configuration + MDMCFG2 = 0x12, // Modem configuration + MDMCFG1 = 0x13, // Modem configuration + MDMCFG0 = 0x14, // Modem configuration + DEVIATN = 0x15, // Modem deviation setting + MCSM2 = 0x16, // Main Radio Control State Machine configuration + MCSM1 = 0x17, // Main Radio Control State Machine configuration + MCSM0 = 0x18, // Main Radio Control State Machine configuration + FOCCFG = 0x19, // Frequency Offset Compensation configuration + BSCFG = 0x1A, // Bit Synchronization configuration + AGCCTRL2 = 0x1B, // AGC control + AGCCTRL1 = 0x1C, // AGC control + AGCCTRL0 = 0x1D, // AGC control + WOREVT1 = 0x1E, // High INT8U Event 0 timeout + WOREVT0 = 0x1F, // Low INT8U Event 0 timeout + WORCTRL = 0x20, // Wake On Radio control + FREND1 = 0x21, // Front end RX configuration + FREND0 = 0x22, // Front end TX configuration + FSCAL3 = 0x23, // Frequency synthesizer calibration + FSCAL2 = 0x24, // Frequency synthesizer calibration + FSCAL1 = 0x25, // Frequency synthesizer calibration + FSCAL0 = 0x26, // Frequency synthesizer calibration + RCCTRL1 = 0x27, // RC oscillator configuration + RCCTRL0 = 0x28, // RC oscillator configuration + FSTEST = 0x29, // Frequency synthesizer calibration control + PTEST = 0x2A, // Production test + AGCTEST = 0x2B, // AGC test + TEST2 = 0x2C, // Various test settings + TEST1 = 0x2D, // Various test settings + TEST0 = 0x2E, // Various test settings + UNUSED = 0x2F, + PARTNUM = 0x30, + VERSION = 0x31, + FREQEST = 0x32, + LQI = 0x33, + RSSI = 0x34, + MARCSTATE = 0x35, + WORTIME1 = 0x36, + WORTIME0 = 0x37, + PKTSTATUS = 0x38, + VCO_VC_DAC = 0x39, + TXBYTES = 0x3A, + RXBYTES = 0x3B, + RCCTRL1_STATUS = 0x3C, + RCCTRL0_STATUS = 0x3D, + PATABLE = 0x3E, + FIFO = 0x3F, +}; + +enum class Command : uint8_t { + RES = 0x30, // Reset chip. + FSTXON = 0x31, // Enable and calibrate frequency synthesizer + XOFF = 0x32, // Turn off crystal oscillator. + CAL = 0x33, // Calibrate frequency synthesizer and turn it off + RX = 0x34, // Enable RX. + TX = 0x35, // Enable TX. + IDLE = 0x36, // Exit RX / TX + // 0x37 is RESERVED / UNDEFINED in CC1101 Datasheet + WOR = 0x38, // Start automatic RX polling sequence (Wake-on-Radio) + PWD = 0x39, // Enter power down mode when CSn goes high. + FRX = 0x3A, // Flush the RX FIFO buffer. + FTX = 0x3B, // Flush the TX FIFO buffer. + WORRST = 0x3C, // Reset real time clock. + NOP = 0x3D, // No operation. +}; + +enum class State : uint8_t { + SLEEP, + IDLE, + XOFF, + VCOON_MC, + REGON_MC, + MANCAL, + VCOON, + REGON, + STARTCAL, + BWBOOST, + FS_LOCK, + IFADCON, + ENDCAL, + RX, + RX_END, + RX_RST, + TXRX_SWITCH, + RXFIFO_OVERFLOW, + FSTXON, + TX, + TX_END, + RXTX_SWITCH, + TXFIFO_UNDERFLOW, +}; + +enum class RxAttenuation : uint8_t { + RX_ATTENUATION_0DB, + RX_ATTENUATION_6DB, + RX_ATTENUATION_12DB, + RX_ATTENUATION_18DB, +}; + +enum class SyncMode : uint8_t { + SYNC_MODE_NONE, + SYNC_MODE_15_16, + SYNC_MODE_16_16, + SYNC_MODE_30_32, +}; + +enum class Modulation : uint8_t { + MODULATION_2_FSK, + MODULATION_GFSK, + MODULATION_UNUSED_2, + MODULATION_ASK_OOK, + MODULATION_4_FSK, + MODULATION_UNUSED_5, + MODULATION_UNUSED_6, + MODULATION_MSK, +}; + +enum class MagnTarget : uint8_t { + MAGN_TARGET_24DB, + MAGN_TARGET_27DB, + MAGN_TARGET_30DB, + MAGN_TARGET_33DB, + MAGN_TARGET_36DB, + MAGN_TARGET_38DB, + MAGN_TARGET_40DB, + MAGN_TARGET_42DB, +}; + +enum class MaxLnaGain : uint8_t { + MAX_LNA_GAIN_DEFAULT, + MAX_LNA_GAIN_MINUS_2P6DB, + MAX_LNA_GAIN_MINUS_6P1DB, + MAX_LNA_GAIN_MINUS_7P4DB, + MAX_LNA_GAIN_MINUS_9P2DB, + MAX_LNA_GAIN_MINUS_11P5DB, + MAX_LNA_GAIN_MINUS_14P6DB, + MAX_LNA_GAIN_MINUS_17P1DB, +}; + +enum class MaxDvgaGain : uint8_t { + MAX_DVGA_GAIN_DEFAULT, + MAX_DVGA_GAIN_MINUS_1, + MAX_DVGA_GAIN_MINUS_2, + MAX_DVGA_GAIN_MINUS_3, +}; + +enum class CarrierSenseRelThr : uint8_t { + CARRIER_SENSE_REL_THR_DEFAULT, + CARRIER_SENSE_REL_THR_PLUS_6DB, + CARRIER_SENSE_REL_THR_PLUS_10DB, + CARRIER_SENSE_REL_THR_PLUS_14DB, +}; + +enum class FilterLengthFskMsk : uint8_t { + FILTER_LENGTH_8DB, + FILTER_LENGTH_16DB, + FILTER_LENGTH_32DB, + FILTER_LENGTH_64DB, +}; + +enum class FilterLengthAskOok : uint8_t { + FILTER_LENGTH_4DB, + FILTER_LENGTH_8DB, + FILTER_LENGTH_12DB, + FILTER_LENGTH_16DB, +}; + +enum class Freeze : uint8_t { + FREEZE_DEFAULT, + FREEZE_ON_SYNC, + FREEZE_ANALOG_ONLY, + FREEZE_ANALOG_AND_DIGITAL, +}; + +enum class WaitTime : uint8_t { + WAIT_TIME_8_SAMPLES, + WAIT_TIME_16_SAMPLES, + WAIT_TIME_24_SAMPLES, + WAIT_TIME_32_SAMPLES, +}; + +enum class HystLevel : uint8_t { + HYST_LEVEL_NONE, + HYST_LEVEL_LOW, + HYST_LEVEL_MEDIUM, + HYST_LEVEL_HIGH, +}; + +struct __attribute__((packed)) CC1101State { + // Byte array accessors for bulk SPI transfers + uint8_t *regs() { return reinterpret_cast(this); } + const uint8_t *regs() const { return reinterpret_cast(this); } + + // 0x00 + union { + uint8_t IOCFG2; + struct { + uint8_t GDO2_CFG : 6; + uint8_t GDO2_INV : 1; + uint8_t : 1; + }; + }; + // 0x01 + union { + uint8_t IOCFG1; + struct { + uint8_t GDO1_CFG : 6; + uint8_t GDO1_INV : 1; + uint8_t GDO_DS : 1; // GDO, not GD0 + }; + }; + // 0x02 + union { + uint8_t IOCFG0; + struct { + uint8_t GDO0_CFG : 6; + uint8_t GDO0_INV : 1; + uint8_t TEMP_SENSOR_ENABLE : 1; + }; + }; + // 0x03 + union { + uint8_t FIFOTHR; + struct { + uint8_t FIFO_THR : 4; + uint8_t CLOSE_IN_RX : 2; // RxAttenuation + uint8_t ADC_RETENTION : 1; + uint8_t : 1; + }; + }; + // 0x04 + uint8_t SYNC1; + // 0x05 + uint8_t SYNC0; + // 0x06 + uint8_t PKTLEN; + // 0x07 + union { + uint8_t PKTCTRL1; + struct { + uint8_t ADR_CHK : 2; + uint8_t APPEND_STATUS : 1; + uint8_t CRC_AUTOFLUSH : 1; + uint8_t : 1; + uint8_t PQT : 3; + }; + }; + // 0x08 + union { + uint8_t PKTCTRL0; + struct { + uint8_t LENGTH_CONFIG : 2; + uint8_t CRC_EN : 1; + uint8_t : 1; + uint8_t PKT_FORMAT : 2; + uint8_t WHITE_DATA : 1; + uint8_t : 1; + }; + }; + // 0x09 + uint8_t ADDR; + // 0x0A + uint8_t CHANNR; + // 0x0B + union { + uint8_t FSCTRL1; + struct { + uint8_t FREQ_IF : 5; + uint8_t RESERVED : 1; // hm? + uint8_t : 2; + }; + }; + // 0x0C + uint8_t FSCTRL0; + // 0x0D + uint8_t FREQ2; // [7:6] always zero + // 0x0E + uint8_t FREQ1; + // 0x0F + uint8_t FREQ0; + // 0x10 + union { + uint8_t MDMCFG4; + struct { + uint8_t DRATE_E : 4; + uint8_t CHANBW_M : 2; + uint8_t CHANBW_E : 2; + }; + }; + // 0x11 + union { + uint8_t MDMCFG3; + struct { + uint8_t DRATE_M : 8; + }; + }; + // 0x12 + union { + uint8_t MDMCFG2; + struct { + uint8_t SYNC_MODE : 2; + uint8_t CARRIER_SENSE_ABOVE_THRESHOLD : 1; + uint8_t MANCHESTER_EN : 1; + uint8_t MOD_FORMAT : 3; // Modulation + uint8_t DEM_DCFILT_OFF : 1; + }; + }; + // 0x13 + union { + uint8_t MDMCFG1; + struct { + uint8_t CHANSPC_E : 2; + uint8_t : 2; + uint8_t NUM_PREAMBLE : 3; + uint8_t FEC_EN : 1; + }; + }; + // 0x14 + union { + uint8_t MDMCFG0; + struct { + uint8_t CHANSPC_M : 8; + }; + }; + // 0x15 + union { + uint8_t DEVIATN; + struct { + uint8_t DEVIATION_M : 3; + uint8_t : 1; + uint8_t DEVIATION_E : 3; + uint8_t : 1; + }; + }; + // 0x16 + union { + uint8_t MCSM2; + struct { + uint8_t RX_TIME : 3; + uint8_t RX_TIME_QUAL : 1; + uint8_t RX_TIME_RSSI : 1; + uint8_t : 3; + }; + }; + // 0x17 + union { + uint8_t MCSM1; + struct { + uint8_t TXOFF_MODE : 2; + uint8_t RXOFF_MODE : 2; + uint8_t CCA_MODE : 2; + uint8_t : 2; + }; + }; + // 0x18 + union { + uint8_t MCSM0; + struct { + uint8_t XOSC_FORCE_ON : 1; + uint8_t PIN_CTRL_EN : 1; + uint8_t PO_TIMEOUT : 2; + uint8_t FS_AUTOCAL : 2; + uint8_t : 2; + }; + }; + // 0x19 + union { + uint8_t FOCCFG; + struct { + uint8_t FOC_LIMIT : 2; + uint8_t FOC_POST_K : 1; + uint8_t FOC_PRE_K : 2; + uint8_t FOC_BS_CS_GATE : 1; + uint8_t : 2; + }; + }; + // 0x1A + union { + uint8_t BSCFG; + struct { + uint8_t BS_LIMIT : 2; + uint8_t BS_POST_KP : 1; + uint8_t BS_POST_KI : 1; + uint8_t BS_PRE_KP : 2; + uint8_t BS_PRE_KI : 2; + }; + }; + // 0x1B + union { + uint8_t AGCCTRL2; + struct { + uint8_t MAGN_TARGET : 3; // MagnTarget + uint8_t MAX_LNA_GAIN : 3; // MaxLnaGain + uint8_t MAX_DVGA_GAIN : 2; // MaxDvgaGain + }; + }; + // 0x1C + union { + uint8_t AGCCTRL1; + struct { + uint8_t CARRIER_SENSE_ABS_THR : 4; + uint8_t CARRIER_SENSE_REL_THR : 2; // CarrierSenseRelThr + uint8_t AGC_LNA_PRIORITY : 1; + uint8_t : 1; + }; + }; + // 0x1D + union { + uint8_t AGCCTRL0; + struct { + uint8_t FILTER_LENGTH : 2; // FilterLengthFskMsk or FilterLengthAskOok + uint8_t AGC_FREEZE : 2; // Freeze + uint8_t WAIT_TIME : 2; // WaitTime + uint8_t HYST_LEVEL : 2; // HystLevel + }; + }; + // 0x1E + uint8_t WOREVT1; + // 0x1F + uint8_t WOREVT0; + // 0x20 + union { + uint8_t WORCTRL; + struct { + uint8_t WOR_RES : 2; + uint8_t : 1; + uint8_t RC_CAL : 1; + uint8_t EVENT1 : 3; + uint8_t RC_PD : 1; + }; + }; + // 0x21 + union { + uint8_t FREND1; + struct { + uint8_t MIX_CURRENT : 2; + uint8_t LODIV_BUF_CURRENT_RX : 2; + uint8_t LNA2MIX_CURRENT : 2; + uint8_t LNA_CURRENT : 2; + }; + }; + // 0x22 + union { + uint8_t FREND0; + struct { + uint8_t PA_POWER : 3; + uint8_t : 1; + uint8_t LODIV_BUF_CURRENT_TX : 2; + uint8_t : 2; + }; + }; + // 0x23 + union { + uint8_t FSCAL3; + struct { + uint8_t FSCAL3_LO : 4; + uint8_t CHP_CURR_CAL_EN : 2; // Disable charge pump calibration stage when 0. + uint8_t FSCAL3_HI : 2; + }; + }; + // 0x24 + union { + // uint8_t FSCAL2; + struct { + uint8_t FSCAL2 : 5; + uint8_t VCO_CORE_H_EN : 1; + uint8_t : 2; + }; + }; + // 0x25 + union { + // uint8_t FSCAL1; + struct { + uint8_t FSCAL1 : 6; + uint8_t : 2; + }; + }; + // 0x26 + union { + // uint8_t FSCAL0; + struct { + uint8_t FSCAL0 : 7; + uint8_t : 1; + }; + }; + // 0x27 + union { + // uint8_t RCCTRL1; + struct { + uint8_t RCCTRL1 : 7; + uint8_t : 1; + }; + }; + // 0x28 + union { + // uint8_t RCCTRL0; + struct { + uint8_t RCCTRL0 : 7; + uint8_t : 1; + }; + }; + // 0x29 + uint8_t FSTEST; + // 0x2A + uint8_t PTEST; + // 0x2B + uint8_t AGCTEST; + // 0x2C + uint8_t TEST2; + // 0x2D + uint8_t TEST1; + // 0x2E + union { + uint8_t TEST0; + struct { + uint8_t TEST0_LO : 1; + uint8_t VCO_SEL_CAL_EN : 1; // Enable VCO selection calibration stage when 1 + uint8_t TEST0_HI : 6; + }; + }; + // 0x2F + uint8_t REG_2F; + // 0x30 + uint8_t PARTNUM; + // 0x31 + uint8_t VERSION; + // 0x32 + union { + uint8_t FREQEST; + struct { + int8_t FREQOFF_EST : 8; + }; + }; + // 0x33 + union { + uint8_t LQI; + struct { + uint8_t LQI_EST : 7; + uint8_t LQI_CRC_OK : 1; + }; + }; + // 0x34 + int8_t RSSI; + // 0x35 + union { + // uint8_t MARCSTATE; + struct { + uint8_t MARC_STATE : 5; // State + uint8_t : 3; + }; + }; + // 0x36 + uint8_t WORTIME1; + // 0x37 + uint8_t WORTIME0; + // 0x38 + union { + uint8_t PKTSTATUS; + struct { + uint8_t GDO0 : 1; + uint8_t : 1; + uint8_t GDO2 : 1; + uint8_t SFD : 1; + uint8_t CCA : 1; + uint8_t PQT_REACHED : 1; + uint8_t CS : 1; + uint8_t CRC_OK : 1; // same as LQI_CRC_OK? + }; + }; + // 0x39 + uint8_t VCO_VC_DAC; + // 0x3A + union { + uint8_t TXBYTES; + struct { + uint8_t NUM_TXBYTES : 7; + uint8_t TXFIFO_UNDERFLOW : 1; + }; + }; + // 0x3B + union { + uint8_t RXBYTES; + struct { + uint8_t NUM_RXBYTES : 7; + uint8_t RXFIFO_OVERFLOW : 1; + }; + }; + // 0x3C + union { + // uint8_t RCCTRL1_STATUS; + struct { + uint8_t RCCTRL1_STATUS : 7; + uint8_t : 1; + }; + }; + // 0x3D + union { + // uint8_t RCCTRL0_STATUS; + struct { + uint8_t RCCTRL0_STATUS : 7; + uint8_t : 1; + }; + }; + // 0x3E + uint8_t REG_3E; + // 0x3F + uint8_t REG_3F; +}; + +static_assert(sizeof(CC1101State) == 0x40, "CC1101State size mismatch"); + +} // namespace esphome::cc1101 diff --git a/esphome/components/cc1101/cc1101pa.h b/esphome/components/cc1101/cc1101pa.h new file mode 100644 index 0000000000..e5e7a47c51 --- /dev/null +++ b/esphome/components/cc1101/cc1101pa.h @@ -0,0 +1,174 @@ +#pragma once + +#include +#include +#include + +namespace esphome::cc1101 { + +// CC1101 Design Note DN013 + +struct PowerTableItem { + uint8_t value; + uint8_t dbm_diff; // starts from 12.0, diff to previous entry, scaled by 10 + + static uint8_t find(const PowerTableItem *items, size_t count, float &dbm_target) { + int32_t dbmi = 120; + int32_t dbmi_target = static_cast(std::lround(dbm_target * 10)); + for (size_t i = 0; i < count; i++) { + dbmi -= items[i].dbm_diff; + if (dbmi_target >= dbmi) { + // Skip invalid PA settings (magic numbers derived from TI DN013/SmartRC logic) + if (items[i].value >= 0x61 && items[i].value <= 0x6F) { + continue; + } + dbm_target = static_cast(dbmi) / 10.0f; + return items[i].value; + } + } + dbm_target = -30.0f; + return 0x03; + } +}; + +static const PowerTableItem PA_TABLE_315[] = { + {0xC0, 14}, // C0 10.6 -35.3 -44.4 -57.8 -53.8 -58.3 -57.2 -57.8 -56.7 28.5 + {0xC3, 10}, // C3 9.6 -39.2 -45.3 -59.0 -54.2 -59.0 -57.5 -58.3 -57.2 26.2 + {0xC6, 11}, // C6 8.5 -43.2 -46.3 -59.2 -54.7 -59.1 -57.7 -58.3 -57.4 24.4 + {0xC9, 10}, // C9 7.5 -47.0 -47.3 -58.9 -55.0 -59.0 -57.9 -58.4 -57.5 23.0 + {0x81, 12}, // 81 6.3 -49.2 -45.7 -57.3 -53.6 -59.0 -56.0 -56.5 -57.5 19.5 + {0x85, 13}, // 85 5.0 -51.0 -47.2 -59.8 -54.2 -59.0 -56.9 -57.9 -58.0 18.3 + {0x88, 11}, // 88 3.9 -46.6 -48.1 -60.0 -55.0 -58.9 -57.5 -58.2 -58.2 17.4 + {0xCF, 11}, // CF 2.8 -49.8 -51.3 -57.6 -56.8 -59.1 -58.4 -58.1 -58.3 18.0 + {0x8D, 11}, // 8D 1.7 -43.8 -49.5 -58.9 -56.3 -58.8 -58.2 -58.4 -58.5 15.8 + {0x50, 10}, // 50 0.7 -59.2 -51.2 -59.0 -56.5 -59.0 -58.3 -58.3 -58.2 15.3 + {0x40, 10}, // 40 -0.3 -58.2 -52.1 -59.4 -56.9 -59.0 -58.4 -58.4 -58.3 14.7 + {0x3D, 10}, // 3D -1.3 -54.4 -48.4 -59.8 -57.5 -58.9 -58.3 -58.5 -58.5 19.3 + {0x55, 10}, // 55 -2.3 -56.7 -53.6 -59.7 -57.5 -59.1 -58.7 -58.4 -58.4 13.7 + {0x39, 11}, // 39 -3.4 -50.9 -49.5 -59.8 -58.0 -59.0 -58.5 -58.4 -58.4 16.8 + {0x2B, 15}, // 2B -4.9 -51.2 -50.4 -59.9 -58.0 -58.9 -58.7 -58.3 -58.4 15.6 + {0x29, 16}, // 29 -6.5 -51.8 -51.6 -59.9 -58.4 -59.0 -58.8 -58.3 -58.3 14.7 + {0x28, 10}, // 28 -7.5 -52.2 -52.5 -60.0 -58.6 -59.0 -58.8 -58.2 -58.4 14.3 + {0x27, 11}, // 27 -8.6 -52.9 -53.1 -60.0 -58.8 -59.1 -58.8 -58.3 -58.5 13.9 + {0x26, 12}, // 26 -9.8 -53.6 -54.3 -60.1 -58.7 -59.0 -58.7 -58.4 -58.4 13.4 + {0x25, 13}, // 25 -11.1 -54.3 -55.5 -60.1 -58.8 -59.1 -58.8 -58.4 -58.4 13.0 + {0x33, 11}, // 33 -12.2 -55.0 -56.3 -60.0 -58.7 -59.0 -58.9 -58.4 -58.4 12.8 + {0x1F, 11}, // 1F -13.3 -55.6 -57.2 -60.0 -58.8 -58.9 -58.9 -58.3 -58.4 12.4 + {0x1D, 12}, // 1D -14.5 -56.0 -58.0 -60.0 -58.8 -59.1 -58.7 -58.4 -58.5 12.1 + {0x32, 11}, // 32 -15.6 -56.9 -58.8 -59.9 -58.8 -59.0 -58.8 -58.3 -58.5 12.2 + {0x1A, 10}, // 1A -16.6 -57.3 -59.5 -59.9 -58.8 -59.1 -58.8 -58.4 -58.4 11.8 + {0x18, 19}, // 18 -18.5 -57.8 -60.3 -60.0 -58.8 -59.0 -58.9 -58.2 -58.5 11.6 + {0x17, 11}, // 17 -19.6 -58.7 -60.9 -60.0 -58.7 -58.9 -58.9 -58.5 -58.4 11.4 + {0x0C, 11}, // C -20.7 -59.4 -61.1 -60.0 -58.8 -59.1 -58.9 -58.4 -58.3 11.3 + {0x0A, 15}, // A -22.2 -59.9 -61.9 -60.0 -58.9 -59.0 -58.9 -58.4 -58.5 11.2 + {0x08, 18}, // 8 -24.0 -60.5 -62.5 -60.0 -58.7 -59.1 -58.8 -58.3 -58.5 11.1 + {0x07, 11}, // 7 -25.1 -61.3 -62.9 -60.1 -58.8 -59.1 -58.8 -58.4 -58.4 11.0 + {0x06, 13}, // 6 -26.4 -61.6 -63.2 -60.1 -58.7 -59.0 -58.9 -58.5 -58.5 11.0 + {0x05, 13}, // 5 -27.7 -62.3 -63.4 -60.1 -58.7 -59.2 -58.8 -58.4 -58.5 10.9 + {0x04, 19}, // 4 -29.6 -62.7 -63.6 -59.9 -58.7 -59.0 -58.9 -58.4 -58.4 10.8 +}; + +static const PowerTableItem PA_TABLE_433[] = { + {0xC0, 21}, // C0 9.9 -43.4 -45.0 -53.9 -55.2 -55.8 -52.3 -55.6 29.1 + {0xC3, 11}, // C3 8.8 -49.3 -45.9 -55.9 -55.4 -57.2 -52.6 -57.5 26.9 + {0xC6, 10}, // C6 7.8 -56.2 -46.9 -56.9 -55.6 -58.2 -53.2 -57.9 25.2 + {0xC9, 10}, // C9 6.8 -56.1 -47.9 -57.3 -55.9 -58.5 -54.0 -56.9 23.8 + {0xCC, 10}, // CC 5.8 -52.8 -48.9 -57.0 -56.1 -58.4 -54.6 -56.2 22.6 + {0x85, 10}, // 85 4.8 -54.2 -53.0 -58.3 -55.0 -57.8 -56.8 -58.0 19.1 + {0x88, 12}, // 88 3.6 -56.2 -53.8 -58.3 -55.7 -58.1 -57.2 -58.2 18.2 + {0x8B, 13}, // 8B 2.3 -57.7 -54.5 -58.0 -56.3 -58.1 -57.5 -58.2 17.3 + {0x8E, 19}, // 8E 0.4 -58.0 -55.5 -57.8 -57.4 -58.2 -58.1 -58.4 16.2 + {0x40, 12}, // 40 -0.8 -59.7 -56.1 -58.2 -57.7 -58.4 -58.3 -58.2 15.4 + {0x3C, 13}, // 3C -2.1 -60.6 -57.3 -58.2 -58.0 -58.5 -58.4 -58.5 19.3 + {0x3A, 10}, // 3A -3.1 -59.5 -57.5 -58.3 -58.3 -58.6 -58.1 -58.6 18.1 + {0x8F, 15}, // 8F -4.6 -52.2 -57.7 -58.1 -58.8 -58.4 -58.7 -58.3 14.4 + {0x37, 10}, // 37 -5.6 -56.8 -58.3 -58.3 -58.8 -58.4 -58.5 -58.4 16.2 + {0x36, 12}, // 36 -6.8 -56.8 -58.9 -58.3 -58.8 -58.3 -58.5 -58.5 15.6 + {0x28, 10}, // 28 -7.8 -56.6 -59.0 -58.2 -59.0 -58.4 -58.5 -58.4 15.1 + {0x26, 21}, // 26 -9.9 -57.0 -59.4 -58.3 -59.0 -58.4 -58.7 -58.4 14.3 + {0x25, 15}, // 25 -11.4 -57.3 -59.7 -58.4 -59.0 -58.3 -58.7 -58.5 13.9 + {0x24, 19}, // 24 -13.3 -57.9 -59.9 -58.2 -59.0 -58.6 -58.7 -58.5 13.5 + {0x1E, 10}, // 1E -14.3 -58.4 -59.8 -58.2 -59.0 -58.4 -58.6 -58.6 13.2 + {0x1C, 12}, // 1C -15.5 -58.6 -59.9 -58.4 -58.8 -58.6 -58.8 -58.5 12.9 + {0x1A, 15}, // 1A -17.0 -59.4 -59.9 -58.3 -59.1 -58.5 -58.7 -58.4 12.7 + {0x18, 18}, // 18 -18.8 -60.2 -59.9 -58.2 -59.0 -58.5 -58.7 -58.6 12.5 + {0x17, 10}, // 17 -19.8 -60.6 -59.9 -58.2 -58.9 -58.4 -58.7 -58.4 12.4 + {0x0C, 12}, // C -21.0 -61.1 -59.9 -58.4 -59.0 -58.5 -58.7 -58.6 12.3 + {0x15, 15}, // 15 -22.5 -61.7 -60.0 -58.2 -59.1 -58.3 -58.6 -58.7 12.2 + {0x08, 18}, // 8 -24.3 -62.3 -59.9 -58.3 -59.0 -58.4 -58.8 -58.5 12.1 + {0x07, 10}, // 7 -25.3 -62.6 -59.9 -58.2 -59.0 -58.6 -58.7 -58.5 12.0 + {0x06, 12}, // 6 -26.5 -63.2 -59.9 -58.3 -58.9 -58.5 -58.6 -58.6 12.0 + {0x05, 14}, // 5 -27.9 -63.5 -59.8 -58.3 -59.1 -58.5 -58.7 -58.4 11.9 + {0x04, 16}, // 4 -29.5 -63.7 -59.9 -58.3 -58.9 -58.5 -58.5 -58.5 11.9 +}; + +static const PowerTableItem PA_TABLE_868[] = { + {0xC0, 13}, // C0 10.7 -35.1 -58.6 -58.6 -57.5 -50.0 34.2 + {0xC3, 11}, // C3 9.6 -41.5 -58.5 -58.3 -57.4 -54.4 31.6 + {0xC6, 11}, // C6 8.5 -47.7 -58.5 -58.3 -57.6 -55.0 29.5 + {0xC9, 10}, // C9 7.5 -44.4 -58.5 -58.5 -57.7 -53.6 27.8 + {0xCC, 10}, // CC 6.5 -40.6 -58.6 -58.4 -57.6 -52.5 26.3 + {0xCE, 10}, // CE 5.5 -38.5 -58.5 -58.4 -57.8 -52.2 25.0 + {0x84, 11}, // 84 4.4 -35.3 -58.7 -58.5 -57.8 -55.8 20.3 + {0x87, 10}, // 87 3.4 -39.4 -58.6 -58.6 -57.8 -55.7 19.5 + {0xCF, 10}, // CF 2.4 -36.6 -58.6 -58.4 -57.7 -53.6 22.0 + {0x8C, 13}, // 8C 1.1 -50.2 -58.6 -58.5 -57.7 -55.9 17.9 + {0x50, 14}, // 50 -0.3 -42.1 -58.5 -58.5 -57.6 -57.1 16.9 + {0x40, 12}, // 40 -1.5 -43.2 -58.5 -58.7 -57.7 -57.2 16.1 + {0x3F, 11}, // 3F -2.6 -53.7 -58.6 -58.5 -57.8 -57.5 21.4 + {0x55, 10}, // 55 -3.6 -44.9 -58.6 -58.4 -57.8 -57.5 15.0 + {0x57, 12}, // 57 -4.8 -46.0 -58.6 -58.5 -57.6 -57.4 14.5 + {0x8F, 12}, // 8F -6.0 -51.6 -58.5 -58.6 -57.7 -57.1 15.0 + {0x2A, 14}, // 2A -7.4 -49.3 -58.5 -58.6 -57.7 -57.4 16.2 + {0x28, 16}, // 28 -9.0 -49.0 -58.5 -58.6 -57.7 -57.4 15.4 + {0x26, 20}, // 26 -11.0 -49.2 -58.5 -58.5 -57.7 -57.4 14.6 + {0x25, 15}, // 25 -12.5 -49.5 -58.6 -58.6 -57.8 -57.3 14.1 + {0x24, 18}, // 24 -14.3 -50.2 -58.5 -58.4 -57.8 -57.4 13.7 + {0x1D, 14}, // 1D -15.7 -50.7 -58.6 -58.6 -57.8 -57.5 13.3 + {0x1B, 13}, // 1B -17.0 -51.3 -58.5 -58.4 -57.7 -57.5 13.1 + {0x19, 16}, // 19 -18.6 -52.0 -58.6 -58.5 -57.8 -57.5 12.9 + {0x22, 10}, // 22 -19.6 -52.5 -58.5 -58.6 -57.7 -57.4 12.9 + {0x0D, 15}, // D -21.1 -53.3 -58.6 -58.6 -57.8 -57.4 12.6 + {0x0B, 12}, // B -22.3 -53.9 -58.6 -58.5 -57.8 -57.4 12.5 + {0x09, 15}, // 9 -23.8 -54.7 -58.5 -58.5 -57.8 -57.5 12.4 + {0x21, 10}, // 21 -24.8 -55.1 -58.5 -58.5 -57.7 -57.5 12.5 + {0x13, 17}, // 13 -26.5 -55.9 -58.6 -58.5 -57.6 -57.6 12.3 + {0x05, 12}, // 5 -27.7 -56.4 -58.4 -58.4 -57.7 -57.5 12.2 + {0x12, 12}, // 12 -28.9 -57.1 -58.4 -58.5 -57.7 -57.3 12.2 +}; + +static const PowerTableItem PA_TABLE_915[] = { + {0xC0, 26}, // C0 9.4 -33.5 -58.5 -58.4 -55.8 -32.6 31.8 + {0xC3, 11}, // C3 8.3 -41.5 -58.6 -58.4 -56.3 -38.0 29.3 + {0xC6, 11}, // C6 7.2 -42.5 -58.5 -58.4 -56.7 -40.5 27.4 + {0xC9, 10}, // C9 6.2 -37.6 -58.6 -58.4 -57.2 -38.8 25.9 + {0xCD, 12}, // CD 5.0 -34.2 -58.6 -58.5 -57.5 -37.3 24.3 + {0x84, 11}, // 84 3.9 -32.0 -58.6 -58.4 -57.7 -40.1 19.7 + {0x87, 10}, // 87 2.9 -36.5 -58.4 -58.5 -57.7 -39.6 18.9 + {0x8A, 11}, // 8A 1.8 -42.2 -58.5 -58.4 -57.7 -39.6 18.1 + {0x8D, 13}, // 8D 0.5 -46.8 -58.5 -58.5 -57.7 -40.4 17.3 + {0x8E, 11}, // 8E -0.6 -46.6 -58.5 -58.5 -57.8 -41.1 16.7 + {0x51, 10}, // 51 -1.6 -38.7 -58.4 -58.5 -57.7 -46.9 16.0 + {0x3E, 11}, // 3E -2.7 -50.0 -58.5 -58.4 -57.6 -55.3 20.7 + {0x3B, 11}, // 3B -3.8 -50.7 -58.6 -58.4 -57.6 -55.2 18.9 + {0x39, 13}, // 39 -5.1 -50.0 -58.5 -58.5 -57.6 -54.0 17.7 + {0x2B, 13}, // 2B -6.4 -47.6 -58.4 -58.4 -57.8 -52.1 16.5 + {0x36, 15}, // 36 -7.9 -46.9 -58.5 -58.4 -57.7 -51.2 15.8 + {0x35, 14}, // 35 -9.3 -46.7 -58.6 -58.4 -57.7 -50.7 15.2 + {0x26, 16}, // 26 -10.9 -47.0 -58.6 -58.4 -57.8 -50.9 14.5 + {0x25, 14}, // 25 -12.3 -47.2 -58.6 -58.3 -57.7 -51.0 14.1 + {0x24, 18}, // 24 -14.1 -48.1 -58.4 -58.4 -57.8 -51.4 13.7 + {0x1D, 14}, // 1D -15.5 -48.7 -58.4 -58.5 -57.7 -51.9 13.2 + {0x1B, 13}, // 1B -16.8 -49.3 -58.6 -58.4 -57.8 -52.3 13.0 + {0x19, 15}, // 19 -18.3 -50.2 -58.5 -58.5 -57.6 -52.8 12.8 + {0x18, 10}, // 18 -19.3 -50.6 -58.5 -58.5 -57.7 -53.1 12.7 + {0x17, 10}, // 17 -20.3 -51.2 -58.6 -58.5 -57.8 -53.1 12.6 + {0x0C, 11}, // C -21.4 -51.8 -58.4 -58.5 -57.7 -53.4 12.5 + {0x0A, 13}, // A -22.7 -52.6 -58.5 -58.4 -57.7 -53.6 12.4 + {0x08, 16}, // 8 -24.3 -53.6 -58.4 -58.4 -57.6 -54.1 12.3 + {0x13, 19}, // 13 -26.2 -54.6 -58.4 -58.5 -57.7 -54.3 12.2 + {0x05, 11}, // 5 -27.3 -55.3 -58.4 -58.4 -57.8 -54.5 12.1 + {0x12, 13}, // 12 -28.6 -55.9 -58.6 -58.5 -57.7 -54.7 12.1 + {0x03, 12}, // 3 -29.8 -56.9 -58.5 -58.4 -57.7 -54.7 12.0 +}; +} // namespace esphome::cc1101 diff --git a/tests/components/cc1101/common.yaml b/tests/components/cc1101/common.yaml new file mode 100644 index 0000000000..7fd265ca4a --- /dev/null +++ b/tests/components/cc1101/common.yaml @@ -0,0 +1,20 @@ +cc1101: + id: transceiver + cs_pin: ${cs_pin} + frequency: 433920 + if_frequency: 153 + filter_bandwidth: 203 + channel: 0 + channel_spacing: 200 + symbol_rate: 5000 + modulation_type: ASK/OOK + +button: + - platform: template + name: "CC1101 Button" + on_press: + then: + - cc1101.begin_tx: transceiver + - cc1101.begin_rx: transceiver + - cc1101.set_idle: transceiver + - cc1101.reset: transceiver diff --git a/tests/components/cc1101/test.esp32-idf.yaml b/tests/components/cc1101/test.esp32-idf.yaml new file mode 100644 index 0000000000..e075629679 --- /dev/null +++ b/tests/components/cc1101/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +substitutions: + cs_pin: GPIO5 + +packages: + spi: !include ../../test_build_components/common/spi/esp32-idf.yaml + remote_receiver: !include ../../test_build_components/common/remote_receiver/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/cc1101/test.esp8266.yaml b/tests/components/cc1101/test.esp8266.yaml new file mode 100644 index 0000000000..7900658bc1 --- /dev/null +++ b/tests/components/cc1101/test.esp8266.yaml @@ -0,0 +1,8 @@ +substitutions: + cs_pin: GPIO5 + +packages: + spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml + remote_receiver: !include ../../test_build_components/common/remote_receiver/esp8266-ard.yaml + +<<: !include common.yaml From a3199792c619465f5b432e1ea61b7529aeffbfa9 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 18 Nov 2025 14:11:49 +1000 Subject: [PATCH 0486/1145] [build] Don't clear pio cache unless requested (#11966) --- esphome/writer.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/writer.py b/esphome/writer.py index 1e49a2c961..3124e9e12c 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -121,7 +121,7 @@ def update_storage_json() -> None: ) else: _LOGGER.info("Core config or version changed, cleaning build files...") - clean_build() + clean_build(clear_pio_cache=False) elif storage_should_update_cmake_cache(old, new): _LOGGER.info("Integrations changed, cleaning cmake cache...") clean_cmake_cache() @@ -301,7 +301,7 @@ def clean_cmake_cache(): pioenvs_cmake_path.unlink() -def clean_build(): +def clean_build(clear_pio_cache: bool = True): import shutil # Allow skipping cache cleaning for integration tests @@ -322,6 +322,9 @@ def clean_build(): _LOGGER.info("Deleting %s", dependencies_lock) dependencies_lock.unlink() + if not clear_pio_cache: + return + # Clean PlatformIO cache to resolve CMake compiler detection issues # This helps when toolchain paths change or get corrupted try: From 71bb94524e0b6733d2c0df159852e49493adf1fd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 27 Nov 2025 16:33:08 -0600 Subject: [PATCH 0487/1145] [usb_uart] Wake main loop immediately when USB data arrives (#12148) --- esphome/components/usb_uart/__init__.py | 7 ++++++- esphome/components/usb_uart/usb_uart.cpp | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/components/usb_uart/__init__.py b/esphome/components/usb_uart/__init__.py index a852e1f78b..d9bb58ae3a 100644 --- a/esphome/components/usb_uart/__init__.py +++ b/esphome/components/usb_uart/__init__.py @@ -1,4 +1,5 @@ import esphome.codegen as cg +from esphome.components import socket from esphome.components.uart import ( CONF_DATA_BITS, CONF_PARITY, @@ -17,7 +18,7 @@ from esphome.const import ( ) from esphome.cpp_types import Component -AUTO_LOAD = ["uart", "usb_host", "bytebuffer"] +AUTO_LOAD = ["uart", "usb_host", "bytebuffer", "socket"] CODEOWNERS = ["@clydebarrow"] usb_uart_ns = cg.esphome_ns.namespace("usb_uart") @@ -116,6 +117,10 @@ CONFIG_SCHEMA = cv.ensure_list( async def to_code(config): + # Enable wake_loop_threadsafe for low-latency USB data processing + # The USB task queues data events that need immediate processing + socket.require_wake_loop_threadsafe() + for device in config: var = await register_usb_client(device) for index, channel in enumerate(device[CONF_CHANNELS]): diff --git a/esphome/components/usb_uart/usb_uart.cpp b/esphome/components/usb_uart/usb_uart.cpp index c24fffb11d..2def7c81c6 100644 --- a/esphome/components/usb_uart/usb_uart.cpp +++ b/esphome/components/usb_uart/usb_uart.cpp @@ -2,6 +2,7 @@ #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) #include "usb_uart.h" #include "esphome/core/log.h" +#include "esphome/core/application.h" #include "esphome/components/uart/uart_debugger.h" #include @@ -262,6 +263,11 @@ void USBUartComponent::start_input(USBUartChannel *channel) { // Push to lock-free queue for main loop processing // Push always succeeds because pool size == queue size this->usb_data_queue_.push(chunk); + + // Wake main loop immediately to process USB data instead of waiting for select() timeout +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + App.wake_loop_threadsafe(); +#endif } // On success, restart input immediately from USB task for performance From 48caff13c9dc2759aafef3097375ba37c1e290ad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Nov 2025 15:33:28 -0600 Subject: [PATCH 0488/1145] [espnow] Initialize LwIP stack when running without WiFi component (#12169) --- esphome/components/espnow/espnow_component.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/components/espnow/espnow_component.cpp b/esphome/components/espnow/espnow_component.cpp index d2f136d1c7..bc05833709 100644 --- a/esphome/components/espnow/espnow_component.cpp +++ b/esphome/components/espnow/espnow_component.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -157,6 +158,12 @@ bool ESPNowComponent::is_wifi_enabled() { } void ESPNowComponent::setup() { +#ifndef USE_WIFI + // Initialize LwIP stack for wake_loop_threadsafe() socket support + // When WiFi component is present, it handles esp_netif_init() + ESP_ERROR_CHECK(esp_netif_init()); +#endif + if (this->enable_on_boot_) { this->enable_(); } else { From 73fa9230e628ad3a71c3eb33d69d134e43d0ea16 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sat, 29 Nov 2025 19:38:29 +1100 Subject: [PATCH 0489/1145] [helpers] Add conversion from FixedVector to std::vector (#12179) --- esphome/core/helpers.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 52a0746057..28f242b8b2 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -225,6 +225,9 @@ template class FixedVector { other.reset_(); } + // Allow conversion to std::vector + operator std::vector() const { return {data_, data_ + size_}; } + FixedVector &operator=(FixedVector &&other) noexcept { if (this != &other) { // Delete our current data From 9d6c81ec234f3479ef1818dc5b55b3df1e0f1bae Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 29 Nov 2025 18:36:12 -0600 Subject: [PATCH 0490/1145] [hlk_fm22x] Fix Action::play method signatures (#12192) --- esphome/components/hlk_fm22x/hlk_fm22x.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/hlk_fm22x/hlk_fm22x.h b/esphome/components/hlk_fm22x/hlk_fm22x.h index 5ecc715ea1..9c981d3c44 100644 --- a/esphome/components/hlk_fm22x/hlk_fm22x.h +++ b/esphome/components/hlk_fm22x/hlk_fm22x.h @@ -189,7 +189,7 @@ template class EnrollmentAction : public Action, public P TEMPLATABLE_VALUE(std::string, name) TEMPLATABLE_VALUE(uint8_t, direction) - void play(Ts... x) override { + void play(const Ts &...x) override { auto name = this->name_.value(x...); auto direction = (HlkFm22xFaceDirection) this->direction_.value(x...); this->parent_->enroll_face(name, direction); @@ -200,7 +200,7 @@ template class DeleteAction : public Action, public Paren public: TEMPLATABLE_VALUE(int16_t, face_id) - void play(Ts... x) override { + void play(const Ts &...x) override { auto face_id = this->face_id_.value(x...); this->parent_->delete_face(face_id); } @@ -208,17 +208,17 @@ template class DeleteAction : public Action, public Paren template class DeleteAllAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->delete_all_faces(); } + void play(const Ts &...x) override { this->parent_->delete_all_faces(); } }; template class ScanAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->scan_face(); } + void play(const Ts &...x) override { this->parent_->scan_face(); } }; template class ResetAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->reset(); } + void play(const Ts &...x) override { this->parent_->reset(); } }; } // namespace esphome::hlk_fm22x From 5c715206358bd9f0872c1e1b4db78d074ff433db Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 30 Nov 2025 08:04:33 -0500 Subject: [PATCH 0491/1145] [mopeka_pro_check] Fix negative temperatures (#12198) Co-authored-by: Claude --- esphome/components/mopeka_pro_check/mopeka_pro_check.cpp | 4 ++-- esphome/components/mopeka_pro_check/mopeka_pro_check.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp index 9527f09f59..42d61f81a3 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp @@ -116,7 +116,7 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) // Get temperature of sensor if (this->temperature_ != nullptr) { - uint8_t temp_in_c = this->parse_temperature_(manu_data.data); + int8_t temp_in_c = this->parse_temperature_(manu_data.data); this->temperature_->publish_state(temp_in_c); } @@ -145,7 +145,7 @@ uint32_t MopekaProCheck::parse_distance_(const std::vector &message) { (MOPEKA_LPG_COEF[0] + MOPEKA_LPG_COEF[1] * raw_t + MOPEKA_LPG_COEF[2] * raw_t * raw_t)); } -uint8_t MopekaProCheck::parse_temperature_(const std::vector &message) { return (message[2] & 0x7F) - 40; } +int8_t MopekaProCheck::parse_temperature_(const std::vector &message) { return (message[2] & 0x7F) - 40; } SensorReadQuality MopekaProCheck::parse_read_quality_(const std::vector &message) { // Since a 8 bit value is being shifted and truncated to 2 bits all possible values are defined as enumeration diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.h b/esphome/components/mopeka_pro_check/mopeka_pro_check.h index 4cbe8f2afe..41fb312152 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.h +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.h @@ -61,7 +61,7 @@ class MopekaProCheck : public Component, public esp32_ble_tracker::ESPBTDeviceLi uint8_t parse_battery_level_(const std::vector &message); uint32_t parse_distance_(const std::vector &message); - uint8_t parse_temperature_(const std::vector &message); + int8_t parse_temperature_(const std::vector &message); SensorReadQuality parse_read_quality_(const std::vector &message); }; From 3fbed1fa79cc8e32506972c100404d22857cb8e1 Mon Sep 17 00:00:00 2001 From: Darsey Litzenberger Date: Sun, 30 Nov 2025 17:17:50 -0700 Subject: [PATCH 0492/1145] [ade7953] Apply voltage_gain setting to both channels (#12180) --- esphome/components/ade7953_base/ade7953_base.cpp | 13 ++++++++----- esphome/components/ade7953_base/ade7953_base.h | 10 ++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/esphome/components/ade7953_base/ade7953_base.cpp b/esphome/components/ade7953_base/ade7953_base.cpp index 5f5fdd27ee..821e4a3105 100644 --- a/esphome/components/ade7953_base/ade7953_base.cpp +++ b/esphome/components/ade7953_base/ade7953_base.cpp @@ -25,7 +25,8 @@ void ADE7953::setup() { this->ade_write_8(PGA_V_8, pga_v_); this->ade_write_8(PGA_IA_8, pga_ia_); this->ade_write_8(PGA_IB_8, pga_ib_); - this->ade_write_32(AVGAIN_32, vgain_); + this->ade_write_32(AVGAIN_32, avgain_); + this->ade_write_32(BVGAIN_32, bvgain_); this->ade_write_32(AIGAIN_32, aigain_); this->ade_write_32(BIGAIN_32, bigain_); this->ade_write_32(AWGAIN_32, awgain_); @@ -34,7 +35,8 @@ void ADE7953::setup() { this->ade_read_8(PGA_V_8, &pga_v_); this->ade_read_8(PGA_IA_8, &pga_ia_); this->ade_read_8(PGA_IB_8, &pga_ib_); - this->ade_read_32(AVGAIN_32, &vgain_); + this->ade_read_32(AVGAIN_32, &avgain_); + this->ade_read_32(BVGAIN_32, &bvgain_); this->ade_read_32(AIGAIN_32, &aigain_); this->ade_read_32(BIGAIN_32, &bigain_); this->ade_read_32(AWGAIN_32, &awgain_); @@ -63,13 +65,14 @@ void ADE7953::dump_config() { " PGA_V_8: 0x%X\n" " PGA_IA_8: 0x%X\n" " PGA_IB_8: 0x%X\n" - " VGAIN_32: 0x%08jX\n" + " AVGAIN_32: 0x%08jX\n" + " BVGAIN_32: 0x%08jX\n" " AIGAIN_32: 0x%08jX\n" " BIGAIN_32: 0x%08jX\n" " AWGAIN_32: 0x%08jX\n" " BWGAIN_32: 0x%08jX", - this->use_acc_energy_regs_, pga_v_, pga_ia_, pga_ib_, (uintmax_t) vgain_, (uintmax_t) aigain_, - (uintmax_t) bigain_, (uintmax_t) awgain_, (uintmax_t) bwgain_); + this->use_acc_energy_regs_, pga_v_, pga_ia_, pga_ib_, (uintmax_t) avgain_, (uintmax_t) bvgain_, + (uintmax_t) aigain_, (uintmax_t) bigain_, (uintmax_t) awgain_, (uintmax_t) bwgain_); } #define ADE_PUBLISH_(name, val, factor) \ diff --git a/esphome/components/ade7953_base/ade7953_base.h b/esphome/components/ade7953_base/ade7953_base.h index d711a5c6be..bcafddca4e 100644 --- a/esphome/components/ade7953_base/ade7953_base.h +++ b/esphome/components/ade7953_base/ade7953_base.h @@ -46,7 +46,12 @@ class ADE7953 : public PollingComponent, public sensor::Sensor { void set_pga_ib(uint8_t pga_ib) { pga_ib_ = pga_ib; } // Set input gains - void set_vgain(uint32_t vgain) { vgain_ = vgain; } + void set_vgain(uint32_t vgain) { + // Datasheet says: "to avoid discrepancies in other registers, + // if AVGAIN is set then BVGAIN should be set to the same value." + avgain_ = vgain; + bvgain_ = vgain; + } void set_aigain(uint32_t aigain) { aigain_ = aigain; } void set_bigain(uint32_t bigain) { bigain_ = bigain; } void set_awgain(uint32_t awgain) { awgain_ = awgain; } @@ -100,7 +105,8 @@ class ADE7953 : public PollingComponent, public sensor::Sensor { uint8_t pga_v_; uint8_t pga_ia_; uint8_t pga_ib_; - uint32_t vgain_; + uint32_t avgain_; + uint32_t bvgain_; uint32_t aigain_; uint32_t bigain_; uint32_t awgain_; From 1d1e47c7577b64dedd6b864fa946075c8a6ed66a Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:40:56 -0500 Subject: [PATCH 0493/1145] [core] Fix clean all windows (#12217) Co-authored-by: Claude Co-authored-by: J. Nick Koston --- esphome/writer.py | 35 ++++++++--- tests/unit_tests/test_writer.py | 103 ++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 9 deletions(-) diff --git a/esphome/writer.py b/esphome/writer.py index 3124e9e12c..721db07f96 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -1,8 +1,12 @@ +from collections.abc import Callable import importlib import logging import os from pathlib import Path import re +import shutil +import stat +from types import TracebackType from esphome import loader from esphome.config import iter_component_configs, iter_components @@ -301,9 +305,24 @@ def clean_cmake_cache(): pioenvs_cmake_path.unlink() -def clean_build(clear_pio_cache: bool = True): - import shutil +def _rmtree_error_handler( + func: Callable[[str], object], + path: str, + exc_info: tuple[type[BaseException], BaseException, TracebackType | None], +) -> None: + """Error handler for shutil.rmtree to handle read-only files on Windows. + On Windows, git pack files and other files may be marked read-only, + causing shutil.rmtree to fail with "Access is denied". This handler + removes the read-only flag and retries the deletion. + """ + if os.access(path, os.W_OK): + raise exc_info[1].with_traceback(exc_info[2]) + os.chmod(path, stat.S_IWUSR | stat.S_IRUSR) + func(path) + + +def clean_build(clear_pio_cache: bool = True): # Allow skipping cache cleaning for integration tests if os.environ.get("ESPHOME_SKIP_CLEAN_BUILD"): _LOGGER.warning("Skipping build cleaning (ESPHOME_SKIP_CLEAN_BUILD set)") @@ -312,11 +331,11 @@ def clean_build(clear_pio_cache: bool = True): pioenvs = CORE.relative_pioenvs_path() if pioenvs.is_dir(): _LOGGER.info("Deleting %s", pioenvs) - shutil.rmtree(pioenvs) + shutil.rmtree(pioenvs, onerror=_rmtree_error_handler) piolibdeps = CORE.relative_piolibdeps_path() if piolibdeps.is_dir(): _LOGGER.info("Deleting %s", piolibdeps) - shutil.rmtree(piolibdeps) + shutil.rmtree(piolibdeps, onerror=_rmtree_error_handler) dependencies_lock = CORE.relative_build_path("dependencies.lock") if dependencies_lock.is_file(): _LOGGER.info("Deleting %s", dependencies_lock) @@ -337,12 +356,10 @@ def clean_build(clear_pio_cache: bool = True): cache_dir = Path(config.get("platformio", "cache_dir")) if cache_dir.is_dir(): _LOGGER.info("Deleting PlatformIO cache %s", cache_dir) - shutil.rmtree(cache_dir) + shutil.rmtree(cache_dir, onerror=_rmtree_error_handler) def clean_all(configuration: list[str]): - import shutil - data_dirs = [] for config in configuration: item = Path(config) @@ -364,7 +381,7 @@ def clean_all(configuration: list[str]): if item.is_file() and not item.name.endswith(".json"): item.unlink() elif item.is_dir() and item.name != "storage": - shutil.rmtree(item) + shutil.rmtree(item, onerror=_rmtree_error_handler) # Clean PlatformIO project files try: @@ -378,7 +395,7 @@ def clean_all(configuration: list[str]): path = Path(config.get("platformio", pio_dir)) if path.is_dir(): _LOGGER.info("Deleting PlatformIO %s %s", pio_dir, path) - shutil.rmtree(path) + shutil.rmtree(path, onerror=_rmtree_error_handler) GITIGNORE_CONTENT = """# Gitignore settings for ESPHome diff --git a/tests/unit_tests/test_writer.py b/tests/unit_tests/test_writer.py index a2a358f4d3..9fa60c06ec 100644 --- a/tests/unit_tests/test_writer.py +++ b/tests/unit_tests/test_writer.py @@ -1,7 +1,9 @@ """Test writer module functionality.""" from collections.abc import Callable +import os from pathlib import Path +import stat from typing import Any from unittest.mock import MagicMock, patch @@ -15,6 +17,7 @@ from esphome.writer import ( CPP_INCLUDE_BEGIN, CPP_INCLUDE_END, GITIGNORE_CONTENT, + clean_all, clean_build, clean_cmake_cache, storage_should_clean, @@ -1062,3 +1065,103 @@ def test_clean_all_preserves_json_files( # Verify logging mentions cleaning assert "Cleaning" in caplog.text assert str(build_dir) in caplog.text + + +@patch("esphome.writer.CORE") +def test_clean_build_handles_readonly_files( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test clean_build handles read-only files (e.g., git pack files on Windows).""" + # Create directory structure with read-only files + pioenvs_dir = tmp_path / ".pioenvs" + pioenvs_dir.mkdir() + git_dir = pioenvs_dir / ".git" / "objects" / "pack" + git_dir.mkdir(parents=True) + + # Create a read-only file (simulating git pack files on Windows) + readonly_file = git_dir / "pack-abc123.pack" + readonly_file.write_text("pack data") + os.chmod(readonly_file, stat.S_IRUSR) # Read-only + + # Setup mocks + mock_core.relative_pioenvs_path.return_value = pioenvs_dir + mock_core.relative_piolibdeps_path.return_value = tmp_path / ".piolibdeps" + mock_core.relative_build_path.return_value = tmp_path / "dependencies.lock" + + # Verify file is read-only + assert not os.access(readonly_file, os.W_OK) + + # Call the function - should not crash + clean_build() + + # Verify directory was removed despite read-only files + assert not pioenvs_dir.exists() + + +@patch("esphome.writer.CORE") +def test_clean_all_handles_readonly_files( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test clean_all handles read-only files.""" + # Create config directory + config_dir = tmp_path / "config" + config_dir.mkdir() + + build_dir = config_dir / ".esphome" + build_dir.mkdir() + + # Create a subdirectory with read-only files + subdir = build_dir / "subdir" + subdir.mkdir() + readonly_file = subdir / "readonly.txt" + readonly_file.write_text("content") + os.chmod(readonly_file, stat.S_IRUSR) # Read-only + + # Verify file is read-only + assert not os.access(readonly_file, os.W_OK) + + # Call the function - should not crash + clean_all([str(config_dir)]) + + # Verify directory was removed despite read-only files + assert not subdir.exists() + assert build_dir.exists() # .esphome dir itself is preserved + + +@patch("esphome.writer.CORE") +def test_clean_build_reraises_for_other_errors( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test clean_build re-raises errors that are not read-only permission issues.""" + # Create directory structure with a read-only subdirectory + # This prevents file deletion and triggers the error handler + pioenvs_dir = tmp_path / ".pioenvs" + pioenvs_dir.mkdir() + subdir = pioenvs_dir / "subdir" + subdir.mkdir() + test_file = subdir / "test.txt" + test_file.write_text("content") + + # Make subdir read-only so files inside can't be deleted + os.chmod(subdir, stat.S_IRUSR | stat.S_IXUSR) + + # Setup mocks + mock_core.relative_pioenvs_path.return_value = pioenvs_dir + mock_core.relative_piolibdeps_path.return_value = tmp_path / ".piolibdeps" + mock_core.relative_build_path.return_value = tmp_path / "dependencies.lock" + + try: + # Mock os.access in writer module to return True (writable) + # This simulates a case where the error is NOT due to read-only permissions + # so the error handler should re-raise instead of trying to fix permissions + with ( + patch("esphome.writer.os.access", return_value=True), + pytest.raises(PermissionError), + ): + clean_build() + finally: + # Cleanup - restore write permission so tmp_path cleanup works + os.chmod(subdir, stat.S_IRWXU) From 1f5a44be3d33935c06192b01dd1e4843920ea829 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 1 Dec 2025 16:03:00 -0500 Subject: [PATCH 0494/1145] [rtl87xx] Fix AsyncTCP compilation by upgrading FreeRTOS to 8.2.3 (#12230) Co-authored-by: Claude --- esphome/components/rtl87xx/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/rtl87xx/__init__.py b/esphome/components/rtl87xx/__init__.py index 109c986f75..d24ffcea3d 100644 --- a/esphome/components/rtl87xx/__init__.py +++ b/esphome/components/rtl87xx/__init__.py @@ -6,6 +6,7 @@ # in schema.py file in this directory. from esphome import pins +import esphome.codegen as cg from esphome.components import libretiny from esphome.components.libretiny.const import ( COMPONENT_RTL87XX, @@ -45,6 +46,9 @@ CONFIG_SCHEMA.prepend_extra(_set_core_data) async def to_code(config): + # Use FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake required by AsyncTCP 3.4.3+ + # https://github.com/esphome/esphome/issues/10220 + cg.add_platformio_option("custom_versions.freertos", "8.2.3") return await libretiny.component_to_code(config) From ccd23e692b48bc5dfe7988096f97b6e21011f6aa Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:40:46 -0500 Subject: [PATCH 0495/1145] [analog_threshold] Fix oscillation when using invert filter (#12251) Co-authored-by: Claude --- .../analog_threshold_binary_sensor.cpp | 11 +++++++---- .../analog_threshold/analog_threshold_binary_sensor.h | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp b/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp index f83f2aff08..0b3bd0e472 100644 --- a/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp +++ b/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp @@ -12,10 +12,11 @@ void AnalogThresholdBinarySensor::setup() { // TRUE state is defined to be when sensor is >= threshold // so when undefined sensor value initialize to FALSE if (std::isnan(sensor_value)) { + this->raw_state_ = false; this->publish_initial_state(false); } else { - this->publish_initial_state(sensor_value >= - (this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f); + this->raw_state_ = sensor_value >= (this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f; + this->publish_initial_state(this->raw_state_); } } @@ -25,8 +26,10 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) { this->sensor_->add_on_state_callback([this](float sensor_value) { // if there is an invalid sensor reading, ignore the change and keep the current state if (!std::isnan(sensor_value)) { - this->publish_state(sensor_value >= - (this->state ? this->lower_threshold_.value() : this->upper_threshold_.value())); + // Use raw_state_ for hysteresis logic, not this->state which is post-filter + this->raw_state_ = + sensor_value >= (this->raw_state_ ? this->lower_threshold_.value() : this->upper_threshold_.value()); + this->publish_state(this->raw_state_); } }); } diff --git a/esphome/components/analog_threshold/analog_threshold_binary_sensor.h b/esphome/components/analog_threshold/analog_threshold_binary_sensor.h index 55d6b15c36..9ea95d8570 100644 --- a/esphome/components/analog_threshold/analog_threshold_binary_sensor.h +++ b/esphome/components/analog_threshold/analog_threshold_binary_sensor.h @@ -20,6 +20,7 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina sensor::Sensor *sensor_{nullptr}; TemplatableValue upper_threshold_{}; TemplatableValue lower_threshold_{}; + bool raw_state_{false}; // Pre-filter state for hysteresis logic }; } // namespace analog_threshold From de68b56c4aed555c71885fac97e90b078f0b3846 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:31:12 -0500 Subject: [PATCH 0496/1145] [rtl87xx] Fix FreeRTOS version for RTL8720C boards (#12261) Co-authored-by: Claude --- esphome/components/rtl87xx/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/rtl87xx/__init__.py b/esphome/components/rtl87xx/__init__.py index d24ffcea3d..8f27544108 100644 --- a/esphome/components/rtl87xx/__init__.py +++ b/esphome/components/rtl87xx/__init__.py @@ -10,7 +10,9 @@ import esphome.codegen as cg from esphome.components import libretiny from esphome.components.libretiny.const import ( COMPONENT_RTL87XX, + FAMILY_RTL8710B, KEY_COMPONENT_DATA, + KEY_FAMILY, KEY_LIBRETINY, LibreTinyComponent, ) @@ -48,7 +50,9 @@ CONFIG_SCHEMA.prepend_extra(_set_core_data) async def to_code(config): # Use FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake required by AsyncTCP 3.4.3+ # https://github.com/esphome/esphome/issues/10220 - cg.add_platformio_option("custom_versions.freertos", "8.2.3") + # Only for RTL8710B (ambz) - RTL8720C (ambz2) requires FreeRTOS 10.x + if CORE.data[KEY_LIBRETINY][KEY_FAMILY] == FAMILY_RTL8710B: + cg.add_platformio_option("custom_versions.freertos", "8.2.3") return await libretiny.component_to_code(config) From 577a6b29416abbbf58b999648b39012a3bafddfc Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:50:28 -0500 Subject: [PATCH 0497/1145] Bump version to 2025.11.3 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index d30bd84257..901b0c92c0 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.11.2 +PROJECT_NUMBER = 2025.11.3 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 45b726e599..10f0b3af4d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.11.2" +__version__ = "2025.11.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From b3812b58112332ca9050588cf27b00cd59eb7afe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 3 Dec 2025 10:22:06 -0600 Subject: [PATCH 0498/1145] [text_sensor] Fix spurious raw_state deprecation warnings (#12262) --- esphome/components/text_sensor/text_sensor.h | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 7217806a55..e411f57d67 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -24,7 +24,17 @@ void log_text_sensor(const char *tag, const char *prefix, const char *type, Text class TextSensor : public EntityBase, public EntityBase_DeviceClass { public: + std::string state; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + /// @deprecated Use get_raw_state() instead. This member will be removed in ESPHome 2026.6.0. + ESPDEPRECATED("Use get_raw_state() instead of .raw_state. Will be removed in 2026.6.0", "2025.12.0") + std::string raw_state; + TextSensor() = default; + ~TextSensor() = default; +#pragma GCC diagnostic pop /// Getter-syntax for .state. std::string get_state() const; @@ -49,15 +59,6 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { /// Add a callback that will be called every time the sensor sends a raw value. void add_on_raw_state_callback(std::function callback); - std::string state; - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - /// @deprecated Use get_raw_state() instead. This member will be removed in ESPHome 2026.6.0. - ESPDEPRECATED("Use get_raw_state() instead of .raw_state. Will be removed in 2026.6.0", "2025.12.0") - std::string raw_state; -#pragma GCC diagnostic pop - // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) From 623cdac689517fa771898ceb09f902fe1ec5caf5 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Wed, 3 Dec 2025 18:36:35 +0100 Subject: [PATCH 0499/1145] [tests] Add testing of command line substitutions (#12210) Co-authored-by: J. Nick Koston Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- .../fixtures/substitutions/00-simple_var.approved.yaml | 4 ++++ .../fixtures/substitutions/00-simple_var.input.yaml | 9 +++++++++ tests/unit_tests/test_substitutions.py | 4 +++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml b/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml index 6f3bae1ac4..9ed9b99c49 100644 --- a/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml +++ b/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml @@ -8,6 +8,8 @@ substitutions: position: x: 79 y: 82 + a: 15 + b: 20 esphome: name: test @@ -34,3 +36,5 @@ test_list: - '{{{"AA"}}}' - '"HELLO"' - '{ 79, 82 }' + - a: 15 should be 15, overridden from command line + b: 20 should stay as 20, not overridden diff --git a/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml b/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml index 306119b753..64701c03dd 100644 --- a/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml +++ b/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml @@ -11,6 +11,13 @@ substitutions: position: x: 79 y: 82 + a: 10 + b: 20 + +# The following key is only used by the test framework +# to simulate command line substitutions +command_line_substitutions: + a: 15 test_list: - "$var1" @@ -35,3 +42,5 @@ test_list: - ${ '{{{"AA"}}}' } - ${ '"HELLO"' } - '{ ${position.x}, ${position.y} }' + - a: ${a} should be 15, overridden from command line + b: ${b} should stay as 20, not overridden diff --git a/tests/unit_tests/test_substitutions.py b/tests/unit_tests/test_substitutions.py index eb9ef5443c..cba1e398c3 100644 --- a/tests/unit_tests/test_substitutions.py +++ b/tests/unit_tests/test_substitutions.py @@ -138,9 +138,11 @@ def test_substitutions_fixtures( # Load using ESPHome's YAML loader config = yaml_util.load_yaml(source_path) + command_line_substitutions = config.pop("command_line_substitutions", None) + config = do_packages_pass(config) - substitutions.do_substitution_pass(config, None) + substitutions.do_substitution_pass(config, command_line_substitutions) resolve_extend_remove(config) verify_database_result = verify_database(config) From a24ba260689310e542a2da5f34cab223d22159fa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 3 Dec 2025 12:33:57 -0600 Subject: [PATCH 0500/1145] [core] Improve CORE.data documentation with dataclass pattern (#12170) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .ai/instructions.md | 44 ++++++++++++++++++++++++---------------- esphome/core/__init__.py | 18 ++++++++++++++-- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/.ai/instructions.md b/.ai/instructions.md index 681829bae6..9d48f467cb 100644 --- a/.ai/instructions.md +++ b/.ai/instructions.md @@ -402,35 +402,45 @@ This document provides essential context for AI models interacting with this pro _use_feature = True ``` - **Good Pattern (CORE.data with Helpers):** + **Bad Pattern (Flat Keys):** ```python + # Don't do this - keys should be namespaced under component domain + MY_FEATURE_KEY = "my_component_feature" + CORE.data[MY_FEATURE_KEY] = True + ``` + + **Good Pattern (dataclass):** + ```python + from dataclasses import dataclass, field from esphome.core import CORE - # Keys for CORE.data storage - COMPONENT_STATE_KEY = "my_component_state" - USE_FEATURE_KEY = "my_component_use_feature" + DOMAIN = "my_component" - def _get_component_state() -> list: - """Get component state from CORE.data.""" - return CORE.data.setdefault(COMPONENT_STATE_KEY, []) + @dataclass + class MyComponentData: + feature_enabled: bool = False + item_count: int = 0 + items: list[str] = field(default_factory=list) - def _get_use_feature() -> bool | None: - """Get feature flag from CORE.data.""" - return CORE.data.get(USE_FEATURE_KEY) + def _get_data() -> MyComponentData: + if DOMAIN not in CORE.data: + CORE.data[DOMAIN] = MyComponentData() + return CORE.data[DOMAIN] - def _set_use_feature(value: bool) -> None: - """Set feature flag in CORE.data.""" - CORE.data[USE_FEATURE_KEY] = value + def request_feature() -> None: + _get_data().feature_enabled = True - def enable_feature(): - _set_use_feature(True) + def add_item(item: str) -> None: + _get_data().items.append(item) ``` + If you need a real-world example, search for components that use `@dataclass` with `CORE.data` in the codebase. Note: Some components may use `TypedDict` for dictionary-based storage; both patterns are acceptable depending on your needs. + **Why this matters:** - Module-level globals persist between compilation runs if the dashboard doesn't fork/exec - `CORE.data` automatically clears between runs - - Typed helper functions provide better IDE support and maintainability - - Encapsulation makes state management explicit and testable + - Namespacing under `DOMAIN` prevents key collisions between components + - `@dataclass` provides type safety and cleaner attribute access * **Security:** Be mindful of security when making changes to the API, web server, or any other network-related code. Do not hardcode secrets or keys. diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 08753b0f2d..721cd5787d 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -541,8 +541,22 @@ class EsphomeCore: self.friendly_name: str | None = None # The area / zone of the node self.area: str | None = None - # Additional data components can store temporary data in - # The first key to this dict should always be the integration name + # Additional data components can store temporary data in. + # This dict is cleared between compilation runs. + # + # Usage pattern (use @dataclass for type safety): + # DOMAIN = "my_component" + # + # @dataclass + # class MyComponentData: + # feature_enabled: bool = False + # + # def _get_data() -> MyComponentData: + # if DOMAIN not in CORE.data: + # CORE.data[DOMAIN] = MyComponentData() + # return CORE.data[DOMAIN] + # + # The first key should always be the component domain name (DOMAIN constant). self.data = {} # The relative path to the configuration YAML self.config_path: Path | None = None From 03aaa66f8e9c9cec01588e20a4797d7c8926af80 Mon Sep 17 00:00:00 2001 From: jsmarion Date: Wed, 3 Dec 2025 14:35:14 -0500 Subject: [PATCH 0501/1145] [cst816] Fix CST826 & CST836 (#12260) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- .../cst816/touchscreen/cst816_touchscreen.cpp | 64 ++++++++++++------- .../cst816/touchscreen/cst816_touchscreen.h | 2 + 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp index f6280a75a1..5be93692c0 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp @@ -9,27 +9,40 @@ void CST816Touchscreen::continue_setup_() { this->interrupt_pin_->setup(); this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); } - if (this->read_byte(REG_CHIP_ID, &this->chip_id_)) { - switch (this->chip_id_) { - case CST820_CHIP_ID: - case CST826_CHIP_ID: - case CST716_CHIP_ID: - case CST816S_CHIP_ID: - case CST816D_CHIP_ID: - case CST816T_CHIP_ID: - break; - default: + + if (!this->read_byte(REG_CHIP_ID, &this->chip_id_) && !this->skip_probe_) { + this->status_set_error(LOG_STR("Failed to read chip ID")); + this->mark_failed(); + return; + } + + // CST826/CST836 return 0 for chip ID, need to read from factory ID register + if (this->chip_id_ == 0) { + if (!this->read_byte(REG_FACTORY_ID, &this->chip_id_) && !this->skip_probe_) { + this->status_set_error(LOG_STR("Failed to read chip ID")); + this->mark_failed(); + return; + } + } + + switch (this->chip_id_) { + case CST716_CHIP_ID: + case CST816S_CHIP_ID: + case CST816D_CHIP_ID: + case CST816T_CHIP_ID: + case CST820_CHIP_ID: + case CST826_CHIP_ID: + case CST836_CHIP_ID: + break; + default: + if (!this->skip_probe_) { ESP_LOGE(TAG, "Unknown chip ID: 0x%02X", this->chip_id_); this->status_set_error(LOG_STR("Unknown chip ID")); this->mark_failed(); return; - } - this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION); - } else if (!this->skip_probe_) { - this->status_set_error(LOG_STR("Failed to read chip id")); - this->mark_failed(); - return; + } } + this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION); if (this->x_raw_max_ == this->x_raw_min_) { this->x_raw_max_ = this->display_->get_native_width(); } @@ -80,11 +93,8 @@ void CST816Touchscreen::dump_config() { this->x_raw_min_, this->x_raw_max_, this->y_raw_min_, this->y_raw_max_); const char *name; switch (this->chip_id_) { - case CST820_CHIP_ID: - name = "CST820"; - break; - case CST826_CHIP_ID: - name = "CST826"; + case CST716_CHIP_ID: + name = "CST716"; break; case CST816S_CHIP_ID: name = "CST816S"; @@ -92,12 +102,18 @@ void CST816Touchscreen::dump_config() { case CST816D_CHIP_ID: name = "CST816D"; break; - case CST716_CHIP_ID: - name = "CST716"; - break; case CST816T_CHIP_ID: name = "CST816T"; break; + case CST820_CHIP_ID: + name = "CST820"; + break; + case CST826_CHIP_ID: + name = "CST826"; + break; + case CST836_CHIP_ID: + name = "CST836"; + break; default: name = "Unknown"; break; diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.h b/esphome/components/cst816/touchscreen/cst816_touchscreen.h index 99ea085e37..99b93d8342 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.h +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.h @@ -19,12 +19,14 @@ static const uint8_t REG_YPOS_HIGH = 0x05; static const uint8_t REG_YPOS_LOW = 0x06; static const uint8_t REG_DIS_AUTOSLEEP = 0xFE; static const uint8_t REG_CHIP_ID = 0xA7; +static const uint8_t REG_FACTORY_ID = 0xAA; static const uint8_t REG_FW_VERSION = 0xA9; static const uint8_t REG_SLEEP = 0xE5; static const uint8_t REG_IRQ_CTL = 0xFA; static const uint8_t IRQ_EN_MOTION = 0x70; static const uint8_t CST826_CHIP_ID = 0x11; +static const uint8_t CST836_CHIP_ID = 0x13; static const uint8_t CST820_CHIP_ID = 0xB7; static const uint8_t CST816S_CHIP_ID = 0xB4; static const uint8_t CST816D_CHIP_ID = 0xB6; From a8518d3cea6d22d4413621240c7a6f6b5ee4fa3a Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Wed, 3 Dec 2025 15:18:59 -0500 Subject: [PATCH 0502/1145] [wifi, wifi_info] Add a WiFi power mode text sensor (#11480) Co-authored-by: J. Nick Koston --- esphome/components/wifi/wifi_component.h | 15 +++++ .../wifi/wifi_component_esp8266.cpp | 10 +++- .../wifi/wifi_component_esp_idf.cpp | 10 +++- .../wifi/wifi_component_libretiny.cpp | 12 +++- .../components/wifi/wifi_component_pico_w.cpp | 10 +++- esphome/components/wifi_info/text_sensor.py | 10 ++++ .../wifi_info/wifi_info_text_sensor.cpp | 60 +++++++++++++++++++ .../wifi_info/wifi_info_text_sensor.h | 11 ++++ tests/components/wifi_info/common.yaml | 4 +- 9 files changed, 137 insertions(+), 5 deletions(-) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 2148f2d4c7..be94e9462b 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -273,6 +273,16 @@ class WiFiConnectStateListener { virtual void on_wifi_connect_state(const std::string &ssid, const bssid_t &bssid) = 0; }; +/** Listener interface for WiFi power save mode changes. + * + * Components can implement this interface to receive power save mode updates + * without the overhead of std::function callbacks. + */ +class WiFiPowerSaveListener { + public: + virtual void on_wifi_power_save(WiFiPowerSaveMode mode) = 0; +}; + /// This component is responsible for managing the ESP WiFi interface. class WiFiComponent : public Component { public: @@ -419,6 +429,10 @@ class WiFiComponent : public Component { void add_connect_state_listener(WiFiConnectStateListener *listener) { this->connect_state_listeners_.push_back(listener); } + /** Add a listener for WiFi power save mode changes. + * Listener receives: WiFiPowerSaveMode + */ + void add_power_save_listener(WiFiPowerSaveListener *listener) { this->power_save_listeners_.push_back(listener); } #endif // USE_WIFI_LISTENERS #ifdef USE_WIFI_RUNTIME_POWER_SAVE @@ -581,6 +595,7 @@ class WiFiComponent : public Component { std::vector ip_state_listeners_; std::vector scan_results_listeners_; std::vector connect_state_listeners_; + std::vector power_save_listeners_; #endif // USE_WIFI_LISTENERS ESPPreferenceObject pref_; #ifdef USE_WIFI_FAST_CONNECT diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index c1c0dd470f..3b1a442bdb 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -104,7 +104,15 @@ bool WiFiComponent::wifi_apply_power_save_() { break; } wifi_fpm_auto_sleep_set_in_null_mode(1); - return wifi_set_sleep_type(power_save); + bool success = wifi_set_sleep_type(power_save); +#ifdef USE_WIFI_LISTENERS + if (success) { + for (auto *listener : this->power_save_listeners_) { + listener->on_wifi_power_save(this->power_save_); + } + } +#endif + return success; } #if LWIP_VERSION_MAJOR != 1 diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index e1f8108892..1f4eb1e42c 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -280,7 +280,15 @@ bool WiFiComponent::wifi_apply_power_save_() { power_save = WIFI_PS_NONE; break; } - return esp_wifi_set_ps(power_save) == ESP_OK; + bool success = esp_wifi_set_ps(power_save) == ESP_OK; +#ifdef USE_WIFI_LISTENERS + if (success) { + for (auto *listener : this->power_save_listeners_) { + listener->on_wifi_power_save(this->power_save_); + } + } +#endif + return success; } bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 0de7003899..1a6f037a87 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -69,7 +69,17 @@ bool WiFiComponent::wifi_sta_pre_setup_() { delay(10); return true; } -bool WiFiComponent::wifi_apply_power_save_() { return WiFi.setSleep(this->power_save_ != WIFI_POWER_SAVE_NONE); } +bool WiFiComponent::wifi_apply_power_save_() { + bool success = WiFi.setSleep(this->power_save_ != WIFI_POWER_SAVE_NONE); +#ifdef USE_WIFI_LISTENERS + if (success) { + for (auto *listener : this->power_save_listeners_) { + listener->on_wifi_power_save(this->power_save_); + } + } +#endif + return success; +} bool WiFiComponent::wifi_sta_ip_config_(const optional &manual_ip) { // enable STA if (!this->wifi_mode_(true, {})) diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index c7dc4120dd..0228755432 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -54,7 +54,15 @@ bool WiFiComponent::wifi_apply_power_save_() { break; } int ret = cyw43_wifi_pm(&cyw43_state, pm); - return ret == 0; + bool success = ret == 0; +#ifdef USE_WIFI_LISTENERS + if (success) { + for (auto *listener : this->power_save_listeners_) { + listener->on_wifi_power_save(this->power_save_); + } + } +#endif + return success; } // TODO: The driver doesn't seem to have an API for this diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index bc0c038f80..8a7f192367 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_DNS_ADDRESS, CONF_IP_ADDRESS, CONF_MAC_ADDRESS, + CONF_POWER_SAVE_MODE, CONF_SCAN_RESULTS, CONF_SSID, ENTITY_CATEGORY_DIAGNOSTIC, @@ -30,6 +31,9 @@ MacAddressWifiInfo = wifi_info_ns.class_( DNSAddressWifiInfo = wifi_info_ns.class_( "DNSAddressWifiInfo", text_sensor.TextSensor, cg.Component ) +PowerSaveModeWiFiInfo = wifi_info_ns.class_( + "PowerSaveModeWiFiInfo", text_sensor.TextSensor, cg.Component +) CONFIG_SCHEMA = cv.Schema( { @@ -58,6 +62,10 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_DNS_ADDRESS): text_sensor.text_sensor_schema( DNSAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ), + cv.Optional(CONF_POWER_SAVE_MODE): text_sensor.text_sensor_schema( + PowerSaveModeWiFiInfo, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), } ) @@ -68,6 +76,7 @@ _NETWORK_INFO_KEYS = { CONF_IP_ADDRESS, CONF_DNS_ADDRESS, CONF_SCAN_RESULTS, + CONF_POWER_SAVE_MODE, } @@ -90,6 +99,7 @@ async def to_code(config): await setup_conf(config, CONF_SCAN_RESULTS) wifi.request_wifi_scan_results() await setup_conf(config, CONF_DNS_ADDRESS) + await setup_conf(config, CONF_POWER_SAVE_MODE) if conf := config.get(CONF_IP_ADDRESS): wifi_info = await text_sensor.new_text_sensor(config[CONF_IP_ADDRESS]) await cg.register_component(wifi_info, config[CONF_IP_ADDRESS]) diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index 6c9d0c00e5..56cf49028c 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -2,6 +2,10 @@ #ifdef USE_WIFI #include "esphome/core/log.h" +#ifdef USE_ESP8266 +#include +#endif + namespace esphome::wifi_info { static const char *const TAG = "wifi_info"; @@ -100,6 +104,62 @@ void BSSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::b this->publish_state(buf); } +/************************ + * PowerSaveModeWiFiInfo + ***********************/ + +void PowerSaveModeWiFiInfo::setup() { wifi::global_wifi_component->add_power_save_listener(this); } + +void PowerSaveModeWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "WiFi Power Save Mode", this); } + +void PowerSaveModeWiFiInfo::on_wifi_power_save(wifi::WiFiPowerSaveMode mode) { +#ifdef USE_ESP8266 +#define MODE_STR(s) static const char MODE_##s[] PROGMEM = #s + MODE_STR(NONE); + MODE_STR(LIGHT); + MODE_STR(HIGH); + MODE_STR(UNKNOWN); + + const char *mode_str_p; + switch (mode) { + case wifi::WIFI_POWER_SAVE_NONE: + mode_str_p = MODE_NONE; + break; + case wifi::WIFI_POWER_SAVE_LIGHT: + mode_str_p = MODE_LIGHT; + break; + case wifi::WIFI_POWER_SAVE_HIGH: + mode_str_p = MODE_HIGH; + break; + default: + mode_str_p = MODE_UNKNOWN; + break; + } + + char mode_str[8]; + strncpy_P(mode_str, mode_str_p, sizeof(mode_str)); + mode_str[sizeof(mode_str) - 1] = '\0'; +#undef MODE_STR +#else + const char *mode_str; + switch (mode) { + case wifi::WIFI_POWER_SAVE_NONE: + mode_str = "NONE"; + break; + case wifi::WIFI_POWER_SAVE_LIGHT: + mode_str = "LIGHT"; + break; + case wifi::WIFI_POWER_SAVE_HIGH: + mode_str = "HIGH"; + break; + default: + mode_str = "UNKNOWN"; + break; + } +#endif + this->publish_state(mode_str); +} + #endif /********************* diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index f1f85c114f..b2242372da 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -63,6 +63,17 @@ class BSSIDWiFiInfo final : public Component, public text_sensor::TextSensor, pu // WiFiConnectStateListener interface void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override; }; + +class PowerSaveModeWiFiInfo final : public Component, + public text_sensor::TextSensor, + public wifi::WiFiPowerSaveListener { + public: + void setup() override; + void dump_config() override; + + // WiFiPowerSaveListener interface + void on_wifi_power_save(wifi::WiFiPowerSaveMode mode) override; +}; #endif class MacAddressWifiInfo final : public Component, public text_sensor::TextSensor { diff --git a/tests/components/wifi_info/common.yaml b/tests/components/wifi_info/common.yaml index f87d381d0c..91dea6c66e 100644 --- a/tests/components/wifi_info/common.yaml +++ b/tests/components/wifi_info/common.yaml @@ -15,4 +15,6 @@ text_sensor: mac_address: name: MAC Address dns_address: - name: DNS ADdress + name: DNS Address + power_save_mode: + name: "WiFi Power Save Mode" From fb331e1c5a5a13768ededf855d3b60fb4c9eccba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 21:04:09 +0000 Subject: [PATCH 0503/1145] Bump actions/stale from 10.1.0 to 10.1.1 (#12270) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 5843b3a5e0..7e03e2a5f9 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Stale - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 + uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch remove-stale-when-updated: true From 20f82a3820881053f8045e639e1fd1a549e7f98a Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 4 Dec 2025 10:49:57 +1100 Subject: [PATCH 0504/1145] [esp32] Add build flag to suppress noexecstack message (#12272) --- esphome/components/esp32/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 14db25fd46..ceb28fd939 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -907,6 +907,7 @@ async def to_code(config): ) cg.set_cpp_standard("gnu++20") cg.add_build_flag("-DUSE_ESP32") + cg.add_build_flag("-Wl,-z,noexecstack") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) variant = config[CONF_VARIANT] cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{variant}") From 22803ef54b5b8ff26dc1c8c8f9cbe7db950fa9af Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Thu, 4 Dec 2025 02:48:11 +0100 Subject: [PATCH 0505/1145] [esp32] Sort variants in situ (#10410) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- .clang-tidy.hash | 2 +- esphome/components/adc/__init__.py | 38 ++++++++++--------- esphome/components/adc/adc_sensor_esp32.cpp | 16 ++++---- esphome/components/deep_sleep/__init__.py | 10 ++--- esphome/components/esp32/__init__.py | 4 +- esphome/components/esp32/const.py | 12 +++--- esphome/components/esp32_can/canbus.py | 4 +- esphome/components/esp32_can/esp32_can.cpp | 4 +- .../components/esp32_rmt_led_strip/light.py | 8 ++-- .../ethernet/ethernet_component.cpp | 4 +- esphome/components/i2s_audio/__init__.py | 4 +- .../improv_serial/improv_serial_component.h | 4 +- .../internal_temperature.cpp | 24 ++++++------ esphome/components/logger/__init__.py | 10 ++--- esphome/components/psram/__init__.py | 4 +- .../components/remote_receiver/__init__.py | 8 ++-- .../components/remote_transmitter/__init__.py | 8 ++-- esphome/components/spi/__init__.py | 2 +- .../components/tinyusb/tinyusb_component.cpp | 2 +- .../components/tinyusb/tinyusb_component.h | 2 +- esphome/components/usb_host/usb_host.h | 4 +- .../components/usb_host/usb_host_client.cpp | 4 +- .../usb_host/usb_host_component.cpp | 4 +- esphome/components/usb_uart/ch34x.cpp | 4 +- esphome/components/usb_uart/cp210x.cpp | 4 +- esphome/components/usb_uart/usb_uart.cpp | 4 +- esphome/components/usb_uart/usb_uart.h | 4 +- esphome/core/defines.h | 6 +-- platformio.ini | 24 ++++++------ tests/component_tests/mipi_spi/test_init.py | 16 ++++---- tests/unit_tests/test_config_validation.py | 20 +++++----- 31 files changed, 133 insertions(+), 131 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 3ade00f0cd..ab3217b5e5 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -3d46b63015d761c85ca9cb77ab79a389509e5776701fb22aed16e7b79d432c0c +29270eecb86ffa07b2b1d2a4ca56dd7f84762ddc89c6248dbf3f012eca8780b6 diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index 15dc447b6c..8f751c496e 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -107,6 +107,17 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { 4: adc_channel_t.ADC_CHANNEL_3, 5: adc_channel_t.ADC_CHANNEL_4, }, + # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32p4/include/soc/adc_channel.h + VARIANT_ESP32P4: { + 16: adc_channel_t.ADC_CHANNEL_0, + 17: adc_channel_t.ADC_CHANNEL_1, + 18: adc_channel_t.ADC_CHANNEL_2, + 19: adc_channel_t.ADC_CHANNEL_3, + 20: adc_channel_t.ADC_CHANNEL_4, + 21: adc_channel_t.ADC_CHANNEL_5, + 22: adc_channel_t.ADC_CHANNEL_6, + 23: adc_channel_t.ADC_CHANNEL_7, + }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h VARIANT_ESP32S2: { 1: adc_channel_t.ADC_CHANNEL_0, @@ -133,16 +144,6 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { 9: adc_channel_t.ADC_CHANNEL_8, 10: adc_channel_t.ADC_CHANNEL_9, }, - VARIANT_ESP32P4: { - 16: adc_channel_t.ADC_CHANNEL_0, - 17: adc_channel_t.ADC_CHANNEL_1, - 18: adc_channel_t.ADC_CHANNEL_2, - 19: adc_channel_t.ADC_CHANNEL_3, - 20: adc_channel_t.ADC_CHANNEL_4, - 21: adc_channel_t.ADC_CHANNEL_5, - 22: adc_channel_t.ADC_CHANNEL_6, - 23: adc_channel_t.ADC_CHANNEL_7, - }, } # pin to adc2 channel mapping @@ -175,6 +176,15 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = { VARIANT_ESP32C6: {}, # no ADC2 # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h VARIANT_ESP32H2: {}, # no ADC2 + # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32p4/include/soc/adc_channel.h + VARIANT_ESP32P4: { + 49: adc_channel_t.ADC_CHANNEL_0, + 50: adc_channel_t.ADC_CHANNEL_1, + 51: adc_channel_t.ADC_CHANNEL_2, + 52: adc_channel_t.ADC_CHANNEL_3, + 53: adc_channel_t.ADC_CHANNEL_4, + 54: adc_channel_t.ADC_CHANNEL_5, + }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h VARIANT_ESP32S2: { 11: adc_channel_t.ADC_CHANNEL_0, @@ -201,14 +211,6 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = { 19: adc_channel_t.ADC_CHANNEL_8, 20: adc_channel_t.ADC_CHANNEL_9, }, - VARIANT_ESP32P4: { - 49: adc_channel_t.ADC_CHANNEL_0, - 50: adc_channel_t.ADC_CHANNEL_1, - 51: adc_channel_t.ADC_CHANNEL_2, - 52: adc_channel_t.ADC_CHANNEL_3, - 53: adc_channel_t.ADC_CHANNEL_4, - 54: adc_channel_t.ADC_CHANNEL_5, - }, } diff --git a/esphome/components/adc/adc_sensor_esp32.cpp b/esphome/components/adc/adc_sensor_esp32.cpp index ab6a89fce0..e25b275cd6 100644 --- a/esphome/components/adc/adc_sensor_esp32.cpp +++ b/esphome/components/adc/adc_sensor_esp32.cpp @@ -74,7 +74,7 @@ void ADCSensor::setup() { adc_cali_handle_t handle = nullptr; #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 + USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 // RISC-V variants and S3 use curve fitting calibration adc_cali_curve_fitting_config_t cali_config = {}; // Zero initialize first #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) @@ -111,7 +111,7 @@ void ADCSensor::setup() { ESP_LOGW(TAG, "Line fitting calibration failed with error %d, will use uncalibrated readings", err); this->setup_flags_.calibration_complete = false; } -#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32S3 || ESP32H2 +#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32H2 || ESP32P4 || ESP32S3 } this->setup_flags_.init_complete = true; @@ -186,11 +186,11 @@ float ADCSensor::sample_fixed_attenuation_() { ESP_LOGW(TAG, "ADC calibration conversion failed with error %d, disabling calibration", err); if (this->calibration_handle_ != nullptr) { #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 + USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); #else // Other ESP32 variants use line fitting calibration adc_cali_delete_scheme_line_fitting(this->calibration_handle_); -#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32S3 || ESP32H2 +#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32H2 || ESP32P4 || ESP32S3 this->calibration_handle_ = nullptr; } } @@ -219,7 +219,7 @@ float ADCSensor::sample_autorange_() { if (this->calibration_handle_ != nullptr) { // Delete old calibration handle #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 + USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); #else adc_cali_delete_scheme_line_fitting(this->calibration_handle_); @@ -231,7 +231,7 @@ float ADCSensor::sample_autorange_() { adc_cali_handle_t handle = nullptr; #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 + USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_curve_fitting_config_t cali_config = {}; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) cali_config.chan = this->channel_; @@ -266,7 +266,7 @@ float ADCSensor::sample_autorange_() { ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err); if (handle != nullptr) { #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 + USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_delete_scheme_curve_fitting(handle); #else adc_cali_delete_scheme_line_fitting(handle); @@ -288,7 +288,7 @@ float ADCSensor::sample_autorange_() { } // Clean up calibration handle #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 + USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_delete_scheme_curve_fitting(handle); #else adc_cali_delete_scheme_line_fitting(handle); diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 19fb726016..18ba167952 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -52,7 +52,10 @@ WAKEUP_PINS = { 38, 39, ], + VARIANT_ESP32C2: [0, 1, 2, 3, 4, 5], VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5], + VARIANT_ESP32C6: [0, 1, 2, 3, 4, 5, 6, 7], + VARIANT_ESP32H2: [7, 8, 9, 10, 11, 12, 13, 14], VARIANT_ESP32S2: [ 0, 1, @@ -101,9 +104,6 @@ WAKEUP_PINS = { 20, 21, ], - VARIANT_ESP32C2: [0, 1, 2, 3, 4, 5], - VARIANT_ESP32C6: [0, 1, 2, 3, 4, 5, 6, 7], - VARIANT_ESP32H2: [7, 8, 9, 10, 11, 12, 13, 14], } @@ -122,10 +122,10 @@ def _validate_ex1_wakeup_mode(value): if value == "ANY_LOW": esp32.only_on_variant( supported=[ - VARIANT_ESP32S2, - VARIANT_ESP32S3, VARIANT_ESP32C6, VARIANT_ESP32H2, + VARIANT_ESP32S2, + VARIANT_ESP32S3, ], msg_prefix="ANY_LOW", )(value) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index ceb28fd939..1d05e16ebd 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -122,14 +122,14 @@ def get_cpu_frequencies(*frequencies): CPU_FREQUENCIES = { VARIANT_ESP32: get_cpu_frequencies(80, 160, 240), - VARIANT_ESP32S2: get_cpu_frequencies(80, 160, 240), - VARIANT_ESP32S3: get_cpu_frequencies(80, 160, 240), VARIANT_ESP32C2: get_cpu_frequencies(80, 120), VARIANT_ESP32C3: get_cpu_frequencies(80, 160), VARIANT_ESP32C5: get_cpu_frequencies(80, 160, 240), VARIANT_ESP32C6: get_cpu_frequencies(80, 120, 160), VARIANT_ESP32H2: get_cpu_frequencies(16, 32, 48, 64, 96), VARIANT_ESP32P4: get_cpu_frequencies(40, 360, 400), + VARIANT_ESP32S2: get_cpu_frequencies(80, 160, 240), + VARIANT_ESP32S3: get_cpu_frequencies(80, 160, 240), } # Make sure not missed here if a new variant added. diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py index 9bef18847f..4358a4b712 100644 --- a/esphome/components/esp32/const.py +++ b/esphome/components/esp32/const.py @@ -13,36 +13,36 @@ KEY_SUBMODULES = "submodules" KEY_EXTRA_BUILD_FILES = "extra_build_files" VARIANT_ESP32 = "ESP32" -VARIANT_ESP32S2 = "ESP32S2" -VARIANT_ESP32S3 = "ESP32S3" VARIANT_ESP32C2 = "ESP32C2" VARIANT_ESP32C3 = "ESP32C3" VARIANT_ESP32C5 = "ESP32C5" VARIANT_ESP32C6 = "ESP32C6" VARIANT_ESP32H2 = "ESP32H2" VARIANT_ESP32P4 = "ESP32P4" +VARIANT_ESP32S2 = "ESP32S2" +VARIANT_ESP32S3 = "ESP32S3" VARIANTS = [ VARIANT_ESP32, - VARIANT_ESP32S2, - VARIANT_ESP32S3, VARIANT_ESP32C2, VARIANT_ESP32C3, VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32H2, VARIANT_ESP32P4, + VARIANT_ESP32S2, + VARIANT_ESP32S3, ] VARIANT_FRIENDLY = { VARIANT_ESP32: "ESP32", - VARIANT_ESP32S2: "ESP32-S2", - VARIANT_ESP32S3: "ESP32-S3", VARIANT_ESP32C2: "ESP32-C2", VARIANT_ESP32C3: "ESP32-C3", VARIANT_ESP32C5: "ESP32-C5", VARIANT_ESP32C6: "ESP32-C6", VARIANT_ESP32H2: "ESP32-H2", VARIANT_ESP32P4: "ESP32-P4", + VARIANT_ESP32S2: "ESP32-S2", + VARIANT_ESP32S3: "ESP32-S3", } esp32_ns = cg.esphome_ns.namespace("esp32") diff --git a/esphome/components/esp32_can/canbus.py b/esphome/components/esp32_can/canbus.py index acc3785f22..5cee27506a 100644 --- a/esphome/components/esp32_can/canbus.py +++ b/esphome/components/esp32_can/canbus.py @@ -64,12 +64,12 @@ CAN_SPEEDS_ESP32_P4 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS = { VARIANT_ESP32: CAN_SPEEDS_ESP32, - VARIANT_ESP32S2: CAN_SPEEDS_ESP32_S2, - VARIANT_ESP32S3: CAN_SPEEDS_ESP32_S3, VARIANT_ESP32C3: CAN_SPEEDS_ESP32_C3, VARIANT_ESP32C6: CAN_SPEEDS_ESP32_C6, VARIANT_ESP32H2: CAN_SPEEDS_ESP32_H2, VARIANT_ESP32P4: CAN_SPEEDS_ESP32_P4, + VARIANT_ESP32S2: CAN_SPEEDS_ESP32_S2, + VARIANT_ESP32S3: CAN_SPEEDS_ESP32_S3, } diff --git a/esphome/components/esp32_can/esp32_can.cpp b/esphome/components/esp32_can/esp32_can.cpp index f9b63b8ebc..c10ad01450 100644 --- a/esphome/components/esp32_can/esp32_can.cpp +++ b/esphome/components/esp32_can/esp32_can.cpp @@ -16,8 +16,8 @@ static const char *const TAG = "esp32_can"; static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config) { switch (bitrate) { -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3) || \ - defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || \ + defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) case canbus::CAN_1KBPS: *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1KBITS(); return true; diff --git a/esphome/components/esp32_rmt_led_strip/light.py b/esphome/components/esp32_rmt_led_strip/light.py index ac4f0b2e92..2ec0750ae6 100644 --- a/esphome/components/esp32_rmt_led_strip/light.py +++ b/esphome/components/esp32_rmt_led_strip/light.py @@ -77,13 +77,13 @@ CONFIG_SCHEMA = cv.All( cv.SplitDefault( CONF_RMT_SYMBOLS, esp32=192, - esp32_s2=192, - esp32_s3=192, - esp32_p4=192, esp32_c3=96, esp32_c5=96, esp32_c6=96, esp32_h2=96, + esp32_p4=192, + esp32_s2=192, + esp32_s3=192, ): cv.int_range(min=2), cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), @@ -91,7 +91,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_IS_WRGB, default=False): cv.boolean, cv.Optional(CONF_USE_DMA): cv.All( esp32.only_on_variant( - supported=[esp32.const.VARIANT_ESP32S3, esp32.const.VARIANT_ESP32P4] + supported=[esp32.const.VARIANT_ESP32P4, esp32.const.VARIANT_ESP32S3] ), cv.boolean, ), diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 9a46aa2687..757e358db3 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -87,8 +87,8 @@ void EthernetComponent::setup() { .intr_flags = 0, }; -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \ - defined(USE_ESP32_VARIANT_ESP32C6) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) auto host = SPI2_HOST; #else auto host = SPI3_HOST; diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index 907429ee0e..0c7c8f6642 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -68,13 +68,13 @@ I2S_ROLE_OPTIONS = { # https://github.com/espressif/esp-idf/blob/master/components/soc/{variant}/include/soc/soc_caps.h (SOC_I2S_NUM) I2S_PORTS = { VARIANT_ESP32: 2, - VARIANT_ESP32S2: 1, - VARIANT_ESP32S3: 2, VARIANT_ESP32C3: 1, VARIANT_ESP32C5: 1, VARIANT_ESP32C6: 1, VARIANT_ESP32H2: 1, VARIANT_ESP32P4: 3, + VARIANT_ESP32S2: 1, + VARIANT_ESP32S3: 2, } i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t") diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h index 057247f376..abe50b87f2 100644 --- a/esphome/components/improv_serial/improv_serial_component.h +++ b/esphome/components/improv_serial/improv_serial_component.h @@ -11,8 +11,8 @@ #ifdef USE_ESP32 #include -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \ - defined(USE_ESP32_VARIANT_ESP32H2) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) #include #include #endif diff --git a/esphome/components/internal_temperature/internal_temperature.cpp b/esphome/components/internal_temperature/internal_temperature.cpp index 28ac55d6de..6365392ce9 100644 --- a/esphome/components/internal_temperature/internal_temperature.cpp +++ b/esphome/components/internal_temperature/internal_temperature.cpp @@ -7,9 +7,9 @@ extern "C" { uint8_t temprature_sens_read(); } -#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \ - defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4) +#elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \ + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ + defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "driver/temperature_sensor.h" #endif // USE_ESP32_VARIANT #endif // USE_ESP32 @@ -27,9 +27,9 @@ namespace internal_temperature { static const char *const TAG = "internal_temperature"; #ifdef USE_ESP32 -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \ - defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \ - defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ + defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) static temperature_sensor_handle_t tsensNew = NULL; #endif // USE_ESP32_VARIANT #endif // USE_ESP32 @@ -43,9 +43,9 @@ void InternalTemperatureSensor::update() { ESP_LOGV(TAG, "Raw temperature value: %d", raw); temperature = (raw - 32) / 1.8f; success = (raw != 128); -#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \ - defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4) +#elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \ + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ + defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) esp_err_t result = temperature_sensor_get_celsius(tsensNew, &temperature); success = (result == ESP_OK); if (!success) { @@ -81,9 +81,9 @@ void InternalTemperatureSensor::update() { void InternalTemperatureSensor::setup() { #ifdef USE_ESP32 -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \ - defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \ - defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ + defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80); esp_err_t result = temperature_sensor_install(&tsens_config, &tsensNew); diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index d9ca44d3c9..c81ade8fc3 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -100,14 +100,14 @@ CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size" UART_SELECTION_ESP32 = { VARIANT_ESP32: [UART0, UART1, UART2], - VARIANT_ESP32S2: [UART0, UART1, USB_CDC], - VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], - VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32C2: [UART0, UART1], + VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32C5: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32P4: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], + VARIANT_ESP32S2: [UART0, UART1, USB_CDC], + VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], } UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1] @@ -238,12 +238,12 @@ CONFIG_SCHEMA = cv.All( CONF_HARDWARE_UART, esp8266=UART0, esp32=UART0, - esp32_s2=USB_CDC, - esp32_s3=USB_SERIAL_JTAG, esp32_c3=USB_SERIAL_JTAG, esp32_c5=USB_SERIAL_JTAG, esp32_c6=USB_SERIAL_JTAG, esp32_p4=USB_SERIAL_JTAG, + esp32_s2=USB_CDC, + esp32_s3=USB_SERIAL_JTAG, rp2040=USB_CDC, bk72xx=DEFAULT, ln882x=DEFAULT, diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index 4ee4e97696..529097889d 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -54,18 +54,18 @@ CONF_ENABLE_ECC = "enable_ecc" SPIRAM_MODES = { VARIANT_ESP32: (TYPE_QUAD,), + VARIANT_ESP32C5: (TYPE_QUAD,), VARIANT_ESP32S2: (TYPE_QUAD,), VARIANT_ESP32S3: (TYPE_QUAD, TYPE_OCTAL), - VARIANT_ESP32C5: (TYPE_QUAD,), VARIANT_ESP32P4: (TYPE_HEX,), } SPIRAM_SPEEDS = { VARIANT_ESP32: (40, 80, 120), + VARIANT_ESP32C5: (40, 80, 120), VARIANT_ESP32S2: (40, 80, 120), VARIANT_ESP32S3: (40, 80, 120), - VARIANT_ESP32C5: (40, 80, 120), VARIANT_ESP32P4: (20, 100, 200), } diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index e79b3f91ed..7f70e2c2a2 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -131,13 +131,13 @@ CONFIG_SCHEMA = remote_base.validate_triggers( cv.SplitDefault( CONF_RMT_SYMBOLS, esp32=192, - esp32_s2=192, - esp32_s3=192, - esp32_p4=192, esp32_c3=96, esp32_c5=96, esp32_c6=96, esp32_h2=96, + esp32_p4=192, + esp32_s2=192, + esp32_s3=192, ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), cv.Optional(CONF_FILTER_SYMBOLS): cv.All( cv.only_on_esp32, cv.int_range(min=0) @@ -148,7 +148,7 @@ CONFIG_SCHEMA = remote_base.validate_triggers( ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), cv.Optional(CONF_USE_DMA): cv.All( esp32.only_on_variant( - supported=[esp32.const.VARIANT_ESP32S3, esp32.const.VARIANT_ESP32P4] + supported=[esp32.const.VARIANT_ESP32P4, esp32.const.VARIANT_ESP32S3] ), cv.boolean, ), diff --git a/esphome/components/remote_transmitter/__init__.py b/esphome/components/remote_transmitter/__init__.py index ff055b959b..ec4f62666d 100644 --- a/esphome/components/remote_transmitter/__init__.py +++ b/esphome/components/remote_transmitter/__init__.py @@ -55,20 +55,20 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_EOT_LEVEL): cv.All(cv.only_on_esp32, cv.boolean), cv.Optional(CONF_USE_DMA): cv.All( esp32.only_on_variant( - supported=[esp32.const.VARIANT_ESP32S3, esp32.const.VARIANT_ESP32P4] + supported=[esp32.const.VARIANT_ESP32P4, esp32.const.VARIANT_ESP32S3] ), cv.boolean, ), cv.SplitDefault( CONF_RMT_SYMBOLS, esp32=64, - esp32_s2=64, - esp32_s3=48, - esp32_p4=48, esp32_c3=48, esp32_c5=48, esp32_c6=48, esp32_h2=48, + esp32_p4=48, + esp32_s2=64, + esp32_s3=48, ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), cv.Optional(CONF_NON_BLOCKING): cv.All(cv.only_on_esp32, cv.boolean), cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True), diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index d803ee66dc..8f23735fff 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -310,7 +310,7 @@ def spi_mode_schema(mode): if pin_count == 8: onlys.append( only_on_variant( - supported=[VARIANT_ESP32S3, VARIANT_ESP32S2, VARIANT_ESP32P4] + supported=[VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3] ) ) return cv.All( diff --git a/esphome/components/tinyusb/tinyusb_component.cpp b/esphome/components/tinyusb/tinyusb_component.cpp index a2057c90ce..19bb545c4b 100644 --- a/esphome/components/tinyusb/tinyusb_component.cpp +++ b/esphome/components/tinyusb/tinyusb_component.cpp @@ -41,4 +41,4 @@ void TinyUSB::dump_config() { } } // namespace esphome::tinyusb -#endif +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/tinyusb/tinyusb_component.h b/esphome/components/tinyusb/tinyusb_component.h index 56c286f455..7d8caade74 100644 --- a/esphome/components/tinyusb/tinyusb_component.h +++ b/esphome/components/tinyusb/tinyusb_component.h @@ -69,4 +69,4 @@ class TinyUSB : public Component { }; } // namespace esphome::tinyusb -#endif +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_host/usb_host.h b/esphome/components/usb_host/usb_host.h index 31bdde2df8..d11a148a0f 100644 --- a/esphome/components/usb_host/usb_host.h +++ b/esphome/components/usb_host/usb_host.h @@ -1,7 +1,7 @@ #pragma once // Should not be needed, but it's required to pass CI clang-tidy checks -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "esphome/core/defines.h" #include "esphome/core/component.h" #include @@ -188,4 +188,4 @@ class USBHost : public Component { } // namespace usb_host } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_host/usb_host_client.cpp b/esphome/components/usb_host/usb_host_client.cpp index fe61353b5d..664f49d137 100644 --- a/esphome/components/usb_host/usb_host_client.cpp +++ b/esphome/components/usb_host/usb_host_client.cpp @@ -1,5 +1,5 @@ // Should not be needed, but it's required to pass CI clang-tidy checks -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "usb_host.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" @@ -531,4 +531,4 @@ void USBClient::release_trq(TransferRequest *trq) { } // namespace usb_host } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_host/usb_host_component.cpp b/esphome/components/usb_host/usb_host_component.cpp index 1e70c289df..790fe6713b 100644 --- a/esphome/components/usb_host/usb_host_component.cpp +++ b/esphome/components/usb_host/usb_host_component.cpp @@ -1,5 +1,5 @@ // Should not be needed, but it's required to pass CI clang-tidy checks -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "usb_host.h" #include #include "esphome/core/log.h" @@ -31,4 +31,4 @@ void USBHost::loop() { } // namespace usb_host } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_uart/ch34x.cpp b/esphome/components/usb_uart/ch34x.cpp index 889366b579..caa4b65657 100644 --- a/esphome/components/usb_uart/ch34x.cpp +++ b/esphome/components/usb_uart/ch34x.cpp @@ -1,4 +1,4 @@ -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "usb_uart.h" #include "usb/usb_host.h" #include "esphome/core/log.h" @@ -78,4 +78,4 @@ void USBUartTypeCH34X::enable_channels() { } } // namespace usb_uart } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_uart/cp210x.cpp b/esphome/components/usb_uart/cp210x.cpp index 5fec0bed02..be024d1ba2 100644 --- a/esphome/components/usb_uart/cp210x.cpp +++ b/esphome/components/usb_uart/cp210x.cpp @@ -1,4 +1,4 @@ -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "usb_uart.h" #include "usb/usb_host.h" #include "esphome/core/log.h" @@ -123,4 +123,4 @@ void USBUartTypeCP210X::enable_channels() { } } // namespace usb_uart } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_uart/usb_uart.cpp b/esphome/components/usb_uart/usb_uart.cpp index fefccd3645..edd01c26c6 100644 --- a/esphome/components/usb_uart/usb_uart.cpp +++ b/esphome/components/usb_uart/usb_uart.cpp @@ -1,5 +1,5 @@ // Should not be needed, but it's required to pass CI clang-tidy checks -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "usb_uart.h" #include "esphome/core/log.h" #include "esphome/core/application.h" @@ -392,4 +392,4 @@ void USBUartTypeCdcAcm::enable_channels() { } // namespace usb_uart } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_uart/usb_uart.h b/esphome/components/usb_uart/usb_uart.h index a5e7905ac5..96c17bd155 100644 --- a/esphome/components/usb_uart/usb_uart.h +++ b/esphome/components/usb_uart/usb_uart.h @@ -1,6 +1,6 @@ #pragma once -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/components/uart/uart_component.h" @@ -173,4 +173,4 @@ class USBUartTypeCH34X : public USBUartTypeCdcAcm { } // namespace usb_uart } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 12dfdba5ce..5d3bca55a2 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -233,9 +233,9 @@ #if defined(USE_ESP32_VARIANT_ESP32S2) #define USE_LOGGER_USB_CDC -#elif defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3) || \ - defined(USE_ESP32_VARIANT_ESP32C5) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || \ - defined(USE_ESP32_VARIANT_ESP32P4) +#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \ + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ + defined(USE_ESP32_VARIANT_ESP32S3) #define USE_LOGGER_USB_CDC #define USE_LOGGER_USB_SERIAL_JTAG #endif diff --git a/platformio.ini b/platformio.ini index 94f58f84ab..81f8b3295b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -378,6 +378,18 @@ build_flags = build_unflags = ${common.build_unflags} +;;;;;;;; ESP32-P4 ;;;;;;;; + +[env:esp32p4-idf] +extends = common:esp32-idf +board = esp32-p4-evboard + +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32p4-idf +build_flags = + ${common:esp32-idf.build_flags} + ${flags:runtime.build_flags} + -DUSE_ESP32_VARIANT_ESP32P4 + ;;;;;;;; ESP32-S2 ;;;;;;;; [env:esp32s2-arduino] @@ -466,18 +478,6 @@ build_flags = build_unflags = ${common.build_unflags} -;;;;;;;; ESP32-P4 ;;;;;;;; - -[env:esp32p4-idf] -extends = common:esp32-idf -board = esp32-p4-evboard - -board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32p4-idf -build_flags = - ${common:esp32-idf.build_flags} - ${flags:runtime.build_flags} - -DUSE_ESP32_VARIANT_ESP32P4 - ;;;;;;;; RP2040 ;;;;;;;; [env:rp2040-pico-arduino] diff --git a/tests/component_tests/mipi_spi/test_init.py b/tests/component_tests/mipi_spi/test_init.py index 56a52df2ab..0c7dea2286 100644 --- a/tests/component_tests/mipi_spi/test_init.py +++ b/tests/component_tests/mipi_spi/test_init.py @@ -304,14 +304,14 @@ def test_all_predefined_models( config = {"model": name} # Get the pins required by this model and find a compatible variant - pins = [ - pin - for pin in [ - model.get_default(pin, None) - for pin in ("dc_pin", "reset_pin", "cs_pin") - ] - if pin is not None - ] + pins = [] + for pin_name in ("dc_pin", "reset_pin", "cs_pin", "enable_pin"): + pin_value = model.get_default(pin_name, None) + if pin_value is not None: + if isinstance(pin_value, list): + pins.extend(pin_value) + else: + pins.append(pin_value) choose_variant_with_pins(pins) # Add required fields that don't have defaults diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 104cdc2b7a..73b15aaadf 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -251,15 +251,6 @@ def test_split_default(framework, platform, variant, full, idf, arduino, simple) "host": "24", } - idf_mappings = { - "esp32_idf": "4", - "esp32_s2_idf": "7", - "esp32_s3_idf": "10", - "esp32_c3_idf": "13", - "esp32_c6_idf": "16", - "esp32_h2_idf": "19", - } - arduino_mappings = { "esp32_arduino": "3", "esp32_s2_arduino": "6", @@ -269,6 +260,15 @@ def test_split_default(framework, platform, variant, full, idf, arduino, simple) "esp32_h2_arduino": "18", } + idf_mappings = { + "esp32_idf": "4", + "esp32_s2_idf": "7", + "esp32_s3_idf": "10", + "esp32_c3_idf": "13", + "esp32_c6_idf": "16", + "esp32_h2_idf": "19", + } + schema = config_validation.Schema( { config_validation.SplitDefault( @@ -293,8 +293,8 @@ def test_split_default(framework, platform, variant, full, idf, arduino, simple) @pytest.mark.parametrize( "framework, platform, message", [ - ("esp-idf", PLATFORM_ESP32, "ESP32 using esp-idf framework"), ("arduino", PLATFORM_ESP32, "ESP32 using arduino framework"), + ("esp-idf", PLATFORM_ESP32, "ESP32 using esp-idf framework"), ("arduino", PLATFORM_ESP8266, "ESP8266 using arduino framework"), ("arduino", PLATFORM_RP2040, "RP2040 using arduino framework"), ("arduino", PLATFORM_BK72XX, "BK72XX using arduino framework"), From 951c5377c5127b89f06ff2965ea194b87c879925 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 4 Dec 2025 02:25:13 -0500 Subject: [PATCH 0506/1145] [ld2420] Add missing USE_SELECT ifdefs (#12275) Co-authored-by: Claude --- esphome/components/ld2420/ld2420.cpp | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index 4fca9494aa..10c623bce0 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -204,8 +204,10 @@ void LD2420Component::dump_config() { LOG_BUTTON(" ", "Factory Reset:", this->factory_reset_button_); LOG_BUTTON(" ", "Restart Module:", this->restart_module_button_); #endif +#ifdef USE_SELECT ESP_LOGCONFIG(TAG, "Select:"); LOG_SELECT(" ", "Operating Mode", this->operating_selector_); +#endif if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) { ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_); } @@ -237,12 +239,20 @@ void LD2420Component::setup() { memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) { this->set_operating_mode(OP_SIMPLE_MODE_STRING); - this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); +#ifdef USE_SELECT + if (this->operating_selector_ != nullptr) { + this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); + } +#endif this->set_mode_(CMD_SYSTEM_MODE_SIMPLE); ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_); } else { this->set_mode_(CMD_SYSTEM_MODE_ENERGY); - this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING); +#ifdef USE_SELECT + if (this->operating_selector_ != nullptr) { + this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING); + } +#endif } #ifdef USE_NUMBER this->init_gate_config_numbers(); @@ -382,8 +392,12 @@ void LD2420Component::set_operating_mode(const char *state) { // If unsupported firmware ignore mode select if (ld2420::get_firmware_int(firmware_ver_) >= CALIBRATE_VERSION_MIN) { this->current_operating_mode = find_uint8(OP_MODE_BY_STR, state); - // Entering Auto Calibrate we need to clear the privoiuos data collection - this->operating_selector_->publish_state(state); + // Entering Auto Calibrate we need to clear the previous data collection +#ifdef USE_SELECT + if (this->operating_selector_ != nullptr) { + this->operating_selector_->publish_state(state); + } +#endif if (current_operating_mode == OP_CALIBRATE_MODE) { this->set_calibration_(true); for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) { @@ -403,7 +417,11 @@ void LD2420Component::set_operating_mode(const char *state) { } } else { this->current_operating_mode = OP_SIMPLE_MODE; - this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); +#ifdef USE_SELECT + if (this->operating_selector_ != nullptr) { + this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); + } +#endif } } From 2af66bd6fceb27ba73f64b8bab6f0e22c3364ac6 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 4 Dec 2025 19:20:55 +1100 Subject: [PATCH 0507/1145] [config] Provide path for `has_at_most_one_of` messages (#12277) --- esphome/config_validation.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index a3fd271a86..ee926b1b6d 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -740,9 +740,10 @@ def has_at_most_one_key(*keys): if not isinstance(obj, dict): raise Invalid("expected dictionary") - number = sum(k in keys for k in obj) - if number > 1: - raise Invalid(f"Cannot specify more than one of {', '.join(keys)}.") + used = set(obj) & set(keys) + if len(used) > 1: + msg = "Cannot specify more than one of '" + "', '".join(used) + "'." + raise MultipleInvalid([Invalid(msg, path=[k]) for k in used]) return obj return validate From 37019231de98f48cb26208481c80d2ada945c56a Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Thu, 4 Dec 2025 10:18:27 +0100 Subject: [PATCH 0508/1145] [lvgl] refactor hello world to yaml file (#12274) --- esphome/components/lvgl/__init__.py | 7 +- esphome/components/lvgl/hello_world.py | 127 ----------------------- esphome/components/lvgl/hello_world.yaml | 118 +++++++++++++++++++++ 3 files changed, 123 insertions(+), 129 deletions(-) delete mode 100644 esphome/components/lvgl/hello_world.py create mode 100644 esphome/components/lvgl/hello_world.yaml diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 040661495c..19c258fcd5 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -1,5 +1,6 @@ import importlib import logging +from pathlib import Path import pkgutil from esphome.automation import build_automation, validate_automation @@ -26,6 +27,7 @@ from esphome.core import CORE, ID, Lambda from esphome.cpp_generator import MockObj from esphome.final_validate import full_config from esphome.helpers import write_file_if_changed +from esphome.yaml_util import load_yaml from . import defines as df, helpers, lv_validation as lvalid, widgets from .automation import disp_update, focused_widgets, refreshed_widgets @@ -37,7 +39,6 @@ from .encoders import ( initial_focus_to_code, ) from .gradient import GRADIENT_SCHEMA, gradients_to_code -from .hello_world import get_hello_world from .keypads import KEYPADS_CONFIG, keypads_to_code from .lv_validation import lv_bool, lv_images_used from .lvcode import LvContext, LvglComponent, lvgl_static @@ -84,6 +85,7 @@ DEPENDENCIES = ["display"] AUTO_LOAD = ["key_provider"] CODEOWNERS = ["@clydebarrow"] LOGGER = logging.getLogger(__name__) +HELLO_WORLD_FILE = "hello_world.yaml" SIMPLE_TRIGGERS = ( @@ -354,7 +356,8 @@ def display_schema(config): def add_hello_world(config): if df.CONF_WIDGETS not in config and CONF_PAGES not in config: LOGGER.info("No pages or widgets configured, creating default hello_world page") - config[df.CONF_WIDGETS] = any_widget_schema()(get_hello_world()) + hello_world_path = Path(__file__).parent / HELLO_WORLD_FILE + config[df.CONF_WIDGETS] = any_widget_schema()(load_yaml(hello_world_path)) return config diff --git a/esphome/components/lvgl/hello_world.py b/esphome/components/lvgl/hello_world.py deleted file mode 100644 index f85da9d8e4..0000000000 --- a/esphome/components/lvgl/hello_world.py +++ /dev/null @@ -1,127 +0,0 @@ -from io import StringIO - -from esphome.yaml_util import parse_yaml - -CONFIG = """ -- obj: - id: hello_world_card_ - pad_all: 12 - bg_color: white - height: 100% - width: 100% - scrollable: false - widgets: - - obj: - align: top_mid - outline_width: 0 - border_width: 0 - pad_all: 4 - scrollable: false - height: size_content - width: 100% - layout: - type: flex - flex_flow: row - flex_align_cross: center - flex_align_track: start - flex_align_main: space_between - widgets: - - button: - checkable: true - radius: 4 - text_font: montserrat_20 - on_click: - lvgl.label.update: - id: hello_world_label_ - text: "Clicked!" - widgets: - - label: - text: "Button" - - label: - id: hello_world_title_ - text: ESPHome - text_font: montserrat_20 - width: 100% - text_align: center - on_boot: - lvgl.widget.refresh: hello_world_title_ - hidden: !lambda |- - return lv_obj_get_width(lv_scr_act()) < 400; - - checkbox: - text: Checkbox - id: hello_world_checkbox_ - on_boot: - lvgl.widget.refresh: hello_world_checkbox_ - hidden: !lambda |- - return lv_obj_get_width(lv_scr_act()) < 240; - on_click: - lvgl.label.update: - id: hello_world_label_ - text: "Checked!" - - obj: - id: hello_world_container_ - align: center - y: 14 - pad_all: 0 - outline_width: 0 - border_width: 0 - width: 100% - height: size_content - scrollable: false - on_click: - lvgl.spinner.update: - id: hello_world_spinner_ - arc_color: springgreen - layout: - type: flex - flex_flow: row_wrap - flex_align_cross: center - flex_align_track: center - flex_align_main: space_evenly - widgets: - - spinner: - id: hello_world_spinner_ - indicator: - arc_color: tomato - height: 100 - width: 100 - spin_time: 2s - arc_length: 60deg - widgets: - - label: - id: hello_world_label_ - text: "Hello World!" - align: center - - obj: - id: hello_world_qrcode_ - outline_width: 0 - border_width: 0 - hidden: !lambda |- - return lv_obj_get_width(lv_scr_act()) < 300 && lv_obj_get_height(lv_scr_act()) < 400; - widgets: - - label: - text_font: montserrat_14 - text: esphome.io - align: top_mid - - qrcode: - text: "https://esphome.io" - size: 80 - align: bottom_mid - on_boot: - lvgl.widget.refresh: hello_world_qrcode_ - - - slider: - width: 80% - align: bottom_mid - on_value: - lvgl.label.update: - id: hello_world_label_ - text: - format: "%.0f%%" - args: [x] -""" - - -def get_hello_world(): - with StringIO(CONFIG) as fp: - return parse_yaml("hello_world", fp) diff --git a/esphome/components/lvgl/hello_world.yaml b/esphome/components/lvgl/hello_world.yaml new file mode 100644 index 0000000000..359e73cd52 --- /dev/null +++ b/esphome/components/lvgl/hello_world.yaml @@ -0,0 +1,118 @@ +# This file defines a placeholder LVGL "Hello World" that is shown when no +# widgets are configured. +- obj: + id: hello_world_card_ + pad_all: 12 + bg_color: white + height: 100% + width: 100% + scrollable: false + widgets: + - obj: + align: top_mid + outline_width: 0 + border_width: 0 + pad_all: 4 + scrollable: false + height: size_content + width: 100% + layout: + type: flex + flex_flow: row + flex_align_cross: center + flex_align_track: start + flex_align_main: space_between + widgets: + - button: + checkable: true + radius: 4 + text_font: montserrat_20 + on_click: + lvgl.label.update: + id: hello_world_label_ + text: "Clicked!" + widgets: + - label: + text: "Button" + - label: + id: hello_world_title_ + text: ESPHome + text_font: montserrat_20 + width: 100% + text_align: center + on_boot: + lvgl.widget.refresh: hello_world_title_ + hidden: !lambda |- + return lv_obj_get_width(lv_scr_act()) < 400; + - checkbox: + text: Checkbox + id: hello_world_checkbox_ + on_boot: + lvgl.widget.refresh: hello_world_checkbox_ + hidden: !lambda |- + return lv_obj_get_width(lv_scr_act()) < 240; + on_click: + lvgl.label.update: + id: hello_world_label_ + text: "Checked!" + - obj: + id: hello_world_container_ + align: center + y: 14 + pad_all: 0 + outline_width: 0 + border_width: 0 + width: 100% + height: size_content + scrollable: false + on_click: + lvgl.spinner.update: + id: hello_world_spinner_ + arc_color: springgreen + layout: + type: flex + flex_flow: row_wrap + flex_align_cross: center + flex_align_track: center + flex_align_main: space_evenly + widgets: + - spinner: + id: hello_world_spinner_ + indicator: + arc_color: tomato + height: 100 + width: 100 + spin_time: 2s + arc_length: 60deg + widgets: + - label: + id: hello_world_label_ + text: "Hello World!" + align: center + - obj: + id: hello_world_qrcode_ + outline_width: 0 + border_width: 0 + hidden: !lambda |- + return lv_obj_get_width(lv_scr_act()) < 300 && lv_obj_get_height(lv_scr_act()) < 400; + widgets: + - label: + text_font: montserrat_14 + text: esphome.io + align: top_mid + - qrcode: + text: "https://esphome.io" + size: 80 + align: bottom_mid + on_boot: + lvgl.widget.refresh: hello_world_qrcode_ + + - slider: + width: 80% + align: bottom_mid + on_value: + lvgl.label.update: + id: hello_world_label_ + text: + format: "%.0f%%" + args: [x] From a31fb223f3c5fecc4a78d5ba828e14e741fc0840 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 4 Dec 2025 10:00:45 -0500 Subject: [PATCH 0509/1145] [es8311] Remove MIN and MAX from mic_gain enum options (#12281) Co-authored-by: Claude --- esphome/components/es8311/audio_dac.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/esphome/components/es8311/audio_dac.py b/esphome/components/es8311/audio_dac.py index 7d80cfd5fb..5941a81935 100644 --- a/esphome/components/es8311/audio_dac.py +++ b/esphome/components/es8311/audio_dac.py @@ -22,7 +22,6 @@ ES8311_BITS_PER_SAMPLE_ENUM = { es8311_mic_gain = es8311_ns.enum("ES8311MicGain") ES8311_MIC_GAIN_ENUM = { - "MIN": es8311_mic_gain.ES8311_MIC_GAIN_MIN, "0DB": es8311_mic_gain.ES8311_MIC_GAIN_0DB, "6DB": es8311_mic_gain.ES8311_MIC_GAIN_6DB, "12DB": es8311_mic_gain.ES8311_MIC_GAIN_12DB, @@ -31,7 +30,6 @@ ES8311_MIC_GAIN_ENUM = { "30DB": es8311_mic_gain.ES8311_MIC_GAIN_30DB, "36DB": es8311_mic_gain.ES8311_MIC_GAIN_36DB, "42DB": es8311_mic_gain.ES8311_MIC_GAIN_42DB, - "MAX": es8311_mic_gain.ES8311_MIC_GAIN_MAX, } From cafa275579f99ae78a2b89a22206ecfe83e109d4 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:47:21 -0500 Subject: [PATCH 0510/1145] [esp32_hosted] Fix build and bump IDF component version to 2.7.0 (#12282) Co-authored-by: Claude --- esphome/components/esp32_hosted/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_hosted/__init__.py b/esphome/components/esp32_hosted/__init__.py index fde75517eb..9c9d1d4bb4 100644 --- a/esphome/components/esp32_hosted/__init__.py +++ b/esphome/components/esp32_hosted/__init__.py @@ -93,9 +93,9 @@ async def to_code(config): framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] os.environ["ESP_IDF_VERSION"] = f"{framework_ver.major}.{framework_ver.minor}" if framework_ver >= cv.Version(5, 5, 0): - esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.1.5") + esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.2.2") esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.3") - esp32.add_idf_component(name="espressif/esp_hosted", ref="2.6.1") + esp32.add_idf_component(name="espressif/esp_hosted", ref="2.7.0") else: esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="0.13.0") esp32.add_idf_component(name="espressif/eppp_link", ref="0.2.0") From 0da157ab98a7472475d8f5d05009c6ae18bd175f Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:14:30 -0500 Subject: [PATCH 0511/1145] [tests] Bump esp32_hosted in the test code (#12289) Co-authored-by: Claude --- esphome/idf_component.yml | 4 ++-- tests/components/esp32/test.esp32-p4-idf.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index b27b6b8ed1..9bb5967248 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -6,7 +6,7 @@ dependencies: espressif/mdns: version: 1.9.1 espressif/esp_wifi_remote: - version: 1.1.5 + version: 1.2.2 rules: - if: "target in [esp32h2, esp32p4]" espressif/eppp_link: @@ -14,7 +14,7 @@ dependencies: rules: - if: "target in [esp32h2, esp32p4]" espressif/esp_hosted: - version: 2.6.1 + version: 2.7.0 rules: - if: "target in [esp32h2, esp32p4]" zorxx/multipart-parser: diff --git a/tests/components/esp32/test.esp32-p4-idf.yaml b/tests/components/esp32/test.esp32-p4-idf.yaml index 1c243ef459..00a4ceec27 100644 --- a/tests/components/esp32/test.esp32-p4-idf.yaml +++ b/tests/components/esp32/test.esp32-p4-idf.yaml @@ -7,7 +7,7 @@ esp32: components: - espressif/mdns^1.8.2 - name: espressif/esp_hosted - ref: 2.6.6 + ref: 2.7.0 advanced: enable_idf_experimental_features: yes From 4db77488157b5b64f280cadad4cd114853a0f40f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 21:53:36 +0000 Subject: [PATCH 0512/1145] Bump ruff from 0.14.7 to 0.14.8 (#12286) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 412a678d02..49b87866f1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.7 + rev: v0.14.8 hooks: # Run the linter. - id: ruff diff --git a/requirements_test.txt b/requirements_test.txt index 9d55d23272..16ac131517 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==4.0.4 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.14.7 # also change in .pre-commit-config.yaml when updating +ruff==0.14.8 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating pre-commit From 8caaf53ef0ef4eeacf824d91f2d7031b7fba903f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 5 Dec 2025 12:53:13 +1300 Subject: [PATCH 0513/1145] [CI] Update renamed action repo (#12290) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ef6b4341c..01689d3697 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -668,7 +668,7 @@ jobs: with: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - - uses: esphome/action@43cd1109c09c544d97196f7730ee5b2e0cc6d81e # v3.0.1 fork with pinned actions/cache + - uses: esphome/pre-commit-action@43cd1109c09c544d97196f7730ee5b2e0cc6d81e # v3.0.1 fork with pinned actions/cache env: SKIP: pylint,clang-tidy-hash - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0 From 78b2ae8a352f4e604277ffd7bb4deeb8737fc925 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:00:08 +1300 Subject: [PATCH 0514/1145] [CI] Trigger generic version notifier job on release (#12292) --- .github/workflows/release.yml | 53 +++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d52595bbb3..51aa1f885e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -219,10 +219,19 @@ jobs: - init - deploy-manifest steps: + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + with: + app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} + private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} + owner: esphome + repositories: home-assistant-addon + - name: Trigger Workflow uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: - github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }} + github-token: ${{ steps.generate-token.outputs.token }} script: | let description = "ESPHome"; if (context.eventName == "release") { @@ -245,10 +254,19 @@ jobs: needs: [init] environment: ${{ needs.init.outputs.deploy_env }} steps: + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + with: + app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} + private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} + owner: esphome + repositories: esphome-schema + - name: Trigger Workflow uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: - github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }} + github-token: ${{ steps.generate-token.outputs.token }} script: | github.rest.actions.createWorkflowDispatch({ owner: "esphome", @@ -259,3 +277,34 @@ jobs: version: "${{ needs.init.outputs.tag }}", } }) + + version-notifier: + if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false' + runs-on: ubuntu-latest + needs: + - init + - deploy-manifest + steps: + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + with: + app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} + private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} + owner: esphome + repositories: version-notifier + + - name: Trigger Workflow + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ steps.generate-token.outputs.token }} + script: | + github.rest.actions.createWorkflowDispatch({ + owner: "esphome", + repo: "version-notifier", + workflow_id: "notify.yml", + ref: "main", + inputs: { + version: "${{ needs.init.outputs.tag }}", + } + }) From 80e881655fbffc6140e9bf13bf1ba93a8fb2439e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 5 Dec 2025 01:14:22 +0000 Subject: [PATCH 0515/1145] [scheduler] Fix use-after-free when cancelling timeouts from non-main-loop threads (#12288) --- esphome/core/scheduler.cpp | 33 ++++++++++++++------------------- esphome/core/scheduler.h | 8 +++++--- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 352587bf10..5e313f770f 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -315,7 +315,7 @@ void Scheduler::full_cleanup_removed_items_() { valid_items.push_back(std::move(item)); } else { // Recycle removed items - this->recycle_item_(std::move(item)); + this->recycle_item_main_loop_(std::move(item)); } } @@ -400,7 +400,7 @@ void HOT Scheduler::call(uint32_t now) { // Don't run on failed components if (item->component != nullptr && item->component->is_failed()) { LockGuard guard{this->lock_}; - this->recycle_item_(this->pop_raw_locked_()); + this->recycle_item_main_loop_(this->pop_raw_locked_()); continue; } @@ -413,7 +413,7 @@ void HOT Scheduler::call(uint32_t now) { { LockGuard guard{this->lock_}; if (is_item_removed_(item.get())) { - this->recycle_item_(this->pop_raw_locked_()); + this->recycle_item_main_loop_(this->pop_raw_locked_()); this->to_remove_--; continue; } @@ -422,7 +422,7 @@ void HOT Scheduler::call(uint32_t now) { // Single-threaded or multi-threaded with atomics: can check without lock if (is_item_removed_(item.get())) { LockGuard guard{this->lock_}; - this->recycle_item_(this->pop_raw_locked_()); + this->recycle_item_main_loop_(this->pop_raw_locked_()); this->to_remove_--; continue; } @@ -449,7 +449,7 @@ void HOT Scheduler::call(uint32_t now) { if (executed_item->remove) { // We were removed/cancelled in the function call, recycle and continue this->to_remove_--; - this->recycle_item_(std::move(executed_item)); + this->recycle_item_main_loop_(std::move(executed_item)); continue; } @@ -460,7 +460,7 @@ void HOT Scheduler::call(uint32_t now) { this->to_add_.push_back(std::move(executed_item)); } else { // Timeout completed - recycle it - this->recycle_item_(std::move(executed_item)); + this->recycle_item_main_loop_(std::move(executed_item)); } has_added_items |= !this->to_add_.empty(); @@ -475,7 +475,7 @@ void HOT Scheduler::process_to_add() { for (auto &it : this->to_add_) { if (is_item_removed_(it.get())) { // Recycle cancelled items - this->recycle_item_(std::move(it)); + this->recycle_item_main_loop_(std::move(it)); continue; } @@ -509,7 +509,7 @@ size_t HOT Scheduler::cleanup_() { if (!item->remove) break; this->to_remove_--; - this->recycle_item_(this->pop_raw_locked_()); + this->recycle_item_main_loop_(this->pop_raw_locked_()); } return this->items_.size(); } @@ -562,20 +562,15 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c #endif /* not ESPHOME_THREAD_SINGLE */ // Cancel items in the main heap - // Special case: if the last item in the heap matches, we can remove it immediately - // (removing the last element doesn't break heap structure) + // We only mark items for removal here - never recycle directly. + // The main loop may be executing an item's callback right now, and recycling + // would destroy the callback while it's running (use-after-free). + // Only the main loop in call() should recycle items after execution completes. if (!this->items_.empty()) { - auto &last_item = this->items_.back(); - if (this->matches_item_locked_(last_item, component, name_cstr, type, match_retry)) { - this->recycle_item_(std::move(this->items_.back())); - this->items_.pop_back(); - total_cancelled++; - } - // For other items in heap, we can only mark for removal (can't remove from middle of heap) size_t heap_cancelled = this->mark_matching_items_removed_locked_(this->items_, component, name_cstr, type, match_retry); total_cancelled += heap_cancelled; - this->to_remove_ += heap_cancelled; // Track removals for heap items + this->to_remove_ += heap_cancelled; } // Cancel items in to_add_ @@ -749,7 +744,7 @@ bool HOT Scheduler::SchedulerItem::cmp(const std::unique_ptr &a, : (a->next_execution_high_ > b->next_execution_high_); } -void Scheduler::recycle_item_(std::unique_ptr item) { +void Scheduler::recycle_item_main_loop_(std::unique_ptr item) { if (!item) return; diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index 08e003c9fb..dcf418c14f 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -272,8 +272,10 @@ class Scheduler { return is_item_removed_(item) || (item->component != nullptr && item->component->is_failed()); } - // Helper to recycle a SchedulerItem - void recycle_item_(std::unique_ptr item); + // Helper to recycle a SchedulerItem back to the pool. + // IMPORTANT: Only call from main loop context! Recycling clears the callback, + // so calling from another thread while the callback is executing causes use-after-free. + void recycle_item_main_loop_(std::unique_ptr item); // Helper to perform full cleanup when too many items are cancelled void full_cleanup_removed_items_(); @@ -329,7 +331,7 @@ class Scheduler { now = this->execute_item_(item.get(), now); } // Recycle the defer item after execution - this->recycle_item_(std::move(item)); + this->recycle_item_main_loop_(std::move(item)); } // If we've consumed all items up to the snapshot point, clean up the dead space From 637cb3f04a9fc0d1efe3bbd1c229c23e36f79ed2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 5 Dec 2025 01:14:35 +0000 Subject: [PATCH 0516/1145] [api] Use loop-based reboot timeout check to avoid scheduler heap churn (#12291) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/api/api_server.cpp | 38 ++++++++++++++------------- esphome/components/api/api_server.h | 2 +- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 4168761c74..565714a4e5 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -52,11 +52,6 @@ void APIServer::setup() { #endif #endif - // Schedule reboot if no clients connect within timeout - if (this->reboot_timeout_ != 0) { - this->schedule_reboot_timeout_(); - } - this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections if (this->socket_ == nullptr) { ESP_LOGW(TAG, "Could not create socket"); @@ -110,16 +105,13 @@ void APIServer::setup() { camera::Camera::instance()->add_listener(this); } #endif -} -void APIServer::schedule_reboot_timeout_() { - this->status_set_warning(); - this->set_timeout("api_reboot", this->reboot_timeout_, []() { - if (!global_api_server->is_connected()) { - ESP_LOGE(TAG, "No clients; rebooting"); - App.reboot(); - } - }); + // Initialize last_connected_ for reboot timeout tracking + this->last_connected_ = App.get_loop_component_start_time(); + // Set warning status if reboot timeout is enabled + if (this->reboot_timeout_ != 0) { + this->status_set_warning(); + } } void APIServer::loop() { @@ -147,15 +139,24 @@ void APIServer::loop() { this->clients_.emplace_back(conn); conn->start(); - // Clear warning status and cancel reboot when first client connects + // First client connected - clear warning and update timestamp if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) { this->status_clear_warning(); - this->cancel_timeout("api_reboot"); + this->last_connected_ = App.get_loop_component_start_time(); } } } if (this->clients_.empty()) { + // Check reboot timeout - done in loop to avoid scheduler heap churn + // (cancelled scheduler items sit in heap memory until their scheduled time) + if (this->reboot_timeout_ != 0) { + const uint32_t now = App.get_loop_component_start_time(); + if (now - this->last_connected_ > this->reboot_timeout_) { + ESP_LOGE(TAG, "No clients; rebooting"); + App.reboot(); + } + } return; } @@ -194,9 +195,10 @@ void APIServer::loop() { } this->clients_.pop_back(); - // Schedule reboot when last client disconnects + // Last client disconnected - set warning and start tracking for reboot timeout if (this->clients_.empty() && this->reboot_timeout_ != 0) { - this->schedule_reboot_timeout_(); + this->status_set_warning(); + this->last_connected_ = App.get_loop_component_start_time(); } // Don't increment client_index since we need to process the swapped element } diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 3089bb1d35..eb495afde7 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -202,7 +202,6 @@ class APIServer : public Component, #endif protected: - void schedule_reboot_timeout_(); #ifdef USE_API_NOISE bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg, const psk_t &active_psk, bool make_active); @@ -218,6 +217,7 @@ class APIServer : public Component, // 4-byte aligned types uint32_t reboot_timeout_{300000}; + uint32_t last_connected_{0}; // Vectors and strings (12 bytes each on 32-bit) std::vector> clients_; From 320ba30d509e134638f9760e61f6e307bd03f61a Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 4 Dec 2025 10:49:57 +1100 Subject: [PATCH 0517/1145] [esp32] Add build flag to suppress noexecstack message (#12272) --- esphome/components/esp32/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index d372af3e6a..d5d5195e94 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -860,6 +860,7 @@ async def to_code(config): ) cg.set_cpp_standard("gnu++20") cg.add_build_flag("-DUSE_ESP32") + cg.add_build_flag("-Wl,-z,noexecstack") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) variant = config[CONF_VARIANT] cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{variant}") From f0673f63045c0875610c53dd40403dc45bffc199 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 4 Dec 2025 02:25:13 -0500 Subject: [PATCH 0518/1145] [ld2420] Add missing USE_SELECT ifdefs (#12275) Co-authored-by: Claude --- esphome/components/ld2420/ld2420.cpp | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index f544acc112..f6182e497e 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -205,8 +205,10 @@ void LD2420Component::dump_config() { LOG_BUTTON(" ", "Factory Reset:", this->factory_reset_button_); LOG_BUTTON(" ", "Restart Module:", this->restart_module_button_); #endif +#ifdef USE_SELECT ESP_LOGCONFIG(TAG, "Select:"); LOG_SELECT(" ", "Operating Mode", this->operating_selector_); +#endif if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) { ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_); } @@ -238,12 +240,20 @@ void LD2420Component::setup() { memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) { this->set_operating_mode(OP_SIMPLE_MODE_STRING); - this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); +#ifdef USE_SELECT + if (this->operating_selector_ != nullptr) { + this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); + } +#endif this->set_mode_(CMD_SYSTEM_MODE_SIMPLE); ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_); } else { this->set_mode_(CMD_SYSTEM_MODE_ENERGY); - this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING); +#ifdef USE_SELECT + if (this->operating_selector_ != nullptr) { + this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING); + } +#endif } #ifdef USE_NUMBER this->init_gate_config_numbers(); @@ -383,8 +393,12 @@ void LD2420Component::set_operating_mode(const char *state) { // If unsupported firmware ignore mode select if (ld2420::get_firmware_int(firmware_ver_) >= CALIBRATE_VERSION_MIN) { this->current_operating_mode = find_uint8(OP_MODE_BY_STR, state); - // Entering Auto Calibrate we need to clear the privoiuos data collection - this->operating_selector_->publish_state(state); + // Entering Auto Calibrate we need to clear the previous data collection +#ifdef USE_SELECT + if (this->operating_selector_ != nullptr) { + this->operating_selector_->publish_state(state); + } +#endif if (current_operating_mode == OP_CALIBRATE_MODE) { this->set_calibration_(true); for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) { @@ -404,7 +418,11 @@ void LD2420Component::set_operating_mode(const char *state) { } } else { this->current_operating_mode = OP_SIMPLE_MODE; - this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); +#ifdef USE_SELECT + if (this->operating_selector_ != nullptr) { + this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); + } +#endif } } From b18e3d943ab2b23e47a597c6d14e8f34d444fde1 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 4 Dec 2025 19:20:55 +1100 Subject: [PATCH 0519/1145] [config] Provide path for `has_at_most_one_of` messages (#12277) --- esphome/config_validation.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index a3fd271a86..ee926b1b6d 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -740,9 +740,10 @@ def has_at_most_one_key(*keys): if not isinstance(obj, dict): raise Invalid("expected dictionary") - number = sum(k in keys for k in obj) - if number > 1: - raise Invalid(f"Cannot specify more than one of {', '.join(keys)}.") + used = set(obj) & set(keys) + if len(used) > 1: + msg = "Cannot specify more than one of '" + "', '".join(used) + "'." + raise MultipleInvalid([Invalid(msg, path=[k]) for k in used]) return obj return validate From 1b53fcf634255705ffefa9d79336740a0f8c45eb Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 4 Dec 2025 10:00:45 -0500 Subject: [PATCH 0520/1145] [es8311] Remove MIN and MAX from mic_gain enum options (#12281) Co-authored-by: Claude --- esphome/components/es8311/audio_dac.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/esphome/components/es8311/audio_dac.py b/esphome/components/es8311/audio_dac.py index 7d80cfd5fb..5941a81935 100644 --- a/esphome/components/es8311/audio_dac.py +++ b/esphome/components/es8311/audio_dac.py @@ -22,7 +22,6 @@ ES8311_BITS_PER_SAMPLE_ENUM = { es8311_mic_gain = es8311_ns.enum("ES8311MicGain") ES8311_MIC_GAIN_ENUM = { - "MIN": es8311_mic_gain.ES8311_MIC_GAIN_MIN, "0DB": es8311_mic_gain.ES8311_MIC_GAIN_0DB, "6DB": es8311_mic_gain.ES8311_MIC_GAIN_6DB, "12DB": es8311_mic_gain.ES8311_MIC_GAIN_12DB, @@ -31,7 +30,6 @@ ES8311_MIC_GAIN_ENUM = { "30DB": es8311_mic_gain.ES8311_MIC_GAIN_30DB, "36DB": es8311_mic_gain.ES8311_MIC_GAIN_36DB, "42DB": es8311_mic_gain.ES8311_MIC_GAIN_42DB, - "MAX": es8311_mic_gain.ES8311_MIC_GAIN_MAX, } From 44148c0c6b93534ecd66342215f19e71533c77f7 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:47:21 -0500 Subject: [PATCH 0521/1145] [esp32_hosted] Fix build and bump IDF component version to 2.7.0 (#12282) Co-authored-by: Claude --- esphome/components/esp32_hosted/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_hosted/__init__.py b/esphome/components/esp32_hosted/__init__.py index fde75517eb..9c9d1d4bb4 100644 --- a/esphome/components/esp32_hosted/__init__.py +++ b/esphome/components/esp32_hosted/__init__.py @@ -93,9 +93,9 @@ async def to_code(config): framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] os.environ["ESP_IDF_VERSION"] = f"{framework_ver.major}.{framework_ver.minor}" if framework_ver >= cv.Version(5, 5, 0): - esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.1.5") + esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.2.2") esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.3") - esp32.add_idf_component(name="espressif/esp_hosted", ref="2.6.1") + esp32.add_idf_component(name="espressif/esp_hosted", ref="2.7.0") else: esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="0.13.0") esp32.add_idf_component(name="espressif/eppp_link", ref="0.2.0") From ef342390644d7dfdfb8928bb951635719172a13b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:00:08 +1300 Subject: [PATCH 0522/1145] [CI] Trigger generic version notifier job on release (#12292) --- .github/workflows/release.yml | 53 +++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75d88abf29..96d119607c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -219,10 +219,19 @@ jobs: - init - deploy-manifest steps: + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + with: + app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} + private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} + owner: esphome + repositories: home-assistant-addon + - name: Trigger Workflow uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: - github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }} + github-token: ${{ steps.generate-token.outputs.token }} script: | let description = "ESPHome"; if (context.eventName == "release") { @@ -245,10 +254,19 @@ jobs: needs: [init] environment: ${{ needs.init.outputs.deploy_env }} steps: + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + with: + app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} + private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} + owner: esphome + repositories: esphome-schema + - name: Trigger Workflow uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: - github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }} + github-token: ${{ steps.generate-token.outputs.token }} script: | github.rest.actions.createWorkflowDispatch({ owner: "esphome", @@ -259,3 +277,34 @@ jobs: version: "${{ needs.init.outputs.tag }}", } }) + + version-notifier: + if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false' + runs-on: ubuntu-latest + needs: + - init + - deploy-manifest + steps: + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + with: + app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} + private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} + owner: esphome + repositories: version-notifier + + - name: Trigger Workflow + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ steps.generate-token.outputs.token }} + script: | + github.rest.actions.createWorkflowDispatch({ + owner: "esphome", + repo: "version-notifier", + workflow_id: "notify.yml", + ref: "main", + inputs: { + version: "${{ needs.init.outputs.tag }}", + } + }) From 7077488dc72131e7bd5c4946054ff2ec3b90c669 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 5 Dec 2025 01:14:22 +0000 Subject: [PATCH 0523/1145] [scheduler] Fix use-after-free when cancelling timeouts from non-main-loop threads (#12288) --- esphome/core/scheduler.cpp | 33 ++++++++++++++------------------- esphome/core/scheduler.h | 8 +++++--- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 352587bf10..5e313f770f 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -315,7 +315,7 @@ void Scheduler::full_cleanup_removed_items_() { valid_items.push_back(std::move(item)); } else { // Recycle removed items - this->recycle_item_(std::move(item)); + this->recycle_item_main_loop_(std::move(item)); } } @@ -400,7 +400,7 @@ void HOT Scheduler::call(uint32_t now) { // Don't run on failed components if (item->component != nullptr && item->component->is_failed()) { LockGuard guard{this->lock_}; - this->recycle_item_(this->pop_raw_locked_()); + this->recycle_item_main_loop_(this->pop_raw_locked_()); continue; } @@ -413,7 +413,7 @@ void HOT Scheduler::call(uint32_t now) { { LockGuard guard{this->lock_}; if (is_item_removed_(item.get())) { - this->recycle_item_(this->pop_raw_locked_()); + this->recycle_item_main_loop_(this->pop_raw_locked_()); this->to_remove_--; continue; } @@ -422,7 +422,7 @@ void HOT Scheduler::call(uint32_t now) { // Single-threaded or multi-threaded with atomics: can check without lock if (is_item_removed_(item.get())) { LockGuard guard{this->lock_}; - this->recycle_item_(this->pop_raw_locked_()); + this->recycle_item_main_loop_(this->pop_raw_locked_()); this->to_remove_--; continue; } @@ -449,7 +449,7 @@ void HOT Scheduler::call(uint32_t now) { if (executed_item->remove) { // We were removed/cancelled in the function call, recycle and continue this->to_remove_--; - this->recycle_item_(std::move(executed_item)); + this->recycle_item_main_loop_(std::move(executed_item)); continue; } @@ -460,7 +460,7 @@ void HOT Scheduler::call(uint32_t now) { this->to_add_.push_back(std::move(executed_item)); } else { // Timeout completed - recycle it - this->recycle_item_(std::move(executed_item)); + this->recycle_item_main_loop_(std::move(executed_item)); } has_added_items |= !this->to_add_.empty(); @@ -475,7 +475,7 @@ void HOT Scheduler::process_to_add() { for (auto &it : this->to_add_) { if (is_item_removed_(it.get())) { // Recycle cancelled items - this->recycle_item_(std::move(it)); + this->recycle_item_main_loop_(std::move(it)); continue; } @@ -509,7 +509,7 @@ size_t HOT Scheduler::cleanup_() { if (!item->remove) break; this->to_remove_--; - this->recycle_item_(this->pop_raw_locked_()); + this->recycle_item_main_loop_(this->pop_raw_locked_()); } return this->items_.size(); } @@ -562,20 +562,15 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c #endif /* not ESPHOME_THREAD_SINGLE */ // Cancel items in the main heap - // Special case: if the last item in the heap matches, we can remove it immediately - // (removing the last element doesn't break heap structure) + // We only mark items for removal here - never recycle directly. + // The main loop may be executing an item's callback right now, and recycling + // would destroy the callback while it's running (use-after-free). + // Only the main loop in call() should recycle items after execution completes. if (!this->items_.empty()) { - auto &last_item = this->items_.back(); - if (this->matches_item_locked_(last_item, component, name_cstr, type, match_retry)) { - this->recycle_item_(std::move(this->items_.back())); - this->items_.pop_back(); - total_cancelled++; - } - // For other items in heap, we can only mark for removal (can't remove from middle of heap) size_t heap_cancelled = this->mark_matching_items_removed_locked_(this->items_, component, name_cstr, type, match_retry); total_cancelled += heap_cancelled; - this->to_remove_ += heap_cancelled; // Track removals for heap items + this->to_remove_ += heap_cancelled; } // Cancel items in to_add_ @@ -749,7 +744,7 @@ bool HOT Scheduler::SchedulerItem::cmp(const std::unique_ptr &a, : (a->next_execution_high_ > b->next_execution_high_); } -void Scheduler::recycle_item_(std::unique_ptr item) { +void Scheduler::recycle_item_main_loop_(std::unique_ptr item) { if (!item) return; diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index 08e003c9fb..dcf418c14f 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -272,8 +272,10 @@ class Scheduler { return is_item_removed_(item) || (item->component != nullptr && item->component->is_failed()); } - // Helper to recycle a SchedulerItem - void recycle_item_(std::unique_ptr item); + // Helper to recycle a SchedulerItem back to the pool. + // IMPORTANT: Only call from main loop context! Recycling clears the callback, + // so calling from another thread while the callback is executing causes use-after-free. + void recycle_item_main_loop_(std::unique_ptr item); // Helper to perform full cleanup when too many items are cancelled void full_cleanup_removed_items_(); @@ -329,7 +331,7 @@ class Scheduler { now = this->execute_item_(item.get(), now); } // Recycle the defer item after execution - this->recycle_item_(std::move(item)); + this->recycle_item_main_loop_(std::move(item)); } // If we've consumed all items up to the snapshot point, clean up the dead space From 8f20abebf688feb47e63019ace17f8cee391df5b Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 4 Dec 2025 21:52:48 -0500 Subject: [PATCH 0524/1145] Bump version to 2025.11.4 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 901b0c92c0..dbb744767b 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.11.3 +PROJECT_NUMBER = 2025.11.4 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 10f0b3af4d..472e0a7bee 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.11.3" +__version__ = "2025.11.4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 22481d9c0e07c8b58b29e4a53a845ee063add179 Mon Sep 17 00:00:00 2001 From: Citizen07 <34106434+Citizen07@users.noreply.github.com> Date: Fri, 5 Dec 2025 05:50:23 +0200 Subject: [PATCH 0525/1145] [remote_receiver] buffer usage fix and idle optimizations (#9999) Co-authored-by: J. Nick Koston Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- .../remote_receiver/remote_receiver.cpp | 181 ++++++++++-------- .../remote_receiver/remote_receiver.h | 32 ++-- .../remote_receiver/remote_receiver_esp32.cpp | 6 +- 3 files changed, 120 insertions(+), 99 deletions(-) diff --git a/esphome/components/remote_receiver/remote_receiver.cpp b/esphome/components/remote_receiver/remote_receiver.cpp index 53bfb0890f..a7ac74199d 100644 --- a/esphome/components/remote_receiver/remote_receiver.cpp +++ b/esphome/components/remote_receiver/remote_receiver.cpp @@ -5,63 +5,79 @@ #if defined(USE_LIBRETINY) || defined(USE_ESP8266) || defined(USE_RP2040) -namespace esphome { -namespace remote_receiver { +namespace esphome::remote_receiver { static const char *const TAG = "remote_receiver"; +static void IRAM_ATTR HOT write_value(RemoteReceiverComponentStore *arg, uint32_t delta, bool level) { + // convert level to -1 or +1 and write the delta to the buffer + int32_t multiplier = ((int32_t) level << 1) - 1; + uint32_t buffer_write = arg->buffer_write; + arg->buffer[buffer_write++] = (int32_t) delta * multiplier; + if (buffer_write >= arg->buffer_size) { + buffer_write = 0; + } + + // detect overflow and reset the write pointer + if (buffer_write == arg->buffer_read) { + buffer_write = arg->buffer_start; + arg->overflow = true; + } + + // detect idle and start a new sequence unless there is only idle in + // which case reset the write pointer instead + if (delta >= arg->idle_us) { + if (arg->buffer_write == arg->buffer_start) { + buffer_write = arg->buffer_start; + } else { + arg->buffer_start = buffer_write; + } + } + arg->buffer_write = buffer_write; +} + +static void IRAM_ATTR HOT commit_value(RemoteReceiverComponentStore *arg, uint32_t micros, bool level) { + // commit value if the level is different from the last commit level + if (level != arg->commit_level) { + write_value(arg, micros - arg->commit_micros, level); + arg->commit_micros = micros; + arg->commit_level = level; + } +} + void IRAM_ATTR HOT RemoteReceiverComponentStore::gpio_intr(RemoteReceiverComponentStore *arg) { - const uint32_t now = micros(); - // If the lhs is 1 (rising edge) we should write to an uneven index and vice versa - const uint32_t next = (arg->buffer_write_at + 1) % arg->buffer_size; - const bool level = arg->pin.digital_read(); - if (level != next % 2) - return; + // invert the level so it matches the level of the signal before the edge + const bool curr_level = !arg->pin.digital_read(); + const uint32_t curr_micros = micros(); + const bool prev_level = arg->prev_level; + const uint32_t prev_micros = arg->prev_micros; - // If next is buffer_read, we have hit an overflow - if (next == arg->buffer_read_at) - return; - - const uint32_t last_change = arg->buffer[arg->buffer_write_at]; - const uint32_t time_since_change = now - last_change; - if (time_since_change <= arg->filter_us) - return; - - arg->buffer[arg->buffer_write_at = next] = now; // NOLINT(clang-diagnostic-deprecated-volatile) + // commit the previous value if the pulse is not filtered and the level is different + if (curr_micros - prev_micros >= arg->filter_us && prev_level != curr_level) { + commit_value(arg, prev_micros, prev_level); + } + arg->prev_micros = curr_micros; + arg->prev_level = curr_level; } void RemoteReceiverComponent::setup() { this->pin_->setup(); - auto &s = this->store_; - s.filter_us = this->filter_us_; - s.pin = this->pin_->to_isr(); - s.buffer_size = this->buffer_size_; - - this->high_freq_.start(); - if (s.buffer_size % 2 != 0) { - // Make sure divisible by two. This way, we know that every 0bxxx0 index is a space and every 0bxxx1 index is a mark - s.buffer_size++; - } - - s.buffer = new uint32_t[s.buffer_size]; - void *buf = (void *) s.buffer; - memset(buf, 0, s.buffer_size * sizeof(uint32_t)); - - // First index is a space. - if (this->pin_->digital_read()) { - s.buffer_write_at = s.buffer_read_at = 1; - } else { - s.buffer_write_at = s.buffer_read_at = 0; - } + this->store_.idle_us = this->idle_us_; + this->store_.filter_us = this->filter_us_; + this->store_.pin = this->pin_->to_isr(); + this->store_.buffer = new int32_t[this->buffer_size_]; + this->store_.buffer_size = this->buffer_size_; + this->store_.prev_micros = micros(); + this->store_.commit_micros = this->store_.prev_micros; + this->store_.prev_level = this->pin_->digital_read(); + this->store_.commit_level = this->store_.prev_level; this->pin_->attach_interrupt(RemoteReceiverComponentStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); + this->high_freq_.start(); } + void RemoteReceiverComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Receiver:"); LOG_PIN(" Pin: ", this->pin_); - if (this->pin_->digital_read()) { - ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to " - "invert the signal using 'inverted: True' in the pin schema!"); - } ESP_LOGCONFIG(TAG, " Buffer Size: %u\n" " Tolerance: %u%s\n" @@ -73,53 +89,54 @@ void RemoteReceiverComponent::dump_config() { } void RemoteReceiverComponent::loop() { + // check for overflow auto &s = this->store_; - - // copy write at to local variables, as it's volatile - const uint32_t write_at = s.buffer_write_at; - const uint32_t dist = (s.buffer_size + write_at - s.buffer_read_at) % s.buffer_size; - // signals must at least one rising and one leading edge - if (dist <= 1) - return; - const uint32_t now = micros(); - if (now - s.buffer[write_at] < this->idle_us_) { - // The last change was fewer than the configured idle time ago. - return; + if (s.overflow) { + ESP_LOGW(TAG, "Buffer overflow"); + s.overflow = false; } - ESP_LOGVV(TAG, "read_at=%u write_at=%u dist=%u now=%u end=%u", s.buffer_read_at, write_at, dist, now, - s.buffer[write_at]); - - // Skip first value, it's from the previous idle level - s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size; - uint32_t prev = s.buffer_read_at; - s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size; - const uint32_t reserve_size = 1 + (s.buffer_size + write_at - s.buffer_read_at) % s.buffer_size; - this->temp_.clear(); - this->temp_.reserve(reserve_size); - int32_t multiplier = s.buffer_read_at % 2 == 0 ? 1 : -1; - - for (uint32_t i = 0; prev != write_at; i++) { - int32_t delta = s.buffer[s.buffer_read_at] - s.buffer[prev]; - if (uint32_t(delta) >= this->idle_us_) { - // already found a space longer than idle. There must have been two pulses - break; + // if no data is available check for uncommitted data stuck in the buffer and commit + // the previous value if needed + uint32_t last_index = s.buffer_start; + if (last_index == s.buffer_read) { + InterruptLock lock; + if (s.buffer_read == s.buffer_start && s.buffer_write != s.buffer_start && + micros() - s.prev_micros >= this->idle_us_) { + commit_value(&s, s.prev_micros, s.prev_level); + write_value(&s, s.idle_us, !s.commit_level); + last_index = s.buffer_start; } - - ESP_LOGVV(TAG, " i=%u buffer[%u]=%u - buffer[%u]=%u -> %d", i, s.buffer_read_at, s.buffer[s.buffer_read_at], prev, - s.buffer[prev], multiplier * delta); - this->temp_.push_back(multiplier * delta); - prev = s.buffer_read_at; - s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size; - multiplier *= -1; } - s.buffer_read_at = (s.buffer_size + s.buffer_read_at - 1) % s.buffer_size; - this->temp_.push_back(this->idle_us_ * multiplier); + if (last_index == s.buffer_read) { + return; + } + // find the size of the packet and reserve the memory + uint32_t temp_read = s.buffer_read; + uint32_t reserve_size = 0; + while (temp_read != last_index && (uint32_t) std::abs(s.buffer[temp_read]) < this->idle_us_) { + reserve_size++; + temp_read++; + if (temp_read >= s.buffer_size) { + temp_read = 0; + } + } + this->temp_.clear(); + this->temp_.reserve(reserve_size + 1); + + // read the buffer + for (uint32_t i = 0; i < reserve_size + 1; i++) { + this->temp_.push_back((int32_t) s.buffer[s.buffer_read++]); + if (s.buffer_read >= s.buffer_size) { + s.buffer_read = 0; + } + } + + // call the listeners and dumpers this->call_listeners_dumpers_(); } -} // namespace remote_receiver -} // namespace esphome +} // namespace esphome::remote_receiver #endif diff --git a/esphome/components/remote_receiver/remote_receiver.h b/esphome/components/remote_receiver/remote_receiver.h index 3d2f7f0ef9..fabf0a481a 100644 --- a/esphome/components/remote_receiver/remote_receiver.h +++ b/esphome/components/remote_receiver/remote_receiver.h @@ -9,25 +9,31 @@ #include #endif -namespace esphome { -namespace remote_receiver { +namespace esphome::remote_receiver { #if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040) struct RemoteReceiverComponentStore { static void gpio_intr(RemoteReceiverComponentStore *arg); - /// Stores the time (in micros) that the leading/falling edge happened at - /// * An even index means a falling edge appeared at the time stored at the index - /// * An uneven index means a rising edge appeared at the time stored at the index - volatile uint32_t *buffer{nullptr}; + /// Stores pulse durations in microseconds as signed integers + /// * Positive values indicate high pulses (marks) + /// * Negative values indicate low pulses (spaces) + volatile int32_t *buffer{nullptr}; /// The position last written to - volatile uint32_t buffer_write_at; + volatile uint32_t buffer_write{0}; + /// The start position of the last sequence + volatile uint32_t buffer_start{0}; /// The position last read from - uint32_t buffer_read_at{0}; - bool overflow{false}; + uint32_t buffer_read{0}; + volatile uint32_t commit_micros{0}; + volatile uint32_t prev_micros{0}; uint32_t buffer_size{1000}; uint32_t filter_us{10}; + uint32_t idle_us{10000}; ISRInternalGPIOPin pin; + volatile bool commit_level{false}; + volatile bool prev_level{false}; + volatile bool overflow{false}; }; #elif defined(USE_ESP32) struct RemoteReceiverComponentStore { @@ -84,15 +90,15 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, std::string error_string_{""}; #endif -#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_ESP32) || defined(USE_RP2040) - RemoteReceiverComponentStore store_; +#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040) HighFrequencyLoopRequester high_freq_; #endif + RemoteReceiverComponentStore store_; + uint32_t buffer_size_{}; uint32_t filter_us_{10}; uint32_t idle_us_{10000}; }; -} // namespace remote_receiver -} // namespace esphome +} // namespace esphome::remote_receiver diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index 49358eef3f..bd0bc8e57b 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -4,8 +4,7 @@ #ifdef USE_ESP32 #include -namespace esphome { -namespace remote_receiver { +namespace esphome::remote_receiver { static const char *const TAG = "remote_receiver.esp32"; #ifdef USE_ESP32_VARIANT_ESP32H2 @@ -248,7 +247,6 @@ void RemoteReceiverComponent::decode_rmt_(rmt_symbol_word_t *item, size_t item_c } } -} // namespace remote_receiver -} // namespace esphome +} // namespace esphome::remote_receiver #endif From 19fa76873070356fda4b277bfee7b3db152e9b49 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 6 Dec 2025 02:48:04 +1300 Subject: [PATCH 0526/1145] Update readme logo (#12294) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0439b1bc06..b8ce8d091d 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ - - ESPHome Logo + + ESPHome Logo From 7fd79fdded14b48e9a6cc2b3bba2191a749972f1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 6 Dec 2025 03:53:08 +1300 Subject: [PATCH 0527/1145] [esp32] Change imports to use esp32 only, not .const (#12243) --- esphome/components/adc/__init__.py | 5 +++-- esphome/components/bme680_bsec/__init__.py | 2 +- esphome/components/deep_sleep/__init__.py | 4 ++-- esphome/components/esp32_can/canbus.py | 4 ++-- esphome/components/esp32_dac/output.py | 3 +-- esphome/components/esp32_hosted/update/__init__.py | 4 ++-- esphome/components/esp32_rmt/__init__.py | 2 +- esphome/components/esp32_rmt_led_strip/light.py | 2 +- esphome/components/esp32_touch/__init__.py | 11 ++++++----- esphome/components/ethernet/__init__.py | 8 +++----- esphome/components/i2s_audio/__init__.py | 5 +++-- esphome/components/i2s_audio/media_player/__init__.py | 2 +- esphome/components/i2s_audio/microphone/__init__.py | 4 ++-- esphome/components/i2s_audio/speaker/__init__.py | 2 +- esphome/components/improv_serial/__init__.py | 3 +-- esphome/components/logger/__init__.py | 5 +++-- esphome/components/mipi_dsi/display.py | 4 ++-- esphome/components/mipi_rgb/display.py | 4 ++-- esphome/components/neopixelbus/_methods.py | 4 ++-- esphome/components/neopixelbus/light.py | 3 +-- esphome/components/psram/__init__.py | 6 ++---- esphome/components/remote_receiver/__init__.py | 4 ++-- esphome/components/remote_transmitter/__init__.py | 2 +- esphome/components/rpi_dpi_rgb/display.py | 4 ++-- esphome/components/spi/__init__.py | 4 ++-- esphome/components/st7701s/display.py | 4 ++-- esphome/components/tinyusb/__init__.py | 5 +++-- esphome/config_validation.py | 3 +-- tests/component_tests/esp32/test_esp32.py | 3 +-- tests/component_tests/psram/test_psram.py | 2 +- tests/unit_tests/test_config_validation.py | 4 ++-- tests/unit_tests/test_main.py | 2 +- 32 files changed, 60 insertions(+), 64 deletions(-) diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index 8f751c496e..62c1a5fffa 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -1,15 +1,16 @@ from esphome import pins import esphome.codegen as cg -from esphome.components.esp32 import VARIANT_ESP32P4, get_esp32_variant -from esphome.components.esp32.const import ( +from esphome.components.esp32 import ( VARIANT_ESP32, VARIANT_ESP32C2, VARIANT_ESP32C3, VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32H2, + VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, + get_esp32_variant, ) import esphome.config_validation as cv from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266 diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index 8a8d74b5f3..06e641d34d 100644 --- a/esphome/components/bme680_bsec/__init__.py +++ b/esphome/components/bme680_bsec/__init__.py @@ -69,7 +69,7 @@ CONFIG_SCHEMA = cv.All( cv.only_on_esp8266, cv.All( cv.only_on_esp32, - esp32.only_on_variant(supported=[esp32.const.VARIANT_ESP32]), + esp32.only_on_variant(supported=[esp32.VARIANT_ESP32]), ), ), ) diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 18ba167952..75affd7843 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -1,8 +1,7 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import esp32, time -from esphome.components.esp32 import get_esp32_variant -from esphome.components.esp32.const import ( +from esphome.components.esp32 import ( VARIANT_ESP32, VARIANT_ESP32C2, VARIANT_ESP32C3, @@ -10,6 +9,7 @@ from esphome.components.esp32.const import ( VARIANT_ESP32H2, VARIANT_ESP32S2, VARIANT_ESP32S3, + get_esp32_variant, ) from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv diff --git a/esphome/components/esp32_can/canbus.py b/esphome/components/esp32_can/canbus.py index 5cee27506a..8708c6fb36 100644 --- a/esphome/components/esp32_can/canbus.py +++ b/esphome/components/esp32_can/canbus.py @@ -4,8 +4,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import canbus from esphome.components.canbus import CONF_BIT_RATE, CanbusComponent, CanSpeed -from esphome.components.esp32 import get_esp32_variant -from esphome.components.esp32.const import ( +from esphome.components.esp32 import ( VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32C6, @@ -13,6 +12,7 @@ from esphome.components.esp32.const import ( VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, + get_esp32_variant, ) import esphome.config_validation as cv from esphome.const import ( diff --git a/esphome/components/esp32_dac/output.py b/esphome/components/esp32_dac/output.py index cf4f12c46d..daace596d3 100644 --- a/esphome/components/esp32_dac/output.py +++ b/esphome/components/esp32_dac/output.py @@ -1,8 +1,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import output -from esphome.components.esp32 import get_esp32_variant -from esphome.components.esp32.const import VARIANT_ESP32, VARIANT_ESP32S2 +from esphome.components.esp32 import VARIANT_ESP32, VARIANT_ESP32S2, get_esp32_variant import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_NUMBER, CONF_PIN diff --git a/esphome/components/esp32_hosted/update/__init__.py b/esphome/components/esp32_hosted/update/__init__.py index 040f989a64..fff0d3623a 100644 --- a/esphome/components/esp32_hosted/update/__init__.py +++ b/esphome/components/esp32_hosted/update/__init__.py @@ -40,8 +40,8 @@ CONFIG_SCHEMA = cv.All( ), esp32.only_on_variant( supported=[ - esp32.const.VARIANT_ESP32H2, - esp32.const.VARIANT_ESP32P4, + esp32.VARIANT_ESP32H2, + esp32.VARIANT_ESP32P4, ] ), ) diff --git a/esphome/components/esp32_rmt/__init__.py b/esphome/components/esp32_rmt/__init__.py index 1e72185e3e..272c7c81ba 100644 --- a/esphome/components/esp32_rmt/__init__.py +++ b/esphome/components/esp32_rmt/__init__.py @@ -9,7 +9,7 @@ def validate_clock_resolution(): cv.only_on_esp32(value) value = cv.int_(value) variant = esp32.get_esp32_variant() - if variant == esp32.const.VARIANT_ESP32H2 and value > 32000000: + if variant == esp32.VARIANT_ESP32H2 and value > 32000000: raise cv.Invalid( f"ESP32 variant {variant} has a max clock_resolution of 32000000." ) diff --git a/esphome/components/esp32_rmt_led_strip/light.py b/esphome/components/esp32_rmt_led_strip/light.py index 2ec0750ae6..f020d02e86 100644 --- a/esphome/components/esp32_rmt_led_strip/light.py +++ b/esphome/components/esp32_rmt_led_strip/light.py @@ -91,7 +91,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_IS_WRGB, default=False): cv.boolean, cv.Optional(CONF_USE_DMA): cv.All( esp32.only_on_variant( - supported=[esp32.const.VARIANT_ESP32P4, esp32.const.VARIANT_ESP32S3] + supported=[esp32.VARIANT_ESP32P4, esp32.VARIANT_ESP32S3] ), cv.boolean, ), diff --git a/esphome/components/esp32_touch/__init__.py b/esphome/components/esp32_touch/__init__.py index b6cb19ebb1..c54ed8b9ea 100644 --- a/esphome/components/esp32_touch/__init__.py +++ b/esphome/components/esp32_touch/__init__.py @@ -1,10 +1,11 @@ import esphome.codegen as cg from esphome.components import esp32 -from esphome.components.esp32 import get_esp32_variant, gpio -from esphome.components.esp32.const import ( +from esphome.components.esp32 import ( VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32S3, + get_esp32_variant, + gpio, ) import esphome.config_validation as cv from esphome.const import ( @@ -255,9 +256,9 @@ CONFIG_SCHEMA = cv.All( cv.has_none_or_all_keys(CONF_WATERPROOF_GUARD_RING, CONF_WATERPROOF_SHIELD_DRIVER), esp32.only_on_variant( supported=[ - esp32.const.VARIANT_ESP32, - esp32.const.VARIANT_ESP32S2, - esp32.const.VARIANT_ESP32S3, + esp32.VARIANT_ESP32, + esp32.VARIANT_ESP32S2, + esp32.VARIANT_ESP32S3, ] ), validate_variant_vars, diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index b4d67635c1..39af1ff4b9 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -3,16 +3,14 @@ import logging from esphome import pins import esphome.codegen as cg from esphome.components.esp32 import ( - add_idf_component, - add_idf_sdkconfig_option, - get_esp32_variant, -) -from esphome.components.esp32.const import ( VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, + add_idf_component, + add_idf_sdkconfig_option, + get_esp32_variant, ) from esphome.components.network import ip_address_literal from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index 0c7c8f6642..802db06f48 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -1,7 +1,6 @@ from esphome import pins import esphome.codegen as cg -from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant -from esphome.components.esp32.const import ( +from esphome.components.esp32 import ( VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32C5, @@ -10,6 +9,8 @@ from esphome.components.esp32.const import ( VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, + add_idf_sdkconfig_option, + get_esp32_variant, ) import esphome.config_validation as cv from esphome.const import CONF_BITS_PER_SAMPLE, CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE diff --git a/esphome/components/i2s_audio/media_player/__init__.py b/esphome/components/i2s_audio/media_player/__init__.py index 316ce7c48b..35c42e1b06 100644 --- a/esphome/components/i2s_audio/media_player/__init__.py +++ b/esphome/components/i2s_audio/media_player/__init__.py @@ -40,7 +40,7 @@ INTERNAL_DAC_OPTIONS = { EXTERNAL_DAC_OPTIONS = [CONF_MONO, CONF_STEREO] -NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2] +NO_INTERNAL_DAC_VARIANTS = [esp32.VARIANT_ESP32S2] I2C_COMM_FMT_OPTIONS = ["lsb", "msb"] diff --git a/esphome/components/i2s_audio/microphone/__init__.py b/esphome/components/i2s_audio/microphone/__init__.py index f919199c60..dd23673db5 100644 --- a/esphome/components/i2s_audio/microphone/__init__.py +++ b/esphome/components/i2s_audio/microphone/__init__.py @@ -37,8 +37,8 @@ I2SAudioMicrophone = i2s_audio_ns.class_( "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component ) -INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32] -PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3] +INTERNAL_ADC_VARIANTS = [esp32.VARIANT_ESP32] +PDM_VARIANTS = [esp32.VARIANT_ESP32, esp32.VARIANT_ESP32S3] def _validate_esp32_variant(config): diff --git a/esphome/components/i2s_audio/speaker/__init__.py b/esphome/components/i2s_audio/speaker/__init__.py index 98322d3a18..2e009a1de1 100644 --- a/esphome/components/i2s_audio/speaker/__init__.py +++ b/esphome/components/i2s_audio/speaker/__init__.py @@ -62,7 +62,7 @@ I2C_COMM_FMT_OPTIONS = { "pcm_long": i2s_comm_format_t.I2S_COMM_FORMAT_PCM_LONG, } -INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32] +INTERNAL_DAC_VARIANTS = [esp32.VARIANT_ESP32] def _set_num_channels_from_config(config): diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index fb2b541707..7f88b17e11 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -1,7 +1,6 @@ import esphome.codegen as cg from esphome.components import improv_base -from esphome.components.esp32 import get_esp32_variant -from esphome.components.esp32.const import VARIANT_ESP32S3 +from esphome.components.esp32 import VARIANT_ESP32S3, get_esp32_variant from esphome.components.logger import USB_CDC import esphome.config_validation as cv from esphome.const import CONF_BAUD_RATE, CONF_HARDWARE_UART, CONF_ID, CONF_LOGGER diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index c81ade8fc3..7369e99c85 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -3,8 +3,7 @@ import re from esphome import automation from esphome.automation import LambdaAction, StatelessLambdaAction import esphome.codegen as cg -from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant -from esphome.components.esp32.const import ( +from esphome.components.esp32 import ( VARIANT_ESP32, VARIANT_ESP32C2, VARIANT_ESP32C3, @@ -14,6 +13,8 @@ from esphome.components.esp32.const import ( VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, + add_idf_sdkconfig_option, + get_esp32_variant, ) from esphome.components.libretiny import get_libretiny_component, get_libretiny_family from esphome.components.libretiny.const import ( diff --git a/esphome/components/mipi_dsi/display.py b/esphome/components/mipi_dsi/display.py index 4fc837be67..90c4cc082e 100644 --- a/esphome/components/mipi_dsi/display.py +++ b/esphome/components/mipi_dsi/display.py @@ -12,7 +12,7 @@ from esphome.components.const import ( CONF_DRAW_ROUNDING, ) from esphome.components.display import CONF_SHOW_TEST_CARD -from esphome.components.esp32 import const, only_on_variant +from esphome.components.esp32 import VARIANT_ESP32P4, only_on_variant from esphome.components.mipi import ( COLOR_ORDERS, CONF_COLOR_DEPTH, @@ -165,7 +165,7 @@ def model_schema(config): ) return cv.All( schema, - only_on_variant(supported=[const.VARIANT_ESP32P4]), + only_on_variant(supported=[VARIANT_ESP32P4]), cv.only_with_esp_idf, ) diff --git a/esphome/components/mipi_rgb/display.py b/esphome/components/mipi_rgb/display.py index 9d6b1fa729..2d2e022045 100644 --- a/esphome/components/mipi_rgb/display.py +++ b/esphome/components/mipi_rgb/display.py @@ -11,7 +11,7 @@ from esphome.components.const import ( CONF_DRAW_ROUNDING, ) from esphome.components.display import CONF_SHOW_TEST_CARD -from esphome.components.esp32 import const, only_on_variant +from esphome.components.esp32 import VARIANT_ESP32S3, only_on_variant from esphome.components.mipi import ( COLOR_ORDERS, CONF_DE_PIN, @@ -224,7 +224,7 @@ def _config_schema(config): schema = model_schema(config) return cv.All( schema, - only_on_variant(supported=[const.VARIANT_ESP32S3]), + only_on_variant(supported=[VARIANT_ESP32S3]), cv.only_with_esp_idf, )(config) diff --git a/esphome/components/neopixelbus/_methods.py b/esphome/components/neopixelbus/_methods.py index 5a00fa2804..9072f78035 100644 --- a/esphome/components/neopixelbus/_methods.py +++ b/esphome/components/neopixelbus/_methods.py @@ -2,12 +2,12 @@ from dataclasses import dataclass from typing import Any import esphome.codegen as cg -from esphome.components.esp32 import get_esp32_variant -from esphome.components.esp32.const import ( +from esphome.components.esp32 import ( VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3, + get_esp32_variant, ) import esphome.config_validation as cv from esphome.const import ( diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index 0c9604e932..d071059185 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -1,8 +1,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import light -from esphome.components.esp32 import get_esp32_variant -from esphome.components.esp32.const import VARIANT_ESP32C3, VARIANT_ESP32S3 +from esphome.components.esp32 import VARIANT_ESP32C3, VARIANT_ESP32S3, get_esp32_variant import esphome.config_validation as cv from esphome.const import ( CONF_CHANNEL, diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index 529097889d..fcbe9ed043 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -7,14 +7,12 @@ from esphome.components.esp32 import ( CONF_CPU_FREQUENCY, CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES, VARIANT_ESP32, - add_idf_sdkconfig_option, - get_esp32_variant, -) -from esphome.components.esp32.const import ( VARIANT_ESP32C5, VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, + add_idf_sdkconfig_option, + get_esp32_variant, ) import esphome.config_validation as cv from esphome.const import ( diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index 7f70e2c2a2..f5d89f2f0f 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -65,7 +65,7 @@ RemoteReceiverComponent = remote_receiver_ns.class_( def validate_config(config): if CORE.is_esp32: variant = esp32.get_esp32_variant() - if variant in (esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S2): + if variant in (esp32.VARIANT_ESP32, esp32.VARIANT_ESP32S2): max_idle = 65535 else: max_idle = 32767 @@ -148,7 +148,7 @@ CONFIG_SCHEMA = remote_base.validate_triggers( ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), cv.Optional(CONF_USE_DMA): cv.All( esp32.only_on_variant( - supported=[esp32.const.VARIANT_ESP32P4, esp32.const.VARIANT_ESP32S3] + supported=[esp32.VARIANT_ESP32P4, esp32.VARIANT_ESP32S3] ), cv.boolean, ), diff --git a/esphome/components/remote_transmitter/__init__.py b/esphome/components/remote_transmitter/__init__.py index ec4f62666d..f182a1ec0d 100644 --- a/esphome/components/remote_transmitter/__init__.py +++ b/esphome/components/remote_transmitter/__init__.py @@ -55,7 +55,7 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_EOT_LEVEL): cv.All(cv.only_on_esp32, cv.boolean), cv.Optional(CONF_USE_DMA): cv.All( esp32.only_on_variant( - supported=[esp32.const.VARIANT_ESP32P4, esp32.const.VARIANT_ESP32S3] + supported=[esp32.VARIANT_ESP32P4, esp32.VARIANT_ESP32S3] ), cv.boolean, ), diff --git a/esphome/components/rpi_dpi_rgb/display.py b/esphome/components/rpi_dpi_rgb/display.py index 513ed8eb58..8e9da43a74 100644 --- a/esphome/components/rpi_dpi_rgb/display.py +++ b/esphome/components/rpi_dpi_rgb/display.py @@ -1,7 +1,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import display -from esphome.components.esp32 import const, only_on_variant +from esphome.components.esp32 import VARIANT_ESP32S3, only_on_variant from esphome.components.mipi import ( CONF_DE_PIN, CONF_HSYNC_BACK_PORCH, @@ -121,7 +121,7 @@ CONFIG_SCHEMA = cv.All( } ) ), - only_on_variant(supported=[const.VARIANT_ESP32S3]), + only_on_variant(supported=[VARIANT_ESP32S3]), cv.only_with_esp_idf, ) diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 8f23735fff..1530ffb882 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -3,8 +3,7 @@ from typing import Any from esphome import pins import esphome.codegen as cg -from esphome.components.esp32 import only_on_variant -from esphome.components.esp32.const import ( +from esphome.components.esp32 import ( KEY_ESP32, VARIANT_ESP32C2, VARIANT_ESP32C3, @@ -13,6 +12,7 @@ from esphome.components.esp32.const import ( VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, + only_on_variant, ) from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv diff --git a/esphome/components/st7701s/display.py b/esphome/components/st7701s/display.py index 497740b8d2..6e4bff6431 100644 --- a/esphome/components/st7701s/display.py +++ b/esphome/components/st7701s/display.py @@ -1,7 +1,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import display, spi -from esphome.components.esp32 import const, only_on_variant +from esphome.components.esp32 import VARIANT_ESP32S3, only_on_variant from esphome.components.mipi import ( CONF_DE_PIN, CONF_HSYNC_BACK_PORCH, @@ -161,7 +161,7 @@ CONFIG_SCHEMA = cv.All( } ).extend(spi.spi_device_schema(cs_pin_required=False, default_data_rate=1e6)) ), - only_on_variant(supported=[const.VARIANT_ESP32S3]), + only_on_variant(supported=[VARIANT_ESP32S3]), cv.only_with_esp_idf, ) diff --git a/esphome/components/tinyusb/__init__.py b/esphome/components/tinyusb/__init__.py index 72afc18387..90043e969c 100644 --- a/esphome/components/tinyusb/__init__.py +++ b/esphome/components/tinyusb/__init__.py @@ -1,10 +1,11 @@ import esphome.codegen as cg from esphome.components import esp32 -from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option -from esphome.components.esp32.const import ( +from esphome.components.esp32 import ( VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, + add_idf_component, + add_idf_sdkconfig_option, ) import esphome.config_validation as cv from esphome.const import CONF_ID diff --git a/esphome/config_validation.py b/esphome/config_validation.py index ee926b1b6d..c52b791120 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1744,8 +1744,7 @@ class SplitDefault(Optional): def default(self): keys = [] if CORE.is_esp32: - from esphome.components.esp32 import get_esp32_variant - from esphome.components.esp32.const import VARIANT_ESP32 + from esphome.components.esp32 import VARIANT_ESP32, get_esp32_variant variant = get_esp32_variant().replace(VARIANT_ESP32, "").lower() framework = CORE.target_framework.replace("esp-", "") diff --git a/tests/component_tests/esp32/test_esp32.py b/tests/component_tests/esp32/test_esp32.py index 91e96f24d6..68bd3a5965 100644 --- a/tests/component_tests/esp32/test_esp32.py +++ b/tests/component_tests/esp32/test_esp32.py @@ -17,8 +17,7 @@ def test_esp32_config( ) -> None: set_core_config(PlatformFramework.ESP32_IDF) - from esphome.components.esp32 import CONFIG_SCHEMA - from esphome.components.esp32.const import VARIANT_ESP32, VARIANT_FRIENDLY + from esphome.components.esp32 import CONFIG_SCHEMA, VARIANT_ESP32, VARIANT_FRIENDLY # Example ESP32 configuration config = { diff --git a/tests/component_tests/psram/test_psram.py b/tests/component_tests/psram/test_psram.py index 86bc29cc84..0924e66adc 100644 --- a/tests/component_tests/psram/test_psram.py +++ b/tests/component_tests/psram/test_psram.py @@ -4,7 +4,7 @@ from typing import Any import pytest -from esphome.components.esp32.const import ( +from esphome.components.esp32 import ( KEY_VARIANT, VARIANT_ESP32, VARIANT_ESP32C2, diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 73b15aaadf..c9d7b7486e 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -6,7 +6,7 @@ import pytest import voluptuous as vol from esphome import config_validation -from esphome.components.esp32.const import ( +from esphome.components.esp32 import ( VARIANT_ESP32, VARIANT_ESP32C2, VARIANT_ESP32C3, @@ -221,7 +221,7 @@ def hex_int__valid(value): ], ) def test_split_default(framework, platform, variant, full, idf, arduino, simple): - from esphome.components.esp32.const import KEY_ESP32 + from esphome.components.esp32 import KEY_ESP32 from esphome.const import ( KEY_CORE, KEY_TARGET_FRAMEWORK, diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index 670d6c16fc..bd14395037 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -35,7 +35,7 @@ from esphome.__main__ import ( upload_program, upload_using_esptool, ) -from esphome.components.esp32.const import KEY_ESP32, KEY_VARIANT, VARIANT_ESP32 +from esphome.components.esp32 import KEY_ESP32, KEY_VARIANT, VARIANT_ESP32 from esphome.const import ( CONF_API, CONF_BROKER, From f4d1c9df714b0478557683153c6c142157ead80a Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:56:11 -0500 Subject: [PATCH 0528/1145] [remote_receiver] Fix Zephyr clang tidy (#12299) Co-authored-by: Claude --- esphome/components/remote_receiver/remote_receiver.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/remote_receiver/remote_receiver.h b/esphome/components/remote_receiver/remote_receiver.h index fabf0a481a..3d9199a904 100644 --- a/esphome/components/remote_receiver/remote_receiver.h +++ b/esphome/components/remote_receiver/remote_receiver.h @@ -90,12 +90,14 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, std::string error_string_{""}; #endif +#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040) || defined(USE_ESP32) + RemoteReceiverComponentStore store_; +#endif + #if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040) HighFrequencyLoopRequester high_freq_; #endif - RemoteReceiverComponentStore store_; - uint32_t buffer_size_{}; uint32_t filter_us_{10}; uint32_t idle_us_{10000}; From 27fcff2092293442459741483f723c5d4b0b3671 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 5 Dec 2025 10:27:41 -0600 Subject: [PATCH 0529/1145] [api] Simplify MessageCreator to trivially copyable type (#12295) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/api/api_connection.cpp | 6 ++--- esphome/components/api/api_connection.h | 27 ++++------------------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 9ad45dc6b7..31f90d9474 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1662,13 +1662,13 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c for (auto &item : items) { if (item.entity == entity && item.message_type == message_type) { // Replace with new creator - item.creator = std::move(creator); + item.creator = creator; return; } } // No existing item found, add new one - items.emplace_back(entity, std::move(creator), message_type, estimated_size); + items.emplace_back(entity, creator, message_type, estimated_size); } void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, @@ -1677,7 +1677,7 @@ void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCre // This avoids expensive vector::insert which shifts all elements // Note: We only ever have one high-priority message at a time (ping OR disconnect) // If we're disconnecting, pings are blocked, so this simple swap is sufficient - items.emplace_back(entity, std::move(creator), message_type, estimated_size); + items.emplace_back(entity, creator, message_type, estimated_size); if (items.size() > 1) { // Swap the new high-priority item to the front std::swap(items.front(), items.back()); diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 05af0ccde7..6bf4f45a5c 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -505,28 +505,9 @@ class APIConnection final : public APIServerConnection { class MessageCreator { public: - // Constructor for function pointer MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; } - - // Constructor for const char * (Event types - no allocation needed) explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; } - // Delete copy operations - MessageCreator should only be moved - MessageCreator(const MessageCreator &other) = delete; - MessageCreator &operator=(const MessageCreator &other) = delete; - - // Move constructor - MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.function_ptr = nullptr; } - - // Move assignment - MessageCreator &operator=(MessageCreator &&other) noexcept { - if (this != &other) { - data_ = other.data_; - other.data_.function_ptr = nullptr; - } - return *this; - } - // Call operator - uses message_type to determine union type uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, uint8_t message_type) const; @@ -535,7 +516,7 @@ class APIConnection final : public APIServerConnection { union Data { MessageCreatorPtr function_ptr; const char *const_char_ptr; - } data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before + } data_; // 4 bytes on 32-bit, 8 bytes on 64-bit }; // Generic batching mechanism for both state updates and entity info @@ -548,7 +529,7 @@ class APIConnection final : public APIServerConnection { // Constructor for creating BatchItem BatchItem(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) - : entity(entity), creator(std::move(creator)), message_type(message_type), estimated_size(estimated_size) {} + : entity(entity), creator(creator), message_type(message_type), estimated_size(estimated_size) {} }; std::vector items; @@ -716,12 +697,12 @@ class APIConnection final : public APIServerConnection { } // Fall back to scheduled batching - return this->schedule_message_(entity, std::move(creator), message_type, estimated_size); + return this->schedule_message_(entity, creator, message_type, estimated_size); } // Helper function to schedule a deferred message with known message type bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) { - this->deferred_batch_.add_item(entity, std::move(creator), message_type, estimated_size); + this->deferred_batch_.add_item(entity, creator, message_type, estimated_size); return this->schedule_batch_(); } From 1a308583b339965b23003607ed100d3815fadac2 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 5 Dec 2025 12:16:19 -0500 Subject: [PATCH 0530/1145] [esp32] Add support for ESP32-C61 variant (#12285) Co-authored-by: Claude Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- esphome/components/adc/__init__.py | 10 ++++ esphome/components/adc/adc_sensor_esp32.cpp | 21 +++++---- esphome/components/deep_sleep/__init__.py | 4 ++ .../deep_sleep/deep_sleep_component.h | 2 +- .../deep_sleep/deep_sleep_esp32.cpp | 10 ++-- esphome/components/esp32/__init__.py | 2 + esphome/components/esp32/boards.py | 2 + esphome/components/esp32/const.py | 3 ++ esphome/components/esp32/gpio.py | 6 +++ esphome/components/esp32/gpio_esp32_c61.py | 46 +++++++++++++++++++ esphome/components/esp32_can/canbus.py | 3 ++ esphome/components/esp32_can/esp32_can.cpp | 5 +- esphome/components/i2s_audio/__init__.py | 2 + .../improv_serial/improv_serial_component.cpp | 7 +-- .../improv_serial/improv_serial_component.h | 4 +- .../internal_temperature.cpp | 16 +++---- esphome/components/logger/__init__.py | 2 + esphome/components/spi/__init__.py | 2 + esphome/core/defines.h | 4 +- 19 files changed, 119 insertions(+), 32 deletions(-) create mode 100644 esphome/components/esp32/gpio_esp32_c61.py diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index 62c1a5fffa..96c8334a6d 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -6,6 +6,7 @@ from esphome.components.esp32 import ( VARIANT_ESP32C3, VARIANT_ESP32C5, VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32H2, VARIANT_ESP32P4, VARIANT_ESP32S2, @@ -100,6 +101,13 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { 5: adc_channel_t.ADC_CHANNEL_5, 6: adc_channel_t.ADC_CHANNEL_6, }, + # https://docs.espressif.com/projects/esp-idf/en/latest/esp32c61/api-reference/peripherals/gpio.html + VARIANT_ESP32C61: { + 1: adc_channel_t.ADC_CHANNEL_0, + 3: adc_channel_t.ADC_CHANNEL_1, + 4: adc_channel_t.ADC_CHANNEL_2, + 5: adc_channel_t.ADC_CHANNEL_3, + }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h VARIANT_ESP32H2: { 1: adc_channel_t.ADC_CHANNEL_0, @@ -175,6 +183,8 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = { VARIANT_ESP32C5: {}, # no ADC2 # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h VARIANT_ESP32C6: {}, # no ADC2 + # ESP32-C61 has no ADC2 + VARIANT_ESP32C61: {}, # no ADC2 # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h VARIANT_ESP32H2: {}, # no ADC2 # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32p4/include/soc/adc_channel.h diff --git a/esphome/components/adc/adc_sensor_esp32.cpp b/esphome/components/adc/adc_sensor_esp32.cpp index e25b275cd6..120cb1c926 100644 --- a/esphome/components/adc/adc_sensor_esp32.cpp +++ b/esphome/components/adc/adc_sensor_esp32.cpp @@ -42,10 +42,11 @@ void ADCSensor::setup() { adc_oneshot_unit_init_cfg_t init_config = {}; // Zero initialize init_config.unit_id = this->adc_unit_; init_config.ulp_mode = ADC_ULP_MODE_DISABLE; -#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32H2 +#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ + USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 init_config.clk_src = ADC_DIGI_CLK_SRC_DEFAULT; #endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || - // USE_ESP32_VARIANT_ESP32H2 + // USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 esp_err_t err = adc_oneshot_new_unit(&init_config, &ADCSensor::shared_adc_handles[this->adc_unit_]); if (err != ESP_OK) { ESP_LOGE(TAG, "Error initializing %s: %d", LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)), err); @@ -74,7 +75,7 @@ void ADCSensor::setup() { adc_cali_handle_t handle = nullptr; #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 + USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 // RISC-V variants and S3 use curve fitting calibration adc_cali_curve_fitting_config_t cali_config = {}; // Zero initialize first #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) @@ -111,7 +112,7 @@ void ADCSensor::setup() { ESP_LOGW(TAG, "Line fitting calibration failed with error %d, will use uncalibrated readings", err); this->setup_flags_.calibration_complete = false; } -#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32H2 || ESP32P4 || ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3 } this->setup_flags_.init_complete = true; @@ -186,11 +187,11 @@ float ADCSensor::sample_fixed_attenuation_() { ESP_LOGW(TAG, "ADC calibration conversion failed with error %d, disabling calibration", err); if (this->calibration_handle_ != nullptr) { #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 + USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); #else // Other ESP32 variants use line fitting calibration adc_cali_delete_scheme_line_fitting(this->calibration_handle_); -#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32H2 || ESP32P4 || ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3 this->calibration_handle_ = nullptr; } } @@ -219,7 +220,7 @@ float ADCSensor::sample_autorange_() { if (this->calibration_handle_ != nullptr) { // Delete old calibration handle #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 + USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); #else adc_cali_delete_scheme_line_fitting(this->calibration_handle_); @@ -231,7 +232,7 @@ float ADCSensor::sample_autorange_() { adc_cali_handle_t handle = nullptr; #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 + USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_curve_fitting_config_t cali_config = {}; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) cali_config.chan = this->channel_; @@ -266,7 +267,7 @@ float ADCSensor::sample_autorange_() { ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err); if (handle != nullptr) { #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 + USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_delete_scheme_curve_fitting(handle); #else adc_cali_delete_scheme_line_fitting(handle); @@ -288,7 +289,7 @@ float ADCSensor::sample_autorange_() { } // Clean up calibration handle #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 + USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_delete_scheme_curve_fitting(handle); #else adc_cali_delete_scheme_line_fitting(handle); diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 75affd7843..fa3ea449e2 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -6,6 +6,7 @@ from esphome.components.esp32 import ( VARIANT_ESP32C2, VARIANT_ESP32C3, VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32H2, VARIANT_ESP32S2, VARIANT_ESP32S3, @@ -55,6 +56,7 @@ WAKEUP_PINS = { VARIANT_ESP32C2: [0, 1, 2, 3, 4, 5], VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5], VARIANT_ESP32C6: [0, 1, 2, 3, 4, 5, 6, 7], + VARIANT_ESP32C61: [0, 1, 2, 3, 4, 5, 6], VARIANT_ESP32H2: [7, 8, 9, 10, 11, 12, 13, 14], VARIANT_ESP32S2: [ 0, @@ -123,6 +125,7 @@ def _validate_ex1_wakeup_mode(value): esp32.only_on_variant( supported=[ VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32H2, VARIANT_ESP32S2, VARIANT_ESP32S3, @@ -219,6 +222,7 @@ CONFIG_SCHEMA = cv.All( VARIANT_ESP32C2, VARIANT_ESP32C3, VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32H2, ], msg_prefix="Wakeup from touch", diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 80381e767c..bca3aa5e4d 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -81,7 +81,7 @@ class DeepSleepComponent : public Component { #endif #if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) && \ - !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2) + !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32H2) void set_touch_wakeup(bool touch_wakeup); #endif diff --git a/esphome/components/deep_sleep/deep_sleep_esp32.cpp b/esphome/components/deep_sleep/deep_sleep_esp32.cpp index b93d9ce601..833be8e76c 100644 --- a/esphome/components/deep_sleep/deep_sleep_esp32.cpp +++ b/esphome/components/deep_sleep/deep_sleep_esp32.cpp @@ -18,6 +18,7 @@ namespace deep_sleep { // | ESP32-C3 | | | | ✓ | // | ESP32-C5 | | (✓) | | (✓) | // | ESP32-C6 | | ✓ | | ✓ | +// | ESP32-C61 | | ✓ | | ✓ | // | ESP32-H2 | | ✓ | | | // // Notes: @@ -55,7 +56,7 @@ void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wa #endif #if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) && \ - !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2) + !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32H2) void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; } #endif @@ -121,8 +122,9 @@ void DeepSleepComponent::deep_sleep_() { } #endif - // GPIO wakeup - C2, C3, C6 only -#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) + // GPIO wakeup - C2, C3, C6, C61 only +#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ + defined(USE_ESP32_VARIANT_ESP32C61) if (this->wakeup_pin_ != nullptr) { const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin()); if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLUP) { @@ -155,7 +157,7 @@ void DeepSleepComponent::deep_sleep_() { // Touch wakeup - ESP32, S2, S3 only #if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) && \ - !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2) + !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32H2) if (this->touch_wakeup_.has_value() && *(this->touch_wakeup_)) { esp_sleep_enable_touchpad_wakeup(); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 1d05e16ebd..94280308bd 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -59,6 +59,7 @@ from .const import ( # noqa VARIANT_ESP32C3, VARIANT_ESP32C5, VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32H2, VARIANT_ESP32P4, VARIANT_ESP32S2, @@ -126,6 +127,7 @@ CPU_FREQUENCIES = { VARIANT_ESP32C3: get_cpu_frequencies(80, 160), VARIANT_ESP32C5: get_cpu_frequencies(80, 160, 240), VARIANT_ESP32C6: get_cpu_frequencies(80, 120, 160), + VARIANT_ESP32C61: get_cpu_frequencies(80, 120, 160), VARIANT_ESP32H2: get_cpu_frequencies(16, 32, 48, 64, 96), VARIANT_ESP32P4: get_cpu_frequencies(40, 360, 400), VARIANT_ESP32S2: get_cpu_frequencies(80, 160, 240), diff --git a/esphome/components/esp32/boards.py b/esphome/components/esp32/boards.py index cbb314650a..7107874a5b 100644 --- a/esphome/components/esp32/boards.py +++ b/esphome/components/esp32/boards.py @@ -4,6 +4,7 @@ from .const import ( VARIANT_ESP32C3, VARIANT_ESP32C5, VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32H2, VARIANT_ESP32P4, VARIANT_ESP32S2, @@ -17,6 +18,7 @@ STANDARD_BOARDS = { VARIANT_ESP32C3: "esp32-c3-devkitm-1", VARIANT_ESP32C5: "esp32-c5-devkitc-1", VARIANT_ESP32C6: "esp32-c6-devkitm-1", + VARIANT_ESP32C61: "esp32-c61-devkitc1-n8r2", VARIANT_ESP32H2: "esp32-h2-devkitm-1", VARIANT_ESP32P4: "esp32-p4-evboard", VARIANT_ESP32S2: "esp32-s2-kaluga-1", diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py index 4358a4b712..dfb736f615 100644 --- a/esphome/components/esp32/const.py +++ b/esphome/components/esp32/const.py @@ -17,6 +17,7 @@ VARIANT_ESP32C2 = "ESP32C2" VARIANT_ESP32C3 = "ESP32C3" VARIANT_ESP32C5 = "ESP32C5" VARIANT_ESP32C6 = "ESP32C6" +VARIANT_ESP32C61 = "ESP32C61" VARIANT_ESP32H2 = "ESP32H2" VARIANT_ESP32P4 = "ESP32P4" VARIANT_ESP32S2 = "ESP32S2" @@ -27,6 +28,7 @@ VARIANTS = [ VARIANT_ESP32C3, VARIANT_ESP32C5, VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32H2, VARIANT_ESP32P4, VARIANT_ESP32S2, @@ -39,6 +41,7 @@ VARIANT_FRIENDLY = { VARIANT_ESP32C3: "ESP32-C3", VARIANT_ESP32C5: "ESP32-C5", VARIANT_ESP32C6: "ESP32-C6", + VARIANT_ESP32C61: "ESP32-C61", VARIANT_ESP32H2: "ESP32-H2", VARIANT_ESP32P4: "ESP32-P4", VARIANT_ESP32S2: "ESP32-S2", diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py index 954891ea8d..c0803f40a8 100644 --- a/esphome/components/esp32/gpio.py +++ b/esphome/components/esp32/gpio.py @@ -29,6 +29,7 @@ from .const import ( VARIANT_ESP32C3, VARIANT_ESP32C5, VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32H2, VARIANT_ESP32P4, VARIANT_ESP32S2, @@ -40,6 +41,7 @@ from .gpio_esp32_c2 import esp32_c2_validate_gpio_pin, esp32_c2_validate_support from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports from .gpio_esp32_c5 import esp32_c5_validate_gpio_pin, esp32_c5_validate_supports from .gpio_esp32_c6 import esp32_c6_validate_gpio_pin, esp32_c6_validate_supports +from .gpio_esp32_c61 import esp32_c61_validate_gpio_pin, esp32_c61_validate_supports from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports from .gpio_esp32_p4 import esp32_p4_validate_gpio_pin, esp32_p4_validate_supports from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports @@ -110,6 +112,10 @@ _esp32_validations = { pin_validation=esp32_c6_validate_gpio_pin, usage_validation=esp32_c6_validate_supports, ), + VARIANT_ESP32C61: ESP32ValidationFunctions( + pin_validation=esp32_c61_validate_gpio_pin, + usage_validation=esp32_c61_validate_supports, + ), VARIANT_ESP32H2: ESP32ValidationFunctions( pin_validation=esp32_h2_validate_gpio_pin, usage_validation=esp32_h2_validate_supports, diff --git a/esphome/components/esp32/gpio_esp32_c61.py b/esphome/components/esp32/gpio_esp32_c61.py new file mode 100644 index 0000000000..77be42db3e --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_c61.py @@ -0,0 +1,46 @@ +import logging + +import esphome.config_validation as cv +from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER +from esphome.pins import check_strapping_pin + +# GPIO14-17, GPIO19-21 are used for SPI flash/PSRAM +_ESP32C61_SPI_PSRAM_PINS = { + 14: "SPICS0", + 15: "SPICLK", + 16: "SPID", + 17: "SPIQ", + 19: "SPIWP", + 20: "SPIHD", + 21: "VDD_SPI", +} + +_ESP32C61_STRAPPING_PINS = {8, 9} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_c61_validate_gpio_pin(value): + if value < 0 or value > 29: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-29)") + if value in _ESP32C61_SPI_PSRAM_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32-C61s and is already used by the SPI/PSRAM interface (function: {_ESP32C61_SPI_PSRAM_PINS[value]})" + ) + + return value + + +def esp32_c61_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + + if num < 0 or num > 29: + raise cv.Invalid(f"Invalid pin number: {num} (must be 0-29)") + if is_input: + # All ESP32-C61 pins support input mode + pass + + check_strapping_pin(value, _ESP32C61_STRAPPING_PINS, _LOGGER) + return value diff --git a/esphome/components/esp32_can/canbus.py b/esphome/components/esp32_can/canbus.py index 8708c6fb36..000ef303fe 100644 --- a/esphome/components/esp32_can/canbus.py +++ b/esphome/components/esp32_can/canbus.py @@ -8,6 +8,7 @@ from esphome.components.esp32 import ( VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32H2, VARIANT_ESP32P4, VARIANT_ESP32S2, @@ -59,6 +60,7 @@ CAN_SPEEDS_ESP32_S2 = { CAN_SPEEDS_ESP32_S3 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_C3 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_C6 = {**CAN_SPEEDS_ESP32_S2} +CAN_SPEEDS_ESP32_C61 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_H2 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_P4 = {**CAN_SPEEDS_ESP32_S2} @@ -66,6 +68,7 @@ CAN_SPEEDS = { VARIANT_ESP32: CAN_SPEEDS_ESP32, VARIANT_ESP32C3: CAN_SPEEDS_ESP32_C3, VARIANT_ESP32C6: CAN_SPEEDS_ESP32_C6, + VARIANT_ESP32C61: CAN_SPEEDS_ESP32_C61, VARIANT_ESP32H2: CAN_SPEEDS_ESP32_H2, VARIANT_ESP32P4: CAN_SPEEDS_ESP32_P4, VARIANT_ESP32S2: CAN_SPEEDS_ESP32_S2, diff --git a/esphome/components/esp32_can/esp32_can.cpp b/esphome/components/esp32_can/esp32_can.cpp index c10ad01450..d50964187d 100644 --- a/esphome/components/esp32_can/esp32_can.cpp +++ b/esphome/components/esp32_can/esp32_can.cpp @@ -16,8 +16,9 @@ static const char *const TAG = "esp32_can"; static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config) { switch (bitrate) { -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || \ - defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || \ + defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) case canbus::CAN_1KBPS: *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1KBITS(); return true; diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index 802db06f48..61c5ca4ec1 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -5,6 +5,7 @@ from esphome.components.esp32 import ( VARIANT_ESP32C3, VARIANT_ESP32C5, VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32H2, VARIANT_ESP32P4, VARIANT_ESP32S2, @@ -72,6 +73,7 @@ I2S_PORTS = { VARIANT_ESP32C3: 1, VARIANT_ESP32C5: 1, VARIANT_ESP32C6: 1, + VARIANT_ESP32C61: 1, VARIANT_ESP32H2: 1, VARIANT_ESP32P4: 3, VARIANT_ESP32S2: 1, diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index 70260eeab3..281e95d12b 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -70,9 +70,10 @@ optional ImprovSerialComponent::read_byte_() { case logger::UART_SELECTION_UART0: case logger::UART_SELECTION_UART1: #if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ - !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) + !defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) case logger::UART_SELECTION_UART2: -#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 +#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32C6 && !USE_ESP32_VARIANT_ESP32C61 && + // !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 if (this->uart_num_ >= 0) { size_t available; uart_get_buffered_data_len(this->uart_num_, &available); @@ -137,7 +138,7 @@ void ImprovSerialComponent::write_data_(const uint8_t *data, const size_t size) case logger::UART_SELECTION_UART0: case logger::UART_SELECTION_UART1: #if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ - !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) + !defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) case logger::UART_SELECTION_UART2: #endif uart_write_bytes(this->uart_num_, this->tx_header_, header_tx_len); diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h index abe50b87f2..dd8f5e4719 100644 --- a/esphome/components/improv_serial/improv_serial_component.h +++ b/esphome/components/improv_serial/improv_serial_component.h @@ -11,8 +11,8 @@ #ifdef USE_ESP32 #include -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || \ - defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || \ + defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32S3) #include #include #endif diff --git a/esphome/components/internal_temperature/internal_temperature.cpp b/esphome/components/internal_temperature/internal_temperature.cpp index 6365392ce9..2ef8cf2649 100644 --- a/esphome/components/internal_temperature/internal_temperature.cpp +++ b/esphome/components/internal_temperature/internal_temperature.cpp @@ -8,8 +8,8 @@ extern "C" { uint8_t temprature_sens_read(); } #elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \ - defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ - defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \ + defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "driver/temperature_sensor.h" #endif // USE_ESP32_VARIANT #endif // USE_ESP32 @@ -28,8 +28,8 @@ namespace internal_temperature { static const char *const TAG = "internal_temperature"; #ifdef USE_ESP32 #if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \ - defined(USE_ESP32_VARIANT_ESP32S3) + defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ + defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) static temperature_sensor_handle_t tsensNew = NULL; #endif // USE_ESP32_VARIANT #endif // USE_ESP32 @@ -44,8 +44,8 @@ void InternalTemperatureSensor::update() { temperature = (raw - 32) / 1.8f; success = (raw != 128); #elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \ - defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ - defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \ + defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) esp_err_t result = temperature_sensor_get_celsius(tsensNew, &temperature); success = (result == ESP_OK); if (!success) { @@ -82,8 +82,8 @@ void InternalTemperatureSensor::update() { void InternalTemperatureSensor::setup() { #ifdef USE_ESP32 #if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \ - defined(USE_ESP32_VARIANT_ESP32S3) + defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ + defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80); esp_err_t result = temperature_sensor_install(&tsens_config, &tsensNew); diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 7369e99c85..fb0ce92cc9 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -9,6 +9,7 @@ from esphome.components.esp32 import ( VARIANT_ESP32C3, VARIANT_ESP32C5, VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32H2, VARIANT_ESP32P4, VARIANT_ESP32S2, @@ -105,6 +106,7 @@ UART_SELECTION_ESP32 = { VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32C5: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], + VARIANT_ESP32C61: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32P4: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32S2: [UART0, UART1, USB_CDC], diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 1530ffb882..0b531b9ed6 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -8,6 +8,7 @@ from esphome.components.esp32 import ( VARIANT_ESP32C2, VARIANT_ESP32C3, VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32H2, VARIANT_ESP32P4, VARIANT_ESP32S2, @@ -129,6 +130,7 @@ def get_hw_interface_list(): VARIANT_ESP32C2, VARIANT_ESP32C3, VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32H2, ]: return [["spi", "spi2"]] diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 5d3bca55a2..358334d7b3 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -234,8 +234,8 @@ #if defined(USE_ESP32_VARIANT_ESP32S2) #define USE_LOGGER_USB_CDC #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \ - defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ - defined(USE_ESP32_VARIANT_ESP32S3) + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \ + defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S3) #define USE_LOGGER_USB_CDC #define USE_LOGGER_USB_SERIAL_JTAG #endif From 7f7c913a853db92d269cc7e3f63040d0b112317c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 5 Dec 2025 11:47:54 -0600 Subject: [PATCH 0531/1145] [light] Fix schedule_show not enabling loop for idle addressable lights (#12302) --- esphome/components/light/addressable_light.h | 2 +- esphome/components/light/light_state.cpp | 3 +-- esphome/components/light/light_state.h | 6 ++++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 2e4b984ce4..fcaf07f578 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -70,7 +70,7 @@ class AddressableLight : public LightOutput, public Component { this->state_parent_ = state; } void update_state(LightState *state) override; - void schedule_show() { this->state_parent_->next_write_ = true; } + void schedule_show() { this->state_parent_->schedule_write_(); } #ifdef USE_POWER_SUPPLY void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_.set_parent(power_supply); } diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index af619a426a..5a50bae50b 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -305,8 +305,7 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot this->remote_values = target; } this->output_->update_state(this); - this->next_write_ = true; - this->enable_loop(); + this->schedule_write_(); } void LightState::disable_loop_if_idle_() { diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index 7ea72306f9..a21c2c7693 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -277,6 +277,12 @@ class LightState : public EntityBase, public Component { /// Disable loop if neither transformer nor effect is active void disable_loop_if_idle_(); + /// Schedule a write to the light output and enable the loop to process it + void schedule_write_() { + this->next_write_ = true; + this->enable_loop(); + } + /// Store the output to allow effects to have more access. LightOutput *output_; /// The currently active transformer for this light (transition/flash). From 78bef42473a049781cdcc115d9dc7d3a916c045a Mon Sep 17 00:00:00 2001 From: c0mputerguru Date: Fri, 5 Dec 2025 10:33:00 -0800 Subject: [PATCH 0532/1145] [sps30] Add idle mode functionality (#12255) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- esphome/components/sps30/automation.h | 17 +++++--- esphome/components/sps30/sensor.py | 20 ++++++++-- esphome/components/sps30/sps30.cpp | 56 +++++++++++++++++++++++++++ esphome/components/sps30/sps30.h | 7 ++++ tests/components/sps30/common.yaml | 1 + 5 files changed, 92 insertions(+), 9 deletions(-) diff --git a/esphome/components/sps30/automation.h b/esphome/components/sps30/automation.h index 67af813687..5eafc1b6c2 100644 --- a/esphome/components/sps30/automation.h +++ b/esphome/components/sps30/automation.h @@ -1,20 +1,25 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/helpers.h" #include "sps30.h" namespace esphome { namespace sps30 { -template class StartFanAction : public Action { +template class StartFanAction : public Action, public Parented { public: - explicit StartFanAction(SPS30Component *sps30) : sps30_(sps30) {} + void play(const Ts &...x) override { this->parent_->start_fan_cleaning(); } +}; - void play(const Ts &...x) override { this->sps30_->start_fan_cleaning(); } +template class StartMeasurementAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->start_measurement(); } +}; - protected: - SPS30Component *sps30_; +template class StopMeasurementAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->stop_measurement(); } }; } // namespace sps30 diff --git a/esphome/components/sps30/sensor.py b/esphome/components/sps30/sensor.py index d4f91b4188..3c967fc01b 100644 --- a/esphome/components/sps30/sensor.py +++ b/esphome/components/sps30/sensor.py @@ -38,8 +38,11 @@ SPS30Component = sps30_ns.class_( # Actions StartFanAction = sps30_ns.class_("StartFanAction", automation.Action) +StartMeasurementAction = sps30_ns.class_("StartMeasurementAction", automation.Action) +StopMeasurementAction = sps30_ns.class_("StopMeasurementAction", automation.Action) CONF_AUTO_CLEANING_INTERVAL = "auto_cleaning_interval" +CONF_IDLE_INTERVAL = "idle_interval" CONFIG_SCHEMA = ( cv.Schema( @@ -109,6 +112,7 @@ CONFIG_SCHEMA = ( state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_AUTO_CLEANING_INTERVAL): cv.update_interval, + cv.Optional(CONF_IDLE_INTERVAL): cv.update_interval, } ) .extend(cv.polling_component_schema("60s")) @@ -164,6 +168,9 @@ async def to_code(config): if CONF_AUTO_CLEANING_INTERVAL in config: cg.add(var.set_auto_cleaning_interval(config[CONF_AUTO_CLEANING_INTERVAL])) + if CONF_IDLE_INTERVAL in config: + cg.add(var.set_idle_interval(config[CONF_IDLE_INTERVAL])) + SPS30_ACTION_SCHEMA = maybe_simple_id( { @@ -175,6 +182,13 @@ SPS30_ACTION_SCHEMA = maybe_simple_id( @automation.register_action( "sps30.start_fan_autoclean", StartFanAction, SPS30_ACTION_SCHEMA ) -async def sps30_fan_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) +@automation.register_action( + "sps30.start_measurement", StartMeasurementAction, SPS30_ACTION_SCHEMA +) +@automation.register_action( + "sps30.stop_measurement", StopMeasurementAction, SPS30_ACTION_SCHEMA +) +async def sps30_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 21a782e49a..dbb44743d2 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -20,6 +20,7 @@ static const uint16_t SPS30_CMD_START_FAN_CLEANING = 0x5607; static const uint16_t SPS30_CMD_SOFT_RESET = 0xD304; static const size_t SERIAL_NUMBER_LENGTH = 8; static const uint8_t MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR = 5; +static const uint32_t SPS30_WARM_UP_SEC = 30; void SPS30Component::setup() { this->write_command(SPS30_CMD_SOFT_RESET); @@ -63,6 +64,8 @@ void SPS30Component::setup() { this->status_clear_warning(); this->skipped_data_read_cycles_ = 0; this->start_continuous_measurement_(); + this->next_state_ms_ = millis() + SPS30_WARM_UP_SEC * 1000; + this->next_state_ = READ; this->setup_complete_ = true; }); }); @@ -101,6 +104,9 @@ void SPS30Component::dump_config() { " Serial number: %s\n" " Firmware version v%0d.%0d", this->serial_number_, this->raw_firmware_version_ >> 8, this->raw_firmware_version_ & 0xFF); + if (this->idle_interval_.has_value()) { + ESP_LOGCONFIG(TAG, " Idle interval: %us", this->idle_interval_.value() / 1000); + } LOG_SENSOR(" ", "PM1.0 Weight Concentration", this->pm_1_0_sensor_); LOG_SENSOR(" ", "PM2.5 Weight Concentration", this->pm_2_5_sensor_); LOG_SENSOR(" ", "PM4 Weight Concentration", this->pm_4_0_sensor_); @@ -132,6 +138,26 @@ void SPS30Component::update() { } return; } + + // If its not time to take an action, do nothing. + const uint32_t update_start_ms = millis(); + if (this->next_state_ != NONE && (int32_t) (this->next_state_ms_ - update_start_ms) > 0) { + ESP_LOGD(TAG, "Sensor waiting for %ums before transitioning to state %d.", (this->next_state_ms_ - update_start_ms), + this->next_state_); + return; + } + + switch (this->next_state_) { + case WAKE: + this->start_measurement(); + return; + case NONE: + return; + case READ: + // Read logic continues below + break; + } + /// Check if measurement is ready before reading the value if (!this->write_command(SPS30_CMD_GET_DATA_READY_STATUS)) { this->status_set_warning(); @@ -211,6 +237,16 @@ void SPS30Component::update() { this->status_clear_warning(); this->skipped_data_read_cycles_ = 0; + + // Stop measurements and wait if we have an idle interval. If not using idle mode, let the next state just execute + // on next update. + if (this->idle_interval_.has_value()) { + this->stop_measurement(); + this->next_state_ms_ = millis() + this->idle_interval_.value(); + this->next_state_ = WAKE; + } else { + this->next_state_ms_ = millis(); + } }); } @@ -219,6 +255,26 @@ bool SPS30Component::start_continuous_measurement_() { ESP_LOGE(TAG, "Error initiating measurements"); return false; } + ESP_LOGD(TAG, "Started measurements"); + + // Notify the state machine to wait the warm up interval before reading + this->next_state_ms_ = millis() + SPS30_WARM_UP_SEC * 1000; + this->next_state_ = READ; + return true; +} + +bool SPS30Component::start_measurement() { return start_continuous_measurement_(); } + +bool SPS30Component::stop_measurement() { + if (!write_command(SPS30_CMD_STOP_MEASUREMENTS)) { + ESP_LOGE(TAG, "Error stopping measurements"); + return false; + } else { + ESP_LOGD(TAG, "Stopped measurements"); + // Exit the state machine if measurement is stopped. + this->next_state_ms_ = 0; + this->next_state_ = NONE; + } return true; } diff --git a/esphome/components/sps30/sps30.h b/esphome/components/sps30/sps30.h index 18847e16d9..4e9b90ba7e 100644 --- a/esphome/components/sps30/sps30.h +++ b/esphome/components/sps30/sps30.h @@ -23,17 +23,23 @@ class SPS30Component : public PollingComponent, public sensirion_common::Sensiri void set_pm_size_sensor(sensor::Sensor *pm_size) { pm_size_sensor_ = pm_size; } void set_auto_cleaning_interval(uint32_t auto_cleaning_interval) { fan_interval_ = auto_cleaning_interval; } + void set_idle_interval(uint32_t idle_interval) { idle_interval_ = idle_interval; } void setup() override; void update() override; void dump_config() override; bool start_fan_cleaning(); + bool stop_measurement(); + bool start_measurement(); protected: bool setup_complete_{false}; uint16_t raw_firmware_version_; char serial_number_[17] = {0}; /// Terminating NULL character uint8_t skipped_data_read_cycles_ = 0; + uint32_t next_state_ms_ = 0; + + enum NextState : uint8_t { WAKE, READ, NONE } next_state_{NONE}; bool start_continuous_measurement_(); @@ -58,6 +64,7 @@ class SPS30Component : public PollingComponent, public sensirion_common::Sensiri sensor::Sensor *pmc_10_0_sensor_{nullptr}; sensor::Sensor *pm_size_sensor_{nullptr}; optional fan_interval_; + optional idle_interval_; }; } // namespace sps30 diff --git a/tests/components/sps30/common.yaml b/tests/components/sps30/common.yaml index d40cd16b6d..a83477b764 100644 --- a/tests/components/sps30/common.yaml +++ b/tests/components/sps30/common.yaml @@ -30,3 +30,4 @@ sensor: id: workshop_PMC_10_0 address: 0x69 update_interval: 10s + idle_interval: 5min From 7421f31160142f9bfd726ad75dcf1ba3c9c199d3 Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Fri, 5 Dec 2025 10:51:32 -0800 Subject: [PATCH 0533/1145] [hub75] HUB75 display component (#11153) Co-authored-by: J. Nick Koston --- .clang-tidy.hash | 2 +- CODEOWNERS | 1 + esphome/components/hub75/__init__.py | 6 + esphome/components/hub75/boards/__init__.py | 80 +++ esphome/components/hub75/boards/adafruit.py | 23 + esphome/components/hub75/boards/apollo.py | 41 ++ esphome/components/hub75/boards/huidu.py | 22 + esphome/components/hub75/boards/trinity.py | 24 + esphome/components/hub75/display.py | 578 ++++++++++++++++++ esphome/components/hub75/hub75.cpp | 192 ++++++ esphome/components/hub75/hub75_component.h | 55 ++ platformio.ini | 2 + tests/components/hub75/test.esp32-idf.yaml | 39 ++ .../hub75/test.esp32-s3-idf-board.yaml | 26 + tests/components/hub75/test.esp32-s3-idf.yaml | 39 ++ 15 files changed, 1129 insertions(+), 1 deletion(-) create mode 100644 esphome/components/hub75/__init__.py create mode 100644 esphome/components/hub75/boards/__init__.py create mode 100644 esphome/components/hub75/boards/adafruit.py create mode 100644 esphome/components/hub75/boards/apollo.py create mode 100644 esphome/components/hub75/boards/huidu.py create mode 100644 esphome/components/hub75/boards/trinity.py create mode 100644 esphome/components/hub75/display.py create mode 100644 esphome/components/hub75/hub75.cpp create mode 100644 esphome/components/hub75/hub75_component.h create mode 100644 tests/components/hub75/test.esp32-idf.yaml create mode 100644 tests/components/hub75/test.esp32-s3-idf-board.yaml create mode 100644 tests/components/hub75/test.esp32-s3-idf.yaml diff --git a/.clang-tidy.hash b/.clang-tidy.hash index ab3217b5e5..7dabee48f1 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -29270eecb86ffa07b2b1d2a4ca56dd7f84762ddc89c6248dbf3f012eca8780b6 +c01eec15857a784dd603c0afd194ab3b29a632422fe6f6b0a806ad4d81b5efc0 diff --git a/CODEOWNERS b/CODEOWNERS index 65405f79d1..4f9fb7ef55 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -227,6 +227,7 @@ esphome/components/hte501/* @Stock-M esphome/components/http_request/ota/* @oarcher esphome/components/http_request/update/* @jesserockz esphome/components/htu31d/* @betterengineering +esphome/components/hub75/* @stuartparmenter esphome/components/hydreon_rgxx/* @functionpointer esphome/components/hyt271/* @Philippe12 esphome/components/i2c/* @esphome/core diff --git a/esphome/components/hub75/__init__.py b/esphome/components/hub75/__init__.py new file mode 100644 index 0000000000..cd5441f749 --- /dev/null +++ b/esphome/components/hub75/__init__.py @@ -0,0 +1,6 @@ +from esphome.cpp_generator import MockObj + +CODEOWNERS = ["@stuartparmenter"] + +# Use fully-qualified namespace to avoid collision with external hub75 library's global ::hub75 namespace +hub75_ns = MockObj("::esphome::hub75", "::") diff --git a/esphome/components/hub75/boards/__init__.py b/esphome/components/hub75/boards/__init__.py new file mode 100644 index 0000000000..52f8864c60 --- /dev/null +++ b/esphome/components/hub75/boards/__init__.py @@ -0,0 +1,80 @@ +"""Board presets for HUB75 displays. + +Each board preset defines standard pin mappings for HUB75 controller boards. +""" + +from dataclasses import dataclass, field +import importlib +import pkgutil +from typing import ClassVar + + +class BoardRegistry: + """Global registry for board configurations.""" + + _boards: ClassVar[dict[str, "BoardConfig"]] = {} + + @classmethod + def register(cls, board: "BoardConfig") -> None: + """Register a board configuration.""" + cls._boards[board.name] = board + + @classmethod + def get_boards(cls) -> dict[str, "BoardConfig"]: + """Return all registered boards.""" + return cls._boards + + +@dataclass +class BoardConfig: + """Board configuration storing HUB75 pin mappings.""" + + name: str + r1_pin: int + g1_pin: int + b1_pin: int + r2_pin: int + g2_pin: int + b2_pin: int + a_pin: int + b_pin: int + c_pin: int + d_pin: int + e_pin: int | None + lat_pin: int + oe_pin: int + clk_pin: int + ignore_strapping_pins: tuple[str, ...] = () # e.g., ("a_pin", "clk_pin") + + # Derived field for pin lookup + pins: dict[str, int | None] = field(default_factory=dict, init=False, repr=False) + + def __post_init__(self): + """Initialize derived fields and register board.""" + self.name = self.name.lower() + self.pins = { + "r1": self.r1_pin, + "g1": self.g1_pin, + "b1": self.b1_pin, + "r2": self.r2_pin, + "g2": self.g2_pin, + "b2": self.b2_pin, + "a": self.a_pin, + "b": self.b_pin, + "c": self.c_pin, + "d": self.d_pin, + "e": self.e_pin, + "lat": self.lat_pin, + "oe": self.oe_pin, + "clk": self.clk_pin, + } + BoardRegistry.register(self) + + def get_pin(self, pin_name: str) -> int | None: + """Get pin number for a given pin name.""" + return self.pins.get(pin_name) + + +# Dynamically import all board definition modules +for module_info in pkgutil.iter_modules(__path__): + importlib.import_module(f".{module_info.name}", package=__package__) diff --git a/esphome/components/hub75/boards/adafruit.py b/esphome/components/hub75/boards/adafruit.py new file mode 100644 index 0000000000..e27eeb9379 --- /dev/null +++ b/esphome/components/hub75/boards/adafruit.py @@ -0,0 +1,23 @@ +"""Adafruit Matrix Portal board definitions.""" + +from . import BoardConfig + +# Adafruit Matrix Portal S3 +BoardConfig( + "adafruit-matrix-portal-s3", + r1_pin=42, + g1_pin=41, + b1_pin=40, + r2_pin=38, + g2_pin=39, + b2_pin=37, + a_pin=45, + b_pin=36, + c_pin=48, + d_pin=35, + e_pin=21, + lat_pin=47, + oe_pin=14, + clk_pin=2, + ignore_strapping_pins=("a_pin",), # GPIO45 is a strapping pin +) diff --git a/esphome/components/hub75/boards/apollo.py b/esphome/components/hub75/boards/apollo.py new file mode 100644 index 0000000000..4b8b2c1f0a --- /dev/null +++ b/esphome/components/hub75/boards/apollo.py @@ -0,0 +1,41 @@ +"""Apollo Automation M1 board definitions.""" + +from . import BoardConfig + +# Apollo Automation M1 Rev4 +BoardConfig( + "apollo-automation-m1-rev4", + r1_pin=42, + g1_pin=41, + b1_pin=40, + r2_pin=38, + g2_pin=39, + b2_pin=37, + a_pin=45, + b_pin=36, + c_pin=48, + d_pin=35, + e_pin=21, + lat_pin=47, + oe_pin=14, + clk_pin=2, +) + +# Apollo Automation M1 Rev6 +BoardConfig( + "apollo-automation-m1-rev6", + r1_pin=1, + g1_pin=5, + b1_pin=6, + r2_pin=7, + g2_pin=13, + b2_pin=9, + a_pin=16, + b_pin=48, + c_pin=47, + d_pin=21, + e_pin=38, + lat_pin=8, + oe_pin=4, + clk_pin=18, +) diff --git a/esphome/components/hub75/boards/huidu.py b/esphome/components/hub75/boards/huidu.py new file mode 100644 index 0000000000..52744d397e --- /dev/null +++ b/esphome/components/hub75/boards/huidu.py @@ -0,0 +1,22 @@ +"""Huidu board definitions.""" + +from . import BoardConfig + +# Huidu HD-WF2 +BoardConfig( + "huidu-hd-wf2", + r1_pin=2, + g1_pin=6, + b1_pin=10, + r2_pin=3, + g2_pin=7, + b2_pin=11, + a_pin=39, + b_pin=38, + c_pin=37, + d_pin=36, + e_pin=21, + lat_pin=33, + oe_pin=35, + clk_pin=34, +) diff --git a/esphome/components/hub75/boards/trinity.py b/esphome/components/hub75/boards/trinity.py new file mode 100644 index 0000000000..bfad779ad0 --- /dev/null +++ b/esphome/components/hub75/boards/trinity.py @@ -0,0 +1,24 @@ +"""ESP32 Trinity board definitions.""" + +from . import BoardConfig + +# ESP32 Trinity +# https://esp32trinity.com/ +# Pin assignments from: https://github.com/witnessmenow/ESP32-Trinity/blob/master/FAQ.md +BoardConfig( + "esp32-trinity", + r1_pin=25, + g1_pin=26, + b1_pin=27, + r2_pin=14, + g2_pin=12, + b2_pin=13, + a_pin=23, + b_pin=19, + c_pin=5, + d_pin=17, + e_pin=18, + lat_pin=4, + oe_pin=15, + clk_pin=16, +) diff --git a/esphome/components/hub75/display.py b/esphome/components/hub75/display.py new file mode 100644 index 0000000000..81dd4ffc1c --- /dev/null +++ b/esphome/components/hub75/display.py @@ -0,0 +1,578 @@ +from typing import Any + +from esphome import pins +import esphome.codegen as cg +from esphome.components import display +from esphome.components.esp32 import add_idf_component +import esphome.config_validation as cv +from esphome.const import ( + CONF_AUTO_CLEAR_ENABLED, + CONF_BIT_DEPTH, + CONF_BOARD, + CONF_BRIGHTNESS, + CONF_CLK_PIN, + CONF_GAMMA_CORRECT, + CONF_ID, + CONF_LAMBDA, + CONF_OE_PIN, + CONF_UPDATE_INTERVAL, +) +import esphome.final_validate as fv +from esphome.types import ConfigType + +from . import boards, hub75_ns + +DEPENDENCIES = ["esp32"] +CODEOWNERS = ["@stuartparmenter"] + +# Load all board presets +BOARDS = boards.BoardRegistry.get_boards() + +# Constants +CONF_HUB75_ID = "hub75_id" + +# Panel dimensions +CONF_PANEL_WIDTH = "panel_width" +CONF_PANEL_HEIGHT = "panel_height" + +# Multi-panel layout +CONF_LAYOUT_ROWS = "layout_rows" +CONF_LAYOUT_COLS = "layout_cols" +CONF_LAYOUT = "layout" + +# Panel hardware +CONF_SCAN_WIRING = "scan_wiring" +CONF_SHIFT_DRIVER = "shift_driver" + +# RGB pins +CONF_R1_PIN = "r1_pin" +CONF_G1_PIN = "g1_pin" +CONF_B1_PIN = "b1_pin" +CONF_R2_PIN = "r2_pin" +CONF_G2_PIN = "g2_pin" +CONF_B2_PIN = "b2_pin" + +# Address pins +CONF_A_PIN = "a_pin" +CONF_B_PIN = "b_pin" +CONF_C_PIN = "c_pin" +CONF_D_PIN = "d_pin" +CONF_E_PIN = "e_pin" + +# Control pins +CONF_LAT_PIN = "lat_pin" + +NEVER = 4294967295 # uint32_t max - value used when update_interval is "never" + +# Pin mapping from config keys to board keys +PIN_MAPPING = { + CONF_R1_PIN: "r1", + CONF_G1_PIN: "g1", + CONF_B1_PIN: "b1", + CONF_R2_PIN: "r2", + CONF_G2_PIN: "g2", + CONF_B2_PIN: "b2", + CONF_A_PIN: "a", + CONF_B_PIN: "b", + CONF_C_PIN: "c", + CONF_D_PIN: "d", + CONF_E_PIN: "e", + CONF_LAT_PIN: "lat", + CONF_OE_PIN: "oe", + CONF_CLK_PIN: "clk", +} + +# Required pins (E pin is optional) +REQUIRED_PINS = [key for key in PIN_MAPPING if key != CONF_E_PIN] + +# Configuration +CONF_CLOCK_SPEED = "clock_speed" +CONF_LATCH_BLANKING = "latch_blanking" +CONF_CLOCK_PHASE = "clock_phase" +CONF_DOUBLE_BUFFER = "double_buffer" +CONF_MIN_REFRESH_RATE = "min_refresh_rate" + +# Map to hub75 library enums (in global namespace) +ShiftDriver = cg.global_ns.enum("ShiftDriver", is_class=True) +SHIFT_DRIVERS = { + "GENERIC": ShiftDriver.GENERIC, + "FM6126A": ShiftDriver.FM6126A, + "ICN2038S": ShiftDriver.ICN2038S, + "FM6124": ShiftDriver.FM6124, + "MBI5124": ShiftDriver.MBI5124, + "DP3246": ShiftDriver.DP3246, +} + +PanelLayout = cg.global_ns.enum("PanelLayout", is_class=True) +PANEL_LAYOUTS = { + "HORIZONTAL": PanelLayout.HORIZONTAL, + "TOP_LEFT_DOWN": PanelLayout.TOP_LEFT_DOWN, + "TOP_RIGHT_DOWN": PanelLayout.TOP_RIGHT_DOWN, + "BOTTOM_LEFT_UP": PanelLayout.BOTTOM_LEFT_UP, + "BOTTOM_RIGHT_UP": PanelLayout.BOTTOM_RIGHT_UP, + "TOP_LEFT_DOWN_ZIGZAG": PanelLayout.TOP_LEFT_DOWN_ZIGZAG, + "TOP_RIGHT_DOWN_ZIGZAG": PanelLayout.TOP_RIGHT_DOWN_ZIGZAG, + "BOTTOM_LEFT_UP_ZIGZAG": PanelLayout.BOTTOM_LEFT_UP_ZIGZAG, + "BOTTOM_RIGHT_UP_ZIGZAG": PanelLayout.BOTTOM_RIGHT_UP_ZIGZAG, +} + +ScanPattern = cg.global_ns.enum("ScanPattern", is_class=True) +SCAN_PATTERNS = { + "STANDARD_TWO_SCAN": ScanPattern.STANDARD_TWO_SCAN, + "FOUR_SCAN_16PX_HIGH": ScanPattern.FOUR_SCAN_16PX_HIGH, + "FOUR_SCAN_32PX_HIGH": ScanPattern.FOUR_SCAN_32PX_HIGH, + "FOUR_SCAN_64PX_HIGH": ScanPattern.FOUR_SCAN_64PX_HIGH, +} + +Hub75ClockSpeed = cg.global_ns.enum("Hub75ClockSpeed", is_class=True) +CLOCK_SPEEDS = { + "8MHZ": Hub75ClockSpeed.HZ_8M, + "10MHZ": Hub75ClockSpeed.HZ_10M, + "16MHZ": Hub75ClockSpeed.HZ_16M, + "20MHZ": Hub75ClockSpeed.HZ_20M, +} + +HUB75Display = hub75_ns.class_("HUB75Display", cg.PollingComponent, display.Display) +Hub75Config = cg.global_ns.struct("Hub75Config") +Hub75Pins = cg.global_ns.struct("Hub75Pins") + + +def _merge_board_pins(config: ConfigType) -> ConfigType: + """Merge board preset pins with explicit pin overrides.""" + board_name = config.get(CONF_BOARD) + + if board_name is None: + # No board specified - validate that all required pins are present + errs = [ + cv.Invalid( + f"Required pin '{pin_name}' is missing. " + f"Either specify a board preset or provide all pin mappings manually.", + path=[pin_name], + ) + for pin_name in REQUIRED_PINS + if pin_name not in config + ] + + if errs: + raise cv.MultipleInvalid(errs) + + # E_PIN is optional + return config + + # Get board configuration + if board_name not in BOARDS: + raise cv.Invalid( + f"Unknown board '{board_name}'. Available boards: {', '.join(sorted(BOARDS.keys()))}" + ) + + board = BOARDS[board_name] + + # Merge board pins with explicit overrides + # Explicit pins in config take precedence over board defaults + for conf_key, board_key in PIN_MAPPING.items(): + if conf_key in config or (board_pin := board.get_pin(board_key)) is None: + continue + # Create pin config + pin_config = {"number": board_pin} + if conf_key in board.ignore_strapping_pins: + pin_config["ignore_strapping_warning"] = True + + # Validate through pin schema to add required fields (id, etc.) + config[conf_key] = pins.gpio_output_pin_schema(pin_config) + + return config + + +def _validate_config(config: ConfigType) -> ConfigType: + """Validate driver and layout requirements.""" + errs: list[cv.Invalid] = [] + + # MBI5124 requires inverted clock phase + driver = config.get(CONF_SHIFT_DRIVER, "GENERIC") + if driver == "MBI5124" and not config.get(CONF_CLOCK_PHASE, False): + errs.append( + cv.Invalid( + "MBI5124 shift driver requires 'clock_phase: true' to be set", + path=[CONF_CLOCK_PHASE], + ) + ) + + # Prevent conflicting min_refresh_rate + update_interval configuration + # min_refresh_rate is auto-calculated from update_interval unless using LVGL mode + update_interval = config.get(CONF_UPDATE_INTERVAL) + if CONF_MIN_REFRESH_RATE in config and update_interval is not None: + # Handle both integer (NEVER) and time object cases + interval_ms = ( + update_interval + if isinstance(update_interval, int) + else update_interval.total_milliseconds + ) + if interval_ms != NEVER: + errs.append( + cv.Invalid( + "Cannot set both 'min_refresh_rate' and 'update_interval' (except 'never'). " + "Refresh rate is auto-calculated from update_interval. " + "Remove 'min_refresh_rate' or use 'update_interval: never' for LVGL mode.", + path=[CONF_MIN_REFRESH_RATE], + ) + ) + + # Validate layout configuration (validate effective config including C++ defaults) + layout = config.get(CONF_LAYOUT, "HORIZONTAL") + layout_rows = config.get(CONF_LAYOUT_ROWS, 1) + layout_cols = config.get(CONF_LAYOUT_COLS, 1) + is_zigzag = "ZIGZAG" in layout + + # Single panel (1x1) should use HORIZONTAL + if layout_rows == 1 and layout_cols == 1 and layout != "HORIZONTAL": + errs.append( + cv.Invalid( + f"Single panel (layout_rows=1, layout_cols=1) should use 'layout: HORIZONTAL' (got {layout})", + path=[CONF_LAYOUT], + ) + ) + + # HORIZONTAL layout requires single row + if layout == "HORIZONTAL" and layout_rows != 1: + errs.append( + cv.Invalid( + f"HORIZONTAL layout requires 'layout_rows: 1' (got {layout_rows}). " + "For multi-row grids, use TOP_LEFT_DOWN or other grid layouts.", + path=[CONF_LAYOUT_ROWS], + ) + ) + + # Grid layouts (non-HORIZONTAL) require more than one panel + if layout != "HORIZONTAL" and layout_rows == 1 and layout_cols == 1: + errs.append( + cv.Invalid( + f"Grid layout '{layout}' requires multiple panels (layout_rows > 1 or layout_cols > 1)", + path=[CONF_LAYOUT], + ) + ) + + # Serpentine layouts (non-ZIGZAG) require multiple rows + # Serpentine physically rotates alternate rows upside down (Y-coordinate inversion) + # Single-row chains should use HORIZONTAL or ZIGZAG variants + if not is_zigzag and layout != "HORIZONTAL" and layout_rows == 1: + errs.append( + cv.Invalid( + f"Serpentine layout '{layout}' requires layout_rows > 1 " + f"(got layout_rows={layout_rows}). " + "Serpentine wiring physically rotates alternate rows upside down. " + "For single-row chains, use 'layout: HORIZONTAL' or add '_ZIGZAG' suffix.", + path=[CONF_LAYOUT_ROWS], + ) + ) + + # ZIGZAG layouts require actual grid (both rows AND cols > 1) + if is_zigzag and (layout_rows == 1 or layout_cols == 1): + errs.append( + cv.Invalid( + f"ZIGZAG layout '{layout}' requires both layout_rows > 1 AND layout_cols > 1 " + f"(got rows={layout_rows}, cols={layout_cols}). " + "For single row/column chains, use non-zigzag layouts or HORIZONTAL.", + path=[CONF_LAYOUT], + ) + ) + + if errs: + raise cv.MultipleInvalid(errs) + + return config + + +def _final_validate(config: ConfigType) -> ConfigType: + """Validate requirements when using HUB75 display.""" + # Local imports to avoid circular dependencies + from esphome.components.esp32 import get_esp32_variant + from esphome.components.esp32.const import VARIANT_ESP32P4 + from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN + from esphome.components.psram import DOMAIN as PSRAM_DOMAIN + + full_config = fv.full_config.get() + errs: list[cv.Invalid] = [] + + # ESP32-P4 requires PSRAM + variant = get_esp32_variant() + if variant == VARIANT_ESP32P4 and PSRAM_DOMAIN not in full_config: + errs.append( + cv.Invalid( + "HUB75 display on ESP32-P4 requires PSRAM. Add 'psram:' to your configuration.", + path=[CONF_ID], + ) + ) + + # LVGL-specific validation + if LVGL_DOMAIN in full_config: + # Check update_interval (converted from "never" to NEVER constant) + update_interval = config.get(CONF_UPDATE_INTERVAL) + if update_interval is not None: + # Handle both integer (NEVER) and time object cases + interval_ms = ( + update_interval + if isinstance(update_interval, int) + else update_interval.total_milliseconds + ) + if interval_ms != NEVER: + errs.append( + cv.Invalid( + "HUB75 display with LVGL must have 'update_interval: never'. " + "LVGL manages its own refresh timing.", + path=[CONF_UPDATE_INTERVAL], + ) + ) + + # Check auto_clear_enabled + auto_clear = config[CONF_AUTO_CLEAR_ENABLED] + if auto_clear is not False: + errs.append( + cv.Invalid( + f"HUB75 display with LVGL must have 'auto_clear_enabled: false' (got '{auto_clear}'). " + "LVGL manages screen clearing.", + path=[CONF_AUTO_CLEAR_ENABLED], + ) + ) + + # Check double_buffer (C++ default: false) + double_buffer = config.get(CONF_DOUBLE_BUFFER, False) + if double_buffer is not False: + errs.append( + cv.Invalid( + f"HUB75 display with LVGL must have 'double_buffer: false' (got '{double_buffer}'). " + "LVGL uses its own buffering strategy.", + path=[CONF_DOUBLE_BUFFER], + ) + ) + + if errs: + raise cv.MultipleInvalid(errs) + + return config + + +FINAL_VALIDATE_SCHEMA = cv.Schema(_final_validate) + + +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(HUB75Display), + # Board preset (optional - provides default pin mappings) + cv.Optional(CONF_BOARD): cv.one_of(*BOARDS.keys(), lower=True), + # Panel dimensions + cv.Required(CONF_PANEL_WIDTH): cv.positive_int, + cv.Required(CONF_PANEL_HEIGHT): cv.positive_int, + # Multi-panel layout + cv.Optional(CONF_LAYOUT_ROWS): cv.positive_int, + cv.Optional(CONF_LAYOUT_COLS): cv.positive_int, + cv.Optional(CONF_LAYOUT): cv.enum(PANEL_LAYOUTS, upper=True, space="_"), + # Panel hardware configuration + cv.Optional(CONF_SCAN_WIRING): cv.enum( + SCAN_PATTERNS, upper=True, space="_" + ), + cv.Optional(CONF_SHIFT_DRIVER): cv.enum(SHIFT_DRIVERS, upper=True), + # Display configuration + cv.Optional(CONF_DOUBLE_BUFFER): cv.boolean, + cv.Optional(CONF_BRIGHTNESS): cv.int_range(min=0, max=255), + cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=6, max=12), + cv.Optional(CONF_GAMMA_CORRECT): cv.enum( + {"LINEAR": 0, "CIE1931": 1, "GAMMA_2_2": 2}, upper=True + ), + cv.Optional(CONF_MIN_REFRESH_RATE): cv.int_range(min=40, max=200), + # RGB data pins + cv.Optional(CONF_R1_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_G1_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_B1_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_R2_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_G2_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_B2_PIN): pins.gpio_output_pin_schema, + # Address pins + cv.Optional(CONF_A_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_B_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_C_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_D_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_E_PIN): pins.gpio_output_pin_schema, + # Control pins + cv.Optional(CONF_LAT_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_OE_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_CLK_PIN): pins.gpio_output_pin_schema, + # Timing configuration + cv.Optional(CONF_CLOCK_SPEED): cv.enum(CLOCK_SPEEDS, upper=True), + cv.Optional(CONF_LATCH_BLANKING): cv.positive_int, + cv.Optional(CONF_CLOCK_PHASE): cv.boolean, + } + ), + _merge_board_pins, + _validate_config, +) + + +DEFAULT_REFRESH_RATE = 60 # Hz + + +def _calculate_min_refresh_rate(config: ConfigType) -> int: + """Calculate minimum refresh rate for the display. + + Priority: + 1. Explicit min_refresh_rate setting (user override) + 2. Derived from update_interval (ms to Hz conversion) + 3. Default 60 Hz (for LVGL or unspecified interval) + """ + if CONF_MIN_REFRESH_RATE in config: + return config[CONF_MIN_REFRESH_RATE] + + update_interval = config.get(CONF_UPDATE_INTERVAL) + if update_interval is None: + return DEFAULT_REFRESH_RATE + + # update_interval can be TimePeriod object or NEVER constant (int) + interval_ms = ( + update_interval + if isinstance(update_interval, int) + else update_interval.total_milliseconds + ) + + # "never" or zero means external refresh (e.g., LVGL) + if interval_ms in (NEVER, 0): + return DEFAULT_REFRESH_RATE + + # Convert ms interval to Hz, clamped to valid range [40, 200] + return max(40, min(200, int(round(1000 / interval_ms)))) + + +def _build_pins_struct( + pin_expressions: dict[str, Any], e_pin_num: int | cg.RawExpression +) -> cg.StructInitializer: + """Build Hub75Pins struct from pin expressions.""" + + def pin_cast(pin): + return cg.RawExpression(f"static_cast({pin.get_pin()})") + + return cg.StructInitializer( + Hub75Pins, + ("r1", pin_cast(pin_expressions["r1"])), + ("g1", pin_cast(pin_expressions["g1"])), + ("b1", pin_cast(pin_expressions["b1"])), + ("r2", pin_cast(pin_expressions["r2"])), + ("g2", pin_cast(pin_expressions["g2"])), + ("b2", pin_cast(pin_expressions["b2"])), + ("a", pin_cast(pin_expressions["a"])), + ("b", pin_cast(pin_expressions["b"])), + ("c", pin_cast(pin_expressions["c"])), + ("d", pin_cast(pin_expressions["d"])), + ("e", e_pin_num), + ("lat", pin_cast(pin_expressions["lat"])), + ("oe", pin_cast(pin_expressions["oe"])), + ("clk", pin_cast(pin_expressions["clk"])), + ) + + +def _append_config_fields( + config: ConfigType, + field_mapping: list[tuple[str, str]], + config_fields: list[tuple[str, Any]], +) -> None: + """Append config fields from mapping if present in config.""" + for conf_key, struct_field in field_mapping: + if conf_key in config: + config_fields.append((struct_field, config[conf_key])) + + +def _build_config_struct( + config: ConfigType, pins_struct: cg.StructInitializer, min_refresh: int +) -> cg.StructInitializer: + """Build Hub75Config struct from config. + + Fields must be added in declaration order (see hub75_types.h) to satisfy + C++ designated initializer requirements. The order is: + 1. fields_before_pins (panel_width through layout) + 2. pins + 3. output_clock_speed + 4. min_refresh_rate + 5. fields_after_min_refresh (latch_blanking through brightness) + """ + fields_before_pins = [ + (CONF_PANEL_WIDTH, "panel_width"), + (CONF_PANEL_HEIGHT, "panel_height"), + # scan_pattern - auto-calculated, not set + (CONF_SCAN_WIRING, "scan_wiring"), + (CONF_SHIFT_DRIVER, "shift_driver"), + (CONF_LAYOUT_ROWS, "layout_rows"), + (CONF_LAYOUT_COLS, "layout_cols"), + (CONF_LAYOUT, "layout"), + ] + fields_after_min_refresh = [ + (CONF_LATCH_BLANKING, "latch_blanking"), + (CONF_DOUBLE_BUFFER, "double_buffer"), + (CONF_CLOCK_PHASE, "clk_phase_inverted"), + (CONF_BRIGHTNESS, "brightness"), + ] + + config_fields: list[tuple[str, Any]] = [] + + _append_config_fields(config, fields_before_pins, config_fields) + + config_fields.append(("pins", pins_struct)) + + if CONF_CLOCK_SPEED in config: + config_fields.append(("output_clock_speed", config[CONF_CLOCK_SPEED])) + + config_fields.append(("min_refresh_rate", min_refresh)) + + _append_config_fields(config, fields_after_min_refresh, config_fields) + + return cg.StructInitializer(Hub75Config, *config_fields) + + +async def to_code(config: ConfigType) -> None: + add_idf_component( + name="esphome/esp-hub75", + ref="0.1.6", + ) + + # Set compile-time configuration via defines + if CONF_BIT_DEPTH in config: + cg.add_define("HUB75_BIT_DEPTH", config[CONF_BIT_DEPTH]) + + if CONF_GAMMA_CORRECT in config: + cg.add_define("HUB75_GAMMA_MODE", config[CONF_GAMMA_CORRECT]) + + # Await all pin expressions + pin_expressions = { + "r1": await cg.gpio_pin_expression(config[CONF_R1_PIN]), + "g1": await cg.gpio_pin_expression(config[CONF_G1_PIN]), + "b1": await cg.gpio_pin_expression(config[CONF_B1_PIN]), + "r2": await cg.gpio_pin_expression(config[CONF_R2_PIN]), + "g2": await cg.gpio_pin_expression(config[CONF_G2_PIN]), + "b2": await cg.gpio_pin_expression(config[CONF_B2_PIN]), + "a": await cg.gpio_pin_expression(config[CONF_A_PIN]), + "b": await cg.gpio_pin_expression(config[CONF_B_PIN]), + "c": await cg.gpio_pin_expression(config[CONF_C_PIN]), + "d": await cg.gpio_pin_expression(config[CONF_D_PIN]), + "lat": await cg.gpio_pin_expression(config[CONF_LAT_PIN]), + "oe": await cg.gpio_pin_expression(config[CONF_OE_PIN]), + "clk": await cg.gpio_pin_expression(config[CONF_CLK_PIN]), + } + + # E pin is optional + if CONF_E_PIN in config: + e_pin = await cg.gpio_pin_expression(config[CONF_E_PIN]) + e_pin_num = cg.RawExpression(f"static_cast({e_pin.get_pin()})") + else: + e_pin_num = -1 + + # Build structs + min_refresh = _calculate_min_refresh_rate(config) + pins_struct = _build_pins_struct(pin_expressions, e_pin_num) + hub75_config = _build_config_struct(config, pins_struct, min_refresh) + + # Create display and register + var = cg.new_Pvariable(config[CONF_ID], hub75_config) + await display.register_display(var, config) + + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/hub75/hub75.cpp b/esphome/components/hub75/hub75.cpp new file mode 100644 index 0000000000..e023e446c4 --- /dev/null +++ b/esphome/components/hub75/hub75.cpp @@ -0,0 +1,192 @@ +#include "hub75_component.h" +#include "esphome/core/application.h" + +#ifdef USE_ESP32 + +namespace esphome::hub75 { + +static const char *const TAG = "hub75"; + +// ======================================== +// Constructor +// ======================================== + +HUB75Display::HUB75Display(const Hub75Config &config) : config_(config) { + // Initialize runtime state from config + this->brightness_ = config.brightness; + this->enabled_ = (config.brightness > 0); +} + +// ======================================== +// Core Component methods +// ======================================== + +void HUB75Display::setup() { + ESP_LOGCONFIG(TAG, "Setting up HUB75Display..."); + + // Create driver with pre-configured config + driver_ = new Hub75Driver(config_); + if (!driver_->begin()) { + ESP_LOGE(TAG, "Failed to initialize HUB75 driver!"); + return; + } + + this->enabled_ = true; +} + +void HUB75Display::dump_config() { + LOG_DISPLAY("", "HUB75", this); + + ESP_LOGCONFIG(TAG, + " Panel: %dx%d pixels\n" + " Layout: %dx%d panels\n" + " Virtual Display: %dx%d pixels", + config_.panel_width, config_.panel_height, config_.layout_cols, config_.layout_rows, + config_.panel_width * config_.layout_cols, config_.panel_height * config_.layout_rows); + + ESP_LOGCONFIG(TAG, + " Scan Wiring: %d\n" + " Shift Driver: %d", + static_cast(config_.scan_wiring), static_cast(config_.shift_driver)); + + ESP_LOGCONFIG(TAG, + " Pins: R1:%i, G1:%i, B1:%i, R2:%i, G2:%i, B2:%i\n" + " Pins: A:%i, B:%i, C:%i, D:%i, E:%i\n" + " Pins: LAT:%i, OE:%i, CLK:%i", + config_.pins.r1, config_.pins.g1, config_.pins.b1, config_.pins.r2, config_.pins.g2, config_.pins.b2, + config_.pins.a, config_.pins.b, config_.pins.c, config_.pins.d, config_.pins.e, config_.pins.lat, + config_.pins.oe, config_.pins.clk); + + ESP_LOGCONFIG(TAG, + " Clock Speed: %u MHz\n" + " Latch Blanking: %i\n" + " Clock Phase: %s\n" + " Min Refresh Rate: %i Hz\n" + " Bit Depth: %i\n" + " Double Buffer: %s", + static_cast(config_.output_clock_speed) / 1000000, config_.latch_blanking, + TRUEFALSE(config_.clk_phase_inverted), config_.min_refresh_rate, HUB75_BIT_DEPTH, + YESNO(config_.double_buffer)); +} + +// ======================================== +// Display/PollingComponent methods +// ======================================== + +void HUB75Display::update() { + if (!driver_) [[unlikely]] + return; + if (!this->enabled_) [[unlikely]] + return; + + this->do_update_(); + + if (config_.double_buffer) { + driver_->flip_buffer(); + } +} + +void HUB75Display::fill(Color color) { + if (!driver_) [[unlikely]] + return; + if (!this->enabled_) [[unlikely]] + return; + + // Special case: black (off) - use fast hardware clear + if (!color.is_on()) { + driver_->clear(); + return; + } + + // For non-black colors, fall back to base class (pixel-by-pixel) + Display::fill(color); +} + +void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) { + if (!driver_) [[unlikely]] + return; + if (!this->enabled_) [[unlikely]] + return; + + if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) [[unlikely]] + return; + + driver_->set_pixel(x, y, color.r, color.g, color.b); + App.feed_wdt(); +} + +void HOT HUB75Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, + ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { + if (!driver_) [[unlikely]] + return; + if (!this->enabled_) [[unlikely]] + return; + + // Map ESPHome enums to hub75 enums + Hub75PixelFormat format; + Hub75ColorOrder color_order = Hub75ColorOrder::RGB; + int bytes_per_pixel; + + // Determine format based on bitness + if (bitness == ColorBitness::COLOR_BITNESS_565) { + format = Hub75PixelFormat::RGB565; + bytes_per_pixel = 2; + } else if (bitness == ColorBitness::COLOR_BITNESS_888) { +#ifdef USE_LVGL +#if LV_COLOR_DEPTH == 32 + // 32-bit: 4 bytes per pixel with padding byte (LVGL mode) + format = Hub75PixelFormat::RGB888_32; + bytes_per_pixel = 4; + + // Map ESPHome ColorOrder to Hub75ColorOrder + // ESPHome ColorOrder is typically BGR for little-endian 32-bit + color_order = (order == ColorOrder::COLOR_ORDER_RGB) ? Hub75ColorOrder::RGB : Hub75ColorOrder::BGR; +#elif LV_COLOR_DEPTH == 24 + // 24-bit: 3 bytes per pixel, tightly packed + format = Hub75PixelFormat::RGB888; + bytes_per_pixel = 3; + // Note: 24-bit is always RGB order in LVGL +#else + ESP_LOGE(TAG, "Unsupported LV_COLOR_DEPTH: %d", LV_COLOR_DEPTH); + return; +#endif +#else + // Non-LVGL mode: standard 24-bit RGB888 + format = Hub75PixelFormat::RGB888; + bytes_per_pixel = 3; + color_order = (order == ColorOrder::COLOR_ORDER_RGB) ? Hub75ColorOrder::RGB : Hub75ColorOrder::BGR; +#endif + } else { + ESP_LOGE(TAG, "Unsupported bitness: %d", static_cast(bitness)); + return; + } + + // Check if buffer is tightly packed (no stride) + const int stride_px = x_offset + w + x_pad; + const bool is_packed = (x_offset == 0 && x_pad == 0 && y_offset == 0); + + if (is_packed) { + // Tightly packed buffer - single bulk call for best performance + driver_->draw_pixels(x_start, y_start, w, h, ptr, format, color_order, big_endian); + } else { + // Buffer has stride (padding between rows) - draw row by row + for (int yy = 0; yy < h; ++yy) { + const size_t row_offset = ((y_offset + yy) * stride_px + x_offset) * bytes_per_pixel; + const uint8_t *row_ptr = ptr + row_offset; + + driver_->draw_pixels(x_start, y_start + yy, w, 1, row_ptr, format, color_order, big_endian); + } + } +} + +void HUB75Display::set_brightness(int brightness) { + this->brightness_ = brightness; + this->enabled_ = (brightness > 0); + if (this->driver_ != nullptr) { + this->driver_->set_brightness(brightness); + } +} + +} // namespace esphome::hub75 + +#endif diff --git a/esphome/components/hub75/hub75_component.h b/esphome/components/hub75/hub75_component.h new file mode 100644 index 0000000000..49d4274483 --- /dev/null +++ b/esphome/components/hub75/hub75_component.h @@ -0,0 +1,55 @@ +#pragma once + +#ifdef USE_ESP32 + +#include + +#include "esphome/components/display/display_buffer.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "hub75.h" // hub75 library + +namespace esphome::hub75 { + +using esphome::display::ColorBitness; +using esphome::display::ColorOrder; + +class HUB75Display : public display::Display { + public: + // Constructor accepting config + explicit HUB75Display(const Hub75Config &config); + + // Core Component methods + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::PROCESSOR; } + + // Display/PollingComponent methods + void update() override; + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + void fill(Color color) override; + void draw_pixel_at(int x, int y, Color color) override; + void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; + + // Brightness control (runtime mutable) + void set_brightness(int brightness); + + protected: + // Display internal methods + int get_width_internal() override { return config_.panel_width * config_.layout_cols; } + int get_height_internal() override { return config_.panel_height * config_.layout_rows; } + + // Member variables + Hub75Driver *driver_{nullptr}; + Hub75Config config_; // Immutable configuration + + // Runtime state (mutable) + int brightness_{128}; + bool enabled_{false}; +}; + +} // namespace esphome::hub75 + +#endif diff --git a/platformio.ini b/platformio.ini index 81f8b3295b..9095d27af8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -152,6 +152,7 @@ lib_deps = esphome/ESP32-audioI2S@2.3.0 ; i2s_audio droscy/esp_wireguard@0.4.2 ; wireguard esphome/esp-audio-libs@2.0.1 ; audio + esphome/esp-hub75@0.1.6 ; hub75 build_flags = ${common:arduino.build_flags} @@ -175,6 +176,7 @@ lib_deps = droscy/esp_wireguard@0.4.2 ; wireguard kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word esphome/esp-audio-libs@2.0.1 ; audio + esphome/esp-hub75@0.1.6 ; hub75 build_flags = ${common:idf.build_flags} -Wno-nonnull-compare diff --git a/tests/components/hub75/test.esp32-idf.yaml b/tests/components/hub75/test.esp32-idf.yaml new file mode 100644 index 0000000000..c275d24187 --- /dev/null +++ b/tests/components/hub75/test.esp32-idf.yaml @@ -0,0 +1,39 @@ +esp32: + board: esp32dev + framework: + type: esp-idf + +display: + - platform: hub75 + id: my_hub75 + panel_width: 64 + panel_height: 32 + double_buffer: true + brightness: 128 + r1_pin: GPIO25 + g1_pin: GPIO26 + b1_pin: GPIO27 + r2_pin: GPIO14 + g2_pin: GPIO12 + b2_pin: GPIO13 + a_pin: GPIO23 + b_pin: GPIO19 + c_pin: GPIO5 + d_pin: GPIO17 + e_pin: GPIO21 + lat_pin: GPIO4 + oe_pin: GPIO15 + clk_pin: GPIO16 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/hub75/test.esp32-s3-idf-board.yaml b/tests/components/hub75/test.esp32-s3-idf-board.yaml new file mode 100644 index 0000000000..9568ccf3aa --- /dev/null +++ b/tests/components/hub75/test.esp32-s3-idf-board.yaml @@ -0,0 +1,26 @@ +esp32: + board: esp32-s3-devkitc-1 + framework: + type: esp-idf + +display: + - platform: hub75 + id: hub75_display_board + board: adafruit-matrix-portal-s3 + panel_width: 64 + panel_height: 32 + double_buffer: true + brightness: 128 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/hub75/test.esp32-s3-idf.yaml b/tests/components/hub75/test.esp32-s3-idf.yaml new file mode 100644 index 0000000000..db678c98a4 --- /dev/null +++ b/tests/components/hub75/test.esp32-s3-idf.yaml @@ -0,0 +1,39 @@ +esp32: + board: esp32-s3-devkitc-1 + framework: + type: esp-idf + +display: + - platform: hub75 + id: my_hub75 + panel_width: 64 + panel_height: 32 + double_buffer: true + brightness: 128 + r1_pin: GPIO42 + g1_pin: GPIO41 + b1_pin: GPIO40 + r2_pin: GPIO38 + g2_pin: GPIO39 + b2_pin: GPIO37 + a_pin: GPIO45 + b_pin: GPIO36 + c_pin: GPIO48 + d_pin: GPIO35 + e_pin: GPIO21 + lat_pin: GPIO47 + oe_pin: GPIO14 + clk_pin: GPIO2 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); From 1fa7adbe8dd63e33c334a57179b6730ecc13e8d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Fri, 5 Dec 2025 21:24:57 +0100 Subject: [PATCH 0534/1145] [mipi_spi] Add M5CORE2 model (#12301) --- esphome/components/mipi_spi/models/ili.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/esphome/components/mipi_spi/models/ili.py b/esphome/components/mipi_spi/models/ili.py index 0102c0f665..60a25c32a9 100644 --- a/esphome/components/mipi_spi/models/ili.py +++ b/esphome/components/mipi_spi/models/ili.py @@ -148,6 +148,19 @@ ILI9341 = DriverChip( ), ), ) +# M5Stack Core2 uses ILI9341 chip - mirror_x disabled for correct orientation +ILI9341.extend( + "M5CORE2", + width=320, + height=240, + mirror_x=False, + cs_pin=5, + dc_pin=15, + invert_colors=True, + pixel_mode="18bit", + data_rate="40MHz", +) + DriverChip( "ILI9481", mirror_x=True, From bbb71b5359e459b6a4091ab95704b628d4c32802 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:16:55 -0600 Subject: [PATCH 0535/1145] Bump peter-evans/create-pull-request from 7.0.9 to 7.0.11 (#12303) 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 ea81a1e013..2c3219e38e 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -41,7 +41,7 @@ jobs: python script/run-in-env.py pre-commit run --all-files - name: Commit changes - uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9 + uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11 with: commit-message: "Synchronise Device Classes from Home Assistant" committer: esphomebot From 10b54df77194eea0f75116d76eeeff6fa6edc1e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:17:10 -0600 Subject: [PATCH 0536/1145] Bump github/codeql-action from 4.31.6 to 4.31.7 (#12304) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d9b6bcdcca..481ad0ec34 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6 + uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -86,6 +86,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6 + uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7 with: category: "/language:${{matrix.language}}" From a517e0ec80a3b8d8b82e57e53c92ebfc1f61e7a1 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 5 Dec 2025 16:28:24 -0500 Subject: [PATCH 0537/1145] [esp32] Add missing variant support (#12305) Co-authored-by: Claude --- esphome/components/deep_sleep/__init__.py | 7 +++++++ esphome/components/esp32_can/canbus.py | 3 +++ esphome/components/ethernet/__init__.py | 12 +++++++++++- esphome/components/ethernet/ethernet_component.cpp | 4 ++-- esphome/components/spi/__init__.py | 2 ++ 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index fa3ea449e2..8849fad7d6 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -5,9 +5,11 @@ from esphome.components.esp32 import ( VARIANT_ESP32, VARIANT_ESP32C2, VARIANT_ESP32C3, + VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32C61, VARIANT_ESP32H2, + VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, get_esp32_variant, @@ -55,9 +57,11 @@ WAKEUP_PINS = { ], VARIANT_ESP32C2: [0, 1, 2, 3, 4, 5], VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5], + VARIANT_ESP32C5: [0, 1, 2, 3, 4, 5, 6, 7], VARIANT_ESP32C6: [0, 1, 2, 3, 4, 5, 6, 7], VARIANT_ESP32C61: [0, 1, 2, 3, 4, 5, 6], VARIANT_ESP32H2: [7, 8, 9, 10, 11, 12, 13, 14], + VARIANT_ESP32P4: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], VARIANT_ESP32S2: [ 0, 1, @@ -124,9 +128,11 @@ def _validate_ex1_wakeup_mode(value): if value == "ANY_LOW": esp32.only_on_variant( supported=[ + VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32C61, VARIANT_ESP32H2, + VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, ], @@ -221,6 +227,7 @@ CONFIG_SCHEMA = cv.All( unsupported=[ VARIANT_ESP32C2, VARIANT_ESP32C3, + VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32C61, VARIANT_ESP32H2, diff --git a/esphome/components/esp32_can/canbus.py b/esphome/components/esp32_can/canbus.py index 000ef303fe..0899a0dc2b 100644 --- a/esphome/components/esp32_can/canbus.py +++ b/esphome/components/esp32_can/canbus.py @@ -7,6 +7,7 @@ from esphome.components.canbus import CONF_BIT_RATE, CanbusComponent, CanSpeed from esphome.components.esp32 import ( VARIANT_ESP32, VARIANT_ESP32C3, + VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32C61, VARIANT_ESP32H2, @@ -59,6 +60,7 @@ CAN_SPEEDS_ESP32_S2 = { CAN_SPEEDS_ESP32_S3 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_C3 = {**CAN_SPEEDS_ESP32_S2} +CAN_SPEEDS_ESP32_C5 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_C6 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_C61 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_H2 = {**CAN_SPEEDS_ESP32_S2} @@ -67,6 +69,7 @@ CAN_SPEEDS_ESP32_P4 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS = { VARIANT_ESP32: CAN_SPEEDS_ESP32, VARIANT_ESP32C3: CAN_SPEEDS_ESP32_C3, + VARIANT_ESP32C5: CAN_SPEEDS_ESP32_C5, VARIANT_ESP32C6: CAN_SPEEDS_ESP32_C6, VARIANT_ESP32C61: CAN_SPEEDS_ESP32_C61, VARIANT_ESP32H2: CAN_SPEEDS_ESP32_H2, diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 39af1ff4b9..b4b1fcd9f6 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -5,6 +5,9 @@ import esphome.codegen as cg from esphome.components.esp32 import ( VARIANT_ESP32, VARIANT_ESP32C3, + VARIANT_ESP32C5, + VARIANT_ESP32C6, + VARIANT_ESP32C61, VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3, @@ -301,7 +304,14 @@ def _final_validate_spi(config): return if spi_configs := fv.full_config.get().get(CONF_SPI): variant = get_esp32_variant() - if variant in (VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3): + if variant in ( + VARIANT_ESP32C3, + VARIANT_ESP32C5, + VARIANT_ESP32C6, + VARIANT_ESP32C61, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + ): spi_host = "SPI2_HOST" else: spi_host = "SPI3_HOST" diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 757e358db3..793ebdec42 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -87,8 +87,8 @@ void EthernetComponent::setup() { .intr_flags = 0, }; -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \ - defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || defined(USE_ESP32_VARIANT_ESP32C6) || \ + defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) auto host = SPI2_HOST; #else auto host = SPI3_HOST; diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 0b531b9ed6..88bb3406e1 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -7,6 +7,7 @@ from esphome.components.esp32 import ( KEY_ESP32, VARIANT_ESP32C2, VARIANT_ESP32C3, + VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32C61, VARIANT_ESP32H2, @@ -129,6 +130,7 @@ def get_hw_interface_list(): if get_target_variant() in [ VARIANT_ESP32C2, VARIANT_ESP32C3, + VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32C61, VARIANT_ESP32H2, From 6716194e47a1d85aa495eb7b913430edd7087713 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sat, 6 Dec 2025 09:59:29 +1100 Subject: [PATCH 0538/1145] [binary_sensor] Fix reporting of 'unknown' (#12296) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: J. Nick Koston --- .../binary_sensor/binary_sensor.cpp | 13 +- .../components/binary_sensor/binary_sensor.h | 2 + esphome/core/entity_base.h | 16 +- tests/integration/README.md | 24 ++- .../binary_sensor_invalidate_state.yaml | 39 +++++ tests/integration/state_utils.py | 63 ++++++++ .../test_binary_sensor_invalidate_state.py | 138 ++++++++++++++++++ 7 files changed, 283 insertions(+), 12 deletions(-) create mode 100644 tests/integration/fixtures/binary_sensor_invalidate_state.yaml create mode 100644 tests/integration/test_binary_sensor_invalidate_state.py diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 92b8db5c51..86b7350aa8 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -34,13 +34,20 @@ void BinarySensor::publish_initial_state(bool new_state) { void BinarySensor::send_state_internal(bool new_state) { // copy the new state to the visible property for backwards compatibility, before any callbacks this->state = new_state; - // Note that set_state_ de-dups and will only trigger callbacks if the state has actually changed - if (this->set_state_(new_state)) { - ESP_LOGD(TAG, "'%s': New state is %s", this->get_name().c_str(), ONOFF(new_state)); + // Note that set_new_state_ de-dups and will only trigger callbacks if the state has actually changed + this->set_new_state(new_state); +} + +bool BinarySensor::set_new_state(const optional &new_state) { + if (StatefulEntityBase::set_new_state(new_state)) { + // weirdly, this file could be compiled even without USE_BINARY_SENSOR defined #if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_binary_sensor_update(this); #endif + ESP_LOGD(TAG, "'%s': %s", this->get_name().c_str(), ONOFFMAYBE(new_state)); + return true; } + return false; } void BinarySensor::add_filter(Filter *filter) { diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 0dca3e1520..83c992bfed 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -61,6 +61,8 @@ class BinarySensor : public StatefulEntityBase, public EntityBase_DeviceCl protected: Filter *filter_list_{nullptr}; + + bool set_new_state(const optional &new_state) override; }; class BinarySensorInitiallyOff : public BinarySensor { diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index aa9b92877a..fdf3f6300a 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -205,7 +205,7 @@ template class StatefulEntityBase : public EntityBase { virtual bool has_state() const { return this->state_.has_value(); } virtual const T &get_state() const { return this->state_.value(); } virtual T get_state_default(T default_value) const { return this->state_.value_or(default_value); } - void invalidate_state() { this->set_state_({}); } + void invalidate_state() { this->set_new_state({}); } void add_full_state_callback(std::function previous, optional current)> &&callback) { if (this->full_state_callbacks_ == nullptr) @@ -227,20 +227,20 @@ template class StatefulEntityBase : public EntityBase { /** * Set a new state for this entity. This will trigger callbacks only if the new state is different from the previous. * - * @param state The new state. + * @param new_state The new state. * @return True if the state was changed, false if it was the same as before. */ - bool set_state_(const optional &state) { - if (this->state_ != state) { + virtual bool set_new_state(const optional &new_state) { + if (this->state_ != new_state) { // call the full state callbacks with the previous and new state if (this->full_state_callbacks_ != nullptr) - this->full_state_callbacks_->call(this->state_, state); + this->full_state_callbacks_->call(this->state_, new_state); // trigger legacy callbacks only if the new state is valid and either the trigger on initial state is enabled or // the previous state was valid auto had_state = this->has_state(); - this->state_ = state; - if (this->state_callbacks_ != nullptr && state.has_value() && (this->trigger_on_initial_state_ || had_state)) - this->state_callbacks_->call(state.value()); + this->state_ = new_state; + if (this->state_callbacks_ != nullptr && new_state.has_value() && (this->trigger_on_initial_state_ || had_state)) + this->state_callbacks_->call(new_state.value()); return true; } return false; diff --git a/tests/integration/README.md b/tests/integration/README.md index 2a6b6fe564..f99139db00 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -7,7 +7,7 @@ This directory contains end-to-end integration tests for ESPHome, focusing on te - `conftest.py` - Common fixtures and utilities - `const.py` - Constants used throughout the integration tests - `types.py` - Type definitions for fixtures and functions -- `state_utils.py` - State handling utilities (e.g., `InitialStateHelper`, `build_key_to_entity_mapping`) +- `state_utils.py` - State handling utilities (e.g., `InitialStateHelper`, `find_entity`, `require_entity`) - `fixtures/` - YAML configuration files for tests - `test_*.py` - Individual test files @@ -53,6 +53,28 @@ The `InitialStateHelper` class solves a common problem in integration tests: whe **Future work:** Consider converting existing integration tests to use `InitialStateHelper` for more reliable state tracking and to eliminate race conditions related to initial state broadcasts. +#### Entity Lookup Helpers (`state_utils.py`) + +Two helper functions simplify finding entities in test code: + +**`find_entity(entities, object_id_substring, entity_type=None)`** +- Finds an entity by searching for a substring in its `object_id` (case-insensitive) +- Optionally filters by entity type (e.g., `BinarySensorInfo`) +- Returns `None` if not found + +**`require_entity(entities, object_id_substring, entity_type=None, description=None)`** +- Same as `find_entity` but raises `AssertionError` if not found +- Use `description` parameter for clearer error messages + +```python +from aioesphomeapi import BinarySensorInfo +from .state_utils import require_entity + +# Find entities with clear error messages +binary_sensor = require_entity(entities, "test_sensor", BinarySensorInfo) +button = require_entity(entities, "set_true", description="Set True button") +``` + ### Writing Tests The simplest way to write a test is to use the `run_compiled` and `api_client_connected` fixtures: diff --git a/tests/integration/fixtures/binary_sensor_invalidate_state.yaml b/tests/integration/fixtures/binary_sensor_invalidate_state.yaml new file mode 100644 index 0000000000..4016cfe281 --- /dev/null +++ b/tests/integration/fixtures/binary_sensor_invalidate_state.yaml @@ -0,0 +1,39 @@ +esphome: + name: test-binary-sensor-invalidate + +host: +api: + batch_delay: 0ms # Disable batching to receive all state updates +logger: + level: DEBUG + +# Template binary sensor that we can control +binary_sensor: + - platform: template + name: "Test Binary Sensor" + id: test_binary_sensor + +# Buttons to control the binary sensor state +button: + - platform: template + name: "Set True" + id: set_true_button + on_press: + - binary_sensor.template.publish: + id: test_binary_sensor + state: true + + - platform: template + name: "Set False" + id: set_false_button + on_press: + - binary_sensor.template.publish: + id: test_binary_sensor + state: false + + - platform: template + name: "Invalidate State" + id: invalidate_button + on_press: + - binary_sensor.invalidate_state: + id: test_binary_sensor diff --git a/tests/integration/state_utils.py b/tests/integration/state_utils.py index 6434a41ddf..b649056f2b 100644 --- a/tests/integration/state_utils.py +++ b/tests/integration/state_utils.py @@ -4,11 +4,74 @@ from __future__ import annotations import asyncio import logging +from typing import TypeVar from aioesphomeapi import ButtonInfo, EntityInfo, EntityState _LOGGER = logging.getLogger(__name__) +T = TypeVar("T", bound=EntityInfo) + + +def find_entity( + entities: list[EntityInfo], + object_id_substring: str, + entity_type: type[T] | None = None, +) -> T | EntityInfo | None: + """Find an entity by object_id substring and optionally by type. + + Args: + entities: List of entity info objects from the API + object_id_substring: Substring to search for in object_id (case-insensitive) + entity_type: Optional entity type to filter by (e.g., BinarySensorInfo) + + Returns: + The first matching entity, or None if not found + + Example: + binary_sensor = find_entity(entities, "test_binary_sensor", BinarySensorInfo) + button = find_entity(entities, "set_true") # Any entity type + """ + substring_lower = object_id_substring.lower() + for entity in entities: + if substring_lower in entity.object_id.lower() and ( + entity_type is None or isinstance(entity, entity_type) + ): + return entity + return None + + +def require_entity( + entities: list[EntityInfo], + object_id_substring: str, + entity_type: type[T] | None = None, + description: str | None = None, +) -> T | EntityInfo: + """Find an entity or raise AssertionError if not found. + + Args: + entities: List of entity info objects from the API + object_id_substring: Substring to search for in object_id (case-insensitive) + entity_type: Optional entity type to filter by (e.g., BinarySensorInfo) + description: Human-readable description for error message + + Returns: + The first matching entity + + Raises: + AssertionError: If no matching entity is found + + Example: + binary_sensor = require_entity(entities, "test_sensor", BinarySensorInfo) + button = require_entity(entities, "set_true", description="Set True button") + """ + entity = find_entity(entities, object_id_substring, entity_type) + if entity is None: + desc = description or f"entity with '{object_id_substring}' in object_id" + type_info = f" of type {entity_type.__name__}" if entity_type else "" + raise AssertionError(f"{desc}{type_info} not found in entities") + return entity + def build_key_to_entity_mapping( entities: list[EntityInfo], entity_names: list[str] diff --git a/tests/integration/test_binary_sensor_invalidate_state.py b/tests/integration/test_binary_sensor_invalidate_state.py new file mode 100644 index 0000000000..ee9e57319c --- /dev/null +++ b/tests/integration/test_binary_sensor_invalidate_state.py @@ -0,0 +1,138 @@ +"""Integration test for binary_sensor.invalidate_state() functionality. + +This tests the fix in PR #12296 where invalidate_state() was not properly +reporting the 'unknown' state to the API. The binary sensor should report +missing_state=True when invalidated. + +Regression test for: https://github.com/esphome/esphome/issues/12252 +""" + +from __future__ import annotations + +import asyncio + +from aioesphomeapi import BinarySensorInfo, BinarySensorState, EntityState +import pytest + +from .state_utils import InitialStateHelper, require_entity +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_binary_sensor_invalidate_state( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that binary_sensor.invalidate_state() reports unknown to the API. + + This verifies that: + 1. Binary sensor starts with missing_state=True (no initial state) + 2. Publishing true sets missing_state=False and state=True + 3. Publishing false sets missing_state=False and state=False + 4. Invalidating state sets missing_state=True (unknown state) + """ + loop = asyncio.get_running_loop() + + # Track state changes + states_received: list[BinarySensorState] = [] + state_future: asyncio.Future[BinarySensorState] = loop.create_future() + + def on_state(state: EntityState) -> None: + """Track binary sensor state changes.""" + if isinstance(state, BinarySensorState): + states_received.append(state) + if not state_future.done(): + state_future.set_result(state) + + async with ( + run_compiled(yaml_config), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "test-binary-sensor-invalidate" + + # Get entities + entities, _ = await client.list_entities_services() + + # Find our binary sensor and buttons using helper + binary_sensor = require_entity(entities, "test_binary_sensor", BinarySensorInfo) + set_true_button = require_entity( + entities, "set_true", description="Set True button" + ) + set_false_button = require_entity( + entities, "set_false", description="Set False button" + ) + invalidate_button = require_entity( + entities, "invalidate", description="Invalidate button" + ) + + # Set up initial state helper to handle the initial state broadcast + initial_state_helper = InitialStateHelper(entities) + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + # Wait for initial states + try: + await initial_state_helper.wait_for_initial_states() + except TimeoutError: + pytest.fail("Timeout waiting for initial states") + + # Check initial state - should be missing (unknown) + initial_state = initial_state_helper.initial_states.get(binary_sensor.key) + assert initial_state is not None, "No initial state received for binary sensor" + assert isinstance(initial_state, BinarySensorState) + assert initial_state.missing_state is True, ( + f"Initial state should have missing_state=True, got {initial_state}" + ) + + # Test 1: Set state to true + states_received.clear() + state_future = loop.create_future() + client.button_command(set_true_button.key) + + try: + state = await asyncio.wait_for(state_future, timeout=5.0) + except TimeoutError: + pytest.fail("Timeout waiting for state=true") + + assert state.missing_state is False, ( + f"After setting true, missing_state should be False, got {state}" + ) + assert state.state is True, f"Expected state=True, got {state}" + + # Test 2: Set state to false + states_received.clear() + state_future = loop.create_future() + client.button_command(set_false_button.key) + + try: + state = await asyncio.wait_for(state_future, timeout=5.0) + except TimeoutError: + pytest.fail("Timeout waiting for state=false") + + assert state.missing_state is False, ( + f"After setting false, missing_state should be False, got {state}" + ) + assert state.state is False, f"Expected state=False, got {state}" + + # Test 3: Invalidate state (set to unknown) + # This is the critical test for the bug fix + states_received.clear() + state_future = loop.create_future() + client.button_command(invalidate_button.key) + + try: + state = await asyncio.wait_for(state_future, timeout=5.0) + except TimeoutError: + pytest.fail( + "Timeout waiting for invalidated state - " + "binary_sensor.invalidate_state() may not be reporting to the API. " + "See issue #12252." + ) + + assert state.missing_state is True, ( + f"After invalidate_state(), missing_state should be True (unknown), " + f"got {state}. This is the regression from issue #12252." + ) From 6220427524bbbf85401ba08f5ac612c509a90fa5 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 5 Dec 2025 22:32:20 -0500 Subject: [PATCH 0539/1145] [cc1101] Use Hz and cv.frequency instead of kHz (#12313) --- esphome/components/cc1101/__init__.py | 10 +++++----- esphome/components/cc1101/cc1101.cpp | 17 ++++++++--------- esphome/components/cc1101/cc1101defs.h | 2 +- tests/components/cc1101/common.yaml | 8 ++++---- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index 0f5743d0cd..e6b31b84f8 100644 --- a/esphome/components/cc1101/__init__.py +++ b/esphome/components/cc1101/__init__.py @@ -152,12 +152,12 @@ CONFIG_MAP = { CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0), CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False), CONF_DC_BLOCKING_FILTER: cv.boolean, - CONF_FREQUENCY: cv.float_range(min=300000.0, max=928000.0), - CONF_IF_FREQUENCY: cv.float_range(min=25, max=788), - CONF_FILTER_BANDWIDTH: cv.float_range(min=58.0, max=812.0), + CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300000000, max=928000000)), + CONF_IF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=25000, max=788000)), + CONF_FILTER_BANDWIDTH: cv.All(cv.frequency, cv.float_range(min=58000, max=812000)), CONF_CHANNEL: cv.uint8_t, - CONF_CHANNEL_SPACING: cv.float_range(min=25, max=405), - CONF_FSK_DEVIATION: cv.float_range(min=1.5, max=381), + CONF_CHANNEL_SPACING: cv.All(cv.frequency, cv.float_range(min=25000, max=405000)), + CONF_FSK_DEVIATION: cv.All(cv.frequency, cv.float_range(min=1500, max=381000)), CONF_MSK_DEVIATION: cv.int_range(min=1, max=8), CONF_SYMBOL_RATE: cv.float_range(min=600, max=500000), CONF_SYNC_MODE: cv.enum(SYNC_MODE, upper=False), diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 1a758e415a..3cbf09ded8 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -160,18 +160,17 @@ void CC1101Component::dump_config() { "4-FSK", "UNUSED", "UNUSED", "MSK"}; int32_t freq = static_cast(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) * XTAL_FREQUENCY / (1 << 16); - float symbol_rate = - (((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY * 1000.0f; + float symbol_rate = (((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY; float bw = XTAL_FREQUENCY / (8.0f * (4 + this->state_.CHANBW_M) * (1 << this->state_.CHANBW_E)); ESP_LOGCONFIG(TAG, "CC1101:"); LOG_PIN(" CS Pin: ", this->cs_); ESP_LOGCONFIG(TAG, " Chip ID: 0x%04X\n" - " Frequency: %" PRId32 " kHz\n" + " Frequency: %" PRId32 " Hz\n" " Channel: %u\n" " Modulation: %s\n" " Symbol Rate: %.0f baud\n" - " Filter Bandwidth: %.1f kHz\n" + " Filter Bandwidth: %.1f Hz\n" " Output Power: %.1f dBm", this->chip_id_, freq, this->state_.CHANNR, MODULATION_NAMES[this->state_.MOD_FORMAT & 0x07], symbol_rate, bw, this->output_power_effective_); @@ -289,13 +288,13 @@ void CC1101Component::set_output_power(float value) { int32_t freq = static_cast(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) * XTAL_FREQUENCY / (1 << 16); uint8_t a = 0xC0; - if (freq >= 300000 && freq <= 348000) { + if (freq >= 300000000 && freq <= 348000000) { a = PowerTableItem::find(PA_TABLE_315, sizeof(PA_TABLE_315) / sizeof(PA_TABLE_315[0]), value); - } else if (freq >= 378000 && freq <= 464000) { + } else if (freq >= 378000000 && freq <= 464000000) { a = PowerTableItem::find(PA_TABLE_433, sizeof(PA_TABLE_433) / sizeof(PA_TABLE_433[0]), value); - } else if (freq >= 779000 && freq < 900000) { + } else if (freq >= 779000000 && freq < 900000000) { a = PowerTableItem::find(PA_TABLE_868, sizeof(PA_TABLE_868) / sizeof(PA_TABLE_868[0]), value); - } else if (freq >= 900000 && freq <= 928000) { + } else if (freq >= 900000000 && freq <= 928000000) { a = PowerTableItem::find(PA_TABLE_915, sizeof(PA_TABLE_915) / sizeof(PA_TABLE_915[0]), value); } @@ -401,7 +400,7 @@ void CC1101Component::set_msk_deviation(uint8_t value) { void CC1101Component::set_symbol_rate(float value) { uint8_t e; uint32_t m; - split_float(value * (1 << 28) / (XTAL_FREQUENCY * 1000), 8, e, m); + split_float(value * (1 << 28) / XTAL_FREQUENCY, 8, e, m); this->state_.DRATE_E = e; this->state_.DRATE_M = static_cast(m); if (this->initialized_) { diff --git a/esphome/components/cc1101/cc1101defs.h b/esphome/components/cc1101/cc1101defs.h index 52f15cb85a..afeb5f1d77 100644 --- a/esphome/components/cc1101/cc1101defs.h +++ b/esphome/components/cc1101/cc1101defs.h @@ -4,7 +4,7 @@ namespace esphome::cc1101 { -static constexpr float XTAL_FREQUENCY = 26000; +static constexpr float XTAL_FREQUENCY = 26000000; static constexpr uint8_t BUS_BURST = 0x40; static constexpr uint8_t BUS_READ = 0x80; diff --git a/tests/components/cc1101/common.yaml b/tests/components/cc1101/common.yaml index 7fd265ca4a..9373ca43e1 100644 --- a/tests/components/cc1101/common.yaml +++ b/tests/components/cc1101/common.yaml @@ -1,11 +1,11 @@ cc1101: id: transceiver cs_pin: ${cs_pin} - frequency: 433920 - if_frequency: 153 - filter_bandwidth: 203 + frequency: 433.92MHz + if_frequency: 153kHz + filter_bandwidth: 203kHz channel: 0 - channel_spacing: 200 + channel_spacing: 200kHz symbol_rate: 5000 modulation_type: ASK/OOK From 7eae0a49725c00fe68dfca20f6648b432a6611a6 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sun, 7 Dec 2025 00:46:39 +1100 Subject: [PATCH 0540/1145] [image] Add USE_IMAGE in defines.h (#12317) --- esphome/core/defines.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 358334d7b3..eea92f77ac 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -46,6 +46,7 @@ #define USE_GRAPHICAL_DISPLAY_MENU #define USE_HOMEASSISTANT_TIME #define USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT 8000 // NOLINT +#define USE_IMAGE #define USE_IMPROV_SERIAL_NEXT_URL #define USE_JSON #define USE_LIGHT From 3c7d6b7fc64cb5037d56434b847bf922a6b9861c Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sun, 7 Dec 2025 00:49:23 +1100 Subject: [PATCH 0541/1145] [ci-custom] Fix after switch from string to path (#12314) --- script/ci-custom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/ci-custom.py b/script/ci-custom.py index 106aa438fe..609d89403f 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -554,10 +554,10 @@ def convert_path_to_relative(abspath, current): "esphome/components/web_server/__init__.py", ], ) -def lint_relative_py_import(fname, line, col, content): +def lint_relative_py_import(fname: Path, line, col, content): import_line = content.splitlines()[line] abspath = import_line[col:].split(" ")[0] - current = fname.removesuffix(".py").replace(os.path.sep, ".") + current = str(fname).removesuffix(".py").replace(os.path.sep, ".") replacement = convert_path_to_relative(abspath, current) newline = import_line.replace(abspath, replacement) return ( From 75c41b11d19b22320e6c84e1423a7c13964e2485 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sun, 7 Dec 2025 01:49:15 +1100 Subject: [PATCH 0542/1145] [lvgl] Number saves value on interactive change (#12315) --- esphome/components/lvgl/number/lvgl_number.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/lvgl/number/lvgl_number.h b/esphome/components/lvgl/number/lvgl_number.h index 7bc44c9e20..d9885bc7fb 100644 --- a/esphome/components/lvgl/number/lvgl_number.h +++ b/esphome/components/lvgl/number/lvgl_number.h @@ -29,15 +29,18 @@ class LVGLNumber : public number::Number, public Component { this->publish_state(value); } - void on_value() { this->publish_state(this->value_lambda_()); } + void on_value() { this->publish_(this->value_lambda_()); } protected: - void control(float value) override { - this->control_lambda_(value); + void publish_(float value) { this->publish_state(value); if (this->restore_) this->pref_.save(&value); } + void control(float value) override { + this->control_lambda_(value); + this->publish_(value); + } std::function control_lambda_; std::function value_lambda_; lv_event_code_t event_; From f20aaf398162cf9c19cf99ad7a22343ff04bef5e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sun, 7 Dec 2025 04:47:57 +1300 Subject: [PATCH 0543/1145] [api] Device defined action responses (#12136) Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- esphome/components/api/__init__.py | 241 ++++++++++++++-- esphome/components/api/api.proto | 24 ++ esphome/components/api/api_connection.cpp | 42 +++ esphome/components/api/api_connection.h | 7 + esphome/components/api/api_pb2.cpp | 37 +++ esphome/components/api/api_pb2.h | 43 ++- esphome/components/api/api_pb2_dump.cpp | 34 +++ esphome/components/api/api_server.cpp | 84 +++++- esphome/components/api/api_server.h | 33 ++- esphome/components/api/custom_api_device.h | 5 +- esphome/components/api/list_entities.cpp | 3 + esphome/components/api/user_services.h | 212 ++++++++++++-- esphome/core/defines.h | 2 + requirements.txt | 2 +- tests/components/api/common-base.yaml | 93 +++++++ tests/integration/README.md | 2 +- .../fixtures/api_action_responses.yaml | 93 +++++++ .../fixtures/api_action_timeout.yaml | 45 +++ .../integration/test_api_action_responses.py | 258 ++++++++++++++++++ tests/integration/test_api_action_timeout.py | 172 ++++++++++++ .../test_api_conditional_memory.py | 4 +- tests/integration/test_api_custom_services.py | 12 +- tests/integration/test_api_homeassistant.py | 2 +- tests/integration/test_api_string_lambda.py | 10 +- .../test_automation_wait_actions.py | 6 +- tests/integration/test_automations.py | 4 +- .../integration/test_continuation_actions.py | 18 +- .../test_scheduler_bulk_cleanup.py | 2 +- .../test_scheduler_defer_cancel.py | 2 +- .../test_scheduler_defer_cancel_regular.py | 2 +- .../test_scheduler_defer_fifo_simple.py | 4 +- .../test_scheduler_defer_stress.py | 2 +- .../integration/test_scheduler_heap_stress.py | 2 +- tests/integration/test_scheduler_null_name.py | 2 +- tests/integration/test_scheduler_pool.py | 16 +- .../test_scheduler_rapid_cancellation.py | 2 +- .../test_scheduler_recursive_timeout.py | 2 +- .../test_scheduler_removed_item_race.py | 2 +- .../test_scheduler_simultaneous_callbacks.py | 2 +- .../test_scheduler_string_lifetime.py | 12 +- .../test_scheduler_string_name_stress.py | 2 +- tests/integration/test_script_delay_params.py | 2 +- tests/integration/test_script_queued.py | 10 +- .../test_wait_until_mid_loop_timing.py | 2 +- tests/integration/test_wait_until_on_boot.py | 2 +- tests/integration/test_wait_until_ordering.py | 2 +- 46 files changed, 1455 insertions(+), 105 deletions(-) create mode 100644 tests/integration/fixtures/api_action_responses.yaml create mode 100644 tests/integration/fixtures/api_action_timeout.yaml create mode 100644 tests/integration/test_api_action_responses.py create mode 100644 tests/integration/test_api_action_timeout.py diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 2910643dfb..d349cf3867 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -27,12 +27,13 @@ from esphome.const import ( CONF_SERVICE, CONF_SERVICES, CONF_TAG, + CONF_THEN, CONF_TRIGGER_ID, CONF_VARIABLES, ) -from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority -from esphome.cpp_generator import TemplateArgsType -from esphome.types import ConfigType +from esphome.core import CORE, ID, CoroPriority, EsphomeError, coroutine_with_priority +from esphome.cpp_generator import MockObj, TemplateArgsType +from esphome.types import ConfigFragmentType, ConfigType _LOGGER = logging.getLogger(__name__) @@ -63,17 +64,21 @@ HomeAssistantActionResponseTrigger = api_ns.class_( "HomeAssistantActionResponseTrigger", automation.Trigger ) APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition) +APIRespondAction = api_ns.class_("APIRespondAction", automation.Action) +APIUnregisterServiceCallAction = api_ns.class_( + "APIUnregisterServiceCallAction", automation.Action +) UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger) ListEntitiesServicesArgument = api_ns.class_("ListEntitiesServicesArgument") -SERVICE_ARG_NATIVE_TYPES = { - "bool": bool, +SERVICE_ARG_NATIVE_TYPES: dict[str, MockObj] = { + "bool": cg.bool_, "int": cg.int32, - "float": float, + "float": cg.float_, "string": cg.std_string, - "bool[]": cg.FixedVector.template(bool).operator("const").operator("ref"), + "bool[]": cg.FixedVector.template(cg.bool_).operator("const").operator("ref"), "int[]": cg.FixedVector.template(cg.int32).operator("const").operator("ref"), - "float[]": cg.FixedVector.template(float).operator("const").operator("ref"), + "float[]": cg.FixedVector.template(cg.float_).operator("const").operator("ref"), "string[]": cg.FixedVector.template(cg.std_string) .operator("const") .operator("ref"), @@ -102,6 +107,85 @@ def validate_encryption_key(value): return value +CONF_SUPPORTS_RESPONSE = "supports_response" + +# Enum values in api::enums namespace +enums_ns = api_ns.namespace("enums") +SUPPORTS_RESPONSE_OPTIONS = { + "none": enums_ns.SUPPORTS_RESPONSE_NONE, + "optional": enums_ns.SUPPORTS_RESPONSE_OPTIONAL, + "only": enums_ns.SUPPORTS_RESPONSE_ONLY, + "status": enums_ns.SUPPORTS_RESPONSE_STATUS, +} + + +def _auto_detect_supports_response(config: ConfigType) -> ConfigType: + """Auto-detect supports_response based on api.respond usage in the action's then block. + + - If api.respond with data found: set to "optional" (unless user explicitly set) + - If api.respond without data found: set to "status" (unless user explicitly set) + - If no api.respond found: set to "none" (unless user explicitly set) + """ + + def scan_actions(items: ConfigFragmentType) -> tuple[bool, bool]: + """Recursively scan actions for api.respond. + + Returns: (found, has_data) tuple - has_data is True if ANY api.respond has data + """ + found_any = False + has_data_any = False + + if isinstance(items, list): + for item in items: + found, has_data = scan_actions(item) + if found: + found_any = True + has_data_any = has_data_any or has_data + elif isinstance(items, dict): + # Check if this is an api.respond action + if "api.respond" in items: + respond_config = items["api.respond"] + has_data = isinstance(respond_config, dict) and "data" in respond_config + return True, has_data + # Recursively check all values + for value in items.values(): + found, has_data = scan_actions(value) + if found: + found_any = True + has_data_any = has_data_any or has_data + + return found_any, has_data_any + + then = config.get(CONF_THEN, []) + action_name = config.get(CONF_ACTION) + found, has_data = scan_actions(then) + + # If user explicitly set supports_response, validate and use that + if CONF_SUPPORTS_RESPONSE in config: + user_value = config[CONF_SUPPORTS_RESPONSE] + # Validate: "only" requires api.respond with data + if user_value == "only" and not has_data: + raise cv.Invalid( + f"Action '{action_name}' has supports_response=only but no api.respond " + "action with 'data:' was found. Use 'status' for responses without data, " + "or add 'data:' to your api.respond action." + ) + return config + + # Auto-detect based on api.respond usage + if found: + config[CONF_SUPPORTS_RESPONSE] = "optional" if has_data else "status" + else: + config[CONF_SUPPORTS_RESPONSE] = "none" + + return config + + +def _validate_supports_response(value): + """Validate supports_response after auto-detection has set the value.""" + return cv.enum(SUPPORTS_RESPONSE_OPTIONS, lower=True)(value) + + ACTIONS_SCHEMA = automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger), @@ -112,10 +196,20 @@ ACTIONS_SCHEMA = automation.validate_automation( cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True), } ), + # No default - auto-detected by _auto_detect_supports_response + cv.Optional(CONF_SUPPORTS_RESPONSE): cv.enum( + SUPPORTS_RESPONSE_OPTIONS, lower=True + ), }, cv.All( cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION), cv.rename_key(CONF_SERVICE, CONF_ACTION), + _auto_detect_supports_response, + # Re-validate supports_response after auto-detection sets it + cv.Schema( + {cv.Required(CONF_SUPPORTS_RESPONSE): _validate_supports_response}, + extra=cv.ALLOW_EXTRA, + ), ), ) @@ -242,7 +336,7 @@ CONFIG_SCHEMA = cv.All( @coroutine_with_priority(CoroPriority.WEB) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) @@ -279,20 +373,61 @@ async def to_code(config): # Collect all triggers first, then register all at once with initializer_list triggers: list[cg.Pvariable] = [] for conf in actions: - template_args = [] - func_args = [] - service_arg_names = [] + func_args: list[tuple[MockObj, str]] = [] + service_template_args: list[MockObj] = [] # User service argument types + + # Determine supports_response mode + # cv.enum returns the key with enum_value attribute containing the MockObj + supports_response_key = conf[CONF_SUPPORTS_RESPONSE] + supports_response = supports_response_key.enum_value + is_none = supports_response_key == "none" + is_optional = supports_response_key == "optional" + + # Add call_id and return_response based on supports_response mode + # These must match the C++ Trigger template arguments + # - none: no extra args + # - status: call_id only (for reporting success/error without data) + # - only: call_id only (response always expected with data) + # - optional: call_id + return_response (client decides) + if not is_none: + # call_id is present for "optional", "only", and "status" + func_args.append((cg.uint32, "call_id")) + # return_response only present for "optional" + if is_optional: + func_args.append((cg.bool_, "return_response")) + + service_arg_names: list[str] = [] for name, var_ in conf[CONF_VARIABLES].items(): native = SERVICE_ARG_NATIVE_TYPES[var_] - template_args.append(native) + service_template_args.append(native) func_args.append((native, name)) service_arg_names.append(name) - templ = cg.TemplateArguments(*template_args) + # Template args: supports_response mode, then user service arg types + templ = cg.TemplateArguments(supports_response, *service_template_args) trigger = cg.new_Pvariable( - conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names + conf[CONF_TRIGGER_ID], + templ, + conf[CONF_ACTION], + service_arg_names, ) triggers.append(trigger) - await automation.build_automation(trigger, func_args, conf) + auto = await automation.build_automation(trigger, func_args, conf) + + # For non-none response modes, automatically append unregister action + # This ensures the call is unregistered after all actions complete (including async ones) + if not is_none: + arg_types = [arg[0] for arg in func_args] + action_templ = cg.TemplateArguments(*arg_types) + unregister_id = ID( + f"{conf[CONF_TRIGGER_ID]}__unregister", + is_declaration=True, + type=APIUnregisterServiceCallAction.template(action_templ), + ) + unregister_action = cg.new_Pvariable( + unregister_id, + var, + ) + cg.add(auto.add_actions([unregister_action])) # Register all services at once - single allocation, no reallocations cg.add(var.initialize_user_services(triggers)) @@ -538,6 +673,80 @@ async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, arg return var +CONF_SUCCESS = "success" +CONF_ERROR_MESSAGE = "error_message" + + +def _validate_api_respond_data(config): + """Set flag during validation so AUTO_LOAD can include json component.""" + if CONF_DATA in config: + CORE.data.setdefault(DOMAIN, {})[CONF_CAPTURE_RESPONSE] = True + return config + + +API_RESPOND_ACTION_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.use_id(APIServer), + cv.Optional(CONF_SUCCESS, default=True): cv.templatable(cv.boolean), + cv.Optional(CONF_ERROR_MESSAGE, default=""): cv.templatable(cv.string), + cv.Optional(CONF_DATA): cv.lambda_, + } + ), + _validate_api_respond_data, +) + + +@automation.register_action( + "api.respond", + APIRespondAction, + API_RESPOND_ACTION_SCHEMA, +) +async def api_respond_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: + # Validate that api.respond is used inside an API action context. + # We can't easily validate this at config time since the schema validation + # doesn't have access to the parent action context. Validating here in to_code + # is still much better than a cryptic C++ compile error. + has_call_id = any(name == "call_id" for _, name in args) + if not has_call_id: + raise EsphomeError( + "api.respond can only be used inside an API action's 'then:' block. " + "The 'call_id' variable is required to send a response." + ) + + cg.add_define("USE_API_USER_DEFINED_ACTION_RESPONSES") + serv = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, serv) + + # Check if we're in optional mode (has return_response arg) + is_optional = any(name == "return_response" for _, name in args) + if is_optional: + cg.add(var.set_is_optional_mode(True)) + + templ = await cg.templatable(config[CONF_SUCCESS], args, cg.bool_) + cg.add(var.set_success(templ)) + + templ = await cg.templatable(config[CONF_ERROR_MESSAGE], args, cg.std_string) + cg.add(var.set_error_message(templ)) + + if CONF_DATA in config: + cg.add_define("USE_API_USER_DEFINED_ACTION_RESPONSES_JSON") + # Lambda populates the JsonObject root - no return value needed + lambda_ = await cg.process_lambda( + config[CONF_DATA], + args + [(cg.JsonObject, "root")], + return_type=cg.void, + ) + cg.add(var.set_data(lambda_)) + + return var + + API_CONNECTED_CONDITION_SCHEMA = cv.Schema( { cv.GenerateID(): cv.use_id(APIServer), diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 5450c2536c..3fc2e1fed8 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -855,6 +855,14 @@ enum ServiceArgType { SERVICE_ARG_TYPE_FLOAT_ARRAY = 6; SERVICE_ARG_TYPE_STRING_ARRAY = 7; } +enum SupportsResponseType { + SUPPORTS_RESPONSE_NONE = 0; + SUPPORTS_RESPONSE_OPTIONAL = 1; + SUPPORTS_RESPONSE_ONLY = 2; + // Status-only response - reports success/error without data payload + // Value is higher to avoid conflicts with future Home Assistant values + SUPPORTS_RESPONSE_STATUS = 100; +} message ListEntitiesServicesArgument { option (ifdef) = "USE_API_USER_DEFINED_ACTIONS"; string name = 1; @@ -868,6 +876,7 @@ message ListEntitiesServicesResponse { string name = 1; fixed32 key = 2; repeated ListEntitiesServicesArgument args = 3 [(fixed_vector) = true]; + SupportsResponseType supports_response = 4; } message ExecuteServiceArgument { option (ifdef) = "USE_API_USER_DEFINED_ACTIONS"; @@ -890,6 +899,21 @@ message ExecuteServiceRequest { fixed32 key = 1; repeated ExecuteServiceArgument args = 2 [(fixed_vector) = true]; + uint32 call_id = 3 [(field_ifdef) = "USE_API_USER_DEFINED_ACTION_RESPONSES"]; + bool return_response = 4 [(field_ifdef) = "USE_API_USER_DEFINED_ACTION_RESPONSES"]; +} + +// Message sent by ESPHome to Home Assistant with service execution response data +message ExecuteServiceResponse { + option (id) = 131; + option (source) = SOURCE_SERVER; + option (no_delay) = true; + option (ifdef) = "USE_API_USER_DEFINED_ACTION_RESPONSES"; + + uint32 call_id = 1; // Matches the call_id from ExecuteServiceRequest + bool success = 2; // Whether the service execution succeeded + string error_message = 3; // Error message if success = false + bytes response_data = 4 [(pointer_to_buffer) = true, (field_ifdef) = "USE_API_USER_DEFINED_ACTION_RESPONSES_JSON"]; } // ==================== CAMERA ==================== diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 31f90d9474..f0428546de 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -6,6 +6,9 @@ #ifdef USE_API_PLAINTEXT #include "api_frame_helper_plaintext.h" #endif +#ifdef USE_API_USER_DEFINED_ACTIONS +#include "user_services.h" +#endif #include #include #include @@ -1554,15 +1557,54 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes #ifdef USE_API_USER_DEFINED_ACTIONS void APIConnection::execute_service(const ExecuteServiceRequest &msg) { bool found = false; +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + // Register the call and get a unique server-generated action_call_id + // This avoids collisions when multiple clients use the same call_id + uint32_t action_call_id = 0; + if (msg.call_id != 0) { + action_call_id = this->parent_->register_active_action_call(msg.call_id, this); + } + // Use the overload that passes action_call_id separately (avoids copying msg) + for (auto *service : this->parent_->get_user_services()) { + if (service->execute_service(msg, action_call_id)) { + found = true; + } + } +#else for (auto *service : this->parent_->get_user_services()) { if (service->execute_service(msg)) { found = true; } } +#endif if (!found) { ESP_LOGV(TAG, "Could not find service"); } + // Note: For services with supports_response != none, the call is unregistered + // by an automatically appended APIUnregisterServiceCallAction at the end of + // the action list. This ensures async actions (delays, waits) complete first. } +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES +void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message) { + ExecuteServiceResponse resp; + resp.call_id = call_id; + resp.success = success; + resp.set_error_message(StringRef(error_message)); + this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE); +} +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON +void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message, + const uint8_t *response_data, size_t response_data_len) { + ExecuteServiceResponse resp; + resp.call_id = call_id; + resp.success = success; + resp.set_error_message(StringRef(error_message)); + resp.response_data = response_data; + resp.response_data_len = response_data_len; + this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE); +} +#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON +#endif // USE_API_USER_DEFINED_ACTION_RESPONSES #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 6bf4f45a5c..b50be5d0d4 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -223,6 +223,13 @@ class APIConnection final : public APIServerConnection { #endif #ifdef USE_API_USER_DEFINED_ACTIONS void execute_service(const ExecuteServiceRequest &msg) override; +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message); +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON + void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message, + const uint8_t *response_data, size_t response_data_len); +#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON +#endif // USE_API_USER_DEFINED_ACTION_RESPONSES #endif #ifdef USE_API_NOISE bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) override; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index c131815456..a3da6591f4 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1010,11 +1010,13 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->args) { buffer.encode_message(3, it, true); } + buffer.encode_uint32(4, static_cast(this->supports_response)); } void ListEntitiesServicesResponse::calculate_size(ProtoSize &size) const { size.add_length(1, this->name_ref_.size()); size.add_fixed32(1, this->key); size.add_repeated_message(1, this->args); + size.add_uint32(1, static_cast(this->supports_response)); } bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -1075,6 +1077,23 @@ void ExecuteServiceArgument::decode(const uint8_t *buffer, size_t length) { this->string_array.init(count_string_array); ProtoDecodableMessage::decode(buffer, length); } +bool ExecuteServiceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + case 3: + this->call_id = value.as_uint32(); + break; +#endif +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + case 4: + this->return_response = value.as_bool(); + break; +#endif + default: + return false; + } + return true; +} bool ExecuteServiceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: @@ -1102,6 +1121,24 @@ void ExecuteServiceRequest::decode(const uint8_t *buffer, size_t length) { ProtoDecodableMessage::decode(buffer, length); } #endif +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES +void ExecuteServiceResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint32(1, this->call_id); + buffer.encode_bool(2, this->success); + buffer.encode_string(3, this->error_message_ref_); +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON + buffer.encode_bytes(4, this->response_data, this->response_data_len); +#endif +} +void ExecuteServiceResponse::calculate_size(ProtoSize &size) const { + size.add_uint32(1, this->call_id); + size.add_bool(1, this->success); + size.add_length(1, this->error_message_ref_.size()); +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON + size.add_length(4, this->response_data_len); +#endif +} +#endif #ifdef USE_CAMERA void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id_ref_); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 74d3834bf5..7e41cd8a22 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -75,6 +75,12 @@ enum ServiceArgType : uint32_t { SERVICE_ARG_TYPE_FLOAT_ARRAY = 6, SERVICE_ARG_TYPE_STRING_ARRAY = 7, }; +enum SupportsResponseType : uint32_t { + SUPPORTS_RESPONSE_NONE = 0, + SUPPORTS_RESPONSE_OPTIONAL = 1, + SUPPORTS_RESPONSE_ONLY = 2, + SUPPORTS_RESPONSE_STATUS = 100, +}; #endif #ifdef USE_CLIMATE enum ClimateMode : uint32_t { @@ -1257,7 +1263,7 @@ class ListEntitiesServicesArgument final : public ProtoMessage { class ListEntitiesServicesResponse final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 41; - static constexpr uint8_t ESTIMATED_SIZE = 48; + static constexpr uint8_t ESTIMATED_SIZE = 50; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_services_response"; } #endif @@ -1265,6 +1271,7 @@ class ListEntitiesServicesResponse final : public ProtoMessage { void set_name(const StringRef &ref) { this->name_ref_ = ref; } uint32_t key{0}; FixedVector args{}; + enums::SupportsResponseType supports_response{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1297,12 +1304,18 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage { class ExecuteServiceRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 42; - static constexpr uint8_t ESTIMATED_SIZE = 39; + static constexpr uint8_t ESTIMATED_SIZE = 45; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "execute_service_request"; } #endif uint32_t key{0}; FixedVector args{}; +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + uint32_t call_id{0}; +#endif +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + bool return_response{false}; +#endif void decode(const uint8_t *buffer, size_t length) override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1311,6 +1324,32 @@ class ExecuteServiceRequest final : public ProtoDecodableMessage { protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +#endif +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES +class ExecuteServiceResponse final : public ProtoMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 131; + static constexpr uint8_t ESTIMATED_SIZE = 34; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "execute_service_response"; } +#endif + uint32_t call_id{0}; + bool success{false}; + StringRef error_message_ref_{}; + void set_error_message(const StringRef &ref) { this->error_message_ref_ = ref; } +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON + const uint8_t *response_data{nullptr}; + uint16_t response_data_len{0}; +#endif + void encode(ProtoWriteBuffer buffer) const override; + void calculate_size(ProtoSize &size) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: }; #endif #ifdef USE_CAMERA diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index bea7fc53c4..59fc1367fe 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -231,6 +231,20 @@ template<> const char *proto_enum_to_string(enums::Servic return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::SupportsResponseType value) { + switch (value) { + case enums::SUPPORTS_RESPONSE_NONE: + return "SUPPORTS_RESPONSE_NONE"; + case enums::SUPPORTS_RESPONSE_OPTIONAL: + return "SUPPORTS_RESPONSE_OPTIONAL"; + case enums::SUPPORTS_RESPONSE_ONLY: + return "SUPPORTS_RESPONSE_ONLY"; + case enums::SUPPORTS_RESPONSE_STATUS: + return "SUPPORTS_RESPONSE_STATUS"; + default: + return "UNKNOWN"; + } +} #endif #ifdef USE_CLIMATE template<> const char *proto_enum_to_string(enums::ClimateMode value) { @@ -1194,6 +1208,7 @@ void ListEntitiesServicesResponse::dump_to(std::string &out) const { it.dump_to(out); out.append("\n"); } + dump_field(out, "supports_response", static_cast(this->supports_response)); } void ExecuteServiceArgument::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ExecuteServiceArgument"); @@ -1223,6 +1238,25 @@ void ExecuteServiceRequest::dump_to(std::string &out) const { it.dump_to(out); out.append("\n"); } +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + dump_field(out, "call_id", this->call_id); +#endif +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + dump_field(out, "return_response", this->return_response); +#endif +} +#endif +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES +void ExecuteServiceResponse::dump_to(std::string &out) const { + MessageDumpHelper helper(out, "ExecuteServiceResponse"); + dump_field(out, "call_id", this->call_id); + dump_field(out, "success", this->success); + dump_field(out, "error_message", this->error_message_ref_); +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON + out.append(" response_data: "); + out.append(format_hex_pretty(this->response_data, this->response_data_len)); + out.append("\n"); +#endif } #endif #ifdef USE_CAMERA diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 565714a4e5..1921ca95d4 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -4,8 +4,8 @@ #include "api_connection.h" #include "esphome/components/network/util.h" #include "esphome/core/application.h" -#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/util.h" @@ -186,6 +186,9 @@ void APIServer::loop() { // Rare case: handle disconnection #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER this->client_disconnected_trigger_->trigger(client->client_info_.name, client->client_info_.peername); +#endif +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + this->unregister_active_action_calls_for_connection(client.get()); #endif ESP_LOGV(TAG, "Remove connection %s", client->client_info_.name.c_str()); @@ -585,5 +588,84 @@ bool APIServer::teardown() { return this->clients_.empty(); } +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES +// Timeout for action calls - matches aioesphomeapi client timeout (default 30s) +// Can be overridden via USE_API_ACTION_CALL_TIMEOUT_MS define for testing +#ifndef USE_API_ACTION_CALL_TIMEOUT_MS +#define USE_API_ACTION_CALL_TIMEOUT_MS 30000 // NOLINT +#endif + +uint32_t APIServer::register_active_action_call(uint32_t client_call_id, APIConnection *conn) { + uint32_t action_call_id = this->next_action_call_id_++; + // Handle wraparound (skip 0 as it means "no call") + if (this->next_action_call_id_ == 0) { + this->next_action_call_id_ = 1; + } + this->active_action_calls_.push_back({action_call_id, client_call_id, conn}); + + // Schedule automatic cleanup after timeout (client will have given up by then) + this->set_timeout(str_sprintf("action_call_%u", action_call_id), USE_API_ACTION_CALL_TIMEOUT_MS, + [this, action_call_id]() { + ESP_LOGD(TAG, "Action call %u timed out", action_call_id); + this->unregister_active_action_call(action_call_id); + }); + + return action_call_id; +} + +void APIServer::unregister_active_action_call(uint32_t action_call_id) { + // Cancel the timeout for this action call + this->cancel_timeout(str_sprintf("action_call_%u", action_call_id)); + + // Swap-and-pop is more efficient than remove_if for unordered vectors + for (size_t i = 0; i < this->active_action_calls_.size(); i++) { + if (this->active_action_calls_[i].action_call_id == action_call_id) { + std::swap(this->active_action_calls_[i], this->active_action_calls_.back()); + this->active_action_calls_.pop_back(); + return; + } + } +} + +void APIServer::unregister_active_action_calls_for_connection(APIConnection *conn) { + // Remove all active action calls for disconnected connection using swap-and-pop + for (size_t i = 0; i < this->active_action_calls_.size();) { + if (this->active_action_calls_[i].connection == conn) { + // Cancel the timeout for this action call + this->cancel_timeout(str_sprintf("action_call_%u", this->active_action_calls_[i].action_call_id)); + + std::swap(this->active_action_calls_[i], this->active_action_calls_.back()); + this->active_action_calls_.pop_back(); + // Don't increment i - need to check the swapped element + } else { + i++; + } + } +} + +void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message) { + for (auto &call : this->active_action_calls_) { + if (call.action_call_id == action_call_id) { + call.connection->send_execute_service_response(call.client_call_id, success, error_message); + return; + } + } + ESP_LOGW(TAG, "Cannot send response: no active call found for action_call_id %u", action_call_id); +} +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON +void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message, + const uint8_t *response_data, size_t response_data_len) { + for (auto &call : this->active_action_calls_) { + if (call.action_call_id == action_call_id) { + call.connection->send_execute_service_response(call.client_call_id, success, error_message, response_data, + response_data_len); + return; + } + } + ESP_LOGW(TAG, "Cannot send response: no active call found for action_call_id %u", action_call_id); +} +#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON +#endif // USE_API_USER_DEFINED_ACTION_RESPONSES + } // namespace esphome::api #endif diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index eb495afde7..2175d047eb 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -12,9 +12,6 @@ #include "esphome/core/log.h" #include "list_entities.h" #include "subscribe_state.h" -#ifdef USE_API_USER_DEFINED_ACTIONS -#include "user_services.h" -#endif #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" #endif @@ -22,11 +19,15 @@ #include "esphome/components/camera/camera.h" #endif -#include #include namespace esphome::api { +#ifdef USE_API_USER_DEFINED_ACTIONS +// Forward declaration - full definition in user_services.h +class UserServiceDescriptor; +#endif + #ifdef USE_API_NOISE struct SavedNoisePsk { psk_t psk; @@ -154,6 +155,19 @@ class APIServer : public Component, // Only compile push_back method when custom_services: true (external components) void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } #endif +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + // Action call context management - supports concurrent calls from multiple clients + // Returns server-generated action_call_id to avoid collisions when clients use same call_id + uint32_t register_active_action_call(uint32_t client_call_id, APIConnection *conn); + void unregister_active_action_call(uint32_t action_call_id); + void unregister_active_action_calls_for_connection(APIConnection *conn); + // Send response for a specific action call (uses action_call_id, sends client_call_id in response) + void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message); +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON + void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message, + const uint8_t *response_data, size_t response_data_len); +#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON +#endif // USE_API_USER_DEFINED_ACTION_RESPONSES #endif #ifdef USE_HOMEASSISTANT_TIME void request_time(); @@ -230,6 +244,17 @@ class APIServer : public Component, #endif #ifdef USE_API_USER_DEFINED_ACTIONS std::vector user_services_; +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + // Active action calls - supports concurrent calls from multiple clients + // Uses server-generated action_call_id to avoid collisions when multiple clients use same call_id + struct ActiveActionCall { + uint32_t action_call_id; // Server-generated unique ID (passed to actions) + uint32_t client_call_id; // Client's original call_id (used in response) + APIConnection *connection; + }; + std::vector active_action_calls_; + uint32_t next_action_call_id_{1}; // Counter for generating unique action_call_ids +#endif // USE_API_USER_DEFINED_ACTION_RESPONSES #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES struct PendingActionResponse { diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index 1006d07533..5e9165326d 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -16,7 +16,10 @@ template class CustomAPIDeviceService : public UserS : UserServiceDynamic(name, arg_names), obj_(obj), callback_(callback) {} protected: - void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT + // CustomAPIDevice services don't support action responses - ignore call_id and return_response + void execute(uint32_t /*call_id*/, bool /*return_response*/, Ts... x) override { + (this->obj_->*this->callback_)(x...); // NOLINT + } T *obj_; void (T::*callback_)(Ts...); diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index e18fc17801..b4d1454153 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -5,6 +5,9 @@ #include "esphome/core/application.h" #include "esphome/core/log.h" #include "esphome/core/util.h" +#ifdef USE_API_USER_DEFINED_ACTIONS +#include "user_services.h" +#endif namespace esphome::api { diff --git a/esphome/components/api/user_services.h b/esphome/components/api/user_services.h index d9c13c520b..001add626f 100644 --- a/esphome/components/api/user_services.h +++ b/esphome/components/api/user_services.h @@ -1,20 +1,31 @@ #pragma once +#include #include #include -#include "esphome/core/component.h" -#include "esphome/core/automation.h" #include "api_pb2.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON +#include "esphome/components/json/json_util.h" +#endif #ifdef USE_API_USER_DEFINED_ACTIONS namespace esphome::api { +// Forward declaration - full definition in api_server.h +class APIServer; + class UserServiceDescriptor { public: virtual ListEntitiesServicesResponse encode_list_service_response() = 0; virtual bool execute_service(const ExecuteServiceRequest &req) = 0; +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + // Overload that accepts server-generated action_call_id (avoids client call_id collisions) + virtual bool execute_service(const ExecuteServiceRequest &req, uint32_t action_call_id) = 0; +#endif bool is_internal() { return false; } }; @@ -27,8 +38,9 @@ template enums::ServiceArgType to_service_arg_type(); // Stores only pointers to string literals in flash - no heap allocation template class UserServiceBase : public UserServiceDescriptor { public: - UserServiceBase(const char *name, const std::array &arg_names) - : name_(name), arg_names_(arg_names) { + UserServiceBase(const char *name, const std::array &arg_names, + enums::SupportsResponseType supports_response = enums::SUPPORTS_RESPONSE_NONE) + : name_(name), arg_names_(arg_names), supports_response_(supports_response) { this->key_ = fnv1_hash(name); } @@ -36,6 +48,7 @@ template class UserServiceBase : public UserServiceDescriptor { ListEntitiesServicesResponse msg; msg.set_name(StringRef(this->name_)); msg.key = this->key_; + msg.supports_response = this->supports_response_; std::array arg_types = {to_service_arg_type()...}; msg.args.init(sizeof...(Ts)); for (size_t i = 0; i < sizeof...(Ts); i++) { @@ -51,21 +64,37 @@ template class UserServiceBase : public UserServiceDescriptor { return false; if (req.args.size() != sizeof...(Ts)) return false; - this->execute_(req.args, std::make_index_sequence{}); +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + this->execute_(req.args, req.call_id, req.return_response, std::make_index_sequence{}); +#else + this->execute_(req.args, 0, false, std::make_index_sequence{}); +#endif return true; } +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + bool execute_service(const ExecuteServiceRequest &req, uint32_t action_call_id) override { + if (req.key != this->key_) + return false; + if (req.args.size() != sizeof...(Ts)) + return false; + this->execute_(req.args, action_call_id, req.return_response, std::make_index_sequence{}); + return true; + } +#endif + protected: - virtual void execute(Ts... x) = 0; + virtual void execute(uint32_t call_id, bool return_response, Ts... x) = 0; template - void execute_(const ArgsContainer &args, std::index_sequence type) { - this->execute((get_execute_arg_value(args[S]))...); + void execute_(const ArgsContainer &args, uint32_t call_id, bool return_response, std::index_sequence /*type*/) { + this->execute(call_id, return_response, (get_execute_arg_value(args[S]))...); } // Pointers to string literals in flash - no heap allocation const char *name_; std::array arg_names_; uint32_t key_{0}; + enums::SupportsResponseType supports_response_{enums::SUPPORTS_RESPONSE_NONE}; }; // Separate class for custom_api_device services (rare case) @@ -81,6 +110,7 @@ template class UserServiceDynamic : public UserServiceDescriptor ListEntitiesServicesResponse msg; msg.set_name(StringRef(this->name_)); msg.key = this->key_; + msg.supports_response = enums::SUPPORTS_RESPONSE_NONE; // Dynamic services don't support responses yet std::array arg_types = {to_service_arg_type()...}; msg.args.init(sizeof...(Ts)); for (size_t i = 0; i < sizeof...(Ts); i++) { @@ -96,15 +126,31 @@ template class UserServiceDynamic : public UserServiceDescriptor return false; if (req.args.size() != sizeof...(Ts)) return false; - this->execute_(req.args, std::make_index_sequence{}); +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + this->execute_(req.args, req.call_id, req.return_response, std::make_index_sequence{}); +#else + this->execute_(req.args, 0, false, std::make_index_sequence{}); +#endif return true; } +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + // Dynamic services don't support responses yet, but need to implement the interface + bool execute_service(const ExecuteServiceRequest &req, uint32_t action_call_id) override { + if (req.key != this->key_) + return false; + if (req.args.size() != sizeof...(Ts)) + return false; + this->execute_(req.args, action_call_id, req.return_response, std::make_index_sequence{}); + return true; + } +#endif + protected: - virtual void execute(Ts... x) = 0; + virtual void execute(uint32_t call_id, bool return_response, Ts... x) = 0; template - void execute_(const ArgsContainer &args, std::index_sequence type) { - this->execute((get_execute_arg_value(args[S]))...); + void execute_(const ArgsContainer &args, uint32_t call_id, bool return_response, std::index_sequence /*type*/) { + this->execute(call_id, return_response, (get_execute_arg_value(args[S]))...); } // Heap-allocated strings for runtime-generated names @@ -113,15 +159,149 @@ template class UserServiceDynamic : public UserServiceDescriptor uint32_t key_{0}; }; -template class UserServiceTrigger : public UserServiceBase, public Trigger { +// Primary template declaration +template class UserServiceTrigger; + +// Specialization for NONE - no extra trigger arguments +template +class UserServiceTrigger : public UserServiceBase, public Trigger { public: - // Constructor for static names (YAML-defined services - used by code generator) UserServiceTrigger(const char *name, const std::array &arg_names) - : UserServiceBase(name, arg_names) {} + : UserServiceBase(name, arg_names, enums::SUPPORTS_RESPONSE_NONE) {} protected: - void execute(Ts... x) override { this->trigger(x...); } // NOLINT + void execute(uint32_t /*call_id*/, bool /*return_response*/, Ts... x) override { this->trigger(x...); } +}; + +// Specialization for OPTIONAL - call_id and return_response trigger arguments +template +class UserServiceTrigger : public UserServiceBase, + public Trigger { + public: + UserServiceTrigger(const char *name, const std::array &arg_names) + : UserServiceBase(name, arg_names, enums::SUPPORTS_RESPONSE_OPTIONAL) {} + + protected: + void execute(uint32_t call_id, bool return_response, Ts... x) override { + this->trigger(call_id, return_response, x...); + } +}; + +// Specialization for ONLY - just call_id trigger argument +template +class UserServiceTrigger : public UserServiceBase, + public Trigger { + public: + UserServiceTrigger(const char *name, const std::array &arg_names) + : UserServiceBase(name, arg_names, enums::SUPPORTS_RESPONSE_ONLY) {} + + protected: + void execute(uint32_t call_id, bool /*return_response*/, Ts... x) override { this->trigger(call_id, x...); } +}; + +// Specialization for STATUS - just call_id trigger argument (reports success/error without data) +template +class UserServiceTrigger : public UserServiceBase, + public Trigger { + public: + UserServiceTrigger(const char *name, const std::array &arg_names) + : UserServiceBase(name, arg_names, enums::SUPPORTS_RESPONSE_STATUS) {} + + protected: + void execute(uint32_t call_id, bool /*return_response*/, Ts... x) override { this->trigger(call_id, x...); } }; } // namespace esphome::api #endif // USE_API_USER_DEFINED_ACTIONS + +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES +// Include full definition of APIServer for template implementation +// Must be outside namespace to avoid including STL headers inside namespace +#include "api_server.h" + +namespace esphome::api { + +template class APIRespondAction : public Action { + public: + explicit APIRespondAction(APIServer *parent) : parent_(parent) {} + + template void set_success(V success) { this->success_ = success; } + template void set_error_message(V error) { this->error_message_ = error; } + void set_is_optional_mode(bool is_optional) { this->is_optional_mode_ = is_optional; } + +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON + void set_data(std::function func) { + this->json_builder_ = std::move(func); + this->has_data_ = true; + } +#endif + + void play(const Ts &...x) override { + // Extract call_id from first argument - it's always first for optional/only/status modes + auto args = std::make_tuple(x...); + uint32_t call_id = std::get<0>(args); + + bool success = this->success_.value(x...); + std::string error_message = this->error_message_.value(x...); + +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON + if (this->has_data_) { + // For optional mode, check return_response (second arg) to decide if client wants data + // Use nested if constexpr to avoid compile error when tuple doesn't have enough elements + // (std::tuple_element_t is evaluated before the && short-circuit, so we must nest) + if constexpr (sizeof...(Ts) >= 2) { + if constexpr (std::is_same_v>, bool>) { + if (this->is_optional_mode_) { + bool return_response = std::get<1>(args); + if (!return_response) { + // Client doesn't want response data, just send success/error + this->parent_->send_action_response(call_id, success, error_message); + return; + } + } + } + } + // Build and send JSON response + json::JsonBuilder builder; + this->json_builder_(x..., builder.root()); + std::string json_str = builder.serialize(); + this->parent_->send_action_response(call_id, success, error_message, + reinterpret_cast(json_str.data()), json_str.size()); + return; + } +#endif + this->parent_->send_action_response(call_id, success, error_message); + } + + protected: + APIServer *parent_; + TemplatableValue success_{true}; + TemplatableValue error_message_{""}; +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON + std::function json_builder_; + bool has_data_{false}; +#endif + bool is_optional_mode_{false}; +}; + +// Action to unregister a service call after execution completes +// Automatically appended to the end of action lists for non-none response modes +template class APIUnregisterServiceCallAction : public Action { + public: + explicit APIUnregisterServiceCallAction(APIServer *parent) : parent_(parent) {} + + void play(const Ts &...x) override { + // Extract call_id from first argument - same convention as APIRespondAction + auto args = std::make_tuple(x...); + uint32_t call_id = std::get<0>(args); + if (call_id != 0) { + this->parent_->unregister_active_action_call(call_id); + } + } + + protected: + APIServer *parent_; +}; + +} // namespace esphome::api +#endif // USE_API_USER_DEFINED_ACTION_RESPONSES diff --git a/esphome/core/defines.h b/esphome/core/defines.h index eea92f77ac..021240cc40 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -129,6 +129,8 @@ #define USE_API_PLAINTEXT #define USE_API_USER_DEFINED_ACTIONS #define USE_API_CUSTOM_SERVICES +#define USE_API_USER_DEFINED_ACTION_RESPONSES +#define USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #define API_MAX_SEND_QUEUE 8 #define USE_MD5 #define USE_SHA256 diff --git a/requirements.txt b/requirements.txt index 5d824a6859..0bad48716e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==42.10.0 +aioesphomeapi==43.0.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.16 # dashboard_import diff --git a/tests/components/api/common-base.yaml b/tests/components/api/common-base.yaml index 0416cebf9b..c766b61b13 100644 --- a/tests/components/api/common-base.yaml +++ b/tests/components/api/common-base.yaml @@ -181,6 +181,99 @@ api: else: - logger.log: "Skipped loops" - logger.log: "After combined test" + # ========================================================================== + # supports_response: status (auto-detected - api.respond without data) + # Has call_id only - reports success/error without data payload + # ========================================================================== + - action: test_respond_status + then: + - api.respond: + success: true + - logger.log: + format: "Status response sent (call_id=%d)" + args: [call_id] + + - action: test_respond_status_error + variables: + error_msg: string + then: + - api.respond: + success: false + error_message: !lambda 'return error_msg;' + + # ========================================================================== + # supports_response: optional (auto-detected - api.respond with data) + # Has call_id and return_response - client decides if it wants response + # ========================================================================== + - action: test_respond_optional + variables: + sensor_name: string + value: float + then: + - logger.log: + format: "Optional response (call_id=%d, return_response=%d)" + args: [call_id, return_response] + - api.respond: + data: !lambda |- + root["sensor"] = sensor_name; + root["value"] = value; + root["unit"] = "°C"; + + - action: test_respond_optional_conditional + variables: + do_succeed: bool + then: + - if: + condition: + lambda: 'return do_succeed;' + then: + - api.respond: + success: true + data: !lambda |- + root["status"] = "ok"; + else: + - api.respond: + success: false + error_message: "Operation failed" + + # ========================================================================== + # supports_response: only (explicit - always expects data response) + # Has call_id only - response is always expected with data + # ========================================================================== + - action: test_respond_only + supports_response: only + variables: + input: string + then: + - logger.log: + format: "Only response (call_id=%d)" + args: [call_id] + - api.respond: + data: !lambda |- + root["input"] = input; + root["processed"] = true; + + - action: test_respond_only_nested + supports_response: only + then: + - api.respond: + data: !lambda |- + root["config"]["wifi"] = "connected"; + root["config"]["api"] = true; + root["items"][0] = "item1"; + root["items"][1] = "item2"; + + # ========================================================================== + # supports_response: none (no api.respond action) + # No call_id or return_response - just user variables + # ========================================================================== + - action: test_no_response + variables: + message: string + then: + - logger.log: + format: "No response action: %s" + args: [message.c_str()] event: - platform: template diff --git a/tests/integration/README.md b/tests/integration/README.md index f99139db00..4de08777b0 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -252,7 +252,7 @@ my_service = next((s for s in services if s.name == "my_service"), None) assert my_service is not None # Execute with parameters -client.execute_service(my_service, {"param1": "value1", "param2": 42}) +await client.execute_service(my_service, {"param1": "value1", "param2": 42}) ``` ##### Multiple Entity Tracking diff --git a/tests/integration/fixtures/api_action_responses.yaml b/tests/integration/fixtures/api_action_responses.yaml new file mode 100644 index 0000000000..755623b7bb --- /dev/null +++ b/tests/integration/fixtures/api_action_responses.yaml @@ -0,0 +1,93 @@ +esphome: + name: api-action-responses-test + +host: + +logger: + level: DEBUG + +api: + actions: + # ========================================================================== + # supports_response: none (default - no api.respond action) + # No call_id or return_response - just user variables + # ========================================================================== + - action: action_no_response + variables: + message: string + then: + - logger.log: + format: "ACTION_NO_RESPONSE called with: %s" + args: [message.c_str()] + + # ========================================================================== + # supports_response: status (auto-detected - api.respond without data) + # Has call_id only - reports success/error without data payload + # ========================================================================== + - action: action_status_response + variables: + should_succeed: bool + then: + - if: + condition: + lambda: 'return should_succeed;' + then: + - api.respond: + success: true + - logger.log: + format: "ACTION_STATUS_RESPONSE success (call_id=%d)" + args: [call_id] + else: + - api.respond: + success: false + error_message: "Intentional failure for testing" + - logger.log: + format: "ACTION_STATUS_RESPONSE error (call_id=%d)" + args: [call_id] + + # ========================================================================== + # supports_response: optional (auto-detected - api.respond with data) + # Has call_id and return_response - client decides if it wants response + # ========================================================================== + - action: action_optional_response + variables: + value: int + then: + - logger.log: + format: "ACTION_OPTIONAL_RESPONSE (call_id=%d, return_response=%d, value=%d)" + args: [call_id, return_response, value] + - api.respond: + data: !lambda |- + root["input"] = value; + root["doubled"] = value * 2; + + # ========================================================================== + # supports_response: only (explicit - always expects data response) + # Has call_id only - response is always expected with data + # ========================================================================== + - action: action_only_response + supports_response: only + variables: + name: string + then: + - logger.log: + format: "ACTION_ONLY_RESPONSE (call_id=%d, name=%s)" + args: [call_id, name.c_str()] + - api.respond: + data: !lambda |- + root["greeting"] = "Hello, " + name + "!"; + root["length"] = name.length(); + + # Test action with nested JSON response + - action: action_nested_json + supports_response: only + then: + - logger.log: + format: "ACTION_NESTED_JSON (call_id=%d)" + args: [call_id] + - api.respond: + data: !lambda |- + root["config"]["wifi"]["connected"] = true; + root["config"]["api"]["port"] = 6053; + root["items"][0] = "first"; + root["items"][1] = "second"; diff --git a/tests/integration/fixtures/api_action_timeout.yaml b/tests/integration/fixtures/api_action_timeout.yaml new file mode 100644 index 0000000000..405d9d0e2b --- /dev/null +++ b/tests/integration/fixtures/api_action_timeout.yaml @@ -0,0 +1,45 @@ +esphome: + name: api-action-timeout-test + # Use a short timeout for testing (500ms instead of 30s) + platformio_options: + build_flags: + - "-DUSE_API_ACTION_CALL_TIMEOUT_MS=500" + +host: + +logger: + level: DEBUG + +api: + actions: + # Action that responds immediately - should work fine + - action: action_immediate + supports_response: only + then: + - logger.log: "ACTION_IMMEDIATE responding" + - api.respond: + data: !lambda |- + root["status"] = "immediate"; + + # Action that delays 200ms before responding - should work (within 500ms timeout) + - action: action_short_delay + supports_response: only + then: + - logger.log: "ACTION_SHORT_DELAY starting" + - delay: 200ms + - logger.log: "ACTION_SHORT_DELAY responding" + - api.respond: + data: !lambda |- + root["status"] = "short_delay"; + + # Action that delays 1s before responding - should fail (exceeds 500ms timeout) + # The api.respond will log a warning because the action call was already cleaned up + - action: action_long_delay + supports_response: only + then: + - logger.log: "ACTION_LONG_DELAY starting" + - delay: 1s + - logger.log: "ACTION_LONG_DELAY responding (after timeout)" + - api.respond: + data: !lambda |- + root["status"] = "long_delay"; diff --git a/tests/integration/test_api_action_responses.py b/tests/integration/test_api_action_responses.py new file mode 100644 index 0000000000..d441a231aa --- /dev/null +++ b/tests/integration/test_api_action_responses.py @@ -0,0 +1,258 @@ +"""Integration test for API action responses feature. + +Tests the supports_response modes: none, status, optional, only. +""" + +from __future__ import annotations + +import asyncio +import json +import re + +from aioesphomeapi import SupportsResponseType, UserService, UserServiceArgType +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_api_action_responses( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test API action response modes work correctly.""" + loop = asyncio.get_running_loop() + + # Track log messages for each action type + no_response_future = loop.create_future() + status_success_future = loop.create_future() + status_error_future = loop.create_future() + optional_response_future = loop.create_future() + only_response_future = loop.create_future() + nested_json_future = loop.create_future() + + # Patterns to match in logs + no_response_pattern = re.compile(r"ACTION_NO_RESPONSE called with: test_message") + status_success_pattern = re.compile( + r"ACTION_STATUS_RESPONSE success \(call_id=\d+\)" + ) + status_error_pattern = re.compile(r"ACTION_STATUS_RESPONSE error \(call_id=\d+\)") + optional_response_pattern = re.compile( + r"ACTION_OPTIONAL_RESPONSE \(call_id=\d+, return_response=\d+, value=42\)" + ) + only_response_pattern = re.compile( + r"ACTION_ONLY_RESPONSE \(call_id=\d+, name=World\)" + ) + nested_json_pattern = re.compile(r"ACTION_NESTED_JSON \(call_id=\d+\)") + + def check_output(line: str) -> None: + """Check log output for expected messages.""" + if not no_response_future.done() and no_response_pattern.search(line): + no_response_future.set_result(True) + elif not status_success_future.done() and status_success_pattern.search(line): + status_success_future.set_result(True) + elif not status_error_future.done() and status_error_pattern.search(line): + status_error_future.set_result(True) + elif not optional_response_future.done() and optional_response_pattern.search( + line + ): + optional_response_future.set_result(True) + elif not only_response_future.done() and only_response_pattern.search(line): + only_response_future.set_result(True) + elif not nested_json_future.done() and nested_json_pattern.search(line): + nested_json_future.set_result(True) + + # Run with log monitoring + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "api-action-responses-test" + + # List services + _, services = await client.list_entities_services() + + # Should have 5 services + assert len(services) == 5, f"Expected 5 services, found {len(services)}" + + # Find our services + action_no_response: UserService | None = None + action_status_response: UserService | None = None + action_optional_response: UserService | None = None + action_only_response: UserService | None = None + action_nested_json: UserService | None = None + + for service in services: + if service.name == "action_no_response": + action_no_response = service + elif service.name == "action_status_response": + action_status_response = service + elif service.name == "action_optional_response": + action_optional_response = service + elif service.name == "action_only_response": + action_only_response = service + elif service.name == "action_nested_json": + action_nested_json = service + + assert action_no_response is not None, "action_no_response not found" + assert action_status_response is not None, "action_status_response not found" + assert action_optional_response is not None, ( + "action_optional_response not found" + ) + assert action_only_response is not None, "action_only_response not found" + assert action_nested_json is not None, "action_nested_json not found" + + # Verify supports_response modes + assert action_no_response.supports_response is None or ( + action_no_response.supports_response == SupportsResponseType.NONE + ), ( + f"action_no_response should have supports_response=NONE, got {action_no_response.supports_response}" + ) + + assert ( + action_status_response.supports_response == SupportsResponseType.STATUS + ), ( + f"action_status_response should have supports_response=STATUS, " + f"got {action_status_response.supports_response}" + ) + + assert ( + action_optional_response.supports_response == SupportsResponseType.OPTIONAL + ), ( + f"action_optional_response should have supports_response=OPTIONAL, " + f"got {action_optional_response.supports_response}" + ) + + assert action_only_response.supports_response == SupportsResponseType.ONLY, ( + f"action_only_response should have supports_response=ONLY, " + f"got {action_only_response.supports_response}" + ) + + assert action_nested_json.supports_response == SupportsResponseType.ONLY, ( + f"action_nested_json should have supports_response=ONLY, " + f"got {action_nested_json.supports_response}" + ) + + # Verify argument types + # action_no_response: string message + assert len(action_no_response.args) == 1 + assert action_no_response.args[0].name == "message" + assert action_no_response.args[0].type == UserServiceArgType.STRING + + # action_status_response: bool should_succeed + assert len(action_status_response.args) == 1 + assert action_status_response.args[0].name == "should_succeed" + assert action_status_response.args[0].type == UserServiceArgType.BOOL + + # action_optional_response: int value + assert len(action_optional_response.args) == 1 + assert action_optional_response.args[0].name == "value" + assert action_optional_response.args[0].type == UserServiceArgType.INT + + # action_only_response: string name + assert len(action_only_response.args) == 1 + assert action_only_response.args[0].name == "name" + assert action_only_response.args[0].type == UserServiceArgType.STRING + + # action_nested_json: no args + assert len(action_nested_json.args) == 0 + + # Test action_no_response (supports_response: none) + # No response expected for this action + response = await client.execute_service( + action_no_response, {"message": "test_message"} + ) + assert response is None, "action_no_response should not return a response" + await asyncio.wait_for(no_response_future, timeout=5.0) + + # Test action_status_response with success (supports_response: status) + response = await client.execute_service( + action_status_response, + {"should_succeed": True}, + return_response=True, + ) + await asyncio.wait_for(status_success_future, timeout=5.0) + assert response is not None, "Expected response for status action" + assert response.success is True, ( + f"Expected success=True, got {response.success}" + ) + assert response.error_message == "", ( + f"Expected empty error_message, got '{response.error_message}'" + ) + + # Test action_status_response with error + response = await client.execute_service( + action_status_response, + {"should_succeed": False}, + return_response=True, + ) + await asyncio.wait_for(status_error_future, timeout=5.0) + assert response is not None, "Expected response for status action" + assert response.success is False, ( + f"Expected success=False, got {response.success}" + ) + assert "Intentional failure" in response.error_message, ( + f"Expected error message containing 'Intentional failure', " + f"got '{response.error_message}'" + ) + + # Test action_optional_response (supports_response: optional) + response = await client.execute_service( + action_optional_response, + {"value": 42}, + return_response=True, + ) + await asyncio.wait_for(optional_response_future, timeout=5.0) + assert response is not None, "Expected response for optional action" + assert response.success is True, ( + f"Expected success=True, got {response.success}" + ) + # Parse response data as JSON + response_json = json.loads(response.response_data.decode("utf-8")) + assert response_json["input"] == 42, ( + f"Expected input=42, got {response_json.get('input')}" + ) + assert response_json["doubled"] == 84, ( + f"Expected doubled=84, got {response_json.get('doubled')}" + ) + + # Test action_only_response (supports_response: only) + response = await client.execute_service( + action_only_response, + {"name": "World"}, + return_response=True, + ) + await asyncio.wait_for(only_response_future, timeout=5.0) + assert response is not None, "Expected response for only action" + assert response.success is True, ( + f"Expected success=True, got {response.success}" + ) + response_json = json.loads(response.response_data.decode("utf-8")) + assert response_json["greeting"] == "Hello, World!", ( + f"Expected greeting='Hello, World!', got {response_json.get('greeting')}" + ) + assert response_json["length"] == 5, ( + f"Expected length=5, got {response_json.get('length')}" + ) + + # Test action_nested_json + response = await client.execute_service( + action_nested_json, + {}, + return_response=True, + ) + await asyncio.wait_for(nested_json_future, timeout=5.0) + assert response is not None, "Expected response for nested json action" + assert response.success is True, ( + f"Expected success=True, got {response.success}" + ) + response_json = json.loads(response.response_data.decode("utf-8")) + # Verify nested structure + assert response_json["config"]["wifi"]["connected"] is True + assert response_json["config"]["api"]["port"] == 6053 + assert response_json["items"][0] == "first" + assert response_json["items"][1] == "second" diff --git a/tests/integration/test_api_action_timeout.py b/tests/integration/test_api_action_timeout.py new file mode 100644 index 0000000000..cec0967131 --- /dev/null +++ b/tests/integration/test_api_action_timeout.py @@ -0,0 +1,172 @@ +"""Integration test for API action call timeout functionality. + +Tests that action calls are automatically cleaned up after timeout, +and that late responses are handled gracefully. +""" + +from __future__ import annotations + +import asyncio +import contextlib +import re + +from aioesphomeapi import UserService +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_api_action_timeout( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test API action call timeout behavior. + + This test uses a 500ms timeout (set via USE_API_ACTION_CALL_TIMEOUT_MS define) + to verify: + 1. Actions that respond within the timeout work correctly + 2. Actions that exceed the timeout have their calls cleaned up + 3. Late responses log a warning but don't crash + """ + loop = asyncio.get_running_loop() + + # Track log messages + immediate_future = loop.create_future() + short_delay_responding_future = loop.create_future() + long_delay_starting_future = loop.create_future() + long_delay_responding_future = loop.create_future() + timeout_warning_future = loop.create_future() + + # Patterns to match in logs + immediate_pattern = re.compile(r"ACTION_IMMEDIATE responding") + short_delay_responding_pattern = re.compile(r"ACTION_SHORT_DELAY responding") + long_delay_starting_pattern = re.compile(r"ACTION_LONG_DELAY starting") + long_delay_responding_pattern = re.compile( + r"ACTION_LONG_DELAY responding \(after timeout\)" + ) + # This warning is logged when api.respond is called after the action call timed out + timeout_warning_pattern = re.compile( + r"Cannot send response: no active call found for action_call_id" + ) + + def check_output(line: str) -> None: + """Check log output for expected messages.""" + if not immediate_future.done() and immediate_pattern.search(line): + immediate_future.set_result(True) + elif ( + not short_delay_responding_future.done() + and short_delay_responding_pattern.search(line) + ): + short_delay_responding_future.set_result(True) + elif ( + not long_delay_starting_future.done() + and long_delay_starting_pattern.search(line) + ): + long_delay_starting_future.set_result(True) + elif ( + not long_delay_responding_future.done() + and long_delay_responding_pattern.search(line) + ): + long_delay_responding_future.set_result(True) + elif not timeout_warning_future.done() and timeout_warning_pattern.search(line): + timeout_warning_future.set_result(True) + + # Run with log monitoring + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "api-action-timeout-test" + + # List services + _, services = await client.list_entities_services() + + # Should have 3 services + assert len(services) == 3, f"Expected 3 services, found {len(services)}" + + # Find our services + action_immediate: UserService | None = None + action_short_delay: UserService | None = None + action_long_delay: UserService | None = None + + for service in services: + if service.name == "action_immediate": + action_immediate = service + elif service.name == "action_short_delay": + action_short_delay = service + elif service.name == "action_long_delay": + action_long_delay = service + + assert action_immediate is not None, "action_immediate not found" + assert action_short_delay is not None, "action_short_delay not found" + assert action_long_delay is not None, "action_long_delay not found" + + # Test 1: Immediate response should work + response = await client.execute_service( + action_immediate, + {}, + return_response=True, + ) + await asyncio.wait_for(immediate_future, timeout=1.0) + assert response is not None, "Expected response for immediate action" + assert response.success is True + + # Test 2: Short delay (200ms) should work within the 500ms timeout + response = await client.execute_service( + action_short_delay, + {}, + return_response=True, + ) + await asyncio.wait_for(short_delay_responding_future, timeout=1.0) + assert response is not None, "Expected response for short delay action" + assert response.success is True + + # Test 3: Long delay (1s) should exceed the 500ms timeout + # The server-side timeout will clean up the action call after 500ms + # The client will timeout waiting for the response + # When the action finally tries to respond after 1s, it will log a warning + + # Start the long delay action (don't await it fully - it will timeout) + long_delay_task = asyncio.create_task( + client.execute_service( + action_long_delay, + {}, + return_response=True, + timeout=2.0, # Give client enough time to see the late response attempt + ) + ) + + # Wait for the action to start + await asyncio.wait_for(long_delay_starting_future, timeout=1.0) + + # Wait for the action to try to respond (after 1s delay) + await asyncio.wait_for(long_delay_responding_future, timeout=2.0) + + # Wait for the warning log about no active call + await asyncio.wait_for(timeout_warning_future, timeout=1.0) + + # The client task should complete (either with None response or timeout) + # Client timing out is acceptable - the server-side timeout already cleaned up the call + with contextlib.suppress(TimeoutError): + await asyncio.wait_for(long_delay_task, timeout=1.0) + + # Verify the system is still functional after the timeout + # Call the immediate action again to prove cleanup worked + immediate_future_2 = loop.create_future() + + def check_output_2(line: str) -> None: + if not immediate_future_2.done() and immediate_pattern.search(line): + immediate_future_2.set_result(True) + + response = await client.execute_service( + action_immediate, + {}, + return_response=True, + ) + assert response is not None, "System should still work after timeout" + assert response.success is True diff --git a/tests/integration/test_api_conditional_memory.py b/tests/integration/test_api_conditional_memory.py index cfa32c431d..349b572859 100644 --- a/tests/integration/test_api_conditional_memory.py +++ b/tests/integration/test_api_conditional_memory.py @@ -88,13 +88,13 @@ async def test_api_conditional_memory( assert arg_types["arg_float"] == UserServiceArgType.FLOAT # Call simple service - client.execute_service(simple_service, {}) + await client.execute_service(simple_service, {}) # Wait for service log await asyncio.wait_for(service_simple_future, timeout=5.0) # Call service with arguments - client.execute_service( + await client.execute_service( service_with_args, { "arg_string": "test_string", diff --git a/tests/integration/test_api_custom_services.py b/tests/integration/test_api_custom_services.py index 967c504112..cd33b5a1fc 100644 --- a/tests/integration/test_api_custom_services.py +++ b/tests/integration/test_api_custom_services.py @@ -114,7 +114,7 @@ async def test_api_custom_services( assert custom_arrays_service is not None, "custom_service_with_arrays not found" # Test YAML service - client.execute_service(yaml_service, {}) + await client.execute_service(yaml_service, {}) await asyncio.wait_for(yaml_service_future, timeout=5.0) # Verify YAML service with args arguments @@ -124,7 +124,7 @@ async def test_api_custom_services( assert yaml_args_types["my_string"] == UserServiceArgType.STRING # Test YAML service with arguments - client.execute_service( + await client.execute_service( yaml_args_service, { "my_int": 123, @@ -144,7 +144,7 @@ async def test_api_custom_services( assert yaml_many_args_types["arg4"] == UserServiceArgType.STRING # Test YAML service with many arguments - client.execute_service( + await client.execute_service( yaml_many_args_service, { "arg1": 42, @@ -156,7 +156,7 @@ async def test_api_custom_services( await asyncio.wait_for(yaml_many_args_future, timeout=5.0) # Test simple CustomAPIDevice service - client.execute_service(custom_service, {}) + await client.execute_service(custom_service, {}) await asyncio.wait_for(custom_service_future, timeout=5.0) # Verify custom_args_service arguments @@ -168,7 +168,7 @@ async def test_api_custom_services( assert arg_types["arg_float"] == UserServiceArgType.FLOAT # Test CustomAPIDevice service with arguments - client.execute_service( + await client.execute_service( custom_args_service, { "arg_string": "test_string", @@ -188,7 +188,7 @@ async def test_api_custom_services( assert array_arg_types["string_array"] == UserServiceArgType.STRING_ARRAY # Test CustomAPIDevice service with arrays - client.execute_service( + await client.execute_service( custom_arrays_service, { "bool_array": [True, False], diff --git a/tests/integration/test_api_homeassistant.py b/tests/integration/test_api_homeassistant.py index f69838396d..98901fb3f9 100644 --- a/tests/integration/test_api_homeassistant.py +++ b/tests/integration/test_api_homeassistant.py @@ -163,7 +163,7 @@ async def test_api_homeassistant( assert trigger_service is not None, "trigger_all_tests service not found" # Execute all tests - client.execute_service(trigger_service, {}) + await client.execute_service(trigger_service, {}) # Wait for all tests to complete with appropriate timeouts try: diff --git a/tests/integration/test_api_string_lambda.py b/tests/integration/test_api_string_lambda.py index f4ef77bad8..ece8b192a2 100644 --- a/tests/integration/test_api_string_lambda.py +++ b/tests/integration/test_api_string_lambda.py @@ -75,10 +75,12 @@ async def test_api_string_lambda( assert char_ptr_service is not None, "test_char_ptr_lambda service not found" # Execute all four services to test different lambda return types - client.execute_service(string_service, {"input_string": "STRING_FROM_LAMBDA"}) - client.execute_service(int_service, {"input_number": 42}) - client.execute_service(float_service, {"input_float": 3.14}) - client.execute_service( + await client.execute_service( + string_service, {"input_string": "STRING_FROM_LAMBDA"} + ) + await client.execute_service(int_service, {"input_number": 42}) + await client.execute_service(float_service, {"input_float": 3.14}) + await client.execute_service( char_ptr_service, {"input_number": 123, "input_string": "test_string"} ) diff --git a/tests/integration/test_automation_wait_actions.py b/tests/integration/test_automation_wait_actions.py index adcb8ba487..f4db247231 100644 --- a/tests/integration/test_automation_wait_actions.py +++ b/tests/integration/test_automation_wait_actions.py @@ -71,7 +71,7 @@ async def test_automation_wait_actions( # Test 1: wait_until in automation - trigger 5 times rapidly test_service = next((s for s in services if s.name == "test_wait_until"), None) assert test_service is not None, "test_wait_until service not found" - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) await asyncio.wait_for(test1_complete, timeout=3.0) # Verify Test 1: All 5 triggers should complete @@ -82,7 +82,7 @@ async def test_automation_wait_actions( # Test 2: script.wait in automation - trigger 5 times rapidly test_service = next((s for s in services if s.name == "test_script_wait"), None) assert test_service is not None, "test_script_wait service not found" - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) await asyncio.wait_for(test2_complete, timeout=3.0) # Verify Test 2: All 5 triggers should complete @@ -95,7 +95,7 @@ async def test_automation_wait_actions( (s for s in services if s.name == "test_wait_timeout"), None ) assert test_service is not None, "test_wait_timeout service not found" - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) await asyncio.wait_for(test3_complete, timeout=3.0) # Verify Test 3: All 5 triggers should timeout and complete diff --git a/tests/integration/test_automations.py b/tests/integration/test_automations.py index 83268c1eea..ffd7f5c587 100644 --- a/tests/integration/test_automations.py +++ b/tests/integration/test_automations.py @@ -67,7 +67,7 @@ async def test_delay_action_cancellation( assert test_service is not None, "start_delay_then_restart service not found" # Execute the test sequence - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) # Wait for the second script to start await asyncio.wait_for(second_script_started, timeout=5.0) @@ -138,7 +138,7 @@ async def test_parallel_script_delays( assert test_service is not None, "test_parallel_delays service not found" # Execute the test - this will start 3 parallel scripts with 1 second delays - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) # Wait for all scripts to complete (should take ~1 second, not 3) await asyncio.wait_for(all_scripts_completed, timeout=2.0) diff --git a/tests/integration/test_continuation_actions.py b/tests/integration/test_continuation_actions.py index 1069ee7581..e6020c711a 100644 --- a/tests/integration/test_continuation_actions.py +++ b/tests/integration/test_continuation_actions.py @@ -142,7 +142,7 @@ async def test_continuation_actions( # Test 1: IfAction with then branch test_service = next((s for s in services if s.name == "test_if_action"), None) assert test_service is not None, "test_if_action service not found" - client.execute_service(test_service, {"condition": True, "value": 42}) + await client.execute_service(test_service, {"condition": True, "value": 42}) await asyncio.wait_for(test1_complete, timeout=2.0) assert test_results["if_then"], "IfAction then branch not executed" assert test_results["if_complete"], "IfAction did not complete" @@ -150,7 +150,7 @@ async def test_continuation_actions( # Test 1b: IfAction with else branch test1_complete = loop.create_future() test_results["if_complete"] = False - client.execute_service(test_service, {"condition": False, "value": 99}) + await client.execute_service(test_service, {"condition": False, "value": 99}) await asyncio.wait_for(test1_complete, timeout=2.0) assert test_results["if_else"], "IfAction else branch not executed" assert test_results["if_complete"], "IfAction did not complete" @@ -160,14 +160,14 @@ async def test_continuation_actions( assert test_service is not None, "test_nested_if service not found" # Both true - client.execute_service(test_service, {"outer": True, "inner": True}) + await client.execute_service(test_service, {"outer": True, "inner": True}) await asyncio.wait_for(test2_complete, timeout=2.0) assert test_results["nested_both_true"], "Nested both true not executed" # Outer true, inner false test2_complete = loop.create_future() test_results["nested_complete"] = False - client.execute_service(test_service, {"outer": True, "inner": False}) + await client.execute_service(test_service, {"outer": True, "inner": False}) await asyncio.wait_for(test2_complete, timeout=2.0) assert test_results["nested_outer_true_inner_false"], ( "Nested outer true inner false not executed" @@ -176,7 +176,7 @@ async def test_continuation_actions( # Outer false test2_complete = loop.create_future() test_results["nested_complete"] = False - client.execute_service(test_service, {"outer": False, "inner": True}) + await client.execute_service(test_service, {"outer": False, "inner": True}) await asyncio.wait_for(test2_complete, timeout=2.0) assert test_results["nested_outer_false"], "Nested outer false not executed" @@ -185,7 +185,7 @@ async def test_continuation_actions( (s for s in services if s.name == "test_while_action"), None ) assert test_service is not None, "test_while_action service not found" - client.execute_service(test_service, {"max_count": 3}) + await client.execute_service(test_service, {"max_count": 3}) await asyncio.wait_for(test3_complete, timeout=2.0) assert test_results["while_iterations"] == 3, ( f"WhileAction expected 3 iterations, got {test_results['while_iterations']}" @@ -197,7 +197,7 @@ async def test_continuation_actions( (s for s in services if s.name == "test_repeat_action"), None ) assert test_service is not None, "test_repeat_action service not found" - client.execute_service(test_service, {"count": 5}) + await client.execute_service(test_service, {"count": 5}) await asyncio.wait_for(test4_complete, timeout=2.0) assert test_results["repeat_iterations"] == 5, ( f"RepeatAction expected 5 iterations, got {test_results['repeat_iterations']}" @@ -207,7 +207,7 @@ async def test_continuation_actions( # Test 5: Combined (if + repeat + while) test_service = next((s for s in services if s.name == "test_combined"), None) assert test_service is not None, "test_combined service not found" - client.execute_service(test_service, {"do_loop": True, "loop_count": 2}) + await client.execute_service(test_service, {"do_loop": True, "loop_count": 2}) await asyncio.wait_for(test5_complete, timeout=2.0) # Should execute: repeat 2 times, each iteration does while from iteration down to 0 # iteration 0: while 0 times = 0 @@ -221,7 +221,7 @@ async def test_continuation_actions( # Test 6: Rapid triggers (tests memory efficiency of ContinuationAction) test_service = next((s for s in services if s.name == "test_rapid_if"), None) assert test_service is not None, "test_rapid_if service not found" - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) await asyncio.wait_for(test6_complete, timeout=2.0) # Values 1, 2 should hit else (<=2), values 3, 4, 5 should hit then (>2) assert test_results["rapid_else"] == 2, ( diff --git a/tests/integration/test_scheduler_bulk_cleanup.py b/tests/integration/test_scheduler_bulk_cleanup.py index b52a4a3496..973f59b838 100644 --- a/tests/integration/test_scheduler_bulk_cleanup.py +++ b/tests/integration/test_scheduler_bulk_cleanup.py @@ -98,7 +98,7 @@ async def test_scheduler_bulk_cleanup( ) # Execute the test - client.execute_service(trigger_bulk_cleanup_service, {}) + await client.execute_service(trigger_bulk_cleanup_service, {}) # Wait for test completion try: diff --git a/tests/integration/test_scheduler_defer_cancel.py b/tests/integration/test_scheduler_defer_cancel.py index 34c46bab82..bf34de9677 100644 --- a/tests/integration/test_scheduler_defer_cancel.py +++ b/tests/integration/test_scheduler_defer_cancel.py @@ -81,7 +81,7 @@ async def test_scheduler_defer_cancel( client.subscribe_states(on_state) # Execute the test - client.execute_service(test_defer_cancel_service, {}) + await client.execute_service(test_defer_cancel_service, {}) # Wait for test completion try: diff --git a/tests/integration/test_scheduler_defer_cancel_regular.py b/tests/integration/test_scheduler_defer_cancel_regular.py index c93d814fbe..4c37062844 100644 --- a/tests/integration/test_scheduler_defer_cancel_regular.py +++ b/tests/integration/test_scheduler_defer_cancel_regular.py @@ -59,7 +59,7 @@ async def test_scheduler_defer_cancels_regular( assert test_service is not None, "test_defer_cancels_regular service not found" # Execute the test - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) # Wait for test completion try: diff --git a/tests/integration/test_scheduler_defer_fifo_simple.py b/tests/integration/test_scheduler_defer_fifo_simple.py index 3502302368..4c5c2b56de 100644 --- a/tests/integration/test_scheduler_defer_fifo_simple.py +++ b/tests/integration/test_scheduler_defer_fifo_simple.py @@ -84,7 +84,7 @@ async def test_scheduler_defer_fifo_simple( client.subscribe_states(on_state) # Test 1: Test set_timeout(0) - client.execute_service(test_set_timeout_service, {}) + await client.execute_service(test_set_timeout_service, {}) # Wait for first test completion try: @@ -102,7 +102,7 @@ async def test_scheduler_defer_fifo_simple( test_result_future = loop.create_future() # Test 2: Test defer() - client.execute_service(test_defer_service, {}) + await client.execute_service(test_defer_service, {}) # Wait for second test completion try: diff --git a/tests/integration/test_scheduler_defer_stress.py b/tests/integration/test_scheduler_defer_stress.py index 6f4d997307..345ba9434c 100644 --- a/tests/integration/test_scheduler_defer_stress.py +++ b/tests/integration/test_scheduler_defer_stress.py @@ -92,7 +92,7 @@ async def test_scheduler_defer_stress( assert run_stress_test_service is not None, "run_stress_test service not found" # Call the run_stress_test service to start the test - client.execute_service(run_stress_test_service, {}) + await client.execute_service(run_stress_test_service, {}) # Wait for all defers to execute (should be quick) try: diff --git a/tests/integration/test_scheduler_heap_stress.py b/tests/integration/test_scheduler_heap_stress.py index 2d55b8ae89..cceadd0661 100644 --- a/tests/integration/test_scheduler_heap_stress.py +++ b/tests/integration/test_scheduler_heap_stress.py @@ -99,7 +99,7 @@ async def test_scheduler_heap_stress( ) # Call the run_heap_stress_test service to start the test - client.execute_service(run_stress_test_service, {}) + await client.execute_service(run_stress_test_service, {}) # Wait for all callbacks to execute (should be quick, but give more time for scheduling) try: diff --git a/tests/integration/test_scheduler_null_name.py b/tests/integration/test_scheduler_null_name.py index 75864ea2d2..9eeb648d59 100644 --- a/tests/integration/test_scheduler_null_name.py +++ b/tests/integration/test_scheduler_null_name.py @@ -48,7 +48,7 @@ async def test_scheduler_null_name( assert test_null_name_service is not None, "test_null_name service not found" # Execute the test - client.execute_service(test_null_name_service, {}) + await client.execute_service(test_null_name_service, {}) # Wait for test completion try: diff --git a/tests/integration/test_scheduler_pool.py b/tests/integration/test_scheduler_pool.py index b5f9f12631..021917cc25 100644 --- a/tests/integration/test_scheduler_pool.py +++ b/tests/integration/test_scheduler_pool.py @@ -120,42 +120,42 @@ async def test_scheduler_pool( try: # Phase 1: Component lifecycle - client.execute_service(phase_services[1], {}) + await client.execute_service(phase_services[1], {}) await asyncio.wait_for(phase_futures[1], timeout=1.0) await asyncio.sleep(0.05) # Let timeouts complete # Phase 2: Sensor polling - client.execute_service(phase_services[2], {}) + await client.execute_service(phase_services[2], {}) await asyncio.wait_for(phase_futures[2], timeout=1.0) await asyncio.sleep(0.1) # Let intervals run a bit # Phase 3: Communication patterns - client.execute_service(phase_services[3], {}) + await client.execute_service(phase_services[3], {}) await asyncio.wait_for(phase_futures[3], timeout=1.0) await asyncio.sleep(0.1) # Let heartbeat run # Phase 4: Defer patterns - client.execute_service(phase_services[4], {}) + await client.execute_service(phase_services[4], {}) await asyncio.wait_for(phase_futures[4], timeout=1.0) await asyncio.sleep(0.2) # Let everything settle and recycle # Phase 5: Pool reuse verification - client.execute_service(phase_services[5], {}) + await client.execute_service(phase_services[5], {}) await asyncio.wait_for(phase_futures[5], timeout=1.0) await asyncio.sleep(0.1) # Let Phase 5 timeouts complete and recycle # Phase 6: Full pool reuse verification - client.execute_service(phase_services[6], {}) + await client.execute_service(phase_services[6], {}) await asyncio.wait_for(phase_futures[6], timeout=1.0) await asyncio.sleep(0.1) # Let Phase 6 timeouts complete # Phase 7: Same-named defer optimization - client.execute_service(phase_services[7], {}) + await client.execute_service(phase_services[7], {}) await asyncio.wait_for(phase_futures[7], timeout=1.0) await asyncio.sleep(0.05) # Let the single defer execute # Complete test - client.execute_service(complete_service, {}) + await client.execute_service(complete_service, {}) await asyncio.wait_for(test_complete_future, timeout=0.5) except TimeoutError as e: diff --git a/tests/integration/test_scheduler_rapid_cancellation.py b/tests/integration/test_scheduler_rapid_cancellation.py index 1b7da32aaa..1b67e7fc33 100644 --- a/tests/integration/test_scheduler_rapid_cancellation.py +++ b/tests/integration/test_scheduler_rapid_cancellation.py @@ -108,7 +108,7 @@ async def test_scheduler_rapid_cancellation( ) # Call the service to start the test - client.execute_service(run_test_service, {}) + await client.execute_service(run_test_service, {}) # Wait for test to complete with timeout try: diff --git a/tests/integration/test_scheduler_recursive_timeout.py b/tests/integration/test_scheduler_recursive_timeout.py index d98d2ac5ee..7d7131f8f6 100644 --- a/tests/integration/test_scheduler_recursive_timeout.py +++ b/tests/integration/test_scheduler_recursive_timeout.py @@ -79,7 +79,7 @@ async def test_scheduler_recursive_timeout( ) # Call the service to start the test - client.execute_service(run_test_service, {}) + await client.execute_service(run_test_service, {}) # Wait for test to complete try: diff --git a/tests/integration/test_scheduler_removed_item_race.py b/tests/integration/test_scheduler_removed_item_race.py index 3e72bacc0d..5c78f829a4 100644 --- a/tests/integration/test_scheduler_removed_item_race.py +++ b/tests/integration/test_scheduler_removed_item_race.py @@ -81,7 +81,7 @@ async def test_scheduler_removed_item_race( assert run_test_service is not None, "run_test service not found" # Execute the test - client.execute_service(run_test_service, {}) + await client.execute_service(run_test_service, {}) # Wait for test completion try: diff --git a/tests/integration/test_scheduler_simultaneous_callbacks.py b/tests/integration/test_scheduler_simultaneous_callbacks.py index 82fd0fc01e..66b2862eef 100644 --- a/tests/integration/test_scheduler_simultaneous_callbacks.py +++ b/tests/integration/test_scheduler_simultaneous_callbacks.py @@ -98,7 +98,7 @@ async def test_scheduler_simultaneous_callbacks( ) # Call the service to start the test - client.execute_service(run_test_service, {}) + await client.execute_service(run_test_service, {}) # Wait for test to complete try: diff --git a/tests/integration/test_scheduler_string_lifetime.py b/tests/integration/test_scheduler_string_lifetime.py index 7ec5a54373..bfa581129b 100644 --- a/tests/integration/test_scheduler_string_lifetime.py +++ b/tests/integration/test_scheduler_string_lifetime.py @@ -134,27 +134,27 @@ async def test_scheduler_string_lifetime( # Run tests sequentially, waiting for each to complete try: # Test 1 - client.execute_service(test_services["test1"], {}) + await client.execute_service(test_services["test1"], {}) await asyncio.wait_for(test1_complete.wait(), timeout=5.0) # Test 2 - client.execute_service(test_services["test2"], {}) + await client.execute_service(test_services["test2"], {}) await asyncio.wait_for(test2_complete.wait(), timeout=5.0) # Test 3 - client.execute_service(test_services["test3"], {}) + await client.execute_service(test_services["test3"], {}) await asyncio.wait_for(test3_complete.wait(), timeout=5.0) # Test 4 - client.execute_service(test_services["test4"], {}) + await client.execute_service(test_services["test4"], {}) await asyncio.wait_for(test4_complete.wait(), timeout=5.0) # Test 5 - client.execute_service(test_services["test5"], {}) + await client.execute_service(test_services["test5"], {}) await asyncio.wait_for(test5_complete.wait(), timeout=5.0) # Final check - client.execute_service(test_services["final"], {}) + await client.execute_service(test_services["final"], {}) await asyncio.wait_for(all_tests_complete.wait(), timeout=5.0) except TimeoutError: diff --git a/tests/integration/test_scheduler_string_name_stress.py b/tests/integration/test_scheduler_string_name_stress.py index 4c52913e63..56b8998c56 100644 --- a/tests/integration/test_scheduler_string_name_stress.py +++ b/tests/integration/test_scheduler_string_name_stress.py @@ -92,7 +92,7 @@ async def test_scheduler_string_name_stress( ) # Call the service to start the test - client.execute_service(run_stress_test_service, {}) + await client.execute_service(run_stress_test_service, {}) # Wait for test to complete or crash try: diff --git a/tests/integration/test_script_delay_params.py b/tests/integration/test_script_delay_params.py index 1b5d70863b..37c72f0f7d 100644 --- a/tests/integration/test_script_delay_params.py +++ b/tests/integration/test_script_delay_params.py @@ -90,7 +90,7 @@ async def test_script_delay_with_params( assert test_service is not None, "test_repeat_with_delay service not found" # Execute the test - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) # Wait for test to complete (10 iterations * ~100ms each + margin) try: diff --git a/tests/integration/test_script_queued.py b/tests/integration/test_script_queued.py index ce1c25b649..c86c289719 100644 --- a/tests/integration/test_script_queued.py +++ b/tests/integration/test_script_queued.py @@ -136,7 +136,7 @@ async def test_script_queued( # Test 1: Queue depth limit test_service = next((s for s in services if s.name == "test_queue_depth"), None) assert test_service is not None, "test_queue_depth service not found" - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) await asyncio.wait_for(test1_complete, timeout=2.0) await asyncio.sleep(0.1) # Give time for rejections @@ -151,7 +151,7 @@ async def test_script_queued( # Test 2: Ring buffer order test_service = next((s for s in services if s.name == "test_ring_buffer"), None) assert test_service is not None, "test_ring_buffer service not found" - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) await asyncio.wait_for(test2_complete, timeout=2.0) # Verify Test 2 @@ -165,7 +165,7 @@ async def test_script_queued( # Test 3: Stop clears queue test_service = next((s for s in services if s.name == "test_stop_clears"), None) assert test_service is not None, "test_stop_clears service not found" - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) await asyncio.wait_for(test3_complete, timeout=2.0) # Verify Test 3 @@ -179,7 +179,7 @@ async def test_script_queued( # Test 4: Rejection enforcement (max_runs=3) test_service = next((s for s in services if s.name == "test_rejection"), None) assert test_service is not None, "test_rejection service not found" - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) await asyncio.wait_for(test4_complete, timeout=2.0) await asyncio.sleep(0.1) # Give time for rejections @@ -194,7 +194,7 @@ async def test_script_queued( # Test 5: No parameters test_service = next((s for s in services if s.name == "test_no_params"), None) assert test_service is not None, "test_no_params service not found" - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) await asyncio.wait_for(test5_complete, timeout=2.0) # Verify Test 5 diff --git a/tests/integration/test_wait_until_mid_loop_timing.py b/tests/integration/test_wait_until_mid_loop_timing.py index 01cad747ae..b5dd1a0028 100644 --- a/tests/integration/test_wait_until_mid_loop_timing.py +++ b/tests/integration/test_wait_until_mid_loop_timing.py @@ -86,7 +86,7 @@ async def test_wait_until_mid_loop_timing( assert test_service is not None, "test_mid_loop_timeout service not found" # Execute the test - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) # Wait for test to complete (100ms delay + 200ms timeout + margins = ~500ms) await asyncio.wait_for(test_complete, timeout=5.0) diff --git a/tests/integration/test_wait_until_on_boot.py b/tests/integration/test_wait_until_on_boot.py index b42c530c54..da21a43200 100644 --- a/tests/integration/test_wait_until_on_boot.py +++ b/tests/integration/test_wait_until_on_boot.py @@ -74,7 +74,7 @@ async def test_wait_until_on_boot( ) assert set_flag_service is not None, "set_test_flag service not found" - client.execute_service(set_flag_service, {}) + await client.execute_service(set_flag_service, {}) # If the fix works, wait_until's loop() will check the condition and proceed # If the bug exists, wait_until is stuck with disabled loop and will timeout diff --git a/tests/integration/test_wait_until_ordering.py b/tests/integration/test_wait_until_ordering.py index 7c39913e5a..96b3a8aed0 100644 --- a/tests/integration/test_wait_until_ordering.py +++ b/tests/integration/test_wait_until_ordering.py @@ -71,7 +71,7 @@ async def test_wait_until_fifo_ordering( assert test_service is not None, "test_wait_until_fifo service not found" # Execute the test - client.execute_service(test_service, {}) + await client.execute_service(test_service, {}) # Wait for test to complete try: From aeedfdcaf3c2341cfd0e6d34dd28bcacb248d333 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Dec 2025 21:31:56 -0600 Subject: [PATCH 0544/1145] Bump aioesphomeapi from 43.0.0 to 43.1.0 (#12333) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0bad48716e..5596f050af 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.0.0 +aioesphomeapi==43.1.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.16 # dashboard_import From 1f271e7c10e28596ff5002088b7ffae33feb4035 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Dec 2025 21:32:08 -0600 Subject: [PATCH 0545/1145] Bump pytest from 9.0.1 to 9.0.2 (#12332) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 16ac131517..60656712b9 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==9.0.1 +pytest==9.0.2 pytest-cov==7.0.0 pytest-mock==3.15.1 pytest-asyncio==1.3.0 From e7a3cccb4d76e44e9c0bd25248feff1f5bb36717 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Dec 2025 13:30:06 -0600 Subject: [PATCH 0546/1145] [text_sensor] Reduce filter memory usage using const char* (#12334) --- esphome/components/text_sensor/filter.cpp | 25 ++- esphome/components/text_sensor/filter.h | 12 +- .../fixtures/text_sensor_raw_state.yaml | 127 ++++++++++++ .../integration/test_text_sensor_raw_state.py | 190 ++++++++++++++++-- 4 files changed, 326 insertions(+), 28 deletions(-) diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp index 40a37febee..4cace372ae 100644 --- a/esphome/components/text_sensor/filter.cpp +++ b/esphome/components/text_sensor/filter.cpp @@ -56,10 +56,16 @@ optional ToLowerFilter::new_value(std::string value) { } // Append -optional AppendFilter::new_value(std::string value) { return value + this->suffix_; } +optional AppendFilter::new_value(std::string value) { + value.append(this->suffix_); + return value; +} // Prepend -optional PrependFilter::new_value(std::string value) { return this->prefix_ + value; } +optional PrependFilter::new_value(std::string value) { + value.insert(0, this->prefix_); + return value; +} // Substitute SubstituteFilter::SubstituteFilter(const std::initializer_list &substitutions) @@ -67,12 +73,15 @@ SubstituteFilter::SubstituteFilter(const std::initializer_list &su optional SubstituteFilter::new_value(std::string value) { for (const auto &sub : this->substitutions_) { + // Compute lengths once per substitution (strlen is fast, called infrequently) + const size_t from_len = strlen(sub.from); + const size_t to_len = strlen(sub.to); std::size_t pos = 0; - while ((pos = value.find(sub.from, pos)) != std::string::npos) { - value.replace(pos, sub.from.size(), sub.to); + while ((pos = value.find(sub.from, pos, from_len)) != std::string::npos) { + value.replace(pos, from_len, sub.to, to_len); // Advance past the replacement to avoid infinite loop when // the replacement contains the search pattern (e.g., f -> foo) - pos += sub.to.size(); + pos += to_len; } } return value; @@ -83,8 +92,10 @@ MapFilter::MapFilter(const std::initializer_list &mappings) : mapp optional MapFilter::new_value(std::string value) { for (const auto &mapping : this->mappings_) { - if (mapping.from == value) - return mapping.to; + if (value == mapping.from) { + value.assign(mapping.to); + return value; + } } return value; // Pass through if no match } diff --git a/esphome/components/text_sensor/filter.h b/esphome/components/text_sensor/filter.h index 85acac5c8d..0f66b753b4 100644 --- a/esphome/components/text_sensor/filter.h +++ b/esphome/components/text_sensor/filter.h @@ -92,26 +92,26 @@ class ToLowerFilter : public Filter { /// A simple filter that adds a string to the end of another string class AppendFilter : public Filter { public: - AppendFilter(std::string suffix) : suffix_(std::move(suffix)) {} + explicit AppendFilter(const char *suffix) : suffix_(suffix) {} optional new_value(std::string value) override; protected: - std::string suffix_; + const char *suffix_; }; /// A simple filter that adds a string to the start of another string class PrependFilter : public Filter { public: - PrependFilter(std::string prefix) : prefix_(std::move(prefix)) {} + explicit PrependFilter(const char *prefix) : prefix_(prefix) {} optional new_value(std::string value) override; protected: - std::string prefix_; + const char *prefix_; }; struct Substitution { - std::string from; - std::string to; + const char *from; + const char *to; }; /// A simple filter that replaces a substring with another substring diff --git a/tests/integration/fixtures/text_sensor_raw_state.yaml b/tests/integration/fixtures/text_sensor_raw_state.yaml index 03aece0a04..54ab2e8dcc 100644 --- a/tests/integration/fixtures/text_sensor_raw_state.yaml +++ b/tests/integration/fixtures/text_sensor_raw_state.yaml @@ -20,6 +20,42 @@ text_sensor: filters: - to_upper + # StringRef-based filters (append, prepend, substitute, map) + - platform: template + name: "Append Sensor" + id: append_sensor + filters: + - append: " suffix" + + - platform: template + name: "Prepend Sensor" + id: prepend_sensor + filters: + - prepend: "prefix " + + - platform: template + name: "Substitute Sensor" + id: substitute_sensor + filters: + - substitute: + - foo -> bar + - hello -> world + + - platform: template + name: "Map Sensor" + id: map_sensor + filters: + - map: + - ON -> Active + - OFF -> Inactive + + - platform: template + name: "Chained Sensor" + id: chained_sensor + filters: + - prepend: "[" + - append: "]" + # Button to publish values and log raw_state vs state button: - platform: template @@ -52,3 +88,94 @@ button: args: - id(with_filter_sensor).state.c_str() - id(with_filter_sensor).get_raw_state().c_str() + + - platform: template + name: "Test Append Button" + id: test_append_button + on_press: + - text_sensor.template.publish: + id: append_sensor + state: "test" + - delay: 50ms + - logger.log: + format: "APPEND: state='%s'" + args: + - id(append_sensor).state.c_str() + + - platform: template + name: "Test Prepend Button" + id: test_prepend_button + on_press: + - text_sensor.template.publish: + id: prepend_sensor + state: "test" + - delay: 50ms + - logger.log: + format: "PREPEND: state='%s'" + args: + - id(prepend_sensor).state.c_str() + + - platform: template + name: "Test Substitute Button" + id: test_substitute_button + on_press: + - text_sensor.template.publish: + id: substitute_sensor + state: "foo says hello" + - delay: 50ms + - logger.log: + format: "SUBSTITUTE: state='%s'" + args: + - id(substitute_sensor).state.c_str() + + - platform: template + name: "Test Map ON Button" + id: test_map_on_button + on_press: + - text_sensor.template.publish: + id: map_sensor + state: "ON" + - delay: 50ms + - logger.log: + format: "MAP_ON: state='%s'" + args: + - id(map_sensor).state.c_str() + + - platform: template + name: "Test Map OFF Button" + id: test_map_off_button + on_press: + - text_sensor.template.publish: + id: map_sensor + state: "OFF" + - delay: 50ms + - logger.log: + format: "MAP_OFF: state='%s'" + args: + - id(map_sensor).state.c_str() + + - platform: template + name: "Test Map Unknown Button" + id: test_map_unknown_button + on_press: + - text_sensor.template.publish: + id: map_sensor + state: "UNKNOWN" + - delay: 50ms + - logger.log: + format: "MAP_UNKNOWN: state='%s'" + args: + - id(map_sensor).state.c_str() + + - platform: template + name: "Test Chained Button" + id: test_chained_button + on_press: + - text_sensor.template.publish: + id: chained_sensor + state: "value" + - delay: 50ms + - logger.log: + format: "CHAINED: state='%s'" + args: + - id(chained_sensor).state.c_str() diff --git a/tests/integration/test_text_sensor_raw_state.py b/tests/integration/test_text_sensor_raw_state.py index a53ec8c963..482ebbe9c2 100644 --- a/tests/integration/test_text_sensor_raw_state.py +++ b/tests/integration/test_text_sensor_raw_state.py @@ -1,8 +1,10 @@ -"""Integration test for TextSensor get_raw_state() functionality. +"""Integration test for TextSensor get_raw_state() and StringRef-based filters. -This tests the optimization in PR #12205 where raw_state is only stored -when filters are configured. When no filters exist, get_raw_state() should -return state directly. +This tests: +1. The optimization in PR #12205 where raw_state is only stored when filters + are configured. When no filters exist, get_raw_state() should return state. +2. StringRef-based filters (append, prepend, substitute, map) which store + static string data in flash instead of heap-allocating std::string. """ from __future__ import annotations @@ -21,16 +23,25 @@ async def test_text_sensor_raw_state( run_compiled: RunCompiledFunction, api_client_connected: APIClientConnectedFactory, ) -> None: - """Test that get_raw_state() works correctly with and without filters. + """Test text sensor filters and raw_state behavior. - Without filters: get_raw_state() should return the same value as state - With filters: get_raw_state() should return the original (unfiltered) value + Tests: + 1. get_raw_state() without filters returns same as state + 2. get_raw_state() with filters returns original (unfiltered) value + 3. StringRef-based filters: append, prepend, substitute, map, chained """ loop = asyncio.get_running_loop() # Futures to track log messages no_filter_future: asyncio.Future[tuple[str, str]] = loop.create_future() with_filter_future: asyncio.Future[tuple[str, str]] = loop.create_future() + append_future: asyncio.Future[str] = loop.create_future() + prepend_future: asyncio.Future[str] = loop.create_future() + substitute_future: asyncio.Future[str] = loop.create_future() + map_on_future: asyncio.Future[str] = loop.create_future() + map_off_future: asyncio.Future[str] = loop.create_future() + map_unknown_future: asyncio.Future[str] = loop.create_future() + chained_future: asyncio.Future[str] = loop.create_future() # Patterns to match log output # NO_FILTER: state='hello world' raw_state='hello world' @@ -39,18 +50,47 @@ async def test_text_sensor_raw_state( with_filter_pattern = re.compile( r"WITH_FILTER: state='([^']*)' raw_state='([^']*)'" ) + # StringRef-based filter patterns + append_pattern = re.compile(r"APPEND: state='([^']*)'") + prepend_pattern = re.compile(r"PREPEND: state='([^']*)'") + substitute_pattern = re.compile(r"SUBSTITUTE: state='([^']*)'") + map_on_pattern = re.compile(r"MAP_ON: state='([^']*)'") + map_off_pattern = re.compile(r"MAP_OFF: state='([^']*)'") + map_unknown_pattern = re.compile(r"MAP_UNKNOWN: state='([^']*)'") + chained_pattern = re.compile(r"CHAINED: state='([^']*)'") def check_output(line: str) -> None: """Check log output for expected messages.""" - if not no_filter_future.done(): - match = no_filter_pattern.search(line) - if match: - no_filter_future.set_result((match.group(1), match.group(2))) + if not no_filter_future.done() and (match := no_filter_pattern.search(line)): + no_filter_future.set_result((match.group(1), match.group(2))) - if not with_filter_future.done(): - match = with_filter_pattern.search(line) - if match: - with_filter_future.set_result((match.group(1), match.group(2))) + if not with_filter_future.done() and ( + match := with_filter_pattern.search(line) + ): + with_filter_future.set_result((match.group(1), match.group(2))) + + if not append_future.done() and (match := append_pattern.search(line)): + append_future.set_result(match.group(1)) + + if not prepend_future.done() and (match := prepend_pattern.search(line)): + prepend_future.set_result(match.group(1)) + + if not substitute_future.done() and (match := substitute_pattern.search(line)): + substitute_future.set_result(match.group(1)) + + if not map_on_future.done() and (match := map_on_pattern.search(line)): + map_on_future.set_result(match.group(1)) + + if not map_off_future.done() and (match := map_off_pattern.search(line)): + map_off_future.set_result(match.group(1)) + + if not map_unknown_future.done() and ( + match := map_unknown_pattern.search(line) + ): + map_unknown_future.set_result(match.group(1)) + + if not chained_future.done() and (match := chained_pattern.search(line)): + chained_future.set_result(match.group(1)) async with ( run_compiled(yaml_config, line_callback=check_output), @@ -112,3 +152,123 @@ async def test_text_sensor_raw_state( f"With filters, state and raw_state should differ. " f"state='{state}', raw_state='{raw_state}'" ) + + # Test 3: Append filter (StringRef-based) + # "test" + " suffix" = "test suffix" + append_button = next( + (e for e in entities if "test_append_button" in e.object_id.lower()), + None, + ) + assert append_button is not None, "Test Append Button not found" + client.button_command(append_button.key) + + try: + state = await asyncio.wait_for(append_future, timeout=5.0) + except TimeoutError: + pytest.fail("Timeout waiting for APPEND log message") + + assert state == "test suffix", ( + f"Append failed: expected 'test suffix', got '{state}'" + ) + + # Test 4: Prepend filter (StringRef-based) + # "prefix " + "test" = "prefix test" + prepend_button = next( + (e for e in entities if "test_prepend_button" in e.object_id.lower()), + None, + ) + assert prepend_button is not None, "Test Prepend Button not found" + client.button_command(prepend_button.key) + + try: + state = await asyncio.wait_for(prepend_future, timeout=5.0) + except TimeoutError: + pytest.fail("Timeout waiting for PREPEND log message") + + assert state == "prefix test", ( + f"Prepend failed: expected 'prefix test', got '{state}'" + ) + + # Test 5: Substitute filter (StringRef-based) + # "foo says hello" with foo->bar, hello->world = "bar says world" + substitute_button = next( + (e for e in entities if "test_substitute_button" in e.object_id.lower()), + None, + ) + assert substitute_button is not None, "Test Substitute Button not found" + client.button_command(substitute_button.key) + + try: + state = await asyncio.wait_for(substitute_future, timeout=5.0) + except TimeoutError: + pytest.fail("Timeout waiting for SUBSTITUTE log message") + + assert state == "bar says world", ( + f"Substitute failed: expected 'bar says world', got '{state}'" + ) + + # Test 6: Map filter - "ON" -> "Active" + map_on_button = next( + (e for e in entities if "test_map_on_button" in e.object_id.lower()), + None, + ) + assert map_on_button is not None, "Test Map ON Button not found" + client.button_command(map_on_button.key) + + try: + state = await asyncio.wait_for(map_on_future, timeout=5.0) + except TimeoutError: + pytest.fail("Timeout waiting for MAP_ON log message") + + assert state == "Active", f"Map ON failed: expected 'Active', got '{state}'" + + # Test 7: Map filter - "OFF" -> "Inactive" + map_off_button = next( + (e for e in entities if "test_map_off_button" in e.object_id.lower()), + None, + ) + assert map_off_button is not None, "Test Map OFF Button not found" + client.button_command(map_off_button.key) + + try: + state = await asyncio.wait_for(map_off_future, timeout=5.0) + except TimeoutError: + pytest.fail("Timeout waiting for MAP_OFF log message") + + assert state == "Inactive", ( + f"Map OFF failed: expected 'Inactive', got '{state}'" + ) + + # Test 8: Map filter - passthrough for unknown values + # "UNKNOWN" -> "UNKNOWN" (no match, passes through unchanged) + map_unknown_button = next( + (e for e in entities if "test_map_unknown_button" in e.object_id.lower()), + None, + ) + assert map_unknown_button is not None, "Test Map Unknown Button not found" + client.button_command(map_unknown_button.key) + + try: + state = await asyncio.wait_for(map_unknown_future, timeout=5.0) + except TimeoutError: + pytest.fail("Timeout waiting for MAP_UNKNOWN log message") + + assert state == "UNKNOWN", ( + f"Map passthrough failed: expected 'UNKNOWN', got '{state}'" + ) + + # Test 9: Chained filters (prepend "[" + append "]") + # "[" + "value" + "]" = "[value]" + chained_button = next( + (e for e in entities if "test_chained_button" in e.object_id.lower()), + None, + ) + assert chained_button is not None, "Test Chained Button not found" + client.button_command(chained_button.key) + + try: + state = await asyncio.wait_for(chained_future, timeout=5.0) + except TimeoutError: + pytest.fail("Timeout waiting for CHAINED log message") + + assert state == "[value]", f"Chained failed: expected '[value]', got '{state}'" From 05826d5ead2b73a8d0c867ce05cd64b03e777583 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Dec 2025 13:30:22 -0600 Subject: [PATCH 0547/1145] [scheduler] Fix missing lock when recycling items in defer queue processing (#12343) --- esphome/core/scheduler.cpp | 4 ++++ esphome/core/scheduler.h | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 5e313f770f..f84495950c 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -744,6 +744,10 @@ bool HOT Scheduler::SchedulerItem::cmp(const std::unique_ptr &a, : (a->next_execution_high_ > b->next_execution_high_); } +// Recycle a SchedulerItem back to the pool for reuse. +// IMPORTANT: Caller must hold the scheduler lock before calling this function. +// This protects scheduler_item_pool_ from concurrent access by other threads +// that may be acquiring items from the pool in set_timer_common_(). void Scheduler::recycle_item_main_loop_(std::unique_ptr item) { if (!item) return; diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index dcf418c14f..5bf3d19adb 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -275,6 +275,7 @@ class Scheduler { // Helper to recycle a SchedulerItem back to the pool. // IMPORTANT: Only call from main loop context! Recycling clears the callback, // so calling from another thread while the callback is executing causes use-after-free. + // IMPORTANT: Caller must hold the scheduler lock before calling this function. void recycle_item_main_loop_(std::unique_ptr item); // Helper to perform full cleanup when too many items are cancelled @@ -331,7 +332,10 @@ class Scheduler { now = this->execute_item_(item.get(), now); } // Recycle the defer item after execution - this->recycle_item_main_loop_(std::move(item)); + { + LockGuard lock(this->lock_); + this->recycle_item_main_loop_(std::move(item)); + } } // If we've consumed all items up to the snapshot point, clean up the dead space From 4b5435fd93fb314e0125fa001bae1596dc7aade3 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Sun, 7 Dec 2025 21:16:49 +0100 Subject: [PATCH 0548/1145] [nextion] Use 16-bit id for pics (#12330) Co-authored-by: Szczepan Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- esphome/components/nextion/nextion.h | 10 +++++----- esphome/components/nextion/nextion_commands.cpp | 16 ++++++---------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 61068b52fc..7e8f563a96 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -171,7 +171,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * This will change the image of the component `pic` to the image with ID `4`. */ - void set_component_picture(const char *component, uint8_t picture_id); + void set_component_picture(const char *component, uint8_t picture_id) { set_component_picc(component, picture_id); }; /** * Set the background color of a component. @@ -374,7 +374,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * This will change the picture id of the component `textview`. */ - void set_component_pic(const char *component, uint8_t pic_id); + void set_component_pic(const char *component, uint16_t pic_id); /** * Set the background picture id of component. @@ -388,7 +388,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * This will change the background picture id of the component `textview`. */ - void set_component_picc(const char *component, uint8_t pic_id); + void set_component_picc(const char *component, uint16_t pic_id); /** * Set the font color of a component. @@ -910,7 +910,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * Draws a QR code with a Wi-Fi network credentials starting at the given coordinates (25,25). */ void qrcode(uint16_t x1, uint16_t y1, const char *content, uint16_t size = 200, uint16_t background_color = 65535, - uint16_t foreground_color = 0, uint8_t logo_pic = -1, uint8_t border_width = 8); + uint16_t foreground_color = 0, int32_t logo_pic = -1, uint8_t border_width = 8); /** * Draws a QR code in the screen @@ -935,7 +935,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe */ void qrcode(uint16_t x1, uint16_t y1, const char *content, uint16_t size, Color background_color = Color(255, 255, 255), Color foreground_color = Color(0, 0, 0), - uint8_t logo_pic = -1, uint8_t border_width = 8); + int32_t logo_pic = -1, uint8_t border_width = 8); /** Set the brightness of the backlight. * diff --git a/esphome/components/nextion/nextion_commands.cpp b/esphome/components/nextion/nextion_commands.cpp index cfaae7e3e0..2adf314a2e 100644 --- a/esphome/components/nextion/nextion_commands.cpp +++ b/esphome/components/nextion/nextion_commands.cpp @@ -143,12 +143,12 @@ void Nextion::set_component_pressed_font_color(const char *component, Color colo } // Set picture -void Nextion::set_component_pic(const char *component, uint8_t pic_id) { - this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.pic=%" PRIu8, component, pic_id); +void Nextion::set_component_pic(const char *component, uint16_t pic_id) { + this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.pic=%" PRIu16, component, pic_id); } -void Nextion::set_component_picc(const char *component, uint8_t pic_id) { - this->add_no_result_to_queue_with_printf_("set_component_picc", "%s.picc=%" PRIu8, component, pic_id); +void Nextion::set_component_picc(const char *component, uint16_t pic_id) { + this->add_no_result_to_queue_with_printf_("set_component_picc", "%s.picc=%" PRIu16, component, pic_id); } // Set video @@ -217,10 +217,6 @@ void Nextion::disable_component_touch(const char *component) { this->add_no_result_to_queue_with_printf_("disable_component_touch", "tsw %s,0", component); } -void Nextion::set_component_picture(const char *component, uint8_t picture_id) { - this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.pic=%" PRIu8, component, picture_id); -} - void Nextion::set_component_text(const char *component, const char *text) { this->add_no_result_to_queue_with_printf_("set_component_text", "%s.txt=\"%s\"", component, text); } @@ -330,14 +326,14 @@ void Nextion::filled_circle(uint16_t center_x, uint16_t center_y, uint16_t radiu } void Nextion::qrcode(uint16_t x1, uint16_t y1, const char *content, uint16_t size, uint16_t background_color, - uint16_t foreground_color, uint8_t logo_pic, uint8_t border_width) { + uint16_t foreground_color, int32_t logo_pic, uint8_t border_width) { this->add_no_result_to_queue_with_printf_( "qrcode", "qrcode %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu8 ",%" PRIu8 ",\"%s\"", x1, y1, size, background_color, foreground_color, logo_pic, border_width, content); } void Nextion::qrcode(uint16_t x1, uint16_t y1, const char *content, uint16_t size, Color background_color, - Color foreground_color, uint8_t logo_pic, uint8_t border_width) { + Color foreground_color, int32_t logo_pic, uint8_t border_width) { this->add_no_result_to_queue_with_printf_( "qrcode", "qrcode %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu8 ",%" PRIu8 ",\"%s\"", x1, y1, size, display::ColorUtil::color_to_565(background_color), display::ColorUtil::color_to_565(foreground_color), From acda5bcd5aa38d7a3e7b1d5577dcb149d84be725 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Dec 2025 14:34:12 -0600 Subject: [PATCH 0549/1145] [text] Add component tests with pattern coverage (#12345) --- tests/components/text/common.yaml | 25 +++++++++++++++++++ tests/components/text/test.esp32-idf.yaml | 2 ++ tests/components/text/test.esp8266-ard.yaml | 2 ++ .../fixtures/api_message_size_batching.yaml | 1 + .../test_api_message_size_batching.py | 3 +++ 5 files changed, 33 insertions(+) create mode 100644 tests/components/text/common.yaml create mode 100644 tests/components/text/test.esp32-idf.yaml create mode 100644 tests/components/text/test.esp8266-ard.yaml diff --git a/tests/components/text/common.yaml b/tests/components/text/common.yaml new file mode 100644 index 0000000000..26618be03a --- /dev/null +++ b/tests/components/text/common.yaml @@ -0,0 +1,25 @@ +text: + - platform: template + name: "Test Text" + id: test_text + optimistic: true + min_length: 0 + max_length: 100 + mode: text + + - platform: template + name: "Test Text with Pattern" + id: test_text_pattern + optimistic: true + min_length: 1 + max_length: 50 + pattern: "[A-Za-z0-9 ]+" + mode: text + + - platform: template + name: "Test Password" + id: test_password + optimistic: true + min_length: 8 + max_length: 32 + mode: password diff --git a/tests/components/text/test.esp32-idf.yaml b/tests/components/text/test.esp32-idf.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/text/test.esp32-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/text/test.esp8266-ard.yaml b/tests/components/text/test.esp8266-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/text/test.esp8266-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/integration/fixtures/api_message_size_batching.yaml b/tests/integration/fixtures/api_message_size_batching.yaml index c730dc1aa3..0fed311e63 100644 --- a/tests/integration/fixtures/api_message_size_batching.yaml +++ b/tests/integration/fixtures/api_message_size_batching.yaml @@ -143,6 +143,7 @@ text: mode: text min_length: 0 max_length: 255 + pattern: "[A-Za-z0-9 ]+" initial_value: "Initial value" update_interval: 5.0s diff --git a/tests/integration/test_api_message_size_batching.py b/tests/integration/test_api_message_size_batching.py index f7859eb902..5b123318c4 100644 --- a/tests/integration/test_api_message_size_batching.py +++ b/tests/integration/test_api_message_size_batching.py @@ -141,6 +141,9 @@ async def test_api_message_size_batching( assert text_input.max_length == 255, ( f"Expected max_length 255, got {text_input.max_length}" ) + assert text_input.pattern == "[A-Za-z0-9 ]+", ( + f"Expected pattern '[A-Za-z0-9 ]+', got '{text_input.pattern}'" + ) # Verify total entity count - messages of various sizes were batched successfully # We have: 3 selects + 3 text sensors + 1 text input + 1 number = 8 total From f015130f2eca456ee34836ca30af757fc6e59d24 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Sun, 7 Dec 2025 21:59:59 +0100 Subject: [PATCH 0550/1145] [esp8266] Allow use of recvfrom for esphome sockets (#12342) --- esphome/components/socket/lwip_raw_tcp_impl.cpp | 6 ++++++ esphome/components/socket/lwip_sockets_impl.cpp | 3 +++ esphome/components/socket/socket.h | 2 -- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index e0d93d8e2f..e57af91b77 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -334,6 +334,12 @@ class LWIPRawImpl : public Socket { } return ret; } + + ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override { + errno = ENOTSUP; + return -1; + } + ssize_t internal_write(const void *buf, size_t len) { if (pcb_ == nullptr) { errno = ECONNRESET; diff --git a/esphome/components/socket/lwip_sockets_impl.cpp b/esphome/components/socket/lwip_sockets_impl.cpp index f8a1cbc046..d94c1fb2ff 100644 --- a/esphome/components/socket/lwip_sockets_impl.cpp +++ b/esphome/components/socket/lwip_sockets_impl.cpp @@ -113,6 +113,9 @@ class LwIPSocketImpl : public Socket { } int listen(int backlog) override { return lwip_listen(fd_, backlog); } ssize_t read(void *buf, size_t len) override { return lwip_read(fd_, buf, len); } + ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override { + return lwip_recvfrom(fd_, buf, len, 0, addr, addr_len); + } ssize_t readv(const struct iovec *iov, int iovcnt) override { return lwip_readv(fd_, iov, iovcnt); } ssize_t write(const void *buf, size_t len) override { return lwip_write(fd_, buf, len); } ssize_t send(void *buf, size_t len, int flags) { return lwip_send(fd_, buf, len, flags); } diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 8f0d28362e..78a89fe008 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -39,9 +39,7 @@ class Socket { virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0; virtual int listen(int backlog) = 0; virtual ssize_t read(void *buf, size_t len) = 0; -#ifdef USE_SOCKET_IMPL_BSD_SOCKETS virtual ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) = 0; -#endif virtual ssize_t readv(const struct iovec *iov, int iovcnt) = 0; virtual ssize_t write(const void *buf, size_t len) = 0; virtual ssize_t writev(const struct iovec *iov, int iovcnt) = 0; From 3d5d89ff002f8bc12a6adcf760ea817c76950831 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Dec 2025 15:09:25 -0600 Subject: [PATCH 0551/1145] [template] Use C++17 nested namespace syntax (#12346) --- .../alarm_control_panel/template_alarm_control_panel.cpp | 6 ++---- .../alarm_control_panel/template_alarm_control_panel.h | 6 ++---- .../template/binary_sensor/template_binary_sensor.cpp | 6 ++---- .../template/binary_sensor/template_binary_sensor.h | 6 ++---- esphome/components/template/button/template_button.h | 6 ++---- esphome/components/template/cover/template_cover.cpp | 6 ++---- esphome/components/template/cover/template_cover.h | 6 ++---- esphome/components/template/datetime/template_date.cpp | 6 ++---- esphome/components/template/datetime/template_date.h | 6 ++---- esphome/components/template/datetime/template_datetime.cpp | 6 ++---- esphome/components/template/datetime/template_datetime.h | 6 ++---- esphome/components/template/datetime/template_time.cpp | 6 ++---- esphome/components/template/datetime/template_time.h | 6 ++---- esphome/components/template/event/template_event.h | 6 ++---- esphome/components/template/fan/template_fan.cpp | 6 ++---- esphome/components/template/fan/template_fan.h | 6 ++---- esphome/components/template/lock/automation.h | 6 ++---- esphome/components/template/lock/template_lock.cpp | 6 ++---- esphome/components/template/lock/template_lock.h | 6 ++---- esphome/components/template/number/template_number.cpp | 6 ++---- esphome/components/template/number/template_number.h | 6 ++---- esphome/components/template/output/template_output.h | 6 ++---- esphome/components/template/select/template_select.cpp | 6 ++---- esphome/components/template/select/template_select.h | 6 ++---- esphome/components/template/sensor/template_sensor.cpp | 6 ++---- esphome/components/template/sensor/template_sensor.h | 6 ++---- esphome/components/template/switch/template_switch.cpp | 6 ++---- esphome/components/template/switch/template_switch.h | 6 ++---- esphome/components/template/text/template_text.cpp | 6 ++---- esphome/components/template/text/template_text.h | 6 ++---- .../template/text_sensor/template_text_sensor.cpp | 6 ++---- .../components/template/text_sensor/template_text_sensor.h | 6 ++---- esphome/components/template/valve/automation.h | 6 ++---- esphome/components/template/valve/template_valve.cpp | 6 ++---- esphome/components/template/valve/template_valve.h | 6 ++---- 35 files changed, 70 insertions(+), 140 deletions(-) diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp index f025435261..50e43da8d5 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp @@ -6,8 +6,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { using namespace esphome::alarm_control_panel; @@ -286,5 +285,4 @@ void TemplateAlarmControlPanel::control(const AlarmControlPanelCall &call) { } } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h index 80ce34b8ae..bdd3747372 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h @@ -14,8 +14,7 @@ #include "esphome/components/binary_sensor/binary_sensor.h" #endif -namespace esphome { -namespace template_ { +namespace esphome::template_ { #ifdef USE_BINARY_SENSOR enum BinarySensorFlags : uint16_t { @@ -169,5 +168,4 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl void arm_(optional code, alarm_control_panel::AlarmControlPanelState state, uint32_t delay); }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/binary_sensor/template_binary_sensor.cpp b/esphome/components/template/binary_sensor/template_binary_sensor.cpp index 806aed49b1..b63121d2db 100644 --- a/esphome/components/template/binary_sensor/template_binary_sensor.cpp +++ b/esphome/components/template/binary_sensor/template_binary_sensor.cpp @@ -1,8 +1,7 @@ #include "template_binary_sensor.h" #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { static const char *const TAG = "template.binary_sensor"; @@ -23,5 +22,4 @@ void TemplateBinarySensor::loop() { void TemplateBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Template Binary Sensor", this); } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/binary_sensor/template_binary_sensor.h b/esphome/components/template/binary_sensor/template_binary_sensor.h index 0af709b097..c78a95e0e3 100644 --- a/esphome/components/template/binary_sensor/template_binary_sensor.h +++ b/esphome/components/template/binary_sensor/template_binary_sensor.h @@ -4,8 +4,7 @@ #include "esphome/core/template_lambda.h" #include "esphome/components/binary_sensor/binary_sensor.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateBinarySensor final : public Component, public binary_sensor::BinarySensor { public: @@ -21,5 +20,4 @@ class TemplateBinarySensor final : public Component, public binary_sensor::Binar TemplateLambda f_; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/button/template_button.h b/esphome/components/template/button/template_button.h index 5bda82c58f..f64a85eef0 100644 --- a/esphome/components/template/button/template_button.h +++ b/esphome/components/template/button/template_button.h @@ -2,8 +2,7 @@ #include "esphome/components/button/button.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateButton final : public button::Button { public: @@ -11,5 +10,4 @@ class TemplateButton final : public button::Button { void press_action() override{}; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/cover/template_cover.cpp b/esphome/components/template/cover/template_cover.cpp index a87f28ccec..9c8a8fc9bc 100644 --- a/esphome/components/template/cover/template_cover.cpp +++ b/esphome/components/template/cover/template_cover.cpp @@ -1,8 +1,7 @@ #include "template_cover.h" #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { using namespace esphome::cover; @@ -133,5 +132,4 @@ void TemplateCover::stop_prev_trigger_() { } } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/cover/template_cover.h b/esphome/components/template/cover/template_cover.h index 125c67bb86..9c4a787283 100644 --- a/esphome/components/template/cover/template_cover.h +++ b/esphome/components/template/cover/template_cover.h @@ -5,8 +5,7 @@ #include "esphome/core/template_lambda.h" #include "esphome/components/cover/cover.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { enum TemplateCoverRestoreMode { COVER_NO_RESTORE, @@ -63,5 +62,4 @@ class TemplateCover final : public cover::Cover, public Component { bool has_tilt_{false}; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/datetime/template_date.cpp b/esphome/components/template/datetime/template_date.cpp index 3f6626e847..303d5ae2b0 100644 --- a/esphome/components/template/datetime/template_date.cpp +++ b/esphome/components/template/datetime/template_date.cpp @@ -4,8 +4,7 @@ #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { static const char *const TAG = "template.date"; @@ -104,7 +103,6 @@ void TemplateDate::dump_config() { LOG_UPDATE_INTERVAL(this); } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ #endif // USE_DATETIME_DATE diff --git a/esphome/components/template/datetime/template_date.h b/esphome/components/template/datetime/template_date.h index fe64b0ba14..0379a9bc67 100644 --- a/esphome/components/template/datetime/template_date.h +++ b/esphome/components/template/datetime/template_date.h @@ -11,8 +11,7 @@ #include "esphome/core/time.h" #include "esphome/core/template_lambda.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateDate final : public datetime::DateEntity, public PollingComponent { public: @@ -41,7 +40,6 @@ class TemplateDate final : public datetime::DateEntity, public PollingComponent ESPPreferenceObject pref_; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ #endif // USE_DATETIME_DATE diff --git a/esphome/components/template/datetime/template_datetime.cpp b/esphome/components/template/datetime/template_datetime.cpp index 62f842a7ad..81a823f53e 100644 --- a/esphome/components/template/datetime/template_datetime.cpp +++ b/esphome/components/template/datetime/template_datetime.cpp @@ -4,8 +4,7 @@ #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { static const char *const TAG = "template.datetime"; @@ -143,7 +142,6 @@ void TemplateDateTime::dump_config() { LOG_UPDATE_INTERVAL(this); } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ #endif // USE_DATETIME_DATETIME diff --git a/esphome/components/template/datetime/template_datetime.h b/esphome/components/template/datetime/template_datetime.h index c44bd85265..b7eb490933 100644 --- a/esphome/components/template/datetime/template_datetime.h +++ b/esphome/components/template/datetime/template_datetime.h @@ -11,8 +11,7 @@ #include "esphome/core/time.h" #include "esphome/core/template_lambda.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateDateTime final : public datetime::DateTimeEntity, public PollingComponent { public: @@ -41,7 +40,6 @@ class TemplateDateTime final : public datetime::DateTimeEntity, public PollingCo ESPPreferenceObject pref_; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ #endif // USE_DATETIME_DATETIME diff --git a/esphome/components/template/datetime/template_time.cpp b/esphome/components/template/datetime/template_time.cpp index dab28d01cc..21f843dcc7 100644 --- a/esphome/components/template/datetime/template_time.cpp +++ b/esphome/components/template/datetime/template_time.cpp @@ -4,8 +4,7 @@ #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { static const char *const TAG = "template.time"; @@ -104,7 +103,6 @@ void TemplateTime::dump_config() { LOG_UPDATE_INTERVAL(this); } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ #endif // USE_DATETIME_TIME diff --git a/esphome/components/template/datetime/template_time.h b/esphome/components/template/datetime/template_time.h index 0c95330d27..cb83b1b3e5 100644 --- a/esphome/components/template/datetime/template_time.h +++ b/esphome/components/template/datetime/template_time.h @@ -11,8 +11,7 @@ #include "esphome/core/time.h" #include "esphome/core/template_lambda.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateTime final : public datetime::TimeEntity, public PollingComponent { public: @@ -41,7 +40,6 @@ class TemplateTime final : public datetime::TimeEntity, public PollingComponent ESPPreferenceObject pref_; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ #endif // USE_DATETIME_TIME diff --git a/esphome/components/template/event/template_event.h b/esphome/components/template/event/template_event.h index 5467a64141..fe83dc9f34 100644 --- a/esphome/components/template/event/template_event.h +++ b/esphome/components/template/event/template_event.h @@ -3,10 +3,8 @@ #include "esphome/core/component.h" #include "esphome/components/event/event.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateEvent final : public Component, public event::Event {}; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/fan/template_fan.cpp b/esphome/components/template/fan/template_fan.cpp index eba4c673b5..384e6b0ca1 100644 --- a/esphome/components/template/fan/template_fan.cpp +++ b/esphome/components/template/fan/template_fan.cpp @@ -1,8 +1,7 @@ #include "template_fan.h" #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { static const char *const TAG = "template.fan"; @@ -34,5 +33,4 @@ void TemplateFan::control(const fan::FanCall &call) { this->publish_state(); } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/fan/template_fan.h b/esphome/components/template/fan/template_fan.h index 052b385b93..b7e1d4ab5a 100644 --- a/esphome/components/template/fan/template_fan.h +++ b/esphome/components/template/fan/template_fan.h @@ -3,8 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/fan/fan.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateFan final : public Component, public fan::Fan { public: @@ -27,5 +26,4 @@ class TemplateFan final : public Component, public fan::Fan { std::vector preset_modes_{}; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/lock/automation.h b/esphome/components/template/lock/automation.h index bd110b7b0c..42a2a826e2 100644 --- a/esphome/components/template/lock/automation.h +++ b/esphome/components/template/lock/automation.h @@ -4,8 +4,7 @@ #include "esphome/core/automation.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { template class TemplateLockPublishAction : public Action, public Parented { public: @@ -14,5 +13,4 @@ template class TemplateLockPublishAction : public Action, void play(const Ts &...x) override { this->parent_->publish_state(this->state_.value(x...)); } }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/lock/template_lock.cpp b/esphome/components/template/lock/template_lock.cpp index 8ed87b9736..de8f9b762c 100644 --- a/esphome/components/template/lock/template_lock.cpp +++ b/esphome/components/template/lock/template_lock.cpp @@ -1,8 +1,7 @@ #include "template_lock.h" #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { using namespace esphome::lock; @@ -56,5 +55,4 @@ void TemplateLock::dump_config() { ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/lock/template_lock.h b/esphome/components/template/lock/template_lock.h index ac10794e4d..f4396c2c5d 100644 --- a/esphome/components/template/lock/template_lock.h +++ b/esphome/components/template/lock/template_lock.h @@ -5,8 +5,7 @@ #include "esphome/core/template_lambda.h" #include "esphome/components/lock/lock.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateLock final : public lock::Lock, public Component { public: @@ -36,5 +35,4 @@ class TemplateLock final : public lock::Lock, public Component { Trigger<> *prev_trigger_{nullptr}; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index 145a89a2f7..76fef82225 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -1,8 +1,7 @@ #include "template_number.h" #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { static const char *const TAG = "template.number"; @@ -51,5 +50,4 @@ void TemplateNumber::dump_config() { LOG_UPDATE_INTERVAL(this); } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/number/template_number.h b/esphome/components/template/number/template_number.h index 876ec96b3b..42c27fc3ca 100644 --- a/esphome/components/template/number/template_number.h +++ b/esphome/components/template/number/template_number.h @@ -6,8 +6,7 @@ #include "esphome/core/preferences.h" #include "esphome/core/template_lambda.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateNumber final : public number::Number, public PollingComponent { public: @@ -34,5 +33,4 @@ class TemplateNumber final : public number::Number, public PollingComponent { ESPPreferenceObject pref_; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/output/template_output.h b/esphome/components/template/output/template_output.h index 9ecfc446b9..e536660b02 100644 --- a/esphome/components/template/output/template_output.h +++ b/esphome/components/template/output/template_output.h @@ -4,8 +4,7 @@ #include "esphome/components/output/binary_output.h" #include "esphome/components/output/float_output.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateBinaryOutput final : public output::BinaryOutput { public: @@ -27,5 +26,4 @@ class TemplateFloatOutput final : public output::FloatOutput { Trigger *trigger_ = new Trigger(); }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/select/template_select.cpp b/esphome/components/template/select/template_select.cpp index 112f24e919..9d2df0956b 100644 --- a/esphome/components/template/select/template_select.cpp +++ b/esphome/components/template/select/template_select.cpp @@ -1,8 +1,7 @@ #include "template_select.h" #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { static const char *const TAG = "template.select"; @@ -63,5 +62,4 @@ void TemplateSelect::dump_config() { YESNO(this->optimistic_), this->option_at(this->initial_option_index_), YESNO(this->restore_value_)); } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/select/template_select.h b/esphome/components/template/select/template_select.h index cb5b546976..2757c51405 100644 --- a/esphome/components/template/select/template_select.h +++ b/esphome/components/template/select/template_select.h @@ -6,8 +6,7 @@ #include "esphome/core/preferences.h" #include "esphome/core/template_lambda.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateSelect final : public select::Select, public PollingComponent { public: @@ -34,5 +33,4 @@ class TemplateSelect final : public select::Select, public PollingComponent { ESPPreferenceObject pref_; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/sensor/template_sensor.cpp b/esphome/components/template/sensor/template_sensor.cpp index 1558ea9b15..313a163e38 100644 --- a/esphome/components/template/sensor/template_sensor.cpp +++ b/esphome/components/template/sensor/template_sensor.cpp @@ -2,8 +2,7 @@ #include "esphome/core/log.h" #include -namespace esphome { -namespace template_ { +namespace esphome::template_ { static const char *const TAG = "template.sensor"; @@ -24,5 +23,4 @@ void TemplateSensor::dump_config() { LOG_UPDATE_INTERVAL(this); } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/sensor/template_sensor.h b/esphome/components/template/sensor/template_sensor.h index 3ca965dde3..825a2b4ffa 100644 --- a/esphome/components/template/sensor/template_sensor.h +++ b/esphome/components/template/sensor/template_sensor.h @@ -4,8 +4,7 @@ #include "esphome/core/template_lambda.h" #include "esphome/components/sensor/sensor.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateSensor final : public sensor::Sensor, public PollingComponent { public: @@ -21,5 +20,4 @@ class TemplateSensor final : public sensor::Sensor, public PollingComponent { TemplateLambda f_; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/switch/template_switch.cpp b/esphome/components/template/switch/template_switch.cpp index 95e8692da5..cfa8798e75 100644 --- a/esphome/components/template/switch/template_switch.cpp +++ b/esphome/components/template/switch/template_switch.cpp @@ -1,8 +1,7 @@ #include "template_switch.h" #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { static const char *const TAG = "template.switch"; @@ -57,5 +56,4 @@ void TemplateSwitch::dump_config() { } void TemplateSwitch::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/switch/template_switch.h b/esphome/components/template/switch/template_switch.h index 35c18af448..91b7b396f6 100644 --- a/esphome/components/template/switch/template_switch.h +++ b/esphome/components/template/switch/template_switch.h @@ -5,8 +5,7 @@ #include "esphome/core/template_lambda.h" #include "esphome/components/switch/switch.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateSwitch final : public switch_::Switch, public Component { public: @@ -37,5 +36,4 @@ class TemplateSwitch final : public switch_::Switch, public Component { Trigger<> *prev_trigger_{nullptr}; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/text/template_text.cpp b/esphome/components/template/text/template_text.cpp index a917c72a14..556abbbd8b 100644 --- a/esphome/components/template/text/template_text.cpp +++ b/esphome/components/template/text/template_text.cpp @@ -1,8 +1,7 @@ #include "template_text.h" #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { static const char *const TAG = "template.text"; @@ -51,5 +50,4 @@ void TemplateText::dump_config() { LOG_UPDATE_INTERVAL(this); } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/text/template_text.h b/esphome/components/template/text/template_text.h index 1a0a66ed5b..178b410ed2 100644 --- a/esphome/components/template/text/template_text.h +++ b/esphome/components/template/text/template_text.h @@ -6,8 +6,7 @@ #include "esphome/core/preferences.h" #include "esphome/core/template_lambda.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { // We keep this separate so we don't have to template and duplicate // the text input for each different size flash allocation. @@ -84,5 +83,4 @@ class TemplateText final : public text::Text, public PollingComponent { TemplateTextSaverBase *pref_ = nullptr; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/text_sensor/template_text_sensor.cpp b/esphome/components/template/text_sensor/template_text_sensor.cpp index 024d0093a2..89a15b6081 100644 --- a/esphome/components/template/text_sensor/template_text_sensor.cpp +++ b/esphome/components/template/text_sensor/template_text_sensor.cpp @@ -1,8 +1,7 @@ #include "template_text_sensor.h" #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { static const char *const TAG = "template.text_sensor"; @@ -20,5 +19,4 @@ float TemplateTextSensor::get_setup_priority() const { return setup_priority::HA void TemplateTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Template Sensor", this); } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/text_sensor/template_text_sensor.h b/esphome/components/template/text_sensor/template_text_sensor.h index da5c518c7f..0538a7ec21 100644 --- a/esphome/components/template/text_sensor/template_text_sensor.h +++ b/esphome/components/template/text_sensor/template_text_sensor.h @@ -5,8 +5,7 @@ #include "esphome/core/template_lambda.h" #include "esphome/components/text_sensor/text_sensor.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { class TemplateTextSensor final : public text_sensor::TextSensor, public PollingComponent { public: @@ -22,5 +21,4 @@ class TemplateTextSensor final : public text_sensor::TextSensor, public PollingC TemplateLambda f_{}; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/valve/automation.h b/esphome/components/template/valve/automation.h index e3f394ac7c..a27e98b25c 100644 --- a/esphome/components/template/valve/automation.h +++ b/esphome/components/template/valve/automation.h @@ -4,8 +4,7 @@ #include "esphome/core/automation.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { template class TemplateValvePublishAction : public Action, public Parented { TEMPLATABLE_VALUE(float, position) @@ -20,5 +19,4 @@ template class TemplateValvePublishAction : public Action } }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/valve/template_valve.cpp b/esphome/components/template/valve/template_valve.cpp index b91b32473e..4e772f9253 100644 --- a/esphome/components/template/valve/template_valve.cpp +++ b/esphome/components/template/valve/template_valve.cpp @@ -1,8 +1,7 @@ #include "template_valve.h" #include "esphome/core/log.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { using namespace esphome::valve; @@ -127,5 +126,4 @@ void TemplateValve::stop_prev_trigger_() { } } -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ diff --git a/esphome/components/template/valve/template_valve.h b/esphome/components/template/valve/template_valve.h index c452648193..4205682a2a 100644 --- a/esphome/components/template/valve/template_valve.h +++ b/esphome/components/template/valve/template_valve.h @@ -5,8 +5,7 @@ #include "esphome/core/template_lambda.h" #include "esphome/components/valve/valve.h" -namespace esphome { -namespace template_ { +namespace esphome::template_ { enum TemplateValveRestoreMode { VALVE_NO_RESTORE, @@ -57,5 +56,4 @@ class TemplateValve final : public valve::Valve, public Component { bool has_position_{false}; }; -} // namespace template_ -} // namespace esphome +} // namespace esphome::template_ From 68a7634228883a951f25e12a816796a59ca66499 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Dec 2025 15:33:15 -0600 Subject: [PATCH 0552/1145] [text] Store pattern as const char* to reduce memory usage (#12335) --- esphome/components/template/text/template_text.cpp | 2 +- esphome/components/text/text_traits.h | 10 +++++----- esphome/components/web_server/web_server.cpp | 2 +- esphome/components/web_server/web_server_v1.cpp | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/template/text/template_text.cpp b/esphome/components/template/text/template_text.cpp index 556abbbd8b..32ed8f047b 100644 --- a/esphome/components/template/text/template_text.cpp +++ b/esphome/components/template/text/template_text.cpp @@ -15,7 +15,7 @@ void TemplateText::setup() { uint32_t key = this->get_preference_hash(); key += this->traits.get_min_length() << 2; key += this->traits.get_max_length() << 4; - key += fnv1_hash(this->traits.get_pattern()) << 6; + key += fnv1_hash(this->traits.get_pattern_c_str()) << 6; this->pref_->setup(key, value); } if (!value.empty()) diff --git a/esphome/components/text/text_traits.h b/esphome/components/text/text_traits.h index ceaba2dead..473daafb8e 100644 --- a/esphome/components/text/text_traits.h +++ b/esphome/components/text/text_traits.h @@ -1,8 +1,7 @@ #pragma once -#include +#include -#include "esphome/core/helpers.h" #include "esphome/core/string_ref.h" namespace esphome { @@ -22,8 +21,9 @@ class TextTraits { int get_max_length() const { return this->max_length_; } // Set/get the pattern. - void set_pattern(std::string pattern) { this->pattern_ = std::move(pattern); } - std::string get_pattern() const { return this->pattern_; } + void set_pattern(const char *pattern) { this->pattern_ = pattern; } + std::string get_pattern() const { return std::string(this->pattern_); } + const char *get_pattern_c_str() const { return this->pattern_; } StringRef get_pattern_ref() const { return StringRef(this->pattern_); } // Set/get the frontend mode. @@ -33,7 +33,7 @@ class TextTraits { protected: int min_length_; int max_length_; - std::string pattern_; + const char *pattern_{""}; TextMode mode_{TEXT_MODE_TEXT}; }; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 1f3605a082..ca3aa21a95 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1211,7 +1211,7 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json set_json_icon_state_value(root, obj, "text", state, value, start_config); root[ESPHOME_F("min_length")] = obj->traits.get_min_length(); root[ESPHOME_F("max_length")] = obj->traits.get_max_length(); - root[ESPHOME_F("pattern")] = obj->traits.get_pattern(); + root[ESPHOME_F("pattern")] = obj->traits.get_pattern_c_str(); if (start_config == DETAIL_ALL) { root[ESPHOME_F("mode")] = (int) obj->traits.get_mode(); this->add_sorting_info_(root, obj); diff --git a/esphome/components/web_server/web_server_v1.cpp b/esphome/components/web_server/web_server_v1.cpp index 870a338620..486c38a2ab 100644 --- a/esphome/components/web_server/web_server_v1.cpp +++ b/esphome/components/web_server/web_server_v1.cpp @@ -142,7 +142,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream.print(R"(" maxlength=")"); stream.print(text->traits.get_max_length()); stream.print(R"(" pattern=")"); - stream.print(text->traits.get_pattern().c_str()); + stream.print(text->traits.get_pattern_c_str()); stream.print(R"(" value=")"); stream.print(text->state.c_str()); stream.print(R"("/>)"); From 1134251c32db10aa56b6f74fa4d57046be5822bf Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Mon, 8 Dec 2025 00:55:36 +0100 Subject: [PATCH 0553/1145] [micronova] Set update_interval on entities instead on hub (#12226) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- CODEOWNERS | 2 +- esphome/components/micronova/__init__.py | 42 ++++--- .../components/micronova/button/__init__.py | 8 +- .../micronova/button/micronova_button.cpp | 2 +- .../micronova/button/micronova_button.h | 5 +- esphome/components/micronova/micronova.cpp | 24 ++-- esphome/components/micronova/micronova.h | 34 ++--- .../components/micronova/number/__init__.py | 47 ++++--- .../micronova/number/micronova_number.cpp | 2 +- .../micronova/number/micronova_number.h | 9 +- .../components/micronova/sensor/__init__.py | 117 +++++++----------- .../micronova/sensor/micronova_sensor.h | 9 +- .../components/micronova/switch/__init__.py | 8 +- .../micronova/switch/micronova_switch.cpp | 2 +- .../micronova/switch/micronova_switch.h | 5 +- .../micronova/text_sensor/__init__.py | 21 ++-- .../text_sensor/micronova_text_sensor.h | 9 +- tests/components/micronova/common.yaml | 7 ++ 18 files changed, 185 insertions(+), 168 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 4f9fb7ef55..1fb6e111b7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -307,7 +307,7 @@ esphome/components/md5/* @esphome/core esphome/components/mdns/* @esphome/core esphome/components/media_player/* @jesserockz esphome/components/micro_wake_word/* @jesserockz @kahrendt -esphome/components/micronova/* @jorre05 +esphome/components/micronova/* @edenhaus @jorre05 esphome/components/microphone/* @jesserockz @kahrendt esphome/components/mics_4514/* @jesserockz esphome/components/midea/* @dudanov diff --git a/esphome/components/micronova/__init__.py b/esphome/components/micronova/__init__.py index 31abc11abf..9b01ae97e3 100644 --- a/esphome/components/micronova/__init__.py +++ b/esphome/components/micronova/__init__.py @@ -4,7 +4,7 @@ from esphome.components import uart import esphome.config_validation as cv from esphome.const import CONF_ID -CODEOWNERS = ["@jorre05"] +CODEOWNERS = ["@jorre05", "@edenhaus"] DEPENDENCIES = ["uart"] @@ -12,6 +12,7 @@ CONF_MICRONOVA_ID = "micronova_id" CONF_ENABLE_RX_PIN = "enable_rx_pin" CONF_MEMORY_LOCATION = "memory_location" CONF_MEMORY_ADDRESS = "memory_address" +DEFAULT_POLLING_INTERVAL = "60s" micronova_ns = cg.esphome_ns.namespace("micronova") @@ -31,22 +32,24 @@ MICRONOVA_FUNCTIONS_ENUM = { "STOVE_FUNCTION_CUSTOM": MicroNovaFunctions.STOVE_FUNCTION_CUSTOM, } -MicroNova = micronova_ns.class_("MicroNova", cg.PollingComponent, uart.UARTDevice) +MicroNova = micronova_ns.class_("MicroNova", cg.Component, uart.UARTDevice) +MicroNovaListener = micronova_ns.class_("MicroNovaListener", cg.PollingComponent) -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(MicroNova), - cv.Required(CONF_ENABLE_RX_PIN): pins.gpio_output_pin_schema, - } - ) - .extend(uart.UART_DEVICE_SCHEMA) - .extend(cv.polling_component_schema("60s")) -) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(MicroNova), + cv.Required(CONF_ENABLE_RX_PIN): pins.gpio_output_pin_schema, + } +).extend(uart.UART_DEVICE_SCHEMA) -def MICRONOVA_LISTENER_SCHEMA(default_memory_location, default_memory_address): - return cv.Schema( +def MICRONOVA_ADDRESS_SCHEMA( + *, + default_memory_location: int, + default_memory_address: int, + is_polling_component: bool, +): + schema = cv.Schema( { cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova), cv.Optional( @@ -57,6 +60,17 @@ def MICRONOVA_LISTENER_SCHEMA(default_memory_location, default_memory_address): ): cv.hex_int_range(), } ) + if is_polling_component: + schema = schema.extend(cv.polling_component_schema(DEFAULT_POLLING_INTERVAL)) + return schema + + +async def to_code_micronova_listener(mv, var, config, micronova_function): + await cg.register_component(var, config) + cg.add(mv.register_micronova_listener(var)) + cg.add(var.set_memory_location(config[CONF_MEMORY_LOCATION])) + cg.add(var.set_memory_address(config[CONF_MEMORY_ADDRESS])) + cg.add(var.set_function(micronova_function)) async def to_code(config): diff --git a/esphome/components/micronova/button/__init__.py b/esphome/components/micronova/button/__init__.py index 813d24efef..dd57c9ec4f 100644 --- a/esphome/components/micronova/button/__init__.py +++ b/esphome/components/micronova/button/__init__.py @@ -6,7 +6,7 @@ from .. import ( CONF_MEMORY_ADDRESS, CONF_MEMORY_LOCATION, CONF_MICRONOVA_ID, - MICRONOVA_LISTENER_SCHEMA, + MICRONOVA_ADDRESS_SCHEMA, MicroNova, MicroNovaFunctions, micronova_ns, @@ -24,8 +24,10 @@ CONFIG_SCHEMA = cv.Schema( MicroNovaButton, ) .extend( - MICRONOVA_LISTENER_SCHEMA( - default_memory_location=0xA0, default_memory_address=0x7D + MICRONOVA_ADDRESS_SCHEMA( + default_memory_location=0xA0, + default_memory_address=0x7D, + is_polling_component=False, ) ) .extend({cv.Required(CONF_MEMORY_DATA): cv.hex_int_range()}), diff --git a/esphome/components/micronova/button/micronova_button.cpp b/esphome/components/micronova/button/micronova_button.cpp index 147fef37bd..c78b4024f9 100644 --- a/esphome/components/micronova/button/micronova_button.cpp +++ b/esphome/components/micronova/button/micronova_button.cpp @@ -10,7 +10,7 @@ void MicroNovaButton::press_action() { default: break; } - this->micronova_->update(); + this->micronova_->request_update_listeners(); } } // namespace esphome::micronova diff --git a/esphome/components/micronova/button/micronova_button.h b/esphome/components/micronova/button/micronova_button.h index 5c1d7d8455..951ae8bba3 100644 --- a/esphome/components/micronova/button/micronova_button.h +++ b/esphome/components/micronova/button/micronova_button.h @@ -9,7 +9,10 @@ namespace esphome::micronova { class MicroNovaButton : public Component, public button::Button, public MicroNovaButtonListener { public: MicroNovaButton(MicroNova *m) : MicroNovaButtonListener(m) {} - void dump_config() override { LOG_BUTTON("", "Micronova button", this); } + void dump_config() override { + LOG_BUTTON("", "Micronova button", this); + this->dump_base_config(); + } void set_memory_data(uint8_t f) { this->memory_data_ = f; } uint8_t get_memory_data() { return this->memory_data_; } diff --git a/esphome/components/micronova/micronova.cpp b/esphome/components/micronova/micronova.cpp index 52b719bff2..7343bc90ba 100644 --- a/esphome/components/micronova/micronova.cpp +++ b/esphome/components/micronova/micronova.cpp @@ -3,6 +3,18 @@ namespace esphome::micronova { +void MicroNovaBaseListener::dump_base_config() { + ESP_LOGCONFIG(TAG, + " Memory Location: %02X\n" + " Memory Address: %02X", + this->memory_location_, this->memory_address_); +} + +void MicroNovaListener::dump_base_config() { + MicroNovaBaseListener::dump_base_config(); + LOG_UPDATE_INTERVAL(this); +} + void MicroNova::setup() { if (this->enable_rx_pin_ != nullptr) { this->enable_rx_pin_->setup(); @@ -21,16 +33,10 @@ void MicroNova::dump_config() { if (this->enable_rx_pin_ != nullptr) { LOG_PIN(" Enable RX Pin: ", this->enable_rx_pin_); } - - for (auto &mv_sensor : this->micronova_listeners_) { - mv_sensor->dump_config(); - ESP_LOGCONFIG(TAG, " sensor location:%02X, address:%02X", mv_sensor->get_memory_location(), - mv_sensor->get_memory_address()); - } } -void MicroNova::update() { - ESP_LOGD(TAG, "Schedule sensor update"); +void MicroNova::request_update_listeners() { + ESP_LOGD(TAG, "Schedule listener update"); for (auto &mv_listener : this->micronova_listeners_) { mv_listener->set_needs_update(true); } @@ -61,7 +67,7 @@ void MicroNova::loop() { } } -void MicroNova::request_address(uint8_t location, uint8_t address, MicroNovaSensorListener *listener) { +void MicroNova::request_address(uint8_t location, uint8_t address, MicroNovaListener *listener) { uint8_t write_data[2] = {0, 0}; uint8_t trash_rx; diff --git a/esphome/components/micronova/micronova.h b/esphome/components/micronova/micronova.h index acb85fad3c..7992bff2d3 100644 --- a/esphome/components/micronova/micronova.h +++ b/esphome/components/micronova/micronova.h @@ -49,7 +49,6 @@ class MicroNovaBaseListener { public: MicroNovaBaseListener() {} MicroNovaBaseListener(MicroNova *m) { this->micronova_ = m; } - virtual void dump_config(); void set_micronova_object(MicroNova *m) { this->micronova_ = m; } @@ -62,6 +61,8 @@ class MicroNovaBaseListener { void set_memory_address(uint8_t a) { this->memory_address_ = a; } uint8_t get_memory_address() { return this->memory_address_; } + void dump_base_config(); + protected: MicroNova *micronova_{nullptr}; MicroNovaFunctions function_ = MicroNovaFunctions::STOVE_FUNCTION_VOID; @@ -69,28 +70,19 @@ class MicroNovaBaseListener { uint8_t memory_address_ = 0; }; -class MicroNovaSensorListener : public MicroNovaBaseListener { +class MicroNovaListener : public MicroNovaBaseListener, public PollingComponent { public: - MicroNovaSensorListener() {} - MicroNovaSensorListener(MicroNova *m) : MicroNovaBaseListener(m) {} + MicroNovaListener() {} + MicroNovaListener(MicroNova *m) : MicroNovaBaseListener(m) {} virtual void request_value_from_stove() = 0; virtual void process_value_from_stove(int value_from_stove) = 0; void set_needs_update(bool u) { this->needs_update_ = u; } bool get_needs_update() { return this->needs_update_; } - protected: - bool needs_update_ = false; -}; + void update() override { this->set_needs_update(true); } -class MicroNovaNumberListener : public MicroNovaBaseListener { - public: - MicroNovaNumberListener(MicroNova *m) : MicroNovaBaseListener(m) {} - virtual void request_value_from_stove() = 0; - virtual void process_value_from_stove(int value_from_stove) = 0; - - void set_needs_update(bool u) { this->needs_update_ = u; } - bool get_needs_update() { return this->needs_update_; } + void dump_base_config(); protected: bool needs_update_ = false; @@ -117,17 +109,17 @@ class MicroNovaButtonListener : public MicroNovaBaseListener { ///////////////////////////////////////////////////////////////////// // Main component class -class MicroNova : public PollingComponent, public uart::UARTDevice { +class MicroNova : public Component, public uart::UARTDevice { public: MicroNova() {} void setup() override; void loop() override; - void update() override; void dump_config() override; - void register_micronova_listener(MicroNovaSensorListener *l) { this->micronova_listeners_.push_back(l); } + void register_micronova_listener(MicroNovaListener *l) { this->micronova_listeners_.push_back(l); } + void request_update_listeners(); - void request_address(uint8_t location, uint8_t address, MicroNovaSensorListener *listener); + void request_address(uint8_t location, uint8_t address, MicroNovaListener *listener); void write_address(uint8_t location, uint8_t address, uint8_t data); int read_stove_reply(); @@ -149,13 +141,13 @@ class MicroNova : public PollingComponent, public uart::UARTDevice { uint8_t memory_location; uint8_t memory_address; bool reply_pending; - MicroNovaSensorListener *initiating_listener; + MicroNovaListener *initiating_listener; }; Mutex reply_pending_mutex_; MicroNovaSerialTransmission current_transmission_; - std::vector micronova_listeners_{}; + std::vector micronova_listeners_{}; MicroNovaSwitchListener *stove_switch_{nullptr}; }; diff --git a/esphome/components/micronova/number/__init__.py b/esphome/components/micronova/number/__init__.py index b0eeaf8dd1..ec994a5aa5 100644 --- a/esphome/components/micronova/number/__init__.py +++ b/esphome/components/micronova/number/__init__.py @@ -4,13 +4,13 @@ import esphome.config_validation as cv from esphome.const import CONF_STEP, DEVICE_CLASS_TEMPERATURE, UNIT_CELSIUS from .. import ( - CONF_MEMORY_ADDRESS, - CONF_MEMORY_LOCATION, CONF_MICRONOVA_ID, - MICRONOVA_LISTENER_SCHEMA, + MICRONOVA_ADDRESS_SCHEMA, MicroNova, MicroNovaFunctions, + MicroNovaListener, micronova_ns, + to_code_micronova_listener, ) ICON_FLASH = "mdi:flash" @@ -19,7 +19,9 @@ CONF_THERMOSTAT_TEMPERATURE = "thermostat_temperature" CONF_POWER_LEVEL = "power_level" CONF_MEMORY_WRITE_LOCATION = "memory_write_location" -MicroNovaNumber = micronova_ns.class_("MicroNovaNumber", number.Number, cg.Component) +MicroNovaNumber = micronova_ns.class_( + "MicroNovaNumber", number.Number, MicroNovaListener +) CONFIG_SCHEMA = cv.Schema( { @@ -30,8 +32,10 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_TEMPERATURE, ) .extend( - MICRONOVA_LISTENER_SCHEMA( - default_memory_location=0x20, default_memory_address=0x7D + MICRONOVA_ADDRESS_SCHEMA( + default_memory_location=0x20, + default_memory_address=0x7D, + is_polling_component=True, ) ) .extend( @@ -47,8 +51,10 @@ CONFIG_SCHEMA = cv.Schema( icon=ICON_FLASH, ) .extend( - MICRONOVA_LISTENER_SCHEMA( - default_memory_location=0x20, default_memory_address=0x7F + MICRONOVA_ADDRESS_SCHEMA( + default_memory_location=0x20, + default_memory_address=0x7F, + is_polling_component=True, ) ) .extend( @@ -68,24 +74,18 @@ async def to_code(config): max_value=40, step=thermostat_temperature_config.get(CONF_STEP), ) + await to_code_micronova_listener( + mv, + numb, + thermostat_temperature_config, + MicroNovaFunctions.STOVE_FUNCTION_THERMOSTAT_TEMPERATURE, + ) cg.add(numb.set_micronova_object(mv)) - cg.add(mv.register_micronova_listener(numb)) - cg.add( - numb.set_memory_location( - thermostat_temperature_config[CONF_MEMORY_LOCATION] - ) - ) - cg.add( - numb.set_memory_address(thermostat_temperature_config[CONF_MEMORY_ADDRESS]) - ) cg.add( numb.set_memory_write_location( thermostat_temperature_config.get(CONF_MEMORY_WRITE_LOCATION) ) ) - cg.add( - numb.set_function(MicroNovaFunctions.STOVE_FUNCTION_THERMOSTAT_TEMPERATURE) - ) if power_level_config := config.get(CONF_POWER_LEVEL): numb = await number.new_number( @@ -94,13 +94,12 @@ async def to_code(config): max_value=5, step=1, ) + await to_code_micronova_listener( + mv, numb, power_level_config, MicroNovaFunctions.STOVE_FUNCTION_POWER_LEVEL + ) cg.add(numb.set_micronova_object(mv)) - cg.add(mv.register_micronova_listener(numb)) - cg.add(numb.set_memory_location(power_level_config[CONF_MEMORY_LOCATION])) - cg.add(numb.set_memory_address(power_level_config[CONF_MEMORY_ADDRESS])) cg.add( numb.set_memory_write_location( power_level_config.get(CONF_MEMORY_WRITE_LOCATION) ) ) - cg.add(numb.set_function(MicroNovaFunctions.STOVE_FUNCTION_POWER_LEVEL)) diff --git a/esphome/components/micronova/number/micronova_number.cpp b/esphome/components/micronova/number/micronova_number.cpp index b311c85b99..66e81e98b7 100644 --- a/esphome/components/micronova/number/micronova_number.cpp +++ b/esphome/components/micronova/number/micronova_number.cpp @@ -37,7 +37,7 @@ void MicroNovaNumber::control(float value) { break; } this->micronova_->write_address(this->memory_write_location_, this->memory_address_, new_number); - this->micronova_->update(); + this->micronova_->request_update_listeners(); } } // namespace esphome::micronova diff --git a/esphome/components/micronova/number/micronova_number.h b/esphome/components/micronova/number/micronova_number.h index 79e59dbc28..e1545bb35a 100644 --- a/esphome/components/micronova/number/micronova_number.h +++ b/esphome/components/micronova/number/micronova_number.h @@ -5,11 +5,14 @@ namespace esphome::micronova { -class MicroNovaNumber : public number::Number, public MicroNovaSensorListener { +class MicroNovaNumber : public number::Number, public MicroNovaListener { public: MicroNovaNumber() {} - MicroNovaNumber(MicroNova *m) : MicroNovaSensorListener(m) {} - void dump_config() override { LOG_NUMBER("", "Micronova number", this); } + MicroNovaNumber(MicroNova *m) : MicroNovaListener(m) {} + void dump_config() override { + LOG_NUMBER("", "Micronova number", this); + this->dump_base_config(); + } void control(float value) override; void request_value_from_stove() override { this->micronova_->request_address(this->memory_location_, this->memory_address_, this); diff --git a/esphome/components/micronova/sensor/__init__.py b/esphome/components/micronova/sensor/__init__.py index ceb4a9ef77..77bdacd5da 100644 --- a/esphome/components/micronova/sensor/__init__.py +++ b/esphome/components/micronova/sensor/__init__.py @@ -10,18 +10,20 @@ from esphome.const import ( ) from .. import ( - CONF_MEMORY_ADDRESS, - CONF_MEMORY_LOCATION, CONF_MICRONOVA_ID, - MICRONOVA_LISTENER_SCHEMA, + MICRONOVA_ADDRESS_SCHEMA, MicroNova, MicroNovaFunctions, + MicroNovaListener, micronova_ns, + to_code_micronova_listener, ) UNIT_BAR = "bar" -MicroNovaSensor = micronova_ns.class_("MicroNovaSensor", sensor.Sensor, cg.Component) +MicroNovaSensor = micronova_ns.class_( + "MicroNovaSensor", sensor.Sensor, MicroNovaListener +) CONF_ROOM_TEMPERATURE = "room_temperature" CONF_FUMES_TEMPERATURE = "fumes_temperature" @@ -42,8 +44,10 @@ CONFIG_SCHEMA = cv.Schema( state_class=STATE_CLASS_MEASUREMENT, accuracy_decimals=1, ).extend( - MICRONOVA_LISTENER_SCHEMA( - default_memory_location=0x00, default_memory_address=0x01 + MICRONOVA_ADDRESS_SCHEMA( + default_memory_location=0x00, + default_memory_address=0x01, + is_polling_component=True, ) ), cv.Optional(CONF_FUMES_TEMPERATURE): sensor.sensor_schema( @@ -53,8 +57,10 @@ CONFIG_SCHEMA = cv.Schema( state_class=STATE_CLASS_MEASUREMENT, accuracy_decimals=1, ).extend( - MICRONOVA_LISTENER_SCHEMA( - default_memory_location=0x00, default_memory_address=0x5A + MICRONOVA_ADDRESS_SCHEMA( + default_memory_location=0x00, + default_memory_address=0x5A, + is_polling_component=True, ) ), cv.Optional(CONF_STOVE_POWER): sensor.sensor_schema( @@ -62,8 +68,10 @@ CONFIG_SCHEMA = cv.Schema( state_class=STATE_CLASS_MEASUREMENT, accuracy_decimals=0, ).extend( - MICRONOVA_LISTENER_SCHEMA( - default_memory_location=0x00, default_memory_address=0x34 + MICRONOVA_ADDRESS_SCHEMA( + default_memory_location=0x00, + default_memory_address=0x34, + is_polling_component=True, ) ), cv.Optional(CONF_FAN_SPEED): sensor.sensor_schema( @@ -72,8 +80,10 @@ CONFIG_SCHEMA = cv.Schema( unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE, ) .extend( - MICRONOVA_LISTENER_SCHEMA( - default_memory_location=0x00, default_memory_address=0x37 + MICRONOVA_ADDRESS_SCHEMA( + default_memory_location=0x00, + default_memory_address=0x37, + is_polling_component=True, ) ) .extend( @@ -86,8 +96,10 @@ CONFIG_SCHEMA = cv.Schema( state_class=STATE_CLASS_MEASUREMENT, accuracy_decimals=1, ).extend( - MICRONOVA_LISTENER_SCHEMA( - default_memory_location=0x00, default_memory_address=0x3B + MICRONOVA_ADDRESS_SCHEMA( + default_memory_location=0x00, + default_memory_address=0x3B, + is_polling_component=True, ) ), cv.Optional(CONF_WATER_PRESSURE): sensor.sensor_schema( @@ -97,15 +109,19 @@ CONFIG_SCHEMA = cv.Schema( state_class=STATE_CLASS_MEASUREMENT, accuracy_decimals=1, ).extend( - MICRONOVA_LISTENER_SCHEMA( - default_memory_location=0x00, default_memory_address=0x3C + MICRONOVA_ADDRESS_SCHEMA( + default_memory_location=0x00, + default_memory_address=0x3C, + is_polling_component=True, ) ), cv.Optional(CONF_MEMORY_ADDRESS_SENSOR): sensor.sensor_schema( MicroNovaSensor, ).extend( - MICRONOVA_LISTENER_SCHEMA( - default_memory_location=0x00, default_memory_address=0x00 + MICRONOVA_ADDRESS_SCHEMA( + default_memory_location=0x00, + default_memory_address=0x00, + is_polling_component=True, ) ), } @@ -115,58 +131,21 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): mv = await cg.get_variable(config[CONF_MICRONOVA_ID]) - if room_temperature_config := config.get(CONF_ROOM_TEMPERATURE): - sens = await sensor.new_sensor(room_temperature_config, mv) - cg.add(mv.register_micronova_listener(sens)) - cg.add(sens.set_memory_location(room_temperature_config[CONF_MEMORY_LOCATION])) - cg.add(sens.set_memory_address(room_temperature_config[CONF_MEMORY_ADDRESS])) - cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_ROOM_TEMPERATURE)) - - if fumes_temperature_config := config.get(CONF_FUMES_TEMPERATURE): - sens = await sensor.new_sensor(fumes_temperature_config, mv) - cg.add(mv.register_micronova_listener(sens)) - cg.add(sens.set_memory_location(fumes_temperature_config[CONF_MEMORY_LOCATION])) - cg.add(sens.set_memory_address(fumes_temperature_config[CONF_MEMORY_ADDRESS])) - cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_FUMES_TEMPERATURE)) - - if stove_power_config := config.get(CONF_STOVE_POWER): - sens = await sensor.new_sensor(stove_power_config, mv) - cg.add(mv.register_micronova_listener(sens)) - cg.add(sens.set_memory_location(stove_power_config[CONF_MEMORY_LOCATION])) - cg.add(sens.set_memory_address(stove_power_config[CONF_MEMORY_ADDRESS])) - cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_STOVE_POWER)) + for key, fn in { + CONF_ROOM_TEMPERATURE: MicroNovaFunctions.STOVE_FUNCTION_ROOM_TEMPERATURE, + CONF_FUMES_TEMPERATURE: MicroNovaFunctions.STOVE_FUNCTION_FUMES_TEMPERATURE, + CONF_STOVE_POWER: MicroNovaFunctions.STOVE_FUNCTION_STOVE_POWER, + CONF_MEMORY_ADDRESS_SENSOR: MicroNovaFunctions.STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR, + CONF_WATER_TEMPERATURE: MicroNovaFunctions.STOVE_FUNCTION_WATER_TEMPERATURE, + CONF_WATER_PRESSURE: MicroNovaFunctions.STOVE_FUNCTION_WATER_PRESSURE, + }.items(): + if sensor_config := config.get(key): + sens = await sensor.new_sensor(sensor_config, mv) + await to_code_micronova_listener(mv, sens, sensor_config, fn) if fan_speed_config := config.get(CONF_FAN_SPEED): sens = await sensor.new_sensor(fan_speed_config, mv) - cg.add(mv.register_micronova_listener(sens)) - cg.add(sens.set_memory_location(fan_speed_config[CONF_MEMORY_LOCATION])) - cg.add(sens.set_memory_address(fan_speed_config[CONF_MEMORY_ADDRESS])) - cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_FAN_SPEED)) + await to_code_micronova_listener( + mv, sens, fan_speed_config, MicroNovaFunctions.STOVE_FUNCTION_FAN_SPEED + ) cg.add(sens.set_fan_speed_offset(fan_speed_config[CONF_FAN_RPM_OFFSET])) - - if memory_address_sensor_config := config.get(CONF_MEMORY_ADDRESS_SENSOR): - sens = await sensor.new_sensor(memory_address_sensor_config, mv) - cg.add(mv.register_micronova_listener(sens)) - cg.add( - sens.set_memory_location(memory_address_sensor_config[CONF_MEMORY_LOCATION]) - ) - cg.add( - sens.set_memory_address(memory_address_sensor_config[CONF_MEMORY_ADDRESS]) - ) - cg.add( - sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR) - ) - - if water_temperature_config := config.get(CONF_WATER_TEMPERATURE): - sens = await sensor.new_sensor(water_temperature_config, mv) - cg.add(mv.register_micronova_listener(sens)) - cg.add(sens.set_memory_location(water_temperature_config[CONF_MEMORY_LOCATION])) - cg.add(sens.set_memory_address(water_temperature_config[CONF_MEMORY_ADDRESS])) - cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_WATER_TEMPERATURE)) - - if water_pressure_config := config.get(CONF_WATER_PRESSURE): - sens = await sensor.new_sensor(water_pressure_config, mv) - cg.add(mv.register_micronova_listener(sens)) - cg.add(sens.set_memory_location(water_pressure_config[CONF_MEMORY_LOCATION])) - cg.add(sens.set_memory_address(water_pressure_config[CONF_MEMORY_ADDRESS])) - cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_WATER_PRESSURE)) diff --git a/esphome/components/micronova/sensor/micronova_sensor.h b/esphome/components/micronova/sensor/micronova_sensor.h index 081e68b09d..119e5eb155 100644 --- a/esphome/components/micronova/sensor/micronova_sensor.h +++ b/esphome/components/micronova/sensor/micronova_sensor.h @@ -5,10 +5,13 @@ namespace esphome::micronova { -class MicroNovaSensor : public sensor::Sensor, public MicroNovaSensorListener { +class MicroNovaSensor : public sensor::Sensor, public MicroNovaListener { public: - MicroNovaSensor(MicroNova *m) : MicroNovaSensorListener(m) {} - void dump_config() override { LOG_SENSOR("", "Micronova sensor", this); } + MicroNovaSensor(MicroNova *m) : MicroNovaListener(m) {} + void dump_config() override { + LOG_SENSOR("", "Micronova sensor", this); + this->dump_base_config(); + } void request_value_from_stove() override { this->micronova_->request_address(this->memory_location_, this->memory_address_, this); diff --git a/esphome/components/micronova/switch/__init__.py b/esphome/components/micronova/switch/__init__.py index 43e5c9d844..006ada92aa 100644 --- a/esphome/components/micronova/switch/__init__.py +++ b/esphome/components/micronova/switch/__init__.py @@ -7,7 +7,7 @@ from .. import ( CONF_MEMORY_ADDRESS, CONF_MEMORY_LOCATION, CONF_MICRONOVA_ID, - MICRONOVA_LISTENER_SCHEMA, + MICRONOVA_ADDRESS_SCHEMA, MicroNova, MicroNovaFunctions, micronova_ns, @@ -27,8 +27,10 @@ CONFIG_SCHEMA = cv.Schema( icon=ICON_POWER, ) .extend( - MICRONOVA_LISTENER_SCHEMA( - default_memory_location=0x80, default_memory_address=0x21 + MICRONOVA_ADDRESS_SCHEMA( + default_memory_location=0x80, + default_memory_address=0x21, + is_polling_component=False, ) ) .extend( diff --git a/esphome/components/micronova/switch/micronova_switch.cpp b/esphome/components/micronova/switch/micronova_switch.cpp index 81d36adccb..3777b6029d 100644 --- a/esphome/components/micronova/switch/micronova_switch.cpp +++ b/esphome/components/micronova/switch/micronova_switch.cpp @@ -22,7 +22,7 @@ void MicroNovaSwitch::write_state(bool state) { ESP_LOGW(TAG, "Unable to turn stove off, invalid state: %d", micronova_->get_current_stove_state()); } } - this->micronova_->update(); + this->micronova_->request_update_listeners(); break; default: diff --git a/esphome/components/micronova/switch/micronova_switch.h b/esphome/components/micronova/switch/micronova_switch.h index 7019084355..ab83973ef7 100644 --- a/esphome/components/micronova/switch/micronova_switch.h +++ b/esphome/components/micronova/switch/micronova_switch.h @@ -9,7 +9,10 @@ namespace esphome::micronova { class MicroNovaSwitch : public Component, public switch_::Switch, public MicroNovaSwitchListener { public: MicroNovaSwitch(MicroNova *m) : MicroNovaSwitchListener(m) {} - void dump_config() override { LOG_SWITCH("", "Micronova switch", this); } + void dump_config() override { + LOG_SWITCH("", "Micronova switch", this); + this->dump_base_config(); + } void set_stove_state(bool v) override { this->publish_state(v); } bool get_stove_state() override { return this->state; } diff --git a/esphome/components/micronova/text_sensor/__init__.py b/esphome/components/micronova/text_sensor/__init__.py index 474c30e13b..e54b9e280a 100644 --- a/esphome/components/micronova/text_sensor/__init__.py +++ b/esphome/components/micronova/text_sensor/__init__.py @@ -3,19 +3,19 @@ from esphome.components import text_sensor import esphome.config_validation as cv from .. import ( - CONF_MEMORY_ADDRESS, - CONF_MEMORY_LOCATION, CONF_MICRONOVA_ID, - MICRONOVA_LISTENER_SCHEMA, + MICRONOVA_ADDRESS_SCHEMA, MicroNova, MicroNovaFunctions, + MicroNovaListener, micronova_ns, + to_code_micronova_listener, ) CONF_STOVE_STATE = "stove_state" MicroNovaTextSensor = micronova_ns.class_( - "MicroNovaTextSensor", text_sensor.TextSensor, cg.Component + "MicroNovaTextSensor", text_sensor.TextSensor, MicroNovaListener ) CONFIG_SCHEMA = cv.Schema( @@ -24,8 +24,10 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_STOVE_STATE): text_sensor.text_sensor_schema( MicroNovaTextSensor ).extend( - MICRONOVA_LISTENER_SCHEMA( - default_memory_location=0x00, default_memory_address=0x21 + MICRONOVA_ADDRESS_SCHEMA( + default_memory_location=0x00, + default_memory_address=0x21, + is_polling_component=True, ) ), } @@ -37,7 +39,6 @@ async def to_code(config): if stove_state_config := config.get(CONF_STOVE_STATE): sens = await text_sensor.new_text_sensor(stove_state_config, mv) - cg.add(mv.register_micronova_listener(sens)) - cg.add(sens.set_memory_location(stove_state_config[CONF_MEMORY_LOCATION])) - cg.add(sens.set_memory_address(stove_state_config[CONF_MEMORY_ADDRESS])) - cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_STOVE_STATE)) + await to_code_micronova_listener( + mv, sens, stove_state_config, MicroNovaFunctions.STOVE_FUNCTION_STOVE_STATE + ) diff --git a/esphome/components/micronova/text_sensor/micronova_text_sensor.h b/esphome/components/micronova/text_sensor/micronova_text_sensor.h index 352f049654..7992bdc243 100644 --- a/esphome/components/micronova/text_sensor/micronova_text_sensor.h +++ b/esphome/components/micronova/text_sensor/micronova_text_sensor.h @@ -5,10 +5,13 @@ namespace esphome::micronova { -class MicroNovaTextSensor : public text_sensor::TextSensor, public MicroNovaSensorListener { +class MicroNovaTextSensor : public text_sensor::TextSensor, public MicroNovaListener { public: - MicroNovaTextSensor(MicroNova *m) : MicroNovaSensorListener(m) {} - void dump_config() override { LOG_TEXT_SENSOR("", "Micronova text sensor", this); } + MicroNovaTextSensor(MicroNova *m) : MicroNovaListener(m) {} + void dump_config() override { + LOG_TEXT_SENSOR("", "Micronova text sensor", this); + this->dump_base_config(); + } void request_value_from_stove() override { this->micronova_->request_address(this->memory_location_, this->memory_address_, this); } diff --git a/tests/components/micronova/common.yaml b/tests/components/micronova/common.yaml index 3cf8e36fb6..73456aa199 100644 --- a/tests/components/micronova/common.yaml +++ b/tests/components/micronova/common.yaml @@ -16,6 +16,7 @@ number: step: 1 power_level: name: Micronova Power level + update_interval: 10s sensor: - platform: micronova @@ -41,3 +42,9 @@ switch: - platform: micronova stove: name: Stove on/off + +text_sensor: + - platform: micronova + stove_state: + name: Stove status + update_interval: 5s From e36e6fbc3fdb7f6ed8474893503b67b1aa4eb9d2 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Mon, 8 Dec 2025 01:08:41 +0100 Subject: [PATCH 0554/1145] [micronova] Move STOVE_STATES to text sensor file as it's used only there (#12349) --- esphome/components/micronova/micronova.h | 12 ------------ .../micronova/text_sensor/micronova_text_sensor.h | 12 ++++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/esphome/components/micronova/micronova.h b/esphome/components/micronova/micronova.h index 7992bff2d3..c5a5ba4f4e 100644 --- a/esphome/components/micronova/micronova.h +++ b/esphome/components/micronova/micronova.h @@ -13,18 +13,6 @@ namespace esphome::micronova { static const char *const TAG = "micronova"; static const int STOVE_REPLY_DELAY = 60; -static const std::string STOVE_STATES[11] = {"Off", - "Start", - "Pellets loading", - "Ignition", - "Working", - "Brazier Cleaning", - "Final Cleaning", - "Standby", - "No pellets alarm", - "No ignition alarm", - "Undefined alarm"}; - enum class MicroNovaFunctions { STOVE_FUNCTION_VOID = 0, STOVE_FUNCTION_SWITCH = 1, diff --git a/esphome/components/micronova/text_sensor/micronova_text_sensor.h b/esphome/components/micronova/text_sensor/micronova_text_sensor.h index 7992bdc243..290f0ca45a 100644 --- a/esphome/components/micronova/text_sensor/micronova_text_sensor.h +++ b/esphome/components/micronova/text_sensor/micronova_text_sensor.h @@ -5,6 +5,18 @@ namespace esphome::micronova { +static const char *const STOVE_STATES[11] = {"Off", + "Start", + "Pellets loading", + "Ignition", + "Working", + "Brazier Cleaning", + "Final Cleaning", + "Standby", + "No pellets alarm", + "No ignition alarm", + "Undefined alarm"}; + class MicroNovaTextSensor : public text_sensor::TextSensor, public MicroNovaListener { public: MicroNovaTextSensor(MicroNova *m) : MicroNovaListener(m) {} From c5cc91f6f004120703e8ef3dfe570ce0157db3bc Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Mon, 8 Dec 2025 03:02:05 +0100 Subject: [PATCH 0555/1145] [micronova] Add FINAL_VALIDATE_SCHEMA to validate uart (#12350) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/micronova/__init__.py | 15 +++++++++++++-- tests/components/micronova/test.esp32-idf.yaml | 2 +- tests/components/micronova/test.esp8266-ard.yaml | 2 +- tests/components/micronova/test.rp2040-ard.yaml | 2 +- .../uart_1200_none_2stopbits/esp32-ard.yaml | 13 +++++++++++++ .../uart_1200_none_2stopbits/esp32-c3-ard.yaml | 13 +++++++++++++ .../uart_1200_none_2stopbits/esp32-c3-idf.yaml | 13 +++++++++++++ .../uart_1200_none_2stopbits/esp32-idf.yaml | 13 +++++++++++++ .../uart_1200_none_2stopbits/esp8266-ard.yaml | 13 +++++++++++++ .../uart_1200_none_2stopbits/rp2040-ard.yaml | 13 +++++++++++++ 10 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 tests/test_build_components/common/uart_1200_none_2stopbits/esp32-ard.yaml create mode 100644 tests/test_build_components/common/uart_1200_none_2stopbits/esp32-c3-ard.yaml create mode 100644 tests/test_build_components/common/uart_1200_none_2stopbits/esp32-c3-idf.yaml create mode 100644 tests/test_build_components/common/uart_1200_none_2stopbits/esp32-idf.yaml create mode 100644 tests/test_build_components/common/uart_1200_none_2stopbits/esp8266-ard.yaml create mode 100644 tests/test_build_components/common/uart_1200_none_2stopbits/rp2040-ard.yaml diff --git a/esphome/components/micronova/__init__.py b/esphome/components/micronova/__init__.py index 9b01ae97e3..11213e82c5 100644 --- a/esphome/components/micronova/__init__.py +++ b/esphome/components/micronova/__init__.py @@ -8,13 +8,14 @@ CODEOWNERS = ["@jorre05", "@edenhaus"] DEPENDENCIES = ["uart"] -CONF_MICRONOVA_ID = "micronova_id" +DOMAIN = "micronova" +CONF_MICRONOVA_ID = f"{DOMAIN}_id" CONF_ENABLE_RX_PIN = "enable_rx_pin" CONF_MEMORY_LOCATION = "memory_location" CONF_MEMORY_ADDRESS = "memory_address" DEFAULT_POLLING_INTERVAL = "60s" -micronova_ns = cg.esphome_ns.namespace("micronova") +micronova_ns = cg.esphome_ns.namespace(DOMAIN) MicroNovaFunctions = micronova_ns.enum("MicroNovaFunctions", is_class=True) MICRONOVA_FUNCTIONS_ENUM = { @@ -42,6 +43,16 @@ CONFIG_SCHEMA = cv.Schema( } ).extend(uart.UART_DEVICE_SCHEMA) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + DOMAIN, + baud_rate=1200, + require_rx=True, + require_tx=True, + data_bits=8, + parity="NONE", + stop_bits=2, +) + def MICRONOVA_ADDRESS_SCHEMA( *, diff --git a/tests/components/micronova/test.esp32-idf.yaml b/tests/components/micronova/test.esp32-idf.yaml index 5cc3a234ca..b3e4714bc3 100644 --- a/tests/components/micronova/test.esp32-idf.yaml +++ b/tests/components/micronova/test.esp32-idf.yaml @@ -2,6 +2,6 @@ substitutions: enable_rx_pin: GPIO13 packages: - uart: !include ../../test_build_components/common/uart/esp32-idf.yaml + uart: !include ../../test_build_components/common/uart_1200_none_2stopbits/esp32-idf.yaml <<: !include common.yaml diff --git a/tests/components/micronova/test.esp8266-ard.yaml b/tests/components/micronova/test.esp8266-ard.yaml index ffe1e0a063..04030801e3 100644 --- a/tests/components/micronova/test.esp8266-ard.yaml +++ b/tests/components/micronova/test.esp8266-ard.yaml @@ -2,6 +2,6 @@ substitutions: enable_rx_pin: GPIO15 packages: - uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml + uart: !include ../../test_build_components/common/uart_1200_none_2stopbits/esp8266-ard.yaml <<: !include common.yaml diff --git a/tests/components/micronova/test.rp2040-ard.yaml b/tests/components/micronova/test.rp2040-ard.yaml index 6dc030e6b6..67110f25b0 100644 --- a/tests/components/micronova/test.rp2040-ard.yaml +++ b/tests/components/micronova/test.rp2040-ard.yaml @@ -2,6 +2,6 @@ substitutions: enable_rx_pin: GPIO3 packages: - uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml + uart: !include ../../test_build_components/common/uart_1200_none_2stopbits/rp2040-ard.yaml <<: !include common.yaml diff --git a/tests/test_build_components/common/uart_1200_none_2stopbits/esp32-ard.yaml b/tests/test_build_components/common/uart_1200_none_2stopbits/esp32-ard.yaml new file mode 100644 index 0000000000..41ded5a763 --- /dev/null +++ b/tests/test_build_components/common/uart_1200_none_2stopbits/esp32-ard.yaml @@ -0,0 +1,13 @@ +# Common UART configuration for ESP32 Arduino tests - 1200 baud NONE parity 2 stop bits + +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 + +uart: + - id: uart_bus + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + baud_rate: 1200 + parity: NONE + stop_bits: 2 diff --git a/tests/test_build_components/common/uart_1200_none_2stopbits/esp32-c3-ard.yaml b/tests/test_build_components/common/uart_1200_none_2stopbits/esp32-c3-ard.yaml new file mode 100644 index 0000000000..1eb5d6d5f9 --- /dev/null +++ b/tests/test_build_components/common/uart_1200_none_2stopbits/esp32-c3-ard.yaml @@ -0,0 +1,13 @@ +# Common UART configuration for ESP32-C3 Arduino tests - 1200 baud NONE parity 2 stop bits + +substitutions: + tx_pin: GPIO20 + rx_pin: GPIO21 + +uart: + - id: uart_bus + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + baud_rate: 1200 + parity: NONE + stop_bits: 2 diff --git a/tests/test_build_components/common/uart_1200_none_2stopbits/esp32-c3-idf.yaml b/tests/test_build_components/common/uart_1200_none_2stopbits/esp32-c3-idf.yaml new file mode 100644 index 0000000000..5181995a40 --- /dev/null +++ b/tests/test_build_components/common/uart_1200_none_2stopbits/esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +# Common UART configuration for ESP32-C3 IDF tests - 1200 baud NONE parity 2 stop bits + +substitutions: + tx_pin: GPIO20 + rx_pin: GPIO21 + +uart: + - id: uart_bus + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + baud_rate: 1200 + parity: NONE + stop_bits: 2 diff --git a/tests/test_build_components/common/uart_1200_none_2stopbits/esp32-idf.yaml b/tests/test_build_components/common/uart_1200_none_2stopbits/esp32-idf.yaml new file mode 100644 index 0000000000..122f05aced --- /dev/null +++ b/tests/test_build_components/common/uart_1200_none_2stopbits/esp32-idf.yaml @@ -0,0 +1,13 @@ +# Common UART configuration for ESP32 IDF tests - 1200 baud NONE parity 2 stop bits + +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 + +uart: + - id: uart_bus + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + baud_rate: 1200 + parity: NONE + stop_bits: 2 diff --git a/tests/test_build_components/common/uart_1200_none_2stopbits/esp8266-ard.yaml b/tests/test_build_components/common/uart_1200_none_2stopbits/esp8266-ard.yaml new file mode 100644 index 0000000000..3bffabf82d --- /dev/null +++ b/tests/test_build_components/common/uart_1200_none_2stopbits/esp8266-ard.yaml @@ -0,0 +1,13 @@ +# Common UART configuration for ESP8266 Arduino tests - 1200 baud NONE parity 2 stop bits + +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +uart: + - id: uart_bus + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + baud_rate: 1200 + parity: NONE + stop_bits: 2 diff --git a/tests/test_build_components/common/uart_1200_none_2stopbits/rp2040-ard.yaml b/tests/test_build_components/common/uart_1200_none_2stopbits/rp2040-ard.yaml new file mode 100644 index 0000000000..fb94939090 --- /dev/null +++ b/tests/test_build_components/common/uart_1200_none_2stopbits/rp2040-ard.yaml @@ -0,0 +1,13 @@ +# Common UART configuration for RP2040 Arduino tests - 1200 baud NONE parity 2 stop bits + +substitutions: + tx_pin: GPIO0 + rx_pin: GPIO1 + +uart: + - id: uart_bus + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + baud_rate: 1200 + parity: NONE + stop_bits: 2 From ffb3e2eb0afaf5efe6b6b6d74eff413f80fd7207 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Dec 2025 22:00:04 -0600 Subject: [PATCH 0556/1145] [wifi] Fix scan timeout loop when scan returns zero networks (#12354) --- esphome/components/wifi/wifi_component.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 317507f242..ff33a81fcf 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1264,8 +1264,8 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() { } case WiFiRetryPhase::SCAN_CONNECTING: - // If scan found no matching networks, skip to hidden network mode - if (!this->scan_result_.empty() && !this->scan_result_[0].get_matches()) { + // If scan found no networks or no matching networks, skip to hidden network mode + if (this->scan_result_.empty() || !this->scan_result_[0].get_matches()) { return WiFiRetryPhase::RETRY_HIDDEN; } From 159194587b7a7f033d5ad361ed9d89b19a79623e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Dec 2025 22:08:21 -0600 Subject: [PATCH 0557/1145] [core] Move Color::gradient to cpp to avoid duplicate code (#12348) --- esphome/core/color.cpp | 14 ++++++++++++++ esphome/core/color.h | 14 +++----------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/esphome/core/color.cpp b/esphome/core/color.cpp index 7e390b2354..14c41c2b0d 100644 --- a/esphome/core/color.cpp +++ b/esphome/core/color.cpp @@ -6,4 +6,18 @@ namespace esphome { constinit const Color Color::BLACK(0, 0, 0, 0); constinit const Color Color::WHITE(255, 255, 255, 255); +Color Color::gradient(const Color &to_color, uint8_t amnt) { + Color new_color; + float amnt_f = float(amnt) / 255.0f; + new_color.r = amnt_f * (to_color.r - this->r) + this->r; + new_color.g = amnt_f * (to_color.g - this->g) + this->g; + new_color.b = amnt_f * (to_color.b - this->b) + this->b; + new_color.w = amnt_f * (to_color.w - this->w) + this->w; + return new_color; +} + +Color Color::fade_to_white(uint8_t amnt) { return this->gradient(Color::WHITE, amnt); } + +Color Color::fade_to_black(uint8_t amnt) { return this->gradient(Color::BLACK, amnt); } + } // namespace esphome diff --git a/esphome/core/color.h b/esphome/core/color.h index 4b0ae5b57a..32d63b1856 100644 --- a/esphome/core/color.h +++ b/esphome/core/color.h @@ -174,17 +174,9 @@ struct Color { uint8_t((uint16_t(b) * 255U / max_rgb)), w); } - Color gradient(const Color &to_color, uint8_t amnt) { - Color new_color; - float amnt_f = float(amnt) / 255.0f; - new_color.r = amnt_f * (to_color.r - (*this).r) + (*this).r; - new_color.g = amnt_f * (to_color.g - (*this).g) + (*this).g; - new_color.b = amnt_f * (to_color.b - (*this).b) + (*this).b; - new_color.w = amnt_f * (to_color.w - (*this).w) + (*this).w; - return new_color; - } - Color fade_to_white(uint8_t amnt) { return (*this).gradient(Color::WHITE, amnt); } - Color fade_to_black(uint8_t amnt) { return (*this).gradient(Color::BLACK, amnt); } + Color gradient(const Color &to_color, uint8_t amnt); + Color fade_to_white(uint8_t amnt); + Color fade_to_black(uint8_t amnt); Color lighten(uint8_t delta) { return *this + delta; } Color darken(uint8_t delta) { return *this - delta; } From 93a85d7979f7d0a23c93aa520a7af8c1996da561 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Dec 2025 22:08:46 -0600 Subject: [PATCH 0558/1145] [wifi_signal] Update signal strength immediately on WiFi connect/disconnect (#12347) --- esphome/components/wifi_signal/sensor.py | 3 ++- .../components/wifi_signal/wifi_signal_sensor.cpp | 6 ++---- esphome/components/wifi_signal/wifi_signal_sensor.h | 12 +++++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/esphome/components/wifi_signal/sensor.py b/esphome/components/wifi_signal/sensor.py index 99b51adea0..82cb90c745 100644 --- a/esphome/components/wifi_signal/sensor.py +++ b/esphome/components/wifi_signal/sensor.py @@ -1,5 +1,5 @@ import esphome.codegen as cg -from esphome.components import sensor +from esphome.components import sensor, wifi import esphome.config_validation as cv from esphome.const import ( DEVICE_CLASS_SIGNAL_STRENGTH, @@ -25,5 +25,6 @@ CONFIG_SCHEMA = sensor.sensor_schema( async def to_code(config): + wifi.request_wifi_listeners() var = await sensor.new_sensor(config) await cg.register_component(var, config) diff --git a/esphome/components/wifi_signal/wifi_signal_sensor.cpp b/esphome/components/wifi_signal/wifi_signal_sensor.cpp index 4347295421..11d816a909 100644 --- a/esphome/components/wifi_signal/wifi_signal_sensor.cpp +++ b/esphome/components/wifi_signal/wifi_signal_sensor.cpp @@ -2,13 +2,11 @@ #ifdef USE_WIFI #include "esphome/core/log.h" -namespace esphome { -namespace wifi_signal { +namespace esphome::wifi_signal { static const char *const TAG = "wifi_signal.sensor"; void WiFiSignalSensor::dump_config() { LOG_SENSOR("", "WiFi Signal", this); } -} // namespace wifi_signal -} // namespace esphome +} // namespace esphome::wifi_signal #endif diff --git a/esphome/components/wifi_signal/wifi_signal_sensor.h b/esphome/components/wifi_signal/wifi_signal_sensor.h index 5cfd19b523..cc951e8dd7 100644 --- a/esphome/components/wifi_signal/wifi_signal_sensor.h +++ b/esphome/components/wifi_signal/wifi_signal_sensor.h @@ -5,17 +5,19 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/wifi/wifi_component.h" #ifdef USE_WIFI -namespace esphome { -namespace wifi_signal { +namespace esphome::wifi_signal { -class WiFiSignalSensor : public sensor::Sensor, public PollingComponent { +class WiFiSignalSensor : public sensor::Sensor, public PollingComponent, public wifi::WiFiConnectStateListener { public: + void setup() override { wifi::global_wifi_component->add_connect_state_listener(this); } void update() override { this->publish_state(wifi::global_wifi_component->wifi_rssi()); } void dump_config() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + + // WiFiConnectStateListener interface - update RSSI immediately on connect + void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override { this->update(); } }; -} // namespace wifi_signal -} // namespace esphome +} // namespace esphome::wifi_signal #endif From 53ddd1a1cd8a5fc5f73550eadf14c2f5ac6c9c86 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Dec 2025 13:43:48 +0100 Subject: [PATCH 0559/1145] [wifi_signal] Add ifdef guards for clang-tidy compatibility (#12362) --- esphome/components/wifi_signal/wifi_signal_sensor.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/esphome/components/wifi_signal/wifi_signal_sensor.h b/esphome/components/wifi_signal/wifi_signal_sensor.h index cc951e8dd7..5d7f4b4562 100644 --- a/esphome/components/wifi_signal/wifi_signal_sensor.h +++ b/esphome/components/wifi_signal/wifi_signal_sensor.h @@ -7,16 +7,24 @@ #ifdef USE_WIFI namespace esphome::wifi_signal { +#ifdef USE_WIFI_LISTENERS class WiFiSignalSensor : public sensor::Sensor, public PollingComponent, public wifi::WiFiConnectStateListener { +#else +class WiFiSignalSensor : public sensor::Sensor, public PollingComponent { +#endif public: +#ifdef USE_WIFI_LISTENERS void setup() override { wifi::global_wifi_component->add_connect_state_listener(this); } +#endif void update() override { this->publish_state(wifi::global_wifi_component->wifi_rssi()); } void dump_config() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } +#ifdef USE_WIFI_LISTENERS // WiFiConnectStateListener interface - update RSSI immediately on connect void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override { this->update(); } +#endif }; } // namespace esphome::wifi_signal From 2515f1c0806d1641d8b04b3688f771c145cc4f48 Mon Sep 17 00:00:00 2001 From: Berik Visschers Date: Mon, 8 Dec 2025 14:37:59 +0100 Subject: [PATCH 0560/1145] Add seeed_xiao_esp32c6 board definition (#12307) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/esp32/boards.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/esphome/components/esp32/boards.py b/esphome/components/esp32/boards.py index 7107874a5b..514d674b55 100644 --- a/esphome/components/esp32/boards.py +++ b/esphome/components/esp32/boards.py @@ -1218,6 +1218,28 @@ ESP32_BOARD_PINS = { "LED_BUILTINB": 4, }, "sensesiot_weizen": {}, + "seeed_xiao_esp32c6": { + "D0": 0, + "D1": 1, + "D2": 2, + "D3": 21, + "D4": 22, + "D5": 23, + "D6": 16, + "D7": 17, + "D8": 19, + "D9": 20, + "D10": 18, + "MTDO": 7, + "MTCK": 6, + "MTDI": 5, + "MTMS": 4, + "BOOT": 9, + "LED": 8, + "LED_BUILTIN": 8, + "RF_SWITCH_EN": 3, + "RF_ANT_SELECT": 14, + }, "sg-o_airMon": {}, "sparkfun_lora_gateway_1-channel": {"MISO": 12, "MOSI": 13, "SCK": 14, "SS": 16}, "tinypico": {}, From 95efb3704524cc1e9f9aefefc2b4c189c3f0aca2 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Mon, 8 Dec 2025 14:39:43 +0100 Subject: [PATCH 0561/1145] [micronova] Set the write bit automatically (#12318) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/micronova/__init__.py | 6 ++++-- .../components/micronova/button/__init__.py | 2 +- esphome/components/micronova/micronova.cpp | 8 ++++++-- esphome/components/micronova/micronova.h | 1 - .../components/micronova/number/__init__.py | 20 +------------------ .../micronova/number/micronova_number.cpp | 2 +- .../micronova/number/micronova_number.h | 6 ------ .../components/micronova/switch/__init__.py | 2 +- tests/components/micronova/common.yaml | 2 +- 9 files changed, 15 insertions(+), 34 deletions(-) diff --git a/esphome/components/micronova/__init__.py b/esphome/components/micronova/__init__.py index 11213e82c5..637d0eb168 100644 --- a/esphome/components/micronova/__init__.py +++ b/esphome/components/micronova/__init__.py @@ -63,12 +63,14 @@ def MICRONOVA_ADDRESS_SCHEMA( schema = cv.Schema( { cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova), + # On write requests the write bit (0x80) is added automatically to the location + # Therefore no locations >= 0x80 are allowed cv.Optional( CONF_MEMORY_LOCATION, default=default_memory_location - ): cv.hex_int_range(), + ): cv.hex_int_range(min=0x00, max=0x79), cv.Optional( CONF_MEMORY_ADDRESS, default=default_memory_address - ): cv.hex_int_range(), + ): cv.hex_int_range(min=0x00, max=0xFF), } ) if is_polling_component: diff --git a/esphome/components/micronova/button/__init__.py b/esphome/components/micronova/button/__init__.py index dd57c9ec4f..38fee2f561 100644 --- a/esphome/components/micronova/button/__init__.py +++ b/esphome/components/micronova/button/__init__.py @@ -25,7 +25,7 @@ CONFIG_SCHEMA = cv.Schema( ) .extend( MICRONOVA_ADDRESS_SCHEMA( - default_memory_location=0xA0, + default_memory_location=0x20, default_memory_address=0x7D, is_polling_component=False, ) diff --git a/esphome/components/micronova/micronova.cpp b/esphome/components/micronova/micronova.cpp index 7343bc90ba..22daef4fe6 100644 --- a/esphome/components/micronova/micronova.cpp +++ b/esphome/components/micronova/micronova.cpp @@ -3,6 +3,9 @@ namespace esphome::micronova { +static const int STOVE_REPLY_DELAY = 60; +static const uint8_t WRITE_BIT = 1 << 7; // 0x80 + void MicroNovaBaseListener::dump_base_config() { ESP_LOGCONFIG(TAG, " Memory Location: %02X\n" @@ -125,7 +128,8 @@ void MicroNova::write_address(uint8_t location, uint8_t address, uint8_t data) { uint16_t checksum = 0; if (this->reply_pending_mutex_.try_lock()) { - write_data[0] = location; + uint8_t write_location = location | WRITE_BIT; + write_data[0] = write_location; write_data[1] = address; write_data[2] = data; @@ -140,7 +144,7 @@ void MicroNova::write_address(uint8_t location, uint8_t address, uint8_t data) { this->enable_rx_pin_->digital_write(false); this->current_transmission_.request_transmission_time = millis(); - this->current_transmission_.memory_location = location; + this->current_transmission_.memory_location = write_location; this->current_transmission_.memory_address = address; this->current_transmission_.reply_pending = true; this->current_transmission_.initiating_listener = nullptr; diff --git a/esphome/components/micronova/micronova.h b/esphome/components/micronova/micronova.h index c5a5ba4f4e..a2eee81be8 100644 --- a/esphome/components/micronova/micronova.h +++ b/esphome/components/micronova/micronova.h @@ -11,7 +11,6 @@ namespace esphome::micronova { static const char *const TAG = "micronova"; -static const int STOVE_REPLY_DELAY = 60; enum class MicroNovaFunctions { STOVE_FUNCTION_VOID = 0, diff --git a/esphome/components/micronova/number/__init__.py b/esphome/components/micronova/number/__init__.py index ec994a5aa5..07023e618c 100644 --- a/esphome/components/micronova/number/__init__.py +++ b/esphome/components/micronova/number/__init__.py @@ -17,7 +17,6 @@ ICON_FLASH = "mdi:flash" CONF_THERMOSTAT_TEMPERATURE = "thermostat_temperature" CONF_POWER_LEVEL = "power_level" -CONF_MEMORY_WRITE_LOCATION = "memory_write_location" MicroNovaNumber = micronova_ns.class_( "MicroNovaNumber", number.Number, MicroNovaListener @@ -40,25 +39,18 @@ CONFIG_SCHEMA = cv.Schema( ) .extend( { - cv.Optional( - CONF_MEMORY_WRITE_LOCATION, default=0xA0 - ): cv.hex_int_range(), cv.Optional(CONF_STEP, default=1.0): cv.float_range(min=0.1, max=10.0), } ), cv.Optional(CONF_POWER_LEVEL): number.number_schema( MicroNovaNumber, icon=ICON_FLASH, - ) - .extend( + ).extend( MICRONOVA_ADDRESS_SCHEMA( default_memory_location=0x20, default_memory_address=0x7F, is_polling_component=True, ) - ) - .extend( - {cv.Optional(CONF_MEMORY_WRITE_LOCATION, default=0xA0): cv.hex_int_range()} ), } ) @@ -81,11 +73,6 @@ async def to_code(config): MicroNovaFunctions.STOVE_FUNCTION_THERMOSTAT_TEMPERATURE, ) cg.add(numb.set_micronova_object(mv)) - cg.add( - numb.set_memory_write_location( - thermostat_temperature_config.get(CONF_MEMORY_WRITE_LOCATION) - ) - ) if power_level_config := config.get(CONF_POWER_LEVEL): numb = await number.new_number( @@ -98,8 +85,3 @@ async def to_code(config): mv, numb, power_level_config, MicroNovaFunctions.STOVE_FUNCTION_POWER_LEVEL ) cg.add(numb.set_micronova_object(mv)) - cg.add( - numb.set_memory_write_location( - power_level_config.get(CONF_MEMORY_WRITE_LOCATION) - ) - ) diff --git a/esphome/components/micronova/number/micronova_number.cpp b/esphome/components/micronova/number/micronova_number.cpp index 66e81e98b7..c71d819ad6 100644 --- a/esphome/components/micronova/number/micronova_number.cpp +++ b/esphome/components/micronova/number/micronova_number.cpp @@ -36,7 +36,7 @@ void MicroNovaNumber::control(float value) { default: break; } - this->micronova_->write_address(this->memory_write_location_, this->memory_address_, new_number); + this->micronova_->write_address(this->memory_location_, this->memory_address_, new_number); this->micronova_->request_update_listeners(); } diff --git a/esphome/components/micronova/number/micronova_number.h b/esphome/components/micronova/number/micronova_number.h index e1545bb35a..391765b730 100644 --- a/esphome/components/micronova/number/micronova_number.h +++ b/esphome/components/micronova/number/micronova_number.h @@ -18,12 +18,6 @@ class MicroNovaNumber : public number::Number, public MicroNovaListener { this->micronova_->request_address(this->memory_location_, this->memory_address_, this); } void process_value_from_stove(int value_from_stove) override; - - void set_memory_write_location(uint8_t l) { this->memory_write_location_ = l; } - uint8_t get_memory_write_location() { return this->memory_write_location_; } - - protected: - uint8_t memory_write_location_ = 0; }; } // namespace esphome::micronova diff --git a/esphome/components/micronova/switch/__init__.py b/esphome/components/micronova/switch/__init__.py index 006ada92aa..c6897d8e5c 100644 --- a/esphome/components/micronova/switch/__init__.py +++ b/esphome/components/micronova/switch/__init__.py @@ -28,7 +28,7 @@ CONFIG_SCHEMA = cv.Schema( ) .extend( MICRONOVA_ADDRESS_SCHEMA( - default_memory_location=0x80, + default_memory_location=0x00, default_memory_address=0x21, is_polling_component=False, ) diff --git a/tests/components/micronova/common.yaml b/tests/components/micronova/common.yaml index 73456aa199..660970350a 100644 --- a/tests/components/micronova/common.yaml +++ b/tests/components/micronova/common.yaml @@ -5,7 +5,7 @@ button: - platform: micronova custom_button: name: Custom Micronova Button - memory_location: 0xA0 + memory_location: 0x20 memory_address: 0x7D memory_data: 0x0F From c7382fc494796dc89f19967c50c61d3c9319e27f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Kub=C3=AD=C4=8Dek?= Date: Mon, 8 Dec 2025 15:07:10 +0100 Subject: [PATCH 0562/1145] [hlw8032] Single-phase metering IC (#7241) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/hlw8032/__init__.py | 1 + esphome/components/hlw8032/hlw8032.cpp | 194 ++++++++++++++++++ esphome/components/hlw8032/hlw8032.h | 44 ++++ esphome/components/hlw8032/sensor.py | 93 +++++++++ tests/components/hlw8032/common.yaml | 17 ++ tests/components/hlw8032/test.esp32-idf.yaml | 4 + .../components/hlw8032/test.esp8266-ard.yaml | 4 + tests/components/hlw8032/test.rp2040-ard.yaml | 4 + 9 files changed, 362 insertions(+) create mode 100644 esphome/components/hlw8032/__init__.py create mode 100644 esphome/components/hlw8032/hlw8032.cpp create mode 100644 esphome/components/hlw8032/hlw8032.h create mode 100644 esphome/components/hlw8032/sensor.py create mode 100644 tests/components/hlw8032/common.yaml create mode 100644 tests/components/hlw8032/test.esp32-idf.yaml create mode 100644 tests/components/hlw8032/test.esp8266-ard.yaml create mode 100644 tests/components/hlw8032/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 1fb6e111b7..2cd1453e12 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -212,6 +212,7 @@ esphome/components/he60r/* @clydebarrow esphome/components/heatpumpir/* @rob-deutsch esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/hlk_fm22x/* @OnFreund +esphome/components/hlw8032/* @rici4kubicek esphome/components/hm3301/* @freekode esphome/components/hmac_md5/* @dwmw2 esphome/components/homeassistant/* @esphome/core @OttoWinter diff --git a/esphome/components/hlw8032/__init__.py b/esphome/components/hlw8032/__init__.py new file mode 100644 index 0000000000..4908e10037 --- /dev/null +++ b/esphome/components/hlw8032/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@rici4kubicek"] diff --git a/esphome/components/hlw8032/hlw8032.cpp b/esphome/components/hlw8032/hlw8032.cpp new file mode 100644 index 0000000000..55e6664a8b --- /dev/null +++ b/esphome/components/hlw8032/hlw8032.cpp @@ -0,0 +1,194 @@ +#include "hlw8032.h" +#include "esphome/core/log.h" +#include + +namespace esphome::hlw8032 { + +static const char *const TAG = "hlw8032"; + +static constexpr uint8_t STATE_REG_OFFSET = 0; +static constexpr uint8_t VOLTAGE_PARAM_OFFSET = 2; +static constexpr uint8_t VOLTAGE_REG_OFFSET = 5; +static constexpr uint8_t CURRENT_PARAM_OFFSET = 8; +static constexpr uint8_t CURRENT_REG_OFFSET = 11; +static constexpr uint8_t POWER_PARAM_OFFSET = 14; +static constexpr uint8_t POWER_REG_OFFSET = 17; +static constexpr uint8_t DATA_UPDATE_REG_OFFSET = 20; +static constexpr uint8_t CHECKSUM_REG_OFFSET = 23; +static constexpr uint8_t PARAM_REG_USABLE_BIT = (1 << 0); +static constexpr uint8_t POWER_OVERFLOW_BIT = (1 << 1); +static constexpr uint8_t CURRENT_OVERFLOW_BIT = (1 << 2); +static constexpr uint8_t VOLTAGE_OVERFLOW_BIT = (1 << 3); +static constexpr uint8_t HAVE_POWER_BIT = (1 << 4); +static constexpr uint8_t HAVE_CURRENT_BIT = (1 << 5); +static constexpr uint8_t HAVE_VOLTAGE_BIT = (1 << 6); +static constexpr uint8_t CHECK_REG = 0x5A; +static constexpr uint8_t STATE_REG_CORRECTION_FUNC_NORMAL = 0x55; +static constexpr uint8_t STATE_REG_CORRECTION_FUNC_FAIL = 0xAA; +static constexpr uint8_t STATE_REG_CORRECTION_MASK = 0xF0; +static constexpr uint8_t STATE_REG_OVERFLOW_MASK = 0xF; +static constexpr uint8_t PACKET_LENGTH = 24; + +void HLW8032Component::loop() { + while (this->available()) { + uint8_t data = this->read(); + if (!this->header_found_) { + if ((data == STATE_REG_CORRECTION_FUNC_NORMAL) || (data == STATE_REG_CORRECTION_FUNC_FAIL) || + (data & STATE_REG_CORRECTION_MASK) == STATE_REG_CORRECTION_MASK) { + this->header_found_ = true; + this->raw_data_[0] = data; + } + } else if (data == CHECK_REG) { + this->raw_data_[1] = data; + this->raw_data_index_ = 2; + this->check_ = 0; + } else if (this->raw_data_index_ >= 2 && this->raw_data_index_ < PACKET_LENGTH) { + this->raw_data_[this->raw_data_index_++] = data; + if (this->raw_data_index_ < PACKET_LENGTH) { + this->check_ += data; + } else if (this->raw_data_index_ == PACKET_LENGTH) { + if (this->check_ == this->raw_data_[CHECKSUM_REG_OFFSET]) { + this->parse_data_(); + } else { + ESP_LOGW(TAG, "Invalid checksum: 0x%02X != 0x%02X", this->check_, this->raw_data_[CHECKSUM_REG_OFFSET]); + } + this->raw_data_index_ = 0; + this->header_found_ = false; + memset(this->raw_data_, 0, PACKET_LENGTH); + } + } + } +} + +uint32_t HLW8032Component::read_uint24_(uint8_t offset) { + return encode_uint24(this->raw_data_[offset], this->raw_data_[offset + 1], this->raw_data_[offset + 2]); +} + +void HLW8032Component::parse_data_() { + // Parse header + uint8_t state_reg = this->raw_data_[STATE_REG_OFFSET]; + + if (state_reg == STATE_REG_CORRECTION_FUNC_FAIL) { + ESP_LOGE(TAG, "The chip's function of error correction fails."); + return; + } + + // Parse data frame + uint32_t voltage_parameter = this->read_uint24_(VOLTAGE_PARAM_OFFSET); + uint32_t voltage_reg = this->read_uint24_(VOLTAGE_REG_OFFSET); + uint32_t current_parameter = this->read_uint24_(CURRENT_PARAM_OFFSET); + uint32_t current_reg = this->read_uint24_(CURRENT_REG_OFFSET); + uint32_t power_parameter = this->read_uint24_(POWER_PARAM_OFFSET); + uint32_t power_reg = this->read_uint24_(POWER_REG_OFFSET); + uint8_t data_update_register = this->raw_data_[DATA_UPDATE_REG_OFFSET]; + + bool have_power = data_update_register & HAVE_POWER_BIT; + bool have_current = data_update_register & HAVE_CURRENT_BIT; + bool have_voltage = data_update_register & HAVE_VOLTAGE_BIT; + + bool power_cycle_exceeds_range = false; + bool parameter_regs_usable = true; + + if ((state_reg & STATE_REG_CORRECTION_MASK) == STATE_REG_CORRECTION_MASK) { + if (state_reg & STATE_REG_OVERFLOW_MASK) { + if (state_reg & VOLTAGE_OVERFLOW_BIT) { + have_voltage = false; + } + if (state_reg & CURRENT_OVERFLOW_BIT) { + have_current = false; + } + if (state_reg & POWER_OVERFLOW_BIT) { + have_power = false; + } + if (state_reg & PARAM_REG_USABLE_BIT) { + parameter_regs_usable = false; + } + + ESP_LOGW(TAG, + "Reports: (0x%02X)\n" + " Voltage REG overflows: %s\n" + " Current REG overflows: %s\n" + " Power REG overflows: %s\n" + " Voltage/Current/Power Parameter REGs not usable: %s\n", + state_reg, YESNO(!have_voltage), YESNO(!have_current), YESNO(!have_power), + YESNO(!parameter_regs_usable)); + + if (!parameter_regs_usable) { + return; + } + } + power_cycle_exceeds_range = have_power; + } + + ESP_LOGVV(TAG, + "Parsed data:\n" + " Voltage: Parameter REG 0x%06" PRIX32 ", REG 0x%06" PRIX32 "\n" + " Current: Parameter REG 0x%06" PRIX32 ", REG 0x%06" PRIX32 "\n" + " Power: Parameter REG 0x%06" PRIX32 ", REG 0x%06" PRIX32 "\n" + " Data Update: REG 0x%02" PRIX8 "\n", + voltage_parameter, voltage_reg, current_parameter, current_reg, power_parameter, power_reg, + data_update_register); + + const float current_multiplier = 1 / (this->current_resistor_ * 1000); + + float voltage = 0.0f; + if (have_voltage && voltage_reg) { + voltage = float(voltage_parameter) * this->voltage_divider_ / float(voltage_reg); + } + if (this->voltage_sensor_ != nullptr) { + this->voltage_sensor_->publish_state(voltage); + } + + float power = 0.0f; + if (have_power && power_reg && !power_cycle_exceeds_range) { + power = (float(power_parameter) / float(power_reg)) * this->voltage_divider_ * current_multiplier; + } + if (this->power_sensor_ != nullptr) { + this->power_sensor_->publish_state(power); + } + + float current = 0.0f; + if (have_current && current_reg) { + current = float(current_parameter) * current_multiplier / float(current_reg); + } + if (this->current_sensor_ != nullptr) { + this->current_sensor_->publish_state(current); + } + + float pf = NAN; + const float apparent_power = voltage * current; + if (have_voltage && have_current) { + if (have_power || power_cycle_exceeds_range) { + if (apparent_power > 0) { + pf = power / apparent_power; + if (pf < 0 || pf > 1) { + ESP_LOGD(TAG, "Impossible power factor: %.4f not in interval [0, 1]", pf); + pf = NAN; + } + } else if (apparent_power == 0 && power == 0) { + // No load, report ideal power factor + pf = 1.0f; + } + } + } + if (this->apparent_power_sensor_ != nullptr) { + this->apparent_power_sensor_->publish_state(apparent_power); + } + if (this->power_factor_sensor_ != nullptr) { + this->power_factor_sensor_->publish_state(pf); + } +} + +void HLW8032Component::dump_config() { + ESP_LOGCONFIG(TAG, + "Configuration:\n" + " Current resistor: %.1f mΩ\n" + " Voltage Divider: %.3f", + this->current_resistor_ * 1000.0f, this->voltage_divider_); + LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); + LOG_SENSOR(" ", "Current", this->current_sensor_); + LOG_SENSOR(" ", "Power", this->power_sensor_); + LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_); + LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_); +} +} // namespace esphome::hlw8032 diff --git a/esphome/components/hlw8032/hlw8032.h b/esphome/components/hlw8032/hlw8032.h new file mode 100644 index 0000000000..d4c7dbd26c --- /dev/null +++ b/esphome/components/hlw8032/hlw8032.h @@ -0,0 +1,44 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" + +namespace esphome::hlw8032 { + +class HLW8032Component : public Component, public uart::UARTDevice { + public: + void loop() override; + void dump_config() override; + + void set_current_resistor(float current_resistor) { this->current_resistor_ = current_resistor; } + void set_voltage_divider(float voltage_divider) { this->voltage_divider_ = voltage_divider; } + void set_voltage_sensor(sensor::Sensor *voltage_sensor) { this->voltage_sensor_ = voltage_sensor; } + void set_current_sensor(sensor::Sensor *current_sensor) { this->current_sensor_ = current_sensor; } + void set_power_sensor(sensor::Sensor *power_sensor) { this->power_sensor_ = power_sensor; } + void set_apparent_power_sensor(sensor::Sensor *apparent_power_sensor) { + this->apparent_power_sensor_ = apparent_power_sensor; + } + void set_power_factor_sensor(sensor::Sensor *power_factor_sensor) { + this->power_factor_sensor_ = power_factor_sensor; + } + + protected: + void parse_data_(); + uint32_t read_uint24_(uint8_t offset); + + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *apparent_power_sensor_{nullptr}; + sensor::Sensor *power_factor_sensor_{nullptr}; + + float current_resistor_{0.001f}; + float voltage_divider_{1.720f}; + uint8_t raw_data_[24]{}; + uint8_t check_{0}; + uint8_t raw_data_index_{0}; + bool header_found_{false}; +}; + +} // namespace esphome::hlw8032 diff --git a/esphome/components/hlw8032/sensor.py b/esphome/components/hlw8032/sensor.py new file mode 100644 index 0000000000..96800e46f4 --- /dev/null +++ b/esphome/components/hlw8032/sensor.py @@ -0,0 +1,93 @@ +import esphome.codegen as cg +from esphome.components import sensor, uart +import esphome.config_validation as cv +from esphome.const import ( + CONF_APPARENT_POWER, + CONF_CURRENT, + CONF_CURRENT_RESISTOR, + CONF_ID, + CONF_POWER, + CONF_POWER_FACTOR, + CONF_VOLTAGE, + CONF_VOLTAGE_DIVIDER, + DEVICE_CLASS_APPARENT_POWER, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + UNIT_AMPERE, + UNIT_VOLT, + UNIT_VOLT_AMPS, + UNIT_WATT, +) + +DEPENDENCIES = ["uart"] + +hlw8032_ns = cg.esphome_ns.namespace("hlw8032") +HLW8032Component = hlw8032_ns.class_("HLW8032Component", cg.Component, uart.UARTDevice) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(HLW8032Component), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT_AMPS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, + cv.Optional(CONF_VOLTAGE_DIVIDER, default=1.720): cv.positive_float, + } +).extend(uart.UART_DEVICE_SCHEMA) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "hlw8032", baud_rate=4800, require_rx=True, data_bits=8, parity="EVEN" +) + + +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 voltage_config := config.get(CONF_VOLTAGE): + sens = await sensor.new_sensor(voltage_config) + cg.add(var.set_voltage_sensor(sens)) + if current_config := config.get(CONF_CURRENT): + sens = await sensor.new_sensor(current_config) + cg.add(var.set_current_sensor(sens)) + if power_config := config.get(CONF_POWER): + sens = await sensor.new_sensor(power_config) + cg.add(var.set_power_sensor(sens)) + if apparent_power_config := config.get(CONF_APPARENT_POWER): + sens = await sensor.new_sensor(apparent_power_config) + cg.add(var.set_apparent_power_sensor(sens)) + if power_factor_config := config.get(CONF_POWER_FACTOR): + sens = await sensor.new_sensor(power_factor_config) + cg.add(var.set_power_factor_sensor(sens)) + cg.add(var.set_current_resistor(config[CONF_CURRENT_RESISTOR])) + cg.add(var.set_voltage_divider(config[CONF_VOLTAGE_DIVIDER])) diff --git a/tests/components/hlw8032/common.yaml b/tests/components/hlw8032/common.yaml new file mode 100644 index 0000000000..1b4e537576 --- /dev/null +++ b/tests/components/hlw8032/common.yaml @@ -0,0 +1,17 @@ +sensor: + - platform: hlw8032 + voltage: + name: HLW8032 Voltage + id: hlw8032_voltage + current: + name: HLW8032 Current + id: hlw8032_current + power: + name: HLW8032 Power + id: hlw8032_power + apparent_power: + name: HLW8032 Apparent Power + id: hlw8032_apparent_power + power_factor: + name: HLW8032 Power Factor + id: hlw8032_power_factor diff --git a/tests/components/hlw8032/test.esp32-idf.yaml b/tests/components/hlw8032/test.esp32-idf.yaml new file mode 100644 index 0000000000..911b867708 --- /dev/null +++ b/tests/components/hlw8032/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + uart_4800_even: !include ../../test_build_components/common/uart_4800_even/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/hlw8032/test.esp8266-ard.yaml b/tests/components/hlw8032/test.esp8266-ard.yaml new file mode 100644 index 0000000000..9c1c11c6a1 --- /dev/null +++ b/tests/components/hlw8032/test.esp8266-ard.yaml @@ -0,0 +1,4 @@ +packages: + uart_4800_even: !include ../../test_build_components/common/uart_4800_even/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/hlw8032/test.rp2040-ard.yaml b/tests/components/hlw8032/test.rp2040-ard.yaml new file mode 100644 index 0000000000..40b6e81bb2 --- /dev/null +++ b/tests/components/hlw8032/test.rp2040-ard.yaml @@ -0,0 +1,4 @@ +packages: + uart_4800_even: !include ../../test_build_components/common/uart_4800_even/rp2040-ard.yaml + +<<: !include common.yaml From 4466c4c69fdec95113e8449b4420938373b636be Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Dec 2025 15:09:04 +0100 Subject: [PATCH 0563/1145] [libretiny] Fix WiFi scan timeout loop when scan fails (#12356) --- esphome/components/wifi/wifi_component_libretiny.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 1a6f037a87..d6bc8e53da 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -445,6 +445,7 @@ bool WiFiComponent::wifi_scan_start_(bool passive) { } void WiFiComponent::wifi_scan_done_callback_() { this->scan_result_.clear(); + this->scan_done_ = true; int16_t num = WiFi.scanComplete(); if (num < 0) @@ -463,7 +464,6 @@ void WiFiComponent::wifi_scan_done_callback_() { ssid.length() == 0); } WiFi.scanDelete(); - this->scan_done_ = true; #ifdef USE_WIFI_LISTENERS for (auto *listener : this->scan_results_listeners_) { listener->on_wifi_scan_results(this->scan_result_); From 5144154f911f6ac06a7017f0b47b26d024d9e2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Blanchet?= <120399978+arno1801@users.noreply.github.com> Date: Mon, 8 Dec 2025 09:31:05 -0500 Subject: [PATCH 0564/1145] [hub75] fix id conflict (#12365) --- tests/components/hub75/test.esp32-idf.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/components/hub75/test.esp32-idf.yaml b/tests/components/hub75/test.esp32-idf.yaml index c275d24187..9f6bd57292 100644 --- a/tests/components/hub75/test.esp32-idf.yaml +++ b/tests/components/hub75/test.esp32-idf.yaml @@ -25,15 +25,15 @@ display: oe_pin: GPIO15 clk_pin: GPIO16 pages: - - id: page1 + - id: page1_hub75 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - - id: page2 + - id: page2_hub75 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); on_page_change: - from: page1 - to: page2 + from: page1_hub75 + to: page2_hub75 then: lambda: |- ESP_LOGD("display", "1 -> 2"); From eda743ee481a75424933e35ed591dc1d933198de Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 8 Dec 2025 08:50:23 -0600 Subject: [PATCH 0565/1145] [usb_cdc_acm] New component (#11687) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/usb_cdc_acm/__init__.py | 76 +++ .../components/usb_cdc_acm/usb_cdc_acm.cpp | 495 ++++++++++++++++++ esphome/components/usb_cdc_acm/usb_cdc_acm.h | 135 +++++ .../usb_cdc_acm/test.esp32-p4-idf.yaml | 5 + .../usb_cdc_acm/test.esp32-s2-idf.yaml | 5 + .../usb_cdc_acm/test.esp32-s3-idf.yaml | 6 + .../usb_cdc_acm/tinyusb_common.yaml | 8 + 8 files changed, 731 insertions(+) create mode 100644 esphome/components/usb_cdc_acm/__init__.py create mode 100644 esphome/components/usb_cdc_acm/usb_cdc_acm.cpp create mode 100644 esphome/components/usb_cdc_acm/usb_cdc_acm.h create mode 100644 tests/components/usb_cdc_acm/test.esp32-p4-idf.yaml create mode 100644 tests/components/usb_cdc_acm/test.esp32-s2-idf.yaml create mode 100644 tests/components/usb_cdc_acm/test.esp32-s3-idf.yaml create mode 100644 tests/components/usb_cdc_acm/tinyusb_common.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 2cd1453e12..af926d2d61 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -524,6 +524,7 @@ esphome/components/ufire_ise/* @pvizeli esphome/components/ultrasonic/* @OttoWinter esphome/components/update/* @jesserockz esphome/components/uponor_smatrix/* @kroimon +esphome/components/usb_cdc_acm/* @kbx81 esphome/components/usb_host/* @clydebarrow esphome/components/usb_uart/* @clydebarrow esphome/components/valve/* @esphome/core diff --git a/esphome/components/usb_cdc_acm/__init__.py b/esphome/components/usb_cdc_acm/__init__.py new file mode 100644 index 0000000000..6693d8e75e --- /dev/null +++ b/esphome/components/usb_cdc_acm/__init__.py @@ -0,0 +1,76 @@ +import esphome.codegen as cg +from esphome.components import esp32, uart +from esphome.components.esp32 import ( + VARIANT_ESP32P4, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + add_idf_sdkconfig_option, +) +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_RX_BUFFER_SIZE, CONF_TX_BUFFER_SIZE +from esphome.types import ConfigType + +CODEOWNERS = ["@kbx81"] +AUTO_LOAD = ["uart"] +DEPENDENCIES = ["tinyusb"] + +CONF_INTERFACES = "interfaces" + +usb_cdc_acm_ns = cg.esphome_ns.namespace("usb_cdc_acm") +USBCDCACMComponent = usb_cdc_acm_ns.class_("USBCDCACMComponent", cg.Component) +USBCDCACMInstance = usb_cdc_acm_ns.class_( + "USBCDCACMInstance", uart.UARTComponent, cg.Parented.template(USBCDCACMComponent) +) + + +# Schema for individual CDC ACM interface instances +INTERFACE_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(USBCDCACMInstance), + } +) + +# Main component schema +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(USBCDCACMComponent), + cv.Optional(CONF_RX_BUFFER_SIZE, default=256): cv.All( + cv.validate_bytes, cv.uint16_t + ), + cv.Optional(CONF_TX_BUFFER_SIZE, default=256): cv.All( + cv.validate_bytes, cv.uint16_t + ), + cv.Optional(CONF_INTERFACES, default=[{}]): cv.All( + cv.ensure_list(INTERFACE_SCHEMA), + cv.Length(min=1, max=2), # At least 1, at most 2 interfaces + ), + } + ).extend(cv.COMPONENT_SCHEMA), + esp32.only_on_variant( + supported=[VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3], + ), +) + + +async def to_code(config: ConfigType) -> None: + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + # Create and register interface instances + for interface_index, interface_conf in enumerate(config[CONF_INTERFACES]): + interface = cg.new_Pvariable(interface_conf[CONF_ID]) + await cg.register_parented(interface, var) + cg.add(interface.set_interface_number(interface_index)) + cg.add(var.add_interface(interface)) + + # Configure TinyUSB with the correct number of CDC interfaces + num_interfaces = len(config[CONF_INTERFACES]) + add_idf_sdkconfig_option("CONFIG_TINYUSB_CDC_ENABLED", True) + add_idf_sdkconfig_option("CONFIG_TINYUSB_CDC_COUNT", num_interfaces) + add_idf_sdkconfig_option( + "CONFIG_TINYUSB_CDC_RX_BUFSIZE", config[CONF_RX_BUFFER_SIZE] + ) + add_idf_sdkconfig_option( + "CONFIG_TINYUSB_CDC_TX_BUFSIZE", config[CONF_TX_BUFFER_SIZE] + ) diff --git a/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp b/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp new file mode 100644 index 0000000000..1cf614286f --- /dev/null +++ b/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp @@ -0,0 +1,495 @@ +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#include "usb_cdc_acm.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/ringbuf.h" +#include "freertos/task.h" +#include "esp_log.h" + +#include "tusb.h" +#include "tusb_cdc_acm.h" + +namespace esphome::usb_cdc_acm { + +static const char *TAG = "usb_cdc_acm"; + +static constexpr size_t USB_TX_TASK_STACK_SIZE = 4096; +static constexpr size_t USB_TX_TASK_STACK_SIZE_VV = 8192; + +// Global component instance for managing USB device +USBCDCACMComponent *global_usb_cdc_component = nullptr; + +static USBCDCACMInstance *get_instance_by_itf(int itf) { + if (global_usb_cdc_component == nullptr) { + return nullptr; + } + return global_usb_cdc_component->get_interface_by_number(itf); +} + +static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) { + USBCDCACMInstance *instance = get_instance_by_itf(itf); + if (instance == nullptr) { + ESP_LOGE(TAG, "RX callback: invalid interface %d", itf); + return; + } + + size_t rx_size = 0; + static uint8_t rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE] = {0}; + + // read from USB + esp_err_t ret = + tinyusb_cdcacm_read(static_cast(itf), rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size); + ESP_LOGV(TAG, "tinyusb_cdc_rx_callback itf=%d (size: %u)", itf, rx_size); + ESP_LOGVV(TAG, "rx_buf = %s", format_hex_pretty(rx_buf, rx_size).c_str()); + + if (ret == ESP_OK && rx_size > 0) { + RingbufHandle_t rx_ringbuf = instance->get_rx_ringbuf(); + if (rx_ringbuf != nullptr) { + BaseType_t send_res = xRingbufferSend(rx_ringbuf, rx_buf, rx_size, 0); + if (send_res != pdTRUE) { + ESP_LOGE(TAG, "USB RX itf=%d: buffer full, %u bytes lost", itf, rx_size); + } else { + ESP_LOGV(TAG, "USB RX itf=%d: queued %u bytes", itf, rx_size); + } + } + } +} + +static void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) { + USBCDCACMInstance *instance = get_instance_by_itf(itf); + if (instance == nullptr) { + ESP_LOGE(TAG, "Line state callback: invalid interface %d", itf); + return; + } + + int dtr = event->line_state_changed_data.dtr; + int rts = event->line_state_changed_data.rts; + ESP_LOGV(TAG, "Line state itf=%d: DTR=%d, RTS=%d", itf, dtr, rts); + + // Queue event for processing in main loop + instance->queue_line_state_event(dtr != 0, rts != 0); +} + +static void tinyusb_cdc_line_coding_changed_callback(int itf, cdcacm_event_t *event) { + USBCDCACMInstance *instance = get_instance_by_itf(itf); + if (instance == nullptr) { + ESP_LOGE(TAG, "Line coding callback: invalid interface %d", itf); + return; + } + + uint32_t bit_rate = event->line_coding_changed_data.p_line_coding->bit_rate; + uint8_t stop_bits = event->line_coding_changed_data.p_line_coding->stop_bits; + uint8_t parity = event->line_coding_changed_data.p_line_coding->parity; + uint8_t data_bits = event->line_coding_changed_data.p_line_coding->data_bits; + ESP_LOGV(TAG, "Line coding itf=%d: bit_rate=%" PRIu32 " stop_bits=%u parity=%u data_bits=%u", itf, bit_rate, + stop_bits, parity, data_bits); + + // Queue event for processing in main loop + instance->queue_line_coding_event(bit_rate, stop_bits, parity, data_bits); +} + +static esp_err_t ringbuf_read_bytes(RingbufHandle_t ring_buf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size, + TickType_t xTicksToWait) { + size_t read_sz; + uint8_t *buf = static_cast(xRingbufferReceiveUpTo(ring_buf, &read_sz, xTicksToWait, out_buf_sz)); + + if (buf == nullptr) { + return ESP_FAIL; + } + + memcpy(out_buf, buf, read_sz); + vRingbufferReturnItem(ring_buf, (void *) buf); + *rx_data_size = read_sz; + + // Buffer's data can be wrapped, in which case we should perform another read + buf = static_cast(xRingbufferReceiveUpTo(ring_buf, &read_sz, 0, out_buf_sz - *rx_data_size)); + if (buf != nullptr) { + memcpy(out_buf + *rx_data_size, buf, read_sz); + vRingbufferReturnItem(ring_buf, (void *) buf); + *rx_data_size += read_sz; + } + + return ESP_OK; +} + +//============================================================================== +// USBCDCACMInstance Implementation +//============================================================================== + +void USBCDCACMInstance::setup() { + this->usb_tx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_TX_BUFSIZE, RINGBUF_TYPE_BYTEBUF); + if (this->usb_tx_ringbuf_ == nullptr) { + ESP_LOGE(TAG, "USB TX buffer creation error for itf %d", this->itf_); + this->parent_->mark_failed(); + return; + } + + this->usb_rx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_RX_BUFSIZE, RINGBUF_TYPE_BYTEBUF); + if (this->usb_rx_ringbuf_ == nullptr) { + ESP_LOGE(TAG, "USB RX buffer creation error for itf %d", this->itf_); + this->parent_->mark_failed(); + return; + } + + // Configure this CDC interface + const tinyusb_config_cdcacm_t acm_cfg = { + .usb_dev = TINYUSB_USBDEV_0, + .cdc_port = this->itf_, + .callback_rx = &tinyusb_cdc_rx_callback, + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback, + .callback_line_coding_changed = &tinyusb_cdc_line_coding_changed_callback, + }; + + esp_err_t result = tusb_cdc_acm_init(&acm_cfg); + if (result != ESP_OK) { + ESP_LOGE(TAG, "tusb_cdc_acm_init failed: %d", result); + this->parent_->mark_failed(); + return; + } + + // Use a larger stack size for (very) verbose logging + const size_t stack_size = esp_log_level_get(TAG) > ESP_LOG_DEBUG ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE; + + // Create a simple, unique task name per interface + char task_name[] = "usb_tx_0"; + task_name[sizeof(task_name) - 1] = format_hex_char(static_cast(this->itf_)); + xTaskCreate(usb_tx_task_fn, task_name, stack_size, this, 4, &this->usb_tx_task_handle_); + + if (this->usb_tx_task_handle_ == nullptr) { + ESP_LOGE(TAG, "Failed to create USB TX task for itf %d", this->itf_); + this->parent_->mark_failed(); + return; + } +} + +void USBCDCACMInstance::loop() { + // Process events from the lock-free queue + this->process_events_(); +} + +void USBCDCACMInstance::queue_line_state_event(bool dtr, bool rts) { + // Allocate event from pool + CDCEvent *event = this->event_pool_.allocate(); + if (event == nullptr) { + ESP_LOGW(TAG, "Event pool exhausted, line state event dropped (itf=%d)", this->itf_); + return; + } + + event->type = CDC_EVENT_LINE_STATE_CHANGED; + event->data.line_state.dtr = dtr; + event->data.line_state.rts = rts; + + if (!this->event_queue_.push(event)) { + ESP_LOGW(TAG, "Event queue full, line state event dropped (itf=%d)", this->itf_); + // Return event to pool since we couldn't queue it + this->event_pool_.release(event); + } else { + // Wake main loop immediately to process event +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + App.wake_loop_threadsafe(); +#endif + } +} + +void USBCDCACMInstance::queue_line_coding_event(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, + uint8_t data_bits) { + // Allocate event from pool + CDCEvent *event = this->event_pool_.allocate(); + if (event == nullptr) { + ESP_LOGW(TAG, "Event pool exhausted, line coding event dropped (itf=%d)", this->itf_); + return; + } + + event->type = CDC_EVENT_LINE_CODING_CHANGED; + event->data.line_coding.bit_rate = bit_rate; + event->data.line_coding.stop_bits = stop_bits; + event->data.line_coding.parity = parity; + event->data.line_coding.data_bits = data_bits; + + if (!this->event_queue_.push(event)) { + ESP_LOGW(TAG, "Event queue full, line coding event dropped (itf=%d)", this->itf_); + // Return event to pool since we couldn't queue it + this->event_pool_.release(event); + } else { + // Wake main loop immediately to process event +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + App.wake_loop_threadsafe(); +#endif + } +} + +void USBCDCACMInstance::process_events_() { + // Process all pending events from the queue + CDCEvent *event; + while ((event = this->event_queue_.pop()) != nullptr) { + switch (event->type) { + case CDC_EVENT_LINE_STATE_CHANGED: { + bool dtr = event->data.line_state.dtr; + bool rts = event->data.line_state.rts; + + // Invoke user callback in main loop context + if (this->line_state_callback_ != nullptr) { + this->line_state_callback_(dtr, rts); + } + break; + } + case CDC_EVENT_LINE_CODING_CHANGED: { + uint32_t bit_rate = event->data.line_coding.bit_rate; + uint8_t stop_bits = event->data.line_coding.stop_bits; + uint8_t parity = event->data.line_coding.parity; + uint8_t data_bits = event->data.line_coding.data_bits; + + // Update UART configuration based on CDC line coding + this->baud_rate_ = bit_rate; + this->data_bits_ = data_bits; + + // Convert CDC stop bits to UART stop bits format + // CDC: 0=1 stop bit, 1=1.5 stop bits, 2=2 stop bits + this->stop_bits_ = (stop_bits == 0) ? 1 : (stop_bits == 1) ? 1 : 2; + + // Convert CDC parity to UART parity format + // CDC: 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space + switch (parity) { + case 0: + this->parity_ = uart::UART_CONFIG_PARITY_NONE; + break; + case 1: + this->parity_ = uart::UART_CONFIG_PARITY_ODD; + break; + case 2: + this->parity_ = uart::UART_CONFIG_PARITY_EVEN; + break; + default: + // Mark and Space parity are not commonly supported, default to None + this->parity_ = uart::UART_CONFIG_PARITY_NONE; + break; + } + + // Invoke user callback in main loop context + if (this->line_coding_callback_ != nullptr) { + this->line_coding_callback_(bit_rate, stop_bits, parity, data_bits); + } + break; + } + } + // Return event to pool for reuse + this->event_pool_.release(event); + } +} + +void USBCDCACMInstance::usb_tx_task_fn(void *arg) { + auto *instance = static_cast(arg); + instance->usb_tx_task(); +} + +void USBCDCACMInstance::usb_tx_task() { + uint8_t data[CONFIG_TINYUSB_CDC_TX_BUFSIZE] = {0}; + size_t tx_data_size = 0; + + while (1) { + // Wait for a notification from the bridge component + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + // When we do wake up, we can be sure there is data in the ring buffer + esp_err_t ret = ringbuf_read_bytes(this->usb_tx_ringbuf_, data, CONFIG_TINYUSB_CDC_TX_BUFSIZE, &tx_data_size, 0); + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "USB TX itf=%d: RingBuf read failed", this->itf_); + continue; + } else if (tx_data_size == 0) { + ESP_LOGD(TAG, "USB TX itf=%d: RingBuf empty, skipping", this->itf_); + continue; + } + + ESP_LOGV(TAG, "USB TX itf=%d: Read %d bytes from buffer", this->itf_, tx_data_size); + ESP_LOGVV(TAG, "data = %s", format_hex_pretty(data, tx_data_size).c_str()); + + // Serial data will be split up into 64 byte chunks to be sent over USB so this + // usually will take multiple iterations + uint8_t *data_head = &data[0]; + + while (tx_data_size > 0) { + size_t queued = tinyusb_cdcacm_write_queue(this->itf_, data_head, tx_data_size); + ESP_LOGV(TAG, "USB TX itf=%d: enqueued: size=%d, queued=%u", this->itf_, tx_data_size, queued); + + tx_data_size -= queued; + data_head += queued; + + ESP_LOGV(TAG, "USB TX itf=%d: waiting 10ms for flush", this->itf_); + esp_err_t flush_ret = tinyusb_cdcacm_write_flush(this->itf_, pdMS_TO_TICKS(10)); + + if (flush_ret != ESP_OK) { + ESP_LOGE(TAG, "USB TX itf=%d: flush failed", this->itf_); + tud_cdc_n_write_clear(this->itf_); + break; + } + } + } +} + +//============================================================================== +// UARTComponent Interface Implementation +//============================================================================== + +void USBCDCACMInstance::write_array(const uint8_t *data, size_t len) { + if (len == 0) { + return; + } + + // Write data to TX ring buffer + BaseType_t send_res = xRingbufferSend(this->usb_tx_ringbuf_, data, len, 0); + if (send_res != pdTRUE) { + ESP_LOGW(TAG, "USB TX itf=%d: buffer full, %u bytes dropped", this->itf_, len); + return; + } + + // Notify TX task that data is available + if (this->usb_tx_task_handle_ != nullptr) { + xTaskNotifyGive(this->usb_tx_task_handle_); + } +} + +bool USBCDCACMInstance::peek_byte(uint8_t *data) { + if (this->has_peek_) { + *data = this->peek_buffer_; + return true; + } + + if (this->read_byte(&this->peek_buffer_)) { + *data = this->peek_buffer_; + this->has_peek_ = true; + return true; + } + + return false; +} + +bool USBCDCACMInstance::read_array(uint8_t *data, size_t len) { + if (len == 0) { + return true; + } + + size_t original_len = len; + size_t bytes_read = 0; + + // First, use the peek buffer if available + if (this->has_peek_) { + data[0] = this->peek_buffer_; + this->has_peek_ = false; + bytes_read = 1; + data++; + if (--len == 0) { // Decrement len first, then check it... + return true; // No more to read + } + } + + // Read remaining bytes from RX ring buffer + size_t rx_size = 0; + uint8_t *buf = static_cast(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len)); + if (buf == nullptr) { + return false; + } + + memcpy(data, buf, rx_size); + vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf); + bytes_read += rx_size; + data += rx_size; + len -= rx_size; + if (len == 0) { + return true; // No more to read + } + + // Buffer's data may wrap around, in which case we should perform another read + buf = static_cast(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len)); + if (buf == nullptr) { + return false; + } + + memcpy(data, buf, rx_size); + vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf); + bytes_read += rx_size; + + return bytes_read == original_len; +} + +int USBCDCACMInstance::available() { + UBaseType_t waiting = 0; + if (this->usb_rx_ringbuf_ != nullptr) { + vRingbufferGetInfo(this->usb_rx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting); + } + return static_cast(waiting) + (this->has_peek_ ? 1 : 0); +} + +void USBCDCACMInstance::flush() { + // Wait for TX ring buffer to be empty + if (this->usb_tx_ringbuf_ == nullptr) { + return; + } + + UBaseType_t waiting = 1; + while (waiting > 0) { + vRingbufferGetInfo(this->usb_tx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting); + if (waiting > 0) { + vTaskDelay(pdMS_TO_TICKS(1)); + } + } + + // Also wait for USB to finish transmitting + tinyusb_cdcacm_write_flush(this->itf_, pdMS_TO_TICKS(100)); +} + +//============================================================================== +// USBCDCACMComponent Implementation +//============================================================================== + +USBCDCACMComponent::USBCDCACMComponent() { global_usb_cdc_component = this; } + +void USBCDCACMComponent::setup() { + // Setup all registered interfaces + for (auto interface : this->interfaces_) { + if (interface != nullptr) { + interface->setup(); + } + } +} + +void USBCDCACMComponent::loop() { + // Call loop() on all registered interfaces to process events + for (auto interface : this->interfaces_) { + if (interface != nullptr) { + interface->loop(); + } + } +} + +void USBCDCACMComponent::dump_config() { + ESP_LOGCONFIG(TAG, + "USB CDC-ACM:\n" + " Number of Interfaces: %d", + this->interfaces_[MAX_USB_CDC_INSTANCES - 1] != nullptr ? MAX_USB_CDC_INSTANCES : 1); +} + +void USBCDCACMComponent::add_interface(USBCDCACMInstance *interface) { + uint8_t itf_num = static_cast(interface->get_itf()); + if (itf_num < MAX_USB_CDC_INSTANCES) { + this->interfaces_[itf_num] = interface; + } else { + ESP_LOGE(TAG, "Interface number must be less than %u", MAX_USB_CDC_INSTANCES); + } +} + +USBCDCACMInstance *USBCDCACMComponent::get_interface_by_number(uint8_t itf) { + for (auto interface : this->interfaces_) { + if ((interface != nullptr) && (interface->get_itf() == static_cast(itf))) { + return interface; + } + } + return nullptr; +} + +} // namespace esphome::usb_cdc_acm +#endif diff --git a/esphome/components/usb_cdc_acm/usb_cdc_acm.h b/esphome/components/usb_cdc_acm/usb_cdc_acm.h new file mode 100644 index 0000000000..8c00f5d52f --- /dev/null +++ b/esphome/components/usb_cdc_acm/usb_cdc_acm.h @@ -0,0 +1,135 @@ +#pragma once +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + +#include "esphome/core/component.h" +#include "esphome/core/event_pool.h" +#include "esphome/core/lock_free_queue.h" +#include "esphome/components/uart/uart_component.h" + +#include +#include "freertos/ringbuf.h" +#include "tusb_cdc_acm.h" + +namespace esphome::usb_cdc_acm { + +static const uint8_t EVENT_QUEUE_SIZE = 12; +static const uint8_t MAX_USB_CDC_INSTANCES = 2; + +// Callback types for line coding and line state changes +using LineCodingCallback = std::function; +using LineStateCallback = std::function; + +// Event types +enum CDCEventType : uint8_t { + CDC_EVENT_LINE_STATE_CHANGED, + CDC_EVENT_LINE_CODING_CHANGED, +}; + +// Event structure for the queue +struct CDCEvent { + CDCEventType type; + union { + struct { + bool dtr; + bool rts; + } line_state; + struct { + uint32_t bit_rate; + uint8_t stop_bits; + uint8_t parity; + uint8_t data_bits; + } line_coding; + } data; + + // Required by EventPool - called before returning to pool + void release() { + // No dynamic memory to clean up, data is stored inline + } +}; + +// Forward declaration +class USBCDCACMComponent; + +/// Represents a single CDC ACM interface instance +class USBCDCACMInstance : public uart::UARTComponent, public Parented { + public: + void set_interface_number(uint8_t itf) { this->itf_ = static_cast(itf); } + + void setup(); + void loop(); + + // Get the CDC port number for this instance + tinyusb_cdcacm_itf_t get_itf() const { return this->itf_; } + + // Ring buffer accessors for bridge components + RingbufHandle_t get_tx_ringbuf() const { return this->usb_tx_ringbuf_; } + RingbufHandle_t get_rx_ringbuf() const { return this->usb_rx_ringbuf_; } + + // Task handle accessor for notifying TX task + TaskHandle_t get_tx_task_handle() const { return this->usb_tx_task_handle_; } + + // Callback registration for line coding and line state changes + void set_line_coding_callback(LineCodingCallback callback) { this->line_coding_callback_ = std::move(callback); } + void set_line_state_callback(LineStateCallback callback) { this->line_state_callback_ = std::move(callback); } + + // Called from TinyUSB task context (SPSC producer) - queues event for processing in main loop + void queue_line_coding_event(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits); + void queue_line_state_event(bool dtr, bool rts); + + static void usb_tx_task_fn(void *arg); + void usb_tx_task(); + + // UARTComponent interface implementation + void write_array(const uint8_t *data, size_t len) override; + bool peek_byte(uint8_t *data) override; + bool read_array(uint8_t *data, size_t len) override; + int available() override; + void flush() override; + + protected: + void check_logger_conflict() override {} + + // Process queued events and invoke callbacks (called from main loop) + void process_events_(); + + TaskHandle_t usb_tx_task_handle_{nullptr}; + tinyusb_cdcacm_itf_t itf_{TINYUSB_CDC_ACM_0}; + + RingbufHandle_t usb_tx_ringbuf_{nullptr}; + RingbufHandle_t usb_rx_ringbuf_{nullptr}; + + // User-registered callbacks (called from main loop) + LineCodingCallback line_coding_callback_{nullptr}; + LineStateCallback line_state_callback_{nullptr}; + + // Lock-free queue and event pool for cross-task event passing + EventPool event_pool_; + LockFreeQueue event_queue_; + + // RX buffer for peek functionality + uint8_t peek_buffer_{0}; + bool has_peek_{false}; +}; + +/// Main USB CDC ACM component that manages the USB device and all CDC interfaces +class USBCDCACMComponent : public Component { + public: + USBCDCACMComponent(); + + void setup() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::IO; } + + // Interface management + void add_interface(USBCDCACMInstance *interface); + USBCDCACMInstance *get_interface_by_number(uint8_t itf); + + protected: + std::array interfaces_{nullptr, nullptr}; +}; + +extern USBCDCACMComponent *global_usb_cdc_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +} // namespace esphome::usb_cdc_acm +#endif diff --git a/tests/components/usb_cdc_acm/test.esp32-p4-idf.yaml b/tests/components/usb_cdc_acm/test.esp32-p4-idf.yaml new file mode 100644 index 0000000000..4786c96bcc --- /dev/null +++ b/tests/components/usb_cdc_acm/test.esp32-p4-idf.yaml @@ -0,0 +1,5 @@ +<<: !include tinyusb_common.yaml + +usb_cdc_acm: + interfaces: + id: usb_cdc_acm1 diff --git a/tests/components/usb_cdc_acm/test.esp32-s2-idf.yaml b/tests/components/usb_cdc_acm/test.esp32-s2-idf.yaml new file mode 100644 index 0000000000..f159b38ff6 --- /dev/null +++ b/tests/components/usb_cdc_acm/test.esp32-s2-idf.yaml @@ -0,0 +1,5 @@ +<<: !include tinyusb_common.yaml + +usb_cdc_acm: + interfaces: + - id: usb_cdc_acm1 diff --git a/tests/components/usb_cdc_acm/test.esp32-s3-idf.yaml b/tests/components/usb_cdc_acm/test.esp32-s3-idf.yaml new file mode 100644 index 0000000000..6913fe21d5 --- /dev/null +++ b/tests/components/usb_cdc_acm/test.esp32-s3-idf.yaml @@ -0,0 +1,6 @@ +<<: !include tinyusb_common.yaml + +usb_cdc_acm: + interfaces: + - id: usb_cdc_acm1 + - id: usb_cdc_acm2 diff --git a/tests/components/usb_cdc_acm/tinyusb_common.yaml b/tests/components/usb_cdc_acm/tinyusb_common.yaml new file mode 100644 index 0000000000..cb3f48836a --- /dev/null +++ b/tests/components/usb_cdc_acm/tinyusb_common.yaml @@ -0,0 +1,8 @@ +tinyusb: + id: tinyusb_test + usb_lang_id: 0x0123 + usb_manufacturer_str: ESPHomeTestManufacturer + usb_product_id: 0x1234 + usb_product_str: ESPHomeTestProduct + usb_serial_str: ESPHomeTestSerialNumber + usb_vendor_id: 0x2345 From 7e486b1c259b5a1304160ebb716cbdc626fe7b10 Mon Sep 17 00:00:00 2001 From: Johannes Nau Date: Mon, 8 Dec 2025 16:34:26 +0100 Subject: [PATCH 0566/1145] [pca9685] Allow to disable the phase balancer for PCA9685 (#9792) --- esphome/components/pca9685/__init__.py | 17 ++++++++++++++++- esphome/components/pca9685/pca9685_output.cpp | 13 ++++++++++++- esphome/components/pca9685/pca9685_output.h | 7 +++++++ tests/components/pca9685/common.yaml | 1 + 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/esphome/components/pca9685/__init__.py b/esphome/components/pca9685/__init__.py index 50f58cdfb9..56101c2d62 100644 --- a/esphome/components/pca9685/__init__.py +++ b/esphome/components/pca9685/__init__.py @@ -1,7 +1,12 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv -from esphome.const import CONF_EXTERNAL_CLOCK_INPUT, CONF_FREQUENCY, CONF_ID +from esphome.const import ( + CONF_EXTERNAL_CLOCK_INPUT, + CONF_FREQUENCY, + CONF_ID, + CONF_PHASE_BALANCER, +) DEPENDENCIES = ["i2c"] MULTI_CONF = True @@ -9,6 +14,12 @@ MULTI_CONF = True pca9685_ns = cg.esphome_ns.namespace("pca9685") PCA9685Output = pca9685_ns.class_("PCA9685Output", cg.Component, i2c.I2CDevice) +phase_balancer = pca9685_ns.enum("PhaseBalancer", is_class=True) +PHASE_BALANCERS = { + "none": phase_balancer.NONE, + "linear": phase_balancer.LINEAR, +} + def validate_frequency(config): if config[CONF_EXTERNAL_CLOCK_INPUT]: @@ -30,6 +41,9 @@ CONFIG_SCHEMA = cv.All( cv.frequency, cv.Range(min=23.84, max=1525.88) ), cv.Optional(CONF_EXTERNAL_CLOCK_INPUT, default=False): cv.boolean, + cv.Optional(CONF_PHASE_BALANCER, default="linear"): cv.enum( + PHASE_BALANCERS + ), } ) .extend(cv.COMPONENT_SCHEMA) @@ -43,5 +57,6 @@ async def to_code(config): if CONF_FREQUENCY in config: cg.add(var.set_frequency(config[CONF_FREQUENCY])) cg.add(var.set_extclk(config[CONF_EXTERNAL_CLOCK_INPUT])) + cg.add(var.set_phase_balancer(config[CONF_PHASE_BALANCER])) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/pca9685/pca9685_output.cpp b/esphome/components/pca9685/pca9685_output.cpp index 6df708ac84..77e3d5a6c6 100644 --- a/esphome/components/pca9685/pca9685_output.cpp +++ b/esphome/components/pca9685/pca9685_output.cpp @@ -105,7 +105,18 @@ void PCA9685Output::loop() { const uint16_t num_channels = this->max_channel_ - this->min_channel_ + 1; const uint16_t phase_delta_begin = 4096 / num_channels; for (uint8_t channel = this->min_channel_; channel <= this->max_channel_; channel++) { - uint16_t phase_begin = (channel - this->min_channel_) * phase_delta_begin; + uint16_t phase_begin; + switch (this->balancer_) { + case PhaseBalancer::NONE: + phase_begin = 0; + break; + case PhaseBalancer::LINEAR: + phase_begin = (channel - this->min_channel_) * phase_delta_begin; + break; + default: + ESP_LOGE(TAG, "Unknown phase balancer %d", static_cast(this->balancer_)); + return; + } uint16_t phase_end; uint16_t amount = this->pwm_amounts_[channel]; if (amount == 0) { diff --git a/esphome/components/pca9685/pca9685_output.h b/esphome/components/pca9685/pca9685_output.h index 8e547d0032..288c923d4c 100644 --- a/esphome/components/pca9685/pca9685_output.h +++ b/esphome/components/pca9685/pca9685_output.h @@ -7,6 +7,11 @@ namespace esphome { namespace pca9685 { +enum class PhaseBalancer { + NONE = 0x00, + LINEAR = 0x01, +}; + /// Inverts polarity of channel output signal extern const uint8_t PCA9685_MODE_INVERTED; /// Channel update happens upon ACK (post-set) rather than on STOP (endTransmission) @@ -47,6 +52,7 @@ class PCA9685Output : public Component, public i2c::I2CDevice { void loop() override; void set_extclk(bool extclk) { this->extclk_ = extclk; } void set_frequency(float frequency) { this->frequency_ = frequency; } + void set_phase_balancer(PhaseBalancer balancer) { this->balancer_ = balancer; } protected: friend PCA9685Channel; @@ -60,6 +66,7 @@ class PCA9685Output : public Component, public i2c::I2CDevice { float frequency_; uint8_t mode_; bool extclk_ = false; + PhaseBalancer balancer_ = PhaseBalancer::LINEAR; uint8_t min_channel_{0xFF}; uint8_t max_channel_{0x00}; diff --git a/tests/components/pca9685/common.yaml b/tests/components/pca9685/common.yaml index 2e238b481c..9e2de6257a 100644 --- a/tests/components/pca9685/common.yaml +++ b/tests/components/pca9685/common.yaml @@ -2,6 +2,7 @@ pca9685: i2c_id: i2c_bus frequency: 500 address: 0x0 + phase_balancer: linear output: - platform: pca9685 From d635892ecf672c9077aa872f6acf03bf61b9aa26 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Dec 2025 16:36:13 +0100 Subject: [PATCH 0567/1145] [core] Use StringRef for get_comment and get_compilation_time to avoid allocations (#12219) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/mqtt/mqtt_component.cpp | 2 +- esphome/components/sen5x/sen5x.cpp | 2 +- esphome/components/sgp30/sgp30.cpp | 2 +- esphome/components/sgp4x/sgp4x.cpp | 2 +- esphome/components/version/version_text_sensor.cpp | 2 +- esphome/components/web_server/web_server.cpp | 2 +- esphome/components/wifi/wifi_component.cpp | 2 +- esphome/core/application.h | 2 ++ esphome/core/string_ref.h | 11 +++++++++++ 9 files changed, 20 insertions(+), 7 deletions(-) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 1cd818964e..5d2bedae79 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -154,7 +154,7 @@ bool MQTTComponent::send_discovery_() { device_info[MQTT_DEVICE_MANUFACTURER] = model == nullptr ? ESPHOME_PROJECT_NAME : std::string(ESPHOME_PROJECT_NAME, model - ESPHOME_PROJECT_NAME); #else - device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_VERSION " (" + App.get_compilation_time() + ")"; + device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_VERSION " (" + App.get_compilation_time_ref() + ")"; device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD; #if defined(USE_ESP8266) || defined(USE_ESP32) device_info[MQTT_DEVICE_MANUFACTURER] = "Espressif"; diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index 3298a5b8db..ffb9e2bc02 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -157,7 +157,7 @@ void SEN5XComponent::setup() { // Hash with compilation time and serial number // This ensures the baseline storage is cleared after OTA // Serial numbers are unique to each sensor, so mulitple sensors can be used without conflict - uint32_t hash = fnv1_hash(App.get_compilation_time() + std::to_string(combined_serial)); + uint32_t hash = fnv1_hash(App.get_compilation_time_ref() + std::to_string(combined_serial)); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 9e8d6b332c..fa548ce94e 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -75,7 +75,7 @@ void SGP30Component::setup() { // Hash with compilation time and serial number // This ensures the baseline storage is cleared after OTA // Serial numbers are unique to each sensor, so mulitple sensors can be used without conflict - uint32_t hash = fnv1_hash(App.get_compilation_time() + std::to_string(this->serial_number_)); + uint32_t hash = fnv1_hash(App.get_compilation_time_ref() + std::to_string(this->serial_number_)); this->pref_ = global_preferences->make_preference(hash, true); if (this->store_baseline_ && this->pref_.load(&this->baselines_storage_)) { diff --git a/esphome/components/sgp4x/sgp4x.cpp b/esphome/components/sgp4x/sgp4x.cpp index 99d88006f7..a0c957d608 100644 --- a/esphome/components/sgp4x/sgp4x.cpp +++ b/esphome/components/sgp4x/sgp4x.cpp @@ -59,7 +59,7 @@ void SGP4xComponent::setup() { // Hash with compilation time and serial number // This ensures the baseline storage is cleared after OTA // Serial numbers are unique to each sensor, so mulitple sensors can be used without conflict - uint32_t hash = fnv1_hash(App.get_compilation_time() + std::to_string(this->serial_number_)); + uint32_t hash = fnv1_hash(App.get_compilation_time_ref() + std::to_string(this->serial_number_)); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { diff --git a/esphome/components/version/version_text_sensor.cpp b/esphome/components/version/version_text_sensor.cpp index 65dbfd27cf..78d0fb501b 100644 --- a/esphome/components/version/version_text_sensor.cpp +++ b/esphome/components/version/version_text_sensor.cpp @@ -13,7 +13,7 @@ void VersionTextSensor::setup() { if (this->hide_timestamp_) { this->publish_state(ESPHOME_VERSION); } else { - this->publish_state(str_sprintf(ESPHOME_VERSION " %s", App.get_compilation_time().c_str())); + this->publish_state(str_sprintf(ESPHOME_VERSION " %s", App.get_compilation_time_ref().c_str())); } } float VersionTextSensor::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index ca3aa21a95..0c22c2f08d 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -287,7 +287,7 @@ std::string WebServer::get_config_json() { JsonObject root = builder.root(); root[ESPHOME_F("title")] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name(); - root[ESPHOME_F("comment")] = App.get_comment(); + root[ESPHOME_F("comment")] = App.get_comment_ref(); #if defined(USE_WEBSERVER_OTA_DISABLED) || !defined(USE_WEBSERVER_OTA) root[ESPHOME_F("ota")] = false; // Note: USE_WEBSERVER_OTA_DISABLED only affects web_server, not captive_portal #else diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index ff33a81fcf..d46916bfd9 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -360,7 +360,7 @@ void WiFiComponent::start() { get_mac_address_pretty_into_buffer(mac_s)); this->last_connected_ = millis(); - uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time()) : 88491487UL; + uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time_ref().c_str()) : 88491487UL; this->pref_ = global_preferences->make_preference(hash, true); #ifdef USE_WIFI_FAST_CONNECT diff --git a/esphome/core/application.h b/esphome/core/application.h index 14e800342e..8e2035b7c5 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -256,6 +256,8 @@ class Application { /// Get the comment of this Application set by pre_setup(). std::string get_comment() const { return this->comment_; } + /// Get the comment as StringRef (avoids allocation) + StringRef get_comment_ref() const { return StringRef(this->comment_); } bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; } diff --git a/esphome/core/string_ref.h b/esphome/core/string_ref.h index efaa17181d..505fdd906a 100644 --- a/esphome/core/string_ref.h +++ b/esphome/core/string_ref.h @@ -128,6 +128,17 @@ inline std::string operator+(const StringRef &lhs, const char *rhs) { return str; } +inline std::string operator+(const StringRef &lhs, const std::string &rhs) { + auto str = lhs.str(); + str.append(rhs); + return str; +} + +inline std::string operator+(const std::string &lhs, const StringRef &rhs) { + std::string str(lhs); + str.append(rhs.c_str(), rhs.size()); + return str; +} #ifdef USE_JSON // NOLINTNEXTLINE(readability-identifier-naming) inline void convertToJson(const StringRef &src, JsonVariant dst) { dst.set(src.c_str()); } From 801d1135ab750ae6b88425786da598170d845d10 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Dec 2025 16:37:51 +0100 Subject: [PATCH 0568/1145] [select] Add zero-copy support for API select commands (#12329) --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_pb2.cpp | 7 +++++-- esphome/components/api/api_pb2.h | 5 +++-- esphome/components/api/api_pb2_dump.cpp | 4 +++- esphome/components/select/select.cpp | 6 ++---- esphome/components/select/select.h | 5 +++-- esphome/components/select/select_call.cpp | 10 +++------- esphome/components/select/select_call.h | 10 ++++++---- 9 files changed, 27 insertions(+), 24 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 3fc2e1fed8..2534ad0b1f 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1195,7 +1195,7 @@ message SelectCommandRequest { option (base_class) = "CommandProtoMessage"; fixed32 key = 1; - string state = 2; + string state = 2 [(pointer_to_buffer) = true]; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index f0428546de..18d80c46df 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -902,7 +902,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection * } void APIConnection::select_command(const SelectCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(select::Select, select, select) - call.set_option(msg.state); + call.set_option(reinterpret_cast(msg.state), msg.state_len); call.perform(); } #endif diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index a3da6591f4..128f82fe7f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1569,9 +1569,12 @@ bool SelectCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: - this->state = value.as_string(); + case 2: { + // Use raw data directly to avoid allocation + this->state = value.data(); + this->state_len = value.size(); break; + } default: return false; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 7e41cd8a22..49f1ea3c52 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1604,11 +1604,12 @@ class SelectStateResponse final : public StateResponseProtoMessage { class SelectCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 54; - static constexpr uint8_t ESTIMATED_SIZE = 18; + static constexpr uint8_t ESTIMATED_SIZE = 28; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "select_command_request"; } #endif - std::string state{}; + const uint8_t *state{nullptr}; + uint16_t state_len{0}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 59fc1367fe..ca69d1ff00 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1453,7 +1453,9 @@ void SelectStateResponse::dump_to(std::string &out) const { void SelectCommandRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "SelectCommandRequest"); dump_field(out, "key", this->key); - dump_field(out, "state", this->state); + out.append(" state: "); + out.append(format_hex_pretty(this->state, this->state_len)); + out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 3ec413f167..4fc4d79b08 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -56,12 +56,10 @@ size_t Select::size() const { return options.size(); } -optional Select::index_of(const std::string &option) const { return this->index_of(option.c_str()); } - -optional Select::index_of(const char *option) const { +optional Select::index_of(const char *option, size_t len) const { const auto &options = traits.get_options(); for (size_t i = 0; i < options.size(); i++) { - if (strcmp(options[i], option) == 0) { + if (strncmp(options[i], option, len) == 0 && options[i][len] == '\0') { return i; } } diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index c4d7412d50..63707f6bd6 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -62,8 +62,9 @@ class Select : public EntityBase { size_t size() const; /// Find the (optional) index offset of the provided option value. - optional index_of(const std::string &option) const; - optional index_of(const char *option) const; + optional index_of(const char *option, size_t len) const; + optional index_of(const std::string &option) const { return this->index_of(option.data(), option.size()); } + optional index_of(const char *option) const { return this->index_of(option, strlen(option)); } /// Return the (optional) index offset of the currently active option. optional active_index() const; diff --git a/esphome/components/select/select_call.cpp b/esphome/components/select/select_call.cpp index aecfed0d64..2ff99c961d 100644 --- a/esphome/components/select/select_call.cpp +++ b/esphome/components/select/select_call.cpp @@ -6,9 +6,7 @@ namespace esphome::select { static const char *const TAG = "select"; -SelectCall &SelectCall::set_option(const std::string &option) { return this->with_option(option); } - -SelectCall &SelectCall::set_option(const char *option) { return this->with_option(option); } +SelectCall &SelectCall::set_option(const char *option, size_t len) { return this->with_option(option, len); } SelectCall &SelectCall::set_index(size_t index) { return this->with_index(index); } @@ -32,12 +30,10 @@ SelectCall &SelectCall::with_cycle(bool cycle) { return *this; } -SelectCall &SelectCall::with_option(const std::string &option) { return this->with_option(option.c_str()); } - -SelectCall &SelectCall::with_option(const char *option) { +SelectCall &SelectCall::with_option(const char *option, size_t len) { this->operation_ = SELECT_OP_SET; // Find the option index - this validates the option exists - this->index_ = this->parent_->index_of(option); + this->index_ = this->parent_->index_of(option, len); return *this; } diff --git a/esphome/components/select/select_call.h b/esphome/components/select/select_call.h index b31d890ef6..c9abbc69a0 100644 --- a/esphome/components/select/select_call.h +++ b/esphome/components/select/select_call.h @@ -20,8 +20,9 @@ class SelectCall { explicit SelectCall(Select *parent) : parent_(parent) {} void perform(); - SelectCall &set_option(const std::string &option); - SelectCall &set_option(const char *option); + SelectCall &set_option(const char *option, size_t len); + SelectCall &set_option(const std::string &option) { return this->set_option(option.data(), option.size()); } + SelectCall &set_option(const char *option) { return this->set_option(option, strlen(option)); } SelectCall &set_index(size_t index); SelectCall &select_next(bool cycle); @@ -31,8 +32,9 @@ class SelectCall { SelectCall &with_operation(SelectOperation operation); SelectCall &with_cycle(bool cycle); - SelectCall &with_option(const std::string &option); - SelectCall &with_option(const char *option); + SelectCall &with_option(const char *option, size_t len); + SelectCall &with_option(const std::string &option) { return this->with_option(option.data(), option.size()); } + SelectCall &with_option(const char *option) { return this->with_option(option, strlen(option)); } SelectCall &with_index(size_t index); protected: From 9f60aed9b0b7a921c6f637142c8b36c5146b36ea Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Mon, 8 Dec 2025 17:18:44 +0100 Subject: [PATCH 0569/1145] [micronova] Make stove switch entity independent (#12355) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/micronova/micronova.h | 20 ------------ .../components/micronova/switch/__init__.py | 17 +++++----- .../micronova/switch/micronova_switch.cpp | 31 +++++++++++++++---- .../micronova/switch/micronova_switch.h | 17 +++++----- .../text_sensor/micronova_text_sensor.cpp | 9 ------ 5 files changed, 44 insertions(+), 50 deletions(-) diff --git a/esphome/components/micronova/micronova.h b/esphome/components/micronova/micronova.h index a2eee81be8..1b2c06f07f 100644 --- a/esphome/components/micronova/micronova.h +++ b/esphome/components/micronova/micronova.h @@ -75,17 +75,6 @@ class MicroNovaListener : public MicroNovaBaseListener, public PollingComponent bool needs_update_ = false; }; -class MicroNovaSwitchListener : public MicroNovaBaseListener { - public: - MicroNovaSwitchListener(MicroNova *m) : MicroNovaBaseListener(m) {} - virtual void set_stove_state(bool v) = 0; - virtual bool get_stove_state() = 0; - - protected: - uint8_t memory_data_on_ = 0; - uint8_t memory_data_off_ = 0; -}; - class MicroNovaButtonListener : public MicroNovaBaseListener { public: MicroNovaButtonListener(MicroNova *m) : MicroNovaBaseListener(m) {} @@ -112,15 +101,7 @@ class MicroNova : public Component, public uart::UARTDevice { void set_enable_rx_pin(GPIOPin *enable_rx_pin) { this->enable_rx_pin_ = enable_rx_pin; } - void set_current_stove_state(uint8_t s) { this->current_stove_state_ = s; } - uint8_t get_current_stove_state() { return this->current_stove_state_; } - - void set_stove(MicroNovaSwitchListener *s) { this->stove_switch_ = s; } - MicroNovaSwitchListener *get_stove_switch() { return this->stove_switch_; } - protected: - uint8_t current_stove_state_ = 0; - GPIOPin *enable_rx_pin_{nullptr}; struct MicroNovaSerialTransmission { @@ -135,7 +116,6 @@ class MicroNova : public Component, public uart::UARTDevice { MicroNovaSerialTransmission current_transmission_; std::vector micronova_listeners_{}; - MicroNovaSwitchListener *stove_switch_{nullptr}; }; } // namespace esphome::micronova diff --git a/esphome/components/micronova/switch/__init__.py b/esphome/components/micronova/switch/__init__.py index c6897d8e5c..62a8a0f008 100644 --- a/esphome/components/micronova/switch/__init__.py +++ b/esphome/components/micronova/switch/__init__.py @@ -4,20 +4,22 @@ import esphome.config_validation as cv from esphome.const import ICON_POWER from .. import ( - CONF_MEMORY_ADDRESS, - CONF_MEMORY_LOCATION, CONF_MICRONOVA_ID, MICRONOVA_ADDRESS_SCHEMA, MicroNova, MicroNovaFunctions, + MicroNovaListener, micronova_ns, + to_code_micronova_listener, ) CONF_STOVE = "stove" CONF_MEMORY_DATA_ON = "memory_data_on" CONF_MEMORY_DATA_OFF = "memory_data_off" -MicroNovaSwitch = micronova_ns.class_("MicroNovaSwitch", switch.Switch, cg.Component) +MicroNovaSwitch = micronova_ns.class_( + "MicroNovaSwitch", switch.Switch, MicroNovaListener +) CONFIG_SCHEMA = cv.Schema( { @@ -30,7 +32,7 @@ CONFIG_SCHEMA = cv.Schema( MICRONOVA_ADDRESS_SCHEMA( default_memory_location=0x00, default_memory_address=0x21, - is_polling_component=False, + is_polling_component=True, ) ) .extend( @@ -48,9 +50,8 @@ async def to_code(config): if stove_config := config.get(CONF_STOVE): sw = await switch.new_switch(stove_config, mv) - cg.add(mv.set_stove(sw)) - cg.add(sw.set_memory_location(stove_config[CONF_MEMORY_LOCATION])) - cg.add(sw.set_memory_address(stove_config[CONF_MEMORY_ADDRESS])) + await to_code_micronova_listener( + mv, sw, stove_config, MicroNovaFunctions.STOVE_FUNCTION_SWITCH + ) cg.add(sw.set_memory_data_on(stove_config[CONF_MEMORY_DATA_ON])) cg.add(sw.set_memory_data_off(stove_config[CONF_MEMORY_DATA_OFF])) - cg.add(sw.set_function(MicroNovaFunctions.STOVE_FUNCTION_SWITCH)) diff --git a/esphome/components/micronova/switch/micronova_switch.cpp b/esphome/components/micronova/switch/micronova_switch.cpp index 3777b6029d..76ef04da8a 100644 --- a/esphome/components/micronova/switch/micronova_switch.cpp +++ b/esphome/components/micronova/switch/micronova_switch.cpp @@ -4,27 +4,46 @@ namespace esphome::micronova { void MicroNovaSwitch::write_state(bool state) { switch (this->get_function()) { - case MicroNovaFunctions::STOVE_FUNCTION_SWITCH: + case MicroNovaFunctions::STOVE_FUNCTION_SWITCH: { if (state) { // Only send power-on when current state is Off - if (this->micronova_->get_current_stove_state() == 0) { + if (this->raw_state_ == 0) { this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_on_); this->publish_state(true); } else { - ESP_LOGW(TAG, "Unable to turn stove on, invalid state: %d", micronova_->get_current_stove_state()); + ESP_LOGW(TAG, "Unable to turn stove on, invalid state: %d", this->raw_state_); } } else { // don't send power-off when status is Off or Final cleaning - if (this->micronova_->get_current_stove_state() != 0 && micronova_->get_current_stove_state() != 6) { + if (this->raw_state_ != 0 && this->raw_state_ != 6) { this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_off_); this->publish_state(false); } else { - ESP_LOGW(TAG, "Unable to turn stove off, invalid state: %d", micronova_->get_current_stove_state()); + ESP_LOGW(TAG, "Unable to turn stove off, invalid state: %d", this->raw_state_); } } - this->micronova_->request_update_listeners(); + this->set_needs_update(true); break; + } + default: + break; + } +} +void MicroNovaSwitch::process_value_from_stove(int value_from_stove) { + this->raw_state_ = value_from_stove; + if (value_from_stove == -1) { + ESP_LOGE(TAG, "Error reading stove state"); + return; + } + + switch (this->get_function()) { + case MicroNovaFunctions::STOVE_FUNCTION_SWITCH: { + // set the stove switch to on for any value but 0 + bool state = value_from_stove != 0; + this->publish_state(state); + break; + } default: break; } diff --git a/esphome/components/micronova/switch/micronova_switch.h b/esphome/components/micronova/switch/micronova_switch.h index ab83973ef7..96c2c14e9e 100644 --- a/esphome/components/micronova/switch/micronova_switch.h +++ b/esphome/components/micronova/switch/micronova_switch.h @@ -6,25 +6,28 @@ namespace esphome::micronova { -class MicroNovaSwitch : public Component, public switch_::Switch, public MicroNovaSwitchListener { +class MicroNovaSwitch : public switch_::Switch, public MicroNovaListener { public: - MicroNovaSwitch(MicroNova *m) : MicroNovaSwitchListener(m) {} + MicroNovaSwitch(MicroNova *m) : MicroNovaListener(m) {} void dump_config() override { LOG_SWITCH("", "Micronova switch", this); this->dump_base_config(); } - - void set_stove_state(bool v) override { this->publish_state(v); } - bool get_stove_state() override { return this->state; } + void request_value_from_stove() override { + this->micronova_->request_address(this->memory_location_, this->memory_address_, this); + } + void process_value_from_stove(int value_from_stove) override; void set_memory_data_on(uint8_t f) { this->memory_data_on_ = f; } - uint8_t get_memory_data_on() { return this->memory_data_on_; } void set_memory_data_off(uint8_t f) { this->memory_data_off_ = f; } - uint8_t get_memory_data_off() { return this->memory_data_off_; } protected: void write_state(bool state) override; + + uint8_t memory_data_on_ = 0; + uint8_t memory_data_off_ = 0; + uint8_t raw_state_ = 0; }; } // namespace esphome::micronova diff --git a/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp b/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp index b62fb1afce..d1c03f66c3 100644 --- a/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp +++ b/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp @@ -10,16 +10,7 @@ void MicroNovaTextSensor::process_value_from_stove(int value_from_stove) { switch (this->get_function()) { case MicroNovaFunctions::STOVE_FUNCTION_STOVE_STATE: - this->micronova_->set_current_stove_state(value_from_stove); this->publish_state(STOVE_STATES[value_from_stove]); - // set the stove switch to on for any value but 0 - if (value_from_stove != 0 && this->micronova_->get_stove_switch() != nullptr && - !this->micronova_->get_stove_switch()->get_stove_state()) { - this->micronova_->get_stove_switch()->set_stove_state(true); - } else if (value_from_stove == 0 && this->micronova_->get_stove_switch() != nullptr && - this->micronova_->get_stove_switch()->get_stove_state()) { - this->micronova_->get_stove_switch()->set_stove_state(false); - } break; default: break; From 7a20c85eec2a25a40e4e146e320234cc6510592f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Blanchet?= <120399978+arno1801@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:12:15 -0500 Subject: [PATCH 0570/1145] [i2c] Fix port logic with ESP-IDF (#12063) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- esphome/components/esp32/gpio_esp32_c5.py | 15 ++++- esphome/components/esp32/gpio_esp32_c6.py | 15 ++++- esphome/components/esp32/gpio_esp32_p4.py | 16 ++++- esphome/components/i2c/__init__.py | 71 ++++++++++++++++++++++ esphome/components/i2c/i2c_bus_esp_idf.cpp | 41 ++++++++----- esphome/components/i2c/i2c_bus_esp_idf.h | 6 ++ esphome/const.py | 1 + 7 files changed, 146 insertions(+), 19 deletions(-) diff --git a/esphome/components/esp32/gpio_esp32_c5.py b/esphome/components/esp32/gpio_esp32_c5.py index ada426771c..fa2ce1a689 100644 --- a/esphome/components/esp32/gpio_esp32_c5.py +++ b/esphome/components/esp32/gpio_esp32_c5.py @@ -1,9 +1,12 @@ import logging import esphome.config_validation as cv -from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER +from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA from esphome.pins import check_strapping_pin +# https://github.com/espressif/esp-idf/blob/master/components/esp_hal_i2c/esp32c5/include/hal/i2c_ll.h +_ESP32C5_I2C_LP_PINS = {"SDA": 2, "SCL": 3} + _ESP32C5_SPI_PSRAM_PINS = { 16: "SPICS0", 17: "SPIQ", @@ -43,3 +46,13 @@ def esp32_c5_validate_supports(value): check_strapping_pin(value, _ESP32C5_STRAPPING_PINS, _LOGGER) return value + + +def esp32_c5_validate_lp_i2c(value): + lp_sda_pin = _ESP32C5_I2C_LP_PINS["SDA"] + lp_scl_pin = _ESP32C5_I2C_LP_PINS["SCL"] + if int(value[CONF_SDA]) != lp_sda_pin or int(value[CONF_SCL]) != lp_scl_pin: + raise cv.Invalid( + f"Low power i2c interface is only supported on GPIO{lp_sda_pin} SDA and GPIO{lp_scl_pin} SCL for ESP32-C5" + ) + return value diff --git a/esphome/components/esp32/gpio_esp32_c6.py b/esphome/components/esp32/gpio_esp32_c6.py index d466adb994..5d679dede2 100644 --- a/esphome/components/esp32/gpio_esp32_c6.py +++ b/esphome/components/esp32/gpio_esp32_c6.py @@ -1,9 +1,12 @@ import logging import esphome.config_validation as cv -from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER +from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA from esphome.pins import check_strapping_pin +# https://github.com/espressif/esp-idf/blob/master/components/esp_hal_i2c/esp32c6/include/hal/i2c_ll.h +_ESP32C6_I2C_LP_PINS = {"SDA": 6, "SCL": 7} + _ESP32C6_SPI_PSRAM_PINS = { 24: "SPICS0", 25: "SPIQ", @@ -43,3 +46,13 @@ def esp32_c6_validate_supports(value): check_strapping_pin(value, _ESP32C6_STRAPPING_PINS, _LOGGER) return value + + +def esp32_c6_validate_lp_i2c(value): + lp_sda_pin = _ESP32C6_I2C_LP_PINS["SDA"] + lp_scl_pin = _ESP32C6_I2C_LP_PINS["SCL"] + if int(value[CONF_SDA]) != lp_sda_pin or int(value[CONF_SCL]) != lp_scl_pin: + raise cv.Invalid( + f"Low power i2c interface is only supported on GPIO{lp_sda_pin} SDA and GPIO{lp_scl_pin} SCL for ESP32-C6" + ) + return value diff --git a/esphome/components/esp32/gpio_esp32_p4.py b/esphome/components/esp32/gpio_esp32_p4.py index 34d1b3139d..b98b567da2 100644 --- a/esphome/components/esp32/gpio_esp32_p4.py +++ b/esphome/components/esp32/gpio_esp32_p4.py @@ -1,9 +1,12 @@ import logging import esphome.config_validation as cv -from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER +from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA from esphome.pins import check_strapping_pin +# https://documentation.espressif.com/esp32-p4-chip-revision-v1.3_datasheet_en.pdf +_ESP32P4_LP_PINS = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + _ESP32P4_USB_JTAG_PINS = {24, 25} _ESP32P4_STRAPPING_PINS = {34, 35, 36, 37, 38} @@ -36,3 +39,14 @@ def esp32_p4_validate_supports(value): pass check_strapping_pin(value, _ESP32P4_STRAPPING_PINS, _LOGGER) return value + + +def esp32_p4_validate_lp_i2c(value): + if ( + int(value[CONF_SDA]) not in _ESP32P4_LP_PINS + or int(value[CONF_SCL]) not in _ESP32P4_LP_PINS + ): + raise cv.Invalid( + f"Low power i2c interface for ESP32-P4 is only supported on low power interface GPIO{min(_ESP32P4_LP_PINS)} - GPIO{max(_ESP32P4_LP_PINS)}" + ) + return value diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 738568cd3c..9e7c9d702c 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -2,6 +2,23 @@ import logging from esphome import pins import esphome.codegen as cg +from esphome.components import esp32 +from esphome.components.esp32 import ( + VARIANT_ESP32, + VARIANT_ESP32C2, + VARIANT_ESP32C3, + VARIANT_ESP32C5, + VARIANT_ESP32C6, + VARIANT_ESP32C61, + VARIANT_ESP32H2, + VARIANT_ESP32P4, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + get_esp32_variant, +) +from esphome.components.esp32.gpio_esp32_c5 import esp32_c5_validate_lp_i2c +from esphome.components.esp32.gpio_esp32_c6 import esp32_c6_validate_lp_i2c +from esphome.components.esp32.gpio_esp32_p4 import esp32_p4_validate_lp_i2c from esphome.components.zephyr import ( zephyr_add_overlay, zephyr_add_prj_conf, @@ -16,6 +33,7 @@ from esphome.const import ( CONF_I2C, CONF_I2C_ID, CONF_ID, + CONF_LOW_POWER_MODE, CONF_SCAN, CONF_SCL, CONF_SDA, @@ -40,6 +58,25 @@ IDFI2CBus = i2c_ns.class_("IDFI2CBus", InternalI2CBus, cg.Component) ZephyrI2CBus = i2c_ns.class_("ZephyrI2CBus", I2CBus, cg.Component) I2CDevice = i2c_ns.class_("I2CDevice") +ESP32_I2C_CAPABILITIES = { + # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/soc_caps.h + VARIANT_ESP32: {"NUM": 2, "HP": 2}, + VARIANT_ESP32C2: {"NUM": 1, "HP": 1}, + VARIANT_ESP32C3: {"NUM": 1, "HP": 1}, + VARIANT_ESP32C5: {"NUM": 2, "HP": 1, "LP": 1}, + VARIANT_ESP32C6: {"NUM": 2, "HP": 1, "LP": 1}, + VARIANT_ESP32C61: {"NUM": 1, "HP": 1}, + VARIANT_ESP32H2: {"NUM": 2, "HP": 2}, + VARIANT_ESP32P4: {"NUM": 3, "HP": 2, "LP": 1}, + VARIANT_ESP32S2: {"NUM": 2, "HP": 2}, + VARIANT_ESP32S3: {"NUM": 2, "HP": 2}, +} +VALIDATE_LP_I2C = { + VARIANT_ESP32C5: esp32_c5_validate_lp_i2c, + VARIANT_ESP32C6: esp32_c6_validate_lp_i2c, + VARIANT_ESP32P4: esp32_p4_validate_lp_i2c, +} +LP_I2C_VARIANT = list(VALIDATE_LP_I2C.keys()) CONF_SDA_PULLUP_ENABLED = "sda_pullup_enabled" CONF_SCL_PULLUP_ENABLED = "scl_pullup_enabled" @@ -91,6 +128,13 @@ CONFIG_SCHEMA = cv.All( cv.positive_time_period, ), cv.Optional(CONF_SCAN, default=True): cv.boolean, + cv.Optional(CONF_LOW_POWER_MODE): cv.All( + cv.only_on_esp32, + esp32.only_on_variant( + supported=LP_I2C_VARIANT, msg_prefix="Low power i2c" + ), + cv.boolean, + ), } ).extend(cv.COMPONENT_SCHEMA), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_NRF52]), @@ -102,6 +146,31 @@ def _final_validate(config): full_config = fv.full_config.get()[CONF_I2C] if CORE.using_zephyr and len(full_config) > 1: raise cv.Invalid("Second i2c is not implemented on Zephyr yet") + if CORE.using_esp_idf and get_esp32_variant() in ESP32_I2C_CAPABILITIES: + variant = get_esp32_variant() + max_num = ESP32_I2C_CAPABILITIES[variant]["NUM"] + if len(full_config) > max_num: + raise cv.Invalid( + f"The maximum number of i2c interfaces for {variant} is {max_num}" + ) + if variant in LP_I2C_VARIANT: + max_lp_num = ESP32_I2C_CAPABILITIES[variant]["LP"] + max_hp_num = ESP32_I2C_CAPABILITIES[variant]["HP"] + lp_num = sum( + CONF_LOW_POWER_MODE in conf and conf[CONF_LOW_POWER_MODE] + for conf in full_config + ) + hp_num = len(full_config) - lp_num + if CONF_LOW_POWER_MODE in config and config[CONF_LOW_POWER_MODE]: + VALIDATE_LP_I2C[variant](config) + if lp_num > max_lp_num: + raise cv.Invalid( + f"The maximum number of low power i2c interfaces for {variant} is {max_lp_num}" + ) + if hp_num > max_hp_num: + raise cv.Invalid( + f"The maximum number of high power i2c interfaces for {variant} is {max_hp_num}" + ) FINAL_VALIDATE_SCHEMA = _final_validate @@ -155,6 +224,8 @@ async def to_code(config): cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds))) if CORE.using_arduino and not CORE.is_esp32: cg.add_library("Wire", None) + if CONF_LOW_POWER_MODE in config: + cg.add(var.set_lp_mode(bool(config[CONF_LOW_POWER_MODE]))) def i2c_device_schema(default_address): diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index c22db51c68..486dc0b7d8 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -16,13 +16,10 @@ namespace i2c { static const char *const TAG = "i2c.idf"; void IDFI2CBus::setup() { - static i2c_port_t next_port = I2C_NUM_0; - this->port_ = next_port; - if (this->port_ == I2C_NUM_MAX) { - ESP_LOGE(TAG, "No more than %u buses supported", I2C_NUM_MAX); - this->mark_failed(); - return; - } + static i2c_port_t next_hp_port = I2C_NUM_0; +#if SOC_LP_I2C_SUPPORTED + static i2c_port_t next_lp_port = LP_I2C_NUM_0; +#endif if (this->timeout_ > 13000) { ESP_LOGW(TAG, "Using max allowed timeout: 13 ms"); @@ -31,23 +28,35 @@ void IDFI2CBus::setup() { this->recover_(); - next_port = (i2c_port_t) (next_port + 1); - i2c_master_bus_config_t bus_conf{}; memset(&bus_conf, 0, sizeof(bus_conf)); bus_conf.sda_io_num = gpio_num_t(sda_pin_); bus_conf.scl_io_num = gpio_num_t(scl_pin_); - bus_conf.i2c_port = this->port_; bus_conf.glitch_ignore_cnt = 7; #if SOC_LP_I2C_SUPPORTED - if (this->port_ < SOC_HP_I2C_NUM) { - bus_conf.clk_source = I2C_CLK_SRC_DEFAULT; - } else { + if (this->lp_mode_) { + if ((next_lp_port - LP_I2C_NUM_0) == SOC_LP_I2C_NUM) { + ESP_LOGE(TAG, "No more than %u LP buses supported", SOC_LP_I2C_NUM); + this->mark_failed(); + return; + } + this->port_ = next_lp_port; + next_lp_port = (i2c_port_t) (next_lp_port + 1); bus_conf.lp_source_clk = LP_I2C_SCLK_DEFAULT; - } -#else - bus_conf.clk_source = I2C_CLK_SRC_DEFAULT; + } else { #endif + if (next_hp_port == SOC_HP_I2C_NUM) { + ESP_LOGE(TAG, "No more than %u HP buses supported", SOC_HP_I2C_NUM); + this->mark_failed(); + return; + } + this->port_ = next_hp_port; + next_hp_port = (i2c_port_t) (next_hp_port + 1); + bus_conf.clk_source = I2C_CLK_SRC_DEFAULT; +#if SOC_LP_I2C_SUPPORTED + } +#endif + bus_conf.i2c_port = this->port_; bus_conf.flags.enable_internal_pullup = sda_pullup_enabled_ || scl_pullup_enabled_; esp_err_t err = i2c_new_master_bus(&bus_conf, &this->bus_); if (err != ESP_OK) { diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index 63fe8b701c..84f4616967 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -30,6 +30,9 @@ class IDFI2CBus : public InternalI2CBus, public Component { void set_scl_pullup_enabled(bool scl_pullup_enabled) { this->scl_pullup_enabled_ = scl_pullup_enabled; } void set_frequency(uint32_t frequency) { this->frequency_ = frequency; } void set_timeout(uint32_t timeout) { this->timeout_ = timeout; } +#if SOC_LP_I2C_SUPPORTED + void set_lp_mode(bool lp_mode) { this->lp_mode_ = lp_mode; } +#endif int get_port() const override { return this->port_; } @@ -48,6 +51,9 @@ class IDFI2CBus : public InternalI2CBus, public Component { uint32_t frequency_{}; uint32_t timeout_ = 0; bool initialized_ = false; +#if SOC_LP_I2C_SUPPORTED + bool lp_mode_ = false; +#endif }; } // namespace i2c diff --git a/esphome/const.py b/esphome/const.py index 59bf0e8b8a..8fa2d8da16 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -559,6 +559,7 @@ CONF_LOGS = "logs" CONF_LONGITUDE = "longitude" CONF_LOOP_TIME = "loop_time" CONF_LOW = "low" +CONF_LOW_POWER_MODE = "low_power_mode" CONF_LOW_VOLTAGE_REFERENCE = "low_voltage_reference" CONF_MAC_ADDRESS = "mac_address" CONF_MAGNITUDE = "magnitude" From 4c31961ae9c9a1654255db226c5e330f93bac378 Mon Sep 17 00:00:00 2001 From: smarthome-10 Date: Mon, 8 Dec 2025 20:37:45 +0100 Subject: [PATCH 0571/1145] Update URLs (#12369) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- CONTRIBUTING.md | 2 +- esphome/components/absolute_humidity/absolute_humidity.cpp | 2 +- esphome/components/api/__init__.py | 2 +- esphome/components/bedjet/climate/__init__.py | 2 +- esphome/components/esp32/__init__.py | 2 +- esphome/components/kalman_combinator/sensor.py | 2 +- esphome/components/pn532/__init__.py | 2 +- esphome/components/sgp40/sensor.py | 2 +- esphome/components/web_server/web_server.h | 2 +- esphome/components/web_server/web_server_v1.cpp | 5 ++--- esphome/core/config.py | 2 +- esphome/espota2.py | 2 +- esphome/pins.py | 2 +- esphome/util.py | 2 +- esphome/wizard.py | 6 ++---- tests/components/qr_code/common.yaml | 2 +- 16 files changed, 18 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 303b548310..66ad3ed599 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ We welcome contributions to the ESPHome suite of code and documentation! -Please read our [contributing guide](https://esphome.io/guides/contributing.html) if you wish to contribute to the +Please read our [contributing guide](https://developers.esphome.io/contributing/code/) if you wish to contribute to the project and be sure to join us on [Discord](https://discord.gg/KhAMKrd). **See also:** diff --git a/esphome/components/absolute_humidity/absolute_humidity.cpp b/esphome/components/absolute_humidity/absolute_humidity.cpp index d16a024d86..74d675b80b 100644 --- a/esphome/components/absolute_humidity/absolute_humidity.cpp +++ b/esphome/components/absolute_humidity/absolute_humidity.cpp @@ -163,7 +163,7 @@ float AbsoluteHumidityComponent::es_wobus(float t) { } // From https://www.environmentalbiophysics.org/chalk-talk-how-to-calculate-absolute-humidity/ -// H/T to https://esphome.io/cookbook/bme280_environment.html +// H/T to https://esphome.io/cookbook/bme280_environment/ // H/T to https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/ float AbsoluteHumidityComponent::vapor_density(float es, float hr, float ta) { // es = saturated vapor pressure (kPa) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index d349cf3867..88618acef4 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -246,7 +246,7 @@ def _validate_api_config(config: ConfigType) -> ConfigType: _LOGGER.warning( "API 'password' authentication has been deprecated since May 2022 and will be removed in version 2026.1.0. " "Please migrate to the 'encryption' configuration. " - "See https://esphome.io/components/api.html#configuration-variables" + "See https://esphome.io/components/api/#configuration-variables" ) return config diff --git a/esphome/components/bedjet/climate/__init__.py b/esphome/components/bedjet/climate/__init__.py index e9c5510256..0da2107d43 100644 --- a/esphome/components/bedjet/climate/__init__.py +++ b/esphome/components/bedjet/climate/__init__.py @@ -44,7 +44,7 @@ CONFIG_SCHEMA = ( cv.Optional(ble_client.CONF_BLE_CLIENT_ID): cv.invalid( "The 'ble_client_id' option has been removed. Please migrate " "to the new `bedjet_id` option in the `bedjet` component.\n" - "See https://esphome.io/components/climate/bedjet.html" + "See https://esphome.io/components/climate/bedjet/" ), cv.Optional(CONF_TIME_ID): cv.invalid( "The 'time_id' option has been moved to the `bedjet` component." diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 94280308bd..3dc5e4bbaa 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -764,7 +764,7 @@ def _show_framework_migration_message(name: str, variant: str) -> None: + "Need help? Check out the migration guide:\n" + color( AnsiFore.BLUE, - "https://esphome.io/guides/esp32_arduino_to_idf.html", + "https://esphome.io/guides/esp32_arduino_to_idf/", ) ) _LOGGER.warning(message) diff --git a/esphome/components/kalman_combinator/sensor.py b/esphome/components/kalman_combinator/sensor.py index c19a17462d..d30a41d6bf 100644 --- a/esphome/components/kalman_combinator/sensor.py +++ b/esphome/components/kalman_combinator/sensor.py @@ -2,5 +2,5 @@ import esphome.config_validation as cv CONFIG_SCHEMA = cv.invalid( "The kalman_combinator sensor has moved.\nPlease use the combination platform instead with type: kalman.\n" - "See https://esphome.io/components/sensor/combination.html" + "See https://esphome.io/components/sensor/combination/" ) diff --git a/esphome/components/pn532/__init__.py b/esphome/components/pn532/__init__.py index 3f04e8e1cc..6f679ed10a 100644 --- a/esphome/components/pn532/__init__.py +++ b/esphome/components/pn532/__init__.py @@ -55,7 +55,7 @@ def CONFIG_SCHEMA(conf): if conf: raise cv.Invalid( "This component has been moved in 1.16, please see the docs for updated " - "instructions. https://esphome.io/components/binary_sensor/pn532.html" + "instructions. https://esphome.io/components/binary_sensor/pn532/" ) diff --git a/esphome/components/sgp40/sensor.py b/esphome/components/sgp40/sensor.py index ad9de6fe24..b16151ec1f 100644 --- a/esphome/components/sgp40/sensor.py +++ b/esphome/components/sgp40/sensor.py @@ -4,5 +4,5 @@ CODEOWNERS = ["@SenexCrenshaw"] CONFIG_SCHEMA = cv.invalid( "SGP40 is deprecated.\nPlease use the SGP4x platform instead.\nSGP4x supports both SPG40 and SGP41.\n" - " See https://esphome.io/components/sensor/sgp4x.html" + " See https://esphome.io/components/sensor/sgp4x/" ) diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 52cf0bedea..bb69d57872 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -171,7 +171,7 @@ class DeferredUpdateEventSourceList : public std::listprint( - ESPHOME_F("

See ESPHome Web API for " - "REST API documentation.

")); + stream->print(ESPHOME_F("

See ESPHome Web API for " + "REST API documentation.

")); #if defined(USE_WEBSERVER_OTA) && !defined(USE_WEBSERVER_OTA_DISABLED) // Show OTA form only if web_server OTA is not explicitly disabled // Note: USE_WEBSERVER_OTA_DISABLED only affects web_server, not captive_portal diff --git a/esphome/core/config.py b/esphome/core/config.py index 0a239c5f5e..3adaf7eb9e 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -87,7 +87,7 @@ def validate_hostname(config): _LOGGER.warning( "'%s': Using the '_' (underscore) character in the hostname is discouraged " "as it can cause problems with some DHCP and local name services. " - "For more information, see https://esphome.io/guides/faq.html#why-shouldn-t-i-use-underscores-in-my-device-name", + "For more information, see https://esphome.io/guides/faq/#why-shouldnt-i-use-underscores-in-my-device-name", config[CONF_NAME], ) return config diff --git a/esphome/espota2.py b/esphome/espota2.py index 2b1b9a8328..c29506224c 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -402,7 +402,7 @@ def run_ota_impl_( ) _LOGGER.error( "(If this error persists, please set a static IP address: " - "https://esphome.io/components/wifi.html#manual-ips)" + "https://esphome.io/components/wifi/#manual-ips)" ) raise OTAError(err) from err diff --git a/esphome/pins.py b/esphome/pins.py index 601c05880a..bdaa0e28ab 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -274,7 +274,7 @@ def check_strapping_pin(conf, strapping_pin_list: set[int], logger: Logger): logger.warning( f"GPIO{num} is a strapping PIN and should only be used for I/O with care.\n" "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" - "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + "See https://esphome.io/guides/faq/#why-am-i-getting-a-warning-about-strapping-pins", ) # mitigate undisciplined use of strapping: if num not in strapping_pin_list and conf.get(CONF_IGNORE_STRAPPING_WARNING): diff --git a/esphome/util.py b/esphome/util.py index d41800dc20..7b896de27e 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -375,6 +375,6 @@ def get_esp32_arduino_flash_error_help() -> str | None: + "For detailed migration instructions, see:\n" + color( AnsiFore.BLUE, - "https://esphome.io/guides/esp32_arduino_to_idf.html\n\n", + "https://esphome.io/guides/esp32_arduino_to_idf/\n\n", ) ) diff --git a/esphome/wizard.py b/esphome/wizard.py index 97343eea99..d77450b04d 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -411,9 +411,7 @@ def wizard(path: Path) -> int: "https://docs.platformio.org/en/latest/platforms/espressif8266.html#boards" ) elif platform == "RP2040": - board_link = ( - "https://www.raspberrypi.com/documentation/microcontrollers/rp2040.html" - ) + board_link = "https://www.raspberrypi.com/documentation/microcontrollers/silicon.html#rp2040" elif platform in ["BK72XX", "LN882X", "RTL87XX"]: board_link = "https://docs.libretiny.eu/docs/status/supported/" else: @@ -555,7 +553,7 @@ def wizard(path: Path) -> int: safe_print("Next steps:") safe_print(" > Follow the rest of the getting started guide:") safe_print( - " > https://esphome.io/guides/getting_started_command_line.html#adding-some-features" + " > https://esphome.io/guides/getting_started_command_line/#adding-some-features" ) safe_print(" > to learn how to customize ESPHome and install it to your device.") return 0 diff --git a/tests/components/qr_code/common.yaml b/tests/components/qr_code/common.yaml index 5fec26c1cc..15b4e387c6 100644 --- a/tests/components/qr_code/common.yaml +++ b/tests/components/qr_code/common.yaml @@ -16,4 +16,4 @@ display: qr_code: - id: qr_code_homepage_qr - value: https://esphome.io/index.html + value: https://esphome.io/ From 3eaa9f164b90a79bc5f7763f10b18c0630cdb6ba Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Mon, 8 Dec 2025 20:38:13 +0100 Subject: [PATCH 0572/1145] [micronova] Remove MicroNovaFunctions (#12363) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/micronova/__init__.py | 19 +------ .../components/micronova/button/__init__.py | 2 - .../micronova/button/micronova_button.cpp | 8 +-- esphome/components/micronova/micronova.h | 20 ------- .../components/micronova/number/__init__.py | 13 ++--- .../micronova/number/micronova_number.cpp | 32 ++++-------- .../micronova/number/micronova_number.h | 5 ++ .../components/micronova/sensor/__init__.py | 23 ++++---- .../micronova/sensor/micronova_sensor.cpp | 26 +++------- .../micronova/sensor/micronova_sensor.h | 11 ++-- .../components/micronova/switch/__init__.py | 5 +- .../micronova/switch/micronova_switch.cpp | 52 +++++++------------ .../micronova/text_sensor/__init__.py | 5 +- .../text_sensor/micronova_text_sensor.cpp | 8 +-- 14 files changed, 68 insertions(+), 161 deletions(-) diff --git a/esphome/components/micronova/__init__.py b/esphome/components/micronova/__init__.py index 637d0eb168..52fbae2da2 100644 --- a/esphome/components/micronova/__init__.py +++ b/esphome/components/micronova/__init__.py @@ -17,22 +17,6 @@ DEFAULT_POLLING_INTERVAL = "60s" micronova_ns = cg.esphome_ns.namespace(DOMAIN) -MicroNovaFunctions = micronova_ns.enum("MicroNovaFunctions", is_class=True) -MICRONOVA_FUNCTIONS_ENUM = { - "STOVE_FUNCTION_SWITCH": MicroNovaFunctions.STOVE_FUNCTION_SWITCH, - "STOVE_FUNCTION_ROOM_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_ROOM_TEMPERATURE, - "STOVE_FUNCTION_THERMOSTAT_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_THERMOSTAT_TEMPERATURE, - "STOVE_FUNCTION_FUMES_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_FUMES_TEMPERATURE, - "STOVE_FUNCTION_STOVE_POWER": MicroNovaFunctions.STOVE_FUNCTION_STOVE_POWER, - "STOVE_FUNCTION_FAN_SPEED": MicroNovaFunctions.STOVE_FUNCTION_FAN_SPEED, - "STOVE_FUNCTION_STOVE_STATE": MicroNovaFunctions.STOVE_FUNCTION_STOVE_STATE, - "STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR": MicroNovaFunctions.STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR, - "STOVE_FUNCTION_WATER_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_WATER_TEMPERATURE, - "STOVE_FUNCTION_WATER_PRESSURE": MicroNovaFunctions.STOVE_FUNCTION_WATER_PRESSURE, - "STOVE_FUNCTION_POWER_LEVEL": MicroNovaFunctions.STOVE_FUNCTION_POWER_LEVEL, - "STOVE_FUNCTION_CUSTOM": MicroNovaFunctions.STOVE_FUNCTION_CUSTOM, -} - MicroNova = micronova_ns.class_("MicroNova", cg.Component, uart.UARTDevice) MicroNovaListener = micronova_ns.class_("MicroNovaListener", cg.PollingComponent) @@ -78,12 +62,11 @@ def MICRONOVA_ADDRESS_SCHEMA( return schema -async def to_code_micronova_listener(mv, var, config, micronova_function): +async def to_code_micronova_listener(mv, var, config): await cg.register_component(var, config) cg.add(mv.register_micronova_listener(var)) cg.add(var.set_memory_location(config[CONF_MEMORY_LOCATION])) cg.add(var.set_memory_address(config[CONF_MEMORY_ADDRESS])) - cg.add(var.set_function(micronova_function)) async def to_code(config): diff --git a/esphome/components/micronova/button/__init__.py b/esphome/components/micronova/button/__init__.py index 38fee2f561..2eda887443 100644 --- a/esphome/components/micronova/button/__init__.py +++ b/esphome/components/micronova/button/__init__.py @@ -8,7 +8,6 @@ from .. import ( CONF_MICRONOVA_ID, MICRONOVA_ADDRESS_SCHEMA, MicroNova, - MicroNovaFunctions, micronova_ns, ) @@ -43,4 +42,3 @@ async def to_code(config): cg.add(bt.set_memory_location(custom_button_config.get(CONF_MEMORY_LOCATION))) cg.add(bt.set_memory_address(custom_button_config.get(CONF_MEMORY_ADDRESS))) cg.add(bt.set_memory_data(custom_button_config[CONF_MEMORY_DATA])) - cg.add(bt.set_function(MicroNovaFunctions.STOVE_FUNCTION_CUSTOM)) diff --git a/esphome/components/micronova/button/micronova_button.cpp b/esphome/components/micronova/button/micronova_button.cpp index c78b4024f9..3f49d4b5b3 100644 --- a/esphome/components/micronova/button/micronova_button.cpp +++ b/esphome/components/micronova/button/micronova_button.cpp @@ -3,13 +3,7 @@ namespace esphome::micronova { void MicroNovaButton::press_action() { - switch (this->get_function()) { - case MicroNovaFunctions::STOVE_FUNCTION_CUSTOM: - this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_); - break; - default: - break; - } + this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_); this->micronova_->request_update_listeners(); } diff --git a/esphome/components/micronova/micronova.h b/esphome/components/micronova/micronova.h index 1b2c06f07f..a70f355ead 100644 --- a/esphome/components/micronova/micronova.h +++ b/esphome/components/micronova/micronova.h @@ -12,22 +12,6 @@ namespace esphome::micronova { static const char *const TAG = "micronova"; -enum class MicroNovaFunctions { - STOVE_FUNCTION_VOID = 0, - STOVE_FUNCTION_SWITCH = 1, - STOVE_FUNCTION_ROOM_TEMPERATURE = 2, - STOVE_FUNCTION_THERMOSTAT_TEMPERATURE = 3, - STOVE_FUNCTION_FUMES_TEMPERATURE = 4, - STOVE_FUNCTION_STOVE_POWER = 5, - STOVE_FUNCTION_FAN_SPEED = 6, - STOVE_FUNCTION_STOVE_STATE = 7, - STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR = 8, - STOVE_FUNCTION_WATER_TEMPERATURE = 9, - STOVE_FUNCTION_WATER_PRESSURE = 10, - STOVE_FUNCTION_POWER_LEVEL = 11, - STOVE_FUNCTION_CUSTOM = 12 -}; - class MicroNova; ////////////////////////////////////////////////////////////////////// @@ -39,9 +23,6 @@ class MicroNovaBaseListener { void set_micronova_object(MicroNova *m) { this->micronova_ = m; } - void set_function(MicroNovaFunctions f) { this->function_ = f; } - MicroNovaFunctions get_function() { return this->function_; } - void set_memory_location(uint8_t l) { this->memory_location_ = l; } uint8_t get_memory_location() { return this->memory_location_; } @@ -52,7 +33,6 @@ class MicroNovaBaseListener { protected: MicroNova *micronova_{nullptr}; - MicroNovaFunctions function_ = MicroNovaFunctions::STOVE_FUNCTION_VOID; uint8_t memory_location_ = 0; uint8_t memory_address_ = 0; }; diff --git a/esphome/components/micronova/number/__init__.py b/esphome/components/micronova/number/__init__.py index 07023e618c..ef6cc0f7d7 100644 --- a/esphome/components/micronova/number/__init__.py +++ b/esphome/components/micronova/number/__init__.py @@ -7,7 +7,6 @@ from .. import ( CONF_MICRONOVA_ID, MICRONOVA_ADDRESS_SCHEMA, MicroNova, - MicroNovaFunctions, MicroNovaListener, micronova_ns, to_code_micronova_listener, @@ -66,13 +65,9 @@ async def to_code(config): max_value=40, step=thermostat_temperature_config.get(CONF_STEP), ) - await to_code_micronova_listener( - mv, - numb, - thermostat_temperature_config, - MicroNovaFunctions.STOVE_FUNCTION_THERMOSTAT_TEMPERATURE, - ) + await to_code_micronova_listener(mv, numb, thermostat_temperature_config) cg.add(numb.set_micronova_object(mv)) + cg.add(numb.set_use_step_scaling(True)) if power_level_config := config.get(CONF_POWER_LEVEL): numb = await number.new_number( @@ -81,7 +76,5 @@ async def to_code(config): max_value=5, step=1, ) - await to_code_micronova_listener( - mv, numb, power_level_config, MicroNovaFunctions.STOVE_FUNCTION_POWER_LEVEL - ) + await to_code_micronova_listener(mv, numb, power_level_config) cg.add(numb.set_micronova_object(mv)) diff --git a/esphome/components/micronova/number/micronova_number.cpp b/esphome/components/micronova/number/micronova_number.cpp index c71d819ad6..8027947468 100644 --- a/esphome/components/micronova/number/micronova_number.cpp +++ b/esphome/components/micronova/number/micronova_number.cpp @@ -3,38 +3,24 @@ namespace esphome::micronova { void MicroNovaNumber::process_value_from_stove(int value_from_stove) { - float new_sensor_value = 0; - if (value_from_stove == -1) { this->publish_state(NAN); return; } - switch (this->get_function()) { - case MicroNovaFunctions::STOVE_FUNCTION_THERMOSTAT_TEMPERATURE: - new_sensor_value = ((float) value_from_stove) * this->traits.get_step(); - break; - case MicroNovaFunctions::STOVE_FUNCTION_POWER_LEVEL: - new_sensor_value = (float) value_from_stove; - break; - default: - break; + float new_value = static_cast(value_from_stove); + if (this->use_step_scaling_) { + new_value *= this->traits.get_step(); } - this->publish_state(new_sensor_value); + this->publish_state(new_value); } void MicroNovaNumber::control(float value) { - uint8_t new_number = 0; - - switch (this->get_function()) { - case MicroNovaFunctions::STOVE_FUNCTION_THERMOSTAT_TEMPERATURE: - new_number = (uint8_t) (value / this->traits.get_step()); - break; - case MicroNovaFunctions::STOVE_FUNCTION_POWER_LEVEL: - new_number = (uint8_t) value; - break; - default: - break; + uint8_t new_number; + if (this->use_step_scaling_) { + new_number = static_cast(value / this->traits.get_step()); + } else { + new_number = static_cast(value); } this->micronova_->write_address(this->memory_location_, this->memory_address_, new_number); this->micronova_->request_update_listeners(); diff --git a/esphome/components/micronova/number/micronova_number.h b/esphome/components/micronova/number/micronova_number.h index 391765b730..3fc5838a4f 100644 --- a/esphome/components/micronova/number/micronova_number.h +++ b/esphome/components/micronova/number/micronova_number.h @@ -18,6 +18,11 @@ class MicroNovaNumber : public number::Number, public MicroNovaListener { this->micronova_->request_address(this->memory_location_, this->memory_address_, this); } void process_value_from_stove(int value_from_stove) override; + + void set_use_step_scaling(bool v) { this->use_step_scaling_ = v; } + + protected: + bool use_step_scaling_ = false; }; } // namespace esphome::micronova diff --git a/esphome/components/micronova/sensor/__init__.py b/esphome/components/micronova/sensor/__init__.py index 77bdacd5da..55318a7fff 100644 --- a/esphome/components/micronova/sensor/__init__.py +++ b/esphome/components/micronova/sensor/__init__.py @@ -13,7 +13,6 @@ from .. import ( CONF_MICRONOVA_ID, MICRONOVA_ADDRESS_SCHEMA, MicroNova, - MicroNovaFunctions, MicroNovaListener, micronova_ns, to_code_micronova_listener, @@ -131,21 +130,21 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): mv = await cg.get_variable(config[CONF_MICRONOVA_ID]) - for key, fn in { - CONF_ROOM_TEMPERATURE: MicroNovaFunctions.STOVE_FUNCTION_ROOM_TEMPERATURE, - CONF_FUMES_TEMPERATURE: MicroNovaFunctions.STOVE_FUNCTION_FUMES_TEMPERATURE, - CONF_STOVE_POWER: MicroNovaFunctions.STOVE_FUNCTION_STOVE_POWER, - CONF_MEMORY_ADDRESS_SENSOR: MicroNovaFunctions.STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR, - CONF_WATER_TEMPERATURE: MicroNovaFunctions.STOVE_FUNCTION_WATER_TEMPERATURE, - CONF_WATER_PRESSURE: MicroNovaFunctions.STOVE_FUNCTION_WATER_PRESSURE, + for key, divisor in { + CONF_ROOM_TEMPERATURE: 2, + CONF_FUMES_TEMPERATURE: None, + CONF_STOVE_POWER: None, + CONF_MEMORY_ADDRESS_SENSOR: None, + CONF_WATER_TEMPERATURE: 2, + CONF_WATER_PRESSURE: 10, }.items(): if sensor_config := config.get(key): sens = await sensor.new_sensor(sensor_config, mv) - await to_code_micronova_listener(mv, sens, sensor_config, fn) + await to_code_micronova_listener(mv, sens, sensor_config) + if divisor: + cg.add(sens.set_divisor(divisor)) if fan_speed_config := config.get(CONF_FAN_SPEED): sens = await sensor.new_sensor(fan_speed_config, mv) - await to_code_micronova_listener( - mv, sens, fan_speed_config, MicroNovaFunctions.STOVE_FUNCTION_FAN_SPEED - ) + await to_code_micronova_listener(mv, sens, fan_speed_config) cg.add(sens.set_fan_speed_offset(fan_speed_config[CONF_FAN_RPM_OFFSET])) diff --git a/esphome/components/micronova/sensor/micronova_sensor.cpp b/esphome/components/micronova/sensor/micronova_sensor.cpp index 9fd8832f29..d845e0ab3c 100644 --- a/esphome/components/micronova/sensor/micronova_sensor.cpp +++ b/esphome/components/micronova/sensor/micronova_sensor.cpp @@ -8,25 +8,15 @@ void MicroNovaSensor::process_value_from_stove(int value_from_stove) { return; } - float new_sensor_value = (float) value_from_stove; - switch (this->get_function()) { - case MicroNovaFunctions::STOVE_FUNCTION_ROOM_TEMPERATURE: - new_sensor_value = new_sensor_value / 2; - break; - case MicroNovaFunctions::STOVE_FUNCTION_THERMOSTAT_TEMPERATURE: - break; - case MicroNovaFunctions::STOVE_FUNCTION_FAN_SPEED: - new_sensor_value = new_sensor_value == 0 ? 0 : (new_sensor_value * 10) + this->fan_speed_offset_; - break; - case MicroNovaFunctions::STOVE_FUNCTION_WATER_TEMPERATURE: - new_sensor_value = new_sensor_value / 2; - break; - case MicroNovaFunctions::STOVE_FUNCTION_WATER_PRESSURE: - new_sensor_value = new_sensor_value / 10; - break; - default: - break; + float new_sensor_value = static_cast(value_from_stove); + + // Fan speed has special calculation: value * 10 + offset (when non-zero) + if (this->is_fan_speed_) { + new_sensor_value = value_from_stove == 0 ? 0.0f : (new_sensor_value * 10) + this->fan_speed_offset_; + } else if (this->divisor_ > 1) { + new_sensor_value = new_sensor_value / this->divisor_; } + this->publish_state(new_sensor_value); } diff --git a/esphome/components/micronova/sensor/micronova_sensor.h b/esphome/components/micronova/sensor/micronova_sensor.h index 119e5eb155..a2f232c7dc 100644 --- a/esphome/components/micronova/sensor/micronova_sensor.h +++ b/esphome/components/micronova/sensor/micronova_sensor.h @@ -18,11 +18,16 @@ class MicroNovaSensor : public sensor::Sensor, public MicroNovaListener { } void process_value_from_stove(int value_from_stove) override; - void set_fan_speed_offset(uint8_t f) { this->fan_speed_offset_ = f; } - uint8_t get_set_fan_speed_offset() { return this->fan_speed_offset_; } + void set_divisor(uint8_t d) { this->divisor_ = d; } + void set_fan_speed_offset(uint8_t offset) { + this->is_fan_speed_ = true; + this->fan_speed_offset_ = offset; + } protected: - int fan_speed_offset_ = 0; + uint8_t divisor_ = 1; + uint8_t fan_speed_offset_ = 0; + bool is_fan_speed_ = false; }; } // namespace esphome::micronova diff --git a/esphome/components/micronova/switch/__init__.py b/esphome/components/micronova/switch/__init__.py index 62a8a0f008..c937a4cac9 100644 --- a/esphome/components/micronova/switch/__init__.py +++ b/esphome/components/micronova/switch/__init__.py @@ -7,7 +7,6 @@ from .. import ( CONF_MICRONOVA_ID, MICRONOVA_ADDRESS_SCHEMA, MicroNova, - MicroNovaFunctions, MicroNovaListener, micronova_ns, to_code_micronova_listener, @@ -50,8 +49,6 @@ async def to_code(config): if stove_config := config.get(CONF_STOVE): sw = await switch.new_switch(stove_config, mv) - await to_code_micronova_listener( - mv, sw, stove_config, MicroNovaFunctions.STOVE_FUNCTION_SWITCH - ) + await to_code_micronova_listener(mv, sw, stove_config) cg.add(sw.set_memory_data_on(stove_config[CONF_MEMORY_DATA_ON])) cg.add(sw.set_memory_data_off(stove_config[CONF_MEMORY_DATA_OFF])) diff --git a/esphome/components/micronova/switch/micronova_switch.cpp b/esphome/components/micronova/switch/micronova_switch.cpp index 76ef04da8a..9b9ad61018 100644 --- a/esphome/components/micronova/switch/micronova_switch.cpp +++ b/esphome/components/micronova/switch/micronova_switch.cpp @@ -3,31 +3,24 @@ namespace esphome::micronova { void MicroNovaSwitch::write_state(bool state) { - switch (this->get_function()) { - case MicroNovaFunctions::STOVE_FUNCTION_SWITCH: { - if (state) { - // Only send power-on when current state is Off - if (this->raw_state_ == 0) { - this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_on_); - this->publish_state(true); - } else { - ESP_LOGW(TAG, "Unable to turn stove on, invalid state: %d", this->raw_state_); - } - } else { - // don't send power-off when status is Off or Final cleaning - if (this->raw_state_ != 0 && this->raw_state_ != 6) { - this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_off_); - this->publish_state(false); - } else { - ESP_LOGW(TAG, "Unable to turn stove off, invalid state: %d", this->raw_state_); - } - } - this->set_needs_update(true); - break; + if (state) { + // Only send power-on when current state is Off + if (this->raw_state_ == 0) { + this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_on_); + this->publish_state(true); + } else { + ESP_LOGW(TAG, "Unable to turn stove on, invalid state: %d", this->raw_state_); + } + } else { + // don't send power-off when status is Off or Final cleaning + if (this->raw_state_ != 0 && this->raw_state_ != 6) { + this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_off_); + this->publish_state(false); + } else { + ESP_LOGW(TAG, "Unable to turn stove off, invalid state: %d", this->raw_state_); } - default: - break; } + this->set_needs_update(true); } void MicroNovaSwitch::process_value_from_stove(int value_from_stove) { @@ -37,16 +30,9 @@ void MicroNovaSwitch::process_value_from_stove(int value_from_stove) { return; } - switch (this->get_function()) { - case MicroNovaFunctions::STOVE_FUNCTION_SWITCH: { - // set the stove switch to on for any value but 0 - bool state = value_from_stove != 0; - this->publish_state(state); - break; - } - default: - break; - } + // set the stove switch to on for any value but 0 + bool state = value_from_stove != 0; + this->publish_state(state); } } // namespace esphome::micronova diff --git a/esphome/components/micronova/text_sensor/__init__.py b/esphome/components/micronova/text_sensor/__init__.py index e54b9e280a..33d0779eae 100644 --- a/esphome/components/micronova/text_sensor/__init__.py +++ b/esphome/components/micronova/text_sensor/__init__.py @@ -6,7 +6,6 @@ from .. import ( CONF_MICRONOVA_ID, MICRONOVA_ADDRESS_SCHEMA, MicroNova, - MicroNovaFunctions, MicroNovaListener, micronova_ns, to_code_micronova_listener, @@ -39,6 +38,4 @@ async def to_code(config): if stove_state_config := config.get(CONF_STOVE_STATE): sens = await text_sensor.new_text_sensor(stove_state_config, mv) - await to_code_micronova_listener( - mv, sens, stove_state_config, MicroNovaFunctions.STOVE_FUNCTION_STOVE_STATE - ) + await to_code_micronova_listener(mv, sens, stove_state_config) diff --git a/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp b/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp index d1c03f66c3..2217ed6d6f 100644 --- a/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp +++ b/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp @@ -8,13 +8,7 @@ void MicroNovaTextSensor::process_value_from_stove(int value_from_stove) { return; } - switch (this->get_function()) { - case MicroNovaFunctions::STOVE_FUNCTION_STOVE_STATE: - this->publish_state(STOVE_STATES[value_from_stove]); - break; - default: - break; - } + this->publish_state(STOVE_STATES[value_from_stove]); } } // namespace esphome::micronova From fcae13836cdfb08ec239aaa1d89dc63a354e9040 Mon Sep 17 00:00:00 2001 From: Mirko Vogt Date: Tue, 9 Dec 2025 04:50:07 +0100 Subject: [PATCH 0573/1145] [sx1509] Change setup priority from HARDWARE to IO (#12373) Co-authored-by: Your Name --- esphome/components/sx1509/sx1509.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sx1509/sx1509.h b/esphome/components/sx1509/sx1509.h index 2afd0d0e4e..f98fc0a44f 100644 --- a/esphome/components/sx1509/sx1509.h +++ b/esphome/components/sx1509/sx1509.h @@ -40,7 +40,7 @@ class SX1509Component : public Component, void setup() override; void dump_config() override; - float get_setup_priority() const override { return setup_priority::HARDWARE; } + float get_setup_priority() const override { return setup_priority::IO; } void loop() override; uint16_t read_key_data(); From 6945b44af59107a183004c0880c525c1179bf7ce Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 10 Dec 2025 00:38:16 +1100 Subject: [PATCH 0574/1145] [psram] Fix boot failure with 120MHz Octal flash (#12377) --- esphome/components/psram/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index fcbe9ed043..39afb407f1 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -197,7 +197,6 @@ async def to_code(config): add_idf_sdkconfig_option("CONFIG_SPIRAM_SPEED", speed) if config[CONF_MODE] == TYPE_OCTAL and speed == 120: add_idf_sdkconfig_option("CONFIG_ESPTOOLPY_FLASHFREQ_120M", True) - add_idf_sdkconfig_option("CONFIG_BOOTLOADER_FLASH_DC_AWARE", True) if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(5, 4, 0): add_idf_sdkconfig_option( "CONFIG_SPIRAM_TIMING_TUNING_POINT_VIA_TEMPERATURE_SENSOR", True From 750f4ea797703ed6301c3a99a3acb6ea648f251b Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 10 Dec 2025 00:40:58 +1100 Subject: [PATCH 0575/1145] [pio] Rationalise library definitions in platformio.ini (#12374) --- .clang-tidy.hash | 2 +- platformio.ini | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 7dabee48f1..a3322ba731 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -c01eec15857a784dd603c0afd194ab3b29a632422fe6f6b0a806ad4d81b5efc0 +766420905c06eeb6c5f360f68fd965e5ddd9c4a5db6b823263d3ad3accb64a07 diff --git a/platformio.ini b/platformio.ini index 9095d27af8..d37c798c05 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,20 +32,24 @@ build_flags = ; This are common settings for all environments. [common] -lib_deps = - esphome/noise-c@0.1.10 ; api - improv/Improv@1.2.4 ; improv_serial / esp32_improv +; Base dependencies for all environments +lib_deps_base = bblanchon/ArduinoJson@7.4.2 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 pavlodn/HaierProtocol@0.9.31 ; haier - kikuchan98/pngle@1.1.0 ; online_image https://github.com/esphome/TinyGPSPlus.git#v1.1.0 ; gps +; This is using the repository until a new release is published to PlatformIO + https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library + lvgl/lvgl@8.4.0 ; lvgl + +lib_deps = + ${common.lib_deps_base} + esphome/noise-c@0.1.10 ; api + improv/Improv@1.2.4 ; improv_serial / esp32_improv + kikuchan98/pngle@1.1.0 ; online_image ; Using the repository directly, otherwise ESP-IDF can't use the library https://github.com/bitbank2/JPEGDEC.git#ca1e0f2 ; online_image - ; This is using the repository until a new release is published to PlatformIO - https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library - lvgl/lvgl@8.4.0 ; lvgl ; This dependency is used only in unit tests. ; Must coincide with PLATFORMIO_GOOGLE_TEST_LIB in scripts/cpp_unit_test.py ; See scripts/cpp_unit_test.py and tests/components/README.md @@ -236,13 +240,7 @@ build_flags = -DUSE_ZEPHYR -DUSE_NRF52 lib_deps = - bblanchon/ArduinoJson@7.4.2 ; json - wjtje/qr-code-generator-library@1.7.0 ; qr_code - pavlodn/HaierProtocol@0.9.31 ; haier - functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 - https://github.com/esphome/TinyGPSPlus.git#v1.1.0 ; gps - https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library - lvgl/lvgl@8.4.0 ; lvgl + ${common.lib_deps_base} ; All the actual environments are defined below. From 861ed8dd41c2966494ebcaaca5843a672e239072 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Dec 2025 15:27:12 +0100 Subject: [PATCH 0576/1145] [scheduler] Avoid std::string allocation in RetryArgs (#12311) --- esphome/core/component.cpp | 9 +++++++ esphome/core/component.h | 4 ++++ esphome/core/scheduler.cpp | 49 ++++++++++++++++++++++++++++++-------- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index b7c0cedb76..97ab2edb5a 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -138,10 +138,19 @@ void Component::set_retry(const std::string &name, uint32_t initial_wait_time, u App.scheduler.set_retry(this, name, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor); } +void Component::set_retry(const char *name, uint32_t initial_wait_time, uint8_t max_attempts, + std::function &&f, float backoff_increase_factor) { // NOLINT + App.scheduler.set_retry(this, name, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor); +} + bool Component::cancel_retry(const std::string &name) { // NOLINT return App.scheduler.cancel_retry(this, name); } +bool Component::cancel_retry(const char *name) { // NOLINT + return App.scheduler.cancel_retry(this, name); +} + void Component::set_timeout(const std::string &name, uint32_t timeout, std::function &&f) { // NOLINT App.scheduler.set_timeout(this, name, timeout, std::move(f)); } diff --git a/esphome/core/component.h b/esphome/core/component.h index 3d45a020c4..32f594d6f8 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -367,6 +367,9 @@ class Component { void set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT std::function &&f, float backoff_increase_factor = 1.0f); // NOLINT + void set_retry(const char *name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT + std::function &&f, float backoff_increase_factor = 1.0f); // NOLINT + void set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f, // NOLINT float backoff_increase_factor = 1.0f); // NOLINT @@ -376,6 +379,7 @@ class Component { * @return Whether a retry function was deleted. */ bool cancel_retry(const std::string &name); // NOLINT + bool cancel_retry(const char *name); // NOLINT /** Set a timeout function with a unique name. * diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index f84495950c..8b713523b6 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -204,13 +204,21 @@ bool HOT Scheduler::cancel_interval(Component *component, const char *name) { } struct RetryArgs { + // Ordered to minimize padding on 32-bit systems std::function func; - uint8_t retry_countdown; - uint32_t current_interval; Component *component; - std::string name; // Keep as std::string since retry uses it dynamically - float backoff_increase_factor; Scheduler *scheduler; + const char *name; // Points to static string or owned copy + uint32_t current_interval; + float backoff_increase_factor; + uint8_t retry_countdown; + bool name_is_dynamic; // True if name needs delete[] + + ~RetryArgs() { + if (this->name_is_dynamic && this->name) { + delete[] this->name; + } + } }; void retry_handler(const std::shared_ptr &args) { @@ -218,8 +226,10 @@ void retry_handler(const std::shared_ptr &args) { if (retry_result == RetryResult::DONE || args->retry_countdown <= 0) return; // second execution of `func` happens after `initial_wait_time` + // Pass is_static_string=true because args->name is owned by the shared_ptr + // which is captured in the lambda and outlives the SchedulerItem args->scheduler->set_timer_common_( - args->component, Scheduler::SchedulerItem::TIMEOUT, false, &args->name, args->current_interval, + args->component, Scheduler::SchedulerItem::TIMEOUT, true, args->name, args->current_interval, [args]() { retry_handler(args); }, /* is_retry= */ true); // backoff_increase_factor applied to third & later executions args->current_interval *= args->backoff_increase_factor; @@ -246,16 +256,35 @@ void HOT Scheduler::set_retry_common_(Component *component, bool is_static_strin auto args = std::make_shared(); args->func = std::move(func); - args->retry_countdown = max_attempts; - args->current_interval = initial_wait_time; args->component = component; - args->name = name_cstr ? name_cstr : ""; // Convert to std::string for RetryArgs - args->backoff_increase_factor = backoff_increase_factor; args->scheduler = this; + args->current_interval = initial_wait_time; + args->backoff_increase_factor = backoff_increase_factor; + args->retry_countdown = max_attempts; + + // Store name - either as static pointer or owned copy + if (name_cstr == nullptr || name_cstr[0] == '\0') { + // Empty or null name - use empty string literal + args->name = ""; + args->name_is_dynamic = false; + } else if (is_static_string) { + // Static string - just store the pointer + args->name = name_cstr; + args->name_is_dynamic = false; + } else { + // Dynamic string - make a copy + size_t len = strlen(name_cstr); + char *copy = new char[len + 1]; + memcpy(copy, name_cstr, len + 1); + args->name = copy; + args->name_is_dynamic = true; + } // First execution of `func` immediately - use set_timer_common_ with is_retry=true + // Pass is_static_string=true because args->name is owned by the shared_ptr + // which is captured in the lambda and outlives the SchedulerItem this->set_timer_common_( - component, SchedulerItem::TIMEOUT, false, &args->name, 0, [args]() { retry_handler(args); }, + component, SchedulerItem::TIMEOUT, true, args->name, 0, [args]() { retry_handler(args); }, /* is_retry= */ true); } From f9aa48295c46e74968fd909c5348df18944cb697 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Dec 2025 15:33:23 +0100 Subject: [PATCH 0577/1145] [mdns] Reduce RAM usage by eliminating MAC address heap allocation (#12073) --- esphome/components/mdns/__init__.py | 10 +++--- esphome/components/mdns/mdns_component.cpp | 6 ++-- esphome/components/mdns/mdns_component.h | 42 ++++++++++++++++++++-- esphome/components/mdns/mdns_esp32.cpp | 14 +++----- esphome/components/mdns/mdns_esp8266.cpp | 12 ++----- esphome/components/mdns/mdns_host.cpp | 9 +++++ esphome/components/mdns/mdns_libretiny.cpp | 12 ++----- esphome/components/mdns/mdns_rp2040.cpp | 12 ++----- esphome/core/defines.h | 3 +- esphome/core/helpers.cpp | 6 ++-- esphome/core/helpers.h | 15 +++++--- 11 files changed, 86 insertions(+), 55 deletions(-) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index 1daac93a2e..99b728b249 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -184,10 +184,8 @@ async def to_code(config): # Calculate compile-time dynamic TXT value count # Dynamic values are those that cannot be stored in flash at compile time + # Note: MAC address is now stored in a fixed char[13] buffer, not dynamic storage dynamic_txt_count = 0 - if "api" in CORE.config: - # Always: get_mac_address() - dynamic_txt_count += 1 # User-provided templatable TXT values (only lambdas, not static strings) dynamic_txt_count += sum( 1 @@ -196,8 +194,10 @@ async def to_code(config): if cg.is_template(txt_value) ) - # Ensure at least 1 to avoid zero-size array - cg.add_define("MDNS_DYNAMIC_TXT_COUNT", max(1, dynamic_txt_count)) + # Only add define if we actually need dynamic storage + if dynamic_txt_count > 0: + cg.add_define("USE_MDNS_DYNAMIC_TXT") + cg.add_define("MDNS_DYNAMIC_TXT_COUNT", dynamic_txt_count) # Enable storage if verbose logging is enabled (for dump_config) if get_logger_level() in ("VERBOSE", "VERY_VERBOSE"): diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 4655907983..47db92610a 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -35,7 +35,7 @@ MDNS_STATIC_CONST_CHAR(SERVICE_TCP, "_tcp"); // Wrap build-time defines into flash storage MDNS_STATIC_CONST_CHAR(VALUE_VERSION, ESPHOME_VERSION); -void MDNSComponent::compile_records_(StaticVector &services) { +void MDNSComponent::compile_records_(StaticVector &services, char *mac_address_buf) { // IMPORTANT: The #ifdef blocks below must match COMPONENTS_WITH_MDNS_SERVICES // in mdns/__init__.py. If you add a new service here, update both locations. @@ -86,7 +86,9 @@ void MDNSComponent::compile_records_(StaticVectoradd_dynamic_txt_value(get_mac_address()))}); + + // MAC address: passed from caller (either member buffer or stack buffer depending on USE_MDNS_STORE_SERVICES) + txt_records.push_back({MDNS_STR(TXT_MAC), MDNS_STR(mac_address_buf)}); #ifdef USE_ESP8266 MDNS_STATIC_CONST_CHAR(PLATFORM_ESP8266, "ESP8266"); diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index 691c45b7df..f696cfff1c 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -60,22 +60,58 @@ class MDNSComponent : public Component { void on_shutdown() override; +#ifdef USE_MDNS_DYNAMIC_TXT /// Add a dynamic TXT value and return pointer to it for use in MDNSTXTRecord const char *add_dynamic_txt_value(const std::string &value) { this->dynamic_txt_values_.push_back(value); return this->dynamic_txt_values_[this->dynamic_txt_values_.size() - 1].c_str(); } +#endif - /// Storage for runtime-generated TXT values (MAC address, user lambdas) + protected: + /// Helper to set up services and MAC buffers, then call platform-specific registration + using PlatformRegisterFn = void (*)(MDNSComponent *, StaticVector &); + + void setup_buffers_and_register_(PlatformRegisterFn platform_register) { +#ifdef USE_MDNS_STORE_SERVICES + auto &services = this->services_; +#else + StaticVector services_storage; + auto &services = services_storage; +#endif + +#ifdef USE_API +#ifdef USE_MDNS_STORE_SERVICES + get_mac_address_into_buffer(this->mac_address_); + char *mac_ptr = this->mac_address_; +#else + char mac_address[MAC_ADDRESS_BUFFER_SIZE]; + get_mac_address_into_buffer(mac_address); + char *mac_ptr = mac_address; +#endif +#else + char *mac_ptr = nullptr; +#endif + + this->compile_records_(services, mac_ptr); + platform_register(this, services); + } + +#ifdef USE_MDNS_DYNAMIC_TXT + /// Storage for runtime-generated TXT values from user lambdas /// Pre-sized at compile time via MDNS_DYNAMIC_TXT_COUNT to avoid heap allocations. /// Static/compile-time values (version, board, etc.) are stored directly in flash and don't use this. StaticVector dynamic_txt_values_; +#endif - protected: +#if defined(USE_API) && defined(USE_MDNS_STORE_SERVICES) + /// Fixed buffer for MAC address (only needed when services are stored) + char mac_address_[MAC_ADDRESS_BUFFER_SIZE]; +#endif #ifdef USE_MDNS_STORE_SERVICES StaticVector services_{}; #endif - void compile_records_(StaticVector &services); + void compile_records_(StaticVector &services, char *mac_address_buf); }; } // namespace esphome::mdns diff --git a/esphome/components/mdns/mdns_esp32.cpp b/esphome/components/mdns/mdns_esp32.cpp index 5547a2524b..e6b43e59cb 100644 --- a/esphome/components/mdns/mdns_esp32.cpp +++ b/esphome/components/mdns/mdns_esp32.cpp @@ -11,19 +11,11 @@ namespace esphome::mdns { static const char *const TAG = "mdns"; -void MDNSComponent::setup() { -#ifdef USE_MDNS_STORE_SERVICES - this->compile_records_(this->services_); - const auto &services = this->services_; -#else - StaticVector services; - this->compile_records_(services); -#endif - +static void register_esp32(MDNSComponent *comp, StaticVector &services) { esp_err_t err = mdns_init(); if (err != ESP_OK) { ESP_LOGW(TAG, "Init failed: %s", esp_err_to_name(err)); - this->mark_failed(); + comp->mark_failed(); return; } @@ -50,6 +42,8 @@ void MDNSComponent::setup() { } } +void MDNSComponent::setup() { this->setup_buffers_and_register_(register_esp32); } + void MDNSComponent::on_shutdown() { mdns_free(); delay(40); // Allow the mdns packets announcing service removal to be sent diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index 06f905884c..dcbe5ebd52 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -11,15 +11,7 @@ namespace esphome::mdns { -void MDNSComponent::setup() { -#ifdef USE_MDNS_STORE_SERVICES - this->compile_records_(this->services_); - const auto &services = this->services_; -#else - StaticVector services; - this->compile_records_(services); -#endif - +static void register_esp8266(MDNSComponent *, StaticVector &services) { MDNS.begin(App.get_name().c_str()); for (const auto &service : services) { @@ -44,6 +36,8 @@ void MDNSComponent::setup() { } } +void MDNSComponent::setup() { this->setup_buffers_and_register_(register_esp8266); } + void MDNSComponent::loop() { MDNS.update(); } void MDNSComponent::on_shutdown() { diff --git a/esphome/components/mdns/mdns_host.cpp b/esphome/components/mdns/mdns_host.cpp index 64b8c8f54b..4d902319b8 100644 --- a/esphome/components/mdns/mdns_host.cpp +++ b/esphome/components/mdns/mdns_host.cpp @@ -9,6 +9,15 @@ namespace esphome::mdns { void MDNSComponent::setup() { +#ifdef USE_MDNS_STORE_SERVICES +#ifdef USE_API + get_mac_address_into_buffer(this->mac_address_); + char *mac_ptr = this->mac_address_; +#else + char *mac_ptr = nullptr; +#endif + this->compile_records_(this->services_, mac_ptr); +#endif // Host platform doesn't have actual mDNS implementation } diff --git a/esphome/components/mdns/mdns_libretiny.cpp b/esphome/components/mdns/mdns_libretiny.cpp index a049fe2109..986099fa1f 100644 --- a/esphome/components/mdns/mdns_libretiny.cpp +++ b/esphome/components/mdns/mdns_libretiny.cpp @@ -11,15 +11,7 @@ namespace esphome::mdns { -void MDNSComponent::setup() { -#ifdef USE_MDNS_STORE_SERVICES - this->compile_records_(this->services_); - const auto &services = this->services_; -#else - StaticVector services; - this->compile_records_(services); -#endif - +static void register_libretiny(MDNSComponent *, StaticVector &services) { MDNS.begin(App.get_name().c_str()); for (const auto &service : services) { @@ -43,6 +35,8 @@ void MDNSComponent::setup() { } } +void MDNSComponent::setup() { this->setup_buffers_and_register_(register_libretiny); } + void MDNSComponent::on_shutdown() {} } // namespace esphome::mdns diff --git a/esphome/components/mdns/mdns_rp2040.cpp b/esphome/components/mdns/mdns_rp2040.cpp index a102e0b6c3..e4a9b60cdb 100644 --- a/esphome/components/mdns/mdns_rp2040.cpp +++ b/esphome/components/mdns/mdns_rp2040.cpp @@ -11,15 +11,7 @@ namespace esphome::mdns { -void MDNSComponent::setup() { -#ifdef USE_MDNS_STORE_SERVICES - this->compile_records_(this->services_); - const auto &services = this->services_; -#else - StaticVector services; - this->compile_records_(services); -#endif - +static void register_rp2040(MDNSComponent *, StaticVector &services) { MDNS.begin(App.get_name().c_str()); for (const auto &service : services) { @@ -43,6 +35,8 @@ void MDNSComponent::setup() { } } +void MDNSComponent::setup() { this->setup_buffers_and_register_(register_rp2040); } + void MDNSComponent::loop() { MDNS.update(); } void MDNSComponent::on_shutdown() { diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 021240cc40..a5170d73ff 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -90,7 +90,8 @@ #define USE_MDNS #define USE_MDNS_STORE_SERVICES #define MDNS_SERVICE_COUNT 3 -#define MDNS_DYNAMIC_TXT_COUNT 3 +#define USE_MDNS_DYNAMIC_TXT +#define MDNS_DYNAMIC_TXT_COUNT 2 #define SNTP_SERVER_COUNT 3 #define USE_MEDIA_PLAYER #define USE_NEXTION_TFT_UPLOAD diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 1f675563c7..77102c8db2 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -642,17 +642,17 @@ std::string get_mac_address() { } std::string get_mac_address_pretty() { - char buf[18]; + char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; return std::string(get_mac_address_pretty_into_buffer(buf)); } -void get_mac_address_into_buffer(std::span buf) { +void get_mac_address_into_buffer(std::span buf) { uint8_t mac[6]; get_mac_address_raw(mac); format_mac_addr_lower_no_sep(mac, buf.data()); } -const char *get_mac_address_pretty_into_buffer(std::span buf) { +const char *get_mac_address_pretty_into_buffer(std::span buf) { uint8_t mac[6]; get_mac_address_raw(mac); format_mac_addr_upper(mac, buf.data()); diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 83a12b9bf0..6054f03353 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1056,6 +1056,12 @@ class HighFrequencyLoopRequester { /// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). void get_mac_address_raw(uint8_t *mac); // NOLINT(readability-non-const-parameter) +/// Buffer size for MAC address in lowercase hex notation (12 hex chars + null terminator) +constexpr size_t MAC_ADDRESS_BUFFER_SIZE = 13; + +/// Buffer size for MAC address in colon-separated uppercase hex notation (17 chars + null terminator) +constexpr size_t MAC_ADDRESS_PRETTY_BUFFER_SIZE = 18; + /// Get the device MAC address as a string, in lowercase hex notation. std::string get_mac_address(); @@ -1063,13 +1069,14 @@ std::string get_mac_address(); std::string get_mac_address_pretty(); /// Get the device MAC address into the given buffer, in lowercase hex notation. -/// Assumes buffer length is 13 (12 digits for hexadecimal representation followed by null terminator). -void get_mac_address_into_buffer(std::span buf); +/// Assumes buffer length is MAC_ADDRESS_BUFFER_SIZE (12 digits for hexadecimal representation followed by null +/// terminator). +void get_mac_address_into_buffer(std::span buf); /// Get the device MAC address into the given buffer, in colon-separated uppercase hex notation. -/// Buffer must be exactly 18 bytes (17 for "XX:XX:XX:XX:XX:XX" + null terminator). +/// Buffer must be exactly MAC_ADDRESS_PRETTY_BUFFER_SIZE bytes (17 for "XX:XX:XX:XX:XX:XX" + null terminator). /// Returns pointer to the buffer for convenience. -const char *get_mac_address_pretty_into_buffer(std::span buf); +const char *get_mac_address_pretty_into_buffer(std::span buf); #ifdef USE_ESP32 /// Set the MAC address to use from the provided byte array (6 bytes). From 74f509c754b9d7e92f867d789b4e11be4372e7bc Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:42:06 -0500 Subject: [PATCH 0578/1145] [core] Add PR template instruction to AI instructions (#12375) Co-authored-by: Claude --- .ai/instructions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ai/instructions.md b/.ai/instructions.md index 9d48f467cb..994d517f75 100644 --- a/.ai/instructions.md +++ b/.ai/instructions.md @@ -276,12 +276,12 @@ This document provides essential context for AI models interacting with this pro ## 7. Specific Instructions for AI Collaboration * **Contribution Workflow (Pull Request Process):** - 1. **Fork & Branch:** Create a new branch in your fork. + 1. **Fork & Branch:** Create a new branch based on the `dev` branch (always use `git checkout -b dev` to ensure you're branching from `dev`, not the currently checked out branch). 2. **Make Changes:** Adhere to all coding conventions and patterns. 3. **Test:** Create component tests for all supported platforms and run the full test suite locally. 4. **Lint:** Run `pre-commit` to ensure code is compliant. 5. **Commit:** Commit your changes. There is no strict format for commit messages. - 6. **Pull Request:** Submit a PR against the `dev` branch. The Pull Request title should have a prefix of the component being worked on (e.g., `[display] Fix bug`, `[abc123] Add new component`). Update documentation, examples, and add `CODEOWNERS` entries as needed. Pull requests should always be made with the PULL_REQUEST_TEMPLATE.md template filled out correctly. + 6. **Pull Request:** Submit a PR against the `dev` branch. The Pull Request title should have a prefix of the component being worked on (e.g., `[display] Fix bug`, `[abc123] Add new component`). Update documentation, examples, and add `CODEOWNERS` entries as needed. Pull requests should always be made using the `.github/PULL_REQUEST_TEMPLATE.md` template - fill out all sections completely without removing any parts of the template. * **Documentation Contributions:** * Documentation is hosted in the separate `esphome/esphome-docs` repository. From 27e031c25785c3ff745941e6a3d41df339f8f22e Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:43:47 -0500 Subject: [PATCH 0579/1145] [mqtt] Fix logger method case sensitivity error (#12379) Co-authored-by: Claude --- esphome/mqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/mqtt.py b/esphome/mqtt.py index 0d50edbc2c..042df12d67 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -192,7 +192,7 @@ def get_esphome_device_ip( data = json.loads(payload) if "name" not in data or data["name"] != dev_name: - _LOGGER.Warn("Wrong device answer") + _LOGGER.warning("Wrong device answer") return dev_ip = [] From e1afd65fae3c951dfc19121962d9788906f8099d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Dec 2025 15:59:27 +0100 Subject: [PATCH 0580/1145] [api] Store device info strings in flash on ESP8266 (#12173) --- esphome/components/api/api_connection.cpp | 50 +++++++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 18d80c46df..d63d6eb2c5 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -14,6 +14,9 @@ #include #include #include +#ifdef USE_ESP8266 +#include +#endif #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/entity_base.h" @@ -1471,35 +1474,64 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { resp.set_compilation_time(App.get_compilation_time_ref()); - // Compile-time StringRef constants for manufacturers + // Manufacturer string - define once, handle ESP8266 PROGMEM separately #if defined(USE_ESP8266) || defined(USE_ESP32) - static constexpr auto MANUFACTURER = StringRef::from_lit("Espressif"); +#define ESPHOME_MANUFACTURER "Espressif" #elif defined(USE_RP2040) - static constexpr auto MANUFACTURER = StringRef::from_lit("Raspberry Pi"); +#define ESPHOME_MANUFACTURER "Raspberry Pi" #elif defined(USE_BK72XX) - static constexpr auto MANUFACTURER = StringRef::from_lit("Beken"); +#define ESPHOME_MANUFACTURER "Beken" #elif defined(USE_LN882X) - static constexpr auto MANUFACTURER = StringRef::from_lit("Lightning"); +#define ESPHOME_MANUFACTURER "Lightning" #elif defined(USE_NRF52) - static constexpr auto MANUFACTURER = StringRef::from_lit("Nordic Semiconductor"); +#define ESPHOME_MANUFACTURER "Nordic Semiconductor" #elif defined(USE_RTL87XX) - static constexpr auto MANUFACTURER = StringRef::from_lit("Realtek"); +#define ESPHOME_MANUFACTURER "Realtek" #elif defined(USE_HOST) - static constexpr auto MANUFACTURER = StringRef::from_lit("Host"); +#define ESPHOME_MANUFACTURER "Host" #endif - resp.set_manufacturer(MANUFACTURER); +#ifdef USE_ESP8266 + // ESP8266 requires PROGMEM for flash storage, copy to stack for memcpy compatibility + static const char MANUFACTURER_PROGMEM[] PROGMEM = ESPHOME_MANUFACTURER; + char manufacturer_buf[sizeof(MANUFACTURER_PROGMEM)]; + memcpy_P(manufacturer_buf, MANUFACTURER_PROGMEM, sizeof(MANUFACTURER_PROGMEM)); + resp.set_manufacturer(StringRef(manufacturer_buf, sizeof(MANUFACTURER_PROGMEM) - 1)); +#else + static constexpr auto MANUFACTURER = StringRef::from_lit(ESPHOME_MANUFACTURER); + resp.set_manufacturer(MANUFACTURER); +#endif +#undef ESPHOME_MANUFACTURER + +#ifdef USE_ESP8266 + static const char MODEL_PROGMEM[] PROGMEM = ESPHOME_BOARD; + char model_buf[sizeof(MODEL_PROGMEM)]; + memcpy_P(model_buf, MODEL_PROGMEM, sizeof(MODEL_PROGMEM)); + resp.set_model(StringRef(model_buf, sizeof(MODEL_PROGMEM) - 1)); +#else static constexpr auto MODEL = StringRef::from_lit(ESPHOME_BOARD); resp.set_model(MODEL); +#endif #ifdef USE_DEEP_SLEEP resp.has_deep_sleep = deep_sleep::global_has_deep_sleep; #endif #ifdef ESPHOME_PROJECT_NAME +#ifdef USE_ESP8266 + static const char PROJECT_NAME_PROGMEM[] PROGMEM = ESPHOME_PROJECT_NAME; + static const char PROJECT_VERSION_PROGMEM[] PROGMEM = ESPHOME_PROJECT_VERSION; + char project_name_buf[sizeof(PROJECT_NAME_PROGMEM)]; + char project_version_buf[sizeof(PROJECT_VERSION_PROGMEM)]; + memcpy_P(project_name_buf, PROJECT_NAME_PROGMEM, sizeof(PROJECT_NAME_PROGMEM)); + memcpy_P(project_version_buf, PROJECT_VERSION_PROGMEM, sizeof(PROJECT_VERSION_PROGMEM)); + resp.set_project_name(StringRef(project_name_buf, sizeof(PROJECT_NAME_PROGMEM) - 1)); + resp.set_project_version(StringRef(project_version_buf, sizeof(PROJECT_VERSION_PROGMEM) - 1)); +#else static constexpr auto PROJECT_NAME = StringRef::from_lit(ESPHOME_PROJECT_NAME); static constexpr auto PROJECT_VERSION = StringRef::from_lit(ESPHOME_PROJECT_VERSION); resp.set_project_name(PROJECT_NAME); resp.set_project_version(PROJECT_VERSION); #endif +#endif #ifdef USE_WEBSERVER resp.webserver_port = USE_WEBSERVER_PORT; #endif From 88a2e75989822faad0914cf6dce6dc7cec68d910 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Tue, 9 Dec 2025 16:04:10 +0100 Subject: [PATCH 0581/1145] [packages] Add more information and deprecation deadline for "single package" includes (#12280) --- esphome/components/packages/__init__.py | 11 ++++++++++- tests/component_tests/packages/test_packages.py | 5 +---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 67fd2770e9..15ab11d6b0 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -91,7 +91,16 @@ def validate_source_shorthand(value): def deprecate_single_package(config): _LOGGER.warning( - "Including a single package under `packages:` is deprecated. Use a list instead." + """ + Including a single package under `packages:`, i.e., `packages: !include mypackage.yaml` is deprecated. + This method for including packages will go away in 2026.7.0 + Please use a list instead: + + packages: + - !include mypackage.yaml + + See https://github.com/esphome/esphome/pull/12116 + """ ) return config diff --git a/tests/component_tests/packages/test_packages.py b/tests/component_tests/packages/test_packages.py index ac4e211fe6..34760587df 100644 --- a/tests/component_tests/packages/test_packages.py +++ b/tests/component_tests/packages/test_packages.py @@ -176,10 +176,7 @@ def test_single_package( assert actual == expected - assert ( - "Including a single package under `packages:` is deprecated. Use a list instead." - in caplog.text - ) + assert "This method for including packages will go away in 2026.7.0" in caplog.text def test_package_append(basic_wifi, basic_esphome): From 443f9c3f57ff93b223046043ae0162c17dfae15c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Dec 2025 16:10:43 +0100 Subject: [PATCH 0582/1145] [api] Use StringRef for ActionResponse error message to avoid copy (#12240) --- .../components/api/homeassistant_service.h | 16 +++++--- .../fixtures/api_homeassistant.yaml | 22 +++++++++++ tests/integration/test_api_homeassistant.py | 39 ++++++++++++++++++- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index d00e9e6257..397520fa2e 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -12,6 +12,7 @@ #endif #include "esphome/core/automation.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" namespace esphome::api { @@ -55,14 +56,16 @@ template class TemplatableKeyValuePair { #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES // Represents the response data from a Home Assistant action +// Note: This class holds a StringRef to the error_message from the protobuf message. +// The protobuf message must outlive the ActionResponse (which is guaranteed since +// the callback is invoked synchronously while the message is on the stack). class ActionResponse { public: - ActionResponse(bool success, std::string error_message = "") - : success_(success), error_message_(std::move(error_message)) {} + ActionResponse(bool success, const std::string &error_message) : success_(success), error_message_(error_message) {} #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - ActionResponse(bool success, std::string error_message, const uint8_t *data, size_t data_len) - : success_(success), error_message_(std::move(error_message)) { + ActionResponse(bool success, const std::string &error_message, const uint8_t *data, size_t data_len) + : success_(success), error_message_(error_message) { if (data == nullptr || data_len == 0) return; this->json_document_ = json::parse_json(data, data_len); @@ -70,7 +73,8 @@ class ActionResponse { #endif bool is_success() const { return this->success_; } - const std::string &get_error_message() const { return this->error_message_; } + // Returns reference to error message - can be implicitly converted to std::string if needed + const StringRef &get_error_message() const { return this->error_message_; } #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON // Get data as parsed JSON object (const version returns read-only view) @@ -79,7 +83,7 @@ class ActionResponse { protected: bool success_; - std::string error_message_; + StringRef error_message_; #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON JsonDocument json_document_; #endif diff --git a/tests/integration/fixtures/api_homeassistant.yaml b/tests/integration/fixtures/api_homeassistant.yaml index ce8628977a..8fe23b9a19 100644 --- a/tests/integration/fixtures/api_homeassistant.yaml +++ b/tests/integration/fixtures/api_homeassistant.yaml @@ -17,6 +17,7 @@ api: - button.press: test_all_empty_service - button.press: test_rapid_service_calls - button.press: test_read_ha_states + - button.press: test_action_response_error - number.set: id: ha_number value: 42.5 @@ -309,3 +310,24 @@ button: } else { ESP_LOGI("test", "HA Empty State has no value (expected)"); } + + # Test 9: Action response error handling (tests StringRef error message) + - platform: template + name: "Test Action Response Error" + id: test_action_response_error + on_press: + - logger.log: "Testing action response error handling" + - homeassistant.action: + action: nonexistent.action_for_error_test + data: + test_field: "test_value" + on_error: + - lambda: |- + // This tests that StringRef error message works correctly + // The error variable is std::string (converted from StringRef) + ESP_LOGI("test", "Action error received: %s", error.c_str()); + - logger.log: + format: "Action failed with error message length: %d" + args: ['error.size()'] + on_success: + - logger.log: "Action succeeded unexpectedly" diff --git a/tests/integration/test_api_homeassistant.py b/tests/integration/test_api_homeassistant.py index 98901fb3f9..1343691f5f 100644 --- a/tests/integration/test_api_homeassistant.py +++ b/tests/integration/test_api_homeassistant.py @@ -81,8 +81,15 @@ async def test_api_homeassistant( "input_number.set_value": loop.create_future(), # ha_number_service_call "switch.turn_on": loop.create_future(), # ha_switch_on_service_call "switch.turn_off": loop.create_future(), # ha_switch_off_service_call + "nonexistent.action_for_error_test": loop.create_future(), # error_test_call } + # Future for error message test + action_error_received_future = loop.create_future() + + # Store client reference for use in callback + client_ref: list = [] # Use list to allow modification in nested function + def on_service_call(service_call: HomeassistantServiceCall) -> None: """Capture HomeAssistant service calls.""" ha_service_calls.append(service_call) @@ -93,6 +100,17 @@ async def test_api_homeassistant( if not future.done(): future.set_result(service_call) + # Immediately respond to the error test call so the test can proceed + # This needs to happen synchronously so ESPHome receives the response + # before logging "=== All tests completed ===" + if service_call.service == "nonexistent.action_for_error_test" and client_ref: + test_error_message = "Test error: action not found" + client_ref[0].send_homeassistant_action_response( + call_id=service_call.call_id, + success=False, + error_message=test_error_message, + ) + def check_output(line: str) -> None: """Check log output for expected messages.""" log_lines.append(line) @@ -131,7 +149,12 @@ async def test_api_homeassistant( if match: ha_number_future.set_result(match.group(1)) - elif not tests_complete_future.done() and tests_complete_pattern.search(line): + # Check for action error message (tests StringRef -> std::string conversion) + # Use separate if (not elif) since this can come after tests_complete + if not action_error_received_future.done() and "Action error received:" in line: + action_error_received_future.set_result(line) + + if not tests_complete_future.done() and tests_complete_pattern.search(line): tests_complete_future.set_result(True) # Run with log monitoring @@ -144,6 +167,9 @@ async def test_api_homeassistant( assert device_info is not None assert device_info.name == "test-ha-api" + # Store client reference for use in service call callback + client_ref.append(client) + # Subscribe to HomeAssistant service calls client.subscribe_service_calls(on_service_call) @@ -292,6 +318,17 @@ async def test_api_homeassistant( assert switch_off_call.service == "switch.turn_off" assert switch_off_call.data["entity_id"] == "switch.test_switch" + # 9. Action response error test (tests StringRef error message) + # The error response is sent automatically in on_service_call callback + # Wait for the error to be logged (proves StringRef -> std::string works) + error_log_line = await asyncio.wait_for( + action_error_received_future, timeout=2.0 + ) + test_error_message = "Test error: action not found" + assert test_error_message in error_log_line, ( + f"Expected error message '{test_error_message}' not found in: {error_log_line}" + ) + except TimeoutError as e: # Show recent log lines for debugging recent_logs = "\n".join(log_lines[-20:]) From 72c74bc0b303d491829e1d12d2e2a0e338a0dd24 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Dec 2025 16:26:11 +0100 Subject: [PATCH 0583/1145] [api] Store Home Assistant state subscriptions in flash instead of heap (#12008) --- esphome/components/api/api_connection.cpp | 11 +++- esphome/components/api/api_server.cpp | 57 ++++++++++++++----- esphome/components/api/api_server.h | 22 ++++++- .../homeassistant_binary_sensor.cpp | 13 ++--- .../homeassistant_binary_sensor.h | 8 +-- .../number/homeassistant_number.cpp | 23 ++++---- .../number/homeassistant_number.h | 4 +- .../sensor/homeassistant_sensor.cpp | 15 +++-- .../sensor/homeassistant_sensor.h | 8 +-- .../switch/homeassistant_switch.cpp | 6 +- .../switch/homeassistant_switch.h | 4 +- .../text_sensor/homeassistant_text_sensor.cpp | 13 ++--- .../text_sensor/homeassistant_text_sensor.h | 8 +-- .../fixtures/api_custom_services.yaml | 1 + .../custom_api_device_component.cpp | 9 +++ .../custom_api_device_component.h | 3 + tests/integration/test_api_custom_services.py | 11 ++++ 17 files changed, 144 insertions(+), 72 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index d63d6eb2c5..4b10610281 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1580,7 +1580,12 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { #ifdef USE_API_HOMEASSISTANT_STATES void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) { for (auto &it : this->parent_->get_state_subs()) { - if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) { + // Compare entity_id and attribute with message fields + bool entity_match = (strcmp(it.entity_id, msg.entity_id.c_str()) == 0); + bool attribute_match = (it.attribute != nullptr && strcmp(it.attribute, msg.attribute.c_str()) == 0) || + (it.attribute == nullptr && msg.attribute.empty()); + + if (entity_match && attribute_match) { it.callback(msg.state); } } @@ -1959,8 +1964,8 @@ void APIConnection::process_state_subscriptions_() { SubscribeHomeAssistantStateResponse resp; resp.set_entity_id(StringRef(it.entity_id)); - // Avoid string copy by directly using the optional's value if it exists - resp.set_attribute(it.attribute.has_value() ? StringRef(it.attribute.value()) : StringRef("")); + // Avoid string copy by using the const char* pointer if it exists + resp.set_attribute(it.attribute != nullptr ? StringRef(it.attribute) : StringRef("")); resp.once = it.once; if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) { diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 1921ca95d4..b1a5ee5d57 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -419,25 +419,56 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std #endif // USE_API_HOMEASSISTANT_SERVICES #ifdef USE_API_HOMEASSISTANT_STATES +// Helper to add subscription (reduces duplication) +void APIServer::add_state_subscription_(const char *entity_id, const char *attribute, + std::function f, bool once) { + this->state_subs_.push_back(HomeAssistantStateSubscription{ + .entity_id = entity_id, .attribute = attribute, .callback = std::move(f), .once = once, + // entity_id_dynamic_storage and attribute_dynamic_storage remain nullptr (no heap allocation) + }); +} + +// Helper to add subscription with heap-allocated strings (reduces duplication) +void APIServer::add_state_subscription_(std::string entity_id, optional attribute, + std::function f, bool once) { + HomeAssistantStateSubscription sub; + // Allocate heap storage for the strings + sub.entity_id_dynamic_storage = std::make_unique(std::move(entity_id)); + sub.entity_id = sub.entity_id_dynamic_storage->c_str(); + + if (attribute.has_value()) { + sub.attribute_dynamic_storage = std::make_unique(std::move(attribute.value())); + sub.attribute = sub.attribute_dynamic_storage->c_str(); + } else { + sub.attribute = nullptr; + } + + sub.callback = std::move(f); + sub.once = once; + this->state_subs_.push_back(std::move(sub)); +} + +// New const char* overload (for internal components - zero allocation) +void APIServer::subscribe_home_assistant_state(const char *entity_id, const char *attribute, + std::function f) { + this->add_state_subscription_(entity_id, attribute, std::move(f), false); +} + +void APIServer::get_home_assistant_state(const char *entity_id, const char *attribute, + std::function f) { + this->add_state_subscription_(entity_id, attribute, std::move(f), true); +} + +// Existing std::string overload (for custom_api_device.h - heap allocation) void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, std::function f) { - this->state_subs_.push_back(HomeAssistantStateSubscription{ - .entity_id = std::move(entity_id), - .attribute = std::move(attribute), - .callback = std::move(f), - .once = false, - }); + this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false); } void APIServer::get_home_assistant_state(std::string entity_id, optional attribute, std::function f) { - this->state_subs_.push_back(HomeAssistantStateSubscription{ - .entity_id = std::move(entity_id), - .attribute = std::move(attribute), - .callback = std::move(f), - .once = true, - }); -}; + this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true); +} const std::vector &APIServer::get_state_subs() const { return this->state_subs_; diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 2175d047eb..ad7d8bf63d 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -190,16 +190,27 @@ class APIServer : public Component, #ifdef USE_API_HOMEASSISTANT_STATES struct HomeAssistantStateSubscription { - std::string entity_id; - optional attribute; + const char *entity_id; // Pointer to flash (internal) or heap (external) + const char *attribute; // Pointer to flash or nullptr (nullptr means no attribute) std::function callback; bool once; + + // Dynamic storage for external components using std::string API (custom_api_device.h) + // These are only allocated when using the std::string overload (nullptr for const char* overload) + std::unique_ptr entity_id_dynamic_storage; + std::unique_ptr attribute_dynamic_storage; }; + // New const char* overload (for internal components - zero allocation) + void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function f); + void get_home_assistant_state(const char *entity_id, const char *attribute, std::function f); + + // Existing std::string overload (for custom_api_device.h - heap allocation) void subscribe_home_assistant_state(std::string entity_id, optional attribute, std::function f); void get_home_assistant_state(std::string entity_id, optional attribute, std::function f); + const std::vector &get_state_subs() const; #endif #ifdef USE_API_USER_DEFINED_ACTIONS @@ -220,6 +231,13 @@ class APIServer : public Component, bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg, const psk_t &active_psk, bool make_active); #endif // USE_API_NOISE +#ifdef USE_API_HOMEASSISTANT_STATES + // Helper methods to reduce code duplication + void add_state_subscription_(const char *entity_id, const char *attribute, std::function f, + bool once); + void add_state_subscription_(std::string entity_id, optional attribute, + std::function f, bool once); +#endif // USE_API_HOMEASSISTANT_STATES // Pointers and pointer-like types first (4 bytes each) std::unique_ptr socket_ = nullptr; #ifdef USE_API_CLIENT_CONNECTED_TRIGGER diff --git a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp index a36fcb204a..5652e7d603 100644 --- a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp +++ b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp @@ -19,11 +19,10 @@ void HomeassistantBinarySensor::setup() { case PARSE_ON: case PARSE_OFF: bool new_state = val == PARSE_ON; - if (this->attribute_.has_value()) { - ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_.c_str(), - this->attribute_.value().c_str(), ONOFF(new_state)); + if (this->attribute_ != nullptr) { + ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_, this->attribute_, ONOFF(new_state)); } else { - ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), ONOFF(new_state)); + ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, ONOFF(new_state)); } if (this->initial_) { this->publish_initial_state(new_state); @@ -37,9 +36,9 @@ void HomeassistantBinarySensor::setup() { } void HomeassistantBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Homeassistant Binary Sensor", this); - ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str()); - if (this->attribute_.has_value()) { - ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_.value().c_str()); + ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_); + if (this->attribute_ != nullptr) { + ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_); } } float HomeassistantBinarySensor::get_setup_priority() const { return setup_priority::AFTER_WIFI; } diff --git a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.h b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.h index 7026496295..9aec61a370 100644 --- a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.h +++ b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.h @@ -8,15 +8,15 @@ namespace homeassistant { class HomeassistantBinarySensor : public binary_sensor::BinarySensor, public Component { public: - void set_entity_id(const std::string &entity_id) { entity_id_ = entity_id; } - void set_attribute(const std::string &attribute) { attribute_ = attribute; } + void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; } + void set_attribute(const char *attribute) { this->attribute_ = attribute; } void setup() override; void dump_config() override; float get_setup_priority() const override; protected: - std::string entity_id_; - optional attribute_; + const char *entity_id_{nullptr}; + const char *attribute_{nullptr}; bool initial_{true}; }; diff --git a/esphome/components/homeassistant/number/homeassistant_number.cpp b/esphome/components/homeassistant/number/homeassistant_number.cpp index 9963f3431d..1ca90180eb 100644 --- a/esphome/components/homeassistant/number/homeassistant_number.cpp +++ b/esphome/components/homeassistant/number/homeassistant_number.cpp @@ -12,21 +12,21 @@ static const char *const TAG = "homeassistant.number"; void HomeassistantNumber::state_changed_(const std::string &state) { auto number_value = parse_number(state); if (!number_value.has_value()) { - ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_.c_str(), state.c_str()); + ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str()); this->publish_state(NAN); return; } if (this->state == number_value.value()) { return; } - ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), state.c_str()); + ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, state.c_str()); this->publish_state(number_value.value()); } void HomeassistantNumber::min_retrieved_(const std::string &min) { auto min_value = parse_number(min); if (!min_value.has_value()) { - ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_.c_str(), min.c_str()); + ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_, min.c_str()); return; } ESP_LOGD(TAG, "'%s': Min retrieved: %s", get_name().c_str(), min.c_str()); @@ -36,7 +36,7 @@ void HomeassistantNumber::min_retrieved_(const std::string &min) { void HomeassistantNumber::max_retrieved_(const std::string &max) { auto max_value = parse_number(max); if (!max_value.has_value()) { - ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_.c_str(), max.c_str()); + ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_, max.c_str()); return; } ESP_LOGD(TAG, "'%s': Max retrieved: %s", get_name().c_str(), max.c_str()); @@ -46,7 +46,7 @@ void HomeassistantNumber::max_retrieved_(const std::string &max) { void HomeassistantNumber::step_retrieved_(const std::string &step) { auto step_value = parse_number(step); if (!step_value.has_value()) { - ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_.c_str(), step.c_str()); + ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_, step.c_str()); return; } ESP_LOGD(TAG, "'%s': Step Retrieved %s", get_name().c_str(), step.c_str()); @@ -55,22 +55,19 @@ void HomeassistantNumber::step_retrieved_(const std::string &step) { void HomeassistantNumber::setup() { api::global_api_server->subscribe_home_assistant_state( - this->entity_id_, nullopt, std::bind(&HomeassistantNumber::state_changed_, this, std::placeholders::_1)); + this->entity_id_, nullptr, std::bind(&HomeassistantNumber::state_changed_, this, std::placeholders::_1)); api::global_api_server->get_home_assistant_state( - this->entity_id_, optional("min"), - std::bind(&HomeassistantNumber::min_retrieved_, this, std::placeholders::_1)); + this->entity_id_, "min", std::bind(&HomeassistantNumber::min_retrieved_, this, std::placeholders::_1)); api::global_api_server->get_home_assistant_state( - this->entity_id_, optional("max"), - std::bind(&HomeassistantNumber::max_retrieved_, this, std::placeholders::_1)); + this->entity_id_, "max", std::bind(&HomeassistantNumber::max_retrieved_, this, std::placeholders::_1)); api::global_api_server->get_home_assistant_state( - this->entity_id_, optional("step"), - std::bind(&HomeassistantNumber::step_retrieved_, this, std::placeholders::_1)); + this->entity_id_, "step", std::bind(&HomeassistantNumber::step_retrieved_, this, std::placeholders::_1)); } void HomeassistantNumber::dump_config() { LOG_NUMBER("", "Homeassistant Number", this); - ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str()); + ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_); } float HomeassistantNumber::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } diff --git a/esphome/components/homeassistant/number/homeassistant_number.h b/esphome/components/homeassistant/number/homeassistant_number.h index 0860b4e91c..0dffc108cb 100644 --- a/esphome/components/homeassistant/number/homeassistant_number.h +++ b/esphome/components/homeassistant/number/homeassistant_number.h @@ -11,7 +11,7 @@ namespace homeassistant { class HomeassistantNumber : public number::Number, public Component { public: - void set_entity_id(const std::string &entity_id) { this->entity_id_ = entity_id; } + void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; } void setup() override; void dump_config() override; @@ -25,7 +25,7 @@ class HomeassistantNumber : public number::Number, public Component { void control(float value) override; - std::string entity_id_; + const char *entity_id_{nullptr}; }; } // namespace homeassistant } // namespace esphome diff --git a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp index 35e660f7c1..78da47f9a1 100644 --- a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp +++ b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp @@ -12,25 +12,24 @@ void HomeassistantSensor::setup() { this->entity_id_, this->attribute_, [this](const std::string &state) { auto val = parse_number(state); if (!val.has_value()) { - ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_.c_str(), state.c_str()); + ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str()); this->publish_state(NAN); return; } - if (this->attribute_.has_value()) { - ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_.c_str(), - this->attribute_.value().c_str(), *val); + if (this->attribute_ != nullptr) { + ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_, this->attribute_, *val); } else { - ESP_LOGD(TAG, "'%s': Got state %.2f", this->entity_id_.c_str(), *val); + ESP_LOGD(TAG, "'%s': Got state %.2f", this->entity_id_, *val); } this->publish_state(*val); }); } void HomeassistantSensor::dump_config() { LOG_SENSOR("", "Homeassistant Sensor", this); - ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str()); - if (this->attribute_.has_value()) { - ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_.value().c_str()); + ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_); + if (this->attribute_ != nullptr) { + ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_); } } float HomeassistantSensor::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } diff --git a/esphome/components/homeassistant/sensor/homeassistant_sensor.h b/esphome/components/homeassistant/sensor/homeassistant_sensor.h index 53b288d7d4..d89fc069ff 100644 --- a/esphome/components/homeassistant/sensor/homeassistant_sensor.h +++ b/esphome/components/homeassistant/sensor/homeassistant_sensor.h @@ -8,15 +8,15 @@ namespace homeassistant { class HomeassistantSensor : public sensor::Sensor, public Component { public: - void set_entity_id(const std::string &entity_id) { entity_id_ = entity_id; } - void set_attribute(const std::string &attribute) { attribute_ = attribute; } + void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; } + void set_attribute(const char *attribute) { this->attribute_ = attribute; } void setup() override; void dump_config() override; float get_setup_priority() const override; protected: - std::string entity_id_; - optional attribute_; + const char *entity_id_{nullptr}; + const char *attribute_{nullptr}; }; } // namespace homeassistant diff --git a/esphome/components/homeassistant/switch/homeassistant_switch.cpp b/esphome/components/homeassistant/switch/homeassistant_switch.cpp index 27d3705fc2..c4abf2295d 100644 --- a/esphome/components/homeassistant/switch/homeassistant_switch.cpp +++ b/esphome/components/homeassistant/switch/homeassistant_switch.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "homeassistant.switch"; using namespace esphome::switch_; void HomeassistantSwitch::setup() { - api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullopt, [this](const std::string &state) { + api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr, [this](const std::string &state) { auto val = parse_on_off(state.c_str()); switch (val) { case PARSE_NONE: @@ -20,7 +20,7 @@ void HomeassistantSwitch::setup() { case PARSE_ON: case PARSE_OFF: bool new_state = val == PARSE_ON; - ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), ONOFF(new_state)); + ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, ONOFF(new_state)); this->publish_state(new_state); break; } @@ -29,7 +29,7 @@ void HomeassistantSwitch::setup() { void HomeassistantSwitch::dump_config() { LOG_SWITCH("", "Homeassistant Switch", this); - ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str()); + ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_); } float HomeassistantSwitch::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } diff --git a/esphome/components/homeassistant/switch/homeassistant_switch.h b/esphome/components/homeassistant/switch/homeassistant_switch.h index a4da257960..c180b7f98a 100644 --- a/esphome/components/homeassistant/switch/homeassistant_switch.h +++ b/esphome/components/homeassistant/switch/homeassistant_switch.h @@ -8,14 +8,14 @@ namespace homeassistant { class HomeassistantSwitch : public switch_::Switch, public Component { public: - void set_entity_id(const std::string &entity_id) { this->entity_id_ = entity_id; } + void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; } void setup() override; void dump_config() override; float get_setup_priority() const override; protected: void write_state(bool state) override; - std::string entity_id_; + const char *entity_id_{nullptr}; }; } // namespace homeassistant diff --git a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp index 9b933fbbbe..6154330a4e 100644 --- a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp +++ b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp @@ -10,20 +10,19 @@ static const char *const TAG = "homeassistant.text_sensor"; void HomeassistantTextSensor::setup() { api::global_api_server->subscribe_home_assistant_state( this->entity_id_, this->attribute_, [this](const std::string &state) { - if (this->attribute_.has_value()) { - ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_.c_str(), - this->attribute_.value().c_str(), state.c_str()); + if (this->attribute_ != nullptr) { + ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_, this->attribute_, state.c_str()); } else { - ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_.c_str(), state.c_str()); + ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str()); } this->publish_state(state); }); } void HomeassistantTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Homeassistant Text Sensor", this); - ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str()); - if (this->attribute_.has_value()) { - ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_.value().c_str()); + ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_); + if (this->attribute_ != nullptr) { + ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_); } } float HomeassistantTextSensor::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } diff --git a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.h b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.h index ce6b2c2c3f..4d66c65a17 100644 --- a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.h +++ b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.h @@ -8,15 +8,15 @@ namespace homeassistant { class HomeassistantTextSensor : public text_sensor::TextSensor, public Component { public: - void set_entity_id(const std::string &entity_id) { entity_id_ = entity_id; } - void set_attribute(const std::string &attribute) { attribute_ = attribute; } + void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; } + void set_attribute(const char *attribute) { this->attribute_ = attribute; } void setup() override; void dump_config() override; float get_setup_priority() const override; protected: - std::string entity_id_; - optional attribute_; + const char *entity_id_{nullptr}; + const char *attribute_{nullptr}; }; } // namespace homeassistant diff --git a/tests/integration/fixtures/api_custom_services.yaml b/tests/integration/fixtures/api_custom_services.yaml index a597c74126..827bee93a6 100644 --- a/tests/integration/fixtures/api_custom_services.yaml +++ b/tests/integration/fixtures/api_custom_services.yaml @@ -5,6 +5,7 @@ host: # This is required for CustomAPIDevice to work api: custom_services: true + homeassistant_states: true # Also test that YAML services still work actions: - action: test_yaml_service diff --git a/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.cpp b/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.cpp index c8581b3d2f..01bc7dcd98 100644 --- a/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.cpp +++ b/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.cpp @@ -17,6 +17,10 @@ void CustomAPIDeviceComponent::setup() { // Test array types register_service(&CustomAPIDeviceComponent::on_service_with_arrays, "custom_service_with_arrays", {"bool_array", "int_array", "float_array", "string_array"}); + + // Test Home Assistant state subscription using std::string API (custom_api_device.h) + // This tests the backward compatibility of the std::string overloads + subscribe_homeassistant_state(&CustomAPIDeviceComponent::on_ha_state_changed, std::string("sensor.custom_test")); } void CustomAPIDeviceComponent::on_test_service() { ESP_LOGI(TAG, "Custom test service called!"); } @@ -48,6 +52,11 @@ void CustomAPIDeviceComponent::on_service_with_arrays(std::vector bool_arr } } +void CustomAPIDeviceComponent::on_ha_state_changed(std::string entity_id, std::string state) { + ESP_LOGI(TAG, "Home Assistant state changed for %s: %s", entity_id.c_str(), state.c_str()); + ESP_LOGI(TAG, "This subscription uses std::string API for backward compatibility"); +} + } // namespace custom_api_device_component } // namespace esphome #endif // USE_API diff --git a/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.h b/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.h index 92960746d9..0720b9e7de 100644 --- a/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.h +++ b/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.h @@ -22,6 +22,9 @@ class CustomAPIDeviceComponent : public Component, public CustomAPIDevice { void on_service_with_arrays(std::vector bool_array, std::vector int_array, std::vector float_array, std::vector string_array); + + // Test Home Assistant state subscription with std::string API + void on_ha_state_changed(std::string entity_id, std::string state); }; } // namespace custom_api_device_component diff --git a/tests/integration/test_api_custom_services.py b/tests/integration/test_api_custom_services.py index cd33b5a1fc..acf69bf092 100644 --- a/tests/integration/test_api_custom_services.py +++ b/tests/integration/test_api_custom_services.py @@ -38,6 +38,7 @@ async def test_api_custom_services( custom_service_future = loop.create_future() custom_args_future = loop.create_future() custom_arrays_future = loop.create_future() + ha_state_future = loop.create_future() # Patterns to match in logs yaml_service_pattern = re.compile(r"YAML service called") @@ -50,6 +51,9 @@ async def test_api_custom_services( custom_arrays_pattern = re.compile( r"Array service called with 2 bools, 3 ints, 2 floats, 2 strings" ) + ha_state_pattern = re.compile( + r"This subscription uses std::string API for backward compatibility" + ) def check_output(line: str) -> None: """Check log output for expected messages.""" @@ -65,6 +69,8 @@ async def test_api_custom_services( custom_args_future.set_result(True) elif not custom_arrays_future.done() and custom_arrays_pattern.search(line): custom_arrays_future.set_result(True) + elif not ha_state_future.done() and ha_state_pattern.search(line): + ha_state_future.set_result(True) # Run with log monitoring async with ( @@ -198,3 +204,8 @@ async def test_api_custom_services( }, ) await asyncio.wait_for(custom_arrays_future, timeout=5.0) + + # Test Home Assistant state subscription (std::string API backward compatibility) + # This verifies that custom_api_device.h can still use std::string overloads + client.send_home_assistant_state("sensor.custom_test", "", "42.5") + await asyncio.wait_for(ha_state_future, timeout=5.0) From e96c37965c6118a679da8198e57955b9c0450854 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Dec 2025 16:26:27 +0100 Subject: [PATCH 0584/1145] [wifi] Fix LibreTiny spurious disconnect events aborting connections (#12357) --- .../wifi/wifi_component_libretiny.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index d6bc8e53da..4fd64bdfa3 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -312,6 +312,23 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ char buf[33]; memcpy(buf, it.ssid, it.ssid_len); buf[it.ssid_len] = '\0'; + + // LibreTiny can send spurious disconnect events with empty ssid/bssid during connection. + // These are typically "Association Leave" events that don't indicate actual failures: + // [W][wifi_lt]: Disconnected ssid='' bssid=00:00:00:00:00:00 reason='Association Leave' + // [W][wifi_lt]: Disconnected ssid='' bssid=00:00:00:00:00:00 reason='Association Leave' + // [V][wifi_lt]: Connected ssid='WIFI' bssid=... channel=3, authmode=WPA2 PSK + // Without this check, the spurious events set s_sta_connecting=false, causing + // wifi_sta_connect_status_() to return IDLE. The main loop then sees + // "Unknown connection status 0" (wifi_component.cpp check_connecting_finished) + // and calls retry_connect(), aborting a connection that may succeed moments later. + // Real connection failures will have ssid/bssid populated, or we'll hit the 30s timeout. + if (it.ssid_len == 0 && s_sta_connecting) { + ESP_LOGV(TAG, "Ignoring disconnect event with empty ssid while connecting (reason=%s)", + get_disconnect_reason_str(it.reason)); + break; + } + if (it.reason == WIFI_REASON_NO_AP_FOUND) { ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); } else { From 2c0f4d8f806ca17069112be5098dedf56805be5a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Dec 2025 16:35:14 +0100 Subject: [PATCH 0585/1145] [api] Reduce heap usage for Home Assistant service call string storage (#12151) --- .../components/api/homeassistant_service.h | 32 ++++++++++------ esphome/core/automation.h | 37 ++++++++++++++----- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 397520fa2e..2da6e15362 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -17,6 +17,12 @@ namespace esphome::api { template class TemplatableStringValue : public TemplatableValue { + // Verify that const char* uses the base class STATIC_STRING optimization (no heap allocation) + // rather than being wrapped in a lambda. The base class constructor for const char* is more + // specialized than the templated constructor here, so it should be selected. + static_assert(std::is_constructible_v, const char *>, + "Base class must have const char* constructor for STATIC_STRING optimization"); + private: // Helper to convert value to string - handles the case where value is already a string template static std::string value_to_string(T &&val) { return to_string(std::forward(val)); } @@ -47,10 +53,10 @@ template class TemplatableKeyValuePair { // Keys are always string literals from YAML dictionary keys (e.g., "code", "event") // and never templatable values or lambdas. Only the value parameter can be a lambda/template. - // Using pass-by-value with std::move allows optimal performance for both lvalues and rvalues. - template TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {} + // Using const char* avoids std::string heap allocation - keys remain in flash. + template TemplatableKeyValuePair(const char *key, T value) : key(key), value(value) {} - std::string key; + const char *key{nullptr}; TemplatableStringValue value; }; @@ -109,14 +115,15 @@ template class HomeAssistantServiceCallAction : public Action void add_data(K &&key, V &&value) { - this->add_kv_(this->data_, std::forward(key), std::forward(value)); + // Using const char* for keys avoids std::string heap allocation - keys remain in flash. + template void add_data(const char *key, V &&value) { + this->add_kv_(this->data_, key, std::forward(value)); } - template void add_data_template(K &&key, V &&value) { - this->add_kv_(this->data_template_, std::forward(key), std::forward(value)); + template void add_data_template(const char *key, V &&value) { + this->add_kv_(this->data_template_, key, std::forward(value)); } - template void add_variable(K &&key, V &&value) { - this->add_kv_(this->variables_, std::forward(key), std::forward(value)); + template void add_variable(const char *key, V &&value) { + this->add_kv_(this->variables_, key, std::forward(value)); } #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES @@ -189,10 +196,11 @@ template class HomeAssistantServiceCallAction : public Action void add_kv_(FixedVector> &vec, K &&key, V &&value) { + // Helper to add key-value pairs to FixedVectors + // Keys are always string literals (const char*), values can be lambdas/templates + template void add_kv_(FixedVector> &vec, const char *key, V &&value) { auto &kv = vec.emplace_back(); - kv.key = std::forward(key); + kv.key = key; kv.value = std::forward(value); } diff --git a/esphome/core/automation.h b/esphome/core/automation.h index dacadd35e8..61d2944acf 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -45,6 +45,12 @@ template class TemplatableValue { public: TemplatableValue() : type_(NONE) {} + // For const char* when T is std::string: store pointer directly, no heap allocation + // String remains in flash and is only converted to std::string when value() is called + TemplatableValue(const char *str) requires std::same_as : type_(STATIC_STRING) { + this->static_str_ = str; + } + template TemplatableValue(F value) requires(!std::invocable) : type_(VALUE) { new (&this->value_) T(std::move(value)); } @@ -64,24 +70,28 @@ template class TemplatableValue { // Copy constructor TemplatableValue(const TemplatableValue &other) : type_(other.type_) { - if (type_ == VALUE) { + if (this->type_ == VALUE) { new (&this->value_) T(other.value_); - } else if (type_ == LAMBDA) { + } else if (this->type_ == LAMBDA) { this->f_ = new std::function(*other.f_); - } else if (type_ == STATELESS_LAMBDA) { + } else if (this->type_ == STATELESS_LAMBDA) { this->stateless_f_ = other.stateless_f_; + } else if (this->type_ == STATIC_STRING) { + this->static_str_ = other.static_str_; } } // Move constructor TemplatableValue(TemplatableValue &&other) noexcept : type_(other.type_) { - if (type_ == VALUE) { + if (this->type_ == VALUE) { new (&this->value_) T(std::move(other.value_)); - } else if (type_ == LAMBDA) { + } else if (this->type_ == LAMBDA) { this->f_ = other.f_; other.f_ = nullptr; - } else if (type_ == STATELESS_LAMBDA) { + } else if (this->type_ == STATELESS_LAMBDA) { this->stateless_f_ = other.stateless_f_; + } else if (this->type_ == STATIC_STRING) { + this->static_str_ = other.static_str_; } other.type_ = NONE; } @@ -104,12 +114,12 @@ template class TemplatableValue { } ~TemplatableValue() { - if (type_ == VALUE) { + if (this->type_ == VALUE) { this->value_.~T(); - } else if (type_ == LAMBDA) { + } else if (this->type_ == LAMBDA) { delete this->f_; } - // STATELESS_LAMBDA/NONE: no cleanup needed (function pointer or empty, not heap-allocated) + // STATELESS_LAMBDA/STATIC_STRING/NONE: no cleanup needed (pointers, not heap-allocated) } bool has_value() { return this->type_ != NONE; } @@ -122,6 +132,13 @@ template class TemplatableValue { return (*this->f_)(x...); // std::function call case VALUE: return this->value_; + case STATIC_STRING: + // if constexpr required: code must compile for all T, but STATIC_STRING + // can only be set when T is std::string (enforced by constructor constraint) + if constexpr (std::same_as) { + return std::string(this->static_str_); + } + __builtin_unreachable(); case NONE: default: return T{}; @@ -148,12 +165,14 @@ template class TemplatableValue { VALUE, LAMBDA, STATELESS_LAMBDA, + STATIC_STRING, // For const char* when T is std::string - avoids heap allocation } type_; union { T value_; std::function *f_; T (*stateless_f_)(X...); + const char *static_str_; // For STATIC_STRING type }; }; From fb7800a22f489bbdc23c016b4ee71491e1d3f9a2 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sat, 6 Dec 2025 09:59:29 +1100 Subject: [PATCH 0586/1145] [binary_sensor] Fix reporting of 'unknown' (#12296) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: J. Nick Koston --- .../binary_sensor/binary_sensor.cpp | 13 +- .../components/binary_sensor/binary_sensor.h | 2 + esphome/core/entity_base.h | 16 +- tests/integration/README.md | 24 ++- .../binary_sensor_invalidate_state.yaml | 39 +++++ tests/integration/state_utils.py | 63 ++++++++ .../test_binary_sensor_invalidate_state.py | 138 ++++++++++++++++++ 7 files changed, 283 insertions(+), 12 deletions(-) create mode 100644 tests/integration/fixtures/binary_sensor_invalidate_state.yaml create mode 100644 tests/integration/test_binary_sensor_invalidate_state.py diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 220ed685db..97dfbb6a24 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -36,13 +36,20 @@ void BinarySensor::publish_initial_state(bool new_state) { void BinarySensor::send_state_internal(bool new_state) { // copy the new state to the visible property for backwards compatibility, before any callbacks this->state = new_state; - // Note that set_state_ de-dups and will only trigger callbacks if the state has actually changed - if (this->set_state_(new_state)) { - ESP_LOGD(TAG, "'%s': New state is %s", this->get_name().c_str(), ONOFF(new_state)); + // Note that set_new_state_ de-dups and will only trigger callbacks if the state has actually changed + this->set_new_state(new_state); +} + +bool BinarySensor::set_new_state(const optional &new_state) { + if (StatefulEntityBase::set_new_state(new_state)) { + // weirdly, this file could be compiled even without USE_BINARY_SENSOR defined #if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_binary_sensor_update(this); #endif + ESP_LOGD(TAG, "'%s': %s", this->get_name().c_str(), ONOFFMAYBE(new_state)); + return true; } + return false; } void BinarySensor::add_filter(Filter *filter) { diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index c1661d710f..3f77def9a0 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -63,6 +63,8 @@ class BinarySensor : public StatefulEntityBase, public EntityBase_DeviceCl protected: Filter *filter_list_{nullptr}; + + bool set_new_state(const optional &new_state) override; }; class BinarySensorInitiallyOff : public BinarySensor { diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 2b52d66f76..8c87806f33 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -202,7 +202,7 @@ template class StatefulEntityBase : public EntityBase { virtual bool has_state() const { return this->state_.has_value(); } virtual const T &get_state() const { return this->state_.value(); } virtual T get_state_default(T default_value) const { return this->state_.value_or(default_value); } - void invalidate_state() { this->set_state_({}); } + void invalidate_state() { this->set_new_state({}); } void add_full_state_callback(std::function previous, optional current)> &&callback) { if (this->full_state_callbacks_ == nullptr) @@ -224,20 +224,20 @@ template class StatefulEntityBase : public EntityBase { /** * Set a new state for this entity. This will trigger callbacks only if the new state is different from the previous. * - * @param state The new state. + * @param new_state The new state. * @return True if the state was changed, false if it was the same as before. */ - bool set_state_(const optional &state) { - if (this->state_ != state) { + virtual bool set_new_state(const optional &new_state) { + if (this->state_ != new_state) { // call the full state callbacks with the previous and new state if (this->full_state_callbacks_ != nullptr) - this->full_state_callbacks_->call(this->state_, state); + this->full_state_callbacks_->call(this->state_, new_state); // trigger legacy callbacks only if the new state is valid and either the trigger on initial state is enabled or // the previous state was valid auto had_state = this->has_state(); - this->state_ = state; - if (this->state_callbacks_ != nullptr && state.has_value() && (this->trigger_on_initial_state_ || had_state)) - this->state_callbacks_->call(state.value()); + this->state_ = new_state; + if (this->state_callbacks_ != nullptr && new_state.has_value() && (this->trigger_on_initial_state_ || had_state)) + this->state_callbacks_->call(new_state.value()); return true; } return false; diff --git a/tests/integration/README.md b/tests/integration/README.md index 2a6b6fe564..f99139db00 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -7,7 +7,7 @@ This directory contains end-to-end integration tests for ESPHome, focusing on te - `conftest.py` - Common fixtures and utilities - `const.py` - Constants used throughout the integration tests - `types.py` - Type definitions for fixtures and functions -- `state_utils.py` - State handling utilities (e.g., `InitialStateHelper`, `build_key_to_entity_mapping`) +- `state_utils.py` - State handling utilities (e.g., `InitialStateHelper`, `find_entity`, `require_entity`) - `fixtures/` - YAML configuration files for tests - `test_*.py` - Individual test files @@ -53,6 +53,28 @@ The `InitialStateHelper` class solves a common problem in integration tests: whe **Future work:** Consider converting existing integration tests to use `InitialStateHelper` for more reliable state tracking and to eliminate race conditions related to initial state broadcasts. +#### Entity Lookup Helpers (`state_utils.py`) + +Two helper functions simplify finding entities in test code: + +**`find_entity(entities, object_id_substring, entity_type=None)`** +- Finds an entity by searching for a substring in its `object_id` (case-insensitive) +- Optionally filters by entity type (e.g., `BinarySensorInfo`) +- Returns `None` if not found + +**`require_entity(entities, object_id_substring, entity_type=None, description=None)`** +- Same as `find_entity` but raises `AssertionError` if not found +- Use `description` parameter for clearer error messages + +```python +from aioesphomeapi import BinarySensorInfo +from .state_utils import require_entity + +# Find entities with clear error messages +binary_sensor = require_entity(entities, "test_sensor", BinarySensorInfo) +button = require_entity(entities, "set_true", description="Set True button") +``` + ### Writing Tests The simplest way to write a test is to use the `run_compiled` and `api_client_connected` fixtures: diff --git a/tests/integration/fixtures/binary_sensor_invalidate_state.yaml b/tests/integration/fixtures/binary_sensor_invalidate_state.yaml new file mode 100644 index 0000000000..4016cfe281 --- /dev/null +++ b/tests/integration/fixtures/binary_sensor_invalidate_state.yaml @@ -0,0 +1,39 @@ +esphome: + name: test-binary-sensor-invalidate + +host: +api: + batch_delay: 0ms # Disable batching to receive all state updates +logger: + level: DEBUG + +# Template binary sensor that we can control +binary_sensor: + - platform: template + name: "Test Binary Sensor" + id: test_binary_sensor + +# Buttons to control the binary sensor state +button: + - platform: template + name: "Set True" + id: set_true_button + on_press: + - binary_sensor.template.publish: + id: test_binary_sensor + state: true + + - platform: template + name: "Set False" + id: set_false_button + on_press: + - binary_sensor.template.publish: + id: test_binary_sensor + state: false + + - platform: template + name: "Invalidate State" + id: invalidate_button + on_press: + - binary_sensor.invalidate_state: + id: test_binary_sensor diff --git a/tests/integration/state_utils.py b/tests/integration/state_utils.py index 6434a41ddf..b649056f2b 100644 --- a/tests/integration/state_utils.py +++ b/tests/integration/state_utils.py @@ -4,11 +4,74 @@ from __future__ import annotations import asyncio import logging +from typing import TypeVar from aioesphomeapi import ButtonInfo, EntityInfo, EntityState _LOGGER = logging.getLogger(__name__) +T = TypeVar("T", bound=EntityInfo) + + +def find_entity( + entities: list[EntityInfo], + object_id_substring: str, + entity_type: type[T] | None = None, +) -> T | EntityInfo | None: + """Find an entity by object_id substring and optionally by type. + + Args: + entities: List of entity info objects from the API + object_id_substring: Substring to search for in object_id (case-insensitive) + entity_type: Optional entity type to filter by (e.g., BinarySensorInfo) + + Returns: + The first matching entity, or None if not found + + Example: + binary_sensor = find_entity(entities, "test_binary_sensor", BinarySensorInfo) + button = find_entity(entities, "set_true") # Any entity type + """ + substring_lower = object_id_substring.lower() + for entity in entities: + if substring_lower in entity.object_id.lower() and ( + entity_type is None or isinstance(entity, entity_type) + ): + return entity + return None + + +def require_entity( + entities: list[EntityInfo], + object_id_substring: str, + entity_type: type[T] | None = None, + description: str | None = None, +) -> T | EntityInfo: + """Find an entity or raise AssertionError if not found. + + Args: + entities: List of entity info objects from the API + object_id_substring: Substring to search for in object_id (case-insensitive) + entity_type: Optional entity type to filter by (e.g., BinarySensorInfo) + description: Human-readable description for error message + + Returns: + The first matching entity + + Raises: + AssertionError: If no matching entity is found + + Example: + binary_sensor = require_entity(entities, "test_sensor", BinarySensorInfo) + button = require_entity(entities, "set_true", description="Set True button") + """ + entity = find_entity(entities, object_id_substring, entity_type) + if entity is None: + desc = description or f"entity with '{object_id_substring}' in object_id" + type_info = f" of type {entity_type.__name__}" if entity_type else "" + raise AssertionError(f"{desc}{type_info} not found in entities") + return entity + def build_key_to_entity_mapping( entities: list[EntityInfo], entity_names: list[str] diff --git a/tests/integration/test_binary_sensor_invalidate_state.py b/tests/integration/test_binary_sensor_invalidate_state.py new file mode 100644 index 0000000000..ee9e57319c --- /dev/null +++ b/tests/integration/test_binary_sensor_invalidate_state.py @@ -0,0 +1,138 @@ +"""Integration test for binary_sensor.invalidate_state() functionality. + +This tests the fix in PR #12296 where invalidate_state() was not properly +reporting the 'unknown' state to the API. The binary sensor should report +missing_state=True when invalidated. + +Regression test for: https://github.com/esphome/esphome/issues/12252 +""" + +from __future__ import annotations + +import asyncio + +from aioesphomeapi import BinarySensorInfo, BinarySensorState, EntityState +import pytest + +from .state_utils import InitialStateHelper, require_entity +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_binary_sensor_invalidate_state( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that binary_sensor.invalidate_state() reports unknown to the API. + + This verifies that: + 1. Binary sensor starts with missing_state=True (no initial state) + 2. Publishing true sets missing_state=False and state=True + 3. Publishing false sets missing_state=False and state=False + 4. Invalidating state sets missing_state=True (unknown state) + """ + loop = asyncio.get_running_loop() + + # Track state changes + states_received: list[BinarySensorState] = [] + state_future: asyncio.Future[BinarySensorState] = loop.create_future() + + def on_state(state: EntityState) -> None: + """Track binary sensor state changes.""" + if isinstance(state, BinarySensorState): + states_received.append(state) + if not state_future.done(): + state_future.set_result(state) + + async with ( + run_compiled(yaml_config), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "test-binary-sensor-invalidate" + + # Get entities + entities, _ = await client.list_entities_services() + + # Find our binary sensor and buttons using helper + binary_sensor = require_entity(entities, "test_binary_sensor", BinarySensorInfo) + set_true_button = require_entity( + entities, "set_true", description="Set True button" + ) + set_false_button = require_entity( + entities, "set_false", description="Set False button" + ) + invalidate_button = require_entity( + entities, "invalidate", description="Invalidate button" + ) + + # Set up initial state helper to handle the initial state broadcast + initial_state_helper = InitialStateHelper(entities) + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + # Wait for initial states + try: + await initial_state_helper.wait_for_initial_states() + except TimeoutError: + pytest.fail("Timeout waiting for initial states") + + # Check initial state - should be missing (unknown) + initial_state = initial_state_helper.initial_states.get(binary_sensor.key) + assert initial_state is not None, "No initial state received for binary sensor" + assert isinstance(initial_state, BinarySensorState) + assert initial_state.missing_state is True, ( + f"Initial state should have missing_state=True, got {initial_state}" + ) + + # Test 1: Set state to true + states_received.clear() + state_future = loop.create_future() + client.button_command(set_true_button.key) + + try: + state = await asyncio.wait_for(state_future, timeout=5.0) + except TimeoutError: + pytest.fail("Timeout waiting for state=true") + + assert state.missing_state is False, ( + f"After setting true, missing_state should be False, got {state}" + ) + assert state.state is True, f"Expected state=True, got {state}" + + # Test 2: Set state to false + states_received.clear() + state_future = loop.create_future() + client.button_command(set_false_button.key) + + try: + state = await asyncio.wait_for(state_future, timeout=5.0) + except TimeoutError: + pytest.fail("Timeout waiting for state=false") + + assert state.missing_state is False, ( + f"After setting false, missing_state should be False, got {state}" + ) + assert state.state is False, f"Expected state=False, got {state}" + + # Test 3: Invalidate state (set to unknown) + # This is the critical test for the bug fix + states_received.clear() + state_future = loop.create_future() + client.button_command(invalidate_button.key) + + try: + state = await asyncio.wait_for(state_future, timeout=5.0) + except TimeoutError: + pytest.fail( + "Timeout waiting for invalidated state - " + "binary_sensor.invalidate_state() may not be reporting to the API. " + "See issue #12252." + ) + + assert state.missing_state is True, ( + f"After invalidate_state(), missing_state should be True (unknown), " + f"got {state}. This is the regression from issue #12252." + ) From b6336f9e638d44c764ef44d9cc94746e5c2b4b63 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sun, 7 Dec 2025 01:49:15 +1100 Subject: [PATCH 0587/1145] [lvgl] Number saves value on interactive change (#12315) --- esphome/components/lvgl/number/lvgl_number.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/lvgl/number/lvgl_number.h b/esphome/components/lvgl/number/lvgl_number.h index 7bc44c9e20..d9885bc7fb 100644 --- a/esphome/components/lvgl/number/lvgl_number.h +++ b/esphome/components/lvgl/number/lvgl_number.h @@ -29,15 +29,18 @@ class LVGLNumber : public number::Number, public Component { this->publish_state(value); } - void on_value() { this->publish_state(this->value_lambda_()); } + void on_value() { this->publish_(this->value_lambda_()); } protected: - void control(float value) override { - this->control_lambda_(value); + void publish_(float value) { this->publish_state(value); if (this->restore_) this->pref_.save(&value); } + void control(float value) override { + this->control_lambda_(value); + this->publish_(value); + } std::function control_lambda_; std::function value_lambda_; lv_event_code_t event_; From b213555dd24b13d57108cd706155c6f2274fff4c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Dec 2025 13:30:22 -0600 Subject: [PATCH 0588/1145] [scheduler] Fix missing lock when recycling items in defer queue processing (#12343) --- esphome/core/scheduler.cpp | 4 ++++ esphome/core/scheduler.h | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 5e313f770f..f84495950c 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -744,6 +744,10 @@ bool HOT Scheduler::SchedulerItem::cmp(const std::unique_ptr &a, : (a->next_execution_high_ > b->next_execution_high_); } +// Recycle a SchedulerItem back to the pool for reuse. +// IMPORTANT: Caller must hold the scheduler lock before calling this function. +// This protects scheduler_item_pool_ from concurrent access by other threads +// that may be acquiring items from the pool in set_timer_common_(). void Scheduler::recycle_item_main_loop_(std::unique_ptr item) { if (!item) return; diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index dcf418c14f..5bf3d19adb 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -275,6 +275,7 @@ class Scheduler { // Helper to recycle a SchedulerItem back to the pool. // IMPORTANT: Only call from main loop context! Recycling clears the callback, // so calling from another thread while the callback is executing causes use-after-free. + // IMPORTANT: Caller must hold the scheduler lock before calling this function. void recycle_item_main_loop_(std::unique_ptr item); // Helper to perform full cleanup when too many items are cancelled @@ -331,7 +332,10 @@ class Scheduler { now = this->execute_item_(item.get(), now); } // Recycle the defer item after execution - this->recycle_item_main_loop_(std::move(item)); + { + LockGuard lock(this->lock_); + this->recycle_item_main_loop_(std::move(item)); + } } // If we've consumed all items up to the snapshot point, clean up the dead space From 436d2c44e84888b59b2ba294c47b68544708bda7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Dec 2025 22:00:04 -0600 Subject: [PATCH 0589/1145] [wifi] Fix scan timeout loop when scan returns zero networks (#12354) --- esphome/components/wifi/wifi_component.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index abf62cb063..2882eab934 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1209,8 +1209,8 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() { } case WiFiRetryPhase::SCAN_CONNECTING: - // If scan found no matching networks, skip to hidden network mode - if (!this->scan_result_.empty() && !this->scan_result_[0].get_matches()) { + // If scan found no networks or no matching networks, skip to hidden network mode + if (this->scan_result_.empty() || !this->scan_result_[0].get_matches()) { return WiFiRetryPhase::RETRY_HIDDEN; } From 16fe8f9e9ed660e18c8fd6b8bbf515fccc5a27d6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Dec 2025 15:09:04 +0100 Subject: [PATCH 0590/1145] [libretiny] Fix WiFi scan timeout loop when scan fails (#12356) --- esphome/components/wifi/wifi_component_libretiny.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 8c6c28ac75..c99924785a 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -412,6 +412,7 @@ bool WiFiComponent::wifi_scan_start_(bool passive) { } void WiFiComponent::wifi_scan_done_callback_() { this->scan_result_.clear(); + this->scan_done_ = true; int16_t num = WiFi.scanComplete(); if (num < 0) @@ -430,7 +431,6 @@ void WiFiComponent::wifi_scan_done_callback_() { ssid.length() == 0); } WiFi.scanDelete(); - this->scan_done_ = true; } #ifdef USE_WIFI_AP From 464607011c342f9540e25ad05da17a30faf4c645 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:43:47 -0500 Subject: [PATCH 0591/1145] [mqtt] Fix logger method case sensitivity error (#12379) Co-authored-by: Claude --- esphome/mqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/mqtt.py b/esphome/mqtt.py index 0d50edbc2c..042df12d67 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -192,7 +192,7 @@ def get_esphome_device_ip( data = json.loads(payload) if "name" not in data or data["name"] != dev_name: - _LOGGER.Warn("Wrong device answer") + _LOGGER.warning("Wrong device answer") return dev_ip = [] From 4743e5592a2cb52b0e5965e675379bd507d8d845 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:02:53 -0500 Subject: [PATCH 0592/1145] Bump version to 2025.11.5 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index dbb744767b..17c1f3d929 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.11.4 +PROJECT_NUMBER = 2025.11.5 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 472e0a7bee..f50a3e3bb1 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.11.4" +__version__ = "2025.11.5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 329b38fa296b88c1dca4cd25ced351048a0ea85e Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Tue, 9 Dec 2025 20:30:55 +0100 Subject: [PATCH 0593/1145] [micronova] Require memory location and address for custom entities (#12371) --- esphome/components/micronova/__init__.py | 24 +++++++++++-------- .../components/micronova/button/__init__.py | 6 ++--- .../components/micronova/sensor/__init__.py | 2 -- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/esphome/components/micronova/__init__.py b/esphome/components/micronova/__init__.py index 52fbae2da2..d6ef93cf30 100644 --- a/esphome/components/micronova/__init__.py +++ b/esphome/components/micronova/__init__.py @@ -40,21 +40,25 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( def MICRONOVA_ADDRESS_SCHEMA( *, - default_memory_location: int, - default_memory_address: int, + default_memory_location: int | None = None, + default_memory_address: int | None = None, is_polling_component: bool, ): + location_key = ( + cv.Optional(CONF_MEMORY_LOCATION, default=default_memory_location) + if default_memory_location is not None + else cv.Required(CONF_MEMORY_LOCATION) + ) + address_key = ( + cv.Optional(CONF_MEMORY_ADDRESS, default=default_memory_address) + if default_memory_address is not None + else cv.Required(CONF_MEMORY_ADDRESS) + ) schema = cv.Schema( { cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova), - # On write requests the write bit (0x80) is added automatically to the location - # Therefore no locations >= 0x80 are allowed - cv.Optional( - CONF_MEMORY_LOCATION, default=default_memory_location - ): cv.hex_int_range(min=0x00, max=0x79), - cv.Optional( - CONF_MEMORY_ADDRESS, default=default_memory_address - ): cv.hex_int_range(min=0x00, max=0xFF), + location_key: cv.hex_int_range(min=0x00, max=0x79), + address_key: cv.hex_int_range(min=0x00, max=0xFF), } ) if is_polling_component: diff --git a/esphome/components/micronova/button/__init__.py b/esphome/components/micronova/button/__init__.py index 2eda887443..6adf8d96fe 100644 --- a/esphome/components/micronova/button/__init__.py +++ b/esphome/components/micronova/button/__init__.py @@ -24,8 +24,6 @@ CONFIG_SCHEMA = cv.Schema( ) .extend( MICRONOVA_ADDRESS_SCHEMA( - default_memory_location=0x20, - default_memory_address=0x7D, is_polling_component=False, ) ) @@ -39,6 +37,6 @@ async def to_code(config): if custom_button_config := config.get(CONF_CUSTOM_BUTTON): bt = await button.new_button(custom_button_config, mv) - cg.add(bt.set_memory_location(custom_button_config.get(CONF_MEMORY_LOCATION))) - cg.add(bt.set_memory_address(custom_button_config.get(CONF_MEMORY_ADDRESS))) + cg.add(bt.set_memory_location(custom_button_config[CONF_MEMORY_LOCATION])) + cg.add(bt.set_memory_address(custom_button_config[CONF_MEMORY_ADDRESS])) cg.add(bt.set_memory_data(custom_button_config[CONF_MEMORY_DATA])) diff --git a/esphome/components/micronova/sensor/__init__.py b/esphome/components/micronova/sensor/__init__.py index 55318a7fff..e53c49aca5 100644 --- a/esphome/components/micronova/sensor/__init__.py +++ b/esphome/components/micronova/sensor/__init__.py @@ -118,8 +118,6 @@ CONFIG_SCHEMA = cv.Schema( MicroNovaSensor, ).extend( MICRONOVA_ADDRESS_SCHEMA( - default_memory_location=0x00, - default_memory_address=0x00, is_polling_component=True, ) ), From 87142efbb43e1fd7619359f15df27cd7b05f0863 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 10 Dec 2025 06:42:11 +1100 Subject: [PATCH 0594/1145] [epaper_spi] Set reasonable default update interval (#12331) --- esphome/components/epaper_spi/display.py | 15 +++++++++------ esphome/components/epaper_spi/epaper_spi.cpp | 10 +++++----- esphome/components/epaper_spi/epaper_spi.h | 2 +- .../components/epaper_spi/models/spectra_e6.py | 6 +++--- esphome/components/lvgl/lvgl_esphome.cpp | 2 +- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/esphome/components/epaper_spi/display.py b/esphome/components/epaper_spi/display.py index ff5693c206..b7e71a3cae 100644 --- a/esphome/components/epaper_spi/display.py +++ b/esphome/components/epaper_spi/display.py @@ -41,6 +41,7 @@ AUTO_LOAD = ["split_buffer"] DEPENDENCIES = ["spi"] CONF_INIT_SEQUENCE_ID = "init_sequence_id" +CONF_MINIMUM_UPDATE_INTERVAL = "minimum_update_interval" epaper_spi_ns = cg.esphome_ns.namespace("epaper_spi") EPaperBase = epaper_spi_ns.class_( @@ -71,6 +72,9 @@ TRANSFORM_OPTIONS = {CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY} def model_schema(config): model = MODELS[config[CONF_MODEL]] class_name = epaper_spi_ns.class_(model.class_name, EPaperBase) + minimum_update_interval = update_interval( + model.get_default(CONF_MINIMUM_UPDATE_INTERVAL, "1s") + ) cv_dimensions = cv.Optional if model.get_default(CONF_WIDTH) else cv.Required return ( display.FULL_DISPLAY_SCHEMA.extend( @@ -90,9 +94,9 @@ def model_schema(config): { cv.Optional(CONF_ROTATION, default=0): validate_rotation, cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True), - cv.Optional( - CONF_UPDATE_INTERVAL, default=cv.UNDEFINED - ): update_interval, + cv.Optional(CONF_UPDATE_INTERVAL, default=cv.UNDEFINED): cv.All( + update_interval, cv.Range(min=minimum_update_interval) + ), cv.Optional(CONF_TRANSFORM): cv.Schema( { cv.Required(CONF_MIRROR_X): cv.boolean, @@ -153,9 +157,8 @@ def _final_validate(config): else: # If no drawing methods are configured, and LVGL is not enabled, show a test card config[CONF_SHOW_TEST_CARD] = True - config[CONF_UPDATE_INTERVAL] = core.TimePeriod( - seconds=60 - ).total_milliseconds + elif CONF_UPDATE_INTERVAL not in config: + config[CONF_UPDATE_INTERVAL] = update_interval("1min") return config diff --git a/esphome/components/epaper_spi/epaper_spi.cpp b/esphome/components/epaper_spi/epaper_spi.cpp index f6313d33ef..b2e58694c8 100644 --- a/esphome/components/epaper_spi/epaper_spi.cpp +++ b/esphome/components/epaper_spi/epaper_spi.cpp @@ -286,7 +286,7 @@ void EPaperBase::initialise_() { * @param y * @return false if the coordinates are out of bounds */ -bool EPaperBase::rotate_coordinates_(int &x, int &y) const { +bool EPaperBase::rotate_coordinates_(int &x, int &y) { if (!this->get_clipping().inside(x, y)) return false; if (this->transform_ & SWAP_XY) @@ -297,6 +297,10 @@ bool EPaperBase::rotate_coordinates_(int &x, int &y) const { y = this->height_ - y - 1; if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0) return false; + this->x_low_ = clamp_at_most(this->x_low_, x); + this->x_high_ = clamp_at_least(this->x_high_, x + 1); + this->y_low_ = clamp_at_most(this->y_low_, y); + this->y_high_ = clamp_at_least(this->y_high_, y + 1); return true; } @@ -319,10 +323,6 @@ void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) { } else { this->buffer_[byte_position] = original | pixel_bit; } - this->x_low_ = clamp_at_most(this->x_low_, x); - this->x_high_ = clamp_at_least(this->x_high_, x + 1); - this->y_low_ = clamp_at_most(this->y_low_, y); - this->y_high_ = clamp_at_least(this->y_high_, y + 1); } void EPaperBase::dump_config() { diff --git a/esphome/components/epaper_spi/epaper_spi.h b/esphome/components/epaper_spi/epaper_spi.h index 544ea3e9ba..6852416cac 100644 --- a/esphome/components/epaper_spi/epaper_spi.h +++ b/esphome/components/epaper_spi/epaper_spi.h @@ -106,7 +106,7 @@ class EPaperBase : public Display, void initialise_(); void wait_for_idle_(bool should_wait); bool init_buffer_(size_t buffer_length); - bool rotate_coordinates_(int &x, int &y) const; + bool rotate_coordinates_(int &x, int &y); /** * Methods that must be implemented by concrete classes to control the display diff --git a/esphome/components/epaper_spi/models/spectra_e6.py b/esphome/components/epaper_spi/models/spectra_e6.py index 42a5a7da72..58015f486e 100644 --- a/esphome/components/epaper_spi/models/spectra_e6.py +++ b/esphome/components/epaper_spi/models/spectra_e6.py @@ -4,8 +4,8 @@ from . import EpaperModel class SpectraE6(EpaperModel): - def __init__(self, name, class_name="EPaperSpectraE6", **kwargs): - super().__init__(name, class_name, **kwargs) + def __init__(self, name, class_name="EPaperSpectraE6", **defaults): + super().__init__(name, class_name, **defaults) # fmt: off def get_init_sequence(self, config: dict): @@ -30,7 +30,7 @@ class SpectraE6(EpaperModel): return self.defaults.get(key, fallback) -spectra_e6 = SpectraE6("spectra-e6") +spectra_e6 = SpectraE6("spectra-e6", minimum_update_interval="30s") spectra_e6_7p3 = spectra_e6.extend( "7.3in-Spectra-E6", diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 18226a9f57..50dba94a2b 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -498,12 +498,12 @@ void LvglComponent::setup() { buf_bytes /= MIN_BUFFER_FRAC; buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT } + this->buffer_frac_ = frac; if (buffer == nullptr) { this->status_set_error(LOG_STR("Memory allocation failure")); this->mark_failed(); return; } - this->buffer_frac_ = frac; lv_disp_draw_buf_init(&this->draw_buf_, buffer, nullptr, buffer_pixels); this->disp_drv_.hor_res = display->get_width(); this->disp_drv_.ver_res = display->get_height(); From ad0218fd4097b7212a16a1a8132e85413950a127 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 10 Dec 2025 08:17:59 +1100 Subject: [PATCH 0595/1145] [mipi_rgb] Add Waveshare 3.16 (#12309) --- esphome/components/mipi_rgb/display.py | 15 ++-- esphome/components/mipi_rgb/mipi_rgb.cpp | 15 +--- esphome/components/mipi_rgb/models/lilygo.py | 2 - esphome/components/mipi_rgb/models/st7701s.py | 1 - .../components/mipi_rgb/models/waveshare.py | 76 ++++++++++++++++++- 5 files changed, 81 insertions(+), 28 deletions(-) diff --git a/esphome/components/mipi_rgb/display.py b/esphome/components/mipi_rgb/display.py index 2d2e022045..61dbeb8ed4 100644 --- a/esphome/components/mipi_rgb/display.py +++ b/esphome/components/mipi_rgb/display.py @@ -24,7 +24,7 @@ from esphome.components.mipi import ( CONF_VSYNC_BACK_PORCH, CONF_VSYNC_FRONT_PORCH, CONF_VSYNC_PULSE_WIDTH, - MODE_BGR, + MODE_RGB, PIXEL_MODE_16BIT, PIXEL_MODE_18BIT, DriverChip, @@ -157,7 +157,7 @@ def model_schema(config): model.option(CONF_ENABLE_PIN, cv.UNDEFINED): cv.ensure_list( pins.gpio_output_pin_schema ), - model.option(CONF_COLOR_ORDER, MODE_BGR): cv.enum(COLOR_ORDERS, upper=True), + model.option(CONF_COLOR_ORDER, MODE_RGB): cv.enum(COLOR_ORDERS, upper=True), model.option(CONF_DRAW_ROUNDING, 2): power_of_two, model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.one_of( *pixel_modes, lower=True @@ -280,14 +280,9 @@ async def to_code(config): red_pins = config[CONF_DATA_PINS][CONF_RED] green_pins = config[CONF_DATA_PINS][CONF_GREEN] blue_pins = config[CONF_DATA_PINS][CONF_BLUE] - if config[CONF_COLOR_ORDER] == "BGR": - dpins.extend(red_pins) - dpins.extend(green_pins) - dpins.extend(blue_pins) - else: - dpins.extend(blue_pins) - dpins.extend(green_pins) - dpins.extend(red_pins) + dpins.extend(blue_pins) + dpins.extend(green_pins) + dpins.extend(red_pins) # swap bytes to match big-endian format dpins = dpins[8:16] + dpins[0:8] else: diff --git a/esphome/components/mipi_rgb/mipi_rgb.cpp b/esphome/components/mipi_rgb/mipi_rgb.cpp index 74eedae4f4..d5d1caf6d2 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.cpp +++ b/esphome/components/mipi_rgb/mipi_rgb.cpp @@ -371,17 +371,10 @@ void MipiRgb::dump_config() { get_pin_name(this->de_pin_).c_str(), get_pin_name(this->pclk_pin_).c_str(), get_pin_name(this->hsync_pin_).c_str(), get_pin_name(this->vsync_pin_).c_str()); - if (this->madctl_ & MADCTL_BGR) { - this->dump_pins_(8, 13, "Blue", 0); - this->dump_pins_(13, 16, "Green", 0); - this->dump_pins_(0, 3, "Green", 3); - this->dump_pins_(3, 8, "Red", 0); - } else { - this->dump_pins_(8, 13, "Red", 0); - this->dump_pins_(13, 16, "Green", 0); - this->dump_pins_(0, 3, "Green", 3); - this->dump_pins_(3, 8, "Blue", 0); - } + this->dump_pins_(8, 13, "Blue", 0); + this->dump_pins_(13, 16, "Green", 0); + this->dump_pins_(0, 3, "Green", 3); + this->dump_pins_(3, 8, "Red", 0); } } // namespace mipi_rgb diff --git a/esphome/components/mipi_rgb/models/lilygo.py b/esphome/components/mipi_rgb/models/lilygo.py index 109dc42af6..c0e91cd8ae 100644 --- a/esphome/components/mipi_rgb/models/lilygo.py +++ b/esphome/components/mipi_rgb/models/lilygo.py @@ -7,7 +7,6 @@ ST7701S( "T-PANEL-S3", width=480, height=480, - color_order="BGR", invert_colors=False, swap_xy=UNDEFINED, spi_mode="MODE3", @@ -56,7 +55,6 @@ t_rgb = ST7701S( "T-RGB-2.1", width=480, height=480, - color_order="BGR", pixel_mode="18bit", invert_colors=False, swap_xy=UNDEFINED, diff --git a/esphome/components/mipi_rgb/models/st7701s.py b/esphome/components/mipi_rgb/models/st7701s.py index 0b0a9548ca..3c66380d04 100644 --- a/esphome/components/mipi_rgb/models/st7701s.py +++ b/esphome/components/mipi_rgb/models/st7701s.py @@ -82,7 +82,6 @@ st7701s.extend( "MAKERFABS-4", width=480, height=480, - color_order="RGB", invert_colors=True, pixel_mode="18bit", cs_pin=1, diff --git a/esphome/components/mipi_rgb/models/waveshare.py b/esphome/components/mipi_rgb/models/waveshare.py index 0fc765fd52..cd1fc341ef 100644 --- a/esphome/components/mipi_rgb/models/waveshare.py +++ b/esphome/components/mipi_rgb/models/waveshare.py @@ -1,13 +1,13 @@ -from esphome.components.mipi import DriverChip +from esphome.components.mipi import DriverChip, delay from esphome.config_validation import UNDEFINED from .st7701s import st7701s +# fmt: off wave_4_3 = DriverChip( "ESP32-S3-TOUCH-LCD-4.3", swap_xy=UNDEFINED, initsequence=(), - color_order="RGB", width=800, height=480, pclk_frequency="16MHz", @@ -55,10 +55,9 @@ wave_4_3.extend( ) st7701s.extend( - "WAVESHARE-4-480x480", + "WAVESHARE-4-480X480", data_rate="2MHz", spi_mode="MODE3", - color_order="BGR", pixel_mode="18bit", width=480, height=480, @@ -76,3 +75,72 @@ st7701s.extend( "blue": [5, 45, 48, 47, 21], }, ) + +st7701s.extend( + "WAVESHARE-3.16-320X820", + width=320, + height=820, + de_pin=40, + hsync_pin=38, + vsync_pin=39, + pclk_pin=41, + cs_pin={ + "number": 0, + "ignore_strapping_warning": True, + }, + pclk_frequency="18MHz", + reset_pin=16, + hsync_back_porch=30, + hsync_front_porch=30, + hsync_pulse_width=6, + vsync_back_porch=20, + vsync_front_porch=20, + vsync_pulse_width=40, + data_pins={ + "red": [17, 46, 3, 8, 18], + "green": [14, 13, 12, 11, 10, 9], + "blue": [21, 5, 45, 48, 47], + }, + initsequence=( + (0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), + (0xEF, 0x08), + (0xFF, 0x77, 0x01, 0x00, 0x00, 0x10), + (0xC0, 0xE5, 0x02), + (0xC1, 0x15, 0x0A), + (0xC2, 0x07, 0x02), + (0xCC, 0x10), + (0xB0, 0x00, 0x08, 0x51, 0x0D, 0xCE, 0x06, 0x00, 0x08, 0x08, 0x24, 0x05, 0xD0, 0x0F, 0x6F, 0x36, 0x1F), + (0xB1, 0x00, 0x10, 0x4F, 0x0C, 0x11, 0x05, 0x00, 0x07, 0x07, 0x18, 0x02, 0xD3, 0x11, 0x6E, 0x34, 0x1F), + (0xFF, 0x77, 0x01, 0x00, 0x00, 0x11), + (0xB0, 0x4D), + (0xB1, 0x37), + (0xB2, 0x87), + (0xB3, 0x80), + (0xB5, 0x4A), + (0xB7, 0x85), + (0xB8, 0x21), + (0xB9, 0x00, 0x13), + (0xC0, 0x09), + (0xC1, 0x78), + (0xC2, 0x78), + (0xD0, 0x88), + (0xE0, 0x80, 0x00, 0x02), + (0xE1, 0x0F, 0xA0, 0x00, 0x00, 0x10, 0xA0, 0x00, 0x00, 0x00, 0x60, 0x60), + (0xE2, 0x30, 0x30, 0x60, 0x60, 0x45, 0xA0, 0x00, 0x00, 0x46, 0xA0, 0x00, 0x00, 0x00), + (0xE3, 0x00, 0x00, 0x33, 0x33), + (0xE4, 0x44, 0x44), + (0xE5, 0x0F, 0x4A, 0xA0, 0xA0, 0x11, 0x4A, 0xA0, 0xA0, 0x13, 0x4A, 0xA0, 0xA0, 0x15, 0x4A, 0xA0, 0xA0), + (0xE6, 0x00, 0x00, 0x33, 0x33), + (0xE7, 0x44, 0x44), + (0xE8, 0x10, 0x4A, 0xA0, 0xA0, 0x12, 0x4A, 0xA0, 0xA0, 0x14, 0x4A, 0xA0, 0xA0, 0x16, 0x4A, 0xA0, 0xA0), + (0xEB, 0x02, 0x00, 0x4E, 0x4E, 0xEE, 0x44, 0x00), + (0xED, 0xFF, 0xFF, 0x04, 0x56, 0x72, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x27, 0x65, 0x40, 0xFF, 0xFF), + (0xEF, 0x08, 0x08, 0x08, 0x40, 0x3F, 0x64), + (0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), + (0xE8, 0x00, 0x0E), + (0xE8, 0x00, 0x0C), + delay(10), + (0xE8, 0x00, 0x00), + (0xFF, 0x77, 0x01, 0x00, 0x00, 0x00), + ) +) From 1e23b10eedc852e6380c1ae9a8736a3d990e6961 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 22:02:42 +0000 Subject: [PATCH 0596/1145] Bump aioesphomeapi from 43.1.0 to 43.2.1 (#12385) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5596f050af..71aaf47ddb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.1.0 +aioesphomeapi==43.2.1 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.16 # dashboard_import From 5919355d182987030fcb15c27626ea1256ca64f9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 00:26:24 +0100 Subject: [PATCH 0597/1145] [ci] Allow memory impact target branch build to fail without blocking CI (#12381) --- .github/workflows/ci.yml | 16 ++--- script/ci_memory_impact_comment.py | 72 +++++++++++++++++-- .../ci_memory_impact_target_unavailable.j2 | 19 +++++ 3 files changed, 93 insertions(+), 14 deletions(-) create mode 100644 script/templates/ci_memory_impact_target_unavailable.j2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01689d3697..03eadb5f0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -959,13 +959,13 @@ jobs: - memory-impact-comment if: always() steps: - - name: Success - if: ${{ !(contains(needs.*.result, 'failure')) }} - run: exit 0 - - name: Failure - if: ${{ contains(needs.*.result, 'failure') }} + - name: Check job results env: - JSON_DOC: ${{ toJSON(needs) }} + NEEDS_JSON: ${{ toJSON(needs) }} run: | - echo $JSON_DOC | jq - exit 1 + # memory-impact-target-branch is allowed to fail without blocking CI. + # This job builds the target branch (dev/beta/release) which may fail because: + # 1. The target branch has a build issue independent of this PR + # 2. This PR fixes a build issue on the target branch + # In either case, we only care that the PR branch builds successfully. + echo "$NEEDS_JSON" | jq -e 'del(.["memory-impact-target-branch"]) | all(.result != "failure")' diff --git a/script/ci_memory_impact_comment.py b/script/ci_memory_impact_comment.py index 1331a44d03..a296130645 100755 --- a/script/ci_memory_impact_comment.py +++ b/script/ci_memory_impact_comment.py @@ -215,6 +215,20 @@ def prepare_symbol_changes_data( } +def format_components_str(components: list[str]) -> str: + """Format a list of components for display. + + Args: + components: List of component names + + Returns: + Formatted string with backtick-quoted component names + """ + if len(components) == 1: + return f"`{components[0]}`" + return ", ".join(f"`{c}`" for c in sorted(components)) + + def prepare_component_breakdown_data( target_analysis: dict | None, pr_analysis: dict | None ) -> list[tuple[str, int, int, int]] | None: @@ -316,11 +330,10 @@ def create_comment_body( } # Format components list + context["components_str"] = format_components_str(components) if len(components) == 1: - context["components_str"] = f"`{components[0]}`" context["config_note"] = "a representative test configuration" else: - context["components_str"] = ", ".join(f"`{c}`" for c in sorted(components)) context["config_note"] = ( f"a merged configuration with {len(components)} components" ) @@ -502,6 +515,43 @@ def post_or_update_comment(pr_number: str, comment_body: str) -> None: print("Comment posted/updated successfully", file=sys.stderr) +def create_target_unavailable_comment( + pr_data: dict, +) -> str: + """Create a comment body when target branch data is unavailable. + + This happens when the target branch (dev/beta/release) fails to build. + This can occur because: + 1. The target branch has a build issue independent of this PR + 2. This PR fixes a build issue on the target branch + In either case, we only care that the PR branch builds successfully. + + Args: + pr_data: Dictionary with PR branch analysis results + + Returns: + Formatted comment body + """ + components = pr_data.get("components", []) + platform = pr_data.get("platform", "unknown") + pr_ram = pr_data.get("ram_bytes", 0) + pr_flash = pr_data.get("flash_bytes", 0) + + env = Environment( + loader=FileSystemLoader(TEMPLATE_DIR), + trim_blocks=True, + lstrip_blocks=True, + ) + template = env.get_template("ci_memory_impact_target_unavailable.j2") + return template.render( + comment_marker=COMMENT_MARKER, + components_str=format_components_str(components), + platform=platform, + pr_ram=format_bytes(pr_ram), + pr_flash=format_bytes(pr_flash), + ) + + def main() -> int: """Main entry point.""" parser = argparse.ArgumentParser( @@ -523,15 +573,25 @@ def main() -> int: # Load analysis JSON files (all data comes from JSON for security) target_data: dict | None = load_analysis_json(args.target_json) - if not target_data: - print("Error: Failed to load target analysis JSON", file=sys.stderr) - sys.exit(1) - pr_data: dict | None = load_analysis_json(args.pr_json) + + # PR data is required - if the PR branch can't build, that's a real error if not pr_data: print("Error: Failed to load PR analysis JSON", file=sys.stderr) sys.exit(1) + # Target data is optional - target branch (dev) may fail to build because: + # 1. The target branch has a build issue independent of this PR + # 2. This PR fixes a build issue on the target branch + if not target_data: + print( + "Warning: Target branch analysis unavailable, posting limited comment", + file=sys.stderr, + ) + comment_body = create_target_unavailable_comment(pr_data) + post_or_update_comment(args.pr_number, comment_body) + return 0 + # Extract detailed analysis if available target_analysis: dict | None = None pr_analysis: dict | None = None diff --git a/script/templates/ci_memory_impact_target_unavailable.j2 b/script/templates/ci_memory_impact_target_unavailable.j2 new file mode 100644 index 0000000000..542bd49d85 --- /dev/null +++ b/script/templates/ci_memory_impact_target_unavailable.j2 @@ -0,0 +1,19 @@ +{{ comment_marker }} +## Memory Impact Analysis + +**Components:** {{ components_str }} +**Platform:** `{{ platform }}` + +| Metric | This PR | +|--------|---------| +| **RAM** | {{ pr_ram }} | +| **Flash** | {{ pr_flash }} | + +> ⚠️ **Target branch comparison unavailable** - The target branch failed to build. +> This can happen when the target branch has a build issue, or when this PR fixes a build issue on the target branch. +> The PR branch compiled successfully with the memory usage shown above. + +--- +> **Note:** This analysis measures **static RAM and Flash usage** only (compile-time allocation). + +*This analysis runs automatically when components change.* From 608f834eaab1d22d0723e1229ec6ce3a4e419d47 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 00:49:29 +0100 Subject: [PATCH 0598/1145] [ci] Isolate usb_cdc_acm in component tests due to tinyusb/usb_host conflict (#12392) --- script/analyze_component_buses.py | 1 + 1 file changed, 1 insertion(+) diff --git a/script/analyze_component_buses.py b/script/analyze_component_buses.py index 27a36f889f..427602dff2 100755 --- a/script/analyze_component_buses.py +++ b/script/analyze_component_buses.py @@ -87,6 +87,7 @@ ISOLATED_COMPONENTS = { "neopixelbus": "RMT type conflict with ESP32 Arduino/ESP-IDF headers (enum vs struct rmt_channel_t)", "packages": "cannot merge packages", "tinyusb": "Conflicts with usb_host component - cannot be used together", + "usb_cdc_acm": "Depends on tinyusb which conflicts with usb_host", } From 3a6edbc2c74c7dbf1451d51cb162a155d6487917 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 00:49:44 +0100 Subject: [PATCH 0599/1145] [micronova] Fix test UART package key to match directory name (#12391) --- tests/components/micronova/test.esp32-idf.yaml | 2 +- tests/components/micronova/test.esp8266-ard.yaml | 2 +- tests/components/micronova/test.rp2040-ard.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/components/micronova/test.esp32-idf.yaml b/tests/components/micronova/test.esp32-idf.yaml index b3e4714bc3..6e5602818f 100644 --- a/tests/components/micronova/test.esp32-idf.yaml +++ b/tests/components/micronova/test.esp32-idf.yaml @@ -2,6 +2,6 @@ substitutions: enable_rx_pin: GPIO13 packages: - uart: !include ../../test_build_components/common/uart_1200_none_2stopbits/esp32-idf.yaml + uart_1200_none_2stopbits: !include ../../test_build_components/common/uart_1200_none_2stopbits/esp32-idf.yaml <<: !include common.yaml diff --git a/tests/components/micronova/test.esp8266-ard.yaml b/tests/components/micronova/test.esp8266-ard.yaml index 04030801e3..80792813ad 100644 --- a/tests/components/micronova/test.esp8266-ard.yaml +++ b/tests/components/micronova/test.esp8266-ard.yaml @@ -2,6 +2,6 @@ substitutions: enable_rx_pin: GPIO15 packages: - uart: !include ../../test_build_components/common/uart_1200_none_2stopbits/esp8266-ard.yaml + uart_1200_none_2stopbits: !include ../../test_build_components/common/uart_1200_none_2stopbits/esp8266-ard.yaml <<: !include common.yaml diff --git a/tests/components/micronova/test.rp2040-ard.yaml b/tests/components/micronova/test.rp2040-ard.yaml index 67110f25b0..f069760378 100644 --- a/tests/components/micronova/test.rp2040-ard.yaml +++ b/tests/components/micronova/test.rp2040-ard.yaml @@ -2,6 +2,6 @@ substitutions: enable_rx_pin: GPIO3 packages: - uart: !include ../../test_build_components/common/uart_1200_none_2stopbits/rp2040-ard.yaml + uart_1200_none_2stopbits: !include ../../test_build_components/common/uart_1200_none_2stopbits/rp2040-ard.yaml <<: !include common.yaml From 36423994600afcbe276f55a4267d44a09c16a3b3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 00:50:26 +0100 Subject: [PATCH 0600/1145] [tests] Fix clang-tidy warnings in custom_api_device_component fixture (#12390) --- .../custom_api_device_component/custom_api_device_component.cpp | 1 + .../custom_api_device_component/custom_api_device_component.h | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.cpp b/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.cpp index 01bc7dcd98..c86ab99242 100644 --- a/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.cpp +++ b/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.cpp @@ -52,6 +52,7 @@ void CustomAPIDeviceComponent::on_service_with_arrays(std::vector bool_arr } } +// NOLINTNEXTLINE(performance-unnecessary-value-param) void CustomAPIDeviceComponent::on_ha_state_changed(std::string entity_id, std::string state) { ESP_LOGI(TAG, "Home Assistant state changed for %s: %s", entity_id.c_str(), state.c_str()); ESP_LOGI(TAG, "This subscription uses std::string API for backward compatibility"); diff --git a/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.h b/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.h index 0720b9e7de..4d519d3ed1 100644 --- a/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.h +++ b/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.h @@ -24,6 +24,7 @@ class CustomAPIDeviceComponent : public Component, public CustomAPIDevice { std::vector float_array, std::vector string_array); // Test Home Assistant state subscription with std::string API + // NOLINTNEXTLINE(performance-unnecessary-value-param) void on_ha_state_changed(std::string entity_id, std::string state); }; From 9f2693ead5405aa653fce7f8451428666f6b958c Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Wed, 10 Dec 2025 00:59:58 +0100 Subject: [PATCH 0601/1145] [core] Packages refactor and conditional package inclusion (package refactor part 1) (#11605) Co-authored-by: J. Nick Koston --- esphome/components/packages/__init__.py | 151 ++++++++--- esphome/config.py | 10 +- .../component_tests/packages/test_packages.py | 238 ++++++++++++++++-- .../06-package_merging.approved.yaml | 43 ++++ .../06-package_merging.input.yaml | 61 +++++ tests/unit_tests/test_substitutions.py | 6 +- 6 files changed, 446 insertions(+), 63 deletions(-) create mode 100644 tests/unit_tests/fixtures/substitutions/06-package_merging.approved.yaml create mode 100644 tests/unit_tests/fixtures/substitutions/06-package_merging.input.yaml diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 15ab11d6b0..6d353ccf11 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -1,5 +1,9 @@ +from collections import UserDict +from collections.abc import Callable +from functools import reduce import logging from pathlib import Path +from typing import Any from esphome import git, yaml_util from esphome.components.substitutions.jinja import has_jinja @@ -15,6 +19,7 @@ from esphome.const import ( CONF_PATH, CONF_REF, CONF_REFRESH, + CONF_SUBSTITUTIONS, CONF_URL, CONF_USERNAME, CONF_VARS, @@ -27,32 +32,43 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = CONF_PACKAGES -def valid_package_contents(package_config: dict): - """Validates that a package_config that will be merged looks as much as possible to a valid config - to fail early on obvious mistakes.""" - if isinstance(package_config, dict): - if CONF_URL in package_config: - # If a URL key is found, then make sure the config conforms to a remote package schema: - return REMOTE_PACKAGE_SCHEMA(package_config) +def validate_has_jinja(value: Any): + if not isinstance(value, str) or not has_jinja(value): + raise cv.Invalid("string does not contain Jinja syntax") + return value - # Validate manually since Voluptuous would regenerate dicts and lose metadata - # such as ESPHomeDataBase - for k, v in package_config.items(): - if not isinstance(k, str): - raise cv.Invalid("Package content keys must be strings") - if isinstance(v, (dict, list, Remove)): - continue # e.g. script: [], psram: !remove, logger: {level: debug} - if v is None: - continue # e.g. web_server: - if isinstance(v, str) and has_jinja(v): - # e.g: remote package shorthand: - # package_name: github://esphome/repo/file.yaml@${ branch } - continue - raise cv.Invalid("Invalid component content in package definition") - return package_config +def valid_package_contents(allow_jinja: bool = True) -> Callable[[Any], dict]: + """Returns a validator that checks if a package_config that will be merged looks as + much as possible to a valid config to fail early on obvious mistakes.""" - raise cv.Invalid("Package contents must be a dict") + def validator(package_config: dict) -> dict: + if isinstance(package_config, dict): + if CONF_URL in package_config: + # If a URL key is found, then make sure the config conforms to a remote package schema: + return REMOTE_PACKAGE_SCHEMA(package_config) + + # Validate manually since Voluptuous would regenerate dicts and lose metadata + # such as ESPHomeDataBase + for k, v in package_config.items(): + if not isinstance(k, str): + raise cv.Invalid("Package content keys must be strings") + if isinstance(v, (dict, list, Remove)): + continue # e.g. script: [], psram: !remove, logger: {level: debug} + if v is None: + continue # e.g. web_server: + if allow_jinja and isinstance(v, str) and has_jinja(v): + # e.g: remote package shorthand: + # package_name: github://esphome/repo/file.yaml@${ branch }, or: + # switch: ${ expression that evals to a switch } + continue + + raise cv.Invalid("Invalid component content in package definition") + return package_config + + raise cv.Invalid("Package contents must be a dict") + + return validator def expand_file_to_files(config: dict): @@ -142,7 +158,10 @@ REMOTE_PACKAGE_SCHEMA = cv.All( PACKAGE_SCHEMA = cv.Any( # A package definition is either: validate_source_shorthand, # A git URL shorthand string that expands to a remote package schema, or REMOTE_PACKAGE_SCHEMA, # a valid remote package schema, or - valid_package_contents, # Something that at least looks like an actual package, e.g. {wifi:{ssid: xxx}} + validate_has_jinja, # a Jinja string that may resolve to a package, or + valid_package_contents( + allow_jinja=True + ), # Something that at least looks like an actual package, e.g. {wifi:{ssid: xxx}} # which will have to be fully validated later as per each component's schema. ) @@ -235,32 +254,84 @@ def _process_remote_package(config: dict, skip_update: bool = False) -> dict: return {"packages": packages} -def _process_package(package_config, config, skip_update: bool = False): - recursive_package = package_config - if CONF_URL in package_config: - package_config = _process_remote_package(package_config, skip_update) - if isinstance(package_config, dict): - recursive_package = do_packages_pass(package_config, skip_update) - return merge_config(recursive_package, config) - - -def do_packages_pass(config: dict, skip_update: bool = False): +def _walk_packages( + config: dict, callback: Callable[[dict], dict], validate_deprecated: bool = True +) -> dict: if CONF_PACKAGES not in config: return config packages = config[CONF_PACKAGES] - with cv.prepend_path(CONF_PACKAGES): + + # The following block and `validate_deprecated` parameter can be safely removed + # once single-package deprecation is effective + if validate_deprecated: packages = CONFIG_SCHEMA(packages) + + with cv.prepend_path(CONF_PACKAGES): if isinstance(packages, dict): for package_name, package_config in reversed(packages.items()): with cv.prepend_path(package_name): - config = _process_package(package_config, config, skip_update) + package_config = callback(package_config) + packages[package_name] = _walk_packages(package_config, callback) elif isinstance(packages, list): - for package_config in reversed(packages): - config = _process_package(package_config, config, skip_update) + for idx in reversed(range(len(packages))): + with cv.prepend_path(idx): + package_config = callback(packages[idx]) + packages[idx] = _walk_packages(package_config, callback) else: raise cv.Invalid( f"Packages must be a key to value mapping or list, got {type(packages)} instead" ) - - del config[CONF_PACKAGES] + config[CONF_PACKAGES] = packages + return config + + +def do_packages_pass(config: dict, skip_update: bool = False) -> dict: + """Processes, downloads and validates all packages in the config. + Also extracts and merges all substitutions found in packages into the main config substitutions. + """ + if CONF_PACKAGES not in config: + return config + + substitutions = UserDict(config.pop(CONF_SUBSTITUTIONS, {})) + + def process_package_callback(package_config: dict) -> dict: + """This will be called for each package found in the config.""" + package_config = PACKAGE_SCHEMA(package_config) + if isinstance(package_config, str): + return package_config # Jinja string, skip processing + if CONF_URL in package_config: + package_config = _process_remote_package(package_config, skip_update) + # Extract substitutions from the package and merge them into the main substitutions: + substitutions.data = merge_config( + package_config.pop(CONF_SUBSTITUTIONS, {}), substitutions.data + ) + return package_config + + _walk_packages(config, process_package_callback) + + if substitutions: + config[CONF_SUBSTITUTIONS] = substitutions.data + + return config + + +def merge_packages(config: dict) -> dict: + """Merges all packages into the main config and removes the `packages:` key.""" + if CONF_PACKAGES not in config: + return config + + # Build flat list of all package configs to merge in priority order: + merge_list: list[dict] = [] + + validate_package = valid_package_contents(allow_jinja=False) + + def process_package_callback(package_config: dict) -> dict: + """This will be called for each package found in the config.""" + merge_list.append(validate_package(package_config)) + return package_config + + _walk_packages(config, process_package_callback, validate_deprecated=False) + # Merge all packages into the main config: + config = reduce(lambda new, old: merge_config(old, new), merge_list, config) + del config[CONF_PACKAGES] return config diff --git a/esphome/config.py b/esphome/config.py index 1c4cdd93c6..694716be34 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -1012,14 +1012,20 @@ def validate_config( CORE.raw_config = config - # 1.1. Resolve !extend and !remove and check for REPLACEME + # 1.1. Merge packages + if CONF_PACKAGES in config: + from esphome.components.packages import merge_packages + + config = merge_packages(config) + + # 1.2. Resolve !extend and !remove and check for REPLACEME # After this step, there will not be any Extend or Remove values in the config anymore try: resolve_extend_remove(config) except vol.Invalid as err: result.add_error(err) - # 1.2. Load external_components + # 1.3. Load external_components if CONF_EXTERNAL_COMPONENTS in config: from esphome.components.external_components import do_external_components_pass diff --git a/tests/component_tests/packages/test_packages.py b/tests/component_tests/packages/test_packages.py index 34760587df..3829e540d7 100644 --- a/tests/component_tests/packages/test_packages.py +++ b/tests/component_tests/packages/test_packages.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch import pytest -from esphome.components.packages import CONFIG_SCHEMA, do_packages_pass +from esphome.components.packages import CONFIG_SCHEMA, do_packages_pass, merge_packages from esphome.config import resolve_extend_remove from esphome.config_helpers import Extend, Remove import esphome.config_validation as cv @@ -27,6 +27,7 @@ from esphome.const import ( CONF_REFRESH, CONF_SENSOR, CONF_SSID, + CONF_SUBSTITUTIONS, CONF_UPDATE_INTERVAL, CONF_URL, CONF_VARS, @@ -68,11 +69,12 @@ def fixture_basic_esphome(): def packages_pass(config): """Wrapper around packages_pass that also resolves Extend and Remove.""" config = do_packages_pass(config) + config = merge_packages(config) resolve_extend_remove(config) return config -def test_package_unused(basic_esphome, basic_wifi): +def test_package_unused(basic_esphome, basic_wifi) -> None: """ Ensures do_package_pass does not change a config if packages aren't used. """ @@ -82,7 +84,7 @@ def test_package_unused(basic_esphome, basic_wifi): assert actual == config -def test_package_invalid_dict(basic_esphome, basic_wifi): +def test_package_invalid_dict(basic_esphome, basic_wifi) -> None: """ If a url: key is present, it's expected to be well-formed remote package spec. Ensure an error is raised if not. Any other simple dict passed as a package will be merged as usual but may fail later validation. @@ -107,7 +109,7 @@ def test_package_invalid_dict(basic_esphome, basic_wifi): ], ], ) -def test_package_shorthand(packages): +def test_package_shorthand(packages) -> None: CONFIG_SCHEMA(packages) @@ -133,12 +135,12 @@ def test_package_shorthand(packages): [3], ], ) -def test_package_invalid(packages): +def test_package_invalid(packages) -> None: with pytest.raises(cv.Invalid): CONFIG_SCHEMA(packages) -def test_package_include(basic_wifi, basic_esphome): +def test_package_include(basic_wifi, basic_esphome) -> None: """ Tests the simple case where an independent config present in a package is added to the top-level config as is. @@ -159,7 +161,7 @@ def test_single_package( basic_esphome, basic_wifi, caplog: pytest.LogCaptureFixture, -): +) -> None: """ Tests the simple case where a single package is added to the top-level config as is. In this test, the CONF_WIFI config is expected to be simply added to the top-level config. @@ -179,7 +181,7 @@ def test_single_package( assert "This method for including packages will go away in 2026.7.0" in caplog.text -def test_package_append(basic_wifi, basic_esphome): +def test_package_append(basic_wifi, basic_esphome) -> None: """ Tests the case where a key is present in both a package and top-level config. @@ -204,7 +206,7 @@ def test_package_append(basic_wifi, basic_esphome): assert actual == expected -def test_package_override(basic_wifi, basic_esphome): +def test_package_override(basic_wifi, basic_esphome) -> None: """ Ensures that the top-level configuration takes precedence over duplicate keys defined in a package. @@ -228,7 +230,7 @@ def test_package_override(basic_wifi, basic_esphome): assert actual == expected -def test_multiple_package_order(): +def test_multiple_package_order() -> None: """ Ensures that mutiple packages are merged in order. """ @@ -257,7 +259,7 @@ def test_multiple_package_order(): assert actual == expected -def test_package_list_merge(): +def test_package_list_merge() -> None: """ Ensures lists defined in both a package and the top-level config are merged correctly """ @@ -313,7 +315,7 @@ def test_package_list_merge(): assert actual == expected -def test_package_list_merge_by_id(): +def test_package_list_merge_by_id() -> None: """ Ensures that components with matching IDs are merged correctly. @@ -391,7 +393,7 @@ def test_package_list_merge_by_id(): assert actual == expected -def test_package_merge_by_id_with_list(): +def test_package_merge_by_id_with_list() -> None: """ Ensures that components with matching IDs are merged correctly when their configuration contains lists. @@ -430,7 +432,7 @@ def test_package_merge_by_id_with_list(): assert actual == expected -def test_package_merge_by_missing_id(): +def test_package_merge_by_missing_id() -> None: """ Ensures that a validation error is thrown when trying to extend a missing ID. """ @@ -466,7 +468,7 @@ def test_package_merge_by_missing_id(): assert error_raised -def test_package_list_remove_by_id(): +def test_package_list_remove_by_id() -> None: """ Ensures that components with matching IDs are removed correctly. @@ -517,7 +519,7 @@ def test_package_list_remove_by_id(): assert actual == expected -def test_multiple_package_list_remove_by_id(): +def test_multiple_package_list_remove_by_id() -> None: """ Ensures that components with matching IDs are removed correctly. @@ -563,7 +565,7 @@ def test_multiple_package_list_remove_by_id(): assert actual == expected -def test_package_dict_remove_by_id(basic_wifi, basic_esphome): +def test_package_dict_remove_by_id(basic_wifi, basic_esphome) -> None: """ Ensures that components with missing IDs are removed from dict. Ensures that the top-level configuration takes precedence over duplicate keys defined in a package. @@ -584,7 +586,7 @@ def test_package_dict_remove_by_id(basic_wifi, basic_esphome): assert actual == expected -def test_package_remove_by_missing_id(): +def test_package_remove_by_missing_id() -> None: """ Ensures that components with missing IDs are not merged. """ @@ -632,7 +634,7 @@ def test_package_remove_by_missing_id(): @patch("esphome.git.clone_or_update") def test_remote_packages_with_files_list( mock_clone_or_update, mock_is_file, mock_load_yaml -): +) -> None: """ Ensures that packages are loaded as mixed list of dictionary and strings """ @@ -704,7 +706,7 @@ def test_remote_packages_with_files_list( @patch("esphome.git.clone_or_update") def test_remote_packages_with_files_and_vars( mock_clone_or_update, mock_is_file, mock_load_yaml -): +) -> None: """ Ensures that packages are loaded as mixed list of dictionary and strings with vars """ @@ -793,3 +795,199 @@ def test_remote_packages_with_files_and_vars( actual = packages_pass(config) assert actual == expected + + +def test_packages_merge_substitutions() -> None: + """ + Tests that substitutions from packages in a complex package hierarchy + are extracted and merged into the top-level config. + """ + config = { + CONF_SUBSTITUTIONS: { + "a": 1, + "b": 2, + "c": 3, + }, + CONF_PACKAGES: { + "package1": { + "logger": { + "level": "DEBUG", + }, + CONF_PACKAGES: [ + { + CONF_SUBSTITUTIONS: { + "a": 10, + "e": 5, + }, + "sensor": [ + {"platform": "template", "id": "sensor1"}, + ], + }, + ], + "sensor": [ + {"platform": "template", "id": "sensor2"}, + ], + }, + "package2": { + "logger": { + "level": "VERBOSE", + }, + }, + "package3": { + CONF_PACKAGES: [ + { + CONF_PACKAGES: [ + { + CONF_SUBSTITUTIONS: { + "b": 20, + "d": 4, + }, + "sensor": [ + {"platform": "template", "id": "sensor3"}, + ], + }, + ], + CONF_SUBSTITUTIONS: { + "b": 20, + "d": 6, + }, + "sensor": [ + {"platform": "template", "id": "sensor4"}, + ], + }, + ], + }, + }, + } + + expected = { + CONF_SUBSTITUTIONS: {"a": 1, "e": 5, "b": 2, "d": 6, "c": 3}, + CONF_PACKAGES: { + "package1": { + "logger": { + "level": "DEBUG", + }, + CONF_PACKAGES: [ + { + "sensor": [ + {"platform": "template", "id": "sensor1"}, + ], + }, + ], + "sensor": [ + {"platform": "template", "id": "sensor2"}, + ], + }, + "package2": { + "logger": { + "level": "VERBOSE", + }, + }, + "package3": { + CONF_PACKAGES: [ + { + CONF_PACKAGES: [ + { + "sensor": [ + {"platform": "template", "id": "sensor3"}, + ], + }, + ], + "sensor": [ + {"platform": "template", "id": "sensor4"}, + ], + }, + ], + }, + }, + } + + actual = do_packages_pass(config) + assert actual == expected + + +def test_package_merge() -> None: + """ + Tests that all packages are merged into the top-level config. + """ + config = { + CONF_SUBSTITUTIONS: {"a": 1, "e": 5, "b": 2, "d": 6, "c": 3}, + CONF_PACKAGES: { + "package1": { + "logger": { + "level": "DEBUG", + }, + CONF_PACKAGES: [ + { + "sensor": [ + {"platform": "template", "id": "sensor1"}, + ], + }, + ], + "sensor": [ + {"platform": "template", "id": "sensor2"}, + ], + }, + "package2": { + "logger": { + "level": "VERBOSE", + }, + }, + "package3": { + CONF_PACKAGES: [ + { + CONF_PACKAGES: [ + { + "sensor": [ + {"platform": "template", "id": "sensor3"}, + ], + }, + ], + "sensor": [ + {"platform": "template", "id": "sensor4"}, + ], + }, + ], + }, + }, + } + expected = { + "sensor": [ + {"platform": "template", "id": "sensor1"}, + {"platform": "template", "id": "sensor2"}, + {"platform": "template", "id": "sensor3"}, + {"platform": "template", "id": "sensor4"}, + ], + "logger": {"level": "VERBOSE"}, + CONF_SUBSTITUTIONS: {"a": 1, "e": 5, "b": 2, "d": 6, "c": 3}, + } + actual = merge_packages(config) + + assert actual == expected + + +@pytest.mark.parametrize( + "invalid_package", + [ + 6, + "some string", + ["some string"], + None, + True, + {"some_component": 8}, + {3: 2}, + {"some_component": r"${unevaluated expression}"}, + ], +) +def test_package_merge_invalid(invalid_package) -> None: + """ + Tests that trying to merge an invalid package raises an error. + """ + config = { + CONF_PACKAGES: { + "some_package": invalid_package, + }, + } + + with pytest.raises(cv.Invalid): + merge_packages(config) diff --git a/tests/unit_tests/fixtures/substitutions/06-package_merging.approved.yaml b/tests/unit_tests/fixtures/substitutions/06-package_merging.approved.yaml new file mode 100644 index 0000000000..3fbf5660d5 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/06-package_merging.approved.yaml @@ -0,0 +1,43 @@ +fancy_component: &id001 + - id: component9 + value: 9 +some_component: + - id: component1 + value: 1 + - id: component2 + value: 2 + - id: component3 + value: 3 + - id: component4 + value: 4 + - id: component5 + value: 79 + power: 200 + - id: component6 + value: 6 + - id: component7 + value: 7 +switch: &id002 + - platform: gpio + id: switch1 + pin: 12 + - platform: gpio + id: switch2 + pin: 13 +display: + - platform: ili9xxx + dimensions: + width: 100 + height: 480 +substitutions: + extended_component: component5 + package_options: + alternative_package: + alternative_component: + - id: component8 + value: 8 + fancy_package: + fancy_component: *id001 + pin: 12 + some_switches: *id002 + package_selection: fancy_package diff --git a/tests/unit_tests/fixtures/substitutions/06-package_merging.input.yaml b/tests/unit_tests/fixtures/substitutions/06-package_merging.input.yaml new file mode 100644 index 0000000000..d937a89306 --- /dev/null +++ b/tests/unit_tests/fixtures/substitutions/06-package_merging.input.yaml @@ -0,0 +1,61 @@ +substitutions: + package_options: + alternative_package: + alternative_component: + - id: component8 + value: 8 + fancy_package: + fancy_component: + - id: component9 + value: 9 + + pin: 12 + some_switches: + - platform: gpio + id: switch1 + pin: ${pin} + - platform: gpio + id: switch2 + pin: ${pin+1} + + package_selection: fancy_package + +packages: + - ${ package_options[package_selection] } + - some_component: + - id: component1 + value: 1 + - some_component: + - id: component2 + value: 2 + - switch: ${ some_switches } + - packages: + package_with_defaults: !include + file: display.yaml + vars: + native_width: 100 + high_dpi: false + my_package: + packages: + - packages: + special_package: + substitutions: + extended_component: component5 + some_component: + - id: component3 + value: 3 + some_component: + - id: component4 + value: 4 + - id: !extend ${ extended_component } + power: 200 + value: 79 + some_component: + - id: component5 + value: 5 + +some_component: + - id: component6 + value: 6 + - id: component7 + value: 7 diff --git a/tests/unit_tests/test_substitutions.py b/tests/unit_tests/test_substitutions.py index cba1e398c3..1d8cb7631d 100644 --- a/tests/unit_tests/test_substitutions.py +++ b/tests/unit_tests/test_substitutions.py @@ -8,7 +8,7 @@ import pytest from esphome import config as config_module, yaml_util from esphome.components import substitutions -from esphome.components.packages import do_packages_pass +from esphome.components.packages import do_packages_pass, merge_packages from esphome.config import resolve_extend_remove from esphome.config_helpers import merge_config from esphome.const import CONF_SUBSTITUTIONS @@ -74,6 +74,8 @@ def verify_database(value: Any, path: str = "") -> str | None: return None if isinstance(value, dict): for k, v in value.items(): + if path == "" and k == CONF_SUBSTITUTIONS: + return None # ignore substitutions key at top level since it is merged. key_result = verify_database(k, f"{path}/{k}") if key_result is not None: return key_result @@ -144,6 +146,8 @@ def test_substitutions_fixtures( substitutions.do_substitution_pass(config, command_line_substitutions) + config = merge_packages(config) + resolve_extend_remove(config) verify_database_result = verify_database(config) if verify_database_result is not None: From 26770e09dcfda0df89f9e2448b087b019539db58 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:08:35 -0500 Subject: [PATCH 0602/1145] Bump version to 2025.12.0b1 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index a19120b9da..ecb156d1f3 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.0-dev +PROJECT_NUMBER = 2025.12.0b1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 8fa2d8da16..93dd39b982 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.0-dev" +__version__ = "2025.12.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 84d5348bd877a4c88fd532a6c5f9427443a13d5c Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:08:35 -0500 Subject: [PATCH 0603/1145] Bump version to 2026.1.0-dev --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index a19120b9da..503979b61e 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.0-dev +PROJECT_NUMBER = 2026.1.0-dev # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 8fa2d8da16..c94ead0be4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.0-dev" +__version__ = "2026.1.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 03c391bd43e599944ca7d1f49c1750fabbd6f271 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 04:19:29 +0100 Subject: [PATCH 0604/1145] [light] Add zero-copy support for API effect commands (#12384) --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_pb2.cpp | 7 +++++-- esphome/components/api/api_pb2.h | 5 +++-- esphome/components/api/api_pb2_dump.cpp | 4 +++- esphome/components/light/light_call.cpp | 9 +++++---- esphome/components/light/light_call.h | 4 +++- 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 2534ad0b1f..50af5061c0 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -579,7 +579,7 @@ message LightCommandRequest { bool has_flash_length = 16; uint32 flash_length = 17; bool has_effect = 18; - string effect = 19; + string effect = 19 [(pointer_to_buffer) = true]; uint32 device_id = 28 [(field_ifdef) = "USE_DEVICES"]; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 4b10610281..09b311c1e4 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -533,7 +533,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) { if (msg.has_flash_length) call.set_flash_length(msg.flash_length); if (msg.has_effect) - call.set_effect(msg.effect); + call.set_effect(reinterpret_cast(msg.effect), msg.effect_len); call.perform(); } #endif diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 128f82fe7f..4a89ee78e1 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -611,9 +611,12 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool LightCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 19: - this->effect = value.as_string(); + case 19: { + // Use raw data directly to avoid allocation + this->effect = value.data(); + this->effect_len = value.size(); break; + } default: return false; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 49f1ea3c52..f23a62fc3c 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -840,7 +840,7 @@ class LightStateResponse final : public StateResponseProtoMessage { class LightCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 32; - static constexpr uint8_t ESTIMATED_SIZE = 112; + static constexpr uint8_t ESTIMATED_SIZE = 122; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "light_command_request"; } #endif @@ -869,7 +869,8 @@ class LightCommandRequest final : public CommandProtoMessage { bool has_flash_length{false}; uint32_t flash_length{0}; bool has_effect{false}; - std::string effect{}; + const uint8_t *effect{nullptr}; + uint16_t effect_len{0}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index ca69d1ff00..5e271f41cb 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -999,7 +999,9 @@ void LightCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_flash_length", this->has_flash_length); dump_field(out, "flash_length", this->flash_length); dump_field(out, "has_effect", this->has_effect); - dump_field(out, "effect", this->effect); + out.append(" effect: "); + out.append(format_hex_pretty(this->effect, this->effect_len)); + out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index dca5861734..8161e8b814 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -504,8 +504,8 @@ color_mode_bitmask_t LightCall::get_suitable_color_modes_mask_() { #undef KEY } -LightCall &LightCall::set_effect(const std::string &effect) { - if (strcasecmp(effect.c_str(), "none") == 0) { +LightCall &LightCall::set_effect(const char *effect, size_t len) { + if (len == 4 && strncasecmp(effect, "none", 4) == 0) { this->set_effect(0); return *this; } @@ -513,15 +513,16 @@ LightCall &LightCall::set_effect(const std::string &effect) { bool found = false; for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) { LightEffect *e = this->parent_->effects_[i]; + const char *name = e->get_name(); - if (strcasecmp(effect.c_str(), e->get_name()) == 0) { + if (strncasecmp(effect, name, len) == 0 && name[len] == '\0') { this->set_effect(i + 1); found = true; break; } } if (!found) { - ESP_LOGW(TAG, "'%s': no such effect '%s'", this->parent_->get_name().c_str(), effect.c_str()); + ESP_LOGW(TAG, "'%s': no such effect '%.*s'", this->parent_->get_name().c_str(), (int) len, effect); } return *this; } diff --git a/esphome/components/light/light_call.h b/esphome/components/light/light_call.h index 6931b58b9d..0926ab6108 100644 --- a/esphome/components/light/light_call.h +++ b/esphome/components/light/light_call.h @@ -129,7 +129,9 @@ class LightCall { /// Set the effect of the light by its name. LightCall &set_effect(optional effect); /// Set the effect of the light by its name. - LightCall &set_effect(const std::string &effect); + LightCall &set_effect(const std::string &effect) { return this->set_effect(effect.data(), effect.size()); } + /// Set the effect of the light by its name and length (zero-copy from API). + LightCall &set_effect(const char *effect, size_t len); /// Set the effect of the light by its internal index number (only for internal use). LightCall &set_effect(uint32_t effect_number); LightCall &set_effect(optional effect_number); From d0fbc82f470a4bcbdc67b58d5596a712e3c426fc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 04:19:52 +0100 Subject: [PATCH 0605/1145] [esp32_ble_client] Use stack-based MAC formatting in auth logging (#12393) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/esp32_ble_client/ble_client_base.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 07e88c7528..a09390c747 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -524,10 +524,9 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ case ESP_GAP_BLE_AUTH_CMPL_EVT: if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr)) return; - esp_bd_addr_t bd_addr; - memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); - ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_, - format_hex(bd_addr, 6).c_str()); + char addr_str[MAC_ADDR_STR_LEN]; + format_mac_addr_upper(param->ble_security.auth_cmpl.bd_addr, addr_str); + ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_, addr_str); if (!param->ble_security.auth_cmpl.success) { this->log_error_("auth fail reason", param->ble_security.auth_cmpl.fail_reason); } else { From b1f9100b0283838b160b61033a5e45c0fdb58428 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 04:20:08 +0100 Subject: [PATCH 0606/1145] [core] Add constexpr parse_hex_char helper and simplify parse_hex (#12394) --- esphome/core/helpers.cpp | 13 +++---------- esphome/core/helpers.h | 11 +++++++++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 77102c8db2..6a4894419c 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -266,19 +266,12 @@ std::string make_name_with_suffix(const std::string &name, char sep, const char // Parsing & formatting size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) { - uint8_t val; size_t chars = std::min(length, 2 * count); for (size_t i = 2 * count - chars; i < 2 * count; i++, str++) { - if (*str >= '0' && *str <= '9') { - val = *str - '0'; - } else if (*str >= 'A' && *str <= 'F') { - val = 10 + (*str - 'A'); - } else if (*str >= 'a' && *str <= 'f') { - val = 10 + (*str - 'a'); - } else { + uint8_t val = parse_hex_char(*str); + if (val > 15) return 0; - } - data[i >> 1] = !(i & 1) ? val << 4 : data[i >> 1] | val; + data[i >> 1] = (i & 1) ? data[i >> 1] | val : val << 4; } return chars; } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 6054f03353..3e44e08dd4 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -624,6 +624,17 @@ template::value, int> = 0> optional< return parse_hex(str.c_str(), str.length()); } +/// Parse a hex character to its nibble value (0-15), returns 255 on invalid input +constexpr uint8_t parse_hex_char(char c) { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return 255; +} + /// Convert a nibble (0-15) to lowercase hex char inline char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; } From 567e82cfec7f448e03347e20ece6ade58e356bb8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 04:20:23 +0100 Subject: [PATCH 0607/1145] [api] Fix potential buffer overflow in noise PSK base64 decode (#12395) --- esphome/components/api/api_connection.cpp | 2 +- esphome/core/helpers.cpp | 44 +++++++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 09b311c1e4..5186e5afda 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1669,7 +1669,7 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption } else { ESP_LOGW(TAG, "Failed to clear encryption key"); } - } else if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) { + } else if (base64_decode(msg.key, psk.data(), psk.size()) != psk.size()) { ESP_LOGW(TAG, "Invalid encryption key length"); } else if (!this->parent_->save_noise_psk(psk, true)) { ESP_LOGW(TAG, "Failed to save encryption key"); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 6a4894419c..fb96869d21 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -473,22 +473,13 @@ std::string base64_encode(const uint8_t *buf, size_t buf_len) { } size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf_len) { - std::vector decoded = base64_decode(encoded_string); - if (decoded.size() > buf_len) { - ESP_LOGW(TAG, "Base64 decode: buffer too small, truncating"); - decoded.resize(buf_len); - } - memcpy(buf, decoded.data(), decoded.size()); - return decoded.size(); -} - -std::vector base64_decode(const std::string &encoded_string) { int in_len = encoded_string.size(); int i = 0; int j = 0; int in = 0; + size_t out = 0; uint8_t char_array_4[4], char_array_3[3]; - std::vector ret; + bool truncated = false; // SAFETY: The loop condition checks is_base64() before processing each character. // This ensures base64_find_char() is only called on valid base64 characters, @@ -504,8 +495,13 @@ std::vector base64_decode(const std::string &encoded_string) { char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (i = 0; (i < 3); i++) - ret.push_back(char_array_3[i]); + for (i = 0; i < 3; i++) { + if (out < buf_len) { + buf[out++] = char_array_3[i]; + } else { + truncated = true; + } + } i = 0; } } @@ -521,10 +517,28 @@ std::vector base64_decode(const std::string &encoded_string) { char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (j = 0; (j < i - 1); j++) - ret.push_back(char_array_3[j]); + for (j = 0; j < i - 1; j++) { + if (out < buf_len) { + buf[out++] = char_array_3[j]; + } else { + truncated = true; + } + } } + if (truncated) { + ESP_LOGW(TAG, "Base64 decode: buffer too small, truncating"); + } + + return out; +} + +std::vector base64_decode(const std::string &encoded_string) { + // Calculate maximum decoded size: every 4 base64 chars = 3 bytes + size_t max_len = ((encoded_string.size() + 3) / 4) * 3; + std::vector ret(max_len); + size_t actual_len = base64_decode(encoded_string, ret.data(), max_len); + ret.resize(actual_len); return ret; } From c124d72ea934e01b7b417993f6fbd957f0a7025e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 04:45:27 +0100 Subject: [PATCH 0608/1145] [esp8266] Eliminate up to 16ms socket latency (#12397) --- .../components/socket/lwip_raw_tcp_impl.cpp | 49 ++++++++++++++++--- esphome/components/socket/socket.h | 9 ++++ esphome/core/application.cpp | 7 +++ 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index e57af91b77..5538206058 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -14,13 +14,36 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#ifdef USE_ESP8266 +#include // For esp_schedule() +#endif + namespace esphome { namespace socket { +#ifdef USE_ESP8266 +// Flag to signal socket activity - checked by socket_delay() to exit early +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +static volatile bool s_socket_woke = false; + +void socket_delay(uint32_t ms) { + // Use esp_delay with a callback that checks if socket data arrived. + // This allows the delay to exit early when socket_wake() is called by + // lwip recv_fn/accept_fn callbacks, reducing socket latency. + s_socket_woke = false; + esp_delay(ms, []() { return !s_socket_woke; }); +} + +void socket_wake() { + s_socket_woke = true; + esp_schedule(); +} +#endif + static const char *const TAG = "socket.lwip"; // set to 1 to enable verbose lwip logging -#if 0 +#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if) #define LWIP_LOG(msg, ...) ESP_LOGVV(TAG, "socket %p: " msg, this, ##__VA_ARGS__) #else #define LWIP_LOG(msg, ...) @@ -323,9 +346,10 @@ class LWIPRawImpl : public Socket { for (int i = 0; i < iovcnt; i++) { ssize_t err = read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); if (err == -1) { - if (ret != 0) + if (ret != 0) { // if we already read some don't return an error break; + } return err; } ret += err; @@ -393,9 +417,10 @@ class LWIPRawImpl : public Socket { ssize_t written = internal_write(buf, len); if (written == -1) return -1; - if (written == 0) + if (written == 0) { // no need to output if nothing written return 0; + } if (nodelay_) { int err = internal_output(); if (err == -1) @@ -408,18 +433,20 @@ class LWIPRawImpl : public Socket { for (int i = 0; i < iovcnt; i++) { ssize_t err = internal_write(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); if (err == -1) { - if (written != 0) + if (written != 0) { // if we already read some don't return an error break; + } return err; } written += err; if ((size_t) err != iov[i].iov_len) break; } - if (written == 0) + if (written == 0) { // no need to output if nothing written return 0; + } if (nodelay_) { int err = internal_output(); if (err == -1) @@ -473,6 +500,10 @@ class LWIPRawImpl : public Socket { } else { pbuf_cat(rx_buf_, pb); } +#ifdef USE_ESP8266 + // Wake the main loop immediately so it can process the received data. + socket_wake(); +#endif return ERR_OK; } @@ -612,7 +643,7 @@ class LWIPRawListenImpl : public LWIPRawImpl { } private: - err_t accept_fn(struct tcp_pcb *newpcb, err_t err) { + err_t accept_fn_(struct tcp_pcb *newpcb, err_t err) { LWIP_LOG("accept(newpcb=%p err=%d)", newpcb, err); if (err != ERR_OK || newpcb == nullptr) { // "An error code if there has been an error accepting. Only return ERR_ABRT if you have @@ -633,12 +664,16 @@ class LWIPRawListenImpl : public LWIPRawImpl { sock->init(); accepted_sockets_[accepted_socket_count_++] = std::move(sock); LWIP_LOG("Accepted connection, queue size: %d", accepted_socket_count_); +#ifdef USE_ESP8266 + // Wake the main loop immediately so it can accept the new connection. + socket_wake(); +#endif return ERR_OK; } static err_t s_accept_fn(void *arg, struct tcp_pcb *newpcb, err_t err) { LWIPRawListenImpl *arg_this = reinterpret_cast(arg); - return arg_this->accept_fn(newpcb, err); + return arg_this->accept_fn_(newpcb, err); } // Accept queue - holds incoming connections briefly until the event loop calls accept() diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 78a89fe008..8936b2cd10 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -82,6 +82,15 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::stri /// Set a sockaddr to the any address and specified port for the IP version used by socket_ip(). socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port); +#if defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP) +/// Delay that can be woken early by socket activity. +/// On ESP8266, lwip callbacks set a flag and call esp_schedule() to wake the delay. +void socket_delay(uint32_t ms); + +/// Called by lwip callbacks to signal socket activity and wake delay. +void socket_wake(); +#endif + } // namespace socket } // namespace esphome #endif diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 75814ae253..a85d671a07 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -12,6 +12,10 @@ #include "esphome/components/status_led/status_led.h" #endif +#if defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP) +#include "esphome/components/socket/socket.h" +#endif + #ifdef USE_SOCKET_SELECT_SUPPORT #include @@ -627,6 +631,9 @@ void Application::yield_with_select_(uint32_t delay_ms) { // No sockets registered, use regular delay delay(delay_ms); } +#elif defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP) + // No select support but can wake on socket activity via esp_schedule() + socket::socket_delay(delay_ms); #else // No select support, use regular delay delay(delay_ms); From d1d376ebc88186532b09afa20be1440415279ffc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 13:05:01 +0100 Subject: [PATCH 0609/1145] Bump actions/create-github-app-token from 2.2.0 to 2.2.1 (#12370) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto-label-pr.yml | 2 +- .github/workflows/release.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-label-pr.yml b/.github/workflows/auto-label-pr.yml index 39164fc2ea..8e96297cc0 100644 --- a/.github/workflows/auto-label-pr.yml +++ b/.github/workflows/auto-label-pr.yml @@ -26,7 +26,7 @@ jobs: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 with: app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 51aa1f885e..9933f63a1c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -221,7 +221,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} @@ -256,7 +256,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} @@ -287,7 +287,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} From 7a9fce90cb1411a268701cc57b03d811db4269d1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 18:13:40 +0100 Subject: [PATCH 0610/1145] [text] Add integration tests for text command API (#12401) --- tests/integration/fixtures/text_command.yaml | 37 ++++++ tests/integration/test_text_command.py | 126 +++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 tests/integration/fixtures/text_command.yaml create mode 100644 tests/integration/test_text_command.py diff --git a/tests/integration/fixtures/text_command.yaml b/tests/integration/fixtures/text_command.yaml new file mode 100644 index 0000000000..cc91e2f792 --- /dev/null +++ b/tests/integration/fixtures/text_command.yaml @@ -0,0 +1,37 @@ +esphome: + name: host-text-command-test + +host: + +api: + batch_delay: 0ms + +logger: + +text: + - platform: template + name: "Test Text" + id: test_text + optimistic: true + min_length: 0 + max_length: 255 + mode: text + initial_value: "initial" + + - platform: template + name: "Test Password" + id: test_password + optimistic: true + min_length: 4 + max_length: 32 + mode: password + initial_value: "secret" + + - platform: template + name: "Test Text Long" + id: test_text_long + optimistic: true + min_length: 0 + max_length: 255 + mode: text + initial_value: "" diff --git a/tests/integration/test_text_command.py b/tests/integration/test_text_command.py new file mode 100644 index 0000000000..82fe981578 --- /dev/null +++ b/tests/integration/test_text_command.py @@ -0,0 +1,126 @@ +"""Integration test for text command zero-copy optimization. + +Tests that TextCommandRequest correctly handles the pointer_to_buffer +optimization for the state field, ensuring text values are properly +transmitted via the API. +""" + +from __future__ import annotations + +import asyncio +from typing import Any + +from aioesphomeapi import TextInfo, TextState +import pytest + +from .state_utils import InitialStateHelper, require_entity +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_text_command( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test text command with various string values including edge cases.""" + loop = asyncio.get_running_loop() + async with run_compiled(yaml_config), api_client_connected() as client: + # Verify we can get device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "host-text-command-test" + + # Get list of entities + entities, _ = await client.list_entities_services() + + # Find our text entities using require_entity + test_text = require_entity(entities, "test_text", TextInfo, "Test Text entity") + test_password = require_entity( + entities, "test_password", TextInfo, "Test Password entity" + ) + test_text_long = require_entity( + entities, "test_text_long", TextInfo, "Test Text Long entity" + ) + + # Track state changes + states: dict[int, Any] = {} + state_futures: dict[int, asyncio.Future[Any]] = {} + + def on_state(state: Any) -> None: + states[state.key] = state + if state.key in state_futures and not state_futures[state.key].done(): + state_futures[state.key].set_result(state) + + # Set up InitialStateHelper to swallow initial state broadcasts + initial_state_helper = InitialStateHelper(entities) + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + # Wait for all initial states to be received + try: + await initial_state_helper.wait_for_initial_states() + except TimeoutError: + pytest.fail("Timeout waiting for initial states") + + # Verify initial states were received + assert test_text.key in initial_state_helper.initial_states + initial_text_state = initial_state_helper.initial_states[test_text.key] + assert isinstance(initial_text_state, TextState) + assert initial_text_state.state == "initial" + + async def wait_for_state_change(key: int, timeout: float = 2.0) -> Any: + """Wait for a state change for the given entity key.""" + state_futures[key] = loop.create_future() + try: + return await asyncio.wait_for(state_futures[key], timeout) + finally: + state_futures.pop(key, None) + + # Test 1: Simple text value + client.text_command(key=test_text.key, state="hello world") + state = await wait_for_state_change(test_text.key) + assert state.state == "hello world" + + # Test 2: Empty string (edge case for zero-copy) + client.text_command(key=test_text.key, state="") + state = await wait_for_state_change(test_text.key) + assert state.state == "" + + # Test 3: Single character + client.text_command(key=test_text.key, state="x") + state = await wait_for_state_change(test_text.key) + assert state.state == "x" + + # Test 4: String with special characters + client.text_command(key=test_text.key, state="hello\tworld\n!") + state = await wait_for_state_change(test_text.key) + assert state.state == "hello\tworld\n!" + + # Test 5: Unicode characters + client.text_command(key=test_text.key, state="hello 世界 🌍") + state = await wait_for_state_change(test_text.key) + assert state.state == "hello 世界 🌍" + + # Test 6: Long string (tests buffer handling) + long_text = "a" * 200 + client.text_command(key=test_text_long.key, state=long_text) + state = await wait_for_state_change(test_text_long.key) + assert state.state == long_text + assert len(state.state) == 200 + + # Test 7: Password field (same mechanism, different mode) + client.text_command(key=test_password.key, state="newpassword123") + state = await wait_for_state_change(test_password.key) + assert state.state == "newpassword123" + + # Test 8: String with null bytes embedded (edge case) + # Note: protobuf strings should handle this but it's good to verify + client.text_command(key=test_text.key, state="before\x00after") + state = await wait_for_state_change(test_text.key) + assert state.state == "before\x00after" + + # Test 9: Rapid successive commands (tests buffer reuse) + for i in range(5): + client.text_command(key=test_text.key, state=f"rapid_{i}") + state = await wait_for_state_change(test_text.key) + assert state.state == f"rapid_{i}" From 22918d3bd5d96793dce4ad208262b81ca71ea709 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 22:21:29 +0100 Subject: [PATCH 0611/1145] Bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12409) 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 2c3219e38e..8c830d99c7 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -41,7 +41,7 @@ jobs: python script/run-in-env.py pre-commit run --all-files - name: Commit changes - uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11 + uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0 with: commit-message: "Synchronise Device Classes from Home Assistant" committer: esphomebot From 1f0a27b18181cd13d8979b9ca88a21510c1844f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 22:34:24 +0100 Subject: [PATCH 0612/1145] Bump codecov/codecov-action from 5.5.1 to 5.5.2 (#12408) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03eadb5f0a..c067df237f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -152,7 +152,7 @@ jobs: . venv/bin/activate pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/ - name: Upload coverage to Codecov - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: token: ${{ secrets.CODECOV_TOKEN }} - name: Save Python virtual environment cache From 369cc70fdfa26b803ec24432952ddf2c0e5e571f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 11 Dec 2025 02:10:42 +0100 Subject: [PATCH 0613/1145] [climate] Save 48 bytes per entity by conditionally compiling visual overrides (#12406) --- esphome/components/climate/__init__.py | 5 +++++ esphome/components/climate/climate.cpp | 27 ++++++++++++++------------ esphome/components/climate/climate.h | 16 +++++++++------ esphome/core/defines.h | 1 + 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 5824e68141..b8e49db6c0 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -275,10 +275,13 @@ async def setup_climate_core_(var, config): visual = config[CONF_VISUAL] if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None: + cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES") cg.add(var.set_visual_min_temperature_override(min_temp)) if (max_temp := visual.get(CONF_MAX_TEMPERATURE)) is not None: + cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES") cg.add(var.set_visual_max_temperature_override(max_temp)) if (temp_step := visual.get(CONF_TEMPERATURE_STEP)) is not None: + cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES") cg.add( var.set_visual_temperature_step_override( temp_step[CONF_TARGET_TEMPERATURE], @@ -286,8 +289,10 @@ async def setup_climate_core_(var, config): ) ) if (min_humidity := visual.get(CONF_MIN_HUMIDITY)) is not None: + cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES") cg.add(var.set_visual_min_humidity_override(min_humidity)) if (max_humidity := visual.get(CONF_MAX_HUMIDITY)) is not None: + cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES") cg.add(var.set_visual_max_humidity_override(max_humidity)) if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index b0fba6aa62..3bc20a17c6 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -473,26 +473,28 @@ void Climate::publish_state() { ClimateTraits Climate::get_traits() { auto traits = this->traits(); - if (this->visual_min_temperature_override_.has_value()) { - traits.set_visual_min_temperature(*this->visual_min_temperature_override_); +#ifdef USE_CLIMATE_VISUAL_OVERRIDES + if (!std::isnan(this->visual_min_temperature_override_)) { + traits.set_visual_min_temperature(this->visual_min_temperature_override_); } - if (this->visual_max_temperature_override_.has_value()) { - traits.set_visual_max_temperature(*this->visual_max_temperature_override_); + if (!std::isnan(this->visual_max_temperature_override_)) { + traits.set_visual_max_temperature(this->visual_max_temperature_override_); } - if (this->visual_target_temperature_step_override_.has_value()) { - traits.set_visual_target_temperature_step(*this->visual_target_temperature_step_override_); - traits.set_visual_current_temperature_step(*this->visual_current_temperature_step_override_); + if (!std::isnan(this->visual_target_temperature_step_override_)) { + traits.set_visual_target_temperature_step(this->visual_target_temperature_step_override_); + traits.set_visual_current_temperature_step(this->visual_current_temperature_step_override_); } - if (this->visual_min_humidity_override_.has_value()) { - traits.set_visual_min_humidity(*this->visual_min_humidity_override_); + if (!std::isnan(this->visual_min_humidity_override_)) { + traits.set_visual_min_humidity(this->visual_min_humidity_override_); } - if (this->visual_max_humidity_override_.has_value()) { - traits.set_visual_max_humidity(*this->visual_max_humidity_override_); + if (!std::isnan(this->visual_max_humidity_override_)) { + traits.set_visual_max_humidity(this->visual_max_humidity_override_); } - +#endif return traits; } +#ifdef USE_CLIMATE_VISUAL_OVERRIDES void Climate::set_visual_min_temperature_override(float visual_min_temperature_override) { this->visual_min_temperature_override_ = visual_min_temperature_override; } @@ -513,6 +515,7 @@ void Climate::set_visual_min_humidity_override(float visual_min_humidity_overrid void Climate::set_visual_max_humidity_override(float visual_max_humidity_override) { this->visual_max_humidity_override_ = visual_max_humidity_override; } +#endif ClimateCall Climate::make_call() { return ClimateCall(this); } diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 28a73d8c05..82df4b815f 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -213,11 +213,13 @@ class Climate : public EntityBase { */ ClimateTraits get_traits(); +#ifdef USE_CLIMATE_VISUAL_OVERRIDES void set_visual_min_temperature_override(float visual_min_temperature_override); void set_visual_max_temperature_override(float visual_max_temperature_override); void set_visual_temperature_step_override(float target, float current); void set_visual_min_humidity_override(float visual_min_humidity_override); void set_visual_max_humidity_override(float visual_max_humidity_override); +#endif /// Check if a custom fan mode is currently active. bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; } @@ -321,12 +323,14 @@ class Climate : public EntityBase { CallbackManager state_callback_{}; CallbackManager control_callback_{}; ESPPreferenceObject rtc_; - optional visual_min_temperature_override_{}; - optional visual_max_temperature_override_{}; - optional visual_target_temperature_step_override_{}; - optional visual_current_temperature_step_override_{}; - optional visual_min_humidity_override_{}; - optional visual_max_humidity_override_{}; +#ifdef USE_CLIMATE_VISUAL_OVERRIDES + float visual_min_temperature_override_{NAN}; + float visual_max_temperature_override_{NAN}; + float visual_target_temperature_step_override_{NAN}; + float visual_current_temperature_step_override_{NAN}; + float visual_min_humidity_override_{NAN}; + float visual_max_humidity_override_{NAN}; +#endif private: /** The active custom fan mode (private - enforces use of safe setters). diff --git a/esphome/core/defines.h b/esphome/core/defines.h index a5170d73ff..750cab5bba 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -28,6 +28,7 @@ #define USE_BUTTON #define USE_CAMERA #define USE_CLIMATE +#define USE_CLIMATE_VISUAL_OVERRIDES #define USE_CONTROLLER_REGISTRY #define USE_COVER #define USE_DATETIME From 74218bc74271b4b282514b1fae1b8547cdbaef6b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 19:33:22 -0600 Subject: [PATCH 0614/1145] [api] Release prologue memory after noise handshake completes (#12412) --- esphome/components/api/api_frame_helper_noise.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index ae69f0b673..1d6f32ee9d 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -539,7 +539,8 @@ APIError APINoiseFrameHelper::init_handshake_() { if (aerr != APIError::OK) return aerr; // set_prologue copies it into handshakestate, so we can get rid of it now - prologue_ = {}; + // Use swap idiom to actually release memory (= {} only clears size, not capacity) + std::vector().swap(prologue_); err = noise_handshakestate_start(handshake_); aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED); From 8d1e68c4c17e572856419f91d49914358f9a3023 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:53:12 -0600 Subject: [PATCH 0615/1145] Bump tornado from 6.5.2 to 6.5.3 (#12430) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 71aaf47ddb..7a50e1296f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ PyYAML==6.0.3 paho-mqtt==1.6.1 colorama==0.4.6 icmplib==3.0.4 -tornado==6.5.2 +tornado==6.5.3 tzlocal==5.3.1 # from time tzdata>=2021.1 # from time pyserial==3.5 From d30d8156c1065897748c66a431df19d6102c343b Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 12 Dec 2025 10:31:17 -0500 Subject: [PATCH 0616/1145] [http_request] Skip update check when network not connected (#12418) Co-authored-by: Claude --- .../components/http_request/update/http_request_update.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index c91b0eba73..26af754e69 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -36,6 +36,10 @@ void HttpRequestUpdate::setup() { } void HttpRequestUpdate::update() { + if (!network::is_connected()) { + ESP_LOGD(TAG, "Network not connected, skipping update check"); + return; + } #ifdef USE_ESP32 xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, 1, &this->update_task_handle_); #else From 1d13d18a165fcefcae203643db9a35e5c25c460c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 04:19:29 +0100 Subject: [PATCH 0617/1145] [light] Add zero-copy support for API effect commands (#12384) --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_pb2.cpp | 7 +++++-- esphome/components/api/api_pb2.h | 5 +++-- esphome/components/api/api_pb2_dump.cpp | 4 +++- esphome/components/light/light_call.cpp | 9 +++++---- esphome/components/light/light_call.h | 4 +++- 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 2534ad0b1f..50af5061c0 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -579,7 +579,7 @@ message LightCommandRequest { bool has_flash_length = 16; uint32 flash_length = 17; bool has_effect = 18; - string effect = 19; + string effect = 19 [(pointer_to_buffer) = true]; uint32 device_id = 28 [(field_ifdef) = "USE_DEVICES"]; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 4b10610281..09b311c1e4 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -533,7 +533,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) { if (msg.has_flash_length) call.set_flash_length(msg.flash_length); if (msg.has_effect) - call.set_effect(msg.effect); + call.set_effect(reinterpret_cast(msg.effect), msg.effect_len); call.perform(); } #endif diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 128f82fe7f..4a89ee78e1 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -611,9 +611,12 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool LightCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 19: - this->effect = value.as_string(); + case 19: { + // Use raw data directly to avoid allocation + this->effect = value.data(); + this->effect_len = value.size(); break; + } default: return false; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 49f1ea3c52..f23a62fc3c 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -840,7 +840,7 @@ class LightStateResponse final : public StateResponseProtoMessage { class LightCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 32; - static constexpr uint8_t ESTIMATED_SIZE = 112; + static constexpr uint8_t ESTIMATED_SIZE = 122; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "light_command_request"; } #endif @@ -869,7 +869,8 @@ class LightCommandRequest final : public CommandProtoMessage { bool has_flash_length{false}; uint32_t flash_length{0}; bool has_effect{false}; - std::string effect{}; + const uint8_t *effect{nullptr}; + uint16_t effect_len{0}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index ca69d1ff00..5e271f41cb 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -999,7 +999,9 @@ void LightCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_flash_length", this->has_flash_length); dump_field(out, "flash_length", this->flash_length); dump_field(out, "has_effect", this->has_effect); - dump_field(out, "effect", this->effect); + out.append(" effect: "); + out.append(format_hex_pretty(this->effect, this->effect_len)); + out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index dca5861734..8161e8b814 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -504,8 +504,8 @@ color_mode_bitmask_t LightCall::get_suitable_color_modes_mask_() { #undef KEY } -LightCall &LightCall::set_effect(const std::string &effect) { - if (strcasecmp(effect.c_str(), "none") == 0) { +LightCall &LightCall::set_effect(const char *effect, size_t len) { + if (len == 4 && strncasecmp(effect, "none", 4) == 0) { this->set_effect(0); return *this; } @@ -513,15 +513,16 @@ LightCall &LightCall::set_effect(const std::string &effect) { bool found = false; for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) { LightEffect *e = this->parent_->effects_[i]; + const char *name = e->get_name(); - if (strcasecmp(effect.c_str(), e->get_name()) == 0) { + if (strncasecmp(effect, name, len) == 0 && name[len] == '\0') { this->set_effect(i + 1); found = true; break; } } if (!found) { - ESP_LOGW(TAG, "'%s': no such effect '%s'", this->parent_->get_name().c_str(), effect.c_str()); + ESP_LOGW(TAG, "'%s': no such effect '%.*s'", this->parent_->get_name().c_str(), (int) len, effect); } return *this; } diff --git a/esphome/components/light/light_call.h b/esphome/components/light/light_call.h index 6931b58b9d..0926ab6108 100644 --- a/esphome/components/light/light_call.h +++ b/esphome/components/light/light_call.h @@ -129,7 +129,9 @@ class LightCall { /// Set the effect of the light by its name. LightCall &set_effect(optional effect); /// Set the effect of the light by its name. - LightCall &set_effect(const std::string &effect); + LightCall &set_effect(const std::string &effect) { return this->set_effect(effect.data(), effect.size()); } + /// Set the effect of the light by its name and length (zero-copy from API). + LightCall &set_effect(const char *effect, size_t len); /// Set the effect of the light by its internal index number (only for internal use). LightCall &set_effect(uint32_t effect_number); LightCall &set_effect(optional effect_number); From 78b76045ce6fc3ebadb54740b75c23fa54678e2e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 04:20:23 +0100 Subject: [PATCH 0618/1145] [api] Fix potential buffer overflow in noise PSK base64 decode (#12395) --- esphome/components/api/api_connection.cpp | 2 +- esphome/core/helpers.cpp | 44 +++++++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 09b311c1e4..5186e5afda 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1669,7 +1669,7 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption } else { ESP_LOGW(TAG, "Failed to clear encryption key"); } - } else if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) { + } else if (base64_decode(msg.key, psk.data(), psk.size()) != psk.size()) { ESP_LOGW(TAG, "Invalid encryption key length"); } else if (!this->parent_->save_noise_psk(psk, true)) { ESP_LOGW(TAG, "Failed to save encryption key"); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 77102c8db2..732e8b6f8b 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -480,22 +480,13 @@ std::string base64_encode(const uint8_t *buf, size_t buf_len) { } size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf_len) { - std::vector decoded = base64_decode(encoded_string); - if (decoded.size() > buf_len) { - ESP_LOGW(TAG, "Base64 decode: buffer too small, truncating"); - decoded.resize(buf_len); - } - memcpy(buf, decoded.data(), decoded.size()); - return decoded.size(); -} - -std::vector base64_decode(const std::string &encoded_string) { int in_len = encoded_string.size(); int i = 0; int j = 0; int in = 0; + size_t out = 0; uint8_t char_array_4[4], char_array_3[3]; - std::vector ret; + bool truncated = false; // SAFETY: The loop condition checks is_base64() before processing each character. // This ensures base64_find_char() is only called on valid base64 characters, @@ -511,8 +502,13 @@ std::vector base64_decode(const std::string &encoded_string) { char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (i = 0; (i < 3); i++) - ret.push_back(char_array_3[i]); + for (i = 0; i < 3; i++) { + if (out < buf_len) { + buf[out++] = char_array_3[i]; + } else { + truncated = true; + } + } i = 0; } } @@ -528,10 +524,28 @@ std::vector base64_decode(const std::string &encoded_string) { char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (j = 0; (j < i - 1); j++) - ret.push_back(char_array_3[j]); + for (j = 0; j < i - 1; j++) { + if (out < buf_len) { + buf[out++] = char_array_3[j]; + } else { + truncated = true; + } + } } + if (truncated) { + ESP_LOGW(TAG, "Base64 decode: buffer too small, truncating"); + } + + return out; +} + +std::vector base64_decode(const std::string &encoded_string) { + // Calculate maximum decoded size: every 4 base64 chars = 3 bytes + size_t max_len = ((encoded_string.size() + 3) / 4) * 3; + std::vector ret(max_len); + size_t actual_len = base64_decode(encoded_string, ret.data(), max_len); + ret.resize(actual_len); return ret; } From 5567d96dd961c0379e3f3d6f512e3310a8cc6abe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 04:45:27 +0100 Subject: [PATCH 0619/1145] [esp8266] Eliminate up to 16ms socket latency (#12397) --- .../components/socket/lwip_raw_tcp_impl.cpp | 49 ++++++++++++++++--- esphome/components/socket/socket.h | 9 ++++ esphome/core/application.cpp | 7 +++ 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index e57af91b77..5538206058 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -14,13 +14,36 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#ifdef USE_ESP8266 +#include // For esp_schedule() +#endif + namespace esphome { namespace socket { +#ifdef USE_ESP8266 +// Flag to signal socket activity - checked by socket_delay() to exit early +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +static volatile bool s_socket_woke = false; + +void socket_delay(uint32_t ms) { + // Use esp_delay with a callback that checks if socket data arrived. + // This allows the delay to exit early when socket_wake() is called by + // lwip recv_fn/accept_fn callbacks, reducing socket latency. + s_socket_woke = false; + esp_delay(ms, []() { return !s_socket_woke; }); +} + +void socket_wake() { + s_socket_woke = true; + esp_schedule(); +} +#endif + static const char *const TAG = "socket.lwip"; // set to 1 to enable verbose lwip logging -#if 0 +#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if) #define LWIP_LOG(msg, ...) ESP_LOGVV(TAG, "socket %p: " msg, this, ##__VA_ARGS__) #else #define LWIP_LOG(msg, ...) @@ -323,9 +346,10 @@ class LWIPRawImpl : public Socket { for (int i = 0; i < iovcnt; i++) { ssize_t err = read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); if (err == -1) { - if (ret != 0) + if (ret != 0) { // if we already read some don't return an error break; + } return err; } ret += err; @@ -393,9 +417,10 @@ class LWIPRawImpl : public Socket { ssize_t written = internal_write(buf, len); if (written == -1) return -1; - if (written == 0) + if (written == 0) { // no need to output if nothing written return 0; + } if (nodelay_) { int err = internal_output(); if (err == -1) @@ -408,18 +433,20 @@ class LWIPRawImpl : public Socket { for (int i = 0; i < iovcnt; i++) { ssize_t err = internal_write(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); if (err == -1) { - if (written != 0) + if (written != 0) { // if we already read some don't return an error break; + } return err; } written += err; if ((size_t) err != iov[i].iov_len) break; } - if (written == 0) + if (written == 0) { // no need to output if nothing written return 0; + } if (nodelay_) { int err = internal_output(); if (err == -1) @@ -473,6 +500,10 @@ class LWIPRawImpl : public Socket { } else { pbuf_cat(rx_buf_, pb); } +#ifdef USE_ESP8266 + // Wake the main loop immediately so it can process the received data. + socket_wake(); +#endif return ERR_OK; } @@ -612,7 +643,7 @@ class LWIPRawListenImpl : public LWIPRawImpl { } private: - err_t accept_fn(struct tcp_pcb *newpcb, err_t err) { + err_t accept_fn_(struct tcp_pcb *newpcb, err_t err) { LWIP_LOG("accept(newpcb=%p err=%d)", newpcb, err); if (err != ERR_OK || newpcb == nullptr) { // "An error code if there has been an error accepting. Only return ERR_ABRT if you have @@ -633,12 +664,16 @@ class LWIPRawListenImpl : public LWIPRawImpl { sock->init(); accepted_sockets_[accepted_socket_count_++] = std::move(sock); LWIP_LOG("Accepted connection, queue size: %d", accepted_socket_count_); +#ifdef USE_ESP8266 + // Wake the main loop immediately so it can accept the new connection. + socket_wake(); +#endif return ERR_OK; } static err_t s_accept_fn(void *arg, struct tcp_pcb *newpcb, err_t err) { LWIPRawListenImpl *arg_this = reinterpret_cast(arg); - return arg_this->accept_fn(newpcb, err); + return arg_this->accept_fn_(newpcb, err); } // Accept queue - holds incoming connections briefly until the event loop calls accept() diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 78a89fe008..8936b2cd10 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -82,6 +82,15 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::stri /// Set a sockaddr to the any address and specified port for the IP version used by socket_ip(). socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port); +#if defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP) +/// Delay that can be woken early by socket activity. +/// On ESP8266, lwip callbacks set a flag and call esp_schedule() to wake the delay. +void socket_delay(uint32_t ms); + +/// Called by lwip callbacks to signal socket activity and wake delay. +void socket_wake(); +#endif + } // namespace socket } // namespace esphome #endif diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 75814ae253..a85d671a07 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -12,6 +12,10 @@ #include "esphome/components/status_led/status_led.h" #endif +#if defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP) +#include "esphome/components/socket/socket.h" +#endif + #ifdef USE_SOCKET_SELECT_SUPPORT #include @@ -627,6 +631,9 @@ void Application::yield_with_select_(uint32_t delay_ms) { // No sockets registered, use regular delay delay(delay_ms); } +#elif defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP) + // No select support but can wake on socket activity via esp_schedule() + socket::socket_delay(delay_ms); #else // No select support, use regular delay delay(delay_ms); From 2c77668a058ae669dead37974e4bc48d7dc8fa99 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 12 Dec 2025 10:31:17 -0500 Subject: [PATCH 0620/1145] [http_request] Skip update check when network not connected (#12418) Co-authored-by: Claude --- .../components/http_request/update/http_request_update.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index c91b0eba73..26af754e69 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -36,6 +36,10 @@ void HttpRequestUpdate::setup() { } void HttpRequestUpdate::update() { + if (!network::is_connected()) { + ESP_LOGD(TAG, "Network not connected, skipping update check"); + return; + } #ifdef USE_ESP32 xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, 1, &this->update_task_handle_); #else From c9506b056d3157af85250dc3a8410679436f0d42 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 12 Dec 2025 11:12:58 -0500 Subject: [PATCH 0621/1145] Bump version to 2025.12.0b2 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index ecb156d1f3..75c624bf2b 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.0b1 +PROJECT_NUMBER = 2025.12.0b2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 93dd39b982..61bdc7df8d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.0b1" +__version__ = "2025.12.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 26a08e3ae324653d351de3dfa333998ed0a23be4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 13:15:28 -0600 Subject: [PATCH 0622/1145] Bump actions/upload-artifact from 5.0.0 to 6.0.0 (#12452) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-api-proto.yml | 2 +- .github/workflows/ci.yml | 4 ++-- .github/workflows/release.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-api-proto.yml b/.github/workflows/ci-api-proto.yml index a0c6568345..4c4bbf9981 100644 --- a/.github/workflows/ci-api-proto.yml +++ b/.github/workflows/ci-api-proto.yml @@ -62,7 +62,7 @@ jobs: run: git diff - if: failure() name: Archive artifacts - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: generated-proto-files path: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c067df237f..141c9cc1d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -821,7 +821,7 @@ jobs: fi - name: Upload memory analysis JSON - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: memory-analysis-target path: memory-analysis-target.json @@ -885,7 +885,7 @@ jobs: --platform "$platform" - name: Upload memory analysis JSON - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: memory-analysis-pr path: memory-analysis-pr.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9933f63a1c..34f4416029 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -138,7 +138,7 @@ jobs: # version: ${{ needs.init.outputs.tag }} - name: Upload digests - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: digests-${{ matrix.platform.arch }} path: /tmp/digests From b3e967a2330cd4f5291e6fe3b6cefb7b5c02129d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 13:15:41 -0600 Subject: [PATCH 0623/1145] Bump actions/download-artifact from 6.0.0 to 7.0.0 (#12449) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 4 ++-- .github/workflows/release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 141c9cc1d6..aab565ca59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -915,13 +915,13 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Download target analysis JSON - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: memory-analysis-target path: ./memory-analysis continue-on-error: true - name: Download PR analysis JSON - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: memory-analysis-pr path: ./memory-analysis diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 34f4416029..10194aa599 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -171,7 +171,7 @@ jobs: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Download digests - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: pattern: digests-* path: /tmp/digests From 2b40af34594bbfe0c4d7ec7dd0e981f653855b72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 13:16:29 -0600 Subject: [PATCH 0624/1145] Bump actions/cache from 4.3.0 to 5.0.1 (#12450) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aab565ca59..434aa388f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: venv # yamllint disable-line rule:line-length @@ -157,7 +157,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} - name: Save Python virtual environment cache if: github.ref == 'refs/heads/dev' - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: venv key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }} @@ -193,7 +193,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Restore components graph cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: .temp/components_graph.json key: components-graph-${{ hashFiles('esphome/components/**/*.py') }} @@ -223,7 +223,7 @@ jobs: echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT - name: Save components graph cache if: github.ref == 'refs/heads/dev' - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: .temp/components_graph.json key: components-graph-${{ hashFiles('esphome/components/**/*.py') }} @@ -245,7 +245,7 @@ jobs: python-version: "3.13" - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: venv key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }} @@ -334,14 +334,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} @@ -413,14 +413,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} @@ -502,14 +502,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} @@ -735,7 +735,7 @@ jobs: - name: Restore cached memory analysis id: cache-memory-analysis if: steps.check-script.outputs.skip != 'true' - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: memory-analysis-target.json key: ${{ steps.cache-key.outputs.cache-key }} @@ -759,7 +759,7 @@ jobs: - name: Cache platformio if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }} @@ -800,7 +800,7 @@ jobs: - name: Save memory analysis to cache if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success' - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: memory-analysis-target.json key: ${{ steps.cache-key.outputs.cache-key }} @@ -847,7 +847,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Cache platformio - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }} From 4993bb2f495c52f6173634452c1964c7ebab69b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 13:16:41 -0600 Subject: [PATCH 0625/1145] Bump github/codeql-action from 4.31.7 to 4.31.8 (#12451) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 481ad0ec34..f917ecd8f8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7 + uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -86,6 +86,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7 + uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 with: category: "/language:${{matrix.language}}" From 9126b32c359f3770f23168bc5c55375d209d02ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 13:17:08 -0600 Subject: [PATCH 0626/1145] Bump actions/cache from 4.3.0 to 5.0.1 in /.github/actions/restore-python (#12453) 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 c4ac3d1a9e..75586fd854 100644 --- a/.github/actions/restore-python/action.yml +++ b/.github/actions/restore-python/action.yml @@ -22,7 +22,7 @@ runs: python-version: ${{ inputs.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: venv # yamllint disable-line rule:line-length From 51b187954a411378f3c151f3424a41e65b805960 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:20:06 +0000 Subject: [PATCH 0627/1145] Bump ruff from 0.14.8 to 0.14.9 (#12448) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 49b87866f1..2f5076a6e6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.8 + rev: v0.14.9 hooks: # Run the linter. - id: ruff diff --git a/requirements_test.txt b/requirements_test.txt index 60656712b9..bfb833e04d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==4.0.4 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.14.8 # also change in .pre-commit-config.yaml when updating +ruff==0.14.9 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating pre-commit From 1a43a06dd4ecdd15b3507fd0c763704f4247b703 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sat, 13 Dec 2025 10:15:50 +0900 Subject: [PATCH 0628/1145] Add USE_SHA256 define to sha256 component to enable tests (#12457) --- esphome/components/sha256/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/sha256/__init__.py b/esphome/components/sha256/__init__.py index f07157416d..5db0e77b76 100644 --- a/esphome/components/sha256/__init__.py +++ b/esphome/components/sha256/__init__.py @@ -12,6 +12,8 @@ CONFIG_SCHEMA = cv.Schema({}) async def to_code(config: ConfigType) -> None: + cg.add_define("USE_SHA256") + # Add OpenSSL library for host platform if not CORE.is_host: return From ff7651875e34a377df943ce2f11e950bf88aed5e Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sat, 13 Dec 2025 10:19:31 +0900 Subject: [PATCH 0629/1145] Add HMAC-MD5 component tests (#12459) --- esphome/components/hmac_md5/__init__.py | 4 +++ tests/components/hmac_md5/common.yaml | 34 +++++++++++++++++++ .../components/hmac_md5/test.bk72xx-ard.yaml | 1 + tests/components/hmac_md5/test.esp32-idf.yaml | 1 + .../components/hmac_md5/test.esp8266-ard.yaml | 1 + .../components/hmac_md5/test.rp2040-ard.yaml | 1 + 6 files changed, 42 insertions(+) create mode 100644 tests/components/hmac_md5/common.yaml create mode 100644 tests/components/hmac_md5/test.bk72xx-ard.yaml create mode 100644 tests/components/hmac_md5/test.esp32-idf.yaml create mode 100644 tests/components/hmac_md5/test.esp8266-ard.yaml create mode 100644 tests/components/hmac_md5/test.rp2040-ard.yaml diff --git a/esphome/components/hmac_md5/__init__.py b/esphome/components/hmac_md5/__init__.py index fe245c0cfd..e37eb9b116 100644 --- a/esphome/components/hmac_md5/__init__.py +++ b/esphome/components/hmac_md5/__init__.py @@ -1,2 +1,6 @@ +import esphome.config_validation as cv + AUTO_LOAD = ["md5"] CODEOWNERS = ["@dwmw2"] + +CONFIG_SCHEMA = cv.Schema({}) diff --git a/tests/components/hmac_md5/common.yaml b/tests/components/hmac_md5/common.yaml new file mode 100644 index 0000000000..ac6d7ecbaa --- /dev/null +++ b/tests/components/hmac_md5/common.yaml @@ -0,0 +1,34 @@ +esphome: + on_boot: + - lambda: |- + // Test HMAC-MD5 functionality + #ifdef USE_MD5 + using esphome::hmac_md5::HmacMD5; + HmacMD5 hmac; + + // Test with key "key" and message "The quick brown fox jumps over the lazy dog" + const char* key = "key"; + const char* message = "The quick brown fox jumps over the lazy dog"; + + hmac.init(key, strlen(key)); + hmac.add(message, strlen(message)); + hmac.calculate(); + + char hex_output[33]; + hmac.get_hex(hex_output); + hex_output[32] = '\0'; + + ESP_LOGD("HMAC_MD5", "HMAC-MD5('%s', '%s') = %s", key, message, hex_output); + + // Expected: 80070713463e7749b90c2dc24911e275 + const char* expected = "80070713463e7749b90c2dc24911e275"; + if (strcmp(hex_output, expected) == 0) { + ESP_LOGI("HMAC_MD5", "Test PASSED"); + } else { + ESP_LOGE("HMAC_MD5", "Test FAILED. Expected %s", expected); + } + #else + ESP_LOGW("HMAC_MD5", "HMAC-MD5 not available on this platform"); + #endif + +hmac_md5: diff --git a/tests/components/hmac_md5/test.bk72xx-ard.yaml b/tests/components/hmac_md5/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_md5/test.bk72xx-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_md5/test.esp32-idf.yaml b/tests/components/hmac_md5/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_md5/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_md5/test.esp8266-ard.yaml b/tests/components/hmac_md5/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_md5/test.esp8266-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_md5/test.rp2040-ard.yaml b/tests/components/hmac_md5/test.rp2040-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_md5/test.rp2040-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml From 6fce0a6104513dfafd90e32082bfe38903a3bb9d Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sat, 13 Dec 2025 11:50:34 +0900 Subject: [PATCH 0630/1145] Add host platform support to MD5 component (#12458) --- esphome/components/md5/__init__.py | 10 +++++++ esphome/components/md5/md5.cpp | 38 ++++++++++++++++++++++++ esphome/components/md5/md5.h | 11 ++++++- tests/components/hmac_md5/test.host.yaml | 1 + 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/components/hmac_md5/test.host.yaml diff --git a/esphome/components/md5/__init__.py b/esphome/components/md5/__init__.py index 1af9ee0b29..1710b00e66 100644 --- a/esphome/components/md5/__init__.py +++ b/esphome/components/md5/__init__.py @@ -1,7 +1,17 @@ import esphome.codegen as cg +from esphome.core import CORE +from esphome.helpers import IS_MACOS CODEOWNERS = ["@esphome/core"] async def to_code(config): cg.add_define("USE_MD5") + + # Add OpenSSL library for host platform + if CORE.is_host: + if IS_MACOS: + # macOS needs special handling for Homebrew OpenSSL + cg.add_build_flag("-I/opt/homebrew/opt/openssl/include") + cg.add_build_flag("-L/opt/homebrew/opt/openssl/lib") + cg.add_build_flag("-lcrypto") diff --git a/esphome/components/md5/md5.cpp b/esphome/components/md5/md5.cpp index 866f00eda4..26554e4d3c 100644 --- a/esphome/components/md5/md5.cpp +++ b/esphome/components/md5/md5.cpp @@ -39,6 +39,44 @@ void MD5Digest::add(const uint8_t *data, size_t len) { br_md5_update(&this->ctx_ void MD5Digest::calculate() { br_md5_out(&this->ctx_, this->digest_); } #endif // USE_RP2040 +#ifdef USE_HOST +MD5Digest::~MD5Digest() { + if (this->ctx_) { + EVP_MD_CTX_free(this->ctx_); + } +} + +void MD5Digest::init() { + if (this->ctx_) { + EVP_MD_CTX_free(this->ctx_); + } + this->ctx_ = EVP_MD_CTX_new(); + EVP_DigestInit_ex(this->ctx_, EVP_md5(), nullptr); + this->calculated_ = false; + memset(this->digest_, 0, 16); +} + +void MD5Digest::add(const uint8_t *data, size_t len) { + if (!this->ctx_) { + this->init(); + } + EVP_DigestUpdate(this->ctx_, data, len); +} + +void MD5Digest::calculate() { + if (!this->ctx_) { + this->init(); + } + if (!this->calculated_) { + unsigned int len = 16; + EVP_DigestFinal_ex(this->ctx_, this->digest_, &len); + this->calculated_ = true; + } +} +#else +MD5Digest::~MD5Digest() = default; +#endif // USE_HOST + } // namespace md5 } // namespace esphome #endif diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h index b0da2c0a3b..6ff651b02e 100644 --- a/esphome/components/md5/md5.h +++ b/esphome/components/md5/md5.h @@ -5,6 +5,10 @@ #include "esphome/core/hash_base.h" +#ifdef USE_HOST +#include +#endif + #ifdef USE_ESP32 #include "esp_rom_md5.h" #define MD5_CTX_TYPE md5_context_t @@ -31,7 +35,7 @@ namespace md5 { class MD5Digest : public HashBase { public: MD5Digest() = default; - ~MD5Digest() override = default; + ~MD5Digest() override; /// Initialize a new MD5 digest computation. void init() override; @@ -47,7 +51,12 @@ class MD5Digest : public HashBase { size_t get_size() const override { return 16; } protected: +#ifdef USE_HOST + EVP_MD_CTX *ctx_{nullptr}; + bool calculated_{false}; +#else MD5_CTX_TYPE ctx_{}; +#endif }; } // namespace md5 diff --git a/tests/components/hmac_md5/test.host.yaml b/tests/components/hmac_md5/test.host.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_md5/test.host.yaml @@ -0,0 +1 @@ +<<: !include common.yaml From e0ce66e01119c5c89e9a4f8cf82b2dbd6d1793c5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 13 Dec 2025 07:38:31 -0600 Subject: [PATCH 0631/1145] [core] Fix CORE.raw_config not updated after package merge (#12456) --- esphome/config.py | 4 +-- .../component_tests/packages/test_packages.py | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/esphome/config.py b/esphome/config.py index 694716be34..6f6ad4886b 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -1010,14 +1010,14 @@ def validate_config( result.add_error(err) return result - CORE.raw_config = config - # 1.1. Merge packages if CONF_PACKAGES in config: from esphome.components.packages import merge_packages config = merge_packages(config) + CORE.raw_config = config + # 1.2. Resolve !extend and !remove and check for REPLACEME # After this step, there will not be any Extend or Remove values in the config anymore try: diff --git a/tests/component_tests/packages/test_packages.py b/tests/component_tests/packages/test_packages.py index 3829e540d7..22fb2c4e32 100644 --- a/tests/component_tests/packages/test_packages.py +++ b/tests/component_tests/packages/test_packages.py @@ -6,6 +6,7 @@ from unittest.mock import MagicMock, patch import pytest from esphome.components.packages import CONFIG_SCHEMA, do_packages_pass, merge_packages +import esphome.config as config_module from esphome.config import resolve_extend_remove from esphome.config_helpers import Extend, Remove import esphome.config_validation as cv @@ -33,6 +34,7 @@ from esphome.const import ( CONF_VARS, CONF_WIFI, ) +from esphome.core import CORE from esphome.util import OrderedDict # Test strings @@ -991,3 +993,35 @@ def test_package_merge_invalid(invalid_package) -> None: with pytest.raises(cv.Invalid): merge_packages(config) + + +def test_raw_config_contains_merged_esphome_from_package(tmp_path) -> None: + """Test that CORE.raw_config contains esphome section from merged package. + + This is a regression test for the bug where CORE.raw_config was set before + packages were merged, causing KeyError when components accessed + CORE.raw_config[CONF_ESPHOME] and the esphome section came from a package. + """ + # Create a config where esphome section comes from a package + test_config = OrderedDict() + test_config[CONF_PACKAGES] = { + "base": { + CONF_ESPHOME: {CONF_NAME: TEST_DEVICE_NAME}, + } + } + test_config["esp32"] = {"board": "esp32dev"} + + # Set up CORE for the test + test_yaml = tmp_path / "test.yaml" + test_yaml.write_text("# test config") + CORE.reset() + CORE.config_path = test_yaml + + # Call validate_config - this should merge packages and set CORE.raw_config + config_module.validate_config(test_config, {}) + + # Verify that CORE.raw_config contains the esphome section from the package + assert CONF_ESPHOME in CORE.raw_config, ( + "CORE.raw_config should contain esphome section after package merge" + ) + assert CORE.raw_config[CONF_ESPHOME][CONF_NAME] == TEST_DEVICE_NAME From ede64a9f47f182e6ed4aa83d1c219e2319c21c79 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:47:15 +1000 Subject: [PATCH 0632/1145] [packet_transport] Ensure retransmission at update intervals (#12472) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../packet_transport/espnow_transport.cpp | 11 +++-------- .../espnow/packet_transport/espnow_transport.h | 1 - esphome/components/packet_transport/__init__.py | 5 +++++ .../packet_transport/packet_transport.cpp | 6 +++++- .../packet_transport/packet_transport.h | 3 ++- .../packet_transport/sx126x_transport.cpp | 6 ------ .../sx126x/packet_transport/sx126x_transport.h | 1 - .../packet_transport/sx127x_transport.cpp | 6 ------ .../sx127x/packet_transport/sx127x_transport.h | 1 - .../uart/packet_transport/uart_transport.cpp | 6 ------ .../uart/packet_transport/uart_transport.h | 1 - .../udp/packet_transport/udp_transport.cpp | 17 +---------------- .../udp/packet_transport/udp_transport.h | 2 -- tests/components/espnow/common.yaml | 4 ++-- 14 files changed, 18 insertions(+), 52 deletions(-) diff --git a/esphome/components/espnow/packet_transport/espnow_transport.cpp b/esphome/components/espnow/packet_transport/espnow_transport.cpp index d30e9447a0..c1252acc9d 100644 --- a/esphome/components/espnow/packet_transport/espnow_transport.cpp +++ b/esphome/components/espnow/packet_transport/espnow_transport.cpp @@ -13,7 +13,7 @@ static const char *const TAG = "espnow.transport"; bool ESPNowTransport::should_send() { return this->parent_ != nullptr && !this->parent_->is_failed(); } void ESPNowTransport::setup() { - packet_transport::PacketTransport::setup(); + PacketTransport::setup(); if (this->parent_ == nullptr) { ESP_LOGE(TAG, "ESPNow component not set"); @@ -26,15 +26,10 @@ void ESPNowTransport::setup() { this->peer_address_[2], this->peer_address_[3], this->peer_address_[4], this->peer_address_[5]); // Register received handler - this->parent_->register_received_handler(static_cast(this)); + this->parent_->register_received_handler(this); // Register broadcasted handler - this->parent_->register_broadcasted_handler(static_cast(this)); -} - -void ESPNowTransport::update() { - packet_transport::PacketTransport::update(); - this->updated_ = true; + this->parent_->register_broadcasted_handler(this); } void ESPNowTransport::send_packet(const std::vector &buf) const { diff --git a/esphome/components/espnow/packet_transport/espnow_transport.h b/esphome/components/espnow/packet_transport/espnow_transport.h index 3629fad2cd..d85119db7d 100644 --- a/esphome/components/espnow/packet_transport/espnow_transport.h +++ b/esphome/components/espnow/packet_transport/espnow_transport.h @@ -18,7 +18,6 @@ class ESPNowTransport : public packet_transport::PacketTransport, public ESPNowBroadcastedHandler { public: void setup() override; - void update() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } void set_peer_address(peer_address_t address) { diff --git a/esphome/components/packet_transport/__init__.py b/esphome/components/packet_transport/__init__.py index 43da7740fe..1930e45e85 100644 --- a/esphome/components/packet_transport/__init__.py +++ b/esphome/components/packet_transport/__init__.py @@ -176,17 +176,22 @@ async def register_packet_transport(var, config): if encryption := provider.get(CONF_ENCRYPTION): cg.add(var.set_provider_encryption(name, hash_encryption_key(encryption))) + is_provider = False for sens_conf in config.get(CONF_SENSORS, ()): + is_provider = True 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, ()): + is_provider = True 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)) + if is_provider: + cg.add(var.set_is_provider(True)) if encryption := config.get(CONF_ENCRYPTION): cg.add(var.set_encryption_key(hash_encryption_key(encryption))) return providers diff --git a/esphome/components/packet_transport/packet_transport.cpp b/esphome/components/packet_transport/packet_transport.cpp index 37e5f3d9e1..da7f5f8bff 100644 --- a/esphome/components/packet_transport/packet_transport.cpp +++ b/esphome/components/packet_transport/packet_transport.cpp @@ -263,6 +263,7 @@ void PacketTransport::flush_() { xxtea::encrypt((uint32_t *) (encode_buffer.data() + header_len), len / 4, (uint32_t *) this->encryption_key_.data()); } + ESP_LOGVV(TAG, "Sending packet %s", format_hex_pretty(encode_buffer.data(), encode_buffer.size()).c_str()); this->send_packet(encode_buffer); } @@ -316,6 +317,9 @@ void PacketTransport::send_data_(bool all) { } void PacketTransport::update() { + // resend all sensors if required + if (this->is_provider_) + this->send_data_(true); if (!this->ping_pong_enable_) { return; } @@ -551,7 +555,7 @@ void PacketTransport::loop() { if (this->resend_ping_key_) this->send_ping_pong_request_(); if (this->updated_) { - this->send_data_(this->resend_data_); + this->send_data_(false); } } diff --git a/esphome/components/packet_transport/packet_transport.h b/esphome/components/packet_transport/packet_transport.h index a2370e9749..86ec564fce 100644 --- a/esphome/components/packet_transport/packet_transport.h +++ b/esphome/components/packet_transport/packet_transport.h @@ -91,6 +91,7 @@ class PacketTransport : public PollingComponent { } } + void set_is_provider(bool is_provider) { this->is_provider_ = is_provider; } 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; } @@ -129,7 +130,7 @@ class PacketTransport : public PollingComponent { uint32_t ping_pong_recyle_time_{}; uint32_t last_key_time_{}; bool resend_ping_key_{}; - bool resend_data_{}; + bool is_provider_{}; const char *name_{}; ESPPreferenceObject pref_{}; diff --git a/esphome/components/sx126x/packet_transport/sx126x_transport.cpp b/esphome/components/sx126x/packet_transport/sx126x_transport.cpp index 2cfc4b700e..59d80bd297 100644 --- a/esphome/components/sx126x/packet_transport/sx126x_transport.cpp +++ b/esphome/components/sx126x/packet_transport/sx126x_transport.cpp @@ -12,12 +12,6 @@ void SX126xTransport::setup() { this->parent_->register_listener(this); } -void SX126xTransport::update() { - PacketTransport::update(); - this->updated_ = true; - this->resend_data_ = true; -} - void SX126xTransport::send_packet(const std::vector &buf) const { this->parent_->transmit_packet(buf); } void SX126xTransport::on_packet(const std::vector &packet, float rssi, float snr) { this->process_(packet); } diff --git a/esphome/components/sx126x/packet_transport/sx126x_transport.h b/esphome/components/sx126x/packet_transport/sx126x_transport.h index 755d30417d..640c6a76f9 100644 --- a/esphome/components/sx126x/packet_transport/sx126x_transport.h +++ b/esphome/components/sx126x/packet_transport/sx126x_transport.h @@ -11,7 +11,6 @@ namespace sx126x { class SX126xTransport : public packet_transport::PacketTransport, public Parented, public SX126xListener { public: void setup() override; - void update() override; void on_packet(const std::vector &packet, float rssi, float snr) override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } diff --git a/esphome/components/sx127x/packet_transport/sx127x_transport.cpp b/esphome/components/sx127x/packet_transport/sx127x_transport.cpp index b1d014bb96..893726e816 100644 --- a/esphome/components/sx127x/packet_transport/sx127x_transport.cpp +++ b/esphome/components/sx127x/packet_transport/sx127x_transport.cpp @@ -12,12 +12,6 @@ void SX127xTransport::setup() { this->parent_->register_listener(this); } -void SX127xTransport::update() { - PacketTransport::update(); - this->updated_ = true; - this->resend_data_ = true; -} - void SX127xTransport::send_packet(const std::vector &buf) const { this->parent_->transmit_packet(buf); } void SX127xTransport::on_packet(const std::vector &packet, float rssi, float snr) { this->process_(packet); } diff --git a/esphome/components/sx127x/packet_transport/sx127x_transport.h b/esphome/components/sx127x/packet_transport/sx127x_transport.h index e27b7f8d57..6208372971 100644 --- a/esphome/components/sx127x/packet_transport/sx127x_transport.h +++ b/esphome/components/sx127x/packet_transport/sx127x_transport.h @@ -11,7 +11,6 @@ namespace sx127x { class SX127xTransport : public packet_transport::PacketTransport, public Parented, public SX127xListener { public: void setup() override; - void update() override; void on_packet(const std::vector &packet, float rssi, float snr) override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } diff --git a/esphome/components/uart/packet_transport/uart_transport.cpp b/esphome/components/uart/packet_transport/uart_transport.cpp index 4a9aa0fe47..6b8eae611c 100644 --- a/esphome/components/uart/packet_transport/uart_transport.cpp +++ b/esphome/components/uart/packet_transport/uart_transport.cpp @@ -55,12 +55,6 @@ void UARTTransport::loop() { } } -void UARTTransport::update() { - this->updated_ = true; - this->resend_data_ = true; - PacketTransport::update(); -} - /** * Write a byte to the UART bus. If the byte is a flag or control byte, it will be escaped. * @param byte The byte to write. diff --git a/esphome/components/uart/packet_transport/uart_transport.h b/esphome/components/uart/packet_transport/uart_transport.h index e84bed95e6..1c92af536e 100644 --- a/esphome/components/uart/packet_transport/uart_transport.h +++ b/esphome/components/uart/packet_transport/uart_transport.h @@ -23,7 +23,6 @@ static const uint8_t CONTROL_BYTE = 0x7D; class UARTTransport : public packet_transport::PacketTransport, public UARTDevice { public: void loop() override; - void update() override; float get_setup_priority() const override { return setup_priority::PROCESSOR; } protected: diff --git a/esphome/components/udp/packet_transport/udp_transport.cpp b/esphome/components/udp/packet_transport/udp_transport.cpp index b92e0d64df..f3e33573a5 100644 --- a/esphome/components/udp/packet_transport/udp_transport.cpp +++ b/esphome/components/udp/packet_transport/udp_transport.cpp @@ -8,29 +8,14 @@ namespace udp { static const char *const TAG = "udp_transport"; -bool UDPTransport::should_send() { return this->should_broadcast_ && network::is_connected(); } +bool UDPTransport::should_send() { return network::is_connected(); } void UDPTransport::setup() { PacketTransport::setup(); - this->should_broadcast_ = this->ping_pong_enable_; -#ifdef USE_SENSOR - this->should_broadcast_ |= !this->sensors_.empty(); -#endif -#ifdef USE_BINARY_SENSOR - this->should_broadcast_ |= !this->binary_sensors_.empty(); -#endif - if (this->should_broadcast_) - this->parent_->set_should_broadcast(); if (!this->providers_.empty() || this->is_encrypted_()) { this->parent_->add_listener([this](std::vector &buf) { this->process_(buf); }); } } -void UDPTransport::update() { - PacketTransport::update(); - this->updated_ = true; - this->resend_data_ = this->should_broadcast_; -} - void UDPTransport::send_packet(const std::vector &buf) const { this->parent_->send_packet(buf); } } // namespace udp } // namespace esphome diff --git a/esphome/components/udp/packet_transport/udp_transport.h b/esphome/components/udp/packet_transport/udp_transport.h index c87eb62780..8d01ae0909 100644 --- a/esphome/components/udp/packet_transport/udp_transport.h +++ b/esphome/components/udp/packet_transport/udp_transport.h @@ -12,14 +12,12 @@ namespace udp { class UDPTransport : public packet_transport::PacketTransport, public Parented { public: void setup() override; - void update() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } protected: void send_packet(const std::vector &buf) const override; bool should_send() override; - bool should_broadcast_{false}; size_t get_max_packet_size() override { return MAX_PACKET_SIZE; } }; diff --git a/tests/components/espnow/common.yaml b/tests/components/espnow/common.yaml index 895ffb9d15..b724af54e0 100644 --- a/tests/components/espnow/common.yaml +++ b/tests/components/espnow/common.yaml @@ -62,7 +62,7 @@ packet_transport: sensors: - temp_sensor providers: - - name: test_provider + - name: test-provider encryption: key: "0123456789abcdef0123456789abcdef" @@ -71,6 +71,6 @@ sensor: id: temp_sensor - platform: packet_transport - provider: test_provider + provider: test-provider remote_id: temp_sensor id: remote_temp From 786d7266f519f0dfb7654fc9a8472dffe1a87687 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:47:52 -0500 Subject: [PATCH 0633/1145] [core] Fix polling_component_schema and type consistency (#12478) Co-authored-by: Claude --- esphome/config_validation.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index c52b791120..08fffa6cec 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -71,6 +71,7 @@ from esphome.const import ( PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, + SCHEDULER_DONT_RUN, TYPE_GIT, TYPE_LOCAL, VALID_SUBSTITUTIONS_CHARACTERS, @@ -894,7 +895,7 @@ def time_period_in_minutes_(value): def update_interval(value): if value == "never": - return 4294967295 # uint32_t max + return TimePeriodMilliseconds(milliseconds=SCHEDULER_DONT_RUN) return positive_time_period_milliseconds(value) @@ -2009,7 +2010,7 @@ def polling_component_schema(default_update_interval): if default_update_interval is None: return COMPONENT_SCHEMA.extend( { - Required(CONF_UPDATE_INTERVAL): default_update_interval, + Required(CONF_UPDATE_INTERVAL): update_interval, } ) assert isinstance(default_update_interval, str) From cfc0d8bdfc1bba66d0b4b90cf3f4edb3859d96e5 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 14 Dec 2025 13:22:55 -0500 Subject: [PATCH 0634/1145] [cc1101] Add packet mode support (#12474) Co-authored-by: Claude --- esphome/components/cc1101/__init__.py | 105 +++++++++++- esphome/components/cc1101/cc1101.cpp | 171 +++++++++++++++++--- esphome/components/cc1101/cc1101.h | 49 +++++- esphome/components/cc1101/cc1101defs.h | 23 +++ esphome/components/const/__init__.py | 2 + esphome/components/sx126x/__init__.py | 3 +- esphome/components/sx127x/__init__.py | 3 +- tests/components/cc1101/common.yaml | 21 ++- tests/components/cc1101/test.esp32-idf.yaml | 2 +- tests/components/cc1101/test.esp8266.yaml | 2 +- 10 files changed, 340 insertions(+), 41 deletions(-) diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index e6b31b84f8..1971817fb1 100644 --- a/esphome/components/cc1101/__init__.py +++ b/esphome/components/cc1101/__init__.py @@ -1,9 +1,17 @@ -from esphome import automation +from esphome import automation, pins from esphome.automation import maybe_simple_id import esphome.codegen as cg from esphome.components import spi +from esphome.components.const import CONF_CRC_ENABLE, CONF_ON_PACKET import esphome.config_validation as cv -from esphome.const import CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, CONF_WAIT_TIME +from esphome.const import ( + CONF_CHANNEL, + CONF_DATA, + CONF_FREQUENCY, + CONF_ID, + CONF_WAIT_TIME, +) +from esphome.core import ID CODEOWNERS = ["@lygris", "@gabest11"] DEPENDENCIES = ["spi"] @@ -29,7 +37,6 @@ CONF_MANCHESTER = "manchester" CONF_NUM_PREAMBLE = "num_preamble" CONF_SYNC1 = "sync1" CONF_SYNC0 = "sync0" -CONF_PKTLEN = "pktlen" CONF_MAGN_TARGET = "magn_target" CONF_MAX_LNA_GAIN = "max_lna_gain" CONF_MAX_DVGA_GAIN = "max_dvga_gain" @@ -41,6 +48,12 @@ CONF_FILTER_LENGTH_ASK_OOK = "filter_length_ask_ook" CONF_FREEZE = "freeze" CONF_HYST_LEVEL = "hyst_level" +# Packet mode config keys +CONF_PACKET_MODE = "packet_mode" +CONF_PACKET_LENGTH = "packet_length" +CONF_WHITENING = "whitening" +CONF_GDO0_PIN = "gdo0_pin" + # Enums SyncMode = ns.enum("SyncMode", True) SYNC_MODE = { @@ -167,7 +180,6 @@ CONFIG_MAP = { CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7), CONF_SYNC1: cv.hex_uint8_t, CONF_SYNC0: cv.hex_uint8_t, - CONF_PKTLEN: cv.uint8_t, CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False), CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False), CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False), @@ -179,13 +191,36 @@ CONFIG_MAP = { CONF_FREEZE: cv.enum(FREEZE, upper=False), CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False), CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False), + CONF_PACKET_MODE: cv.boolean, + CONF_PACKET_LENGTH: cv.uint8_t, + CONF_CRC_ENABLE: cv.boolean, + CONF_WHITENING: cv.boolean, } -CONFIG_SCHEMA = ( - cv.Schema({cv.GenerateID(): cv.declare_id(CC1101Component)}) + +def _validate_packet_mode(config): + if config.get(CONF_PACKET_MODE, False): + if CONF_GDO0_PIN not in config: + raise cv.Invalid("gdo0_pin is required when packet_mode is enabled") + if CONF_PACKET_LENGTH not in config: + raise cv.Invalid("packet_length is required when packet_mode is enabled") + if config[CONF_PACKET_LENGTH] > 64: + raise cv.Invalid("packet_length must be <= 64 (FIFO size)") + return config + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(CC1101Component), + cv.Optional(CONF_GDO0_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True), + } + ) .extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()}) .extend(cv.COMPONENT_SCHEMA) - .extend(spi.spi_device_schema(cs_pin_required=True)) + .extend(spi.spi_device_schema(cs_pin_required=True)), + _validate_packet_mode, ) @@ -198,12 +233,29 @@ async def to_code(config): if key in config: cg.add(getattr(var, f"set_{key}")(config[key])) + if CONF_GDO0_PIN in config: + gdo0_pin = await cg.gpio_pin_expression(config[CONF_GDO0_PIN]) + cg.add(var.set_gdo0_pin(gdo0_pin)) + if CONF_ON_PACKET in config: + await automation.build_automation( + var.get_packet_trigger(), + [ + (cg.std_vector.template(cg.uint8), "x"), + (cg.float_, "rssi"), + (cg.uint8, "lqi"), + ], + config[CONF_ON_PACKET], + ) + # Actions BeginTxAction = ns.class_("BeginTxAction", automation.Action) BeginRxAction = ns.class_("BeginRxAction", automation.Action) ResetAction = ns.class_("ResetAction", automation.Action) SetIdleAction = ns.class_("SetIdleAction", automation.Action) +SendPacketAction = ns.class_( + "SendPacketAction", automation.Action, cg.Parented.template(CC1101Component) +) CC1101_ACTION_SCHEMA = cv.Schema( maybe_simple_id({cv.GenerateID(CONF_ID): cv.use_id(CC1101Component)}) @@ -218,3 +270,42 @@ async def cc1101_action_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) return var + + +def validate_raw_data(value): + if isinstance(value, str): + return value.encode("utf-8") + if isinstance(value, list): + return cv.Schema([cv.hex_uint8_t])(value) + raise cv.Invalid( + "data must either be a string wrapped in quotes or a list of bytes" + ) + + +SEND_PACKET_ACTION_SCHEMA = cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(CC1101Component), + cv.Required(CONF_DATA): cv.templatable(validate_raw_data), + }, + key=CONF_DATA, +) + + +@automation.register_action( + "cc1101.send_packet", SendPacketAction, SEND_PACKET_ACTION_SCHEMA +) +async def send_packet_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + data = config[CONF_DATA] + if isinstance(data, bytes): + data = list(data) + if cg.is_template(data): + templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) + cg.add(var.set_data_template(templ)) + else: + # Generate static array in flash to avoid RAM copy + arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) + arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data)) + cg.add(var.set_data_static(arr, len(data))) + return var diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 3cbf09ded8..5b6eb545bc 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -143,6 +143,11 @@ void CC1101Component::setup() { return; } + // Setup GDO0 pin if configured + if (this->gdo0_pin_ != nullptr) { + this->gdo0_pin_->setup(); + } + this->initialized_ = true; for (uint8_t i = 0; i <= static_cast(Register::TEST0); i++) { @@ -151,8 +156,69 @@ void CC1101Component::setup() { } this->write_(static_cast(i)); } - this->write_(Register::PATABLE, this->pa_table_, sizeof(this->pa_table_)); + this->set_output_power(this->output_power_requested_); this->strobe_(Command::RX); + + // Defer pin mode setup until after all components have completed setup() + // This handles the case where remote_transmitter runs after CC1101 and changes pin mode + if (this->gdo0_pin_ != nullptr) { + this->defer([this]() { this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT); }); + } +} + +void CC1101Component::loop() { + if (this->state_.PKT_FORMAT != static_cast(PacketFormat::PACKET_FORMAT_FIFO) || this->gdo0_pin_ == nullptr || + !this->gdo0_pin_->digital_read()) { + return; + } + + // Read state + this->read_(Register::RXBYTES); + uint8_t rx_bytes = this->state_.NUM_RXBYTES; + bool overflow = this->state_.RXFIFO_OVERFLOW; + if (overflow || rx_bytes == 0) { + ESP_LOGW(TAG, "RX FIFO overflow, flushing"); + this->enter_idle_(); + this->strobe_(Command::FRX); + this->strobe_(Command::RX); + this->wait_for_state_(State::RX); + return; + } + + // Read packet + uint8_t payload_length; + if (this->state_.LENGTH_CONFIG == static_cast(LengthConfig::LENGTH_CONFIG_VARIABLE)) { + this->read_(Register::FIFO, &payload_length, 1); + } else { + payload_length = this->state_.PKTLEN; + } + if (payload_length == 0 || payload_length > 64) { + ESP_LOGW(TAG, "Invalid payload length: %u", payload_length); + this->enter_idle_(); + this->strobe_(Command::FRX); + this->strobe_(Command::RX); + this->wait_for_state_(State::RX); + return; + } + this->packet_.resize(payload_length); + this->read_(Register::FIFO, this->packet_.data(), payload_length); + + // Read status and trigger + uint8_t status[2]; + this->read_(Register::FIFO, status, 2); + int8_t rssi_raw = static_cast(status[0]); + float rssi = (rssi_raw * RSSI_STEP) - RSSI_OFFSET; + bool crc_ok = (status[1] & STATUS_CRC_OK_MASK) != 0; + uint8_t lqi = status[1] & STATUS_LQI_MASK; + if (this->state_.CRC_EN == 0 || crc_ok) { + this->packet_trigger_->trigger(this->packet_, rssi, lqi); + } + + // Return to rx + this->enter_idle_(); + this->strobe_(Command::FRX); + this->strobe_(Command::RX); + this->wait_for_state_(State::RX); } void CC1101Component::dump_config() { @@ -177,9 +243,12 @@ void CC1101Component::dump_config() { } void CC1101Component::begin_tx() { - // Ensure Packet Format is 3 (Async Serial), use GDO0 as input during TX + // Ensure Packet Format is 3 (Async Serial) this->write_(Register::PKTCTRL0, 0x32); ESP_LOGV(TAG, "Beginning TX sequence"); + if (this->gdo0_pin_ != nullptr) { + this->gdo0_pin_->pin_mode(gpio::FLAG_OUTPUT); + } this->strobe_(Command::TX); if (!this->wait_for_state_(State::TX, 50)) { ESP_LOGW(TAG, "Timed out waiting for TX state!"); @@ -188,6 +257,9 @@ void CC1101Component::begin_tx() { void CC1101Component::begin_rx() { ESP_LOGV(TAG, "Beginning RX sequence"); + if (this->gdo0_pin_ != nullptr) { + this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT); + } this->strobe_(Command::RX); } @@ -201,20 +273,6 @@ void CC1101Component::set_idle() { this->enter_idle_(); } -void CC1101Component::set_gdo0_config(uint8_t value) { - this->state_.GDO0_CFG = value; - if (this->initialized_) { - this->write_(Register::IOCFG0); - } -} - -void CC1101Component::set_gdo2_config(uint8_t value) { - this->state_.GDO2_CFG = value; - if (this->initialized_) { - this->write_(Register::IOCFG2); - } -} - bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) { uint32_t start = millis(); while (millis() - start < timeout_ms) { @@ -282,6 +340,33 @@ void CC1101Component::read_(Register reg, uint8_t *buffer, size_t length) { this->disable(); } +CC1101Error CC1101Component::transmit_packet(const std::vector &packet) { + if (this->state_.PKT_FORMAT != static_cast(PacketFormat::PACKET_FORMAT_FIFO)) { + return CC1101Error::PARAMS; + } + + // Write packet + this->enter_idle_(); + this->strobe_(Command::FTX); + if (this->state_.LENGTH_CONFIG == static_cast(LengthConfig::LENGTH_CONFIG_VARIABLE)) { + this->write_(Register::FIFO, static_cast(packet.size())); + } + this->write_(Register::FIFO, packet.data(), packet.size()); + this->strobe_(Command::TX); + if (!this->wait_for_state_(State::IDLE, 1000)) { + ESP_LOGW(TAG, "TX timeout"); + this->enter_idle_(); + this->strobe_(Command::RX); + this->wait_for_state_(State::RX); + return CC1101Error::TIMEOUT; + } + + // Return to rx + this->strobe_(Command::RX); + this->wait_for_state_(State::RX); + return CC1101Error::NONE; +} + // Setters void CC1101Component::set_output_power(float value) { this->output_power_requested_ = value; @@ -428,6 +513,7 @@ void CC1101Component::set_modulation_type(Modulation value) { this->state_.PA_POWER = value == Modulation::MODULATION_ASK_OOK ? 1 : 0; if (this->initialized_) { this->enter_idle_(); + this->set_output_power(this->output_power_requested_); this->write_(Register::MDMCFG2); this->write_(Register::FREND0); this->strobe_(Command::RX); @@ -462,13 +548,6 @@ void CC1101Component::set_sync0(uint8_t value) { } } -void CC1101Component::set_pktlen(uint8_t value) { - this->state_.PKTLEN = value; - if (this->initialized_) { - this->write_(Register::PKTLEN); - } -} - void CC1101Component::set_magn_target(MagnTarget value) { this->state_.MAGN_TARGET = static_cast(value); if (this->initialized_) { @@ -546,4 +625,50 @@ void CC1101Component::set_hyst_level(HystLevel value) { } } +void CC1101Component::set_packet_mode(bool value) { + this->state_.PKT_FORMAT = + static_cast(value ? PacketFormat::PACKET_FORMAT_FIFO : PacketFormat::PACKET_FORMAT_ASYNC_SERIAL); + if (value) { + // Configure GDO0 for FIFO status (asserts on RX FIFO threshold or end of packet) + this->state_.GDO0_CFG = 0x01; + // Set max RX FIFO threshold to ensure we only trigger on end-of-packet + this->state_.FIFO_THR = 15; + } else { + // Configure GDO0 for serial data (async serial mode) + this->state_.GDO0_CFG = 0x0D; + } + if (this->initialized_) { + this->write_(Register::PKTCTRL0); + this->write_(Register::IOCFG0); + this->write_(Register::FIFOTHR); + } +} + +void CC1101Component::set_packet_length(uint8_t value) { + if (value == 0) { + this->state_.LENGTH_CONFIG = static_cast(LengthConfig::LENGTH_CONFIG_VARIABLE); + } else { + this->state_.LENGTH_CONFIG = static_cast(LengthConfig::LENGTH_CONFIG_FIXED); + this->state_.PKTLEN = value; + } + if (this->initialized_) { + this->write_(Register::PKTCTRL0); + this->write_(Register::PKTLEN); + } +} + +void CC1101Component::set_crc_enable(bool value) { + this->state_.CRC_EN = value ? 1 : 0; + if (this->initialized_) { + this->write_(Register::PKTCTRL0); + } +} + +void CC1101Component::set_whitening(bool value) { + this->state_.WHITE_DATA = value ? 1 : 0; + if (this->initialized_) { + this->write_(Register::PKTCTRL0); + } +} + } // namespace esphome::cc1101 diff --git a/esphome/components/cc1101/cc1101.h b/esphome/components/cc1101/cc1101.h index 65aeb2ea82..b896f7e974 100644 --- a/esphome/components/cc1101/cc1101.h +++ b/esphome/components/cc1101/cc1101.h @@ -5,9 +5,12 @@ #include "esphome/components/spi/spi.h" #include "esphome/core/automation.h" #include "cc1101defs.h" +#include namespace esphome::cc1101 { +enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW }; + class CC1101Component : public Component, public spi::SPIDevice { @@ -15,6 +18,7 @@ class CC1101Component : public Component, CC1101Component(); void setup() override; + void loop() override; void dump_config() override; // Actions @@ -24,8 +28,7 @@ class CC1101Component : public Component, void set_idle(); // GDO Pin Configuration - void set_gdo0_config(uint8_t value); - void set_gdo2_config(uint8_t value); + void set_gdo0_pin(InternalGPIOPin *pin) { this->gdo0_pin_ = pin; } // Configuration Setters void set_output_power(float value); @@ -48,7 +51,6 @@ class CC1101Component : public Component, void set_num_preamble(uint8_t value); void set_sync1(uint8_t value); void set_sync0(uint8_t value); - void set_pktlen(uint8_t value); // AGC settings void set_magn_target(MagnTarget value); @@ -63,6 +65,16 @@ class CC1101Component : public Component, void set_wait_time(WaitTime value); void set_hyst_level(HystLevel value); + // Packet mode settings + void set_packet_mode(bool value); + void set_packet_length(uint8_t value); + void set_crc_enable(bool value); + void set_whitening(bool value); + + // Packet mode operations + CC1101Error transmit_packet(const std::vector &packet); + Trigger, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; } + protected: uint16_t chip_id_{0}; bool initialized_{false}; @@ -73,6 +85,13 @@ class CC1101Component : public Component, CC1101State state_; + // GDO pin for packet reception + InternalGPIOPin *gdo0_pin_{nullptr}; + + // Packet handling + Trigger, float, uint8_t> *packet_trigger_{new Trigger, float, uint8_t>()}; + std::vector packet_; + // Low-level Helpers uint8_t strobe_(Command cmd); void write_(Register reg); @@ -107,4 +126,28 @@ template class SetIdleAction : public Action, public Pare void play(const Ts &...x) override { this->parent_->set_idle(); } }; +template class SendPacketAction : public Action, public Parented { + public: + void set_data_template(std::function(Ts...)> func) { this->data_func_ = func; } + void set_data_static(const uint8_t *data, size_t len) { + this->data_static_ = data; + this->data_static_len_ = len; + } + + void play(const Ts &...x) override { + if (this->data_func_) { + auto data = this->data_func_(x...); + this->parent_->transmit_packet(data); + } else if (this->data_static_ != nullptr) { + std::vector data(this->data_static_, this->data_static_ + this->data_static_len_); + this->parent_->transmit_packet(data); + } + } + + protected: + std::function(Ts...)> data_func_{}; + const uint8_t *data_static_{nullptr}; + size_t data_static_len_{0}; +}; + } // namespace esphome::cc1101 diff --git a/esphome/components/cc1101/cc1101defs.h b/esphome/components/cc1101/cc1101defs.h index afeb5f1d77..1bc42f5859 100644 --- a/esphome/components/cc1101/cc1101defs.h +++ b/esphome/components/cc1101/cc1101defs.h @@ -6,6 +6,12 @@ namespace esphome::cc1101 { static constexpr float XTAL_FREQUENCY = 26000000; +static constexpr float RSSI_OFFSET = 74.0f; +static constexpr float RSSI_STEP = 0.5f; + +static constexpr uint8_t STATUS_CRC_OK_MASK = 0x80; +static constexpr uint8_t STATUS_LQI_MASK = 0x7F; + static constexpr uint8_t BUS_BURST = 0x40; static constexpr uint8_t BUS_READ = 0x80; static constexpr uint8_t BUS_WRITE = 0x00; @@ -134,6 +140,10 @@ enum class SyncMode : uint8_t { SYNC_MODE_15_16, SYNC_MODE_16_16, SYNC_MODE_30_32, + SYNC_MODE_NONE_CS, + SYNC_MODE_15_16_CS, + SYNC_MODE_16_16_CS, + SYNC_MODE_30_32_CS, }; enum class Modulation : uint8_t { @@ -218,6 +228,19 @@ enum class HystLevel : uint8_t { HYST_LEVEL_HIGH, }; +enum class PacketFormat : uint8_t { + PACKET_FORMAT_FIFO, + PACKET_FORMAT_SYNC_SERIAL, + PACKET_FORMAT_RANDOM_TX, + PACKET_FORMAT_ASYNC_SERIAL, +}; + +enum class LengthConfig : uint8_t { + LENGTH_CONFIG_FIXED, + LENGTH_CONFIG_VARIABLE, + LENGTH_CONFIG_INFINITE, +}; + struct __attribute__((packed)) CC1101State { // Byte array accessors for bulk SPI transfers uint8_t *regs() { return reinterpret_cast(this); } diff --git a/esphome/components/const/__init__.py b/esphome/components/const/__init__.py index 12a69551f5..fcfafa0c1a 100644 --- a/esphome/components/const/__init__.py +++ b/esphome/components/const/__init__.py @@ -7,9 +7,11 @@ BYTE_ORDER_LITTLE = "little_endian" BYTE_ORDER_BIG = "big_endian" CONF_COLOR_DEPTH = "color_depth" +CONF_CRC_ENABLE = "crc_enable" CONF_DRAW_ROUNDING = "draw_rounding" CONF_ENABLED = "enabled" CONF_IGNORE_NOT_FOUND = "ignore_not_found" +CONF_ON_PACKET = "on_packet" CONF_ON_RECEIVE = "on_receive" CONF_ON_STATE_CHANGE = "on_state_change" CONF_REQUEST_HEADERS = "request_headers" diff --git a/esphome/components/sx126x/__init__.py b/esphome/components/sx126x/__init__.py index 1eb83b7a33..4641db6483 100644 --- a/esphome/components/sx126x/__init__.py +++ b/esphome/components/sx126x/__init__.py @@ -1,6 +1,7 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import spi +from esphome.components.const import CONF_CRC_ENABLE, CONF_ON_PACKET import esphome.config_validation as cv from esphome.const import CONF_BUSY_PIN, CONF_DATA, CONF_FREQUENCY, CONF_ID from esphome.core import ID, TimePeriod @@ -14,7 +15,6 @@ CONF_SX126X_ID = "sx126x_id" CONF_BANDWIDTH = "bandwidth" CONF_BITRATE = "bitrate" CONF_CODING_RATE = "coding_rate" -CONF_CRC_ENABLE = "crc_enable" CONF_CRC_INVERTED = "crc_inverted" CONF_CRC_SIZE = "crc_size" CONF_CRC_POLYNOMIAL = "crc_polynomial" @@ -23,7 +23,6 @@ CONF_DEVIATION = "deviation" CONF_DIO1_PIN = "dio1_pin" CONF_HW_VERSION = "hw_version" CONF_MODULATION = "modulation" -CONF_ON_PACKET = "on_packet" CONF_PA_POWER = "pa_power" CONF_PA_RAMP = "pa_ramp" CONF_PAYLOAD_LENGTH = "payload_length" diff --git a/esphome/components/sx127x/__init__.py b/esphome/components/sx127x/__init__.py index 77cb61f7f8..b569a75972 100644 --- a/esphome/components/sx127x/__init__.py +++ b/esphome/components/sx127x/__init__.py @@ -1,6 +1,7 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import spi +from esphome.components.const import CONF_CRC_ENABLE, CONF_ON_PACKET import esphome.config_validation as cv from esphome.const import CONF_DATA, CONF_FREQUENCY, CONF_ID from esphome.core import ID @@ -16,11 +17,9 @@ CONF_BANDWIDTH = "bandwidth" CONF_BITRATE = "bitrate" CONF_BITSYNC = "bitsync" CONF_CODING_RATE = "coding_rate" -CONF_CRC_ENABLE = "crc_enable" CONF_DEVIATION = "deviation" CONF_DIO0_PIN = "dio0_pin" CONF_MODULATION = "modulation" -CONF_ON_PACKET = "on_packet" CONF_PA_PIN = "pa_pin" CONF_PA_POWER = "pa_power" CONF_PA_RAMP = "pa_ramp" diff --git a/tests/components/cc1101/common.yaml b/tests/components/cc1101/common.yaml index 9373ca43e1..93f03e582e 100644 --- a/tests/components/cc1101/common.yaml +++ b/tests/components/cc1101/common.yaml @@ -1,13 +1,26 @@ cc1101: id: transceiver cs_pin: ${cs_pin} + gdo0_pin: ${gdo0_pin} frequency: 433.92MHz if_frequency: 153kHz filter_bandwidth: 203kHz channel: 0 channel_spacing: 200kHz - symbol_rate: 5000 - modulation_type: ASK/OOK + symbol_rate: 4800 + modulation_type: GFSK + packet_mode: true + packet_length: 8 + crc_enable: true + whitening: false + sync_mode: "16/16" + sync0: 0x91 + sync1: 0xD3 + num_preamble: 2 + on_packet: + then: + - lambda: |- + ESP_LOGD("cc1101", "packet %s rssi %.1f dBm lqi %u", format_hex(x).c_str(), rssi, lqi); button: - platform: template @@ -18,3 +31,7 @@ button: - cc1101.begin_rx: transceiver - cc1101.set_idle: transceiver - cc1101.reset: transceiver + - cc1101.send_packet: + data: [0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef] + - cc1101.send_packet: !lambda |- + return {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; diff --git a/tests/components/cc1101/test.esp32-idf.yaml b/tests/components/cc1101/test.esp32-idf.yaml index e075629679..966f11bb64 100644 --- a/tests/components/cc1101/test.esp32-idf.yaml +++ b/tests/components/cc1101/test.esp32-idf.yaml @@ -1,8 +1,8 @@ substitutions: cs_pin: GPIO5 + gdo0_pin: GPIO4 packages: spi: !include ../../test_build_components/common/spi/esp32-idf.yaml - remote_receiver: !include ../../test_build_components/common/remote_receiver/esp32-idf.yaml <<: !include common.yaml diff --git a/tests/components/cc1101/test.esp8266.yaml b/tests/components/cc1101/test.esp8266.yaml index 7900658bc1..6f0f078507 100644 --- a/tests/components/cc1101/test.esp8266.yaml +++ b/tests/components/cc1101/test.esp8266.yaml @@ -1,8 +1,8 @@ substitutions: cs_pin: GPIO5 + gdo0_pin: GPIO4 packages: spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml - remote_receiver: !include ../../test_build_components/common/remote_receiver/esp8266-ard.yaml <<: !include common.yaml From 780a407b10fc04163fcba4a8e8bc2cbbe5cc5d37 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Dec 2025 13:30:55 -0600 Subject: [PATCH 0635/1145] [dashboard] Add ESPHOME_TRUSTED_DOMAINS support to events WebSocket (#12479) --- esphome/dashboard/web_server.py | 32 ++++++----- tests/dashboard/test_web_server.py | 87 ++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 14 deletions(-) diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index 804a2b99af..f94d8eea22 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -164,8 +164,24 @@ def websocket_method(name): return wrap +class CheckOriginMixin: + """Mixin to handle WebSocket origin checks for reverse proxy setups.""" + + def check_origin(self, origin: str) -> bool: + if "ESPHOME_TRUSTED_DOMAINS" not in os.environ: + return super().check_origin(origin) + trusted_domains = [ + s.strip() for s in os.environ["ESPHOME_TRUSTED_DOMAINS"].split(",") + ] + url = urlparse(origin) + if url.hostname in trusted_domains: + return True + _LOGGER.info("check_origin %s, domain is not trusted", origin) + return False + + @websocket_class -class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler): +class EsphomeCommandWebSocket(CheckOriginMixin, tornado.websocket.WebSocketHandler): """Base class for ESPHome websocket commands.""" def __init__( @@ -183,18 +199,6 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler): # use Popen() with a reading thread instead self._use_popen = os.name == "nt" - def check_origin(self, origin): - if "ESPHOME_TRUSTED_DOMAINS" not in os.environ: - return super().check_origin(origin) - trusted_domains = [ - s.strip() for s in os.environ["ESPHOME_TRUSTED_DOMAINS"].split(",") - ] - url = urlparse(origin) - if url.hostname in trusted_domains: - return True - _LOGGER.info("check_origin %s, domain is not trusted", origin) - return False - def open(self, *args: str, **kwargs: str) -> None: """Handle new WebSocket connection.""" # Ensure messages from the subprocess are sent immediately @@ -601,7 +605,7 @@ DASHBOARD_SUBSCRIBER = DashboardSubscriber() @websocket_class -class DashboardEventsWebSocket(tornado.websocket.WebSocketHandler): +class DashboardEventsWebSocket(CheckOriginMixin, tornado.websocket.WebSocketHandler): """WebSocket handler for real-time dashboard events.""" _event_listeners: list[Callable[[], None]] | None = None diff --git a/tests/dashboard/test_web_server.py b/tests/dashboard/test_web_server.py index 385841b1c8..10ca6061e6 100644 --- a/tests/dashboard/test_web_server.py +++ b/tests/dashboard/test_web_server.py @@ -1567,3 +1567,90 @@ async def test_dashboard_yaml_loading_with_packages_and_secrets( # If we get here, secret resolution worked! assert "esphome" in config assert config["esphome"]["name"] == "test-download-secrets" + + +@pytest.mark.asyncio +async def test_websocket_check_origin_default_same_origin( + dashboard: DashboardTestHelper, +) -> None: + """Test WebSocket uses default same-origin check when ESPHOME_TRUSTED_DOMAINS not set.""" + # Ensure ESPHOME_TRUSTED_DOMAINS is not set + env = os.environ.copy() + env.pop("ESPHOME_TRUSTED_DOMAINS", None) + with patch.dict(os.environ, env, clear=True): + from tornado.httpclient import HTTPRequest + + url = f"ws://127.0.0.1:{dashboard.port}/events" + # Same origin should work (default Tornado behavior) + request = HTTPRequest( + url, headers={"Origin": f"http://127.0.0.1:{dashboard.port}"} + ) + ws = await websocket_connect(request) + try: + msg = await ws.read_message() + assert msg is not None + data = json.loads(msg) + assert data["event"] == "initial_state" + finally: + ws.close() + + +@pytest.mark.asyncio +async def test_websocket_check_origin_trusted_domain( + dashboard: DashboardTestHelper, +) -> None: + """Test WebSocket accepts connections from trusted domains.""" + with patch.dict(os.environ, {"ESPHOME_TRUSTED_DOMAINS": "trusted.example.com"}): + from tornado.httpclient import HTTPRequest + + url = f"ws://127.0.0.1:{dashboard.port}/events" + request = HTTPRequest(url, headers={"Origin": "https://trusted.example.com"}) + ws = await websocket_connect(request) + try: + # Should receive initial state + msg = await ws.read_message() + assert msg is not None + data = json.loads(msg) + assert data["event"] == "initial_state" + finally: + ws.close() + + +@pytest.mark.asyncio +async def test_websocket_check_origin_untrusted_domain( + dashboard: DashboardTestHelper, +) -> None: + """Test WebSocket rejects connections from untrusted domains.""" + with patch.dict(os.environ, {"ESPHOME_TRUSTED_DOMAINS": "trusted.example.com"}): + from tornado.httpclient import HTTPRequest + + url = f"ws://127.0.0.1:{dashboard.port}/events" + request = HTTPRequest(url, headers={"Origin": "https://untrusted.example.com"}) + with pytest.raises(HTTPClientError) as exc_info: + await websocket_connect(request) + # Should get HTTP 403 Forbidden due to origin check failure + assert exc_info.value.code == 403 + + +@pytest.mark.asyncio +async def test_websocket_check_origin_multiple_trusted_domains( + dashboard: DashboardTestHelper, +) -> None: + """Test WebSocket accepts connections from multiple trusted domains.""" + with patch.dict( + os.environ, + {"ESPHOME_TRUSTED_DOMAINS": "first.example.com, second.example.com"}, + ): + from tornado.httpclient import HTTPRequest + + url = f"ws://127.0.0.1:{dashboard.port}/events" + # Test second domain in list (with space after comma) + request = HTTPRequest(url, headers={"Origin": "https://second.example.com"}) + ws = await websocket_connect(request) + try: + msg = await ws.read_message() + assert msg is not None + data = json.loads(msg) + assert data["event"] == "initial_state" + finally: + ws.close() From 96e418a8caa6e0ea6a59d1d3a9df5a8827eb9707 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Dec 2025 13:31:07 -0600 Subject: [PATCH 0636/1145] [wifi_signal] Skip publishing disconnected RSSI value (#12482) --- esphome/components/wifi_signal/wifi_signal_sensor.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi_signal/wifi_signal_sensor.h b/esphome/components/wifi_signal/wifi_signal_sensor.h index 5d7f4b4562..9f581f1eb2 100644 --- a/esphome/components/wifi_signal/wifi_signal_sensor.h +++ b/esphome/components/wifi_signal/wifi_signal_sensor.h @@ -16,7 +16,12 @@ class WiFiSignalSensor : public sensor::Sensor, public PollingComponent { #ifdef USE_WIFI_LISTENERS void setup() override { wifi::global_wifi_component->add_connect_state_listener(this); } #endif - void update() override { this->publish_state(wifi::global_wifi_component->wifi_rssi()); } + void update() override { + int8_t rssi = wifi::global_wifi_component->wifi_rssi(); + if (rssi != wifi::WIFI_RSSI_DISCONNECTED) { + this->publish_state(rssi); + } + } void dump_config() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } From 3a5e708c135af4016a64629f0c70cc16b76a5162 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Dec 2025 13:31:19 -0600 Subject: [PATCH 0637/1145] [web_server_idf] Always enable LRU purge to prevent socket exhaustion (#12481) --- .../captive_portal/captive_portal.cpp | 6 ------ .../captive_portal/captive_portal.h | 4 ---- .../web_server_idf/web_server_idf.cpp | 19 +++++-------------- .../web_server_idf/web_server_idf.h | 2 -- 4 files changed, 5 insertions(+), 26 deletions(-) diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 4eb00835b1..e1f92d2d2b 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -65,12 +65,6 @@ void CaptivePortal::start() { this->base_->init(); if (!this->initialized_) { this->base_->add_handler(this); -#ifdef USE_ESP32 - // Enable LRU socket purging to handle captive portal detection probe bursts - // OS captive portal detection makes many simultaneous HTTP requests which can - // exhaust sockets. LRU purging automatically closes oldest idle connections. - this->base_->get_server()->set_lru_purge_enable(true); -#endif } network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index ae9b9dfba0..f48c286f0c 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -40,10 +40,6 @@ class CaptivePortal : public AsyncWebHandler, public Component { void end() { this->active_ = false; this->disable_loop(); // Stop processing DNS requests -#ifdef USE_ESP32 - // Disable LRU socket purging now that captive portal is done - this->base_->get_server()->set_lru_purge_enable(false); -#endif this->base_->deinit(); if (this->dns_server_ != nullptr) { this->dns_server_->stop(); diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index af99b85e53..8c3ad288c0 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -117,18 +117,6 @@ void AsyncWebServer::end() { } } -void AsyncWebServer::set_lru_purge_enable(bool enable) { - if (this->lru_purge_enable_ == enable) { - return; // No change needed - } - this->lru_purge_enable_ = enable; - // If server is already running, restart it with new config - if (this->server_) { - this->end(); - this->begin(); - } -} - void AsyncWebServer::begin() { if (this->server_) { this->end(); @@ -136,8 +124,11 @@ void AsyncWebServer::begin() { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.server_port = this->port_; config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; }; - // Enable LRU purging if requested (e.g., by captive portal to handle probe bursts) - config.lru_purge_enable = this->lru_purge_enable_; + // Always enable LRU purging to handle socket exhaustion gracefully. + // When max sockets is reached, the oldest connection is closed to make room for new ones. + // This prevents "httpd_accept_conn: error in accept (23)" errors. + // See: https://github.com/esphome/esphome/issues/12464 + config.lru_purge_enable = true; // Use custom close function that shuts down before closing to prevent lwIP race conditions config.close_fn = AsyncWebServer::safe_close_with_shutdown; if (httpd_start(&this->server_, &config) == ESP_OK) { diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index a139e9e4df..5f9f598388 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -199,13 +199,11 @@ class AsyncWebServer { return *handler; } - void set_lru_purge_enable(bool enable); httpd_handle_t get_server() { return this->server_; } protected: uint16_t port_{}; httpd_handle_t server_{}; - bool lru_purge_enable_{false}; static esp_err_t request_handler(httpd_req_t *r); static esp_err_t request_post_handler(httpd_req_t *r); esp_err_t request_handler_(AsyncWebServerRequest *request) const; From 8524b894d637b936a5b8dc3bc652f2ce4f2e0b8b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Dec 2025 13:47:11 -0600 Subject: [PATCH 0638/1145] [ota] Match client timeout to device timeout to prevent premature failures (#12484) --- esphome/espota2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/espota2.py b/esphome/espota2.py index c29506224c..6349ad0fa8 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -322,8 +322,8 @@ def perform_ota( hash_func, nonce_size, hash_name = _AUTH_METHODS[auth] perform_auth(sock, password, hash_func, nonce_size, hash_name) - # Set higher timeout during upload - sock.settimeout(30.0) + # Timeout must match device-side OTA_SOCKET_TIMEOUT_DATA to prevent premature failures + sock.settimeout(90.0) upload_size = len(upload_contents) upload_size_encoded = [ From cee532a1e3234f8ea2a12934d6587db5f896d980 Mon Sep 17 00:00:00 2001 From: guillempages Date: Sun, 14 Dec 2025 21:15:19 +0100 Subject: [PATCH 0639/1145] [core] Use Arduino string macros only on ESP8266 (#12471) --- esphome/core/progmem.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/core/progmem.h b/esphome/core/progmem.h index 67131fd113..f9508945e8 100644 --- a/esphome/core/progmem.h +++ b/esphome/core/progmem.h @@ -1,16 +1,16 @@ #pragma once // Platform-agnostic macros for PROGMEM string handling -// On ESP32 (both Arduino and IDF): Use plain strings (no PROGMEM) // On ESP8266/Arduino: Use Arduino's F() macro for PROGMEM strings +// On other platforms: Use plain strings (no PROGMEM) -#ifdef USE_ESP32 -#define ESPHOME_F(string_literal) (string_literal) -#define ESPHOME_PGM_P const char * -#define ESPHOME_strncpy_P strncpy -#else -// ESP8266 and other Arduino platforms use Arduino macros +#ifdef USE_ESP8266 +// ESP8266 uses Arduino macros #define ESPHOME_F(string_literal) F(string_literal) #define ESPHOME_PGM_P PGM_P #define ESPHOME_strncpy_P strncpy_P +#else +#define ESPHOME_F(string_literal) (string_literal) +#define ESPHOME_PGM_P const char * +#define ESPHOME_strncpy_P strncpy #endif From fa5b14fad43273ad01d6ed247bca721c6fc7b216 Mon Sep 17 00:00:00 2001 From: mbohdal Date: Sun, 14 Dec 2025 22:40:08 +0100 Subject: [PATCH 0640/1145] [ethernet] fix used pins validation in configuration of RMII pins (#12486) --- esphome/components/ethernet/__init__.py | 5 ++++- .../test-lan8720-with-expander.esp32-idf.yaml | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/components/ethernet/test-lan8720-with-expander.esp32-idf.yaml diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index b4b1fcd9f6..e1ed327fb9 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -434,10 +434,13 @@ def _final_validate_rmii_pins(config: ConfigType) -> None: # Check all used pins against RMII reserved pins for pin_list in pins.PIN_SCHEMA_REGISTRY.pins_used.values(): - for pin_path, _, pin_config in pin_list: + for pin_path, pin_device, pin_config in pin_list: pin_num = pin_config.get(CONF_NUMBER) if pin_num not in rmii_pins: continue + # Skip if pin is not directly on ESP, but at some expander (device set to something else than 'None') + if pin_device is not None: + continue # Found a conflict - show helpful error message pin_function = rmii_pins[pin_num] component_path = ".".join(str(p) for p in pin_path) diff --git a/tests/components/ethernet/test-lan8720-with-expander.esp32-idf.yaml b/tests/components/ethernet/test-lan8720-with-expander.esp32-idf.yaml new file mode 100644 index 0000000000..09da8d90d9 --- /dev/null +++ b/tests/components/ethernet/test-lan8720-with-expander.esp32-idf.yaml @@ -0,0 +1,15 @@ +<<: !include common-lan8720.yaml + +sn74hc165: + - id: sn74hc165_hub + clock_pin: GPIO13 + data_pin: GPIO14 + load_pin: GPIO15 + sr_count: 3 + +binary_sensor: + - platform: gpio + pin: + sn74hc165: sn74hc165_hub + number: 19 + id: relay_2 From ffce80f96cf3348537401642f84b5a49ce95fa69 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Dec 2025 16:26:34 -0600 Subject: [PATCH 0641/1145] [wifi] Fix WiFi recovery after failed connection attempts (#12483) --- esphome/components/wifi/wifi_component.cpp | 26 ++++++++++++++++--- .../wifi/wifi_component_esp_idf.cpp | 1 + .../wifi/wifi_component_libretiny.cpp | 10 +++++-- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index d46916bfd9..a5e8c4a59d 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -205,6 +205,21 @@ static constexpr uint32_t WIFI_COOLDOWN_DURATION_MS = 500; /// While connecting, WiFi can't beacon the AP properly, so needs longer cooldown static constexpr uint32_t WIFI_COOLDOWN_WITH_AP_ACTIVE_MS = 30000; +/// Timeout for WiFi scan operations +/// This is a fallback in case we don't receive a scan done callback from the WiFi driver. +/// Normal scans complete via callback; this only triggers if something goes wrong. +static constexpr uint32_t WIFI_SCAN_TIMEOUT_MS = 31000; + +/// Timeout for WiFi connection attempts +/// This is a fallback in case we don't receive connection success/failure callbacks. +/// Some platforms (especially LibreTiny/Beken) can take 30-60 seconds to connect, +/// particularly with fast_connect enabled where no prior scan provides channel info. +/// Do not lower this value - connection failures are detected via callbacks, not timeout. +/// If this timeout fires prematurely while a connection is still in progress, it causes +/// cascading failures: the subsequent scan will also fail because the WiFi driver is +/// still busy with the previous connection attempt. +static constexpr uint32_t WIFI_CONNECT_TIMEOUT_MS = 46000; + static constexpr uint8_t get_max_retries_for_phase(WiFiRetryPhase phase) { switch (phase) { case WiFiRetryPhase::INITIAL_CONNECT: @@ -1035,7 +1050,7 @@ __attribute__((noinline)) static void log_scan_result(const WiFiScanResult &res) void WiFiComponent::check_scanning_finished() { if (!this->scan_done_) { - if (millis() - this->action_started_ > 30000) { + if (millis() - this->action_started_ > WIFI_SCAN_TIMEOUT_MS) { ESP_LOGE(TAG, "Scan timeout"); this->retry_connect(); } @@ -1184,8 +1199,9 @@ void WiFiComponent::check_connecting_finished() { } uint32_t now = millis(); - if (now - this->action_started_ > 30000) { - ESP_LOGW(TAG, "Connection timeout"); + if (now - this->action_started_ > WIFI_CONNECT_TIMEOUT_MS) { + ESP_LOGW(TAG, "Connection timeout, aborting connection attempt"); + this->wifi_disconnect_(); this->retry_connect(); return; } @@ -1405,6 +1421,10 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) { // without disrupting the captive portal/improv connection if (!this->is_captive_portal_active_() && !this->is_esp32_improv_active_()) { this->restart_adapter(); + } else { + // Even when skipping full restart, disconnect to clear driver state + // Without this, platforms like LibreTiny may think we're still connecting + this->wifi_disconnect_(); } // Clear scan flag - we're starting a new retry cycle this->did_scan_this_cycle_ = false; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 1f4eb1e42c..4a3c40a119 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -720,6 +720,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_STOP) { ESP_LOGV(TAG, "STA stop"); s_sta_started = false; + s_sta_connecting = false; } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_AUTHMODE_CHANGE) { const auto &it = data->data.sta_authmode_change; diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 4fd64bdfa3..36003a6eb4 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -291,6 +291,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } case ESPHOME_EVENT_ID_WIFI_STA_STOP: { ESP_LOGV(TAG, "STA stop"); + s_sta_connecting = false; break; } case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { @@ -322,7 +323,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ // wifi_sta_connect_status_() to return IDLE. The main loop then sees // "Unknown connection status 0" (wifi_component.cpp check_connecting_finished) // and calls retry_connect(), aborting a connection that may succeed moments later. - // Real connection failures will have ssid/bssid populated, or we'll hit the 30s timeout. + // Real connection failures will have ssid/bssid populated, or we'll hit the connection timeout. if (it.ssid_len == 0 && s_sta_connecting) { ESP_LOGV(TAG, "Ignoring disconnect event with empty ssid while connecting (reason=%s)", get_disconnect_reason_str(it.reason)); @@ -527,7 +528,12 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.softAPIP()}; } #endif // USE_WIFI_AP -bool WiFiComponent::wifi_disconnect_() { return WiFi.disconnect(); } +bool WiFiComponent::wifi_disconnect_() { + // Clear connecting flag first so disconnect events aren't ignored + // and wifi_sta_connect_status_() returns IDLE instead of CONNECTING + s_sta_connecting = false; + return WiFi.disconnect(); +} bssid_t WiFiComponent::wifi_bssid() { bssid_t bssid{}; From c69d58273a0b6a2eefc034158e5bf7c1411f9359 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 13 Dec 2025 07:38:31 -0600 Subject: [PATCH 0642/1145] [core] Fix CORE.raw_config not updated after package merge (#12456) --- esphome/config.py | 4 +-- .../component_tests/packages/test_packages.py | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/esphome/config.py b/esphome/config.py index 694716be34..6f6ad4886b 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -1010,14 +1010,14 @@ def validate_config( result.add_error(err) return result - CORE.raw_config = config - # 1.1. Merge packages if CONF_PACKAGES in config: from esphome.components.packages import merge_packages config = merge_packages(config) + CORE.raw_config = config + # 1.2. Resolve !extend and !remove and check for REPLACEME # After this step, there will not be any Extend or Remove values in the config anymore try: diff --git a/tests/component_tests/packages/test_packages.py b/tests/component_tests/packages/test_packages.py index 3829e540d7..22fb2c4e32 100644 --- a/tests/component_tests/packages/test_packages.py +++ b/tests/component_tests/packages/test_packages.py @@ -6,6 +6,7 @@ from unittest.mock import MagicMock, patch import pytest from esphome.components.packages import CONFIG_SCHEMA, do_packages_pass, merge_packages +import esphome.config as config_module from esphome.config import resolve_extend_remove from esphome.config_helpers import Extend, Remove import esphome.config_validation as cv @@ -33,6 +34,7 @@ from esphome.const import ( CONF_VARS, CONF_WIFI, ) +from esphome.core import CORE from esphome.util import OrderedDict # Test strings @@ -991,3 +993,35 @@ def test_package_merge_invalid(invalid_package) -> None: with pytest.raises(cv.Invalid): merge_packages(config) + + +def test_raw_config_contains_merged_esphome_from_package(tmp_path) -> None: + """Test that CORE.raw_config contains esphome section from merged package. + + This is a regression test for the bug where CORE.raw_config was set before + packages were merged, causing KeyError when components accessed + CORE.raw_config[CONF_ESPHOME] and the esphome section came from a package. + """ + # Create a config where esphome section comes from a package + test_config = OrderedDict() + test_config[CONF_PACKAGES] = { + "base": { + CONF_ESPHOME: {CONF_NAME: TEST_DEVICE_NAME}, + } + } + test_config["esp32"] = {"board": "esp32dev"} + + # Set up CORE for the test + test_yaml = tmp_path / "test.yaml" + test_yaml.write_text("# test config") + CORE.reset() + CORE.config_path = test_yaml + + # Call validate_config - this should merge packages and set CORE.raw_config + config_module.validate_config(test_config, {}) + + # Verify that CORE.raw_config contains the esphome section from the package + assert CONF_ESPHOME in CORE.raw_config, ( + "CORE.raw_config should contain esphome section after package merge" + ) + assert CORE.raw_config[CONF_ESPHOME][CONF_NAME] == TEST_DEVICE_NAME From 4da95ccd7e6ed96d7932b22528457c6fbcc0dde7 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:47:15 +1000 Subject: [PATCH 0643/1145] [packet_transport] Ensure retransmission at update intervals (#12472) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../packet_transport/espnow_transport.cpp | 11 +++-------- .../espnow/packet_transport/espnow_transport.h | 1 - esphome/components/packet_transport/__init__.py | 5 +++++ .../packet_transport/packet_transport.cpp | 6 +++++- .../packet_transport/packet_transport.h | 3 ++- .../packet_transport/sx126x_transport.cpp | 6 ------ .../sx126x/packet_transport/sx126x_transport.h | 1 - .../packet_transport/sx127x_transport.cpp | 6 ------ .../sx127x/packet_transport/sx127x_transport.h | 1 - .../uart/packet_transport/uart_transport.cpp | 6 ------ .../uart/packet_transport/uart_transport.h | 1 - .../udp/packet_transport/udp_transport.cpp | 17 +---------------- .../udp/packet_transport/udp_transport.h | 2 -- tests/components/espnow/common.yaml | 4 ++-- 14 files changed, 18 insertions(+), 52 deletions(-) diff --git a/esphome/components/espnow/packet_transport/espnow_transport.cpp b/esphome/components/espnow/packet_transport/espnow_transport.cpp index d30e9447a0..c1252acc9d 100644 --- a/esphome/components/espnow/packet_transport/espnow_transport.cpp +++ b/esphome/components/espnow/packet_transport/espnow_transport.cpp @@ -13,7 +13,7 @@ static const char *const TAG = "espnow.transport"; bool ESPNowTransport::should_send() { return this->parent_ != nullptr && !this->parent_->is_failed(); } void ESPNowTransport::setup() { - packet_transport::PacketTransport::setup(); + PacketTransport::setup(); if (this->parent_ == nullptr) { ESP_LOGE(TAG, "ESPNow component not set"); @@ -26,15 +26,10 @@ void ESPNowTransport::setup() { this->peer_address_[2], this->peer_address_[3], this->peer_address_[4], this->peer_address_[5]); // Register received handler - this->parent_->register_received_handler(static_cast(this)); + this->parent_->register_received_handler(this); // Register broadcasted handler - this->parent_->register_broadcasted_handler(static_cast(this)); -} - -void ESPNowTransport::update() { - packet_transport::PacketTransport::update(); - this->updated_ = true; + this->parent_->register_broadcasted_handler(this); } void ESPNowTransport::send_packet(const std::vector &buf) const { diff --git a/esphome/components/espnow/packet_transport/espnow_transport.h b/esphome/components/espnow/packet_transport/espnow_transport.h index 3629fad2cd..d85119db7d 100644 --- a/esphome/components/espnow/packet_transport/espnow_transport.h +++ b/esphome/components/espnow/packet_transport/espnow_transport.h @@ -18,7 +18,6 @@ class ESPNowTransport : public packet_transport::PacketTransport, public ESPNowBroadcastedHandler { public: void setup() override; - void update() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } void set_peer_address(peer_address_t address) { diff --git a/esphome/components/packet_transport/__init__.py b/esphome/components/packet_transport/__init__.py index 43da7740fe..1930e45e85 100644 --- a/esphome/components/packet_transport/__init__.py +++ b/esphome/components/packet_transport/__init__.py @@ -176,17 +176,22 @@ async def register_packet_transport(var, config): if encryption := provider.get(CONF_ENCRYPTION): cg.add(var.set_provider_encryption(name, hash_encryption_key(encryption))) + is_provider = False for sens_conf in config.get(CONF_SENSORS, ()): + is_provider = True 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, ()): + is_provider = True 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)) + if is_provider: + cg.add(var.set_is_provider(True)) if encryption := config.get(CONF_ENCRYPTION): cg.add(var.set_encryption_key(hash_encryption_key(encryption))) return providers diff --git a/esphome/components/packet_transport/packet_transport.cpp b/esphome/components/packet_transport/packet_transport.cpp index 37e5f3d9e1..da7f5f8bff 100644 --- a/esphome/components/packet_transport/packet_transport.cpp +++ b/esphome/components/packet_transport/packet_transport.cpp @@ -263,6 +263,7 @@ void PacketTransport::flush_() { xxtea::encrypt((uint32_t *) (encode_buffer.data() + header_len), len / 4, (uint32_t *) this->encryption_key_.data()); } + ESP_LOGVV(TAG, "Sending packet %s", format_hex_pretty(encode_buffer.data(), encode_buffer.size()).c_str()); this->send_packet(encode_buffer); } @@ -316,6 +317,9 @@ void PacketTransport::send_data_(bool all) { } void PacketTransport::update() { + // resend all sensors if required + if (this->is_provider_) + this->send_data_(true); if (!this->ping_pong_enable_) { return; } @@ -551,7 +555,7 @@ void PacketTransport::loop() { if (this->resend_ping_key_) this->send_ping_pong_request_(); if (this->updated_) { - this->send_data_(this->resend_data_); + this->send_data_(false); } } diff --git a/esphome/components/packet_transport/packet_transport.h b/esphome/components/packet_transport/packet_transport.h index a2370e9749..86ec564fce 100644 --- a/esphome/components/packet_transport/packet_transport.h +++ b/esphome/components/packet_transport/packet_transport.h @@ -91,6 +91,7 @@ class PacketTransport : public PollingComponent { } } + void set_is_provider(bool is_provider) { this->is_provider_ = is_provider; } 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; } @@ -129,7 +130,7 @@ class PacketTransport : public PollingComponent { uint32_t ping_pong_recyle_time_{}; uint32_t last_key_time_{}; bool resend_ping_key_{}; - bool resend_data_{}; + bool is_provider_{}; const char *name_{}; ESPPreferenceObject pref_{}; diff --git a/esphome/components/sx126x/packet_transport/sx126x_transport.cpp b/esphome/components/sx126x/packet_transport/sx126x_transport.cpp index 2cfc4b700e..59d80bd297 100644 --- a/esphome/components/sx126x/packet_transport/sx126x_transport.cpp +++ b/esphome/components/sx126x/packet_transport/sx126x_transport.cpp @@ -12,12 +12,6 @@ void SX126xTransport::setup() { this->parent_->register_listener(this); } -void SX126xTransport::update() { - PacketTransport::update(); - this->updated_ = true; - this->resend_data_ = true; -} - void SX126xTransport::send_packet(const std::vector &buf) const { this->parent_->transmit_packet(buf); } void SX126xTransport::on_packet(const std::vector &packet, float rssi, float snr) { this->process_(packet); } diff --git a/esphome/components/sx126x/packet_transport/sx126x_transport.h b/esphome/components/sx126x/packet_transport/sx126x_transport.h index 755d30417d..640c6a76f9 100644 --- a/esphome/components/sx126x/packet_transport/sx126x_transport.h +++ b/esphome/components/sx126x/packet_transport/sx126x_transport.h @@ -11,7 +11,6 @@ namespace sx126x { class SX126xTransport : public packet_transport::PacketTransport, public Parented, public SX126xListener { public: void setup() override; - void update() override; void on_packet(const std::vector &packet, float rssi, float snr) override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } diff --git a/esphome/components/sx127x/packet_transport/sx127x_transport.cpp b/esphome/components/sx127x/packet_transport/sx127x_transport.cpp index b1d014bb96..893726e816 100644 --- a/esphome/components/sx127x/packet_transport/sx127x_transport.cpp +++ b/esphome/components/sx127x/packet_transport/sx127x_transport.cpp @@ -12,12 +12,6 @@ void SX127xTransport::setup() { this->parent_->register_listener(this); } -void SX127xTransport::update() { - PacketTransport::update(); - this->updated_ = true; - this->resend_data_ = true; -} - void SX127xTransport::send_packet(const std::vector &buf) const { this->parent_->transmit_packet(buf); } void SX127xTransport::on_packet(const std::vector &packet, float rssi, float snr) { this->process_(packet); } diff --git a/esphome/components/sx127x/packet_transport/sx127x_transport.h b/esphome/components/sx127x/packet_transport/sx127x_transport.h index e27b7f8d57..6208372971 100644 --- a/esphome/components/sx127x/packet_transport/sx127x_transport.h +++ b/esphome/components/sx127x/packet_transport/sx127x_transport.h @@ -11,7 +11,6 @@ namespace sx127x { class SX127xTransport : public packet_transport::PacketTransport, public Parented, public SX127xListener { public: void setup() override; - void update() override; void on_packet(const std::vector &packet, float rssi, float snr) override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } diff --git a/esphome/components/uart/packet_transport/uart_transport.cpp b/esphome/components/uart/packet_transport/uart_transport.cpp index 4a9aa0fe47..6b8eae611c 100644 --- a/esphome/components/uart/packet_transport/uart_transport.cpp +++ b/esphome/components/uart/packet_transport/uart_transport.cpp @@ -55,12 +55,6 @@ void UARTTransport::loop() { } } -void UARTTransport::update() { - this->updated_ = true; - this->resend_data_ = true; - PacketTransport::update(); -} - /** * Write a byte to the UART bus. If the byte is a flag or control byte, it will be escaped. * @param byte The byte to write. diff --git a/esphome/components/uart/packet_transport/uart_transport.h b/esphome/components/uart/packet_transport/uart_transport.h index e84bed95e6..1c92af536e 100644 --- a/esphome/components/uart/packet_transport/uart_transport.h +++ b/esphome/components/uart/packet_transport/uart_transport.h @@ -23,7 +23,6 @@ static const uint8_t CONTROL_BYTE = 0x7D; class UARTTransport : public packet_transport::PacketTransport, public UARTDevice { public: void loop() override; - void update() override; float get_setup_priority() const override { return setup_priority::PROCESSOR; } protected: diff --git a/esphome/components/udp/packet_transport/udp_transport.cpp b/esphome/components/udp/packet_transport/udp_transport.cpp index b92e0d64df..f3e33573a5 100644 --- a/esphome/components/udp/packet_transport/udp_transport.cpp +++ b/esphome/components/udp/packet_transport/udp_transport.cpp @@ -8,29 +8,14 @@ namespace udp { static const char *const TAG = "udp_transport"; -bool UDPTransport::should_send() { return this->should_broadcast_ && network::is_connected(); } +bool UDPTransport::should_send() { return network::is_connected(); } void UDPTransport::setup() { PacketTransport::setup(); - this->should_broadcast_ = this->ping_pong_enable_; -#ifdef USE_SENSOR - this->should_broadcast_ |= !this->sensors_.empty(); -#endif -#ifdef USE_BINARY_SENSOR - this->should_broadcast_ |= !this->binary_sensors_.empty(); -#endif - if (this->should_broadcast_) - this->parent_->set_should_broadcast(); if (!this->providers_.empty() || this->is_encrypted_()) { this->parent_->add_listener([this](std::vector &buf) { this->process_(buf); }); } } -void UDPTransport::update() { - PacketTransport::update(); - this->updated_ = true; - this->resend_data_ = this->should_broadcast_; -} - void UDPTransport::send_packet(const std::vector &buf) const { this->parent_->send_packet(buf); } } // namespace udp } // namespace esphome diff --git a/esphome/components/udp/packet_transport/udp_transport.h b/esphome/components/udp/packet_transport/udp_transport.h index c87eb62780..8d01ae0909 100644 --- a/esphome/components/udp/packet_transport/udp_transport.h +++ b/esphome/components/udp/packet_transport/udp_transport.h @@ -12,14 +12,12 @@ namespace udp { class UDPTransport : public packet_transport::PacketTransport, public Parented { public: void setup() override; - void update() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } protected: void send_packet(const std::vector &buf) const override; bool should_send() override; - bool should_broadcast_{false}; size_t get_max_packet_size() override { return MAX_PACKET_SIZE; } }; diff --git a/tests/components/espnow/common.yaml b/tests/components/espnow/common.yaml index 895ffb9d15..b724af54e0 100644 --- a/tests/components/espnow/common.yaml +++ b/tests/components/espnow/common.yaml @@ -62,7 +62,7 @@ packet_transport: sensors: - temp_sensor providers: - - name: test_provider + - name: test-provider encryption: key: "0123456789abcdef0123456789abcdef" @@ -71,6 +71,6 @@ sensor: id: temp_sensor - platform: packet_transport - provider: test_provider + provider: test-provider remote_id: temp_sensor id: remote_temp From 359f45400f717b4fb00869c9b168d28604544c7e Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:47:52 -0500 Subject: [PATCH 0644/1145] [core] Fix polling_component_schema and type consistency (#12478) Co-authored-by: Claude --- esphome/config_validation.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index c52b791120..08fffa6cec 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -71,6 +71,7 @@ from esphome.const import ( PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, + SCHEDULER_DONT_RUN, TYPE_GIT, TYPE_LOCAL, VALID_SUBSTITUTIONS_CHARACTERS, @@ -894,7 +895,7 @@ def time_period_in_minutes_(value): def update_interval(value): if value == "never": - return 4294967295 # uint32_t max + return TimePeriodMilliseconds(milliseconds=SCHEDULER_DONT_RUN) return positive_time_period_milliseconds(value) @@ -2009,7 +2010,7 @@ def polling_component_schema(default_update_interval): if default_update_interval is None: return COMPONENT_SCHEMA.extend( { - Required(CONF_UPDATE_INTERVAL): default_update_interval, + Required(CONF_UPDATE_INTERVAL): update_interval, } ) assert isinstance(default_update_interval, str) From 46574fcbeceecf2ea6698a80fef0ceee5d2c99fe Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 14 Dec 2025 13:22:55 -0500 Subject: [PATCH 0645/1145] [cc1101] Add packet mode support (#12474) Co-authored-by: Claude --- esphome/components/cc1101/__init__.py | 105 +++++++++++- esphome/components/cc1101/cc1101.cpp | 171 +++++++++++++++++--- esphome/components/cc1101/cc1101.h | 49 +++++- esphome/components/cc1101/cc1101defs.h | 23 +++ esphome/components/const/__init__.py | 2 + esphome/components/sx126x/__init__.py | 3 +- esphome/components/sx127x/__init__.py | 3 +- tests/components/cc1101/common.yaml | 21 ++- tests/components/cc1101/test.esp32-idf.yaml | 2 +- tests/components/cc1101/test.esp8266.yaml | 2 +- 10 files changed, 340 insertions(+), 41 deletions(-) diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index e6b31b84f8..1971817fb1 100644 --- a/esphome/components/cc1101/__init__.py +++ b/esphome/components/cc1101/__init__.py @@ -1,9 +1,17 @@ -from esphome import automation +from esphome import automation, pins from esphome.automation import maybe_simple_id import esphome.codegen as cg from esphome.components import spi +from esphome.components.const import CONF_CRC_ENABLE, CONF_ON_PACKET import esphome.config_validation as cv -from esphome.const import CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, CONF_WAIT_TIME +from esphome.const import ( + CONF_CHANNEL, + CONF_DATA, + CONF_FREQUENCY, + CONF_ID, + CONF_WAIT_TIME, +) +from esphome.core import ID CODEOWNERS = ["@lygris", "@gabest11"] DEPENDENCIES = ["spi"] @@ -29,7 +37,6 @@ CONF_MANCHESTER = "manchester" CONF_NUM_PREAMBLE = "num_preamble" CONF_SYNC1 = "sync1" CONF_SYNC0 = "sync0" -CONF_PKTLEN = "pktlen" CONF_MAGN_TARGET = "magn_target" CONF_MAX_LNA_GAIN = "max_lna_gain" CONF_MAX_DVGA_GAIN = "max_dvga_gain" @@ -41,6 +48,12 @@ CONF_FILTER_LENGTH_ASK_OOK = "filter_length_ask_ook" CONF_FREEZE = "freeze" CONF_HYST_LEVEL = "hyst_level" +# Packet mode config keys +CONF_PACKET_MODE = "packet_mode" +CONF_PACKET_LENGTH = "packet_length" +CONF_WHITENING = "whitening" +CONF_GDO0_PIN = "gdo0_pin" + # Enums SyncMode = ns.enum("SyncMode", True) SYNC_MODE = { @@ -167,7 +180,6 @@ CONFIG_MAP = { CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7), CONF_SYNC1: cv.hex_uint8_t, CONF_SYNC0: cv.hex_uint8_t, - CONF_PKTLEN: cv.uint8_t, CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False), CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False), CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False), @@ -179,13 +191,36 @@ CONFIG_MAP = { CONF_FREEZE: cv.enum(FREEZE, upper=False), CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False), CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False), + CONF_PACKET_MODE: cv.boolean, + CONF_PACKET_LENGTH: cv.uint8_t, + CONF_CRC_ENABLE: cv.boolean, + CONF_WHITENING: cv.boolean, } -CONFIG_SCHEMA = ( - cv.Schema({cv.GenerateID(): cv.declare_id(CC1101Component)}) + +def _validate_packet_mode(config): + if config.get(CONF_PACKET_MODE, False): + if CONF_GDO0_PIN not in config: + raise cv.Invalid("gdo0_pin is required when packet_mode is enabled") + if CONF_PACKET_LENGTH not in config: + raise cv.Invalid("packet_length is required when packet_mode is enabled") + if config[CONF_PACKET_LENGTH] > 64: + raise cv.Invalid("packet_length must be <= 64 (FIFO size)") + return config + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(CC1101Component), + cv.Optional(CONF_GDO0_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True), + } + ) .extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()}) .extend(cv.COMPONENT_SCHEMA) - .extend(spi.spi_device_schema(cs_pin_required=True)) + .extend(spi.spi_device_schema(cs_pin_required=True)), + _validate_packet_mode, ) @@ -198,12 +233,29 @@ async def to_code(config): if key in config: cg.add(getattr(var, f"set_{key}")(config[key])) + if CONF_GDO0_PIN in config: + gdo0_pin = await cg.gpio_pin_expression(config[CONF_GDO0_PIN]) + cg.add(var.set_gdo0_pin(gdo0_pin)) + if CONF_ON_PACKET in config: + await automation.build_automation( + var.get_packet_trigger(), + [ + (cg.std_vector.template(cg.uint8), "x"), + (cg.float_, "rssi"), + (cg.uint8, "lqi"), + ], + config[CONF_ON_PACKET], + ) + # Actions BeginTxAction = ns.class_("BeginTxAction", automation.Action) BeginRxAction = ns.class_("BeginRxAction", automation.Action) ResetAction = ns.class_("ResetAction", automation.Action) SetIdleAction = ns.class_("SetIdleAction", automation.Action) +SendPacketAction = ns.class_( + "SendPacketAction", automation.Action, cg.Parented.template(CC1101Component) +) CC1101_ACTION_SCHEMA = cv.Schema( maybe_simple_id({cv.GenerateID(CONF_ID): cv.use_id(CC1101Component)}) @@ -218,3 +270,42 @@ async def cc1101_action_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) return var + + +def validate_raw_data(value): + if isinstance(value, str): + return value.encode("utf-8") + if isinstance(value, list): + return cv.Schema([cv.hex_uint8_t])(value) + raise cv.Invalid( + "data must either be a string wrapped in quotes or a list of bytes" + ) + + +SEND_PACKET_ACTION_SCHEMA = cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(CC1101Component), + cv.Required(CONF_DATA): cv.templatable(validate_raw_data), + }, + key=CONF_DATA, +) + + +@automation.register_action( + "cc1101.send_packet", SendPacketAction, SEND_PACKET_ACTION_SCHEMA +) +async def send_packet_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + data = config[CONF_DATA] + if isinstance(data, bytes): + data = list(data) + if cg.is_template(data): + templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) + cg.add(var.set_data_template(templ)) + else: + # Generate static array in flash to avoid RAM copy + arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) + arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data)) + cg.add(var.set_data_static(arr, len(data))) + return var diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 3cbf09ded8..5b6eb545bc 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -143,6 +143,11 @@ void CC1101Component::setup() { return; } + // Setup GDO0 pin if configured + if (this->gdo0_pin_ != nullptr) { + this->gdo0_pin_->setup(); + } + this->initialized_ = true; for (uint8_t i = 0; i <= static_cast(Register::TEST0); i++) { @@ -151,8 +156,69 @@ void CC1101Component::setup() { } this->write_(static_cast(i)); } - this->write_(Register::PATABLE, this->pa_table_, sizeof(this->pa_table_)); + this->set_output_power(this->output_power_requested_); this->strobe_(Command::RX); + + // Defer pin mode setup until after all components have completed setup() + // This handles the case where remote_transmitter runs after CC1101 and changes pin mode + if (this->gdo0_pin_ != nullptr) { + this->defer([this]() { this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT); }); + } +} + +void CC1101Component::loop() { + if (this->state_.PKT_FORMAT != static_cast(PacketFormat::PACKET_FORMAT_FIFO) || this->gdo0_pin_ == nullptr || + !this->gdo0_pin_->digital_read()) { + return; + } + + // Read state + this->read_(Register::RXBYTES); + uint8_t rx_bytes = this->state_.NUM_RXBYTES; + bool overflow = this->state_.RXFIFO_OVERFLOW; + if (overflow || rx_bytes == 0) { + ESP_LOGW(TAG, "RX FIFO overflow, flushing"); + this->enter_idle_(); + this->strobe_(Command::FRX); + this->strobe_(Command::RX); + this->wait_for_state_(State::RX); + return; + } + + // Read packet + uint8_t payload_length; + if (this->state_.LENGTH_CONFIG == static_cast(LengthConfig::LENGTH_CONFIG_VARIABLE)) { + this->read_(Register::FIFO, &payload_length, 1); + } else { + payload_length = this->state_.PKTLEN; + } + if (payload_length == 0 || payload_length > 64) { + ESP_LOGW(TAG, "Invalid payload length: %u", payload_length); + this->enter_idle_(); + this->strobe_(Command::FRX); + this->strobe_(Command::RX); + this->wait_for_state_(State::RX); + return; + } + this->packet_.resize(payload_length); + this->read_(Register::FIFO, this->packet_.data(), payload_length); + + // Read status and trigger + uint8_t status[2]; + this->read_(Register::FIFO, status, 2); + int8_t rssi_raw = static_cast(status[0]); + float rssi = (rssi_raw * RSSI_STEP) - RSSI_OFFSET; + bool crc_ok = (status[1] & STATUS_CRC_OK_MASK) != 0; + uint8_t lqi = status[1] & STATUS_LQI_MASK; + if (this->state_.CRC_EN == 0 || crc_ok) { + this->packet_trigger_->trigger(this->packet_, rssi, lqi); + } + + // Return to rx + this->enter_idle_(); + this->strobe_(Command::FRX); + this->strobe_(Command::RX); + this->wait_for_state_(State::RX); } void CC1101Component::dump_config() { @@ -177,9 +243,12 @@ void CC1101Component::dump_config() { } void CC1101Component::begin_tx() { - // Ensure Packet Format is 3 (Async Serial), use GDO0 as input during TX + // Ensure Packet Format is 3 (Async Serial) this->write_(Register::PKTCTRL0, 0x32); ESP_LOGV(TAG, "Beginning TX sequence"); + if (this->gdo0_pin_ != nullptr) { + this->gdo0_pin_->pin_mode(gpio::FLAG_OUTPUT); + } this->strobe_(Command::TX); if (!this->wait_for_state_(State::TX, 50)) { ESP_LOGW(TAG, "Timed out waiting for TX state!"); @@ -188,6 +257,9 @@ void CC1101Component::begin_tx() { void CC1101Component::begin_rx() { ESP_LOGV(TAG, "Beginning RX sequence"); + if (this->gdo0_pin_ != nullptr) { + this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT); + } this->strobe_(Command::RX); } @@ -201,20 +273,6 @@ void CC1101Component::set_idle() { this->enter_idle_(); } -void CC1101Component::set_gdo0_config(uint8_t value) { - this->state_.GDO0_CFG = value; - if (this->initialized_) { - this->write_(Register::IOCFG0); - } -} - -void CC1101Component::set_gdo2_config(uint8_t value) { - this->state_.GDO2_CFG = value; - if (this->initialized_) { - this->write_(Register::IOCFG2); - } -} - bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) { uint32_t start = millis(); while (millis() - start < timeout_ms) { @@ -282,6 +340,33 @@ void CC1101Component::read_(Register reg, uint8_t *buffer, size_t length) { this->disable(); } +CC1101Error CC1101Component::transmit_packet(const std::vector &packet) { + if (this->state_.PKT_FORMAT != static_cast(PacketFormat::PACKET_FORMAT_FIFO)) { + return CC1101Error::PARAMS; + } + + // Write packet + this->enter_idle_(); + this->strobe_(Command::FTX); + if (this->state_.LENGTH_CONFIG == static_cast(LengthConfig::LENGTH_CONFIG_VARIABLE)) { + this->write_(Register::FIFO, static_cast(packet.size())); + } + this->write_(Register::FIFO, packet.data(), packet.size()); + this->strobe_(Command::TX); + if (!this->wait_for_state_(State::IDLE, 1000)) { + ESP_LOGW(TAG, "TX timeout"); + this->enter_idle_(); + this->strobe_(Command::RX); + this->wait_for_state_(State::RX); + return CC1101Error::TIMEOUT; + } + + // Return to rx + this->strobe_(Command::RX); + this->wait_for_state_(State::RX); + return CC1101Error::NONE; +} + // Setters void CC1101Component::set_output_power(float value) { this->output_power_requested_ = value; @@ -428,6 +513,7 @@ void CC1101Component::set_modulation_type(Modulation value) { this->state_.PA_POWER = value == Modulation::MODULATION_ASK_OOK ? 1 : 0; if (this->initialized_) { this->enter_idle_(); + this->set_output_power(this->output_power_requested_); this->write_(Register::MDMCFG2); this->write_(Register::FREND0); this->strobe_(Command::RX); @@ -462,13 +548,6 @@ void CC1101Component::set_sync0(uint8_t value) { } } -void CC1101Component::set_pktlen(uint8_t value) { - this->state_.PKTLEN = value; - if (this->initialized_) { - this->write_(Register::PKTLEN); - } -} - void CC1101Component::set_magn_target(MagnTarget value) { this->state_.MAGN_TARGET = static_cast(value); if (this->initialized_) { @@ -546,4 +625,50 @@ void CC1101Component::set_hyst_level(HystLevel value) { } } +void CC1101Component::set_packet_mode(bool value) { + this->state_.PKT_FORMAT = + static_cast(value ? PacketFormat::PACKET_FORMAT_FIFO : PacketFormat::PACKET_FORMAT_ASYNC_SERIAL); + if (value) { + // Configure GDO0 for FIFO status (asserts on RX FIFO threshold or end of packet) + this->state_.GDO0_CFG = 0x01; + // Set max RX FIFO threshold to ensure we only trigger on end-of-packet + this->state_.FIFO_THR = 15; + } else { + // Configure GDO0 for serial data (async serial mode) + this->state_.GDO0_CFG = 0x0D; + } + if (this->initialized_) { + this->write_(Register::PKTCTRL0); + this->write_(Register::IOCFG0); + this->write_(Register::FIFOTHR); + } +} + +void CC1101Component::set_packet_length(uint8_t value) { + if (value == 0) { + this->state_.LENGTH_CONFIG = static_cast(LengthConfig::LENGTH_CONFIG_VARIABLE); + } else { + this->state_.LENGTH_CONFIG = static_cast(LengthConfig::LENGTH_CONFIG_FIXED); + this->state_.PKTLEN = value; + } + if (this->initialized_) { + this->write_(Register::PKTCTRL0); + this->write_(Register::PKTLEN); + } +} + +void CC1101Component::set_crc_enable(bool value) { + this->state_.CRC_EN = value ? 1 : 0; + if (this->initialized_) { + this->write_(Register::PKTCTRL0); + } +} + +void CC1101Component::set_whitening(bool value) { + this->state_.WHITE_DATA = value ? 1 : 0; + if (this->initialized_) { + this->write_(Register::PKTCTRL0); + } +} + } // namespace esphome::cc1101 diff --git a/esphome/components/cc1101/cc1101.h b/esphome/components/cc1101/cc1101.h index 65aeb2ea82..b896f7e974 100644 --- a/esphome/components/cc1101/cc1101.h +++ b/esphome/components/cc1101/cc1101.h @@ -5,9 +5,12 @@ #include "esphome/components/spi/spi.h" #include "esphome/core/automation.h" #include "cc1101defs.h" +#include namespace esphome::cc1101 { +enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW }; + class CC1101Component : public Component, public spi::SPIDevice { @@ -15,6 +18,7 @@ class CC1101Component : public Component, CC1101Component(); void setup() override; + void loop() override; void dump_config() override; // Actions @@ -24,8 +28,7 @@ class CC1101Component : public Component, void set_idle(); // GDO Pin Configuration - void set_gdo0_config(uint8_t value); - void set_gdo2_config(uint8_t value); + void set_gdo0_pin(InternalGPIOPin *pin) { this->gdo0_pin_ = pin; } // Configuration Setters void set_output_power(float value); @@ -48,7 +51,6 @@ class CC1101Component : public Component, void set_num_preamble(uint8_t value); void set_sync1(uint8_t value); void set_sync0(uint8_t value); - void set_pktlen(uint8_t value); // AGC settings void set_magn_target(MagnTarget value); @@ -63,6 +65,16 @@ class CC1101Component : public Component, void set_wait_time(WaitTime value); void set_hyst_level(HystLevel value); + // Packet mode settings + void set_packet_mode(bool value); + void set_packet_length(uint8_t value); + void set_crc_enable(bool value); + void set_whitening(bool value); + + // Packet mode operations + CC1101Error transmit_packet(const std::vector &packet); + Trigger, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; } + protected: uint16_t chip_id_{0}; bool initialized_{false}; @@ -73,6 +85,13 @@ class CC1101Component : public Component, CC1101State state_; + // GDO pin for packet reception + InternalGPIOPin *gdo0_pin_{nullptr}; + + // Packet handling + Trigger, float, uint8_t> *packet_trigger_{new Trigger, float, uint8_t>()}; + std::vector packet_; + // Low-level Helpers uint8_t strobe_(Command cmd); void write_(Register reg); @@ -107,4 +126,28 @@ template class SetIdleAction : public Action, public Pare void play(const Ts &...x) override { this->parent_->set_idle(); } }; +template class SendPacketAction : public Action, public Parented { + public: + void set_data_template(std::function(Ts...)> func) { this->data_func_ = func; } + void set_data_static(const uint8_t *data, size_t len) { + this->data_static_ = data; + this->data_static_len_ = len; + } + + void play(const Ts &...x) override { + if (this->data_func_) { + auto data = this->data_func_(x...); + this->parent_->transmit_packet(data); + } else if (this->data_static_ != nullptr) { + std::vector data(this->data_static_, this->data_static_ + this->data_static_len_); + this->parent_->transmit_packet(data); + } + } + + protected: + std::function(Ts...)> data_func_{}; + const uint8_t *data_static_{nullptr}; + size_t data_static_len_{0}; +}; + } // namespace esphome::cc1101 diff --git a/esphome/components/cc1101/cc1101defs.h b/esphome/components/cc1101/cc1101defs.h index afeb5f1d77..1bc42f5859 100644 --- a/esphome/components/cc1101/cc1101defs.h +++ b/esphome/components/cc1101/cc1101defs.h @@ -6,6 +6,12 @@ namespace esphome::cc1101 { static constexpr float XTAL_FREQUENCY = 26000000; +static constexpr float RSSI_OFFSET = 74.0f; +static constexpr float RSSI_STEP = 0.5f; + +static constexpr uint8_t STATUS_CRC_OK_MASK = 0x80; +static constexpr uint8_t STATUS_LQI_MASK = 0x7F; + static constexpr uint8_t BUS_BURST = 0x40; static constexpr uint8_t BUS_READ = 0x80; static constexpr uint8_t BUS_WRITE = 0x00; @@ -134,6 +140,10 @@ enum class SyncMode : uint8_t { SYNC_MODE_15_16, SYNC_MODE_16_16, SYNC_MODE_30_32, + SYNC_MODE_NONE_CS, + SYNC_MODE_15_16_CS, + SYNC_MODE_16_16_CS, + SYNC_MODE_30_32_CS, }; enum class Modulation : uint8_t { @@ -218,6 +228,19 @@ enum class HystLevel : uint8_t { HYST_LEVEL_HIGH, }; +enum class PacketFormat : uint8_t { + PACKET_FORMAT_FIFO, + PACKET_FORMAT_SYNC_SERIAL, + PACKET_FORMAT_RANDOM_TX, + PACKET_FORMAT_ASYNC_SERIAL, +}; + +enum class LengthConfig : uint8_t { + LENGTH_CONFIG_FIXED, + LENGTH_CONFIG_VARIABLE, + LENGTH_CONFIG_INFINITE, +}; + struct __attribute__((packed)) CC1101State { // Byte array accessors for bulk SPI transfers uint8_t *regs() { return reinterpret_cast(this); } diff --git a/esphome/components/const/__init__.py b/esphome/components/const/__init__.py index 12a69551f5..fcfafa0c1a 100644 --- a/esphome/components/const/__init__.py +++ b/esphome/components/const/__init__.py @@ -7,9 +7,11 @@ BYTE_ORDER_LITTLE = "little_endian" BYTE_ORDER_BIG = "big_endian" CONF_COLOR_DEPTH = "color_depth" +CONF_CRC_ENABLE = "crc_enable" CONF_DRAW_ROUNDING = "draw_rounding" CONF_ENABLED = "enabled" CONF_IGNORE_NOT_FOUND = "ignore_not_found" +CONF_ON_PACKET = "on_packet" CONF_ON_RECEIVE = "on_receive" CONF_ON_STATE_CHANGE = "on_state_change" CONF_REQUEST_HEADERS = "request_headers" diff --git a/esphome/components/sx126x/__init__.py b/esphome/components/sx126x/__init__.py index 1eb83b7a33..4641db6483 100644 --- a/esphome/components/sx126x/__init__.py +++ b/esphome/components/sx126x/__init__.py @@ -1,6 +1,7 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import spi +from esphome.components.const import CONF_CRC_ENABLE, CONF_ON_PACKET import esphome.config_validation as cv from esphome.const import CONF_BUSY_PIN, CONF_DATA, CONF_FREQUENCY, CONF_ID from esphome.core import ID, TimePeriod @@ -14,7 +15,6 @@ CONF_SX126X_ID = "sx126x_id" CONF_BANDWIDTH = "bandwidth" CONF_BITRATE = "bitrate" CONF_CODING_RATE = "coding_rate" -CONF_CRC_ENABLE = "crc_enable" CONF_CRC_INVERTED = "crc_inverted" CONF_CRC_SIZE = "crc_size" CONF_CRC_POLYNOMIAL = "crc_polynomial" @@ -23,7 +23,6 @@ CONF_DEVIATION = "deviation" CONF_DIO1_PIN = "dio1_pin" CONF_HW_VERSION = "hw_version" CONF_MODULATION = "modulation" -CONF_ON_PACKET = "on_packet" CONF_PA_POWER = "pa_power" CONF_PA_RAMP = "pa_ramp" CONF_PAYLOAD_LENGTH = "payload_length" diff --git a/esphome/components/sx127x/__init__.py b/esphome/components/sx127x/__init__.py index 77cb61f7f8..b569a75972 100644 --- a/esphome/components/sx127x/__init__.py +++ b/esphome/components/sx127x/__init__.py @@ -1,6 +1,7 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import spi +from esphome.components.const import CONF_CRC_ENABLE, CONF_ON_PACKET import esphome.config_validation as cv from esphome.const import CONF_DATA, CONF_FREQUENCY, CONF_ID from esphome.core import ID @@ -16,11 +17,9 @@ CONF_BANDWIDTH = "bandwidth" CONF_BITRATE = "bitrate" CONF_BITSYNC = "bitsync" CONF_CODING_RATE = "coding_rate" -CONF_CRC_ENABLE = "crc_enable" CONF_DEVIATION = "deviation" CONF_DIO0_PIN = "dio0_pin" CONF_MODULATION = "modulation" -CONF_ON_PACKET = "on_packet" CONF_PA_PIN = "pa_pin" CONF_PA_POWER = "pa_power" CONF_PA_RAMP = "pa_ramp" diff --git a/tests/components/cc1101/common.yaml b/tests/components/cc1101/common.yaml index 9373ca43e1..93f03e582e 100644 --- a/tests/components/cc1101/common.yaml +++ b/tests/components/cc1101/common.yaml @@ -1,13 +1,26 @@ cc1101: id: transceiver cs_pin: ${cs_pin} + gdo0_pin: ${gdo0_pin} frequency: 433.92MHz if_frequency: 153kHz filter_bandwidth: 203kHz channel: 0 channel_spacing: 200kHz - symbol_rate: 5000 - modulation_type: ASK/OOK + symbol_rate: 4800 + modulation_type: GFSK + packet_mode: true + packet_length: 8 + crc_enable: true + whitening: false + sync_mode: "16/16" + sync0: 0x91 + sync1: 0xD3 + num_preamble: 2 + on_packet: + then: + - lambda: |- + ESP_LOGD("cc1101", "packet %s rssi %.1f dBm lqi %u", format_hex(x).c_str(), rssi, lqi); button: - platform: template @@ -18,3 +31,7 @@ button: - cc1101.begin_rx: transceiver - cc1101.set_idle: transceiver - cc1101.reset: transceiver + - cc1101.send_packet: + data: [0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef] + - cc1101.send_packet: !lambda |- + return {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; diff --git a/tests/components/cc1101/test.esp32-idf.yaml b/tests/components/cc1101/test.esp32-idf.yaml index e075629679..966f11bb64 100644 --- a/tests/components/cc1101/test.esp32-idf.yaml +++ b/tests/components/cc1101/test.esp32-idf.yaml @@ -1,8 +1,8 @@ substitutions: cs_pin: GPIO5 + gdo0_pin: GPIO4 packages: spi: !include ../../test_build_components/common/spi/esp32-idf.yaml - remote_receiver: !include ../../test_build_components/common/remote_receiver/esp32-idf.yaml <<: !include common.yaml diff --git a/tests/components/cc1101/test.esp8266.yaml b/tests/components/cc1101/test.esp8266.yaml index 7900658bc1..6f0f078507 100644 --- a/tests/components/cc1101/test.esp8266.yaml +++ b/tests/components/cc1101/test.esp8266.yaml @@ -1,8 +1,8 @@ substitutions: cs_pin: GPIO5 + gdo0_pin: GPIO4 packages: spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml - remote_receiver: !include ../../test_build_components/common/remote_receiver/esp8266-ard.yaml <<: !include common.yaml From 078afe965668356118b9bdc6eb864a156a78af40 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Dec 2025 13:30:55 -0600 Subject: [PATCH 0646/1145] [dashboard] Add ESPHOME_TRUSTED_DOMAINS support to events WebSocket (#12479) --- esphome/dashboard/web_server.py | 32 ++++++----- tests/dashboard/test_web_server.py | 87 ++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 14 deletions(-) diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index 804a2b99af..f94d8eea22 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -164,8 +164,24 @@ def websocket_method(name): return wrap +class CheckOriginMixin: + """Mixin to handle WebSocket origin checks for reverse proxy setups.""" + + def check_origin(self, origin: str) -> bool: + if "ESPHOME_TRUSTED_DOMAINS" not in os.environ: + return super().check_origin(origin) + trusted_domains = [ + s.strip() for s in os.environ["ESPHOME_TRUSTED_DOMAINS"].split(",") + ] + url = urlparse(origin) + if url.hostname in trusted_domains: + return True + _LOGGER.info("check_origin %s, domain is not trusted", origin) + return False + + @websocket_class -class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler): +class EsphomeCommandWebSocket(CheckOriginMixin, tornado.websocket.WebSocketHandler): """Base class for ESPHome websocket commands.""" def __init__( @@ -183,18 +199,6 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler): # use Popen() with a reading thread instead self._use_popen = os.name == "nt" - def check_origin(self, origin): - if "ESPHOME_TRUSTED_DOMAINS" not in os.environ: - return super().check_origin(origin) - trusted_domains = [ - s.strip() for s in os.environ["ESPHOME_TRUSTED_DOMAINS"].split(",") - ] - url = urlparse(origin) - if url.hostname in trusted_domains: - return True - _LOGGER.info("check_origin %s, domain is not trusted", origin) - return False - def open(self, *args: str, **kwargs: str) -> None: """Handle new WebSocket connection.""" # Ensure messages from the subprocess are sent immediately @@ -601,7 +605,7 @@ DASHBOARD_SUBSCRIBER = DashboardSubscriber() @websocket_class -class DashboardEventsWebSocket(tornado.websocket.WebSocketHandler): +class DashboardEventsWebSocket(CheckOriginMixin, tornado.websocket.WebSocketHandler): """WebSocket handler for real-time dashboard events.""" _event_listeners: list[Callable[[], None]] | None = None diff --git a/tests/dashboard/test_web_server.py b/tests/dashboard/test_web_server.py index 385841b1c8..10ca6061e6 100644 --- a/tests/dashboard/test_web_server.py +++ b/tests/dashboard/test_web_server.py @@ -1567,3 +1567,90 @@ async def test_dashboard_yaml_loading_with_packages_and_secrets( # If we get here, secret resolution worked! assert "esphome" in config assert config["esphome"]["name"] == "test-download-secrets" + + +@pytest.mark.asyncio +async def test_websocket_check_origin_default_same_origin( + dashboard: DashboardTestHelper, +) -> None: + """Test WebSocket uses default same-origin check when ESPHOME_TRUSTED_DOMAINS not set.""" + # Ensure ESPHOME_TRUSTED_DOMAINS is not set + env = os.environ.copy() + env.pop("ESPHOME_TRUSTED_DOMAINS", None) + with patch.dict(os.environ, env, clear=True): + from tornado.httpclient import HTTPRequest + + url = f"ws://127.0.0.1:{dashboard.port}/events" + # Same origin should work (default Tornado behavior) + request = HTTPRequest( + url, headers={"Origin": f"http://127.0.0.1:{dashboard.port}"} + ) + ws = await websocket_connect(request) + try: + msg = await ws.read_message() + assert msg is not None + data = json.loads(msg) + assert data["event"] == "initial_state" + finally: + ws.close() + + +@pytest.mark.asyncio +async def test_websocket_check_origin_trusted_domain( + dashboard: DashboardTestHelper, +) -> None: + """Test WebSocket accepts connections from trusted domains.""" + with patch.dict(os.environ, {"ESPHOME_TRUSTED_DOMAINS": "trusted.example.com"}): + from tornado.httpclient import HTTPRequest + + url = f"ws://127.0.0.1:{dashboard.port}/events" + request = HTTPRequest(url, headers={"Origin": "https://trusted.example.com"}) + ws = await websocket_connect(request) + try: + # Should receive initial state + msg = await ws.read_message() + assert msg is not None + data = json.loads(msg) + assert data["event"] == "initial_state" + finally: + ws.close() + + +@pytest.mark.asyncio +async def test_websocket_check_origin_untrusted_domain( + dashboard: DashboardTestHelper, +) -> None: + """Test WebSocket rejects connections from untrusted domains.""" + with patch.dict(os.environ, {"ESPHOME_TRUSTED_DOMAINS": "trusted.example.com"}): + from tornado.httpclient import HTTPRequest + + url = f"ws://127.0.0.1:{dashboard.port}/events" + request = HTTPRequest(url, headers={"Origin": "https://untrusted.example.com"}) + with pytest.raises(HTTPClientError) as exc_info: + await websocket_connect(request) + # Should get HTTP 403 Forbidden due to origin check failure + assert exc_info.value.code == 403 + + +@pytest.mark.asyncio +async def test_websocket_check_origin_multiple_trusted_domains( + dashboard: DashboardTestHelper, +) -> None: + """Test WebSocket accepts connections from multiple trusted domains.""" + with patch.dict( + os.environ, + {"ESPHOME_TRUSTED_DOMAINS": "first.example.com, second.example.com"}, + ): + from tornado.httpclient import HTTPRequest + + url = f"ws://127.0.0.1:{dashboard.port}/events" + # Test second domain in list (with space after comma) + request = HTTPRequest(url, headers={"Origin": "https://second.example.com"}) + ws = await websocket_connect(request) + try: + msg = await ws.read_message() + assert msg is not None + data = json.loads(msg) + assert data["event"] == "initial_state" + finally: + ws.close() From 2e9ddd967c05608d7b6c910e04f6afd9f83e1969 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Dec 2025 13:31:07 -0600 Subject: [PATCH 0647/1145] [wifi_signal] Skip publishing disconnected RSSI value (#12482) --- esphome/components/wifi_signal/wifi_signal_sensor.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi_signal/wifi_signal_sensor.h b/esphome/components/wifi_signal/wifi_signal_sensor.h index 5d7f4b4562..9f581f1eb2 100644 --- a/esphome/components/wifi_signal/wifi_signal_sensor.h +++ b/esphome/components/wifi_signal/wifi_signal_sensor.h @@ -16,7 +16,12 @@ class WiFiSignalSensor : public sensor::Sensor, public PollingComponent { #ifdef USE_WIFI_LISTENERS void setup() override { wifi::global_wifi_component->add_connect_state_listener(this); } #endif - void update() override { this->publish_state(wifi::global_wifi_component->wifi_rssi()); } + void update() override { + int8_t rssi = wifi::global_wifi_component->wifi_rssi(); + if (rssi != wifi::WIFI_RSSI_DISCONNECTED) { + this->publish_state(rssi); + } + } void dump_config() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } From c85b1b8609f1a7d4545b2ed40c74eb8dca7afacc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Dec 2025 13:31:19 -0600 Subject: [PATCH 0648/1145] [web_server_idf] Always enable LRU purge to prevent socket exhaustion (#12481) --- .../captive_portal/captive_portal.cpp | 6 ------ .../captive_portal/captive_portal.h | 4 ---- .../web_server_idf/web_server_idf.cpp | 19 +++++-------------- .../web_server_idf/web_server_idf.h | 2 -- 4 files changed, 5 insertions(+), 26 deletions(-) diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 4eb00835b1..e1f92d2d2b 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -65,12 +65,6 @@ void CaptivePortal::start() { this->base_->init(); if (!this->initialized_) { this->base_->add_handler(this); -#ifdef USE_ESP32 - // Enable LRU socket purging to handle captive portal detection probe bursts - // OS captive portal detection makes many simultaneous HTTP requests which can - // exhaust sockets. LRU purging automatically closes oldest idle connections. - this->base_->get_server()->set_lru_purge_enable(true); -#endif } network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index ae9b9dfba0..f48c286f0c 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -40,10 +40,6 @@ class CaptivePortal : public AsyncWebHandler, public Component { void end() { this->active_ = false; this->disable_loop(); // Stop processing DNS requests -#ifdef USE_ESP32 - // Disable LRU socket purging now that captive portal is done - this->base_->get_server()->set_lru_purge_enable(false); -#endif this->base_->deinit(); if (this->dns_server_ != nullptr) { this->dns_server_->stop(); diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index af99b85e53..8c3ad288c0 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -117,18 +117,6 @@ void AsyncWebServer::end() { } } -void AsyncWebServer::set_lru_purge_enable(bool enable) { - if (this->lru_purge_enable_ == enable) { - return; // No change needed - } - this->lru_purge_enable_ = enable; - // If server is already running, restart it with new config - if (this->server_) { - this->end(); - this->begin(); - } -} - void AsyncWebServer::begin() { if (this->server_) { this->end(); @@ -136,8 +124,11 @@ void AsyncWebServer::begin() { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.server_port = this->port_; config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; }; - // Enable LRU purging if requested (e.g., by captive portal to handle probe bursts) - config.lru_purge_enable = this->lru_purge_enable_; + // Always enable LRU purging to handle socket exhaustion gracefully. + // When max sockets is reached, the oldest connection is closed to make room for new ones. + // This prevents "httpd_accept_conn: error in accept (23)" errors. + // See: https://github.com/esphome/esphome/issues/12464 + config.lru_purge_enable = true; // Use custom close function that shuts down before closing to prevent lwIP race conditions config.close_fn = AsyncWebServer::safe_close_with_shutdown; if (httpd_start(&this->server_, &config) == ESP_OK) { diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index a139e9e4df..5f9f598388 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -199,13 +199,11 @@ class AsyncWebServer { return *handler; } - void set_lru_purge_enable(bool enable); httpd_handle_t get_server() { return this->server_; } protected: uint16_t port_{}; httpd_handle_t server_{}; - bool lru_purge_enable_{false}; static esp_err_t request_handler(httpd_req_t *r); static esp_err_t request_post_handler(httpd_req_t *r); esp_err_t request_handler_(AsyncWebServerRequest *request) const; From 3a1be6822eedc17616be8c31efe4f6daa4c67ef7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Dec 2025 13:47:11 -0600 Subject: [PATCH 0649/1145] [ota] Match client timeout to device timeout to prevent premature failures (#12484) --- esphome/espota2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/espota2.py b/esphome/espota2.py index c29506224c..6349ad0fa8 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -322,8 +322,8 @@ def perform_ota( hash_func, nonce_size, hash_name = _AUTH_METHODS[auth] perform_auth(sock, password, hash_func, nonce_size, hash_name) - # Set higher timeout during upload - sock.settimeout(30.0) + # Timeout must match device-side OTA_SOCKET_TIMEOUT_DATA to prevent premature failures + sock.settimeout(90.0) upload_size = len(upload_contents) upload_size_encoded = [ From 734710d22ac0243e54af296d8a4aae0b364382c3 Mon Sep 17 00:00:00 2001 From: guillempages Date: Sun, 14 Dec 2025 21:15:19 +0100 Subject: [PATCH 0650/1145] [core] Use Arduino string macros only on ESP8266 (#12471) --- esphome/core/progmem.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/core/progmem.h b/esphome/core/progmem.h index 67131fd113..f9508945e8 100644 --- a/esphome/core/progmem.h +++ b/esphome/core/progmem.h @@ -1,16 +1,16 @@ #pragma once // Platform-agnostic macros for PROGMEM string handling -// On ESP32 (both Arduino and IDF): Use plain strings (no PROGMEM) // On ESP8266/Arduino: Use Arduino's F() macro for PROGMEM strings +// On other platforms: Use plain strings (no PROGMEM) -#ifdef USE_ESP32 -#define ESPHOME_F(string_literal) (string_literal) -#define ESPHOME_PGM_P const char * -#define ESPHOME_strncpy_P strncpy -#else -// ESP8266 and other Arduino platforms use Arduino macros +#ifdef USE_ESP8266 +// ESP8266 uses Arduino macros #define ESPHOME_F(string_literal) F(string_literal) #define ESPHOME_PGM_P PGM_P #define ESPHOME_strncpy_P strncpy_P +#else +#define ESPHOME_F(string_literal) (string_literal) +#define ESPHOME_PGM_P const char * +#define ESPHOME_strncpy_P strncpy #endif From fffa16e4d81ea0adc06c0e78873850215cb6a070 Mon Sep 17 00:00:00 2001 From: mbohdal Date: Sun, 14 Dec 2025 22:40:08 +0100 Subject: [PATCH 0651/1145] [ethernet] fix used pins validation in configuration of RMII pins (#12486) --- esphome/components/ethernet/__init__.py | 5 ++++- .../test-lan8720-with-expander.esp32-idf.yaml | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/components/ethernet/test-lan8720-with-expander.esp32-idf.yaml diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index b4b1fcd9f6..e1ed327fb9 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -434,10 +434,13 @@ def _final_validate_rmii_pins(config: ConfigType) -> None: # Check all used pins against RMII reserved pins for pin_list in pins.PIN_SCHEMA_REGISTRY.pins_used.values(): - for pin_path, _, pin_config in pin_list: + for pin_path, pin_device, pin_config in pin_list: pin_num = pin_config.get(CONF_NUMBER) if pin_num not in rmii_pins: continue + # Skip if pin is not directly on ESP, but at some expander (device set to something else than 'None') + if pin_device is not None: + continue # Found a conflict - show helpful error message pin_function = rmii_pins[pin_num] component_path = ".".join(str(p) for p in pin_path) diff --git a/tests/components/ethernet/test-lan8720-with-expander.esp32-idf.yaml b/tests/components/ethernet/test-lan8720-with-expander.esp32-idf.yaml new file mode 100644 index 0000000000..09da8d90d9 --- /dev/null +++ b/tests/components/ethernet/test-lan8720-with-expander.esp32-idf.yaml @@ -0,0 +1,15 @@ +<<: !include common-lan8720.yaml + +sn74hc165: + - id: sn74hc165_hub + clock_pin: GPIO13 + data_pin: GPIO14 + load_pin: GPIO15 + sr_count: 3 + +binary_sensor: + - platform: gpio + pin: + sn74hc165: sn74hc165_hub + number: 19 + id: relay_2 From fa0f07bfe9212fb646b5cb52451d538ab29445e2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Dec 2025 16:26:34 -0600 Subject: [PATCH 0652/1145] [wifi] Fix WiFi recovery after failed connection attempts (#12483) --- esphome/components/wifi/wifi_component.cpp | 26 ++++++++++++++++--- .../wifi/wifi_component_esp_idf.cpp | 1 + .../wifi/wifi_component_libretiny.cpp | 10 +++++-- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index d46916bfd9..a5e8c4a59d 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -205,6 +205,21 @@ static constexpr uint32_t WIFI_COOLDOWN_DURATION_MS = 500; /// While connecting, WiFi can't beacon the AP properly, so needs longer cooldown static constexpr uint32_t WIFI_COOLDOWN_WITH_AP_ACTIVE_MS = 30000; +/// Timeout for WiFi scan operations +/// This is a fallback in case we don't receive a scan done callback from the WiFi driver. +/// Normal scans complete via callback; this only triggers if something goes wrong. +static constexpr uint32_t WIFI_SCAN_TIMEOUT_MS = 31000; + +/// Timeout for WiFi connection attempts +/// This is a fallback in case we don't receive connection success/failure callbacks. +/// Some platforms (especially LibreTiny/Beken) can take 30-60 seconds to connect, +/// particularly with fast_connect enabled where no prior scan provides channel info. +/// Do not lower this value - connection failures are detected via callbacks, not timeout. +/// If this timeout fires prematurely while a connection is still in progress, it causes +/// cascading failures: the subsequent scan will also fail because the WiFi driver is +/// still busy with the previous connection attempt. +static constexpr uint32_t WIFI_CONNECT_TIMEOUT_MS = 46000; + static constexpr uint8_t get_max_retries_for_phase(WiFiRetryPhase phase) { switch (phase) { case WiFiRetryPhase::INITIAL_CONNECT: @@ -1035,7 +1050,7 @@ __attribute__((noinline)) static void log_scan_result(const WiFiScanResult &res) void WiFiComponent::check_scanning_finished() { if (!this->scan_done_) { - if (millis() - this->action_started_ > 30000) { + if (millis() - this->action_started_ > WIFI_SCAN_TIMEOUT_MS) { ESP_LOGE(TAG, "Scan timeout"); this->retry_connect(); } @@ -1184,8 +1199,9 @@ void WiFiComponent::check_connecting_finished() { } uint32_t now = millis(); - if (now - this->action_started_ > 30000) { - ESP_LOGW(TAG, "Connection timeout"); + if (now - this->action_started_ > WIFI_CONNECT_TIMEOUT_MS) { + ESP_LOGW(TAG, "Connection timeout, aborting connection attempt"); + this->wifi_disconnect_(); this->retry_connect(); return; } @@ -1405,6 +1421,10 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) { // without disrupting the captive portal/improv connection if (!this->is_captive_portal_active_() && !this->is_esp32_improv_active_()) { this->restart_adapter(); + } else { + // Even when skipping full restart, disconnect to clear driver state + // Without this, platforms like LibreTiny may think we're still connecting + this->wifi_disconnect_(); } // Clear scan flag - we're starting a new retry cycle this->did_scan_this_cycle_ = false; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 1f4eb1e42c..4a3c40a119 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -720,6 +720,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_STOP) { ESP_LOGV(TAG, "STA stop"); s_sta_started = false; + s_sta_connecting = false; } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_AUTHMODE_CHANGE) { const auto &it = data->data.sta_authmode_change; diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 4fd64bdfa3..36003a6eb4 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -291,6 +291,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } case ESPHOME_EVENT_ID_WIFI_STA_STOP: { ESP_LOGV(TAG, "STA stop"); + s_sta_connecting = false; break; } case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { @@ -322,7 +323,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ // wifi_sta_connect_status_() to return IDLE. The main loop then sees // "Unknown connection status 0" (wifi_component.cpp check_connecting_finished) // and calls retry_connect(), aborting a connection that may succeed moments later. - // Real connection failures will have ssid/bssid populated, or we'll hit the 30s timeout. + // Real connection failures will have ssid/bssid populated, or we'll hit the connection timeout. if (it.ssid_len == 0 && s_sta_connecting) { ESP_LOGV(TAG, "Ignoring disconnect event with empty ssid while connecting (reason=%s)", get_disconnect_reason_str(it.reason)); @@ -527,7 +528,12 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.softAPIP()}; } #endif // USE_WIFI_AP -bool WiFiComponent::wifi_disconnect_() { return WiFi.disconnect(); } +bool WiFiComponent::wifi_disconnect_() { + // Clear connecting flag first so disconnect events aren't ignored + // and wifi_sta_connect_status_() returns IDLE instead of CONNECTING + s_sta_connecting = false; + return WiFi.disconnect(); +} bssid_t WiFiComponent::wifi_bssid() { bssid_t bssid{}; From 3a101d8886ac204381a84ddb6ce91cd34d785382 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 14 Dec 2025 18:17:00 -0500 Subject: [PATCH 0653/1145] Bump version to 2025.12.0b3 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 75c624bf2b..532e207788 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.0b2 +PROJECT_NUMBER = 2025.12.0b3 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 61bdc7df8d..916136a69c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.0b2" +__version__ = "2025.12.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 450962850a04341e3b821549538dab1a262dc85d Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:29:51 -0500 Subject: [PATCH 0654/1145] [remote_base] Fix crash when ABBWelcome action has no data field (#12493) Co-authored-by: Claude --- esphome/components/remote_base/abbwelcome_protocol.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/remote_base/abbwelcome_protocol.h b/esphome/components/remote_base/abbwelcome_protocol.h index 4b922eb2f1..b8d9293c11 100644 --- a/esphome/components/remote_base/abbwelcome_protocol.h +++ b/esphome/components/remote_base/abbwelcome_protocol.h @@ -232,10 +232,10 @@ template class ABBWelcomeAction : public RemoteTransmitterAction data.set_message_id(this->message_id_.value(x...)); data.auto_message_id = this->auto_message_id_.value(x...); std::vector data_vec; - if (this->len_ >= 0) { + if (this->len_ > 0) { // Static mode: copy from flash to vector data_vec.assign(this->data_.data, this->data_.data + this->len_); - } else { + } else if (this->len_ < 0) { // Template mode: call function data_vec = this->data_.func(x...); } @@ -245,7 +245,7 @@ template class ABBWelcomeAction : public RemoteTransmitterAction } protected: - ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length + ssize_t len_{0}; // <0 = template mode, >=0 = static mode with length union Data { std::vector (*func)(Ts...); // Function pointer (stateless lambdas) const uint8_t *data; // Pointer to static data in flash From 61cbd07e1d867fc035b34cef26fefedd21b9bf84 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Mon, 15 Dec 2025 16:55:03 +0000 Subject: [PATCH 0655/1145] Add hmac-sha256 support (#12437) Co-authored-by: J. Nick Koston --- CODEOWNERS | 1 + esphome/components/hmac_sha256/__init__.py | 6 ++ .../components/hmac_sha256/hmac_sha256.cpp | 102 ++++++++++++++++++ esphome/components/hmac_sha256/hmac_sha256.h | 59 ++++++++++ tests/components/hmac_sha256/common.yaml | 34 ++++++ .../hmac_sha256/test.bk72xx-ard.yaml | 1 + .../hmac_sha256/test.esp32-ard.yaml | 1 + .../hmac_sha256/test.esp32-idf.yaml | 1 + .../hmac_sha256/test.esp8266-ard.yaml | 1 + tests/components/hmac_sha256/test.host.yaml | 1 + .../hmac_sha256/test.rp2040-ard.yaml | 1 + .../hmac_sha256/test.rtl87xx-ard.yaml | 1 + 12 files changed, 209 insertions(+) create mode 100644 esphome/components/hmac_sha256/__init__.py create mode 100644 esphome/components/hmac_sha256/hmac_sha256.cpp create mode 100644 esphome/components/hmac_sha256/hmac_sha256.h create mode 100644 tests/components/hmac_sha256/common.yaml create mode 100644 tests/components/hmac_sha256/test.bk72xx-ard.yaml create mode 100644 tests/components/hmac_sha256/test.esp32-ard.yaml create mode 100644 tests/components/hmac_sha256/test.esp32-idf.yaml create mode 100644 tests/components/hmac_sha256/test.esp8266-ard.yaml create mode 100644 tests/components/hmac_sha256/test.host.yaml create mode 100644 tests/components/hmac_sha256/test.rp2040-ard.yaml create mode 100644 tests/components/hmac_sha256/test.rtl87xx-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index af926d2d61..fc27253d23 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -215,6 +215,7 @@ esphome/components/hlk_fm22x/* @OnFreund esphome/components/hlw8032/* @rici4kubicek esphome/components/hm3301/* @freekode esphome/components/hmac_md5/* @dwmw2 +esphome/components/hmac_sha256/* @dwmw2 esphome/components/homeassistant/* @esphome/core @OttoWinter esphome/components/homeassistant/number/* @landonr esphome/components/homeassistant/switch/* @Links2004 diff --git a/esphome/components/hmac_sha256/__init__.py b/esphome/components/hmac_sha256/__init__.py new file mode 100644 index 0000000000..158d740dc5 --- /dev/null +++ b/esphome/components/hmac_sha256/__init__.py @@ -0,0 +1,6 @@ +import esphome.config_validation as cv + +AUTO_LOAD = ["sha256"] +CODEOWNERS = ["@dwmw2"] + +CONFIG_SCHEMA = cv.Schema({}) diff --git a/esphome/components/hmac_sha256/hmac_sha256.cpp b/esphome/components/hmac_sha256/hmac_sha256.cpp new file mode 100644 index 0000000000..cf5daf63af --- /dev/null +++ b/esphome/components/hmac_sha256/hmac_sha256.cpp @@ -0,0 +1,102 @@ +#include +#include +#include "hmac_sha256.h" +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST) +#include "esphome/core/helpers.h" + +namespace esphome::hmac_sha256 { + +constexpr size_t SHA256_DIGEST_SIZE = 32; + +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + +HmacSHA256::~HmacSHA256() { mbedtls_md_free(&this->ctx_); } + +void HmacSHA256::init(const uint8_t *key, size_t len) { + mbedtls_md_init(&this->ctx_); + const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + mbedtls_md_setup(&this->ctx_, md_info, 1); // 1 = HMAC mode + mbedtls_md_hmac_starts(&this->ctx_, key, len); +} + +void HmacSHA256::add(const uint8_t *data, size_t len) { mbedtls_md_hmac_update(&this->ctx_, data, len); } + +void HmacSHA256::calculate() { mbedtls_md_hmac_finish(&this->ctx_, this->digest_); } + +void HmacSHA256::get_bytes(uint8_t *output) { memcpy(output, this->digest_, SHA256_DIGEST_SIZE); } + +void HmacSHA256::get_hex(char *output) { + for (size_t i = 0; i < SHA256_DIGEST_SIZE; i++) { + sprintf(output + (i * 2), "%02x", this->digest_[i]); + } +} + +bool HmacSHA256::equals_bytes(const uint8_t *expected) { + return memcmp(this->digest_, expected, SHA256_DIGEST_SIZE) == 0; +} + +bool HmacSHA256::equals_hex(const char *expected) { + char hex_output[SHA256_DIGEST_SIZE * 2 + 1]; + this->get_hex(hex_output); + hex_output[SHA256_DIGEST_SIZE * 2] = '\0'; + return strncmp(hex_output, expected, SHA256_DIGEST_SIZE * 2) == 0; +} + +#else + +HmacSHA256::~HmacSHA256() = default; + +// HMAC block size for SHA256 (RFC 2104) +constexpr size_t HMAC_BLOCK_SIZE = 64; + +void HmacSHA256::init(const uint8_t *key, size_t len) { + uint8_t ipad[HMAC_BLOCK_SIZE], opad[HMAC_BLOCK_SIZE]; + + memset(ipad, 0, sizeof(ipad)); + if (len > HMAC_BLOCK_SIZE) { + sha256::SHA256 keysha256; + keysha256.init(); + keysha256.add(key, len); + keysha256.calculate(); + keysha256.get_bytes(ipad); + } else { + memcpy(ipad, key, len); + } + memcpy(opad, ipad, sizeof(opad)); + + for (size_t i = 0; i < HMAC_BLOCK_SIZE; i++) { + ipad[i] ^= 0x36; + opad[i] ^= 0x5c; + } + + this->ihash_.init(); + this->ihash_.add(ipad, sizeof(ipad)); + + this->ohash_.init(); + this->ohash_.add(opad, sizeof(opad)); +} + +void HmacSHA256::add(const uint8_t *data, size_t len) { this->ihash_.add(data, len); } + +void HmacSHA256::calculate() { + uint8_t ibytes[32]; + + this->ihash_.calculate(); + this->ihash_.get_bytes(ibytes); + + this->ohash_.add(ibytes, sizeof(ibytes)); + this->ohash_.calculate(); +} + +void HmacSHA256::get_bytes(uint8_t *output) { this->ohash_.get_bytes(output); } + +void HmacSHA256::get_hex(char *output) { this->ohash_.get_hex(output); } + +bool HmacSHA256::equals_bytes(const uint8_t *expected) { return this->ohash_.equals_bytes(expected); } + +bool HmacSHA256::equals_hex(const char *expected) { return this->ohash_.equals_hex(expected); } + +#endif // USE_ESP32 || USE_LIBRETINY + +} // namespace esphome::hmac_sha256 +#endif diff --git a/esphome/components/hmac_sha256/hmac_sha256.h b/esphome/components/hmac_sha256/hmac_sha256.h new file mode 100644 index 0000000000..fa6b64aa94 --- /dev/null +++ b/esphome/components/hmac_sha256/hmac_sha256.h @@ -0,0 +1,59 @@ +#pragma once + +#include "esphome/core/defines.h" +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST) + +#include + +#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#include "mbedtls/md.h" +#else +#include "esphome/components/sha256/sha256.h" +#endif + +namespace esphome::hmac_sha256 { + +class HmacSHA256 { + public: + HmacSHA256() = default; + ~HmacSHA256(); + + /// Initialize a new HMAC-SHA256 digest computation. + void init(const uint8_t *key, size_t len); + void init(const char *key, size_t len) { this->init((const uint8_t *) key, len); } + void init(const std::string &key) { this->init(key.c_str(), key.length()); } + + /// Add bytes of data for the digest. + void add(const uint8_t *data, size_t len); + void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); } + + /// Compute the digest, based on the provided data. + void calculate(); + + /// Retrieve the HMAC-SHA256 digest as bytes. + /// The output must be able to hold 32 bytes or more. + void get_bytes(uint8_t *output); + + /// Retrieve the HMAC-SHA256 digest as hex characters. + /// The output must be able to hold 64 bytes or more. + void get_hex(char *output); + + /// Compare the digest against a provided byte-encoded digest (32 bytes). + bool equals_bytes(const uint8_t *expected); + + /// Compare the digest against a provided hex-encoded digest (64 bytes). + bool equals_hex(const char *expected); + + protected: +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + static constexpr size_t SHA256_DIGEST_SIZE = 32; + mbedtls_md_context_t ctx_{}; + uint8_t digest_[SHA256_DIGEST_SIZE]{}; +#else + sha256::SHA256 ihash_; + sha256::SHA256 ohash_; +#endif +}; + +} // namespace esphome::hmac_sha256 +#endif diff --git a/tests/components/hmac_sha256/common.yaml b/tests/components/hmac_sha256/common.yaml new file mode 100644 index 0000000000..9bbed295fd --- /dev/null +++ b/tests/components/hmac_sha256/common.yaml @@ -0,0 +1,34 @@ +esphome: + on_boot: + - lambda: |- + // Test HMAC-SHA256 functionality + #ifdef USE_SHA256 + using esphome::hmac_sha256::HmacSHA256; + HmacSHA256 hmac; + + // Test with key "key" and message "The quick brown fox jumps over the lazy dog" + const char* key = "key"; + const char* message = "The quick brown fox jumps over the lazy dog"; + + hmac.init(key, strlen(key)); + hmac.add(message, strlen(message)); + hmac.calculate(); + + char hex_output[65]; + hmac.get_hex(hex_output); + hex_output[64] = '\0'; + + ESP_LOGD("HMAC_SHA256", "HMAC-SHA256('%s', '%s') = %s", key, message, hex_output); + + // Expected: f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8 + const char* expected = "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"; + if (strcmp(hex_output, expected) == 0) { + ESP_LOGI("HMAC_SHA256", "Test PASSED"); + } else { + ESP_LOGE("HMAC_SHA256", "Test FAILED. Expected %s", expected); + } + #else + ESP_LOGW("HMAC_SHA256", "HMAC-SHA256 not available on this platform"); + #endif + +hmac_sha256: diff --git a/tests/components/hmac_sha256/test.bk72xx-ard.yaml b/tests/components/hmac_sha256/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.bk72xx-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_sha256/test.esp32-ard.yaml b/tests/components/hmac_sha256/test.esp32-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.esp32-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_sha256/test.esp32-idf.yaml b/tests/components/hmac_sha256/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_sha256/test.esp8266-ard.yaml b/tests/components/hmac_sha256/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.esp8266-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_sha256/test.host.yaml b/tests/components/hmac_sha256/test.host.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.host.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_sha256/test.rp2040-ard.yaml b/tests/components/hmac_sha256/test.rp2040-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.rp2040-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_sha256/test.rtl87xx-ard.yaml b/tests/components/hmac_sha256/test.rtl87xx-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.rtl87xx-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml From 2e899dd010adbcb3dc880e5d009a6a1568f566cd Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:07:02 -0500 Subject: [PATCH 0656/1145] [esp32] Support all IDF component version operators in shorthand syntax (#12499) Co-authored-by: Claude --- esphome/components/esp32/__init__.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 3dc5e4bbaa..0142fd4841 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -4,6 +4,7 @@ import itertools import logging import os from pathlib import Path +import re from esphome import yaml_util import esphome.codegen as cg @@ -616,10 +617,13 @@ def require_vfs_dir() -> None: def _parse_idf_component(value: str) -> ConfigType: """Parse IDF component shorthand syntax like 'owner/component^version'""" - if "^" not in value: - raise cv.Invalid(f"Invalid IDF component shorthand '{value}'") - name, ref = value.split("^", 1) - return {CONF_NAME: name, CONF_REF: ref} + # Match operator followed by version-like string (digit or *) + if match := re.search(r"(~=|>=|<=|==|!=|>|<|\^|~)(\d|\*)", value): + return {CONF_NAME: value[: match.start()], CONF_REF: value[match.start() :]} + raise cv.Invalid( + f"Invalid IDF component shorthand '{value}'. " + f"Expected format: 'owner/componentversion' where is one of: ^, ~, ~=, ==, !=, >=, >, <=, <" + ) def _validate_idf_component(config: ConfigType) -> ConfigType: From 260ffba2a59f3c9db2ebca87ffdb4342902ceb2c Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 15 Dec 2025 18:54:12 +0100 Subject: [PATCH 0657/1145] [http_request] Fix infinite loop when server doesn't send Content-Length header (#12480) Co-authored-by: Claude Sonnet 4.5 --- .../components/http_request/http_request.h | 3 +++ .../http_request/ota/ota_http_request.cpp | 20 ++++++++++++++----- .../update/http_request_update.cpp | 5 +++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 8a82a44d7d..8adf13b954 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -255,6 +255,9 @@ template class HttpRequestSendAction : public Action { size_t read_index = 0; while (container->get_bytes_read() < max_length) { int read = container->read(buf + read_index, std::min(max_length - read_index, 512)); + if (read <= 0) { + break; + } App.feed_wdt(); yield(); read_index += read; diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 4552fcc9df..b257518e06 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -132,11 +132,18 @@ uint8_t OtaHttpRequestComponent::do_ota_() { App.feed_wdt(); yield(); - if (bufsize < 0) { - ESP_LOGE(TAG, "Stream closed"); - this->cleanup_(std::move(backend), container); - return OTA_CONNECTION_ERROR; - } else if (bufsize > 0 && bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) { + // Exit loop if no data available (stream closed or end of data) + if (bufsize <= 0) { + if (bufsize < 0) { + ESP_LOGE(TAG, "Stream closed with error"); + this->cleanup_(std::move(backend), container); + return OTA_CONNECTION_ERROR; + } + // bufsize == 0: no more data available, exit loop + break; + } + + if (bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) { // add read bytes to MD5 md5_receive.add(buf, bufsize); @@ -247,6 +254,9 @@ bool OtaHttpRequestComponent::http_get_md5_() { int read_len = 0; while (container->get_bytes_read() < MD5_SIZE) { read_len = container->read((uint8_t *) this->md5_expected_.data(), MD5_SIZE); + if (read_len <= 0) { + break; + } App.feed_wdt(); yield(); } diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 26af754e69..22cad625d1 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -76,6 +76,11 @@ void HttpRequestUpdate::update_task(void *params) { yield(); + if (read_bytes <= 0) { + // Network error or connection closed - break to avoid infinite loop + break; + } + read_index += read_bytes; } From 1214bb6bad973805a1b4ec99cb56443f4ba10b5f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 20:07:20 +0000 Subject: [PATCH 0658/1145] Bump aioesphomeapi from 43.2.1 to 43.3.0 (#12507) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7a50e1296f..21b575a23f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.2.1 +aioesphomeapi==43.3.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.16 # dashboard_import From 24d7e9dd233c4acd6d11cbced55decea377ddaa0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 20:08:16 +0000 Subject: [PATCH 0659/1145] Bump tornado from 6.5.3 to 6.5.4 (#12508) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 21b575a23f..011a2d4f0b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ PyYAML==6.0.3 paho-mqtt==1.6.1 colorama==0.4.6 icmplib==3.0.4 -tornado==6.5.3 +tornado==6.5.4 tzlocal==5.3.1 # from time tzdata>=2021.1 # from time pyserial==3.5 From 839139df36fcca5cba859e455ea4de9802a5d898 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Mon, 15 Dec 2025 20:23:54 +0000 Subject: [PATCH 0660/1145] Add FNV-1a hash functions (#12502) --- esphome/core/helpers.cpp | 16 ++++- esphome/core/helpers.h | 15 +++++ tests/integration/fixtures/fnv1a_hash.yaml | 60 +++++++++++++++++++ tests/integration/test_fnv1a_hash.py | 69 ++++++++++++++++++++++ 4 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 tests/integration/fixtures/fnv1a_hash.yaml create mode 100644 tests/integration/test_fnv1a_hash.py diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index fb96869d21..55466fca8a 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -143,17 +143,29 @@ uint16_t crc16be(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t poly, return refout ? (crc ^ 0xffff) : crc; } +// FNV-1 hash - deprecated, use fnv1a_hash() for new code uint32_t fnv1_hash(const char *str) { - uint32_t hash = 2166136261UL; + uint32_t hash = FNV1_OFFSET_BASIS; if (str) { while (*str) { - hash *= 16777619UL; + hash *= FNV1_PRIME; hash ^= *str++; } } return hash; } +// FNV-1a hash - preferred for new code +uint32_t fnv1a_hash_extend(uint32_t hash, const char *str) { + if (str) { + while (*str) { + hash ^= *str++; + hash *= FNV1_PRIME; + } + } + return hash; +} + float random_float() { return static_cast(random_uint32()) / static_cast(UINT32_MAX); } // Strings diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 3e44e08dd4..cd9efef213 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -378,9 +378,24 @@ uint16_t crc16be(const uint8_t *data, uint16_t len, uint16_t crc = 0, uint16_t p bool refout = false); /// Calculate a FNV-1 hash of \p str. +/// Note: FNV-1a (fnv1a_hash) is preferred for new code due to better avalanche characteristics. uint32_t fnv1_hash(const char *str); inline uint32_t fnv1_hash(const std::string &str) { return fnv1_hash(str.c_str()); } +/// FNV-1 32-bit offset basis +constexpr uint32_t FNV1_OFFSET_BASIS = 2166136261UL; +/// FNV-1 32-bit prime +constexpr uint32_t FNV1_PRIME = 16777619UL; + +/// Extend a FNV-1a hash with additional string data. +uint32_t fnv1a_hash_extend(uint32_t hash, const char *str); +inline uint32_t fnv1a_hash_extend(uint32_t hash, const std::string &str) { + return fnv1a_hash_extend(hash, str.c_str()); +} +/// Calculate a FNV-1a hash of \p str. +inline uint32_t fnv1a_hash(const char *str) { return fnv1a_hash_extend(FNV1_OFFSET_BASIS, str); } +inline uint32_t fnv1a_hash(const std::string &str) { return fnv1a_hash(str.c_str()); } + /// Return a random 32-bit unsigned integer. uint32_t random_uint32(); /// Return a random float between 0 and 1. diff --git a/tests/integration/fixtures/fnv1a_hash.yaml b/tests/integration/fixtures/fnv1a_hash.yaml new file mode 100644 index 0000000000..d9c80601b8 --- /dev/null +++ b/tests/integration/fixtures/fnv1a_hash.yaml @@ -0,0 +1,60 @@ +esphome: + name: fnv1a-hash-test + platformio_options: + build_flags: + - "-DDEBUG" + on_boot: + - lambda: |- + using esphome::fnv1a_hash; + using esphome::fnv1a_hash_extend; + + // Test empty string (should return offset basis) + uint32_t hash_empty = fnv1a_hash(""); + if (hash_empty == 0x811c9dc5) { + ESP_LOGI("FNV1A", "empty PASSED"); + } else { + ESP_LOGE("FNV1A", "empty FAILED: 0x%08x != 0x811c9dc5", hash_empty); + } + + // Test known FNV-1a hashes + uint32_t hash_hello = fnv1a_hash("hello"); + if (hash_hello == 0x4f9f2cab) { + ESP_LOGI("FNV1A", "known_hello PASSED"); + } else { + ESP_LOGE("FNV1A", "known_hello FAILED: 0x%08x != 0x4f9f2cab", hash_hello); + } + + uint32_t hash_helloworld = fnv1a_hash("helloworld"); + if (hash_helloworld == 0x3b9f5c61) { + ESP_LOGI("FNV1A", "known_helloworld PASSED"); + } else { + ESP_LOGE("FNV1A", "known_helloworld FAILED: 0x%08x != 0x3b9f5c61", hash_helloworld); + } + + // Test fnv1a_hash_extend consistency + uint32_t hash1 = fnv1a_hash("hello"); + hash1 = fnv1a_hash_extend(hash1, "world"); + uint32_t hash2 = fnv1a_hash("helloworld"); + + if (hash1 == hash2) { + ESP_LOGI("FNV1A", "extend PASSED"); + } else { + ESP_LOGE("FNV1A", "extend FAILED: 0x%08x != 0x%08x", hash1, hash2); + } + + // Test with std::string + std::string str1 = "foo"; + std::string str2 = "bar"; + uint32_t hash3 = fnv1a_hash(str1); + hash3 = fnv1a_hash_extend(hash3, str2); + uint32_t hash4 = fnv1a_hash("foobar"); + + if (hash3 == hash4) { + ESP_LOGI("FNV1A", "string PASSED"); + } else { + ESP_LOGE("FNV1A", "string FAILED: 0x%08x != 0x%08x", hash3, hash4); + } + +host: +api: +logger: diff --git a/tests/integration/test_fnv1a_hash.py b/tests/integration/test_fnv1a_hash.py new file mode 100644 index 0000000000..366ea42cda --- /dev/null +++ b/tests/integration/test_fnv1a_hash.py @@ -0,0 +1,69 @@ +"""Integration test for FNV-1a hash functions.""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_fnv1a_hash( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that FNV-1a hash functions work correctly.""" + + test_results = {} + all_tests_complete = asyncio.Event() + expected_tests = {"empty", "known_hello", "known_helloworld", "extend", "string"} + + def on_log_line(line: str) -> None: + """Capture log lines with test results.""" + # Strip ANSI escape codes + clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line) + # Look for our test result messages + # Format: "[timestamp][level][FNV1A:line]: test_name PASSED" + match = re.search(r"\[FNV1A:\d+\]:\s+(\w+)\s+(PASSED|FAILED)", clean_line) + if match: + test_name = match.group(1) + result = match.group(2) + test_results[test_name] = result + if set(test_results.keys()) >= expected_tests: + all_tests_complete.set() + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + api_client_connected() as client, + ): + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "fnv1a-hash-test" + + # Wait for all tests to complete or timeout + try: + await asyncio.wait_for(all_tests_complete.wait(), timeout=2.0) + except TimeoutError: + pytest.fail(f"Tests timed out. Got results for: {set(test_results.keys())}") + + # Verify all tests passed + assert "empty" in test_results, "empty string test not found" + assert test_results["empty"] == "PASSED", "empty string test failed" + + assert "known_hello" in test_results, "known_hello test not found" + assert test_results["known_hello"] == "PASSED", "known_hello test failed" + + assert "known_helloworld" in test_results, "known_helloworld test not found" + assert test_results["known_helloworld"] == "PASSED", ( + "known_helloworld test failed" + ) + + assert "extend" in test_results, "fnv1a_hash_extend test not found" + assert test_results["extend"] == "PASSED", "fnv1a_hash_extend test failed" + + assert "string" in test_results, "std::string test not found" + assert test_results["string"] == "PASSED", "std::string test failed" From 803bb742c9358245e97aed967b962f8aed34ffdc Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:29:51 -0500 Subject: [PATCH 0661/1145] [remote_base] Fix crash when ABBWelcome action has no data field (#12493) Co-authored-by: Claude --- esphome/components/remote_base/abbwelcome_protocol.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/remote_base/abbwelcome_protocol.h b/esphome/components/remote_base/abbwelcome_protocol.h index 4b922eb2f1..b8d9293c11 100644 --- a/esphome/components/remote_base/abbwelcome_protocol.h +++ b/esphome/components/remote_base/abbwelcome_protocol.h @@ -232,10 +232,10 @@ template class ABBWelcomeAction : public RemoteTransmitterAction data.set_message_id(this->message_id_.value(x...)); data.auto_message_id = this->auto_message_id_.value(x...); std::vector data_vec; - if (this->len_ >= 0) { + if (this->len_ > 0) { // Static mode: copy from flash to vector data_vec.assign(this->data_.data, this->data_.data + this->len_); - } else { + } else if (this->len_ < 0) { // Template mode: call function data_vec = this->data_.func(x...); } @@ -245,7 +245,7 @@ template class ABBWelcomeAction : public RemoteTransmitterAction } protected: - ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length + ssize_t len_{0}; // <0 = template mode, >=0 = static mode with length union Data { std::vector (*func)(Ts...); // Function pointer (stateless lambdas) const uint8_t *data; // Pointer to static data in flash From 8dff7ee746fdb76b5c09176e96e5749310a19e61 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:07:02 -0500 Subject: [PATCH 0662/1145] [esp32] Support all IDF component version operators in shorthand syntax (#12499) Co-authored-by: Claude --- esphome/components/esp32/__init__.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 3dc5e4bbaa..0142fd4841 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -4,6 +4,7 @@ import itertools import logging import os from pathlib import Path +import re from esphome import yaml_util import esphome.codegen as cg @@ -616,10 +617,13 @@ def require_vfs_dir() -> None: def _parse_idf_component(value: str) -> ConfigType: """Parse IDF component shorthand syntax like 'owner/component^version'""" - if "^" not in value: - raise cv.Invalid(f"Invalid IDF component shorthand '{value}'") - name, ref = value.split("^", 1) - return {CONF_NAME: name, CONF_REF: ref} + # Match operator followed by version-like string (digit or *) + if match := re.search(r"(~=|>=|<=|==|!=|>|<|\^|~)(\d|\*)", value): + return {CONF_NAME: value[: match.start()], CONF_REF: value[match.start() :]} + raise cv.Invalid( + f"Invalid IDF component shorthand '{value}'. " + f"Expected format: 'owner/componentversion' where is one of: ^, ~, ~=, ==, !=, >=, >, <=, <" + ) def _validate_idf_component(config: ConfigType) -> ConfigType: From 57634b612ac73e291522026ed6188f404f7d8b48 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 15 Dec 2025 18:54:12 +0100 Subject: [PATCH 0663/1145] [http_request] Fix infinite loop when server doesn't send Content-Length header (#12480) Co-authored-by: Claude Sonnet 4.5 --- .../components/http_request/http_request.h | 3 +++ .../http_request/ota/ota_http_request.cpp | 20 ++++++++++++++----- .../update/http_request_update.cpp | 5 +++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 8a82a44d7d..8adf13b954 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -255,6 +255,9 @@ template class HttpRequestSendAction : public Action { size_t read_index = 0; while (container->get_bytes_read() < max_length) { int read = container->read(buf + read_index, std::min(max_length - read_index, 512)); + if (read <= 0) { + break; + } App.feed_wdt(); yield(); read_index += read; diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 4552fcc9df..b257518e06 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -132,11 +132,18 @@ uint8_t OtaHttpRequestComponent::do_ota_() { App.feed_wdt(); yield(); - if (bufsize < 0) { - ESP_LOGE(TAG, "Stream closed"); - this->cleanup_(std::move(backend), container); - return OTA_CONNECTION_ERROR; - } else if (bufsize > 0 && bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) { + // Exit loop if no data available (stream closed or end of data) + if (bufsize <= 0) { + if (bufsize < 0) { + ESP_LOGE(TAG, "Stream closed with error"); + this->cleanup_(std::move(backend), container); + return OTA_CONNECTION_ERROR; + } + // bufsize == 0: no more data available, exit loop + break; + } + + if (bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) { // add read bytes to MD5 md5_receive.add(buf, bufsize); @@ -247,6 +254,9 @@ bool OtaHttpRequestComponent::http_get_md5_() { int read_len = 0; while (container->get_bytes_read() < MD5_SIZE) { read_len = container->read((uint8_t *) this->md5_expected_.data(), MD5_SIZE); + if (read_len <= 0) { + break; + } App.feed_wdt(); yield(); } diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 26af754e69..22cad625d1 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -76,6 +76,11 @@ void HttpRequestUpdate::update_task(void *params) { yield(); + if (read_bytes <= 0) { + // Network error or connection closed - break to avoid infinite loop + break; + } + read_index += read_bytes; } From 4c926cca60128b4335b4146e98667b01fd678b25 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:09:42 -0500 Subject: [PATCH 0664/1145] Bump version to 2025.12.0b4 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 532e207788..039bba2136 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.0b3 +PROJECT_NUMBER = 2025.12.0b4 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 916136a69c..0aae3e2b17 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.0b3" +__version__ = "2025.12.0b4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From ead60bc5c47e535b12787526f5a42cb91b972fac Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 16 Dec 2025 00:48:30 -0600 Subject: [PATCH 0665/1145] [socket] Fix getpeername() returning local address instead of remote in LWIP raw TCP (#12475) --- esphome/components/socket/lwip_raw_tcp_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 5538206058..328df24bdd 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -188,7 +188,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } - return this->ip2sockaddr_(&pcb_->local_ip, pcb_->local_port, name, addrlen); + return this->ip2sockaddr_(&pcb_->remote_ip, pcb_->remote_port, name, addrlen); } std::string getpeername() override { if (pcb_ == nullptr) { From 1897551b2874f2f4fc92407dc39abfb5bdf97866 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:17:17 -0500 Subject: [PATCH 0666/1145] [uart] Fix UART on default UART0 pins for ESP-IDF (#12519) Co-authored-by: Claude --- .../uart/uart_component_esp_idf.cpp | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index b438e4f7a6..b4f6eedf91 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -9,6 +9,7 @@ #include "esphome/core/gpio.h" #include "driver/gpio.h" #include "soc/gpio_num.h" +#include "soc/uart_pins.h" #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" @@ -139,6 +140,22 @@ void IDFUARTComponent::load_settings(bool dump_config) { return; } + int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; + int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1; + int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1; + + // Workaround for ESP-IDF issue: https://github.com/espressif/esp-idf/issues/17459 + // Commit 9ed617fb17 removed gpio_func_sel() calls from uart_set_pin(), which breaks + // UART on default UART0 pins that may have residual state from boot console. + // Reset these pins before configuring UART to ensure they're in a clean state. + if (tx == U0TXD_GPIO_NUM || tx == U0RXD_GPIO_NUM) { + gpio_reset_pin(static_cast(tx)); + } + if (rx == U0TXD_GPIO_NUM || rx == U0RXD_GPIO_NUM) { + gpio_reset_pin(static_cast(rx)); + } + + // Setup pins after reset to preserve open drain/pullup/pulldown flags auto setup_pin_if_needed = [](InternalGPIOPin *pin) { if (!pin) { return; @@ -154,10 +171,6 @@ void IDFUARTComponent::load_settings(bool dump_config) { setup_pin_if_needed(this->tx_pin_); } - int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; - int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1; - int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1; - uint32_t invert = 0; if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) { invert |= UART_SIGNAL_TXD_INV; From 7216120bfd12f590b6cb84891cc5a14dce908c11 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 16 Dec 2025 00:48:30 -0600 Subject: [PATCH 0667/1145] [socket] Fix getpeername() returning local address instead of remote in LWIP raw TCP (#12475) --- esphome/components/socket/lwip_raw_tcp_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 5538206058..328df24bdd 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -188,7 +188,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } - return this->ip2sockaddr_(&pcb_->local_ip, pcb_->local_port, name, addrlen); + return this->ip2sockaddr_(&pcb_->remote_ip, pcb_->remote_port, name, addrlen); } std::string getpeername() override { if (pcb_ == nullptr) { From 4d6a93f92de0aa16a0a61ac750d5f42076333a9d Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:17:17 -0500 Subject: [PATCH 0668/1145] [uart] Fix UART on default UART0 pins for ESP-IDF (#12519) Co-authored-by: Claude --- .../uart/uart_component_esp_idf.cpp | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index b438e4f7a6..b4f6eedf91 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -9,6 +9,7 @@ #include "esphome/core/gpio.h" #include "driver/gpio.h" #include "soc/gpio_num.h" +#include "soc/uart_pins.h" #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" @@ -139,6 +140,22 @@ void IDFUARTComponent::load_settings(bool dump_config) { return; } + int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; + int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1; + int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1; + + // Workaround for ESP-IDF issue: https://github.com/espressif/esp-idf/issues/17459 + // Commit 9ed617fb17 removed gpio_func_sel() calls from uart_set_pin(), which breaks + // UART on default UART0 pins that may have residual state from boot console. + // Reset these pins before configuring UART to ensure they're in a clean state. + if (tx == U0TXD_GPIO_NUM || tx == U0RXD_GPIO_NUM) { + gpio_reset_pin(static_cast(tx)); + } + if (rx == U0TXD_GPIO_NUM || rx == U0RXD_GPIO_NUM) { + gpio_reset_pin(static_cast(rx)); + } + + // Setup pins after reset to preserve open drain/pullup/pulldown flags auto setup_pin_if_needed = [](InternalGPIOPin *pin) { if (!pin) { return; @@ -154,10 +171,6 @@ void IDFUARTComponent::load_settings(bool dump_config) { setup_pin_if_needed(this->tx_pin_); } - int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; - int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1; - int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1; - uint32_t invert = 0; if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) { invert |= UART_SIGNAL_TXD_INV; From 9c88e44300748b63f35a7f9af1db707b974311a7 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:35:31 -0500 Subject: [PATCH 0669/1145] Bump version to 2025.12.0b5 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 039bba2136..ee19d5840d 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.0b4 +PROJECT_NUMBER = 2025.12.0b5 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 0aae3e2b17..9cdc210425 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.0b4" +__version__ = "2025.12.0b5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From fa3d998c3da9f95ec48de00553062ef6c14187b5 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:15:50 -0500 Subject: [PATCH 0670/1145] Bump version to 2025.12.0 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index ee19d5840d..7dfcbd6b6f 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.0b5 +PROJECT_NUMBER = 2025.12.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 9cdc210425..111396cab5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.0b5" +__version__ = "2025.12.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From fab4efb4690468004749296b2e7372fc6090a168 Mon Sep 17 00:00:00 2001 From: Jeff Zigler <123041141+zigboi@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:42:12 -0800 Subject: [PATCH 0671/1145] [esp32] Fix serial logging on h2, c2 & c61 (#12522) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/logger/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index fb0ce92cc9..8968a5eab8 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -241,9 +241,12 @@ CONFIG_SCHEMA = cv.All( CONF_HARDWARE_UART, esp8266=UART0, esp32=UART0, + esp32_c2=UART0, esp32_c3=USB_SERIAL_JTAG, esp32_c5=USB_SERIAL_JTAG, esp32_c6=USB_SERIAL_JTAG, + esp32_c61=USB_SERIAL_JTAG, + esp32_h2=USB_SERIAL_JTAG, esp32_p4=USB_SERIAL_JTAG, esp32_s2=USB_CDC, esp32_s3=USB_SERIAL_JTAG, From 046ea922e8af37c2b91cff9c1e6c54f69fce0812 Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Wed, 17 Dec 2025 01:42:52 +0100 Subject: [PATCH 0672/1145] [esp32] improve types and variable naming (#12423) --- esphome/components/esp32/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 0142fd4841..b726a40508 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -118,8 +118,8 @@ ARDUINO_ALLOWED_VARIANTS = [ ] -def get_cpu_frequencies(*frequencies): - return [str(x) + "MHZ" for x in frequencies] +def get_cpu_frequencies(*frequencies: int) -> list[str]: + return [f"{frequency}MHZ" for frequency in frequencies] CPU_FREQUENCIES = { @@ -136,7 +136,7 @@ CPU_FREQUENCIES = { } # Make sure not missed here if a new variant added. -assert all(v in CPU_FREQUENCIES for v in VARIANTS) +assert all(variant in CPU_FREQUENCIES for variant in VARIANTS) FULL_CPU_FREQUENCIES = set(itertools.chain.from_iterable(CPU_FREQUENCIES.values())) @@ -250,10 +250,10 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType): def add_idf_component( *, name: str, - repo: str = None, - ref: str = None, - path: str = None, - refresh: TimePeriod = None, + repo: str | None = None, + ref: str | None = None, + path: str | None = None, + refresh: TimePeriod | None = None, components: list[str] | None = None, submodules: list[str] | None = None, ): @@ -334,7 +334,7 @@ def _format_framework_espidf_version(ver: cv.Version, release: str) -> str: return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.{ext}" -def _is_framework_url(source: str) -> str: +def _is_framework_url(source: str) -> bool: # platformio accepts many URL schemes for framework repositories and archives including http, https, git, file, and symlink import urllib.parse @@ -1193,7 +1193,7 @@ APP_PARTITION_SIZES = { } -def get_arduino_partition_csv(flash_size): +def get_arduino_partition_csv(flash_size: str): app_partition_size = APP_PARTITION_SIZES[flash_size] eeprom_partition_size = 0x1000 # 4 KB spiffs_partition_size = 0xF000 # 60 KB @@ -1213,7 +1213,7 @@ spiffs, data, spiffs, 0x{spiffs_partition_start:X}, 0x{spiffs_partition_size: """ -def get_idf_partition_csv(flash_size): +def get_idf_partition_csv(flash_size: str): app_partition_size = APP_PARTITION_SIZES[flash_size] return f"""\ From 93621d85b08d4a84d29c396cbcca9009b472f1f3 Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Wed, 17 Dec 2025 01:43:10 +0100 Subject: [PATCH 0673/1145] [climate] Improve temperature unit regex (#12032) --- esphome/components/climate/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index b8e49db6c0..2150a30c3e 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -117,9 +117,7 @@ CONF_MIN_HUMIDITY = "min_humidity" CONF_MAX_HUMIDITY = "max_humidity" CONF_TARGET_HUMIDITY = "target_humidity" -visual_temperature = cv.float_with_unit( - "visual_temperature", "(°C|° C|°|C|°K|° K|K|°F|° F|F)?" -) +visual_temperature = cv.float_with_unit("visual_temperature", "(°|(° ?)?[CKF])?") VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Schema( From 9727c7135cdeeed19dbf09950ce4ccf24adc86b7 Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Wed, 17 Dec 2025 01:43:18 +0100 Subject: [PATCH 0674/1145] [openthread] channel range, fix typo, use C++17 nested namespace syntax (#12422) --- esphome/components/openthread/__init__.py | 4 ++-- esphome/components/openthread/openthread.cpp | 7 ++----- esphome/components/openthread/openthread.h | 6 ++---- esphome/components/openthread/openthread_esp.cpp | 6 ++---- .../openthread_info/openthread_info_text_sensor.cpp | 6 ++---- .../openthread_info/openthread_info_text_sensor.h | 6 ++---- 6 files changed, 12 insertions(+), 23 deletions(-) diff --git a/esphome/components/openthread/__init__.py b/esphome/components/openthread/__init__.py index 5b1abe4fb5..050e45cdc9 100644 --- a/esphome/components/openthread/__init__.py +++ b/esphome/components/openthread/__init__.py @@ -91,7 +91,7 @@ def set_sdkconfig_options(config): add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", True) add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5) - # TODO: Add suport for synchronized sleepy end devices (SSED) + # TODO: Add support for synchronized sleepy end devices (SSED) add_idf_sdkconfig_option(f"CONFIG_OPENTHREAD_{config.get(CONF_DEVICE_TYPE)}", True) @@ -102,7 +102,7 @@ OpenThreadSrpComponent = openthread_ns.class_("OpenThreadSrpComponent", cg.Compo _CONNECTION_SCHEMA = cv.Schema( { cv.Optional(CONF_PAN_ID): cv.hex_int, - cv.Optional(CONF_CHANNEL): cv.int_, + cv.Optional(CONF_CHANNEL): cv.int_range(min=11, max=26), cv.Optional(CONF_NETWORK_KEY): cv.hex_int, cv.Optional(CONF_EXT_PAN_ID): cv.hex_int, cv.Optional(CONF_NETWORK_NAME): cv.string_strict, diff --git a/esphome/components/openthread/openthread.cpp b/esphome/components/openthread/openthread.cpp index 721ab89326..90da17e2d3 100644 --- a/esphome/components/openthread/openthread.cpp +++ b/esphome/components/openthread/openthread.cpp @@ -21,8 +21,7 @@ static const char *const TAG = "openthread"; -namespace esphome { -namespace openthread { +namespace esphome::openthread { OpenThreadComponent *global_openthread_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -275,7 +274,5 @@ const char *OpenThreadComponent::get_use_address() const { return this->use_addr void OpenThreadComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; } -} // namespace openthread -} // namespace esphome - +} // namespace esphome::openthread #endif diff --git a/esphome/components/openthread/openthread.h b/esphome/components/openthread/openthread.h index 546128b366..3c60acaadd 100644 --- a/esphome/components/openthread/openthread.h +++ b/esphome/components/openthread/openthread.h @@ -13,8 +13,7 @@ #include #include -namespace esphome { -namespace openthread { +namespace esphome::openthread { class InstanceLock; @@ -91,6 +90,5 @@ class InstanceLock { InstanceLock() {} }; -} // namespace openthread -} // namespace esphome +} // namespace esphome::openthread #endif diff --git a/esphome/components/openthread/openthread_esp.cpp b/esphome/components/openthread/openthread_esp.cpp index 72dc521091..b47e4b884a 100644 --- a/esphome/components/openthread/openthread_esp.cpp +++ b/esphome/components/openthread/openthread_esp.cpp @@ -24,8 +24,7 @@ static const char *const TAG = "openthread"; -namespace esphome { -namespace openthread { +namespace esphome::openthread { void OpenThreadComponent::setup() { // Used eventfds: @@ -209,6 +208,5 @@ otInstance *InstanceLock::get_instance() { return esp_openthread_get_instance(); InstanceLock::~InstanceLock() { esp_openthread_lock_release(); } -} // namespace openthread -} // namespace esphome +} // namespace esphome::openthread #endif diff --git a/esphome/components/openthread_info/openthread_info_text_sensor.cpp b/esphome/components/openthread_info/openthread_info_text_sensor.cpp index 10724f3e2f..fc61ad81b2 100644 --- a/esphome/components/openthread_info/openthread_info_text_sensor.cpp +++ b/esphome/components/openthread_info/openthread_info_text_sensor.cpp @@ -3,8 +3,7 @@ #ifdef USE_OPENTHREAD #include "esphome/core/log.h" -namespace esphome { -namespace openthread_info { +namespace esphome::openthread_info { static const char *const TAG = "openthread_info"; @@ -19,6 +18,5 @@ void NetworkKeyOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "Network Key" void PanIdOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "PAN ID", this); } void ExtPanIdOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "Extended PAN ID", this); } -} // namespace openthread_info -} // namespace esphome +} // namespace esphome::openthread_info #endif diff --git a/esphome/components/openthread_info/openthread_info_text_sensor.h b/esphome/components/openthread_info/openthread_info_text_sensor.h index bbcd2d4655..35e46212cb 100644 --- a/esphome/components/openthread_info/openthread_info_text_sensor.h +++ b/esphome/components/openthread_info/openthread_info_text_sensor.h @@ -5,8 +5,7 @@ #include "esphome/core/component.h" #ifdef USE_OPENTHREAD -namespace esphome { -namespace openthread_info { +namespace esphome::openthread_info { using esphome::openthread::InstanceLock; @@ -213,6 +212,5 @@ class ExtPanIdOpenThreadInfo : public DatasetOpenThreadInfo, public text_sensor: std::array last_extpanid_{}; }; -} // namespace openthread_info -} // namespace esphome +} // namespace esphome::openthread_info #endif From 9cd888cef6cff7a9309ac01542d228ccc39ce1b3 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:44:01 -0500 Subject: [PATCH 0675/1145] [spi] Use ESP-IDF driver for ESP32 Arduino (#12420) Co-authored-by: Claude --- esphome/components/spi/__init__.py | 13 ++++++----- esphome/components/spi/spi.cpp | 6 ++--- esphome/components/spi/spi.h | 31 ++++++++++---------------- esphome/components/spi/spi_arduino.cpp | 10 ++++----- esphome/components/spi/spi_esp_idf.cpp | 10 ++++----- 5 files changed, 30 insertions(+), 40 deletions(-) diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 88bb3406e1..ad279dcf1a 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -272,10 +272,11 @@ def validate_spi_config(config): # Given an SPI index, convert to a string that represents the C++ object for it. def get_spi_interface(index): - if CORE.using_esp_idf: + platform = get_target_platform() + if platform == PLATFORM_ESP32: + # ESP32 uses ESP-IDF SPI driver for both Arduino and IDF frameworks return ["SPI2_HOST", "SPI3_HOST"][index] # Arduino code follows - platform = get_target_platform() if platform == PLATFORM_RP2040: return ["&SPI", "&SPI1"][index] if index == 0: @@ -356,7 +357,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(configs): cg.add_define("USE_SPI") cg.add_global(spi_ns.using) - if CORE.using_arduino: + if CORE.using_arduino and not CORE.is_esp32: cg.add_library("SPI", None) for spi in configs: var = cg.new_Pvariable(spi[CONF_ID]) @@ -447,13 +448,15 @@ def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: FILTER_SOURCE_FILES = filter_source_files_from_platform( { "spi_arduino.cpp": { - PlatformFramework.ESP32_ARDUINO, PlatformFramework.ESP8266_ARDUINO, PlatformFramework.RP2040_ARDUINO, PlatformFramework.BK72XX_ARDUINO, PlatformFramework.RTL87XX_ARDUINO, PlatformFramework.LN882X_ARDUINO, }, - "spi_esp_idf.cpp": {PlatformFramework.ESP32_IDF}, + "spi_esp_idf.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, } ) diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 00e9845a03..c4876d1a74 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -2,8 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -namespace esphome { -namespace spi { +namespace esphome::spi { const char *const TAG = "spi"; @@ -119,5 +118,4 @@ uint16_t SPIDelegateBitBash::transfer_(uint16_t data, size_t num_bits) { return out_data; } -} // namespace spi -} // namespace esphome +} // namespace esphome::spi diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 5bc80350da..43b55d72bc 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -1,4 +1,5 @@ #pragma once +#ifndef USE_ZEPHYR #include "esphome/core/application.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" @@ -7,7 +8,13 @@ #include #include -#ifdef USE_ARDUINO +#ifdef USE_ESP32 + +#include "driver/spi_master.h" + +using SPIInterface = spi_host_device_t; + +#elif defined(USE_ARDUINO) #include @@ -17,26 +24,12 @@ using SPIInterface = SPIClassRP2040 *; using SPIInterface = SPIClass *; #endif -#endif - -#ifdef USE_ESP_IDF - -#include "driver/spi_master.h" - -using SPIInterface = spi_host_device_t; - -#endif // USE_ESP_IDF - -#ifdef USE_ZEPHYR -// TODO supprse clang-tidy. Remove after SPI driver for nrf52 is added. -using SPIInterface = void *; -#endif +#endif // USE_ESP32 / USE_ARDUINO /** * Implementation of SPI Controller mode. */ -namespace esphome { -namespace spi { +namespace esphome::spi { /// The bit-order for SPI devices. This defines how the data read from and written to the device is interpreted. enum SPIBitOrder { @@ -509,5 +502,5 @@ class SPIDevice : public SPIClient { template void transfer_array(std::array &data) { this->transfer_array(data.data(), N); } }; -} // namespace spi -} // namespace esphome +} // namespace esphome::spi +#endif // USE_ZEPHYR diff --git a/esphome/components/spi/spi_arduino.cpp b/esphome/components/spi/spi_arduino.cpp index a34e3c3c82..4267fe63ce 100644 --- a/esphome/components/spi/spi_arduino.cpp +++ b/esphome/components/spi/spi_arduino.cpp @@ -1,9 +1,8 @@ #include "spi.h" #include -namespace esphome { -namespace spi { -#ifdef USE_ARDUINO +namespace esphome::spi { +#if defined(USE_ARDUINO) && !defined(USE_ESP32) static const char *const TAG = "spi-esp-arduino"; class SPIDelegateHw : public SPIDelegate { @@ -101,6 +100,5 @@ SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo return new SPIBusHw(clk, sdo, sdi, interface); } -#endif // USE_ARDUINO -} // namespace spi -} // namespace esphome +#endif // USE_ARDUINO && !USE_ESP32 +} // namespace esphome::spi diff --git a/esphome/components/spi/spi_esp_idf.cpp b/esphome/components/spi/spi_esp_idf.cpp index 549f516eb1..a1837fa58d 100644 --- a/esphome/components/spi/spi_esp_idf.cpp +++ b/esphome/components/spi/spi_esp_idf.cpp @@ -1,10 +1,9 @@ #include "spi.h" #include -namespace esphome { -namespace spi { +namespace esphome::spi { -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 static const char *const TAG = "spi-esp-idf"; static const size_t MAX_TRANSFER_SIZE = 4092; // dictated by ESP-IDF API. @@ -266,6 +265,5 @@ SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo return new SPIBusHw(clk, sdo, sdi, interface, data_pins); } -#endif -} // namespace spi -} // namespace esphome +#endif // USE_ESP32 +} // namespace esphome::spi From 18814f12dca7034e52935bbabc800427ffea501b Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:44:14 -0500 Subject: [PATCH 0676/1145] [http_request] Use ESP-IDF for ESP32 Arduino (#12428) Co-authored-by: Claude --- esphome/components/http_request/__init__.py | 52 ++++++++----------- .../components/http_request/http_request.cpp | 6 +-- .../components/http_request/http_request.h | 6 +-- .../http_request/http_request_arduino.cpp | 15 ++---- .../http_request/http_request_arduino.h | 12 ++--- .../http_request/http_request_host.cpp | 6 +-- .../http_request/http_request_host.h | 7 ++- .../http_request/http_request_idf.cpp | 10 ++-- .../http_request/http_request_idf.h | 10 ++-- 9 files changed, 49 insertions(+), 75 deletions(-) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index f4fa448c5b..b133aa69b2 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -69,9 +69,6 @@ def validate_url(value): def validate_ssl_verification(config): error_message = "" - if CORE.is_esp32 and not CORE.using_esp_idf and config[CONF_VERIFY_SSL]: - error_message = "ESPHome supports certificate verification only via ESP-IDF" - if CORE.is_rp2040 and config[CONF_VERIFY_SSL]: error_message = "ESPHome does not support certificate verification on RP2040" @@ -93,9 +90,9 @@ def validate_ssl_verification(config): def _declare_request_class(value): if CORE.is_host: return cv.declare_id(HttpRequestHost)(value) - if CORE.using_esp_idf: + if CORE.is_esp32: return cv.declare_id(HttpRequestIDF)(value) - if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040: + if CORE.is_esp8266 or CORE.is_rp2040: return cv.declare_id(HttpRequestArduino)(value) return NotImplementedError @@ -121,11 +118,11 @@ CONFIG_SCHEMA = cv.All( cv.positive_not_null_time_period, cv.positive_time_period_milliseconds, ), - cv.SplitDefault(CONF_BUFFER_SIZE_RX, esp32_idf=512): cv.All( - cv.uint16_t, cv.only_with_esp_idf + cv.SplitDefault(CONF_BUFFER_SIZE_RX, esp32=512): cv.All( + cv.uint16_t, cv.only_on_esp32 ), - cv.SplitDefault(CONF_BUFFER_SIZE_TX, esp32_idf=512): cv.All( - cv.uint16_t, cv.only_with_esp_idf + cv.SplitDefault(CONF_BUFFER_SIZE_TX, esp32=512): cv.All( + cv.uint16_t, cv.only_on_esp32 ), cv.Optional(CONF_CA_CERTIFICATE_PATH): cv.All( cv.file_, @@ -158,25 +155,20 @@ async def to_code(config): cg.add(var.set_watchdog_timeout(timeout_ms)) if CORE.is_esp32: - if CORE.using_esp_idf: - cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX])) - cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX])) + cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX])) + cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX])) - esp32.add_idf_sdkconfig_option( - "CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", - config.get(CONF_VERIFY_SSL), - ) - esp32.add_idf_sdkconfig_option( - "CONFIG_ESP_TLS_INSECURE", - not config.get(CONF_VERIFY_SSL), - ) - esp32.add_idf_sdkconfig_option( - "CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", - not config.get(CONF_VERIFY_SSL), - ) - else: - cg.add_library("NetworkClientSecure", None) - cg.add_library("HTTPClient", None) + if config.get(CONF_VERIFY_SSL): + esp32.add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True) + + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_TLS_INSECURE", + not config.get(CONF_VERIFY_SSL), + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", + not config.get(CONF_VERIFY_SSL), + ) if CORE.is_esp8266: cg.add_library("ESP8266HTTPClient", None) if CORE.is_rp2040 and CORE.using_arduino: @@ -327,13 +319,15 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( { "http_request_host.cpp": {PlatformFramework.HOST_NATIVE}, "http_request_arduino.cpp": { - PlatformFramework.ESP32_ARDUINO, PlatformFramework.ESP8266_ARDUINO, PlatformFramework.RP2040_ARDUINO, PlatformFramework.BK72XX_ARDUINO, PlatformFramework.RTL87XX_ARDUINO, PlatformFramework.LN882X_ARDUINO, }, - "http_request_idf.cpp": {PlatformFramework.ESP32_IDF}, + "http_request_idf.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, } ) diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 806354baf1..11dde4715a 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -4,8 +4,7 @@ #include -namespace esphome { -namespace http_request { +namespace esphome::http_request { static const char *const TAG = "http_request"; @@ -42,5 +41,4 @@ std::string HttpContainer::get_response_header(const std::string &header_name) { } } -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 8adf13b954..1b5fd9f00e 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -15,8 +15,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace http_request { +namespace esphome::http_request { struct Header { std::string name; @@ -305,5 +304,4 @@ template class HttpRequestSendAction : public Action { size_t max_response_buffer_size_{SIZE_MAX}; }; -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request diff --git a/esphome/components/http_request/http_request_arduino.cpp b/esphome/components/http_request/http_request_arduino.cpp index c64a7be554..a653942b18 100644 --- a/esphome/components/http_request/http_request_arduino.cpp +++ b/esphome/components/http_request/http_request_arduino.cpp @@ -1,6 +1,6 @@ #include "http_request_arduino.h" -#ifdef USE_ARDUINO +#if defined(USE_ARDUINO) && !defined(USE_ESP32) #include "esphome/components/network/util.h" #include "esphome/components/watchdog/watchdog.h" @@ -9,8 +9,7 @@ #include "esphome/core/defines.h" #include "esphome/core/log.h" -namespace esphome { -namespace http_request { +namespace esphome::http_request { static const char *const TAG = "http_request.arduino"; @@ -75,8 +74,6 @@ std::shared_ptr HttpRequestArduino::perform(const std::string &ur container->client_.setInsecure(); } bool status = container->client_.begin(url.c_str()); -#elif defined(USE_ESP32) - bool status = container->client_.begin(url.c_str()); #endif App.feed_wdt(); @@ -90,9 +87,6 @@ std::shared_ptr HttpRequestArduino::perform(const std::string &ur container->client_.setReuse(true); container->client_.setTimeout(this->timeout_); -#if defined(USE_ESP32) - container->client_.setConnectTimeout(this->timeout_); -#endif if (this->useragent_ != nullptr) { container->client_.setUserAgent(this->useragent_); @@ -177,7 +171,6 @@ void HttpContainerArduino::end() { this->client_.end(); } -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request -#endif // USE_ARDUINO +#endif // USE_ARDUINO && !USE_ESP32 diff --git a/esphome/components/http_request/http_request_arduino.h b/esphome/components/http_request/http_request_arduino.h index b736bb56d1..d9b5af9d81 100644 --- a/esphome/components/http_request/http_request_arduino.h +++ b/esphome/components/http_request/http_request_arduino.h @@ -2,9 +2,9 @@ #include "http_request.h" -#ifdef USE_ARDUINO +#if defined(USE_ARDUINO) && !defined(USE_ESP32) -#if defined(USE_ESP32) || defined(USE_RP2040) +#if defined(USE_RP2040) #include #include #endif @@ -15,8 +15,7 @@ #endif #endif -namespace esphome { -namespace http_request { +namespace esphome::http_request { class HttpRequestArduino; class HttpContainerArduino : public HttpContainer { @@ -36,7 +35,6 @@ class HttpRequestArduino : public HttpRequestComponent { const std::set &collect_headers) override; }; -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request -#endif // USE_ARDUINO +#endif // USE_ARDUINO && !USE_ESP32 diff --git a/esphome/components/http_request/http_request_host.cpp b/esphome/components/http_request/http_request_host.cpp index 402affc1d1..b94570be12 100644 --- a/esphome/components/http_request/http_request_host.cpp +++ b/esphome/components/http_request/http_request_host.cpp @@ -12,8 +12,7 @@ #include "esphome/core/application.h" #include "esphome/core/log.h" -namespace esphome { -namespace http_request { +namespace esphome::http_request { static const char *const TAG = "http_request.host"; @@ -139,7 +138,6 @@ void HttpContainerHost::end() { this->bytes_read_ = 0; } -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request #endif // USE_HOST diff --git a/esphome/components/http_request/http_request_host.h b/esphome/components/http_request/http_request_host.h index 886ba94938..32e149e6a3 100644 --- a/esphome/components/http_request/http_request_host.h +++ b/esphome/components/http_request/http_request_host.h @@ -2,8 +2,8 @@ #ifdef USE_HOST #include "http_request.h" -namespace esphome { -namespace http_request { + +namespace esphome::http_request { class HttpRequestHost; class HttpContainerHost : public HttpContainer { @@ -27,7 +27,6 @@ class HttpRequestHost : public HttpRequestComponent { const char *ca_path_{}; }; -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request #endif // USE_HOST diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index 34a3fb87eb..725a9c1c1e 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -1,6 +1,6 @@ #include "http_request_idf.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/components/network/util.h" #include "esphome/components/watchdog/watchdog.h" @@ -14,8 +14,7 @@ #include "esp_task_wdt.h" -namespace esphome { -namespace http_request { +namespace esphome::http_request { static const char *const TAG = "http_request.idf"; @@ -245,7 +244,6 @@ void HttpContainerIDF::feed_wdt() { } } -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/http_request/http_request_idf.h b/esphome/components/http_request/http_request_idf.h index e51b3aaebc..4dc4736423 100644 --- a/esphome/components/http_request/http_request_idf.h +++ b/esphome/components/http_request/http_request_idf.h @@ -2,15 +2,14 @@ #include "http_request.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include #include #include #include -namespace esphome { -namespace http_request { +namespace esphome::http_request { class HttpContainerIDF : public HttpContainer { public: @@ -48,7 +47,6 @@ class HttpRequestIDF : public HttpRequestComponent { static esp_err_t http_event_handler(esp_http_client_event_t *evt); }; -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request -#endif // USE_ESP_IDF +#endif // USE_ESP32 From 08beaf875008019f6d6ecb24e8f55e1b0c9143bb Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 16 Dec 2025 20:06:12 -0500 Subject: [PATCH 0677/1145] [esp32] Remove Arduino-specific code from core.cpp (#12501) Co-authored-by: Claude --- .clang-tidy.hash | 2 +- esphome/components/esp32/__init__.py | 6 ---- esphome/components/esp32/core.cpp | 50 +++++----------------------- esphome/core/defines.h | 2 +- sdkconfig.defaults | 1 - tests/script/test_clang_tidy_hash.py | 2 +- 6 files changed, 12 insertions(+), 51 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index a3322ba731..13c7ce5f97 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -766420905c06eeb6c5f360f68fd965e5ddd9c4a5db6b823263d3ad3accb64a07 +6857423aecf90accd0a8bf584d36ee094a4938f872447a4efc05a2efc6dc6481 diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index b726a40508..1379fd705f 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -986,14 +986,8 @@ async def to_code(config): f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})" ), ) - add_idf_sdkconfig_option( - "CONFIG_ARDUINO_LOOP_STACK_SIZE", - conf[CONF_ADVANCED][CONF_LOOP_TASK_STACK_SIZE], - ) - add_idf_sdkconfig_option("CONFIG_AUTOSTART_ARDUINO", True) add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True) add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True) - add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True) # ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency if get_esp32_variant() == VARIANT_ESP32S2: diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 6215ff862f..d8cc909c83 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -4,25 +4,20 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "preferences.h" -#include -#include +#include +#include #include #include #include #include -#include +#include +#include -#include +void setup(); // NOLINT(readability-redundant-declaration) +void loop(); // NOLINT(readability-redundant-declaration) -#ifdef USE_ARDUINO -#include -#else -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) -#include -#endif -void setup(); -void loop(); -#endif +// Weak stub for initArduino - overridden when the Arduino component is present +extern "C" __attribute__((weak)) void initArduino() {} namespace esphome { @@ -41,19 +36,7 @@ void arch_restart() { void arch_init() { // Enable the task watchdog only on the loop task (from which we're currently running) -#if defined(USE_ESP_IDF) esp_task_wdt_add(nullptr); - // Idle task watchdog is disabled on ESP-IDF -#elif defined(USE_ARDUINO) - enableLoopWDT(); - // Disable idle task watchdog on the core we're using (Arduino pins the task to a core) -#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0) && CONFIG_ARDUINO_RUNNING_CORE == 0 - disableCore0WDT(); -#endif -#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1) && CONFIG_ARDUINO_RUNNING_CORE == 1 - disableCore1WDT(); -#endif -#endif // If the bootloader was compiled with CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE the current // partition will get rolled back unless it is marked as valid. @@ -71,21 +54,10 @@ uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); } uint32_t arch_get_cpu_freq_hz() { uint32_t freq = 0; -#ifdef USE_ESP_IDF -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq); -#else - rtc_cpu_freq_config_t config; - rtc_clk_cpu_freq_get_config(&config); - freq = config.freq_mhz * 1000000U; -#endif -#elif defined(USE_ARDUINO) - freq = ESP.getCpuFreqMHz() * 1000000; -#endif return freq; } -#ifdef USE_ESP_IDF TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void loop_task(void *pv_params) { @@ -96,6 +68,7 @@ void loop_task(void *pv_params) { } extern "C" void app_main() { + initArduino(); esp32::setup_preferences(); #if CONFIG_FREERTOS_UNICORE xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle); @@ -103,11 +76,6 @@ extern "C" void app_main() { xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle, 1); #endif } -#endif // USE_ESP_IDF - -#ifdef USE_ARDUINO -extern "C" void init() { esp32::setup_preferences(); } -#endif // USE_ARDUINO } // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 750cab5bba..986ab9eff3 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -165,7 +165,6 @@ // IDF-specific feature flags #ifdef USE_ESP_IDF #define USE_MQTT_IDF_ENQUEUE -#define ESPHOME_LOOP_TASK_STACK_SIZE 8192 #endif // ESP32-specific feature flags @@ -197,6 +196,7 @@ #define ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT 1 #define ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT 1 #define ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT 2 +#define ESPHOME_LOOP_TASK_STACK_SIZE 8192 #define USE_ESP32_CAMERA_JPEG_ENCODER #define USE_HTTP_REQUEST_RESPONSE #define USE_I2C diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 322efb701a..72ca3f6e9c 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -13,7 +13,6 @@ CONFIG_ESP_TASK_WDT=y CONFIG_ESP_TASK_WDT_PANIC=y CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n -CONFIG_AUTOSTART_ARDUINO=y # esp32_ble CONFIG_BT_ENABLED=y diff --git a/tests/script/test_clang_tidy_hash.py b/tests/script/test_clang_tidy_hash.py index b1690a6a2d..e19e7886a2 100644 --- a/tests/script/test_clang_tidy_hash.py +++ b/tests/script/test_clang_tidy_hash.py @@ -49,7 +49,7 @@ def test_calculate_clang_tidy_hash_with_sdkconfig(tmp_path: Path) -> None: clang_tidy_content = b"Checks: '-*,readability-*'\n" requirements_version = "clang-tidy==18.1.5" platformio_content = b"[env:esp32]\nplatform = espressif32\n" - sdkconfig_content = b"CONFIG_AUTOSTART_ARDUINO=y\n" + sdkconfig_content = b"" requirements_content = "clang-tidy==18.1.5\n" # Create temporary files From 431183eebcb2450be6e9327f7a26f5344ea05926 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 16 Dec 2025 20:07:09 -0500 Subject: [PATCH 0678/1145] [ledc,mqtt,resampler] Remove unnecessary ESP-IDF framework restrictions (#12442) Co-authored-by: Claude --- esphome/components/ledc/output.py | 2 +- esphome/components/mqtt/__init__.py | 10 +++++----- esphome/components/resampler/speaker/__init__.py | 4 +--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 2133c4daf9..5e74677a84 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -48,7 +48,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency, cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15), cv.Optional(CONF_PHASE_ANGLE): cv.All( - cv.only_with_esp_idf, cv.angle, cv.float_range(min=0.0, max=360.0) + cv.angle, cv.float_range(min=0.0, max=360.0) ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 237ed2ce38..e73de49fef 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -233,11 +233,11 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_PASSWORD, default=""): cv.string, cv.Optional(CONF_CLEAN_SESSION, default=False): cv.boolean, cv.Optional(CONF_CLIENT_ID): cv.string, - cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32_idf=False): cv.All( - cv.boolean, cv.only_with_esp_idf + cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32=False): cv.All( + cv.boolean, cv.only_on_esp32 ), cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All( - cv.string, cv.only_with_esp_idf + cv.string, cv.only_on_esp32 ), cv.Inclusive(CONF_CLIENT_CERTIFICATE, "cert-key-pair"): cv.All( cv.string, cv.only_on_esp32 @@ -245,8 +245,8 @@ CONFIG_SCHEMA = cv.All( cv.Inclusive(CONF_CLIENT_CERTIFICATE_KEY, "cert-key-pair"): cv.All( cv.string, cv.only_on_esp32 ), - cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All( - cv.boolean, cv.only_with_esp_idf + cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32=False): cv.All( + cv.boolean, cv.only_on_esp32 ), cv.Optional(CONF_DISCOVERY, default=True): cv.Any( cv.boolean, cv.one_of("CLEAN", upper=True) diff --git a/esphome/components/resampler/speaker/__init__.py b/esphome/components/resampler/speaker/__init__.py index def62547b2..7036862d14 100644 --- a/esphome/components/resampler/speaker/__init__.py +++ b/esphome/components/resampler/speaker/__init__.py @@ -63,9 +63,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_BUFFER_DURATION, default="100ms" ): cv.positive_time_period_milliseconds, - cv.SplitDefault(CONF_TASK_STACK_IN_PSRAM, esp32_idf=False): cv.All( - cv.boolean, cv.only_with_esp_idf - ), + cv.Optional(CONF_TASK_STACK_IN_PSRAM, default=False): cv.boolean, cv.Optional(CONF_FILTERS, default=16): cv.int_range(min=2, max=1024), cv.Optional(CONF_TAPS, default=16): _validate_taps, } From 1122ec354f994823b2d4e1f32d0056fea35e2434 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 16 Dec 2025 20:07:57 -0500 Subject: [PATCH 0679/1145] [esp32] Add OTA rollback support (#12460) Co-authored-by: Claude --- esphome/components/esp32/__init__.py | 16 ++++++++++++++++ esphome/components/esp32/core.cpp | 14 +++++--------- esphome/components/safe_mode/safe_mode.cpp | 16 ++++++++++++++++ esphome/core/defines.h | 1 + tests/components/esp32/test.esp32-idf.yaml | 1 + 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 1379fd705f..4448b6bbe7 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -13,6 +13,7 @@ from esphome.const import ( CONF_ADVANCED, CONF_BOARD, CONF_COMPONENTS, + CONF_DISABLED, CONF_ESPHOME, CONF_FRAMEWORK, CONF_IGNORE_EFUSE_CUSTOM_MAC, @@ -24,6 +25,7 @@ from esphome.const import ( CONF_PLATFORMIO_OPTIONS, CONF_REF, CONF_REFRESH, + CONF_SAFE_MODE, CONF_SOURCE, CONF_TYPE, CONF_VARIANT, @@ -81,6 +83,7 @@ CONF_ASSERTION_LEVEL = "assertion_level" CONF_COMPILER_OPTIMIZATION = "compiler_optimization" CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features" CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert" +CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback" CONF_EXECUTE_FROM_PSRAM = "execute_from_psram" CONF_RELEASE = "release" @@ -571,6 +574,13 @@ def final_validate(config): path=[CONF_FLASH_SIZE], ) ) + if advanced[CONF_ENABLE_OTA_ROLLBACK]: + safe_mode_config = full_config.get(CONF_SAFE_MODE) + if safe_mode_config is None or safe_mode_config.get(CONF_DISABLED, False): + _LOGGER.warning( + "OTA rollback requires safe_mode, disabling rollback support" + ) + advanced[CONF_ENABLE_OTA_ROLLBACK] = False if errs: raise cv.MultipleInvalid(errs) @@ -691,6 +701,7 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range( min=8192, max=32768 ), + cv.Optional(CONF_ENABLE_OTA_ROLLBACK, default=True): cv.boolean, } ), cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( @@ -1158,6 +1169,11 @@ async def to_code(config): "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH", True ) + # Enable OTA rollback support + if advanced[CONF_ENABLE_OTA_ROLLBACK]: + add_idf_sdkconfig_option("CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE", True) + cg.add_define("USE_OTA_ROLLBACK") + cg.add_define("ESPHOME_LOOP_TASK_STACK_SIZE", advanced[CONF_LOOP_TASK_STACK_SIZE]) cg.add_define( diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index d8cc909c83..09a45c14a6 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -38,15 +38,11 @@ void arch_init() { // Enable the task watchdog only on the loop task (from which we're currently running) esp_task_wdt_add(nullptr); - // If the bootloader was compiled with CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE the current - // partition will get rolled back unless it is marked as valid. - esp_ota_img_states_t state; - const esp_partition_t *running = esp_ota_get_running_partition(); - if (esp_ota_get_state_partition(running, &state) == ESP_OK) { - if (state == ESP_OTA_IMG_PENDING_VERIFY) { - esp_ota_mark_app_valid_cancel_rollback(); - } - } + // Handle OTA rollback: mark partition valid immediately unless USE_OTA_ROLLBACK is enabled, + // in which case safe_mode will mark it valid after confirming successful boot. +#ifndef USE_OTA_ROLLBACK + esp_ota_mark_app_valid_cancel_rollback(); +#endif } void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); } diff --git a/esphome/components/safe_mode/safe_mode.cpp b/esphome/components/safe_mode/safe_mode.cpp index 62bbca4fb1..f8e5d7d8e5 100644 --- a/esphome/components/safe_mode/safe_mode.cpp +++ b/esphome/components/safe_mode/safe_mode.cpp @@ -9,6 +9,10 @@ #include #include +#ifdef USE_OTA_ROLLBACK +#include +#endif + namespace esphome { namespace safe_mode { @@ -32,6 +36,14 @@ void SafeModeComponent::dump_config() { ESP_LOGW(TAG, "SAFE MODE IS ACTIVE"); } } + +#ifdef USE_OTA_ROLLBACK + const esp_partition_t *last_invalid = esp_ota_get_last_invalid_partition(); + if (last_invalid != nullptr) { + ESP_LOGW(TAG, "OTA rollback detected! Rolled back from partition '%s'", last_invalid->label); + ESP_LOGW(TAG, "The device reset before the boot was marked successful"); + } +#endif } float SafeModeComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; } @@ -42,6 +54,10 @@ void SafeModeComponent::loop() { ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter"); this->clean_rtc(); this->boot_successful_ = true; +#ifdef USE_OTA_ROLLBACK + // Mark OTA partition as valid to prevent rollback + esp_ota_mark_app_valid_cancel_rollback(); +#endif // Disable loop since we no longer need to check this->disable_loop(); } diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 986ab9eff3..4cbe683723 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -170,6 +170,7 @@ // ESP32-specific feature flags #ifdef USE_ESP32 #define USE_ESPHOME_TASK_LOG_BUFFER +#define USE_OTA_ROLLBACK #define USE_BLUETOOTH_PROXY #define BLUETOOTH_PROXY_MAX_CONNECTIONS 3 diff --git a/tests/components/esp32/test.esp32-idf.yaml b/tests/components/esp32/test.esp32-idf.yaml index 6338fe98dd..0e220623a1 100644 --- a/tests/components/esp32/test.esp32-idf.yaml +++ b/tests/components/esp32/test.esp32-idf.yaml @@ -3,6 +3,7 @@ esp32: framework: type: esp-idf advanced: + enable_ota_rollback: true enable_lwip_mdns_queries: true enable_lwip_bridge_interface: true disable_libc_locks_in_iram: false # Test explicit opt-out of RAM optimization From 084f517a20dce7a259a67fa56c90ea4561cb07b1 Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Tue, 16 Dec 2025 19:12:33 -0800 Subject: [PATCH 0680/1145] [hub75] Add set_brightness action (#12521) --- esphome/components/hub75/display.py | 29 ++++++++++++++++++- esphome/components/hub75/hub75.cpp | 2 +- esphome/components/hub75/hub75_component.h | 12 ++++++-- tests/components/hub75/common.yaml | 12 ++++++++ tests/components/hub75/test.esp32-idf.yaml | 7 ++--- .../hub75/test.esp32-s3-idf-board.yaml | 7 ++--- tests/components/hub75/test.esp32-s3-idf.yaml | 7 ++--- 7 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 tests/components/hub75/common.yaml diff --git a/esphome/components/hub75/display.py b/esphome/components/hub75/display.py index 81dd4ffc1c..f401f35406 100644 --- a/esphome/components/hub75/display.py +++ b/esphome/components/hub75/display.py @@ -1,6 +1,6 @@ from typing import Any -from esphome import pins +from esphome import automation, pins import esphome.codegen as cg from esphome.components import display from esphome.components.esp32 import add_idf_component @@ -17,6 +17,8 @@ from esphome.const import ( CONF_OE_PIN, CONF_UPDATE_INTERVAL, ) +from esphome.core import ID +from esphome.cpp_generator import MockObj, TemplateArgsType import esphome.final_validate as fv from esphome.types import ConfigType @@ -135,6 +137,7 @@ CLOCK_SPEEDS = { HUB75Display = hub75_ns.class_("HUB75Display", cg.PollingComponent, display.Display) Hub75Config = cg.global_ns.struct("Hub75Config") Hub75Pins = cg.global_ns.struct("Hub75Pins") +SetBrightnessAction = hub75_ns.class_("SetBrightnessAction", automation.Action) def _merge_board_pins(config: ConfigType) -> ConfigType: @@ -576,3 +579,27 @@ async def to_code(config: ConfigType) -> None: config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) + + +@automation.register_action( + "hub75.set_brightness", + SetBrightnessAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(HUB75Display), + cv.Required(CONF_BRIGHTNESS): cv.templatable(cv.int_range(min=0, max=255)), + }, + key=CONF_BRIGHTNESS, + ), +) +async def hub75_set_brightness_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + template_ = await cg.templatable(config[CONF_BRIGHTNESS], args, cg.uint8) + cg.add(var.set_brightness(template_)) + return var diff --git a/esphome/components/hub75/hub75.cpp b/esphome/components/hub75/hub75.cpp index e023e446c4..7317174831 100644 --- a/esphome/components/hub75/hub75.cpp +++ b/esphome/components/hub75/hub75.cpp @@ -179,7 +179,7 @@ void HOT HUB75Display::draw_pixels_at(int x_start, int y_start, int w, int h, co } } -void HUB75Display::set_brightness(int brightness) { +void HUB75Display::set_brightness(uint8_t brightness) { this->brightness_ = brightness; this->enabled_ = (brightness > 0); if (this->driver_ != nullptr) { diff --git a/esphome/components/hub75/hub75_component.h b/esphome/components/hub75/hub75_component.h index 49d4274483..f0e7ea10d5 100644 --- a/esphome/components/hub75/hub75_component.h +++ b/esphome/components/hub75/hub75_component.h @@ -5,6 +5,7 @@ #include #include "esphome/components/display/display_buffer.h" +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" @@ -34,7 +35,7 @@ class HUB75Display : public display::Display { display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; // Brightness control (runtime mutable) - void set_brightness(int brightness); + void set_brightness(uint8_t brightness); protected: // Display internal methods @@ -46,10 +47,17 @@ class HUB75Display : public display::Display { Hub75Config config_; // Immutable configuration // Runtime state (mutable) - int brightness_{128}; + uint8_t brightness_{128}; bool enabled_{false}; }; +template class SetBrightnessAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(uint8_t, brightness) + + void play(const Ts &...x) override { this->parent_->set_brightness(this->brightness_.value(x...)); } +}; + } // namespace esphome::hub75 #endif diff --git a/tests/components/hub75/common.yaml b/tests/components/hub75/common.yaml new file mode 100644 index 0000000000..87e9e1c128 --- /dev/null +++ b/tests/components/hub75/common.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + # Test simple value + - hub75.set_brightness: 200 + + # Test templatable value + - hub75.set_brightness: !lambda 'return 100;' + + # Test with explicit ID + - hub75.set_brightness: + id: my_hub75 + brightness: 50 diff --git a/tests/components/hub75/test.esp32-idf.yaml b/tests/components/hub75/test.esp32-idf.yaml index 9f6bd57292..dad2a02c24 100644 --- a/tests/components/hub75/test.esp32-idf.yaml +++ b/tests/components/hub75/test.esp32-idf.yaml @@ -1,8 +1,3 @@ -esp32: - board: esp32dev - framework: - type: esp-idf - display: - platform: hub75 id: my_hub75 @@ -37,3 +32,5 @@ display: then: lambda: |- ESP_LOGD("display", "1 -> 2"); + +<<: !include common.yaml diff --git a/tests/components/hub75/test.esp32-s3-idf-board.yaml b/tests/components/hub75/test.esp32-s3-idf-board.yaml index 9568ccf3aa..3723a80006 100644 --- a/tests/components/hub75/test.esp32-s3-idf-board.yaml +++ b/tests/components/hub75/test.esp32-s3-idf-board.yaml @@ -1,8 +1,3 @@ -esp32: - board: esp32-s3-devkitc-1 - framework: - type: esp-idf - display: - platform: hub75 id: hub75_display_board @@ -24,3 +19,5 @@ display: then: lambda: |- ESP_LOGD("display", "1 -> 2"); + +<<: !include common.yaml diff --git a/tests/components/hub75/test.esp32-s3-idf.yaml b/tests/components/hub75/test.esp32-s3-idf.yaml index db678c98a4..f8ee26e73d 100644 --- a/tests/components/hub75/test.esp32-s3-idf.yaml +++ b/tests/components/hub75/test.esp32-s3-idf.yaml @@ -1,8 +1,3 @@ -esp32: - board: esp32-s3-devkitc-1 - framework: - type: esp-idf - display: - platform: hub75 id: my_hub75 @@ -37,3 +32,5 @@ display: then: lambda: |- ESP_LOGD("display", "1 -> 2"); + +<<: !include common.yaml From a065990ab9f4818342579d0ed49fc1d1a1697002 Mon Sep 17 00:00:00 2001 From: Roger Fachini Date: Tue, 16 Dec 2025 19:20:12 -0800 Subject: [PATCH 0681/1145] [update] Add check action to trigger update checks (#12415) --- esphome/components/update/__init__.py | 18 ++++++++++++++++++ esphome/components/update/automation.h | 5 +++++ tests/components/update/common.yaml | 2 ++ 3 files changed, 25 insertions(+) diff --git a/esphome/components/update/__init__.py b/esphome/components/update/__init__.py index 7a381c85a8..e146f7e685 100644 --- a/esphome/components/update/__init__.py +++ b/esphome/components/update/__init__.py @@ -29,6 +29,9 @@ UpdateInfo = update_ns.struct("UpdateInfo") PerformAction = update_ns.class_( "PerformAction", automation.Action, cg.Parented.template(UpdateEntity) ) +CheckAction = update_ns.class_( + "CheckAction", automation.Action, cg.Parented.template(UpdateEntity) +) IsAvailableCondition = update_ns.class_( "IsAvailableCondition", automation.Condition, cg.Parented.template(UpdateEntity) ) @@ -143,6 +146,21 @@ async def update_perform_action_to_code(config, action_id, template_arg, args): return var +@automation.register_action( + "update.check", + CheckAction, + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(UpdateEntity), + } + ), +) +async def update_check_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + @automation.register_condition( "update.is_available", IsAvailableCondition, diff --git a/esphome/components/update/automation.h b/esphome/components/update/automation.h index 8563b855fe..af24c838b1 100644 --- a/esphome/components/update/automation.h +++ b/esphome/components/update/automation.h @@ -14,6 +14,11 @@ template class PerformAction : public Action, public Pare void play(const Ts &...x) override { this->parent_->perform(this->force_.value(x...)); } }; +template class CheckAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->check(); } +}; + template class IsAvailableCondition : public Condition, public Parented { public: bool check(const Ts &...x) override { return this->parent_->state == UPDATE_STATE_AVAILABLE; } diff --git a/tests/components/update/common.yaml b/tests/components/update/common.yaml index 521a0a6a5c..40042945c8 100644 --- a/tests/components/update/common.yaml +++ b/tests/components/update/common.yaml @@ -9,6 +9,8 @@ esphome: update.is_available: then: - logger.log: "Update available" + else: + - update.check: - update.perform: force_update: true From 56c1691d72818ec41b42bb0c77d60c3d352af490 Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Wed, 17 Dec 2025 04:52:28 +0100 Subject: [PATCH 0682/1145] [pca9685,sx126x,sx127x] Use frequency/float_range check (#12490) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/ade7880/sensor.py | 2 +- esphome/components/cc1101/__init__.py | 2 +- esphome/components/esp32_camera/__init__.py | 2 +- esphome/components/esp8266_pwm/output.py | 2 +- esphome/components/i2c/__init__.py | 2 +- esphome/components/ledc/output.py | 4 +++- esphome/components/libretiny_pwm/output.py | 4 +++- esphome/components/pca9685/__init__.py | 2 +- esphome/components/rp2040_pwm/output.py | 2 +- esphome/components/sx126x/__init__.py | 8 ++++++-- esphome/components/sx127x/__init__.py | 8 ++++++-- 11 files changed, 25 insertions(+), 13 deletions(-) diff --git a/esphome/components/ade7880/sensor.py b/esphome/components/ade7880/sensor.py index 39dbeb225f..beb74d7310 100644 --- a/esphome/components/ade7880/sensor.py +++ b/esphome/components/ade7880/sensor.py @@ -227,7 +227,7 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(ADE7880), cv.Optional(CONF_FREQUENCY, default="50Hz"): cv.All( - cv.frequency, cv.Range(min=45.0, max=66.0) + cv.frequency, cv.float_range(min=45.0, max=66.0) ), cv.Optional(CONF_IRQ0_PIN): pins.internal_gpio_input_pin_schema, cv.Required(CONF_IRQ1_PIN): pins.internal_gpio_input_pin_schema, diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index 1971817fb1..e314da7079 100644 --- a/esphome/components/cc1101/__init__.py +++ b/esphome/components/cc1101/__init__.py @@ -165,7 +165,7 @@ CONFIG_MAP = { CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0), CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False), CONF_DC_BLOCKING_FILTER: cv.boolean, - CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300000000, max=928000000)), + CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)), CONF_IF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=25000, max=788000)), CONF_FILTER_BANDWIDTH: cv.All(cv.frequency, cv.float_range(min=58000, max=812000)), CONF_CHANNEL: cv.uint8_t, diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index d9d9bc0a56..4182683bdc 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -186,7 +186,7 @@ CONFIG_SCHEMA = cv.All( { cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( - cv.frequency, cv.Range(min=8e6, max=20e6) + cv.frequency, cv.float_range(min=8e6, max=20e6) ), } ), diff --git a/esphome/components/esp8266_pwm/output.py b/esphome/components/esp8266_pwm/output.py index 1404ef8ac3..2ddf4b9014 100644 --- a/esphome/components/esp8266_pwm/output.py +++ b/esphome/components/esp8266_pwm/output.py @@ -16,7 +16,7 @@ def valid_pwm_pin(value): esp8266_pwm_ns = cg.esphome_ns.namespace("esp8266_pwm") ESP8266PWM = esp8266_pwm_ns.class_("ESP8266PWM", output.FloatOutput, cg.Component) SetFrequencyAction = esp8266_pwm_ns.class_("SetFrequencyAction", automation.Action) -validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6)) +validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6)) CONFIG_SCHEMA = cv.All( output.FLOAT_OUTPUT_SCHEMA.extend( diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 9e7c9d702c..7706484e97 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -121,7 +121,7 @@ CONFIG_SCHEMA = cv.All( nrf52="100kHz", ): cv.All( cv.frequency, - cv.Range(min=0, min_included=False), + cv.float_range(min=0, min_included=False), ), cv.Optional(CONF_TIMEOUT): cv.All( cv.only_with_framework(["arduino", "esp-idf"]), diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 5e74677a84..7a45b9dc3f 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -45,7 +45,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { cv.Required(CONF_ID): cv.declare_id(LEDCOutput), cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency, + cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All( + cv.frequency, cv.float_range(min=0, min_included=False) + ), cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15), cv.Optional(CONF_PHASE_ANGLE): cv.All( cv.angle, cv.float_range(min=0.0, max=360.0) diff --git a/esphome/components/libretiny_pwm/output.py b/esphome/components/libretiny_pwm/output.py index 1eb4869da3..28556514d8 100644 --- a/esphome/components/libretiny_pwm/output.py +++ b/esphome/components/libretiny_pwm/output.py @@ -14,7 +14,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { cv.Required(CONF_ID): cv.declare_id(LibreTinyPWM), cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency, + cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All( + cv.frequency, cv.float_range(min=0, min_included=False) + ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/pca9685/__init__.py b/esphome/components/pca9685/__init__.py index 56101c2d62..0e238ff7da 100644 --- a/esphome/components/pca9685/__init__.py +++ b/esphome/components/pca9685/__init__.py @@ -38,7 +38,7 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(PCA9685Output), cv.Optional(CONF_FREQUENCY): cv.All( - cv.frequency, cv.Range(min=23.84, max=1525.88) + cv.frequency, cv.float_range(min=23.84, max=1525.88) ), cv.Optional(CONF_EXTERNAL_CLOCK_INPUT, default=False): cv.boolean, cv.Optional(CONF_PHASE_BALANCER, default="linear"): cv.enum( diff --git a/esphome/components/rp2040_pwm/output.py b/esphome/components/rp2040_pwm/output.py index ac1892fa29..441a52de7f 100644 --- a/esphome/components/rp2040_pwm/output.py +++ b/esphome/components/rp2040_pwm/output.py @@ -11,7 +11,7 @@ DEPENDENCIES = ["rp2040"] rp2040_pwm_ns = cg.esphome_ns.namespace("rp2040_pwm") RP2040PWM = rp2040_pwm_ns.class_("RP2040PWM", output.FloatOutput, cg.Component) SetFrequencyAction = rp2040_pwm_ns.class_("SetFrequencyAction", automation.Action) -validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6)) +validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6)) CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { diff --git a/esphome/components/sx126x/__init__.py b/esphome/components/sx126x/__init__.py index 4641db6483..ed878ed0d4 100644 --- a/esphome/components/sx126x/__init__.py +++ b/esphome/components/sx126x/__init__.py @@ -199,9 +199,13 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_CRC_INITIAL, default=0x1D0F): cv.All( cv.hex_int, cv.Range(min=0, max=0xFFFF) ), - cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000), + cv.Optional(CONF_DEVIATION, default="5kHz"): cv.All( + cv.frequency, cv.float_range(min=0, max=100000) + ), cv.Required(CONF_DIO1_PIN): pins.gpio_input_pin_schema, - cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000), + cv.Required(CONF_FREQUENCY): cv.All( + cv.frequency, cv.float_range(min=137.0e6, max=1020.0e6) + ), cv.Required(CONF_HW_VERSION): cv.one_of( "sx1261", "sx1262", "sx1268", "llcc68", lower=True ), diff --git a/esphome/components/sx127x/__init__.py b/esphome/components/sx127x/__init__.py index b569a75972..f3a9cca93f 100644 --- a/esphome/components/sx127x/__init__.py +++ b/esphome/components/sx127x/__init__.py @@ -196,9 +196,13 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_BITSYNC): cv.boolean, cv.Optional(CONF_CODING_RATE, default="CR_4_5"): cv.enum(CODING_RATE), cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean, - cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000), + cv.Optional(CONF_DEVIATION, default="5kHz"): cv.All( + cv.frequency, cv.float_range(min=0, max=100000) + ), cv.Optional(CONF_DIO0_PIN): pins.internal_gpio_input_pin_schema, - cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000), + cv.Required(CONF_FREQUENCY): cv.All( + cv.frequency, cv.float_range(min=137.0e6, max=1020.0e6) + ), cv.Required(CONF_MODULATION): cv.enum(MOD), cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True), cv.Optional(CONF_PA_PIN, default="BOOST"): cv.enum(PA_PIN), From 9928ab09cf94a63893d3fd10aaf0ffd937b147ea Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Dec 2025 09:29:43 -0700 Subject: [PATCH 0683/1145] [time] Convert to C++17 nested namespace syntax (#12463) --- esphome/components/time/automation.cpp | 6 ++---- esphome/components/time/automation.h | 6 ++---- esphome/components/time/real_time_clock.cpp | 6 ++---- esphome/components/time/real_time_clock.h | 6 ++---- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/esphome/components/time/automation.cpp b/esphome/components/time/automation.cpp index f7c1916ffe..8bc87878d1 100644 --- a/esphome/components/time/automation.cpp +++ b/esphome/components/time/automation.cpp @@ -4,8 +4,7 @@ #include -namespace esphome { -namespace time { +namespace esphome::time { static const char *const TAG = "automation"; static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider @@ -92,5 +91,4 @@ SyncTrigger::SyncTrigger(RealTimeClock *rtc) : rtc_(rtc) { rtc->add_on_time_sync_callback([this]() { this->trigger(); }); } -} // namespace time -} // namespace esphome +} // namespace esphome::time diff --git a/esphome/components/time/automation.h b/esphome/components/time/automation.h index b5c8291533..4ccfc641d6 100644 --- a/esphome/components/time/automation.h +++ b/esphome/components/time/automation.h @@ -8,8 +8,7 @@ #include -namespace esphome { -namespace time { +namespace esphome::time { class CronTrigger : public Trigger<>, public Component { public: @@ -48,5 +47,4 @@ class SyncTrigger : public Trigger<>, public Component { protected: RealTimeClock *rtc_; }; -} // namespace time -} // namespace esphome +} // namespace esphome::time diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 175cee0c1f..639af4457f 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -17,8 +17,7 @@ #include -namespace esphome { -namespace time { +namespace esphome::time { static const char *const TAG = "time"; @@ -78,5 +77,4 @@ void RealTimeClock::apply_timezone_() { } #endif -} // namespace time -} // namespace esphome +} // namespace esphome::time diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index 2f17bd86d6..70469e11b0 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -7,8 +7,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/time.h" -namespace esphome { -namespace time { +namespace esphome::time { /// The RealTimeClock class exposes common timekeeping functions via the device's local real-time clock. /// @@ -75,5 +74,4 @@ template class TimeHasTimeCondition : public Condition { RealTimeClock *parent_; }; -} // namespace time -} // namespace esphome +} // namespace esphome::time From bf6a03d1cf70f5ec8d04a129c09a941b14593b3f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Dec 2025 09:29:51 -0700 Subject: [PATCH 0684/1145] [factory_reset] Optimize memory by storing interval as uint16_t seconds (#12462) --- esphome/components/factory_reset/__init__.py | 6 ++++-- esphome/components/factory_reset/factory_reset.cpp | 14 ++++++-------- esphome/components/factory_reset/factory_reset.h | 14 ++++++-------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/esphome/components/factory_reset/__init__.py b/esphome/components/factory_reset/__init__.py index f3cefe6970..5784d09ce6 100644 --- a/esphome/components/factory_reset/__init__.py +++ b/esphome/components/factory_reset/__init__.py @@ -50,7 +50,9 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(): cv.declare_id(FactoryResetComponent), cv.Optional(CONF_MAX_DELAY, default="10s"): cv.All( cv.positive_time_period_seconds, - cv.Range(min=cv.TimePeriod(milliseconds=1000)), + cv.Range( + min=cv.TimePeriod(seconds=1), max=cv.TimePeriod(seconds=65535) + ), ), cv.Optional(CONF_RESETS_REQUIRED): cv.positive_not_null_int, cv.Optional(CONF_ON_INCREMENT): validate_automation( @@ -82,7 +84,7 @@ async def to_code(config): var = cg.new_Pvariable( config[CONF_ID], reset_count, - config[CONF_MAX_DELAY].total_milliseconds, + config[CONF_MAX_DELAY].total_seconds, ) await cg.register_component(var, config) for conf in config.get(CONF_ON_INCREMENT, []): diff --git a/esphome/components/factory_reset/factory_reset.cpp b/esphome/components/factory_reset/factory_reset.cpp index c900759d90..bbbe399148 100644 --- a/esphome/components/factory_reset/factory_reset.cpp +++ b/esphome/components/factory_reset/factory_reset.cpp @@ -8,8 +8,7 @@ #if !defined(USE_RP2040) && !defined(USE_HOST) -namespace esphome { -namespace factory_reset { +namespace esphome::factory_reset { static const char *const TAG = "factory_reset"; static const uint32_t POWER_CYCLES_KEY = 0xFA5C0DE; @@ -33,10 +32,10 @@ void FactoryResetComponent::dump_config() { this->flash_.load(&count); ESP_LOGCONFIG(TAG, "Factory Reset by Reset:"); ESP_LOGCONFIG(TAG, - " Max interval between resets %" PRIu32 " seconds\n" + " Max interval between resets: %u seconds\n" " Current count: %u\n" " Factory reset after %u resets", - this->max_interval_ / 1000, count, this->required_count_); + this->max_interval_, count, this->required_count_); } void FactoryResetComponent::save_(uint8_t count) { @@ -61,8 +60,8 @@ void FactoryResetComponent::setup() { } this->save_(count); ESP_LOGD(TAG, "Power on reset detected, incremented count to %u", count); - this->set_timeout(this->max_interval_, [this]() { - ESP_LOGD(TAG, "No reset in the last %" PRIu32 " seconds, resetting count", this->max_interval_ / 1000); + this->set_timeout(static_cast(this->max_interval_) * 1000, [this]() { + ESP_LOGD(TAG, "No reset in the last %u seconds, resetting count", this->max_interval_); this->save_(0); // reset count }); } else { @@ -70,7 +69,6 @@ void FactoryResetComponent::setup() { } } -} // namespace factory_reset -} // namespace esphome +} // namespace esphome::factory_reset #endif // !defined(USE_RP2040) && !defined(USE_HOST) diff --git a/esphome/components/factory_reset/factory_reset.h b/esphome/components/factory_reset/factory_reset.h index 80942b29bd..990bb2edb6 100644 --- a/esphome/components/factory_reset/factory_reset.h +++ b/esphome/components/factory_reset/factory_reset.h @@ -9,12 +9,11 @@ #include #endif -namespace esphome { -namespace factory_reset { +namespace esphome::factory_reset { class FactoryResetComponent : public Component { public: - FactoryResetComponent(uint8_t required_count, uint32_t max_interval) - : required_count_(required_count), max_interval_(max_interval) {} + FactoryResetComponent(uint8_t required_count, uint16_t max_interval) + : max_interval_(max_interval), required_count_(required_count) {} void dump_config() override; void setup() override; @@ -26,9 +25,9 @@ class FactoryResetComponent : public Component { ~FactoryResetComponent() = default; void save_(uint8_t count); ESPPreferenceObject flash_{}; // saves the number of fast power cycles - uint8_t required_count_; // The number of boot attempts before fast boot is enabled - uint32_t max_interval_; // max interval between power cycles CallbackManager increment_callback_{}; + uint16_t max_interval_; // max interval between power cycles in seconds + uint8_t required_count_; // The number of boot attempts before fast boot is enabled }; class FastBootTrigger : public Trigger { @@ -37,7 +36,6 @@ class FastBootTrigger : public Trigger { parent->add_increment_callback([this](uint8_t current, uint8_t target) { this->trigger(current, target); }); } }; -} // namespace factory_reset -} // namespace esphome +} // namespace esphome::factory_reset #endif // !defined(USE_RP2040) && !defined(USE_HOST) From ab73ed76b8baf27cb17d5a4b4e11d01c55d0c386 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Dec 2025 09:29:58 -0700 Subject: [PATCH 0685/1145] [esphome] Improve OTA field alignment to save 4 bytes on 32-bit (#12461) --- esphome/components/esphome/ota/ota_esphome.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esphome/ota/ota_esphome.h b/esphome/components/esphome/ota/ota_esphome.h index 057461e6a4..4412a65757 100644 --- a/esphome/components/esphome/ota/ota_esphome.h +++ b/esphome/components/esphome/ota/ota_esphome.h @@ -80,6 +80,7 @@ class ESPHomeOTAComponent : public ota::OTAComponent { #ifdef USE_OTA_PASSWORD std::string password_; + std::unique_ptr auth_buf_; #endif // USE_OTA_PASSWORD std::unique_ptr server_; @@ -93,7 +94,6 @@ class ESPHomeOTAComponent : public ota::OTAComponent { uint8_t handshake_buf_pos_{0}; uint8_t ota_features_{0}; #ifdef USE_OTA_PASSWORD - std::unique_ptr auth_buf_; uint8_t auth_buf_pos_{0}; uint8_t auth_type_{0}; // Store auth type to know which hasher to use #endif // USE_OTA_PASSWORD From 63fc8b4e5acca786f2fdf95269df98157d518ef6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Dec 2025 09:30:12 -0700 Subject: [PATCH 0686/1145] [core] Refactor str_snake_case and str_sanitize to use constexpr helpers (#12454) --- esphome/core/helpers.cpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 55466fca8a..84079227e1 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -200,22 +200,27 @@ template std::string str_ctype_transform(const std::string &str) } std::string str_lower_case(const std::string &str) { return str_ctype_transform(str); } std::string str_upper_case(const std::string &str) { return str_ctype_transform(str); } +// Convert char to snake_case: lowercase and spaces to underscores +static constexpr char to_snake_case_char(char c) { + return (c == ' ') ? '_' : (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; +} +// Sanitize char: keep alphanumerics, dashes, underscores; replace others with underscore +static constexpr char to_sanitized_char(char c) { + return (c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ? c : '_'; +} std::string str_snake_case(const std::string &str) { - std::string result; - result.resize(str.length()); - std::transform(str.begin(), str.end(), result.begin(), ::tolower); - std::replace(result.begin(), result.end(), ' ', '_'); + std::string result = str; + for (char &c : result) { + c = to_snake_case_char(c); + } return result; } std::string str_sanitize(const std::string &str) { - std::string out = str; - std::replace_if( - out.begin(), out.end(), - [](const char &c) { - return c != '-' && c != '_' && (c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z'); - }, - '_'); - return out; + std::string result = str; + for (char &c : result) { + c = to_sanitized_char(c); + } + return result; } std::string str_snprintf(const char *fmt, size_t len, ...) { std::string str; From e91c6a79ea31a076396a9b5a614e7bef7367353d Mon Sep 17 00:00:00 2001 From: Piotr Szulc Date: Wed, 17 Dec 2025 17:45:05 +0100 Subject: [PATCH 0687/1145] [deep_sleep] Deep sleep for BK72xx (#12267) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/deep_sleep/__init__.py | 111 ++++++++++++++++-- .../deep_sleep/deep_sleep_bk72xx.cpp | 64 ++++++++++ .../deep_sleep/deep_sleep_component.h | 31 ++++- .../deep_sleep/test.bk72xx-ard.yaml | 14 +++ 4 files changed, 206 insertions(+), 14 deletions(-) create mode 100644 esphome/components/deep_sleep/deep_sleep_bk72xx.cpp create mode 100644 tests/components/deep_sleep/test.bk72xx-ard.yaml diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 8849fad7d6..3cfe7aa641 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -1,4 +1,4 @@ -from esphome import automation, pins +from esphome import automation, core, pins import esphome.codegen as cg from esphome.components import esp32, time from esphome.components.esp32 import ( @@ -23,16 +23,20 @@ from esphome.const import ( CONF_MINUTE, CONF_MODE, CONF_NUMBER, + CONF_PIN, CONF_PINS, CONF_RUN_DURATION, CONF_SECOND, CONF_SLEEP_DURATION, CONF_TIME_ID, CONF_WAKEUP_PIN, + PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, PlatformFramework, ) +from esphome.core import CORE +from esphome.types import ConfigType WAKEUP_PINS = { VARIANT_ESP32: [ @@ -113,7 +117,7 @@ WAKEUP_PINS = { } -def validate_pin_number(value): +def validate_pin_number_esp32(value: ConfigType) -> ConfigType: valid_pins = WAKEUP_PINS.get(get_esp32_variant(), WAKEUP_PINS[VARIANT_ESP32]) if value[CONF_NUMBER] not in valid_pins: raise cv.Invalid( @@ -122,6 +126,51 @@ def validate_pin_number(value): return value +def validate_pin_number(value: ConfigType) -> ConfigType: + if not CORE.is_esp32: + return value + return validate_pin_number_esp32(value) + + +def validate_wakeup_pin( + value: ConfigType | list[ConfigType], +) -> list[ConfigType]: + if not isinstance(value, list): + processed_pins: list[ConfigType] = [{CONF_PIN: value}] + else: + processed_pins = list(value) + + for i, pin_config in enumerate(processed_pins): + # now validate each item + validated_pin = WAKEUP_PIN_SCHEMA(pin_config) + validate_pin_number(validated_pin[CONF_PIN]) + processed_pins[i] = validated_pin + + return processed_pins + + +def validate_config(config: ConfigType) -> ConfigType: + # right now only BK72XX supports the list format for wakeup pins + if CORE.is_bk72xx: + if CONF_WAKEUP_PIN_MODE in config: + wakeup_pins = config.get(CONF_WAKEUP_PIN, []) + if len(wakeup_pins) > 1: + raise cv.Invalid( + "You need to remove the global wakeup_pin_mode and define it per pin" + ) + if wakeup_pins: + wakeup_pins[0][CONF_WAKEUP_PIN_MODE] = config.pop(CONF_WAKEUP_PIN_MODE) + elif ( + isinstance(config.get(CONF_WAKEUP_PIN), list) + and len(config[CONF_WAKEUP_PIN]) > 1 + ): + raise cv.Invalid( + "Your platform does not support providing multiple entries in wakeup_pin" + ) + + return config + + def _validate_ex1_wakeup_mode(value): if value == "ALL_LOW": esp32.only_on_variant(supported=[VARIANT_ESP32], msg_prefix="ALL_LOW")(value) @@ -141,6 +190,15 @@ def _validate_ex1_wakeup_mode(value): return value +def _validate_sleep_duration(value: core.TimePeriod) -> core.TimePeriod: + if not CORE.is_bk72xx: + return value + max_duration = core.TimePeriod(hours=36) + if value > max_duration: + raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX") + return value + + deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep") DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component) EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action) @@ -186,6 +244,13 @@ WAKEUP_CAUSES_SCHEMA = cv.Schema( } ) +WAKEUP_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_WAKEUP_PIN_MODE): cv.enum(WAKEUP_PIN_MODES, upper=True), + } +) + CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -194,14 +259,15 @@ CONFIG_SCHEMA = cv.All( cv.All(cv.only_on_esp32, WAKEUP_CAUSES_SCHEMA), cv.positive_time_period_milliseconds, ), - cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, - cv.Optional(CONF_WAKEUP_PIN): cv.All( - cv.only_on_esp32, - pins.internal_gpio_input_pin_schema, - validate_pin_number, + cv.Optional(CONF_SLEEP_DURATION): cv.All( + cv.positive_time_period_milliseconds, + _validate_sleep_duration, ), + cv.Optional(CONF_WAKEUP_PIN): validate_wakeup_pin, cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All( - cv.only_on_esp32, cv.enum(WAKEUP_PIN_MODES), upper=True + cv.only_on([PLATFORM_ESP32, PLATFORM_BK72XX]), + cv.enum(WAKEUP_PIN_MODES), + upper=True, ), cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All( cv.only_on_esp32, @@ -212,7 +278,8 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.Required(CONF_PINS): cv.ensure_list( - pins.internal_gpio_input_pin_schema, validate_pin_number + pins.internal_gpio_input_pin_schema, + validate_pin_number_esp32, ), cv.Required(CONF_MODE): cv.All( cv.enum(EXT1_WAKEUP_MODES, upper=True), @@ -238,7 +305,8 @@ CONFIG_SCHEMA = cv.All( ), } ).extend(cv.COMPONENT_SCHEMA), - cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]), + cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]), + validate_config, ) @@ -249,8 +317,21 @@ async def to_code(config): if CONF_SLEEP_DURATION in config: cg.add(var.set_sleep_duration(config[CONF_SLEEP_DURATION])) if CONF_WAKEUP_PIN in config: - pin = await cg.gpio_pin_expression(config[CONF_WAKEUP_PIN]) - cg.add(var.set_wakeup_pin(pin)) + pins_as_list = config.get(CONF_WAKEUP_PIN, []) + if CORE.is_bk72xx: + cg.add(var.init_wakeup_pins_(len(pins_as_list))) + for item in pins_as_list: + cg.add( + var.add_wakeup_pin( + await cg.gpio_pin_expression(item[CONF_PIN]), + item.get( + CONF_WAKEUP_PIN_MODE, WakeupPinMode.WAKEUP_PIN_MODE_IGNORE + ), + ) + ) + else: + pin = await cg.gpio_pin_expression(pins_as_list[0][CONF_PIN]) + cg.add(var.set_wakeup_pin(pin)) if CONF_WAKEUP_PIN_MODE in config: cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE])) if CONF_RUN_DURATION in config: @@ -305,7 +386,10 @@ DEEP_SLEEP_ENTER_SCHEMA = cv.All( cv.Schema( { cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable( - cv.positive_time_period_milliseconds + cv.All( + cv.positive_time_period_milliseconds, + _validate_sleep_duration, + ) ), # Only on ESP32 due to how long the RTC on ESP8266 can stay asleep cv.Exclusive(CONF_UNTIL, "time"): cv.All( @@ -363,5 +447,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( PlatformFramework.ESP32_IDF, }, "deep_sleep_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "deep_sleep_bk72xx.cpp": {PlatformFramework.BK72XX_ARDUINO}, } ) diff --git a/esphome/components/deep_sleep/deep_sleep_bk72xx.cpp b/esphome/components/deep_sleep/deep_sleep_bk72xx.cpp new file mode 100644 index 0000000000..b5fadd7230 --- /dev/null +++ b/esphome/components/deep_sleep/deep_sleep_bk72xx.cpp @@ -0,0 +1,64 @@ +#ifdef USE_BK72XX + +#include "deep_sleep_component.h" +#include "esphome/core/log.h" + +namespace esphome::deep_sleep { + +static const char *const TAG = "deep_sleep.bk72xx"; + +optional DeepSleepComponent::get_run_duration_() const { return this->run_duration_; } + +void DeepSleepComponent::dump_config_platform_() { + for (const WakeUpPinItem &item : this->wakeup_pins_) { + LOG_PIN(" Wakeup Pin: ", item.wakeup_pin); + } +} + +bool DeepSleepComponent::pin_prevents_sleep_(WakeUpPinItem &pinItem) const { + return (pinItem.wakeup_pin_mode == WAKEUP_PIN_MODE_KEEP_AWAKE && pinItem.wakeup_pin != nullptr && + !this->sleep_duration_.has_value() && (pinItem.wakeup_level == get_real_pin_state_(*pinItem.wakeup_pin))); +} + +bool DeepSleepComponent::prepare_to_sleep_() { + if (wakeup_pins_.size() > 0) { + for (WakeUpPinItem &item : this->wakeup_pins_) { + if (pin_prevents_sleep_(item)) { + // Defer deep sleep until inactive + if (!this->next_enter_deep_sleep_) { + this->status_set_warning(); + ESP_LOGV(TAG, "Waiting for pin to switch state to enter deep sleep..."); + } + this->next_enter_deep_sleep_ = true; + return false; + } + } + } + return true; +} + +void DeepSleepComponent::deep_sleep_() { + for (WakeUpPinItem &item : this->wakeup_pins_) { + if (item.wakeup_pin_mode == WAKEUP_PIN_MODE_INVERT_WAKEUP) { + if (item.wakeup_level == get_real_pin_state_(*item.wakeup_pin)) { + item.wakeup_level = !item.wakeup_level; + } + } + ESP_LOGI(TAG, "Wake-up on P%u %s (%d)", item.wakeup_pin->get_pin(), item.wakeup_level ? "HIGH" : "LOW", + static_cast(item.wakeup_pin_mode)); + } + + if (this->sleep_duration_.has_value()) + lt_deep_sleep_config_timer((*this->sleep_duration_ / 1000) & 0xFFFFFFFF); + + for (WakeUpPinItem &item : this->wakeup_pins_) { + lt_deep_sleep_config_gpio(1 << item.wakeup_pin->get_pin(), item.wakeup_level); + lt_deep_sleep_keep_floating_gpio(1 << item.wakeup_pin->get_pin(), true); + } + + lt_deep_sleep_enter(); +} + +} // namespace esphome::deep_sleep + +#endif // USE_BK72XX diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index bca3aa5e4d..3e6eda2257 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -19,7 +19,7 @@ namespace esphome { namespace deep_sleep { -#ifdef USE_ESP32 +#if defined(USE_ESP32) || defined(USE_BK72XX) /** The values of this enum define what should be done if deep sleep is set up with a wakeup pin on the ESP32 * and the scenario occurs that the wakeup pin is already in the wakeup state. @@ -33,7 +33,17 @@ enum WakeupPinMode { */ WAKEUP_PIN_MODE_INVERT_WAKEUP, }; +#endif +#if defined(USE_BK72XX) +struct WakeUpPinItem { + InternalGPIOPin *wakeup_pin; + WakeupPinMode wakeup_pin_mode; + bool wakeup_level; +}; +#endif // USE_BK72XX + +#ifdef USE_ESP32 #if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) struct Ext1Wakeup { uint64_t mask; @@ -75,6 +85,13 @@ class DeepSleepComponent : public Component { void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode); #endif // USE_ESP32 +#if defined(USE_BK72XX) + void init_wakeup_pins_(size_t capacity) { this->wakeup_pins_.init(capacity); } + void add_wakeup_pin(InternalGPIOPin *wakeup_pin, WakeupPinMode wakeup_pin_mode) { + this->wakeup_pins_.emplace_back(WakeUpPinItem{wakeup_pin, wakeup_pin_mode, !wakeup_pin->is_inverted()}); + } +#endif // USE_BK72XX + #if defined(USE_ESP32) #if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) void set_ext1_wakeup(Ext1Wakeup ext1_wakeup); @@ -114,7 +131,17 @@ class DeepSleepComponent : public Component { bool prepare_to_sleep_(); void deep_sleep_(); +#ifdef USE_BK72XX + bool pin_prevents_sleep_(WakeUpPinItem &pinItem) const; + bool get_real_pin_state_(InternalGPIOPin &pin) const { return (pin.digital_read() ^ pin.is_inverted()); } +#endif // USE_BK72XX + optional sleep_duration_; + +#ifdef USE_BK72XX + FixedVector wakeup_pins_; +#endif // USE_BK72XX + #ifdef USE_ESP32 InternalGPIOPin *wakeup_pin_; WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE}; @@ -124,8 +151,10 @@ class DeepSleepComponent : public Component { #endif optional touch_wakeup_; + optional wakeup_cause_to_run_duration_; #endif // USE_ESP32 + optional run_duration_; bool next_enter_deep_sleep_{false}; bool prevent_{false}; diff --git a/tests/components/deep_sleep/test.bk72xx-ard.yaml b/tests/components/deep_sleep/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..2385fbb4db --- /dev/null +++ b/tests/components/deep_sleep/test.bk72xx-ard.yaml @@ -0,0 +1,14 @@ +deep_sleep: + run_duration: 30s + sleep_duration: 12h + wakeup_pin: + - pin: + number: P6 + - pin: P7 + wakeup_pin_mode: KEEP_AWAKE + - pin: + number: P10 + inverted: true + wakeup_pin_mode: INVERT_WAKEUP + +<<: !include common.yaml From 0707f383a69cbfc9c9d6365e1f43893ef16204ac Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:45:17 +0100 Subject: [PATCH 0688/1145] [nextion] Use ESP-IDF for ESP32 Arduino (#9429) Co-authored-by: J. Nick Koston Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/nextion/__init__.py | 6 +- esphome/components/nextion/display.py | 5 +- esphome/components/nextion/nextion.h | 41 ++++------- .../nextion/nextion_upload_arduino.cpp | 73 ++++++++----------- ...pload_idf.cpp => nextion_upload_esp32.cpp} | 55 ++++++++------ 5 files changed, 84 insertions(+), 96 deletions(-) rename esphome/components/nextion/{nextion_upload_idf.cpp => nextion_upload_esp32.cpp} (90%) diff --git a/esphome/components/nextion/__init__.py b/esphome/components/nextion/__init__.py index 8adc49d68c..38f449dc03 100644 --- a/esphome/components/nextion/__init__.py +++ b/esphome/components/nextion/__init__.py @@ -13,14 +13,16 @@ CONF_SEND_TO_NEXTION = "send_to_nextion" FILTER_SOURCE_FILES = filter_source_files_from_platform( { - "nextion_upload_arduino.cpp": { + "nextion_upload_esp32.cpp": { PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + "nextion_upload_arduino.cpp": { PlatformFramework.ESP8266_ARDUINO, PlatformFramework.RP2040_ARDUINO, PlatformFramework.BK72XX_ARDUINO, PlatformFramework.RTL87XX_ARDUINO, PlatformFramework.LN882X_ARDUINO, }, - "nextion_upload_idf.cpp": {PlatformFramework.ESP32_IDF}, } ) diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index ed6cd93027..b95df55a61 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -154,14 +154,11 @@ async def to_code(config): cg.add_define("USE_NEXTION_TFT_UPLOAD") cg.add(var.set_tft_url(config[CONF_TFT_URL])) if CORE.is_esp32: - if CORE.using_arduino: - cg.add_library("NetworkClientSecure", None) - cg.add_library("HTTPClient", None) esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True) esp32.add_idf_sdkconfig_option( "CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", True ) - elif CORE.is_esp8266 and CORE.using_arduino: + elif CORE.is_esp8266: cg.add_library("ESP8266HTTPClient", None) if CONF_TOUCH_SLEEP_TIMEOUT in config: diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 7e8f563a96..f4fc50ee7d 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -13,17 +13,12 @@ #include "esphome/components/display/display_color_utils.h" #ifdef USE_NEXTION_TFT_UPLOAD -#ifdef USE_ARDUINO #ifdef USE_ESP32 -#include -#endif // USE_ESP32 -#ifdef USE_ESP8266 +#include +#elif defined(USE_ESP8266) #include #include -#endif // USE_ESP8266 -#elif defined(USE_ESP_IDF) -#include -#endif // ARDUINO vs USE_ESP_IDF +#endif // USE_ESP32 vs USE_ESP8266 #endif // USE_NEXTION_TFT_UPLOAD namespace esphome { @@ -1078,7 +1073,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe #ifdef USE_NEXTION_TFT_UPLOAD /** - * Set the tft file URL. https seems problematic with Arduino.. + * Set the tft file URL. */ void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; } @@ -1422,16 +1417,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe uint32_t original_baud_rate_ = 0; bool upload_first_chunk_sent_ = false; -#ifdef USE_ARDUINO - /** - * will request chunk_size chunks from the web server - * and send each to the nextion - * @param HTTPClient http_client HTTP client handler. - * @param int range_start Position of next byte to transfer. - * @return position of last byte transferred, -1 for failure. - */ - int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start); -#elif defined(USE_ESP_IDF) +#ifdef USE_ESP32 /** * will request 4096 bytes chunks from the web server * and send each to Nextion @@ -1440,7 +1426,16 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * @return position of last byte transferred, -1 for failure. */ int upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &range_start); -#endif // USE_ARDUINO vs USE_ESP_IDF +#else + /** + * will request chunk_size chunks from the web server + * and send each to the nextion + * @param HTTPClient http_client HTTP client handler. + * @param int range_start Position of next byte to transfer. + * @return position of last byte transferred, -1 for failure. + */ + int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start); +#endif // USE_ESP32 vs others /** * Ends the upload process, restart Nextion and, if successful, @@ -1450,12 +1445,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe */ bool upload_end_(bool successful); - /** - * Returns the ESP Free Heap memory. This is framework independent. - * @return Free Heap in bytes. - */ - uint32_t get_free_heap_(); - #endif // USE_NEXTION_TFT_UPLOAD bool check_connect_(); diff --git a/esphome/components/nextion/nextion_upload_arduino.cpp b/esphome/components/nextion/nextion_upload_arduino.cpp index baea938729..dfbb5a497e 100644 --- a/esphome/components/nextion/nextion_upload_arduino.cpp +++ b/esphome/components/nextion/nextion_upload_arduino.cpp @@ -1,7 +1,7 @@ #include "nextion.h" #ifdef USE_NEXTION_TFT_UPLOAD -#ifdef USE_ARDUINO +#ifndef USE_ESP32 #include #include "esphome/components/network/util.h" @@ -10,10 +10,6 @@ #include "esphome/core/log.h" #include "esphome/core/util.h" -#ifdef USE_ESP32 -#include -#endif - namespace esphome { namespace nextion { static const char *const TAG = "nextion.upload.arduino"; @@ -21,23 +17,17 @@ static const char *const TAG = "nextion.upload.arduino"; // Followed guide // https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 -inline uint32_t Nextion::get_free_heap_() { -#if defined(USE_ESP32) - return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); -#elif defined(USE_ESP8266) - return EspClass::getFreeHeap(); -#endif // USE_ESP32 vs USE_ESP8266 -} - int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) { uint32_t range_size = this->tft_size_ - range_start; - ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_()); + ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap()); uint32_t range_end = ((upload_first_chunk_sent_ or this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1; ESP_LOGD(TAG, "Range start: %" PRIu32, range_start); if (range_size <= 0 or range_end <= range_start) { - ESP_LOGD(TAG, "Range end: %" PRIu32, range_end); - ESP_LOGD(TAG, "Range size: %" PRIu32, range_size); ESP_LOGE(TAG, "Invalid range"); + ESP_LOGD(TAG, + "Range end: %" PRIu32 "\n" + "Range size: %" PRIu32, + range_end, range_size); return -1; } @@ -95,14 +85,8 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) { this->recv_ret_string_(recv_string, upload_first_chunk_sent_ ? 500 : 5000, true); this->content_length_ -= read_len; const float upload_percentage = 100.0f * (this->tft_size_ - this->content_length_) / this->tft_size_; -#if defined(USE_ESP32) && defined(USE_PSRAM) - ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 "+%" PRIu32 ")", upload_percentage, - this->content_length_, static_cast(heap_caps_get_free_size(MALLOC_CAP_INTERNAL)), - static_cast(heap_caps_get_free_size(MALLOC_CAP_SPIRAM))); -#else ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 ")", upload_percentage, this->content_length_, - this->get_free_heap_()); -#endif + EspClass::getFreeHeap()); upload_first_chunk_sent_ = true; if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request ESP_LOGD(TAG, "Recv: [%s]", @@ -148,9 +132,11 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) { } bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { - ESP_LOGD(TAG, "TFT upload requested"); - ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse)); - ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str()); + ESP_LOGD(TAG, + "TFT upload requested\n" + "Exit reparse: %s\n" + "URL: %s", + YESNO(exit_reparse), this->tft_url_.c_str()); if (this->connection_state_.is_updating_) { ESP_LOGW(TAG, "Upload in progress"); @@ -180,15 +166,14 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); // Define the configuration for the HTTP client - ESP_LOGV(TAG, "Init HTTP client"); - ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_()); + ESP_LOGV(TAG, + "Init HTTP client\n" + "Heap: %" PRIu32, + EspClass::getFreeHeap()); HTTPClient http_client; http_client.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along bool begin_status = false; -#ifdef USE_ESP32 - begin_status = http_client.begin(this->tft_url_.c_str()); -#endif #ifdef USE_ESP8266 #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http_client.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); @@ -256,22 +241,24 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { this->send_command_("sleep=0"); this->send_command_("dim=100"); delay(250); // NOLINT - ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_()); + ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap()); App.feed_wdt(); char command[128]; // Tells the Nextion the content length of the tft file and baud rate it will be sent at // Once the Nextion accepts the command it will wait until the file is successfully uploaded // If it fails for any reason a power cycle of the display will be needed - sprintf(command, "whmi-wris %d,%d,1", this->content_length_, baud_rate); + snprintf(command, sizeof(command), "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, baud_rate); // Clear serial receive buffer ESP_LOGV(TAG, "Clear RX buffer"); this->reset_(false); delay(250); // NOLINT - ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_()); - ESP_LOGV(TAG, "Upload cmd: %s", command); + ESP_LOGV(TAG, + "Heap: %" PRIu32 "\n" + "Upload cmd: %s", + EspClass::getFreeHeap(), command); this->send_command_(command); if (baud_rate != this->original_baud_rate_) { @@ -290,7 +277,7 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { ESP_LOGD(TAG, "Upload resp: [%s] %zu B", format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str(), response.length()); - ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_()); + ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap()); if (response.find(0x05) != std::string::npos) { ESP_LOGV(TAG, "Upload prep done"); @@ -302,10 +289,12 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { return this->upload_end_(false); } - ESP_LOGD(TAG, "Upload TFT:"); - ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str()); - ESP_LOGD(TAG, " Size: %d bytes", this->content_length_); - ESP_LOGD(TAG, " Heap: %" PRIu32, this->get_free_heap_()); + ESP_LOGD(TAG, + "Upload TFT:\n" + " URL: %s\n" + " Size: %d bytes\n" + " Heap: %" PRIu32, + this->tft_url_.c_str(), this->content_length_, EspClass::getFreeHeap()); // Proceed with the content download as before @@ -322,7 +311,7 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { return this->upload_end_(false); } App.feed_wdt(); - ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, this->get_free_heap_(), this->content_length_); + ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, EspClass::getFreeHeap(), this->content_length_); } ESP_LOGD(TAG, "Upload complete"); @@ -356,5 +345,5 @@ WiFiClient *Nextion::get_wifi_client_() { } // namespace nextion } // namespace esphome -#endif // USE_ARDUINO +#endif // NOT USE_ESP32 #endif // USE_NEXTION_TFT_UPLOAD diff --git a/esphome/components/nextion/nextion_upload_idf.cpp b/esphome/components/nextion/nextion_upload_esp32.cpp similarity index 90% rename from esphome/components/nextion/nextion_upload_idf.cpp rename to esphome/components/nextion/nextion_upload_esp32.cpp index 942e3dd6c3..29a7e3c8d7 100644 --- a/esphome/components/nextion/nextion_upload_idf.cpp +++ b/esphome/components/nextion/nextion_upload_esp32.cpp @@ -1,7 +1,7 @@ #include "nextion.h" #ifdef USE_NEXTION_TFT_UPLOAD -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include #include @@ -14,7 +14,7 @@ namespace esphome { namespace nextion { -static const char *const TAG = "nextion.upload.idf"; +static const char *const TAG = "nextion.upload.esp32"; // Followed guide // https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 @@ -25,8 +25,10 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r uint32_t range_end = ((upload_first_chunk_sent_ or this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1; ESP_LOGD(TAG, "Range start: %" PRIu32, range_start); if (range_size <= 0 or range_end <= range_start) { - ESP_LOGD(TAG, "Range end: %" PRIu32, range_end); - ESP_LOGD(TAG, "Range size: %" PRIu32, range_size); + ESP_LOGD(TAG, + "Range end: %" PRIu32 "\n" + "Range size: %" PRIu32, + range_end, range_size); ESP_LOGE(TAG, "Invalid range"); return -1; } @@ -151,9 +153,11 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r } bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { - ESP_LOGD(TAG, "TFT upload requested"); - ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse)); - ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str()); + ESP_LOGD(TAG, + "TFT upload requested\n" + "Exit reparse: %s\n" + "URL: %s", + YESNO(exit_reparse), this->tft_url_.c_str()); if (this->connection_state_.is_updating_) { ESP_LOGW(TAG, "Upload in progress"); @@ -183,8 +187,10 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); // Define the configuration for the HTTP client - ESP_LOGV(TAG, "Init HTTP client"); - ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGV(TAG, + "Init HTTP client\n" + "Heap: %" PRIu32, + esp_get_free_heap_size()); esp_http_client_config_t config = { .url = this->tft_url_.c_str(), .cert_pem = nullptr, @@ -208,8 +214,10 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { } // Perform the HTTP request - ESP_LOGV(TAG, "Check connection"); - ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGV(TAG, + "Check connection\n" + "Heap: %" PRIu32, + esp_get_free_heap_size()); err = esp_http_client_perform(http_client); if (err != ESP_OK) { ESP_LOGE(TAG, "HTTP failed: %s", esp_err_to_name(err)); @@ -218,8 +226,10 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { } // Check the HTTP Status Code - ESP_LOGV(TAG, "Check status"); - ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGV(TAG, + "Check status\n" + "Heap: %" PRIu32, + esp_get_free_heap_size()); int status_code = esp_http_client_get_status_code(http_client); if (status_code != 200 && status_code != 206) { return this->upload_end_(false); @@ -255,7 +265,7 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { // Tells the Nextion the content length of the tft file and baud rate it will be sent at // Once the Nextion accepts the command it will wait until the file is successfully uploaded // If it fails for any reason a power cycle of the display will be needed - sprintf(command, "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, baud_rate); + snprintf(command, sizeof(command), "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, baud_rate); // Clear serial receive buffer ESP_LOGV(TAG, "Clear RX buffer"); @@ -300,10 +310,12 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { return this->upload_end_(false); } - ESP_LOGD(TAG, "Uploading TFT:"); - ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str()); - ESP_LOGD(TAG, " Size: %" PRIu32 " bytes", this->content_length_); - ESP_LOGD(TAG, " Heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGD(TAG, + "Uploading TFT:\n" + " URL: %s\n" + " Size: %" PRIu32 " bytes\n" + " Heap: %" PRIu32, + this->tft_url_.c_str(), this->content_length_, esp_get_free_heap_size()); // Proceed with the content download as before @@ -324,9 +336,8 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, esp_get_free_heap_size(), this->content_length_); } - ESP_LOGD(TAG, "TFT upload complete"); - - ESP_LOGD(TAG, "Close HTTP"); + ESP_LOGD(TAG, "TFT upload complete\n" + "Close HTTP"); esp_http_client_close(http_client); esp_http_client_cleanup(http_client); ESP_LOGV(TAG, "Connection closed"); @@ -336,5 +347,5 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { } // namespace nextion } // namespace esphome -#endif // USE_ESP_IDF +#endif // USE_ESP32 #endif // USE_NEXTION_TFT_UPLOAD From f32bb618ac29bbef674a7735f8a6ffe20bb23918 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Dec 2025 09:59:35 -0700 Subject: [PATCH 0689/1145] [esp32] Store preference keys as uint32_t, convert to string only at NVS boundary (#12494) --- esphome/components/esp32/preferences.cpp | 79 +++++++++++++----------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index 7bdbb265ca..e19a85e4e3 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -4,26 +4,28 @@ #include "esphome/core/log.h" #include "esphome/core/preferences.h" #include -#include #include -#include -#include +#include #include +#include namespace esphome { namespace esp32 { static const char *const TAG = "esp32.preferences"; +// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding +static constexpr size_t KEY_BUFFER_SIZE = 12; + struct NVSData { - std::string key; + uint32_t key; std::unique_ptr data; size_t len; void set_data(const uint8_t *src, size_t size) { - data = std::make_unique(size); - memcpy(data.get(), src, size); - len = size; + this->data = std::make_unique(size); + memcpy(this->data.get(), src, size); + this->len = size; } }; @@ -31,27 +33,27 @@ static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-n class ESP32PreferenceBackend : public ESPPreferenceBackend { public: - std::string key; + uint32_t key; uint32_t nvs_handle; bool save(const uint8_t *data, size_t len) override { // try find in pending saves and update that for (auto &obj : s_pending_save) { - if (obj.key == key) { + if (obj.key == this->key) { obj.set_data(data, len); return true; } } NVSData save{}; - save.key = key; + save.key = this->key; save.set_data(data, len); s_pending_save.emplace_back(std::move(save)); - ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %zu", key.c_str(), len); + ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); return true; } bool load(uint8_t *data, size_t len) override { // try find in pending saves and load from that for (auto &obj : s_pending_save) { - if (obj.key == key) { + if (obj.key == this->key) { if (obj.len != len) { // size mismatch return false; @@ -61,22 +63,24 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend { } } + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); size_t actual_len; - esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len); + esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len); if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key.c_str(), esp_err_to_name(err)); + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); return false; } if (actual_len != len) { ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); return false; } - err = nvs_get_blob(nvs_handle, key.c_str(), data, &len); + err = nvs_get_blob(this->nvs_handle, key_str, data, &len); if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key.c_str(), esp_err_to_name(err)); + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); return false; } else { - ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key.c_str(), len); + ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len); } return true; } @@ -103,14 +107,12 @@ class ESP32Preferences : public ESPPreferences { } } ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - return make_preference(length, type); + return this->make_preference(length, type); } ESPPreferenceObject make_preference(size_t length, uint32_t type) override { auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->nvs_handle = nvs_handle; - - uint32_t keyval = type; - pref->key = str_sprintf("%" PRIu32, keyval); + pref->nvs_handle = this->nvs_handle; + pref->key = type; return ESPPreferenceObject(pref); } @@ -123,17 +125,19 @@ class ESP32Preferences : public ESPPreferences { // goal try write all pending saves even if one fails int cached = 0, written = 0, failed = 0; esp_err_t last_err = ESP_OK; - std::string last_key{}; + uint32_t last_key = 0; // go through vector from back to front (makes erase easier/more efficient) for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) { const auto &save = s_pending_save[i]; - ESP_LOGVV(TAG, "Checking if NVS data %s has changed", save.key.c_str()); - if (is_changed(nvs_handle, save)) { - esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.get(), save.len); - ESP_LOGV(TAG, "sync: key: %s, len: %zu", save.key.c_str(), save.len); + ESP_LOGVV(TAG, "Checking if NVS data %" PRIu32 " has changed", save.key); + if (this->is_changed(this->nvs_handle, save)) { + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); + esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.get(), save.len); + ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len); if (err != 0) { - ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", save.key.c_str(), save.len, esp_err_to_name(err)); + ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.len, esp_err_to_name(err)); failed++; last_err = err; last_key = save.key; @@ -141,7 +145,7 @@ class ESP32Preferences : public ESPPreferences { } written++; } else { - ESP_LOGV(TAG, "NVS data not changed skipping %s len=%zu", save.key.c_str(), save.len); + ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.len); cached++; } s_pending_save.erase(s_pending_save.begin() + i); @@ -149,12 +153,12 @@ class ESP32Preferences : public ESPPreferences { ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, failed); if (failed > 0) { - ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%s", failed, esp_err_to_name(last_err), - last_key.c_str()); + ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err), + last_key); } // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes - esp_err_t err = nvs_commit(nvs_handle); + esp_err_t err = nvs_commit(this->nvs_handle); if (err != 0) { ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err)); return false; @@ -163,10 +167,13 @@ class ESP32Preferences : public ESPPreferences { return failed == 0; } bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) { + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, to_save.key); + size_t actual_len; - esp_err_t err = nvs_get_blob(nvs_handle, to_save.key.c_str(), nullptr, &actual_len); + esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len); if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", to_save.key.c_str(), esp_err_to_name(err)); + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); return true; } // Check size first before allocating memory @@ -174,9 +181,9 @@ class ESP32Preferences : public ESPPreferences { return true; } auto stored_data = std::make_unique(actual_len); - err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.get(), &actual_len); + err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len); if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err)); + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); return true; } return memcmp(to_save.data.get(), stored_data.get(), to_save.len) != 0; From 94763ebdabf614e13b844003212c9e0762622442 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Dec 2025 09:59:40 -0700 Subject: [PATCH 0690/1145] [libretiny] Store preference keys as uint32_t, convert to string only at FlashDB boundary (#12500) --- esphome/components/libretiny/preferences.cpp | 74 +++++++++++--------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index 871b186d8e..c21c5813a8 100644 --- a/esphome/components/libretiny/preferences.cpp +++ b/esphome/components/libretiny/preferences.cpp @@ -4,24 +4,27 @@ #include "esphome/core/log.h" #include "esphome/core/preferences.h" #include +#include #include #include -#include namespace esphome { namespace libretiny { static const char *const TAG = "lt.preferences"; +// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding +static constexpr size_t KEY_BUFFER_SIZE = 12; + struct NVSData { - std::string key; + uint32_t key; std::unique_ptr data; size_t len; void set_data(const uint8_t *src, size_t size) { - data = std::make_unique(size); - memcpy(data.get(), src, size); - len = size; + this->data = std::make_unique(size); + memcpy(this->data.get(), src, size); + this->len = size; } }; @@ -29,30 +32,30 @@ static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-n class LibreTinyPreferenceBackend : public ESPPreferenceBackend { public: - std::string key; + uint32_t key; fdb_kvdb_t db; fdb_blob_t blob; bool save(const uint8_t *data, size_t len) override { // try find in pending saves and update that for (auto &obj : s_pending_save) { - if (obj.key == key) { + if (obj.key == this->key) { obj.set_data(data, len); return true; } } NVSData save{}; - save.key = key; + save.key = this->key; save.set_data(data, len); s_pending_save.emplace_back(std::move(save)); - ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %zu", key.c_str(), len); + ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); return true; } bool load(uint8_t *data, size_t len) override { // try find in pending saves and load from that for (auto &obj : s_pending_save) { - if (obj.key == key) { + if (obj.key == this->key) { if (obj.len != len) { // size mismatch return false; @@ -62,13 +65,15 @@ class LibreTinyPreferenceBackend : public ESPPreferenceBackend { } } - fdb_blob_make(blob, data, len); - size_t actual_len = fdb_kv_get_blob(db, key.c_str(), blob); + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); + fdb_blob_make(this->blob, data, len); + size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob); if (actual_len != len) { ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); return false; } else { - ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key.c_str(), len); + ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key_str, len); } return true; } @@ -90,16 +95,14 @@ class LibreTinyPreferences : public ESPPreferences { } ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - return make_preference(length, type); + return this->make_preference(length, type); } ESPPreferenceObject make_preference(size_t length, uint32_t type) override { auto *pref = new LibreTinyPreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->db = &db; - pref->blob = &blob; - - uint32_t keyval = type; - pref->key = str_sprintf("%u", keyval); + pref->db = &this->db; + pref->blob = &this->blob; + pref->key = type; return ESPPreferenceObject(pref); } @@ -112,18 +115,20 @@ class LibreTinyPreferences : public ESPPreferences { // goal try write all pending saves even if one fails int cached = 0, written = 0, failed = 0; fdb_err_t last_err = FDB_NO_ERR; - std::string last_key{}; + uint32_t last_key = 0; // go through vector from back to front (makes erase easier/more efficient) for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) { const auto &save = s_pending_save[i]; - ESP_LOGVV(TAG, "Checking if FDB data %s has changed", save.key.c_str()); - if (is_changed(&db, save)) { - ESP_LOGV(TAG, "sync: key: %s, len: %zu", save.key.c_str(), save.len); - fdb_blob_make(&blob, save.data.get(), save.len); - fdb_err_t err = fdb_kv_set_blob(&db, save.key.c_str(), &blob); + ESP_LOGVV(TAG, "Checking if FDB data %" PRIu32 " has changed", save.key); + if (this->is_changed(&this->db, save)) { + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); + ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len); + fdb_blob_make(&this->blob, save.data.get(), save.len); + fdb_err_t err = fdb_kv_set_blob(&this->db, key_str, &this->blob); if (err != FDB_NO_ERR) { - ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", save.key.c_str(), save.len, err); + ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", key_str, save.len, err); failed++; last_err = err; last_key = save.key; @@ -131,7 +136,7 @@ class LibreTinyPreferences : public ESPPreferences { } written++; } else { - ESP_LOGD(TAG, "FDB data not changed; skipping %s len=%zu", save.key.c_str(), save.len); + ESP_LOGD(TAG, "FDB data not changed; skipping %" PRIu32 " len=%zu", save.key, save.len); cached++; } s_pending_save.erase(s_pending_save.begin() + i); @@ -139,17 +144,20 @@ class LibreTinyPreferences : public ESPPreferences { ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, failed); if (failed > 0) { - ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%s", failed, last_err, last_key.c_str()); + ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%" PRIu32, failed, last_err, last_key); } return failed == 0; } bool is_changed(const fdb_kvdb_t db, const NVSData &to_save) { + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, to_save.key); + struct fdb_kv kv; - fdb_kv_t kvp = fdb_kv_get_obj(db, to_save.key.c_str(), &kv); + fdb_kv_t kvp = fdb_kv_get_obj(db, key_str, &kv); if (kvp == nullptr) { - ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str()); + ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", key_str); return true; } @@ -160,10 +168,10 @@ class LibreTinyPreferences : public ESPPreferences { // Allocate buffer on heap to avoid stack allocation for large data auto stored_data = std::make_unique(kv.value_len); - fdb_blob_make(&blob, stored_data.get(), kv.value_len); - size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob); + fdb_blob_make(&this->blob, stored_data.get(), kv.value_len); + size_t actual_len = fdb_kv_get_blob(db, key_str, &this->blob); if (actual_len != kv.value_len) { - ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", to_save.key.c_str(), actual_len, kv.value_len); + ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", key_str, actual_len, kv.value_len); return true; } From 42e061c9aeb39145e82d66dc84a58d711aa6ab64 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Dec 2025 10:00:19 -0700 Subject: [PATCH 0691/1145] [text] Avoid string copies in callbacks by passing const ref (#12504) --- esphome/components/text/text.cpp | 2 +- esphome/components/text/text.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index 933d82c85c..d06c350832 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -23,7 +23,7 @@ void Text::publish_state(const std::string &state) { #endif } -void Text::add_on_state_callback(std::function &&callback) { +void Text::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } diff --git a/esphome/components/text/text.h b/esphome/components/text/text.h index 74d08eda8a..f24464cb20 100644 --- a/esphome/components/text/text.h +++ b/esphome/components/text/text.h @@ -31,7 +31,7 @@ class Text : public EntityBase { /// Instantiate a TextCall object to modify this text component's state. TextCall make_call() { return TextCall(this); } - void add_on_state_callback(std::function &&callback); + void add_on_state_callback(std::function &&callback); protected: friend class TextCall; @@ -44,7 +44,7 @@ class Text : public EntityBase { */ virtual void control(const std::string &value) = 0; - CallbackManager state_callback_; + CallbackManager state_callback_; }; } // namespace text From 0e71fa97a70d1768682a54e004b30f5c269a3cf2 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 17 Dec 2025 12:18:25 -0500 Subject: [PATCH 0692/1145] [spi] Add SPIInterface stub for clang-tidy on unsupported platforms (#12532) Co-authored-by: Claude --- esphome/components/spi/spi.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 43b55d72bc..256cbcc65f 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -1,5 +1,4 @@ #pragma once -#ifndef USE_ZEPHYR #include "esphome/core/application.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" @@ -24,6 +23,10 @@ using SPIInterface = SPIClassRP2040 *; using SPIInterface = SPIClass *; #endif +#elif defined(CLANG_TIDY) + +using SPIInterface = void *; // Stub for platforms without SPI (e.g., Zephyr) + #endif // USE_ESP32 / USE_ARDUINO /** @@ -503,4 +506,3 @@ class SPIDevice : public SPIClient { }; } // namespace esphome::spi -#endif // USE_ZEPHYR From d7b04a3d18a94dc77c2cdf4a9097f7557e8e9e44 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:59:49 -0500 Subject: [PATCH 0693/1145] [nextion] Fix clang-tidy error on Zephyr for HTTPClient (#12538) Co-authored-by: Claude --- esphome/components/nextion/nextion.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index f4fc50ee7d..331e901578 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -1426,7 +1426,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * @return position of last byte transferred, -1 for failure. */ int upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &range_start); -#else +#elif defined(USE_ARDUINO) /** * will request chunk_size chunks from the web server * and send each to the nextion @@ -1435,7 +1435,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * @return position of last byte transferred, -1 for failure. */ int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start); -#endif // USE_ESP32 vs others +#endif // USE_ESP32 vs USE_ARDUINO /** * Ends the upload process, restart Nextion and, if successful, From f9720026d0aa7c102de65f14856efb3138bcb43b Mon Sep 17 00:00:00 2001 From: Anna Oake Date: Wed, 17 Dec 2025 20:19:18 +0100 Subject: [PATCH 0694/1145] [cc1101] Fix default frequencies (#12539) --- esphome/components/cc1101/cc1101.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 5b6eb545bc..1fe402d6c6 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -99,11 +99,11 @@ CC1101Component::CC1101Component() { this->state_.FS_AUTOCAL = 1; // Default Settings - this->set_frequency(433920); - this->set_if_frequency(153); - this->set_filter_bandwidth(203); + this->set_frequency(433920000); + this->set_if_frequency(153000); + this->set_filter_bandwidth(203000); this->set_channel(0); - this->set_channel_spacing(200); + this->set_channel_spacing(200000); this->set_symbol_rate(5000); this->set_sync_mode(SyncMode::SYNC_MODE_NONE); this->set_carrier_sense_above_threshold(true); From b02696edc09996e7420563a22094a3b80bcf9add Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Wed, 17 Dec 2025 20:40:31 +0000 Subject: [PATCH 0695/1145] [pm1006] Fix "never" update interval detection (#12529) --- esphome/components/pm1006/sensor.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/esphome/components/pm1006/sensor.py b/esphome/components/pm1006/sensor.py index c693cfea19..8ff21ab069 100644 --- a/esphome/components/pm1006/sensor.py +++ b/esphome/components/pm1006/sensor.py @@ -7,10 +7,10 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, DEVICE_CLASS_PM25, ICON_BLUR, + SCHEDULER_DONT_RUN, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ) -from esphome.core import TimePeriodMilliseconds CODEOWNERS = ["@habbie"] DEPENDENCIES = ["uart"] @@ -41,16 +41,12 @@ CONFIG_SCHEMA = cv.All( def validate_interval_uart(config): - require_tx = False - interval = config.get(CONF_UPDATE_INTERVAL) - - if isinstance(interval, TimePeriodMilliseconds): - # 'never' is encoded as a very large int, not as a TimePeriodMilliseconds objects - require_tx = True - uart.final_validate_device_schema( - "pm1006", baud_rate=9600, require_rx=True, require_tx=require_tx + "pm1006", + baud_rate=9600, + require_rx=True, + require_tx=interval.total_milliseconds != SCHEDULER_DONT_RUN, )(config) From 3d673ac55e571624620b72fb4a3b9e934ea7670b Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:13:18 -0500 Subject: [PATCH 0696/1145] [ci] Check changed headers in clang-tidy when using --changed (#12540) Co-authored-by: Claude --- script/clang-tidy | 26 +++++++++++++++++++------- script/helpers.py | 29 ++++++++++++++++------------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/script/clang-tidy b/script/clang-tidy index 142b616119..17bcafacc7 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -253,19 +253,31 @@ def main(): print(f"Split {args.split_at}/{args.split_num}: checking {len(files)} files") # Print file count before adding header file - print(f"\nTotal files to check: {len(files)}") + print(f"\nTotal cpp files to check: {len(files)}") + + # Add header file for checking (before early exit check) + if args.all_headers and args.split_at in (None, 1): + # When --changed is used, only include changed headers instead of all headers + if args.changed: + all_headers = [ + os.path.relpath(p, cwd) for p in git_ls_files(["esphome/**/*.h"]) + ] + changed_headers = filter_changed(all_headers) + if changed_headers: + build_all_include(changed_headers) + files.insert(0, temp_header_file) + else: + print("No changed headers to check") + else: + build_all_include() + files.insert(0, temp_header_file) + print(f"Added all-include header file, new total: {len(files)}") # Early exit if no files to check if not files: print("No files to check - exiting early") return 0 - # Only build header file if we have actual files to check - if args.all_headers and args.split_at in (None, 1): - build_all_include() - files.insert(0, temp_header_file) - print(f"Added all-include header file, new total: {len(files)}") - # Print final file list before loading idedata print_file_list(files, "Final files to process:") diff --git a/script/helpers.py b/script/helpers.py index 06a50a3092..202ac9b5fc 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -156,22 +156,25 @@ def print_error_for_file(file: str | Path, body: str | None) -> None: print() -def build_all_include() -> None: - # Build a cpp file that includes all header files in this repo. - # Otherwise header-only integrations would not be tested by clang-tidy +def build_all_include(header_files: list[str] | None = None) -> None: + # Build a cpp file that includes header files for clang-tidy to check. + # If header_files is provided, only include those headers. + # Otherwise, include all header files in the esphome directory. - # Use git ls-files to find all .h files in the esphome directory - # This is much faster than walking the filesystem - cmd = ["git", "ls-files", "esphome/**/*.h"] - proc = subprocess.run(cmd, capture_output=True, text=True, check=True) + if header_files is None: + # Use git ls-files to find all .h files in the esphome directory + # This is much faster than walking the filesystem + cmd = ["git", "ls-files", "esphome/**/*.h"] + proc = subprocess.run(cmd, capture_output=True, text=True, check=True) - # Process git output - git already returns paths relative to repo root - headers = [ - f'#include "{include_p}"' - for line in proc.stdout.strip().split("\n") - if (include_p := line.replace(os.path.sep, "/")) - ] + # Process git output - git already returns paths relative to repo root + header_files = [ + line.replace(os.path.sep, "/") + for line in proc.stdout.strip().split("\n") + if line + ] + headers = [f'#include "{h}"' for h in header_files] headers.sort() headers.append("") content = "\n".join(headers) From dc8f7abce2d0dea4458f8e0c1d52da1e20fb831c Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:07:42 -0500 Subject: [PATCH 0697/1145] [bme68x_bsec2_i2c] Add MULTI_CONF support for multiple sensors (#12535) Co-authored-by: Claude --- esphome/components/bme68x_bsec2_i2c/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/bme68x_bsec2_i2c/__init__.py b/esphome/components/bme68x_bsec2_i2c/__init__.py index d6fb7fa9be..c8ca0ba022 100644 --- a/esphome/components/bme68x_bsec2_i2c/__init__.py +++ b/esphome/components/bme68x_bsec2_i2c/__init__.py @@ -11,6 +11,7 @@ CODEOWNERS = ["@neffs", "@kbx81"] AUTO_LOAD = ["bme68x_bsec2"] DEPENDENCIES = ["i2c"] +MULTI_CONF = True bme68x_bsec2_i2c_ns = cg.esphome_ns.namespace("bme68x_bsec2_i2c") BME68xBSEC2I2CComponent = bme68x_bsec2_i2c_ns.class_( From 91c504061b90ca64c21d42ce26f833a7ec12d9b5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Dec 2025 15:19:26 -0700 Subject: [PATCH 0698/1145] [select] Eliminate string allocation in state callbacks (#12505) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/copy/select/copy_select.cpp | 2 +- esphome/components/mqtt/mqtt_select.cpp | 3 +-- esphome/components/select/automation.h | 8 ++++++-- esphome/components/select/select.cpp | 5 ++--- esphome/components/select/select.h | 4 ++-- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/esphome/components/copy/select/copy_select.cpp b/esphome/components/copy/select/copy_select.cpp index e45338e785..e85e08e353 100644 --- a/esphome/components/copy/select/copy_select.cpp +++ b/esphome/components/copy/select/copy_select.cpp @@ -7,7 +7,7 @@ namespace copy { static const char *const TAG = "copy.select"; void CopySelect::setup() { - source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(index); }); + source_->add_on_state_callback([this](size_t index) { this->publish_state(index); }); traits.set_options(source_->traits.get_options()); diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index e1660b07ea..e48af980c8 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -21,8 +21,7 @@ void MQTTSelectComponent::setup() { call.set_option(state); call.perform(); }); - this->select_->add_on_state_callback( - [this](const std::string &state, size_t index) { this->publish_state(this->select_->option_at(index)); }); + this->select_->add_on_state_callback([this](size_t index) { this->publish_state(this->select_->option_at(index)); }); } void MQTTSelectComponent::dump_config() { diff --git a/esphome/components/select/automation.h b/esphome/components/select/automation.h index 768f2621f7..dda5403557 100644 --- a/esphome/components/select/automation.h +++ b/esphome/components/select/automation.h @@ -8,9 +8,13 @@ namespace esphome::select { class SelectStateTrigger : public Trigger { public: - explicit SelectStateTrigger(Select *parent) { - parent->add_on_state_callback([this](const std::string &value, size_t index) { this->trigger(value, index); }); + explicit SelectStateTrigger(Select *parent) : parent_(parent) { + parent->add_on_state_callback( + [this](size_t index) { this->trigger(std::string(this->parent_->option_at(index)), index); }); } + + protected: + Select *parent_; }; template class SelectSetAction : public Action { diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 4fc4d79b08..28d7eb07d4 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -32,8 +32,7 @@ void Select::publish_state(size_t index) { this->state = option; // Update deprecated member for backward compatibility #pragma GCC diagnostic pop ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", this->get_name().c_str(), option, index); - // Callback signature requires std::string, create temporary for compatibility - this->state_callback_.call(std::string(option), index); + this->state_callback_.call(index); #if defined(USE_SELECT) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_select_update(this); #endif @@ -41,7 +40,7 @@ void Select::publish_state(size_t index) { const char *Select::current_option() const { return this->has_state() ? this->option_at(this->active_index_) : ""; } -void Select::add_on_state_callback(std::function &&callback) { +void Select::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index 63707f6bd6..854fdcf252 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -75,7 +75,7 @@ class Select : public EntityBase { /// Return the option value at the provided index offset (as const char* from flash). const char *option_at(size_t index) const; - void add_on_state_callback(std::function &&callback); + void add_on_state_callback(std::function &&callback); protected: friend class SelectCall; @@ -111,7 +111,7 @@ class Select : public EntityBase { } } - CallbackManager state_callback_; + CallbackManager state_callback_; }; } // namespace esphome::select From 4ddaff4027fc8c8cb4e39957087915204922a1da Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:26:56 -0500 Subject: [PATCH 0699/1145] [esp32] Dynamically embed managed component server certificates (#12509) Co-authored-by: Claude Co-authored-by: J. Nick Koston --- esphome/components/esp32/__init__.py | 9 --------- esphome/components/esp32/post_build.py.script | 12 ++++++++++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 4448b6bbe7..dc442cfbd2 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -982,15 +982,6 @@ async def to_code(config): cg.add_platformio_option("framework", "arduino, espidf") cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") - cg.add_platformio_option( - "board_build.embed_txtfiles", - [ - "managed_components/espressif__esp_insights/server_certs/https_server.crt", - "managed_components/espressif__esp_rainmaker/server_certs/rmaker_mqtt_server.crt", - "managed_components/espressif__esp_rainmaker/server_certs/rmaker_claim_service_server.crt", - "managed_components/espressif__esp_rainmaker/server_certs/rmaker_ota_server.crt", - ], - ) cg.add_define( "USE_ARDUINO_VERSION_CODE", cg.RawExpression( diff --git a/esphome/components/esp32/post_build.py.script b/esphome/components/esp32/post_build.py.script index c995214232..5ef5860687 100644 --- a/esphome/components/esp32/post_build.py.script +++ b/esphome/components/esp32/post_build.py.script @@ -5,6 +5,7 @@ import json # noqa: E402 import os # noqa: E402 import pathlib # noqa: E402 import shutil # noqa: E402 +from glob import glob # noqa: E402 def merge_factory_bin(source, target, env): @@ -126,3 +127,14 @@ def esp32_copy_ota_bin(source, target, env): # Run merge first, then ota copy second env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) # noqa: F821 env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) # noqa: F821 + +# Find server certificates in managed components and generate .S files. +# Workaround for PlatformIO not processing target_add_binary_data() from managed component CMakeLists. +project_dir = env.subst("$PROJECT_DIR") +managed_components = os.path.join(project_dir, "managed_components") +if os.path.isdir(managed_components): + for cert_file in glob(os.path.join(managed_components, "**/server_certs/*.crt"), recursive=True): + try: + env.FileToAsm(cert_file, FILE_TYPE="TEXT") + except Exception as e: + print(f"Error processing {os.path.basename(cert_file)}: {e}") From 2b337aa3062b60fe7418540364052f88a86e006f Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:37:59 -0500 Subject: [PATCH 0700/1145] [esp32_camera] Fix I2C driver conflict with other components (#12533) Co-authored-by: Claude Co-authored-by: J. Nick Koston --- esphome/components/esp32_camera/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 4182683bdc..ca37cb392d 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -3,7 +3,7 @@ import logging from esphome import automation, pins import esphome.codegen as cg from esphome.components import i2c -from esphome.components.esp32 import add_idf_component +from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option from esphome.components.psram import DOMAIN as psram_domain import esphome.config_validation as cv from esphome.const import ( @@ -352,6 +352,8 @@ async def to_code(config): cg.add_define("USE_CAMERA") add_idf_component(name="espressif/esp32-camera", ref="2.1.1") + add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW", True) + add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY", False) for conf in config.get(CONF_ON_STREAM_START, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) From 9de7df7b5b3c142be008d38ef5a5e24e3b08cc71 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Thu, 18 Dec 2025 00:06:52 +0000 Subject: [PATCH 0701/1145] Add build info to image (#12425) Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/__main__.py | 39 + esphome/components/api/api_connection.cpp | 5 +- esphome/components/mqtt/mqtt_component.cpp | 10 +- esphome/components/sen5x/sen5x.cpp | 7 +- esphome/components/sgp30/sgp30.cpp | 6 +- esphome/components/sgp4x/sgp4x.cpp | 7 +- .../version/version_text_sensor.cpp | 26 +- esphome/components/wifi/wifi_component.cpp | 2 +- esphome/core/__init__.py | 18 + esphome/core/application.cpp | 16 +- esphome/core/application.h | 35 +- esphome/core/build_info_data.h | 10 + esphome/core/config.py | 1 - esphome/core/helpers.cpp | 11 - esphome/core/helpers.h | 12 +- esphome/core/progmem.h | 2 + esphome/helpers.py | 11 +- esphome/writer.py | 109 ++- esphome/yaml_util.py | 12 +- tests/dummy_main.cpp | 2 +- tests/integration/fixtures/build_info.yaml | 31 + tests/integration/test_build_info.py | 117 +++ tests/unit_tests/conftest.py | 1 + tests/unit_tests/core/test_config.py | 71 ++ tests/unit_tests/test_main.py | 197 +++++ tests/unit_tests/test_writer.py | 725 ++++++++++++++++++ tests/unit_tests/test_yaml_util.py | 28 + 27 files changed, 1458 insertions(+), 53 deletions(-) create mode 100644 esphome/core/build_info_data.h create mode 100644 tests/integration/fixtures/build_info.yaml create mode 100644 tests/integration/test_build_info.py diff --git a/esphome/__main__.py b/esphome/__main__.py index 55fbbc6c8a..119ab957a3 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -518,10 +518,49 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int: rc = platformio_api.run_compile(config, CORE.verbose) if rc != 0: return rc + + # Check if firmware was rebuilt and emit build_info + create manifest + _check_and_emit_build_info() + idedata = platformio_api.get_idedata(config) return 0 if idedata is not None else 1 +def _check_and_emit_build_info() -> None: + """Check if firmware was rebuilt and emit build_info.""" + import json + + firmware_path = CORE.firmware_bin + build_info_json_path = CORE.relative_build_path("build_info.json") + + # Check if both files exist + if not firmware_path.exists() or not build_info_json_path.exists(): + return + + # Check if firmware is newer than build_info (indicating a relink occurred) + if firmware_path.stat().st_mtime <= build_info_json_path.stat().st_mtime: + return + + # Read build_info from JSON + try: + with open(build_info_json_path, encoding="utf-8") as f: + build_info = json.load(f) + except (OSError, json.JSONDecodeError) as e: + _LOGGER.debug("Failed to read build_info: %s", e) + return + + config_hash = build_info.get("config_hash") + build_time_str = build_info.get("build_time_str") + + if config_hash is None or build_time_str is None: + return + + # Emit build_info with human-readable time + _LOGGER.info( + "Build Info: config_hash=0x%08x build_time_str=%s", config_hash, build_time_str + ) + + def upload_using_esptool( config: ConfigType, port: str, file: str, speed: int ) -> str | int: diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5186e5afda..85f4566f3c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1472,7 +1472,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { resp.set_esphome_version(ESPHOME_VERSION_REF); - resp.set_compilation_time(App.get_compilation_time_ref()); + // Stack buffer for build time string + char build_time_str[Application::BUILD_TIME_STR_SIZE]; + App.get_build_time_string(build_time_str); + resp.set_compilation_time(StringRef(build_time_str)); // Manufacturer string - define once, handle ESP8266 PROGMEM separately #if defined(USE_ESP8266) || defined(USE_ESP32) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 5d2bedae79..9db1b1f7c8 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -154,7 +154,15 @@ bool MQTTComponent::send_discovery_() { device_info[MQTT_DEVICE_MANUFACTURER] = model == nullptr ? ESPHOME_PROJECT_NAME : std::string(ESPHOME_PROJECT_NAME, model - ESPHOME_PROJECT_NAME); #else - device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_VERSION " (" + App.get_compilation_time_ref() + ")"; + static const char ver_fmt[] PROGMEM = ESPHOME_VERSION " (config hash 0x%08" PRIx32 ")"; +#ifdef USE_ESP8266 + char fmt_buf[sizeof(ver_fmt)]; + strcpy_P(fmt_buf, ver_fmt); + const char *fmt = fmt_buf; +#else + const char *fmt = ver_fmt; +#endif + device_info[MQTT_DEVICE_SW_VERSION] = str_sprintf(fmt, App.get_config_hash()); device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD; #if defined(USE_ESP8266) || defined(USE_ESP32) device_info[MQTT_DEVICE_MANUFACTURER] = "Espressif"; diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index ffb9e2bc02..c72ccf2595 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -1,4 +1,5 @@ #include "sen5x.h" +#include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -154,10 +155,10 @@ void SEN5XComponent::setup() { if (this->voc_sensor_ && this->store_baseline_) { uint32_t combined_serial = encode_uint24(this->serial_number_[0], this->serial_number_[1], this->serial_number_[2]); - // Hash with compilation time and serial number + // Hash with config hash, version, and serial number // This ensures the baseline storage is cleared after OTA - // Serial numbers are unique to each sensor, so mulitple sensors can be used without conflict - uint32_t hash = fnv1_hash(App.get_compilation_time_ref() + std::to_string(combined_serial)); + // Serial numbers are unique to each sensor, so multiple sensors can be used without conflict + uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), std::to_string(combined_serial)); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index fa548ce94e..1326356437 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -72,10 +72,10 @@ void SGP30Component::setup() { return; } - // Hash with compilation time and serial number + // Hash with config hash, version, and serial number // This ensures the baseline storage is cleared after OTA - // Serial numbers are unique to each sensor, so mulitple sensors can be used without conflict - uint32_t hash = fnv1_hash(App.get_compilation_time_ref() + std::to_string(this->serial_number_)); + // Serial numbers are unique to each sensor, so multiple sensors can be used without conflict + uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), std::to_string(this->serial_number_)); this->pref_ = global_preferences->make_preference(hash, true); if (this->store_baseline_ && this->pref_.load(&this->baselines_storage_)) { diff --git a/esphome/components/sgp4x/sgp4x.cpp b/esphome/components/sgp4x/sgp4x.cpp index a0c957d608..7c0f51c782 100644 --- a/esphome/components/sgp4x/sgp4x.cpp +++ b/esphome/components/sgp4x/sgp4x.cpp @@ -1,4 +1,5 @@ #include "sgp4x.h" +#include "esphome/core/application.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" #include @@ -56,10 +57,10 @@ void SGP4xComponent::setup() { ESP_LOGD(TAG, "Version 0x%0X", featureset); if (this->store_baseline_) { - // Hash with compilation time and serial number + // Hash with config hash, version, and serial number // This ensures the baseline storage is cleared after OTA - // Serial numbers are unique to each sensor, so mulitple sensors can be used without conflict - uint32_t hash = fnv1_hash(App.get_compilation_time_ref() + std::to_string(this->serial_number_)); + // Serial numbers are unique to each sensor, so multiple sensors can be used without conflict + uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), std::to_string(this->serial_number_)); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { diff --git a/esphome/components/version/version_text_sensor.cpp b/esphome/components/version/version_text_sensor.cpp index 78d0fb501b..584b8abfb2 100644 --- a/esphome/components/version/version_text_sensor.cpp +++ b/esphome/components/version/version_text_sensor.cpp @@ -1,8 +1,9 @@ #include "version_text_sensor.h" -#include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/log.h" #include "esphome/core/version.h" #include "esphome/core/helpers.h" +#include "esphome/core/progmem.h" namespace esphome { namespace version { @@ -10,11 +11,26 @@ namespace version { static const char *const TAG = "version.text_sensor"; void VersionTextSensor::setup() { - if (this->hide_timestamp_) { - this->publish_state(ESPHOME_VERSION); - } else { - this->publish_state(str_sprintf(ESPHOME_VERSION " %s", App.get_compilation_time_ref().c_str())); + static const char PREFIX[] PROGMEM = ESPHOME_VERSION " (config hash 0x"; + static const char BUILT_STR[] PROGMEM = ", built "; + // Buffer size: PREFIX + 8 hex chars + BUILT_STR + BUILD_TIME_STR_SIZE + ")" + null + constexpr size_t buf_size = sizeof(PREFIX) + 8 + sizeof(BUILT_STR) + esphome::Application::BUILD_TIME_STR_SIZE + 2; + char version_str[buf_size]; + + ESPHOME_strncpy_P(version_str, PREFIX, sizeof(version_str)); + + size_t len = strlen(version_str); + snprintf(version_str + len, sizeof(version_str) - len, "%08" PRIx32, App.get_config_hash()); + + if (!this->hide_timestamp_) { + size_t len = strlen(version_str); + ESPHOME_strncat_P(version_str, BUILT_STR, sizeof(version_str) - len - 1); + ESPHOME_strncat_P(version_str, ESPHOME_BUILD_TIME_STR, sizeof(version_str) - strlen(version_str) - 1); } + + strncat(version_str, ")", sizeof(version_str) - strlen(version_str) - 1); + version_str[sizeof(version_str) - 1] = '\0'; + this->publish_state(version_str); } float VersionTextSensor::get_setup_priority() const { return setup_priority::DATA; } void VersionTextSensor::set_hide_timestamp(bool hide_timestamp) { this->hide_timestamp_ = hide_timestamp; } diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index a5e8c4a59d..a550aa679d 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -375,7 +375,7 @@ void WiFiComponent::start() { get_mac_address_pretty_into_buffer(mac_s)); this->last_connected_ = millis(); - uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time_ref().c_str()) : 88491487UL; + uint32_t hash = this->has_sta() ? App.get_config_version_hash() : 88491487UL; this->pref_ = global_preferences->make_preference(hash, true); #ifdef USE_WIFI_FAST_CONNECT diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 721cd5787d..ad9844a3bf 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -608,6 +608,8 @@ class EsphomeCore: self.current_component: str | None = None # Address cache for DNS and mDNS lookups from command line arguments self.address_cache: AddressCache | None = None + # Cached config hash (computed lazily) + self._config_hash: int | None = None def reset(self): from esphome.pins import PIN_SCHEMA_REGISTRY @@ -636,6 +638,7 @@ class EsphomeCore: self.unique_ids = {} self.current_component = None self.address_cache = None + self._config_hash = None PIN_SCHEMA_REGISTRY.reset() @contextmanager @@ -685,6 +688,21 @@ class EsphomeCore: return None + @property + def config_hash(self) -> int: + """Get the FNV-1a 32-bit hash of the config. + + The hash is computed lazily and cached for performance. + Uses sort_keys=True to ensure deterministic ordering. + """ + if self._config_hash is None: + from esphome import yaml_util + from esphome.helpers import fnv1a_32bit_hash + + config_str = yaml_util.dump(self.config, show_secrets=True, sort_keys=True) + self._config_hash = fnv1a_32bit_hash(config_str) + return self._config_hash + @property def config_dir(self) -> Path: if self.config_path.is_dir(): diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index a85d671a07..4c9cc6b2b6 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -1,5 +1,12 @@ #include "esphome/core/application.h" +#include "esphome/core/build_info_data.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" +#include + +#ifdef USE_ESP8266 +#include +#endif #include "esphome/core/version.h" #include "esphome/core/hal.h" #include @@ -191,7 +198,9 @@ void Application::loop() { if (this->dump_config_at_ < this->components_.size()) { if (this->dump_config_at_ == 0) { - ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", this->compilation_time_); + char build_time_str[Application::BUILD_TIME_STR_SIZE]; + this->get_build_time_string(build_time_str); + ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", build_time_str); #ifdef ESPHOME_PROJECT_NAME ESP_LOGI(TAG, "Project " ESPHOME_PROJECT_NAME " version " ESPHOME_PROJECT_VERSION); #endif @@ -711,4 +720,9 @@ void Application::wake_loop_threadsafe() { } #endif // defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) +void Application::get_build_time_string(std::span buffer) { + ESPHOME_strncpy_P(buffer.data(), ESPHOME_BUILD_TIME_STR, buffer.size()); + buffer[buffer.size() - 1] = '\0'; +} + } // namespace esphome diff --git a/esphome/core/application.h b/esphome/core/application.h index 8e2035b7c5..f462553a81 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -1,9 +1,12 @@ #pragma once #include +#include #include +#include #include #include +#include "esphome/core/build_info_data.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/hal.h" @@ -11,6 +14,7 @@ #include "esphome/core/preferences.h" #include "esphome/core/scheduler.h" #include "esphome/core/string_ref.h" +#include "esphome/core/version.h" #ifdef USE_DEVICES #include "esphome/core/device.h" @@ -101,7 +105,7 @@ static const uint32_t TEARDOWN_TIMEOUT_REBOOT_MS = 1000; // 1 second for quick class Application { public: void pre_setup(const std::string &name, const std::string &friendly_name, const char *comment, - const char *compilation_time, bool name_add_mac_suffix) { + bool name_add_mac_suffix) { arch_init(); this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { @@ -121,7 +125,6 @@ class Application { this->friendly_name_ = friendly_name; } this->comment_ = comment; - this->compilation_time_ = compilation_time; } #ifdef USE_DEVICES @@ -261,9 +264,30 @@ class Application { bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; } - std::string get_compilation_time() const { return this->compilation_time_; } - /// Get the compilation time as StringRef (for API usage) - StringRef get_compilation_time_ref() const { return StringRef(this->compilation_time_); } + /// Size of buffer required for build time string (including null terminator) + static constexpr size_t BUILD_TIME_STR_SIZE = 26; + + /// Get the config hash as a 32-bit integer + constexpr uint32_t get_config_hash() { return ESPHOME_CONFIG_HASH; } + + /// Get the config hash extended with ESPHome version + constexpr uint32_t get_config_version_hash() { return fnv1a_hash_extend(ESPHOME_CONFIG_HASH, ESPHOME_VERSION); } + + /// Get the build time as a Unix timestamp + constexpr time_t get_build_time() { return ESPHOME_BUILD_TIME; } + + /// Copy the build time string into the provided buffer + /// Buffer must be BUILD_TIME_STR_SIZE bytes (compile-time enforced) + void get_build_time_string(std::span buffer); + + /// Get the build time as a string (deprecated, use get_build_time_string() instead) + // Remove before 2026.7.0 + ESPDEPRECATED("Use get_build_time_string() instead. Removed in 2026.7.0", "2026.1.0") + std::string get_compilation_time() { + char buf[BUILD_TIME_STR_SIZE]; + this->get_build_time_string(buf); + return std::string(buf); + } /// Get the cached time in milliseconds from when the current component started its loop execution inline uint32_t IRAM_ATTR HOT get_loop_component_start_time() const { return this->loop_component_start_time_; } @@ -478,7 +502,6 @@ class Application { // Pointer-sized members first Component *current_component_{nullptr}; const char *comment_{nullptr}; - const char *compilation_time_{nullptr}; // std::vector (3 pointers each: begin, end, capacity) // Partitioned vector design for looping components diff --git a/esphome/core/build_info_data.h b/esphome/core/build_info_data.h new file mode 100644 index 0000000000..5e424ffaca --- /dev/null +++ b/esphome/core/build_info_data.h @@ -0,0 +1,10 @@ +#pragma once + +// This file is not used by the runtime, instead, a version is generated during +// compilation with the actual build info values. +// +// This file is only used by static analyzers and IDEs. + +#define ESPHOME_CONFIG_HASH 0x12345678U // NOLINT +#define ESPHOME_BUILD_TIME 1700000000 // NOLINT +static const char ESPHOME_BUILD_TIME_STR[] = "2024-01-01 00:00:00 +0000"; diff --git a/esphome/core/config.py b/esphome/core/config.py index 3adaf7eb9e..97157b6f92 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -501,7 +501,6 @@ async def to_code(config: ConfigType) -> None: config[CONF_NAME], config[CONF_FRIENDLY_NAME], config.get(CONF_COMMENT, ""), - cg.RawExpression('__DATE__ ", " __TIME__'), config[CONF_NAME_ADD_MAC_SUFFIX], ) ) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 84079227e1..bbe59e53f1 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -155,17 +155,6 @@ uint32_t fnv1_hash(const char *str) { return hash; } -// FNV-1a hash - preferred for new code -uint32_t fnv1a_hash_extend(uint32_t hash, const char *str) { - if (str) { - while (*str) { - hash ^= *str++; - hash *= FNV1_PRIME; - } - } - return hash; -} - float random_float() { return static_cast(random_uint32()) / static_cast(UINT32_MAX); } // Strings diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index cd9efef213..f9dcfccb45 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -388,12 +388,20 @@ constexpr uint32_t FNV1_OFFSET_BASIS = 2166136261UL; constexpr uint32_t FNV1_PRIME = 16777619UL; /// Extend a FNV-1a hash with additional string data. -uint32_t fnv1a_hash_extend(uint32_t hash, const char *str); +constexpr uint32_t fnv1a_hash_extend(uint32_t hash, const char *str) { + if (str) { + while (*str) { + hash ^= *str++; + hash *= FNV1_PRIME; + } + } + return hash; +} inline uint32_t fnv1a_hash_extend(uint32_t hash, const std::string &str) { return fnv1a_hash_extend(hash, str.c_str()); } /// Calculate a FNV-1a hash of \p str. -inline uint32_t fnv1a_hash(const char *str) { return fnv1a_hash_extend(FNV1_OFFSET_BASIS, str); } +constexpr uint32_t fnv1a_hash(const char *str) { return fnv1a_hash_extend(FNV1_OFFSET_BASIS, str); } inline uint32_t fnv1a_hash(const std::string &str) { return fnv1a_hash(str.c_str()); } /// Return a random 32-bit unsigned integer. diff --git a/esphome/core/progmem.h b/esphome/core/progmem.h index f9508945e8..d1594f47e7 100644 --- a/esphome/core/progmem.h +++ b/esphome/core/progmem.h @@ -9,8 +9,10 @@ #define ESPHOME_F(string_literal) F(string_literal) #define ESPHOME_PGM_P PGM_P #define ESPHOME_strncpy_P strncpy_P +#define ESPHOME_strncat_P strncat_P #else #define ESPHOME_F(string_literal) (string_literal) #define ESPHOME_PGM_P const char * #define ESPHOME_strncpy_P strncpy +#define ESPHOME_strncat_P strncat #endif diff --git a/esphome/helpers.py b/esphome/helpers.py index ea6abff50a..d1623d1d3c 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -424,9 +424,13 @@ def write_file_if_changed(path: Path, text: str) -> bool: return True -def copy_file_if_changed(src: Path, dst: Path) -> None: +def copy_file_if_changed(src: Path, dst: Path) -> bool: + """Copy file from src to dst if contents differ. + + Returns True if file was copied, False if files already matched. + """ if file_compare(src, dst): - return + return False dst.parent.mkdir(parents=True, exist_ok=True) try: shutil.copyfile(src, dst) @@ -441,11 +445,12 @@ def copy_file_if_changed(src: Path, dst: Path) -> None: with suppress(OSError): os.unlink(dst) shutil.copyfile(src, dst) - return + return True from esphome.core import EsphomeError raise EsphomeError(f"Error copying file {src} to {dst}: {err}") from err + return True def list_starts_with(list_, sub): diff --git a/esphome/writer.py b/esphome/writer.py index 721db07f96..183fff8730 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -1,11 +1,13 @@ from collections.abc import Callable import importlib +import json import logging import os from pathlib import Path import re import shutil import stat +import time from types import TracebackType from esphome import loader @@ -23,6 +25,7 @@ from esphome.helpers import ( is_ha_addon, read_file, walk_files, + write_file, write_file_if_changed, ) from esphome.storage_json import StorageJSON, storage_path @@ -173,6 +176,7 @@ VERSION_H_FORMAT = """\ """ DEFINES_H_TARGET = "esphome/core/defines.h" VERSION_H_TARGET = "esphome/core/version.h" +BUILD_INFO_DATA_H_TARGET = "esphome/core/build_info_data.h" ESPHOME_README_TXT = """ THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY @@ -206,10 +210,16 @@ def copy_src_tree(): include_s = "\n".join(include_l) source_files_copy = source_files_map.copy() - ignore_targets = [Path(x) for x in (DEFINES_H_TARGET, VERSION_H_TARGET)] + ignore_targets = [ + Path(x) for x in (DEFINES_H_TARGET, VERSION_H_TARGET, BUILD_INFO_DATA_H_TARGET) + ] for t in ignore_targets: - source_files_copy.pop(t) + source_files_copy.pop(t, None) + # Files to exclude from sources_changed tracking (generated files) + generated_files = {Path("esphome/core/build_info_data.h")} + + sources_changed = False for fname in walk_files(CORE.relative_src_path("esphome")): p = Path(fname) if p.suffix not in SOURCE_FILE_EXTENSIONS: @@ -223,28 +233,80 @@ def copy_src_tree(): if target not in source_files_copy: # Source file removed, delete target p.unlink() + if target not in generated_files: + sources_changed = True else: src_file = source_files_copy.pop(target) with src_file.path() as src_path: - copy_file_if_changed(src_path, p) + if copy_file_if_changed(src_path, p) and target not in generated_files: + sources_changed = True # Now copy new files for target, src_file in source_files_copy.items(): dst_path = CORE.relative_src_path(*target.parts) with src_file.path() as src_path: - copy_file_if_changed(src_path, dst_path) + if ( + copy_file_if_changed(src_path, dst_path) + and target not in generated_files + ): + sources_changed = True # Finally copy defines - write_file_if_changed( + if write_file_if_changed( CORE.relative_src_path("esphome", "core", "defines.h"), generate_defines_h() - ) + ): + sources_changed = True write_file_if_changed(CORE.relative_build_path("README.txt"), ESPHOME_README_TXT) - write_file_if_changed( + if write_file_if_changed( CORE.relative_src_path("esphome.h"), ESPHOME_H_FORMAT.format(include_s) - ) - write_file_if_changed( + ): + sources_changed = True + if write_file_if_changed( CORE.relative_src_path("esphome", "core", "version.h"), generate_version_h() + ): + sources_changed = True + + # Generate new build_info files if needed + build_info_data_h_path = CORE.relative_src_path( + "esphome", "core", "build_info_data.h" ) + build_info_json_path = CORE.relative_build_path("build_info.json") + config_hash, build_time, build_time_str = get_build_info() + + # Defensively force a rebuild if the build_info files don't exist, or if + # there was a config change which didn't actually cause a source change + if not build_info_data_h_path.exists(): + sources_changed = True + else: + try: + existing = json.loads(build_info_json_path.read_text(encoding="utf-8")) + if ( + existing.get("config_hash") != config_hash + or existing.get("esphome_version") != __version__ + ): + sources_changed = True + except (json.JSONDecodeError, KeyError, OSError): + sources_changed = True + + # Write build_info header and JSON metadata + if sources_changed: + write_file( + build_info_data_h_path, + generate_build_info_data_h(config_hash, build_time, build_time_str), + ) + write_file( + build_info_json_path, + json.dumps( + { + "config_hash": config_hash, + "build_time": build_time, + "build_time_str": build_time_str, + "esphome_version": __version__, + }, + indent=2, + ) + + "\n", + ) platform = "esphome.components." + CORE.target_platform try: @@ -270,6 +332,35 @@ def generate_version_h(): ) +def get_build_info() -> tuple[int, int, str]: + """Calculate build_info values from current config. + + Returns: + Tuple of (config_hash, build_time, build_time_str) + """ + config_hash = CORE.config_hash + build_time = int(time.time()) + build_time_str = time.strftime("%Y-%m-%d %H:%M:%S %z", time.localtime(build_time)) + return config_hash, build_time, build_time_str + + +def generate_build_info_data_h( + config_hash: int, build_time: int, build_time_str: str +) -> str: + """Generate build_info_data.h header with config hash and build time.""" + return f"""#pragma once +// Auto-generated build_info data +#define ESPHOME_CONFIG_HASH 0x{config_hash:08x}U // NOLINT +#define ESPHOME_BUILD_TIME {build_time} // NOLINT +#ifdef USE_ESP8266 +#include +static const char ESPHOME_BUILD_TIME_STR[] PROGMEM = "{build_time_str}"; +#else +static const char ESPHOME_BUILD_TIME_STR[] = "{build_time_str}"; +#endif +""" + + def write_cpp(code_s): path = CORE.relative_src_path("main.cpp") if path.is_file(): diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index 359b72b48f..bba4bbf487 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -1,6 +1,7 @@ from __future__ import annotations from collections.abc import Callable +from contextlib import suppress import functools import inspect from io import BytesIO, TextIOBase, TextIOWrapper @@ -501,13 +502,17 @@ def _load_yaml_internal_with_type( loader.dispose() -def dump(dict_, show_secrets=False): +def dump(dict_, show_secrets=False, sort_keys=False): """Dump YAML to a string and remove null.""" if show_secrets: _SECRET_VALUES.clear() _SECRET_CACHE.clear() return yaml.dump( - dict_, default_flow_style=False, allow_unicode=True, Dumper=ESPHomeDumper + dict_, + default_flow_style=False, + allow_unicode=True, + Dumper=ESPHomeDumper, + sort_keys=sort_keys, ) @@ -543,6 +548,9 @@ class ESPHomeDumper(yaml.SafeDumper): best_style = True if hasattr(mapping, "items"): mapping = list(mapping.items()) + if self.sort_keys: + with suppress(TypeError): + mapping = sorted(mapping) for item_key, item_value in mapping: node_key = self.represent_data(item_key) node_value = self.represent_data(item_value) diff --git a/tests/dummy_main.cpp b/tests/dummy_main.cpp index afd393c095..5849f4eb95 100644 --- a/tests/dummy_main.cpp +++ b/tests/dummy_main.cpp @@ -12,7 +12,7 @@ using namespace esphome; void setup() { - App.pre_setup("livingroom", "LivingRoom", "comment", __DATE__ ", " __TIME__, false); + App.pre_setup("livingroom", "LivingRoom", "comment", false); auto *log = new logger::Logger(115200, 512); // NOLINT log->pre_setup(); log->set_uart_selection(logger::UART_SELECTION_UART0); diff --git a/tests/integration/fixtures/build_info.yaml b/tests/integration/fixtures/build_info.yaml new file mode 100644 index 0000000000..5d6101543a --- /dev/null +++ b/tests/integration/fixtures/build_info.yaml @@ -0,0 +1,31 @@ +esphome: + name: build-info-test +host: +api: +logger: + +text_sensor: + - platform: template + name: "Config Hash" + id: config_hash_sensor + update_interval: 100ms + lambda: |- + char buf[16]; + snprintf(buf, sizeof(buf), "0x%08x", App.get_config_hash()); + return std::string(buf); + - platform: template + name: "Build Time" + id: build_time_sensor + update_interval: 100ms + lambda: |- + char buf[32]; + snprintf(buf, sizeof(buf), "%ld", (long)App.get_build_time()); + return std::string(buf); + - platform: template + name: "Build Time String" + id: build_time_str_sensor + update_interval: 100ms + lambda: |- + char buf[Application::BUILD_TIME_STR_SIZE]; + App.get_build_time_string(buf); + return std::string(buf); diff --git a/tests/integration/test_build_info.py b/tests/integration/test_build_info.py new file mode 100644 index 0000000000..7079594471 --- /dev/null +++ b/tests/integration/test_build_info.py @@ -0,0 +1,117 @@ +"""Integration test for build_info values.""" + +from __future__ import annotations + +import asyncio +from datetime import datetime +import re +import time + +from aioesphomeapi import EntityState, TextSensorState +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_build_info( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that build_info values are sane.""" + async with run_compiled(yaml_config), api_client_connected() as client: + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "build-info-test" + + # Verify compilation_time from device_info is present and parseable + # The format is ISO 8601 with timezone: "YYYY-MM-DD HH:MM:SS +ZZZZ" + compilation_time = device_info.compilation_time + assert compilation_time is not None + + # Validate the ISO format: "YYYY-MM-DD HH:MM:SS +ZZZZ" + parsed = datetime.strptime(compilation_time, "%Y-%m-%d %H:%M:%S %z") + assert parsed.year >= time.localtime().tm_year + + # Get entities + entities, _ = await client.list_entities_services() + + # Find our text sensors by object_id + config_hash_entity = next( + (e for e in entities if e.object_id == "config_hash"), None + ) + build_time_entity = next( + (e for e in entities if e.object_id == "build_time"), None + ) + build_time_str_entity = next( + (e for e in entities if e.object_id == "build_time_string"), None + ) + + assert config_hash_entity is not None, "Config Hash sensor not found" + assert build_time_entity is not None, "Build Time sensor not found" + assert build_time_str_entity is not None, "Build Time String sensor not found" + + # Wait for all three text sensors to have valid states + loop = asyncio.get_running_loop() + states: dict[int, TextSensorState] = {} + all_received = loop.create_future() + expected_keys = { + config_hash_entity.key, + build_time_entity.key, + build_time_str_entity.key, + } + + def on_state(state: EntityState) -> None: + if isinstance(state, TextSensorState) and not state.missing_state: + states[state.key] = state + if expected_keys <= states.keys() and not all_received.done(): + all_received.set_result(True) + + client.subscribe_states(on_state) + + try: + await asyncio.wait_for(all_received, timeout=5.0) + except TimeoutError: + pytest.fail( + f"Timeout waiting for text sensor states. Got: {list(states.keys())}" + ) + + config_hash_state = states[config_hash_entity.key] + build_time_state = states[build_time_entity.key] + build_time_str_state = states[build_time_str_entity.key] + + # Validate config_hash format (0x followed by 8 hex digits) + config_hash = config_hash_state.state + assert re.match(r"^0x[0-9a-f]{8}$", config_hash), ( + f"config_hash should be 0x followed by 8 hex digits, got: {config_hash}" + ) + + # Validate build_time is a reasonable Unix timestamp + build_time = int(build_time_state.state) + current_time = int(time.time()) + # Build time should be within last hour and not in the future + assert build_time <= current_time, ( + f"build_time {build_time} should not be in the future (current: {current_time})" + ) + assert build_time > current_time - 3600, ( + f"build_time {build_time} should be within the last hour" + ) + + # Validate build_time_str matches the new ISO format + build_time_str = build_time_str_state.state + # Format: "YYYY-MM-DD HH:MM:SS +ZZZZ" + parsed_build_time = datetime.strptime(build_time_str, "%Y-%m-%d %H:%M:%S %z") + assert parsed_build_time.year >= time.localtime().tm_year + + # Verify build_time_str matches what we get from build_time timestamp + expected_str = time.strftime("%Y-%m-%d %H:%M:%S %z", time.localtime(build_time)) + assert build_time_str == expected_str, ( + f"build_time_str '{build_time_str}' should match timestamp '{expected_str}'" + ) + + # Verify compilation_time matches build_time_str (they should be the same) + assert compilation_time == build_time_str, ( + f"compilation_time '{compilation_time}' should match " + f"build_time_str '{build_time_str}'" + ) diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index fc61841500..1a1bfffd03 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -58,6 +58,7 @@ def mock_write_file_if_changed() -> Generator[Mock, None, None]: def mock_copy_file_if_changed() -> Generator[Mock, None, None]: """Mock copy_file_if_changed for core.config.""" with patch("esphome.core.config.copy_file_if_changed") as mock: + mock.return_value = True yield mock diff --git a/tests/unit_tests/core/test_config.py b/tests/unit_tests/core/test_config.py index 90b2f5edba..ab7bdbb98c 100644 --- a/tests/unit_tests/core/test_config.py +++ b/tests/unit_tests/core/test_config.py @@ -892,3 +892,74 @@ async def test_add_includes_overwrites_existing_files( mock_copy_file_if_changed.assert_called_once_with( include_file, CORE.build_path / "src" / "header.h" ) + + +def test_config_hash_returns_int() -> None: + """Test that config_hash returns an integer.""" + CORE.reset() + CORE.config = {"esphome": {"name": "test"}} + assert isinstance(CORE.config_hash, int) + + +def test_config_hash_is_cached() -> None: + """Test that config_hash is computed once and cached.""" + CORE.reset() + CORE.config = {"esphome": {"name": "test"}} + + # First access computes the hash + hash1 = CORE.config_hash + + # Modify config (without resetting cache) + CORE.config = {"esphome": {"name": "different"}} + + # Second access returns cached value + hash2 = CORE.config_hash + + assert hash1 == hash2 + + +def test_config_hash_reset_clears_cache() -> None: + """Test that reset() clears the cached config_hash.""" + CORE.reset() + CORE.config = {"esphome": {"name": "test"}} + hash1 = CORE.config_hash + + # Reset clears the cache + CORE.reset() + CORE.config = {"esphome": {"name": "different"}} + + hash2 = CORE.config_hash + + # After reset, hash should be recomputed + assert hash1 != hash2 + + +def test_config_hash_deterministic_key_order() -> None: + """Test that config_hash is deterministic regardless of key insertion order.""" + CORE.reset() + # Create two configs with same content but different key order + config1 = {"z_key": 1, "a_key": 2, "nested": {"z_nested": "z", "a_nested": "a"}} + config2 = {"a_key": 2, "z_key": 1, "nested": {"a_nested": "a", "z_nested": "z"}} + + CORE.config = config1 + hash1 = CORE.config_hash + + CORE.reset() + CORE.config = config2 + hash2 = CORE.config_hash + + # Hashes should be equal because keys are sorted during serialization + assert hash1 == hash2 + + +def test_config_hash_different_for_different_configs() -> None: + """Test that different configs produce different hashes.""" + CORE.reset() + CORE.config = {"esphome": {"name": "test1"}} + hash1 = CORE.config_hash + + CORE.reset() + CORE.config = {"esphome": {"name": "test2"}} + hash2 = CORE.config_hash + + assert hash1 != hash2 diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index bd14395037..36a284c382 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -4,9 +4,11 @@ from __future__ import annotations from collections.abc import Generator from dataclasses import dataclass +import json import logging from pathlib import Path import re +import time from typing import Any from unittest.mock import MagicMock, Mock, patch @@ -22,6 +24,7 @@ from esphome.__main__ import ( command_rename, command_update_all, command_wizard, + compile_program, detect_external_components, get_port_type, has_ip_address, @@ -2605,3 +2608,197 @@ def test_command_analyze_memory_no_idedata( assert result == 1 assert "Failed to get IDE data for memory analysis" in caplog.text + + +@pytest.fixture +def mock_compile_build_info_run_compile() -> Generator[Mock]: + """Mock platformio_api.run_compile for build_info tests.""" + with patch("esphome.platformio_api.run_compile", return_value=0) as mock: + yield mock + + +@pytest.fixture +def mock_compile_build_info_get_idedata() -> Generator[Mock]: + """Mock platformio_api.get_idedata for build_info tests.""" + mock_idedata = MagicMock() + with patch("esphome.platformio_api.get_idedata", return_value=mock_idedata) as mock: + yield mock + + +def _setup_build_info_test( + tmp_path: Path, + *, + create_firmware: bool = True, + create_build_info: bool = True, + build_info_content: str | None = None, + firmware_first: bool = False, +) -> tuple[Path, Path]: + """Set up build directory structure for build_info tests. + + Args: + tmp_path: Temporary directory path. + create_firmware: Whether to create firmware.bin file. + create_build_info: Whether to create build_info.json file. + build_info_content: Custom content for build_info.json, or None for default. + firmware_first: If True, create firmware before build_info (makes firmware older). + + Returns: + Tuple of (build_info_path, firmware_path). + """ + setup_core(platform=PLATFORM_ESP32, tmp_path=tmp_path, name="test_device") + + build_path = tmp_path / ".esphome" / "build" / "test_device" + pioenvs_path = build_path / ".pioenvs" / "test_device" + pioenvs_path.mkdir(parents=True, exist_ok=True) + + build_info_path = build_path / "build_info.json" + firmware_path = pioenvs_path / "firmware.bin" + + default_build_info = json.dumps( + { + "config_hash": 0x12345678, + "build_time": int(time.time()), + "build_time_str": "Dec 13 2025, 12:00:00", + "esphome_version": "2025.1.0", + } + ) + + def create_build_info_file() -> None: + if create_build_info: + content = ( + build_info_content + if build_info_content is not None + else default_build_info + ) + build_info_path.write_text(content) + + def create_firmware_file() -> None: + if create_firmware: + firmware_path.write_bytes(b"fake firmware") + + if firmware_first: + create_firmware_file() + time.sleep(0.01) # Ensure different timestamps + create_build_info_file() + else: + create_build_info_file() + time.sleep(0.01) # Ensure different timestamps + create_firmware_file() + + return build_info_path, firmware_path + + +def test_compile_program_emits_build_info_when_firmware_rebuilt( + tmp_path: Path, + caplog: pytest.LogCaptureFixture, + mock_compile_build_info_run_compile: Mock, + mock_compile_build_info_get_idedata: Mock, +) -> None: + """Test that compile_program logs build_info when firmware is rebuilt.""" + _setup_build_info_test(tmp_path, firmware_first=False) + + config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}} + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = compile_program(args, config) + + assert result == 0 + assert "Build Info: config_hash=0x12345678" in caplog.text + + +def test_compile_program_no_build_info_when_firmware_not_rebuilt( + tmp_path: Path, + caplog: pytest.LogCaptureFixture, + mock_compile_build_info_run_compile: Mock, + mock_compile_build_info_get_idedata: Mock, +) -> None: + """Test that compile_program doesn't log build_info when firmware wasn't rebuilt.""" + _setup_build_info_test(tmp_path, firmware_first=True) + + config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}} + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = compile_program(args, config) + + assert result == 0 + assert "Build Info:" not in caplog.text + + +def test_compile_program_no_build_info_when_firmware_missing( + tmp_path: Path, + caplog: pytest.LogCaptureFixture, + mock_compile_build_info_run_compile: Mock, + mock_compile_build_info_get_idedata: Mock, +) -> None: + """Test that compile_program doesn't log build_info when firmware.bin doesn't exist.""" + _setup_build_info_test(tmp_path, create_firmware=False) + + config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}} + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = compile_program(args, config) + + assert result == 0 + assert "Build Info:" not in caplog.text + + +def test_compile_program_no_build_info_when_json_missing( + tmp_path: Path, + caplog: pytest.LogCaptureFixture, + mock_compile_build_info_run_compile: Mock, + mock_compile_build_info_get_idedata: Mock, +) -> None: + """Test that compile_program doesn't log build_info when build_info.json doesn't exist.""" + _setup_build_info_test(tmp_path, create_build_info=False) + + config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}} + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = compile_program(args, config) + + assert result == 0 + assert "Build Info:" not in caplog.text + + +def test_compile_program_no_build_info_when_json_invalid( + tmp_path: Path, + caplog: pytest.LogCaptureFixture, + mock_compile_build_info_run_compile: Mock, + mock_compile_build_info_get_idedata: Mock, +) -> None: + """Test that compile_program doesn't log build_info when build_info.json is invalid.""" + _setup_build_info_test(tmp_path, build_info_content="not valid json {{{") + + config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}} + args = MockArgs() + + with caplog.at_level(logging.DEBUG): + result = compile_program(args, config) + + assert result == 0 + assert "Build Info:" not in caplog.text + + +def test_compile_program_no_build_info_when_json_missing_keys( + tmp_path: Path, + caplog: pytest.LogCaptureFixture, + mock_compile_build_info_run_compile: Mock, + mock_compile_build_info_get_idedata: Mock, +) -> None: + """Test that compile_program doesn't log build_info when build_info.json is missing required keys.""" + _setup_build_info_test( + tmp_path, build_info_content=json.dumps({"build_time": 1234567890}) + ) + + config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}} + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = compile_program(args, config) + + assert result == 0 + assert "Build Info:" not in caplog.text diff --git a/tests/unit_tests/test_writer.py b/tests/unit_tests/test_writer.py index 9fa60c06ec..06a7d5dbdf 100644 --- a/tests/unit_tests/test_writer.py +++ b/tests/unit_tests/test_writer.py @@ -1,6 +1,10 @@ """Test writer module functionality.""" from collections.abc import Callable +from contextlib import contextmanager +from dataclasses import dataclass +from datetime import datetime +import json import os from pathlib import Path import stat @@ -20,6 +24,9 @@ from esphome.writer import ( clean_all, clean_build, clean_cmake_cache, + copy_src_tree, + generate_build_info_data_h, + get_build_info, storage_should_clean, update_storage_json, write_cpp, @@ -1165,3 +1172,721 @@ def test_clean_build_reraises_for_other_errors( finally: # Cleanup - restore write permission so tmp_path cleanup works os.chmod(subdir, stat.S_IRWXU) + + +# Tests for get_build_info() + + +@patch("esphome.writer.CORE") +def test_get_build_info_new_build( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info returns new build_time when no existing build_info.json.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0x12345678 + + config_hash, build_time, build_time_str = get_build_info() + + assert config_hash == 0x12345678 + assert isinstance(build_time, int) + assert build_time > 0 + assert isinstance(build_time_str, str) + # Verify build_time_str format matches expected pattern + assert len(build_time_str) >= 19 # e.g., "2025-12-15 16:27:44 +0000" + + +@patch("esphome.writer.CORE") +def test_get_build_info_always_returns_current_time( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info always returns current build_time.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0x12345678 + + # Create existing build_info.json with matching config_hash and version + existing_build_time = 1700000000 + existing_build_time_str = "2023-11-14 22:13:20 +0000" + build_info_path.write_text( + json.dumps( + { + "config_hash": 0x12345678, + "build_time": existing_build_time, + "build_time_str": existing_build_time_str, + "esphome_version": "2025.1.0-dev", + } + ) + ) + + with patch("esphome.writer.__version__", "2025.1.0-dev"): + config_hash, build_time, build_time_str = get_build_info() + + assert config_hash == 0x12345678 + # get_build_info now always returns current time + assert build_time != existing_build_time + assert build_time > existing_build_time + assert build_time_str != existing_build_time_str + + +@patch("esphome.writer.CORE") +def test_get_build_info_config_changed( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info returns new build_time when config hash changed.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0xABCDEF00 # Different from existing + + # Create existing build_info.json with different config_hash + existing_build_time = 1700000000 + build_info_path.write_text( + json.dumps( + { + "config_hash": 0x12345678, # Different + "build_time": existing_build_time, + "build_time_str": "2023-11-14 22:13:20 +0000", + "esphome_version": "2025.1.0-dev", + } + ) + ) + + with patch("esphome.writer.__version__", "2025.1.0-dev"): + config_hash, build_time, build_time_str = get_build_info() + + assert config_hash == 0xABCDEF00 + assert build_time != existing_build_time # New time generated + assert build_time > existing_build_time + + +@patch("esphome.writer.CORE") +def test_get_build_info_version_changed( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info returns new build_time when ESPHome version changed.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0x12345678 + + # Create existing build_info.json with different version + existing_build_time = 1700000000 + build_info_path.write_text( + json.dumps( + { + "config_hash": 0x12345678, + "build_time": existing_build_time, + "build_time_str": "2023-11-14 22:13:20 +0000", + "esphome_version": "2024.12.0", # Old version + } + ) + ) + + with patch("esphome.writer.__version__", "2025.1.0-dev"): # New version + config_hash, build_time, build_time_str = get_build_info() + + assert config_hash == 0x12345678 + assert build_time != existing_build_time # New time generated + assert build_time > existing_build_time + + +@patch("esphome.writer.CORE") +def test_get_build_info_invalid_json( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info handles invalid JSON gracefully.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0x12345678 + + # Create invalid JSON file + build_info_path.write_text("not valid json {{{") + + config_hash, build_time, build_time_str = get_build_info() + + assert config_hash == 0x12345678 + assert isinstance(build_time, int) + assert build_time > 0 + + +@patch("esphome.writer.CORE") +def test_get_build_info_missing_keys( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info handles missing keys gracefully.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0x12345678 + + # Create JSON with missing keys + build_info_path.write_text(json.dumps({"config_hash": 0x12345678})) + + with patch("esphome.writer.__version__", "2025.1.0-dev"): + config_hash, build_time, build_time_str = get_build_info() + + assert config_hash == 0x12345678 + assert isinstance(build_time, int) + assert build_time > 0 + + +@patch("esphome.writer.CORE") +def test_get_build_info_build_time_str_format( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info returns correctly formatted build_time_str.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0x12345678 + + config_hash, build_time, build_time_str = get_build_info() + + # Verify the format matches "%Y-%m-%d %H:%M:%S %z" + # e.g., "2025-12-15 16:27:44 +0000" + parsed = datetime.strptime(build_time_str, "%Y-%m-%d %H:%M:%S %z") + assert parsed.year >= 2024 + + +def test_generate_build_info_data_h_format() -> None: + """Test generate_build_info_data_h produces correct header content.""" + config_hash = 0x12345678 + build_time = 1700000000 + build_time_str = "2023-11-14 22:13:20 +0000" + + result = generate_build_info_data_h(config_hash, build_time, build_time_str) + + assert "#pragma once" in result + assert "#define ESPHOME_CONFIG_HASH 0x12345678U" in result + assert "#define ESPHOME_BUILD_TIME 1700000000" in result + assert 'ESPHOME_BUILD_TIME_STR[] = "2023-11-14 22:13:20 +0000"' in result + + +def test_generate_build_info_data_h_esp8266_progmem() -> None: + """Test generate_build_info_data_h includes PROGMEM for ESP8266.""" + result = generate_build_info_data_h(0xABCDEF01, 1700000000, "test") + + # Should have ESP8266 PROGMEM conditional + assert "#ifdef USE_ESP8266" in result + assert "#include " in result + assert "PROGMEM" in result + + +def test_generate_build_info_data_h_hash_formatting() -> None: + """Test generate_build_info_data_h formats hash with leading zeros.""" + # Test with small hash value that needs leading zeros + result = generate_build_info_data_h(0x00000001, 0, "test") + assert "#define ESPHOME_CONFIG_HASH 0x00000001U" in result + + # Test with larger hash value + result = generate_build_info_data_h(0xFFFFFFFF, 0, "test") + assert "#define ESPHOME_CONFIG_HASH 0xffffffffU" in result + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_writes_build_info_files( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test copy_src_tree writes build_info_data.h and build_info.json.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_core_path = src_path / "esphome" / "core" + esphome_core_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create mock source files for defines.h and version.h + mock_defines_h = esphome_core_path / "defines.h" + mock_defines_h.write_text("// mock defines.h") + mock_version_h = esphome_core_path / "version.h" + mock_version_h.write_text("// mock version.h") + + # Create mock FileResource that returns our temp files + @dataclass(frozen=True) + class MockFileResource: + package: str + resource: str + _path: Path + + @contextmanager + def path(self): + yield self._path + + # Create mock resources for defines.h and version.h (required by copy_src_tree) + mock_resources = [ + MockFileResource( + package="esphome.core", + resource="defines.h", + _path=mock_defines_h, + ), + MockFileResource( + package="esphome.core", + resource="version.h", + _path=mock_version_h, + ), + ] + + # Create mock component with resources + mock_component = MagicMock() + mock_component.resources = mock_resources + + # Setup mocks + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [("core", mock_component)] + mock_walk_files.return_value = [] + + # Create mock module without copy_files attribute (causes AttributeError which is caught) + mock_module = MagicMock(spec=[]) # Empty spec = no copy_files attribute + + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module", return_value=mock_module), + ): + copy_src_tree() + + # Verify build_info_data.h was written + build_info_h_path = esphome_core_path / "build_info_data.h" + assert build_info_h_path.exists() + build_info_h_content = build_info_h_path.read_text() + assert "#define ESPHOME_CONFIG_HASH 0xdeadbeefU" in build_info_h_content + assert "#define ESPHOME_BUILD_TIME" in build_info_h_content + assert "ESPHOME_BUILD_TIME_STR" in build_info_h_content + + # Verify build_info.json was written + build_info_json_path = build_path / "build_info.json" + assert build_info_json_path.exists() + build_info_json = json.loads(build_info_json_path.read_text()) + assert build_info_json["config_hash"] == 0xDEADBEEF + assert "build_time" in build_info_json + assert "build_time_str" in build_info_json + assert build_info_json["esphome_version"] == "2025.1.0-dev" + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_detects_config_hash_change( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test copy_src_tree detects when config_hash changes.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_core_path = src_path / "esphome" / "core" + esphome_core_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create existing build_info.json with different config_hash + build_info_json_path = build_path / "build_info.json" + build_info_json_path.write_text( + json.dumps( + { + "config_hash": 0x12345678, # Different from current + "build_time": 1700000000, + "build_time_str": "2023-11-14 22:13:20 +0000", + "esphome_version": "2025.1.0-dev", + } + ) + ) + + # Create existing build_info_data.h + build_info_h_path = esphome_core_path / "build_info_data.h" + build_info_h_path.write_text("// old build_info_data.h") + + # Setup mocks + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF # Different from existing + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [] + mock_walk_files.return_value = [] + + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + # Verify build_info files were updated due to config_hash change + assert build_info_h_path.exists() + new_content = build_info_h_path.read_text() + assert "0xdeadbeef" in new_content.lower() + + new_json = json.loads(build_info_json_path.read_text()) + assert new_json["config_hash"] == 0xDEADBEEF + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_detects_version_change( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test copy_src_tree detects when esphome_version changes.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_core_path = src_path / "esphome" / "core" + esphome_core_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create existing build_info.json with different version + build_info_json_path = build_path / "build_info.json" + build_info_json_path.write_text( + json.dumps( + { + "config_hash": 0xDEADBEEF, + "build_time": 1700000000, + "build_time_str": "2023-11-14 22:13:20 +0000", + "esphome_version": "2024.12.0", # Old version + } + ) + ) + + # Create existing build_info_data.h + build_info_h_path = esphome_core_path / "build_info_data.h" + build_info_h_path.write_text("// old build_info_data.h") + + # Setup mocks + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [] + mock_walk_files.return_value = [] + + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), # New version + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + # Verify build_info files were updated due to version change + assert build_info_h_path.exists() + new_json = json.loads(build_info_json_path.read_text()) + assert new_json["esphome_version"] == "2025.1.0-dev" + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_handles_invalid_build_info_json( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test copy_src_tree handles invalid build_info.json gracefully.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_core_path = src_path / "esphome" / "core" + esphome_core_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create invalid build_info.json + build_info_json_path = build_path / "build_info.json" + build_info_json_path.write_text("invalid json {{{") + + # Create existing build_info_data.h + build_info_h_path = esphome_core_path / "build_info_data.h" + build_info_h_path.write_text("// old build_info_data.h") + + # Setup mocks + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [] + mock_walk_files.return_value = [] + + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + # Verify build_info files were created despite invalid JSON + assert build_info_h_path.exists() + new_json = json.loads(build_info_json_path.read_text()) + assert new_json["config_hash"] == 0xDEADBEEF + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_build_info_timestamp_behavior( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test build_info behaviour: regenerated on change, preserved when unchanged.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_core_path = src_path / "esphome" / "core" + esphome_core_path.mkdir(parents=True) + esphome_components_path = src_path / "esphome" / "components" + esphome_components_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create a source file + source_file = tmp_path / "source" / "test.cpp" + source_file.parent.mkdir() + source_file.write_text("// version 1") + + # Create destination file in build tree + dest_file = esphome_components_path / "test.cpp" + + # Create mock FileResource + @dataclass(frozen=True) + class MockFileResource: + package: str + resource: str + _path: Path + + @contextmanager + def path(self): + yield self._path + + mock_resources = [ + MockFileResource( + package="esphome.components", + resource="test.cpp", + _path=source_file, + ), + ] + + mock_component = MagicMock() + mock_component.resources = mock_resources + + # Setup mocks + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [("test", mock_component)] + + build_info_json_path = build_path / "build_info.json" + + # First run: initial setup, should create build_info + mock_walk_files.return_value = [] + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + # Manually set an old timestamp for testing + old_timestamp = 1700000000 + old_timestamp_str = "2023-11-14 22:13:20 +0000" + build_info_json_path.write_text( + json.dumps( + { + "config_hash": 0xDEADBEEF, + "build_time": old_timestamp, + "build_time_str": old_timestamp_str, + "esphome_version": "2025.1.0-dev", + } + ) + ) + + # Second run: no changes, should NOT regenerate build_info + mock_walk_files.return_value = [str(dest_file)] + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + second_json = json.loads(build_info_json_path.read_text()) + second_timestamp = second_json["build_time"] + + # Verify timestamp was NOT changed + assert second_timestamp == old_timestamp, ( + f"build_info should not be regenerated when no files change: " + f"{old_timestamp} != {second_timestamp}" + ) + + # Third run: change source file, should regenerate build_info with new timestamp + source_file.write_text("// version 2") + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + third_json = json.loads(build_info_json_path.read_text()) + third_timestamp = third_json["build_time"] + + # Verify timestamp WAS changed + assert third_timestamp != old_timestamp, ( + f"build_info should be regenerated when source file changes: " + f"{old_timestamp} == {third_timestamp}" + ) + assert third_timestamp > old_timestamp + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_detects_removed_source_file( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test copy_src_tree detects when a non-generated source file is removed.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_components_path = src_path / "esphome" / "components" + esphome_components_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create an existing source file in the build tree + existing_file = esphome_components_path / "test.cpp" + existing_file.write_text("// test file") + + # Setup mocks - no components, so the file should be removed + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [] # No components = file should be removed + mock_walk_files.return_value = [str(existing_file)] + + # Create existing build_info.json + build_info_json_path = build_path / "build_info.json" + old_timestamp = 1700000000 + build_info_json_path.write_text( + json.dumps( + { + "config_hash": 0xDEADBEEF, + "build_time": old_timestamp, + "build_time_str": "2023-11-14 22:13:20 +0000", + "esphome_version": "2025.1.0-dev", + } + ) + ) + + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + # Verify file was removed + assert not existing_file.exists() + + # Verify build_info was regenerated due to source file removal + new_json = json.loads(build_info_json_path.read_text()) + assert new_json["build_time"] != old_timestamp + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_ignores_removed_generated_file( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test copy_src_tree doesn't mark sources_changed when only generated file removed.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_core_path = src_path / "esphome" / "core" + esphome_core_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create existing build_info_data.h (a generated file) + build_info_h = esphome_core_path / "build_info_data.h" + build_info_h.write_text("// old generated file") + + # Setup mocks + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [] + # walk_files returns the generated file, but it's not in source_files_copy + mock_walk_files.return_value = [str(build_info_h)] + + # Create existing build_info.json with old timestamp + build_info_json_path = build_path / "build_info.json" + old_timestamp = 1700000000 + build_info_json_path.write_text( + json.dumps( + { + "config_hash": 0xDEADBEEF, + "build_time": old_timestamp, + "build_time_str": "2023-11-14 22:13:20 +0000", + "esphome_version": "2025.1.0-dev", + } + ) + ) + + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + # Verify build_info_data.h was regenerated (not removed) + assert build_info_h.exists() + + # Note: build_info.json will have a new timestamp because get_build_info() + # always returns current time. The key test is that the old build_info_data.h + # file was removed and regenerated, not that it triggered sources_changed. + new_json = json.loads(build_info_json_path.read_text()) + assert new_json["config_hash"] == 0xDEADBEEF diff --git a/tests/unit_tests/test_yaml_util.py b/tests/unit_tests/test_yaml_util.py index eac0ceabb8..c8cb3e144f 100644 --- a/tests/unit_tests/test_yaml_util.py +++ b/tests/unit_tests/test_yaml_util.py @@ -278,3 +278,31 @@ def test_secret_values_tracking(fixture_path: Path) -> None: assert yaml_util._SECRET_VALUES["super_secret_wifi"] == "wifi_password" assert "0123456789abcdef" in yaml_util._SECRET_VALUES assert yaml_util._SECRET_VALUES["0123456789abcdef"] == "api_key" + + +def test_dump_sort_keys() -> None: + """Test that dump with sort_keys=True produces sorted output.""" + # Create a dict with unsorted keys + data = { + "zebra": 1, + "alpha": 2, + "nested": { + "z_key": "z_value", + "a_key": "a_value", + }, + } + + # Without sort_keys, keys are in insertion order + unsorted = yaml_util.dump(data, sort_keys=False) + lines_unsorted = unsorted.strip().split("\n") + # First key should be "zebra" (insertion order) + assert lines_unsorted[0].startswith("zebra:") + + # With sort_keys, keys are alphabetically sorted + sorted_dump = yaml_util.dump(data, sort_keys=True) + lines_sorted = sorted_dump.strip().split("\n") + # First key should be "alpha" (alphabetical order) + assert lines_sorted[0].startswith("alpha:") + # nested keys should also be sorted + assert "a_key:" in sorted_dump + assert sorted_dump.index("a_key:") < sorted_dump.index("z_key:") From 1b5af7d21d500f8d03c1a505b4afef4eb5a578cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:22:19 -1000 Subject: [PATCH 0702/1145] Bump github/codeql-action from 4.31.8 to 4.31.9 (#12524) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f917ecd8f8..e63c3075dd 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 + uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -86,6 +86,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 + uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 with: category: "/language:${{matrix.language}}" From 426305836dcb1c225d52e1ca999142760208599d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Dec 2025 21:16:14 -0700 Subject: [PATCH 0703/1145] [esp32][libretiny] Avoid duplicate snprintf when syncing preferences (#12542) --- esphome/components/esp32/preferences.cpp | 13 ++++++------- esphome/components/libretiny/preferences.cpp | 14 ++++++-------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index e19a85e4e3..5e1e8734e5 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -130,10 +130,10 @@ class ESP32Preferences : public ESPPreferences { // go through vector from back to front (makes erase easier/more efficient) for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) { const auto &save = s_pending_save[i]; - ESP_LOGVV(TAG, "Checking if NVS data %" PRIu32 " has changed", save.key); - if (this->is_changed(this->nvs_handle, save)) { - char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); + ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str); + if (this->is_changed_(this->nvs_handle, save, key_str)) { esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.get(), save.len); ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len); if (err != 0) { @@ -166,10 +166,9 @@ class ESP32Preferences : public ESPPreferences { return failed == 0; } - bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) { - char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, to_save.key); + protected: + bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) { size_t actual_len; esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len); if (err != 0) { diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index c21c5813a8..e47e88c6f3 100644 --- a/esphome/components/libretiny/preferences.cpp +++ b/esphome/components/libretiny/preferences.cpp @@ -120,10 +120,10 @@ class LibreTinyPreferences : public ESPPreferences { // go through vector from back to front (makes erase easier/more efficient) for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) { const auto &save = s_pending_save[i]; - ESP_LOGVV(TAG, "Checking if FDB data %" PRIu32 " has changed", save.key); - if (this->is_changed(&this->db, save)) { - char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); + ESP_LOGVV(TAG, "Checking if FDB data %s has changed", key_str); + if (this->is_changed_(&this->db, save, key_str)) { ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len); fdb_blob_make(&this->blob, save.data.get(), save.len); fdb_err_t err = fdb_kv_set_blob(&this->db, key_str, &this->blob); @@ -150,10 +150,8 @@ class LibreTinyPreferences : public ESPPreferences { return failed == 0; } - bool is_changed(const fdb_kvdb_t db, const NVSData &to_save) { - char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, to_save.key); - + protected: + bool is_changed_(fdb_kvdb_t db, const NVSData &to_save, const char *key_str) { struct fdb_kv kv; fdb_kv_t kvp = fdb_kv_get_obj(db, key_str, &kv); if (kvp == nullptr) { From 4f821a6d76a27960cd1a007ac97e443d419d413a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Dec 2025 21:21:46 -0700 Subject: [PATCH 0704/1145] [wifi] Reduce scan logging to prevent blocking loop during connection (#12544) --- esphome/components/wifi/wifi_component.cpp | 41 ++++++++++++++++------ 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index a550aa679d..7c5b001be9 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1031,23 +1031,30 @@ template static void insertion_sort_scan_results(VectorType } } -// Helper function to log scan results - marked noinline to prevent re-inlining into loop +// Helper function to log matching scan results - marked noinline to prevent re-inlining into loop __attribute__((noinline)) static void log_scan_result(const WiFiScanResult &res) { char bssid_s[18]; auto bssid = res.get_bssid(); format_mac_addr_upper(bssid.data(), bssid_s); - if (res.get_matches()) { - ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), - res.get_is_hidden() ? LOG_STR_LITERAL("(HIDDEN) ") : LOG_STR_LITERAL(""), bssid_s, - LOG_STR_ARG(get_signal_bars(res.get_rssi()))); - ESP_LOGD(TAG, " Channel: %2u, RSSI: %3d dB, Priority: %4d", res.get_channel(), res.get_rssi(), res.get_priority()); - } else { - ESP_LOGD(TAG, "- " LOG_SECRET("'%s'") " " LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), bssid_s, - LOG_STR_ARG(get_signal_bars(res.get_rssi()))); - } + ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), + res.get_is_hidden() ? LOG_STR_LITERAL("(HIDDEN) ") : LOG_STR_LITERAL(""), bssid_s, + LOG_STR_ARG(get_signal_bars(res.get_rssi()))); + ESP_LOGD(TAG, " Channel: %2u, RSSI: %3d dB, Priority: %4d", res.get_channel(), res.get_rssi(), res.get_priority()); } +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE +// Helper function to log non-matching scan results at verbose level +__attribute__((noinline)) static void log_scan_result_non_matching(const WiFiScanResult &res) { + char bssid_s[18]; + auto bssid = res.get_bssid(); + format_mac_addr_upper(bssid.data(), bssid_s); + + ESP_LOGV(TAG, "- " LOG_SECRET("'%s'") " " LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), bssid_s, + LOG_STR_ARG(get_signal_bars(res.get_rssi()))); +} +#endif + void WiFiComponent::check_scanning_finished() { if (!this->scan_done_) { if (millis() - this->action_started_ > WIFI_SCAN_TIMEOUT_MS) { @@ -1084,8 +1091,20 @@ void WiFiComponent::check_scanning_finished() { // Sort scan results using insertion sort for better memory efficiency insertion_sort_scan_results(this->scan_result_); + size_t non_matching_count = 0; for (auto &res : this->scan_result_) { - log_scan_result(res); + if (res.get_matches()) { + log_scan_result(res); + } else { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + log_scan_result_non_matching(res); +#else + non_matching_count++; +#endif + } + } + if (non_matching_count > 0) { + ESP_LOGD(TAG, "- %zu non-matching (VERBOSE to show)", non_matching_count); } // SYNCHRONIZATION POINT: Establish link between scan_result_[0] and selected_sta_index_ From ca47bad90ac0c3f561e53946de3832e4ea0384b9 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 17 Dec 2025 23:34:04 -0500 Subject: [PATCH 0705/1145] [template.alarm_control_panel] Fix compile without binary_sensor (#12548) Co-authored-by: Claude --- .../alarm_control_panel/template_alarm_control_panel.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h index bdd3747372..2038d8f1b0 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h @@ -39,6 +39,7 @@ enum TemplateAlarmControlPanelRestoreMode { ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED, }; +#ifdef USE_BINARY_SENSOR struct SensorDataStore { bool last_chime_state; }; @@ -49,7 +50,6 @@ struct SensorInfo { uint8_t store_index; }; -#ifdef USE_BINARY_SENSOR struct AlarmSensor { binary_sensor::BinarySensor *sensor; SensorInfo info; @@ -139,6 +139,9 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl FixedVector sensors_; // a list of automatically bypassed sensors std::vector bypassed_sensor_indicies_; + // Per sensor data store + std::vector sensor_data_; + uint8_t next_store_index_ = 0; #endif TemplateAlarmControlPanelRestoreMode restore_mode_{}; @@ -154,14 +157,11 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl uint32_t trigger_time_; // a list of codes std::vector codes_; - // Per sensor data store - std::vector sensor_data_; // requires a code to arm bool requires_code_to_arm_ = false; bool supports_arm_home_ = false; bool supports_arm_night_ = false; bool sensors_ready_ = false; - uint8_t next_store_index_ = 0; // check if the code is valid bool is_code_valid_(optional code); From 663a4304e01baf6d0d03caa2e105880f8f716116 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 18 Dec 2025 07:50:31 -0500 Subject: [PATCH 0706/1145] [libretiny] Fix millis() ambiguity on BK72XX (#12534) Co-authored-by: Claude --- esphome/core/config.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 97157b6f92..507a39b401 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -382,10 +382,15 @@ def include_file(path: Path, basename: Path, is_c_header: bool = False): ARDUINO_GLUE_CODE = """\ +#undef yield #define yield() esphome::yield() +#undef millis #define millis() esphome::millis() +#undef micros #define micros() esphome::micros() +#undef delay #define delay(x) esphome::delay(x) +#undef delayMicroseconds #define delayMicroseconds(x) esphome::delayMicroseconds(x) """ @@ -536,7 +541,7 @@ async def to_code(config: ConfigType) -> None: if config[CONF_DEBUG_SCHEDULER]: cg.add_define("ESPHOME_DEBUG_SCHEDULER") - if CORE.using_arduino and not CORE.is_bk72xx: + if CORE.using_arduino: CORE.add_job(add_arduino_global_workaround) if config[CONF_INCLUDES]: From b47b7d43fd29d1837a2b1499900ba1424a6aa13d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 18 Dec 2025 09:06:16 -0700 Subject: [PATCH 0707/1145] [api] Remove unused force parameter from encode_message (#12551) --- esphome/components/api/api_pb2.cpp | 24 ++++++++++++------------ esphome/components/api/proto.h | 4 ++-- script/api_protobuf/api_protobuf.py | 22 ++++++++++++++-------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 4a89ee78e1..52f4b495e9 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -124,12 +124,12 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { #endif #ifdef USE_DEVICES for (const auto &it : this->devices) { - buffer.encode_message(20, it, true); + buffer.encode_message(20, it); } #endif #ifdef USE_AREAS for (const auto &it : this->areas) { - buffer.encode_message(21, it, true); + buffer.encode_message(21, it); } #endif #ifdef USE_AREAS @@ -878,13 +878,13 @@ void HomeassistantServiceMap::calculate_size(ProtoSize &size) const { void HomeassistantActionRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->service_ref_); for (auto &it : this->data) { - buffer.encode_message(2, it, true); + buffer.encode_message(2, it); } for (auto &it : this->data_template) { - buffer.encode_message(3, it, true); + buffer.encode_message(3, it); } for (auto &it : this->variables) { - buffer.encode_message(4, it, true); + buffer.encode_message(4, it); } buffer.encode_bool(5, this->is_event); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES @@ -1011,7 +1011,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->name_ref_); buffer.encode_fixed32(2, this->key); for (auto &it : this->args) { - buffer.encode_message(3, it, true); + buffer.encode_message(3, it); } buffer.encode_uint32(4, static_cast(this->supports_response)); } @@ -1867,7 +1867,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_bool(8, this->supports_pause); for (auto &it : this->supported_formats) { - buffer.encode_message(9, it, true); + buffer.encode_message(9, it); } #ifdef USE_DEVICES buffer.encode_uint32(10, this->device_id); @@ -1987,7 +1987,7 @@ void BluetoothLERawAdvertisement::calculate_size(ProtoSize &size) const { } void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const { for (uint16_t i = 0; i < this->advertisements_len; i++) { - buffer.encode_message(1, this->advertisements[i], true); + buffer.encode_message(1, this->advertisements[i]); } } void BluetoothLERawAdvertisementsResponse::calculate_size(ProtoSize &size) const { @@ -2060,7 +2060,7 @@ void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(2, this->handle); buffer.encode_uint32(3, this->properties); for (auto &it : this->descriptors) { - buffer.encode_message(4, it, true); + buffer.encode_message(4, it); } buffer.encode_uint32(5, this->short_uuid); } @@ -2081,7 +2081,7 @@ void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const { } buffer.encode_uint32(2, this->handle); for (auto &it : this->characteristics) { - buffer.encode_message(3, it, true); + buffer.encode_message(3, it); } buffer.encode_uint32(4, this->short_uuid); } @@ -2097,7 +2097,7 @@ void BluetoothGATTService::calculate_size(ProtoSize &size) const { void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); for (auto &it : this->services) { - buffer.encode_message(2, it, true); + buffer.encode_message(2, it); } } void BluetoothGATTGetServicesResponse::calculate_size(ProtoSize &size) const { @@ -2557,7 +2557,7 @@ bool VoiceAssistantConfigurationRequest::decode_length(uint32_t field_id, ProtoL } void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->available_wake_words) { - buffer.encode_message(1, it, true); + buffer.encode_message(1, it); } for (const auto &it : *this->active_wake_words) { buffer.encode_string(2, it, true); diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 83b6922be1..efdab9341c 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -334,7 +334,7 @@ class ProtoWriteBuffer { void encode_sint64(uint32_t field_id, int64_t value, bool force = false) { this->encode_uint64(field_id, encode_zigzag64(value), force); } - void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false); + void encode_message(uint32_t field_id, const ProtoMessage &value); std::vector *get_buffer() const { return buffer_; } protected: @@ -795,7 +795,7 @@ class ProtoSize { }; // Implementation of encode_message - must be after ProtoMessage is defined -inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value, bool force) { +inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value) { this->encode_field_raw(field_id, 2); // type 2: Length-delimited message // Calculate the message size first diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 3412fac5db..cb09ef7050 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -1215,6 +1215,9 @@ class FixedArrayRepeatedType(TypeInfo): """Helper to generate encode statement for a single element.""" if isinstance(self._ti, EnumType): return f"buffer.{self._ti.encode_func}({self.number}, static_cast({element}), true);" + # MessageType.encode_message doesn't have a force parameter + if isinstance(self._ti, MessageType): + return f"buffer.{self._ti.encode_func}({self.number}, {element});" return f"buffer.{self._ti.encode_func}({self.number}, {element}, true);" @property @@ -1536,6 +1539,15 @@ class RepeatedTypeInfo(TypeInfo): # std::vector is specialized for bool, reference does not work return isinstance(self._ti, BoolType) + def _encode_element_call(self, element: str) -> str: + """Helper to generate encode call for a single element.""" + if isinstance(self._ti, EnumType): + return f"buffer.{self._ti.encode_func}({self.number}, static_cast({element}), true);" + # MessageType.encode_message doesn't have a force parameter + if isinstance(self._ti, MessageType): + return f"buffer.{self._ti.encode_func}({self.number}, {element});" + return f"buffer.{self._ti.encode_func}({self.number}, {element}, true);" + @property def encode_content(self) -> str: if self._use_pointer: @@ -1546,17 +1558,11 @@ class RepeatedTypeInfo(TypeInfo): o += f" buffer.{self._ti.encode_func}({self.number}, it, strlen(it), true);\n" else: o = f"for (const auto &it : *this->{self.field_name}) {{\n" - if isinstance(self._ti, EnumType): - o += f" buffer.{self._ti.encode_func}({self.number}, static_cast(it), true);\n" - else: - o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n" + o += f" {self._encode_element_call('it')}\n" o += "}" return o o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" - if isinstance(self._ti, EnumType): - o += f" buffer.{self._ti.encode_func}({self.number}, static_cast(it), true);\n" - else: - o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n" + o += f" {self._encode_element_call('it')}\n" o += "}" return o From 2cf6ed2af729bed731326b2eac6342e19010b8c5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 18 Dec 2025 09:07:35 -0700 Subject: [PATCH 0708/1145] [socket] Refactor socket implementations for memory efficiency and code quality (#12550) --- .../components/socket/bsd_sockets_impl.cpp | 106 ++++++++++-------- .../components/socket/lwip_raw_tcp_impl.cpp | 6 +- .../components/socket/lwip_sockets_impl.cpp | 106 ++++++++++-------- esphome/components/socket/socket.cpp | 28 +---- esphome/components/socket/socket.h | 13 +-- 5 files changed, 122 insertions(+), 137 deletions(-) diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index c7cca62027..09cd81752a 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -12,8 +12,7 @@ #include #endif -namespace esphome { -namespace socket { +namespace esphome::socket { std::string format_sockaddr(const struct sockaddr_storage &storage) { if (storage.ss_family == AF_INET) { @@ -44,11 +43,11 @@ class BSDSocketImpl : public Socket { BSDSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) { #ifdef USE_SOCKET_SELECT_SUPPORT // Register new socket with the application for select() if monitoring requested - if (monitor_loop && fd_ >= 0) { + if (monitor_loop && this->fd_ >= 0) { // Only set loop_monitored_ to true if registration succeeds - loop_monitored_ = App.register_socket_fd(fd_); + this->loop_monitored_ = App.register_socket_fd(this->fd_); } else { - loop_monitored_ = false; + this->loop_monitored_ = false; } #else // Without select support, ignore monitor_loop parameter @@ -56,70 +55,69 @@ class BSDSocketImpl : public Socket { #endif } ~BSDSocketImpl() override { - if (!closed_) { - close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) + if (!this->closed_) { + this->close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) } } - int connect(const struct sockaddr *addr, socklen_t addrlen) override { return ::connect(fd_, addr, addrlen); } + int connect(const struct sockaddr *addr, socklen_t addrlen) override { return ::connect(this->fd_, addr, addrlen); } std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { - return accept_impl_(addr, addrlen, false); - } - std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) override { - return accept_impl_(addr, addrlen, true); - } - - private: - std::unique_ptr accept_impl_(struct sockaddr *addr, socklen_t *addrlen, bool loop_monitored) { - int fd = ::accept(fd_, addr, addrlen); + int fd = ::accept(this->fd_, addr, addrlen); if (fd == -1) return {}; - return make_unique(fd, loop_monitored); + return make_unique(fd, false); + } + std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) override { + int fd = ::accept(this->fd_, addr, addrlen); + if (fd == -1) + return {}; + return make_unique(fd, true); } - public: - int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(fd_, addr, addrlen); } + int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(this->fd_, addr, addrlen); } int close() override { - if (!closed_) { + if (!this->closed_) { #ifdef USE_SOCKET_SELECT_SUPPORT // Unregister from select() before closing if monitored - if (loop_monitored_) { - App.unregister_socket_fd(fd_); + if (this->loop_monitored_) { + App.unregister_socket_fd(this->fd_); } #endif - int ret = ::close(fd_); - closed_ = true; + int ret = ::close(this->fd_); + this->closed_ = true; return ret; } return 0; } - int shutdown(int how) override { return ::shutdown(fd_, how); } + int shutdown(int how) override { return ::shutdown(this->fd_, how); } - int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { return ::getpeername(fd_, addr, addrlen); } + int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { + return ::getpeername(this->fd_, addr, addrlen); + } std::string getpeername() override { struct sockaddr_storage storage; socklen_t len = sizeof(storage); - int err = this->getpeername((struct sockaddr *) &storage, &len); - if (err != 0) + if (::getpeername(this->fd_, (struct sockaddr *) &storage, &len) != 0) return {}; return format_sockaddr(storage); } - int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { return ::getsockname(fd_, addr, addrlen); } + int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { + return ::getsockname(this->fd_, addr, addrlen); + } std::string getsockname() override { struct sockaddr_storage storage; socklen_t len = sizeof(storage); - int err = this->getsockname((struct sockaddr *) &storage, &len); - if (err != 0) + if (::getsockname(this->fd_, (struct sockaddr *) &storage, &len) != 0) return {}; return format_sockaddr(storage); } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { - return ::getsockopt(fd_, level, optname, optval, optlen); + return ::getsockopt(this->fd_, level, optname, optval, optlen); } int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { - return ::setsockopt(fd_, level, optname, optval, optlen); + return ::setsockopt(this->fd_, level, optname, optval, optlen); } - int listen(int backlog) override { return ::listen(fd_, backlog); } - ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); } + int listen(int backlog) override { return ::listen(this->fd_, backlog); } + ssize_t read(void *buf, size_t len) override { return ::read(this->fd_, buf, len); } ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override { #if defined(USE_ESP32) || defined(USE_HOST) return ::recvfrom(this->fd_, buf, len, 0, addr, addr_len); @@ -129,41 +127,52 @@ class BSDSocketImpl : public Socket { } ssize_t readv(const struct iovec *iov, int iovcnt) override { #if defined(USE_ESP32) - return ::lwip_readv(fd_, iov, iovcnt); + return ::lwip_readv(this->fd_, iov, iovcnt); #else - return ::readv(fd_, iov, iovcnt); + return ::readv(this->fd_, iov, iovcnt); #endif } - ssize_t write(const void *buf, size_t len) override { return ::write(fd_, buf, len); } - ssize_t send(void *buf, size_t len, int flags) { return ::send(fd_, buf, len, flags); } + ssize_t write(const void *buf, size_t len) override { return ::write(this->fd_, buf, len); } + ssize_t send(void *buf, size_t len, int flags) { return ::send(this->fd_, buf, len, flags); } ssize_t writev(const struct iovec *iov, int iovcnt) override { #if defined(USE_ESP32) - return ::lwip_writev(fd_, iov, iovcnt); + return ::lwip_writev(this->fd_, iov, iovcnt); #else - return ::writev(fd_, iov, iovcnt); + return ::writev(this->fd_, iov, iovcnt); #endif } ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override { - return ::sendto(fd_, buf, len, flags, to, tolen); // NOLINT(readability-suspicious-call-argument) + return ::sendto(this->fd_, buf, len, flags, to, tolen); // NOLINT(readability-suspicious-call-argument) } int setblocking(bool blocking) override { - int fl = ::fcntl(fd_, F_GETFL, 0); + int fl = ::fcntl(this->fd_, F_GETFL, 0); if (blocking) { fl &= ~O_NONBLOCK; } else { fl |= O_NONBLOCK; } - ::fcntl(fd_, F_SETFL, fl); + ::fcntl(this->fd_, F_SETFL, fl); return 0; } - int get_fd() const override { return fd_; } + int get_fd() const override { return this->fd_; } + +#ifdef USE_SOCKET_SELECT_SUPPORT + bool ready() const override { + if (!this->loop_monitored_) + return true; + return App.is_socket_ready(this->fd_); + } +#endif protected: int fd_; - bool closed_ = false; + bool closed_{false}; +#ifdef USE_SOCKET_SELECT_SUPPORT + bool loop_monitored_{false}; +#endif }; // Helper to create a socket with optional monitoring @@ -182,7 +191,6 @@ std::unique_ptr socket_loop_monitored(int domain, int type, int protocol return create_socket(domain, type, protocol, true); } -} // namespace socket -} // namespace esphome +} // namespace esphome::socket #endif // USE_SOCKET_IMPL_BSD_SOCKETS diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 328df24bdd..cb5d17d5af 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -18,8 +18,7 @@ #include // For esp_schedule() #endif -namespace esphome { -namespace socket { +namespace esphome::socket { #ifdef USE_ESP8266 // Flag to signal socket activity - checked by socket_delay() to exit early @@ -711,7 +710,6 @@ std::unique_ptr socket_loop_monitored(int domain, int type, int protocol return socket(domain, type, protocol); } -} // namespace socket -} // namespace esphome +} // namespace esphome::socket #endif // USE_SOCKET_IMPL_LWIP_TCP diff --git a/esphome/components/socket/lwip_sockets_impl.cpp b/esphome/components/socket/lwip_sockets_impl.cpp index d94c1fb2ff..23fb1a7f6f 100644 --- a/esphome/components/socket/lwip_sockets_impl.cpp +++ b/esphome/components/socket/lwip_sockets_impl.cpp @@ -7,8 +7,7 @@ #include #include "esphome/core/application.h" -namespace esphome { -namespace socket { +namespace esphome::socket { std::string format_sockaddr(const struct sockaddr_storage &storage) { if (storage.ss_family == AF_INET) { @@ -37,11 +36,11 @@ class LwIPSocketImpl : public Socket { LwIPSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) { #ifdef USE_SOCKET_SELECT_SUPPORT // Register new socket with the application for select() if monitoring requested - if (monitor_loop && fd_ >= 0) { + if (monitor_loop && this->fd_ >= 0) { // Only set loop_monitored_ to true if registration succeeds - loop_monitored_ = App.register_socket_fd(fd_); + this->loop_monitored_ = App.register_socket_fd(this->fd_); } else { - loop_monitored_ = false; + this->loop_monitored_ = false; } #else // Without select support, ignore monitor_loop parameter @@ -49,96 +48,108 @@ class LwIPSocketImpl : public Socket { #endif } ~LwIPSocketImpl() override { - if (!closed_) { - close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) + if (!this->closed_) { + this->close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) } } - int connect(const struct sockaddr *addr, socklen_t addrlen) override { return lwip_connect(fd_, addr, addrlen); } + int connect(const struct sockaddr *addr, socklen_t addrlen) override { + return lwip_connect(this->fd_, addr, addrlen); + } std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { - return accept_impl_(addr, addrlen, false); - } - std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) override { - return accept_impl_(addr, addrlen, true); - } - - private: - std::unique_ptr accept_impl_(struct sockaddr *addr, socklen_t *addrlen, bool loop_monitored) { - int fd = lwip_accept(fd_, addr, addrlen); + int fd = lwip_accept(this->fd_, addr, addrlen); if (fd == -1) return {}; - return make_unique(fd, loop_monitored); + return make_unique(fd, false); + } + std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) override { + int fd = lwip_accept(this->fd_, addr, addrlen); + if (fd == -1) + return {}; + return make_unique(fd, true); } - public: - int bind(const struct sockaddr *addr, socklen_t addrlen) override { return lwip_bind(fd_, addr, addrlen); } + int bind(const struct sockaddr *addr, socklen_t addrlen) override { return lwip_bind(this->fd_, addr, addrlen); } int close() override { - if (!closed_) { + if (!this->closed_) { #ifdef USE_SOCKET_SELECT_SUPPORT // Unregister from select() before closing if monitored - if (loop_monitored_) { - App.unregister_socket_fd(fd_); + if (this->loop_monitored_) { + App.unregister_socket_fd(this->fd_); } #endif - int ret = lwip_close(fd_); - closed_ = true; + int ret = lwip_close(this->fd_); + this->closed_ = true; return ret; } return 0; } - int shutdown(int how) override { return lwip_shutdown(fd_, how); } + int shutdown(int how) override { return lwip_shutdown(this->fd_, how); } - int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { return lwip_getpeername(fd_, addr, addrlen); } + int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { + return lwip_getpeername(this->fd_, addr, addrlen); + } std::string getpeername() override { struct sockaddr_storage storage; socklen_t len = sizeof(storage); - int err = this->getpeername((struct sockaddr *) &storage, &len); - if (err != 0) + if (lwip_getpeername(this->fd_, (struct sockaddr *) &storage, &len) != 0) return {}; return format_sockaddr(storage); } - int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { return lwip_getsockname(fd_, addr, addrlen); } + int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { + return lwip_getsockname(this->fd_, addr, addrlen); + } std::string getsockname() override { struct sockaddr_storage storage; socklen_t len = sizeof(storage); - int err = this->getsockname((struct sockaddr *) &storage, &len); - if (err != 0) + if (lwip_getsockname(this->fd_, (struct sockaddr *) &storage, &len) != 0) return {}; return format_sockaddr(storage); } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { - return lwip_getsockopt(fd_, level, optname, optval, optlen); + return lwip_getsockopt(this->fd_, level, optname, optval, optlen); } int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { - return lwip_setsockopt(fd_, level, optname, optval, optlen); + return lwip_setsockopt(this->fd_, level, optname, optval, optlen); } - int listen(int backlog) override { return lwip_listen(fd_, backlog); } - ssize_t read(void *buf, size_t len) override { return lwip_read(fd_, buf, len); } + int listen(int backlog) override { return lwip_listen(this->fd_, backlog); } + ssize_t read(void *buf, size_t len) override { return lwip_read(this->fd_, buf, len); } ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override { - return lwip_recvfrom(fd_, buf, len, 0, addr, addr_len); + return lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len); } - ssize_t readv(const struct iovec *iov, int iovcnt) override { return lwip_readv(fd_, iov, iovcnt); } - ssize_t write(const void *buf, size_t len) override { return lwip_write(fd_, buf, len); } - ssize_t send(void *buf, size_t len, int flags) { return lwip_send(fd_, buf, len, flags); } - ssize_t writev(const struct iovec *iov, int iovcnt) override { return lwip_writev(fd_, iov, iovcnt); } + ssize_t readv(const struct iovec *iov, int iovcnt) override { return lwip_readv(this->fd_, iov, iovcnt); } + ssize_t write(const void *buf, size_t len) override { return lwip_write(this->fd_, buf, len); } + ssize_t send(void *buf, size_t len, int flags) { return lwip_send(this->fd_, buf, len, flags); } + ssize_t writev(const struct iovec *iov, int iovcnt) override { return lwip_writev(this->fd_, iov, iovcnt); } ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override { - return lwip_sendto(fd_, buf, len, flags, to, tolen); + return lwip_sendto(this->fd_, buf, len, flags, to, tolen); } int setblocking(bool blocking) override { - int fl = lwip_fcntl(fd_, F_GETFL, 0); + int fl = lwip_fcntl(this->fd_, F_GETFL, 0); if (blocking) { fl &= ~O_NONBLOCK; } else { fl |= O_NONBLOCK; } - lwip_fcntl(fd_, F_SETFL, fl); + lwip_fcntl(this->fd_, F_SETFL, fl); return 0; } - int get_fd() const override { return fd_; } + int get_fd() const override { return this->fd_; } + +#ifdef USE_SOCKET_SELECT_SUPPORT + bool ready() const override { + if (!this->loop_monitored_) + return true; + return App.is_socket_ready(this->fd_); + } +#endif protected: int fd_; - bool closed_ = false; + bool closed_{false}; +#ifdef USE_SOCKET_SELECT_SUPPORT + bool loop_monitored_{false}; +#endif }; // Helper to create a socket with optional monitoring @@ -157,7 +168,6 @@ std::unique_ptr socket_loop_monitored(int domain, int type, int protocol return create_socket(domain, type, protocol, true); } -} // namespace socket -} // namespace esphome +} // namespace esphome::socket #endif // USE_SOCKET_IMPL_LWIP_SOCKETS diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp index cc9232d21a..ffe0233abc 100644 --- a/esphome/components/socket/socket.cpp +++ b/esphome/components/socket/socket.cpp @@ -6,33 +6,10 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -namespace esphome { -namespace socket { +namespace esphome::socket { Socket::~Socket() {} -bool Socket::ready() const { -#ifdef USE_SOCKET_SELECT_SUPPORT - if (!loop_monitored_) { - // Non-monitored sockets always return true (assume data may be available) - return true; - } - - // For loop-monitored sockets, check with the Application's select() results - int fd = this->get_fd(); - if (fd < 0) { - // No valid file descriptor, assume ready (fallback behavior) - return true; - } - - return App.is_socket_ready(fd); -#else - // Without select() support, we can't monitor sockets in the loop - // Always return true (assume data may be available) - return true; -#endif -} - std::unique_ptr socket_ip(int type, int protocol) { #if USE_NETWORK_IPV6 return socket(AF_INET6, type, protocol); @@ -113,6 +90,5 @@ socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t po return sizeof(sockaddr_in); #endif /* USE_NETWORK_IPV6 */ } -} // namespace socket -} // namespace esphome +} // namespace esphome::socket #endif diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 8936b2cd10..75eb07de4a 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -6,8 +6,7 @@ #include "headers.h" #if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) -namespace esphome { -namespace socket { +namespace esphome::socket { class Socket { public: @@ -54,12 +53,7 @@ class Socket { /// Check if socket has data ready to read /// For loop-monitored sockets, checks with the Application's select() results /// For non-monitored sockets, always returns true (assumes data may be available) - bool ready() const; - - protected: -#ifdef USE_SOCKET_SELECT_SUPPORT - bool loop_monitored_{false}; ///< Whether this socket is monitored by the event loop -#endif + virtual bool ready() const { return true; } }; /// Create a socket of the given domain, type and protocol. @@ -91,6 +85,5 @@ void socket_delay(uint32_t ms); void socket_wake(); #endif -} // namespace socket -} // namespace esphome +} // namespace esphome::socket #endif From 41fd1762e927cb63fe77e9d10b8f6fad7d5656b5 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:46:16 -0500 Subject: [PATCH 0709/1145] [core] Deprecate custom_components folder (#12552) Co-authored-by: Claude --- esphome/loader.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/loader.py b/esphome/loader.py index 387443c032..968c8cf3e0 100644 --- a/esphome/loader.py +++ b/esphome/loader.py @@ -187,7 +187,14 @@ def install_meta_finder( def install_custom_components_meta_finder(): + # Remove before 2026.6.0 custom_components_dir = (Path(CORE.config_dir) / "custom_components").resolve() + if custom_components_dir.is_dir() and any(custom_components_dir.iterdir()): + _LOGGER.warning( + "The 'custom_components' folder is deprecated and will be removed in 2026.6.0. " + "Please use 'external_components' instead. " + "See https://esphome.io/components/external_components.html for more information." + ) install_meta_finder(custom_components_dir) From 1c50c2b672f9e70065169ebfe0082a27478bf2e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:19:19 -1000 Subject: [PATCH 0710/1145] Bump ruamel-yaml from 0.18.16 to 0.18.17 (#12555) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 011a2d4f0b..62352ce754 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ esphome-dashboard==20251013.0 aioesphomeapi==43.3.0 zeroconf==0.148.0 puremagic==1.30 -ruamel.yaml==0.18.16 # dashboard_import +ruamel.yaml==0.18.17 # dashboard_import ruamel.yaml.clib==0.2.15 # dashboard_import esphome-glyphsets==0.2.0 pillow==11.3.0 From 7ae3a11d6b4b9fc97dc84406c3fc51360fa8c293 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Thu, 18 Dec 2025 19:42:47 -0600 Subject: [PATCH 0711/1145] [esp32_ble, esp32_ble_tracker] Fix crash, error messages when `ble.disable` called during boot (#12560) --- esphome/components/esp32_ble/ble.cpp | 16 ++++++++++++---- esphome/components/esp32_ble/ble.h | 12 +++++++++--- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 5 ++++- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index a0ed9ee90c..a279f7d2a4 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -308,13 +308,21 @@ bool ESP32BLE::ble_setup_() { bool ESP32BLE::ble_dismantle_() { esp_err_t err = esp_bluedroid_disable(); if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err); - return false; + // ESP_ERR_INVALID_STATE means Bluedroid is already disabled, which is fine + if (err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err); + return false; + } + ESP_LOGD(TAG, "Already disabled"); } err = esp_bluedroid_deinit(); if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_bluedroid_deinit failed: %d", err); - return false; + // ESP_ERR_INVALID_STATE means Bluedroid is already deinitialized, which is fine + if (err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "esp_bluedroid_deinit failed: %d", err); + return false; + } + ESP_LOGD(TAG, "Already deinitialized"); } #ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 393ec2e911..1999c870f8 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -212,17 +212,23 @@ extern ESP32BLE *global_ble; template class BLEEnabledCondition : public Condition { public: - bool check(const Ts &...x) override { return global_ble->is_active(); } + bool check(const Ts &...x) override { return global_ble != nullptr && global_ble->is_active(); } }; template class BLEEnableAction : public Action { public: - void play(const Ts &...x) override { global_ble->enable(); } + void play(const Ts &...x) override { + if (global_ble != nullptr) + global_ble->enable(); + } }; template class BLEDisableAction : public Action { public: - void play(const Ts &...x) override { global_ble->disable(); } + void play(const Ts &...x) override { + if (global_ble != nullptr) + global_ble->disable(); + } }; } // namespace esphome::esp32_ble diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index d3c5edfb94..45e343c0d2 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -185,7 +185,10 @@ void ESP32BLETracker::ble_before_disabled_event_handler() { this->stop_scan_(); void ESP32BLETracker::stop_scan_() { if (this->scanner_state_ != ScannerState::RUNNING && this->scanner_state_ != ScannerState::FAILED) { - ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_)); + // If scanner is already idle, there's nothing to stop - this is not an error + if (this->scanner_state_ != ScannerState::IDLE) { + ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_)); + } return; } // Reset timeout state machine when stopping scan From f962497db1c7466155b414eab9ccd1b7aae11cc9 Mon Sep 17 00:00:00 2001 From: pixelgrb <10325178+pixelgrb@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:13:36 -0800 Subject: [PATCH 0712/1145] [mmc5603] enable AUTO_SR_en to compensate for temperature drift (#12556) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/mmc5603/mmc5603.cpp | 4 +++- esphome/components/mmc5603/mmc5603.h | 2 ++ esphome/components/mmc5603/sensor.py | 5 +++++ tests/components/mmc5603/common.yaml | 1 + 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/esphome/components/mmc5603/mmc5603.cpp b/esphome/components/mmc5603/mmc5603.cpp index f0d1044f3f..d6321eae8f 100644 --- a/esphome/components/mmc5603/mmc5603.cpp +++ b/esphome/components/mmc5603/mmc5603.cpp @@ -83,6 +83,7 @@ void MMC5603Component::dump_config() { ESP_LOGE(TAG, "The ID registers don't match - Is this really an MMC5603?"); } LOG_UPDATE_INTERVAL(this); + ESP_LOGCONFIG(TAG, " Auto set/reset: %s", ONOFF(this->auto_set_reset_)); LOG_SENSOR(" ", "X Axis", this->x_sensor_); LOG_SENSOR(" ", "Y Axis", this->y_sensor_); @@ -93,7 +94,8 @@ void MMC5603Component::dump_config() { float MMC5603Component::get_setup_priority() const { return setup_priority::DATA; } void MMC5603Component::update() { - if (!this->write_byte(MMC56X3_CTRL0_REG, 0x01)) { + uint8_t ctrl0 = (this->auto_set_reset_) ? 0x21 : 0x01; + if (!this->write_byte(MMC56X3_CTRL0_REG, ctrl0)) { this->status_set_warning(); return; } diff --git a/esphome/components/mmc5603/mmc5603.h b/esphome/components/mmc5603/mmc5603.h index cd0893053c..09718bd3b7 100644 --- a/esphome/components/mmc5603/mmc5603.h +++ b/esphome/components/mmc5603/mmc5603.h @@ -25,6 +25,7 @@ class MMC5603Component : public PollingComponent, public i2c::I2CDevice { void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; } void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; } void set_heading_sensor(sensor::Sensor *heading_sensor) { heading_sensor_ = heading_sensor; } + void set_auto_set_reset(bool auto_set_reset) { auto_set_reset_ = auto_set_reset; } protected: MMC5603Datarate datarate_; @@ -32,6 +33,7 @@ class MMC5603Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *y_sensor_{nullptr}; sensor::Sensor *z_sensor_{nullptr}; sensor::Sensor *heading_sensor_{nullptr}; + bool auto_set_reset_{true}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/mmc5603/sensor.py b/esphome/components/mmc5603/sensor.py index 3223225271..5b3982cee6 100644 --- a/esphome/components/mmc5603/sensor.py +++ b/esphome/components/mmc5603/sensor.py @@ -16,6 +16,8 @@ from esphome.const import ( UNIT_MICROTESLA, ) +CONF_AUTO_SET_RESET = "auto_set_reset" + DEPENDENCIES = ["i2c"] mmc5603_ns = cg.esphome_ns.namespace("mmc5603") @@ -54,6 +56,7 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema, cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema, cv.Optional(CONF_HEADING): heading_schema, + cv.Optional(CONF_AUTO_SET_RESET, default=True): cv.boolean, } ) .extend(cv.polling_component_schema("60s")) @@ -88,3 +91,5 @@ async def to_code(config): if CONF_HEADING in config: sens = await sensor.new_sensor(config[CONF_HEADING]) cg.add(var.set_heading_sensor(sens)) + if CONF_AUTO_SET_RESET in config: + cg.add(var.set_auto_set_reset(config[CONF_AUTO_SET_RESET])) diff --git a/tests/components/mmc5603/common.yaml b/tests/components/mmc5603/common.yaml index 6f6e35e9af..ddb5c3b25e 100644 --- a/tests/components/mmc5603/common.yaml +++ b/tests/components/mmc5603/common.yaml @@ -2,6 +2,7 @@ sensor: - platform: mmc5603 i2c_id: i2c_bus address: 0x30 + auto_set_reset: true field_strength_x: name: HMC5883L Field Strength X field_strength_y: From 7e080920122246bd37863b117ccafd7c8b74a0ed Mon Sep 17 00:00:00 2001 From: Anna Oake Date: Wed, 17 Dec 2025 20:19:18 +0100 Subject: [PATCH 0713/1145] [cc1101] Fix default frequencies (#12539) --- esphome/components/cc1101/cc1101.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 5b6eb545bc..1fe402d6c6 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -99,11 +99,11 @@ CC1101Component::CC1101Component() { this->state_.FS_AUTOCAL = 1; // Default Settings - this->set_frequency(433920); - this->set_if_frequency(153); - this->set_filter_bandwidth(203); + this->set_frequency(433920000); + this->set_if_frequency(153000); + this->set_filter_bandwidth(203000); this->set_channel(0); - this->set_channel_spacing(200); + this->set_channel_spacing(200000); this->set_symbol_rate(5000); this->set_sync_mode(SyncMode::SYNC_MODE_NONE); this->set_carrier_sense_above_threshold(true); From 195b1c632369011fe1fe6b841a75126ce9b84758 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Wed, 17 Dec 2025 20:40:31 +0000 Subject: [PATCH 0714/1145] [pm1006] Fix "never" update interval detection (#12529) --- esphome/components/pm1006/sensor.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/esphome/components/pm1006/sensor.py b/esphome/components/pm1006/sensor.py index c693cfea19..8ff21ab069 100644 --- a/esphome/components/pm1006/sensor.py +++ b/esphome/components/pm1006/sensor.py @@ -7,10 +7,10 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, DEVICE_CLASS_PM25, ICON_BLUR, + SCHEDULER_DONT_RUN, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ) -from esphome.core import TimePeriodMilliseconds CODEOWNERS = ["@habbie"] DEPENDENCIES = ["uart"] @@ -41,16 +41,12 @@ CONFIG_SCHEMA = cv.All( def validate_interval_uart(config): - require_tx = False - interval = config.get(CONF_UPDATE_INTERVAL) - - if isinstance(interval, TimePeriodMilliseconds): - # 'never' is encoded as a very large int, not as a TimePeriodMilliseconds objects - require_tx = True - uart.final_validate_device_schema( - "pm1006", baud_rate=9600, require_rx=True, require_tx=require_tx + "pm1006", + baud_rate=9600, + require_rx=True, + require_tx=interval.total_milliseconds != SCHEDULER_DONT_RUN, )(config) From 636be92c97f6c6dd7da511a6420768981253f5e2 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:07:42 -0500 Subject: [PATCH 0715/1145] [bme68x_bsec2_i2c] Add MULTI_CONF support for multiple sensors (#12535) Co-authored-by: Claude --- esphome/components/bme68x_bsec2_i2c/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/bme68x_bsec2_i2c/__init__.py b/esphome/components/bme68x_bsec2_i2c/__init__.py index d6fb7fa9be..c8ca0ba022 100644 --- a/esphome/components/bme68x_bsec2_i2c/__init__.py +++ b/esphome/components/bme68x_bsec2_i2c/__init__.py @@ -11,6 +11,7 @@ CODEOWNERS = ["@neffs", "@kbx81"] AUTO_LOAD = ["bme68x_bsec2"] DEPENDENCIES = ["i2c"] +MULTI_CONF = True bme68x_bsec2_i2c_ns = cg.esphome_ns.namespace("bme68x_bsec2_i2c") BME68xBSEC2I2CComponent = bme68x_bsec2_i2c_ns.class_( From 3e38a5e630978ab076a34223948d22ec53922c75 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:37:59 -0500 Subject: [PATCH 0716/1145] [esp32_camera] Fix I2C driver conflict with other components (#12533) Co-authored-by: Claude Co-authored-by: J. Nick Koston --- esphome/components/esp32_camera/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index d9d9bc0a56..2ad48173f1 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -3,7 +3,7 @@ import logging from esphome import automation, pins import esphome.codegen as cg from esphome.components import i2c -from esphome.components.esp32 import add_idf_component +from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option from esphome.components.psram import DOMAIN as psram_domain import esphome.config_validation as cv from esphome.const import ( @@ -352,6 +352,8 @@ async def to_code(config): cg.add_define("USE_CAMERA") add_idf_component(name="espressif/esp32-camera", ref="2.1.1") + add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW", True) + add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY", False) for conf in config.get(CONF_ON_STREAM_START, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) From 7ca11764abf1091d2f009599f49dcece33b0d05e Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 17 Dec 2025 23:34:04 -0500 Subject: [PATCH 0717/1145] [template.alarm_control_panel] Fix compile without binary_sensor (#12548) Co-authored-by: Claude --- .../alarm_control_panel/template_alarm_control_panel.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h index bdd3747372..2038d8f1b0 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h @@ -39,6 +39,7 @@ enum TemplateAlarmControlPanelRestoreMode { ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED, }; +#ifdef USE_BINARY_SENSOR struct SensorDataStore { bool last_chime_state; }; @@ -49,7 +50,6 @@ struct SensorInfo { uint8_t store_index; }; -#ifdef USE_BINARY_SENSOR struct AlarmSensor { binary_sensor::BinarySensor *sensor; SensorInfo info; @@ -139,6 +139,9 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl FixedVector sensors_; // a list of automatically bypassed sensors std::vector bypassed_sensor_indicies_; + // Per sensor data store + std::vector sensor_data_; + uint8_t next_store_index_ = 0; #endif TemplateAlarmControlPanelRestoreMode restore_mode_{}; @@ -154,14 +157,11 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl uint32_t trigger_time_; // a list of codes std::vector codes_; - // Per sensor data store - std::vector sensor_data_; // requires a code to arm bool requires_code_to_arm_ = false; bool supports_arm_home_ = false; bool supports_arm_night_ = false; bool sensors_ready_ = false; - uint8_t next_store_index_ = 0; // check if the code is valid bool is_code_valid_(optional code); From f0d0ea60a727f020aafe26471ba22b5168b6e73a Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Thu, 18 Dec 2025 19:42:47 -0600 Subject: [PATCH 0718/1145] [esp32_ble, esp32_ble_tracker] Fix crash, error messages when `ble.disable` called during boot (#12560) --- esphome/components/esp32_ble/ble.cpp | 16 ++++++++++++---- esphome/components/esp32_ble/ble.h | 12 +++++++++--- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 5 ++++- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index a0ed9ee90c..a279f7d2a4 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -308,13 +308,21 @@ bool ESP32BLE::ble_setup_() { bool ESP32BLE::ble_dismantle_() { esp_err_t err = esp_bluedroid_disable(); if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err); - return false; + // ESP_ERR_INVALID_STATE means Bluedroid is already disabled, which is fine + if (err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err); + return false; + } + ESP_LOGD(TAG, "Already disabled"); } err = esp_bluedroid_deinit(); if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_bluedroid_deinit failed: %d", err); - return false; + // ESP_ERR_INVALID_STATE means Bluedroid is already deinitialized, which is fine + if (err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "esp_bluedroid_deinit failed: %d", err); + return false; + } + ESP_LOGD(TAG, "Already deinitialized"); } #ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 393ec2e911..1999c870f8 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -212,17 +212,23 @@ extern ESP32BLE *global_ble; template class BLEEnabledCondition : public Condition { public: - bool check(const Ts &...x) override { return global_ble->is_active(); } + bool check(const Ts &...x) override { return global_ble != nullptr && global_ble->is_active(); } }; template class BLEEnableAction : public Action { public: - void play(const Ts &...x) override { global_ble->enable(); } + void play(const Ts &...x) override { + if (global_ble != nullptr) + global_ble->enable(); + } }; template class BLEDisableAction : public Action { public: - void play(const Ts &...x) override { global_ble->disable(); } + void play(const Ts &...x) override { + if (global_ble != nullptr) + global_ble->disable(); + } }; } // namespace esphome::esp32_ble diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index d3c5edfb94..45e343c0d2 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -185,7 +185,10 @@ void ESP32BLETracker::ble_before_disabled_event_handler() { this->stop_scan_(); void ESP32BLETracker::stop_scan_() { if (this->scanner_state_ != ScannerState::RUNNING && this->scanner_state_ != ScannerState::FAILED) { - ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_)); + // If scanner is already idle, there's nothing to stop - this is not an error + if (this->scanner_state_ != ScannerState::IDLE) { + ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_)); + } return; } // Reset timeout state machine when stopping scan From 3a888326d8056867380b8b26dffea1b24246d1af Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:13:35 -0500 Subject: [PATCH 0719/1145] Bump version to 2025.12.1 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 7dfcbd6b6f..4c533ec87f 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.0 +PROJECT_NUMBER = 2025.12.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 111396cab5..8f9a3497ff 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.0" +__version__ = "2025.12.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 940e6194816f25401e57ec76fd33fff54f814141 Mon Sep 17 00:00:00 2001 From: Jas Strong Date: Fri, 19 Dec 2025 10:42:11 -0800 Subject: [PATCH 0720/1145] [aqi, hm3301, pmsx003] Air Quality Index improvements (#12203) Co-authored-by: jas Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/aqi/__init__.py | 14 ++++++++ .../{hm3301 => aqi}/abstract_aqi_calculator.h | 6 ++-- .../{hm3301 => aqi}/aqi_calculator.h | 11 +++--- .../{hm3301 => aqi}/aqi_calculator_factory.h | 14 ++++---- .../{hm3301 => aqi}/caqi_calculator.h | 6 ++-- esphome/components/hm3301/hm3301.cpp | 2 +- esphome/components/hm3301/hm3301.h | 8 ++--- esphome/components/hm3301/sensor.py | 10 ++---- esphome/components/pmsx003/pmsx003.cpp | 34 ++++++++++++++++--- esphome/components/pmsx003/pmsx003.h | 12 +++++++ esphome/components/pmsx003/sensor.py | 26 ++++++++++++++ tests/components/pmsx003/common.yaml | 3 ++ 13 files changed, 109 insertions(+), 38 deletions(-) create mode 100644 esphome/components/aqi/__init__.py rename esphome/components/{hm3301 => aqi}/abstract_aqi_calculator.h (64%) rename esphome/components/{hm3301 => aqi}/aqi_calculator.h (94%) rename esphome/components/{hm3301 => aqi}/aqi_calculator_factory.h (55%) rename esphome/components/{hm3301 => aqi}/caqi_calculator.h (94%) diff --git a/CODEOWNERS b/CODEOWNERS index fc27253d23..21be3e36d1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -42,6 +42,7 @@ esphome/components/animation/* @syndlex esphome/components/anova/* @buxtronix esphome/components/apds9306/* @aodrenah esphome/components/api/* @esphome/core +esphome/components/aqi/* @freekode @jasstrong @ximex esphome/components/as5600/* @ammmze esphome/components/as5600/sensor/* @ammmze esphome/components/as7341/* @mrgnr diff --git a/esphome/components/aqi/__init__.py b/esphome/components/aqi/__init__.py new file mode 100644 index 0000000000..4b979ab406 --- /dev/null +++ b/esphome/components/aqi/__init__.py @@ -0,0 +1,14 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@jasstrong", "@ximex", "@freekode"] + +aqi_ns = cg.esphome_ns.namespace("aqi") +AQICalculatorType = aqi_ns.enum("AQICalculatorType") + +CONF_AQI = "aqi" +CONF_CALCULATION_TYPE = "calculation_type" + +AQI_CALCULATION_TYPE = { + "CAQI": AQICalculatorType.CAQI_TYPE, + "AQI": AQICalculatorType.AQI_TYPE, +} diff --git a/esphome/components/hm3301/abstract_aqi_calculator.h b/esphome/components/aqi/abstract_aqi_calculator.h similarity index 64% rename from esphome/components/hm3301/abstract_aqi_calculator.h rename to esphome/components/aqi/abstract_aqi_calculator.h index 038828e9de..7836c76cdc 100644 --- a/esphome/components/hm3301/abstract_aqi_calculator.h +++ b/esphome/components/aqi/abstract_aqi_calculator.h @@ -2,13 +2,11 @@ #include -namespace esphome { -namespace hm3301 { +namespace esphome::aqi { class AbstractAQICalculator { public: virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0; }; -} // namespace hm3301 -} // namespace esphome +} // namespace esphome::aqi diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/aqi/aqi_calculator.h similarity index 94% rename from esphome/components/hm3301/aqi_calculator.h rename to esphome/components/aqi/aqi_calculator.h index aa01060d2c..959d6a2438 100644 --- a/esphome/components/hm3301/aqi_calculator.h +++ b/esphome/components/aqi/aqi_calculator.h @@ -1,10 +1,11 @@ #pragma once + #include #include "abstract_aqi_calculator.h" + // https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf -namespace esphome { -namespace hm3301 { +namespace esphome::aqi { class AQICalculator : public AbstractAQICalculator { public: @@ -28,6 +29,9 @@ class AQICalculator : public AbstractAQICalculator { int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { int grid_index = get_grid_index_(value, array); + if (grid_index == -1) { + return -1; + } int aqi_lo = index_grid_[grid_index][0]; int aqi_hi = index_grid_[grid_index][1]; int conc_lo = array[grid_index][0]; @@ -46,5 +50,4 @@ class AQICalculator : public AbstractAQICalculator { } }; -} // namespace hm3301 -} // namespace esphome +} // namespace esphome::aqi diff --git a/esphome/components/hm3301/aqi_calculator_factory.h b/esphome/components/aqi/aqi_calculator_factory.h similarity index 55% rename from esphome/components/hm3301/aqi_calculator_factory.h rename to esphome/components/aqi/aqi_calculator_factory.h index 55608b6e51..db7eaab1bb 100644 --- a/esphome/components/hm3301/aqi_calculator_factory.h +++ b/esphome/components/aqi/aqi_calculator_factory.h @@ -3,8 +3,7 @@ #include "caqi_calculator.h" #include "aqi_calculator.h" -namespace esphome { -namespace hm3301 { +namespace esphome::aqi { enum AQICalculatorType { CAQI_TYPE = 0, AQI_TYPE = 1 }; @@ -12,18 +11,17 @@ class AQICalculatorFactory { public: AbstractAQICalculator *get_calculator(AQICalculatorType type) { if (type == 0) { - return caqi_calculator_; + return &this->caqi_calculator_; } else if (type == 1) { - return aqi_calculator_; + return &this->aqi_calculator_; } return nullptr; } protected: - CAQICalculator *caqi_calculator_ = new CAQICalculator(); - AQICalculator *aqi_calculator_ = new AQICalculator(); + CAQICalculator caqi_calculator_; + AQICalculator aqi_calculator_; }; -} // namespace hm3301 -} // namespace esphome +} // namespace esphome::aqi diff --git a/esphome/components/hm3301/caqi_calculator.h b/esphome/components/aqi/caqi_calculator.h similarity index 94% rename from esphome/components/hm3301/caqi_calculator.h rename to esphome/components/aqi/caqi_calculator.h index 3f338776d8..d493dcdf39 100644 --- a/esphome/components/hm3301/caqi_calculator.h +++ b/esphome/components/aqi/caqi_calculator.h @@ -3,8 +3,7 @@ #include "esphome/core/log.h" #include "abstract_aqi_calculator.h" -namespace esphome { -namespace hm3301 { +namespace esphome::aqi { class CAQICalculator : public AbstractAQICalculator { public: @@ -48,5 +47,4 @@ class CAQICalculator : public AbstractAQICalculator { } }; -} // namespace hm3301 -} // namespace esphome +} // namespace esphome::aqi diff --git a/esphome/components/hm3301/hm3301.cpp b/esphome/components/hm3301/hm3301.cpp index a19d9dd09f..00fb85397c 100644 --- a/esphome/components/hm3301/hm3301.cpp +++ b/esphome/components/hm3301/hm3301.cpp @@ -63,7 +63,7 @@ void HM3301Component::update() { int16_t aqi_value = -1; if (this->aqi_sensor_ != nullptr && pm_2_5_value != -1 && pm_10_0_value != -1) { - AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_); + aqi::AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_); aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value); } diff --git a/esphome/components/hm3301/hm3301.h b/esphome/components/hm3301/hm3301.h index 6779b4e195..e155ed6b4b 100644 --- a/esphome/components/hm3301/hm3301.h +++ b/esphome/components/hm3301/hm3301.h @@ -3,7 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" -#include "aqi_calculator_factory.h" +#include "esphome/components/aqi/aqi_calculator_factory.h" namespace esphome { namespace hm3301 { @@ -19,7 +19,7 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { void set_pm_10_0_sensor(sensor::Sensor *pm_10_0_sensor) { pm_10_0_sensor_ = pm_10_0_sensor; } void set_aqi_sensor(sensor::Sensor *aqi_sensor) { aqi_sensor_ = aqi_sensor; } - void set_aqi_calculation_type(AQICalculatorType aqi_calc_type) { aqi_calc_type_ = aqi_calc_type; } + void set_aqi_calculation_type(aqi::AQICalculatorType aqi_calc_type) { aqi_calc_type_ = aqi_calc_type; } void setup() override; void dump_config() override; @@ -41,8 +41,8 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *pm_10_0_sensor_{nullptr}; sensor::Sensor *aqi_sensor_{nullptr}; - AQICalculatorType aqi_calc_type_; - AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory(); + aqi::AQICalculatorType aqi_calc_type_; + aqi::AQICalculatorFactory aqi_calculator_factory_ = aqi::AQICalculatorFactory(); bool validate_checksum_(const uint8_t *data); uint16_t get_sensor_value_(const uint8_t *data, uint8_t i); diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index 5eb1773518..389da97b1e 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import i2c, sensor +from esphome.components.aqi import AQI_CALCULATION_TYPE, CONF_AQI, CONF_CALCULATION_TYPE import esphome.config_validation as cv from esphome.const import ( CONF_ID, @@ -16,23 +17,16 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["aqi"] CODEOWNERS = ["@freekode"] hm3301_ns = cg.esphome_ns.namespace("hm3301") HM3301Component = hm3301_ns.class_( "HM3301Component", cg.PollingComponent, i2c.I2CDevice ) -AQICalculatorType = hm3301_ns.enum("AQICalculatorType") -CONF_AQI = "aqi" -CONF_CALCULATION_TYPE = "calculation_type" UNIT_INDEX = "index" -AQI_CALCULATION_TYPE = { - "CAQI": AQICalculatorType.CAQI_TYPE, - "AQI": AQICalculatorType.AQI_TYPE, -} - def _validate(config): if CONF_AQI in config and CONF_PM_2_5 not in config: diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index eb10d19c91..3bdb5219ed 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -18,6 +18,8 @@ static const uint16_t PMS_CMD_MEASUREMENT_MODE_ACTIVE = 0x0001; // automaticall static const uint16_t PMS_CMD_SLEEP_MODE_SLEEP = 0x0000; // go to sleep mode static const uint16_t PMS_CMD_SLEEP_MODE_WAKEUP = 0x0001; // wake up from sleep mode +void PMSX003Component::setup() {} + void PMSX003Component::dump_config() { ESP_LOGCONFIG(TAG, "PMSX003:"); LOG_SENSOR(" ", "PM1.0STD", this->pm_1_0_std_sensor_); @@ -39,21 +41,36 @@ void PMSX003Component::dump_config() { LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); + + if (this->update_interval_ <= PMS_STABILISING_MS) { + ESP_LOGCONFIG(TAG, " Mode: active continuous (sensor default)"); + } else { + ESP_LOGCONFIG(TAG, " Mode: passive with sleep/wake cycles"); + } + this->check_uart_settings(9600); } void PMSX003Component::loop() { const uint32_t now = App.get_loop_component_start_time(); + // Initialize sensor mode on first loop + if (this->initialised_ == 0) { + if (this->update_interval_ > PMS_STABILISING_MS) { + // Long update interval: use passive mode with sleep/wake cycles + this->send_command_(PMS_CMD_MEASUREMENT_MODE, PMS_CMD_MEASUREMENT_MODE_PASSIVE); + this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_WAKEUP); + } else { + // Short/zero update interval: use active continuous mode + this->send_command_(PMS_CMD_MEASUREMENT_MODE, PMS_CMD_MEASUREMENT_MODE_ACTIVE); + } + this->initialised_ = 1; + } + // If we update less often than it takes the device to stabilise, spin the fan down // rather than running it constantly. It does take some time to stabilise, so we // need to keep track of what state we're in. if (this->update_interval_ > PMS_STABILISING_MS) { - if (this->initialised_ == 0) { - this->send_command_(PMS_CMD_MEASUREMENT_MODE, PMS_CMD_MEASUREMENT_MODE_PASSIVE); - this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_WAKEUP); - this->initialised_ = 1; - } switch (this->state_) { case PMSX003_STATE_IDLE: // Power on the sensor now so it'll be ready when we hit the update time @@ -248,6 +265,13 @@ void PMSX003Component::parse_data_() { if (this->pm_particles_25um_sensor_ != nullptr) this->pm_particles_25um_sensor_->publish_state(pm_particles_25um); + // Calculate and publish AQI if sensor is configured + if (this->aqi_sensor_ != nullptr) { + aqi::AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_); + int32_t aqi_value = calculator->get_aqi(pm_2_5_concentration, pm_10_0_concentration); + this->aqi_sensor_->publish_state(aqi_value); + } + if (this->type_ == PMSX003_TYPE_5003T) { ESP_LOGD(TAG, "Got PM0.3 Particles: %u Count/0.1L, PM0.5 Particles: %u Count/0.1L, PM1.0 Particles: %u Count/0.1L, " diff --git a/esphome/components/pmsx003/pmsx003.h b/esphome/components/pmsx003/pmsx003.h index ba607b4487..229972e2e5 100644 --- a/esphome/components/pmsx003/pmsx003.h +++ b/esphome/components/pmsx003/pmsx003.h @@ -4,6 +4,7 @@ #include "esphome/core/helpers.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/uart/uart.h" +#include "esphome/components/aqi/aqi_calculator_factory.h" namespace esphome { namespace pmsx003 { @@ -31,6 +32,7 @@ enum PMSX003State { class PMSX003Component : public uart::UARTDevice, public Component { public: PMSX003Component() = default; + void setup() override; void dump_config() override; void loop() override; @@ -72,6 +74,10 @@ class PMSX003Component : public uart::UARTDevice, public Component { void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } + void set_aqi_sensor(sensor::Sensor *aqi_sensor) { aqi_sensor_ = aqi_sensor; } + + void set_aqi_calculation_type(aqi::AQICalculatorType aqi_calc_type) { aqi_calc_type_ = aqi_calc_type; } + protected: optional check_byte_(); void parse_data_(); @@ -115,6 +121,12 @@ class PMSX003Component : public uart::UARTDevice, public Component { // Temperature and Humidity sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; + + // AQI + sensor::Sensor *aqi_sensor_{nullptr}; + + aqi::AQICalculatorType aqi_calc_type_; + aqi::AQICalculatorFactory aqi_calculator_factory_ = aqi::AQICalculatorFactory(); }; } // namespace pmsx003 diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index bebd3a01ee..b2d6744547 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import sensor, uart +from esphome.components.aqi import AQI_CALCULATION_TYPE, CONF_AQI, CONF_CALCULATION_TYPE import esphome.config_validation as cv from esphome.const import ( CONF_FORMALDEHYDE, @@ -20,6 +21,7 @@ from esphome.const import ( CONF_TEMPERATURE, CONF_TYPE, CONF_UPDATE_INTERVAL, + DEVICE_CLASS_AQI, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PM1, DEVICE_CLASS_PM10, @@ -35,11 +37,13 @@ from esphome.const import ( CODEOWNERS = ["@ximex"] DEPENDENCIES = ["uart"] +AUTO_LOAD = ["aqi"] pmsx003_ns = cg.esphome_ns.namespace("pmsx003") PMSX003Component = pmsx003_ns.class_("PMSX003Component", uart.UARTDevice, cg.Component) PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor) +UNIT_INDEX = "index" TYPE_PMSX003 = "PMSX003" TYPE_PMS5003T = "PMS5003T" TYPE_PMS5003ST = "PMS5003ST" @@ -77,6 +81,10 @@ def validate_pmsx003_sensors(value): for key, types in SENSORS_TO_TYPE.items(): if key in value and value[CONF_TYPE] not in types: raise cv.Invalid(f"{value[CONF_TYPE]} does not have {key} sensor!") + if CONF_AQI in value and CONF_PM_2_5 not in value: + raise cv.Invalid("AQI computation requires PM 2.5 sensor") + if CONF_AQI in value and CONF_PM_10_0 not in value: + raise cv.Invalid("AQI computation requires PM 10 sensor") return value @@ -192,6 +200,19 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_AQI): sensor.sensor_schema( + unit_of_measurement=UNIT_INDEX, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + device_class=DEVICE_CLASS_AQI, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Required(CONF_CALCULATION_TYPE): cv.enum( + AQI_CALCULATION_TYPE, upper=True + ), + } + ), cv.Optional(CONF_UPDATE_INTERVAL, default="0s"): validate_update_interval, } ) @@ -278,4 +299,9 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_HUMIDITY]) cg.add(var.set_humidity_sensor(sens)) + if CONF_AQI in config: + sens = await sensor.new_sensor(config[CONF_AQI]) + cg.add(var.set_aqi_sensor(sens)) + cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) + cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL])) diff --git a/tests/components/pmsx003/common.yaml b/tests/components/pmsx003/common.yaml index 3c60995804..9dd79723d1 100644 --- a/tests/components/pmsx003/common.yaml +++ b/tests/components/pmsx003/common.yaml @@ -25,4 +25,7 @@ sensor: name: Particulate Count >5.0um pm_10_0um: name: Particulate Count >10.0um + aqi: + name: AQI + calculation_type: AQI update_interval: 30s From 26c16f4ca25e1d4ac562ad484d4c1a9e7b5fedcf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 09:18:33 -1000 Subject: [PATCH 0721/1145] Bump voluptuous from 0.15.2 to 0.16.0 (#12573) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 62352ce754..5c3d82e219 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ cryptography==45.0.1 -voluptuous==0.15.2 +voluptuous==0.16.0 PyYAML==6.0.3 paho-mqtt==1.6.1 colorama==0.4.6 From 59b38d79b40a7956b9db1a42c5ceb644aee7c9b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 09:18:52 -1000 Subject: [PATCH 0722/1145] Bump docker/setup-buildx-action from 3.11.1 to 3.12.0 in the docker-actions group (#12574) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-docker.yml | 2 +- .github/workflows/release.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index bf7fa0c262..84d79cda17 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -49,7 +49,7 @@ jobs: with: python-version: "3.11" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Set TAG run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 10194aa599..b41b118504 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -99,7 +99,7 @@ jobs: python-version: "3.11" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Log in to docker hub uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 @@ -178,7 +178,7 @@ jobs: merge-multiple: true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Log in to docker hub if: matrix.registry == 'dockerhub' From 98ed679b1995d2b96ab85cdc9f93a4d593e8331c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 19:22:56 +0000 Subject: [PATCH 0723/1145] Bump ruff from 0.14.9 to 0.14.10 (#12572) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2f5076a6e6..de7d30cfa2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.9 + rev: v0.14.10 hooks: # Run the linter. - id: ruff diff --git a/requirements_test.txt b/requirements_test.txt index bfb833e04d..f00bcd0a0d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==4.0.4 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.14.9 # also change in .pre-commit-config.yaml when updating +ruff==0.14.10 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating pre-commit From 25cebedcfcd0ff7b0fb1d9df81858f984bae9862 Mon Sep 17 00:00:00 2001 From: Rene Guca <45061891+rguca@users.noreply.github.com> Date: Fri, 19 Dec 2025 21:42:39 +0100 Subject: [PATCH 0724/1145] [dht] Fix "Falling edge for bit 39 failed!" for Sonoff THS01 (#9225) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/dht/dht.cpp | 38 ++++++++++++++++++---------------- esphome/components/dht/dht.h | 8 +++++-- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index cc0bf55a80..e0abb7c5f0 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -8,17 +8,20 @@ namespace dht { static const char *const TAG = "dht"; void DHT::setup() { - this->pin_->digital_write(true); - this->pin_->setup(); - this->pin_->digital_write(true); + this->t_pin_->digital_write(true); + this->t_pin_->setup(); +#ifdef USE_ESP32 + this->t_pin_->pin_mode(this->t_pin_->get_flags() | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN); +#endif + this->t_pin_->digital_write(true); } void DHT::dump_config() { ESP_LOGCONFIG(TAG, "DHT:"); - LOG_PIN(" Pin: ", this->pin_); + LOG_PIN(" Pin: ", this->t_pin_); ESP_LOGCONFIG(TAG, " %sModel: %s", this->is_auto_detect_ ? "Auto-detected " : "", this->model_ == DHT_MODEL_DHT11 ? "DHT11" : "DHT22 or equivalent"); - ESP_LOGCONFIG(TAG, " Internal pull-up: %s", ONOFF(this->pin_->get_flags() & gpio::FLAG_PULLUP)); + ESP_LOGCONFIG(TAG, " Internal pull-up: %s", ONOFF(this->t_pin_->get_flags() & gpio::FLAG_PULLUP)); LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); @@ -72,21 +75,15 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r int8_t i = 0; uint8_t data[5] = {0, 0, 0, 0, 0}; - this->pin_->digital_write(false); - this->pin_->pin_mode(gpio::FLAG_OUTPUT); - this->pin_->digital_write(false); +#ifndef USE_ESP32 + this->pin_.pin_mode(gpio::FLAG_OUTPUT); +#endif + this->pin_.digital_write(false); if (this->model_ == DHT_MODEL_DHT11) { delayMicroseconds(18000); } else if (this->model_ == DHT_MODEL_SI7021) { -#ifdef USE_ESP8266 delayMicroseconds(500); - this->pin_->digital_write(true); - delayMicroseconds(40); -#else - delayMicroseconds(400); - this->pin_->digital_write(true); -#endif } else if (this->model_ == DHT_MODEL_DHT22_TYPE2) { delayMicroseconds(2000); } else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) { @@ -94,7 +91,12 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r } else { delayMicroseconds(800); } - this->pin_->pin_mode(this->pin_->get_flags()); + +#ifdef USE_ESP32 + this->pin_.digital_write(true); +#else + this->pin_.pin_mode(this->t_pin_->get_flags()); +#endif { InterruptLock lock; @@ -110,7 +112,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r uint32_t start_time = micros(); // Wait for rising edge - while (!this->pin_->digital_read()) { + while (!this->pin_.digital_read()) { if (micros() - start_time > 90) { if (i < 0) { error_code = 1; // line didn't clear @@ -127,7 +129,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r uint32_t end_time = start_time; // Wait for falling edge - while (this->pin_->digital_read()) { + while (this->pin_.digital_read()) { end_time = micros(); if (end_time - start_time > 90) { if (i < 0) { diff --git a/esphome/components/dht/dht.h b/esphome/components/dht/dht.h index 327e8a4f5c..9047dd2c96 100644 --- a/esphome/components/dht/dht.h +++ b/esphome/components/dht/dht.h @@ -38,7 +38,10 @@ class DHT : public PollingComponent { */ void set_dht_model(DHTModel model); - void set_pin(InternalGPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { + this->t_pin_ = pin; + this->pin_ = pin->to_isr(); + } void set_model(DHTModel model) { model_ = model; } void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } @@ -54,7 +57,8 @@ class DHT : public PollingComponent { protected: bool read_sensor_(float *temperature, float *humidity, bool report_errors); - InternalGPIOPin *pin_; + InternalGPIOPin *t_pin_; + ISRInternalGPIOPin pin_; DHTModel model_{DHT_MODEL_AUTO_DETECT}; bool is_auto_detect_{false}; sensor::Sensor *temperature_sensor_{nullptr}; From ebc3d28adeeb57db9291f9c73add09e8516d08e6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Dec 2025 11:18:15 -1000 Subject: [PATCH 0725/1145] [wifi] Replace optional with sentinel values to reduce RAM and clarify API (#12446) --- esphome/components/wifi/wifi_component.cpp | 51 ++++++++++--------- esphome/components/wifi/wifi_component.h | 24 +++++---- .../wifi/wifi_component_esp8266.cpp | 10 ++-- .../wifi/wifi_component_esp_idf.cpp | 10 ++-- .../wifi/wifi_component_libretiny.cpp | 6 +-- .../components/wifi/wifi_component_pico_w.cpp | 2 +- 6 files changed, 55 insertions(+), 48 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 7c5b001be9..242265344d 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -2,6 +2,7 @@ #ifdef USE_WIFI #include #include +#include #ifdef USE_ESP32 #if (ESP_IDF_VERSION_MAJOR >= 5 && ESP_IDF_VERSION_MINOR >= 1) @@ -394,7 +395,7 @@ void WiFiComponent::start() { if (this->has_sta()) { this->wifi_sta_pre_setup_(); - if (this->output_power_.has_value() && !this->wifi_apply_output_power_(*this->output_power_)) { + if (!std::isnan(this->output_power_) && !this->wifi_apply_output_power_(this->output_power_)) { ESP_LOGV(TAG, "Setting Output Power Option failed"); } @@ -441,7 +442,7 @@ void WiFiComponent::start() { #ifdef USE_WIFI_AP } else if (this->has_ap()) { this->setup_ap_config_(); - if (this->output_power_.has_value() && !this->wifi_apply_output_power_(*this->output_power_)) { + if (!std::isnan(this->output_power_) && !this->wifi_apply_output_power_(this->output_power_)) { ESP_LOGV(TAG, "Setting Output Power Option failed"); } #ifdef USE_CAPTIVE_PORTAL @@ -713,8 +714,8 @@ WiFiAP WiFiComponent::build_params_for_current_phase_() { case WiFiRetryPhase::RETRY_HIDDEN: // Hidden network mode: clear BSSID/channel to trigger probe request // (both explicit hidden and retry hidden use same behavior) - params.set_bssid(optional{}); - params.set_channel(optional{}); + params.clear_bssid(); + params.clear_channel(); break; case WiFiRetryPhase::SCAN_CONNECTING: @@ -766,21 +767,20 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { char bssid_s[18]; int8_t priority = 0; - if (ap.get_bssid().has_value()) { - format_mac_addr_upper(ap.get_bssid().value().data(), bssid_s); - priority = this->get_sta_priority(ap.get_bssid().value()); + if (ap.has_bssid()) { + format_mac_addr_upper(ap.get_bssid().data(), bssid_s); + priority = this->get_sta_priority(ap.get_bssid()); } ESP_LOGI(TAG, "Connecting to " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") " (priority %d, attempt %u/%u in phase %s)...", - ap.get_ssid().c_str(), ap.get_bssid().has_value() ? bssid_s : LOG_STR_LITERAL("any"), priority, - this->num_retried_ + 1, get_max_retries_for_phase(this->retry_phase_), - LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_))); + ap.get_ssid().c_str(), ap.has_bssid() ? bssid_s : LOG_STR_LITERAL("any"), priority, this->num_retried_ + 1, + get_max_retries_for_phase(this->retry_phase_), LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_))); #ifdef ESPHOME_LOG_HAS_VERBOSE ESP_LOGV(TAG, "Connection Params:"); ESP_LOGV(TAG, " SSID: '%s'", ap.get_ssid().c_str()); - if (ap.get_bssid().has_value()) { + if (ap.has_bssid()) { ESP_LOGV(TAG, " BSSID: %s", bssid_s); } else { ESP_LOGV(TAG, " BSSID: Not Set"); @@ -808,8 +808,8 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { #ifdef USE_WIFI_WPA2_EAP } #endif - if (ap.get_channel().has_value()) { - ESP_LOGV(TAG, " Channel: %u", *ap.get_channel()); + if (ap.has_channel()) { + ESP_LOGV(TAG, " Channel: %u", ap.get_channel()); } else { ESP_LOGV(TAG, " Channel not set"); } @@ -919,8 +919,8 @@ void WiFiComponent::print_connect_params_() { get_wifi_channel(), wifi_subnet_mask_().str().c_str(), wifi_gateway_ip_().str().c_str(), wifi_dns_ip_(0).str().c_str(), wifi_dns_ip_(1).str().c_str()); #ifdef ESPHOME_LOG_HAS_VERBOSE - if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_bssid().has_value()) { - ESP_LOGV(TAG, " Priority: %d", this->get_sta_priority(*config->get_bssid())); + if (const WiFiAP *config = this->get_selected_sta_(); config && config->has_bssid()) { + ESP_LOGV(TAG, " Priority: %d", this->get_sta_priority(config->get_bssid())); } #endif #ifdef USE_WIFI_11KV_SUPPORT @@ -1514,9 +1514,9 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { if (this->retry_phase_ == WiFiRetryPhase::SCAN_CONNECTING && !this->scan_result_.empty()) { // Scan-based phase: always use best result (index 0) failed_bssid = this->scan_result_[0].get_bssid(); - } else if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_bssid()) { + } else if (const WiFiAP *config = this->get_selected_sta_(); config && config->has_bssid()) { // Config has specific BSSID (fast_connect or user-specified) - failed_bssid = *config->get_bssid(); + failed_bssid = config->get_bssid(); } if (!failed_bssid.has_value()) { @@ -1784,24 +1784,27 @@ void WiFiComponent::save_fast_connect_settings_() { #endif void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = ssid; } -void WiFiAP::set_bssid(bssid_t bssid) { this->bssid_ = bssid; } -void WiFiAP::set_bssid(optional bssid) { this->bssid_ = bssid; } +void WiFiAP::set_bssid(const bssid_t &bssid) { this->bssid_ = bssid; } +void WiFiAP::clear_bssid() { this->bssid_ = {}; } void WiFiAP::set_password(const std::string &password) { this->password_ = password; } #ifdef USE_WIFI_WPA2_EAP void WiFiAP::set_eap(optional eap_auth) { this->eap_ = std::move(eap_auth); } #endif -void WiFiAP::set_channel(optional channel) { this->channel_ = channel; } +void WiFiAP::set_channel(uint8_t channel) { this->channel_ = channel; } +void WiFiAP::clear_channel() { this->channel_ = 0; } #ifdef USE_WIFI_MANUAL_IP void WiFiAP::set_manual_ip(optional manual_ip) { this->manual_ip_ = manual_ip; } #endif void WiFiAP::set_hidden(bool hidden) { this->hidden_ = hidden; } const std::string &WiFiAP::get_ssid() const { return this->ssid_; } -const optional &WiFiAP::get_bssid() const { return this->bssid_; } +const bssid_t &WiFiAP::get_bssid() const { return this->bssid_; } +bool WiFiAP::has_bssid() const { return this->bssid_ != bssid_t{}; } const std::string &WiFiAP::get_password() const { return this->password_; } #ifdef USE_WIFI_WPA2_EAP const optional &WiFiAP::get_eap() const { return this->eap_; } #endif -const optional &WiFiAP::get_channel() const { return this->channel_; } +uint8_t WiFiAP::get_channel() const { return this->channel_; } +bool WiFiAP::has_channel() const { return this->channel_ != 0; } #ifdef USE_WIFI_MANUAL_IP const optional &WiFiAP::get_manual_ip() const { return this->manual_ip_; } #endif @@ -1829,7 +1832,7 @@ bool WiFiScanResult::matches(const WiFiAP &config) const { // network is configured without SSID - match other settings } // If BSSID configured, only match for correct BSSIDs - if (config.get_bssid().has_value() && *config.get_bssid() != this->bssid_) + if (config.has_bssid() && config.get_bssid() != this->bssid_) return false; #ifdef USE_WIFI_WPA2_EAP @@ -1847,7 +1850,7 @@ bool WiFiScanResult::matches(const WiFiAP &config) const { #endif // If channel configured, only match networks on that channel. - if (config.get_channel().has_value() && *config.get_channel() != this->channel_) { + if (config.has_channel() && config.get_channel() != this->channel_) { return false; } return true; diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index be94e9462b..604efa8a7e 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -151,25 +151,28 @@ template using wifi_scan_vector_t = FixedVector; class WiFiAP { public: void set_ssid(const std::string &ssid); - void set_bssid(bssid_t bssid); - void set_bssid(optional bssid); + void set_bssid(const bssid_t &bssid); + void clear_bssid(); void set_password(const std::string &password); #ifdef USE_WIFI_WPA2_EAP void set_eap(optional eap_auth); #endif // USE_WIFI_WPA2_EAP - void set_channel(optional channel); + void set_channel(uint8_t channel); + void clear_channel(); void set_priority(int8_t priority) { priority_ = priority; } #ifdef USE_WIFI_MANUAL_IP void set_manual_ip(optional manual_ip); #endif void set_hidden(bool hidden); const std::string &get_ssid() const; - const optional &get_bssid() const; + const bssid_t &get_bssid() const; + bool has_bssid() const; const std::string &get_password() const; #ifdef USE_WIFI_WPA2_EAP const optional &get_eap() const; #endif // USE_WIFI_WPA2_EAP - const optional &get_channel() const; + uint8_t get_channel() const; + bool has_channel() const; int8_t get_priority() const { return priority_; } #ifdef USE_WIFI_MANUAL_IP const optional &get_manual_ip() const; @@ -179,16 +182,17 @@ class WiFiAP { protected: std::string ssid_; std::string password_; - optional bssid_; #ifdef USE_WIFI_WPA2_EAP optional eap_; #endif // USE_WIFI_WPA2_EAP #ifdef USE_WIFI_MANUAL_IP optional manual_ip_; #endif - optional channel_; - int8_t priority_{0}; - bool hidden_{false}; + // Group small types together to minimize padding + bssid_t bssid_{}; // 6 bytes, all zeros = any/not set + uint8_t channel_{0}; // 1 byte, 0 = auto/not set + int8_t priority_{0}; // 1 byte + bool hidden_{false}; // 1 byte (+ 3 bytes end padding to 4-byte align) }; class WiFiScanResult { @@ -590,7 +594,7 @@ class WiFiComponent : public Component { #ifdef USE_WIFI_AP WiFiAP ap_; #endif - optional output_power_; + float output_power_{NAN}; #ifdef USE_WIFI_LISTENERS std::vector ip_state_listeners_; std::vector scan_results_listeners_; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 3b1a442bdb..1329103f98 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -257,9 +257,9 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { memcpy(reinterpret_cast(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); memcpy(reinterpret_cast(conf.password), ap.get_password().c_str(), ap.get_password().size()); - if (ap.get_bssid().has_value()) { + if (ap.has_bssid()) { conf.bssid_set = 1; - memcpy(conf.bssid, ap.get_bssid()->data(), 6); + memcpy(conf.bssid, ap.get_bssid().data(), 6); } else { conf.bssid_set = 0; } @@ -381,8 +381,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } #endif /* USE_NETWORK_IPV6 */ - if (ap.get_channel().has_value()) { - ret = wifi_set_channel(*ap.get_channel()); + if (ap.has_channel()) { + ret = wifi_set_channel(ap.get_channel()); if (!ret) { ESP_LOGV(TAG, "wifi_set_channel failed"); return false; @@ -845,7 +845,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { } memcpy(reinterpret_cast(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); conf.ssid_len = static_cast(ap.get_ssid().size()); - conf.channel = ap.get_channel().value_or(1); + conf.channel = ap.has_channel() ? ap.get_channel() : 1; conf.ssid_hidden = ap.get_hidden(); conf.max_connection = 5; conf.beacon_interval = 100; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 4a3c40a119..f9e117f468 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -339,14 +339,14 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { conf.sta.rm_enabled = this->rrm_; #endif - if (ap.get_bssid().has_value()) { + if (ap.has_bssid()) { conf.sta.bssid_set = true; - memcpy(conf.sta.bssid, ap.get_bssid()->data(), 6); + memcpy(conf.sta.bssid, ap.get_bssid().data(), 6); } else { conf.sta.bssid_set = false; } - if (ap.get_channel().has_value()) { - conf.sta.channel = *ap.get_channel(); + if (ap.has_channel()) { + conf.sta.channel = ap.get_channel(); conf.sta.scan_method = WIFI_FAST_SCAN; } else { conf.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; @@ -1003,7 +1003,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return false; } memcpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); - conf.ap.channel = ap.get_channel().value_or(1); + conf.ap.channel = ap.has_channel() ? ap.get_channel() : 1; conf.ap.ssid_hidden = ap.get_ssid().size(); conf.ap.max_connection = 5; conf.ap.beacon_interval = 100; diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 36003a6eb4..ffc6b21359 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -139,8 +139,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { s_sta_connecting = true; WiFiStatus status = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().empty() ? NULL : ap.get_password().c_str(), - ap.get_channel().has_value() ? *ap.get_channel() : 0, - ap.get_bssid().has_value() ? ap.get_bssid()->data() : NULL); + ap.get_channel(), // 0 = auto + ap.has_bssid() ? ap.get_bssid().data() : NULL); if (status != WL_CONNECTED) { ESP_LOGW(TAG, "esp_wifi_connect failed: %d", status); return false; @@ -522,7 +522,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { yield(); return WiFi.softAP(ap.get_ssid().c_str(), ap.get_password().empty() ? NULL : ap.get_password().c_str(), - ap.get_channel().value_or(1), ap.get_hidden()); + ap.has_channel() ? ap.get_channel() : 1, ap.get_hidden()); } network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.softAPIP()}; } diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 0228755432..4e763a9e22 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -192,7 +192,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { } #endif - WiFi.beginAP(ap.get_ssid().c_str(), ap.get_password().c_str(), ap.get_channel().value_or(1)); + WiFi.beginAP(ap.get_ssid().c_str(), ap.get_password().c_str(), ap.has_channel() ? ap.get_channel() : 1); return true; } From 81e91c2a8f05a0192d6c1cda0e1b13cb465312f5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Dec 2025 11:18:32 -1000 Subject: [PATCH 0726/1145] [esp32_ble] Add stack-based UUID formatting to avoid heap allocations (#12510) --- esphome/components/esp32_ble/ble_uuid.cpp | 17 +++++++++------- esphome/components/esp32_ble/ble_uuid.h | 5 +++++ .../esp32_ble_client/ble_characteristic.cpp | 6 +++++- .../esp32_ble_client/ble_client_base.cpp | 7 +++++-- .../esp32_ble_client/ble_service.cpp | 7 +++++-- .../esp32_ble_server/ble_characteristic.cpp | 6 +++++- .../esp32_ble_server/ble_descriptor.cpp | 6 +++++- .../esp32_ble_server/ble_server.cpp | 20 +++++++++++++++---- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 17 +++++++++++----- 9 files changed, 68 insertions(+), 23 deletions(-) diff --git a/esphome/components/esp32_ble/ble_uuid.cpp b/esphome/components/esp32_ble/ble_uuid.cpp index dcbb285e07..c6b27f3bb9 100644 --- a/esphome/components/esp32_ble/ble_uuid.cpp +++ b/esphome/components/esp32_ble/ble_uuid.cpp @@ -143,9 +143,8 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const { return this->as_128bit() == uuid.as_128bit(); } esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; } -std::string ESPBTUUID::to_string() const { - char buf[40]; // Enough for 128-bit UUID with dashes - char *pos = buf; +void ESPBTUUID::to_str(std::span output) const { + char *pos = output.data(); switch (this->uuid_.len) { case ESP_UUID_LEN_16: @@ -156,7 +155,7 @@ std::string ESPBTUUID::to_string() const { *pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid16 >> 4) & 0x0F); *pos++ = format_hex_pretty_char(this->uuid_.uuid.uuid16 & 0x0F); *pos = '\0'; - return std::string(buf); + return; case ESP_UUID_LEN_32: *pos++ = '0'; @@ -165,7 +164,7 @@ std::string ESPBTUUID::to_string() const { *pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid32 >> shift) & 0x0F); } *pos = '\0'; - return std::string(buf); + return; default: case ESP_UUID_LEN_128: @@ -179,9 +178,13 @@ std::string ESPBTUUID::to_string() const { } } *pos = '\0'; - return std::string(buf); + return; } - return ""; +} +std::string ESPBTUUID::to_string() const { + char buf[UUID_STR_LEN]; + this->to_str(buf); + return std::string(buf); } } // namespace esphome::esp32_ble diff --git a/esphome/components/esp32_ble/ble_uuid.h b/esphome/components/esp32_ble/ble_uuid.h index 4cf2d10abd..ed561d70e4 100644 --- a/esphome/components/esp32_ble/ble_uuid.h +++ b/esphome/components/esp32_ble/ble_uuid.h @@ -7,11 +7,15 @@ #ifdef USE_ESP32 #ifdef USE_ESP32_BLE_UUID +#include #include #include namespace esphome::esp32_ble { +/// Buffer size for UUID string: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\0" +static constexpr size_t UUID_STR_LEN = 37; + class ESPBTUUID { public: ESPBTUUID(); @@ -37,6 +41,7 @@ class ESPBTUUID { esp_bt_uuid_t get_uuid() const; std::string to_string() const; + void to_str(std::span output) const; protected: esp_bt_uuid_t uuid_; diff --git a/esphome/components/esp32_ble_client/ble_characteristic.cpp b/esphome/components/esp32_ble_client/ble_characteristic.cpp index e0d0174c57..e830702f11 100644 --- a/esphome/components/esp32_ble_client/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_client/ble_characteristic.cpp @@ -50,8 +50,12 @@ void BLECharacteristic::parse_descriptors() { desc->handle = result.handle; desc->characteristic = this; this->descriptors.push_back(desc); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uuid_buf[espbt::UUID_STR_LEN]; + desc->uuid.to_str(uuid_buf); ESP_LOGV(TAG, "[%d] [%s] descriptor %s, handle 0x%x", this->service->client->get_connection_index(), - this->service->client->address_str(), desc->uuid.to_string().c_str(), desc->handle); + this->service->client->address_str(), uuid_buf, desc->handle); +#endif offset++; } } diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index a09390c747..8017b577f4 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -411,12 +411,15 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium"); } else if (this->connection_type_ != espbt::ConnectionType::V3_WITH_CACHE) { #ifdef USE_ESP32_BLE_DEVICE +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE for (auto &svc : this->services_) { - ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_, - svc->uuid.to_string().c_str()); + char uuid_buf[espbt::UUID_STR_LEN]; + svc->uuid.to_str(uuid_buf); + ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_, uuid_buf); ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_, this->address_str_, svc->start_handle, svc->end_handle); } +#endif #endif } ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_); diff --git a/esphome/components/esp32_ble_client/ble_service.cpp b/esphome/components/esp32_ble_client/ble_service.cpp index deaaa3de02..695f468c5b 100644 --- a/esphome/components/esp32_ble_client/ble_service.cpp +++ b/esphome/components/esp32_ble_client/ble_service.cpp @@ -64,9 +64,12 @@ void BLEService::parse_characteristics() { characteristic->handle = result.char_handle; characteristic->service = this; this->characteristics.push_back(characteristic); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uuid_buf[espbt::UUID_STR_LEN]; + characteristic->uuid.to_str(uuid_buf); ESP_LOGV(TAG, "[%d] [%s] characteristic %s, handle 0x%x, properties 0x%x", this->client->get_connection_index(), - this->client->address_str(), characteristic->uuid.to_string().c_str(), characteristic->handle, - characteristic->properties); + this->client->address_str(), uuid_buf, characteristic->handle, characteristic->properties); +#endif offset++; } } diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index 7627a58338..0482848ea0 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -109,7 +109,11 @@ void BLECharacteristic::do_create(BLEService *service) { esp_attr_control_t control; control.auto_rsp = ESP_GATT_RSP_BY_APP; - ESP_LOGV(TAG, "Creating characteristic - %s", this->uuid_.to_string().c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uuid_buf[esp32_ble::UUID_STR_LEN]; + this->uuid_.to_str(uuid_buf); + ESP_LOGV(TAG, "Creating characteristic - %s", uuid_buf); +#endif esp_bt_uuid_t uuid = this->uuid_.get_uuid(); esp_err_t err = esp_ble_gatts_add_char(service->get_handle(), &uuid, static_cast(this->permissions_), diff --git a/esphome/components/esp32_ble_server/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp index 2d053c09bd..4ffca7312b 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -34,7 +34,11 @@ void BLEDescriptor::do_create(BLECharacteristic *characteristic) { esp_attr_control_t control; control.auto_rsp = ESP_GATT_AUTO_RSP; - ESP_LOGV(TAG, "Creating descriptor - %s", this->uuid_.to_string().c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uuid_buf[esp32_ble::UUID_STR_LEN]; + this->uuid_.to_str(uuid_buf); + ESP_LOGV(TAG, "Creating descriptor - %s", uuid_buf); +#endif esp_bt_uuid_t uuid = this->uuid_.get_uuid(); esp_err_t err = esp_ble_gatts_add_char_descr(this->characteristic_->get_service()->get_handle(), &uuid, this->permissions_, &this->value_, &control); diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 0e58224a5a..2c13a8ac36 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -106,7 +106,11 @@ void BLEServer::restart_advertising_() { } BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles) { - ESP_LOGV(TAG, "Creating BLE service - %s", uuid.to_string().c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uuid_buf[esp32_ble::UUID_STR_LEN]; + uuid.to_str(uuid_buf); + ESP_LOGV(TAG, "Creating BLE service - %s", uuid_buf); +#endif // Calculate the inst_id for the service uint8_t inst_id = 0; for (; inst_id < 0xFF; inst_id++) { @@ -115,7 +119,9 @@ BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t n } } if (inst_id == 0xFF) { - ESP_LOGW(TAG, "Could not create BLE service %s, too many instances", uuid.to_string().c_str()); + char warn_uuid_buf[esp32_ble::UUID_STR_LEN]; + uuid.to_str(warn_uuid_buf); + ESP_LOGW(TAG, "Could not create BLE service %s, too many instances", warn_uuid_buf); return nullptr; } BLEService *service = // NOLINT(cppcoreguidelines-owning-memory) @@ -128,7 +134,11 @@ BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t n } void BLEServer::remove_service(ESPBTUUID uuid, uint8_t inst_id) { - ESP_LOGV(TAG, "Removing BLE service - %s %d", uuid.to_string().c_str(), inst_id); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uuid_buf[esp32_ble::UUID_STR_LEN]; + uuid.to_str(uuid_buf); + ESP_LOGV(TAG, "Removing BLE service - %s %d", uuid_buf, inst_id); +#endif for (auto it = this->services_.begin(); it != this->services_.end(); ++it) { if (it->uuid == uuid && it->inst_id == inst_id) { it->service->do_delete(); @@ -137,7 +147,9 @@ void BLEServer::remove_service(ESPBTUUID uuid, uint8_t inst_id) { return; } } - ESP_LOGW(TAG, "BLE service %s %d does not exist", uuid.to_string().c_str(), inst_id); + char warn_uuid_buf[esp32_ble::UUID_STR_LEN]; + uuid.to_str(warn_uuid_buf); + ESP_LOGW(TAG, "BLE service %s %d does not exist", warn_uuid_buf, inst_id); } BLEService *BLEServer::get_service(ESPBTUUID uuid, uint8_t inst_id) { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 45e343c0d2..cb83eb5a0d 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -438,24 +438,31 @@ void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) { ESP_LOGVV(TAG, " Ad Flag: %u", *this->ad_flag_); } for (auto &uuid : this->service_uuids_) { - ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str()); + char uuid_buf[esp32_ble::UUID_STR_LEN]; + uuid.to_str(uuid_buf); + ESP_LOGVV(TAG, " Service UUID: %s", uuid_buf); } for (auto &data : this->manufacturer_datas_) { auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(data); if (ibeacon.has_value()) { ESP_LOGVV(TAG, " Manufacturer iBeacon:"); - ESP_LOGVV(TAG, " UUID: %s", ibeacon.value().get_uuid().to_string().c_str()); + char uuid_buf[esp32_ble::UUID_STR_LEN]; + ibeacon.value().get_uuid().to_str(uuid_buf); + ESP_LOGVV(TAG, " UUID: %s", uuid_buf); ESP_LOGVV(TAG, " Major: %u", ibeacon.value().get_major()); ESP_LOGVV(TAG, " Minor: %u", ibeacon.value().get_minor()); ESP_LOGVV(TAG, " TXPower: %d", ibeacon.value().get_signal_power()); } else { - ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", data.uuid.to_string().c_str(), - format_hex_pretty(data.data).c_str()); + char uuid_buf[esp32_ble::UUID_STR_LEN]; + data.uuid.to_str(uuid_buf); + ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", uuid_buf, format_hex_pretty(data.data).c_str()); } } for (auto &data : this->service_datas_) { ESP_LOGVV(TAG, " Service data:"); - ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str()); + char uuid_buf[esp32_ble::UUID_STR_LEN]; + data.uuid.to_str(uuid_buf); + ESP_LOGVV(TAG, " UUID: %s", uuid_buf); ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str()); } From 940afdbb12e212299edc1ad67481ef9a6d5f568e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Dec 2025 11:18:50 -1000 Subject: [PATCH 0727/1145] [climate] Add zero-copy support for API custom fan mode and preset commands (#12402) --- esphome/components/api/api.proto | 4 +- esphome/components/api/api_connection.cpp | 4 +- esphome/components/api/api_pb2.cpp | 14 +++++-- esphome/components/api/api_pb2.h | 8 ++-- esphome/components/api/api_pb2_dump.cpp | 8 +++- esphome/components/climate/climate.cpp | 45 +++++++++++++++------ esphome/components/climate/climate.h | 6 +++ esphome/components/climate/climate_traits.h | 22 +++++++--- 8 files changed, 80 insertions(+), 31 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 50af5061c0..dd8320bebb 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1091,11 +1091,11 @@ message ClimateCommandRequest { bool has_swing_mode = 14; ClimateSwingMode swing_mode = 15; bool has_custom_fan_mode = 16; - string custom_fan_mode = 17; + string custom_fan_mode = 17 [(pointer_to_buffer) = true]; bool has_preset = 18; ClimatePreset preset = 19; bool has_custom_preset = 20; - string custom_preset = 21; + string custom_preset = 21 [(pointer_to_buffer) = true]; bool has_target_humidity = 22; float target_humidity = 23; uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"]; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 85f4566f3c..686fdcba41 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -712,11 +712,11 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { if (msg.has_fan_mode) call.set_fan_mode(static_cast(msg.fan_mode)); if (msg.has_custom_fan_mode) - call.set_fan_mode(msg.custom_fan_mode); + call.set_fan_mode(reinterpret_cast(msg.custom_fan_mode), msg.custom_fan_mode_len); if (msg.has_preset) call.set_preset(static_cast(msg.preset)); if (msg.has_custom_preset) - call.set_preset(msg.custom_preset); + call.set_preset(reinterpret_cast(msg.custom_preset), msg.custom_preset_len); if (msg.has_swing_mode) call.set_swing_mode(static_cast(msg.swing_mode)); call.perform(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 52f4b495e9..211f856e3b 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1392,12 +1392,18 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) } bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 17: - this->custom_fan_mode = value.as_string(); + case 17: { + // Use raw data directly to avoid allocation + this->custom_fan_mode = value.data(); + this->custom_fan_mode_len = value.size(); break; - case 21: - this->custom_preset = value.as_string(); + } + case 21: { + // Use raw data directly to avoid allocation + this->custom_preset = value.data(); + this->custom_preset_len = value.size(); break; + } default: return false; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index f23a62fc3c..4e10c63881 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1475,7 +1475,7 @@ class ClimateStateResponse final : public StateResponseProtoMessage { class ClimateCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 48; - static constexpr uint8_t ESTIMATED_SIZE = 84; + static constexpr uint8_t ESTIMATED_SIZE = 104; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "climate_command_request"; } #endif @@ -1492,11 +1492,13 @@ class ClimateCommandRequest final : public CommandProtoMessage { bool has_swing_mode{false}; enums::ClimateSwingMode swing_mode{}; bool has_custom_fan_mode{false}; - std::string custom_fan_mode{}; + const uint8_t *custom_fan_mode{nullptr}; + uint16_t custom_fan_mode_len{0}; bool has_preset{false}; enums::ClimatePreset preset{}; bool has_custom_preset{false}; - std::string custom_preset{}; + const uint8_t *custom_preset{nullptr}; + uint16_t custom_preset_len{0}; bool has_target_humidity{false}; float target_humidity{0.0f}; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 5e271f41cb..90e8e75c93 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1374,11 +1374,15 @@ void ClimateCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_swing_mode", this->has_swing_mode); dump_field(out, "swing_mode", static_cast(this->swing_mode)); dump_field(out, "has_custom_fan_mode", this->has_custom_fan_mode); - dump_field(out, "custom_fan_mode", this->custom_fan_mode); + out.append(" custom_fan_mode: "); + out.append(format_hex_pretty(this->custom_fan_mode, this->custom_fan_mode_len)); + out.append("\n"); dump_field(out, "has_preset", this->has_preset); dump_field(out, "preset", static_cast(this->preset)); dump_field(out, "has_custom_preset", this->has_custom_preset); - dump_field(out, "custom_preset", this->custom_preset); + out.append(" custom_preset: "); + out.append(format_hex_pretty(this->custom_preset, this->custom_preset_len)); + out.append("\n"); dump_field(out, "has_target_humidity", this->has_target_humidity); dump_field(out, "target_humidity", this->target_humidity); #ifdef USE_DEVICES diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 3bc20a17c6..229862ce01 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/macros.h" +#include namespace esphome::climate { @@ -190,24 +191,30 @@ ClimateCall &ClimateCall::set_fan_mode(ClimateFanMode fan_mode) { } ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode) { + return this->set_fan_mode(custom_fan_mode, strlen(custom_fan_mode)); +} + +ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { + return this->set_fan_mode(fan_mode.data(), fan_mode.size()); +} + +ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode, size_t len) { // Check if it's a standard enum mode first for (const auto &mode_entry : CLIMATE_FAN_MODES_BY_STR) { - if (str_equals_case_insensitive(custom_fan_mode, mode_entry.str)) { + if (strncasecmp(custom_fan_mode, mode_entry.str, len) == 0 && mode_entry.str[len] == '\0') { return this->set_fan_mode(static_cast(mode_entry.value)); } } // Find the matching pointer from parent climate device - if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode)) { + if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode, len)) { this->custom_fan_mode_ = mode_ptr; this->fan_mode_.reset(); return *this; } - ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), custom_fan_mode); + ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %.*s", this->parent_->get_name().c_str(), (int) len, custom_fan_mode); return *this; } -ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { return this->set_fan_mode(fan_mode.c_str()); } - ClimateCall &ClimateCall::set_fan_mode(optional fan_mode) { if (fan_mode.has_value()) { this->set_fan_mode(fan_mode.value()); @@ -222,24 +229,30 @@ ClimateCall &ClimateCall::set_preset(ClimatePreset preset) { } ClimateCall &ClimateCall::set_preset(const char *custom_preset) { + return this->set_preset(custom_preset, strlen(custom_preset)); +} + +ClimateCall &ClimateCall::set_preset(const std::string &preset) { + return this->set_preset(preset.data(), preset.size()); +} + +ClimateCall &ClimateCall::set_preset(const char *custom_preset, size_t len) { // Check if it's a standard enum preset first for (const auto &preset_entry : CLIMATE_PRESETS_BY_STR) { - if (str_equals_case_insensitive(custom_preset, preset_entry.str)) { + if (strncasecmp(custom_preset, preset_entry.str, len) == 0 && preset_entry.str[len] == '\0') { return this->set_preset(static_cast(preset_entry.value)); } } // Find the matching pointer from parent climate device - if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset)) { + if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset, len)) { this->custom_preset_ = preset_ptr; this->preset_.reset(); return *this; } - ESP_LOGW(TAG, "'%s' - Unrecognized preset %s", this->parent_->get_name().c_str(), custom_preset); + ESP_LOGW(TAG, "'%s' - Unrecognized preset %.*s", this->parent_->get_name().c_str(), (int) len, custom_preset); return *this; } -ClimateCall &ClimateCall::set_preset(const std::string &preset) { return this->set_preset(preset.c_str()); } - ClimateCall &ClimateCall::set_preset(optional preset) { if (preset.has_value()) { this->set_preset(preset.value()); @@ -688,11 +701,19 @@ bool Climate::set_custom_preset_(const char *preset) { void Climate::clear_custom_preset_() { this->custom_preset_ = nullptr; } const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode) { - return this->get_traits().find_custom_fan_mode_(custom_fan_mode); + return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode)); +} + +const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode, size_t len) { + return this->get_traits().find_custom_fan_mode_(custom_fan_mode, len); } const char *Climate::find_custom_preset_(const char *custom_preset) { - return this->get_traits().find_custom_preset_(custom_preset); + return this->find_custom_preset_(custom_preset, strlen(custom_preset)); +} + +const char *Climate::find_custom_preset_(const char *custom_preset, size_t len) { + return this->get_traits().find_custom_preset_(custom_preset, len); } void Climate::dump_traits_(const char *tag) { diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 82df4b815f..0bae28df5a 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -78,6 +78,8 @@ class ClimateCall { ClimateCall &set_fan_mode(optional fan_mode); /// Set the custom fan mode of the climate device. ClimateCall &set_fan_mode(const char *custom_fan_mode); + /// Set the custom fan mode of the climate device (zero-copy API path). + ClimateCall &set_fan_mode(const char *custom_fan_mode, size_t len); /// Set the swing mode of the climate device. ClimateCall &set_swing_mode(ClimateSwingMode swing_mode); /// Set the swing mode of the climate device. @@ -94,6 +96,8 @@ class ClimateCall { ClimateCall &set_preset(optional preset); /// Set the custom preset of the climate device. ClimateCall &set_preset(const char *custom_preset); + /// Set the custom preset of the climate device (zero-copy API path). + ClimateCall &set_preset(const char *custom_preset, size_t len); void perform(); @@ -290,9 +294,11 @@ class Climate : public EntityBase { /// Find and return the matching custom fan mode pointer from traits, or nullptr if not found. const char *find_custom_fan_mode_(const char *custom_fan_mode); + const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len); /// Find and return the matching custom preset pointer from traits, or nullptr if not found. const char *find_custom_preset_(const char *custom_preset); + const char *find_custom_preset_(const char *custom_preset, size_t len); /** Get the default traits of this climate device. * diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index d358293475..80ef0854d5 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -20,18 +20,22 @@ using ClimatePresetMask = FiniteSetMask &vec, const char *value) { +inline bool vector_contains(const std::vector &vec, const char *value, size_t len) { for (const char *item : vec) { - if (strcmp(item, value) == 0) + if (strncmp(item, value, len) == 0 && item[len] == '\0') return true; } return false; } +inline bool vector_contains(const std::vector &vec, const char *value) { + return vector_contains(vec, value, strlen(value)); +} + // Find and return matching pointer from vector, or nullptr if not found -inline const char *vector_find(const std::vector &vec, const char *value) { +inline const char *vector_find(const std::vector &vec, const char *value, size_t len) { for (const char *item : vec) { - if (strcmp(item, value) == 0) + if (strncmp(item, value, len) == 0 && item[len] == '\0') return item; } return nullptr; @@ -257,13 +261,19 @@ class ClimateTraits { /// Find and return the matching custom fan mode pointer from supported modes, or nullptr if not found /// This is protected as it's an implementation detail - use Climate::find_custom_fan_mode_() instead const char *find_custom_fan_mode_(const char *custom_fan_mode) const { - return vector_find(this->supported_custom_fan_modes_, custom_fan_mode); + return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode)); + } + const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len) const { + return vector_find(this->supported_custom_fan_modes_, custom_fan_mode, len); } /// Find and return the matching custom preset pointer from supported presets, or nullptr if not found /// This is protected as it's an implementation detail - use Climate::find_custom_preset_() instead const char *find_custom_preset_(const char *custom_preset) const { - return vector_find(this->supported_custom_presets_, custom_preset); + return this->find_custom_preset_(custom_preset, strlen(custom_preset)); + } + const char *find_custom_preset_(const char *custom_preset, size_t len) const { + return vector_find(this->supported_custom_presets_, custom_preset, len); } uint32_t feature_flags_{0}; From 988b888c6308d3b819918a6ec50a31aca76b8e43 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Dec 2025 11:19:07 -1000 Subject: [PATCH 0728/1145] [ota] Replace std::function callbacks with listener interface (#12167) --- .../components/esp32_ble_tracker/__init__.py | 4 +- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 27 +++--- .../esp32_ble_tracker/esp32_ble_tracker.h | 11 +++ .../components/esphome/ota/ota_esphome.cpp | 20 ++-- .../http_request/ota/ota_http_request.cpp | 22 ++--- .../http_request/ota/ota_http_request.h | 1 - .../http_request/update/__init__.py | 4 +- .../update/http_request_update.cpp | 26 +++--- .../http_request/update/http_request_update.h | 4 +- .../components/micro_wake_word/__init__.py | 4 +- .../micro_wake_word/micro_wake_word.cpp | 21 +++-- .../micro_wake_word/micro_wake_word.h | 16 +++- esphome/components/ota/__init__.py | 22 ++++- esphome/components/ota/automation.h | 92 +++++++++---------- esphome/components/ota/ota_backend.cpp | 9 +- esphome/components/ota/ota_backend.h | 91 ++++++++++-------- .../speaker/media_player/__init__.py | 4 +- .../media_player/speaker_media_player.cpp | 42 +++++---- .../media_player/speaker_media_player.h | 18 +++- .../web_server/ota/ota_web_server.cpp | 40 ++++---- esphome/core/defines.h | 2 +- 21 files changed, 274 insertions(+), 206 deletions(-) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 4e25434aad..37e74672ed 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -5,7 +5,7 @@ import logging from esphome import automation import esphome.codegen as cg -from esphome.components import esp32_ble +from esphome.components import esp32_ble, ota from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.esp32_ble import ( IDF_MAX_CONNECTIONS, @@ -328,7 +328,7 @@ async def to_code(config): # Note: CONFIG_BT_ACL_CONNECTIONS and CONFIG_BTDM_CTRL_BLE_MAX_CONN are now # configured in esp32_ble component based on max_connections setting - cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts + ota.request_ota_state_listeners() # To be notified when an OTA update starts cg.add_define("USE_ESP32_BLE_CLIENT") CORE.add_job(_add_ble_features) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index cb83eb5a0d..47da2e3570 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -71,21 +71,24 @@ void ESP32BLETracker::setup() { global_esp32_ble_tracker = this; -#ifdef USE_OTA - ota::get_global_ota_callback()->add_on_state_callback( - [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { - if (state == ota::OTA_STARTED) { - this->stop_scan(); -#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT - for (auto *client : this->clients_) { - client->disconnect(); - } -#endif - } - }); +#ifdef USE_OTA_STATE_LISTENER + ota::get_global_ota_callback()->add_global_state_listener(this); #endif } +#ifdef USE_OTA_STATE_LISTENER +void ESP32BLETracker::on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { + if (state == ota::OTA_STARTED) { + this->stop_scan(); +#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT + for (auto *client : this->clients_) { + client->disconnect(); + } +#endif + } +} +#endif + void ESP32BLETracker::loop() { if (!this->parent_->is_active()) { this->ble_was_disabled_ = true; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 92d13a62ad..b64e36279c 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -22,6 +22,10 @@ #include "esphome/components/esp32_ble/ble_uuid.h" #include "esphome/components/esp32_ble/ble_scan_result.h" +#ifdef USE_OTA_STATE_LISTENER +#include "esphome/components/ota/ota_backend.h" +#endif + namespace esphome::esp32_ble_tracker { using namespace esp32_ble; @@ -241,6 +245,9 @@ class ESP32BLETracker : public Component, public GAPScanEventHandler, public GATTcEventHandler, public BLEStatusEventHandler, +#ifdef USE_OTA_STATE_LISTENER + public ota::OTAGlobalStateListener, +#endif public Parented { public: void set_scan_duration(uint32_t scan_duration) { scan_duration_ = scan_duration; } @@ -274,6 +281,10 @@ class ESP32BLETracker : public Component, void gap_scan_event_handler(const BLEScanResult &scan_result) override; void ble_before_disabled_event_handler() override; +#ifdef USE_OTA_STATE_LISTENER + void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override; +#endif + /// Add a listener for scanner state changes void add_scanner_state_listener(BLEScannerStateListener *listener) { this->scanner_state_listeners_.push_back(listener); diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 6cfd543553..b589a6119f 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -41,10 +41,6 @@ static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 #endif // USE_OTA_PASSWORD void ESPHomeOTAComponent::setup() { -#ifdef USE_OTA_STATE_CALLBACK - ota::register_ota_platform(this); -#endif - this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections if (this->server_ == nullptr) { this->log_socket_error_(LOG_STR("creation")); @@ -297,8 +293,8 @@ void ESPHomeOTAComponent::handle_data_() { // accidentally trigger the update process. this->log_start_(LOG_STR("update")); this->status_set_warning(); -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_STARTED, 0.0f, 0); #endif // This will block for a few seconds as it locks flash @@ -357,8 +353,8 @@ void ESPHomeOTAComponent::handle_data_() { last_progress = now; float percentage = (total * 100.0f) / ota_size; ESP_LOGD(TAG, "Progress: %0.1f%%", percentage); -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_IN_PROGRESS, percentage, 0); #endif // feed watchdog and give other tasks a chance to run this->yield_and_feed_watchdog_(); @@ -387,8 +383,8 @@ void ESPHomeOTAComponent::handle_data_() { delay(10); ESP_LOGI(TAG, "Update complete"); this->status_clear_warning(); -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, 0); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_COMPLETED, 100.0f, 0); #endif delay(100); // NOLINT App.safe_reboot(); @@ -402,8 +398,8 @@ error: } this->status_momentary_error("err", 5000); -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_ERROR, 0.0f, static_cast(error_code)); #endif } diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index b257518e06..058579752e 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -16,12 +16,6 @@ namespace http_request { static const char *const TAG = "http_request.ota"; -void OtaHttpRequestComponent::setup() { -#ifdef USE_OTA_STATE_CALLBACK - ota::register_ota_platform(this); -#endif -} - void OtaHttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air updates via HTTP request"); }; void OtaHttpRequestComponent::set_md5_url(const std::string &url) { @@ -48,24 +42,24 @@ void OtaHttpRequestComponent::flash() { } ESP_LOGI(TAG, "Starting update"); -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_STARTED, 0.0f, 0); #endif auto ota_status = this->do_ota_(); switch (ota_status) { case ota::OTA_RESPONSE_OK: -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, ota_status); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_COMPLETED, 100.0f, ota_status); #endif delay(10); App.safe_reboot(); break; default: -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_ERROR, 0.0f, ota_status); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_ERROR, 0.0f, ota_status); #endif this->md5_computed_.clear(); // will be reset at next attempt this->md5_expected_.clear(); // will be reset at next attempt @@ -165,8 +159,8 @@ uint8_t OtaHttpRequestComponent::do_ota_() { last_progress = now; float percentage = container->get_bytes_read() * 100.0f / container->content_length; ESP_LOGD(TAG, "Progress: %0.1f%%", percentage); -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_IN_PROGRESS, percentage, 0); #endif } } // while diff --git a/esphome/components/http_request/ota/ota_http_request.h b/esphome/components/http_request/ota/ota_http_request.h index 6a86b4ab43..8735189e99 100644 --- a/esphome/components/http_request/ota/ota_http_request.h +++ b/esphome/components/http_request/ota/ota_http_request.h @@ -24,7 +24,6 @@ enum OtaHttpRequestError : uint8_t { class OtaHttpRequestComponent : public ota::OTAComponent, public Parented { public: - void setup() override; void dump_config() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } diff --git a/esphome/components/http_request/update/__init__.py b/esphome/components/http_request/update/__init__.py index abb4b2a430..d84d80109a 100644 --- a/esphome/components/http_request/update/__init__.py +++ b/esphome/components/http_request/update/__init__.py @@ -1,5 +1,5 @@ import esphome.codegen as cg -from esphome.components import update +from esphome.components import ota, update import esphome.config_validation as cv from esphome.const import CONF_SOURCE @@ -38,6 +38,6 @@ async def to_code(config): cg.add(var.set_source_url(config[CONF_SOURCE])) - cg.add_define("USE_OTA_STATE_CALLBACK") + ota.request_ota_state_listeners() await cg.register_component(var, config) diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 22cad625d1..a9392ad736 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -20,19 +20,19 @@ static const char *const TAG = "http_request.update"; static const size_t MAX_READ_SIZE = 256; -void HttpRequestUpdate::setup() { - this->ota_parent_->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t err) { - if (state == ota::OTAState::OTA_IN_PROGRESS) { - this->state_ = update::UPDATE_STATE_INSTALLING; - this->update_info_.has_progress = true; - this->update_info_.progress = progress; - this->publish_state(); - } else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) { - this->state_ = update::UPDATE_STATE_AVAILABLE; - this->status_set_error(LOG_STR("Failed to install firmware")); - this->publish_state(); - } - }); +void HttpRequestUpdate::setup() { this->ota_parent_->add_state_listener(this); } + +void HttpRequestUpdate::on_ota_state(ota::OTAState state, float progress, uint8_t error) { + if (state == ota::OTAState::OTA_IN_PROGRESS) { + this->state_ = update::UPDATE_STATE_INSTALLING; + this->update_info_.has_progress = true; + this->update_info_.progress = progress; + this->publish_state(); + } else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) { + this->state_ = update::UPDATE_STATE_AVAILABLE; + this->status_set_error(LOG_STR("Failed to install firmware")); + this->publish_state(); + } } void HttpRequestUpdate::update() { diff --git a/esphome/components/http_request/update/http_request_update.h b/esphome/components/http_request/update/http_request_update.h index e05fdb0cc2..cf34ace18e 100644 --- a/esphome/components/http_request/update/http_request_update.h +++ b/esphome/components/http_request/update/http_request_update.h @@ -14,7 +14,7 @@ namespace esphome { namespace http_request { -class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent { +class HttpRequestUpdate final : public update::UpdateEntity, public PollingComponent, public ota::OTAStateListener { public: void setup() override; void update() override; @@ -29,6 +29,8 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent { float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + void on_ota_state(ota::OTAState state, float progress, uint8_t error) override; + protected: HttpRequestComponent *request_parent_; OtaHttpRequestComponent *ota_parent_; diff --git a/esphome/components/micro_wake_word/__init__.py b/esphome/components/micro_wake_word/__init__.py index 575fb97799..0d478f749b 100644 --- a/esphome/components/micro_wake_word/__init__.py +++ b/esphome/components/micro_wake_word/__init__.py @@ -7,7 +7,7 @@ from urllib.parse import urljoin from esphome import automation, external_files, git from esphome.automation import register_action, register_condition import esphome.codegen as cg -from esphome.components import esp32, microphone, socket +from esphome.components import esp32, microphone, ota, socket import esphome.config_validation as cv from esphome.const import ( CONF_FILE, @@ -452,7 +452,7 @@ async def to_code(config): cg.add(var.set_microphone_source(mic_source)) cg.add_define("USE_MICRO_WAKE_WORD") - cg.add_define("USE_OTA_STATE_CALLBACK") + ota.request_ota_state_listeners() esp32.add_idf_component(name="espressif/esp-tflite-micro", ref="1.3.3~1") diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp index ec8fa34da4..b8377ead38 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.cpp +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -119,18 +119,21 @@ void MicroWakeWord::setup() { } }); -#ifdef USE_OTA - ota::get_global_ota_callback()->add_on_state_callback( - [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { - if (state == ota::OTA_STARTED) { - this->suspend_task_(); - } else if (state == ota::OTA_ERROR) { - this->resume_task_(); - } - }); +#ifdef USE_OTA_STATE_LISTENER + ota::get_global_ota_callback()->add_global_state_listener(this); #endif } +#ifdef USE_OTA_STATE_LISTENER +void MicroWakeWord::on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { + if (state == ota::OTA_STARTED) { + this->suspend_task_(); + } else if (state == ota::OTA_ERROR) { + this->resume_task_(); + } +} +#endif + void MicroWakeWord::inference_task(void *params) { MicroWakeWord *this_mww = (MicroWakeWord *) params; diff --git a/esphome/components/micro_wake_word/micro_wake_word.h b/esphome/components/micro_wake_word/micro_wake_word.h index d46c40e48b..84261eaa5b 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.h +++ b/esphome/components/micro_wake_word/micro_wake_word.h @@ -9,8 +9,13 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/ring_buffer.h" +#ifdef USE_OTA_STATE_LISTENER +#include "esphome/components/ota/ota_backend.h" +#endif + #include #include @@ -26,13 +31,22 @@ enum State { STOPPED, }; -class MicroWakeWord : public Component { +class MicroWakeWord : public Component +#ifdef USE_OTA_STATE_LISTENER + , + public ota::OTAGlobalStateListener +#endif +{ public: void setup() override; void loop() override; float get_setup_priority() const override; void dump_config() override; +#ifdef USE_OTA_STATE_LISTENER + void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override; +#endif + void start(); void stop(); diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index be1b6da241..8bed9cee42 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -13,6 +13,8 @@ from esphome.const import ( from esphome.core import CORE, coroutine_with_priority from esphome.coroutine import CoroPriority +OTA_STATE_LISTENER_KEY = "ota_state_listener" + CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["md5", "safe_mode"] @@ -86,6 +88,7 @@ BASE_OTA_SCHEMA = cv.Schema( @coroutine_with_priority(CoroPriority.OTA_UPDATES) async def to_code(config): cg.add_define("USE_OTA") + CORE.add_job(final_step) if CORE.is_rp2040 and CORE.using_arduino: cg.add_library("Updater", None) @@ -119,7 +122,24 @@ async def ota_to_code(var, config): await automation.build_automation(trigger, [(cg.uint8, "x")], conf) use_state_callback = True if use_state_callback: - cg.add_define("USE_OTA_STATE_CALLBACK") + request_ota_state_listeners() + + +def request_ota_state_listeners() -> None: + """Request that OTA state listeners be compiled in. + + Components that need to be notified about OTA state changes (start, progress, + complete, error) should call this function during their code generation. + This enables the add_state_listener() API on OTAComponent. + """ + CORE.data[OTA_STATE_LISTENER_KEY] = True + + +@coroutine_with_priority(CoroPriority.FINAL) +async def final_step(): + """Final code generation step to configure optional OTA features.""" + if CORE.data.get(OTA_STATE_LISTENER_KEY, False): + cg.add_define("USE_OTA_STATE_LISTENER") FILTER_SOURCE_FILES = filter_source_files_from_platform( diff --git a/esphome/components/ota/automation.h b/esphome/components/ota/automation.h index 7e1a60f3ce..92c0050ba0 100644 --- a/esphome/components/ota/automation.h +++ b/esphome/components/ota/automation.h @@ -1,5 +1,5 @@ #pragma once -#ifdef USE_OTA_STATE_CALLBACK +#ifdef USE_OTA_STATE_LISTENER #include "ota_backend.h" #include "esphome/core/automation.h" @@ -7,70 +7,64 @@ namespace esphome { namespace ota { -class OTAStateChangeTrigger : public Trigger { +class OTAStateChangeTrigger final : public Trigger, public OTAStateListener { public: - explicit OTAStateChangeTrigger(OTAComponent *parent) { - parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (!parent->is_failed()) { - trigger(state); - } - }); + explicit OTAStateChangeTrigger(OTAComponent *parent) : parent_(parent) { parent->add_state_listener(this); } + + void on_ota_state(OTAState state, float progress, uint8_t error) override { + if (!this->parent_->is_failed()) { + this->trigger(state); + } } + + protected: + OTAComponent *parent_; }; -class OTAStartTrigger : public Trigger<> { +template class OTAStateTrigger final : public Trigger<>, public OTAStateListener { public: - explicit OTAStartTrigger(OTAComponent *parent) { - parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == OTA_STARTED && !parent->is_failed()) { - trigger(); - } - }); + explicit OTAStateTrigger(OTAComponent *parent) : parent_(parent) { parent->add_state_listener(this); } + + void on_ota_state(OTAState state, float progress, uint8_t error) override { + if (state == State && !this->parent_->is_failed()) { + this->trigger(); + } } + + protected: + OTAComponent *parent_; }; -class OTAProgressTrigger : public Trigger { +using OTAStartTrigger = OTAStateTrigger; +using OTAEndTrigger = OTAStateTrigger; +using OTAAbortTrigger = OTAStateTrigger; + +class OTAProgressTrigger final : public Trigger, public OTAStateListener { public: - explicit OTAProgressTrigger(OTAComponent *parent) { - parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == OTA_IN_PROGRESS && !parent->is_failed()) { - trigger(progress); - } - }); + explicit OTAProgressTrigger(OTAComponent *parent) : parent_(parent) { parent->add_state_listener(this); } + + void on_ota_state(OTAState state, float progress, uint8_t error) override { + if (state == OTA_IN_PROGRESS && !this->parent_->is_failed()) { + this->trigger(progress); + } } + + protected: + OTAComponent *parent_; }; -class OTAEndTrigger : public Trigger<> { +class OTAErrorTrigger final : public Trigger, public OTAStateListener { public: - explicit OTAEndTrigger(OTAComponent *parent) { - parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == OTA_COMPLETED && !parent->is_failed()) { - trigger(); - } - }); - } -}; + explicit OTAErrorTrigger(OTAComponent *parent) : parent_(parent) { parent->add_state_listener(this); } -class OTAAbortTrigger : public Trigger<> { - public: - explicit OTAAbortTrigger(OTAComponent *parent) { - parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == OTA_ABORT && !parent->is_failed()) { - trigger(); - } - }); + void on_ota_state(OTAState state, float progress, uint8_t error) override { + if (state == OTA_ERROR && !this->parent_->is_failed()) { + this->trigger(error); + } } -}; -class OTAErrorTrigger : public Trigger { - public: - explicit OTAErrorTrigger(OTAComponent *parent) { - parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == OTA_ERROR && !parent->is_failed()) { - trigger(error); - } - }); - } + protected: + OTAComponent *parent_; }; } // namespace ota diff --git a/esphome/components/ota/ota_backend.cpp b/esphome/components/ota/ota_backend.cpp index 30de4ec4b3..8fb9f67214 100644 --- a/esphome/components/ota/ota_backend.cpp +++ b/esphome/components/ota/ota_backend.cpp @@ -3,7 +3,7 @@ namespace esphome { namespace ota { -#ifdef USE_OTA_STATE_CALLBACK +#ifdef USE_OTA_STATE_LISTENER OTAGlobalCallback *global_ota_callback{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) OTAGlobalCallback *get_global_ota_callback() { @@ -13,7 +13,12 @@ OTAGlobalCallback *get_global_ota_callback() { return global_ota_callback; } -void register_ota_platform(OTAComponent *ota_caller) { get_global_ota_callback()->register_ota(ota_caller); } +void OTAComponent::notify_state_(OTAState state, float progress, uint8_t error) { + for (auto *listener : this->state_listeners_) { + listener->on_ota_state(state, progress, error); + } + get_global_ota_callback()->notify_ota_state(state, progress, error, this); +} #endif } // namespace ota diff --git a/esphome/components/ota/ota_backend.h b/esphome/components/ota/ota_backend.h index 64ee0b9f7c..e03afd4fc6 100644 --- a/esphome/components/ota/ota_backend.h +++ b/esphome/components/ota/ota_backend.h @@ -4,8 +4,8 @@ #include "esphome/core/defines.h" #include "esphome/core/helpers.h" -#ifdef USE_OTA_STATE_CALLBACK -#include "esphome/core/automation.h" +#ifdef USE_OTA_STATE_LISTENER +#include #endif namespace esphome { @@ -60,62 +60,75 @@ class OTABackend { virtual bool supports_compression() = 0; }; -class OTAComponent : public Component { -#ifdef USE_OTA_STATE_CALLBACK +/** Listener interface for OTA state changes. + * + * Components can implement this interface to receive OTA state updates + * without the overhead of std::function callbacks. + */ +class OTAStateListener { public: - void add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); - } + virtual ~OTAStateListener() = default; + virtual void on_ota_state(OTAState state, float progress, uint8_t error) = 0; +}; + +class OTAComponent : public Component { +#ifdef USE_OTA_STATE_LISTENER + public: + void add_state_listener(OTAStateListener *listener) { this->state_listeners_.push_back(listener); } protected: - /** Extended callback manager with deferred call support. + void notify_state_(OTAState state, float progress, uint8_t error); + + /** Notify state with deferral to main loop (for thread safety). * - * This adds a call_deferred() method for thread-safe execution from other tasks. + * This should be used by OTA implementations that run in separate tasks + * (like web_server OTA) to ensure listeners execute in the main loop. */ - class StateCallbackManager : public CallbackManager { - public: - StateCallbackManager(OTAComponent *component) : component_(component) {} + void notify_state_deferred_(OTAState state, float progress, uint8_t error) { + this->defer([this, state, progress, error]() { this->notify_state_(state, progress, error); }); + } - /** Call callbacks with deferral to main loop (for thread safety). - * - * This should be used by OTA implementations that run in separate tasks - * (like web_server OTA) to ensure callbacks execute in the main loop. - */ - void call_deferred(ota::OTAState state, float progress, uint8_t error) { - component_->defer([this, state, progress, error]() { this->call(state, progress, error); }); - } - - private: - OTAComponent *component_; - }; - - StateCallbackManager state_callback_{this}; + std::vector state_listeners_; #endif }; -#ifdef USE_OTA_STATE_CALLBACK +#ifdef USE_OTA_STATE_LISTENER + +/** Listener interface for global OTA state changes (includes OTA component pointer). + * + * Used by OTAGlobalCallback to aggregate state from multiple OTA components. + */ +class OTAGlobalStateListener { + public: + virtual ~OTAGlobalStateListener() = default; + virtual void on_ota_global_state(OTAState state, float progress, uint8_t error, OTAComponent *component) = 0; +}; + +/** Global callback that aggregates OTA state from all OTA components. + * + * OTA components call notify_ota_state() directly with their pointer, + * which forwards the event to all registered global listeners. + */ class OTAGlobalCallback { public: - void register_ota(OTAComponent *ota_caller) { - ota_caller->add_on_state_callback([this, ota_caller](OTAState state, float progress, uint8_t error) { - this->state_callback_.call(state, progress, error, ota_caller); - }); - } - void add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); + void add_global_state_listener(OTAGlobalStateListener *listener) { this->global_listeners_.push_back(listener); } + + void notify_ota_state(OTAState state, float progress, uint8_t error, OTAComponent *component) { + for (auto *listener : this->global_listeners_) { + listener->on_ota_global_state(state, progress, error, component); + } } protected: - CallbackManager state_callback_{}; + std::vector global_listeners_; }; OTAGlobalCallback *get_global_ota_callback(); -void register_ota_platform(OTAComponent *ota_caller); // OTA implementations should use: -// - state_callback_.call() when already in main loop (e.g., esphome OTA) -// - state_callback_.call_deferred() when in separate task (e.g., web_server OTA) -// This ensures proper callback execution in all contexts. +// - notify_state_() when already in main loop (e.g., esphome OTA) +// - notify_state_deferred_() when in separate task (e.g., web_server OTA) +// This ensures proper listener execution in all contexts. #endif std::unique_ptr make_ota_backend(); diff --git a/esphome/components/speaker/media_player/__init__.py b/esphome/components/speaker/media_player/__init__.py index 062bff92f8..4ca57f2c4a 100644 --- a/esphome/components/speaker/media_player/__init__.py +++ b/esphome/components/speaker/media_player/__init__.py @@ -6,7 +6,7 @@ from pathlib import Path from esphome import automation, external_files import esphome.codegen as cg -from esphome.components import audio, esp32, media_player, network, psram, speaker +from esphome.components import audio, esp32, media_player, network, ota, psram, speaker import esphome.config_validation as cv from esphome.const import ( CONF_BUFFER_SIZE, @@ -342,7 +342,7 @@ async def to_code(config): var = await media_player.new_media_player(config) await cg.register_component(var, config) - cg.add_define("USE_OTA_STATE_CALLBACK") + ota.request_ota_state_listeners() cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE])) diff --git a/esphome/components/speaker/media_player/speaker_media_player.cpp b/esphome/components/speaker/media_player/speaker_media_player.cpp index b45a78010a..5722aab195 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.cpp +++ b/esphome/components/speaker/media_player/speaker_media_player.cpp @@ -66,25 +66,8 @@ void SpeakerMediaPlayer::setup() { this->set_mute_state_(false); } -#ifdef USE_OTA - ota::get_global_ota_callback()->add_on_state_callback( - [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { - if (state == ota::OTA_STARTED) { - if (this->media_pipeline_ != nullptr) { - this->media_pipeline_->suspend_tasks(); - } - if (this->announcement_pipeline_ != nullptr) { - this->announcement_pipeline_->suspend_tasks(); - } - } else if (state == ota::OTA_ERROR) { - if (this->media_pipeline_ != nullptr) { - this->media_pipeline_->resume_tasks(); - } - if (this->announcement_pipeline_ != nullptr) { - this->announcement_pipeline_->resume_tasks(); - } - } - }); +#ifdef USE_OTA_STATE_LISTENER + ota::get_global_ota_callback()->add_global_state_listener(this); #endif this->announcement_pipeline_ = @@ -300,6 +283,27 @@ void SpeakerMediaPlayer::watch_media_commands_() { } } +#ifdef USE_OTA_STATE_LISTENER +void SpeakerMediaPlayer::on_ota_global_state(ota::OTAState state, float progress, uint8_t error, + ota::OTAComponent *comp) { + if (state == ota::OTA_STARTED) { + if (this->media_pipeline_ != nullptr) { + this->media_pipeline_->suspend_tasks(); + } + if (this->announcement_pipeline_ != nullptr) { + this->announcement_pipeline_->suspend_tasks(); + } + } else if (state == ota::OTA_ERROR) { + if (this->media_pipeline_ != nullptr) { + this->media_pipeline_->resume_tasks(); + } + if (this->announcement_pipeline_ != nullptr) { + this->announcement_pipeline_->resume_tasks(); + } + } +} +#endif + void SpeakerMediaPlayer::loop() { this->watch_media_commands_(); diff --git a/esphome/components/speaker/media_player/speaker_media_player.h b/esphome/components/speaker/media_player/speaker_media_player.h index 967772d1a5..f1c564b63d 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.h +++ b/esphome/components/speaker/media_player/speaker_media_player.h @@ -5,14 +5,18 @@ #include "audio_pipeline.h" #include "esphome/components/audio/audio.h" - #include "esphome/components/media_player/media_player.h" #include "esphome/components/speaker/speaker.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/preferences.h" +#ifdef USE_OTA_STATE_LISTENER +#include "esphome/components/ota/ota_backend.h" +#endif + #include #include #include @@ -39,12 +43,22 @@ struct VolumeRestoreState { bool is_muted; }; -class SpeakerMediaPlayer : public Component, public media_player::MediaPlayer { +class SpeakerMediaPlayer : public Component, + public media_player::MediaPlayer +#ifdef USE_OTA_STATE_LISTENER + , + public ota::OTAGlobalStateListener +#endif +{ public: float get_setup_priority() const override { return esphome::setup_priority::PROCESSOR; } void setup() override; void loop() override; +#ifdef USE_OTA_STATE_LISTENER + void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override; +#endif + // MediaPlayer implementations media_player::MediaPlayerTraits get_traits() override; bool is_muted() const override { return this->is_muted_; } diff --git a/esphome/components/web_server/ota/ota_web_server.cpp b/esphome/components/web_server/ota/ota_web_server.cpp index 7929f3647f..f612aa056c 100644 --- a/esphome/components/web_server/ota/ota_web_server.cpp +++ b/esphome/components/web_server/ota/ota_web_server.cpp @@ -84,9 +84,9 @@ void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) { } else { ESP_LOGD(TAG, "OTA in progress: %" PRIu32 " bytes read", this->ota_read_length_); } -#ifdef USE_OTA_STATE_CALLBACK - // Report progress - use call_deferred since we're in web server task - this->parent_->state_callback_.call_deferred(ota::OTA_IN_PROGRESS, percentage, 0); +#ifdef USE_OTA_STATE_LISTENER + // Report progress - use notify_state_deferred_ since we're in web server task + this->parent_->notify_state_deferred_(ota::OTA_IN_PROGRESS, percentage, 0); #endif this->last_ota_progress_ = now; } @@ -114,9 +114,9 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf // Initialize OTA on first call this->ota_init_(filename.c_str()); -#ifdef USE_OTA_STATE_CALLBACK - // Notify OTA started - use call_deferred since we're in web server task - this->parent_->state_callback_.call_deferred(ota::OTA_STARTED, 0.0f, 0); +#ifdef USE_OTA_STATE_LISTENER + // Notify OTA started - use notify_state_deferred_ since we're in web server task + this->parent_->notify_state_deferred_(ota::OTA_STARTED, 0.0f, 0); #endif // Platform-specific pre-initialization @@ -134,9 +134,9 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf this->ota_backend_ = ota::make_ota_backend(); if (!this->ota_backend_) { ESP_LOGE(TAG, "Failed to create OTA backend"); -#ifdef USE_OTA_STATE_CALLBACK - this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, - static_cast(ota::OTA_RESPONSE_ERROR_UNKNOWN)); +#ifdef USE_OTA_STATE_LISTENER + this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, + static_cast(ota::OTA_RESPONSE_ERROR_UNKNOWN)); #endif return; } @@ -148,8 +148,8 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf if (error_code != ota::OTA_RESPONSE_OK) { ESP_LOGE(TAG, "OTA begin failed: %d", error_code); this->ota_backend_.reset(); -#ifdef USE_OTA_STATE_CALLBACK - this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#ifdef USE_OTA_STATE_LISTENER + this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast(error_code)); #endif return; } @@ -166,8 +166,8 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf ESP_LOGE(TAG, "OTA write failed: %d", error_code); this->ota_backend_->abort(); this->ota_backend_.reset(); -#ifdef USE_OTA_STATE_CALLBACK - this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#ifdef USE_OTA_STATE_LISTENER + this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast(error_code)); #endif return; } @@ -186,15 +186,15 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf error_code = this->ota_backend_->end(); if (error_code == ota::OTA_RESPONSE_OK) { this->ota_success_ = true; -#ifdef USE_OTA_STATE_CALLBACK - // Report completion before reboot - use call_deferred since we're in web server task - this->parent_->state_callback_.call_deferred(ota::OTA_COMPLETED, 100.0f, 0); +#ifdef USE_OTA_STATE_LISTENER + // Report completion before reboot - use notify_state_deferred_ since we're in web server task + this->parent_->notify_state_deferred_(ota::OTA_COMPLETED, 100.0f, 0); #endif this->schedule_ota_reboot_(); } else { ESP_LOGE(TAG, "OTA end failed: %d", error_code); -#ifdef USE_OTA_STATE_CALLBACK - this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#ifdef USE_OTA_STATE_LISTENER + this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast(error_code)); #endif } this->ota_backend_.reset(); @@ -232,10 +232,6 @@ void WebServerOTAComponent::setup() { // AsyncWebServer takes ownership of the handler and will delete it when the server is destroyed base->add_handler(new OTARequestHandler(this)); // NOLINT -#ifdef USE_OTA_STATE_CALLBACK - // Register with global OTA callback system - ota::register_ota_platform(this); -#endif } void WebServerOTAComponent::dump_config() { ESP_LOGCONFIG(TAG, "Web Server OTA"); } diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 4cbe683723..0c12b29eb7 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -146,7 +146,7 @@ #define USE_OTA_PASSWORD #define USE_OTA_SHA256 #define ALLOW_OTA_DOWNGRADE_MD5 -#define USE_OTA_STATE_CALLBACK +#define USE_OTA_STATE_LISTENER #define USE_OTA_VERSION 2 #define USE_TIME_TIMEZONE #define USE_WIFI From ada6c42f3f021ec57f7864185c635feee28d7dd4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Dec 2025 11:48:14 -1000 Subject: [PATCH 0729/1145] [alarm_control_panel] Remove redundant per-state callbacks (#12171) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../alarm_control_panel.cpp | 46 +-- .../alarm_control_panel/alarm_control_panel.h | 63 +--- .../alarm_control_panel/automation.h | 66 ++-- ...alarm_control_panel_state_transitions.yaml | 106 ++++++ ...t_alarm_control_panel_state_transitions.py | 319 ++++++++++++++++++ 5 files changed, 453 insertions(+), 147 deletions(-) create mode 100644 tests/integration/fixtures/alarm_control_panel_state_transitions.yaml create mode 100644 tests/integration/test_alarm_control_panel_state_transitions.py diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index c29e02c8ef..f938155dd3 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -35,26 +35,12 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) { ESP_LOGD(TAG, "Set state to: %s, previous: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)), LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state))); this->current_state_ = state; + // Single state callback - triggers check get_state() for specific states this->state_callback_.call(); #if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_alarm_control_panel_update(this); #endif - if (state == ACP_STATE_TRIGGERED) { - this->triggered_callback_.call(); - } else if (state == ACP_STATE_ARMING) { - this->arming_callback_.call(); - } else if (state == ACP_STATE_PENDING) { - this->pending_callback_.call(); - } else if (state == ACP_STATE_ARMED_HOME) { - this->armed_home_callback_.call(); - } else if (state == ACP_STATE_ARMED_NIGHT) { - this->armed_night_callback_.call(); - } else if (state == ACP_STATE_ARMED_AWAY) { - this->armed_away_callback_.call(); - } else if (state == ACP_STATE_DISARMED) { - this->disarmed_callback_.call(); - } - + // Cleared fires when leaving TRIGGERED state if (prev_state == ACP_STATE_TRIGGERED) { this->cleared_callback_.call(); } @@ -69,34 +55,6 @@ void AlarmControlPanel::add_on_state_callback(std::function &&callback) this->state_callback_.add(std::move(callback)); } -void AlarmControlPanel::add_on_triggered_callback(std::function &&callback) { - this->triggered_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_arming_callback(std::function &&callback) { - this->arming_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_armed_home_callback(std::function &&callback) { - this->armed_home_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_armed_night_callback(std::function &&callback) { - this->armed_night_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_armed_away_callback(std::function &&callback) { - this->armed_away_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_pending_callback(std::function &&callback) { - this->pending_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_disarmed_callback(std::function &&callback) { - this->disarmed_callback_.add(std::move(callback)); -} - void AlarmControlPanel::add_on_cleared_callback(std::function &&callback) { this->cleared_callback_.add(std::move(callback)); } diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.h b/esphome/components/alarm_control_panel/alarm_control_panel.h index 85c2b2148e..c46edc11c2 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel.h @@ -35,54 +35,13 @@ class AlarmControlPanel : public EntityBase { */ void publish_state(AlarmControlPanelState state); - /** Add a callback for when the state of the alarm_control_panel changes + /** Add a callback for when the state of the alarm_control_panel changes. + * Triggers can check get_state() to determine the new state. * * @param callback The callback function */ void add_on_state_callback(std::function &&callback); - /** Add a callback for when the state of the alarm_control_panel chanes to triggered - * - * @param callback The callback function - */ - void add_on_triggered_callback(std::function &&callback); - - /** Add a callback for when the state of the alarm_control_panel chanes to arming - * - * @param callback The callback function - */ - void add_on_arming_callback(std::function &&callback); - - /** Add a callback for when the state of the alarm_control_panel changes to pending - * - * @param callback The callback function - */ - void add_on_pending_callback(std::function &&callback); - - /** Add a callback for when the state of the alarm_control_panel changes to armed_home - * - * @param callback The callback function - */ - void add_on_armed_home_callback(std::function &&callback); - - /** Add a callback for when the state of the alarm_control_panel changes to armed_night - * - * @param callback The callback function - */ - void add_on_armed_night_callback(std::function &&callback); - - /** Add a callback for when the state of the alarm_control_panel changes to armed_away - * - * @param callback The callback function - */ - void add_on_armed_away_callback(std::function &&callback); - - /** Add a callback for when the state of the alarm_control_panel changes to disarmed - * - * @param callback The callback function - */ - void add_on_disarmed_callback(std::function &&callback); - /** Add a callback for when the state of the alarm_control_panel clears from triggered * * @param callback The callback function @@ -172,23 +131,9 @@ class AlarmControlPanel : public EntityBase { uint32_t last_update_; // the call control function virtual void control(const AlarmControlPanelCall &call) = 0; - // state callback + // state callback - triggers check get_state() for specific state CallbackManager state_callback_{}; - // trigger callback - CallbackManager triggered_callback_{}; - // arming callback - CallbackManager arming_callback_{}; - // pending callback - CallbackManager pending_callback_{}; - // armed_home callback - CallbackManager armed_home_callback_{}; - // armed_night callback - CallbackManager armed_night_callback_{}; - // armed_away callback - CallbackManager armed_away_callback_{}; - // disarmed callback - CallbackManager disarmed_callback_{}; - // clear callback + // clear callback - fires when leaving TRIGGERED state CallbackManager cleared_callback_{}; // chime callback CallbackManager chime_callback_{}; diff --git a/esphome/components/alarm_control_panel/automation.h b/esphome/components/alarm_control_panel/automation.h index db2ef78158..af4a14e27a 100644 --- a/esphome/components/alarm_control_panel/automation.h +++ b/esphome/components/alarm_control_panel/automation.h @@ -6,6 +6,7 @@ namespace esphome { namespace alarm_control_panel { +/// Trigger on any state change class StateTrigger : public Trigger<> { public: explicit StateTrigger(AlarmControlPanel *alarm_control_panel) { @@ -13,55 +14,30 @@ class StateTrigger : public Trigger<> { } }; -class TriggeredTrigger : public Trigger<> { +/// Template trigger that fires when entering a specific state +template class StateEnterTrigger : public Trigger<> { public: - explicit TriggeredTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_triggered_callback([this]() { this->trigger(); }); + explicit StateEnterTrigger(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) { + alarm_control_panel->add_on_state_callback([this]() { + if (this->alarm_control_panel_->get_state() == State) + this->trigger(); + }); } + + protected: + AlarmControlPanel *alarm_control_panel_; }; -class ArmingTrigger : public Trigger<> { - public: - explicit ArmingTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_arming_callback([this]() { this->trigger(); }); - } -}; - -class PendingTrigger : public Trigger<> { - public: - explicit PendingTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_pending_callback([this]() { this->trigger(); }); - } -}; - -class ArmedHomeTrigger : public Trigger<> { - public: - explicit ArmedHomeTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_armed_home_callback([this]() { this->trigger(); }); - } -}; - -class ArmedNightTrigger : public Trigger<> { - public: - explicit ArmedNightTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_armed_night_callback([this]() { this->trigger(); }); - } -}; - -class ArmedAwayTrigger : public Trigger<> { - public: - explicit ArmedAwayTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_armed_away_callback([this]() { this->trigger(); }); - } -}; - -class DisarmedTrigger : public Trigger<> { - public: - explicit DisarmedTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_disarmed_callback([this]() { this->trigger(); }); - } -}; +// Type aliases for state-specific triggers +using TriggeredTrigger = StateEnterTrigger; +using ArmingTrigger = StateEnterTrigger; +using PendingTrigger = StateEnterTrigger; +using ArmedHomeTrigger = StateEnterTrigger; +using ArmedNightTrigger = StateEnterTrigger; +using ArmedAwayTrigger = StateEnterTrigger; +using DisarmedTrigger = StateEnterTrigger; +/// Trigger when leaving TRIGGERED state (alarm cleared) class ClearedTrigger : public Trigger<> { public: explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) { @@ -69,6 +45,7 @@ class ClearedTrigger : public Trigger<> { } }; +/// Trigger on chime event (zone opened while disarmed) class ChimeTrigger : public Trigger<> { public: explicit ChimeTrigger(AlarmControlPanel *alarm_control_panel) { @@ -76,6 +53,7 @@ class ChimeTrigger : public Trigger<> { } }; +/// Trigger on ready state change class ReadyTrigger : public Trigger<> { public: explicit ReadyTrigger(AlarmControlPanel *alarm_control_panel) { diff --git a/tests/integration/fixtures/alarm_control_panel_state_transitions.yaml b/tests/integration/fixtures/alarm_control_panel_state_transitions.yaml new file mode 100644 index 0000000000..1edb401a0d --- /dev/null +++ b/tests/integration/fixtures/alarm_control_panel_state_transitions.yaml @@ -0,0 +1,106 @@ +esphome: + name: alarm-state-transitions + friendly_name: "Alarm Control Panel State Transitions Test" + +logger: + +host: + +globals: + - id: door_sensor_state + type: bool + initial_value: "false" + - id: chime_sensor_state + type: bool + initial_value: "false" + +switch: + # Switch to control the door sensor state + - platform: template + id: door_sensor_switch + name: "Door Sensor Switch" + optimistic: true + turn_on_action: + - globals.set: + id: door_sensor_state + value: "true" + turn_off_action: + - globals.set: + id: door_sensor_state + value: "false" + # Switch to control the chime sensor state + - platform: template + id: chime_sensor_switch + name: "Chime Sensor Switch" + optimistic: true + turn_on_action: + - globals.set: + id: chime_sensor_state + value: "true" + turn_off_action: + - globals.set: + id: chime_sensor_state + value: "false" + +binary_sensor: + - platform: template + id: door_sensor + name: "Door Sensor" + lambda: |- + return id(door_sensor_state); + - platform: template + id: chime_sensor + name: "Chime Sensor" + lambda: |- + return id(chime_sensor_state); + +alarm_control_panel: + - platform: template + id: test_alarm + name: "Test Alarm" + codes: + - "1234" + requires_code_to_arm: true + # Short timeouts for faster testing + arming_away_time: 50ms + arming_home_time: 50ms + arming_night_time: 50ms + pending_time: 50ms + trigger_time: 100ms + restore_mode: ALWAYS_DISARMED + binary_sensors: + - input: door_sensor + bypass_armed_home: false + bypass_armed_night: false + chime: false + trigger_mode: DELAYED + - input: chime_sensor + bypass_armed_home: true + bypass_armed_night: true + chime: true + trigger_mode: DELAYED + on_state: + - logger.log: "State changed" + on_disarmed: + - logger.log: "Alarm disarmed" + on_arming: + - logger.log: "Alarm arming" + on_armed_away: + - logger.log: "Alarm armed away" + on_armed_home: + - logger.log: "Alarm armed home" + on_armed_night: + - logger.log: "Alarm armed night" + on_pending: + - logger.log: "Alarm pending" + on_triggered: + - logger.log: "Alarm triggered" + on_cleared: + - logger.log: "Alarm cleared" + on_chime: + - logger.log: "Chime activated" + on_ready: + - logger.log: "Sensors ready state changed" + +api: + batch_delay: 0ms diff --git a/tests/integration/test_alarm_control_panel_state_transitions.py b/tests/integration/test_alarm_control_panel_state_transitions.py new file mode 100644 index 0000000000..2977ff56c2 --- /dev/null +++ b/tests/integration/test_alarm_control_panel_state_transitions.py @@ -0,0 +1,319 @@ +"""Integration test for alarm control panel state transitions.""" + +from __future__ import annotations + +import asyncio +import re + +import aioesphomeapi +from aioesphomeapi import ( + AlarmControlPanelCommand, + AlarmControlPanelEntityState, + AlarmControlPanelInfo, + AlarmControlPanelState, + SwitchInfo, +) +import pytest + +from .state_utils import InitialStateHelper +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_alarm_control_panel_state_transitions( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test alarm control panel state transitions. + + This comprehensive test verifies all state transitions and listener callbacks: + + 1. Basic arm/disarm sequences: + - DISARMED -> ARMING -> ARMED_AWAY -> DISARMED + - DISARMED -> ARMING -> ARMED_HOME -> DISARMED + - DISARMED -> ARMING -> ARMED_NIGHT -> DISARMED + + 2. Wrong code rejection + + 3. Sensor triggering while armed: + - ARMED_AWAY -> PENDING -> TRIGGERED (delayed sensor) + - TRIGGERED -> ARMED_AWAY (auto-reset after trigger_time, fires on_cleared) + + 4. Chime functionality: + - Sensor open while DISARMED triggers on_chime + + 5. Ready state: + - Sensor state changes trigger on_ready + """ + loop = asyncio.get_running_loop() + + # Track log messages for callback verification + log_lines: list[str] = [] + chime_future: asyncio.Future[bool] = loop.create_future() + ready_futures: list[asyncio.Future[bool]] = [] + cleared_future: asyncio.Future[bool] = loop.create_future() + + # Patterns to match log output from callbacks + chime_pattern = re.compile(r"Chime activated") + ready_pattern = re.compile(r"Sensors ready state changed") + cleared_pattern = re.compile(r"Alarm cleared") + + def on_log_line(line: str) -> None: + log_lines.append(line) + if not chime_future.done() and chime_pattern.search(line): + chime_future.set_result(True) + if ready_pattern.search(line): + # Create new future for each ready event + for fut in ready_futures: + if not fut.done(): + fut.set_result(True) + break + if not cleared_future.done() and cleared_pattern.search(line): + cleared_future.set_result(True) + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + api_client_connected() as client, + ): + entities, _ = await client.list_entities_services() + + # Find entities + alarm_info: AlarmControlPanelInfo | None = None + door_switch_info: SwitchInfo | None = None + chime_switch_info: SwitchInfo | None = None + + for entity in entities: + if isinstance(entity, AlarmControlPanelInfo): + alarm_info = entity + elif isinstance(entity, SwitchInfo): + if entity.name == "Door Sensor Switch": + door_switch_info = entity + elif entity.name == "Chime Sensor Switch": + chime_switch_info = entity + + assert alarm_info is not None, "Alarm control panel not found" + assert door_switch_info is not None, "Door sensor switch not found" + assert chime_switch_info is not None, "Chime sensor switch not found" + + # Track state changes + states_received: list[AlarmControlPanelState] = [] + state_event = asyncio.Event() + + def on_state(state: aioesphomeapi.EntityState) -> None: + if ( + isinstance(state, AlarmControlPanelEntityState) + and state.key == alarm_info.key + ): + states_received.append(state.state) + state_event.set() + + # Use InitialStateHelper to handle initial state broadcast + initial_state_helper = InitialStateHelper(entities) + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + # Wait for initial states from all entities + await initial_state_helper.wait_for_initial_states() + + # Verify alarm panel started in DISARMED state + initial_alarm_state = initial_state_helper.initial_states.get(alarm_info.key) + assert initial_alarm_state is not None, "No initial alarm state received" + assert isinstance(initial_alarm_state, AlarmControlPanelEntityState) + assert initial_alarm_state.state == AlarmControlPanelState.DISARMED + + # Helper to wait for specific state + async def wait_for_state( + expected: AlarmControlPanelState, timeout: float = 5.0 + ) -> None: + deadline = loop.time() + timeout + while True: + remaining = deadline - loop.time() + if remaining <= 0: + raise TimeoutError( + f"Timeout waiting for state {expected}, " + f"last state: {states_received[-1] if states_received else 'none'}" + ) + await asyncio.wait_for(state_event.wait(), timeout=remaining) + state_event.clear() + if states_received[-1] == expected: + return + + # ===== Test wrong code rejection ===== + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.ARM_AWAY, + code="0000", # Wrong code + ) + + # Should NOT transition - wait a bit and verify no state changes + with pytest.raises(asyncio.TimeoutError): + await asyncio.wait_for(state_event.wait(), timeout=0.5) + # No state changes should have occurred (list is empty) + assert len(states_received) == 0, f"Unexpected state changes: {states_received}" + + # ===== Test ARM_AWAY sequence ===== + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.ARM_AWAY, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.ARMING) + await wait_for_state(AlarmControlPanelState.ARMED_AWAY) + + # Disarm + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.DISARM, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.DISARMED) + + # ===== Test ARM_HOME sequence ===== + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.ARM_HOME, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.ARMING) + await wait_for_state(AlarmControlPanelState.ARMED_HOME) + + # Disarm + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.DISARM, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.DISARMED) + + # ===== Test ARM_NIGHT sequence ===== + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.ARM_NIGHT, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.ARMING) + await wait_for_state(AlarmControlPanelState.ARMED_NIGHT) + + # Disarm + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.DISARM, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.DISARMED) + + # Verify basic state sequence (initial DISARMED is handled by InitialStateHelper) + expected_states = [ + AlarmControlPanelState.ARMING, # Arm away + AlarmControlPanelState.ARMED_AWAY, + AlarmControlPanelState.DISARMED, + AlarmControlPanelState.ARMING, # Arm home + AlarmControlPanelState.ARMED_HOME, + AlarmControlPanelState.DISARMED, + AlarmControlPanelState.ARMING, # Arm night + AlarmControlPanelState.ARMED_NIGHT, + AlarmControlPanelState.DISARMED, + ] + assert states_received == expected_states, ( + f"State sequence mismatch.\nExpected: {expected_states}\n" + f"Got: {states_received}" + ) + + # ===== Test PENDING -> TRIGGERED -> CLEARED sequence ===== + # This tests on_pending, on_triggered, and on_cleared callbacks + + # Arm away first + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.ARM_AWAY, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.ARMING) + await wait_for_state(AlarmControlPanelState.ARMED_AWAY) + + # Trip the door sensor (delayed mode triggers PENDING first) + client.switch_command(door_switch_info.key, True) + + # Should go to PENDING (delayed sensor) + await wait_for_state(AlarmControlPanelState.PENDING) + + # Should go to TRIGGERED after pending_time (50ms) + await wait_for_state(AlarmControlPanelState.TRIGGERED) + + # Close the sensor + client.switch_command(door_switch_info.key, False) + + # Wait for trigger_time to expire and auto-reset (100ms) + # The alarm should go back to ARMED_AWAY after trigger_time + # This transition FROM TRIGGERED fires on_cleared + await wait_for_state(AlarmControlPanelState.ARMED_AWAY, timeout=2.0) + + # Verify on_cleared was logged + try: + await asyncio.wait_for(cleared_future, timeout=1.0) + except TimeoutError: + pytest.fail(f"on_cleared callback not fired. Log lines: {log_lines[-20:]}") + + # Disarm + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.DISARM, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.DISARMED) + + # Verify trigger sequence was added + assert AlarmControlPanelState.PENDING in states_received + assert AlarmControlPanelState.TRIGGERED in states_received + + # ===== Test chime (sensor open while disarmed) ===== + # The chime_sensor has chime: true, so opening it while disarmed + # should trigger on_chime callback + + # We're currently DISARMED - open the chime sensor + client.switch_command(chime_switch_info.key, True) + + # Wait for chime callback to be logged + try: + await asyncio.wait_for(chime_future, timeout=2.0) + except TimeoutError: + pytest.fail(f"on_chime callback not fired. Log lines: {log_lines[-20:]}") + + # Close the chime sensor + client.switch_command(chime_switch_info.key, False) + + # ===== Test ready state changes ===== + # Opening/closing sensors while disarmed affects ready state + # The on_ready callback fires when sensors_ready changes + + # Set up futures for ready state changes + ready_future_1: asyncio.Future[bool] = loop.create_future() + ready_future_2: asyncio.Future[bool] = loop.create_future() + ready_futures.extend([ready_future_1, ready_future_2]) + + # Open door sensor (makes alarm not ready) + client.switch_command(door_switch_info.key, True) + + # Wait for first on_ready callback (not ready) + try: + await asyncio.wait_for(ready_future_1, timeout=2.0) + except TimeoutError: + pytest.fail( + f"on_ready callback not fired when sensor opened. " + f"Log lines: {log_lines[-20:]}" + ) + + # Close door sensor (makes alarm ready again) + client.switch_command(door_switch_info.key, False) + + # Wait for second on_ready callback (ready) + try: + await asyncio.wait_for(ready_future_2, timeout=2.0) + except TimeoutError: + pytest.fail( + f"on_ready callback not fired when sensor closed. " + f"Log lines: {log_lines[-20:]}" + ) + + # Final state should still be DISARMED + assert states_received[-1] == AlarmControlPanelState.DISARMED From c9fccdff251ae4a796d1123c656afe1ac9490631 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Dec 2025 12:05:52 -1000 Subject: [PATCH 0730/1145] [fan] Add zero-copy support for API preset mode commands (#12404) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_pb2.cpp | 7 +++++-- esphome/components/api/api_pb2.h | 5 +++-- esphome/components/api/api_pb2_dump.cpp | 4 +++- esphome/components/fan/fan.cpp | 22 +++++++++++++++++----- esphome/components/fan/fan.h | 2 ++ esphome/components/fan/fan_traits.h | 7 +++++-- 8 files changed, 37 insertions(+), 14 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index dd8320bebb..e8c900df26 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -477,7 +477,7 @@ message FanCommandRequest { bool has_speed_level = 10; int32 speed_level = 11; bool has_preset_mode = 12; - string preset_mode = 13; + string preset_mode = 13 [(pointer_to_buffer) = true]; uint32 device_id = 14 [(field_ifdef) = "USE_DEVICES"]; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 686fdcba41..126d3cb220 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -447,7 +447,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { if (msg.has_direction) call.set_direction(static_cast(msg.direction)); if (msg.has_preset_mode) - call.set_preset_mode(msg.preset_mode); + call.set_preset_mode(reinterpret_cast(msg.preset_mode), msg.preset_mode_len); call.perform(); } #endif diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 211f856e3b..8bba13a4de 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -447,9 +447,12 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool FanCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 13: - this->preset_mode = value.as_string(); + case 13: { + // Use raw data directly to avoid allocation + this->preset_mode = value.data(); + this->preset_mode_len = value.size(); break; + } default: return false; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 4e10c63881..d3b91ac56b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -765,7 +765,7 @@ class FanStateResponse final : public StateResponseProtoMessage { class FanCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 31; - static constexpr uint8_t ESTIMATED_SIZE = 38; + static constexpr uint8_t ESTIMATED_SIZE = 48; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "fan_command_request"; } #endif @@ -778,7 +778,8 @@ class FanCommandRequest final : public CommandProtoMessage { bool has_speed_level{false}; int32_t speed_level{0}; bool has_preset_mode{false}; - std::string preset_mode{}; + const uint8_t *preset_mode{nullptr}; + uint16_t preset_mode_len{0}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 90e8e75c93..d733e66a6d 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -923,7 +923,9 @@ void FanCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_speed_level", this->has_speed_level); dump_field(out, "speed_level", this->speed_level); dump_field(out, "has_preset_mode", this->has_preset_mode); - dump_field(out, "preset_mode", this->preset_mode); + out.append(" preset_mode: "); + out.append(format_hex_pretty(this->preset_mode, this->preset_mode_len)); + out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index d37825a651..bf5506da4b 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -19,22 +19,28 @@ const LogString *fan_direction_to_string(FanDirection direction) { } } -FanCall &FanCall::set_preset_mode(const std::string &preset_mode) { return this->set_preset_mode(preset_mode.c_str()); } +FanCall &FanCall::set_preset_mode(const std::string &preset_mode) { + return this->set_preset_mode(preset_mode.data(), preset_mode.size()); +} FanCall &FanCall::set_preset_mode(const char *preset_mode) { - if (preset_mode == nullptr || strlen(preset_mode) == 0) { + return this->set_preset_mode(preset_mode, preset_mode ? strlen(preset_mode) : 0); +} + +FanCall &FanCall::set_preset_mode(const char *preset_mode, size_t len) { + if (preset_mode == nullptr || len == 0) { this->preset_mode_ = nullptr; return *this; } // Find and validate pointer from traits immediately auto traits = this->parent_.get_traits(); - const char *validated_mode = traits.find_preset_mode(preset_mode); + const char *validated_mode = traits.find_preset_mode(preset_mode, len); if (validated_mode != nullptr) { this->preset_mode_ = validated_mode; // Store pointer from traits } else { // Preset mode not found in traits - log warning and don't set - ESP_LOGW(TAG, "%s: Preset mode '%s' not supported", this->parent_.get_name().c_str(), preset_mode); + ESP_LOGW(TAG, "%s: Preset mode '%.*s' not supported", this->parent_.get_name().c_str(), (int) len, preset_mode); this->preset_mode_ = nullptr; } return *this; @@ -140,7 +146,13 @@ FanCall Fan::turn_off() { return this->make_call().set_state(false); } FanCall Fan::toggle() { return this->make_call().set_state(!this->state); } FanCall Fan::make_call() { return FanCall(*this); } -const char *Fan::find_preset_mode_(const char *preset_mode) { return this->get_traits().find_preset_mode(preset_mode); } +const char *Fan::find_preset_mode_(const char *preset_mode) { + return this->find_preset_mode_(preset_mode, preset_mode ? strlen(preset_mode) : 0); +} + +const char *Fan::find_preset_mode_(const char *preset_mode, size_t len) { + return this->get_traits().find_preset_mode(preset_mode, len); +} bool Fan::set_preset_mode_(const char *preset_mode) { if (preset_mode == nullptr) { diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index e38a80dbbe..70c4dab940 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -72,6 +72,7 @@ class FanCall { optional get_direction() const { return this->direction_; } FanCall &set_preset_mode(const std::string &preset_mode); FanCall &set_preset_mode(const char *preset_mode); + FanCall &set_preset_mode(const char *preset_mode, size_t len); const char *get_preset_mode() const { return this->preset_mode_; } bool has_preset_mode() const { return this->preset_mode_ != nullptr; } @@ -152,6 +153,7 @@ class Fan : public EntityBase { void clear_preset_mode_(); /// Find and return the matching preset mode pointer from traits, or nullptr if not found. const char *find_preset_mode_(const char *preset_mode); + const char *find_preset_mode_(const char *preset_mode, size_t len); CallbackManager state_callback_{}; ESPPreferenceObject rtc_; diff --git a/esphome/components/fan/fan_traits.h b/esphome/components/fan/fan_traits.h index 24987fe984..c0c5f34c50 100644 --- a/esphome/components/fan/fan_traits.h +++ b/esphome/components/fan/fan_traits.h @@ -47,10 +47,13 @@ class FanTraits { bool supports_preset_modes() const { return !this->preset_modes_.empty(); } /// Find and return the matching preset mode pointer from supported modes, or nullptr if not found. const char *find_preset_mode(const char *preset_mode) const { - if (preset_mode == nullptr) + return this->find_preset_mode(preset_mode, preset_mode ? strlen(preset_mode) : 0); + } + const char *find_preset_mode(const char *preset_mode, size_t len) const { + if (preset_mode == nullptr || len == 0) return nullptr; for (const char *mode : this->preset_modes_) { - if (strcmp(mode, preset_mode) == 0) { + if (strncmp(mode, preset_mode, len) == 0 && mode[len] == '\0') { return mode; // Return pointer from traits } } From 730bf206de6842aa4c65485801b03319957cbdf4 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 19 Dec 2025 20:25:16 -0600 Subject: [PATCH 0731/1145] [wifi] Fix for `wifi_info` when static IP is configured (#12576) --- esphome/components/wifi/wifi_component_esp8266.cpp | 10 ++++++++++ esphome/components/wifi/wifi_component_esp_idf.cpp | 8 ++++++++ esphome/components/wifi/wifi_component_libretiny.cpp | 8 ++++++++ esphome/components/wifi/wifi_component_pico_w.cpp | 9 +++++++++ 4 files changed, 35 insertions(+) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 1329103f98..550b5579ff 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -528,6 +528,16 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { for (auto *listener : global_wifi_component->connect_state_listeners_) { listener->on_wifi_connect_state(global_wifi_component->wifi_ssid(), global_wifi_component->wifi_bssid()); } + // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here +#ifdef USE_WIFI_MANUAL_IP + if (const WiFiAP *config = global_wifi_component->get_selected_sta_(); + config && config->get_manual_ip().has_value()) { + for (auto *listener : global_wifi_component->ip_state_listeners_) { + listener->on_ip_state(global_wifi_component->wifi_sta_ip_addresses(), + global_wifi_component->get_dns_address(0), global_wifi_component->get_dns_address(1)); + } + } +#endif #endif break; } diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index f9e117f468..212514af93 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -739,6 +739,14 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { for (auto *listener : this->connect_state_listeners_) { listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); } + // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here +#ifdef USE_WIFI_MANUAL_IP + if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } + } +#endif #endif } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) { diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index ffc6b21359..340537b228 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -305,6 +305,14 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ for (auto *listener : this->connect_state_listeners_) { listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); } + // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here +#ifdef USE_WIFI_MANUAL_IP + if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } + } +#endif #endif break; } diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 4e763a9e22..61709852ff 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -259,6 +259,15 @@ void WiFiComponent::wifi_loop_() { for (auto *listener : this->connect_state_listeners_) { listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); } + // For static IP configurations, notify IP listeners immediately as the IP is already configured +#ifdef USE_WIFI_MANUAL_IP + if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { + s_sta_had_ip = true; + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } + } +#endif #endif } else if (!is_connected && s_sta_was_connected) { // Just disconnected From be6c1e4ec00b1d53972a471109ba223667b430b2 Mon Sep 17 00:00:00 2001 From: Martin Ebner <185941678+mebner86@users.noreply.github.com> Date: Sat, 20 Dec 2025 07:59:02 +0530 Subject: [PATCH 0732/1145] [sen5x][sgp4x] Move configuration keys from SEN5x and SGP4x to const.py (#12567) Co-authored-by: Martin Ebner --- esphome/components/sen5x/sensor.py | 22 +++++++++++----------- esphome/components/sgp4x/sensor.py | 18 +++++++++--------- esphome/const.py | 11 +++++++++++ 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/esphome/components/sen5x/sensor.py b/esphome/components/sen5x/sensor.py index 9668a253c0..9c3114b9e2 100644 --- a/esphome/components/sen5x/sensor.py +++ b/esphome/components/sen5x/sensor.py @@ -4,17 +4,28 @@ import esphome.codegen as cg from esphome.components import i2c, sensirion_common, sensor import esphome.config_validation as cv from esphome.const import ( + CONF_ALGORITHM_TUNING, CONF_GAIN_FACTOR, + CONF_GATING_MAX_DURATION_MINUTES, CONF_HUMIDITY, CONF_ID, + CONF_INDEX_OFFSET, + CONF_LEARNING_TIME_GAIN_HOURS, + CONF_LEARNING_TIME_OFFSET_HOURS, + CONF_NORMALIZED_OFFSET_SLOPE, + CONF_NOX, CONF_OFFSET, CONF_PM_1_0, CONF_PM_2_5, CONF_PM_4_0, CONF_PM_10_0, + CONF_STD_INITIAL, CONF_STORE_BASELINE, CONF_TEMPERATURE, CONF_TEMPERATURE_COMPENSATION, + CONF_TIME_CONSTANT, + CONF_VOC, + CONF_VOC_BASELINE, DEVICE_CLASS_AQI, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PM1, @@ -42,18 +53,7 @@ SEN5XComponent = sen5x_ns.class_( RhtAccelerationMode = sen5x_ns.enum("RhtAccelerationMode") CONF_ACCELERATION_MODE = "acceleration_mode" -CONF_ALGORITHM_TUNING = "algorithm_tuning" CONF_AUTO_CLEANING_INTERVAL = "auto_cleaning_interval" -CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes" -CONF_INDEX_OFFSET = "index_offset" -CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours" -CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours" -CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope" -CONF_NOX = "nox" -CONF_STD_INITIAL = "std_initial" -CONF_TIME_CONSTANT = "time_constant" -CONF_VOC = "voc" -CONF_VOC_BASELINE = "voc_baseline" # Actions diff --git a/esphome/components/sgp4x/sensor.py b/esphome/components/sgp4x/sensor.py index 7c6fe580b2..ab78ab59d9 100644 --- a/esphome/components/sgp4x/sensor.py +++ b/esphome/components/sgp4x/sensor.py @@ -2,11 +2,20 @@ import esphome.codegen as cg from esphome.components import i2c, sensirion_common, sensor import esphome.config_validation as cv from esphome.const import ( + CONF_ALGORITHM_TUNING, CONF_COMPENSATION, CONF_GAIN_FACTOR, + CONF_GATING_MAX_DURATION_MINUTES, CONF_ID, + CONF_INDEX_OFFSET, + CONF_LEARNING_TIME_GAIN_HOURS, + CONF_LEARNING_TIME_OFFSET_HOURS, + CONF_NOX, + CONF_STD_INITIAL, CONF_STORE_BASELINE, CONF_TEMPERATURE_SOURCE, + CONF_VOC, + CONF_VOC_BASELINE, DEVICE_CLASS_AQI, ICON_RADIATOR, STATE_CLASS_MEASUREMENT, @@ -24,16 +33,7 @@ SGP4xComponent = sgp4x_ns.class_( sensirion_common.SensirionI2CDevice, ) -CONF_ALGORITHM_TUNING = "algorithm_tuning" -CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes" CONF_HUMIDITY_SOURCE = "humidity_source" -CONF_INDEX_OFFSET = "index_offset" -CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours" -CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours" -CONF_NOX = "nox" -CONF_STD_INITIAL = "std_initial" -CONF_VOC = "voc" -CONF_VOC_BASELINE = "voc_baseline" def validate_sensors(config): diff --git a/esphome/const.py b/esphome/const.py index c94ead0be4..075679d177 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -123,6 +123,7 @@ CONF_ADDRESS = "address" CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" CONF_ADVANCED = "advanced" CONF_AFTER = "after" +CONF_ALGORITHM_TUNING = "algorithm_tuning" CONF_ALL = "all" CONF_ALLOW_OTHER_USES = "allow_other_uses" CONF_ALPHA = "alpha" @@ -435,6 +436,7 @@ CONF_GAIN_FACTOR = "gain_factor" CONF_GAMMA_CORRECT = "gamma_correct" CONF_GAS_RESISTANCE = "gas_resistance" CONF_GATEWAY = "gateway" +CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes" CONF_GLASS_ATTENUATION_FACTOR = "glass_attenuation_factor" CONF_GLYPHS = "glyphs" CONF_GPIO = "gpio" @@ -497,6 +499,7 @@ CONF_INCLUDE_INTERNAL = "include_internal" CONF_INCLUDES = "includes" CONF_INCLUDES_C = "includes_c" CONF_INDEX = "index" +CONF_INDEX_OFFSET = "index_offset" CONF_INDOOR = "indoor" CONF_INFRARED = "infrared" CONF_INIT_SEQUENCE = "init_sequence" @@ -534,6 +537,8 @@ CONF_LAMBDA = "lambda" CONF_LAST_CONFIDENCE = "last_confidence" CONF_LAST_FINGER_ID = "last_finger_id" CONF_LATITUDE = "latitude" +CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours" +CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours" CONF_LED = "led" CONF_LEGEND = "legend" CONF_LENGTH = "length" @@ -645,7 +650,9 @@ CONF_NEVER = "never" CONF_NEW_PASSWORD = "new_password" CONF_NITROGEN_DIOXIDE = "nitrogen_dioxide" CONF_NOISE_LEVEL = "noise_level" +CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope" CONF_NOTIFY = "notify" +CONF_NOX = "nox" CONF_NUM_ATTEMPTS = "num_attempts" CONF_NUM_CHANNELS = "num_channels" CONF_NUM_CHIPS = "num_chips" @@ -939,6 +946,7 @@ CONF_STATE_TOPIC = "state_topic" CONF_STATIC_IP = "static_ip" CONF_STATUS = "status" CONF_STB_PIN = "stb_pin" +CONF_STD_INITIAL = "std_initial" CONF_STEP = "step" CONF_STEP_DELAY = "step_delay" CONF_STEP_MODE = "step_mode" @@ -1006,6 +1014,7 @@ CONF_TILT_COMMAND_TOPIC = "tilt_command_topic" CONF_TILT_LAMBDA = "tilt_lambda" CONF_TILT_STATE_TOPIC = "tilt_state_topic" CONF_TIME = "time" +CONF_TIME_CONSTANT = "time_constant" CONF_TIME_ID = "time_id" CONF_TIMEOUT = "timeout" CONF_TIMES = "times" @@ -1060,6 +1069,8 @@ CONF_VERSION = "version" CONF_VIBRATIONS = "vibrations" CONF_VISIBLE = "visible" CONF_VISUAL = "visual" +CONF_VOC = "voc" +CONF_VOC_BASELINE = "voc_baseline" CONF_VOLTAGE = "voltage" CONF_VOLTAGE_ATTENUATION = "voltage_attenuation" CONF_VOLTAGE_DIVIDER = "voltage_divider" From 3e313014e12c94b68f70974b205242fb2af6ddef Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Dec 2025 19:04:21 -1000 Subject: [PATCH 0733/1145] [core] Migrate entities to use lazy callbacks (#12580) --- .../alarm_control_panel/alarm_control_panel.h | 8 ++-- esphome/components/button/button.h | 2 +- esphome/components/climate/climate.h | 4 +- esphome/components/cover/cover.h | 2 +- esphome/components/datetime/datetime_base.h | 2 +- esphome/components/event/event.h | 2 +- esphome/components/fan/fan.h | 2 +- esphome/components/lock/lock.h | 2 +- .../components/media_player/media_player.h | 2 +- esphome/components/number/number.h | 2 +- esphome/components/select/select.h | 2 +- esphome/components/sensor/sensor.cpp | 9 +--- esphome/components/sensor/sensor.h | 4 +- esphome/components/switch/switch.h | 4 +- esphome/components/text/text.h | 2 +- .../components/text_sensor/text_sensor.cpp | 9 +--- esphome/components/text_sensor/text_sensor.h | 5 +-- esphome/components/update/update_entity.h | 2 +- esphome/components/valve/valve.h | 2 +- esphome/core/helpers.h | 44 +++++++++++++++++++ 20 files changed, 72 insertions(+), 39 deletions(-) diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.h b/esphome/components/alarm_control_panel/alarm_control_panel.h index c46edc11c2..59ccf0e484 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel.h @@ -132,13 +132,13 @@ class AlarmControlPanel : public EntityBase { // the call control function virtual void control(const AlarmControlPanelCall &call) = 0; // state callback - triggers check get_state() for specific state - CallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; // clear callback - fires when leaving TRIGGERED state - CallbackManager cleared_callback_{}; + LazyCallbackManager cleared_callback_{}; // chime callback - CallbackManager chime_callback_{}; + LazyCallbackManager chime_callback_{}; // ready callback - CallbackManager ready_callback_{}; + LazyCallbackManager ready_callback_{}; }; } // namespace alarm_control_panel diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index 18122f6f2f..be6e080917 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -41,7 +41,7 @@ class Button : public EntityBase, public EntityBase_DeviceClass { */ virtual void press_action() = 0; - CallbackManager press_callback_{}; + LazyCallbackManager press_callback_{}; }; } // namespace esphome::button diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 0bae28df5a..06adb580cf 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -326,8 +326,8 @@ class Climate : public EntityBase { void dump_traits_(const char *tag); - CallbackManager state_callback_{}; - CallbackManager control_callback_{}; + LazyCallbackManager state_callback_{}; + LazyCallbackManager control_callback_{}; ESPPreferenceObject rtc_; #ifdef USE_CLIMATE_VISUAL_OVERRIDES float visual_min_temperature_override_{NAN}; diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index d8c45ab2bd..e710915a0e 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -152,7 +152,7 @@ class Cover : public EntityBase, public EntityBase_DeviceClass { optional restore_state_(); - CallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; ESPPreferenceObject rtc_; }; diff --git a/esphome/components/datetime/datetime_base.h b/esphome/components/datetime/datetime_base.h index 7b9b281ea4..1b0b3d5463 100644 --- a/esphome/components/datetime/datetime_base.h +++ b/esphome/components/datetime/datetime_base.h @@ -22,7 +22,7 @@ class DateTimeBase : public EntityBase { #endif protected: - CallbackManager state_callback_; + LazyCallbackManager state_callback_; #ifdef USE_TIME time::RealTimeClock *rtc_; diff --git a/esphome/components/event/event.h b/esphome/components/event/event.h index e4b2e0b845..0d5850d339 100644 --- a/esphome/components/event/event.h +++ b/esphome/components/event/event.h @@ -50,7 +50,7 @@ class Event : public EntityBase, public EntityBase_DeviceClass { void add_on_event_callback(std::function &&callback); protected: - CallbackManager event_callback_; + LazyCallbackManager event_callback_; FixedVector types_; private: diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index 70c4dab940..7c79fda83e 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -155,7 +155,7 @@ class Fan : public EntityBase { const char *find_preset_mode_(const char *preset_mode); const char *find_preset_mode_(const char *preset_mode, size_t len); - CallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; ESPPreferenceObject rtc_; FanRestoreMode restore_mode_; diff --git a/esphome/components/lock/lock.h b/esphome/components/lock/lock.h index 4001a182b8..f77b11b145 100644 --- a/esphome/components/lock/lock.h +++ b/esphome/components/lock/lock.h @@ -174,7 +174,7 @@ class Lock : public EntityBase { */ virtual void control(const LockCall &call) = 0; - CallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; Deduplicator publish_dedup_; ESPPreferenceObject rtc_; }; diff --git a/esphome/components/media_player/media_player.h b/esphome/components/media_player/media_player.h index 2f1c99115f..b753e2d088 100644 --- a/esphome/components/media_player/media_player.h +++ b/esphome/components/media_player/media_player.h @@ -157,7 +157,7 @@ class MediaPlayer : public EntityBase { virtual void control(const MediaPlayerCall &call) = 0; - CallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; }; } // namespace media_player diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 472e06ad61..0425714702 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -49,7 +49,7 @@ class Number : public EntityBase { */ virtual void control(float value) = 0; - CallbackManager state_callback_; + LazyCallbackManager state_callback_; }; } // namespace esphome::number diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index 854fdcf252..330d18ce6f 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -111,7 +111,7 @@ class Select : public EntityBase { } } - CallbackManager state_callback_; + LazyCallbackManager state_callback_; }; } // namespace esphome::select diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 49dc56edaa..c1d28bf260 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -76,9 +76,7 @@ StateClass Sensor::get_state_class() { void Sensor::publish_state(float state) { this->raw_state = state; - if (this->raw_callback_) { - this->raw_callback_->call(state); - } + this->raw_callback_.call(state); ESP_LOGV(TAG, "'%s': Received new state %f", this->name_.c_str(), state); @@ -91,10 +89,7 @@ void Sensor::publish_state(float state) { void Sensor::add_on_state_callback(std::function &&callback) { this->callback_.add(std::move(callback)); } void Sensor::add_on_raw_state_callback(std::function &&callback) { - if (!this->raw_callback_) { - this->raw_callback_ = make_unique>(); - } - this->raw_callback_->add(std::move(callback)); + this->raw_callback_.add(std::move(callback)); } void Sensor::add_filter(Filter *filter) { diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 5d387a1ad7..a792c0d3fd 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -125,8 +125,8 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa void internal_send_state_to_frontend(float state); protected: - std::unique_ptr> raw_callback_; ///< Storage for raw state callbacks (lazy allocated). - CallbackManager callback_; ///< Storage for filtered state callbacks. + LazyCallbackManager raw_callback_; ///< Storage for raw state callbacks. + LazyCallbackManager callback_; ///< Storage for filtered state callbacks. Filter *filter_list_{nullptr}; ///< Store all active filters. diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index 6371e35292..9319adf9ed 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -134,8 +134,8 @@ class Switch : public EntityBase, public EntityBase_DeviceClass { // Pointer first (4 bytes) ESPPreferenceObject rtc_; - // CallbackManager (12 bytes on 32-bit - contains vector) - CallbackManager state_callback_{}; + // LazyCallbackManager (4 bytes on 32-bit - nullptr when empty) + LazyCallbackManager state_callback_{}; // Small types grouped together Deduplicator publish_dedup_; // 2 bytes (bool has_value_ + bool last_value_) diff --git a/esphome/components/text/text.h b/esphome/components/text/text.h index f24464cb20..b8881c59e6 100644 --- a/esphome/components/text/text.h +++ b/esphome/components/text/text.h @@ -44,7 +44,7 @@ class Text : public EntityBase { */ virtual void control(const std::string &value) = 0; - CallbackManager state_callback_; + LazyCallbackManager state_callback_; }; } // namespace text diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 51923ebd96..76c1acf56c 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -30,9 +30,7 @@ void TextSensor::publish_state(const std::string &state) { #pragma GCC diagnostic ignored "-Wdeprecated-declarations" this->raw_state = state; #pragma GCC diagnostic pop - if (this->raw_callback_) { - this->raw_callback_->call(state); - } + this->raw_callback_.call(state); ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str()); @@ -77,10 +75,7 @@ void TextSensor::add_on_state_callback(std::function callback this->callback_.add(std::move(callback)); } void TextSensor::add_on_raw_state_callback(std::function callback) { - if (!this->raw_callback_) { - this->raw_callback_ = make_unique>(); - } - this->raw_callback_->add(std::move(callback)); + this->raw_callback_.add(std::move(callback)); } std::string TextSensor::get_state() const { return this->state; } diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index e411f57d67..f926f171a7 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -65,9 +65,8 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { void internal_send_state_to_frontend(const std::string &state); protected: - std::unique_ptr> - raw_callback_; ///< Storage for raw state callbacks (lazy allocated). - CallbackManager callback_; ///< Storage for filtered state callbacks. + LazyCallbackManager raw_callback_; ///< Storage for raw state callbacks. + LazyCallbackManager callback_; ///< Storage for filtered state callbacks. Filter *filter_list_{nullptr}; ///< Store all active filters. }; diff --git a/esphome/components/update/update_entity.h b/esphome/components/update/update_entity.h index 9424e80b9f..8eba78b44b 100644 --- a/esphome/components/update/update_entity.h +++ b/esphome/components/update/update_entity.h @@ -50,7 +50,7 @@ class UpdateEntity : public EntityBase, public EntityBase_DeviceClass { UpdateState state_{UPDATE_STATE_UNKNOWN}; UpdateInfo update_info_; - CallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; std::unique_ptr> update_available_trigger_{nullptr}; }; diff --git a/esphome/components/valve/valve.h b/esphome/components/valve/valve.h index 2cb28e4b2f..2b3419b67a 100644 --- a/esphome/components/valve/valve.h +++ b/esphome/components/valve/valve.h @@ -144,7 +144,7 @@ class Valve : public EntityBase, public EntityBase_DeviceClass { optional restore_state_(); - CallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; ESPPreferenceObject rtc_; }; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index f9dcfccb45..9ff2458a74 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -934,6 +934,50 @@ template class CallbackManager { std::vector> callbacks_; }; +template class LazyCallbackManager; + +/** Lazy-allocating callback manager that only allocates memory when callbacks are registered. + * + * This is a drop-in replacement for CallbackManager that saves memory when no callbacks + * are registered (common case after the Controller Registry eliminated per-entity callbacks + * from API and web_server components). + * + * Memory overhead comparison (32-bit systems): + * - CallbackManager: 12 bytes (empty std::vector) + * - LazyCallbackManager: 4 bytes (nullptr unique_ptr) + * + * @tparam Ts The arguments for the callbacks, wrapped in void(). + */ +template class LazyCallbackManager { + public: + /// Add a callback to the list. Allocates the underlying CallbackManager on first use. + void add(std::function &&callback) { + if (!this->callbacks_) { + this->callbacks_ = make_unique>(); + } + this->callbacks_->add(std::move(callback)); + } + + /// Call all callbacks in this manager. No-op if no callbacks registered. + void call(Ts... args) { + if (this->callbacks_) { + this->callbacks_->call(args...); + } + } + + /// Return the number of registered callbacks. + size_t size() const { return this->callbacks_ ? this->callbacks_->size() : 0; } + + /// Check if any callbacks are registered. + bool empty() const { return !this->callbacks_ || this->callbacks_->size() == 0; } + + /// Call all callbacks in this manager. + void operator()(Ts... args) { this->call(args...); } + + protected: + std::unique_ptr> callbacks_; +}; + /// Helper class to deduplicate items in a series of values. template class Deduplicator { public: From 48cdf9e036d402d84518ee0256f7db24306e6b3e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 20 Dec 2025 05:47:29 -1000 Subject: [PATCH 0734/1145] [tests] Fix race condition in alarm control panel state transitions test (#12581) --- ...t_alarm_control_panel_state_transitions.py | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/tests/integration/test_alarm_control_panel_state_transitions.py b/tests/integration/test_alarm_control_panel_state_transitions.py index 2977ff56c2..09348f5bea 100644 --- a/tests/integration/test_alarm_control_panel_state_transitions.py +++ b/tests/integration/test_alarm_control_panel_state_transitions.py @@ -279,14 +279,30 @@ async def test_alarm_control_panel_state_transitions( except TimeoutError: pytest.fail(f"on_chime callback not fired. Log lines: {log_lines[-20:]}") - # Close the chime sensor + # Close the chime sensor and wait for alarm to become ready again + # We need to wait for this transition before testing door sensor, + # otherwise there's a race where the door sensor state change could + # arrive before the chime sensor state change, leaving the alarm in + # a continuous "not ready" state with no on_ready callback fired. + ready_after_chime_close: asyncio.Future[bool] = loop.create_future() + ready_futures.append(ready_after_chime_close) + client.switch_command(chime_switch_info.key, False) - # ===== Test ready state changes ===== - # Opening/closing sensors while disarmed affects ready state - # The on_ready callback fires when sensors_ready changes + # Wait for alarm to become ready again (chime sensor closed) + try: + await asyncio.wait_for(ready_after_chime_close, timeout=2.0) + except TimeoutError: + pytest.fail( + f"on_ready callback not fired when chime sensor closed. " + f"Log lines: {log_lines[-20:]}" + ) - # Set up futures for ready state changes + # ===== Test ready state changes ===== + # Now the alarm is confirmed ready. Opening/closing door sensor + # should trigger on_ready callbacks. + + # Set up futures for door sensor state changes ready_future_1: asyncio.Future[bool] = loop.create_future() ready_future_2: asyncio.Future[bool] = loop.create_future() ready_futures.extend([ready_future_1, ready_future_2]) From 121375ff392260487ed9dfcc2872ffccb15f6da3 Mon Sep 17 00:00:00 2001 From: Eduard Llull Date: Sat, 20 Dec 2025 16:59:14 +0100 Subject: [PATCH 0735/1145] [display_menu_base] Call on_value_ after updating the select (#12584) --- esphome/components/display_menu_base/menu_item.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/display_menu_base/menu_item.cpp b/esphome/components/display_menu_base/menu_item.cpp index 8224adf3fe..08f758045e 100644 --- a/esphome/components/display_menu_base/menu_item.cpp +++ b/esphome/components/display_menu_base/menu_item.cpp @@ -54,6 +54,7 @@ bool MenuItemSelect::select_next() { if (this->select_var_ != nullptr) { this->select_var_->make_call().select_next(true).perform(); + this->on_value_(); changed = true; } @@ -65,6 +66,7 @@ bool MenuItemSelect::select_prev() { if (this->select_var_ != nullptr) { this->select_var_->make_call().select_previous(true).perform(); + this->on_value_(); changed = true; } From 64269334ce9e1c5ae70268f679c550c7d62686de Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 20 Dec 2025 06:46:13 -1000 Subject: [PATCH 0736/1145] [text_sensor] Avoid string copies in callbacks by passing const ref (#12503) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/text_sensor/text_sensor.cpp | 4 ++-- esphome/components/text_sensor/text_sensor.h | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 76c1acf56c..ad1dc0f521 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -71,10 +71,10 @@ void TextSensor::clear_filters() { this->filter_list_ = nullptr; } -void TextSensor::add_on_state_callback(std::function callback) { +void TextSensor::add_on_state_callback(std::function callback) { this->callback_.add(std::move(callback)); } -void TextSensor::add_on_raw_state_callback(std::function callback) { +void TextSensor::add_on_raw_state_callback(std::function callback) { this->raw_callback_.add(std::move(callback)); } diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index f926f171a7..919bf81c8c 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -55,9 +55,9 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { /// Clear the entire filter chain. void clear_filters(); - void add_on_state_callback(std::function callback); + void add_on_state_callback(std::function callback); /// Add a callback that will be called every time the sensor sends a raw value. - void add_on_raw_state_callback(std::function callback); + void add_on_raw_state_callback(std::function callback); // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) @@ -65,8 +65,8 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { void internal_send_state_to_frontend(const std::string &state); protected: - LazyCallbackManager raw_callback_; ///< Storage for raw state callbacks. - LazyCallbackManager callback_; ///< Storage for filtered state callbacks. + LazyCallbackManager raw_callback_; ///< Storage for raw state callbacks. + LazyCallbackManager callback_; ///< Storage for filtered state callbacks. Filter *filter_list_{nullptr}; ///< Store all active filters. }; From 40eb898814aec3fe077bf0cfc0b2101934970cf6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 20 Dec 2025 06:47:30 -1000 Subject: [PATCH 0737/1145] [api] Add zero-copy support for noise encryption key requests (#12405) --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_connection.cpp | 4 ++-- esphome/components/api/api_pb2.cpp | 7 +++++-- esphome/components/api/api_pb2.h | 5 +++-- esphome/components/api/api_pb2_dump.cpp | 2 +- esphome/core/helpers.cpp | 12 ++++++++---- esphome/core/helpers.h | 1 + 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index e8c900df26..5d44d7e549 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -747,7 +747,7 @@ message NoiseEncryptionSetKeyRequest { option (source) = SOURCE_CLIENT; option (ifdef) = "USE_API_NOISE"; - bytes key = 1; + bytes key = 1 [(pointer_to_buffer) = true]; } message NoiseEncryptionSetKeyResponse { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 126d3cb220..0f551d1bc3 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1666,13 +1666,13 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption resp.success = false; psk_t psk{}; - if (msg.key.empty()) { + if (msg.key_len == 0) { if (this->parent_->clear_noise_psk(true)) { resp.success = true; } else { ESP_LOGW(TAG, "Failed to clear encryption key"); } - } else if (base64_decode(msg.key, psk.data(), psk.size()) != psk.size()) { + } else if (base64_decode(msg.key, msg.key_len, psk.data(), psk.size()) != psk.size()) { ESP_LOGW(TAG, "Invalid encryption key length"); } else if (!this->parent_->save_noise_psk(psk, true)) { ESP_LOGW(TAG, "Failed to save encryption key"); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 8bba13a4de..8b84f9651f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -858,9 +858,12 @@ void SubscribeLogsResponse::calculate_size(ProtoSize &size) const { #ifdef USE_API_NOISE bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->key = value.as_string(); + case 1: { + // Use raw data directly to avoid allocation + this->key = value.data(); + this->key_len = value.size(); break; + } default: return false; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index d3b91ac56b..668c0af461 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1054,11 +1054,12 @@ class SubscribeLogsResponse final : public ProtoMessage { class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 124; - static constexpr uint8_t ESTIMATED_SIZE = 9; + static constexpr uint8_t ESTIMATED_SIZE = 19; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "noise_encryption_set_key_request"; } #endif - std::string key{}; + const uint8_t *key{nullptr}; + uint16_t key_len{0}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index d733e66a6d..38c3b473e6 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1115,7 +1115,7 @@ void SubscribeLogsResponse::dump_to(std::string &out) const { void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "NoiseEncryptionSetKeyRequest"); out.append(" key: "); - out.append(format_hex_pretty(reinterpret_cast(this->key.data()), this->key.size())); + out.append(format_hex_pretty(this->key, this->key_len)); out.append("\n"); } void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); } diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index bbe59e53f1..156f41a2dc 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -479,10 +479,14 @@ std::string base64_encode(const uint8_t *buf, size_t buf_len) { } size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf_len) { - int in_len = encoded_string.size(); + return base64_decode(reinterpret_cast(encoded_string.data()), encoded_string.size(), buf, buf_len); +} + +size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *buf, size_t buf_len) { + size_t in_len = encoded_len; int i = 0; int j = 0; - int in = 0; + size_t in = 0; size_t out = 0; uint8_t char_array_4[4], char_array_3[3]; bool truncated = false; @@ -490,8 +494,8 @@ size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf // SAFETY: The loop condition checks is_base64() before processing each character. // This ensures base64_find_char() is only called on valid base64 characters, // preventing the edge case where invalid chars would return 0 (same as 'A'). - while (in_len-- && (encoded_string[in] != '=') && is_base64(encoded_string[in])) { - char_array_4[i++] = encoded_string[in]; + while (in_len-- && (encoded_data[in] != '=') && is_base64(encoded_data[in])) { + char_array_4[i++] = encoded_data[in]; in++; if (i == 4) { for (i = 0; i < 4; i++) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 9ff2458a74..6028c93ce2 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -878,6 +878,7 @@ std::string base64_encode(const std::vector &buf); std::vector base64_decode(const std::string &encoded_string); size_t base64_decode(std::string const &encoded_string, uint8_t *buf, size_t buf_len); +size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *buf, size_t buf_len); ///@} From 6f3bfc20600804205c3b79cd59a9b5ca6607bb2d Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Sat, 20 Dec 2025 10:18:20 -0800 Subject: [PATCH 0738/1145] [hub75] Bump esp-hub75 version to 0.1.7 (#12564) --- .clang-tidy.hash | 2 +- esphome/components/hub75/display.py | 46 ++++++++++++++--------------- esphome/idf_component.yml | 4 +++ platformio.ini | 2 -- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 13c7ce5f97..240b205158 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -6857423aecf90accd0a8bf584d36ee094a4938f872447a4efc05a2efc6dc6481 +4268ab0b5150f79ab1c317e8f3834c8bb0b4c8122da4f6b1fd67c49d0f2098c9 diff --git a/esphome/components/hub75/display.py b/esphome/components/hub75/display.py index f401f35406..7736319330 100644 --- a/esphome/components/hub75/display.py +++ b/esphome/components/hub75/display.py @@ -95,35 +95,35 @@ CONF_DOUBLE_BUFFER = "double_buffer" CONF_MIN_REFRESH_RATE = "min_refresh_rate" # Map to hub75 library enums (in global namespace) -ShiftDriver = cg.global_ns.enum("ShiftDriver", is_class=True) +Hub75ShiftDriver = cg.global_ns.enum("Hub75ShiftDriver", is_class=True) SHIFT_DRIVERS = { - "GENERIC": ShiftDriver.GENERIC, - "FM6126A": ShiftDriver.FM6126A, - "ICN2038S": ShiftDriver.ICN2038S, - "FM6124": ShiftDriver.FM6124, - "MBI5124": ShiftDriver.MBI5124, - "DP3246": ShiftDriver.DP3246, + "GENERIC": Hub75ShiftDriver.GENERIC, + "FM6126A": Hub75ShiftDriver.FM6126A, + "ICN2038S": Hub75ShiftDriver.ICN2038S, + "FM6124": Hub75ShiftDriver.FM6124, + "MBI5124": Hub75ShiftDriver.MBI5124, + "DP3246": Hub75ShiftDriver.DP3246, } -PanelLayout = cg.global_ns.enum("PanelLayout", is_class=True) +Hub75PanelLayout = cg.global_ns.enum("Hub75PanelLayout", is_class=True) PANEL_LAYOUTS = { - "HORIZONTAL": PanelLayout.HORIZONTAL, - "TOP_LEFT_DOWN": PanelLayout.TOP_LEFT_DOWN, - "TOP_RIGHT_DOWN": PanelLayout.TOP_RIGHT_DOWN, - "BOTTOM_LEFT_UP": PanelLayout.BOTTOM_LEFT_UP, - "BOTTOM_RIGHT_UP": PanelLayout.BOTTOM_RIGHT_UP, - "TOP_LEFT_DOWN_ZIGZAG": PanelLayout.TOP_LEFT_DOWN_ZIGZAG, - "TOP_RIGHT_DOWN_ZIGZAG": PanelLayout.TOP_RIGHT_DOWN_ZIGZAG, - "BOTTOM_LEFT_UP_ZIGZAG": PanelLayout.BOTTOM_LEFT_UP_ZIGZAG, - "BOTTOM_RIGHT_UP_ZIGZAG": PanelLayout.BOTTOM_RIGHT_UP_ZIGZAG, + "HORIZONTAL": Hub75PanelLayout.HORIZONTAL, + "TOP_LEFT_DOWN": Hub75PanelLayout.TOP_LEFT_DOWN, + "TOP_RIGHT_DOWN": Hub75PanelLayout.TOP_RIGHT_DOWN, + "BOTTOM_LEFT_UP": Hub75PanelLayout.BOTTOM_LEFT_UP, + "BOTTOM_RIGHT_UP": Hub75PanelLayout.BOTTOM_RIGHT_UP, + "TOP_LEFT_DOWN_ZIGZAG": Hub75PanelLayout.TOP_LEFT_DOWN_ZIGZAG, + "TOP_RIGHT_DOWN_ZIGZAG": Hub75PanelLayout.TOP_RIGHT_DOWN_ZIGZAG, + "BOTTOM_LEFT_UP_ZIGZAG": Hub75PanelLayout.BOTTOM_LEFT_UP_ZIGZAG, + "BOTTOM_RIGHT_UP_ZIGZAG": Hub75PanelLayout.BOTTOM_RIGHT_UP_ZIGZAG, } -ScanPattern = cg.global_ns.enum("ScanPattern", is_class=True) +Hub75ScanWiring = cg.global_ns.enum("Hub75ScanWiring", is_class=True) SCAN_PATTERNS = { - "STANDARD_TWO_SCAN": ScanPattern.STANDARD_TWO_SCAN, - "FOUR_SCAN_16PX_HIGH": ScanPattern.FOUR_SCAN_16PX_HIGH, - "FOUR_SCAN_32PX_HIGH": ScanPattern.FOUR_SCAN_32PX_HIGH, - "FOUR_SCAN_64PX_HIGH": ScanPattern.FOUR_SCAN_64PX_HIGH, + "STANDARD_TWO_SCAN": Hub75ScanWiring.STANDARD_TWO_SCAN, + "FOUR_SCAN_16PX_HIGH": Hub75ScanWiring.FOUR_SCAN_16PX_HIGH, + "FOUR_SCAN_32PX_HIGH": Hub75ScanWiring.FOUR_SCAN_32PX_HIGH, + "FOUR_SCAN_64PX_HIGH": Hub75ScanWiring.FOUR_SCAN_64PX_HIGH, } Hub75ClockSpeed = cg.global_ns.enum("Hub75ClockSpeed", is_class=True) @@ -531,7 +531,7 @@ def _build_config_struct( async def to_code(config: ConfigType) -> None: add_idf_component( name="esphome/esp-hub75", - ref="0.1.6", + ref="0.1.7", ) # Set compile-time configuration via defines diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 9bb5967248..4573391bc1 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -27,3 +27,7 @@ dependencies: version: "1.7.6~1" rules: - if: "target in [esp32s2, esp32s3, esp32p4]" + esphome/esp-hub75: + version: 0.1.7 + rules: + - if: "target in [esp32, esp32s2, esp32s3, esp32p4]" diff --git a/platformio.ini b/platformio.ini index d37c798c05..a27fb1f537 100644 --- a/platformio.ini +++ b/platformio.ini @@ -156,7 +156,6 @@ lib_deps = esphome/ESP32-audioI2S@2.3.0 ; i2s_audio droscy/esp_wireguard@0.4.2 ; wireguard esphome/esp-audio-libs@2.0.1 ; audio - esphome/esp-hub75@0.1.6 ; hub75 build_flags = ${common:arduino.build_flags} @@ -180,7 +179,6 @@ lib_deps = droscy/esp_wireguard@0.4.2 ; wireguard kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word esphome/esp-audio-libs@2.0.1 ; audio - esphome/esp-hub75@0.1.6 ; hub75 build_flags = ${common:idf.build_flags} -Wno-nonnull-compare From 6c2d255230d6bb8a139b21203742c7139c2dad9b Mon Sep 17 00:00:00 2001 From: Leo Bergolth Date: Sat, 20 Dec 2025 21:04:59 +0100 Subject: [PATCH 0739/1145] send NIL ("-") as timestamp if time source is not valid (#12588) --- esphome/components/syslog/esphome_syslog.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/syslog/esphome_syslog.cpp b/esphome/components/syslog/esphome_syslog.cpp index f5c20c891e..851fb30c22 100644 --- a/esphome/components/syslog/esphome_syslog.cpp +++ b/esphome/components/syslog/esphome_syslog.cpp @@ -34,7 +34,15 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t severity = LOG_LEVEL_TO_SYSLOG_SEVERITY[level]; } int pri = this->facility_ * 8 + severity; - auto timestamp = this->time_->now().strftime("%b %e %H:%M:%S"); + auto now = this->time_->now(); + std::string timestamp; + if (now.is_valid()) { + timestamp = now.strftime("%b %e %H:%M:%S"); + } else { + // RFC 5424: A syslog application MUST use the NILVALUE as TIMESTAMP if the syslog application is incapable of + // obtaining system time. + timestamp = "-"; + } size_t len = message_len; // remove color formatting if (this->strip_ && message[0] == 0x1B && len > 11) { From 644e806afd7e20ed211fd6c0f4199d72f7d322d9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 20 Dec 2025 10:40:43 -1000 Subject: [PATCH 0740/1145] [zwave_proxy] Add missing USE_API guards for clang-tidy (#12590) --- esphome/components/zwave_proxy/zwave_proxy.cpp | 5 +++++ esphome/components/zwave_proxy/zwave_proxy.h | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp index e0ca5529b8..bd3f85772b 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.cpp +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -1,4 +1,7 @@ #include "zwave_proxy.h" + +#ifdef USE_API + #include "esphome/components/api/api_server.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" @@ -344,3 +347,5 @@ bool ZWaveProxy::response_handler_() { ZWaveProxy *global_zwave_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esphome::zwave_proxy + +#endif // USE_API diff --git a/esphome/components/zwave_proxy/zwave_proxy.h b/esphome/components/zwave_proxy/zwave_proxy.h index e23e202bea..137a1206e3 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.h +++ b/esphome/components/zwave_proxy/zwave_proxy.h @@ -1,5 +1,8 @@ #pragma once +#include "esphome/core/defines.h" +#ifdef USE_API + #include "esphome/components/api/api_connection.h" #include "esphome/components/api/api_pb2.h" #include "esphome/core/component.h" @@ -89,3 +92,5 @@ class ZWaveProxy : public uart::UARTDevice, public Component { extern ZWaveProxy *global_zwave_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esphome::zwave_proxy + +#endif // USE_API From bf554a58ef20e53a3dcccee127b9093476141860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Metrich?= <45318189+FredM67@users.noreply.github.com> Date: Sat, 20 Dec 2025 23:17:09 +0100 Subject: [PATCH 0741/1145] [const] Add CONF_ON_DATA and consolidate definitions (#12595) Co-authored-by: Claude Opus 4.5 --- esphome/components/microphone/__init__.py | 3 +-- esphome/components/sml/__init__.py | 3 +-- esphome/const.py | 1 + 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/components/microphone/__init__.py b/esphome/components/microphone/__init__.py index 1fc0df88a3..ce31484413 100644 --- a/esphome/components/microphone/__init__.py +++ b/esphome/components/microphone/__init__.py @@ -9,6 +9,7 @@ from esphome.const import ( CONF_GAIN_FACTOR, CONF_ID, CONF_MICROPHONE, + CONF_ON_DATA, CONF_TRIGGER_ID, ) from esphome.core import CORE @@ -19,8 +20,6 @@ CODEOWNERS = ["@jesserockz", "@kahrendt"] IS_PLATFORM_COMPONENT = True -CONF_ON_DATA = "on_data" - microphone_ns = cg.esphome_ns.namespace("microphone") Microphone = microphone_ns.class_("Microphone") diff --git a/esphome/components/sml/__init__.py b/esphome/components/sml/__init__.py index 936efd8561..eaeddce390 100644 --- a/esphome/components/sml/__init__.py +++ b/esphome/components/sml/__init__.py @@ -4,7 +4,7 @@ from esphome import automation import esphome.codegen as cg from esphome.components import uart import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_TRIGGER_ID +from esphome.const import CONF_ID, CONF_ON_DATA, CONF_TRIGGER_ID CODEOWNERS = ["@alengwenus"] @@ -17,7 +17,6 @@ MULTI_CONF = True CONF_SML_ID = "sml_id" CONF_OBIS_CODE = "obis_code" CONF_SERVER_ID = "server_id" -CONF_ON_DATA = "on_data" sml_ns = cg.esphome_ns.namespace("sml") diff --git a/esphome/const.py b/esphome/const.py index 075679d177..f43204fd9f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -679,6 +679,7 @@ CONF_ON_CLIENT_CONNECTED = "on_client_connected" CONF_ON_CLIENT_DISCONNECTED = "on_client_disconnected" CONF_ON_CONNECT = "on_connect" CONF_ON_CONTROL = "on_control" +CONF_ON_DATA = "on_data" CONF_ON_DIRECTION_SET = "on_direction_set" CONF_ON_DISCONNECT = "on_disconnect" CONF_ON_DOUBLE_CLICK = "on_double_click" From f1362cd9fe4320593e6845b309766ee4c62aeab7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Dec 2025 22:37:10 +0000 Subject: [PATCH 0742/1145] Bump aioesphomeapi from 43.3.0 to 43.4.0 (#12597) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5c3d82e219..a2e24cc7a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.3.0 +aioesphomeapi==43.4.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.17 # dashboard_import From e89fe9b9456430dd6ef1c517fc9ee43be897fcc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Dec 2025 23:59:48 +0000 Subject: [PATCH 0743/1145] Bump aioesphomeapi from 43.4.0 to 43.5.0 (#12599) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a2e24cc7a3..5718ced617 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.4.0 +aioesphomeapi==43.5.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.17 # dashboard_import From 2113858f8955ab4464d3bb271477f9ae595aa9b6 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 21 Dec 2025 02:45:24 -0600 Subject: [PATCH 0744/1145] [sprinkler] Squash a few bugs + minor optimization (#12436) --- esphome/components/sprinkler/automation.h | 6 +- esphome/components/sprinkler/sprinkler.cpp | 113 +++++++++++++-------- esphome/components/sprinkler/sprinkler.h | 16 +-- 3 files changed, 83 insertions(+), 52 deletions(-) diff --git a/esphome/components/sprinkler/automation.h b/esphome/components/sprinkler/automation.h index d6c877ae90..b3f030805d 100644 --- a/esphome/components/sprinkler/automation.h +++ b/esphome/components/sprinkler/automation.h @@ -4,8 +4,7 @@ #include "esphome/core/component.h" #include "esphome/components/sprinkler/sprinkler.h" -namespace esphome { -namespace sprinkler { +namespace esphome::sprinkler { template class SetDividerAction : public Action { public: @@ -181,5 +180,4 @@ template class ResumeOrStartAction : public Action { Sprinkler *sprinkler_; }; -} // namespace sprinkler -} // namespace esphome +} // namespace esphome::sprinkler diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index 8edb240a41..69452f2e9e 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -7,8 +7,7 @@ #include #include -namespace esphome { -namespace sprinkler { +namespace esphome::sprinkler { static const char *const TAG = "sprinkler"; @@ -411,7 +410,8 @@ void Sprinkler::loop() { for (auto &vo : this->valve_op_) { vo.loop(); } - if (this->prev_req_.has_request() && this->prev_req_.valve_operator()->state() == IDLE) { + if (this->prev_req_.has_request() && this->prev_req_.has_valve_operator() && + this->prev_req_.valve_operator()->state() == IDLE) { this->prev_req_.reset(); } } @@ -808,11 +808,11 @@ bool Sprinkler::standby() { void Sprinkler::start_from_queue() { if (this->standby()) { - ESP_LOGD(TAG, "start_from_queue called but standby is enabled; no action taken"); + this->log_standby_warning_(LOG_STR("start_from_queue")); return; } if (this->multiplier() == 0) { - ESP_LOGD(TAG, "start_from_queue called but multiplier is set to zero; no action taken"); + this->log_multiplier_zero_warning_(LOG_STR("start_from_queue")); return; } if (this->queued_valves_.empty()) { @@ -832,11 +832,11 @@ void Sprinkler::start_from_queue() { void Sprinkler::start_full_cycle() { if (this->standby()) { - ESP_LOGD(TAG, "start_full_cycle called but standby is enabled; no action taken"); + this->log_standby_warning_(LOG_STR("start_full_cycle")); return; } if (this->multiplier() == 0) { - ESP_LOGD(TAG, "start_full_cycle called but multiplier is set to zero; no action taken"); + this->log_multiplier_zero_warning_(LOG_STR("start_full_cycle")); return; } if (this->auto_advance() && this->active_valve().has_value()) { @@ -855,11 +855,11 @@ void Sprinkler::start_full_cycle() { void Sprinkler::start_single_valve(const optional valve_number, optional run_duration) { if (this->standby()) { - ESP_LOGD(TAG, "start_single_valve called but standby is enabled; no action taken"); + this->log_standby_warning_(LOG_STR("start_single_valve")); return; } if (this->multiplier() == 0) { - ESP_LOGD(TAG, "start_single_valve called but multiplier is set to zero; no action taken"); + this->log_multiplier_zero_warning_(LOG_STR("start_single_valve")); return; } if (!valve_number.has_value() || (valve_number == this->active_valve())) { @@ -891,6 +891,11 @@ void Sprinkler::clear_queued_valves() { } void Sprinkler::next_valve() { + if (this->standby()) { + this->log_standby_warning_(LOG_STR("next_valve")); + return; + } + if (this->state_ == IDLE) { this->reset_cycle_states_(); // just in case auto-advance is switched on later } @@ -914,6 +919,11 @@ void Sprinkler::next_valve() { } void Sprinkler::previous_valve() { + if (this->standby()) { + this->log_standby_warning_(LOG_STR("previous_valve")); + return; + } + if (this->state_ == IDLE) { this->reset_cycle_states_(); // just in case auto-advance is switched on later } @@ -964,7 +974,7 @@ void Sprinkler::pause() { void Sprinkler::resume() { if (this->standby()) { - ESP_LOGD(TAG, "resume called but standby is enabled; no action taken"); + this->log_standby_warning_(LOG_STR("resume")); return; } @@ -1009,7 +1019,7 @@ optional Sprinkler::active_valve_request_is_from } optional Sprinkler::active_valve() { - if (!this->valve_overlap_ && this->prev_req_.has_request() && + if (!this->valve_overlap_ && this->prev_req_.has_request() && this->prev_req_.has_valve_operator() && (this->prev_req_.valve_operator()->state() == STARTING || this->prev_req_.valve_operator()->state() == ACTIVE)) { return this->prev_req_.valve_as_opt(); } @@ -1029,9 +1039,7 @@ optional Sprinkler::manual_valve() { return this->manual_valve_; } size_t Sprinkler::number_of_valves() { return this->valve_.size(); } -bool Sprinkler::is_a_valid_valve(const size_t valve_number) { - return ((valve_number >= 0) && (valve_number < this->number_of_valves())); -} +bool Sprinkler::is_a_valid_valve(const size_t valve_number) { return (valve_number < this->number_of_valves()); } bool Sprinkler::pump_in_use(SprinklerSwitch *pump_switch) { if (pump_switch == nullptr) { @@ -1062,8 +1070,12 @@ bool Sprinkler::pump_in_use(SprinklerSwitch *pump_switch) { this->active_req_.has_request() && (this->state_ != STOPPING)) { // ...the controller is configured to keep the pump on during a valve open delay, so just return // whether or not the next valve shares the same pump - return (pump_switch->off_switch() == this->valve_pump_switch(this->active_req_.valve())->off_switch()) && - (pump_switch->on_switch() == this->valve_pump_switch(this->active_req_.valve())->on_switch()); + auto *valve_pump = this->valve_pump_switch(this->active_req_.valve()); + if (valve_pump == nullptr) { + return false; // valve has no pump, so this pump isn't in use by it + } + return (pump_switch->off_switch() == valve_pump->off_switch()) && + (pump_switch->on_switch() == valve_pump->on_switch()); } return false; } @@ -1426,8 +1438,8 @@ void Sprinkler::start_valve_(SprinklerValveRunRequest *req) { if (vo.state() == IDLE) { auto run_duration = req->run_duration() ? req->run_duration() : this->valve_run_duration_adjusted(req->valve()); ESP_LOGD(TAG, "%s is starting valve %zu for %" PRIu32 " seconds, cycle %" PRIu32 " of %" PRIu32, - this->req_as_str_(req->request_is_from()).c_str(), req->valve(), run_duration, this->repeat_count_ + 1, - this->repeat().value_or(0) + 1); + LOG_STR_ARG(this->req_as_str_(req->request_is_from())), req->valve(), run_duration, + this->repeat_count_ + 1, this->repeat().value_or(0) + 1); req->set_valve_operator(&vo); vo.set_controller(this); vo.set_valve(&this->valve_[req->valve()]); @@ -1488,7 +1500,7 @@ void Sprinkler::fsm_kick_() { } void Sprinkler::fsm_transition_() { - ESP_LOGVV(TAG, "fsm_transition_ called; state is %s", this->state_as_str_(this->state_).c_str()); + ESP_LOGVV(TAG, "fsm_transition_ called; state is %s", LOG_STR_ARG(this->state_as_str_(this->state_))); switch (this->state_) { case IDLE: // the system was off -> start it up // advances to ACTIVE @@ -1502,8 +1514,11 @@ void Sprinkler::fsm_transition_() { case STARTING: { // follows valve open delay interval - this->set_timer_duration_(sprinkler::TIMER_SM, - this->active_req_.run_duration() - this->switching_delay_.value_or(0)); + uint32_t timer_duration = this->active_req_.run_duration(); + if (timer_duration > this->switching_delay_.value_or(0)) { + timer_duration -= this->switching_delay_.value_or(0); + } + this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration); this->start_timer_(sprinkler::TIMER_SM); this->start_valve_(&this->active_req_); this->state_ = ACTIVE; @@ -1531,7 +1546,7 @@ void Sprinkler::fsm_transition_() { this->set_timer_duration_(sprinkler::TIMER_SM, this->manual_selection_delay_.value_or(1)); this->start_timer_(sprinkler::TIMER_SM); } - ESP_LOGVV(TAG, "fsm_transition_ complete; new state is %s", this->state_as_str_(this->state_).c_str()); + ESP_LOGVV(TAG, "fsm_transition_ complete; new state is %s", LOG_STR_ARG(this->state_as_str_(this->state_))); } void Sprinkler::fsm_transition_from_shutdown_() { @@ -1543,8 +1558,11 @@ void Sprinkler::fsm_transition_from_shutdown_() { this->active_req_.set_run_duration(this->next_req_.run_duration()); this->next_req_.reset(); - this->set_timer_duration_(sprinkler::TIMER_SM, - this->active_req_.run_duration() - this->switching_delay_.value_or(0)); + uint32_t timer_duration = this->active_req_.run_duration(); + if (timer_duration > this->switching_delay_.value_or(0)) { + timer_duration -= this->switching_delay_.value_or(0); + } + this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration); this->start_timer_(sprinkler::TIMER_SM); this->start_valve_(&this->active_req_); this->state_ = ACTIVE; @@ -1571,8 +1589,9 @@ void Sprinkler::fsm_transition_from_valve_run_() { this->load_next_valve_run_request_(this->active_req_.valve()); if (this->next_req_.has_request()) { // there is another valve to run... - bool same_pump = - this->valve_pump_switch(this->active_req_.valve()) == this->valve_pump_switch(this->next_req_.valve()); + auto *active_pump = this->valve_pump_switch(this->active_req_.valve()); + auto *next_pump = this->valve_pump_switch(this->next_req_.valve()); + bool same_pump = (active_pump != nullptr) && (next_pump != nullptr) && (active_pump == next_pump); this->active_req_.set_valve(this->next_req_.valve()); this->active_req_.set_request_from(this->next_req_.request_is_from()); @@ -1581,8 +1600,11 @@ void Sprinkler::fsm_transition_from_valve_run_() { // this->state_ = ACTIVE; // state isn't changing if (this->valve_overlap_ || !this->switching_delay_.has_value()) { - this->set_timer_duration_(sprinkler::TIMER_SM, - this->active_req_.run_duration() - this->switching_delay_.value_or(0)); + uint32_t timer_duration = this->active_req_.run_duration(); + if (timer_duration > this->switching_delay_.value_or(0)) { + timer_duration -= this->switching_delay_.value_or(0); + } + this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration); this->start_timer_(sprinkler::TIMER_SM); this->start_valve_(&this->active_req_); } else { @@ -1605,41 +1627,49 @@ void Sprinkler::fsm_transition_to_shutdown_() { this->start_timer_(sprinkler::TIMER_SM); } -std::string Sprinkler::req_as_str_(SprinklerValveRunRequestOrigin origin) { +void Sprinkler::log_standby_warning_(const LogString *method_name) { + ESP_LOGW(TAG, "%s called but standby is enabled; no action taken", LOG_STR_ARG(method_name)); +} + +void Sprinkler::log_multiplier_zero_warning_(const LogString *method_name) { + ESP_LOGW(TAG, "%s called but multiplier is set to zero; no action taken", LOG_STR_ARG(method_name)); +} + +const LogString *Sprinkler::req_as_str_(SprinklerValveRunRequestOrigin origin) { switch (origin) { case USER: - return "USER"; + return LOG_STR("USER"); case CYCLE: - return "CYCLE"; + return LOG_STR("CYCLE"); case QUEUE: - return "QUEUE"; + return LOG_STR("QUEUE"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -std::string Sprinkler::state_as_str_(SprinklerState state) { +const LogString *Sprinkler::state_as_str_(SprinklerState state) { switch (state) { case IDLE: - return "IDLE"; + return LOG_STR("IDLE"); case STARTING: - return "STARTING"; + return LOG_STR("STARTING"); case ACTIVE: - return "ACTIVE"; + return LOG_STR("ACTIVE"); case STOPPING: - return "STOPPING"; + return LOG_STR("STOPPING"); case BYPASS: - return "BYPASS"; + return LOG_STR("BYPASS"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } @@ -1737,5 +1767,4 @@ void Sprinkler::dump_config() { } } -} // namespace sprinkler -} // namespace esphome +} // namespace esphome::sprinkler diff --git a/esphome/components/sprinkler/sprinkler.h b/esphome/components/sprinkler/sprinkler.h index c4a8b8aeb8..7aa33c2df9 100644 --- a/esphome/components/sprinkler/sprinkler.h +++ b/esphome/components/sprinkler/sprinkler.h @@ -8,8 +8,7 @@ #include -namespace esphome { -namespace sprinkler { +namespace esphome::sprinkler { const std::string MIN_STR = "min"; @@ -490,11 +489,17 @@ class Sprinkler : public Component { /// starts up the system from IDLE state void fsm_transition_to_shutdown_(); + /// log error message when a method is called but standby is enabled + void log_standby_warning_(const LogString *method_name); + + /// log error message when a method is called but multiplier is zero + void log_multiplier_zero_warning_(const LogString *method_name); + /// return the specified SprinklerValveRunRequestOrigin as a string - std::string req_as_str_(SprinklerValveRunRequestOrigin origin); + const LogString *req_as_str_(SprinklerValveRunRequestOrigin origin); /// return the specified SprinklerState state as a string - std::string state_as_str_(SprinklerState state); + const LogString *state_as_str_(SprinklerState state); /// Start/cancel/get status of valve timers void start_timer_(SprinklerTimerIndex timer_index); @@ -607,5 +612,4 @@ class Sprinkler : public Component { std::unique_ptr> sprinkler_standby_turn_on_automation_; }; -} // namespace sprinkler -} // namespace esphome +} // namespace esphome::sprinkler From 60756db06d9ce8bd58a499a58df5b1406b1f60b6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 20 Dec 2025 22:47:37 -1000 Subject: [PATCH 0745/1145] [syslog] Use C++17 nested namespace syntax (#12594) --- esphome/components/syslog/esphome_syslog.cpp | 6 ++---- esphome/components/syslog/esphome_syslog.h | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/esphome/components/syslog/esphome_syslog.cpp b/esphome/components/syslog/esphome_syslog.cpp index 851fb30c22..d48fb4f15c 100644 --- a/esphome/components/syslog/esphome_syslog.cpp +++ b/esphome/components/syslog/esphome_syslog.cpp @@ -4,8 +4,7 @@ #include "esphome/core/application.h" #include "esphome/core/time.h" -namespace esphome { -namespace syslog { +namespace esphome::syslog { // Map log levels to syslog severity using an array, indexed by ESPHome log level (1-7) constexpr int LOG_LEVEL_TO_SYSLOG_SEVERITY[] = { @@ -54,5 +53,4 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t this->parent_->send_packet((const uint8_t *) data.data(), data.size()); } -} // namespace syslog -} // namespace esphome +} // namespace esphome::syslog diff --git a/esphome/components/syslog/esphome_syslog.h b/esphome/components/syslog/esphome_syslog.h index 1010993265..bde6ab5ed4 100644 --- a/esphome/components/syslog/esphome_syslog.h +++ b/esphome/components/syslog/esphome_syslog.h @@ -7,8 +7,7 @@ #include "esphome/components/time/real_time_clock.h" #ifdef USE_NETWORK -namespace esphome { -namespace syslog { +namespace esphome::syslog { class Syslog : public Component, public Parented, public logger::LogListener { public: Syslog(int level, time::RealTimeClock *time) : log_level_(level), time_(time) {} @@ -24,6 +23,5 @@ class Syslog : public Component, public Parented, public logg bool strip_{true}; int facility_{16}; }; -} // namespace syslog -} // namespace esphome +} // namespace esphome::syslog #endif From 5a36cea5eccead440a6984227ae84d2ac4c34173 Mon Sep 17 00:00:00 2001 From: polyfloyd Date: Sun, 21 Dec 2025 15:26:03 +0100 Subject: [PATCH 0746/1145] Add nix files to gitignore (#12604) --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 390d1ab45b..da568d9b83 100644 --- a/.gitignore +++ b/.gitignore @@ -91,6 +91,10 @@ venv-*/ # mypy .mypy_cache/ +# nix +/default.nix +/shell.nix + .pioenvs .piolibdeps .pio From a799ac64882db9f7817e55386ea9c782adaa30b1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Dec 2025 07:10:27 -1000 Subject: [PATCH 0747/1145] [syslog] Eliminate heap allocations in log path (#12589) --- esphome/components/syslog/esphome_syslog.cpp | 46 ++- tests/integration/fixtures/syslog.yaml | 43 +++ tests/integration/test_syslog.py | 284 +++++++++++++++++++ 3 files changed, 362 insertions(+), 11 deletions(-) create mode 100644 tests/integration/fixtures/syslog.yaml create mode 100644 tests/integration/test_syslog.py diff --git a/esphome/components/syslog/esphome_syslog.cpp b/esphome/components/syslog/esphome_syslog.cpp index d48fb4f15c..83ad6b2720 100644 --- a/esphome/components/syslog/esphome_syslog.cpp +++ b/esphome/components/syslog/esphome_syslog.cpp @@ -33,15 +33,7 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t severity = LOG_LEVEL_TO_SYSLOG_SEVERITY[level]; } int pri = this->facility_ * 8 + severity; - auto now = this->time_->now(); - std::string timestamp; - if (now.is_valid()) { - timestamp = now.strftime("%b %e %H:%M:%S"); - } else { - // RFC 5424: A syslog application MUST use the NILVALUE as TIMESTAMP if the syslog application is incapable of - // obtaining system time. - timestamp = "-"; - } + size_t len = message_len; // remove color formatting if (this->strip_ && message[0] == 0x1B && len > 11) { @@ -49,8 +41,40 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t len -= 11; } - auto data = str_sprintf("<%d>%s %s %s: %.*s", pri, timestamp.c_str(), App.get_name().c_str(), tag, len, message); - this->parent_->send_packet((const uint8_t *) data.data(), data.size()); + // Build syslog packet on stack (508 bytes chosen as practical limit for syslog over UDP) + char packet[508]; + size_t offset = 0; + size_t remaining = sizeof(packet); + + // Write PRI - abort if this fails as packet would be malformed + int ret = snprintf(packet, remaining, "<%d>", pri); + if (ret <= 0 || static_cast(ret) >= remaining) { + return; + } + offset = ret; + remaining -= ret; + + // Write timestamp directly into packet (RFC 5424: use "-" if time not valid or strftime fails) + auto now = this->time_->now(); + size_t ts_written = now.is_valid() ? now.strftime(packet + offset, remaining, "%b %e %H:%M:%S") : 0; + if (ts_written > 0) { + offset += ts_written; + remaining -= ts_written; + } else if (remaining > 0) { + packet[offset++] = '-'; + remaining--; + } + + // Write hostname, tag, and message + ret = snprintf(packet + offset, remaining, " %s %s: %.*s", App.get_name().c_str(), tag, (int) len, message); + if (ret > 0) { + // snprintf returns chars that would be written; clamp to actual buffer space + offset += std::min(static_cast(ret), remaining > 0 ? remaining - 1 : 0); + } + + if (offset > 0) { + this->parent_->send_packet(reinterpret_cast(packet), offset); + } } } // namespace esphome::syslog diff --git a/tests/integration/fixtures/syslog.yaml b/tests/integration/fixtures/syslog.yaml new file mode 100644 index 0000000000..df376087e3 --- /dev/null +++ b/tests/integration/fixtures/syslog.yaml @@ -0,0 +1,43 @@ +esphome: + name: syslog-test + +host: + +api: + services: + - service: log_long_message + then: + - lambda: |- + // Log a message that exceeds 508 bytes to test truncation + ESP_LOGI("trunctest", "START|%s|END", + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + - service: log_short_message + then: + - lambda: |- + // Log a short message that should arrive complete (not truncated) + ESP_LOGI("shorttest", "BEGIN|SHORT_MESSAGE_CONTENT|FINISH"); + +logger: + level: DEBUG + +time: + - platform: host + id: host_time + +udp: + - id: syslog_udp + addresses: + - "127.0.0.1" + +syslog: + udp_id: syslog_udp + time_id: host_time + port: SYSLOG_PORT_PLACEHOLDER + level: DEBUG + strip: true + facility: 16 diff --git a/tests/integration/test_syslog.py b/tests/integration/test_syslog.py new file mode 100644 index 0000000000..b31a19392c --- /dev/null +++ b/tests/integration/test_syslog.py @@ -0,0 +1,284 @@ +"""Integration test for syslog component.""" + +from __future__ import annotations + +import asyncio +from collections.abc import AsyncGenerator +import contextlib +from contextlib import asynccontextmanager +from dataclasses import dataclass, field +import re +import socket +from typing import TypedDict + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +class ParsedSyslogMessage(TypedDict): + """Parsed syslog message components.""" + + pri: int + facility: int + severity: int + timestamp: str + hostname: str + tag: str + message: str + + +# RFC 3164 syslog message pattern: +# TIMESTAMP HOSTNAME TAG: MESSAGE +# Example: <134>Dec 20 14:30:45 syslog-test app: [D][app:029]: Running... +SYSLOG_PATTERN = re.compile( + r"<(\d+)>" # PRI (priority = facility * 8 + severity) + r"(\S+ +\d+ \d+:\d+:\d+|-)" # TIMESTAMP (BSD-style "%b %e %H:%M:%S", e.g. "Dec 20 14:30:45", or NILVALUE "-") + r" (\S+)" # HOSTNAME + r" (\S+):" # TAG + r" (.*)" # MESSAGE +) + + +@dataclass +class SyslogReceiver: + """Collects syslog messages received over UDP.""" + + messages: list[str] = field(default_factory=list) + message_received: asyncio.Event = field(default_factory=asyncio.Event) + _waiters: list[tuple[re.Pattern, asyncio.Event]] = field(default_factory=list) + + def on_message(self, msg: str) -> None: + """Called when a message is received.""" + self.messages.append(msg) + self.message_received.set() + # Check pattern waiters + for pattern, event in self._waiters: + if pattern.search(msg): + event.set() + + async def wait_for_messages(self, timeout: float = 10.0) -> None: + """Wait for at least one message to be received.""" + await asyncio.wait_for(self.message_received.wait(), timeout=timeout) + + async def wait_for_pattern(self, pattern: str, timeout: float = 5.0) -> str: + """Wait for a message matching the pattern.""" + compiled = re.compile(pattern) + event = asyncio.Event() + self._waiters.append((compiled, event)) + try: + # Check existing messages first + for msg in self.messages: + if compiled.search(msg): + return msg + # Wait for new message + await asyncio.wait_for(event.wait(), timeout=timeout) + # Find and return the matching message + for msg in reversed(self.messages): + if compiled.search(msg): + return msg + raise RuntimeError("Event set but no matching message found") + finally: + self._waiters.remove((compiled, event)) + + +@asynccontextmanager +async def syslog_udp_listener() -> AsyncGenerator[tuple[int, SyslogReceiver]]: + """Async context manager that listens for syslog UDP messages. + + Yields: + Tuple of (port, SyslogReceiver) where port is the UDP port to send to + and SyslogReceiver contains the received messages. + """ + # Create and bind UDP socket + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(("127.0.0.1", 0)) + sock.setblocking(False) + port = sock.getsockname()[1] + + receiver = SyslogReceiver() + + async def receive_messages() -> None: + """Background task to receive syslog messages.""" + loop = asyncio.get_running_loop() + while True: + try: + data = await loop.sock_recv(sock, 4096) + if data: + msg = data.decode("utf-8", errors="replace") + receiver.on_message(msg) + except BlockingIOError: + await asyncio.sleep(0.01) + except Exception: + break + + task = asyncio.create_task(receive_messages()) + try: + yield port, receiver + finally: + task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await task + sock.close() + + +def parse_syslog_message(msg: str) -> ParsedSyslogMessage | None: + """Parse a syslog message and return its components.""" + match = SYSLOG_PATTERN.match(msg) + if not match: + return None + pri, timestamp, hostname, tag, message = match.groups() + pri_val = int(pri) + # PRI = facility * 8 + severity + facility = pri_val // 8 + severity = pri_val % 8 + return ParsedSyslogMessage( + pri=pri_val, + facility=facility, + severity=severity, + timestamp=timestamp, + hostname=hostname, + tag=tag, + message=message, + ) + + +@pytest.mark.asyncio +async def test_syslog( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test syslog component sends properly formatted messages.""" + async with syslog_udp_listener() as (udp_port, receiver): + # Replace the placeholder port in the config + config = yaml_config.replace("SYSLOG_PORT_PLACEHOLDER", str(udp_port)) + + async with run_compiled(config), api_client_connected() as client: + # Verify device is running + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "syslog-test" + + # Wait for syslog messages (ESPHome logs during startup) + try: + await receiver.wait_for_messages(timeout=10.0) + except TimeoutError: + pytest.fail("No syslog messages received within timeout") + + # Give it a moment to collect more messages + await asyncio.sleep(0.5) + + # Verify we received messages + assert len(receiver.messages) > 0, "No syslog messages received" + + # Parse and validate all messages + parsed_messages: list[ParsedSyslogMessage] = [] + for msg in receiver.messages: + parsed = parse_syslog_message(msg) + if parsed: + parsed_messages.append(parsed) + + assert len(parsed_messages) > 0, ( + f"No valid syslog messages found. Received: {receiver.messages[:5]}" + ) + + # Validate message format for all parsed messages + for parsed in parsed_messages: + # Validate PRI is in valid range (0-191) + assert 0 <= parsed["pri"] <= 191, f"Invalid PRI: {parsed['pri']}" + + # Validate facility matches config (16 = local0) + assert parsed["facility"] == 16, ( + f"Expected facility 16, got {parsed['facility']}" + ) + + # Validate severity is in valid range (0-7) + assert 0 <= parsed["severity"] <= 7, ( + f"Invalid severity: {parsed['severity']}" + ) + + # Validate hostname matches device name + assert parsed["hostname"] == "syslog-test", ( + f"Unexpected hostname: {parsed['hostname']}" + ) + + # Validate timestamp format (BSD or NILVALUE) + if parsed["timestamp"] != "-": + assert re.match( + r"[A-Z][a-z]{2} +\d+ \d{2}:\d{2}:\d{2}", + parsed["timestamp"], + ), f"Invalid timestamp format: {parsed['timestamp']}" + + # Verify we see different severity levels in the logs + severities_seen = {p["severity"] for p in parsed_messages} + # ESPHome startup logs should include at least INFO (5) or DEBUG (7) + assert len(severities_seen) >= 1, "Expected to see at least one severity" + + # Verify messages don't contain ANSI color codes (strip=true) + for parsed in parsed_messages: + assert "\x1b[" not in parsed["message"], ( + f"Color codes not stripped: {parsed['message'][:50]}" + ) + + # Verify message content is not empty for most messages + non_empty_messages = [p for p in parsed_messages if p["message"].strip()] + assert len(non_empty_messages) > 0, "All messages are empty" + + # Verify tag format (should be component name like "app", "wifi", etc.) + for parsed in parsed_messages: + assert len(parsed["tag"]) > 0, "Empty tag" + # Tag should not contain spaces or colons + assert " " not in parsed["tag"], f"Tag contains space: {parsed['tag']}" + + # Test message truncation - call service that logs a very long message + _, services = await client.list_entities_services() + log_service = next( + (s for s in services if s.name == "log_long_message"), None + ) + assert log_service is not None, "log_long_message service not found" + + # Call the service to trigger a long log message + await client.execute_service(log_service, {}) + + # Wait specifically for the truncation test message + try: + trunc_msg = await receiver.wait_for_pattern(r"trunctest.*START\|") + except TimeoutError: + pytest.fail( + f"Truncation test message not received. Got: {receiver.messages}" + ) + + # Verify message is truncated to max 508 bytes + assert len(trunc_msg) <= 508, f"Message exceeds 508 bytes: {len(trunc_msg)}" + + # Verify the message starts correctly but is truncated (no "|END") + assert "START|" in trunc_msg, "Message should contain START marker" + assert "|END" not in trunc_msg, ( + "Message should be truncated before END marker" + ) + + # Test short message - should arrive complete (not truncated) + short_service = next( + (s for s in services if s.name == "log_short_message"), None + ) + assert short_service is not None, "log_short_message service not found" + + await client.execute_service(short_service, {}) + + try: + short_msg = await receiver.wait_for_pattern(r"shorttest.*BEGIN\|") + except TimeoutError: + pytest.fail( + f"Short test message not received. Got: {receiver.messages[-10:]}" + ) + + # Verify short message arrived complete with both markers + assert "BEGIN|" in short_msg, "Short message missing BEGIN marker" + assert "|FINISH" in short_msg, ( + f"Short message truncated unexpectedly: {short_msg}" + ) + assert "SHORT_MESSAGE_CONTENT" in short_msg, ( + f"Short message content missing: {short_msg}" + ) From c70eab931e003be5704ebdc6261f62c7357537c7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Dec 2025 07:31:54 -1000 Subject: [PATCH 0748/1145] [api] Add zero-copy support for Home Assistant state response messages (#12585) --- esphome/components/api/api.proto | 6 ++--- esphome/components/api/api_connection.cpp | 28 +++++++++++++++------ esphome/components/api/api_pb2.cpp | 21 +++++++++++----- esphome/components/api/api_pb2.h | 11 +++++--- esphome/components/api/api_pb2_dump.cpp | 12 ++++++--- tests/integration/test_api_homeassistant.py | 6 +++++ 6 files changed, 61 insertions(+), 23 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 5d44d7e549..bf39f0b14b 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -824,9 +824,9 @@ message HomeAssistantStateResponse { option (no_delay) = true; option (ifdef) = "USE_API_HOMEASSISTANT_STATES"; - string entity_id = 1; - string state = 2; - string attribute = 3; + string entity_id = 1 [(pointer_to_buffer) = true]; + string state = 2 [(pointer_to_buffer) = true]; + string attribute = 3 [(pointer_to_buffer) = true]; } // ==================== IMPORT TIME ==================== diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 0f551d1bc3..1bcb90b0b0 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1582,15 +1582,29 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { #ifdef USE_API_HOMEASSISTANT_STATES void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) { - for (auto &it : this->parent_->get_state_subs()) { - // Compare entity_id and attribute with message fields - bool entity_match = (strcmp(it.entity_id, msg.entity_id.c_str()) == 0); - bool attribute_match = (it.attribute != nullptr && strcmp(it.attribute, msg.attribute.c_str()) == 0) || - (it.attribute == nullptr && msg.attribute.empty()); + // Skip if entity_id is empty (invalid message) + if (msg.entity_id_len == 0) { + return; + } - if (entity_match && attribute_match) { - it.callback(msg.state); + for (auto &it : this->parent_->get_state_subs()) { + // Compare entity_id: check length matches and content matches + size_t entity_id_len = strlen(it.entity_id); + if (entity_id_len != msg.entity_id_len || memcmp(it.entity_id, msg.entity_id, msg.entity_id_len) != 0) { + continue; } + + // Compare attribute: either both have matching attribute, or both have none + size_t sub_attr_len = it.attribute != nullptr ? strlen(it.attribute) : 0; + if (sub_attr_len != msg.attribute_len || + (sub_attr_len > 0 && memcmp(it.attribute, msg.attribute, sub_attr_len) != 0)) { + continue; + } + + // Create temporary string for callback (callback takes const std::string &) + // Handle empty state (nullptr with len=0) + std::string state(msg.state_len > 0 ? reinterpret_cast(msg.state) : "", msg.state_len); + it.callback(state); } } #endif diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 8b84f9651f..6a2d902f8f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -966,15 +966,24 @@ void SubscribeHomeAssistantStateResponse::calculate_size(ProtoSize &size) const } bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->entity_id = value.as_string(); + case 1: { + // Use raw data directly to avoid allocation + this->entity_id = value.data(); + this->entity_id_len = value.size(); break; - case 2: - this->state = value.as_string(); + } + case 2: { + // Use raw data directly to avoid allocation + this->state = value.data(); + this->state_len = value.size(); break; - case 3: - this->attribute = value.as_string(); + } + case 3: { + // Use raw data directly to avoid allocation + this->attribute = value.data(); + this->attribute_len = value.size(); break; + } default: return false; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 668c0af461..22deb19be8 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1203,13 +1203,16 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage { class HomeAssistantStateResponse final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 40; - static constexpr uint8_t ESTIMATED_SIZE = 27; + static constexpr uint8_t ESTIMATED_SIZE = 57; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "home_assistant_state_response"; } #endif - std::string entity_id{}; - std::string state{}; - std::string attribute{}; + const uint8_t *entity_id{nullptr}; + uint16_t entity_id_len{0}; + const uint8_t *state{nullptr}; + uint16_t state_len{0}; + const uint8_t *attribute{nullptr}; + uint16_t attribute_len{0}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 38c3b473e6..7815eb73e4 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1184,9 +1184,15 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { } void HomeAssistantStateResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HomeAssistantStateResponse"); - dump_field(out, "entity_id", this->entity_id); - dump_field(out, "state", this->state); - dump_field(out, "attribute", this->attribute); + out.append(" entity_id: "); + out.append(format_hex_pretty(this->entity_id, this->entity_id_len)); + out.append("\n"); + out.append(" state: "); + out.append(format_hex_pretty(this->state, this->state_len)); + out.append("\n"); + out.append(" attribute: "); + out.append(format_hex_pretty(this->attribute, this->attribute_len)); + out.append("\n"); } #endif void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); } diff --git a/tests/integration/test_api_homeassistant.py b/tests/integration/test_api_homeassistant.py index 1343691f5f..3fe0dfe045 100644 --- a/tests/integration/test_api_homeassistant.py +++ b/tests/integration/test_api_homeassistant.py @@ -179,6 +179,12 @@ async def test_api_homeassistant( client.send_home_assistant_state("binary_sensor.external_motion", "", "ON") client.send_home_assistant_state("weather.home", "condition", "sunny") + # Test edge cases for zero-copy implementation safety + # Empty entity_id should be silently ignored (no crash) + client.send_home_assistant_state("", "", "should_be_ignored") + # Empty state with valid entity should work (use different entity to not interfere with test) + client.send_home_assistant_state("sensor.edge_case_empty_state", "", "") + # List entities and services _, services = await client.list_entities_services() From bf617c327902fbc739e4a551c31450891af9464c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Dec 2025 07:32:05 -1000 Subject: [PATCH 0749/1145] [web_server] Replace str_sprintf with stack buffers (#12592) --- esphome/components/web_server/web_server.cpp | 27 ++++++++++++++++--- .../web_server_idf/web_server_idf.cpp | 5 ++-- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 0c22c2f08d..6870a1dc87 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1042,7 +1042,13 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con json::JsonBuilder builder; JsonObject root = builder.root(); - std::string value = str_sprintf("%d-%02d-%02d", obj->year, obj->month, obj->day); + // Format: YYYY-MM-DD (max 10 chars + null) + char value[12]; +#ifdef USE_ESP8266 + snprintf_P(value, sizeof(value), PSTR("%d-%02d-%02d"), obj->year, obj->month, obj->day); +#else + snprintf(value, sizeof(value), "%d-%02d-%02d", obj->year, obj->month, obj->day); +#endif set_json_icon_state_value(root, obj, "date", value, value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); @@ -1098,7 +1104,13 @@ std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_con json::JsonBuilder builder; JsonObject root = builder.root(); - std::string value = str_sprintf("%02d:%02d:%02d", obj->hour, obj->minute, obj->second); + // Format: HH:MM:SS (8 chars + null) + char value[12]; +#ifdef USE_ESP8266 + snprintf_P(value, sizeof(value), PSTR("%02d:%02d:%02d"), obj->hour, obj->minute, obj->second); +#else + snprintf(value, sizeof(value), "%02d:%02d:%02d", obj->hour, obj->minute, obj->second); +#endif set_json_icon_state_value(root, obj, "time", value, value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); @@ -1154,8 +1166,15 @@ std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail s json::JsonBuilder builder; JsonObject root = builder.root(); - std::string value = - str_sprintf("%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, obj->minute, obj->second); + // Format: YYYY-MM-DD HH:MM:SS (max 19 chars + null) + char value[24]; +#ifdef USE_ESP8266 + snprintf_P(value, sizeof(value), PSTR("%d-%02d-%02d %02d:%02d:%02d"), obj->year, obj->month, obj->day, obj->hour, + obj->minute, obj->second); +#else + snprintf(value, sizeof(value), "%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, obj->minute, + obj->second); +#endif set_json_icon_state_value(root, obj, "datetime", value, value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 8c3ad288c0..3d76b86a14 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -343,8 +343,9 @@ bool AsyncWebServerRequest::authenticate(const char *username, const char *passw void AsyncWebServerRequest::requestAuthentication(const char *realm) const { httpd_resp_set_hdr(*this, "Connection", "keep-alive"); - auto auth_val = str_sprintf("Basic realm=\"%s\"", realm ? realm : "Login Required"); - httpd_resp_set_hdr(*this, "WWW-Authenticate", auth_val.c_str()); + // Note: realm is never configured in ESPHome, always nullptr -> "Login Required" + (void) realm; // Unused - always use default + httpd_resp_set_hdr(*this, "WWW-Authenticate", "Basic realm=\"Login Required\""); httpd_resp_send_err(*this, HTTPD_401_UNAUTHORIZED, nullptr); } #endif From d89eaf5bf6312d101537a4a9842abede020c96b5 Mon Sep 17 00:00:00 2001 From: Anna Oake Date: Sun, 21 Dec 2025 19:04:17 +0100 Subject: [PATCH 0750/1145] [cc1101] Fix option defaults and move them to YAML (#12608) --- esphome/components/cc1101/__init__.py | 95 +++++++++++++++++---------- esphome/components/cc1101/cc1101.cpp | 17 ----- 2 files changed, 59 insertions(+), 53 deletions(-) diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index e314da7079..c205ff2f69 100644 --- a/esphome/components/cc1101/__init__.py +++ b/esphome/components/cc1101/__init__.py @@ -160,41 +160,63 @@ HYST_LEVEL = { "High": HystLevel.HYST_LEVEL_HIGH, } -# Config key -> Validator mapping +# Optional settings to generate setter calls for CONFIG_MAP = { - CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0), - CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False), - CONF_DC_BLOCKING_FILTER: cv.boolean, - CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)), - CONF_IF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=25000, max=788000)), - CONF_FILTER_BANDWIDTH: cv.All(cv.frequency, cv.float_range(min=58000, max=812000)), - CONF_CHANNEL: cv.uint8_t, - CONF_CHANNEL_SPACING: cv.All(cv.frequency, cv.float_range(min=25000, max=405000)), - CONF_FSK_DEVIATION: cv.All(cv.frequency, cv.float_range(min=1500, max=381000)), - CONF_MSK_DEVIATION: cv.int_range(min=1, max=8), - CONF_SYMBOL_RATE: cv.float_range(min=600, max=500000), - CONF_SYNC_MODE: cv.enum(SYNC_MODE, upper=False), - CONF_CARRIER_SENSE_ABOVE_THRESHOLD: cv.boolean, - CONF_MODULATION_TYPE: cv.enum(MODULATION, upper=False), - CONF_MANCHESTER: cv.boolean, - CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7), - CONF_SYNC1: cv.hex_uint8_t, - CONF_SYNC0: cv.hex_uint8_t, - CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False), - CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False), - CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False), - CONF_CARRIER_SENSE_ABS_THR: cv.int_range(min=-8, max=7), - CONF_CARRIER_SENSE_REL_THR: cv.enum(CARRIER_SENSE_REL_THR, upper=False), - CONF_LNA_PRIORITY: cv.boolean, - CONF_FILTER_LENGTH_FSK_MSK: cv.enum(FILTER_LENGTH_FSK_MSK, upper=False), - CONF_FILTER_LENGTH_ASK_OOK: cv.enum(FILTER_LENGTH_ASK_OOK, upper=False), - CONF_FREEZE: cv.enum(FREEZE, upper=False), - CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False), - CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False), - CONF_PACKET_MODE: cv.boolean, - CONF_PACKET_LENGTH: cv.uint8_t, - CONF_CRC_ENABLE: cv.boolean, - CONF_WHITENING: cv.boolean, + cv.Optional(CONF_OUTPUT_POWER, default=10): cv.float_range(min=-30.0, max=11.0), + cv.Optional(CONF_RX_ATTENUATION, default="0dB"): cv.enum( + RX_ATTENUATION, upper=False + ), + cv.Optional(CONF_DC_BLOCKING_FILTER, default=True): cv.boolean, + cv.Optional(CONF_FREQUENCY, default="433.92MHz"): cv.All( + cv.frequency, cv.float_range(min=300.0e6, max=928.0e6) + ), + cv.Optional(CONF_IF_FREQUENCY, default="153kHz"): cv.All( + cv.frequency, cv.float_range(min=25000, max=788000) + ), + cv.Optional(CONF_FILTER_BANDWIDTH, default="203kHz"): cv.All( + cv.frequency, cv.float_range(min=58000, max=812000) + ), + cv.Optional(CONF_CHANNEL, default=0): cv.uint8_t, + cv.Optional(CONF_CHANNEL_SPACING, default="200kHz"): cv.All( + cv.frequency, cv.float_range(min=25000, max=405000) + ), + cv.Optional(CONF_FSK_DEVIATION): cv.All( + cv.frequency, cv.float_range(min=1500, max=381000) + ), + cv.Optional(CONF_MSK_DEVIATION): cv.int_range(min=1, max=8), + cv.Optional(CONF_SYMBOL_RATE, default=5000): cv.float_range(min=600, max=500000), + cv.Optional(CONF_SYNC_MODE, default="16/16"): cv.enum(SYNC_MODE, upper=False), + cv.Optional(CONF_CARRIER_SENSE_ABOVE_THRESHOLD, default=False): cv.boolean, + cv.Optional(CONF_MODULATION_TYPE, default="ASK/OOK"): cv.enum( + MODULATION, upper=False + ), + cv.Optional(CONF_MANCHESTER, default=False): cv.boolean, + cv.Optional(CONF_NUM_PREAMBLE, default=2): cv.int_range(min=0, max=7), + cv.Optional(CONF_SYNC1, default=0xD3): cv.hex_uint8_t, + cv.Optional(CONF_SYNC0, default=0x91): cv.hex_uint8_t, + cv.Optional(CONF_MAGN_TARGET, default="42dB"): cv.enum(MAGN_TARGET, upper=False), + cv.Optional(CONF_MAX_LNA_GAIN, default="Default"): cv.enum( + MAX_LNA_GAIN, upper=False + ), + cv.Optional(CONF_MAX_DVGA_GAIN, default="-3"): cv.enum(MAX_DVGA_GAIN, upper=False), + cv.Optional(CONF_CARRIER_SENSE_ABS_THR): cv.int_range(min=-8, max=7), + cv.Optional(CONF_CARRIER_SENSE_REL_THR): cv.enum( + CARRIER_SENSE_REL_THR, upper=False + ), + cv.Optional(CONF_LNA_PRIORITY, default=False): cv.boolean, + cv.Optional(CONF_FILTER_LENGTH_FSK_MSK): cv.enum( + FILTER_LENGTH_FSK_MSK, upper=False + ), + cv.Optional(CONF_FILTER_LENGTH_ASK_OOK): cv.enum( + FILTER_LENGTH_ASK_OOK, upper=False + ), + cv.Optional(CONF_FREEZE): cv.enum(FREEZE, upper=False), + cv.Optional(CONF_WAIT_TIME, default="32"): cv.enum(WAIT_TIME, upper=False), + cv.Optional(CONF_HYST_LEVEL): cv.enum(HYST_LEVEL, upper=False), + cv.Optional(CONF_PACKET_MODE, default=False): cv.boolean, + cv.Optional(CONF_PACKET_LENGTH): cv.uint8_t, + cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean, + cv.Optional(CONF_WHITENING, default=False): cv.boolean, } @@ -217,7 +239,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True), } ) - .extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()}) + .extend(CONFIG_MAP) .extend(cv.COMPONENT_SCHEMA) .extend(spi.spi_device_schema(cs_pin_required=True)), _validate_packet_mode, @@ -229,7 +251,8 @@ async def to_code(config): await cg.register_component(var, config) await spi.register_spi_device(var, config) - for key in CONFIG_MAP: + for opt in CONFIG_MAP: + key = opt.schema if key in config: cg.add(getattr(var, f"set_{key}")(config[key])) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 1fe402d6c6..f98afd94a1 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -98,25 +98,8 @@ CC1101Component::CC1101Component() { this->state_.LENGTH_CONFIG = 2; this->state_.FS_AUTOCAL = 1; - // Default Settings - this->set_frequency(433920000); - this->set_if_frequency(153000); - this->set_filter_bandwidth(203000); - this->set_channel(0); - this->set_channel_spacing(200000); - this->set_symbol_rate(5000); - this->set_sync_mode(SyncMode::SYNC_MODE_NONE); - this->set_carrier_sense_above_threshold(true); - this->set_modulation_type(Modulation::MODULATION_ASK_OOK); - this->set_magn_target(MagnTarget::MAGN_TARGET_42DB); - this->set_max_lna_gain(MaxLnaGain::MAX_LNA_GAIN_DEFAULT); - this->set_max_dvga_gain(MaxDvgaGain::MAX_DVGA_GAIN_MINUS_3); - this->set_lna_priority(false); - this->set_wait_time(WaitTime::WAIT_TIME_32_SAMPLES); - // CRITICAL: Initialize PA Table to avoid transmitting 0 power (Silence) memset(this->pa_table_, 0, sizeof(this->pa_table_)); - this->set_output_power(10.0f); } void CC1101Component::setup() { From 637e032528d9c35496b429fd5ef9d64aded0c5f6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Dec 2025 09:04:43 -1000 Subject: [PATCH 0751/1145] [esp32_camera] Throttle frame logging to reduce overhead and improve throughput (#12586) --- .../components/esp32_camera/esp32_camera.cpp | 18 +++++++++++++++++- esphome/components/esp32_camera/esp32_camera.h | 4 ++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 5080a6f32d..a3677330ca 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -11,6 +11,9 @@ namespace esphome { namespace esp32_camera { static const char *const TAG = "esp32_camera"; +#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE +static constexpr uint32_t FRAME_LOG_INTERVAL_MS = 60000; +#endif /* ---------------- public API (derivated) ---------------- */ void ESP32Camera::setup() { @@ -204,7 +207,20 @@ void ESP32Camera::loop() { } this->current_image_ = std::make_shared(fb, this->single_requesters_ | this->stream_requesters_); - ESP_LOGD(TAG, "Got Image: len=%u", fb->len); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + ESP_LOGV(TAG, "Got Image: len=%u", fb->len); +#else + // Initialize log time on first frame to ensure accurate interval measurement + if (this->frame_count_ == 0) { + this->last_log_time_ = now; + } + this->frame_count_++; + if (now - this->last_log_time_ >= FRAME_LOG_INTERVAL_MS) { + ESP_LOGD(TAG, "Received %u images in last %us", this->frame_count_, FRAME_LOG_INTERVAL_MS / 1000); + this->last_log_time_ = now; + this->frame_count_ = 0; + } +#endif for (auto *listener : this->listeners_) { listener->on_camera_image(this->current_image_); } diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 54a7d6064a..a49fca6511 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -213,6 +213,10 @@ class ESP32Camera : public camera::Camera { uint32_t last_idle_request_{0}; uint32_t last_update_{0}; +#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE + uint32_t last_log_time_{0}; + uint16_t frame_count_{0}; +#endif #ifdef USE_I2C i2c::InternalI2CBus *i2c_bus_{nullptr}; #endif // USE_I2C From 39926909af216486cc42edd6e3b1de53a8a98e13 Mon Sep 17 00:00:00 2001 From: Douwe <61123717+dhoeben@users.noreply.github.com> Date: Sun, 21 Dec 2025 22:36:34 +0100 Subject: [PATCH 0752/1145] [water_heater] (1/4) Implement API/Core/component for new water_heater component (#12498) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- CODEOWNERS | 1 + esphome/components/api/api.proto | 79 +++++ esphome/components/api/api_connection.cpp | 54 ++++ esphome/components/api/api_connection.h | 11 + esphome/components/api/api_pb2.cpp | 108 +++++++ esphome/components/api/api_pb2.h | 83 ++++++ esphome/components/api/api_pb2_dump.cpp | 90 ++++++ esphome/components/api/api_pb2_includes.h | 4 + esphome/components/api/api_pb2_service.cpp | 11 + esphome/components/api/api_pb2_service.h | 4 + esphome/components/api/api_server.cpp | 4 + esphome/components/api/api_server.h | 3 + esphome/components/api/list_entities.cpp | 3 + esphome/components/api/list_entities.h | 3 + esphome/components/api/subscribe_state.cpp | 3 + esphome/components/api/subscribe_state.h | 3 + esphome/components/water_heater/__init__.py | 111 +++++++ .../components/water_heater/water_heater.cpp | 281 ++++++++++++++++++ .../components/water_heater/water_heater.h | 259 ++++++++++++++++ .../components/web_server/list_entities.cpp | 7 + esphome/components/web_server/list_entities.h | 3 + esphome/const.py | 2 + esphome/core/application.h | 15 + esphome/core/component_iterator.cpp | 6 + esphome/core/component_iterator.h | 6 + esphome/core/controller.h | 6 + esphome/core/controller_registry.cpp | 4 + esphome/core/controller_registry.h | 10 + esphome/core/defines.h | 3 + 29 files changed, 1177 insertions(+) create mode 100644 esphome/components/water_heater/__init__.py create mode 100644 esphome/components/water_heater/water_heater.cpp create mode 100644 esphome/components/water_heater/water_heater.h diff --git a/CODEOWNERS b/CODEOWNERS index 21be3e36d1..941c2e2849 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -537,6 +537,7 @@ esphome/components/version/* @esphome/core esphome/components/voice_assistant/* @jesserockz @kahrendt esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 esphome/components/watchdog/* @oarcher +esphome/components/water_heater/* @dhoeben esphome/components/waveshare_epaper/* @clydebarrow esphome/components/web_server/ota/* @esphome/core esphome/components/web_server_base/* @esphome/core diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index bf39f0b14b..c351bc8c9c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1101,6 +1101,85 @@ message ClimateCommandRequest { uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"]; } +// ==================== WATER_HEATER ==================== +enum WaterHeaterMode { + WATER_HEATER_MODE_OFF = 0; + WATER_HEATER_MODE_ECO = 1; + WATER_HEATER_MODE_ELECTRIC = 2; + WATER_HEATER_MODE_PERFORMANCE = 3; + WATER_HEATER_MODE_HIGH_DEMAND = 4; + WATER_HEATER_MODE_HEAT_PUMP = 5; + WATER_HEATER_MODE_GAS = 6; +} + +message ListEntitiesWaterHeaterResponse { + option (id) = 132; + option (base_class) = "InfoResponseProtoMessage"; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_WATER_HEATER"; + + string object_id = 1; + fixed32 key = 2; + string name = 3; + string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"]; + bool disabled_by_default = 5; + EntityCategory entity_category = 6; + uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"]; + float min_temperature = 8; + float max_temperature = 9; + float target_temperature_step = 10; + repeated WaterHeaterMode supported_modes = 11 [(container_pointer_no_template) = "water_heater::WaterHeaterModeMask"]; + // Bitmask of WaterHeaterFeature flags + uint32 supported_features = 12; +} + +message WaterHeaterStateResponse { + option (id) = 133; + option (base_class) = "StateResponseProtoMessage"; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_WATER_HEATER"; + option (no_delay) = true; + + fixed32 key = 1; + float current_temperature = 2; + float target_temperature = 3; + WaterHeaterMode mode = 4; + uint32 device_id = 5 [(field_ifdef) = "USE_DEVICES"]; + // Bitmask of current state flags (bit 0 = away, bit 1 = on) + uint32 state = 6; + float target_temperature_low = 7; + float target_temperature_high = 8; +} + +// Bitmask for WaterHeaterCommandRequest.has_fields +enum WaterHeaterCommandHasField { + WATER_HEATER_COMMAND_HAS_NONE = 0; + WATER_HEATER_COMMAND_HAS_MODE = 1; + WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE = 2; + WATER_HEATER_COMMAND_HAS_STATE = 4; + WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8; + WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16; +} + +message WaterHeaterCommandRequest { + option (id) = 134; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_WATER_HEATER"; + option (no_delay) = true; + option (base_class) = "CommandProtoMessage"; + + fixed32 key = 1; + // Bitmask of which fields are set (see WaterHeaterCommandHasField) + uint32 has_fields = 2; + WaterHeaterMode mode = 3; + float target_temperature = 4; + uint32 device_id = 5 [(field_ifdef) = "USE_DEVICES"]; + // State flags bitmask (bit 0 = away, bit 1 = on) + uint32 state = 6; + float target_temperature_low = 7; + float target_temperature_high = 8; +} + // ==================== NUMBER ==================== enum NumberMode { NUMBER_MODE_AUTO = 0; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 1bcb90b0b0..28970a321c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -42,6 +42,9 @@ #ifdef USE_ZWAVE_PROXY #include "esphome/components/zwave_proxy/zwave_proxy.h" #endif +#ifdef USE_WATER_HEATER +#include "esphome/components/water_heater/water_heater.h" +#endif namespace esphome::api { @@ -1306,6 +1309,57 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe } #endif +#ifdef USE_WATER_HEATER +bool APIConnection::send_water_heater_state(water_heater::WaterHeater *water_heater) { + return this->send_message_smart_(water_heater, &APIConnection::try_send_water_heater_state, + WaterHeaterStateResponse::MESSAGE_TYPE, WaterHeaterStateResponse::ESTIMATED_SIZE); +} +uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, + bool is_single) { + auto *wh = static_cast(entity); + WaterHeaterStateResponse resp; + resp.mode = static_cast(wh->get_mode()); + resp.current_temperature = wh->get_current_temperature(); + resp.target_temperature = wh->get_target_temperature(); + resp.target_temperature_low = wh->get_target_temperature_low(); + resp.target_temperature_high = wh->get_target_temperature_high(); + resp.state = wh->get_state(); + resp.key = wh->get_object_id_hash(); + + return encode_message_to_buffer(resp, WaterHeaterStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); +} +uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, + bool is_single) { + auto *wh = static_cast(entity); + ListEntitiesWaterHeaterResponse msg; + auto traits = wh->get_traits(); + msg.min_temperature = traits.get_min_temperature(); + msg.max_temperature = traits.get_max_temperature(); + msg.target_temperature_step = traits.get_target_temperature_step(); + msg.supported_modes = &traits.get_supported_modes(); + msg.supported_features = traits.get_feature_flags(); + return fill_and_encode_entity_info(wh, msg, ListEntitiesWaterHeaterResponse::MESSAGE_TYPE, conn, remaining_size, + is_single); +} + +void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) { + ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater) + if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE) + call.set_mode(static_cast(msg.mode)); + if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE) + call.set_target_temperature(msg.target_temperature); + if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW) + call.set_target_temperature_low(msg.target_temperature_low); + if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH) + call.set_target_temperature_high(msg.target_temperature_high); + if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_STATE) { + call.set_away((msg.state & water_heater::WATER_HEATER_STATE_AWAY) != 0); + call.set_on((msg.state & water_heater::WATER_HEATER_STATE_ON) != 0); + } + call.perform(); +} +#endif + #ifdef USE_EVENT void APIConnection::send_event(event::Event *event, const char *event_type) { this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE, diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index b50be5d0d4..7351b5082f 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -176,6 +176,11 @@ class APIConnection final : public APIServerConnection { void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; #endif +#ifdef USE_WATER_HEATER + bool send_water_heater_state(water_heater::WaterHeater *water_heater); + void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override; +#endif + #ifdef USE_EVENT void send_event(event::Event *event, const char *event_type); #endif @@ -456,6 +461,12 @@ class APIConnection final : public APIServerConnection { static uint16_t try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single); #endif +#ifdef USE_WATER_HEATER + static uint16_t try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, + bool is_single); + static uint16_t try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, + bool is_single); +#endif #ifdef USE_EVENT static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn, uint32_t remaining_size, bool is_single); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 6a2d902f8f..3376b022c5 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1447,6 +1447,114 @@ bool ClimateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return true; } #endif +#ifdef USE_WATER_HEATER +void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->object_id_ref_); + buffer.encode_fixed32(2, this->key); + buffer.encode_string(3, this->name_ref_); +#ifdef USE_ENTITY_ICON + buffer.encode_string(4, this->icon_ref_); +#endif + buffer.encode_bool(5, this->disabled_by_default); + buffer.encode_uint32(6, static_cast(this->entity_category)); +#ifdef USE_DEVICES + buffer.encode_uint32(7, this->device_id); +#endif + buffer.encode_float(8, this->min_temperature); + buffer.encode_float(9, this->max_temperature); + buffer.encode_float(10, this->target_temperature_step); + for (const auto &it : *this->supported_modes) { + buffer.encode_uint32(11, static_cast(it), true); + } + buffer.encode_uint32(12, this->supported_features); +} +void ListEntitiesWaterHeaterResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); +#ifdef USE_ENTITY_ICON + size.add_length(1, this->icon_ref_.size()); +#endif + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); +#ifdef USE_DEVICES + size.add_uint32(1, this->device_id); +#endif + size.add_float(1, this->min_temperature); + size.add_float(1, this->max_temperature); + size.add_float(1, this->target_temperature_step); + if (!this->supported_modes->empty()) { + for (const auto &it : *this->supported_modes) { + size.add_uint32_force(1, static_cast(it)); + } + } + size.add_uint32(1, this->supported_features); +} +void WaterHeaterStateResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_float(2, this->current_temperature); + buffer.encode_float(3, this->target_temperature); + buffer.encode_uint32(4, static_cast(this->mode)); +#ifdef USE_DEVICES + buffer.encode_uint32(5, this->device_id); +#endif + buffer.encode_uint32(6, this->state); + buffer.encode_float(7, this->target_temperature_low); + buffer.encode_float(8, this->target_temperature_high); +} +void WaterHeaterStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_float(1, this->current_temperature); + size.add_float(1, this->target_temperature); + size.add_uint32(1, static_cast(this->mode)); +#ifdef USE_DEVICES + size.add_uint32(1, this->device_id); +#endif + size.add_uint32(1, this->state); + size.add_float(1, this->target_temperature_low); + size.add_float(1, this->target_temperature_high); +} +bool WaterHeaterCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: + this->has_fields = value.as_uint32(); + break; + case 3: + this->mode = static_cast(value.as_uint32()); + break; +#ifdef USE_DEVICES + case 5: + this->device_id = value.as_uint32(); + break; +#endif + case 6: + this->state = value.as_uint32(); + break; + default: + return false; + } + return true; +} +bool WaterHeaterCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: + this->key = value.as_fixed32(); + break; + case 4: + this->target_temperature = value.as_float(); + break; + case 7: + this->target_temperature_low = value.as_float(); + break; + case 8: + this->target_temperature_high = value.as_float(); + break; + default: + return false; + } + return true; +} +#endif #ifdef USE_NUMBER void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id_ref_); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 22deb19be8..2111c2a895 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -129,6 +129,25 @@ enum ClimatePreset : uint32_t { CLIMATE_PRESET_ACTIVITY = 7, }; #endif +#ifdef USE_WATER_HEATER +enum WaterHeaterMode : uint32_t { + WATER_HEATER_MODE_OFF = 0, + WATER_HEATER_MODE_ECO = 1, + WATER_HEATER_MODE_ELECTRIC = 2, + WATER_HEATER_MODE_PERFORMANCE = 3, + WATER_HEATER_MODE_HIGH_DEMAND = 4, + WATER_HEATER_MODE_HEAT_PUMP = 5, + WATER_HEATER_MODE_GAS = 6, +}; +#endif +enum WaterHeaterCommandHasField : uint32_t { + WATER_HEATER_COMMAND_HAS_NONE = 0, + WATER_HEATER_COMMAND_HAS_MODE = 1, + WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE = 2, + WATER_HEATER_COMMAND_HAS_STATE = 4, + WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8, + WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16, +}; #ifdef USE_NUMBER enum NumberMode : uint32_t { NUMBER_MODE_AUTO = 0, @@ -1516,6 +1535,70 @@ class ClimateCommandRequest final : public CommandProtoMessage { bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; #endif +#ifdef USE_WATER_HEATER +class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 132; + static constexpr uint8_t ESTIMATED_SIZE = 63; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "list_entities_water_heater_response"; } +#endif + float min_temperature{0.0f}; + float max_temperature{0.0f}; + float target_temperature_step{0.0f}; + const water_heater::WaterHeaterModeMask *supported_modes{}; + uint32_t supported_features{0}; + void encode(ProtoWriteBuffer buffer) const override; + void calculate_size(ProtoSize &size) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: +}; +class WaterHeaterStateResponse final : public StateResponseProtoMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 133; + static constexpr uint8_t ESTIMATED_SIZE = 35; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "water_heater_state_response"; } +#endif + float current_temperature{0.0f}; + float target_temperature{0.0f}; + enums::WaterHeaterMode mode{}; + uint32_t state{0}; + float target_temperature_low{0.0f}; + float target_temperature_high{0.0f}; + void encode(ProtoWriteBuffer buffer) const override; + void calculate_size(ProtoSize &size) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: +}; +class WaterHeaterCommandRequest final : public CommandProtoMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 134; + static constexpr uint8_t ESTIMATED_SIZE = 34; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "water_heater_command_request"; } +#endif + uint32_t has_fields{0}; + enums::WaterHeaterMode mode{}; + float target_temperature{0.0f}; + uint32_t state{0}; + float target_temperature_low{0.0f}; + float target_temperature_high{0.0f}; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +#endif #ifdef USE_NUMBER class ListEntitiesNumberResponse final : public InfoResponseProtoMessage { public: diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 7815eb73e4..9faf39e29e 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -348,6 +348,47 @@ template<> const char *proto_enum_to_string(enums::Climate } } #endif +#ifdef USE_WATER_HEATER +template<> const char *proto_enum_to_string(enums::WaterHeaterMode value) { + switch (value) { + case enums::WATER_HEATER_MODE_OFF: + return "WATER_HEATER_MODE_OFF"; + case enums::WATER_HEATER_MODE_ECO: + return "WATER_HEATER_MODE_ECO"; + case enums::WATER_HEATER_MODE_ELECTRIC: + return "WATER_HEATER_MODE_ELECTRIC"; + case enums::WATER_HEATER_MODE_PERFORMANCE: + return "WATER_HEATER_MODE_PERFORMANCE"; + case enums::WATER_HEATER_MODE_HIGH_DEMAND: + return "WATER_HEATER_MODE_HIGH_DEMAND"; + case enums::WATER_HEATER_MODE_HEAT_PUMP: + return "WATER_HEATER_MODE_HEAT_PUMP"; + case enums::WATER_HEATER_MODE_GAS: + return "WATER_HEATER_MODE_GAS"; + default: + return "UNKNOWN"; + } +} +#endif +template<> +const char *proto_enum_to_string(enums::WaterHeaterCommandHasField value) { + switch (value) { + case enums::WATER_HEATER_COMMAND_HAS_NONE: + return "WATER_HEATER_COMMAND_HAS_NONE"; + case enums::WATER_HEATER_COMMAND_HAS_MODE: + return "WATER_HEATER_COMMAND_HAS_MODE"; + case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE: + return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE"; + case enums::WATER_HEATER_COMMAND_HAS_STATE: + return "WATER_HEATER_COMMAND_HAS_STATE"; + case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW: + return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW"; + case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH: + return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH"; + default: + return "UNKNOWN"; + } +} #ifdef USE_NUMBER template<> const char *proto_enum_to_string(enums::NumberMode value) { switch (value) { @@ -1398,6 +1439,55 @@ void ClimateCommandRequest::dump_to(std::string &out) const { #endif } #endif +#ifdef USE_WATER_HEATER +void ListEntitiesWaterHeaterResponse::dump_to(std::string &out) const { + MessageDumpHelper helper(out, "ListEntitiesWaterHeaterResponse"); + dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "key", this->key); + dump_field(out, "name", this->name_ref_); +#ifdef USE_ENTITY_ICON + dump_field(out, "icon", this->icon_ref_); +#endif + dump_field(out, "disabled_by_default", this->disabled_by_default); + dump_field(out, "entity_category", static_cast(this->entity_category)); +#ifdef USE_DEVICES + dump_field(out, "device_id", this->device_id); +#endif + dump_field(out, "min_temperature", this->min_temperature); + dump_field(out, "max_temperature", this->max_temperature); + dump_field(out, "target_temperature_step", this->target_temperature_step); + for (const auto &it : *this->supported_modes) { + dump_field(out, "supported_modes", static_cast(it), 4); + } + dump_field(out, "supported_features", this->supported_features); +} +void WaterHeaterStateResponse::dump_to(std::string &out) const { + MessageDumpHelper helper(out, "WaterHeaterStateResponse"); + dump_field(out, "key", this->key); + dump_field(out, "current_temperature", this->current_temperature); + dump_field(out, "target_temperature", this->target_temperature); + dump_field(out, "mode", static_cast(this->mode)); +#ifdef USE_DEVICES + dump_field(out, "device_id", this->device_id); +#endif + dump_field(out, "state", this->state); + dump_field(out, "target_temperature_low", this->target_temperature_low); + dump_field(out, "target_temperature_high", this->target_temperature_high); +} +void WaterHeaterCommandRequest::dump_to(std::string &out) const { + MessageDumpHelper helper(out, "WaterHeaterCommandRequest"); + dump_field(out, "key", this->key); + dump_field(out, "has_fields", this->has_fields); + dump_field(out, "mode", static_cast(this->mode)); + dump_field(out, "target_temperature", this->target_temperature); +#ifdef USE_DEVICES + dump_field(out, "device_id", this->device_id); +#endif + dump_field(out, "state", this->state); + dump_field(out, "target_temperature_low", this->target_temperature_low); + dump_field(out, "target_temperature_high", this->target_temperature_high); +} +#endif #ifdef USE_NUMBER void ListEntitiesNumberResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesNumberResponse"); diff --git a/esphome/components/api/api_pb2_includes.h b/esphome/components/api/api_pb2_includes.h index 55d95304b1..f45e091c6f 100644 --- a/esphome/components/api/api_pb2_includes.h +++ b/esphome/components/api/api_pb2_includes.h @@ -10,6 +10,10 @@ #include "esphome/components/climate/climate_traits.h" #endif +#ifdef USE_WATER_HEATER +#include "esphome/components/water_heater/water_heater.h" +#endif + #ifdef USE_LIGHT #include "esphome/components/light/light_traits.h" #endif diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 45f6ecd30e..984cb0bb6e 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -621,6 +621,17 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, this->on_homeassistant_action_response(msg); break; } +#endif +#ifdef USE_WATER_HEATER + case WaterHeaterCommandRequest::MESSAGE_TYPE: { + WaterHeaterCommandRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_water_heater_command_request: %s", msg.dump().c_str()); +#endif + this->on_water_heater_command_request(msg); + break; + } #endif default: break; diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 6d94046a23..261d9fbd27 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -91,6 +91,10 @@ class APIServerConnectionBase : public ProtoService { virtual void on_climate_command_request(const ClimateCommandRequest &value){}; #endif +#ifdef USE_WATER_HEATER + virtual void on_water_heater_command_request(const WaterHeaterCommandRequest &value){}; +#endif + #ifdef USE_NUMBER virtual void on_number_command_request(const NumberCommandRequest &value){}; #endif diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index b1a5ee5d57..7a03d8f8ad 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -335,6 +335,10 @@ API_DISPATCH_UPDATE(valve::Valve, valve) API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player) #endif +#ifdef USE_WATER_HEATER +API_DISPATCH_UPDATE(water_heater::WaterHeater, water_heater) +#endif + #ifdef USE_EVENT // Event is a special case - unlike other entities with simple state fields, // events store their state in a member accessed via obj->get_last_event_type() diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index ad7d8bf63d..96c56fd08a 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -133,6 +133,9 @@ class APIServer : public Component, #ifdef USE_MEDIA_PLAYER void on_media_player_update(media_player::MediaPlayer *obj) override; #endif +#ifdef USE_WATER_HEATER + void on_water_heater_update(water_heater::WaterHeater *obj) override; +#endif #ifdef USE_API_HOMEASSISTANT_SERVICES void send_homeassistant_action(const HomeassistantActionRequest &call); diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index b4d1454153..2470899c93 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -73,6 +73,9 @@ LIST_ENTITIES_HANDLER(media_player, media_player::MediaPlayer, ListEntitiesMedia LIST_ENTITIES_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel, ListEntitiesAlarmControlPanelResponse) #endif +#ifdef USE_WATER_HEATER +LIST_ENTITIES_HANDLER(water_heater, water_heater::WaterHeater, ListEntitiesWaterHeaterResponse) +#endif #ifdef USE_EVENT LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse) #endif diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index 4c90dbbad8..04e6525eb0 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -82,6 +82,9 @@ class ListEntitiesIterator : public ComponentIterator { #ifdef USE_ALARM_CONTROL_PANEL bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override; #endif +#ifdef USE_WATER_HEATER + bool on_water_heater(water_heater::WaterHeater *entity) override; +#endif #ifdef USE_EVENT bool on_event(event::Event *entity) override; #endif diff --git a/esphome/components/api/subscribe_state.cpp b/esphome/components/api/subscribe_state.cpp index 3a563f2221..4bbc17018e 100644 --- a/esphome/components/api/subscribe_state.cpp +++ b/esphome/components/api/subscribe_state.cpp @@ -60,6 +60,9 @@ INITIAL_STATE_HANDLER(media_player, media_player::MediaPlayer) #ifdef USE_ALARM_CONTROL_PANEL INITIAL_STATE_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel) #endif +#ifdef USE_WATER_HEATER +INITIAL_STATE_HANDLER(water_heater, water_heater::WaterHeater) +#endif #ifdef USE_UPDATE INITIAL_STATE_HANDLER(update, update::UpdateEntity) #endif diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index 2c22c322ec..9230000ace 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -76,6 +76,9 @@ class InitialStateIterator : public ComponentIterator { #ifdef USE_ALARM_CONTROL_PANEL bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override; #endif +#ifdef USE_WATER_HEATER + bool on_water_heater(water_heater::WaterHeater *entity) override; +#endif #ifdef USE_EVENT bool on_event(event::Event *event) override { return true; }; #endif diff --git a/esphome/components/water_heater/__init__.py b/esphome/components/water_heater/__init__.py new file mode 100644 index 0000000000..5420e7c435 --- /dev/null +++ b/esphome/components/water_heater/__init__.py @@ -0,0 +1,111 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_MAX_TEMPERATURE, + CONF_MIN_TEMPERATURE, + CONF_VISUAL, +) +from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity +from esphome.cpp_generator import MockObjClass +from esphome.types import ConfigType + +CODEOWNERS = ["@dhoeben"] + +IS_PLATFORM_COMPONENT = True + +water_heater_ns = cg.esphome_ns.namespace("water_heater") +WaterHeater = water_heater_ns.class_("WaterHeater", cg.EntityBase, cg.Component) +WaterHeaterCall = water_heater_ns.class_("WaterHeaterCall") +WaterHeaterTraits = water_heater_ns.class_("WaterHeaterTraits") + +CONF_TARGET_TEMPERATURE_STEP = "target_temperature_step" + +WaterHeaterMode = water_heater_ns.enum("WaterHeaterMode") +WATER_HEATER_MODES = { + "OFF": WaterHeaterMode.WATER_HEATER_MODE_OFF, + "ECO": WaterHeaterMode.WATER_HEATER_MODE_ECO, + "ELECTRIC": WaterHeaterMode.WATER_HEATER_MODE_ELECTRIC, + "PERFORMANCE": WaterHeaterMode.WATER_HEATER_MODE_PERFORMANCE, + "HIGH_DEMAND": WaterHeaterMode.WATER_HEATER_MODE_HIGH_DEMAND, + "HEAT_PUMP": WaterHeaterMode.WATER_HEATER_MODE_HEAT_PUMP, + "GAS": WaterHeaterMode.WATER_HEATER_MODE_GAS, +} +validate_water_heater_mode = cv.enum(WATER_HEATER_MODES, upper=True) + +_WATER_HEATER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( + { + cv.Optional(CONF_VISUAL, default={}): cv.Schema( + { + cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, + cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, + cv.Optional(CONF_TARGET_TEMPERATURE_STEP): cv.float_, + } + ), + } +).extend(cv.COMPONENT_SCHEMA) + +_WATER_HEATER_SCHEMA.add_extra(entity_duplicate_validator("water_heater")) + + +def water_heater_schema( + class_: MockObjClass, + *, + icon: str = cv.UNDEFINED, + entity_category: str = cv.UNDEFINED, +) -> cv.Schema: + schema = {cv.GenerateID(): cv.declare_id(class_)} + + for key, default, validator in [ + (CONF_ICON, icon, cv.icon), + (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), + ]: + if default is not cv.UNDEFINED: + schema[cv.Optional(key, default=default)] = validator + + return _WATER_HEATER_SCHEMA.extend(schema) + + +async def setup_water_heater_core_(var: cg.Pvariable, config: ConfigType) -> None: + """Set up the core water heater properties in C++.""" + await setup_entity(var, config, "water_heater") + + visual = config[CONF_VISUAL] + if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None: + cg.add_define("USE_WATER_HEATER_VISUAL_OVERRIDES") + cg.add(var.set_visual_min_temperature_override(min_temp)) + if (max_temp := visual.get(CONF_MAX_TEMPERATURE)) is not None: + cg.add_define("USE_WATER_HEATER_VISUAL_OVERRIDES") + cg.add(var.set_visual_max_temperature_override(max_temp)) + if (temp_step := visual.get(CONF_TARGET_TEMPERATURE_STEP)) is not None: + cg.add_define("USE_WATER_HEATER_VISUAL_OVERRIDES") + cg.add(var.set_visual_target_temperature_step_override(temp_step)) + + +async def register_water_heater(var: cg.Pvariable, config: ConfigType) -> cg.Pvariable: + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + + cg.add_define("USE_WATER_HEATER") + + await cg.register_component(var, config) + + cg.add(cg.App.register_water_heater(var)) + + CORE.register_platform_component("water_heater", var) + await setup_water_heater_core_(var, config) + return var + + +async def new_water_heater(config: ConfigType, *args) -> cg.Pvariable: + var = cg.new_Pvariable(config[CONF_ID], *args) + await register_water_heater(var, config) + return var + + +@coroutine_with_priority(CoroPriority.CORE) +async def to_code(config: ConfigType) -> None: + cg.add_global(water_heater_ns.using) diff --git a/esphome/components/water_heater/water_heater.cpp b/esphome/components/water_heater/water_heater.cpp new file mode 100644 index 0000000000..441872ec00 --- /dev/null +++ b/esphome/components/water_heater/water_heater.cpp @@ -0,0 +1,281 @@ +#include "water_heater.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/core/controller_registry.h" + +#include + +namespace esphome::water_heater { + +static const char *const TAG = "water_heater"; + +void log_water_heater(const char *tag, const char *prefix, const char *type, WaterHeater *obj) { + if (obj != nullptr) { + ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str()); + } +} + +WaterHeaterCall::WaterHeaterCall(WaterHeater *parent) : parent_(parent) {} + +WaterHeaterCall &WaterHeaterCall::set_mode(WaterHeaterMode mode) { + this->mode_ = mode; + return *this; +} + +WaterHeaterCall &WaterHeaterCall::set_mode(const std::string &mode) { + if (str_equals_case_insensitive(mode, "OFF")) { + this->set_mode(WATER_HEATER_MODE_OFF); + } else if (str_equals_case_insensitive(mode, "ECO")) { + this->set_mode(WATER_HEATER_MODE_ECO); + } else if (str_equals_case_insensitive(mode, "ELECTRIC")) { + this->set_mode(WATER_HEATER_MODE_ELECTRIC); + } else if (str_equals_case_insensitive(mode, "PERFORMANCE")) { + this->set_mode(WATER_HEATER_MODE_PERFORMANCE); + } else if (str_equals_case_insensitive(mode, "HIGH_DEMAND")) { + this->set_mode(WATER_HEATER_MODE_HIGH_DEMAND); + } else if (str_equals_case_insensitive(mode, "HEAT_PUMP")) { + this->set_mode(WATER_HEATER_MODE_HEAT_PUMP); + } else if (str_equals_case_insensitive(mode, "GAS")) { + this->set_mode(WATER_HEATER_MODE_GAS); + } else { + ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str()); + } + return *this; +} + +WaterHeaterCall &WaterHeaterCall::set_target_temperature(float temperature) { + this->target_temperature_ = temperature; + return *this; +} + +WaterHeaterCall &WaterHeaterCall::set_target_temperature_low(float temperature) { + this->target_temperature_low_ = temperature; + return *this; +} + +WaterHeaterCall &WaterHeaterCall::set_target_temperature_high(float temperature) { + this->target_temperature_high_ = temperature; + return *this; +} + +WaterHeaterCall &WaterHeaterCall::set_away(bool away) { + if (away) { + this->state_ |= WATER_HEATER_STATE_AWAY; + } else { + this->state_ &= ~WATER_HEATER_STATE_AWAY; + } + return *this; +} + +WaterHeaterCall &WaterHeaterCall::set_on(bool on) { + if (on) { + this->state_ |= WATER_HEATER_STATE_ON; + } else { + this->state_ &= ~WATER_HEATER_STATE_ON; + } + return *this; +} + +void WaterHeaterCall::perform() { + ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); + this->validate_(); + if (this->mode_.has_value()) { + ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(water_heater_mode_to_string(*this->mode_))); + } + if (!std::isnan(this->target_temperature_)) { + ESP_LOGD(TAG, " Target Temperature: %.2f", this->target_temperature_); + } + if (!std::isnan(this->target_temperature_low_)) { + ESP_LOGD(TAG, " Target Temperature Low: %.2f", this->target_temperature_low_); + } + if (!std::isnan(this->target_temperature_high_)) { + ESP_LOGD(TAG, " Target Temperature High: %.2f", this->target_temperature_high_); + } + if (this->state_ & WATER_HEATER_STATE_AWAY) { + ESP_LOGD(TAG, " Away: YES"); + } + if (this->state_ & WATER_HEATER_STATE_ON) { + ESP_LOGD(TAG, " On: YES"); + } + this->parent_->control(*this); +} + +void WaterHeaterCall::validate_() { + auto traits = this->parent_->get_traits(); + if (this->mode_.has_value()) { + if (!traits.supports_mode(*this->mode_)) { + ESP_LOGW(TAG, "'%s' - Mode %d not supported", this->parent_->get_name().c_str(), *this->mode_); + this->mode_.reset(); + } + } + if (!std::isnan(this->target_temperature_)) { + if (traits.get_supports_two_point_target_temperature()) { + ESP_LOGW(TAG, "'%s' - Cannot set target temperature for device with two-point target temperature", + this->parent_->get_name().c_str()); + this->target_temperature_ = NAN; + } else if (this->target_temperature_ < traits.get_min_temperature() || + this->target_temperature_ > traits.get_max_temperature()) { + ESP_LOGW(TAG, "'%s' - Target temperature %.1f is out of range [%.1f - %.1f]", this->parent_->get_name().c_str(), + this->target_temperature_, traits.get_min_temperature(), traits.get_max_temperature()); + this->target_temperature_ = + std::max(traits.get_min_temperature(), std::min(this->target_temperature_, traits.get_max_temperature())); + } + } + if (!std::isnan(this->target_temperature_low_) || !std::isnan(this->target_temperature_high_)) { + if (!traits.get_supports_two_point_target_temperature()) { + ESP_LOGW(TAG, "'%s' - Cannot set low/high target temperature", this->parent_->get_name().c_str()); + this->target_temperature_low_ = NAN; + this->target_temperature_high_ = NAN; + } + } + if (!std::isnan(this->target_temperature_low_) && !std::isnan(this->target_temperature_high_)) { + if (this->target_temperature_low_ > this->target_temperature_high_) { + ESP_LOGW(TAG, "'%s' - Target temperature low %.2f must be less than high %.2f", this->parent_->get_name().c_str(), + this->target_temperature_low_, this->target_temperature_high_); + this->target_temperature_low_ = NAN; + this->target_temperature_high_ = NAN; + } + } + if ((this->state_ & WATER_HEATER_STATE_AWAY) && !traits.get_supports_away_mode()) { + ESP_LOGW(TAG, "'%s' - Away mode not supported", this->parent_->get_name().c_str()); + this->state_ &= ~WATER_HEATER_STATE_AWAY; + } + // If ON/OFF not supported, device is always on - clear the flag silently + if (!traits.has_feature_flags(WATER_HEATER_SUPPORTS_ON_OFF)) { + this->state_ &= ~WATER_HEATER_STATE_ON; + } +} + +void WaterHeater::setup() { + this->pref_ = global_preferences->make_preference(this->get_preference_hash()); +} + +void WaterHeater::publish_state() { + auto traits = this->get_traits(); + ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str()); + ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(water_heater_mode_to_string(this->mode_))); + if (!std::isnan(this->current_temperature_)) { + ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature_); + } + if (traits.get_supports_two_point_target_temperature()) { + ESP_LOGD(TAG, " Target Temperature: Low: %.2f°C High: %.2f°C", this->target_temperature_low_, + this->target_temperature_high_); + } else if (!std::isnan(this->target_temperature_)) { + ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature_); + } + if (this->state_ & WATER_HEATER_STATE_AWAY) { + ESP_LOGD(TAG, " Away: YES"); + } + if (traits.has_feature_flags(WATER_HEATER_SUPPORTS_ON_OFF)) { + ESP_LOGD(TAG, " On: %s", (this->state_ & WATER_HEATER_STATE_ON) ? "YES" : "NO"); + } + +#if defined(USE_WATER_HEATER) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_water_heater_update(this); +#endif + + SavedWaterHeaterState saved{}; + saved.mode = this->mode_; + if (traits.get_supports_two_point_target_temperature()) { + saved.target_temperature_low = this->target_temperature_low_; + saved.target_temperature_high = this->target_temperature_high_; + } else { + saved.target_temperature = this->target_temperature_; + } + saved.state = this->state_; + this->pref_.save(&saved); +} + +optional WaterHeater::restore_state() { + SavedWaterHeaterState recovered{}; + if (!this->pref_.load(&recovered)) + return {}; + + auto traits = this->get_traits(); + auto call = this->make_call(); + call.set_mode(recovered.mode); + if (traits.get_supports_two_point_target_temperature()) { + call.set_target_temperature_low(recovered.target_temperature_low); + call.set_target_temperature_high(recovered.target_temperature_high); + } else { + call.set_target_temperature(recovered.target_temperature); + } + call.set_away((recovered.state & WATER_HEATER_STATE_AWAY) != 0); + call.set_on((recovered.state & WATER_HEATER_STATE_ON) != 0); + return call; +} + +WaterHeaterTraits WaterHeater::get_traits() { + auto traits = this->traits(); +#ifdef USE_WATER_HEATER_VISUAL_OVERRIDES + if (!std::isnan(this->visual_min_temperature_override_)) { + traits.set_min_temperature(this->visual_min_temperature_override_); + } + if (!std::isnan(this->visual_max_temperature_override_)) { + traits.set_max_temperature(this->visual_max_temperature_override_); + } + if (!std::isnan(this->visual_target_temperature_step_override_)) { + traits.set_target_temperature_step(this->visual_target_temperature_step_override_); + } +#endif + return traits; +} + +#ifdef USE_WATER_HEATER_VISUAL_OVERRIDES +void WaterHeater::set_visual_min_temperature_override(float min_temperature_override) { + this->visual_min_temperature_override_ = min_temperature_override; +} +void WaterHeater::set_visual_max_temperature_override(float max_temperature_override) { + this->visual_max_temperature_override_ = max_temperature_override; +} +void WaterHeater::set_visual_target_temperature_step_override(float visual_target_temperature_step_override) { + this->visual_target_temperature_step_override_ = visual_target_temperature_step_override; +} +#endif + +const LogString *water_heater_mode_to_string(WaterHeaterMode mode) { + switch (mode) { + case WATER_HEATER_MODE_OFF: + return LOG_STR("OFF"); + case WATER_HEATER_MODE_ECO: + return LOG_STR("ECO"); + case WATER_HEATER_MODE_ELECTRIC: + return LOG_STR("ELECTRIC"); + case WATER_HEATER_MODE_PERFORMANCE: + return LOG_STR("PERFORMANCE"); + case WATER_HEATER_MODE_HIGH_DEMAND: + return LOG_STR("HIGH_DEMAND"); + case WATER_HEATER_MODE_HEAT_PUMP: + return LOG_STR("HEAT_PUMP"); + case WATER_HEATER_MODE_GAS: + return LOG_STR("GAS"); + default: + return LOG_STR("UNKNOWN"); + } +} + +void WaterHeater::dump_traits_(const char *tag) { + auto traits = this->get_traits(); + ESP_LOGCONFIG(tag, + " Min Temperature: %.1f°C\n" + " Max Temperature: %.1f°C\n" + " Temperature Step: %.1f", + traits.get_min_temperature(), traits.get_max_temperature(), traits.get_target_temperature_step()); + if (traits.get_supports_two_point_target_temperature()) { + ESP_LOGCONFIG(tag, " Supports Two-Point Target Temperature: YES"); + } + if (traits.get_supports_away_mode()) { + ESP_LOGCONFIG(tag, " Supports Away Mode: YES"); + } + if (traits.has_feature_flags(WATER_HEATER_SUPPORTS_ON_OFF)) { + ESP_LOGCONFIG(tag, " Supports On/Off: YES"); + } + if (!traits.get_supported_modes().empty()) { + ESP_LOGCONFIG(tag, " Supported Modes:"); + for (WaterHeaterMode m : traits.get_supported_modes()) { + ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(water_heater_mode_to_string(m))); + } + } +} + +} // namespace esphome::water_heater diff --git a/esphome/components/water_heater/water_heater.h b/esphome/components/water_heater/water_heater.h new file mode 100644 index 0000000000..e223dd59b2 --- /dev/null +++ b/esphome/components/water_heater/water_heater.h @@ -0,0 +1,259 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/finite_set_mask.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/preferences.h" + +namespace esphome::water_heater { + +class WaterHeater; +struct WaterHeaterCallInternal; + +void log_water_heater(const char *tag, const char *prefix, const char *type, WaterHeater *obj); +#define LOG_WATER_HEATER(prefix, type, obj) log_water_heater(TAG, prefix, LOG_STR_LITERAL(type), obj) + +enum WaterHeaterMode : uint32_t { + WATER_HEATER_MODE_OFF = 0, + WATER_HEATER_MODE_ECO = 1, + WATER_HEATER_MODE_ELECTRIC = 2, + WATER_HEATER_MODE_PERFORMANCE = 3, + WATER_HEATER_MODE_HIGH_DEMAND = 4, + WATER_HEATER_MODE_HEAT_PUMP = 5, + WATER_HEATER_MODE_GAS = 6, +}; + +// Type alias for water heater mode bitmask +// Replaces std::set to eliminate red-black tree overhead +using WaterHeaterModeMask = + FiniteSetMask>; + +/// Feature flags for water heater capabilities (matches Home Assistant WaterHeaterEntityFeature) +enum WaterHeaterFeature : uint32_t { + /// The water heater supports reporting the current temperature. + WATER_HEATER_SUPPORTS_CURRENT_TEMPERATURE = 1 << 0, + /// The water heater supports a target temperature. + WATER_HEATER_SUPPORTS_TARGET_TEMPERATURE = 1 << 1, + /// The water heater supports operation mode selection. + WATER_HEATER_SUPPORTS_OPERATION_MODE = 1 << 2, + /// The water heater supports an away/vacation mode. + WATER_HEATER_SUPPORTS_AWAY_MODE = 1 << 3, + /// The water heater can be turned on/off. + WATER_HEATER_SUPPORTS_ON_OFF = 1 << 4, + /// The water heater supports two-point target temperature (low/high range). + WATER_HEATER_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE = 1 << 5, +}; + +/// State flags for water heater current state (bitmask) +enum WaterHeaterStateFlag : uint32_t { + /// Away/vacation mode is currently active + WATER_HEATER_STATE_AWAY = 1 << 0, + /// Water heater is on (not in standby) + WATER_HEATER_STATE_ON = 1 << 1, +}; + +struct SavedWaterHeaterState { + WaterHeaterMode mode; + union { + float target_temperature; + struct { + float target_temperature_low; + float target_temperature_high; + }; + } __attribute__((packed)); + uint32_t state; +} __attribute__((packed)); + +class WaterHeaterCall { + friend struct WaterHeaterCallInternal; + + public: + WaterHeaterCall() : parent_(nullptr) {} + + WaterHeaterCall(WaterHeater *parent); + + WaterHeaterCall &set_mode(WaterHeaterMode mode); + WaterHeaterCall &set_mode(const std::string &mode); + WaterHeaterCall &set_target_temperature(float temperature); + WaterHeaterCall &set_target_temperature_low(float temperature); + WaterHeaterCall &set_target_temperature_high(float temperature); + WaterHeaterCall &set_away(bool away); + WaterHeaterCall &set_on(bool on); + + void perform(); + + const optional &get_mode() const { return this->mode_; } + float get_target_temperature() const { return this->target_temperature_; } + float get_target_temperature_low() const { return this->target_temperature_low_; } + float get_target_temperature_high() const { return this->target_temperature_high_; } + /// Get state flags value + uint32_t get_state() const { return this->state_; } + + protected: + void validate_(); + WaterHeater *parent_; + optional mode_; + float target_temperature_{NAN}; + float target_temperature_low_{NAN}; + float target_temperature_high_{NAN}; + uint32_t state_{0}; +}; + +struct WaterHeaterCallInternal : public WaterHeaterCall { + WaterHeaterCallInternal(WaterHeater *parent) : WaterHeaterCall(parent) {} + + WaterHeaterCallInternal &set_from_restore(const WaterHeaterCall &restore) { + this->mode_ = restore.mode_; + this->target_temperature_ = restore.target_temperature_; + this->target_temperature_low_ = restore.target_temperature_low_; + this->target_temperature_high_ = restore.target_temperature_high_; + this->state_ = restore.state_; + return *this; + } +}; + +class WaterHeaterTraits { + public: + /// Get/set feature flags (see WaterHeaterFeature enum) + void add_feature_flags(uint32_t flags) { this->feature_flags_ |= flags; } + void clear_feature_flags(uint32_t flags) { this->feature_flags_ &= ~flags; } + bool has_feature_flags(uint32_t flags) const { return (this->feature_flags_ & flags) == flags; } + uint32_t get_feature_flags() const { return this->feature_flags_; } + + bool get_supports_current_temperature() const { + return this->has_feature_flags(WATER_HEATER_SUPPORTS_CURRENT_TEMPERATURE); + } + void set_supports_current_temperature(bool supports) { + if (supports) { + this->add_feature_flags(WATER_HEATER_SUPPORTS_CURRENT_TEMPERATURE); + } else { + this->clear_feature_flags(WATER_HEATER_SUPPORTS_CURRENT_TEMPERATURE); + } + } + + bool get_supports_away_mode() const { return this->has_feature_flags(WATER_HEATER_SUPPORTS_AWAY_MODE); } + void set_supports_away_mode(bool supports) { + if (supports) { + this->add_feature_flags(WATER_HEATER_SUPPORTS_AWAY_MODE); + } else { + this->clear_feature_flags(WATER_HEATER_SUPPORTS_AWAY_MODE); + } + } + + bool get_supports_two_point_target_temperature() const { + return this->has_feature_flags(WATER_HEATER_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE); + } + void set_supports_two_point_target_temperature(bool supports) { + if (supports) { + this->add_feature_flags(WATER_HEATER_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE); + } else { + this->clear_feature_flags(WATER_HEATER_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE); + } + } + + void set_min_temperature(float min_temperature) { this->min_temperature_ = min_temperature; } + float get_min_temperature() const { return this->min_temperature_; } + + void set_max_temperature(float max_temperature) { this->max_temperature_ = max_temperature; } + float get_max_temperature() const { return this->max_temperature_; } + + void set_target_temperature_step(float target_temperature_step) { + this->target_temperature_step_ = target_temperature_step; + } + float get_target_temperature_step() const { return this->target_temperature_step_; } + + void set_supported_modes(WaterHeaterModeMask modes) { this->supported_modes_ = modes; } + const WaterHeaterModeMask &get_supported_modes() const { return this->supported_modes_; } + bool supports_mode(WaterHeaterMode mode) const { return this->supported_modes_.count(mode); } + + protected: + // Ordered to minimize padding: 4-byte members first + uint32_t feature_flags_{0}; + float min_temperature_{0.0f}; + float max_temperature_{0.0f}; + float target_temperature_step_{0.0f}; + WaterHeaterModeMask supported_modes_; +}; + +class WaterHeater : public EntityBase, public Component { + public: + WaterHeaterMode get_mode() const { return this->mode_; } + float get_current_temperature() const { return this->current_temperature_; } + float get_target_temperature() const { return this->target_temperature_; } + float get_target_temperature_low() const { return this->target_temperature_low_; } + float get_target_temperature_high() const { return this->target_temperature_high_; } + /// Get the current state flags bitmask + uint32_t get_state() const { return this->state_; } + /// Check if away mode is currently active + bool is_away() const { return (this->state_ & WATER_HEATER_STATE_AWAY) != 0; } + /// Check if the water heater is on + bool is_on() const { return (this->state_ & WATER_HEATER_STATE_ON) != 0; } + + void set_current_temperature(float current_temperature) { this->current_temperature_ = current_temperature; } + + virtual void publish_state(); + virtual WaterHeaterTraits get_traits(); + virtual WaterHeaterCallInternal make_call() = 0; + +#ifdef USE_WATER_HEATER_VISUAL_OVERRIDES + void set_visual_min_temperature_override(float min_temperature_override); + void set_visual_max_temperature_override(float max_temperature_override); + void set_visual_target_temperature_step_override(float visual_target_temperature_step_override); +#endif + virtual void control(const WaterHeaterCall &call) = 0; + + void setup() override; + + optional restore_state(); + + protected: + virtual WaterHeaterTraits traits() = 0; + + /// Log the traits of this water heater for dump_config(). + void dump_traits_(const char *tag); + + /// Set the mode of the water heater. Should only be called from control(). + void set_mode_(WaterHeaterMode mode) { this->mode_ = mode; } + /// Set the target temperature of the water heater. Should only be called from control(). + void set_target_temperature_(float target_temperature) { this->target_temperature_ = target_temperature; } + /// Set the low target temperature (for two-point control). Should only be called from control(). + void set_target_temperature_low_(float target_temperature_low) { + this->target_temperature_low_ = target_temperature_low; + } + /// Set the high target temperature (for two-point control). Should only be called from control(). + void set_target_temperature_high_(float target_temperature_high) { + this->target_temperature_high_ = target_temperature_high; + } + /// Set the state flags. Should only be called from control(). + void set_state_(uint32_t state) { this->state_ = state; } + /// Set or clear a state flag. Should only be called from control(). + void set_state_flag_(uint32_t flag, bool value) { + if (value) { + this->state_ |= flag; + } else { + this->state_ &= ~flag; + } + } + + WaterHeaterMode mode_{WATER_HEATER_MODE_OFF}; + float current_temperature_{NAN}; + float target_temperature_{NAN}; + float target_temperature_low_{NAN}; + float target_temperature_high_{NAN}; + uint32_t state_{0}; // Bitmask of WaterHeaterStateFlag + +#ifdef USE_WATER_HEATER_VISUAL_OVERRIDES + float visual_min_temperature_override_{NAN}; + float visual_max_temperature_override_{NAN}; + float visual_target_temperature_step_override_{NAN}; +#endif + + ESPPreferenceObject pref_; +}; + +/// Convert the given WaterHeaterMode to a human-readable string for logging. +const LogString *water_heater_mode_to_string(WaterHeaterMode mode); + +} // namespace esphome::water_heater diff --git a/esphome/components/web_server/list_entities.cpp b/esphome/components/web_server/list_entities.cpp index 6b27545549..16b1d1e797 100644 --- a/esphome/components/web_server/list_entities.cpp +++ b/esphome/components/web_server/list_entities.cpp @@ -135,6 +135,13 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont } #endif +#ifdef USE_WATER_HEATER +bool ListEntitiesIterator::on_water_heater(water_heater::WaterHeater *obj) { + // Water heater web_server support not yet implemented - this stub acknowledges the entity + return true; +} +#endif + #ifdef USE_EVENT bool ListEntitiesIterator::on_event(event::Event *obj) { // Null event type, since we are just iterating over entities diff --git a/esphome/components/web_server/list_entities.h b/esphome/components/web_server/list_entities.h index 43e1cc2544..5d9049b082 100644 --- a/esphome/components/web_server/list_entities.h +++ b/esphome/components/web_server/list_entities.h @@ -79,6 +79,9 @@ class ListEntitiesIterator : public ComponentIterator { #ifdef USE_ALARM_CONTROL_PANEL bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *obj) override; #endif +#ifdef USE_WATER_HEATER + bool on_water_heater(water_heater::WaterHeater *obj) override; +#endif #ifdef USE_EVENT bool on_event(event::Event *obj) override; #endif diff --git a/esphome/const.py b/esphome/const.py index f43204fd9f..1d46e81f9d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1086,6 +1086,7 @@ CONF_WARM_WHITE = "warm_white" CONF_WARM_WHITE_COLOR_TEMPERATURE = "warm_white_color_temperature" CONF_WATCHDOG_THRESHOLD = "watchdog_threshold" CONF_WATCHDOG_TIMEOUT = "watchdog_timeout" +CONF_WATER_HEATER = "water_heater" CONF_WEB_SERVER = "web_server" CONF_WEB_SERVER_ID = "web_server_id" CONF_WEIGHT = "weight" @@ -1179,6 +1180,7 @@ ICON_TIMELAPSE = "mdi:timelapse" ICON_TIMER = "mdi:timer-outline" ICON_VIBRATE = "mdi:vibrate" ICON_WATER = "mdi:water" +ICON_WATER_HEATER = "mdi:water-boiler" ICON_WATER_PERCENT = "mdi:water-percent" ICON_WEATHER_SUNSET = "mdi:weather-sunset" ICON_WEATHER_SUNSET_DOWN = "mdi:weather-sunset-down" diff --git a/esphome/core/application.h b/esphome/core/application.h index f462553a81..d2146a6c16 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -87,6 +87,9 @@ #ifdef USE_ALARM_CONTROL_PANEL #include "esphome/components/alarm_control_panel/alarm_control_panel.h" #endif +#ifdef USE_WATER_HEATER +#include "esphome/components/water_heater/water_heater.h" +#endif #ifdef USE_EVENT #include "esphome/components/event/event.h" #endif @@ -217,6 +220,10 @@ class Application { } #endif +#ifdef USE_WATER_HEATER + void register_water_heater(water_heater::WaterHeater *water_heater) { this->water_heaters_.push_back(water_heater); } +#endif + #ifdef USE_EVENT void register_event(event::Event *event) { this->events_.push_back(event); } #endif @@ -437,6 +444,11 @@ class Application { GET_ENTITY_METHOD(alarm_control_panel::AlarmControlPanel, alarm_control_panel, alarm_control_panels) #endif +#ifdef USE_WATER_HEATER + auto &get_water_heaters() const { return this->water_heaters_; } + GET_ENTITY_METHOD(water_heater::WaterHeater, water_heater, water_heaters) +#endif + #ifdef USE_EVENT auto &get_events() const { return this->events_; } GET_ENTITY_METHOD(event::Event, event, events) @@ -634,6 +646,9 @@ class Application { StaticVector alarm_control_panels_{}; #endif +#ifdef USE_WATER_HEATER + StaticVector water_heaters_{}; +#endif #ifdef USE_UPDATE StaticVector updates_{}; #endif diff --git a/esphome/core/component_iterator.cpp b/esphome/core/component_iterator.cpp index 8c6a7b95b5..4015d8ec60 100644 --- a/esphome/core/component_iterator.cpp +++ b/esphome/core/component_iterator.cpp @@ -163,6 +163,12 @@ void ComponentIterator::advance() { break; #endif +#ifdef USE_WATER_HEATER + case IteratorState::WATER_HEATER: + this->process_platform_item_(App.get_water_heaters(), &ComponentIterator::on_water_heater); + break; +#endif + #ifdef USE_EVENT case IteratorState::EVENT: this->process_platform_item_(App.get_events(), &ComponentIterator::on_event); diff --git a/esphome/core/component_iterator.h b/esphome/core/component_iterator.h index 1b1bd80ac5..37d1960601 100644 --- a/esphome/core/component_iterator.h +++ b/esphome/core/component_iterator.h @@ -84,6 +84,9 @@ class ComponentIterator { #ifdef USE_ALARM_CONTROL_PANEL virtual bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) = 0; #endif +#ifdef USE_WATER_HEATER + virtual bool on_water_heater(water_heater::WaterHeater *water_heater) = 0; +#endif #ifdef USE_EVENT virtual bool on_event(event::Event *event) = 0; #endif @@ -161,6 +164,9 @@ class ComponentIterator { #ifdef USE_ALARM_CONTROL_PANEL ALARM_CONTROL_PANEL, #endif +#ifdef USE_WATER_HEATER + WATER_HEATER, +#endif #ifdef USE_EVENT EVENT, #endif diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 697017217d..632b46c893 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -58,6 +58,9 @@ #ifdef USE_ALARM_CONTROL_PANEL #include "esphome/components/alarm_control_panel/alarm_control_panel.h" #endif +#ifdef USE_WATER_HEATER +#include "esphome/components/water_heater/water_heater.h" +#endif #ifdef USE_EVENT #include "esphome/components/event/event.h" #endif @@ -123,6 +126,9 @@ class Controller { #ifdef USE_ALARM_CONTROL_PANEL virtual void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj){}; #endif +#ifdef USE_WATER_HEATER + virtual void on_water_heater_update(water_heater::WaterHeater *obj){}; +#endif #ifdef USE_EVENT virtual void on_event(event::Event *obj){}; #endif diff --git a/esphome/core/controller_registry.cpp b/esphome/core/controller_registry.cpp index 0a84bb0d0d..13b505e8e9 100644 --- a/esphome/core/controller_registry.cpp +++ b/esphome/core/controller_registry.cpp @@ -98,6 +98,10 @@ CONTROLLER_REGISTRY_NOTIFY(media_player::MediaPlayer, media_player) CONTROLLER_REGISTRY_NOTIFY(alarm_control_panel::AlarmControlPanel, alarm_control_panel) #endif +#ifdef USE_WATER_HEATER +CONTROLLER_REGISTRY_NOTIFY(water_heater::WaterHeater, water_heater) +#endif + #ifdef USE_EVENT CONTROLLER_REGISTRY_NOTIFY_NO_UPDATE_SUFFIX(event::Event, event) #endif diff --git a/esphome/core/controller_registry.h b/esphome/core/controller_registry.h index 640a276a0a..d6452d8827 100644 --- a/esphome/core/controller_registry.h +++ b/esphome/core/controller_registry.h @@ -119,6 +119,12 @@ class AlarmControlPanel; } #endif +#ifdef USE_WATER_HEATER +namespace water_heater { +class WaterHeater; +} +#endif + #ifdef USE_EVENT namespace event { class Event; @@ -228,6 +234,10 @@ class ControllerRegistry { static void notify_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj); #endif +#ifdef USE_WATER_HEATER + static void notify_water_heater_update(water_heater::WaterHeater *obj); +#endif + #ifdef USE_EVENT static void notify_event(event::Event *obj); #endif diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 0c12b29eb7..11c5062140 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -113,6 +113,8 @@ #define USE_UART_WAKE_LOOP_ON_RX #define USE_UPDATE #define USE_VALVE +#define USE_WATER_HEATER +#define USE_WATER_HEATER_VISUAL_OVERRIDES #define USE_ZWAVE_PROXY // Feature flags which do not work for zephyr @@ -337,3 +339,4 @@ #define ESPHOME_ENTITY_TIME_COUNT 1 #define ESPHOME_ENTITY_UPDATE_COUNT 1 #define ESPHOME_ENTITY_VALVE_COUNT 1 +#define ESPHOME_ENTITY_WATER_HEATER_COUNT 1 From 0d993691d477c8f6dc3e9be8129307097032eaed Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Dec 2025 17:59:30 -1000 Subject: [PATCH 0753/1145] [logger] RP2040: Use write() with known length instead of println() (#12615) --- esphome/components/logger/logger.h | 4 ++-- esphome/components/logger/logger_rp2040.cpp | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 8abc1196e1..36195b919a 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -118,11 +118,11 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128; static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; // Platform-specific: does write_msg_ add its own newline? -// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, LibreTiny) +// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, RP2040, LibreTiny) // Allows single write call with newline included for efficiency // true: write_msg_ adds newline itself via puts()/println() (other platforms) // Newline should NOT be added to buffer -#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) static constexpr bool WRITE_MSG_ADDS_NEWLINE = false; #else static constexpr bool WRITE_MSG_ADDS_NEWLINE = true; diff --git a/esphome/components/logger/logger_rp2040.cpp b/esphome/components/logger/logger_rp2040.cpp index 4a8535c8e4..be8252f56a 100644 --- a/esphome/components/logger/logger_rp2040.cpp +++ b/esphome/components/logger/logger_rp2040.cpp @@ -27,7 +27,10 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg, size_t) { this->hw_serial_->println(msg); } +void HOT Logger::write_msg_(const char *msg, size_t len) { + // Single write with newline already in buffer (added by caller) + this->hw_serial_->write(msg, len); +} const LogString *Logger::get_uart_selection_() { switch (this->uart_) { From 52eb08f48fe61066509ed831c3800f3da442eb9e Mon Sep 17 00:00:00 2001 From: Clint Armstrong Date: Mon, 22 Dec 2025 00:52:17 -0500 Subject: [PATCH 0754/1145] [thermostat] Enhance timer behavior for immediate response to duration changes (#12610) --- .../thermostat/thermostat_climate.cpp | 59 ++++++++++++------- .../thermostat/thermostat_climate.h | 2 + 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index e79eed4055..e588407c4a 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -1330,45 +1330,64 @@ void ThermostatClimate::set_heat_deadband(float deadband) { this->heating_deadba void ThermostatClimate::set_heat_overrun(float overrun) { this->heating_overrun_ = overrun; } void ThermostatClimate::set_supplemental_cool_delta(float delta) { this->supplemental_cool_delta_ = delta; } void ThermostatClimate::set_supplemental_heat_delta(float delta) { this->supplemental_heat_delta_ = delta; } + +void ThermostatClimate::set_timer_duration_in_sec_(ThermostatClimateTimerIndex timer_index, uint32_t time) { + uint32_t new_duration_ms = 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + + if (this->timer_[timer_index].active) { + // Timer is running, calculate elapsed time and adjust if needed + uint32_t current_time = App.get_loop_component_start_time(); + uint32_t elapsed = current_time - this->timer_[timer_index].started; + + if (elapsed >= new_duration_ms) { + // Timer should complete immediately (including when new_duration_ms is 0) + ESP_LOGVV(TAG, "timer %d completing immediately (elapsed %d >= new %d)", timer_index, elapsed, new_duration_ms); + this->timer_[timer_index].active = false; + // Trigger the timer callback immediately + this->timer_[timer_index].func(); + return; + } else { + // Adjust timer to run for remaining time - keep original start time + ESP_LOGVV(TAG, "timer %d adjusted: elapsed %d, new total %d, remaining %d", timer_index, elapsed, new_duration_ms, + new_duration_ms - elapsed); + this->timer_[timer_index].time = new_duration_ms; + return; + } + } + + // Original logic for non-running timers + this->timer_[timer_index].time = new_duration_ms; +} + void ThermostatClimate::set_cooling_maximum_run_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME, time); } void ThermostatClimate::set_cooling_minimum_off_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_COOLING_OFF].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_COOLING_OFF, time); } void ThermostatClimate::set_cooling_minimum_run_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_COOLING_ON].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_COOLING_ON, time); } void ThermostatClimate::set_fan_mode_minimum_switching_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_FAN_MODE].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_FAN_MODE, time); } void ThermostatClimate::set_fanning_minimum_off_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_FANNING_OFF].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_FANNING_OFF, time); } void ThermostatClimate::set_fanning_minimum_run_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_FANNING_ON].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_FANNING_ON, time); } void ThermostatClimate::set_heating_maximum_run_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME, time); } void ThermostatClimate::set_heating_minimum_off_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_HEATING_OFF].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_HEATING_OFF, time); } void ThermostatClimate::set_heating_minimum_run_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_HEATING_ON].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_HEATING_ON, time); } void ThermostatClimate::set_idle_minimum_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_IDLE_ON].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_IDLE_ON, time); } void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } void ThermostatClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) { diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 69d2307b1c..2443af58d6 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -267,6 +267,8 @@ class ThermostatClimate : public climate::Climate, public Component { bool timer_active_(ThermostatClimateTimerIndex timer_index); uint32_t timer_duration_(ThermostatClimateTimerIndex timer_index); std::function timer_cbf_(ThermostatClimateTimerIndex timer_index); + /// Enhanced timer duration setter with running timer adjustment + void set_timer_duration_in_sec_(ThermostatClimateTimerIndex timer_index, uint32_t time); /// set_timeout() callbacks for various actions (see above) void cooling_max_run_time_timer_callback_(); From 74b075d3cff8fea73f931880ace85af5cb621c7b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 07:03:17 -1000 Subject: [PATCH 0755/1145] [codegen] Add static storage class to global variables for size optimization (#12616) --- esphome/components/lvgl/__init__.py | 2 ++ esphome/components/lvgl/lvcode.py | 4 ++-- esphome/components/mapping/__init__.py | 2 +- esphome/cpp_generator.py | 22 +++++++++++++++------- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 19c258fcd5..c9cad1ac90 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -256,9 +256,11 @@ async def to_code(configs): True, type=lv_font_t.operator("ptr").operator("const"), ) + # static=False because LV_FONT_CUSTOM_DECLARE creates an extern declaration cg.new_variable( globfont_id, MockObj(await lvalid.lv_font.process(default_font), "->").get_lv_font(), + static=False, ) add_define("LV_FONT_DEFAULT", df.DEFAULT_ESPHOME_FONT) else: diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index c11597131f..e2c70642a8 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -337,7 +337,7 @@ def lv_Pvariable(type, name) -> MockObj: """ if isinstance(name, str): name = ID(name, True, type) - decl = VariableDeclarationExpression(type, "*", name) + decl = VariableDeclarationExpression(type, "*", name, static=True) CORE.add_global(decl) var = MockObj(name, "->") CORE.register_variable(name, var) @@ -353,7 +353,7 @@ def lv_variable(type, name) -> MockObj: """ if isinstance(name, str): name = ID(name, True, type) - decl = VariableDeclarationExpression(type, "", name) + decl = VariableDeclarationExpression(type, "", name, static=True) CORE.add_global(decl) var = MockObj(name, ".") CORE.register_variable(name, var) diff --git a/esphome/components/mapping/__init__.py b/esphome/components/mapping/__init__.py index 94c7c10a82..a36b414fd5 100644 --- a/esphome/components/mapping/__init__.py +++ b/esphome/components/mapping/__init__.py @@ -133,7 +133,7 @@ async def to_code(config): value_type, ) var = MockObj(varid, ".") - decl = VariableDeclarationExpression(varid.type, "", varid) + decl = VariableDeclarationExpression(varid.type, "", varid, static=True) add_global(decl) CORE.register_variable(varid, var) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 1a47b346b7..ddccb574e4 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -51,15 +51,19 @@ class AssignmentExpression(Expression): class VariableDeclarationExpression(Expression): - __slots__ = ("type", "modifier", "name") + __slots__ = ("type", "modifier", "name", "static") - def __init__(self, type_, modifier, name): + def __init__( + self, type_: "MockObj", modifier: str, name: ID, *, static: bool = False + ) -> None: self.type = type_ self.modifier = modifier self.name = name + self.static = static - def __str__(self): - return f"{self.type} {self.modifier}{self.name}" + def __str__(self) -> str: + prefix = "static " if self.static else "" + return f"{prefix}{self.type} {self.modifier}{self.name}" class ExpressionList(Expression): @@ -507,13 +511,17 @@ def with_local_variable(id_: ID, rhs: SafeExpType, callback: Callable, *args) -> CORE.add(RawStatement("}")) # output closing curly brace -def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": +def new_variable( + id_: ID, rhs: SafeExpType, type_: "MockObj" = None, *, static: bool = True +) -> "MockObj": """Declare and define a new variable, not pointer type, in the code generation. :param id_: The ID used to declare the variable. :param rhs: The expression to place on the right hand side of the assignment. :param type_: Manually define a type for the variable, only use this when it's not possible to do so during config validation phase (for example because of template arguments). + :param static: If True (default), declare with static storage class for optimization. + Set to False when the variable must have external linkage (e.g., to match library declarations). :return: The new variable as a MockObj. """ @@ -522,7 +530,7 @@ def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj obj = MockObj(id_, ".") if type_ is not None: id_.type = type_ - decl = VariableDeclarationExpression(id_.type, "", id_) + decl = VariableDeclarationExpression(id_.type, "", id_, static=static) CORE.add_global(decl) assignment = AssignmentExpression(None, "", id_, rhs) CORE.add(assignment) @@ -544,7 +552,7 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": obj = MockObj(id_, "->") if type_ is not None: id_.type = type_ - decl = VariableDeclarationExpression(id_.type, "*", id_) + decl = VariableDeclarationExpression(id_.type, "*", id_, static=True) CORE.add_global(decl) assignment = AssignmentExpression(None, None, id_, rhs) CORE.add(assignment) From 1756fc31b057a2221226c5f23c4c49c04f4baacf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 07:54:17 -1000 Subject: [PATCH 0756/1145] [api] Use union for iterators to reduce APIConnection size by ~16 bytes (#12563) --- esphome/components/api/api_connection.cpp | 75 +++++++++++++++++------ esphome/components/api/api_connection.h | 32 +++++++--- 2 files changed, 82 insertions(+), 25 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 28970a321c..ecbad96a64 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #ifdef USE_ESP8266 #include @@ -96,8 +97,7 @@ static const int CAMERA_STOP_STREAM = 5000; return; #endif // USE_DEVICES -APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent) - : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { +APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent) : parent_(parent) { #if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE) auto &noise_ctx = parent->get_noise_ctx(); if (noise_ctx.has_psk()) { @@ -136,6 +136,7 @@ void APIConnection::start() { } APIConnection::~APIConnection() { + this->destroy_active_iterator_(); #ifdef USE_BLUETOOTH_PROXY if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) { bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this); @@ -148,6 +149,32 @@ APIConnection::~APIConnection() { #endif } +void APIConnection::destroy_active_iterator_() { + switch (this->active_iterator_) { + case ActiveIterator::LIST_ENTITIES: + this->iterator_storage_.list_entities.~ListEntitiesIterator(); + break; + case ActiveIterator::INITIAL_STATE: + this->iterator_storage_.initial_state.~InitialStateIterator(); + break; + case ActiveIterator::NONE: + break; + } + this->active_iterator_ = ActiveIterator::NONE; +} + +void APIConnection::begin_iterator_(ActiveIterator type) { + this->destroy_active_iterator_(); + this->active_iterator_ = type; + if (type == ActiveIterator::LIST_ENTITIES) { + new (&this->iterator_storage_.list_entities) ListEntitiesIterator(this); + this->iterator_storage_.list_entities.begin(); + } else { + new (&this->iterator_storage_.initial_state) InitialStateIterator(this); + this->iterator_storage_.initial_state.begin(); + } +} + void APIConnection::loop() { if (this->flags_.next_close) { // requested a disconnect @@ -190,23 +217,35 @@ void APIConnection::loop() { this->process_batch_(); } - if (!this->list_entities_iterator_.completed()) { - this->process_iterator_batch_(this->list_entities_iterator_); - } else if (!this->initial_state_iterator_.completed()) { - this->process_iterator_batch_(this->initial_state_iterator_); - - // If we've completed initial states, process any remaining and clear the flag - if (this->initial_state_iterator_.completed()) { - // Process any remaining batched messages immediately - if (!this->deferred_batch_.empty()) { - this->process_batch_(); + switch (this->active_iterator_) { + case ActiveIterator::LIST_ENTITIES: + if (this->iterator_storage_.list_entities.completed()) { + this->destroy_active_iterator_(); + if (this->flags_.state_subscription) { + this->begin_iterator_(ActiveIterator::INITIAL_STATE); + } + } else { + this->process_iterator_batch_(this->iterator_storage_.list_entities); } - // Now that everything is sent, enable immediate sending for future state changes - this->flags_.should_try_send_immediately = true; - // Release excess memory from buffers that grew during initial sync - this->deferred_batch_.release_buffer(); - this->helper_->release_buffers(); - } + break; + case ActiveIterator::INITIAL_STATE: + if (this->iterator_storage_.initial_state.completed()) { + this->destroy_active_iterator_(); + // Process any remaining batched messages immediately + if (!this->deferred_batch_.empty()) { + this->process_batch_(); + } + // Now that everything is sent, enable immediate sending for future state changes + this->flags_.should_try_send_immediately = true; + // Release excess memory from buffers that grew during initial sync + this->deferred_batch_.release_buffer(); + this->helper_->release_buffers(); + } else { + this->process_iterator_batch_(this->iterator_storage_.initial_state); + } + break; + case ActiveIterator::NONE: + break; } if (this->flags_.sent_ping) { diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 7351b5082f..845661f183 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -208,10 +208,14 @@ class APIConnection final : public APIServerConnection { bool send_disconnect_response(const DisconnectRequest &msg) override; bool send_ping_response(const PingRequest &msg) override; bool send_device_info_response(const DeviceInfoRequest &msg) override; - void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); } + void list_entities(const ListEntitiesRequest &msg) override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); } void subscribe_states(const SubscribeStatesRequest &msg) override { this->flags_.state_subscription = true; - this->initial_state_iterator_.begin(); + // Start initial state iterator only if no iterator is active + // If list_entities is running, we'll start initial_state when it completes + if (this->active_iterator_ == ActiveIterator::NONE) { + this->begin_iterator_(ActiveIterator::INITIAL_STATE); + } } void subscribe_logs(const SubscribeLogsRequest &msg) override { this->flags_.log_subscription = msg.level; @@ -501,10 +505,22 @@ class APIConnection final : public APIServerConnection { std::unique_ptr helper_; APIServer *parent_; - // Group 2: Larger objects (must be 4-byte aligned) - // These contain vectors/pointers internally, so putting them early ensures good alignment - InitialStateIterator initial_state_iterator_; - ListEntitiesIterator list_entities_iterator_; + // Group 2: Iterator union (saves ~16 bytes vs separate iterators) + // These iterators are never active simultaneously - list_entities runs to completion + // before initial_state begins, so we use a union with explicit construction/destruction. + enum class ActiveIterator : uint8_t { NONE, LIST_ENTITIES, INITIAL_STATE }; + + union IteratorUnion { + ListEntitiesIterator list_entities; + InitialStateIterator initial_state; + // Constructor/destructor do nothing - use placement new/explicit destructor + IteratorUnion() {} + ~IteratorUnion() {} + } iterator_storage_; + + // Helper methods for iterator lifecycle management + void destroy_active_iterator_(); + void begin_iterator_(ActiveIterator type); #ifdef USE_CAMERA std::unique_ptr image_reader_; #endif @@ -619,7 +635,9 @@ class APIConnection final : public APIServerConnection { // 2-byte types immediately after flags_ (no padding between them) uint16_t client_api_version_major_{0}; uint16_t client_api_version_minor_{0}; - // Total: 2 (flags) + 2 + 2 = 6 bytes, then 2 bytes padding to next 4-byte boundary + // 1-byte type to fill padding + ActiveIterator active_iterator_{ActiveIterator::NONE}; + // Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary uint32_t get_batch_delay_ms_() const; // Message will use 8 more bytes than the minimum size, and typical From 1bdbc4cb85eacaf4790516d2de6e3dbe9528dbb3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 07:54:55 -1000 Subject: [PATCH 0757/1145] [esp32_ble] Avoid string allocation when setting BLE device name (#12579) --- esphome/components/esp32_ble/ble.cpp | 26 +++++++++++++++++--------- esphome/core/helpers.cpp | 20 +++++++++++++------- esphome/core/helpers.h | 12 ++++++++++++ 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index a279f7d2a4..42f8ab8fd4 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -256,8 +256,11 @@ bool ESP32BLE::ble_setup_() { } #endif + // BLE device names are limited to 20 characters + // Buffer: 20 chars + null terminator + constexpr size_t ble_name_max_len = 21; + char name_buffer[ble_name_max_len]; const char *device_name; - std::string name_with_suffix; if (this->name_ != nullptr) { if (App.is_name_add_mac_suffix_enabled()) { @@ -268,23 +271,28 @@ bool ESP32BLE::ble_setup_() { char mac_addr[mac_address_len]; get_mac_address_into_buffer(mac_addr); const char *mac_suffix_ptr = mac_addr + mac_address_suffix_len; - name_with_suffix = - make_name_with_suffix(this->name_, strlen(this->name_), '-', mac_suffix_ptr, mac_address_suffix_len); - device_name = name_with_suffix.c_str(); + make_name_with_suffix_to(name_buffer, sizeof(name_buffer), this->name_, strlen(this->name_), '-', mac_suffix_ptr, + mac_address_suffix_len); + device_name = name_buffer; } else { device_name = this->name_; } } else { - name_with_suffix = App.get_name(); - if (name_with_suffix.length() > 20) { + const std::string &app_name = App.get_name(); + size_t name_len = app_name.length(); + if (name_len > 20) { if (App.is_name_add_mac_suffix_enabled()) { // Keep first 13 chars and last 7 chars (MAC suffix), remove middle - name_with_suffix.erase(13, name_with_suffix.length() - 20); + memcpy(name_buffer, app_name.c_str(), 13); + memcpy(name_buffer + 13, app_name.c_str() + name_len - 7, 7); } else { - name_with_suffix.resize(20); + memcpy(name_buffer, app_name.c_str(), 20); } + name_buffer[20] = '\0'; + } else { + memcpy(name_buffer, app_name.c_str(), name_len + 1); // Include null terminator } - device_name = name_with_suffix.c_str(); + device_name = name_buffer; } err = esp_ble_gap_set_device_name(device_name); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 156f41a2dc..2f76b6c17d 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -244,17 +244,16 @@ std::string str_sprintf(const char *fmt, ...) { // Maximum size for name with suffix: 120 (max friendly name) + 1 (separator) + 6 (MAC suffix) + 1 (null term) static constexpr size_t MAX_NAME_WITH_SUFFIX_SIZE = 128; -std::string make_name_with_suffix(const char *name, size_t name_len, char sep, const char *suffix_ptr, - size_t suffix_len) { - char buffer[MAX_NAME_WITH_SUFFIX_SIZE]; +size_t make_name_with_suffix_to(char *buffer, size_t buffer_size, const char *name, size_t name_len, char sep, + const char *suffix_ptr, size_t suffix_len) { size_t total_len = name_len + 1 + suffix_len; // Silently truncate if needed: prioritize keeping the full suffix - if (total_len >= MAX_NAME_WITH_SUFFIX_SIZE) { - // NOTE: This calculation could underflow if suffix_len >= MAX_NAME_WITH_SUFFIX_SIZE - 2, + if (total_len >= buffer_size) { + // NOTE: This calculation could underflow if suffix_len >= buffer_size - 2, // but this is safe because this helper is only called with small suffixes: // MAC suffixes (6-12 bytes), ".local" (5 bytes), etc. - name_len = MAX_NAME_WITH_SUFFIX_SIZE - suffix_len - 2; // -2 for separator and null terminator + name_len = buffer_size - suffix_len - 2; // -2 for separator and null terminator total_len = name_len + 1 + suffix_len; } @@ -262,7 +261,14 @@ std::string make_name_with_suffix(const char *name, size_t name_len, char sep, c buffer[name_len] = sep; memcpy(buffer + name_len + 1, suffix_ptr, suffix_len); buffer[total_len] = '\0'; - return std::string(buffer, total_len); + return total_len; +} + +std::string make_name_with_suffix(const char *name, size_t name_len, char sep, const char *suffix_ptr, + size_t suffix_len) { + char buffer[MAX_NAME_WITH_SUFFIX_SIZE]; + size_t len = make_name_with_suffix_to(buffer, sizeof(buffer), name, name_len, sep, suffix_ptr, suffix_len); + return std::string(buffer, len); } std::string make_name_with_suffix(const std::string &name, char sep, const char *suffix_ptr, size_t suffix_len) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 6028c93ce2..02d050d2d1 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -549,6 +549,18 @@ std::string make_name_with_suffix(const std::string &name, char sep, const char std::string make_name_with_suffix(const char *name, size_t name_len, char sep, const char *suffix_ptr, size_t suffix_len); +/// Zero-allocation version: format name + separator + suffix directly into buffer. +/// @param buffer Output buffer (must have space for result + null terminator) +/// @param buffer_size Size of the output buffer +/// @param name The base name string +/// @param name_len Length of the name +/// @param sep Single character separator +/// @param suffix_ptr Pointer to the suffix characters +/// @param suffix_len Length of the suffix +/// @return Length written (excluding null terminator) +size_t make_name_with_suffix_to(char *buffer, size_t buffer_size, const char *name, size_t name_len, char sep, + const char *suffix_ptr, size_t suffix_len); + ///@} /// @name Parsing & formatting From 265ad9d2640fa5a9bfa4b02ae1c98cd566ca8503 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 07:55:28 -1000 Subject: [PATCH 0758/1145] [esp32_camera] Reduce loop overhead and improve frame latency with wake_loop_threadsafe (#12601) --- esphome/components/api/api_connection.cpp | 63 ++++++++++++------- esphome/components/api/api_connection.h | 4 ++ esphome/components/esp32_camera/__init__.py | 5 +- .../components/esp32_camera/esp32_camera.cpp | 37 +++++++---- .../components/esp32_camera/esp32_camera.h | 5 +- 5 files changed, 74 insertions(+), 40 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index ecbad96a64..b5628f654e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -270,33 +270,17 @@ void APIConnection::loop() { } } -#ifdef USE_CAMERA - if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) { - uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available()); - bool done = this->image_reader_->available() == to_send; - - CameraImageResponse msg; - msg.key = camera::Camera::instance()->get_object_id_hash(); - msg.set_data(this->image_reader_->peek_data_buffer(), to_send); - msg.done = done; -#ifdef USE_DEVICES - msg.device_id = camera::Camera::instance()->get_device_id(); -#endif - - if (this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) { - this->image_reader_->consume_data(to_send); - if (done) { - this->image_reader_->return_image(); - } - } - } -#endif - #ifdef USE_API_HOMEASSISTANT_STATES if (state_subs_at_ >= 0) { this->process_state_subscriptions_(); } #endif + +#ifdef USE_CAMERA + // Process camera last - state updates are higher priority + // (missing a frame is fine, missing a state update is not) + this->try_send_camera_image_(); +#endif } bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) { @@ -1099,6 +1083,36 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { #endif #ifdef USE_CAMERA +void APIConnection::try_send_camera_image_() { + if (!this->image_reader_) + return; + + // Send as many chunks as possible without blocking + while (this->image_reader_->available()) { + if (!this->helper_->can_write_without_blocking()) + return; + + uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available()); + bool done = this->image_reader_->available() == to_send; + + CameraImageResponse msg; + msg.key = camera::Camera::instance()->get_object_id_hash(); + msg.set_data(this->image_reader_->peek_data_buffer(), to_send); + msg.done = done; +#ifdef USE_DEVICES + msg.device_id = camera::Camera::instance()->get_device_id(); +#endif + + if (!this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) { + return; // Send failed, try again later + } + this->image_reader_->consume_data(to_send); + if (done) { + this->image_reader_->return_image(); + return; + } + } +} void APIConnection::set_camera_state(std::shared_ptr image) { if (!this->flags_.state_subscription) return; @@ -1106,8 +1120,11 @@ void APIConnection::set_camera_state(std::shared_ptr image) return; if (this->image_reader_->available()) return; - if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) + if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) { this->image_reader_->set_image(std::move(image)); + // Try to send immediately to reduce latency + this->try_send_camera_image_(); + } } uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 845661f183..6753e68749 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -296,6 +296,10 @@ class APIConnection final : public APIServerConnection { // Helper function to handle authentication completion void complete_authentication_(); +#ifdef USE_CAMERA + void try_send_camera_image_(); +#endif + #ifdef USE_API_HOMEASSISTANT_STATES void process_state_subscriptions_(); #endif diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index ca37cb392d..db6244fb3f 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -2,7 +2,7 @@ import logging from esphome import automation, pins import esphome.codegen as cg -from esphome.components import i2c +from esphome.components import i2c, socket from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option from esphome.components.psram import DOMAIN as psram_domain import esphome.config_validation as cv @@ -27,7 +27,7 @@ import esphome.final_validate as fv _LOGGER = logging.getLogger(__name__) -AUTO_LOAD = ["camera"] +AUTO_LOAD = ["camera", "socket"] DEPENDENCIES = ["esp32"] esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") @@ -324,6 +324,7 @@ SETTERS = { async def to_code(config): cg.add_define("USE_CAMERA") + socket.require_wake_loop_threadsafe() var = cg.new_Pvariable(config[CONF_ID]) await setup_entity(var, config, "camera") await cg.register_component(var, config) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index a3677330ca..06ba7ff16f 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -11,6 +11,7 @@ namespace esphome { namespace esp32_camera { static const char *const TAG = "esp32_camera"; +static constexpr size_t FRAMEBUFFER_TASK_STACK_SIZE = 1792; #if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE static constexpr uint32_t FRAME_LOG_INTERVAL_MS = 60000; #endif @@ -42,12 +43,12 @@ void ESP32Camera::setup() { this->framebuffer_get_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); this->framebuffer_return_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task, - "framebuffer_task", // name - 1024, // stack size - this, // task pv params - 1, // priority - nullptr, // handle - 1 // core + "framebuffer_task", // name + FRAMEBUFFER_TASK_STACK_SIZE, // stack size + this, // task pv params + 1, // priority + nullptr, // handle + 1 // core ); } @@ -167,6 +168,19 @@ void ESP32Camera::dump_config() { } void ESP32Camera::loop() { + // Fast path: skip all work when truly idle + // (no current image, no pending requests, and not time for idle request yet) + const uint32_t now = App.get_loop_component_start_time(); + if (!this->current_image_ && !this->has_requested_image_()) { + // Only check idle interval when we're otherwise idle + if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) { + this->last_idle_request_ = now; + this->request_image(camera::IDLE); + } else { + return; + } + } + // check if we can return the image if (this->can_return_image_()) { // return image @@ -175,13 +189,6 @@ void ESP32Camera::loop() { this->current_image_.reset(); } - // request idle image every idle_update_interval - const uint32_t now = App.get_loop_component_start_time(); - if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) { - this->last_idle_request_ = now; - this->request_image(camera::IDLE); - } - // Check if we should fetch a new image if (!this->has_requested_image_()) return; @@ -421,6 +428,10 @@ void ESP32Camera::framebuffer_task(void *pv) { while (true) { camera_fb_t *framebuffer = esp_camera_fb_get(); xQueueSend(that->framebuffer_get_queue_, &framebuffer, portMAX_DELAY); + // Only wake the main loop if there's a pending request to consume the frame + if (that->has_requested_image_()) { + App.wake_loop_threadsafe(); + } // return is no-op for config with 1 fb xQueueReceive(that->framebuffer_return_queue_, &framebuffer, portMAX_DELAY); esp_camera_fb_return(framebuffer); diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index a49fca6511..e97eb27c70 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -2,6 +2,7 @@ #ifdef USE_ESP32 +#include #include #include #include @@ -205,8 +206,8 @@ class ESP32Camera : public camera::Camera { esp_err_t init_error_{ESP_OK}; std::shared_ptr current_image_; - uint8_t single_requesters_{0}; - uint8_t stream_requesters_{0}; + std::atomic single_requesters_{0}; + std::atomic stream_requesters_{0}; QueueHandle_t framebuffer_get_queue_; QueueHandle_t framebuffer_return_queue_; std::vector listeners_; From 6383fe4598a07057cbede7760c9e735b7bc4ff04 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 07:56:33 -1000 Subject: [PATCH 0759/1145] [core] Add zero-allocation object_id methods (#12578) --- esphome/components/api/api_connection.h | 15 ++---- esphome/components/web_server/web_server.cpp | 8 ++-- esphome/components/web_server/web_server.h | 12 ++--- .../components/web_server/web_server_v1.cpp | 3 +- esphome/core/entity_base.cpp | 46 ++++++++++++++----- esphome/core/entity_base.h | 29 ++++++------ esphome/core/helpers.cpp | 8 ---- esphome/core/helpers.h | 7 +++ 8 files changed, 70 insertions(+), 58 deletions(-) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 6753e68749..6363116900 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -323,17 +323,10 @@ class APIConnection final : public APIServerConnection { APIConnection *conn, uint32_t remaining_size, bool is_single) { // Set common fields that are shared by all entity types msg.key = entity->get_object_id_hash(); - // Try to use static reference first to avoid allocation - StringRef static_ref = entity->get_object_id_ref_for_api_(); - // Store dynamic string outside the if-else to maintain lifetime - std::string object_id; - if (!static_ref.empty()) { - msg.set_object_id(static_ref); - } else { - // Dynamic case - need to allocate - object_id = entity->get_object_id(); - msg.set_object_id(StringRef(object_id)); - } + // Get object_id with zero heap allocation + // Static case returns direct reference, dynamic case uses buffer + char object_id_buf[OBJECT_ID_MAX_LEN]; + msg.set_object_id(entity->get_object_id_to(object_id_buf)); if (entity->has_own_name()) { msg.set_name(entity->get_name()); diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 6870a1dc87..207eafad5c 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -404,9 +404,11 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) { // Helper functions to reduce code size by avoiding macro expansion static void set_json_id(JsonObject &root, EntityBase *obj, const char *prefix, JsonDetail start_config) { - char id_buf[160]; // object_id can be up to 128 chars + prefix + dash + null - const auto &object_id = obj->get_object_id(); - snprintf(id_buf, sizeof(id_buf), "%s-%s", prefix, object_id.c_str()); + char id_buf[160]; // prefix + dash + object_id (up to 128) + null + size_t len = strlen(prefix); + memcpy(id_buf, prefix, len); // NOLINT(bugprone-not-null-terminated-result) - null added by write_object_id_to + id_buf[len++] = '-'; + obj->write_object_id_to(id_buf + len, sizeof(id_buf) - len); root[ESPHOME_F("id")] = id_buf; if (start_config == DETAIL_ALL) { root[ESPHOME_F("name")] = obj->get_name(); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index bb69d57872..98234ec1ae 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -52,14 +52,10 @@ struct UrlMatch { } bool id_equals_entity(EntityBase *entity) const { - // Zero-copy comparison using StringRef - StringRef static_ref = entity->get_object_id_ref_for_api_(); - if (!static_ref.empty()) { - return id && id_len == static_ref.size() && memcmp(id, static_ref.c_str(), id_len) == 0; - } - // Fallback to allocation (rare) - const auto &obj_id = entity->get_object_id(); - return id && id_len == obj_id.length() && memcmp(id, obj_id.c_str(), id_len) == 0; + // Get object_id with zero heap allocation + char object_id_buf[OBJECT_ID_MAX_LEN]; + StringRef object_id = entity->get_object_id_to(object_id_buf); + return id && id_len == object_id.size() && memcmp(id, object_id.c_str(), id_len) == 0; } bool method_equals(const char *str) const { diff --git a/esphome/components/web_server/web_server_v1.cpp b/esphome/components/web_server/web_server_v1.cpp index 4f0d0cd1a9..cbc25b9dec 100644 --- a/esphome/components/web_server/web_server_v1.cpp +++ b/esphome/components/web_server/web_server_v1.cpp @@ -15,7 +15,8 @@ void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string & stream->print("\" id=\""); stream->print(klass.c_str()); stream->print("-"); - stream->print(obj->get_object_id().c_str()); + char object_id_buf[OBJECT_ID_MAX_LEN]; + stream->print(obj->get_object_id_to(object_id_buf).c_str()); stream->print("\">"); stream->print(obj->get_name().c_str()); stream->print(""); diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 046f99d8cc..b7616a9ad3 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -60,15 +60,6 @@ std::string EntityBase::get_object_id() const { // `App.get_friendly_name()` is constant. return this->object_id_c_str_ == nullptr ? "" : this->object_id_c_str_; } -StringRef EntityBase::get_object_id_ref_for_api_() const { - static constexpr auto EMPTY_STRING = StringRef::from_lit(""); - // Return empty for dynamic case (MAC suffix) - if (this->is_object_id_dynamic_()) { - return EMPTY_STRING; - } - // For static case, return the string or empty if null - return this->object_id_c_str_ == nullptr ? EMPTY_STRING : StringRef(this->object_id_c_str_); -} void EntityBase::set_object_id(const char *object_id) { this->object_id_c_str_ = object_id; this->calc_object_id_(); @@ -82,8 +73,41 @@ void EntityBase::set_name_and_object_id(const char *name, const char *object_id) // Calculate Object ID Hash from Entity Name void EntityBase::calc_object_id_() { - this->object_id_hash_ = - fnv1_hash(this->is_object_id_dynamic_() ? this->get_object_id().c_str() : this->object_id_c_str_); + char buf[OBJECT_ID_MAX_LEN]; + StringRef object_id = this->get_object_id_to(buf); + this->object_id_hash_ = fnv1_hash(object_id.c_str()); +} + +// Format dynamic object_id: sanitized snake_case of friendly_name +static size_t format_dynamic_object_id(char *buf, size_t buf_size) { + const std::string &name = App.get_friendly_name(); + size_t len = std::min(name.size(), buf_size - 1); + for (size_t i = 0; i < len; i++) { + buf[i] = to_sanitized_char(to_snake_case_char(name[i])); + } + buf[len] = '\0'; + return len; +} + +size_t EntityBase::write_object_id_to(char *buf, size_t buf_size) const { + if (this->is_object_id_dynamic_()) { + return format_dynamic_object_id(buf, buf_size); + } + const char *src = this->object_id_c_str_ == nullptr ? "" : this->object_id_c_str_; + size_t len = strlen(src); + if (len >= buf_size) + len = buf_size - 1; + memcpy(buf, src, len); + buf[len] = '\0'; + return len; +} + +StringRef EntityBase::get_object_id_to(std::span buf) const { + if (this->is_object_id_dynamic_()) { + size_t len = format_dynamic_object_id(buf.data(), buf.size()); + return StringRef(buf.data(), len); + } + return this->object_id_c_str_ == nullptr ? StringRef() : StringRef(this->object_id_c_str_); } uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index fdf3f6300a..eb1ba46c94 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -1,7 +1,8 @@ #pragma once -#include #include +#include +#include #include "string_ref.h" #include "helpers.h" #include "log.h" @@ -12,14 +13,8 @@ namespace esphome { -// Forward declaration for friend access -namespace api { -class APIConnection; -} // namespace api - -namespace web_server { -struct UrlMatch; -} // namespace web_server +// Maximum size for object_id buffer (friendly_name max ~120 + margin) +static constexpr size_t OBJECT_ID_MAX_LEN = 128; enum EntityCategory : uint8_t { ENTITY_CATEGORY_NONE = 0, @@ -47,6 +42,15 @@ class EntityBase { // Get the unique Object ID of this Entity uint32_t get_object_id_hash(); + /// Get object_id with zero heap allocation + /// For static case: returns StringRef to internal storage (buffer unused) + /// For dynamic case: formats into buffer and returns StringRef to buffer + StringRef get_object_id_to(std::span buf) const; + + /// Write object_id directly to buffer, returns length written (excluding null) + /// Useful for building compound strings without intermediate buffer + size_t write_object_id_to(char *buf, size_t buf_size) const; + // Get/set whether this Entity should be hidden outside ESPHome bool is_internal() const { return this->flags_.internal; } void set_internal(bool internal) { this->flags_.internal = internal; } @@ -125,13 +129,6 @@ class EntityBase { } protected: - friend class api::APIConnection; - friend struct web_server::UrlMatch; - - // Get object_id as StringRef when it's static (for API usage) - // Returns empty StringRef if object_id is dynamic (needs allocation) - StringRef get_object_id_ref_for_api_() const; - void calc_object_id_(); /// Check if the object_id is dynamic (changes with MAC suffix) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 2f76b6c17d..f55f53f16b 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -189,14 +189,6 @@ template std::string str_ctype_transform(const std::string &str) } std::string str_lower_case(const std::string &str) { return str_ctype_transform(str); } std::string str_upper_case(const std::string &str) { return str_ctype_transform(str); } -// Convert char to snake_case: lowercase and spaces to underscores -static constexpr char to_snake_case_char(char c) { - return (c == ' ') ? '_' : (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; -} -// Sanitize char: keep alphanumerics, dashes, underscores; replace others with underscore -static constexpr char to_sanitized_char(char c) { - return (c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ? c : '_'; -} std::string str_snake_case(const std::string &str) { std::string result = str; for (char &c : result) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 02d050d2d1..b575a14d14 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -516,9 +516,16 @@ std::string str_until(const std::string &str, char ch); std::string str_lower_case(const std::string &str); /// Convert the string to upper case. std::string str_upper_case(const std::string &str); + +/// Convert a single char to snake_case: lowercase and space to underscore. +constexpr char to_snake_case_char(char c) { return (c == ' ') ? '_' : (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; } /// Convert the string to snake case (lowercase with underscores). std::string str_snake_case(const std::string &str); +/// Sanitize a single char: keep alphanumerics, dashes, underscores; replace others with underscore. +constexpr char to_sanitized_char(char c) { + return (c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ? c : '_'; +} /// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores. std::string str_sanitize(const std::string &str); From 84b5d9b21c90f8e38e360762b4368bbf8b29e511 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:00:12 -0500 Subject: [PATCH 0760/1145] [core] Remove deprecated config options from before 2025 (#12622) Co-authored-by: Claude --- esphome/components/bedjet/climate/__init__.py | 25 ++----------------- esphome/components/bh1750/sensor.py | 10 -------- esphome/components/ethernet/__init__.py | 4 --- esphome/components/i2c/__init__.py | 4 --- esphome/components/remote_base/__init__.py | 15 +---------- esphome/components/sensor/__init__.py | 3 --- esphome/components/tca9548a/__init__.py | 3 +-- .../components/template/switch/__init__.py | 4 --- esphome/components/tuya/light/__init__.py | 6 ----- esphome/components/uart/__init__.py | 4 --- esphome/components/wifi/__init__.py | 4 --- 11 files changed, 4 insertions(+), 78 deletions(-) diff --git a/esphome/components/bedjet/climate/__init__.py b/esphome/components/bedjet/climate/__init__.py index 0da2107d43..4de9dcca0b 100644 --- a/esphome/components/bedjet/climate/__init__.py +++ b/esphome/components/bedjet/climate/__init__.py @@ -1,12 +1,7 @@ import esphome.codegen as cg -from esphome.components import ble_client, climate +from esphome.components import climate import esphome.config_validation as cv -from esphome.const import ( - CONF_HEAT_MODE, - CONF_RECEIVE_TIMEOUT, - CONF_TEMPERATURE_SOURCE, - CONF_TIME_ID, -) +from esphome.const import CONF_HEAT_MODE, CONF_TEMPERATURE_SOURCE from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child @@ -38,22 +33,6 @@ CONFIG_SCHEMA = ( } ) .extend(cv.polling_component_schema("60s")) - .extend( - # TODO: remove compat layer. - { - cv.Optional(ble_client.CONF_BLE_CLIENT_ID): cv.invalid( - "The 'ble_client_id' option has been removed. Please migrate " - "to the new `bedjet_id` option in the `bedjet` component.\n" - "See https://esphome.io/components/climate/bedjet/" - ), - cv.Optional(CONF_TIME_ID): cv.invalid( - "The 'time_id' option has been moved to the `bedjet` component." - ), - cv.Optional(CONF_RECEIVE_TIMEOUT): cv.invalid( - "The 'receive_timeout' option has been moved to the `bedjet` component." - ), - } - ) .extend(BEDJET_CLIENT_SCHEMA) ) diff --git a/esphome/components/bh1750/sensor.py b/esphome/components/bh1750/sensor.py index 7c7eecb88c..36af5aeef9 100644 --- a/esphome/components/bh1750/sensor.py +++ b/esphome/components/bh1750/sensor.py @@ -20,16 +20,6 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_ILLUMINANCE, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.Optional("resolution"): cv.invalid( - "The 'resolution' option has been removed. The optimal value is now dynamically calculated." - ), - cv.Optional("measurement_duration"): cv.invalid( - "The 'measurement_duration' option has been removed. The optimal value is now dynamically calculated." - ), - } - ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x23)) ) diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index e1ed327fb9..f140f395e4 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -220,10 +220,6 @@ BASE_SCHEMA = cv.Schema( cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA, cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, - cv.Optional("enable_mdns"): cv.invalid( - "This option has been removed. Please use the [disabled] option under the " - "new mdns component instead." - ), cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 7706484e97..b7436ccc39 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -237,10 +237,6 @@ def i2c_device_schema(default_address): """ schema = { cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CBus), - cv.Optional("multiplexer"): cv.invalid( - "This option has been removed, please see " - "the tca9584a docs for the updated way to use multiplexers" - ), } if default_address is None: schema[cv.Required(CONF_ADDRESS)] = cv.i2c_address diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index d24d24b000..9d3e655c57 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -108,9 +108,6 @@ def register_trigger(name, type, data_type): validator = automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(type), - cv.Optional(CONF_RECEIVER_ID): cv.invalid( - "This has been removed in ESPHome 2022.3.0 and the trigger attaches directly to the parent receiver." - ), } ) registerer = TRIGGER_REGISTRY.register(f"on_{name}", validator) @@ -207,13 +204,7 @@ validate_binary_sensor = cv.validate_registry_entry( "remote receiver", BINARY_SENSOR_REGISTRY ) TRIGGER_REGISTRY = SimpleRegistry() -DUMPER_REGISTRY = Registry( - { - cv.Optional(CONF_RECEIVER_ID): cv.invalid( - "This has been removed in ESPHome 1.20.0 and the dumper attaches directly to the parent receiver." - ), - } -) +DUMPER_REGISTRY = Registry() def validate_dumpers(value): @@ -480,10 +471,6 @@ COOLIX_BASE_SCHEMA = cv.Schema( { cv.Required(CONF_FIRST): cv.hex_int_range(0, 16777215), cv.Optional(CONF_SECOND, default=0): cv.hex_int_range(0, 16777215), - cv.Optional(CONF_DATA): cv.invalid( - "'data' option has been removed in ESPHome 2023.8. " - "Use the 'first' and 'second' options instead." - ), } ) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 027d9a69b8..83b2656661 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -304,9 +304,6 @@ _SENSOR_SCHEMA = ( cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_STATE_CLASS): validate_state_class, cv.Optional(CONF_ENTITY_CATEGORY): sensor_entity_category, - cv.Optional("last_reset_type"): cv.invalid( - "last_reset_type has been removed since 2021.9.0. state_class: total_increasing should be used for total values." - ), cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, cv.Optional(CONF_EXPIRE_AFTER): cv.All( cv.requires_component("mqtt"), diff --git a/esphome/components/tca9548a/__init__.py b/esphome/components/tca9548a/__init__.py index cef779de2e..72973a54ad 100644 --- a/esphome/components/tca9548a/__init__.py +++ b/esphome/components/tca9548a/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv -from esphome.const import CONF_CHANNEL, CONF_CHANNELS, CONF_ID, CONF_SCAN +from esphome.const import CONF_CHANNEL, CONF_CHANNELS, CONF_ID CODEOWNERS = ["@andreashergert1984"] @@ -18,7 +18,6 @@ CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(TCA9548AComponent), - cv.Optional(CONF_SCAN): cv.invalid("This option has been removed"), cv.Optional(CONF_CHANNELS, default=[]): cv.ensure_list( { cv.Required(CONF_BUS_ID): cv.declare_id(TCA9548AChannel), diff --git a/esphome/components/template/switch/__init__.py b/esphome/components/template/switch/__init__.py index e86657510f..8ae5a07dc3 100644 --- a/esphome/components/template/switch/__init__.py +++ b/esphome/components/template/switch/__init__.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC, - CONF_RESTORE_STATE, CONF_STATE, CONF_TURN_OFF_ACTION, CONF_TURN_ON_ACTION, @@ -44,9 +43,6 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_TURN_ON_ACTION): automation.validate_automation( single=True ), - cv.Optional(CONF_RESTORE_STATE): cv.invalid( - "The restore_state option has been removed in 2023.7.0. Use the restore_mode option instead" - ), } ) .extend(cv.COMPONENT_SCHEMA), diff --git a/esphome/components/tuya/light/__init__.py b/esphome/components/tuya/light/__init__.py index 1d2286e3c7..4d2ccba8b1 100644 --- a/esphome/components/tuya/light/__init__.py +++ b/esphome/components/tuya/light/__init__.py @@ -37,10 +37,6 @@ COLOR_TYPES = { TuyaLight = tuya_ns.class_("TuyaLight", light.LightOutput, cg.Component) -COLOR_CONFIG_ERROR = ( - "This option has been removed, use color_datapoint and color_type instead." -) - CONFIG_SCHEMA = cv.All( light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend( { @@ -49,8 +45,6 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DIMMER_DATAPOINT): cv.uint8_t, cv.Optional(CONF_MIN_VALUE_DATAPOINT): cv.uint8_t, cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_RGB_DATAPOINT): cv.invalid(COLOR_CONFIG_ERROR), - cv.Optional(CONF_HSV_DATAPOINT): cv.invalid(COLOR_CONFIG_ERROR), cv.Inclusive(CONF_COLOR_DATAPOINT, "color"): cv.uint8_t, cv.Inclusive(CONF_COLOR_TYPE, "color"): cv.enum(COLOR_TYPES, upper=True), cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 6494aaa286..9baa6ebd81 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -19,7 +19,6 @@ from esphome.const import ( CONF_DUMMY_RECEIVER_ID, CONF_FLOW_CONTROL_PIN, CONF_ID, - CONF_INVERT, CONF_LAMBDA, CONF_NUMBER, CONF_PORT, @@ -304,9 +303,6 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_PARITY, default="NONE"): cv.enum( UART_PARITY_OPTIONS, upper=True ), - cv.Optional(CONF_INVERT): cv.invalid( - "This option has been removed. Please instead use invert in the tx/rx pin schemas." - ), cv.Optional(CONF_DEBUG): maybe_empty_debug, } ).extend(cv.COMPONENT_SCHEMA), diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 2c10506011..fb23837e78 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -348,10 +348,6 @@ CONFIG_SCHEMA = cv.All( cv.boolean, cv.only_on_esp32 ), cv.Optional(CONF_PASSIVE_SCAN, default=False): cv.boolean, - cv.Optional("enable_mdns"): cv.invalid( - "This option has been removed. Please use the [disabled] option under the " - "new mdns component instead." - ), cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, cv.Optional(CONF_ON_CONNECT): automation.validate_automation(single=True), cv.Optional(CONF_ON_DISCONNECT): automation.validate_automation( From cd45fe0c3a72eeeb474e8c748c3dc7368288f38c Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 22 Dec 2025 13:13:03 -0600 Subject: [PATCH 0761/1145] [thermostat] Optimizations to reduce binary size (#12621) --- .../thermostat/thermostat_climate.cpp | 54 +++++++++++++++---- .../thermostat/thermostat_climate.h | 29 +++------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index e588407c4a..d5fb259dad 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -3,8 +3,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace thermostat { +namespace esphome::thermostat { static const char *const TAG = "thermostat.climate"; @@ -66,10 +65,12 @@ void ThermostatClimate::setup() { } void ThermostatClimate::loop() { - for (auto &timer : this->timer_) { - if (timer.active && (timer.started + timer.time < App.get_loop_component_start_time())) { + uint32_t now = App.get_loop_component_start_time(); + for (uint8_t i = 0; i < THERMOSTAT_TIMER_COUNT; i++) { + auto &timer = this->timer_[i]; + if (timer.active && (now - timer.started >= timer.time)) { timer.active = false; - timer.func(); + this->call_timer_callback_(static_cast(i)); } } } @@ -916,8 +917,42 @@ uint32_t ThermostatClimate::timer_duration_(ThermostatClimateTimerIndex timer_in return this->timer_[timer_index].time; } -std::function ThermostatClimate::timer_cbf_(ThermostatClimateTimerIndex timer_index) { - return this->timer_[timer_index].func; +void ThermostatClimate::call_timer_callback_(ThermostatClimateTimerIndex timer_index) { + switch (timer_index) { + case THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME: + this->cooling_max_run_time_timer_callback_(); + break; + case THERMOSTAT_TIMER_COOLING_OFF: + this->cooling_off_timer_callback_(); + break; + case THERMOSTAT_TIMER_COOLING_ON: + this->cooling_on_timer_callback_(); + break; + case THERMOSTAT_TIMER_FAN_MODE: + this->fan_mode_timer_callback_(); + break; + case THERMOSTAT_TIMER_FANNING_OFF: + this->fanning_off_timer_callback_(); + break; + case THERMOSTAT_TIMER_FANNING_ON: + this->fanning_on_timer_callback_(); + break; + case THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME: + this->heating_max_run_time_timer_callback_(); + break; + case THERMOSTAT_TIMER_HEATING_OFF: + this->heating_off_timer_callback_(); + break; + case THERMOSTAT_TIMER_HEATING_ON: + this->heating_on_timer_callback_(); + break; + case THERMOSTAT_TIMER_IDLE_ON: + this->idle_on_timer_callback_(); + break; + case THERMOSTAT_TIMER_COUNT: + default: + break; + } } void ThermostatClimate::cooling_max_run_time_timer_callback_() { @@ -1344,7 +1379,7 @@ void ThermostatClimate::set_timer_duration_in_sec_(ThermostatClimateTimerIndex t ESP_LOGVV(TAG, "timer %d completing immediately (elapsed %d >= new %d)", timer_index, elapsed, new_duration_ms); this->timer_[timer_index].active = false; // Trigger the timer callback immediately - this->timer_[timer_index].func(); + this->call_timer_callback_(timer_index); return; } else { // Adjust timer to run for remaining time - keep original start time @@ -1672,5 +1707,4 @@ ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig(float defau float default_temperature_high) : default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {} -} // namespace thermostat -} // namespace esphome +} // namespace esphome::thermostat diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 2443af58d6..564b6127b3 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -10,8 +10,7 @@ #include #include -namespace esphome { -namespace thermostat { +namespace esphome::thermostat { enum HumidificationAction : uint8_t { THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF = 0, @@ -41,13 +40,11 @@ enum OnBootRestoreFrom : uint8_t { struct ThermostatClimateTimer { ThermostatClimateTimer() = default; - ThermostatClimateTimer(bool active, uint32_t time, uint32_t started, std::function func) - : active(active), time(time), started(started), func(std::move(func)) {} + ThermostatClimateTimer(bool active, uint32_t time, uint32_t started) : active(active), time(time), started(started) {} bool active; uint32_t time; uint32_t started; - std::function func; }; struct ThermostatClimateTargetTempConfig { @@ -266,7 +263,8 @@ class ThermostatClimate : public climate::Climate, public Component { bool cancel_timer_(ThermostatClimateTimerIndex timer_index); bool timer_active_(ThermostatClimateTimerIndex timer_index); uint32_t timer_duration_(ThermostatClimateTimerIndex timer_index); - std::function timer_cbf_(ThermostatClimateTimerIndex timer_index); + /// Call the appropriate timer callback based on timer index + void call_timer_callback_(ThermostatClimateTimerIndex timer_index); /// Enhanced timer duration setter with running timer adjustment void set_timer_duration_in_sec_(ThermostatClimateTimerIndex timer_index, uint32_t time); @@ -534,27 +532,16 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *prev_humidity_control_trigger_{nullptr}; /// Climate action timers - std::array timer_{ - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)), - }; + std::array timer_{}; /// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc) FixedVector preset_config_{}; /// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset") FixedVector custom_preset_config_{}; - /// Default custom preset to use on start up (pointer to entry in custom_preset_config_) + private: + /// Default custom preset to use on start up (pointer to entry in custom_preset_config_) const char *default_custom_preset_{nullptr}; }; -} // namespace thermostat -} // namespace esphome +} // namespace esphome::thermostat From 08c0f65f30e711e48b4af20d1ce77d2d7ca30b28 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 22 Dec 2025 13:13:18 -0600 Subject: [PATCH 0762/1145] [sprinkler] Remove internal latching valve support (#12603) --- esphome/components/sprinkler/__init__.py | 109 ++++-------- esphome/components/sprinkler/sprinkler.cpp | 195 +++++---------------- esphome/components/sprinkler/sprinkler.h | 51 +----- 3 files changed, 84 insertions(+), 271 deletions(-) diff --git a/esphome/components/sprinkler/__init__.py b/esphome/components/sprinkler/__init__.py index 2dccb6896a..50c69f9496 100644 --- a/esphome/components/sprinkler/__init__.py +++ b/esphome/components/sprinkler/__init__.py @@ -19,6 +19,7 @@ from esphome.const import ( UNIT_MINUTE, UNIT_SECOND, ) +from esphome.helpers import docs_url AUTO_LOAD = ["number", "switch"] CODEOWNERS = ["@kbx81"] @@ -162,55 +163,9 @@ def validate_sprinkler(config): raise cv.Invalid( f"{CONF_RUN_DURATION} must be greater than {CONF_VALVE_OPEN_DELAY}" ) - if ( - CONF_PUMP_OFF_SWITCH_ID in valve and CONF_PUMP_ON_SWITCH_ID not in valve - ) or ( - CONF_PUMP_ON_SWITCH_ID in valve and CONF_PUMP_OFF_SWITCH_ID not in valve - ): + if CONF_VALVE_SWITCH_ID not in valve: raise cv.Invalid( - f"Both {CONF_PUMP_OFF_SWITCH_ID} and {CONF_PUMP_ON_SWITCH_ID} must be specified for latching pump configuration" - ) - if CONF_PUMP_SWITCH_ID in valve and ( - CONF_PUMP_OFF_SWITCH_ID in valve or CONF_PUMP_ON_SWITCH_ID in valve - ): - raise cv.Invalid( - f"Do not specify {CONF_PUMP_OFF_SWITCH_ID} or {CONF_PUMP_ON_SWITCH_ID} when using {CONF_PUMP_SWITCH_ID}" - ) - if CONF_PUMP_PULSE_DURATION not in sprinkler_controller and ( - CONF_PUMP_OFF_SWITCH_ID in valve or CONF_PUMP_ON_SWITCH_ID in valve - ): - raise cv.Invalid( - f"{CONF_PUMP_PULSE_DURATION} must be specified when using {CONF_PUMP_OFF_SWITCH_ID} and {CONF_PUMP_ON_SWITCH_ID}" - ) - if ( - CONF_VALVE_OFF_SWITCH_ID in valve - and CONF_VALVE_ON_SWITCH_ID not in valve - ) or ( - CONF_VALVE_ON_SWITCH_ID in valve - and CONF_VALVE_OFF_SWITCH_ID not in valve - ): - raise cv.Invalid( - f"Both {CONF_VALVE_OFF_SWITCH_ID} and {CONF_VALVE_ON_SWITCH_ID} must be specified for latching valve configuration" - ) - if CONF_VALVE_SWITCH_ID in valve and ( - CONF_VALVE_OFF_SWITCH_ID in valve or CONF_VALVE_ON_SWITCH_ID in valve - ): - raise cv.Invalid( - f"Do not specify {CONF_VALVE_OFF_SWITCH_ID} or {CONF_VALVE_ON_SWITCH_ID} when using {CONF_VALVE_SWITCH_ID}" - ) - if CONF_VALVE_PULSE_DURATION not in sprinkler_controller and ( - CONF_VALVE_OFF_SWITCH_ID in valve or CONF_VALVE_ON_SWITCH_ID in valve - ): - raise cv.Invalid( - f"{CONF_VALVE_PULSE_DURATION} must be specified when using {CONF_VALVE_OFF_SWITCH_ID} and {CONF_VALVE_ON_SWITCH_ID}" - ) - if ( - CONF_VALVE_SWITCH_ID not in valve - and CONF_VALVE_OFF_SWITCH_ID not in valve - and CONF_VALVE_ON_SWITCH_ID not in valve - ): - raise cv.Invalid( - f"Either {CONF_VALVE_SWITCH_ID} or {CONF_VALVE_OFF_SWITCH_ID} and {CONF_VALVE_ON_SWITCH_ID} must be specified in valve configuration" + f"{CONF_VALVE_SWITCH_ID} must be specified in valve configuration" ) if CONF_RUN_DURATION not in valve and CONF_RUN_DURATION_NUMBER not in valve: raise cv.Invalid( @@ -290,8 +245,15 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema( ), key=CONF_NAME, ), - cv.Optional(CONF_PUMP_OFF_SWITCH_ID): cv.use_id(switch.Switch), - cv.Optional(CONF_PUMP_ON_SWITCH_ID): cv.use_id(switch.Switch), + # Removed latching pump keys - accepted for validation error reporting + cv.Optional(CONF_PUMP_OFF_SWITCH_ID): cv.invalid( + f"This option was removed in 2026.1.0; for latching pumps, use {CONF_PUMP_SWITCH_ID} with an H-Bridge switch. " + f"See {docs_url('components/switch/h_bridge')} for more information" + ), + cv.Optional(CONF_PUMP_ON_SWITCH_ID): cv.invalid( + f"This option was removed in 2026.1.0; for latching pumps, use {CONF_PUMP_SWITCH_ID} with an H-Bridge switch. " + f"See {docs_url('components/switch/h_bridge')} for more information" + ), cv.Optional(CONF_PUMP_SWITCH_ID): cv.use_id(switch.Switch), cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_seconds, cv.Optional(CONF_RUN_DURATION_NUMBER): cv.maybe_simple_value( @@ -321,8 +283,15 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema( switch.switch_schema(SprinklerControllerSwitch), key=CONF_NAME, ), - cv.Optional(CONF_VALVE_OFF_SWITCH_ID): cv.use_id(switch.Switch), - cv.Optional(CONF_VALVE_ON_SWITCH_ID): cv.use_id(switch.Switch), + # Removed latching valve keys - accepted for validation error reporting + cv.Optional(CONF_VALVE_OFF_SWITCH_ID): cv.invalid( + f"This option was removed in 2026.1.0; for latching valves, use {CONF_VALVE_SWITCH_ID} with an H-Bridge switch. " + f"See {docs_url('components/switch/h_bridge')} for more information" + ), + cv.Optional(CONF_VALVE_ON_SWITCH_ID): cv.invalid( + f"This option was removed in 2026.1.0; for latching valves, use {CONF_VALVE_SWITCH_ID} with an H-Bridge switch. " + f"See {docs_url('components/switch/h_bridge')} for more information" + ), cv.Optional(CONF_VALVE_SWITCH_ID): cv.use_id(switch.Switch), } ) @@ -410,8 +379,15 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema( validate_min_max, key=CONF_NAME, ), - cv.Optional(CONF_PUMP_PULSE_DURATION): cv.positive_time_period_milliseconds, - cv.Optional(CONF_VALVE_PULSE_DURATION): cv.positive_time_period_milliseconds, + # Removed latching valve keys - accepted for validation error reporting + cv.Optional(CONF_PUMP_PULSE_DURATION): cv.invalid( + f"This option was removed in 2026.1.0; for latching pumps, use {CONF_PUMP_SWITCH_ID} with an H-Bridge switch. " + f"See {docs_url('components/switch/h_bridge')} for more information" + ), + cv.Optional(CONF_VALVE_PULSE_DURATION): cv.invalid( + f"This option was removed in 2026.1.0; for latching valves, use {CONF_VALVE_SWITCH_ID} with an H-Bridge switch. " + f"See {docs_url('components/switch/h_bridge')} for more information" + ), cv.Exclusive( CONF_PUMP_START_PUMP_DELAY, "pump_start_xxxx_delay" ): cv.positive_time_period_seconds, @@ -765,35 +741,10 @@ async def to_code(config): valve_index, valve_switch, valve[CONF_RUN_DURATION] ) ) - elif CONF_VALVE_OFF_SWITCH_ID in valve and CONF_VALVE_ON_SWITCH_ID in valve: - valve_switch_off = await cg.get_variable( - valve[CONF_VALVE_OFF_SWITCH_ID] - ) - valve_switch_on = await cg.get_variable(valve[CONF_VALVE_ON_SWITCH_ID]) - cg.add( - var.configure_valve_switch_pulsed( - valve_index, - valve_switch_off, - valve_switch_on, - sprinkler_controller[CONF_VALVE_PULSE_DURATION], - valve[CONF_RUN_DURATION], - ) - ) if CONF_PUMP_SWITCH_ID in valve: pump = await cg.get_variable(valve[CONF_PUMP_SWITCH_ID]) cg.add(var.configure_valve_pump_switch(valve_index, pump)) - elif CONF_PUMP_OFF_SWITCH_ID in valve and CONF_PUMP_ON_SWITCH_ID in valve: - pump_off = await cg.get_variable(valve[CONF_PUMP_OFF_SWITCH_ID]) - pump_on = await cg.get_variable(valve[CONF_PUMP_ON_SWITCH_ID]) - cg.add( - var.configure_valve_pump_switch_pulsed( - valve_index, - pump_off, - pump_on, - sprinkler_controller[CONF_PUMP_PULSE_DURATION], - ) - ) if CONF_RUN_DURATION_NUMBER in valve: num_rd_var = await number.new_number( diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index 69452f2e9e..ca9f85abd8 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -11,70 +11,6 @@ namespace esphome::sprinkler { static const char *const TAG = "sprinkler"; -SprinklerSwitch::SprinklerSwitch() {} -SprinklerSwitch::SprinklerSwitch(switch_::Switch *sprinkler_switch) : on_switch_(sprinkler_switch) {} -SprinklerSwitch::SprinklerSwitch(switch_::Switch *off_switch, switch_::Switch *on_switch, uint32_t pulse_duration) - : pulse_duration_(pulse_duration), off_switch_(off_switch), on_switch_(on_switch) {} - -bool SprinklerSwitch::is_latching_valve() { return (this->off_switch_ != nullptr) && (this->on_switch_ != nullptr); } - -void SprinklerSwitch::loop() { - if ((this->pinned_millis_) && (App.get_loop_component_start_time() > this->pinned_millis_ + this->pulse_duration_)) { - this->pinned_millis_ = 0; // reset tracker - if (this->off_switch_->state) { - this->off_switch_->turn_off(); - } - if (this->on_switch_->state) { - this->on_switch_->turn_off(); - } - } -} - -void SprinklerSwitch::turn_off() { - if (!this->state()) { // do nothing if we're already in the requested state - return; - } - if (this->off_switch_ != nullptr) { // latching valve, start a pulse - if (!this->off_switch_->state) { - this->off_switch_->turn_on(); - } - this->pinned_millis_ = millis(); - } else if (this->on_switch_ != nullptr) { // non-latching valve - this->on_switch_->turn_off(); - } - this->state_ = false; -} - -void SprinklerSwitch::turn_on() { - if (this->state()) { // do nothing if we're already in the requested state - return; - } - if (this->off_switch_ != nullptr) { // latching valve, start a pulse - if (!this->on_switch_->state) { - this->on_switch_->turn_on(); - } - this->pinned_millis_ = millis(); - } else if (this->on_switch_ != nullptr) { // non-latching valve - this->on_switch_->turn_on(); - } - this->state_ = true; -} - -bool SprinklerSwitch::state() { - if ((this->off_switch_ == nullptr) && (this->on_switch_ != nullptr)) { // latching valve is not configured... - return this->on_switch_->state; // ...so just return the pump switch state - } - return this->state_; -} - -void SprinklerSwitch::sync_valve_state(bool latch_state) { - if (this->is_latching_valve()) { - this->state_ = latch_state; - } else if (this->on_switch_ != nullptr) { - this->state_ = this->on_switch_->state; - } -} - void SprinklerControllerNumber::setup() { float value; if (!this->restore_value_) { @@ -219,8 +155,8 @@ void SprinklerValveOperator::start() { this->state_ = STARTING; // STARTING state requires both a pump and a start_delay_ if (this->start_delay_is_valve_delay_) { this->pump_on_(); - } else if (!this->pump_switch()->state()) { // if the pump is already on, wait to switch on the valve - this->valve_on_(); // to ensure consistent run time + } else if (!this->pump_switch()->state) { // if the pump is already on, wait to switch on the valve + this->valve_on_(); // to ensure consistent run time } } else { this->run_(); // there is no start_delay_, so just start the pump and valve @@ -240,8 +176,8 @@ void SprinklerValveOperator::stop() { } else { this->valve_off_(); } - if (this->pump_switch()->state()) { // if the pump is still on at this point, it may be in use... - this->valve_off_(); // ...so just switch the valve off now to ensure consistent run time + if (this->pump_switch()->state) { // if the pump is still on at this point, it may be in use... + this->valve_off_(); // ...so just switch the valve off now to ensure consistent run time } } else { this->kill_(); // there is no stop_delay_, so just stop the pump and valve @@ -274,7 +210,7 @@ uint32_t SprinklerValveOperator::time_remaining() { SprinklerState SprinklerValveOperator::state() { return this->state_; } -SprinklerSwitch *SprinklerValveOperator::pump_switch() { +switch_::Switch *SprinklerValveOperator::pump_switch() { if ((this->controller_ == nullptr) || (this->valve_ == nullptr)) { return nullptr; } @@ -285,48 +221,50 @@ SprinklerSwitch *SprinklerValveOperator::pump_switch() { } void SprinklerValveOperator::pump_off_() { - if ((this->valve_ == nullptr) || (this->pump_switch() == nullptr)) { // safety first! + auto *pump = this->pump_switch(); + if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first! return; } if (this->controller_ == nullptr) { // safety first! - this->pump_switch()->turn_off(); // if no controller was set, just switch off the pump + pump->turn_off(); // if no controller was set, just switch off the pump } else { // ...otherwise, do it "safely" auto state = this->state_; // this is silly, but... this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does - this->controller_->set_pump_state(this->pump_switch(), false); + this->controller_->set_pump_state(pump, false); this->state_ = state; } } void SprinklerValveOperator::pump_on_() { - if ((this->valve_ == nullptr) || (this->pump_switch() == nullptr)) { // safety first! + auto *pump = this->pump_switch(); + if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first! return; } if (this->controller_ == nullptr) { // safety first! - this->pump_switch()->turn_on(); // if no controller was set, just switch on the pump + pump->turn_on(); // if no controller was set, just switch on the pump } else { // ...otherwise, do it "safely" auto state = this->state_; // this is silly, but... this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does - this->controller_->set_pump_state(this->pump_switch(), true); + this->controller_->set_pump_state(pump, true); this->state_ = state; } } void SprinklerValveOperator::valve_off_() { - if (this->valve_ == nullptr) { // safety first! + if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first! return; } - if (this->valve_->valve_switch.state()) { - this->valve_->valve_switch.turn_off(); + if (this->valve_->valve_switch->state) { + this->valve_->valve_switch->turn_off(); } } void SprinklerValveOperator::valve_on_() { - if (this->valve_ == nullptr) { // safety first! + if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first! return; } - if (!this->valve_->valve_switch.state()) { - this->valve_->valve_switch.turn_on(); + if (!this->valve_->valve_switch->state) { + this->valve_->valve_switch->turn_on(); } } @@ -401,12 +339,6 @@ Sprinkler::Sprinkler(const std::string &name) { void Sprinkler::setup() { this->all_valves_off_(true); } void Sprinkler::loop() { - for (auto &p : this->pump_) { - p.loop(); - } - for (auto &v : this->valve_) { - v.valve_switch.loop(); - } for (auto &vo : this->valve_op_) { vo.loop(); } @@ -423,10 +355,15 @@ void Sprinkler::add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControll new_valve->controller_switch = valve_sw; new_valve->controller_switch->set_state_lambda([this, new_valve_number]() -> optional { - if (this->valve_pump_switch(new_valve_number) != nullptr) { - return this->valve_switch(new_valve_number)->state() && this->valve_pump_switch(new_valve_number)->state(); + auto *valve = this->valve_switch(new_valve_number); + auto *pump = this->valve_pump_switch(new_valve_number); + if (valve == nullptr) { + return false; } - return this->valve_switch(new_valve_number)->state(); + if (pump != nullptr) { + return valve->state && pump->state; + } + return valve->state; }); new_valve->valve_turn_off_automation = @@ -496,18 +433,7 @@ void Sprinkler::set_controller_repeat_number(SprinklerControllerNumber *repeat_n void Sprinkler::configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration) { if (this->is_a_valid_valve(valve_number)) { - this->valve_[valve_number].valve_switch.set_on_switch(valve_switch); - this->valve_[valve_number].run_duration = run_duration; - } -} - -void Sprinkler::configure_valve_switch_pulsed(size_t valve_number, switch_::Switch *valve_switch_off, - switch_::Switch *valve_switch_on, uint32_t pulse_duration, - uint32_t run_duration) { - if (this->is_a_valid_valve(valve_number)) { - this->valve_[valve_number].valve_switch.set_off_switch(valve_switch_off); - this->valve_[valve_number].valve_switch.set_on_switch(valve_switch_on); - this->valve_[valve_number].valve_switch.set_pulse_duration(pulse_duration); + this->valve_[valve_number].valve_switch = valve_switch; this->valve_[valve_number].run_duration = run_duration; } } @@ -515,31 +441,12 @@ void Sprinkler::configure_valve_switch_pulsed(size_t valve_number, switch_::Swit void Sprinkler::configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch) { if (this->is_a_valid_valve(valve_number)) { for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump - if (this->pump_[i].on_switch() == pump_switch) { // if the "new" pump matches one we already have... - this->valve_[valve_number].pump_switch_index = i; // ...save its index in the SprinklerSwitch vector pump_... + if (this->pump_[i] == pump_switch) { // if the "new" pump matches one we already have... + this->valve_[valve_number].pump_switch_index = i; // ...save its index in the pump vector... return; // ...and we are done } - } // if we end up here, no pumps matched, so add a new one and set the valve's SprinklerSwitch at it - this->pump_.resize(this->pump_.size() + 1); - this->pump_.back().set_on_switch(pump_switch); - this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump - } -} - -void Sprinkler::configure_valve_pump_switch_pulsed(size_t valve_number, switch_::Switch *pump_switch_off, - switch_::Switch *pump_switch_on, uint32_t pulse_duration) { - if (this->is_a_valid_valve(valve_number)) { - for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump - if ((this->pump_[i].off_switch() == pump_switch_off) && - (this->pump_[i].on_switch() == pump_switch_on)) { // if the "new" pump matches one we already have... - this->valve_[valve_number].pump_switch_index = i; // ...save its index in the SprinklerSwitch vector pump_... - return; // ...and we are done - } - } // if we end up here, no pumps matched, so add a new one and set the valve's SprinklerSwitch at it - this->pump_.resize(this->pump_.size() + 1); - this->pump_.back().set_off_switch(pump_switch_off); - this->pump_.back().set_on_switch(pump_switch_on); - this->pump_.back().set_pulse_duration(pulse_duration); + } // if we end up here, no pumps matched, so add a new one + this->pump_.push_back(pump_switch); this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump } } @@ -1041,7 +948,7 @@ size_t Sprinkler::number_of_valves() { return this->valve_.size(); } bool Sprinkler::is_a_valid_valve(const size_t valve_number) { return (valve_number < this->number_of_valves()); } -bool Sprinkler::pump_in_use(SprinklerSwitch *pump_switch) { +bool Sprinkler::pump_in_use(switch_::Switch *pump_switch) { if (pump_switch == nullptr) { return false; // we can't do anything if there's nothing to check } @@ -1054,8 +961,7 @@ bool Sprinkler::pump_in_use(SprinklerSwitch *pump_switch) { for (auto &vo : this->valve_op_) { // first, check if any SprinklerValveOperator has a valve dependent on this pump if ((vo.state() != BYPASS) && (vo.pump_switch() != nullptr)) { // the SprinklerValveOperator is configured with a pump; now check if it is the pump of interest - if ((vo.pump_switch()->off_switch() == pump_switch->off_switch()) && - (vo.pump_switch()->on_switch() == pump_switch->on_switch())) { + if (vo.pump_switch() == pump_switch) { // now if the SprinklerValveOperator has a pump and it is either ACTIVE, is STARTING with a valve delay or // is STOPPING with a valve delay, its pump can be considered "in use", so just return indicating this now if ((vo.state() == ACTIVE) || @@ -1074,13 +980,12 @@ bool Sprinkler::pump_in_use(SprinklerSwitch *pump_switch) { if (valve_pump == nullptr) { return false; // valve has no pump, so this pump isn't in use by it } - return (pump_switch->off_switch() == valve_pump->off_switch()) && - (pump_switch->on_switch() == valve_pump->on_switch()); + return pump_switch == valve_pump; } return false; } -void Sprinkler::set_pump_state(SprinklerSwitch *pump_switch, bool state) { +void Sprinkler::set_pump_state(switch_::Switch *pump_switch, bool state) { if (pump_switch == nullptr) { return; // we can't do anything if there's nothing to check } @@ -1091,15 +996,10 @@ void Sprinkler::set_pump_state(SprinklerSwitch *pump_switch, bool state) { if (controller != this) { // dummy check if (controller->pump_in_use(pump_switch)) { hold_pump_on = true; // if another controller says it's using this pump, keep it on - // at this point we know if there exists another SprinklerSwitch that is "on" with its - // off_switch_ and on_switch_ pointers pointing to the same pair of switch objects } } } if (hold_pump_on) { - // at this point we know if there exists another SprinklerSwitch that is "on" with its - // off_switch_ and on_switch_ pointers pointing to the same pair of switch objects... - pump_switch->sync_valve_state(true); // ...so ensure our state is consistent ESP_LOGD(TAG, "Leaving pump on because another controller instance is using it"); } @@ -1107,8 +1007,6 @@ void Sprinkler::set_pump_state(SprinklerSwitch *pump_switch, bool state) { pump_switch->turn_on(); } else if (!hold_pump_on && !this->pump_in_use(pump_switch)) { pump_switch->turn_off(); - } else if (hold_pump_on) { // we must assume the other controller will switch off the pump when done... - pump_switch->sync_valve_state(false); // ...this only impacts latching valves } } @@ -1274,23 +1172,23 @@ SprinklerControllerSwitch *Sprinkler::enable_switch(size_t valve_number) { return nullptr; } -SprinklerSwitch *Sprinkler::valve_switch(const size_t valve_number) { +switch_::Switch *Sprinkler::valve_switch(const size_t valve_number) { if (this->is_a_valid_valve(valve_number)) { - return &this->valve_[valve_number].valve_switch; + return this->valve_[valve_number].valve_switch; } return nullptr; } -SprinklerSwitch *Sprinkler::valve_pump_switch(const size_t valve_number) { +switch_::Switch *Sprinkler::valve_pump_switch(const size_t valve_number) { if (this->is_a_valid_valve(valve_number) && this->valve_[valve_number].pump_switch_index.has_value()) { - return &this->pump_[this->valve_[valve_number].pump_switch_index.value()]; + return this->pump_[this->valve_[valve_number].pump_switch_index.value()]; } return nullptr; } -SprinklerSwitch *Sprinkler::valve_pump_switch_by_pump_index(size_t pump_index) { +switch_::Switch *Sprinkler::valve_pump_switch_by_pump_index(size_t pump_index) { if (pump_index < this->pump_.size()) { - return &this->pump_[pump_index]; + return this->pump_[pump_index]; } return nullptr; } @@ -1454,8 +1352,9 @@ void Sprinkler::start_valve_(SprinklerValveRunRequest *req) { void Sprinkler::all_valves_off_(const bool include_pump) { for (size_t valve_index = 0; valve_index < this->number_of_valves(); valve_index++) { - if (this->valve_[valve_index].valve_switch.state()) { - this->valve_[valve_index].valve_switch.turn_off(); + auto *valve_sw = this->valve_[valve_index].valve_switch; + if ((valve_sw != nullptr) && valve_sw->state) { + valve_sw->turn_off(); } if (include_pump) { this->set_pump_state(this->valve_pump_switch(valve_index), false); @@ -1754,10 +1653,6 @@ void Sprinkler::dump_config() { " Name: %s\n" " Run Duration: %" PRIu32 " seconds", valve_number, this->valve_name(valve_number), this->valve_run_duration(valve_number)); - if (this->valve_[valve_number].valve_switch.pulse_duration()) { - ESP_LOGCONFIG(TAG, " Pulse Duration: %" PRIu32 " milliseconds", - this->valve_[valve_number].valve_switch.pulse_duration()); - } } if (!this->pump_.empty()) { ESP_LOGCONFIG(TAG, " Total number of pumps: %zu", this->pump_.size()); diff --git a/esphome/components/sprinkler/sprinkler.h b/esphome/components/sprinkler/sprinkler.h index 7aa33c2df9..25e2d42446 100644 --- a/esphome/components/sprinkler/sprinkler.h +++ b/esphome/components/sprinkler/sprinkler.h @@ -35,7 +35,6 @@ enum SprinklerValveRunRequestOrigin : uint8_t { class Sprinkler; // this component class SprinklerControllerNumber; // number components that appear in the front end; based on number core class SprinklerControllerSwitch; // switches that appear in the front end; based on switch core -class SprinklerSwitch; // switches representing any valve or pump; provides abstraction for latching valves class SprinklerValveOperator; // manages all switching on/off of valves and associated pumps class SprinklerValveRunRequest; // tells the sprinkler controller what valve to run and for how long as well as what // SprinklerValveOperator is handling it @@ -43,34 +42,6 @@ template class StartSingleValveAction; template class ShutdownAction; template class ResumeOrStartAction; -class SprinklerSwitch { - public: - SprinklerSwitch(); - SprinklerSwitch(switch_::Switch *sprinkler_switch); - SprinklerSwitch(switch_::Switch *off_switch, switch_::Switch *on_switch, uint32_t pulse_duration); - - bool is_latching_valve(); // returns true if configured as a latching valve - void loop(); // called as a part of loop(), used for latching valve pulses - uint32_t pulse_duration() { return this->pulse_duration_; } - bool state(); // returns the switch's current state - void set_off_switch(switch_::Switch *off_switch) { this->off_switch_ = off_switch; } - void set_on_switch(switch_::Switch *on_switch) { this->on_switch_ = on_switch; } - void set_pulse_duration(uint32_t pulse_duration) { this->pulse_duration_ = pulse_duration; } - void sync_valve_state( - bool latch_state); // syncs internal state to switch; if latching valve, sets state to latch_state - void turn_off(); // sets internal flag and actuates the switch - void turn_on(); // sets internal flag and actuates the switch - switch_::Switch *off_switch() { return this->off_switch_; } - switch_::Switch *on_switch() { return this->on_switch_; } - - protected: - bool state_{false}; - uint32_t pulse_duration_{0}; - uint64_t pinned_millis_{0}; - switch_::Switch *off_switch_{nullptr}; // only used for latching valves - switch_::Switch *on_switch_{nullptr}; // used for both latching and non-latching valves -}; - struct SprinklerQueueItem { size_t valve_number; uint32_t run_duration; @@ -88,7 +59,7 @@ struct SprinklerValve { SprinklerControllerNumber *run_duration_number; SprinklerControllerSwitch *controller_switch; SprinklerControllerSwitch *enable_switch; - SprinklerSwitch valve_switch; + switch_::Switch *valve_switch; uint32_t run_duration; optional pump_switch_index; bool valve_cycle_complete; @@ -155,7 +126,7 @@ class SprinklerValveOperator { uint32_t run_duration(); // returns the desired run duration in seconds uint32_t time_remaining(); // returns seconds remaining (does not include stop_delay_) SprinklerState state(); // returns the valve's state/status - SprinklerSwitch *pump_switch(); // returns this SprinklerValveOperator's pump's SprinklerSwitch + switch_::Switch *pump_switch(); // returns this SprinklerValveOperator's pump switch protected: void pump_off_(); @@ -228,13 +199,9 @@ class Sprinkler : public Component { /// configure a valve's switch object and run duration. run_duration is time in seconds. void configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration); - void configure_valve_switch_pulsed(size_t valve_number, switch_::Switch *valve_switch_off, - switch_::Switch *valve_switch_on, uint32_t pulse_duration, uint32_t run_duration); /// configure a valve's associated pump switch object void configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch); - void configure_valve_pump_switch_pulsed(size_t valve_number, switch_::Switch *pump_switch_off, - switch_::Switch *pump_switch_on, uint32_t pulse_duration); /// configure a valve's run duration number component void configure_valve_run_duration_number(size_t valve_number, SprinklerControllerNumber *run_duration_number); @@ -383,10 +350,10 @@ class Sprinkler : public Component { bool is_a_valid_valve(size_t valve_number); /// returns true if the pump the pointer points to is in use - bool pump_in_use(SprinklerSwitch *pump_switch); + bool pump_in_use(switch_::Switch *pump_switch); /// switches on/off a pump "safely" by checking that the new state will not conflict with another controller - void set_pump_state(SprinklerSwitch *pump_switch, bool state); + void set_pump_state(switch_::Switch *pump_switch, bool state); /// returns the amount of time in seconds required for all valves uint32_t total_cycle_time_all_valves(); @@ -419,13 +386,13 @@ class Sprinkler : public Component { SprinklerControllerSwitch *enable_switch(size_t valve_number); /// returns a pointer to a valve's switch object - SprinklerSwitch *valve_switch(size_t valve_number); + switch_::Switch *valve_switch(size_t valve_number); /// returns a pointer to a valve's pump switch object - SprinklerSwitch *valve_pump_switch(size_t valve_number); + switch_::Switch *valve_pump_switch(size_t valve_number); /// returns a pointer to a valve's pump switch object - SprinklerSwitch *valve_pump_switch_by_pump_index(size_t pump_index); + switch_::Switch *valve_pump_switch_by_pump_index(size_t pump_index); protected: /// returns true if valve number is enabled @@ -577,8 +544,8 @@ class Sprinkler : public Component { /// Queue of valves to activate next, regardless of auto-advance std::vector queued_valves_; - /// Sprinkler valve pump objects - std::vector pump_; + /// Sprinkler valve pump switches + std::vector pump_; /// Sprinkler valve objects std::vector valve_; From 918bc4b74f2f9af49d1c3efa8846d392e1ee324e Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 22 Dec 2025 15:41:14 -0500 Subject: [PATCH 0763/1145] [esp32] Remove remaining using_esp_idf checks (#12623) Co-authored-by: Claude --- esphome/components/captive_portal/__init__.py | 2 +- esphome/components/i2c/__init__.py | 2 +- esphome/components/i2s_audio/__init__.py | 5 +++-- esphome/components/improv_serial/__init__.py | 2 +- esphome/components/mdns/__init__.py | 6 ++---- esphome/components/network/__init__.py | 10 +++++----- esphome/components/wifi/__init__.py | 4 ++-- esphome/core/__init__.py | 5 +++++ 8 files changed, 20 insertions(+), 16 deletions(-) diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 25d0a22083..763e2e4ec5 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -25,7 +25,7 @@ _LOGGER = logging.getLogger(__name__) def AUTO_LOAD() -> list[str]: auto_load = ["web_server_base", "ota.web_server"] - if CORE.using_esp_idf: + if CORE.is_esp32: auto_load.append("socket") return auto_load diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index b7436ccc39..56e0c8e4ab 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -146,7 +146,7 @@ def _final_validate(config): full_config = fv.full_config.get()[CONF_I2C] if CORE.using_zephyr and len(full_config) > 1: raise cv.Invalid("Second i2c is not implemented on Zephyr yet") - if CORE.using_esp_idf and get_esp32_variant() in ESP32_I2C_CAPABILITIES: + if CORE.is_esp32 and get_esp32_variant() in ESP32_I2C_CAPABILITIES: variant = get_esp32_variant() max_num = ESP32_I2C_CAPABILITIES[variant]["NUM"] if len(full_config) > max_num: diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index 61c5ca4ec1..d3128c5f4c 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -232,6 +232,8 @@ def validate_use_legacy(value): if (not value[CONF_USE_LEGACY]) and (CORE.using_arduino): raise cv.Invalid("Arduino supports only the legacy i2s driver") _set_use_legacy_driver(value[CONF_USE_LEGACY]) + elif CORE.using_arduino: + _set_use_legacy_driver(True) return value @@ -261,8 +263,7 @@ def _final_validate(_): def use_legacy(): - legacy_driver = _get_use_legacy_driver() - return not (CORE.using_esp_idf and not legacy_driver) + return _get_use_legacy_driver() FINAL_VALIDATE_SCHEMA = _final_validate diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index 7f88b17e11..9a2ac2f40f 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -26,7 +26,7 @@ def validate_logger(config): logger_conf = fv.full_config.get()[CONF_LOGGER] if logger_conf[CONF_BAUD_RATE] == 0: raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0") - if CORE.using_esp_idf and ( + if CORE.is_esp32 and ( logger_conf[CONF_HARDWARE_UART] == USB_CDC and get_esp32_variant() == VARIANT_ESP32S3 ): diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index 99b728b249..3088d8ad7e 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -157,14 +157,12 @@ async def to_code(config): return if CORE.using_arduino: - if CORE.is_esp32: - cg.add_library("ESPmDNS", None) - elif CORE.is_esp8266: + if CORE.is_esp8266: cg.add_library("ESP8266mDNS", None) elif CORE.is_rp2040: cg.add_library("LEAmDNS", None) - if CORE.using_esp_idf: + if CORE.is_esp32: add_idf_component(name="espressif/mdns", ref="1.9.1") cg.add_define("USE_MDNS") diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index d7a51fb0c6..5b63bbfce9 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -156,7 +156,7 @@ async def to_code(config): "High performance networking disabled by user configuration (overriding component request)" ) - if CORE.is_esp32 and CORE.using_esp_idf and should_enable: + if CORE.is_esp32 and should_enable: # Check if PSRAM is guaranteed (set by psram component during final validation) psram_guaranteed = psram_is_guaranteed() @@ -210,12 +210,12 @@ async def to_code(config): "USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT] ) if CORE.is_esp32: - if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6) - add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6) - else: + if CORE.using_arduino: add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", True) add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", True) + else: + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6) + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6) elif enable_ipv6: cg.add_build_flag("-DCONFIG_LWIP_IPV6") cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG") diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index fb23837e78..232e8d4f27 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -464,7 +464,7 @@ async def to_code(config): ) cg.add(var.set_ap_timeout(conf[CONF_AP_TIMEOUT])) cg.add_define("USE_WIFI_AP") - elif CORE.is_esp32 and CORE.using_esp_idf: + elif CORE.is_esp32 and not CORE.using_arduino: add_idf_sdkconfig_option("CONFIG_ESP_WIFI_SOFTAP_SUPPORT", False) add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False) @@ -509,7 +509,7 @@ async def to_code(config): add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True) # Apply high performance WiFi settings if high performance networking is enabled - if CORE.is_esp32 and CORE.using_esp_idf and has_high_performance_networking(): + if CORE.is_esp32 and has_high_performance_networking(): # Check if PSRAM is guaranteed (set by psram component during final validation) psram_guaranteed = psram_is_guaranteed() diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index ad9844a3bf..3baec93186 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -798,6 +798,11 @@ class EsphomeCore: @property def using_esp_idf(self): + _LOGGER.warning( + "CORE.using_esp_idf was deprecated in 2026.1, will change behavior in 2026.6. " + "ESP32 Arduino builds on top of ESP-IDF, so ESP-IDF features are available in both frameworks. " + "Use CORE.is_esp32 and/or CORE.using_arduino instead." + ) return self.target_framework == "esp-idf" @property From c8b531ac06d569197da9b6740dd7062f1a7e77d9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 11:13:51 -1000 Subject: [PATCH 0764/1145] [safe_mode] Defer preference sync in clean_rtc to avoid blocking event loop (#12625) --- esphome/components/safe_mode/safe_mode.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/safe_mode/safe_mode.cpp b/esphome/components/safe_mode/safe_mode.cpp index f8e5d7d8e5..c933222273 100644 --- a/esphome/components/safe_mode/safe_mode.cpp +++ b/esphome/components/safe_mode/safe_mode.cpp @@ -141,7 +141,14 @@ uint32_t SafeModeComponent::read_rtc_() { return val; } -void SafeModeComponent::clean_rtc() { this->write_rtc_(0); } +void SafeModeComponent::clean_rtc() { + // Save without sync - preferences will be written at shutdown or by IntervalSyncer. + // This avoids blocking the loop for 50+ ms on flash write. If the device crashes + // before sync, the boot wasn't really successful anyway and the counter should + // remain incremented. + uint32_t val = 0; + this->rtc_.save(&val); +} void SafeModeComponent::on_safe_shutdown() { if (this->read_rtc_() != SafeModeComponent::ENTER_SAFE_MODE_MAGIC) From bdbe72b7f15e0f0dc861546565798dfa9ed86fa9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 11:14:11 -1000 Subject: [PATCH 0765/1145] [web_server] Make internal JSON helper methods private (#12624) --- esphome/components/web_server/web_server.cpp | 182 +++++++++---------- esphome/components/web_server/web_server.h | 105 ++++++----- 2 files changed, 154 insertions(+), 133 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 207eafad5c..ece9d65121 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -455,7 +455,7 @@ void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlM // Note: request->method() is always HTTP_GET here (canHandle ensures this) if (match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->sensor_json(obj, obj->state, detail); + std::string data = this->sensor_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -463,12 +463,12 @@ void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlM request->send(404); } std::string WebServer::sensor_state_json_generator(WebServer *web_server, void *source) { - return web_server->sensor_json((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_STATE); + return web_server->sensor_json_((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_STATE); } std::string WebServer::sensor_all_json_generator(WebServer *web_server, void *source) { - return web_server->sensor_json((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_ALL); + return web_server->sensor_json_((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_ALL); } -std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) { +std::string WebServer::sensor_json_(sensor::Sensor *obj, float value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -500,7 +500,7 @@ void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const // Note: request->method() is always HTTP_GET here (canHandle ensures this) if (match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->text_sensor_json(obj, obj->state, detail); + std::string data = this->text_sensor_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -508,15 +508,15 @@ void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const request->send(404); } std::string WebServer::text_sensor_state_json_generator(WebServer *web_server, void *source) { - return web_server->text_sensor_json((text_sensor::TextSensor *) (source), - ((text_sensor::TextSensor *) (source))->state, DETAIL_STATE); + return web_server->text_sensor_json_((text_sensor::TextSensor *) (source), + ((text_sensor::TextSensor *) (source))->state, DETAIL_STATE); } std::string WebServer::text_sensor_all_json_generator(WebServer *web_server, void *source) { - return web_server->text_sensor_json((text_sensor::TextSensor *) (source), - ((text_sensor::TextSensor *) (source))->state, DETAIL_ALL); + return web_server->text_sensor_json_((text_sensor::TextSensor *) (source), + ((text_sensor::TextSensor *) (source))->state, DETAIL_ALL); } -std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, - JsonDetail start_config) { +std::string WebServer::text_sensor_json_(text_sensor::TextSensor *obj, const std::string &value, + JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -542,7 +542,7 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->switch_json(obj, obj->state, detail); + std::string data = this->switch_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -584,12 +584,12 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM request->send(404); } std::string WebServer::switch_state_json_generator(WebServer *web_server, void *source) { - return web_server->switch_json((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_STATE); + return web_server->switch_json_((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_STATE); } std::string WebServer::switch_all_json_generator(WebServer *web_server, void *source) { - return web_server->switch_json((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_ALL); + return web_server->switch_json_((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_ALL); } -std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) { +std::string WebServer::switch_json_(switch_::Switch *obj, bool value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -610,7 +610,7 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM continue; if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->button_json(obj, detail); + std::string data = this->button_json_(obj, detail); request->send(200, "application/json", data.c_str()); } else if (match.method_equals("press")) { this->defer([obj]() { obj->press(); }); @@ -624,12 +624,12 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM request->send(404); } std::string WebServer::button_state_json_generator(WebServer *web_server, void *source) { - return web_server->button_json((button::Button *) (source), DETAIL_STATE); + return web_server->button_json_((button::Button *) (source), DETAIL_STATE); } std::string WebServer::button_all_json_generator(WebServer *web_server, void *source) { - return web_server->button_json((button::Button *) (source), DETAIL_ALL); + return web_server->button_json_((button::Button *) (source), DETAIL_ALL); } -std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) { +std::string WebServer::button_json_(button::Button *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -655,7 +655,7 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con // Note: request->method() is always HTTP_GET here (canHandle ensures this) if (match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->binary_sensor_json(obj, obj->state, detail); + std::string data = this->binary_sensor_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -663,14 +663,14 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con request->send(404); } std::string WebServer::binary_sensor_state_json_generator(WebServer *web_server, void *source) { - return web_server->binary_sensor_json((binary_sensor::BinarySensor *) (source), - ((binary_sensor::BinarySensor *) (source))->state, DETAIL_STATE); + return web_server->binary_sensor_json_((binary_sensor::BinarySensor *) (source), + ((binary_sensor::BinarySensor *) (source))->state, DETAIL_STATE); } std::string WebServer::binary_sensor_all_json_generator(WebServer *web_server, void *source) { - return web_server->binary_sensor_json((binary_sensor::BinarySensor *) (source), - ((binary_sensor::BinarySensor *) (source))->state, DETAIL_ALL); + return web_server->binary_sensor_json_((binary_sensor::BinarySensor *) (source), + ((binary_sensor::BinarySensor *) (source))->state, DETAIL_ALL); } -std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { +std::string WebServer::binary_sensor_json_(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -696,7 +696,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->fan_json(obj, detail); + std::string data = this->fan_json_(obj, detail); request->send(200, "application/json", data.c_str()); } else if (match.method_equals("toggle")) { this->defer([obj]() { obj->toggle().perform(); }); @@ -738,12 +738,12 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc request->send(404); } std::string WebServer::fan_state_json_generator(WebServer *web_server, void *source) { - return web_server->fan_json((fan::Fan *) (source), DETAIL_STATE); + return web_server->fan_json_((fan::Fan *) (source), DETAIL_STATE); } std::string WebServer::fan_all_json_generator(WebServer *web_server, void *source) { - return web_server->fan_json((fan::Fan *) (source), DETAIL_ALL); + return web_server->fan_json_((fan::Fan *) (source), DETAIL_ALL); } -std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { +std::string WebServer::fan_json_(fan::Fan *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -776,7 +776,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->light_json(obj, detail); + std::string data = this->light_json_(obj, detail); request->send(200, "application/json", data.c_str()); } else if (match.method_equals("toggle")) { this->defer([obj]() { obj->toggle().perform(); }); @@ -816,12 +816,12 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa request->send(404); } std::string WebServer::light_state_json_generator(WebServer *web_server, void *source) { - return web_server->light_json((light::LightState *) (source), DETAIL_STATE); + return web_server->light_json_((light::LightState *) (source), DETAIL_STATE); } std::string WebServer::light_all_json_generator(WebServer *web_server, void *source) { - return web_server->light_json((light::LightState *) (source), DETAIL_ALL); + return web_server->light_json_((light::LightState *) (source), DETAIL_ALL); } -std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) { +std::string WebServer::light_json_(light::LightState *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -854,7 +854,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->cover_json(obj, detail); + std::string data = this->cover_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -903,12 +903,12 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa request->send(404); } std::string WebServer::cover_state_json_generator(WebServer *web_server, void *source) { - return web_server->cover_json((cover::Cover *) (source), DETAIL_STATE); + return web_server->cover_json_((cover::Cover *) (source), DETAIL_STATE); } std::string WebServer::cover_all_json_generator(WebServer *web_server, void *source) { - return web_server->cover_json((cover::Cover *) (source), DETAIL_ALL); + return web_server->cover_json_((cover::Cover *) (source), DETAIL_ALL); } -std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { +std::string WebServer::cover_json_(cover::Cover *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -942,7 +942,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->number_json(obj, obj->state, detail); + std::string data = this->number_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -962,12 +962,12 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM } std::string WebServer::number_state_json_generator(WebServer *web_server, void *source) { - return web_server->number_json((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_STATE); + return web_server->number_json_((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_STATE); } std::string WebServer::number_all_json_generator(WebServer *web_server, void *source) { - return web_server->number_json((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_ALL); + return web_server->number_json_((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_ALL); } -std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) { +std::string WebServer::number_json_(number::Number *obj, float value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1009,7 +1009,7 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat continue; if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->date_json(obj, detail); + std::string data = this->date_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1035,12 +1035,12 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat } std::string WebServer::date_state_json_generator(WebServer *web_server, void *source) { - return web_server->date_json((datetime::DateEntity *) (source), DETAIL_STATE); + return web_server->date_json_((datetime::DateEntity *) (source), DETAIL_STATE); } std::string WebServer::date_all_json_generator(WebServer *web_server, void *source) { - return web_server->date_json((datetime::DateEntity *) (source), DETAIL_ALL); + return web_server->date_json_((datetime::DateEntity *) (source), DETAIL_ALL); } -std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_config) { +std::string WebServer::date_json_(datetime::DateEntity *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1072,7 +1072,7 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat continue; if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->time_json(obj, detail); + std::string data = this->time_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1097,12 +1097,12 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat request->send(404); } std::string WebServer::time_state_json_generator(WebServer *web_server, void *source) { - return web_server->time_json((datetime::TimeEntity *) (source), DETAIL_STATE); + return web_server->time_json_((datetime::TimeEntity *) (source), DETAIL_STATE); } std::string WebServer::time_all_json_generator(WebServer *web_server, void *source) { - return web_server->time_json((datetime::TimeEntity *) (source), DETAIL_ALL); + return web_server->time_json_((datetime::TimeEntity *) (source), DETAIL_ALL); } -std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_config) { +std::string WebServer::time_json_(datetime::TimeEntity *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1134,7 +1134,7 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur continue; if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->datetime_json(obj, detail); + std::string data = this->datetime_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1159,12 +1159,12 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur request->send(404); } std::string WebServer::datetime_state_json_generator(WebServer *web_server, void *source) { - return web_server->datetime_json((datetime::DateTimeEntity *) (source), DETAIL_STATE); + return web_server->datetime_json_((datetime::DateTimeEntity *) (source), DETAIL_STATE); } std::string WebServer::datetime_all_json_generator(WebServer *web_server, void *source) { - return web_server->datetime_json((datetime::DateTimeEntity *) (source), DETAIL_ALL); + return web_server->datetime_json_((datetime::DateTimeEntity *) (source), DETAIL_ALL); } -std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config) { +std::string WebServer::datetime_json_(datetime::DateTimeEntity *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1199,7 +1199,7 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->text_json(obj, obj->state, detail); + std::string data = this->text_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1219,12 +1219,12 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat } std::string WebServer::text_state_json_generator(WebServer *web_server, void *source) { - return web_server->text_json((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_STATE); + return web_server->text_json_((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_STATE); } std::string WebServer::text_all_json_generator(WebServer *web_server, void *source) { - return web_server->text_json((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_ALL); + return web_server->text_json_((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_ALL); } -std::string WebServer::text_json(text::Text *obj, const std::string &value, JsonDetail start_config) { +std::string WebServer::text_json_(text::Text *obj, const std::string &value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1255,7 +1255,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->select_json(obj, obj->has_state() ? obj->current_option() : "", detail); + std::string data = this->select_json_(obj, obj->has_state() ? obj->current_option() : "", detail); request->send(200, "application/json", data.c_str()); return; } @@ -1276,13 +1276,13 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM } std::string WebServer::select_state_json_generator(WebServer *web_server, void *source) { auto *obj = (select::Select *) (source); - return web_server->select_json(obj, obj->has_state() ? obj->current_option() : "", DETAIL_STATE); + return web_server->select_json_(obj, obj->has_state() ? obj->current_option() : "", DETAIL_STATE); } std::string WebServer::select_all_json_generator(WebServer *web_server, void *source) { auto *obj = (select::Select *) (source); - return web_server->select_json(obj, obj->has_state() ? obj->current_option() : "", DETAIL_ALL); + return web_server->select_json_(obj, obj->has_state() ? obj->current_option() : "", DETAIL_ALL); } -std::string WebServer::select_json(select::Select *obj, const char *value, JsonDetail start_config) { +std::string WebServer::select_json_(select::Select *obj, const char *value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1312,7 +1312,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->climate_json(obj, detail); + std::string data = this->climate_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1342,13 +1342,13 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url } std::string WebServer::climate_state_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - return web_server->climate_json((climate::Climate *) (source), DETAIL_STATE); + return web_server->climate_json_((climate::Climate *) (source), DETAIL_STATE); } std::string WebServer::climate_all_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - return web_server->climate_json((climate::Climate *) (source), DETAIL_ALL); + return web_server->climate_json_((climate::Climate *) (source), DETAIL_ALL); } -std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { +std::string WebServer::climate_json_(climate::Climate *obj, JsonDetail start_config) { // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1456,7 +1456,7 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->lock_json(obj, obj->state, detail); + std::string data = this->lock_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1498,12 +1498,12 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat request->send(404); } std::string WebServer::lock_state_json_generator(WebServer *web_server, void *source) { - return web_server->lock_json((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_STATE); + return web_server->lock_json_((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_STATE); } std::string WebServer::lock_all_json_generator(WebServer *web_server, void *source) { - return web_server->lock_json((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_ALL); + return web_server->lock_json_((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_ALL); } -std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { +std::string WebServer::lock_json_(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1530,7 +1530,7 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->valve_json(obj, detail); + std::string data = this->valve_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1577,12 +1577,12 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa request->send(404); } std::string WebServer::valve_state_json_generator(WebServer *web_server, void *source) { - return web_server->valve_json((valve::Valve *) (source), DETAIL_STATE); + return web_server->valve_json_((valve::Valve *) (source), DETAIL_STATE); } std::string WebServer::valve_all_json_generator(WebServer *web_server, void *source) { - return web_server->valve_json((valve::Valve *) (source), DETAIL_ALL); + return web_server->valve_json_((valve::Valve *) (source), DETAIL_ALL); } -std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) { +std::string WebServer::valve_json_(valve::Valve *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1614,7 +1614,7 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->alarm_control_panel_json(obj, obj->get_state(), detail); + std::string data = this->alarm_control_panel_json_(obj, obj->get_state(), detail); request->send(200, "application/json", data.c_str()); return; } @@ -1655,18 +1655,18 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques request->send(404); } std::string WebServer::alarm_control_panel_state_json_generator(WebServer *web_server, void *source) { - return web_server->alarm_control_panel_json((alarm_control_panel::AlarmControlPanel *) (source), - ((alarm_control_panel::AlarmControlPanel *) (source))->get_state(), - DETAIL_STATE); + return web_server->alarm_control_panel_json_((alarm_control_panel::AlarmControlPanel *) (source), + ((alarm_control_panel::AlarmControlPanel *) (source))->get_state(), + DETAIL_STATE); } std::string WebServer::alarm_control_panel_all_json_generator(WebServer *web_server, void *source) { - return web_server->alarm_control_panel_json((alarm_control_panel::AlarmControlPanel *) (source), - ((alarm_control_panel::AlarmControlPanel *) (source))->get_state(), - DETAIL_ALL); + return web_server->alarm_control_panel_json_((alarm_control_panel::AlarmControlPanel *) (source), + ((alarm_control_panel::AlarmControlPanel *) (source))->get_state(), + DETAIL_ALL); } -std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj, - alarm_control_panel::AlarmControlPanelState value, - JsonDetail start_config) { +std::string WebServer::alarm_control_panel_json_(alarm_control_panel::AlarmControlPanel *obj, + alarm_control_panel::AlarmControlPanelState value, + JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1696,7 +1696,7 @@ void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMa // Note: request->method() is always HTTP_GET here (canHandle ensures this) if (match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->event_json(obj, "", detail); + std::string data = this->event_json_(obj, "", detail); request->send(200, "application/json", data.c_str()); return; } @@ -1711,14 +1711,14 @@ static std::string get_event_type(event::Event *event) { std::string WebServer::event_state_json_generator(WebServer *web_server, void *source) { auto *event = static_cast(source); - return web_server->event_json(event, get_event_type(event), DETAIL_STATE); + return web_server->event_json_(event, get_event_type(event), DETAIL_STATE); } // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson std::string WebServer::event_all_json_generator(WebServer *web_server, void *source) { auto *event = static_cast(source); - return web_server->event_json(event, get_event_type(event), DETAIL_ALL); + return web_server->event_json_(event, get_event_type(event), DETAIL_ALL); } -std::string WebServer::event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config) { +std::string WebServer::event_json_(event::Event *obj, const std::string &event_type, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1764,7 +1764,7 @@ void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlM if (request->method() == HTTP_GET && match.method_empty()) { auto detail = get_request_detail(request); - std::string data = this->update_json(obj, detail); + std::string data = this->update_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1782,13 +1782,13 @@ void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlM } std::string WebServer::update_state_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - return web_server->update_json((update::UpdateEntity *) (source), DETAIL_STATE); + return web_server->update_json_((update::UpdateEntity *) (source), DETAIL_STATE); } std::string WebServer::update_all_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - return web_server->update_json((update::UpdateEntity *) (source), DETAIL_STATE); + return web_server->update_json_((update::UpdateEntity *) (source), DETAIL_STATE); } -std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_config) { +std::string WebServer::update_json_(update::UpdateEntity *obj, JsonDetail start_config) { // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson json::JsonBuilder builder; JsonObject root = builder.root(); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 98234ec1ae..0078146284 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -271,8 +271,6 @@ class WebServer : public Controller, static std::string sensor_state_json_generator(WebServer *web_server, void *source); static std::string sensor_all_json_generator(WebServer *web_server, void *source); - /// Dump the sensor state with its value as a JSON string. - std::string sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config); #endif #ifdef USE_SWITCH @@ -283,8 +281,6 @@ class WebServer : public Controller, static std::string switch_state_json_generator(WebServer *web_server, void *source); static std::string switch_all_json_generator(WebServer *web_server, void *source); - /// Dump the switch state with its value as a JSON string. - std::string switch_json(switch_::Switch *obj, bool value, JsonDetail start_config); #endif #ifdef USE_BUTTON @@ -293,8 +289,6 @@ class WebServer : public Controller, static std::string button_state_json_generator(WebServer *web_server, void *source); static std::string button_all_json_generator(WebServer *web_server, void *source); - /// Dump the button details with its value as a JSON string. - std::string button_json(button::Button *obj, JsonDetail start_config); #endif #ifdef USE_BINARY_SENSOR @@ -305,8 +299,6 @@ class WebServer : public Controller, static std::string binary_sensor_state_json_generator(WebServer *web_server, void *source); static std::string binary_sensor_all_json_generator(WebServer *web_server, void *source); - /// Dump the binary sensor state with its value as a JSON string. - std::string binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config); #endif #ifdef USE_FAN @@ -317,8 +309,6 @@ class WebServer : public Controller, static std::string fan_state_json_generator(WebServer *web_server, void *source); static std::string fan_all_json_generator(WebServer *web_server, void *source); - /// Dump the fan state as a JSON string. - std::string fan_json(fan::Fan *obj, JsonDetail start_config); #endif #ifdef USE_LIGHT @@ -329,8 +319,6 @@ class WebServer : public Controller, static std::string light_state_json_generator(WebServer *web_server, void *source); static std::string light_all_json_generator(WebServer *web_server, void *source); - /// Dump the light state as a JSON string. - std::string light_json(light::LightState *obj, JsonDetail start_config); #endif #ifdef USE_TEXT_SENSOR @@ -341,8 +329,6 @@ class WebServer : public Controller, static std::string text_sensor_state_json_generator(WebServer *web_server, void *source); static std::string text_sensor_all_json_generator(WebServer *web_server, void *source); - /// Dump the text sensor state with its value as a JSON string. - std::string text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config); #endif #ifdef USE_COVER @@ -353,8 +339,6 @@ class WebServer : public Controller, static std::string cover_state_json_generator(WebServer *web_server, void *source); static std::string cover_all_json_generator(WebServer *web_server, void *source); - /// Dump the cover state as a JSON string. - std::string cover_json(cover::Cover *obj, JsonDetail start_config); #endif #ifdef USE_NUMBER @@ -364,8 +348,6 @@ class WebServer : public Controller, static std::string number_state_json_generator(WebServer *web_server, void *source); static std::string number_all_json_generator(WebServer *web_server, void *source); - /// Dump the number state with its value as a JSON string. - std::string number_json(number::Number *obj, float value, JsonDetail start_config); #endif #ifdef USE_DATETIME_DATE @@ -375,8 +357,6 @@ class WebServer : public Controller, static std::string date_state_json_generator(WebServer *web_server, void *source); static std::string date_all_json_generator(WebServer *web_server, void *source); - /// Dump the date state with its value as a JSON string. - std::string date_json(datetime::DateEntity *obj, JsonDetail start_config); #endif #ifdef USE_DATETIME_TIME @@ -386,8 +366,6 @@ class WebServer : public Controller, static std::string time_state_json_generator(WebServer *web_server, void *source); static std::string time_all_json_generator(WebServer *web_server, void *source); - /// Dump the time state with its value as a JSON string. - std::string time_json(datetime::TimeEntity *obj, JsonDetail start_config); #endif #ifdef USE_DATETIME_DATETIME @@ -397,8 +375,6 @@ class WebServer : public Controller, static std::string datetime_state_json_generator(WebServer *web_server, void *source); static std::string datetime_all_json_generator(WebServer *web_server, void *source); - /// Dump the datetime state with its value as a JSON string. - std::string datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config); #endif #ifdef USE_TEXT @@ -408,8 +384,6 @@ class WebServer : public Controller, static std::string text_state_json_generator(WebServer *web_server, void *source); static std::string text_all_json_generator(WebServer *web_server, void *source); - /// Dump the text state with its value as a JSON string. - std::string text_json(text::Text *obj, const std::string &value, JsonDetail start_config); #endif #ifdef USE_SELECT @@ -419,8 +393,6 @@ class WebServer : public Controller, static std::string select_state_json_generator(WebServer *web_server, void *source); static std::string select_all_json_generator(WebServer *web_server, void *source); - /// Dump the select state with its value as a JSON string. - std::string select_json(select::Select *obj, const char *value, JsonDetail start_config); #endif #ifdef USE_CLIMATE @@ -430,8 +402,6 @@ class WebServer : public Controller, static std::string climate_state_json_generator(WebServer *web_server, void *source); static std::string climate_all_json_generator(WebServer *web_server, void *source); - /// Dump the climate details - std::string climate_json(climate::Climate *obj, JsonDetail start_config); #endif #ifdef USE_LOCK @@ -442,8 +412,6 @@ class WebServer : public Controller, static std::string lock_state_json_generator(WebServer *web_server, void *source); static std::string lock_all_json_generator(WebServer *web_server, void *source); - /// Dump the lock state with its value as a JSON string. - std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config); #endif #ifdef USE_VALVE @@ -454,8 +422,6 @@ class WebServer : public Controller, static std::string valve_state_json_generator(WebServer *web_server, void *source); static std::string valve_all_json_generator(WebServer *web_server, void *source); - /// Dump the valve state as a JSON string. - std::string valve_json(valve::Valve *obj, JsonDetail start_config); #endif #ifdef USE_ALARM_CONTROL_PANEL @@ -466,9 +432,6 @@ class WebServer : public Controller, static std::string alarm_control_panel_state_json_generator(WebServer *web_server, void *source); static std::string alarm_control_panel_all_json_generator(WebServer *web_server, void *source); - /// Dump the alarm_control_panel state with its value as a JSON string. - std::string alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj, - alarm_control_panel::AlarmControlPanelState value, JsonDetail start_config); #endif #ifdef USE_EVENT @@ -479,9 +442,6 @@ class WebServer : public Controller, /// Handle a event request under '/event'. void handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match); - - /// Dump the event details with its value as a JSON string. - std::string event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config); #endif #ifdef USE_UPDATE @@ -492,8 +452,6 @@ class WebServer : public Controller, static std::string update_state_json_generator(WebServer *web_server, void *source); static std::string update_all_json_generator(WebServer *web_server, void *source); - /// Dump the update state with its value as a JSON string. - std::string update_json(update::UpdateEntity *obj, JsonDetail start_config); #endif /// Override the web handler's canHandle method. @@ -593,6 +551,69 @@ class WebServer : public Controller, const char *js_include_{nullptr}; #endif bool expose_log_{true}; + + private: +#ifdef USE_SENSOR + std::string sensor_json_(sensor::Sensor *obj, float value, JsonDetail start_config); +#endif +#ifdef USE_SWITCH + std::string switch_json_(switch_::Switch *obj, bool value, JsonDetail start_config); +#endif +#ifdef USE_BUTTON + std::string button_json_(button::Button *obj, JsonDetail start_config); +#endif +#ifdef USE_BINARY_SENSOR + std::string binary_sensor_json_(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config); +#endif +#ifdef USE_FAN + std::string fan_json_(fan::Fan *obj, JsonDetail start_config); +#endif +#ifdef USE_LIGHT + std::string light_json_(light::LightState *obj, JsonDetail start_config); +#endif +#ifdef USE_TEXT_SENSOR + std::string text_sensor_json_(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config); +#endif +#ifdef USE_COVER + std::string cover_json_(cover::Cover *obj, JsonDetail start_config); +#endif +#ifdef USE_NUMBER + std::string number_json_(number::Number *obj, float value, JsonDetail start_config); +#endif +#ifdef USE_DATETIME_DATE + std::string date_json_(datetime::DateEntity *obj, JsonDetail start_config); +#endif +#ifdef USE_DATETIME_TIME + std::string time_json_(datetime::TimeEntity *obj, JsonDetail start_config); +#endif +#ifdef USE_DATETIME_DATETIME + std::string datetime_json_(datetime::DateTimeEntity *obj, JsonDetail start_config); +#endif +#ifdef USE_TEXT + std::string text_json_(text::Text *obj, const std::string &value, JsonDetail start_config); +#endif +#ifdef USE_SELECT + std::string select_json_(select::Select *obj, const char *value, JsonDetail start_config); +#endif +#ifdef USE_CLIMATE + std::string climate_json_(climate::Climate *obj, JsonDetail start_config); +#endif +#ifdef USE_LOCK + std::string lock_json_(lock::Lock *obj, lock::LockState value, JsonDetail start_config); +#endif +#ifdef USE_VALVE + std::string valve_json_(valve::Valve *obj, JsonDetail start_config); +#endif +#ifdef USE_ALARM_CONTROL_PANEL + std::string alarm_control_panel_json_(alarm_control_panel::AlarmControlPanel *obj, + alarm_control_panel::AlarmControlPanelState value, JsonDetail start_config); +#endif +#ifdef USE_EVENT + std::string event_json_(event::Event *obj, const std::string &event_type, JsonDetail start_config); +#endif +#ifdef USE_UPDATE + std::string update_json_(update::UpdateEntity *obj, JsonDetail start_config); +#endif }; } // namespace web_server From f238f9331272c25bf073b921f443e62e75c73c6c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 11:37:51 -1000 Subject: [PATCH 0766/1145] [core] Move comment to PROGMEM on ESP8266 (#12554) --- esphome/components/web_server/web_server.cpp | 4 +- esphome/core/application.h | 23 +++--- esphome/core/build_info_data.h | 2 + esphome/core/config.py | 3 +- esphome/writer.py | 25 +++++-- tests/dummy_main.cpp | 2 +- tests/unit_tests/test_writer.py | 76 +++++++++++++++++--- 7 files changed, 105 insertions(+), 30 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index ece9d65121..b0731f335b 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -287,7 +287,9 @@ std::string WebServer::get_config_json() { JsonObject root = builder.root(); root[ESPHOME_F("title")] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name(); - root[ESPHOME_F("comment")] = App.get_comment_ref(); + char comment_buffer[ESPHOME_COMMENT_SIZE]; + App.get_comment_string(comment_buffer); + root[ESPHOME_F("comment")] = comment_buffer; #if defined(USE_WEBSERVER_OTA_DISABLED) || !defined(USE_WEBSERVER_OTA) root[ESPHOME_F("ota")] = false; // Note: USE_WEBSERVER_OTA_DISABLED only affects web_server, not captive_portal #else diff --git a/esphome/core/application.h b/esphome/core/application.h index d2146a6c16..13461b3ebd 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -12,6 +12,7 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" +#include "esphome/core/progmem.h" #include "esphome/core/scheduler.h" #include "esphome/core/string_ref.h" #include "esphome/core/version.h" @@ -107,8 +108,7 @@ static const uint32_t TEARDOWN_TIMEOUT_REBOOT_MS = 1000; // 1 second for quick class Application { public: - void pre_setup(const std::string &name, const std::string &friendly_name, const char *comment, - bool name_add_mac_suffix) { + void pre_setup(const std::string &name, const std::string &friendly_name, bool name_add_mac_suffix) { arch_init(); this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { @@ -127,7 +127,6 @@ class Application { this->name_ = name; this->friendly_name_ = friendly_name; } - this->comment_ = comment; } #ifdef USE_DEVICES @@ -264,10 +263,19 @@ class Application { return ""; } - /// Get the comment of this Application set by pre_setup(). - std::string get_comment() const { return this->comment_; } - /// Get the comment as StringRef (avoids allocation) - StringRef get_comment_ref() const { return StringRef(this->comment_); } + /// Copy the comment string into the provided buffer + /// Buffer must be ESPHOME_COMMENT_SIZE bytes (compile-time enforced) + void get_comment_string(std::span buffer) { + ESPHOME_strncpy_P(buffer.data(), ESPHOME_COMMENT_STR, buffer.size()); + buffer[buffer.size() - 1] = '\0'; + } + + /// Get the comment of this Application as a string + std::string get_comment() { + char buffer[ESPHOME_COMMENT_SIZE]; + this->get_comment_string(buffer); + return std::string(buffer); + } bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; } @@ -513,7 +521,6 @@ class Application { // Pointer-sized members first Component *current_component_{nullptr}; - const char *comment_{nullptr}; // std::vector (3 pointers each: begin, end, capacity) // Partitioned vector design for looping components diff --git a/esphome/core/build_info_data.h b/esphome/core/build_info_data.h index 5e424ffaca..02bb465e44 100644 --- a/esphome/core/build_info_data.h +++ b/esphome/core/build_info_data.h @@ -7,4 +7,6 @@ #define ESPHOME_CONFIG_HASH 0x12345678U // NOLINT #define ESPHOME_BUILD_TIME 1700000000 // NOLINT +#define ESPHOME_COMMENT_SIZE 1 // NOLINT static const char ESPHOME_BUILD_TIME_STR[] = "2024-01-01 00:00:00 +0000"; +static const char ESPHOME_COMMENT_STR[] = ""; diff --git a/esphome/core/config.py b/esphome/core/config.py index 507a39b401..5e32b9380d 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -209,7 +209,7 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_NAME): cv.valid_name, cv.Optional(CONF_FRIENDLY_NAME, ""): cv.All(cv.string, cv.Length(max=120)), cv.Optional(CONF_AREA): validate_area_config, - cv.Optional(CONF_COMMENT): cv.string, + cv.Optional(CONF_COMMENT): cv.All(cv.string, cv.Length(max=255)), cv.Required(CONF_BUILD_PATH): cv.string, cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( { @@ -505,7 +505,6 @@ async def to_code(config: ConfigType) -> None: cg.App.pre_setup( config[CONF_NAME], config[CONF_FRIENDLY_NAME], - config.get(CONF_COMMENT, ""), config[CONF_NAME_ADD_MAC_SUFFIX], ) ) diff --git a/esphome/writer.py b/esphome/writer.py index 183fff8730..9ae40e417a 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -21,6 +21,7 @@ from esphome.const import ( from esphome.core import CORE, EsphomeError from esphome.helpers import ( copy_file_if_changed, + cpp_string_escape, get_str_env, is_ha_addon, read_file, @@ -271,7 +272,7 @@ def copy_src_tree(): "esphome", "core", "build_info_data.h" ) build_info_json_path = CORE.relative_build_path("build_info.json") - config_hash, build_time, build_time_str = get_build_info() + config_hash, build_time, build_time_str, comment = get_build_info() # Defensively force a rebuild if the build_info files don't exist, or if # there was a config change which didn't actually cause a source change @@ -292,7 +293,9 @@ def copy_src_tree(): if sources_changed: write_file( build_info_data_h_path, - generate_build_info_data_h(config_hash, build_time, build_time_str), + generate_build_info_data_h( + config_hash, build_time, build_time_str, comment + ), ) write_file( build_info_json_path, @@ -332,31 +335,39 @@ def generate_version_h(): ) -def get_build_info() -> tuple[int, int, str]: +def get_build_info() -> tuple[int, int, str, str]: """Calculate build_info values from current config. Returns: - Tuple of (config_hash, build_time, build_time_str) + Tuple of (config_hash, build_time, build_time_str, comment) """ config_hash = CORE.config_hash build_time = int(time.time()) build_time_str = time.strftime("%Y-%m-%d %H:%M:%S %z", time.localtime(build_time)) - return config_hash, build_time, build_time_str + comment = CORE.comment or "" + return config_hash, build_time, build_time_str, comment def generate_build_info_data_h( - config_hash: int, build_time: int, build_time_str: str + config_hash: int, build_time: int, build_time_str: str, comment: str ) -> str: - """Generate build_info_data.h header with config hash and build time.""" + """Generate build_info_data.h header with config hash, build time, and comment.""" + # cpp_string_escape returns '"escaped"', slice off the quotes since template has them + escaped_comment = cpp_string_escape(comment)[1:-1] + # +1 for null terminator + comment_size = len(comment) + 1 return f"""#pragma once // Auto-generated build_info data #define ESPHOME_CONFIG_HASH 0x{config_hash:08x}U // NOLINT #define ESPHOME_BUILD_TIME {build_time} // NOLINT +#define ESPHOME_COMMENT_SIZE {comment_size} // NOLINT #ifdef USE_ESP8266 #include static const char ESPHOME_BUILD_TIME_STR[] PROGMEM = "{build_time_str}"; +static const char ESPHOME_COMMENT_STR[] PROGMEM = "{escaped_comment}"; #else static const char ESPHOME_BUILD_TIME_STR[] = "{build_time_str}"; +static const char ESPHOME_COMMENT_STR[] = "{escaped_comment}"; #endif """ diff --git a/tests/dummy_main.cpp b/tests/dummy_main.cpp index 5849f4eb95..e6fe733807 100644 --- a/tests/dummy_main.cpp +++ b/tests/dummy_main.cpp @@ -12,7 +12,7 @@ using namespace esphome; void setup() { - App.pre_setup("livingroom", "LivingRoom", "comment", false); + App.pre_setup("livingroom", "LivingRoom", false); auto *log = new logger::Logger(115200, 512); // NOLINT log->pre_setup(); log->set_uart_selection(logger::UART_SELECTION_UART0); diff --git a/tests/unit_tests/test_writer.py b/tests/unit_tests/test_writer.py index 06a7d5dbdf..f354d71bb7 100644 --- a/tests/unit_tests/test_writer.py +++ b/tests/unit_tests/test_writer.py @@ -1186,8 +1186,9 @@ def test_get_build_info_new_build( build_info_path = tmp_path / "build_info.json" mock_core.relative_build_path.return_value = build_info_path mock_core.config_hash = 0x12345678 + mock_core.comment = "Test comment" - config_hash, build_time, build_time_str = get_build_info() + config_hash, build_time, build_time_str, comment = get_build_info() assert config_hash == 0x12345678 assert isinstance(build_time, int) @@ -1195,6 +1196,7 @@ def test_get_build_info_new_build( assert isinstance(build_time_str, str) # Verify build_time_str format matches expected pattern assert len(build_time_str) >= 19 # e.g., "2025-12-15 16:27:44 +0000" + assert comment == "Test comment" @patch("esphome.writer.CORE") @@ -1206,6 +1208,7 @@ def test_get_build_info_always_returns_current_time( build_info_path = tmp_path / "build_info.json" mock_core.relative_build_path.return_value = build_info_path mock_core.config_hash = 0x12345678 + mock_core.comment = "" # Create existing build_info.json with matching config_hash and version existing_build_time = 1700000000 @@ -1222,7 +1225,7 @@ def test_get_build_info_always_returns_current_time( ) with patch("esphome.writer.__version__", "2025.1.0-dev"): - config_hash, build_time, build_time_str = get_build_info() + config_hash, build_time, build_time_str, comment = get_build_info() assert config_hash == 0x12345678 # get_build_info now always returns current time @@ -1240,6 +1243,7 @@ def test_get_build_info_config_changed( build_info_path = tmp_path / "build_info.json" mock_core.relative_build_path.return_value = build_info_path mock_core.config_hash = 0xABCDEF00 # Different from existing + mock_core.comment = "" # Create existing build_info.json with different config_hash existing_build_time = 1700000000 @@ -1255,7 +1259,7 @@ def test_get_build_info_config_changed( ) with patch("esphome.writer.__version__", "2025.1.0-dev"): - config_hash, build_time, build_time_str = get_build_info() + config_hash, build_time, build_time_str, comment = get_build_info() assert config_hash == 0xABCDEF00 assert build_time != existing_build_time # New time generated @@ -1271,6 +1275,7 @@ def test_get_build_info_version_changed( build_info_path = tmp_path / "build_info.json" mock_core.relative_build_path.return_value = build_info_path mock_core.config_hash = 0x12345678 + mock_core.comment = "" # Create existing build_info.json with different version existing_build_time = 1700000000 @@ -1286,7 +1291,7 @@ def test_get_build_info_version_changed( ) with patch("esphome.writer.__version__", "2025.1.0-dev"): # New version - config_hash, build_time, build_time_str = get_build_info() + config_hash, build_time, build_time_str, comment = get_build_info() assert config_hash == 0x12345678 assert build_time != existing_build_time # New time generated @@ -1302,11 +1307,12 @@ def test_get_build_info_invalid_json( build_info_path = tmp_path / "build_info.json" mock_core.relative_build_path.return_value = build_info_path mock_core.config_hash = 0x12345678 + mock_core.comment = "" # Create invalid JSON file build_info_path.write_text("not valid json {{{") - config_hash, build_time, build_time_str = get_build_info() + config_hash, build_time, build_time_str, comment = get_build_info() assert config_hash == 0x12345678 assert isinstance(build_time, int) @@ -1322,12 +1328,13 @@ def test_get_build_info_missing_keys( build_info_path = tmp_path / "build_info.json" mock_core.relative_build_path.return_value = build_info_path mock_core.config_hash = 0x12345678 + mock_core.comment = "" # Create JSON with missing keys build_info_path.write_text(json.dumps({"config_hash": 0x12345678})) with patch("esphome.writer.__version__", "2025.1.0-dev"): - config_hash, build_time, build_time_str = get_build_info() + config_hash, build_time, build_time_str, comment = get_build_info() assert config_hash == 0x12345678 assert isinstance(build_time, int) @@ -1343,8 +1350,9 @@ def test_get_build_info_build_time_str_format( build_info_path = tmp_path / "build_info.json" mock_core.relative_build_path.return_value = build_info_path mock_core.config_hash = 0x12345678 + mock_core.comment = "" - config_hash, build_time, build_time_str = get_build_info() + config_hash, build_time, build_time_str, comment = get_build_info() # Verify the format matches "%Y-%m-%d %H:%M:%S %z" # e.g., "2025-12-15 16:27:44 +0000" @@ -1357,36 +1365,73 @@ def test_generate_build_info_data_h_format() -> None: config_hash = 0x12345678 build_time = 1700000000 build_time_str = "2023-11-14 22:13:20 +0000" + comment = "Test comment" - result = generate_build_info_data_h(config_hash, build_time, build_time_str) + result = generate_build_info_data_h( + config_hash, build_time, build_time_str, comment + ) assert "#pragma once" in result assert "#define ESPHOME_CONFIG_HASH 0x12345678U" in result assert "#define ESPHOME_BUILD_TIME 1700000000" in result + assert "#define ESPHOME_COMMENT_SIZE 13" in result # len("Test comment") + 1 assert 'ESPHOME_BUILD_TIME_STR[] = "2023-11-14 22:13:20 +0000"' in result + assert 'ESPHOME_COMMENT_STR[] = "Test comment"' in result def test_generate_build_info_data_h_esp8266_progmem() -> None: """Test generate_build_info_data_h includes PROGMEM for ESP8266.""" - result = generate_build_info_data_h(0xABCDEF01, 1700000000, "test") + result = generate_build_info_data_h(0xABCDEF01, 1700000000, "test", "comment") # Should have ESP8266 PROGMEM conditional assert "#ifdef USE_ESP8266" in result assert "#include " in result assert "PROGMEM" in result + # Both build time and comment should have PROGMEM versions + assert 'ESPHOME_BUILD_TIME_STR[] PROGMEM = "test"' in result + assert 'ESPHOME_COMMENT_STR[] PROGMEM = "comment"' in result def test_generate_build_info_data_h_hash_formatting() -> None: """Test generate_build_info_data_h formats hash with leading zeros.""" # Test with small hash value that needs leading zeros - result = generate_build_info_data_h(0x00000001, 0, "test") + result = generate_build_info_data_h(0x00000001, 0, "test", "") assert "#define ESPHOME_CONFIG_HASH 0x00000001U" in result # Test with larger hash value - result = generate_build_info_data_h(0xFFFFFFFF, 0, "test") + result = generate_build_info_data_h(0xFFFFFFFF, 0, "test", "") assert "#define ESPHOME_CONFIG_HASH 0xffffffffU" in result +def test_generate_build_info_data_h_comment_escaping() -> None: + r"""Test generate_build_info_data_h properly escapes special characters in comment. + + Uses cpp_string_escape which outputs octal escapes for special characters: + - backslash (ASCII 92) -> \134 + - double quote (ASCII 34) -> \042 + - newline (ASCII 10) -> \012 + """ + # Test backslash escaping (ASCII 92 = octal 134) + result = generate_build_info_data_h(0, 0, "test", "backslash\\here") + assert 'ESPHOME_COMMENT_STR[] = "backslash\\134here"' in result + + # Test quote escaping (ASCII 34 = octal 042) + result = generate_build_info_data_h(0, 0, "test", 'has "quotes"') + assert 'ESPHOME_COMMENT_STR[] = "has \\042quotes\\042"' in result + + # Test newline escaping (ASCII 10 = octal 012) + result = generate_build_info_data_h(0, 0, "test", "line1\nline2") + assert 'ESPHOME_COMMENT_STR[] = "line1\\012line2"' in result + + +def test_generate_build_info_data_h_empty_comment() -> None: + """Test generate_build_info_data_h handles empty comment.""" + result = generate_build_info_data_h(0, 0, "test", "") + + assert "#define ESPHOME_COMMENT_SIZE 1" in result # Just null terminator + assert 'ESPHOME_COMMENT_STR[] = ""' in result + + @patch("esphome.writer.CORE") @patch("esphome.writer.iter_components") @patch("esphome.writer.walk_files") @@ -1445,6 +1490,7 @@ def test_copy_src_tree_writes_build_info_files( mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) mock_core.defines = [] mock_core.config_hash = 0xDEADBEEF + mock_core.comment = "Test comment" mock_core.target_platform = "test_platform" mock_core.config = {} mock_iter_components.return_value = [("core", mock_component)] @@ -1466,6 +1512,8 @@ def test_copy_src_tree_writes_build_info_files( assert "#define ESPHOME_CONFIG_HASH 0xdeadbeefU" in build_info_h_content assert "#define ESPHOME_BUILD_TIME" in build_info_h_content assert "ESPHOME_BUILD_TIME_STR" in build_info_h_content + assert "#define ESPHOME_COMMENT_SIZE" in build_info_h_content + assert "ESPHOME_COMMENT_STR" in build_info_h_content # Verify build_info.json was written build_info_json_path = build_path / "build_info.json" @@ -1517,6 +1565,7 @@ def test_copy_src_tree_detects_config_hash_change( mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) mock_core.defines = [] mock_core.config_hash = 0xDEADBEEF # Different from existing + mock_core.comment = "" mock_core.target_platform = "test_platform" mock_core.config = {} mock_iter_components.return_value = [] @@ -1578,6 +1627,7 @@ def test_copy_src_tree_detects_version_change( mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) mock_core.defines = [] mock_core.config_hash = 0xDEADBEEF + mock_core.comment = "" mock_core.target_platform = "test_platform" mock_core.config = {} mock_iter_components.return_value = [] @@ -1627,6 +1677,7 @@ def test_copy_src_tree_handles_invalid_build_info_json( mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) mock_core.defines = [] mock_core.config_hash = 0xDEADBEEF + mock_core.comment = "" mock_core.target_platform = "test_platform" mock_core.config = {} mock_iter_components.return_value = [] @@ -1700,6 +1751,7 @@ def test_copy_src_tree_build_info_timestamp_behavior( mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) mock_core.defines = [] mock_core.config_hash = 0xDEADBEEF + mock_core.comment = "" mock_core.target_platform = "test_platform" mock_core.config = {} mock_iter_components.return_value = [("test", mock_component)] @@ -1794,6 +1846,7 @@ def test_copy_src_tree_detects_removed_source_file( mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) mock_core.defines = [] mock_core.config_hash = 0xDEADBEEF + mock_core.comment = "" mock_core.target_platform = "test_platform" mock_core.config = {} mock_iter_components.return_value = [] # No components = file should be removed @@ -1855,6 +1908,7 @@ def test_copy_src_tree_ignores_removed_generated_file( mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) mock_core.defines = [] mock_core.config_hash = 0xDEADBEEF + mock_core.comment = "" mock_core.target_platform = "test_platform" mock_core.config = {} mock_iter_components.return_value = [] From af0d4d2c2cfd6c955bd2903af7a4039d7793c1cf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 11:56:07 -1000 Subject: [PATCH 0767/1145] [web_server] Use stack buffers for value formatting to reduce flash usage (#12575) --- esphome/components/web_server/web_server.cpp | 68 +++++++++++--------- esphome/core/helpers.cpp | 34 ++++++---- esphome/core/helpers.h | 11 +++- 3 files changed, 68 insertions(+), 45 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index b0731f335b..df8a5364cf 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -432,7 +432,7 @@ static void set_json_value(JsonObject &root, EntityBase *obj, const char *prefix } template -static void set_json_icon_state_value(JsonObject &root, EntityBase *obj, const char *prefix, const std::string &state, +static void set_json_icon_state_value(JsonObject &root, EntityBase *obj, const char *prefix, const char *state, const T &value, JsonDetail start_config) { set_json_value(root, obj, prefix, value, start_config); root[ESPHOME_F("state")] = state; @@ -475,9 +475,10 @@ std::string WebServer::sensor_json_(sensor::Sensor *obj, float value, JsonDetail JsonObject root = builder.root(); const auto uom_ref = obj->get_unit_of_measurement_ref(); - - std::string state = - std::isnan(value) ? "NA" : value_accuracy_with_uom_to_string(value, obj->get_accuracy_decimals(), uom_ref); + char buf[VALUE_ACCURACY_MAX_LEN]; + const char *state = std::isnan(value) + ? "NA" + : (value_accuracy_with_uom_to_buf(buf, value, obj->get_accuracy_decimals(), uom_ref), buf); set_json_icon_state_value(root, obj, "sensor", state, value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); @@ -522,7 +523,7 @@ std::string WebServer::text_sensor_json_(text_sensor::TextSensor *obj, const std json::JsonBuilder builder; JsonObject root = builder.root(); - set_json_icon_state_value(root, obj, "text_sensor", value, value, start_config); + set_json_icon_state_value(root, obj, "text_sensor", value.c_str(), value.c_str(), start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); } @@ -974,21 +975,20 @@ std::string WebServer::number_json_(number::Number *obj, float value, JsonDetail JsonObject root = builder.root(); const auto uom_ref = obj->traits.get_unit_of_measurement_ref(); + const int8_t accuracy = step_to_accuracy_decimals(obj->traits.get_step()); - std::string val_str = std::isnan(value) - ? "\"NaN\"" - : value_accuracy_to_string(value, step_to_accuracy_decimals(obj->traits.get_step())); - std::string state_str = std::isnan(value) ? "NA" - : value_accuracy_with_uom_to_string( - value, step_to_accuracy_decimals(obj->traits.get_step()), uom_ref); + // Need two buffers: one for value, one for state with UOM + char val_buf[VALUE_ACCURACY_MAX_LEN]; + char state_buf[VALUE_ACCURACY_MAX_LEN]; + const char *val_str = std::isnan(value) ? "\"NaN\"" : (value_accuracy_to_buf(val_buf, value, accuracy), val_buf); + const char *state_str = + std::isnan(value) ? "NA" : (value_accuracy_with_uom_to_buf(state_buf, value, accuracy, uom_ref), state_buf); set_json_icon_state_value(root, obj, "number", state_str, val_str, start_config); if (start_config == DETAIL_ALL) { - root[ESPHOME_F("min_value")] = - value_accuracy_to_string(obj->traits.get_min_value(), step_to_accuracy_decimals(obj->traits.get_step())); - root[ESPHOME_F("max_value")] = - value_accuracy_to_string(obj->traits.get_max_value(), step_to_accuracy_decimals(obj->traits.get_step())); - root[ESPHOME_F("step")] = - value_accuracy_to_string(obj->traits.get_step(), step_to_accuracy_decimals(obj->traits.get_step())); + // ArduinoJson copies the string immediately, so we can reuse val_buf + root[ESPHOME_F("min_value")] = (value_accuracy_to_buf(val_buf, obj->traits.get_min_value(), accuracy), val_buf); + root[ESPHOME_F("max_value")] = (value_accuracy_to_buf(val_buf, obj->traits.get_max_value(), accuracy), val_buf); + root[ESPHOME_F("step")] = (value_accuracy_to_buf(val_buf, obj->traits.get_step(), accuracy), val_buf); root[ESPHOME_F("mode")] = (int) obj->traits.get_mode(); if (!uom_ref.empty()) root[ESPHOME_F("uom")] = uom_ref; @@ -1230,8 +1230,8 @@ std::string WebServer::text_json_(text::Text *obj, const std::string &value, Jso json::JsonBuilder builder; JsonObject root = builder.root(); - std::string state = obj->traits.get_mode() == text::TextMode::TEXT_MODE_PASSWORD ? "********" : value; - set_json_icon_state_value(root, obj, "text", state, value, start_config); + const char *state = obj->traits.get_mode() == text::TextMode::TEXT_MODE_PASSWORD ? "********" : value.c_str(); + set_json_icon_state_value(root, obj, "text", state, value.c_str(), start_config); root[ESPHOME_F("min_length")] = obj->traits.get_min_length(); root[ESPHOME_F("max_length")] = obj->traits.get_max_length(); root[ESPHOME_F("pattern")] = obj->traits.get_pattern_c_str(); @@ -1359,6 +1359,7 @@ std::string WebServer::climate_json_(climate::Climate *obj, JsonDetail start_con int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals(); int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals(); char buf[PSTR_LOCAL_SIZE]; + char temp_buf[VALUE_ACCURACY_MAX_LEN]; if (start_config == DETAIL_ALL) { JsonArray opt = root[ESPHOME_F("modes")].to(); @@ -1395,8 +1396,10 @@ std::string WebServer::climate_json_(climate::Climate *obj, JsonDetail start_con bool has_state = false; root[ESPHOME_F("mode")] = PSTR_LOCAL(climate_mode_to_string(obj->mode)); - root[ESPHOME_F("max_temp")] = value_accuracy_to_string(traits.get_visual_max_temperature(), target_accuracy); - root[ESPHOME_F("min_temp")] = value_accuracy_to_string(traits.get_visual_min_temperature(), target_accuracy); + root[ESPHOME_F("max_temp")] = + (value_accuracy_to_buf(temp_buf, traits.get_visual_max_temperature(), target_accuracy), temp_buf); + root[ESPHOME_F("min_temp")] = + (value_accuracy_to_buf(temp_buf, traits.get_visual_min_temperature(), target_accuracy), temp_buf); root[ESPHOME_F("step")] = traits.get_visual_target_temperature_step(); if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) { root[ESPHOME_F("action")] = PSTR_LOCAL(climate_action_to_string(obj->action)); @@ -1419,23 +1422,26 @@ std::string WebServer::climate_json_(climate::Climate *obj, JsonDetail start_con root[ESPHOME_F("swing_mode")] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode)); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) { - if (!std::isnan(obj->current_temperature)) { - root[ESPHOME_F("current_temperature")] = value_accuracy_to_string(obj->current_temperature, current_accuracy); - } else { - root[ESPHOME_F("current_temperature")] = "NA"; - } + root[ESPHOME_F("current_temperature")] = + std::isnan(obj->current_temperature) + ? "NA" + : (value_accuracy_to_buf(temp_buf, obj->current_temperature, current_accuracy), temp_buf); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { - root[ESPHOME_F("target_temperature_low")] = value_accuracy_to_string(obj->target_temperature_low, target_accuracy); + root[ESPHOME_F("target_temperature_low")] = + (value_accuracy_to_buf(temp_buf, obj->target_temperature_low, target_accuracy), temp_buf); root[ESPHOME_F("target_temperature_high")] = - value_accuracy_to_string(obj->target_temperature_high, target_accuracy); + (value_accuracy_to_buf(temp_buf, obj->target_temperature_high, target_accuracy), temp_buf); if (!has_state) { - root[ESPHOME_F("state")] = value_accuracy_to_string( - (obj->target_temperature_high + obj->target_temperature_low) / 2.0f, target_accuracy); + root[ESPHOME_F("state")] = + (value_accuracy_to_buf(temp_buf, (obj->target_temperature_high + obj->target_temperature_low) / 2.0f, + target_accuracy), + temp_buf); } } else { - root[ESPHOME_F("target_temperature")] = value_accuracy_to_string(obj->target_temperature, target_accuracy); + root[ESPHOME_F("target_temperature")] = + (value_accuracy_to_buf(temp_buf, obj->target_temperature, target_accuracy), temp_buf); if (!has_state) root[ESPHOME_F("state")] = root[ESPHOME_F("target_temperature")]; } diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index f55f53f16b..d7d32ea28f 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -383,23 +383,33 @@ static inline void normalize_accuracy_decimals(float &value, int8_t &accuracy_de } std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { - normalize_accuracy_decimals(value, accuracy_decimals); - char tmp[32]; // should be enough, but we should maybe improve this at some point. - snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); - return std::string(tmp); + char buf[VALUE_ACCURACY_MAX_LEN]; + value_accuracy_to_buf(buf, value, accuracy_decimals); + return std::string(buf); } -std::string value_accuracy_with_uom_to_string(float value, int8_t accuracy_decimals, StringRef unit_of_measurement) { +size_t value_accuracy_to_buf(std::span buf, float value, int8_t accuracy_decimals) { normalize_accuracy_decimals(value, accuracy_decimals); - // Buffer sized for float (up to ~15 chars) + space + typical UOM (usually <20 chars like "μS/cm") - // snprintf truncates safely if exceeded, though ESPHome UOMs are typically short - char tmp[64]; + // snprintf returns chars that would be written (excluding null), or negative on error + int len = snprintf(buf.data(), buf.size(), "%.*f", accuracy_decimals, value); + if (len < 0) + return 0; // encoding error + // On truncation, snprintf returns would-be length; actual written is buf.size() - 1 + return static_cast(len) >= buf.size() ? buf.size() - 1 : static_cast(len); +} + +size_t value_accuracy_with_uom_to_buf(std::span buf, float value, + int8_t accuracy_decimals, StringRef unit_of_measurement) { if (unit_of_measurement.empty()) { - snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); - } else { - snprintf(tmp, sizeof(tmp), "%.*f %s", accuracy_decimals, value, unit_of_measurement.c_str()); + return value_accuracy_to_buf(buf, value, accuracy_decimals); } - return std::string(tmp); + normalize_accuracy_decimals(value, accuracy_decimals); + // snprintf returns chars that would be written (excluding null), or negative on error + int len = snprintf(buf.data(), buf.size(), "%.*f %s", accuracy_decimals, value, unit_of_measurement.c_str()); + if (len < 0) + return 0; // encoding error + // On truncation, snprintf returns would-be length; actual written is buf.size() - 1 + return static_cast(len) >= buf.size() ? buf.size() - 1 : static_cast(len); } int8_t step_to_accuracy_decimals(float step) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index b575a14d14..769041160c 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -886,8 +886,15 @@ ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const ch /// Create a string from a value and an accuracy in decimals. std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); -/// Create a string from a value, an accuracy in decimals, and a unit of measurement. -std::string value_accuracy_with_uom_to_string(float value, int8_t accuracy_decimals, StringRef unit_of_measurement); + +/// Maximum buffer size for value_accuracy formatting (float ~15 chars + space + UOM ~40 chars + null) +static constexpr size_t VALUE_ACCURACY_MAX_LEN = 64; + +/// Format value with accuracy to buffer, returns chars written (excluding null) +size_t value_accuracy_to_buf(std::span buf, float value, int8_t accuracy_decimals); +/// Format value with accuracy and UOM to buffer, returns chars written (excluding null) +size_t value_accuracy_with_uom_to_buf(std::span buf, float value, + int8_t accuracy_decimals, StringRef unit_of_measurement); /// Derive accuracy in decimals from an increment step. int8_t step_to_accuracy_decimals(float step); From 1b31253287343b150a01a3a04e8673dabc68f73e Mon Sep 17 00:00:00 2001 From: eoasmxd <42328021+eoasmxd@users.noreply.github.com> Date: Tue, 23 Dec 2025 06:19:48 +0800 Subject: [PATCH 0768/1145] Add Event Component to UART (#11765) Co-authored-by: J. Nick Koston --- CODEOWNERS | 1 + esphome/components/uart/event/__init__.py | 90 ++++++++++++++++++++ esphome/components/uart/event/uart_event.cpp | 48 +++++++++++ esphome/components/uart/event/uart_event.h | 31 +++++++ tests/components/uart/test.esp32-idf.yaml | 8 ++ tests/components/uart/test.esp8266-ard.yaml | 8 ++ 6 files changed, 186 insertions(+) create mode 100644 esphome/components/uart/event/__init__.py create mode 100644 esphome/components/uart/event/uart_event.cpp create mode 100644 esphome/components/uart/event/uart_event.h diff --git a/CODEOWNERS b/CODEOWNERS index 941c2e2849..f95d68a46d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -519,6 +519,7 @@ esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core esphome/components/uart/button/* @ssieb +esphome/components/uart/event/* @eoasmxd esphome/components/uart/packet_transport/* @clydebarrow esphome/components/udp/* @clydebarrow esphome/components/ufire_ec/* @pvizeli diff --git a/esphome/components/uart/event/__init__.py b/esphome/components/uart/event/__init__.py new file mode 100644 index 0000000000..64af318a11 --- /dev/null +++ b/esphome/components/uart/event/__init__.py @@ -0,0 +1,90 @@ +import esphome.codegen as cg +from esphome.components import event, uart +import esphome.config_validation as cv +from esphome.const import CONF_EVENT_TYPES, CONF_ID +from esphome.core import ID +from esphome.types import ConfigType + +from .. import uart_ns + +CODEOWNERS = ["@eoasmxd"] + +DEPENDENCIES = ["uart"] + +UARTEvent = uart_ns.class_("UARTEvent", event.Event, uart.UARTDevice, cg.Component) + + +def validate_event_types(value) -> list[tuple[str, str | list[int]]]: + if not isinstance(value, list): + raise cv.Invalid("Event type must be a list of key-value mappings.") + + processed: list[tuple[str, str | list[int]]] = [] + for item in value: + if not isinstance(item, dict): + raise cv.Invalid(f"Event type item must be a mapping (dictionary): {item}") + if len(item) != 1: + raise cv.Invalid( + f"Event type item must be a single key-value mapping: {item}" + ) + + # Get the single key-value pair + event_name, match_data = next(iter(item.items())) + + if not isinstance(event_name, str): + raise cv.Invalid(f"Event name (key) must be a string: {event_name}") + + try: + # Try to validate as list of hex bytes + match_data_bin = cv.ensure_list(cv.hex_uint8_t)(match_data) + processed.append((event_name, match_data_bin)) + continue + except cv.Invalid: + pass # Not binary, try string + + try: + # Try to validate as string + match_data_str = cv.string_strict(match_data) + processed.append((event_name, match_data_str)) + continue + except cv.Invalid: + pass # Not string either + + # If neither validation passed + raise cv.Invalid( + f"Event match data for '{event_name}' must be a string or a list of hex bytes. Invalid data: {match_data}" + ) + + if not processed: + raise cv.Invalid("event_types must contain at least one event mapping.") + + return processed + + +CONFIG_SCHEMA = ( + event.event_schema(UARTEvent) + .extend( + { + cv.Required(CONF_EVENT_TYPES): validate_event_types, + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config: ConfigType) -> None: + event_names = [item[0] for item in config[CONF_EVENT_TYPES]] + var = await event.new_event(config, event_types=event_names) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + for i, (event_name, match_data) in enumerate(config[CONF_EVENT_TYPES]): + if isinstance(match_data, str): + match_data = [ord(c) for c in match_data] + + match_data_var_id = ID( + f"match_data_{config[CONF_ID]}_{i}", is_declaration=True, type=cg.uint8 + ) + match_data_var = cg.static_const_array( + match_data_var_id, cg.ArrayInitializer(*match_data) + ) + cg.add(var.add_event_matcher(event_name, match_data_var, len(match_data))) diff --git a/esphome/components/uart/event/uart_event.cpp b/esphome/components/uart/event/uart_event.cpp new file mode 100644 index 0000000000..02c5f2e631 --- /dev/null +++ b/esphome/components/uart/event/uart_event.cpp @@ -0,0 +1,48 @@ +#include "uart_event.h" +#include "esphome/core/log.h" +#include + +namespace esphome::uart { + +static const char *const TAG = "uart.event"; + +void UARTEvent::setup() {} + +void UARTEvent::dump_config() { LOG_EVENT("", "UART Event", this); } + +void UARTEvent::loop() { this->read_data_(); } + +void UARTEvent::add_event_matcher(const char *event_name, const uint8_t *match_data, size_t match_data_len) { + this->matchers_.push_back({event_name, match_data, match_data_len}); + if (match_data_len > this->max_matcher_len_) { + this->max_matcher_len_ = match_data_len; + } +} + +void UARTEvent::read_data_() { + while (this->available()) { + uint8_t data; + this->read_byte(&data); + this->buffer_.push_back(data); + + bool match_found = false; + for (const auto &matcher : this->matchers_) { + if (this->buffer_.size() < matcher.data_len) { + continue; + } + + if (std::equal(matcher.data, matcher.data + matcher.data_len, this->buffer_.end() - matcher.data_len)) { + this->trigger(matcher.event_name); + this->buffer_.clear(); + match_found = true; + break; + } + } + + if (!match_found && this->max_matcher_len_ > 0 && this->buffer_.size() > this->max_matcher_len_) { + this->buffer_.erase(this->buffer_.begin()); + } + } +} + +} // namespace esphome::uart diff --git a/esphome/components/uart/event/uart_event.h b/esphome/components/uart/event/uart_event.h new file mode 100644 index 0000000000..8a00b5894b --- /dev/null +++ b/esphome/components/uart/event/uart_event.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/event/event.h" +#include "esphome/components/uart/uart.h" +#include + +namespace esphome::uart { + +class UARTEvent : public event::Event, public UARTDevice, public Component { + public: + void setup() override; + void loop() override; + void dump_config() override; + + void add_event_matcher(const char *event_name, const uint8_t *match_data, size_t match_data_len); + + protected: + struct EventMatcher { + const char *event_name; + const uint8_t *data; + size_t data_len; + }; + + void read_data_(); + std::vector matchers_; + std::vector buffer_; + size_t max_matcher_len_ = 0; +}; + +} // namespace esphome::uart diff --git a/tests/components/uart/test.esp32-idf.yaml b/tests/components/uart/test.esp32-idf.yaml index 6ffd0d7282..2a97f9a5de 100644 --- a/tests/components/uart/test.esp32-idf.yaml +++ b/tests/components/uart/test.esp32-idf.yaml @@ -75,3 +75,11 @@ button: - uart.write: !lambda |- std::string cmd = "VALUE=" + str_sprintf("%.0f", id(test_number).state) + "\r\n"; return std::vector(cmd.begin(), cmd.end()); + +event: + - platform: uart + uart_id: uart_uart + name: "UART Event" + event_types: + - "string_event_A": "*A#" + - "bytes_event_B": [0x2A, 0x42, 0x23] diff --git a/tests/components/uart/test.esp8266-ard.yaml b/tests/components/uart/test.esp8266-ard.yaml index 566038ee3e..c2670b289a 100644 --- a/tests/components/uart/test.esp8266-ard.yaml +++ b/tests/components/uart/test.esp8266-ard.yaml @@ -31,3 +31,11 @@ button: name: "UART Button" uart_id: uart_uart data: [0xFF, 0xEE] + +event: + - platform: uart + uart_id: uart_uart + name: "UART Event" + event_types: + - "string_event_A": "*A#" + - "bytes_event_B": [0x2A, 0x42, 0x23] From b4c92dd8cbbf61810e876c3fbb3ca0ebaa199600 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 14:29:47 -1000 Subject: [PATCH 0769/1145] [logger] Zephyr: Use k_str_out() with known length instead of printk() (#12619) --- esphome/components/logger/logger.h | 4 ++-- esphome/components/logger/logger_zephyr.cpp | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 36195b919a..2cedd7a76f 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -118,11 +118,11 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128; static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; // Platform-specific: does write_msg_ add its own newline? -// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, RP2040, LibreTiny) +// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, RP2040, LibreTiny, Zephyr) // Allows single write call with newline included for efficiency // true: write_msg_ adds newline itself via puts()/println() (other platforms) // Newline should NOT be added to buffer -#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) static constexpr bool WRITE_MSG_ADDS_NEWLINE = false; #else static constexpr bool WRITE_MSG_ADDS_NEWLINE = true; diff --git a/esphome/components/logger/logger_zephyr.cpp b/esphome/components/logger/logger_zephyr.cpp index ec2ff3013c..330dfa96ec 100644 --- a/esphome/components/logger/logger_zephyr.cpp +++ b/esphome/components/logger/logger_zephyr.cpp @@ -6,6 +6,7 @@ #include #include +#include #include namespace esphome::logger { @@ -14,7 +15,7 @@ static const char *const TAG = "logger"; #ifdef USE_LOGGER_USB_CDC void Logger::loop() { - if (this->uart_ != UART_SELECTION_USB_CDC || nullptr == this->uart_dev_) { + if (this->uart_ != UART_SELECTION_USB_CDC || this->uart_dev_ == nullptr) { return; } static bool opened = false; @@ -62,18 +63,17 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg, size_t) { +void HOT Logger::write_msg_(const char *msg, size_t len) { + // Single write with newline already in buffer (added by caller) #ifdef CONFIG_PRINTK - printk("%s\n", msg); + k_str_out(const_cast(msg), len); #endif - if (nullptr == this->uart_dev_) { + if (this->uart_dev_ == nullptr) { return; } - while (*msg) { - uart_poll_out(this->uart_dev_, *msg); - ++msg; + for (size_t i = 0; i < len; ++i) { + uart_poll_out(this->uart_dev_, msg[i]); } - uart_poll_out(this->uart_dev_, '\n'); } const LogString *Logger::get_uart_selection_() { From 7d5342bca5df37ba48e735342f72f3d615a01555 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 16:45:22 -1000 Subject: [PATCH 0770/1145] [logger] Host: Use fwrite() with explicit length and remove platform branching (#12628) --- esphome/components/logger/logger.cpp | 4 +-- esphome/components/logger/logger.h | 41 ++++++++--------------- esphome/components/logger/logger_host.cpp | 23 ++++++++----- 3 files changed, 31 insertions(+), 37 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 21e2b44808..474eb9ec38 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -65,8 +65,8 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch uint16_t buffer_at = 0; // Initialize buffer position this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE); - // Add newline if platform needs it (ESP32 doesn't add via write_msg_) - this->add_newline_to_buffer_if_needed_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE); + // Add newline before writing to console + this->add_newline_to_buffer_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE); this->write_msg_(console_buffer, buffer_at); } diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 2cedd7a76f..ba8d4667b6 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -117,17 +117,6 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128; // "0x" + 2 hex digits per byte + '\0' static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; -// Platform-specific: does write_msg_ add its own newline? -// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, RP2040, LibreTiny, Zephyr) -// Allows single write call with newline included for efficiency -// true: write_msg_ adds newline itself via puts()/println() (other platforms) -// Newline should NOT be added to buffer -#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) -static constexpr bool WRITE_MSG_ADDS_NEWLINE = false; -#else -static constexpr bool WRITE_MSG_ADDS_NEWLINE = true; -#endif - #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) /** Enum for logging UART selection * @@ -259,22 +248,20 @@ class Logger : public Component { } } - // Helper to add newline to buffer for platforms that need it + // Helper to add newline to buffer before writing to console // Modifies buffer_at to include the newline - inline void HOT add_newline_to_buffer_if_needed_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { - if constexpr (!WRITE_MSG_ADDS_NEWLINE) { - // Add newline - don't need to maintain null termination - // write_msg_ now always receives explicit length, so we can safely overwrite the null terminator - // This is safe because: - // 1. Callbacks already received the message (before we add newline) - // 2. write_msg_ receives the length explicitly (doesn't need null terminator) - if (*buffer_at < buffer_size) { - buffer[(*buffer_at)++] = '\n'; - } else if (buffer_size > 0) { - // Buffer was full - replace last char with newline to ensure it's visible - buffer[buffer_size - 1] = '\n'; - *buffer_at = buffer_size; - } + inline void HOT add_newline_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { + // Add newline - don't need to maintain null termination + // write_msg_ receives explicit length, so we can safely overwrite the null terminator + // This is safe because: + // 1. Callbacks already received the message (before we add newline) + // 2. write_msg_ receives the length explicitly (doesn't need null terminator) + if (*buffer_at < buffer_size) { + buffer[(*buffer_at)++] = '\n'; + } else if (buffer_size > 0) { + // Buffer was full - replace last char with newline to ensure it's visible + buffer[buffer_size - 1] = '\n'; + *buffer_at = buffer_size; } } @@ -283,7 +270,7 @@ class Logger : public Component { inline void HOT write_tx_buffer_to_console_(uint16_t offset = 0, uint16_t *length = nullptr) { if (this->baud_rate_ > 0) { uint16_t *len_ptr = length ? length : &this->tx_buffer_at_; - this->add_newline_to_buffer_if_needed_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset); + this->add_newline_to_buffer_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset); this->write_msg_(this->tx_buffer_ + offset, *len_ptr); } } diff --git a/esphome/components/logger/logger_host.cpp b/esphome/components/logger/logger_host.cpp index c5e1e6f865..cbca06e431 100644 --- a/esphome/components/logger/logger_host.cpp +++ b/esphome/components/logger/logger_host.cpp @@ -3,16 +3,23 @@ namespace esphome::logger { -void HOT Logger::write_msg_(const char *msg, size_t) { - time_t rawtime; - struct tm *timeinfo; - char buffer[80]; +void HOT Logger::write_msg_(const char *msg, size_t len) { + static constexpr size_t TIMESTAMP_LEN = 10; // "[HH:MM:SS]" + // tx_buffer_size_ defaults to 512, so 768 covers default + headroom + char buffer[TIMESTAMP_LEN + 768]; + time_t rawtime; time(&rawtime); - timeinfo = localtime(&rawtime); - strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo); - fputs(buffer, stdout); - puts(msg); + struct tm *timeinfo = localtime(&rawtime); + size_t pos = strftime(buffer, TIMESTAMP_LEN + 1, "[%H:%M:%S]", timeinfo); + + // Copy message (with newline already included by caller) + size_t copy_len = std::min(len, sizeof(buffer) - pos); + memcpy(buffer + pos, msg, copy_len); + pos += copy_len; + + // Single write for everything + fwrite(buffer, 1, pos, stdout); } void Logger::pre_setup() { global_logger = this; } From ffefa8929ede21b22c1c37d8415074699778a34c Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:05:48 -0500 Subject: [PATCH 0771/1145] [cc1101] Fix packet mode RSSI/LQI (#12630) Co-authored-by: Claude --- esphome/components/cc1101/cc1101.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index f98afd94a1..7e5309e165 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -169,14 +169,16 @@ void CC1101Component::loop() { } // Read packet - uint8_t payload_length; + uint8_t payload_length, expected_rx; if (this->state_.LENGTH_CONFIG == static_cast(LengthConfig::LENGTH_CONFIG_VARIABLE)) { this->read_(Register::FIFO, &payload_length, 1); + expected_rx = payload_length + 1; } else { payload_length = this->state_.PKTLEN; + expected_rx = payload_length; } - if (payload_length == 0 || payload_length > 64) { - ESP_LOGW(TAG, "Invalid payload length: %u", payload_length); + if (payload_length == 0 || payload_length > 64 || rx_bytes != expected_rx) { + ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length); this->enter_idle_(); this->strobe_(Command::FRX); this->strobe_(Command::RX); @@ -186,13 +188,12 @@ void CC1101Component::loop() { this->packet_.resize(payload_length); this->read_(Register::FIFO, this->packet_.data(), payload_length); - // Read status and trigger - uint8_t status[2]; - this->read_(Register::FIFO, status, 2); - int8_t rssi_raw = static_cast(status[0]); - float rssi = (rssi_raw * RSSI_STEP) - RSSI_OFFSET; - bool crc_ok = (status[1] & STATUS_CRC_OK_MASK) != 0; - uint8_t lqi = status[1] & STATUS_LQI_MASK; + // Read status from registers (more reliable than FIFO status bytes due to timing issues) + this->read_(Register::RSSI); + this->read_(Register::LQI); + float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET; + bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0; + uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK; if (this->state_.CRC_EN == 0 || crc_ok) { this->packet_trigger_->trigger(this->packet_, rssi, lqi); } @@ -616,12 +617,15 @@ void CC1101Component::set_packet_mode(bool value) { this->state_.GDO0_CFG = 0x01; // Set max RX FIFO threshold to ensure we only trigger on end-of-packet this->state_.FIFO_THR = 15; + // Don't append status bytes to FIFO - we read from registers instead + this->state_.APPEND_STATUS = 0; } else { // Configure GDO0 for serial data (async serial mode) this->state_.GDO0_CFG = 0x0D; } if (this->initialized_) { this->write_(Register::PKTCTRL0); + this->write_(Register::PKTCTRL1); this->write_(Register::IOCFG0); this->write_(Register::FIFOTHR); } From dc943d7e7a5cf2cb7be4081d69fe605ffe0e63ca Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Wed, 17 Dec 2025 04:52:28 +0100 Subject: [PATCH 0772/1145] [pca9685,sx126x,sx127x] Use frequency/float_range check (#12490) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/ade7880/sensor.py | 2 +- esphome/components/cc1101/__init__.py | 2 +- esphome/components/esp32_camera/__init__.py | 2 +- esphome/components/esp8266_pwm/output.py | 2 +- esphome/components/i2c/__init__.py | 2 +- esphome/components/ledc/output.py | 4 +++- esphome/components/libretiny_pwm/output.py | 4 +++- esphome/components/pca9685/__init__.py | 2 +- esphome/components/rp2040_pwm/output.py | 2 +- esphome/components/sx126x/__init__.py | 8 ++++++-- esphome/components/sx127x/__init__.py | 8 ++++++-- 11 files changed, 25 insertions(+), 13 deletions(-) diff --git a/esphome/components/ade7880/sensor.py b/esphome/components/ade7880/sensor.py index 39dbeb225f..beb74d7310 100644 --- a/esphome/components/ade7880/sensor.py +++ b/esphome/components/ade7880/sensor.py @@ -227,7 +227,7 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(ADE7880), cv.Optional(CONF_FREQUENCY, default="50Hz"): cv.All( - cv.frequency, cv.Range(min=45.0, max=66.0) + cv.frequency, cv.float_range(min=45.0, max=66.0) ), cv.Optional(CONF_IRQ0_PIN): pins.internal_gpio_input_pin_schema, cv.Required(CONF_IRQ1_PIN): pins.internal_gpio_input_pin_schema, diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index 1971817fb1..e314da7079 100644 --- a/esphome/components/cc1101/__init__.py +++ b/esphome/components/cc1101/__init__.py @@ -165,7 +165,7 @@ CONFIG_MAP = { CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0), CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False), CONF_DC_BLOCKING_FILTER: cv.boolean, - CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300000000, max=928000000)), + CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)), CONF_IF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=25000, max=788000)), CONF_FILTER_BANDWIDTH: cv.All(cv.frequency, cv.float_range(min=58000, max=812000)), CONF_CHANNEL: cv.uint8_t, diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 2ad48173f1..ca37cb392d 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -186,7 +186,7 @@ CONFIG_SCHEMA = cv.All( { cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( - cv.frequency, cv.Range(min=8e6, max=20e6) + cv.frequency, cv.float_range(min=8e6, max=20e6) ), } ), diff --git a/esphome/components/esp8266_pwm/output.py b/esphome/components/esp8266_pwm/output.py index 1404ef8ac3..2ddf4b9014 100644 --- a/esphome/components/esp8266_pwm/output.py +++ b/esphome/components/esp8266_pwm/output.py @@ -16,7 +16,7 @@ def valid_pwm_pin(value): esp8266_pwm_ns = cg.esphome_ns.namespace("esp8266_pwm") ESP8266PWM = esp8266_pwm_ns.class_("ESP8266PWM", output.FloatOutput, cg.Component) SetFrequencyAction = esp8266_pwm_ns.class_("SetFrequencyAction", automation.Action) -validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6)) +validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6)) CONFIG_SCHEMA = cv.All( output.FLOAT_OUTPUT_SCHEMA.extend( diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 9e7c9d702c..7706484e97 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -121,7 +121,7 @@ CONFIG_SCHEMA = cv.All( nrf52="100kHz", ): cv.All( cv.frequency, - cv.Range(min=0, min_included=False), + cv.float_range(min=0, min_included=False), ), cv.Optional(CONF_TIMEOUT): cv.All( cv.only_with_framework(["arduino", "esp-idf"]), diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 2133c4daf9..7ce79aa514 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -45,7 +45,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { cv.Required(CONF_ID): cv.declare_id(LEDCOutput), cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency, + cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All( + cv.frequency, cv.float_range(min=0, min_included=False) + ), cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15), cv.Optional(CONF_PHASE_ANGLE): cv.All( cv.only_with_esp_idf, cv.angle, cv.float_range(min=0.0, max=360.0) diff --git a/esphome/components/libretiny_pwm/output.py b/esphome/components/libretiny_pwm/output.py index 1eb4869da3..28556514d8 100644 --- a/esphome/components/libretiny_pwm/output.py +++ b/esphome/components/libretiny_pwm/output.py @@ -14,7 +14,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { cv.Required(CONF_ID): cv.declare_id(LibreTinyPWM), cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency, + cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All( + cv.frequency, cv.float_range(min=0, min_included=False) + ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/pca9685/__init__.py b/esphome/components/pca9685/__init__.py index 56101c2d62..0e238ff7da 100644 --- a/esphome/components/pca9685/__init__.py +++ b/esphome/components/pca9685/__init__.py @@ -38,7 +38,7 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(PCA9685Output), cv.Optional(CONF_FREQUENCY): cv.All( - cv.frequency, cv.Range(min=23.84, max=1525.88) + cv.frequency, cv.float_range(min=23.84, max=1525.88) ), cv.Optional(CONF_EXTERNAL_CLOCK_INPUT, default=False): cv.boolean, cv.Optional(CONF_PHASE_BALANCER, default="linear"): cv.enum( diff --git a/esphome/components/rp2040_pwm/output.py b/esphome/components/rp2040_pwm/output.py index ac1892fa29..441a52de7f 100644 --- a/esphome/components/rp2040_pwm/output.py +++ b/esphome/components/rp2040_pwm/output.py @@ -11,7 +11,7 @@ DEPENDENCIES = ["rp2040"] rp2040_pwm_ns = cg.esphome_ns.namespace("rp2040_pwm") RP2040PWM = rp2040_pwm_ns.class_("RP2040PWM", output.FloatOutput, cg.Component) SetFrequencyAction = rp2040_pwm_ns.class_("SetFrequencyAction", automation.Action) -validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6)) +validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6)) CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { diff --git a/esphome/components/sx126x/__init__.py b/esphome/components/sx126x/__init__.py index 4641db6483..ed878ed0d4 100644 --- a/esphome/components/sx126x/__init__.py +++ b/esphome/components/sx126x/__init__.py @@ -199,9 +199,13 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_CRC_INITIAL, default=0x1D0F): cv.All( cv.hex_int, cv.Range(min=0, max=0xFFFF) ), - cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000), + cv.Optional(CONF_DEVIATION, default="5kHz"): cv.All( + cv.frequency, cv.float_range(min=0, max=100000) + ), cv.Required(CONF_DIO1_PIN): pins.gpio_input_pin_schema, - cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000), + cv.Required(CONF_FREQUENCY): cv.All( + cv.frequency, cv.float_range(min=137.0e6, max=1020.0e6) + ), cv.Required(CONF_HW_VERSION): cv.one_of( "sx1261", "sx1262", "sx1268", "llcc68", lower=True ), diff --git a/esphome/components/sx127x/__init__.py b/esphome/components/sx127x/__init__.py index b569a75972..f3a9cca93f 100644 --- a/esphome/components/sx127x/__init__.py +++ b/esphome/components/sx127x/__init__.py @@ -196,9 +196,13 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_BITSYNC): cv.boolean, cv.Optional(CONF_CODING_RATE, default="CR_4_5"): cv.enum(CODING_RATE), cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean, - cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000), + cv.Optional(CONF_DEVIATION, default="5kHz"): cv.All( + cv.frequency, cv.float_range(min=0, max=100000) + ), cv.Optional(CONF_DIO0_PIN): pins.internal_gpio_input_pin_schema, - cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000), + cv.Required(CONF_FREQUENCY): cv.All( + cv.frequency, cv.float_range(min=137.0e6, max=1020.0e6) + ), cv.Required(CONF_MODULATION): cv.enum(MOD), cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True), cv.Optional(CONF_PA_PIN, default="BOOST"): cv.enum(PA_PIN), From 1922455fa74082b8fc22455080b98cd848cdea04 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 19 Dec 2025 20:25:16 -0600 Subject: [PATCH 0773/1145] [wifi] Fix for `wifi_info` when static IP is configured (#12576) --- esphome/components/wifi/wifi_component_esp8266.cpp | 10 ++++++++++ esphome/components/wifi/wifi_component_esp_idf.cpp | 8 ++++++++ esphome/components/wifi/wifi_component_libretiny.cpp | 8 ++++++++ esphome/components/wifi/wifi_component_pico_w.cpp | 9 +++++++++ 4 files changed, 35 insertions(+) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 3b1a442bdb..41594b947c 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -528,6 +528,16 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { for (auto *listener : global_wifi_component->connect_state_listeners_) { listener->on_wifi_connect_state(global_wifi_component->wifi_ssid(), global_wifi_component->wifi_bssid()); } + // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here +#ifdef USE_WIFI_MANUAL_IP + if (const WiFiAP *config = global_wifi_component->get_selected_sta_(); + config && config->get_manual_ip().has_value()) { + for (auto *listener : global_wifi_component->ip_state_listeners_) { + listener->on_ip_state(global_wifi_component->wifi_sta_ip_addresses(), + global_wifi_component->get_dns_address(0), global_wifi_component->get_dns_address(1)); + } + } +#endif #endif break; } diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 4a3c40a119..380e4ea7fd 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -739,6 +739,14 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { for (auto *listener : this->connect_state_listeners_) { listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); } + // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here +#ifdef USE_WIFI_MANUAL_IP + if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } + } +#endif #endif } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) { diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 36003a6eb4..1012b0be6c 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -305,6 +305,14 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ for (auto *listener : this->connect_state_listeners_) { listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); } + // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here +#ifdef USE_WIFI_MANUAL_IP + if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } + } +#endif #endif break; } diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 0228755432..c88aeb2d4f 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -259,6 +259,15 @@ void WiFiComponent::wifi_loop_() { for (auto *listener : this->connect_state_listeners_) { listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); } + // For static IP configurations, notify IP listeners immediately as the IP is already configured +#ifdef USE_WIFI_MANUAL_IP + if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { + s_sta_had_ip = true; + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } + } +#endif #endif } else if (!is_connected && s_sta_was_connected) { // Just disconnected From 726db746c815751618def87a15218ee228be8f98 Mon Sep 17 00:00:00 2001 From: Eduard Llull Date: Sat, 20 Dec 2025 16:59:14 +0100 Subject: [PATCH 0774/1145] [display_menu_base] Call on_value_ after updating the select (#12584) --- esphome/components/display_menu_base/menu_item.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/display_menu_base/menu_item.cpp b/esphome/components/display_menu_base/menu_item.cpp index 8224adf3fe..08f758045e 100644 --- a/esphome/components/display_menu_base/menu_item.cpp +++ b/esphome/components/display_menu_base/menu_item.cpp @@ -54,6 +54,7 @@ bool MenuItemSelect::select_next() { if (this->select_var_ != nullptr) { this->select_var_->make_call().select_next(true).perform(); + this->on_value_(); changed = true; } @@ -65,6 +66,7 @@ bool MenuItemSelect::select_prev() { if (this->select_var_ != nullptr) { this->select_var_->make_call().select_previous(true).perform(); + this->on_value_(); changed = true; } From b055f5b4bf9783d59b72833976a23c2a835606ec Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Sat, 20 Dec 2025 10:18:20 -0800 Subject: [PATCH 0775/1145] [hub75] Bump esp-hub75 version to 0.1.7 (#12564) --- .clang-tidy.hash | 2 +- esphome/components/hub75/display.py | 46 ++++++++++++++--------------- esphome/idf_component.yml | 4 +++ platformio.ini | 2 -- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index a3322ba731..240b205158 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -766420905c06eeb6c5f360f68fd965e5ddd9c4a5db6b823263d3ad3accb64a07 +4268ab0b5150f79ab1c317e8f3834c8bb0b4c8122da4f6b1fd67c49d0f2098c9 diff --git a/esphome/components/hub75/display.py b/esphome/components/hub75/display.py index 81dd4ffc1c..0518731a6a 100644 --- a/esphome/components/hub75/display.py +++ b/esphome/components/hub75/display.py @@ -93,35 +93,35 @@ CONF_DOUBLE_BUFFER = "double_buffer" CONF_MIN_REFRESH_RATE = "min_refresh_rate" # Map to hub75 library enums (in global namespace) -ShiftDriver = cg.global_ns.enum("ShiftDriver", is_class=True) +Hub75ShiftDriver = cg.global_ns.enum("Hub75ShiftDriver", is_class=True) SHIFT_DRIVERS = { - "GENERIC": ShiftDriver.GENERIC, - "FM6126A": ShiftDriver.FM6126A, - "ICN2038S": ShiftDriver.ICN2038S, - "FM6124": ShiftDriver.FM6124, - "MBI5124": ShiftDriver.MBI5124, - "DP3246": ShiftDriver.DP3246, + "GENERIC": Hub75ShiftDriver.GENERIC, + "FM6126A": Hub75ShiftDriver.FM6126A, + "ICN2038S": Hub75ShiftDriver.ICN2038S, + "FM6124": Hub75ShiftDriver.FM6124, + "MBI5124": Hub75ShiftDriver.MBI5124, + "DP3246": Hub75ShiftDriver.DP3246, } -PanelLayout = cg.global_ns.enum("PanelLayout", is_class=True) +Hub75PanelLayout = cg.global_ns.enum("Hub75PanelLayout", is_class=True) PANEL_LAYOUTS = { - "HORIZONTAL": PanelLayout.HORIZONTAL, - "TOP_LEFT_DOWN": PanelLayout.TOP_LEFT_DOWN, - "TOP_RIGHT_DOWN": PanelLayout.TOP_RIGHT_DOWN, - "BOTTOM_LEFT_UP": PanelLayout.BOTTOM_LEFT_UP, - "BOTTOM_RIGHT_UP": PanelLayout.BOTTOM_RIGHT_UP, - "TOP_LEFT_DOWN_ZIGZAG": PanelLayout.TOP_LEFT_DOWN_ZIGZAG, - "TOP_RIGHT_DOWN_ZIGZAG": PanelLayout.TOP_RIGHT_DOWN_ZIGZAG, - "BOTTOM_LEFT_UP_ZIGZAG": PanelLayout.BOTTOM_LEFT_UP_ZIGZAG, - "BOTTOM_RIGHT_UP_ZIGZAG": PanelLayout.BOTTOM_RIGHT_UP_ZIGZAG, + "HORIZONTAL": Hub75PanelLayout.HORIZONTAL, + "TOP_LEFT_DOWN": Hub75PanelLayout.TOP_LEFT_DOWN, + "TOP_RIGHT_DOWN": Hub75PanelLayout.TOP_RIGHT_DOWN, + "BOTTOM_LEFT_UP": Hub75PanelLayout.BOTTOM_LEFT_UP, + "BOTTOM_RIGHT_UP": Hub75PanelLayout.BOTTOM_RIGHT_UP, + "TOP_LEFT_DOWN_ZIGZAG": Hub75PanelLayout.TOP_LEFT_DOWN_ZIGZAG, + "TOP_RIGHT_DOWN_ZIGZAG": Hub75PanelLayout.TOP_RIGHT_DOWN_ZIGZAG, + "BOTTOM_LEFT_UP_ZIGZAG": Hub75PanelLayout.BOTTOM_LEFT_UP_ZIGZAG, + "BOTTOM_RIGHT_UP_ZIGZAG": Hub75PanelLayout.BOTTOM_RIGHT_UP_ZIGZAG, } -ScanPattern = cg.global_ns.enum("ScanPattern", is_class=True) +Hub75ScanWiring = cg.global_ns.enum("Hub75ScanWiring", is_class=True) SCAN_PATTERNS = { - "STANDARD_TWO_SCAN": ScanPattern.STANDARD_TWO_SCAN, - "FOUR_SCAN_16PX_HIGH": ScanPattern.FOUR_SCAN_16PX_HIGH, - "FOUR_SCAN_32PX_HIGH": ScanPattern.FOUR_SCAN_32PX_HIGH, - "FOUR_SCAN_64PX_HIGH": ScanPattern.FOUR_SCAN_64PX_HIGH, + "STANDARD_TWO_SCAN": Hub75ScanWiring.STANDARD_TWO_SCAN, + "FOUR_SCAN_16PX_HIGH": Hub75ScanWiring.FOUR_SCAN_16PX_HIGH, + "FOUR_SCAN_32PX_HIGH": Hub75ScanWiring.FOUR_SCAN_32PX_HIGH, + "FOUR_SCAN_64PX_HIGH": Hub75ScanWiring.FOUR_SCAN_64PX_HIGH, } Hub75ClockSpeed = cg.global_ns.enum("Hub75ClockSpeed", is_class=True) @@ -528,7 +528,7 @@ def _build_config_struct( async def to_code(config: ConfigType) -> None: add_idf_component( name="esphome/esp-hub75", - ref="0.1.6", + ref="0.1.7", ) # Set compile-time configuration via defines diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 9bb5967248..4573391bc1 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -27,3 +27,7 @@ dependencies: version: "1.7.6~1" rules: - if: "target in [esp32s2, esp32s3, esp32p4]" + esphome/esp-hub75: + version: 0.1.7 + rules: + - if: "target in [esp32, esp32s2, esp32s3, esp32p4]" diff --git a/platformio.ini b/platformio.ini index d37c798c05..a27fb1f537 100644 --- a/platformio.ini +++ b/platformio.ini @@ -156,7 +156,6 @@ lib_deps = esphome/ESP32-audioI2S@2.3.0 ; i2s_audio droscy/esp_wireguard@0.4.2 ; wireguard esphome/esp-audio-libs@2.0.1 ; audio - esphome/esp-hub75@0.1.6 ; hub75 build_flags = ${common:arduino.build_flags} @@ -180,7 +179,6 @@ lib_deps = droscy/esp_wireguard@0.4.2 ; wireguard kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word esphome/esp-audio-libs@2.0.1 ; audio - esphome/esp-hub75@0.1.6 ; hub75 build_flags = ${common:idf.build_flags} -Wno-nonnull-compare From 086ec770ea3dfef98abafd3fe30deeecc6f61e3a Mon Sep 17 00:00:00 2001 From: Leo Bergolth Date: Sat, 20 Dec 2025 21:04:59 +0100 Subject: [PATCH 0776/1145] send NIL ("-") as timestamp if time source is not valid (#12588) --- esphome/components/syslog/esphome_syslog.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/syslog/esphome_syslog.cpp b/esphome/components/syslog/esphome_syslog.cpp index f5c20c891e..851fb30c22 100644 --- a/esphome/components/syslog/esphome_syslog.cpp +++ b/esphome/components/syslog/esphome_syslog.cpp @@ -34,7 +34,15 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t severity = LOG_LEVEL_TO_SYSLOG_SEVERITY[level]; } int pri = this->facility_ * 8 + severity; - auto timestamp = this->time_->now().strftime("%b %e %H:%M:%S"); + auto now = this->time_->now(); + std::string timestamp; + if (now.is_valid()) { + timestamp = now.strftime("%b %e %H:%M:%S"); + } else { + // RFC 5424: A syslog application MUST use the NILVALUE as TIMESTAMP if the syslog application is incapable of + // obtaining system time. + timestamp = "-"; + } size_t len = message_len; // remove color formatting if (this->strip_ && message[0] == 0x1B && len > 11) { From 61ec3508ed75f826ab0f20dacceadc1f212985bf Mon Sep 17 00:00:00 2001 From: Anna Oake Date: Sun, 21 Dec 2025 19:04:17 +0100 Subject: [PATCH 0777/1145] [cc1101] Fix option defaults and move them to YAML (#12608) --- esphome/components/cc1101/__init__.py | 95 +++++++++++++++++---------- esphome/components/cc1101/cc1101.cpp | 17 ----- 2 files changed, 59 insertions(+), 53 deletions(-) diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index e314da7079..c205ff2f69 100644 --- a/esphome/components/cc1101/__init__.py +++ b/esphome/components/cc1101/__init__.py @@ -160,41 +160,63 @@ HYST_LEVEL = { "High": HystLevel.HYST_LEVEL_HIGH, } -# Config key -> Validator mapping +# Optional settings to generate setter calls for CONFIG_MAP = { - CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0), - CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False), - CONF_DC_BLOCKING_FILTER: cv.boolean, - CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)), - CONF_IF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=25000, max=788000)), - CONF_FILTER_BANDWIDTH: cv.All(cv.frequency, cv.float_range(min=58000, max=812000)), - CONF_CHANNEL: cv.uint8_t, - CONF_CHANNEL_SPACING: cv.All(cv.frequency, cv.float_range(min=25000, max=405000)), - CONF_FSK_DEVIATION: cv.All(cv.frequency, cv.float_range(min=1500, max=381000)), - CONF_MSK_DEVIATION: cv.int_range(min=1, max=8), - CONF_SYMBOL_RATE: cv.float_range(min=600, max=500000), - CONF_SYNC_MODE: cv.enum(SYNC_MODE, upper=False), - CONF_CARRIER_SENSE_ABOVE_THRESHOLD: cv.boolean, - CONF_MODULATION_TYPE: cv.enum(MODULATION, upper=False), - CONF_MANCHESTER: cv.boolean, - CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7), - CONF_SYNC1: cv.hex_uint8_t, - CONF_SYNC0: cv.hex_uint8_t, - CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False), - CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False), - CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False), - CONF_CARRIER_SENSE_ABS_THR: cv.int_range(min=-8, max=7), - CONF_CARRIER_SENSE_REL_THR: cv.enum(CARRIER_SENSE_REL_THR, upper=False), - CONF_LNA_PRIORITY: cv.boolean, - CONF_FILTER_LENGTH_FSK_MSK: cv.enum(FILTER_LENGTH_FSK_MSK, upper=False), - CONF_FILTER_LENGTH_ASK_OOK: cv.enum(FILTER_LENGTH_ASK_OOK, upper=False), - CONF_FREEZE: cv.enum(FREEZE, upper=False), - CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False), - CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False), - CONF_PACKET_MODE: cv.boolean, - CONF_PACKET_LENGTH: cv.uint8_t, - CONF_CRC_ENABLE: cv.boolean, - CONF_WHITENING: cv.boolean, + cv.Optional(CONF_OUTPUT_POWER, default=10): cv.float_range(min=-30.0, max=11.0), + cv.Optional(CONF_RX_ATTENUATION, default="0dB"): cv.enum( + RX_ATTENUATION, upper=False + ), + cv.Optional(CONF_DC_BLOCKING_FILTER, default=True): cv.boolean, + cv.Optional(CONF_FREQUENCY, default="433.92MHz"): cv.All( + cv.frequency, cv.float_range(min=300.0e6, max=928.0e6) + ), + cv.Optional(CONF_IF_FREQUENCY, default="153kHz"): cv.All( + cv.frequency, cv.float_range(min=25000, max=788000) + ), + cv.Optional(CONF_FILTER_BANDWIDTH, default="203kHz"): cv.All( + cv.frequency, cv.float_range(min=58000, max=812000) + ), + cv.Optional(CONF_CHANNEL, default=0): cv.uint8_t, + cv.Optional(CONF_CHANNEL_SPACING, default="200kHz"): cv.All( + cv.frequency, cv.float_range(min=25000, max=405000) + ), + cv.Optional(CONF_FSK_DEVIATION): cv.All( + cv.frequency, cv.float_range(min=1500, max=381000) + ), + cv.Optional(CONF_MSK_DEVIATION): cv.int_range(min=1, max=8), + cv.Optional(CONF_SYMBOL_RATE, default=5000): cv.float_range(min=600, max=500000), + cv.Optional(CONF_SYNC_MODE, default="16/16"): cv.enum(SYNC_MODE, upper=False), + cv.Optional(CONF_CARRIER_SENSE_ABOVE_THRESHOLD, default=False): cv.boolean, + cv.Optional(CONF_MODULATION_TYPE, default="ASK/OOK"): cv.enum( + MODULATION, upper=False + ), + cv.Optional(CONF_MANCHESTER, default=False): cv.boolean, + cv.Optional(CONF_NUM_PREAMBLE, default=2): cv.int_range(min=0, max=7), + cv.Optional(CONF_SYNC1, default=0xD3): cv.hex_uint8_t, + cv.Optional(CONF_SYNC0, default=0x91): cv.hex_uint8_t, + cv.Optional(CONF_MAGN_TARGET, default="42dB"): cv.enum(MAGN_TARGET, upper=False), + cv.Optional(CONF_MAX_LNA_GAIN, default="Default"): cv.enum( + MAX_LNA_GAIN, upper=False + ), + cv.Optional(CONF_MAX_DVGA_GAIN, default="-3"): cv.enum(MAX_DVGA_GAIN, upper=False), + cv.Optional(CONF_CARRIER_SENSE_ABS_THR): cv.int_range(min=-8, max=7), + cv.Optional(CONF_CARRIER_SENSE_REL_THR): cv.enum( + CARRIER_SENSE_REL_THR, upper=False + ), + cv.Optional(CONF_LNA_PRIORITY, default=False): cv.boolean, + cv.Optional(CONF_FILTER_LENGTH_FSK_MSK): cv.enum( + FILTER_LENGTH_FSK_MSK, upper=False + ), + cv.Optional(CONF_FILTER_LENGTH_ASK_OOK): cv.enum( + FILTER_LENGTH_ASK_OOK, upper=False + ), + cv.Optional(CONF_FREEZE): cv.enum(FREEZE, upper=False), + cv.Optional(CONF_WAIT_TIME, default="32"): cv.enum(WAIT_TIME, upper=False), + cv.Optional(CONF_HYST_LEVEL): cv.enum(HYST_LEVEL, upper=False), + cv.Optional(CONF_PACKET_MODE, default=False): cv.boolean, + cv.Optional(CONF_PACKET_LENGTH): cv.uint8_t, + cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean, + cv.Optional(CONF_WHITENING, default=False): cv.boolean, } @@ -217,7 +239,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True), } ) - .extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()}) + .extend(CONFIG_MAP) .extend(cv.COMPONENT_SCHEMA) .extend(spi.spi_device_schema(cs_pin_required=True)), _validate_packet_mode, @@ -229,7 +251,8 @@ async def to_code(config): await cg.register_component(var, config) await spi.register_spi_device(var, config) - for key in CONFIG_MAP: + for opt in CONFIG_MAP: + key = opt.schema if key in config: cg.add(getattr(var, f"set_{key}")(config[key])) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 1fe402d6c6..f98afd94a1 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -98,25 +98,8 @@ CC1101Component::CC1101Component() { this->state_.LENGTH_CONFIG = 2; this->state_.FS_AUTOCAL = 1; - // Default Settings - this->set_frequency(433920000); - this->set_if_frequency(153000); - this->set_filter_bandwidth(203000); - this->set_channel(0); - this->set_channel_spacing(200000); - this->set_symbol_rate(5000); - this->set_sync_mode(SyncMode::SYNC_MODE_NONE); - this->set_carrier_sense_above_threshold(true); - this->set_modulation_type(Modulation::MODULATION_ASK_OOK); - this->set_magn_target(MagnTarget::MAGN_TARGET_42DB); - this->set_max_lna_gain(MaxLnaGain::MAX_LNA_GAIN_DEFAULT); - this->set_max_dvga_gain(MaxDvgaGain::MAX_DVGA_GAIN_MINUS_3); - this->set_lna_priority(false); - this->set_wait_time(WaitTime::WAIT_TIME_32_SAMPLES); - // CRITICAL: Initialize PA Table to avoid transmitting 0 power (Silence) memset(this->pa_table_, 0, sizeof(this->pa_table_)); - this->set_output_power(10.0f); } void CC1101Component::setup() { From 6054685daee93e6c4baad681df16024d88613112 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Dec 2025 09:04:43 -1000 Subject: [PATCH 0778/1145] [esp32_camera] Throttle frame logging to reduce overhead and improve throughput (#12586) --- .../components/esp32_camera/esp32_camera.cpp | 18 +++++++++++++++++- esphome/components/esp32_camera/esp32_camera.h | 4 ++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 5080a6f32d..a3677330ca 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -11,6 +11,9 @@ namespace esphome { namespace esp32_camera { static const char *const TAG = "esp32_camera"; +#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE +static constexpr uint32_t FRAME_LOG_INTERVAL_MS = 60000; +#endif /* ---------------- public API (derivated) ---------------- */ void ESP32Camera::setup() { @@ -204,7 +207,20 @@ void ESP32Camera::loop() { } this->current_image_ = std::make_shared(fb, this->single_requesters_ | this->stream_requesters_); - ESP_LOGD(TAG, "Got Image: len=%u", fb->len); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + ESP_LOGV(TAG, "Got Image: len=%u", fb->len); +#else + // Initialize log time on first frame to ensure accurate interval measurement + if (this->frame_count_ == 0) { + this->last_log_time_ = now; + } + this->frame_count_++; + if (now - this->last_log_time_ >= FRAME_LOG_INTERVAL_MS) { + ESP_LOGD(TAG, "Received %u images in last %us", this->frame_count_, FRAME_LOG_INTERVAL_MS / 1000); + this->last_log_time_ = now; + this->frame_count_ = 0; + } +#endif for (auto *listener : this->listeners_) { listener->on_camera_image(this->current_image_); } diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 54a7d6064a..a49fca6511 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -213,6 +213,10 @@ class ESP32Camera : public camera::Camera { uint32_t last_idle_request_{0}; uint32_t last_update_{0}; +#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE + uint32_t last_log_time_{0}; + uint16_t frame_count_{0}; +#endif #ifdef USE_I2C i2c::InternalI2CBus *i2c_bus_{nullptr}; #endif // USE_I2C From c8fb694dcbdc1b837fcaf6683b13e43722bd6431 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:05:48 -0500 Subject: [PATCH 0779/1145] [cc1101] Fix packet mode RSSI/LQI (#12630) Co-authored-by: Claude --- esphome/components/cc1101/cc1101.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index f98afd94a1..7e5309e165 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -169,14 +169,16 @@ void CC1101Component::loop() { } // Read packet - uint8_t payload_length; + uint8_t payload_length, expected_rx; if (this->state_.LENGTH_CONFIG == static_cast(LengthConfig::LENGTH_CONFIG_VARIABLE)) { this->read_(Register::FIFO, &payload_length, 1); + expected_rx = payload_length + 1; } else { payload_length = this->state_.PKTLEN; + expected_rx = payload_length; } - if (payload_length == 0 || payload_length > 64) { - ESP_LOGW(TAG, "Invalid payload length: %u", payload_length); + if (payload_length == 0 || payload_length > 64 || rx_bytes != expected_rx) { + ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length); this->enter_idle_(); this->strobe_(Command::FRX); this->strobe_(Command::RX); @@ -186,13 +188,12 @@ void CC1101Component::loop() { this->packet_.resize(payload_length); this->read_(Register::FIFO, this->packet_.data(), payload_length); - // Read status and trigger - uint8_t status[2]; - this->read_(Register::FIFO, status, 2); - int8_t rssi_raw = static_cast(status[0]); - float rssi = (rssi_raw * RSSI_STEP) - RSSI_OFFSET; - bool crc_ok = (status[1] & STATUS_CRC_OK_MASK) != 0; - uint8_t lqi = status[1] & STATUS_LQI_MASK; + // Read status from registers (more reliable than FIFO status bytes due to timing issues) + this->read_(Register::RSSI); + this->read_(Register::LQI); + float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET; + bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0; + uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK; if (this->state_.CRC_EN == 0 || crc_ok) { this->packet_trigger_->trigger(this->packet_, rssi, lqi); } @@ -616,12 +617,15 @@ void CC1101Component::set_packet_mode(bool value) { this->state_.GDO0_CFG = 0x01; // Set max RX FIFO threshold to ensure we only trigger on end-of-packet this->state_.FIFO_THR = 15; + // Don't append status bytes to FIFO - we read from registers instead + this->state_.APPEND_STATUS = 0; } else { // Configure GDO0 for serial data (async serial mode) this->state_.GDO0_CFG = 0x0D; } if (this->initialized_) { this->write_(Register::PKTCTRL0); + this->write_(Register::PKTCTRL1); this->write_(Register::IOCFG0); this->write_(Register::FIFOTHR); } From 0922f240e0ab48a8dd86e59876980481358a47ca Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:23:04 -0500 Subject: [PATCH 0780/1145] Bump version to 2025.12.2 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 4c533ec87f..d41459ea46 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.1 +PROJECT_NUMBER = 2025.12.2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 8f9a3497ff..41bb419aaf 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.1" +__version__ = "2025.12.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From ebb6babb3d9db6952c0dd6af6c4cc14c898d94b1 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:26:38 -0500 Subject: [PATCH 0781/1145] Fix hash --- .clang-tidy.hash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 240b205158..18379de92e 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -4268ab0b5150f79ab1c317e8f3834c8bb0b4c8122da4f6b1fd67c49d0f2098c9 +5969e705693278d984c5292e998df0cbaf34f7e1f04dfc7f7b7ad7168527bfa7 From 0c566c6f00b1bf2fc11a4bb019f1f9292c2f0d5f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Dec 2025 06:59:07 -1000 Subject: [PATCH 0782/1145] [core] Deprecate get_object_id() and migrate remaining usages to get_object_id_to() (#12629) --- esphome/components/pid/pid_climate.cpp | 6 +++--- esphome/components/prometheus/prometheus_handler.cpp | 6 +++++- esphome/core/entity_base.h | 9 +++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index fd74eabd87..2094c0e942 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -162,14 +162,14 @@ void PIDClimate::start_autotune(std::unique_ptr &&autotune) { float min_value = this->supports_cool_() ? -1.0f : 0.0f; float max_value = this->supports_heat_() ? 1.0f : 0.0f; this->autotuner_->config(min_value, max_value); - this->autotuner_->set_autotuner_id(this->get_object_id()); + this->autotuner_->set_autotuner_id(this->get_name()); ESP_LOGI(TAG, "%s: Autotune has started. This can take a long time depending on the " "responsiveness of your system. Your system " "output will be altered to deliberately oscillate above and below the setpoint multiple times. " "Until your sensor provides a reading, the autotuner may display \'nan\'", - this->get_object_id().c_str()); + this->get_name().c_str()); this->set_interval("autotune-progress", 10000, [this]() { if (this->autotuner_ != nullptr && !this->autotuner_->is_finished()) @@ -178,7 +178,7 @@ void PIDClimate::start_autotune(std::unique_ptr &&autotune) { if (mode != climate::CLIMATE_MODE_HEAT_COOL) { ESP_LOGW(TAG, "%s: !!! For PID autotuner you need to set AUTO (also called heat/cool) mode!", - this->get_object_id().c_str()); + this->get_name().c_str()); } } diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 4b5d834ebf..88b357041a 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -112,7 +112,11 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) { std::string PrometheusHandler::relabel_id_(EntityBase *obj) { auto item = relabel_map_id_.find(obj); - return item == relabel_map_id_.end() ? obj->get_object_id() : item->second; + if (item != relabel_map_id_.end()) { + return item->second; + } + char object_id_buf[OBJECT_ID_MAX_LEN]; + return obj->get_object_id_to(object_id_buf).str(); } std::string PrometheusHandler::relabel_name_(EntityBase *obj) { diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index eb1ba46c94..93f989934a 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -33,6 +33,15 @@ class EntityBase { bool has_own_name() const { return this->flags_.has_own_name; } // Get the sanitized name of this Entity as an ID. + // Deprecated: object_id mangles names and all object_id methods are planned for removal. + // See https://github.com/esphome/backlog/issues/76 + // Now is the time to stop using object_id entirely. If you still need it temporarily, + // use get_object_id_to() which will remain available longer but will also eventually be removed. + ESPDEPRECATED("object_id mangles names and all object_id methods are planned for removal " + "(see https://github.com/esphome/backlog/issues/76). " + "Now is the time to stop using object_id. If still needed, use get_object_id_to() " + "which will remain available longer. get_object_id() will be removed in 2026.7.0", + "2025.12.0") std::string get_object_id() const; void set_object_id(const char *object_id); From 958a35e26279a302f6da4d0efd8b7191355b09d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Dec 2025 14:17:52 -1000 Subject: [PATCH 0783/1145] Bump aioesphomeapi from 43.5.0 to 43.6.0 (#12644) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5718ced617..e741a70f48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.5.0 +aioesphomeapi==43.6.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.17 # dashboard_import From 4f706636584137d9c697320928162c8743b03e91 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Dec 2025 07:57:33 -1000 Subject: [PATCH 0784/1145] [alarm_control_panel] Use C++17 nested namespace and remove unused include (#12662) --- .../alarm_control_panel/alarm_control_panel.cpp | 6 ++---- .../components/alarm_control_panel/alarm_control_panel.h | 8 ++------ .../alarm_control_panel/alarm_control_panel_call.cpp | 6 ++---- .../alarm_control_panel/alarm_control_panel_call.h | 6 ++---- .../alarm_control_panel/alarm_control_panel_state.cpp | 6 ++---- .../alarm_control_panel/alarm_control_panel_state.h | 6 ++---- esphome/components/alarm_control_panel/automation.h | 6 ++---- 7 files changed, 14 insertions(+), 30 deletions(-) diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index f938155dd3..89c0908a74 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -8,8 +8,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { static const char *const TAG = "alarm_control_panel"; @@ -115,5 +114,4 @@ void AlarmControlPanel::disarm(optional code) { call.perform(); } -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.h b/esphome/components/alarm_control_panel/alarm_control_panel.h index 59ccf0e484..340f15bcd6 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel.h @@ -1,7 +1,5 @@ #pragma once -#include - #include "alarm_control_panel_call.h" #include "alarm_control_panel_state.h" @@ -9,8 +7,7 @@ #include "esphome/core/entity_base.h" #include "esphome/core/log.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { enum AlarmControlPanelFeature : uint8_t { // Matches Home Assistant values @@ -141,5 +138,4 @@ class AlarmControlPanel : public EntityBase { LazyCallbackManager ready_callback_{}; }; -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp index 7bb9b9989c..5e98d58368 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp @@ -4,8 +4,7 @@ #include "esphome/core/log.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { static const char *const TAG = "alarm_control_panel"; @@ -99,5 +98,4 @@ void AlarmControlPanelCall::perform() { } } -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_call.h b/esphome/components/alarm_control_panel/alarm_control_panel_call.h index 034e3142da..cff00900dd 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_call.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel_call.h @@ -6,8 +6,7 @@ #include "esphome/core/helpers.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { class AlarmControlPanel; @@ -36,5 +35,4 @@ class AlarmControlPanelCall { void validate_(); }; -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp b/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp index abe6f51995..862c620497 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp @@ -1,7 +1,6 @@ #include "alarm_control_panel_state.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) { switch (state) { @@ -30,5 +29,4 @@ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState stat } } -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_state.h b/esphome/components/alarm_control_panel/alarm_control_panel_state.h index ad16222dc0..dd0b91f064 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_state.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel_state.h @@ -3,8 +3,7 @@ #include #include "esphome/core/log.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { enum AlarmControlPanelState : uint8_t { ACP_STATE_DISARMED = 0, @@ -25,5 +24,4 @@ enum AlarmControlPanelState : uint8_t { */ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state); -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/automation.h b/esphome/components/alarm_control_panel/automation.h index af4a14e27a..ce5ceadb47 100644 --- a/esphome/components/alarm_control_panel/automation.h +++ b/esphome/components/alarm_control_panel/automation.h @@ -3,8 +3,7 @@ #include "esphome/core/automation.h" #include "alarm_control_panel.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { /// Trigger on any state change class StateTrigger : public Trigger<> { @@ -165,5 +164,4 @@ template class AlarmControlPanelCondition : public Condition Date: Fri, 26 Dec 2025 07:58:46 -1000 Subject: [PATCH 0785/1145] [text_sensor] Return state by const reference to avoid copies (#12661) --- esphome/components/text_sensor/text_sensor.cpp | 4 ++-- esphome/components/text_sensor/text_sensor.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index ad1dc0f521..8dfb9dad05 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -78,8 +78,8 @@ void TextSensor::add_on_raw_state_callback(std::functionraw_callback_.add(std::move(callback)); } -std::string TextSensor::get_state() const { return this->state; } -std::string TextSensor::get_raw_state() const { +const std::string &TextSensor::get_state() const { return this->state; } +const std::string &TextSensor::get_raw_state() const { // Suppress deprecation warning - get_raw_state() is the replacement API #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 919bf81c8c..2cd8a65e87 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -37,9 +37,9 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { #pragma GCC diagnostic pop /// Getter-syntax for .state. - std::string get_state() const; + const std::string &get_state() const; /// Getter-syntax for .raw_state - std::string get_raw_state() const; + const std::string &get_raw_state() const; void publish_state(const std::string &state); From 0919017d496a8c3613b4fbaeff2447986781201b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Dec 2025 08:44:03 -1000 Subject: [PATCH 0786/1145] [wifi] Avoid unnecessary string copy in failed connection logging (#12659) --- esphome/components/wifi/wifi_component.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 242265344d..5fa894d8f9 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1523,12 +1523,12 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { return; // No BSSID to penalize } - // Get SSID for logging - std::string ssid; + // Get SSID for logging (use pointer to avoid copy) + const std::string *ssid = nullptr; if (this->retry_phase_ == WiFiRetryPhase::SCAN_CONNECTING && !this->scan_result_.empty()) { - ssid = this->scan_result_[0].get_ssid(); + ssid = &this->scan_result_[0].get_ssid(); } else if (const WiFiAP *config = this->get_selected_sta_()) { - ssid = config->get_ssid(); + ssid = &config->get_ssid(); } // Only decrease priority on the last attempt for this phase @@ -1548,8 +1548,8 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { } char bssid_s[18]; format_mac_addr_upper(failed_bssid.value().data(), bssid_s); - ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", ssid.c_str(), bssid_s, - old_priority, new_priority); + ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", + ssid != nullptr ? ssid->c_str() : "", bssid_s, old_priority, new_priority); // After adjusting priority, check if all priorities are now at minimum // If so, clear the vector to save memory and reset for fresh start From f1fecd22e30748759490ca10be1fd4848a08eb72 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Dec 2025 08:44:17 -1000 Subject: [PATCH 0787/1145] [web_server] Move HTTP header strings to flash on ESP8266 (#12668) --- esphome/components/web_server/web_server.cpp | 23 +++++++------------ .../web_server_base/web_server_base.h | 2 +- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index df8a5364cf..8a1ed49408 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -45,13 +45,6 @@ static const char *const TAG = "web_server"; static constexpr size_t PSTR_LOCAL_SIZE = 18; #define PSTR_LOCAL(mode_s) ESPHOME_strncpy_P(buf, (ESPHOME_PGM_P) ((mode_s)), PSTR_LOCAL_SIZE - 1) -#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS -static const char *const HEADER_PNA_NAME = "Private-Network-Access-Name"; -static const char *const HEADER_PNA_ID = "Private-Network-Access-ID"; -static const char *const HEADER_CORS_REQ_PNA = "Access-Control-Request-Private-Network"; -static const char *const HEADER_CORS_ALLOW_PNA = "Access-Control-Allow-Private-Network"; -#endif - // Parse URL and return match info static UrlMatch match_url(const char *url_ptr, size_t url_len, bool only_domain) { UrlMatch match{}; @@ -348,7 +341,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #else AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); #endif - response->addHeader("Content-Encoding", "gzip"); + response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip")); request->send(response); } #elif USE_WEBSERVER_VERSION >= 2 @@ -368,10 +361,10 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS void WebServer::handle_pna_cors_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse(200, ""); - response->addHeader(HEADER_CORS_ALLOW_PNA, "true"); - response->addHeader(HEADER_PNA_NAME, App.get_name().c_str()); + response->addHeader(ESPHOME_F("Access-Control-Allow-Private-Network"), ESPHOME_F("true")); + response->addHeader(ESPHOME_F("Private-Network-Access-Name"), App.get_name().c_str()); char mac_s[18]; - response->addHeader(HEADER_PNA_ID, get_mac_address_pretty_into_buffer(mac_s)); + response->addHeader(ESPHOME_F("Private-Network-Access-ID"), get_mac_address_pretty_into_buffer(mac_s)); request->send(response); } #endif @@ -385,7 +378,7 @@ void WebServer::handle_css_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE); #endif - response->addHeader("Content-Encoding", "gzip"); + response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip")); request->send(response); } #endif @@ -399,7 +392,7 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE); #endif - response->addHeader("Content-Encoding", "gzip"); + response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip")); request->send(response); } #endif @@ -1841,7 +1834,7 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const { } #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS - if (method == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) + if (method == HTTP_OPTIONS && request->hasHeader(ESPHOME_F("Access-Control-Request-Private-Network"))) return true; #endif @@ -1974,7 +1967,7 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { #endif #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS - if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) { + if (request->method() == HTTP_OPTIONS && request->hasHeader(ESPHOME_F("Access-Control-Request-Private-Network"))) { this->handle_pna_cors_request(request); return; } diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 54ec997671..7e95e00f29 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -100,7 +100,7 @@ class WebServerBase : public Component { } this->server_ = std::make_unique(this->port_); // All content is controlled and created by user - so allowing all origins is fine here. - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); + DefaultHeaders::Instance().addHeader(ESPHOME_F("Access-Control-Allow-Origin"), ESPHOME_F("*")); this->server_->begin(); for (auto *handler : this->handlers_) From 5a2e0612a818a73543fca92d5126b9dda523d1b1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Dec 2025 08:44:34 -1000 Subject: [PATCH 0788/1145] [web_server] Use C++17 nested namespace syntax (#12663) --- esphome/components/web_server/list_entities.cpp | 6 ++---- esphome/components/web_server/list_entities.h | 11 +++++------ esphome/components/web_server/ota/ota_web_server.cpp | 6 ++---- esphome/components/web_server/ota/ota_web_server.h | 6 ++---- esphome/components/web_server/server_index_v2.h | 6 ++---- esphome/components/web_server/server_index_v3.h | 6 ++---- esphome/components/web_server/web_server.cpp | 6 ++---- esphome/components/web_server/web_server.h | 6 ++---- esphome/components/web_server/web_server_v1.cpp | 6 ++---- 9 files changed, 21 insertions(+), 38 deletions(-) diff --git a/esphome/components/web_server/list_entities.cpp b/esphome/components/web_server/list_entities.cpp index 16b1d1e797..55beed812f 100644 --- a/esphome/components/web_server/list_entities.cpp +++ b/esphome/components/web_server/list_entities.cpp @@ -6,8 +6,7 @@ #include "web_server.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { #ifdef USE_ESP32 ListEntitiesIterator::ListEntitiesIterator(const WebServer *ws, AsyncEventSource *es) : web_server_(ws), events_(es) {} @@ -157,6 +156,5 @@ bool ListEntitiesIterator::on_update(update::UpdateEntity *obj) { } #endif -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server/list_entities.h b/esphome/components/web_server/list_entities.h index 5d9049b082..56fd91a8c6 100644 --- a/esphome/components/web_server/list_entities.h +++ b/esphome/components/web_server/list_entities.h @@ -4,13 +4,13 @@ #ifdef USE_WEBSERVER #include "esphome/core/component.h" #include "esphome/core/component_iterator.h" -namespace esphome { +namespace esphome::web_server_idf { #ifdef USE_ESP32 -namespace web_server_idf { class AsyncEventSource; -} #endif -namespace web_server { +} // namespace esphome::web_server_idf + +namespace esphome::web_server { #if !defined(USE_ESP32) && defined(USE_ARDUINO) class DeferredUpdateEventSource; @@ -99,6 +99,5 @@ class ListEntitiesIterator : public ComponentIterator { #endif }; -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server/ota/ota_web_server.cpp b/esphome/components/web_server/ota/ota_web_server.cpp index f612aa056c..572c351245 100644 --- a/esphome/components/web_server/ota/ota_web_server.cpp +++ b/esphome/components/web_server/ota/ota_web_server.cpp @@ -23,8 +23,7 @@ using PlatformString = std::string; using PlatformString = String; #endif -namespace esphome { -namespace web_server { +namespace esphome::web_server { static const char *const TAG = "web_server.ota"; @@ -236,7 +235,6 @@ void WebServerOTAComponent::setup() { void WebServerOTAComponent::dump_config() { ESP_LOGCONFIG(TAG, "Web Server OTA"); } -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif // USE_WEBSERVER_OTA diff --git a/esphome/components/web_server/ota/ota_web_server.h b/esphome/components/web_server/ota/ota_web_server.h index a7170c0e34..53ff99899c 100644 --- a/esphome/components/web_server/ota/ota_web_server.h +++ b/esphome/components/web_server/ota/ota_web_server.h @@ -7,8 +7,7 @@ #include "esphome/components/web_server_base/web_server_base.h" #include "esphome/core/component.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { class WebServerOTAComponent : public ota::OTAComponent { public: @@ -20,7 +19,6 @@ class WebServerOTAComponent : public ota::OTAComponent { friend class OTARequestHandler; }; -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif // USE_WEBSERVER_OTA diff --git a/esphome/components/web_server/server_index_v2.h b/esphome/components/web_server/server_index_v2.h index e675d81552..b2d204c9e7 100644 --- a/esphome/components/web_server/server_index_v2.h +++ b/esphome/components/web_server/server_index_v2.h @@ -6,8 +6,7 @@ #include "esphome/core/hal.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { const uint8_t INDEX_GZ[] PROGMEM = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcd, 0x7d, 0xdb, 0x72, 0xdb, 0xc6, 0xb6, 0xe0, 0xf3, @@ -644,8 +643,7 @@ const uint8_t INDEX_GZ[] PROGMEM = { 0x2b, 0x4d, 0x17, 0xb8, 0x87, 0x4c, 0xe9, 0x50, 0x19, 0x14, 0xba, 0x92, 0xde, 0x0a, 0xea, 0x97, 0xce, 0xad, 0x80, 0x4f, 0xc7, 0xf5, 0xfe, 0x1f, 0xe7, 0xe0, 0x1c, 0x12, 0xcf, 0x89, 0x00, 0x00}; -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif #endif diff --git a/esphome/components/web_server/server_index_v3.h b/esphome/components/web_server/server_index_v3.h index 39518197a3..8a8ced9153 100644 --- a/esphome/components/web_server/server_index_v3.h +++ b/esphome/components/web_server/server_index_v3.h @@ -6,8 +6,7 @@ #include "esphome/core/hal.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { const uint8_t INDEX_GZ[] PROGMEM = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcc, 0xbd, 0xeb, 0x7a, 0x1b, 0xb7, 0xb2, 0x20, 0xfa, @@ -4048,8 +4047,7 @@ const uint8_t INDEX_GZ[] PROGMEM = { 0x3b, 0x6c, 0x78, 0x02, 0xa6, 0xdc, 0xb4, 0xe8, 0xee, 0x6a, 0xc5, 0x97, 0x94, 0x7e, 0xd1, 0x9b, 0x83, 0x45, 0xb2, 0xf4, 0x87, 0xff, 0x07, 0x52, 0xaf, 0x09, 0x6c, 0x30, 0x6a, 0x03, 0x00}; -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif #endif diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 8a1ed49408..f613d6bc36 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -36,8 +36,7 @@ #endif #endif -namespace esphome { -namespace web_server { +namespace esphome::web_server { static const char *const TAG = "web_server"; @@ -2105,6 +2104,5 @@ void WebServer::add_sorting_group(uint64_t group_id, const std::string &group_na } #endif -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 0078146284..b9e852c745 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -33,8 +33,7 @@ extern const uint8_t ESPHOME_WEBSERVER_JS_INCLUDE[] PROGMEM; extern const size_t ESPHOME_WEBSERVER_JS_INCLUDE_SIZE; #endif -namespace esphome { -namespace web_server { +namespace esphome::web_server { /// Internal helper struct that is used to parse incoming URLs struct UrlMatch { @@ -616,6 +615,5 @@ class WebServer : public Controller, #endif }; -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server/web_server_v1.cpp b/esphome/components/web_server/web_server_v1.cpp index cbc25b9dec..e27306ad78 100644 --- a/esphome/components/web_server/web_server_v1.cpp +++ b/esphome/components/web_server/web_server_v1.cpp @@ -3,8 +3,7 @@ #if USE_WEBSERVER_VERSION == 1 -namespace esphome { -namespace web_server { +namespace esphome::web_server { void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action, const std::function &action_func = nullptr) { @@ -215,6 +214,5 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { request->send(stream); } -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif From bdc087148a541241277ce13c489763c8064efa6e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Dec 2025 12:52:41 -1000 Subject: [PATCH 0789/1145] [wifi_info] Reduce heap allocations in text sensor formatting (#12660) --- esphome/components/network/ip_address.h | 9 ++++ .../wifi_info/wifi_info_text_sensor.cpp | 45 +++++++++++++------ esphome/core/helpers.h | 24 ++++++++++ 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index 3d8b062d0b..27cc212a47 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -40,6 +40,9 @@ using ip4_addr_t = in_addr; namespace esphome { namespace network { +/// Buffer size for IP address string (IPv6 max: 39 chars + null) +static constexpr size_t IP_ADDRESS_BUFFER_SIZE = 40; + struct IPAddress { public: #ifdef USE_HOST @@ -50,6 +53,10 @@ struct IPAddress { IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); } IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; } std::string str() const { return str_lower_case(inet_ntoa(ip_addr_)); } + /// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes. + char *str_to(char *buf) const { + return const_cast(inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE)); + } #else IPAddress() { ip_addr_set_zero(&ip_addr_); } IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) { @@ -128,6 +135,8 @@ struct IPAddress { bool is_ip6() const { return IP_IS_V6(&ip_addr_); } bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); } std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); } + /// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes. + char *str_to(char *buf) const { return ipaddr_ntoa_r(&ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE); } bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); } bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); } IPAddress &operator+=(uint8_t increase) { diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index 56cf49028c..eae0f87b40 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -46,8 +46,13 @@ void DNSAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "DNS Address", this void DNSAddressWifiInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, const network::IPAddress &dns2) { - std::string dns_results = dns1.str() + " " + dns2.str(); - this->publish_state(dns_results); + // IP_ADDRESS_BUFFER_SIZE (40) = max IP (39) + null; space reuses first null's slot + char buf[network::IP_ADDRESS_BUFFER_SIZE * 2]; + dns1.str_to(buf); + size_t len1 = strlen(buf); + buf[len1] = ' '; + dns2.str_to(buf + len1 + 1); + this->publish_state(buf); } /********************** @@ -58,22 +63,36 @@ void ScanResultsWiFiInfo::setup() { wifi::global_wifi_component->add_scan_result void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "Scan Results", this); } +// Format: "SSID: -XXdB\n" - caller must ensure ssid_len + 9 bytes available in buffer +static char *format_scan_entry(char *buf, const char *ssid, size_t ssid_len, int8_t rssi) { + memcpy(buf, ssid, ssid_len); + buf += ssid_len; + *buf++ = ':'; + *buf++ = ' '; + buf = int8_to_str(buf, rssi); + *buf++ = 'd'; + *buf++ = 'B'; + *buf++ = '\n'; + return buf; +} + void ScanResultsWiFiInfo::on_wifi_scan_results(const wifi::wifi_scan_vector_t &results) { - std::string scan_results; + char buf[MAX_STATE_LENGTH + 1]; + char *ptr = buf; + const char *end = buf + MAX_STATE_LENGTH; + for (const auto &scan : results) { if (scan.get_is_hidden()) continue; + const std::string &ssid = scan.get_ssid(); + // Max space: ssid + ": " (2) + "-128" (4) + "dB\n" (3) = ssid + 9 + if (ptr + ssid.size() + 9 > end) + break; + ptr = format_scan_entry(ptr, ssid.c_str(), ssid.size(), scan.get_rssi()); + } - scan_results += scan.get_ssid(); - scan_results += ": "; - scan_results += esphome::to_string(scan.get_rssi()); - scan_results += "dB\n"; - } - // There's a limit of 255 characters per state; longer states just don't get sent so we truncate it - if (scan_results.length() > MAX_STATE_LENGTH) { - scan_results.resize(MAX_STATE_LENGTH); - } - this->publish_state(scan_results); + *ptr = '\0'; + this->publish_state(buf); } /*************** diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 769041160c..48a2313e2c 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -684,6 +684,30 @@ inline char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + /// This always uses uppercase (A-F) for pretty/human-readable output inline char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; } +/// Write int8 value to buffer without modulo operations. +/// Buffer must have at least 4 bytes free. Returns pointer past last char written. +inline char *int8_to_str(char *buf, int8_t val) { + int32_t v = val; + if (v < 0) { + *buf++ = '-'; + v = -v; + } + if (v >= 100) { + *buf++ = '1'; // int8 max is 128, so hundreds digit is always 1 + v -= 100; + // Must write tens digit (even if 0) after hundreds + int32_t tens = v / 10; + *buf++ = '0' + tens; + v -= tens * 10; + } else if (v >= 10) { + int32_t tens = v / 10; + *buf++ = '0' + tens; + v -= tens * 10; + } + *buf++ = '0' + v; + return buf; +} + /// Format MAC address as XX:XX:XX:XX:XX:XX (uppercase) inline void format_mac_addr_upper(const uint8_t *mac, char *output) { for (size_t i = 0; i < 6; i++) { From 34067f8b15b7159e8391c9905a301e110d610e94 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Dec 2025 08:29:15 -1000 Subject: [PATCH 0790/1145] [esp8266] Native OTA backend to reduce Arduino dependencies (#12675) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/esp8266/__init__.py | 8 +- .../esp8266/exclude_updater.py.script | 21 ++ .../components/esphome/ota/ota_esphome.cpp | 2 +- .../http_request/ota/ota_http_request.cpp | 2 +- esphome/components/ota/__init__.py | 2 +- .../ota/ota_backend_arduino_esp8266.cpp | 89 ----- .../ota/ota_backend_arduino_esp8266.h | 33 -- .../components/ota/ota_backend_esp8266.cpp | 356 ++++++++++++++++++ esphome/components/ota/ota_backend_esp8266.h | 58 +++ .../web_server/ota/ota_web_server.cpp | 7 +- 10 files changed, 446 insertions(+), 132 deletions(-) create mode 100644 esphome/components/esp8266/exclude_updater.py.script delete mode 100644 esphome/components/ota/ota_backend_arduino_esp8266.cpp delete mode 100644 esphome/components/ota/ota_backend_arduino_esp8266.h create mode 100644 esphome/components/ota/ota_backend_esp8266.cpp create mode 100644 esphome/components/ota/ota_backend_esp8266.h diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index a74f9ee8ce..c4969a79b2 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -191,7 +191,8 @@ async def to_code(config): cg.add_define(ThreadModel.SINGLE) cg.add_platformio_option( - "extra_scripts", ["pre:testing_mode.py", "post:post_build.py"] + "extra_scripts", + ["pre:testing_mode.py", "pre:exclude_updater.py", "post:post_build.py"], ) conf = config[CONF_FRAMEWORK] @@ -278,3 +279,8 @@ def copy_files(): testing_mode_file, CORE.relative_build_path("testing_mode.py"), ) + exclude_updater_file = dir / "exclude_updater.py.script" + copy_file_if_changed( + exclude_updater_file, + CORE.relative_build_path("exclude_updater.py"), + ) diff --git a/esphome/components/esp8266/exclude_updater.py.script b/esphome/components/esp8266/exclude_updater.py.script new file mode 100644 index 0000000000..69331e3b03 --- /dev/null +++ b/esphome/components/esp8266/exclude_updater.py.script @@ -0,0 +1,21 @@ +# pylint: disable=E0602 +Import("env") # noqa + +import os + +# Filter out Updater.cpp from the Arduino core build +# This saves 228 bytes of .bss by not instantiating the global Update object +# ESPHome uses its own native OTA backend instead + + +def filter_updater_from_core(env, node): + """Filter callback to exclude Updater.cpp from framework build.""" + path = node.get_path() + if path.endswith("Updater.cpp"): + print(f"ESPHome: Excluding {os.path.basename(path)} from build (using native OTA backend)") + return None + return node + + +# Apply the filter to framework sources +env.AddBuildMiddleware(filter_updater_from_core, "**/cores/esp8266/Updater.cpp") diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index b589a6119f..f9984e1425 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -10,7 +10,7 @@ #endif #include "esphome/components/network/util.h" #include "esphome/components/ota/ota_backend.h" -#include "esphome/components/ota/ota_backend_arduino_esp8266.h" +#include "esphome/components/ota/ota_backend_esp8266.h" #include "esphome/components/ota/ota_backend_arduino_libretiny.h" #include "esphome/components/ota/ota_backend_arduino_rp2040.h" #include "esphome/components/ota/ota_backend_esp_idf.h" diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 058579752e..2cd7489e38 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -7,7 +7,7 @@ #include "esphome/components/md5/md5.h" #include "esphome/components/watchdog/watchdog.h" #include "esphome/components/ota/ota_backend.h" -#include "esphome/components/ota/ota_backend_arduino_esp8266.h" +#include "esphome/components/ota/ota_backend_esp8266.h" #include "esphome/components/ota/ota_backend_arduino_rp2040.h" #include "esphome/components/ota/ota_backend_esp_idf.h" diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 8bed9cee42..a514a7482f 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -148,7 +148,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( PlatformFramework.ESP32_ARDUINO, PlatformFramework.ESP32_IDF, }, - "ota_backend_arduino_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "ota_backend_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, "ota_backend_arduino_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO}, "ota_backend_arduino_libretiny.cpp": { PlatformFramework.BK72XX_ARDUINO, diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.cpp b/esphome/components/ota/ota_backend_arduino_esp8266.cpp deleted file mode 100644 index 375c4e7200..0000000000 --- a/esphome/components/ota/ota_backend_arduino_esp8266.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#ifdef USE_ARDUINO -#ifdef USE_ESP8266 -#include "ota_backend_arduino_esp8266.h" -#include "ota_backend.h" - -#include "esphome/components/esp8266/preferences.h" -#include "esphome/core/defines.h" -#include "esphome/core/log.h" - -#include - -namespace esphome { -namespace ota { - -static const char *const TAG = "ota.arduino_esp8266"; - -std::unique_ptr make_ota_backend() { return make_unique(); } - -OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) { - // Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space - if (image_size == 0) { - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - image_size = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; - } - bool ret = Update.begin(image_size, U_FLASH); - if (ret) { - esp8266::preferences_prevent_write(true); - return OTA_RESPONSE_OK; - } - - uint8_t error = Update.getError(); - if (error == UPDATE_ERROR_BOOTSTRAP) - return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; - if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) - return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; - if (error == UPDATE_ERROR_FLASH_CONFIG) - return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; - if (error == UPDATE_ERROR_SPACE) - return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; - - ESP_LOGE(TAG, "Begin error: %d", error); - - return OTA_RESPONSE_ERROR_UNKNOWN; -} - -void ArduinoESP8266OTABackend::set_update_md5(const char *md5) { - Update.setMD5(md5); - this->md5_set_ = true; -} - -OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) { - size_t written = Update.write(data, len); - if (written == len) { - return OTA_RESPONSE_OK; - } - - uint8_t error = Update.getError(); - ESP_LOGE(TAG, "Write error: %d", error); - - return OTA_RESPONSE_ERROR_WRITING_FLASH; -} - -OTAResponseTypes ArduinoESP8266OTABackend::end() { - // Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5 - // This matches the behavior of the old web_server OTA implementation - bool success = Update.end(!this->md5_set_); - - // On ESP8266, Update.end() might return false even with error code 0 - // Check the actual error code to determine success - uint8_t error = Update.getError(); - - if (success || error == UPDATE_ERROR_OK) { - return OTA_RESPONSE_OK; - } - - ESP_LOGE(TAG, "End error: %d", error); - return OTA_RESPONSE_ERROR_UPDATE_END; -} - -void ArduinoESP8266OTABackend::abort() { - Update.end(); - esp8266::preferences_prevent_write(false); -} - -} // namespace ota -} // namespace esphome - -#endif -#endif diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h deleted file mode 100644 index e1b9015cc7..0000000000 --- a/esphome/components/ota/ota_backend_arduino_esp8266.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#ifdef USE_ARDUINO -#ifdef USE_ESP8266 -#include "ota_backend.h" - -#include "esphome/core/defines.h" -#include "esphome/core/macros.h" - -namespace esphome { -namespace ota { - -class ArduinoESP8266OTABackend : public OTABackend { - public: - OTAResponseTypes begin(size_t image_size) override; - void set_update_md5(const char *md5) override; - OTAResponseTypes write(uint8_t *data, size_t len) override; - OTAResponseTypes end() override; - void abort() override; -#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) - bool supports_compression() override { return true; } -#else - bool supports_compression() override { return false; } -#endif - - private: - bool md5_set_{false}; -}; - -} // namespace ota -} // namespace esphome - -#endif -#endif diff --git a/esphome/components/ota/ota_backend_esp8266.cpp b/esphome/components/ota/ota_backend_esp8266.cpp new file mode 100644 index 0000000000..4b84708cd9 --- /dev/null +++ b/esphome/components/ota/ota_backend_esp8266.cpp @@ -0,0 +1,356 @@ +#ifdef USE_ESP8266 +#include "ota_backend_esp8266.h" +#include "ota_backend.h" + +#include "esphome/components/esp8266/preferences.h" +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include +#include + +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +// Note: FLASH_SECTOR_SIZE (0x1000) is already defined in spi_flash_geometry.h + +// Flash header offsets +static constexpr uint8_t FLASH_MODE_OFFSET = 2; + +// Firmware magic bytes +static constexpr uint8_t FIRMWARE_MAGIC = 0xE9; +static constexpr uint8_t GZIP_MAGIC_1 = 0x1F; +static constexpr uint8_t GZIP_MAGIC_2 = 0x8B; + +// ESP8266 flash memory base address (memory-mapped flash starts here) +static constexpr uint32_t FLASH_BASE_ADDRESS = 0x40200000; + +// Boot mode extraction from GPI register (bits 16-19 contain boot mode) +static constexpr int BOOT_MODE_SHIFT = 16; +static constexpr int BOOT_MODE_MASK = 0xf; + +// Boot mode indicating UART download mode (OTA not possible) +static constexpr int BOOT_MODE_UART_DOWNLOAD = 1; + +// Minimum buffer size when memory is constrained +static constexpr size_t MIN_BUFFER_SIZE = 256; + +namespace esphome::ota { + +static const char *const TAG = "ota.esp8266"; + +std::unique_ptr make_ota_backend() { return make_unique(); } + +OTAResponseTypes ESP8266OTABackend::begin(size_t image_size) { + // Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space + if (image_size == 0) { + // Round down to sector boundary: subtract one sector, then mask to sector alignment + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + image_size = (ESP.getFreeSketchSpace() - FLASH_SECTOR_SIZE) & ~(FLASH_SECTOR_SIZE - 1); + } + + // Check boot mode - if boot mode is UART download mode, + // we will not be able to reset into normal mode once update is done + int boot_mode = (GPI >> BOOT_MODE_SHIFT) & BOOT_MODE_MASK; + if (boot_mode == BOOT_MODE_UART_DOWNLOAD) { + return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; + } + + // Check flash configuration - real size must be >= configured size + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + if (!ESP.checkFlashConfig(false)) { + return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; + } + + // Get current sketch size + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + uint32_t sketch_size = ESP.getSketchSize(); + + // Size of current sketch rounded to sector boundary + uint32_t current_sketch_size = (sketch_size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); + + // Size of update rounded to sector boundary + uint32_t rounded_size = (image_size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); + + // End of available space for sketch and update (start of filesystem) + uint32_t update_end_address = FS_start - FLASH_BASE_ADDRESS; + + // Calculate start address for the update (write from end backwards) + this->start_address_ = (update_end_address > rounded_size) ? (update_end_address - rounded_size) : 0; + + // Check if there's enough space for both current sketch and update + if (this->start_address_ < current_sketch_size) { + return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; + } + + // Allocate buffer for sector writes (use smaller buffer if memory constrained) + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + this->buffer_size_ = (ESP.getFreeHeap() > 2 * FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : MIN_BUFFER_SIZE; + + // ESP8266's umm_malloc guarantees 4-byte aligned allocations, which is required + // for spi_flash_write(). This is the same pattern used by Arduino's Updater class. + this->buffer_ = make_unique(this->buffer_size_); + if (!this->buffer_) { + return OTA_RESPONSE_ERROR_UNKNOWN; + } + + this->current_address_ = this->start_address_; + this->image_size_ = image_size; + this->buffer_len_ = 0; + this->md5_set_ = false; + + // Disable WiFi sleep during update + wifi_set_sleep_type(NONE_SLEEP_T); + + // Prevent preference writes during update + esp8266::preferences_prevent_write(true); + + // Initialize MD5 computation + this->md5_.init(); + + ESP_LOGD(TAG, "OTA begin: start=0x%08" PRIX32 ", size=%zu", this->start_address_, image_size); + + return OTA_RESPONSE_OK; +} + +void ESP8266OTABackend::set_update_md5(const char *md5) { + // Parse hex string to bytes + if (parse_hex(md5, this->expected_md5_, 16)) { + this->md5_set_ = true; + } +} + +OTAResponseTypes ESP8266OTABackend::write(uint8_t *data, size_t len) { + if (!this->buffer_) { + return OTA_RESPONSE_ERROR_UNKNOWN; + } + + size_t written = 0; + while (written < len) { + // Calculate how much we can buffer + size_t to_buffer = std::min(len - written, this->buffer_size_ - this->buffer_len_); + memcpy(this->buffer_.get() + this->buffer_len_, data + written, to_buffer); + this->buffer_len_ += to_buffer; + written += to_buffer; + + // If buffer is full, write to flash + if (this->buffer_len_ == this->buffer_size_ && !this->write_buffer_()) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + } + + return OTA_RESPONSE_OK; +} + +bool ESP8266OTABackend::erase_sector_if_needed_() { + if ((this->current_address_ % FLASH_SECTOR_SIZE) != 0) { + return true; // Not at sector boundary + } + + App.feed_wdt(); + if (spi_flash_erase_sector(this->current_address_ / FLASH_SECTOR_SIZE) != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Flash erase failed at 0x%08" PRIX32, this->current_address_); + return false; + } + return true; +} + +bool ESP8266OTABackend::flash_write_() { + App.feed_wdt(); + if (spi_flash_write(this->current_address_, reinterpret_cast(this->buffer_.get()), this->buffer_len_) != + SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Flash write failed at 0x%08" PRIX32, this->current_address_); + return false; + } + return true; +} + +bool ESP8266OTABackend::write_buffer_() { + if (this->buffer_len_ == 0) { + return true; + } + + if (!this->erase_sector_if_needed_()) { + return false; + } + + // Patch flash mode in first sector if needed + // This is analogous to what esptool.py does when it receives a --flash_mode argument + bool is_first_sector = (this->current_address_ == this->start_address_); + uint8_t original_flash_mode = 0; + bool patched_flash_mode = false; + + // Only patch if we have enough bytes to access flash mode offset and it's not GZIP + if (is_first_sector && this->buffer_len_ > FLASH_MODE_OFFSET && this->buffer_[0] != GZIP_MAGIC_1) { + // Not GZIP compressed - check and patch flash mode + uint8_t current_flash_mode = this->get_flash_chip_mode_(); + uint8_t buffer_flash_mode = this->buffer_[FLASH_MODE_OFFSET]; + + if (buffer_flash_mode != current_flash_mode) { + original_flash_mode = buffer_flash_mode; + this->buffer_[FLASH_MODE_OFFSET] = current_flash_mode; + patched_flash_mode = true; + } + } + + if (!this->flash_write_()) { + return false; + } + + // Restore original flash mode for MD5 calculation + if (patched_flash_mode) { + this->buffer_[FLASH_MODE_OFFSET] = original_flash_mode; + } + + // Update MD5 with original (unpatched) data + this->md5_.add(this->buffer_.get(), this->buffer_len_); + + this->current_address_ += this->buffer_len_; + this->buffer_len_ = 0; + + return true; +} + +bool ESP8266OTABackend::write_buffer_final_() { + // Similar to write_buffer_(), but without flash mode patching or MD5 update (for final padded write) + if (this->buffer_len_ == 0) { + return true; + } + + if (!this->erase_sector_if_needed_() || !this->flash_write_()) { + return false; + } + + this->current_address_ += this->buffer_len_; + this->buffer_len_ = 0; + + return true; +} + +OTAResponseTypes ESP8266OTABackend::end() { + // Write any remaining buffered data + if (this->buffer_len_ > 0) { + // Add actual data to MD5 before padding + this->md5_.add(this->buffer_.get(), this->buffer_len_); + + // Pad to 4-byte alignment for flash write + while (this->buffer_len_ % 4 != 0) { + this->buffer_[this->buffer_len_++] = 0xFF; + } + if (!this->write_buffer_final_()) { + this->abort(); + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + } + + // Calculate actual bytes written + size_t actual_size = this->current_address_ - this->start_address_; + + // Check if any data was written + if (actual_size == 0) { + ESP_LOGE(TAG, "No data written"); + this->abort(); + return OTA_RESPONSE_ERROR_UPDATE_END; + } + + // Verify MD5 if set (strict mode), otherwise use lenient mode + // In lenient mode (no MD5), we accept whatever was written + if (this->md5_set_) { + this->md5_.calculate(); + if (!this->md5_.equals_bytes(this->expected_md5_)) { + ESP_LOGE(TAG, "MD5 mismatch"); + this->abort(); + return OTA_RESPONSE_ERROR_MD5_MISMATCH; + } + } else { + // Lenient mode: adjust size to what was actually written + // This matches Arduino's Update.end(true) behavior + this->image_size_ = actual_size; + } + + // Verify firmware header + if (!this->verify_end_()) { + this->abort(); + return OTA_RESPONSE_ERROR_UPDATE_END; + } + + // Write eboot command to copy firmware on next boot + eboot_command ebcmd; + ebcmd.action = ACTION_COPY_RAW; + ebcmd.args[0] = this->start_address_; + ebcmd.args[1] = 0x00000; // Destination: start of flash + ebcmd.args[2] = this->image_size_; + eboot_command_write(&ebcmd); + + ESP_LOGI(TAG, "OTA update staged: 0x%08" PRIX32 " -> 0x00000, size=%zu", this->start_address_, this->image_size_); + + // Clean up + this->buffer_.reset(); + esp8266::preferences_prevent_write(false); + + return OTA_RESPONSE_OK; +} + +void ESP8266OTABackend::abort() { + this->buffer_.reset(); + this->buffer_len_ = 0; + this->image_size_ = 0; + esp8266::preferences_prevent_write(false); +} + +bool ESP8266OTABackend::verify_end_() { + uint32_t buf; + if (spi_flash_read(this->start_address_, &buf, 4) != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Failed to read firmware header"); + return false; + } + + uint8_t *bytes = reinterpret_cast(&buf); + + // Check for GZIP (compressed firmware) + if (bytes[0] == GZIP_MAGIC_1 && bytes[1] == GZIP_MAGIC_2) { + // GZIP compressed - can't verify further + return true; + } + + // Check firmware magic byte + if (bytes[0] != FIRMWARE_MAGIC) { + ESP_LOGE(TAG, "Invalid firmware magic: 0x%02X (expected 0x%02X)", bytes[0], FIRMWARE_MAGIC); + return false; + } + +#if !FLASH_MAP_SUPPORT + // Check if new firmware's flash size fits (only when auto-detection is disabled) + // With FLASH_MAP_SUPPORT (modern cores), flash size is auto-detected from chip + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + uint32_t bin_flash_size = ESP.magicFlashChipSize((bytes[3] & 0xf0) >> 4); + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + if (bin_flash_size > ESP.getFlashChipRealSize()) { + ESP_LOGE(TAG, "Firmware flash size (%" PRIu32 ") exceeds chip size (%" PRIu32 ")", bin_flash_size, + ESP.getFlashChipRealSize()); + return false; + } +#endif + + return true; +} + +uint8_t ESP8266OTABackend::get_flash_chip_mode_() { + uint32_t data; + if (spi_flash_read(0x0000, &data, 4) != SPI_FLASH_RESULT_OK) { + return 0; // Default to QIO + } + return (reinterpret_cast(&data))[FLASH_MODE_OFFSET]; +} + +} // namespace esphome::ota +#endif // USE_ESP8266 diff --git a/esphome/components/ota/ota_backend_esp8266.h b/esphome/components/ota/ota_backend_esp8266.h new file mode 100644 index 0000000000..a9d6dd2ccc --- /dev/null +++ b/esphome/components/ota/ota_backend_esp8266.h @@ -0,0 +1,58 @@ +#pragma once +#ifdef USE_ESP8266 +#include "ota_backend.h" + +#include "esphome/components/md5/md5.h" +#include "esphome/core/defines.h" + +#include + +namespace esphome::ota { + +/// OTA backend for ESP8266 using native SDK functions. +/// This implementation bypasses the Arduino Updater library to save ~228 bytes of RAM +/// by not having a global Update object in .bss. +class ESP8266OTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + // Compression supported in all ESP8266 Arduino versions ESPHome supports (>= 2.7.0) + bool supports_compression() override { return true; } + + protected: + /// Erase flash sector if current address is at sector boundary + bool erase_sector_if_needed_(); + + /// Write buffer to flash (does not update address or clear buffer) + bool flash_write_(); + + /// Write buffered data to flash and update MD5 + bool write_buffer_(); + + /// Write buffered data to flash without MD5 update (for final padded write) + bool write_buffer_final_(); + + /// Verify the firmware header is valid + bool verify_end_(); + + /// Get current flash chip mode from flash header + uint8_t get_flash_chip_mode_(); + + std::unique_ptr buffer_; + size_t buffer_size_{0}; + size_t buffer_len_{0}; + + uint32_t start_address_{0}; + uint32_t current_address_{0}; + size_t image_size_{0}; + + md5::MD5Digest md5_{}; + uint8_t expected_md5_[16]; // Fixed-size buffer for 128-bit (16-byte) MD5 digest + bool md5_set_{false}; +}; + +} // namespace esphome::ota +#endif // USE_ESP8266 diff --git a/esphome/components/web_server/ota/ota_web_server.cpp b/esphome/components/web_server/ota/ota_web_server.cpp index 572c351245..b8bea40b84 100644 --- a/esphome/components/web_server/ota/ota_web_server.cpp +++ b/esphome/components/web_server/ota/ota_web_server.cpp @@ -10,9 +10,7 @@ #endif #ifdef USE_ARDUINO -#ifdef USE_ESP8266 -#include -#elif defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) #include #endif #endif // USE_ARDUINO @@ -120,9 +118,6 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf // Platform-specific pre-initialization #ifdef USE_ARDUINO -#ifdef USE_ESP8266 - Update.runAsync(true); -#endif #if defined(USE_ESP32) || defined(USE_LIBRETINY) if (Update.isRunning()) { Update.abort(); From e9f2d75aab3a8e31f82e82e30ac7d0bc811e617d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Dec 2025 08:34:45 -1000 Subject: [PATCH 0791/1145] [core] Add format_hex_to helper for zero-allocation hex formatting (#12670) --- esphome/components/one_wire/one_wire_bus.cpp | 6 ++++-- esphome/components/sx126x/sx126x.cpp | 4 +++- esphome/components/sx127x/sx127x.cpp | 4 +++- esphome/core/helpers.cpp | 13 +++++++++++++ esphome/core/helpers.h | 18 ++++++++++++++++++ 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/esphome/components/one_wire/one_wire_bus.cpp b/esphome/components/one_wire/one_wire_bus.cpp index c2542177cf..27b7d58a0f 100644 --- a/esphome/components/one_wire/one_wire_bus.cpp +++ b/esphome/components/one_wire/one_wire_bus.cpp @@ -49,7 +49,8 @@ void OneWireBus::search() { break; auto *address8 = reinterpret_cast(&address); if (crc8(address8, 7) != address8[7]) { - ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str()); + char hex_buf[17]; + ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex_to(hex_buf, address)); } else { this->devices_.push_back(address); } @@ -82,8 +83,9 @@ void OneWireBus::dump_devices_(const char *tag) { ESP_LOGW(tag, " Found no devices!"); } else { ESP_LOGCONFIG(tag, " Found devices:"); + char hex_buf[17]; // uint64_t = 16 hex chars + null for (auto &address : this->devices_) { - ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex(address).c_str(), LOG_STR_ARG(get_model_str(address & 0xff))); + ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex_to(hex_buf, address), LOG_STR_ARG(get_model_str(address & 0xff))); } } } diff --git a/esphome/components/sx126x/sx126x.cpp b/esphome/components/sx126x/sx126x.cpp index bb59f26b79..707d6f1fbf 100644 --- a/esphome/components/sx126x/sx126x.cpp +++ b/esphome/components/sx126x/sx126x.cpp @@ -527,7 +527,9 @@ void SX126x::dump_config() { this->spreading_factor_, cr, this->preamble_size_); } if (!this->sync_value_.empty()) { - ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", format_hex(this->sync_value_).c_str()); + char hex_buf[17]; // 8 bytes max = 16 hex chars + null + ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", + format_hex_to(hex_buf, this->sync_value_.data(), this->sync_value_.size())); } if (this->is_failed()) { ESP_LOGE(TAG, "Configuring SX126x failed"); diff --git a/esphome/components/sx127x/sx127x.cpp b/esphome/components/sx127x/sx127x.cpp index 8e6db5dc9e..3185574b1a 100644 --- a/esphome/components/sx127x/sx127x.cpp +++ b/esphome/components/sx127x/sx127x.cpp @@ -476,7 +476,9 @@ void SX127x::dump_config() { ESP_LOGCONFIG(TAG, " Payload Length: %" PRIu32, this->payload_length_); } if (!this->sync_value_.empty()) { - ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", format_hex(this->sync_value_).c_str()); + char hex_buf[17]; // 8 bytes max = 16 hex chars + null + ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", + format_hex_to(hex_buf, this->sync_value_.data(), this->sync_value_.size())); } if (this->preamble_size_ > 0 || this->preamble_detect_ > 0) { ESP_LOGCONFIG(TAG, diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index d7d32ea28f..5e361ecce2 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -297,6 +297,19 @@ std::string format_hex(const uint8_t *data, size_t length) { } std::string format_hex(const std::vector &data) { return format_hex(data.data(), data.size()); } +char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) { + size_t max_bytes = (buffer_size - 1) / 2; + if (length > max_bytes) { + length = max_bytes; + } + for (size_t i = 0; i < length; i++) { + buffer[2 * i] = format_hex_char(data[i] >> 4); + buffer[2 * i + 1] = format_hex_char(data[i] & 0x0F); + } + buffer[length * 2] = '\0'; + return buffer; +} + // Shared implementation for uint8_t and string hex formatting static std::string format_hex_pretty_uint8(const uint8_t *data, size_t length, char separator, bool show_length) { if (data == nullptr || length == 0) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 48a2313e2c..4319e32510 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -730,6 +730,24 @@ inline void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output) { output[12] = '\0'; } +/// Format byte array as lowercase hex to buffer (base implementation). +char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length); + +/// Format byte array as lowercase hex to buffer. Automatically deduces buffer size. +/// Truncates output if data exceeds buffer capacity. Returns pointer to buffer. +template inline char *format_hex_to(char (&buffer)[N], const uint8_t *data, size_t length) { + static_assert(N >= 3, "Buffer must hold at least one hex byte (3 chars)"); + return format_hex_to(buffer, N, data, length); +} + +/// Format an unsigned integer in lowercased hex to buffer, starting with the most significant byte. +template::value, int> = 0> +inline char *format_hex_to(char (&buffer)[N], T val) { + static_assert(N >= sizeof(T) * 2 + 1, "Buffer too small for type"); + val = convert_big_endian(val); + return format_hex_to(buffer, reinterpret_cast(&val), sizeof(T)); +} + /// Format the six-byte array \p mac into a MAC address. std::string format_mac_address_pretty(const uint8_t mac[6]); /// Format the byte array \p data of length \p len in lowercased hex. From a275f37135ae249196691d813b93abc5bcf60dd4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Dec 2025 08:35:16 -1000 Subject: [PATCH 0792/1145] [udp] Use stack buffer for listen address logging in dump_config (#12667) --- esphome/components/udp/udp_component.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/udp/udp_component.cpp b/esphome/components/udp/udp_component.cpp index 9105ced21e..daa6c52f98 100644 --- a/esphome/components/udp/udp_component.cpp +++ b/esphome/components/udp/udp_component.cpp @@ -130,7 +130,8 @@ void UDPComponent::dump_config() { for (const auto &address : this->addresses_) ESP_LOGCONFIG(TAG, " Address: %s", address.c_str()); if (this->listen_address_.has_value()) { - ESP_LOGCONFIG(TAG, " Listen address: %s", this->listen_address_.value().str().c_str()); + char addr_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGCONFIG(TAG, " Listen address: %s", this->listen_address_.value().str_to(addr_buf)); } ESP_LOGCONFIG(TAG, " Broadcasting: %s\n" From be0bf1e5b92a736d0b7bdc6ae95f60a68e92e2ad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Dec 2025 08:35:36 -1000 Subject: [PATCH 0793/1145] [lvgl] Fix lambdas in canvas actions called from outside LVGL context (#12671) --- esphome/components/lvgl/defines.py | 10 +++------- esphome/components/lvgl/lv_validation.py | 11 +++-------- esphome/components/lvgl/lvcode.py | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 1d528b2f73..91077a1ff4 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -5,7 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i """ import logging -from typing import TYPE_CHECKING, Any +from typing import Any from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_ITEMS @@ -96,13 +96,9 @@ class LValidator: return None if isinstance(value, Lambda): # Local import to avoid circular import - from .lvcode import CodeContext, LambdaContext + from .lvcode import get_lambda_context_args - if TYPE_CHECKING: - # CodeContext does not have get_automation_parameters - # so we need to assert the type here - assert isinstance(CodeContext.code_context, LambdaContext) - args = args or CodeContext.code_context.get_automation_parameters() + args = args or get_lambda_context_args() return cg.RawExpression( call_lambda( await cg.process_lambda(value, args, return_type=self.rtype) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index 9c1dd22085..947e44b131 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -1,5 +1,5 @@ import re -from typing import TYPE_CHECKING, Any +from typing import Any import esphome.codegen as cg from esphome.components import image @@ -404,14 +404,9 @@ class TextValidator(LValidator): self, value: Any, args: list[tuple[SafeExpType, str]] | None = None ) -> Expression: # Local import to avoid circular import at module level + from .lvcode import get_lambda_context_args - from .lvcode import CodeContext, LambdaContext - - if TYPE_CHECKING: - # CodeContext does not have get_automation_parameters - # so we need to assert the type here - assert isinstance(CodeContext.code_context, LambdaContext) - args = args or CodeContext.code_context.get_automation_parameters() + args = args or get_lambda_context_args() if isinstance(value, dict): if format_str := value.get(CONF_FORMAT): diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index e2c70642a8..b79d1e88dd 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -1,4 +1,5 @@ import abc +from typing import TYPE_CHECKING from esphome import codegen as cg from esphome.config import Config @@ -200,6 +201,21 @@ class LvContext(LambdaContext): return self.add(*args) +def get_lambda_context_args() -> list[tuple[SafeExpType, str]]: + """Get automation parameters from the current lambda context if available. + + When called from outside LVGL's context (e.g., from interval), + CodeContext.code_context will be None, so return empty args. + """ + if CodeContext.code_context is None: + return [] + if TYPE_CHECKING: + # CodeContext base class doesn't define get_automation_parameters(), + # but LambdaContext and LvContext (the concrete implementations) do. + assert isinstance(CodeContext.code_context, LambdaContext) + return CodeContext.code_context.get_automation_parameters() + + class LocalVariable(MockObj): """ Create a local variable and enclose the code using it within a block. From f243e609a51b96771269b37e206e4aaaec811930 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Dec 2025 08:35:58 -1000 Subject: [PATCH 0794/1145] [wifi] Use StringRef and std::span in WiFiConnectStateListener to avoid allocations (#12672) --- esphome/components/wifi/wifi_component.h | 4 +++- esphome/components/wifi/wifi_component_esp8266.cpp | 5 +++-- esphome/components/wifi/wifi_component_esp_idf.cpp | 5 +++-- esphome/components/wifi/wifi_component_libretiny.cpp | 5 +++-- esphome/components/wifi/wifi_component_pico_w.cpp | 7 +++++-- esphome/components/wifi_info/wifi_info_text_sensor.cpp | 6 +++--- esphome/components/wifi_info/wifi_info_text_sensor.h | 6 ++++-- esphome/components/wifi_signal/wifi_signal_sensor.h | 4 +++- 8 files changed, 27 insertions(+), 15 deletions(-) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 604efa8a7e..4f888292f1 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -6,7 +6,9 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" +#include #include #include @@ -274,7 +276,7 @@ class WiFiScanResultsListener { */ class WiFiConnectStateListener { public: - virtual void on_wifi_connect_state(const std::string &ssid, const bssid_t &bssid) = 0; + virtual void on_wifi_connect_state(StringRef ssid, std::span bssid) = 0; }; /** Listener interface for WiFi power save mode changes. diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 550b5579ff..598ae2d5b7 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -526,7 +526,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { s_sta_connected = true; #ifdef USE_WIFI_LISTENERS for (auto *listener : global_wifi_component->connect_state_listeners_) { - listener->on_wifi_connect_state(global_wifi_component->wifi_ssid(), global_wifi_component->wifi_bssid()); + listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid); } // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here #ifdef USE_WIFI_MANUAL_IP @@ -559,8 +559,9 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { s_sta_connected = false; s_sta_connecting = false; #ifdef USE_WIFI_LISTENERS + static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : global_wifi_component->connect_state_listeners_) { - listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID); } #endif break; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 212514af93..67314ae31f 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -737,7 +737,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { s_sta_connected = true; #ifdef USE_WIFI_LISTENERS for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); + listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid); } // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here #ifdef USE_WIFI_MANUAL_IP @@ -772,8 +772,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { s_sta_connecting = false; error_from_callback_ = true; #ifdef USE_WIFI_LISTENERS + static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID); } #endif diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 340537b228..2aa6fa3484 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -303,7 +303,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); #ifdef USE_WIFI_LISTENERS for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); + listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid); } // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here #ifdef USE_WIFI_MANUAL_IP @@ -357,8 +357,9 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ s_sta_connecting = false; #ifdef USE_WIFI_LISTENERS + static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID); } #endif break; diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 61709852ff..b755b8544f 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -256,8 +256,10 @@ void WiFiComponent::wifi_loop_() { s_sta_was_connected = true; ESP_LOGV(TAG, "Connected"); #ifdef USE_WIFI_LISTENERS + String ssid = WiFi.SSID(); + bssid_t bssid = this->wifi_bssid(); for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); + listener->on_wifi_connect_state(StringRef(ssid.c_str(), ssid.length()), bssid); } // For static IP configurations, notify IP listeners immediately as the IP is already configured #ifdef USE_WIFI_MANUAL_IP @@ -275,8 +277,9 @@ void WiFiComponent::wifi_loop_() { s_sta_had_ip = false; ESP_LOGV(TAG, "Disconnected"); #ifdef USE_WIFI_LISTENERS + static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID); } #endif } diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index eae0f87b40..0cca3e16ef 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -103,8 +103,8 @@ void SSIDWiFiInfo::setup() { wifi::global_wifi_component->add_connect_state_list void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); } -void SSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) { - this->publish_state(ssid); +void SSIDWiFiInfo::on_wifi_connect_state(StringRef ssid, std::span bssid) { + this->publish_state(ssid.str()); } /**************** @@ -115,7 +115,7 @@ void BSSIDWiFiInfo::setup() { wifi::global_wifi_component->add_connect_state_lis void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "BSSID", this); } -void BSSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) { +void BSSIDWiFiInfo::on_wifi_connect_state(StringRef ssid, std::span bssid) { char buf[18] = "unknown"; if (mac_address_is_valid(bssid.data())) { format_mac_addr_upper(bssid.data(), buf); diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index b2242372da..6beb1372f5 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -2,10 +2,12 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/wifi/wifi_component.h" #ifdef USE_WIFI #include +#include namespace esphome::wifi_info { @@ -52,7 +54,7 @@ class SSIDWiFiInfo final : public Component, public text_sensor::TextSensor, pub void dump_config() override; // WiFiConnectStateListener interface - void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override; + void on_wifi_connect_state(StringRef ssid, std::span bssid) override; }; class BSSIDWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiConnectStateListener { @@ -61,7 +63,7 @@ class BSSIDWiFiInfo final : public Component, public text_sensor::TextSensor, pu void dump_config() override; // WiFiConnectStateListener interface - void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override; + void on_wifi_connect_state(StringRef ssid, std::span bssid) override; }; class PowerSaveModeWiFiInfo final : public Component, diff --git a/esphome/components/wifi_signal/wifi_signal_sensor.h b/esphome/components/wifi_signal/wifi_signal_sensor.h index 9f581f1eb2..2e1f8cbb2b 100644 --- a/esphome/components/wifi_signal/wifi_signal_sensor.h +++ b/esphome/components/wifi_signal/wifi_signal_sensor.h @@ -2,9 +2,11 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/wifi/wifi_component.h" #ifdef USE_WIFI +#include namespace esphome::wifi_signal { #ifdef USE_WIFI_LISTENERS @@ -28,7 +30,7 @@ class WiFiSignalSensor : public sensor::Sensor, public PollingComponent { #ifdef USE_WIFI_LISTENERS // WiFiConnectStateListener interface - update RSSI immediately on connect - void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override { this->update(); } + void on_wifi_connect_state(StringRef ssid, std::span bssid) override { this->update(); } #endif }; From a6097f4a0f7fd8229d12fdd61bf627b08aad7aff Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Dec 2025 08:36:19 -1000 Subject: [PATCH 0795/1145] [wifi] Eliminate heap allocations in dump_config logging (#12664) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/wifi/wifi_component.cpp | 16 ++++++++++++---- esphome/components/wifi/wifi_component.h | 6 ++++++ .../components/wifi/wifi_component_esp8266.cpp | 12 ++++++++++++ .../components/wifi/wifi_component_esp_idf.cpp | 13 +++++++++++++ .../components/wifi/wifi_component_libretiny.cpp | 8 ++++++++ .../components/wifi/wifi_component_pico_w.cpp | 8 ++++++++ 6 files changed, 59 insertions(+), 4 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 5fa894d8f9..50c0938cf1 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -899,12 +899,20 @@ void WiFiComponent::print_connect_params_() { ESP_LOGCONFIG(TAG, " Disabled"); return; } + // Use stack buffers for IP address formatting to avoid heap allocations + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; for (auto &ip : wifi_sta_ip_addresses()) { if (ip.is_set()) { - ESP_LOGCONFIG(TAG, " IP Address: %s", ip.str().c_str()); + ESP_LOGCONFIG(TAG, " IP Address: %s", ip.str_to(ip_buf)); } } int8_t rssi = wifi_rssi(); + // Use stack buffers for SSID and all IP addresses to avoid heap allocations + char ssid_buf[SSID_BUFFER_SIZE]; + char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, " SSID: " LOG_SECRET("'%s'") "\n" " BSSID: " LOG_SECRET("%s") "\n" @@ -915,9 +923,9 @@ void WiFiComponent::print_connect_params_() { " Gateway: %s\n" " DNS1: %s\n" " DNS2: %s", - wifi_ssid().c_str(), bssid_s, App.get_name().c_str(), rssi, LOG_STR_ARG(get_signal_bars(rssi)), - get_wifi_channel(), wifi_subnet_mask_().str().c_str(), wifi_gateway_ip_().str().c_str(), - wifi_dns_ip_(0).str().c_str(), wifi_dns_ip_(1).str().c_str()); + wifi_ssid_to(ssid_buf), bssid_s, App.get_name().c_str(), rssi, LOG_STR_ARG(get_signal_bars(rssi)), + get_wifi_channel(), wifi_subnet_mask_().str_to(subnet_buf), wifi_gateway_ip_().str_to(gateway_buf), + wifi_dns_ip_(0).str_to(dns1_buf), wifi_dns_ip_(1).str_to(dns2_buf)); #ifdef ESPHOME_LOG_HAS_VERBOSE if (const WiFiAP *config = this->get_selected_sta_(); config && config->has_bssid()) { ESP_LOGV(TAG, " Priority: %d", this->get_sta_priority(config->get_bssid())); diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 4f888292f1..ff2bfe12a4 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -61,6 +61,9 @@ namespace esphome::wifi { /// Sentinel value for RSSI when WiFi is not connected static constexpr int8_t WIFI_RSSI_DISCONNECTED = -127; +/// Buffer size for SSID (IEEE 802.11 max 32 bytes + null terminator) +static constexpr size_t SSID_BUFFER_SIZE = 33; + struct SavedWifiSettings { char ssid[33]; char password[65]; @@ -408,6 +411,9 @@ class WiFiComponent : public Component { network::IPAddresses wifi_sta_ip_addresses(); std::string wifi_ssid(); + /// Write SSID to buffer without heap allocation. + /// Returns pointer to buffer, or empty string if not connected. + const char *wifi_ssid_to(std::span buffer); bssid_t wifi_bssid(); int8_t wifi_rssi(); diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 598ae2d5b7..1c744648bb 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -913,6 +913,18 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +const char *WiFiComponent::wifi_ssid_to(std::span buffer) { + struct station_config conf {}; + if (!wifi_station_get_config(&conf)) { + buffer[0] = '\0'; + return buffer.data(); + } + // conf.ssid is uint8[32], not null-terminated if full + size_t len = strnlen(reinterpret_cast(conf.ssid), sizeof(conf.ssid)); + memcpy(buffer.data(), conf.ssid, len); + buffer[len] = '\0'; + return buffer.data(); +} int8_t WiFiComponent::wifi_rssi() { if (WiFi.status() != WL_CONNECTED) return WIFI_RSSI_DISCONNECTED; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 67314ae31f..b26ac3d2e2 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -1086,6 +1086,19 @@ std::string WiFiComponent::wifi_ssid() { size_t len = strnlen(ssid_s, sizeof(info.ssid)); return {ssid_s, len}; } +const char *WiFiComponent::wifi_ssid_to(std::span buffer) { + wifi_ap_record_t info{}; + esp_err_t err = esp_wifi_sta_get_ap_info(&info); + if (err != ESP_OK) { + buffer[0] = '\0'; + return buffer.data(); + } + // info.ssid is uint8[33], but only 32 bytes are SSID data + size_t len = strnlen(reinterpret_cast(info.ssid), 32); + memcpy(buffer.data(), info.ssid, len); + buffer[len] = '\0'; + return buffer.data(); +} int8_t WiFiComponent::wifi_rssi() { wifi_ap_record_t info; esp_err_t err = esp_wifi_sta_get_ap_info(&info); diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 2aa6fa3484..9b8653d0db 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -554,6 +554,14 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +const char *WiFiComponent::wifi_ssid_to(std::span buffer) { + // TODO: Find direct LibreTiny API to avoid Arduino String allocation + String ssid = WiFi.SSID(); + size_t len = std::min(static_cast(ssid.length()), SSID_BUFFER_SIZE - 1); + memcpy(buffer.data(), ssid.c_str(), len); + buffer[len] = '\0'; + return buffer.data(); +} int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index b755b8544f..1aa737ff4a 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -214,6 +214,14 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +const char *WiFiComponent::wifi_ssid_to(std::span buffer) { + // TODO: Find direct CYW43 API to avoid Arduino String allocation + String ssid = WiFi.SSID(); + size_t len = std::min(static_cast(ssid.length()), SSID_BUFFER_SIZE - 1); + memcpy(buffer.data(), ssid.c_str(), len); + buffer[len] = '\0'; + return buffer.data(); +} int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } From 5e99dd14ae78a9c0a59f4595a91af945430c3ed3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Dec 2025 08:36:35 -1000 Subject: [PATCH 0796/1145] [ethernet] Eliminate heap allocations in dump_config logging (#12665) --- .../ethernet/ethernet_component.cpp | 28 +++++++++++++------ .../components/ethernet/ethernet_component.h | 2 ++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 793ebdec42..114000401f 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -644,6 +644,12 @@ void EthernetComponent::dump_connect_params_() { dns_ip2 = dns_getserver(1); } + // Use stack buffers for IP address formatting to avoid heap allocations + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, " IP Address: %s\n" " Hostname: '%s'\n" @@ -651,9 +657,9 @@ void EthernetComponent::dump_connect_params_() { " Gateway: %s\n" " DNS1: %s\n" " DNS2: %s", - network::IPAddress(&ip.ip).str().c_str(), App.get_name().c_str(), - network::IPAddress(&ip.netmask).str().c_str(), network::IPAddress(&ip.gw).str().c_str(), - network::IPAddress(dns_ip1).str().c_str(), network::IPAddress(dns_ip2).str().c_str()); + network::IPAddress(&ip.ip).str_to(ip_buf), App.get_name().c_str(), + network::IPAddress(&ip.netmask).str_to(subnet_buf), network::IPAddress(&ip.gw).str_to(gateway_buf), + network::IPAddress(dns_ip1).str_to(dns1_buf), network::IPAddress(dns_ip2).str_to(dns2_buf)); #if USE_NETWORK_IPV6 struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; @@ -665,12 +671,13 @@ void EthernetComponent::dump_connect_params_() { } #endif /* USE_NETWORK_IPV6 */ + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, " MAC Address: %s\n" " Is Full Duplex: %s\n" " Link Speed: %u", - this->get_eth_mac_address_pretty().c_str(), YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), - this->get_link_speed() == ETH_SPEED_100M ? 100 : 10); + this->get_eth_mac_address_pretty_into_buffer(mac_buf), + YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), this->get_link_speed() == ETH_SPEED_100M ? 100 : 10); } #ifdef USE_ETHERNET_SPI @@ -711,11 +718,16 @@ void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) { } std::string EthernetComponent::get_eth_mac_address_pretty() { + char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + return std::string(this->get_eth_mac_address_pretty_into_buffer(buf)); +} + +const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer( + std::span buf) { uint8_t mac[6]; get_eth_mac_address_raw(mac); - char buf[18]; - format_mac_addr_upper(mac, buf); - return std::string(buf); + format_mac_addr_upper(mac, buf.data()); + return buf.data(); } eth_duplex_t EthernetComponent::get_duplex_mode() { diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index bffed4dc4a..490a9d026e 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" #include "esphome/components/network/ip_address.h" #ifdef USE_ESP32 @@ -93,6 +94,7 @@ class EthernetComponent : public Component { void set_use_address(const char *use_address); void get_eth_mac_address_raw(uint8_t *mac); std::string get_eth_mac_address_pretty(); + const char *get_eth_mac_address_pretty_into_buffer(std::span buf); eth_duplex_t get_duplex_mode(); eth_speed_t get_link_speed(); bool powerdown(); From 45e61f100c0e45c957a081ad9e3fdade7a7f4041 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:59:55 -0500 Subject: [PATCH 0797/1145] [core] Replace USE_ESP_IDF with USE_ESP32 across components (#12673) Co-authored-by: Claude Co-authored-by: J. Nick Koston --- .clang-tidy.hash | 2 +- esphome/components/audio/audio_reader.cpp | 2 +- esphome/components/audio/audio_reader.h | 2 +- esphome/components/captive_portal/__init__.py | 9 ++++----- .../captive_portal/captive_portal.cpp | 5 ++--- .../captive_portal/captive_portal.h | 20 +++++++++---------- .../captive_portal/dns_server_esp32_idf.cpp | 4 ++-- .../captive_portal/dns_server_esp32_idf.h | 4 ++-- esphome/components/climate/climate.cpp | 2 +- esphome/components/debug/debug_esp32.cpp | 2 +- esphome/components/esp32_ble/ble.cpp | 18 +++-------------- esphome/components/esp_ldo/__init__.py | 2 +- .../components/micro_wake_word/__init__.py | 2 +- .../components/micro_wake_word/automation.h | 2 +- .../micro_wake_word/micro_wake_word.cpp | 4 ++-- .../micro_wake_word/micro_wake_word.h | 4 ++-- .../micro_wake_word/preprocessor_settings.h | 2 +- .../micro_wake_word/streaming_model.cpp | 2 +- .../micro_wake_word/streaming_model.h | 2 +- esphome/components/mipi_dsi/display.py | 2 +- esphome/components/mipi_rgb/display.py | 2 +- esphome/components/mipi_spi/display.py | 2 +- esphome/components/mixer/speaker/__init__.py | 4 +--- .../components/mqtt/mqtt_backend_esp32.cpp | 10 ---------- esphome/components/network/ip_address.h | 2 +- esphome/components/openthread/__init__.py | 1 - .../components/openthread/openthread_esp.cpp | 2 +- esphome/components/qspi_dbi/display.py | 2 +- esphome/components/qspi_dbi/qspi_dbi.cpp | 2 +- esphome/components/qspi_dbi/qspi_dbi.h | 2 +- esphome/components/rpi_dpi_rgb/display.py | 1 - .../speaker/media_player/__init__.py | 2 +- .../speaker/media_player/audio_pipeline.cpp | 2 +- .../speaker/media_player/audio_pipeline.h | 2 +- .../speaker/media_player/automation.h | 2 +- .../media_player/speaker_media_player.cpp | 2 +- .../media_player/speaker_media_player.h | 2 +- esphome/components/spi/__init__.py | 4 ++-- esphome/components/st7701s/display.py | 2 +- esphome/components/usb_host/__init__.py | 1 - .../voice_assistant/voice_assistant.cpp | 2 +- .../web_server/ota/ota_web_server.cpp | 4 ++-- esphome/config_validation.py | 11 +++++++++- esphome/core/defines.h | 8 ++------ esphome/core/log.cpp | 2 +- esphome/core/log.h | 7 ++----- platformio.ini | 1 + tests/component_tests/mipi_spi/test_init.py | 17 ---------------- 48 files changed, 74 insertions(+), 119 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 240b205158..e1f5e096c0 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -4268ab0b5150f79ab1c317e8f3834c8bb0b4c8122da4f6b1fd67c49d0f2098c9 +5ac05ac603766d76b86a05cdf6a43febcaae807fe9e2406d812c47d4b5fed91d diff --git a/esphome/components/audio/audio_reader.cpp b/esphome/components/audio/audio_reader.cpp index 6966c95db7..7794187a69 100644 --- a/esphome/components/audio/audio_reader.cpp +++ b/esphome/components/audio/audio_reader.cpp @@ -1,6 +1,6 @@ #include "audio_reader.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/defines.h" #include "esphome/core/hal.h" diff --git a/esphome/components/audio/audio_reader.h b/esphome/components/audio/audio_reader.h index 3fdc3c3ff2..0b73923e84 100644 --- a/esphome/components/audio/audio_reader.h +++ b/esphome/components/audio/audio_reader.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "audio.h" #include "audio_transfer_buffer.h" diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 763e2e4ec5..232b868e82 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -97,10 +97,6 @@ async def to_code(config): cg.add_define("USE_CAPTIVE_PORTAL") if CORE.using_arduino: - if CORE.is_esp32: - cg.add_library("ESP32 Async UDP", None) - cg.add_library("DNSServer", None) - cg.add_library("WiFi", None) if CORE.is_esp8266: cg.add_library("DNSServer", None) if CORE.is_libretiny: @@ -110,6 +106,9 @@ async def to_code(config): # Only compile the ESP-IDF DNS server when using ESP-IDF framework FILTER_SOURCE_FILES = filter_source_files_from_platform( { - "dns_server_esp32_idf.cpp": {PlatformFramework.ESP32_IDF}, + "dns_server_esp32_idf.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, } ) diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index e1f92d2d2b..749aa705df 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -69,12 +69,11 @@ void CaptivePortal::start() { network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); -#ifdef USE_ESP_IDF +#if defined(USE_ESP32) // Create DNS server instance for ESP-IDF this->dns_server_ = make_unique(); this->dns_server_->start(ip); -#endif -#ifdef USE_ARDUINO +#elif defined(USE_ARDUINO) this->dns_server_ = make_unique(); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); this->dns_server_->start(53, ESPHOME_F("*"), ip); diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index f48c286f0c..0c63a3670a 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -2,11 +2,10 @@ #include "esphome/core/defines.h" #ifdef USE_CAPTIVE_PORTAL #include -#ifdef USE_ARDUINO -#include -#endif -#ifdef USE_ESP_IDF +#if defined(USE_ESP32) #include "dns_server_esp32_idf.h" +#elif defined(USE_ARDUINO) +#include #endif #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -23,15 +22,14 @@ class CaptivePortal : public AsyncWebHandler, public Component { void setup() override; void dump_config() override; void loop() override { -#ifdef USE_ARDUINO - if (this->dns_server_ != nullptr) { - this->dns_server_->processNextRequest(); - } -#endif -#ifdef USE_ESP_IDF +#if defined(USE_ESP32) if (this->dns_server_ != nullptr) { this->dns_server_->process_next_request(); } +#elif defined(USE_ARDUINO) + if (this->dns_server_ != nullptr) { + this->dns_server_->processNextRequest(); + } #endif } float get_setup_priority() const override; @@ -64,7 +62,7 @@ class CaptivePortal : public AsyncWebHandler, public Component { web_server_base::WebServerBase *base_; bool initialized_{false}; bool active_{false}; -#if defined(USE_ARDUINO) || defined(USE_ESP_IDF) +#if defined(USE_ARDUINO) || defined(USE_ESP32) std::unique_ptr dns_server_{nullptr}; #endif }; diff --git a/esphome/components/captive_portal/dns_server_esp32_idf.cpp b/esphome/components/captive_portal/dns_server_esp32_idf.cpp index 740107400a..5188b2047f 100644 --- a/esphome/components/captive_portal/dns_server_esp32_idf.cpp +++ b/esphome/components/captive_portal/dns_server_esp32_idf.cpp @@ -1,5 +1,5 @@ #include "dns_server_esp32_idf.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/log.h" #include "esphome/core/hal.h" @@ -202,4 +202,4 @@ void DNSServer::process_next_request() { } // namespace esphome::captive_portal -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/captive_portal/dns_server_esp32_idf.h b/esphome/components/captive_portal/dns_server_esp32_idf.h index 13d9def8e3..3e0ac07373 100644 --- a/esphome/components/captive_portal/dns_server_esp32_idf.h +++ b/esphome/components/captive_portal/dns_server_esp32_idf.h @@ -1,5 +1,5 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include #include "esphome/core/helpers.h" @@ -24,4 +24,4 @@ class DNSServer { } // namespace esphome::captive_portal -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 229862ce01..2d35509493 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -369,7 +369,7 @@ optional Climate::restore_state_() { } void Climate::save_state_() { -#if (defined(USE_ESP_IDF) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \ +#if (defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \ !defined(CLANG_TIDY) #pragma GCC diagnostic ignored "-Wclass-memaccess" #define TEMP_IGNORE_MEMACCESS diff --git a/esphome/components/debug/debug_esp32.cpp b/esphome/components/debug/debug_esp32.cpp index 1c3dc3699b..25852b32a7 100644 --- a/esphome/components/debug/debug_esp32.cpp +++ b/esphome/components/debug/debug_esp32.cpp @@ -200,7 +200,7 @@ void DebugComponent::get_device_info_(std::string &device_info) { #ifdef USE_ARDUINO ESP_LOGD(TAG, "Framework: Arduino"); device_info += "Arduino"; -#elif defined(USE_ESP_IDF) +#elif defined(USE_ESP32) ESP_LOGD(TAG, "Framework: ESP-IDF"); device_info += "ESP-IDF"; #else diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 42f8ab8fd4..87b5e2b738 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -24,7 +24,9 @@ extern "C" { #include #ifdef USE_ARDUINO -#include +// Prevent Arduino from releasing BT memory at startup (esp32-hal-misc.c). +// Without this, esp_bt_controller_init() fails with ESP_ERR_INVALID_STATE. +extern "C" bool btInUse() { return true; } // NOLINT(readability-identifier-naming) #endif namespace esphome::esp32_ble { @@ -165,12 +167,6 @@ void ESP32BLE::advertising_init_() { bool ESP32BLE::ble_setup_() { esp_err_t err; #ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID -#ifdef USE_ARDUINO - if (!btStart()) { - ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); - return false; - } -#else if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { // start bt controller if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { @@ -195,7 +191,6 @@ bool ESP32BLE::ble_setup_() { return false; } } -#endif esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); #else @@ -334,12 +329,6 @@ bool ESP32BLE::ble_dismantle_() { } #ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID -#ifdef USE_ARDUINO - if (!btStop()) { - ESP_LOGE(TAG, "btStop failed: %d", esp_bt_controller_get_status()); - return false; - } -#else if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) { // stop bt controller if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) { @@ -363,7 +352,6 @@ bool ESP32BLE::ble_dismantle_() { return false; } } -#endif #else if (esp_hosted_bt_controller_disable() != ESP_OK) { ESP_LOGW(TAG, "esp_hosted_bt_controller_disable failed"); diff --git a/esphome/components/esp_ldo/__init__.py b/esphome/components/esp_ldo/__init__.py index 38e684c537..f136dd149b 100644 --- a/esphome/components/esp_ldo/__init__.py +++ b/esphome/components/esp_ldo/__init__.py @@ -31,7 +31,7 @@ CONFIG_SCHEMA = cv.All( } ) ), - cv.only_with_esp_idf, + cv.only_on_esp32, only_on_variant(supported=[VARIANT_ESP32P4]), ) diff --git a/esphome/components/micro_wake_word/__init__.py b/esphome/components/micro_wake_word/__init__.py index 0d478f749b..74696584da 100644 --- a/esphome/components/micro_wake_word/__init__.py +++ b/esphome/components/micro_wake_word/__init__.py @@ -368,7 +368,7 @@ CONFIG_SCHEMA = cv.All( ), } ).extend(cv.COMPONENT_SCHEMA), - cv.only_with_esp_idf, + cv.only_on_esp32, ) diff --git a/esphome/components/micro_wake_word/automation.h b/esphome/components/micro_wake_word/automation.h index e1795a7e64..218ce9e4bc 100644 --- a/esphome/components/micro_wake_word/automation.h +++ b/esphome/components/micro_wake_word/automation.h @@ -3,7 +3,7 @@ #include "micro_wake_word.h" #include "streaming_model.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 namespace esphome { namespace micro_wake_word { diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp index b8377ead38..d7e80efc84 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.cpp +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -1,6 +1,6 @@ #include "micro_wake_word.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/application.h" #include "esphome/core/hal.h" @@ -473,4 +473,4 @@ bool MicroWakeWord::update_model_probabilities_(const int8_t audio_features[PREP } // namespace micro_wake_word } // namespace esphome -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/micro_wake_word/micro_wake_word.h b/esphome/components/micro_wake_word/micro_wake_word.h index 84261eaa5b..b427e4dfcb 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.h +++ b/esphome/components/micro_wake_word/micro_wake_word.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "preprocessor_settings.h" #include "streaming_model.h" @@ -140,4 +140,4 @@ class MicroWakeWord : public Component } // namespace micro_wake_word } // namespace esphome -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/micro_wake_word/preprocessor_settings.h b/esphome/components/micro_wake_word/preprocessor_settings.h index 3de21de92e..c9d195b49b 100644 --- a/esphome/components/micro_wake_word/preprocessor_settings.h +++ b/esphome/components/micro_wake_word/preprocessor_settings.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include diff --git a/esphome/components/micro_wake_word/streaming_model.cpp b/esphome/components/micro_wake_word/streaming_model.cpp index 2b073cce56..47d2c70e13 100644 --- a/esphome/components/micro_wake_word/streaming_model.cpp +++ b/esphome/components/micro_wake_word/streaming_model.cpp @@ -1,6 +1,6 @@ #include "streaming_model.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/helpers.h" #include "esphome/core/log.h" diff --git a/esphome/components/micro_wake_word/streaming_model.h b/esphome/components/micro_wake_word/streaming_model.h index b7b22b9700..0811bfb19b 100644 --- a/esphome/components/micro_wake_word/streaming_model.h +++ b/esphome/components/micro_wake_word/streaming_model.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "preprocessor_settings.h" diff --git a/esphome/components/mipi_dsi/display.py b/esphome/components/mipi_dsi/display.py index 90c4cc082e..c288b33cd2 100644 --- a/esphome/components/mipi_dsi/display.py +++ b/esphome/components/mipi_dsi/display.py @@ -165,8 +165,8 @@ def model_schema(config): ) return cv.All( schema, + cv.only_on_esp32, only_on_variant(supported=[VARIANT_ESP32P4]), - cv.only_with_esp_idf, ) diff --git a/esphome/components/mipi_rgb/display.py b/esphome/components/mipi_rgb/display.py index 61dbeb8ed4..96e167b2e6 100644 --- a/esphome/components/mipi_rgb/display.py +++ b/esphome/components/mipi_rgb/display.py @@ -224,8 +224,8 @@ def _config_schema(config): schema = model_schema(config) return cv.All( schema, + cv.only_on_esp32, only_on_variant(supported=[VARIANT_ESP32S3]), - cv.only_with_esp_idf, )(config) diff --git a/esphome/components/mipi_spi/display.py b/esphome/components/mipi_spi/display.py index 50ea826eab..69bf133c68 100644 --- a/esphome/components/mipi_spi/display.py +++ b/esphome/components/mipi_spi/display.py @@ -224,7 +224,7 @@ def model_schema(config): } ) if bus_mode != TYPE_SINGLE: - return cv.All(schema, cv.only_with_esp_idf) + return cv.All(schema, cv.only_on_esp32) return schema diff --git a/esphome/components/mixer/speaker/__init__.py b/esphome/components/mixer/speaker/__init__.py index 46729f8eda..c4069851af 100644 --- a/esphome/components/mixer/speaker/__init__.py +++ b/esphome/components/mixer/speaker/__init__.py @@ -93,9 +93,7 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_NUM_CHANNELS): cv.int_range(min=1, max=2), cv.Optional(CONF_QUEUE_MODE, default=False): cv.boolean, - cv.SplitDefault(CONF_TASK_STACK_IN_PSRAM, esp32_idf=False): cv.All( - cv.boolean, cv.only_with_esp_idf - ), + cv.Optional(CONF_TASK_STACK_IN_PSRAM, default=False): cv.boolean, } ), cv.only_on([PLATFORM_ESP32]), diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index dcc51ed60e..e3105f4860 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -232,16 +232,6 @@ void MQTTBackendESP32::esphome_mqtt_task(void *params) { this_mqtt->mqtt_event_pool_.release(elem); } } - - // Clean up any remaining items in the queue - struct QueueElement *elem; - while ((elem = this_mqtt->mqtt_queue_.pop()) != nullptr) { - this_mqtt->mqtt_event_pool_.release(elem); - } - - // Note: EventPool destructor will clean up the pool itself - // Task will delete itself - vTaskDelete(nullptr); } bool MQTTBackendESP32::enqueue_(MqttQueueTypeT type, const char *topic, int qos, bool retain, const char *payload, diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index 27cc212a47..b719d1a70e 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -8,7 +8,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/macros.h" -#if defined(USE_ESP_IDF) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0) #include #endif diff --git a/esphome/components/openthread/__init__.py b/esphome/components/openthread/__init__.py index 050e45cdc9..26c05a0a86 100644 --- a/esphome/components/openthread/__init__.py +++ b/esphome/components/openthread/__init__.py @@ -152,7 +152,6 @@ CONFIG_SCHEMA = cv.All( } ).extend(_CONNECTION_SCHEMA), cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV), - cv.only_with_esp_idf, only_on_variant(supported=[VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32H2]), _validate, _require_vfs_select, diff --git a/esphome/components/openthread/openthread_esp.cpp b/esphome/components/openthread/openthread_esp.cpp index b47e4b884a..1f18e51496 100644 --- a/esphome/components/openthread/openthread_esp.cpp +++ b/esphome/components/openthread/openthread_esp.cpp @@ -1,5 +1,5 @@ #include "esphome/core/defines.h" -#if defined(USE_OPENTHREAD) && defined(USE_ESP_IDF) +#if defined(USE_OPENTHREAD) && defined(USE_ESP32) #include #include "openthread.h" diff --git a/esphome/components/qspi_dbi/display.py b/esphome/components/qspi_dbi/display.py index 74d837a794..e4440c9b81 100644 --- a/esphome/components/qspi_dbi/display.py +++ b/esphome/components/qspi_dbi/display.py @@ -154,7 +154,7 @@ CONFIG_SCHEMA = cv.All( upper=True, key=CONF_MODEL, ), - cv.only_with_esp_idf, + cv.only_on_esp32, ) diff --git a/esphome/components/qspi_dbi/qspi_dbi.cpp b/esphome/components/qspi_dbi/qspi_dbi.cpp index 6c95bb7cf2..24b9a0ce0a 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.cpp +++ b/esphome/components/qspi_dbi/qspi_dbi.cpp @@ -1,4 +1,4 @@ -#if defined(USE_ESP_IDF) && defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32) && defined(USE_ESP32_VARIANT_ESP32S3) #include "qspi_dbi.h" #include "esphome/core/log.h" diff --git a/esphome/components/qspi_dbi/qspi_dbi.h b/esphome/components/qspi_dbi/qspi_dbi.h index f35f0e519c..3eee9bec47 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.h +++ b/esphome/components/qspi_dbi/qspi_dbi.h @@ -3,7 +3,7 @@ // #pragma once -#if defined(USE_ESP_IDF) && defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32) && defined(USE_ESP32_VARIANT_ESP32S3) #include "esphome/components/spi/spi.h" #include "esphome/components/display/display.h" #include "esphome/components/display/display_buffer.h" diff --git a/esphome/components/rpi_dpi_rgb/display.py b/esphome/components/rpi_dpi_rgb/display.py index 8e9da43a74..e92eee7c0c 100644 --- a/esphome/components/rpi_dpi_rgb/display.py +++ b/esphome/components/rpi_dpi_rgb/display.py @@ -122,7 +122,6 @@ CONFIG_SCHEMA = cv.All( ) ), only_on_variant(supported=[VARIANT_ESP32S3]), - cv.only_with_esp_idf, ) diff --git a/esphome/components/speaker/media_player/__init__.py b/esphome/components/speaker/media_player/__init__.py index 4ca57f2c4a..370b4576a7 100644 --- a/esphome/components/speaker/media_player/__init__.py +++ b/esphome/components/speaker/media_player/__init__.py @@ -315,7 +315,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ON_VOLUME): automation.validate_automation(single=True), } ), - cv.only_with_esp_idf, + cv.only_on_esp32, _validate_repeated_speaker, _request_high_performance_networking, ) diff --git a/esphome/components/speaker/media_player/audio_pipeline.cpp b/esphome/components/speaker/media_player/audio_pipeline.cpp index dc8572ae43..8be37d740a 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.cpp +++ b/esphome/components/speaker/media_player/audio_pipeline.cpp @@ -1,6 +1,6 @@ #include "audio_pipeline.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/defines.h" #include "esphome/core/hal.h" diff --git a/esphome/components/speaker/media_player/audio_pipeline.h b/esphome/components/speaker/media_player/audio_pipeline.h index 98f43fda6e..6fffde6c20 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.h +++ b/esphome/components/speaker/media_player/audio_pipeline.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/components/audio/audio.h" #include "esphome/components/audio/audio_reader.h" diff --git a/esphome/components/speaker/media_player/automation.h b/esphome/components/speaker/media_player/automation.h index fdf3db07f9..6270da7bd4 100644 --- a/esphome/components/speaker/media_player/automation.h +++ b/esphome/components/speaker/media_player/automation.h @@ -2,7 +2,7 @@ #include "speaker_media_player.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/components/audio/audio.h" #include "esphome/core/automation.h" diff --git a/esphome/components/speaker/media_player/speaker_media_player.cpp b/esphome/components/speaker/media_player/speaker_media_player.cpp index 5722aab195..9a3a47bac8 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.cpp +++ b/esphome/components/speaker/media_player/speaker_media_player.cpp @@ -1,6 +1,6 @@ #include "speaker_media_player.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/log.h" diff --git a/esphome/components/speaker/media_player/speaker_media_player.h b/esphome/components/speaker/media_player/speaker_media_player.h index f1c564b63d..065926d0cf 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.h +++ b/esphome/components/speaker/media_player/speaker_media_player.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "audio_pipeline.h" diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index ad279dcf1a..045cdd09d3 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -311,7 +311,7 @@ def spi_mode_schema(mode): if mode == TYPE_SINGLE: return SPI_SINGLE_SCHEMA pin_count = 4 if mode == TYPE_QUAD else 8 - onlys = [cv.only_on([PLATFORM_ESP32]), cv.only_with_esp_idf] + onlys = [cv.only_on([PLATFORM_ESP32])] if pin_count == 8: onlys.append( only_on_variant( @@ -399,7 +399,7 @@ def spi_device_schema( cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum( SPI_MODE_OPTIONS, upper=True ), - cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_with_esp_idf), + cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_on_esp32), } if cs_pin_required: schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema diff --git a/esphome/components/st7701s/display.py b/esphome/components/st7701s/display.py index 6e4bff6431..3078158d25 100644 --- a/esphome/components/st7701s/display.py +++ b/esphome/components/st7701s/display.py @@ -161,8 +161,8 @@ CONFIG_SCHEMA = cv.All( } ).extend(spi.spi_device_schema(cs_pin_required=False, default_data_rate=1e6)) ), + cv.only_on_esp32, only_on_variant(supported=[VARIANT_ESP32S3]), - cv.only_with_esp_idf, ) FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( diff --git a/esphome/components/usb_host/__init__.py b/esphome/components/usb_host/__init__.py index cccabcf646..9e058ee20b 100644 --- a/esphome/components/usb_host/__init__.py +++ b/esphome/components/usb_host/__init__.py @@ -53,7 +53,6 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DEVICES): cv.ensure_list(usb_device_schema()), } ), - cv.only_with_esp_idf, only_on_variant(supported=[VARIANT_ESP32S2, VARIANT_ESP32S3, VARIANT_ESP32P4]), ) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 551f0370f2..b946e3b38a 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -947,7 +947,7 @@ void VoiceAssistant::on_set_configuration(const std::vector &active } // Enable only active wake words - for (auto ww_id : active_wake_words) { + for (const auto &ww_id : active_wake_words) { for (auto &model : this->micro_wake_word_->get_wake_words()) { if (model->get_id() == ww_id) { model->enable(); diff --git a/esphome/components/web_server/ota/ota_web_server.cpp b/esphome/components/web_server/ota/ota_web_server.cpp index b8bea40b84..3793f01eb5 100644 --- a/esphome/components/web_server/ota/ota_web_server.cpp +++ b/esphome/components/web_server/ota/ota_web_server.cpp @@ -10,7 +10,7 @@ #endif #ifdef USE_ARDUINO -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_LIBRETINY) #include #endif #endif // USE_ARDUINO @@ -118,7 +118,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf // Platform-specific pre-initialization #ifdef USE_ARDUINO -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_LIBRETINY) if (Update.isRunning()) { Update.abort(); } diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 08fffa6cec..d085206ee8 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -697,7 +697,16 @@ only_on_esp32 = only_on(PLATFORM_ESP32) only_on_esp8266 = only_on(PLATFORM_ESP8266) only_on_rp2040 = only_on(PLATFORM_RP2040) only_with_arduino = only_with_framework(Framework.ARDUINO) -only_with_esp_idf = only_with_framework(Framework.ESP_IDF) + + +def only_with_esp_idf(obj): + """Deprecated: use only_on_esp32 instead.""" + _LOGGER.warning( + "cv.only_with_esp_idf was deprecated in 2026.1, will change behavior in 2026.6. " + "ESP32 Arduino builds on top of ESP-IDF, so ESP-IDF features are available in both frameworks. " + "Use cv.only_on_esp32 and/or cv.only_with_arduino instead." + ) + return only_with_framework(Framework.ESP_IDF)(obj) # Adapted from: diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 11c5062140..a269f40479 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -164,13 +164,9 @@ #define USE_I2S_LEGACY #endif -// IDF-specific feature flags -#ifdef USE_ESP_IDF -#define USE_MQTT_IDF_ENQUEUE -#endif - // ESP32-specific feature flags #ifdef USE_ESP32 +#define USE_MQTT_IDF_ENQUEUE #define USE_ESPHOME_TASK_LOG_BUFFER #define USE_OTA_ROLLBACK @@ -231,7 +227,7 @@ #define USE_ETHERNET_MANUAL_IP #endif -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #define USE_MICRO_WAKE_WORD #define USE_MICRO_WAKE_WORD_VAD #if defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) diff --git a/esphome/core/log.cpp b/esphome/core/log.cpp index 909319dd28..8338efbb33 100644 --- a/esphome/core/log.cpp +++ b/esphome/core/log.cpp @@ -46,7 +46,7 @@ void HOT esp_log_vprintf_(int level, const char *tag, int line, const __FlashStr } #endif -#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) +#ifdef USE_ESP32 int HOT esp_idf_log_vprintf_(const char *format, va_list args) { // NOLINT #ifdef USE_LOGGER auto *log = logger::global_logger; diff --git a/esphome/core/log.h b/esphome/core/log.h index cade6a74c1..a2c4b35c6e 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -14,13 +14,10 @@ #endif // Include ESP-IDF/Arduino based logging methods here so they don't undefine ours later -#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) +#if defined(USE_ESP32) #include #include #endif -#ifdef USE_ESP32_FRAMEWORK_ARDUINO -#include -#endif #ifdef USE_LIBRETINY #include #endif @@ -66,7 +63,7 @@ void esp_log_vprintf_(int level, const char *tag, int line, const char *format, #ifdef USE_STORE_LOG_STR_IN_FLASH void esp_log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, va_list args); #endif -#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) +#if defined(USE_ESP32) int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #endif diff --git a/platformio.ini b/platformio.ini index a27fb1f537..e38e1a5f3c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -156,6 +156,7 @@ lib_deps = esphome/ESP32-audioI2S@2.3.0 ; i2s_audio droscy/esp_wireguard@0.4.2 ; wireguard esphome/esp-audio-libs@2.0.1 ; audio + kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word build_flags = ${common:arduino.build_flags} diff --git a/tests/component_tests/mipi_spi/test_init.py b/tests/component_tests/mipi_spi/test_init.py index 0c7dea2286..f752c41d8c 100644 --- a/tests/component_tests/mipi_spi/test_init.py +++ b/tests/component_tests/mipi_spi/test_init.py @@ -227,23 +227,6 @@ def test_esp32s3_specific_errors( run_schema_validation(config) -def test_framework_specific_errors( - set_core_config: SetCoreConfigCallable, -) -> None: - """Test framework-specific configuration errors""" - - set_core_config( - PlatformFramework.ESP32_ARDUINO, - platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32}, - ) - - with pytest.raises( - cv.Invalid, - match=r"This feature is only available with framework\(s\) esp-idf", - ): - run_schema_validation({"model": "wt32-sc01-plus"}) - - def test_custom_model_with_all_options( set_core_config: SetCoreConfigCallable, ) -> None: From eb050ff13e334ee5ca20453c3fa30124e8f521d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 28 Dec 2025 16:48:08 -1000 Subject: [PATCH 0798/1145] Bump aioesphomeapi from 43.6.0 to 43.7.0 (#12699) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e741a70f48..30726006de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.6.0 +aioesphomeapi==43.7.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.17 # dashboard_import From a1e0121330f730723862f2a1dbd3d4b8d9de8877 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 28 Dec 2025 16:48:20 -1000 Subject: [PATCH 0799/1145] Bump bleak from 2.0.0 to 2.1.0 (#12700) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 30726006de..d40db03bc2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ pillow==11.3.0 cairosvg==2.8.2 freetype-py==2.5.1 jinja2==3.1.6 -bleak==2.0.0 +bleak==2.1.0 # esp-idf >= 5.0 requires this pyparsing >= 3.0 From 5cbef3ef95ddc280cd4e04b3b4a0141cd7b9c839 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 03:15:40 +0000 Subject: [PATCH 0800/1145] Bump aioesphomeapi from 43.7.0 to 43.8.0 (#12701) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d40db03bc2..efd143a44a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.7.0 +aioesphomeapi==43.8.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.17 # dashboard_import From d0673122a86415da4e3b87702a8d13aa628806fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 28 Dec 2025 18:15:06 -1000 Subject: [PATCH 0801/1145] Bump aioesphomeapi from 43.8.0 to 43.9.0 (#12702) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index efd143a44a..65ff74a4a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.8.0 +aioesphomeapi==43.9.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.17 # dashboard_import From 6a6c6b648f95c9b92ce57995677b1775e4885a38 Mon Sep 17 00:00:00 2001 From: Swaptor Date: Mon, 29 Dec 2025 17:32:32 +0100 Subject: [PATCH 0802/1145] [internal_temperature] Add ESP32-C5 support (#12713) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- .../internal_temperature.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/esphome/components/internal_temperature/internal_temperature.cpp b/esphome/components/internal_temperature/internal_temperature.cpp index 2ef8cf2649..34d7baf880 100644 --- a/esphome/components/internal_temperature/internal_temperature.cpp +++ b/esphome/components/internal_temperature/internal_temperature.cpp @@ -8,8 +8,9 @@ extern "C" { uint8_t temprature_sens_read(); } #elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \ - defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \ - defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + defined(USE_ESP32_VARIANT_ESP32C5) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || \ + defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) #include "driver/temperature_sensor.h" #endif // USE_ESP32_VARIANT #endif // USE_ESP32 @@ -27,9 +28,9 @@ namespace internal_temperature { static const char *const TAG = "internal_temperature"; #ifdef USE_ESP32 -#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ - defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \ + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \ + defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) static temperature_sensor_handle_t tsensNew = NULL; #endif // USE_ESP32_VARIANT #endif // USE_ESP32 @@ -44,8 +45,9 @@ void InternalTemperatureSensor::update() { temperature = (raw - 32) / 1.8f; success = (raw != 128); #elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \ - defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \ - defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + defined(USE_ESP32_VARIANT_ESP32C5) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || \ + defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) esp_err_t result = temperature_sensor_get_celsius(tsensNew, &temperature); success = (result == ESP_OK); if (!success) { @@ -81,9 +83,9 @@ void InternalTemperatureSensor::update() { void InternalTemperatureSensor::setup() { #ifdef USE_ESP32 -#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ - defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \ + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \ + defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80); esp_err_t result = temperature_sensor_install(&tsens_config, &tsensNew); From 890d531cea5e595319caec584ec8f9bf3da0ea2d Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 29 Dec 2025 11:35:54 -0500 Subject: [PATCH 0803/1145] [esp32] Bump to ESP-IDF 5.5.2, Arduino 3.3.5, platform 55.3.35 (#12681) Co-authored-by: Claude Co-authored-by: J. Nick Koston --- .clang-tidy.hash | 2 +- esphome/components/esp32/__init__.py | 20 +++++++++++--------- esphome/components/esp32/boards.py | 18 +++++++++++++++--- esphome/core/defines.h | 2 +- platformio.ini | 8 ++++---- 5 files changed, 32 insertions(+), 18 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index e1f5e096c0..a14b44ef96 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -5ac05ac603766d76b86a05cdf6a43febcaae807fe9e2406d812c47d4b5fed91d +94557f94be073390342833aff12ef8676a8b597db5fa770a5a1232e9425cb48f diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index dc442cfbd2..d307ae75c8 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -357,11 +357,12 @@ def _is_framework_url(source: str) -> bool: # The default/recommended arduino framework version # - https://github.com/espressif/arduino-esp32/releases ARDUINO_FRAMEWORK_VERSION_LOOKUP = { - "recommended": cv.Version(3, 3, 2), - "latest": cv.Version(3, 3, 4), - "dev": cv.Version(3, 3, 4), + "recommended": cv.Version(3, 3, 5), + "latest": cv.Version(3, 3, 5), + "dev": cv.Version(3, 3, 5), } ARDUINO_PLATFORM_VERSION_LOOKUP = { + cv.Version(3, 3, 5): cv.Version(55, 3, 35), cv.Version(3, 3, 4): cv.Version(55, 3, 31, "2"), cv.Version(3, 3, 3): cv.Version(55, 3, 31, "2"), cv.Version(3, 3, 2): cv.Version(55, 3, 31, "2"), @@ -378,11 +379,12 @@ ARDUINO_PLATFORM_VERSION_LOOKUP = { # The default/recommended esp-idf framework version # - https://github.com/espressif/esp-idf/releases ESP_IDF_FRAMEWORK_VERSION_LOOKUP = { - "recommended": cv.Version(5, 5, 1), - "latest": cv.Version(5, 5, 1), - "dev": cv.Version(5, 5, 1), + "recommended": cv.Version(5, 5, 2), + "latest": cv.Version(5, 5, 2), + "dev": cv.Version(5, 5, 2), } ESP_IDF_PLATFORM_VERSION_LOOKUP = { + cv.Version(5, 5, 2): cv.Version(55, 3, 35), cv.Version(5, 5, 1): cv.Version(55, 3, 31, "2"), cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"), cv.Version(5, 4, 3): cv.Version(55, 3, 32), @@ -399,9 +401,9 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = { # The platform-espressif32 version # - https://github.com/pioarduino/platform-espressif32/releases PLATFORM_VERSION_LOOKUP = { - "recommended": cv.Version(55, 3, 31, "2"), - "latest": cv.Version(55, 3, 31, "2"), - "dev": cv.Version(55, 3, 31, "2"), + "recommended": cv.Version(55, 3, 35), + "latest": cv.Version(55, 3, 35), + "dev": cv.Version(55, 3, 35), } diff --git a/esphome/components/esp32/boards.py b/esphome/components/esp32/boards.py index 514d674b55..8a7a9428db 100644 --- a/esphome/components/esp32/boards.py +++ b/esphome/components/esp32/boards.py @@ -1488,6 +1488,10 @@ BOARDS = { "name": "Arduino Nano ESP32", "variant": VARIANT_ESP32S3, }, + "arduino_nesso_n1": { + "name": "Arduino Nesso-N1", + "variant": VARIANT_ESP32C6, + }, "atd147_s3": { "name": "ArtronShop ATD1.47-S3", "variant": VARIANT_ESP32S3, @@ -1656,6 +1660,10 @@ BOARDS = { "name": "Espressif ESP32-C6-DevKitM-1", "variant": VARIANT_ESP32C6, }, + "esp32-c61-devkitc1-n8r2": { + "name": "Espressif ESP32-C61-DevKitC-1 N8R2 (8 MB Flash Quad, 2 MB PSRAM Quad)", + "variant": VARIANT_ESP32C61, + }, "esp32-devkitlipo": { "name": "OLIMEX ESP32-DevKit-LiPo", "variant": VARIANT_ESP32, @@ -1673,11 +1681,15 @@ BOARDS = { "variant": VARIANT_ESP32H2, }, "esp32-p4": { - "name": "Espressif ESP32-P4 generic", + "name": "Espressif ESP32-P4 ES (pre rev.300) generic", "variant": VARIANT_ESP32P4, }, "esp32-p4-evboard": { - "name": "Espressif ESP32-P4 Function EV Board", + "name": "Espressif ESP32-P4 Function EV Board (ES pre rev.300)", + "variant": VARIANT_ESP32P4, + }, + "esp32-p4_r3": { + "name": "Espressif ESP32-P4 rev.300 generic", "variant": VARIANT_ESP32P4, }, "esp32-pico-devkitm-2": { @@ -2093,7 +2105,7 @@ BOARDS = { "variant": VARIANT_ESP32, }, "m5stack-tab5-p4": { - "name": "M5STACK Tab5 esp32-p4 Board", + "name": "M5STACK Tab5 esp32-p4 Board (ES pre rev.300)", "variant": VARIANT_ESP32P4, }, "m5stack-timer-cam": { diff --git a/esphome/core/defines.h b/esphome/core/defines.h index a269f40479..be429a9784 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -221,7 +221,7 @@ #define USB_HOST_MAX_REQUESTS 16 #ifdef USE_ARDUINO -#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 3, 2) +#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 3, 5) #define USE_ETHERNET #define USE_ETHERNET_KSZ8081 #define USE_ETHERNET_MANUAL_IP diff --git a/platformio.ini b/platformio.ini index e38e1a5f3c..e58989c566 100644 --- a/platformio.ini +++ b/platformio.ini @@ -133,9 +133,9 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script ; This are common settings for the ESP32 (all variants) using Arduino. [common:esp32-arduino] extends = common:arduino -platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.31-1/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.35/platform-espressif32.zip platform_packages = - pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/3.3.2/esp32-3.3.2.zip + pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/3.3.5/esp32-3.3.5.zip framework = arduino, espidf ; Arduino as an ESP-IDF component lib_deps = @@ -170,9 +170,9 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script ; This are common settings for the ESP32 (all variants) using IDF. [common:esp32-idf] extends = common:idf -platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.31-1/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.35/platform-espressif32.zip platform_packages = - pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.5.1/esp-idf-v5.5.1.zip + pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.5.2/esp-idf-v5.5.2.tar.xz framework = espidf lib_deps = From 7e362cdafc260bf2a8ec1d987fa7ec1a73451290 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 29 Dec 2025 08:43:54 -1000 Subject: [PATCH 0804/1145] [ota] Use precision format specifier for auth logging (#12706) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../components/esphome/ota/ota_esphome.cpp | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index f9984e1425..98569c96cb 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -654,12 +654,7 @@ bool ESPHomeOTAComponent::handle_auth_send_() { this->auth_buf_[0] = this->auth_type_; hasher->get_hex(buf); -#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE - char log_buf[65]; // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too - memcpy(log_buf, buf, hex_size); - log_buf[hex_size] = '\0'; - ESP_LOGV(TAG, "Auth: Nonce is %s", log_buf); -#endif + ESP_LOGV(TAG, "Auth: Nonce is %.*s", hex_size, buf); } // Try to write auth_type + nonce @@ -739,23 +734,13 @@ bool ESPHomeOTAComponent::handle_auth_read_() { hasher->add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer) hasher->calculate(); + ESP_LOGV(TAG, "Auth: CNonce is %.*s", hex_size, cnonce); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE - char log_buf[65]; // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too - // Log CNonce - memcpy(log_buf, cnonce, hex_size); - log_buf[hex_size] = '\0'; - ESP_LOGV(TAG, "Auth: CNonce is %s", log_buf); - - // Log computed hash - hasher->get_hex(log_buf); - log_buf[hex_size] = '\0'; - ESP_LOGV(TAG, "Auth: Result is %s", log_buf); - - // Log received response - memcpy(log_buf, response, hex_size); - log_buf[hex_size] = '\0'; - ESP_LOGV(TAG, "Auth: Response is %s", log_buf); + char computed_hash[65]; // Buffer for hex-encoded hash (max expected length + null terminator) + hasher->get_hex(computed_hash); + ESP_LOGV(TAG, "Auth: Result is %.*s", hex_size, computed_hash); #endif + ESP_LOGV(TAG, "Auth: Response is %.*s", hex_size, response); // Compare response bool matches = hasher->equals_hex(response); From 97af01c5edf08347b314642cdc26a103dd5a8538 Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Mon, 29 Dec 2025 20:19:36 +0100 Subject: [PATCH 0805/1145] [usb_host] sort esp32 variants (#12720) --- esphome/components/usb_host/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/usb_host/__init__.py b/esphome/components/usb_host/__init__.py index 9e058ee20b..e4c11be489 100644 --- a/esphome/components/usb_host/__init__.py +++ b/esphome/components/usb_host/__init__.py @@ -53,7 +53,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DEVICES): cv.ensure_list(usb_device_schema()), } ), - only_on_variant(supported=[VARIANT_ESP32S2, VARIANT_ESP32S3, VARIANT_ESP32P4]), + only_on_variant(supported=[VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3]), ) From dd3beb58418bb4c08e4617fd130d06e35cc06d1d Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Mon, 29 Dec 2025 20:20:38 +0100 Subject: [PATCH 0806/1145] [tests] fix typo mipi tests (#12715) --- tests/component_tests/mipi_spi/test_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/component_tests/mipi_spi/test_init.py b/tests/component_tests/mipi_spi/test_init.py index f752c41d8c..bae39d3879 100644 --- a/tests/component_tests/mipi_spi/test_init.py +++ b/tests/component_tests/mipi_spi/test_init.py @@ -1,4 +1,4 @@ -"""Tests for mpip_spi configuration validation.""" +"""Tests for mipi_spi configuration validation.""" from collections.abc import Callable from pathlib import Path From 93e2a1bd1aee8fe9a8c8d4b841dedd074bdba243 Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Mon, 29 Dec 2025 20:21:07 +0100 Subject: [PATCH 0807/1145] [tests] improve mipi_spi variable naming (#12716) --- tests/component_tests/mipi_spi/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/component_tests/mipi_spi/conftest.py b/tests/component_tests/mipi_spi/conftest.py index c3070c7965..eddf0987d0 100644 --- a/tests/component_tests/mipi_spi/conftest.py +++ b/tests/component_tests/mipi_spi/conftest.py @@ -20,9 +20,9 @@ def choose_variant_with_pins() -> Generator[Callable[[list], None]]: """ def chooser(pins: list) -> None: - for v in VARIANTS: + for variant in VARIANTS: try: - CORE.data[KEY_ESP32][KEY_VARIANT] = v + CORE.data[KEY_ESP32][KEY_VARIANT] = variant for pin in pins: if pin is not None: pin = gpio_pin_schema( From 636cccc6a3413a5f3fe859c193d1642689114045 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 09:55:26 -1000 Subject: [PATCH 0808/1145] Bump aioesphomeapi from 43.9.0 to 43.9.1 (#12724) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 65ff74a4a6..a6262e0d10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.9.0 +aioesphomeapi==43.9.1 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.17 # dashboard_import From 2e7cdad532e55d27a25752c3fb2a416c841cc5bb Mon Sep 17 00:00:00 2001 From: hsand <14326961+hsand@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:58:38 +0100 Subject: [PATCH 0809/1145] [pvvx_mithermometer] fix displaying negative numbers (#12735) --- .../components/pvvx_mithermometer/display/pvvx_display.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.h b/esphome/components/pvvx_mithermometer/display/pvvx_display.h index 8637506bae..06837b94ab 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.h +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.h @@ -60,13 +60,13 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent { * Valid values are from -99.5 to 1999.5. Smaller values are displayed as Lo, higher as Hi. * It will printed as it fits in the screen. */ - void print_bignum(float bignum) { this->bignum_ = bignum * 10; } + void print_bignum(float bignum) { this->bignum_ = static_cast(bignum * 10); } /** * Print the small number * * Valid values are from -9 to 99. Smaller values are displayed as Lo, higher as Hi. */ - void print_smallnum(float smallnum) { this->smallnum_ = smallnum; } + void print_smallnum(float smallnum) { this->smallnum_ = static_cast(smallnum); } /** * Print a happy face * @@ -107,8 +107,8 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent { bool auto_clear_enabled_{true}; uint32_t disconnect_delay_ms_ = 5000; uint16_t validity_period_ = 300; - uint16_t bignum_ = 0; - uint16_t smallnum_ = 0; + int16_t bignum_ = 0; + int16_t smallnum_ = 0; uint8_t cfg_ = 0; void setcfgbit_(uint8_t bit, bool value); From 20e43398fa8849fddf1050948ae7852d4c4429f5 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 30 Dec 2025 12:21:30 +1000 Subject: [PATCH 0810/1145] [cli] Report program path on host (#12743) --- esphome/__main__.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 119ab957a3..3822af0330 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -789,7 +789,13 @@ def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None: exit_code = compile_program(args, config) if exit_code != 0: return exit_code - _LOGGER.info("Successfully compiled program.") + if CORE.is_host: + from esphome.platformio_api import get_idedata + + program_path = str(get_idedata(config).firmware_elf_path) + _LOGGER.info("Successfully compiled program to path '%s'", program_path) + else: + _LOGGER.info("Successfully compiled program.") return 0 @@ -839,10 +845,8 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None: if CORE.is_host: from esphome.platformio_api import get_idedata - idedata = get_idedata(config) - if idedata is None: - return 1 - program_path = idedata.raw["prog_path"] + program_path = str(get_idedata(config).firmware_elf_path) + _LOGGER.info("Running program from path '%s'", program_path) return run_external_process(program_path) # Get devices, resolving special identifiers like OTA From 63464a13c31127885fe75b554f201a929dcbbaa2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 29 Dec 2025 16:57:22 -1000 Subject: [PATCH 0811/1145] [core] Fix incremental build failures when adding components on ESP32-Arduino (#12745) --- esphome/writer.py | 9 ++-- tests/unit_tests/test_writer.py | 88 +++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/esphome/writer.py b/esphome/writer.py index 9ae40e417a..cb9c921693 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -103,14 +103,11 @@ def storage_should_clean(old: StorageJSON | None, new: StorageJSON) -> bool: def storage_should_update_cmake_cache(old: StorageJSON, new: StorageJSON) -> bool: - if ( + # ESP32 uses CMake for both Arduino and ESP-IDF frameworks + return ( old.loaded_integrations != new.loaded_integrations or old.loaded_platforms != new.loaded_platforms - ) and new.core_platform == PLATFORM_ESP32: - from esphome.components.esp32 import FRAMEWORK_ESP_IDF - - return new.framework == FRAMEWORK_ESP_IDF - return False + ) and new.core_platform == PLATFORM_ESP32 def update_storage_json() -> None: diff --git a/tests/unit_tests/test_writer.py b/tests/unit_tests/test_writer.py index f354d71bb7..ac05e0d31b 100644 --- a/tests/unit_tests/test_writer.py +++ b/tests/unit_tests/test_writer.py @@ -13,6 +13,13 @@ from unittest.mock import MagicMock, patch import pytest +from esphome.const import ( + PLATFORM_BK72XX, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RP2040, + PLATFORM_RTL87XX, +) from esphome.core import EsphomeError from esphome.storage_json import StorageJSON from esphome.writer import ( @@ -28,6 +35,7 @@ from esphome.writer import ( generate_build_info_data_h, get_build_info, storage_should_clean, + storage_should_update_cmake_cache, update_storage_json, write_cpp, write_gitignore, @@ -171,6 +179,86 @@ def test_storage_edge_case_from_empty_integrations( assert storage_should_clean(old, new) is False +# Tests for storage_should_update_cmake_cache + + +@pytest.mark.parametrize("framework", ["arduino", "esp-idf"]) +def test_storage_should_update_cmake_cache_when_integration_added_esp32( + create_storage: Callable[..., StorageJSON], + framework: str, +) -> None: + """Test cmake cache update triggered when integration added on ESP32.""" + old = create_storage( + loaded_integrations=["api", "wifi"], + core_platform=PLATFORM_ESP32, + framework=framework, + ) + new = create_storage( + loaded_integrations=["api", "wifi", "restart"], + core_platform=PLATFORM_ESP32, + framework=framework, + ) + assert storage_should_update_cmake_cache(old, new) is True + + +def test_storage_should_update_cmake_cache_when_platform_changed_esp32( + create_storage: Callable[..., StorageJSON], +) -> None: + """Test cmake cache update triggered when platforms change on ESP32.""" + old = create_storage( + loaded_integrations=["api", "wifi"], + loaded_platforms={"sensor"}, + core_platform=PLATFORM_ESP32, + framework="arduino", + ) + new = create_storage( + loaded_integrations=["api", "wifi"], + loaded_platforms={"sensor", "binary_sensor"}, + core_platform=PLATFORM_ESP32, + framework="arduino", + ) + assert storage_should_update_cmake_cache(old, new) is True + + +def test_storage_should_not_update_cmake_cache_when_nothing_changes( + create_storage: Callable[..., StorageJSON], +) -> None: + """Test cmake cache not updated when nothing changes.""" + old = create_storage( + loaded_integrations=["api", "wifi"], + core_platform=PLATFORM_ESP32, + framework="arduino", + ) + new = create_storage( + loaded_integrations=["api", "wifi"], + core_platform=PLATFORM_ESP32, + framework="arduino", + ) + assert storage_should_update_cmake_cache(old, new) is False + + +@pytest.mark.parametrize( + "core_platform", + [PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_BK72XX, PLATFORM_RTL87XX], +) +def test_storage_should_not_update_cmake_cache_for_non_esp32( + create_storage: Callable[..., StorageJSON], + core_platform: str, +) -> None: + """Test cmake cache not updated for non-ESP32 platforms.""" + old = create_storage( + loaded_integrations=["api", "wifi"], + core_platform=core_platform, + framework="arduino", + ) + new = create_storage( + loaded_integrations=["api", "wifi", "restart"], + core_platform=core_platform, + framework="arduino", + ) + assert storage_should_update_cmake_cache(old, new) is False + + @patch("esphome.writer.clean_build") @patch("esphome.writer.StorageJSON") @patch("esphome.writer.storage_path") From d86c05bfe65b7d2746356df435dcc998c60f2907 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 29 Dec 2025 17:23:41 -1000 Subject: [PATCH 0812/1145] [esp32] Breaking Change: Change default framework to ESP-IDF (#12746) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/esp32/__init__.py | 52 +++++++++++----------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index d307ae75c8..929ced6e3b 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -729,12 +729,14 @@ FRAMEWORK_SCHEMA = cv.Schema( ) +# Remove this class in 2026.7.0 class _FrameworkMigrationWarning: shown = False def _show_framework_migration_message(name: str, variant: str) -> None: - """Show a friendly message about framework migration when defaulting to Arduino.""" + """Show a message about the framework default change and how to switch back to Arduino.""" + # Remove this function in 2026.7.0 if _FrameworkMigrationWarning.shown: return _FrameworkMigrationWarning.shown = True @@ -744,41 +746,27 @@ def _show_framework_migration_message(name: str, variant: str) -> None: message = ( color( AnsiFore.BOLD_CYAN, - f"💡 IMPORTANT: {name} doesn't have a framework specified!", + f"💡 NOTICE: {name} does not have a framework specified.", ) + "\n\n" - + f"Currently, {variant} defaults to the Arduino framework.\n" - + color(AnsiFore.YELLOW, "This will change to ESP-IDF in ESPHome 2026.1.0.\n") + + f"Starting with ESPHome 2026.1.0, the default framework for {variant} is ESP-IDF.\n" + + "(We've been warning about this change since ESPHome 2025.8.0)\n" + "\n" - + "Note: Newer ESP32 variants (C6, H2, P4, etc.) already use ESP-IDF by default.\n" - + "\n" - + "Why change? ESP-IDF offers:\n" - + color(AnsiFore.GREEN, " ✨ Up to 40% smaller binaries\n") - + color(AnsiFore.GREEN, " 🚀 Better performance and optimization\n") + + "Why we made this change:\n" + + color(AnsiFore.GREEN, " ✨ Up to 40% smaller firmware binaries\n") + color(AnsiFore.GREEN, " ⚡ 2-3x faster compile times\n") - + color(AnsiFore.GREEN, " 📦 Custom-built firmware for your exact needs\n") - + color( - AnsiFore.GREEN, - " 🔧 Active development and testing by ESPHome developers\n", - ) + + color(AnsiFore.GREEN, " 🚀 Better performance and newer features\n") + + color(AnsiFore.GREEN, " 🔧 More actively maintained by ESPHome\n") + "\n" - + "Trade-offs:\n" - + color(AnsiFore.YELLOW, " 🔄 Some components need migration\n") + + "To continue using Arduino, add this to your YAML under 'esp32:':\n" + + color(AnsiFore.WHITE, " framework:\n") + + color(AnsiFore.WHITE, " type: arduino\n") + "\n" - + "What should I do?\n" - + color(AnsiFore.CYAN, " Option 1") - + ": Migrate to ESP-IDF (recommended)\n" - + " Add this to your YAML under 'esp32:':\n" - + color(AnsiFore.WHITE, " framework:\n") - + color(AnsiFore.WHITE, " type: esp-idf\n") + + "To silence this message with ESP-IDF, explicitly set:\n" + + color(AnsiFore.WHITE, " framework:\n") + + color(AnsiFore.WHITE, " type: esp-idf\n") + "\n" - + color(AnsiFore.CYAN, " Option 2") - + ": Keep using Arduino (still supported)\n" - + " Add this to your YAML under 'esp32:':\n" - + color(AnsiFore.WHITE, " framework:\n") - + color(AnsiFore.WHITE, " type: arduino\n") - + "\n" - + "Need help? Check out the migration guide:\n" + + "Migration guide: " + color( AnsiFore.BLUE, "https://esphome.io/guides/esp32_arduino_to_idf/", @@ -793,13 +781,13 @@ def _set_default_framework(config): config[CONF_FRAMEWORK] = FRAMEWORK_SCHEMA({}) if CONF_TYPE not in config[CONF_FRAMEWORK]: variant = config[CONF_VARIANT] + config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF + # Show migration message for variants that previously defaulted to Arduino + # Remove this message in 2026.7.0 if variant in ARDUINO_ALLOWED_VARIANTS: - config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO _show_framework_migration_message( config.get(CONF_NAME, "This device"), variant ) - else: - config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF return config From 4c16afeacb059bbff720caae2e4524ad053ecc8a Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:25:26 -0500 Subject: [PATCH 0813/1145] [esp32] Add IDF framework source for Arduino builds (#12731) Co-authored-by: Claude Opus 4.5 Co-authored-by: J. Nick Koston --- esphome/components/esp32/__init__.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 929ced6e3b..d8397a87cc 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -375,6 +375,23 @@ ARDUINO_PLATFORM_VERSION_LOOKUP = { cv.Version(3, 1, 1): cv.Version(53, 3, 11), cv.Version(3, 1, 0): cv.Version(53, 3, 10), } +# Maps Arduino framework versions to a compatible ESP-IDF version +# These versions correspond to pioarduino/esp-idf releases +# See: https://github.com/pioarduino/esp-idf/releases +ARDUINO_IDF_VERSION_LOOKUP = { + cv.Version(3, 3, 5): cv.Version(5, 5, 2), + cv.Version(3, 3, 4): cv.Version(5, 5, 1), + cv.Version(3, 3, 3): cv.Version(5, 5, 1), + cv.Version(3, 3, 2): cv.Version(5, 5, 1), + cv.Version(3, 3, 1): cv.Version(5, 5, 1), + cv.Version(3, 3, 0): cv.Version(5, 5, 0), + cv.Version(3, 2, 1): cv.Version(5, 4, 2), + cv.Version(3, 2, 0): cv.Version(5, 4, 2), + cv.Version(3, 1, 3): cv.Version(5, 3, 2), + cv.Version(3, 1, 2): cv.Version(5, 3, 2), + cv.Version(3, 1, 1): cv.Version(5, 3, 1), + cv.Version(3, 1, 0): cv.Version(5, 3, 0), +} # The default/recommended esp-idf framework version # - https://github.com/espressif/esp-idf/releases @@ -981,6 +998,13 @@ async def to_code(config): add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True) add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True) + # Add IDF framework source for Arduino builds to ensure it uses the same version as + # the ESP-IDF framework + if (idf_ver := ARDUINO_IDF_VERSION_LOOKUP.get(framework_ver)) is not None: + cg.add_platformio_option( + "platform_packages", [_format_framework_espidf_version(idf_ver, None)] + ) + # ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency if get_esp32_variant() == VARIANT_ESP32S2: cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=1") From 468bd7b04f4ff1f59ff0680064d1f253748109d0 Mon Sep 17 00:00:00 2001 From: bakroistvan Date: Tue, 30 Dec 2025 07:53:28 +0100 Subject: [PATCH 0814/1145] [dallas_temp] higher precision for logged temperature (#12695) --- esphome/components/dallas_temp/dallas_temp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/dallas_temp/dallas_temp.cpp b/esphome/components/dallas_temp/dallas_temp.cpp index a3969e081e..a1b684abbf 100644 --- a/esphome/components/dallas_temp/dallas_temp.cpp +++ b/esphome/components/dallas_temp/dallas_temp.cpp @@ -51,7 +51,7 @@ void DallasTemperatureSensor::update() { } float tempc = this->get_temp_c_(); - ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", this->get_name().c_str(), tempc); + ESP_LOGD(TAG, "'%s': Got Temperature=%f°C", this->get_name().c_str(), tempc); this->publish_state(tempc); }); } From a615b28ecf05345a908f85e54ec6f4155241a8b7 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Tue, 30 Dec 2025 07:22:36 +0000 Subject: [PATCH 0815/1145] [bme68x_bsec2] add `id:` to allow extending (#12649) --- esphome/components/bme68x_bsec2/sensor.py | 1 + tests/components/bme68x_bsec2_i2c/common.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/esphome/components/bme68x_bsec2/sensor.py b/esphome/components/bme68x_bsec2/sensor.py index c7dca437d7..45a9e54c1e 100644 --- a/esphome/components/bme68x_bsec2/sensor.py +++ b/esphome/components/bme68x_bsec2/sensor.py @@ -50,6 +50,7 @@ TYPES = [ CONFIG_SCHEMA = cv.Schema( { + cv.GenerateID(): cv.declare_id(cg.Component), cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, diff --git a/tests/components/bme68x_bsec2_i2c/common.yaml b/tests/components/bme68x_bsec2_i2c/common.yaml index bee964f433..a462bdaf7f 100644 --- a/tests/components/bme68x_bsec2_i2c/common.yaml +++ b/tests/components/bme68x_bsec2_i2c/common.yaml @@ -9,6 +9,7 @@ bme68x_bsec2_i2c: sensor: - platform: bme68x_bsec2 + id: bme_sensor temperature: name: BME68X Temperature pressure: From 339399eb7084fdecc09d8e65cebda54759bd08a0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Dec 2025 08:35:36 -1000 Subject: [PATCH 0816/1145] [lvgl] Fix lambdas in canvas actions called from outside LVGL context (#12671) --- esphome/components/lvgl/defines.py | 10 +++------- esphome/components/lvgl/lv_validation.py | 11 +++-------- esphome/components/lvgl/lvcode.py | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 1d528b2f73..91077a1ff4 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -5,7 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i """ import logging -from typing import TYPE_CHECKING, Any +from typing import Any from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_ITEMS @@ -96,13 +96,9 @@ class LValidator: return None if isinstance(value, Lambda): # Local import to avoid circular import - from .lvcode import CodeContext, LambdaContext + from .lvcode import get_lambda_context_args - if TYPE_CHECKING: - # CodeContext does not have get_automation_parameters - # so we need to assert the type here - assert isinstance(CodeContext.code_context, LambdaContext) - args = args or CodeContext.code_context.get_automation_parameters() + args = args or get_lambda_context_args() return cg.RawExpression( call_lambda( await cg.process_lambda(value, args, return_type=self.rtype) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index 9c1dd22085..947e44b131 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -1,5 +1,5 @@ import re -from typing import TYPE_CHECKING, Any +from typing import Any import esphome.codegen as cg from esphome.components import image @@ -404,14 +404,9 @@ class TextValidator(LValidator): self, value: Any, args: list[tuple[SafeExpType, str]] | None = None ) -> Expression: # Local import to avoid circular import at module level + from .lvcode import get_lambda_context_args - from .lvcode import CodeContext, LambdaContext - - if TYPE_CHECKING: - # CodeContext does not have get_automation_parameters - # so we need to assert the type here - assert isinstance(CodeContext.code_context, LambdaContext) - args = args or CodeContext.code_context.get_automation_parameters() + args = args or get_lambda_context_args() if isinstance(value, dict): if format_str := value.get(CONF_FORMAT): diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index c11597131f..2a1da2383c 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -1,4 +1,5 @@ import abc +from typing import TYPE_CHECKING from esphome import codegen as cg from esphome.config import Config @@ -200,6 +201,21 @@ class LvContext(LambdaContext): return self.add(*args) +def get_lambda_context_args() -> list[tuple[SafeExpType, str]]: + """Get automation parameters from the current lambda context if available. + + When called from outside LVGL's context (e.g., from interval), + CodeContext.code_context will be None, so return empty args. + """ + if CodeContext.code_context is None: + return [] + if TYPE_CHECKING: + # CodeContext base class doesn't define get_automation_parameters(), + # but LambdaContext and LvContext (the concrete implementations) do. + assert isinstance(CodeContext.code_context, LambdaContext) + return CodeContext.code_context.get_automation_parameters() + + class LocalVariable(MockObj): """ Create a local variable and enclose the code using it within a block. From 0194bfd9ea8ef1ccd4eb1ef416dede9c522edb77 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 29 Dec 2025 16:57:22 -1000 Subject: [PATCH 0817/1145] [core] Fix incremental build failures when adding components on ESP32-Arduino (#12745) --- esphome/writer.py | 9 ++-- tests/unit_tests/test_writer.py | 88 +++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/esphome/writer.py b/esphome/writer.py index 721db07f96..684b3f9dc5 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -99,14 +99,11 @@ def storage_should_clean(old: StorageJSON | None, new: StorageJSON) -> bool: def storage_should_update_cmake_cache(old: StorageJSON, new: StorageJSON) -> bool: - if ( + # ESP32 uses CMake for both Arduino and ESP-IDF frameworks + return ( old.loaded_integrations != new.loaded_integrations or old.loaded_platforms != new.loaded_platforms - ) and new.core_platform == PLATFORM_ESP32: - from esphome.components.esp32 import FRAMEWORK_ESP_IDF - - return new.framework == FRAMEWORK_ESP_IDF - return False + ) and new.core_platform == PLATFORM_ESP32 def update_storage_json() -> None: diff --git a/tests/unit_tests/test_writer.py b/tests/unit_tests/test_writer.py index 9fa60c06ec..fa2ca0a696 100644 --- a/tests/unit_tests/test_writer.py +++ b/tests/unit_tests/test_writer.py @@ -9,6 +9,13 @@ from unittest.mock import MagicMock, patch import pytest +from esphome.const import ( + PLATFORM_BK72XX, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RP2040, + PLATFORM_RTL87XX, +) from esphome.core import EsphomeError from esphome.storage_json import StorageJSON from esphome.writer import ( @@ -21,6 +28,7 @@ from esphome.writer import ( clean_build, clean_cmake_cache, storage_should_clean, + storage_should_update_cmake_cache, update_storage_json, write_cpp, write_gitignore, @@ -164,6 +172,86 @@ def test_storage_edge_case_from_empty_integrations( assert storage_should_clean(old, new) is False +# Tests for storage_should_update_cmake_cache + + +@pytest.mark.parametrize("framework", ["arduino", "esp-idf"]) +def test_storage_should_update_cmake_cache_when_integration_added_esp32( + create_storage: Callable[..., StorageJSON], + framework: str, +) -> None: + """Test cmake cache update triggered when integration added on ESP32.""" + old = create_storage( + loaded_integrations=["api", "wifi"], + core_platform=PLATFORM_ESP32, + framework=framework, + ) + new = create_storage( + loaded_integrations=["api", "wifi", "restart"], + core_platform=PLATFORM_ESP32, + framework=framework, + ) + assert storage_should_update_cmake_cache(old, new) is True + + +def test_storage_should_update_cmake_cache_when_platform_changed_esp32( + create_storage: Callable[..., StorageJSON], +) -> None: + """Test cmake cache update triggered when platforms change on ESP32.""" + old = create_storage( + loaded_integrations=["api", "wifi"], + loaded_platforms={"sensor"}, + core_platform=PLATFORM_ESP32, + framework="arduino", + ) + new = create_storage( + loaded_integrations=["api", "wifi"], + loaded_platforms={"sensor", "binary_sensor"}, + core_platform=PLATFORM_ESP32, + framework="arduino", + ) + assert storage_should_update_cmake_cache(old, new) is True + + +def test_storage_should_not_update_cmake_cache_when_nothing_changes( + create_storage: Callable[..., StorageJSON], +) -> None: + """Test cmake cache not updated when nothing changes.""" + old = create_storage( + loaded_integrations=["api", "wifi"], + core_platform=PLATFORM_ESP32, + framework="arduino", + ) + new = create_storage( + loaded_integrations=["api", "wifi"], + core_platform=PLATFORM_ESP32, + framework="arduino", + ) + assert storage_should_update_cmake_cache(old, new) is False + + +@pytest.mark.parametrize( + "core_platform", + [PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_BK72XX, PLATFORM_RTL87XX], +) +def test_storage_should_not_update_cmake_cache_for_non_esp32( + create_storage: Callable[..., StorageJSON], + core_platform: str, +) -> None: + """Test cmake cache not updated for non-ESP32 platforms.""" + old = create_storage( + loaded_integrations=["api", "wifi"], + core_platform=core_platform, + framework="arduino", + ) + new = create_storage( + loaded_integrations=["api", "wifi", "restart"], + core_platform=core_platform, + framework="arduino", + ) + assert storage_should_update_cmake_cache(old, new) is False + + @patch("esphome.writer.clean_build") @patch("esphome.writer.StorageJSON") @patch("esphome.writer.storage_path") From c737033cc42eda289648ce0b33a61f1d0ee7267f Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 30 Dec 2025 09:22:03 -0500 Subject: [PATCH 0818/1145] Bump version to 2025.12.3 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index d41459ea46..fbd5ffa80e 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.2 +PROJECT_NUMBER = 2025.12.3 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 41bb419aaf..ab72bfcaac 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.2" +__version__ = "2025.12.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From dae7ba604a28285cb88e0cc1cd44daefe8f51163 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 30 Dec 2025 10:25:51 -1000 Subject: [PATCH 0819/1145] [ethernet_info] Eliminate heap allocations in DNS text sensor (#12756) --- .../ethernet_info_text_sensor.cpp | 6 ++-- .../ethernet_info/ethernet_info_text_sensor.h | 28 +++++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp index 329fb9113a..35e18c7de5 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp @@ -3,8 +3,7 @@ #ifdef USE_ESP32 -namespace esphome { -namespace ethernet_info { +namespace esphome::ethernet_info { static const char *const TAG = "ethernet_info"; @@ -12,7 +11,6 @@ void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IP void DNSAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo DNS Address", this); } void MACAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo MAC Address", this); } -} // namespace ethernet_info -} // namespace esphome +} // namespace esphome::ethernet_info #endif // USE_ESP32 diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.h b/esphome/components/ethernet_info/ethernet_info_text_sensor.h index 2adc08e31e..b49ddc263d 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.h +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.h @@ -6,8 +6,7 @@ #ifdef USE_ESP32 -namespace esphome { -namespace ethernet_info { +namespace esphome::ethernet_info { class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor { public: @@ -40,21 +39,27 @@ class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextS class DNSAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor { public: void update() override { - auto dns_one = ethernet::global_eth_component->get_dns_address(0); - auto dns_two = ethernet::global_eth_component->get_dns_address(1); + auto dns1 = ethernet::global_eth_component->get_dns_address(0); + auto dns2 = ethernet::global_eth_component->get_dns_address(1); - std::string dns_results = dns_one.str() + " " + dns_two.str(); - - if (dns_results != this->last_results_) { - this->last_results_ = dns_results; - this->publish_state(dns_results); + if (dns1 != this->last_dns1_ || dns2 != this->last_dns2_) { + this->last_dns1_ = dns1; + this->last_dns2_ = dns2; + // IP_ADDRESS_BUFFER_SIZE (40) = max IP (39) + null; space reuses first null's slot + char buf[network::IP_ADDRESS_BUFFER_SIZE * 2]; + dns1.str_to(buf); + size_t len1 = strlen(buf); + buf[len1] = ' '; + dns2.str_to(buf + len1 + 1); + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::ETHERNET; } void dump_config() override; protected: - std::string last_results_; + network::IPAddress last_dns1_; + network::IPAddress last_dns2_; }; class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor { @@ -64,7 +69,6 @@ class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor void dump_config() override; }; -} // namespace ethernet_info -} // namespace esphome +} // namespace esphome::ethernet_info #endif // USE_ESP32 From bd3ecad3a14e57c1bd4284578dd2e1da04a25dc6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 30 Dec 2025 11:51:51 -1000 Subject: [PATCH 0820/1145] [core] Add format_hex_pretty_to buffer helper and reduce code duplication (#12687) --- esphome/components/hmac_md5/hmac_md5.h | 2 +- esphome/components/hmac_sha256/hmac_sha256.h | 2 +- .../components/zwave_proxy/zwave_proxy.cpp | 6 +- esphome/components/zwave_proxy/zwave_proxy.h | 5 +- esphome/core/hash_base.h | 10 +-- esphome/core/helpers.cpp | 63 +++++++++------ esphome/core/helpers.h | 76 +++++++++++-------- 7 files changed, 96 insertions(+), 68 deletions(-) diff --git a/esphome/components/hmac_md5/hmac_md5.h b/esphome/components/hmac_md5/hmac_md5.h index b83b9d5421..fb9479e3af 100644 --- a/esphome/components/hmac_md5/hmac_md5.h +++ b/esphome/components/hmac_md5/hmac_md5.h @@ -30,7 +30,7 @@ class HmacMD5 { void get_bytes(uint8_t *output); /// Retrieve the HMAC-MD5 digest as hex characters. - /// The output must be able to hold 32 bytes or more. + /// The output must be able to hold 33 bytes or more (32 hex chars + null terminator). void get_hex(char *output); /// Compare the digest against a provided byte-encoded digest (16 bytes). diff --git a/esphome/components/hmac_sha256/hmac_sha256.h b/esphome/components/hmac_sha256/hmac_sha256.h index fa6b64aa94..85622cac46 100644 --- a/esphome/components/hmac_sha256/hmac_sha256.h +++ b/esphome/components/hmac_sha256/hmac_sha256.h @@ -35,7 +35,7 @@ class HmacSHA256 { void get_bytes(uint8_t *output); /// Retrieve the HMAC-SHA256 digest as hex characters. - /// The output must be able to hold 64 bytes or more. + /// The output must be able to hold 65 bytes or more (64 hex chars + null terminator). void get_hex(char *output); /// Compare the digest against a provided byte-encoded digest (32 bytes). diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp index bd3f85772b..e4efa55e25 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.cpp +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -123,10 +123,11 @@ void ZWaveProxy::process_uart_() { } void ZWaveProxy::dump_config() { + char hex_buf[format_hex_pretty_size(ZWAVE_HOME_ID_SIZE)]; ESP_LOGCONFIG(TAG, "Z-Wave Proxy:\n" " Home ID: %s", - format_hex_pretty(this->home_id_.data(), this->home_id_.size(), ':', false).c_str()); + format_hex_pretty_to(hex_buf, this->home_id_.data(), this->home_id_.size())); } void ZWaveProxy::api_connection_authenticated(api::APIConnection *conn) { @@ -167,7 +168,8 @@ bool ZWaveProxy::set_home_id(const uint8_t *new_home_id) { return false; // No change } std::memcpy(this->home_id_.data(), new_home_id, this->home_id_.size()); - ESP_LOGI(TAG, "Home ID: %s", format_hex_pretty(this->home_id_.data(), this->home_id_.size(), ':', false).c_str()); + char hex_buf[format_hex_pretty_size(ZWAVE_HOME_ID_SIZE)]; + ESP_LOGI(TAG, "Home ID: %s", format_hex_pretty_to(hex_buf, this->home_id_.data(), this->home_id_.size())); this->home_id_ready_ = true; return true; // Home ID was changed } diff --git a/esphome/components/zwave_proxy/zwave_proxy.h b/esphome/components/zwave_proxy/zwave_proxy.h index 137a1206e3..f36287d32a 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.h +++ b/esphome/components/zwave_proxy/zwave_proxy.h @@ -14,6 +14,7 @@ namespace esphome::zwave_proxy { static constexpr size_t MAX_ZWAVE_FRAME_SIZE = 257; // Maximum Z-Wave frame size +static constexpr size_t ZWAVE_HOME_ID_SIZE = 4; // Z-Wave Home ID size in bytes enum ZWaveResponseTypes : uint8_t { ZWAVE_FRAME_TYPE_ACK = 0x06, @@ -73,8 +74,8 @@ class ZWaveProxy : public uart::UARTDevice, public Component { // Pre-allocated message - always ready to send api::ZWaveProxyFrame outgoing_proto_msg_; - std::array buffer_; // Fixed buffer for incoming data - std::array home_id_{0, 0, 0, 0}; // Fixed buffer for home ID + std::array buffer_; // Fixed buffer for incoming data + std::array home_id_{}; // Fixed buffer for home ID // Pointers and 32-bit values (aligned together) api::APIConnection *api_connection_{nullptr}; // Current subscribed client diff --git a/esphome/core/hash_base.h b/esphome/core/hash_base.h index c45c4df70b..0c1c2dce33 100644 --- a/esphome/core/hash_base.h +++ b/esphome/core/hash_base.h @@ -25,14 +25,8 @@ class HashBase { /// Retrieve the hash as bytes void get_bytes(uint8_t *output) { memcpy(output, this->digest_, this->get_size()); } - /// Retrieve the hash as hex characters - void get_hex(char *output) { - for (size_t i = 0; i < this->get_size(); i++) { - uint8_t byte = this->digest_[i]; - output[i * 2] = format_hex_char(byte >> 4); - output[i * 2 + 1] = format_hex_char(byte & 0x0F); - } - } + /// Retrieve the hash as hex characters. Output buffer must hold get_size() * 2 + 1 bytes. + void get_hex(char *output) { format_hex_to(output, this->get_size() * 2 + 1, this->digest_, this->get_size()); } /// Compare the hash against a provided byte-encoded hash bool equals_bytes(const uint8_t *expected) { return memcmp(this->digest_, expected, this->get_size()) == 0; } diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 5e361ecce2..1c68f1a021 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -286,43 +286,60 @@ std::string format_mac_address_pretty(const uint8_t *mac) { return std::string(buf); } -std::string format_hex(const uint8_t *data, size_t length) { - std::string ret; - ret.resize(length * 2); - for (size_t i = 0; i < length; i++) { - ret[2 * i] = format_hex_char(data[i] >> 4); - ret[2 * i + 1] = format_hex_char(data[i] & 0x0F); +// Internal helper for hex formatting - base is 'a' for lowercase or 'A' for uppercase +static char *format_hex_internal(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator, + char base) { + if (length == 0) { + buffer[0] = '\0'; + return buffer; + } + // With separator: total length is 3*length (2*length hex chars, (length-1) separators, 1 null terminator) + // Without separator: total length is 2*length + 1 (2*length hex chars, 1 null terminator) + uint8_t stride = separator ? 3 : 2; + size_t max_bytes = separator ? (buffer_size / stride) : ((buffer_size - 1) / stride); + if (max_bytes == 0) { + buffer[0] = '\0'; + return buffer; } - return ret; -} -std::string format_hex(const std::vector &data) { return format_hex(data.data(), data.size()); } - -char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) { - size_t max_bytes = (buffer_size - 1) / 2; if (length > max_bytes) { length = max_bytes; } for (size_t i = 0; i < length; i++) { - buffer[2 * i] = format_hex_char(data[i] >> 4); - buffer[2 * i + 1] = format_hex_char(data[i] & 0x0F); + size_t pos = i * stride; + buffer[pos] = format_hex_char(data[i] >> 4, base); + buffer[pos + 1] = format_hex_char(data[i] & 0x0F, base); + if (separator && i < length - 1) { + buffer[pos + 2] = separator; + } } - buffer[length * 2] = '\0'; + buffer[length * stride - (separator ? 1 : 0)] = '\0'; return buffer; } +char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) { + return format_hex_internal(buffer, buffer_size, data, length, 0, 'a'); +} + +std::string format_hex(const uint8_t *data, size_t length) { + std::string ret; + ret.resize(length * 2); + format_hex_to(&ret[0], length * 2 + 1, data, length); + return ret; +} +std::string format_hex(const std::vector &data) { return format_hex(data.data(), data.size()); } + +char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator) { + return format_hex_internal(buffer, buffer_size, data, length, separator, 'A'); +} + // Shared implementation for uint8_t and string hex formatting static std::string format_hex_pretty_uint8(const uint8_t *data, size_t length, char separator, bool show_length) { if (data == nullptr || length == 0) return ""; std::string ret; - uint8_t multiple = separator ? 3 : 2; // 3 if separator is not \0, 2 otherwise - ret.resize(multiple * length - (separator ? 1 : 0)); - for (size_t i = 0; i < length; i++) { - ret[multiple * i] = format_hex_pretty_char(data[i] >> 4); - ret[multiple * i + 1] = format_hex_pretty_char(data[i] & 0x0F); - if (separator && i != length - 1) - ret[multiple * i + 2] = separator; - } + size_t hex_len = separator ? (length * 3 - 1) : (length * 2); + ret.resize(hex_len); + format_hex_pretty_to(&ret[0], hex_len + 1, data, length, separator); if (show_length && length > 4) return ret + " (" + std::to_string(length) + ")"; return ret; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 4319e32510..37534849d0 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -677,12 +677,14 @@ constexpr uint8_t parse_hex_char(char c) { return 255; } +/// Convert a nibble (0-15) to hex char with specified base ('a' for lowercase, 'A' for uppercase) +inline char format_hex_char(uint8_t v, char base) { return v >= 10 ? base + (v - 10) : '0' + v; } + /// Convert a nibble (0-15) to lowercase hex char -inline char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; } +inline char format_hex_char(uint8_t v) { return format_hex_char(v, 'a'); } /// Convert a nibble (0-15) to uppercase hex char (used for pretty printing) -/// This always uses uppercase (A-F) for pretty/human-readable output -inline char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; } +inline char format_hex_pretty_char(uint8_t v) { return format_hex_char(v, 'A'); } /// Write int8 value to buffer without modulo operations. /// Buffer must have at least 4 bytes free. Returns pointer past last char written. @@ -708,28 +710,6 @@ inline char *int8_to_str(char *buf, int8_t val) { return buf; } -/// Format MAC address as XX:XX:XX:XX:XX:XX (uppercase) -inline void format_mac_addr_upper(const uint8_t *mac, char *output) { - for (size_t i = 0; i < 6; i++) { - uint8_t byte = mac[i]; - output[i * 3] = format_hex_pretty_char(byte >> 4); - output[i * 3 + 1] = format_hex_pretty_char(byte & 0x0F); - if (i < 5) - output[i * 3 + 2] = ':'; - } - output[17] = '\0'; -} - -/// Format MAC address as xxxxxxxxxxxxxx (lowercase, no separators) -inline void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output) { - for (size_t i = 0; i < 6; i++) { - uint8_t byte = mac[i]; - output[i * 2] = format_hex_char(byte >> 4); - output[i * 2 + 1] = format_hex_char(byte & 0x0F); - } - output[12] = '\0'; -} - /// Format byte array as lowercase hex to buffer (base implementation). char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length); @@ -748,6 +728,46 @@ inline char *format_hex_to(char (&buffer)[N], T val) { return format_hex_to(buffer, reinterpret_cast(&val), sizeof(T)); } +/// Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0" +constexpr size_t format_hex_pretty_size(size_t byte_count) { return byte_count * 3; } + +/** Format byte array as uppercase hex to buffer (base implementation). + * + * @param buffer Output buffer to write to. + * @param buffer_size Size of the output buffer. + * @param data Pointer to the byte array to format. + * @param length Number of bytes in the array. + * @param separator Character to use between hex bytes, or '\0' for no separator. + * @return Pointer to buffer. + * + * Buffer size needed: length * 3 with separator (for "XX:XX:XX\0"), length * 2 + 1 without. + */ +char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator = ':'); + +/// Format byte array as uppercase hex with separator to buffer. Automatically deduces buffer size. +template +inline char *format_hex_pretty_to(char (&buffer)[N], const uint8_t *data, size_t length, char separator = ':') { + static_assert(N >= 3, "Buffer must hold at least one hex byte"); + return format_hex_pretty_to(buffer, N, data, length, separator); +} + +/// MAC address size in bytes +static constexpr size_t MAC_ADDRESS_SIZE = 6; +/// Buffer size for MAC address with separators: "XX:XX:XX:XX:XX:XX\0" +static constexpr size_t MAC_ADDRESS_PRETTY_BUFFER_SIZE = format_hex_pretty_size(MAC_ADDRESS_SIZE); +/// Buffer size for MAC address without separators: "XXXXXXXXXXXX\0" +static constexpr size_t MAC_ADDRESS_BUFFER_SIZE = MAC_ADDRESS_SIZE * 2 + 1; + +/// Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators) +inline void format_mac_addr_upper(const uint8_t *mac, char *output) { + format_hex_pretty_to(output, MAC_ADDRESS_PRETTY_BUFFER_SIZE, mac, MAC_ADDRESS_SIZE, ':'); +} + +/// Format MAC address as xxxxxxxxxxxxxx (lowercase, no separators) +inline void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output) { + format_hex_to(output, MAC_ADDRESS_BUFFER_SIZE, mac, MAC_ADDRESS_SIZE); +} + /// Format the six-byte array \p mac into a MAC address. std::string format_mac_address_pretty(const uint8_t mac[6]); /// Format the byte array \p data of length \p len in lowercased hex. @@ -1203,12 +1223,6 @@ class HighFrequencyLoopRequester { /// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). void get_mac_address_raw(uint8_t *mac); // NOLINT(readability-non-const-parameter) -/// Buffer size for MAC address in lowercase hex notation (12 hex chars + null terminator) -constexpr size_t MAC_ADDRESS_BUFFER_SIZE = 13; - -/// Buffer size for MAC address in colon-separated uppercase hex notation (17 chars + null terminator) -constexpr size_t MAC_ADDRESS_PRETTY_BUFFER_SIZE = 18; - /// Get the device MAC address as a string, in lowercase hex notation. std::string get_mac_address(); From 98cdef25683c26b42617f1cf65b44c3fb42d8ee2 Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Wed, 31 Dec 2025 12:58:37 -0800 Subject: [PATCH 0821/1145] [hub75] Add clipping check (#12762) --- esphome/components/hub75/hub75.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/hub75/hub75.cpp b/esphome/components/hub75/hub75.cpp index 7317174831..e29f1a898c 100644 --- a/esphome/components/hub75/hub75.cpp +++ b/esphome/components/hub75/hub75.cpp @@ -111,6 +111,9 @@ void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) { if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) [[unlikely]] return; + if (!this->get_clipping().inside(x, y)) + return; + driver_->set_pixel(x, y, color.r, color.g, color.b); App.feed_wdt(); } From 476d00d0e591e10a5ccb41183c649a66291acde9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 31 Dec 2025 10:59:28 -1000 Subject: [PATCH 0822/1145] [wifi] Fix ESP-IDF reporting connected before DHCP completes on reconnect (#12755) --- esphome/components/wifi/wifi_component_esp_idf.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index b26ac3d2e2..5d4d003d62 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -483,6 +483,12 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { s_sta_connected = false; s_sta_connect_error = false; s_sta_connect_not_found = false; + // Reset IP address flags - ensures we don't report connected before DHCP completes + // (IP_EVENT_STA_LOST_IP doesn't always fire on disconnect) + this->got_ipv4_address_ = false; +#if USE_NETWORK_IPV6 + this->num_ipv6_addresses_ = 0; +#endif err = esp_wifi_connect(); if (err != ESP_OK) { From 4633803d5dae4d969f00b246d528aee84aab19a8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 31 Dec 2025 11:05:58 -1000 Subject: [PATCH 0823/1145] [docker] Add build-essential to fix ruamel.yaml 0.19.0 compilation (#12769) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- docker/Dockerfile | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 64ce67e819..348a503bc8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,6 +11,16 @@ FROM base-source-${BUILD_TYPE} AS base RUN git config --system --add safe.directory "*" +# Install build tools for Python packages that require compilation +# (e.g., ruamel.yaml.clibz used by ESP-IDF's idf-component-manager) +RUN if command -v apk > /dev/null; then \ + apk add --no-cache build-base; \ + else \ + apt-get update \ + && apt-get install -y --no-install-recommends build-essential \ + && rm -rf /var/lib/apt/lists/*; \ + fi + ENV PIP_DISABLE_PIP_VERSION_CHECK=1 RUN pip install --no-cache-dir -U pip uv==0.6.14 From dd855985bec15d235924b9e3bd4a3204a7137221 Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Wed, 31 Dec 2025 12:58:37 -0800 Subject: [PATCH 0824/1145] [hub75] Add clipping check (#12762) --- esphome/components/hub75/hub75.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/hub75/hub75.cpp b/esphome/components/hub75/hub75.cpp index e023e446c4..a09094b87c 100644 --- a/esphome/components/hub75/hub75.cpp +++ b/esphome/components/hub75/hub75.cpp @@ -111,6 +111,9 @@ void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) { if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) [[unlikely]] return; + if (!this->get_clipping().inside(x, y)) + return; + driver_->set_pixel(x, y, color.r, color.g, color.b); App.feed_wdt(); } From f0f01c081ad5498533173b2ba532f3817787e6bb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 31 Dec 2025 10:59:28 -1000 Subject: [PATCH 0825/1145] [wifi] Fix ESP-IDF reporting connected before DHCP completes on reconnect (#12755) --- esphome/components/wifi/wifi_component_esp_idf.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 380e4ea7fd..fb28018b07 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -483,6 +483,12 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { s_sta_connected = false; s_sta_connect_error = false; s_sta_connect_not_found = false; + // Reset IP address flags - ensures we don't report connected before DHCP completes + // (IP_EVENT_STA_LOST_IP doesn't always fire on disconnect) + this->got_ipv4_address_ = false; +#if USE_NETWORK_IPV6 + this->num_ipv6_addresses_ = 0; +#endif err = esp_wifi_connect(); if (err != ESP_OK) { From 062840dd7bb7aa4e01d5c71b6b85406223cf8e7a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 31 Dec 2025 11:05:58 -1000 Subject: [PATCH 0826/1145] [docker] Add build-essential to fix ruamel.yaml 0.19.0 compilation (#12769) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- docker/Dockerfile | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 64ce67e819..348a503bc8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,6 +11,16 @@ FROM base-source-${BUILD_TYPE} AS base RUN git config --system --add safe.directory "*" +# Install build tools for Python packages that require compilation +# (e.g., ruamel.yaml.clibz used by ESP-IDF's idf-component-manager) +RUN if command -v apk > /dev/null; then \ + apk add --no-cache build-base; \ + else \ + apt-get update \ + && apt-get install -y --no-install-recommends build-essential \ + && rm -rf /var/lib/apt/lists/*; \ + fi + ENV PIP_DISABLE_PIP_VERSION_CHECK=1 RUN pip install --no-cache-dir -U pip uv==0.6.14 From e9e07129599394a2b42a6594bb1e4bf72f045236 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 31 Dec 2025 16:07:00 -0500 Subject: [PATCH 0827/1145] Bump version to 2025.12.4 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index fbd5ffa80e..ff74757639 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.3 +PROJECT_NUMBER = 2025.12.4 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index ab72bfcaac..3fbdb69215 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.3" +__version__ = "2025.12.4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 1d96de986ec752c19aecbc81aa70f7413d3168fb Mon Sep 17 00:00:00 2001 From: Konstantin Tretyakov <220083+konstantint@users.noreply.github.com> Date: Wed, 31 Dec 2025 22:49:43 +0100 Subject: [PATCH 0828/1145] [sdist] Include yaml files in components in source distribution package Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 45d5e86672..ed65edc656 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ include LICENSE include README.md include requirements.txt +recursive-include esphome *.yaml recursive-include esphome *.cpp *.h *.tcc *.c recursive-include esphome *.py.script recursive-include esphome LICENSE.txt From 4313130f2e3a87f8c1e35fa32db03c5085760415 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 1 Jan 2026 13:44:21 +1000 Subject: [PATCH 0829/1145] [lvgl] Fix arc background angles (#12773) --- esphome/components/lvgl/widgets/arc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/lvgl/widgets/arc.py b/esphome/components/lvgl/widgets/arc.py index 21530441f8..34ac9c51f7 100644 --- a/esphome/components/lvgl/widgets/arc.py +++ b/esphome/components/lvgl/widgets/arc.py @@ -85,11 +85,11 @@ class ArcType(NumberType): lv.arc_set_range(w.obj, min_value, max_value) await w.set_property( - CONF_START_ANGLE, + "bg_start_angle", await lv_angle_degrees.process(config.get(CONF_START_ANGLE)), ) await w.set_property( - CONF_END_ANGLE, await lv_angle_degrees.process(config.get(CONF_END_ANGLE)) + "bg_end_angle", await lv_angle_degrees.process(config.get(CONF_END_ANGLE)) ) await w.set_property( CONF_ROTATION, await lv_angle_degrees.process(config.get(CONF_ROTATION)) From 1945e85ddc55d7c7bd98be201dcd4f967ddb5989 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 1 Jan 2026 21:07:35 +1000 Subject: [PATCH 0830/1145] [core] Make LockFreeQueue more widely available (#12766) --- esphome/core/lock_free_queue.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/core/lock_free_queue.h b/esphome/core/lock_free_queue.h index 68e2825d09..e96b739b58 100644 --- a/esphome/core/lock_free_queue.h +++ b/esphome/core/lock_free_queue.h @@ -1,12 +1,12 @@ #pragma once -#if defined(USE_ESP32) - #include #include +#ifdef USE_ESP32 #include #include +#endif /* * Lock-free queue for single-producer single-consumer scenarios. @@ -95,7 +95,7 @@ template class LockFreeQueue { } protected: - T *buffer_[SIZE]; + T *buffer_[SIZE]{}; // Atomic: written by producer (push/increment), read+reset by consumer (get_and_reset) std::atomic dropped_count_; // 65535 max - more than enough for drop tracking // Atomic: written by consumer (pop), read by producer (push) to check if full @@ -106,6 +106,7 @@ template class LockFreeQueue { std::atomic tail_; }; +#ifdef USE_ESP32 // Extended queue with task notification support template class NotifyingLockFreeQueue : public LockFreeQueue { public: @@ -140,7 +141,6 @@ template class NotifyingLockFreeQueue : public LockFreeQu private: TaskHandle_t task_to_notify_; }; +#endif } // namespace esphome - -#endif // defined(USE_ESP32) From dc320f455a3c03dcd2b339148aa394d580dc67de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jan 2026 09:16:01 -1000 Subject: [PATCH 0831/1145] Bump bleak from 2.1.0 to 2.1.1 (#12804) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a6262e0d10..d457be9cd2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ pillow==11.3.0 cairosvg==2.8.2 freetype-py==2.5.1 jinja2==3.1.6 -bleak==2.1.0 +bleak==2.1.1 # esp-idf >= 5.0 requires this pyparsing >= 3.0 From 9847e51fbcd9f1ba9b46cbaecabdc915fdb059f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=2E=20=C3=81rkosi=20R=C3=B3bert?= Date: Thu, 1 Jan 2026 22:40:18 +0100 Subject: [PATCH 0832/1145] [bthome_mithermometer] Add BTHome parsing for Xiaomi Mijia BLE Sensors (#12635) --- CODEOWNERS | 1 + .../bthome_mithermometer/__init__.py | 36 +++ .../bthome_mithermometer/bthome_ble.cpp | 298 ++++++++++++++++++ .../bthome_mithermometer/bthome_ble.h | 44 +++ .../components/bthome_mithermometer/sensor.py | 88 ++++++ .../bthome_mithermometer/common.yaml | 15 + .../bthome_mithermometer/test.esp32-idf.yaml | 4 + 7 files changed, 486 insertions(+) create mode 100644 esphome/components/bthome_mithermometer/__init__.py create mode 100644 esphome/components/bthome_mithermometer/bthome_ble.cpp create mode 100644 esphome/components/bthome_mithermometer/bthome_ble.h create mode 100644 esphome/components/bthome_mithermometer/sensor.py create mode 100644 tests/components/bthome_mithermometer/common.yaml create mode 100644 tests/components/bthome_mithermometer/test.esp32-idf.yaml diff --git a/CODEOWNERS b/CODEOWNERS index f95d68a46d..0d9396aa6f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -91,6 +91,7 @@ esphome/components/bmp3xx_spi/* @latonita esphome/components/bmp581/* @kahrendt esphome/components/bp1658cj/* @Cossid esphome/components/bp5758d/* @Cossid +esphome/components/bthome_mithermometer/* @nagyrobi esphome/components/button/* @esphome/core esphome/components/bytebuffer/* @clydebarrow esphome/components/camera/* @bdraco @DT-art1 diff --git a/esphome/components/bthome_mithermometer/__init__.py b/esphome/components/bthome_mithermometer/__init__.py new file mode 100644 index 0000000000..0e84278afa --- /dev/null +++ b/esphome/components/bthome_mithermometer/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +from esphome.components import esp32_ble_tracker +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_MAC_ADDRESS + +CODEOWNERS = ["@nagyrobi"] +DEPENDENCIES = ["esp32_ble_tracker"] + +BLE_DEVICE_SCHEMA = esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA + +bthome_mithermometer_ns = cg.esphome_ns.namespace("bthome_mithermometer") +BTHomeMiThermometer = bthome_mithermometer_ns.class_( + "BTHomeMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + + +def bthome_mithermometer_base_schema(extra_schema=None): + if extra_schema is None: + extra_schema = {} + return ( + cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(BTHomeMiThermometer), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + } + ) + .extend(BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) + .extend(extra_schema) + ) + + +async def setup_bthome_mithermometer(var, config): + await cg.register_component(var, config) + await esp32_ble_tracker.register_ble_device(var, config) + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) diff --git a/esphome/components/bthome_mithermometer/bthome_ble.cpp b/esphome/components/bthome_mithermometer/bthome_ble.cpp new file mode 100644 index 0000000000..b8da51a783 --- /dev/null +++ b/esphome/components/bthome_mithermometer/bthome_ble.cpp @@ -0,0 +1,298 @@ +#include "bthome_ble.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include + +#ifdef USE_ESP32 + +namespace esphome { +namespace bthome_mithermometer { + +static const char *const TAG = "bthome_mithermometer"; + +static std::string format_mac_address(uint64_t address) { + std::array mac{}; + for (size_t i = 0; i < MAC_ADDRESS_SIZE; i++) { + mac[i] = (address >> ((MAC_ADDRESS_SIZE - 1 - i) * 8)) & 0xFF; + } + + char buffer[MAC_ADDRESS_SIZE * 3]; + format_mac_addr_upper(mac.data(), buffer); + return buffer; +} + +static bool get_bthome_value_length(uint8_t obj_type, size_t &value_length) { + switch (obj_type) { + case 0x00: // packet id + case 0x01: // battery + case 0x09: // count (uint8) + case 0x0F: // generic boolean + case 0x10: // power (bool) + case 0x11: // opening + case 0x15: // battery low + case 0x16: // battery charging + case 0x17: // carbon monoxide + case 0x18: // cold + case 0x19: // connectivity + case 0x1A: // door + case 0x1B: // garage door + case 0x1C: // gas + case 0x1D: // heat + case 0x1E: // light + case 0x1F: // lock + case 0x20: // moisture + case 0x21: // motion + case 0x22: // moving + case 0x23: // occupancy + case 0x24: // plug + case 0x25: // presence + case 0x26: // problem + case 0x27: // running + case 0x28: // safety + case 0x29: // smoke + case 0x2A: // sound + case 0x2B: // tamper + case 0x2C: // vibration + case 0x2D: // water leak + case 0x2E: // humidity (uint8) + case 0x2F: // moisture (uint8) + case 0x46: // UV index + case 0x57: // temperature (sint8) + case 0x58: // temperature (0.35C step) + case 0x59: // count (sint8) + case 0x60: // channel + value_length = 1; + return true; + case 0x02: // temperature (0.01C) + case 0x03: // humidity + case 0x06: // mass (kg) + case 0x07: // mass (lb) + case 0x08: // dewpoint + case 0x0C: // voltage (mV) + case 0x0D: // pm2.5 + case 0x0E: // pm10 + case 0x12: // CO2 + case 0x13: // TVOC + case 0x14: // moisture + case 0x3D: // count (uint16) + case 0x3F: // rotation + case 0x40: // distance (mm) + case 0x41: // distance (m) + case 0x43: // current (A) + case 0x44: // speed + case 0x45: // temperature (0.1C) + case 0x47: // volume (L) + case 0x48: // volume (mL) + case 0x49: // volume flow rate + case 0x4A: // voltage (0.1V) + case 0x51: // acceleration + case 0x52: // gyroscope + case 0x56: // conductivity + case 0x5A: // count (sint16) + case 0x5D: // current (sint16) + case 0x5E: // direction + case 0x5F: // precipitation + case 0x61: // rotational speed + case 0xF0: // button event + value_length = 2; + return true; + case 0x04: // pressure + case 0x05: // illuminance + case 0x0A: // energy + case 0x0B: // power + case 0x42: // duration + case 0x4B: // gas (uint24) + case 0xF2: // firmware version (uint24) + value_length = 3; + return true; + case 0x3E: // count (uint32) + case 0x4C: // gas (uint32) + case 0x4D: // energy (uint32) + case 0x4E: // volume (uint32) + case 0x4F: // water (uint32) + case 0x50: // timestamp + case 0x55: // volume storage + case 0x5B: // count (sint32) + case 0x5C: // power (sint32) + case 0x62: // speed (sint32) + case 0x63: // acceleration (sint32) + case 0xF1: // firmware version (uint32) + value_length = 4; + return true; + default: + return false; + } +} + +void BTHomeMiThermometer::dump_config() { + ESP_LOGCONFIG(TAG, "BTHome MiThermometer"); + ESP_LOGCONFIG(TAG, " MAC Address: %s", format_mac_address(this->address_).c_str()); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Humidity", this->humidity_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); + LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_); + LOG_SENSOR(" ", "Signal Strength", this->signal_strength_); +} + +bool BTHomeMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + bool matched = false; + for (auto &service_data : device.get_service_datas()) { + if (this->handle_service_data_(service_data, device)) { + matched = true; + } + } + if (matched && this->signal_strength_ != nullptr) { + this->signal_strength_->publish_state(device.get_rssi()); + } + return matched; +} + +bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceData &service_data, + const esp32_ble_tracker::ESPBTDevice &device) { + if (!service_data.uuid.contains(0xD2, 0xFC)) { + return false; + } + + const auto &data = service_data.data; + if (data.size() < 2) { + ESP_LOGVV(TAG, "BTHome data too short: %zu", data.size()); + return false; + } + + const uint8_t adv_info = data[0]; + const bool is_encrypted = adv_info & 0x01; + const bool mac_included = adv_info & 0x02; + const bool is_trigger_based = adv_info & 0x04; + const uint8_t version = (adv_info >> 5) & 0x07; + + if (version != 0x02) { + ESP_LOGVV(TAG, "Unsupported BTHome version %u", version); + return false; + } + + if (is_encrypted) { + ESP_LOGV(TAG, "Ignoring encrypted BTHome frame from %s", device.address_str().c_str()); + return false; + } + + size_t payload_index = 1; + uint64_t source_address = device.address_uint64(); + + if (mac_included) { + if (data.size() < 7) { + ESP_LOGVV(TAG, "BTHome payload missing MAC address"); + return false; + } + source_address = 0; + for (int i = 5; i >= 0; i--) { + source_address = (source_address << 8) | data[1 + i]; + } + payload_index = 7; + } + + if (source_address != this->address_) { + ESP_LOGVV(TAG, "BTHome frame from unexpected device %s", format_mac_address(source_address).c_str()); + return false; + } + + if (payload_index >= data.size()) { + ESP_LOGVV(TAG, "BTHome payload empty after header"); + return false; + } + + bool reported = false; + size_t offset = payload_index; + uint8_t last_type = 0; + + while (offset < data.size()) { + const uint8_t obj_type = data[offset++]; + size_t value_length = 0; + bool has_length_byte = obj_type == 0x53; // text objects include explicit length + + if (has_length_byte) { + if (offset >= data.size()) { + break; + } + value_length = data[offset++]; + } else { + if (!get_bthome_value_length(obj_type, value_length)) { + ESP_LOGVV(TAG, "Unknown BTHome object 0x%02X", obj_type); + break; + } + } + + if (value_length == 0) { + break; + } + + if (offset + value_length > data.size()) { + ESP_LOGVV(TAG, "BTHome object length exceeds payload"); + break; + } + + const uint8_t *value = &data[offset]; + offset += value_length; + + if (obj_type < last_type) { + ESP_LOGVV(TAG, "BTHome objects not in ascending order"); + } + last_type = obj_type; + + switch (obj_type) { + case 0x00: { // packet id + const uint8_t packet_id = value[0]; + if (this->last_packet_id_.has_value() && *this->last_packet_id_ == packet_id) { + return reported; + } + this->last_packet_id_ = packet_id; + break; + } + case 0x01: { // battery percentage + if (this->battery_level_ != nullptr) { + this->battery_level_->publish_state(value[0]); + reported = true; + } + break; + } + case 0x0C: { // battery voltage (mV) + if (this->battery_voltage_ != nullptr) { + const uint16_t raw = encode_uint16(value[1], value[0]); + this->battery_voltage_->publish_state(raw * 0.001f); + reported = true; + } + break; + } + case 0x02: { // temperature + if (this->temperature_ != nullptr) { + const int16_t raw = encode_uint16(value[1], value[0]); + this->temperature_->publish_state(raw * 0.01f); + reported = true; + } + break; + } + case 0x03: { // humidity + if (this->humidity_ != nullptr) { + const uint16_t raw = encode_uint16(value[1], value[0]); + this->humidity_->publish_state(raw * 0.01f); + reported = true; + } + break; + } + default: + break; + } + } + + if (reported) { + ESP_LOGD(TAG, "BTHome data%sfrom %s", is_trigger_based ? " (triggered) " : " ", device.address_str().c_str()); + } + + return reported; +} + +} // namespace bthome_mithermometer +} // namespace esphome + +#endif diff --git a/esphome/components/bthome_mithermometer/bthome_ble.h b/esphome/components/bthome_mithermometer/bthome_ble.h new file mode 100644 index 0000000000..3d2380b48d --- /dev/null +++ b/esphome/components/bthome_mithermometer/bthome_ble.h @@ -0,0 +1,44 @@ +#pragma once + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +#include + +#ifdef USE_ESP32 + +namespace esphome { +namespace bthome_mithermometer { + +class BTHomeMiThermometer : public esp32_ble_tracker::ESPBTDeviceListener, public Component { + public: + void set_address(uint64_t address) { this->address_ = address; } + + void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { this->humidity_ = humidity; } + void set_battery_level(sensor::Sensor *battery_level) { this->battery_level_ = battery_level; } + void set_battery_voltage(sensor::Sensor *battery_voltage) { this->battery_voltage_ = battery_voltage; } + void set_signal_strength(sensor::Sensor *signal_strength) { this->signal_strength_ = signal_strength; } + + void dump_config() override; + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + + protected: + bool handle_service_data_(const esp32_ble_tracker::ServiceData &service_data, + const esp32_ble_tracker::ESPBTDevice &device); + + uint64_t address_{0}; + optional last_packet_id_{}; + + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; + sensor::Sensor *battery_voltage_{nullptr}; + sensor::Sensor *signal_strength_{nullptr}; +}; + +} // namespace bthome_mithermometer +} // namespace esphome + +#endif diff --git a/esphome/components/bthome_mithermometer/sensor.py b/esphome/components/bthome_mithermometer/sensor.py new file mode 100644 index 0000000000..9b50866db0 --- /dev/null +++ b/esphome/components/bthome_mithermometer/sensor.py @@ -0,0 +1,88 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_BATTERY_VOLTAGE, + CONF_HUMIDITY, + CONF_ID, + CONF_SIGNAL_STRENGTH, + CONF_TEMPERATURE, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_DECIBEL_MILLIWATT, + UNIT_PERCENT, + UNIT_VOLT, +) + +from . import bthome_mithermometer_base_schema, setup_bthome_mithermometer + +CODEOWNERS = ["@nagyrobi"] + +DEPENDENCIES = ["esp32_ble_tracker"] + +CONFIG_SCHEMA = bthome_mithermometer_base_schema( + { + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + icon="mdi:battery-plus", + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_SIGNAL_STRENGTH): sensor.sensor_schema( + unit_of_measurement=UNIT_DECIBEL_MILLIWATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await setup_bthome_mithermometer(var, config) + + if temp_sens := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temp_sens) + cg.add(var.set_temperature(sens)) + if humi_sens := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humi_sens) + cg.add(var.set_humidity(sens)) + if batl_sens := config.get(CONF_BATTERY_LEVEL): + sens = await sensor.new_sensor(batl_sens) + cg.add(var.set_battery_level(sens)) + if batv_sens := config.get(CONF_BATTERY_VOLTAGE): + sens = await sensor.new_sensor(batv_sens) + cg.add(var.set_battery_voltage(sens)) + if sgnl_sens := config.get(CONF_SIGNAL_STRENGTH): + sens = await sensor.new_sensor(sgnl_sens) + cg.add(var.set_signal_strength(sens)) diff --git a/tests/components/bthome_mithermometer/common.yaml b/tests/components/bthome_mithermometer/common.yaml new file mode 100644 index 0000000000..ba94e46878 --- /dev/null +++ b/tests/components/bthome_mithermometer/common.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + - platform: bthome_mithermometer + mac_address: A4:C1:38:4E:16:78 + temperature: + name: "BTHome Temperature" + humidity: + name: "BTHome Humidity" + battery_level: + name: "BTHome Battery" + battery_voltage: + name: "BTHome Battery Voltage" + signal_strength: + name: "BTHome Signal" diff --git a/tests/components/bthome_mithermometer/test.esp32-idf.yaml b/tests/components/bthome_mithermometer/test.esp32-idf.yaml new file mode 100644 index 0000000000..7a6541ae76 --- /dev/null +++ b/tests/components/bthome_mithermometer/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + ble: !include ../../test_build_components/common/ble/esp32-idf.yaml + +<<: !include common.yaml From ed435241b1e990a7f0067246e098a7c67a204704 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 11:48:37 -1000 Subject: [PATCH 0833/1145] [mipi_spi] Use stack buffer for hex formatting in verbose logging (#12778) --- esphome/components/mipi_spi/mipi_spi.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/mipi_spi/mipi_spi.h b/esphome/components/mipi_spi/mipi_spi.h index 1953aef035..7dfd5a9992 100644 --- a/esphome/components/mipi_spi/mipi_spi.h +++ b/esphome/components/mipi_spi/mipi_spi.h @@ -5,11 +5,15 @@ #include "esphome/components/spi/spi.h" #include "esphome/components/display/display.h" #include "esphome/components/display/display_color_utils.h" +#include "esphome/core/helpers.h" namespace esphome { namespace mipi_spi { constexpr static const char *const TAG = "display.mipi_spi"; + +// Maximum bytes to log for commands (truncated if larger) +static constexpr size_t MIPI_SPI_MAX_CMD_LOG_BYTES = 64; static constexpr uint8_t SW_RESET_CMD = 0x01; static constexpr uint8_t SLEEP_OUT = 0x11; static constexpr uint8_t NORON = 0x13; @@ -241,7 +245,10 @@ class MipiSpi : public display::Display, // Writes a command to the display, with the given bytes. void write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) { - esph_log_v(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty(bytes, len).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MIPI_SPI_MAX_CMD_LOG_BYTES)]; + esph_log_v(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty_to(hex_buf, bytes, len)); +#endif if constexpr (BUS_TYPE == BUS_TYPE_QUAD) { this->enable(); this->write_cmd_addr_data(8, 0x02, 24, cmd << 8, bytes, len); From 2841b5fe44bf0aac569c140c0223e79b7271c07e Mon Sep 17 00:00:00 2001 From: Artur <130101347+aanikei@users.noreply.github.com> Date: Fri, 2 Jan 2026 04:28:10 +0000 Subject: [PATCH 0834/1145] [sn74hc595]: fix 'Attempted read from write-only channel' when using esp-idf framework (#12801) --- esphome/components/sn74hc595/sn74hc595.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sn74hc595/sn74hc595.cpp b/esphome/components/sn74hc595/sn74hc595.cpp index fc47a6dc5e..a9ada432e4 100644 --- a/esphome/components/sn74hc595/sn74hc595.cpp +++ b/esphome/components/sn74hc595/sn74hc595.cpp @@ -70,7 +70,7 @@ void SN74HC595GPIOComponent::write_gpio() { void SN74HC595SPIComponent::write_gpio() { for (uint8_t &output_byte : std::ranges::reverse_view(this->output_bytes_)) { this->enable(); - this->transfer_byte(output_byte); + this->write_byte(output_byte); this->disable(); } SN74HC595Component::write_gpio(); From 7483bbd6ea67300b43302b7c05e027113dd089cd Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Thu, 1 Jan 2026 21:34:39 -0800 Subject: [PATCH 0835/1145] [display] Ensure drivers respect clipping during `fill()` (#12808) --- esphome/components/epaper_spi/epaper_spi.h | 6 ++++++ .../components/epaper_spi/epaper_spi_spectra_e6.cpp | 6 ++++++ esphome/components/ili9xxx/ili9xxx_display.cpp | 7 +++++++ esphome/components/inkplate/inkplate.cpp | 7 +++++++ esphome/components/mipi_dsi/mipi_dsi.cpp | 7 +++++++ esphome/components/mipi_rgb/mipi_rgb.cpp | 7 +++++++ esphome/components/mipi_spi/mipi_spi.h | 6 ++++++ esphome/components/pcd8544/pcd_8544.cpp | 6 ++++++ esphome/components/ssd1306_base/ssd1306_base.cpp | 6 ++++++ esphome/components/ssd1322_base/ssd1322_base.cpp | 6 ++++++ esphome/components/ssd1327_base/ssd1327_base.cpp | 6 ++++++ esphome/components/ssd1331_base/ssd1331_base.cpp | 6 ++++++ esphome/components/ssd1351_base/ssd1351_base.cpp | 6 ++++++ esphome/components/st7567_base/st7567_base.cpp | 11 ++++++++++- esphome/components/st7920/st7920.cpp | 11 ++++++++++- .../components/waveshare_epaper/waveshare_epaper.cpp | 12 ++++++++++++ 16 files changed, 114 insertions(+), 2 deletions(-) diff --git a/esphome/components/epaper_spi/epaper_spi.h b/esphome/components/epaper_spi/epaper_spi.h index 6852416cac..b587b07e8f 100644 --- a/esphome/components/epaper_spi/epaper_spi.h +++ b/esphome/components/epaper_spi/epaper_spi.h @@ -76,6 +76,12 @@ class EPaperBase : public Display, return 0; } void fill(Color color) override { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + auto pixel_color = color_to_bit(color) ? 0xFF : 0x00; // We store 8 pixels per byte diff --git a/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp b/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp index d0e68595d0..be243145fc 100644 --- a/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp +++ b/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp @@ -97,6 +97,12 @@ void EPaperSpectraE6::deep_sleep() { } void EPaperSpectraE6::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + EPaperBase::fill(color); + return; + } + auto pixel_color = color_to_hex(color); // We store 2 pixels per byte diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index 2a3d0edca7..a3eff901d3 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -131,6 +131,13 @@ float ILI9XXXDisplay::get_setup_priority() const { return setup_priority::HARDWA void ILI9XXXDisplay::fill(Color color) { if (!this->check_buffer_()) return; + + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + uint16_t new_color = 0; this->x_low_ = 0; this->y_low_ = 0; diff --git a/esphome/components/inkplate/inkplate.cpp b/esphome/components/inkplate/inkplate.cpp index f96fb6905e..c921c643fa 100644 --- a/esphome/components/inkplate/inkplate.cpp +++ b/esphome/components/inkplate/inkplate.cpp @@ -293,6 +293,13 @@ void Inkplate::fill(Color color) { ESP_LOGV(TAG, "Fill called"); uint32_t start_time = millis(); + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time); + return; + } + if (this->greyscale_) { uint8_t fill = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5; memset(this->buffer_, (fill << 4) | fill, this->get_buffer_length_()); diff --git a/esphome/components/mipi_dsi/mipi_dsi.cpp b/esphome/components/mipi_dsi/mipi_dsi.cpp index cae8647398..7471aaa5c5 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.cpp +++ b/esphome/components/mipi_dsi/mipi_dsi.cpp @@ -293,6 +293,13 @@ void MIPI_DSI::draw_pixel_at(int x, int y, Color color) { void MIPI_DSI::fill(Color color) { if (!this->check_buffer_()) return; + + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + switch (this->color_depth_) { case display::COLOR_BITNESS_565: { auto *ptr_16 = reinterpret_cast(this->buffer_); diff --git a/esphome/components/mipi_rgb/mipi_rgb.cpp b/esphome/components/mipi_rgb/mipi_rgb.cpp index d5d1caf6d2..c4485af8a7 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.cpp +++ b/esphome/components/mipi_rgb/mipi_rgb.cpp @@ -300,6 +300,13 @@ void MipiRgb::draw_pixel_at(int x, int y, Color color) { void MipiRgb::fill(Color color) { if (!this->check_buffer_()) return; + + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + auto *ptr_16 = reinterpret_cast(this->buffer_); uint8_t hi_byte = static_cast(color.r & 0xF8) | (color.g >> 5); uint8_t lo_byte = static_cast((color.g & 0x1C) << 3) | (color.b >> 3); diff --git a/esphome/components/mipi_spi/mipi_spi.h b/esphome/components/mipi_spi/mipi_spi.h index 7dfd5a9992..a59cb8104b 100644 --- a/esphome/components/mipi_spi/mipi_spi.h +++ b/esphome/components/mipi_spi/mipi_spi.h @@ -569,6 +569,12 @@ class MipiSpiBuffer : public MipiSpiget_clipping().is_set()) { + display::Display::fill(color); + return; + } + this->x_low_ = 0; this->y_low_ = this->start_line_; this->x_high_ = WIDTH - 1; diff --git a/esphome/components/pcd8544/pcd_8544.cpp b/esphome/components/pcd8544/pcd_8544.cpp index f5b018b127..95d91ff18a 100644 --- a/esphome/components/pcd8544/pcd_8544.cpp +++ b/esphome/components/pcd8544/pcd_8544.cpp @@ -117,6 +117,12 @@ void PCD8544::update() { } void PCD8544::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + uint8_t fill = color.is_on() ? 0xFF : 0x00; for (uint32_t i = 0; i < this->get_buffer_length_(); i++) this->buffer_[i] = fill; diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 00425b853f..b0c39033e3 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -329,6 +329,12 @@ void HOT SSD1306::draw_absolute_pixel_internal(int x, int y, Color color) { } } void SSD1306::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + uint8_t fill = color.is_on() ? 0xFF : 0x00; for (uint32_t i = 0; i < this->get_buffer_length_(); i++) this->buffer_[i] = fill; diff --git a/esphome/components/ssd1322_base/ssd1322_base.cpp b/esphome/components/ssd1322_base/ssd1322_base.cpp index eb8d87998f..23576e7b2c 100644 --- a/esphome/components/ssd1322_base/ssd1322_base.cpp +++ b/esphome/components/ssd1322_base/ssd1322_base.cpp @@ -174,6 +174,12 @@ void HOT SSD1322::draw_absolute_pixel_internal(int x, int y, Color color) { this->buffer_[pos] |= color4; } void SSD1322::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + const uint32_t color4 = display::ColorUtil::color_to_grayscale4(color); uint8_t fill = (color4 & SSD1322_COLORMASK) | ((color4 & SSD1322_COLORMASK) << SSD1322_COLORSHIFT); for (uint32_t i = 0; i < this->get_buffer_length_(); i++) diff --git a/esphome/components/ssd1327_base/ssd1327_base.cpp b/esphome/components/ssd1327_base/ssd1327_base.cpp index 6b83ec5f9d..2498bfcd67 100644 --- a/esphome/components/ssd1327_base/ssd1327_base.cpp +++ b/esphome/components/ssd1327_base/ssd1327_base.cpp @@ -150,6 +150,12 @@ void HOT SSD1327::draw_absolute_pixel_internal(int x, int y, Color color) { this->buffer_[pos] |= color4; } void SSD1327::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + const uint32_t color4 = display::ColorUtil::color_to_grayscale4(color); uint8_t fill = (color4 & SSD1327_COLORMASK) | ((color4 & SSD1327_COLORMASK) << SSD1327_COLORSHIFT); for (uint32_t i = 0; i < this->get_buffer_length_(); i++) diff --git a/esphome/components/ssd1331_base/ssd1331_base.cpp b/esphome/components/ssd1331_base/ssd1331_base.cpp index 8ee12387e4..a2993edef3 100644 --- a/esphome/components/ssd1331_base/ssd1331_base.cpp +++ b/esphome/components/ssd1331_base/ssd1331_base.cpp @@ -128,6 +128,12 @@ void HOT SSD1331::draw_absolute_pixel_internal(int x, int y, Color color) { this->buffer_[pos] = color565 & 0xff; } void SSD1331::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + const uint32_t color565 = display::ColorUtil::color_to_565(color); for (uint32_t i = 0; i < this->get_buffer_length_(); i++) { if (i & 1) { diff --git a/esphome/components/ssd1351_base/ssd1351_base.cpp b/esphome/components/ssd1351_base/ssd1351_base.cpp index 09530e8a27..69bf67f476 100644 --- a/esphome/components/ssd1351_base/ssd1351_base.cpp +++ b/esphome/components/ssd1351_base/ssd1351_base.cpp @@ -160,6 +160,12 @@ void HOT SSD1351::draw_absolute_pixel_internal(int x, int y, Color color) { this->buffer_[pos] = color565 & 0xff; } void SSD1351::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + const uint32_t color565 = display::ColorUtil::color_to_565(color); for (uint32_t i = 0; i < this->get_buffer_length_(); i++) { if (i & 1) { diff --git a/esphome/components/st7567_base/st7567_base.cpp b/esphome/components/st7567_base/st7567_base.cpp index 0afd2a70ba..8c47094b26 100644 --- a/esphome/components/st7567_base/st7567_base.cpp +++ b/esphome/components/st7567_base/st7567_base.cpp @@ -131,7 +131,16 @@ void HOT ST7567::draw_absolute_pixel_internal(int x, int y, Color color) { } } -void ST7567::fill(Color color) { memset(buffer_, color.is_on() ? 0xFF : 0x00, this->get_buffer_length_()); } +void ST7567::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + + uint8_t fill = color.is_on() ? 0xFF : 0x00; + memset(buffer_, fill, this->get_buffer_length_()); +} void ST7567::init_reset_() { if (this->reset_pin_ != nullptr) { diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp index c7ce7140e3..afd7cd61bd 100644 --- a/esphome/components/st7920/st7920.cpp +++ b/esphome/components/st7920/st7920.cpp @@ -89,7 +89,16 @@ void HOT ST7920::write_display_data() { } } -void ST7920::fill(Color color) { memset(this->buffer_, color.is_on() ? 0xFF : 0x00, this->get_buffer_length_()); } +void ST7920::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + + uint8_t fill = color.is_on() ? 0xFF : 0x00; + memset(this->buffer_, fill, this->get_buffer_length_()); +} void ST7920::dump_config() { LOG_DISPLAY("", "ST7920", this); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 3510d157d6..9ab050395d 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -172,6 +172,12 @@ void WaveshareEPaperBase::update() { this->display(); } void WaveshareEPaper::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + // flip logic const uint8_t fill = color.is_on() ? 0x00 : 0xFF; for (uint32_t i = 0; i < this->get_buffer_length_(); i++) @@ -234,6 +240,12 @@ uint8_t WaveshareEPaper7C::color_to_hex(Color color) { return hex_code; } void WaveshareEPaper7C::fill(Color color) { + // If clipping is active, use base class (3-bit packing is complex for partial fills) + if (this->get_clipping().is_set()) { + display::Display::fill(color); + return; + } + uint8_t pixel_color; if (color.is_on()) { pixel_color = this->color_to_hex(color); From 544aaeaa6676bb34751968dc3be63a84ebd62102 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:08:57 -1000 Subject: [PATCH 0836/1145] [mipi_dsi] Use stack buffer for hex formatting in very verbose logging (#12776) --- esphome/components/mipi_dsi/mipi_dsi.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/mipi_dsi/mipi_dsi.cpp b/esphome/components/mipi_dsi/mipi_dsi.cpp index 7471aaa5c5..18cafab684 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.cpp +++ b/esphome/components/mipi_dsi/mipi_dsi.cpp @@ -1,10 +1,14 @@ #ifdef USE_ESP32_VARIANT_ESP32P4 #include #include "mipi_dsi.h" +#include "esphome/core/helpers.h" namespace esphome { namespace mipi_dsi { +// Maximum bytes to log for init commands (truncated if larger) +static constexpr size_t MIPI_DSI_MAX_CMD_LOG_BYTES = 64; + static bool notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) { auto *sem = static_cast(user_ctx); BaseType_t need_yield = pdFALSE; @@ -121,8 +125,11 @@ void MIPI_DSI::setup() { } } const auto *ptr = vec.data() + index; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(MIPI_DSI_MAX_CMD_LOG_BYTES)]; +#endif ESP_LOGVV(TAG, "Command %02X, length %d, byte(s) %s", cmd, num_args, - format_hex_pretty(ptr, num_args, '.', false).c_str()); + format_hex_pretty_to(hex_buf, ptr, num_args, '.')); err = esp_lcd_panel_io_tx_param(this->io_handle_, cmd, ptr, num_args); if (err != ESP_OK) { this->smark_failed(LOG_STR("lcd_panel_io_tx_param failed"), err); From 14e97642f77a71599459a2ffbbbdecdaef2ae7d7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:09:37 -1000 Subject: [PATCH 0837/1145] [mipi_rgb] Use stack buffer for hex formatting in init sequence logging (#12777) --- esphome/components/mipi_rgb/mipi_rgb.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/mipi_rgb/mipi_rgb.cpp b/esphome/components/mipi_rgb/mipi_rgb.cpp index c4485af8a7..ef96da8a1c 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.cpp +++ b/esphome/components/mipi_rgb/mipi_rgb.cpp @@ -1,5 +1,6 @@ #ifdef USE_ESP32_VARIANT_ESP32S3 #include "mipi_rgb.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" #include "esp_lcd_panel_rgb.h" @@ -8,6 +9,9 @@ namespace esphome { namespace mipi_rgb { static const uint8_t DELAY_FLAG = 0xFF; + +// Maximum bytes to log for init commands (truncated if larger) +static constexpr size_t MIPI_RGB_MAX_CMD_LOG_BYTES = 64; static constexpr uint8_t MADCTL_MY = 0x80; // Bit 7 Bottom to top static constexpr uint8_t MADCTL_MX = 0x40; // Bit 6 Right to left static constexpr uint8_t MADCTL_MV = 0x20; // Bit 5 Swap axes @@ -91,8 +95,9 @@ void MipiRgbSpi::write_init_sequence_() { delay(120); // NOLINT } const auto *ptr = vec.data() + index; + char hex_buf[format_hex_pretty_size(MIPI_RGB_MAX_CMD_LOG_BYTES)]; ESP_LOGD(TAG, "Write command %02X, length %d, byte(s) %s", cmd, num_args, - format_hex_pretty(ptr, num_args, '.', false).c_str()); + format_hex_pretty_to(hex_buf, ptr, num_args, '.')); index += num_args; this->write_command_(cmd); while (num_args-- != 0) From 09242815457f3035d69e68b59dd756f93fe6bfbd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:10:08 -1000 Subject: [PATCH 0838/1145] [mitsubishi] Use stack buffer for hex formatting in verbose logging (#12779) --- esphome/components/mitsubishi/mitsubishi.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index 10ab4f3b5c..d80b7aeff5 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -1,4 +1,5 @@ #include "mitsubishi.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -6,6 +7,9 @@ namespace mitsubishi { static const char *const TAG = "mitsubishi.climate"; +// IR frame size for Mitsubishi climate +static constexpr size_t MITSUBISHI_FRAME_SIZE = 18; + const uint8_t MITSUBISHI_OFF = 0x00; const uint8_t MITSUBISHI_MODE_AUTO = 0x20; @@ -388,7 +392,10 @@ bool MitsubishiClimate::on_receive(remote_base::RemoteReceiveData data) { break; } - ESP_LOGV(TAG, "Receiving: %s", format_hex_pretty(state_frame, 18).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MITSUBISHI_FRAME_SIZE)]; +#endif + ESP_LOGV(TAG, "Receiving: %s", format_hex_pretty_to(hex_buf, state_frame, MITSUBISHI_FRAME_SIZE)); this->publish_state(); return true; From b5188731f82b1000b2f385be3d37a46d7d8b2e42 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:10:45 -1000 Subject: [PATCH 0839/1145] [modbus] Use stack buffer for hex formatting in verbose logging (#12780) --- esphome/components/modbus/modbus.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 20271b4bdb..457dff4075 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -8,6 +8,9 @@ namespace modbus { static const char *const TAG = "modbus"; +// Maximum bytes to log for Modbus frames (truncated if larger) +static constexpr size_t MODBUS_MAX_LOG_BYTES = 64; + void Modbus::setup() { if (this->flow_control_pin_ != nullptr) { this->flow_control_pin_->setup(); @@ -255,7 +258,10 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address this->flow_control_pin_->digital_write(false); waiting_for_response = address; last_send_ = millis(); - ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MODBUS_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty_to(hex_buf, data.data(), data.size())); } // Helper function for lambdas @@ -276,7 +282,10 @@ void Modbus::send_raw(const std::vector &payload) { if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(false); waiting_for_response = payload[0]; - ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty(payload).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MODBUS_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty_to(hex_buf, payload.data(), payload.size())); last_send_ = millis(); } From 7df41124b287bbdf7d1791c425f3041228fa83b7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:11:53 -1000 Subject: [PATCH 0840/1145] [pn532_spi] Replace format_hex_pretty with stack-based format_hex_pretty_to (#12782) --- esphome/components/pn532_spi/pn532_spi.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/esphome/components/pn532_spi/pn532_spi.cpp b/esphome/components/pn532_spi/pn532_spi.cpp index 0871f7acab..118421c47f 100644 --- a/esphome/components/pn532_spi/pn532_spi.cpp +++ b/esphome/components/pn532_spi/pn532_spi.cpp @@ -1,4 +1,5 @@ #include "pn532_spi.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" // Based on: @@ -11,6 +12,9 @@ namespace pn532_spi { static const char *const TAG = "pn532_spi"; +// Maximum bytes to log in verbose hex output +static constexpr size_t PN532_MAX_LOG_BYTES = 64; + void PN532Spi::setup() { this->spi_setup(); @@ -32,7 +36,10 @@ bool PN532Spi::write_data(const std::vector &data) { delay(2); // First byte, communication mode: Write data this->write_byte(0x01); - ESP_LOGV(TAG, "Writing data: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(PN532_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Writing data: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size())); this->write_array(data.data(), data.size()); this->disable(); @@ -55,7 +62,10 @@ bool PN532Spi::read_data(std::vector &data, uint8_t len) { this->read_array(data.data(), len); this->disable(); data.insert(data.begin(), 0x01); - ESP_LOGV(TAG, "Read data: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(PN532_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Read data: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size())); return true; } @@ -73,7 +83,10 @@ bool PN532Spi::read_response(uint8_t command, std::vector &data) { std::vector header(7); this->read_array(header.data(), 7); - ESP_LOGV(TAG, "Header data: %s", format_hex_pretty(header).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(PN532_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Header data: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), header.data(), header.size())); if (header[0] != 0x00 && header[1] != 0x00 && header[2] != 0xFF) { // invalid packet @@ -103,7 +116,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector &data) { this->read_array(data.data(), len + 1); this->disable(); - ESP_LOGV(TAG, "Response data: %s", format_hex_pretty(data).c_str()); + ESP_LOGV(TAG, "Response data: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size())); uint8_t checksum = header[5] + header[6]; // TFI + Command response code for (int i = 0; i < len - 1; i++) { From c81ce243cc02881fbed9b553719de5554e3f8872 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:13:10 -1000 Subject: [PATCH 0841/1145] [qspi_dbi] Replace format_hex_pretty with stack-based format_hex_pretty_to (#12783) --- esphome/components/qspi_dbi/qspi_dbi.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/qspi_dbi/qspi_dbi.cpp b/esphome/components/qspi_dbi/qspi_dbi.cpp index 24b9a0ce0a..00a4a375eb 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.cpp +++ b/esphome/components/qspi_dbi/qspi_dbi.cpp @@ -1,10 +1,14 @@ #if defined(USE_ESP32) && defined(USE_ESP32_VARIANT_ESP32S3) #include "qspi_dbi.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { namespace qspi_dbi { +// Maximum bytes to log in verbose hex output +static constexpr size_t QSPI_DBI_MAX_LOG_BYTES = 64; + void QspiDbi::setup() { this->spi_setup(); if (this->enable_pin_ != nullptr) { @@ -174,7 +178,11 @@ void QspiDbi::write_to_display_(int x_start, int y_start, int w, int h, const ui this->disable(); } void QspiDbi::write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) { - ESP_LOGV(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty(bytes, len).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(QSPI_DBI_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Command %02X, length %d, bytes %s", cmd, len, + format_hex_pretty_to(hex_buf, sizeof(hex_buf), bytes, len)); this->enable(); this->write_cmd_addr_data(8, 0x02, 24, cmd << 8, bytes, len); this->disable(); From 4fcd263ea85e9cade892b556f6cd00b1dbfe5597 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:16:40 -1000 Subject: [PATCH 0842/1145] [seeed_mr60bha2] Replace format_hex_pretty with stack-based format_hex_pretty_to (#12784) --- .../seeed_mr60bha2/seeed_mr60bha2.cpp | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp index c815c98419..b9ce1f9151 100644 --- a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp +++ b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp @@ -10,6 +10,9 @@ namespace seeed_mr60bha2 { static const char *const TAG = "seeed_mr60bha2"; +// Maximum bytes to log in verbose hex output +static constexpr size_t MR60BHA2_MAX_LOG_BYTES = 64; + // Prints the component's configuration data. dump_config() prints all of the component's configuration // items in an easy-to-read format, including the configuration key-value pairs. void MR60BHA2Component::dump_config() { @@ -110,7 +113,10 @@ bool MR60BHA2Component::validate_message_() { if (at == 7) { if (!validate_checksum(data, 7, header_checksum)) { ESP_LOGE(TAG, "HEAD_CKSUM_FRAME ERROR: 0x%02x", header_checksum); - ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MR60BHA2_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data, 8)); return false; } return true; @@ -125,14 +131,22 @@ bool MR60BHA2Component::validate_message_() { if (at == 8 + length) { if (!validate_checksum(data + 8, length, data_checksum)) { ESP_LOGE(TAG, "DATA_CKSUM_FRAME ERROR: 0x%02x", data_checksum); - ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8 + length).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MR60BHA2_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data, 8 + length)); return false; } } const uint8_t *frame_data = data + 8; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf1[format_hex_pretty_size(MR60BHA2_MAX_LOG_BYTES)]; + char hex_buf2[format_hex_pretty_size(MR60BHA2_MAX_LOG_BYTES)]; +#endif ESP_LOGV(TAG, "Received Frame: ID: 0x%04x, Type: 0x%04x, Data: [%s] Raw Data: [%s]", frame_id, frame_type, - format_hex_pretty(frame_data, length).c_str(), format_hex_pretty(this->rx_message_).c_str()); + format_hex_pretty_to(hex_buf1, sizeof(hex_buf1), frame_data, length), + format_hex_pretty_to(hex_buf2, sizeof(hex_buf2), this->rx_message_.data(), this->rx_message_.size())); this->process_frame_(frame_id, frame_type, data + 8, length); // Return false to reset rx buffer From e1788bba45304ba9af8e3a714cfb5cf354f70a4e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:17:22 -1000 Subject: [PATCH 0843/1145] [seeed_mr60fda2] Use stack-based format_hex_pretty_to for verbose logging (#12785) --- .../seeed_mr60fda2/seeed_mr60fda2.cpp | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp index 7f8bd6a43c..b5b5b4d05a 100644 --- a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp +++ b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp @@ -10,6 +10,9 @@ namespace seeed_mr60fda2 { static const char *const TAG = "seeed_mr60fda2"; +// Maximum bytes to log in verbose hex output +static constexpr size_t MR60FDA2_MAX_LOG_BYTES = 64; + // Prints the component's configuration data. dump_config() prints all of the component's configuration // items in an easy-to-read format, including the configuration key-value pairs. void MR60FDA2Component::dump_config() { @@ -202,9 +205,13 @@ void MR60FDA2Component::split_frame_(uint8_t buffer) { this->current_frame_locate_++; } else { ESP_LOGD(TAG, "HEAD_CKSUM_FRAME ERROR: 0x%02x", buffer); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char frame_buf[format_hex_pretty_size(MR60FDA2_MAX_LOG_BYTES)]; + char byte_buf[format_hex_pretty_size(1)]; +#endif ESP_LOGV(TAG, "CURRENT_FRAME: %s %s", - format_hex_pretty(this->current_frame_buf_, this->current_frame_len_).c_str(), - format_hex_pretty(&buffer, 1).c_str()); + format_hex_pretty_to(frame_buf, this->current_frame_buf_, this->current_frame_len_), + format_hex_pretty_to(byte_buf, &buffer, 1)); this->current_frame_locate_ = LOCATE_FRAME_HEADER; } break; @@ -228,9 +235,13 @@ void MR60FDA2Component::split_frame_(uint8_t buffer) { this->process_frame_(); } else { ESP_LOGD(TAG, "DATA_CKSUM_FRAME ERROR: 0x%02x", buffer); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char frame_buf[format_hex_pretty_size(MR60FDA2_MAX_LOG_BYTES)]; + char byte_buf[format_hex_pretty_size(1)]; +#endif ESP_LOGV(TAG, "GET CURRENT_FRAME: %s %s", - format_hex_pretty(this->current_frame_buf_, this->current_frame_len_).c_str(), - format_hex_pretty(&buffer, 1).c_str()); + format_hex_pretty_to(frame_buf, this->current_frame_buf_, this->current_frame_len_), + format_hex_pretty_to(byte_buf, &buffer, 1)); this->current_frame_locate_ = LOCATE_FRAME_HEADER; } @@ -328,7 +339,10 @@ void MR60FDA2Component::set_install_height(uint8_t index) { float_to_bytes(INSTALL_HEIGHT[index], &send_data[8]); send_data[12] = calculate_checksum(send_data + 8, 4); this->write_array(send_data, 13); - ESP_LOGV(TAG, "SEND INSTALL HEIGHT FRAME: %s", format_hex_pretty(send_data, 13).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(13)]; +#endif + ESP_LOGV(TAG, "SEND INSTALL HEIGHT FRAME: %s", format_hex_pretty_to(hex_buf, send_data, 13)); } void MR60FDA2Component::set_height_threshold(uint8_t index) { @@ -336,7 +350,10 @@ void MR60FDA2Component::set_height_threshold(uint8_t index) { float_to_bytes(HEIGHT_THRESHOLD[index], &send_data[8]); send_data[12] = calculate_checksum(send_data + 8, 4); this->write_array(send_data, 13); - ESP_LOGV(TAG, "SEND HEIGHT THRESHOLD: %s", format_hex_pretty(send_data, 13).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(13)]; +#endif + ESP_LOGV(TAG, "SEND HEIGHT THRESHOLD: %s", format_hex_pretty_to(hex_buf, send_data, 13)); } void MR60FDA2Component::set_sensitivity(uint8_t index) { @@ -346,19 +363,28 @@ void MR60FDA2Component::set_sensitivity(uint8_t index) { send_data[12] = calculate_checksum(send_data + 8, 4); this->write_array(send_data, 13); - ESP_LOGV(TAG, "SEND SET SENSITIVITY: %s", format_hex_pretty(send_data, 13).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(13)]; +#endif + ESP_LOGV(TAG, "SEND SET SENSITIVITY: %s", format_hex_pretty_to(hex_buf, send_data, 13)); } void MR60FDA2Component::get_radar_parameters() { uint8_t send_data[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x06, 0xF6}; this->write_array(send_data, 8); - ESP_LOGV(TAG, "SEND GET PARAMETERS: %s", format_hex_pretty(send_data, 8).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(8)]; +#endif + ESP_LOGV(TAG, "SEND GET PARAMETERS: %s", format_hex_pretty_to(hex_buf, send_data, 8)); } void MR60FDA2Component::factory_reset() { uint8_t send_data[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x21, 0x10, 0xCF}; this->write_array(send_data, 8); - ESP_LOGV(TAG, "SEND RESET: %s", format_hex_pretty(send_data, 8).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(8)]; +#endif + ESP_LOGV(TAG, "SEND RESET: %s", format_hex_pretty_to(hex_buf, send_data, 8)); this->get_radar_parameters(); } From 0049c8ad38be45e0b04459e7098effaa3fd92bc4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:17:51 -1000 Subject: [PATCH 0844/1145] [zwave_proxy] Use stack-based format_hex_pretty_to for very verbose logging (#12786) --- esphome/components/zwave_proxy/zwave_proxy.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp index e4efa55e25..c1fde4de6b 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.cpp +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -12,6 +12,9 @@ namespace esphome::zwave_proxy { static const char *const TAG = "zwave_proxy"; +// Maximum bytes to log in very verbose hex output (168 * 3 = 504, under TX buffer size of 512) +static constexpr size_t ZWAVE_MAX_LOG_BYTES = 168; + static constexpr uint8_t ZWAVE_COMMAND_GET_NETWORK_IDS = 0x20; // GET_NETWORK_IDS response: [SOF][LENGTH][TYPE][CMD][HOME_ID(4)][NODE_ID][...] static constexpr uint8_t ZWAVE_COMMAND_TYPE_RESPONSE = 0x01; // Response type field value @@ -179,7 +182,10 @@ void ZWaveProxy::send_frame(const uint8_t *data, size_t length) { ESP_LOGV(TAG, "Skipping sending duplicate response: 0x%02X", data[0]); return; } - ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty(data, length).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(ZWAVE_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty_to(hex_buf, data, length)); this->write_array(data, length); } @@ -252,7 +258,10 @@ bool ZWaveProxy::parse_byte_(uint8_t byte) { this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_NAK; } else { this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_ACK; - ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(this->buffer_.data(), this->buffer_index_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(ZWAVE_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_index_)); frame_completed = true; } this->response_handler_(); From c6f3860f90e655fbd1518b4a6cf558b8a4a62a57 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:18:23 -1000 Subject: [PATCH 0845/1145] [ee895] Use stack-based format_hex_to for verbose logging (#12789) --- esphome/components/ee895/ee895.cpp | 8 +++++++- esphome/core/helpers.h | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/esphome/components/ee895/ee895.cpp b/esphome/components/ee895/ee895.cpp index c6eaf4e728..602e31db14 100644 --- a/esphome/components/ee895/ee895.cpp +++ b/esphome/components/ee895/ee895.cpp @@ -7,6 +7,9 @@ namespace ee895 { static const char *const TAG = "ee895"; +// Serial number is 16 bytes +static constexpr size_t EE895_SERIAL_NUMBER_SIZE = 16; + static const uint16_t CRC16_ONEWIRE_START = 0xFFFF; static const uint8_t FUNCTION_CODE_READ = 0x03; static const uint16_t SERIAL_NUMBER = 0x0000; @@ -26,7 +29,10 @@ void EE895Component::setup() { this->mark_failed(); return; } - ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(serial_number + 2, 16).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char serial_hex[format_hex_size(EE895_SERIAL_NUMBER_SIZE)]; +#endif + ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex_to(serial_hex, serial_number + 2, EE895_SERIAL_NUMBER_SIZE)); } void EE895Component::dump_config() { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 37534849d0..ac7a96a8c8 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -728,6 +728,9 @@ inline char *format_hex_to(char (&buffer)[N], T val) { return format_hex_to(buffer, reinterpret_cast(&val), sizeof(T)); } +/// Calculate buffer size needed for format_hex_to: "XXXXXXXX...\0" = bytes * 2 + 1 +constexpr size_t format_hex_size(size_t byte_count) { return byte_count * 2 + 1; } + /// Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0" constexpr size_t format_hex_pretty_size(size_t byte_count) { return byte_count * 3; } From 71c3d4ca27e00bffc9ce4349dd6b567b8c7f3186 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:19:20 -1000 Subject: [PATCH 0846/1145] [mopeka_std_check] Use stack-based format_hex_pretty_to for very verbose logging (#12790) --- esphome/components/mopeka_std_check/mopeka_std_check.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/mopeka_std_check/mopeka_std_check.cpp b/esphome/components/mopeka_std_check/mopeka_std_check.cpp index 0d8340f95f..986a9a9fdc 100644 --- a/esphome/components/mopeka_std_check/mopeka_std_check.cpp +++ b/esphome/components/mopeka_std_check/mopeka_std_check.cpp @@ -13,6 +13,9 @@ static const uint16_t SERVICE_UUID = 0xADA0; static const uint8_t MANUFACTURER_DATA_LENGTH = 23; static const uint16_t MANUFACTURER_ID = 0x000D; +// Maximum bytes to log in very verbose hex output +static constexpr size_t MOPEKA_MAX_LOG_BYTES = 32; + void MopekaStdCheck::dump_config() { ESP_LOGCONFIG(TAG, "Mopeka Std Check"); ESP_LOGCONFIG(TAG, " Propane Butane mix: %.0f%%", this->propane_butane_mix_ * 100); @@ -60,7 +63,11 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const auto &manu_data = manu_datas[0]; - ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", device.address_str().c_str(), format_hex_pretty(manu_data.data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(MOPEKA_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", device.address_str().c_str(), + format_hex_pretty_to(hex_buf, manu_data.data.data(), manu_data.data.size())); if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) { ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", device.address_str().c_str(), manu_data.data.size()); From bcc6bbbf5f0bc7671f36d62d98da3a199c08becd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:19:49 -1000 Subject: [PATCH 0847/1145] [espnow] Use stack buffer for hex formatting in verbose logging (#12738) --- esphome/components/espnow/espnow_component.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/espnow/espnow_component.cpp b/esphome/components/espnow/espnow_component.cpp index bc05833709..16e2331937 100644 --- a/esphome/components/espnow/espnow_component.cpp +++ b/esphome/components/espnow/espnow_component.cpp @@ -6,6 +6,7 @@ #include "esphome/core/application.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include @@ -299,9 +300,10 @@ void ESPNowComponent::loop() { // Intentionally left as if instead of else in case the peer is added above if (esp_now_is_peer_exist(info.src_addr)) { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(ESP_NOW_MAX_DATA_LEN)]; ESP_LOGV(TAG, "<<< [%s -> %s] %s", format_mac_address_pretty(info.src_addr).c_str(), format_mac_address_pretty(info.des_addr).c_str(), - format_hex_pretty(packet->packet_.receive.data, packet->packet_.receive.size).c_str()); + format_hex_pretty_to(hex_buf, packet->packet_.receive.data, packet->packet_.receive.size)); #endif if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) { for (auto *handler : this->broadcasted_handlers_) { From 1cc18055ef7903620933b9e3a18a4227fc3f697b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:20:24 -1000 Subject: [PATCH 0848/1145] [i2c] Use stack buffer for hex formatting in verbose logging (#12739) --- esphome/components/i2c/i2c_bus_arduino.cpp | 8 +++++++- esphome/components/i2c/i2c_bus_esp_idf.cpp | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 1579020c9b..e728830147 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -12,6 +12,9 @@ namespace i2c { static const char *const TAG = "i2c.arduino"; +// Maximum bytes to log in hex format (truncates larger transfers) +static constexpr size_t I2C_MAX_LOG_BYTES = 32; + void ArduinoI2CBus::setup() { recover_(); @@ -107,7 +110,10 @@ ErrorCode ArduinoI2CBus::write_readv(uint8_t address, const uint8_t *write_buffe return ERROR_NOT_INITIALIZED; } - ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty(write_buffer, write_count).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(I2C_MAX_LOG_BYTES)]; + ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty_to(hex_buf, write_buffer, write_count)); +#endif uint8_t status = 0; if (write_count != 0 || read_count == 0) { diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 486dc0b7d8..191c849aa3 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -15,6 +15,9 @@ namespace i2c { static const char *const TAG = "i2c.idf"; +// Maximum bytes to log in hex format (truncates larger transfers) +static constexpr size_t I2C_MAX_LOG_BYTES = 32; + void IDFI2CBus::setup() { static i2c_port_t next_hp_port = I2C_NUM_0; #if SOC_LP_I2C_SUPPORTED @@ -147,7 +150,10 @@ ErrorCode IDFI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, s jobs[num_jobs++].write.total_bytes = 1; } else { if (write_count != 0) { - ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty(write_buffer, write_count).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(I2C_MAX_LOG_BYTES)]; + ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty_to(hex_buf, write_buffer, write_count)); +#endif jobs[num_jobs++].command = I2C_MASTER_CMD_START; jobs[num_jobs].command = I2C_MASTER_CMD_WRITE; jobs[num_jobs].write.ack_check = true; From 69ec311d212a6d19d654cb2a017385b3cdf8d74b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:20:58 -1000 Subject: [PATCH 0849/1145] [hlk_fm22x] Use stack buffer for hex formatting in verbose logging (#12740) --- esphome/components/hlk_fm22x/hlk_fm22x.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/hlk_fm22x/hlk_fm22x.cpp b/esphome/components/hlk_fm22x/hlk_fm22x.cpp index ab15a2340d..c0f14c7105 100644 --- a/esphome/components/hlk_fm22x/hlk_fm22x.cpp +++ b/esphome/components/hlk_fm22x/hlk_fm22x.cpp @@ -8,6 +8,9 @@ namespace esphome::hlk_fm22x { static const char *const TAG = "hlk_fm22x"; +// Maximum response size is 36 bytes (VERIFY reply: face_id + 32-byte name) +static constexpr size_t HLK_FM22X_MAX_RESPONSE_SIZE = 36; + void HlkFm22xComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up HLK-FM22X..."); this->set_enrolling_(false); @@ -142,7 +145,10 @@ void HlkFm22xComponent::recv_command_() { data.push_back(byte); } - ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type, format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(HLK_FM22X_MAX_RESPONSE_SIZE)]; + ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type, format_hex_pretty_to(hex_buf, data.data(), data.size())); +#endif byte = this->read(); if (byte != checksum) { From 2e8baa04936cd08f6a963f6f254a0d8506618986 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:21:33 -1000 Subject: [PATCH 0850/1145] [esp32_ble_tracker] Use stack buffer for hex formatting in very verbose logging (#12741) --- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 47da2e3570..63675ec377 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -37,6 +37,9 @@ namespace esphome::esp32_ble_tracker { static const char *const TAG = "esp32_ble_tracker"; +// BLE advertisement max: 31 bytes adv data + 31 bytes scan response +static constexpr size_t BLE_ADV_MAX_LOG_BYTES = 62; + ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) const char *client_state_to_string(ClientState state) { @@ -445,6 +448,7 @@ void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) { uuid.to_str(uuid_buf); ESP_LOGVV(TAG, " Service UUID: %s", uuid_buf); } + char hex_buf[format_hex_pretty_size(BLE_ADV_MAX_LOG_BYTES)]; for (auto &data : this->manufacturer_datas_) { auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(data); if (ibeacon.has_value()) { @@ -458,7 +462,8 @@ void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) { } else { char uuid_buf[esp32_ble::UUID_STR_LEN]; data.uuid.to_str(uuid_buf); - ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", uuid_buf, format_hex_pretty(data.data).c_str()); + ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", uuid_buf, + format_hex_pretty_to(hex_buf, data.data.data(), data.data.size())); } } for (auto &data : this->service_datas_) { @@ -466,11 +471,11 @@ void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) { char uuid_buf[esp32_ble::UUID_STR_LEN]; data.uuid.to_str(uuid_buf); ESP_LOGVV(TAG, " UUID: %s", uuid_buf); - ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str()); + ESP_LOGVV(TAG, " Data: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size())); } ESP_LOGVV(TAG, " Adv data: %s", - format_hex_pretty(scan_result.ble_adv, scan_result.adv_data_len + scan_result.scan_rsp_len).c_str()); + format_hex_pretty_to(hex_buf, scan_result.ble_adv, scan_result.adv_data_len + scan_result.scan_rsp_len)); #endif } From 7702a9ae8552eb9bdfbbb00f6ba63a2c6e20a5d9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:22:19 -1000 Subject: [PATCH 0851/1145] [ethernet] Use stack buffer for hex formatting in very verbose logging (#12742) --- esphome/components/ethernet/ethernet_component.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 114000401f..af4f652d8b 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -1,5 +1,6 @@ #include "ethernet_component.h" #include "esphome/core/application.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/util.h" @@ -39,6 +40,9 @@ namespace ethernet { static const char *const TAG = "ethernet"; +// PHY register size for hex logging +static constexpr size_t PHY_REG_SIZE = 2; + EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void EthernetComponent::log_error_and_mark_failed_(esp_err_t err, const char *message) { @@ -773,7 +777,10 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { uint32_t phy_control_2; err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2)); ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed"); - ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(PHY_REG_SIZE)]; +#endif + ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE)); /* * Bit 7 is `RMII Reference Clock Select`. Default is `0`. @@ -790,7 +797,8 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed"); err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2)); ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed"); - ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str()); + ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", + format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE)); } } #endif // USE_ETHERNET_KSZ8081 From 3a4cca002735c75f38252b40ade21e464f71235d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:22:48 -1000 Subject: [PATCH 0852/1145] [ble_client] Use stack buffer for hex formatting in very verbose logging (#12744) --- esphome/components/ble_client/automation.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index ccda894509..f9f613ae76 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -7,8 +7,12 @@ #include "esphome/core/automation.h" #include "esphome/components/ble_client/ble_client.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" +// Maximum bytes to log in hex format for BLE writes (many logging buffers are 256 chars) +static constexpr size_t BLE_WRITE_MAX_LOG_BYTES = 64; + namespace esphome::ble_client { // placeholder class for static TAG . @@ -151,7 +155,10 @@ template class BLEClientWriteAction : public Action, publ esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected"); return false; } - esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty(data, len).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(BLE_WRITE_MAX_LOG_BYTES)]; + esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty_to(hex_buf, data, len)); +#endif esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len, const_cast(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE); From ebfa0149cc343131c86fea0ffde11c3542fd3e72 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:23:37 -1000 Subject: [PATCH 0853/1145] [light] Use StringRef to avoid allocation in JSON effect name serialization (#12758) --- esphome/components/light/light_json_schema.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 3365d1f417..7679002e74 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -36,7 +36,7 @@ static const char *get_color_mode_json_str(ColorMode mode) { void LightJSONSchema::dump_json(LightState &state, JsonObject root) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (state.supports_effects()) { - root[ESPHOME_F("effect")] = state.get_effect_name(); + root[ESPHOME_F("effect")] = state.get_effect_name_ref(); root[ESPHOME_F("effect_index")] = state.get_current_effect_index(); root[ESPHOME_F("effect_count")] = state.get_effect_count(); } From a828abf53dcf5b731eecf776451c40f93e68e8e0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:24:31 -1000 Subject: [PATCH 0854/1145] [ota] Remove MD5 authentication support (#12707) --- esphome/components/esphome/ota/__init__.py | 19 +-- .../components/esphome/ota/ota_esphome.cpp | 150 +++--------------- esphome/components/esphome/ota/ota_esphome.h | 2 +- esphome/core/defines.h | 3 - 4 files changed, 24 insertions(+), 150 deletions(-) diff --git a/esphome/components/esphome/ota/__init__.py b/esphome/components/esphome/ota/__init__.py index e56e85b231..2f637d714d 100644 --- a/esphome/components/esphome/ota/__init__.py +++ b/esphome/components/esphome/ota/__init__.py @@ -16,7 +16,7 @@ from esphome.const import ( CONF_SAFE_MODE, CONF_VERSION, ) -from esphome.core import CORE, coroutine_with_priority +from esphome.core import coroutine_with_priority from esphome.coroutine import CoroPriority import esphome.final_validate as fv from esphome.types import ConfigType @@ -28,17 +28,7 @@ CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] -def supports_sha256() -> bool: - """Check if the current platform supports SHA256 for OTA authentication.""" - return bool(CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny) - - -def AUTO_LOAD() -> list[str]: - """Conditionally auto-load sha256 only on platforms that support it.""" - base_components = ["md5", "socket"] - if supports_sha256(): - return base_components + ["sha256"] - return base_components +AUTO_LOAD = ["sha256", "socket"] esphome = cg.esphome_ns.namespace("esphome") @@ -155,11 +145,6 @@ async def to_code(config: ConfigType) -> None: if config.get(CONF_PASSWORD): cg.add(var.set_auth_password(config[CONF_PASSWORD])) cg.add_define("USE_OTA_PASSWORD") - # Only include hash algorithms when password is configured - cg.add_define("USE_OTA_MD5") - # Only include SHA256 support on platforms that have it - if supports_sha256(): - cg.add_define("USE_OTA_SHA256") cg.add_define("USE_OTA_VERSION", config[CONF_VERSION]) await cg.register_component(var, config) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 98569c96cb..16d7089f02 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -1,13 +1,8 @@ #include "ota_esphome.h" #ifdef USE_OTA #ifdef USE_OTA_PASSWORD -#ifdef USE_OTA_MD5 -#include "esphome/components/md5/md5.h" -#endif -#ifdef USE_OTA_SHA256 #include "esphome/components/sha256/sha256.h" #endif -#endif #include "esphome/components/network/util.h" #include "esphome/components/ota/ota_backend.h" #include "esphome/components/ota/ota_backend_esp8266.h" @@ -31,15 +26,6 @@ static constexpr size_t OTA_BUFFER_SIZE = 1024; // buffer size static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 20000; // milliseconds for initial handshake static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer -#ifdef USE_OTA_PASSWORD -#ifdef USE_OTA_MD5 -static constexpr size_t MD5_HEX_SIZE = 32; // MD5 hash as hex string (16 bytes * 2) -#endif -#ifdef USE_OTA_SHA256 -static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2) -#endif -#endif // USE_OTA_PASSWORD - void ESPHomeOTAComponent::setup() { this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections if (this->server_ == nullptr) { @@ -108,15 +94,7 @@ void ESPHomeOTAComponent::loop() { } static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01; -#ifdef USE_OTA_SHA256 static const uint8_t FEATURE_SUPPORTS_SHA256_AUTH = 0x02; -#endif - -// Temporary flag to allow MD5 downgrade for ~3 versions (until 2026.1.0) -// This allows users to downgrade via OTA if they encounter issues after updating. -// Without this, users would need to do a serial flash to downgrade. -// TODO: Remove this flag and all associated code in 2026.1.0 -#define ALLOW_OTA_DOWNGRADE_MD5 void ESPHomeOTAComponent::handle_handshake_() { /// Handle the OTA handshake and authentication. @@ -547,26 +525,8 @@ void ESPHomeOTAComponent::yield_and_feed_watchdog_() { void ESPHomeOTAComponent::log_auth_warning_(const LogString *msg) { ESP_LOGW(TAG, "Auth: %s", LOG_STR_ARG(msg)); } bool ESPHomeOTAComponent::select_auth_type_() { -#ifdef USE_OTA_SHA256 bool client_supports_sha256 = (this->ota_features_ & FEATURE_SUPPORTS_SHA256_AUTH) != 0; -#ifdef ALLOW_OTA_DOWNGRADE_MD5 - // Allow fallback to MD5 if client doesn't support SHA256 - if (client_supports_sha256) { - this->auth_type_ = ota::OTA_RESPONSE_REQUEST_SHA256_AUTH; - return true; - } -#ifdef USE_OTA_MD5 - this->log_auth_warning_(LOG_STR("Using deprecated MD5")); - this->auth_type_ = ota::OTA_RESPONSE_REQUEST_AUTH; - return true; -#else - this->log_auth_warning_(LOG_STR("SHA256 required")); - this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID); - return false; -#endif // USE_OTA_MD5 - -#else // !ALLOW_OTA_DOWNGRADE_MD5 // Require SHA256 if (!client_supports_sha256) { this->log_auth_warning_(LOG_STR("SHA256 required")); @@ -575,20 +535,6 @@ bool ESPHomeOTAComponent::select_auth_type_() { } this->auth_type_ = ota::OTA_RESPONSE_REQUEST_SHA256_AUTH; return true; -#endif // ALLOW_OTA_DOWNGRADE_MD5 - -#else // !USE_OTA_SHA256 -#ifdef USE_OTA_MD5 - // Only MD5 available - this->auth_type_ = ota::OTA_RESPONSE_REQUEST_AUTH; - return true; -#else - // No auth methods available - this->log_auth_warning_(LOG_STR("No auth methods available")); - this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID); - return false; -#endif // USE_OTA_MD5 -#endif // USE_OTA_SHA256 } bool ESPHomeOTAComponent::handle_auth_send_() { @@ -612,31 +558,12 @@ bool ESPHomeOTAComponent::handle_auth_send_() { // [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce // [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash - // Declare both hash objects in same stack frame, use pointer to select. - // NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3 - // hardware SHA acceleration - the object must exist in this stack frame for all operations. - // Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope. -#ifdef USE_OTA_SHA256 - sha256::SHA256 sha_hasher; -#endif -#ifdef USE_OTA_MD5 - md5::MD5Digest md5_hasher; -#endif - HashBase *hasher = nullptr; + // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame + // (no passing to other functions). All hash operations must happen in this function. + sha256::SHA256 hasher; -#ifdef USE_OTA_SHA256 - if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) { - hasher = &sha_hasher; - } -#endif -#ifdef USE_OTA_MD5 - if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) { - hasher = &md5_hasher; - } -#endif - - const size_t hex_size = hasher->get_size() * 2; - const size_t nonce_len = hasher->get_size() / 4; + const size_t hex_size = hasher.get_size() * 2; + const size_t nonce_len = hasher.get_size() / 4; const size_t auth_buf_size = 1 + 3 * hex_size; this->auth_buf_ = std::make_unique(auth_buf_size); this->auth_buf_pos_ = 0; @@ -648,17 +575,17 @@ bool ESPHomeOTAComponent::handle_auth_send_() { return false; } - hasher->init(); - hasher->add(buf, nonce_len); - hasher->calculate(); + hasher.init(); + hasher.add(buf, nonce_len); + hasher.calculate(); this->auth_buf_[0] = this->auth_type_; - hasher->get_hex(buf); + hasher.get_hex(buf); ESP_LOGV(TAG, "Auth: Nonce is %.*s", hex_size, buf); } // Try to write auth_type + nonce - size_t hex_size = this->get_auth_hex_size_(); + constexpr size_t hex_size = SHA256_HEX_SIZE; const size_t to_write = 1 + hex_size; size_t remaining = to_write - this->auth_buf_pos_; @@ -680,7 +607,7 @@ bool ESPHomeOTAComponent::handle_auth_send_() { } bool ESPHomeOTAComponent::handle_auth_read_() { - size_t hex_size = this->get_auth_hex_size_(); + constexpr size_t hex_size = SHA256_HEX_SIZE; const size_t to_read = hex_size * 2; // CNonce + Response // Try to read remaining bytes (CNonce + Response) @@ -705,45 +632,25 @@ bool ESPHomeOTAComponent::handle_auth_read_() { const char *cnonce = nonce + hex_size; const char *response = cnonce + hex_size; - // CRITICAL ESP32-S3: Hash objects must stay in same stack frame (no passing to other functions). - // Declare both hash objects in same stack frame, use pointer to select. - // NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3 - // hardware SHA acceleration - the object must exist in this stack frame for all operations. - // Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope. -#ifdef USE_OTA_SHA256 - sha256::SHA256 sha_hasher; -#endif -#ifdef USE_OTA_MD5 - md5::MD5Digest md5_hasher; -#endif - HashBase *hasher = nullptr; + // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame + // (no passing to other functions). All hash operations must happen in this function. + sha256::SHA256 hasher; -#ifdef USE_OTA_SHA256 - if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) { - hasher = &sha_hasher; - } -#endif -#ifdef USE_OTA_MD5 - if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) { - hasher = &md5_hasher; - } -#endif - - hasher->init(); - hasher->add(this->password_.c_str(), this->password_.length()); - hasher->add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer) - hasher->calculate(); + hasher.init(); + hasher.add(this->password_.c_str(), this->password_.length()); + hasher.add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer) + hasher.calculate(); ESP_LOGV(TAG, "Auth: CNonce is %.*s", hex_size, cnonce); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE - char computed_hash[65]; // Buffer for hex-encoded hash (max expected length + null terminator) - hasher->get_hex(computed_hash); + char computed_hash[SHA256_HEX_SIZE + 1]; // Buffer for hex-encoded hash (max expected length + null terminator) + hasher.get_hex(computed_hash); ESP_LOGV(TAG, "Auth: Result is %.*s", hex_size, computed_hash); #endif ESP_LOGV(TAG, "Auth: Response is %.*s", hex_size, response); // Compare response - bool matches = hasher->equals_hex(response); + bool matches = hasher.equals_hex(response); if (!matches) { this->log_auth_warning_(LOG_STR("Password mismatch")); @@ -757,21 +664,6 @@ bool ESPHomeOTAComponent::handle_auth_read_() { return true; } -size_t ESPHomeOTAComponent::get_auth_hex_size_() const { -#ifdef USE_OTA_SHA256 - if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) { - return SHA256_HEX_SIZE; - } -#endif -#ifdef USE_OTA_MD5 - return MD5_HEX_SIZE; -#else -#ifndef USE_OTA_SHA256 -#error "Either USE_OTA_MD5 or USE_OTA_SHA256 must be defined when USE_OTA_PASSWORD is enabled" -#endif -#endif -} - void ESPHomeOTAComponent::cleanup_auth_() { this->auth_buf_ = nullptr; this->auth_buf_pos_ = 0; diff --git a/esphome/components/esphome/ota/ota_esphome.h b/esphome/components/esphome/ota/ota_esphome.h index 4412a65757..e199b7e406 100644 --- a/esphome/components/esphome/ota/ota_esphome.h +++ b/esphome/components/esphome/ota/ota_esphome.h @@ -44,10 +44,10 @@ class ESPHomeOTAComponent : public ota::OTAComponent { void handle_handshake_(); void handle_data_(); #ifdef USE_OTA_PASSWORD + static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2) bool handle_auth_send_(); bool handle_auth_read_(); bool select_auth_type_(); - size_t get_auth_hex_size_() const; void cleanup_auth_(); void log_auth_warning_(const LogString *msg); #endif // USE_OTA_PASSWORD diff --git a/esphome/core/defines.h b/esphome/core/defines.h index be429a9784..579edc065a 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -144,10 +144,7 @@ #define USE_ONLINE_IMAGE_PNG_SUPPORT #define USE_ONLINE_IMAGE_JPEG_SUPPORT #define USE_OTA -#define USE_OTA_MD5 #define USE_OTA_PASSWORD -#define USE_OTA_SHA256 -#define ALLOW_OTA_DOWNGRADE_MD5 #define USE_OTA_STATE_LISTENER #define USE_OTA_VERSION 2 #define USE_TIME_TIMEZONE From 4e8c02b39673ae962d5f775ba773426abf6c9efc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 20:25:12 -1000 Subject: [PATCH 0855/1145] [xiaomi_*] Use stack-based hex formatting for bindkey logging (#12798) --- esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp | 6 +++++- esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp | 6 +++++- esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp | 6 +++++- esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp | 6 +++++- esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp | 6 +++++- esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp | 6 +++++- esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp | 6 +++++- .../components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp | 6 +++++- 8 files changed, 40 insertions(+), 8 deletions(-) diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index 4642768f90..d7f1ec3782 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -1,4 +1,5 @@ #include "xiaomi_cgd1.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,11 +9,14 @@ namespace xiaomi_cgd1 { static const char *const TAG = "xiaomi_cgd1"; +static constexpr size_t CGD1_BINDKEY_SIZE = 16; + void XiaomiCGD1::dump_config() { + char bindkey_hex[format_hex_pretty_size(CGD1_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi CGD1\n" " Bindkey: %s", - format_hex_pretty(this->bindkey_, 16).c_str()); + format_hex_pretty_to(bindkey_hex, this->bindkey_, CGD1_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index 0dcbcbd05c..9151cbde41 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -1,4 +1,5 @@ #include "xiaomi_cgdk2.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,11 +9,14 @@ namespace xiaomi_cgdk2 { static const char *const TAG = "xiaomi_cgdk2"; +static constexpr size_t CGDK2_BINDKEY_SIZE = 16; + void XiaomiCGDK2::dump_config() { + char bindkey_hex[format_hex_pretty_size(CGDK2_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi CGDK2\n" " Bindkey: %s", - format_hex_pretty(this->bindkey_, 16).c_str()); + format_hex_pretty_to(bindkey_hex, this->bindkey_, CGDK2_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index f9fffa3f20..54b50a2eee 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -1,4 +1,5 @@ #include "xiaomi_cgg1.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,11 +9,14 @@ namespace xiaomi_cgg1 { static const char *const TAG = "xiaomi_cgg1"; +static constexpr size_t CGG1_BINDKEY_SIZE = 16; + void XiaomiCGG1::dump_config() { + char bindkey_hex[format_hex_pretty_size(CGG1_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi CGG1\n" " Bindkey: %s", - format_hex_pretty(this->bindkey_, 16).c_str()); + format_hex_pretty_to(bindkey_hex, this->bindkey_, CGG1_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp index dff1228f64..da5229c100 100644 --- a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp +++ b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp @@ -1,4 +1,5 @@ #include "xiaomi_lywsd02mmc.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,11 +9,14 @@ namespace xiaomi_lywsd02mmc { static const char *const TAG = "xiaomi_lywsd02mmc"; +static constexpr size_t LYWSD02MMC_BINDKEY_SIZE = 16; + void XiaomiLYWSD02MMC::dump_config() { + char bindkey_hex[format_hex_pretty_size(LYWSD02MMC_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi LYWSD02MMC\n" " Bindkey: %s", - format_hex_pretty(this->bindkey_, 16).c_str()); + format_hex_pretty_to(bindkey_hex, this->bindkey_, LYWSD02MMC_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index fb0165a21f..44fdb3b816 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -1,4 +1,5 @@ #include "xiaomi_lywsd03mmc.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,11 +9,14 @@ namespace xiaomi_lywsd03mmc { static const char *const TAG = "xiaomi_lywsd03mmc"; +static constexpr size_t LYWSD03MMC_BINDKEY_SIZE = 16; + void XiaomiLYWSD03MMC::dump_config() { + char bindkey_hex[format_hex_pretty_size(LYWSD03MMC_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi LYWSD03MMC\n" " Bindkey: %s", - format_hex_pretty(this->bindkey_, 16).c_str()); + format_hex_pretty_to(bindkey_hex, this->bindkey_, LYWSD03MMC_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index 90b654873b..55b81b301e 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -1,4 +1,5 @@ #include "xiaomi_mhoc401.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,11 +9,14 @@ namespace xiaomi_mhoc401 { static const char *const TAG = "xiaomi_mhoc401"; +static constexpr size_t MHOC401_BINDKEY_SIZE = 16; + void XiaomiMHOC401::dump_config() { + char bindkey_hex[format_hex_pretty_size(MHOC401_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi MHOC401\n" " Bindkey: %s", - format_hex_pretty(this->bindkey_, 16).c_str()); + format_hex_pretty_to(bindkey_hex, this->bindkey_, MHOC401_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp index 498e724368..112bf442e0 100644 --- a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp +++ b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp @@ -1,4 +1,5 @@ #include "xiaomi_rtcgq02lm.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,9 +9,12 @@ namespace xiaomi_rtcgq02lm { static const char *const TAG = "xiaomi_rtcgq02lm"; +static constexpr size_t RTCGQ02LM_BINDKEY_SIZE = 16; + void XiaomiRTCGQ02LM::dump_config() { + char bindkey_hex[format_hex_pretty_size(RTCGQ02LM_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi RTCGQ02LM"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty_to(bindkey_hex, this->bindkey_, RTCGQ02LM_BINDKEY_SIZE, '.')); #ifdef USE_BINARY_SENSOR LOG_BINARY_SENSOR(" ", "Motion", this->motion_); LOG_BINARY_SENSOR(" ", "Light", this->light_); diff --git a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp index f8712e7fd4..d3fec6cc9e 100644 --- a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp +++ b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp @@ -1,4 +1,5 @@ #include "xiaomi_xmwsdj04mmc.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,9 +9,12 @@ namespace xiaomi_xmwsdj04mmc { static const char *const TAG = "xiaomi_xmwsdj04mmc"; +static constexpr size_t XMWSDJ04MMC_BINDKEY_SIZE = 16; + void XiaomiXMWSDJ04MMC::dump_config() { + char bindkey_hex[format_hex_pretty_size(XMWSDJ04MMC_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi XMWSDJ04MMC"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty_to(bindkey_hex, this->bindkey_, XMWSDJ04MMC_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); From 8acaa16987f50a6d65b71832dd559d1716d9f927 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Jan 2026 21:04:11 -1000 Subject: [PATCH 0856/1145] [usb_cdc_acm] Use stack-based hex formatting in verbose logging (#12792) --- esphome/components/usb_cdc_acm/usb_cdc_acm.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp b/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp index 1cf614286f..29120a3d0b 100644 --- a/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp +++ b/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp @@ -1,6 +1,7 @@ #if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "usb_cdc_acm.h" #include "esphome/core/application.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include @@ -16,6 +17,9 @@ namespace esphome::usb_cdc_acm { static const char *TAG = "usb_cdc_acm"; +// Maximum bytes to log in very verbose hex output (168 * 3 = 504, under TX buffer size of 512) +static constexpr size_t USB_CDC_MAX_LOG_BYTES = 168; + static constexpr size_t USB_TX_TASK_STACK_SIZE = 4096; static constexpr size_t USB_TX_TASK_STACK_SIZE_VV = 8192; @@ -43,7 +47,10 @@ static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) { esp_err_t ret = tinyusb_cdcacm_read(static_cast(itf), rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size); ESP_LOGV(TAG, "tinyusb_cdc_rx_callback itf=%d (size: %u)", itf, rx_size); - ESP_LOGVV(TAG, "rx_buf = %s", format_hex_pretty(rx_buf, rx_size).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char rx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, "rx_buf = %s", format_hex_pretty_to(rx_hex_buf, rx_buf, rx_size)); if (ret == ESP_OK && rx_size > 0) { RingbufHandle_t rx_ringbuf = instance->get_rx_ringbuf(); @@ -306,7 +313,10 @@ void USBCDCACMInstance::usb_tx_task() { } ESP_LOGV(TAG, "USB TX itf=%d: Read %d bytes from buffer", this->itf_, tx_data_size); - ESP_LOGVV(TAG, "data = %s", format_hex_pretty(data, tx_data_size).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char tx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, "data = %s", format_hex_pretty_to(tx_hex_buf, data, tx_data_size)); // Serial data will be split up into 64 byte chunks to be sent over USB so this // usually will take multiple iterations From d7fd85e61009544bd08b06569fcbaf0cd8a4b1c4 Mon Sep 17 00:00:00 2001 From: Tobias Stanzel Date: Fri, 2 Jan 2026 08:10:30 +0100 Subject: [PATCH 0857/1145] [spi] Allow any achievable data rate (#12753) Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/spi/__init__.py | 94 +++++++++++++------ .../components/animation/test.rp2040-ard.yaml | 1 + tests/components/chsc6x/test.rp2040-ard.yaml | 1 + tests/components/display/common.yaml | 1 + tests/components/ili9xxx/common.yaml | 2 + tests/components/image/test.rp2040-ard.yaml | 1 + .../online_image/common-rp2040.yaml | 1 + tests/components/qr_code/common.yaml | 1 + tests/components/xpt2046/common.yaml | 1 + 9 files changed, 76 insertions(+), 27 deletions(-) diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 045cdd09d3..e890567abf 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -49,21 +49,60 @@ SPIDevice = spi_ns.class_("SPIDevice") SPIDataRate = spi_ns.enum("SPIDataRate") SPIMode = spi_ns.enum("SPIMode") -SPI_DATA_RATE_OPTIONS = { - 80e6: SPIDataRate.DATA_RATE_80MHZ, - 40e6: SPIDataRate.DATA_RATE_40MHZ, - 20e6: SPIDataRate.DATA_RATE_20MHZ, - 10e6: SPIDataRate.DATA_RATE_10MHZ, - 8e6: SPIDataRate.DATA_RATE_8MHZ, - 5e6: SPIDataRate.DATA_RATE_5MHZ, - 4e6: SPIDataRate.DATA_RATE_4MHZ, - 2e6: SPIDataRate.DATA_RATE_2MHZ, - 1e6: SPIDataRate.DATA_RATE_1MHZ, - 2e5: SPIDataRate.DATA_RATE_200KHZ, - 75e3: SPIDataRate.DATA_RATE_75KHZ, - 1e3: SPIDataRate.DATA_RATE_1KHZ, +PLATFORM_SPI_CLOCKS = { + PLATFORM_ESP8266: 40e6, + PLATFORM_ESP32: 80e6, + PLATFORM_RP2040: 62.5e6, } -SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS)) + +MAX_DATA_RATE_ERROR = 0.05 # Max allowable actual data rate difference from requested + + +def _render_hz(value: float) -> str: + """Render a frequency in Hz as a human-readable string using Hz, KHz or MHz. + + Examples: + 500 -> "500 Hz" + 1500 -> "1.5 kHz" + 2000000 -> "2 MHz" + """ + if value >= 1e6: + unit = "MHz" + num = value / 1e6 + elif value >= 1e3: + unit = "kHz" + num = value / 1e3 + else: + unit = "Hz" + num = value + + # Format with up to 2 decimal places, then strip unnecessary trailing zeros and dot + formatted = f"{int(num)}" if unit == "Hz" else f"{num:.2f}".rstrip("0").rstrip(".") + return formatted + unit + + +def _frequency_validator(value): + platform = get_target_platform() + frequency = PLATFORM_SPI_CLOCKS[platform] + value = cv.frequency(value) + if value > frequency: + raise cv.Invalid( + f"The configured SPI data rate ({_render_hz(value)}) exceeds the maximum for this platform ({_render_hz(frequency)})" + ) + if value < 1000: + raise cv.Invalid("The configured SPI data rate must be at least 1000Hz") + divisor = round(frequency / value) + actual = frequency / divisor + error = abs(actual - value) / value + if error > MAX_DATA_RATE_ERROR: + raise cv.Invalid( + f"The configured SPI data rate ({_render_hz(value)}) is not available for this chip - closest is {_render_hz(actual)}" + ) + return value + + +SPI_DATA_RATE_SCHEMA = _frequency_validator + SPI_MODE_OPTIONS = { "MODE0": SPIMode.MODE0, @@ -393,19 +432,20 @@ def spi_device_schema( :param mode Choose single, quad or octal mode. :return: The SPI device schema, `extend` this in your config schema. """ - schema = { - cv.GenerateID(CONF_SPI_ID): cv.use_id(TYPE_CLASS[mode]), - cv.Optional(CONF_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA, - cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum( - SPI_MODE_OPTIONS, upper=True - ), - cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_on_esp32), - } - if cs_pin_required: - schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema - else: - schema[cv.Optional(CONF_CS_PIN)] = pins.gpio_output_pin_schema - return cv.Schema(schema) + cs_pin_option = cv.Required if cs_pin_required else cv.Optional + return cv.Schema( + { + cv.GenerateID(CONF_SPI_ID): cv.use_id(TYPE_CLASS[mode]), + cv.Optional( + CONF_DATA_RATE, default=default_data_rate + ): SPI_DATA_RATE_SCHEMA, + cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum( + SPI_MODE_OPTIONS, upper=True + ), + cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_on_esp32), + cs_pin_option(CONF_CS_PIN): pins.gpio_output_pin_schema, + } + ) async def register_spi_device(var, config): diff --git a/tests/components/animation/test.rp2040-ard.yaml b/tests/components/animation/test.rp2040-ard.yaml index 32fb4efb04..2c99e937f3 100644 --- a/tests/components/animation/test.rp2040-ard.yaml +++ b/tests/components/animation/test.rp2040-ard.yaml @@ -11,3 +11,4 @@ display: dc_pin: 21 reset_pin: 22 invert_colors: false + data_rate: 10MHz diff --git a/tests/components/chsc6x/test.rp2040-ard.yaml b/tests/components/chsc6x/test.rp2040-ard.yaml index 2e3613a4a3..eb21b8ec4b 100644 --- a/tests/components/chsc6x/test.rp2040-ard.yaml +++ b/tests/components/chsc6x/test.rp2040-ard.yaml @@ -9,6 +9,7 @@ display: invert_colors: True cs_pin: 20 dc_pin: 21 + data_rate: 20MHz pages: - id: page1 lambda: |- diff --git a/tests/components/display/common.yaml b/tests/components/display/common.yaml index 27abb23e03..a722a5f7c2 100644 --- a/tests/components/display/common.yaml +++ b/tests/components/display/common.yaml @@ -6,6 +6,7 @@ display: dc_pin: 13 reset_pin: 21 invert_colors: false + data_rate: 20MHz lambda: |- // Draw an analog clock in the center of the screen int centerX = it.get_width() / 2; diff --git a/tests/components/ili9xxx/common.yaml b/tests/components/ili9xxx/common.yaml index 47384b1398..4665e55e4b 100644 --- a/tests/components/ili9xxx/common.yaml +++ b/tests/components/ili9xxx/common.yaml @@ -11,6 +11,7 @@ display: cs_pin: ${cs_pin1} dc_pin: ${dc_pin1} reset_pin: ${reset_pin1} + data_rate: 20MHz lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ili9xxx @@ -27,5 +28,6 @@ display: reset_pin: ${reset_pin2} auto_clear_enabled: false rotation: 90 + data_rate: 20MHz lambda: |- it.fill(Color::WHITE); diff --git a/tests/components/image/test.rp2040-ard.yaml b/tests/components/image/test.rp2040-ard.yaml index 40f17d57fe..03a9c42a38 100644 --- a/tests/components/image/test.rp2040-ard.yaml +++ b/tests/components/image/test.rp2040-ard.yaml @@ -9,6 +9,7 @@ display: cs_pin: 20 dc_pin: 21 reset_pin: 22 + data_rate: 20MHz invert_colors: true <<: !include common.yaml diff --git a/tests/components/online_image/common-rp2040.yaml b/tests/components/online_image/common-rp2040.yaml index 25891b94bc..bbb514bded 100644 --- a/tests/components/online_image/common-rp2040.yaml +++ b/tests/components/online_image/common-rp2040.yaml @@ -8,6 +8,7 @@ display: spi_id: spi_bus id: main_lcd model: ili9342 + data_rate: 20MHz cs_pin: 20 dc_pin: 17 reset_pin: 21 diff --git a/tests/components/qr_code/common.yaml b/tests/components/qr_code/common.yaml index 15b4e387c6..2ffba67763 100644 --- a/tests/components/qr_code/common.yaml +++ b/tests/components/qr_code/common.yaml @@ -5,6 +5,7 @@ display: cs_pin: ${cs_pin} dc_pin: ${dc_pin} reset_pin: ${reset_pin} + data_rate: 500kHz invert_colors: false lambda: |- // Draw a QR code in the center of the screen diff --git a/tests/components/xpt2046/common.yaml b/tests/components/xpt2046/common.yaml index 3a8e3286a6..eddbd24d6a 100644 --- a/tests/components/xpt2046/common.yaml +++ b/tests/components/xpt2046/common.yaml @@ -6,6 +6,7 @@ display: cs_pin: ${disp_cs_pin} dc_pin: ${dc_pin} reset_pin: ${reset_pin} + data_rate: 20MHz invert_colors: false lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); From 6d4f4d8d23a8321704e315d64aacae9a8224966b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 08:17:05 -1000 Subject: [PATCH 0858/1145] [api] Auto-generate StringRef for incoming API string fields (#12648) --- esphome/components/api/api.proto | 22 +-- esphome/components/api/api_connection.cpp | 32 ++-- esphome/components/api/api_pb2.cpp | 149 ++++++++---------- esphome/components/api/api_pb2.h | 87 +++++----- esphome/components/api/api_pb2_dump.cpp | 98 ++++++++---- .../voice_assistant/voice_assistant.cpp | 26 +-- script/api_protobuf/api_protobuf.py | 103 +++++++----- 7 files changed, 280 insertions(+), 237 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index c351bc8c9c..debea5808c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -102,7 +102,7 @@ message HelloRequest { // For example "Home Assistant" // Not strictly necessary to send but nice for debugging // purposes. - string client_info = 1 [(pointer_to_buffer) = true]; + string client_info = 1; uint32 api_version_major = 2; uint32 api_version_minor = 3; } @@ -139,7 +139,7 @@ message AuthenticationRequest { option (ifdef) = "USE_API_PASSWORD"; // The password to log in with - string password = 1 [(pointer_to_buffer) = true]; + string password = 1; } // Confirmation of successful connection. After this the connection is available for all traffic. @@ -477,7 +477,7 @@ message FanCommandRequest { bool has_speed_level = 10; int32 speed_level = 11; bool has_preset_mode = 12; - string preset_mode = 13 [(pointer_to_buffer) = true]; + string preset_mode = 13; uint32 device_id = 14 [(field_ifdef) = "USE_DEVICES"]; } @@ -579,7 +579,7 @@ message LightCommandRequest { bool has_flash_length = 16; uint32 flash_length = 17; bool has_effect = 18; - string effect = 19 [(pointer_to_buffer) = true]; + string effect = 19; uint32 device_id = 28 [(field_ifdef) = "USE_DEVICES"]; } @@ -824,9 +824,9 @@ message HomeAssistantStateResponse { option (no_delay) = true; option (ifdef) = "USE_API_HOMEASSISTANT_STATES"; - string entity_id = 1 [(pointer_to_buffer) = true]; - string state = 2 [(pointer_to_buffer) = true]; - string attribute = 3 [(pointer_to_buffer) = true]; + string entity_id = 1; + string state = 2; + string attribute = 3; } // ==================== IMPORT TIME ==================== @@ -841,7 +841,7 @@ message GetTimeResponse { option (no_delay) = true; fixed32 epoch_seconds = 1; - string timezone = 2 [(pointer_to_buffer) = true]; + string timezone = 2; } // ==================== USER-DEFINES SERVICES ==================== @@ -1091,11 +1091,11 @@ message ClimateCommandRequest { bool has_swing_mode = 14; ClimateSwingMode swing_mode = 15; bool has_custom_fan_mode = 16; - string custom_fan_mode = 17 [(pointer_to_buffer) = true]; + string custom_fan_mode = 17; bool has_preset = 18; ClimatePreset preset = 19; bool has_custom_preset = 20; - string custom_preset = 21 [(pointer_to_buffer) = true]; + string custom_preset = 21; bool has_target_humidity = 22; float target_humidity = 23; uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"]; @@ -1274,7 +1274,7 @@ message SelectCommandRequest { option (base_class) = "CommandProtoMessage"; fixed32 key = 1; - string state = 2 [(pointer_to_buffer) = true]; + string state = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b5628f654e..26ddb16e9a 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -473,7 +473,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { if (msg.has_direction) call.set_direction(static_cast(msg.direction)); if (msg.has_preset_mode) - call.set_preset_mode(reinterpret_cast(msg.preset_mode), msg.preset_mode_len); + call.set_preset_mode(msg.preset_mode.c_str(), msg.preset_mode.size()); call.perform(); } #endif @@ -559,7 +559,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) { if (msg.has_flash_length) call.set_flash_length(msg.flash_length); if (msg.has_effect) - call.set_effect(reinterpret_cast(msg.effect), msg.effect_len); + call.set_effect(msg.effect.c_str(), msg.effect.size()); call.perform(); } #endif @@ -738,11 +738,11 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { if (msg.has_fan_mode) call.set_fan_mode(static_cast(msg.fan_mode)); if (msg.has_custom_fan_mode) - call.set_fan_mode(reinterpret_cast(msg.custom_fan_mode), msg.custom_fan_mode_len); + call.set_fan_mode(msg.custom_fan_mode.c_str(), msg.custom_fan_mode.size()); if (msg.has_preset) call.set_preset(static_cast(msg.preset)); if (msg.has_custom_preset) - call.set_preset(reinterpret_cast(msg.custom_preset), msg.custom_preset_len); + call.set_preset(msg.custom_preset.c_str(), msg.custom_preset.size()); if (msg.has_swing_mode) call.set_swing_mode(static_cast(msg.swing_mode)); call.perform(); @@ -931,7 +931,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection * } void APIConnection::select_command(const SelectCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(select::Select, select, select) - call.set_option(reinterpret_cast(msg.state), msg.state_len); + call.set_option(msg.state.c_str(), msg.state.size()); call.perform(); } #endif @@ -1153,9 +1153,8 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) { if (homeassistant::global_homeassistant_time != nullptr) { homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds); #ifdef USE_TIME_TIMEZONE - if (value.timezone_len > 0) { - homeassistant::global_homeassistant_time->set_timezone(reinterpret_cast(value.timezone), - value.timezone_len); + if (!value.timezone.empty()) { + homeassistant::global_homeassistant_time->set_timezone(value.timezone.c_str(), value.timezone.size()); } #endif } @@ -1522,7 +1521,7 @@ void APIConnection::complete_authentication_() { } bool APIConnection::send_hello_response(const HelloRequest &msg) { - this->client_info_.name.assign(reinterpret_cast(msg.client_info), msg.client_info_len); + this->client_info_.name.assign(msg.client_info.c_str(), msg.client_info.size()); this->client_info_.peername = this->helper_->getpeername(); this->client_api_version_major_ = msg.api_version_major; this->client_api_version_minor_ = msg.api_version_minor; @@ -1550,7 +1549,7 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) { bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) { AuthenticationResponse resp; // bool invalid_password = 1; - resp.invalid_password = !this->parent_->check_password(msg.password, msg.password_len); + resp.invalid_password = !this->parent_->check_password(msg.password.byte(), msg.password.size()); if (!resp.invalid_password) { this->complete_authentication_(); } @@ -1693,27 +1692,28 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { #ifdef USE_API_HOMEASSISTANT_STATES void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) { // Skip if entity_id is empty (invalid message) - if (msg.entity_id_len == 0) { + if (msg.entity_id.empty()) { return; } for (auto &it : this->parent_->get_state_subs()) { // Compare entity_id: check length matches and content matches size_t entity_id_len = strlen(it.entity_id); - if (entity_id_len != msg.entity_id_len || memcmp(it.entity_id, msg.entity_id, msg.entity_id_len) != 0) { + if (entity_id_len != msg.entity_id.size() || + memcmp(it.entity_id, msg.entity_id.c_str(), msg.entity_id.size()) != 0) { continue; } // Compare attribute: either both have matching attribute, or both have none size_t sub_attr_len = it.attribute != nullptr ? strlen(it.attribute) : 0; - if (sub_attr_len != msg.attribute_len || - (sub_attr_len > 0 && memcmp(it.attribute, msg.attribute, sub_attr_len) != 0)) { + if (sub_attr_len != msg.attribute.size() || + (sub_attr_len > 0 && memcmp(it.attribute, msg.attribute.c_str(), sub_attr_len) != 0)) { continue; } // Create temporary string for callback (callback takes const std::string &) - // Handle empty state (nullptr with len=0) - std::string state(msg.state_len > 0 ? reinterpret_cast(msg.state) : "", msg.state_len); + // Handle empty state + std::string state(!msg.state.empty() ? msg.state.c_str() : "", msg.state.size()); it.callback(state); } } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 3376b022c5..edd6dfc6a9 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -23,9 +23,7 @@ bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly to avoid allocation - this->client_info = value.data(); - this->client_info_len = value.size(); + this->client_info = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -49,9 +47,7 @@ void HelloResponse::calculate_size(ProtoSize &size) const { bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly to avoid allocation - this->password = value.data(); - this->password_len = value.size(); + this->password = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -448,9 +444,7 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool FanCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 13: { - // Use raw data directly to avoid allocation - this->preset_mode = value.data(); - this->preset_mode_len = value.size(); + this->preset_mode = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -615,9 +609,7 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool LightCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 19: { - // Use raw data directly to avoid allocation - this->effect = value.data(); - this->effect_len = value.size(); + this->effect = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -859,7 +851,6 @@ void SubscribeLogsResponse::calculate_size(ProtoSize &size) const { bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly to avoid allocation this->key = value.data(); this->key_len = value.size(); break; @@ -936,12 +927,12 @@ bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt v } bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 3: - this->error_message = value.as_string(); + case 3: { + this->error_message = StringRef(reinterpret_cast(value.data()), value.size()); break; + } #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON case 4: { - // Use raw data directly to avoid allocation this->response_data = value.data(); this->response_data_len = value.size(); break; @@ -967,21 +958,15 @@ void SubscribeHomeAssistantStateResponse::calculate_size(ProtoSize &size) const bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly to avoid allocation - this->entity_id = value.data(); - this->entity_id_len = value.size(); + this->entity_id = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 2: { - // Use raw data directly to avoid allocation - this->state = value.data(); - this->state_len = value.size(); + this->state = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 3: { - // Use raw data directly to avoid allocation - this->attribute = value.data(); - this->attribute_len = value.size(); + this->attribute = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -993,9 +978,7 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - // Use raw data directly to avoid allocation - this->timezone = value.data(); - this->timezone_len = value.size(); + this->timezone = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -1060,9 +1043,10 @@ bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value) } bool ExecuteServiceArgument::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 4: - this->string_ = value.as_string(); + case 4: { + this->string_ = StringRef(reinterpret_cast(value.data()), value.size()); break; + } case 9: this->string_array.push_back(value.as_string()); break; @@ -1153,7 +1137,7 @@ void ExecuteServiceResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->success); size.add_length(1, this->error_message_ref_.size()); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON - size.add_length(4, this->response_data_len); + size.add_length(1, this->response_data_len); #endif } #endif @@ -1408,15 +1392,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 17: { - // Use raw data directly to avoid allocation - this->custom_fan_mode = value.data(); - this->custom_fan_mode_len = value.size(); + this->custom_fan_mode = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 21: { - // Use raw data directly to avoid allocation - this->custom_preset = value.data(); - this->custom_preset_len = value.size(); + this->custom_preset = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -1702,9 +1682,7 @@ bool SelectCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - // Use raw data directly to avoid allocation - this->state = value.data(); - this->state_len = value.size(); + this->state = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -1808,9 +1786,10 @@ bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool SirenCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 5: - this->tone = value.as_string(); + case 5: { + this->tone = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -1899,9 +1878,10 @@ bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 4: - this->code = value.as_string(); + case 4: { + this->code = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2069,9 +2049,10 @@ bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt val } bool MediaPlayerCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 7: - this->media_url = value.as_string(); + case 7: { + this->media_url = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2279,7 +2260,6 @@ bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt val bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 4: { - // Use raw data directly to avoid allocation this->data = value.data(); this->data_len = value.size(); break; @@ -2318,7 +2298,6 @@ bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, Proto bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 3: { - // Use raw data directly to avoid allocation this->data = value.data(); this->data_len = value.size(); break; @@ -2502,12 +2481,14 @@ bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) } bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->name = value.as_string(); + case 1: { + this->name = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 2: - this->value = value.as_string(); + } + case 2: { + this->value = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2583,12 +2564,14 @@ bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVar } bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: - this->timer_id = value.as_string(); + case 2: { + this->timer_id = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 3: - this->name = value.as_string(); + } + case 3: { + this->name = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2606,15 +2589,18 @@ bool VoiceAssistantAnnounceRequest::decode_varint(uint32_t field_id, ProtoVarInt } bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->media_id = value.as_string(); + case 1: { + this->media_id = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 2: - this->text = value.as_string(); + } + case 2: { + this->text = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 3: - this->preannounce_media_id = value.as_string(); + } + case 3: { + this->preannounce_media_id = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2650,24 +2636,29 @@ bool VoiceAssistantExternalWakeWord::decode_varint(uint32_t field_id, ProtoVarIn } bool VoiceAssistantExternalWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->id = value.as_string(); + case 1: { + this->id = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 2: - this->wake_word = value.as_string(); + } + case 2: { + this->wake_word = StringRef(reinterpret_cast(value.data()), value.size()); break; + } case 3: this->trained_languages.push_back(value.as_string()); break; - case 4: - this->model_type = value.as_string(); + case 4: { + this->model_type = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 6: - this->model_hash = value.as_string(); + } + case 6: { + this->model_hash = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 7: - this->url = value.as_string(); + } + case 7: { + this->url = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2777,9 +2768,10 @@ bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarI } bool AlarmControlPanelCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 3: - this->code = value.as_string(); + case 3: { + this->code = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2861,9 +2853,10 @@ bool TextCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: - this->state = value.as_string(); + case 2: { + this->state = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -3331,7 +3324,6 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly to avoid allocation this->data = value.data(); this->data_len = value.size(); break; @@ -3356,7 +3348,6 @@ bool ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool ZWaveProxyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - // Use raw data directly to avoid allocation this->data = value.data(); this->data_len = value.size(); break; @@ -3372,7 +3363,7 @@ void ZWaveProxyRequest::encode(ProtoWriteBuffer buffer) const { } void ZWaveProxyRequest::calculate_size(ProtoSize &size) const { size.add_uint32(1, static_cast(this->type)); - size.add_length(2, this->data_len); + size.add_length(1, this->data_len); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 2111c2a895..9d7a1eb9cb 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -357,12 +357,11 @@ class CommandProtoMessage : public ProtoDecodableMessage { class HelloRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 1; - static constexpr uint8_t ESTIMATED_SIZE = 27; + static constexpr uint8_t ESTIMATED_SIZE = 17; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "hello_request"; } #endif - const uint8_t *client_info{nullptr}; - uint16_t client_info_len{0}; + StringRef client_info{}; uint32_t api_version_major{0}; uint32_t api_version_minor{0}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -398,12 +397,11 @@ class HelloResponse final : public ProtoMessage { class AuthenticationRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 3; - static constexpr uint8_t ESTIMATED_SIZE = 19; + static constexpr uint8_t ESTIMATED_SIZE = 9; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "authentication_request"; } #endif - const uint8_t *password{nullptr}; - uint16_t password_len{0}; + StringRef password{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -784,7 +782,7 @@ class FanStateResponse final : public StateResponseProtoMessage { class FanCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 31; - static constexpr uint8_t ESTIMATED_SIZE = 48; + static constexpr uint8_t ESTIMATED_SIZE = 38; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "fan_command_request"; } #endif @@ -797,8 +795,7 @@ class FanCommandRequest final : public CommandProtoMessage { bool has_speed_level{false}; int32_t speed_level{0}; bool has_preset_mode{false}; - const uint8_t *preset_mode{nullptr}; - uint16_t preset_mode_len{0}; + StringRef preset_mode{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -860,7 +857,7 @@ class LightStateResponse final : public StateResponseProtoMessage { class LightCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 32; - static constexpr uint8_t ESTIMATED_SIZE = 122; + static constexpr uint8_t ESTIMATED_SIZE = 112; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "light_command_request"; } #endif @@ -889,8 +886,7 @@ class LightCommandRequest final : public CommandProtoMessage { bool has_flash_length{false}; uint32_t flash_length{0}; bool has_effect{false}; - const uint8_t *effect{nullptr}; - uint16_t effect_len{0}; + StringRef effect{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1171,7 +1167,7 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage { #endif uint32_t call_id{0}; bool success{false}; - std::string error_message{}; + StringRef error_message{}; #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON const uint8_t *response_data{nullptr}; uint16_t response_data_len{0}; @@ -1222,16 +1218,13 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage { class HomeAssistantStateResponse final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 40; - static constexpr uint8_t ESTIMATED_SIZE = 57; + static constexpr uint8_t ESTIMATED_SIZE = 27; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "home_assistant_state_response"; } #endif - const uint8_t *entity_id{nullptr}; - uint16_t entity_id_len{0}; - const uint8_t *state{nullptr}; - uint16_t state_len{0}; - const uint8_t *attribute{nullptr}; - uint16_t attribute_len{0}; + StringRef entity_id{}; + StringRef state{}; + StringRef attribute{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1256,13 +1249,12 @@ class GetTimeRequest final : public ProtoMessage { class GetTimeResponse final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 37; - static constexpr uint8_t ESTIMATED_SIZE = 24; + static constexpr uint8_t ESTIMATED_SIZE = 14; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "get_time_response"; } #endif uint32_t epoch_seconds{0}; - const uint8_t *timezone{nullptr}; - uint16_t timezone_len{0}; + StringRef timezone{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1310,7 +1302,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage { bool bool_{false}; int32_t legacy_int{0}; float float_{0.0f}; - std::string string_{}; + StringRef string_{}; int32_t int_{0}; FixedVector bool_array{}; FixedVector int_array{}; @@ -1499,7 +1491,7 @@ class ClimateStateResponse final : public StateResponseProtoMessage { class ClimateCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 48; - static constexpr uint8_t ESTIMATED_SIZE = 104; + static constexpr uint8_t ESTIMATED_SIZE = 84; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "climate_command_request"; } #endif @@ -1516,13 +1508,11 @@ class ClimateCommandRequest final : public CommandProtoMessage { bool has_swing_mode{false}; enums::ClimateSwingMode swing_mode{}; bool has_custom_fan_mode{false}; - const uint8_t *custom_fan_mode{nullptr}; - uint16_t custom_fan_mode_len{0}; + StringRef custom_fan_mode{}; bool has_preset{false}; enums::ClimatePreset preset{}; bool has_custom_preset{false}; - const uint8_t *custom_preset{nullptr}; - uint16_t custom_preset_len{0}; + StringRef custom_preset{}; bool has_target_humidity{false}; float target_humidity{0.0f}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1695,12 +1685,11 @@ class SelectStateResponse final : public StateResponseProtoMessage { class SelectCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 54; - static constexpr uint8_t ESTIMATED_SIZE = 28; + static constexpr uint8_t ESTIMATED_SIZE = 18; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "select_command_request"; } #endif - const uint8_t *state{nullptr}; - uint16_t state_len{0}; + StringRef state{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1756,7 +1745,7 @@ class SirenCommandRequest final : public CommandProtoMessage { bool has_state{false}; bool state{false}; bool has_tone{false}; - std::string tone{}; + StringRef tone{}; bool has_duration{false}; uint32_t duration{0}; bool has_volume{false}; @@ -1817,7 +1806,7 @@ class LockCommandRequest final : public CommandProtoMessage { #endif enums::LockCommand command{}; bool has_code{false}; - std::string code{}; + StringRef code{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1927,7 +1916,7 @@ class MediaPlayerCommandRequest final : public CommandProtoMessage { bool has_volume{false}; float volume{0.0f}; bool has_media_url{false}; - std::string media_url{}; + StringRef media_url{}; bool has_announcement{false}; bool announcement{false}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2503,8 +2492,8 @@ class VoiceAssistantResponse final : public ProtoDecodableMessage { }; class VoiceAssistantEventData final : public ProtoDecodableMessage { public: - std::string name{}; - std::string value{}; + StringRef name{}; + StringRef value{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2562,8 +2551,8 @@ class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage { const char *message_name() const override { return "voice_assistant_timer_event_response"; } #endif enums::VoiceAssistantTimerEvent event_type{}; - std::string timer_id{}; - std::string name{}; + StringRef timer_id{}; + StringRef name{}; uint32_t total_seconds{0}; uint32_t seconds_left{0}; bool is_active{false}; @@ -2582,9 +2571,9 @@ class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_announce_request"; } #endif - std::string media_id{}; - std::string text{}; - std::string preannounce_media_id{}; + StringRef media_id{}; + StringRef text{}; + StringRef preannounce_media_id{}; bool start_conversation{false}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2627,13 +2616,13 @@ class VoiceAssistantWakeWord final : public ProtoMessage { }; class VoiceAssistantExternalWakeWord final : public ProtoDecodableMessage { public: - std::string id{}; - std::string wake_word{}; + StringRef id{}; + StringRef wake_word{}; std::vector trained_languages{}; - std::string model_type{}; + StringRef model_type{}; uint32_t model_size{0}; - std::string model_hash{}; - std::string url{}; + StringRef model_hash{}; + StringRef url{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2734,7 +2723,7 @@ class AlarmControlPanelCommandRequest final : public CommandProtoMessage { const char *message_name() const override { return "alarm_control_panel_command_request"; } #endif enums::AlarmControlPanelStateCommand command{}; - std::string code{}; + StringRef code{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2791,7 +2780,7 @@ class TextCommandRequest final : public CommandProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "text_command_request"; } #endif - std::string state{}; + StringRef state{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 9faf39e29e..567f10fcc0 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -736,7 +736,7 @@ template<> const char *proto_enum_to_string(enums: void HelloRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HelloRequest"); out.append(" client_info: "); - out.append(format_hex_pretty(this->client_info, this->client_info_len)); + out.append("'").append(this->client_info.c_str(), this->client_info.size()).append("'"); out.append("\n"); dump_field(out, "api_version_major", this->api_version_major); dump_field(out, "api_version_minor", this->api_version_minor); @@ -752,7 +752,7 @@ void HelloResponse::dump_to(std::string &out) const { void AuthenticationRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "AuthenticationRequest"); out.append(" password: "); - out.append(format_hex_pretty(this->password, this->password_len)); + out.append("'").append(this->password.c_str(), this->password.size()).append("'"); out.append("\n"); } void AuthenticationResponse::dump_to(std::string &out) const { @@ -965,7 +965,7 @@ void FanCommandRequest::dump_to(std::string &out) const { dump_field(out, "speed_level", this->speed_level); dump_field(out, "has_preset_mode", this->has_preset_mode); out.append(" preset_mode: "); - out.append(format_hex_pretty(this->preset_mode, this->preset_mode_len)); + out.append("'").append(this->preset_mode.c_str(), this->preset_mode.size()).append("'"); out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); @@ -1043,7 +1043,7 @@ void LightCommandRequest::dump_to(std::string &out) const { dump_field(out, "flash_length", this->flash_length); dump_field(out, "has_effect", this->has_effect); out.append(" effect: "); - out.append(format_hex_pretty(this->effect, this->effect_len)); + out.append("'").append(this->effect.c_str(), this->effect.size()).append("'"); out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); @@ -1205,7 +1205,9 @@ void HomeassistantActionResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HomeassistantActionResponse"); dump_field(out, "call_id", this->call_id); dump_field(out, "success", this->success); - dump_field(out, "error_message", this->error_message); + out.append(" error_message: "); + out.append("'").append(this->error_message.c_str(), this->error_message.size()).append("'"); + out.append("\n"); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON out.append(" response_data: "); out.append(format_hex_pretty(this->response_data, this->response_data_len)); @@ -1226,13 +1228,13 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { void HomeAssistantStateResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HomeAssistantStateResponse"); out.append(" entity_id: "); - out.append(format_hex_pretty(this->entity_id, this->entity_id_len)); + out.append("'").append(this->entity_id.c_str(), this->entity_id.size()).append("'"); out.append("\n"); out.append(" state: "); - out.append(format_hex_pretty(this->state, this->state_len)); + out.append("'").append(this->state.c_str(), this->state.size()).append("'"); out.append("\n"); out.append(" attribute: "); - out.append(format_hex_pretty(this->attribute, this->attribute_len)); + out.append("'").append(this->attribute.c_str(), this->attribute.size()).append("'"); out.append("\n"); } #endif @@ -1241,7 +1243,7 @@ void GetTimeResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "GetTimeResponse"); dump_field(out, "epoch_seconds", this->epoch_seconds); out.append(" timezone: "); - out.append(format_hex_pretty(this->timezone, this->timezone_len)); + out.append("'").append(this->timezone.c_str(), this->timezone.size()).append("'"); out.append("\n"); } #ifdef USE_API_USER_DEFINED_ACTIONS @@ -1266,7 +1268,9 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { dump_field(out, "bool_", this->bool_); dump_field(out, "legacy_int", this->legacy_int); dump_field(out, "float_", this->float_); - dump_field(out, "string_", this->string_); + out.append(" string_: "); + out.append("'").append(this->string_.c_str(), this->string_.size()).append("'"); + out.append("\n"); dump_field(out, "int_", this->int_); for (const auto it : this->bool_array) { dump_field(out, "bool_array", static_cast(it), 4); @@ -1424,13 +1428,13 @@ void ClimateCommandRequest::dump_to(std::string &out) const { dump_field(out, "swing_mode", static_cast(this->swing_mode)); dump_field(out, "has_custom_fan_mode", this->has_custom_fan_mode); out.append(" custom_fan_mode: "); - out.append(format_hex_pretty(this->custom_fan_mode, this->custom_fan_mode_len)); + out.append("'").append(this->custom_fan_mode.c_str(), this->custom_fan_mode.size()).append("'"); out.append("\n"); dump_field(out, "has_preset", this->has_preset); dump_field(out, "preset", static_cast(this->preset)); dump_field(out, "has_custom_preset", this->has_custom_preset); out.append(" custom_preset: "); - out.append(format_hex_pretty(this->custom_preset, this->custom_preset_len)); + out.append("'").append(this->custom_preset.c_str(), this->custom_preset.size()).append("'"); out.append("\n"); dump_field(out, "has_target_humidity", this->has_target_humidity); dump_field(out, "target_humidity", this->target_humidity); @@ -1558,7 +1562,7 @@ void SelectCommandRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "SelectCommandRequest"); dump_field(out, "key", this->key); out.append(" state: "); - out.append(format_hex_pretty(this->state, this->state_len)); + out.append("'").append(this->state.c_str(), this->state.size()).append("'"); out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); @@ -1599,7 +1603,9 @@ void SirenCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_state", this->has_state); dump_field(out, "state", this->state); dump_field(out, "has_tone", this->has_tone); - dump_field(out, "tone", this->tone); + out.append(" tone: "); + out.append("'").append(this->tone.c_str(), this->tone.size()).append("'"); + out.append("\n"); dump_field(out, "has_duration", this->has_duration); dump_field(out, "duration", this->duration); dump_field(out, "has_volume", this->has_volume); @@ -1641,7 +1647,9 @@ void LockCommandRequest::dump_to(std::string &out) const { dump_field(out, "key", this->key); dump_field(out, "command", static_cast(this->command)); dump_field(out, "has_code", this->has_code); - dump_field(out, "code", this->code); + out.append(" code: "); + out.append("'").append(this->code.c_str(), this->code.size()).append("'"); + out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -1719,7 +1727,9 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_volume", this->has_volume); dump_field(out, "volume", this->volume); dump_field(out, "has_media_url", this->has_media_url); - dump_field(out, "media_url", this->media_url); + out.append(" media_url: "); + out.append("'").append(this->media_url.c_str(), this->media_url.size()).append("'"); + out.append("\n"); dump_field(out, "has_announcement", this->has_announcement); dump_field(out, "announcement", this->announcement); #ifdef USE_DEVICES @@ -1949,8 +1959,12 @@ void VoiceAssistantResponse::dump_to(std::string &out) const { } void VoiceAssistantEventData::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantEventData"); - dump_field(out, "name", this->name); - dump_field(out, "value", this->value); + out.append(" name: "); + out.append("'").append(this->name.c_str(), this->name.size()).append("'"); + out.append("\n"); + out.append(" value: "); + out.append("'").append(this->value.c_str(), this->value.size()).append("'"); + out.append("\n"); } void VoiceAssistantEventResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantEventResponse"); @@ -1975,17 +1989,27 @@ void VoiceAssistantAudio::dump_to(std::string &out) const { void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantTimerEventResponse"); dump_field(out, "event_type", static_cast(this->event_type)); - dump_field(out, "timer_id", this->timer_id); - dump_field(out, "name", this->name); + out.append(" timer_id: "); + out.append("'").append(this->timer_id.c_str(), this->timer_id.size()).append("'"); + out.append("\n"); + out.append(" name: "); + out.append("'").append(this->name.c_str(), this->name.size()).append("'"); + out.append("\n"); dump_field(out, "total_seconds", this->total_seconds); dump_field(out, "seconds_left", this->seconds_left); dump_field(out, "is_active", this->is_active); } void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantAnnounceRequest"); - dump_field(out, "media_id", this->media_id); - dump_field(out, "text", this->text); - dump_field(out, "preannounce_media_id", this->preannounce_media_id); + out.append(" media_id: "); + out.append("'").append(this->media_id.c_str(), this->media_id.size()).append("'"); + out.append("\n"); + out.append(" text: "); + out.append("'").append(this->text.c_str(), this->text.size()).append("'"); + out.append("\n"); + out.append(" preannounce_media_id: "); + out.append("'").append(this->preannounce_media_id.c_str(), this->preannounce_media_id.size()).append("'"); + out.append("\n"); dump_field(out, "start_conversation", this->start_conversation); } void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { dump_field(out, "success", this->success); } @@ -1999,15 +2023,25 @@ void VoiceAssistantWakeWord::dump_to(std::string &out) const { } void VoiceAssistantExternalWakeWord::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantExternalWakeWord"); - dump_field(out, "id", this->id); - dump_field(out, "wake_word", this->wake_word); + out.append(" id: "); + out.append("'").append(this->id.c_str(), this->id.size()).append("'"); + out.append("\n"); + out.append(" wake_word: "); + out.append("'").append(this->wake_word.c_str(), this->wake_word.size()).append("'"); + out.append("\n"); for (const auto &it : this->trained_languages) { dump_field(out, "trained_languages", it, 4); } - dump_field(out, "model_type", this->model_type); + out.append(" model_type: "); + out.append("'").append(this->model_type.c_str(), this->model_type.size()).append("'"); + out.append("\n"); dump_field(out, "model_size", this->model_size); - dump_field(out, "model_hash", this->model_hash); - dump_field(out, "url", this->url); + out.append(" model_hash: "); + out.append("'").append(this->model_hash.c_str(), this->model_hash.size()).append("'"); + out.append("\n"); + out.append(" url: "); + out.append("'").append(this->url.c_str(), this->url.size()).append("'"); + out.append("\n"); } void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest"); @@ -2066,7 +2100,9 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "AlarmControlPanelCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "command", static_cast(this->command)); - dump_field(out, "code", this->code); + out.append(" code: "); + out.append("'").append(this->code.c_str(), this->code.size()).append("'"); + out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -2103,7 +2139,9 @@ void TextStateResponse::dump_to(std::string &out) const { void TextCommandRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "TextCommandRequest"); dump_field(out, "key", this->key); - dump_field(out, "state", this->state); + out.append(" state: "); + out.append("'").append(this->state.c_str(), this->state.size()).append("'"); + out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index b946e3b38a..9bb5393be2 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -627,9 +627,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { ESP_LOGD(TAG, "Assist Pipeline running"); #ifdef USE_MEDIA_PLAYER this->started_streaming_tts_ = false; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "url") { - this->tts_response_url_ = std::move(arg.value); + this->tts_response_url_ = arg.value; } } #endif @@ -648,9 +648,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { break; case api::enums::VOICE_ASSISTANT_STT_END: { std::string text; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "text") { - text = std::move(arg.value); + text = arg.value; } } if (text.empty()) { @@ -693,9 +693,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { break; } case api::enums::VOICE_ASSISTANT_INTENT_END: { - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "conversation_id") { - this->conversation_id_ = std::move(arg.value); + this->conversation_id_ = arg.value; } else if (arg.name == "continue_conversation") { this->continue_conversation_ = (arg.value == "1"); } @@ -705,9 +705,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } case api::enums::VOICE_ASSISTANT_TTS_START: { std::string text; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "text") { - text = std::move(arg.value); + text = arg.value; } } if (text.empty()) { @@ -731,9 +731,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } case api::enums::VOICE_ASSISTANT_TTS_END: { std::string url; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "url") { - url = std::move(arg.value); + url = arg.value; } } if (url.empty()) { @@ -778,11 +778,11 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { case api::enums::VOICE_ASSISTANT_ERROR: { std::string code = ""; std::string message = ""; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "code") { - code = std::move(arg.value); + code = arg.value; } else if (arg.name == "message") { - message = std::move(arg.value); + message = arg.value; } } if (code == "wake-word-timeout" || code == "wake_word_detection_aborted" || code == "no_wake_word") { diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index cb09ef7050..f22b248747 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -374,20 +374,16 @@ def create_field_type_info( # Traditional fixed array approach with copy return FixedArrayBytesType(field, fixed_size) - # Check for pointer_to_buffer option on string fields - if field.type == 9: - has_pointer_to_buffer = get_field_opt(field, pb.pointer_to_buffer, False) - - if has_pointer_to_buffer: - # Zero-copy pointer approach for strings - return PointerToBytesBufferType(field, None) - # Special handling for bytes fields if field.type == 12: return BytesType(field, needs_decode, needs_encode) # Special handling for string fields if field.type == 9: + # For SOURCE_CLIENT only messages (decode but no encode), use StringRef + # for zero-copy access to the receive buffer + if needs_decode and not needs_encode: + return PointerToStringBufferType(field, None) return StringType(field, needs_decode, needs_encode) validate_field_type(field.type, field.name) @@ -840,8 +836,8 @@ class BytesType(TypeInfo): return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes -class PointerToBytesBufferType(TypeInfo): - """Type for bytes fields that use pointer_to_buffer option for zero-copy.""" +class PointerToBufferTypeBase(TypeInfo): + """Base class for pointer_to_buffer types (bytes and strings) for zero-copy decoding.""" @classmethod def can_use_dump_field(cls) -> bool: @@ -851,29 +847,34 @@ class PointerToBytesBufferType(TypeInfo): self, field: descriptor.FieldDescriptorProto, size: int | None = None ) -> None: super().__init__(field) - # Size is not used for pointer_to_buffer - we always use size_t for length self.array_size = 0 @property - def cpp_type(self) -> str: - return "const uint8_t*" + def decode_length(self) -> str | None: + # This is handled in decode_length_content + return None @property - def default_value(self) -> str: - return "nullptr" + def wire_type(self) -> WireType: + """Get the wire type for this field.""" + return WireType.LENGTH_DELIMITED # Uses wire type 2 - @property - def reference_type(self) -> str: - return "const uint8_t*" + def get_estimated_size(self) -> int: + # field ID + length varint + typical data (assume small for pointer fields) + return self.calculate_field_id_size() + 2 + 16 - @property - def const_reference_type(self) -> str: - return "const uint8_t*" + +class PointerToBytesBufferType(PointerToBufferTypeBase): + """Type for bytes fields that use pointer_to_buffer option for zero-copy.""" + + cpp_type = "const uint8_t*" + default_value = "nullptr" + reference_type = "const uint8_t*" + const_reference_type = "const uint8_t*" @property def public_content(self) -> list[str]: # Use uint16_t for length - max packet size is well below 65535 - # Add pointer and length fields return [ f"const uint8_t* {self.field_name}{{nullptr}};", f"uint16_t {self.field_name}_len{{0}};", @@ -885,24 +886,12 @@ class PointerToBytesBufferType(TypeInfo): @property def decode_length_content(self) -> str | None: - # Decode directly stores the pointer to avoid allocation return f"""case {self.number}: {{ - // Use raw data directly to avoid allocation this->{self.field_name} = value.data(); this->{self.field_name}_len = value.size(); break; }}""" - @property - def decode_length(self) -> str | None: - # This is handled in decode_length_content - return None - - @property - def wire_type(self) -> WireType: - """Get the wire type for this bytes field.""" - return WireType.LENGTH_DELIMITED # Uses wire type 2 - def dump(self, name: str) -> str: return ( f"format_hex_pretty(this->{self.field_name}, this->{self.field_name}_len)" @@ -910,7 +899,6 @@ class PointerToBytesBufferType(TypeInfo): @property def dump_content(self) -> str: - # Custom dump that doesn't use dump_field template return ( f'out.append(" {self.name}: ");\n' + f"out.append({self.dump(self.field_name)});\n" @@ -918,11 +906,48 @@ class PointerToBytesBufferType(TypeInfo): ) def get_size_calculation(self, name: str, force: bool = False) -> str: - return f"size.add_length({self.number}, this->{self.field_name}_len);" + return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}_len);" - def get_estimated_size(self) -> int: - # field ID + length varint + typical data (assume small for pointer fields) - return self.calculate_field_id_size() + 2 + 16 + +class PointerToStringBufferType(PointerToBufferTypeBase): + """Type for string fields that use pointer_to_buffer option for zero-copy. + + Uses StringRef instead of separate pointer and length fields. + """ + + cpp_type = "StringRef" + default_value = "" + reference_type = "StringRef &" + const_reference_type = "const StringRef &" + + @property + def public_content(self) -> list[str]: + return [f"StringRef {self.field_name}{{}};"] + + @property + def encode_content(self) -> str: + return f"buffer.encode_string({self.number}, this->{self.field_name});" + + @property + def decode_length_content(self) -> str | None: + return f"""case {self.number}: {{ + this->{self.field_name} = StringRef(reinterpret_cast(value.data()), value.size()); + break; + }}""" + + def dump(self, name: str) -> str: + return f'out.append("\'").append(this->{self.field_name}.c_str(), this->{self.field_name}.size()).append("\'");' + + @property + def dump_content(self) -> str: + return ( + f'out.append(" {self.name}: ");\n' + + f"{self.dump(self.field_name)}\n" + + 'out.append("\\n");' + ) + + def get_size_calculation(self, name: str, force: bool = False) -> str: + return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}.size());" class FixedArrayBytesType(TypeInfo): From 763515d3a19561f76461f004a9631bab9493bcba Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:47:14 -0500 Subject: [PATCH 0859/1145] [core] Remove unused USE_ESP32_FRAMEWORK_ARDUINO ifdefs (#12813) Co-authored-by: Claude --- esphome/components/wifi/wifi_component.h | 10 ---------- esphome/core/hal.h | 10 +--------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index ff2bfe12a4..5bf1f444e8 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -12,12 +12,6 @@ #include #include -#ifdef USE_ESP32_FRAMEWORK_ARDUINO -#include -#include -#include -#endif - #ifdef USE_LIBRETINY #include #endif @@ -578,10 +572,6 @@ class WiFiComponent : public Component { static void s_wifi_scan_done_callback(void *arg, STATUS status); #endif -#ifdef USE_ESP32_FRAMEWORK_ARDUINO - void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info); - void wifi_scan_done_callback_(); -#endif #ifdef USE_ESP32 void wifi_process_event_(IDFWiFiEvent *data); #endif diff --git a/esphome/core/hal.h b/esphome/core/hal.h index 0ccf21ad83..1a4230e421 100644 --- a/esphome/core/hal.h +++ b/esphome/core/hal.h @@ -3,20 +3,12 @@ #include #include "gpio.h" -#if defined(USE_ESP32_FRAMEWORK_ESP_IDF) +#if defined(USE_ESP32) #include #ifndef PROGMEM #define PROGMEM #endif -#elif defined(USE_ESP32_FRAMEWORK_ARDUINO) - -#include - -#ifndef PROGMEM -#define PROGMEM -#endif - #elif defined(USE_ESP8266) #include From 087f521b198eb39ccb7b95f1106ab5b9a395d78a Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 2 Jan 2026 15:58:53 -0500 Subject: [PATCH 0860/1145] [ultrasonic] Use interrupt-based measurement for reliability (#12617) Co-authored-by: Claude --- esphome/components/ultrasonic/sensor.py | 2 +- .../ultrasonic/ultrasonic_sensor.cpp | 94 +++++++++++++------ .../components/ultrasonic/ultrasonic_sensor.h | 42 +++++---- 3 files changed, 89 insertions(+), 49 deletions(-) diff --git a/esphome/components/ultrasonic/sensor.py b/esphome/components/ultrasonic/sensor.py index 937d9a5261..d341acb9d1 100644 --- a/esphome/components/ultrasonic/sensor.py +++ b/esphome/components/ultrasonic/sensor.py @@ -28,7 +28,7 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.Required(CONF_TRIGGER_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_TRIGGER_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_ECHO_PIN): pins.internal_gpio_input_pin_schema, cv.Optional(CONF_TIMEOUT, default="2m"): cv.distance, cv.Optional( diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.cpp b/esphome/components/ultrasonic/ultrasonic_sensor.cpp index e864ea6419..184d93f189 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.cpp +++ b/esphome/components/ultrasonic/ultrasonic_sensor.cpp @@ -1,64 +1,96 @@ #include "ultrasonic_sensor.h" -#include "esphome/core/log.h" #include "esphome/core/hal.h" +#include "esphome/core/log.h" -namespace esphome { -namespace ultrasonic { +namespace esphome::ultrasonic { static const char *const TAG = "ultrasonic.sensor"; +static constexpr uint32_t DEBOUNCE_US = 50; // Ignore edges within 50us (noise filtering) +static constexpr uint32_t TIMEOUT_MARGIN_US = 1000; // Extra margin for sensor processing time + +void IRAM_ATTR UltrasonicSensorStore::gpio_intr(UltrasonicSensorStore *arg) { + uint32_t now = micros(); + if (!arg->echo_start || (now - arg->echo_start_us) <= DEBOUNCE_US) { + arg->echo_start_us = now; + arg->echo_start = true; + } else { + arg->echo_end_us = now; + arg->echo_end = true; + } +} + +void IRAM_ATTR UltrasonicSensorComponent::send_trigger_pulse_() { + InterruptLock lock; + this->store_.echo_start_us = 0; + this->store_.echo_end_us = 0; + this->store_.echo_start = false; + this->store_.echo_end = false; + this->trigger_pin_isr_.digital_write(true); + delayMicroseconds(this->pulse_time_us_); + this->trigger_pin_isr_.digital_write(false); + this->measurement_pending_ = true; + this->measurement_start_us_ = micros(); +} + void UltrasonicSensorComponent::setup() { this->trigger_pin_->setup(); this->trigger_pin_->digital_write(false); + this->trigger_pin_isr_ = this->trigger_pin_->to_isr(); this->echo_pin_->setup(); - // isr is faster to access - echo_isr_ = echo_pin_->to_isr(); + this->echo_pin_->attach_interrupt(UltrasonicSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); } + void UltrasonicSensorComponent::update() { - this->trigger_pin_->digital_write(true); - delayMicroseconds(this->pulse_time_us_); - this->trigger_pin_->digital_write(false); + if (this->measurement_pending_) { + return; + } + this->send_trigger_pulse_(); +} - const uint32_t start = micros(); - while (micros() - start < timeout_us_ && echo_isr_.digital_read()) - ; - while (micros() - start < timeout_us_ && !echo_isr_.digital_read()) - ; - const uint32_t pulse_start = micros(); - while (micros() - start < timeout_us_ && echo_isr_.digital_read()) - ; - const uint32_t pulse_end = micros(); +void UltrasonicSensorComponent::loop() { + if (!this->measurement_pending_) { + return; + } - ESP_LOGV(TAG, "Echo took %" PRIu32 "µs", pulse_end - pulse_start); - - if (pulse_end - start >= timeout_us_) { - ESP_LOGD(TAG, "'%s' - Distance measurement timed out!", this->name_.c_str()); - this->publish_state(NAN); - } else { - float result = UltrasonicSensorComponent::us_to_m(pulse_end - pulse_start); + if (this->store_.echo_end) { + uint32_t pulse_duration = this->store_.echo_end_us - this->store_.echo_start_us; + ESP_LOGV(TAG, "Echo took %" PRIu32 "us", pulse_duration); + float result = UltrasonicSensorComponent::us_to_m(pulse_duration); ESP_LOGD(TAG, "'%s' - Got distance: %.3f m", this->name_.c_str(), result); this->publish_state(result); + this->measurement_pending_ = false; + return; + } + + uint32_t elapsed = micros() - this->measurement_start_us_; + if (elapsed >= this->timeout_us_ + TIMEOUT_MARGIN_US) { + ESP_LOGD(TAG, + "'%s' - Timeout after %" PRIu32 "us (measurement_start=%" PRIu32 ", echo_start=%" PRIu32 + ", echo_end=%" PRIu32 ")", + this->name_.c_str(), elapsed, this->measurement_start_us_, this->store_.echo_start_us, + this->store_.echo_end_us); + this->publish_state(NAN); + this->measurement_pending_ = false; } } + void UltrasonicSensorComponent::dump_config() { LOG_SENSOR("", "Ultrasonic Sensor", this); LOG_PIN(" Echo Pin: ", this->echo_pin_); LOG_PIN(" Trigger Pin: ", this->trigger_pin_); ESP_LOGCONFIG(TAG, - " Pulse time: %" PRIu32 " µs\n" - " Timeout: %" PRIu32 " µs", + " Pulse time: %" PRIu32 " us\n" + " Timeout: %" PRIu32 " us", this->pulse_time_us_, this->timeout_us_); LOG_UPDATE_INTERVAL(this); } + float UltrasonicSensorComponent::us_to_m(uint32_t us) { const float speed_sound_m_per_s = 343.0f; const float time_s = us / 1e6f; const float total_dist = time_s * speed_sound_m_per_s; return total_dist / 2.0f; } -float UltrasonicSensorComponent::get_setup_priority() const { return setup_priority::DATA; } -void UltrasonicSensorComponent::set_pulse_time_us(uint32_t pulse_time_us) { this->pulse_time_us_ = pulse_time_us; } -void UltrasonicSensorComponent::set_timeout_us(uint32_t timeout_us) { this->timeout_us_ = timeout_us; } -} // namespace ultrasonic -} // namespace esphome +} // namespace esphome::ultrasonic diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.h b/esphome/components/ultrasonic/ultrasonic_sensor.h index 1a255d6122..e2266543ce 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.h +++ b/esphome/components/ultrasonic/ultrasonic_sensor.h @@ -6,41 +6,49 @@ #include -namespace esphome { -namespace ultrasonic { +namespace esphome::ultrasonic { + +struct UltrasonicSensorStore { + static void gpio_intr(UltrasonicSensorStore *arg); + + volatile uint32_t echo_start_us{0}; + volatile uint32_t echo_end_us{0}; + volatile bool echo_start{false}; + volatile bool echo_end{false}; +}; class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent { public: - void set_trigger_pin(GPIOPin *trigger_pin) { trigger_pin_ = trigger_pin; } - void set_echo_pin(InternalGPIOPin *echo_pin) { echo_pin_ = echo_pin; } + void set_trigger_pin(InternalGPIOPin *trigger_pin) { this->trigger_pin_ = trigger_pin; } + void set_echo_pin(InternalGPIOPin *echo_pin) { this->echo_pin_ = echo_pin; } /// Set the timeout for waiting for the echo in µs. - void set_timeout_us(uint32_t timeout_us); + void set_timeout_us(uint32_t timeout_us) { this->timeout_us_ = timeout_us; } - // ========== INTERNAL METHODS ========== - // (In most use cases you won't need these) - /// Set up pins and register interval. void setup() override; + void loop() override; void dump_config() override; - void update() override; - float get_setup_priority() const override; + float get_setup_priority() const override { return setup_priority::DATA; } /// Set the time in µs the trigger pin should be enabled for in µs, defaults to 10µs (for HC-SR04) - void set_pulse_time_us(uint32_t pulse_time_us); + void set_pulse_time_us(uint32_t pulse_time_us) { this->pulse_time_us_ = pulse_time_us; } protected: /// Helper function to convert the specified echo duration in µs to meters. static float us_to_m(uint32_t us); - /// Helper function to convert the specified distance in meters to the echo duration in µs. + void send_trigger_pulse_(); - GPIOPin *trigger_pin_; + InternalGPIOPin *trigger_pin_; + ISRInternalGPIOPin trigger_pin_isr_; InternalGPIOPin *echo_pin_; - ISRInternalGPIOPin echo_isr_; - uint32_t timeout_us_{}; /// 2 meters. + UltrasonicSensorStore store_; + uint32_t timeout_us_{}; uint32_t pulse_time_us_{}; + + uint32_t measurement_start_us_{0}; + bool measurement_pending_{false}; }; -} // namespace ultrasonic -} // namespace esphome +} // namespace esphome::ultrasonic From c6713eaccb76cb2ce485ad429df0c948ad1c88bd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 13:07:11 -1000 Subject: [PATCH 0861/1145] [web_server] Fix URL collisions with UTF-8 names and sub-devices (#12627) --- esphome/components/web_server/web_server.cpp | 315 +++++++++++++----- esphome/components/web_server/web_server.h | 40 +-- .../components/web_server/web_server_v1.cpp | 44 ++- esphome/components/web_server_idf/utils.cpp | 6 +- esphome/components/web_server_idf/utils.h | 4 + .../web_server_idf/web_server_idf.cpp | 17 +- esphome/config_validation.py | 39 +++ esphome/core/config.py | 8 +- esphome/core/entity_base.h | 2 + tests/unit_tests/test_config_validation.py | 57 ++++ 10 files changed, 413 insertions(+), 119 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index f613d6bc36..7c015adcf7 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -45,62 +45,144 @@ static constexpr size_t PSTR_LOCAL_SIZE = 18; #define PSTR_LOCAL(mode_s) ESPHOME_strncpy_P(buf, (ESPHOME_PGM_P) ((mode_s)), PSTR_LOCAL_SIZE - 1) // Parse URL and return match info -static UrlMatch match_url(const char *url_ptr, size_t url_len, bool only_domain) { - UrlMatch match{}; +// URL formats (disambiguated by HTTP method for 3-segment case): +// GET /{domain}/{entity_name} - main device state +// POST /{domain}/{entity_name}/{action} - main device action +// GET /{domain}/{device_name}/{entity_name} - sub-device state (USE_DEVICES only) +// POST /{domain}/{device_name}/{entity_name}/{action} - sub-device action (USE_DEVICES only) +static UrlMatch match_url(const char *url_ptr, size_t url_len, bool only_domain, bool is_post = false) { + // URL must start with '/' and have content after it + if (url_len < 2 || url_ptr[0] != '/') + return UrlMatch{}; - // URL must start with '/' - if (url_len < 2 || url_ptr[0] != '/') { - return match; - } - - // Skip leading '/' - const char *start = url_ptr + 1; + const char *p = url_ptr + 1; const char *end = url_ptr + url_len; - // Find domain (everything up to next '/' or end) - const char *domain_end = (const char *) memchr(start, '/', end - start); - if (!domain_end) { - // No second slash found - original behavior returns invalid - return match; - } + // Helper to find next segment: returns pointer after '/' or nullptr if no more slashes + auto next_segment = [&end](const char *start) -> const char * { + const char *slash = (const char *) memchr(start, '/', end - start); + return slash ? slash + 1 : nullptr; + }; - // Set domain - match.domain = start; - match.domain_len = domain_end - start; + // Helper to make StringRef from segment start to next segment (or end) + auto make_ref = [&end](const char *start, const char *next_start) -> StringRef { + return StringRef(start, (next_start ? next_start - 1 : end) - start); + }; + + // Parse domain segment + const char *s1 = p; + const char *s2 = next_segment(s1); + + // Must have domain with trailing slash + if (!s2) + return UrlMatch{}; + + UrlMatch match{}; + match.domain = make_ref(s1, s2); match.valid = true; - if (only_domain) { + if (only_domain || s2 >= end) return match; - } - // Parse ID if present - if (domain_end + 1 >= end) { - return match; // Nothing after domain slash - } + // Parse remaining segments only when needed + const char *s3 = next_segment(s2); + const char *s4 = s3 ? next_segment(s3) : nullptr; - const char *id_start = domain_end + 1; - const char *id_end = (const char *) memchr(id_start, '/', end - id_start); + StringRef seg2 = make_ref(s2, s3); + StringRef seg3 = s3 ? make_ref(s3, s4) : StringRef(); + StringRef seg4 = s4 ? make_ref(s4, nullptr) : StringRef(); - if (!id_end) { - // No more slashes, entire remaining string is ID - match.id = id_start; - match.id_len = end - id_start; - return match; - } + // Reject empty segments + if (seg2.empty() || (s3 && seg3.empty()) || (s4 && seg4.empty())) + return UrlMatch{}; - // Set ID - match.id = id_start; - match.id_len = id_end - id_start; - - // Parse method if present - if (id_end + 1 < end) { - match.method = id_end + 1; - match.method_len = end - (id_end + 1); + // Interpret based on segment count + if (!s3) { + // 1 segment after domain: /{domain}/{entity} + match.id = seg2; + } else if (!s4) { + // 2 segments after domain: /{domain}/{X}/{Y} + // HTTP method disambiguates: GET = device/entity, POST = entity/action + if (is_post) { + match.id = seg2; + match.method = seg3; + return match; + } +#ifdef USE_DEVICES + match.device_name = seg2; + match.id = seg3; +#else + return UrlMatch{}; // 3-segment GET not supported without USE_DEVICES +#endif + } else { + // 3 segments after domain: /{domain}/{device}/{entity}/{action} +#ifdef USE_DEVICES + if (!is_post) { + return UrlMatch{}; // 4-segment GET not supported (action requires POST) + } + match.device_name = seg2; + match.id = seg3; + match.method = seg4; +#else + return UrlMatch{}; // Not supported without USE_DEVICES +#endif } return match; } +EntityMatchResult UrlMatch::match_entity(EntityBase *entity) const { + EntityMatchResult result{false, this->method.empty()}; + +#ifdef USE_DEVICES + Device *entity_device = entity->get_device(); + bool url_has_device = !this->device_name.empty(); + bool entity_has_device = (entity_device != nullptr); + + // Device matching: URL device segment must match entity's device + if (url_has_device != entity_has_device) { + return result; // Mismatch: one has device, other doesn't + } + if (url_has_device && this->device_name != entity_device->get_name()) { + return result; // Device name doesn't match + } +#endif + + // Try matching by entity name (new format) + if (this->id == entity->get_name()) { + result.matched = true; + return result; + } + + // Fall back to object_id (deprecated format) + char object_id_buf[OBJECT_ID_MAX_LEN]; + StringRef object_id = entity->get_object_id_to(object_id_buf); + if (this->id == object_id) { + result.matched = true; + // Log deprecation warning +#ifdef USE_DEVICES + Device *device = entity->get_device(); + if (device != nullptr) { + ESP_LOGW(TAG, + "Deprecated URL format: /%.*s/%.*s/%.*s - use entity name '/%.*s/%s/%s' instead. " + "Object ID URLs will be removed in 2026.7.0.", + (int) this->domain.size(), this->domain.c_str(), (int) this->device_name.size(), + this->device_name.c_str(), (int) this->id.size(), this->id.c_str(), (int) this->domain.size(), + this->domain.c_str(), device->get_name(), entity->get_name().c_str()); + } else +#endif + { + ESP_LOGW(TAG, + "Deprecated URL format: /%.*s/%.*s - use entity name '/%.*s/%s' instead. " + "Object ID URLs will be removed in 2026.7.0.", + (int) this->domain.size(), this->domain.c_str(), (int) this->id.size(), this->id.c_str(), + (int) this->domain.size(), this->domain.c_str(), entity->get_name().c_str()); + } + } + + return result; +} + #if !defined(USE_ESP32) && defined(USE_ARDUINO) // helper for allowing only unique entries in the queue void DeferredUpdateEventSource::deq_push_back_with_dedup_(void *source, message_generator_t *message_generator) { @@ -397,15 +479,53 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) { #endif // Helper functions to reduce code size by avoiding macro expansion +// Build unique id as: {domain}/{device_name}/{entity_name} or {domain}/{entity_name} +// Uses names (not object_id) to avoid UTF-8 collision issues static void set_json_id(JsonObject &root, EntityBase *obj, const char *prefix, JsonDetail start_config) { - char id_buf[160]; // prefix + dash + object_id (up to 128) + null - size_t len = strlen(prefix); - memcpy(id_buf, prefix, len); // NOLINT(bugprone-not-null-terminated-result) - null added by write_object_id_to - id_buf[len++] = '-'; - obj->write_object_id_to(id_buf + len, sizeof(id_buf) - len); + const StringRef &name = obj->get_name(); + size_t prefix_len = strlen(prefix); + size_t name_len = name.size(); + +#ifdef USE_DEVICES + Device *device = obj->get_device(); + const char *device_name = device ? device->get_name() : nullptr; + size_t device_len = device_name ? strlen(device_name) : 0; +#endif + + // Build id into stack buffer - ArduinoJson copies the string + // Format: {prefix}/{device?}/{name} + // Buffer size guaranteed by schema validation (NAME_MAX_LENGTH=120): + // With devices: domain(20) + "/" + device(120) + "/" + name(120) + null = 263, rounded up to 280 for safety margin + // Without devices: domain(20) + "/" + name(120) + null = 142, rounded up to 150 for safety margin +#ifdef USE_DEVICES + char id_buf[280]; +#else + char id_buf[150]; +#endif + char *p = id_buf; + memcpy(p, prefix, prefix_len); + p += prefix_len; + *p++ = '/'; +#ifdef USE_DEVICES + if (device_name) { + memcpy(p, device_name, device_len); + p += device_len; + *p++ = '/'; + } +#endif + memcpy(p, name.c_str(), name_len); + p[name_len] = '\0'; + root[ESPHOME_F("id")] = id_buf; + if (start_config == DETAIL_ALL) { - root[ESPHOME_F("name")] = obj->get_name(); + root[ESPHOME_F("domain")] = prefix; + root[ESPHOME_F("name")] = name; +#ifdef USE_DEVICES + if (device_name) { + root[ESPHOME_F("device")] = device_name; + } +#endif root[ESPHOME_F("icon")] = obj->get_icon_ref(); root[ESPHOME_F("entity_category")] = obj->get_entity_category(); bool is_disabled = obj->is_disabled_by_default(); @@ -444,10 +564,11 @@ void WebServer::on_sensor_update(sensor::Sensor *obj) { } void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (sensor::Sensor *obj : App.get_sensors()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; // Note: request->method() is always HTTP_GET here (canHandle ensures this) - if (match.method_empty()) { + if (entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->sensor_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); @@ -490,10 +611,11 @@ void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj) { } void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (text_sensor::TextSensor *obj : App.get_text_sensors()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; // Note: request->method() is always HTTP_GET here (canHandle ensures this) - if (match.method_empty()) { + if (entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->text_sensor_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); @@ -532,10 +654,11 @@ void WebServer::on_switch_update(switch_::Switch *obj) { } void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (switch_::Switch *obj : App.get_switches()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->switch_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); @@ -601,9 +724,10 @@ std::string WebServer::switch_json_(switch_::Switch *obj, bool value, JsonDetail #ifdef USE_BUTTON void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (button::Button *obj : App.get_buttons()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->button_json_(obj, detail); request->send(200, "application/json", data.c_str()); @@ -645,10 +769,11 @@ void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) { } void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; // Note: request->method() is always HTTP_GET here (canHandle ensures this) - if (match.method_empty()) { + if (entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->binary_sensor_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); @@ -686,10 +811,11 @@ void WebServer::on_fan_update(fan::Fan *obj) { } void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (fan::Fan *obj : App.get_fans()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->fan_json_(obj, detail); request->send(200, "application/json", data.c_str()); @@ -766,10 +892,11 @@ void WebServer::on_light_update(light::LightState *obj) { } void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (light::LightState *obj : App.get_lights()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->light_json_(obj, detail); request->send(200, "application/json", data.c_str()); @@ -844,10 +971,11 @@ void WebServer::on_cover_update(cover::Cover *obj) { } void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (cover::Cover *obj : App.get_covers()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->cover_json_(obj, detail); request->send(200, "application/json", data.c_str()); @@ -932,10 +1060,11 @@ void WebServer::on_number_update(number::Number *obj) { } void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_numbers()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->number_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); @@ -999,9 +1128,10 @@ void WebServer::on_date_update(datetime::DateEntity *obj) { } void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_dates()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->date_json_(obj, detail); request->send(200, "application/json", data.c_str()); @@ -1062,9 +1192,10 @@ void WebServer::on_time_update(datetime::TimeEntity *obj) { } void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_times()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->time_json_(obj, detail); request->send(200, "application/json", data.c_str()); @@ -1124,9 +1255,10 @@ void WebServer::on_datetime_update(datetime::DateTimeEntity *obj) { } void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_datetimes()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->datetime_json_(obj, detail); request->send(200, "application/json", data.c_str()); @@ -1188,10 +1320,11 @@ void WebServer::on_text_update(text::Text *obj) { } void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_texts()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->text_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); @@ -1244,10 +1377,11 @@ void WebServer::on_select_update(select::Select *obj) { } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_selects()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->select_json_(obj, obj->has_state() ? obj->current_option() : "", detail); request->send(200, "application/json", data.c_str()); @@ -1301,10 +1435,11 @@ void WebServer::on_climate_update(climate::Climate *obj) { } void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_climates()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->climate_json_(obj, detail); request->send(200, "application/json", data.c_str()); @@ -1451,10 +1586,11 @@ void WebServer::on_lock_update(lock::Lock *obj) { } void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (lock::Lock *obj : App.get_locks()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->lock_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); @@ -1525,10 +1661,11 @@ void WebServer::on_valve_update(valve::Valve *obj) { } void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (valve::Valve *obj : App.get_valves()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->valve_json_(obj, detail); request->send(200, "application/json", data.c_str()); @@ -1609,10 +1746,11 @@ void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP } void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (alarm_control_panel::AlarmControlPanel *obj : App.get_alarm_control_panels()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->alarm_control_panel_json_(obj, obj->get_state(), detail); request->send(200, "application/json", data.c_str()); @@ -1690,11 +1828,12 @@ void WebServer::on_event(event::Event *obj) { void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (event::Event *obj : App.get_events()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; // Note: request->method() is always HTTP_GET here (canHandle ensures this) - if (match.method_empty()) { + if (entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->event_json_(obj, "", detail); request->send(200, "application/json", data.c_str()); @@ -1759,10 +1898,11 @@ void WebServer::on_update(update::UpdateEntity *obj) { } void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (update::UpdateEntity *obj : App.get_updates()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); std::string data = this->update_json_(obj, detail); request->send(200, "application/json", data.c_str()); @@ -1973,7 +2113,8 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { #endif // Parse URL for component routing - UrlMatch match = match_url(url.c_str(), url.length(), false); + // Pass HTTP method to disambiguate 3-segment URLs (GET=sub-device state, POST=main device action) + UrlMatch match = match_url(url.c_str(), url.length(), false, request->method() == HTTP_POST); // Route to appropriate handler based on domain // NOLINTNEXTLINE(readability-simplify-boolean-expr) diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index b9e852c745..3e1dd867c6 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -35,33 +35,29 @@ extern const size_t ESPHOME_WEBSERVER_JS_INCLUDE_SIZE; namespace esphome::web_server { +/// Result of matching a URL against an entity +struct EntityMatchResult { + bool matched; ///< True if entity matched the URL + bool action_is_empty; ///< True if no action/method segment in URL +}; + /// Internal helper struct that is used to parse incoming URLs struct UrlMatch { - const char *domain; ///< Pointer to domain within URL, for example "sensor" - const char *id; ///< Pointer to id within URL, for example "living_room_fan" - const char *method; ///< Pointer to method within URL, for example "turn_on" - uint8_t domain_len; ///< Length of domain string - uint8_t id_len; ///< Length of id string - uint8_t method_len; ///< Length of method string - bool valid; ///< Whether this match is valid + StringRef domain; ///< Domain within URL, for example "sensor" + StringRef id; ///< Entity name/id within URL, for example "Temperature" + StringRef method; ///< Method within URL, for example "turn_on" +#ifdef USE_DEVICES + StringRef device_name; ///< Device name within URL, empty for main device +#endif + bool valid{false}; ///< Whether this match is valid // Helper methods for string comparisons - bool domain_equals(const char *str) const { - return domain && domain_len == strlen(str) && memcmp(domain, str, domain_len) == 0; - } + bool domain_equals(const char *str) const { return this->domain == str; } + bool method_equals(const char *str) const { return this->method == str; } - bool id_equals_entity(EntityBase *entity) const { - // Get object_id with zero heap allocation - char object_id_buf[OBJECT_ID_MAX_LEN]; - StringRef object_id = entity->get_object_id_to(object_id_buf); - return id && id_len == object_id.size() && memcmp(id, object_id.c_str(), id_len) == 0; - } - - bool method_equals(const char *str) const { - return method && method_len == strlen(str) && memcmp(method, str, method_len) == 0; - } - - bool method_empty() const { return method_len == 0; } + /// Match entity by name first, then fall back to object_id with deprecation warning + /// Returns EntityMatchResult with match status and whether action segment is empty + EntityMatchResult match_entity(EntityBase *entity) const; }; #ifdef USE_WEBSERVER_SORTING diff --git a/esphome/components/web_server/web_server_v1.cpp b/esphome/components/web_server/web_server_v1.cpp index e27306ad78..a21e9cb9ff 100644 --- a/esphome/components/web_server/web_server_v1.cpp +++ b/esphome/components/web_server/web_server_v1.cpp @@ -5,6 +5,29 @@ namespace esphome::web_server { +// Write HTML-escaped text to stream (escapes ", &, <, >) +static void write_html_escaped(AsyncResponseStream *stream, const char *text) { + for (const char *p = text; *p; ++p) { + switch (*p) { + case '"': + stream->print("""); + break; + case '&': + stream->print("&"); + break; + case '<': + stream->print("<"); + break; + case '>': + stream->print(">"); + break; + default: + stream->write(*p); + break; + } + } +} + void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action, const std::function &action_func = nullptr) { stream->print("print("-"); char object_id_buf[OBJECT_ID_MAX_LEN]; stream->print(obj->get_object_id_to(object_id_buf).c_str()); + // Add data attributes for hierarchical URL support + stream->print("\" data-domain=\""); + stream->print(klass.c_str()); + stream->print("\" data-name=\""); + write_html_escaped(stream, obj->get_name().c_str()); +#ifdef USE_DEVICES + Device *device = obj->get_device(); + if (device != nullptr) { + stream->print("\" data-device=\""); + write_html_escaped(stream, device->get_name()); + } +#endif stream->print("\">"); - stream->print(obj->get_name().c_str()); +#ifdef USE_DEVICES + if (device != nullptr) { + stream->print("["); + write_html_escaped(stream, device->get_name()); + stream->print("] "); + } +#endif + write_html_escaped(stream, obj->get_name().c_str()); stream->print(""); stream->print(action.c_str()); if (action_func) { diff --git a/esphome/components/web_server_idf/utils.cpp b/esphome/components/web_server_idf/utils.cpp index d5d34b520b..f27814062c 100644 --- a/esphome/components/web_server_idf/utils.cpp +++ b/esphome/components/web_server_idf/utils.cpp @@ -13,7 +13,8 @@ namespace web_server_idf { static const char *const TAG = "web_server_idf_utils"; -void url_decode(char *str) { +size_t url_decode(char *str) { + char *start = str; char *ptr = str, buf; for (; *str; str++, ptr++) { if (*str == '%') { @@ -31,7 +32,8 @@ void url_decode(char *str) { *ptr = *str; } } - *ptr = *str; + *ptr = '\0'; + return ptr - start; } bool request_has_header(httpd_req_t *req, const char *name) { return httpd_req_get_hdr_value_len(req, name); } diff --git a/esphome/components/web_server_idf/utils.h b/esphome/components/web_server_idf/utils.h index f70a5f0760..3a86aec7ac 100644 --- a/esphome/components/web_server_idf/utils.h +++ b/esphome/components/web_server_idf/utils.h @@ -8,6 +8,10 @@ namespace esphome { namespace web_server_idf { +/// Decode URL-encoded string in-place (e.g., %20 -> space, + -> space) +/// Returns the new length of the decoded string +size_t url_decode(char *str); + bool request_has_header(httpd_req_t *req, const char *name); optional request_get_header(httpd_req_t *req, const char *name); optional request_get_url_query(httpd_req_t *req); diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 3d76b86a14..5062aa1e6c 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -247,11 +247,20 @@ optional AsyncWebServerRequest::get_header(const char *name) const } std::string AsyncWebServerRequest::url() const { - auto *str = strchr(this->req_->uri, '?'); - if (str == nullptr) { - return this->req_->uri; + auto *query_start = strchr(this->req_->uri, '?'); + std::string result; + if (query_start == nullptr) { + result = this->req_->uri; + } else { + result = std::string(this->req_->uri, query_start - this->req_->uri); } - return std::string(this->req_->uri, str - this->req_->uri); + // Decode URL-encoded characters in-place (e.g., %20 -> space) + // This matches AsyncWebServer behavior on Arduino + if (!result.empty()) { + size_t new_len = url_decode(&result[0]); + result.resize(new_len); + } + return result; } std::string AsyncWebServerRequest::host() const { return this->get_header("Host").value(); } diff --git a/esphome/config_validation.py b/esphome/config_validation.py index d085206ee8..b0da88c50d 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1981,6 +1981,26 @@ MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( ) +def _validate_no_slash(value): + """Validate that a name does not contain '/' characters. + + The '/' character is used as a path separator in web server URLs, + so it cannot be used in entity or device names. + """ + if "/" in value: + raise Invalid( + f"Name cannot contain '/' character (used as URL path separator): {value}" + ) + return value + + +# Maximum length for entity, device, and area names +# This ensures web server URL IDs fit in a 280-byte buffer: +# domain(20) + "/" + device(120) + "/" + name(120) + null = 263 bytes +# Note: Must be < 255 because web_server UrlMatch uses uint8_t for length fields +NAME_MAX_LENGTH = 120 + + def _validate_entity_name(value): value = string(value) try: @@ -1991,9 +2011,28 @@ def _validate_entity_name(value): requires_friendly_name( "Name cannot be None when esphome->friendly_name is not set!" )(value) + if value is not None: + # Validate length for web server URL compatibility + if len(value) > NAME_MAX_LENGTH: + raise Invalid( + f"Name is too long ({len(value)} chars). " + f"Maximum length is {NAME_MAX_LENGTH} characters." + ) + # Validate no '/' in name for web server URL compatibility + _validate_no_slash(value) return value +def string_no_slash(value): + """Validate a string that cannot contain '/' characters. + + Used for device and area names where '/' is reserved as a URL path separator. + Use with cv.Length() to also enforce maximum length. + """ + value = string(value) + return _validate_no_slash(value) + + ENTITY_BASE_SCHEMA = Schema( { Optional(CONF_NAME): _validate_entity_name, diff --git a/esphome/core/config.py b/esphome/core/config.py index 5e32b9380d..f9c3011507 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -186,14 +186,14 @@ else: AREA_SCHEMA = cv.Schema( { cv.GenerateID(CONF_ID): cv.declare_id(Area), - cv.Required(CONF_NAME): cv.string, + cv.Required(CONF_NAME): cv.All(cv.string_no_slash, cv.Length(max=120)), } ) DEVICE_SCHEMA = cv.Schema( { cv.GenerateID(CONF_ID): cv.declare_id(Device), - cv.Required(CONF_NAME): cv.string, + cv.Required(CONF_NAME): cv.All(cv.string_no_slash, cv.Length(max=120)), cv.Optional(CONF_AREA_ID): cv.use_id(Area), } ) @@ -207,7 +207,9 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.Required(CONF_NAME): cv.valid_name, - cv.Optional(CONF_FRIENDLY_NAME, ""): cv.All(cv.string, cv.Length(max=120)), + cv.Optional(CONF_FRIENDLY_NAME, ""): cv.All( + cv.string_no_slash, cv.Length(max=120) + ), cv.Optional(CONF_AREA): validate_area_config, cv.Optional(CONF_COMMENT): cv.All(cv.string, cv.Length(max=255)), cv.Required(CONF_BUILD_PATH): cv.string, diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 93f989934a..a5c69f132c 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -100,6 +100,8 @@ class EntityBase { return this->device_->get_device_id(); } void set_device(Device *device) { this->device_ = device; } + // Get the device this entity belongs to (nullptr if main device) + Device *get_device() const { return this->device_; } #endif // Check if this entity has state diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index c9d7b7486e..94224f2364 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -502,3 +502,60 @@ def test_only_with_user_value_overrides_default() -> None: result = schema({"mqtt_id": "custom_id"}) assert result.get("mqtt_id") == "custom_id" + + +@pytest.mark.parametrize("value", ("hello", "Hello World", "test_name", "温度")) +def test_string_no_slash__valid(value: str) -> None: + actual = config_validation.string_no_slash(value) + assert actual == value + + +@pytest.mark.parametrize("value", ("has/slash", "a/b/c", "/leading", "trailing/")) +def test_string_no_slash__slash_rejected(value: str) -> None: + with pytest.raises(Invalid, match="cannot contain '/' character"): + config_validation.string_no_slash(value) + + +def test_string_no_slash__long_string_allowed() -> None: + # string_no_slash doesn't enforce length - use cv.Length() separately + long_value = "x" * 200 + assert config_validation.string_no_slash(long_value) == long_value + + +def test_string_no_slash__empty() -> None: + assert config_validation.string_no_slash("") == "" + + +@pytest.mark.parametrize("value", ("Temperature", "Living Room Light", "温度传感器")) +def test_validate_entity_name__valid(value: str) -> None: + actual = config_validation._validate_entity_name(value) + assert actual == value + + +def test_validate_entity_name__slash_rejected() -> None: + with pytest.raises(Invalid, match="cannot contain '/' character"): + config_validation._validate_entity_name("has/slash") + + +def test_validate_entity_name__max_length() -> None: + # 120 chars should pass + assert config_validation._validate_entity_name("x" * 120) == "x" * 120 + + # 121 chars should fail + with pytest.raises(Invalid, match="too long.*121 chars.*Maximum.*120"): + config_validation._validate_entity_name("x" * 121) + + +def test_validate_entity_name__none_without_friendly_name() -> None: + # When name is "None" and friendly_name is not set, it should fail + CORE.friendly_name = None + with pytest.raises(Invalid, match="friendly_name is not set"): + config_validation._validate_entity_name("None") + + +def test_validate_entity_name__none_with_friendly_name() -> None: + # When name is "None" but friendly_name is set, it should return None + CORE.friendly_name = "My Device" + result = config_validation._validate_entity_name("None") + assert result is None + CORE.friendly_name = None # Reset From 5bb9ffa0cbb28b3875b06df1913f8b6015e852c6 Mon Sep 17 00:00:00 2001 From: esphomebot Date: Sat, 3 Jan 2026 12:14:11 +1300 Subject: [PATCH 0862/1145] Update webserver local assets to 20260102-230255 (#12817) --- .../components/web_server/server_index_v2.h | 1276 +-- .../components/web_server/server_index_v3.h | 8086 +++++++++-------- 2 files changed, 4690 insertions(+), 4672 deletions(-) diff --git a/esphome/components/web_server/server_index_v2.h b/esphome/components/web_server/server_index_v2.h index b2d204c9e7..4f2ea8a6ab 100644 --- a/esphome/components/web_server/server_index_v2.h +++ b/esphome/components/web_server/server_index_v2.h @@ -6,644 +6,652 @@ #include "esphome/core/hal.h" -namespace esphome::web_server { +namespace esphome { +namespace web_server { const uint8_t INDEX_GZ[] PROGMEM = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcd, 0x7d, 0xdb, 0x72, 0xdb, 0xc6, 0xb6, 0xe0, 0xf3, - 0xe4, 0x2b, 0x20, 0x44, 0x5b, 0x41, 0x6f, 0x36, 0x21, 0x92, 0x92, 0x6c, 0x19, 0x54, 0x93, 0x5b, 0x96, 0x9d, 0xed, - 0x64, 0xfb, 0x16, 0xcb, 0x4e, 0x76, 0xc2, 0x68, 0x4b, 0x10, 0xd1, 0x24, 0x3a, 0x06, 0xd1, 0x0c, 0xd0, 0xa4, 0xa4, - 0x90, 0x38, 0x35, 0x1f, 0x30, 0x55, 0x53, 0x35, 0x4f, 0xf3, 0x32, 0x35, 0xe7, 0x61, 0x3e, 0x62, 0x9e, 0xcf, 0xa7, - 0x9c, 0x1f, 0x98, 0xf9, 0x84, 0xa9, 0xd5, 0x17, 0xa0, 0xc1, 0x8b, 0xac, 0x5c, 0xce, 0x39, 0x53, 0x2e, 0xdb, 0x44, - 0xa3, 0x2f, 0xab, 0x57, 0xaf, 0x5e, 0xf7, 0x6e, 0x9c, 0xec, 0x44, 0x7c, 0x28, 0xee, 0xa6, 0xd4, 0x89, 0xc5, 0x24, - 0xe9, 0x9d, 0xe8, 0x7f, 0x69, 0x18, 0xf5, 0x4e, 0x12, 0x96, 0x7e, 0x74, 0x32, 0x9a, 0x10, 0x36, 0xe4, 0xa9, 0x13, - 0x67, 0x74, 0x44, 0xa2, 0x50, 0x84, 0x01, 0x9b, 0x84, 0x63, 0xea, 0xec, 0xf7, 0x4e, 0x26, 0x54, 0x84, 0xce, 0x30, - 0x0e, 0xb3, 0x9c, 0x0a, 0xf2, 0xe1, 0xfd, 0x97, 0xcd, 0xe3, 0xde, 0x49, 0x3e, 0xcc, 0xd8, 0x54, 0x38, 0xd0, 0x25, - 0x99, 0xf0, 0x68, 0x96, 0xd0, 0xde, 0xfe, 0xfe, 0xcd, 0xcd, 0x8d, 0xff, 0x53, 0xfe, 0xd9, 0x90, 0xa7, 0xb9, 0x70, - 0x5e, 0x92, 0x1b, 0x96, 0x46, 0xfc, 0x06, 0x33, 0x41, 0x5e, 0xfa, 0xe7, 0x71, 0x18, 0xf1, 0x9b, 0x77, 0x9c, 0x8b, - 0xbd, 0x3d, 0x4f, 0x3d, 0xde, 0x9d, 0x9d, 0x9f, 0x13, 0x42, 0xe6, 0x9c, 0x45, 0x4e, 0x6b, 0xb9, 0xac, 0x0a, 0xfd, - 0x34, 0x14, 0x6c, 0x4e, 0x55, 0x13, 0xb4, 0xb7, 0xe7, 0x86, 0x11, 0x9f, 0x0a, 0x1a, 0x9d, 0x8b, 0xbb, 0x84, 0x9e, - 0xc7, 0x94, 0x8a, 0xdc, 0x65, 0xa9, 0xf3, 0x8c, 0x0f, 0x67, 0x13, 0x9a, 0x0a, 0x7f, 0x9a, 0x71, 0xc1, 0x01, 0x92, - 0xbd, 0x3d, 0x37, 0xa3, 0xd3, 0x24, 0x1c, 0x52, 0x78, 0x7f, 0x76, 0x7e, 0x5e, 0xb5, 0xa8, 0x2a, 0xe1, 0x5c, 0x90, - 0xf3, 0xbb, 0xc9, 0x35, 0x4f, 0x3c, 0x84, 0x43, 0x41, 0x52, 0x7a, 0xe3, 0x7c, 0x47, 0xc3, 0x8f, 0xaf, 0xc2, 0x69, - 0x77, 0x98, 0x84, 0x79, 0xee, 0xdc, 0x88, 0x85, 0x9c, 0x42, 0x36, 0x1b, 0x0a, 0x9e, 0x79, 0x02, 0x53, 0xcc, 0xd0, - 0x82, 0x8d, 0x3c, 0x11, 0xb3, 0xdc, 0xbf, 0xdc, 0x1d, 0xe6, 0xf9, 0x3b, 0x9a, 0xcf, 0x12, 0xb1, 0x4b, 0x76, 0x5a, - 0x98, 0xed, 0x10, 0x92, 0x0b, 0x24, 0xe2, 0x8c, 0xdf, 0x38, 0xcf, 0xb3, 0x8c, 0x67, 0x9e, 0x7b, 0x76, 0x7e, 0xae, - 0x6a, 0x38, 0x2c, 0x77, 0x52, 0x2e, 0x9c, 0xb2, 0xbf, 0xf0, 0x3a, 0xa1, 0xbe, 0xf3, 0x21, 0xa7, 0xce, 0xd5, 0x2c, - 0xcd, 0xc3, 0x11, 0x3d, 0x3b, 0x3f, 0xbf, 0x72, 0x78, 0xe6, 0x5c, 0x0d, 0xf3, 0xfc, 0xca, 0x61, 0x69, 0x2e, 0x68, - 0x18, 0xf9, 0x2e, 0xea, 0xca, 0xc1, 0x86, 0x79, 0xfe, 0x9e, 0xde, 0x0a, 0x22, 0xb0, 0x7c, 0x14, 0x84, 0x16, 0x63, - 0x2a, 0x9c, 0xbc, 0x9c, 0x97, 0x87, 0x16, 0x09, 0x15, 0x8e, 0x20, 0xf2, 0x3d, 0xef, 0x2a, 0xdc, 0x53, 0xf5, 0x28, - 0xba, 0x6c, 0xe4, 0x31, 0xb1, 0xb7, 0x27, 0x4a, 0x3c, 0x23, 0x35, 0x35, 0x87, 0x11, 0xba, 0x63, 0xca, 0xf6, 0xf6, - 0xa8, 0x9f, 0xd0, 0x74, 0x2c, 0x62, 0x42, 0x48, 0xbb, 0xcb, 0xf6, 0xf6, 0x3c, 0x41, 0x42, 0xe1, 0x8f, 0xa9, 0xf0, - 0x28, 0x42, 0xb8, 0x6a, 0xbd, 0xb7, 0xe7, 0x29, 0x24, 0x70, 0xa2, 0x10, 0x57, 0xc3, 0x31, 0xf2, 0x35, 0xf6, 0xcf, - 0xef, 0xd2, 0xa1, 0x67, 0xc3, 0x8f, 0x30, 0xdb, 0xdb, 0x0b, 0x85, 0x9f, 0x43, 0x8f, 0x58, 0x20, 0x54, 0x64, 0x54, - 0xcc, 0xb2, 0xd4, 0x11, 0x85, 0xe0, 0xe7, 0x22, 0x63, 0xe9, 0xd8, 0x43, 0x0b, 0x53, 0x66, 0x35, 0x2c, 0x0a, 0x05, - 0xee, 0x07, 0x41, 0x52, 0xd2, 0x83, 0x11, 0x6f, 0x84, 0x07, 0xab, 0xc8, 0x47, 0x4e, 0x4a, 0x88, 0x9b, 0xcb, 0xb6, - 0x6e, 0x3f, 0x0d, 0xd2, 0x86, 0xeb, 0x62, 0x05, 0x25, 0xce, 0x05, 0xc2, 0x6f, 0x88, 0x97, 0x62, 0xdf, 0xf7, 0x05, - 0x22, 0xbd, 0x85, 0xc1, 0x4a, 0x6a, 0xcd, 0xb3, 0x9f, 0x0e, 0x5a, 0x17, 0x81, 0xf0, 0x33, 0x1a, 0xcd, 0x86, 0xd4, - 0xf3, 0x18, 0xce, 0x71, 0x86, 0x48, 0x8f, 0x35, 0x3c, 0x4e, 0x7a, 0xb0, 0xdc, 0xbc, 0xbe, 0xd6, 0x84, 0xec, 0xb4, - 0x90, 0x86, 0x91, 0x1b, 0x00, 0x01, 0xc3, 0x1a, 0x1e, 0x4e, 0x88, 0x9b, 0xce, 0x26, 0xd7, 0x34, 0x73, 0xcb, 0x6a, - 0xdd, 0x1a, 0x59, 0xcc, 0x72, 0xea, 0x0c, 0xf3, 0xdc, 0x19, 0xcd, 0xd2, 0xa1, 0x60, 0x3c, 0x75, 0xdc, 0x06, 0x6f, - 0xb8, 0x8a, 0x1c, 0x4a, 0x6a, 0x70, 0x51, 0x81, 0xbc, 0x1c, 0x35, 0xd2, 0x41, 0xd6, 0x68, 0x5f, 0x60, 0x80, 0x12, - 0x75, 0x75, 0x7f, 0x1a, 0x01, 0x14, 0xa7, 0x30, 0xc7, 0x02, 0xbf, 0x17, 0x30, 0x4b, 0x39, 0x45, 0x26, 0xfa, 0xa9, - 0xbf, 0xbe, 0x51, 0x88, 0xf0, 0x27, 0xe1, 0xd4, 0xa3, 0xa4, 0x47, 0x25, 0x71, 0x85, 0xe9, 0x10, 0x60, 0xad, 0xad, - 0x5b, 0x9f, 0x06, 0xd4, 0xaf, 0x48, 0x0a, 0x05, 0xc2, 0x1f, 0xf1, 0xec, 0x79, 0x38, 0x8c, 0xa1, 0x5d, 0x49, 0x30, - 0x91, 0xd9, 0x6f, 0xc3, 0x8c, 0x86, 0x82, 0x3e, 0x4f, 0x28, 0x3c, 0x79, 0xae, 0x6c, 0xe9, 0x22, 0x9c, 0x93, 0x97, - 0x7e, 0xc2, 0xc4, 0x6b, 0x9e, 0x0e, 0x69, 0x37, 0xb7, 0xa8, 0x8b, 0xc1, 0xba, 0x9f, 0x0a, 0x91, 0xb1, 0xeb, 0x99, - 0xa0, 0x9e, 0x9b, 0x42, 0x0d, 0x17, 0xe7, 0x08, 0x33, 0x5f, 0xd0, 0x5b, 0x71, 0xc6, 0x53, 0x41, 0x53, 0x41, 0xa8, - 0x41, 0x2a, 0x4e, 0xfd, 0x70, 0x3a, 0xa5, 0x69, 0x74, 0x16, 0xb3, 0x24, 0xf2, 0x18, 0x2a, 0x50, 0x81, 0x63, 0x41, - 0x60, 0x8e, 0xa4, 0x97, 0x06, 0xf0, 0xcf, 0xf6, 0xd9, 0x78, 0x82, 0xf4, 0xe4, 0xa6, 0xa0, 0xc4, 0x75, 0xbb, 0x23, - 0x9e, 0x79, 0x7a, 0x06, 0x0e, 0x1f, 0x39, 0x02, 0xc6, 0x78, 0x37, 0x4b, 0x68, 0x8e, 0x68, 0x83, 0xb0, 0x72, 0x19, - 0x35, 0x82, 0x3f, 0x00, 0xc5, 0x17, 0xc8, 0x4b, 0x51, 0x90, 0x76, 0xe7, 0x61, 0xe6, 0x7c, 0xa7, 0x77, 0xd4, 0x33, - 0xc3, 0xcd, 0x86, 0x82, 0x3c, 0xf3, 0x45, 0x36, 0xcb, 0x05, 0x8d, 0xde, 0xdf, 0x4d, 0x69, 0x8e, 0x5f, 0x0b, 0x32, - 0x14, 0xfd, 0xa1, 0xf0, 0xe9, 0x64, 0x2a, 0xee, 0xce, 0x25, 0x63, 0x0c, 0x5c, 0x17, 0x47, 0x50, 0x33, 0xa3, 0xe1, - 0x10, 0x98, 0x99, 0xc6, 0xd6, 0x5b, 0x9e, 0xdc, 0x8d, 0x58, 0x92, 0x9c, 0xcf, 0xa6, 0x53, 0x9e, 0x09, 0xfc, 0x77, - 0xb2, 0x10, 0xbc, 0x42, 0x0d, 0xac, 0xe5, 0x22, 0xbf, 0x61, 0x62, 0x18, 0x7b, 0x02, 0x2d, 0x86, 0x61, 0x4e, 0x9d, - 0xa7, 0x9c, 0x27, 0x34, 0x84, 0x49, 0xa7, 0xfd, 0xd7, 0x22, 0x48, 0x67, 0x49, 0xd2, 0xbd, 0xce, 0x68, 0xf8, 0xb1, - 0x2b, 0x5f, 0xbf, 0xb9, 0xfe, 0x89, 0x0e, 0x45, 0x20, 0x7f, 0x9f, 0x66, 0x59, 0x78, 0x07, 0x15, 0x09, 0x81, 0x6a, - 0xfd, 0x34, 0xf8, 0xfa, 0xfc, 0xcd, 0x6b, 0x5f, 0x6d, 0x12, 0x36, 0xba, 0xf3, 0xd2, 0x72, 0xe3, 0xa5, 0x05, 0x1e, - 0x65, 0x7c, 0xb2, 0x32, 0xb4, 0xc2, 0x5a, 0xda, 0xdd, 0x02, 0x02, 0x25, 0xe9, 0x8e, 0xea, 0xda, 0x86, 0xe0, 0xb5, - 0xa4, 0x79, 0x78, 0x49, 0xcc, 0xb8, 0xb3, 0x24, 0x09, 0x54, 0xb1, 0x97, 0xa2, 0xfb, 0xa1, 0x15, 0xd9, 0xdd, 0x82, - 0x12, 0x09, 0xe7, 0x14, 0x24, 0x0c, 0xc0, 0x38, 0x0c, 0xc5, 0x30, 0x5e, 0x50, 0xd9, 0x59, 0x61, 0x20, 0xa6, 0x45, - 0x81, 0x6f, 0x4b, 0x7a, 0x17, 0x00, 0x88, 0x64, 0x54, 0x44, 0x2c, 0x97, 0x30, 0x61, 0x84, 0x7f, 0x20, 0x8b, 0xd0, - 0xcc, 0x27, 0xd8, 0x69, 0x61, 0xd8, 0x97, 0x81, 0xe2, 0x2e, 0x78, 0xc8, 0xd3, 0x39, 0xcd, 0x04, 0xcd, 0x82, 0xbf, - 0xe3, 0x8c, 0x8e, 0x12, 0x80, 0x62, 0xa7, 0x8d, 0xe3, 0x30, 0x3f, 0x8b, 0xc3, 0x74, 0x4c, 0xa3, 0xe0, 0x56, 0x14, - 0x58, 0x08, 0xe2, 0x8e, 0x58, 0x1a, 0x26, 0xec, 0x17, 0x1a, 0xb9, 0x5a, 0x1c, 0x3c, 0x77, 0xe8, 0xad, 0xa0, 0x69, - 0x94, 0x3b, 0x2f, 0xde, 0xbf, 0x7a, 0xa9, 0x17, 0xb2, 0x26, 0x21, 0xd0, 0x22, 0x9f, 0x4d, 0x69, 0xe6, 0x21, 0xac, - 0x25, 0xc4, 0x73, 0x26, 0xb9, 0xe3, 0xab, 0x70, 0xaa, 0x4a, 0x58, 0xfe, 0x61, 0x1a, 0x85, 0x82, 0xbe, 0xa5, 0x69, - 0xc4, 0xd2, 0x31, 0xd9, 0x69, 0xab, 0xf2, 0x38, 0xd4, 0x2f, 0xa2, 0xb2, 0xe8, 0x72, 0xf7, 0x79, 0x22, 0x27, 0x5e, - 0x3e, 0xce, 0x3c, 0x54, 0xe4, 0x22, 0x14, 0x6c, 0xe8, 0x84, 0x51, 0xf4, 0x55, 0xca, 0x04, 0x93, 0x00, 0x66, 0xb0, - 0x3e, 0x40, 0xa3, 0x54, 0xc9, 0x0a, 0x03, 0xb8, 0x87, 0xb0, 0xe7, 0x69, 0x09, 0x10, 0x23, 0xbd, 0x60, 0x7b, 0x7b, - 0x15, 0xbf, 0xef, 0xd3, 0x40, 0xbd, 0x24, 0x83, 0x0b, 0xe4, 0x4f, 0x67, 0x39, 0xac, 0xb4, 0x19, 0x02, 0xc4, 0x0b, - 0xbf, 0xce, 0x69, 0x36, 0xa7, 0x51, 0x49, 0x1d, 0xb9, 0x87, 0x16, 0x2b, 0x63, 0xe8, 0x7d, 0x21, 0xc8, 0xe0, 0xa2, - 0x6b, 0x33, 0x6e, 0xaa, 0x09, 0x3d, 0xe3, 0x53, 0x9a, 0x09, 0x46, 0xf3, 0x92, 0x97, 0x78, 0x20, 0x46, 0x4b, 0x7e, - 0x92, 0x13, 0x33, 0xbf, 0xa9, 0xc7, 0x30, 0x45, 0x35, 0x8e, 0x61, 0x24, 0xed, 0xf3, 0xb9, 0x14, 0x19, 0x39, 0x66, - 0x08, 0x0b, 0x05, 0x69, 0x8e, 0x50, 0x81, 0xb0, 0x30, 0xe0, 0x2a, 0x5e, 0xa4, 0x47, 0xbb, 0x03, 0x59, 0x4d, 0x7e, - 0x90, 0xb2, 0x1a, 0x38, 0x5a, 0x28, 0xe8, 0xde, 0x9e, 0x47, 0xfd, 0x92, 0x2a, 0xc8, 0x4e, 0x5b, 0xaf, 0x91, 0x85, - 0xac, 0x2d, 0x60, 0xc3, 0xc0, 0x02, 0x53, 0x84, 0x77, 0xa8, 0x9f, 0xf2, 0xd3, 0xe1, 0x90, 0xe6, 0x39, 0xcf, 0xf6, - 0xf6, 0x76, 0x64, 0xfd, 0x52, 0x9d, 0x80, 0x35, 0x7c, 0x73, 0x93, 0x56, 0x10, 0xa0, 0x4a, 0xc4, 0x6a, 0xc1, 0x20, - 0x40, 0x50, 0x49, 0x8d, 0xc3, 0xed, 0x1b, 0xcd, 0x23, 0x70, 0x2f, 0x2f, 0xdd, 0x86, 0xc0, 0x1a, 0x0d, 0x63, 0x6a, - 0x86, 0xbe, 0x7b, 0x46, 0x95, 0x6e, 0x25, 0x35, 0x8f, 0x35, 0xcc, 0xa8, 0x0d, 0xe4, 0x47, 0x74, 0xc4, 0x52, 0x6b, - 0xda, 0x35, 0x90, 0xb0, 0xc0, 0x39, 0x2a, 0xac, 0x05, 0xdd, 0xd8, 0xb5, 0x54, 0x6a, 0xd4, 0xca, 0x2d, 0xc6, 0x52, - 0x91, 0xb0, 0x96, 0x71, 0x40, 0x2f, 0x0a, 0x2c, 0x51, 0x6f, 0x66, 0x93, 0x49, 0x40, 0x07, 0xe2, 0xa2, 0xab, 0xdf, - 0x93, 0x5c, 0x61, 0x2e, 0xa3, 0x3f, 0xcf, 0x68, 0x2e, 0x14, 0x1d, 0x7b, 0x02, 0x67, 0x98, 0xa1, 0x02, 0xf6, 0xdb, - 0x88, 0x8d, 0x67, 0x19, 0xe8, 0x3b, 0xb0, 0x17, 0x69, 0x3a, 0x9b, 0x50, 0xf3, 0xb4, 0x09, 0xb6, 0x37, 0x53, 0x90, - 0x88, 0x39, 0xd0, 0xf4, 0xfd, 0xe4, 0x04, 0xb0, 0x0a, 0xb4, 0x5c, 0xfe, 0x60, 0x3a, 0xa9, 0x96, 0xb2, 0xd4, 0xd1, - 0x56, 0xd7, 0x44, 0x20, 0x2d, 0x91, 0x77, 0xda, 0x0a, 0x7c, 0x21, 0x2e, 0xc8, 0x4e, 0xab, 0xa4, 0x61, 0x8d, 0x55, - 0x05, 0x8e, 0x42, 0xe2, 0x1b, 0xd5, 0x15, 0x92, 0x02, 0xbe, 0x46, 0x2e, 0x7e, 0xbc, 0x46, 0xa9, 0x31, 0x19, 0x80, - 0xaa, 0xe1, 0xc7, 0x17, 0xdb, 0xc8, 0xc9, 0xf0, 0x03, 0x4f, 0xac, 0xbf, 0xab, 0xd8, 0xc6, 0xbc, 0xce, 0x36, 0x56, - 0xa6, 0xe1, 0x4e, 0xcb, 0x26, 0x6e, 0x49, 0x65, 0x7a, 0xa3, 0x57, 0xaf, 0x30, 0x93, 0xc0, 0x54, 0x53, 0xb2, 0xba, - 0x78, 0x1d, 0x4e, 0x68, 0xee, 0x51, 0x84, 0xb7, 0x55, 0x50, 0xe4, 0x09, 0x55, 0x2e, 0x2c, 0xc9, 0x99, 0x83, 0xe4, - 0x64, 0x48, 0x29, 0x66, 0xf5, 0x0d, 0x97, 0x63, 0x3a, 0xc8, 0x2f, 0x2a, 0x7d, 0xce, 0x9a, 0xbc, 0x14, 0xc9, 0x9a, - 0xbe, 0x0d, 0xfe, 0x54, 0x99, 0x42, 0x9a, 0xd4, 0x1b, 0x72, 0x84, 0x77, 0x5a, 0xab, 0x2b, 0x69, 0x6a, 0x55, 0x73, - 0x1c, 0x5c, 0xc0, 0x3a, 0x48, 0x89, 0xe1, 0xb3, 0x5c, 0xfe, 0x5f, 0xdb, 0x69, 0x80, 0xb6, 0x73, 0x20, 0x0c, 0x7f, - 0x94, 0x84, 0xc2, 0x6b, 0xef, 0xb7, 0x40, 0x19, 0x9d, 0x53, 0x10, 0x28, 0x08, 0xad, 0x4f, 0x85, 0xfa, 0xb3, 0x34, - 0x8f, 0xd9, 0x48, 0x78, 0xb1, 0x90, 0x2c, 0x85, 0x26, 0x39, 0x75, 0x44, 0x4d, 0x25, 0x96, 0xec, 0x26, 0x06, 0x62, - 0x2b, 0xf5, 0x2f, 0x6a, 0x20, 0x95, 0x6c, 0x0b, 0xb8, 0x43, 0xa5, 0x4e, 0x57, 0x5c, 0xc6, 0xd4, 0x66, 0xa0, 0x32, - 0xb6, 0xfb, 0xaa, 0xc7, 0x40, 0x33, 0x03, 0x66, 0x69, 0xad, 0x2c, 0xb0, 0x39, 0x84, 0x2e, 0x14, 0xbe, 0xe0, 0x2f, - 0xf9, 0x0d, 0xcd, 0xce, 0x42, 0x00, 0x3e, 0x50, 0xcd, 0x0b, 0x25, 0x08, 0x24, 0xbf, 0x17, 0x5d, 0x43, 0x2f, 0x97, - 0x72, 0xe2, 0x6f, 0x33, 0x3e, 0x61, 0x39, 0x05, 0x65, 0x4d, 0xe1, 0x3f, 0x85, 0x7d, 0x26, 0x37, 0x24, 0x08, 0x1b, - 0x5a, 0xd2, 0xd7, 0xe9, 0xcb, 0x3a, 0x7d, 0x5d, 0xee, 0x3e, 0x1f, 0x1b, 0x06, 0x58, 0xdf, 0xc6, 0x08, 0x7b, 0xda, - 0xa4, 0xb0, 0xe4, 0x9c, 0x1f, 0x23, 0x2d, 0xe1, 0x97, 0x4b, 0x61, 0x59, 0x6e, 0x35, 0x75, 0x91, 0xaa, 0x6d, 0x83, - 0x8a, 0x30, 0x8a, 0x40, 0xb1, 0xcb, 0x78, 0x92, 0x58, 0xa2, 0x0a, 0xb3, 0x6e, 0x29, 0x9c, 0x2e, 0x77, 0x9f, 0x9f, - 0xdf, 0x27, 0x9f, 0xe0, 0xbd, 0x2d, 0xa2, 0x0c, 0xa0, 0x69, 0x44, 0x33, 0xb0, 0x24, 0xad, 0xd5, 0xd2, 0x52, 0xf6, - 0x8c, 0xa7, 0x29, 0x1d, 0x0a, 0x1a, 0x81, 0xa1, 0xc2, 0x88, 0xf0, 0x63, 0x9e, 0x8b, 0xb2, 0xb0, 0x82, 0x9e, 0x59, - 0xd0, 0x33, 0x7f, 0x18, 0x26, 0x89, 0xa7, 0x8c, 0x92, 0x09, 0x9f, 0xd3, 0x0d, 0x50, 0x77, 0x6b, 0x20, 0x97, 0xdd, - 0x50, 0xab, 0x1b, 0xea, 0xe7, 0xd3, 0x84, 0x0d, 0x69, 0x29, 0xba, 0xce, 0x7d, 0x96, 0x46, 0xf4, 0x16, 0xf8, 0x08, - 0xea, 0xf5, 0x7a, 0x2d, 0xdc, 0x46, 0x85, 0x42, 0xf8, 0x62, 0x0d, 0xb1, 0xf7, 0x08, 0x4d, 0x20, 0x32, 0xd2, 0x5b, - 0x6c, 0xe2, 0x07, 0x14, 0x59, 0x92, 0x92, 0x19, 0xe3, 0x4a, 0x71, 0x67, 0x84, 0x23, 0x9a, 0x50, 0x41, 0x0d, 0x37, - 0x07, 0x15, 0x5a, 0x6d, 0xdd, 0x77, 0x25, 0xfe, 0x4a, 0x72, 0x32, 0xbb, 0xcc, 0xac, 0x79, 0x5e, 0x1a, 0xeb, 0xd5, - 0xf2, 0x54, 0xd8, 0xee, 0x0b, 0xb5, 0x3c, 0xa1, 0x10, 0xe1, 0x30, 0x56, 0x56, 0xba, 0xb7, 0x36, 0xa5, 0xaa, 0x0f, - 0xcd, 0xd9, 0xcb, 0x4d, 0xf4, 0xde, 0x80, 0xb9, 0x09, 0x05, 0xe7, 0x9a, 0x29, 0x50, 0x30, 0xfc, 0xd4, 0xb2, 0x9d, - 0x85, 0x49, 0x72, 0x1d, 0x0e, 0x3f, 0xd6, 0xa9, 0xbf, 0x22, 0x03, 0xb2, 0xca, 0x8d, 0xad, 0x57, 0x16, 0xcb, 0xb2, - 0xe7, 0x6d, 0xb8, 0x74, 0x6d, 0xa3, 0x78, 0x3b, 0xad, 0x8a, 0xec, 0xeb, 0x0b, 0xbd, 0x95, 0xda, 0x25, 0x44, 0x4c, - 0xcf, 0xcc, 0x03, 0x2e, 0xf0, 0x49, 0x8a, 0x33, 0xfc, 0x40, 0xd3, 0x1d, 0x98, 0x1b, 0xc5, 0x0a, 0x20, 0x02, 0x2d, - 0x8a, 0x88, 0xe5, 0xdb, 0x31, 0xf0, 0x87, 0x40, 0xf9, 0xcc, 0x1a, 0xe1, 0xa1, 0x80, 0x96, 0x3c, 0x4e, 0x6b, 0xcd, - 0x25, 0x64, 0x5a, 0x9f, 0x30, 0x8c, 0xe6, 0x6f, 0xa0, 0xbb, 0x48, 0x7a, 0x7f, 0xa3, 0x5e, 0x81, 0x56, 0x06, 0x50, - 0xe4, 0x5d, 0x5b, 0x9d, 0xa8, 0x51, 0x80, 0xe6, 0xa9, 0x4c, 0x8a, 0xdc, 0xac, 0x66, 0x3f, 0x6a, 0x8d, 0x5d, 0x99, - 0xe0, 0x9a, 0xe5, 0x72, 0xe2, 0x79, 0x5e, 0x0e, 0x26, 0x9c, 0x51, 0xed, 0xab, 0x49, 0xe4, 0x6b, 0x93, 0xc8, 0x7d, - 0xcb, 0xce, 0x42, 0x15, 0x2d, 0x5b, 0xcd, 0x83, 0xbf, 0x23, 0xbb, 0x12, 0xa8, 0xab, 0x3e, 0xf0, 0x67, 0x54, 0xb2, - 0xdb, 0x84, 0x08, 0xcc, 0xb5, 0x8d, 0xa3, 0x29, 0x0d, 0x18, 0x46, 0xd5, 0x24, 0x43, 0x6a, 0x6b, 0xd4, 0xec, 0xdd, - 0x0c, 0x73, 0xb4, 0xa2, 0xdb, 0x17, 0x85, 0xc6, 0x11, 0x45, 0x7a, 0x6d, 0x6a, 0x4a, 0xb1, 0x85, 0x15, 0x9c, 0x11, - 0xad, 0x08, 0x2b, 0xbd, 0x67, 0x15, 0x37, 0x65, 0xbf, 0x3b, 0x84, 0x64, 0x15, 0x6a, 0x6a, 0x1a, 0xa5, 0x51, 0xad, - 0x32, 0x84, 0x63, 0xa3, 0x93, 0xf2, 0x6a, 0xde, 0x84, 0xb8, 0xc6, 0x21, 0xe1, 0xf6, 0x17, 0x35, 0xab, 0x30, 0xb0, - 0xaa, 0x15, 0x01, 0xb0, 0x54, 0xbe, 0x09, 0xdd, 0x9b, 0x68, 0xa6, 0xd6, 0x8f, 0x85, 0x70, 0x6e, 0x23, 0xdc, 0xc2, - 0x6c, 0xa6, 0x38, 0x57, 0x76, 0x41, 0xe2, 0x7a, 0x5b, 0x8f, 0x62, 0xae, 0xd6, 0x61, 0x0d, 0x89, 0xab, 0xaa, 0xa7, - 0x24, 0x41, 0xb0, 0x61, 0x73, 0x50, 0xee, 0x6c, 0xf9, 0xe0, 0x01, 0xec, 0x6c, 0xb9, 0x5c, 0x23, 0xba, 0x8d, 0x1a, - 0x28, 0xf2, 0x2b, 0xbb, 0x70, 0xb9, 0xbc, 0x15, 0xc8, 0xd3, 0xba, 0x2f, 0xa6, 0xa8, 0x6f, 0x38, 0xee, 0xe9, 0x4b, - 0xa8, 0x25, 0x55, 0xd1, 0xaa, 0xa4, 0x34, 0x1a, 0xea, 0x34, 0x5b, 0x5f, 0x27, 0x61, 0xb1, 0xed, 0xb3, 0x35, 0xee, - 0x25, 0x0b, 0xb5, 0x98, 0xae, 0xa6, 0x7c, 0xa6, 0xbb, 0x66, 0x08, 0xa1, 0x20, 0x97, 0x76, 0xcc, 0xce, 0x26, 0xd3, - 0x72, 0x6f, 0x2f, 0xb7, 0x3a, 0xba, 0x2c, 0xd9, 0xc4, 0x4f, 0x1e, 0x88, 0xe4, 0xfc, 0x2e, 0x95, 0xba, 0xcb, 0x4f, - 0x46, 0x08, 0xad, 0x19, 0xa6, 0xad, 0x2e, 0x18, 0xe4, 0xe1, 0x4d, 0xc8, 0x84, 0x53, 0xf6, 0xa2, 0x0c, 0x72, 0x8f, - 0xa2, 0x85, 0x56, 0x35, 0xfc, 0x8c, 0x82, 0xf2, 0x08, 0x3c, 0xc1, 0xa8, 0xd0, 0x8a, 0xee, 0x87, 0x31, 0x05, 0x5f, - 0xb0, 0xd1, 0x22, 0x4a, 0xcb, 0x70, 0x47, 0x4b, 0x11, 0xdd, 0xf1, 0x66, 0xd8, 0x8b, 0xd5, 0xe6, 0x35, 0x4b, 0x60, - 0x4a, 0xb3, 0x11, 0xcf, 0x26, 0xe6, 0x5d, 0xb1, 0xf2, 0xac, 0x39, 0x23, 0x1b, 0x79, 0x1b, 0xfb, 0xd6, 0xfa, 0x7f, - 0x77, 0xc5, 0xec, 0xae, 0x0c, 0xf6, 0x9a, 0x28, 0x2d, 0xa5, 0xaf, 0x72, 0x09, 0x1a, 0xca, 0xcc, 0x6d, 0x03, 0x5f, - 0xfb, 0x53, 0xbb, 0xca, 0x67, 0xb2, 0xd3, 0xee, 0x96, 0x56, 0x9f, 0xa1, 0x86, 0xae, 0xf2, 0x6d, 0x68, 0x91, 0xca, - 0x67, 0x49, 0xa4, 0x81, 0x65, 0x08, 0x53, 0x4d, 0x47, 0x37, 0x2c, 0x49, 0xaa, 0xd2, 0x5f, 0xc3, 0xd7, 0x73, 0xcd, - 0xd7, 0x33, 0xc3, 0xd7, 0x81, 0x53, 0x00, 0x5f, 0x57, 0xdd, 0x55, 0xcd, 0xb3, 0xb5, 0xdd, 0x99, 0x29, 0x8e, 0x9e, - 0x4b, 0x4b, 0x1a, 0xc6, 0x9b, 0x19, 0x08, 0x50, 0xa9, 0x79, 0x7d, 0xf4, 0xb4, 0x1f, 0x06, 0x4c, 0x40, 0xe5, 0xc5, - 0xa4, 0xb6, 0x93, 0xe2, 0xa3, 0x87, 0x70, 0x5e, 0xd0, 0x92, 0xb2, 0x4f, 0x9f, 0x83, 0x9f, 0xce, 0x9a, 0x0e, 0x08, - 0x31, 0x59, 0xfc, 0xab, 0x94, 0x28, 0x33, 0x3b, 0xa6, 0x67, 0x97, 0x9b, 0xd9, 0x01, 0xa7, 0xaf, 0x66, 0x17, 0xdd, - 0xcf, 0xeb, 0xe5, 0xf4, 0x58, 0x39, 0xbd, 0x6a, 0xbd, 0x97, 0x4b, 0x6f, 0xa5, 0x04, 0x5c, 0xf8, 0xda, 0x44, 0xc9, - 0xca, 0xde, 0x81, 0x07, 0xd8, 0x98, 0x81, 0x82, 0x42, 0x4d, 0xba, 0x14, 0x71, 0x2f, 0x3f, 0xe5, 0xe2, 0x91, 0x9e, - 0x7a, 0xd5, 0xfe, 0x8c, 0x4f, 0xa6, 0xa0, 0x8d, 0xad, 0x90, 0xf4, 0x98, 0xea, 0x01, 0xab, 0xf7, 0xc5, 0x86, 0xb2, - 0x5a, 0x1b, 0xb9, 0x1f, 0x6b, 0xd4, 0x54, 0x5a, 0xcc, 0x3b, 0xad, 0x62, 0x56, 0x16, 0x95, 0x8c, 0x63, 0x93, 0x5b, - 0xe5, 0x6c, 0xd5, 0x29, 0x63, 0x5e, 0xbc, 0xf1, 0x98, 0xe2, 0xc3, 0x0c, 0x78, 0x9d, 0xc5, 0x7e, 0x0c, 0xb9, 0xdb, - 0xeb, 0x5f, 0x54, 0xc8, 0x59, 0x14, 0x2b, 0xe8, 0x5b, 0x14, 0xc5, 0x73, 0x6d, 0x65, 0xe3, 0xe7, 0xdb, 0xcd, 0xe1, - 0xea, 0x9d, 0xb6, 0x16, 0x07, 0x17, 0xf8, 0xf9, 0xba, 0xee, 0x48, 0x16, 0x13, 0x1e, 0xd1, 0xc0, 0xe5, 0x53, 0x9a, - 0xba, 0x05, 0x78, 0x56, 0xf5, 0xe2, 0x47, 0xc2, 0x5b, 0xbc, 0xab, 0xbb, 0x58, 0x83, 0xe7, 0x05, 0x38, 0xc0, 0xbe, - 0x5b, 0x77, 0xbe, 0x7e, 0x4b, 0xb3, 0x5c, 0x6a, 0xa2, 0xa5, 0x52, 0xfb, 0x5d, 0x25, 0x97, 0xbe, 0x0b, 0xb6, 0xd6, - 0xaf, 0x6c, 0x10, 0xb7, 0xed, 0x3f, 0xf2, 0x0f, 0x5c, 0x24, 0x5d, 0xc3, 0x5f, 0xeb, 0x1d, 0xff, 0x93, 0x71, 0x0d, - 0x9f, 0x93, 0x9f, 0xea, 0x9e, 0xe1, 0x99, 0x20, 0xe7, 0xfd, 0x73, 0x63, 0x32, 0xf3, 0x84, 0x0d, 0xef, 0x3c, 0x37, - 0x61, 0xa2, 0x09, 0xe1, 0x37, 0x17, 0x2f, 0xd4, 0x0b, 0xf0, 0x2a, 0x4a, 0x97, 0x76, 0x61, 0x8c, 0x3d, 0x4c, 0x05, - 0x71, 0x77, 0x13, 0x26, 0x76, 0x5d, 0x3c, 0x21, 0x57, 0xf0, 0x63, 0x77, 0xe1, 0xbd, 0x0a, 0x45, 0xec, 0x67, 0x61, - 0x1a, 0xf1, 0x89, 0x87, 0x1a, 0xae, 0x8b, 0xfc, 0x5c, 0x1a, 0x1c, 0x4f, 0x50, 0xb1, 0x7b, 0x85, 0x4f, 0x05, 0x71, - 0xfb, 0x6e, 0x63, 0x82, 0xdf, 0x09, 0x72, 0x75, 0xb2, 0xbb, 0x38, 0x15, 0x45, 0xef, 0x0a, 0xdf, 0x96, 0x5e, 0x7b, - 0xfc, 0x81, 0x78, 0x88, 0xf4, 0x6e, 0x35, 0x34, 0x67, 0x7c, 0xa2, 0xbc, 0xf7, 0x2e, 0xc2, 0xef, 0x65, 0x6c, 0xa5, - 0x62, 0x37, 0x3a, 0xbc, 0xb2, 0x43, 0x5c, 0x2e, 0x7d, 0x04, 0xee, 0xde, 0x9e, 0x55, 0x56, 0xea, 0x0a, 0xf8, 0xb9, - 0x20, 0x35, 0x8b, 0x1c, 0xbf, 0x90, 0x51, 0x9a, 0xe7, 0xc2, 0x4b, 0x91, 0xe9, 0xc6, 0x33, 0xbe, 0x68, 0xbd, 0x37, - 0xd3, 0x81, 0x72, 0x31, 0xf8, 0x4c, 0xd0, 0x2c, 0x14, 0x3c, 0xbb, 0x40, 0xb6, 0xfe, 0x81, 0xff, 0x46, 0xae, 0x06, - 0xce, 0x7f, 0xfa, 0xec, 0xc7, 0xd1, 0x8f, 0xd9, 0xc5, 0x15, 0x7e, 0x4b, 0xf6, 0x4f, 0xbc, 0x7e, 0xe0, 0xed, 0x34, - 0x9b, 0xcb, 0x1f, 0xf7, 0x07, 0xff, 0x08, 0x9b, 0xbf, 0x9c, 0x36, 0x7f, 0xb8, 0x40, 0x4b, 0xef, 0xc7, 0xfd, 0xfe, - 0x40, 0x3f, 0x0d, 0xfe, 0xd1, 0xfb, 0x31, 0xbf, 0xf8, 0xb3, 0x2a, 0xdc, 0x45, 0x68, 0x7f, 0x8c, 0xa7, 0x82, 0xec, - 0x37, 0x9b, 0xbd, 0xfd, 0x31, 0x1e, 0x0b, 0xb2, 0x0f, 0xff, 0x5f, 0x93, 0x77, 0x74, 0xfc, 0xfc, 0x76, 0xea, 0x5d, - 0xf5, 0x96, 0xbb, 0x8b, 0xbf, 0x15, 0xd0, 0xeb, 0xe0, 0x1f, 0x3f, 0xfe, 0x98, 0xbb, 0x5f, 0xf4, 0xc8, 0xfe, 0x45, - 0x03, 0x79, 0x50, 0xfa, 0x67, 0x22, 0xff, 0xf5, 0xfa, 0xc1, 0xe0, 0x1f, 0x1a, 0x0a, 0xf7, 0x8b, 0x1f, 0xaf, 0x4e, - 0x7a, 0xe4, 0x62, 0xe9, 0xb9, 0xcb, 0x2f, 0xd0, 0x12, 0xa1, 0xe5, 0x2e, 0xba, 0xc2, 0xee, 0xd8, 0x45, 0x78, 0x2e, - 0xc8, 0xfe, 0x17, 0xfb, 0x63, 0x3c, 0x12, 0x64, 0xdf, 0xdd, 0x1f, 0xe3, 0x73, 0x41, 0xf6, 0xff, 0xe1, 0xf5, 0x03, - 0xe5, 0x64, 0x5b, 0x4a, 0xff, 0xc6, 0x12, 0x02, 0x1c, 0x61, 0x46, 0xc3, 0xa5, 0x60, 0x22, 0xa1, 0x68, 0x77, 0x9f, - 0xe1, 0x33, 0x89, 0x26, 0x4f, 0x80, 0x17, 0x06, 0x8c, 0x3b, 0x6f, 0x71, 0x09, 0x8b, 0x0d, 0x34, 0xb3, 0x1b, 0x40, - 0x64, 0x07, 0x1c, 0x01, 0x79, 0x20, 0xf0, 0x3c, 0x4c, 0x66, 0x34, 0x0f, 0x68, 0x81, 0xf0, 0x90, 0x9c, 0x09, 0xaf, - 0x8d, 0xf0, 0x53, 0x01, 0x3f, 0x3a, 0x08, 0x9f, 0xe9, 0x20, 0x26, 0xec, 0x64, 0x45, 0x54, 0x29, 0x57, 0x2a, 0x8b, - 0x8b, 0xf0, 0x74, 0xc3, 0x4b, 0x11, 0x83, 0x7b, 0x01, 0xe1, 0xdd, 0x5a, 0xc8, 0x13, 0xdf, 0x10, 0x43, 0x12, 0xef, - 0x33, 0x4a, 0xbf, 0x0b, 0x93, 0x8f, 0x34, 0xf3, 0x6e, 0x71, 0xbb, 0xf3, 0x04, 0x4b, 0x2f, 0xf4, 0x4e, 0x1b, 0x75, - 0xcb, 0x78, 0xd5, 0x47, 0xa1, 0xe2, 0x04, 0x20, 0x65, 0xeb, 0xce, 0x18, 0x58, 0xf1, 0x9d, 0x74, 0xcd, 0x63, 0x95, - 0x85, 0x37, 0x2e, 0xaa, 0xc7, 0x46, 0x59, 0x3a, 0x0f, 0x13, 0x16, 0x39, 0x82, 0x4e, 0xa6, 0x49, 0x28, 0xa8, 0xa3, - 0xe7, 0xeb, 0x84, 0xd0, 0x91, 0x5b, 0xea, 0x0c, 0x33, 0xcb, 0xe2, 0x9c, 0x99, 0xa0, 0x13, 0xec, 0x15, 0x0f, 0x22, - 0x54, 0x5a, 0xef, 0x78, 0x55, 0x05, 0xc0, 0x56, 0x63, 0x7c, 0xcd, 0x36, 0x78, 0xc2, 0x2e, 0xa4, 0x7c, 0xce, 0x71, - 0x46, 0x40, 0x8a, 0x76, 0xfa, 0xee, 0x49, 0x3e, 0x1f, 0xf7, 0x5c, 0x88, 0xcf, 0x70, 0xf2, 0x56, 0x3a, 0x86, 0xa0, - 0x42, 0x4c, 0x5a, 0xdd, 0xf8, 0x84, 0x76, 0xe3, 0x46, 0xc3, 0x28, 0xd1, 0x09, 0x49, 0x07, 0xb1, 0x6a, 0x1e, 0xe2, - 0x08, 0xcf, 0x48, 0xb3, 0x8d, 0xc7, 0xa4, 0x25, 0x9b, 0x74, 0xc7, 0x27, 0x89, 0x1e, 0x66, 0x6f, 0xcf, 0xe3, 0x7e, - 0x12, 0xe6, 0xe2, 0x2b, 0xb0, 0xf6, 0xc9, 0x18, 0x47, 0x84, 0xfb, 0xf4, 0x96, 0x0e, 0xbd, 0x04, 0xe1, 0x48, 0x73, - 0x1a, 0xd4, 0x45, 0x63, 0x62, 0x55, 0x03, 0x2b, 0x82, 0xbc, 0xed, 0x47, 0x83, 0xf6, 0x05, 0x21, 0xc4, 0xdd, 0x69, - 0x36, 0xdd, 0x3e, 0x27, 0x53, 0x11, 0x40, 0x89, 0xa5, 0x2b, 0x93, 0x31, 0x14, 0x75, 0xac, 0x22, 0xef, 0x5c, 0xf8, - 0x82, 0xe6, 0xc2, 0x83, 0x62, 0xb0, 0xff, 0x73, 0x43, 0xd8, 0xee, 0xc9, 0xbe, 0xdb, 0x80, 0x52, 0x49, 0x9c, 0x08, - 0x73, 0x72, 0x8d, 0x82, 0x68, 0x70, 0x70, 0x61, 0x0b, 0x00, 0x59, 0x08, 0x83, 0x5f, 0xf7, 0xa3, 0x41, 0x4b, 0x0e, - 0xde, 0x73, 0xfb, 0x1e, 0x27, 0xb9, 0xd2, 0xd0, 0xfa, 0x79, 0xf0, 0x56, 0x4e, 0x15, 0x05, 0x1a, 0x38, 0xb3, 0x02, - 0xa4, 0xd9, 0x09, 0xbc, 0x99, 0x3d, 0x89, 0x26, 0x0c, 0xa6, 0xb1, 0x80, 0x43, 0x02, 0xf5, 0x31, 0x27, 0x30, 0x62, - 0xd5, 0xec, 0x3a, 0xd0, 0xcf, 0x5f, 0xb8, 0x5f, 0xf4, 0x47, 0x22, 0x98, 0x0b, 0x35, 0xfc, 0x48, 0x2c, 0x97, 0xf0, - 0xff, 0x5c, 0xf4, 0x39, 0xb9, 0x96, 0x45, 0x53, 0x5d, 0x34, 0x86, 0xa2, 0xb7, 0x01, 0x80, 0x8a, 0xf3, 0x52, 0xcb, - 0x52, 0x6b, 0x32, 0x27, 0x12, 0xf6, 0xbd, 0xbd, 0x74, 0x10, 0x37, 0xda, 0x17, 0xe0, 0xe2, 0xcf, 0x44, 0xfe, 0x1d, - 0x13, 0xb1, 0xe7, 0xee, 0xf7, 0x5c, 0xd4, 0x77, 0x1d, 0x58, 0xda, 0x6e, 0xd6, 0x20, 0x0a, 0xc3, 0x49, 0xe3, 0x9d, - 0x08, 0x66, 0x3d, 0xd2, 0xea, 0x7b, 0x4c, 0xb1, 0xf0, 0x10, 0xe1, 0x44, 0x33, 0xce, 0x16, 0x9e, 0xa1, 0x06, 0x15, - 0x0d, 0xf3, 0x3c, 0x43, 0x8d, 0x49, 0x63, 0x8e, 0x82, 0xa4, 0x31, 0x69, 0x78, 0x33, 0x42, 0x48, 0xb3, 0x53, 0x36, - 0x33, 0xe2, 0x2f, 0x46, 0xc1, 0xdc, 0x78, 0x3b, 0x07, 0x72, 0x3b, 0x64, 0x0d, 0x2f, 0x1d, 0xd0, 0x8b, 0xe5, 0xd2, - 0x3d, 0xe9, 0xf7, 0x5c, 0xd4, 0xf0, 0x0c, 0xa1, 0xed, 0x1b, 0x4a, 0x43, 0x08, 0xb3, 0x8b, 0x42, 0x47, 0x93, 0x5e, - 0xd7, 0x22, 0x47, 0x8b, 0x6a, 0xb3, 0x5b, 0x3c, 0x80, 0x16, 0xa5, 0x21, 0xa3, 0x14, 0xd6, 0x29, 0x4c, 0xd3, 0x10, - 0x73, 0x46, 0x5a, 0x98, 0x13, 0xe3, 0xbc, 0x8e, 0x89, 0xa8, 0x08, 0x3e, 0x21, 0x55, 0x75, 0x3c, 0x08, 0x71, 0x74, - 0x41, 0x5e, 0x29, 0x83, 0xa4, 0x6b, 0x5c, 0xe3, 0x34, 0x21, 0xaf, 0x57, 0x22, 0xb8, 0x21, 0x84, 0x57, 0x6e, 0xfc, - 0xe1, 0x2c, 0xcb, 0x68, 0x2a, 0x5e, 0xf3, 0x48, 0xeb, 0x69, 0x34, 0x01, 0x53, 0x09, 0x42, 0xb3, 0x18, 0x94, 0xb4, - 0x8e, 0xd9, 0x19, 0xb3, 0xb5, 0xd7, 0x63, 0x32, 0x53, 0xfa, 0x93, 0x0c, 0xd8, 0x76, 0xc7, 0xda, 0x30, 0xf6, 0x10, - 0x9e, 0xe9, 0x48, 0xae, 0xe7, 0xfb, 0xfe, 0xd8, 0x1f, 0xc2, 0x6b, 0x18, 0x20, 0x47, 0x85, 0xdc, 0x47, 0x5e, 0x4e, - 0x6e, 0xfc, 0x94, 0xde, 0xca, 0x51, 0x3d, 0x54, 0x49, 0x66, 0xb3, 0xbd, 0x4e, 0xe2, 0xae, 0x64, 0x37, 0xb9, 0x9f, - 0xf2, 0x88, 0x02, 0x7a, 0x20, 0x76, 0xaf, 0x8b, 0xe2, 0x30, 0xb7, 0x43, 0x54, 0x15, 0x7c, 0x03, 0xdb, 0x7b, 0x3d, - 0x06, 0x97, 0xaf, 0x54, 0xb6, 0xca, 0xca, 0xca, 0x0f, 0x8e, 0x10, 0x1b, 0x79, 0x63, 0x1f, 0x42, 0x7b, 0x92, 0x84, - 0x28, 0xd8, 0x72, 0x63, 0x9b, 0xa8, 0x26, 0x65, 0x9f, 0x73, 0x12, 0x0d, 0x78, 0xa3, 0x21, 0xdd, 0xd0, 0x33, 0x45, - 0x12, 0x63, 0x84, 0xe7, 0xe5, 0xde, 0x32, 0xf5, 0xbe, 0x24, 0xf5, 0x91, 0xbc, 0x79, 0xdd, 0x9d, 0xdb, 0x80, 0x34, - 0x09, 0xf0, 0x14, 0x0a, 0x6f, 0x82, 0xf0, 0x29, 0xd9, 0xf7, 0x06, 0x7e, 0xff, 0x2f, 0x17, 0xa8, 0xef, 0xf9, 0x7f, - 0x46, 0xfb, 0x8a, 0x71, 0xcc, 0x51, 0x37, 0x51, 0x43, 0x2c, 0x64, 0x08, 0xb3, 0x8d, 0xa5, 0x27, 0x31, 0xc8, 0x70, - 0x1a, 0x4e, 0x68, 0x70, 0x0a, 0x7b, 0xdc, 0xd0, 0xcd, 0x97, 0x18, 0xe8, 0x28, 0x38, 0xd5, 0x9c, 0xc4, 0x77, 0xfb, - 0xcf, 0x44, 0xf9, 0xd4, 0x77, 0xfb, 0x5f, 0x55, 0x4f, 0x7f, 0x71, 0xfb, 0x3f, 0x8b, 0xe0, 0x97, 0x42, 0x3b, 0xbb, - 0x6b, 0x43, 0x3c, 0x32, 0x43, 0x14, 0x6a, 0x61, 0x2c, 0xcc, 0xcd, 0xd0, 0xba, 0x9f, 0x63, 0x8c, 0x0a, 0x36, 0x2a, - 0x59, 0x51, 0xee, 0x8b, 0x70, 0x0c, 0x28, 0xb5, 0x56, 0x20, 0xb7, 0x23, 0xfb, 0xd5, 0x84, 0x81, 0x50, 0x0c, 0xb5, - 0x02, 0x2a, 0xc7, 0xbd, 0x16, 0x5a, 0xd4, 0xea, 0x4a, 0x8d, 0xa9, 0x1e, 0x49, 0x2f, 0xb9, 0xf4, 0x9c, 0xb4, 0xba, - 0xf3, 0x93, 0x71, 0x77, 0xde, 0x68, 0xa0, 0xdc, 0x10, 0xd6, 0x6c, 0x30, 0xbf, 0xc0, 0x1f, 0xc0, 0xa7, 0x67, 0x53, - 0x12, 0xae, 0x4d, 0xaf, 0xa3, 0xa7, 0xd7, 0x68, 0x64, 0x05, 0xea, 0x5a, 0x4d, 0xc7, 0xaa, 0x69, 0x51, 0x28, 0x9c, - 0xac, 0x12, 0xda, 0x31, 0x92, 0x25, 0x90, 0x0e, 0x45, 0x08, 0x39, 0x15, 0x68, 0x63, 0xaf, 0xd0, 0x27, 0x34, 0x97, - 0x3b, 0x16, 0x98, 0xa7, 0x92, 0x11, 0x1e, 0x60, 0x01, 0x9a, 0x96, 0x8e, 0xe0, 0x09, 0x9e, 0x35, 0xda, 0x92, 0xc8, - 0x9b, 0xed, 0x6e, 0xbd, 0xaf, 0xc7, 0x55, 0x5f, 0x78, 0xd6, 0x20, 0x93, 0x12, 0x4b, 0x45, 0xd6, 0x68, 0x14, 0xf5, - 0x68, 0xa7, 0xd9, 0xb7, 0xb5, 0xf8, 0xc3, 0xed, 0x6a, 0x5a, 0x86, 0x91, 0xaf, 0x95, 0x44, 0x65, 0x3e, 0x4b, 0x53, - 0x9a, 0x81, 0x0c, 0x25, 0x02, 0xb3, 0xa2, 0xa8, 0xe4, 0x3a, 0x08, 0x51, 0x4c, 0x49, 0x0a, 0x7c, 0x47, 0x9a, 0x5d, - 0x38, 0xc3, 0x1c, 0xc7, 0x92, 0x6b, 0x10, 0x42, 0xce, 0x4c, 0x42, 0x8b, 0x90, 0x1c, 0x28, 0x21, 0xcc, 0x92, 0x48, - 0x39, 0xa1, 0xfe, 0xe5, 0xee, 0x19, 0xbf, 0xd7, 0x24, 0x1b, 0xb0, 0x8b, 0x40, 0x56, 0x4b, 0x34, 0xdf, 0x0a, 0xc9, - 0x7b, 0x4f, 0xa0, 0x32, 0x38, 0xe2, 0x4b, 0xf6, 0xf7, 0x8c, 0x65, 0x54, 0x6a, 0xe0, 0xbb, 0xc6, 0xec, 0x4b, 0xea, - 0xea, 0x63, 0x62, 0x3b, 0x6f, 0x00, 0x91, 0x21, 0xf8, 0x76, 0x32, 0xb2, 0x56, 0xed, 0x72, 0xf7, 0xf4, 0xcd, 0x26, - 0x13, 0x78, 0xb9, 0xd4, 0xc6, 0xaf, 0xd4, 0x6c, 0x70, 0x58, 0x41, 0x9a, 0xe8, 0x1f, 0x81, 0x97, 0x48, 0x05, 0x29, - 0xf4, 0x52, 0xa0, 0xa2, 0xcb, 0xdd, 0xd3, 0xf7, 0x5e, 0x2a, 0x5d, 0x4b, 0x08, 0xdb, 0xd3, 0xf6, 0x38, 0xf1, 0x62, - 0x42, 0x91, 0x9a, 0x7b, 0xc9, 0xb8, 0xb8, 0x25, 0xbe, 0x83, 0x58, 0xbe, 0x04, 0xfb, 0x61, 0xc0, 0x2e, 0x48, 0xa2, - 0x31, 0x40, 0x12, 0x84, 0x93, 0x9a, 0x59, 0x46, 0x60, 0x01, 0xe4, 0x58, 0xe7, 0xb0, 0x12, 0xbe, 0x52, 0xfc, 0x10, - 0x4e, 0xe4, 0xa8, 0xa2, 0x50, 0xa2, 0xe3, 0xe5, 0x5a, 0x5e, 0x5a, 0x65, 0x8d, 0x7e, 0x0b, 0x96, 0x93, 0x79, 0x78, - 0xad, 0xbb, 0x2e, 0x0b, 0x9e, 0x99, 0x04, 0xb2, 0xcb, 0xdd, 0xd3, 0x57, 0x3a, 0x87, 0x6c, 0x1a, 0x1a, 0x6e, 0xbf, - 0x66, 0x61, 0x9e, 0xbe, 0xf2, 0xab, 0xb7, 0xb2, 0xf2, 0xe5, 0xee, 0xe9, 0x87, 0x4d, 0xd5, 0xa0, 0xbc, 0x98, 0x55, - 0x26, 0xbe, 0x84, 0x6f, 0x41, 0x93, 0x60, 0xa1, 0x45, 0x43, 0xc0, 0x0a, 0x2c, 0xc5, 0x51, 0x90, 0x17, 0xa5, 0x67, - 0xe4, 0x19, 0xce, 0x88, 0x8c, 0x02, 0xd5, 0x57, 0x4d, 0x2b, 0x79, 0x8c, 0xa7, 0xe7, 0x43, 0x3e, 0xa5, 0x5b, 0x42, - 0x43, 0xb7, 0xc8, 0x67, 0x13, 0x48, 0x9e, 0x91, 0xa0, 0x33, 0xbc, 0xd3, 0x42, 0xdd, 0xba, 0xf0, 0xca, 0x24, 0x91, - 0xf2, 0x9a, 0x64, 0xc1, 0x31, 0x69, 0xe1, 0x84, 0xb4, 0x70, 0x48, 0xf2, 0x41, 0x4b, 0x89, 0x87, 0x6e, 0x58, 0xf6, - 0xab, 0x84, 0x0c, 0xe4, 0x85, 0xe9, 0xdd, 0xaa, 0xc4, 0x6f, 0xd4, 0x0d, 0xa5, 0xeb, 0x51, 0x4a, 0xf4, 0x48, 0x92, - 0xc5, 0x0b, 0x8f, 0x63, 0x2e, 0x3b, 0x3e, 0x67, 0xd7, 0x09, 0xa4, 0x96, 0xc0, 0xac, 0xb0, 0x40, 0x41, 0x59, 0xb5, - 0xad, 0xab, 0x86, 0xbe, 0x5c, 0x27, 0x8e, 0x43, 0x1f, 0x18, 0x37, 0x0e, 0x75, 0x26, 0x4e, 0xbe, 0xde, 0xe4, 0xd1, - 0xde, 0x9e, 0xa7, 0x1a, 0xfd, 0x22, 0x3c, 0x6e, 0xde, 0x57, 0x81, 0xbb, 0x6f, 0x15, 0xaf, 0x88, 0x90, 0x84, 0xbf, - 0xd1, 0x48, 0x2e, 0x0a, 0x88, 0x42, 0x7b, 0x61, 0x1d, 0x83, 0x06, 0x78, 0xa9, 0xe9, 0xd5, 0xa7, 0xdf, 0x68, 0x94, - 0x41, 0xda, 0x3a, 0xb6, 0x6e, 0x71, 0x56, 0xcc, 0xbd, 0x32, 0xf9, 0xa7, 0xb5, 0x96, 0x31, 0x65, 0x40, 0x40, 0xcc, - 0xa6, 0x59, 0x66, 0x26, 0x63, 0x6d, 0x09, 0x06, 0xf5, 0xbe, 0xd2, 0x69, 0x0b, 0x58, 0xe6, 0x57, 0xe9, 0x4a, 0x86, - 0x9d, 0x75, 0x50, 0x60, 0x2a, 0x41, 0x50, 0x0a, 0x2a, 0x35, 0x0a, 0x4d, 0xde, 0x2f, 0xd6, 0xb3, 0x2e, 0x71, 0x8e, - 0xb4, 0x8f, 0x4b, 0x42, 0x21, 0x91, 0xd5, 0x29, 0x91, 0xf2, 0x82, 0x4c, 0xb7, 0x93, 0xfc, 0xa9, 0x45, 0xf2, 0x4f, - 0x09, 0xb5, 0xc8, 0x5f, 0x79, 0x38, 0x7c, 0xae, 0x5d, 0x0b, 0xb9, 0x79, 0x75, 0x36, 0x25, 0xe0, 0x43, 0xab, 0x63, - 0xb4, 0x16, 0x55, 0xdc, 0xc2, 0x50, 0xec, 0x1d, 0x22, 0xbd, 0x90, 0xd8, 0x84, 0x80, 0xbd, 0x2a, 0xa6, 0x06, 0x43, - 0x6f, 0x72, 0xe9, 0xd9, 0x1c, 0xf0, 0xf4, 0xc3, 0xfd, 0xe1, 0xd0, 0xb3, 0xe9, 0xfa, 0xce, 0xb5, 0xb2, 0x3f, 0x61, - 0xd6, 0xd6, 0xc6, 0xad, 0xe7, 0x82, 0xc2, 0xf8, 0x65, 0x18, 0xbb, 0xce, 0x7c, 0x56, 0x36, 0xa1, 0x91, 0x7f, 0x00, - 0x6d, 0xbb, 0x2d, 0x6b, 0x50, 0xab, 0x5b, 0xe0, 0x47, 0x2a, 0x07, 0x35, 0xcc, 0xb6, 0xb0, 0x8f, 0x53, 0x59, 0x81, - 0xa6, 0xd1, 0xe6, 0xd7, 0x4f, 0x0b, 0x4d, 0x26, 0x0a, 0x34, 0xb4, 0x00, 0xfe, 0xa7, 0x48, 0x1e, 0xe8, 0x46, 0xca, - 0x05, 0x40, 0xd0, 0x54, 0xe2, 0xa9, 0x42, 0x98, 0xeb, 0x56, 0xce, 0xf7, 0x17, 0x3b, 0x84, 0x4c, 0x2b, 0xe7, 0xe3, - 0xbb, 0x2a, 0xf7, 0x0a, 0xc8, 0x02, 0x05, 0x60, 0x3c, 0x96, 0x05, 0x2a, 0x7a, 0x79, 0x66, 0xaa, 0x4b, 0x03, 0xd2, - 0xaf, 0xf4, 0x6d, 0x2b, 0xb2, 0x29, 0xbd, 0x72, 0xea, 0xbd, 0x41, 0xc3, 0xca, 0xdb, 0x5d, 0x78, 0xfb, 0x42, 0x48, - 0x18, 0xe1, 0xf9, 0xbd, 0xac, 0x6d, 0xfa, 0x2d, 0x3e, 0xae, 0x26, 0xb0, 0xac, 0x2c, 0x8a, 0xcf, 0xd2, 0x9c, 0x66, - 0xe2, 0x29, 0x1d, 0xf1, 0x0c, 0x42, 0x16, 0x25, 0x4e, 0x50, 0xb1, 0x6b, 0xb9, 0xed, 0xe4, 0xfc, 0xac, 0x38, 0xc1, - 0xca, 0x04, 0xe5, 0xaf, 0x8f, 0x32, 0x66, 0x7d, 0xb9, 0xda, 0x6a, 0xba, 0xb7, 0xf7, 0xbe, 0x42, 0x93, 0x86, 0x52, - 0x42, 0x61, 0x31, 0x2d, 0xa5, 0xd2, 0xe8, 0x40, 0xee, 0xae, 0x57, 0xba, 0x00, 0x0c, 0xc3, 0xb0, 0x79, 0xcf, 0x0b, - 0x22, 0x8a, 0xf1, 0x2a, 0x8b, 0xd7, 0xae, 0x09, 0x66, 0x9b, 0x2d, 0xc0, 0xe1, 0xc1, 0xd0, 0x56, 0xbe, 0xa2, 0xbc, - 0x4a, 0x87, 0x2d, 0x61, 0x38, 0x03, 0x64, 0x79, 0xd2, 0x08, 0xb1, 0x28, 0x70, 0xa3, 0x51, 0xf2, 0x11, 0xf4, 0xca, - 0x18, 0xe7, 0x7e, 0x0c, 0x09, 0xb0, 0xb5, 0x2d, 0x8b, 0x10, 0x56, 0x79, 0x39, 0x56, 0x26, 0xc1, 0xe9, 0x8b, 0x4d, - 0x1e, 0x65, 0x43, 0xd4, 0x54, 0x4a, 0x1d, 0xa8, 0x91, 0xa1, 0xb2, 0x81, 0x3f, 0xf7, 0x98, 0x56, 0xdc, 0x4c, 0xd8, - 0x0c, 0x18, 0xf0, 0x4b, 0xe1, 0xa9, 0x58, 0x14, 0xc8, 0x0c, 0xee, 0xcf, 0xbc, 0xda, 0xd0, 0x5d, 0x2e, 0x9b, 0x61, - 0x8d, 0xb8, 0xd8, 0x46, 0x13, 0x97, 0x61, 0xbd, 0xb3, 0x8a, 0x97, 0xee, 0xaa, 0x1c, 0x6a, 0x61, 0xb8, 0x60, 0x95, - 0x47, 0x62, 0x4d, 0x7f, 0x57, 0xa5, 0x45, 0x97, 0x95, 0x40, 0x0d, 0xa3, 0x37, 0xce, 0x6b, 0xb9, 0x06, 0xb4, 0x00, - 0xfa, 0x5a, 0x3c, 0x17, 0xd6, 0x8a, 0x1a, 0x1f, 0xb6, 0x1c, 0xd3, 0x92, 0xfa, 0xef, 0x20, 0xd3, 0x65, 0x75, 0xcf, - 0xbf, 0x90, 0xb2, 0x90, 0xe1, 0xbc, 0xc6, 0xd8, 0x33, 0xc9, 0xd8, 0x11, 0xe8, 0x69, 0x26, 0xf5, 0xbb, 0xaf, 0x13, - 0x5e, 0x98, 0x96, 0x72, 0x9a, 0xc4, 0x3e, 0x94, 0xc1, 0x72, 0xeb, 0xf7, 0xca, 0x6a, 0x04, 0x8c, 0x40, 0x12, 0x10, - 0xd6, 0x9c, 0x3d, 0x43, 0x38, 0x6f, 0x34, 0xba, 0xf9, 0x09, 0xad, 0x5c, 0x24, 0x15, 0x8c, 0x0c, 0xe2, 0xb9, 0x40, - 0xf0, 0x35, 0x19, 0x0a, 0x11, 0x7f, 0x93, 0x9b, 0x9d, 0x83, 0xab, 0xfd, 0xf4, 0x9d, 0x67, 0x73, 0x35, 0xbb, 0x6e, - 0x19, 0x33, 0x85, 0xf9, 0x78, 0x55, 0xbc, 0xe5, 0xed, 0xfd, 0xf9, 0x1d, 0x00, 0xf7, 0x4e, 0x1b, 0x43, 0x2e, 0x1a, - 0xea, 0x0a, 0xc5, 0x12, 0xca, 0xdd, 0xd7, 0x45, 0x55, 0x5a, 0xa2, 0x3d, 0x58, 0x57, 0x54, 0xa6, 0xac, 0x20, 0x79, - 0x51, 0xe4, 0xb4, 0x8a, 0xee, 0xaf, 0xe4, 0x5f, 0x4a, 0xe1, 0xb2, 0xee, 0x6c, 0x3f, 0x9b, 0x12, 0x81, 0x2d, 0x42, - 0x7d, 0xbb, 0x2d, 0xf4, 0x51, 0x81, 0x09, 0xfb, 0x5a, 0x0b, 0xc5, 0x5f, 0x36, 0x09, 0x45, 0x9c, 0xe9, 0x2d, 0x2f, - 0x05, 0x62, 0xfb, 0x01, 0x02, 0x51, 0x3b, 0xd9, 0x8d, 0x4c, 0x04, 0x75, 0xa4, 0x26, 0x13, 0xeb, 0x4b, 0x4a, 0x32, - 0xcc, 0xf4, 0x6a, 0xf4, 0x3a, 0xcb, 0x25, 0x1b, 0xb4, 0xc0, 0x89, 0xe4, 0xba, 0xf0, 0xb3, 0xad, 0x7e, 0x5a, 0x9c, - 0x58, 0x39, 0x81, 0x3d, 0x56, 0x9a, 0x2c, 0xc8, 0x87, 0x14, 0x67, 0x4f, 0xe6, 0x64, 0x49, 0x9a, 0xd6, 0x14, 0xa4, - 0x09, 0x9c, 0xb0, 0x32, 0xca, 0x04, 0x10, 0x4b, 0x59, 0xa1, 0x0d, 0x48, 0x6f, 0x63, 0xf2, 0x9f, 0x31, 0x2f, 0x3f, - 0xad, 0x89, 0xd6, 0xe4, 0x8a, 0x52, 0x1f, 0x6a, 0xe9, 0x06, 0x1a, 0x02, 0xad, 0x1f, 0xee, 0x48, 0x13, 0xb4, 0x12, - 0xe5, 0xc8, 0x96, 0x43, 0xb8, 0x05, 0x2e, 0xb4, 0x9d, 0xf7, 0x2a, 0xc0, 0xbb, 0x41, 0x9a, 0x60, 0x6e, 0xd1, 0xf5, - 0x0b, 0x22, 0x6a, 0xac, 0x24, 0x26, 0xda, 0x52, 0xc2, 0xa1, 0x24, 0x53, 0x41, 0xb2, 0x41, 0xeb, 0x02, 0x14, 0xd0, - 0x6e, 0x72, 0x92, 0x55, 0x26, 0x70, 0xd2, 0x68, 0xa0, 0xd0, 0x8c, 0x1a, 0x0f, 0x58, 0x23, 0xb9, 0xc0, 0x14, 0x27, - 0xca, 0x30, 0x39, 0xdb, 0xdb, 0xf3, 0xc2, 0x6a, 0xdc, 0x41, 0x72, 0x81, 0x30, 0x5f, 0x2e, 0x3d, 0x09, 0x56, 0x88, - 0x96, 0xcb, 0xd0, 0x06, 0x4b, 0xbe, 0x86, 0x66, 0xd3, 0xbe, 0x20, 0x53, 0x29, 0x00, 0xa7, 0x00, 0x61, 0x83, 0x78, - 0xa1, 0x76, 0xee, 0x85, 0xe0, 0x8c, 0x6a, 0x64, 0x83, 0xa4, 0xd1, 0xbe, 0xb0, 0x18, 0xd7, 0x20, 0xb9, 0x20, 0x61, - 0xc1, 0xf7, 0xf6, 0x76, 0x72, 0x2d, 0x22, 0x7f, 0x02, 0x51, 0xf6, 0x93, 0x94, 0x2c, 0xaa, 0x43, 0x7b, 0x35, 0x56, - 0x9d, 0x01, 0x25, 0x45, 0xe9, 0x65, 0x35, 0xf5, 0x6a, 0x49, 0x10, 0x65, 0x25, 0xac, 0x63, 0xc1, 0x7d, 0xb0, 0xec, - 0x4b, 0x32, 0x7f, 0x26, 0xca, 0x24, 0xeb, 0x5f, 0x36, 0xa6, 0x56, 0xfb, 0xbe, 0x1f, 0x66, 0x63, 0x19, 0xc9, 0x30, - 0x51, 0x58, 0x49, 0xfc, 0x07, 0x1a, 0x4c, 0x6b, 0xe0, 0x41, 0x39, 0xd6, 0x05, 0x51, 0xe0, 0x1b, 0xd5, 0xc6, 0x9c, - 0x26, 0xf9, 0x69, 0xa3, 0x97, 0x41, 0x41, 0xf2, 0xd5, 0x6f, 0x85, 0xe4, 0x50, 0x43, 0xa2, 0xc8, 0x63, 0x05, 0x67, - 0x5b, 0x70, 0xf1, 0x93, 0x58, 0xc1, 0xd9, 0x76, 0xdc, 0x1a, 0x4c, 0xfd, 0xbc, 0x0d, 0x3e, 0x8b, 0x37, 0x28, 0x40, - 0xab, 0x02, 0x0b, 0xca, 0xa3, 0x55, 0xdd, 0x4b, 0xb1, 0x52, 0x10, 0xa6, 0x82, 0x78, 0xac, 0xbe, 0x01, 0x2a, 0x6d, - 0xd4, 0x32, 0x7c, 0x59, 0x30, 0x45, 0x96, 0x4b, 0xa0, 0x9e, 0xb9, 0x02, 0xe4, 0xa4, 0x7d, 0xed, 0xd3, 0xbd, 0x3d, - 0xb0, 0x0d, 0x40, 0x89, 0xf3, 0x87, 0xe1, 0x54, 0xcc, 0x32, 0x50, 0xa5, 0x72, 0xf3, 0x1b, 0x8a, 0xe1, 0x1c, 0x88, - 0x2c, 0x83, 0x1f, 0x50, 0x30, 0x0d, 0xf3, 0x9c, 0xcd, 0x55, 0x99, 0xfe, 0x8d, 0x39, 0x31, 0xa4, 0x9c, 0x2b, 0x9d, - 0x30, 0x43, 0xdd, 0x4c, 0xd3, 0x69, 0x1d, 0x6d, 0xcf, 0xe7, 0x34, 0x15, 0x2f, 0x59, 0x2e, 0x68, 0x0a, 0xd3, 0xaf, - 0x28, 0x0e, 0x66, 0x94, 0x23, 0xd8, 0xb0, 0xb5, 0x56, 0x61, 0x14, 0xdd, 0xdb, 0x44, 0xd4, 0x75, 0xa0, 0x38, 0x4c, - 0xa3, 0x44, 0x0d, 0x62, 0xa7, 0x33, 0x9a, 0x14, 0xce, 0xb2, 0xa6, 0x9d, 0x4e, 0x53, 0x29, 0x1b, 0x92, 0xbb, 0x7b, - 0x8c, 0x18, 0x49, 0x60, 0xa4, 0xe7, 0xbd, 0x5a, 0x0b, 0x04, 0xbc, 0xb7, 0x2c, 0x82, 0x3d, 0x13, 0x2c, 0x2c, 0x8e, - 0xea, 0xd7, 0xe1, 0x2c, 0x05, 0xc9, 0xc6, 0x43, 0x6d, 0x9b, 0x84, 0x83, 0xa4, 0x93, 0x47, 0xdb, 0x2d, 0xab, 0x57, - 0x46, 0x72, 0x18, 0x69, 0xc1, 0x1e, 0xca, 0x98, 0xd1, 0xc2, 0x90, 0x17, 0x32, 0x5b, 0xf1, 0x52, 0x90, 0x9f, 0xe0, - 0xd4, 0xd0, 0x0b, 0x31, 0x49, 0x56, 0x0e, 0xc7, 0x74, 0x2f, 0x4b, 0xed, 0xff, 0x52, 0x78, 0xaf, 0xf1, 0x0b, 0x08, - 0xeb, 0x7e, 0x5d, 0x55, 0x5f, 0x0f, 0xe7, 0x7e, 0x5d, 0x21, 0xe8, 0xeb, 0x60, 0xad, 0x9e, 0x15, 0xc6, 0xed, 0xf8, - 0xc7, 0x7e, 0xcb, 0x35, 0xda, 0xd2, 0xb7, 0x2a, 0x88, 0xa4, 0x12, 0x2d, 0xe5, 0x7e, 0xc0, 0x55, 0x9a, 0x1a, 0xa4, - 0xcb, 0xd5, 0x2d, 0x24, 0xaa, 0x13, 0x0c, 0x95, 0x0e, 0xbf, 0x6d, 0x79, 0xb4, 0x8c, 0xc9, 0x94, 0x9d, 0xf1, 0x36, - 0xcc, 0xc4, 0x2e, 0xec, 0x32, 0xbe, 0x76, 0x12, 0x2f, 0x26, 0xe0, 0x41, 0x7b, 0xd8, 0x10, 0x96, 0xb1, 0x9d, 0xab, - 0x93, 0x40, 0x76, 0xff, 0x84, 0x1b, 0xdd, 0xad, 0x6e, 0x65, 0x7c, 0x00, 0xfb, 0x1f, 0xe1, 0xd8, 0x1c, 0x8f, 0xa3, - 0x9a, 0x03, 0xd3, 0x60, 0x51, 0x94, 0x4e, 0x01, 0xae, 0x94, 0xb7, 0x14, 0x61, 0x5e, 0xc8, 0xf0, 0xf6, 0x37, 0xf8, - 0x7b, 0xcd, 0x12, 0x47, 0x25, 0xc7, 0x79, 0xfe, 0x50, 0x8e, 0xa8, 0xc0, 0x2f, 0xa3, 0xf7, 0x40, 0xc7, 0x92, 0x42, - 0x0b, 0x43, 0x45, 0xcf, 0xb8, 0x9e, 0xc8, 0xd6, 0xac, 0x54, 0x4c, 0xcb, 0x8c, 0x1a, 0x39, 0xcc, 0x86, 0x34, 0x4e, - 0x63, 0x65, 0x8b, 0x72, 0x57, 0xd5, 0xc6, 0x45, 0x5b, 0xb0, 0x58, 0x05, 0x16, 0x97, 0x4b, 0xaf, 0x8e, 0x6a, 0xc2, - 0xac, 0x38, 0x06, 0xc2, 0xcc, 0x4a, 0xa8, 0xa8, 0x69, 0xd6, 0xaa, 0x8d, 0x87, 0x56, 0xf3, 0x89, 0x8c, 0x6e, 0x5e, - 0x83, 0xc3, 0x76, 0x21, 0xa8, 0xe6, 0xb6, 0x4f, 0x01, 0xab, 0xd9, 0x95, 0x03, 0x59, 0x18, 0xfa, 0xb6, 0xcc, 0x94, - 0xad, 0x52, 0x5a, 0x37, 0xe0, 0x17, 0xdd, 0x93, 0x2b, 0xab, 0x51, 0xb7, 0xfe, 0xde, 0xca, 0x35, 0x7a, 0xc6, 0xb7, - 0xe5, 0x1a, 0xd5, 0xb4, 0xdd, 0x9d, 0x16, 0xba, 0x3f, 0x2b, 0x55, 0x8d, 0xb5, 0xb9, 0xca, 0x6f, 0x18, 0xae, 0x0d, - 0xb4, 0xa9, 0xd0, 0x6c, 0xb8, 0xca, 0x59, 0x51, 0x8c, 0xca, 0xb3, 0x04, 0x32, 0x75, 0x67, 0xa4, 0xe8, 0x5f, 0x5b, - 0x8d, 0xf2, 0x40, 0xae, 0xf7, 0x0d, 0x19, 0x27, 0xfc, 0x3a, 0x4c, 0xde, 0xc3, 0x78, 0xd5, 0xcb, 0x17, 0x77, 0x51, - 0x16, 0x0a, 0xaa, 0xb9, 0x4b, 0x05, 0xc3, 0x37, 0x16, 0x0c, 0xdf, 0x28, 0x3e, 0x5d, 0xb5, 0xc7, 0x8b, 0x97, 0x65, - 0x07, 0xc1, 0xa8, 0x30, 0x2c, 0x63, 0x22, 0x36, 0x8f, 0xb1, 0xca, 0xc2, 0x26, 0x25, 0x0b, 0x9b, 0x08, 0x6f, 0xb5, - 0x2b, 0xcf, 0xfb, 0x7e, 0x73, 0x2f, 0xeb, 0x9c, 0xed, 0xfb, 0x6a, 0xe3, 0x7f, 0x1f, 0xdc, 0xdb, 0xc6, 0xe2, 0x72, - 0x07, 0xfe, 0x81, 0x4c, 0x56, 0x51, 0x20, 0x3f, 0x85, 0xa4, 0x03, 0x41, 0x7a, 0xd6, 0x91, 0x83, 0x4a, 0x4e, 0x99, - 0x3c, 0x20, 0x6f, 0x38, 0xcb, 0x05, 0x9f, 0xe8, 0x3e, 0x73, 0x7d, 0xce, 0x48, 0xbe, 0x04, 0x57, 0xb4, 0x8c, 0xb5, - 0x07, 0xf5, 0x93, 0x5c, 0x8b, 0x8f, 0x2c, 0x8d, 0x82, 0x1c, 0x6b, 0x29, 0x92, 0x07, 0x59, 0x41, 0x4c, 0xae, 0xf1, - 0xfa, 0x3b, 0x3c, 0x62, 0x29, 0xcb, 0x63, 0x9a, 0x79, 0x1c, 0x2d, 0xb6, 0x0d, 0xc6, 0x21, 0x20, 0xa3, 0x06, 0xc3, - 0x5f, 0x56, 0x47, 0xfe, 0x7c, 0xe8, 0x0d, 0xfc, 0x40, 0x13, 0x2a, 0x62, 0x1e, 0x41, 0x5a, 0x8a, 0x1f, 0x95, 0x47, - 0x9a, 0xf6, 0xf6, 0x76, 0x3c, 0x57, 0xba, 0x25, 0xe0, 0xf0, 0xb7, 0xfd, 0x06, 0xf5, 0x17, 0x70, 0x3a, 0xa7, 0x1a, - 0x9a, 0xa2, 0x05, 0x5d, 0x3d, 0xc8, 0x22, 0xfc, 0x8f, 0xf4, 0x0e, 0xa7, 0xa8, 0x28, 0x02, 0x05, 0xb5, 0x3b, 0x62, - 0x34, 0x89, 0x5c, 0xfc, 0x91, 0xde, 0x05, 0xe5, 0x79, 0x71, 0x79, 0xbc, 0x59, 0x2e, 0xa0, 0xcb, 0x6f, 0x52, 0x17, - 0x57, 0x83, 0x04, 0x8b, 0x02, 0xf3, 0x8c, 0x8d, 0x81, 0x38, 0xff, 0x46, 0xef, 0x02, 0xd5, 0x1f, 0xb3, 0x4e, 0xeb, - 0xa1, 0x85, 0x41, 0xbd, 0x6f, 0x15, 0xdb, 0xcb, 0xa0, 0x0d, 0x8a, 0x81, 0x6c, 0x7b, 0x41, 0x6a, 0xf5, 0x2a, 0xf3, - 0x10, 0xa1, 0xe2, 0xa1, 0x53, 0xc1, 0xdf, 0xd9, 0xa2, 0x4d, 0xd4, 0x32, 0x5f, 0x57, 0x1a, 0x51, 0x68, 0x50, 0x65, - 0x7a, 0x5c, 0x7a, 0xa9, 0xd9, 0x75, 0xfa, 0x08, 0x82, 0xe5, 0x08, 0xfb, 0x4e, 0xe8, 0x4e, 0x83, 0x2f, 0x55, 0x42, - 0x48, 0x15, 0x49, 0x7a, 0x55, 0xb5, 0x73, 0x2e, 0x3d, 0xc0, 0x3b, 0x24, 0xb4, 0x84, 0xf2, 0x40, 0x66, 0x61, 0xb2, - 0x45, 0x7f, 0x10, 0xc4, 0x5b, 0x98, 0x29, 0x04, 0xa9, 0x8d, 0x45, 0x51, 0x00, 0x15, 0x6a, 0xfa, 0x52, 0x09, 0x80, - 0x70, 0x86, 0x7d, 0x4d, 0x6a, 0x66, 0x52, 0x6a, 0xfa, 0x16, 0xc6, 0xb7, 0x48, 0x49, 0x2a, 0x91, 0x21, 0x95, 0x48, - 0x29, 0xf4, 0xf4, 0xe2, 0x6a, 0x12, 0xb2, 0x17, 0xb4, 0x3c, 0x3f, 0xa7, 0xd6, 0x3c, 0xab, 0x81, 0xe5, 0xc9, 0x7e, - 0x50, 0x11, 0xc0, 0x94, 0xa8, 0xaa, 0x50, 0x94, 0xc7, 0xb2, 0x4d, 0x7a, 0xab, 0xc7, 0x7d, 0x33, 0x2d, 0x62, 0x50, - 0xe2, 0xc5, 0x68, 0x91, 0x7a, 0x31, 0xce, 0x20, 0x1d, 0x91, 0x17, 0x25, 0xfc, 0xd4, 0x5e, 0x8d, 0x5a, 0xb2, 0xf2, - 0xe6, 0x33, 0x7e, 0xa0, 0xcc, 0x0b, 0x48, 0xd1, 0xc4, 0xa9, 0xe1, 0x29, 0xa9, 0x27, 0x0f, 0xdb, 0x59, 0xcb, 0xf6, - 0xb5, 0x4e, 0xd0, 0xd1, 0x80, 0xfd, 0x20, 0xbc, 0x85, 0x35, 0x0b, 0xfb, 0x34, 0xb7, 0x3e, 0xf3, 0xa7, 0x83, 0x7d, - 0x55, 0x0e, 0xa9, 0x97, 0x93, 0x15, 0x89, 0x73, 0x7f, 0xaa, 0xe5, 0xcf, 0x33, 0x9a, 0xdd, 0x9d, 0x53, 0x48, 0x75, - 0xe6, 0x70, 0xda, 0xb7, 0x5a, 0x86, 0x2a, 0x4d, 0xbd, 0x9f, 0x49, 0x65, 0xa5, 0xa8, 0x9f, 0x02, 0x5c, 0x3d, 0x23, - 0x58, 0xc8, 0x68, 0xa3, 0xe5, 0x88, 0x51, 0xbb, 0x85, 0x6e, 0x3d, 0x3d, 0x49, 0xbb, 0x0c, 0xfc, 0x6b, 0x15, 0xa6, - 0x75, 0xb0, 0x00, 0x73, 0xfb, 0x44, 0xea, 0x20, 0xbf, 0x58, 0xf5, 0xca, 0x40, 0x11, 0x84, 0xef, 0xb2, 0xed, 0x53, - 0xdd, 0x94, 0x34, 0xbb, 0x7d, 0xaa, 0xb5, 0xa0, 0x9f, 0x4c, 0xf8, 0xc1, 0x7a, 0x9c, 0xf2, 0xf8, 0x32, 0x2b, 0x0a, - 0x54, 0x00, 0x78, 0x7f, 0xed, 0x7a, 0xde, 0x5f, 0x75, 0xca, 0xa0, 0x0f, 0xb1, 0xd8, 0xf3, 0x84, 0x1b, 0x26, 0x5e, - 0x8d, 0xff, 0xd7, 0xb5, 0xf1, 0xff, 0x6a, 0x9d, 0x39, 0x05, 0xd3, 0x68, 0x9c, 0xd2, 0xc8, 0xb0, 0x4e, 0xa4, 0x08, - 0x50, 0xea, 0x6d, 0xa9, 0x20, 0x6f, 0xae, 0x02, 0xd0, 0xb8, 0x16, 0x23, 0x9e, 0x8a, 0xe6, 0x28, 0x9c, 0xb0, 0xe4, - 0x2e, 0x98, 0xb1, 0xe6, 0x84, 0xa7, 0x3c, 0x9f, 0x86, 0x43, 0x8a, 0xf3, 0xbb, 0x5c, 0xd0, 0x49, 0x73, 0xc6, 0xf0, - 0x0b, 0x9a, 0xcc, 0xa9, 0x60, 0xc3, 0x10, 0xbb, 0xa7, 0x19, 0x0b, 0x13, 0xe7, 0x75, 0x98, 0x65, 0xfc, 0xc6, 0xc5, - 0xef, 0xf8, 0x35, 0x17, 0x1c, 0xbf, 0xb9, 0xbd, 0x1b, 0xd3, 0x14, 0x7f, 0xb8, 0x9e, 0xa5, 0x62, 0x86, 0xf3, 0x30, - 0xcd, 0x9b, 0x39, 0xcd, 0xd8, 0xa8, 0x3b, 0xe4, 0x09, 0xcf, 0x9a, 0x90, 0xb1, 0x3d, 0xa1, 0x41, 0xc2, 0xc6, 0xb1, - 0x70, 0xa2, 0x30, 0xfb, 0xd8, 0x6d, 0x36, 0xa7, 0x19, 0x9b, 0x84, 0xd9, 0x5d, 0x53, 0xd6, 0x08, 0x3e, 0x6f, 0x1d, - 0x84, 0x4f, 0x46, 0x87, 0x5d, 0x91, 0x85, 0x69, 0xce, 0x60, 0x99, 0x82, 0x30, 0x49, 0x9c, 0x83, 0xa3, 0xd6, 0x24, - 0xdf, 0x51, 0x81, 0xbc, 0x30, 0x15, 0xc5, 0x15, 0x7e, 0x03, 0x70, 0xfb, 0xd7, 0x22, 0xc5, 0xd7, 0x33, 0x21, 0x78, - 0xba, 0x18, 0xce, 0xb2, 0x9c, 0x67, 0xc1, 0x94, 0xb3, 0x54, 0xd0, 0xac, 0x7b, 0xcd, 0xb3, 0x88, 0x66, 0xcd, 0x2c, - 0x8c, 0xd8, 0x2c, 0x0f, 0x0e, 0xa7, 0xb7, 0x5d, 0xd0, 0x2c, 0xc6, 0x19, 0x9f, 0xa5, 0x91, 0x1e, 0x8b, 0xa5, 0x31, - 0xcd, 0x98, 0xb0, 0x5f, 0xc8, 0x4b, 0x4c, 0x82, 0x84, 0xa5, 0x34, 0xcc, 0x9a, 0x63, 0x68, 0x0c, 0x66, 0x51, 0x2b, - 0xa2, 0x63, 0x9c, 0x8d, 0xaf, 0x43, 0xaf, 0xdd, 0x79, 0x8c, 0xcd, 0x5f, 0xff, 0x08, 0x39, 0xad, 0xcd, 0xc5, 0xed, - 0x56, 0xeb, 0x4f, 0xa8, 0xbb, 0x32, 0x8a, 0x04, 0x28, 0x68, 0x4f, 0x6f, 0x9d, 0x9c, 0x43, 0x46, 0xdb, 0xa6, 0x96, - 0xdd, 0x69, 0x18, 0x41, 0x3e, 0x70, 0xd0, 0x99, 0xde, 0x16, 0x30, 0xbb, 0x40, 0xa5, 0x98, 0xea, 0x49, 0xea, 0xa7, - 0xc5, 0x6f, 0x85, 0xf8, 0x78, 0x33, 0xc4, 0x1d, 0x03, 0x71, 0x85, 0xf5, 0x66, 0x34, 0xcb, 0x64, 0x6c, 0x35, 0x68, - 0xe7, 0x0a, 0x90, 0x98, 0xcf, 0x69, 0x66, 0xe0, 0x90, 0x0f, 0xbf, 0x19, 0x8c, 0xce, 0x66, 0x30, 0x8e, 0x3f, 0x05, - 0x46, 0x96, 0x46, 0x8b, 0xfa, 0xba, 0xb6, 0x33, 0x3a, 0xe9, 0xc6, 0x14, 0xe8, 0x29, 0xe8, 0xc0, 0xef, 0x1b, 0x16, - 0x89, 0x58, 0xfd, 0x94, 0xe4, 0x7c, 0xa3, 0xde, 0x1d, 0xb5, 0x5a, 0xea, 0x39, 0x67, 0xbf, 0xd0, 0xa0, 0xed, 0x43, - 0x85, 0xe2, 0x0a, 0xff, 0xad, 0x3c, 0xcb, 0x5b, 0xe7, 0x9e, 0xf8, 0x1b, 0xfb, 0x90, 0xaf, 0x95, 0xa2, 0x58, 0x1d, - 0x89, 0xc6, 0x99, 0x91, 0x95, 0x4a, 0xf8, 0x80, 0xdb, 0x4e, 0x72, 0x47, 0xc2, 0x7a, 0xe5, 0x21, 0x4e, 0xd6, 0xff, - 0x46, 0xe5, 0x5d, 0x04, 0x10, 0xe9, 0xb0, 0x52, 0x0d, 0x79, 0x37, 0xeb, 0x91, 0x56, 0x37, 0x6b, 0x36, 0x91, 0xc7, - 0x49, 0x3a, 0xc8, 0x74, 0x72, 0x9e, 0xc7, 0xfa, 0x5c, 0x1a, 0xdb, 0x39, 0x0a, 0x38, 0x9c, 0x34, 0x5d, 0x2e, 0xab, - 0x30, 0x00, 0x93, 0xa7, 0x35, 0xfe, 0x26, 0x74, 0x05, 0x9c, 0x5b, 0x9c, 0x9c, 0x9b, 0xab, 0x5d, 0x52, 0xc3, 0x2b, - 0x12, 0x3e, 0x94, 0x98, 0xf3, 0xa7, 0xa1, 0x88, 0xc1, 0x4b, 0x51, 0x8a, 0x9f, 0x2a, 0x85, 0xc9, 0xdd, 0x77, 0x51, - 0x3f, 0x2d, 0xf3, 0xdb, 0x20, 0x8f, 0x2f, 0x2d, 0xa0, 0x97, 0xef, 0x05, 0x81, 0x1e, 0xf1, 0x57, 0x44, 0xd9, 0x74, - 0xc6, 0xa2, 0x1b, 0x3d, 0xd4, 0xa2, 0xa3, 0xa9, 0x60, 0x32, 0x73, 0xdb, 0x44, 0x1c, 0xe2, 0x30, 0xbf, 0x1c, 0xaa, - 0xa3, 0x92, 0x79, 0x75, 0x30, 0x20, 0x94, 0xd0, 0x2b, 0x23, 0x8d, 0x66, 0xd2, 0x1e, 0xfd, 0xab, 0xd8, 0x6a, 0x9f, - 0xa4, 0xf7, 0xd9, 0x27, 0xe5, 0xc4, 0x73, 0x3e, 0xcb, 0x86, 0x10, 0x8e, 0xd4, 0x52, 0x6f, 0xdd, 0x71, 0xe3, 0x4a, - 0x15, 0xc3, 0xc5, 0xc2, 0xca, 0x03, 0x15, 0x98, 0xd9, 0xd7, 0x4a, 0x50, 0x19, 0xf2, 0x52, 0xc7, 0x35, 0xb4, 0x88, - 0x33, 0x53, 0x02, 0x99, 0x1d, 0xc9, 0x94, 0x46, 0x2f, 0x23, 0xbd, 0xcc, 0x9f, 0xa5, 0xec, 0xe7, 0x19, 0xbd, 0x64, - 0xa0, 0x6b, 0x32, 0x9f, 0x45, 0x32, 0xd6, 0x04, 0xb2, 0xaf, 0xd9, 0x86, 0xe0, 0x05, 0x8b, 0xd4, 0xc2, 0x64, 0xf2, - 0xa5, 0xce, 0x6d, 0x72, 0x9b, 0x2e, 0xf8, 0x8b, 0x41, 0x3b, 0x60, 0x38, 0xe2, 0x93, 0x90, 0xa5, 0x81, 0x74, 0xf9, - 0x96, 0x9d, 0x05, 0x50, 0x1b, 0xb3, 0x28, 0xc8, 0xf4, 0xf2, 0xb4, 0x91, 0xff, 0x13, 0x67, 0xa9, 0x6c, 0x5a, 0x74, - 0xb9, 0x44, 0xa8, 0x42, 0x1f, 0x31, 0x08, 0x3e, 0x55, 0x72, 0x8d, 0x23, 0x6c, 0xbf, 0x2e, 0x4f, 0x9d, 0xd7, 0x56, - 0xa0, 0xb5, 0xb2, 0x50, 0xca, 0x08, 0xe0, 0xab, 0xa5, 0x39, 0xcf, 0x84, 0xe7, 0xc5, 0x38, 0x41, 0xa4, 0x17, 0x4b, - 0x67, 0xd7, 0x49, 0x22, 0xff, 0xeb, 0x37, 0xdb, 0x41, 0xbb, 0x34, 0xdf, 0x6b, 0x87, 0x81, 0x55, 0x72, 0x94, 0x3e, - 0x50, 0x2a, 0xa7, 0x51, 0xfe, 0x56, 0x53, 0xad, 0x9e, 0xcb, 0xe9, 0x62, 0xbd, 0xdd, 0x94, 0xa8, 0xf2, 0x6a, 0x40, - 0xc8, 0x60, 0xd1, 0x96, 0xa1, 0x50, 0x51, 0xcd, 0xbb, 0x54, 0x25, 0xaf, 0x94, 0x88, 0xbe, 0xdc, 0x5d, 0xa4, 0x7a, - 0xc4, 0xe2, 0x8a, 0x19, 0x27, 0x53, 0x9d, 0xe4, 0x0a, 0x8d, 0x11, 0x4b, 0x0f, 0xdd, 0x54, 0x4d, 0xc1, 0x72, 0x47, - 0xd2, 0x8d, 0x74, 0xeb, 0xab, 0x47, 0xaa, 0x14, 0x84, 0xcd, 0x55, 0x64, 0xaa, 0xde, 0x26, 0xc0, 0xc0, 0x6c, 0xcd, - 0x85, 0x99, 0x02, 0x68, 0x63, 0x23, 0x0a, 0xe7, 0x68, 0xae, 0x76, 0x17, 0xdf, 0x8b, 0x62, 0xdf, 0xaa, 0x2a, 0x7f, - 0xb3, 0x08, 0xfe, 0x07, 0x09, 0xb8, 0x50, 0x4a, 0x69, 0xe0, 0xbe, 0x7d, 0x73, 0xfe, 0xde, 0xc5, 0x70, 0x3b, 0x17, - 0xcd, 0xf2, 0x60, 0xe1, 0xea, 0xd4, 0xb8, 0x26, 0x84, 0x59, 0xdd, 0xc0, 0x0d, 0xa7, 0x70, 0xd2, 0x58, 0xf2, 0x82, - 0xfd, 0xdb, 0xe6, 0xcd, 0xcd, 0x4d, 0x13, 0x0e, 0x42, 0x35, 0x67, 0x59, 0x42, 0xd3, 0x21, 0x8f, 0x68, 0xe4, 0x16, - 0x05, 0xf2, 0x45, 0x4c, 0xd3, 0xf2, 0xfe, 0x1e, 0x9e, 0x50, 0x3f, 0xe1, 0x63, 0x75, 0x88, 0x73, 0xd5, 0xaa, 0x1e, - 0x5e, 0x9d, 0xc8, 0x7b, 0xa9, 0x7a, 0x27, 0x42, 0xdd, 0x08, 0x26, 0x32, 0xf8, 0xd9, 0x83, 0x98, 0xcb, 0xc9, 0xbe, - 0x88, 0xe5, 0xc3, 0x39, 0xec, 0x30, 0xf9, 0xb4, 0xbb, 0x58, 0xa3, 0xbe, 0x3e, 0x74, 0x11, 0xf7, 0xd4, 0x9c, 0x73, - 0x59, 0xeb, 0x2a, 0x18, 0x5e, 0x5d, 0x15, 0x27, 0xfb, 0xd0, 0xd7, 0xbe, 0xe9, 0xf7, 0x9a, 0x47, 0x77, 0xa6, 0x7d, - 0x49, 0x91, 0x70, 0x3f, 0x51, 0x4a, 0x7a, 0xd0, 0x05, 0x8c, 0x1b, 0xf5, 0x00, 0x2b, 0x40, 0x91, 0xd0, 0x3a, 0x2a, - 0x4b, 0xe4, 0x16, 0x57, 0x45, 0xdb, 0x20, 0x50, 0x15, 0xab, 0x8d, 0xa2, 0xdc, 0xaf, 0x15, 0x41, 0x18, 0x90, 0x22, - 0x1b, 0xba, 0x2b, 0x04, 0xff, 0x4b, 0xc8, 0x4e, 0xf6, 0x15, 0x1e, 0xae, 0xec, 0xcb, 0x50, 0xd4, 0x35, 0x05, 0x25, - 0xb6, 0x06, 0xa9, 0xc0, 0x6f, 0x04, 0x7e, 0x73, 0x25, 0xab, 0x1a, 0xe9, 0x05, 0x6a, 0x15, 0x48, 0xf9, 0x96, 0x51, - 0x53, 0x86, 0x3c, 0x49, 0xc2, 0x69, 0x4e, 0x03, 0xf3, 0x43, 0x0b, 0x32, 0x90, 0x87, 0xeb, 0x9a, 0x83, 0xce, 0xc7, - 0x39, 0x03, 0xfd, 0x62, 0x5d, 0xad, 0x99, 0x87, 0x99, 0xd7, 0x6c, 0x0e, 0x9b, 0xd7, 0x63, 0x54, 0x88, 0x78, 0x61, - 0x8b, 0xc1, 0x47, 0xad, 0x56, 0x17, 0x92, 0x27, 0x9b, 0x61, 0xc2, 0xc6, 0x69, 0x90, 0xd0, 0x91, 0x28, 0x04, 0x9c, - 0x6a, 0x5b, 0x18, 0xbd, 0xc3, 0xef, 0x1c, 0x65, 0x74, 0xe2, 0xf8, 0xf0, 0xef, 0xfd, 0x03, 0x17, 0x22, 0x0a, 0x52, - 0x11, 0x37, 0x65, 0x92, 0x2e, 0x1c, 0x31, 0x10, 0x71, 0xed, 0x79, 0x61, 0x0d, 0x34, 0xa4, 0xa0, 0x93, 0x15, 0x22, - 0x73, 0x44, 0x8c, 0x45, 0x66, 0xd7, 0x4b, 0xd1, 0x62, 0x6d, 0x06, 0xeb, 0xaa, 0xc1, 0x01, 0x2a, 0x72, 0xa9, 0x49, - 0xaf, 0x57, 0x36, 0xfa, 0x55, 0xfd, 0x69, 0x0d, 0x7d, 0x96, 0x26, 0x58, 0x28, 0x4f, 0xf4, 0x42, 0xb5, 0x78, 0x08, - 0x32, 0x6b, 0x3a, 0x2a, 0xb6, 0x5b, 0xa0, 0x82, 0xa5, 0xd3, 0x99, 0x18, 0x48, 0x2f, 0x78, 0x06, 0xe7, 0x29, 0x2e, - 0xb0, 0x55, 0x02, 0x38, 0xb8, 0x58, 0x28, 0x60, 0x86, 0x61, 0x32, 0xf4, 0x00, 0x22, 0xa7, 0xe9, 0x1c, 0x67, 0x74, - 0x82, 0xba, 0x13, 0x96, 0x36, 0xd5, 0xbb, 0x23, 0x4b, 0x8f, 0xf1, 0x1f, 0xc3, 0x53, 0xe1, 0xcb, 0xde, 0xb0, 0x4c, - 0x76, 0xdd, 0x80, 0xcb, 0xab, 0x8b, 0xa2, 0xe8, 0x66, 0xc2, 0x1b, 0xbc, 0xf2, 0xd0, 0x05, 0xfe, 0xca, 0xba, 0xce, - 0xc5, 0x35, 0x5b, 0xc5, 0xc5, 0x1d, 0xb4, 0xa5, 0x8a, 0xbd, 0x17, 0x64, 0xb5, 0xaf, 0x08, 0x54, 0x7c, 0xea, 0xb9, - 0x34, 0x9f, 0x36, 0x15, 0xb3, 0x6b, 0x4a, 0x92, 0x75, 0xa1, 0x29, 0xd2, 0xae, 0xdd, 0xbf, 0x8a, 0x85, 0xe4, 0x63, - 0xfa, 0x4c, 0x87, 0xf2, 0x3e, 0x5c, 0x94, 0x67, 0x80, 0xf4, 0xb3, 0x7d, 0xea, 0x07, 0xd5, 0xf8, 0xc9, 0xd5, 0x69, - 0x9d, 0x29, 0x02, 0x23, 0x2b, 0xef, 0xbc, 0x0b, 0x93, 0x04, 0x06, 0xbc, 0x32, 0xfa, 0x8e, 0x7d, 0x49, 0xc8, 0x40, - 0x5c, 0x78, 0xa8, 0xd0, 0xfb, 0xf4, 0xa9, 0xd4, 0x41, 0xad, 0x8b, 0xf6, 0x76, 0x84, 0x89, 0x2e, 0x29, 0x71, 0xcd, - 0x20, 0x3e, 0x5e, 0xcb, 0xa3, 0xee, 0x56, 0xbc, 0x4b, 0x69, 0xb0, 0x8e, 0x9c, 0x10, 0x71, 0xb3, 0x34, 0x72, 0x9d, - 0xbf, 0x0c, 0x13, 0x36, 0xfc, 0x48, 0xdc, 0xdd, 0x85, 0x87, 0xd6, 0x8f, 0x49, 0x4a, 0xae, 0x60, 0x38, 0x3c, 0xaa, - 0x7b, 0xde, 0x33, 0xdf, 0x62, 0xde, 0xea, 0x1e, 0x1d, 0xb7, 0xb7, 0xbb, 0x00, 0xc6, 0xa3, 0xc6, 0xe9, 0x5d, 0x15, - 0x97, 0xd5, 0xf5, 0x58, 0x15, 0x14, 0x80, 0x66, 0x55, 0xee, 0x48, 0xa2, 0x22, 0xee, 0x27, 0x29, 0xcd, 0x75, 0x14, - 0x53, 0x03, 0x38, 0x85, 0xe6, 0x6f, 0xae, 0xf3, 0x97, 0xb2, 0x8c, 0x96, 0x2e, 0x10, 0x99, 0xc3, 0x41, 0x5c, 0x18, - 0x0b, 0xec, 0x5e, 0x3f, 0xa2, 0x22, 0x64, 0x89, 0x6a, 0xd2, 0x35, 0x16, 0xfb, 0xca, 0x8c, 0x96, 0xcb, 0xbc, 0x3e, - 0x17, 0x56, 0xc7, 0xa0, 0x9c, 0xd9, 0xc9, 0x7e, 0x05, 0xb7, 0x9c, 0x99, 0xdc, 0x93, 0x76, 0x2c, 0xb1, 0x9a, 0xa1, - 0x7a, 0xe7, 0xfc, 0x65, 0x28, 0x4f, 0x19, 0x01, 0x80, 0x5c, 0x03, 0x08, 0x51, 0x6e, 0x75, 0x8a, 0xc6, 0x4b, 0x08, - 0xf7, 0x45, 0x98, 0x8d, 0xa9, 0x58, 0x41, 0x6c, 0xa2, 0x92, 0x5a, 0xbb, 0x26, 0xa2, 0xbd, 0x06, 0x6d, 0x58, 0x87, - 0xf6, 0x0a, 0x90, 0xde, 0xdf, 0x5d, 0xb0, 0x82, 0xec, 0x2e, 0x94, 0x5c, 0xfb, 0xf0, 0xee, 0x2b, 0x38, 0x14, 0xc9, - 0x53, 0xb0, 0x44, 0x62, 0x04, 0x92, 0x56, 0x2e, 0x8e, 0x12, 0x21, 0x5c, 0x8a, 0x10, 0xc5, 0x09, 0x1c, 0x39, 0x96, - 0x04, 0xb1, 0x70, 0x9d, 0xbe, 0x82, 0x9c, 0x46, 0x0a, 0x66, 0x92, 0xc9, 0x56, 0xbc, 0x38, 0xd9, 0x57, 0xb5, 0x95, - 0x08, 0x50, 0x95, 0x00, 0x09, 0x72, 0x9f, 0x56, 0x38, 0x80, 0x44, 0x68, 0x1b, 0x0f, 0x11, 0x9b, 0x97, 0xc4, 0x26, - 0xcf, 0x5b, 0xf5, 0x4e, 0x92, 0xf0, 0x9a, 0x26, 0xbd, 0xdd, 0x45, 0xb6, 0x5c, 0xb6, 0x8a, 0x93, 0x7d, 0xf5, 0xe8, - 0x9c, 0x48, 0xbe, 0xa1, 0xee, 0xc8, 0x94, 0x4b, 0x0c, 0x87, 0x18, 0x21, 0x3d, 0xd4, 0xe4, 0x45, 0x05, 0xba, 0x83, - 0xc2, 0x75, 0x64, 0x46, 0x86, 0xac, 0x54, 0x6a, 0x50, 0x85, 0xeb, 0xb0, 0x68, 0xbd, 0x2c, 0x17, 0x74, 0x0a, 0xa5, - 0xf1, 0x72, 0xd9, 0x2e, 0x5c, 0x67, 0xc2, 0x52, 0x78, 0xca, 0x96, 0x4b, 0x79, 0x3e, 0x70, 0xc2, 0x52, 0xaf, 0x05, - 0x64, 0xeb, 0x3a, 0x93, 0xf0, 0x56, 0x4e, 0xd8, 0xbc, 0x09, 0x6f, 0xbd, 0xb6, 0x7e, 0xe5, 0x97, 0xf8, 0xc9, 0x81, - 0xe2, 0xaa, 0x15, 0x4d, 0xf4, 0x8a, 0x46, 0x78, 0xa6, 0x4e, 0x3e, 0x11, 0x2f, 0x22, 0xc9, 0xe6, 0x15, 0x8d, 0xcc, - 0x8a, 0xce, 0xb6, 0xac, 0xe8, 0xec, 0x9e, 0x15, 0x0d, 0xf5, 0xea, 0x39, 0x25, 0xee, 0xf8, 0x72, 0xd9, 0x6e, 0x55, - 0xd8, 0x3b, 0xd9, 0x8f, 0xd8, 0x1c, 0x56, 0x03, 0xf4, 0x42, 0xc1, 0x26, 0x74, 0x33, 0x51, 0xd6, 0x51, 0x4c, 0x7f, - 0x15, 0x26, 0x2b, 0x2c, 0x64, 0x75, 0x2c, 0xd8, 0x74, 0x5d, 0x06, 0xe9, 0xfe, 0x48, 0xca, 0x66, 0x80, 0x87, 0x1c, - 0xf0, 0x10, 0x9b, 0x3b, 0x33, 0x3d, 0xf7, 0xbd, 0x8b, 0x5d, 0xc7, 0x35, 0x64, 0x7d, 0x55, 0x5c, 0x82, 0x8c, 0x90, - 0xf3, 0x7b, 0x10, 0x2d, 0x42, 0x6d, 0xb7, 0xb7, 0x9d, 0xe6, 0x20, 0x9e, 0x7e, 0xc3, 0xb3, 0xc8, 0x0d, 0x54, 0xd5, - 0x5f, 0x85, 0xaa, 0x09, 0x4b, 0x75, 0x76, 0xd6, 0x56, 0x5a, 0xab, 0xde, 0xdb, 0x14, 0xd7, 0x39, 0x3a, 0x52, 0x35, - 0xa6, 0xa1, 0x10, 0x34, 0x4b, 0x35, 0xe5, 0xba, 0xee, 0xff, 0x17, 0x54, 0xb8, 0x81, 0xaf, 0x84, 0x66, 0x01, 0x0c, - 0x01, 0x6a, 0x0d, 0x5f, 0xf3, 0x7c, 0x25, 0x9e, 0x76, 0x2a, 0x0d, 0xf6, 0x0e, 0xd9, 0x56, 0x86, 0x2a, 0x02, 0xa3, - 0x67, 0x36, 0xa1, 0xd1, 0xa5, 0x64, 0xd0, 0xfd, 0xe1, 0x95, 0x56, 0x58, 0x57, 0xc4, 0x5d, 0xd5, 0x00, 0xbb, 0x3f, - 0xce, 0x3a, 0x8f, 0x0f, 0xcf, 0x5c, 0xac, 0x78, 0x3c, 0x1f, 0x8d, 0x5c, 0x54, 0x38, 0x0f, 0x6b, 0xd6, 0x3e, 0xfc, - 0x71, 0xf6, 0xe5, 0xf3, 0xd6, 0x97, 0x65, 0xe3, 0x14, 0x88, 0x48, 0x27, 0x04, 0x18, 0x51, 0x65, 0xc1, 0x6b, 0x66, - 0x34, 0x0a, 0xd3, 0xed, 0xd3, 0x19, 0xd8, 0xd3, 0xc9, 0xa7, 0x94, 0x46, 0x40, 0x9c, 0x78, 0xad, 0xf4, 0x32, 0xa1, - 0x73, 0x6a, 0xee, 0x2a, 0xdc, 0x30, 0xd8, 0x86, 0x16, 0x43, 0x3e, 0x4b, 0x85, 0xce, 0x8c, 0xd0, 0xac, 0xd6, 0x9a, - 0xd2, 0x95, 0x9c, 0x83, 0x6d, 0x23, 0xdc, 0x29, 0x39, 0x57, 0x97, 0x5e, 0xc5, 0x15, 0x76, 0x2d, 0x00, 0xb6, 0x42, - 0xd6, 0xdf, 0x52, 0x1e, 0xb4, 0x70, 0x6b, 0x1b, 0x6c, 0xb8, 0x8d, 0x02, 0xd7, 0xbd, 0x30, 0x78, 0x92, 0xce, 0xcd, - 0xda, 0x05, 0x13, 0x5b, 0xf1, 0xf5, 0x49, 0x0c, 0x5c, 0x67, 0xd0, 0x59, 0x4a, 0xf3, 0x7c, 0x2b, 0x02, 0xca, 0x45, - 0xc4, 0x6e, 0x55, 0xdb, 0xdd, 0xd2, 0x0b, 0x6e, 0x61, 0xd8, 0x61, 0x12, 0xe0, 0x32, 0xc4, 0xaa, 0x6b, 0xd1, 0xd1, - 0x88, 0x0e, 0x4b, 0xdf, 0x30, 0x04, 0xcb, 0x46, 0x2c, 0x11, 0x10, 0x33, 0x92, 0xc1, 0x1c, 0xf7, 0x35, 0x4f, 0xa9, - 0x8b, 0x4c, 0xfa, 0xa7, 0x86, 0x5f, 0xcb, 0xff, 0xcd, 0xf0, 0xa8, 0x1e, 0xeb, 0xb0, 0xe8, 0x51, 0x96, 0x4b, 0xe3, - 0x17, 0xaa, 0x95, 0xd7, 0x11, 0xc9, 0xa5, 0xe3, 0x67, 0xdb, 0x06, 0x7a, 0xd8, 0x36, 0x59, 0xb4, 0xbf, 0x3c, 0x6a, - 0xb7, 0x0a, 0x17, 0xbb, 0xd0, 0xdd, 0x43, 0x77, 0x89, 0x6c, 0x75, 0x00, 0xad, 0x66, 0xe9, 0xaf, 0x69, 0xd7, 0x69, - 0x3f, 0x69, 0xbb, 0x58, 0xdd, 0x3b, 0x80, 0x8a, 0x92, 0x19, 0x0c, 0xc1, 0x5b, 0xfa, 0xbb, 0xa7, 0x52, 0xef, 0xfc, - 0x61, 0xf0, 0x3c, 0x6a, 0xb7, 0x5c, 0xec, 0xe6, 0x82, 0x4f, 0x7f, 0xc5, 0x14, 0x0e, 0x5c, 0xec, 0x0e, 0x13, 0x9e, - 0x53, 0x7b, 0x0e, 0x4a, 0x9d, 0xfd, 0xfd, 0x93, 0x50, 0x10, 0x4d, 0x33, 0x9a, 0xe7, 0x8e, 0xdd, 0xbf, 0x26, 0xa5, - 0x4f, 0x30, 0xcc, 0x8d, 0x14, 0x97, 0x53, 0x21, 0xf1, 0xa2, 0xae, 0x04, 0xb0, 0xa9, 0x4a, 0x95, 0xad, 0x11, 0x9b, - 0x14, 0x01, 0x25, 0x63, 0x53, 0xda, 0xd5, 0x27, 0x47, 0xde, 0xb0, 0xf5, 0xd4, 0xc0, 0x2a, 0x88, 0xbc, 0x3e, 0x40, - 0xad, 0x64, 0xc2, 0xd2, 0xcb, 0x0d, 0xa5, 0xe1, 0xed, 0x86, 0x52, 0x50, 0xd9, 0x4a, 0xe8, 0xf4, 0x75, 0x35, 0x9f, - 0xc6, 0x7a, 0xa5, 0xf8, 0xd8, 0x20, 0x46, 0xd2, 0xd1, 0xf9, 0x09, 0x48, 0xad, 0x65, 0x90, 0x3d, 0xfc, 0xf6, 0xe1, - 0xa0, 0xe4, 0xd7, 0x0c, 0x57, 0xf6, 0xf2, 0xfb, 0x66, 0x08, 0xa5, 0x4d, 0x70, 0x78, 0x27, 0xbf, 0x6a, 0xae, 0xf4, - 0xf6, 0xd3, 0x04, 0x67, 0x69, 0x55, 0xbf, 0x63, 0xe9, 0xf5, 0xb1, 0xf7, 0xd5, 0xb5, 0xdf, 0x50, 0xac, 0x15, 0x9f, - 0x72, 0xfd, 0x87, 0x09, 0x9b, 0x54, 0x24, 0xb0, 0x0e, 0xa6, 0xd4, 0x78, 0x20, 0xfb, 0xc9, 0xee, 0x44, 0xa9, 0x3e, - 0x97, 0x70, 0xa6, 0x13, 0xae, 0xcd, 0x98, 0x65, 0xf4, 0x32, 0xe1, 0x37, 0xab, 0xf7, 0x80, 0x6d, 0xaf, 0x1c, 0xb3, - 0x71, 0x6c, 0x1d, 0xd4, 0xa2, 0xa4, 0x5c, 0x84, 0x7b, 0x07, 0x28, 0xfe, 0xe5, 0x9f, 0x7d, 0xff, 0x5f, 0xfe, 0xf9, - 0x93, 0x55, 0xa1, 0xfb, 0xe2, 0x0a, 0x8b, 0xaa, 0xdb, 0xed, 0xbb, 0x6b, 0xf3, 0x48, 0x75, 0x9c, 0x6f, 0xae, 0xb3, - 0xb6, 0x08, 0xf0, 0x7e, 0x6d, 0x09, 0xd6, 0x0a, 0xd5, 0xee, 0x73, 0x7e, 0x0b, 0x60, 0x30, 0xaf, 0x4f, 0x42, 0x06, - 0x95, 0x7e, 0x17, 0x68, 0x57, 0x28, 0x78, 0xd0, 0x8a, 0xfc, 0x76, 0x0c, 0x7f, 0x6a, 0x0e, 0xbf, 0x13, 0x7c, 0xed, - 0x9f, 0x18, 0x5e, 0x5d, 0x95, 0x19, 0x79, 0x76, 0x53, 0x38, 0xef, 0xdf, 0x5f, 0x2b, 0xd1, 0x8a, 0x47, 0xd0, 0x42, - 0x3d, 0x79, 0x9e, 0x90, 0x0c, 0xaf, 0x5e, 0xc1, 0x25, 0x3f, 0x27, 0xd7, 0x99, 0x71, 0xf0, 0xde, 0x23, 0x1c, 0xa0, - 0x8b, 0xfa, 0xac, 0x64, 0xa7, 0x6b, 0x92, 0x01, 0x4a, 0xc1, 0xdc, 0x00, 0x30, 0xf1, 0xf0, 0x4a, 0x5b, 0x9b, 0x67, - 0xca, 0x0d, 0x13, 0xac, 0x92, 0xb6, 0x76, 0xcf, 0xd4, 0x90, 0x8e, 0x9d, 0xf7, 0x12, 0x5f, 0xb2, 0x32, 0xad, 0xac, - 0x7b, 0xe9, 0xea, 0x02, 0x3b, 0xa2, 0x64, 0x3f, 0xf3, 0x30, 0x99, 0x3f, 0x8c, 0xf1, 0x6d, 0x17, 0xa8, 0x4b, 0x67, - 0xf9, 0x6f, 0xad, 0x12, 0x2c, 0x9b, 0xcb, 0x9a, 0x3e, 0x20, 0xb3, 0x12, 0xfe, 0xbe, 0x2d, 0x70, 0x2a, 0xe8, 0x27, - 0x03, 0xa7, 0xc9, 0x83, 0x02, 0xa7, 0xea, 0x86, 0xbe, 0x3f, 0x32, 0x70, 0xfa, 0x77, 0x3b, 0x70, 0x0a, 0x24, 0xf8, - 0xf3, 0x83, 0x82, 0x9b, 0x26, 0xf0, 0xc4, 0x6f, 0x72, 0xd2, 0xd6, 0x46, 0x40, 0xc2, 0xc7, 0x10, 0xd9, 0xfc, 0xb7, - 0x0f, 0x54, 0x26, 0x7c, 0x6c, 0x87, 0x29, 0xe1, 0x8e, 0x5a, 0x88, 0x4b, 0xe2, 0x8c, 0x2c, 0xdc, 0x1f, 0x6f, 0xdb, - 0x4f, 0x07, 0xed, 0xee, 0x41, 0x7b, 0xe2, 0x06, 0x2e, 0x48, 0x5d, 0x59, 0xd0, 0xea, 0x1e, 0x1c, 0x40, 0xc1, 0x8d, - 0x55, 0xd0, 0x81, 0x02, 0x66, 0x15, 0x1c, 0x41, 0xc1, 0xd0, 0x2a, 0x78, 0x04, 0x05, 0x91, 0x55, 0xf0, 0x18, 0x0a, - 0xe6, 0x6e, 0x31, 0x60, 0x65, 0x74, 0xf8, 0x31, 0x92, 0xd7, 0x59, 0xec, 0x64, 0xf5, 0x54, 0xfe, 0x98, 0x98, 0x2a, - 0x8f, 0xcb, 0x63, 0x40, 0xcd, 0x43, 0x73, 0x6b, 0xc5, 0xd5, 0x67, 0x57, 0x08, 0x27, 0x04, 0x4e, 0xe5, 0x61, 0x30, - 0xca, 0x55, 0xcd, 0x03, 0xf3, 0xda, 0x0d, 0xca, 0x7b, 0xa9, 0x5a, 0xb8, 0x63, 0x22, 0x9c, 0x81, 0x8b, 0xf0, 0xac, - 0xac, 0x7c, 0xd4, 0x88, 0x74, 0xb7, 0x70, 0x21, 0x44, 0x75, 0x1b, 0xcb, 0x01, 0xc2, 0xea, 0x02, 0xec, 0x67, 0x52, - 0x3e, 0xfa, 0x82, 0xbf, 0x67, 0x13, 0x6a, 0x3e, 0x0f, 0x62, 0x06, 0x70, 0x5c, 0x04, 0x07, 0xb8, 0xe3, 0xea, 0x0a, - 0xb3, 0x2f, 0xf1, 0x69, 0x75, 0x01, 0xd0, 0x5b, 0x41, 0xd4, 0x8d, 0x0a, 0x19, 0x56, 0x86, 0xde, 0x18, 0x8b, 0x70, - 0x1c, 0x40, 0xc8, 0x12, 0x7c, 0xa6, 0xc1, 0x29, 0x21, 0xa4, 0xd5, 0x9f, 0x05, 0x5f, 0xe2, 0x9b, 0x98, 0xa6, 0xc1, - 0xbc, 0xe8, 0x96, 0x04, 0xa0, 0x22, 0xa6, 0x6f, 0x45, 0x79, 0x6f, 0x9c, 0xa4, 0x8a, 0xea, 0xb5, 0x82, 0xb3, 0x59, - 0x52, 0xcf, 0x96, 0x58, 0x9a, 0xe5, 0x93, 0x19, 0x25, 0xfc, 0xa6, 0x79, 0xeb, 0xf6, 0x36, 0xc7, 0xd7, 0x60, 0x76, - 0x65, 0x7c, 0xed, 0x25, 0x00, 0x5b, 0x3e, 0xbd, 0x0f, 0xc7, 0xe5, 0xef, 0x57, 0x34, 0xcf, 0xc3, 0xb1, 0xae, 0xb9, - 0x3d, 0x9e, 0x26, 0x41, 0xb4, 0x63, 0x69, 0x06, 0x08, 0x88, 0x89, 0x01, 0x46, 0xc0, 0xa7, 0xa1, 0x43, 0x64, 0x30, - 0xf5, 0x7a, 0x74, 0x4d, 0xe2, 0xaa, 0x5e, 0x24, 0xc2, 0x71, 0x55, 0x70, 0x32, 0xcd, 0xa8, 0x2c, 0x55, 0x68, 0x2c, - 0x4e, 0xf6, 0xa1, 0x40, 0xbd, 0xde, 0x12, 0x45, 0x33, 0x0e, 0x94, 0xed, 0xb1, 0x34, 0xc7, 0x44, 0xd1, 0xec, 0x44, - 0xa5, 0x32, 0x4b, 0x69, 0x3d, 0x76, 0xf3, 0x79, 0x7b, 0x08, 0x7f, 0x74, 0x64, 0xe8, 0xf3, 0xd1, 0x68, 0x74, 0x6f, - 0x54, 0xed, 0xf3, 0x68, 0x44, 0x3b, 0xf4, 0xa8, 0x0b, 0x49, 0x2c, 0x4d, 0x1d, 0x8b, 0x69, 0x17, 0x12, 0x77, 0x8b, - 0x87, 0x55, 0x86, 0xb0, 0x8d, 0x88, 0x17, 0x0f, 0x8f, 0xb0, 0x15, 0xd3, 0x8c, 0x2e, 0x26, 0x61, 0x36, 0x66, 0x69, - 0xd0, 0x2a, 0xfc, 0xb9, 0x0e, 0x49, 0x7d, 0x7e, 0x7c, 0x7c, 0x5c, 0xf8, 0x91, 0x79, 0x6a, 0x45, 0x51, 0xe1, 0x0f, - 0x17, 0xe5, 0x34, 0x5a, 0xad, 0xd1, 0xa8, 0xf0, 0x99, 0x29, 0x38, 0xe8, 0x0c, 0xa3, 0x83, 0x4e, 0xe1, 0xdf, 0x58, - 0x35, 0x0a, 0x9f, 0xea, 0xa7, 0x8c, 0x46, 0xb5, 0x4c, 0x98, 0xc7, 0xad, 0x56, 0xe1, 0x2b, 0x42, 0x5b, 0x80, 0x59, - 0xaa, 0x7e, 0x06, 0xe1, 0x4c, 0x70, 0x60, 0xee, 0xdd, 0x44, 0x78, 0x83, 0x4b, 0x7d, 0xcb, 0x88, 0xfa, 0x26, 0x47, - 0x81, 0x2e, 0xf0, 0xcf, 0x76, 0xf0, 0x08, 0x88, 0x59, 0x06, 0x8d, 0x12, 0x13, 0x5b, 0xaa, 0xbd, 0x06, 0xca, 0x92, - 0xaf, 0x7f, 0x26, 0x49, 0x15, 0x53, 0x02, 0x4e, 0x06, 0x35, 0xd5, 0x65, 0x78, 0x94, 0x6e, 0x91, 0x1f, 0xec, 0xd3, - 0xf2, 0xe3, 0xee, 0x21, 0xe2, 0x83, 0xfd, 0xe1, 0xe2, 0x83, 0x52, 0x4b, 0x7c, 0x28, 0xe6, 0x71, 0x27, 0x88, 0x3b, - 0x8c, 0xe9, 0xf0, 0xe3, 0x35, 0xbf, 0x6d, 0xc2, 0x96, 0xc8, 0x5c, 0x29, 0x58, 0x76, 0x7f, 0x6b, 0xd6, 0x8c, 0xe9, - 0xcc, 0xfa, 0xa2, 0x87, 0x54, 0x1f, 0xde, 0xa4, 0xc4, 0x7d, 0x63, 0x6c, 0x5b, 0x55, 0x32, 0x1a, 0x11, 0xf7, 0xcd, - 0x68, 0xe4, 0x9a, 0xb3, 0x92, 0xa1, 0xa0, 0xb2, 0xd6, 0xeb, 0x5a, 0x89, 0xac, 0xf5, 0xe5, 0x97, 0x76, 0x99, 0x5d, - 0xa0, 0x43, 0x4f, 0x76, 0x98, 0x49, 0xbf, 0x89, 0x58, 0x0e, 0x5b, 0x0d, 0x3e, 0x34, 0x52, 0xbf, 0xab, 0x31, 0xad, - 0x5d, 0xab, 0x5d, 0x02, 0xbc, 0xe1, 0x2e, 0xf0, 0xd5, 0x8b, 0x02, 0xc6, 0xd4, 0xe4, 0x2d, 0x3e, 0xbd, 0xfb, 0x2a, - 0xf2, 0xee, 0x04, 0x2a, 0x58, 0xfe, 0x26, 0x5d, 0x39, 0x04, 0xa4, 0x60, 0x24, 0xc4, 0x9e, 0x56, 0x21, 0xf8, 0x78, - 0x9c, 0xc0, 0xb7, 0x5e, 0x16, 0xb5, 0xfb, 0x63, 0x55, 0xf3, 0x7e, 0x6d, 0xbe, 0x81, 0xdd, 0x50, 0xdf, 0xb6, 0x2a, - 0x3f, 0x3d, 0xa5, 0x92, 0xc7, 0xe7, 0xfa, 0x1b, 0x44, 0xd2, 0x2c, 0x5e, 0x68, 0x26, 0xbf, 0x50, 0x29, 0xc7, 0x02, - 0xd2, 0x6d, 0x54, 0xc7, 0x51, 0x51, 0xe8, 0xc3, 0x1a, 0x11, 0xcb, 0xa7, 0x70, 0xaf, 0xa9, 0x6a, 0x49, 0x3f, 0xc5, - 0xc2, 0xf3, 0x1b, 0x2b, 0xbe, 0x53, 0x5b, 0xae, 0xc2, 0x04, 0x78, 0x94, 0xc3, 0xfc, 0x4e, 0x14, 0xae, 0xf6, 0xbb, - 0x1b, 0x24, 0xba, 0x8e, 0xc2, 0xa7, 0x8a, 0x3c, 0x59, 0x33, 0x04, 0xe7, 0x77, 0xb9, 0x20, 0xe6, 0x95, 0x29, 0x28, - 0xec, 0xf8, 0xa5, 0x7c, 0xa3, 0xb0, 0x25, 0xa3, 0x25, 0xf9, 0x34, 0x4c, 0x15, 0x1b, 0x25, 0xae, 0xe2, 0x07, 0xbb, - 0x8b, 0x6a, 0xe5, 0x0b, 0xd7, 0x80, 0xad, 0x88, 0xb7, 0x77, 0xb2, 0x0f, 0x0d, 0x7a, 0x4e, 0x0d, 0xf4, 0x74, 0x2d, - 0xc8, 0xf2, 0x89, 0x74, 0x87, 0x2b, 0x3f, 0xbf, 0xc1, 0x7e, 0x7e, 0xe3, 0xfc, 0x79, 0xd1, 0xbc, 0xa1, 0xd7, 0x1f, - 0x99, 0x68, 0x8a, 0x70, 0xda, 0x04, 0xc3, 0x47, 0x3a, 0x47, 0x35, 0x7b, 0x96, 0x59, 0x7e, 0xea, 0xaa, 0x83, 0xee, - 0x2c, 0x87, 0xac, 0x08, 0xa9, 0xbe, 0x07, 0x29, 0x4f, 0x69, 0xb7, 0x9e, 0xcd, 0x69, 0x07, 0xd9, 0x0d, 0xb6, 0x2e, - 0x16, 0x1c, 0xb2, 0x28, 0xc4, 0x5d, 0xd0, 0xd2, 0x6c, 0xbd, 0x65, 0x22, 0xe8, 0xad, 0x8d, 0xf5, 0x03, 0x8d, 0xdc, - 0x86, 0x94, 0x5e, 0xd9, 0x7a, 0x26, 0xc1, 0xb6, 0x4c, 0x80, 0x4f, 0xe5, 0x36, 0x82, 0x4b, 0xd5, 0xfc, 0xb5, 0x92, - 0x42, 0x57, 0x8b, 0x65, 0x6e, 0xe3, 0x43, 0x20, 0x0b, 0xc2, 0x91, 0xa0, 0x19, 0x7e, 0x48, 0xcd, 0x6b, 0x79, 0x0c, - 0x69, 0x01, 0x62, 0x26, 0x68, 0x1f, 0x4f, 0x6f, 0x1f, 0xde, 0xfd, 0xfd, 0xd3, 0x2f, 0x34, 0x8e, 0xcc, 0xb5, 0x3c, - 0xae, 0xdb, 0x85, 0x8d, 0x90, 0x84, 0x77, 0x01, 0x4b, 0xa5, 0xcc, 0xbb, 0x06, 0xbf, 0x68, 0x77, 0xca, 0x75, 0x92, - 0x6e, 0x46, 0x13, 0xf9, 0x15, 0x3e, 0xbd, 0x14, 0x07, 0x8f, 0xa6, 0xb7, 0x66, 0x35, 0xda, 0x2b, 0xc9, 0xb7, 0x7f, - 0x68, 0x8e, 0xed, 0xf6, 0xa4, 0xde, 0x7a, 0x9e, 0xe8, 0xd1, 0xf4, 0xb6, 0xab, 0x04, 0x6d, 0x33, 0x53, 0x50, 0xb5, - 0xa6, 0xb7, 0x76, 0x96, 0x71, 0xd5, 0x91, 0xe3, 0x1f, 0xe4, 0x0e, 0x0d, 0x73, 0xda, 0x85, 0x7b, 0xc7, 0xd9, 0x30, - 0x4c, 0xb4, 0x30, 0x9f, 0xb0, 0x28, 0x4a, 0x68, 0xd7, 0xc8, 0x6b, 0xa7, 0xfd, 0x08, 0x92, 0x74, 0xed, 0x25, 0xab, - 0xaf, 0x8a, 0x85, 0xbc, 0x12, 0x4f, 0xe1, 0x75, 0xce, 0x13, 0xf8, 0xe8, 0xc7, 0x46, 0x74, 0xea, 0xec, 0xd5, 0x56, - 0x85, 0x3c, 0xf9, 0xbb, 0x3e, 0x97, 0xa3, 0xd6, 0x9f, 0xba, 0x72, 0xc1, 0x5b, 0x5d, 0xc1, 0xa7, 0x41, 0xf3, 0xa0, - 0x3e, 0x11, 0x78, 0x55, 0x4e, 0x01, 0x6f, 0x98, 0x16, 0x06, 0x69, 0xa5, 0xf8, 0xb4, 0xe3, 0xb7, 0x75, 0x99, 0xec, - 0x00, 0xf2, 0xc2, 0xca, 0xa2, 0xa2, 0x3e, 0x99, 0x7f, 0x9b, 0xdd, 0xf2, 0x64, 0xf3, 0x6e, 0x79, 0x62, 0x76, 0xcb, - 0xfd, 0x14, 0xfb, 0xf9, 0xa8, 0x0d, 0x7f, 0xba, 0xd5, 0x84, 0x82, 0x96, 0x73, 0x30, 0xbd, 0x75, 0x40, 0x4f, 0x6b, - 0x76, 0xa6, 0xb7, 0x2a, 0xc7, 0x1a, 0x62, 0x37, 0x2d, 0xc8, 0x3a, 0xc6, 0x2d, 0x07, 0x0a, 0xe1, 0x6f, 0xab, 0xf6, - 0xaa, 0x7d, 0x08, 0xef, 0xa0, 0xd5, 0xd1, 0xfa, 0xbb, 0xce, 0xfd, 0x9b, 0x36, 0x48, 0xb9, 0xf0, 0x02, 0xc3, 0x8d, - 0x91, 0x2f, 0xc2, 0xeb, 0x6b, 0x1a, 0x05, 0x23, 0x3e, 0x9c, 0xe5, 0xff, 0xa4, 0xe1, 0xd7, 0x48, 0xbc, 0x77, 0x4b, - 0xaf, 0xf4, 0x63, 0x9a, 0xaa, 0x8c, 0x6f, 0xd3, 0xc3, 0xa2, 0x5c, 0xa7, 0x20, 0x1f, 0x86, 0x09, 0xf5, 0x3a, 0xfe, - 0xe1, 0x86, 0x4d, 0xf0, 0xef, 0xb2, 0x36, 0x1b, 0x27, 0xf3, 0x7b, 0x91, 0x71, 0x2f, 0x12, 0x7e, 0x15, 0x0e, 0xec, - 0x35, 0x6c, 0x1d, 0x6f, 0x06, 0x77, 0x60, 0x46, 0xba, 0x30, 0x42, 0x41, 0xcb, 0x9d, 0x88, 0x8e, 0xc2, 0x59, 0x22, - 0xee, 0xef, 0x75, 0x1b, 0x65, 0xac, 0xf5, 0x7a, 0x0f, 0x43, 0xaf, 0xea, 0x3e, 0x90, 0x4b, 0x7f, 0xfe, 0xe4, 0x10, - 0xfe, 0xa8, 0xfc, 0xaf, 0xbb, 0x4a, 0x57, 0x57, 0x76, 0x2f, 0xe8, 0xea, 0xbb, 0x35, 0x65, 0x5c, 0x89, 0x70, 0xa9, - 0x8f, 0x3f, 0xb4, 0x36, 0x68, 0x95, 0x0f, 0xaa, 0xae, 0xb5, 0xac, 0x5f, 0x55, 0xfb, 0xd7, 0x75, 0xfe, 0xc0, 0xba, - 0x43, 0xa5, 0xb9, 0xd6, 0xeb, 0xea, 0xcf, 0x10, 0xae, 0x55, 0x36, 0x18, 0x97, 0xf5, 0x77, 0xc9, 0x5d, 0x69, 0xa2, - 0xa8, 0x68, 0x2c, 0x58, 0x29, 0xbb, 0xca, 0x4a, 0xc9, 0x29, 0xb9, 0x3a, 0xe9, 0xdf, 0x4e, 0x12, 0x67, 0xae, 0x8e, - 0x4b, 0x12, 0xb7, 0xed, 0xb7, 0x5c, 0x47, 0xe6, 0x01, 0xc0, 0xad, 0xed, 0xae, 0xfc, 0xbc, 0xad, 0xdb, 0x07, 0x4d, - 0x6b, 0x3e, 0x96, 0x9a, 0xdd, 0xcb, 0xf0, 0x8e, 0x66, 0x97, 0x1d, 0xd7, 0x01, 0x3f, 0x4d, 0x53, 0xa5, 0x4c, 0xc8, - 0x32, 0xa7, 0xe3, 0x3a, 0xb7, 0x93, 0x24, 0xcd, 0x89, 0x1b, 0x0b, 0x31, 0x0d, 0xd4, 0xf7, 0x6f, 0x6f, 0x0e, 0x7c, - 0x9e, 0x8d, 0xf7, 0x3b, 0xad, 0x56, 0x0b, 0x2e, 0x80, 0x75, 0x9d, 0x39, 0xa3, 0x37, 0x4f, 0xf9, 0x2d, 0x71, 0x5b, - 0x4e, 0xcb, 0x69, 0x77, 0x8e, 0x9d, 0x76, 0xe7, 0xd0, 0x7f, 0x74, 0xec, 0xf6, 0x3e, 0x73, 0x9c, 0x93, 0x88, 0x8e, - 0x72, 0xf8, 0xe1, 0x38, 0x27, 0x52, 0xf1, 0x52, 0xbf, 0x1d, 0xc7, 0x1f, 0x26, 0x79, 0xb3, 0xed, 0x2c, 0xf4, 0xa3, - 0xe3, 0xc0, 0xa1, 0xd2, 0xc0, 0xf9, 0x7c, 0xd4, 0x19, 0x1d, 0x8e, 0x9e, 0x74, 0x75, 0x71, 0xf1, 0x59, 0xad, 0x3a, - 0x56, 0xff, 0x77, 0xac, 0x66, 0xb9, 0xc8, 0xf8, 0x47, 0xaa, 0x73, 0x12, 0x1d, 0x10, 0x3d, 0x1b, 0x9b, 0x76, 0xd6, - 0x47, 0x6a, 0x1f, 0x5f, 0x0f, 0x47, 0x9d, 0xaa, 0xba, 0x84, 0x71, 0xbf, 0x04, 0xf2, 0x64, 0xdf, 0x80, 0x7e, 0x62, - 0xa3, 0xa9, 0xdd, 0xdc, 0x84, 0xa8, 0xb6, 0xab, 0xe7, 0x38, 0x36, 0xf3, 0x3b, 0x81, 0x33, 0x0c, 0x46, 0x57, 0x95, - 0x10, 0xb8, 0x4e, 0x44, 0xdc, 0x57, 0xed, 0xce, 0x31, 0x6e, 0xb7, 0x1f, 0xf9, 0x8f, 0x8e, 0x87, 0x2d, 0x7c, 0xe8, - 0x1f, 0x36, 0x0f, 0xfc, 0x47, 0xf8, 0xb8, 0x79, 0x8c, 0x8f, 0x5f, 0x1c, 0x0f, 0x9b, 0x87, 0xfe, 0x21, 0x6e, 0x35, - 0x8f, 0xa1, 0xb0, 0x79, 0xdc, 0x3c, 0x9e, 0x37, 0x0f, 0x8f, 0x87, 0x2d, 0x59, 0xda, 0xf1, 0x8f, 0x8e, 0x9a, 0xed, - 0x96, 0x7f, 0x74, 0x84, 0x8f, 0xfc, 0x47, 0x8f, 0x9a, 0xed, 0x03, 0xff, 0xd1, 0xa3, 0x97, 0x47, 0xc7, 0xfe, 0x01, - 0xbc, 0x3b, 0x38, 0x18, 0x1e, 0xf8, 0xed, 0x76, 0x13, 0xfe, 0xc1, 0xc7, 0x7e, 0x47, 0xfd, 0x68, 0xb7, 0xfd, 0x83, - 0x36, 0x6e, 0x25, 0x47, 0x1d, 0xff, 0xd1, 0x13, 0x2c, 0xff, 0x95, 0xd5, 0xb0, 0xfc, 0x07, 0xba, 0xc1, 0x4f, 0xfc, - 0xce, 0x23, 0xf5, 0x4b, 0x76, 0x38, 0x3f, 0x3c, 0xfe, 0xc1, 0xdd, 0xdf, 0x3a, 0x87, 0xb6, 0x9a, 0xc3, 0xf1, 0x91, - 0x7f, 0x70, 0x80, 0x0f, 0xdb, 0xfe, 0xf1, 0x41, 0xdc, 0x3c, 0xec, 0xf8, 0x8f, 0x1e, 0x0f, 0x9b, 0x6d, 0xff, 0xf1, - 0x63, 0xdc, 0x6a, 0x1e, 0xf8, 0x1d, 0xdc, 0xf6, 0x0f, 0x0f, 0xe4, 0x8f, 0x03, 0xbf, 0x33, 0x7f, 0xfc, 0xc4, 0x7f, - 0x74, 0x14, 0x3f, 0xf2, 0x0f, 0xbf, 0x3d, 0x3c, 0xf6, 0x3b, 0x07, 0xf1, 0xc1, 0x23, 0xbf, 0xf3, 0x78, 0xfe, 0xc8, - 0x3f, 0x8c, 0x9b, 0x9d, 0x47, 0xf7, 0xb6, 0x6c, 0x77, 0x7c, 0xc0, 0x91, 0x7c, 0x0d, 0x2f, 0xb0, 0x7e, 0x01, 0x7f, - 0x63, 0xd9, 0xf6, 0xdf, 0xb1, 0x9b, 0x7c, 0xbd, 0xe9, 0x13, 0xff, 0xf8, 0xf1, 0x50, 0x55, 0x87, 0x82, 0xa6, 0xa9, - 0x01, 0x4d, 0xe6, 0x4d, 0x35, 0xac, 0xec, 0xae, 0x69, 0x3a, 0x32, 0x7f, 0xf5, 0x60, 0xf3, 0x26, 0x0c, 0xac, 0xc6, - 0xfd, 0x0f, 0xed, 0xa7, 0x5c, 0xf2, 0x93, 0xfd, 0xb1, 0x22, 0xfd, 0x71, 0xef, 0x33, 0x75, 0xbb, 0xf3, 0x67, 0x57, - 0x38, 0xdd, 0xe6, 0xf8, 0xc8, 0x3e, 0xed, 0xf8, 0xe0, 0xf4, 0x21, 0x9e, 0x8f, 0xec, 0x0f, 0xf7, 0x7c, 0xa4, 0x74, - 0xc5, 0x71, 0x7e, 0x2d, 0xd6, 0x1c, 0x1c, 0xab, 0x56, 0xf1, 0x53, 0xe1, 0x0d, 0x72, 0xf8, 0x8e, 0x58, 0xd1, 0xbd, - 0x16, 0x84, 0x53, 0xdb, 0x0f, 0xc4, 0x81, 0xc5, 0x5e, 0x0b, 0xc5, 0x63, 0x93, 0x6d, 0x08, 0x09, 0x3f, 0x8d, 0x90, - 0x6f, 0x1f, 0x82, 0x8f, 0xf0, 0x0f, 0xc7, 0x47, 0x62, 0xe3, 0xa3, 0xe6, 0xcb, 0x97, 0x9e, 0x06, 0xe9, 0x29, 0x38, - 0x97, 0xcf, 0x1e, 0x1c, 0xa2, 0x6a, 0xb8, 0xfb, 0x14, 0x8a, 0x72, 0x57, 0x45, 0xbe, 0xde, 0xfd, 0x9a, 0xb0, 0x83, - 0x3a, 0x31, 0x49, 0x5c, 0xed, 0x96, 0x99, 0x4a, 0xa9, 0xa3, 0x1f, 0x4a, 0xa1, 0xd4, 0xf1, 0x5b, 0x7e, 0xab, 0x74, - 0xe9, 0xc0, 0x29, 0x59, 0xb2, 0xe0, 0x22, 0x84, 0x2f, 0xd6, 0x26, 0x7c, 0x2c, 0xbf, 0x6d, 0x0b, 0x5f, 0x13, 0x80, - 0xa4, 0x9f, 0xa1, 0xfa, 0x90, 0x43, 0xe0, 0xba, 0xfa, 0x6e, 0x0d, 0x38, 0x85, 0xf9, 0x0d, 0x9c, 0x54, 0x35, 0x51, - 0x89, 0x09, 0x78, 0x3b, 0x5e, 0xd1, 0x88, 0x85, 0x9e, 0xeb, 0x4d, 0x33, 0x3a, 0xa2, 0x59, 0xde, 0xac, 0x1d, 0xdf, - 0x94, 0x27, 0x37, 0x91, 0x6b, 0x3e, 0x8d, 0x9a, 0xc1, 0xed, 0xd8, 0x64, 0xa0, 0xfd, 0x8d, 0xae, 0x36, 0xc0, 0xdc, - 0x02, 0x9b, 0x92, 0x0c, 0x64, 0x6d, 0xa5, 0xb4, 0xb9, 0x4a, 0x6b, 0x6b, 0xfb, 0x9d, 0x23, 0xe4, 0xc8, 0x62, 0xb8, - 0x77, 0xf8, 0x7b, 0xaf, 0x79, 0xd0, 0xfa, 0x13, 0xb2, 0x9a, 0x95, 0x1d, 0x5d, 0x68, 0x77, 0x5b, 0x5a, 0x7d, 0x53, - 0xba, 0x7e, 0xb6, 0xd6, 0x55, 0x14, 0xf1, 0xb9, 0x9a, 0xbb, 0x8b, 0xba, 0xa9, 0x8e, 0x70, 0xab, 0x1b, 0x22, 0x46, - 0x6c, 0xec, 0xd9, 0x5f, 0x0c, 0x56, 0xf7, 0x1a, 0xcb, 0x0f, 0x8d, 0xa3, 0xa2, 0xaa, 0x92, 0xa2, 0x85, 0x8c, 0xb7, - 0xb0, 0xd4, 0x49, 0x97, 0x4b, 0x2f, 0x05, 0x17, 0x39, 0xb1, 0x70, 0x0a, 0xcf, 0xa8, 0x86, 0xe4, 0x14, 0x97, 0x00, - 0x49, 0x04, 0x93, 0x54, 0xfd, 0x5f, 0x15, 0x9b, 0x1f, 0xda, 0xf1, 0xe5, 0x27, 0x61, 0x3a, 0x06, 0x2a, 0x0c, 0xd3, - 0xf1, 0x9a, 0x5b, 0x4d, 0x85, 0x8c, 0x56, 0x4a, 0xab, 0xae, 0x2a, 0xf7, 0x59, 0xfe, 0xf4, 0xee, 0xbd, 0xbe, 0x00, - 0xcd, 0x05, 0xef, 0xb4, 0x8c, 0x70, 0x54, 0x97, 0x35, 0x37, 0xc8, 0x17, 0x27, 0x13, 0x2a, 0x42, 0x95, 0xaf, 0x09, - 0xfa, 0x04, 0x9c, 0x9a, 0x75, 0xb4, 0x35, 0x4a, 0x5c, 0x29, 0xdd, 0x49, 0x44, 0xe7, 0x6c, 0xa8, 0x45, 0x3d, 0x76, - 0xf4, 0xcd, 0x01, 0x4d, 0xb9, 0x34, 0xa4, 0x8d, 0x95, 0x3f, 0x66, 0x18, 0xca, 0x8c, 0x7c, 0x92, 0x72, 0xb7, 0xf7, - 0x45, 0xf9, 0xf5, 0xd3, 0x6d, 0x8b, 0x90, 0xb0, 0xf4, 0xe3, 0x20, 0xa3, 0xc9, 0x3f, 0x91, 0x2f, 0xd8, 0x90, 0xa7, - 0x5f, 0x5c, 0xc0, 0x57, 0xe9, 0xfd, 0x38, 0xa3, 0x23, 0xf2, 0x05, 0xc8, 0xf8, 0x40, 0x5a, 0x1f, 0xc0, 0x08, 0x1b, - 0xb7, 0x93, 0x04, 0x4b, 0x8d, 0xe9, 0x01, 0x0a, 0x91, 0x02, 0xd7, 0xed, 0x1c, 0xb9, 0x8e, 0xb2, 0x89, 0xe5, 0xef, - 0x9e, 0x12, 0xa7, 0x52, 0x09, 0x70, 0xda, 0x1d, 0xff, 0x28, 0xee, 0xf8, 0x4f, 0xe6, 0x8f, 0xfd, 0xe3, 0xb8, 0xfd, - 0x78, 0xde, 0x84, 0xff, 0x3b, 0xfe, 0x93, 0xa4, 0xd9, 0xf1, 0x9f, 0xc0, 0xdf, 0x6f, 0x0f, 0xfd, 0xa3, 0xb8, 0xd9, - 0xf6, 0x8f, 0xe7, 0x07, 0xfe, 0xc1, 0xcb, 0x76, 0xc7, 0x3f, 0x70, 0xda, 0x8e, 0x6a, 0x07, 0xec, 0x5a, 0x71, 0xe7, - 0x2f, 0x56, 0x36, 0xc4, 0x86, 0x70, 0x9c, 0xca, 0x39, 0x75, 0xb1, 0x57, 0x7e, 0x63, 0x51, 0xef, 0x4f, 0xed, 0xac, - 0x7b, 0x16, 0x66, 0xf0, 0xa1, 0x9b, 0xfa, 0xde, 0xad, 0xbd, 0xc3, 0x35, 0x7e, 0xb1, 0x61, 0x08, 0xd8, 0xe1, 0x2e, - 0xb6, 0x8f, 0xde, 0xc3, 0xb9, 0x75, 0x79, 0x2f, 0xb8, 0xb9, 0x1e, 0x71, 0x3b, 0x69, 0xab, 0x8a, 0xe6, 0x0a, 0x46, - 0xc9, 0x2c, 0x98, 0xfc, 0x02, 0x83, 0x1c, 0xe4, 0xab, 0xa8, 0x58, 0x1d, 0x1f, 0x52, 0x5f, 0x33, 0x6e, 0xdd, 0x3e, - 0x40, 0xab, 0x03, 0x1b, 0x11, 0x83, 0xfb, 0x22, 0x8a, 0xc2, 0x80, 0x5e, 0x73, 0xd3, 0x56, 0x58, 0x92, 0xfc, 0x82, - 0xe6, 0x7d, 0x17, 0x8a, 0xdc, 0xc0, 0x95, 0x2e, 0x3e, 0xb7, 0xfc, 0xd8, 0x4f, 0x49, 0xd8, 0x55, 0x01, 0x96, 0x87, - 0xae, 0x60, 0xd7, 0x02, 0x7e, 0x5c, 0xb4, 0xb7, 0xb7, 0x75, 0xbf, 0x48, 0x05, 0x12, 0xe6, 0x5a, 0x7d, 0x23, 0xc4, - 0x66, 0x45, 0xae, 0x8d, 0xe8, 0xb2, 0x5f, 0x89, 0x42, 0xa4, 0xf1, 0x74, 0x4d, 0x43, 0xe1, 0x87, 0xa9, 0x4a, 0xa2, - 0xb1, 0x18, 0x16, 0x6e, 0xd3, 0x03, 0x54, 0x70, 0x11, 0x5a, 0xdf, 0x01, 0xd6, 0xfb, 0x9c, 0x8b, 0xd0, 0x9c, 0xa5, - 0xb5, 0xae, 0x0d, 0x02, 0x47, 0x6f, 0xdc, 0xe9, 0xbd, 0x79, 0x7f, 0xea, 0xa8, 0xed, 0x79, 0xb2, 0x1f, 0x77, 0x7a, - 0x27, 0xd2, 0x67, 0xa2, 0x4e, 0xe2, 0x11, 0x75, 0x12, 0xcf, 0xd1, 0xa7, 0x32, 0x21, 0x92, 0x56, 0xec, 0xab, 0x69, - 0x4b, 0x9b, 0x41, 0x79, 0x7b, 0x27, 0xb3, 0x44, 0x30, 0xb8, 0xe3, 0x7a, 0x5f, 0x1e, 0xc3, 0x83, 0x05, 0x2b, 0xf3, - 0xb0, 0xb5, 0x76, 0x78, 0x2d, 0x52, 0xe3, 0x1b, 0x1e, 0xb1, 0x84, 0x9a, 0xcc, 0x6b, 0xdd, 0x55, 0x79, 0x52, 0x60, - 0xbd, 0x76, 0x3e, 0xbb, 0x9e, 0x30, 0xe1, 0x9a, 0xf3, 0x0c, 0x1f, 0x74, 0x83, 0x13, 0x39, 0x54, 0xef, 0xaa, 0xd0, - 0xce, 0x6b, 0xf3, 0x35, 0x9f, 0xfa, 0x92, 0xea, 0xd9, 0x6b, 0x09, 0x01, 0x27, 0xe4, 0xe2, 0x83, 0x5e, 0xe9, 0x2e, - 0xb6, 0xdf, 0x15, 0x27, 0xfb, 0xf1, 0x41, 0xef, 0x2a, 0x98, 0xea, 0xfe, 0x5e, 0xf2, 0xf1, 0xe6, 0xbe, 0x12, 0x3e, - 0xee, 0xcb, 0xa3, 0x20, 0xea, 0x90, 0xb2, 0x51, 0x7e, 0x79, 0xe2, 0xf6, 0x4e, 0xb4, 0x32, 0xe0, 0xc8, 0xc0, 0xba, - 0x7b, 0xd4, 0x32, 0xa7, 0x4b, 0x12, 0x3e, 0x86, 0x0d, 0xa9, 0x9a, 0x58, 0x83, 0xd4, 0x3c, 0xee, 0x71, 0xbb, 0x77, - 0x12, 0x3a, 0x92, 0xb7, 0x48, 0xe6, 0x91, 0x07, 0xfb, 0xd0, 0x38, 0xe6, 0x13, 0xea, 0x33, 0xbe, 0x7f, 0x43, 0xaf, - 0x9b, 0xe1, 0x94, 0x55, 0xee, 0x6d, 0x50, 0x3a, 0xca, 0x21, 0xb9, 0xf1, 0x88, 0xeb, 0xb3, 0x57, 0x9d, 0xca, 0xdd, - 0x76, 0x08, 0x36, 0x8f, 0x71, 0xcd, 0x49, 0x9f, 0x9c, 0x05, 0x16, 0xef, 0x9d, 0xec, 0x87, 0x2b, 0x18, 0x91, 0xfc, - 0xbe, 0xd0, 0x8e, 0x76, 0x30, 0x6c, 0x80, 0xde, 0x5c, 0x47, 0x89, 0x03, 0xe3, 0x90, 0xd7, 0x82, 0xba, 0x70, 0x7b, - 0xff, 0xfa, 0x3f, 0xfe, 0x97, 0xf6, 0xb1, 0x9f, 0xec, 0xc7, 0x6d, 0xd3, 0xd7, 0xca, 0xaa, 0x14, 0x27, 0x70, 0xdc, - 0xb3, 0x0a, 0x0a, 0xd3, 0xdb, 0xe6, 0x38, 0x63, 0x51, 0x33, 0x0e, 0x93, 0x91, 0xdb, 0xdb, 0x8e, 0x4d, 0xfb, 0xd8, - 0x96, 0x86, 0xba, 0x5e, 0x04, 0xf4, 0xfa, 0x9b, 0x0e, 0x1e, 0x99, 0xf3, 0x2b, 0x72, 0x6b, 0xdb, 0xc7, 0x90, 0xaa, - 0xdd, 0x57, 0x3b, 0x8a, 0x94, 0xea, 0x4f, 0x84, 0x69, 0x0e, 0x98, 0xd6, 0x4e, 0x20, 0x15, 0xae, 0x53, 0x06, 0xb5, - 0xfe, 0xef, 0xff, 0xfc, 0x2f, 0xff, 0xcd, 0x3c, 0x42, 0xac, 0xea, 0x5f, 0xff, 0xfb, 0x7f, 0xfe, 0x3f, 0xff, 0xfb, - 0xbf, 0xc2, 0xa9, 0x15, 0x1d, 0xcf, 0x92, 0x4c, 0xc5, 0xa9, 0x82, 0x59, 0x8a, 0xbb, 0x38, 0x90, 0xd8, 0x39, 0x61, - 0xb9, 0x60, 0xc3, 0xfa, 0x99, 0xa4, 0x73, 0x39, 0xa0, 0xdc, 0x99, 0x1a, 0x3a, 0xb9, 0xc3, 0x8b, 0x8a, 0xa0, 0x6a, - 0x28, 0x97, 0x84, 0x5b, 0x9c, 0xec, 0x03, 0xbe, 0x1f, 0x76, 0x8c, 0xd3, 0x2f, 0x97, 0x63, 0x61, 0xc8, 0x04, 0x4a, - 0x8a, 0xaa, 0xdc, 0x81, 0xd8, 0xca, 0x02, 0x1e, 0x83, 0x8e, 0x55, 0x2c, 0x57, 0xaf, 0xd6, 0xa6, 0xfb, 0xd3, 0x2c, - 0x17, 0x6c, 0x04, 0x28, 0x57, 0x7e, 0x62, 0x19, 0xc6, 0x6e, 0x82, 0xae, 0x98, 0xdc, 0x15, 0xb2, 0x17, 0x45, 0xa0, - 0x87, 0xc7, 0x7f, 0x2a, 0xfe, 0x32, 0x01, 0x8d, 0xcc, 0xf1, 0x26, 0xe1, 0xad, 0x36, 0xcf, 0x1f, 0xb5, 0x5a, 0xd3, - 0x5b, 0xb4, 0xa8, 0x46, 0xc0, 0xdb, 0x06, 0x93, 0x74, 0x6c, 0x77, 0x28, 0xe3, 0xdf, 0xa5, 0x1b, 0xbb, 0xe5, 0x80, - 0x2f, 0xdc, 0x69, 0x15, 0xc5, 0x9f, 0x17, 0xd2, 0x93, 0xca, 0x7e, 0x81, 0x38, 0xb5, 0x76, 0x3a, 0x5f, 0x73, 0x7b, - 0x72, 0x0b, 0xab, 0x55, 0x47, 0xb5, 0x8a, 0xdb, 0xeb, 0xa7, 0x13, 0xed, 0x38, 0xbb, 0x1d, 0x21, 0x3f, 0x84, 0x98, - 0x77, 0xdc, 0xc6, 0x71, 0x67, 0x51, 0x76, 0x2f, 0x04, 0x9f, 0xd8, 0x81, 0x75, 0x1a, 0xd2, 0x21, 0x1d, 0x19, 0x67, - 0xbd, 0x7e, 0xaf, 0x82, 0xe6, 0x45, 0x7c, 0xb0, 0x61, 0x2c, 0x0d, 0x92, 0x0c, 0xa8, 0x3b, 0xad, 0xe2, 0x73, 0xd8, - 0x81, 0x8b, 0x51, 0xc2, 0x43, 0x11, 0x48, 0x82, 0xed, 0xda, 0xe1, 0xf9, 0x10, 0x78, 0x12, 0x5f, 0x58, 0xf0, 0x74, - 0x55, 0x55, 0x70, 0x9b, 0xd7, 0xcf, 0x90, 0x16, 0xbe, 0x6c, 0x6e, 0x77, 0xa5, 0xbc, 0x6e, 0xdf, 0xea, 0xa8, 0xf7, - 0xbb, 0x9a, 0xbb, 0x4a, 0x0b, 0xa4, 0x0e, 0xda, 0xfc, 0x5e, 0xc9, 0x75, 0xf5, 0xf6, 0x6b, 0xe1, 0xb9, 0x12, 0x4c, - 0x77, 0xb5, 0x96, 0x2c, 0x84, 0x5a, 0xef, 0xc8, 0xb7, 0xa5, 0xc9, 0x14, 0x4e, 0xa7, 0xb2, 0x22, 0xea, 0x9e, 0xec, - 0x2b, 0x4d, 0x17, 0xb8, 0x87, 0x4c, 0xe9, 0x50, 0x19, 0x14, 0xba, 0x92, 0xde, 0x0a, 0xea, 0x97, 0xce, 0xad, 0x80, - 0x4f, 0xc7, 0xf5, 0xfe, 0x1f, 0xe7, 0xe0, 0x1c, 0x12, 0xcf, 0x89, 0x00, 0x00}; + 0xe4, 0x2b, 0x20, 0x44, 0x5b, 0x41, 0x6f, 0x36, 0xc1, 0x8b, 0x2e, 0x96, 0x41, 0x36, 0x19, 0x59, 0x76, 0xe2, 0x64, + 0xfb, 0x16, 0xcb, 0x4e, 0x76, 0xc2, 0x70, 0x4b, 0x10, 0xd1, 0x24, 0xda, 0x06, 0x01, 0x06, 0x68, 0x52, 0x52, 0x48, + 0x9c, 0x9a, 0x0f, 0x98, 0xaa, 0xa9, 0x9a, 0xa7, 0x79, 0x99, 0x9a, 0xf3, 0x30, 0x1f, 0x31, 0xcf, 0xe7, 0x53, 0xce, + 0x0f, 0xcc, 0x7c, 0xc2, 0xd4, 0xea, 0x0b, 0xd0, 0xe0, 0x45, 0x56, 0x2e, 0xe7, 0x9c, 0x29, 0x97, 0x6d, 0xa2, 0xd1, + 0x97, 0xd5, 0xab, 0x57, 0xaf, 0x7b, 0x37, 0xba, 0x7b, 0x41, 0x32, 0xe2, 0x77, 0x33, 0x6a, 0x85, 0x7c, 0x1a, 0xf5, + 0xba, 0xea, 0x5f, 0xea, 0x07, 0xbd, 0x6e, 0xc4, 0xe2, 0x8f, 0x56, 0x4a, 0x23, 0xc2, 0x46, 0x49, 0x6c, 0x85, 0x29, + 0x1d, 0x93, 0xc0, 0xe7, 0xbe, 0xc7, 0xa6, 0xfe, 0x84, 0x5a, 0x8d, 0x5e, 0x77, 0x4a, 0xb9, 0x6f, 0x8d, 0x42, 0x3f, + 0xcd, 0x28, 0x27, 0xef, 0xdf, 0x7d, 0x55, 0x3f, 0xed, 0x75, 0xb3, 0x51, 0xca, 0x66, 0xdc, 0x82, 0x2e, 0xc9, 0x34, + 0x09, 0xe6, 0x11, 0xed, 0x35, 0x1a, 0x37, 0x37, 0x37, 0xee, 0x87, 0xec, 0xb3, 0x51, 0x12, 0x67, 0xdc, 0x7a, 0x41, + 0x6e, 0x58, 0x1c, 0x24, 0x37, 0x98, 0x71, 0xf2, 0xc2, 0xbd, 0x08, 0xfd, 0x20, 0xb9, 0x79, 0x9b, 0x24, 0xfc, 0xe0, + 0xc0, 0x91, 0x8f, 0x77, 0xe7, 0x17, 0x17, 0x84, 0x90, 0x45, 0xc2, 0x02, 0xab, 0xb9, 0x5a, 0x95, 0x85, 0x6e, 0xec, + 0x73, 0xb6, 0xa0, 0xb2, 0x09, 0x3a, 0x38, 0xb0, 0xfd, 0x20, 0x99, 0x71, 0x1a, 0x5c, 0xf0, 0xbb, 0x88, 0x5e, 0x84, + 0x94, 0xf2, 0xcc, 0x66, 0xb1, 0xf5, 0x34, 0x19, 0xcd, 0xa7, 0x34, 0xe6, 0xee, 0x2c, 0x4d, 0x78, 0x02, 0x90, 0x1c, + 0x1c, 0xd8, 0x29, 0x9d, 0x45, 0xfe, 0x88, 0xc2, 0xfb, 0xf3, 0x8b, 0x8b, 0xb2, 0x45, 0x59, 0x09, 0x67, 0x9c, 0x5c, + 0xdc, 0x4d, 0xaf, 0x93, 0xc8, 0x41, 0xd8, 0xe7, 0x24, 0xa6, 0x37, 0xd6, 0x0f, 0xd4, 0xff, 0xf8, 0xd2, 0x9f, 0x75, + 0x46, 0x91, 0x9f, 0x65, 0xd6, 0x2d, 0x5f, 0x8a, 0x29, 0xa4, 0xf3, 0x11, 0x4f, 0x52, 0x87, 0x63, 0x8a, 0x19, 0x5a, + 0xb2, 0xb1, 0xc3, 0x43, 0x96, 0xb9, 0x97, 0xfb, 0xa3, 0x2c, 0x7b, 0x4b, 0xb3, 0x79, 0xc4, 0xf7, 0xc9, 0x5e, 0x13, + 0xb3, 0x3d, 0x42, 0x32, 0x8e, 0x78, 0x98, 0x26, 0x37, 0xd6, 0xb3, 0x34, 0x4d, 0x52, 0xc7, 0x3e, 0xbf, 0xb8, 0x90, + 0x35, 0x2c, 0x96, 0x59, 0x71, 0xc2, 0xad, 0xa2, 0x3f, 0xff, 0x3a, 0xa2, 0xae, 0xf5, 0x3e, 0xa3, 0xd6, 0xd5, 0x3c, + 0xce, 0xfc, 0x31, 0x3d, 0xbf, 0xb8, 0xb8, 0xb2, 0x92, 0xd4, 0xba, 0x1a, 0x65, 0xd9, 0x95, 0xc5, 0xe2, 0x8c, 0x53, + 0x3f, 0x70, 0x6d, 0xd4, 0x11, 0x83, 0x8d, 0xb2, 0xec, 0x1d, 0xbd, 0xe5, 0x84, 0x63, 0xf1, 0xc8, 0x09, 0xcd, 0x27, + 0x94, 0x5b, 0x59, 0x31, 0x2f, 0x07, 0x2d, 0x23, 0xca, 0x2d, 0x4e, 0xc4, 0xfb, 0xa4, 0x23, 0x71, 0x4f, 0xe5, 0x23, + 0xef, 0xb0, 0xb1, 0xc3, 0xf8, 0xc1, 0x01, 0x2f, 0xf0, 0x8c, 0xe4, 0xd4, 0x2c, 0x46, 0xe8, 0x9e, 0x2e, 0x3b, 0x38, + 0xa0, 0x6e, 0x44, 0xe3, 0x09, 0x0f, 0x09, 0x21, 0xad, 0x0e, 0x3b, 0x38, 0x70, 0x38, 0xf1, 0xb9, 0x3b, 0xa1, 0xdc, + 0xa1, 0x08, 0xe1, 0xb2, 0xf5, 0xc1, 0x81, 0x23, 0x91, 0x90, 0x10, 0x89, 0xb8, 0x0a, 0x8e, 0x91, 0xab, 0xb0, 0x7f, + 0x71, 0x17, 0x8f, 0x1c, 0x13, 0x7e, 0x84, 0xd9, 0xc1, 0x81, 0xcf, 0xdd, 0x0c, 0x7a, 0xc4, 0x1c, 0xa1, 0x3c, 0xa5, + 0x7c, 0x9e, 0xc6, 0x16, 0xcf, 0x79, 0x72, 0xc1, 0x53, 0x16, 0x4f, 0x1c, 0xb4, 0xd4, 0x65, 0x46, 0xc3, 0x3c, 0x97, + 0xe0, 0xbe, 0xe2, 0x24, 0x26, 0x3d, 0x18, 0xf1, 0x96, 0x3b, 0xb0, 0x8a, 0xc9, 0xd8, 0x8a, 0x09, 0xb1, 0x33, 0xd1, + 0xd6, 0xee, 0xc7, 0x5e, 0x5c, 0xb3, 0x6d, 0x2c, 0xa1, 0xc4, 0x19, 0x47, 0xf8, 0x35, 0x71, 0x62, 0xec, 0xba, 0x2e, + 0x47, 0xa4, 0xb7, 0xd4, 0x58, 0x89, 0x8d, 0x79, 0xf6, 0xe3, 0x41, 0x73, 0xe8, 0x71, 0x37, 0xa5, 0xc1, 0x7c, 0x44, + 0x1d, 0x87, 0xe1, 0x0c, 0xa7, 0x88, 0xf4, 0x58, 0xcd, 0x49, 0x48, 0x0f, 0x96, 0x3b, 0xa9, 0xae, 0x35, 0x21, 0x7b, + 0x4d, 0xa4, 0x60, 0x4c, 0x34, 0x80, 0x80, 0x61, 0x05, 0x4f, 0x42, 0x88, 0x1d, 0xcf, 0xa7, 0xd7, 0x34, 0xb5, 0x8b, + 0x6a, 0x9d, 0x0a, 0x59, 0xcc, 0x33, 0x6a, 0x8d, 0xb2, 0xcc, 0x1a, 0xcf, 0xe3, 0x11, 0x67, 0x49, 0x6c, 0xd9, 0xb5, + 0xa4, 0x66, 0x4b, 0x72, 0x28, 0xa8, 0xc1, 0x46, 0x39, 0x72, 0x32, 0x54, 0x8b, 0x07, 0x69, 0xad, 0x35, 0xc4, 0x00, + 0x25, 0xea, 0xa8, 0xfe, 0x14, 0x02, 0x28, 0x8e, 0x61, 0x8e, 0x39, 0x7e, 0xcb, 0x61, 0x96, 0x62, 0x8a, 0x8c, 0xf7, + 0x63, 0x77, 0x73, 0xa3, 0x10, 0xee, 0x4e, 0xfd, 0x99, 0x43, 0x49, 0x8f, 0x0a, 0xe2, 0xf2, 0xe3, 0x11, 0xc0, 0x5a, + 0x59, 0xb7, 0x3e, 0xf5, 0xa8, 0x5b, 0x92, 0x14, 0xf2, 0xb8, 0x3b, 0x4e, 0xd2, 0x67, 0xfe, 0x28, 0x84, 0x76, 0x05, + 0xc1, 0x04, 0x7a, 0xbf, 0x8d, 0x52, 0xea, 0x73, 0xfa, 0x2c, 0xa2, 0xf0, 0xe4, 0xd8, 0xa2, 0xa5, 0x8d, 0x70, 0x46, + 0x5e, 0xb8, 0x11, 0xe3, 0xaf, 0x92, 0x78, 0x44, 0x3b, 0x99, 0x41, 0x5d, 0x0c, 0xd6, 0xfd, 0x8c, 0xf3, 0x94, 0x5d, + 0xcf, 0x39, 0x75, 0xec, 0x18, 0x6a, 0xd8, 0x38, 0x43, 0x98, 0xb9, 0x9c, 0xde, 0xf2, 0xf3, 0x24, 0xe6, 0x34, 0xe6, + 0x84, 0x6a, 0xa4, 0xe2, 0xd8, 0xf5, 0x67, 0x33, 0x1a, 0x07, 0xe7, 0x21, 0x8b, 0x02, 0x87, 0xa1, 0x1c, 0xe5, 0x38, + 0xe4, 0x04, 0xe6, 0x48, 0x7a, 0xb1, 0x07, 0xff, 0xec, 0x9e, 0x8d, 0xc3, 0x49, 0x4f, 0x6c, 0x0a, 0x4a, 0x6c, 0xbb, + 0x33, 0x4e, 0x52, 0x47, 0xcd, 0xc0, 0x4a, 0xc6, 0x16, 0x87, 0x31, 0xde, 0xce, 0x23, 0x9a, 0x21, 0x5a, 0x23, 0xac, + 0x58, 0x46, 0x85, 0xe0, 0x57, 0x40, 0xf1, 0x39, 0x72, 0x62, 0xe4, 0xc5, 0x9d, 0x85, 0x9f, 0x5a, 0x3f, 0xa8, 0x1d, + 0xf5, 0x54, 0x73, 0xb3, 0x11, 0x27, 0x4f, 0x5d, 0x9e, 0xce, 0x33, 0x4e, 0x83, 0x77, 0x77, 0x33, 0x9a, 0xe1, 0xe7, + 0x9c, 0x8c, 0x78, 0x7f, 0xc4, 0x5d, 0x3a, 0x9d, 0xf1, 0xbb, 0x0b, 0xc1, 0x18, 0x3d, 0xdb, 0xc6, 0x01, 0xd4, 0x4c, + 0xa9, 0x3f, 0x02, 0x66, 0xa6, 0xb0, 0xf5, 0x26, 0x89, 0xee, 0xc6, 0x2c, 0x8a, 0x2e, 0xe6, 0xb3, 0x59, 0x92, 0x72, + 0xfc, 0x77, 0xb2, 0xe4, 0x49, 0x89, 0x1a, 0x58, 0xcb, 0x65, 0x76, 0xc3, 0xf8, 0x28, 0x74, 0x38, 0x5a, 0x8e, 0xfc, + 0x8c, 0x5a, 0x4f, 0x92, 0x24, 0xa2, 0x3e, 0x4c, 0x3a, 0xee, 0x3f, 0xe7, 0x5e, 0x3c, 0x8f, 0xa2, 0xce, 0x75, 0x4a, + 0xfd, 0x8f, 0x1d, 0xf1, 0xfa, 0xf5, 0xf5, 0x07, 0x3a, 0xe2, 0x9e, 0xf8, 0x7d, 0x96, 0xa6, 0xfe, 0x1d, 0x54, 0x24, + 0x04, 0xaa, 0xf5, 0x63, 0xef, 0xdb, 0x8b, 0xd7, 0xaf, 0x5c, 0xb9, 0x49, 0xd8, 0xf8, 0xce, 0x89, 0x8b, 0x8d, 0x17, + 0xe7, 0x78, 0x9c, 0x26, 0xd3, 0xb5, 0xa1, 0x25, 0xd6, 0xe2, 0xce, 0x0e, 0x10, 0x28, 0x89, 0xf7, 0x64, 0xd7, 0x26, + 0x04, 0xaf, 0x04, 0xcd, 0xc3, 0x4b, 0xa2, 0xc7, 0x9d, 0x47, 0x91, 0x27, 0x8b, 0x9d, 0x18, 0xdd, 0x0f, 0x2d, 0x4f, + 0xef, 0x96, 0x94, 0x08, 0x38, 0x67, 0x20, 0x61, 0x00, 0xc6, 0x91, 0xcf, 0x47, 0xe1, 0x92, 0x8a, 0xce, 0x72, 0x0d, + 0x31, 0xcd, 0x73, 0x7c, 0x56, 0xd0, 0x3b, 0x07, 0x40, 0x04, 0xa3, 0x22, 0x7c, 0xb5, 0x82, 0x09, 0x23, 0xfc, 0x13, + 0x59, 0xfa, 0x7a, 0x3e, 0xde, 0x5e, 0x13, 0xc3, 0xbe, 0xf4, 0x24, 0x77, 0xc1, 0xa3, 0x24, 0x5e, 0xd0, 0x94, 0xd3, + 0xd4, 0xfb, 0x3b, 0x4e, 0xe9, 0x38, 0x02, 0x28, 0xf6, 0x5a, 0x38, 0xf4, 0xb3, 0xf3, 0xd0, 0x8f, 0x27, 0x34, 0xf0, + 0xce, 0x78, 0x8e, 0x39, 0x27, 0xf6, 0x98, 0xc5, 0x7e, 0xc4, 0x7e, 0xa5, 0x81, 0xad, 0xc4, 0xc1, 0x33, 0x8b, 0xde, + 0x72, 0x1a, 0x07, 0x99, 0xf5, 0xfc, 0xdd, 0xcb, 0x17, 0x6a, 0x21, 0x2b, 0x12, 0x02, 0x2d, 0xb3, 0xf9, 0x8c, 0xa6, + 0x0e, 0xc2, 0x4a, 0x42, 0x3c, 0x63, 0x82, 0x3b, 0xbe, 0xf4, 0x67, 0xb2, 0x84, 0x65, 0xef, 0x67, 0x81, 0xcf, 0xe9, + 0x1b, 0x1a, 0x07, 0x2c, 0x9e, 0x90, 0xbd, 0x96, 0x2c, 0x0f, 0x7d, 0xf5, 0x22, 0x28, 0x8a, 0x2e, 0xf7, 0x9f, 0x45, + 0x62, 0xe2, 0xc5, 0xe3, 0xdc, 0x41, 0x79, 0xc6, 0x7d, 0xce, 0x46, 0x96, 0x1f, 0x04, 0xdf, 0xc4, 0x8c, 0x33, 0x01, + 0x60, 0x0a, 0xeb, 0x03, 0x34, 0x4a, 0xa5, 0xac, 0xd0, 0x80, 0x3b, 0x08, 0x3b, 0x8e, 0x92, 0x00, 0x21, 0x52, 0x0b, + 0x76, 0x70, 0x50, 0xf2, 0xfb, 0x3e, 0xf5, 0xe4, 0x4b, 0x32, 0x18, 0x22, 0x77, 0x36, 0xcf, 0x60, 0xa5, 0xf5, 0x10, + 0x20, 0x5e, 0x92, 0xeb, 0x8c, 0xa6, 0x0b, 0x1a, 0x14, 0xd4, 0x91, 0x39, 0x68, 0xb9, 0x36, 0x86, 0xda, 0x17, 0x9c, + 0x0c, 0x86, 0x1d, 0x93, 0x71, 0x53, 0x45, 0xe8, 0x69, 0x32, 0xa3, 0x29, 0x67, 0x34, 0x2b, 0x78, 0x89, 0x03, 0x62, + 0xb4, 0xe0, 0x27, 0x19, 0xd1, 0xf3, 0x9b, 0x39, 0x0c, 0x53, 0x54, 0xe1, 0x18, 0x5a, 0xd2, 0x3e, 0x5b, 0x08, 0x91, + 0x91, 0x61, 0x86, 0x30, 0x97, 0x90, 0x66, 0x08, 0xe5, 0x08, 0x73, 0x0d, 0xae, 0xe4, 0x45, 0x6a, 0xb4, 0x3b, 0x90, + 0xd5, 0xe4, 0x27, 0x21, 0xab, 0x81, 0xa3, 0xf9, 0x9c, 0x1e, 0x1c, 0x38, 0xd4, 0x2d, 0xa8, 0x82, 0xec, 0xb5, 0xd4, + 0x1a, 0x19, 0xc8, 0xda, 0x01, 0x36, 0x0c, 0xcc, 0x31, 0x45, 0x78, 0x8f, 0xba, 0x71, 0x72, 0x36, 0x1a, 0xd1, 0x2c, + 0x4b, 0xd2, 0x83, 0x83, 0x3d, 0x51, 0xbf, 0x50, 0x27, 0x60, 0x0d, 0x5f, 0xdf, 0xc4, 0x25, 0x04, 0xa8, 0x14, 0xb1, + 0x4a, 0x30, 0x70, 0x10, 0x54, 0x42, 0xe3, 0xb0, 0xfb, 0x5a, 0xf3, 0xf0, 0xec, 0xcb, 0x4b, 0xbb, 0xc6, 0xb1, 0x42, + 0xc3, 0x84, 0xea, 0xa1, 0xef, 0x9e, 0x52, 0xa9, 0x5b, 0x09, 0xcd, 0x63, 0x03, 0x33, 0x72, 0x03, 0xb9, 0x01, 0x1d, + 0xb3, 0xd8, 0x98, 0x76, 0x05, 0x24, 0xcc, 0x71, 0x86, 0x72, 0x63, 0x41, 0xb7, 0x76, 0x2d, 0x94, 0x1a, 0xb9, 0x72, + 0xcb, 0x89, 0x50, 0x24, 0x8c, 0x65, 0x1c, 0xd0, 0x61, 0x8e, 0x05, 0xea, 0xf5, 0x6c, 0x52, 0x01, 0xe8, 0x80, 0x0f, + 0x3b, 0xea, 0x3d, 0xc9, 0x24, 0xe6, 0x52, 0xfa, 0xcb, 0x9c, 0x66, 0x5c, 0xd2, 0xb1, 0xc3, 0x71, 0x8a, 0x19, 0xca, + 0x61, 0xbf, 0x8d, 0xd9, 0x64, 0x9e, 0x82, 0xbe, 0x03, 0x7b, 0x91, 0xc6, 0xf3, 0x29, 0xd5, 0x4f, 0xdb, 0x60, 0x7b, + 0x3d, 0x03, 0x89, 0x98, 0x01, 0x4d, 0xdf, 0x4f, 0x4e, 0x00, 0x2b, 0x47, 0xab, 0xd5, 0x4f, 0xba, 0x93, 0x72, 0x29, + 0x0b, 0x1d, 0x6d, 0x7d, 0x4d, 0x38, 0x52, 0x12, 0x79, 0xaf, 0x25, 0xc1, 0xe7, 0x7c, 0x48, 0xf6, 0x9a, 0x05, 0x0d, + 0x2b, 0xac, 0x4a, 0x70, 0x24, 0x12, 0x5f, 0xcb, 0xae, 0x90, 0x10, 0xf0, 0x15, 0x72, 0x71, 0xc3, 0x0d, 0x4a, 0x0d, + 0xc9, 0x00, 0x54, 0x0d, 0x37, 0x1c, 0xee, 0x22, 0x27, 0xcd, 0x0f, 0x1c, 0xbe, 0xf9, 0xae, 0x64, 0x1b, 0x8b, 0x2a, + 0xdb, 0x58, 0x9b, 0x86, 0x3d, 0x2b, 0x9a, 0xd8, 0x05, 0x95, 0xa9, 0x8d, 0x5e, 0xbe, 0xc2, 0x4c, 0x00, 0x53, 0x4e, + 0xc9, 0xe8, 0xe2, 0x95, 0x3f, 0xa5, 0x99, 0x43, 0x11, 0xde, 0x55, 0x41, 0x92, 0x27, 0x54, 0x19, 0x1a, 0x92, 0x33, + 0x03, 0xc9, 0xc9, 0x90, 0x54, 0xcc, 0xaa, 0x1b, 0x2e, 0xc3, 0x74, 0x90, 0x0d, 0x4b, 0x7d, 0xce, 0x98, 0xbc, 0x10, + 0xc9, 0x8a, 0xbe, 0x35, 0xfe, 0x64, 0x99, 0x44, 0x9a, 0xd0, 0x1b, 0x32, 0x84, 0xf7, 0x9a, 0xeb, 0x2b, 0xa9, 0x6b, + 0x95, 0x73, 0x1c, 0x0c, 0x61, 0x1d, 0x84, 0xc4, 0x70, 0x59, 0x26, 0xfe, 0xaf, 0xec, 0x34, 0x40, 0xdb, 0x05, 0x10, + 0x86, 0x3b, 0x8e, 0x7c, 0xee, 0xb4, 0x1a, 0x4d, 0x50, 0x46, 0x17, 0x14, 0x04, 0x0a, 0x42, 0x9b, 0x53, 0xa1, 0xee, + 0x3c, 0xce, 0x42, 0x36, 0xe6, 0x4e, 0xc8, 0x05, 0x4b, 0xa1, 0x51, 0x46, 0x2d, 0x5e, 0x51, 0x89, 0x05, 0xbb, 0x09, + 0x81, 0xd8, 0x0a, 0xfd, 0x8b, 0x6a, 0x48, 0x05, 0xdb, 0x02, 0xee, 0x50, 0xaa, 0xd3, 0x25, 0x97, 0xd1, 0xb5, 0x19, + 0xa8, 0x8c, 0xad, 0xbe, 0xec, 0xd1, 0x53, 0xcc, 0x80, 0x19, 0x5a, 0x2b, 0xf3, 0x4c, 0x0e, 0xa1, 0x0a, 0xb9, 0xcb, + 0x93, 0x17, 0xc9, 0x0d, 0x4d, 0xcf, 0x7d, 0x00, 0xde, 0x93, 0xcd, 0x73, 0x29, 0x08, 0x04, 0xbf, 0xe7, 0x1d, 0x4d, + 0x2f, 0x97, 0x62, 0xe2, 0x6f, 0xd2, 0x64, 0xca, 0x32, 0x0a, 0xca, 0x9a, 0xc4, 0x7f, 0x0c, 0xfb, 0x4c, 0x6c, 0x48, + 0x10, 0x36, 0xb4, 0xa0, 0xaf, 0xb3, 0x17, 0x55, 0xfa, 0xba, 0xdc, 0x7f, 0x36, 0xd1, 0x0c, 0xb0, 0xba, 0x8d, 0x11, + 0x76, 0x94, 0x49, 0x61, 0xc8, 0x39, 0x37, 0x44, 0x4a, 0xc2, 0xaf, 0x56, 0xdc, 0xb0, 0xdc, 0x2a, 0xea, 0x22, 0x95, + 0xdb, 0x06, 0xe5, 0x7e, 0x10, 0x80, 0x62, 0x97, 0x26, 0x51, 0x64, 0x88, 0x2a, 0xcc, 0x3a, 0x85, 0x70, 0xba, 0xdc, + 0x7f, 0x76, 0x71, 0x9f, 0x7c, 0x82, 0xf7, 0xa6, 0x88, 0xd2, 0x80, 0xc6, 0x01, 0x4d, 0xc1, 0x92, 0x34, 0x56, 0x4b, + 0x49, 0xd9, 0xf3, 0x24, 0x8e, 0xe9, 0x88, 0xd3, 0x00, 0x0c, 0x15, 0x46, 0xb8, 0x1b, 0x26, 0x19, 0x2f, 0x0a, 0x4b, + 0xe8, 0x99, 0x01, 0x3d, 0x73, 0x47, 0x7e, 0x14, 0x39, 0xd2, 0x28, 0x99, 0x26, 0x0b, 0xba, 0x05, 0xea, 0x4e, 0x05, + 0xe4, 0xa2, 0x1b, 0x6a, 0x74, 0x43, 0xdd, 0x6c, 0x16, 0xb1, 0x11, 0x2d, 0x44, 0xd7, 0x85, 0xcb, 0xe2, 0x80, 0xde, + 0x02, 0x1f, 0x41, 0xbd, 0x5e, 0xaf, 0x89, 0x5b, 0x28, 0x97, 0x08, 0x5f, 0x6e, 0x20, 0xf6, 0x1e, 0xa1, 0x09, 0x44, + 0x46, 0x7a, 0xcb, 0x6d, 0xfc, 0x80, 0x22, 0x43, 0x52, 0x32, 0x6d, 0x5c, 0x49, 0xee, 0x8c, 0x70, 0x40, 0x23, 0xca, + 0xa9, 0xe6, 0xe6, 0xa0, 0x42, 0xcb, 0xad, 0xfb, 0xb6, 0xc0, 0x5f, 0x41, 0x4e, 0x7a, 0x97, 0xe9, 0x35, 0xcf, 0x0a, + 0x63, 0xbd, 0x5c, 0x9e, 0x12, 0xdb, 0x7d, 0x2e, 0x97, 0xc7, 0xe7, 0xdc, 0x1f, 0x85, 0xd2, 0x4a, 0x77, 0x36, 0xa6, + 0x54, 0xf6, 0xa1, 0x38, 0x7b, 0xb1, 0x89, 0xde, 0x6a, 0x30, 0xb7, 0xa1, 0xe0, 0x42, 0x31, 0x05, 0x0a, 0x86, 0x9f, + 0x5c, 0xb6, 0x73, 0x3f, 0x8a, 0xae, 0xfd, 0xd1, 0xc7, 0x2a, 0xf5, 0x97, 0x64, 0x40, 0xd6, 0xb9, 0xb1, 0xf1, 0xca, + 0x60, 0x59, 0xe6, 0xbc, 0x35, 0x97, 0xae, 0x6c, 0x14, 0x67, 0xaf, 0x59, 0x92, 0x7d, 0x75, 0xa1, 0x77, 0x52, 0xbb, + 0x80, 0x88, 0xa9, 0x99, 0x39, 0xc0, 0x05, 0x3e, 0x49, 0x71, 0x9a, 0x1f, 0x28, 0xba, 0x03, 0x73, 0x23, 0x5f, 0x03, + 0x84, 0xa3, 0x65, 0x1e, 0xb0, 0x6c, 0x37, 0x06, 0xfe, 0x14, 0x28, 0x9f, 0x1a, 0x23, 0x3c, 0x14, 0xd0, 0x82, 0xc7, + 0x29, 0xad, 0xb9, 0x80, 0x4c, 0xe9, 0x13, 0x9a, 0xd1, 0xfc, 0x0d, 0x74, 0x17, 0x41, 0xef, 0xaf, 0xe5, 0x2b, 0xd0, + 0xca, 0x00, 0x8a, 0xac, 0x63, 0xaa, 0x13, 0x15, 0x0a, 0x50, 0x3c, 0x95, 0x09, 0x91, 0x9b, 0x56, 0xec, 0x47, 0xa5, + 0xb1, 0x4b, 0x13, 0x5c, 0xb1, 0xdc, 0x84, 0x38, 0x8e, 0x93, 0x81, 0x09, 0xa7, 0x55, 0xfb, 0x72, 0x12, 0xd9, 0xc6, + 0x24, 0x32, 0xd7, 0xb0, 0xb3, 0x50, 0x49, 0xcb, 0x46, 0x73, 0xef, 0xef, 0xc8, 0xac, 0x04, 0xea, 0xaa, 0x0b, 0xfc, + 0x19, 0x15, 0xec, 0x36, 0x22, 0x1c, 0x27, 0xca, 0xc6, 0x51, 0x94, 0x06, 0x0c, 0xa3, 0x6c, 0x92, 0x22, 0xb9, 0x35, + 0x2a, 0xf6, 0x6e, 0x8a, 0x13, 0xb4, 0xa6, 0xdb, 0xe7, 0xb9, 0xc2, 0x11, 0x45, 0x6a, 0x6d, 0x2a, 0x4a, 0xb1, 0x81, + 0x15, 0x9c, 0x12, 0xa5, 0x08, 0x4b, 0xbd, 0x67, 0x1d, 0x37, 0x45, 0xbf, 0x7b, 0x84, 0xa4, 0x25, 0x6a, 0x2a, 0x1a, + 0xa5, 0x56, 0xad, 0x52, 0x84, 0x43, 0xad, 0x93, 0x26, 0xe5, 0xbc, 0x09, 0xb1, 0xb5, 0x43, 0xc2, 0xee, 0x2f, 0x2b, + 0x56, 0xa1, 0x67, 0x54, 0xcb, 0x3d, 0x60, 0xa9, 0xc9, 0x36, 0x74, 0x6f, 0xa3, 0x99, 0x4a, 0x3f, 0x06, 0xc2, 0x13, + 0x13, 0xe1, 0x06, 0x66, 0x53, 0xc9, 0xb9, 0xd2, 0x21, 0x09, 0xab, 0x6d, 0x1d, 0x8a, 0x13, 0xb9, 0x0e, 0x1b, 0x48, + 0x5c, 0x57, 0x3d, 0x05, 0x09, 0x82, 0x0d, 0x9b, 0x81, 0x72, 0x67, 0xca, 0x07, 0x07, 0x60, 0x67, 0xab, 0xd5, 0x06, + 0xd1, 0x6d, 0xd5, 0x40, 0x91, 0x5b, 0xda, 0x85, 0xab, 0xd5, 0x19, 0x47, 0x8e, 0xd2, 0x7d, 0x31, 0x45, 0x7d, 0xcd, + 0x71, 0xcf, 0x5e, 0x40, 0x2d, 0xa1, 0x8a, 0x96, 0x25, 0x85, 0xd1, 0x50, 0xa5, 0xd9, 0xea, 0x3a, 0x71, 0x83, 0x6d, + 0x9f, 0x6f, 0x70, 0x2f, 0x51, 0xa8, 0xc4, 0x74, 0x39, 0xe5, 0x73, 0xd5, 0x35, 0x43, 0x08, 0x79, 0x99, 0xb0, 0x63, + 0xf6, 0xb6, 0x99, 0x96, 0x07, 0x07, 0x99, 0xd1, 0xd1, 0x65, 0xc1, 0x26, 0x3e, 0x38, 0x20, 0x92, 0xb3, 0xbb, 0x58, + 0xe8, 0x2e, 0x1f, 0xb4, 0x10, 0xda, 0x30, 0x4c, 0x9b, 0x1d, 0x30, 0xc8, 0xfd, 0x1b, 0x9f, 0x71, 0xab, 0xe8, 0x45, + 0x1a, 0xe4, 0x0e, 0x45, 0x4b, 0xa5, 0x6a, 0xb8, 0x29, 0x05, 0xe5, 0x11, 0x78, 0x82, 0x56, 0xa1, 0x25, 0xdd, 0x8f, + 0x42, 0x0a, 0xbe, 0x60, 0xad, 0x45, 0x14, 0x96, 0xe1, 0x9e, 0x92, 0x22, 0xaa, 0xe3, 0xed, 0xb0, 0xe7, 0xeb, 0xcd, + 0x2b, 0x96, 0xc0, 0x8c, 0xa6, 0xe3, 0x24, 0x9d, 0xea, 0x77, 0xf9, 0xda, 0xb3, 0xe2, 0x8c, 0x6c, 0xec, 0x6c, 0xed, + 0x5b, 0xe9, 0xff, 0x9d, 0x35, 0xb3, 0xbb, 0x34, 0xd8, 0x2b, 0xa2, 0xb4, 0x90, 0xbe, 0xd2, 0x25, 0xa8, 0x29, 0x33, + 0x33, 0x0d, 0x7c, 0xe5, 0x4f, 0xed, 0x48, 0x9f, 0xc9, 0x5e, 0xab, 0x53, 0x58, 0x7d, 0x9a, 0x1a, 0x3a, 0xd2, 0xb7, + 0xa1, 0x44, 0x6a, 0x32, 0x8f, 0x02, 0x05, 0x2c, 0x43, 0x98, 0x2a, 0x3a, 0xba, 0x61, 0x51, 0x54, 0x96, 0xfe, 0x16, + 0xbe, 0x9e, 0x29, 0xbe, 0x9e, 0x6a, 0xbe, 0x0e, 0x9c, 0x02, 0xf8, 0xba, 0xec, 0xae, 0x6c, 0x9e, 0x6e, 0xec, 0xce, + 0x54, 0x72, 0xf4, 0x4c, 0x58, 0xd2, 0x30, 0xde, 0x5c, 0x43, 0x80, 0x0a, 0xcd, 0xeb, 0xa3, 0xa3, 0xfc, 0x30, 0x60, + 0x02, 0x4a, 0x2f, 0x26, 0x35, 0x9d, 0x14, 0x1f, 0x1d, 0x84, 0xb3, 0x9c, 0x16, 0x94, 0x7d, 0xf6, 0x0c, 0xfc, 0x74, + 0xc6, 0x74, 0x40, 0x88, 0x89, 0xe2, 0xdf, 0xa4, 0x44, 0xe9, 0xd9, 0x31, 0x35, 0xbb, 0x4c, 0xcf, 0x0e, 0x38, 0x7d, + 0x39, 0xbb, 0xe0, 0x7e, 0x5e, 0x2f, 0xa6, 0xc7, 0x8a, 0xe9, 0x95, 0xeb, 0xbd, 0x5a, 0x39, 0x6b, 0x25, 0xe0, 0xc2, + 0x57, 0x26, 0x4a, 0x5a, 0xf4, 0x0e, 0x3c, 0xc0, 0xc4, 0x0c, 0x14, 0xe4, 0x72, 0xd2, 0x85, 0x88, 0x7b, 0xf1, 0x29, + 0x17, 0x8f, 0xf0, 0xd4, 0xcb, 0xf6, 0xe7, 0xc9, 0x74, 0x06, 0xda, 0xd8, 0x1a, 0x49, 0x4f, 0xa8, 0x1a, 0xb0, 0x7c, + 0x9f, 0x6f, 0x29, 0xab, 0xb4, 0x11, 0xfb, 0xb1, 0x42, 0x4d, 0x85, 0xc5, 0xbc, 0xd7, 0xcc, 0xe7, 0x45, 0x51, 0xc1, + 0x38, 0xb6, 0xb9, 0x55, 0xce, 0xd7, 0x9d, 0x32, 0xfa, 0xc5, 0x6b, 0x87, 0x49, 0x3e, 0xcc, 0x80, 0xd7, 0x19, 0xec, + 0x47, 0x93, 0xbb, 0xb9, 0xfe, 0x79, 0x89, 0x9c, 0x65, 0xbe, 0x86, 0xbe, 0x65, 0x9e, 0x3f, 0x53, 0x56, 0x36, 0x7e, + 0xb6, 0xdb, 0x1c, 0x2e, 0xdf, 0x29, 0x6b, 0x71, 0x30, 0xc4, 0xcf, 0x36, 0x75, 0x47, 0xb2, 0x9c, 0x26, 0x01, 0xf5, + 0xec, 0x64, 0x46, 0x63, 0x3b, 0x07, 0xcf, 0xaa, 0x5a, 0xfc, 0x80, 0x3b, 0xcb, 0xb7, 0x55, 0x17, 0xab, 0xf7, 0x2c, + 0x07, 0x07, 0xd8, 0x0f, 0x9b, 0xce, 0xd7, 0xef, 0x69, 0x9a, 0x09, 0x4d, 0xb4, 0x50, 0x6a, 0x7f, 0x28, 0xe5, 0xd2, + 0x0f, 0xde, 0xce, 0xfa, 0xa5, 0x0d, 0x62, 0xb7, 0xdc, 0x13, 0xf7, 0xd0, 0x46, 0xc2, 0x35, 0xfc, 0xad, 0xda, 0xf1, + 0x1f, 0xb4, 0x6b, 0xf8, 0x82, 0x7c, 0xa8, 0x7a, 0x86, 0xe7, 0x9c, 0x5c, 0xf4, 0x2f, 0xb4, 0xc9, 0x9c, 0x44, 0x6c, + 0x74, 0xe7, 0xd8, 0x11, 0xe3, 0x75, 0x08, 0xbf, 0xd9, 0x78, 0x29, 0x5f, 0x80, 0x57, 0x51, 0xb8, 0xb4, 0x73, 0x6d, + 0xec, 0x61, 0xca, 0x89, 0xbd, 0x1f, 0x31, 0xbe, 0x6f, 0xe3, 0x29, 0xb9, 0x82, 0x1f, 0xfb, 0x4b, 0xe7, 0xa5, 0xcf, + 0x43, 0x37, 0xf5, 0xe3, 0x20, 0x99, 0x3a, 0xa8, 0x66, 0xdb, 0xc8, 0xcd, 0x84, 0xc1, 0xf1, 0x18, 0xe5, 0xfb, 0x57, + 0xf8, 0x19, 0x27, 0x76, 0xdf, 0xae, 0x4d, 0xf1, 0x13, 0x4e, 0xae, 0xba, 0xfb, 0xcb, 0x67, 0x3c, 0xef, 0x5d, 0xe1, + 0xdb, 0xc2, 0x6b, 0x8f, 0xdf, 0x13, 0x07, 0x91, 0xde, 0xad, 0x82, 0xe6, 0x3c, 0x99, 0x4a, 0xef, 0xbd, 0x8d, 0xf0, + 0x3b, 0x11, 0x5b, 0x29, 0xd9, 0x8d, 0x0a, 0xaf, 0xec, 0x11, 0x3b, 0x11, 0x3e, 0x02, 0xfb, 0xe0, 0xc0, 0x28, 0x2b, + 0x74, 0x05, 0x7c, 0xc1, 0x49, 0xc5, 0x22, 0xc7, 0x2f, 0x45, 0x94, 0xe6, 0x82, 0x3b, 0x31, 0xd2, 0xdd, 0x38, 0xda, + 0x17, 0xad, 0xf6, 0x66, 0x3c, 0x90, 0x2e, 0x06, 0x97, 0x71, 0x9a, 0xfa, 0x3c, 0x49, 0x87, 0xc8, 0xd4, 0x3f, 0xf0, + 0xdf, 0xc8, 0xd5, 0xc0, 0xfa, 0x4f, 0x9f, 0xfd, 0x3c, 0xfe, 0x39, 0x1d, 0x5e, 0xe1, 0x37, 0xa4, 0xd1, 0x75, 0xfa, + 0x9e, 0xb3, 0x57, 0xaf, 0xaf, 0x7e, 0x6e, 0x0c, 0xfe, 0xe1, 0xd7, 0x7f, 0x3d, 0xab, 0xff, 0x34, 0x44, 0x2b, 0xe7, + 0xe7, 0x46, 0x7f, 0xa0, 0x9e, 0x06, 0xff, 0xe8, 0xfd, 0x9c, 0x0d, 0xff, 0x2a, 0x0b, 0xf7, 0x11, 0x6a, 0x4c, 0xf0, + 0x8c, 0x93, 0x46, 0xbd, 0xde, 0x6b, 0x4c, 0xf0, 0x84, 0x93, 0x06, 0xfc, 0x7f, 0x4d, 0xde, 0xd2, 0xc9, 0xb3, 0xdb, + 0x99, 0x73, 0xd5, 0x5b, 0xed, 0x2f, 0xff, 0x96, 0x43, 0xaf, 0x83, 0x7f, 0xfc, 0xfc, 0x73, 0x66, 0x7f, 0xd1, 0x23, + 0x8d, 0x61, 0x0d, 0x39, 0x50, 0xfa, 0x57, 0x22, 0xfe, 0x75, 0xfa, 0xde, 0xe0, 0x1f, 0x0a, 0x0a, 0xfb, 0x8b, 0x9f, + 0xaf, 0xba, 0x3d, 0x32, 0x5c, 0x39, 0xf6, 0xea, 0x0b, 0xb4, 0x42, 0x68, 0xb5, 0x8f, 0xae, 0xb0, 0x3d, 0xb1, 0x11, + 0x5e, 0x70, 0xd2, 0xf8, 0xa2, 0x31, 0xc1, 0x63, 0x4e, 0x1a, 0x76, 0x63, 0x82, 0xcf, 0x39, 0x69, 0xfc, 0xc3, 0xe9, + 0x7b, 0xd2, 0xc9, 0xb6, 0x12, 0xfe, 0x8d, 0x15, 0x04, 0x38, 0xfc, 0x94, 0xfa, 0x2b, 0xce, 0x78, 0x44, 0xd1, 0x7e, + 0x83, 0xe1, 0x8f, 0x02, 0x4d, 0x0e, 0x07, 0x2f, 0x0c, 0x18, 0x77, 0xce, 0xf2, 0x12, 0x16, 0x1b, 0x68, 0x66, 0xdf, + 0x83, 0xc8, 0x0e, 0x38, 0x02, 0x32, 0x8f, 0xe3, 0x85, 0x1f, 0xcd, 0x69, 0xe6, 0xd1, 0x1c, 0xe1, 0x11, 0xf9, 0xc8, + 0x9d, 0x16, 0xc2, 0x2f, 0x38, 0xfc, 0x68, 0x23, 0x7c, 0xae, 0x82, 0x98, 0xb0, 0x93, 0x25, 0x51, 0xc5, 0x89, 0x54, + 0x59, 0x6c, 0x84, 0x67, 0x5b, 0x5e, 0xf2, 0x10, 0xdc, 0x0b, 0x08, 0xef, 0x57, 0x42, 0x9e, 0xf8, 0x86, 0x68, 0x92, + 0x78, 0x97, 0x52, 0xfa, 0x83, 0x1f, 0x7d, 0xa4, 0xa9, 0x73, 0x8b, 0x5b, 0xed, 0xc7, 0x58, 0x78, 0xa1, 0xf7, 0x5a, + 0xa8, 0x53, 0xc4, 0xab, 0x5e, 0x73, 0x19, 0x27, 0x00, 0x29, 0x5b, 0x75, 0xc6, 0xc0, 0x8a, 0xef, 0xc5, 0x1b, 0x1e, + 0xab, 0xd4, 0xbf, 0xb1, 0x51, 0x35, 0x36, 0xca, 0xe2, 0x85, 0x1f, 0xb1, 0xc0, 0xe2, 0x74, 0x3a, 0x8b, 0x7c, 0x4e, + 0x2d, 0x35, 0x5f, 0xcb, 0x87, 0x8e, 0xec, 0x42, 0x67, 0x98, 0x1b, 0x16, 0xe7, 0x5c, 0x07, 0x9d, 0x60, 0xaf, 0x38, + 0x10, 0xa1, 0x52, 0x7a, 0xc7, 0xd3, 0x32, 0x00, 0xb6, 0x1e, 0xe3, 0xab, 0xb7, 0xc0, 0x13, 0x36, 0x14, 0xf2, 0x39, + 0xc3, 0x29, 0x01, 0x29, 0xda, 0xee, 0xdb, 0xdd, 0x6c, 0x31, 0xe9, 0xd9, 0x10, 0x9f, 0x49, 0xc8, 0x1b, 0xe1, 0x18, + 0x82, 0x0a, 0x21, 0x69, 0x76, 0xc2, 0x2e, 0xed, 0x84, 0xb5, 0x9a, 0x56, 0xa2, 0x23, 0x12, 0x0f, 0x42, 0xd9, 0xdc, + 0xc7, 0x01, 0x9e, 0x93, 0x7a, 0x0b, 0x4f, 0x48, 0x53, 0x34, 0xe9, 0x4c, 0xba, 0x91, 0x1a, 0xe6, 0xe0, 0xc0, 0x49, + 0xdc, 0xc8, 0xcf, 0xf8, 0x37, 0x60, 0xed, 0x93, 0x09, 0x0e, 0x48, 0xe2, 0xd2, 0x5b, 0x3a, 0x72, 0x22, 0x84, 0x03, + 0xc5, 0x69, 0x50, 0x07, 0x4d, 0x88, 0x51, 0x0d, 0xac, 0x08, 0xf2, 0xa6, 0x1f, 0x0c, 0x5a, 0x43, 0x42, 0x88, 0xbd, + 0x57, 0xaf, 0xdb, 0xfd, 0x84, 0xcc, 0xb8, 0x07, 0x25, 0x86, 0xae, 0x4c, 0x26, 0x50, 0xd4, 0x36, 0x8a, 0x9c, 0x73, + 0xee, 0x72, 0x9a, 0x71, 0x07, 0x8a, 0xc1, 0xfe, 0xcf, 0x34, 0x61, 0xdb, 0xdd, 0x86, 0x5d, 0x83, 0x52, 0x41, 0x9c, + 0x08, 0x27, 0xe4, 0x1a, 0x79, 0xc1, 0xe0, 0x70, 0x68, 0x0a, 0x00, 0x51, 0x08, 0x83, 0x5f, 0xf7, 0x83, 0x41, 0x53, + 0x0c, 0xde, 0xb3, 0xfb, 0x4e, 0x42, 0x32, 0xa9, 0xa1, 0xf5, 0x33, 0xef, 0x8d, 0x98, 0x2a, 0xf2, 0x14, 0x70, 0x7a, + 0x05, 0x48, 0xbd, 0xed, 0x39, 0x73, 0x73, 0x12, 0x75, 0x18, 0x4c, 0x61, 0x01, 0xfb, 0x04, 0xea, 0xe3, 0x84, 0xc0, + 0x88, 0x65, 0xb3, 0x6b, 0x4f, 0x3d, 0x7f, 0x61, 0x7f, 0xd1, 0x1f, 0x73, 0x6f, 0xc1, 0xe5, 0xf0, 0x63, 0xbe, 0x5a, + 0xc1, 0xff, 0x0b, 0xde, 0x4f, 0xc8, 0xb5, 0x28, 0x9a, 0xa9, 0xa2, 0x09, 0x14, 0xbd, 0xf1, 0x00, 0x54, 0x9c, 0x15, + 0x5a, 0x96, 0x5c, 0x93, 0x05, 0x11, 0xb0, 0x1f, 0x1c, 0xc4, 0x83, 0xb0, 0xd6, 0x1a, 0x82, 0x8b, 0x3f, 0xe5, 0xd9, + 0x0f, 0x8c, 0x87, 0x8e, 0xdd, 0xe8, 0xd9, 0xa8, 0x6f, 0x5b, 0xb0, 0xb4, 0x9d, 0xb4, 0x46, 0x24, 0x86, 0xa3, 0xda, + 0x13, 0xee, 0xcd, 0x7b, 0xa4, 0xd9, 0x77, 0x98, 0x64, 0xe1, 0x3e, 0xc2, 0x91, 0x62, 0x9c, 0x4d, 0x3c, 0x47, 0x35, + 0xca, 0x6b, 0xfa, 0x79, 0x8e, 0x6a, 0xd3, 0xda, 0x02, 0x79, 0x51, 0x6d, 0x5a, 0x73, 0xe6, 0x84, 0x90, 0x7a, 0xbb, + 0x68, 0xa6, 0xc5, 0x5f, 0x88, 0xbc, 0x85, 0xf6, 0x76, 0x0e, 0xc4, 0x76, 0x48, 0x6b, 0x4e, 0x3c, 0xa0, 0xc3, 0xd5, + 0xca, 0xee, 0xf6, 0x7b, 0x36, 0xaa, 0x39, 0x9a, 0xd0, 0x1a, 0x9a, 0xd2, 0x10, 0xc2, 0x6c, 0x98, 0xab, 0x68, 0xd2, + 0xab, 0x4a, 0xe4, 0x68, 0x59, 0x6e, 0x76, 0x83, 0x07, 0xd0, 0xbc, 0x30, 0x64, 0xa4, 0xc2, 0x3a, 0x83, 0x69, 0x6a, + 0x62, 0x4e, 0x49, 0x13, 0x27, 0x44, 0x3b, 0xaf, 0x43, 0xc2, 0x4b, 0x82, 0x8f, 0x48, 0x59, 0x1d, 0x0f, 0x7c, 0x1c, + 0x0c, 0xc9, 0x53, 0x69, 0x90, 0x74, 0xb4, 0x6b, 0x9c, 0x46, 0xe4, 0xd5, 0x5a, 0x04, 0xd7, 0x87, 0xf0, 0xca, 0x8d, + 0x3b, 0x9a, 0xa7, 0x29, 0x8d, 0xf9, 0xab, 0x24, 0x50, 0x7a, 0x1a, 0x8d, 0xc0, 0x54, 0x82, 0xd0, 0x2c, 0x06, 0x25, + 0xad, 0xad, 0x77, 0xc6, 0x7c, 0xe3, 0xf5, 0x84, 0xcc, 0xa5, 0xfe, 0x24, 0x02, 0xb6, 0x9d, 0x89, 0x32, 0x8c, 0x1d, + 0x84, 0xe7, 0x2a, 0x92, 0xeb, 0xb8, 0xae, 0x3b, 0x71, 0x47, 0xf0, 0x1a, 0x06, 0xc8, 0x50, 0x2e, 0xf6, 0x91, 0x93, + 0x91, 0x1b, 0x37, 0xa6, 0xb7, 0x62, 0x54, 0x07, 0x95, 0x92, 0x59, 0x6f, 0xaf, 0x6e, 0xd8, 0x11, 0xec, 0x26, 0x73, + 0xe3, 0x24, 0xa0, 0x80, 0x1e, 0x88, 0xdd, 0xab, 0xa2, 0xd0, 0xcf, 0xcc, 0x10, 0x55, 0x09, 0xdf, 0xc0, 0xf4, 0x5e, + 0x4f, 0xc0, 0xe5, 0x2b, 0x94, 0xad, 0xa2, 0xb2, 0xf4, 0x83, 0x23, 0xc4, 0xc6, 0xce, 0xc4, 0x85, 0xd0, 0x9e, 0x20, + 0x21, 0x0a, 0xb6, 0xdc, 0xc4, 0x24, 0xaa, 0x69, 0xd1, 0xe7, 0x82, 0x04, 0x83, 0xa4, 0x56, 0x13, 0x6e, 0xe8, 0xb9, + 0x24, 0x89, 0x09, 0xc2, 0x8b, 0x62, 0x6f, 0xe9, 0x7a, 0x5f, 0x91, 0xea, 0x48, 0xce, 0xa2, 0xea, 0xce, 0xad, 0x41, + 0x9a, 0x04, 0x78, 0x0a, 0xb9, 0x33, 0x45, 0xf8, 0x8c, 0x34, 0x9c, 0x81, 0xdb, 0xff, 0x72, 0x88, 0xfa, 0x8e, 0xfb, + 0x57, 0xd4, 0x90, 0x8c, 0x63, 0x81, 0x3a, 0x91, 0x1c, 0x62, 0x29, 0x42, 0x98, 0x2d, 0x2c, 0x3c, 0x89, 0x5e, 0x8a, + 0x63, 0x7f, 0x4a, 0xbd, 0x33, 0xd8, 0xe3, 0x9a, 0x6e, 0xbe, 0xc2, 0x40, 0x47, 0xde, 0x99, 0xe2, 0x24, 0xae, 0xdd, + 0xff, 0x86, 0x17, 0x4f, 0x7d, 0xbb, 0xff, 0x6b, 0xf9, 0xf4, 0xa5, 0xdd, 0xff, 0x9e, 0x7b, 0xbf, 0xe6, 0xca, 0xd9, + 0x5d, 0x19, 0xe2, 0x44, 0x0f, 0x91, 0xcb, 0x85, 0x31, 0x30, 0x37, 0x47, 0x9b, 0x7e, 0x8e, 0x09, 0xca, 0xd9, 0xb8, + 0x60, 0x45, 0x99, 0xcb, 0xfd, 0x09, 0xa0, 0xd4, 0x58, 0x81, 0xcc, 0x8c, 0xec, 0x97, 0x13, 0x06, 0x42, 0xd1, 0xd4, + 0x0a, 0xa8, 0x9c, 0xf4, 0x9a, 0x68, 0x59, 0xa9, 0x2b, 0x34, 0xa6, 0x6a, 0x24, 0xbd, 0xe0, 0xd2, 0x0b, 0xd2, 0xec, + 0x2c, 0xba, 0x93, 0xce, 0xa2, 0x56, 0x43, 0x99, 0x26, 0xac, 0xf9, 0x60, 0x31, 0xc4, 0xef, 0xc1, 0xa7, 0x67, 0x52, + 0x12, 0xae, 0x4c, 0xaf, 0xad, 0xa6, 0x57, 0xab, 0xa5, 0x39, 0xea, 0x18, 0x4d, 0x27, 0xb2, 0x69, 0x9e, 0x4b, 0x9c, + 0xac, 0x13, 0xda, 0x29, 0x12, 0x25, 0x90, 0x0e, 0x45, 0x08, 0x79, 0xc6, 0xd1, 0xd6, 0x5e, 0xa1, 0x4f, 0x68, 0x2e, + 0x76, 0x2c, 0x30, 0x4f, 0x29, 0x23, 0x1c, 0xc0, 0x02, 0x34, 0x2d, 0x1c, 0xc1, 0x53, 0x3c, 0xaf, 0xb5, 0x04, 0x91, + 0xd7, 0x5b, 0x9d, 0x6a, 0x5f, 0x8f, 0xca, 0xbe, 0xf0, 0xbc, 0x46, 0xa6, 0x05, 0x96, 0xf2, 0xb4, 0x56, 0xcb, 0xab, + 0xd1, 0x4e, 0xbd, 0x6f, 0x2b, 0xf1, 0x87, 0xdb, 0xf5, 0xb4, 0x0c, 0x2d, 0x5f, 0x4b, 0x89, 0xca, 0x5c, 0x16, 0xc7, + 0x34, 0x05, 0x19, 0x4a, 0x38, 0x66, 0x79, 0x5e, 0xc8, 0xf5, 0x8f, 0x20, 0x44, 0x31, 0x25, 0x31, 0xf0, 0x1d, 0x61, + 0x76, 0xe1, 0x14, 0x27, 0x38, 0x14, 0x5c, 0x83, 0x10, 0x72, 0xae, 0x13, 0x5a, 0xb8, 0xe0, 0x40, 0x11, 0x61, 0x86, + 0x44, 0xca, 0x08, 0x75, 0x2f, 0xf7, 0xcf, 0x93, 0x7b, 0x4d, 0xb2, 0x01, 0x1b, 0x7a, 0xa2, 0x5a, 0xa4, 0xf8, 0x96, + 0x4f, 0xde, 0x39, 0x1c, 0x15, 0xc1, 0x11, 0x57, 0xb0, 0xbf, 0xa7, 0x2c, 0xa5, 0x42, 0x03, 0xdf, 0xd7, 0x66, 0x5f, + 0x54, 0x55, 0x1f, 0x23, 0xd3, 0x79, 0x03, 0x88, 0xf4, 0xc1, 0xb7, 0x93, 0x92, 0x8d, 0x6a, 0x97, 0xfb, 0x67, 0xaf, + 0xb7, 0x99, 0xc0, 0xab, 0x95, 0x32, 0x7e, 0x85, 0x66, 0x83, 0xfd, 0x12, 0xd2, 0x48, 0xfd, 0xf0, 0x9c, 0x48, 0x28, + 0x48, 0xbe, 0x13, 0x03, 0x15, 0x5d, 0xee, 0x9f, 0xbd, 0x73, 0x62, 0xe1, 0x5a, 0x42, 0xd8, 0x9c, 0xb6, 0x93, 0x10, + 0x27, 0x24, 0x14, 0xc9, 0xb9, 0x17, 0x8c, 0x2b, 0x31, 0xc4, 0xb7, 0x17, 0x8a, 0x97, 0x60, 0x3f, 0x0c, 0xd8, 0x90, + 0x44, 0x0a, 0x03, 0x24, 0x42, 0x38, 0xaa, 0x98, 0x65, 0x04, 0x16, 0x40, 0x8c, 0x75, 0x01, 0x2b, 0xe1, 0x4a, 0xc5, + 0x0f, 0xe1, 0x48, 0x8c, 0xca, 0x73, 0x29, 0x3a, 0x3e, 0x6c, 0xe4, 0xa5, 0x95, 0xd6, 0xe8, 0xf7, 0x60, 0x39, 0xe9, + 0x87, 0x57, 0xaa, 0xeb, 0xa2, 0xe0, 0xa9, 0x4e, 0x20, 0xbb, 0xdc, 0x3f, 0x7b, 0xa9, 0x72, 0xc8, 0x66, 0xbe, 0xe6, + 0xf6, 0x1b, 0x16, 0xe6, 0xd9, 0x4b, 0xb7, 0x7c, 0x2b, 0x2a, 0x5f, 0xee, 0x9f, 0xbd, 0xdf, 0x56, 0x0d, 0xca, 0xf3, + 0x79, 0x69, 0xe2, 0x0b, 0xf8, 0x96, 0x34, 0xf2, 0x96, 0x4a, 0x34, 0x78, 0x2c, 0xc7, 0x42, 0x1c, 0x79, 0x59, 0x5e, + 0x78, 0x46, 0x9e, 0xe2, 0x94, 0x88, 0x28, 0x50, 0x75, 0xd5, 0x94, 0x92, 0xc7, 0x92, 0xf8, 0x62, 0x94, 0xcc, 0xe8, + 0x8e, 0xd0, 0xd0, 0x2d, 0x72, 0xd9, 0x14, 0x92, 0x67, 0x04, 0xe8, 0x0c, 0xef, 0x35, 0x51, 0xa7, 0x2a, 0xbc, 0x52, + 0x41, 0xa4, 0x49, 0x45, 0xb2, 0xe0, 0x90, 0x34, 0x71, 0x44, 0x9a, 0xd8, 0x27, 0xd9, 0xa0, 0x29, 0xc5, 0x43, 0xc7, + 0x2f, 0xfa, 0x95, 0x42, 0x06, 0xf2, 0xc2, 0xd4, 0x6e, 0x95, 0xe2, 0x37, 0xe8, 0xf8, 0xc2, 0xf5, 0x28, 0x24, 0x7a, + 0x20, 0xc8, 0xe2, 0xb9, 0x93, 0xe0, 0x44, 0x74, 0x7c, 0xc1, 0xae, 0x23, 0x48, 0x2d, 0x81, 0x59, 0x61, 0x8e, 0xbc, + 0xa2, 0x6a, 0x4b, 0x55, 0xf5, 0x5d, 0xb1, 0x4e, 0x09, 0xf6, 0x5d, 0x60, 0xdc, 0xd8, 0x57, 0x99, 0x38, 0xd9, 0x66, + 0x93, 0x93, 0x83, 0x03, 0x47, 0x36, 0xfa, 0x8a, 0x3b, 0x89, 0x7e, 0x5f, 0x06, 0xee, 0xbe, 0x97, 0xbc, 0x22, 0x40, + 0x02, 0xfe, 0x5a, 0x2d, 0x1a, 0xe6, 0x10, 0x85, 0x76, 0xfc, 0x2a, 0x06, 0x35, 0xf0, 0x42, 0xd3, 0xab, 0x4e, 0xbf, + 0x56, 0x2b, 0x82, 0xb4, 0x55, 0x6c, 0xdd, 0xe2, 0x34, 0x5f, 0x38, 0x45, 0xf2, 0x4f, 0x73, 0x23, 0x63, 0x4a, 0x83, + 0x80, 0x98, 0x49, 0xb3, 0x4c, 0x4f, 0xc6, 0xd8, 0x12, 0x0c, 0xea, 0x7d, 0xa3, 0xd2, 0x16, 0xb0, 0xc8, 0xaf, 0x52, + 0x95, 0x34, 0x3b, 0x6b, 0x23, 0x4f, 0x57, 0x82, 0xa0, 0x14, 0x54, 0xaa, 0xe5, 0x8a, 0xbc, 0x9f, 0x6f, 0x66, 0x5d, + 0xe2, 0x0c, 0x29, 0x1f, 0x97, 0x80, 0x42, 0x20, 0xab, 0x5d, 0x20, 0xe5, 0x39, 0x99, 0xed, 0x26, 0xf9, 0x33, 0x83, + 0xe4, 0x9f, 0x10, 0x6a, 0x90, 0xbf, 0xf4, 0x70, 0xb8, 0x89, 0x72, 0x2d, 0x64, 0xfa, 0xd5, 0xf9, 0x8c, 0x80, 0x0f, + 0xad, 0x8a, 0xd1, 0x4a, 0x54, 0x71, 0x07, 0x43, 0x31, 0x77, 0x88, 0xf0, 0x42, 0x62, 0x1d, 0x02, 0x76, 0xca, 0x98, + 0x1a, 0x0c, 0xbd, 0xcd, 0xa5, 0x67, 0x72, 0xc0, 0xb3, 0xf7, 0xf7, 0x87, 0x43, 0xcf, 0x67, 0x9b, 0x3b, 0xd7, 0xc8, + 0xfe, 0x84, 0x59, 0x1b, 0x1b, 0xb7, 0x9a, 0x0b, 0x0a, 0xe3, 0x17, 0x61, 0xec, 0x2a, 0xf3, 0x59, 0xdb, 0x84, 0x5a, + 0xfe, 0x01, 0xb4, 0xad, 0x96, 0xa8, 0x41, 0x8d, 0x6e, 0x81, 0x1f, 0xc9, 0x1c, 0x54, 0x3f, 0xdd, 0xc1, 0x3e, 0xce, + 0x44, 0x05, 0x1a, 0x07, 0xdb, 0x5f, 0x3f, 0xc9, 0x15, 0x99, 0x48, 0xd0, 0xd0, 0x12, 0xf8, 0x9f, 0x24, 0x79, 0xa0, + 0x1b, 0x21, 0x17, 0x00, 0x41, 0x33, 0x81, 0xa7, 0x12, 0x61, 0xb6, 0x5d, 0x3a, 0xdf, 0x9f, 0xef, 0x11, 0x32, 0x2b, + 0x9d, 0x8f, 0x6f, 0xcb, 0xdc, 0x2b, 0x20, 0x0b, 0xe4, 0x81, 0xf1, 0x58, 0x14, 0xc8, 0xe8, 0xe5, 0xb9, 0xae, 0x2e, + 0x0c, 0x48, 0xb7, 0xd4, 0xb7, 0x8d, 0xc8, 0xa6, 0xf0, 0xca, 0xc9, 0xf7, 0x1a, 0x0d, 0x6b, 0x6f, 0xf7, 0xe1, 0xed, + 0x4b, 0x2e, 0x60, 0x84, 0xe7, 0x77, 0xa2, 0xb6, 0xee, 0x37, 0xff, 0xb8, 0x9e, 0xc0, 0xb2, 0xb6, 0x28, 0x2e, 0x8b, + 0x33, 0x9a, 0xf2, 0x27, 0x74, 0x9c, 0xa4, 0x10, 0xb2, 0x28, 0x70, 0x82, 0xf2, 0x7d, 0xc3, 0x6d, 0x27, 0xe6, 0x67, + 0xc4, 0x09, 0xd6, 0x26, 0x28, 0x7e, 0x7d, 0x14, 0x31, 0xeb, 0xcb, 0xf5, 0x56, 0xb3, 0x83, 0x83, 0x77, 0x25, 0x9a, + 0x14, 0x94, 0x02, 0x0a, 0x83, 0x69, 0x49, 0x95, 0x46, 0x05, 0x72, 0xf7, 0x9d, 0xc2, 0x05, 0xa0, 0x19, 0x86, 0xc9, + 0x7b, 0x9e, 0x13, 0x9e, 0x4f, 0xd6, 0x59, 0xbc, 0x72, 0x4d, 0x30, 0xd3, 0x6c, 0x01, 0x0e, 0x0f, 0x86, 0xb6, 0xf4, + 0x15, 0x65, 0x65, 0x3a, 0x6c, 0x01, 0xc3, 0x39, 0x20, 0xcb, 0x11, 0x46, 0x88, 0x41, 0x81, 0x5b, 0x8d, 0x92, 0xd7, + 0xa0, 0x57, 0x86, 0x38, 0x73, 0x43, 0x48, 0x80, 0xad, 0x6c, 0x59, 0x84, 0xb0, 0xcc, 0xcb, 0x31, 0x32, 0x09, 0xce, + 0x9e, 0x6f, 0xf3, 0x28, 0x6b, 0xa2, 0xa6, 0x42, 0xea, 0x40, 0x8d, 0x14, 0x15, 0x0d, 0xdc, 0x85, 0xc3, 0x94, 0xe2, + 0xa6, 0xc3, 0x66, 0xc0, 0x80, 0x3f, 0x70, 0x47, 0xc6, 0xa2, 0x40, 0x66, 0x24, 0xee, 0xdc, 0xa9, 0x0c, 0xdd, 0x49, + 0x44, 0x33, 0xac, 0x10, 0x17, 0x9a, 0x68, 0x4a, 0x44, 0x58, 0xef, 0xbc, 0xe4, 0xa5, 0xfb, 0x32, 0x87, 0x9a, 0x6b, + 0x2e, 0x58, 0xe6, 0x91, 0x18, 0xd3, 0xdf, 0x97, 0x69, 0xd1, 0x45, 0x25, 0x50, 0xc3, 0xe8, 0x8d, 0xf5, 0x4a, 0xac, + 0x01, 0xcd, 0x81, 0xbe, 0x96, 0x17, 0xdc, 0x58, 0x51, 0xed, 0xc3, 0x16, 0x63, 0x1a, 0x52, 0xff, 0x2d, 0x64, 0xba, + 0xac, 0xef, 0xf9, 0xe7, 0x42, 0x16, 0x32, 0x9c, 0x55, 0x18, 0x7b, 0x2a, 0x18, 0x3b, 0x02, 0x3d, 0x4d, 0xa7, 0x7e, + 0xf7, 0x55, 0xc2, 0x0b, 0x53, 0x52, 0x4e, 0x91, 0xd8, 0xfb, 0x22, 0x58, 0x6e, 0xfc, 0x5e, 0x5b, 0x0d, 0x8f, 0x11, + 0x48, 0x02, 0xc2, 0x8a, 0xb3, 0xa7, 0x08, 0x67, 0xb5, 0x5a, 0x27, 0xeb, 0xd2, 0xd2, 0x45, 0x52, 0xc2, 0xc8, 0x20, + 0x9e, 0x0b, 0x04, 0x5f, 0x91, 0xa1, 0x10, 0xf1, 0xd7, 0xb9, 0xd9, 0x19, 0xb8, 0xda, 0xcf, 0xde, 0x3a, 0x26, 0x57, + 0x33, 0xeb, 0x16, 0x31, 0x53, 0x98, 0x8f, 0x53, 0xc6, 0x5b, 0xde, 0xdc, 0x9f, 0xdf, 0x01, 0x70, 0xef, 0xb5, 0x30, + 0xe4, 0xa2, 0xa1, 0x0e, 0x97, 0x2c, 0xa1, 0xd8, 0x7d, 0x1d, 0x54, 0xa6, 0x25, 0x9a, 0x83, 0x75, 0x78, 0x69, 0xca, + 0x72, 0x92, 0xe5, 0x79, 0x46, 0xcb, 0xe8, 0xfe, 0x5a, 0xfe, 0xa5, 0x10, 0x2e, 0x9b, 0xce, 0xf6, 0xf3, 0x19, 0xe1, + 0xd8, 0x20, 0xd4, 0x37, 0xbb, 0x42, 0x1f, 0x25, 0x98, 0xb0, 0xaf, 0x95, 0x50, 0xfc, 0x75, 0x9b, 0x50, 0xc4, 0xa9, + 0xda, 0xf2, 0x42, 0x20, 0xb6, 0x1e, 0x20, 0x10, 0x95, 0x93, 0x5d, 0xcb, 0x44, 0x50, 0x47, 0x2a, 0x32, 0xb1, 0xba, + 0xa4, 0x24, 0xc5, 0x4c, 0xad, 0x46, 0xaf, 0xbd, 0x5a, 0xb1, 0x41, 0x13, 0x9c, 0x48, 0xb6, 0x0d, 0x3f, 0x5b, 0xf2, + 0xa7, 0xc1, 0x89, 0xa5, 0x13, 0xd8, 0x61, 0x85, 0xc9, 0x82, 0x5c, 0x48, 0x71, 0x76, 0x44, 0x4e, 0x96, 0xa0, 0x69, + 0x45, 0x41, 0x8a, 0xc0, 0x09, 0x2b, 0xa2, 0x4c, 0x00, 0xb1, 0x90, 0x15, 0xca, 0x80, 0x74, 0xb6, 0x26, 0xff, 0x69, + 0xf3, 0xf2, 0xd3, 0x9a, 0x68, 0x45, 0xae, 0x48, 0xf5, 0xa1, 0x92, 0x6e, 0xa0, 0x20, 0x50, 0xfa, 0xe1, 0x9e, 0x30, + 0x41, 0x4b, 0x51, 0x8e, 0x4c, 0x39, 0x84, 0x9b, 0xe0, 0x42, 0xdb, 0x7b, 0x27, 0x03, 0xbc, 0x5b, 0xa4, 0x09, 0x4e, + 0x0c, 0xba, 0x7e, 0x4e, 0x78, 0x85, 0x95, 0x84, 0x44, 0x59, 0x4a, 0xd8, 0x17, 0x64, 0xca, 0x49, 0x3a, 0x68, 0x0e, + 0x41, 0x01, 0xed, 0x44, 0xdd, 0xb4, 0x34, 0x81, 0xa3, 0x5a, 0x0d, 0xf9, 0x7a, 0xd4, 0x70, 0xc0, 0x6a, 0xd1, 0x10, + 0x53, 0x1c, 0x49, 0xc3, 0xe4, 0xfc, 0xe0, 0xc0, 0xf1, 0xcb, 0x71, 0x07, 0xd1, 0x10, 0xe1, 0x64, 0xb5, 0x72, 0x04, + 0x58, 0x3e, 0x5a, 0xad, 0x7c, 0x13, 0x2c, 0xf1, 0x1a, 0x9a, 0xcd, 0xfa, 0x9c, 0xcc, 0x84, 0x00, 0x9c, 0x01, 0x84, + 0x35, 0xe2, 0xf8, 0xca, 0xb9, 0xe7, 0x83, 0x33, 0xaa, 0x96, 0x0e, 0xa2, 0x5a, 0x6b, 0x68, 0x30, 0xae, 0x41, 0x34, + 0x24, 0x7e, 0x9e, 0x1c, 0x1c, 0xec, 0x65, 0x4a, 0x44, 0x7e, 0x00, 0x51, 0xf6, 0x41, 0x48, 0x16, 0xd9, 0xa1, 0xb9, + 0x1a, 0xeb, 0xce, 0x80, 0x82, 0xa2, 0xd4, 0xb2, 0xea, 0x7a, 0x95, 0x24, 0x88, 0xa2, 0x12, 0x56, 0xb1, 0xe0, 0x3e, + 0x58, 0xf6, 0x05, 0x99, 0x7f, 0xc3, 0x8b, 0x24, 0xeb, 0x5f, 0xb7, 0xa6, 0x56, 0xbb, 0xae, 0xeb, 0xa7, 0x13, 0x11, + 0xc9, 0xd0, 0x51, 0x58, 0x41, 0xfc, 0x87, 0x0a, 0x4c, 0x63, 0xe0, 0x41, 0x31, 0xd6, 0x90, 0x48, 0xf0, 0xb5, 0x6a, + 0xa3, 0x4f, 0x93, 0xfc, 0xb2, 0xd5, 0xcb, 0xa0, 0x36, 0xdc, 0xef, 0x85, 0xe4, 0x48, 0x41, 0x22, 0xc9, 0x63, 0x0d, + 0x67, 0x3b, 0x70, 0xf1, 0x0b, 0x5f, 0xc3, 0xd9, 0x6e, 0xdc, 0x6a, 0x4c, 0x7d, 0xbf, 0x0b, 0x3e, 0x83, 0x37, 0x48, + 0x40, 0xcb, 0x02, 0x03, 0xca, 0xe3, 0x75, 0xdd, 0x4b, 0xb2, 0x52, 0x10, 0xa6, 0x9c, 0x38, 0xac, 0xba, 0x01, 0x4a, + 0x6d, 0xd4, 0x30, 0x7c, 0x99, 0x37, 0x43, 0x86, 0x4b, 0xa0, 0x9a, 0xb9, 0x02, 0xe4, 0xa4, 0x7c, 0xed, 0xb3, 0x83, + 0x03, 0xb0, 0x0d, 0x40, 0x89, 0x73, 0x47, 0xfe, 0x8c, 0xcf, 0x53, 0x50, 0xa5, 0x32, 0xfd, 0x1b, 0x8a, 0xe1, 0x1c, + 0x88, 0x28, 0x83, 0x1f, 0x50, 0x30, 0xf3, 0xb3, 0x8c, 0x2d, 0x64, 0x99, 0xfa, 0x8d, 0x13, 0xa2, 0x49, 0x39, 0x93, + 0x3a, 0x61, 0x8a, 0x3a, 0xa9, 0xa2, 0xd3, 0x2a, 0xda, 0x9e, 0x2d, 0x68, 0xcc, 0x5f, 0xb0, 0x8c, 0xd3, 0x18, 0xa6, + 0x5f, 0x52, 0x1c, 0xcc, 0x28, 0x43, 0xb0, 0x61, 0x2b, 0xad, 0xfc, 0x20, 0xb8, 0xb7, 0x09, 0xaf, 0xea, 0x40, 0xa1, + 0x1f, 0x07, 0x91, 0x1c, 0xc4, 0x4c, 0x67, 0xd4, 0x29, 0x9c, 0x45, 0x4d, 0x33, 0x9d, 0xa6, 0x54, 0x36, 0x04, 0x77, + 0x77, 0x18, 0xd1, 0x92, 0x40, 0x4b, 0xcf, 0x7b, 0xb5, 0x16, 0x08, 0x78, 0xef, 0x58, 0x04, 0x73, 0x26, 0x98, 0x1b, + 0x1c, 0xd5, 0xad, 0xc2, 0xa9, 0xe9, 0xe6, 0xab, 0xad, 0x87, 0xda, 0xb6, 0x09, 0x07, 0x41, 0x27, 0x27, 0xbb, 0x2d, + 0xab, 0x97, 0x5a, 0x72, 0x68, 0x69, 0xc1, 0x1e, 0xca, 0x98, 0xd1, 0x52, 0x93, 0x17, 0xd2, 0x5b, 0xf1, 0x92, 0x93, + 0x0f, 0x70, 0x6a, 0xe8, 0x39, 0x9f, 0x46, 0x6b, 0x87, 0x63, 0x3a, 0x97, 0x85, 0xf6, 0x7f, 0xc9, 0x9d, 0x57, 0xf8, + 0x39, 0x84, 0x75, 0xbf, 0x2d, 0xab, 0x6f, 0x86, 0x73, 0xbf, 0x2d, 0x11, 0xf4, 0xad, 0xb7, 0x51, 0xcf, 0x08, 0xe3, + 0xb6, 0xdd, 0x53, 0xb7, 0x69, 0x6b, 0x6d, 0xe9, 0x07, 0x19, 0x44, 0x92, 0x89, 0x96, 0x62, 0x3f, 0xe0, 0x32, 0x4d, + 0x0d, 0xd2, 0xe5, 0xaa, 0x16, 0x12, 0x55, 0x09, 0x86, 0x52, 0x87, 0xdf, 0xb5, 0x3c, 0x4a, 0xc6, 0xa4, 0xd2, 0xce, + 0x78, 0xe3, 0xa7, 0x7c, 0x1f, 0x76, 0x59, 0xb2, 0x71, 0x12, 0x2f, 0x24, 0xe0, 0x41, 0x7b, 0xd8, 0x10, 0x86, 0xb1, + 0x9d, 0xc9, 0x93, 0x40, 0x66, 0xff, 0x24, 0xd1, 0xba, 0x5b, 0xd5, 0xca, 0x78, 0x0f, 0xf6, 0x3f, 0xc2, 0xa1, 0x3e, + 0x1e, 0x47, 0x15, 0x07, 0xa6, 0xde, 0x32, 0x2f, 0x9c, 0x02, 0x89, 0x54, 0xde, 0x62, 0x84, 0x93, 0x5c, 0x84, 0xb7, + 0xbf, 0xc3, 0x3f, 0x2a, 0x96, 0x38, 0x2e, 0x38, 0xce, 0xb3, 0x87, 0x72, 0x44, 0x09, 0x7e, 0x11, 0xbd, 0x07, 0x3a, + 0x16, 0x14, 0x9a, 0x6b, 0x2a, 0x7a, 0x9a, 0xa8, 0x89, 0xec, 0xcc, 0x4a, 0xc5, 0xb4, 0xc8, 0xa8, 0x11, 0xc3, 0x6c, + 0x49, 0xe3, 0xd4, 0x56, 0x36, 0x2f, 0x76, 0x55, 0x65, 0x5c, 0xb4, 0x03, 0x8b, 0x65, 0x60, 0x71, 0xb5, 0x72, 0xaa, + 0xa8, 0x26, 0xcc, 0x88, 0x63, 0x20, 0xcc, 0x8c, 0x84, 0x8a, 0x8a, 0x66, 0x2d, 0xdb, 0x38, 0x68, 0x3d, 0x9f, 0x48, + 0xeb, 0xe6, 0x15, 0x38, 0x4c, 0x17, 0x82, 0x6c, 0x6e, 0xfa, 0x14, 0xb0, 0x9c, 0x5d, 0x31, 0x90, 0x81, 0xa1, 0x1f, + 0x8a, 0x4c, 0xd9, 0x32, 0xa5, 0x75, 0x0b, 0x7e, 0xd1, 0x3d, 0xb9, 0xb2, 0x0a, 0x75, 0x9b, 0xef, 0x8d, 0x5c, 0xa3, + 0xa7, 0xc9, 0xae, 0x5c, 0xa3, 0x8a, 0xb6, 0xbb, 0xd7, 0x44, 0xf7, 0x67, 0xa5, 0xca, 0xb1, 0xb6, 0x57, 0xf9, 0x1d, + 0xc3, 0xb5, 0x80, 0x36, 0x25, 0x9a, 0x35, 0x57, 0x39, 0xcf, 0xf3, 0x71, 0x71, 0x96, 0x40, 0xa4, 0xee, 0x8c, 0x25, + 0xfd, 0x2b, 0xab, 0x51, 0x1c, 0xc8, 0x75, 0xbe, 0x23, 0x93, 0x28, 0xb9, 0xf6, 0xa3, 0x77, 0x30, 0x5e, 0xf9, 0xf2, + 0xf9, 0x5d, 0x90, 0xfa, 0x9c, 0x2a, 0xee, 0x52, 0xc2, 0xf0, 0x9d, 0x01, 0xc3, 0x77, 0x92, 0x4f, 0x97, 0xed, 0xf1, + 0xf2, 0x45, 0xd1, 0x81, 0x37, 0xce, 0x35, 0xcb, 0x98, 0xf2, 0xed, 0x63, 0xac, 0xb3, 0xb0, 0x69, 0xc1, 0xc2, 0xa6, + 0xdc, 0x59, 0xef, 0xca, 0x71, 0x7e, 0xdc, 0xde, 0xcb, 0x26, 0x67, 0xfb, 0xb1, 0xdc, 0xf8, 0x3f, 0x7a, 0xf7, 0xb6, + 0x31, 0xb8, 0xdc, 0xa1, 0x7b, 0x28, 0x92, 0x55, 0x24, 0xc8, 0x4f, 0x20, 0xe9, 0x80, 0x93, 0x9e, 0x71, 0xe4, 0xa0, + 0x94, 0x53, 0x3a, 0x0f, 0xc8, 0x19, 0xcd, 0x33, 0x9e, 0x4c, 0x55, 0x9f, 0x99, 0x3a, 0x67, 0x24, 0x5e, 0x82, 0x2b, + 0x5a, 0xc4, 0xda, 0xbd, 0xea, 0x49, 0xae, 0xe5, 0x47, 0x16, 0x07, 0x5e, 0x86, 0x95, 0x14, 0xc9, 0xbc, 0x34, 0x27, + 0x3a, 0xd7, 0x78, 0xf3, 0x1d, 0x1e, 0xb3, 0x98, 0x65, 0x21, 0x4d, 0x9d, 0x04, 0x2d, 0x77, 0x0d, 0x96, 0x40, 0x40, + 0x46, 0x0e, 0x86, 0x7f, 0x2a, 0x8f, 0xfc, 0xb9, 0xd0, 0x1b, 0xf8, 0x81, 0xa6, 0x94, 0x87, 0x49, 0x00, 0x69, 0x29, + 0x6e, 0x50, 0x1c, 0x69, 0x3a, 0x38, 0xd8, 0x73, 0x6c, 0xe1, 0x96, 0x80, 0xc3, 0xdf, 0xe6, 0x1b, 0xd4, 0x5f, 0xc2, + 0xe9, 0x9c, 0x72, 0x68, 0x8a, 0x96, 0x74, 0xfd, 0x20, 0x0b, 0x77, 0x3f, 0xd2, 0x3b, 0x1c, 0xa3, 0x3c, 0xf7, 0x24, + 0xd4, 0xf6, 0x98, 0xd1, 0x28, 0xb0, 0xf1, 0x47, 0x7a, 0xe7, 0x15, 0xe7, 0xc5, 0xc5, 0xf1, 0x66, 0xb1, 0x80, 0x76, + 0x72, 0x13, 0xdb, 0xb8, 0x1c, 0xc4, 0x5b, 0xe6, 0x38, 0x49, 0xd9, 0x04, 0x88, 0xf3, 0x6f, 0xf4, 0xce, 0x93, 0xfd, + 0x31, 0xe3, 0xb4, 0x1e, 0x5a, 0x6a, 0xd4, 0xbb, 0x46, 0xb1, 0xb9, 0x0c, 0xca, 0xa0, 0x18, 0x88, 0xb6, 0x43, 0x52, + 0xa9, 0x57, 0x9a, 0x87, 0x08, 0xe5, 0x0f, 0x9d, 0x0a, 0xfe, 0xd6, 0x14, 0x6d, 0xbc, 0x92, 0xf9, 0xba, 0xd6, 0x88, + 0x42, 0x83, 0x32, 0xd3, 0xe3, 0xd2, 0x89, 0xf5, 0xae, 0x53, 0x47, 0x10, 0x0c, 0x47, 0xd8, 0xb7, 0x5c, 0x75, 0xea, + 0xfd, 0x24, 0x13, 0x42, 0xca, 0x48, 0xd2, 0xcb, 0xb2, 0x9d, 0x75, 0xe9, 0x00, 0xde, 0x21, 0xa1, 0xc5, 0x17, 0x07, + 0x32, 0x73, 0x9d, 0x2d, 0xfa, 0x37, 0x4e, 0x9c, 0xa5, 0x9e, 0x82, 0x17, 0x9b, 0x58, 0xe4, 0x39, 0x50, 0xa1, 0xa2, + 0x2f, 0x99, 0x00, 0x08, 0x67, 0xd8, 0x37, 0xa4, 0x66, 0x2a, 0xa4, 0xa6, 0x6b, 0x60, 0x7c, 0x87, 0x94, 0xa4, 0x02, + 0x19, 0x42, 0x89, 0x14, 0x42, 0x4f, 0x2d, 0xae, 0x22, 0x21, 0x73, 0x41, 0x8b, 0xf3, 0x73, 0x72, 0xcd, 0xd3, 0x0a, + 0x58, 0x8e, 0xe8, 0x07, 0xe5, 0x1e, 0x4c, 0x89, 0xca, 0x0a, 0x79, 0x71, 0x2c, 0x5b, 0xa7, 0xb7, 0x3a, 0x89, 0xab, + 0xa7, 0x45, 0x34, 0x4a, 0x9c, 0x10, 0x2d, 0x63, 0x27, 0xc4, 0x29, 0xa4, 0x23, 0x26, 0x79, 0x01, 0x3f, 0x35, 0x57, + 0xa3, 0x92, 0xac, 0xbc, 0xfd, 0x8c, 0x1f, 0x28, 0xf3, 0x1c, 0x52, 0x34, 0x71, 0xac, 0x79, 0x4a, 0xec, 0x88, 0xc3, + 0x76, 0xc6, 0xb2, 0x7d, 0xa7, 0x12, 0x74, 0x14, 0x60, 0x7f, 0xe3, 0xce, 0xd2, 0x98, 0x85, 0x79, 0x9a, 0x5b, 0x9d, + 0xf9, 0x53, 0xc1, 0xbe, 0x32, 0x87, 0xd4, 0xc9, 0xc8, 0x9a, 0xc4, 0xb9, 0x3f, 0xd5, 0xf2, 0x97, 0x39, 0x4d, 0xef, + 0x2e, 0x28, 0xa4, 0x3a, 0x27, 0x70, 0xda, 0xb7, 0x5c, 0x86, 0x32, 0x4d, 0xbd, 0x9f, 0x0a, 0x65, 0x25, 0xaf, 0x9e, + 0x02, 0x5c, 0x3f, 0x23, 0x98, 0x8b, 0x68, 0xa3, 0xe1, 0x88, 0x91, 0xbb, 0x85, 0xee, 0x3c, 0x3d, 0x49, 0x3b, 0x0c, + 0xfc, 0x6b, 0x25, 0xa6, 0x55, 0xb0, 0x00, 0x27, 0xe6, 0x89, 0xd4, 0x41, 0x36, 0x5c, 0xf7, 0xca, 0x40, 0x11, 0x84, + 0xef, 0xd2, 0xdd, 0x53, 0xdd, 0x96, 0x34, 0xbb, 0x7b, 0xaa, 0x95, 0xa0, 0x9f, 0x48, 0xf8, 0xc1, 0x6a, 0x9c, 0xe2, + 0xf8, 0x32, 0xcb, 0x73, 0x94, 0x03, 0x78, 0x5f, 0x77, 0x1c, 0xe7, 0x6b, 0x95, 0x32, 0xe8, 0x42, 0x2c, 0xf6, 0x22, + 0x4a, 0x34, 0x13, 0x2f, 0xc7, 0xff, 0x7a, 0x63, 0xfc, 0xaf, 0x8d, 0x33, 0xa7, 0x60, 0x1a, 0x4d, 0x62, 0x1a, 0x68, + 0xd6, 0x89, 0x24, 0x01, 0x0a, 0xbd, 0x2d, 0xe6, 0xe4, 0xf5, 0x95, 0x07, 0x1a, 0xd7, 0x72, 0x9c, 0xc4, 0xbc, 0x3e, + 0xf6, 0xa7, 0x2c, 0xba, 0xf3, 0xe6, 0xac, 0x3e, 0x4d, 0xe2, 0x24, 0x9b, 0xf9, 0x23, 0x8a, 0xb3, 0xbb, 0x8c, 0xd3, + 0x69, 0x7d, 0xce, 0xf0, 0x73, 0x1a, 0x2d, 0x28, 0x67, 0x23, 0x1f, 0xdb, 0x67, 0x29, 0xf3, 0x23, 0xeb, 0x95, 0x9f, + 0xa6, 0xc9, 0x8d, 0x8d, 0xdf, 0x26, 0xd7, 0x09, 0x4f, 0xf0, 0xeb, 0xdb, 0xbb, 0x09, 0x8d, 0xf1, 0xfb, 0xeb, 0x79, + 0xcc, 0xe7, 0x38, 0xf3, 0xe3, 0xac, 0x9e, 0xd1, 0x94, 0x8d, 0x3b, 0xa3, 0x24, 0x4a, 0xd2, 0x3a, 0x64, 0x6c, 0x4f, + 0xa9, 0x17, 0xb1, 0x49, 0xc8, 0xad, 0xc0, 0x4f, 0x3f, 0x76, 0xea, 0xf5, 0x59, 0xca, 0xa6, 0x7e, 0x7a, 0x57, 0x17, + 0x35, 0xbc, 0xcf, 0x9b, 0x87, 0xfe, 0xe3, 0xf1, 0x51, 0x87, 0xa7, 0x7e, 0x9c, 0x31, 0x58, 0x26, 0xcf, 0x8f, 0x22, + 0xeb, 0xf0, 0xb8, 0x39, 0xcd, 0xf6, 0x64, 0x20, 0xcf, 0x8f, 0x79, 0x7e, 0x85, 0xdf, 0x00, 0xdc, 0xee, 0x35, 0x8f, + 0xf1, 0xf5, 0x9c, 0xf3, 0x24, 0x5e, 0x8e, 0xe6, 0x69, 0x96, 0xa4, 0xde, 0x2c, 0x61, 0x31, 0xa7, 0x69, 0xe7, 0x3a, + 0x49, 0x03, 0x9a, 0xd6, 0x53, 0x3f, 0x60, 0xf3, 0xcc, 0x3b, 0x9a, 0xdd, 0x76, 0x40, 0xb3, 0x98, 0xa4, 0xc9, 0x3c, + 0x0e, 0xd4, 0x58, 0x2c, 0x0e, 0x69, 0xca, 0xb8, 0xf9, 0x42, 0x5c, 0x62, 0xe2, 0x45, 0x2c, 0xa6, 0x7e, 0x5a, 0x9f, + 0x40, 0x63, 0x30, 0x8b, 0x9a, 0x01, 0x9d, 0xe0, 0x74, 0x72, 0xed, 0x3b, 0xad, 0xf6, 0x23, 0xac, 0xff, 0xba, 0xc7, + 0xc8, 0x6a, 0x6e, 0x2f, 0x6e, 0x35, 0x9b, 0x7f, 0x41, 0x9d, 0xb5, 0x51, 0x04, 0x40, 0x5e, 0x6b, 0x76, 0x6b, 0x65, + 0x09, 0x64, 0xb4, 0x6d, 0x6b, 0xd9, 0x99, 0xf9, 0x01, 0xe4, 0x03, 0x7b, 0xed, 0xd9, 0x6d, 0x0e, 0xb3, 0xf3, 0x64, + 0x8a, 0xa9, 0x9a, 0xa4, 0x7a, 0x5a, 0xfe, 0x5e, 0x88, 0x4f, 0xb7, 0x43, 0xdc, 0xd6, 0x10, 0x97, 0x58, 0xaf, 0x07, + 0xf3, 0x54, 0xc4, 0x56, 0xbd, 0x56, 0x26, 0x01, 0x09, 0x93, 0x05, 0x4d, 0x35, 0x1c, 0xe2, 0xe1, 0x77, 0x83, 0xd1, + 0xde, 0x0e, 0xc6, 0xe9, 0xa7, 0xc0, 0x48, 0xe3, 0x60, 0x59, 0x5d, 0xd7, 0x56, 0x4a, 0xa7, 0x9d, 0x90, 0x02, 0x3d, + 0x79, 0x6d, 0xf8, 0x7d, 0xc3, 0x02, 0x1e, 0xca, 0x9f, 0x82, 0x9c, 0x6f, 0xe4, 0xbb, 0xe3, 0x66, 0x53, 0x3e, 0x67, + 0xec, 0x57, 0xea, 0xb5, 0x5c, 0xa8, 0x90, 0x5f, 0xe1, 0x1f, 0x8b, 0xb3, 0xbc, 0x55, 0xee, 0x89, 0xbf, 0x36, 0x0f, + 0xf9, 0x1a, 0x29, 0x8a, 0xe5, 0x91, 0x68, 0x9c, 0x6a, 0x59, 0x29, 0x85, 0x0f, 0xb8, 0xed, 0x04, 0x77, 0x24, 0xac, + 0x57, 0x1c, 0xe2, 0x64, 0xfd, 0xaf, 0x65, 0xde, 0x85, 0x07, 0x91, 0x0e, 0x23, 0xd5, 0x30, 0xe9, 0xa4, 0x3d, 0xd2, + 0xec, 0xa4, 0xf5, 0x3a, 0x72, 0x12, 0x12, 0x0f, 0x52, 0x95, 0x9c, 0xe7, 0xb0, 0x7e, 0x22, 0x8c, 0xed, 0x0c, 0x79, + 0x09, 0x9c, 0x34, 0x5d, 0xad, 0xca, 0x30, 0x00, 0x13, 0xa7, 0x35, 0x7e, 0xe4, 0xaa, 0x02, 0xce, 0x0c, 0x4e, 0x9e, + 0xe8, 0xab, 0x5d, 0x62, 0xcd, 0x2b, 0xa2, 0x64, 0x24, 0x30, 0xe7, 0xce, 0x7c, 0x1e, 0x82, 0x97, 0xa2, 0x10, 0x3f, + 0x65, 0x0a, 0x93, 0xdd, 0xb0, 0x51, 0x3f, 0x2e, 0xf2, 0xdb, 0x20, 0x8f, 0x2f, 0xce, 0xa1, 0x97, 0x3b, 0x4e, 0x84, + 0xc5, 0x54, 0xf4, 0xff, 0x9e, 0x1b, 0x92, 0x3a, 0x76, 0x59, 0x3c, 0x8a, 0xe6, 0x01, 0xcd, 0x44, 0x0f, 0xa5, 0x38, + 0xff, 0xbb, 0x59, 0x4b, 0x34, 0x81, 0xde, 0x45, 0x36, 0x0f, 0x54, 0x84, 0x1b, 0x54, 0x8a, 0xe7, 0xba, 0x78, 0x2e, + 0xdb, 0xea, 0x4b, 0x25, 0xd8, 0xd8, 0x81, 0x96, 0xee, 0x3c, 0x66, 0xbf, 0xcc, 0xe9, 0x25, 0x0b, 0x8c, 0x73, 0xbb, + 0x34, 0x1e, 0x25, 0x01, 0x7d, 0xff, 0xf6, 0x1b, 0xc8, 0x76, 0x4f, 0x62, 0x20, 0xb1, 0x58, 0xfa, 0xbb, 0x70, 0x46, + 0x62, 0x37, 0xa0, 0x0b, 0x36, 0xa2, 0xfd, 0xab, 0xfd, 0xe5, 0xd6, 0x8a, 0xf2, 0x35, 0xca, 0x1b, 0x57, 0x22, 0xe9, + 0x4f, 0x40, 0x79, 0xb5, 0xbf, 0xbc, 0xe3, 0x79, 0x63, 0x7f, 0x19, 0xbb, 0x41, 0x32, 0xf5, 0x59, 0x0c, 0xbf, 0xb3, + 0x7c, 0x7f, 0xc9, 0xe0, 0x07, 0xcf, 0xaf, 0xf2, 0x32, 0x51, 0xb4, 0x80, 0xc8, 0x98, 0x82, 0xc2, 0x5d, 0x0b, 0xb9, + 0x1f, 0x12, 0x16, 0x8b, 0xa2, 0xfb, 0x7a, 0xa6, 0xba, 0x57, 0x40, 0xf2, 0x37, 0x44, 0x1a, 0xcc, 0xda, 0x5c, 0x1e, + 0x3f, 0xd4, 0x5c, 0xa6, 0x31, 0x67, 0x22, 0x2d, 0x5e, 0x87, 0x73, 0x42, 0x3f, 0xbb, 0x1c, 0xc9, 0x73, 0xa8, 0x59, + 0x79, 0xea, 0xc2, 0x17, 0x88, 0x95, 0x16, 0x30, 0x4d, 0x85, 0xb1, 0x4f, 0x77, 0x1f, 0x94, 0x8c, 0xef, 0x33, 0xfe, + 0x0a, 0xaa, 0xca, 0x92, 0x79, 0x3a, 0x82, 0x58, 0xaf, 0x52, 0x29, 0x36, 0xbd, 0x62, 0xb6, 0xd0, 0xdf, 0x6c, 0xcc, + 0x8d, 0x24, 0x5b, 0x8e, 0x99, 0x79, 0x67, 0x07, 0x15, 0xf1, 0x44, 0x79, 0x16, 0x46, 0xe9, 0x0f, 0x7a, 0x4a, 0xa0, + 0x10, 0x05, 0x22, 0x5f, 0xd4, 0x49, 0x49, 0x2f, 0x2d, 0x71, 0x4e, 0x08, 0x61, 0x2e, 0x0b, 0x44, 0x20, 0x0f, 0x14, + 0x8b, 0x7a, 0x0b, 0x22, 0x43, 0x2c, 0x28, 0x35, 0x3c, 0xa6, 0xf0, 0xbc, 0x5a, 0xfd, 0x9d, 0x3b, 0xb2, 0xae, 0x74, + 0xaa, 0x80, 0x0e, 0xc6, 0xb0, 0x7c, 0xe9, 0xa5, 0xb8, 0xe8, 0xd2, 0x83, 0x4a, 0x79, 0x27, 0x11, 0xe8, 0x93, 0xc8, + 0x22, 0x1a, 0x9d, 0x67, 0x52, 0x45, 0x48, 0x10, 0x36, 0x5f, 0x17, 0x07, 0xf8, 0x2b, 0xf8, 0x6e, 0xae, 0x2d, 0x8b, + 0xb4, 0xa7, 0x92, 0xf5, 0xd2, 0x2c, 0x49, 0xb9, 0xe3, 0x84, 0x38, 0x42, 0xa4, 0x17, 0x0a, 0xaa, 0xed, 0x46, 0xe2, + 0xbf, 0x7e, 0xbd, 0xe5, 0xb5, 0x0a, 0x4f, 0x48, 0xe5, 0x5c, 0xb5, 0xcc, 0x33, 0x53, 0x67, 0x73, 0x01, 0x5c, 0x5c, + 0xfc, 0x96, 0xf3, 0x29, 0x9f, 0x8b, 0x69, 0x61, 0xc5, 0xb9, 0xa4, 0xd4, 0x77, 0x2a, 0x40, 0x88, 0xb8, 0xdb, 0x8e, + 0xa1, 0x50, 0x5e, 0xce, 0xbb, 0xd8, 0xc5, 0x57, 0x52, 0xdb, 0xb9, 0x34, 0xc8, 0xf8, 0x8a, 0x69, 0x7f, 0x5d, 0x95, + 0xc0, 0x72, 0x85, 0x11, 0x83, 0x05, 0x6c, 0xab, 0x26, 0x61, 0xb9, 0x23, 0xf1, 0x56, 0x2a, 0x75, 0xe5, 0x23, 0x95, + 0xba, 0xd6, 0xf6, 0x2a, 0x22, 0xeb, 0x71, 0x1b, 0x60, 0xe0, 0x01, 0xc8, 0xb8, 0x9e, 0x02, 0x30, 0x93, 0x31, 0x15, + 0x17, 0xd3, 0x48, 0xd6, 0x82, 0x97, 0x52, 0x8d, 0xf7, 0xec, 0x37, 0xaf, 0x2f, 0xde, 0xd9, 0x18, 0xee, 0x33, 0xa3, + 0x69, 0xe6, 0x2d, 0x6d, 0x95, 0x4c, 0x58, 0x87, 0xc0, 0xb4, 0xed, 0xd9, 0xfe, 0x0c, 0xce, 0x66, 0x0b, 0xee, 0xd9, + 0xb8, 0xad, 0xdf, 0xdc, 0xdc, 0xd4, 0xe1, 0xe8, 0x58, 0x7d, 0x9e, 0x46, 0x92, 0xaf, 0x04, 0x76, 0x9e, 0x23, 0x97, + 0x87, 0x34, 0x2e, 0x6e, 0x3c, 0x4a, 0x22, 0xea, 0x46, 0xc9, 0x44, 0x1e, 0x7b, 0x5d, 0xf7, 0x43, 0x8c, 0xae, 0xba, + 0xe2, 0x26, 0xaf, 0x5e, 0x97, 0xcb, 0x3b, 0xd4, 0x78, 0x0a, 0x3f, 0x7b, 0x10, 0xa5, 0xea, 0x36, 0x78, 0x28, 0x1e, + 0x2e, 0x60, 0xdb, 0x88, 0xa7, 0xfd, 0xe5, 0x06, 0x91, 0xf5, 0xa1, 0x8b, 0xb0, 0x27, 0xa7, 0x96, 0x89, 0x5a, 0x57, + 0xde, 0xe8, 0xea, 0x2a, 0xef, 0x36, 0xa0, 0xaf, 0x86, 0xee, 0xf7, 0x3a, 0x09, 0xee, 0x74, 0xfb, 0x82, 0xf0, 0xe0, + 0x46, 0xa7, 0x98, 0xf4, 0xa0, 0x0b, 0x18, 0x37, 0xe8, 0x09, 0x9c, 0x29, 0x5e, 0x39, 0x28, 0x1f, 0xf2, 0xa1, 0x05, + 0x9c, 0x31, 0x87, 0x12, 0xa0, 0x4b, 0xe8, 0x3c, 0x28, 0x1a, 0x88, 0x6d, 0x2d, 0x8b, 0x76, 0x01, 0x28, 0x2b, 0x96, + 0xdb, 0x45, 0xfa, 0xb3, 0x4b, 0xb2, 0xd0, 0x10, 0x07, 0x26, 0xf0, 0x57, 0x08, 0xfe, 0x17, 0x80, 0x77, 0x1b, 0x12, + 0x4d, 0x57, 0xe6, 0xed, 0x32, 0xf2, 0xde, 0x87, 0x02, 0x99, 0x83, 0x98, 0xe3, 0x37, 0x1c, 0xbf, 0xbe, 0x12, 0x55, + 0xb5, 0x3a, 0x00, 0x7a, 0x2a, 0xa8, 0x4d, 0x4d, 0xad, 0xf7, 0x8d, 0x92, 0x28, 0xf2, 0x67, 0x19, 0xf5, 0xf4, 0x0f, + 0xa5, 0x19, 0x80, 0x82, 0xb1, 0xa9, 0x8a, 0xa9, 0x04, 0xa7, 0x73, 0x50, 0xd8, 0x36, 0xf5, 0xc4, 0x85, 0x9f, 0x3a, + 0xf5, 0xfa, 0xa8, 0x7e, 0x3d, 0x41, 0x39, 0x0f, 0x97, 0xa6, 0x5e, 0x71, 0xd2, 0x6c, 0x76, 0x20, 0x1b, 0xb5, 0xee, + 0x47, 0x6c, 0x12, 0x7b, 0x11, 0x1d, 0xf3, 0x9c, 0xc3, 0x31, 0xc1, 0xa5, 0x56, 0xe4, 0xdc, 0xf6, 0x71, 0x4a, 0xa7, + 0x96, 0x0b, 0xff, 0xde, 0x3f, 0x70, 0xce, 0x03, 0x2f, 0xe6, 0x61, 0x5d, 0x64, 0x3d, 0xc3, 0x99, 0x0d, 0x1e, 0x56, + 0x9e, 0x97, 0xc6, 0x40, 0x23, 0x0a, 0x4a, 0x6e, 0xce, 0x53, 0x8b, 0x87, 0x98, 0xa7, 0x66, 0xbd, 0x18, 0x2d, 0x37, + 0x66, 0xb0, 0xa9, 0x6b, 0x1d, 0xa2, 0x3c, 0x13, 0xa6, 0xc9, 0x66, 0x65, 0xad, 0xb0, 0x56, 0x9f, 0x36, 0xd0, 0x67, + 0xa8, 0xd6, 0xb9, 0x74, 0xed, 0x2f, 0x65, 0x8b, 0x87, 0x20, 0xb3, 0xa2, 0xf4, 0x63, 0xb3, 0x05, 0xca, 0x59, 0x3c, + 0x9b, 0xf3, 0x81, 0x08, 0x2b, 0xa4, 0x70, 0x40, 0x65, 0x88, 0x8d, 0x12, 0xc0, 0xc1, 0x70, 0x29, 0x81, 0x19, 0xf9, + 0xd1, 0xc8, 0x01, 0x88, 0xac, 0xba, 0x75, 0x9a, 0xd2, 0x29, 0xea, 0x4c, 0x59, 0x5c, 0x97, 0xef, 0x8e, 0x0d, 0xc5, + 0xd0, 0x7d, 0x04, 0x4f, 0xb9, 0x2b, 0x7a, 0xc3, 0x22, 0x7b, 0x78, 0x0b, 0x2e, 0xaf, 0x86, 0x79, 0xde, 0x49, 0xb9, + 0x33, 0x78, 0xe9, 0xa0, 0x21, 0xfe, 0xc6, 0xb8, 0x1f, 0xc7, 0xd6, 0x3b, 0xc9, 0xc6, 0x6d, 0xb4, 0xa3, 0x8a, 0xb9, + 0x17, 0x44, 0xb5, 0x6f, 0x08, 0x54, 0x7c, 0xe2, 0xd8, 0x34, 0x9b, 0xd5, 0x25, 0xcb, 0xab, 0x0b, 0x92, 0xb5, 0xa1, + 0x29, 0x52, 0xbe, 0x72, 0x4a, 0x97, 0x82, 0x9b, 0xa9, 0x43, 0x32, 0xd2, 0x9d, 0x33, 0x2c, 0x0e, 0x55, 0xa9, 0x67, + 0xf3, 0x18, 0x15, 0xaa, 0xb0, 0x9b, 0xab, 0xb3, 0x2a, 0x6b, 0x04, 0xe5, 0xa2, 0xb8, 0x44, 0xd0, 0x8f, 0x22, 0x18, + 0xf0, 0x4a, 0x6b, 0x24, 0xe6, 0xad, 0x2b, 0x03, 0x3e, 0x74, 0x50, 0xae, 0xf6, 0xe9, 0x13, 0xa1, 0xd4, 0x1b, 0x37, + 0x17, 0xee, 0x71, 0x1d, 0xae, 0x93, 0x22, 0x9a, 0x41, 0xc2, 0x41, 0x25, 0x31, 0xbd, 0x53, 0xb2, 0x36, 0x69, 0x12, + 0x58, 0x62, 0x42, 0xc4, 0x4e, 0xe3, 0xc0, 0xb6, 0xbe, 0x1c, 0x45, 0x6c, 0xf4, 0x91, 0xd8, 0xfb, 0x4b, 0x07, 0x6d, + 0x9e, 0x3b, 0x15, 0x5c, 0x41, 0xf3, 0x79, 0x54, 0x0d, 0x65, 0xa4, 0xae, 0xc1, 0xc2, 0xe5, 0xc5, 0x44, 0x76, 0x0f, + 0xf4, 0xa6, 0x6e, 0x43, 0x8e, 0xd3, 0xbb, 0xca, 0x2f, 0xcb, 0xfb, 0xc6, 0x4a, 0x28, 0x00, 0xcd, 0xb2, 0xdc, 0x12, + 0x44, 0x45, 0xec, 0x4f, 0x52, 0x9a, 0x6d, 0x49, 0xa6, 0x06, 0x70, 0x72, 0xc5, 0xdf, 0x6c, 0xeb, 0xcb, 0xa2, 0x8c, + 0x16, 0x3e, 0x25, 0x91, 0x14, 0x43, 0x6c, 0x18, 0x0b, 0x1c, 0x09, 0x6e, 0x40, 0xb9, 0xcf, 0x22, 0xd9, 0xa4, 0xa3, + 0x5d, 0x20, 0x6b, 0x33, 0x5a, 0xad, 0xb2, 0xea, 0x5c, 0x58, 0x15, 0x83, 0x62, 0x66, 0xdd, 0x46, 0x09, 0xb7, 0x98, + 0x99, 0xd8, 0x93, 0x66, 0x70, 0xb6, 0x9c, 0xa1, 0x7c, 0x67, 0x7d, 0x39, 0x12, 0xc7, 0xb6, 0x00, 0xc0, 0x44, 0x01, + 0x08, 0x69, 0x03, 0xf2, 0x58, 0x92, 0x13, 0x91, 0xc4, 0xe5, 0x7e, 0x3a, 0xa1, 0x7c, 0x0d, 0xb1, 0x91, 0xcc, 0x12, + 0xee, 0xe8, 0x14, 0x81, 0x0d, 0x68, 0xfd, 0x2a, 0xb4, 0xa0, 0x44, 0xe7, 0x7d, 0xd0, 0x83, 0xc9, 0x56, 0x75, 0x3a, + 0x44, 0x20, 0x6f, 0xc5, 0xe2, 0x48, 0x09, 0x93, 0x08, 0x09, 0x23, 0x39, 0x81, 0x25, 0xc6, 0x12, 0x20, 0xe6, 0xb6, + 0xd5, 0x97, 0x90, 0xd3, 0x40, 0xc2, 0x4c, 0x52, 0xd1, 0x2a, 0xc9, 0xbb, 0x0d, 0x59, 0x5b, 0x8a, 0x00, 0x59, 0x09, + 0x90, 0x20, 0xf6, 0x69, 0x89, 0x03, 0xc8, 0x2c, 0x37, 0xf1, 0x10, 0xb0, 0x45, 0x41, 0x6c, 0xe2, 0x00, 0x5b, 0xaf, + 0x1b, 0xf9, 0xd7, 0x34, 0xea, 0xed, 0x2f, 0xd3, 0xd5, 0xaa, 0x99, 0x77, 0x1b, 0xf2, 0xd1, 0xea, 0x0a, 0xbe, 0x21, + 0x2f, 0x1d, 0x15, 0x4b, 0x0c, 0xa7, 0x42, 0x21, 0xdf, 0x56, 0x27, 0x9a, 0x79, 0xaa, 0x83, 0xdc, 0xb6, 0x44, 0x8a, + 0x8b, 0xa8, 0x54, 0xe8, 0x51, 0xb9, 0x6d, 0xb1, 0x60, 0xb3, 0x2c, 0xe3, 0x74, 0x06, 0xa5, 0xe1, 0x6a, 0xd5, 0xca, + 0x6d, 0x6b, 0xca, 0x62, 0x78, 0x4a, 0x57, 0x2b, 0x71, 0xe0, 0x72, 0xca, 0x62, 0xa7, 0x09, 0x64, 0x6b, 0x5b, 0x53, + 0xff, 0x56, 0x4c, 0x58, 0xbf, 0xf1, 0x6f, 0x9d, 0x96, 0x7a, 0xe5, 0x16, 0xf8, 0xc9, 0x80, 0xe2, 0xca, 0x15, 0x8d, + 0xd4, 0x8a, 0x06, 0x78, 0x2e, 0x8f, 0x92, 0x11, 0x27, 0x20, 0xd1, 0xf6, 0x15, 0x0d, 0xf4, 0x8a, 0xce, 0x77, 0xac, + 0xe8, 0xfc, 0x9e, 0x15, 0xf5, 0xd5, 0xea, 0x59, 0x05, 0xee, 0x92, 0xd5, 0xaa, 0xd5, 0x2c, 0xb1, 0xd7, 0x6d, 0x04, + 0x6c, 0x01, 0xab, 0x01, 0xda, 0x21, 0x67, 0x53, 0xba, 0x9d, 0x28, 0xab, 0x28, 0xa6, 0xbf, 0x09, 0x93, 0x25, 0x16, + 0xd2, 0x2a, 0x16, 0x4c, 0xba, 0x2e, 0xa2, 0x9e, 0x7f, 0x26, 0x65, 0x33, 0xc0, 0x43, 0x06, 0x78, 0x08, 0xf5, 0x25, + 0xa4, 0x8e, 0xfd, 0xce, 0xc6, 0xb6, 0x65, 0x6b, 0xb2, 0xbe, 0xca, 0x2f, 0x41, 0x46, 0x88, 0xf9, 0x3d, 0x88, 0x16, + 0xa1, 0xb6, 0xdd, 0xdb, 0x4d, 0x73, 0x90, 0xa0, 0x70, 0x93, 0xa4, 0x81, 0xed, 0xc9, 0xaa, 0xbf, 0x09, 0x55, 0x53, + 0x16, 0xab, 0x74, 0xb7, 0x9d, 0xb4, 0x56, 0xbe, 0x37, 0x29, 0xae, 0x7d, 0x7c, 0x2c, 0x6b, 0xcc, 0x7c, 0xce, 0x69, + 0x1a, 0x2b, 0xca, 0xb5, 0xed, 0xff, 0x2f, 0xa8, 0x70, 0x0b, 0x5f, 0xf1, 0xf5, 0x02, 0x68, 0x02, 0x54, 0x7a, 0xbe, + 0xe2, 0xf9, 0x52, 0x3c, 0xed, 0x95, 0x0a, 0xee, 0x1d, 0x32, 0x6d, 0x0d, 0x59, 0x04, 0xa6, 0xcf, 0x7c, 0x4a, 0x83, + 0x4b, 0xc1, 0xa0, 0xfb, 0xa3, 0x2b, 0xa5, 0xb0, 0xae, 0x89, 0xbb, 0xb2, 0x01, 0xb6, 0x7f, 0x9e, 0xb7, 0x1f, 0x1d, + 0x9d, 0xdb, 0x58, 0xf2, 0xf8, 0x64, 0x3c, 0xb6, 0x51, 0x6e, 0x3d, 0xac, 0x59, 0xeb, 0xe8, 0xe7, 0xf9, 0x57, 0xcf, + 0x9a, 0x5f, 0x15, 0x8d, 0x63, 0x20, 0x22, 0x95, 0x61, 0xa1, 0x45, 0x95, 0x01, 0xaf, 0x9e, 0xd1, 0xd8, 0x8f, 0x77, + 0x4f, 0x67, 0x60, 0x4e, 0x27, 0x9b, 0x51, 0x1a, 0x00, 0x71, 0xe2, 0x8d, 0xd2, 0xcb, 0x88, 0x2e, 0xa8, 0xbe, 0xfc, + 0x71, 0xcb, 0x60, 0x5b, 0x5a, 0x8c, 0x92, 0x79, 0xcc, 0x55, 0xaa, 0x89, 0x62, 0xb5, 0xc6, 0x94, 0xae, 0xc4, 0x1c, + 0x4c, 0x13, 0xe2, 0x4e, 0xca, 0xb9, 0xaa, 0xf4, 0xca, 0xaf, 0xb0, 0x6d, 0x00, 0xb0, 0x13, 0xb2, 0xfe, 0x8e, 0x72, + 0xaf, 0x89, 0x9b, 0xbb, 0x60, 0xc3, 0x2d, 0xe4, 0xd9, 0xf6, 0x50, 0xe3, 0x49, 0x78, 0x8b, 0x2b, 0x37, 0x76, 0xec, + 0xc4, 0xd7, 0x27, 0x31, 0x70, 0x9d, 0x42, 0x67, 0x31, 0xcd, 0xb2, 0x9d, 0x08, 0x28, 0x16, 0x11, 0xdb, 0x65, 0x6d, + 0x7b, 0x47, 0x2f, 0xb8, 0x89, 0x61, 0x87, 0x09, 0x80, 0x8b, 0x98, 0xb5, 0xaa, 0x45, 0xc7, 0x63, 0x3a, 0x2a, 0x9c, + 0xed, 0x10, 0x7d, 0x1c, 0xb3, 0x88, 0x43, 0x10, 0x4e, 0x44, 0xc7, 0xec, 0x57, 0x49, 0x4c, 0x6d, 0xa4, 0xf3, 0x69, + 0x15, 0xfc, 0x4a, 0xfe, 0x6f, 0x87, 0x47, 0xf6, 0x58, 0x85, 0x45, 0x8d, 0xb2, 0x5a, 0x69, 0x5f, 0x50, 0xa5, 0xbc, + 0x8a, 0xc8, 0x44, 0x38, 0x7b, 0x76, 0x6d, 0xa0, 0x87, 0x6d, 0x93, 0x65, 0xeb, 0xab, 0xe3, 0x56, 0x33, 0xb7, 0xb1, + 0x0d, 0xdd, 0x3d, 0x74, 0x97, 0x88, 0x56, 0x87, 0xd0, 0x6a, 0x1e, 0xff, 0x96, 0x76, 0xed, 0xd6, 0xe3, 0x96, 0x8d, + 0xe5, 0x45, 0x0e, 0x28, 0x2f, 0x98, 0xc1, 0x08, 0xdc, 0xcf, 0x7f, 0x78, 0x2a, 0xd5, 0xce, 0x1f, 0x06, 0xcf, 0x49, + 0xab, 0x69, 0x63, 0x3b, 0xe3, 0xc9, 0xec, 0x37, 0x4c, 0xe1, 0xd0, 0xc6, 0xf6, 0x28, 0x4a, 0x32, 0x6a, 0xce, 0x41, + 0xaa, 0xb3, 0x7f, 0x7c, 0x12, 0x12, 0xa2, 0x59, 0x4a, 0xb3, 0xcc, 0x32, 0xfb, 0x57, 0xa4, 0xf4, 0x09, 0x86, 0xb9, + 0x95, 0xe2, 0x32, 0xca, 0x05, 0x5e, 0xe4, 0x1d, 0x0b, 0x26, 0x55, 0xc9, 0xb2, 0x0d, 0x62, 0x13, 0x22, 0xa0, 0x60, + 0x6c, 0x52, 0xbb, 0xfa, 0xe4, 0xc8, 0x5b, 0xb6, 0x9e, 0x1c, 0x58, 0x46, 0xe5, 0x37, 0x07, 0xa8, 0x94, 0x4c, 0x59, + 0x7c, 0xb9, 0xa5, 0xd4, 0xbf, 0xdd, 0x52, 0x0a, 0x2a, 0x5b, 0x01, 0x9d, 0xba, 0xff, 0xe7, 0xd3, 0x58, 0x2f, 0x15, + 0x1f, 0x13, 0xc4, 0x40, 0x38, 0x37, 0x3f, 0x01, 0xa9, 0xb1, 0x0c, 0xa2, 0x87, 0xdf, 0x3f, 0x1c, 0x94, 0xfc, 0x96, + 0xe1, 0x8a, 0x5e, 0xfe, 0xd8, 0x0c, 0xa1, 0xb4, 0x0e, 0x11, 0x84, 0xe8, 0x37, 0xcd, 0x95, 0xde, 0x7e, 0x9a, 0xe0, + 0x0c, 0xad, 0xea, 0x0f, 0x2c, 0xbd, 0xba, 0x47, 0x60, 0x7d, 0xed, 0xb7, 0x14, 0x2b, 0xc5, 0xa7, 0x58, 0xff, 0x51, + 0xc4, 0xa6, 0x25, 0x09, 0x6c, 0x82, 0x29, 0x34, 0x1e, 0x48, 0x27, 0x33, 0x3b, 0x91, 0xaa, 0xcf, 0x25, 0x1c, 0x92, + 0x85, 0x7b, 0x48, 0xe6, 0x29, 0xbd, 0x8c, 0x92, 0x9b, 0xf5, 0x8b, 0xd5, 0x76, 0x57, 0x0e, 0xd9, 0x24, 0x34, 0x4e, + 0xbe, 0x51, 0x52, 0x2c, 0xc2, 0xbd, 0x03, 0xe4, 0xff, 0xf2, 0xcf, 0xae, 0xfb, 0x2f, 0xff, 0xfc, 0xc9, 0xaa, 0xd0, + 0x7d, 0x7e, 0x85, 0x79, 0xd9, 0xed, 0xee, 0xdd, 0xb5, 0x7d, 0xa4, 0x2a, 0xce, 0xb7, 0xd7, 0xd9, 0x58, 0x04, 0x78, + 0xbf, 0xb1, 0x04, 0x1b, 0x85, 0x72, 0xf7, 0x59, 0xbf, 0x07, 0x30, 0x98, 0xd7, 0x27, 0x21, 0x83, 0x4a, 0x7f, 0x08, + 0xb4, 0x2b, 0xe4, 0x3d, 0x68, 0x45, 0x7e, 0x3f, 0x86, 0x3f, 0x35, 0x87, 0x3f, 0x08, 0xbe, 0xf2, 0x4f, 0x8c, 0xae, + 0xae, 0x8a, 0x14, 0x47, 0xb3, 0x29, 0x5c, 0xa0, 0xd0, 0xdf, 0x28, 0x51, 0x8a, 0x87, 0xd7, 0x44, 0x3d, 0x71, 0x40, + 0x93, 0x8c, 0xae, 0x5e, 0xc2, 0xad, 0x49, 0xdd, 0xeb, 0x54, 0x3b, 0x78, 0xef, 0x11, 0x0e, 0xd0, 0x45, 0x75, 0x56, + 0xa2, 0xd3, 0x0d, 0xc9, 0x00, 0xa5, 0x60, 0x6e, 0x00, 0x98, 0x78, 0x74, 0xa5, 0xac, 0xcd, 0x73, 0xe9, 0x86, 0xf1, + 0xd6, 0x49, 0x5b, 0xb9, 0x67, 0x2a, 0x48, 0xc7, 0xd6, 0x3b, 0x81, 0x2f, 0x51, 0x99, 0x96, 0xd6, 0xbd, 0x70, 0x75, + 0x81, 0x1d, 0x51, 0xb0, 0x9f, 0x85, 0x1f, 0x2d, 0x1e, 0xc6, 0xf8, 0x76, 0x0b, 0xd4, 0x95, 0xb5, 0xfa, 0xb7, 0x56, + 0x09, 0x56, 0xf5, 0x55, 0x45, 0x1f, 0x10, 0x69, 0x1e, 0x8c, 0xee, 0x88, 0x44, 0x67, 0xf4, 0x93, 0x91, 0xe8, 0xe8, + 0x41, 0x91, 0xe8, 0x8c, 0xfe, 0xd9, 0x91, 0x68, 0x46, 0x8d, 0x48, 0x34, 0x90, 0xe0, 0x2f, 0x0f, 0x0a, 0x68, 0xea, + 0xf0, 0x53, 0x72, 0x93, 0x91, 0x96, 0x32, 0x02, 0xa2, 0x64, 0x02, 0xd1, 0xcc, 0x7f, 0xfb, 0xe0, 0x64, 0x94, 0x4c, + 0xcc, 0xd0, 0x24, 0x5c, 0xfa, 0x0b, 0xb1, 0x48, 0x9c, 0x92, 0xa5, 0xfd, 0xf3, 0x6d, 0xeb, 0xc9, 0xa0, 0xd5, 0x39, + 0x6c, 0x4d, 0x6d, 0xcf, 0x06, 0xa9, 0x2b, 0x0a, 0x9a, 0x9d, 0xc3, 0x43, 0x28, 0xb8, 0x31, 0x0a, 0xda, 0x50, 0xc0, + 0x8c, 0x82, 0x63, 0x28, 0x18, 0x19, 0x05, 0x27, 0x50, 0x10, 0x18, 0x05, 0x8f, 0xa0, 0x60, 0x61, 0xe7, 0x03, 0x56, + 0x84, 0xdb, 0x1f, 0x21, 0x71, 0x3f, 0xc8, 0x5e, 0x5a, 0x3d, 0x1b, 0x11, 0x12, 0x5d, 0xe5, 0x51, 0x71, 0xae, 0xaa, + 0x7e, 0xa4, 0xaf, 0x01, 0xb9, 0xfa, 0xec, 0x0a, 0xe1, 0x88, 0xc0, 0x31, 0x47, 0x0c, 0x46, 0xb9, 0xac, 0x79, 0xa8, + 0x5f, 0xdb, 0x5e, 0x11, 0x93, 0x6e, 0xe2, 0xb6, 0x8e, 0x4a, 0x7b, 0x36, 0xc2, 0xf3, 0xa2, 0xf2, 0x71, 0x2d, 0x50, + 0xdd, 0xc2, 0x0d, 0x1b, 0xe5, 0xf5, 0x36, 0x87, 0x08, 0xcb, 0x1b, 0xc5, 0x9f, 0x0a, 0xf9, 0xe8, 0xf2, 0xe4, 0x1d, + 0x9b, 0x52, 0xfd, 0xbd, 0x15, 0x3d, 0x80, 0x25, 0xe2, 0xf6, 0x9d, 0xb0, 0xbc, 0x13, 0xee, 0x2b, 0x7c, 0x56, 0xde, + 0xa8, 0xf4, 0x8e, 0x13, 0x79, 0x45, 0x45, 0x8a, 0xa5, 0xa1, 0x37, 0xc1, 0xdc, 0x9f, 0x78, 0x10, 0xb8, 0x04, 0x9f, + 0xa9, 0x77, 0x46, 0x08, 0x69, 0xf6, 0xe7, 0xde, 0x57, 0xf8, 0x26, 0xa4, 0xb1, 0xb7, 0xc8, 0x3b, 0x05, 0x01, 0xc8, + 0xb8, 0xe9, 0x3b, 0x5e, 0x5c, 0xc4, 0x27, 0xa8, 0xa2, 0x7c, 0x2d, 0xe1, 0xac, 0x17, 0xd4, 0xb3, 0x23, 0xd4, 0x66, + 0xf8, 0x64, 0xc6, 0x51, 0x72, 0x53, 0xbf, 0xb5, 0x7b, 0xdb, 0xc3, 0x6f, 0x30, 0xbb, 0x22, 0xfc, 0xf6, 0x02, 0x80, + 0x2d, 0x9e, 0xde, 0xf9, 0x93, 0xe2, 0xf7, 0x4b, 0x9a, 0x65, 0xfe, 0x44, 0xd5, 0xdc, 0x1d, 0x6e, 0x13, 0x20, 0x9a, + 0xa1, 0x36, 0x0d, 0x04, 0xc4, 0xc4, 0x00, 0x23, 0xe0, 0xd3, 0x50, 0x21, 0x32, 0x98, 0x7a, 0x35, 0xba, 0x26, 0x70, + 0x55, 0x2d, 0xe2, 0xfe, 0xa4, 0x2c, 0xe8, 0xce, 0x52, 0xaa, 0xe2, 0x76, 0x80, 0xc6, 0xbc, 0xdb, 0x80, 0x02, 0xf9, + 0x7a, 0x47, 0x14, 0x4d, 0x3b, 0x50, 0x76, 0xc7, 0xd2, 0x2c, 0x1d, 0x45, 0x33, 0x33, 0xbf, 0x8a, 0xb4, 0xaf, 0xcd, + 0xd8, 0xcd, 0xe7, 0xad, 0x11, 0xfc, 0x51, 0x91, 0xa1, 0xcf, 0xc7, 0xe3, 0xf1, 0xbd, 0x51, 0xb5, 0xcf, 0x83, 0x31, + 0x6d, 0xd3, 0xe3, 0x0e, 0x64, 0x05, 0xd5, 0x55, 0x2c, 0xa6, 0x95, 0x0b, 0xdc, 0x2d, 0x1f, 0x56, 0x19, 0xc2, 0x36, + 0x3c, 0x5c, 0x3e, 0x3c, 0xc2, 0x96, 0xcf, 0x52, 0xba, 0x9c, 0xfa, 0xe9, 0x84, 0xc5, 0x5e, 0x33, 0x77, 0x17, 0x2a, + 0x24, 0xf5, 0xf9, 0xe9, 0xe9, 0x69, 0xee, 0x06, 0xfa, 0xa9, 0x19, 0x04, 0xb9, 0x3b, 0x5a, 0x16, 0xd3, 0x68, 0x36, + 0xc7, 0xe3, 0xdc, 0x65, 0xba, 0xe0, 0xb0, 0x3d, 0x0a, 0x0e, 0xdb, 0xb9, 0x7b, 0x63, 0xd4, 0xc8, 0x5d, 0xaa, 0x9e, + 0x52, 0x1a, 0x54, 0x52, 0x8b, 0x1e, 0x35, 0x9b, 0xb9, 0x2b, 0x09, 0x6d, 0x09, 0x66, 0xa9, 0xfc, 0xe9, 0xf9, 0x73, + 0x9e, 0x00, 0x73, 0xef, 0x44, 0xdc, 0x19, 0x5c, 0xaa, 0x6b, 0x5b, 0xe4, 0x47, 0x4e, 0x72, 0x34, 0xc4, 0xbf, 0x98, + 0xc1, 0x23, 0x20, 0x66, 0x11, 0x34, 0x8a, 0x74, 0x6c, 0xa9, 0xf2, 0x1a, 0x28, 0x4b, 0xbc, 0xfe, 0x85, 0x44, 0x65, + 0x4c, 0x09, 0x38, 0x19, 0xd4, 0x94, 0xb7, 0x0b, 0xc6, 0xbb, 0xe4, 0x47, 0xfa, 0x69, 0xf9, 0x71, 0xf7, 0x10, 0xf1, + 0x91, 0xfe, 0xe9, 0xe2, 0x23, 0x36, 0xc5, 0x87, 0x64, 0x1e, 0xd7, 0x9c, 0xd8, 0xa3, 0x90, 0x8e, 0x3e, 0x5e, 0x27, + 0xb7, 0x75, 0xd8, 0x12, 0xa9, 0x2d, 0x04, 0xcb, 0xfe, 0xef, 0xcd, 0x94, 0xd1, 0x9d, 0x19, 0x9f, 0x48, 0x11, 0xea, + 0xc3, 0xeb, 0x98, 0xd8, 0xaf, 0xb5, 0x6d, 0x2b, 0x4b, 0xc6, 0x63, 0x62, 0xbf, 0x1e, 0x8f, 0x6d, 0x7d, 0xf8, 0xd4, + 0xe7, 0x54, 0xd4, 0x7a, 0x55, 0x29, 0x11, 0xb5, 0xbe, 0xfa, 0xca, 0x2c, 0x33, 0x0b, 0x54, 0xe8, 0xc9, 0x0c, 0x33, + 0xa9, 0x37, 0x01, 0xcb, 0x60, 0xab, 0xc1, 0x97, 0x5b, 0xaa, 0x97, 0x5f, 0xc6, 0x95, 0x7b, 0xca, 0x0b, 0x80, 0xb7, + 0x5c, 0xae, 0xbe, 0x7e, 0xf3, 0xc2, 0x84, 0xea, 0x44, 0xd0, 0x27, 0x77, 0xdf, 0x04, 0xce, 0x35, 0x47, 0x39, 0xcb, + 0x5e, 0xc7, 0x6b, 0xa7, 0xaa, 0x24, 0x8c, 0x84, 0x98, 0xd3, 0xca, 0x79, 0x32, 0x99, 0x44, 0xf0, 0xf1, 0x9c, 0x65, + 0xe5, 0x42, 0x5e, 0xd9, 0xbc, 0x5f, 0x99, 0xaf, 0x67, 0x36, 0x54, 0xd7, 0xd7, 0x8a, 0x6f, 0x79, 0xc9, 0x6c, 0xfc, + 0x85, 0xfa, 0xa8, 0x93, 0x30, 0x8b, 0x97, 0x8a, 0xc9, 0x2f, 0x65, 0x0e, 0x37, 0xc7, 0x2c, 0x90, 0xcd, 0x59, 0x90, + 0xe7, 0xea, 0xf4, 0x4b, 0xc0, 0xb2, 0x19, 0x5c, 0x14, 0x2b, 0x5b, 0xd2, 0x4f, 0xb1, 0xf0, 0xec, 0xc6, 0x88, 0xef, + 0x54, 0x96, 0x2b, 0xd7, 0x01, 0x1e, 0xe9, 0x30, 0xbf, 0xe6, 0xb9, 0xad, 0xfc, 0xee, 0x1a, 0x89, 0xb6, 0x25, 0xf1, + 0x29, 0x23, 0x4f, 0xc6, 0x0c, 0xc1, 0xf9, 0x5d, 0x2c, 0x88, 0x7e, 0xa5, 0x0b, 0x72, 0x33, 0x7e, 0x29, 0xde, 0x48, + 0x6c, 0x89, 0x68, 0x49, 0x36, 0xf3, 0x63, 0xc9, 0x46, 0x89, 0x2d, 0xf9, 0xc1, 0xfe, 0xb2, 0x5c, 0xf9, 0xdc, 0xd6, + 0x60, 0x4b, 0xe2, 0xed, 0x75, 0x1b, 0xd0, 0xa0, 0x67, 0x55, 0x40, 0x8f, 0x37, 0x82, 0x2c, 0xf7, 0xa7, 0x3b, 0xbc, + 0xbe, 0x72, 0xb3, 0x1b, 0xec, 0x66, 0x37, 0xd6, 0x5f, 0x97, 0xf5, 0x1b, 0x7a, 0xfd, 0x91, 0xf1, 0x3a, 0xf7, 0x67, + 0x75, 0x30, 0x7c, 0x84, 0x73, 0x54, 0xb1, 0x67, 0x91, 0x36, 0x29, 0xef, 0x8e, 0xe8, 0xcc, 0x33, 0xc8, 0x8a, 0x10, + 0xea, 0xbb, 0x17, 0x27, 0x31, 0xed, 0x54, 0xd3, 0x63, 0xcd, 0x20, 0xbb, 0xc6, 0xd6, 0x70, 0x99, 0x40, 0x16, 0x05, + 0xbf, 0xf3, 0x9a, 0x8a, 0xad, 0x37, 0x75, 0x04, 0xbd, 0xb9, 0xb5, 0xbe, 0xa7, 0x90, 0x5b, 0x13, 0xd2, 0x2b, 0xdd, + 0xcc, 0x24, 0xd8, 0x95, 0x09, 0xf0, 0xa9, 0x64, 0x51, 0x70, 0xa9, 0xea, 0xbf, 0x46, 0x96, 0xed, 0x7a, 0xb1, 0x48, + 0x16, 0x7d, 0x08, 0x64, 0x9e, 0x3f, 0xe6, 0x34, 0xc5, 0x0f, 0xa9, 0x79, 0x2d, 0xce, 0x75, 0x2d, 0x41, 0xcc, 0x78, + 0xad, 0xd3, 0xd9, 0xed, 0xc3, 0xbb, 0xbf, 0x7f, 0xfa, 0xb9, 0xc2, 0x91, 0xbe, 0xe7, 0xc8, 0xb6, 0x3b, 0xb0, 0x11, + 0x22, 0xff, 0xce, 0x63, 0xb1, 0x90, 0x79, 0xd7, 0xe0, 0x17, 0xed, 0xcc, 0x12, 0x95, 0xf5, 0x9c, 0xd2, 0x48, 0x7c, + 0xd6, 0x50, 0x2d, 0xc5, 0xe1, 0xc9, 0xec, 0x56, 0xaf, 0x46, 0x6b, 0x2d, 0x9b, 0xf9, 0x4f, 0x4d, 0x5a, 0xde, 0x9d, + 0x25, 0x5d, 0x4d, 0xbc, 0x3d, 0x9e, 0xdd, 0x76, 0xa4, 0xa0, 0xad, 0xa7, 0x12, 0xaa, 0xe6, 0xec, 0xd6, 0x4c, 0xdb, + 0x2e, 0x3b, 0xb2, 0xdc, 0xc3, 0xcc, 0xa2, 0x7e, 0x46, 0x3b, 0x70, 0x91, 0x3b, 0x1b, 0xf9, 0x91, 0x12, 0xe6, 0x53, + 0x16, 0x04, 0x11, 0xed, 0x68, 0x79, 0x6d, 0xb5, 0x4e, 0x20, 0xeb, 0xd9, 0x5c, 0xb2, 0xea, 0xaa, 0x18, 0xc8, 0x2b, + 0xf0, 0xe4, 0x5f, 0x67, 0x49, 0x04, 0x5f, 0x51, 0xd9, 0x8a, 0x4e, 0x95, 0x0e, 0xdc, 0x2c, 0x91, 0x27, 0x7e, 0x57, + 0xe7, 0x72, 0xdc, 0xfc, 0x4b, 0x47, 0x2c, 0x78, 0xb3, 0xc3, 0x93, 0x99, 0x57, 0x3f, 0xac, 0x4e, 0x04, 0x5e, 0x15, + 0x53, 0xc0, 0x5b, 0xa6, 0x85, 0x41, 0x5a, 0x49, 0x3e, 0x6d, 0xb9, 0x2d, 0x55, 0x26, 0x3a, 0x80, 0xb4, 0xb1, 0xa2, + 0x28, 0xaf, 0x4e, 0xe6, 0xdf, 0x66, 0xb7, 0x3c, 0xde, 0xbe, 0x5b, 0x1e, 0xeb, 0xdd, 0x72, 0x3f, 0xc5, 0x7e, 0x3e, + 0x6e, 0xc1, 0x9f, 0x4e, 0x39, 0x21, 0xaf, 0x69, 0x1d, 0xce, 0x6e, 0x2d, 0xd0, 0xd3, 0xea, 0xed, 0xd9, 0xad, 0x4c, + 0x5a, 0x87, 0xd8, 0x4d, 0x13, 0xd2, 0xb8, 0x71, 0xd3, 0x82, 0x42, 0xf8, 0xdb, 0xac, 0xbc, 0x6a, 0x1d, 0xc1, 0x3b, + 0x68, 0x75, 0xbc, 0xf9, 0xae, 0x7d, 0xff, 0xa6, 0xf5, 0xe2, 0x84, 0x3b, 0x9e, 0xe6, 0xc6, 0xc8, 0xe5, 0xfe, 0xf5, + 0x35, 0x0d, 0xbc, 0x71, 0x32, 0x9a, 0x67, 0xff, 0xa4, 0xe0, 0x57, 0x48, 0xbc, 0x77, 0x4b, 0xaf, 0xf5, 0xa3, 0x9b, + 0xca, 0x14, 0x7a, 0xdd, 0xc3, 0xb2, 0x58, 0x27, 0x2f, 0x1b, 0xf9, 0x11, 0x75, 0xda, 0xee, 0xd1, 0x96, 0x4d, 0xf0, + 0xef, 0xb2, 0x36, 0x5b, 0x27, 0xf3, 0x47, 0x91, 0x71, 0x2f, 0x12, 0x7e, 0x13, 0x0e, 0xcc, 0x35, 0x6c, 0x9e, 0x6e, + 0x07, 0x77, 0xa0, 0x47, 0x1a, 0x6a, 0xa1, 0xa0, 0xe4, 0x4e, 0x40, 0xc7, 0xfe, 0x3c, 0xe2, 0xf7, 0xf7, 0xba, 0x8b, + 0x32, 0x36, 0x7a, 0xbd, 0x87, 0xa1, 0x97, 0x75, 0x1f, 0xc8, 0xa5, 0x3f, 0x7f, 0x7c, 0x04, 0x7f, 0x64, 0xfe, 0xd7, + 0x5d, 0xa9, 0xab, 0x4b, 0xbb, 0x17, 0x74, 0xf5, 0xfd, 0x8a, 0x32, 0x2e, 0x45, 0xb8, 0xd0, 0xc7, 0x1f, 0x5a, 0x1b, + 0xb4, 0xca, 0x07, 0x55, 0x57, 0x5a, 0xd6, 0x6f, 0xaa, 0xfd, 0xdb, 0x3a, 0x7f, 0x60, 0xdd, 0x91, 0xd4, 0x5c, 0xab, + 0x75, 0xd5, 0x77, 0x1d, 0x37, 0x2a, 0x6b, 0x8c, 0x8b, 0xfa, 0xfb, 0xe4, 0xae, 0x30, 0x51, 0x64, 0x34, 0x16, 0xac, + 0x94, 0x7d, 0x69, 0xa5, 0x24, 0x94, 0x5c, 0x75, 0xfb, 0xb7, 0xd3, 0xc8, 0x5a, 0xc8, 0xf3, 0xa7, 0xc4, 0x6e, 0xb9, + 0x4d, 0xdb, 0x12, 0x79, 0x00, 0x70, 0x0d, 0xbe, 0x2d, 0xbe, 0x17, 0x6c, 0xf7, 0x41, 0xd3, 0x5a, 0x4c, 0x84, 0x66, + 0xf7, 0xc2, 0xbf, 0xa3, 0xe9, 0x65, 0xdb, 0xb6, 0xc0, 0x4f, 0x53, 0x97, 0x29, 0x13, 0xa2, 0xcc, 0x6a, 0xdb, 0xd6, + 0xed, 0x34, 0x8a, 0x33, 0x62, 0x87, 0x9c, 0xcf, 0x3c, 0xf9, 0x41, 0xe1, 0x9b, 0x43, 0x37, 0x49, 0x27, 0x8d, 0x76, + 0xb3, 0xd9, 0x84, 0x1b, 0x75, 0x6d, 0x6b, 0xc1, 0xe8, 0xcd, 0x93, 0xe4, 0x96, 0xd8, 0x4d, 0xab, 0x69, 0xb5, 0xda, + 0xa7, 0x56, 0xab, 0x7d, 0xe4, 0x9e, 0x9c, 0xda, 0xbd, 0xcf, 0x2c, 0xab, 0x1b, 0xd0, 0x71, 0x06, 0x3f, 0x2c, 0xab, + 0x2b, 0x14, 0x2f, 0xf9, 0xdb, 0xb2, 0xdc, 0x51, 0x94, 0xd5, 0x5b, 0xd6, 0x52, 0x3d, 0x5a, 0x16, 0x9c, 0xd2, 0xf5, + 0xac, 0xcf, 0xc7, 0xed, 0xf1, 0xd1, 0xf8, 0x71, 0x47, 0x15, 0xe7, 0x9f, 0x55, 0xaa, 0x63, 0xf9, 0x7f, 0xdb, 0x68, + 0x96, 0xf1, 0x34, 0xf9, 0x48, 0x55, 0x4e, 0xa2, 0x05, 0xa2, 0x67, 0x6b, 0xd3, 0xf6, 0xe6, 0x48, 0xad, 0xd3, 0xeb, + 0xd1, 0xb8, 0x5d, 0x56, 0x17, 0x30, 0x36, 0x0a, 0x20, 0xbb, 0x0d, 0x0d, 0x7a, 0xd7, 0x44, 0x53, 0xab, 0xbe, 0x0d, + 0x51, 0x2d, 0x5b, 0xcd, 0x71, 0xa2, 0xe7, 0xd7, 0x85, 0x43, 0x21, 0x5a, 0x57, 0x15, 0x10, 0xd8, 0x56, 0x40, 0xec, + 0x97, 0xad, 0xf6, 0x29, 0x6e, 0xb5, 0x4e, 0xdc, 0x93, 0xd3, 0x51, 0x13, 0x1f, 0xb9, 0x47, 0xf5, 0x43, 0xf7, 0x04, + 0x9f, 0xd6, 0x4f, 0xf1, 0xe9, 0xf3, 0xd3, 0x51, 0xfd, 0xc8, 0x3d, 0xc2, 0xcd, 0xfa, 0x29, 0x14, 0xd6, 0x4f, 0xeb, + 0xa7, 0x8b, 0xfa, 0xd1, 0xe9, 0xa8, 0x29, 0x4a, 0xdb, 0xee, 0xf1, 0x71, 0xbd, 0xd5, 0x74, 0x8f, 0x8f, 0xf1, 0xb1, + 0x7b, 0x72, 0x52, 0x6f, 0x1d, 0xba, 0x27, 0x27, 0x2f, 0x8e, 0x4f, 0xdd, 0x43, 0x78, 0x77, 0x78, 0x38, 0x3a, 0x74, + 0x5b, 0xad, 0x3a, 0xfc, 0x83, 0x4f, 0xdd, 0xb6, 0xfc, 0xd1, 0x6a, 0xb9, 0x87, 0x2d, 0xdc, 0x8c, 0x8e, 0xdb, 0xee, + 0xc9, 0x63, 0x2c, 0xfe, 0x15, 0xd5, 0xb0, 0xf8, 0x07, 0xba, 0xc1, 0x8f, 0xdd, 0xf6, 0x89, 0xfc, 0x25, 0x3a, 0x5c, + 0x1c, 0x9d, 0xfe, 0x64, 0x37, 0x76, 0xce, 0xa1, 0x25, 0xe7, 0x70, 0x7a, 0xec, 0x1e, 0x1e, 0xe2, 0xa3, 0x96, 0x7b, + 0x7a, 0x18, 0xd6, 0x8f, 0xda, 0xee, 0xc9, 0xa3, 0x51, 0xbd, 0xe5, 0x3e, 0x7a, 0x84, 0x9b, 0xf5, 0x43, 0xb7, 0x8d, + 0x5b, 0xee, 0xd1, 0xa1, 0xf8, 0x71, 0xe8, 0xb6, 0x17, 0x8f, 0x1e, 0xbb, 0x27, 0xc7, 0xe1, 0x89, 0x7b, 0xf4, 0xfd, + 0xd1, 0xa9, 0xdb, 0x3e, 0x0c, 0x0f, 0x4f, 0xdc, 0xf6, 0xa3, 0xc5, 0x89, 0x7b, 0x14, 0xd6, 0xdb, 0x27, 0xf7, 0xb6, + 0x6c, 0xb5, 0x5d, 0xc0, 0x91, 0x78, 0x0d, 0x2f, 0xb0, 0x7a, 0x01, 0x7f, 0x43, 0xd1, 0xf6, 0xdf, 0xb1, 0x9b, 0x6c, + 0xb3, 0xe9, 0x63, 0xf7, 0xf4, 0xd1, 0x48, 0x56, 0x87, 0x82, 0xba, 0xae, 0x01, 0x4d, 0x16, 0x75, 0x39, 0xac, 0xe8, + 0xae, 0xae, 0x3b, 0xd2, 0x7f, 0xd5, 0x60, 0x8b, 0x3a, 0x0c, 0x2c, 0xc7, 0xfd, 0x0f, 0xed, 0xa7, 0x58, 0xf2, 0x6e, + 0x63, 0x22, 0x49, 0x7f, 0xd2, 0xfb, 0x4c, 0x5e, 0x97, 0xfd, 0xd9, 0x15, 0x8e, 0x76, 0x39, 0x3e, 0xfc, 0x4f, 0x3b, + 0x3e, 0x42, 0xfa, 0x10, 0xcf, 0x87, 0xff, 0xa7, 0x7b, 0x3e, 0xa2, 0x75, 0xc7, 0xf9, 0x0d, 0xdf, 0x70, 0x70, 0xac, + 0x5b, 0xc5, 0x2f, 0xb8, 0x33, 0x48, 0xe0, 0xc3, 0x6c, 0x79, 0xe7, 0x86, 0x93, 0x90, 0x9a, 0x7e, 0xa0, 0x04, 0x58, + 0xec, 0x0d, 0x97, 0x3c, 0x76, 0xb4, 0x0b, 0x21, 0xc1, 0xa7, 0x11, 0xf2, 0xfd, 0x43, 0xf0, 0x11, 0xfc, 0xe9, 0xf8, + 0x18, 0x99, 0xf8, 0xa8, 0xf8, 0xf2, 0x85, 0xa7, 0x41, 0x78, 0x0a, 0x2e, 0xc4, 0xb3, 0x03, 0xa7, 0xd2, 0x6a, 0x76, + 0x83, 0x42, 0x51, 0x66, 0xcb, 0xc8, 0xd7, 0xdb, 0xdf, 0x12, 0x76, 0x90, 0x47, 0x50, 0x89, 0xad, 0xdc, 0x32, 0x33, + 0x21, 0x75, 0xd4, 0x43, 0x21, 0x94, 0xda, 0x6e, 0xd3, 0x6d, 0x16, 0x2e, 0x1d, 0x38, 0x76, 0x4c, 0x96, 0x09, 0xf7, + 0xe1, 0x13, 0xc0, 0x51, 0x32, 0x11, 0x1f, 0x0b, 0x86, 0xcf, 0x33, 0x40, 0xd2, 0xcf, 0x48, 0x7e, 0x19, 0x03, 0xce, + 0x4d, 0x28, 0x47, 0x8f, 0x9f, 0x7e, 0xfc, 0x0e, 0x8e, 0xfe, 0xea, 0xa8, 0xc4, 0x14, 0xbc, 0x1d, 0x2f, 0x69, 0xc0, + 0x7c, 0xc7, 0x76, 0x66, 0x29, 0x1d, 0xd3, 0x34, 0xab, 0x57, 0xce, 0xc3, 0x8a, 0xa3, 0xb0, 0xc8, 0xd6, 0xdf, 0x9a, + 0x4d, 0xe1, 0xba, 0x71, 0x32, 0x50, 0xfe, 0x46, 0x5b, 0x19, 0x60, 0x76, 0x8e, 0x75, 0x49, 0x0a, 0xb2, 0xb6, 0x54, + 0xda, 0x6c, 0xa9, 0xb5, 0xb5, 0xdc, 0xf6, 0x31, 0xb2, 0x44, 0x31, 0x5c, 0xe4, 0xfc, 0xa3, 0x53, 0x3f, 0x6c, 0xfe, + 0x05, 0x19, 0xcd, 0x8a, 0x8e, 0x86, 0xca, 0xdd, 0x16, 0x97, 0x1f, 0xe9, 0xae, 0x1e, 0x56, 0xb6, 0x25, 0x45, 0x7c, + 0x2e, 0xe7, 0x6e, 0xa3, 0x4e, 0xac, 0x22, 0xdc, 0xf2, 0xca, 0x8d, 0x31, 0x9b, 0x38, 0xe6, 0x27, 0x98, 0xe5, 0x45, + 0xd1, 0xe2, 0xcb, 0xed, 0x28, 0x2f, 0xab, 0xc4, 0x68, 0x29, 0xe2, 0x2d, 0x2c, 0xb6, 0xe2, 0xd5, 0xca, 0x89, 0xc1, + 0x45, 0x4e, 0x0c, 0x9c, 0xc2, 0x33, 0xaa, 0x20, 0x39, 0xc6, 0x05, 0x40, 0x02, 0xc1, 0x24, 0x96, 0xff, 0x97, 0xc5, + 0xfa, 0x87, 0x72, 0x7c, 0xb9, 0x91, 0x1f, 0x4f, 0x80, 0x0a, 0xfd, 0x78, 0xb2, 0xe1, 0x56, 0x93, 0x21, 0xa3, 0xb5, + 0xd2, 0xb2, 0xab, 0xd2, 0x7d, 0x96, 0x3d, 0xb9, 0x7b, 0xa7, 0x6e, 0x94, 0xb3, 0xc1, 0x3b, 0x2d, 0x22, 0x1c, 0xe5, + 0xed, 0xd7, 0x35, 0xf2, 0x45, 0x77, 0x4a, 0xb9, 0x2f, 0xf3, 0x35, 0x41, 0x9f, 0x80, 0x63, 0xc8, 0x96, 0xb2, 0x46, + 0x89, 0x2d, 0xa4, 0x3b, 0x91, 0x67, 0x68, 0xa4, 0xa8, 0xc7, 0x96, 0xba, 0x8a, 0xa1, 0x2e, 0x96, 0x86, 0xb4, 0xb0, + 0xf4, 0xc7, 0x8c, 0x7c, 0x91, 0x91, 0x4f, 0xe2, 0xc4, 0xee, 0x7d, 0x51, 0x7c, 0x4e, 0x76, 0xd7, 0x22, 0x44, 0x2c, + 0xfe, 0x38, 0x48, 0x69, 0xf4, 0x4f, 0xe4, 0x0b, 0x36, 0x4a, 0xe2, 0x2f, 0x86, 0x36, 0xea, 0x70, 0x37, 0x4c, 0xe9, + 0x98, 0x7c, 0x01, 0x32, 0xde, 0x13, 0xd6, 0x07, 0x30, 0xc2, 0xda, 0xed, 0x34, 0xc2, 0x42, 0x63, 0x7a, 0x80, 0x42, + 0x24, 0xc1, 0xb5, 0xdb, 0xc7, 0xb6, 0x25, 0x6d, 0x62, 0xf1, 0xbb, 0x27, 0xc5, 0xa9, 0x50, 0x02, 0xac, 0x56, 0xdb, + 0x3d, 0x0e, 0xdb, 0xee, 0xe3, 0xc5, 0x23, 0xf7, 0x34, 0x6c, 0x3d, 0x5a, 0xd4, 0xe1, 0xff, 0xb6, 0xfb, 0x38, 0xaa, + 0xb7, 0xdd, 0xc7, 0xf0, 0xf7, 0xfb, 0x23, 0xf7, 0x38, 0xac, 0xb7, 0xdc, 0xd3, 0xc5, 0xa1, 0x7b, 0xf8, 0xa2, 0xd5, + 0x76, 0x0f, 0xad, 0x96, 0x25, 0xdb, 0x01, 0xbb, 0x96, 0xdc, 0xf9, 0x8b, 0xb5, 0x0d, 0xb1, 0x25, 0x1c, 0x27, 0x73, + 0x4e, 0x6d, 0xec, 0x14, 0x1f, 0xad, 0x54, 0xfb, 0x53, 0x39, 0xeb, 0x9e, 0xfa, 0x29, 0x7c, 0x39, 0xa8, 0xba, 0x77, + 0x2b, 0xef, 0x70, 0x85, 0x5f, 0x6c, 0x19, 0x02, 0x76, 0xb8, 0x8d, 0xcd, 0xbb, 0x0c, 0xe0, 0x22, 0x00, 0x71, 0xd1, + 0xba, 0xbe, 0x6f, 0x72, 0x37, 0x69, 0xcb, 0x8a, 0xfa, 0x4e, 0x4b, 0xc1, 0x2c, 0x98, 0xf8, 0xa4, 0x85, 0x18, 0xe4, + 0x9b, 0x20, 0x5f, 0x1f, 0x1f, 0x52, 0x5f, 0xd3, 0xc4, 0xb8, 0xce, 0x81, 0x96, 0x07, 0x36, 0x02, 0x06, 0x17, 0x70, + 0xe4, 0xb9, 0x06, 0xbd, 0xe2, 0xa6, 0x2d, 0xb1, 0x24, 0xf8, 0x05, 0xcd, 0xfa, 0x36, 0x14, 0xd9, 0x9e, 0x2d, 0x5c, + 0x7c, 0x76, 0xf1, 0xf5, 0xa4, 0x82, 0xb0, 0xcb, 0x02, 0x2c, 0x0e, 0x5d, 0xc1, 0xae, 0x05, 0xfc, 0xd8, 0xe8, 0xe0, + 0x60, 0xe7, 0x7e, 0x11, 0x0a, 0x24, 0xcc, 0xb5, 0xfc, 0xe8, 0x8a, 0xc9, 0x8a, 0x6c, 0x13, 0xd1, 0x45, 0xbf, 0x02, + 0x85, 0x48, 0xe1, 0xe9, 0x9a, 0xfa, 0xdc, 0xf5, 0x63, 0x99, 0x44, 0x63, 0x30, 0x2c, 0xdc, 0xa2, 0x87, 0x28, 0x4f, + 0xb8, 0x6f, 0x7c, 0x58, 0x59, 0xed, 0xf3, 0x84, 0xfb, 0xfa, 0x70, 0xb2, 0x71, 0x0f, 0x13, 0x38, 0x7a, 0xc3, 0x76, + 0xef, 0xf5, 0xbb, 0x33, 0x4b, 0x6e, 0xcf, 0x6e, 0x23, 0x6c, 0xf7, 0xba, 0xc2, 0x67, 0x22, 0x0f, 0xea, 0x11, 0x79, + 0x50, 0xcf, 0x52, 0x67, 0x33, 0x21, 0x92, 0x96, 0x37, 0xe4, 0xb4, 0x85, 0xcd, 0x20, 0xbd, 0xbd, 0xd3, 0x79, 0xc4, + 0x19, 0x5c, 0x1a, 0xde, 0x10, 0xa7, 0xf4, 0x60, 0xc1, 0x8a, 0x3c, 0x6c, 0xa5, 0x1d, 0x5e, 0xf3, 0x58, 0xfb, 0x86, + 0xc7, 0x2c, 0xa2, 0x3a, 0xf3, 0x5a, 0x75, 0x55, 0x9c, 0x14, 0xd8, 0xac, 0x9d, 0xcd, 0xaf, 0xa7, 0x8c, 0xdb, 0xfa, + 0x3c, 0xc3, 0x7b, 0xd5, 0xa0, 0x2b, 0x86, 0xea, 0x5d, 0xe5, 0xca, 0x79, 0xad, 0x3f, 0x8f, 0x54, 0x5d, 0x52, 0x35, + 0x7b, 0x25, 0x21, 0xe0, 0x84, 0x5c, 0x78, 0xd8, 0x2b, 0xdc, 0xc5, 0xe6, 0xbb, 0xbc, 0xdb, 0x08, 0x0f, 0x7b, 0x57, + 0xde, 0x4c, 0xf5, 0xf7, 0x22, 0x99, 0x6c, 0xef, 0x2b, 0x4a, 0x26, 0x7d, 0x71, 0x14, 0x44, 0x9e, 0x99, 0xd6, 0xca, + 0x6f, 0x12, 0xd9, 0xbd, 0xae, 0x52, 0x06, 0x2c, 0x11, 0x58, 0xb7, 0x8f, 0x9b, 0xfa, 0x74, 0x49, 0x94, 0x4c, 0x60, + 0x43, 0xca, 0x26, 0xc6, 0x20, 0x15, 0x8f, 0x7b, 0xd8, 0xea, 0x75, 0x7d, 0x4b, 0xf0, 0x16, 0xc1, 0x3c, 0x32, 0xaf, + 0x01, 0x8d, 0xc3, 0x64, 0x4a, 0x5d, 0x96, 0x34, 0x6e, 0xe8, 0x75, 0xdd, 0x9f, 0xb1, 0xd2, 0xbd, 0x0d, 0x4a, 0x47, + 0x31, 0x64, 0xa2, 0x3d, 0xe2, 0xea, 0xec, 0x55, 0xbb, 0x74, 0xb7, 0x1d, 0x81, 0xcd, 0xa3, 0x5d, 0x73, 0xc2, 0x27, + 0x67, 0x80, 0x95, 0xf4, 0xba, 0x0d, 0x7f, 0x0d, 0x23, 0x82, 0xdf, 0xe7, 0xca, 0xd1, 0x0e, 0x86, 0x0d, 0xd0, 0x9b, + 0x6d, 0x49, 0x71, 0xa0, 0x1d, 0xf2, 0x4a, 0x50, 0xe7, 0x76, 0xef, 0x5f, 0xff, 0xc7, 0xff, 0x52, 0x3e, 0xf6, 0x6e, + 0x23, 0x6c, 0xe9, 0xbe, 0xd6, 0x56, 0x25, 0xef, 0xc2, 0xf9, 0xd0, 0x32, 0x28, 0x4c, 0x6f, 0xeb, 0x93, 0x94, 0x05, + 0xf5, 0xd0, 0x8f, 0xc6, 0x76, 0x6f, 0x37, 0x36, 0xcd, 0x63, 0x5b, 0x0a, 0xea, 0x6a, 0x11, 0xd0, 0xeb, 0xef, 0x3a, + 0x78, 0xa4, 0xcf, 0xaf, 0x88, 0xad, 0x6d, 0x1e, 0x43, 0x2a, 0x77, 0x5f, 0xe5, 0x28, 0x52, 0xac, 0xbe, 0xb9, 0xa6, + 0x38, 0x60, 0x5c, 0x39, 0x81, 0x94, 0xdb, 0x56, 0x11, 0xd4, 0xfa, 0xbf, 0xff, 0xf3, 0xbf, 0xfc, 0x37, 0xfd, 0x08, + 0xb1, 0xaa, 0x7f, 0xfd, 0xef, 0xff, 0xf9, 0xff, 0xfc, 0xef, 0xff, 0x0a, 0xa7, 0x56, 0x54, 0x3c, 0x4b, 0x30, 0x15, + 0xab, 0x0c, 0x66, 0x49, 0xee, 0x62, 0x41, 0x62, 0xe7, 0x94, 0x65, 0x9c, 0x8d, 0xaa, 0x67, 0x92, 0x2e, 0xc4, 0x80, + 0x62, 0x67, 0x2a, 0xe8, 0xc4, 0x0e, 0xcf, 0x4b, 0x82, 0xaa, 0xa0, 0x5c, 0x10, 0x6e, 0xde, 0x6d, 0x00, 0xbe, 0x1f, + 0x76, 0x8c, 0xd3, 0x2d, 0x96, 0x63, 0xa9, 0xc9, 0x04, 0x4a, 0xf2, 0xb2, 0xdc, 0x82, 0xd8, 0xca, 0x12, 0x1e, 0xbd, + 0xb6, 0x51, 0x2c, 0x56, 0xaf, 0xd2, 0xa6, 0xf3, 0x61, 0x9e, 0x71, 0x36, 0x06, 0x94, 0x4b, 0x3f, 0xb1, 0x08, 0x63, + 0xd7, 0x41, 0x57, 0x8c, 0xee, 0x72, 0xd1, 0x8b, 0x24, 0xd0, 0xa3, 0xd3, 0xbf, 0xe4, 0x5f, 0x4e, 0x41, 0x23, 0xb3, + 0x9c, 0xa9, 0x7f, 0xab, 0xcc, 0xf3, 0x93, 0x66, 0x73, 0x76, 0x8b, 0x96, 0xe5, 0x08, 0x78, 0xd7, 0x60, 0x82, 0x8e, + 0xcd, 0x0e, 0x45, 0xfc, 0xbb, 0x70, 0x63, 0x37, 0x2d, 0xf0, 0x85, 0x5b, 0xcd, 0x3c, 0xff, 0xeb, 0x52, 0x78, 0x52, + 0xd9, 0xaf, 0x10, 0xa7, 0x56, 0x4e, 0xe7, 0xeb, 0xc4, 0x9c, 0xdc, 0xd2, 0x68, 0xd5, 0x96, 0xad, 0xc2, 0xd6, 0xe6, + 0xe9, 0x44, 0x33, 0xce, 0x6e, 0x46, 0xc8, 0x8f, 0x20, 0xe6, 0x1d, 0xb6, 0x70, 0xd8, 0x5e, 0x16, 0xdd, 0x73, 0x9e, + 0x4c, 0xcd, 0xc0, 0x3a, 0xf5, 0xe9, 0x88, 0x8e, 0xb5, 0xb3, 0x5e, 0xbd, 0x97, 0x41, 0xf3, 0x3c, 0x3c, 0xdc, 0x32, + 0x96, 0x02, 0x49, 0x04, 0xd4, 0xad, 0x66, 0xfe, 0x39, 0xec, 0xc0, 0xe5, 0x38, 0x4a, 0x7c, 0xee, 0x09, 0x82, 0xed, + 0x98, 0xe1, 0x79, 0x1f, 0x78, 0x52, 0xb2, 0x34, 0xe0, 0xe9, 0xc8, 0xaa, 0xe0, 0x36, 0xaf, 0x9e, 0x21, 0xcd, 0x5d, + 0xd1, 0xdc, 0xec, 0x4a, 0x7a, 0xdd, 0xbe, 0x57, 0x51, 0xef, 0xb7, 0x15, 0x77, 0x95, 0x12, 0x48, 0x6d, 0xb4, 0xfd, + 0xbd, 0x94, 0xeb, 0xf2, 0xed, 0x77, 0xdc, 0xb1, 0x05, 0x98, 0xf6, 0x7a, 0x2d, 0x51, 0x08, 0xb5, 0xde, 0x92, 0xef, + 0x0b, 0x93, 0xc9, 0x9f, 0xcd, 0x44, 0x45, 0xd4, 0xe9, 0x36, 0xa4, 0xa6, 0x0b, 0xdc, 0x43, 0xa4, 0x74, 0xc8, 0x0c, + 0x0a, 0x55, 0x49, 0x6d, 0x05, 0xf9, 0x4b, 0xe5, 0x56, 0xc0, 0xb7, 0xf8, 0x7a, 0xff, 0x0f, 0x85, 0xa3, 0x0b, 0x12, + 0x20, 0x8b, 0x00, 0x00}; -} // namespace esphome::web_server +} // namespace web_server +} // namespace esphome #endif #endif diff --git a/esphome/components/web_server/server_index_v3.h b/esphome/components/web_server/server_index_v3.h index 8a8ced9153..725bdc34e3 100644 --- a/esphome/components/web_server/server_index_v3.h +++ b/esphome/components/web_server/server_index_v3.h @@ -6,4048 +6,4058 @@ #include "esphome/core/hal.h" -namespace esphome::web_server { +namespace esphome { +namespace web_server { const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcc, 0xbd, 0xeb, 0x7a, 0x1b, 0xb7, 0xb2, 0x20, 0xfa, - 0xfb, 0xcc, 0x53, 0x48, 0xbd, 0x1d, 0xa5, 0x21, 0x82, 0x2d, 0x92, 0xba, 0x58, 0x6e, 0x0a, 0xe2, 0xf8, 0x1a, 0x3b, - 0x71, 0x6c, 0xc7, 0x72, 0xec, 0x38, 0x0c, 0xb7, 0x0c, 0x36, 0x41, 0x12, 0x76, 0x13, 0x60, 0x1a, 0xa0, 0x25, 0x85, - 0xe4, 0xbb, 0x9f, 0xaf, 0x70, 0xe9, 0x46, 0x93, 0xb4, 0xd7, 0x5a, 0x73, 0x66, 0xce, 0x37, 0x3b, 0x7b, 0x59, 0x6c, - 0xdc, 0x51, 0x28, 0x14, 0xaa, 0x0a, 0x55, 0x85, 0x8b, 0xfd, 0x91, 0xcc, 0xf4, 0xdd, 0x9c, 0xed, 0x4d, 0xf5, 0x2c, - 0xbf, 0xbc, 0x70, 0xff, 0x32, 0x3a, 0xba, 0xbc, 0xc8, 0xb9, 0xf8, 0xb2, 0x57, 0xb0, 0x9c, 0xf0, 0x4c, 0x8a, 0xbd, - 0x69, 0xc1, 0xc6, 0x64, 0x44, 0x35, 0x4d, 0xf9, 0x8c, 0x4e, 0xd8, 0xde, 0xd1, 0xe5, 0xc5, 0x8c, 0x69, 0xba, 0x97, - 0x4d, 0x69, 0xa1, 0x98, 0x26, 0xbf, 0xbf, 0x7b, 0xd6, 0x3c, 0xbf, 0xbc, 0x50, 0x59, 0xc1, 0xe7, 0x7a, 0x0f, 0x9a, - 0x24, 0x33, 0x39, 0x5a, 0xe4, 0xec, 0xf2, 0xe8, 0xe8, 0xe6, 0xe6, 0x26, 0xf9, 0xac, 0xfe, 0xc7, 0x57, 0x5a, 0xec, - 0xfd, 0x52, 0x90, 0xd7, 0xc3, 0xcf, 0x2c, 0xd3, 0xc9, 0x88, 0x8d, 0xb9, 0x60, 0x6f, 0x0a, 0x39, 0x67, 0x85, 0xbe, - 0xeb, 0x42, 0xe6, 0x4f, 0x05, 0x89, 0x39, 0xd6, 0x98, 0x21, 0x72, 0xa9, 0xf7, 0xb8, 0xd8, 0xe3, 0xbd, 0x5f, 0x0a, - 0x93, 0xb2, 0x64, 0x62, 0x31, 0x63, 0x05, 0x1d, 0xe6, 0x2c, 0xdd, 0x6f, 0xe1, 0x4c, 0x8a, 0x31, 0x9f, 0x2c, 0xca, - 0xef, 0x9b, 0x82, 0x6b, 0xff, 0xfb, 0x2b, 0xcd, 0x17, 0x2c, 0x65, 0x6b, 0x94, 0xf2, 0xbe, 0x1e, 0x10, 0x66, 0x5a, - 0xfe, 0x52, 0x35, 0x1c, 0xff, 0x64, 0x9a, 0xbc, 0x9b, 0x33, 0x39, 0xde, 0xd3, 0xfb, 0x24, 0x52, 0x77, 0xb3, 0xa1, - 0xcc, 0xa3, 0x9e, 0x6e, 0x44, 0x51, 0x0a, 0x65, 0x30, 0x43, 0xdd, 0x4c, 0x0a, 0xa5, 0xf7, 0x04, 0x27, 0x37, 0x5c, - 0x8c, 0xe4, 0x0d, 0xbe, 0x11, 0x44, 0xf0, 0xe4, 0x6a, 0x4a, 0x47, 0xf2, 0xe6, 0xad, 0x94, 0xfa, 0xe0, 0x20, 0x76, - 0xdf, 0x77, 0x8f, 0xaf, 0xae, 0x08, 0x21, 0x5f, 0x25, 0x1f, 0xed, 0xb5, 0x56, 0xab, 0x20, 0x35, 0x11, 0x54, 0xf3, - 0xaf, 0xcc, 0x56, 0x42, 0x07, 0x07, 0x11, 0x1d, 0xc9, 0xb9, 0x66, 0xa3, 0x2b, 0x7d, 0x97, 0xb3, 0xab, 0x29, 0x63, - 0x5a, 0x45, 0x5c, 0xec, 0x3d, 0x91, 0xd9, 0x62, 0xc6, 0x84, 0x4e, 0xe6, 0x85, 0xd4, 0x12, 0x06, 0x76, 0x70, 0x10, - 0x15, 0x6c, 0x9e, 0xd3, 0x8c, 0x41, 0xfe, 0xe3, 0xab, 0xab, 0xaa, 0x46, 0x55, 0x08, 0x7f, 0x11, 0xe4, 0xca, 0x0c, - 0x3d, 0x46, 0xf8, 0x83, 0x20, 0x82, 0xdd, 0xec, 0x7d, 0x60, 0xf4, 0xcb, 0xaf, 0x74, 0xde, 0xcd, 0x72, 0xaa, 0xd4, - 0xde, 0x33, 0xb9, 0x34, 0xd3, 0x28, 0x16, 0x99, 0x96, 0x45, 0xac, 0x31, 0xc3, 0x02, 0x2d, 0xf9, 0x38, 0xd6, 0x53, - 0xae, 0x92, 0xeb, 0x7b, 0x99, 0x52, 0x6f, 0x99, 0x5a, 0xe4, 0xfa, 0x1e, 0xd9, 0x6f, 0x61, 0xb1, 0x4f, 0xc8, 0x17, - 0x81, 0xf4, 0xb4, 0x90, 0x37, 0x7b, 0x4f, 0x8b, 0x42, 0x16, 0x71, 0xf4, 0xf8, 0xea, 0xca, 0x96, 0xd8, 0xe3, 0x6a, - 0x4f, 0x48, 0xbd, 0x57, 0xb6, 0x07, 0xd0, 0x4e, 0xf6, 0x7e, 0x57, 0x6c, 0xef, 0xd3, 0x42, 0x28, 0x3a, 0x66, 0x8f, - 0xaf, 0xae, 0x3e, 0xed, 0xc9, 0x62, 0xef, 0x53, 0xa6, 0xd4, 0xa7, 0x3d, 0x2e, 0x94, 0x66, 0x74, 0x94, 0x44, 0xa8, - 0x6b, 0x3a, 0xcb, 0x94, 0x7a, 0xc7, 0x6e, 0x35, 0xd1, 0xd8, 0x7c, 0x6a, 0xc2, 0xd6, 0x13, 0xa6, 0xf7, 0x54, 0x39, - 0xaf, 0x18, 0x2d, 0x73, 0xa6, 0xf7, 0x34, 0x31, 0xf9, 0xd2, 0xc1, 0x9f, 0xd9, 0x4f, 0xdd, 0xe5, 0xe3, 0xf8, 0x46, - 0x1c, 0x1c, 0xe8, 0x12, 0xd0, 0x68, 0xe9, 0x56, 0x88, 0xb0, 0x7d, 0x9f, 0x76, 0x70, 0xc0, 0x92, 0x9c, 0x89, 0x89, - 0x9e, 0x12, 0x42, 0xda, 0x5d, 0x71, 0x70, 0x10, 0x6b, 0xf2, 0x41, 0x24, 0x13, 0xa6, 0x63, 0x86, 0x10, 0xae, 0x6a, - 0x1f, 0x1c, 0xc4, 0x16, 0x08, 0x92, 0x68, 0x03, 0xb8, 0x1a, 0x8c, 0x51, 0xe2, 0xa0, 0x7f, 0x75, 0x27, 0xb2, 0x38, - 0x1c, 0x3f, 0xc2, 0xe2, 0xe0, 0xe0, 0x83, 0x48, 0x14, 0xb4, 0x88, 0x35, 0x42, 0xeb, 0x82, 0xe9, 0x45, 0x21, 0xf6, - 0xf4, 0x5a, 0xcb, 0x2b, 0x5d, 0x70, 0x31, 0x89, 0xd1, 0xd2, 0xa7, 0x05, 0x15, 0xd7, 0x6b, 0x3b, 0xdc, 0xdf, 0x0a, - 0xc2, 0xc9, 0x25, 0xf4, 0xf8, 0x4c, 0xc6, 0x0e, 0x07, 0x39, 0x21, 0x91, 0x32, 0x75, 0xa3, 0x1e, 0x4f, 0x79, 0x23, - 0x8a, 0xb0, 0x1d, 0x25, 0xfe, 0x22, 0x10, 0x16, 0x1a, 0x50, 0x37, 0x49, 0x12, 0x8d, 0xc8, 0xe5, 0xd2, 0x83, 0x85, - 0x07, 0x13, 0xed, 0xf1, 0x7e, 0x6b, 0x90, 0xea, 0xa4, 0x60, 0xa3, 0x45, 0xc6, 0xe2, 0x58, 0x60, 0x85, 0x25, 0x22, - 0x97, 0xa2, 0x11, 0x17, 0xe4, 0x12, 0xd6, 0xbb, 0xa8, 0x2f, 0x36, 0x21, 0xfb, 0x2d, 0xe4, 0x06, 0x59, 0xf8, 0x11, - 0x02, 0x88, 0xdd, 0x80, 0x0a, 0x42, 0x22, 0xb1, 0x98, 0x0d, 0x59, 0x11, 0x95, 0xc5, 0xba, 0x35, 0xbc, 0x58, 0x28, - 0xb6, 0x97, 0x29, 0xb5, 0x37, 0x5e, 0x88, 0x4c, 0x73, 0x29, 0xf6, 0xa2, 0x46, 0xd1, 0x88, 0x2c, 0x3e, 0x94, 0xe8, - 0x10, 0xa1, 0x35, 0x8a, 0x15, 0x6a, 0xf0, 0xbe, 0x6c, 0xb4, 0x07, 0x18, 0x46, 0x89, 0xba, 0xae, 0x3d, 0x07, 0x01, - 0x86, 0x39, 0x4c, 0x72, 0x8d, 0xff, 0xb4, 0x3b, 0x1f, 0xa6, 0x78, 0x23, 0x7a, 0x3c, 0xd9, 0xde, 0x29, 0x44, 0x27, - 0x33, 0x3a, 0x8f, 0x19, 0xb9, 0x64, 0x06, 0xbb, 0xa8, 0xc8, 0x60, 0xac, 0xb5, 0x85, 0xeb, 0xb1, 0x94, 0x25, 0x15, - 0x4e, 0xa1, 0x54, 0x27, 0x63, 0x59, 0x3c, 0xa5, 0xd9, 0x14, 0xea, 0x95, 0x18, 0x33, 0xf2, 0x1b, 0x2e, 0x2b, 0x18, - 0xd5, 0xec, 0x69, 0xce, 0xe0, 0x2b, 0x8e, 0x4c, 0xcd, 0x08, 0x61, 0x05, 0x5b, 0x3d, 0xe7, 0xfa, 0x95, 0x14, 0x19, - 0xeb, 0xaa, 0x00, 0xbf, 0xcc, 0xca, 0x3f, 0xd4, 0xba, 0xe0, 0xc3, 0x85, 0x66, 0x71, 0x24, 0xa0, 0x44, 0x84, 0x15, - 0xc2, 0x22, 0xd1, 0xec, 0x56, 0x3f, 0x96, 0x42, 0x33, 0xa1, 0x09, 0xf3, 0x50, 0xc5, 0x3c, 0xa1, 0xf3, 0x39, 0x13, - 0xa3, 0xc7, 0x53, 0x9e, 0x8f, 0x62, 0x81, 0xd6, 0x68, 0x8d, 0x7f, 0x17, 0x04, 0x26, 0x49, 0x2e, 0x79, 0x0a, 0xff, - 0x7c, 0x7b, 0x3a, 0xb1, 0x26, 0x97, 0x66, 0x5b, 0x30, 0x12, 0x45, 0xdd, 0xb1, 0x2c, 0x62, 0x37, 0x85, 0x3d, 0x20, - 0x5d, 0xd0, 0xc7, 0xdb, 0x45, 0xce, 0x14, 0x62, 0x0d, 0x22, 0xca, 0x75, 0x74, 0x10, 0xfe, 0xad, 0x88, 0x19, 0x2c, - 0x00, 0x47, 0x29, 0x37, 0x24, 0xf0, 0x25, 0x77, 0x9b, 0x6a, 0x54, 0x12, 0xb5, 0x8f, 0x82, 0x8c, 0x78, 0xa2, 0x8b, - 0x85, 0xd2, 0x6c, 0xf4, 0xee, 0x6e, 0xce, 0x14, 0xfe, 0xb9, 0x20, 0x1f, 0x45, 0xef, 0xa3, 0x48, 0xd8, 0x6c, 0xae, - 0xef, 0xae, 0x0c, 0x35, 0x4f, 0xa3, 0x08, 0xff, 0x6d, 0x8a, 0x16, 0x8c, 0x66, 0x40, 0xd2, 0x1c, 0xc8, 0xde, 0xc8, - 0xfc, 0x6e, 0xcc, 0xf3, 0xfc, 0x6a, 0x31, 0x9f, 0xcb, 0x42, 0x63, 0x2d, 0xc8, 0x52, 0xcb, 0x0a, 0x3e, 0xb0, 0xa2, - 0x4b, 0x75, 0xc3, 0x75, 0x36, 0x8d, 0x35, 0x5a, 0x66, 0x54, 0xb1, 0xbd, 0x47, 0x52, 0xe6, 0x8c, 0x8a, 0x94, 0x13, - 0xde, 0xfb, 0xb9, 0x48, 0xc5, 0x22, 0xcf, 0xbb, 0xc3, 0x82, 0xd1, 0x2f, 0x5d, 0x93, 0x6d, 0x0f, 0x87, 0xd4, 0xfc, - 0x7e, 0x58, 0x14, 0xf4, 0x0e, 0x0a, 0x12, 0x02, 0xc5, 0x7a, 0x3c, 0xfd, 0xf9, 0xea, 0xf5, 0xab, 0xc4, 0xee, 0x15, - 0x3e, 0xbe, 0x8b, 0x79, 0xb9, 0xff, 0xf8, 0x1a, 0x8f, 0x0b, 0x39, 0xdb, 0xe8, 0xda, 0x82, 0x8e, 0x77, 0xbf, 0x31, - 0x04, 0x46, 0xf8, 0xbe, 0x6d, 0x3a, 0x1c, 0xc1, 0x2b, 0x83, 0xf9, 0x90, 0x49, 0x5c, 0xbf, 0xf0, 0x4f, 0x6a, 0x93, - 0x63, 0x8e, 0xbe, 0x3f, 0x5a, 0x5d, 0xdc, 0x2d, 0x19, 0x31, 0xe3, 0x9c, 0xc3, 0xc1, 0x08, 0x63, 0xcc, 0xa8, 0xce, - 0xa6, 0x4b, 0x66, 0x1a, 0x5b, 0xfb, 0x11, 0xb3, 0xf5, 0x1a, 0xbf, 0x92, 0x1e, 0xeb, 0xf5, 0x3e, 0x21, 0xdc, 0xd0, - 0x2b, 0xa2, 0x57, 0x2b, 0x4e, 0x08, 0x47, 0xf8, 0x2d, 0x27, 0x4b, 0xea, 0x27, 0x04, 0x27, 0x1b, 0x6c, 0xcf, 0xd4, - 0x52, 0x19, 0x38, 0x01, 0xbf, 0xb2, 0x42, 0xb3, 0x22, 0xd5, 0x02, 0x17, 0x6c, 0x9c, 0xc3, 0x38, 0xf6, 0xdb, 0x78, - 0x4a, 0xd5, 0xe3, 0x29, 0x15, 0x13, 0x36, 0x4a, 0x5f, 0xc9, 0x35, 0x66, 0x82, 0x44, 0x63, 0x2e, 0x68, 0xce, 0xff, - 0x61, 0xa3, 0xc8, 0x9d, 0x0b, 0xef, 0xf5, 0x1e, 0xbb, 0xd5, 0x4c, 0x8c, 0xd4, 0xde, 0xf3, 0x77, 0xbf, 0xbe, 0x74, - 0x8b, 0x59, 0x3b, 0x2b, 0xd0, 0x52, 0x2d, 0xe6, 0xac, 0x88, 0x11, 0x76, 0x67, 0xc5, 0x53, 0x6e, 0xe8, 0xe4, 0xaf, - 0x74, 0x6e, 0x53, 0xb8, 0xfa, 0x7d, 0x3e, 0xa2, 0x9a, 0xbd, 0x61, 0x62, 0xc4, 0xc5, 0x84, 0xec, 0xb7, 0x6d, 0xfa, - 0x94, 0xba, 0x8c, 0x51, 0x99, 0x74, 0x7d, 0xef, 0x69, 0x6e, 0xe6, 0x5e, 0x7e, 0x2e, 0x62, 0xb4, 0x56, 0x9a, 0x6a, - 0x9e, 0xed, 0xd1, 0xd1, 0xe8, 0x85, 0xe0, 0x9a, 0x9b, 0x11, 0x16, 0xb0, 0x44, 0x80, 0xab, 0xcc, 0x9e, 0x1a, 0x7e, - 0xe4, 0x31, 0xc2, 0x71, 0xec, 0xce, 0x82, 0x29, 0x72, 0x6b, 0x76, 0x70, 0x50, 0x51, 0xfe, 0x1e, 0x4b, 0x6d, 0x26, - 0xe9, 0x0f, 0x50, 0x32, 0x5f, 0x28, 0x58, 0x6c, 0xdf, 0x05, 0x1c, 0x34, 0x72, 0xa8, 0x58, 0xf1, 0x95, 0x8d, 0x4a, - 0x04, 0x51, 0x31, 0x5a, 0x6e, 0xf4, 0xe1, 0xb6, 0x87, 0x26, 0xfd, 0x41, 0x37, 0x24, 0xe1, 0xcc, 0x21, 0xbb, 0xe5, - 0x54, 0x38, 0x53, 0x25, 0x51, 0x89, 0xe1, 0x40, 0x2d, 0x09, 0x8b, 0x22, 0x7e, 0x7e, 0xf3, 0x58, 0x00, 0x0f, 0x11, - 0x52, 0x0e, 0x7f, 0xe6, 0x3e, 0xfd, 0x6a, 0x0e, 0x0f, 0x85, 0x05, 0xc2, 0xda, 0x8e, 0x54, 0x21, 0xb4, 0x46, 0x58, - 0xfb, 0xe1, 0x5a, 0xa2, 0xe4, 0xf9, 0x22, 0x38, 0xb5, 0xc9, 0x5b, 0x6e, 0x8e, 0x6d, 0xa0, 0x6d, 0x54, 0xb3, 0x83, - 0x83, 0x98, 0x25, 0x25, 0x62, 0x90, 0xfd, 0xb6, 0x5b, 0xa4, 0x00, 0x5a, 0xdf, 0x18, 0x37, 0xf4, 0x6c, 0x18, 0x9c, - 0x7d, 0x96, 0x08, 0xf9, 0x30, 0xcb, 0x98, 0x52, 0xb2, 0x38, 0x38, 0xd8, 0x37, 0xe5, 0x4b, 0xce, 0x02, 0x16, 0xf1, - 0xf5, 0x8d, 0xa8, 0x86, 0x80, 0xaa, 0xd3, 0xd6, 0xf3, 0x4d, 0xa4, 0xe2, 0x9b, 0x3c, 0x13, 0x92, 0x46, 0xd7, 0xd7, - 0x51, 0x43, 0x63, 0x07, 0x87, 0x09, 0xf3, 0x5d, 0xdf, 0x3d, 0x61, 0x96, 0x2d, 0x34, 0x4c, 0xc8, 0x16, 0x68, 0x76, - 0xf2, 0x83, 0x71, 0x7d, 0x48, 0x58, 0x63, 0x85, 0xd6, 0xc1, 0x8a, 0xee, 0x6c, 0xda, 0xf0, 0x37, 0x76, 0xe9, 0x96, - 0x13, 0xc3, 0x53, 0x04, 0xeb, 0xd8, 0x67, 0x83, 0x35, 0x36, 0xb0, 0xf7, 0xb3, 0x91, 0x66, 0xa0, 0x7d, 0x3d, 0xe8, - 0xba, 0x7c, 0xa2, 0x2c, 0xe4, 0x0a, 0xf6, 0xf7, 0x82, 0x29, 0x6d, 0x11, 0x39, 0xd6, 0x58, 0x62, 0x38, 0xa3, 0x36, - 0x99, 0xce, 0x1a, 0x4b, 0xba, 0x6b, 0x6c, 0xaf, 0xe7, 0x70, 0x36, 0x2a, 0x40, 0xea, 0xef, 0xe3, 0x13, 0x8c, 0x55, - 0xa3, 0xd5, 0xea, 0x2d, 0xf7, 0xad, 0x54, 0x6b, 0x59, 0xf2, 0x6b, 0x1b, 0x8b, 0xc2, 0x04, 0x72, 0x87, 0xf3, 0x7e, - 0xdb, 0x8d, 0x5f, 0x0c, 0xc8, 0x7e, 0xab, 0xc4, 0x62, 0x07, 0x56, 0x3b, 0x1e, 0x0b, 0xc5, 0xd7, 0xb6, 0x29, 0x64, - 0xce, 0xfa, 0x1a, 0xbe, 0x24, 0xd3, 0x2d, 0x5c, 0x9d, 0x92, 0x3e, 0x70, 0x1d, 0xc9, 0x74, 0xf0, 0x2d, 0x7c, 0xf2, - 0x14, 0x21, 0xd6, 0xdb, 0x79, 0x15, 0xe1, 0xf8, 0x5a, 0x27, 0x1c, 0x1b, 0xd3, 0x88, 0xe6, 0x65, 0x95, 0xa8, 0x44, - 0x33, 0xb7, 0xd5, 0xab, 0x2c, 0x2c, 0xcc, 0x60, 0xaa, 0x29, 0x05, 0x4d, 0xbc, 0xa2, 0x33, 0xa6, 0x62, 0x86, 0xf0, - 0xb7, 0x0a, 0x58, 0xfc, 0x84, 0x22, 0x83, 0xe0, 0x0c, 0x55, 0x70, 0x86, 0x02, 0xbb, 0x0b, 0x4c, 0x5a, 0x7d, 0xcb, - 0x29, 0xcc, 0xfa, 0x6a, 0x50, 0xf1, 0x76, 0xc1, 0xe4, 0xcd, 0xe1, 0xec, 0x10, 0xdc, 0xc3, 0xcf, 0xa6, 0x59, 0xa0, - 0x19, 0x16, 0x42, 0x21, 0xbc, 0xdf, 0xda, 0x5c, 0x49, 0x5f, 0xaa, 0x9a, 0x63, 0x7f, 0x00, 0xeb, 0x60, 0x8e, 0x8d, - 0x84, 0x2b, 0xf3, 0xb7, 0xb6, 0xd5, 0x00, 0x6c, 0x57, 0x80, 0x19, 0xc9, 0x38, 0xa7, 0x3a, 0x6e, 0x1f, 0xb5, 0x80, - 0x31, 0xfd, 0xca, 0xe0, 0x54, 0x41, 0x68, 0x7b, 0x2a, 0x2c, 0x59, 0x08, 0x35, 0xe5, 0x63, 0x1d, 0xff, 0x2e, 0x0c, - 0x51, 0x61, 0xb9, 0x62, 0x20, 0xe1, 0x04, 0xec, 0xb1, 0x21, 0x38, 0xbf, 0x0b, 0xe8, 0xa7, 0x5b, 0x1e, 0x44, 0x6e, - 0xa4, 0x86, 0x70, 0x01, 0x79, 0xa8, 0x58, 0xeb, 0x8a, 0xcc, 0x94, 0x8c, 0x1b, 0x70, 0x8f, 0xed, 0x9e, 0x6d, 0x31, - 0x75, 0xd4, 0x40, 0x04, 0x1c, 0xac, 0x48, 0x43, 0x12, 0xe1, 0x12, 0x75, 0xa2, 0xe5, 0x4b, 0x79, 0xc3, 0x8a, 0xc7, - 0x14, 0x06, 0x9f, 0xda, 0xea, 0x6b, 0x7b, 0x14, 0x18, 0x8a, 0xaf, 0xbb, 0x1e, 0x5f, 0xae, 0xcd, 0xc4, 0xdf, 0x14, - 0x72, 0xc6, 0x15, 0x03, 0xbe, 0xcd, 0xc2, 0x5f, 0xc0, 0x46, 0x33, 0x3b, 0x12, 0x8e, 0x1b, 0x56, 0xe2, 0xd7, 0xc3, - 0x97, 0x75, 0xfc, 0xba, 0xbe, 0xf7, 0x74, 0xe2, 0x29, 0x60, 0x7d, 0x1f, 0x23, 0x1c, 0x3b, 0xf1, 0x22, 0x38, 0xe9, - 0x92, 0x29, 0x72, 0xc7, 0xfc, 0x6a, 0xa5, 0x03, 0x31, 0xae, 0xc6, 0x39, 0x32, 0xbb, 0x6d, 0xd0, 0x9a, 0x8e, 0x46, - 0xc0, 0xe2, 0x15, 0x32, 0xcf, 0x83, 0xc3, 0x0a, 0x8b, 0x6e, 0x79, 0x3c, 0x5d, 0xdf, 0x7b, 0x7a, 0xf5, 0xbd, 0x13, - 0x0a, 0xf2, 0xc3, 0x43, 0xca, 0x0f, 0x54, 0x8c, 0x58, 0x01, 0x72, 0x65, 0xb0, 0x5a, 0xee, 0x9c, 0x7d, 0x2c, 0x85, - 0x60, 0x99, 0x66, 0x23, 0x10, 0x5a, 0x04, 0xd1, 0xc9, 0x54, 0x2a, 0x5d, 0x26, 0x56, 0xa3, 0x17, 0xa1, 0x10, 0x9a, - 0x64, 0x34, 0xcf, 0x63, 0x2b, 0xa0, 0xcc, 0xe4, 0x57, 0xb6, 0x63, 0xd4, 0xdd, 0xda, 0x90, 0xcb, 0x66, 0x58, 0xd0, - 0x0c, 0x4b, 0xd4, 0x3c, 0xe7, 0x19, 0x2b, 0x0f, 0xaf, 0xab, 0x84, 0x8b, 0x11, 0xbb, 0x05, 0x3a, 0x82, 0x2e, 0x2f, - 0x2f, 0x5b, 0xb8, 0x8d, 0xd6, 0x16, 0xe0, 0xcb, 0x2d, 0xc0, 0x7e, 0xe7, 0xd8, 0xb4, 0x82, 0xf8, 0x72, 0x27, 0x59, - 0x43, 0xc1, 0x59, 0xc9, 0xbd, 0xa0, 0x65, 0xc9, 0x33, 0xc2, 0x23, 0x96, 0x33, 0xcd, 0x3c, 0x39, 0x07, 0x66, 0xda, - 0x6e, 0xdd, 0xb7, 0x25, 0xfc, 0x4a, 0x74, 0xf2, 0xbb, 0xcc, 0xaf, 0xb9, 0x2a, 0x45, 0xf7, 0x6a, 0x79, 0x2a, 0x68, - 0xf7, 0xb4, 0x5d, 0x1e, 0xaa, 0x35, 0xcd, 0xa6, 0x56, 0x62, 0x8f, 0xb7, 0xa6, 0x54, 0xb5, 0xe1, 0x48, 0x7b, 0xb9, - 0x89, 0xfe, 0x2c, 0xdc, 0x30, 0x77, 0x81, 0xe0, 0xca, 0x11, 0x05, 0x06, 0x42, 0xa0, 0x5d, 0xb6, 0xc7, 0x34, 0xcf, - 0x87, 0x34, 0xfb, 0x52, 0xc7, 0xfe, 0x0a, 0x0d, 0xc8, 0x26, 0x35, 0x0e, 0xb2, 0x02, 0x92, 0x15, 0xce, 0xdb, 0x53, - 0xe9, 0xda, 0x46, 0x89, 0xf7, 0x5b, 0x15, 0xda, 0xd7, 0x17, 0xfa, 0x9b, 0xd8, 0x6e, 0x46, 0x24, 0xdc, 0xcc, 0x62, - 0xa0, 0x02, 0xff, 0x12, 0xe3, 0x3c, 0x3d, 0x70, 0x78, 0x07, 0x82, 0xc7, 0x7a, 0x63, 0x20, 0x1a, 0x2d, 0xd7, 0x23, - 0xae, 0xbe, 0x0d, 0x81, 0xff, 0x2d, 0xa3, 0x7c, 0x12, 0xf4, 0xf0, 0xef, 0x0e, 0xb4, 0xa4, 0x71, 0x8e, 0x71, 0x2e, - 0x47, 0xe6, 0x18, 0x0a, 0x4f, 0x68, 0x7e, 0x01, 0xe6, 0xc5, 0xe0, 0xfb, 0x6b, 0x9b, 0x65, 0xf8, 0x32, 0x18, 0x86, - 0xea, 0x86, 0x0c, 0x45, 0x0d, 0x05, 0x1c, 0x51, 0x15, 0xe6, 0xcc, 0x95, 0x35, 0x51, 0xd2, 0x71, 0xed, 0x56, 0x1c, - 0x77, 0x34, 0xb7, 0x20, 0x71, 0x1c, 0x2b, 0x90, 0xe6, 0x3c, 0x7f, 0x5f, 0xcd, 0x42, 0x6d, 0xcd, 0x42, 0x25, 0x81, - 0xb4, 0x85, 0x2a, 0x64, 0x0e, 0xaa, 0xa7, 0x5a, 0xa0, 0xb0, 0x14, 0xb0, 0xac, 0x09, 0x50, 0x68, 0x54, 0x12, 0xdc, - 0x9c, 0x68, 0x5c, 0x38, 0x51, 0xc7, 0xe1, 0x1a, 0x90, 0x8c, 0xaa, 0x8a, 0x44, 0x76, 0x73, 0xd4, 0x64, 0x5f, 0x89, - 0x0b, 0xb4, 0xc1, 0xdf, 0xaf, 0xd7, 0x0e, 0x4a, 0x0c, 0xb9, 0xd5, 0xa9, 0x31, 0xc6, 0x01, 0x58, 0xb0, 0x24, 0x8e, - 0x19, 0xb6, 0xac, 0xcf, 0x26, 0x70, 0xca, 0x76, 0xf7, 0x09, 0x91, 0x15, 0x6c, 0x6a, 0x4c, 0xa5, 0xe7, 0xae, 0x24, - 0xc2, 0xd4, 0xb3, 0xa5, 0x45, 0x35, 0x71, 0x42, 0x22, 0xaf, 0x9d, 0x88, 0x7a, 0xcb, 0x9a, 0x70, 0x98, 0x06, 0xc5, - 0xd6, 0x29, 0x10, 0xd5, 0x62, 0x17, 0xbc, 0x77, 0x61, 0x4d, 0xad, 0x9d, 0x00, 0xe2, 0x45, 0x0d, 0xe2, 0x01, 0x68, - 0xa5, 0x25, 0x5e, 0x72, 0x40, 0x68, 0xbd, 0x72, 0xcc, 0x70, 0x61, 0x17, 0x62, 0x0b, 0x8a, 0x9b, 0xec, 0xa7, 0xc1, - 0x42, 0x90, 0x65, 0x15, 0xf0, 0x77, 0xe1, 0x11, 0x11, 0xc3, 0xe0, 0xc5, 0x6a, 0xb5, 0x85, 0x76, 0x3b, 0xb9, 0x50, - 0x94, 0x54, 0xd2, 0xe1, 0x6a, 0xf5, 0x4a, 0xa2, 0xd8, 0xf1, 0xbf, 0x98, 0xa1, 0x9e, 0x27, 0xba, 0x0f, 0x5f, 0x42, - 0x29, 0xc3, 0x8e, 0x56, 0x29, 0xa5, 0xe0, 0x50, 0xc7, 0xda, 0xfa, 0x42, 0xe9, 0x80, 0x72, 0x3f, 0xde, 0x22, 0x60, - 0x26, 0xd1, 0x9d, 0xd4, 0xd5, 0x94, 0x1f, 0xbb, 0xa6, 0x05, 0x42, 0x28, 0x55, 0x46, 0x96, 0xd9, 0xdf, 0x25, 0x5f, - 0x1e, 0x1c, 0xa8, 0xa0, 0xa1, 0xeb, 0x92, 0x52, 0x7c, 0x8e, 0xe1, 0x54, 0x56, 0x77, 0xc2, 0xb0, 0x2f, 0x9f, 0xfd, - 0x39, 0xb4, 0x25, 0x9d, 0xb6, 0xba, 0x20, 0x98, 0xd3, 0x1b, 0xca, 0xf5, 0x5e, 0xd9, 0x8a, 0x15, 0xcc, 0x63, 0x86, - 0x96, 0x8e, 0xdb, 0x48, 0x0a, 0x06, 0xfc, 0x23, 0x90, 0x05, 0xcf, 0x45, 0x5b, 0xc4, 0xcf, 0xa6, 0x0c, 0x54, 0xd9, - 0x9e, 0x91, 0x28, 0xc5, 0xc3, 0x7d, 0x77, 0x90, 0xb8, 0x86, 0x77, 0x8f, 0x7d, 0xbd, 0x59, 0xbd, 0x26, 0x0d, 0xcc, - 0x59, 0x31, 0x96, 0xc5, 0xcc, 0xe7, 0xad, 0x37, 0xbe, 0x1d, 0x71, 0xe4, 0xe3, 0x78, 0x67, 0xdb, 0x4e, 0x04, 0xe8, - 0x6e, 0xc8, 0xde, 0x95, 0xd4, 0x5e, 0x3b, 0x4d, 0xcb, 0x03, 0xd8, 0x2a, 0x08, 0x3d, 0x66, 0xaa, 0x50, 0xca, 0x77, - 0xea, 0xd5, 0xae, 0xd5, 0x9d, 0xec, 0xb7, 0xbb, 0xa5, 0xe4, 0xe7, 0xb1, 0xa1, 0x6b, 0x75, 0x1c, 0xee, 0x54, 0x95, - 0x8b, 0x7c, 0xe4, 0x06, 0x2b, 0x10, 0x66, 0x0e, 0x8f, 0x6e, 0x78, 0x9e, 0x57, 0xa9, 0xff, 0x09, 0x69, 0x57, 0x8e, - 0xb4, 0x4b, 0x4f, 0xda, 0x81, 0x54, 0x00, 0x69, 0xb7, 0xcd, 0x55, 0xd5, 0xe5, 0xd6, 0xf6, 0x94, 0x96, 0xa8, 0x2b, - 0x23, 0x4e, 0x43, 0x7f, 0x0b, 0x3f, 0x02, 0x54, 0x32, 0x5f, 0x5f, 0x62, 0xa7, 0x8f, 0x01, 0x31, 0xd0, 0xea, 0x34, - 0x59, 0xa8, 0xa9, 0xf8, 0x12, 0x23, 0xac, 0xd6, 0xac, 0xc4, 0xec, 0x87, 0x4f, 0x41, 0x69, 0x17, 0x4c, 0x07, 0xce, - 0x31, 0x93, 0xfc, 0x1f, 0xf1, 0x51, 0x7e, 0x76, 0xc2, 0xcd, 0x4e, 0xf9, 0xd9, 0x01, 0xad, 0xaf, 0x66, 0x37, 0xfa, - 0x3e, 0xb5, 0x37, 0xd3, 0x13, 0xe5, 0xf4, 0xaa, 0xf5, 0x5e, 0xad, 0xe2, 0x8d, 0x14, 0xd0, 0xe8, 0x3b, 0x29, 0xa5, - 0x28, 0x5b, 0x07, 0x1a, 0x10, 0x42, 0x06, 0x12, 0xd6, 0x76, 0xd2, 0xe5, 0x29, 0xf7, 0xf2, 0x5f, 0xe9, 0x79, 0x8c, - 0xe2, 0xde, 0xd6, 0x7f, 0x2c, 0x67, 0x73, 0x60, 0xc8, 0x36, 0x50, 0x7a, 0xc2, 0x5c, 0x87, 0x55, 0xfe, 0x7a, 0x47, - 0x5a, 0xad, 0x8e, 0xd9, 0x8f, 0x35, 0x6c, 0x2a, 0xa5, 0xe6, 0xfd, 0xd6, 0x7a, 0x51, 0x26, 0x95, 0x84, 0x63, 0x97, - 0x6e, 0xe5, 0xf1, 0xa6, 0x66, 0xc6, 0x67, 0xbc, 0x8e, 0x85, 0xa5, 0xc3, 0x02, 0x68, 0x5d, 0x40, 0x7e, 0x3c, 0xba, - 0x87, 0xeb, 0xbf, 0xae, 0x80, 0xb3, 0x5c, 0x6f, 0x80, 0x6f, 0xb9, 0x5e, 0xbf, 0xd7, 0x4e, 0xd2, 0xc6, 0xef, 0x77, - 0xc8, 0xbd, 0x25, 0xf4, 0xaa, 0x4c, 0x27, 0x33, 0xf6, 0x07, 0x90, 0xb6, 0xc5, 0x42, 0x92, 0xe5, 0x4c, 0x8e, 0x58, - 0x1a, 0xc9, 0x39, 0x13, 0xd1, 0x1a, 0xf4, 0xac, 0x0e, 0x01, 0xfe, 0x16, 0xf1, 0xf2, 0x6d, 0x5d, 0xdf, 0x9a, 0xbe, - 0xd7, 0x6b, 0x50, 0x85, 0xbd, 0xe4, 0x3b, 0x94, 0xb1, 0xef, 0x59, 0xa1, 0x0c, 0x4f, 0x5a, 0xb2, 0xb7, 0x2f, 0x79, - 0x75, 0x40, 0xbd, 0xe4, 0xe9, 0xb7, 0xab, 0x54, 0x02, 0x49, 0xd4, 0x4e, 0xce, 0x92, 0xe3, 0x08, 0x19, 0x8d, 0xf1, - 0x33, 0xaf, 0x31, 0x5e, 0x94, 0x1a, 0xe3, 0xe7, 0x9a, 0x2c, 0x36, 0x34, 0xc6, 0x7f, 0x08, 0xf2, 0x5c, 0xf7, 0x9e, - 0x7b, 0x6d, 0xfa, 0x1b, 0x99, 0xf3, 0xec, 0x2e, 0x8e, 0x72, 0xae, 0x9b, 0x70, 0x9b, 0x18, 0xe1, 0xa5, 0xcd, 0x00, - 0x55, 0xa3, 0xd1, 0x77, 0xaf, 0xbd, 0xfc, 0x87, 0x85, 0x20, 0xd1, 0xbd, 0x9c, 0xeb, 0x7b, 0x11, 0x9e, 0x6a, 0xf2, - 0x09, 0x7e, 0xdd, 0x5b, 0xc6, 0xbf, 0x52, 0x3d, 0x4d, 0x0a, 0x2a, 0x46, 0x72, 0x16, 0xa3, 0x46, 0x14, 0xa1, 0x44, - 0x19, 0x21, 0xe4, 0x01, 0x5a, 0xdf, 0xfb, 0x84, 0xff, 0x91, 0x24, 0xea, 0x45, 0x8d, 0xa9, 0xc6, 0x9a, 0x92, 0x4f, - 0x17, 0xf7, 0x96, 0xff, 0xc8, 0xf5, 0xe5, 0x27, 0xfc, 0x54, 0x97, 0x6a, 0x7d, 0x7c, 0xcb, 0x48, 0x8c, 0xc8, 0xe5, - 0x53, 0x3f, 0xa4, 0xc7, 0x72, 0x66, 0x15, 0xfc, 0x11, 0xc2, 0x5f, 0x41, 0xaf, 0x7b, 0xc9, 0x2b, 0x22, 0xe4, 0xee, - 0x60, 0xf6, 0x49, 0x24, 0x8d, 0xf2, 0x20, 0x3a, 0x38, 0x08, 0xd2, 0x4a, 0x16, 0x02, 0x7f, 0x96, 0xa4, 0x26, 0xaa, - 0x63, 0x46, 0xa1, 0xa5, 0xcf, 0x32, 0xe6, 0xc8, 0x37, 0x13, 0x7b, 0x4d, 0xb5, 0xdb, 0xb1, 0xbc, 0x6f, 0x75, 0x0f, - 0x09, 0xd7, 0xac, 0xa0, 0x5a, 0x16, 0x03, 0x14, 0xb2, 0x25, 0xf8, 0x15, 0x27, 0x9f, 0xfa, 0x7b, 0xff, 0xcf, 0xff, - 0xf8, 0x6b, 0xfc, 0x57, 0x31, 0xf8, 0x84, 0x05, 0x23, 0x47, 0x17, 0x71, 0x2f, 0x8d, 0xf7, 0x9b, 0xcd, 0xd5, 0x5f, - 0x47, 0xfd, 0xff, 0xa6, 0xcd, 0x7f, 0x1e, 0x36, 0xff, 0x1c, 0xa0, 0x55, 0xfc, 0xd7, 0x51, 0xaf, 0xef, 0xbe, 0xfa, - 0xff, 0x7d, 0xf9, 0x97, 0x1a, 0x1c, 0xda, 0xc4, 0x7b, 0x08, 0x1d, 0x4d, 0xf0, 0x2f, 0x82, 0x1c, 0x35, 0x9b, 0x97, - 0x47, 0x13, 0xfc, 0x93, 0x20, 0x47, 0xf0, 0xf7, 0x4e, 0x93, 0xb7, 0x6c, 0xf2, 0xf4, 0x76, 0x1e, 0x7f, 0xba, 0x5c, - 0xdd, 0x5b, 0xbe, 0xe2, 0x6b, 0x68, 0xb7, 0xff, 0xdf, 0x7f, 0xfd, 0xa5, 0xa2, 0x1f, 0x2f, 0xc9, 0xd1, 0xa0, 0x81, - 0x62, 0x93, 0x7c, 0x48, 0xec, 0x9f, 0xb8, 0x97, 0xf6, 0xff, 0xdb, 0x0d, 0x25, 0xfa, 0xf1, 0xaf, 0x4f, 0x17, 0x97, - 0x64, 0xb0, 0x8a, 0xa3, 0xd5, 0x8f, 0x68, 0x85, 0xd0, 0xea, 0x1e, 0xfa, 0x84, 0xa3, 0x49, 0x84, 0xf0, 0x6f, 0x82, - 0x1c, 0xfd, 0x78, 0x34, 0xc1, 0x7f, 0x0a, 0x72, 0x14, 0x1d, 0x4d, 0xf0, 0x23, 0x49, 0x8e, 0xfe, 0x3b, 0xee, 0xa5, - 0x56, 0x09, 0xb7, 0x32, 0xea, 0x8f, 0x15, 0xdc, 0x84, 0xd0, 0x82, 0xd1, 0x95, 0xe6, 0x3a, 0x67, 0xe8, 0xde, 0x11, - 0xc7, 0xef, 0x25, 0x00, 0x2b, 0xd6, 0xa0, 0xa4, 0x31, 0x97, 0xb0, 0xcb, 0x6b, 0x58, 0x78, 0xc0, 0xa0, 0x7b, 0x29, - 0xc7, 0x56, 0x4f, 0xa0, 0x52, 0x6d, 0x6f, 0x6f, 0x15, 0x5c, 0xdf, 0xe2, 0xc7, 0xe4, 0xbd, 0x8c, 0xdb, 0x08, 0x73, - 0x0a, 0x3f, 0x3a, 0x08, 0x7f, 0xd0, 0xee, 0xc2, 0x13, 0xb6, 0xb9, 0xc5, 0x30, 0x21, 0x2d, 0x3f, 0x13, 0x21, 0xfc, - 0x72, 0x47, 0xa6, 0x9e, 0x82, 0xfa, 0x01, 0xe1, 0x9f, 0x6b, 0xd7, 0xa3, 0xf8, 0xb1, 0x26, 0x25, 0x72, 0xbc, 0x2b, - 0x18, 0xfb, 0x40, 0xf3, 0x2f, 0xac, 0x88, 0x9f, 0x6a, 0xdc, 0xee, 0x3c, 0xc0, 0x46, 0x55, 0xbd, 0xdf, 0x46, 0xdd, - 0xf2, 0x76, 0xeb, 0xb9, 0xb4, 0xf7, 0x09, 0x70, 0x0a, 0xd7, 0xf5, 0x35, 0xb0, 0xf6, 0xfb, 0x7c, 0x4b, 0xa9, 0x55, - 0xd0, 0x9b, 0x08, 0xd5, 0xaf, 0x52, 0xb9, 0xf8, 0x4a, 0x73, 0x3e, 0xda, 0xd3, 0x6c, 0x36, 0xcf, 0xa9, 0x66, 0x7b, - 0x6e, 0xce, 0x7b, 0x14, 0x1a, 0x8a, 0x4a, 0x9e, 0xe2, 0x0f, 0x51, 0x6d, 0xda, 0x3f, 0x44, 0x52, 0xed, 0x9d, 0x18, - 0xee, 0xb3, 0x1c, 0x5f, 0x22, 0x68, 0x79, 0x5d, 0xb6, 0x79, 0x23, 0xd8, 0x6c, 0x83, 0xb2, 0x6c, 0x60, 0xce, 0x6f, - 0x85, 0xe1, 0x7e, 0x93, 0x90, 0x4e, 0x2f, 0xba, 0x50, 0x5f, 0x27, 0x97, 0x11, 0xdc, 0xe4, 0x14, 0x44, 0x30, 0xa3, - 0x3c, 0x82, 0x12, 0x94, 0xb4, 0xba, 0xf4, 0x82, 0x75, 0x69, 0xa3, 0xe1, 0xd9, 0xec, 0x8c, 0xf0, 0x3e, 0xb5, 0xf5, - 0x73, 0x3c, 0xc5, 0x23, 0xd2, 0x6c, 0xe3, 0x05, 0x69, 0x99, 0x2a, 0xdd, 0xc5, 0x45, 0xe6, 0xfa, 0x39, 0x38, 0x88, - 0x8b, 0x24, 0xa7, 0x4a, 0xbf, 0x00, 0x8d, 0x00, 0x59, 0xe0, 0x29, 0x29, 0x12, 0x76, 0xcb, 0xb2, 0x38, 0x43, 0x78, - 0xea, 0x68, 0x10, 0xea, 0xa2, 0x05, 0x09, 0x8a, 0x81, 0x9c, 0x41, 0x04, 0xeb, 0x4d, 0xfb, 0xed, 0x01, 0x21, 0x24, - 0xda, 0x6f, 0x36, 0xa3, 0x5e, 0x41, 0x7e, 0x11, 0x29, 0xa4, 0x04, 0xec, 0x34, 0xf9, 0x09, 0x92, 0x3a, 0x41, 0x52, - 0xfc, 0x48, 0x26, 0x9a, 0x29, 0x1d, 0x43, 0x32, 0x28, 0x09, 0x94, 0xc7, 0xf0, 0xe8, 0xe2, 0x28, 0x6a, 0x40, 0xaa, - 0x41, 0x51, 0x84, 0x0b, 0x72, 0xa7, 0x51, 0x3a, 0xed, 0x1f, 0x0f, 0xc2, 0x33, 0xc2, 0xa6, 0x42, 0xff, 0x77, 0xba, - 0x37, 0xed, 0xb7, 0x4c, 0xff, 0x97, 0x51, 0x2f, 0x2e, 0x88, 0xb2, 0x6c, 0x5c, 0x4f, 0xa5, 0x82, 0x99, 0xf9, 0xa2, - 0xd4, 0x0d, 0xd0, 0xf5, 0x3d, 0x22, 0xcd, 0x4e, 0x1a, 0x8f, 0xc2, 0x99, 0x34, 0xa1, 0x43, 0x07, 0x0a, 0x9c, 0x13, - 0x28, 0x8f, 0x0b, 0x02, 0x9d, 0x56, 0xd5, 0xee, 0x74, 0xea, 0x12, 0x7e, 0x8c, 0x7e, 0xec, 0xfd, 0x29, 0xd2, 0xdf, - 0x84, 0x1d, 0xc1, 0x9f, 0x62, 0xb5, 0x82, 0xbf, 0xbf, 0x89, 0x1e, 0x0c, 0xcb, 0xa4, 0xfd, 0xe2, 0xd2, 0x7e, 0x82, - 0x34, 0xc1, 0x52, 0x33, 0x60, 0xac, 0x4a, 0x7e, 0xcc, 0x2e, 0xce, 0x98, 0xd8, 0x19, 0x1c, 0x1c, 0xf0, 0x3e, 0x6d, - 0xb4, 0x07, 0x70, 0x23, 0x50, 0x68, 0xf5, 0x81, 0xeb, 0x69, 0x1c, 0x1d, 0x5d, 0x46, 0xa8, 0x17, 0xed, 0xc1, 0x2a, - 0x77, 0x65, 0x83, 0x38, 0x58, 0x67, 0x0d, 0x4d, 0xd3, 0xd1, 0x25, 0x69, 0xf5, 0x62, 0x61, 0x89, 0x7c, 0x8e, 0x70, - 0xe6, 0x68, 0x6a, 0x0b, 0x8f, 0x50, 0x43, 0x88, 0x86, 0xff, 0x1e, 0xa1, 0xc6, 0x54, 0x37, 0xc6, 0x28, 0xcd, 0xe0, - 0x6f, 0x3c, 0x22, 0x84, 0x34, 0x3b, 0x65, 0x45, 0x7f, 0x58, 0x52, 0x94, 0x8e, 0xbd, 0x7a, 0xb4, 0x6f, 0x36, 0x87, - 0x6c, 0xc4, 0xbc, 0xcf, 0x06, 0xab, 0x55, 0x74, 0xd1, 0xbb, 0x8c, 0x50, 0x23, 0xf6, 0x68, 0x77, 0xe4, 0xf1, 0x0e, - 0x21, 0x2c, 0x06, 0x6b, 0x77, 0x03, 0x75, 0xc3, 0x6a, 0xb7, 0x4d, 0xcb, 0x6a, 0xff, 0x07, 0x64, 0x81, 0xad, 0x4b, - 0xb9, 0xc7, 0xf2, 0xb7, 0x73, 0x98, 0xaa, 0xc7, 0x6d, 0x49, 0x5a, 0xb8, 0x20, 0x5e, 0xdd, 0x4d, 0x89, 0xae, 0xf0, - 0x3f, 0x23, 0x55, 0x71, 0xdc, 0xcf, 0xf1, 0x74, 0x40, 0x04, 0x35, 0xf2, 0x4b, 0xd7, 0x2b, 0xd3, 0x59, 0x4e, 0x6e, - 0xd8, 0xc6, 0xfd, 0x6f, 0x0e, 0x77, 0x32, 0x8f, 0x75, 0x92, 0x2d, 0x8a, 0x82, 0x09, 0xfd, 0x4a, 0x8e, 0x1c, 0x63, - 0xc7, 0x72, 0x90, 0xad, 0xe0, 0x62, 0x17, 0x03, 0x57, 0xd7, 0xf1, 0x3b, 0x65, 0xb4, 0x95, 0xbd, 0x20, 0x23, 0xcb, - 0x70, 0x99, 0xeb, 0xde, 0xee, 0xc2, 0x89, 0xd2, 0x31, 0xc2, 0x23, 0x77, 0x0f, 0x1c, 0x27, 0x49, 0xb2, 0x48, 0x32, - 0xc8, 0x86, 0x0e, 0x14, 0x5a, 0x9b, 0x7d, 0x15, 0x2b, 0xf2, 0x58, 0x27, 0x82, 0xdd, 0x9a, 0x6e, 0x63, 0x54, 0x1d, - 0xe2, 0x7e, 0xbf, 0x5d, 0xd0, 0xae, 0x21, 0x40, 0x2a, 0x11, 0x72, 0xc4, 0x00, 0x42, 0x70, 0xf7, 0xef, 0x92, 0xa6, - 0x54, 0x85, 0x37, 0x5b, 0xd5, 0x00, 0xfb, 0xa1, 0xca, 0x7b, 0x01, 0x7a, 0x62, 0xc3, 0x9e, 0x95, 0x85, 0xad, 0xf2, - 0x1c, 0x21, 0x3e, 0x8e, 0x17, 0x09, 0xdc, 0x08, 0x1a, 0x4c, 0x12, 0x02, 0xad, 0x56, 0x8b, 0x10, 0xb7, 0xa6, 0x95, - 0x62, 0x7a, 0x4c, 0xa6, 0xfd, 0xa2, 0xd1, 0x30, 0xca, 0xeb, 0x91, 0xc5, 0x8b, 0x05, 0xc2, 0xe3, 0x72, 0xaf, 0xf9, - 0x72, 0x73, 0x52, 0xef, 0x2a, 0x1e, 0xd7, 0x95, 0xc0, 0x0d, 0x21, 0x90, 0xd1, 0x2f, 0x6a, 0x68, 0x1d, 0x4f, 0xc8, - 0x51, 0xdc, 0x4f, 0x7a, 0xff, 0x73, 0x80, 0x7a, 0x71, 0x72, 0x88, 0x8e, 0x2c, 0x2d, 0x19, 0xa3, 0x6e, 0x66, 0xfb, - 0x58, 0x9a, 0xdb, 0xcf, 0x36, 0x36, 0x0a, 0xc8, 0x54, 0x62, 0x41, 0x67, 0x2c, 0x9d, 0xc0, 0xae, 0xf7, 0xc8, 0x33, - 0xc7, 0x80, 0x4c, 0xe9, 0xc4, 0xd1, 0x96, 0x24, 0xea, 0x49, 0x5a, 0x7e, 0xf5, 0xa2, 0x1e, 0xad, 0xbe, 0xfe, 0x67, - 0xd4, 0xcb, 0x68, 0xfa, 0x98, 0xaf, 0x9d, 0x92, 0xbc, 0xd6, 0xc7, 0x99, 0xef, 0x63, 0x6d, 0x17, 0x27, 0x00, 0xde, - 0x08, 0x6d, 0x6b, 0x47, 0x16, 0x68, 0xcd, 0xc7, 0x25, 0x75, 0x52, 0x89, 0xa6, 0x13, 0x80, 0x6a, 0xb0, 0x08, 0x2a, - 0xb4, 0x0d, 0x08, 0xa6, 0x0c, 0xd8, 0xe2, 0x91, 0x16, 0xa0, 0xb9, 0xb8, 0x6c, 0xa1, 0x65, 0xad, 0xb0, 0xe3, 0xac, - 0xea, 0x77, 0xf1, 0x25, 0xf1, 0x1e, 0x03, 0x55, 0xbe, 0x58, 0x74, 0xc7, 0x8d, 0x06, 0x52, 0x1e, 0xbf, 0x46, 0xfd, - 0xf1, 0x00, 0xdf, 0x02, 0x0a, 0xe1, 0x1a, 0x46, 0xe1, 0xda, 0x1c, 0x3b, 0x6e, 0x8e, 0x8d, 0x86, 0x5c, 0xa3, 0x6e, - 0x50, 0x79, 0xe1, 0x2a, 0xaf, 0xd7, 0x16, 0x32, 0x9b, 0x18, 0x77, 0x8e, 0x4c, 0x0a, 0x18, 0x82, 0x11, 0x42, 0xfe, - 0x91, 0x68, 0x67, 0xb3, 0xd0, 0x28, 0x54, 0x37, 0xbb, 0x17, 0x28, 0xaa, 0x3d, 0x3d, 0x62, 0x80, 0x05, 0x54, 0x2d, - 0xd5, 0xc8, 0x53, 0x8d, 0x47, 0x8d, 0xb6, 0x41, 0xf7, 0x66, 0xbb, 0x5b, 0x6f, 0xec, 0x7e, 0xd5, 0x18, 0x1e, 0x35, - 0xc8, 0xb4, 0xda, 0xe1, 0x6b, 0xd9, 0x68, 0xac, 0xeb, 0xf7, 0xa5, 0x7e, 0x13, 0xd7, 0xee, 0x2f, 0x9e, 0x6e, 0x99, - 0x78, 0xf8, 0xd3, 0xb7, 0x3a, 0x6f, 0x45, 0xc2, 0x85, 0x60, 0x05, 0x9c, 0xb0, 0x44, 0x63, 0xb1, 0x5e, 0x97, 0xa7, - 0xfe, 0xef, 0xda, 0xda, 0x8c, 0x11, 0x0e, 0x74, 0xc8, 0x48, 0x6d, 0x58, 0xe2, 0x02, 0x53, 0x43, 0x45, 0x08, 0x21, - 0x1f, 0xb4, 0x37, 0x8f, 0xd1, 0x86, 0x24, 0x65, 0x24, 0x38, 0xbb, 0x63, 0x45, 0x58, 0x72, 0x7d, 0xef, 0xb1, 0xfc, - 0xae, 0x48, 0xd7, 0x17, 0x83, 0xd4, 0x14, 0xcb, 0x1d, 0x21, 0xcb, 0xc9, 0x57, 0x90, 0x73, 0xca, 0x0b, 0x96, 0xc4, - 0x10, 0xc4, 0x27, 0xbc, 0x60, 0x86, 0x71, 0xbf, 0xe7, 0xe5, 0xc6, 0xac, 0xce, 0x69, 0x66, 0xa1, 0xf6, 0x07, 0xa0, - 0x99, 0x83, 0x72, 0x48, 0x92, 0xad, 0x62, 0xd7, 0xf7, 0x1e, 0xbe, 0xde, 0x25, 0x43, 0xaf, 0x56, 0x4e, 0x7a, 0xce, - 0x80, 0xf5, 0xc1, 0x79, 0x35, 0xd4, 0xcc, 0xfd, 0x48, 0xe3, 0xcc, 0x30, 0x51, 0x79, 0xcc, 0x01, 0x99, 0xae, 0xef, - 0x3d, 0x7c, 0x17, 0x73, 0xa3, 0x9b, 0x42, 0x38, 0x9c, 0x77, 0x5c, 0x90, 0x98, 0x12, 0x86, 0xec, 0xe4, 0x4b, 0x3a, - 0x56, 0x04, 0xa7, 0x7b, 0x4a, 0x4d, 0x26, 0x88, 0x1d, 0x7d, 0x31, 0x20, 0x99, 0x03, 0x01, 0xc9, 0x10, 0xce, 0x6a, - 0x72, 0x1d, 0x31, 0x6b, 0x60, 0x3a, 0xbb, 0x82, 0xc5, 0x48, 0x2c, 0x7b, 0x88, 0x70, 0x66, 0xba, 0xd5, 0x6b, 0x7b, - 0x9c, 0x28, 0xba, 0x69, 0xe8, 0x56, 0xc9, 0xb3, 0xef, 0x41, 0xf0, 0xf2, 0x1f, 0xaf, 0x5c, 0xdb, 0x65, 0xc2, 0x13, - 0x6f, 0x91, 0x76, 0x7d, 0xef, 0xe1, 0xaf, 0xce, 0x28, 0x6d, 0x4e, 0x3d, 0xf9, 0xdf, 0x92, 0x51, 0x1f, 0xfe, 0x9a, - 0x54, 0xb9, 0xa6, 0xf0, 0xf5, 0xbd, 0x87, 0xbf, 0xef, 0x2a, 0x06, 0xe9, 0xeb, 0x45, 0xa5, 0x24, 0x30, 0xe3, 0x5b, - 0xb2, 0x3c, 0x5d, 0xba, 0xb3, 0x22, 0x15, 0x6b, 0x6c, 0x4e, 0xa8, 0x54, 0xad, 0x4b, 0xdd, 0xca, 0x13, 0x2c, 0x89, - 0xb9, 0x4a, 0xaa, 0x2f, 0x9b, 0x43, 0x63, 0x2e, 0xc5, 0x55, 0x26, 0xe7, 0xec, 0x1b, 0xf7, 0x4b, 0x4f, 0x35, 0x4a, - 0xf8, 0x0c, 0x0c, 0x71, 0xcc, 0xd8, 0x05, 0xde, 0x6f, 0xa1, 0xee, 0xc6, 0x79, 0x26, 0x0d, 0xa2, 0x16, 0xf5, 0xc3, - 0x06, 0x53, 0xd2, 0xc2, 0x19, 0x69, 0xe1, 0x9c, 0xa8, 0x7e, 0xcb, 0x9e, 0x18, 0xdd, 0xbc, 0x6c, 0xda, 0x9e, 0x3b, - 0xb0, 0xdd, 0x73, 0xbb, 0x6f, 0xed, 0xa1, 0x3c, 0xed, 0xe6, 0x46, 0x7f, 0x69, 0x0e, 0xfa, 0xa9, 0x41, 0x8d, 0x27, - 0x2c, 0x2e, 0x70, 0x61, 0x5a, 0xbe, 0xe2, 0xc3, 0x1c, 0xec, 0x54, 0x60, 0x66, 0x58, 0xa3, 0xb4, 0x2c, 0xdb, 0x76, - 0x65, 0xf3, 0xc4, 0xac, 0x55, 0x81, 0xf3, 0x04, 0x48, 0x39, 0xce, 0x9d, 0x5d, 0x8f, 0xda, 0xae, 0x72, 0x76, 0x70, - 0x10, 0xbb, 0x4a, 0x34, 0x2e, 0x7c, 0x7e, 0x75, 0x03, 0xf8, 0xde, 0x52, 0x8d, 0x29, 0x32, 0x13, 0x68, 0x34, 0xb2, - 0xc1, 0x9a, 0xee, 0x13, 0x12, 0xe7, 0x75, 0x28, 0xfa, 0xd1, 0x1b, 0x66, 0x70, 0x03, 0x00, 0x8d, 0x46, 0x79, 0xdd, - 0xbb, 0x01, 0xb1, 0xa7, 0x1a, 0xcb, 0xf5, 0xd7, 0xb8, 0xb4, 0x26, 0x6a, 0x6d, 0xd9, 0x61, 0xf9, 0x51, 0x20, 0x11, - 0xe2, 0xae, 0xf0, 0xf3, 0x09, 0xb6, 0x86, 0x80, 0x72, 0x2f, 0x9c, 0x0d, 0x04, 0x36, 0x56, 0x5b, 0xae, 0x90, 0x27, - 0x6d, 0x1d, 0x94, 0xfa, 0x42, 0x70, 0xc1, 0x05, 0x85, 0x1a, 0x6b, 0x87, 0xe5, 0x4f, 0xd8, 0xb6, 0x39, 0x27, 0x56, - 0xc8, 0x69, 0xcb, 0xcc, 0x30, 0x0c, 0xc0, 0x3a, 0x25, 0x60, 0x9e, 0x93, 0x97, 0xdf, 0x46, 0xfd, 0x87, 0x01, 0xea, - 0x3f, 0x22, 0x2c, 0xd8, 0x06, 0x56, 0x57, 0x92, 0x48, 0xa7, 0xa0, 0x50, 0x3e, 0xeb, 0xf1, 0x9c, 0x80, 0x36, 0xae, - 0x0e, 0xd5, 0xda, 0x15, 0xe5, 0x37, 0x28, 0x4b, 0xb8, 0x53, 0x8c, 0x3e, 0x13, 0xfb, 0xfb, 0xe4, 0xb8, 0xba, 0xa0, - 0x83, 0xae, 0x77, 0x29, 0x07, 0x43, 0x52, 0xf8, 0xf0, 0xf7, 0xef, 0xdf, 0xad, 0x3e, 0x9e, 0x6f, 0xef, 0xe0, 0xc0, - 0xac, 0x14, 0x66, 0x1d, 0x6c, 0xe0, 0xba, 0x91, 0x29, 0xf4, 0x5f, 0xde, 0x89, 0xd7, 0xa9, 0xd0, 0xc6, 0x66, 0xf4, - 0xc7, 0x21, 0x8c, 0xb6, 0xdd, 0x36, 0x25, 0x58, 0xd0, 0x2c, 0xd0, 0x25, 0x6b, 0xdc, 0x4a, 0x8b, 0x6f, 0x90, 0x91, - 0x87, 0xa6, 0x00, 0x13, 0xa3, 0xdd, 0xd9, 0x8f, 0xd6, 0x0e, 0x4f, 0xec, 0xd0, 0xd0, 0xd2, 0x10, 0x42, 0x8b, 0xf7, - 0x80, 0x39, 0xf6, 0x88, 0x00, 0x10, 0xbd, 0x34, 0x90, 0xaa, 0x40, 0x16, 0x45, 0x95, 0x22, 0xff, 0xf9, 0x3e, 0x21, - 0x2f, 0x2b, 0x45, 0xe6, 0xdb, 0xca, 0x98, 0x0b, 0x10, 0x03, 0xa5, 0x70, 0x91, 0x50, 0x26, 0xd8, 0xcb, 0xd0, 0x0f, - 0xda, 0x97, 0x37, 0xd2, 0x66, 0x52, 0x71, 0xe3, 0xc1, 0x4d, 0xa9, 0x51, 0xf1, 0xd9, 0x7c, 0x0f, 0x89, 0x8d, 0xdc, - 0x7b, 0x90, 0xcb, 0xa8, 0x19, 0x24, 0x7c, 0xbf, 0x33, 0xa5, 0x7d, 0xbb, 0xeb, 0x2f, 0x9b, 0x16, 0x31, 0x1b, 0xeb, - 0x92, 0x70, 0xa1, 0x58, 0xa1, 0x1f, 0xb1, 0xb1, 0x2c, 0xe0, 0xfe, 0xa3, 0x04, 0x0b, 0x5a, 0xdf, 0x0b, 0x74, 0x80, - 0x66, 0x82, 0xc1, 0xa5, 0xc3, 0xc6, 0x0c, 0xcd, 0xaf, 0x2f, 0xe6, 0x0e, 0xfc, 0x7a, 0xb3, 0xd6, 0xcb, 0x83, 0x83, - 0xaf, 0xac, 0x02, 0x94, 0x1b, 0xa6, 0x19, 0x46, 0x40, 0xbc, 0x2c, 0x97, 0xe3, 0x6e, 0x86, 0xef, 0xc5, 0x95, 0xca, - 0xc0, 0x13, 0x8e, 0x90, 0x08, 0x3d, 0x27, 0x7a, 0x3d, 0xd9, 0xa4, 0xf7, 0x4e, 0x9b, 0x21, 0x42, 0xb1, 0x06, 0xc8, - 0x3d, 0xc8, 0xe5, 0x56, 0xc9, 0xa4, 0x2a, 0x5b, 0xdb, 0x72, 0x10, 0x8f, 0x01, 0x5c, 0xb1, 0x11, 0x52, 0x02, 0x34, - 0xdc, 0x2d, 0xb4, 0x3c, 0x97, 0xc0, 0xfe, 0x63, 0x95, 0x80, 0x48, 0x8b, 0x6a, 0x1b, 0x17, 0x21, 0x6c, 0x4d, 0x7d, - 0x02, 0xe3, 0x84, 0x87, 0xcf, 0x77, 0x69, 0xa8, 0x3d, 0x6a, 0x33, 0x73, 0x06, 0x41, 0x09, 0x89, 0xca, 0x0a, 0xc9, - 0xd7, 0x58, 0x38, 0x6e, 0xce, 0xdf, 0xc3, 0x01, 0x29, 0x56, 0x34, 0xb6, 0x77, 0x5b, 0x70, 0x7c, 0x14, 0xc9, 0x22, - 0xae, 0x75, 0xdd, 0x2d, 0x4c, 0x35, 0xec, 0x40, 0x47, 0x43, 0x38, 0x15, 0xe6, 0x9e, 0xf0, 0x71, 0x45, 0x52, 0x7f, - 0xb6, 0x26, 0xda, 0xda, 0x13, 0xc3, 0xca, 0x34, 0x25, 0x98, 0xff, 0xcf, 0xd6, 0xea, 0xba, 0x2c, 0x84, 0x99, 0x19, - 0xc6, 0x8d, 0x5d, 0x05, 0xb6, 0x06, 0x1c, 0x5b, 0x7e, 0x96, 0xc1, 0xa2, 0x7a, 0xa5, 0xb8, 0xe9, 0x34, 0x60, 0x02, - 0xde, 0x82, 0xf5, 0xcc, 0xe6, 0xd6, 0x7f, 0x6e, 0x0e, 0x46, 0x81, 0x55, 0x8d, 0xc0, 0x4b, 0x43, 0xe0, 0x11, 0x30, - 0x6e, 0xde, 0xb4, 0xbc, 0xe7, 0x8c, 0x68, 0x84, 0x3f, 0xf1, 0x1c, 0x9e, 0x59, 0x96, 0x7b, 0xeb, 0x63, 0x63, 0x45, - 0x52, 0x41, 0xc0, 0xb6, 0x08, 0x3b, 0x22, 0x2f, 0x11, 0x56, 0x8d, 0x46, 0x57, 0x5d, 0xb0, 0x4a, 0xab, 0x52, 0x0d, - 0x53, 0xc0, 0x2d, 0x31, 0xe0, 0x7d, 0xed, 0x44, 0x05, 0x43, 0x02, 0x6f, 0xfd, 0xad, 0x40, 0x7d, 0xff, 0xf0, 0x6d, - 0x1c, 0xd2, 0xb7, 0xb0, 0x6c, 0x79, 0x11, 0x0b, 0x53, 0x8a, 0xab, 0x3b, 0x9c, 0x37, 0xdf, 0x37, 0x1b, 0x81, 0x71, - 0xef, 0xb7, 0x31, 0xd8, 0xb8, 0xa1, 0xae, 0xb6, 0xa4, 0xa1, 0xdc, 0x84, 0x5d, 0x54, 0xd9, 0x3b, 0x86, 0x9d, 0x75, - 0x75, 0x25, 0xed, 0x6a, 0xa2, 0xd6, 0x6b, 0xc5, 0x2a, 0xa3, 0x81, 0x0d, 0xc3, 0x4e, 0x73, 0xcc, 0x6c, 0x2b, 0xf0, - 0x1f, 0xcf, 0x89, 0xc6, 0x01, 0xb2, 0xbe, 0xf9, 0xd6, 0x75, 0x4a, 0x35, 0x4c, 0xd8, 0xde, 0xee, 0x7c, 0x7c, 0xcc, - 0x77, 0x9d, 0x8f, 0x58, 0xba, 0xad, 0x6f, 0xce, 0xc6, 0xf6, 0xbf, 0x71, 0x36, 0x3a, 0xb5, 0xbd, 0x3f, 0x1e, 0x81, - 0x3b, 0xa9, 0x1d, 0x8f, 0xf5, 0x35, 0x25, 0x12, 0x0b, 0xb7, 0x1c, 0x97, 0x9d, 0xd5, 0x4a, 0xf4, 0x5b, 0xa0, 0x76, - 0x8a, 0x22, 0xf8, 0xd9, 0xb6, 0x3f, 0x03, 0x92, 0x6c, 0x75, 0xc8, 0xb1, 0x28, 0x45, 0x19, 0x94, 0x80, 0x01, 0x75, - 0x6c, 0x6c, 0xbd, 0x0c, 0x62, 0x3b, 0x1c, 0x72, 0x58, 0x4e, 0x44, 0x79, 0x75, 0x05, 0x23, 0x36, 0xc7, 0x86, 0x13, - 0x30, 0xe3, 0x9d, 0x56, 0x85, 0x5e, 0xfc, 0xfc, 0xd7, 0xcc, 0x69, 0xed, 0x88, 0xb1, 0x9c, 0x44, 0xcd, 0x8a, 0xc1, - 0x8d, 0xc0, 0x31, 0x8c, 0xfb, 0x46, 0x42, 0xad, 0x4e, 0x75, 0x54, 0x3b, 0x92, 0x70, 0x0b, 0xd4, 0x6e, 0xfb, 0xe6, - 0x5c, 0x5a, 0xad, 0x76, 0x1e, 0x2c, 0xb8, 0x08, 0x70, 0xfb, 0x39, 0xd1, 0x35, 0x92, 0x42, 0x89, 0x93, 0xa0, 0x70, - 0x6e, 0x50, 0x55, 0x13, 0xd9, 0x6f, 0x0d, 0x80, 0x27, 0xed, 0x66, 0x17, 0xb2, 0x12, 0x92, 0xb3, 0x46, 0x03, 0xe5, - 0x65, 0xc7, 0xb4, 0x2f, 0x1a, 0xd9, 0x00, 0x33, 0x9c, 0x59, 0x81, 0x05, 0x4e, 0xaf, 0x38, 0xaf, 0xba, 0xee, 0x67, - 0x03, 0x84, 0x8b, 0xd5, 0x2a, 0xb6, 0x43, 0xcb, 0xd1, 0x6a, 0x95, 0x87, 0x43, 0x33, 0xf9, 0x50, 0xf1, 0x65, 0x4f, - 0x93, 0x97, 0xe6, 0x3c, 0x7c, 0x09, 0x83, 0x6c, 0x90, 0x38, 0x77, 0x2a, 0xc1, 0x1c, 0x34, 0x57, 0x0d, 0xd9, 0xcf, - 0x1a, 0xed, 0x41, 0x40, 0xc3, 0xfa, 0xd9, 0x80, 0xe4, 0x6b, 0xb0, 0x9c, 0x55, 0xee, 0xc0, 0xfc, 0x0c, 0x07, 0xdb, - 0x67, 0x73, 0xce, 0xd8, 0x06, 0xc3, 0x35, 0xd9, 0x54, 0x19, 0x94, 0x78, 0xe5, 0x16, 0xd7, 0x97, 0xab, 0x19, 0x58, - 0x94, 0x85, 0xb0, 0xbb, 0x66, 0xee, 0x81, 0xf0, 0x5f, 0x62, 0xbb, 0xa4, 0xa5, 0x11, 0xf7, 0x06, 0xe2, 0x7b, 0xdb, - 0xed, 0x24, 0x49, 0x68, 0x31, 0x31, 0x57, 0x22, 0xfe, 0x86, 0xd7, 0xec, 0x81, 0x63, 0x37, 0xce, 0xa0, 0xe7, 0x7e, - 0xd9, 0xd9, 0x80, 0xd8, 0xf1, 0x7b, 0x66, 0xc7, 0x3b, 0xae, 0x14, 0x74, 0xb7, 0x2e, 0xc2, 0x0e, 0x86, 0xfe, 0x2f, - 0x0f, 0xe6, 0xc4, 0x0d, 0xc6, 0xa2, 0xc9, 0x06, 0xdc, 0xbe, 0x01, 0x8f, 0x82, 0x6e, 0xc0, 0xed, 0xdb, 0xf0, 0xf5, - 0xd0, 0xca, 0xbe, 0x39, 0xc0, 0x80, 0x4c, 0xd8, 0x91, 0x56, 0x09, 0xc1, 0x30, 0x4f, 0x37, 0x39, 0x32, 0x4b, 0x56, - 0xe1, 0x70, 0xd5, 0x24, 0x16, 0x1b, 0x7b, 0xa1, 0x62, 0x52, 0x03, 0xc1, 0x58, 0xa4, 0x2f, 0x51, 0xa8, 0x34, 0xa8, - 0x1b, 0xc7, 0x00, 0x56, 0x39, 0x6d, 0xfd, 0xcb, 0x83, 0x03, 0x10, 0x1a, 0x80, 0xb5, 0x4b, 0x32, 0x3a, 0xd7, 0x8b, - 0x02, 0xf8, 0x2b, 0xe5, 0x7f, 0x43, 0x32, 0xb8, 0x9d, 0x98, 0x34, 0xf8, 0x01, 0x09, 0x73, 0xaa, 0x14, 0xff, 0x6a, - 0xd3, 0xdc, 0x6f, 0x5c, 0x10, 0x8f, 0xd1, 0xca, 0x72, 0x8a, 0x12, 0x75, 0xa5, 0x43, 0xd7, 0x3a, 0xe4, 0x9e, 0x7e, - 0x65, 0x42, 0xbf, 0xe4, 0x4a, 0x33, 0x01, 0x00, 0xa8, 0x10, 0x0f, 0xa6, 0xa4, 0x10, 0x6c, 0xdd, 0x5a, 0x2d, 0x3a, - 0x1a, 0x7d, 0xb7, 0x8a, 0xae, 0xb3, 0x45, 0x53, 0x2a, 0x46, 0xb9, 0xed, 0x24, 0xb4, 0x99, 0xf4, 0x76, 0xa2, 0x65, - 0xc9, 0xd0, 0x62, 0xa7, 0x62, 0x3f, 0x0c, 0xad, 0x8f, 0x05, 0xf1, 0xe7, 0x82, 0x3f, 0x4b, 0xbf, 0xcb, 0xc7, 0xc0, - 0x95, 0xfa, 0x37, 0x56, 0x21, 0x9c, 0x09, 0xd6, 0x01, 0x79, 0x4d, 0xea, 0xe3, 0xf4, 0xa8, 0x93, 0x6f, 0x29, 0x17, - 0x4a, 0xa3, 0xb0, 0x8d, 0x93, 0xc2, 0x60, 0xca, 0xd9, 0xb7, 0x25, 0xae, 0x5f, 0xfd, 0x31, 0xe2, 0x8f, 0x0e, 0xf1, - 0xef, 0x52, 0x69, 0xb4, 0x2c, 0x11, 0x0c, 0xf9, 0x1d, 0xa9, 0x15, 0x5c, 0xc5, 0xe6, 0x5c, 0x3f, 0xd7, 0xb3, 0x7c, - 0xc3, 0x13, 0xa7, 0xab, 0x55, 0x29, 0x15, 0xa8, 0xf8, 0x86, 0xe1, 0x27, 0x0c, 0xee, 0x8d, 0x9f, 0xf1, 0xa0, 0xca, - 0xf6, 0x7d, 0xf1, 0xb3, 0xe0, 0xbe, 0xf8, 0x19, 0x4f, 0xb7, 0x8b, 0x06, 0xf7, 0xc4, 0x9d, 0xe4, 0x3c, 0x69, 0x45, - 0x9e, 0x8f, 0x9a, 0xd2, 0xca, 0xbf, 0xd2, 0x6e, 0x0d, 0x5c, 0xd9, 0xc4, 0x81, 0x71, 0x5e, 0x5d, 0x84, 0x62, 0xce, - 0x9c, 0xd1, 0x72, 0xf8, 0xdf, 0x5a, 0x27, 0x77, 0xf2, 0x48, 0x2b, 0x85, 0xbc, 0xa1, 0x85, 0xbe, 0x07, 0x1b, 0xae, - 0xd8, 0xf2, 0x01, 0xa4, 0x04, 0x94, 0x6d, 0xff, 0x5e, 0x17, 0x81, 0x38, 0xae, 0xac, 0xf3, 0x51, 0xd8, 0x3e, 0x29, - 0x4a, 0xae, 0xae, 0x2e, 0x84, 0xdc, 0x1a, 0x2d, 0x01, 0xc2, 0xd4, 0xbb, 0xe6, 0x31, 0x47, 0x93, 0x59, 0xba, 0x5c, - 0x97, 0xaa, 0x83, 0xc2, 0x72, 0x75, 0x1c, 0xe1, 0x62, 0x6d, 0x6e, 0xd0, 0xff, 0xe1, 0xf8, 0x33, 0x77, 0x34, 0xf2, - 0xe7, 0x92, 0x02, 0xbd, 0xdf, 0xed, 0x6b, 0xb3, 0x83, 0x44, 0xda, 0x39, 0x94, 0x96, 0x02, 0x80, 0xd5, 0x06, 0x5f, - 0xd7, 0x1e, 0xa7, 0x9e, 0x48, 0x37, 0x9b, 0x6f, 0x1a, 0xc2, 0x62, 0x56, 0x5a, 0xf0, 0x98, 0x6e, 0x76, 0x58, 0x8e, - 0x7a, 0x59, 0x5c, 0x97, 0x7b, 0xac, 0xd6, 0x2f, 0xfa, 0x06, 0x28, 0x2b, 0x43, 0xb4, 0xd5, 0x2a, 0xae, 0xc3, 0x9b, - 0x88, 0xe0, 0x1a, 0x04, 0x61, 0x11, 0x18, 0x70, 0xd4, 0x18, 0x6f, 0x5b, 0x27, 0x46, 0x9b, 0xf6, 0x4b, 0x9e, 0x75, - 0xaf, 0x8d, 0x23, 0x54, 0x34, 0xd8, 0xea, 0xa1, 0xe6, 0x01, 0xdb, 0xd9, 0x95, 0x1d, 0x05, 0x10, 0x9a, 0x52, 0x6f, - 0x9c, 0x5b, 0x59, 0xd1, 0xee, 0x80, 0x2f, 0xfa, 0x8e, 0x79, 0xae, 0x03, 0xdd, 0x76, 0x7e, 0x60, 0xdb, 0xf4, 0x44, - 0x7e, 0xcb, 0xb6, 0xa9, 0xc6, 0x09, 0xef, 0xb7, 0xd0, 0xf7, 0x0d, 0x61, 0x6d, 0x5f, 0xbb, 0x8b, 0xfc, 0x2f, 0x74, - 0xd7, 0x06, 0xf4, 0xb4, 0x60, 0xf6, 0x34, 0xe6, 0x83, 0x5e, 0xaf, 0x7f, 0x2e, 0xfd, 0x17, 0x8c, 0xad, 0xd0, 0xcf, - 0x76, 0x17, 0x38, 0xb1, 0xd2, 0x38, 0x04, 0xc7, 0xff, 0x70, 0x32, 0xc9, 0xe5, 0x90, 0xe6, 0xef, 0xa0, 0xc7, 0x2a, - 0xf7, 0xf9, 0xdd, 0xa8, 0xa0, 0x9a, 0x39, 0x5a, 0x53, 0x8d, 0xe2, 0x1f, 0x1e, 0x0c, 0xe3, 0x1f, 0x6e, 0x29, 0x77, - 0xd5, 0x02, 0x5e, 0xbe, 0x2c, 0x9b, 0x48, 0x7f, 0x5e, 0x97, 0x32, 0x98, 0xda, 0xdd, 0xcb, 0x26, 0x49, 0x63, 0x25, - 0x49, 0x63, 0x2a, 0xde, 0x6c, 0x2a, 0x8e, 0x3f, 0x7f, 0x63, 0xb0, 0xdb, 0x64, 0xee, 0x73, 0x40, 0xe6, 0x3e, 0xf3, - 0xf4, 0xbb, 0xb5, 0x02, 0x8a, 0x77, 0x9c, 0x1c, 0x1b, 0xcb, 0x18, 0x3b, 0xea, 0xb7, 0x1a, 0x0c, 0x1a, 0x34, 0xb9, - 0x0c, 0xbc, 0x1d, 0xaa, 0xd3, 0xcb, 0xdb, 0x1f, 0xc5, 0xd9, 0x42, 0x69, 0x39, 0x73, 0x8d, 0x2a, 0xe7, 0xe3, 0x64, - 0x32, 0x41, 0x81, 0x6d, 0xee, 0xf0, 0xd3, 0xba, 0x1b, 0xd9, 0xf2, 0x0b, 0x17, 0xa3, 0x54, 0x61, 0x77, 0xb6, 0xa8, - 0x54, 0xae, 0x89, 0x37, 0x73, 0xde, 0xce, 0xc3, 0x63, 0x2e, 0xb8, 0x9a, 0xb2, 0x22, 0x2e, 0xd0, 0xf2, 0x5b, 0x9d, - 0x15, 0x70, 0x9b, 0x63, 0x3b, 0xc3, 0xa3, 0xd2, 0x72, 0x40, 0x27, 0xd0, 0x1a, 0xe8, 0x8c, 0x66, 0x4c, 0x4f, 0xe5, - 0x08, 0x0c, 0x5f, 0x92, 0x51, 0xe9, 0x4e, 0x75, 0x70, 0xb0, 0x1f, 0x47, 0x46, 0x7f, 0x01, 0x3e, 0xe8, 0x61, 0x0e, - 0xea, 0x2d, 0xc1, 0x31, 0xa8, 0xea, 0x9a, 0xa1, 0x25, 0xdb, 0xf4, 0xa1, 0xd1, 0xc9, 0x17, 0x76, 0x87, 0x39, 0x5a, - 0xaf, 0x53, 0x3b, 0xea, 0x68, 0xcc, 0x59, 0x3e, 0x8a, 0xf0, 0x17, 0x76, 0x97, 0x96, 0x6e, 0xeb, 0xc6, 0xcb, 0xda, - 0x2c, 0x62, 0x24, 0x6f, 0x44, 0x84, 0xab, 0x4e, 0xd2, 0xe5, 0x1a, 0xcb, 0x82, 0x4f, 0x00, 0x47, 0x7f, 0x61, 0x77, - 0xa9, 0x6b, 0x2f, 0x70, 0x15, 0x44, 0x4b, 0x0f, 0xfa, 0x24, 0x48, 0x0e, 0x97, 0xc1, 0x09, 0x1c, 0x7d, 0x53, 0x77, - 0x40, 0x6a, 0xe5, 0x2a, 0x11, 0x12, 0xa1, 0xf5, 0xbf, 0x3b, 0x15, 0xbc, 0x08, 0xcf, 0x39, 0x5d, 0xb3, 0xb8, 0xdd, - 0xa8, 0xc4, 0xa0, 0x42, 0x65, 0x41, 0xf2, 0x31, 0xe6, 0x7e, 0xf7, 0x39, 0xef, 0x87, 0x40, 0x67, 0xb6, 0xa0, 0xae, - 0xd1, 0x74, 0x64, 0x7e, 0xa1, 0xea, 0x0e, 0x6a, 0xa6, 0xab, 0x8a, 0x7b, 0x1f, 0x63, 0x00, 0x3c, 0x58, 0xcb, 0x50, - 0xe3, 0x10, 0xba, 0xf6, 0x66, 0xaa, 0x63, 0x4a, 0xe2, 0xa5, 0x9f, 0x43, 0xca, 0x43, 0x30, 0xea, 0x35, 0xa0, 0xa1, - 0x43, 0x30, 0x6b, 0x79, 0xc8, 0xc7, 0xb1, 0xd8, 0x3a, 0x43, 0xa5, 0x39, 0x43, 0x93, 0x00, 0xe4, 0xdf, 0x38, 0x33, - 0x99, 0x81, 0x86, 0xe1, 0x2d, 0xcd, 0x01, 0xe8, 0x56, 0xd7, 0xe1, 0x50, 0xb8, 0xa2, 0xa5, 0xf3, 0x9e, 0x5d, 0x74, - 0x59, 0x1b, 0x56, 0x6c, 0xda, 0x41, 0xeb, 0x14, 0xa6, 0xc4, 0x6c, 0x81, 0xb5, 0xd7, 0xfb, 0x70, 0x6f, 0x57, 0x1b, - 0x17, 0x89, 0x9f, 0x16, 0xf1, 0x30, 0x89, 0x29, 0x5a, 0xf2, 0x98, 0x62, 0x09, 0x76, 0x90, 0xc5, 0xba, 0x1c, 0x3f, - 0x0b, 0x97, 0xa3, 0x66, 0x25, 0xbd, 0xdb, 0xc1, 0x10, 0xb8, 0x7c, 0x0d, 0xb6, 0xa1, 0x98, 0x7b, 0xc2, 0xc2, 0x63, - 0xe3, 0xe9, 0x17, 0xac, 0xdb, 0xdc, 0x2e, 0x88, 0x5f, 0x81, 0x31, 0x8d, 0x97, 0xc1, 0x2c, 0x42, 0xa7, 0x72, 0xe7, - 0x70, 0xe8, 0xae, 0x09, 0x2b, 0xe3, 0xd5, 0x58, 0x91, 0x8d, 0xa3, 0xe7, 0xfb, 0x36, 0x9e, 0x7f, 0x2f, 0x58, 0x71, - 0x77, 0xc5, 0xc0, 0xc6, 0x5a, 0x82, 0xbb, 0x71, 0xb5, 0x0c, 0x95, 0x81, 0x7c, 0x4f, 0x1a, 0xd6, 0x65, 0x8d, 0xbf, - 0x1b, 0x15, 0x63, 0x6d, 0xee, 0x29, 0x03, 0x6d, 0x8d, 0xdd, 0x2e, 0xec, 0x9b, 0xae, 0x9b, 0xac, 0x6b, 0x14, 0x71, - 0x15, 0xa4, 0xdd, 0xdd, 0x02, 0x2e, 0x42, 0x7f, 0xd8, 0xbe, 0x1a, 0x6c, 0xaa, 0x6e, 0x20, 0x09, 0xae, 0xfd, 0xe4, - 0xb7, 0xa7, 0xba, 0xcb, 0x5a, 0xf7, 0xdb, 0x53, 0xad, 0x5d, 0x16, 0x1a, 0x43, 0x22, 0xec, 0xfa, 0x29, 0xfd, 0xa7, - 0xc5, 0x7a, 0x8d, 0xd6, 0x30, 0xbc, 0x47, 0xbc, 0x1b, 0xc7, 0x8f, 0xbc, 0x85, 0x62, 0x02, 0x17, 0xb9, 0x57, 0xb9, - 0xf4, 0x84, 0xbc, 0x1a, 0xc1, 0x23, 0xbe, 0x35, 0x84, 0x47, 0x3c, 0x70, 0x7a, 0x05, 0xa9, 0x69, 0x22, 0xd8, 0xc8, - 0xd3, 0x4f, 0x64, 0x91, 0xd0, 0xf0, 0x71, 0xaf, 0x39, 0x11, 0xfa, 0x53, 0x0a, 0xfc, 0x17, 0x1e, 0x2e, 0xb4, 0x96, - 0x02, 0x73, 0x31, 0x5f, 0x68, 0xac, 0xcc, 0xe8, 0x97, 0x63, 0x29, 0x74, 0x73, 0x4c, 0x67, 0x3c, 0xbf, 0x4b, 0x17, - 0xbc, 0x39, 0x93, 0x42, 0xaa, 0x39, 0xcd, 0x18, 0x56, 0x77, 0x4a, 0xb3, 0x59, 0x73, 0xc1, 0xf1, 0x73, 0x96, 0x7f, - 0x65, 0x9a, 0x67, 0x14, 0xbf, 0x95, 0x43, 0xa9, 0x25, 0x7e, 0x7d, 0x7b, 0x37, 0x61, 0x02, 0xff, 0x3e, 0x5c, 0x08, - 0xbd, 0xc0, 0x8a, 0x0a, 0xd5, 0x54, 0xac, 0xe0, 0xe3, 0x6e, 0xb3, 0x39, 0x2f, 0xf8, 0x8c, 0x16, 0x77, 0xcd, 0x4c, - 0xe6, 0xb2, 0x48, 0xff, 0xab, 0x75, 0x4c, 0x1f, 0x8c, 0x4f, 0xba, 0xba, 0xa0, 0x42, 0x71, 0x58, 0x98, 0x94, 0xe6, - 0xf9, 0xde, 0xf1, 0x69, 0x6b, 0xa6, 0xf6, 0xed, 0x85, 0x1f, 0x15, 0x7a, 0xfd, 0x09, 0x7f, 0x90, 0x30, 0xca, 0x64, - 0xa8, 0x85, 0x1b, 0xe4, 0x32, 0x5b, 0x14, 0x4a, 0x16, 0xe9, 0x5c, 0x72, 0xa1, 0x59, 0xd1, 0x1d, 0xca, 0x62, 0xc4, - 0x8a, 0x66, 0x41, 0x47, 0x7c, 0xa1, 0xd2, 0x93, 0xf9, 0x6d, 0xb7, 0xde, 0x83, 0xcd, 0x4f, 0x85, 0x14, 0xac, 0x0b, - 0xfc, 0xc6, 0xa4, 0x90, 0x0b, 0x31, 0x72, 0xc3, 0x58, 0x08, 0xc5, 0x74, 0x77, 0x4e, 0x47, 0x60, 0x07, 0x9c, 0x9e, - 0xcf, 0x6f, 0xbb, 0x66, 0xd6, 0x37, 0x8c, 0x4f, 0xa6, 0x3a, 0x3d, 0x6d, 0xb5, 0xec, 0xb7, 0xe2, 0xff, 0xb0, 0xb4, - 0xdd, 0x49, 0x3a, 0xa7, 0xf3, 0x5b, 0xe0, 0xe0, 0x35, 0x2b, 0x9a, 0x00, 0x0b, 0xa8, 0xd4, 0x4e, 0x5a, 0x0f, 0x8e, - 0xef, 0x43, 0x06, 0xd8, 0x38, 0x34, 0xcd, 0x84, 0xc0, 0xd8, 0x3d, 0x5d, 0xcc, 0xe7, 0xac, 0x00, 0x2f, 0xfa, 0xee, - 0x8c, 0x16, 0x13, 0x2e, 0x9a, 0x85, 0x69, 0xb4, 0x79, 0x3e, 0xbf, 0x5d, 0xc3, 0x7c, 0x52, 0x6b, 0xb6, 0xea, 0xa6, - 0xe5, 0xbe, 0x96, 0xc1, 0x10, 0x4d, 0x4c, 0x9a, 0xb4, 0x98, 0x0c, 0x69, 0xdc, 0xee, 0xdc, 0xc7, 0xfe, 0x7f, 0x49, - 0x07, 0x05, 0x60, 0x6b, 0x8e, 0x16, 0x85, 0xb9, 0x45, 0x4d, 0xdb, 0xca, 0x36, 0x3b, 0x95, 0x5f, 0x59, 0xe1, 0x5b, - 0x35, 0x1f, 0xcb, 0xad, 0x79, 0xff, 0x47, 0x8d, 0x52, 0xdb, 0xd6, 0x0b, 0x75, 0x05, 0x34, 0x7a, 0xbb, 0xb1, 0xff, - 0xea, 0x9c, 0xd3, 0xfb, 0x27, 0xa7, 0x1e, 0xee, 0xe3, 0xf1, 0xb8, 0x06, 0x74, 0x0f, 0xdd, 0x76, 0x6b, 0x7e, 0xbb, - 0xd7, 0x69, 0x79, 0x18, 0x5b, 0x98, 0x9e, 0xcd, 0x6f, 0x77, 0xac, 0x60, 0x80, 0x15, 0x9b, 0xbd, 0xed, 0x25, 0xc7, - 0x6a, 0x8f, 0x51, 0xc5, 0xd6, 0x9f, 0xf0, 0x84, 0x02, 0x6e, 0x18, 0xa4, 0xed, 0x1b, 0x39, 0x15, 0x56, 0x60, 0xb0, - 0xbc, 0xe1, 0x23, 0x3d, 0x4d, 0xdb, 0xad, 0xd6, 0x0f, 0x15, 0x26, 0x75, 0xa7, 0x76, 0x49, 0xdb, 0x05, 0x9b, 0xd5, - 0xf0, 0x6b, 0x46, 0xcb, 0x5d, 0xb0, 0x9c, 0x4b, 0xd7, 0x69, 0xc1, 0x72, 0x13, 0xe5, 0x66, 0xed, 0xb6, 0xc2, 0xd6, - 0x94, 0xb9, 0x98, 0xb2, 0x82, 0xeb, 0x6e, 0xfd, 0xab, 0xea, 0x78, 0x7b, 0x4e, 0x6b, 0x2b, 0x1f, 0x2f, 0x6d, 0x0d, - 0x77, 0x19, 0xfb, 0x18, 0x3e, 0xb6, 0xb1, 0xf2, 0x2b, 0x2d, 0xe2, 0x8d, 0x0d, 0x83, 0xc3, 0x1a, 0x68, 0x1d, 0xcc, - 0xb9, 0x00, 0x53, 0xd1, 0x01, 0xfe, 0x06, 0x14, 0x32, 0x9a, 0x67, 0x31, 0x8c, 0x68, 0xaf, 0xb9, 0x77, 0x5c, 0xb0, - 0x19, 0xf2, 0x80, 0x48, 0xee, 0x9f, 0x16, 0x6c, 0xb6, 0x4e, 0x4c, 0xf5, 0xa5, 0x41, 0x5d, 0x9a, 0xf3, 0x89, 0x48, - 0x33, 0x06, 0xdb, 0x6a, 0x9d, 0x30, 0xa1, 0xb9, 0xbe, 0x6b, 0x16, 0xf2, 0x66, 0x39, 0xe2, 0x6a, 0x9e, 0xd3, 0xbb, - 0x74, 0x9c, 0xb3, 0xdb, 0xae, 0x29, 0xd5, 0xe4, 0x9a, 0xcd, 0x94, 0x2b, 0xdb, 0x85, 0xf4, 0xe6, 0xc8, 0x9a, 0x73, - 0x00, 0xf4, 0xe4, 0xcd, 0xe6, 0xbe, 0xf6, 0x8b, 0xd6, 0x94, 0x0b, 0xbd, 0xd7, 0x52, 0xdd, 0x19, 0x17, 0x4d, 0x37, - 0x90, 0x13, 0xc0, 0x88, 0x6d, 0xc8, 0x07, 0xfd, 0x27, 0xec, 0x76, 0x4e, 0xc5, 0x88, 0x8d, 0x96, 0x41, 0xb5, 0x0e, - 0xd4, 0x0b, 0x4b, 0xa5, 0x42, 0x4f, 0x9b, 0xc6, 0x06, 0x2d, 0xee, 0x08, 0xf4, 0x0d, 0x94, 0x7f, 0xd0, 0xc2, 0xf6, - 0xff, 0x93, 0x36, 0x0a, 0x2b, 0xef, 0x41, 0x38, 0x28, 0x3e, 0xbe, 0x6b, 0xc2, 0xdf, 0x25, 0xf8, 0x3c, 0xf1, 0x8c, - 0xe6, 0x0e, 0x22, 0x33, 0x3e, 0x1a, 0xe5, 0xb5, 0x11, 0x5d, 0x06, 0x9d, 0xb5, 0xd1, 0x12, 0xe6, 0x9f, 0xb6, 0xf6, - 0x5a, 0x7b, 0x66, 0x2e, 0x6e, 0x1b, 0x9c, 0x9c, 0xdc, 0x3f, 0x7e, 0xc0, 0xba, 0x39, 0x17, 0xac, 0x36, 0xd5, 0xef, - 0x82, 0x3a, 0x6c, 0xb8, 0xe3, 0x1a, 0x6e, 0xef, 0xb5, 0xf7, 0x4e, 0x5a, 0x3f, 0x78, 0x2a, 0x92, 0xb3, 0xb1, 0xb6, - 0xfb, 0xa6, 0x46, 0x56, 0xce, 0x7d, 0xd3, 0x37, 0x05, 0x9d, 0xa7, 0x42, 0xc2, 0x9f, 0x2e, 0x6c, 0xfe, 0x71, 0x2e, - 0x6f, 0xd2, 0x29, 0x1f, 0x8d, 0x98, 0xb0, 0x05, 0xca, 0x44, 0x96, 0xe7, 0x7c, 0xae, 0xb8, 0x5d, 0x0d, 0x87, 0xbb, - 0xa7, 0x1b, 0x50, 0x0d, 0x07, 0x74, 0x1c, 0x0c, 0xe8, 0xb4, 0x1a, 0x50, 0xd5, 0x7f, 0x38, 0xc2, 0xce, 0xc6, 0x5c, - 0x4d, 0xa9, 0x6e, 0x0d, 0x93, 0x3e, 0x2f, 0x94, 0x06, 0x98, 0x7b, 0xe3, 0x11, 0x73, 0xba, 0x34, 0x87, 0x4c, 0xdf, - 0x30, 0x26, 0xbe, 0x3d, 0x88, 0xcb, 0x54, 0x8a, 0xfc, 0xce, 0x7e, 0x2e, 0xc3, 0x2e, 0xe9, 0x42, 0xcb, 0x75, 0x32, - 0xe4, 0x82, 0x16, 0x77, 0xd7, 0x8a, 0x09, 0x25, 0x8b, 0x6b, 0x39, 0x1e, 0x2f, 0xbf, 0x45, 0xf2, 0xee, 0xa3, 0x75, - 0xa2, 0xb8, 0x98, 0xe4, 0xcc, 0x12, 0x38, 0x83, 0x08, 0xee, 0x90, 0xb1, 0xed, 0x9a, 0x26, 0x6b, 0x83, 0x5e, 0x27, - 0x59, 0xce, 0x67, 0x54, 0x33, 0x03, 0xe7, 0x80, 0xd4, 0xb8, 0xc9, 0x5b, 0x2a, 0xd7, 0xda, 0xb3, 0x7f, 0xaa, 0xd2, - 0xb0, 0x8d, 0x82, 0xc2, 0xbe, 0x49, 0x2e, 0x0c, 0x7e, 0x18, 0x70, 0x98, 0x5d, 0x64, 0x56, 0xcf, 0xac, 0x5d, 0x00, - 0x3b, 0x98, 0x5d, 0xad, 0xa9, 0x4b, 0x47, 0x97, 0x6c, 0x8b, 0xa7, 0xad, 0x1f, 0xea, 0xb9, 0x39, 0x1d, 0xb2, 0x7c, - 0x69, 0x37, 0xaa, 0x07, 0xae, 0xdb, 0xaa, 0xe1, 0x32, 0x07, 0x24, 0xc3, 0x80, 0x68, 0x90, 0xa6, 0xcd, 0x1b, 0x36, - 0xfc, 0xc2, 0xb5, 0xdd, 0x32, 0x4d, 0x75, 0x03, 0x4e, 0x45, 0x66, 0x4c, 0x73, 0x56, 0x2c, 0x3d, 0x21, 0x6f, 0xd5, - 0x08, 0xe8, 0x95, 0x30, 0x07, 0xb4, 0xa6, 0xc3, 0x26, 0x84, 0x58, 0x63, 0xc5, 0x72, 0xd7, 0xe4, 0x66, 0xf4, 0xd6, - 0xa1, 0xd8, 0x83, 0xd6, 0x0f, 0xb5, 0x43, 0xf6, 0xa4, 0xd5, 0xf2, 0x47, 0x44, 0xd3, 0xd6, 0x48, 0xdb, 0xc9, 0x29, - 0x9b, 0x95, 0x89, 0x5a, 0xce, 0xd3, 0x5a, 0xc2, 0x50, 0x6a, 0x2d, 0x67, 0x36, 0x6d, 0x07, 0x35, 0xaa, 0x93, 0xde, - 0x76, 0x67, 0x7e, 0xbb, 0x67, 0xfe, 0x69, 0xed, 0xb5, 0xb6, 0x49, 0xed, 0x36, 0x56, 0x1c, 0x23, 0x8f, 0xc7, 0xd0, - 0x71, 0x9b, 0xcd, 0xba, 0x0b, 0x05, 0xc7, 0xbd, 0x81, 0xb8, 0x39, 0xd1, 0xd6, 0x66, 0xb2, 0x00, 0x58, 0xca, 0x05, - 0x9c, 0xae, 0xf6, 0xb0, 0x83, 0x3e, 0x94, 0x04, 0x73, 0xf8, 0x9d, 0x8d, 0xd6, 0x87, 0xd5, 0xda, 0xab, 0x06, 0x06, - 0xff, 0xac, 0x3f, 0x55, 0xfc, 0xf9, 0x0b, 0x16, 0xc8, 0x47, 0xbc, 0x91, 0x9c, 0xae, 0x5a, 0x4e, 0x26, 0x1a, 0xe9, - 0x4a, 0x54, 0x33, 0x1e, 0x25, 0x33, 0x7a, 0x6b, 0x5d, 0x4b, 0x66, 0x5c, 0x80, 0xe1, 0x1a, 0xc2, 0x3a, 0x30, 0xf1, - 0x9f, 0x86, 0x0d, 0x8d, 0x74, 0x0c, 0x0d, 0x1f, 0x76, 0x92, 0xd3, 0x53, 0x84, 0x5b, 0xb8, 0x73, 0x7a, 0x1a, 0xc8, - 0x64, 0x63, 0xbd, 0xab, 0xe8, 0xae, 0x92, 0x72, 0x47, 0xc9, 0x23, 0xd3, 0xe8, 0x51, 0xbb, 0xd5, 0xc2, 0xc6, 0x7d, - 0xbe, 0x2c, 0xcc, 0xd5, 0x8e, 0x66, 0xdb, 0xad, 0x16, 0x34, 0x0b, 0x7f, 0xdc, 0xbc, 0x7e, 0x21, 0xcb, 0x56, 0xda, - 0xc2, 0xed, 0xb4, 0x8d, 0x3b, 0x69, 0x07, 0x1f, 0xa7, 0xc7, 0xf8, 0x24, 0x3d, 0xc1, 0xa7, 0xe9, 0x29, 0x3e, 0x4b, - 0xcf, 0xf0, 0xfd, 0xf4, 0x3e, 0x3e, 0x4f, 0xcf, 0xf1, 0x83, 0xf4, 0x01, 0x7e, 0x98, 0xb6, 0x5b, 0xf8, 0x51, 0xda, - 0x6e, 0xe3, 0xc7, 0x69, 0xbb, 0x83, 0x9f, 0xa4, 0xed, 0x63, 0xfc, 0x34, 0x6d, 0x9f, 0xe0, 0x67, 0x69, 0xfb, 0x14, - 0x53, 0xc8, 0x1d, 0x42, 0x6e, 0x06, 0xb9, 0x23, 0xc8, 0x65, 0x90, 0x3b, 0x4e, 0xdb, 0xa7, 0x6b, 0xac, 0x6c, 0xc8, - 0x8d, 0xa8, 0xd5, 0xee, 0x1c, 0x9f, 0x9c, 0x9e, 0xdd, 0x3f, 0x7f, 0xf0, 0xf0, 0xd1, 0xe3, 0x27, 0x4f, 0x9f, 0x45, - 0x03, 0x3c, 0x34, 0x9e, 0x2f, 0x4a, 0xf4, 0xf9, 0x41, 0xfb, 0x74, 0x80, 0xaf, 0xfd, 0x67, 0xcc, 0x0f, 0x3a, 0x27, - 0x2d, 0x74, 0x79, 0x79, 0x32, 0x68, 0x94, 0xb9, 0x8f, 0x8c, 0xc3, 0x4d, 0x95, 0x45, 0x08, 0x89, 0x21, 0x07, 0xe1, - 0x3b, 0x53, 0xef, 0x11, 0x8b, 0x79, 0x52, 0xa0, 0x83, 0x03, 0xf3, 0x63, 0xe2, 0x7f, 0x0c, 0xfd, 0x0f, 0x1a, 0x2c, - 0xd2, 0x2d, 0x8d, 0x9d, 0xc7, 0xb5, 0x2e, 0xfd, 0x1d, 0x4a, 0x53, 0xa2, 0x3d, 0xee, 0x8c, 0xfa, 0xff, 0x2b, 0xb2, - 0x46, 0x3b, 0xe4, 0xc4, 0x2a, 0xc6, 0x4e, 0x7b, 0x8c, 0x2c, 0x8b, 0xb4, 0x73, 0x7a, 0x7a, 0xf0, 0x4b, 0x9f, 0xf7, - 0xdb, 0x83, 0xc1, 0x61, 0xfb, 0x3e, 0x9e, 0x94, 0x09, 0x1d, 0x9b, 0x30, 0x2c, 0x13, 0x8e, 0x6d, 0x02, 0x4d, 0x6d, - 0x6d, 0x48, 0x3a, 0x31, 0x49, 0x50, 0x62, 0x9d, 0x9a, 0xb6, 0xef, 0xdb, 0xb6, 0x1f, 0x80, 0x35, 0x99, 0x69, 0xde, - 0x35, 0x7d, 0x71, 0x71, 0xb2, 0x72, 0x8d, 0xe2, 0x49, 0xea, 0x5a, 0xf3, 0x89, 0x27, 0x83, 0x01, 0x1e, 0x9a, 0xc4, - 0xd3, 0x2a, 0xf1, 0x6c, 0x30, 0x70, 0x5d, 0x3d, 0x30, 0x5d, 0xdd, 0xaf, 0xb2, 0xce, 0x07, 0x03, 0xd3, 0x25, 0x72, - 0xb1, 0x03, 0x94, 0xde, 0xfb, 0x5a, 0xea, 0x6f, 0xf8, 0x45, 0xe7, 0xf4, 0xb4, 0x07, 0x18, 0x66, 0x6c, 0x82, 0x3d, - 0x8c, 0x6e, 0x02, 0x18, 0xdd, 0xc1, 0xef, 0xde, 0x90, 0xa6, 0xd7, 0xb4, 0x04, 0x52, 0x2f, 0xfa, 0xaf, 0xa8, 0xa1, - 0x0d, 0xcc, 0xcd, 0x9f, 0x89, 0xfd, 0x33, 0x44, 0x8d, 0xaf, 0x14, 0xc0, 0x0d, 0x1a, 0x29, 0xaf, 0x52, 0x36, 0x3d, - 0x7e, 0xa1, 0xe0, 0xe2, 0x33, 0x55, 0x39, 0xed, 0xad, 0xa6, 0x37, 0xc3, 0xd5, 0x54, 0x7d, 0x45, 0x7f, 0xc5, 0x7f, - 0xa9, 0xc3, 0xb8, 0xdf, 0x6c, 0x24, 0xec, 0xaf, 0x11, 0xf8, 0x12, 0xf5, 0xd2, 0x11, 0x9b, 0xa0, 0x5e, 0xff, 0x2f, - 0x85, 0x07, 0x8d, 0x20, 0xe3, 0x87, 0xed, 0x14, 0xf0, 0x34, 0xda, 0x4c, 0x8c, 0x7f, 0x40, 0x3d, 0xd4, 0xfb, 0x4b, - 0x1d, 0xfe, 0x85, 0xee, 0x1d, 0x55, 0x73, 0xf9, 0x5d, 0xba, 0x2d, 0x5c, 0x85, 0x1f, 0x3a, 0x2c, 0xb7, 0x30, 0xc3, - 0xed, 0x26, 0x83, 0x60, 0x6d, 0xe0, 0x8a, 0x4e, 0x62, 0xd9, 0xe0, 0x47, 0xc7, 0x2d, 0xf4, 0x43, 0xbb, 0x03, 0xca, - 0x95, 0xa6, 0x38, 0xdc, 0xde, 0xf4, 0x45, 0xf3, 0x18, 0x3f, 0x68, 0x16, 0xb8, 0x8d, 0x70, 0xb3, 0xed, 0xb5, 0xde, - 0x7d, 0x15, 0xb7, 0x10, 0x56, 0xf1, 0x39, 0xfc, 0x73, 0x82, 0x06, 0xd5, 0x86, 0xbc, 0xa2, 0x9b, 0xbd, 0x83, 0xdf, - 0x2c, 0x89, 0x55, 0x83, 0x1f, 0x9d, 0xb5, 0xd0, 0x0f, 0x67, 0xa6, 0x23, 0x76, 0xa8, 0x77, 0x74, 0x25, 0xf1, 0x49, - 0x53, 0x42, 0x47, 0xad, 0xb2, 0x1f, 0x11, 0x9f, 0x22, 0x2c, 0xe2, 0x63, 0xf8, 0xa7, 0x1d, 0xf6, 0xf3, 0xeb, 0x56, - 0x3f, 0x66, 0xde, 0x6d, 0x9c, 0x9c, 0x5a, 0x37, 0x5c, 0x65, 0xef, 0xc4, 0x1b, 0xec, 0xb2, 0x6d, 0x2e, 0xf3, 0xda, - 0x47, 0xf0, 0x81, 0xb0, 0x3e, 0x24, 0x0a, 0xb3, 0x43, 0xf0, 0xdf, 0x05, 0xb3, 0x15, 0x75, 0x71, 0xdc, 0x55, 0x8d, - 0x06, 0x12, 0x7d, 0x35, 0x38, 0x24, 0xed, 0xa6, 0x6e, 0x32, 0x0c, 0xbf, 0x1b, 0xa4, 0x0c, 0x0a, 0x27, 0xaa, 0x5e, - 0x1f, 0xbb, 0x5e, 0xed, 0xcd, 0xbf, 0xc7, 0x0e, 0x42, 0x88, 0xea, 0xc5, 0xba, 0xc9, 0xd0, 0x91, 0x68, 0xc4, 0xfa, - 0x82, 0xf5, 0xce, 0xd2, 0x16, 0x32, 0xd8, 0xa9, 0x7a, 0x31, 0x6b, 0x72, 0x48, 0xef, 0xa4, 0x31, 0x6f, 0x6a, 0xf8, - 0x75, 0x12, 0xcc, 0x42, 0x00, 0xde, 0x55, 0xde, 0x48, 0xc5, 0x51, 0xe7, 0xf4, 0x14, 0x0b, 0xc2, 0x93, 0x89, 0xf9, - 0xa5, 0x08, 0x4f, 0x86, 0xe6, 0x97, 0x24, 0x25, 0xbc, 0x6c, 0xef, 0xb8, 0x20, 0xc1, 0xaa, 0x9a, 0x14, 0x0a, 0x0b, - 0x5a, 0xa0, 0xa3, 0x8e, 0x37, 0x0b, 0xc0, 0x53, 0x3f, 0x07, 0x50, 0x83, 0x14, 0xc6, 0x22, 0x54, 0x36, 0x0b, 0x9c, - 0x13, 0x7a, 0x99, 0x9c, 0xf6, 0xa6, 0x47, 0x71, 0xa7, 0x29, 0x9b, 0x05, 0x4a, 0xa7, 0x47, 0xa6, 0x26, 0xce, 0xc8, - 0x63, 0x6a, 0x5b, 0xc3, 0x53, 0xb8, 0xcb, 0xcd, 0x48, 0x76, 0x78, 0xd6, 0x6a, 0x24, 0xa7, 0x08, 0xf7, 0xb3, 0x55, - 0x0b, 0xe7, 0xab, 0x55, 0x0b, 0xd3, 0x60, 0x19, 0x1e, 0x0b, 0x0f, 0x90, 0x52, 0x53, 0xb7, 0x19, 0x9b, 0xa7, 0xc7, - 0x63, 0x0d, 0x76, 0x09, 0x1a, 0xbc, 0x7d, 0x34, 0xf8, 0x21, 0xa5, 0xdc, 0x5d, 0x08, 0x22, 0x13, 0x9d, 0x70, 0x1c, - 0xea, 0xee, 0x5e, 0x0b, 0xbf, 0xae, 0xde, 0xb2, 0x54, 0xc4, 0xbf, 0x4b, 0x6c, 0xd3, 0x82, 0x62, 0x74, 0xbb, 0xd8, - 0xaf, 0x74, 0xab, 0xd8, 0x9b, 0x1d, 0xc5, 0xae, 0xb6, 0x8b, 0x7d, 0x94, 0x81, 0xa6, 0x91, 0xff, 0x70, 0x7c, 0xd6, - 0x6a, 0x1c, 0x03, 0xb2, 0x1e, 0x9f, 0xb5, 0xaa, 0x42, 0xf7, 0x68, 0xb5, 0x56, 0x9a, 0x7c, 0xa1, 0xd6, 0xd7, 0x82, - 0x7b, 0xa7, 0x6f, 0xb3, 0x70, 0xd6, 0xe5, 0xbc, 0xf4, 0x2f, 0xef, 0x9f, 0x82, 0x2d, 0x8b, 0x30, 0xd4, 0x4e, 0xf7, - 0xcf, 0x06, 0xbd, 0x29, 0x8b, 0x1b, 0x90, 0x8a, 0xd2, 0xb1, 0x76, 0xbf, 0x50, 0x79, 0xa5, 0xfd, 0x51, 0x42, 0x52, - 0x67, 0x80, 0xb0, 0x24, 0x0d, 0xdd, 0x3f, 0x1e, 0x98, 0xf3, 0xae, 0x80, 0xdf, 0x27, 0xe6, 0x77, 0xa9, 0x50, 0x72, - 0x0e, 0x19, 0xd3, 0x9b, 0x61, 0xd4, 0x13, 0xe4, 0x35, 0x8d, 0x8d, 0x8d, 0x3d, 0x4a, 0xcb, 0x0c, 0xf5, 0x15, 0x32, - 0xde, 0x94, 0x19, 0x82, 0xbc, 0x16, 0xee, 0x37, 0x5e, 0x16, 0x29, 0xd8, 0xdb, 0xe0, 0x49, 0x0a, 0xb6, 0x36, 0x78, - 0x98, 0x0a, 0xf0, 0x07, 0xa1, 0x29, 0x0b, 0xac, 0xf8, 0x1f, 0x3a, 0x0d, 0x9e, 0xb9, 0x75, 0x26, 0x06, 0x4b, 0xbb, - 0x0c, 0x4e, 0x8a, 0x8f, 0x32, 0x86, 0xbf, 0x0d, 0x8d, 0x30, 0x83, 0x36, 0x19, 0xc2, 0x3c, 0x29, 0x08, 0xa4, 0x61, - 0x9e, 0x4c, 0x08, 0x83, 0x26, 0x79, 0x32, 0x24, 0xac, 0xdf, 0x09, 0xd0, 0xe4, 0xa9, 0x81, 0x1d, 0x00, 0x87, 0xd7, - 0x2f, 0xf2, 0xb5, 0x6d, 0x1c, 0x2c, 0x04, 0xa0, 0x09, 0x41, 0xb8, 0x8a, 0x61, 0x16, 0xb0, 0x39, 0xcd, 0xcf, 0x4e, - 0x15, 0xfe, 0x92, 0x27, 0xd4, 0x50, 0xef, 0x4f, 0x40, 0x56, 0xe3, 0x7b, 0x4b, 0xb6, 0xc6, 0x7b, 0xf7, 0x96, 0x62, - 0xfd, 0x03, 0xfc, 0x51, 0xf6, 0x0f, 0x30, 0x0f, 0x09, 0x45, 0x6b, 0xf4, 0x29, 0x85, 0x62, 0x3b, 0x4a, 0xa1, 0x4f, - 0xde, 0x1d, 0x50, 0x91, 0xe5, 0x6d, 0x1a, 0x8d, 0x68, 0xf1, 0x25, 0xc2, 0x7f, 0xa6, 0x51, 0x0e, 0xdc, 0x62, 0x84, - 0x3f, 0xa6, 0x51, 0xc1, 0x22, 0xfc, 0x47, 0x1a, 0x0d, 0xf3, 0x45, 0x84, 0x3f, 0xa4, 0xd1, 0xa4, 0x88, 0xf0, 0x7b, - 0x50, 0xd6, 0x8e, 0xf8, 0x62, 0x16, 0xe1, 0xdf, 0xd3, 0x48, 0x19, 0x6f, 0x08, 0xfc, 0x30, 0x8d, 0x18, 0x8b, 0xf0, - 0xbb, 0x34, 0x92, 0x79, 0x84, 0xaf, 0xd2, 0x48, 0x16, 0x11, 0x7e, 0x94, 0x46, 0x05, 0x8d, 0xf0, 0xe3, 0x34, 0x82, - 0x42, 0x93, 0x08, 0x3f, 0x49, 0x23, 0x68, 0x59, 0x45, 0xf8, 0x6d, 0x1a, 0x71, 0x11, 0xe1, 0xdf, 0xd2, 0x48, 0x2f, - 0x8a, 0xbf, 0x17, 0x92, 0xab, 0x08, 0x3f, 0x4d, 0xa3, 0x29, 0x8f, 0xf0, 0x9b, 0x34, 0x2a, 0x64, 0x84, 0x5f, 0xa7, - 0x11, 0xcd, 0x23, 0xfc, 0x2a, 0x8d, 0x72, 0x16, 0xe1, 0x5f, 0xd3, 0x68, 0xc4, 0x22, 0xfc, 0x32, 0x8d, 0xee, 0x58, - 0x9e, 0xcb, 0x08, 0x3f, 0x4b, 0x23, 0x26, 0x22, 0xfc, 0x4b, 0x1a, 0x65, 0xd3, 0x08, 0xff, 0x94, 0x46, 0xb4, 0xf8, - 0xa2, 0x22, 0xfc, 0x3c, 0x8d, 0x18, 0x8d, 0xf0, 0x0b, 0xdb, 0xd1, 0x24, 0xc2, 0x3f, 0xa7, 0xd1, 0xcd, 0x34, 0x5a, - 0x63, 0xa5, 0xc8, 0xf2, 0x35, 0xcf, 0xd8, 0x1f, 0x2c, 0x8d, 0xc6, 0xad, 0xf1, 0xf9, 0x78, 0x1c, 0x61, 0x2a, 0x34, - 0xff, 0x7b, 0xc1, 0x6e, 0x9e, 0x6a, 0x48, 0xa4, 0x6c, 0x38, 0xba, 0x1f, 0x61, 0xfa, 0xf7, 0x82, 0xa6, 0xd1, 0x78, - 0x6c, 0x0a, 0xfc, 0xbd, 0xa0, 0x33, 0x5a, 0xbc, 0x65, 0x69, 0x74, 0x7f, 0x3c, 0x1e, 0x8f, 0x4e, 0x22, 0x4c, 0xff, - 0x59, 0x7c, 0x34, 0x2d, 0x98, 0x02, 0x43, 0xc6, 0x27, 0x50, 0xf7, 0x74, 0x7c, 0x3a, 0xca, 0x22, 0x3c, 0xe4, 0xea, - 0xef, 0x05, 0x7c, 0x8f, 0xd9, 0x49, 0x76, 0x12, 0xe1, 0x61, 0x4e, 0xb3, 0x2f, 0x69, 0xd4, 0x32, 0xbf, 0xc4, 0x2f, - 0x6c, 0xf4, 0x7a, 0x26, 0xcd, 0x55, 0xc6, 0x98, 0x0d, 0xb3, 0x51, 0x84, 0xcd, 0x60, 0xc6, 0xf0, 0xf7, 0x2b, 0x7f, - 0xc7, 0x74, 0x1a, 0x9d, 0xd3, 0xce, 0x90, 0x75, 0x22, 0x3c, 0x7c, 0x73, 0x23, 0xd2, 0x88, 0x9e, 0x76, 0x68, 0x87, - 0x46, 0x78, 0xb8, 0x28, 0xf2, 0xbb, 0x1b, 0x29, 0x47, 0x00, 0x84, 0xe1, 0xf9, 0xf9, 0xfd, 0x08, 0x67, 0xf4, 0x57, - 0x0d, 0xb5, 0x4f, 0xc7, 0x0f, 0x18, 0x6d, 0x45, 0xf8, 0x17, 0x5a, 0xe8, 0x8f, 0x0b, 0xe5, 0x06, 0xda, 0x82, 0x14, - 0x99, 0xbd, 0x03, 0x35, 0x7f, 0x34, 0xea, 0x9c, 0x3d, 0x68, 0xb3, 0x08, 0x67, 0x57, 0xaf, 0xa1, 0xb7, 0xfb, 0xe3, - 0xd3, 0x16, 0x7c, 0x08, 0x90, 0x4b, 0x59, 0x01, 0x8d, 0x9c, 0x9d, 0x3c, 0x38, 0x65, 0x23, 0x93, 0xa8, 0x78, 0xfe, - 0xc5, 0xcc, 0xfe, 0x1c, 0xe6, 0x93, 0x15, 0x7c, 0xa6, 0xa4, 0x48, 0xa3, 0x51, 0xd6, 0x3e, 0x39, 0x86, 0x84, 0x3b, - 0x2a, 0x3c, 0x70, 0x6e, 0xa1, 0xea, 0xf9, 0x30, 0xc2, 0xb7, 0x36, 0xf5, 0x7c, 0x68, 0x3e, 0x26, 0xef, 0x7e, 0x15, - 0x6f, 0x46, 0x69, 0x34, 0x3c, 0x3f, 0x3f, 0x6b, 0x41, 0xc2, 0x07, 0x7a, 0x97, 0x46, 0xf4, 0x01, 0xfc, 0x07, 0xd9, - 0x1f, 0x9f, 0x41, 0x87, 0x30, 0xc2, 0xdb, 0xc9, 0xc7, 0x30, 0xe7, 0xcb, 0x94, 0x7e, 0xe1, 0x69, 0x34, 0x1c, 0x0d, - 0xef, 0x9f, 0x41, 0xbd, 0x19, 0x9d, 0x3c, 0xd3, 0x14, 0xda, 0x6d, 0xb5, 0x4c, 0xcb, 0xef, 0xf8, 0x57, 0x66, 0xaa, - 0x9f, 0x9e, 0x9e, 0x0d, 0x3b, 0x30, 0x82, 0x2b, 0x50, 0xa8, 0xc0, 0x78, 0xce, 0x33, 0xd3, 0xe0, 0x55, 0xf6, 0x74, - 0x94, 0x46, 0x0f, 0x1e, 0x1c, 0x77, 0xb2, 0x2c, 0xc2, 0xb7, 0x1f, 0x47, 0xb6, 0xb6, 0xc9, 0x53, 0x00, 0xfb, 0x34, - 0x62, 0x0f, 0x1e, 0x9c, 0xdd, 0xa7, 0xf0, 0xfd, 0xdc, 0xb4, 0x75, 0x3e, 0x1e, 0x66, 0xe7, 0xd0, 0xd6, 0xef, 0x30, - 0x9d, 0x93, 0xf3, 0xe3, 0x91, 0xe9, 0xeb, 0x77, 0x33, 0xea, 0xce, 0xf8, 0x64, 0x7c, 0x62, 0x32, 0xcd, 0x50, 0xcb, - 0xcf, 0xdf, 0x58, 0x1a, 0x65, 0x6c, 0xd4, 0x8e, 0xf0, 0xad, 0x5b, 0xb8, 0x07, 0x27, 0xad, 0xd6, 0xe8, 0x38, 0xc2, - 0xa3, 0x87, 0xf3, 0xf9, 0x5b, 0x03, 0xc1, 0xf6, 0xc9, 0x03, 0xfb, 0xad, 0xbe, 0xdc, 0x41, 0xd3, 0x43, 0x03, 0xb4, - 0x11, 0x9f, 0x99, 0x96, 0xcf, 0x1e, 0xc0, 0x7f, 0xe6, 0xdb, 0x34, 0x5d, 0x7e, 0xcb, 0xd1, 0xc4, 0x2e, 0x4a, 0x9b, - 0x3d, 0x68, 0x41, 0x8d, 0x31, 0xff, 0x38, 0x2c, 0x38, 0xa0, 0xd1, 0xb0, 0x03, 0xff, 0x17, 0xe1, 0x71, 0x7e, 0xf5, - 0xda, 0xe1, 0xec, 0x78, 0x4c, 0xc7, 0xad, 0x08, 0x8f, 0xe5, 0x47, 0xa5, 0x3f, 0x3c, 0x14, 0x69, 0xd4, 0xe9, 0x9c, - 0x0f, 0x4d, 0x99, 0xc5, 0x2f, 0x8a, 0x1b, 0x3c, 0x6e, 0x99, 0x56, 0x26, 0xf4, 0xad, 0x1a, 0x5e, 0x49, 0x58, 0x49, - 0xf8, 0x2f, 0xc2, 0x13, 0xd0, 0xc2, 0xb9, 0x56, 0xce, 0xed, 0x76, 0x98, 0xbc, 0x33, 0xa8, 0x39, 0xba, 0x0f, 0xf0, - 0xf2, 0xcb, 0x38, 0xa2, 0xf4, 0xb4, 0xd3, 0x8a, 0xb0, 0x19, 0xf5, 0x79, 0x0b, 0xfe, 0x8b, 0xb0, 0x85, 0x9c, 0x81, - 0xeb, 0xe4, 0xe3, 0xb3, 0x97, 0x37, 0x69, 0x44, 0x47, 0xe3, 0x31, 0x2c, 0x89, 0x99, 0x8c, 0x2f, 0x36, 0x95, 0x82, - 0xdd, 0xfd, 0x7a, 0xe3, 0xb6, 0x8b, 0x49, 0xd0, 0x0e, 0x3a, 0x67, 0x0f, 0x86, 0x27, 0x11, 0x7e, 0x3b, 0xe2, 0x54, - 0xc0, 0x2a, 0x65, 0xa3, 0xd3, 0xec, 0x34, 0x33, 0x09, 0x13, 0x99, 0x46, 0x27, 0xb0, 0xe4, 0x9d, 0x08, 0xf3, 0xaf, - 0x57, 0x77, 0x16, 0xdd, 0xa0, 0xb6, 0x43, 0x90, 0x71, 0x8b, 0x9d, 0x9d, 0x67, 0x11, 0xce, 0xe9, 0xd7, 0x67, 0xbf, - 0x16, 0x69, 0xc4, 0xce, 0xd8, 0xd9, 0x98, 0xfa, 0xef, 0x3f, 0xd4, 0xd4, 0xd4, 0x68, 0x8d, 0x4f, 0x21, 0xe9, 0x46, - 0x98, 0xb1, 0xde, 0xcf, 0xc6, 0x06, 0x43, 0x5e, 0xcd, 0xa4, 0xc8, 0x9e, 0x8e, 0xc7, 0xd2, 0x62, 0x31, 0x85, 0x4d, - 0xf8, 0x27, 0x40, 0x9b, 0x8e, 0x46, 0xe7, 0xec, 0x2c, 0xc2, 0x7f, 0xda, 0x5d, 0xe2, 0x26, 0xf0, 0xa7, 0xc5, 0x6c, - 0xe6, 0x76, 0xfb, 0x9f, 0x16, 0x28, 0x30, 0xdf, 0x31, 0x1d, 0xd3, 0x51, 0x27, 0xc2, 0x7f, 0x1a, 0xb8, 0x8c, 0x8e, - 0xe1, 0x3f, 0x28, 0x00, 0x9d, 0x3d, 0x68, 0x31, 0xf6, 0xa0, 0x65, 0xbe, 0xc2, 0x3c, 0x37, 0xf3, 0xe1, 0x59, 0xd6, - 0x8e, 0xf0, 0x9f, 0x0e, 0x1d, 0xc7, 0x63, 0xda, 0x02, 0x74, 0xfc, 0xd3, 0xa1, 0x63, 0xa7, 0x35, 0xec, 0x50, 0xf3, - 0x6d, 0xb1, 0xe6, 0xfc, 0x7e, 0xc6, 0x60, 0x72, 0x7f, 0x5a, 0x84, 0xbc, 0x7f, 0xff, 0xfc, 0xfc, 0xc1, 0x03, 0xf8, - 0x34, 0x6d, 0x97, 0x9f, 0x4a, 0x3f, 0xcc, 0x0d, 0x92, 0xb5, 0xb2, 0x13, 0xa0, 0x93, 0x7f, 0x9a, 0x31, 0x8e, 0xc7, - 0x63, 0xd6, 0x8a, 0x70, 0xce, 0x67, 0xcc, 0x62, 0x82, 0xfd, 0x6d, 0x3a, 0x3a, 0xee, 0x64, 0xa3, 0xe3, 0x4e, 0x84, - 0xf3, 0xb7, 0xcf, 0xcc, 0x6c, 0x5a, 0x30, 0x7b, 0xbf, 0xe5, 0x3c, 0xd6, 0xcc, 0xe8, 0x1b, 0x18, 0x24, 0xac, 0x34, - 0x54, 0x7e, 0x1f, 0xd0, 0xc3, 0xb3, 0xb3, 0x6c, 0x04, 0x03, 0x7d, 0x0f, 0xdd, 0x02, 0x18, 0xdf, 0xdb, 0xcd, 0x37, - 0xa4, 0xa7, 0xa7, 0x30, 0xdd, 0xf7, 0xf3, 0x45, 0x31, 0x7f, 0x95, 0x46, 0x0f, 0x8e, 0xef, 0xb7, 0x46, 0xc3, 0x08, - 0xbf, 0x77, 0x13, 0x3c, 0xce, 0x86, 0xc7, 0xf7, 0xdb, 0x11, 0x7e, 0x6f, 0xf6, 0xdb, 0xfd, 0xe1, 0xd9, 0x39, 0x9c, - 0x1b, 0xef, 0xd5, 0xbc, 0x78, 0x3b, 0x31, 0x05, 0xc6, 0xf4, 0x01, 0x34, 0xfb, 0x9b, 0xd9, 0x8d, 0xa3, 0x36, 0x6c, - 0xe4, 0xf7, 0x66, 0x93, 0x19, 0x3c, 0xb9, 0xdf, 0x3e, 0x3d, 0x3f, 0x8d, 0xf0, 0x8c, 0x8f, 0x04, 0x10, 0x78, 0xb3, - 0x51, 0x1e, 0xb4, 0x1f, 0xdc, 0x6f, 0x45, 0x78, 0xf6, 0x56, 0x67, 0x1f, 0xe9, 0xcc, 0x50, 0xe3, 0x31, 0xc0, 0x6c, - 0xc6, 0x95, 0xbe, 0x7b, 0xa3, 0x1c, 0x3d, 0x66, 0xed, 0x08, 0xcf, 0x64, 0x96, 0x51, 0xf5, 0xd6, 0x26, 0x0c, 0x4f, - 0x23, 0x2c, 0xe8, 0x57, 0xfa, 0x59, 0xfa, 0xcd, 0x34, 0x62, 0x74, 0x64, 0xd2, 0x0c, 0x0e, 0x47, 0xf8, 0xdd, 0x08, - 0x2e, 0x23, 0xd3, 0x68, 0x3c, 0x1a, 0x9f, 0x02, 0x78, 0x80, 0x00, 0x59, 0xec, 0x06, 0x68, 0xc0, 0xd7, 0xe8, 0xd1, - 0x30, 0x8d, 0xce, 0x86, 0xe7, 0xac, 0x73, 0x1c, 0xe1, 0x92, 0x1a, 0xd1, 0x53, 0xc8, 0x37, 0x9f, 0x1f, 0xcd, 0x96, - 0x3a, 0xb1, 0x09, 0x06, 0x40, 0x23, 0x7a, 0xbf, 0x35, 0x3a, 0x8b, 0xf0, 0xfc, 0x35, 0xf3, 0x7b, 0x8c, 0x31, 0x76, - 0x0e, 0xb0, 0x84, 0x24, 0x83, 0x40, 0xe7, 0xe3, 0xe1, 0x83, 0x73, 0xf3, 0x0d, 0x60, 0xa0, 0x63, 0xc6, 0x00, 0x48, - 0xf3, 0xd7, 0xac, 0x04, 0xc4, 0x68, 0x78, 0xbf, 0x05, 0xf4, 0x65, 0x4e, 0xe7, 0xf4, 0x8e, 0xde, 0x3c, 0x9d, 0x9b, - 0x39, 0x8d, 0x47, 0xa7, 0x11, 0x9e, 0x3f, 0xff, 0x65, 0xbe, 0x18, 0x8f, 0xcd, 0x84, 0xe8, 0xf0, 0x41, 0x84, 0xe7, - 0xac, 0x58, 0xc0, 0x1a, 0x9d, 0x9f, 0x1e, 0x8f, 0x23, 0xec, 0xd0, 0x30, 0x6b, 0x65, 0x43, 0xb8, 0x6d, 0x5d, 0xcc, - 0xd2, 0x68, 0x34, 0xa2, 0xad, 0x11, 0xdc, 0xbd, 0xca, 0x9b, 0x5f, 0x0b, 0x8b, 0x46, 0xcc, 0xe0, 0x83, 0x5b, 0x43, - 0x98, 0x2f, 0xc0, 0xe3, 0xe3, 0x90, 0x65, 0x19, 0x75, 0x89, 0x67, 0x67, 0xc7, 0xc7, 0x80, 0x7b, 0x76, 0x86, 0x16, - 0x41, 0xde, 0xa8, 0xbb, 0x61, 0x21, 0xe1, 0xe8, 0x02, 0xa2, 0x0a, 0x64, 0xf5, 0xcd, 0xdd, 0x6b, 0x43, 0x57, 0xdb, - 0x67, 0x0f, 0x60, 0x01, 0x14, 0x1d, 0x8d, 0x5e, 0xd9, 0xc3, 0xed, 0x7c, 0x78, 0x72, 0xda, 0x3e, 0x8e, 0xb0, 0xdf, - 0x08, 0xf4, 0xbc, 0x75, 0xbf, 0x03, 0x25, 0xc4, 0xe8, 0xce, 0x96, 0x18, 0x9f, 0xd0, 0x93, 0xb3, 0x56, 0x84, 0xfd, - 0xd6, 0x60, 0xe7, 0xc3, 0xd3, 0xfb, 0xf0, 0xa9, 0xa6, 0x2c, 0xcf, 0x0d, 0x7e, 0x9f, 0x02, 0x5c, 0x14, 0x7f, 0x26, - 0x68, 0x1a, 0xd1, 0xd6, 0x69, 0xa7, 0x33, 0x82, 0xcf, 0xfc, 0x2b, 0x2b, 0xd2, 0x28, 0x6b, 0xc1, 0x7f, 0x11, 0x0e, - 0x76, 0x12, 0x1b, 0x46, 0xd8, 0xe0, 0xdd, 0x19, 0x3d, 0x35, 0x7b, 0xdf, 0xed, 0xaa, 0xd6, 0x79, 0x0b, 0x36, 0xac, - 0xdb, 0x54, 0xee, 0x4b, 0x09, 0x79, 0xe3, 0x48, 0x2c, 0x8d, 0x70, 0x80, 0xa0, 0xe3, 0xfb, 0xe3, 0x08, 0xfb, 0x1d, - 0x77, 0x72, 0x76, 0xde, 0x01, 0x52, 0xa6, 0x81, 0x50, 0x8c, 0x3a, 0xc3, 0x13, 0x20, 0x4d, 0x9a, 0xbd, 0xb6, 0x78, - 0x12, 0x61, 0xfd, 0x54, 0xe9, 0x57, 0x69, 0x34, 0x3a, 0x1f, 0x8e, 0x47, 0xe7, 0x11, 0xd6, 0x72, 0x46, 0xb5, 0x34, - 0x14, 0xf0, 0xf8, 0xe4, 0x7e, 0x84, 0x0d, 0x9a, 0xb7, 0x58, 0x6b, 0xd4, 0x8a, 0xb0, 0x3b, 0x4a, 0x18, 0x3b, 0xef, - 0xc0, 0xb4, 0x7e, 0x7e, 0xae, 0x01, 0x97, 0x47, 0x6c, 0x78, 0x1c, 0xe1, 0x92, 0xde, 0x1b, 0x42, 0x04, 0x5f, 0x6a, - 0x26, 0xbf, 0x38, 0xd6, 0x03, 0x48, 0x9d, 0xdf, 0xf0, 0xb0, 0x0c, 0x2f, 0x6f, 0x2c, 0x1a, 0x51, 0xb3, 0xc5, 0x83, - 0xdb, 0xe8, 0x27, 0x34, 0xf6, 0x6c, 0x3b, 0x27, 0xcb, 0x35, 0x2e, 0x83, 0xbc, 0x7e, 0x61, 0x77, 0x2a, 0x56, 0xca, - 0x70, 0xb2, 0x41, 0x0a, 0x38, 0x62, 0x38, 0xb7, 0x06, 0xe7, 0xb9, 0x0a, 0x82, 0xa4, 0x20, 0xad, 0xae, 0xb8, 0xf0, - 0xde, 0xb4, 0x5d, 0x01, 0xa1, 0x1f, 0x20, 0xbd, 0x20, 0x94, 0x68, 0x88, 0x90, 0x63, 0x85, 0x49, 0xef, 0x64, 0x60, - 0x64, 0x4a, 0x69, 0xdd, 0x16, 0x28, 0xa1, 0x3e, 0x36, 0x3e, 0x5c, 0x95, 0x43, 0xf4, 0x28, 0xd4, 0x95, 0xc4, 0x44, - 0xba, 0x7e, 0x21, 0x74, 0xac, 0x54, 0xbf, 0x18, 0xe0, 0xf6, 0x19, 0xc2, 0x10, 0x43, 0x82, 0xf4, 0xe5, 0xe5, 0x65, - 0xfb, 0xec, 0xc0, 0x08, 0x7d, 0x97, 0x97, 0xe7, 0xf6, 0x07, 0xfc, 0x3b, 0xa8, 0xe2, 0x76, 0xc3, 0xf8, 0xde, 0xb3, - 0x40, 0xa3, 0x67, 0xf8, 0xeb, 0xf7, 0x6c, 0xb5, 0x8a, 0xdf, 0x33, 0x02, 0x33, 0xc6, 0xef, 0x59, 0x62, 0xee, 0x48, - 0xac, 0x87, 0x10, 0xe9, 0x83, 0xe6, 0xac, 0x85, 0x21, 0x9a, 0xbc, 0xe7, 0xbc, 0xdf, 0xb3, 0x3e, 0xaf, 0x7b, 0x97, - 0x57, 0x21, 0x9c, 0x0f, 0x0e, 0x96, 0x45, 0xaa, 0xad, 0x98, 0xa0, 0xad, 0x98, 0xa0, 0xad, 0x98, 0xa0, 0xab, 0x20, - 0xfa, 0x27, 0x3d, 0x90, 0x52, 0x8c, 0xb2, 0xc5, 0xf1, 0xd4, 0xef, 0x40, 0xed, 0x01, 0xda, 0xc9, 0x5e, 0xa5, 0xec, - 0x28, 0x75, 0x15, 0x3b, 0x15, 0x18, 0x3b, 0x13, 0x9d, 0xb6, 0xe3, 0xe8, 0xdf, 0x51, 0x77, 0xbc, 0xac, 0x89, 0x65, - 0xef, 0x76, 0x8a, 0x65, 0xb0, 0x92, 0x46, 0x34, 0xdb, 0xb7, 0xf1, 0x48, 0x74, 0xff, 0xbe, 0x11, 0xcc, 0xaa, 0x20, - 0x79, 0x0d, 0x48, 0xea, 0x82, 0x14, 0x72, 0x6e, 0xa4, 0xb4, 0x02, 0xa5, 0x23, 0x1d, 0x17, 0xa0, 0xa1, 0xf4, 0x0a, - 0xca, 0x32, 0x96, 0x6b, 0xc3, 0x00, 0x44, 0x59, 0x19, 0xcd, 0xca, 0x6a, 0xa7, 0x20, 0xba, 0x80, 0x26, 0xcc, 0x48, - 0x2c, 0xd0, 0x80, 0x30, 0x0d, 0x08, 0x57, 0x19, 0xc4, 0x19, 0x97, 0x7d, 0x62, 0xb2, 0x95, 0xc9, 0x56, 0x65, 0xb6, - 0xf4, 0xd9, 0x56, 0x48, 0x94, 0x26, 0x5b, 0x96, 0xd9, 0x20, 0xb3, 0xe1, 0x49, 0xaa, 0xf0, 0x30, 0x95, 0x56, 0x54, - 0xab, 0x64, 0xab, 0xb7, 0x34, 0xd4, 0xe6, 0x1e, 0x1c, 0xc4, 0xa5, 0x9c, 0x64, 0xd4, 0xc4, 0xf7, 0x96, 0x3c, 0x29, - 0x8c, 0x0c, 0xc4, 0x93, 0x89, 0xfb, 0x3b, 0x5c, 0x6f, 0xca, 0x4a, 0xc5, 0x64, 0xf8, 0x8d, 0x92, 0xe8, 0x93, 0x57, - 0xa2, 0xbe, 0xe7, 0x26, 0x0a, 0xd0, 0x05, 0x49, 0x5a, 0xad, 0xe3, 0xf6, 0x71, 0xeb, 0xbc, 0xc7, 0x0f, 0xdb, 0x9d, - 0xe4, 0x41, 0x27, 0x35, 0x8a, 0x88, 0xb9, 0xbc, 0x01, 0x05, 0xcc, 0x51, 0x27, 0x39, 0x41, 0x87, 0xed, 0xa4, 0x75, - 0x7a, 0xda, 0x84, 0x7f, 0xf0, 0x23, 0x5d, 0x56, 0x3b, 0x69, 0x9d, 0x9c, 0xf6, 0xf8, 0xd1, 0x46, 0xa5, 0x98, 0x37, - 0xa0, 0x20, 0x3a, 0x32, 0x95, 0x30, 0xd4, 0xaf, 0x96, 0xf7, 0xd9, 0x96, 0x9e, 0xe7, 0x91, 0x8e, 0xa5, 0x55, 0xc5, - 0x01, 0x54, 0xfd, 0xd7, 0xc4, 0x00, 0xd1, 0x7f, 0x0d, 0xcb, 0x48, 0xbd, 0xcb, 0x02, 0x44, 0xed, 0xf7, 0x3c, 0x16, - 0x0d, 0x76, 0x18, 0xdb, 0x7c, 0x0d, 0x75, 0x9b, 0x10, 0x3d, 0x0f, 0x4f, 0x5c, 0xae, 0x0a, 0x73, 0x27, 0x08, 0x35, - 0x15, 0xe4, 0x0e, 0x5d, 0xae, 0x0c, 0x73, 0x87, 0x08, 0x35, 0x25, 0xe4, 0xd2, 0x94, 0x27, 0x14, 0x72, 0x74, 0x42, - 0x9b, 0x06, 0x92, 0xd5, 0xa2, 0x3c, 0x67, 0x7e, 0xd8, 0x7c, 0x0c, 0xcb, 0x63, 0x08, 0x8a, 0x13, 0xa4, 0x05, 0xbc, - 0xb0, 0x52, 0x6a, 0x73, 0x5a, 0xb8, 0x54, 0xe3, 0x40, 0x46, 0x03, 0xfe, 0x39, 0x64, 0xe6, 0xd9, 0x8d, 0x56, 0xef, - 0xf8, 0xac, 0x95, 0xb6, 0xc1, 0x55, 0x1c, 0x64, 0x6d, 0x61, 0x65, 0x6d, 0xe1, 0x65, 0x6d, 0xe1, 0x65, 0x6d, 0x10, - 0xe0, 0x83, 0xbe, 0xff, 0x96, 0x35, 0xf3, 0x1b, 0x5e, 0xda, 0xf2, 0x58, 0x63, 0x8d, 0x58, 0xaf, 0x56, 0xcb, 0x35, - 0x58, 0x5a, 0x55, 0x2a, 0x77, 0x55, 0xa9, 0x3f, 0x97, 0x45, 0xda, 0xc2, 0x93, 0x14, 0xb4, 0xdc, 0x2d, 0x4c, 0xcd, - 0xe6, 0xf6, 0x54, 0x61, 0x33, 0x8a, 0x4f, 0xcf, 0xab, 0x93, 0x2f, 0xc9, 0xb1, 0xd1, 0x1e, 0x2f, 0x8b, 0x94, 0x5b, - 0x9a, 0xc1, 0x2d, 0xcd, 0xe0, 0x96, 0x66, 0x40, 0x23, 0xb8, 0x2c, 0x6c, 0xca, 0x26, 0x94, 0xc0, 0x95, 0x40, 0xff, - 0x78, 0x00, 0x41, 0x0c, 0x63, 0x4d, 0xcc, 0xa8, 0x37, 0x3a, 0x6f, 0x43, 0xd0, 0x36, 0x5b, 0x52, 0x27, 0xd4, 0xf8, - 0xae, 0x97, 0x63, 0x7e, 0x55, 0x43, 0xfb, 0x04, 0x5e, 0xd4, 0x79, 0xa8, 0xe3, 0x16, 0x98, 0xae, 0x44, 0x45, 0xd4, - 0x33, 0x64, 0x21, 0x35, 0x3a, 0x1b, 0x67, 0x92, 0xfe, 0x65, 0xc3, 0x13, 0xd8, 0x52, 0x82, 0xf0, 0x1d, 0x89, 0x2f, - 0xac, 0x0a, 0x4d, 0x50, 0x5a, 0xdc, 0x3a, 0x73, 0x39, 0x7b, 0x24, 0x74, 0xc1, 0x6c, 0xde, 0xc7, 0xbc, 0xea, 0x09, - 0x22, 0x95, 0x71, 0xda, 0x24, 0x55, 0xd4, 0x66, 0x70, 0x62, 0x26, 0xb7, 0xd4, 0xb8, 0xf4, 0xbc, 0xb0, 0x7f, 0x5e, - 0xd1, 0xc0, 0xe7, 0xb1, 0x98, 0x0c, 0xbd, 0xab, 0xf0, 0xb5, 0x89, 0x6d, 0x44, 0xf6, 0xf7, 0xad, 0x45, 0xbb, 0xf9, - 0xda, 0x34, 0x69, 0x37, 0x89, 0x26, 0x1b, 0x76, 0xa8, 0x5f, 0xa3, 0xbf, 0xbd, 0xc7, 0x5e, 0x31, 0x19, 0xa2, 0x80, - 0x66, 0x1b, 0xb0, 0xca, 0x0a, 0x58, 0xca, 0xd5, 0x2b, 0x1d, 0x39, 0xa1, 0x77, 0x33, 0xe6, 0x75, 0x31, 0x19, 0xee, - 0x7c, 0x7a, 0xc5, 0xf6, 0xd8, 0x7b, 0x4b, 0x83, 0x1e, 0xbc, 0x6a, 0x7b, 0xca, 0x6e, 0xbf, 0x57, 0xe7, 0x66, 0x67, - 0x1d, 0x95, 0x7f, 0xaf, 0xce, 0xd3, 0x5d, 0x75, 0x66, 0xfc, 0x36, 0xf6, 0x7b, 0x47, 0x07, 0x6a, 0x6c, 0x63, 0x26, - 0x35, 0x19, 0x42, 0xac, 0x7c, 0xf8, 0x6b, 0x23, 0xda, 0x74, 0x3d, 0x09, 0x87, 0x55, 0x90, 0xbd, 0xe4, 0x34, 0x65, - 0x98, 0x92, 0xce, 0x61, 0x61, 0x62, 0xda, 0x88, 0x84, 0x36, 0x55, 0x42, 0x71, 0x4e, 0xe2, 0x98, 0x1e, 0x66, 0x10, - 0x99, 0xa7, 0xdd, 0xa3, 0x69, 0x4c, 0x1b, 0x19, 0x3a, 0x8a, 0xdb, 0x0d, 0x7a, 0x98, 0x21, 0xd4, 0x68, 0x83, 0xce, - 0x54, 0x92, 0x76, 0x33, 0x87, 0x58, 0x9d, 0x86, 0x14, 0xe7, 0x87, 0x22, 0x29, 0x1a, 0xf2, 0x50, 0x25, 0x45, 0x23, - 0x39, 0xc5, 0x22, 0x99, 0x94, 0xc9, 0x13, 0x93, 0x3c, 0xb1, 0xc9, 0xc3, 0x32, 0x79, 0x68, 0x92, 0x87, 0x36, 0x99, - 0x92, 0xe2, 0x50, 0x24, 0xb4, 0x11, 0xb7, 0x9b, 0x05, 0x3a, 0x84, 0x11, 0xf8, 0xd1, 0x13, 0x11, 0x86, 0x48, 0x5f, - 0x1b, 0x1b, 0xa3, 0xb9, 0xcc, 0x5d, 0xd0, 0xd2, 0x0a, 0x48, 0xa5, 0xe3, 0x17, 0xd4, 0x79, 0x16, 0x80, 0x09, 0x6b, - 0xfb, 0xc7, 0x87, 0xe4, 0x5b, 0x67, 0xb9, 0x14, 0x81, 0x63, 0x1b, 0xd8, 0xe2, 0x7f, 0x71, 0xee, 0x3c, 0x00, 0xd5, - 0x35, 0xcd, 0xe7, 0x53, 0xba, 0xe5, 0x3d, 0x5c, 0x4c, 0x86, 0x6e, 0x67, 0x95, 0xcd, 0x30, 0x5a, 0xd8, 0x50, 0xd7, - 0x75, 0x3f, 0x4f, 0x00, 0xb5, 0xf7, 0x2d, 0x4d, 0xa8, 0x51, 0x92, 0xdb, 0x1a, 0x93, 0x82, 0xdd, 0xa9, 0x8c, 0xe6, - 0x2c, 0xae, 0x0e, 0xe0, 0x6a, 0x98, 0x8c, 0xbc, 0x00, 0x8f, 0x80, 0xe2, 0x30, 0x39, 0x6e, 0xe8, 0x64, 0x72, 0x98, - 0x9c, 0x3e, 0x68, 0xe8, 0x64, 0x78, 0x98, 0xb4, 0xdb, 0x15, 0xce, 0x26, 0x05, 0xd1, 0xc9, 0x84, 0x68, 0xd0, 0x18, - 0xda, 0x46, 0xe5, 0x9c, 0x82, 0x89, 0xdb, 0xbf, 0x31, 0x8c, 0x86, 0x1b, 0x86, 0x60, 0x13, 0x1b, 0xf5, 0x73, 0x6b, - 0x0c, 0x61, 0x37, 0x9d, 0xd3, 0xd3, 0xa6, 0x4e, 0x0a, 0xac, 0xed, 0x4a, 0x36, 0x75, 0x32, 0xc1, 0xda, 0x2e, 0x5f, - 0x53, 0x27, 0x43, 0xdb, 0x94, 0xd1, 0x01, 0x32, 0x11, 0x00, 0xeb, 0x39, 0x0b, 0x20, 0xdf, 0xf1, 0x4e, 0x3a, 0x6b, - 0xd0, 0x1a, 0x7e, 0xaf, 0x5c, 0xd3, 0x17, 0x54, 0x54, 0x83, 0xa9, 0x13, 0xfb, 0x56, 0xd1, 0x76, 0xd5, 0x24, 0xfb, - 0xd7, 0x65, 0xcb, 0x66, 0x0b, 0xa9, 0xeb, 0x05, 0x1f, 0xd6, 0x30, 0xc4, 0x95, 0x72, 0x07, 0xf7, 0x3f, 0x94, 0xc4, - 0x10, 0xdb, 0xcf, 0x9c, 0x42, 0x9c, 0x78, 0x3d, 0x32, 0x24, 0xf1, 0x46, 0x63, 0x8d, 0xe2, 0xe0, 0xbc, 0x7d, 0x1a, - 0x52, 0xd5, 0xad, 0x80, 0x7f, 0x84, 0x44, 0x0b, 0x61, 0x4d, 0x42, 0x47, 0x51, 0xc0, 0x82, 0x38, 0xed, 0x6e, 0xed, - 0x80, 0x38, 0x38, 0xd8, 0x3c, 0x2f, 0xfc, 0xd3, 0x0b, 0x5b, 0xcf, 0x2d, 0x54, 0xf6, 0x84, 0xfe, 0x41, 0x28, 0x6b, - 0x69, 0xcc, 0x03, 0x44, 0xf1, 0xa1, 0xb7, 0xee, 0x1b, 0x0a, 0xdf, 0xaf, 0xe2, 0x0e, 0xba, 0x9c, 0xe6, 0x99, 0xc9, - 0x30, 0x7d, 0x0d, 0x82, 0xb1, 0xbd, 0x09, 0x27, 0x54, 0xda, 0x4a, 0xfe, 0xcb, 0x8e, 0x83, 0x4e, 0xdc, 0x83, 0x35, - 0x61, 0xa3, 0x9f, 0x43, 0xcb, 0xe4, 0x0a, 0x36, 0xce, 0x27, 0x7d, 0xb5, 0xaa, 0x3d, 0x4f, 0x64, 0x1f, 0xc1, 0x41, - 0x07, 0x07, 0x5c, 0x3d, 0x03, 0x63, 0x6a, 0x16, 0x37, 0xc2, 0xc3, 0xf7, 0xef, 0xda, 0x69, 0xfd, 0xd9, 0x9c, 0xab, - 0x69, 0x70, 0xd0, 0x3d, 0xac, 0xe5, 0xef, 0x5c, 0x89, 0x9e, 0x4e, 0xb9, 0x5b, 0xeb, 0xcf, 0x95, 0xa9, 0xfa, 0xd6, - 0x43, 0x59, 0x07, 0x07, 0xbc, 0x0a, 0x57, 0x15, 0xfd, 0x10, 0xa1, 0x9e, 0x91, 0x41, 0x9e, 0xe5, 0x92, 0xc2, 0x8d, - 0x28, 0x5c, 0x31, 0xa4, 0x0d, 0x7e, 0xa4, 0xf1, 0x1f, 0xf2, 0xff, 0x53, 0x23, 0x87, 0x3a, 0x6d, 0xf0, 0x40, 0x00, - 0x0b, 0x59, 0xa1, 0x2a, 0x50, 0xa4, 0x81, 0x74, 0x68, 0x79, 0x8e, 0xca, 0xc3, 0x9c, 0xce, 0xe7, 0xf9, 0x9d, 0x79, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcc, 0xbd, 0x6d, 0x7b, 0x1b, 0x37, 0xb2, 0x20, 0xfa, + 0xf9, 0xee, 0xaf, 0x90, 0xfa, 0x38, 0x4a, 0x43, 0x04, 0x5b, 0x24, 0x25, 0xca, 0x72, 0x53, 0x10, 0xd7, 0xaf, 0x63, + 0x27, 0x8e, 0xed, 0x58, 0xb6, 0x33, 0x0e, 0xc3, 0xe3, 0x80, 0x4d, 0x90, 0x84, 0xdd, 0x04, 0x98, 0x06, 0x68, 0x49, + 0x21, 0xf9, 0xdf, 0xef, 0x53, 0x78, 0xe9, 0x46, 0x93, 0xb4, 0x67, 0x66, 0xef, 0xee, 0x7d, 0xf6, 0xe4, 0x8c, 0xc5, + 0xc6, 0x3b, 0x0a, 0x85, 0x42, 0x55, 0xa1, 0xaa, 0x70, 0x79, 0x38, 0x96, 0x99, 0xbe, 0x5b, 0xb0, 0x83, 0x99, 0x9e, + 0xe7, 0x57, 0x97, 0xee, 0x5f, 0x46, 0xc7, 0x57, 0x97, 0x39, 0x17, 0x5f, 0x0e, 0x0a, 0x96, 0x13, 0x9e, 0x49, 0x71, + 0x30, 0x2b, 0xd8, 0x84, 0x8c, 0xa9, 0xa6, 0x29, 0x9f, 0xd3, 0x29, 0x3b, 0x38, 0xb9, 0xba, 0x9c, 0x33, 0x4d, 0x0f, + 0xb2, 0x19, 0x2d, 0x14, 0xd3, 0xe4, 0xfd, 0xbb, 0x67, 0xcd, 0x8b, 0xab, 0x4b, 0x95, 0x15, 0x7c, 0xa1, 0x0f, 0xa0, + 0x49, 0x32, 0x97, 0xe3, 0x65, 0xce, 0xae, 0x4e, 0x4e, 0x6e, 0x6e, 0x6e, 0x92, 0xcf, 0xea, 0x7f, 0x7c, 0xa5, 0xc5, + 0xc1, 0x3f, 0x0a, 0xf2, 0x7a, 0xf4, 0x99, 0x65, 0x3a, 0x19, 0xb3, 0x09, 0x17, 0xec, 0x4d, 0x21, 0x17, 0xac, 0xd0, + 0x77, 0x3d, 0xc8, 0xfc, 0xb5, 0x20, 0x31, 0xc7, 0x1a, 0x33, 0x44, 0xae, 0xf4, 0x01, 0x17, 0x07, 0xbc, 0xff, 0x8f, + 0xc2, 0xa4, 0xac, 0x98, 0x58, 0xce, 0x59, 0x41, 0x47, 0x39, 0x4b, 0x0f, 0x5b, 0x38, 0x93, 0x62, 0xc2, 0xa7, 0xcb, + 0xf2, 0xfb, 0xa6, 0xe0, 0xda, 0xff, 0xfe, 0x4a, 0xf3, 0x25, 0x4b, 0xd9, 0x06, 0xa5, 0x7c, 0xa0, 0x87, 0x84, 0x99, + 0x96, 0xbf, 0x54, 0x0d, 0xc7, 0xbf, 0x9a, 0x26, 0xef, 0x16, 0x4c, 0x4e, 0x0e, 0xf4, 0x21, 0x89, 0xd4, 0xdd, 0x7c, + 0x24, 0xf3, 0xa8, 0xaf, 0x1b, 0x51, 0x94, 0x42, 0x19, 0xcc, 0x50, 0x2f, 0x93, 0x42, 0xe9, 0x03, 0xc1, 0xc9, 0x0d, + 0x17, 0x63, 0x79, 0x83, 0x6f, 0x04, 0x11, 0x3c, 0xb9, 0x9e, 0xd1, 0xb1, 0xbc, 0x79, 0x2b, 0xa5, 0x3e, 0x3a, 0x8a, + 0xdd, 0xf7, 0xdd, 0xe3, 0xeb, 0x6b, 0x42, 0xc8, 0x57, 0xc9, 0xc7, 0x07, 0xad, 0xf5, 0x3a, 0x48, 0x4d, 0x04, 0xd5, + 0xfc, 0x2b, 0xb3, 0x95, 0xd0, 0xd1, 0x51, 0x44, 0xc7, 0x72, 0xa1, 0xd9, 0xf8, 0x5a, 0xdf, 0xe5, 0xec, 0x7a, 0xc6, + 0x98, 0x56, 0x11, 0x17, 0x07, 0x4f, 0x64, 0xb6, 0x9c, 0x33, 0xa1, 0x93, 0x45, 0x21, 0xb5, 0x84, 0x81, 0x1d, 0x1d, + 0x45, 0x05, 0x5b, 0xe4, 0x34, 0x63, 0x90, 0xff, 0xf8, 0xfa, 0xba, 0xaa, 0x51, 0x15, 0xc2, 0x5f, 0x04, 0xb9, 0x36, + 0x43, 0x8f, 0x11, 0xfe, 0x4d, 0x10, 0xc1, 0x6e, 0x0e, 0x7e, 0x63, 0xf4, 0xcb, 0x2f, 0x74, 0xd1, 0xcb, 0x72, 0xaa, + 0xd4, 0xc1, 0x2b, 0xb9, 0x32, 0xd3, 0x28, 0x96, 0x99, 0x96, 0x45, 0xac, 0x31, 0xc3, 0x02, 0xad, 0xf8, 0x24, 0xd6, + 0x33, 0xae, 0x92, 0x4f, 0xf7, 0x32, 0xa5, 0xde, 0x32, 0xb5, 0xcc, 0xf5, 0x3d, 0x72, 0xd8, 0xc2, 0xe2, 0x90, 0x90, + 0x2f, 0x02, 0xe9, 0x59, 0x21, 0x6f, 0x0e, 0x9e, 0x16, 0x85, 0x2c, 0xe2, 0xe8, 0xf1, 0xf5, 0xb5, 0x2d, 0x71, 0xc0, + 0xd5, 0x81, 0x90, 0xfa, 0xa0, 0x6c, 0x0f, 0xa0, 0x9d, 0x1c, 0xbc, 0x57, 0xec, 0xe0, 0xcf, 0xa5, 0x50, 0x74, 0xc2, + 0x1e, 0x5f, 0x5f, 0xff, 0x79, 0x20, 0x8b, 0x83, 0x3f, 0x33, 0xa5, 0xfe, 0x3c, 0xe0, 0x42, 0x69, 0x46, 0xc7, 0x49, + 0x84, 0x7a, 0xa6, 0xb3, 0x4c, 0xa9, 0x77, 0xec, 0x56, 0x13, 0x8d, 0xcd, 0xa7, 0x26, 0x6c, 0x33, 0x65, 0xfa, 0x40, + 0x95, 0xf3, 0x8a, 0xd1, 0x2a, 0x67, 0xfa, 0x40, 0x13, 0x93, 0x2f, 0x1d, 0xfc, 0x99, 0xfd, 0xd4, 0x3d, 0x3e, 0x89, + 0x6f, 0xc4, 0xd1, 0x91, 0x2e, 0x01, 0x8d, 0x56, 0x6e, 0x85, 0x08, 0x3b, 0xf4, 0x69, 0x47, 0x47, 0x2c, 0xc9, 0x99, + 0x98, 0xea, 0x19, 0x21, 0xa4, 0xdd, 0x13, 0x47, 0x47, 0xb1, 0x26, 0xbf, 0x89, 0x64, 0xca, 0x74, 0xcc, 0x10, 0xc2, + 0x55, 0xed, 0xa3, 0xa3, 0xd8, 0x02, 0x41, 0x12, 0x6d, 0x00, 0x57, 0x83, 0x31, 0x4a, 0x1c, 0xf4, 0xaf, 0xef, 0x44, + 0x16, 0x87, 0xe3, 0x47, 0x58, 0x1c, 0x1d, 0xfd, 0x26, 0x12, 0x05, 0x2d, 0x62, 0x8d, 0xd0, 0xa6, 0x60, 0x7a, 0x59, + 0x88, 0x03, 0xbd, 0xd1, 0xf2, 0x5a, 0x17, 0x5c, 0x4c, 0x63, 0xb4, 0xf2, 0x69, 0x41, 0xc5, 0xcd, 0xc6, 0x0e, 0xf7, + 0xf7, 0x82, 0x70, 0x72, 0x05, 0x3d, 0xbe, 0x92, 0xb1, 0xc3, 0x41, 0x4e, 0x48, 0xa4, 0x4c, 0xdd, 0xa8, 0xcf, 0x53, + 0xde, 0x88, 0x22, 0x6c, 0x47, 0x89, 0xbf, 0x08, 0x84, 0x85, 0x06, 0xd4, 0x4d, 0x92, 0x44, 0x23, 0x72, 0xb5, 0xf2, + 0x60, 0xe1, 0xc1, 0x44, 0xfb, 0x7c, 0xd0, 0x1a, 0xa6, 0x3a, 0x29, 0xd8, 0x78, 0x99, 0xb1, 0x38, 0x16, 0x58, 0x61, + 0x89, 0xc8, 0x95, 0x68, 0xc4, 0x05, 0xb9, 0x82, 0xf5, 0x2e, 0xea, 0x8b, 0x4d, 0xc8, 0x61, 0x0b, 0xb9, 0x41, 0x16, + 0x7e, 0x84, 0x00, 0x62, 0x37, 0xa0, 0x82, 0x90, 0x48, 0x2c, 0xe7, 0x23, 0x56, 0x44, 0x65, 0xb1, 0x5e, 0x0d, 0x2f, + 0x96, 0x8a, 0x1d, 0x64, 0x4a, 0x1d, 0x4c, 0x96, 0x22, 0xd3, 0x5c, 0x8a, 0x83, 0xa8, 0x51, 0x34, 0x22, 0x8b, 0x0f, + 0x25, 0x3a, 0x44, 0x68, 0x83, 0x62, 0x85, 0x1a, 0x7c, 0x20, 0x1b, 0xed, 0x21, 0x86, 0x51, 0xa2, 0x9e, 0x6b, 0xcf, + 0x41, 0x80, 0x61, 0x0e, 0x93, 0xdc, 0xe0, 0x9f, 0xec, 0xce, 0x87, 0x29, 0xde, 0x88, 0x3e, 0x4f, 0x76, 0x77, 0x0a, + 0xd1, 0xc9, 0x9c, 0x2e, 0x62, 0x46, 0xae, 0x98, 0xc1, 0x2e, 0x2a, 0x32, 0x18, 0x6b, 0x6d, 0xe1, 0xfa, 0x2c, 0x65, + 0x49, 0x85, 0x53, 0x28, 0xd5, 0xc9, 0x44, 0x16, 0x4f, 0x69, 0x36, 0x83, 0x7a, 0x25, 0xc6, 0x8c, 0xfd, 0x86, 0xcb, + 0x0a, 0x46, 0x35, 0x7b, 0x9a, 0x33, 0xf8, 0x8a, 0x23, 0x53, 0x33, 0x42, 0x58, 0xc1, 0x56, 0xcf, 0xb9, 0x7e, 0x25, + 0x45, 0xc6, 0x7a, 0x2a, 0xc0, 0x2f, 0xb3, 0xf2, 0x0f, 0xb5, 0x2e, 0xf8, 0x68, 0xa9, 0x59, 0x1c, 0x09, 0x28, 0x11, + 0x61, 0x85, 0xb0, 0x48, 0x34, 0xbb, 0xd5, 0x8f, 0xa5, 0xd0, 0x4c, 0x68, 0xc2, 0x3c, 0x54, 0x31, 0x4f, 0xe8, 0x62, + 0xc1, 0xc4, 0xf8, 0xf1, 0x8c, 0xe7, 0xe3, 0x58, 0xa0, 0x0d, 0xda, 0xe0, 0x8f, 0x82, 0xc0, 0x24, 0xc9, 0x15, 0x4f, + 0xe1, 0x9f, 0x6f, 0x4f, 0x27, 0xd6, 0xe4, 0xca, 0x6c, 0x0b, 0x46, 0xa2, 0xa8, 0x37, 0x91, 0x45, 0xec, 0xa6, 0x70, + 0x00, 0xa4, 0x0b, 0xfa, 0x78, 0xbb, 0xcc, 0x99, 0x42, 0xac, 0x41, 0x44, 0xb9, 0x8e, 0x0e, 0xc2, 0xbf, 0x17, 0x31, + 0x83, 0x05, 0xe0, 0x28, 0xe5, 0x86, 0x04, 0xbe, 0xe4, 0x6e, 0x53, 0x8d, 0x4b, 0xa2, 0xf6, 0x97, 0x20, 0x63, 0x9e, + 0xe8, 0x62, 0xa9, 0x34, 0x1b, 0xbf, 0xbb, 0x5b, 0x30, 0x85, 0x35, 0x25, 0x7f, 0x89, 0xfe, 0x5f, 0x22, 0x61, 0xf3, + 0x85, 0xbe, 0xbb, 0x36, 0xd4, 0x3c, 0x8d, 0x22, 0xfc, 0x4f, 0x53, 0xb4, 0x60, 0x34, 0x03, 0x92, 0xe6, 0x40, 0xf6, + 0x46, 0xe6, 0x77, 0x13, 0x9e, 0xe7, 0xd7, 0xcb, 0xc5, 0x42, 0x16, 0x1a, 0x6b, 0x41, 0x56, 0x5a, 0x56, 0xf0, 0x81, + 0x15, 0x5d, 0xa9, 0x1b, 0xae, 0xb3, 0x59, 0xac, 0xd1, 0x2a, 0xa3, 0x8a, 0x1d, 0x3c, 0x92, 0x32, 0x67, 0x54, 0xa4, + 0x9c, 0xf0, 0xbe, 0xa6, 0xa9, 0x58, 0xe6, 0x79, 0x6f, 0x54, 0x30, 0xfa, 0xa5, 0x67, 0xb2, 0xed, 0xe1, 0x90, 0x9a, + 0xdf, 0x0f, 0x8b, 0x82, 0xde, 0x41, 0x41, 0x42, 0xa0, 0x58, 0x9f, 0xa7, 0x3f, 0x5d, 0xbf, 0x7e, 0x95, 0xd8, 0xbd, + 0xc2, 0x27, 0x77, 0x31, 0x2f, 0xf7, 0x1f, 0xdf, 0xe0, 0x49, 0x21, 0xe7, 0x5b, 0x5d, 0x5b, 0xd0, 0xf1, 0xde, 0x37, + 0x86, 0xc0, 0x08, 0x3f, 0xb4, 0x4d, 0x87, 0x23, 0x78, 0x65, 0x30, 0x1f, 0x32, 0x89, 0xeb, 0x17, 0xfe, 0x49, 0x6d, + 0x72, 0xcc, 0xd1, 0xf7, 0x47, 0xab, 0x8b, 0xbb, 0x15, 0x23, 0x66, 0x9c, 0x0b, 0x38, 0x18, 0x61, 0x8c, 0x19, 0xd5, + 0xd9, 0x6c, 0xc5, 0x4c, 0x63, 0x1b, 0x3f, 0x62, 0xb6, 0xd9, 0xe0, 0xbf, 0xa5, 0xc7, 0x7a, 0x7d, 0x48, 0x08, 0x37, + 0xf4, 0x8a, 0xe8, 0xf5, 0x9a, 0x13, 0xc2, 0x11, 0x7e, 0xcb, 0xc9, 0x8a, 0xfa, 0x09, 0xc1, 0xc9, 0x06, 0xdb, 0x33, + 0xb5, 0x54, 0x06, 0x4e, 0xc0, 0xaf, 0xac, 0xd0, 0xac, 0x48, 0xb5, 0xc0, 0x05, 0x9b, 0xe4, 0x30, 0x8e, 0xc3, 0x36, + 0x9e, 0x51, 0xf5, 0x78, 0x46, 0xc5, 0x94, 0x8d, 0xd3, 0xbf, 0xe5, 0x06, 0x33, 0x41, 0xa2, 0x09, 0x17, 0x34, 0xe7, + 0x7f, 0xb3, 0x71, 0xe4, 0xce, 0x85, 0x0f, 0xfa, 0x80, 0xdd, 0x6a, 0x26, 0xc6, 0xea, 0xe0, 0xf9, 0xbb, 0x5f, 0x5e, + 0xba, 0xc5, 0xac, 0x9d, 0x15, 0x68, 0xa5, 0x96, 0x0b, 0x56, 0xc4, 0x08, 0xbb, 0xb3, 0xe2, 0x29, 0x37, 0x74, 0xf2, + 0x17, 0xba, 0xb0, 0x29, 0x5c, 0xbd, 0x5f, 0x8c, 0xa9, 0x66, 0x6f, 0x98, 0x18, 0x73, 0x31, 0x25, 0x87, 0x6d, 0x9b, + 0x3e, 0xa3, 0x2e, 0x63, 0x5c, 0x26, 0x7d, 0xba, 0xf7, 0x34, 0x37, 0x73, 0x2f, 0x3f, 0x97, 0x31, 0xda, 0x28, 0x4d, + 0x35, 0xcf, 0x0e, 0xe8, 0x78, 0xfc, 0x42, 0x70, 0xcd, 0xcd, 0x08, 0x0b, 0x58, 0x22, 0xc0, 0x55, 0x66, 0x4f, 0x0d, + 0x3f, 0xf2, 0x18, 0xe1, 0x38, 0x76, 0x67, 0xc1, 0x0c, 0xb9, 0x35, 0x3b, 0x3a, 0xaa, 0x28, 0x7f, 0x9f, 0xa5, 0x36, + 0x93, 0x0c, 0x86, 0x28, 0x59, 0x2c, 0x15, 0x2c, 0xb6, 0xef, 0x02, 0x0e, 0x1a, 0x39, 0x52, 0xac, 0xf8, 0xca, 0xc6, + 0x25, 0x82, 0xa8, 0x18, 0xad, 0xb6, 0xfa, 0x70, 0xdb, 0x43, 0x93, 0xc1, 0xb0, 0x17, 0x92, 0x70, 0xe6, 0x90, 0xdd, + 0x72, 0x2a, 0x9c, 0xa9, 0x92, 0xa8, 0xc4, 0x70, 0xa0, 0x96, 0x84, 0x45, 0x11, 0x3f, 0xbf, 0x45, 0x2c, 0x80, 0x87, + 0x08, 0x29, 0x87, 0x3f, 0x73, 0x9f, 0x7e, 0x35, 0x87, 0x87, 0xc2, 0x02, 0x61, 0x6d, 0x47, 0xaa, 0x10, 0xda, 0x20, + 0xac, 0xfd, 0x70, 0x2d, 0x51, 0xf2, 0x7c, 0x11, 0x9c, 0xda, 0xe4, 0x2d, 0x37, 0xc7, 0x36, 0xd0, 0x36, 0xaa, 0xd9, + 0xd1, 0x51, 0xcc, 0x92, 0x12, 0x31, 0xc8, 0x61, 0xdb, 0x2d, 0x52, 0x00, 0xad, 0x6f, 0x8c, 0x1b, 0x7a, 0x36, 0x0c, + 0xce, 0x21, 0x4b, 0x84, 0x7c, 0x98, 0x65, 0x4c, 0x29, 0x59, 0x1c, 0x1d, 0x1d, 0x9a, 0xf2, 0x25, 0x67, 0x01, 0x8b, + 0xf8, 0xfa, 0x46, 0x54, 0x43, 0x40, 0xd5, 0x69, 0xeb, 0xf9, 0x26, 0x52, 0xf1, 0x4d, 0x9e, 0x09, 0x49, 0xa3, 0x4f, + 0x9f, 0xa2, 0x86, 0xc6, 0x0e, 0x0e, 0x53, 0xe6, 0xbb, 0xbe, 0x7b, 0xc2, 0x2c, 0x5b, 0x68, 0x98, 0x90, 0x1d, 0xd0, + 0xec, 0xe5, 0x07, 0xe3, 0xfa, 0x90, 0xb0, 0xc6, 0x0a, 0x6d, 0x82, 0x15, 0xdd, 0xdb, 0xb4, 0xe1, 0x6f, 0xec, 0xd2, + 0xad, 0xa6, 0x86, 0xa7, 0x08, 0xd6, 0x71, 0xc0, 0x86, 0x1b, 0x6c, 0x60, 0xef, 0x67, 0x23, 0xcd, 0x40, 0x07, 0x7a, + 0xd8, 0x73, 0xf9, 0x44, 0x59, 0xc8, 0x15, 0xec, 0xaf, 0x25, 0x53, 0xda, 0x22, 0x72, 0xac, 0xb1, 0xc4, 0x70, 0x46, + 0x6d, 0x33, 0x9d, 0x35, 0x96, 0x74, 0xdf, 0xd8, 0x5e, 0x2f, 0xe0, 0x6c, 0x54, 0x80, 0xd4, 0xdf, 0xc7, 0x27, 0x18, + 0xab, 0x46, 0xeb, 0xf5, 0x5b, 0xee, 0x5b, 0xa9, 0xd6, 0xb2, 0xe4, 0xd7, 0xb6, 0x16, 0x85, 0x09, 0xe4, 0x0e, 0xe7, + 0xc3, 0xb6, 0x1b, 0xbf, 0x18, 0x92, 0xc3, 0x56, 0x89, 0xc5, 0x0e, 0xac, 0x76, 0x3c, 0x16, 0x8a, 0xaf, 0x6d, 0x53, + 0xc8, 0x9c, 0xf5, 0x35, 0x7c, 0x49, 0x66, 0x3b, 0xb8, 0x3a, 0x23, 0x03, 0xe0, 0x3a, 0x92, 0xd9, 0xf0, 0x5b, 0xf8, + 0xe4, 0x29, 0x42, 0xac, 0x77, 0xf3, 0x2a, 0xc2, 0xf1, 0xb5, 0x4e, 0x38, 0xb6, 0xa6, 0x11, 0x2d, 0xca, 0x2a, 0x51, + 0x89, 0x66, 0x6e, 0xab, 0x57, 0x59, 0x58, 0x98, 0xc1, 0x54, 0x53, 0x0a, 0x9a, 0x78, 0x45, 0xe7, 0x4c, 0xc5, 0x0c, + 0xe1, 0x6f, 0x15, 0xb0, 0xf8, 0x09, 0x45, 0x86, 0xc1, 0x19, 0xaa, 0xe0, 0x0c, 0x05, 0x76, 0x17, 0x98, 0xb4, 0xfa, + 0x96, 0x53, 0x98, 0x0d, 0xd4, 0xb0, 0xe2, 0xed, 0x82, 0xc9, 0x9b, 0xc3, 0xd9, 0x21, 0xb8, 0x87, 0x9f, 0x4d, 0xb3, + 0x40, 0x33, 0x2c, 0x84, 0x42, 0xf8, 0xb0, 0xb5, 0xbd, 0x92, 0xbe, 0x54, 0x35, 0xc7, 0xc1, 0x10, 0xd6, 0xc1, 0x1c, + 0x1b, 0x09, 0x57, 0xe6, 0x6f, 0x6d, 0xab, 0x01, 0xd8, 0xae, 0x01, 0x33, 0x92, 0x49, 0x4e, 0x75, 0xdc, 0x3e, 0x69, + 0x01, 0x63, 0xfa, 0x95, 0xc1, 0xa9, 0x82, 0xd0, 0xee, 0x54, 0x58, 0xb2, 0x14, 0x6a, 0xc6, 0x27, 0x3a, 0xfe, 0x28, + 0x0c, 0x51, 0x61, 0xb9, 0x62, 0x20, 0xe1, 0x04, 0xec, 0xb1, 0x21, 0x38, 0x1f, 0x05, 0xf4, 0xd3, 0x2b, 0x0f, 0x22, + 0x37, 0x52, 0x43, 0xb8, 0x80, 0x3c, 0x54, 0xac, 0x75, 0x45, 0x66, 0x4a, 0xc6, 0x0d, 0xb8, 0xc7, 0x76, 0xdf, 0xb6, + 0x98, 0x3a, 0x6a, 0x20, 0x02, 0x0e, 0x56, 0xa4, 0x21, 0x89, 0x70, 0x89, 0x3a, 0xd1, 0xf2, 0xa5, 0xbc, 0x61, 0xc5, + 0x63, 0x0a, 0x83, 0x4f, 0x6d, 0xf5, 0x8d, 0x3d, 0x0a, 0x0c, 0xc5, 0xd7, 0x3d, 0x8f, 0x2f, 0x9f, 0xcc, 0xc4, 0xdf, + 0x14, 0x72, 0xce, 0x15, 0x03, 0xbe, 0xcd, 0xc2, 0x5f, 0xc0, 0x46, 0x33, 0x3b, 0x12, 0x8e, 0x1b, 0x56, 0xe2, 0xd7, + 0xc3, 0x97, 0x75, 0xfc, 0xfa, 0x74, 0xef, 0xe9, 0xd4, 0x53, 0xc0, 0xfa, 0x3e, 0x46, 0x38, 0x76, 0xe2, 0x45, 0x70, + 0xd2, 0x25, 0x33, 0xe4, 0x8e, 0xf9, 0xf5, 0x5a, 0x07, 0x62, 0x5c, 0x8d, 0x73, 0x64, 0x76, 0xdb, 0xa0, 0x0d, 0x1d, + 0x8f, 0x81, 0xc5, 0x2b, 0x64, 0x9e, 0x07, 0x87, 0x15, 0x16, 0xbd, 0xf2, 0x78, 0xfa, 0x74, 0xef, 0xe9, 0xf5, 0xf7, + 0x4e, 0x28, 0xc8, 0x0f, 0x0f, 0x29, 0x3f, 0x50, 0x31, 0x66, 0x05, 0xc8, 0x95, 0xc1, 0x6a, 0xb9, 0x73, 0xf6, 0xb1, + 0x14, 0x82, 0x65, 0x9a, 0x8d, 0x41, 0x68, 0x11, 0x44, 0x27, 0x33, 0xa9, 0x74, 0x99, 0x58, 0x8d, 0x5e, 0x84, 0x42, + 0x68, 0x92, 0xd1, 0x3c, 0x8f, 0xad, 0x80, 0x32, 0x97, 0x5f, 0xd9, 0x9e, 0x51, 0xf7, 0x6a, 0x43, 0x2e, 0x9b, 0x61, + 0x41, 0x33, 0x2c, 0x51, 0x8b, 0x9c, 0x67, 0xac, 0x3c, 0xbc, 0xae, 0x13, 0x2e, 0xc6, 0xec, 0x16, 0xe8, 0x08, 0xba, + 0xba, 0xba, 0x6a, 0xe1, 0x36, 0xda, 0x58, 0x80, 0xaf, 0x76, 0x00, 0xfb, 0x9d, 0x63, 0xd3, 0x0a, 0xe2, 0xab, 0xbd, + 0x64, 0x0d, 0x05, 0x67, 0x25, 0xf7, 0x82, 0x96, 0x25, 0xcf, 0x08, 0x8f, 0x59, 0xce, 0x34, 0xf3, 0xe4, 0x1c, 0x98, + 0x69, 0xbb, 0x75, 0xdf, 0x96, 0xf0, 0x2b, 0xd1, 0xc9, 0xef, 0x32, 0xbf, 0xe6, 0xaa, 0x14, 0xdd, 0xab, 0xe5, 0xa9, + 0xa0, 0xdd, 0xd7, 0x76, 0x79, 0xa8, 0xd6, 0x34, 0x9b, 0x59, 0x89, 0x3d, 0xde, 0x99, 0x52, 0xd5, 0x86, 0x23, 0xed, + 0xe5, 0x26, 0xfa, 0xa9, 0x70, 0xc3, 0xdc, 0x07, 0x82, 0x6b, 0x47, 0x14, 0x18, 0x08, 0x81, 0x76, 0xd9, 0x1e, 0xd3, + 0x3c, 0x1f, 0xd1, 0xec, 0x4b, 0x1d, 0xfb, 0x2b, 0x34, 0x20, 0xdb, 0xd4, 0x38, 0xc8, 0x0a, 0x48, 0x56, 0x38, 0x6f, + 0x4f, 0xa5, 0x6b, 0x1b, 0x25, 0x3e, 0x6c, 0x55, 0x68, 0x5f, 0x5f, 0xe8, 0x6f, 0x62, 0xbb, 0x19, 0x91, 0x70, 0x33, + 0x8b, 0x81, 0x0a, 0xfc, 0x4b, 0x8c, 0xf3, 0xf4, 0xc0, 0xe1, 0x1d, 0x08, 0x1e, 0x9b, 0xad, 0x81, 0x68, 0xb4, 0xda, + 0x8c, 0xb9, 0xfa, 0x36, 0x04, 0xfe, 0xb7, 0x8c, 0xf2, 0x49, 0xd0, 0xc3, 0xbf, 0x3b, 0xd0, 0x92, 0xc6, 0x39, 0xc6, + 0xb9, 0x1c, 0x99, 0x63, 0x28, 0x3c, 0xa1, 0xf9, 0x19, 0x98, 0x17, 0x83, 0xef, 0xaf, 0x6d, 0x96, 0xe1, 0xcb, 0x60, + 0x18, 0xaa, 0x17, 0x32, 0x14, 0x35, 0x14, 0x70, 0x44, 0x55, 0x98, 0x33, 0x57, 0xd6, 0x44, 0x49, 0xc7, 0xb5, 0x5b, + 0x71, 0xdc, 0xd1, 0xdc, 0x82, 0xc4, 0x71, 0xac, 0x40, 0x9a, 0xf3, 0xfc, 0x7d, 0x35, 0x0b, 0xb5, 0x33, 0x0b, 0x95, + 0x04, 0xd2, 0x16, 0xaa, 0x90, 0x39, 0xa8, 0x9e, 0x6a, 0x81, 0xc2, 0x52, 0xc0, 0xb2, 0x26, 0x40, 0xa1, 0x51, 0x49, + 0x70, 0x73, 0xa2, 0x71, 0xe1, 0x44, 0x1d, 0x87, 0x6b, 0x40, 0x32, 0xaa, 0x2a, 0x12, 0xd9, 0xcd, 0x51, 0x93, 0x7d, + 0x25, 0x2e, 0xd0, 0x16, 0x7f, 0xbf, 0xd9, 0x38, 0x28, 0x31, 0xe4, 0x56, 0xa7, 0xc6, 0x18, 0x07, 0x60, 0xc1, 0x92, + 0x38, 0x66, 0xd8, 0xb2, 0x3e, 0xdb, 0xc0, 0x29, 0xdb, 0x3d, 0x24, 0x44, 0x56, 0xb0, 0xa9, 0x31, 0x95, 0x9e, 0xbb, + 0x92, 0x08, 0x53, 0xcf, 0x96, 0x16, 0xd5, 0xc4, 0x09, 0x89, 0xbc, 0x76, 0x22, 0xea, 0xaf, 0x6a, 0xc2, 0x61, 0x1a, + 0x14, 0xdb, 0xa4, 0x40, 0x54, 0x8b, 0x7d, 0xf0, 0xde, 0x87, 0x35, 0xb5, 0x76, 0x02, 0x88, 0x17, 0x35, 0x88, 0x07, + 0xa0, 0x95, 0x96, 0x78, 0xc9, 0x21, 0xa1, 0xf5, 0xca, 0x31, 0xc3, 0x85, 0x5d, 0x88, 0x1d, 0x28, 0x6e, 0xb3, 0x9f, + 0x06, 0x0b, 0x41, 0x96, 0x55, 0xc0, 0xdf, 0x85, 0x47, 0x44, 0x0c, 0x83, 0x17, 0xeb, 0xf5, 0x0e, 0xda, 0xed, 0xe5, + 0x42, 0x51, 0x52, 0x49, 0x87, 0xeb, 0xf5, 0xdf, 0x12, 0xc5, 0x8e, 0xff, 0xc5, 0x0c, 0xf5, 0x3d, 0xd1, 0x7d, 0xf8, + 0x12, 0x4a, 0x19, 0x76, 0xb4, 0x4a, 0x29, 0x05, 0x87, 0x3a, 0xd6, 0xd6, 0x17, 0x4a, 0x07, 0x94, 0xfb, 0xf1, 0x0e, + 0x01, 0x33, 0x89, 0xee, 0xa4, 0xae, 0xa6, 0xfc, 0xd8, 0x35, 0x2d, 0x10, 0x42, 0xa9, 0x32, 0xb2, 0xcc, 0xe1, 0x3e, + 0xf9, 0xf2, 0xe8, 0x48, 0x05, 0x0d, 0x7d, 0x2a, 0x29, 0xc5, 0xe7, 0x18, 0x4e, 0x65, 0x75, 0x27, 0x0c, 0xfb, 0xf2, + 0xd9, 0x9f, 0x43, 0x3b, 0xd2, 0x69, 0xab, 0x07, 0x82, 0x39, 0xbd, 0xa1, 0x5c, 0x1f, 0x94, 0xad, 0x58, 0xc1, 0x3c, + 0x66, 0x68, 0xe5, 0xb8, 0x8d, 0xa4, 0x60, 0xc0, 0x3f, 0x02, 0x59, 0xf0, 0x5c, 0xb4, 0x45, 0xfc, 0x6c, 0xc6, 0x40, + 0x95, 0xed, 0x19, 0x89, 0x52, 0x3c, 0x3c, 0x74, 0x07, 0x89, 0x6b, 0x78, 0xff, 0xd8, 0x37, 0xdb, 0xd5, 0x6b, 0xd2, + 0xc0, 0x82, 0x15, 0x13, 0x59, 0xcc, 0x7d, 0xde, 0x66, 0xeb, 0xdb, 0x11, 0x47, 0x3e, 0x89, 0xf7, 0xb6, 0xed, 0x44, + 0x80, 0xde, 0x96, 0xec, 0x5d, 0x49, 0xed, 0xb5, 0xd3, 0xb4, 0x3c, 0x80, 0xad, 0x82, 0xd0, 0x63, 0xa6, 0x0a, 0xa5, + 0x7c, 0xa7, 0x5e, 0xed, 0x59, 0xdd, 0xc9, 0x61, 0xbb, 0x57, 0x4a, 0x7e, 0x1e, 0x1b, 0x7a, 0x56, 0xc7, 0xe1, 0x4e, + 0x55, 0xb9, 0xcc, 0xc7, 0x6e, 0xb0, 0x02, 0x61, 0xe6, 0xf0, 0xe8, 0x86, 0xe7, 0x79, 0x95, 0xfa, 0x9f, 0x90, 0x76, + 0xe5, 0x48, 0xbb, 0xf4, 0xa4, 0x1d, 0x48, 0x05, 0x90, 0x76, 0xdb, 0x5c, 0x55, 0x5d, 0xee, 0x6c, 0x4f, 0x69, 0x89, + 0xba, 0x32, 0xe2, 0x34, 0xf4, 0xb7, 0xf4, 0x23, 0x40, 0x25, 0xf3, 0xf5, 0x25, 0x76, 0xfa, 0x18, 0x10, 0x03, 0xad, + 0x4e, 0x93, 0x85, 0x9a, 0x8a, 0x2f, 0x31, 0xc2, 0x6a, 0xc3, 0x4a, 0xcc, 0x7e, 0xf8, 0x14, 0x94, 0x76, 0xc1, 0x74, + 0xe0, 0x1c, 0x33, 0xc9, 0xff, 0x11, 0x1f, 0xe5, 0x67, 0x27, 0xdc, 0xec, 0x94, 0x9f, 0x1d, 0xd0, 0xfa, 0x6a, 0x76, + 0xe3, 0xef, 0x53, 0x7b, 0x33, 0x3d, 0x51, 0x4e, 0xaf, 0x5a, 0xef, 0xf5, 0x3a, 0xde, 0x4a, 0x01, 0x8d, 0xbe, 0x93, + 0x52, 0x8a, 0xb2, 0x75, 0xa0, 0x01, 0x21, 0x64, 0x20, 0x61, 0x63, 0x27, 0x5d, 0x9e, 0x72, 0x2f, 0xff, 0x95, 0x9e, + 0xc7, 0x28, 0xee, 0x6d, 0xfd, 0xc7, 0x72, 0xbe, 0x00, 0x86, 0x6c, 0x0b, 0xa5, 0xa7, 0xcc, 0x75, 0x58, 0xe5, 0x6f, + 0xf6, 0xa4, 0xd5, 0xea, 0x98, 0xfd, 0x58, 0xc3, 0xa6, 0x52, 0x6a, 0x3e, 0x6c, 0x6d, 0x96, 0x65, 0x52, 0x49, 0x38, + 0xf6, 0xe9, 0x56, 0x1e, 0x6f, 0x6b, 0x66, 0x7c, 0xc6, 0xeb, 0x58, 0x58, 0x3a, 0x2c, 0x80, 0xd6, 0x05, 0xe4, 0xc7, + 0xa3, 0x7b, 0xb8, 0xfe, 0x9b, 0x0a, 0x38, 0xab, 0xcd, 0x16, 0xf8, 0x56, 0x9b, 0xcd, 0x07, 0xed, 0x24, 0x6d, 0xfc, + 0x61, 0x8f, 0xdc, 0x5b, 0x42, 0xaf, 0xca, 0x74, 0x32, 0xe3, 0x60, 0x08, 0x69, 0x3b, 0x2c, 0x24, 0x59, 0xcd, 0xe5, + 0x98, 0xa5, 0x91, 0x5c, 0x30, 0x11, 0x6d, 0x40, 0xcf, 0xea, 0x10, 0xe0, 0x9f, 0x22, 0x5e, 0xbd, 0xad, 0xeb, 0x5b, + 0xd3, 0x0f, 0x7a, 0x03, 0xaa, 0xb0, 0x97, 0x7c, 0x8f, 0x32, 0xf6, 0x03, 0x2b, 0x94, 0xe1, 0x49, 0x4b, 0xf6, 0xf6, + 0x25, 0xaf, 0x0e, 0xa8, 0x97, 0x3c, 0xfd, 0x76, 0x95, 0x4a, 0x20, 0x89, 0xda, 0xc9, 0x79, 0x72, 0x1a, 0x21, 0xa3, + 0x31, 0x7e, 0xe6, 0x35, 0xc6, 0xcb, 0x52, 0x63, 0xfc, 0x5c, 0x93, 0xe5, 0x96, 0xc6, 0xf8, 0x67, 0x41, 0x9e, 0xeb, + 0xfe, 0x73, 0xaf, 0x4d, 0x7f, 0x23, 0x73, 0x9e, 0xdd, 0xc5, 0x51, 0xce, 0x75, 0x13, 0x6e, 0x13, 0x23, 0xbc, 0xb2, + 0x19, 0xa0, 0x6a, 0x34, 0xfa, 0xee, 0x8d, 0x97, 0xff, 0xb0, 0x10, 0x24, 0xba, 0x97, 0x73, 0x7d, 0x2f, 0xc2, 0x33, + 0x4d, 0xfe, 0x84, 0x5f, 0xf7, 0x56, 0xf1, 0x2f, 0x54, 0xcf, 0x92, 0x82, 0x8a, 0xb1, 0x9c, 0xc7, 0xa8, 0x11, 0x45, + 0x28, 0x51, 0x46, 0x08, 0x79, 0x80, 0x36, 0xf7, 0xfe, 0xc4, 0x9f, 0x25, 0x89, 0xfa, 0x51, 0x63, 0xa6, 0x31, 0xa3, + 0xe4, 0xcf, 0xcb, 0x7b, 0xab, 0xcf, 0x72, 0x73, 0xf5, 0x27, 0x7e, 0xaa, 0x4b, 0xb5, 0x3e, 0xbe, 0x65, 0x24, 0x46, + 0xe4, 0xea, 0xa9, 0x1f, 0xd2, 0x63, 0x39, 0xb7, 0x0a, 0xfe, 0x08, 0xe1, 0xaf, 0xa0, 0xd7, 0xbd, 0xe2, 0x15, 0x11, + 0x72, 0x77, 0x30, 0x87, 0x24, 0x92, 0x46, 0x79, 0x10, 0x1d, 0x1d, 0x05, 0x69, 0x25, 0x0b, 0x81, 0x1f, 0x49, 0x52, + 0x13, 0xd5, 0x31, 0xa7, 0xd0, 0xd2, 0x23, 0x19, 0x73, 0xe4, 0x9b, 0x89, 0xbd, 0xa6, 0xda, 0xed, 0x58, 0x3e, 0xb0, + 0xba, 0x87, 0x84, 0x6b, 0x56, 0x50, 0x2d, 0x8b, 0x21, 0x0a, 0xd9, 0x12, 0xfc, 0x8a, 0x93, 0x3f, 0x07, 0x07, 0xff, + 0xcf, 0xff, 0xf8, 0x63, 0xf2, 0x47, 0x31, 0xfc, 0x13, 0x0b, 0x46, 0x4e, 0x2e, 0xe3, 0x7e, 0x1a, 0x1f, 0x36, 0x9b, + 0xeb, 0x3f, 0x4e, 0x06, 0xff, 0x4d, 0x9b, 0x7f, 0x3f, 0x6c, 0xfe, 0x3e, 0x44, 0xeb, 0xf8, 0x8f, 0x93, 0xfe, 0xc0, + 0x7d, 0x0d, 0xfe, 0xfb, 0xea, 0x0f, 0x35, 0x3c, 0xb6, 0x89, 0xf7, 0x10, 0x3a, 0x99, 0xe2, 0x7f, 0x08, 0x72, 0xd2, + 0x6c, 0x5e, 0x9d, 0x4c, 0xf1, 0xaf, 0x82, 0x9c, 0xc0, 0xdf, 0x3b, 0x4d, 0xde, 0xb2, 0xe9, 0xd3, 0xdb, 0x45, 0xfc, + 0xe7, 0xd5, 0xfa, 0xde, 0xea, 0x15, 0xdf, 0x40, 0xbb, 0x83, 0xff, 0xfe, 0xe3, 0x0f, 0x15, 0xfd, 0x78, 0x45, 0x4e, + 0x86, 0x0d, 0x14, 0x9b, 0xe4, 0x63, 0x62, 0xff, 0xc4, 0xfd, 0x74, 0xf0, 0xdf, 0x6e, 0x28, 0xd1, 0x8f, 0x7f, 0xfc, + 0x79, 0x79, 0x45, 0x86, 0xeb, 0x38, 0x5a, 0xff, 0x88, 0xd6, 0x08, 0xad, 0xef, 0xa1, 0x3f, 0x71, 0x34, 0x8d, 0x10, + 0xfe, 0x5d, 0x90, 0x93, 0x1f, 0x4f, 0xa6, 0xf8, 0x27, 0x41, 0x4e, 0xa2, 0x93, 0x29, 0xfe, 0x20, 0xc9, 0xc9, 0x7f, + 0xc7, 0xfd, 0xd4, 0x2a, 0xe1, 0xd6, 0x46, 0xfd, 0xb1, 0x86, 0x9b, 0x10, 0x5a, 0x30, 0xba, 0xd6, 0x5c, 0xe7, 0x0c, + 0xdd, 0x3b, 0xe1, 0xf8, 0xb9, 0x04, 0x60, 0xc5, 0x1a, 0x94, 0x34, 0xe6, 0x12, 0x76, 0xf5, 0x09, 0x16, 0x1e, 0x30, + 0xe8, 0x5e, 0xca, 0xb1, 0xd5, 0x13, 0xa8, 0x54, 0xdb, 0xdb, 0x5b, 0x05, 0xd7, 0xb7, 0xf8, 0x31, 0x79, 0x2e, 0xe3, + 0x36, 0xc2, 0x82, 0xc2, 0x8f, 0x0e, 0xc2, 0xef, 0xb5, 0xbb, 0xf0, 0x84, 0x6d, 0x6e, 0x31, 0x4c, 0x48, 0xcb, 0xcf, + 0x44, 0x08, 0xbf, 0xdc, 0x93, 0xa9, 0x67, 0xa0, 0x7e, 0x40, 0x58, 0xab, 0xf0, 0x7a, 0x14, 0x3f, 0xd6, 0xa4, 0x44, + 0x8e, 0x77, 0x05, 0x63, 0xbf, 0xd1, 0xfc, 0x0b, 0x2b, 0xe2, 0xa7, 0x1a, 0xb7, 0x3b, 0x0f, 0xb0, 0x51, 0x55, 0x1f, + 0xb6, 0x51, 0xaf, 0xbc, 0xdd, 0x7a, 0x2f, 0xed, 0x7d, 0x02, 0x9c, 0xc2, 0x75, 0x7d, 0x0d, 0xac, 0xfd, 0x21, 0xdf, + 0x51, 0x6a, 0x15, 0xf4, 0x26, 0x42, 0xf5, 0xab, 0x54, 0x2e, 0xbe, 0xd2, 0x9c, 0x8f, 0x0f, 0x34, 0x9b, 0x2f, 0x72, + 0xaa, 0xd9, 0x81, 0x9b, 0xf3, 0x01, 0x85, 0x86, 0xa2, 0x92, 0xa7, 0xf8, 0x59, 0x54, 0x9b, 0xf6, 0x67, 0x91, 0x54, + 0x7b, 0x27, 0x86, 0xfb, 0x2c, 0xc7, 0x97, 0x28, 0x5a, 0x5e, 0x97, 0x6d, 0xdf, 0x08, 0x36, 0xdb, 0xa0, 0x2c, 0x1b, + 0x9a, 0xf3, 0x5b, 0x61, 0xb8, 0xdf, 0x24, 0xa4, 0xd3, 0x8f, 0x2e, 0xd5, 0xd7, 0xe9, 0x55, 0x04, 0x37, 0x39, 0x05, + 0x11, 0xcc, 0x28, 0x8f, 0xa0, 0x04, 0x25, 0xad, 0x1e, 0xbd, 0x64, 0x3d, 0xda, 0x68, 0x78, 0x36, 0x3b, 0x23, 0x7c, + 0x40, 0x6d, 0xfd, 0x1c, 0xcf, 0xf0, 0x98, 0x34, 0xdb, 0x78, 0x49, 0x5a, 0xa6, 0x4a, 0x6f, 0x79, 0x99, 0xb9, 0x7e, + 0x8e, 0x8e, 0xe2, 0x22, 0xc9, 0xa9, 0xd2, 0x2f, 0x40, 0x23, 0x40, 0x96, 0x78, 0x46, 0x8a, 0x84, 0xdd, 0xb2, 0x2c, + 0xce, 0x10, 0x9e, 0x39, 0x1a, 0x84, 0x7a, 0x68, 0x49, 0x82, 0x62, 0x20, 0x67, 0x10, 0xc1, 0xfa, 0xb3, 0x41, 0x7b, + 0x48, 0x08, 0x89, 0x0e, 0x9b, 0xcd, 0xa8, 0x5f, 0x90, 0x7f, 0x88, 0x14, 0x52, 0x02, 0x76, 0x9a, 0xfc, 0x0a, 0x49, + 0x9d, 0x20, 0x29, 0xfe, 0x20, 0x13, 0xcd, 0x94, 0x8e, 0x21, 0x19, 0x94, 0x04, 0xca, 0x63, 0x78, 0x74, 0x79, 0x12, + 0x35, 0x20, 0xd5, 0xa0, 0x28, 0xc2, 0x05, 0xb9, 0xd3, 0x28, 0x9d, 0x0d, 0x4e, 0x87, 0xe1, 0x19, 0x61, 0x53, 0xa1, + 0xff, 0x3b, 0xdd, 0x9f, 0x0d, 0x5a, 0xa6, 0xff, 0xab, 0xa8, 0x1f, 0x17, 0x44, 0x59, 0x36, 0xae, 0xaf, 0x52, 0xc1, + 0xcc, 0x7c, 0x51, 0xea, 0x06, 0xe8, 0xfa, 0x1e, 0x93, 0x66, 0x27, 0x8d, 0xc7, 0xe1, 0x4c, 0x9a, 0xd0, 0xa1, 0x03, + 0x05, 0xce, 0x09, 0x94, 0xc7, 0x05, 0x81, 0x4e, 0xab, 0x6a, 0x77, 0x3a, 0x75, 0x09, 0x3f, 0x46, 0x3f, 0xf6, 0x7f, + 0x12, 0xe9, 0xef, 0xc2, 0x8e, 0xe0, 0x27, 0xb1, 0x5e, 0xc3, 0xdf, 0xdf, 0x45, 0x1f, 0x86, 0x65, 0xd2, 0xfe, 0xe1, + 0xd2, 0x7e, 0x85, 0x34, 0xc1, 0x52, 0x33, 0x60, 0xac, 0x4a, 0x7e, 0xcc, 0x2e, 0xce, 0x84, 0xd8, 0x19, 0x1c, 0x1d, + 0xf1, 0x01, 0x6d, 0xb4, 0x87, 0x70, 0x23, 0x50, 0x68, 0xf5, 0x1b, 0xd7, 0xb3, 0x38, 0x3a, 0xb9, 0x8a, 0x50, 0x3f, + 0x3a, 0x80, 0x55, 0xee, 0xc9, 0x06, 0x71, 0xb0, 0xce, 0x1a, 0x8c, 0xa6, 0xe3, 0x2b, 0xd2, 0xea, 0xc7, 0xc2, 0x12, + 0xf9, 0x1c, 0xe1, 0xcc, 0xd1, 0xd4, 0x16, 0x1e, 0xa3, 0x86, 0x10, 0x0d, 0xff, 0x3d, 0x46, 0x8d, 0x99, 0x6e, 0x4c, + 0x50, 0x9a, 0xc1, 0xdf, 0x78, 0x4c, 0x08, 0x69, 0x76, 0xca, 0x8a, 0xfe, 0xb0, 0xa4, 0x28, 0x9d, 0x78, 0xf5, 0xe8, + 0xc0, 0x6c, 0x0e, 0xd9, 0x88, 0xf9, 0x80, 0x0d, 0xd7, 0xeb, 0xe8, 0xb2, 0x7f, 0x15, 0xa1, 0x46, 0xec, 0xd1, 0xee, + 0xc4, 0xe3, 0x1d, 0x42, 0x58, 0x0c, 0x37, 0xee, 0x06, 0xea, 0x86, 0xd5, 0x6e, 0x9b, 0x56, 0xd5, 0xfe, 0x0f, 0xc8, + 0x02, 0xdb, 0x94, 0x72, 0x8f, 0xe5, 0x6f, 0x17, 0x30, 0x55, 0x8f, 0xdb, 0x92, 0xb4, 0x70, 0x41, 0xbc, 0xba, 0x9b, + 0x12, 0x5d, 0xe1, 0x7f, 0x46, 0xaa, 0xe2, 0x78, 0x90, 0xe3, 0xd9, 0x90, 0x28, 0x6a, 0xe4, 0x97, 0x9e, 0x57, 0xa6, + 0xb3, 0x9c, 0xdc, 0xb0, 0xad, 0xfb, 0xdf, 0x1c, 0xee, 0x64, 0x1e, 0xeb, 0x24, 0x5b, 0x16, 0x05, 0x13, 0xfa, 0x95, + 0x1c, 0x3b, 0xc6, 0x8e, 0xe5, 0x20, 0x5b, 0xc1, 0xc5, 0x2e, 0x06, 0xae, 0xae, 0xe3, 0x77, 0xca, 0x78, 0x27, 0x7b, + 0x49, 0xc6, 0x96, 0xe1, 0x32, 0xd7, 0xbd, 0xbd, 0xa5, 0x13, 0xa5, 0x63, 0x84, 0xc7, 0xee, 0x1e, 0x38, 0x4e, 0x92, + 0x64, 0x99, 0x64, 0x90, 0x0d, 0x1d, 0x28, 0xb4, 0x31, 0xfb, 0x2a, 0x56, 0xe4, 0xb1, 0x4e, 0x04, 0xbb, 0x35, 0xdd, + 0xc6, 0xa8, 0x3a, 0xc4, 0xfd, 0x7e, 0xbb, 0xa4, 0x3d, 0x43, 0x80, 0x54, 0x22, 0xe4, 0x98, 0x01, 0x84, 0xe0, 0xee, + 0xdf, 0x25, 0xcd, 0xa8, 0x0a, 0x6f, 0xb6, 0xaa, 0x01, 0x0e, 0x42, 0x95, 0xf7, 0x12, 0xf4, 0xc4, 0x86, 0x3d, 0x2b, + 0x0b, 0x5b, 0xe5, 0x39, 0x42, 0x7c, 0x12, 0x2f, 0x13, 0xb8, 0x11, 0x34, 0x98, 0x24, 0x04, 0x5a, 0xaf, 0x97, 0x21, + 0x6e, 0xcd, 0x2a, 0xc5, 0xf4, 0x84, 0xcc, 0x06, 0x45, 0xa3, 0x61, 0x94, 0xd7, 0x63, 0x8b, 0x17, 0x4b, 0x84, 0x27, + 0xe5, 0x5e, 0xf3, 0xe5, 0x16, 0xa4, 0xde, 0x55, 0x3c, 0xa9, 0x2b, 0x81, 0x1b, 0x42, 0x20, 0xa3, 0x5f, 0xd4, 0xd0, + 0x3a, 0x9e, 0x92, 0x93, 0x78, 0x90, 0xf4, 0xff, 0xe7, 0x10, 0xf5, 0xe3, 0xe4, 0x18, 0x9d, 0x58, 0x5a, 0x32, 0x41, + 0xbd, 0xcc, 0xf6, 0xb1, 0x32, 0xb7, 0x9f, 0x6d, 0x6c, 0x14, 0x90, 0xa9, 0xc4, 0x82, 0xce, 0x59, 0x3a, 0x85, 0x5d, + 0xef, 0x91, 0x67, 0x81, 0x01, 0x99, 0xd2, 0xa9, 0xa3, 0x2d, 0x49, 0xd4, 0x2f, 0x68, 0xf9, 0xd5, 0x8f, 0xfa, 0x59, + 0xf5, 0xf5, 0x3f, 0xa3, 0x7e, 0x4e, 0xd3, 0xc7, 0x7c, 0xe3, 0x94, 0xe4, 0xb5, 0x3e, 0xce, 0x7d, 0x1f, 0x1b, 0xbb, + 0x38, 0x01, 0xf0, 0xc6, 0x68, 0x57, 0x3b, 0xb2, 0x44, 0x1b, 0x3e, 0x29, 0xa9, 0x93, 0x4a, 0x34, 0x9d, 0x02, 0x54, + 0x83, 0x45, 0x50, 0xa1, 0x6d, 0x40, 0x30, 0x65, 0xc0, 0x16, 0x8f, 0xb4, 0x00, 0xcd, 0xe5, 0x55, 0x0b, 0xad, 0x6a, + 0x85, 0x1d, 0x67, 0x55, 0xbf, 0x8b, 0x2f, 0x89, 0xf7, 0x04, 0xa8, 0xf2, 0xe5, 0xb2, 0x37, 0x69, 0x34, 0x90, 0xf2, + 0xf8, 0x35, 0x1e, 0x4c, 0x86, 0xf8, 0x16, 0x50, 0x08, 0xd7, 0x30, 0x0a, 0xd7, 0xe6, 0xd8, 0x71, 0x73, 0x6c, 0x34, + 0xe4, 0x06, 0xf5, 0x82, 0xca, 0x4b, 0x57, 0x79, 0xb3, 0xb1, 0x90, 0xd9, 0xc6, 0xb8, 0x0b, 0x64, 0x52, 0xc0, 0x10, + 0x8c, 0x10, 0xf2, 0x59, 0xa2, 0xbd, 0xcd, 0x42, 0xa3, 0x50, 0xdd, 0xec, 0x5e, 0xa0, 0xa8, 0xf6, 0xf4, 0x88, 0x01, + 0x16, 0x50, 0xb5, 0x54, 0x23, 0xcf, 0x34, 0x1e, 0x37, 0xda, 0x06, 0xdd, 0x9b, 0xed, 0x5e, 0xbd, 0xb1, 0xfb, 0x55, + 0x63, 0x78, 0xdc, 0x20, 0xb3, 0x6a, 0x87, 0x6f, 0x64, 0xa3, 0xb1, 0xa9, 0xdf, 0x97, 0xfa, 0x4d, 0x5c, 0xbb, 0xbf, + 0x78, 0xba, 0x63, 0xe2, 0xe1, 0x4f, 0xdf, 0xea, 0xbc, 0x15, 0x09, 0x17, 0x82, 0x15, 0x70, 0xc2, 0x12, 0x8d, 0xc5, + 0x66, 0x53, 0x9e, 0xfa, 0xbf, 0x69, 0x6b, 0x33, 0x46, 0x38, 0xd0, 0x21, 0x23, 0xb5, 0x61, 0x89, 0x0b, 0x4c, 0x0d, + 0x15, 0x21, 0x84, 0xbc, 0xd7, 0xde, 0x3c, 0x46, 0x1b, 0x92, 0x94, 0x91, 0xe0, 0xec, 0x8e, 0x15, 0x61, 0xc9, 0xa7, + 0x7b, 0x8f, 0xe5, 0x77, 0x45, 0xba, 0x81, 0x18, 0xa6, 0xa6, 0x58, 0xee, 0x08, 0x59, 0x4e, 0xbe, 0x82, 0x9c, 0x53, + 0x5e, 0xb0, 0x24, 0x86, 0x20, 0x3e, 0xe1, 0x05, 0x33, 0x8c, 0xfb, 0x3d, 0x2f, 0x37, 0x66, 0x75, 0x4e, 0x33, 0x0b, + 0xb5, 0x3f, 0x00, 0xcd, 0x1c, 0x94, 0x43, 0x92, 0xec, 0x14, 0xfb, 0x74, 0xef, 0xe1, 0xeb, 0x7d, 0x32, 0xf4, 0x7a, + 0xed, 0xa4, 0xe7, 0x0c, 0x58, 0x1f, 0x9c, 0x57, 0x43, 0xcd, 0xdc, 0x8f, 0x34, 0xce, 0x0c, 0x13, 0x95, 0xc7, 0x1c, + 0x90, 0xe9, 0xd3, 0xbd, 0x87, 0xef, 0x62, 0x6e, 0x74, 0x53, 0x08, 0x87, 0xf3, 0x8e, 0x0b, 0x12, 0x53, 0xc2, 0x90, + 0x9d, 0x7c, 0x49, 0xc7, 0x8a, 0xe0, 0x74, 0x4f, 0xa9, 0xc9, 0x04, 0xb1, 0x63, 0x20, 0x86, 0x24, 0x73, 0x20, 0x20, + 0x19, 0xc2, 0x59, 0x4d, 0xae, 0x23, 0x66, 0x0d, 0x4c, 0x67, 0xd7, 0xb0, 0x18, 0x89, 0x65, 0x0f, 0x11, 0xce, 0x4c, + 0xb7, 0x7a, 0x63, 0x8f, 0x13, 0x49, 0xb7, 0x0d, 0xdd, 0x2a, 0x79, 0xf6, 0x03, 0x08, 0x5e, 0xfe, 0xe3, 0x95, 0x6b, + 0xbb, 0x4c, 0x78, 0xe2, 0x2d, 0xd2, 0x3e, 0xdd, 0x7b, 0xf8, 0x8b, 0x33, 0x4a, 0x5b, 0x50, 0x4f, 0xfe, 0x77, 0x64, + 0xd4, 0x87, 0xbf, 0x24, 0x55, 0xae, 0x29, 0xfc, 0xe9, 0xde, 0xc3, 0xf7, 0xfb, 0x8a, 0x41, 0xfa, 0x66, 0x59, 0x29, + 0x09, 0xcc, 0xf8, 0x56, 0x2c, 0x4f, 0x57, 0xee, 0xac, 0x48, 0xc5, 0x06, 0x9b, 0x13, 0x2a, 0x55, 0x9b, 0x52, 0xb7, + 0xf2, 0x04, 0x4b, 0x62, 0xae, 0x92, 0xea, 0xcb, 0xe6, 0xd0, 0x98, 0x4b, 0x71, 0x9d, 0xc9, 0x05, 0xfb, 0xc6, 0xfd, + 0xd2, 0x53, 0x8d, 0x12, 0x3e, 0x07, 0x43, 0x1c, 0x33, 0x76, 0x81, 0x0f, 0x5b, 0xa8, 0xb7, 0x75, 0x9e, 0x49, 0x83, + 0xa8, 0x45, 0xfd, 0xb0, 0xc1, 0x94, 0xb4, 0x70, 0x46, 0x5a, 0x38, 0x27, 0x6a, 0xd0, 0xb2, 0x27, 0x46, 0x2f, 0x2f, + 0x9b, 0xb6, 0xe7, 0x0e, 0x6c, 0xf7, 0xdc, 0xee, 0x5b, 0x7b, 0x28, 0xcf, 0x7a, 0xb9, 0xd1, 0x5f, 0x9a, 0x83, 0x7e, + 0x66, 0x50, 0xe3, 0x05, 0x8b, 0x0b, 0x5c, 0x98, 0x96, 0xaf, 0xf9, 0x28, 0x07, 0x3b, 0x15, 0x98, 0x19, 0xd6, 0x28, + 0x2d, 0xcb, 0xb6, 0x5d, 0xd9, 0x3c, 0x31, 0x6b, 0x55, 0xe0, 0x3c, 0x01, 0x52, 0x8e, 0x73, 0x67, 0xd7, 0xa3, 0x76, + 0xab, 0x9c, 0x1f, 0x1d, 0xc5, 0xb6, 0xd2, 0x8c, 0xc6, 0x85, 0xcf, 0xaf, 0x6e, 0x00, 0x3f, 0x58, 0xaa, 0x31, 0x43, + 0x66, 0x02, 0x8d, 0x46, 0x36, 0xdc, 0xd0, 0x43, 0x42, 0xe2, 0xbc, 0x0e, 0x45, 0x3f, 0x7a, 0xc3, 0x0c, 0x6e, 0x01, + 0xa0, 0xd1, 0x28, 0xaf, 0x7b, 0xb7, 0x20, 0xf6, 0x54, 0x63, 0xb9, 0xf9, 0x1a, 0x97, 0xd6, 0x44, 0xad, 0x1d, 0x3b, + 0x2c, 0x3f, 0x0a, 0x24, 0x42, 0xdc, 0x15, 0x7e, 0x3e, 0xc1, 0xd6, 0x10, 0x50, 0xee, 0x85, 0xb3, 0x81, 0xc0, 0xc6, + 0x6a, 0xcb, 0x15, 0xf2, 0xa4, 0xad, 0x83, 0x52, 0x5f, 0x08, 0x2e, 0xb8, 0xa0, 0x50, 0x63, 0xe3, 0xb0, 0xfc, 0x05, + 0xdb, 0x35, 0xe7, 0xc4, 0x0a, 0x39, 0x6d, 0x99, 0x19, 0x86, 0x01, 0x58, 0xa7, 0x04, 0xcc, 0x73, 0xf2, 0xf2, 0xdb, + 0xa8, 0xff, 0x30, 0x40, 0xfd, 0x47, 0x84, 0x05, 0xdb, 0xc0, 0xea, 0x4a, 0x12, 0xe9, 0x14, 0x14, 0xca, 0x67, 0x3d, + 0x5e, 0x10, 0xd0, 0xc6, 0xd5, 0xa1, 0x5a, 0xbb, 0xa2, 0xfc, 0x06, 0x65, 0x09, 0x77, 0x8a, 0xd1, 0x67, 0x62, 0x7f, + 0x9f, 0x1c, 0x57, 0x17, 0x74, 0xd0, 0xf5, 0x3e, 0xe5, 0x60, 0x48, 0x0a, 0x1f, 0xbe, 0xff, 0xfe, 0xdd, 0xea, 0xe3, + 0xc5, 0xee, 0x0e, 0x0e, 0xcc, 0x4a, 0x61, 0xd6, 0xc1, 0x06, 0xae, 0x1b, 0x99, 0x42, 0xff, 0xe5, 0x9d, 0x78, 0x9d, + 0x0a, 0x6d, 0x6d, 0x46, 0x7f, 0x1c, 0xc2, 0x68, 0xdb, 0x6d, 0x53, 0x82, 0x05, 0xcd, 0x02, 0x5d, 0xb2, 0xc6, 0xad, + 0xb4, 0xf8, 0x06, 0x19, 0x79, 0x68, 0x0a, 0x30, 0x31, 0xde, 0x9f, 0xfd, 0x68, 0xe3, 0xf0, 0xc4, 0x0e, 0x0d, 0xad, + 0x0c, 0x21, 0xb4, 0x78, 0x0f, 0x98, 0x63, 0x8f, 0x08, 0x00, 0xd1, 0x4b, 0x03, 0xa9, 0x0a, 0x64, 0x51, 0x54, 0x29, + 0xf2, 0x9f, 0x1f, 0x12, 0xf2, 0xb2, 0x52, 0x64, 0xbe, 0xad, 0x8c, 0xb9, 0x00, 0x31, 0x50, 0x0a, 0x17, 0x09, 0x65, + 0x82, 0xbd, 0x0c, 0x7d, 0xaf, 0x7d, 0x79, 0x23, 0x6d, 0x26, 0x15, 0x37, 0x1e, 0xdc, 0x94, 0x1a, 0x15, 0x9f, 0xcd, + 0xf7, 0x90, 0xd8, 0xca, 0xbd, 0x07, 0xb9, 0x9c, 0x9a, 0x41, 0xc2, 0xf7, 0x3b, 0x53, 0xda, 0xb7, 0xbb, 0xf9, 0xb2, + 0x6d, 0x11, 0xb3, 0xb5, 0x2e, 0x09, 0x17, 0x8a, 0x15, 0xfa, 0x11, 0x9b, 0xc8, 0x02, 0xee, 0x3f, 0x4a, 0xb0, 0xa0, + 0xcd, 0xbd, 0x40, 0x07, 0x68, 0x26, 0x18, 0x5c, 0x3a, 0x6c, 0xcd, 0xd0, 0xfc, 0xfa, 0x62, 0xee, 0xc0, 0x3f, 0x6d, + 0xd7, 0x7a, 0x79, 0x74, 0xf4, 0x95, 0x55, 0x80, 0x72, 0xc3, 0x34, 0xc3, 0x08, 0x88, 0x97, 0xe5, 0x72, 0xdc, 0xcd, + 0xf0, 0xbd, 0xb8, 0x52, 0x19, 0x78, 0xc2, 0x11, 0x12, 0xa1, 0xe7, 0x44, 0x6f, 0xa6, 0xdb, 0xf4, 0xde, 0x69, 0x33, + 0x44, 0x28, 0xd6, 0x00, 0xb9, 0x07, 0xb9, 0xdc, 0x2a, 0x99, 0x54, 0x65, 0x6b, 0x5b, 0x0e, 0xe2, 0x31, 0x80, 0x2b, + 0x36, 0x42, 0x4a, 0x80, 0x86, 0xfb, 0x85, 0x96, 0xf7, 0x12, 0xd8, 0x7f, 0xac, 0x12, 0x10, 0x69, 0x51, 0x6d, 0xe3, + 0x22, 0x84, 0xad, 0xa9, 0x4f, 0x60, 0x9c, 0xf0, 0xf0, 0xf9, 0x3e, 0x0d, 0xb5, 0x47, 0x6d, 0x66, 0xce, 0x20, 0x28, + 0x21, 0x51, 0x59, 0x21, 0xf9, 0x1a, 0x0b, 0xc7, 0xcd, 0xf9, 0x7b, 0x38, 0x20, 0xc5, 0x92, 0xc6, 0xf6, 0x6e, 0x0b, + 0x8e, 0x8f, 0x22, 0x59, 0xc6, 0xb5, 0xae, 0x7b, 0x85, 0xa9, 0x86, 0x1d, 0xe8, 0x68, 0x08, 0xa7, 0xc2, 0xdc, 0x13, + 0x3e, 0xae, 0x48, 0xaa, 0x76, 0x16, 0x50, 0x9e, 0x18, 0x56, 0xa6, 0x29, 0xc1, 0xfc, 0xb5, 0x33, 0x5f, 0x2b, 0x8f, + 0x09, 0x66, 0x86, 0x71, 0x63, 0x57, 0x81, 0x6d, 0x00, 0xc7, 0x56, 0x8f, 0x64, 0xb0, 0xa8, 0x5e, 0x29, 0x6e, 0x3a, + 0x0d, 0x98, 0x80, 0xb7, 0x60, 0x3d, 0xb3, 0xbd, 0xf5, 0x9f, 0x9b, 0x83, 0x51, 0x60, 0x55, 0x23, 0xf0, 0xd2, 0x10, + 0x78, 0x04, 0x8c, 0x9b, 0x37, 0x2d, 0xef, 0x3b, 0x23, 0x1a, 0xe1, 0x4f, 0x3c, 0x87, 0x67, 0x96, 0xe5, 0xde, 0xf9, + 0xd8, 0x5a, 0x91, 0x54, 0x10, 0xb0, 0x2d, 0xc2, 0x8e, 0xc8, 0x4b, 0x84, 0x55, 0xa3, 0xd1, 0x53, 0x97, 0xac, 0xd2, + 0xaa, 0x54, 0xc3, 0x14, 0x70, 0x4b, 0x0c, 0x78, 0x5f, 0x3b, 0x51, 0xc1, 0x90, 0xc0, 0x5b, 0x7f, 0x2b, 0x50, 0xdf, + 0x3f, 0x7c, 0x1b, 0x87, 0xf4, 0x2d, 0x2c, 0x5b, 0x5e, 0xc4, 0xc2, 0x94, 0xe2, 0xea, 0x0e, 0xe7, 0xcd, 0xf7, 0xcd, + 0x46, 0x60, 0xdc, 0x87, 0x6d, 0x0c, 0x36, 0x6e, 0xa8, 0xa7, 0x2d, 0x69, 0x28, 0x37, 0x61, 0x0f, 0x55, 0xf6, 0x8e, + 0x61, 0x67, 0x3d, 0x5d, 0x49, 0xbb, 0x9a, 0xa8, 0xcd, 0x46, 0xb1, 0xca, 0x68, 0x60, 0xcb, 0xb0, 0xd3, 0x1c, 0x33, + 0xbb, 0x0a, 0xfc, 0xc7, 0x0b, 0xa2, 0x71, 0x80, 0xac, 0x6f, 0xbe, 0x75, 0x9d, 0x52, 0x0d, 0x13, 0xb6, 0xb7, 0x3b, + 0x1f, 0x1f, 0xf3, 0x7d, 0xe7, 0x23, 0x96, 0x6e, 0xeb, 0x9b, 0xb3, 0xb1, 0xfd, 0x6f, 0x9c, 0x8d, 0x4e, 0x6d, 0xef, + 0x8f, 0x47, 0xe0, 0x4e, 0x6a, 0xc7, 0x63, 0x7d, 0x4d, 0x89, 0xc4, 0xc2, 0x2d, 0xc7, 0x55, 0x67, 0xbd, 0x16, 0x83, + 0x16, 0xa8, 0x9d, 0xa2, 0x08, 0x7e, 0xb6, 0xed, 0xcf, 0x80, 0x24, 0x5b, 0x1d, 0x72, 0x2c, 0x4a, 0x51, 0x06, 0x25, + 0x60, 0x40, 0x1d, 0x1b, 0x5b, 0x2f, 0x83, 0xd8, 0x0e, 0x87, 0x1c, 0x96, 0x13, 0x51, 0x5e, 0x5d, 0xc1, 0x88, 0xcd, + 0xb1, 0xe1, 0x04, 0xcc, 0x78, 0xaf, 0x55, 0xa1, 0x17, 0x3f, 0xff, 0x35, 0x73, 0x5a, 0x3b, 0x62, 0x2c, 0x27, 0x51, + 0xb3, 0x62, 0x70, 0x23, 0x70, 0x0c, 0xe3, 0xa1, 0x91, 0x50, 0xab, 0x53, 0x1d, 0xd5, 0x8e, 0x24, 0xdc, 0x02, 0xb5, + 0xdb, 0xa1, 0x39, 0x97, 0xd6, 0xeb, 0xbd, 0x07, 0x0b, 0x2e, 0x02, 0xdc, 0x7e, 0x4e, 0x74, 0x8d, 0xa4, 0x50, 0xe2, + 0x24, 0x28, 0x9c, 0x1b, 0x54, 0xd5, 0x44, 0x0e, 0x5a, 0x43, 0xe0, 0x49, 0x7b, 0xd9, 0xa5, 0xac, 0x84, 0xe4, 0xac, + 0xd1, 0x40, 0x79, 0xd9, 0x31, 0x1d, 0x88, 0x46, 0x36, 0xc4, 0x0c, 0x67, 0x56, 0x60, 0x81, 0xd3, 0x2b, 0xce, 0xab, + 0xae, 0x07, 0xd9, 0x10, 0xe1, 0x62, 0xbd, 0x8e, 0xed, 0xd0, 0x72, 0xb4, 0x5e, 0xe7, 0xe1, 0xd0, 0x4c, 0x3e, 0x54, + 0x7c, 0xd9, 0xd7, 0xe4, 0xa5, 0x39, 0x0f, 0x5f, 0xc2, 0x20, 0x1b, 0x24, 0xce, 0x9d, 0x4a, 0x30, 0x07, 0xcd, 0x55, + 0x43, 0x0e, 0xb2, 0x46, 0x7b, 0x18, 0xd0, 0xb0, 0x41, 0x36, 0x24, 0xf9, 0x06, 0x2c, 0x67, 0x95, 0x3b, 0x30, 0x3f, + 0xc3, 0xc1, 0xf6, 0xd9, 0x9c, 0x33, 0xb6, 0xc1, 0x70, 0x4d, 0xb6, 0x55, 0x06, 0x25, 0x5e, 0xb9, 0xc5, 0xf5, 0xe5, + 0x6a, 0x06, 0x16, 0x65, 0x21, 0xec, 0xae, 0x99, 0xfb, 0x20, 0xfc, 0x97, 0xd8, 0x5e, 0xd0, 0xd2, 0x88, 0x7b, 0x0b, + 0xf1, 0xbd, 0xed, 0x76, 0x92, 0x24, 0xb4, 0x98, 0x9a, 0x2b, 0x11, 0x7f, 0xc3, 0x6b, 0xf6, 0xc0, 0xa9, 0x1b, 0x67, + 0xd0, 0xf3, 0xa0, 0xec, 0x6c, 0x48, 0xec, 0xf8, 0x3d, 0xb3, 0xe3, 0x1d, 0x57, 0x28, 0xdd, 0xaf, 0x8b, 0xb0, 0x83, + 0xc9, 0xfe, 0x97, 0x07, 0x73, 0xe6, 0x06, 0x63, 0xd1, 0x64, 0x0b, 0x6e, 0xdf, 0x80, 0x07, 0xa5, 0x5b, 0x70, 0xfb, + 0x36, 0x7c, 0x3d, 0xb4, 0xf2, 0x6f, 0x0e, 0x30, 0x20, 0x13, 0x76, 0xa4, 0x55, 0x42, 0x30, 0xcc, 0xee, 0x36, 0x47, + 0x66, 0xc9, 0x2a, 0x1c, 0xae, 0x9a, 0xc4, 0x62, 0x6b, 0x2f, 0x54, 0x4c, 0x6a, 0x20, 0x18, 0x8b, 0xf4, 0x25, 0x0a, + 0x95, 0x06, 0x75, 0xe3, 0x18, 0xc0, 0x2a, 0xa7, 0xad, 0x7f, 0x79, 0x74, 0x04, 0x42, 0x03, 0xb0, 0x76, 0x49, 0x46, + 0x17, 0x7a, 0x59, 0x00, 0x7f, 0xa5, 0xfc, 0x6f, 0x48, 0x06, 0xb7, 0x13, 0x93, 0x06, 0x3f, 0x20, 0x61, 0x41, 0x95, + 0xe2, 0x5f, 0x6d, 0x9a, 0xfb, 0x8d, 0x0b, 0xe2, 0x31, 0x5a, 0x59, 0x4e, 0x51, 0xa2, 0x9e, 0x74, 0xe8, 0x5a, 0x87, + 0xdc, 0xd3, 0xaf, 0x4c, 0xe8, 0x97, 0x5c, 0x69, 0x26, 0x00, 0x00, 0x15, 0xe2, 0xc1, 0x94, 0x14, 0x82, 0xad, 0x5b, + 0xab, 0x45, 0xc7, 0xe3, 0xef, 0x56, 0xd1, 0x75, 0xb6, 0x68, 0x46, 0xc5, 0x38, 0xb7, 0x9d, 0x84, 0x36, 0x93, 0xde, + 0x4e, 0xb4, 0x2c, 0x19, 0x5a, 0xec, 0x54, 0xec, 0x87, 0xa1, 0xf5, 0xb1, 0x20, 0xfe, 0x5c, 0xf0, 0x67, 0xe9, 0x77, + 0xf9, 0x18, 0xb8, 0x52, 0xff, 0xc6, 0x2a, 0x84, 0x33, 0xc1, 0x3a, 0x20, 0xaf, 0x49, 0x7d, 0x9c, 0x1e, 0x75, 0x66, + 0x3b, 0xca, 0x85, 0xd2, 0x28, 0x6c, 0xeb, 0xa4, 0x30, 0x98, 0x72, 0xfe, 0x6d, 0x89, 0xeb, 0x17, 0x7f, 0x8c, 0xf8, + 0xa3, 0x43, 0xfc, 0xbb, 0x54, 0x1a, 0xad, 0x4a, 0x04, 0x43, 0x7e, 0x47, 0x32, 0x05, 0x57, 0xb1, 0x39, 0xd7, 0xcf, + 0xf5, 0x3c, 0xdf, 0xf2, 0xc4, 0xe9, 0x31, 0x55, 0x42, 0x47, 0xc5, 0x37, 0x0c, 0xbf, 0x60, 0x70, 0x6f, 0xfc, 0x8c, + 0x07, 0x55, 0x76, 0xef, 0x8b, 0x9f, 0x05, 0xf7, 0xc5, 0xcf, 0x78, 0xba, 0x5b, 0x34, 0xb8, 0x27, 0xee, 0x24, 0x17, + 0x49, 0x2b, 0xf2, 0x7c, 0xd4, 0x98, 0x56, 0xfe, 0x95, 0x76, 0x6b, 0xe0, 0xca, 0x26, 0x0e, 0x8c, 0xf3, 0xea, 0x22, + 0x14, 0x73, 0xe6, 0x8c, 0x96, 0xc3, 0xff, 0xd6, 0x3a, 0xb9, 0x93, 0x47, 0x5a, 0x29, 0xe4, 0x0d, 0x2d, 0xf4, 0x3d, + 0xd8, 0x70, 0xc5, 0x8e, 0x0f, 0x20, 0x25, 0xa0, 0x6c, 0xfb, 0xf7, 0xba, 0x08, 0xc4, 0x71, 0x65, 0x9d, 0x8f, 0xc2, + 0xf6, 0x49, 0x51, 0x72, 0x75, 0x75, 0x21, 0xe4, 0xd6, 0x68, 0x09, 0x10, 0xa6, 0xde, 0x35, 0x8f, 0x39, 0x9a, 0xcc, + 0xd2, 0xd5, 0xa6, 0x54, 0x1d, 0x14, 0x96, 0xab, 0xe3, 0x08, 0x17, 0x1b, 0x73, 0x83, 0xfe, 0x37, 0xc7, 0x9f, 0xb9, + 0xa3, 0x91, 0x3f, 0x95, 0x14, 0xe8, 0xc3, 0x7e, 0x5f, 0x9b, 0x3d, 0x24, 0xd2, 0xce, 0xa1, 0xb4, 0x14, 0x00, 0xac, + 0x36, 0xf8, 0xba, 0xf1, 0x38, 0xf5, 0x44, 0xba, 0xd9, 0x7c, 0xd3, 0x10, 0x16, 0xb3, 0xd2, 0x82, 0xc7, 0x74, 0xb3, + 0xc7, 0x72, 0xd4, 0xcb, 0xe2, 0xba, 0xdc, 0x63, 0xb5, 0x7e, 0xd1, 0x37, 0x40, 0x59, 0x19, 0xa2, 0xad, 0xd7, 0x71, + 0x1d, 0xde, 0x44, 0x04, 0xd7, 0x20, 0x08, 0x8b, 0xc0, 0x80, 0xa3, 0xc6, 0x78, 0xdb, 0x3a, 0x31, 0xda, 0xb6, 0x5f, + 0xf2, 0xac, 0x7b, 0x6d, 0x1c, 0xa1, 0xa2, 0xc1, 0x56, 0x0f, 0x35, 0x0f, 0xd8, 0xce, 0xae, 0xec, 0x28, 0x80, 0xd0, + 0x98, 0x7a, 0xe3, 0xdc, 0xca, 0x8a, 0x76, 0x0f, 0x7c, 0xd1, 0x77, 0xcc, 0x73, 0x1d, 0xe8, 0x76, 0xf3, 0x03, 0xdb, + 0xa6, 0x27, 0xf2, 0x5b, 0xb6, 0x4d, 0x35, 0x4e, 0xf8, 0xb0, 0x85, 0xbe, 0x6f, 0x08, 0x6b, 0xfb, 0xda, 0x5f, 0xe4, + 0x7f, 0xa1, 0xbb, 0x36, 0xa0, 0xa7, 0x05, 0xb3, 0xa7, 0x31, 0xef, 0xf5, 0x66, 0xf3, 0x53, 0xe9, 0xbf, 0x60, 0x6c, + 0x85, 0x7e, 0xb2, 0xbb, 0xc0, 0x89, 0x95, 0xc6, 0x21, 0x38, 0xfe, 0x9b, 0x93, 0x69, 0x2e, 0x47, 0x34, 0x7f, 0x07, + 0x3d, 0x56, 0xb9, 0xcf, 0xef, 0xc6, 0x05, 0xd5, 0xcc, 0xd1, 0x9a, 0x6a, 0x14, 0x7f, 0xf3, 0x60, 0x18, 0x7f, 0x73, + 0x4b, 0xb9, 0xab, 0x16, 0xf0, 0xea, 0x65, 0xd9, 0x44, 0xfa, 0xd3, 0xc6, 0xd3, 0x0e, 0xae, 0xf6, 0xf7, 0xb2, 0x4d, + 0xd2, 0x78, 0x49, 0xd2, 0xb8, 0x8a, 0xb7, 0x9b, 0x8a, 0xe3, 0xcf, 0xdf, 0x18, 0xec, 0x2e, 0x99, 0xfb, 0x1c, 0x90, + 0xb9, 0xcf, 0x3c, 0xfd, 0x6e, 0xad, 0x80, 0xe2, 0x9d, 0x26, 0xa7, 0xc6, 0x32, 0xc6, 0x8e, 0xfa, 0xad, 0x06, 0x83, + 0x06, 0x4d, 0xae, 0x02, 0x6f, 0x87, 0xea, 0xf4, 0xf2, 0xf6, 0x47, 0x71, 0xb6, 0x54, 0x5a, 0xce, 0x5d, 0xa3, 0xca, + 0xf9, 0x38, 0x99, 0x4c, 0x50, 0x60, 0x9b, 0x3b, 0xfc, 0xb4, 0xee, 0x46, 0xb6, 0xfa, 0xc2, 0xc5, 0x38, 0x55, 0xd8, + 0x9d, 0x2d, 0x2a, 0x95, 0x1b, 0xe2, 0xcd, 0x9c, 0x77, 0xf3, 0xf0, 0x84, 0x0b, 0xae, 0x66, 0xac, 0x88, 0x0b, 0xb4, + 0xfa, 0x56, 0x67, 0x05, 0xdc, 0xe6, 0xd8, 0xce, 0xf0, 0xb2, 0xb4, 0x1c, 0xd0, 0x09, 0xb4, 0x06, 0x3a, 0xa3, 0x39, + 0xd3, 0x33, 0x39, 0x06, 0xc3, 0x97, 0x64, 0x5c, 0xba, 0x53, 0x1d, 0x1d, 0x1d, 0xc6, 0x91, 0xd1, 0x5f, 0x80, 0x0f, + 0x7a, 0x98, 0x83, 0xfa, 0x2b, 0x70, 0x0c, 0xaa, 0xba, 0x66, 0x68, 0xc5, 0xb6, 0x7d, 0x68, 0x74, 0xf2, 0x85, 0xdd, + 0x61, 0x8e, 0x36, 0x9b, 0xd4, 0x8e, 0x3a, 0x9a, 0x70, 0x96, 0x8f, 0x23, 0xfc, 0x85, 0xdd, 0xa5, 0xa5, 0xdb, 0xba, + 0xf1, 0xb2, 0x36, 0x8b, 0x18, 0xc9, 0x1b, 0x11, 0xe1, 0xaa, 0x93, 0x74, 0xb5, 0xc1, 0xb2, 0xe0, 0x53, 0xc0, 0xd1, + 0x9f, 0xd9, 0x5d, 0xea, 0xda, 0x0b, 0x5c, 0x05, 0xd1, 0xca, 0x83, 0x3e, 0x09, 0x92, 0xc3, 0x65, 0x70, 0x02, 0xc7, + 0xc0, 0xd4, 0x1d, 0x92, 0x5a, 0xb9, 0x4a, 0x84, 0x44, 0x68, 0xf3, 0xef, 0x4e, 0x05, 0x4f, 0xc2, 0x73, 0x4e, 0xd7, + 0x2c, 0x6e, 0xb7, 0x2a, 0x31, 0xa8, 0x50, 0x59, 0x90, 0x7c, 0x8c, 0xb9, 0xdf, 0x7d, 0xce, 0xfb, 0x21, 0xd0, 0x99, + 0x4d, 0xa8, 0x6b, 0x34, 0x5d, 0x9a, 0x5f, 0xa8, 0xba, 0x83, 0x9a, 0xeb, 0xaa, 0xe2, 0xc1, 0xc7, 0x18, 0x00, 0x0f, + 0xd6, 0x32, 0xd4, 0x38, 0x84, 0x6e, 0xbc, 0x99, 0xea, 0x82, 0x92, 0x78, 0xe5, 0xe7, 0x90, 0xf2, 0x10, 0x8c, 0x7a, + 0x03, 0x68, 0xe8, 0x10, 0xcc, 0x5a, 0x1e, 0xf2, 0x49, 0x2c, 0x76, 0xce, 0x50, 0x69, 0xce, 0xd0, 0x24, 0x00, 0xf9, + 0x37, 0xce, 0x4c, 0x66, 0xa0, 0x61, 0x78, 0x4b, 0x73, 0x00, 0xba, 0xd5, 0x75, 0x38, 0x14, 0xae, 0x68, 0xe9, 0xbc, + 0x67, 0x17, 0x5d, 0xd6, 0x86, 0x15, 0x9b, 0x76, 0xd0, 0x26, 0x85, 0x29, 0x31, 0x5b, 0x60, 0xe3, 0xf5, 0x3e, 0xdc, + 0xdb, 0xd5, 0xc6, 0x45, 0xe2, 0xa7, 0x45, 0x3c, 0x4c, 0x62, 0x8a, 0x56, 0x3c, 0xa6, 0x58, 0x82, 0x1d, 0x64, 0xb1, + 0x29, 0xc7, 0xcf, 0xc2, 0xe5, 0xa8, 0x59, 0x49, 0xef, 0x77, 0x30, 0x04, 0x2e, 0x5f, 0x83, 0x6d, 0x28, 0xe6, 0x25, + 0x61, 0x89, 0x8d, 0xa7, 0x5f, 0xb0, 0x6e, 0x53, 0xbb, 0x20, 0x7e, 0x05, 0x16, 0x34, 0x5e, 0x05, 0xb3, 0x08, 0x9d, + 0xca, 0x9d, 0xc3, 0xa1, 0xbb, 0x26, 0xac, 0x8c, 0x57, 0x63, 0x45, 0xb6, 0x8e, 0x9e, 0xef, 0xdb, 0x78, 0xfe, 0xb5, + 0x64, 0xc5, 0xdd, 0x35, 0x03, 0x1b, 0x6b, 0x09, 0xee, 0xc6, 0xd5, 0x32, 0x54, 0x06, 0xf2, 0x7d, 0x69, 0x58, 0x97, + 0x0d, 0xfe, 0x6e, 0x54, 0x8c, 0x8d, 0xb9, 0xa7, 0x0c, 0xb4, 0x35, 0x76, 0xbb, 0xb0, 0x6f, 0xba, 0x6e, 0xb2, 0x9e, + 0x89, 0x95, 0x50, 0x41, 0xda, 0xdd, 0x2d, 0xe0, 0x22, 0xf4, 0x87, 0x1d, 0xa8, 0xe1, 0xb6, 0xea, 0x06, 0x92, 0xe0, + 0xda, 0x4f, 0x7e, 0x7b, 0xaa, 0xfb, 0xac, 0x75, 0xbf, 0x3d, 0xd5, 0xda, 0x65, 0xa1, 0x31, 0x24, 0xc2, 0xae, 0x9f, + 0xd2, 0x7f, 0x5a, 0x6c, 0x36, 0x68, 0x03, 0xc3, 0x7b, 0xc4, 0x7b, 0x71, 0xfc, 0xc8, 0x5b, 0x28, 0x26, 0x70, 0x91, + 0x7b, 0x9d, 0x4b, 0x4f, 0xc8, 0xab, 0x11, 0x3c, 0xe2, 0x3b, 0x43, 0x78, 0xc4, 0x03, 0xa7, 0x57, 0x90, 0x9a, 0xa6, + 0x82, 0x8d, 0x3d, 0xfd, 0x44, 0x16, 0x09, 0x0d, 0x1f, 0xf7, 0x9a, 0x13, 0xa1, 0xff, 0x4c, 0x81, 0xff, 0xc2, 0xa3, + 0xa5, 0xd6, 0x52, 0x60, 0x2e, 0x16, 0x4b, 0x8d, 0x95, 0x19, 0xfd, 0x6a, 0x22, 0x85, 0x6e, 0x4e, 0xe8, 0x9c, 0xe7, + 0x77, 0xe9, 0x92, 0x37, 0xe7, 0x52, 0x48, 0xb5, 0xa0, 0x19, 0xc3, 0xea, 0x4e, 0x69, 0x36, 0x6f, 0x2e, 0x39, 0x7e, + 0xce, 0xf2, 0xaf, 0x4c, 0xf3, 0x8c, 0xe2, 0xb7, 0x72, 0x24, 0xb5, 0xc4, 0xaf, 0x6f, 0xef, 0xa6, 0x4c, 0xe0, 0xf7, + 0xa3, 0xa5, 0xd0, 0x4b, 0xac, 0xa8, 0x50, 0x4d, 0xc5, 0x0a, 0x3e, 0xe9, 0x35, 0x9b, 0x8b, 0x82, 0xcf, 0x69, 0x71, + 0xd7, 0xcc, 0x64, 0x2e, 0x8b, 0xf4, 0xbf, 0x5a, 0xa7, 0xf4, 0xc1, 0xe4, 0xac, 0xa7, 0x0b, 0x2a, 0x14, 0x87, 0x85, + 0x49, 0x69, 0x9e, 0x1f, 0x9c, 0x76, 0x5b, 0x73, 0x75, 0x68, 0x2f, 0xfc, 0xa8, 0xd0, 0x9b, 0x3f, 0xf1, 0x6f, 0x12, + 0x46, 0x99, 0x8c, 0xb4, 0x70, 0x83, 0x5c, 0x65, 0xcb, 0x42, 0xc9, 0x22, 0x5d, 0x48, 0x2e, 0x34, 0x2b, 0x7a, 0x23, + 0x59, 0x8c, 0x59, 0xd1, 0x2c, 0xe8, 0x98, 0x2f, 0x55, 0x7a, 0xb6, 0xb8, 0xed, 0xd5, 0x7b, 0xb0, 0xf9, 0xa9, 0x90, + 0x82, 0xf5, 0x80, 0xdf, 0x98, 0x16, 0x72, 0x29, 0xc6, 0x6e, 0x18, 0x4b, 0xa1, 0x98, 0xee, 0x2d, 0xe8, 0x18, 0xec, + 0x80, 0xd3, 0x8b, 0xc5, 0x6d, 0xcf, 0xcc, 0xfa, 0x86, 0xf1, 0xe9, 0x4c, 0xa7, 0xdd, 0x56, 0xcb, 0x7e, 0x2b, 0xfe, + 0x37, 0x4b, 0xdb, 0x9d, 0xa4, 0xd3, 0x5d, 0xdc, 0x02, 0x07, 0xaf, 0x59, 0xd1, 0x04, 0x58, 0x40, 0xa5, 0x76, 0xd2, + 0x7a, 0x70, 0x7a, 0x1f, 0x32, 0xc0, 0xc6, 0xa1, 0x69, 0x26, 0x04, 0xc6, 0xee, 0xe9, 0x72, 0xb1, 0x60, 0x05, 0x78, + 0xd1, 0xf7, 0xe6, 0xb4, 0x98, 0x72, 0xd1, 0x2c, 0x4c, 0xa3, 0xcd, 0x8b, 0xc5, 0xed, 0x06, 0xe6, 0x93, 0x5a, 0xb3, + 0x55, 0x37, 0x2d, 0xf7, 0xb5, 0x0a, 0x86, 0x68, 0x62, 0xd2, 0xa4, 0xc5, 0x74, 0x44, 0xe3, 0x76, 0xe7, 0x3e, 0xf6, + 0xff, 0x4b, 0x3a, 0x28, 0x00, 0x5b, 0x73, 0xbc, 0x2c, 0xcc, 0x2d, 0x6a, 0xda, 0x56, 0xb6, 0xd9, 0x99, 0xfc, 0xca, + 0x0a, 0xdf, 0xaa, 0xf9, 0x58, 0xed, 0xcc, 0xfb, 0x3f, 0x6a, 0x94, 0xda, 0xb6, 0x5e, 0xa8, 0x6b, 0xa0, 0xd1, 0xbb, + 0x8d, 0xfd, 0x57, 0xe7, 0x82, 0xde, 0x3f, 0xeb, 0x7a, 0xb8, 0x4f, 0x26, 0x93, 0x1a, 0xd0, 0x3d, 0x74, 0xdb, 0xad, + 0xc5, 0xed, 0x41, 0xa7, 0xe5, 0x61, 0x6c, 0x61, 0x7a, 0xbe, 0xb8, 0xdd, 0xb3, 0x82, 0x01, 0x56, 0x6c, 0xf7, 0x76, + 0x90, 0x9c, 0xaa, 0x03, 0x46, 0x15, 0xdb, 0xfc, 0x89, 0xe7, 0x14, 0x70, 0xc3, 0x20, 0xed, 0xc0, 0xc8, 0xa9, 0xb0, + 0x02, 0xc3, 0xd5, 0x0d, 0x1f, 0xeb, 0x59, 0xda, 0x6e, 0xb5, 0x7e, 0xa8, 0x30, 0xa9, 0x37, 0xb3, 0x4b, 0xda, 0x2e, + 0xd8, 0xbc, 0x86, 0x5f, 0x23, 0x5a, 0xee, 0x82, 0xd5, 0x42, 0xba, 0x4e, 0x0b, 0x96, 0x9b, 0x28, 0x37, 0x1b, 0xb7, + 0x15, 0x76, 0xa6, 0xcc, 0xc5, 0x8c, 0x15, 0x5c, 0xf7, 0xea, 0x5f, 0x55, 0xc7, 0xbb, 0x73, 0xda, 0x58, 0xf9, 0x78, + 0x65, 0x6b, 0xb8, 0xcb, 0xd8, 0xc7, 0xf0, 0xb1, 0x8b, 0x95, 0x5f, 0x69, 0x11, 0x6f, 0x6d, 0x18, 0x1c, 0xd6, 0x40, + 0x9b, 0x60, 0xce, 0x05, 0x98, 0x8a, 0x0e, 0xf1, 0x37, 0xa0, 0x90, 0xd1, 0x3c, 0x8b, 0x61, 0x44, 0x07, 0xcd, 0x83, + 0xd3, 0x82, 0xcd, 0x91, 0x07, 0x44, 0x72, 0xbf, 0x5b, 0xb0, 0xf9, 0x26, 0x31, 0xd5, 0x57, 0x06, 0x75, 0x69, 0xce, + 0xa7, 0x22, 0xcd, 0x18, 0x6c, 0xab, 0x4d, 0xc2, 0x84, 0xe6, 0xfa, 0xae, 0x59, 0xc8, 0x9b, 0xd5, 0x98, 0xab, 0x45, + 0x4e, 0xef, 0xd2, 0x49, 0xce, 0x6e, 0x7b, 0xa6, 0x54, 0x93, 0x6b, 0x36, 0x57, 0xae, 0x6c, 0x0f, 0xd2, 0x9b, 0x63, + 0x6b, 0xce, 0x01, 0xd0, 0x93, 0x37, 0xdb, 0xfb, 0xda, 0x2f, 0x5a, 0x53, 0x2e, 0xf5, 0x41, 0x4b, 0xf5, 0xe6, 0x5c, + 0x34, 0xdd, 0x40, 0xce, 0x00, 0x23, 0x76, 0x21, 0x1f, 0xf4, 0x9f, 0xb0, 0xdb, 0x05, 0x15, 0x63, 0x36, 0x5e, 0x05, + 0xd5, 0x3a, 0x50, 0x2f, 0x2c, 0x95, 0x0a, 0x3d, 0x6b, 0x1a, 0x1b, 0xb4, 0xb8, 0x23, 0xd0, 0x37, 0x50, 0xfe, 0x41, + 0x0b, 0xdb, 0xff, 0x4f, 0xda, 0x28, 0xac, 0x7c, 0x00, 0xe1, 0xa0, 0xf8, 0xe4, 0xae, 0x09, 0x7f, 0x57, 0xe0, 0xf3, + 0xc4, 0x33, 0x9a, 0x3b, 0x88, 0xcc, 0xf9, 0x78, 0x9c, 0xd7, 0x46, 0x74, 0x15, 0x74, 0xd6, 0x46, 0x2b, 0x98, 0x7f, + 0xda, 0x3a, 0x68, 0x1d, 0x98, 0xb9, 0xb8, 0x6d, 0x70, 0x76, 0x76, 0xff, 0xf4, 0x01, 0xeb, 0xe5, 0x5c, 0xb0, 0xda, + 0x54, 0xbf, 0x0b, 0xea, 0xb0, 0xe1, 0x8e, 0x6b, 0xb8, 0x7d, 0xd0, 0x3e, 0x38, 0x6b, 0xfd, 0xe0, 0xa9, 0x48, 0xce, + 0x26, 0xda, 0xee, 0x9b, 0x1a, 0x59, 0xb9, 0xf0, 0x4d, 0xdf, 0x14, 0x74, 0x91, 0x0a, 0x09, 0x7f, 0x7a, 0xb0, 0xf9, + 0x27, 0xb9, 0xbc, 0x49, 0x67, 0x7c, 0x3c, 0x66, 0xc2, 0x16, 0x28, 0x13, 0x59, 0x9e, 0xf3, 0x85, 0xe2, 0x76, 0x35, + 0x1c, 0xee, 0x76, 0xb7, 0xa0, 0x1a, 0x0e, 0xe8, 0x34, 0x18, 0x50, 0xb7, 0x1a, 0x50, 0xd5, 0x7f, 0x38, 0xc2, 0xce, + 0xd6, 0x5c, 0x4d, 0xa9, 0x5e, 0x0d, 0x93, 0x3e, 0x2f, 0x95, 0x06, 0x98, 0x7b, 0xe3, 0x11, 0x73, 0xba, 0x34, 0x47, + 0x4c, 0xdf, 0x30, 0x26, 0xbe, 0x3d, 0x88, 0xab, 0x54, 0x8a, 0xfc, 0xce, 0x7e, 0xae, 0xc2, 0x2e, 0xe9, 0x52, 0xcb, + 0x4d, 0x32, 0xe2, 0x82, 0x16, 0x77, 0x9f, 0x14, 0x13, 0x4a, 0x16, 0x9f, 0xe4, 0x64, 0xb2, 0xfa, 0x16, 0xc9, 0xbb, + 0x8f, 0x36, 0x89, 0xe2, 0x62, 0x9a, 0x33, 0x4b, 0xe0, 0x0c, 0x22, 0xb8, 0x43, 0xc6, 0xb6, 0x6b, 0x9a, 0xac, 0x0d, + 0x7a, 0x93, 0x64, 0x39, 0x9f, 0x53, 0xcd, 0x0c, 0x9c, 0x03, 0x52, 0xe3, 0x26, 0x6f, 0xa9, 0x5c, 0xeb, 0xc0, 0xfe, + 0xa9, 0x4a, 0xc3, 0x36, 0x0a, 0x0a, 0xfb, 0x26, 0xb9, 0x30, 0xf8, 0x61, 0xc0, 0x61, 0x76, 0x91, 0x59, 0x3d, 0xb3, + 0x76, 0x01, 0xec, 0x60, 0x76, 0xb5, 0xa6, 0xae, 0x1c, 0x5d, 0xb2, 0x2d, 0x76, 0x5b, 0x3f, 0xd4, 0x73, 0x73, 0x3a, + 0x62, 0xf9, 0xca, 0x6e, 0x54, 0x0f, 0x5c, 0xb7, 0x55, 0xc3, 0x65, 0x0e, 0x48, 0x86, 0x01, 0xd1, 0x30, 0x4d, 0x9b, + 0x37, 0x6c, 0xf4, 0x85, 0x6b, 0xbb, 0x65, 0x9a, 0xea, 0x06, 0x9c, 0x8a, 0xcc, 0x98, 0x16, 0xac, 0x58, 0x79, 0x42, + 0xde, 0xaa, 0x11, 0xd0, 0x6b, 0x61, 0x0e, 0x68, 0x4d, 0x47, 0x4d, 0x08, 0xb1, 0xc6, 0x8a, 0xd5, 0xbe, 0xc9, 0xcd, + 0xe9, 0xad, 0x43, 0xb1, 0x07, 0xad, 0x1f, 0x6a, 0x87, 0xec, 0x59, 0xab, 0xe5, 0x8f, 0x88, 0xa6, 0xad, 0x91, 0xb6, + 0x93, 0x2e, 0x9b, 0x97, 0x89, 0x5a, 0x2e, 0xd2, 0x5a, 0xc2, 0x48, 0x6a, 0x2d, 0xe7, 0x36, 0x6d, 0x0f, 0x35, 0xaa, + 0x93, 0xde, 0x76, 0x67, 0x71, 0x7b, 0x60, 0xfe, 0x69, 0x1d, 0xb4, 0x76, 0x49, 0xed, 0x2e, 0x56, 0x9c, 0x22, 0x8f, + 0xc7, 0xd0, 0x71, 0x9b, 0xcd, 0x7b, 0x4b, 0x05, 0xc7, 0xbd, 0x81, 0xb8, 0x39, 0xd1, 0x36, 0x66, 0xb2, 0x00, 0x58, + 0xca, 0x05, 0x9c, 0xae, 0xf6, 0xb0, 0x83, 0x3e, 0x94, 0x04, 0x73, 0xf8, 0xbd, 0x8d, 0xd6, 0x87, 0xd5, 0x3a, 0xa8, + 0x06, 0x06, 0xff, 0x6c, 0xfe, 0xac, 0xf8, 0xf3, 0x27, 0x2c, 0x90, 0x8f, 0x78, 0x23, 0xe9, 0xae, 0x5b, 0x4e, 0x26, + 0x1a, 0xeb, 0x4a, 0x54, 0x33, 0x1e, 0x25, 0x73, 0x7a, 0x6b, 0x5d, 0x4b, 0xe6, 0x5c, 0x80, 0xe1, 0x1a, 0xc2, 0x3a, + 0x30, 0xf1, 0x9f, 0x85, 0x0d, 0x8d, 0x75, 0x0c, 0x0d, 0x1f, 0x77, 0x92, 0x6e, 0x17, 0xe1, 0x16, 0xee, 0x74, 0xbb, + 0x81, 0x4c, 0x36, 0xd1, 0xfb, 0x8a, 0xee, 0x2b, 0x29, 0xf7, 0x94, 0x3c, 0x31, 0x8d, 0x9e, 0xb4, 0x5b, 0x2d, 0x6c, + 0xdc, 0xe7, 0xcb, 0xc2, 0x42, 0xed, 0x69, 0xb6, 0xdd, 0x6a, 0x41, 0xb3, 0xf0, 0xc7, 0xcd, 0xeb, 0x67, 0xb2, 0x6a, + 0xa5, 0x2d, 0xdc, 0x4e, 0xdb, 0xb8, 0x93, 0x76, 0xf0, 0x69, 0x7a, 0x8a, 0xcf, 0xd2, 0x33, 0xdc, 0x4d, 0xbb, 0xf8, + 0x3c, 0x3d, 0xc7, 0xf7, 0xd3, 0xfb, 0xf8, 0x22, 0xbd, 0xc0, 0x0f, 0xd2, 0x07, 0xf8, 0x61, 0xda, 0x6e, 0xe1, 0x47, + 0x69, 0xbb, 0x8d, 0x1f, 0xa7, 0xed, 0x0e, 0x7e, 0x92, 0xb6, 0x4f, 0xf1, 0xd3, 0xb4, 0x7d, 0x86, 0x9f, 0xa5, 0xed, + 0x2e, 0xa6, 0x90, 0x3b, 0x82, 0xdc, 0x0c, 0x72, 0xc7, 0x90, 0xcb, 0x20, 0x77, 0x92, 0xb6, 0xbb, 0x1b, 0xac, 0x6c, + 0xc8, 0x8d, 0xa8, 0xd5, 0xee, 0x9c, 0x9e, 0x75, 0xcf, 0xef, 0x5f, 0x3c, 0x78, 0xf8, 0xe8, 0xf1, 0x93, 0xa7, 0xcf, + 0xa2, 0x21, 0xfe, 0x64, 0x3c, 0x5f, 0x94, 0x18, 0xf0, 0xa3, 0x76, 0x77, 0x88, 0xef, 0xfc, 0x67, 0xcc, 0x8f, 0x3a, + 0x67, 0x2d, 0x74, 0x75, 0x75, 0x36, 0x6c, 0x94, 0xb9, 0x8f, 0x8c, 0xc3, 0x4d, 0x95, 0x45, 0x08, 0x89, 0x21, 0x07, + 0xe1, 0x5b, 0xeb, 0x40, 0xc3, 0x62, 0x9e, 0x14, 0xe8, 0xe8, 0xc8, 0xfc, 0x98, 0xfa, 0x1f, 0x23, 0xff, 0x83, 0x06, + 0x8b, 0xf4, 0x95, 0xc6, 0xce, 0xe3, 0x5a, 0x97, 0xfe, 0x0e, 0xa5, 0x29, 0xd1, 0x01, 0x77, 0x46, 0xfd, 0xff, 0x15, + 0x59, 0xa3, 0x1d, 0x72, 0x66, 0x15, 0x63, 0xdd, 0x3e, 0x23, 0xab, 0x22, 0xed, 0x74, 0xbb, 0x47, 0x3f, 0x0f, 0xf8, + 0xa0, 0x3d, 0x1c, 0x1e, 0xb7, 0xef, 0xe3, 0x69, 0x99, 0xd0, 0xb1, 0x09, 0xa3, 0x32, 0xe1, 0xd4, 0x26, 0xd0, 0xd4, + 0xd6, 0x86, 0xa4, 0x33, 0x93, 0x04, 0x25, 0x36, 0xa9, 0x69, 0xfb, 0xbe, 0x6d, 0xfb, 0x01, 0x58, 0x93, 0x99, 0xe6, + 0x5d, 0xd3, 0x97, 0x97, 0x67, 0x6b, 0xd7, 0x28, 0x9e, 0xa6, 0xae, 0x35, 0x9f, 0x78, 0x36, 0x1c, 0xe2, 0x91, 0x49, + 0xec, 0x56, 0x89, 0xe7, 0xc3, 0xa1, 0xeb, 0xea, 0x81, 0xe9, 0xea, 0x7e, 0x95, 0x75, 0x31, 0x1c, 0x9a, 0x2e, 0x91, + 0x8b, 0x1d, 0xa0, 0xf4, 0xc1, 0x4d, 0xa9, 0xbf, 0xe1, 0x97, 0x9d, 0x6e, 0xb7, 0x0f, 0x18, 0x66, 0x6c, 0x82, 0x3d, + 0x8c, 0xbe, 0x04, 0x30, 0xba, 0x85, 0xdf, 0xfd, 0x4f, 0x34, 0xbd, 0xa3, 0x25, 0x90, 0xfa, 0xd1, 0x7f, 0x45, 0x0d, + 0x6d, 0x60, 0x6e, 0xfe, 0x4c, 0xed, 0x9f, 0x11, 0x6a, 0xdc, 0x50, 0x00, 0x37, 0x68, 0xa4, 0xbc, 0x4a, 0xd9, 0xf4, + 0x78, 0x4d, 0xc1, 0xc5, 0x67, 0xa6, 0x72, 0xda, 0x5f, 0xcf, 0x6e, 0x46, 0xeb, 0x99, 0xfa, 0x8a, 0xfe, 0x88, 0xff, + 0x50, 0xc7, 0xf1, 0xa0, 0xd9, 0x48, 0xd8, 0x1f, 0x63, 0xf0, 0x25, 0xea, 0xa7, 0x63, 0x36, 0x45, 0xfd, 0xc1, 0x1f, + 0x0a, 0x0f, 0x1b, 0x41, 0xc6, 0x0f, 0xbb, 0x29, 0xe0, 0x69, 0xb4, 0x9d, 0x18, 0xff, 0x80, 0xfa, 0xa8, 0xff, 0x87, + 0x3a, 0xfe, 0x03, 0xdd, 0x3b, 0x09, 0xb4, 0x26, 0xd2, 0x6d, 0xe1, 0x2a, 0xfc, 0xd0, 0x71, 0xb9, 0x85, 0x19, 0x6e, + 0x37, 0x19, 0x04, 0x6b, 0x03, 0x57, 0x74, 0x12, 0xcb, 0x06, 0x3f, 0x39, 0x6d, 0xa1, 0x1f, 0xda, 0x1d, 0x50, 0xae, + 0x34, 0xc5, 0xf1, 0xee, 0xa6, 0x2f, 0x9a, 0xa7, 0xf8, 0x41, 0xb3, 0xc0, 0x6d, 0x84, 0x9b, 0x6d, 0xaf, 0xf5, 0x1e, + 0xa8, 0xb8, 0x85, 0xb0, 0x8a, 0x2f, 0xe0, 0x9f, 0x33, 0x34, 0xac, 0x36, 0xe4, 0x2f, 0x74, 0xbb, 0x77, 0xf0, 0x9b, + 0x25, 0xb1, 0x6a, 0xf0, 0x93, 0xf3, 0x16, 0xfa, 0xe1, 0xdc, 0x74, 0xc4, 0x8e, 0xf5, 0x9e, 0xae, 0x24, 0x3e, 0x6b, + 0x4a, 0xe8, 0xa8, 0x55, 0xf6, 0x23, 0xe2, 0x2e, 0xc2, 0x22, 0x3e, 0x85, 0x7f, 0xda, 0x61, 0x3f, 0x8f, 0x77, 0xfa, + 0x31, 0xf3, 0x6e, 0xe3, 0xa4, 0x6b, 0xdd, 0x70, 0x95, 0xbd, 0x13, 0x6f, 0xb0, 0xab, 0xb6, 0xb9, 0xcc, 0x6b, 0x9f, + 0xc0, 0x07, 0xc2, 0xfa, 0x98, 0x28, 0xcc, 0x8e, 0xc1, 0x7f, 0x17, 0xcc, 0x56, 0xd4, 0xe5, 0x69, 0x4f, 0x35, 0x1a, + 0x48, 0x0c, 0xd4, 0xf0, 0x98, 0xb4, 0x9b, 0xba, 0xc9, 0x30, 0xfc, 0x6e, 0x90, 0x32, 0x28, 0x9c, 0xa8, 0x7a, 0x7d, + 0xed, 0x7a, 0xb5, 0x37, 0xff, 0x1e, 0x3b, 0x08, 0x21, 0xaa, 0x1f, 0xeb, 0x26, 0x43, 0x27, 0xa2, 0x11, 0xeb, 0x4b, + 0xd6, 0x3f, 0x4f, 0x5b, 0xc8, 0x60, 0xa7, 0xea, 0xc7, 0xac, 0xc9, 0x21, 0xbd, 0x93, 0xc6, 0xbc, 0xa9, 0xe1, 0xd7, + 0x59, 0x00, 0x2d, 0x01, 0x78, 0x57, 0x79, 0x23, 0x15, 0x27, 0x9d, 0x6e, 0x17, 0x0b, 0xc2, 0x93, 0xa9, 0xf9, 0xa5, + 0x08, 0x4f, 0x46, 0xe6, 0x97, 0x24, 0x25, 0xbc, 0x6c, 0xef, 0xb8, 0x20, 0xc1, 0xaa, 0x9a, 0x14, 0x0a, 0x0b, 0x5a, + 0xa0, 0x93, 0x8e, 0x37, 0x0b, 0xc0, 0x33, 0x3f, 0x07, 0x50, 0x83, 0x14, 0xc6, 0x22, 0x54, 0x36, 0x0b, 0x9c, 0x13, + 0x7a, 0x95, 0x74, 0xfb, 0xb3, 0x93, 0xb8, 0xd3, 0x94, 0xcd, 0x02, 0xa5, 0xb3, 0x13, 0x53, 0x13, 0x67, 0xe4, 0x35, + 0xb5, 0xad, 0xe1, 0x19, 0xdc, 0xe5, 0x66, 0x24, 0x3b, 0x3e, 0x6f, 0x35, 0x92, 0x2e, 0xc2, 0x83, 0x6c, 0xdd, 0xc2, + 0xf9, 0x7a, 0xdd, 0xc2, 0x34, 0x5c, 0x06, 0xe1, 0x01, 0x52, 0x6a, 0xea, 0xb6, 0x63, 0xf3, 0xf4, 0x79, 0xac, 0xc1, + 0x2e, 0x41, 0x83, 0xb7, 0x8f, 0x06, 0x3f, 0xa4, 0x94, 0xbb, 0x0b, 0x41, 0x64, 0xa2, 0x13, 0x4e, 0x42, 0xdd, 0xdd, + 0x6b, 0xe1, 0xd7, 0xd5, 0x5b, 0x96, 0x8a, 0xf8, 0xa3, 0xc4, 0x36, 0xad, 0x2a, 0xf6, 0x86, 0xee, 0x16, 0x7b, 0x4c, + 0x77, 0x8a, 0xdd, 0xdb, 0x53, 0xec, 0x97, 0xdd, 0x62, 0x7f, 0xc9, 0x40, 0xd3, 0xc8, 0x7f, 0x38, 0x3d, 0x6f, 0x35, + 0x4e, 0x01, 0x59, 0x4f, 0xcf, 0x5b, 0x55, 0xa1, 0x87, 0xb4, 0x5a, 0x2b, 0x4d, 0xae, 0xa9, 0xf5, 0xb5, 0xe0, 0xde, + 0xe9, 0xdb, 0x2c, 0x9c, 0x75, 0x39, 0x2f, 0xfd, 0xcb, 0x07, 0x5d, 0xb0, 0x65, 0x11, 0x86, 0xda, 0xe9, 0xc1, 0xf9, + 0xb0, 0x3f, 0x63, 0x71, 0x03, 0x52, 0x51, 0x3a, 0xd1, 0xee, 0x17, 0x2a, 0xaf, 0xb4, 0xff, 0x92, 0x90, 0xd4, 0x19, + 0x22, 0x2c, 0x49, 0x43, 0x0f, 0x4e, 0x87, 0xe6, 0xbc, 0x2b, 0xe0, 0xf7, 0x99, 0xf9, 0x5d, 0x2a, 0x94, 0x9c, 0x43, + 0xc6, 0xec, 0x66, 0x14, 0xf5, 0x05, 0x79, 0x43, 0x63, 0x63, 0x63, 0x8f, 0xd2, 0x32, 0x43, 0x7d, 0x85, 0x8c, 0x7b, + 0x65, 0x86, 0x20, 0xaf, 0x85, 0xfb, 0x8d, 0x57, 0x45, 0x0a, 0xf6, 0x36, 0x78, 0x9a, 0x82, 0xad, 0x0d, 0x1e, 0xa5, + 0x02, 0xfc, 0x41, 0x68, 0xca, 0x02, 0x2b, 0xfe, 0xa7, 0x4e, 0x83, 0x67, 0x6e, 0x9d, 0x89, 0xc1, 0xd2, 0x1e, 0x83, + 0x93, 0xe2, 0x2f, 0x19, 0xc3, 0xdf, 0x86, 0x46, 0x98, 0x41, 0x9b, 0x0c, 0x61, 0x9e, 0x14, 0x04, 0xd2, 0x30, 0x4f, + 0xa6, 0x84, 0x41, 0x93, 0x3c, 0x19, 0x11, 0x36, 0xe8, 0x04, 0x68, 0xf2, 0xc2, 0xc0, 0x0e, 0x80, 0xc3, 0xeb, 0x17, + 0xf9, 0xda, 0x36, 0x0e, 0x16, 0x02, 0xd0, 0x84, 0x20, 0x10, 0x73, 0x61, 0x00, 0x66, 0x23, 0xca, 0xfe, 0xec, 0x54, + 0xe1, 0x2f, 0x79, 0x42, 0x0d, 0xf5, 0xfe, 0x13, 0xc8, 0x6a, 0x7c, 0x6f, 0xc5, 0x36, 0xf8, 0xe0, 0xde, 0x4a, 0x6c, + 0x7e, 0x80, 0x3f, 0xca, 0xfe, 0x01, 0xe6, 0x21, 0xa1, 0x68, 0x83, 0xfe, 0x4c, 0xa1, 0xd8, 0x9e, 0x52, 0xe8, 0x4f, + 0xef, 0x0e, 0xa8, 0xc8, 0xea, 0x36, 0x8d, 0xc6, 0xb4, 0xf8, 0x12, 0xe1, 0xdf, 0xd3, 0x28, 0x07, 0x6e, 0x31, 0xc2, + 0x1f, 0xd3, 0xa8, 0x60, 0x11, 0xfe, 0x67, 0x1a, 0x8d, 0xf2, 0x65, 0x84, 0x7f, 0x4b, 0xa3, 0x69, 0x11, 0xe1, 0x0f, + 0xa0, 0xac, 0x1d, 0xf3, 0xe5, 0x3c, 0xc2, 0xef, 0xd3, 0x48, 0x19, 0x6f, 0x08, 0xfc, 0x30, 0x8d, 0x18, 0x8b, 0xf0, + 0xbb, 0x34, 0x92, 0x79, 0x84, 0xaf, 0xd3, 0x48, 0x16, 0x11, 0x7e, 0x94, 0x46, 0x05, 0x8d, 0xf0, 0xe3, 0x34, 0x82, + 0x42, 0xd3, 0x08, 0x3f, 0x49, 0x23, 0x68, 0x59, 0x45, 0xf8, 0x6d, 0x1a, 0x71, 0x11, 0xe1, 0x5f, 0xd3, 0x48, 0x2f, + 0x8b, 0xbf, 0x96, 0x92, 0xab, 0x08, 0x3f, 0x4d, 0xa3, 0x19, 0x8f, 0xf0, 0x9b, 0x34, 0x2a, 0x64, 0x84, 0x5f, 0xa7, + 0x11, 0xcd, 0x23, 0xfc, 0x2a, 0x8d, 0x72, 0x16, 0xe1, 0x5f, 0xd2, 0x68, 0xcc, 0x22, 0xfc, 0x32, 0x8d, 0xee, 0x58, + 0x9e, 0xcb, 0x08, 0x3f, 0x4b, 0x23, 0x26, 0x22, 0xfc, 0x73, 0x1a, 0x65, 0xb3, 0x08, 0xff, 0x23, 0x8d, 0x68, 0xf1, + 0x45, 0x45, 0xf8, 0x79, 0x1a, 0x31, 0x1a, 0xe1, 0x17, 0xb6, 0xa3, 0x69, 0x84, 0x7f, 0x4a, 0xa3, 0x9b, 0x59, 0xb4, + 0xc1, 0x52, 0x91, 0xd5, 0x6b, 0x9e, 0xb1, 0x7f, 0xb2, 0x34, 0x9a, 0xb4, 0x26, 0x17, 0x93, 0x49, 0x84, 0xa9, 0xd0, + 0xfc, 0xaf, 0x25, 0xbb, 0x79, 0xaa, 0x21, 0x91, 0xb2, 0xd1, 0xf8, 0x7e, 0x84, 0xe9, 0x5f, 0x4b, 0x9a, 0x46, 0x93, + 0x89, 0x29, 0xf0, 0xd7, 0x92, 0xce, 0x69, 0xf1, 0x96, 0xa5, 0xd1, 0xfd, 0xc9, 0x64, 0x32, 0x3e, 0x8b, 0x30, 0xfd, + 0x7b, 0xf9, 0xd1, 0xb4, 0x60, 0x0a, 0x8c, 0x18, 0x9f, 0x42, 0xdd, 0xee, 0xa4, 0x3b, 0xce, 0x22, 0x3c, 0xe2, 0xea, + 0xaf, 0x25, 0x7c, 0x4f, 0xd8, 0x59, 0x76, 0x16, 0xe1, 0x51, 0x4e, 0xb3, 0x2f, 0x69, 0xd4, 0x32, 0xbf, 0xc4, 0xcf, + 0x6c, 0xfc, 0x7a, 0x2e, 0xcd, 0x55, 0xc6, 0x84, 0x8d, 0xb2, 0x71, 0x84, 0xcd, 0x60, 0x26, 0xf0, 0xf7, 0x2b, 0x7f, + 0xc7, 0x74, 0x1a, 0x5d, 0xd0, 0xce, 0x88, 0x75, 0x22, 0x3c, 0x7a, 0x73, 0x23, 0xd2, 0x88, 0x76, 0x3b, 0xb4, 0x43, + 0x23, 0x3c, 0x5a, 0x16, 0xf9, 0xdd, 0x8d, 0x94, 0x63, 0x00, 0xc2, 0xe8, 0xe2, 0xe2, 0x7e, 0x84, 0x33, 0xfa, 0x8b, + 0x86, 0xda, 0xdd, 0xc9, 0x03, 0x46, 0x5b, 0x11, 0xfe, 0x99, 0x16, 0xfa, 0xe3, 0x52, 0xb9, 0x81, 0xb6, 0x20, 0x45, + 0x66, 0xef, 0x40, 0xcd, 0x1f, 0x8d, 0x3b, 0xe7, 0x0f, 0xda, 0x2c, 0xc2, 0xd9, 0xf5, 0x6b, 0xe8, 0xed, 0xfe, 0xa4, + 0xdb, 0x82, 0x0f, 0x01, 0x72, 0x29, 0x2b, 0xa0, 0x91, 0xf3, 0xb3, 0x07, 0x5d, 0x36, 0x36, 0x89, 0x8a, 0xe7, 0x5f, + 0xcc, 0xec, 0x2f, 0x60, 0x3e, 0x59, 0xc1, 0xe7, 0x4a, 0x8a, 0x34, 0x1a, 0x67, 0xed, 0xb3, 0x53, 0x48, 0xb8, 0xa3, + 0xc2, 0x03, 0xe7, 0x16, 0xaa, 0x5e, 0x8c, 0x22, 0x7c, 0x6b, 0x53, 0x2f, 0x46, 0xe6, 0x63, 0xfa, 0xee, 0x17, 0xf1, + 0x66, 0x9c, 0x46, 0xa3, 0x8b, 0x8b, 0xf3, 0x16, 0x24, 0xfc, 0x46, 0xef, 0xd2, 0x88, 0x3e, 0x80, 0xff, 0x20, 0xfb, + 0xe3, 0x33, 0xe8, 0x10, 0x46, 0x78, 0x3b, 0xfd, 0x18, 0xe6, 0x7c, 0x99, 0xd1, 0x2f, 0x3c, 0x8d, 0x46, 0xe3, 0xd1, + 0xfd, 0x73, 0xa8, 0x37, 0xa7, 0xd3, 0x67, 0x9a, 0x42, 0xbb, 0xad, 0x96, 0x69, 0xf9, 0x1d, 0xff, 0xca, 0x4c, 0xf5, + 0x6e, 0xf7, 0x7c, 0xd4, 0x81, 0x11, 0x5c, 0x83, 0x42, 0x05, 0xc6, 0x73, 0x91, 0x99, 0x06, 0xaf, 0xb3, 0xa7, 0xe3, + 0x34, 0x7a, 0xf0, 0xe0, 0xb4, 0x93, 0x65, 0x11, 0xbe, 0xfd, 0x38, 0xb6, 0xb5, 0x4d, 0x9e, 0x02, 0xd8, 0xa7, 0x11, + 0x7b, 0xf0, 0xe0, 0xfc, 0x3e, 0x85, 0xef, 0xe7, 0xa6, 0xad, 0x8b, 0xc9, 0x28, 0xbb, 0x80, 0xb6, 0xde, 0xc3, 0x74, + 0xce, 0x2e, 0x4e, 0xc7, 0xa6, 0xaf, 0xf7, 0x66, 0xd4, 0x9d, 0xc9, 0xd9, 0xe4, 0xcc, 0x64, 0x9a, 0xa1, 0x96, 0x9f, + 0xbf, 0xb2, 0x34, 0xca, 0xd8, 0xb8, 0x1d, 0xe1, 0x5b, 0xb7, 0x70, 0x0f, 0xce, 0x5a, 0xad, 0xf1, 0x69, 0x84, 0xc7, + 0x0f, 0x17, 0x8b, 0xb7, 0x06, 0x82, 0xed, 0xb3, 0x07, 0xf6, 0x5b, 0x7d, 0xb9, 0x83, 0xa6, 0x47, 0x06, 0x68, 0x63, + 0x3e, 0x37, 0x2d, 0x9f, 0x3f, 0x80, 0xff, 0xcc, 0xb7, 0x69, 0xba, 0xfc, 0x96, 0xe3, 0xa9, 0x5d, 0x94, 0x36, 0x7b, + 0xd0, 0x82, 0x1a, 0x13, 0xfe, 0x71, 0x54, 0x70, 0x40, 0xa3, 0x51, 0x07, 0xfe, 0x2f, 0xc2, 0x93, 0xfc, 0xfa, 0xb5, + 0xc3, 0xd9, 0xc9, 0x84, 0x4e, 0x5a, 0x11, 0x9e, 0xc8, 0x8f, 0x4a, 0xff, 0xf6, 0x50, 0xa4, 0x51, 0xa7, 0x73, 0x31, + 0x32, 0x65, 0x96, 0x3f, 0x2b, 0x6e, 0xf0, 0xb8, 0x65, 0x5a, 0x99, 0xd2, 0xb7, 0x6a, 0x74, 0x2d, 0x61, 0x25, 0xe1, + 0xbf, 0x08, 0x4f, 0x41, 0x0b, 0xe7, 0x5a, 0xb9, 0xb0, 0xdb, 0x61, 0xfa, 0xce, 0xa0, 0xe6, 0xf8, 0x3e, 0xc0, 0xcb, + 0x2f, 0xe3, 0x98, 0xd2, 0x6e, 0xa7, 0x15, 0x61, 0x33, 0xea, 0x8b, 0x16, 0xfc, 0x17, 0x61, 0x0b, 0x39, 0x03, 0xd7, + 0xe9, 0xc7, 0x67, 0x2f, 0x6f, 0xd2, 0x88, 0x8e, 0x27, 0x13, 0x58, 0x12, 0x33, 0x19, 0x5f, 0x6c, 0x26, 0x05, 0xbb, + 0xfb, 0xe5, 0xc6, 0x6d, 0x17, 0x93, 0xa0, 0x1d, 0x74, 0xce, 0x1f, 0x8c, 0xce, 0x22, 0xfc, 0x76, 0xcc, 0xa9, 0x80, + 0x55, 0xca, 0xc6, 0xdd, 0xac, 0x9b, 0x99, 0x84, 0xa9, 0x4c, 0xa3, 0x33, 0x58, 0xf2, 0x4e, 0x84, 0xf9, 0xd7, 0xeb, + 0x3b, 0x8b, 0x6e, 0x50, 0xdb, 0x21, 0xc8, 0xa4, 0xc5, 0xce, 0x2f, 0xb2, 0x08, 0xe7, 0xf4, 0xeb, 0xb3, 0x5f, 0x8a, + 0x34, 0x62, 0xe7, 0xec, 0x7c, 0x42, 0xfd, 0xf7, 0x3f, 0xd5, 0xcc, 0xd4, 0x68, 0x4d, 0xba, 0x90, 0x74, 0x23, 0xcc, + 0x58, 0xef, 0x67, 0x13, 0x83, 0x21, 0xaf, 0xe6, 0x52, 0x64, 0x4f, 0x27, 0x13, 0x69, 0xb1, 0x98, 0xc2, 0x26, 0xfc, + 0x1d, 0xa0, 0x4d, 0xc7, 0xe3, 0x0b, 0x76, 0x1e, 0xe1, 0xdf, 0xed, 0x2e, 0x71, 0x13, 0xf8, 0xdd, 0x62, 0x36, 0x73, + 0xbb, 0xfd, 0x77, 0x0b, 0x14, 0x98, 0xef, 0x84, 0x4e, 0xe8, 0xb8, 0x13, 0xe1, 0xdf, 0x0d, 0x5c, 0xc6, 0xa7, 0xf0, + 0x1f, 0x14, 0x80, 0xce, 0x1e, 0xb4, 0x18, 0x7b, 0xd0, 0x32, 0x5f, 0x61, 0x9e, 0x9b, 0xf9, 0xe8, 0x3c, 0x6b, 0x47, + 0xf8, 0x77, 0x87, 0x8e, 0x93, 0x09, 0x6d, 0x01, 0x3a, 0xfe, 0xee, 0xd0, 0xb1, 0xd3, 0x1a, 0x75, 0xa8, 0xf9, 0xb6, + 0x58, 0x73, 0x71, 0x3f, 0x63, 0x30, 0xb9, 0xdf, 0x2d, 0x42, 0xde, 0xbf, 0x7f, 0x71, 0xf1, 0xe0, 0x01, 0x7c, 0x9a, + 0xb6, 0xcb, 0x4f, 0xa5, 0x1f, 0xe6, 0x06, 0xc9, 0x5a, 0xd9, 0x19, 0xd0, 0xc9, 0xdf, 0xcd, 0x18, 0x27, 0x93, 0x09, + 0x6b, 0x45, 0x38, 0xe7, 0x73, 0x66, 0x31, 0xc1, 0xfe, 0x36, 0x1d, 0x9d, 0x76, 0xb2, 0xf1, 0x69, 0x27, 0xc2, 0xf9, + 0xdb, 0x67, 0x66, 0x36, 0x2d, 0x98, 0xbd, 0xdf, 0x72, 0x1e, 0x6b, 0xe6, 0xf4, 0x0d, 0x0c, 0x12, 0x56, 0x1a, 0x2a, + 0x7f, 0x08, 0xe8, 0xe1, 0xf9, 0x79, 0x36, 0x86, 0x81, 0x7e, 0x80, 0x6e, 0x01, 0x8c, 0x1f, 0xec, 0xe6, 0x1b, 0xd1, + 0x6e, 0x17, 0xa6, 0xfb, 0x61, 0xb1, 0x2c, 0x16, 0xaf, 0xd2, 0xe8, 0xc1, 0xe9, 0xfd, 0xd6, 0x78, 0x14, 0xe1, 0x0f, + 0x6e, 0x82, 0xa7, 0xd9, 0xe8, 0xf4, 0x7e, 0x3b, 0xc2, 0x1f, 0xcc, 0x7e, 0xbb, 0x3f, 0x3a, 0xbf, 0x80, 0x73, 0xe3, + 0x83, 0x5a, 0x14, 0x6f, 0xa7, 0xa6, 0xc0, 0x84, 0x3e, 0x80, 0x66, 0x7f, 0x35, 0xbb, 0x71, 0xdc, 0x86, 0x8d, 0xfc, + 0xc1, 0x6c, 0x32, 0x83, 0x27, 0xf7, 0xdb, 0xdd, 0x8b, 0x6e, 0x84, 0xe7, 0x7c, 0x2c, 0x80, 0xc0, 0x9b, 0x8d, 0xf2, + 0xa0, 0xfd, 0xe0, 0x7e, 0x2b, 0xc2, 0xf3, 0xb7, 0x3a, 0xfb, 0x48, 0xe7, 0x86, 0x1a, 0x4f, 0x00, 0x66, 0x73, 0xae, + 0xf4, 0xdd, 0x1b, 0xe5, 0xe8, 0x31, 0x6b, 0x47, 0x78, 0x2e, 0xb3, 0x8c, 0xaa, 0xb7, 0x36, 0x61, 0xd4, 0x8d, 0xb0, + 0xa0, 0x5f, 0xe9, 0x67, 0xe9, 0x37, 0xd3, 0x98, 0xd1, 0xb1, 0x49, 0x33, 0x38, 0x1c, 0xe1, 0x77, 0x63, 0xb8, 0x8c, + 0x4c, 0xa3, 0xc9, 0x78, 0xd2, 0x05, 0xf0, 0x00, 0x01, 0xb2, 0xd8, 0x0d, 0xd0, 0x80, 0xaf, 0xf1, 0xa3, 0x51, 0x1a, + 0x9d, 0x8f, 0x2e, 0x58, 0xe7, 0x34, 0xc2, 0x25, 0x35, 0xa2, 0x5d, 0xc8, 0x37, 0x9f, 0x1f, 0xcd, 0x96, 0x3a, 0xb3, + 0x09, 0x06, 0x40, 0x63, 0x7a, 0xbf, 0x35, 0x3e, 0x8f, 0xf0, 0xe2, 0x35, 0xf3, 0x7b, 0x8c, 0x31, 0x76, 0x01, 0xb0, + 0x84, 0x24, 0x83, 0x40, 0x17, 0x93, 0xd1, 0x83, 0x0b, 0xf3, 0x0d, 0x60, 0xa0, 0x13, 0xc6, 0x00, 0x48, 0x8b, 0xd7, + 0xac, 0x04, 0xc4, 0x78, 0x74, 0xbf, 0x05, 0xf4, 0x65, 0x41, 0x17, 0xf4, 0x8e, 0xde, 0x3c, 0x5d, 0x98, 0x39, 0x4d, + 0xc6, 0xdd, 0x08, 0x2f, 0x9e, 0xff, 0xbc, 0x58, 0x4e, 0x26, 0x66, 0x42, 0x74, 0xf4, 0x20, 0xc2, 0x0b, 0x56, 0x2c, + 0x61, 0x8d, 0x2e, 0xba, 0xa7, 0x93, 0x08, 0x3b, 0x34, 0xcc, 0x5a, 0xd9, 0x08, 0x6e, 0x5b, 0x97, 0xf3, 0x34, 0x1a, + 0x8f, 0x69, 0x6b, 0x0c, 0x77, 0xaf, 0xf2, 0xe6, 0x97, 0xc2, 0xa2, 0x11, 0x33, 0xf8, 0xe0, 0xd6, 0x10, 0xe6, 0x0b, + 0xf0, 0xf8, 0x38, 0x62, 0x59, 0x46, 0x5d, 0xe2, 0xf9, 0xf9, 0xe9, 0x29, 0xe0, 0x9e, 0x9d, 0xa1, 0x45, 0x90, 0x37, + 0xea, 0x6e, 0x54, 0x48, 0x38, 0xba, 0x80, 0xa8, 0x02, 0x59, 0x7d, 0x73, 0xf7, 0xda, 0xd0, 0xd5, 0xf6, 0xf9, 0x03, + 0x58, 0x00, 0x45, 0xc7, 0xe3, 0x57, 0xf6, 0x70, 0xbb, 0x18, 0x9d, 0x75, 0xdb, 0xa7, 0x11, 0xf6, 0x1b, 0x81, 0x5e, + 0xb4, 0xee, 0x77, 0xa0, 0x84, 0x18, 0xdf, 0xd9, 0x12, 0x93, 0x33, 0x7a, 0x76, 0xde, 0x8a, 0xb0, 0xdf, 0x1a, 0xec, + 0x62, 0xd4, 0xbd, 0x0f, 0x9f, 0x6a, 0xc6, 0xf2, 0xdc, 0xe0, 0x77, 0x17, 0xe0, 0xa2, 0xf8, 0x33, 0x41, 0xd3, 0x88, + 0xb6, 0xba, 0x9d, 0xce, 0x18, 0x3e, 0xf3, 0xaf, 0xac, 0x48, 0xa3, 0xac, 0x05, 0xff, 0x45, 0x38, 0xd8, 0x49, 0x6c, + 0x14, 0x61, 0x83, 0x77, 0xe7, 0xb4, 0x6b, 0xf6, 0xbe, 0xdb, 0x55, 0xad, 0x8b, 0x16, 0x6c, 0x58, 0xb7, 0xa9, 0xdc, + 0x97, 0x12, 0xf2, 0xc6, 0x91, 0x58, 0x1a, 0xe1, 0x00, 0x41, 0x27, 0xf7, 0x27, 0x11, 0xf6, 0x3b, 0xee, 0xec, 0xfc, + 0xa2, 0x03, 0xa4, 0x4c, 0x03, 0xa1, 0x18, 0x77, 0x46, 0x67, 0x40, 0x9a, 0x34, 0x7b, 0x6d, 0xf1, 0x24, 0xc2, 0xfa, + 0xa9, 0xd2, 0xaf, 0xd2, 0x68, 0x7c, 0x31, 0x9a, 0x8c, 0x2f, 0x22, 0xac, 0xe5, 0x9c, 0x6a, 0x69, 0x28, 0xe0, 0xe9, + 0xd9, 0xfd, 0x08, 0x1b, 0x34, 0x6f, 0xb1, 0xd6, 0xb8, 0x15, 0x61, 0x77, 0x94, 0x30, 0x76, 0xd1, 0x81, 0x69, 0xfd, + 0xf4, 0x5c, 0x03, 0x2e, 0x8f, 0xd9, 0xe8, 0x34, 0xc2, 0x25, 0xbd, 0x37, 0x84, 0x08, 0xbe, 0xd4, 0x5c, 0x7e, 0x71, + 0xac, 0x07, 0x90, 0x3a, 0xbf, 0xe1, 0x61, 0x19, 0x5e, 0xde, 0x58, 0x34, 0xa2, 0x66, 0x8b, 0x07, 0xb7, 0xd1, 0x4f, + 0x68, 0xec, 0xd9, 0x76, 0x4e, 0x56, 0x1b, 0x5c, 0x06, 0x79, 0xfd, 0xc2, 0xee, 0x54, 0x2c, 0x95, 0xe1, 0x64, 0x83, + 0x14, 0xa5, 0x90, 0x77, 0x6b, 0x70, 0x9e, 0xab, 0x20, 0x48, 0x0a, 0xd2, 0xea, 0x89, 0x4b, 0xef, 0x4d, 0xdb, 0x13, + 0x10, 0xfa, 0x01, 0xd2, 0x0b, 0x42, 0x89, 0x86, 0x08, 0x39, 0x56, 0x98, 0xf4, 0x4e, 0x06, 0x46, 0xa6, 0x94, 0xd6, + 0x6d, 0x81, 0x12, 0xea, 0x63, 0xe3, 0xc7, 0x12, 0x2b, 0x88, 0x1e, 0x85, 0x7a, 0x92, 0x98, 0x48, 0xd7, 0x2f, 0x84, + 0x8e, 0xa5, 0x1a, 0x14, 0x43, 0xdc, 0x3e, 0x47, 0x18, 0x62, 0x48, 0x90, 0x81, 0xbc, 0xba, 0x6a, 0x9f, 0x1f, 0x19, + 0xa1, 0xef, 0xea, 0xea, 0xc2, 0xfe, 0x80, 0x7f, 0x87, 0x55, 0xdc, 0x6e, 0x18, 0xdf, 0x07, 0x56, 0xcd, 0xf1, 0x9d, + 0xe1, 0xaf, 0x3f, 0xb0, 0xf5, 0x3a, 0xfe, 0xc0, 0x08, 0xcc, 0x18, 0x7f, 0x60, 0x89, 0xb9, 0x23, 0xb1, 0x1e, 0x42, + 0x64, 0x00, 0x9a, 0xb3, 0x16, 0x86, 0x68, 0xf2, 0x9e, 0xf3, 0xfe, 0xc0, 0x06, 0xbc, 0xee, 0x5d, 0x5e, 0x85, 0x70, + 0x3e, 0x3a, 0x5a, 0x15, 0xa9, 0xb6, 0x62, 0x82, 0xb6, 0x62, 0x82, 0xb6, 0x62, 0x82, 0xae, 0x82, 0xe8, 0x9f, 0xf5, + 0x41, 0x4a, 0x31, 0xca, 0x16, 0xc7, 0x53, 0xbf, 0x04, 0xb5, 0x07, 0x68, 0x27, 0xfb, 0x95, 0xb2, 0xa3, 0xd4, 0x55, + 0xec, 0x55, 0x60, 0xec, 0x4d, 0x74, 0xda, 0x8e, 0x93, 0x7f, 0x47, 0xdd, 0xf1, 0xb6, 0x26, 0x96, 0xbd, 0xdc, 0x2b, + 0x96, 0xc1, 0x4a, 0x1a, 0xd1, 0xec, 0xd0, 0xc6, 0x23, 0xd1, 0x83, 0xfb, 0x46, 0x30, 0xab, 0x82, 0xe4, 0x35, 0x20, + 0xa9, 0x07, 0x52, 0xc8, 0x85, 0x91, 0xd2, 0x0a, 0x94, 0x8e, 0x75, 0x5c, 0x80, 0x86, 0xd2, 0x2b, 0x28, 0xcb, 0x58, + 0xae, 0x0d, 0x03, 0x10, 0x65, 0x65, 0x34, 0x2b, 0xab, 0x75, 0x41, 0x74, 0x01, 0x4d, 0x98, 0x91, 0x58, 0xa0, 0x01, + 0x61, 0x1a, 0x10, 0xae, 0x32, 0x88, 0x33, 0x2e, 0xfb, 0xcc, 0x64, 0x2b, 0x93, 0xad, 0xca, 0x6c, 0xe9, 0xb3, 0xad, + 0x90, 0x28, 0x4d, 0xb6, 0x2c, 0xb3, 0x41, 0x66, 0xc3, 0xd3, 0x54, 0xe1, 0x51, 0x2a, 0xad, 0xa8, 0x56, 0xc9, 0x56, + 0xcf, 0x68, 0xa8, 0xcd, 0x3d, 0x3a, 0x8a, 0x4b, 0x39, 0xc9, 0xa8, 0x89, 0xef, 0xad, 0x78, 0x52, 0x18, 0x19, 0x88, + 0x27, 0x53, 0xf7, 0x77, 0xb4, 0xd9, 0x96, 0x95, 0x8a, 0xe9, 0xe8, 0x1b, 0x25, 0xd1, 0x9f, 0x5e, 0x89, 0xfa, 0x81, + 0x9b, 0x28, 0x40, 0x97, 0x24, 0x69, 0xb5, 0x4e, 0xdb, 0xa7, 0xad, 0x8b, 0x3e, 0x3f, 0x6e, 0x77, 0x92, 0x07, 0x9d, + 0xd4, 0x28, 0x22, 0x16, 0xf2, 0x06, 0x14, 0x30, 0x27, 0x9d, 0xe4, 0x0c, 0x1d, 0xb7, 0x93, 0x56, 0xb7, 0xdb, 0x84, + 0x7f, 0xf0, 0x23, 0x5d, 0x56, 0x3b, 0x6b, 0x9d, 0x75, 0xfb, 0xfc, 0x64, 0xab, 0x52, 0xcc, 0x1b, 0x50, 0x10, 0x9d, + 0x98, 0x4a, 0x18, 0xea, 0x57, 0xcb, 0xfb, 0x6a, 0x47, 0xcf, 0xf3, 0x48, 0xc7, 0xd2, 0xaa, 0xe2, 0x00, 0xaa, 0xfe, + 0x6b, 0x6a, 0x80, 0xe8, 0xbf, 0x46, 0x65, 0xa4, 0xde, 0x55, 0x01, 0xa2, 0xf6, 0x07, 0x1e, 0x8b, 0x06, 0x3b, 0x8e, + 0x6d, 0xbe, 0x86, 0xba, 0x4d, 0x88, 0x9e, 0x87, 0xa7, 0x2e, 0x57, 0x85, 0xb9, 0x53, 0x84, 0x9a, 0x0a, 0x72, 0x47, + 0x2e, 0x57, 0x86, 0xb9, 0x23, 0x84, 0x9a, 0x12, 0x72, 0x69, 0xca, 0x13, 0x0a, 0x39, 0x3a, 0xa1, 0x4d, 0x03, 0xc9, + 0x6a, 0x51, 0x9e, 0x33, 0x3f, 0x6c, 0x3e, 0x81, 0xe5, 0x31, 0x04, 0xc5, 0x09, 0xd2, 0x02, 0x5e, 0x58, 0x29, 0xb5, + 0x39, 0x2d, 0x5c, 0xaa, 0x71, 0x20, 0xa3, 0x01, 0xff, 0x1c, 0x33, 0xf3, 0xec, 0x46, 0xab, 0x7f, 0x7a, 0xde, 0x4a, + 0xdb, 0xe0, 0x2a, 0x0e, 0xb2, 0xb6, 0xb0, 0xb2, 0xb6, 0xf0, 0xb2, 0xb6, 0xf0, 0xb2, 0x36, 0x08, 0xf0, 0x41, 0xdf, + 0xff, 0x94, 0x35, 0xf3, 0x1b, 0x5e, 0xda, 0xf2, 0x58, 0x63, 0x8d, 0x58, 0xaf, 0xd7, 0xab, 0x0d, 0x58, 0x5a, 0x95, + 0x35, 0x0a, 0x55, 0xa9, 0x3f, 0x57, 0x45, 0xda, 0xc2, 0xd3, 0x14, 0xb4, 0xdc, 0x2d, 0x4c, 0xcd, 0xe6, 0xf6, 0x54, + 0x61, 0x3b, 0x8a, 0x4f, 0xdf, 0xab, 0x93, 0xaf, 0xc8, 0xa9, 0xd1, 0x1e, 0xaf, 0x8a, 0x94, 0x5b, 0x9a, 0xc1, 0x2d, + 0xcd, 0xe0, 0x96, 0x66, 0x40, 0x23, 0xb8, 0x2c, 0x6c, 0xca, 0x26, 0x94, 0xc0, 0x95, 0xc0, 0xe0, 0x74, 0x08, 0x41, + 0x0c, 0x63, 0x4d, 0xcc, 0xa8, 0xb7, 0x3a, 0x6f, 0x43, 0xd0, 0x36, 0x5b, 0x52, 0x27, 0xd4, 0xf8, 0xae, 0x97, 0x63, + 0xfe, 0xbb, 0x86, 0xf6, 0x09, 0xbc, 0xa8, 0xf3, 0x50, 0xc7, 0x2d, 0x30, 0x5d, 0x89, 0x8a, 0xa8, 0x6f, 0xc8, 0x42, + 0x6a, 0x74, 0x36, 0xce, 0x24, 0xfd, 0xcb, 0x96, 0x27, 0xb0, 0xa5, 0x04, 0xe1, 0x3b, 0x12, 0x5f, 0x58, 0x15, 0x9a, + 0xa0, 0xb4, 0xb8, 0x75, 0xe6, 0x72, 0xf6, 0x48, 0xe8, 0x81, 0xd9, 0xbc, 0x8f, 0x79, 0xd5, 0x17, 0xa4, 0x80, 0x98, + 0x8f, 0xa9, 0x49, 0x74, 0x51, 0x9b, 0xc1, 0x89, 0x99, 0x7c, 0xa5, 0xc6, 0xa5, 0xe7, 0x9d, 0xfd, 0xf3, 0x37, 0x0d, + 0x7c, 0x1e, 0x8b, 0xe9, 0xc8, 0xbb, 0x0a, 0x7f, 0x32, 0xb1, 0x8d, 0xc8, 0xe1, 0xa1, 0xb5, 0x68, 0x37, 0x5f, 0xdb, + 0x26, 0xed, 0x26, 0xd1, 0x64, 0xc3, 0x0e, 0xf5, 0x6b, 0xf4, 0x4f, 0xef, 0xb1, 0x57, 0x4c, 0x47, 0x28, 0xa0, 0xd9, + 0x06, 0xac, 0xb2, 0x02, 0x96, 0x72, 0xf5, 0x4a, 0x47, 0x4e, 0xe8, 0xdd, 0x8c, 0x79, 0x53, 0x4c, 0x47, 0x7b, 0x9f, + 0x5e, 0xb1, 0x3d, 0xf6, 0x9f, 0xd1, 0xa0, 0x07, 0xaf, 0xda, 0x9e, 0xb1, 0xdb, 0xef, 0xd5, 0xf9, 0xb2, 0xb7, 0x8e, + 0xca, 0xbf, 0x57, 0xe7, 0xc5, 0xbe, 0x3a, 0x73, 0x7e, 0x1b, 0xfb, 0xbd, 0xa3, 0x03, 0x35, 0xb6, 0x31, 0x93, 0x9a, + 0x8e, 0x20, 0x56, 0x3e, 0xfc, 0xb5, 0x11, 0x6d, 0x7a, 0x9e, 0x84, 0xc3, 0x2a, 0xc8, 0x7e, 0xd2, 0x4d, 0x19, 0xa6, + 0xa4, 0x73, 0x5c, 0x98, 0x98, 0x36, 0x22, 0xa1, 0x4d, 0x95, 0x50, 0x9c, 0x93, 0x38, 0xa6, 0xc7, 0x19, 0x44, 0xe6, + 0x69, 0xf7, 0x69, 0x1a, 0xd3, 0x46, 0x86, 0x4e, 0xe2, 0x76, 0x83, 0x1e, 0x67, 0x08, 0x35, 0xda, 0xa0, 0x33, 0x95, + 0xa4, 0xdd, 0xcc, 0x21, 0x56, 0xa7, 0x21, 0xc5, 0xf9, 0xb1, 0x48, 0x8a, 0x86, 0x3c, 0x56, 0x49, 0xd1, 0x48, 0xba, + 0x58, 0x24, 0xd3, 0x32, 0x79, 0x6a, 0x92, 0xa7, 0x36, 0x79, 0x54, 0x26, 0x8f, 0x4c, 0xf2, 0xc8, 0x26, 0x53, 0x52, + 0x1c, 0x8b, 0x84, 0x36, 0xe2, 0x76, 0xb3, 0x40, 0xc7, 0x30, 0x02, 0x3f, 0x7a, 0x22, 0xc2, 0x10, 0xe9, 0x1b, 0x63, + 0x63, 0xb4, 0x90, 0xb9, 0x0b, 0x5a, 0x5a, 0x01, 0xa9, 0x74, 0xfc, 0x82, 0x3a, 0xaf, 0x02, 0x30, 0x61, 0x6d, 0xff, + 0xf8, 0x90, 0x7c, 0x9b, 0x2c, 0x97, 0x22, 0x70, 0x6c, 0x03, 0x5b, 0xfc, 0x2f, 0xce, 0x9d, 0x07, 0xa0, 0xba, 0xa1, + 0xf9, 0x62, 0x46, 0x77, 0xbc, 0x87, 0x8b, 0xe9, 0xc8, 0xed, 0xac, 0xb2, 0x19, 0x46, 0x0b, 0x1b, 0xea, 0xba, 0xee, + 0xe7, 0x09, 0xa0, 0xf6, 0xbe, 0xa5, 0x09, 0x35, 0x4a, 0x72, 0x5b, 0x63, 0x5a, 0xb0, 0x3b, 0x95, 0xd1, 0x9c, 0xc5, + 0xd5, 0x01, 0x5c, 0x0d, 0x93, 0x91, 0x27, 0xe0, 0x11, 0x50, 0x1c, 0x27, 0xa7, 0x0d, 0x9d, 0x4c, 0x8f, 0x93, 0xee, + 0x83, 0x86, 0x4e, 0x46, 0xc7, 0x49, 0xbb, 0x5d, 0xe1, 0x6c, 0x52, 0x10, 0x9d, 0x4c, 0x89, 0x06, 0x8d, 0xa1, 0x6d, + 0x54, 0x2e, 0x28, 0x98, 0xb8, 0xfd, 0x1b, 0xc3, 0x68, 0xb8, 0x61, 0x08, 0x36, 0xb5, 0x51, 0x3f, 0x77, 0xc6, 0x10, + 0x76, 0xd3, 0xe9, 0x76, 0x9b, 0x3a, 0x29, 0xb0, 0xb6, 0x2b, 0xd9, 0xd4, 0xc9, 0x14, 0x6b, 0xbb, 0x7c, 0x4d, 0x9d, + 0x8c, 0x6c, 0x53, 0x46, 0x07, 0xc8, 0x44, 0x00, 0xac, 0xe7, 0x2c, 0x80, 0x7c, 0xc7, 0x3b, 0xe9, 0x6c, 0x40, 0x6b, + 0xf8, 0xbd, 0x72, 0x4d, 0x5f, 0x50, 0x51, 0x0d, 0xa6, 0x4e, 0xec, 0x5b, 0x45, 0xdb, 0x55, 0x93, 0xec, 0x5f, 0x97, + 0x2d, 0x9b, 0x2d, 0xa4, 0xae, 0x17, 0x7c, 0x5a, 0xc3, 0x10, 0x57, 0xca, 0x1d, 0xdc, 0x9f, 0x29, 0x89, 0x21, 0xb6, + 0x9f, 0x39, 0x85, 0x38, 0xf1, 0x7a, 0x64, 0x48, 0xe2, 0x8d, 0xc6, 0x06, 0xc5, 0xc1, 0x79, 0xfb, 0x34, 0xa4, 0xaa, + 0x3b, 0x01, 0xff, 0x08, 0x89, 0x96, 0xc2, 0x9a, 0x84, 0x8e, 0xa3, 0x8a, 0x16, 0xbf, 0x75, 0xda, 0xdd, 0xda, 0x01, + 0x71, 0x74, 0xb4, 0x7d, 0x5e, 0xf8, 0xa7, 0x17, 0x76, 0x9e, 0x5b, 0xa8, 0xec, 0x09, 0xfd, 0x83, 0x50, 0xd6, 0xd2, + 0x98, 0x07, 0x88, 0xe2, 0x43, 0x6f, 0xdd, 0x37, 0x14, 0x7e, 0x50, 0xc5, 0x1d, 0x74, 0x39, 0xcd, 0x73, 0x93, 0x61, + 0xfa, 0x1a, 0x06, 0x63, 0x7b, 0x13, 0x4e, 0xa8, 0xb4, 0x95, 0xfc, 0x97, 0x1d, 0x07, 0x9d, 0xb8, 0x07, 0x6b, 0xc2, + 0x46, 0x3f, 0x87, 0x96, 0xc9, 0x15, 0x6c, 0x9c, 0x4f, 0xfa, 0x7a, 0x5d, 0x7b, 0x9e, 0xc8, 0x3e, 0x82, 0x83, 0x8e, + 0x8e, 0xb8, 0x7a, 0x06, 0xc6, 0xd4, 0x2c, 0x6e, 0x84, 0x87, 0xef, 0x5f, 0xb5, 0xd3, 0xfa, 0xb3, 0x39, 0x57, 0xd3, + 0xe0, 0xa0, 0x7b, 0x58, 0xcb, 0xdf, 0xbb, 0x12, 0x7d, 0x9d, 0x72, 0xb7, 0xd6, 0x8f, 0x2a, 0x53, 0xf5, 0x9d, 0x87, + 0xb2, 0x8e, 0x8e, 0x78, 0x15, 0xae, 0x2a, 0xfa, 0x21, 0x42, 0x7d, 0x23, 0x83, 0x3c, 0xcb, 0x25, 0x85, 0x1b, 0x51, + 0xb8, 0x62, 0x48, 0x1b, 0xfc, 0x44, 0xe3, 0x9f, 0xe5, 0xff, 0xa7, 0x46, 0x8e, 0x75, 0xda, 0xe0, 0x15, 0x4a, 0xbd, + 0x08, 0x59, 0xa1, 0x2a, 0x50, 0xa4, 0x81, 0x74, 0x68, 0x79, 0x8e, 0xca, 0xc3, 0x9c, 0x2e, 0x16, 0xf9, 0x9d, 0x79, 0x2b, 0x2c, 0xe0, 0xa8, 0xaa, 0x8b, 0x26, 0x17, 0xa5, 0x0f, 0x17, 0xc0, 0xd3, 0x03, 0xee, 0x21, 0xe3, 0x65, 0x5b, - 0x5e, 0x6e, 0x0b, 0x04, 0x92, 0x99, 0x22, 0xb2, 0xd9, 0xee, 0xaa, 0x4b, 0x90, 0xcb, 0x9a, 0x4d, 0xa4, 0x5d, 0xf0, - 0x72, 0xcc, 0x41, 0x26, 0x53, 0xd6, 0x93, 0x76, 0xcf, 0x16, 0x04, 0xc9, 0x4d, 0x1a, 0x91, 0x6d, 0x77, 0x29, 0x3e, - 0x8e, 0x01, 0x8d, 0x90, 0x15, 0xf8, 0x42, 0x61, 0x91, 0x03, 0xd7, 0x59, 0xf8, 0x8e, 0xbf, 0xd1, 0x52, 0xd1, 0x57, - 0x83, 0x01, 0x2e, 0xcc, 0xf3, 0x18, 0xe5, 0x7c, 0x0a, 0x15, 0x3c, 0xb7, 0x14, 0x88, 0x28, 0x7c, 0xb5, 0xda, 0x87, - 0xd7, 0x8c, 0x5c, 0x9b, 0xe0, 0x7a, 0xeb, 0x7e, 0x56, 0x2f, 0x97, 0xc0, 0x38, 0x18, 0x69, 0x99, 0x8b, 0x42, 0x27, - 0x6f, 0xb2, 0x0b, 0xd1, 0x6d, 0x34, 0x98, 0x09, 0x34, 0x45, 0x20, 0xaa, 0x1c, 0xf8, 0x45, 0xc2, 0x1f, 0x1b, 0x3b, - 0x4a, 0x31, 0x1b, 0x81, 0x0f, 0x42, 0x83, 0xd7, 0x12, 0x56, 0x2b, 0x65, 0x23, 0xbc, 0x98, 0x1c, 0x1b, 0xeb, 0xa5, - 0xec, 0xa7, 0x0c, 0x25, 0x5b, 0x99, 0x71, 0x70, 0xb7, 0xd5, 0xdf, 0x56, 0xfb, 0x79, 0x8f, 0xdb, 0x6b, 0x3c, 0x6e, - 0xe2, 0x26, 0x18, 0x40, 0x2d, 0x37, 0x36, 0xb8, 0xb5, 0xf3, 0x8f, 0xad, 0x51, 0x32, 0xdb, 0x84, 0xa0, 0x28, 0xe3, - 0x04, 0xd8, 0x9b, 0x5b, 0x1f, 0x37, 0x51, 0x99, 0x39, 0x29, 0xa4, 0xfb, 0x20, 0x47, 0x0f, 0x08, 0x74, 0x6e, 0x7f, - 0x56, 0x74, 0xa1, 0x92, 0x89, 0xcb, 0x31, 0xfe, 0x12, 0xdc, 0xe6, 0xf5, 0xa3, 0xeb, 0x6b, 0xb3, 0xc9, 0xaf, 0xaf, - 0x23, 0x1c, 0x1a, 0xd7, 0x47, 0x01, 0x2f, 0x18, 0x0d, 0xca, 0xd0, 0x5a, 0x66, 0xe3, 0x37, 0xdb, 0x55, 0x63, 0x8f, - 0x68, 0x85, 0x77, 0xb0, 0x3c, 0xa6, 0xf1, 0x2d, 0x67, 0xd4, 0x3e, 0x07, 0x78, 0xb3, 0x3e, 0x1f, 0x74, 0xdf, 0xc4, - 0x0a, 0x1d, 0x1c, 0xbc, 0x89, 0x25, 0xea, 0x5d, 0x31, 0x73, 0xe7, 0x06, 0xde, 0xe8, 0x7d, 0x6e, 0x86, 0x2f, 0x03, - 0x04, 0xb8, 0x62, 0x9b, 0x92, 0xcd, 0x5b, 0x13, 0xfb, 0x23, 0x85, 0xd8, 0xe2, 0x10, 0xe1, 0xd8, 0x81, 0x04, 0x7a, - 0x7d, 0x13, 0x42, 0xbb, 0xcb, 0x08, 0x03, 0x16, 0xbe, 0xf4, 0x15, 0x64, 0xc9, 0x8c, 0x15, 0x13, 0x56, 0xac, 0x56, - 0x8f, 0xa8, 0xf5, 0xff, 0xdb, 0x08, 0x55, 0xa9, 0xba, 0x8d, 0x06, 0x35, 0xe3, 0x07, 0xf1, 0x81, 0x0e, 0xf0, 0xfe, - 0x9b, 0xb8, 0x40, 0x08, 0x2c, 0x8c, 0xb8, 0x58, 0x78, 0x5f, 0xb7, 0xac, 0xb6, 0x2e, 0x05, 0x2a, 0x1b, 0xc9, 0x49, - 0x0b, 0x4f, 0x49, 0x56, 0xae, 0xd1, 0xc5, 0xb4, 0xdb, 0x68, 0xe4, 0x48, 0xc6, 0x59, 0x3f, 0x1f, 0x60, 0x8e, 0x0b, - 0xb8, 0x4c, 0xdd, 0x5e, 0x87, 0x39, 0xab, 0x51, 0x2e, 0x37, 0xdf, 0xa5, 0x1d, 0x6b, 0xfa, 0x9e, 0xae, 0x03, 0x60, - 0xbc, 0xa7, 0x01, 0x91, 0xd8, 0x05, 0x64, 0x61, 0x81, 0xac, 0x3c, 0x90, 0x85, 0x01, 0xb2, 0x42, 0xbd, 0x39, 0x04, - 0x6d, 0x52, 0x28, 0xdd, 0xa2, 0xe8, 0xf5, 0xf0, 0xa2, 0xce, 0x75, 0x05, 0x73, 0x13, 0xe1, 0xc2, 0x2d, 0x07, 0xb8, - 0xb1, 0x38, 0x6f, 0x48, 0x45, 0x96, 0x51, 0x64, 0x22, 0xed, 0xe2, 0x5b, 0xf3, 0x27, 0xb9, 0xc5, 0x77, 0xf6, 0xc7, - 0x5d, 0xa0, 0x4c, 0x7a, 0x5e, 0xd3, 0x36, 0x70, 0x17, 0x97, 0x2e, 0x4a, 0x22, 0x40, 0x6b, 0x17, 0x64, 0x51, 0xd4, - 0xdf, 0x9d, 0x53, 0x36, 0x1c, 0x86, 0x68, 0x10, 0x85, 0x45, 0x40, 0x3a, 0xff, 0xfa, 0x2b, 0x42, 0x3d, 0x01, 0xd1, - 0x8c, 0xdc, 0xc9, 0xd6, 0x6c, 0xa3, 0x46, 0x94, 0x44, 0x69, 0xec, 0x83, 0x65, 0xc0, 0xce, 0x88, 0xa2, 0xe0, 0xcd, - 0x99, 0x72, 0x18, 0x1f, 0x6a, 0xc3, 0x30, 0x83, 0xaa, 0xc2, 0x7f, 0x5c, 0x2e, 0x37, 0x83, 0x2d, 0x19, 0xa8, 0x0a, - 0x13, 0xe9, 0x06, 0xd9, 0x87, 0xd8, 0x18, 0x61, 0x07, 0x07, 0xac, 0x2f, 0x06, 0xc1, 0xcb, 0x6a, 0xd5, 0x75, 0xb8, - 0x0e, 0x17, 0x2e, 0xa6, 0x10, 0xed, 0x7e, 0xb5, 0xb2, 0x7f, 0xc9, 0x07, 0x23, 0xcd, 0xc0, 0x13, 0x79, 0xc1, 0x19, - 0x2b, 0x76, 0xcb, 0x62, 0x89, 0x96, 0xbf, 0x83, 0x65, 0x9f, 0x8b, 0x5d, 0xc8, 0xdd, 0x54, 0xdb, 0x1e, 0xea, 0x73, - 0xa3, 0x51, 0x08, 0x22, 0x07, 0x57, 0x47, 0x1a, 0x9e, 0xeb, 0x30, 0xaf, 0x16, 0x01, 0x38, 0x53, 0x65, 0x20, 0x57, - 0x38, 0x52, 0x12, 0xb0, 0xf4, 0x36, 0x74, 0x12, 0x7e, 0xd4, 0xa9, 0xa4, 0x63, 0x21, 0x01, 0x0a, 0x1c, 0x99, 0xcb, - 0x79, 0x13, 0xa8, 0x9f, 0xa1, 0x1d, 0x44, 0x2e, 0x30, 0xa1, 0xa9, 0xcb, 0x96, 0x2e, 0xa2, 0x56, 0x34, 0x93, 0x0b, - 0xc5, 0x16, 0x73, 0x38, 0xdf, 0xcb, 0xb4, 0x2c, 0xe7, 0xd9, 0x97, 0x7a, 0x0a, 0x18, 0x44, 0xde, 0xea, 0x19, 0x13, - 0x8b, 0xc8, 0xcd, 0xf3, 0x95, 0x15, 0xf7, 0xdf, 0xbc, 0xc0, 0xef, 0x49, 0xe7, 0xf0, 0x15, 0xfe, 0x48, 0xc9, 0xfb, - 0xc6, 0x2b, 0x3c, 0xe1, 0xc4, 0xf2, 0x06, 0xc9, 0x9b, 0xd7, 0x57, 0x2f, 0xde, 0xbd, 0x78, 0xff, 0xf4, 0xfa, 0xc5, - 0xab, 0x67, 0x2f, 0x5e, 0xbd, 0x78, 0xf7, 0x11, 0xff, 0x4d, 0xc9, 0xab, 0xa3, 0xf6, 0x79, 0x0b, 0x7f, 0x20, 0xaf, - 0x8e, 0x3a, 0xf8, 0x56, 0x93, 0x57, 0x47, 0x27, 0x38, 0x57, 0xe4, 0xd5, 0x61, 0xe7, 0xe8, 0x18, 0x2f, 0xb4, 0x6d, - 0x32, 0x97, 0x93, 0x76, 0x0b, 0xff, 0xed, 0xbe, 0x40, 0xbc, 0xaf, 0x66, 0x31, 0x61, 0x1b, 0xc6, 0x0f, 0xa6, 0x0c, - 0x1d, 0x2a, 0x63, 0x88, 0x72, 0x11, 0xa0, 0xd3, 0x54, 0x85, 0xe8, 0x64, 0x43, 0x49, 0x83, 0x0d, 0x23, 0xa0, 0x15, - 0x27, 0xae, 0x1d, 0x7e, 0xd4, 0x66, 0xc7, 0x40, 0x9f, 0x78, 0x29, 0x1c, 0x97, 0x2a, 0x9c, 0xb6, 0xd3, 0x62, 0x8c, - 0x73, 0x29, 0x8b, 0x78, 0x01, 0x8c, 0x80, 0xd1, 0x5a, 0xf0, 0xa3, 0x32, 0x66, 0x95, 0xb8, 0x20, 0xed, 0x5e, 0x3b, - 0x15, 0x17, 0xa4, 0xd3, 0xeb, 0xc0, 0x9f, 0xd3, 0xde, 0x69, 0xda, 0x6e, 0xa1, 0xc3, 0x60, 0x1c, 0x7f, 0xd4, 0xd0, - 0xba, 0x3f, 0xc0, 0xae, 0x0b, 0xf5, 0x77, 0xa1, 0xbd, 0x4a, 0x4f, 0x38, 0x75, 0x6c, 0xbb, 0x2b, 0x2e, 0x98, 0xd1, - 0xc3, 0xf2, 0x1f, 0x00, 0xb5, 0x8d, 0x5b, 0x4d, 0xb9, 0x71, 0xdc, 0x2f, 0x7e, 0x24, 0x50, 0x2d, 0x30, 0x4e, 0xcc, - 0x56, 0x2d, 0x04, 0x4c, 0xa3, 0xc9, 0x06, 0x73, 0xa0, 0x44, 0xc9, 0x42, 0xfb, 0xe0, 0xfe, 0xaa, 0x29, 0x51, 0x32, - 0x97, 0xf3, 0xb8, 0xa6, 0x6a, 0xf8, 0x35, 0x30, 0x73, 0xdc, 0xe7, 0xea, 0x15, 0x7d, 0x15, 0xd7, 0x78, 0x9e, 0x90, - 0xb5, 0x0b, 0xb7, 0xc5, 0x2f, 0xce, 0x8a, 0xa2, 0x06, 0xae, 0x12, 0xb0, 0x7e, 0x54, 0x4d, 0x7d, 0x01, 0xaf, 0x18, - 0xb2, 0x86, 0xbe, 0x24, 0x01, 0xf5, 0xfc, 0xa9, 0x34, 0xe3, 0x2a, 0x95, 0xd1, 0x5e, 0x11, 0x6d, 0xcc, 0x82, 0xbc, - 0x22, 0xfa, 0x42, 0x19, 0x20, 0x48, 0xc2, 0xfb, 0x62, 0x00, 0x07, 0xbe, 0x1d, 0xa0, 0x34, 0x74, 0x0e, 0xd4, 0x4a, - 0x95, 0x99, 0x90, 0xf9, 0x34, 0x21, 0x1a, 0x40, 0xf3, 0x54, 0xa9, 0xa0, 0xcc, 0x27, 0x96, 0x28, 0x18, 0xfa, 0x9f, - 0xe1, 0x06, 0x38, 0x8c, 0x0d, 0x2a, 0x06, 0xd9, 0xf7, 0x44, 0x3d, 0xbf, 0x7d, 0xde, 0x3a, 0x7a, 0x15, 0xe4, 0x8f, - 0x94, 0xb7, 0xf7, 0xf8, 0x1c, 0x50, 0x72, 0x1b, 0x54, 0xac, 0x8d, 0x7d, 0x3c, 0xb8, 0x6e, 0x08, 0x90, 0x43, 0x8d, - 0x8e, 0xcc, 0x83, 0x8e, 0x5d, 0xa4, 0x0f, 0x49, 0xbb, 0x05, 0x41, 0xdc, 0x76, 0x50, 0xbe, 0x9f, 0x36, 0x60, 0xaa, - 0x93, 0xdb, 0x26, 0xd0, 0x6a, 0x78, 0xe3, 0xe9, 0xae, 0xc9, 0x93, 0x3b, 0xac, 0x02, 0x9c, 0x61, 0x87, 0xac, 0x21, - 0x0e, 0x05, 0x72, 0xc1, 0x6f, 0xed, 0x06, 0xd0, 0x54, 0x74, 0xec, 0x5b, 0x83, 0xde, 0x38, 0xea, 0xa2, 0x99, 0x9c, - 0x1e, 0xbe, 0x3a, 0x38, 0x88, 0x65, 0x83, 0xbc, 0x47, 0x78, 0x49, 0xc1, 0x66, 0x1b, 0x7c, 0xef, 0xb8, 0x65, 0xe2, - 0x53, 0x15, 0x50, 0xc7, 0x85, 0xaa, 0x1d, 0x6b, 0x55, 0x67, 0xe5, 0x6e, 0xf0, 0x63, 0xea, 0xa0, 0x46, 0x90, 0x66, - 0x47, 0xd7, 0x09, 0xa1, 0xfc, 0x5b, 0xcd, 0x51, 0x0e, 0xb6, 0x65, 0xe3, 0x23, 0x45, 0x3f, 0xbc, 0x6f, 0xbe, 0x0a, - 0xca, 0xd4, 0x4c, 0x93, 0xde, 0x37, 0xde, 0xa3, 0x1f, 0xde, 0x07, 0xae, 0x8e, 0xbc, 0x62, 0x4f, 0x3c, 0x37, 0xf2, - 0x9b, 0xe5, 0x4a, 0x7f, 0x03, 0xc9, 0xbe, 0x20, 0xbf, 0x01, 0x96, 0x53, 0xf2, 0x5b, 0x2c, 0x9b, 0x10, 0x02, 0x92, - 0xfc, 0x16, 0x17, 0xf0, 0x23, 0x27, 0xbf, 0xc5, 0x80, 0xed, 0x78, 0x6a, 0x7e, 0x14, 0x25, 0x30, 0xc0, 0xbd, 0x4e, - 0x5a, 0x2f, 0xbb, 0x62, 0xb5, 0x12, 0x07, 0x07, 0xd2, 0xfe, 0xa2, 0x97, 0xd9, 0xc1, 0x41, 0x7e, 0x31, 0x0d, 0x6c, - 0x6f, 0xf5, 0x2e, 0xfa, 0x62, 0x10, 0x0a, 0x07, 0xa6, 0x69, 0xbc, 0x86, 0x57, 0x35, 0xca, 0x0a, 0x0d, 0x34, 0x8f, - 0x3b, 0xf7, 0xcf, 0xce, 0x31, 0xfc, 0x7b, 0x3f, 0x28, 0xf8, 0x73, 0xc9, 0x77, 0x91, 0x36, 0x6b, 0x9e, 0x55, 0x75, - 0x2e, 0x03, 0x7c, 0xc6, 0x0c, 0x35, 0xc5, 0xc1, 0x01, 0xbf, 0x08, 0x70, 0x19, 0x33, 0xd4, 0x08, 0x2c, 0xf6, 0x1e, - 0x96, 0xf6, 0x64, 0x86, 0x6b, 0x82, 0xc7, 0x7d, 0x79, 0xbf, 0x18, 0x5c, 0x68, 0x47, 0x4d, 0xc2, 0x10, 0xe0, 0x8a, - 0xb4, 0xdc, 0x26, 0xeb, 0x8a, 0xa6, 0xba, 0x6c, 0x77, 0x91, 0x24, 0xaa, 0x21, 0x2e, 0x2f, 0xdb, 0x18, 0x54, 0xf2, - 0x3d, 0x45, 0x64, 0x2a, 0x88, 0x77, 0x53, 0x5c, 0xe6, 0x32, 0x55, 0x78, 0xca, 0x53, 0xe1, 0xe5, 0xec, 0xd7, 0xde, - 0x7a, 0xda, 0x38, 0x8e, 0x9a, 0x9e, 0x19, 0x16, 0x3d, 0x55, 0x3a, 0x3c, 0xc2, 0x26, 0x55, 0x03, 0x78, 0x3b, 0xb1, - 0xc4, 0x3c, 0x66, 0xbd, 0xfc, 0x18, 0xc4, 0xa6, 0x56, 0x8d, 0x36, 0x64, 0xc2, 0xe7, 0x3a, 0x55, 0x30, 0x50, 0x53, - 0xf8, 0x02, 0xc8, 0x54, 0x56, 0x19, 0x66, 0xfb, 0x86, 0xa1, 0x80, 0x80, 0x02, 0x97, 0x84, 0x05, 0x12, 0x3c, 0xdc, - 0x7e, 0x04, 0x84, 0xa3, 0x4e, 0x2e, 0xec, 0xe4, 0x2e, 0x14, 0x74, 0x27, 0x06, 0x17, 0xba, 0x8b, 0x44, 0xa3, 0xe1, - 0xb8, 0xed, 0x4b, 0x61, 0x06, 0xd1, 0x6c, 0x0f, 0x2e, 0x59, 0x17, 0xa9, 0x66, 0xb3, 0x34, 0x80, 0xbc, 0x6c, 0xad, - 0x56, 0xea, 0xc2, 0x37, 0xd2, 0xf3, 0xe7, 0xb8, 0xe1, 0xbb, 0xbc, 0xe0, 0xf9, 0x9b, 0x24, 0xfd, 0x08, 0xa8, 0x2a, - 0xf0, 0xd9, 0x72, 0x1e, 0xe1, 0xc8, 0x3c, 0xab, 0x07, 0x7f, 0xcd, 0x73, 0x68, 0x11, 0x8e, 0xdc, 0x4b, 0x7b, 0xd1, - 0xa0, 0x1a, 0x2c, 0xcf, 0xca, 0x20, 0xf1, 0x3c, 0xb9, 0x06, 0xc6, 0x41, 0x7f, 0x56, 0x68, 0x59, 0xfd, 0x4e, 0x72, - 0x17, 0x2e, 0x45, 0xf9, 0xc7, 0xdf, 0xdc, 0xa8, 0xd6, 0xbb, 0x1d, 0x54, 0x39, 0x8e, 0x7c, 0x55, 0x78, 0x44, 0xe1, - 0x3b, 0xaf, 0x4f, 0xb6, 0xdd, 0xa3, 0xe7, 0xcb, 0xb2, 0x07, 0xe0, 0xbc, 0xd7, 0x6b, 0x84, 0x7f, 0x93, 0x3b, 0x5f, - 0x40, 0x8e, 0xae, 0xa5, 0x78, 0x42, 0x35, 0x8d, 0x1a, 0x6f, 0x8c, 0xe1, 0x9b, 0x95, 0xb3, 0xba, 0xdf, 0x1a, 0x07, - 0xfb, 0xb7, 0xba, 0x87, 0x00, 0x16, 0xb5, 0xc7, 0x9a, 0xac, 0xec, 0x6b, 0xc2, 0x96, 0xc8, 0xc0, 0xf4, 0x6d, 0x07, - 0x3c, 0xfc, 0x18, 0x29, 0xb8, 0x55, 0x5b, 0x3e, 0x89, 0x42, 0x64, 0xd8, 0x9a, 0x33, 0x37, 0xa4, 0xd8, 0x3e, 0x8c, - 0xe3, 0xef, 0x1a, 0x85, 0x5c, 0xf7, 0x58, 0xd5, 0x89, 0x69, 0xd5, 0x8d, 0x91, 0x3a, 0xd8, 0x26, 0x0b, 0xce, 0xaa, - 0xde, 0x8d, 0x84, 0x52, 0xbd, 0x6b, 0x67, 0xde, 0x26, 0x6d, 0xb6, 0xcd, 0x63, 0xcf, 0xf6, 0xf5, 0x3b, 0x05, 0x86, - 0xbc, 0xfb, 0x65, 0xd0, 0xae, 0x4b, 0x38, 0x76, 0xe3, 0x00, 0xb2, 0x92, 0x5c, 0x2e, 0xdd, 0xcb, 0x74, 0xbc, 0x2f, - 0x07, 0xeb, 0xf2, 0x9d, 0xba, 0x00, 0x0f, 0xaa, 0x91, 0x8a, 0x2c, 0xe4, 0x0c, 0xfc, 0x23, 0x8f, 0x35, 0xfd, 0x10, - 0xff, 0x07, 0x0e, 0xf8, 0x0a, 0x49, 0x53, 0xab, 0x7e, 0x82, 0xf7, 0xa3, 0x40, 0xe1, 0x6d, 0xeb, 0xfe, 0x29, 0x43, - 0x47, 0xdd, 0xba, 0x4e, 0xc5, 0xfa, 0xc2, 0xd6, 0x15, 0x2b, 0x65, 0xe1, 0x80, 0x6a, 0xc5, 0x68, 0x9d, 0x3a, 0xbf, - 0x59, 0xf7, 0xe8, 0xd4, 0x43, 0x01, 0xbe, 0x31, 0x5c, 0x8a, 0x67, 0x05, 0x44, 0x11, 0x0b, 0xf5, 0x69, 0x3f, 0xcb, - 0xf0, 0x55, 0xe5, 0x3e, 0xdc, 0x13, 0x96, 0x3c, 0x67, 0xf9, 0x12, 0x38, 0x2c, 0x90, 0x02, 0x0a, 0xa5, 0xb0, 0x58, - 0xad, 0x62, 0x01, 0x81, 0x24, 0xfe, 0x74, 0xa1, 0x85, 0xdd, 0x1b, 0x22, 0x46, 0x7f, 0x07, 0x75, 0xb1, 0x57, 0x8f, - 0x18, 0x13, 0x56, 0x14, 0x5e, 0x3a, 0xa9, 0x2c, 0xe8, 0x6b, 0x57, 0x1f, 0xa2, 0x9a, 0x72, 0x2f, 0x36, 0xfa, 0xde, - 0x77, 0x7c, 0xc6, 0xe4, 0x02, 0x1e, 0x6f, 0xc2, 0x8c, 0x28, 0xa6, 0xfd, 0x37, 0x50, 0x10, 0x78, 0x01, 0x88, 0x87, - 0xf8, 0x08, 0x7c, 0x95, 0xa7, 0x75, 0x32, 0xf3, 0x4f, 0x82, 0x44, 0x26, 0x64, 0x67, 0xd4, 0x8b, 0xc0, 0x8b, 0x08, - 0x44, 0x28, 0x42, 0x22, 0x26, 0x46, 0x51, 0x2f, 0x32, 0x2e, 0x59, 0x11, 0x58, 0x8d, 0x81, 0x92, 0x3b, 0xc2, 0x73, - 0x55, 0x11, 0xb1, 0xb0, 0xa6, 0x0e, 0x2a, 0xb1, 0xd4, 0x98, 0x69, 0x1f, 0x75, 0x2a, 0x10, 0x16, 0xd9, 0xa6, 0xa0, - 0xac, 0x37, 0xd4, 0x05, 0x58, 0x12, 0x63, 0x7a, 0xcb, 0x93, 0x6b, 0xe0, 0xe6, 0xd8, 0xc8, 0x15, 0x5d, 0xf2, 0x2b, - 0x50, 0x4f, 0xa7, 0x05, 0xbe, 0x36, 0x0c, 0xdb, 0x28, 0xa5, 0x6b, 0xc2, 0x71, 0x46, 0x8a, 0x84, 0xde, 0x42, 0x6c, - 0x8d, 0x19, 0x17, 0x69, 0x8e, 0x67, 0xf4, 0x36, 0x9d, 0xe2, 0x19, 0x17, 0x4f, 0xec, 0xb2, 0xa7, 0x23, 0x48, 0xf2, - 0x1f, 0x8b, 0x35, 0x31, 0x4f, 0x83, 0xfd, 0xae, 0x58, 0xf1, 0x08, 0x78, 0x15, 0x15, 0xa3, 0xee, 0xc8, 0xd8, 0x94, - 0x73, 0x5d, 0x19, 0xaf, 0xbf, 0xd6, 0x31, 0xc5, 0x19, 0xce, 0x51, 0x92, 0x4b, 0xcc, 0x7a, 0x22, 0x7d, 0x0d, 0x71, - 0xb5, 0x33, 0x6c, 0x9f, 0x15, 0xe3, 0xb7, 0x2c, 0x7f, 0x26, 0x8b, 0xf7, 0x66, 0xcb, 0xe7, 0x08, 0x0a, 0x81, 0x8b, - 0x8a, 0x68, 0xc2, 0xed, 0xde, 0xa2, 0x27, 0xab, 0xa6, 0xe8, 0xad, 0x6d, 0xca, 0x0d, 0x71, 0x0a, 0x01, 0x89, 0x93, - 0x29, 0x6f, 0xb4, 0x31, 0xeb, 0xb5, 0xbe, 0xd3, 0xe8, 0x14, 0x95, 0x25, 0x11, 0x86, 0xb5, 0x6a, 0xaa, 0x54, 0x12, - 0xd1, 0x54, 0x4e, 0xc2, 0x5b, 0x1a, 0x60, 0xa7, 0x0a, 0x67, 0x72, 0x21, 0x74, 0x2a, 0x03, 0xbc, 0xa1, 0xd5, 0xe6, - 0x5a, 0xde, 0x5a, 0x88, 0x69, 0x7c, 0x67, 0x7f, 0x30, 0x7c, 0x6d, 0x54, 0xfc, 0x6f, 0xc1, 0xb0, 0x47, 0xa5, 0x02, - 0xe0, 0x07, 0x86, 0xb3, 0x00, 0x39, 0xcb, 0x4f, 0xde, 0x02, 0xf8, 0x2c, 0x0b, 0x79, 0x07, 0xa9, 0xcc, 0xa4, 0xde, - 0x41, 0x2a, 0x83, 0x54, 0xe3, 0x51, 0xbf, 0x2f, 0x2a, 0x65, 0x51, 0xd8, 0x20, 0x51, 0xb8, 0x54, 0x07, 0x4b, 0x22, - 0x12, 0x68, 0xd7, 0x88, 0x72, 0x33, 0x2e, 0x20, 0xb4, 0x22, 0x34, 0x6e, 0xbf, 0xe9, 0x2d, 0x7c, 0xdf, 0xd9, 0x7c, - 0xe6, 0xf3, 0xef, 0x6c, 0xbe, 0xe9, 0xc8, 0x63, 0x7c, 0xfd, 0xb6, 0xd3, 0x58, 0xc6, 0x4b, 0x87, 0xb5, 0x1f, 0xca, - 0x87, 0x6c, 0x5a, 0xe6, 0xc1, 0x70, 0xd2, 0xc6, 0x93, 0x00, 0x29, 0x9b, 0x15, 0x0f, 0xd7, 0xc1, 0xed, 0xd6, 0x61, - 0xcc, 0x9b, 0xa4, 0x8d, 0xd0, 0xa1, 0x13, 0xae, 0x44, 0x6c, 0x24, 0xa7, 0xc3, 0xf7, 0x47, 0x70, 0xf7, 0x32, 0x53, - 0x1b, 0xbe, 0x52, 0xb6, 0x5a, 0xb3, 0xdd, 0x3a, 0xe4, 0x3b, 0xab, 0x34, 0xda, 0x78, 0xc6, 0xc8, 0x12, 0x3c, 0xd0, - 0x68, 0x61, 0x55, 0x0d, 0xe0, 0xb2, 0xfa, 0x42, 0xfc, 0xb6, 0xa0, 0x23, 0xf3, 0x7d, 0x68, 0x53, 0x5e, 0x2f, 0xb4, - 0x4f, 0x6a, 0x72, 0x18, 0x44, 0x07, 0xb9, 0x92, 0x41, 0x4e, 0xcc, 0x8f, 0x48, 0x72, 0x8a, 0x2e, 0xda, 0xbd, 0xe4, - 0xf4, 0x90, 0x1f, 0xf2, 0x14, 0x78, 0xd8, 0xb8, 0xe9, 0x2b, 0x34, 0xdb, 0xbe, 0xce, 0xe3, 0xc5, 0x90, 0x67, 0xae, - 0xf9, 0xaa, 0x83, 0x32, 0xd5, 0xce, 0x11, 0xb2, 0x00, 0xc5, 0x7c, 0x2f, 0x41, 0x76, 0xbd, 0x9b, 0x43, 0x9e, 0x42, - 0x3f, 0x50, 0xab, 0x63, 0x6b, 0x95, 0x83, 0xfb, 0x6d, 0x01, 0x08, 0xe6, 0x3b, 0xaa, 0xcd, 0xc5, 0xa6, 0x37, 0xe3, - 0xaa, 0xb3, 0x43, 0x5e, 0x8d, 0x30, 0x2c, 0xb3, 0xdd, 0x9f, 0x9f, 0x5a, 0xd5, 0xe5, 0x61, 0x00, 0x91, 0xdf, 0x16, - 0x5c, 0x84, 0x9d, 0x86, 0xdd, 0xba, 0x9c, 0xb0, 0xd3, 0xfa, 0x2c, 0x83, 0x22, 0xdb, 0xbd, 0x6e, 0xcd, 0xb4, 0x3e, - 0xdb, 0x2b, 0x70, 0x24, 0x84, 0x49, 0x99, 0x95, 0xce, 0xe0, 0x0a, 0xfd, 0xf0, 0x03, 0x72, 0xad, 0xbf, 0x5e, 0x68, - 0x9f, 0x5f, 0x22, 0x02, 0x64, 0x57, 0x5d, 0x97, 0xd5, 0xa1, 0x8f, 0xb2, 0x89, 0x57, 0x87, 0x3c, 0x58, 0xb9, 0xa7, - 0xb7, 0x73, 0x99, 0x7a, 0x7c, 0xed, 0xb5, 0xd2, 0x2d, 0xe4, 0x04, 0xe2, 0xe1, 0xba, 0x0b, 0xcb, 0x82, 0x9c, 0xdd, - 0xdc, 0x42, 0xc9, 0x70, 0xe2, 0xbe, 0xf4, 0x07, 0x66, 0xaf, 0x1b, 0xf8, 0x45, 0x72, 0x0a, 0x53, 0xdf, 0xec, 0xe1, - 0xb0, 0x03, 0x7d, 0x18, 0x38, 0x6c, 0x36, 0xe8, 0x33, 0x2b, 0x88, 0x3c, 0xe6, 0x85, 0xc5, 0xb3, 0x4b, 0xd2, 0xee, - 0xf1, 0xd4, 0x6d, 0x26, 0x23, 0x1a, 0xb5, 0x9b, 0x3c, 0x98, 0x19, 0xe0, 0x97, 0x2b, 0x1b, 0x16, 0xf1, 0xeb, 0x14, - 0x40, 0xc9, 0x17, 0xab, 0xd6, 0xa7, 0x82, 0x57, 0xbd, 0xe1, 0x74, 0x33, 0xdd, 0xaf, 0x1b, 0xdc, 0xee, 0x7a, 0x78, - 0xc2, 0x43, 0x34, 0x16, 0xad, 0xfd, 0xc4, 0x27, 0xc0, 0x01, 0x25, 0xad, 0xfb, 0xa7, 0xe0, 0x42, 0x59, 0xc2, 0x72, - 0xbb, 0xdc, 0x6c, 0xab, 0x9c, 0x85, 0xa3, 0x2d, 0x19, 0x70, 0x07, 0x9b, 0x10, 0x85, 0x0e, 0x0e, 0x3b, 0x38, 0x69, - 0xb7, 0x3b, 0xa7, 0x38, 0x39, 0x39, 0x85, 0x81, 0x36, 0x92, 0xd3, 0xc3, 0x99, 0xb2, 0x00, 0x0c, 0x72, 0xd6, 0xae, - 0xdd, 0x47, 0x10, 0xb4, 0x2a, 0x14, 0xaf, 0xf9, 0x61, 0x1c, 0xb7, 0x93, 0xfb, 0xad, 0xf6, 0xe9, 0x79, 0x03, 0x00, - 0xd4, 0x74, 0x1f, 0xae, 0xc6, 0xeb, 0x85, 0xae, 0x57, 0x29, 0x11, 0xbe, 0x5e, 0xad, 0xe1, 0xab, 0x35, 0xda, 0xeb, - 0x6a, 0x0a, 0xbe, 0xaa, 0x13, 0xce, 0x6d, 0x11, 0xaf, 0xb4, 0x09, 0xb7, 0x45, 0x6c, 0x07, 0x12, 0x83, 0x74, 0x9e, - 0x9c, 0x76, 0x4e, 0x91, 0x1d, 0x8b, 0x76, 0xf8, 0x51, 0xee, 0x93, 0xad, 0x22, 0x0d, 0x0d, 0x48, 0x52, 0xce, 0x4e, - 0x2e, 0x40, 0xa2, 0xe6, 0xe4, 0xb2, 0xdd, 0x9c, 0xb1, 0xc4, 0x4f, 0xc0, 0xa4, 0xc2, 0x72, 0x96, 0xab, 0xe0, 0x92, - 0x02, 0x40, 0x5c, 0x80, 0x71, 0xd1, 0xfd, 0xd3, 0xde, 0xfd, 0xe4, 0xf4, 0xac, 0x63, 0x89, 0x1e, 0xbf, 0xe8, 0xd4, - 0xd2, 0xcc, 0xd4, 0x93, 0x53, 0x93, 0x06, 0x5d, 0x27, 0xf7, 0x4f, 0xa1, 0x8c, 0x4b, 0x09, 0x4b, 0x41, 0xb0, 0x8d, - 0xaa, 0x18, 0x44, 0xd8, 0x48, 0x6b, 0xb9, 0x67, 0xb5, 0xec, 0xf3, 0x93, 0xe3, 0xfb, 0xa7, 0x21, 0xd4, 0xca, 0x59, - 0x98, 0x85, 0x76, 0x13, 0xf1, 0xb3, 0x83, 0xa5, 0x45, 0x87, 0xc9, 0x69, 0xba, 0x35, 0x41, 0xbb, 0x69, 0x0e, 0x0d, - 0x0e, 0x04, 0x0a, 0xc7, 0xa7, 0xc2, 0xe9, 0x4b, 0x82, 0xfb, 0xb1, 0xca, 0xd0, 0x24, 0x54, 0x38, 0xfb, 0x7b, 0xca, - 0xe0, 0x3d, 0xcd, 0xf0, 0xaa, 0xf2, 0x31, 0x15, 0x5f, 0xa9, 0x7a, 0x43, 0x21, 0x82, 0x88, 0x18, 0x44, 0x2e, 0xbe, - 0x79, 0x3d, 0xf7, 0x27, 0x70, 0x11, 0x66, 0x02, 0x2e, 0x34, 0xbd, 0x12, 0xb4, 0xe2, 0x05, 0x86, 0xa1, 0x43, 0xad, - 0x19, 0x56, 0x8f, 0xa7, 0xce, 0xa4, 0x20, 0xd4, 0x6d, 0x3d, 0xe7, 0xdf, 0x2b, 0x97, 0x94, 0x57, 0xd9, 0xc9, 0x29, - 0x4a, 0xdc, 0x65, 0x79, 0xd2, 0x46, 0x49, 0x60, 0x42, 0xe2, 0x8e, 0xe4, 0x2c, 0x23, 0xfd, 0xe8, 0x36, 0xc2, 0xd1, - 0x5d, 0x84, 0x23, 0xeb, 0xc3, 0xfc, 0x01, 0xfc, 0xb8, 0x23, 0x1c, 0x59, 0x57, 0xe6, 0x08, 0x47, 0x9a, 0x09, 0x08, - 0x2c, 0x16, 0x0d, 0x70, 0x0e, 0xa5, 0x8d, 0x67, 0x75, 0x59, 0xfa, 0xb1, 0xff, 0x2a, 0x5d, 0xaf, 0x6d, 0x4a, 0x20, - 0x65, 0x4e, 0xcd, 0x0e, 0xb5, 0x0f, 0x63, 0x47, 0xd4, 0x33, 0xeb, 0x11, 0x06, 0x01, 0x84, 0xde, 0xf9, 0x87, 0xf5, - 0xaa, 0x98, 0x24, 0xec, 0x18, 0x56, 0x1a, 0x5c, 0xd1, 0xa3, 0xf0, 0x0c, 0x8b, 0xf0, 0x58, 0xf8, 0xc2, 0x20, 0x56, - 0xf8, 0xdf, 0xb9, 0x94, 0x73, 0xff, 0x5b, 0xcb, 0xf2, 0x17, 0x3c, 0xc7, 0xe2, 0x2c, 0x5a, 0xc0, 0x72, 0xcb, 0x86, - 0x40, 0x1a, 0xb2, 0xfa, 0x08, 0xae, 0xc7, 0x2e, 0x4c, 0x1d, 0x48, 0x84, 0xd7, 0x46, 0xa0, 0xf2, 0xf2, 0xe1, 0xb5, - 0x0d, 0x99, 0x64, 0x3e, 0x21, 0x66, 0x1a, 0x84, 0x45, 0x96, 0x70, 0xa1, 0x31, 0x29, 0x98, 0x52, 0x91, 0x8d, 0x25, - 0x18, 0x49, 0xe1, 0x1f, 0x87, 0xf4, 0x29, 0x63, 0x11, 0x99, 0x0e, 0xeb, 0xb3, 0xb5, 0xe2, 0x70, 0x2e, 0x0b, 0x95, - 0xda, 0x97, 0x62, 0x3c, 0x18, 0xe7, 0xe5, 0x33, 0x8c, 0x69, 0x9e, 0xad, 0xb1, 0xbd, 0xc3, 0x2e, 0x0b, 0xb9, 0x2b, - 0xed, 0xb0, 0x54, 0x96, 0xad, 0xbf, 0x35, 0x21, 0x55, 0x9b, 0x51, 0x30, 0xd1, 0x6a, 0x40, 0x55, 0xe0, 0x0e, 0x28, - 0x6c, 0x83, 0xd2, 0xa4, 0xcb, 0xb2, 0x64, 0xba, 0x2c, 0x97, 0xe1, 0xa4, 0xd5, 0x5a, 0xaf, 0x71, 0xc1, 0x4c, 0x20, - 0x97, 0x9d, 0x25, 0x20, 0x5f, 0x4d, 0xe5, 0x4d, 0x90, 0xab, 0xd2, 0x72, 0x96, 0x66, 0x89, 0xa2, 0xc0, 0x08, 0x36, - 0x5a, 0xe3, 0xaf, 0x5c, 0x71, 0x80, 0xa7, 0x9b, 0xdd, 0x50, 0xca, 0x9c, 0x51, 0x88, 0xa1, 0x16, 0x34, 0xb9, 0xc6, - 0x53, 0x3e, 0x62, 0xbb, 0xdb, 0x04, 0x33, 0xe6, 0x7f, 0xaf, 0x45, 0x8f, 0x40, 0x96, 0xdd, 0x33, 0xa8, 0x03, 0x8b, - 0xb8, 0x82, 0x0e, 0x42, 0x19, 0x7c, 0x14, 0xe2, 0x66, 0x4e, 0xef, 0xe4, 0x42, 0x03, 0x5c, 0x16, 0x5a, 0xbe, 0x71, - 0xe1, 0x10, 0xf6, 0x5b, 0xd8, 0x47, 0x46, 0x58, 0x42, 0xc8, 0x80, 0x16, 0xb6, 0x11, 0x31, 0x5a, 0xd8, 0x05, 0x2a, - 0x68, 0x61, 0x13, 0x9e, 0xa2, 0xb5, 0x2e, 0x63, 0x9b, 0x5d, 0x97, 0x4f, 0x6a, 0x56, 0x9b, 0x60, 0xe1, 0xa4, 0x43, - 0x4d, 0x74, 0x70, 0x7b, 0xc8, 0x08, 0x6f, 0xfc, 0x7c, 0xf5, 0xfa, 0x95, 0x8b, 0x5c, 0xcd, 0xc7, 0xe0, 0xb2, 0xe9, - 0x54, 0x63, 0xd7, 0xe6, 0x2d, 0xaa, 0xb8, 0x52, 0x94, 0x5a, 0xe1, 0x14, 0x5a, 0x7e, 0x21, 0x74, 0x9e, 0xd8, 0xcb, - 0x8b, 0x67, 0xb2, 0x98, 0x51, 0x7b, 0x63, 0x84, 0xaf, 0x95, 0x7b, 0x7c, 0xde, 0xbc, 0x6f, 0x53, 0x4d, 0xf2, 0xdd, - 0xe6, 0x55, 0xc4, 0x22, 0x33, 0xf2, 0x2b, 0x68, 0x03, 0x4c, 0xe5, 0xf2, 0xed, 0xe0, 0x82, 0xb8, 0xf8, 0xff, 0x01, - 0x79, 0x79, 0x6b, 0xa9, 0x4b, 0x14, 0x35, 0xb8, 0xc1, 0x4f, 0x56, 0xf0, 0x2c, 0xb8, 0x2e, 0x34, 0xec, 0x91, 0x13, - 0x2f, 0xa2, 0x56, 0x54, 0x7f, 0x7b, 0xd7, 0xa8, 0x12, 0x7c, 0xec, 0xd8, 0x24, 0x97, 0x20, 0x7a, 0x94, 0xcf, 0xfc, - 0x71, 0x10, 0x4d, 0xfc, 0xdd, 0xf3, 0x65, 0xdb, 0xd3, 0xd9, 0xbc, 0x52, 0x27, 0x96, 0x57, 0x26, 0xe0, 0xe1, 0x68, - 0x1f, 0xd2, 0x41, 0x38, 0x48, 0x64, 0xa5, 0xf6, 0xd0, 0xe7, 0xa2, 0x6e, 0x9c, 0x5f, 0xb4, 0x59, 0xf3, 0x64, 0xb5, - 0xca, 0x2f, 0xdb, 0xac, 0x7d, 0x6a, 0x9f, 0xdd, 0x8b, 0x54, 0x06, 0x34, 0x97, 0x8f, 0x79, 0x16, 0x81, 0x76, 0x76, - 0x9c, 0x99, 0x70, 0x0a, 0x3e, 0x50, 0x34, 0x59, 0xe8, 0xaa, 0x2f, 0x09, 0xc6, 0xa5, 0xc4, 0xea, 0xf1, 0x0b, 0xd4, - 0x6b, 0xa7, 0xdb, 0xae, 0xd2, 0xcd, 0xf6, 0x61, 0x70, 0xe1, 0x52, 0x20, 0xdc, 0x81, 0x90, 0x07, 0xa0, 0xdf, 0x5d, - 0x0a, 0x30, 0x0d, 0x02, 0x54, 0x56, 0x20, 0xd2, 0xf2, 0xd9, 0x62, 0xf6, 0xac, 0xa0, 0x66, 0x19, 0x9e, 0xf0, 0x09, - 0xd7, 0x2a, 0xa5, 0x20, 0xdd, 0xee, 0x4a, 0x5f, 0xef, 0x96, 0xa0, 0xb2, 0x5a, 0xfc, 0xdd, 0x44, 0xf3, 0xec, 0x8b, - 0x72, 0x0b, 0x87, 0xb0, 0x59, 0x59, 0x81, 0x33, 0xb4, 0xc6, 0xb9, 0x9c, 0xd0, 0x82, 0xeb, 0xe9, 0xec, 0xdf, 0x5a, - 0x1d, 0xd6, 0xd7, 0x03, 0x73, 0x61, 0x05, 0x20, 0xa1, 0x62, 0xb4, 0x5a, 0xf1, 0xa3, 0xef, 0xdf, 0x27, 0x79, 0x9f, - 0xf0, 0x36, 0xee, 0xe0, 0x63, 0x7c, 0x8a, 0xdb, 0x2d, 0xdc, 0x3e, 0x85, 0xab, 0xfb, 0x2c, 0x5f, 0x8c, 0x98, 0x8a, - 0xe1, 0xfd, 0x35, 0x7d, 0x99, 0x9c, 0x1f, 0x96, 0xaf, 0x0e, 0xe8, 0x22, 0x71, 0xe8, 0x12, 0x04, 0xbf, 0x77, 0x51, - 0x03, 0xa3, 0x28, 0x0c, 0x59, 0x37, 0x0e, 0x55, 0x27, 0xa5, 0x7e, 0xe1, 0xf2, 0xb8, 0x07, 0xf6, 0xdc, 0x76, 0x65, - 0x9b, 0x60, 0xf6, 0x6d, 0x7f, 0xa6, 0xd5, 0xcf, 0xa6, 0x2e, 0x11, 0xc3, 0x43, 0xaf, 0x42, 0x0f, 0x74, 0x49, 0xda, - 0x07, 0x07, 0x60, 0x75, 0x14, 0xcc, 0x86, 0xdb, 0xe8, 0x07, 0xbc, 0x59, 0x4b, 0x83, 0x60, 0x05, 0x60, 0xdc, 0xf9, - 0x86, 0x93, 0xa5, 0x85, 0xad, 0x06, 0x2a, 0xac, 0x8b, 0x30, 0xae, 0x5e, 0x48, 0x2a, 0x8c, 0x10, 0x0d, 0x47, 0x98, - 0x0b, 0x86, 0xb2, 0xdf, 0xc2, 0x72, 0x3c, 0x56, 0x4c, 0xc3, 0xd1, 0x51, 0xb0, 0xaf, 0xac, 0x50, 0xe6, 0x14, 0x19, - 0xb2, 0x09, 0x17, 0x0f, 0xf5, 0x9f, 0xac, 0x90, 0xe6, 0xd3, 0x68, 0x30, 0xd2, 0xc8, 0xac, 0x62, 0x84, 0xb3, 0x9c, - 0xcf, 0xa1, 0xea, 0xa4, 0x00, 0xa7, 0x1f, 0xf8, 0xcb, 0x47, 0x69, 0xd8, 0x26, 0x90, 0xaf, 0x0f, 0x36, 0xa6, 0x0b, - 0x1e, 0x15, 0xf4, 0xe6, 0xb5, 0x78, 0x0c, 0x3b, 0xea, 0x61, 0xc1, 0x28, 0x64, 0x43, 0xd2, 0x3b, 0x68, 0x0a, 0x3e, - 0xa0, 0xcd, 0x97, 0x06, 0x70, 0xe9, 0xb9, 0xf9, 0xb0, 0x15, 0x7d, 0xec, 0xc6, 0xa4, 0x6c, 0xcb, 0x64, 0x9a, 0x53, - 0xba, 0xca, 0xb4, 0x51, 0xa8, 0xca, 0x29, 0xac, 0xb1, 0x8b, 0x7a, 0x12, 0x0e, 0x66, 0x44, 0xd5, 0x34, 0xed, 0x0f, - 0xcc, 0xdf, 0xd7, 0xb6, 0x64, 0x0b, 0xbb, 0x88, 0x33, 0x6b, 0x6c, 0x1e, 0x4e, 0x0d, 0xca, 0xb7, 0x31, 0xdc, 0xc3, - 0xc2, 0xeb, 0x9d, 0x35, 0xf2, 0x79, 0xe2, 0xc9, 0xe6, 0xc9, 0x7a, 0x6d, 0x06, 0xa2, 0x52, 0xd0, 0x03, 0xbd, 0xf5, - 0xdb, 0xa6, 0x05, 0xdb, 0xa3, 0xfc, 0x3a, 0x6d, 0xe1, 0x19, 0x87, 0xc7, 0x48, 0x7d, 0x7b, 0x57, 0xba, 0x90, 0x5f, - 0x1c, 0x48, 0x5a, 0x41, 0x8a, 0x9d, 0x4e, 0xd0, 0xd9, 0x31, 0x0e, 0x46, 0x0e, 0xf4, 0xfc, 0xea, 0x8b, 0x85, 0xb5, - 0xff, 0xfd, 0xa6, 0x2c, 0x68, 0xe2, 0xe9, 0x94, 0x13, 0xca, 0xfc, 0xf9, 0xf9, 0x86, 0x27, 0x15, 0x2a, 0xb8, 0x57, - 0xbc, 0x60, 0x4f, 0xdb, 0x40, 0x9f, 0x33, 0xfa, 0xd9, 0xfe, 0xb0, 0x31, 0x7c, 0x4a, 0x2d, 0x5b, 0x56, 0x48, 0xa5, - 0x1e, 0xda, 0x34, 0x7b, 0xf4, 0xc0, 0x11, 0xf9, 0x12, 0xba, 0x00, 0x5e, 0x7f, 0x54, 0xc8, 0xb9, 0x41, 0x04, 0xf7, - 0xdb, 0x8d, 0xdb, 0xf8, 0x0a, 0x80, 0xb7, 0xc3, 0x5e, 0xf5, 0x4f, 0x0b, 0xd8, 0xdf, 0xa8, 0x2c, 0xe9, 0xc7, 0xdb, - 0xb1, 0xc7, 0x7f, 0x21, 0x21, 0x6a, 0xbc, 0xc5, 0xc3, 0xc4, 0xa1, 0x53, 0xc9, 0x9a, 0x95, 0x3f, 0xb7, 0x4a, 0x02, - 0x86, 0xd5, 0x0b, 0x86, 0x6c, 0xdc, 0x56, 0x71, 0x9b, 0xf9, 0x1f, 0x54, 0x30, 0x58, 0xf0, 0xad, 0x91, 0x54, 0x2c, - 0x8b, 0xdf, 0x3e, 0x75, 0xfe, 0xab, 0xce, 0x71, 0xed, 0xeb, 0xda, 0x4b, 0xa1, 0x43, 0x13, 0xa5, 0x39, 0x42, 0x07, - 0x07, 0x1b, 0x19, 0x74, 0x0c, 0x80, 0x47, 0x8e, 0xfd, 0xf2, 0xcb, 0xe7, 0xd9, 0x31, 0xa3, 0x79, 0x2c, 0xa2, 0x90, - 0xb9, 0xf3, 0xdc, 0x9c, 0x9d, 0xc8, 0x13, 0xaa, 0xa6, 0xbe, 0x30, 0xc0, 0xf1, 0xd1, 0x56, 0x2a, 0xe0, 0x7b, 0xb4, - 0xde, 0x31, 0x81, 0x0d, 0x7e, 0xcb, 0x4e, 0x6a, 0x57, 0x41, 0xbf, 0x40, 0xcb, 0x5d, 0x4c, 0xe5, 0xc6, 0x02, 0x47, - 0x9b, 0x13, 0xd9, 0x39, 0xf4, 0x8d, 0x3a, 0x25, 0xeb, 0xf1, 0x64, 0xb7, 0xd1, 0x97, 0x14, 0xbb, 0x92, 0x2b, 0xda, - 0x36, 0x64, 0xd5, 0x6b, 0xc1, 0xba, 0x32, 0x75, 0xaa, 0xae, 0x79, 0x2b, 0x4b, 0x9b, 0xd2, 0x2e, 0xc9, 0xde, 0x6d, - 0xb1, 0xf0, 0x2a, 0xbc, 0xd1, 0x28, 0x2f, 0x42, 0xc1, 0x1e, 0x4b, 0x0c, 0xba, 0x9c, 0xc0, 0xf5, 0xc2, 0x6a, 0x15, - 0xc3, 0x9f, 0x5d, 0x63, 0xd8, 0x65, 0xba, 0xf4, 0x81, 0x6f, 0xf0, 0x2b, 0x41, 0xc0, 0x62, 0x67, 0x07, 0x09, 0xd6, - 0x5d, 0x6e, 0xd0, 0x70, 0x9c, 0xf8, 0x2f, 0x78, 0x2e, 0x5b, 0x7b, 0x97, 0x83, 0x49, 0xf6, 0x8d, 0x27, 0xf6, 0x4a, - 0xd6, 0xb2, 0x16, 0xed, 0x7e, 0x43, 0x82, 0x21, 0x76, 0x53, 0x3a, 0xc7, 0xad, 0xa4, 0x8d, 0x22, 0x57, 0xac, 0x42, - 0xff, 0x6f, 0x15, 0xc9, 0x6c, 0xe6, 0x7f, 0x9d, 0x9d, 0x9d, 0xb9, 0x14, 0x67, 0xf3, 0xa7, 0x8c, 0x07, 0x9c, 0x49, - 0x60, 0x5f, 0x79, 0xc6, 0x8c, 0x0e, 0xf9, 0x2d, 0x0c, 0x85, 0x08, 0x72, 0x29, 0x1c, 0xbb, 0x04, 0xaf, 0x3d, 0x02, - 0xe5, 0x01, 0xf6, 0xef, 0xc9, 0x46, 0x39, 0xff, 0x5c, 0x94, 0x0f, 0xa7, 0x5c, 0x36, 0xc8, 0xbe, 0x9a, 0xcf, 0xbe, - 0x35, 0x93, 0x81, 0x17, 0x12, 0x22, 0x6c, 0x7f, 0x1b, 0x96, 0xd6, 0x59, 0xca, 0xe0, 0x48, 0xcb, 0x45, 0x36, 0xb5, - 0x9a, 0x7f, 0xf7, 0x61, 0xca, 0xba, 0xa7, 0x86, 0x20, 0x72, 0x17, 0x59, 0xba, 0xa8, 0xa0, 0xd1, 0x8f, 0x65, 0x00, - 0xd0, 0xbd, 0x57, 0x6c, 0xc1, 0x7e, 0xc4, 0x7b, 0x55, 0x0a, 0x7c, 0x3c, 0x2c, 0x38, 0xcd, 0x7f, 0xc4, 0x7b, 0x55, - 0x20, 0x50, 0x70, 0x85, 0x34, 0xb1, 0x34, 0xb1, 0x79, 0x56, 0x3b, 0x8d, 0x04, 0x50, 0xd0, 0x3c, 0x32, 0x07, 0xd9, - 0x73, 0x17, 0xa3, 0x31, 0xe9, 0x60, 0x17, 0x1c, 0xcc, 0x46, 0x84, 0xb5, 0x81, 0xd4, 0x21, 0x6e, 0x5d, 0x39, 0x1b, - 0xf3, 0xf5, 0x68, 0x63, 0x41, 0x8c, 0x32, 0x99, 0x5c, 0x3e, 0xe7, 0xf1, 0xd6, 0x62, 0xa1, 0xb0, 0x5a, 0xb0, 0x40, - 0xb5, 0x2a, 0x55, 0x7a, 0x58, 0x7c, 0xbb, 0x60, 0x16, 0x14, 0x31, 0x5b, 0xef, 0xe1, 0x2d, 0x57, 0x04, 0xa4, 0x64, - 0x97, 0x04, 0x2f, 0xa3, 0x1b, 0x4c, 0x25, 0xcb, 0x99, 0x1c, 0x31, 0x4b, 0xe8, 0x99, 0xd2, 0x11, 0x36, 0x79, 0x0a, - 0x22, 0x89, 0xed, 0xb7, 0xb0, 0x63, 0x8d, 0x5e, 0x08, 0x2f, 0xa4, 0xc0, 0xb9, 0x6a, 0x9a, 0x98, 0x51, 0x6e, 0xa2, - 0x8b, 0x3d, 0x54, 0x73, 0x96, 0x69, 0x8b, 0x00, 0xfb, 0x0e, 0x0d, 0xa5, 0x78, 0x6e, 0x40, 0x61, 0x9e, 0xf4, 0x76, - 0x29, 0x8f, 0x61, 0xf1, 0x82, 0x14, 0x20, 0x6a, 0x5c, 0x4c, 0xca, 0x3a, 0xf3, 0x7c, 0x31, 0xe1, 0xa2, 0x42, 0x86, - 0x82, 0xa9, 0xb9, 0x14, 0xf0, 0xa2, 0x46, 0x59, 0xc4, 0xd0, 0xa1, 0x1a, 0xbe, 0x5b, 0x12, 0x56, 0xd6, 0x31, 0xc7, - 0x14, 0x17, 0x55, 0x0d, 0x60, 0x2e, 0x1e, 0x1a, 0x01, 0xd1, 0x87, 0x97, 0x7d, 0x2d, 0xde, 0xc9, 0x79, 0x95, 0xef, - 0x69, 0x9c, 0x0f, 0x5c, 0xef, 0xec, 0x86, 0xd1, 0xda, 0x3c, 0x7a, 0x15, 0x6c, 0xdf, 0x0f, 0xbc, 0x7a, 0x08, 0x6e, - 0x6d, 0x9e, 0xcd, 0x2a, 0xb3, 0x86, 0xac, 0x7c, 0x23, 0xa2, 0x6a, 0xaf, 0x5e, 0x55, 0x0a, 0x5b, 0x11, 0xa0, 0x52, - 0xf0, 0xd1, 0x56, 0xfe, 0x13, 0x6d, 0xf3, 0xed, 0x39, 0x54, 0x86, 0x07, 0xf2, 0x64, 0xa8, 0xea, 0x01, 0x17, 0xe5, - 0x87, 0x00, 0x16, 0x3f, 0x32, 0xf1, 0x83, 0x77, 0x5d, 0x20, 0x73, 0xa6, 0x62, 0x89, 0x97, 0x7d, 0x3a, 0x48, 0xad, - 0x3c, 0x94, 0x4a, 0xb0, 0xed, 0xb9, 0x29, 0xb8, 0xf6, 0x81, 0x8a, 0x71, 0x9f, 0x0d, 0xd2, 0x65, 0x3d, 0x98, 0xb1, - 0x0d, 0xa7, 0xec, 0xcd, 0x39, 0x4d, 0xf4, 0x5f, 0x3a, 0xc0, 0x39, 0x01, 0xdb, 0x63, 0xcf, 0x9e, 0xbe, 0x89, 0x33, - 0xd4, 0xab, 0x73, 0xf8, 0xcb, 0x35, 0xce, 0x71, 0x86, 0xd2, 0x87, 0x31, 0x5c, 0x60, 0xad, 0x31, 0x80, 0x2f, 0xb3, - 0xa4, 0x0a, 0x3c, 0x52, 0x33, 0x23, 0xb1, 0xba, 0x8b, 0x40, 0xb4, 0xd4, 0xe1, 0xed, 0x38, 0xf3, 0xe1, 0xc0, 0x0d, - 0xf7, 0xfa, 0xcc, 0x08, 0x87, 0x93, 0x2c, 0xae, 0x9d, 0x33, 0x9c, 0x5c, 0xee, 0xf3, 0xda, 0x89, 0x09, 0xd6, 0xde, - 0xe1, 0xa9, 0x02, 0x7a, 0x34, 0x38, 0x55, 0x2c, 0x0d, 0x81, 0x98, 0x09, 0xe0, 0xcd, 0x1c, 0x1e, 0x6d, 0x01, 0xce, - 0x47, 0x6b, 0x1c, 0x7c, 0xa5, 0xb5, 0xae, 0x36, 0x95, 0x28, 0xeb, 0x35, 0xee, 0x4f, 0x33, 0x3c, 0xca, 0xf0, 0x3c, - 0x1b, 0x04, 0xc7, 0xcd, 0x2c, 0x0b, 0x4d, 0xba, 0x56, 0xab, 0xa7, 0xce, 0x8c, 0x10, 0xd9, 0x9f, 0x96, 0xfe, 0xa0, - 0x1e, 0x20, 0x7c, 0x0a, 0x59, 0x40, 0x4b, 0x7a, 0xee, 0x6f, 0xc3, 0xbe, 0x16, 0x8e, 0x1a, 0x31, 0x4f, 0x2c, 0x19, - 0xe9, 0xf9, 0x1f, 0x65, 0x96, 0x6d, 0xad, 0x11, 0xcd, 0x6f, 0xf7, 0xa2, 0x86, 0x6f, 0x2f, 0xd0, 0xb2, 0x95, 0x66, - 0x3b, 0x80, 0x28, 0xd6, 0x38, 0x49, 0x07, 0x6b, 0x24, 0x57, 0xab, 0xd8, 0xa6, 0x10, 0x9e, 0xcc, 0x18, 0x55, 0x8b, - 0xc2, 0x3c, 0xa0, 0x17, 0x2b, 0x94, 0x18, 0x7e, 0x17, 0x3b, 0x1b, 0x51, 0x78, 0xaf, 0x4e, 0x82, 0xe1, 0x46, 0x2c, - 0x88, 0xac, 0x89, 0xdc, 0xc3, 0xac, 0xb2, 0x0c, 0x12, 0x44, 0x18, 0x91, 0xdf, 0x5e, 0x97, 0x0a, 0xfb, 0x44, 0x9f, - 0xfd, 0x63, 0x7c, 0x01, 0xe1, 0xe6, 0x6d, 0x42, 0x8b, 0x21, 0x9d, 0x00, 0x1b, 0x0b, 0x71, 0x08, 0xb7, 0x12, 0x56, - 0xab, 0xfe, 0xa0, 0x2b, 0x0c, 0x79, 0x76, 0x0f, 0x08, 0x96, 0x0d, 0xed, 0x6e, 0x00, 0xae, 0xba, 0x2d, 0x35, 0xd7, - 0x46, 0xf7, 0x43, 0xcd, 0x1b, 0x67, 0xdc, 0x25, 0xb9, 0x67, 0x4a, 0xaa, 0x97, 0xc8, 0x6b, 0x16, 0xe0, 0x26, 0x74, - 0x15, 0x1e, 0xe1, 0x85, 0xb5, 0xe1, 0x34, 0x0f, 0x5a, 0x51, 0xf3, 0x8e, 0x15, 0x3c, 0x9f, 0x4d, 0x58, 0x3f, 0x1b, - 0xe0, 0x91, 0x0f, 0x77, 0xbe, 0xff, 0x36, 0x1e, 0x21, 0x54, 0x10, 0x03, 0x53, 0xeb, 0xb2, 0x3d, 0xaa, 0xec, 0xf6, - 0x4d, 0xa6, 0x61, 0x18, 0x8c, 0x11, 0xf3, 0x28, 0x34, 0x62, 0xce, 0x1b, 0x0d, 0xb4, 0x20, 0x23, 0x30, 0x62, 0x5e, - 0x04, 0xad, 0x2d, 0xec, 0x63, 0xa7, 0x41, 0x7b, 0x0b, 0x84, 0xba, 0x1c, 0x68, 0x9a, 0x86, 0x67, 0x4d, 0xaa, 0x67, - 0xe5, 0xfd, 0x23, 0x5b, 0x47, 0x1d, 0x50, 0x24, 0x8c, 0x2f, 0xfd, 0x24, 0xac, 0x6b, 0xb8, 0x1d, 0xf7, 0xd8, 0x8c, - 0xdb, 0xd9, 0x36, 0xa8, 0xbe, 0xec, 0x67, 0x83, 0x41, 0x57, 0x7a, 0x2b, 0x89, 0x16, 0x1e, 0x57, 0x0f, 0xa1, 0x54, - 0x8b, 0xf7, 0x55, 0x6f, 0x5e, 0x79, 0x73, 0xff, 0xbe, 0xea, 0xe6, 0x79, 0x0c, 0x1c, 0xd0, 0x3e, 0xdc, 0x0f, 0x55, - 0xf1, 0xc1, 0x8e, 0x3a, 0x10, 0x05, 0x2d, 0x6d, 0xd5, 0x04, 0x52, 0x6b, 0x66, 0x17, 0xeb, 0xa6, 0x42, 0x87, 0x02, - 0xc2, 0x90, 0xa9, 0xaa, 0xbb, 0x3b, 0x15, 0xa8, 0x86, 0x38, 0x9c, 0xfa, 0x8f, 0xad, 0x11, 0x6b, 0x1c, 0x75, 0x46, - 0x91, 0x31, 0x92, 0xb4, 0xcb, 0x07, 0x6f, 0x1f, 0x81, 0x95, 0x80, 0x8f, 0x41, 0x6d, 0x92, 0x8c, 0x21, 0xc1, 0x5b, - 0x96, 0x69, 0xc3, 0x87, 0x70, 0x87, 0xa0, 0x3c, 0xb1, 0x41, 0x69, 0x5d, 0x25, 0x0b, 0xb9, 0xaa, 0xcb, 0xeb, 0x00, - 0x3d, 0xef, 0xca, 0xdf, 0xd8, 0x70, 0x64, 0xc1, 0xc0, 0xb2, 0xad, 0x7d, 0x02, 0x1e, 0xf9, 0xb8, 0x42, 0x10, 0xbf, - 0x14, 0x3a, 0x31, 0xf1, 0xba, 0xaf, 0x60, 0x83, 0xe2, 0x39, 0x38, 0x08, 0x3a, 0x09, 0x0e, 0x83, 0x77, 0x99, 0xd5, - 0x24, 0x1b, 0xdc, 0x9a, 0x91, 0x78, 0xbe, 0x5a, 0xb5, 0xd0, 0xe1, 0xdf, 0xe6, 0x49, 0xea, 0x71, 0xa9, 0x70, 0x1f, - 0x57, 0x0a, 0x77, 0xb0, 0x04, 0x24, 0xe3, 0x40, 0xd7, 0x8e, 0x65, 0xa8, 0x46, 0x87, 0x68, 0xe9, 0x2f, 0x20, 0x76, - 0xb6, 0x3b, 0x96, 0x40, 0xcf, 0xbe, 0x55, 0xc0, 0xea, 0xda, 0xcb, 0x12, 0xc8, 0x08, 0xee, 0x7e, 0x13, 0x18, 0x15, - 0xa2, 0xf1, 0xf9, 0x33, 0xaf, 0x5a, 0xf0, 0xc4, 0xf9, 0x73, 0xcd, 0x0c, 0xeb, 0x5e, 0xd0, 0x1b, 0xd3, 0x7c, 0x3c, - 0xc6, 0xcd, 0xb1, 0x05, 0xe7, 0x51, 0x07, 0x7e, 0x5a, 0x88, 0x1e, 0x75, 0xb0, 0x4b, 0xc5, 0xe3, 0x12, 0xc8, 0x21, - 0x7a, 0x3a, 0x03, 0x29, 0x60, 0xa5, 0x63, 0xab, 0x45, 0x9a, 0xa0, 0xd5, 0x6a, 0x72, 0x41, 0x5a, 0x08, 0x2d, 0xd5, - 0x0d, 0xd7, 0xd9, 0x14, 0x7c, 0xa4, 0x41, 0x31, 0xf0, 0x86, 0xea, 0x69, 0x8c, 0xf0, 0x18, 0x2d, 0x47, 0x6c, 0x4c, - 0x17, 0xb9, 0x4e, 0x55, 0x8f, 0x27, 0x36, 0x70, 0x2f, 0xb3, 0x91, 0xe0, 0x8e, 0x3a, 0x78, 0x62, 0xf8, 0xcb, 0xf7, - 0xc6, 0x1c, 0xa4, 0xc8, 0x4c, 0xf2, 0xc4, 0x24, 0x60, 0x9e, 0x64, 0xb9, 0x54, 0xcc, 0x36, 0xd3, 0xb5, 0xb6, 0xe5, - 0x10, 0x92, 0x3c, 0xd2, 0x05, 0x37, 0x56, 0x94, 0x51, 0x3a, 0x25, 0xaa, 0xa7, 0x8e, 0x3a, 0xe9, 0x04, 0xf3, 0x04, - 0x38, 0xbd, 0x77, 0x32, 0x66, 0x8d, 0xf2, 0x56, 0x74, 0x86, 0x0e, 0xa7, 0x58, 0x54, 0x97, 0xa8, 0x33, 0x74, 0x38, - 0x41, 0x78, 0xd6, 0x20, 0xb9, 0x02, 0x8f, 0x61, 0x2e, 0xfe, 0x8f, 0x94, 0xff, 0xe6, 0xb0, 0x21, 0xc4, 0xf4, 0x5b, - 0xd8, 0x29, 0x6c, 0x14, 0xa5, 0x39, 0x01, 0xaf, 0xc5, 0xf6, 0x19, 0xce, 0xc8, 0xa4, 0x99, 0xfb, 0x80, 0x7b, 0xa6, - 0x95, 0xc6, 0xad, 0x46, 0x87, 0x19, 0x1e, 0x6d, 0x26, 0xc5, 0x66, 0xae, 0xcd, 0x3c, 0xcd, 0xe0, 0x7c, 0xaf, 0x46, - 0xe1, 0xca, 0x2f, 0x36, 0x93, 0xc2, 0xf2, 0x0e, 0xb8, 0xcd, 0x11, 0x16, 0x4d, 0x8a, 0x73, 0x3c, 0x6b, 0xbe, 0xc2, - 0xb3, 0xe6, 0x87, 0x32, 0xa3, 0xb1, 0xc0, 0x02, 0x82, 0xf7, 0x41, 0x22, 0x9e, 0x55, 0xc9, 0x23, 0x2c, 0x1a, 0xa6, - 0x3c, 0x9e, 0x35, 0xaa, 0xd2, 0xcd, 0x05, 0x16, 0x0d, 0x53, 0xba, 0xf1, 0x01, 0xcf, 0x1a, 0xaf, 0xfe, 0xc5, 0xa4, - 0xa3, 0x14, 0xd0, 0x65, 0x8e, 0x96, 0x99, 0x1d, 0xe2, 0xd5, 0x6f, 0x6f, 0xdf, 0xb5, 0xaf, 0x3b, 0x87, 0x13, 0xec, - 0xd7, 0x2f, 0x33, 0x38, 0x96, 0xe9, 0x98, 0x35, 0x01, 0xa2, 0x19, 0xee, 0x1c, 0x4e, 0x71, 0xe7, 0x30, 0x73, 0x4d, - 0xad, 0x67, 0x0d, 0x72, 0xab, 0x43, 0x28, 0xea, 0x28, 0x0d, 0xe1, 0xe3, 0x27, 0x9b, 0x4e, 0x50, 0x0d, 0x94, 0xe8, - 0x70, 0x52, 0x03, 0x15, 0x7c, 0x2f, 0x6a, 0xdf, 0x55, 0xbd, 0x0a, 0x83, 0x2c, 0x94, 0x50, 0xb8, 0xe6, 0x06, 0x3c, - 0xb5, 0x14, 0x03, 0x99, 0x30, 0xc5, 0x02, 0xe5, 0x3b, 0xa0, 0x30, 0xca, 0x13, 0x33, 0xf4, 0x60, 0x3a, 0x26, 0xf1, - 0xff, 0xe7, 0xc9, 0x94, 0x43, 0x2f, 0xb7, 0xcc, 0xd6, 0xf4, 0xdc, 0x64, 0xc2, 0xe1, 0x03, 0x8f, 0xf5, 0x7f, 0xed, - 0x40, 0xb1, 0x01, 0x29, 0xfe, 0xbf, 0x74, 0x74, 0x21, 0x18, 0x21, 0x2b, 0x4a, 0x0b, 0x87, 0xf8, 0xdf, 0x1f, 0x56, - 0xd0, 0x7d, 0xb1, 0xd5, 0x7d, 0x61, 0xba, 0x0f, 0x9b, 0x36, 0xaa, 0x9c, 0xb4, 0xaa, 0x64, 0xc9, 0x7f, 0x9d, 0x6e, - 0x6d, 0x81, 0x46, 0xd4, 0xe8, 0xd9, 0x24, 0x6c, 0x70, 0xbf, 0x9d, 0xee, 0x40, 0xe6, 0x35, 0xb7, 0x2f, 0xa4, 0xc2, - 0xe1, 0x1b, 0xdc, 0xa9, 0x5e, 0xb6, 0xc0, 0x7b, 0x53, 0x19, 0x7d, 0x65, 0x1c, 0x5a, 0x0e, 0xd2, 0x4d, 0x53, 0x6e, - 0x63, 0x2c, 0x9d, 0x9c, 0x62, 0xe3, 0x8a, 0x08, 0x95, 0x6e, 0x2f, 0x41, 0x29, 0x3e, 0xd6, 0x4d, 0x66, 0xbe, 0x2e, - 0x74, 0x62, 0x2e, 0xa1, 0x1a, 0xe6, 0xf3, 0xee, 0x52, 0x27, 0x5a, 0xce, 0x6d, 0xde, 0xdd, 0x05, 0xf4, 0x09, 0x1a, - 0xd6, 0x46, 0x60, 0xb7, 0xcf, 0x0a, 0xa7, 0xdf, 0xa9, 0x0e, 0xc1, 0xf0, 0x00, 0x72, 0xa4, 0xc5, 0xf6, 0x81, 0x4d, - 0x6b, 0xd8, 0x75, 0xd1, 0x2c, 0x13, 0x6d, 0xab, 0x4d, 0x93, 0x6b, 0xf7, 0x30, 0x9f, 0x87, 0x3c, 0x05, 0x2f, 0xac, - 0x7e, 0x7c, 0x07, 0xbb, 0x71, 0x5b, 0x63, 0x24, 0xea, 0x4a, 0xa6, 0x12, 0xfa, 0xc9, 0x2d, 0x66, 0xc9, 0x9d, 0xf1, - 0x62, 0x54, 0xc6, 0xdf, 0xc7, 0xc4, 0xe5, 0x8f, 0x2a, 0x49, 0x0e, 0x2c, 0xfb, 0x1b, 0x2c, 0xb9, 0x05, 0xf3, 0xc4, - 0xb2, 0x9a, 0xc4, 0x3a, 0xb9, 0x0b, 0x16, 0x51, 0x9a, 0x46, 0xd6, 0x86, 0x01, 0x35, 0xcd, 0x58, 0xf5, 0xe0, 0x3e, - 0x04, 0x7a, 0xe8, 0x95, 0xa5, 0xb4, 0xeb, 0x2c, 0xad, 0x75, 0xaf, 0x4d, 0xf7, 0x9b, 0x03, 0x0a, 0xf8, 0xc2, 0x80, - 0x6b, 0xfa, 0x57, 0x93, 0x48, 0x86, 0xec, 0x1f, 0xce, 0x8a, 0xc7, 0x8b, 0xc2, 0x60, 0x9a, 0xe8, 0xe9, 0x24, 0x9b, - 0xb7, 0xc1, 0x54, 0x2f, 0x9b, 0x77, 0x6e, 0xb1, 0xfb, 0xbe, 0xb3, 0xdf, 0x77, 0x58, 0xf4, 0x98, 0xc9, 0x48, 0x99, - 0x29, 0xe6, 0xbf, 0xef, 0xec, 0xf7, 0x1d, 0xde, 0x1e, 0xcc, 0x8d, 0xbf, 0x50, 0x2c, 0xd9, 0x19, 0x2e, 0xc1, 0x84, - 0x3c, 0xe0, 0x6e, 0x6a, 0x59, 0x26, 0x08, 0x6c, 0x2d, 0x01, 0xe2, 0x7c, 0x3e, 0x8d, 0x2b, 0x5e, 0x0d, 0x01, 0xf7, - 0xe9, 0x5d, 0xdb, 0xab, 0x54, 0xe0, 0x31, 0x41, 0x23, 0x62, 0x62, 0xdb, 0x98, 0xd7, 0xcd, 0x80, 0xcb, 0x23, 0xba, - 0xd4, 0x93, 0x24, 0xc0, 0xab, 0x1a, 0x95, 0xb7, 0x29, 0x52, 0x7e, 0x91, 0x20, 0xc7, 0x17, 0x7b, 0x44, 0x15, 0x03, - 0x58, 0x95, 0x25, 0x7d, 0x02, 0xa9, 0xe7, 0x07, 0x13, 0xfd, 0xb2, 0x89, 0x3c, 0xf6, 0x9d, 0xdf, 0x2f, 0x4c, 0x4f, - 0x0b, 0xb9, 0x98, 0x4c, 0xc1, 0x87, 0x16, 0x58, 0x86, 0xc2, 0xd4, 0xab, 0x6c, 0xfd, 0x6b, 0x92, 0x9b, 0x00, 0x0a, - 0xa7, 0x9b, 0x32, 0xa1, 0x99, 0x5e, 0xd0, 0xdc, 0x58, 0x92, 0x72, 0x31, 0x79, 0x24, 0x6f, 0x5f, 0x02, 0x76, 0x53, - 0xa2, 0x1b, 0x3b, 0xf2, 0xde, 0xc2, 0x0e, 0xc0, 0x19, 0x61, 0xbb, 0x2a, 0x3e, 0x54, 0xa0, 0xf3, 0xc7, 0x39, 0x61, - 0xbb, 0xaa, 0x3e, 0x61, 0x36, 0x7b, 0x4a, 0x36, 0x86, 0xdb, 0x8b, 0xb3, 0x46, 0x8e, 0x8e, 0x3a, 0x69, 0xde, 0xf5, - 0xc4, 0xc0, 0x02, 0x34, 0x00, 0xee, 0xd6, 0xf6, 0x2c, 0xef, 0x6e, 0x08, 0xe8, 0x5d, 0x32, 0x69, 0xaf, 0xcb, 0x4d, - 0xca, 0x6a, 0xd5, 0xa9, 0xa8, 0x60, 0x81, 0xa7, 0xc1, 0x5e, 0xa0, 0xf6, 0x6b, 0x07, 0xc5, 0xb9, 0xca, 0x36, 0x4d, - 0xcf, 0xcb, 0xbe, 0xbb, 0x3b, 0x16, 0x19, 0xdb, 0xb4, 0xb7, 0x3b, 0x88, 0x84, 0xe5, 0x84, 0x75, 0xc0, 0x09, 0x57, - 0xb5, 0x03, 0x02, 0x74, 0x1d, 0x88, 0xdc, 0x58, 0x92, 0xe5, 0xba, 0x32, 0xba, 0x0f, 0xfc, 0x6e, 0x29, 0x91, 0x6e, - 0xb4, 0x25, 0xc1, 0xf4, 0x09, 0x46, 0x4d, 0x67, 0x9e, 0xa6, 0xae, 0xbd, 0xba, 0xbc, 0x29, 0xda, 0xfa, 0x37, 0xa0, - 0xb1, 0xd9, 0x1e, 0x26, 0x86, 0x32, 0x88, 0x81, 0xde, 0x47, 0xbc, 0xdb, 0x68, 0x64, 0x08, 0x14, 0x32, 0xd9, 0x00, - 0xcb, 0xc4, 0x6b, 0xd1, 0x0f, 0x0e, 0x0c, 0x3c, 0xaa, 0x04, 0x84, 0x29, 0x08, 0x21, 0x61, 0xd7, 0x06, 0x61, 0xc3, - 0xe5, 0xaa, 0xe5, 0xc2, 0x46, 0xaa, 0x0d, 0x1d, 0xfc, 0xbf, 0xc2, 0x65, 0xab, 0x67, 0x96, 0x8b, 0x62, 0x70, 0x33, - 0x37, 0x60, 0x91, 0x20, 0x3d, 0xda, 0x6c, 0x0f, 0xc5, 0xdd, 0xb9, 0xd8, 0x6c, 0x08, 0x48, 0xcc, 0x61, 0x82, 0xa2, - 0xe1, 0xdc, 0x18, 0x63, 0x95, 0x54, 0x5a, 0xd6, 0x9a, 0xc4, 0x1c, 0xf8, 0xd2, 0x85, 0xeb, 0xbe, 0xbc, 0x4d, 0x19, - 0xbe, 0x4b, 0x05, 0xbe, 0x01, 0x4f, 0x9a, 0x54, 0x62, 0xf7, 0x78, 0x41, 0xb1, 0x26, 0xba, 0xeb, 0xd9, 0xdb, 0x02, - 0xd6, 0xd9, 0xec, 0x11, 0x11, 0xfc, 0xae, 0x7e, 0xb5, 0xc1, 0x77, 0x0b, 0xbf, 0x02, 0xeb, 0xe7, 0xe0, 0x24, 0xc5, - 0xa2, 0x21, 0x9b, 0x85, 0x3b, 0x32, 0xa0, 0x5c, 0xc5, 0x2f, 0x87, 0xa9, 0x5b, 0xc5, 0x70, 0xed, 0xe3, 0x15, 0xfe, - 0xb0, 0xd1, 0x6e, 0x43, 0x95, 0xc5, 0xed, 0xde, 0x14, 0x0d, 0x59, 0x35, 0xbd, 0x23, 0x73, 0x23, 0xa5, 0xfe, 0xf5, - 0x01, 0xb7, 0xb6, 0xda, 0xf7, 0xd3, 0x7c, 0xeb, 0xd1, 0xb9, 0x6a, 0xda, 0xa7, 0xd6, 0x8a, 0xe0, 0xe0, 0x67, 0x0b, - 0x37, 0xb7, 0x06, 0x1c, 0xc0, 0xcf, 0xdf, 0xd1, 0x3c, 0xce, 0x20, 0x3a, 0xbd, 0xd5, 0x8c, 0xaf, 0xe2, 0xbf, 0x46, - 0x8d, 0xb8, 0x97, 0xfe, 0x95, 0xfc, 0x35, 0x6a, 0xa0, 0x1e, 0x8a, 0xe7, 0xb7, 0x2b, 0x36, 0x5b, 0x41, 0xb0, 0xb5, - 0x7b, 0x47, 0xf8, 0x75, 0x58, 0x92, 0x6b, 0x9a, 0xf3, 0x6c, 0xe5, 0x1e, 0x04, 0x5c, 0xb9, 0x57, 0x89, 0x56, 0xe6, - 0x8d, 0xab, 0x55, 0x2c, 0x87, 0x39, 0x04, 0x16, 0x8e, 0xf7, 0x9a, 0xbd, 0x7e, 0xab, 0xf9, 0x60, 0x60, 0xff, 0x35, - 0x11, 0xee, 0x51, 0x2d, 0x62, 0xdb, 0x9b, 0x8d, 0xad, 0x1f, 0x83, 0x61, 0x07, 0x84, 0x02, 0x07, 0xb9, 0xf4, 0x71, - 0x86, 0xac, 0xef, 0xc9, 0x6a, 0xc5, 0x5c, 0x34, 0x6b, 0xa7, 0xc1, 0x2f, 0x63, 0x33, 0x1d, 0xb6, 0x93, 0x4e, 0xd7, - 0x8b, 0xb1, 0xa4, 0x01, 0x91, 0xa6, 0x31, 0x83, 0x40, 0x52, 0x4b, 0xc3, 0x61, 0xcd, 0x6f, 0xa3, 0xb4, 0xba, 0x3f, - 0x82, 0x94, 0x1f, 0xa2, 0x94, 0x1f, 0x11, 0x08, 0xa0, 0x6d, 0x99, 0xa3, 0xb2, 0x21, 0xef, 0xbb, 0x74, 0xcf, 0x38, - 0x33, 0x34, 0xf8, 0x6a, 0xd5, 0xaa, 0x86, 0x29, 0x8a, 0xfa, 0x30, 0x97, 0x6b, 0x2c, 0xc8, 0x1b, 0xd0, 0x35, 0x2b, - 0x22, 0x7a, 0xa1, 0xab, 0x3c, 0xbc, 0x87, 0x8c, 0x25, 0x01, 0x27, 0xfd, 0x9e, 0xe8, 0x15, 0xe4, 0xf2, 0x61, 0x0c, - 0x3e, 0x66, 0x98, 0xf7, 0x75, 0xbf, 0x18, 0x0c, 0x50, 0xea, 0x9c, 0xce, 0x52, 0x13, 0x71, 0x25, 0xf0, 0x4b, 0x2e, - 0xc0, 0x2f, 0x59, 0x21, 0xd6, 0x2f, 0x06, 0xe4, 0x5e, 0x16, 0x4b, 0x70, 0xca, 0xdf, 0xe1, 0xf3, 0xf8, 0x30, 0x34, - 0x30, 0x35, 0xc3, 0x32, 0x17, 0xd9, 0x60, 0x31, 0x67, 0x2d, 0x81, 0xe0, 0x66, 0xc0, 0x5d, 0x6a, 0x43, 0xa2, 0xb1, - 0x06, 0x8a, 0x6e, 0xa3, 0xd0, 0xcc, 0xe8, 0xe9, 0x56, 0x1b, 0xfd, 0xc8, 0xe1, 0x85, 0xb9, 0x86, 0xb1, 0x08, 0x64, - 0x2e, 0x57, 0x3d, 0xf6, 0x97, 0x1f, 0x36, 0x2b, 0x0c, 0x5e, 0x91, 0xe9, 0xd0, 0x1d, 0xc7, 0x8c, 0xaf, 0xf2, 0xc4, - 0x31, 0x04, 0x99, 0x58, 0x2a, 0xdd, 0x70, 0x4c, 0x5c, 0x49, 0x9f, 0x89, 0x21, 0xdb, 0x0d, 0xcf, 0xcc, 0x85, 0x6e, - 0xb6, 0x7f, 0x38, 0xb7, 0x73, 0x4e, 0xb8, 0xd1, 0x4a, 0x1a, 0x6d, 0xd4, 0x33, 0x43, 0x55, 0x5d, 0x30, 0xbf, 0x87, - 0x4e, 0x4b, 0x8b, 0x9d, 0xab, 0x77, 0x37, 0x7c, 0x9d, 0xaf, 0x8c, 0xbf, 0xc5, 0xaa, 0xd0, 0x8a, 0x0c, 0xb7, 0x5b, - 0xc8, 0x9b, 0x33, 0x3d, 0xf4, 0x8a, 0x5c, 0xa8, 0x0e, 0x7f, 0x51, 0x57, 0x98, 0x07, 0x3b, 0xa3, 0x86, 0xf0, 0xe8, - 0xf7, 0x3a, 0x03, 0xe5, 0x1f, 0x4c, 0x4c, 0xe6, 0x2c, 0xb9, 0xa1, 0x85, 0x88, 0x7f, 0x7c, 0x21, 0x4c, 0xac, 0xaa, - 0x3d, 0x18, 0xc8, 0x9e, 0xa9, 0xb8, 0x07, 0xb7, 0x26, 0x7c, 0xcc, 0xd9, 0x28, 0xdd, 0x8b, 0x7e, 0x6c, 0x88, 0xc6, - 0x8f, 0xd1, 0x8f, 0xe0, 0xee, 0xec, 0x5e, 0x87, 0x2c, 0xe3, 0x42, 0xf8, 0x7b, 0xac, 0x87, 0xa5, 0x4a, 0x19, 0x6b, - 0xaf, 0x5b, 0x0e, 0x2f, 0xa4, 0xde, 0x64, 0xf1, 0x43, 0x47, 0xac, 0x6d, 0x0a, 0xd6, 0x21, 0x25, 0x85, 0x67, 0x57, - 0xcc, 0xad, 0x16, 0x73, 0x97, 0x5a, 0xc2, 0x5f, 0x5f, 0x3d, 0x2c, 0x55, 0xd0, 0x70, 0x10, 0xba, 0xd2, 0x16, 0x12, - 0x60, 0xe0, 0x52, 0xfa, 0x74, 0xba, 0x33, 0x89, 0xcc, 0xb2, 0x18, 0xde, 0x3d, 0xa8, 0x60, 0xfe, 0x3b, 0xdb, 0x08, - 0xab, 0x02, 0x97, 0x2b, 0x55, 0xd4, 0x4b, 0x49, 0x20, 0x00, 0x7d, 0xe9, 0x3d, 0x28, 0x2f, 0x8a, 0x6e, 0xa3, 0x21, - 0x41, 0x0b, 0x4b, 0xcd, 0xb5, 0x2a, 0xa6, 0xfb, 0xe1, 0xab, 0x86, 0xc1, 0x87, 0x77, 0x48, 0xdb, 0x78, 0x5a, 0x94, - 0x12, 0x6a, 0x77, 0xd0, 0x3e, 0x58, 0x65, 0x07, 0xe5, 0xdf, 0xc6, 0x14, 0xd9, 0xfc, 0x3e, 0xfb, 0x81, 0xba, 0x0e, - 0x07, 0xae, 0x60, 0xd5, 0x4b, 0x19, 0x05, 0x03, 0x56, 0x4e, 0x81, 0xda, 0x3b, 0xc9, 0x68, 0x36, 0x65, 0xa0, 0xee, - 0xb7, 0x45, 0xab, 0xb9, 0x3d, 0xa9, 0xfb, 0x0d, 0x19, 0x67, 0x1f, 0x61, 0x9c, 0x7d, 0x14, 0x78, 0xb1, 0x48, 0xf2, - 0x87, 0x8c, 0x35, 0x8e, 0x55, 0x53, 0xa0, 0xa3, 0x0e, 0x70, 0x67, 0xe0, 0xc0, 0x03, 0xb6, 0x28, 0x07, 0x07, 0xd4, - 0x59, 0xdc, 0xd3, 0x46, 0xe6, 0xbd, 0x3d, 0xa1, 0x76, 0x11, 0x0b, 0xdc, 0xac, 0x99, 0x69, 0x41, 0x6b, 0x85, 0x71, - 0x1e, 0x0f, 0x78, 0x9b, 0x67, 0xb5, 0xf8, 0x09, 0x1b, 0xd6, 0x54, 0xf5, 0x1b, 0x68, 0x8e, 0x6a, 0x41, 0x6e, 0x9e, - 0x18, 0x6f, 0x55, 0xd2, 0x8f, 0xa2, 0x81, 0xe5, 0x54, 0x88, 0x21, 0x19, 0xfd, 0xd6, 0x20, 0xb8, 0xd5, 0x5e, 0xad, - 0xb8, 0x47, 0x7c, 0x51, 0xf3, 0x56, 0x33, 0xb7, 0x00, 0xb4, 0x88, 0xa3, 0xf2, 0xde, 0x24, 0x02, 0xef, 0xdb, 0x32, - 0x42, 0xda, 0xb2, 0x6f, 0x9f, 0xae, 0x2c, 0x15, 0x9b, 0xef, 0xe8, 0x64, 0x90, 0x46, 0x76, 0x44, 0x11, 0xbe, 0x2e, - 0x21, 0x09, 0x57, 0x49, 0xd7, 0x2a, 0x93, 0x73, 0xa6, 0x52, 0x8e, 0xaf, 0x0b, 0x29, 0xf5, 0x95, 0xfd, 0x92, 0xb8, - 0xba, 0x93, 0x11, 0xf8, 0x7a, 0xc2, 0xf4, 0x3b, 0x5a, 0x4c, 0x18, 0xf8, 0x15, 0xf9, 0xdb, 0xb1, 0x94, 0x92, 0xcb, - 0x27, 0x22, 0xee, 0x53, 0x0c, 0xef, 0xae, 0x0e, 0xb0, 0x36, 0x21, 0x50, 0x4a, 0x5c, 0x84, 0x0b, 0xa2, 0x37, 0x85, - 0xbc, 0xbd, 0x8b, 0x0b, 0xec, 0x1c, 0x00, 0x4b, 0xa7, 0x49, 0x80, 0x7f, 0xf9, 0x98, 0x8f, 0xd5, 0x98, 0x53, 0xa3, - 0xeb, 0x77, 0xbf, 0x93, 0x6b, 0xa0, 0xb7, 0xa5, 0xa3, 0x60, 0xbf, 0x35, 0x80, 0x5c, 0xb8, 0x0b, 0x83, 0x8b, 0xaf, - 0xb0, 0xb6, 0x2c, 0x8c, 0x37, 0x16, 0x40, 0xef, 0x73, 0x06, 0x16, 0x6c, 0x98, 0x63, 0x0a, 0x8f, 0xd6, 0x4e, 0x98, - 0x0e, 0xa2, 0x82, 0x3c, 0x29, 0x9f, 0x25, 0xad, 0xd5, 0x7e, 0xcb, 0xc6, 0x70, 0x87, 0x91, 0x7c, 0xbb, 0x70, 0xe2, - 0xc0, 0x03, 0x32, 0x4d, 0x66, 0x9b, 0x7d, 0xe3, 0x23, 0x8f, 0xbc, 0x1e, 0xc7, 0xbb, 0x5a, 0x0a, 0xf3, 0xcd, 0x8a, - 0xae, 0x31, 0x84, 0xa2, 0x08, 0xfb, 0xfd, 0xaa, 0x62, 0x8a, 0x2a, 0x83, 0x36, 0x68, 0x58, 0xde, 0x88, 0x5f, 0xe0, - 0x8c, 0xa1, 0xf5, 0x42, 0xf6, 0x8e, 0xce, 0x3a, 0x9c, 0x39, 0xcc, 0x98, 0x12, 0x18, 0x95, 0x96, 0x05, 0x9d, 0x80, - 0xa3, 0x73, 0xf5, 0x41, 0x54, 0x5c, 0x1d, 0x2b, 0x00, 0x4f, 0x32, 0x85, 0x7f, 0xf2, 0x4d, 0xb0, 0xee, 0xb7, 0x6a, - 0x86, 0xa9, 0xbf, 0xe8, 0x6d, 0xd7, 0xf2, 0x65, 0x88, 0x23, 0x6d, 0x0c, 0xa1, 0x75, 0x6e, 0xef, 0x00, 0x45, 0x5c, - 0xd0, 0x8b, 0x54, 0xe3, 0x6b, 0xb5, 0x18, 0x9a, 0xf5, 0x35, 0xae, 0x63, 0xda, 0x20, 0x8a, 0x75, 0xd7, 0xc4, 0xd7, - 0xd5, 0x2b, 0xb0, 0x2a, 0x55, 0x70, 0x06, 0x09, 0x84, 0x55, 0x79, 0xd9, 0x90, 0x4a, 0x72, 0x69, 0x3a, 0x95, 0xa6, - 0xd3, 0x0a, 0xa1, 0x5c, 0x7a, 0x52, 0xde, 0xbf, 0x42, 0x08, 0x03, 0x53, 0x66, 0x07, 0x56, 0xa9, 0x2d, 0xac, 0x82, - 0x57, 0x2f, 0x36, 0xb0, 0x4a, 0xc2, 0xf1, 0x5c, 0xa2, 0x51, 0x51, 0xe1, 0x90, 0x21, 0x7d, 0x21, 0x16, 0x41, 0x02, - 0x60, 0xd1, 0xbb, 0xcc, 0xe5, 0x7d, 0x0f, 0x87, 0xc2, 0x9e, 0x64, 0x12, 0x4e, 0x37, 0xa1, 0x39, 0x3c, 0x0f, 0xac, - 0x7a, 0x1e, 0x21, 0x60, 0xe9, 0x39, 0x86, 0x67, 0xa1, 0xbf, 0xff, 0x1c, 0xad, 0xb3, 0x20, 0x4f, 0xff, 0x25, 0x4a, - 0x42, 0x63, 0xff, 0x39, 0x1e, 0x3a, 0x24, 0x0c, 0x07, 0xbe, 0x39, 0xc2, 0x0a, 0x07, 0xb7, 0x8a, 0xf8, 0x0c, 0xee, - 0xf0, 0xb1, 0x0e, 0x3d, 0x00, 0x2c, 0xa1, 0x38, 0x04, 0xf9, 0x06, 0x8a, 0x19, 0x1c, 0xd0, 0x64, 0x19, 0x5e, 0xe0, - 0x82, 0xd5, 0x42, 0x79, 0x7f, 0xdb, 0xf2, 0x52, 0x5a, 0xed, 0x92, 0xd7, 0x98, 0x03, 0x95, 0x9f, 0xe1, 0x85, 0xaf, - 0x30, 0xef, 0x55, 0xbb, 0x2f, 0x7c, 0xed, 0x80, 0x9e, 0x42, 0xc0, 0x48, 0xf7, 0x7b, 0x4d, 0xb8, 0xa7, 0xe8, 0x65, - 0x2e, 0x0e, 0xdb, 0x0e, 0xba, 0x17, 0x98, 0xab, 0xab, 0x2a, 0x6b, 0x0e, 0xa6, 0xd0, 0xe0, 0xa0, 0x0a, 0x67, 0x04, - 0xe6, 0xea, 0x45, 0x59, 0x70, 0x0e, 0xe2, 0x7d, 0x4f, 0x98, 0x9c, 0x32, 0x1a, 0xc0, 0x8b, 0xac, 0x7c, 0x74, 0xaa, - 0xc7, 0xc1, 0x65, 0xdc, 0xb0, 0x89, 0x2f, 0x84, 0x4f, 0x05, 0x56, 0xd2, 0x1a, 0x87, 0x46, 0x74, 0x44, 0xe7, 0x60, - 0xb6, 0x01, 0x14, 0xdc, 0x9d, 0x0f, 0x1b, 0x0b, 0x15, 0x3c, 0xc9, 0x5b, 0x7b, 0x41, 0x9b, 0x10, 0x67, 0xd2, 0x14, - 0xdc, 0x6d, 0x1b, 0x64, 0xf0, 0xe6, 0xb7, 0xff, 0x56, 0x58, 0x24, 0x18, 0x50, 0xa9, 0x49, 0x82, 0xf0, 0x04, 0xa5, - 0x91, 0x6e, 0xe5, 0x66, 0x02, 0xe9, 0x44, 0xd4, 0x8c, 0xba, 0x37, 0xce, 0x57, 0x47, 0x0d, 0x44, 0x45, 0x0d, 0x54, - 0x40, 0x0d, 0x64, 0x7d, 0xfb, 0x17, 0xb0, 0x10, 0x36, 0x42, 0x95, 0x08, 0x02, 0x22, 0xcc, 0xb5, 0xe1, 0x03, 0x8a, - 0x24, 0x84, 0xbc, 0x01, 0x54, 0x4c, 0xc9, 0x4b, 0x30, 0x1a, 0x87, 0xd7, 0x7b, 0xc0, 0xfd, 0xd2, 0x32, 0x0c, 0x9e, - 0x53, 0x30, 0xf9, 0x6f, 0x7d, 0x3e, 0x54, 0x2f, 0x57, 0x07, 0x21, 0xfc, 0x02, 0x62, 0x45, 0x38, 0xfe, 0xe2, 0x17, - 0x20, 0x9b, 0x0a, 0xcb, 0x83, 0x03, 0x09, 0x02, 0x3f, 0x44, 0x11, 0x0e, 0x78, 0x86, 0x97, 0xd9, 0x06, 0xd1, 0xf3, - 0xb3, 0x52, 0xd5, 0xac, 0x64, 0x30, 0xab, 0xc2, 0xd3, 0x38, 0xba, 0x26, 0x0c, 0x04, 0x17, 0x6a, 0xf7, 0x0d, 0x42, - 0xa0, 0x6c, 0xb9, 0x31, 0x74, 0xe9, 0x29, 0x98, 0x8f, 0xc6, 0xd1, 0x5b, 0x06, 0x0f, 0x0b, 0x1b, 0x93, 0x7f, 0xa6, - 0x59, 0xa6, 0x0d, 0xf3, 0xd8, 0x08, 0x9c, 0xd4, 0x29, 0x4a, 0x3e, 0x4b, 0x2e, 0xe2, 0xa8, 0x79, 0x19, 0xa1, 0x06, - 0xfc, 0xdb, 0xe0, 0xa8, 0x4b, 0x13, 0x3a, 0x1a, 0xf9, 0xe0, 0x37, 0x19, 0x31, 0x9b, 0x6c, 0xb5, 0x12, 0x15, 0x41, - 0x4f, 0xec, 0x06, 0x03, 0x56, 0xe2, 0x05, 0xb0, 0x0f, 0x96, 0x83, 0x25, 0xef, 0x44, 0xac, 0xfc, 0x29, 0x85, 0xc1, - 0xea, 0x39, 0x43, 0x08, 0x67, 0x41, 0xcc, 0xc6, 0xff, 0x7c, 0xa6, 0xe1, 0xfa, 0xf9, 0xf9, 0x3a, 0x46, 0x44, 0xfa, - 0x20, 0x72, 0x35, 0x76, 0x44, 0x04, 0x61, 0xcb, 0x74, 0xdf, 0x95, 0xf9, 0xc1, 0x5b, 0x57, 0x0f, 0x6c, 0xb8, 0x38, - 0x30, 0xa0, 0x46, 0x81, 0xd1, 0x0a, 0xce, 0x49, 0x39, 0x70, 0x50, 0x42, 0x68, 0x56, 0xc4, 0x53, 0x72, 0x09, 0x91, - 0xf0, 0x32, 0xd4, 0x05, 0xc3, 0x82, 0x40, 0x82, 0x9a, 0x82, 0x04, 0x95, 0xf9, 0xda, 0x23, 0x98, 0x75, 0x6e, 0x66, - 0x3b, 0x45, 0x5d, 0x17, 0xe4, 0xe7, 0x17, 0x1d, 0x8f, 0x80, 0xa5, 0x3d, 0x38, 0x28, 0x20, 0x82, 0x18, 0x50, 0xf0, - 0x52, 0x02, 0x0c, 0xc2, 0xf1, 0x15, 0x1b, 0x1a, 0xf0, 0xb9, 0x36, 0x5e, 0x07, 0xc6, 0xd6, 0xa7, 0x0c, 0x72, 0xf1, - 0xac, 0xda, 0xd3, 0x84, 0x90, 0xfd, 0x56, 0x4f, 0xa7, 0xdb, 0x11, 0x12, 0x7b, 0x1f, 0xb5, 0x09, 0x34, 0xe6, 0x48, - 0x77, 0xb5, 0x31, 0x5f, 0xd5, 0xf4, 0x88, 0xd5, 0x24, 0xa4, 0x0b, 0xd2, 0xe5, 0xf9, 0xb4, 0x67, 0x70, 0xc5, 0x2a, - 0x8d, 0x1c, 0x5c, 0x80, 0x3e, 0x1b, 0x10, 0xa0, 0x40, 0xa5, 0xa9, 0x44, 0x51, 0xc4, 0x45, 0x52, 0xb2, 0x61, 0x98, - 0x41, 0x98, 0xc2, 0x6a, 0x25, 0xe8, 0xc6, 0x1a, 0x00, 0xef, 0xcc, 0xec, 0x9f, 0xd2, 0x07, 0x9b, 0xae, 0xbd, 0x79, - 0x04, 0x10, 0x90, 0xfd, 0x76, 0xc9, 0xae, 0x8b, 0x8d, 0xca, 0x2c, 0xac, 0x65, 0x6c, 0xe5, 0xb6, 0x3d, 0xc6, 0xde, - 0x89, 0x6d, 0x3e, 0x01, 0x42, 0xd4, 0x96, 0x4c, 0x23, 0x44, 0x48, 0x2c, 0x62, 0x5d, 0x1b, 0xb2, 0xd1, 0x86, 0xc2, - 0x53, 0x89, 0x1c, 0xb8, 0x44, 0x13, 0x24, 0xdf, 0x71, 0x09, 0x0e, 0xe1, 0x85, 0x47, 0xf8, 0x5b, 0x60, 0x91, 0x0a, - 0xcc, 0xb0, 0x5c, 0xad, 0xa0, 0x9e, 0xc7, 0xfb, 0x6c, 0x33, 0x38, 0xa9, 0xdc, 0x18, 0xbb, 0xb4, 0x13, 0x8f, 0xcb, - 0x26, 0x24, 0xce, 0xa0, 0x5f, 0x5f, 0x11, 0xf5, 0xf6, 0xdb, 0xe9, 0x13, 0xff, 0x5e, 0x99, 0xdb, 0x81, 0xd8, 0xb0, - 0xde, 0x60, 0xf5, 0x01, 0xb4, 0xfc, 0x9f, 0xcc, 0x3f, 0x54, 0x16, 0xdc, 0x24, 0xa8, 0xcd, 0x45, 0xec, 0xb2, 0x2e, - 0x62, 0xa4, 0xb6, 0xb8, 0x3b, 0x84, 0xf8, 0x7f, 0xb6, 0xa2, 0x18, 0xf0, 0xa4, 0xe2, 0x9f, 0x63, 0xd4, 0x85, 0x50, - 0xd4, 0xd6, 0xc3, 0x06, 0x28, 0xed, 0x72, 0x5d, 0x89, 0x91, 0x21, 0x81, 0x7c, 0xeb, 0xc2, 0x0b, 0x9a, 0x93, 0x48, - 0x81, 0x9c, 0x1c, 0x44, 0x25, 0xcd, 0x36, 0x84, 0xb9, 0xee, 0x16, 0x8e, 0x99, 0xab, 0x0d, 0x5a, 0xc4, 0x2f, 0x80, - 0x9d, 0xe1, 0x46, 0xb2, 0x74, 0xe0, 0x53, 0x35, 0xf0, 0xf9, 0x35, 0x37, 0x14, 0x45, 0xa1, 0xde, 0x3b, 0xfb, 0xc8, - 0x1c, 0xfc, 0x4e, 0x03, 0xf1, 0x91, 0x3a, 0x1d, 0xc9, 0x46, 0xa8, 0x35, 0x67, 0xc7, 0xcb, 0x36, 0x23, 0x0c, 0x0a, - 0x1b, 0xbd, 0xaf, 0x42, 0x56, 0xb1, 0xb3, 0x53, 0x11, 0xcc, 0xe9, 0xab, 0xaa, 0x9c, 0x53, 0xb9, 0x65, 0x54, 0x4b, - 0x4d, 0x03, 0x44, 0xb8, 0xf2, 0x89, 0xe4, 0x51, 0x66, 0xc2, 0x3f, 0x18, 0x8c, 0xab, 0x47, 0x0a, 0x7f, 0xb4, 0x2b, - 0x76, 0xc8, 0x76, 0x74, 0xb8, 0x8d, 0xa0, 0x79, 0xa1, 0x82, 0x07, 0x1c, 0x95, 0x2c, 0x21, 0x52, 0xe4, 0x72, 0x5f, - 0xd5, 0x4c, 0xd9, 0xae, 0x23, 0x84, 0x90, 0xf6, 0x38, 0xeb, 0x86, 0x56, 0x0f, 0x3d, 0x52, 0x45, 0x39, 0xdc, 0xa2, - 0xb9, 0x2e, 0x40, 0x85, 0x11, 0x48, 0x97, 0x5f, 0xd8, 0x5d, 0x2a, 0x21, 0x7a, 0xf9, 0xda, 0x85, 0x30, 0x76, 0x56, - 0x96, 0xb8, 0x30, 0xa3, 0xb6, 0x61, 0x74, 0xdd, 0xc6, 0x70, 0x36, 0x30, 0x66, 0x1a, 0x94, 0xb4, 0x20, 0xd4, 0x75, - 0x97, 0x5e, 0x64, 0x26, 0xd0, 0x63, 0x4e, 0x68, 0x83, 0xe1, 0x29, 0xd1, 0x60, 0xd9, 0x54, 0x80, 0x05, 0xdf, 0xb2, - 0x48, 0xad, 0xcd, 0x26, 0x8b, 0x3f, 0xea, 0xd8, 0x3c, 0xed, 0x97, 0x57, 0xcc, 0x73, 0xe1, 0xa8, 0xdb, 0xf3, 0xcc, - 0xc7, 0xa3, 0x7b, 0xfa, 0xe6, 0xea, 0xc5, 0xcb, 0xd7, 0xaf, 0x56, 0xab, 0x36, 0x6b, 0xb6, 0x4f, 0xf0, 0x4f, 0xba, - 0x8c, 0x07, 0x5b, 0x46, 0x01, 0x3a, 0x38, 0xd8, 0xe7, 0xc6, 0x85, 0xe7, 0x0b, 0x9f, 0x43, 0xdc, 0x20, 0x3d, 0xc0, - 0x59, 0x51, 0xc6, 0x04, 0xb9, 0x8d, 0x7a, 0xd1, 0x5d, 0x04, 0x4a, 0xa8, 0x8a, 0xfc, 0x7d, 0xd8, 0x9c, 0xfd, 0x1e, - 0x04, 0x26, 0x82, 0xfa, 0x10, 0x01, 0x04, 0xe2, 0x95, 0xe2, 0x82, 0x30, 0x9f, 0x00, 0x51, 0xbc, 0x17, 0xc0, 0x99, - 0x9a, 0xa8, 0x55, 0x0b, 0x15, 0x17, 0x40, 0x12, 0x6d, 0x38, 0x4a, 0x7a, 0x64, 0x02, 0x78, 0x43, 0x50, 0x4a, 0xfb, - 0xab, 0x9b, 0x3b, 0x77, 0xa9, 0x1c, 0xf5, 0x5a, 0x69, 0x8e, 0xa7, 0xee, 0x73, 0x0a, 0x9f, 0xd3, 0xae, 0x3f, 0x1d, - 0xc4, 0x61, 0x8e, 0x17, 0x44, 0x1c, 0xfa, 0x67, 0x11, 0x97, 0xf3, 0x82, 0x7d, 0xe5, 0x72, 0xa1, 0xd2, 0xe5, 0x6d, - 0x2a, 0x93, 0xdb, 0xe6, 0xe8, 0x30, 0x2e, 0x92, 0xdb, 0xa6, 0x4a, 0x6e, 0x11, 0xbe, 0x4b, 0x65, 0x72, 0x67, 0x53, - 0xee, 0x9a, 0x0a, 0x6e, 0xbe, 0xb0, 0x80, 0x43, 0xd1, 0x16, 0x6d, 0x2c, 0x36, 0x8b, 0xda, 0x14, 0x57, 0x34, 0xc0, - 0xe0, 0xdf, 0x77, 0x6c, 0xfc, 0x30, 0x7c, 0x09, 0x2e, 0x4d, 0x9a, 0xc8, 0x4f, 0x20, 0xfd, 0xb4, 0x2a, 0x03, 0xf7, - 0x29, 0x69, 0x75, 0xa7, 0x17, 0xa2, 0xd9, 0xee, 0x36, 0x1a, 0x53, 0xd8, 0xbb, 0x19, 0xc9, 0x7d, 0xb1, 0x69, 0xc3, - 0xc4, 0xd7, 0xd9, 0xcf, 0x56, 0xab, 0xfd, 0x1c, 0x99, 0x0d, 0x37, 0x61, 0xb1, 0xee, 0x4f, 0x07, 0xb8, 0x85, 0x9f, - 0x67, 0x08, 0x2d, 0x59, 0x7f, 0x3a, 0x20, 0xac, 0x3f, 0x6d, 0xb4, 0x07, 0xd6, 0xd0, 0xce, 0x6c, 0xc5, 0x35, 0x84, - 0xd0, 0x9c, 0x0e, 0x8e, 0x4c, 0x49, 0xe9, 0xf2, 0xed, 0x17, 0xad, 0x02, 0xfa, 0xa9, 0x5a, 0xf0, 0x32, 0x89, 0x3b, - 0xd0, 0x17, 0xbd, 0xb0, 0x4f, 0xb7, 0x16, 0xe4, 0xf8, 0xa8, 0x72, 0xb5, 0xa7, 0x08, 0x9b, 0x9e, 0xd4, 0x61, 0x71, - 0x68, 0x9a, 0x71, 0x5d, 0x4a, 0xf7, 0x1d, 0x6a, 0x46, 0x3e, 0x3a, 0x58, 0x00, 0x82, 0x54, 0xf0, 0xc8, 0x0a, 0x17, - 0x4e, 0x29, 0x84, 0x8b, 0x83, 0xca, 0x16, 0x4c, 0x72, 0xd2, 0xea, 0xe6, 0xc6, 0xd2, 0x3f, 0x77, 0x11, 0x4d, 0x29, - 0xa6, 0x24, 0xf3, 0x25, 0x73, 0x03, 0x16, 0xba, 0x49, 0x79, 0xa6, 0xa0, 0x57, 0x1a, 0xe0, 0x11, 0x81, 0x78, 0x48, - 0xdd, 0xc2, 0x18, 0x78, 0xc5, 0xd3, 0x66, 0xd1, 0x67, 0x03, 0x74, 0x74, 0x8c, 0x69, 0xff, 0x53, 0x36, 0x6f, 0xc3, - 0x63, 0x81, 0x9f, 0x06, 0x64, 0xda, 0x94, 0x65, 0x82, 0x80, 0x84, 0x51, 0x53, 0x1e, 0xc2, 0x5e, 0x42, 0x38, 0xb3, - 0x15, 0xb3, 0x3e, 0x1b, 0x34, 0xa7, 0x65, 0xc5, 0x8e, 0xaf, 0xd8, 0x90, 0x65, 0x82, 0xad, 0xd8, 0x70, 0x15, 0xc3, - 0xd7, 0x19, 0x0c, 0x08, 0x42, 0x00, 0x30, 0x00, 0x80, 0x46, 0x41, 0x34, 0x5f, 0xac, 0x88, 0xdf, 0xec, 0xf6, 0x1e, - 0xbf, 0x05, 0x16, 0x68, 0xb5, 0xfd, 0xbf, 0x0b, 0x65, 0xc0, 0x9e, 0xb2, 0x30, 0x31, 0x73, 0x0b, 0xab, 0xa2, 0x03, - 0xa8, 0x94, 0x08, 0x53, 0x18, 0xc8, 0xec, 0x67, 0x06, 0x6a, 0x81, 0xd6, 0x20, 0xef, 0xeb, 0x41, 0x33, 0x83, 0x23, - 0x06, 0xde, 0xa1, 0x21, 0x53, 0x63, 0x4c, 0x18, 0xe7, 0x30, 0xc5, 0xcc, 0x80, 0x67, 0x9a, 0xb6, 0xd6, 0xd2, 0xc8, - 0x72, 0xbd, 0xbc, 0xf7, 0xb7, 0x8e, 0x55, 0xbf, 0x68, 0xb6, 0x07, 0x68, 0x9f, 0x10, 0xfb, 0x31, 0x80, 0x4d, 0xe6, - 0x52, 0x1b, 0xe6, 0xfb, 0xa8, 0x93, 0xda, 0x4f, 0xf8, 0x33, 0x58, 0x9b, 0x1d, 0x00, 0x3a, 0x32, 0x6c, 0xd6, 0x5f, - 0xd6, 0x54, 0x5e, 0x1f, 0x77, 0x46, 0xa9, 0xdc, 0xf5, 0xee, 0x74, 0xa0, 0x29, 0x0e, 0xbd, 0xf5, 0x70, 0xf9, 0x50, - 0x0f, 0x01, 0x33, 0x06, 0x73, 0xcb, 0x8c, 0xbe, 0x17, 0x22, 0xb9, 0x20, 0x12, 0x4b, 0x83, 0x35, 0x0c, 0xf6, 0xd6, - 0xc1, 0x81, 0xa9, 0xc6, 0x1a, 0xf0, 0x3c, 0x29, 0x02, 0xc1, 0xc0, 0x47, 0x50, 0x06, 0x34, 0x51, 0xe6, 0x36, 0x9c, - 0x7c, 0x64, 0xee, 0x17, 0x2e, 0x6f, 0x1f, 0x0b, 0xa7, 0x6d, 0x35, 0xd7, 0xe3, 0x65, 0x81, 0xbb, 0xf2, 0x5e, 0xd2, - 0x2a, 0xb8, 0x91, 0xbd, 0xc9, 0x53, 0xe6, 0x6e, 0xdd, 0x97, 0xea, 0xec, 0x6e, 0xa6, 0x53, 0x36, 0xd3, 0xd9, 0x6e, - 0x26, 0xd4, 0xcc, 0x7c, 0xcb, 0x2a, 0xd2, 0x9c, 0xac, 0x89, 0x9a, 0x53, 0xf1, 0x13, 0x9d, 0x83, 0x76, 0x94, 0xdb, - 0x7b, 0x55, 0x38, 0xb9, 0x72, 0x72, 0xb9, 0x9f, 0x1b, 0xe2, 0x8a, 0xcc, 0x85, 0x3a, 0x04, 0x78, 0x79, 0x51, 0x3e, - 0x3e, 0xc0, 0xa5, 0xf8, 0x55, 0x8e, 0x5c, 0x94, 0x53, 0x21, 0xb5, 0x14, 0x2c, 0x42, 0x06, 0x55, 0x5d, 0x0c, 0xec, - 0xa5, 0xdd, 0x7b, 0xa2, 0xc7, 0xfb, 0x55, 0xc4, 0xbc, 0x81, 0x79, 0xee, 0xe3, 0x7b, 0x9a, 0x62, 0xa7, 0x26, 0xce, - 0xc8, 0x87, 0x2c, 0xce, 0x41, 0x36, 0xeb, 0x57, 0xaf, 0xfd, 0x36, 0xda, 0xb8, 0x68, 0xc6, 0xa2, 0x67, 0x9e, 0x38, - 0xf9, 0xa1, 0x30, 0xc6, 0x01, 0xd6, 0xd1, 0x1f, 0x61, 0x6a, 0xc1, 0x9e, 0x25, 0x9e, 0x42, 0x27, 0xb7, 0x36, 0xed, - 0x2e, 0x4c, 0xbb, 0x33, 0x69, 0x1d, 0x28, 0x07, 0xa4, 0xd9, 0x95, 0xe9, 0xdc, 0xf9, 0xef, 0x3b, 0x78, 0xe9, 0x76, - 0x0d, 0x91, 0xb8, 0xe7, 0x8f, 0x8c, 0x31, 0xc4, 0x1b, 0xb0, 0x11, 0x55, 0x07, 0x07, 0x7f, 0x38, 0xef, 0xdb, 0x4a, - 0xee, 0xfb, 0x56, 0x38, 0xb0, 0x0d, 0xa6, 0xd2, 0xe5, 0x8d, 0x64, 0xb6, 0x00, 0xbb, 0xce, 0xfd, 0x6f, 0xc4, 0xc3, - 0x17, 0x21, 0xd3, 0x62, 0x5d, 0xc5, 0x5f, 0xc9, 0x51, 0xe9, 0x21, 0xaa, 0x21, 0x02, 0x69, 0x65, 0x5d, 0x1a, 0x9a, - 0x8e, 0x5e, 0x4d, 0xe9, 0x48, 0xde, 0xbc, 0x95, 0x52, 0x0f, 0xec, 0x8b, 0xdc, 0x3a, 0x81, 0x47, 0x0b, 0x6b, 0x0c, - 0xcd, 0x5d, 0xe9, 0x9d, 0x64, 0x03, 0xa2, 0xd6, 0xc7, 0x1d, 0x4a, 0x22, 0xb1, 0xa8, 0xee, 0x42, 0x38, 0xdc, 0x85, - 0x60, 0x5e, 0x06, 0x6d, 0x83, 0xd8, 0xed, 0x2e, 0x68, 0x1b, 0x38, 0x75, 0xdb, 0xc0, 0xed, 0xc1, 0x60, 0x61, 0xef, - 0xc3, 0xcb, 0xb1, 0x1c, 0x0b, 0x7f, 0x4d, 0x66, 0x1f, 0x00, 0x02, 0xb5, 0x0f, 0x2b, 0x9e, 0x38, 0x10, 0x24, 0xce, - 0x70, 0xf4, 0x3d, 0x67, 0x37, 0xd6, 0x72, 0x78, 0x36, 0x5f, 0x68, 0x36, 0x32, 0x77, 0xd4, 0xa0, 0xe2, 0xab, 0xfb, - 0x79, 0xfd, 0x94, 0xd5, 0x74, 0xe3, 0xf7, 0x20, 0x8c, 0x84, 0x53, 0x76, 0x18, 0x85, 0x84, 0x0d, 0x66, 0x55, 0xc6, - 0x6b, 0xfb, 0x0d, 0xe2, 0x3d, 0x68, 0x13, 0x4e, 0xb0, 0xa8, 0x5d, 0x50, 0x45, 0xd8, 0xc6, 0x1b, 0x0b, 0xa2, 0x3c, - 0xbc, 0xd9, 0x32, 0x9a, 0x5e, 0xae, 0x21, 0xd0, 0x71, 0x2f, 0x6a, 0x46, 0x0d, 0x96, 0xba, 0xa0, 0xcc, 0x3e, 0xc2, - 0xb8, 0xba, 0x38, 0x31, 0x71, 0xda, 0x4b, 0xbd, 0xfa, 0x6f, 0x19, 0x18, 0xe0, 0x0b, 0xf0, 0x12, 0x0b, 0xa3, 0xbb, - 0xf6, 0x75, 0x03, 0xea, 0xcb, 0x06, 0x1b, 0xa0, 0xd5, 0xaa, 0x55, 0x3e, 0x03, 0xe5, 0xae, 0xb9, 0x84, 0xbd, 0xe6, - 0x12, 0xee, 0x9a, 0x4b, 0xf8, 0x6b, 0x2e, 0x61, 0xae, 0xb9, 0x84, 0xbf, 0xe6, 0xf2, 0x20, 0xfc, 0x33, 0x88, 0xe3, - 0x18, 0x73, 0x88, 0xab, 0xa8, 0x6d, 0x64, 0x3c, 0xb8, 0xf0, 0xdc, 0x67, 0x89, 0x2a, 0x97, 0x3f, 0x8c, 0x21, 0xb7, - 0x65, 0x2b, 0x61, 0xdc, 0xa6, 0x98, 0x82, 0xc8, 0xe9, 0x07, 0x07, 0xa5, 0xbb, 0x33, 0xf8, 0xa8, 0xa7, 0x1c, 0x2f, - 0xad, 0x13, 0xed, 0x1f, 0xa0, 0x93, 0x37, 0xbf, 0x3e, 0xa6, 0x72, 0x4d, 0x84, 0x33, 0xb9, 0xdf, 0x6f, 0x7b, 0x4a, - 0xf1, 0x67, 0x66, 0xc2, 0x93, 0xf3, 0x44, 0x1b, 0x11, 0x04, 0x21, 0x4a, 0x14, 0xce, 0x88, 0xfc, 0x7f, 0xd9, 0x7b, - 0xd7, 0xe5, 0xb6, 0x91, 0x2c, 0x5d, 0xf4, 0x55, 0x24, 0x86, 0xcd, 0x02, 0xcc, 0x24, 0x45, 0x79, 0xef, 0x99, 0x88, - 0x03, 0x2a, 0xc5, 0xf0, 0xa5, 0xdc, 0xe5, 0xee, 0xf2, 0xa5, 0x2d, 0x77, 0x75, 0x55, 0x33, 0x78, 0x58, 0x10, 0x90, - 0x14, 0xe0, 0x02, 0x01, 0x16, 0x00, 0x4a, 0xa4, 0x49, 0xbc, 0xfb, 0x8e, 0xb5, 0x56, 0x5e, 0x41, 0x50, 0x76, 0xcf, - 0xec, 0xf9, 0x75, 0xce, 0x1f, 0x5b, 0x4c, 0x24, 0x12, 0x79, 0xcf, 0x95, 0xeb, 0xf2, 0x7d, 0xb4, 0xde, 0x55, 0x28, - 0x3c, 0xaa, 0xa2, 0x94, 0x5b, 0xc9, 0xab, 0x0c, 0x82, 0xd8, 0xd1, 0x0b, 0xc3, 0x9f, 0x40, 0x08, 0x41, 0x84, 0x09, - 0xbf, 0x0e, 0x33, 0xda, 0xce, 0x22, 0x9d, 0xf4, 0xdb, 0x30, 0xc3, 0x0d, 0xac, 0xe4, 0xe7, 0xaa, 0xcf, 0xf6, 0xdb, - 0x20, 0x64, 0xbb, 0x20, 0x62, 0xb7, 0xc5, 0x36, 0x28, 0x6d, 0x5f, 0x10, 0x65, 0xf8, 0x5b, 0x7a, 0xbd, 0x3c, 0x84, - 0x78, 0x9f, 0x5e, 0x9a, 0x9f, 0xa5, 0xad, 0x28, 0xc0, 0x7d, 0x84, 0x1e, 0xd5, 0x81, 0x60, 0x27, 0x3c, 0xe1, 0x01, - 0x9c, 0xac, 0x66, 0x15, 0x7f, 0x92, 0x82, 0x38, 0x51, 0x70, 0x08, 0xb8, 0xda, 0xde, 0xa4, 0x5f, 0xc1, 0xf0, 0xa5, - 0x83, 0x2d, 0x87, 0xb7, 0xc5, 0xb6, 0xc7, 0x4a, 0xfe, 0x11, 0xd8, 0xb7, 0x7a, 0x32, 0x56, 0xb7, 0x07, 0xce, 0xba, - 0x94, 0xa2, 0xe3, 0x4d, 0x71, 0x78, 0x7b, 0x3e, 0xdb, 0x6f, 0x83, 0x88, 0xed, 0x82, 0x0c, 0x6b, 0x9d, 0x34, 0xfc, - 0xaf, 0xb4, 0x75, 0xb0, 0x18, 0x61, 0xff, 0x97, 0xf5, 0xc0, 0x4b, 0x48, 0x0d, 0x05, 0x2e, 0x06, 0x1b, 0x8e, 0xd6, - 0x76, 0x99, 0x06, 0x6e, 0x6a, 0xd0, 0xeb, 0x7b, 0x0a, 0x51, 0x5e, 0x32, 0x9a, 0x1b, 0xc1, 0xba, 0x31, 0xe4, 0xe2, - 0x70, 0xdc, 0x2c, 0x87, 0xbc, 0xa4, 0xe9, 0x34, 0x08, 0xa5, 0x3b, 0xcb, 0x1a, 0x92, 0x28, 0xfb, 0x20, 0xd4, 0xae, - 0x2d, 0xfb, 0x6d, 0x60, 0xfb, 0xf2, 0x47, 0xc3, 0xd8, 0xbf, 0x58, 0x3e, 0x13, 0xd2, 0x45, 0x3c, 0x07, 0x41, 0xd4, - 0x7e, 0x9e, 0x0d, 0x37, 0xfe, 0xc5, 0xfa, 0x99, 0x50, 0x7e, 0xe3, 0xb9, 0x2d, 0x87, 0xd4, 0x59, 0x0b, 0x5f, 0x18, - 0x0f, 0x0f, 0xae, 0x0c, 0x6d, 0x87, 0x83, 0xd0, 0x7f, 0x9b, 0x35, 0x82, 0x1b, 0x1b, 0xda, 0xe7, 0x0b, 0x1f, 0xb6, - 0x36, 0x1a, 0x6b, 0x8a, 0xe9, 0x16, 0xfa, 0x37, 0x99, 0x2d, 0xed, 0x69, 0x54, 0xf2, 0xe2, 0xd4, 0x34, 0x62, 0x21, - 0x0c, 0x18, 0xfa, 0xc9, 0x7c, 0x04, 0xd5, 0xdc, 0xf1, 0x08, 0x64, 0xf2, 0x81, 0x1e, 0xac, 0x49, 0xad, 0xfa, 0x6b, - 0x98, 0xc9, 0xff, 0x23, 0x15, 0x16, 0xa3, 0xbb, 0x6d, 0x98, 0xa9, 0x3f, 0x22, 0xf9, 0x07, 0xcb, 0xf9, 0x2e, 0xf5, - 0x42, 0xed, 0xc7, 0xc2, 0x0a, 0x0c, 0x4a, 0x54, 0x0d, 0xe8, 0x81, 0x08, 0xaa, 0x32, 0x48, 0x33, 0xac, 0xce, 0x41, - 0xbf, 0x7b, 0x5a, 0x75, 0x24, 0x87, 0xb4, 0x56, 0x43, 0x2a, 0x98, 0x2a, 0x35, 0xc8, 0x0f, 0x87, 0xbb, 0x94, 0xe9, - 0x32, 0xe0, 0x92, 0x7e, 0x97, 0x2a, 0xa5, 0xf0, 0x9f, 0x08, 0x40, 0xe7, 0xe0, 0x1e, 0x5f, 0x8e, 0x81, 0x34, 0xc3, - 0xc2, 0x6f, 0xcd, 0x8e, 0xaf, 0x49, 0xb8, 0x4d, 0x82, 0x8b, 0x01, 0xce, 0xd1, 0x55, 0x58, 0xde, 0xa5, 0x10, 0x41, - 0x55, 0x42, 0x7d, 0x2b, 0xd3, 0xa0, 0xb4, 0xd5, 0x20, 0xac, 0x49, 0xa8, 0x33, 0xc9, 0x46, 0xa5, 0xed, 0x46, 0x61, - 0xb6, 0x88, 0xeb, 0x19, 0x61, 0xcd, 0xd9, 0x4c, 0x35, 0x30, 0x69, 0x38, 0x6e, 0x1a, 0xad, 0x45, 0x85, 0x9a, 0xc2, - 0xbc, 0xc6, 0x55, 0xa5, 0xaa, 0xbb, 0x39, 0xb5, 0x94, 0x96, 0xed, 0x55, 0x37, 0xc9, 0x86, 0x5c, 0x86, 0x32, 0x0c, - 0x36, 0x72, 0x04, 0x13, 0x48, 0x92, 0x33, 0x7f, 0x23, 0xff, 0x50, 0x9b, 0xae, 0x05, 0xcc, 0x31, 0x66, 0xd9, 0xb0, - 0xa0, 0x57, 0xe0, 0x1e, 0x68, 0xa5, 0xe7, 0xd3, 0xec, 0x22, 0x0f, 0x92, 0x61, 0xa1, 0x97, 0x4d, 0xc6, 0xff, 0x14, - 0x46, 0x9a, 0xcc, 0x58, 0xc9, 0x22, 0xdb, 0xd5, 0x29, 0x71, 0x1e, 0x27, 0xb0, 0x3d, 0x9a, 0xde, 0xf2, 0x7d, 0x06, - 0x51, 0x41, 0xa0, 0x60, 0xc6, 0x7c, 0xd9, 0xc5, 0x73, 0xdf, 0x67, 0x96, 0xa9, 0xfb, 0x70, 0x30, 0x66, 0x6c, 0xbf, - 0xdf, 0xcf, 0xfb, 0x7d, 0x35, 0xdf, 0xfa, 0xfd, 0xe4, 0xda, 0xfc, 0xed, 0x01, 0x83, 0x82, 0x9c, 0x88, 0xa6, 0x42, - 0x04, 0xff, 0x90, 0x3c, 0x43, 0x32, 0xba, 0xe3, 0x3e, 0xb7, 0x9c, 0x2d, 0xab, 0x23, 0x10, 0xcc, 0xc3, 0xe1, 0x52, - 0x81, 0x5d, 0x4b, 0x14, 0x09, 0x59, 0xfe, 0x33, 0x30, 0x9e, 0xb9, 0x0f, 0xb0, 0x64, 0x00, 0xc2, 0x56, 0x79, 0xba, - 0xde, 0xf3, 0x55, 0xf0, 0x4e, 0xc7, 0xbb, 0xc6, 0x8a, 0x0c, 0xc4, 0x2d, 0xb0, 0x11, 0x6b, 0xed, 0x01, 0x39, 0x53, - 0x80, 0xe3, 0xc5, 0xe1, 0x70, 0x2e, 0x7f, 0xe9, 0x66, 0xeb, 0x04, 0x2a, 0x05, 0x6e, 0x8f, 0x4e, 0x0e, 0xfe, 0x3b, - 0xd0, 0x0c, 0xca, 0x61, 0x5e, 0x6f, 0x7f, 0x67, 0x4e, 0x7e, 0x7a, 0x8a, 0x7f, 0xc2, 0x43, 0x74, 0xfa, 0xed, 0xde, - 0xfc, 0x41, 0x51, 0x79, 0x38, 0xa8, 0xc5, 0x7f, 0xce, 0x79, 0x05, 0xbf, 0xf0, 0x4d, 0x60, 0x36, 0x99, 0x7a, 0x27, - 0xdf, 0xe4, 0x39, 0x53, 0xaf, 0xf1, 0x8a, 0xc9, 0x77, 0x38, 0x9c, 0x8b, 0x51, 0xbd, 0x1d, 0x39, 0xd1, 0x4e, 0x39, - 0xc6, 0xc1, 0xe0, 0xbf, 0x88, 0xb6, 0x09, 0x01, 0x86, 0xd4, 0x2d, 0x69, 0x66, 0xe3, 0xca, 0x12, 0xcf, 0xd2, 0xf9, - 0xe5, 0xa4, 0x2e, 0x77, 0x5a, 0xf1, 0xb4, 0x07, 0x16, 0xb7, 0x35, 0x78, 0x01, 0xdc, 0x5b, 0x6c, 0x5d, 0x29, 0x38, - 0x5c, 0x40, 0x9c, 0xe2, 0x04, 0x44, 0xd0, 0x7e, 0x5f, 0xe2, 0xbd, 0x82, 0x3e, 0xe9, 0x47, 0x08, 0x86, 0xfc, 0x59, - 0x02, 0xee, 0x7a, 0xbd, 0x1a, 0xe3, 0x7b, 0x29, 0x04, 0xd7, 0x67, 0x1a, 0x80, 0x16, 0xfc, 0x2e, 0x1f, 0xcb, 0xe9, - 0x37, 0x11, 0x78, 0xb6, 0xec, 0x4d, 0x94, 0xbb, 0x0d, 0x4f, 0xfb, 0x47, 0x0b, 0x01, 0x58, 0x8a, 0x67, 0x4a, 0xb0, - 0x20, 0xa7, 0x98, 0x8b, 0xff, 0x17, 0x7c, 0xc4, 0x7c, 0x4f, 0xba, 0x88, 0xad, 0xb7, 0x4f, 0x2e, 0x0c, 0x24, 0xd0, - 0x74, 0x00, 0x7e, 0xbc, 0x0a, 0xe8, 0xca, 0xf8, 0xf9, 0x59, 0xd6, 0x63, 0x7d, 0xfc, 0xa7, 0xe0, 0x3e, 0xfd, 0x4c, - 0xe1, 0xa3, 0xc3, 0x71, 0x95, 0x8e, 0x76, 0x94, 0x82, 0xe8, 0xe8, 0xf6, 0xf9, 0x94, 0x67, 0xdf, 0x55, 0x40, 0x6e, - 0x39, 0x6a, 0x4f, 0x05, 0x60, 0xb1, 0xa5, 0x23, 0xf0, 0x69, 0x96, 0x4f, 0xc8, 0xf7, 0x7a, 0x2a, 0xae, 0x2e, 0x75, - 0xba, 0xb8, 0x1e, 0x4f, 0xe1, 0x7f, 0x20, 0xf6, 0xb0, 0x4c, 0x91, 0x1d, 0xbb, 0x2e, 0x7e, 0x10, 0x6f, 0x6b, 0x3b, - 0xfa, 0x63, 0x07, 0x91, 0x8e, 0x7b, 0x72, 0xa1, 0xbe, 0x84, 0x54, 0x72, 0xa1, 0x6e, 0x20, 0x76, 0xa1, 0xc6, 0x3b, - 0x2e, 0x62, 0xad, 0xbf, 0xab, 0x51, 0xb0, 0x12, 0x70, 0xa6, 0xbd, 0x03, 0x83, 0x0d, 0xac, 0x5b, 0x96, 0xc1, 0xdf, - 0x70, 0x4d, 0x13, 0xb8, 0x61, 0x91, 0xf5, 0xde, 0x60, 0x2b, 0xbd, 0x03, 0x47, 0xcb, 0xc4, 0xb9, 0x94, 0x64, 0x65, - 0x8b, 0x8c, 0xab, 0x47, 0x21, 0x55, 0xd3, 0xfd, 0xad, 0xa8, 0x1f, 0x84, 0xc8, 0x83, 0x55, 0xca, 0xa2, 0x62, 0x05, - 0x32, 0x7b, 0xf0, 0xf7, 0x90, 0x91, 0xa3, 0x1c, 0x38, 0x0a, 0xfd, 0xbd, 0x09, 0x74, 0x9e, 0xbf, 0x86, 0x3a, 0x8f, - 0x04, 0x5b, 0xa9, 0x87, 0xc2, 0xca, 0x0b, 0x88, 0x0e, 0xb6, 0x30, 0x56, 0x79, 0x12, 0x2a, 0x36, 0x65, 0x22, 0x8f, - 0x83, 0x5a, 0x02, 0xc6, 0x0a, 0x82, 0x39, 0xcb, 0xa5, 0x0b, 0x52, 0xd5, 0xe8, 0x61, 0x91, 0xb9, 0x9f, 0x0a, 0xca, - 0xff, 0x54, 0xe5, 0x84, 0xeb, 0xcb, 0x10, 0xe0, 0x68, 0x9f, 0x82, 0x28, 0x31, 0xd6, 0x2f, 0x5a, 0xbc, 0x93, 0x99, - 0xb3, 0xa9, 0xed, 0x25, 0xc8, 0xd8, 0x0e, 0xbf, 0x42, 0x68, 0xb5, 0x50, 0x64, 0xd1, 0x70, 0xc1, 0x74, 0x7b, 0x4a, - 0xab, 0xee, 0x61, 0xc3, 0xb3, 0xd2, 0x43, 0xa5, 0xbe, 0x8d, 0x09, 0x2c, 0xab, 0x94, 0xe1, 0xdb, 0x09, 0x55, 0x27, - 0x06, 0x15, 0xeb, 0x86, 0x2d, 0xe1, 0x10, 0x8b, 0x49, 0x63, 0x9d, 0x0d, 0x78, 0xc4, 0x12, 0xf8, 0x67, 0xc3, 0xc7, - 0x6c, 0xc9, 0xa3, 0xc9, 0xe6, 0x6a, 0xd9, 0xef, 0x97, 0x5e, 0xe8, 0xd5, 0xb3, 0xec, 0x69, 0x34, 0x9f, 0xe5, 0x73, - 0x1f, 0x15, 0x17, 0x93, 0xc1, 0x60, 0xe3, 0x67, 0xc3, 0x21, 0x4b, 0x86, 0xc3, 0x49, 0xf6, 0x14, 0x5e, 0x7b, 0xca, - 0x23, 0xb5, 0xa4, 0x92, 0xab, 0x0c, 0xf6, 0xf7, 0x01, 0x8f, 0x7c, 0xd6, 0xf9, 0x69, 0xd9, 0x74, 0xe9, 0x7e, 0x66, - 0x75, 0x40, 0xa9, 0x3b, 0xc0, 0xc6, 0xdb, 0x06, 0x1d, 0xf9, 0xb7, 0x3b, 0xa4, 0xd4, 0x4d, 0x06, 0x60, 0x37, 0x1a, - 0xe0, 0x90, 0xa9, 0x5e, 0x8a, 0xac, 0x5e, 0xca, 0x54, 0x2f, 0xc9, 0xca, 0x25, 0x58, 0x48, 0x4c, 0x95, 0xdb, 0xc8, - 0xca, 0x2d, 0x1b, 0xae, 0x87, 0x83, 0xad, 0x15, 0x97, 0xcd, 0x1d, 0xdc, 0x17, 0x56, 0x14, 0xf8, 0x7f, 0xcb, 0x16, - 0xec, 0x5e, 0x1e, 0x03, 0xef, 0xd0, 0x31, 0x09, 0x2e, 0x10, 0xf7, 0xec, 0x16, 0xec, 0xb0, 0xf0, 0x17, 0x5c, 0x27, - 0xc7, 0x6c, 0x87, 0x8f, 0x42, 0xaf, 0x60, 0xb7, 0x3e, 0x01, 0xed, 0x82, 0xad, 0x01, 0xb2, 0xb1, 0x2d, 0x3e, 0xba, - 0x3b, 0x1c, 0xde, 0x79, 0x3e, 0x7b, 0xc0, 0x1f, 0xe7, 0x77, 0x87, 0xc3, 0xce, 0x33, 0xea, 0xbd, 0x1b, 0x9e, 0xb0, - 0x0f, 0x3c, 0x99, 0xdc, 0x5c, 0xf1, 0x78, 0x32, 0x18, 0xdc, 0xf8, 0x0b, 0x5e, 0xcf, 0x6e, 0x40, 0x3b, 0x70, 0xbe, - 0x90, 0xba, 0x66, 0xef, 0x96, 0x67, 0xde, 0x02, 0xc7, 0xe6, 0x16, 0x8e, 0xde, 0x7e, 0xdf, 0xbb, 0xe3, 0x91, 0x77, - 0x4b, 0x2a, 0xa6, 0x15, 0x57, 0x1c, 0x6f, 0x5b, 0xdc, 0x4f, 0x57, 0x3c, 0x84, 0x47, 0x58, 0x95, 0xe9, 0x4d, 0xf0, - 0xc1, 0x67, 0x2b, 0xcd, 0x02, 0xf7, 0x80, 0x39, 0xd6, 0x64, 0x27, 0x34, 0x13, 0x7f, 0x85, 0xfd, 0x73, 0xa3, 0xfa, - 0x87, 0xe6, 0x7f, 0xa9, 0xfb, 0x09, 0xdc, 0xbe, 0xc8, 0x82, 0xc4, 0x3e, 0xf0, 0x1b, 0x76, 0xcf, 0x0d, 0xdb, 0xec, - 0x99, 0x29, 0xfb, 0x44, 0xa9, 0xf1, 0x23, 0xa5, 0xae, 0x2d, 0xc3, 0x4a, 0xe6, 0xee, 0xcb, 0x08, 0x1c, 0x0e, 0xc8, - 0x4f, 0x77, 0x88, 0x83, 0xd0, 0xba, 0xc9, 0x6a, 0xae, 0x28, 0xe7, 0x42, 0x5b, 0x66, 0x5e, 0x0e, 0x2c, 0x66, 0x29, - 0x85, 0xc6, 0x02, 0x00, 0xc1, 0xa4, 0xd0, 0xda, 0x7b, 0x19, 0x40, 0x4e, 0xd0, 0xf0, 0xc7, 0xe6, 0xaa, 0x28, 0x6b, - 0xd9, 0x92, 0x10, 0x65, 0xbb, 0x1e, 0x5e, 0x22, 0x64, 0x5a, 0xbf, 0x7f, 0x4e, 0x24, 0x6b, 0x93, 0xea, 0xaa, 0x46, - 0x4b, 0x40, 0x45, 0x96, 0x80, 0x89, 0x5f, 0x69, 0x3e, 0x01, 0x78, 0xd2, 0xf1, 0xa0, 0x7a, 0xca, 0x6b, 0x26, 0x88, - 0x6c, 0xa3, 0xf2, 0x27, 0xc5, 0x35, 0x92, 0x11, 0x14, 0x4f, 0x6b, 0x95, 0xb1, 0x30, 0xcc, 0x03, 0x05, 0xe4, 0xdd, - 0xbb, 0x53, 0xdf, 0xda, 0x1f, 0x3b, 0xf6, 0x6c, 0xad, 0x42, 0x2d, 0xd4, 0x14, 0x2e, 0x39, 0x44, 0x57, 0x90, 0x81, - 0x42, 0xc6, 0x93, 0xd7, 0x83, 0xcb, 0x49, 0x74, 0xc5, 0x05, 0x3a, 0xe3, 0xeb, 0x9b, 0x6e, 0x3a, 0x8b, 0x9e, 0x56, - 0xf3, 0x09, 0x29, 0xc9, 0x0e, 0x87, 0x6c, 0x54, 0xd5, 0xc5, 0x7a, 0x1a, 0xca, 0x9f, 0x1e, 0x82, 0xaf, 0x17, 0xd4, - 0x6b, 0xb2, 0x4a, 0xf5, 0x53, 0xaa, 0x94, 0x17, 0x0d, 0x2f, 0xfd, 0xa7, 0x95, 0xdc, 0xf7, 0x80, 0xb4, 0x96, 0x97, - 0x5c, 0xbe, 0x1f, 0x21, 0xc6, 0x88, 0x1f, 0x78, 0x25, 0x8f, 0x58, 0xa8, 0xa6, 0x70, 0xcd, 0x23, 0x04, 0x79, 0xcb, - 0x74, 0xf0, 0xb7, 0x9e, 0x38, 0xdd, 0x9f, 0x28, 0xed, 0xe2, 0x0b, 0x8b, 0xba, 0x27, 0x6b, 0xeb, 0x06, 0xe4, 0x60, - 0xc3, 0x74, 0x51, 0x90, 0x6d, 0x4a, 0x23, 0x68, 0xa3, 0xe5, 0xc0, 0x86, 0x53, 0xa9, 0x0d, 0x67, 0xae, 0x21, 0xb8, - 0xcf, 0xcf, 0xd3, 0xd1, 0x02, 0x3e, 0xa4, 0xba, 0xbd, 0xc4, 0xcf, 0x87, 0x0d, 0x8f, 0x80, 0xcc, 0x8e, 0xf8, 0xcc, - 0x26, 0x92, 0x4e, 0xea, 0x5c, 0x01, 0xbb, 0x9d, 0xbd, 0x03, 0x39, 0x62, 0xe6, 0xbe, 0x42, 0xf5, 0x2d, 0x1a, 0x70, - 0x65, 0xac, 0x7d, 0x4d, 0x32, 0x16, 0x5e, 0x95, 0xd3, 0x70, 0x00, 0x30, 0x74, 0x19, 0x7d, 0x6d, 0xb9, 0xc9, 0xb2, - 0x9f, 0x0b, 0x08, 0x82, 0x28, 0x89, 0xc7, 0x07, 0xbc, 0x2f, 0xab, 0xa1, 0x46, 0xc9, 0xc7, 0xb2, 0x91, 0x4a, 0xaf, - 0x44, 0x7f, 0x37, 0xe6, 0x12, 0x03, 0xbe, 0xab, 0xda, 0x82, 0xc2, 0x79, 0x7e, 0x38, 0x9c, 0xe7, 0x23, 0xe3, 0x59, - 0x06, 0xaa, 0x95, 0x69, 0x1d, 0xc4, 0x66, 0xbe, 0x58, 0xf8, 0x8b, 0x9d, 0x93, 0x88, 0x28, 0x08, 0xec, 0x48, 0x78, - 0x10, 0xa9, 0x5f, 0x55, 0x9e, 0xee, 0x54, 0x9f, 0xed, 0x17, 0x36, 0x91, 0x5e, 0x50, 0x32, 0xf9, 0x24, 0xd8, 0xab, - 0xfe, 0x0e, 0xc2, 0x86, 0xf0, 0xe6, 0x55, 0xaf, 0xb3, 0x4c, 0xcd, 0x4a, 0x90, 0x30, 0x63, 0x8e, 0xe0, 0x71, 0xd8, - 0x69, 0x6c, 0xc3, 0x63, 0x0b, 0x8e, 0xce, 0x5b, 0xb3, 0x3b, 0xb6, 0x62, 0xb7, 0xaa, 0x4e, 0x0b, 0x1e, 0x4e, 0x87, - 0x97, 0x01, 0xae, 0xbe, 0xf5, 0x39, 0xe7, 0x77, 0x74, 0x82, 0xad, 0x07, 0x3c, 0x9a, 0x88, 0xd9, 0xfa, 0x69, 0xa4, - 0x16, 0xcf, 0x7a, 0xc8, 0x17, 0xb4, 0xfe, 0xc4, 0xec, 0xce, 0x24, 0xdf, 0x0d, 0xf8, 0x62, 0xb2, 0x7e, 0x1a, 0xc1, - 0xab, 0x4f, 0xc1, 0x8a, 0x91, 0x39, 0xb3, 0x6c, 0xfd, 0x34, 0xc2, 0x31, 0xbb, 0x7b, 0x1a, 0xd1, 0xa8, 0xad, 0xe4, - 0xbe, 0x74, 0xdb, 0x80, 0xb0, 0x72, 0xcb, 0x62, 0x78, 0x0d, 0xc4, 0x33, 0x6d, 0x24, 0x5d, 0x4b, 0x43, 0x6f, 0xcc, - 0xc3, 0x69, 0x1c, 0xac, 0xa9, 0x15, 0xf2, 0xcc, 0x10, 0xb3, 0xf8, 0x69, 0x34, 0x67, 0x2b, 0xac, 0xc8, 0x86, 0xc7, - 0x83, 0xcb, 0xc9, 0xe6, 0x8a, 0xaf, 0x81, 0xfc, 0x6c, 0xb2, 0x31, 0x5b, 0xd4, 0x2d, 0x17, 0xb3, 0xcd, 0xd3, 0x68, - 0x3e, 0x59, 0x41, 0xcf, 0xda, 0x03, 0xe6, 0xbd, 0x01, 0x11, 0x4a, 0x42, 0x6a, 0xca, 0x4d, 0xaf, 0xc7, 0xd6, 0xe3, - 0xe0, 0x8e, 0xad, 0x2f, 0x83, 0x5b, 0xb6, 0x1e, 0x03, 0x11, 0x07, 0xf5, 0xbb, 0xb7, 0x81, 0xc5, 0x17, 0xb1, 0xf5, - 0xa5, 0x49, 0xdb, 0x3c, 0x8d, 0x98, 0x3b, 0x38, 0x0d, 0x5c, 0xb0, 0x36, 0x99, 0xb7, 0x62, 0x70, 0x09, 0x59, 0x7a, - 0x31, 0xdb, 0x0c, 0x2f, 0xd9, 0x7a, 0x84, 0x53, 0x3d, 0xf1, 0xd9, 0x1d, 0xbf, 0x65, 0x09, 0x5f, 0x35, 0xf1, 0xd5, - 0x06, 0x34, 0xa2, 0x47, 0x19, 0xf4, 0x15, 0xd4, 0xcc, 0x9c, 0x57, 0x16, 0x46, 0xe5, 0xbe, 0x05, 0x07, 0x14, 0xa4, - 0x6d, 0x80, 0x20, 0x89, 0x67, 0xf7, 0x2a, 0x5c, 0xdf, 0x48, 0x61, 0xc0, 0x4d, 0x60, 0x06, 0x0c, 0x4c, 0x3f, 0x83, - 0x1f, 0x56, 0xba, 0x44, 0x88, 0xb3, 0x9f, 0x52, 0x92, 0xcc, 0xf3, 0xd7, 0x22, 0xcd, 0xdd, 0xc2, 0x75, 0x0a, 0xb3, - 0xa2, 0x40, 0xf5, 0x53, 0x52, 0x1a, 0x58, 0xa8, 0x44, 0xa6, 0x52, 0xf0, 0xcb, 0xe6, 0x3c, 0xca, 0x8e, 0xd1, 0xb9, - 0xce, 0x2f, 0x27, 0xce, 0xe9, 0xa4, 0xef, 0x3f, 0x70, 0x0c, 0x5b, 0xc8, 0xc0, 0x85, 0x3f, 0xf5, 0x84, 0x71, 0x6a, - 0x05, 0x62, 0x2a, 0x79, 0xf6, 0x14, 0x3e, 0x13, 0x5a, 0x1d, 0x5d, 0xf8, 0x7e, 0x50, 0x68, 0x93, 0x74, 0x0b, 0x92, - 0x14, 0x3c, 0x45, 0xcf, 0x39, 0x6f, 0x03, 0x95, 0x62, 0x44, 0x0b, 0x22, 0x6d, 0x2d, 0x33, 0x07, 0x69, 0x4b, 0xf3, - 0x5d, 0x13, 0x3f, 0x87, 0x05, 0x5c, 0x44, 0x0b, 0x5b, 0xc3, 0xa3, 0x2a, 0x56, 0xee, 0x4d, 0x9e, 0x23, 0x9c, 0xd1, - 0xa5, 0x4c, 0x00, 0x5c, 0xef, 0xd7, 0x61, 0xad, 0xf0, 0x8a, 0x9a, 0x45, 0x5e, 0xd4, 0xf4, 0xc9, 0x16, 0xb8, 0x8f, - 0x45, 0x89, 0x02, 0x67, 0x2d, 0x18, 0xb0, 0x15, 0x96, 0xec, 0xa4, 0xb0, 0x29, 0x5a, 0x42, 0x6f, 0x8f, 0x9f, 0x0e, - 0x6a, 0x26, 0x03, 0x68, 0x02, 0x68, 0x3c, 0xfe, 0x05, 0xa0, 0xa6, 0x37, 0xb5, 0x58, 0x57, 0x41, 0xa9, 0x94, 0x9b, - 0xf0, 0x33, 0x30, 0xcc, 0xf0, 0x43, 0x21, 0xb7, 0x89, 0x12, 0x39, 0x3f, 0x16, 0xa5, 0x58, 0x96, 0xa2, 0x4a, 0xda, - 0x0d, 0x05, 0x8f, 0x08, 0xb7, 0x41, 0x63, 0xe6, 0xf6, 0x44, 0x17, 0xad, 0x08, 0xe5, 0xd8, 0xac, 0x63, 0xa4, 0x51, - 0x66, 0x27, 0xbb, 0x4e, 0x16, 0xda, 0xef, 0xab, 0x1c, 0xb2, 0x0e, 0x58, 0x23, 0xf9, 0x7a, 0xcd, 0xa1, 0xdb, 0x46, - 0x79, 0xf1, 0xe0, 0xf9, 0x0a, 0x4e, 0x73, 0x3c, 0xb1, 0xbb, 0x5e, 0x77, 0x8a, 0x44, 0xbc, 0xc2, 0x49, 0x95, 0x8f, - 0x64, 0xe1, 0xb8, 0x73, 0xa7, 0xb5, 0x58, 0x55, 0x2e, 0xeb, 0xa9, 0xc5, 0x11, 0x81, 0x4f, 0xe5, 0xd1, 0x5e, 0x68, - 0x5b, 0x14, 0x0b, 0x61, 0xf4, 0xe8, 0x84, 0x9f, 0x94, 0xc0, 0xfa, 0x3a, 0x1c, 0x96, 0x7e, 0xc4, 0xd1, 0xef, 0x34, - 0x1a, 0x2d, 0x08, 0x69, 0x78, 0xea, 0x45, 0xa3, 0x45, 0x5d, 0xd4, 0x61, 0x76, 0x9d, 0xeb, 0x81, 0xc2, 0x30, 0x02, - 0xf5, 0x83, 0xab, 0x0c, 0x3e, 0x8b, 0x10, 0x35, 0x0f, 0x4c, 0xb3, 0x21, 0x1c, 0x75, 0x81, 0x87, 0x56, 0xd0, 0x62, - 0x66, 0x3e, 0x0a, 0x31, 0x7c, 0x48, 0x17, 0xe7, 0x4f, 0xc8, 0xca, 0x07, 0xd8, 0x1d, 0xba, 0x0b, 0xe5, 0x9c, 0xa9, - 0x18, 0xe0, 0x47, 0x01, 0xf9, 0x28, 0x01, 0x37, 0x03, 0x64, 0x8f, 0x2c, 0x01, 0xc4, 0x8a, 0xd1, 0xd1, 0xe4, 0x73, - 0xdf, 0x8b, 0x14, 0xbc, 0xb3, 0xcf, 0x72, 0x35, 0x61, 0x28, 0x7c, 0x62, 0xa0, 0x9b, 0xdf, 0xf8, 0xed, 0x79, 0x0b, - 0x46, 0x76, 0x49, 0x8a, 0xd7, 0x9a, 0xe1, 0x7e, 0x03, 0x6e, 0x47, 0x40, 0x59, 0x53, 0x1d, 0x93, 0x6c, 0xd3, 0x10, - 0xc9, 0x80, 0x19, 0x31, 0x22, 0xa8, 0x2c, 0x17, 0xfe, 0x77, 0x2f, 0x8b, 0x02, 0x07, 0x70, 0x35, 0x93, 0xc1, 0x6b, - 0x17, 0x46, 0x05, 0xc0, 0x39, 0x0d, 0x9d, 0xd2, 0x5e, 0x55, 0x1d, 0x92, 0x55, 0xf3, 0x83, 0xd9, 0xbc, 0x69, 0x98, - 0x18, 0x11, 0x44, 0x17, 0xe1, 0x04, 0xd3, 0x2b, 0xd2, 0xd7, 0x4a, 0x4e, 0x47, 0xab, 0x8e, 0xd6, 0x12, 0x13, 0x73, - 0x45, 0xf1, 0xd7, 0x80, 0xc7, 0x0d, 0x5e, 0x9d, 0xa4, 0xe9, 0x44, 0xf5, 0xe8, 0xf1, 0xeb, 0x34, 0x9d, 0x94, 0xb8, - 0x2b, 0xfc, 0x06, 0x5c, 0x34, 0xdb, 0x7c, 0xe8, 0xc7, 0x2f, 0x28, 0xe2, 0xa2, 0x06, 0x57, 0xde, 0xa9, 0xbe, 0x52, - 0x7d, 0x04, 0xb5, 0xf0, 0xc4, 0xc8, 0x5a, 0x78, 0x72, 0xc9, 0x5a, 0x0b, 0x82, 0x99, 0xcd, 0x81, 0x0b, 0xf9, 0x95, - 0x52, 0xc4, 0x9b, 0x48, 0xa8, 0xc5, 0xa0, 0xf5, 0x98, 0x39, 0xab, 0x46, 0x0b, 0x95, 0x19, 0xa1, 0x7d, 0x5b, 0x8b, - 0xce, 0x6f, 0xe4, 0xa7, 0x3c, 0xb5, 0x2f, 0xdb, 0xe3, 0x7c, 0xbc, 0x47, 0x77, 0xd5, 0x59, 0x66, 0x52, 0xc6, 0x27, - 0xb3, 0x04, 0x85, 0xbb, 0x04, 0x1b, 0x90, 0x64, 0xbf, 0xd5, 0x01, 0x32, 0x6a, 0xaf, 0xfd, 0xae, 0xb3, 0x7c, 0x75, - 0xb3, 0x35, 0x14, 0x95, 0x5a, 0x49, 0x8a, 0x83, 0x0c, 0xd7, 0x6d, 0xe5, 0xc3, 0xc5, 0x05, 0xf4, 0x8c, 0x91, 0xc8, - 0x3c, 0x7f, 0x22, 0x5f, 0x82, 0x73, 0xc6, 0x59, 0x21, 0x30, 0x61, 0xac, 0xde, 0xb5, 0x96, 0x4a, 0x43, 0x8a, 0xb1, - 0xa3, 0x51, 0x96, 0x55, 0x96, 0x2e, 0xb3, 0xb5, 0x84, 0x2d, 0xab, 0xc8, 0x2d, 0x6c, 0x99, 0xc9, 0x6a, 0x7e, 0xa8, - 0xb8, 0x83, 0xf2, 0xcd, 0xd6, 0x19, 0xdf, 0x4b, 0x64, 0xef, 0x36, 0x50, 0xc2, 0xf5, 0xe8, 0x3f, 0x90, 0x7e, 0x9b, - 0x61, 0x9c, 0x72, 0x5b, 0x49, 0x0b, 0x70, 0xfa, 0x87, 0xc3, 0x87, 0x0a, 0x83, 0x06, 0x47, 0x18, 0x47, 0xd6, 0xef, - 0xdf, 0x56, 0x5e, 0x8d, 0x89, 0x3a, 0x3e, 0xab, 0xdf, 0xaf, 0xe8, 0xe1, 0xb4, 0x1a, 0xad, 0xd2, 0x2d, 0xb2, 0x13, - 0xda, 0x58, 0xf9, 0x41, 0xad, 0x80, 0xd9, 0x5b, 0x9f, 0x4f, 0x07, 0xa0, 0x63, 0x01, 0x12, 0xcd, 0x66, 0x22, 0x31, - 0x27, 0xdd, 0x93, 0xf0, 0xf8, 0xc0, 0x02, 0x07, 0x98, 0x8a, 0xff, 0x43, 0x78, 0x33, 0xb0, 0x41, 0xa3, 0x44, 0x5f, - 0xa3, 0xab, 0xda, 0xdc, 0xe8, 0x78, 0xe9, 0x29, 0x24, 0xb2, 0x82, 0x55, 0x73, 0x5f, 0x6e, 0xe0, 0xb4, 0x87, 0x9a, - 0x43, 0x65, 0x09, 0xfe, 0xf6, 0xcb, 0xfc, 0x70, 0x58, 0x67, 0x50, 0xd8, 0x6e, 0x2d, 0xb4, 0x37, 0x66, 0xa9, 0x86, - 0x8a, 0x70, 0xd0, 0xf9, 0x4a, 0xcc, 0xea, 0x11, 0xfd, 0x3d, 0x3f, 0x1c, 0x56, 0x04, 0x06, 0x1c, 0x96, 0x32, 0x13, - 0x2d, 0x14, 0x4b, 0xeb, 0x6c, 0x46, 0x75, 0xe0, 0x81, 0x89, 0x39, 0x0b, 0x77, 0x00, 0xda, 0xa4, 0x56, 0x81, 0x5e, - 0x45, 0xf4, 0x13, 0xf7, 0x6b, 0xfb, 0xf5, 0x7a, 0x64, 0x96, 0x8e, 0xdc, 0x18, 0x0b, 0x00, 0x0e, 0x3c, 0xaf, 0x49, - 0x9e, 0x93, 0xaf, 0xa1, 0xdd, 0x93, 0x0b, 0xf9, 0x13, 0x94, 0x2d, 0x3c, 0x57, 0x4d, 0x2b, 0x8b, 0x15, 0x57, 0xd5, - 0xab, 0x0b, 0x5e, 0x99, 0x4c, 0xab, 0xb4, 0x12, 0x95, 0x12, 0x0c, 0xa8, 0x4b, 0xbc, 0xd6, 0x34, 0xa3, 0xd4, 0x46, - 0x9d, 0x89, 0x1a, 0xb0, 0xc1, 0x7e, 0xaa, 0x36, 0x3a, 0x39, 0x97, 0xcf, 0x2f, 0x8d, 0xc3, 0xa7, 0x5d, 0xbd, 0x99, - 0xa9, 0x1c, 0xf8, 0x6b, 0xe5, 0x43, 0xab, 0xc7, 0x40, 0x07, 0xe4, 0xf4, 0xc7, 0xb0, 0x98, 0xd8, 0x1d, 0x9a, 0xb7, - 0xbb, 0xcb, 0xea, 0x22, 0xbd, 0xd3, 0x94, 0xcc, 0xea, 0x2d, 0x9f, 0x59, 0x3d, 0x3a, 0xe0, 0xc5, 0x63, 0xbd, 0x57, - 0x98, 0x49, 0x04, 0x17, 0x43, 0x35, 0x89, 0xec, 0x0e, 0xb4, 0xe6, 0x51, 0xc5, 0x04, 0xf8, 0x41, 0xa9, 0x35, 0xbd, - 0xb7, 0xbb, 0x42, 0x9d, 0x52, 0x78, 0xdc, 0x5a, 0xf2, 0x03, 0x73, 0xa7, 0x5d, 0xeb, 0x7c, 0x3c, 0xbf, 0xf4, 0xfd, - 0x46, 0x9e, 0xd0, 0x66, 0x67, 0x72, 0xfa, 0x27, 0x6f, 0xf5, 0x0f, 0x53, 0x7d, 0x0b, 0xdd, 0x09, 0xfa, 0x0c, 0x5d, - 0x55, 0xdd, 0x95, 0xd8, 0xc2, 0x50, 0x4f, 0x2c, 0xf2, 0x42, 0x9e, 0xb4, 0xc6, 0x8e, 0x83, 0xbd, 0x01, 0x4e, 0xfc, - 0xf2, 0x70, 0x10, 0x57, 0xb9, 0xcf, 0xce, 0xbb, 0x46, 0x56, 0x0e, 0x60, 0x05, 0x51, 0x30, 0x6e, 0xcd, 0xc7, 0x36, - 0x48, 0x97, 0xb8, 0x1a, 0x1f, 0xbf, 0xa1, 0x58, 0x26, 0x9b, 0x88, 0x8b, 0x8b, 0xfc, 0xe9, 0x73, 0x20, 0x2d, 0xeb, - 0xf7, 0xa3, 0xeb, 0xcb, 0xe9, 0xf3, 0x61, 0x14, 0x80, 0x63, 0x97, 0xbd, 0xbc, 0x8c, 0xf9, 0xea, 0x92, 0x59, 0xa6, - 0xb0, 0xc8, 0x37, 0x03, 0xaa, 0x4b, 0x56, 0x4b, 0xd7, 0x2b, 0xc0, 0xd2, 0xe5, 0x37, 0x0f, 0x61, 0x6a, 0x40, 0x23, - 0x6b, 0xee, 0x4e, 0x73, 0x2d, 0x50, 0xea, 0x79, 0x3f, 0x33, 0xe4, 0xeb, 0x32, 0xe8, 0x0a, 0xd2, 0x3d, 0x8f, 0x48, - 0x2f, 0xf7, 0xd2, 0xe9, 0x7e, 0x5f, 0x0a, 0xb0, 0xd4, 0x97, 0xe2, 0x0b, 0x28, 0x2c, 0x1a, 0xdf, 0x08, 0xd0, 0xd6, - 0x50, 0x4d, 0x7b, 0xa5, 0xa8, 0x7a, 0x41, 0xaf, 0x14, 0x5f, 0x7a, 0x7a, 0xa8, 0xcc, 0x97, 0xa5, 0xa3, 0xff, 0x09, - 0x35, 0x17, 0x9c, 0x10, 0x33, 0x31, 0x07, 0x50, 0x09, 0xda, 0xf8, 0x56, 0x47, 0x1b, 0x9f, 0xea, 0x55, 0xdc, 0xf4, - 0x79, 0x6d, 0x2d, 0x73, 0x42, 0xd8, 0x74, 0x2f, 0x01, 0x2a, 0xf2, 0x4a, 0x78, 0x04, 0xcb, 0x2f, 0x7f, 0xc8, 0xd3, - 0x15, 0xa2, 0x75, 0xdc, 0xb3, 0xcc, 0xa5, 0xb1, 0x7f, 0x63, 0x30, 0x7d, 0x7d, 0xbb, 0x2d, 0xf2, 0x53, 0x13, 0x13, - 0xd6, 0x63, 0x45, 0xdf, 0xbc, 0x0f, 0x57, 0x02, 0x05, 0x0e, 0x25, 0x12, 0xdb, 0x54, 0xa1, 0x88, 0x07, 0x49, 0x9f, - 0x2e, 0x5a, 0x9f, 0x06, 0x98, 0x5a, 0xcb, 0x81, 0x39, 0x84, 0xab, 0xb8, 0xf0, 0xd1, 0xd3, 0xb7, 0x98, 0x85, 0xf3, - 0x89, 0xf7, 0xc9, 0x2b, 0x46, 0xe6, 0xe3, 0x3e, 0x2a, 0x95, 0xf4, 0xcf, 0xc3, 0x61, 0x56, 0xcd, 0x7d, 0x87, 0x3e, - 0xd2, 0x43, 0x95, 0x0b, 0xca, 0xde, 0x18, 0x93, 0x08, 0x94, 0xc6, 0x78, 0x1f, 0x07, 0xc7, 0x79, 0x9f, 0x06, 0x90, - 0xda, 0x27, 0x3e, 0x90, 0x92, 0xc3, 0x73, 0x8e, 0x39, 0xa1, 0xb4, 0x22, 0xac, 0xe2, 0x8b, 0x0c, 0xe5, 0xba, 0x53, - 0x0a, 0x26, 0x39, 0x24, 0x18, 0xfe, 0xaa, 0x79, 0x13, 0x2b, 0x10, 0x76, 0xcd, 0xbc, 0x1a, 0x3d, 0xa9, 0x92, 0xb0, - 0x14, 0x70, 0x54, 0x66, 0x9e, 0x61, 0x6f, 0x78, 0x62, 0x18, 0x39, 0x58, 0xee, 0x8f, 0xea, 0x44, 0xe4, 0x1e, 0x5d, - 0x60, 0x54, 0x16, 0x9e, 0x37, 0x74, 0xa5, 0x41, 0x25, 0xd9, 0xf1, 0x57, 0x5c, 0x03, 0x6a, 0x6b, 0x8c, 0x18, 0x0a, - 0x18, 0x05, 0xaf, 0xed, 0x0f, 0x21, 0x8b, 0xb2, 0xf5, 0x1b, 0x1c, 0xf3, 0x59, 0xc9, 0x5d, 0xef, 0x70, 0x16, 0x5a, - 0x42, 0x9e, 0xdc, 0x31, 0x48, 0xd3, 0x58, 0x1a, 0x01, 0x27, 0x22, 0xd9, 0xc6, 0x52, 0x38, 0x02, 0x08, 0x08, 0x74, - 0x53, 0x66, 0x18, 0xd3, 0xc1, 0xc8, 0xf3, 0xa4, 0x67, 0xbc, 0x57, 0xe1, 0x29, 0xa4, 0xc9, 0xf6, 0xf5, 0xfc, 0xbd, - 0x11, 0x64, 0xe5, 0x96, 0x73, 0x3c, 0x2c, 0xbe, 0x71, 0xf6, 0x55, 0x4e, 0x9e, 0x62, 0x96, 0x91, 0xde, 0x29, 0xe6, - 0x05, 0xfc, 0xa9, 0x2c, 0xf5, 0x39, 0x4a, 0x6f, 0x99, 0x4f, 0x56, 0x91, 0x74, 0xe9, 0x6d, 0xfa, 0xfd, 0x78, 0xa4, - 0x0e, 0x35, 0x7f, 0x1f, 0x8f, 0xe4, 0x19, 0xb6, 0x61, 0x09, 0x0b, 0xad, 0x82, 0x31, 0x80, 0x24, 0x36, 0x22, 0x1a, - 0x8c, 0xf6, 0xe6, 0x70, 0x38, 0xdf, 0x98, 0xb3, 0x64, 0x0f, 0xae, 0xaf, 0x3c, 0x31, 0xef, 0xc0, 0x97, 0x79, 0x4c, - 0x10, 0xb1, 0x99, 0xb7, 0x61, 0x35, 0x78, 0xb0, 0x83, 0xeb, 0x23, 0xb6, 0x28, 0xd6, 0x3a, 0x96, 0xca, 0x3a, 0x38, - 0xad, 0x63, 0xd3, 0x8c, 0x94, 0x22, 0xfb, 0x1c, 0xfb, 0x7b, 0x37, 0xb8, 0xba, 0x36, 0x06, 0xb5, 0xc6, 0x1d, 0xe6, - 0xce, 0xa9, 0x80, 0x7a, 0x4c, 0x57, 0x50, 0x3d, 0xab, 0xc8, 0x97, 0xdf, 0xda, 0x39, 0x20, 0x68, 0x04, 0x02, 0x17, - 0x0d, 0xb4, 0x6a, 0x97, 0x72, 0xde, 0x05, 0x84, 0xf8, 0x2e, 0x05, 0x7d, 0x3a, 0x83, 0x4d, 0x6c, 0x3e, 0x81, 0x58, - 0x34, 0xdd, 0xe7, 0x5a, 0x33, 0x5f, 0x8c, 0x68, 0x67, 0xd6, 0xdd, 0x22, 0xb7, 0x5a, 0x88, 0x64, 0xf4, 0x6c, 0x33, - 0xe1, 0xa2, 0x43, 0x39, 0x23, 0x01, 0x13, 0xb4, 0xb6, 0x52, 0xf2, 0xb9, 0xee, 0x75, 0x82, 0xf6, 0x40, 0xd2, 0xba, - 0x7f, 0xb3, 0xe8, 0x8c, 0x92, 0x93, 0xeb, 0x4d, 0xce, 0x20, 0x05, 0x0b, 0xb6, 0x97, 0x39, 0xe1, 0x06, 0xf8, 0xc4, - 0x66, 0xc9, 0x69, 0x1a, 0xe4, 0xb1, 0x30, 0x1e, 0x79, 0x6d, 0x7e, 0x59, 0x40, 0x87, 0x92, 0x45, 0x23, 0xc4, 0x03, - 0xec, 0x1c, 0x92, 0xab, 0x02, 0x75, 0xd3, 0x40, 0x57, 0xae, 0x9c, 0x29, 0xa6, 0xc0, 0x85, 0x50, 0x10, 0xb5, 0xa3, - 0x93, 0xa8, 0x9c, 0xf7, 0x49, 0x75, 0x99, 0x4f, 0x0b, 0x69, 0x1a, 0xc8, 0xa7, 0x95, 0x63, 0x1e, 0xd8, 0xd9, 0xc6, - 0x35, 0x81, 0x81, 0x4e, 0xed, 0x6b, 0x51, 0xce, 0xb1, 0x8a, 0xe8, 0x7d, 0xfe, 0xb1, 0xb2, 0xa7, 0x0f, 0x22, 0x6c, - 0x54, 0xa0, 0xb1, 0x94, 0x18, 0x1b, 0x39, 0xfe, 0x2d, 0x51, 0x36, 0x64, 0x08, 0x08, 0x21, 0x6d, 0xe4, 0xf4, 0xc3, - 0xfa, 0xf2, 0x36, 0xd3, 0xfe, 0x9f, 0x24, 0x7e, 0x1b, 0xec, 0xe5, 0xd4, 0x9f, 0x7a, 0xc4, 0xe3, 0xb5, 0x46, 0x8f, - 0x29, 0xe9, 0x36, 0xc8, 0x53, 0xe5, 0x29, 0x48, 0x26, 0x8c, 0x25, 0x04, 0x8b, 0x72, 0xc1, 0x73, 0x5e, 0x71, 0x09, - 0xf7, 0x51, 0xcb, 0x8a, 0x08, 0x55, 0x89, 0x9c, 0x3e, 0x5f, 0x01, 0xcf, 0x04, 0x04, 0x3a, 0xc6, 0x48, 0xa3, 0x0a, - 0xbe, 0x04, 0xc6, 0x3a, 0x50, 0x76, 0x9a, 0x91, 0xe0, 0xb2, 0x7b, 0x83, 0x44, 0xa9, 0xaf, 0x49, 0x49, 0xfa, 0x4e, - 0xd4, 0x78, 0x25, 0x56, 0x11, 0x09, 0x64, 0xa8, 0x21, 0x62, 0x55, 0x3d, 0x75, 0xaf, 0x8a, 0xc9, 0x60, 0x50, 0xf9, - 0x72, 0x7a, 0xe2, 0x0d, 0x0d, 0x95, 0x77, 0x5d, 0xd1, 0x4e, 0x4f, 0xb4, 0x52, 0xde, 0x42, 0x5a, 0x82, 0xa6, 0x61, - 0xa4, 0x39, 0x94, 0xba, 0x92, 0xee, 0xc6, 0x20, 0xbe, 0x64, 0xa2, 0x67, 0x3b, 0xb5, 0xa3, 0xb4, 0x25, 0xed, 0x21, - 0xa4, 0xe7, 0x2e, 0xf9, 0x98, 0x85, 0x5c, 0xdd, 0x29, 0x27, 0xe5, 0x55, 0x88, 0x4e, 0xee, 0x7b, 0x0c, 0x89, 0x40, - 0x9f, 0x73, 0x0c, 0xeb, 0xa2, 0xa1, 0xce, 0x61, 0x85, 0x98, 0x2d, 0x94, 0x30, 0x5f, 0x32, 0x9e, 0x4a, 0x06, 0x0d, - 0x80, 0x0c, 0xf8, 0xe2, 0x65, 0x60, 0xf9, 0x2b, 0x88, 0x1f, 0x6d, 0x7c, 0x38, 0xfc, 0x59, 0x53, 0x88, 0xed, 0x9f, - 0xb0, 0x19, 0xc2, 0xa3, 0x7a, 0xc0, 0x33, 0xdf, 0xc4, 0x09, 0x5a, 0x01, 0x49, 0x99, 0x1d, 0x4d, 0x64, 0xaf, 0x7a, - 0x08, 0xa7, 0xb2, 0x02, 0x75, 0x94, 0x75, 0x56, 0xc2, 0x8f, 0x30, 0xd5, 0xad, 0xc4, 0x5a, 0xa0, 0xcd, 0xd5, 0x8a, - 0xb5, 0x00, 0x0e, 0xfc, 0x1c, 0x82, 0x27, 0xf2, 0x39, 0xb8, 0x18, 0x14, 0xe0, 0x73, 0x00, 0xbc, 0xc8, 0x5d, 0x78, - 0x30, 0x8f, 0x2c, 0xab, 0x11, 0x86, 0xa3, 0x8a, 0x58, 0xbf, 0x66, 0x3b, 0xf2, 0x81, 0xdb, 0x31, 0x3e, 0xd7, 0x1e, - 0x4b, 0x96, 0x83, 0x51, 0xe6, 0x5e, 0x2d, 0xd1, 0xf3, 0x26, 0x8d, 0x9b, 0xd1, 0x93, 0x7d, 0x2d, 0xff, 0x17, 0xf4, - 0x32, 0xe8, 0x6f, 0xe1, 0x96, 0xd7, 0xfc, 0x6e, 0x41, 0xa4, 0x99, 0x5e, 0x41, 0xa4, 0x8c, 0x1a, 0x91, 0x31, 0x84, - 0x4d, 0xaa, 0x9b, 0xdb, 0xa4, 0xba, 0x10, 0xf0, 0x74, 0x44, 0xaa, 0x6b, 0x21, 0x6d, 0xe4, 0xd3, 0x3a, 0x90, 0xb1, - 0x48, 0xef, 0x7f, 0xfc, 0xcb, 0x8b, 0xcf, 0x6f, 0x7f, 0xf9, 0x71, 0xf1, 0xf6, 0xfd, 0x9b, 0xb7, 0xef, 0xdf, 0x7e, - 0xfe, 0x8d, 0x20, 0x3c, 0xa6, 0x42, 0x65, 0xf8, 0xf8, 0xe1, 0xe6, 0xad, 0x93, 0xc1, 0xf6, 0x66, 0xc8, 0xda, 0x37, - 0x72, 0x30, 0x04, 0x22, 0x1b, 0x84, 0x0c, 0xb2, 0x53, 0x32, 0xc7, 0x4c, 0xcc, 0x31, 0xf6, 0x4e, 0x60, 0xb2, 0x05, - 0x9c, 0x63, 0x99, 0x97, 0x8c, 0xc8, 0x55, 0xa1, 0xf5, 0x03, 0x5a, 0xf0, 0x0e, 0x5c, 0x64, 0xd2, 0xfc, 0xee, 0x17, - 0x82, 0xd8, 0xa7, 0x95, 0x94, 0xfb, 0x6a, 0x5b, 0xf3, 0x7c, 0x7b, 0xbf, 0x97, 0x70, 0xfe, 0x73, 0x69, 0x44, 0x2d, - 0xc0, 0x01, 0xf8, 0x1c, 0xfe, 0xb8, 0xd2, 0x96, 0x34, 0x99, 0x45, 0xfb, 0x19, 0x43, 0xd0, 0xa5, 0xc1, 0x07, 0xb1, - 0x47, 0x5e, 0xea, 0x93, 0x85, 0x04, 0xee, 0x88, 0xe1, 0xd3, 0x8a, 0xa0, 0x57, 0x8c, 0x28, 0x2e, 0xb9, 0x42, 0xa5, - 0x94, 0xfc, 0x1b, 0x65, 0x17, 0x15, 0x72, 0x56, 0xb0, 0x7b, 0x45, 0x8e, 0x8c, 0x1f, 0x04, 0x13, 0x5f, 0x0e, 0xee, - 0xbf, 0xc4, 0x3b, 0x9c, 0x29, 0x8e, 0xe4, 0x84, 0x3f, 0x64, 0x18, 0xd8, 0x9f, 0x83, 0xcf, 0xab, 0xc3, 0xbc, 0xbc, - 0xd1, 0xa7, 0xdc, 0x92, 0x8f, 0x27, 0xcb, 0x2b, 0x30, 0xd8, 0x2f, 0x55, 0x73, 0xd7, 0xbc, 0x9e, 0x2d, 0xe7, 0x6c, - 0x3f, 0x8b, 0xe6, 0xc1, 0x1d, 0x9b, 0x65, 0xf3, 0x60, 0xd5, 0xf0, 0x35, 0xbb, 0xe5, 0x6b, 0xab, 0x6a, 0x6b, 0xbb, - 0x6a, 0x93, 0x0d, 0xbf, 0x05, 0x09, 0xe1, 0x26, 0xf3, 0x80, 0xf7, 0xf8, 0xce, 0x67, 0x1b, 0x90, 0x68, 0x57, 0x6c, - 0x03, 0x17, 0xb1, 0x35, 0xff, 0xb1, 0xf2, 0x36, 0xac, 0x64, 0xe7, 0x63, 0x96, 0xe3, 0xfc, 0xf3, 0xe1, 0x01, 0xed, - 0x85, 0xfa, 0xd9, 0xa5, 0x7a, 0x36, 0x51, 0x76, 0xb3, 0xcd, 0x68, 0x71, 0x9f, 0x56, 0x9b, 0x30, 0x43, 0xcf, 0x72, - 0xf8, 0x68, 0x2b, 0x05, 0x3f, 0xbd, 0xc0, 0x2f, 0xd9, 0x51, 0x5b, 0x69, 0xdb, 0xae, 0x4a, 0x6c, 0x05, 0x2d, 0x8a, - 0xac, 0x56, 0x78, 0x60, 0xce, 0xaf, 0x61, 0x01, 0x63, 0xcf, 0x71, 0xce, 0x6b, 0x7f, 0x84, 0x8c, 0xf7, 0x0e, 0x00, - 0x5a, 0xe6, 0x38, 0xc0, 0x23, 0x56, 0x8c, 0xa2, 0xc1, 0x3b, 0xbf, 0x54, 0x56, 0x2b, 0xcd, 0x49, 0x68, 0x1b, 0xb1, - 0x6a, 0x39, 0x52, 0x35, 0x23, 0xd2, 0x07, 0xe9, 0x79, 0xdf, 0x23, 0xaa, 0xc1, 0x9e, 0xcc, 0xeb, 0xc0, 0x3e, 0xbd, - 0x6a, 0xad, 0xea, 0xce, 0xef, 0xa9, 0xd2, 0x25, 0x47, 0xb6, 0xfc, 0x74, 0x19, 0x3e, 0xa8, 0x3f, 0x25, 0xd7, 0x87, - 0x02, 0x47, 0x78, 0xac, 0x02, 0xce, 0xd7, 0x2b, 0xd1, 0xee, 0x44, 0xd8, 0x95, 0x4b, 0x40, 0x88, 0x2f, 0x69, 0x9a, - 0xe3, 0x71, 0x44, 0x13, 0x11, 0x36, 0x31, 0xfa, 0x0b, 0xbb, 0x0f, 0x25, 0x96, 0xf3, 0x5c, 0x83, 0x92, 0x4b, 0x06, - 0xef, 0x49, 0x7b, 0x0d, 0x9a, 0xe5, 0x55, 0xa9, 0xc9, 0x44, 0x0e, 0xca, 0x87, 0x43, 0x01, 0x7b, 0xa9, 0xf1, 0xd3, - 0x84, 0x9f, 0xb0, 0xbc, 0xb5, 0xb7, 0xa6, 0x14, 0x95, 0x34, 0x40, 0x05, 0x3e, 0x66, 0xf0, 0xbf, 0x3b, 0x43, 0x2c, - 0x98, 0xa2, 0xe3, 0x87, 0x33, 0x31, 0xb7, 0x9e, 0x5b, 0x65, 0x1d, 0x65, 0x6b, 0x94, 0x13, 0xf0, 0x6f, 0xa9, 0x8e, - 0x93, 0x44, 0x38, 0xf5, 0x1e, 0x71, 0x51, 0xf7, 0x72, 0x88, 0xba, 0x61, 0x6f, 0x2b, 0x1d, 0x6c, 0x39, 0x4d, 0x83, - 0x23, 0xf1, 0x2b, 0xf5, 0xd9, 0x87, 0xcc, 0xe2, 0x51, 0x47, 0x36, 0xa2, 0x24, 0x8d, 0x63, 0x91, 0xc3, 0xf6, 0xbe, - 0x90, 0xfb, 0x7f, 0xbf, 0x0f, 0xe1, 0xa4, 0x55, 0x90, 0x94, 0x9e, 0x40, 0x44, 0x38, 0x3a, 0xfc, 0x88, 0xf0, 0x44, - 0xaa, 0x0a, 0x9f, 0xd4, 0x27, 0x6e, 0xcc, 0xee, 0x85, 0x39, 0xaa, 0xb7, 0x00, 0xc3, 0x58, 0x6f, 0x2d, 0x42, 0x12, - 0xad, 0x34, 0xa3, 0xad, 0x07, 0xc4, 0x88, 0x0f, 0x6b, 0x8b, 0x0c, 0xc6, 0xda, 0x92, 0x48, 0x00, 0xbf, 0x23, 0x21, - 0x43, 0xdb, 0x46, 0x60, 0xc6, 0xf0, 0x76, 0x56, 0x5c, 0xba, 0x0e, 0xdb, 0x9c, 0xc3, 0x17, 0xb2, 0xd0, 0xac, 0x23, - 0x4a, 0x13, 0x84, 0xfc, 0x03, 0x4e, 0x16, 0x0a, 0xa3, 0x79, 0x7d, 0x94, 0x4e, 0x12, 0xeb, 0x87, 0xae, 0x52, 0xc1, - 0x66, 0x73, 0x83, 0xfa, 0xb2, 0xa3, 0xe4, 0x57, 0xe0, 0xa4, 0xe3, 0x24, 0x8b, 0x1c, 0x44, 0x2d, 0x2a, 0xe7, 0x26, - 0x09, 0x4b, 0xbb, 0x3a, 0xd5, 0x66, 0xbd, 0x2e, 0xca, 0xba, 0x7a, 0x2d, 0x22, 0x45, 0xef, 0xa3, 0x1e, 0x3d, 0x91, - 0x90, 0x0a, 0xad, 0x4a, 0xed, 0xf2, 0x08, 0xdc, 0x36, 0xb5, 0x62, 0x5b, 0x2e, 0x61, 0x89, 0x1a, 0xff, 0x19, 0xfa, - 0x28, 0x17, 0x0f, 0x32, 0x40, 0xa3, 0xe3, 0xa9, 0x79, 0xeb, 0x91, 0x57, 0x8e, 0xf2, 0x4b, 0xab, 0x4d, 0xfa, 0x15, - 0x90, 0x19, 0xed, 0x1f, 0x2d, 0x25, 0x90, 0x19, 0x98, 0x49, 0x4b, 0x43, 0x22, 0x47, 0x31, 0x4b, 0xf3, 0x3f, 0x70, - 0xc5, 0x56, 0x88, 0x34, 0xac, 0xe6, 0x1e, 0x7f, 0x51, 0x79, 0xb5, 0x5c, 0xcb, 0x4c, 0x73, 0xb3, 0xc4, 0xb1, 0x62, - 0x71, 0x51, 0xaf, 0x2b, 0x91, 0x05, 0x42, 0x1c, 0x61, 0x1a, 0xeb, 0xa9, 0x37, 0x4a, 0xab, 0x8f, 0x48, 0x28, 0xf3, - 0x23, 0xf6, 0x76, 0xec, 0xf5, 0x20, 0x0b, 0x71, 0x6c, 0x39, 0xd8, 0x6c, 0xbd, 0xcf, 0x65, 0x2a, 0xe2, 0xb3, 0xba, - 0x38, 0xdb, 0x54, 0xe2, 0xac, 0x4e, 0xc4, 0xd9, 0x0f, 0x90, 0xf3, 0x87, 0x33, 0x2a, 0xfa, 0xec, 0x21, 0xad, 0x93, - 0x62, 0x53, 0xd3, 0x93, 0x37, 0x58, 0xc6, 0x0f, 0x67, 0xc4, 0x55, 0x73, 0x46, 0x23, 0x19, 0x8f, 0xce, 0x3e, 0x66, - 0x40, 0xf2, 0x7a, 0x96, 0xae, 0x60, 0xf0, 0xce, 0xc2, 0x3c, 0x3e, 0x2b, 0xc5, 0x1d, 0x58, 0x9c, 0xca, 0xce, 0xf7, - 0x20, 0xc3, 0x2a, 0xfc, 0x43, 0x9c, 0x01, 0xb4, 0xeb, 0x59, 0x5a, 0x9f, 0xa5, 0xd5, 0x59, 0x5e, 0xd4, 0x67, 0x4a, - 0x0a, 0x87, 0x30, 0x7e, 0x78, 0x4f, 0x5f, 0xd9, 0xe5, 0x6d, 0x16, 0x77, 0x59, 0xe4, 0x4f, 0xd1, 0xab, 0x88, 0x98, - 0x34, 0x2a, 0xe1, 0xb5, 0xfb, 0xdb, 0xe6, 0xfe, 0xe1, 0x75, 0x63, 0xf7, 0xb3, 0x3b, 0x46, 0x74, 0x41, 0x3d, 0x5e, - 0x49, 0x4a, 0x05, 0x05, 0x04, 0x4e, 0x34, 0x6b, 0x3c, 0xb8, 0xe3, 0x80, 0x57, 0x03, 0x5b, 0xb2, 0xb5, 0xcf, 0xaf, - 0x63, 0x19, 0xa6, 0xbd, 0x09, 0xf0, 0xaf, 0xb2, 0x37, 0x5d, 0x07, 0x4b, 0xbc, 0x6f, 0x21, 0xdb, 0xd0, 0xdb, 0xd7, - 0xfc, 0x85, 0x97, 0xab, 0xbf, 0xd9, 0x3f, 0x00, 0x08, 0x03, 0x62, 0x56, 0x7d, 0x34, 0x71, 0xef, 0xac, 0x2c, 0x3b, - 0x27, 0xcb, 0xae, 0x87, 0x7e, 0x4d, 0x62, 0x54, 0x5a, 0x59, 0x4a, 0x27, 0x4b, 0x09, 0x59, 0xc0, 0x27, 0x46, 0x53, - 0x1b, 0x01, 0x84, 0xed, 0x28, 0x95, 0x2f, 0x54, 0x5e, 0x44, 0xe1, 0x9c, 0xe0, 0x79, 0x22, 0x46, 0xf7, 0x56, 0x32, - 0x60, 0x38, 0x84, 0x60, 0x0e, 0xda, 0x62, 0x6f, 0xe8, 0x26, 0xe2, 0xaf, 0x37, 0x45, 0xf9, 0x36, 0x26, 0x9f, 0x82, - 0xdd, 0xc9, 0xc7, 0x25, 0x3c, 0x2e, 0x4f, 0x3e, 0x0e, 0xd1, 0x23, 0xe1, 0xe4, 0x63, 0xf0, 0x3d, 0x92, 0xf3, 0xba, - 0xeb, 0x71, 0x82, 0xdc, 0x42, 0xba, 0xbf, 0x1d, 0x93, 0x00, 0xcd, 0x6b, 0x58, 0x8e, 0x9a, 0x8a, 0x6b, 0x66, 0xc6, - 0x78, 0xde, 0xe8, 0xfd, 0xb1, 0xe3, 0x2d, 0x53, 0x28, 0x66, 0x31, 0xaf, 0xe1, 0xf7, 0xac, 0x0a, 0xd4, 0x5d, 0x6f, - 0x93, 0xdc, 0x32, 0xab, 0xe7, 0x68, 0xf7, 0xfd, 0x50, 0x27, 0x82, 0xda, 0xdf, 0x61, 0xcf, 0x33, 0xeb, 0x5d, 0x15, - 0x03, 0x97, 0x2a, 0xd9, 0x21, 0x53, 0xd5, 0xf4, 0x40, 0xa5, 0x34, 0x78, 0x7a, 0x69, 0x5d, 0xbe, 0x54, 0xda, 0xc8, - 0x33, 0xcd, 0x6f, 0x00, 0x2f, 0xa6, 0x2e, 0x8b, 0xdd, 0x37, 0xf7, 0x15, 0xdc, 0xc6, 0xfb, 0xfd, 0x65, 0xe5, 0x99, - 0x9f, 0xb8, 0x00, 0xec, 0x4d, 0x85, 0xd6, 0x09, 0x94, 0x1a, 0xd6, 0xe1, 0xab, 0x44, 0x44, 0x7f, 0xb4, 0xcb, 0x75, - 0xe6, 0x3a, 0x60, 0x44, 0x11, 0xbf, 0x8d, 0x47, 0x7f, 0x80, 0xe2, 0xda, 0xd8, 0x03, 0xc2, 0x3a, 0x24, 0xf4, 0x19, - 0x01, 0x48, 0x3d, 0xe6, 0x28, 0x01, 0xcd, 0x8a, 0xe6, 0x8e, 0xc9, 0xcf, 0xf5, 0x95, 0xd2, 0xdf, 0x2f, 0x2b, 0x8f, - 0xcc, 0x29, 0x6d, 0x33, 0x8d, 0xd5, 0x9a, 0x4a, 0x20, 0xbc, 0xa2, 0x92, 0x55, 0xf8, 0x6c, 0xde, 0x88, 0x7e, 0x5f, - 0x1e, 0xe1, 0x69, 0xf5, 0xe3, 0x16, 0xe3, 0x5b, 0x01, 0xd1, 0x48, 0x00, 0xf4, 0x13, 0xc0, 0xbc, 0xc8, 0x66, 0x76, - 0x1f, 0x07, 0x54, 0x29, 0xd1, 0x34, 0xce, 0xe6, 0xf9, 0x3d, 0xbd, 0x29, 0x3b, 0xe8, 0xd4, 0xa9, 0x02, 0x17, 0x5c, - 0x95, 0x8c, 0x57, 0xd6, 0x13, 0xf9, 0xfc, 0xe6, 0x76, 0x93, 0x66, 0xf1, 0x87, 0xf2, 0x1f, 0x38, 0xb6, 0xba, 0x0e, - 0x8f, 0x4c, 0x9d, 0xae, 0x9d, 0x47, 0x5a, 0x7b, 0x21, 0x20, 0xa2, 0x5d, 0x43, 0xad, 0x17, 0x16, 0x7a, 0xa4, 0x27, - 0xc2, 0x39, 0x49, 0xd4, 0xb4, 0x03, 0x2d, 0x8d, 0xd0, 0xd7, 0xd7, 0x9c, 0xfe, 0xc2, 0x60, 0xed, 0xf3, 0x31, 0x03, - 0xb2, 0x12, 0xfd, 0x58, 0x3d, 0x34, 0x36, 0x73, 0xe8, 0x59, 0xab, 0xf2, 0xcc, 0xab, 0x0e, 0x07, 0xc4, 0x87, 0xd1, - 0x5f, 0xf2, 0xfb, 0xfd, 0xd7, 0x34, 0xff, 0x98, 0x50, 0xe3, 0x67, 0x9b, 0x01, 0xba, 0xf6, 0x5d, 0x79, 0x20, 0xea, - 0xb9, 0x56, 0x09, 0x42, 0xbc, 0x41, 0x4c, 0x34, 0x23, 0xe6, 0xe0, 0xb4, 0x43, 0xcd, 0x3f, 0x49, 0x0d, 0x08, 0x51, - 0xe2, 0x75, 0x4c, 0x59, 0x90, 0xd3, 0x26, 0x8e, 0xf4, 0xa3, 0x70, 0x22, 0x3f, 0x89, 0xaa, 0xc8, 0xee, 0xe1, 0x82, - 0xc1, 0xd4, 0x7b, 0xda, 0x2f, 0xd1, 0x6f, 0x09, 0x47, 0xce, 0xd1, 0xaa, 0x10, 0x44, 0x4e, 0x08, 0x6b, 0x0d, 0x61, - 0x82, 0xd8, 0x20, 0x5e, 0xf6, 0x5d, 0x92, 0xe1, 0x48, 0xc1, 0x65, 0x1d, 0x3b, 0xc6, 0x5c, 0x1d, 0x55, 0xaf, 0x01, - 0x8c, 0x57, 0x8e, 0xa0, 0xd9, 0x28, 0xb2, 0x4b, 0x88, 0x2a, 0x72, 0x3c, 0x01, 0xb5, 0x83, 0xd2, 0xd8, 0x4c, 0xcf, - 0xc7, 0x41, 0x3e, 0x5a, 0x54, 0xa8, 0x73, 0x62, 0x19, 0xaf, 0x01, 0x58, 0x3b, 0x57, 0xfd, 0x3c, 0xab, 0xc1, 0x93, - 0x86, 0xf8, 0x7c, 0x8c, 0xb6, 0x57, 0x36, 0x07, 0xd5, 0x76, 0x3a, 0x2b, 0xaf, 0x98, 0x2e, 0x07, 0xc6, 0x7d, 0xc3, - 0x2b, 0x8a, 0x33, 0xfc, 0xe4, 0xc1, 0x16, 0xe7, 0x4f, 0x37, 0xd4, 0x7e, 0xcc, 0x8d, 0x7a, 0x18, 0x68, 0x2d, 0x78, - 0x53, 0x10, 0xeb, 0xef, 0xc7, 0x8e, 0x6c, 0x1f, 0xb4, 0xc8, 0x68, 0xf2, 0xd9, 0xcf, 0x3f, 0x96, 0xe9, 0x2a, 0x85, - 0xfb, 0x92, 0x93, 0x45, 0x33, 0x0f, 0x81, 0xbd, 0x21, 0x86, 0xeb, 0xa3, 0xc2, 0x23, 0xca, 0xfa, 0x7d, 0xf8, 0x7d, - 0x95, 0x81, 0x29, 0x06, 0xae, 0x2b, 0x04, 0xe3, 0x21, 0x10, 0xc4, 0xc3, 0x34, 0x3a, 0x19, 0xd4, 0xa0, 0x0d, 0xdf, - 0x00, 0x64, 0x06, 0x78, 0x64, 0x2e, 0x3d, 0x02, 0xee, 0x02, 0xd7, 0x9e, 0x8c, 0xc7, 0xfe, 0xc4, 0x34, 0x34, 0x6a, - 0x4a, 0x33, 0x3d, 0x37, 0x7e, 0xd3, 0x51, 0x2d, 0xd7, 0xce, 0x7f, 0x7c, 0xc9, 0x6f, 0xd0, 0x0b, 0x5a, 0x5e, 0xee, - 0x23, 0x75, 0xb9, 0xcf, 0x28, 0x2e, 0x13, 0xc9, 0x61, 0x41, 0x2c, 0x4b, 0x38, 0xf0, 0x18, 0x95, 0x2c, 0xb6, 0xf4, - 0x58, 0x15, 0x2d, 0x5f, 0x94, 0x1b, 0xa4, 0x43, 0x27, 0x04, 0x4b, 0x54, 0x10, 0x2c, 0x81, 0x71, 0x11, 0x6b, 0xbe, - 0x19, 0xe4, 0x2c, 0x9e, 0x6d, 0xe6, 0x1c, 0x09, 0xeb, 0x92, 0xc3, 0xa1, 0x90, 0x60, 0x33, 0xd9, 0x6c, 0x3d, 0x67, - 0x6b, 0x9f, 0x81, 0x12, 0xa0, 0x94, 0x69, 0x82, 0xd2, 0xb4, 0x62, 0x2b, 0x6e, 0x5a, 0x83, 0xd5, 0x6a, 0xca, 0x56, - 0x35, 0x65, 0xe7, 0x34, 0xe5, 0xa8, 0x82, 0x92, 0x13, 0x4a, 0x51, 0x86, 0x01, 0x8c, 0xd8, 0x24, 0xba, 0xca, 0xd0, - 0xc7, 0x3b, 0xe1, 0x11, 0x54, 0x11, 0x91, 0x4f, 0x18, 0x42, 0x60, 0x22, 0x8a, 0x0b, 0x55, 0x28, 0x06, 0xc8, 0x88, - 0x04, 0x82, 0x89, 0x4a, 0x9d, 0x02, 0xf3, 0xd1, 0x54, 0x31, 0x6c, 0xda, 0x13, 0xe5, 0x7b, 0xea, 0xb8, 0x47, 0xd9, - 0xe6, 0x6f, 0x62, 0x17, 0x84, 0xc8, 0xdd, 0xb8, 0x53, 0x3f, 0x23, 0xde, 0xdb, 0x1d, 0x61, 0xfc, 0x64, 0xc7, 0x2d, - 0xc2, 0x15, 0xc1, 0x96, 0x6a, 0x0e, 0xb1, 0x98, 0x57, 0x93, 0x04, 0xb5, 0x2c, 0x89, 0xbf, 0xe1, 0xc9, 0x20, 0x67, - 0x4b, 0xf0, 0xa0, 0x9d, 0xb3, 0x0c, 0xf0, 0x57, 0xac, 0x16, 0xfd, 0x56, 0x7b, 0x4b, 0x90, 0x9f, 0x36, 0x76, 0xa3, - 0x30, 0x31, 0x82, 0x44, 0xdd, 0xae, 0x0c, 0xe4, 0x87, 0x8f, 0x38, 0x1d, 0x8f, 0x3d, 0x65, 0xcc, 0xad, 0x4c, 0x2f, - 0xd3, 0xb9, 0x92, 0x6f, 0xe4, 0x5e, 0xfa, 0xd8, 0x4b, 0xb0, 0x73, 0xc0, 0x1b, 0x48, 0x1b, 0x78, 0x03, 0xdb, 0x85, - 0xd7, 0x06, 0x09, 0x33, 0x02, 0x6c, 0x71, 0x7c, 0x8c, 0x94, 0xc0, 0x10, 0x8e, 0xb3, 0x14, 0x80, 0x69, 0xf4, 0x65, - 0xb6, 0xb2, 0x2f, 0xb3, 0x5a, 0xb3, 0xa5, 0x72, 0xba, 0x77, 0x6e, 0xdd, 0xce, 0x27, 0x12, 0x00, 0x4c, 0xea, 0x1c, - 0x88, 0x33, 0x13, 0xec, 0xd2, 0x24, 0xb2, 0x7c, 0x0a, 0xf3, 0x3b, 0xf1, 0xa6, 0x2c, 0x56, 0xaa, 0x2b, 0xda, 0x3e, - 0x33, 0xf9, 0x8c, 0x74, 0x12, 0x2a, 0xa0, 0xa0, 0x90, 0x6b, 0x7d, 0xfa, 0x3e, 0x7c, 0x1f, 0x14, 0x1a, 0x98, 0xad, - 0xc2, 0x3d, 0x4d, 0xd6, 0x48, 0xbd, 0x51, 0xf5, 0xfb, 0xe4, 0x1a, 0x48, 0x75, 0xe6, 0xd0, 0xb2, 0x27, 0x15, 0x06, - 0x88, 0x1d, 0xf5, 0x19, 0x09, 0x75, 0x20, 0xf5, 0x80, 0x21, 0x44, 0xdb, 0xf4, 0xf1, 0x27, 0x43, 0xa2, 0x0b, 0xb0, - 0x85, 0x68, 0x03, 0x3f, 0xfe, 0x04, 0xfb, 0x2c, 0x08, 0x8f, 0x69, 0xfe, 0x0e, 0x92, 0x8e, 0x0d, 0x9c, 0x56, 0x9f, - 0x82, 0x0f, 0x92, 0x1c, 0x4c, 0xd4, 0xc1, 0xcb, 0xfd, 0xa5, 0xdf, 0x87, 0x2d, 0x3b, 0x97, 0x52, 0x1d, 0x2b, 0xf5, - 0xb6, 0xad, 0xfd, 0x20, 0xda, 0x82, 0x23, 0x8b, 0xf8, 0x87, 0x0c, 0x11, 0xc1, 0xcc, 0x20, 0xc2, 0xae, 0x85, 0xba, - 0xdb, 0x53, 0x6a, 0x59, 0xd4, 0xdb, 0x9e, 0x52, 0xea, 0x36, 0x0c, 0xdf, 0x4d, 0x30, 0x53, 0xdc, 0xf0, 0x3f, 0x32, - 0x2f, 0xd4, 0x1b, 0x8f, 0x45, 0x81, 0xee, 0xf9, 0xfb, 0x25, 0xaf, 0x66, 0x1b, 0x65, 0xc2, 0xbc, 0xe3, 0xcb, 0x59, - 0x28, 0xbb, 0x5a, 0x1a, 0x77, 0xbe, 0x78, 0x4b, 0x35, 0x1f, 0xfc, 0xc3, 0x21, 0x81, 0x78, 0xa3, 0xf8, 0xea, 0xae, - 0x91, 0x5b, 0xd7, 0x64, 0x73, 0x55, 0x02, 0xea, 0xf7, 0xf9, 0x1a, 0xf7, 0x5b, 0xac, 0x7f, 0xf7, 0x34, 0xc8, 0x58, - 0xcd, 0x70, 0xc5, 0x14, 0x3e, 0x05, 0x80, 0xc1, 0xe1, 0x54, 0x90, 0x16, 0x78, 0xc3, 0xcb, 0xe1, 0xe5, 0x64, 0x43, - 0x26, 0xdd, 0x8d, 0x8f, 0xdc, 0x59, 0xa0, 0xea, 0xfd, 0x8e, 0xe2, 0xa4, 0x41, 0xa2, 0xb1, 0xd7, 0xe0, 0x8b, 0x2c, - 0xa3, 0x5c, 0x34, 0x71, 0x1f, 0x93, 0xaf, 0xf4, 0x00, 0xe6, 0x2a, 0x94, 0x00, 0xd1, 0x6f, 0x2c, 0x8b, 0x8d, 0x68, - 0x5b, 0x6c, 0x60, 0x29, 0x55, 0x73, 0xbd, 0x9a, 0xbe, 0x78, 0x25, 0x9a, 0xf7, 0xd1, 0x8c, 0x53, 0x1a, 0x0d, 0x38, - 0x4e, 0xa3, 0x70, 0xfb, 0xe1, 0x5e, 0x94, 0xcb, 0x0c, 0x2c, 0xd9, 0x2a, 0x9c, 0xe2, 0xb2, 0x51, 0x67, 0xc4, 0x8b, - 0x3c, 0x56, 0x00, 0x1d, 0x8f, 0x09, 0x80, 0xea, 0x82, 0x80, 0x8a, 0x68, 0x29, 0xbd, 0x15, 0x5a, 0x2c, 0xd4, 0x1b, - 0x8e, 0x52, 0xf8, 0x23, 0xfd, 0x79, 0x90, 0x4f, 0x01, 0x88, 0x5d, 0x1f, 0x47, 0x6f, 0x8a, 0x92, 0x3e, 0x55, 0xcc, - 0x72, 0x39, 0x98, 0xc0, 0xae, 0x4e, 0x64, 0xa8, 0x15, 0xe4, 0xad, 0xba, 0xf2, 0x56, 0x26, 0x6f, 0x63, 0x9c, 0x92, - 0x1f, 0xb9, 0xe9, 0x58, 0x23, 0x06, 0x5e, 0x79, 0x5a, 0xa7, 0x09, 0xd2, 0xe4, 0x02, 0x18, 0x86, 0xf8, 0x36, 0xf3, - 0x5e, 0x78, 0x8e, 0x54, 0x05, 0xc9, 0x6c, 0x97, 0x79, 0xea, 0x22, 0xaa, 0xaf, 0x9c, 0x5a, 0x3a, 0x73, 0xfa, 0x11, - 0xc0, 0x7b, 0x4c, 0x4d, 0x1a, 0xf2, 0x11, 0x6e, 0x4b, 0xf1, 0xf5, 0x56, 0x5d, 0xe3, 0xa5, 0xd1, 0xb9, 0x7b, 0xf9, - 0xd2, 0x9d, 0x06, 0xfd, 0x14, 0x04, 0xe5, 0x7c, 0x51, 0x0a, 0xd8, 0x53, 0x66, 0x73, 0xbd, 0x5a, 0xb5, 0x42, 0xeb, - 0x70, 0x18, 0x6b, 0x47, 0x21, 0xad, 0xce, 0x02, 0xb6, 0x1a, 0xe9, 0x94, 0x00, 0x21, 0x38, 0x4e, 0xc3, 0x4e, 0x30, - 0xee, 0xd2, 0x69, 0x44, 0xd6, 0x2b, 0x25, 0xe9, 0xc2, 0x0c, 0x92, 0x7f, 0x92, 0xd7, 0x33, 0xa0, 0x25, 0x80, 0x43, - 0x11, 0x4b, 0x78, 0x38, 0x49, 0xae, 0x00, 0x3a, 0x1d, 0x0e, 0x2a, 0x0d, 0xcd, 0x59, 0xcd, 0x92, 0xf9, 0x24, 0x96, - 0xaa, 0xca, 0xc3, 0xc1, 0x53, 0x6e, 0x06, 0xfd, 0x7e, 0x36, 0x2d, 0x95, 0x0b, 0x40, 0x10, 0xeb, 0xc2, 0x00, 0xf1, - 0x48, 0x0b, 0x4f, 0x16, 0x7d, 0x4a, 0xe2, 0x97, 0xb3, 0x64, 0x6e, 0xb2, 0xe1, 0x1d, 0x18, 0xc1, 0x66, 0x5c, 0x97, - 0x94, 0x69, 0x8f, 0xca, 0xef, 0x19, 0x3d, 0xb5, 0x7d, 0xad, 0xd5, 0x16, 0xb1, 0xae, 0x83, 0xab, 0x12, 0xf5, 0x14, - 0x1f, 0x94, 0x24, 0x78, 0xbf, 0x76, 0x6e, 0x46, 0xca, 0xd7, 0x22, 0xf7, 0x83, 0x76, 0xa6, 0x56, 0x0e, 0x1c, 0x81, - 0x1c, 0xab, 0xa8, 0xe4, 0xf5, 0xae, 0x43, 0xf0, 0xe8, 0xae, 0x54, 0xa0, 0x1c, 0x7c, 0x0d, 0x62, 0x74, 0x7d, 0xd5, - 0x59, 0x43, 0xcd, 0x34, 0xaa, 0x3c, 0x82, 0x4e, 0x1d, 0xc0, 0x93, 0x82, 0x97, 0x5a, 0xfd, 0x78, 0x38, 0x78, 0xe6, - 0x07, 0x7f, 0x95, 0xe9, 0x5b, 0x88, 0x89, 0x72, 0xaa, 0x11, 0x12, 0x57, 0x4a, 0x12, 0xf1, 0xf1, 0xa2, 0x65, 0xc5, - 0xa8, 0x0c, 0x1f, 0x78, 0xa5, 0xca, 0x57, 0xa7, 0x2a, 0x2f, 0x46, 0xda, 0x96, 0xc0, 0x6b, 0xf2, 0x0f, 0x91, 0x6b, - 0xde, 0xfa, 0xba, 0xab, 0x0c, 0x7d, 0x27, 0x2b, 0xd0, 0x11, 0x6c, 0x65, 0x29, 0x39, 0xe0, 0x93, 0xea, 0xae, 0x5a, - 0xb5, 0x3e, 0xa7, 0x6c, 0x23, 0xdc, 0xe4, 0xd7, 0xb1, 0x83, 0x23, 0xe5, 0x37, 0x78, 0x2e, 0x80, 0xbd, 0x06, 0xec, - 0xcd, 0x39, 0x2b, 0x9a, 0x47, 0x87, 0xb4, 0x2d, 0xd0, 0xc8, 0xcc, 0xed, 0x5c, 0xdd, 0xb7, 0xe5, 0x51, 0x1a, 0x43, - 0x64, 0xda, 0x23, 0xd3, 0xc1, 0x66, 0x94, 0xff, 0x9e, 0xf2, 0x5b, 0x85, 0x63, 0xe0, 0xdb, 0xa9, 0x77, 0x00, 0x55, - 0x4f, 0x1b, 0x64, 0xac, 0x19, 0x86, 0x56, 0x76, 0xb9, 0x14, 0x5a, 0x82, 0x96, 0xba, 0x09, 0x82, 0xf3, 0x23, 0xa2, - 0x1c, 0x01, 0xe8, 0x22, 0x05, 0x4c, 0xf0, 0x53, 0xda, 0xee, 0x7e, 0x7f, 0x9d, 0x7a, 0xe4, 0xde, 0x15, 0x6a, 0x94, - 0x50, 0x82, 0xb1, 0x9f, 0x68, 0xcc, 0xa0, 0xa3, 0x2b, 0x72, 0xc2, 0xb3, 0x56, 0x87, 0x75, 0xdd, 0x94, 0x41, 0x59, - 0x1c, 0xf3, 0x6a, 0x3a, 0xfb, 0xfd, 0xc9, 0xbe, 0x6e, 0x90, 0x85, 0xfc, 0x77, 0xd6, 0x43, 0x32, 0xe8, 0x1e, 0x84, - 0x42, 0xf4, 0xe6, 0xc1, 0x0c, 0xff, 0x63, 0x1b, 0x9e, 0x7d, 0xc7, 0x8d, 0x3a, 0x01, 0xcc, 0x11, 0xd7, 0x4b, 0x4f, - 0xd1, 0xd6, 0xc3, 0x2d, 0x90, 0xad, 0xf1, 0xf2, 0xd6, 0x5e, 0x03, 0x39, 0xc5, 0xf1, 0xdf, 0xf1, 0x4c, 0xad, 0x6c, - 0xf0, 0xd3, 0x53, 0xb6, 0x03, 0x0f, 0x2f, 0x42, 0x40, 0x31, 0x2c, 0x1b, 0x7f, 0x67, 0x39, 0xce, 0xe8, 0xbf, 0x79, - 0xc4, 0x30, 0x58, 0x44, 0x7e, 0x7c, 0x59, 0x0a, 0xf1, 0x55, 0x78, 0x6f, 0x2b, 0xef, 0x8e, 0x9c, 0x32, 0xef, 0xf4, - 0x30, 0xba, 0x2e, 0x49, 0xdf, 0x25, 0x1f, 0x5b, 0xc3, 0xf6, 0xbb, 0x76, 0xbf, 0x19, 0x22, 0x08, 0xa1, 0x1c, 0x3f, - 0x67, 0x74, 0x42, 0xe3, 0xc3, 0x6a, 0x76, 0x7a, 0xfd, 0xde, 0x39, 0x5e, 0xb0, 0x35, 0x1a, 0xe0, 0xf1, 0xd0, 0xc5, - 0x3c, 0x51, 0x43, 0xa7, 0xeb, 0xda, 0x39, 0x78, 0x60, 0x90, 0xe5, 0xc9, 0x77, 0x0c, 0x4b, 0xec, 0x4f, 0x22, 0x9e, - 0xb4, 0x55, 0x1b, 0x9b, 0x23, 0xd5, 0x46, 0xcd, 0xc0, 0x0f, 0x5e, 0x41, 0x81, 0xd1, 0x05, 0x69, 0x05, 0xc6, 0xe1, - 0x08, 0x40, 0x56, 0x8c, 0xe3, 0x91, 0xc1, 0x04, 0x86, 0x74, 0x43, 0x51, 0x00, 0x1e, 0x1e, 0xc7, 0x83, 0x90, 0x01, - 0xa4, 0x0b, 0x1e, 0x1a, 0xb6, 0x49, 0x48, 0xf9, 0x79, 0x9e, 0xd7, 0x6a, 0x08, 0x7d, 0x67, 0xa1, 0x3a, 0xf6, 0x23, - 0xed, 0x15, 0xeb, 0x5a, 0x95, 0x8e, 0x6c, 0x75, 0x80, 0xbe, 0x21, 0x03, 0xdf, 0x3a, 0xb6, 0x00, 0x88, 0x96, 0xf8, - 0x2d, 0xf5, 0x6a, 0x5f, 0xc6, 0xac, 0x50, 0xaf, 0x2f, 0x4c, 0xbb, 0x5e, 0x4b, 0x8b, 0x02, 0x2a, 0x6e, 0x5b, 0xb5, - 0x3d, 0x92, 0xf3, 0x1f, 0xdf, 0x75, 0xb4, 0xe3, 0xb3, 0x53, 0x63, 0x4b, 0x28, 0x73, 0x8b, 0x27, 0xb2, 0x3a, 0xda, - 0x52, 0x9d, 0xea, 0x03, 0x2e, 0x35, 0xa9, 0xce, 0x0c, 0x0c, 0xaf, 0x11, 0xa0, 0xdc, 0x42, 0x24, 0x8d, 0xc3, 0xde, - 0xf9, 0x64, 0x50, 0x30, 0xb7, 0x48, 0x40, 0x02, 0xdb, 0xd8, 0xda, 0x45, 0x73, 0xfd, 0xfa, 0x2d, 0xf5, 0xaa, 0x36, - 0x55, 0x3d, 0x78, 0xe3, 0x05, 0xce, 0xde, 0x69, 0x2d, 0x20, 0x80, 0xc2, 0xd6, 0xb2, 0x1c, 0x9c, 0xbb, 0x5d, 0xd5, - 0x52, 0x51, 0x46, 0xfd, 0xfe, 0xf9, 0x6f, 0x29, 0x2a, 0x62, 0x4f, 0x15, 0xa7, 0xac, 0xdf, 0x6e, 0x99, 0x8b, 0xca, - 0x92, 0x37, 0xa8, 0xa2, 0xb5, 0x3a, 0x6a, 0x2a, 0xd7, 0xcd, 0x55, 0x4b, 0x26, 0x88, 0xd1, 0x7d, 0xba, 0xd6, 0xb9, - 0x53, 0xef, 0xbd, 0x8a, 0x23, 0x06, 0x82, 0x9b, 0xee, 0xf1, 0xc1, 0x41, 0x68, 0x54, 0x94, 0x0b, 0x6e, 0x94, 0x56, - 0x95, 0x94, 0x42, 0xde, 0xaa, 0x68, 0xce, 0xf4, 0x11, 0x00, 0x11, 0x60, 0x95, 0xa8, 0xff, 0xcd, 0x97, 0xc6, 0x78, - 0xf0, 0xc0, 0xd7, 0xe4, 0x3a, 0xb6, 0xde, 0x3f, 0xad, 0x91, 0x56, 0x1b, 0xc7, 0xa4, 0x56, 0xbd, 0x6c, 0x15, 0x2f, - 0xbb, 0xd7, 0xa9, 0x18, 0x3c, 0xff, 0x9f, 0xfb, 0x00, 0x35, 0xa2, 0xa5, 0x0c, 0x6e, 0x5d, 0x0d, 0xd0, 0xf8, 0x70, - 0x2c, 0x7c, 0xe3, 0x87, 0x8c, 0xf3, 0xc1, 0x0c, 0x1d, 0xd5, 0xe6, 0xe0, 0x80, 0xe0, 0xa8, 0xee, 0xd1, 0x98, 0x30, - 0x0b, 0xe7, 0x1e, 0x04, 0xaa, 0x4f, 0xdc, 0x67, 0x5c, 0x7b, 0x41, 0x9b, 0xc0, 0x27, 0xeb, 0xba, 0xa6, 0x08, 0x70, - 0x11, 0x1b, 0x13, 0x31, 0xc4, 0x65, 0x93, 0x48, 0x7d, 0x33, 0x06, 0x05, 0x40, 0x71, 0x5d, 0x91, 0x5c, 0xba, 0x48, - 0xf3, 0x4a, 0x94, 0xb5, 0x6e, 0x46, 0xc5, 0x8a, 0x21, 0x00, 0x3c, 0x04, 0xc5, 0x55, 0x65, 0x26, 0x34, 0x62, 0x03, - 0xa9, 0x2c, 0x05, 0xab, 0x86, 0x85, 0xdf, 0xb4, 0xdf, 0x24, 0x27, 0xbd, 0xf3, 0x71, 0xeb, 0xdc, 0xb1, 0xef, 0x1d, - 0x85, 0x94, 0xf6, 0x50, 0x4c, 0x10, 0x04, 0x3f, 0xad, 0xc3, 0xf9, 0x33, 0x7e, 0x4d, 0x60, 0x2a, 0xb2, 0x19, 0x03, - 0x0e, 0x42, 0x44, 0x66, 0xfc, 0x9e, 0xc3, 0x6b, 0x5e, 0x4e, 0xc2, 0xe1, 0xd0, 0x07, 0x7d, 0x28, 0xcf, 0x66, 0xe1, - 0x50, 0xcc, 0xa5, 0xf7, 0x3a, 0x58, 0xeb, 0x42, 0x5e, 0x4f, 0x42, 0x44, 0x0b, 0x0d, 0x7d, 0x70, 0x5e, 0x77, 0xcd, - 0x11, 0x96, 0x00, 0x34, 0x71, 0xf4, 0x65, 0xfd, 0x7e, 0xe4, 0x69, 0x43, 0x8b, 0x14, 0x17, 0x8d, 0x32, 0x9b, 0xe5, - 0xb2, 0x13, 0x36, 0xae, 0xdd, 0x02, 0xa1, 0x78, 0x98, 0xb6, 0x50, 0xb5, 0x9e, 0xea, 0xf5, 0xdc, 0xb4, 0xfb, 0xee, - 0x51, 0xb5, 0xca, 0x91, 0xce, 0xda, 0x74, 0xa5, 0x56, 0xb7, 0x8c, 0xaa, 0x75, 0x96, 0x46, 0x54, 0xb9, 0x49, 0xee, - 0x1a, 0xb5, 0xe0, 0x93, 0x0d, 0x5d, 0xa6, 0xec, 0x6c, 0x0d, 0x4e, 0x1c, 0x79, 0x2e, 0xb9, 0xe5, 0xbb, 0xf3, 0x8a, - 0xee, 0x4e, 0xb5, 0x6f, 0x01, 0xee, 0xcd, 0xb0, 0x21, 0x73, 0x5e, 0x63, 0xa7, 0x41, 0x98, 0x04, 0x7e, 0xc4, 0x3e, - 0x66, 0xc8, 0x06, 0x03, 0x3a, 0x0a, 0xe9, 0x7f, 0x6d, 0x99, 0x23, 0x01, 0x93, 0xbf, 0x9e, 0xfb, 0xcd, 0xa2, 0xc8, - 0x61, 0x31, 0x7e, 0xdc, 0x60, 0xa4, 0xb1, 0x5a, 0x83, 0x61, 0x79, 0x87, 0xc8, 0x9f, 0xda, 0x1d, 0xd3, 0x54, 0xc7, - 0x9b, 0xf5, 0x5a, 0xf3, 0xab, 0xa7, 0x4f, 0x75, 0x7d, 0xfe, 0xdb, 0xf7, 0x97, 0x61, 0xcd, 0xec, 0x0f, 0x41, 0x28, - 0xed, 0xde, 0x2d, 0xce, 0x1d, 0x89, 0xde, 0xb1, 0xd2, 0xcc, 0x2e, 0xed, 0x92, 0x5d, 0x9a, 0xd2, 0x6e, 0xc8, 0xf5, - 0xea, 0x1b, 0xe5, 0x8d, 0x9d, 0x57, 0x4c, 0xf7, 0xef, 0x85, 0xde, 0x51, 0x4e, 0xd5, 0x04, 0x22, 0x9a, 0xb4, 0x23, - 0x71, 0xbb, 0x57, 0x86, 0xcf, 0x27, 0x79, 0xbb, 0x84, 0xa3, 0xae, 0x61, 0xb9, 0xf9, 0xf6, 0x3f, 0xf2, 0xaa, 0xb3, - 0xc2, 0xed, 0x97, 0xc6, 0xac, 0xfd, 0x29, 0x88, 0xab, 0xfa, 0xc3, 0x7b, 0x52, 0x33, 0x25, 0xff, 0x57, 0x3d, 0x06, - 0xae, 0x7e, 0x32, 0xed, 0xe8, 0x9e, 0x42, 0xd8, 0x60, 0xf6, 0xf3, 0xe3, 0x87, 0x16, 0xac, 0xaa, 0x0b, 0x14, 0xc9, - 0x01, 0x74, 0xee, 0x92, 0x11, 0xde, 0xef, 0x18, 0xe7, 0xfe, 0xd5, 0x2f, 0x6a, 0x72, 0x84, 0x88, 0x76, 0x11, 0x0e, - 0x00, 0xe2, 0x4e, 0x53, 0x59, 0x87, 0x1a, 0xa0, 0x0f, 0x08, 0xac, 0x43, 0xdf, 0x66, 0x00, 0x07, 0x7d, 0xb4, 0x79, - 0x16, 0x81, 0xbc, 0xee, 0xdd, 0xb3, 0x77, 0x6c, 0xe7, 0xf3, 0xeb, 0x55, 0xea, 0xdd, 0xa3, 0x43, 0xf0, 0xf9, 0xd8, - 0x9f, 0x5e, 0x06, 0x06, 0x17, 0x9a, 0xbd, 0x7b, 0x26, 0xd8, 0x8e, 0xed, 0x9e, 0x21, 0x52, 0x51, 0x77, 0xfe, 0xe1, - 0xa5, 0x89, 0x9e, 0x77, 0x5e, 0xb8, 0xe3, 0x4b, 0x00, 0x0f, 0x64, 0x31, 0xa0, 0xf8, 0x2c, 0xbd, 0x7f, 0xb2, 0x04, - 0xd4, 0xe4, 0xb7, 0x7c, 0xed, 0xbd, 0xa7, 0xd4, 0x05, 0xfc, 0x39, 0xa0, 0xf4, 0x49, 0xce, 0xbd, 0xbb, 0xe1, 0xad, - 0x7f, 0xf1, 0x1c, 0x9c, 0x27, 0x56, 0xc3, 0x05, 0xfc, 0x55, 0xf0, 0xa1, 0x77, 0x37, 0xc0, 0xc4, 0x92, 0x0f, 0xbd, - 0xd5, 0x00, 0x52, 0x15, 0x2e, 0x24, 0xc6, 0x3e, 0xfc, 0x1a, 0xe4, 0x0c, 0xff, 0xf8, 0x4d, 0x63, 0xb0, 0xfe, 0x1a, - 0x14, 0x1a, 0x8d, 0xb5, 0x54, 0x21, 0x4b, 0xb1, 0x38, 0x13, 0x60, 0x13, 0x8e, 0xbb, 0x7d, 0xb1, 0xaa, 0xcd, 0x5a, - 0xd0, 0x9f, 0x8f, 0xf8, 0x1e, 0x8d, 0xd5, 0x55, 0x39, 0x17, 0xe5, 0x27, 0xa4, 0x4f, 0x75, 0x7c, 0x8c, 0x8a, 0x4d, - 0xdd, 0x9d, 0x4e, 0xb5, 0xea, 0x48, 0xfb, 0x4d, 0xb9, 0x06, 0x3b, 0x5e, 0x27, 0x47, 0x96, 0xc2, 0xb3, 0x0e, 0x3b, - 0x2f, 0x9d, 0x12, 0x1d, 0x86, 0xf1, 0x6e, 0xab, 0x9e, 0x31, 0x94, 0xe7, 0x06, 0x63, 0xba, 0xe0, 0x11, 0xbf, 0x1e, - 0xe4, 0x32, 0x34, 0xe6, 0x23, 0xb2, 0x61, 0x28, 0x1f, 0x5a, 0x64, 0x48, 0x88, 0x78, 0x0f, 0x95, 0x80, 0x6d, 0x0b, - 0xca, 0xa4, 0x80, 0xb3, 0x68, 0xf0, 0x5b, 0xed, 0xe5, 0xc0, 0x7b, 0x10, 0xf9, 0x8d, 0x74, 0x29, 0x97, 0xd8, 0xe8, - 0xc4, 0xb1, 0x2c, 0xb4, 0xf3, 0xb8, 0xfe, 0x3a, 0x06, 0xf5, 0x7b, 0xa5, 0xdf, 0xa0, 0x9c, 0xfd, 0x49, 0xb2, 0x4e, - 0x1b, 0x4f, 0x8c, 0x7f, 0xb9, 0xca, 0x3f, 0x45, 0x4b, 0x3d, 0xfc, 0x7f, 0xc6, 0x14, 0x4a, 0xff, 0x2a, 0x2d, 0xa3, - 0xcd, 0x6a, 0x29, 0x4a, 0x91, 0x47, 0xe2, 0xe4, 0x6b, 0x91, 0x9d, 0xcb, 0x77, 0x3e, 0x85, 0x7e, 0x01, 0x68, 0xd9, - 0x27, 0xc8, 0xe8, 0x5f, 0x98, 0xe0, 0xc3, 0x5f, 0xb4, 0x73, 0x6d, 0xce, 0xc7, 0x93, 0xfc, 0xca, 0xda, 0xbb, 0x1d, - 0x2f, 0x12, 0xa3, 0x18, 0xcb, 0x7d, 0xd5, 0xcd, 0xca, 0x89, 0x4a, 0x0e, 0x8c, 0x74, 0x4d, 0xf6, 0x72, 0x25, 0xeb, - 0x76, 0xba, 0x95, 0x40, 0x44, 0x15, 0x78, 0x8f, 0x71, 0x15, 0xfb, 0x08, 0xa6, 0xeb, 0x8e, 0xcb, 0x68, 0xc7, 0x7b, - 0xc6, 0xab, 0x13, 0x65, 0x05, 0xb7, 0x1b, 0xd1, 0x9e, 0xd0, 0xd1, 0x4f, 0x93, 0xda, 0xb2, 0x70, 0x00, 0x72, 0x97, - 0x30, 0x96, 0x0d, 0xc1, 0x8a, 0x41, 0xe9, 0xeb, 0x35, 0x25, 0xcb, 0x02, 0x2c, 0x3a, 0xbb, 0x8c, 0x40, 0x0c, 0xeb, - 0xa6, 0x39, 0xa1, 0xe3, 0xa5, 0x8b, 0xf3, 0x5e, 0xab, 0x48, 0xc1, 0x33, 0x5a, 0x74, 0xcc, 0x4d, 0x47, 0xba, 0x31, - 0xda, 0xdb, 0x97, 0x06, 0x21, 0xc5, 0xf3, 0x07, 0xb6, 0x5a, 0x17, 0x17, 0x89, 0x57, 0xc8, 0x44, 0x0b, 0x62, 0x29, - 0x02, 0x33, 0x5e, 0x68, 0x1a, 0x61, 0x82, 0x32, 0x25, 0x58, 0xb4, 0x46, 0x87, 0xf6, 0x87, 0x25, 0xec, 0x1e, 0x63, - 0x04, 0x08, 0x54, 0x99, 0xbe, 0x84, 0xad, 0x09, 0xb3, 0xa9, 0x8b, 0x0d, 0xd0, 0x56, 0x31, 0x34, 0x08, 0x6b, 0x43, - 0xcc, 0xa7, 0x34, 0xbf, 0xfb, 0x27, 0x16, 0x63, 0x7b, 0x02, 0xb1, 0xbd, 0xdb, 0x35, 0x09, 0xd3, 0xbd, 0x16, 0x37, - 0xd6, 0xcb, 0xed, 0x29, 0xc7, 0xd4, 0x8e, 0xb5, 0x51, 0x3b, 0xd6, 0x52, 0xef, 0x58, 0x6b, 0xbd, 0x63, 0xdd, 0x35, - 0xfc, 0x63, 0xe6, 0xc5, 0x2c, 0x01, 0xfd, 0xee, 0x8a, 0xab, 0x06, 0x41, 0x33, 0x36, 0xec, 0x16, 0x7e, 0x4b, 0xac, - 0xdd, 0xd2, 0xbf, 0x58, 0xb2, 0x85, 0xe9, 0x03, 0xdd, 0x3a, 0xc0, 0x32, 0xa2, 0x26, 0xdf, 0x23, 0xef, 0xa6, 0xb3, - 0xa2, 0x70, 0x7b, 0x62, 0x0b, 0x9f, 0xbd, 0x33, 0x6f, 0xde, 0x3f, 0x8b, 0x20, 0xf7, 0x8e, 0x7b, 0xf7, 0xc3, 0x77, - 0xfe, 0x85, 0x6e, 0x81, 0x9c, 0xcc, 0x72, 0x06, 0x52, 0x47, 0x7c, 0x86, 0x68, 0x65, 0x4f, 0xf9, 0x4e, 0xc8, 0x9d, - 0x6d, 0xfd, 0xec, 0xde, 0xdd, 0xd6, 0xee, 0x9e, 0xdd, 0xb3, 0x6a, 0x44, 0xb1, 0xe2, 0x34, 0x45, 0xc2, 0x2c, 0xda, - 0x00, 0x4f, 0xbd, 0x7c, 0xbf, 0x63, 0xc7, 0x1c, 0xee, 0x9e, 0x75, 0x74, 0xbc, 0x9c, 0x03, 0x76, 0xf7, 0x1f, 0x6d, - 0xc2, 0xc6, 0x4a, 0xd7, 0x2a, 0x74, 0xb8, 0x7b, 0x96, 0x69, 0x3c, 0x87, 0x23, 0xf9, 0x74, 0xac, 0xb1, 0x41, 0x50, - 0xd7, 0xe7, 0x0c, 0x6a, 0xc7, 0xee, 0x6b, 0xc2, 0x2e, 0x3b, 0xe6, 0xb5, 0xae, 0x79, 0x7b, 0xe5, 0xa9, 0xd8, 0x10, - 0xd0, 0xe1, 0x6b, 0x75, 0x83, 0xfc, 0x4b, 0xe0, 0x14, 0x01, 0x20, 0x87, 0xe3, 0x25, 0x8f, 0x7d, 0x9f, 0x66, 0x69, - 0xbd, 0x43, 0xad, 0x45, 0x65, 0x59, 0x86, 0xb5, 0xf7, 0x83, 0x56, 0x0c, 0x4b, 0x4d, 0xff, 0x74, 0x1c, 0xb8, 0x9d, - 0xed, 0x56, 0xc6, 0x2e, 0xe3, 0x59, 0x71, 0xf1, 0xcb, 0x69, 0xa1, 0x5c, 0xbb, 0x79, 0x1b, 0xbf, 0x69, 0xb5, 0x64, - 0x69, 0xad, 0x87, 0xbc, 0xb4, 0x2c, 0x22, 0x10, 0xc0, 0x70, 0xa4, 0xec, 0x62, 0x09, 0xf7, 0x08, 0xab, 0x7b, 0x10, - 0x4a, 0xe6, 0x85, 0x8b, 0xe7, 0x2c, 0x86, 0x44, 0x80, 0xed, 0x0e, 0x15, 0xdb, 0xc2, 0xc5, 0x73, 0xb6, 0xe1, 0x45, - 0xbf, 0x9f, 0xa9, 0x4e, 0x21, 0xeb, 0xce, 0x92, 0x6f, 0x54, 0x73, 0xac, 0xa1, 0x66, 0x6b, 0x93, 0x6c, 0x8d, 0x73, - 0x5b, 0xf1, 0x71, 0xd7, 0x56, 0x7c, 0xac, 0xac, 0x75, 0xe9, 0x5e, 0xef, 0x51, 0x5d, 0x00, 0x5b, 0xff, 0xed, 0xf1, - 0xca, 0xf5, 0x7c, 0x46, 0x00, 0x5f, 0x0b, 0x3e, 0x9e, 0x2c, 0xd0, 0xab, 0x64, 0xe1, 0xdf, 0x0e, 0xd4, 0xf8, 0x3b, - 0x9d, 0xbb, 0x00, 0xe8, 0x4a, 0xca, 0x2b, 0x20, 0xef, 0x20, 0xc7, 0xdc, 0xb2, 0x2b, 0xef, 0x4f, 0xbe, 0xc3, 0xde, - 0xf1, 0x7a, 0xb6, 0x98, 0xb3, 0x1d, 0x38, 0x15, 0x24, 0x03, 0x7b, 0x59, 0xb1, 0x5d, 0x10, 0xdb, 0x09, 0xbf, 0x11, - 0x30, 0xe5, 0x0b, 0x08, 0xe2, 0x0a, 0x6e, 0x21, 0x0e, 0x4f, 0xfe, 0x39, 0xb8, 0x6f, 0x6d, 0xd6, 0xf7, 0xcc, 0xea, - 0x9c, 0x60, 0xcd, 0xac, 0x1e, 0x0c, 0x96, 0xcd, 0x64, 0xd5, 0xef, 0x7b, 0x3b, 0xed, 0xf8, 0x74, 0x27, 0x75, 0x62, - 0xa7, 0xb5, 0x5a, 0x0b, 0xf6, 0x4e, 0x6a, 0x5d, 0x8c, 0xa1, 0x07, 0x88, 0x9f, 0x6e, 0x07, 0xfc, 0xbe, 0x63, 0x6d, - 0x79, 0xef, 0xd8, 0x82, 0xed, 0xe0, 0x12, 0xd4, 0xb4, 0x97, 0xfd, 0x49, 0xe5, 0x82, 0x76, 0xec, 0x92, 0x78, 0x38, - 0x63, 0x56, 0x29, 0x33, 0xeb, 0xa4, 0xba, 0x12, 0x9d, 0x31, 0x9d, 0xb5, 0x9e, 0xcf, 0xd5, 0x7c, 0x52, 0x68, 0x50, - 0xbf, 0x73, 0xe2, 0x23, 0x2a, 0x3a, 0x4f, 0x60, 0x6b, 0x59, 0x41, 0xac, 0xf6, 0x39, 0x58, 0x6b, 0xb5, 0x4b, 0xbf, - 0x97, 0x0f, 0xb8, 0x4d, 0x39, 0xac, 0x03, 0x83, 0x9a, 0x13, 0x2b, 0xea, 0x31, 0xdb, 0x31, 0x6e, 0x7e, 0x7a, 0xf9, - 0x83, 0x13, 0x96, 0xac, 0x58, 0xed, 0x4f, 0x7f, 0x79, 0xe6, 0xe9, 0xef, 0xd4, 0xfe, 0x85, 0xf0, 0x83, 0xf1, 0xbf, - 0x6b, 0xf7, 0xb5, 0x16, 0xa3, 0xb2, 0x55, 0x8e, 0xd0, 0xb8, 0x5b, 0x49, 0x93, 0xe5, 0x67, 0xe1, 0x09, 0x6b, 0xc1, - 0xb3, 0x5c, 0x2f, 0xd1, 0xac, 0x80, 0x15, 0xd6, 0x32, 0x09, 0x57, 0x18, 0xab, 0xa5, 0xad, 0xbe, 0x45, 0xd3, 0x1c, - 0x1f, 0xce, 0xb5, 0x41, 0x99, 0x72, 0x76, 0x46, 0xac, 0x86, 0xcb, 0xb0, 0x34, 0xa1, 0x08, 0xd9, 0xbd, 0x1d, 0xdc, - 0xd8, 0x29, 0x4b, 0x29, 0xc3, 0x39, 0x06, 0x13, 0x1e, 0x89, 0x51, 0x95, 0xef, 0xef, 0x4b, 0x8a, 0x9c, 0xb6, 0xe5, - 0xa0, 0x0a, 0x61, 0x1f, 0x49, 0x94, 0xc0, 0xad, 0x48, 0x0b, 0x45, 0xca, 0xe2, 0x6f, 0x07, 0xe8, 0x02, 0x2f, 0xa0, - 0xae, 0x46, 0xdd, 0xfe, 0x70, 0xc4, 0xc3, 0x47, 0xa6, 0x3e, 0x30, 0x62, 0x49, 0xa0, 0xb6, 0x17, 0x59, 0x7a, 0x07, - 0x2a, 0xfc, 0x1e, 0xae, 0x26, 0x62, 0x3f, 0xb7, 0xa4, 0xa8, 0xc8, 0x46, 0x7a, 0x43, 0x6b, 0xf0, 0x08, 0xad, 0x29, - 0x2f, 0x9d, 0x54, 0x9b, 0x74, 0xde, 0x11, 0x72, 0xac, 0xbe, 0xb5, 0x84, 0xd1, 0xae, 0xe8, 0xc5, 0xbd, 0xa3, 0xf7, - 0x3c, 0x5d, 0xf5, 0xdc, 0x9f, 0xb8, 0x62, 0x9e, 0xdc, 0x46, 0xa0, 0x6e, 0x05, 0xd5, 0xed, 0x83, 0x4a, 0xb0, 0x60, - 0x49, 0xbb, 0x8f, 0xdf, 0xce, 0xda, 0x81, 0xa8, 0x8c, 0x55, 0xfa, 0x96, 0x24, 0xec, 0x89, 0x41, 0xa7, 0x50, 0x95, - 0xdb, 0xdd, 0xd1, 0x16, 0xb8, 0x8e, 0x59, 0x8a, 0x5e, 0xd8, 0x22, 0x77, 0xcb, 0xbf, 0x7b, 0xae, 0xc8, 0xd9, 0x2f, - 0x01, 0xc1, 0xa9, 0xf9, 0x86, 0xf8, 0x72, 0x84, 0x47, 0xd5, 0x2d, 0x70, 0x9c, 0xbe, 0x03, 0xf8, 0x87, 0xc3, 0x25, - 0x68, 0x02, 0x62, 0xc1, 0x7a, 0x69, 0xdc, 0x63, 0xbd, 0xb8, 0xd8, 0xdc, 0x25, 0xf9, 0x06, 0x9c, 0x19, 0x28, 0xd5, - 0xd2, 0x0f, 0x1c, 0xab, 0x05, 0x54, 0x38, 0x98, 0x9d, 0xd4, 0x0b, 0xcb, 0xa8, 0xc7, 0xf4, 0xf9, 0x19, 0xec, 0x1d, - 0x21, 0x01, 0x70, 0xbf, 0xec, 0x03, 0x12, 0xf0, 0xd0, 0x99, 0x1d, 0x10, 0x4e, 0x98, 0x45, 0x55, 0x20, 0x91, 0x1c, - 0xe9, 0x67, 0x8f, 0x99, 0x48, 0xfe, 0x60, 0xd6, 0x73, 0x4e, 0x89, 0x1e, 0xeb, 0xa9, 0x23, 0xa4, 0xc7, 0x7a, 0xd6, - 0x11, 0xd1, 0x63, 0x3d, 0xeb, 0xf8, 0xe8, 0xb1, 0x9e, 0x39, 0x76, 0x7a, 0x10, 0x98, 0x00, 0x91, 0x07, 0xac, 0x47, - 0x93, 0xa9, 0xa7, 0xb8, 0x07, 0x88, 0x06, 0x81, 0xf5, 0xa4, 0x70, 0xde, 0x03, 0xe4, 0x31, 0x12, 0xab, 0x83, 0xde, - 0x7f, 0x8c, 0x9f, 0xf6, 0x8c, 0x8c, 0x3c, 0x6e, 0x1d, 0x56, 0xff, 0xeb, 0x3f, 0x21, 0x00, 0x0e, 0xcf, 0xa6, 0xde, - 0xe5, 0x18, 0xb2, 0xca, 0x32, 0x02, 0xc9, 0x4f, 0x0c, 0xbe, 0x7c, 0x01, 0x50, 0xf5, 0x99, 0xae, 0xd5, 0xe4, 0xa8, - 0x3d, 0xe6, 0xd0, 0x15, 0x03, 0xc0, 0x36, 0x2c, 0x51, 0x55, 0x0b, 0x9b, 0xb0, 0xb8, 0xfd, 0x0c, 0xa3, 0xb9, 0x6c, - 0x7a, 0x41, 0x03, 0xf5, 0x08, 0xc1, 0x2f, 0xad, 0x87, 0xd6, 0x5a, 0xa6, 0x1c, 0xba, 0x36, 0x8a, 0x2a, 0x1b, 0xea, - 0x12, 0x56, 0x6b, 0x11, 0xd5, 0x44, 0x91, 0x72, 0xc9, 0x28, 0x8a, 0xa5, 0x0a, 0xf6, 0x99, 0xb8, 0x83, 0xa8, 0x79, - 0xda, 0x6a, 0xab, 0x60, 0x7f, 0x07, 0x08, 0x6b, 0x61, 0x2d, 0xa4, 0x33, 0xa8, 0xbd, 0xd3, 0x8f, 0x94, 0xbf, 0xbc, - 0x90, 0xdb, 0xb9, 0x85, 0x22, 0xdc, 0x9e, 0x83, 0xf2, 0xa6, 0xae, 0x4a, 0x45, 0x34, 0x5a, 0x02, 0xa5, 0xcc, 0x09, - 0x22, 0x0b, 0x10, 0xc0, 0x71, 0x03, 0x81, 0xcf, 0x6b, 0x7c, 0x02, 0x8d, 0x42, 0x20, 0x3f, 0xb0, 0x0a, 0xd7, 0x1e, - 0xd2, 0x52, 0x6b, 0x44, 0x94, 0x88, 0x1f, 0x5d, 0x3d, 0xc7, 0xf6, 0xd5, 0xd3, 0x58, 0x5b, 0x4a, 0x13, 0xc4, 0x4f, - 0x2c, 0xb6, 0x10, 0x13, 0x44, 0x75, 0x88, 0x8e, 0x60, 0x39, 0x21, 0x44, 0xe1, 0x0f, 0xa1, 0x9f, 0x1a, 0xf8, 0x4b, - 0xb6, 0x2c, 0xf2, 0x9a, 0x60, 0x31, 0x2b, 0x06, 0x68, 0x55, 0x04, 0x9e, 0xe9, 0x6c, 0xa9, 0xcc, 0x69, 0x1e, 0x1d, - 0xd9, 0xc1, 0x79, 0xd7, 0xc1, 0x5e, 0xfa, 0x32, 0x76, 0xb2, 0x6c, 0x1a, 0xb5, 0xb1, 0x21, 0x12, 0x5e, 0x91, 0xbf, - 0xca, 0x52, 0xe3, 0x1c, 0x99, 0xcb, 0xf5, 0x5d, 0x17, 0x77, 0x77, 0xb4, 0x4d, 0x58, 0x85, 0x08, 0x75, 0xdb, 0x50, - 0xb9, 0x14, 0x66, 0x63, 0xd3, 0x34, 0xc0, 0x17, 0x8a, 0x4a, 0xa5, 0x2a, 0xb5, 0x95, 0x4a, 0x4e, 0x78, 0xd7, 0x37, - 0xb5, 0x48, 0x5d, 0x11, 0x6c, 0x63, 0x86, 0x7a, 0x28, 0x37, 0x6a, 0xec, 0xdb, 0x8e, 0x55, 0x7a, 0x87, 0x09, 0x72, - 0x46, 0x5e, 0xe4, 0xe0, 0xa2, 0xa4, 0x20, 0x73, 0x35, 0x84, 0xf9, 0xa3, 0x86, 0x4f, 0x0b, 0xcb, 0x3d, 0x94, 0x80, - 0xd9, 0x51, 0xc3, 0xcb, 0x08, 0x81, 0x88, 0x4b, 0x65, 0x5f, 0x31, 0xf1, 0x7b, 0x0a, 0x66, 0xc9, 0x84, 0xee, 0x45, - 0x2c, 0x8c, 0xd0, 0xc6, 0x27, 0x49, 0x32, 0xf5, 0x34, 0x05, 0x37, 0x72, 0x19, 0xe6, 0x68, 0x84, 0x96, 0x7c, 0xe4, - 0x40, 0xfa, 0x5a, 0x4e, 0x25, 0xf8, 0x88, 0x3a, 0x05, 0x1c, 0xcf, 0xcf, 0x0b, 0xeb, 0x27, 0xcb, 0x25, 0xe6, 0xb2, - 0x36, 0xff, 0x65, 0x47, 0xc7, 0x60, 0x97, 0xa7, 0x89, 0xe3, 0xea, 0x3f, 0xaa, 0x92, 0xe2, 0xe1, 0xe7, 0x34, 0x07, - 0x14, 0xc1, 0xcc, 0x9e, 0x62, 0x7c, 0xec, 0xb3, 0x4c, 0x01, 0x7f, 0xbb, 0xde, 0x5a, 0x32, 0xb1, 0x4b, 0xda, 0xcd, - 0x95, 0xf1, 0x4b, 0x6d, 0xd8, 0x71, 0x70, 0x6e, 0x00, 0x8a, 0xb3, 0x46, 0x87, 0xe5, 0xb5, 0x6e, 0x5b, 0x15, 0x2a, - 0x50, 0xeb, 0x7f, 0xef, 0x16, 0xa6, 0xbc, 0xcd, 0x4b, 0xe5, 0x6d, 0x1e, 0x9a, 0x00, 0x81, 0xc8, 0x0c, 0x79, 0xd6, - 0x74, 0x4c, 0x12, 0xf7, 0x8e, 0x94, 0xb4, 0xef, 0x48, 0xf1, 0xa3, 0x77, 0x24, 0xe4, 0x5b, 0x42, 0x47, 0xf6, 0x25, - 0x27, 0x27, 0x50, 0x66, 0xb0, 0x97, 0xd7, 0x4c, 0xf6, 0x0f, 0x68, 0x2f, 0x9c, 0xcb, 0xf2, 0x8a, 0xbf, 0x13, 0xde, - 0xda, 0x9f, 0xae, 0x4f, 0xbb, 0xaa, 0xde, 0x7e, 0x63, 0x66, 0x1e, 0x0e, 0xc5, 0xe1, 0x50, 0x99, 0xa0, 0xdd, 0x05, - 0x17, 0x83, 0x9c, 0xdd, 0xbb, 0xf1, 0xf1, 0xef, 0x38, 0x8a, 0xd8, 0x4a, 0x79, 0x24, 0x5d, 0xa8, 0xc4, 0xf0, 0xd2, - 0xc0, 0xc3, 0xec, 0xf8, 0x78, 0xb2, 0xbb, 0xba, 0x9f, 0x0c, 0x06, 0x3b, 0xd5, 0xb7, 0x5b, 0x5e, 0xcf, 0x76, 0x73, - 0xf6, 0xc0, 0x6f, 0xa7, 0xdb, 0x60, 0xdf, 0xc0, 0xb6, 0xbb, 0xbb, 0x12, 0x87, 0xc3, 0xee, 0x9a, 0x2f, 0xfc, 0xfd, - 0x03, 0x02, 0x3a, 0xf3, 0xf3, 0x71, 0x1b, 0xe3, 0xe7, 0xa6, 0xed, 0xaa, 0xb5, 0x03, 0x78, 0xfa, 0x1f, 0xbd, 0x9b, - 0xd9, 0x72, 0xee, 0xb3, 0x27, 0xfc, 0x01, 0xfc, 0xf3, 0x71, 0x93, 0x44, 0xea, 0x13, 0xed, 0x32, 0x79, 0x03, 0x0e, - 0xe4, 0x3b, 0x9f, 0xbd, 0xe5, 0x0f, 0xb3, 0xe5, 0x9c, 0x17, 0x87, 0xc3, 0x87, 0x69, 0x88, 0x64, 0x4d, 0x61, 0x45, - 0x2c, 0x29, 0x9e, 0x1f, 0x84, 0xc7, 0xef, 0x45, 0x64, 0x88, 0xb4, 0xdc, 0xbb, 0x43, 0x76, 0xc3, 0x22, 0x3f, 0x80, - 0x0f, 0xb2, 0x9d, 0x3f, 0x91, 0x35, 0xa5, 0xfb, 0xc5, 0x13, 0xff, 0x70, 0xa0, 0xbf, 0xde, 0xfa, 0x87, 0xc3, 0x07, - 0xf6, 0x80, 0xe0, 0xe8, 0x7c, 0x07, 0xfd, 0xa3, 0x6f, 0x1d, 0x50, 0x95, 0xe1, 0xbb, 0xd9, 0x66, 0xee, 0x5f, 0xaf, - 0xd8, 0x1d, 0x70, 0xa1, 0x28, 0x2f, 0xb4, 0x1b, 0xf6, 0x80, 0x5e, 0x67, 0xe4, 0x44, 0x34, 0xdb, 0xcd, 0x7d, 0x16, - 0xe3, 0x73, 0x75, 0x5f, 0x4c, 0xbe, 0x79, 0x5f, 0xdc, 0xb1, 0x6d, 0xf7, 0x7d, 0x51, 0xbe, 0xe9, 0xae, 0x9f, 0x2d, - 0xdb, 0xb1, 0x07, 0x98, 0x61, 0xef, 0xf8, 0x4d, 0x73, 0xec, 0x18, 0xfb, 0xcd, 0x1b, 0x23, 0x80, 0x32, 0x5b, 0xb0, - 0x58, 0x70, 0x50, 0xaa, 0x55, 0xdb, 0x92, 0xc8, 0x2b, 0x1d, 0xa8, 0x36, 0x23, 0xb8, 0xaf, 0x16, 0x72, 0xe6, 0x99, - 0x81, 0xbe, 0xad, 0x10, 0x2d, 0x1c, 0x36, 0xe0, 0x6f, 0xb4, 0x75, 0x8c, 0x61, 0x9a, 0xd5, 0x4c, 0xdb, 0xa2, 0x2e, - 0xbf, 0xef, 0x3d, 0x93, 0xdf, 0xc8, 0xc0, 0x16, 0x22, 0x29, 0x1c, 0xc7, 0x17, 0xcf, 0x4f, 0xf8, 0xaf, 0x5a, 0x1e, - 0xb5, 0xda, 0x2f, 0x94, 0xfa, 0xf4, 0x15, 0x1d, 0xd1, 0xc4, 0xbd, 0x68, 0xcb, 0xb0, 0x46, 0x59, 0x53, 0x4b, 0x87, - 0x61, 0x5c, 0xc3, 0xbe, 0x3c, 0x70, 0xe8, 0x3b, 0x20, 0xd0, 0x56, 0xa9, 0x14, 0x68, 0xe1, 0x18, 0x46, 0x61, 0x16, - 0x52, 0x1e, 0x17, 0x66, 0x29, 0xef, 0xb1, 0x40, 0x8b, 0x5b, 0x75, 0x8f, 0xa9, 0xed, 0x16, 0x44, 0x58, 0xbd, 0x65, - 0x9c, 0x5f, 0x36, 0xaa, 0x70, 0x5b, 0x80, 0xa2, 0x08, 0xca, 0x60, 0x4f, 0x72, 0xdb, 0x42, 0x49, 0xb3, 0x51, 0x58, - 0x8b, 0xbb, 0xa2, 0xdc, 0xf5, 0x1a, 0xb6, 0xc0, 0x0b, 0xaa, 0x7e, 0x42, 0xd8, 0x96, 0x3d, 0xeb, 0x50, 0x2e, 0xd2, - 0x7f, 0xcb, 0xd2, 0xf3, 0xfd, 0xd6, 0x9c, 0xff, 0xe9, 0x2b, 0xfa, 0xa8, 0xfc, 0xf7, 0x2f, 0xe9, 0x27, 0x83, 0x65, - 0xe4, 0x94, 0xfa, 0x25, 0x1a, 0xdd, 0xa6, 0x39, 0x61, 0x6c, 0xf9, 0xfa, 0xe9, 0x77, 0xc8, 0x14, 0x24, 0x87, 0x52, - 0xaa, 0x72, 0xb2, 0x87, 0xbe, 0xf0, 0xba, 0x0f, 0x33, 0xc1, 0x00, 0x84, 0xd7, 0x68, 0x53, 0x4d, 0x98, 0xc4, 0xa3, - 0x2b, 0xf8, 0xbf, 0x11, 0xc4, 0xa0, 0x7d, 0xa2, 0xa8, 0x63, 0xdb, 0x48, 0xd7, 0x6d, 0xe7, 0x20, 0xb9, 0x53, 0x57, - 0xfe, 0xa8, 0x9c, 0xfc, 0x3b, 0x1a, 0x22, 0xaf, 0xb8, 0x42, 0xac, 0x2c, 0xb8, 0xc4, 0x62, 0xa8, 0x48, 0x01, 0xae, - 0x21, 0x88, 0x94, 0x45, 0x49, 0xe1, 0x96, 0x83, 0xaa, 0x08, 0xc0, 0xb8, 0x5a, 0x1d, 0x75, 0x22, 0x7c, 0xdc, 0x5a, - 0x8b, 0x10, 0xac, 0x68, 0xd4, 0xca, 0x5a, 0x81, 0x2f, 0x48, 0x5f, 0x3a, 0x14, 0xc4, 0xf4, 0x28, 0xa4, 0xaa, 0x74, - 0x28, 0x90, 0xe6, 0x50, 0xf1, 0x8d, 0xc1, 0x46, 0x51, 0x91, 0x9e, 0xbf, 0x34, 0x29, 0xb9, 0x34, 0x66, 0x7c, 0x14, - 0x65, 0x24, 0xf2, 0x3a, 0xbc, 0x13, 0xd3, 0x02, 0xf9, 0x46, 0x8f, 0x1f, 0x04, 0x97, 0xf0, 0x6e, 0xc8, 0xbd, 0x02, - 0x6c, 0x09, 0xd8, 0x01, 0xee, 0x95, 0x19, 0xe5, 0x3a, 0xad, 0xeb, 0xb7, 0xd6, 0x43, 0x31, 0x0c, 0x9f, 0x59, 0x02, - 0xdb, 0xd1, 0x3a, 0x3a, 0xd2, 0xc3, 0x87, 0xff, 0x75, 0x55, 0x73, 0xd4, 0xa9, 0x5c, 0xce, 0x8e, 0x27, 0x2c, 0x45, - 0xcc, 0xa0, 0xfb, 0xeb, 0xf6, 0x95, 0x00, 0xba, 0x5d, 0x16, 0xf3, 0x6c, 0xb4, 0x93, 0x7f, 0x4b, 0x37, 0x56, 0x94, - 0x36, 0xf1, 0x2e, 0xeb, 0x8d, 0xfd, 0xe1, 0xe8, 0x3f, 0x9e, 0xbd, 0x9f, 0x10, 0xaa, 0xce, 0x86, 0xad, 0x75, 0x9c, - 0xcb, 0xff, 0xfa, 0xcf, 0x31, 0x59, 0x41, 0x50, 0x10, 0x96, 0x9d, 0x62, 0xa2, 0x82, 0x51, 0xa4, 0x58, 0xf3, 0xf1, - 0x64, 0x8d, 0x3a, 0xe1, 0xb5, 0xbf, 0xd4, 0x3a, 0x61, 0x62, 0x64, 0xa5, 0xf2, 0xd7, 0xac, 0x62, 0x77, 0x2a, 0xb3, - 0x80, 0xcc, 0x83, 0x7c, 0xb2, 0x36, 0x1a, 0xcc, 0x15, 0xaf, 0x67, 0xeb, 0xb9, 0x54, 0x3e, 0x83, 0x29, 0x67, 0x39, - 0x38, 0x59, 0x0a, 0xbb, 0x27, 0x81, 0xa2, 0x35, 0x43, 0xd7, 0xfe, 0x14, 0x5b, 0xf5, 0x3a, 0xad, 0x6a, 0x80, 0x07, - 0x84, 0x18, 0x18, 0x6a, 0xaf, 0x16, 0x1e, 0x5a, 0x0b, 0x60, 0xed, 0x8f, 0x4a, 0x3f, 0x18, 0x4f, 0x96, 0x7c, 0x81, - 0xfc, 0xcb, 0x91, 0xa3, 0x76, 0xef, 0xf7, 0xbd, 0x7b, 0x90, 0x82, 0x23, 0xd7, 0x42, 0x81, 0x44, 0x40, 0x0b, 0xbe, - 0xf1, 0x95, 0x0f, 0xc6, 0x3b, 0xd4, 0x56, 0x83, 0x82, 0xda, 0xd1, 0x2d, 0x8f, 0x1d, 0xbd, 0xf3, 0xfd, 0x09, 0x7d, - 0xf5, 0x42, 0x0b, 0xc7, 0xdf, 0x38, 0x23, 0xd7, 0x6c, 0xd5, 0x21, 0x47, 0x34, 0x93, 0x0e, 0x21, 0x62, 0xc5, 0xd6, - 0xec, 0x1d, 0xa9, 0x9c, 0x3b, 0x87, 0xec, 0xf4, 0x11, 0xaa, 0xf4, 0x5a, 0x8f, 0x6f, 0x27, 0x4a, 0x77, 0x7b, 0xbc, - 0x9b, 0x7c, 0xcf, 0x26, 0x22, 0x06, 0x03, 0xda, 0x20, 0x9c, 0x91, 0x75, 0x88, 0x54, 0x3a, 0x40, 0x08, 0x1c, 0x13, - 0xd0, 0xf4, 0x5f, 0xdf, 0x92, 0x28, 0xe0, 0x48, 0x1b, 0x21, 0x6b, 0xd9, 0xe1, 0x90, 0x83, 0x46, 0xb9, 0xf9, 0xc3, - 0x2b, 0xd4, 0x69, 0x0e, 0xcc, 0xd3, 0x25, 0xec, 0x39, 0x78, 0xa4, 0x17, 0xc7, 0x47, 0xfa, 0x7f, 0x47, 0x13, 0x35, - 0xfe, 0xf7, 0x35, 0x51, 0x4a, 0x8b, 0xe4, 0xa8, 0x96, 0xbe, 0x4b, 0x1d, 0x05, 0x17, 0x79, 0x47, 0x2d, 0x64, 0xcf, - 0xb2, 0x71, 0xa3, 0x9a, 0xf7, 0xff, 0x6b, 0x65, 0xfe, 0xbf, 0xa6, 0x95, 0x61, 0x4a, 0x76, 0x2c, 0xd5, 0xcc, 0x03, - 0xad, 0x62, 0x98, 0xfd, 0x4c, 0x12, 0x22, 0xc3, 0xa5, 0x01, 0x3f, 0xaa, 0x60, 0x1f, 0xa7, 0xd5, 0x3a, 0x0b, 0x77, - 0xa8, 0x44, 0xbd, 0x15, 0x77, 0x69, 0xfe, 0xa2, 0xfe, 0x97, 0x28, 0x0b, 0x98, 0xda, 0x77, 0x65, 0x1a, 0x07, 0x64, - 0xe1, 0xcf, 0xc2, 0x12, 0x27, 0x37, 0xb6, 0xf1, 0x67, 0x39, 0x9e, 0xf6, 0xab, 0xce, 0xcc, 0x03, 0x09, 0xd4, 0x40, - 0xfc, 0x91, 0x73, 0x59, 0x59, 0x3c, 0x20, 0x74, 0xf3, 0x8f, 0x65, 0x59, 0x94, 0x5e, 0xef, 0x73, 0x92, 0x56, 0x67, - 0x2b, 0x51, 0x27, 0x45, 0xac, 0xa0, 0x6c, 0x52, 0x80, 0xd1, 0x87, 0x95, 0x27, 0xe2, 0xe0, 0x0c, 0x81, 0x1a, 0xce, - 0xea, 0x24, 0x04, 0xa0, 0x61, 0x85, 0xb0, 0x7f, 0x06, 0x2d, 0x3c, 0x0b, 0xe3, 0x70, 0x0d, 0x30, 0x39, 0x69, 0x75, - 0xb6, 0x2e, 0x8b, 0xfb, 0x34, 0x16, 0xf1, 0xa8, 0xa7, 0x28, 0x59, 0xde, 0xe4, 0xae, 0x9c, 0xeb, 0xef, 0xff, 0xa0, - 0x00, 0x76, 0x03, 0x66, 0xdb, 0x02, 0x3b, 0x00, 0x48, 0x50, 0x20, 0x5b, 0xa8, 0xd3, 0xe8, 0x4c, 0x2d, 0x15, 0x78, - 0xcf, 0xf5, 0x00, 0x7f, 0x93, 0x03, 0x96, 0x71, 0x5d, 0xc8, 0x80, 0x11, 0x04, 0x30, 0x02, 0x07, 0x25, 0x60, 0xe8, - 0x0c, 0x71, 0x5b, 0x95, 0xb3, 0x16, 0x9a, 0x2b, 0xdd, 0x96, 0xdc, 0x34, 0xca, 0xd9, 0x4a, 0x04, 0xd0, 0x57, 0x37, - 0x25, 0x4e, 0x97, 0xcb, 0x56, 0x12, 0xf6, 0xed, 0x87, 0x76, 0xaa, 0xc8, 0xe3, 0xa3, 0x34, 0xe4, 0x15, 0x78, 0x92, - 0x71, 0x24, 0x89, 0x12, 0xc1, 0x9b, 0xbc, 0x31, 0xe3, 0xf0, 0xa2, 0x4d, 0x39, 0xb5, 0x37, 0xeb, 0x05, 0xe0, 0x3c, - 0x41, 0x5b, 0x06, 0x18, 0x0b, 0x18, 0x9c, 0x0b, 0xb1, 0xe4, 0x29, 0x82, 0x5f, 0x3a, 0x91, 0xc2, 0xb8, 0xcb, 0x61, - 0x98, 0x07, 0x45, 0xef, 0x92, 0xfa, 0xa3, 0xdf, 0x47, 0x6d, 0x32, 0x18, 0x82, 0x4a, 0x00, 0x95, 0x75, 0x83, 0xc4, - 0xc0, 0xaa, 0xb4, 0x90, 0xb8, 0x84, 0x78, 0x99, 0xaf, 0xa6, 0x75, 0x14, 0x7c, 0xa8, 0x27, 0x84, 0x70, 0x82, 0xf1, - 0x21, 0x6e, 0x80, 0x80, 0xc1, 0x2a, 0x2e, 0x30, 0x48, 0x9e, 0x4b, 0x74, 0x7f, 0x3c, 0xdf, 0x31, 0xc0, 0x95, 0xf3, - 0x9e, 0x6a, 0x57, 0x0f, 0xec, 0xe5, 0x2a, 0x5d, 0x32, 0x42, 0x58, 0xf1, 0x7f, 0x11, 0x79, 0xdf, 0x0e, 0x13, 0x50, - 0xdb, 0xc8, 0x1f, 0x83, 0xc4, 0x5c, 0x26, 0x8a, 0x20, 0x1e, 0x65, 0x05, 0x4b, 0xd2, 0x60, 0x33, 0x4a, 0x52, 0xd0, - 0x68, 0x62, 0x0c, 0x99, 0x0a, 0xed, 0x90, 0x34, 0x9a, 0x8d, 0xc9, 0x3e, 0x86, 0xbc, 0x86, 0x8b, 0xc5, 0x02, 0xef, - 0xfb, 0x59, 0xa8, 0x0e, 0xb6, 0xa5, 0x39, 0x04, 0x9c, 0x24, 0xd8, 0x53, 0x57, 0xa4, 0x24, 0xcc, 0x46, 0x9f, 0x42, - 0xce, 0x0d, 0xe8, 0x38, 0x69, 0x0c, 0xd5, 0x07, 0x26, 0xe1, 0x55, 0x84, 0x4e, 0xca, 0x0a, 0x61, 0x01, 0xf7, 0x8d, - 0x8c, 0x46, 0x2b, 0x69, 0x10, 0x78, 0x9b, 0x61, 0x2b, 0xb0, 0x09, 0x0d, 0x7f, 0x91, 0x79, 0x98, 0x56, 0xb3, 0x12, - 0xcc, 0xf9, 0x06, 0x2a, 0x31, 0x9e, 0x2c, 0xaf, 0xf8, 0xc6, 0xc5, 0x4a, 0x4c, 0x66, 0xcb, 0xf9, 0x64, 0x2d, 0xa9, - 0xe6, 0x72, 0x6f, 0xcd, 0x32, 0xb6, 0x84, 0xfd, 0xc3, 0xc0, 0x50, 0x3a, 0xb0, 0xa3, 0xa9, 0xa6, 0x4d, 0x02, 0x4c, - 0xa6, 0x73, 0xce, 0x87, 0x97, 0x88, 0x26, 0xab, 0x53, 0x77, 0x32, 0x55, 0xed, 0xe0, 0x9a, 0x9c, 0xc9, 0xe9, 0x91, - 0x7a, 0xaa, 0x75, 0x2f, 0xf9, 0x68, 0x3b, 0xac, 0x46, 0x5b, 0x3f, 0x00, 0xb7, 0x4e, 0x61, 0xa7, 0xef, 0x86, 0xd5, - 0x68, 0xe7, 0x6b, 0xd8, 0x5d, 0x52, 0x08, 0x54, 0x7f, 0x96, 0x35, 0x99, 0x8b, 0xd7, 0xc5, 0x83, 0x57, 0xb0, 0xe7, - 0xfe, 0x40, 0xff, 0x2a, 0xd9, 0x73, 0xdf, 0x66, 0x72, 0xfd, 0x33, 0xed, 0x1a, 0x8d, 0x99, 0x8e, 0xd7, 0xae, 0xc0, - 0x0a, 0x0d, 0x90, 0x5f, 0xb0, 0xa3, 0xbd, 0xcd, 0x41, 0x20, 0x40, 0xf7, 0x12, 0x1c, 0x45, 0x01, 0x51, 0xd3, 0xaa, - 0xf2, 0xe8, 0x74, 0xef, 0xef, 0xf1, 0x8d, 0x10, 0xb0, 0xc9, 0x53, 0xeb, 0xde, 0x32, 0xf6, 0x0f, 0x07, 0x08, 0xa1, - 0x97, 0xd3, 0x6f, 0xb4, 0x65, 0xf5, 0x68, 0xc7, 0x72, 0xdf, 0x30, 0xea, 0x29, 0x18, 0xc3, 0xd0, 0x85, 0x55, 0x8c, - 0xe4, 0x19, 0x90, 0x35, 0x7e, 0x83, 0xe8, 0x02, 0x16, 0xbd, 0xde, 0xeb, 0x23, 0x1a, 0x44, 0x40, 0xa5, 0xd7, 0xfc, - 0xa5, 0xc8, 0xe7, 0xaa, 0x10, 0xbd, 0xf7, 0xd6, 0xce, 0x9b, 0x19, 0xc9, 0x32, 0x69, 0xa4, 0xda, 0xad, 0x2c, 0xd6, - 0x95, 0x37, 0x3b, 0x21, 0x5d, 0xcc, 0x31, 0x54, 0x06, 0x8f, 0x03, 0x50, 0x7a, 0xfe, 0x25, 0xf4, 0x4a, 0x86, 0x4c, - 0xb3, 0x44, 0x33, 0xbb, 0x6b, 0xfc, 0xc9, 0x2a, 0xf5, 0x62, 0x44, 0xcc, 0x06, 0xb6, 0x10, 0xb7, 0x45, 0xa5, 0xdb, - 0xa2, 0x50, 0xb6, 0x28, 0xd2, 0x87, 0xda, 0x99, 0xee, 0xcc, 0xc2, 0x67, 0x95, 0x69, 0xdf, 0xdb, 0xcc, 0x8c, 0x0d, - 0xd0, 0x76, 0x11, 0xbe, 0x81, 0x0e, 0x54, 0x08, 0xf9, 0x8f, 0x88, 0x88, 0x44, 0xc0, 0x2e, 0xa7, 0xee, 0xc4, 0xa6, - 0x43, 0x32, 0x0f, 0x31, 0x2b, 0xd4, 0x28, 0x2f, 0x79, 0x72, 0x34, 0x20, 0x15, 0xa1, 0x6e, 0xf7, 0xfb, 0xe7, 0x4b, - 0x17, 0xd4, 0x7e, 0x4d, 0xb1, 0x63, 0x74, 0x53, 0xc0, 0xb9, 0xe0, 0x51, 0xde, 0x73, 0xef, 0x1c, 0xd0, 0x1c, 0xdb, - 0x53, 0x64, 0x0d, 0x38, 0xbd, 0xed, 0x42, 0x80, 0xed, 0xb3, 0x66, 0x6b, 0x7f, 0xb2, 0xba, 0x8a, 0xa6, 0x5e, 0xc9, - 0x67, 0xba, 0x8b, 0x12, 0xb7, 0x8b, 0x62, 0xd9, 0x45, 0x9b, 0x06, 0x82, 0x1d, 0x57, 0x7e, 0x00, 0xbc, 0xa1, 0x51, - 0xbf, 0x5f, 0xb6, 0x7a, 0xf6, 0xe4, 0x6b, 0xc7, 0x3d, 0x9b, 0xf9, 0xac, 0x34, 0x3d, 0xfb, 0x6b, 0xea, 0xf6, 0xac, - 0x9c, 0xec, 0x45, 0xe7, 0x64, 0x9f, 0xce, 0xe6, 0x81, 0xe0, 0x72, 0xe7, 0x3e, 0xcf, 0xa7, 0x7a, 0xda, 0x55, 0x7e, - 0xd0, 0x1a, 0x22, 0xf3, 0x85, 0xcf, 0x55, 0xf7, 0xba, 0x82, 0x05, 0x2c, 0xc1, 0xdd, 0x7a, 0x69, 0xfe, 0x2b, 0x76, - 0x7f, 0x2f, 0xe8, 0xa5, 0xf9, 0x6f, 0xf4, 0x27, 0x05, 0x70, 0x00, 0x1a, 0x53, 0xbb, 0x05, 0x1e, 0x62, 0xa8, 0xa0, - 0x70, 0x37, 0x2b, 0xe7, 0x5e, 0x0d, 0x70, 0x98, 0xa4, 0x6f, 0x68, 0xf5, 0x4a, 0x8b, 0x5d, 0x2f, 0x93, 0xbd, 0x02, - 0x3c, 0x54, 0x21, 0x0f, 0x0f, 0x87, 0xa8, 0x63, 0xd8, 0x41, 0x1d, 0x01, 0xc3, 0x1e, 0x42, 0x63, 0x0b, 0x3c, 0x1f, - 0x3f, 0x67, 0x7c, 0x2f, 0x40, 0x6d, 0x84, 0xf0, 0x78, 0xb5, 0x28, 0x43, 0x6c, 0xd9, 0x5b, 0xa4, 0x92, 0xfa, 0x59, - 0x20, 0xca, 0x68, 0x15, 0xd0, 0x56, 0x7b, 0xcc, 0xd2, 0x78, 0x03, 0xa1, 0x62, 0xa9, 0x8f, 0x21, 0x34, 0x70, 0xf8, - 0x1d, 0x0e, 0x20, 0xc1, 0x97, 0x5c, 0x93, 0xcd, 0xbd, 0xcd, 0xef, 0x69, 0x9f, 0x3f, 0x1c, 0xce, 0x2f, 0x11, 0x94, - 0x2e, 0x85, 0x8f, 0x54, 0x22, 0xaa, 0xa7, 0xb8, 0x29, 0x21, 0x9b, 0x25, 0x2b, 0xfd, 0xe0, 0x57, 0xf5, 0x0b, 0x00, - 0x64, 0x21, 0xd0, 0x26, 0x32, 0xfb, 0xd3, 0x99, 0x8a, 0x2e, 0x00, 0x0e, 0xf1, 0xc7, 0x4f, 0x10, 0x7d, 0x43, 0xcb, - 0xb4, 0x7c, 0x9c, 0xf0, 0x10, 0xb4, 0xb6, 0xa4, 0x93, 0x88, 0x95, 0x02, 0x1b, 0x22, 0xe1, 0xfb, 0xfd, 0xf3, 0x58, - 0xd2, 0x81, 0x46, 0xad, 0xee, 0x8d, 0x5b, 0xdd, 0x2b, 0x5f, 0xd7, 0x9d, 0xdc, 0xf8, 0xa0, 0x68, 0x9f, 0xcd, 0x1b, - 0x95, 0xef, 0xfb, 0x3a, 0x67, 0x77, 0xba, 0x77, 0xe4, 0x9c, 0xf8, 0xfe, 0x1e, 0x42, 0xd1, 0x43, 0x53, 0x64, 0x59, - 0x12, 0x06, 0xb4, 0xd6, 0xae, 0x3d, 0xcb, 0xe8, 0xe0, 0xb5, 0x6f, 0x08, 0x11, 0x79, 0x8a, 0x4f, 0x42, 0x6e, 0x71, - 0x7c, 0x50, 0xa0, 0x7f, 0x66, 0xfc, 0x99, 0x13, 0x3f, 0x6c, 0xf5, 0x0b, 0xe0, 0xdc, 0x74, 0xef, 0xdd, 0x89, 0x59, - 0x8f, 0xa1, 0x94, 0x8d, 0xff, 0xfb, 0x7d, 0x22, 0x0b, 0x74, 0x3a, 0xa2, 0x61, 0x20, 0xb8, 0x8b, 0xea, 0xff, 0x5e, - 0xf1, 0xba, 0x67, 0xad, 0xce, 0x97, 0x9f, 0x3a, 0x3d, 0xe9, 0xd5, 0xcb, 0xb8, 0x07, 0x54, 0xe8, 0x00, 0xe1, 0xbc, - 0xee, 0x37, 0x6c, 0xf7, 0xdd, 0x2f, 0xef, 0x8e, 0x5e, 0x06, 0x36, 0x29, 0x12, 0xdb, 0x4a, 0x3e, 0xeb, 0x81, 0xc2, - 0xaf, 0xc7, 0x7a, 0x75, 0xb1, 0xee, 0xb1, 0x1e, 0x6a, 0x01, 0xd1, 0xc3, 0x02, 0xd4, 0x7f, 0x3d, 0xfb, 0x34, 0x14, - 0x0e, 0xb2, 0x71, 0xaa, 0x40, 0x91, 0x05, 0xbf, 0x16, 0xa3, 0x75, 0x41, 0x80, 0xc8, 0x96, 0x90, 0x56, 0x9d, 0xcc, - 0x1e, 0x97, 0x5a, 0x92, 0xc1, 0x37, 0x01, 0x99, 0x1d, 0x58, 0x39, 0x41, 0xe9, 0xb8, 0x35, 0xe0, 0xca, 0x16, 0x8f, - 0x76, 0xfb, 0xd3, 0x20, 0x3b, 0x6b, 0x4e, 0x1a, 0xed, 0xc3, 0x3e, 0xcd, 0x03, 0x04, 0x22, 0x99, 0x8a, 0x20, 0xd7, - 0xdc, 0x5b, 0xd2, 0x47, 0x87, 0x73, 0x5e, 0xc8, 0x3f, 0xa7, 0x52, 0x87, 0x38, 0x94, 0x58, 0x03, 0x81, 0xca, 0x33, - 0x54, 0x39, 0x6c, 0x90, 0xe3, 0x9f, 0x1d, 0xc9, 0x4c, 0x62, 0xb2, 0xc8, 0xdd, 0x9a, 0xa9, 0xf0, 0x03, 0xc1, 0xc7, - 0x2c, 0xe7, 0xc0, 0x05, 0x36, 0x9b, 0xfb, 0x6a, 0x8a, 0x8b, 0x2b, 0xf0, 0xc7, 0x14, 0x7e, 0xc5, 0x53, 0xd8, 0x69, - 0xf7, 0xeb, 0xa2, 0x4a, 0x51, 0xb7, 0x51, 0x58, 0x54, 0xb2, 0x60, 0x5a, 0x43, 0x9a, 0xe8, 0x30, 0xfa, 0x83, 0x9c, - 0x81, 0x82, 0x90, 0x5f, 0x36, 0x0d, 0x30, 0x52, 0xc9, 0xe5, 0x41, 0x95, 0x04, 0x5e, 0x80, 0x6d, 0x50, 0xb1, 0x75, - 0x01, 0x41, 0xb6, 0x49, 0x51, 0xa6, 0x5f, 0x8b, 0xbc, 0x0e, 0xb3, 0xa0, 0x1a, 0xa5, 0xd5, 0x4f, 0xfa, 0x27, 0x30, - 0x6f, 0x53, 0x31, 0xaa, 0x55, 0x4c, 0x7e, 0xa3, 0xdf, 0x2f, 0x06, 0xad, 0x0f, 0x19, 0x7c, 0xf4, 0xda, 0x34, 0xf8, - 0x93, 0xd3, 0x60, 0x87, 0x89, 0x46, 0x00, 0x24, 0x73, 0x6a, 0xc9, 0x43, 0xd1, 0x1f, 0x41, 0x8e, 0x35, 0xaa, 0x9c, - 0x82, 0xc1, 0xfa, 0x8f, 0x47, 0x3b, 0x30, 0xf5, 0xe2, 0x68, 0x4b, 0x76, 0xd0, 0xca, 0x37, 0xc0, 0xfd, 0x1a, 0xd9, - 0x62, 0x96, 0x03, 0x34, 0x7b, 0x8d, 0xc8, 0xf8, 0xe4, 0x05, 0x30, 0x66, 0xeb, 0x2c, 0x8c, 0x44, 0x1c, 0x8c, 0x55, - 0x63, 0xc6, 0x0c, 0x0c, 0x5c, 0xa0, 0x6b, 0x99, 0x94, 0xa4, 0x21, 0x1d, 0x0c, 0x58, 0x29, 0x5b, 0x38, 0xe0, 0x45, - 0x73, 0xdc, 0x8e, 0x37, 0x2d, 0x1a, 0x0f, 0x6c, 0x17, 0xdb, 0xdf, 0xbf, 0x2c, 0xb6, 0xef, 0xc2, 0x2d, 0xe9, 0x15, - 0x72, 0x96, 0xd0, 0xcf, 0x9f, 0x64, 0x9f, 0x35, 0x9c, 0x9c, 0x0a, 0xcd, 0xd0, 0x52, 0x24, 0x94, 0xe2, 0x9d, 0x9e, - 0x14, 0x18, 0xcb, 0x58, 0xf8, 0x7b, 0xe0, 0x9c, 0x2e, 0x14, 0x91, 0x3b, 0x70, 0x1c, 0xdf, 0x40, 0x05, 0xa3, 0x86, - 0x83, 0x97, 0x31, 0x6c, 0x8b, 0x62, 0x16, 0x12, 0x4e, 0x21, 0x5c, 0xac, 0xb2, 0x7e, 0x5f, 0xfe, 0xa2, 0x2e, 0xba, - 0xc8, 0x64, 0xdd, 0x27, 0xe1, 0xc8, 0x8c, 0xe5, 0xd4, 0x0b, 0xc9, 0xf3, 0x9e, 0x27, 0xd3, 0xe4, 0x59, 0x1e, 0x44, - 0x00, 0xf9, 0x1c, 0xde, 0x87, 0x69, 0x06, 0x56, 0x69, 0x52, 0x7e, 0x84, 0xd2, 0x17, 0x9f, 0x57, 0x7e, 0xa0, 0xb3, - 0xe7, 0x26, 0x19, 0xde, 0xac, 0x5a, 0x6f, 0x52, 0xeb, 0xba, 0x78, 0xc0, 0xdf, 0x3b, 0x83, 0x8d, 0x73, 0x9d, 0x09, - 0x0e, 0xbc, 0x48, 0x6a, 0xbd, 0x66, 0xfc, 0x3a, 0xc3, 0x75, 0xa9, 0xda, 0xe8, 0xa3, 0x10, 0x9d, 0x43, 0xa6, 0x02, - 0x14, 0x8a, 0xb4, 0x7f, 0x50, 0x6a, 0x65, 0x52, 0x69, 0x23, 0x01, 0x74, 0x0f, 0x93, 0x06, 0x5b, 0x0c, 0x65, 0x2c, - 0x4d, 0xa2, 0xdc, 0x69, 0x10, 0x57, 0xf6, 0xe7, 0x4a, 0xe2, 0xd0, 0xb2, 0x48, 0xfe, 0xbd, 0xeb, 0xe9, 0x2b, 0xa4, - 0xee, 0x64, 0x81, 0xcc, 0x18, 0x2f, 0xf2, 0xf8, 0x33, 0x10, 0x66, 0x83, 0x36, 0x2a, 0x0a, 0x21, 0x64, 0x83, 0x18, - 0x34, 0x5e, 0xe4, 0xf1, 0x4b, 0x45, 0xe3, 0x21, 0x1f, 0x45, 0xbe, 0xfa, 0xab, 0xd4, 0x7f, 0x85, 0x3e, 0x33, 0xc1, - 0x23, 0x54, 0x13, 0xfd, 0xbb, 0xe7, 0xb3, 0x7b, 0x50, 0x1b, 0x46, 0x61, 0x66, 0xca, 0xaf, 0x7c, 0x53, 0x9c, 0xbd, - 0xfe, 0x8a, 0xae, 0xb2, 0xad, 0xfb, 0xd1, 0xa7, 0x23, 0x02, 0x6b, 0x63, 0x74, 0xc5, 0x8d, 0x01, 0xe4, 0x30, 0x79, - 0xbf, 0xa2, 0xb4, 0x1c, 0xd2, 0x20, 0x74, 0xd0, 0x10, 0xf4, 0x4a, 0xa2, 0x0f, 0x24, 0x16, 0x31, 0x86, 0x17, 0xe2, - 0x19, 0xa9, 0xc9, 0x44, 0x43, 0xbc, 0x22, 0xf6, 0x43, 0xb4, 0xe4, 0xd4, 0x44, 0x37, 0xc2, 0x14, 0x03, 0x89, 0x9d, - 0x41, 0x72, 0x92, 0xd4, 0xca, 0x2f, 0x9e, 0x49, 0xc2, 0x12, 0x3b, 0x0f, 0x31, 0x98, 0xd4, 0xd2, 0x9d, 0xde, 0x54, - 0xe9, 0xeb, 0x91, 0x96, 0x83, 0xf6, 0x01, 0xd8, 0xa5, 0xa4, 0xf7, 0x4f, 0x0a, 0x45, 0x7c, 0x0c, 0xe3, 0x18, 0xc2, - 0xb7, 0x88, 0xea, 0x0a, 0x9c, 0x6b, 0x05, 0x1a, 0xab, 0x81, 0x87, 0x66, 0x56, 0xcd, 0x87, 0x9c, 0x7e, 0x2a, 0x2d, - 0x7f, 0x8c, 0x68, 0x6c, 0xb4, 0x6e, 0x0e, 0x87, 0x3d, 0xad, 0x7a, 0xe9, 0x1c, 0x74, 0xd9, 0x4c, 0x62, 0xe2, 0x06, - 0xd2, 0xf5, 0xa3, 0xdf, 0x4c, 0xd8, 0x8b, 0xa8, 0x90, 0x4b, 0x21, 0x28, 0x68, 0x75, 0x20, 0x70, 0x28, 0xbc, 0x45, - 0x99, 0x2f, 0x62, 0xda, 0x40, 0x18, 0x7c, 0x7e, 0x20, 0x3f, 0xdf, 0x14, 0xa4, 0x62, 0xc7, 0xba, 0xf6, 0xfb, 0x9b, - 0xd2, 0x03, 0x3c, 0x39, 0x93, 0xe4, 0x69, 0x33, 0x84, 0x15, 0x01, 0x34, 0x66, 0x35, 0x59, 0x9c, 0x70, 0x65, 0x0e, - 0x3f, 0x55, 0x5e, 0xc9, 0x52, 0xa6, 0xce, 0x53, 0xbd, 0x00, 0xa2, 0x8e, 0x37, 0x68, 0x45, 0xea, 0x57, 0xe8, 0xec, - 0x35, 0x2b, 0x21, 0xe3, 0xe1, 0x39, 0xe7, 0xe9, 0xe8, 0x81, 0x25, 0x3c, 0xc2, 0xbf, 0x92, 0x89, 0x3e, 0xfc, 0x1e, - 0x38, 0xdc, 0x8c, 0x13, 0x1e, 0xb9, 0xcd, 0xde, 0x57, 0xe1, 0x0a, 0x6e, 0xa6, 0x05, 0x20, 0xb9, 0x05, 0x49, 0x13, - 0x50, 0x42, 0x22, 0x13, 0x32, 0x6b, 0x4a, 0x7e, 0x69, 0x69, 0x1b, 0xac, 0x61, 0xd2, 0x79, 0xc0, 0x8b, 0x56, 0x1f, - 0xad, 0x26, 0xda, 0x65, 0x96, 0xcf, 0x87, 0x38, 0x43, 0x35, 0xc7, 0xdd, 0x19, 0xfc, 0x1c, 0xf0, 0x8a, 0x55, 0x4d, - 0x3a, 0xda, 0x0d, 0xb8, 0xf0, 0xe4, 0x3a, 0x4f, 0x47, 0x5b, 0xfc, 0x25, 0xf7, 0x07, 0x80, 0x0e, 0xa6, 0x2e, 0x81, - 0x3f, 0x55, 0x5b, 0x4d, 0xa5, 0x5e, 0xb6, 0xf6, 0xeb, 0xba, 0xb3, 0x5a, 0xb9, 0x67, 0x5d, 0x86, 0xf6, 0xc8, 0x90, - 0x33, 0x66, 0xc0, 0x9f, 0x33, 0x96, 0xfc, 0x39, 0x63, 0xc5, 0x9f, 0x33, 0x6e, 0x8c, 0x0c, 0xa0, 0x04, 0xf7, 0x92, - 0x5f, 0xef, 0x11, 0x33, 0xc4, 0x6a, 0x50, 0x09, 0xac, 0x2c, 0xe5, 0xdc, 0x47, 0x4e, 0x31, 0xe5, 0x94, 0xe1, 0xa5, - 0xd3, 0x99, 0x3b, 0x90, 0xf3, 0x60, 0xe6, 0x0e, 0x93, 0xb3, 0x3e, 0xc5, 0xb1, 0x34, 0x26, 0x45, 0x05, 0xe9, 0x9c, - 0x0e, 0x37, 0xaf, 0x8e, 0xf3, 0x84, 0x65, 0x7c, 0xdc, 0x3e, 0x53, 0x20, 0xc4, 0x16, 0xcf, 0x90, 0x48, 0xa9, 0x9a, - 0xe5, 0x36, 0x7f, 0x38, 0xd4, 0xa3, 0x07, 0xbd, 0xd3, 0xc3, 0xaf, 0x84, 0xbd, 0xcc, 0x3c, 0xfb, 0x04, 0x01, 0x4c, - 0x12, 0x79, 0x26, 0xe1, 0xe8, 0xc7, 0x72, 0xf4, 0x37, 0x0d, 0xff, 0x9a, 0xa1, 0xba, 0x3b, 0x04, 0x26, 0xb6, 0xec, - 0xc0, 0x21, 0x38, 0x5d, 0x55, 0x22, 0x01, 0x07, 0x9b, 0x0d, 0x8b, 0xf4, 0x1e, 0x0f, 0x71, 0x3e, 0x28, 0x7c, 0x84, - 0x86, 0x19, 0xbd, 0xdf, 0xdf, 0x08, 0xaf, 0x92, 0xad, 0x3c, 0x1c, 0x12, 0xeb, 0x2e, 0xec, 0xe8, 0xe3, 0x68, 0x8f, - 0x12, 0x6a, 0x3f, 0xaa, 0xf5, 0xa6, 0x52, 0x0f, 0x72, 0xb3, 0x0b, 0x89, 0x41, 0xc5, 0x52, 0x7d, 0x7a, 0xa5, 0xfa, - 0x50, 0xb3, 0xce, 0xef, 0xea, 0xb8, 0x4f, 0xc5, 0x68, 0x2d, 0x27, 0x04, 0xb8, 0x0e, 0x12, 0x8d, 0x0e, 0x80, 0x71, - 0xb6, 0xd9, 0xf2, 0x52, 0x5b, 0x27, 0x4a, 0xc7, 0x71, 0xae, 0x8f, 0xe3, 0xc3, 0x41, 0x8a, 0x19, 0x97, 0x47, 0x62, - 0xc6, 0x65, 0x03, 0xf0, 0x66, 0x9d, 0x07, 0xf5, 0xe1, 0x70, 0x49, 0x97, 0x22, 0xd3, 0xd9, 0x46, 0xf9, 0x59, 0x8f, - 0x1e, 0x9e, 0x25, 0x68, 0xee, 0xad, 0xb0, 0xf7, 0x22, 0xd9, 0x9e, 0xc9, 0x3a, 0xf5, 0x32, 0xf2, 0xe9, 0x85, 0x7b, - 0x76, 0xc9, 0xd5, 0x0f, 0xab, 0xaf, 0xa7, 0xbf, 0x0a, 0x2f, 0x62, 0x15, 0xed, 0xd6, 0x25, 0x13, 0xf6, 0x96, 0x52, - 0x49, 0xab, 0xbc, 0x7c, 0xba, 0xf1, 0x03, 0xcc, 0x4c, 0x7b, 0xfa, 0x20, 0x1b, 0x51, 0xfd, 0x59, 0x89, 0x5a, 0x19, - 0x26, 0x0b, 0xe7, 0x25, 0x53, 0x4f, 0x06, 0x3c, 0x66, 0x25, 0x8f, 0x64, 0xa7, 0x37, 0x06, 0x41, 0x00, 0xeb, 0x9c, - 0xb4, 0xea, 0x8c, 0xa3, 0xd1, 0xaa, 0x72, 0x71, 0xba, 0xca, 0x05, 0x86, 0xdb, 0xad, 0xd9, 0x46, 0xd5, 0x59, 0x6e, - 0x6a, 0x95, 0xf2, 0x1d, 0xc0, 0xc7, 0xb2, 0xca, 0x05, 0x1d, 0x53, 0xa6, 0xce, 0x1b, 0x08, 0xc6, 0x56, 0x35, 0x2e, - 0x9c, 0x1a, 0x17, 0x3c, 0xa2, 0x76, 0x37, 0x4d, 0x3d, 0xda, 0x02, 0x4b, 0xe9, 0x68, 0xc7, 0x4b, 0x54, 0x29, 0xfc, - 0x4d, 0xf0, 0x7d, 0x18, 0xc7, 0x2f, 0x8b, 0xad, 0x3a, 0x10, 0x6f, 0x8b, 0x2d, 0xd2, 0xbe, 0xc8, 0xbf, 0x10, 0x07, - 0xbc, 0xd6, 0x35, 0xe5, 0xb5, 0x35, 0xa7, 0x81, 0xad, 0x61, 0xa4, 0xa4, 0x70, 0x6e, 0xfe, 0x3c, 0x1c, 0x68, 0x65, - 0xd7, 0xea, 0xae, 0x50, 0xeb, 0x31, 0x87, 0x0d, 0x7b, 0x91, 0x85, 0x3b, 0x51, 0x82, 0x23, 0x97, 0xfc, 0xeb, 0x70, - 0xd0, 0x2a, 0x4b, 0x75, 0xa4, 0xcf, 0xf6, 0x5f, 0x83, 0x31, 0x43, 0x97, 0x26, 0x60, 0xd9, 0x18, 0xc9, 0xbf, 0x9a, - 0x66, 0xde, 0x30, 0x59, 0x33, 0x85, 0xe3, 0xd0, 0x30, 0x42, 0x1a, 0xd0, 0x6d, 0x50, 0x1b, 0x9e, 0xcc, 0x37, 0x55, - 0xf9, 0xd5, 0x1d, 0xa9, 0xf6, 0x83, 0xe1, 0xe5, 0x44, 0x9c, 0xd3, 0x25, 0x49, 0x3d, 0x95, 0x50, 0x12, 0x82, 0x5d, - 0xfa, 0x40, 0x4e, 0xac, 0x80, 0xac, 0x65, 0x2c, 0xbf, 0xd5, 0x03, 0x42, 0xff, 0x69, 0xb7, 0x5e, 0xe8, 0x3f, 0x4d, - 0xb3, 0x85, 0xba, 0xfe, 0x30, 0xb9, 0xef, 0xe8, 0xf5, 0x07, 0x87, 0x77, 0xea, 0xaa, 0xe2, 0x2a, 0x1e, 0xd5, 0x86, - 0x49, 0x6e, 0x94, 0x85, 0xbb, 0x62, 0x53, 0xab, 0xe5, 0xe9, 0x38, 0x8c, 0xc0, 0x8c, 0xa0, 0x00, 0x59, 0xd7, 0x6d, - 0x44, 0x0c, 0x2b, 0xb9, 0x4c, 0xc8, 0x27, 0x04, 0x64, 0x51, 0x6a, 0x9c, 0x8f, 0x5b, 0xa0, 0x12, 0xc1, 0xe0, 0x34, - 0xb4, 0x56, 0xdd, 0xe4, 0x27, 0x95, 0x8d, 0xdd, 0x01, 0x39, 0x24, 0x99, 0x2c, 0xee, 0x46, 0xb7, 0x62, 0x59, 0x94, - 0xe2, 0x67, 0xac, 0x87, 0x6b, 0xb6, 0x70, 0x9f, 0x01, 0xa1, 0xfd, 0x44, 0x69, 0x6f, 0x22, 0x4d, 0xd0, 0x7d, 0xc7, - 0x56, 0x00, 0x32, 0x80, 0xa2, 0xae, 0x76, 0xeb, 0x73, 0x7e, 0x8e, 0xa4, 0x19, 0x0e, 0xa3, 0xdb, 0xa7, 0x77, 0xc1, - 0xdd, 0xe0, 0x12, 0xb5, 0xd2, 0x97, 0x2c, 0x6e, 0x61, 0x50, 0xed, 0xcd, 0x12, 0x0e, 0x6a, 0x66, 0xad, 0x8d, 0x40, - 0x30, 0xd9, 0x43, 0x41, 0xc5, 0x5c, 0xc1, 0x3e, 0x28, 0x58, 0x4b, 0x5e, 0x07, 0x87, 0x5b, 0xfb, 0xb2, 0x52, 0x5c, - 0x3c, 0xbf, 0x48, 0x5a, 0x17, 0x96, 0xf2, 0xe2, 0x79, 0x03, 0x06, 0x97, 0x23, 0x6c, 0xaa, 0xca, 0x9f, 0x6c, 0x00, - 0x74, 0x2b, 0xa2, 0x88, 0x17, 0xa5, 0xb0, 0x6d, 0xe5, 0x33, 0x27, 0x6c, 0xb0, 0x61, 0x0f, 0x70, 0xaf, 0x0c, 0x4a, - 0x06, 0x17, 0x62, 0xdc, 0x6e, 0x76, 0x01, 0xae, 0x60, 0x28, 0x8c, 0xad, 0xf9, 0x9b, 0xcc, 0x8b, 0x94, 0x80, 0x9b, - 0x21, 0xca, 0xd7, 0x06, 0x4e, 0x26, 0x3d, 0xb9, 0x96, 0x2c, 0x06, 0x2c, 0x68, 0xf0, 0x1d, 0xb5, 0xfe, 0xce, 0xe4, - 0xdf, 0x78, 0x7a, 0xe8, 0x07, 0x5f, 0x32, 0x6f, 0xe9, 0xb3, 0x37, 0x95, 0x8c, 0xd6, 0x24, 0x51, 0x5e, 0x3d, 0x5c, - 0x82, 0xdc, 0xb0, 0x1c, 0x3d, 0xb0, 0x25, 0x88, 0x13, 0xcb, 0x51, 0x42, 0x19, 0x5d, 0xe1, 0x5e, 0x65, 0xb6, 0x4c, - 0x04, 0x52, 0x1c, 0x58, 0x4a, 0xb9, 0xb7, 0x58, 0x07, 0x4b, 0xdc, 0x9f, 0x48, 0x2e, 0xa0, 0xe4, 0x01, 0x94, 0x2b, - 0x05, 0x04, 0x7c, 0x3a, 0x80, 0xf2, 0xa5, 0xbc, 0x08, 0x7f, 0xe2, 0x44, 0x0d, 0x96, 0xa3, 0x87, 0x86, 0xfd, 0xe4, - 0x85, 0x96, 0xfd, 0xe1, 0x4e, 0x6b, 0x1a, 0x56, 0xfc, 0x0e, 0xa6, 0xc5, 0xc4, 0xed, 0xcb, 0x95, 0x5d, 0x15, 0x9f, - 0xad, 0xd4, 0xd9, 0x4d, 0x0d, 0x49, 0xd8, 0x37, 0x64, 0x15, 0xe0, 0x60, 0x55, 0xc4, 0x3d, 0xcb, 0x72, 0x1f, 0x46, - 0x7f, 0x6e, 0xd2, 0x52, 0x58, 0xa8, 0x92, 0xfe, 0xbe, 0x29, 0x05, 0x52, 0x99, 0xe8, 0x44, 0x0b, 0xc1, 0x15, 0x18, - 0x04, 0xee, 0x45, 0x5e, 0x03, 0x60, 0x0c, 0xb8, 0x14, 0x28, 0xcb, 0xb6, 0x84, 0x90, 0xea, 0x7e, 0x06, 0x6a, 0x3b, - 0x71, 0x9f, 0x46, 0x64, 0x2d, 0x44, 0x5f, 0x05, 0x63, 0xe6, 0xbc, 0x94, 0x6e, 0xb1, 0xe9, 0x6a, 0xb3, 0xba, 0x41, - 0xe7, 0xd2, 0x96, 0x9b, 0x9f, 0xb0, 0xc5, 0x5a, 0x81, 0xb2, 0x09, 0x49, 0xdb, 0x39, 0xcf, 0x51, 0x36, 0xa1, 0xa5, - 0xbd, 0xa7, 0x1e, 0x15, 0xaa, 0x93, 0xad, 0x97, 0xaa, 0xa9, 0x45, 0x58, 0x2d, 0x2e, 0x2a, 0x3f, 0x00, 0xdd, 0x54, - 0x5a, 0xbd, 0xa8, 0x6b, 0x34, 0x85, 0x5a, 0x2d, 0x1c, 0x37, 0xda, 0xd9, 0x74, 0x99, 0xde, 0x21, 0xce, 0xaa, 0xb4, - 0x43, 0xff, 0x92, 0x69, 0xd7, 0xcb, 0x8e, 0x7e, 0x33, 0xae, 0x2e, 0x70, 0x21, 0x36, 0xe0, 0x73, 0xee, 0x2f, 0xaf, - 0xf7, 0x3c, 0xee, 0xf9, 0x87, 0x03, 0xb2, 0x27, 0xb5, 0x3f, 0x54, 0x1f, 0xbb, 0x82, 0x21, 0x0b, 0xa3, 0xd4, 0x5f, - 0xa4, 0xbc, 0xf7, 0x04, 0xc7, 0xfd, 0x4b, 0xd5, 0x63, 0x3f, 0x65, 0x7c, 0x5f, 0x17, 0x9b, 0x28, 0xa1, 0xa8, 0x86, - 0xde, 0xaa, 0xd8, 0x54, 0x22, 0x2e, 0x1e, 0xf2, 0x1e, 0xc3, 0x64, 0x18, 0x0b, 0x99, 0x0a, 0x7f, 0xca, 0x54, 0xf0, - 0x08, 0xa1, 0xc4, 0xcd, 0xba, 0x47, 0xda, 0x4d, 0x88, 0x53, 0xaa, 0x45, 0x29, 0x93, 0xf1, 0x6f, 0xfd, 0x04, 0xca, - 0x73, 0x8a, 0x96, 0xe9, 0x47, 0x85, 0xcb, 0xf4, 0xcd, 0xfa, 0xb8, 0xf4, 0x4c, 0x84, 0x3a, 0x73, 0xb1, 0xa9, 0x75, - 0x3a, 0xc6, 0x4e, 0xe9, 0xd4, 0x86, 0xbd, 0xaf, 0x14, 0x97, 0x15, 0x85, 0x7f, 0x23, 0x91, 0x55, 0xcf, 0x88, 0xe3, - 0xff, 0xcc, 0xda, 0x67, 0x58, 0x05, 0x7e, 0x19, 0xc8, 0xfb, 0x05, 0xc0, 0xc7, 0x75, 0x5d, 0xa6, 0xb7, 0x1b, 0xa0, - 0x0d, 0xa1, 0xe1, 0xef, 0xf9, 0xc8, 0x80, 0xe9, 0x3e, 0xc2, 0x19, 0xd2, 0x43, 0x9d, 0x73, 0x3a, 0x2b, 0xd3, 0x39, - 0x57, 0x61, 0x2d, 0xc1, 0x5e, 0x4e, 0x9a, 0x5c, 0xae, 0x4b, 0x50, 0x33, 0x81, 0xdb, 0x87, 0xf6, 0x88, 0x10, 0x6a, - 0x53, 0x56, 0xd3, 0x4b, 0xa8, 0x79, 0x27, 0xa7, 0x1d, 0x4d, 0x4a, 0x70, 0xd5, 0xd0, 0x59, 0xb9, 0xfe, 0xeb, 0x70, - 0xe8, 0xdd, 0x66, 0x45, 0xf4, 0x47, 0x0f, 0xfd, 0x1d, 0xb7, 0x37, 0xe9, 0x57, 0x88, 0x96, 0xb1, 0xfe, 0x86, 0x0c, - 0xe8, 0x78, 0x32, 0xbc, 0x2d, 0xb6, 0x3d, 0xf6, 0x1e, 0x35, 0x58, 0xfa, 0xfa, 0xf1, 0x07, 0x48, 0xa8, 0xba, 0xf6, - 0x85, 0xc5, 0x13, 0xe6, 0x29, 0xd1, 0xb6, 0xf0, 0x21, 0x2c, 0xf4, 0x3d, 0x44, 0x46, 0x42, 0xb8, 0xa9, 0xec, 0x1e, - 0x25, 0xed, 0x42, 0x5f, 0xfa, 0x5a, 0xf6, 0x95, 0xef, 0x5c, 0x00, 0xac, 0xec, 0x73, 0x1b, 0xee, 0x49, 0x7f, 0x4a, - 0xf5, 0x61, 0xfb, 0x5b, 0xb2, 0x80, 0x42, 0x0b, 0xeb, 0xa9, 0x9c, 0x9d, 0xeb, 0x92, 0xa7, 0xd9, 0x74, 0xbf, 0x86, - 0x3d, 0xea, 0x1e, 0xbd, 0xa6, 0x82, 0xf3, 0x4b, 0x33, 0x7a, 0xff, 0x30, 0x14, 0xaa, 0xa3, 0xce, 0x1d, 0x64, 0x5d, - 0x5a, 0x97, 0x9c, 0xdf, 0xac, 0xdc, 0x51, 0x98, 0xdf, 0x87, 0xe0, 0x19, 0xd6, 0xbd, 0xbb, 0x38, 0xef, 0xfd, 0xd9, - 0x9a, 0x23, 0x3f, 0x65, 0xb3, 0x14, 0xb1, 0x48, 0xe6, 0x60, 0xf5, 0x43, 0x3f, 0x8f, 0xfd, 0x36, 0xc8, 0xe1, 0xb8, - 0x69, 0x40, 0x87, 0x0d, 0x99, 0xb5, 0x2f, 0x11, 0x38, 0xd5, 0x08, 0xd2, 0xd4, 0x04, 0x35, 0xcb, 0x43, 0x24, 0xb6, - 0x4b, 0xd9, 0x36, 0xc8, 0x75, 0x17, 0x4c, 0x73, 0xa4, 0x3d, 0x83, 0xf7, 0x4d, 0x9a, 0xa4, 0x42, 0xb3, 0x48, 0x5b, - 0x25, 0xe3, 0xdf, 0x91, 0x36, 0x53, 0xb2, 0xc7, 0xd6, 0xc0, 0x7b, 0x09, 0xca, 0xc9, 0x30, 0xc5, 0xf0, 0x1d, 0x5f, - 0xef, 0x3c, 0xe6, 0x9e, 0x73, 0xcc, 0x36, 0x29, 0x3b, 0x82, 0x49, 0xb2, 0xf1, 0x0d, 0xc5, 0x1b, 0x7e, 0xb8, 0xad, - 0x44, 0x09, 0xa0, 0x97, 0x05, 0xbf, 0x96, 0x36, 0x57, 0xe8, 0x76, 0xf7, 0x8e, 0x52, 0xf8, 0x25, 0x2f, 0x0f, 0x87, - 0x6d, 0xea, 0x85, 0xd0, 0xf9, 0x22, 0x7e, 0x0f, 0xe6, 0x30, 0x86, 0xd8, 0x8c, 0x00, 0x61, 0x8e, 0x0f, 0xa8, 0x83, - 0xf5, 0x23, 0x00, 0x8d, 0x13, 0x28, 0xc0, 0xe8, 0xab, 0x6d, 0x41, 0xdf, 0xf2, 0xe2, 0x22, 0x42, 0xd4, 0x28, 0xc0, - 0x44, 0x49, 0xb3, 0x18, 0x86, 0x03, 0x9d, 0xdf, 0x37, 0xb7, 0x75, 0x29, 0x70, 0xe8, 0x1d, 0xcb, 0xf0, 0xdf, 0xfe, - 0xc7, 0xda, 0xd2, 0xaa, 0xb2, 0xdd, 0x1a, 0xa7, 0x99, 0xff, 0xed, 0xb6, 0xd0, 0xf7, 0x5f, 0x09, 0xc5, 0xf3, 0x8e, - 0xd7, 0xed, 0xaf, 0x10, 0xbd, 0xaf, 0x5b, 0x79, 0x57, 0x6a, 0x37, 0xcc, 0x94, 0x3f, 0xa4, 0x79, 0x5c, 0x3c, 0x8c, - 0xe2, 0xd6, 0x91, 0x37, 0x49, 0xcf, 0x39, 0xff, 0x5a, 0xf5, 0xfb, 0xde, 0x57, 0x20, 0xe3, 0x7d, 0x25, 0x8c, 0x23, - 0x26, 0x71, 0xf0, 0xed, 0xc5, 0x28, 0xda, 0x94, 0xb0, 0x21, 0xb7, 0x4f, 0x4b, 0xd0, 0xcc, 0xf4, 0xfb, 0x28, 0x51, - 0x5a, 0xf3, 0xfd, 0x2f, 0x72, 0xbe, 0xbf, 0x12, 0xf2, 0x66, 0x25, 0x3f, 0x7c, 0xb4, 0xc2, 0xc0, 0xf7, 0x38, 0xfd, - 0x2a, 0x7a, 0xec, 0xae, 0xf4, 0xe1, 0xbb, 0xd2, 0xd2, 0x67, 0x15, 0xf5, 0x77, 0x54, 0xd4, 0xbc, 0x12, 0x23, 0x22, - 0x1e, 0x04, 0xed, 0x6c, 0xbb, 0xd4, 0xae, 0x25, 0x68, 0x17, 0x6c, 0x0a, 0xfb, 0xd7, 0xa3, 0x43, 0xde, 0xef, 0x7f, - 0xca, 0xbd, 0x16, 0xaf, 0xbb, 0x0e, 0x4d, 0xf9, 0x6b, 0xe1, 0x21, 0x04, 0xb0, 0x96, 0x81, 0x32, 0x8e, 0x30, 0xe9, - 0x22, 0xaf, 0x51, 0x36, 0x9d, 0x08, 0x7c, 0xcc, 0xb2, 0x2b, 0x27, 0x99, 0x06, 0x98, 0x51, 0x4d, 0x61, 0x26, 0xc0, - 0x48, 0x7d, 0xc2, 0xba, 0xe9, 0x69, 0x15, 0x5a, 0xbe, 0x86, 0x60, 0x5d, 0x64, 0x19, 0x47, 0x31, 0x13, 0x00, 0x6c, - 0x3e, 0x81, 0x7c, 0x45, 0x57, 0x87, 0xa4, 0x95, 0x2a, 0xef, 0xd7, 0x19, 0x91, 0xd1, 0x24, 0x44, 0xf3, 0x5b, 0x78, - 0x60, 0xdf, 0x36, 0x33, 0xaa, 0xd4, 0x33, 0xaa, 0xf2, 0x19, 0x0e, 0x4b, 0xe1, 0x18, 0xf1, 0xff, 0x96, 0xaa, 0x1e, - 0x11, 0xe8, 0x55, 0x99, 0x56, 0x51, 0x91, 0xe7, 0x22, 0x42, 0x84, 0x6a, 0xe9, 0x1c, 0x0e, 0xfd, 0xd8, 0xef, 0xe3, - 0x40, 0x98, 0x17, 0xff, 0xfa, 0x58, 0x57, 0xfe, 0xb5, 0xc0, 0xb5, 0x92, 0x02, 0xa7, 0xa2, 0x46, 0x88, 0x10, 0xde, - 0x9f, 0xc0, 0xb3, 0x9a, 0xfa, 0x7e, 0x63, 0x99, 0xe8, 0xfe, 0x91, 0x01, 0xe5, 0x0f, 0xc8, 0xd7, 0x95, 0x14, 0x67, - 0xea, 0xe4, 0x31, 0x71, 0xc6, 0x01, 0x88, 0xf9, 0xb6, 0x44, 0xa3, 0xb1, 0xff, 0x01, 0x09, 0x86, 0xea, 0x07, 0x3b, - 0xdd, 0xd4, 0xfb, 0x67, 0x26, 0x71, 0x14, 0x7d, 0xda, 0x26, 0x8f, 0x25, 0x4b, 0xa3, 0x85, 0xa3, 0xf7, 0x88, 0x61, - 0x1c, 0x4e, 0xe7, 0x63, 0x92, 0x6d, 0x4c, 0x56, 0x01, 0xa4, 0x93, 0x99, 0x3a, 0xa6, 0xd4, 0xd1, 0x38, 0xd7, 0x0b, - 0xaa, 0xd0, 0x63, 0x5d, 0xf2, 0x1c, 0xac, 0x27, 0x3f, 0x7a, 0xa5, 0x3f, 0x15, 0x72, 0x0e, 0x1b, 0x89, 0xa0, 0xf0, - 0x03, 0x5c, 0x0d, 0x56, 0x0a, 0x18, 0x4c, 0x7d, 0x0b, 0x5f, 0x13, 0xcf, 0x51, 0xf0, 0x28, 0xec, 0x62, 0x6c, 0xad, - 0x7c, 0xe7, 0x93, 0x82, 0x72, 0xcf, 0x8a, 0x39, 0xaf, 0x80, 0x73, 0x19, 0x14, 0xc2, 0x74, 0x3c, 0xcb, 0xff, 0x99, - 0xe4, 0xf5, 0xc4, 0x86, 0x00, 0x19, 0xfc, 0x29, 0x71, 0x5a, 0xba, 0x43, 0x77, 0x1e, 0x7a, 0x16, 0x71, 0xd8, 0xe8, - 0xc9, 0xba, 0x2c, 0xb6, 0x29, 0xea, 0x25, 0xcc, 0x0f, 0xe4, 0xe7, 0x2d, 0xf9, 0x3e, 0x44, 0xf1, 0x36, 0xf8, 0x35, - 0x63, 0xb1, 0xc0, 0xbf, 0xfe, 0x96, 0x31, 0x9a, 0x68, 0xc1, 0xbf, 0xb2, 0x06, 0x89, 0x8a, 0xff, 0x9a, 0x4d, 0x00, - 0xd6, 0x91, 0xab, 0x0f, 0x9f, 0x12, 0xe3, 0xad, 0xd9, 0xf0, 0xc8, 0x37, 0x2b, 0xd0, 0xa9, 0xcf, 0xdd, 0x95, 0xed, - 0xa9, 0x6a, 0xfc, 0x2d, 0xd5, 0xd5, 0x48, 0x55, 0x35, 0xfe, 0x96, 0x52, 0x35, 0x7e, 0xcb, 0x28, 0x7e, 0xa7, 0xf2, - 0x19, 0x32, 0x27, 0x9b, 0x98, 0xa4, 0xd3, 0xf7, 0x86, 0x13, 0xbb, 0xec, 0x37, 0x6f, 0x13, 0x99, 0x89, 0x14, 0x72, - 0x6f, 0x00, 0xda, 0x7e, 0x97, 0x1b, 0x4e, 0x89, 0xf3, 0x73, 0x0f, 0x57, 0x6c, 0x5a, 0xbd, 0xa2, 0x05, 0x0b, 0x6c, - 0x5e, 0x66, 0x79, 0x8a, 0x04, 0xb6, 0x4d, 0x99, 0xf5, 0xe7, 0xdc, 0x03, 0x08, 0x66, 0x52, 0x13, 0x00, 0xd2, 0x42, - 0x54, 0x0a, 0x91, 0xbf, 0xc2, 0x59, 0x7d, 0xce, 0x7b, 0x9b, 0x3c, 0x26, 0xd2, 0xea, 0x5e, 0xbf, 0x9f, 0x9e, 0xa5, - 0x39, 0x05, 0x35, 0x1c, 0x67, 0x9d, 0xfe, 0x92, 0x05, 0x75, 0x22, 0x57, 0xe9, 0xdf, 0xdd, 0x20, 0x2f, 0xe3, 0xfb, - 0xba, 0xed, 0xf9, 0x13, 0xf5, 0xf7, 0xce, 0xfa, 0xdb, 0x02, 0xc1, 0x9d, 0x1c, 0xfb, 0xc9, 0xaa, 0x94, 0x27, 0xc6, - 0xa5, 0xbd, 0xe7, 0x37, 0x75, 0x51, 0x64, 0x75, 0xba, 0xfe, 0x28, 0xf5, 0x34, 0xba, 0x2f, 0xf6, 0x60, 0x0c, 0xde, - 0x01, 0xe0, 0x99, 0x0e, 0x0d, 0x90, 0xbe, 0x67, 0xe4, 0xe1, 0x3e, 0xb7, 0xe4, 0x27, 0x95, 0xb5, 0x49, 0xc2, 0x8a, - 0x62, 0x33, 0x8c, 0x11, 0x4a, 0xc6, 0x69, 0x6c, 0xfd, 0x7e, 0x5f, 0xfd, 0xbd, 0xc3, 0x28, 0x2a, 0x2a, 0xee, 0x18, - 0x8d, 0xca, 0xaa, 0x1e, 0x6d, 0x07, 0x87, 0xc3, 0x79, 0x6e, 0xe3, 0x68, 0xeb, 0x15, 0xb0, 0xb7, 0x42, 0xa5, 0xec, - 0x95, 0x08, 0xcb, 0x0f, 0x57, 0x7e, 0xbf, 0x0f, 0xff, 0xca, 0x48, 0x0b, 0xcf, 0x9f, 0xe2, 0xaf, 0x45, 0x5d, 0x60, - 0x78, 0x06, 0xad, 0xd1, 0x0a, 0x82, 0x09, 0xfe, 0xde, 0x81, 0x7a, 0x69, 0xa5, 0x7d, 0x02, 0xdd, 0x0a, 0xf4, 0xa0, - 0x1e, 0xfa, 0x34, 0x69, 0x5f, 0x48, 0xd4, 0xed, 0xad, 0x4e, 0xa3, 0x3f, 0x2a, 0xb8, 0x9c, 0xc2, 0xe4, 0x70, 0x43, - 0x9f, 0x56, 0xe1, 0xf6, 0x33, 0x3c, 0xfd, 0x19, 0x28, 0xb7, 0x0e, 0x87, 0x1c, 0xc4, 0x16, 0x70, 0xf3, 0x58, 0x85, - 0x5f, 0x8a, 0x52, 0x46, 0xd4, 0xc7, 0xd3, 0x02, 0xb4, 0x77, 0x01, 0x3a, 0x60, 0x69, 0x10, 0xaf, 0x90, 0x3c, 0x67, - 0x23, 0x80, 0x65, 0x07, 0x96, 0xb3, 0x8c, 0x53, 0x98, 0x67, 0x79, 0xad, 0x56, 0xda, 0x59, 0x99, 0x78, 0x35, 0xcb, - 0xc0, 0x59, 0xe0, 0xa2, 0xf2, 0x59, 0xa6, 0x55, 0x4f, 0x55, 0x82, 0x3e, 0xaf, 0xe4, 0x04, 0x57, 0x82, 0x93, 0x0d, - 0xc8, 0x2f, 0x40, 0x92, 0xa6, 0x94, 0x35, 0xe5, 0xf5, 0x25, 0xdd, 0x90, 0xd1, 0x73, 0xde, 0xf3, 0xa2, 0x61, 0xe8, - 0x5f, 0x78, 0x25, 0x84, 0x6f, 0xe2, 0xb6, 0x8d, 0x52, 0xd8, 0x5f, 0x04, 0x16, 0x9f, 0xb0, 0x1f, 0xbd, 0xa5, 0x3f, - 0x1d, 0x07, 0xe1, 0x10, 0xb9, 0xa1, 0x62, 0x0e, 0xec, 0x69, 0xc0, 0x62, 0x13, 0x5f, 0x6d, 0x26, 0xf1, 0x60, 0xe0, - 0xeb, 0x8c, 0xc5, 0x2c, 0x06, 0x1a, 0xe4, 0x78, 0x70, 0x39, 0xd7, 0x27, 0x84, 0x7e, 0x18, 0x51, 0x39, 0x2a, 0xd0, - 0x39, 0x88, 0x06, 0x4b, 0xc0, 0x53, 0x6f, 0x65, 0x83, 0x24, 0x63, 0x92, 0x49, 0x5c, 0x6b, 0x92, 0xea, 0x70, 0x42, - 0xeb, 0x40, 0xc7, 0xd5, 0x05, 0x74, 0x3e, 0xae, 0x7b, 0x1f, 0xaf, 0x86, 0x0b, 0x2a, 0xfd, 0x42, 0x0c, 0xbc, 0x7a, - 0x3a, 0x0e, 0x2e, 0xe9, 0x56, 0xb8, 0x58, 0x85, 0xdb, 0x9f, 0xe5, 0x03, 0xc7, 0x1d, 0x95, 0x34, 0x04, 0x06, 0x6f, - 0x0f, 0xdd, 0xcd, 0x0c, 0x0d, 0x75, 0xd2, 0x3e, 0x8c, 0x43, 0x39, 0xc4, 0xaa, 0x15, 0x17, 0xd2, 0x1b, 0xc1, 0xb7, - 0x0b, 0xc5, 0x58, 0x36, 0x76, 0x69, 0x28, 0x0a, 0x7f, 0x05, 0xb0, 0x43, 0xed, 0xaf, 0x54, 0xf2, 0x31, 0x32, 0xaa, - 0x69, 0xa0, 0x63, 0x00, 0x96, 0x2c, 0x4d, 0x24, 0x55, 0xa4, 0x91, 0xf8, 0x23, 0x33, 0xd6, 0x51, 0xd3, 0xf5, 0x05, - 0x53, 0xd5, 0x22, 0xe9, 0x76, 0x26, 0xb1, 0x9c, 0x48, 0x52, 0xdb, 0x7d, 0x44, 0x0c, 0x06, 0x3e, 0xd8, 0x88, 0x69, - 0x26, 0xc2, 0x11, 0x8f, 0x4a, 0x64, 0xd1, 0xe5, 0xb7, 0x51, 0x26, 0x6d, 0x5f, 0x56, 0x64, 0x0b, 0x82, 0xe9, 0x49, - 0xf4, 0x41, 0x92, 0x72, 0x2a, 0x12, 0x69, 0x46, 0x08, 0xf0, 0xe3, 0x49, 0x79, 0xa5, 0x3f, 0x07, 0x4d, 0x2b, 0xc1, - 0x4b, 0x06, 0xc9, 0x23, 0xf1, 0x33, 0x29, 0x98, 0xc5, 0x58, 0x35, 0x18, 0x60, 0x39, 0xd5, 0x33, 0xc7, 0x24, 0xfd, - 0x97, 0x4e, 0x27, 0xec, 0x17, 0x5e, 0x6e, 0x6b, 0x79, 0xd3, 0xdc, 0x7b, 0xe1, 0x55, 0x2c, 0xd5, 0xb0, 0x0c, 0xfa, - 0xaf, 0x89, 0x76, 0xc1, 0xd6, 0x96, 0x31, 0x61, 0xd5, 0x0f, 0x20, 0xed, 0x91, 0x2e, 0xaf, 0x1a, 0xe6, 0x4c, 0xf0, - 0xe8, 0xc2, 0x9a, 0x07, 0xd1, 0x85, 0xf0, 0x91, 0xcb, 0x6e, 0x92, 0x5c, 0x8d, 0x27, 0x7e, 0x38, 0x18, 0x28, 0x00, - 0x5a, 0x5a, 0x27, 0xc5, 0x20, 0x7c, 0x26, 0xe4, 0x40, 0x1a, 0x1d, 0x55, 0x01, 0x16, 0xcb, 0xec, 0xaa, 0x9c, 0x64, - 0x83, 0x81, 0x0f, 0x62, 0x63, 0x62, 0x37, 0x34, 0x9b, 0xfb, 0xec, 0x44, 0x41, 0x56, 0x9b, 0xc3, 0xd6, 0x4c, 0xb7, - 0xc0, 0x00, 0x60, 0x10, 0x11, 0x2c, 0xf7, 0xb9, 0x91, 0x8f, 0xa8, 0xd3, 0x53, 0x18, 0x01, 0xc1, 0x2f, 0x27, 0x02, - 0x91, 0x8b, 0x04, 0xea, 0x01, 0x66, 0x02, 0xcc, 0xa8, 0x62, 0x78, 0x09, 0xec, 0xe2, 0xb9, 0x79, 0xc5, 0xa0, 0x7f, - 0xd1, 0x24, 0x4b, 0x34, 0x95, 0x38, 0x1a, 0x23, 0xa7, 0xd2, 0x18, 0x19, 0x10, 0xbb, 0x38, 0xfe, 0x3d, 0xa5, 0x47, - 0x41, 0xca, 0xbe, 0x54, 0x86, 0x38, 0x1c, 0xc5, 0x57, 0xb0, 0x6a, 0x1c, 0x0e, 0xb5, 0x79, 0x3d, 0x9d, 0xd5, 0xf3, - 0x81, 0x08, 0xe0, 0xbf, 0xa1, 0x60, 0x2f, 0x35, 0x15, 0xb9, 0x41, 0xea, 0x3c, 0x1c, 0x52, 0x90, 0x4f, 0x75, 0x93, - 0x7f, 0xa9, 0xdc, 0xfd, 0x74, 0x36, 0xb7, 0xe6, 0xe8, 0x45, 0x8d, 0xeb, 0xd6, 0xea, 0x86, 0x42, 0xa2, 0x35, 0x4d, - 0x8a, 0xab, 0x6a, 0x52, 0x0c, 0x78, 0xee, 0x0b, 0xd5, 0xc5, 0xd6, 0x08, 0x16, 0xfe, 0xdc, 0x02, 0x61, 0x32, 0xee, - 0xc5, 0x47, 0x0b, 0x39, 0xa5, 0x5d, 0x5b, 0xed, 0xb6, 0x95, 0x0d, 0x29, 0x9a, 0x0f, 0x2f, 0x61, 0x97, 0x4e, 0x11, - 0x6d, 0xbb, 0x24, 0xf8, 0x02, 0xb4, 0xac, 0x2e, 0x44, 0x1e, 0xd3, 0xaf, 0x90, 0x5f, 0x8a, 0xe1, 0x7f, 0x4a, 0xf7, - 0xe6, 0xd4, 0x06, 0x39, 0x80, 0xed, 0xde, 0xc3, 0xed, 0x18, 0x3d, 0x90, 0xc1, 0x1b, 0x21, 0xe7, 0x9c, 0x5f, 0x4e, - 0xad, 0x19, 0x13, 0x0d, 0x0b, 0x56, 0x0e, 0x23, 0x3f, 0x40, 0xc6, 0xcb, 0x29, 0xb0, 0xb2, 0x1f, 0x15, 0x71, 0xe9, - 0x0f, 0x23, 0xff, 0xe2, 0x79, 0x90, 0x71, 0x2f, 0x1a, 0x76, 0x7c, 0x01, 0xf6, 0xea, 0x8b, 0xe7, 0x2c, 0x1a, 0xf0, - 0xea, 0xaa, 0x9e, 0x66, 0xc1, 0x30, 0x63, 0xd1, 0x55, 0x31, 0x04, 0x1f, 0xda, 0xeb, 0x72, 0x10, 0xfa, 0xbe, 0xd9, - 0x39, 0x74, 0x37, 0x24, 0xf2, 0x08, 0xfb, 0x09, 0xdc, 0x76, 0xb5, 0xc4, 0x0c, 0x26, 0x9b, 0xbb, 0x88, 0x19, 0x6c, - 0xf9, 0x8b, 0xe7, 0x86, 0x4b, 0xa8, 0xba, 0x96, 0x9a, 0x8d, 0x02, 0xcd, 0xc9, 0x15, 0x9a, 0x93, 0x95, 0x50, 0x4b, - 0x3e, 0xa9, 0x70, 0xc2, 0xce, 0x27, 0xb9, 0xb2, 0x1b, 0x8d, 0x31, 0x70, 0xd1, 0x9e, 0xdb, 0xc2, 0xc8, 0x4c, 0x67, - 0x29, 0x1a, 0xb0, 0xf0, 0x4c, 0x9c, 0xd2, 0x18, 0xd0, 0xbe, 0x1c, 0x58, 0xda, 0x90, 0x9f, 0xe4, 0xcc, 0x40, 0xdb, - 0x90, 0xd2, 0xa8, 0x19, 0xf8, 0x33, 0x35, 0x61, 0x7e, 0x05, 0x2b, 0x11, 0x44, 0x75, 0x01, 0x26, 0x49, 0x4e, 0x46, - 0x23, 0x65, 0x25, 0x92, 0x73, 0xc0, 0xfb, 0x04, 0x9e, 0x2c, 0x62, 0x5b, 0xfb, 0x53, 0xfa, 0x5f, 0x1d, 0x3e, 0x97, - 0xfe, 0x33, 0x01, 0x2c, 0xe4, 0xd2, 0x20, 0x32, 0x50, 0x38, 0xa4, 0xa6, 0x12, 0x71, 0xe2, 0x78, 0x06, 0xbe, 0x81, - 0x0b, 0x34, 0x05, 0xf4, 0x07, 0x35, 0xa3, 0x88, 0x2c, 0xfc, 0xd5, 0xb3, 0x9b, 0xba, 0xd1, 0xf3, 0xcc, 0x79, 0x0d, - 0x9a, 0x19, 0x08, 0xe9, 0x71, 0xaa, 0xde, 0x86, 0x44, 0xe7, 0xe5, 0xa5, 0x7e, 0x99, 0x10, 0xc9, 0x8a, 0xc8, 0xd3, - 0xf7, 0x39, 0x98, 0x47, 0x14, 0xa1, 0x83, 0x2b, 0xf3, 0x70, 0x38, 0x17, 0x14, 0xbe, 0xa3, 0x3c, 0x1f, 0x70, 0x9a, - 0x45, 0x09, 0x68, 0x03, 0x59, 0x6e, 0xca, 0x5c, 0x27, 0x2d, 0x53, 0xf7, 0x1e, 0xac, 0x04, 0x15, 0xba, 0x39, 0x05, - 0x85, 0x32, 0x12, 0x94, 0xd2, 0x6a, 0x10, 0x4a, 0x75, 0x58, 0x04, 0x91, 0x43, 0x16, 0x02, 0x6e, 0xa6, 0xa2, 0xd1, - 0x92, 0x86, 0x47, 0x38, 0x37, 0x50, 0x08, 0x40, 0x62, 0x4f, 0x15, 0x65, 0x5c, 0x0e, 0x01, 0x1f, 0x25, 0x1c, 0xe2, - 0xac, 0x49, 0x5b, 0x9e, 0x83, 0x38, 0x96, 0x4b, 0xbe, 0xae, 0x10, 0x0c, 0x22, 0xf4, 0x19, 0xf2, 0x27, 0xcb, 0xf9, - 0x77, 0xeb, 0x30, 0xed, 0x08, 0x1f, 0x76, 0xb5, 0x05, 0x17, 0xb3, 0xdb, 0xf9, 0x04, 0xe2, 0x5b, 0x6e, 0xe7, 0xc7, - 0x18, 0x22, 0x0b, 0x7f, 0x70, 0x37, 0x94, 0x5c, 0x51, 0xe8, 0xb2, 0x1e, 0x91, 0x22, 0x7b, 0xba, 0xe6, 0x08, 0x82, - 0x03, 0xad, 0x1a, 0x64, 0x68, 0x24, 0xbe, 0x78, 0x0e, 0x59, 0x83, 0x35, 0xff, 0x52, 0x91, 0xb3, 0xba, 0x3f, 0xd9, - 0x40, 0x35, 0xc9, 0x64, 0xad, 0xa8, 0x9c, 0xbf, 0x5d, 0x95, 0xe5, 0xc9, 0xaa, 0x0c, 0x57, 0x83, 0xae, 0xaa, 0x2c, - 0x39, 0x52, 0x1b, 0xa0, 0x35, 0x5d, 0x21, 0x86, 0x42, 0xd6, 0x60, 0x69, 0x55, 0x65, 0x4d, 0x7d, 0x02, 0x81, 0x3e, - 0xc0, 0x32, 0x6a, 0xf6, 0xd3, 0xe1, 0x3f, 0x83, 0x7f, 0xaa, 0x90, 0xa5, 0x3a, 0xad, 0x33, 0xf1, 0x6b, 0xb0, 0x64, - 0xf8, 0xc7, 0x6f, 0xc1, 0x1a, 0xb0, 0x04, 0xc8, 0x72, 0xb7, 0xb1, 0xd1, 0x7a, 0xe5, 0x15, 0xe2, 0x7d, 0xad, 0x2f, - 0xfa, 0xad, 0xdb, 0x44, 0xad, 0x00, 0x23, 0x14, 0x5a, 0x04, 0xd8, 0xea, 0x81, 0x7b, 0x0a, 0x7e, 0x20, 0x86, 0x73, - 0x4d, 0x5a, 0x53, 0x27, 0xbc, 0xce, 0xc6, 0x91, 0x88, 0xea, 0x2d, 0x5c, 0xdc, 0xeb, 0xad, 0xc5, 0xdf, 0xa8, 0x40, - 0x00, 0x64, 0x31, 0xc5, 0xda, 0x79, 0x43, 0x7a, 0x65, 0xd8, 0x49, 0xe8, 0xbd, 0x61, 0x27, 0x90, 0x17, 0x87, 0x9d, - 0x42, 0x97, 0x68, 0x3b, 0x45, 0x6a, 0xa2, 0xed, 0xa4, 0xc5, 0x2a, 0x2c, 0x21, 0xf8, 0x55, 0x7b, 0xeb, 0x28, 0xdb, - 0x17, 0x59, 0xc2, 0xb4, 0x05, 0x8c, 0x72, 0xab, 0x3e, 0x73, 0x8a, 0x58, 0x29, 0x7b, 0xa7, 0x93, 0x2a, 0x77, 0x91, - 0xcf, 0xad, 0xa6, 0xc8, 0xe4, 0x97, 0xc7, 0x2d, 0x92, 0x4f, 0x7e, 0x6e, 0x37, 0x4c, 0xa6, 0x7f, 0x3a, 0xfa, 0x02, - 0xba, 0x22, 0x3b, 0x7d, 0x02, 0x01, 0x99, 0x0a, 0xaa, 0xd5, 0xad, 0x62, 0x9a, 0xb7, 0xab, 0xec, 0xf6, 0x42, 0x89, - 0xe1, 0x74, 0x76, 0x12, 0x1e, 0x6d, 0x86, 0x0c, 0x1c, 0x82, 0x40, 0x21, 0x54, 0x14, 0xc3, 0x23, 0x50, 0x6b, 0x24, - 0x1f, 0xe0, 0x47, 0xbb, 0x53, 0x41, 0xa4, 0x76, 0x53, 0x71, 0xe3, 0xe4, 0xa6, 0xeb, 0xa5, 0x40, 0xad, 0x53, 0xb2, - 0x02, 0x28, 0x21, 0xea, 0xcf, 0x62, 0x5b, 0xbf, 0x82, 0x2b, 0x36, 0xdf, 0x37, 0x8a, 0x9e, 0x5c, 0x9f, 0xa2, 0x6e, - 0xc5, 0xd5, 0x69, 0xda, 0x6a, 0x8e, 0x1d, 0x67, 0xc8, 0xc1, 0xb3, 0x82, 0x60, 0x3b, 0x2a, 0x51, 0xbe, 0x6b, 0x37, - 0x1d, 0x13, 0x5b, 0xfd, 0xb3, 0xa8, 0x36, 0x77, 0x50, 0x11, 0x11, 0x1f, 0x65, 0x37, 0x4f, 0xda, 0xef, 0x60, 0x8f, - 0xb5, 0x1a, 0x44, 0xf6, 0x19, 0x5c, 0xe5, 0x3a, 0x2d, 0x72, 0x5b, 0x06, 0xe7, 0x1f, 0x5e, 0xed, 0x2a, 0x6c, 0x72, - 0xac, 0xab, 0xab, 0x99, 0xea, 0xa4, 0x62, 0x03, 0x63, 0x4d, 0x6b, 0xa9, 0xe6, 0x31, 0x24, 0xdd, 0x95, 0xc5, 0x59, - 0x95, 0x74, 0xd3, 0x73, 0xe3, 0x4c, 0x21, 0x06, 0xce, 0x56, 0xa3, 0xe5, 0x0c, 0x43, 0x74, 0x7d, 0x98, 0x25, 0x7e, - 0xab, 0xa7, 0xdc, 0xe7, 0xe1, 0xd6, 0xef, 0xea, 0x05, 0x27, 0x93, 0xfd, 0xe4, 0x38, 0x77, 0xbb, 0x48, 0xfb, 0x89, - 0x6f, 0xc3, 0xfc, 0xeb, 0x1b, 0xc4, 0x9d, 0xa8, 0xff, 0x51, 0x01, 0xd0, 0xe0, 0x26, 0x8f, 0x25, 0x4a, 0xfd, 0x5e, - 0x55, 0x3f, 0xa8, 0x99, 0xaa, 0x69, 0x20, 0x98, 0x53, 0x29, 0xe0, 0x0f, 0xb7, 0x0b, 0x57, 0x3c, 0xe2, 0x86, 0x85, - 0xf1, 0x4f, 0xaf, 0x66, 0xa7, 0x82, 0xca, 0xc0, 0xcd, 0xf8, 0x4f, 0x4f, 0xb0, 0x53, 0x58, 0x2b, 0x20, 0x2b, 0xfc, - 0xe9, 0xe5, 0x8f, 0xbc, 0x5f, 0xf1, 0x3f, 0xbd, 0xea, 0x91, 0xf7, 0x11, 0xe7, 0xe5, 0x4f, 0x24, 0x75, 0x42, 0x54, - 0x97, 0x3f, 0x09, 0x53, 0x6c, 0x95, 0xe6, 0xaf, 0x49, 0xe1, 0x13, 0x7c, 0x01, 0xbe, 0xc3, 0x55, 0xb8, 0x35, 0xbf, - 0xc1, 0x63, 0xc7, 0x62, 0xdb, 0xa5, 0xbe, 0x80, 0x72, 0x04, 0x16, 0x91, 0xdb, 0x6f, 0x57, 0xf6, 0xab, 0x85, 0x51, - 0xc6, 0xd8, 0x7d, 0xc9, 0x4a, 0x94, 0xce, 0xfa, 0xfd, 0x42, 0x0a, 0x46, 0x76, 0x61, 0x8d, 0xf6, 0x28, 0x55, 0xaf, - 0xbe, 0x0b, 0xeb, 0x28, 0x49, 0xf3, 0x3b, 0x19, 0x7d, 0x24, 0xc3, 0x8e, 0xf4, 0x95, 0x94, 0x68, 0xaf, 0x55, 0x58, - 0x8e, 0x66, 0xbf, 0x2e, 0x39, 0x50, 0x5e, 0xb7, 0x82, 0xf2, 0x55, 0x13, 0x40, 0xaf, 0x54, 0xfb, 0x0c, 0xb4, 0x82, - 0xc2, 0x52, 0x79, 0xb0, 0x12, 0xe7, 0xa2, 0xcf, 0x8a, 0xc3, 0x41, 0x5d, 0x0c, 0x09, 0x05, 0xaa, 0xc4, 0x49, 0x68, - 0xc4, 0x73, 0xb8, 0x10, 0x8a, 0xeb, 0x1c, 0x63, 0x2b, 0x72, 0xe0, 0x40, 0x86, 0x1f, 0x10, 0x78, 0x2f, 0xfb, 0x57, - 0x30, 0x18, 0x26, 0xb8, 0x91, 0x51, 0x27, 0xe7, 0xec, 0x4f, 0x0c, 0xcc, 0xa0, 0x9e, 0xd4, 0xee, 0xb3, 0x7b, 0x15, - 0xd8, 0x0b, 0x67, 0x40, 0x7b, 0x37, 0x46, 0x3f, 0xab, 0x62, 0xed, 0xa4, 0x7f, 0x2e, 0xd6, 0x90, 0x4c, 0x87, 0xc5, - 0xd1, 0x36, 0x0d, 0x8f, 0xe4, 0xc9, 0x71, 0xbc, 0xe9, 0x1f, 0x0e, 0x63, 0xfc, 0x38, 0xca, 0xaf, 0x2d, 0xe0, 0x55, - 0xdc, 0x42, 0x1a, 0x8b, 0x14, 0xbd, 0x03, 0x31, 0x87, 0xa2, 0x97, 0xec, 0xb7, 0x8c, 0x97, 0x13, 0x41, 0x29, 0x49, - 0x6c, 0x78, 0x47, 0x7a, 0x9a, 0xd6, 0xa3, 0xad, 0x0c, 0xd8, 0xaf, 0x47, 0x3b, 0xfa, 0x0b, 0x14, 0x8f, 0x16, 0xfe, - 0x92, 0xfe, 0x2e, 0xee, 0xe6, 0x9e, 0xf3, 0x4d, 0xe3, 0x3b, 0xe2, 0x02, 0xc5, 0x9a, 0xdd, 0x5f, 0xd3, 0xd2, 0x59, - 0x07, 0x82, 0x03, 0xde, 0x62, 0x17, 0xed, 0xfb, 0x8d, 0xeb, 0xf4, 0xb4, 0xff, 0xde, 0xad, 0x51, 0xbe, 0xf7, 0x0f, - 0x89, 0x72, 0xb0, 0x7f, 0xed, 0xa2, 0xf9, 0xdb, 0x4f, 0x19, 0x92, 0x0a, 0xcd, 0x0d, 0xb6, 0x93, 0x2d, 0xc2, 0xda, - 0x18, 0x07, 0x15, 0xbb, 0x2b, 0xc3, 0x08, 0x18, 0xd4, 0xb1, 0xff, 0xd1, 0x67, 0xd3, 0x86, 0xec, 0x03, 0x40, 0xe5, - 0x2a, 0x04, 0xec, 0x01, 0x38, 0xd1, 0x08, 0x37, 0xc0, 0xad, 0x46, 0x4b, 0x3a, 0xa8, 0xdb, 0x82, 0x81, 0x68, 0x09, - 0x1b, 0x79, 0xdb, 0xd5, 0xe9, 0x1b, 0xc2, 0x87, 0xda, 0x49, 0xe9, 0x50, 0xfe, 0xe6, 0x39, 0xfb, 0xef, 0x1d, 0xd6, - 0xd4, 0x94, 0x1b, 0xc0, 0xcc, 0x59, 0x89, 0xbc, 0x42, 0xe8, 0x14, 0xf9, 0xbd, 0xaa, 0x2b, 0x31, 0x5c, 0xd6, 0xa2, - 0xec, 0xcc, 0x6e, 0x9d, 0xe8, 0x9d, 0x53, 0x50, 0x4b, 0x65, 0x83, 0x9c, 0xa4, 0xda, 0x7c, 0x64, 0xad, 0xa0, 0x44, - 0x5d, 0xa3, 0xc0, 0xf1, 0x29, 0xd7, 0xee, 0xff, 0x9d, 0x33, 0x41, 0xcd, 0x36, 0xaa, 0xfb, 0x6b, 0xfd, 0x54, 0xd5, - 0x24, 0x16, 0xe0, 0x72, 0x92, 0xe6, 0x1d, 0x8f, 0xb0, 0xfa, 0xc7, 0xc9, 0x52, 0x04, 0x7a, 0x1d, 0xd1, 0xae, 0x04, - 0x24, 0x68, 0x27, 0x67, 0xa1, 0x22, 0x50, 0xa0, 0xaf, 0xbf, 0xdc, 0xa4, 0x59, 0x2c, 0x57, 0xb3, 0x3d, 0x4c, 0x94, - 0xc5, 0x7a, 0x88, 0x20, 0x67, 0xa6, 0x0e, 0xf6, 0x7b, 0x9a, 0xd1, 0x2c, 0xbc, 0x32, 0x25, 0xb8, 0x14, 0x57, 0x51, - 0x91, 0x83, 0xcf, 0x21, 0xbe, 0xf0, 0xb9, 0x90, 0x1b, 0x44, 0x34, 0xfd, 0x45, 0xa2, 0xda, 0x91, 0x02, 0x39, 0x94, - 0xfc, 0x84, 0xf8, 0x4b, 0xd6, 0xc6, 0xb8, 0x5f, 0x3a, 0xd5, 0x7e, 0xa5, 0x10, 0xdc, 0x7f, 0xb6, 0xc5, 0x46, 0x95, - 0x27, 0x7a, 0xf4, 0x29, 0xd6, 0xff, 0x64, 0x01, 0xa5, 0xba, 0x6f, 0x83, 0x53, 0xf1, 0x28, 0xdc, 0xd4, 0xc5, 0x0d, - 0x42, 0x0b, 0x94, 0xa3, 0xaa, 0xd8, 0x94, 0x11, 0x71, 0xc2, 0x6e, 0xea, 0xa2, 0xa7, 0x39, 0xd0, 0xa9, 0xc3, 0xd2, - 0x44, 0x9e, 0x08, 0xed, 0x16, 0x74, 0x4f, 0x73, 0xac, 0xc4, 0x0b, 0x59, 0x3a, 0xc8, 0x3a, 0x91, 0x26, 0x54, 0xee, - 0xea, 0xaa, 0xa3, 0x52, 0xa9, 0x1b, 0xde, 0xa4, 0x9a, 0xf1, 0x77, 0x69, 0xfe, 0xc4, 0xb2, 0xdf, 0xb4, 0x7e, 0xab, - 0xd5, 0xde, 0x58, 0x3d, 0x2a, 0x59, 0x73, 0x9c, 0x4d, 0x48, 0x4a, 0x9f, 0xb0, 0xdd, 0x4c, 0xba, 0xd6, 0x81, 0x27, - 0xc1, 0xe5, 0xd0, 0x13, 0x50, 0x31, 0x68, 0xe2, 0xed, 0x2e, 0x50, 0x8f, 0xc0, 0x33, 0x50, 0x3e, 0x51, 0xeb, 0x80, - 0x9f, 0xd7, 0x5a, 0x9e, 0x32, 0xc2, 0xb0, 0xda, 0x59, 0xb4, 0x1c, 0x9c, 0x77, 0x8a, 0xc0, 0xb5, 0x2b, 0x81, 0xe7, - 0x43, 0xf5, 0x5e, 0x08, 0x18, 0xee, 0x9f, 0x0b, 0x95, 0xcd, 0x6e, 0x86, 0xf3, 0xa8, 0x71, 0x7a, 0xa0, 0xbd, 0xed, - 0x5a, 0x0f, 0xf5, 0xae, 0xdb, 0xb9, 0xad, 0x74, 0xef, 0xd7, 0x4e, 0x26, 0x5d, 0x40, 0x6b, 0xf3, 0xd9, 0x77, 0x76, - 0xa5, 0x75, 0xd3, 0x73, 0xf6, 0x60, 0xeb, 0x96, 0xe8, 0x5c, 0x10, 0x4d, 0x7e, 0x3f, 0xf0, 0xac, 0x6d, 0x47, 0xbf, - 0x4d, 0x3b, 0xb6, 0xb9, 0x87, 0xba, 0x57, 0x50, 0xeb, 0x0d, 0xcd, 0xfb, 0x67, 0xae, 0x6d, 0xc7, 0x57, 0xbf, 0xae, - 0x3b, 0x5c, 0xe7, 0x4d, 0x70, 0xdc, 0x74, 0x6d, 0xab, 0x9d, 0xfd, 0xdc, 0xdd, 0x5b, 0x8b, 0x28, 0xcc, 0xb2, 0x9f, - 0x8a, 0xe2, 0x8f, 0x4a, 0xdf, 0x11, 0xe8, 0xe8, 0xce, 0x8b, 0x3a, 0x5d, 0xee, 0x3e, 0x12, 0xc6, 0x93, 0x57, 0x1f, - 0x11, 0xdd, 0xfa, 0x3e, 0x73, 0xbf, 0x02, 0xdc, 0x08, 0xee, 0x20, 0xda, 0xbb, 0xa5, 0x3e, 0xa9, 0xd5, 0xd7, 0x7a, - 0xed, 0x3c, 0x3d, 0xbf, 0xe9, 0xdc, 0x7e, 0xf7, 0xcd, 0xd1, 0xd6, 0x7b, 0x5c, 0x58, 0x2b, 0x4b, 0x4f, 0x55, 0xc1, - 0xde, 0x2c, 0x4f, 0x55, 0xc1, 0xe4, 0x81, 0xd7, 0xec, 0x17, 0x34, 0xb8, 0xd2, 0xd1, 0xc6, 0x7b, 0xa2, 0x06, 0x6e, - 0x51, 0x58, 0x3a, 0xfc, 0x92, 0x9b, 0xc9, 0x2b, 0xdc, 0x5f, 0x2a, 0x72, 0xb1, 0xef, 0x9c, 0xd1, 0x9d, 0x99, 0x75, - 0xaf, 0x2a, 0x5c, 0x2d, 0xc8, 0xd5, 0x81, 0xad, 0x65, 0x17, 0x87, 0x1b, 0x16, 0x51, 0x80, 0x40, 0x4c, 0xaf, 0xd4, - 0xda, 0x1f, 0xd1, 0x20, 0xe4, 0x83, 0x81, 0x5f, 0x60, 0xb0, 0x2a, 0x50, 0xf8, 0x40, 0x91, 0xfc, 0xb5, 0x27, 0x60, - 0x17, 0xcf, 0x00, 0xdd, 0x8a, 0xcd, 0x8a, 0x11, 0x22, 0x64, 0xb2, 0x9c, 0xd5, 0x74, 0x06, 0xf9, 0xd4, 0x17, 0xdf, - 0xd9, 0xaa, 0xd3, 0x79, 0x5b, 0x53, 0xe5, 0xd4, 0xa1, 0xd0, 0xdd, 0x4d, 0xdd, 0xb9, 0x75, 0x91, 0xa7, 0x0e, 0x21, - 0x57, 0x2a, 0x56, 0x62, 0x1a, 0x6a, 0x9e, 0xa4, 0x19, 0xf5, 0xa5, 0xbd, 0xdf, 0x6b, 0x14, 0x4e, 0xf9, 0xd3, 0x31, - 0xa8, 0xc2, 0x55, 0x0d, 0x71, 0x2c, 0x55, 0xf1, 0xc8, 0x06, 0x81, 0xe6, 0xd5, 0xad, 0x4a, 0x9a, 0x90, 0xc9, 0x8d, - 0xf0, 0xa9, 0x49, 0x29, 0x4f, 0xd3, 0x26, 0xad, 0x14, 0xa9, 0x83, 0x0f, 0xea, 0x54, 0xe3, 0xb9, 0x59, 0x5d, 0x03, - 0x98, 0x71, 0x7e, 0xc5, 0x2f, 0x15, 0x97, 0x51, 0x5b, 0x99, 0x49, 0xfb, 0x93, 0xa3, 0xb1, 0x51, 0x97, 0xd3, 0x46, - 0x19, 0x61, 0xa5, 0x34, 0x27, 0xc5, 0x72, 0x3c, 0xff, 0x80, 0xc1, 0x9a, 0x27, 0xb0, 0x83, 0x89, 0x4a, 0x79, 0x1f, - 0x01, 0xf1, 0x75, 0x92, 0xde, 0x25, 0x90, 0x22, 0xfd, 0x4b, 0x97, 0x3c, 0x75, 0x18, 0x1b, 0x88, 0x31, 0x2b, 0x66, - 0x46, 0xff, 0x83, 0xbb, 0xa4, 0x3f, 0x09, 0x01, 0x70, 0x13, 0x4d, 0xa1, 0x53, 0xe7, 0xc9, 0x45, 0x1e, 0x2c, 0x2f, - 0x3c, 0xb4, 0x62, 0xc4, 0x83, 0xff, 0xbc, 0x0e, 0x11, 0xc4, 0x1c, 0x53, 0x3c, 0xfd, 0xc2, 0xe8, 0x3f, 0x82, 0x4b, - 0x8c, 0x20, 0x74, 0xf7, 0xce, 0x61, 0x08, 0x37, 0x7b, 0x90, 0x41, 0xfd, 0xa1, 0x0e, 0x89, 0x1a, 0xfe, 0x54, 0x79, - 0xd0, 0xff, 0x75, 0x26, 0x2c, 0xb5, 0x9f, 0x9e, 0x0e, 0xa0, 0x82, 0xf7, 0x15, 0x6f, 0x23, 0xe2, 0xfb, 0xc4, 0xcf, - 0xe2, 0xc1, 0xe6, 0xd9, 0x06, 0xac, 0x75, 0x4f, 0x72, 0x63, 0x5d, 0x25, 0x6c, 0x20, 0xe0, 0x6b, 0x4c, 0x6b, 0xcf, - 0x6b, 0xb7, 0x7b, 0xf0, 0x9f, 0xfe, 0x45, 0xc8, 0x80, 0x89, 0xd3, 0xf7, 0x99, 0x93, 0x35, 0xba, 0xc8, 0x64, 0xfa, - 0xd0, 0x49, 0xdf, 0xe8, 0x74, 0xdf, 0x09, 0xff, 0xa8, 0x98, 0xc5, 0x87, 0x5b, 0xfa, 0x4a, 0x93, 0xe2, 0x0e, 0x58, - 0xd9, 0x3c, 0x2a, 0x08, 0x75, 0x2e, 0xa2, 0x6f, 0x4c, 0xf9, 0x96, 0x50, 0xb3, 0x6f, 0x2c, 0x29, 0xa5, 0x7b, 0x0d, - 0xbd, 0x49, 0x6b, 0xfd, 0x36, 0x4a, 0x30, 0x26, 0x3a, 0x9e, 0xbc, 0x8c, 0xc7, 0xca, 0xfb, 0x78, 0xdc, 0x48, 0x85, - 0x3c, 0x00, 0x11, 0xa8, 0x18, 0x7f, 0xba, 0xf2, 0xe4, 0xa4, 0x17, 0xc6, 0xab, 0x50, 0x0a, 0x0a, 0x03, 0xba, 0x02, - 0x29, 0xe0, 0x51, 0x7b, 0xa2, 0xb3, 0xb0, 0x4b, 0xb8, 0x47, 0x37, 0x01, 0x63, 0x7d, 0xfe, 0x09, 0xd0, 0xdc, 0x85, - 0x3b, 0xbc, 0x18, 0xa0, 0x36, 0xf5, 0xea, 0xee, 0xe3, 0x5a, 0x9d, 0xc3, 0x21, 0x38, 0x58, 0x0d, 0x22, 0x38, 0x9d, - 0x4f, 0x1d, 0xcd, 0xb2, 0x00, 0x95, 0x93, 0xe5, 0x46, 0xde, 0x3c, 0x5a, 0xf4, 0xea, 0xbe, 0xb7, 0x4c, 0xcb, 0xaa, - 0x0e, 0x32, 0x96, 0x85, 0x15, 0xe0, 0xea, 0xd0, 0xfa, 0x41, 0xb8, 0x2c, 0x9c, 0x3f, 0x10, 0x82, 0xd8, 0xbd, 0xda, - 0x96, 0x3c, 0x57, 0x73, 0xf8, 0xd9, 0x73, 0xb6, 0xe6, 0x12, 0x75, 0xd2, 0x99, 0x08, 0x40, 0xec, 0xa9, 0x59, 0x45, - 0xd7, 0x40, 0x52, 0xa7, 0x59, 0x45, 0xd7, 0xd4, 0x6c, 0x63, 0x1c, 0xc8, 0x47, 0xab, 0x14, 0xb0, 0xef, 0xa6, 0xe3, - 0x60, 0xf5, 0x2c, 0x96, 0xd7, 0xa1, 0xbb, 0x67, 0x1b, 0xe5, 0x33, 0xa8, 0x5b, 0x6d, 0x8c, 0x89, 0xed, 0xe6, 0xcb, - 0xb9, 0x7e, 0x3b, 0x58, 0xfa, 0x76, 0xd0, 0x9c, 0x53, 0xf6, 0x9d, 0x2e, 0x7b, 0x65, 0x97, 0x4d, 0x3d, 0x77, 0x54, - 0xb4, 0x1a, 0x03, 0x7a, 0x03, 0x0b, 0xd6, 0xe7, 0x22, 0xcd, 0x56, 0xa5, 0x2a, 0x01, 0x2f, 0x8c, 0x15, 0xbb, 0xf3, - 0x1b, 0x99, 0x21, 0x09, 0xf3, 0x38, 0x13, 0xef, 0xe8, 0x5e, 0x0b, 0x93, 0xe3, 0x58, 0x24, 0x53, 0x42, 0xa7, 0x74, - 0x67, 0x1b, 0x3a, 0x57, 0x61, 0x14, 0xd1, 0x5a, 0x49, 0xa5, 0x91, 0xc0, 0xd4, 0x0c, 0x50, 0x32, 0x57, 0xe0, 0x94, - 0x2e, 0xf7, 0xbf, 0x23, 0x31, 0xce, 0x7c, 0x51, 0x32, 0x03, 0xba, 0xe5, 0xd7, 0xc5, 0xba, 0x95, 0x22, 0x23, 0xcc, - 0x9b, 0xe3, 0xf6, 0xba, 0x3e, 0x04, 0x72, 0xb5, 0xec, 0x51, 0x34, 0x0e, 0x0a, 0x1d, 0x2e, 0x55, 0x02, 0xec, 0x8b, - 0xc4, 0xcf, 0x08, 0x5b, 0xda, 0x03, 0xb9, 0x3d, 0x3a, 0x13, 0xe6, 0x9c, 0x93, 0xb2, 0xec, 0x5c, 0x9a, 0xc1, 0xe5, - 0xc4, 0x95, 0xe0, 0x22, 0xbd, 0x6d, 0x4f, 0x93, 0x96, 0xb6, 0x8f, 0x0d, 0xe7, 0x68, 0x68, 0x1b, 0x74, 0xc7, 0xfe, - 0xd0, 0x5c, 0x2c, 0x62, 0xeb, 0x62, 0x31, 0xec, 0xcc, 0x7e, 0xb4, 0x58, 0x80, 0x1c, 0x00, 0x8e, 0xba, 0x0d, 0x1f, - 0xb3, 0x25, 0x70, 0x5a, 0x4d, 0xb3, 0xa9, 0xb7, 0xe1, 0xd5, 0x33, 0xd5, 0xd3, 0x4b, 0x9e, 0x3f, 0x13, 0x66, 0x2c, - 0x36, 0x3c, 0x7f, 0x66, 0x1d, 0x39, 0xd5, 0x33, 0xa1, 0x44, 0xeb, 0x02, 0x9a, 0x81, 0xd7, 0x14, 0x30, 0x62, 0xc9, - 0x64, 0x4a, 0x15, 0x79, 0xdc, 0x9b, 0x6e, 0xd4, 0xe0, 0x05, 0x85, 0x43, 0x20, 0xa5, 0xd3, 0x2f, 0x9e, 0x33, 0xfd, - 0xde, 0xc5, 0xf3, 0x0e, 0x59, 0xdb, 0x30, 0x5d, 0x6e, 0x86, 0xc9, 0xa0, 0xf4, 0x9f, 0x99, 0x89, 0x71, 0x61, 0x4d, - 0x12, 0x40, 0xfc, 0x1b, 0xfb, 0x1d, 0x52, 0xb8, 0x79, 0x7f, 0x39, 0x8c, 0x1f, 0x79, 0x3f, 0x46, 0xf6, 0x24, 0xcd, - 0x10, 0x6b, 0x26, 0x15, 0x72, 0xf7, 0xd5, 0xfa, 0xc7, 0xc4, 0x6e, 0xb2, 0x07, 0x16, 0x80, 0xd8, 0x9a, 0xb6, 0xba, - 0xe5, 0xfd, 0xbe, 0x67, 0x8a, 0x00, 0x3f, 0x28, 0xff, 0xe8, 0xce, 0x90, 0x0c, 0xca, 0xae, 0x1b, 0x42, 0x3c, 0x28, - 0x9b, 0xa6, 0xbd, 0xde, 0xf6, 0xce, 0x3c, 0x56, 0xd7, 0x69, 0x67, 0x71, 0xb5, 0xc8, 0x20, 0xad, 0x3e, 0x64, 0xc7, - 0x99, 0x7d, 0x76, 0xb4, 0x54, 0xba, 0xdf, 0x87, 0x88, 0xb8, 0xa3, 0xac, 0xed, 0xb7, 0x5b, 0x70, 0x0d, 0x47, 0x83, - 0xd0, 0x95, 0xbd, 0x5d, 0x46, 0x1b, 0x17, 0xe2, 0xb8, 0x67, 0x3a, 0x5f, 0xf0, 0xe5, 0x51, 0xda, 0x79, 0x70, 0xaa, - 0x27, 0xfa, 0xdc, 0x74, 0x57, 0x99, 0x5c, 0xeb, 0xb0, 0x1a, 0x83, 0xda, 0x2c, 0x6c, 0xe1, 0x2e, 0x6c, 0xa3, 0x83, - 0xd6, 0xbe, 0x2c, 0xf8, 0xa7, 0x0c, 0xc0, 0x97, 0x9e, 0x2d, 0xdb, 0x5e, 0x93, 0x56, 0x6f, 0x64, 0x14, 0x62, 0x4b, - 0xdb, 0xab, 0x4f, 0x47, 0xf9, 0xb8, 0x39, 0xa1, 0xb8, 0x90, 0xa3, 0xfc, 0xe8, 0x35, 0x44, 0x5d, 0xeb, 0x3a, 0x2e, - 0x16, 0x1d, 0x6e, 0x5c, 0x75, 0xdb, 0x8d, 0xeb, 0x47, 0xc4, 0x5b, 0xa3, 0x4d, 0x0a, 0xb5, 0x32, 0x76, 0x04, 0x2f, - 0xcb, 0x87, 0x43, 0x26, 0x86, 0x43, 0x09, 0x99, 0xfa, 0xd8, 0xbd, 0xa1, 0x69, 0x9f, 0x9f, 0xb6, 0x7e, 0xc4, 0x52, - 0xe3, 0x28, 0x36, 0xbc, 0xd3, 0x77, 0x1e, 0x5b, 0xe3, 0x4a, 0xbe, 0x0c, 0x66, 0xbb, 0x82, 0x6a, 0x6b, 0xbc, 0x61, - 0x2f, 0xe7, 0xbf, 0x54, 0x52, 0xc9, 0xdf, 0xfe, 0x0c, 0xd7, 0xf0, 0xd6, 0x96, 0x0e, 0x9a, 0x6a, 0x96, 0xb3, 0x5c, - 0xdf, 0x0b, 0x8e, 0x3f, 0xee, 0x5e, 0x11, 0x0c, 0x7e, 0x4f, 0x47, 0x41, 0x2e, 0x96, 0x6a, 0x0d, 0x28, 0x48, 0x47, - 0x76, 0x4c, 0x65, 0x81, 0x61, 0x00, 0x6f, 0xc8, 0x00, 0x79, 0x4c, 0xe1, 0x6e, 0xa8, 0xf0, 0xc2, 0x97, 0x15, 0xd9, - 0x25, 0xb0, 0xad, 0x19, 0x1f, 0x33, 0xdc, 0x41, 0xc8, 0x3f, 0x82, 0xdd, 0xb1, 0x15, 0xbb, 0x65, 0x0b, 0x86, 0x64, - 0xe3, 0x38, 0x8c, 0x31, 0x1f, 0x4f, 0xe2, 0x2b, 0x31, 0x89, 0x07, 0x3c, 0x42, 0xc7, 0x88, 0x35, 0xaf, 0x67, 0xb1, - 0x1c, 0x40, 0x76, 0xc7, 0x95, 0x0e, 0x08, 0xa1, 0xb1, 0xa1, 0x25, 0x6f, 0x0a, 0x83, 0x8b, 0x1d, 0xfb, 0x8c, 0x44, - 0x32, 0x0e, 0xc1, 0xa2, 0x55, 0x0d, 0x2c, 0x4c, 0xec, 0x96, 0x17, 0xb3, 0xd5, 0x1c, 0xff, 0x39, 0x1c, 0x10, 0x00, - 0x3b, 0xd8, 0x37, 0xec, 0x2e, 0x42, 0xa4, 0xb7, 0x05, 0xbf, 0xb3, 0x3c, 0x5d, 0xd8, 0x3d, 0x7f, 0xc7, 0xc7, 0xec, - 0xfc, 0x47, 0x0f, 0x22, 0x67, 0xcf, 0x3f, 0x01, 0x1a, 0xe2, 0x3d, 0xbf, 0x4d, 0xbd, 0x8a, 0xdd, 0x12, 0x05, 0xe1, - 0x2d, 0x38, 0x03, 0xdd, 0x43, 0x04, 0xec, 0x3b, 0xbe, 0xc0, 0x58, 0xb1, 0xb3, 0x74, 0xe9, 0x61, 0x46, 0xa8, 0x3d, - 0x9d, 0x2f, 0x6b, 0x35, 0x09, 0x37, 0x57, 0xcb, 0xc9, 0x60, 0xb0, 0xf1, 0x77, 0x7c, 0x0d, 0x7c, 0x30, 0xe7, 0x3f, - 0x7a, 0x3b, 0x2a, 0x17, 0xfe, 0xf3, 0x3a, 0x4b, 0xde, 0xf9, 0xec, 0xdd, 0x80, 0x2f, 0x00, 0x6f, 0x09, 0x1d, 0xb8, - 0xee, 0x7d, 0x26, 0xf1, 0xda, 0xde, 0xe9, 0x6b, 0x04, 0x12, 0xf9, 0x02, 0x30, 0x62, 0x62, 0x7e, 0xbf, 0x83, 0x08, - 0x8c, 0x04, 0x7c, 0x5b, 0xb5, 0x47, 0xfc, 0x96, 0x1b, 0xc0, 0xaf, 0xcc, 0x67, 0x0f, 0x3c, 0xd4, 0x3f, 0x13, 0x9f, - 0xdd, 0xf0, 0x0f, 0xfc, 0xda, 0x93, 0x92, 0x74, 0x39, 0xfb, 0x30, 0x87, 0xeb, 0xa1, 0x94, 0xa7, 0x43, 0xfa, 0xd9, - 0x18, 0x0c, 0x20, 0x14, 0x32, 0x6f, 0x3c, 0x60, 0x4d, 0x0a, 0xf1, 0x2f, 0xe0, 0xdb, 0x51, 0xc2, 0xe6, 0x8d, 0xb7, - 0xf5, 0xb5, 0xbc, 0x79, 0xe3, 0x3d, 0xf8, 0x14, 0x05, 0x58, 0x05, 0xa5, 0x2c, 0xb0, 0x0a, 0xc2, 0x46, 0x1b, 0x61, - 0x0c, 0x5c, 0xbd, 0x6b, 0x0c, 0x75, 0x3d, 0x47, 0x6c, 0x5b, 0xe9, 0xfb, 0xf0, 0x3d, 0x64, 0xc0, 0x07, 0x6f, 0x8a, - 0x92, 0xe8, 0x73, 0x6a, 0x8a, 0xa4, 0x75, 0xcf, 0xfd, 0xd6, 0xba, 0xa3, 0x35, 0xa5, 0x3e, 0x72, 0x35, 0x3e, 0x1c, - 0xea, 0x6b, 0xa1, 0x45, 0x82, 0x29, 0x68, 0x5c, 0x83, 0xb6, 0x00, 0x41, 0x9f, 0x07, 0xc8, 0x5a, 0x52, 0x2c, 0xf8, - 0xf6, 0x57, 0x88, 0xc1, 0x2b, 0xd3, 0x3b, 0x97, 0xab, 0x8c, 0x84, 0xed, 0x85, 0x5f, 0x0e, 0x6b, 0x7f, 0xe2, 0xd4, - 0xc2, 0xd2, 0x6a, 0x0e, 0xea, 0x67, 0xb6, 0x1c, 0xa7, 0xaa, 0xf6, 0x2f, 0x49, 0x52, 0xed, 0x2a, 0x2d, 0xa7, 0xf7, - 0xf6, 0x4d, 0x97, 0x09, 0x36, 0xf6, 0x03, 0xaa, 0x8e, 0xac, 0x86, 0xdd, 0x17, 0xea, 0x8b, 0x9e, 0x92, 0x09, 0xcd, - 0x47, 0x15, 0xcd, 0xb3, 0xfb, 0xcd, 0x8e, 0xfa, 0x4f, 0x2f, 0x87, 0x22, 0x40, 0xb2, 0x4a, 0x8b, 0xa5, 0xc8, 0xd9, - 0xd8, 0x8f, 0x87, 0x49, 0xa6, 0xc2, 0x0b, 0xd2, 0xd1, 0xdd, 0x6f, 0xdc, 0xdf, 0x72, 0x03, 0x59, 0xa1, 0x55, 0x1b, - 0x8c, 0x95, 0xa2, 0x65, 0xb0, 0xbe, 0x1a, 0xf7, 0xfb, 0xe2, 0x6a, 0x3c, 0x15, 0x41, 0x0d, 0xc4, 0x45, 0xe2, 0x7a, - 0x3c, 0xad, 0x89, 0x25, 0xb5, 0x2b, 0x30, 0x46, 0x8f, 0xab, 0xa2, 0xf6, 0xa9, 0xaf, 0x21, 0x14, 0xa9, 0xd6, 0xcc, - 0xb1, 0xc6, 0x8d, 0x11, 0x71, 0x87, 0x95, 0x6b, 0xa7, 0xf6, 0x3a, 0x00, 0xcb, 0xab, 0x71, 0x41, 0xd8, 0x24, 0xc7, - 0xce, 0x05, 0xac, 0x46, 0x43, 0xaa, 0xdd, 0x70, 0xeb, 0x65, 0xe7, 0x37, 0x8f, 0x13, 0x5b, 0x1b, 0xe1, 0x96, 0x02, - 0xca, 0x28, 0xbf, 0xb1, 0x9c, 0xb0, 0x3b, 0xd5, 0x3b, 0x52, 0xb5, 0x23, 0x4e, 0x5c, 0xc0, 0x72, 0xc3, 0x53, 0xab, - 0x6f, 0x62, 0x70, 0x22, 0x54, 0xad, 0x74, 0xb8, 0x93, 0x09, 0xc4, 0xfd, 0xea, 0xbe, 0xee, 0x95, 0xe0, 0x27, 0x21, - 0xaf, 0xdf, 0xf2, 0x0e, 0x00, 0x2b, 0x3e, 0xe4, 0xc5, 0xb4, 0x70, 0xb4, 0x2e, 0x83, 0x32, 0x40, 0x84, 0x66, 0x00, - 0x74, 0x72, 0x75, 0x10, 0xa5, 0x81, 0x2b, 0xee, 0x10, 0xe1, 0xa7, 0xd1, 0xb3, 0xfc, 0x3a, 0x7c, 0x56, 0x4d, 0xc3, - 0x8b, 0x3c, 0x88, 0x2e, 0xaa, 0x20, 0x7a, 0x56, 0x5d, 0x85, 0xcf, 0xf2, 0x69, 0x74, 0x91, 0x07, 0xe1, 0x45, 0xd5, - 0xd8, 0x77, 0xed, 0xee, 0x9e, 0x90, 0xb7, 0x5d, 0xfd, 0x91, 0x73, 0x65, 0x4f, 0x99, 0x9e, 0x9f, 0xd7, 0x7a, 0xa5, - 0x76, 0x9b, 0xeb, 0x35, 0x6a, 0xa6, 0x3e, 0xca, 0xfe, 0x62, 0x1b, 0x0b, 0x8f, 0xe6, 0x10, 0xfa, 0x8c, 0xb4, 0x98, - 0x7b, 0x9c, 0xeb, 0xcd, 0x9e, 0x14, 0x06, 0x46, 0x4c, 0x2a, 0x19, 0x39, 0xbd, 0xc0, 0x45, 0xa8, 0x42, 0x0c, 0x6b, - 0xe9, 0x6a, 0x9f, 0x75, 0xe9, 0x0d, 0xd4, 0x35, 0xc5, 0xbe, 0x86, 0x0c, 0xbc, 0x68, 0x7a, 0x19, 0x8c, 0x01, 0x39, - 0x02, 0xef, 0xf8, 0x6c, 0x09, 0x07, 0xe6, 0x1a, 0xa0, 0x6f, 0x1e, 0xf5, 0x75, 0xb9, 0xe3, 0x6b, 0xd5, 0x37, 0xd3, - 0xf5, 0x48, 0x29, 0x3f, 0x56, 0xfc, 0xee, 0xe2, 0x39, 0xbb, 0xe5, 0x1a, 0x15, 0xe5, 0xa5, 0x5e, 0xac, 0xf7, 0xc0, - 0x55, 0xf7, 0x12, 0x6e, 0xb3, 0x78, 0xec, 0xca, 0x03, 0x96, 0x6d, 0xd9, 0x03, 0xbb, 0x61, 0x1f, 0xd8, 0x13, 0xf6, - 0x96, 0x7d, 0x65, 0x35, 0x42, 0x94, 0x97, 0x4a, 0xca, 0xf3, 0x17, 0xfc, 0x56, 0xda, 0x1e, 0x25, 0x2c, 0xd9, 0x83, - 0x6d, 0xa7, 0x19, 0x6e, 0xd8, 0x07, 0xbe, 0x18, 0xae, 0xd8, 0x5b, 0xc8, 0x86, 0x42, 0xf1, 0x60, 0xc5, 0x6a, 0xb8, - 0xc2, 0x52, 0x06, 0x7d, 0x1a, 0x96, 0x96, 0xb0, 0x68, 0x0a, 0x45, 0x29, 0xfa, 0x2d, 0xaf, 0x09, 0x3b, 0xad, 0xc6, - 0x42, 0xe4, 0x87, 0x86, 0x2b, 0xf6, 0xc0, 0x17, 0x83, 0x15, 0xfb, 0xa0, 0x6d, 0x44, 0x83, 0x8d, 0x5b, 0x1c, 0x81, - 0x59, 0xe9, 0xc2, 0xa4, 0x40, 0xbd, 0xb5, 0x6f, 0x82, 0x1b, 0x76, 0x83, 0xf5, 0x7b, 0x82, 0x45, 0xa3, 0xcc, 0x3f, - 0x58, 0xb1, 0xaf, 0x5c, 0x62, 0xa8, 0xb9, 0xe5, 0x49, 0xc7, 0x50, 0x5d, 0x20, 0x5d, 0x11, 0x9e, 0x70, 0x7a, 0x91, - 0x7d, 0xc5, 0x32, 0xe8, 0x2b, 0xc3, 0x15, 0xdb, 0x62, 0xed, 0x6e, 0x8c, 0x71, 0xcb, 0xaa, 0x9e, 0x04, 0x05, 0x46, - 0x59, 0xa5, 0xb4, 0x5c, 0x1c, 0xb1, 0x6c, 0xea, 0xa8, 0x41, 0x6d, 0x18, 0xd0, 0x07, 0xa3, 0xff, 0xf0, 0xf5, 0xbb, - 0x1f, 0xbd, 0x52, 0xdf, 0x7c, 0x5f, 0x3a, 0xde, 0x95, 0x25, 0x7a, 0x57, 0xfe, 0xca, 0xcb, 0xd9, 0xcb, 0xf9, 0x44, - 0xd7, 0x92, 0x36, 0x19, 0x72, 0x37, 0x9d, 0xbd, 0xec, 0xf0, 0xb7, 0xfc, 0xd5, 0xf7, 0x1b, 0xab, 0x8f, 0xd5, 0x77, - 0x75, 0xf7, 0x3e, 0x0c, 0x36, 0x8d, 0x53, 0xf1, 0xdd, 0xe9, 0x8a, 0x63, 0x3b, 0x6b, 0xed, 0x9d, 0xf9, 0x3f, 0x5c, - 0xeb, 0x2d, 0x8e, 0xdd, 0x0d, 0xdf, 0x0e, 0x37, 0xf6, 0x30, 0xc8, 0xef, 0x4b, 0xc5, 0x71, 0x56, 0xf3, 0x17, 0x5e, - 0xa7, 0x24, 0x0b, 0xa8, 0x46, 0x9f, 0x8d, 0x34, 0x74, 0xc9, 0x4c, 0x4c, 0x43, 0x7c, 0x91, 0x01, 0x3a, 0x17, 0x88, - 0x67, 0xf7, 0x7c, 0x3c, 0xb9, 0xbf, 0x8a, 0x27, 0xf7, 0x03, 0xfe, 0xd9, 0xb4, 0xa0, 0xbd, 0xe0, 0xee, 0x7d, 0xf6, - 0x2b, 0x2f, 0xec, 0x25, 0xf9, 0xd2, 0x67, 0xef, 0x85, 0xbb, 0x4a, 0x5f, 0xfa, 0xec, 0xab, 0xe0, 0xbf, 0x8e, 0x34, - 0x59, 0x06, 0xfb, 0x5a, 0xf3, 0x5f, 0x47, 0xc8, 0xfa, 0xc1, 0xbe, 0x08, 0xfe, 0x1e, 0xfc, 0xbf, 0xab, 0x04, 0x2d, - 0xe3, 0x5f, 0x6a, 0xf5, 0xf3, 0x83, 0x8c, 0xcd, 0x81, 0x37, 0xa1, 0x15, 0xf4, 0xe6, 0x6d, 0x2d, 0x7f, 0x12, 0x17, - 0x47, 0xaa, 0x9e, 0x1a, 0x0e, 0x5a, 0x2c, 0x66, 0x51, 0x1f, 0xa5, 0x53, 0x79, 0x93, 0x77, 0x3c, 0x93, 0x16, 0xe6, - 0x7b, 0x08, 0x07, 0x7e, 0x67, 0xc3, 0x14, 0xec, 0x38, 0x6e, 0x06, 0xef, 0x18, 0x40, 0x48, 0x66, 0xd3, 0x2d, 0xbf, - 0xe1, 0x4f, 0xf8, 0x57, 0xbe, 0x0b, 0x1e, 0xf8, 0x07, 0xfe, 0x96, 0xd7, 0x35, 0xdf, 0xb1, 0xa5, 0x84, 0x3c, 0xad, - 0xb7, 0x97, 0xc1, 0x96, 0xd5, 0xbb, 0xcb, 0xe0, 0x81, 0xd5, 0xdb, 0xe7, 0xc1, 0x0d, 0xab, 0x77, 0xcf, 0x83, 0x0f, - 0x6c, 0x7b, 0x19, 0x3c, 0x61, 0xbb, 0xcb, 0xe0, 0x2d, 0xdb, 0x3e, 0x0f, 0xbe, 0xb2, 0xdd, 0xf3, 0xa0, 0x56, 0x48, - 0x0f, 0x5f, 0x85, 0x64, 0x3a, 0xf9, 0x5a, 0x33, 0xc3, 0xaa, 0x1b, 0x7c, 0x11, 0xd6, 0x2f, 0xaa, 0x65, 0xf0, 0xa5, - 0x66, 0xba, 0xcd, 0x81, 0x10, 0x4c, 0xb7, 0x38, 0xb8, 0xa5, 0x27, 0xa6, 0x5d, 0x41, 0x2a, 0x58, 0x57, 0x4b, 0x83, - 0x45, 0xdd, 0xb4, 0x4e, 0x66, 0xc7, 0x3b, 0x31, 0xee, 0xf0, 0x4e, 0x5c, 0xb0, 0x65, 0xd3, 0xe9, 0xaa, 0x73, 0xfa, - 0x3c, 0xd0, 0x47, 0x80, 0xde, 0xfb, 0x2b, 0xe9, 0x41, 0x53, 0x34, 0x3c, 0x57, 0xba, 0xe3, 0xd6, 0x7e, 0x1f, 0x5a, - 0xfb, 0x3d, 0x93, 0x8a, 0xb4, 0x88, 0x45, 0x65, 0x51, 0x55, 0xc8, 0x27, 0x1e, 0x64, 0x5a, 0xab, 0x96, 0x30, 0x52, - 0x67, 0x02, 0x26, 0x7d, 0x41, 0x87, 0x41, 0x4e, 0x76, 0x05, 0xb6, 0xe4, 0x9b, 0x41, 0xc2, 0xd6, 0x3c, 0x9e, 0x0e, - 0x93, 0x60, 0xc9, 0xee, 0xf8, 0xb0, 0x5b, 0x2c, 0x58, 0xa9, 0x30, 0x26, 0x7d, 0x7d, 0x3a, 0xda, 0xdd, 0x79, 0x6f, - 0x95, 0xc6, 0x71, 0x26, 0x50, 0xe7, 0x56, 0xe9, 0x6d, 0x7e, 0xeb, 0xec, 0xea, 0x6b, 0xb5, 0xcb, 0x83, 0xc0, 0xf0, - 0x2b, 0x10, 0xed, 0x10, 0xef, 0x1d, 0xd4, 0x18, 0xe9, 0x96, 0xcc, 0xba, 0xaf, 0xec, 0x7d, 0x7d, 0x6b, 0xb6, 0xea, - 0x7f, 0xb7, 0x08, 0xda, 0xcb, 0x65, 0xef, 0x7f, 0x36, 0xaf, 0xfe, 0xd6, 0xf1, 0xea, 0xc6, 0x9f, 0x3c, 0xf0, 0xcf, - 0x18, 0x9d, 0x80, 0x89, 0x6c, 0xc7, 0x3f, 0x8f, 0xb6, 0x8d, 0x53, 0x9e, 0xdc, 0xcb, 0xff, 0xaf, 0x14, 0x68, 0xef, - 0xe6, 0x95, 0xbd, 0x29, 0x6e, 0x79, 0xc7, 0x5e, 0xbe, 0xb4, 0xf6, 0x44, 0x83, 0x50, 0xf2, 0x99, 0xbb, 0x41, 0xd1, - 0xb0, 0x27, 0xbe, 0xe4, 0xd5, 0xec, 0xf3, 0x7c, 0xb2, 0xe5, 0xc7, 0x3b, 0xe2, 0xe7, 0x8e, 0x1d, 0xf1, 0xa5, 0x3f, - 0x58, 0x36, 0xdf, 0xea, 0xd5, 0xce, 0x9d, 0xdc, 0xa9, 0xf4, 0x8e, 0x1f, 0xef, 0xe3, 0xc3, 0x7f, 0xbb, 0xd2, 0xbb, - 0xef, 0xae, 0xb4, 0x5d, 0xe5, 0xee, 0xce, 0x37, 0x1d, 0xdf, 0xc8, 0x5a, 0x63, 0xb8, 0x99, 0x51, 0x30, 0xc2, 0xb4, - 0x85, 0x69, 0x1a, 0x44, 0x96, 0x62, 0x11, 0x12, 0x35, 0x4a, 0xe7, 0x44, 0x9f, 0x05, 0x9d, 0x82, 0x2e, 0x6e, 0xf4, - 0xb7, 0x7c, 0xcc, 0x16, 0xc6, 0x65, 0xf3, 0xf6, 0x6a, 0x31, 0x19, 0x0c, 0x6e, 0xfd, 0xfd, 0x3d, 0x0f, 0x67, 0xb7, - 0x73, 0xf6, 0x8e, 0xdf, 0xd3, 0x7a, 0x9a, 0xa8, 0xc6, 0x17, 0x8f, 0x49, 0x60, 0xb7, 0xbe, 0x3f, 0xb1, 0x88, 0x60, - 0xed, 0x1b, 0xe7, 0xad, 0x3f, 0x90, 0x66, 0x69, 0xb9, 0xb5, 0x7f, 0x78, 0x5c, 0x43, 0x71, 0x0b, 0x42, 0xc6, 0x07, - 0x5b, 0xe5, 0xf0, 0x96, 0x7f, 0xf2, 0xde, 0xf9, 0xd3, 0x77, 0x3a, 0xf8, 0x66, 0xa2, 0xce, 0xa5, 0xb7, 0x17, 0xcf, - 0xd9, 0xaf, 0xfc, 0xb3, 0x3c, 0x53, 0xde, 0x0b, 0x39, 0x6d, 0x6f, 0x90, 0xc4, 0x89, 0x8e, 0x8a, 0xaf, 0x6e, 0x22, - 0x81, 0x42, 0x20, 0x1e, 0x47, 0xcd, 0x1f, 0x26, 0xe5, 0xd4, 0xdb, 0x01, 0xc9, 0x2b, 0xb7, 0x15, 0xd1, 0xb7, 0x9c, - 0xf3, 0xc5, 0xf0, 0x72, 0xfa, 0xb5, 0xdb, 0xb7, 0x47, 0x85, 0xb5, 0xa9, 0x88, 0xb7, 0x5b, 0x0c, 0xc2, 0x3a, 0x99, - 0x59, 0xe6, 0x92, 0x2f, 0x7d, 0xad, 0xcd, 0xdc, 0x63, 0x7a, 0xc7, 0x99, 0x66, 0xc8, 0xe8, 0x0b, 0xcc, 0x4c, 0x87, - 0xc3, 0xdd, 0x39, 0x96, 0xc7, 0x87, 0x6f, 0x9f, 0x3d, 0x19, 0x3c, 0xc1, 0x10, 0x2e, 0x2b, 0x2c, 0xe4, 0x2b, 0x1f, - 0x66, 0x75, 0xeb, 0xda, 0x71, 0xf1, 0x7c, 0xf8, 0x12, 0xf2, 0x06, 0x5d, 0x0f, 0x4d, 0x11, 0xad, 0xf2, 0x3b, 0x8a, - 0x3e, 0x51, 0x72, 0xd0, 0xf1, 0x04, 0x6a, 0x87, 0x5c, 0xb8, 0x5f, 0x9f, 0x71, 0x50, 0x74, 0x60, 0xa9, 0xfd, 0xfe, - 0xf9, 0x67, 0x22, 0x94, 0x86, 0xf1, 0x7e, 0x19, 0x46, 0x7f, 0xc4, 0x65, 0xb1, 0x86, 0x23, 0x76, 0x00, 0x9f, 0x7b, - 0xa6, 0xaf, 0x61, 0x77, 0xbe, 0xef, 0x07, 0xde, 0x96, 0xdf, 0xb0, 0xaf, 0xdc, 0xbb, 0x1c, 0xbe, 0xf5, 0x9f, 0x3d, - 0x01, 0xf9, 0x09, 0xc6, 0xe5, 0x0b, 0x86, 0xc4, 0x76, 0x14, 0xa3, 0xd6, 0xe1, 0x97, 0x1a, 0x62, 0xb5, 0x3e, 0x23, - 0x75, 0x17, 0xa4, 0x7f, 0x54, 0xc8, 0x7e, 0x42, 0x60, 0x35, 0x49, 0x9f, 0x02, 0x93, 0xf8, 0xb6, 0x86, 0x04, 0xd2, - 0xb4, 0x40, 0x0c, 0x0e, 0x14, 0x9f, 0x0a, 0xfe, 0x75, 0xf8, 0x85, 0xe4, 0xbf, 0x45, 0xcd, 0xc7, 0xf0, 0x37, 0x0c, - 0xcd, 0xa4, 0x7a, 0x48, 0xeb, 0x28, 0xf1, 0x6a, 0x38, 0xf5, 0xc2, 0x4a, 0xa8, 0x93, 0x21, 0x48, 0xc5, 0x90, 0x0b, - 0x71, 0xf1, 0x7c, 0x72, 0x5b, 0x8a, 0xf0, 0x8f, 0x09, 0x3e, 0x93, 0x2b, 0x4d, 0x3e, 0xa3, 0x27, 0x8d, 0x2c, 0xe0, - 0x41, 0xbe, 0x2f, 0x7b, 0x35, 0x58, 0xd4, 0x43, 0x7e, 0x5b, 0xbb, 0xef, 0xcb, 0x39, 0x41, 0x8f, 0xec, 0x07, 0x34, - 0x07, 0x03, 0x35, 0x03, 0x29, 0x43, 0x70, 0x0b, 0x97, 0x7e, 0x4f, 0x15, 0xe4, 0xcb, 0xef, 0x7d, 0x11, 0x32, 0x70, - 0x65, 0x41, 0x98, 0x72, 0xa9, 0x90, 0x02, 0xc7, 0x6d, 0x3d, 0xf8, 0xa2, 0xd1, 0x49, 0x24, 0xf8, 0x94, 0x80, 0x24, - 0x69, 0x79, 0x20, 0x69, 0xc4, 0x74, 0x20, 0x2e, 0x94, 0xa6, 0x59, 0x49, 0x11, 0x87, 0xd8, 0x55, 0xdf, 0x21, 0xe1, - 0x59, 0xf0, 0x81, 0xc1, 0xda, 0x91, 0xa2, 0xc5, 0x57, 0x63, 0x3a, 0xd6, 0x61, 0x43, 0x77, 0xb2, 0xb8, 0x5f, 0x25, - 0x75, 0x1a, 0x89, 0x2b, 0xef, 0x85, 0xfc, 0xf9, 0x4f, 0x25, 0x02, 0xe9, 0x5d, 0x0d, 0xc4, 0x20, 0xf8, 0x01, 0xfa, - 0x0f, 0x58, 0xe4, 0x20, 0x28, 0xd5, 0x65, 0x98, 0x57, 0x19, 0x15, 0x38, 0xdb, 0xb1, 0xed, 0x9c, 0xa9, 0xba, 0x05, - 0x5f, 0x84, 0x61, 0x48, 0x3b, 0x5b, 0x35, 0x27, 0xb7, 0x7a, 0x03, 0xf5, 0x4c, 0xe2, 0x48, 0x2d, 0xc5, 0x91, 0xb6, - 0xe6, 0x3e, 0x5d, 0x7a, 0xdd, 0xf2, 0x82, 0x86, 0x0b, 0xd0, 0x8b, 0xd2, 0x5d, 0xe7, 0x13, 0x0a, 0x5d, 0x56, 0xe3, - 0x6a, 0x28, 0xea, 0x50, 0x8e, 0xb1, 0xf6, 0xe7, 0x4a, 0x9e, 0xdf, 0x81, 0xf5, 0x08, 0x0d, 0x5f, 0x95, 0x3a, 0x88, - 0xed, 0x27, 0x7a, 0xd7, 0xa9, 0xd4, 0xdf, 0x00, 0x30, 0x70, 0xea, 0x78, 0xa8, 0x8f, 0xda, 0x29, 0x64, 0x3b, 0xf7, - 0x96, 0x18, 0x95, 0x2b, 0xe1, 0xa9, 0xd2, 0xf2, 0x94, 0xb2, 0xea, 0x6b, 0xc1, 0xad, 0xec, 0x3e, 0x1b, 0x40, 0x46, - 0x1b, 0x14, 0xc8, 0x33, 0x6a, 0x6b, 0x3c, 0x48, 0x35, 0xcd, 0x12, 0xc7, 0xf0, 0x41, 0x91, 0x66, 0x15, 0x58, 0xbc, - 0xcc, 0x25, 0x73, 0x50, 0xb0, 0x5c, 0x6f, 0x36, 0xd3, 0x4c, 0xf5, 0x45, 0x6e, 0x6f, 0x34, 0x5e, 0xa6, 0xff, 0x66, - 0xc9, 0x80, 0x47, 0x17, 0xcf, 0xfd, 0x00, 0xd2, 0x24, 0xc5, 0x03, 0x24, 0xc1, 0xf6, 0x60, 0x17, 0x3b, 0x0c, 0x5b, - 0xc5, 0xca, 0x9e, 0x3c, 0x5d, 0xee, 0xd0, 0x94, 0x4b, 0x70, 0xc9, 0x89, 0xb9, 0x9c, 0xfa, 0xbe, 0x64, 0xbd, 0xa1, - 0x38, 0x65, 0xd3, 0x04, 0x94, 0x04, 0xda, 0x2d, 0xf8, 0x2f, 0x7c, 0x6a, 0xe8, 0xb4, 0x00, 0x4b, 0x6d, 0x37, 0xe0, - 0xbf, 0xd0, 0x2f, 0xb6, 0xbb, 0xa8, 0x1f, 0x98, 0x07, 0x7b, 0xb3, 0xb8, 0x32, 0x06, 0x9c, 0x24, 0xae, 0x34, 0x8f, - 0x5c, 0x3f, 0x28, 0xfa, 0x74, 0x59, 0x3b, 0x70, 0xa6, 0xb8, 0xb0, 0x4a, 0x6d, 0x92, 0x5e, 0xfb, 0x2d, 0x35, 0xf1, - 0x26, 0x4a, 0xaa, 0xc2, 0x76, 0x48, 0xfb, 0x97, 0x94, 0x33, 0x55, 0xdc, 0x21, 0x7a, 0xb2, 0x9b, 0xb8, 0x0a, 0xbc, - 0xb0, 0xaa, 0xd8, 0x08, 0xb5, 0x19, 0x59, 0x4e, 0xe0, 0x74, 0x8f, 0xd5, 0x05, 0x1f, 0xdb, 0xd5, 0xec, 0x82, 0x95, - 0x6c, 0xcd, 0xa4, 0xfb, 0xbc, 0x1d, 0x73, 0x21, 0xaf, 0xf4, 0xb2, 0x68, 0x05, 0xb4, 0x07, 0x81, 0xc3, 0x2f, 0x35, - 0xdd, 0xa3, 0x67, 0x9b, 0x6d, 0x6a, 0xb3, 0xb1, 0xb5, 0x08, 0x21, 0x03, 0xd1, 0xd0, 0x17, 0x72, 0x46, 0x91, 0xaf, - 0xd2, 0x72, 0xad, 0x36, 0x56, 0x19, 0x2f, 0x30, 0x11, 0x64, 0x38, 0x0b, 0xef, 0xd1, 0xd3, 0x7a, 0xa4, 0x29, 0x26, - 0xc1, 0x49, 0x17, 0x7f, 0x01, 0x36, 0x94, 0x27, 0xb9, 0x39, 0x20, 0x07, 0x50, 0xb9, 0x14, 0xa5, 0x52, 0x06, 0xff, - 0xac, 0xee, 0xc8, 0xb6, 0xea, 0xbf, 0xd3, 0x40, 0x06, 0x77, 0xa0, 0x6f, 0x7b, 0xa1, 0xb5, 0xa3, 0x9d, 0x2b, 0x5b, - 0xd3, 0xb6, 0x4c, 0xf3, 0x18, 0x59, 0x6c, 0x00, 0xf9, 0x44, 0x3a, 0x07, 0x22, 0xaf, 0x89, 0xc6, 0x3b, 0xbb, 0xe6, - 0xe3, 0xa9, 0x78, 0x4c, 0xde, 0xab, 0x7c, 0xdf, 0xdc, 0xeb, 0x83, 0x31, 0xf6, 0x2d, 0x28, 0x13, 0x1f, 0xad, 0xb6, - 0xd6, 0x25, 0xd6, 0x5b, 0xa5, 0x49, 0x74, 0xc3, 0x15, 0x74, 0x1c, 0x89, 0x1b, 0xc4, 0xe0, 0x98, 0xf1, 0xda, 0x2a, - 0x4b, 0x5f, 0x61, 0x99, 0xeb, 0x98, 0x25, 0x43, 0x26, 0x75, 0x9e, 0x28, 0x78, 0xf2, 0xf3, 0x84, 0x64, 0x44, 0xd4, - 0x6c, 0xcb, 0x51, 0xca, 0x4d, 0x0b, 0xb8, 0xcc, 0xc8, 0x00, 0xbe, 0x49, 0x13, 0x80, 0x72, 0xf9, 0x12, 0xa4, 0xd2, - 0x10, 0xc1, 0x35, 0xdb, 0x4b, 0x46, 0xb7, 0x8e, 0xd6, 0x41, 0x95, 0x64, 0xee, 0xe0, 0xdc, 0xce, 0x22, 0xa5, 0xde, - 0x7c, 0x84, 0x61, 0x27, 0x1f, 0xc3, 0x3a, 0xc1, 0x6f, 0x03, 0x6a, 0xd2, 0xe7, 0xc2, 0x8b, 0x46, 0x80, 0xa6, 0xbe, - 0x53, 0x65, 0x7c, 0x2e, 0xbc, 0x6c, 0xb4, 0x65, 0x19, 0xa5, 0x50, 0x5d, 0x30, 0xbb, 0x35, 0x5d, 0x88, 0x79, 0x55, - 0x0d, 0xb4, 0x41, 0x6e, 0xd7, 0x31, 0x03, 0x1a, 0xb5, 0x5d, 0x79, 0x64, 0x01, 0x6e, 0xcd, 0x44, 0x60, 0xe4, 0xfc, - 0x87, 0xfc, 0x95, 0x0a, 0xe7, 0xe9, 0xf7, 0x43, 0x6f, 0xbf, 0x0d, 0xa2, 0xd1, 0xf6, 0x92, 0xed, 0x82, 0x68, 0xb4, - 0xbb, 0x6c, 0x18, 0xfd, 0x7e, 0x4e, 0xbf, 0x9f, 0x37, 0xa0, 0x2a, 0x11, 0x26, 0xe2, 0x5e, 0xbf, 0x51, 0xcb, 0x57, - 0x6a, 0xfd, 0x4e, 0x2d, 0x5f, 0xaa, 0xe1, 0xad, 0x3d, 0x89, 0x04, 0x91, 0xa5, 0xb1, 0x79, 0x90, 0x6c, 0xa9, 0x96, - 0x4a, 0xc7, 0xa8, 0x32, 0xa2, 0x96, 0xce, 0xe6, 0x58, 0x31, 0xd2, 0xce, 0x41, 0xc9, 0x80, 0x4c, 0x8b, 0xab, 0x1a, - 0xd3, 0xcd, 0x8a, 0x96, 0x98, 0x8c, 0xb0, 0xb2, 0x2d, 0x6f, 0x37, 0xa9, 0x9a, 0xce, 0xc9, 0xcd, 0xad, 0x52, 0x6e, - 0x6e, 0x05, 0xcf, 0xbf, 0xa1, 0x5b, 0x2e, 0xb9, 0xf6, 0x32, 0x9b, 0x16, 0x4a, 0xb7, 0x8c, 0x6b, 0xb0, 0xb5, 0x6f, - 0x02, 0x59, 0xe6, 0x23, 0x45, 0x8d, 0xed, 0x45, 0xa3, 0x7c, 0x83, 0x6c, 0x45, 0x8c, 0x3a, 0x65, 0xc1, 0xf8, 0xdb, - 0x1d, 0x3d, 0x90, 0x81, 0xaa, 0xaa, 0x36, 0x0e, 0xee, 0xac, 0xf4, 0x87, 0xe5, 0xc5, 0x73, 0x96, 0x58, 0xe9, 0xe4, - 0x42, 0x15, 0xfa, 0x83, 0x10, 0xdd, 0x54, 0x36, 0x1c, 0x1c, 0xea, 0x62, 0x2b, 0x03, 0x42, 0x0f, 0xd3, 0x7b, 0x1b, - 0x2b, 0x59, 0xee, 0x9a, 0xf2, 0xc5, 0x8c, 0x27, 0x1c, 0x47, 0x5f, 0xae, 0x16, 0x61, 0xad, 0x16, 0xd9, 0x09, 0xf0, - 0xd0, 0x5a, 0x2d, 0x85, 0x5c, 0x2d, 0xc2, 0x99, 0xe9, 0x42, 0xcd, 0xf4, 0x0c, 0x14, 0x90, 0x42, 0xcd, 0xf2, 0x04, - 0x60, 0xe1, 0x85, 0x99, 0xe1, 0xc2, 0xcc, 0x70, 0x1c, 0x52, 0xe3, 0xff, 0xa0, 0xf7, 0x3a, 0xf7, 0xdc, 0x72, 0x37, - 0x3a, 0x8d, 0xf8, 0x76, 0xb4, 0xc1, 0x1c, 0x1f, 0x84, 0x13, 0x08, 0x15, 0x2c, 0x11, 0xab, 0x47, 0x23, 0xec, 0xa8, - 0xa1, 0x72, 0xb4, 0x5f, 0x16, 0x96, 0x64, 0x49, 0x58, 0x92, 0x7b, 0x35, 0xce, 0xa5, 0xe5, 0xe2, 0x55, 0x12, 0x88, - 0x44, 0xc6, 0x4b, 0x69, 0x82, 0x4f, 0x78, 0x39, 0x32, 0x52, 0xf3, 0x64, 0x91, 0x7a, 0x39, 0xcb, 0xd8, 0x18, 0x31, - 0x8c, 0x42, 0xbf, 0xa9, 0xfa, 0xfd, 0xb4, 0xf4, 0x72, 0x6a, 0xe7, 0x67, 0x70, 0xbd, 0x3c, 0x75, 0x16, 0x39, 0x42, - 0x5e, 0x8d, 0xa4, 0xc2, 0xf2, 0x5a, 0xa9, 0xa7, 0x2f, 0xc1, 0x07, 0x75, 0xf7, 0x46, 0x01, 0x10, 0x17, 0xb9, 0xf4, - 0xaf, 0x2d, 0xe1, 0xd2, 0x94, 0x1b, 0x18, 0xf4, 0x90, 0xe7, 0x24, 0x84, 0x4a, 0x10, 0x92, 0xc2, 0xba, 0x71, 0x5f, - 0x3c, 0x9f, 0xb8, 0xee, 0x2c, 0x36, 0x30, 0xc1, 0xe1, 0x00, 0x88, 0x07, 0x53, 0x2f, 0x1a, 0xf0, 0x52, 0xcd, 0x99, - 0x4f, 0x5e, 0x4e, 0x30, 0x19, 0xa0, 0xaa, 0x18, 0x38, 0x65, 0x3d, 0x93, 0x8f, 0x8c, 0x9b, 0x99, 0xef, 0x07, 0xf8, - 0x6e, 0x5d, 0x48, 0xf4, 0x07, 0x05, 0x50, 0x90, 0x29, 0x80, 0x82, 0xc4, 0x00, 0x14, 0xc4, 0x06, 0xa0, 0x60, 0xd3, - 0xf0, 0xb5, 0xd4, 0xe1, 0x46, 0x40, 0x17, 0xe1, 0x43, 0xcf, 0xc2, 0xc6, 0x0a, 0xc5, 0xb3, 0x31, 0x1b, 0xb3, 0x42, - 0xed, 0x3c, 0xb9, 0x9c, 0x8a, 0x9d, 0xc5, 0x58, 0x57, 0x91, 0x65, 0xe2, 0x85, 0x84, 0x22, 0xe7, 0xdc, 0x48, 0xd4, - 0xdd, 0xcf, 0xbd, 0x97, 0x64, 0x2c, 0x99, 0x37, 0x34, 0x6a, 0x30, 0x2f, 0xbb, 0x0e, 0x60, 0x5a, 0xf2, 0x6d, 0x41, - 0x83, 0xe9, 0x54, 0x79, 0x44, 0x9a, 0x04, 0xb5, 0x73, 0x99, 0x14, 0x39, 0x21, 0x4c, 0x82, 0x5e, 0x09, 0x7e, 0x23, - 0x51, 0xfe, 0xbf, 0xe9, 0x04, 0x0f, 0x70, 0x4c, 0xb4, 0x4a, 0xbe, 0x82, 0x01, 0x33, 0xe7, 0x2f, 0xa4, 0x53, 0x36, - 0x42, 0x31, 0x96, 0x69, 0x3c, 0xfa, 0xca, 0x86, 0x08, 0x6d, 0xf5, 0x02, 0x4d, 0x4c, 0x50, 0x07, 0x78, 0x44, 0x7f, - 0x8d, 0xbe, 0x1a, 0x0a, 0x95, 0xae, 0x46, 0xea, 0x9a, 0x9d, 0x73, 0xfe, 0xbe, 0x36, 0x9c, 0xc8, 0x98, 0x36, 0x05, - 0xbe, 0x01, 0x81, 0x7c, 0x03, 0x01, 0xe0, 0xaa, 0xe9, 0xcc, 0x5e, 0x01, 0x9c, 0x03, 0x01, 0x3c, 0xce, 0x3b, 0x1e, - 0x3f, 0xd2, 0x5f, 0xc5, 0x71, 0xef, 0x34, 0x0d, 0xdb, 0x7f, 0x05, 0xc6, 0x62, 0x28, 0xc7, 0xf3, 0x9d, 0x82, 0x64, - 0x8f, 0x52, 0x96, 0xae, 0x9a, 0xc8, 0x0e, 0xc5, 0xfa, 0x34, 0xa7, 0x8c, 0xa5, 0x6d, 0x39, 0x46, 0x1b, 0xaf, 0x1f, - 0xe3, 0xf1, 0xcd, 0x8d, 0x9e, 0x7c, 0xd0, 0x83, 0xdb, 0xdb, 0xdb, 0xd7, 0x3d, 0x66, 0xf3, 0xad, 0x58, 0x3c, 0x2b, - 0xe2, 0xc4, 0x69, 0x1d, 0x72, 0x80, 0x83, 0x9c, 0x84, 0x40, 0x3a, 0xc6, 0xa5, 0x16, 0x1d, 0xd4, 0x2c, 0xe7, 0x35, - 0xb0, 0xcc, 0x22, 0xc8, 0x06, 0x88, 0x6a, 0x9a, 0x8a, 0xd5, 0xf0, 0xa0, 0x54, 0xcd, 0x29, 0x95, 0xda, 0x37, 0x9c, - 0xad, 0x4e, 0x9f, 0x58, 0xb5, 0x09, 0xb7, 0xfe, 0xb5, 0xf6, 0x04, 0x6d, 0x25, 0x0d, 0x84, 0x7a, 0xbe, 0x4e, 0xef, - 0x28, 0x8a, 0xc7, 0x99, 0x89, 0xa7, 0x2a, 0x30, 0xf6, 0xad, 0x1d, 0x41, 0x41, 0xd2, 0x74, 0x1d, 0x70, 0x98, 0x46, - 0x27, 0x2c, 0xfe, 0x29, 0x7d, 0x28, 0x2f, 0x6a, 0x05, 0x4e, 0xf2, 0x77, 0xe1, 0x22, 0x92, 0x58, 0xe8, 0x97, 0x04, - 0x40, 0x22, 0x83, 0x57, 0xa3, 0x62, 0x2d, 0x54, 0x80, 0x9c, 0xa2, 0xf4, 0x56, 0xf1, 0x71, 0x29, 0x4a, 0x95, 0x52, - 0x99, 0x1b, 0x95, 0x02, 0xc2, 0xda, 0xc0, 0xd1, 0x05, 0x7c, 0x01, 0x41, 0x6b, 0xb9, 0x5b, 0xdb, 0x9e, 0x37, 0x32, - 0x9f, 0x99, 0xe6, 0x69, 0xf5, 0x51, 0xfd, 0xfd, 0x61, 0x89, 0x61, 0x36, 0x9e, 0xfe, 0xbe, 0xcd, 0x10, 0x6e, 0xfe, - 0x86, 0x21, 0xba, 0x03, 0x70, 0xcc, 0xd2, 0x1e, 0x0a, 0x59, 0x30, 0xc1, 0x1a, 0xaa, 0xf2, 0x94, 0xcf, 0x5e, 0x3e, - 0xb9, 0x05, 0x34, 0x35, 0x74, 0x71, 0xa3, 0x53, 0x5d, 0x95, 0x20, 0x7c, 0xdf, 0x15, 0xea, 0xb1, 0x39, 0xe0, 0xd4, - 0x00, 0x50, 0x2c, 0xf2, 0x5a, 0x8f, 0xed, 0x1f, 0xf4, 0x46, 0xbd, 0x01, 0xe2, 0xe9, 0x9c, 0x17, 0xfe, 0x11, 0xfd, - 0x3a, 0xf5, 0x67, 0x5c, 0x08, 0xa2, 0x5e, 0x4f, 0xc2, 0x7b, 0x71, 0x96, 0xc6, 0xc1, 0x59, 0x6f, 0x60, 0x2e, 0x02, - 0xc5, 0x59, 0x9a, 0x9f, 0x81, 0x58, 0x8e, 0xf0, 0x88, 0x35, 0xbb, 0x03, 0xc4, 0xc0, 0x52, 0x87, 0x24, 0xab, 0x8e, - 0xed, 0xf7, 0xdf, 0x8c, 0x0c, 0x6f, 0x3a, 0x22, 0xc2, 0xe8, 0xdf, 0x15, 0x08, 0x50, 0xb0, 0xcc, 0x6c, 0x67, 0x26, - 0x5d, 0xed, 0x59, 0x3d, 0x6f, 0x36, 0x79, 0x57, 0xef, 0x58, 0x4d, 0xcb, 0xa9, 0x69, 0x95, 0xd5, 0xb4, 0x49, 0x0e, - 0x35, 0x13, 0xfd, 0xbe, 0xc6, 0x47, 0xcd, 0xe7, 0x80, 0xcb, 0x86, 0xc9, 0x6f, 0x66, 0xd5, 0xbc, 0xdf, 0xf7, 0xe4, - 0x23, 0xf8, 0x85, 0xc4, 0x65, 0x6e, 0x8d, 0xe5, 0xd3, 0xd7, 0xc4, 0x67, 0x66, 0x10, 0x8f, 0xee, 0x8e, 0xa0, 0xbe, - 0x6e, 0x84, 0xd7, 0x31, 0x57, 0xd8, 0x4c, 0x4c, 0xdf, 0xc0, 0xe0, 0x79, 0xc2, 0x07, 0x6f, 0x39, 0xfa, 0x1b, 0xe9, - 0xcc, 0x14, 0x2c, 0xe4, 0xdc, 0x9f, 0xbc, 0x41, 0xe8, 0x64, 0x44, 0x7a, 0xd0, 0xe9, 0x04, 0x0d, 0xd9, 0xef, 0xaf, - 0xa0, 0x33, 0x5b, 0xa9, 0x94, 0xad, 0x8a, 0xca, 0x74, 0x5d, 0x17, 0x65, 0x05, 0x1d, 0x4b, 0x3f, 0x6f, 0x85, 0xcc, - 0xac, 0x9f, 0x59, 0xc8, 0x4f, 0x2b, 0x89, 0x35, 0x65, 0xdb, 0x27, 0x6a, 0x83, 0x34, 0xeb, 0x42, 0x75, 0x81, 0x73, - 0x67, 0xed, 0xf5, 0x46, 0xa8, 0x7f, 0xce, 0x47, 0xeb, 0x62, 0xed, 0x81, 0x4b, 0xcc, 0x2c, 0x9d, 0x2b, 0x0e, 0x8d, - 0xdc, 0x1f, 0x7d, 0x29, 0xd2, 0x9c, 0xf2, 0x00, 0x0d, 0xa2, 0x98, 0xdb, 0x6f, 0x81, 0xf4, 0x43, 0x6f, 0x81, 0xec, - 0xa3, 0x73, 0x4e, 0xde, 0x00, 0x38, 0x1d, 0x22, 0xe2, 0x56, 0x24, 0xe8, 0x58, 0x35, 0xbc, 0xb5, 0x70, 0x4f, 0x7b, - 0x69, 0xdc, 0x4b, 0xf3, 0xb3, 0xb4, 0xdf, 0x37, 0x00, 0x9a, 0x29, 0x22, 0xc3, 0xe3, 0x8c, 0x5c, 0x24, 0x2d, 0x04, - 0x53, 0xda, 0x7f, 0x35, 0x86, 0x04, 0x81, 0x80, 0xff, 0x5d, 0x78, 0x4f, 0x00, 0x6d, 0x93, 0x36, 0xe0, 0xaa, 0xc7, - 0x74, 0x60, 0xb6, 0xe4, 0x6c, 0xd5, 0xd9, 0x00, 0x94, 0x53, 0xa5, 0xf5, 0x94, 0xc7, 0x35, 0x45, 0x44, 0xaa, 0x2c, - 0xd4, 0x6f, 0xac, 0x27, 0x93, 0x55, 0x2e, 0x32, 0xe4, 0xa8, 0x4c, 0xef, 0x6b, 0x46, 0x88, 0x5d, 0xfa, 0xf9, 0x02, - 0x96, 0x6c, 0xfc, 0x09, 0x27, 0x6f, 0x09, 0x90, 0xb6, 0xb3, 0x76, 0x55, 0xed, 0x72, 0xdc, 0xda, 0xcd, 0x01, 0xc9, - 0xd7, 0x1b, 0x8d, 0x46, 0xda, 0x4f, 0x4e, 0xc0, 0x50, 0xf5, 0xd4, 0x52, 0xe8, 0xb1, 0x5a, 0x61, 0xeb, 0x76, 0xe4, - 0x32, 0x4b, 0x06, 0xf3, 0x85, 0x71, 0xfc, 0xca, 0x7c, 0xf4, 0xf1, 0x52, 0x59, 0xbb, 0x8e, 0xf8, 0xfa, 0x8f, 0xb2, - 0x5a, 0xdf, 0xf3, 0xae, 0x6a, 0x02, 0xbe, 0xa8, 0x62, 0x4b, 0xbf, 0xe3, 0x3d, 0xd9, 0xbb, 0xf8, 0xda, 0x0d, 0x76, - 0xc9, 0xf7, 0xbc, 0x45, 0x9d, 0xe7, 0x2b, 0x5f, 0x37, 0xaa, 0x74, 0x7b, 0x2f, 0x59, 0xe0, 0xda, 0x3b, 0x6a, 0x1a, - 0xeb, 0x99, 0x1f, 0x3d, 0x2c, 0x42, 0xb6, 0xf3, 0xb1, 0xf7, 0x55, 0xf3, 0xf4, 0xac, 0xa1, 0x37, 0xa9, 0xa1, 0x8f, - 0xbd, 0x28, 0xdb, 0xa7, 0xa6, 0x11, 0xbd, 0x86, 0x0d, 0x7d, 0xec, 0x2d, 0x39, 0x39, 0x24, 0x18, 0x9c, 0x1a, 0xf3, - 0xc7, 0x87, 0xd3, 0x19, 0xfe, 0x8e, 0x01, 0x95, 0x98, 0xcc, 0xa7, 0xc7, 0xb4, 0xa3, 0x00, 0x33, 0xaa, 0xf4, 0xf6, - 0xe9, 0x81, 0xed, 0x78, 0x59, 0x0f, 0x2d, 0xbd, 0x7b, 0x72, 0x74, 0x3b, 0x5e, 0x55, 0xe3, 0x4b, 0x39, 0xe4, 0x79, - 0x3e, 0x1b, 0x8d, 0x46, 0xc2, 0xa0, 0x73, 0x57, 0x7a, 0x03, 0x2b, 0x90, 0xc1, 0x45, 0xf5, 0xa1, 0x5c, 0x7a, 0x3b, - 0x75, 0x68, 0x57, 0xfe, 0x24, 0x3f, 0x1c, 0x8a, 0x91, 0x39, 0xc6, 0x01, 0xe7, 0xa4, 0x50, 0x72, 0x94, 0xac, 0x25, - 0x88, 0x4e, 0x69, 0x3c, 0x95, 0xf5, 0xda, 0x8a, 0xc8, 0xab, 0x11, 0xf2, 0x21, 0xf8, 0xc9, 0x03, 0xb5, 0xf8, 0xb5, - 0x16, 0xc4, 0x1e, 0xfb, 0x54, 0x29, 0x1d, 0xe2, 0x55, 0x01, 0x21, 0xc2, 0x80, 0x37, 0xd0, 0x0e, 0x4a, 0x70, 0xd8, - 0xe1, 0x3e, 0x22, 0x42, 0xf4, 0x6b, 0x2f, 0x9f, 0xc9, 0x70, 0xe5, 0xde, 0xa0, 0x9a, 0x33, 0x40, 0xac, 0xf4, 0x19, - 0xb8, 0x60, 0x02, 0xea, 0x29, 0x3e, 0x45, 0xff, 0x7a, 0xf3, 0xb0, 0xe9, 0xfa, 0xb4, 0x04, 0x54, 0x44, 0xcf, 0x7e, - 0x3e, 0x06, 0xf0, 0xce, 0xae, 0xcd, 0x48, 0x7b, 0xf9, 0x1b, 0x60, 0x58, 0x29, 0x49, 0xb4, 0x73, 0x4a, 0x04, 0xee, - 0x7c, 0x64, 0x4b, 0x3f, 0x4a, 0x81, 0x98, 0x3b, 0x9e, 0x24, 0xb2, 0x07, 0x1b, 0x39, 0x81, 0x5b, 0x0c, 0x78, 0x74, - 0x00, 0x2a, 0x57, 0x0a, 0x72, 0xaf, 0x39, 0x92, 0x3b, 0x7e, 0xe8, 0xfd, 0x30, 0xa8, 0x07, 0x3f, 0xf4, 0xce, 0x52, - 0x92, 0x3b, 0xc2, 0x33, 0x35, 0x25, 0x44, 0x7c, 0xf6, 0xc3, 0x20, 0x1f, 0xe0, 0x59, 0xa2, 0x45, 0x5a, 0xe4, 0x56, - 0x13, 0x35, 0x6e, 0xc2, 0x8b, 0x44, 0xd2, 0x10, 0xed, 0x3a, 0x8f, 0x88, 0x05, 0x80, 0x64, 0xf1, 0xd9, 0xbc, 0xa1, - 0xa8, 0x77, 0x13, 0xbe, 0x45, 0x77, 0x59, 0xec, 0xf7, 0xb7, 0x79, 0x5a, 0xf7, 0x74, 0xa8, 0x0c, 0xbe, 0x20, 0xd5, - 0x04, 0x78, 0xb4, 0xbf, 0x36, 0xc7, 0xab, 0x57, 0x9b, 0x23, 0x65, 0xa1, 0x4a, 0xd4, 0x6f, 0xb1, 0x9a, 0xf5, 0x10, - 0x91, 0x3b, 0xcb, 0x8c, 0xbd, 0xbd, 0xe0, 0x95, 0x9c, 0x55, 0xb1, 0x5d, 0x8e, 0xaf, 0x08, 0x6b, 0x2b, 0x09, 0xd0, - 0xd1, 0x7a, 0xac, 0x4d, 0x31, 0xf2, 0x2b, 0x85, 0x04, 0x5c, 0x74, 0x6c, 0x2d, 0x14, 0x1b, 0x2f, 0x40, 0x5f, 0xb2, - 0x33, 0x0d, 0xb0, 0xde, 0xe8, 0x55, 0xc4, 0x6d, 0xf9, 0x48, 0x85, 0x37, 0xb9, 0xa9, 0x32, 0x2b, 0x9b, 0x45, 0xbb, - 0x9f, 0x2a, 0x5e, 0x21, 0x6e, 0xbd, 0x51, 0x7b, 0x14, 0xa0, 0xf6, 0xd0, 0x42, 0x19, 0xa0, 0x4b, 0xd3, 0x0c, 0x00, - 0x19, 0x00, 0x64, 0xaa, 0x88, 0xcf, 0x04, 0xa8, 0xb4, 0xd5, 0x8d, 0x02, 0x27, 0xd2, 0x6b, 0x60, 0x5c, 0x60, 0xa5, - 0x8f, 0x6c, 0x64, 0xb0, 0xd8, 0x22, 0xc0, 0x2d, 0x47, 0xfa, 0x30, 0x0d, 0x27, 0xdb, 0x68, 0x0e, 0x93, 0x34, 0xbf, - 0x0f, 0xb3, 0x54, 0x42, 0x4b, 0xfc, 0x28, 0x6b, 0x8c, 0x58, 0x40, 0xfa, 0x3e, 0xbd, 0x28, 0xb2, 0x98, 0x20, 0xe1, - 0xac, 0xa7, 0x0e, 0xa0, 0x9a, 0x9c, 0x6b, 0x4d, 0xab, 0x67, 0xb5, 0xc9, 0x43, 0x16, 0xe8, 0xec, 0xc1, 0x98, 0xd4, - 0x72, 0x43, 0x8f, 0xec, 0xaf, 0x1c, 0xcf, 0x08, 0xdf, 0xf5, 0x0c, 0xa7, 0xfe, 0xbb, 0xa9, 0x81, 0x94, 0x29, 0x01, - 0x04, 0x19, 0x1c, 0x4d, 0x08, 0xe5, 0xe9, 0x98, 0x4c, 0x6d, 0x7e, 0x04, 0xc2, 0x11, 0xc1, 0x2b, 0x78, 0x6e, 0x68, - 0xdd, 0x72, 0x63, 0x67, 0x91, 0xa7, 0x09, 0x20, 0x8b, 0x17, 0x7c, 0x0b, 0xc8, 0x9c, 0x7a, 0x55, 0xc8, 0x9e, 0x3d, - 0x17, 0xd3, 0xd9, 0x3c, 0x78, 0x48, 0x68, 0xff, 0x62, 0xc2, 0x6f, 0xba, 0xab, 0xe4, 0xca, 0xd4, 0xba, 0x37, 0xd1, - 0x63, 0x2e, 0x77, 0xfa, 0xb4, 0xe2, 0x18, 0xf1, 0x0c, 0x56, 0x01, 0x39, 0x67, 0x43, 0x7e, 0x7d, 0x0e, 0xd8, 0x2d, - 0x2b, 0xe1, 0x45, 0xfc, 0x3a, 0x94, 0xd5, 0x02, 0xe4, 0x47, 0xce, 0x23, 0xf3, 0xcb, 0x57, 0xdb, 0xa1, 0x9c, 0x53, - 0x14, 0xd1, 0x72, 0x6a, 0x5a, 0x52, 0xc8, 0x0e, 0x3d, 0x05, 0x93, 0xa9, 0x2d, 0x7f, 0x6f, 0x13, 0x97, 0xe4, 0x9b, - 0x49, 0x64, 0x5f, 0x07, 0x58, 0xb3, 0x56, 0xdd, 0x43, 0x37, 0x04, 0x03, 0x44, 0x46, 0x28, 0xb3, 0xb9, 0xbe, 0x5b, - 0x0f, 0x06, 0x0a, 0xe6, 0x57, 0xd0, 0x4d, 0x8b, 0x4e, 0x71, 0x80, 0x9c, 0xb5, 0xae, 0x51, 0xa9, 0x2a, 0x0e, 0x1d, - 0xe6, 0xdd, 0xb2, 0x2a, 0xbb, 0x2c, 0xbd, 0x10, 0xa4, 0x46, 0x5d, 0x05, 0x8b, 0x94, 0x8a, 0x28, 0xde, 0x93, 0x5f, - 0x03, 0x13, 0xcf, 0xac, 0x1c, 0xa5, 0xf1, 0x1c, 0x10, 0x83, 0x14, 0x10, 0xa7, 0xfc, 0x0a, 0xd0, 0x44, 0x17, 0x51, - 0x98, 0xbd, 0x8d, 0xab, 0xa0, 0xb6, 0x9a, 0x7e, 0xef, 0x40, 0xc6, 0x9e, 0xd7, 0xfd, 0x7e, 0x4a, 0x8c, 0x7e, 0x18, - 0x85, 0x81, 0x7f, 0x8f, 0xa7, 0xfb, 0x26, 0x48, 0xcd, 0x2b, 0x0f, 0xf0, 0x8a, 0x2e, 0xb7, 0x36, 0xe5, 0x8a, 0xc6, - 0x85, 0xbf, 0x46, 0x70, 0xf8, 0xd4, 0x51, 0x6c, 0xb7, 0xa9, 0x72, 0x6a, 0x63, 0x30, 0x08, 0xe1, 0xbe, 0x95, 0xf1, - 0xfb, 0xc4, 0xcb, 0x67, 0xd1, 0x1c, 0x14, 0xa5, 0x99, 0xe6, 0x0b, 0x29, 0xa4, 0x9b, 0x00, 0x7d, 0x34, 0x08, 0xb5, - 0xba, 0xf2, 0x8f, 0xc4, 0x4b, 0xd5, 0xb4, 0x36, 0x4f, 0xb1, 0x46, 0x81, 0x98, 0x45, 0xf3, 0x86, 0x65, 0x74, 0x48, - 0xaa, 0xcb, 0xa5, 0x69, 0xc6, 0x1f, 0x56, 0x33, 0x54, 0x2b, 0x8e, 0x9a, 0xa0, 0x46, 0xe9, 0x06, 0x2e, 0x80, 0x7f, - 0xa3, 0x3b, 0x8e, 0x6a, 0x14, 0x29, 0x1a, 0xf0, 0x09, 0x62, 0xc4, 0x9a, 0xcd, 0x13, 0xd6, 0x9a, 0xba, 0x66, 0xf4, - 0xfb, 0x32, 0x64, 0xc8, 0x24, 0x21, 0x4f, 0x1f, 0x2e, 0xd7, 0x4f, 0xa4, 0xba, 0x00, 0x7e, 0xe5, 0x8a, 0xcd, 0x7a, - 0xbd, 0x39, 0xc0, 0xf5, 0xc2, 0xfa, 0x85, 0x8d, 0x2b, 0x38, 0xbf, 0x24, 0xf8, 0x5d, 0xf5, 0x23, 0xcc, 0x32, 0xa8, - 0x02, 0x32, 0xfe, 0x58, 0x48, 0xe7, 0xb9, 0x8b, 0x49, 0xfd, 0x66, 0xa4, 0x2e, 0x28, 0xb3, 0x74, 0x6e, 0x71, 0x82, - 0x80, 0xf3, 0xb0, 0x7a, 0x02, 0xc9, 0xbe, 0x7c, 0xec, 0xd3, 0x8c, 0x02, 0xd5, 0x11, 0xe0, 0xb3, 0x59, 0x3f, 0x84, - 0xfd, 0x03, 0x22, 0x0b, 0xf5, 0x37, 0x6f, 0xe4, 0xac, 0x21, 0x79, 0x20, 0xd5, 0xdc, 0xc7, 0x70, 0x6a, 0x2c, 0xf0, - 0xa5, 0x45, 0x6f, 0x2a, 0x78, 0x4d, 0xc8, 0xdc, 0x0b, 0xb4, 0xf6, 0x2d, 0xe0, 0x08, 0x11, 0x5c, 0x46, 0x29, 0x4e, - 0x7b, 0xbb, 0x5e, 0x80, 0xdc, 0xe6, 0x16, 0xe4, 0xf5, 0x3b, 0x17, 0xbf, 0x38, 0x45, 0x7a, 0x16, 0x5d, 0x60, 0xa0, - 0x0b, 0x32, 0x6f, 0xfc, 0xb3, 0x82, 0x95, 0x0b, 0xe8, 0xbd, 0x54, 0xac, 0xe4, 0x64, 0xdb, 0xa9, 0x3f, 0x4a, 0x65, - 0xbf, 0x3d, 0xb3, 0x26, 0xf0, 0xab, 0xc4, 0x7e, 0x89, 0x4c, 0xbe, 0xe9, 0xb1, 0xc9, 0x57, 0x86, 0x45, 0xa7, 0x96, - 0xc1, 0x39, 0x3d, 0x32, 0x38, 0xf7, 0x76, 0x56, 0x6d, 0x42, 0x18, 0x0a, 0x92, 0x40, 0xd3, 0xa5, 0x87, 0x75, 0xd3, - 0x9f, 0x9f, 0xb4, 0xa8, 0xb6, 0x6a, 0xdf, 0xba, 0x1f, 0x87, 0xd8, 0xc5, 0xaf, 0x12, 0xcf, 0x10, 0x91, 0xfa, 0x40, - 0x07, 0x26, 0x83, 0x27, 0x2e, 0xfb, 0x7d, 0x28, 0x6c, 0x36, 0x9e, 0x8f, 0xea, 0xe2, 0xe7, 0xe2, 0x01, 0x50, 0x1d, - 0x2a, 0xb0, 0xcb, 0xa1, 0x0c, 0x65, 0xc4, 0xa6, 0xb6, 0xdc, 0xf3, 0xfb, 0xab, 0x30, 0x07, 0x79, 0x47, 0xc3, 0xe3, - 0x9c, 0x81, 0x18, 0x06, 0x5f, 0xff, 0xe1, 0xc9, 0x3e, 0x6d, 0x7e, 0x38, 0x83, 0xef, 0x8e, 0xce, 0x3e, 0x22, 0xdd, - 0xcd, 0xd9, 0xba, 0x2c, 0xee, 0xd3, 0x58, 0x9c, 0xfd, 0x00, 0xa9, 0x3f, 0x9c, 0x15, 0xe5, 0xd9, 0x0f, 0xaa, 0x32, - 0x3f, 0x9c, 0xd1, 0x82, 0x1b, 0xfd, 0x6e, 0x4d, 0xbc, 0x7f, 0x54, 0x9a, 0x01, 0x6d, 0x09, 0x91, 0x59, 0x5a, 0xfd, - 0x08, 0x4a, 0x44, 0xc5, 0x8f, 0x2a, 0xa3, 0x5a, 0xad, 0x1d, 0xe7, 0x43, 0xa2, 0x91, 0xb2, 0x69, 0x42, 0xe2, 0x6a, - 0x09, 0xeb, 0x50, 0xcf, 0x4e, 0x9b, 0x6f, 0xc7, 0x79, 0xa0, 0x0e, 0x88, 0x9c, 0x5f, 0xe7, 0xa3, 0x2d, 0x7d, 0x0d, - 0xbe, 0x75, 0x38, 0xe4, 0xa3, 0x9d, 0xf9, 0xe9, 0x93, 0xb5, 0x52, 0xc6, 0x1d, 0x29, 0x72, 0x21, 0xe4, 0x8c, 0xdb, - 0xf6, 0x18, 0x70, 0x00, 0xf8, 0x87, 0x03, 0xfd, 0xde, 0xc9, 0xdf, 0x6a, 0xb7, 0xb4, 0xea, 0xf9, 0xb1, 0xc5, 0x9d, - 0xf1, 0xba, 0x36, 0x44, 0x6d, 0x7b, 0x89, 0x2d, 0xbd, 0x6f, 0x1a, 0xd4, 0x14, 0xd1, 0x4f, 0x58, 0x4d, 0xac, 0xe2, - 0xb0, 0x20, 0x25, 0x24, 0x31, 0x1c, 0xa3, 0x1d, 0x7a, 0x9c, 0x2e, 0x96, 0x9e, 0xdc, 0x77, 0x78, 0xb9, 0xf5, 0x7d, - 0x40, 0xd2, 0x2a, 0x9c, 0x7f, 0xf4, 0x42, 0x03, 0x8f, 0x5e, 0xe4, 0x55, 0x91, 0x89, 0x91, 0xa0, 0x51, 0x7e, 0x4b, - 0xe2, 0xcc, 0x19, 0xd6, 0xe2, 0x4c, 0x81, 0x85, 0x85, 0x04, 0xd0, 0x5d, 0x94, 0x94, 0x1e, 0x9c, 0x3d, 0xd9, 0x97, - 0xcd, 0xef, 0x04, 0x0f, 0x31, 0x5a, 0x00, 0x23, 0xce, 0xae, 0x5d, 0xde, 0x43, 0x58, 0xe6, 0xde, 0xef, 0x6f, 0xef, - 0xf2, 0x02, 0x42, 0x34, 0xcf, 0xa4, 0x62, 0xb5, 0x3c, 0x03, 0xc6, 0x3c, 0x11, 0x9f, 0x85, 0x95, 0x9c, 0x06, 0x55, - 0x47, 0xb1, 0x7a, 0x1b, 0xcf, 0x3d, 0xa0, 0xf8, 0xfe, 0x90, 0x00, 0x97, 0xbb, 0xcf, 0xde, 0x28, 0xd7, 0x54, 0xd2, - 0x23, 0xcf, 0x31, 0x5a, 0x32, 0x01, 0x8a, 0x67, 0x88, 0x93, 0x14, 0x56, 0xcf, 0x4d, 0x90, 0x8a, 0x7c, 0x7d, 0x42, - 0xf1, 0x45, 0xf3, 0x28, 0x6a, 0x58, 0xc8, 0x12, 0x38, 0x1e, 0x92, 0x59, 0x36, 0x47, 0x96, 0xf2, 0xb4, 0x3d, 0x45, - 0x3a, 0x3a, 0xb1, 0xc4, 0x6f, 0x6b, 0x7e, 0xbd, 0x48, 0x45, 0x60, 0xd2, 0xce, 0x56, 0xe6, 0x5e, 0x08, 0x43, 0x95, - 0x70, 0xef, 0x75, 0x3d, 0x0b, 0xe5, 0xa6, 0x68, 0x55, 0xcc, 0x1e, 0xa6, 0xc4, 0x0c, 0x53, 0xac, 0xbf, 0xb0, 0xe1, - 0x37, 0x89, 0x17, 0x83, 0xe1, 0x7a, 0xc9, 0xcb, 0xd9, 0xc6, 0x2c, 0x84, 0xc3, 0x61, 0x33, 0x29, 0x66, 0x4b, 0x08, - 0x73, 0x5d, 0xce, 0x0f, 0x87, 0xae, 0x96, 0xad, 0x85, 0x07, 0x0f, 0x55, 0x0b, 0x37, 0x0d, 0xcb, 0xe1, 0x67, 0x32, - 0x8b, 0xb1, 0x7d, 0x8d, 0xcf, 0xec, 0xcf, 0x17, 0xdd, 0xb3, 0x04, 0xc9, 0x37, 0xd6, 0x40, 0x3b, 0x36, 0x6b, 0x77, - 0xb8, 0x1a, 0x01, 0x49, 0xe9, 0x6e, 0xf4, 0x77, 0x65, 0x27, 0x4f, 0x09, 0x72, 0x47, 0x2b, 0xb0, 0xdf, 0x7d, 0xe3, - 0x4f, 0xb4, 0xd8, 0x83, 0x76, 0x1b, 0x5b, 0x42, 0x54, 0xd3, 0x9e, 0xcb, 0x95, 0x62, 0x69, 0xde, 0x4a, 0x1b, 0x3d, - 0x1f, 0xd6, 0xe7, 0xbe, 0x91, 0x03, 0x05, 0x63, 0xc4, 0x53, 0xeb, 0x20, 0x9a, 0xcd, 0x81, 0x06, 0x03, 0xcd, 0x23, - 0x3c, 0xb5, 0xd0, 0x41, 0x99, 0xb5, 0x61, 0x3f, 0x49, 0x4e, 0x96, 0xc7, 0xe1, 0x5b, 0xf8, 0x97, 0xcf, 0xb0, 0x49, - 0x4c, 0xb1, 0x3d, 0xfe, 0x56, 0x29, 0x2a, 0x3c, 0xb6, 0x20, 0xae, 0xb5, 0x1b, 0x51, 0x1b, 0x2a, 0x87, 0x7f, 0x09, - 0xfb, 0x08, 0xfb, 0x0d, 0x4d, 0x10, 0x06, 0xbb, 0xfe, 0x4c, 0x20, 0x44, 0x2c, 0xc4, 0x0b, 0xfe, 0x56, 0x49, 0x2a, - 0x3a, 0xe1, 0xb3, 0x45, 0x09, 0xbc, 0x75, 0x18, 0xd0, 0x27, 0x14, 0x29, 0x85, 0x30, 0x34, 0x13, 0x7a, 0x47, 0xff, - 0x8d, 0xd8, 0xc9, 0x26, 0xb9, 0x15, 0xf2, 0x81, 0xa4, 0x92, 0x60, 0x82, 0x95, 0x17, 0xca, 0x17, 0xee, 0x85, 0x52, - 0x6b, 0x2d, 0x68, 0xfd, 0xf2, 0x27, 0x89, 0x67, 0xf0, 0xf7, 0x40, 0xc6, 0xa0, 0xdb, 0x88, 0x6a, 0x92, 0x63, 0xfa, - 0x28, 0x9d, 0x67, 0xa0, 0x02, 0x3a, 0x5b, 0x67, 0x61, 0xbd, 0x2c, 0xca, 0x55, 0x2b, 0x52, 0x54, 0x96, 0x3e, 0x52, - 0x8f, 0x31, 0x2f, 0xcc, 0x93, 0x13, 0xf9, 0xe0, 0x11, 0x00, 0xe3, 0x51, 0x9e, 0x56, 0x1d, 0xa5, 0xf5, 0x03, 0xcb, - 0x80, 0x11, 0x38, 0x51, 0x06, 0x3c, 0xc2, 0x32, 0x30, 0x4f, 0xbb, 0x0c, 0x35, 0x88, 0x35, 0xaa, 0xae, 0xd4, 0x06, - 0x73, 0xa2, 0x28, 0xf9, 0x14, 0x4b, 0x2b, 0x8c, 0xa1, 0xa9, 0x2b, 0x8f, 0xac, 0x97, 0x9c, 0xb0, 0x27, 0xbb, 0x81, - 0x74, 0x0b, 0x1b, 0x85, 0x33, 0xe8, 0x5a, 0x96, 0x28, 0x17, 0xdd, 0x32, 0xa2, 0x4c, 0x84, 0xd4, 0xcf, 0x1e, 0xce, - 0xb4, 0xda, 0x6f, 0xec, 0xa4, 0x7d, 0x7b, 0xa4, 0xe8, 0x05, 0x83, 0xf6, 0x69, 0x8f, 0x94, 0x7a, 0xd6, 0xc8, 0x65, - 0x60, 0x4b, 0x97, 0xaa, 0x9e, 0xff, 0x02, 0xe5, 0x3b, 0x98, 0x19, 0x67, 0xb3, 0xdf, 0xf5, 0xe6, 0xf6, 0x64, 0x5f, - 0x37, 0xbf, 0xb3, 0x5e, 0x0f, 0xb6, 0x06, 0x99, 0xf8, 0x42, 0xb1, 0x50, 0x59, 0x85, 0x58, 0x41, 0xda, 0xff, 0x12, - 0xde, 0xef, 0xf0, 0xd6, 0x08, 0xcd, 0xca, 0x78, 0x98, 0x8f, 0x9e, 0xec, 0x45, 0xf3, 0x7b, 0x67, 0xd9, 0x56, 0xae, - 0x4a, 0x66, 0xfb, 0xfd, 0x28, 0x69, 0xce, 0x1e, 0xaf, 0x91, 0xd4, 0x01, 0x3e, 0x5e, 0x9f, 0xe1, 0x23, 0x95, 0x50, - 0x6a, 0x41, 0x55, 0x83, 0xd6, 0xc7, 0x7e, 0x6f, 0x3d, 0xa7, 0x8f, 0x1f, 0xcb, 0xe9, 0x96, 0x14, 0x61, 0xfc, 0xc0, - 0x60, 0xca, 0x4e, 0x9c, 0xba, 0xe4, 0xcd, 0x90, 0xde, 0x75, 0xab, 0xa4, 0x2e, 0x7b, 0x94, 0x08, 0x42, 0x1d, 0xac, - 0x5f, 0xec, 0x87, 0x30, 0xb3, 0x45, 0x7f, 0xd8, 0xac, 0xe6, 0x04, 0x88, 0x08, 0x68, 0xad, 0xf2, 0x3e, 0x70, 0xcc, - 0x17, 0x66, 0xcd, 0x0d, 0xe9, 0xd6, 0x9b, 0x2b, 0xed, 0x95, 0x14, 0xd0, 0xcf, 0x41, 0xe6, 0xf6, 0xd1, 0x2d, 0x57, - 0x2d, 0xf3, 0x5c, 0xda, 0x72, 0xc0, 0xa2, 0x85, 0x40, 0xcd, 0xce, 0xa5, 0xc3, 0x81, 0x82, 0x50, 0x57, 0xa2, 0x8a, - 0xb8, 0x3a, 0x8a, 0x16, 0xa2, 0x56, 0xab, 0x76, 0x39, 0xd9, 0x54, 0xc8, 0x96, 0x44, 0x90, 0x51, 0xb2, 0x57, 0x42, - 0x7d, 0x94, 0xab, 0x3d, 0xd3, 0x70, 0x80, 0x26, 0x60, 0xd3, 0x06, 0x7f, 0x0b, 0xdc, 0xcb, 0xe0, 0xcc, 0xb4, 0x4f, - 0xc3, 0x08, 0x38, 0xcd, 0x21, 0xe6, 0xcf, 0xef, 0x7a, 0x50, 0xc1, 0x83, 0x8e, 0xf4, 0xd7, 0xf5, 0xac, 0xc0, 0x33, - 0xf7, 0xc4, 0xf3, 0x37, 0x27, 0xd2, 0x8b, 0x1c, 0x1e, 0x68, 0x1a, 0xc4, 0x8c, 0xbf, 0x28, 0xcb, 0x70, 0x37, 0x5a, - 0x96, 0xc5, 0xca, 0x8b, 0xf4, 0x3e, 0x9e, 0x49, 0x31, 0x90, 0x98, 0x31, 0x33, 0xba, 0x8a, 0x75, 0x9c, 0xc3, 0xb8, - 0xb7, 0x27, 0x61, 0x85, 0xf6, 0xcf, 0x12, 0x7b, 0x5d, 0x00, 0x96, 0x43, 0xd6, 0xa0, 0x15, 0xde, 0xe9, 0xf6, 0x76, - 0x8f, 0x4b, 0x76, 0x14, 0x37, 0x80, 0x7e, 0x56, 0x43, 0xcb, 0x04, 0xb5, 0xcc, 0xba, 0x93, 0xc9, 0x14, 0xc9, 0xe5, - 0xdb, 0xb0, 0x37, 0xac, 0xc8, 0xe7, 0x8d, 0xdc, 0x1e, 0xde, 0x87, 0x2b, 0x11, 0x6b, 0x0b, 0x3a, 0xe9, 0xc8, 0x38, - 0xdc, 0x0b, 0xcd, 0x8d, 0x74, 0xff, 0xa4, 0x4a, 0xc2, 0x52, 0xc4, 0x70, 0x0b, 0x64, 0x7b, 0xb5, 0xad, 0x04, 0x25, - 0xf0, 0xc1, 0x7e, 0x2c, 0xc5, 0x32, 0xdd, 0x0a, 0xc0, 0x75, 0xe0, 0x7f, 0x4a, 0x44, 0x42, 0x77, 0xe7, 0x21, 0x8a, - 0x35, 0xf2, 0xbe, 0x41, 0x34, 0xf6, 0xd7, 0x20, 0xa7, 0x01, 0x99, 0x48, 0x31, 0x92, 0x05, 0x03, 0x1f, 0x40, 0xce, - 0xd7, 0x60, 0x92, 0x9b, 0xe6, 0x9e, 0x1f, 0xe4, 0xba, 0x83, 0x69, 0x1f, 0x74, 0x2f, 0xae, 0x35, 0xcb, 0xc1, 0x2b, - 0x26, 0xe2, 0x7f, 0xab, 0xbd, 0x92, 0xe5, 0x2c, 0xf3, 0x1b, 0x73, 0xd1, 0xc9, 0xe0, 0xaa, 0x21, 0xfc, 0x62, 0x96, - 0xcd, 0x79, 0x34, 0xcb, 0x74, 0xd4, 0x7f, 0xd1, 0x1c, 0x95, 0x02, 0x70, 0xea, 0x78, 0x01, 0xd6, 0xd0, 0x57, 0xba, - 0x69, 0xc5, 0x23, 0x8d, 0x31, 0x0a, 0x2a, 0x74, 0x10, 0xfa, 0x5b, 0x0d, 0x48, 0x1b, 0x4c, 0xd2, 0x24, 0x54, 0x3e, - 0xb8, 0xa0, 0x1b, 0xe6, 0xe5, 0xca, 0xe5, 0xaa, 0x49, 0xd5, 0xf2, 0xcb, 0x11, 0xf5, 0x5d, 0x2d, 0xb9, 0x54, 0x9b, - 0x4f, 0x8d, 0xb2, 0x46, 0x90, 0xc9, 0x51, 0xfa, 0x7d, 0xca, 0x85, 0x5b, 0x19, 0x93, 0xf5, 0xe1, 0xe0, 0x15, 0xdc, - 0xd4, 0xf8, 0x75, 0x4e, 0x84, 0xa2, 0xf6, 0x90, 0x08, 0x5b, 0xbb, 0x15, 0xba, 0xf7, 0xb8, 0x51, 0x9a, 0x47, 0xd9, - 0x26, 0x16, 0x95, 0xd7, 0x4b, 0xc0, 0x5a, 0xdc, 0x03, 0x5e, 0x54, 0x5a, 0xfa, 0x15, 0x2b, 0x00, 0x3d, 0x40, 0x0a, - 0x1b, 0x3f, 0x22, 0x03, 0xd6, 0x47, 0x2f, 0xf5, 0xfb, 0x7d, 0x63, 0xca, 0xff, 0xf0, 0x90, 0x03, 0x49, 0xa1, 0x28, - 0xeb, 0x1d, 0x4c, 0x20, 0xb8, 0x76, 0x92, 0xf6, 0xac, 0xe6, 0xd7, 0xeb, 0xda, 0x03, 0x7e, 0x2b, 0xdf, 0x22, 0xb1, - 0x7a, 0x6d, 0x5f, 0x6c, 0xf6, 0x69, 0x75, 0x63, 0x34, 0x0e, 0x82, 0xa5, 0xd5, 0x5b, 0xad, 0x72, 0xc8, 0x1b, 0x5e, - 0x81, 0x48, 0x65, 0x5d, 0x5d, 0x2b, 0xe7, 0xea, 0x5a, 0x70, 0xe4, 0x92, 0x2d, 0x79, 0x0e, 0xff, 0x85, 0xdc, 0x2b, - 0x0f, 0x87, 0xc2, 0xef, 0xf7, 0xd3, 0x19, 0x69, 0x65, 0x81, 0x3d, 0x6d, 0x5d, 0x7b, 0xa1, 0x7f, 0x38, 0xfc, 0x08, - 0x5e, 0x23, 0xfe, 0xe1, 0x50, 0xf6, 0xfb, 0x9f, 0xcc, 0x4d, 0xe6, 0x7c, 0xac, 0x94, 0xb2, 0x97, 0xa8, 0x74, 0x7f, - 0x9b, 0xf0, 0xde, 0xff, 0x1e, 0xfd, 0xef, 0xd1, 0x65, 0x4f, 0x85, 0x80, 0x25, 0x7c, 0x86, 0x37, 0x74, 0xa6, 0x2e, - 0xe7, 0x4c, 0xba, 0xbb, 0x2b, 0x3f, 0xf4, 0x9e, 0x86, 0x8a, 0xef, 0xcd, 0x4d, 0x1b, 0x7f, 0xad, 0x8e, 0x34, 0x09, - 0x1d, 0x17, 0xfd, 0xc3, 0xe1, 0x73, 0xa2, 0xf5, 0x69, 0xa9, 0xd2, 0xa7, 0x29, 0x1c, 0x25, 0x43, 0x8c, 0xeb, 0x16, - 0xa6, 0x03, 0xfb, 0x71, 0xf3, 0x55, 0xf2, 0xe2, 0x2c, 0x85, 0x6b, 0x6f, 0x3e, 0x4b, 0xe7, 0x53, 0xb0, 0xae, 0x0c, - 0xf3, 0x59, 0x3d, 0x0f, 0x20, 0x75, 0x08, 0x69, 0xd6, 0x34, 0xfc, 0x4b, 0xe5, 0x0a, 0xde, 0xda, 0xe3, 0xdd, 0xc0, - 0x45, 0xa9, 0x23, 0x7d, 0xd2, 0x46, 0xd3, 0x25, 0x95, 0xfc, 0x27, 0x91, 0xc7, 0x18, 0xb3, 0xf1, 0x9a, 0x78, 0x3f, - 0x8b, 0xfc, 0x55, 0x01, 0xd8, 0x45, 0x00, 0x86, 0x9c, 0xce, 0x1d, 0x49, 0xfc, 0xe7, 0xe4, 0xfb, 0x3f, 0xa6, 0x4b, - 0xfb, 0x58, 0x16, 0x77, 0xa5, 0xa8, 0xaa, 0xa3, 0xd2, 0x76, 0xb6, 0x5c, 0x0f, 0x4c, 0xa2, 0xfd, 0xbe, 0x64, 0x12, - 0x4d, 0x31, 0x14, 0x05, 0x6e, 0x8d, 0xbd, 0x69, 0xca, 0x15, 0x63, 0xf5, 0xc8, 0x58, 0x3f, 0x5f, 0xee, 0xde, 0xc6, - 0x5e, 0xea, 0x07, 0x29, 0x08, 0xc2, 0x1a, 0x4a, 0x29, 0x45, 0x3e, 0x38, 0x9f, 0x61, 0x2a, 0x51, 0xeb, 0x52, 0xaa, - 0xfc, 0x61, 0xa4, 0xf9, 0x30, 0x05, 0xbd, 0xec, 0xdf, 0x2b, 0x98, 0xff, 0xba, 0x3d, 0x58, 0x9f, 0xd6, 0x65, 0x1a, - 0x55, 0x44, 0x95, 0x17, 0xa6, 0xda, 0x04, 0x22, 0xf8, 0xb5, 0xb0, 0xf8, 0x7e, 0x7d, 0x72, 0x24, 0x68, 0xcc, 0x64, - 0xf9, 0x74, 0xe4, 0x7e, 0x61, 0x5f, 0xb9, 0x8e, 0xe7, 0x7f, 0x6e, 0xe6, 0xff, 0x00, 0x9d, 0x21, 0x8b, 0x6b, 0x6e, - 0x19, 0x2c, 0x70, 0xf6, 0x4b, 0x57, 0x0f, 0xf8, 0x9b, 0x79, 0xe2, 0x1a, 0xe8, 0x98, 0xaf, 0xd1, 0x55, 0x31, 0x9d, - 0x15, 0x03, 0xe0, 0xb2, 0xf5, 0x1b, 0x6b, 0x4e, 0xbc, 0xb1, 0x28, 0xaf, 0xe4, 0x82, 0xd0, 0xd7, 0x55, 0x98, 0x8d, - 0xab, 0x62, 0x53, 0x89, 0x62, 0x53, 0xf7, 0x48, 0x2d, 0x9b, 0x4f, 0x6b, 0x5b, 0x21, 0xfb, 0x57, 0xd1, 0x62, 0xf0, - 0x32, 0xac, 0x93, 0x51, 0x96, 0xae, 0xa7, 0xc0, 0xaf, 0x17, 0xc0, 0x59, 0x64, 0x5e, 0x79, 0xef, 0xec, 0x01, 0x5b, - 0x34, 0x9e, 0x02, 0x39, 0x2a, 0xfd, 0x91, 0x37, 0x46, 0xa7, 0x27, 0xfa, 0xfd, 0x7c, 0x4a, 0x31, 0x5f, 0x7f, 0x05, - 0x78, 0xae, 0x5a, 0x2e, 0x40, 0x5f, 0x86, 0x3a, 0xa8, 0x44, 0xa9, 0x15, 0xc3, 0x88, 0x85, 0xbf, 0x0a, 0x24, 0x72, - 0xa6, 0xc0, 0x66, 0x15, 0x25, 0xa1, 0x12, 0x95, 0x92, 0xad, 0x09, 0x6a, 0xe9, 0x7d, 0x51, 0xd6, 0xfb, 0x0a, 0x1c, - 0x25, 0x23, 0x6d, 0x96, 0x93, 0x66, 0x5c, 0x81, 0x32, 0x17, 0xfd, 0x60, 0xff, 0xaa, 0x3c, 0xbf, 0x91, 0xf9, 0x2c, - 0xf7, 0x1d, 0x9d, 0xd3, 0x76, 0x5c, 0xa0, 0xcc, 0x2d, 0xa7, 0xad, 0x96, 0x3c, 0x26, 0xef, 0x59, 0xb0, 0xed, 0xbf, - 0x48, 0x90, 0x62, 0x11, 0xe6, 0x13, 0xaa, 0x6c, 0xfe, 0x0e, 0xa1, 0xb6, 0x38, 0xb0, 0xc7, 0x2e, 0x4c, 0xc4, 0x7f, - 0x0b, 0x96, 0xc4, 0x30, 0x2b, 0x45, 0x18, 0xef, 0xc0, 0xfb, 0x67, 0x53, 0x89, 0xd1, 0x19, 0x3a, 0xb9, 0x9f, 0x3d, - 0xa4, 0x75, 0x72, 0xf6, 0xf6, 0xf5, 0xd9, 0x0f, 0xbd, 0x41, 0x31, 0x4a, 0xe3, 0x41, 0xef, 0x87, 0xb3, 0xd5, 0x06, - 0xd0, 0x32, 0xc5, 0x59, 0x4c, 0xa6, 0x34, 0x11, 0x9f, 0x91, 0x61, 0xf0, 0xac, 0x4e, 0xc4, 0x19, 0x4d, 0x4c, 0xf7, - 0x35, 0x4a, 0x93, 0x6f, 0x47, 0x61, 0x0e, 0x2f, 0x97, 0x62, 0x53, 0x89, 0x18, 0xec, 0x94, 0x6a, 0x9e, 0xe5, 0xed, - 0xb3, 0x38, 0x1f, 0x75, 0xc8, 0x2a, 0x1d, 0xf8, 0xdb, 0x13, 0x69, 0x57, 0xa5, 0x2b, 0x20, 0xf4, 0x00, 0x38, 0xe9, - 0xca, 0x9f, 0x87, 0x83, 0x48, 0x20, 0xd4, 0x82, 0x39, 0x99, 0x46, 0x74, 0x43, 0x7a, 0x85, 0x7d, 0x06, 0x66, 0x21, - 0xa5, 0x79, 0x70, 0x73, 0xb5, 0x18, 0xba, 0x2b, 0x56, 0x8e, 0xc2, 0x6a, 0x2d, 0xa2, 0x1a, 0x59, 0x8f, 0xc1, 0x79, - 0x07, 0x22, 0x00, 0x14, 0x39, 0x78, 0xc6, 0xa3, 0x7e, 0x3f, 0x52, 0x41, 0x39, 0x09, 0xfd, 0xa2, 0xd0, 0x2f, 0x0d, - 0x47, 0x19, 0xf3, 0xaf, 0xa1, 0xe6, 0x08, 0xa8, 0xff, 0x0f, 0x6f, 0xdf, 0xc2, 0xdd, 0xb6, 0x8d, 0xad, 0xfb, 0x57, - 0x2c, 0xde, 0x54, 0x25, 0x22, 0x48, 0x96, 0xdc, 0xa4, 0x33, 0xa5, 0x0c, 0xeb, 0xb8, 0x79, 0xb4, 0xe9, 0x34, 0x8f, - 0xc6, 0x69, 0xa7, 0x53, 0x5d, 0x1d, 0x97, 0x26, 0x61, 0x8b, 0x0d, 0x0d, 0xa8, 0x24, 0xe5, 0x47, 0x25, 0xfe, 0xf7, - 0xbb, 0xf6, 0xc6, 0x93, 0x14, 0xe5, 0x64, 0xe6, 0x9e, 0x7b, 0x57, 0xd6, 0x8a, 0x45, 0x10, 0xc4, 0x1b, 0x1b, 0x1b, - 0xfb, 0xf1, 0xed, 0x3b, 0x16, 0x9b, 0x70, 0x01, 0xb8, 0x9d, 0x13, 0xea, 0x8c, 0xf7, 0x58, 0x13, 0x98, 0xd3, 0x84, - 0xa0, 0x30, 0xd7, 0xc1, 0xc2, 0x00, 0xd0, 0xbb, 0xf6, 0x68, 0xcb, 0x49, 0x97, 0x60, 0xf1, 0xdc, 0xc0, 0xe2, 0xd5, - 0xc5, 0xa2, 0xba, 0xe6, 0x5a, 0x6e, 0x61, 0x53, 0xca, 0x2a, 0x86, 0x00, 0x02, 0xcd, 0x98, 0x61, 0x77, 0xdc, 0xe5, - 0x48, 0xd6, 0x45, 0xc1, 0xc5, 0x4e, 0x60, 0xe8, 0x66, 0x5c, 0x32, 0x73, 0x70, 0x35, 0xc3, 0x3a, 0xa9, 0x28, 0xc0, - 0xae, 0x2e, 0x40, 0xf6, 0xc2, 0x50, 0xd7, 0xcd, 0x6c, 0xb9, 0x0e, 0x7c, 0x5d, 0xba, 0xf0, 0x25, 0x05, 0x2f, 0x57, - 0x52, 0x94, 0xd9, 0x0d, 0xff, 0xd1, 0xbe, 0x6c, 0xc6, 0x92, 0x42, 0x3b, 0xd2, 0xd7, 0xed, 0xee, 0x68, 0x31, 0x8e, - 0x2d, 0xc7, 0xb7, 0x54, 0xba, 0xd7, 0xa3, 0xea, 0x85, 0xd0, 0xd6, 0xb9, 0x96, 0x59, 0x9a, 0x72, 0xf1, 0x4a, 0xa4, - 0x59, 0xe2, 0x25, 0xc7, 0x3a, 0x56, 0xb5, 0x0b, 0x82, 0xe5, 0xc2, 0x24, 0x3f, 0xcf, 0x4a, 0x8c, 0x1d, 0xdc, 0x68, - 0x54, 0x2b, 0xea, 0x94, 0x89, 0x81, 0x21, 0xdf, 0x63, 0xf0, 0x6d, 0x56, 0x26, 0xc0, 0xf0, 0x63, 0xa2, 0xbe, 0xa4, - 0xa7, 0x10, 0xf0, 0x41, 0x85, 0xe6, 0x7e, 0xce, 0x11, 0xfc, 0xda, 0xaa, 0xcc, 0x81, 0xc9, 0xd6, 0x2a, 0x48, 0xc4, - 0xbd, 0xcb, 0xe6, 0x7a, 0x11, 0x2d, 0xd4, 0x5d, 0xa8, 0x17, 0x6f, 0xb7, 0xbd, 0x44, 0xd1, 0x01, 0x27, 0x3f, 0x0d, - 0x5e, 0xc6, 0x59, 0xce, 0xd3, 0x83, 0x4a, 0x1e, 0xa8, 0x0d, 0x75, 0xa0, 0x9c, 0x39, 0x60, 0xe7, 0x7d, 0x59, 0x1d, - 0xe8, 0x35, 0x7d, 0xa0, 0xdb, 0x79, 0x00, 0x17, 0x0c, 0xdc, 0xb9, 0x57, 0xd9, 0x0d, 0x17, 0x07, 0xa0, 0x0c, 0xb4, - 0xc6, 0x03, 0x75, 0x59, 0x8d, 0xd4, 0xc4, 0xe8, 0x18, 0xd6, 0x89, 0x3e, 0x98, 0x03, 0xfa, 0x1d, 0x84, 0xb5, 0x6f, - 0xbd, 0x5d, 0xe9, 0x83, 0x36, 0xa0, 0x3f, 0x2e, 0x4d, 0x1f, 0x74, 0xe0, 0x78, 0x15, 0x1d, 0xb8, 0x31, 0xa4, 0x1a, - 0xb4, 0xd5, 0xc8, 0x2a, 0x50, 0xbc, 0xe1, 0x2d, 0xde, 0x9d, 0x6b, 0xc9, 0xc6, 0x7b, 0x89, 0x18, 0x5f, 0x99, 0xa8, - 0xe2, 0x4c, 0x9c, 0x7a, 0xa9, 0xbc, 0xd6, 0x4e, 0x32, 0xc2, 0xf8, 0x96, 0x95, 0xd4, 0xdf, 0x21, 0xe6, 0x16, 0x69, - 0x0e, 0x83, 0x17, 0x61, 0x45, 0x66, 0xbc, 0xdf, 0x97, 0x33, 0x19, 0x95, 0x33, 0x71, 0x58, 0x46, 0x0a, 0xac, 0x6d, - 0x9f, 0x08, 0xe8, 0x41, 0x09, 0x90, 0x2f, 0x00, 0xaa, 0x1e, 0x12, 0xfe, 0x3c, 0x24, 0xf5, 0xe9, 0x14, 0xfa, 0x14, - 0xda, 0x7a, 0xc5, 0x15, 0xc4, 0xab, 0xba, 0x31, 0xb2, 0x8d, 0x0a, 0x5a, 0x3c, 0x96, 0x67, 0xb5, 0x61, 0x6c, 0x4e, - 0xad, 0x7f, 0xbd, 0xd9, 0x60, 0xca, 0xe6, 0x42, 0xad, 0xc2, 0x90, 0x44, 0xb7, 0xa5, 0x17, 0x49, 0xc4, 0xc2, 0x66, - 0xb5, 0x36, 0xbf, 0x09, 0x03, 0x92, 0x89, 0x14, 0xf7, 0xb3, 0x25, 0xce, 0x5d, 0x3c, 0x9e, 0x57, 0x7d, 0xad, 0xa5, - 0x45, 0xa6, 0xcd, 0xf7, 0xfa, 0x32, 0xa4, 0xa9, 0xa8, 0x21, 0x8d, 0x3a, 0x33, 0xe8, 0xbe, 0x5d, 0xde, 0xb2, 0x1a, - 0x61, 0x02, 0xbc, 0xd2, 0x19, 0x74, 0xa3, 0xf1, 0x40, 0x2c, 0xab, 0x51, 0xb1, 0x16, 0x02, 0x81, 0x87, 0x21, 0xc7, - 0xcc, 0x12, 0x92, 0xec, 0x2f, 0xfe, 0xad, 0x8a, 0xb3, 0x50, 0xc4, 0xb7, 0x06, 0xd9, 0xbb, 0xb2, 0xae, 0xdd, 0x75, - 0xe4, 0xe7, 0xc4, 0xc2, 0x6a, 0xff, 0xa1, 0x79, 0xd4, 0x1a, 0x67, 0x01, 0x6d, 0x4d, 0xab, 0x1b, 0x0e, 0xf7, 0xa8, - 0x8e, 0x45, 0x69, 0xb0, 0x89, 0x3d, 0xb2, 0x5c, 0xb4, 0x8e, 0x19, 0x34, 0xa0, 0xbf, 0xcb, 0xae, 0xd7, 0xd7, 0x08, - 0xe0, 0x56, 0x22, 0xeb, 0x24, 0x95, 0x7f, 0x49, 0x7b, 0xd4, 0xb5, 0x3d, 0x95, 0xff, 0x6d, 0x9b, 0x2a, 0x87, 0x16, - 0x53, 0x1e, 0xbb, 0x39, 0x0b, 0x54, 0x47, 0x82, 0x28, 0x50, 0x5b, 0x2f, 0x98, 0x7a, 0xa7, 0x4c, 0xd1, 0x01, 0x02, - 0x5d, 0x98, 0x33, 0xec, 0x33, 0x8e, 0x18, 0xb3, 0x54, 0x62, 0x30, 0xf5, 0x31, 0x46, 0x35, 0xad, 0x15, 0xa0, 0xeb, - 0xa7, 0x1b, 0xf8, 0x13, 0x15, 0x35, 0x1a, 0x6a, 0x8d, 0xa4, 0x50, 0x34, 0x51, 0xa1, 0xc8, 0xd2, 0x42, 0xc7, 0x55, - 0xe8, 0x24, 0x12, 0x96, 0x80, 0x86, 0x09, 0xd1, 0x49, 0x05, 0xde, 0x1a, 0xc0, 0x99, 0x8f, 0x8b, 0x72, 0x5d, 0x68, - 0x83, 0xb9, 0xef, 0xe3, 0x1b, 0xfe, 0xea, 0xb9, 0x33, 0xaa, 0x6f, 0x59, 0xeb, 0x7b, 0x5a, 0x90, 0xef, 0x43, 0x4e, - 0xd1, 0x81, 0x89, 0x9d, 0x6c, 0xd0, 0x18, 0xa3, 0xac, 0x75, 0xd4, 0x8b, 0xb7, 0x3a, 0x14, 0x8b, 0x36, 0xc1, 0x7b, - 0xc0, 0x53, 0x44, 0x1b, 0x1e, 0x0a, 0x63, 0x55, 0x8d, 0x4f, 0x25, 0x6b, 0xe9, 0xc1, 0x0a, 0x9e, 0xae, 0x13, 0x1e, - 0x82, 0x1e, 0x89, 0xb0, 0x93, 0xb0, 0x98, 0xc7, 0x0b, 0x38, 0x4e, 0x0a, 0x02, 0x6a, 0x07, 0x7d, 0x05, 0x9f, 0x2f, - 0xd0, 0xfd, 0x55, 0xa2, 0x07, 0x18, 0x5a, 0x10, 0x37, 0xa3, 0xa0, 0x8e, 0xae, 0xe3, 0x55, 0x43, 0x45, 0xc2, 0xe7, - 0x05, 0xd8, 0x0e, 0x29, 0xf5, 0x14, 0x68, 0xa1, 0x12, 0xa5, 0x1f, 0x06, 0xbe, 0x43, 0x63, 0x60, 0x6b, 0x1d, 0xa0, - 0xa1, 0x9f, 0x31, 0x4d, 0xad, 0x33, 0x54, 0x3e, 0xf3, 0xee, 0x99, 0xd1, 0x72, 0x66, 0xd1, 0x18, 0xf4, 0x6d, 0x34, - 0x45, 0x71, 0x4e, 0x3e, 0x0b, 0x8a, 0x38, 0xcd, 0xe2, 0x1c, 0xfc, 0x36, 0xe3, 0x02, 0x33, 0x26, 0x71, 0xc5, 0xaf, - 0x64, 0x01, 0xda, 0xee, 0x5c, 0xa5, 0xd6, 0x35, 0x08, 0xc8, 0xbe, 0x07, 0xab, 0x97, 0x86, 0x8e, 0xca, 0x79, 0x77, - 0x69, 0x53, 0x88, 0x58, 0x84, 0x60, 0xd3, 0x4c, 0x97, 0xec, 0x34, 0x54, 0xda, 0x1c, 0x08, 0x75, 0x84, 0xc6, 0xfd, - 0xd3, 0x30, 0xb6, 0x9a, 0x62, 0x6b, 0xf7, 0xb6, 0xdd, 0xfe, 0x5a, 0x7a, 0xe9, 0x34, 0x27, 0x3d, 0xc6, 0x7e, 0x2d, - 0xc3, 0x62, 0x64, 0x3b, 0x42, 0x60, 0xc9, 0x79, 0x9f, 0xfa, 0xaf, 0x68, 0x39, 0x4f, 0xc0, 0x74, 0x44, 0x07, 0xcb, - 0x05, 0xca, 0x8e, 0x01, 0xdd, 0x81, 0xc1, 0x15, 0xfd, 0x3e, 0x58, 0x65, 0x98, 0x0b, 0xc9, 0x92, 0xa4, 0x0c, 0x9e, - 0xa7, 0x1e, 0x1c, 0xfc, 0x9a, 0x29, 0x73, 0x17, 0x65, 0x7d, 0xba, 0x24, 0xd3, 0x14, 0x19, 0x88, 0x75, 0xb8, 0xc9, - 0xd2, 0x28, 0x51, 0x22, 0xb2, 0x25, 0xfa, 0x47, 0x1a, 0x8a, 0xa5, 0x23, 0xf7, 0x22, 0x55, 0x22, 0x54, 0xcc, 0x53, - 0x3c, 0xa9, 0xd3, 0x3a, 0x1d, 0x61, 0xe8, 0x49, 0x50, 0xca, 0xd5, 0x30, 0x50, 0x25, 0xd5, 0x4b, 0x61, 0x53, 0x6c, - 0xb7, 0xfa, 0x62, 0x25, 0xe6, 0xf1, 0x02, 0x5f, 0x0a, 0x1c, 0xc5, 0x7f, 0x70, 0x2f, 0xec, 0x94, 0xda, 0x1e, 0xd4, - 0x8e, 0x28, 0xa1, 0xff, 0xe0, 0x70, 0x91, 0xf8, 0x56, 0xea, 0x10, 0x80, 0x68, 0x11, 0x72, 0xae, 0x0e, 0x52, 0xc3, - 0x0d, 0xed, 0x08, 0xff, 0x0d, 0xd7, 0x67, 0x9c, 0xd1, 0x9b, 0x6a, 0x46, 0x0d, 0xe5, 0xeb, 0x41, 0x1b, 0xa3, 0x3e, - 0x1b, 0x38, 0xac, 0x10, 0x85, 0x36, 0xec, 0xa4, 0x54, 0xa2, 0x85, 0xa1, 0x54, 0x7f, 0x09, 0x15, 0x27, 0xdc, 0x99, - 0x51, 0x96, 0x8c, 0x4f, 0xcb, 0x63, 0x31, 0x1d, 0x0c, 0x4a, 0x52, 0x19, 0x0b, 0x3d, 0xb8, 0x1e, 0x78, 0xfe, 0x3d, - 0x70, 0x0b, 0xf1, 0x90, 0x91, 0xc5, 0x90, 0x1b, 0x9c, 0xfc, 0x16, 0x27, 0x57, 0x8d, 0x4a, 0x15, 0xc7, 0x9a, 0xa8, - 0x16, 0xfc, 0xa3, 0x0c, 0x03, 0xf4, 0x49, 0x0a, 0xc0, 0x64, 0x30, 0xe5, 0x77, 0x20, 0x51, 0x3a, 0x57, 0x37, 0xa4, - 0x9f, 0x45, 0xc1, 0x2f, 0x79, 0xc1, 0x45, 0xe2, 0x0a, 0xb0, 0xbc, 0x83, 0xed, 0x75, 0x54, 0x51, 0x85, 0xc9, 0x6b, - 0x7a, 0x1c, 0x71, 0xe3, 0xfd, 0x67, 0x7a, 0x6c, 0x31, 0x5b, 0xad, 0x63, 0x83, 0xcf, 0x1c, 0x83, 0x0b, 0xba, 0x96, - 0xd8, 0x1a, 0xaa, 0x61, 0x45, 0x60, 0xe0, 0x02, 0x0e, 0xc2, 0x12, 0xc5, 0xb1, 0x95, 0xbc, 0x22, 0x0d, 0x29, 0xed, - 0x03, 0xc3, 0xd1, 0x26, 0x39, 0xbe, 0xcd, 0xb2, 0x9b, 0xc0, 0xf9, 0xa2, 0x73, 0xd2, 0x4c, 0x58, 0x1b, 0xbc, 0xcf, - 0x9b, 0xf3, 0x6b, 0xff, 0x90, 0x50, 0x15, 0xf7, 0x86, 0xb7, 0xe3, 0xde, 0x38, 0xe1, 0xd7, 0x5c, 0x2c, 0x74, 0xa8, - 0x16, 0x73, 0xc9, 0xf2, 0x5b, 0xeb, 0xdd, 0x92, 0xa4, 0x56, 0x40, 0xfb, 0x2c, 0x0b, 0x6a, 0x22, 0x00, 0xe4, 0x0f, - 0x7f, 0x81, 0xd0, 0x19, 0xfe, 0xf6, 0x18, 0x5c, 0x91, 0xc2, 0xbd, 0x43, 0x20, 0xac, 0xe9, 0xe6, 0x4e, 0x6d, 0xc0, - 0x17, 0xe3, 0xfe, 0x8c, 0xa9, 0xa7, 0xdf, 0x66, 0x72, 0x57, 0xd7, 0xed, 0x91, 0x65, 0xf8, 0x08, 0x57, 0x0a, 0xe0, - 0x66, 0xc2, 0x5f, 0x0c, 0x33, 0xa9, 0x3e, 0x01, 0x4c, 0x35, 0x1d, 0xdc, 0x27, 0x08, 0x0c, 0xa0, 0x12, 0x2d, 0x46, - 0x37, 0xca, 0x11, 0xcd, 0xc0, 0xad, 0xe9, 0x56, 0x18, 0x6f, 0x3d, 0x68, 0xa1, 0x67, 0x1a, 0x4e, 0xfc, 0x07, 0xcd, - 0xbc, 0x2a, 0x20, 0x80, 0x56, 0x46, 0xf0, 0xd6, 0xfa, 0x68, 0x8e, 0x10, 0x9f, 0xb0, 0x24, 0x9a, 0xb0, 0x78, 0xa6, - 0xf8, 0x31, 0xa1, 0x9b, 0xa6, 0xb6, 0xe9, 0x03, 0xd2, 0x5f, 0x5c, 0xb3, 0x7e, 0xca, 0xb2, 0xf6, 0xed, 0xa1, 0xe2, - 0xc5, 0xb4, 0x19, 0x07, 0x31, 0x51, 0xc5, 0xf8, 0x5f, 0x70, 0x5f, 0x6a, 0x05, 0x88, 0xcc, 0x5d, 0xf5, 0xf4, 0xfb, - 0xcd, 0x6c, 0x39, 0x10, 0x2a, 0xbf, 0x33, 0x48, 0xfa, 0x74, 0x68, 0x3f, 0xb0, 0x49, 0xd4, 0x16, 0x7a, 0xfe, 0xb8, - 0xd4, 0x4d, 0xbc, 0xbc, 0x36, 0x35, 0xa2, 0x15, 0x32, 0x54, 0xb6, 0x0e, 0x58, 0xdf, 0xdf, 0x87, 0xbb, 0x8b, 0x9a, - 0x86, 0x5a, 0xf7, 0xdc, 0xb5, 0x28, 0x38, 0xf1, 0x07, 0x18, 0x8b, 0x0b, 0x49, 0xad, 0xe3, 0x31, 0xe9, 0x47, 0x0b, - 0x99, 0xdc, 0xa8, 0xab, 0x93, 0x33, 0xc5, 0x3c, 0x81, 0x0b, 0x70, 0xd9, 0xf6, 0x57, 0x54, 0xea, 0x52, 0x6e, 0xaf, - 0x28, 0x4d, 0x0f, 0x69, 0x7b, 0x15, 0xe7, 0x6d, 0xc1, 0x05, 0xff, 0x4c, 0xc1, 0x85, 0x75, 0xb0, 0xee, 0xb8, 0x53, - 0xf6, 0x84, 0x27, 0xca, 0xb4, 0x36, 0xb8, 0xeb, 0x06, 0x63, 0x62, 0xec, 0x77, 0x97, 0x3c, 0xf9, 0x88, 0x2c, 0xf8, - 0xb7, 0x99, 0x00, 0xcf, 0x64, 0xf7, 0x4a, 0xe5, 0xff, 0xde, 0xbf, 0xda, 0xda, 0x77, 0xd6, 0xfc, 0xd3, 0xb3, 0x1e, - 0xee, 0x1c, 0x26, 0x3f, 0x56, 0x67, 0x40, 0x37, 0xd7, 0x32, 0xe5, 0x80, 0x0c, 0x60, 0x2d, 0x92, 0xd1, 0x80, 0x0f, - 0xad, 0x2c, 0xdb, 0xbe, 0xd3, 0xea, 0x82, 0xb0, 0x97, 0xc0, 0x4d, 0xf7, 0xd7, 0x66, 0x66, 0x4e, 0xd7, 0x4a, 0x34, - 0x5d, 0x1a, 0x5b, 0xcb, 0x52, 0x85, 0xf1, 0xbe, 0xf7, 0x24, 0x9b, 0xe6, 0xc7, 0xcb, 0x69, 0x6e, 0xa9, 0xdb, 0xc6, - 0x2d, 0x1b, 0x40, 0x43, 0xec, 0x5a, 0x5b, 0x39, 0xe0, 0xe5, 0xf6, 0x20, 0x9a, 0xaf, 0x15, 0xa1, 0xa7, 0x4a, 0x84, - 0x3e, 0x4d, 0x9b, 0x7d, 0xb0, 0xab, 0x6a, 0xdd, 0x08, 0x79, 0x34, 0x48, 0x35, 0x23, 0xff, 0xf6, 0x86, 0x17, 0x97, - 0xb9, 0xbc, 0x05, 0x38, 0x64, 0x52, 0x1b, 0x85, 0xe5, 0x35, 0xb8, 0xf3, 0xa3, 0xe3, 0x38, 0x13, 0xa3, 0x1c, 0xe3, - 0xb6, 0x22, 0x52, 0xb2, 0x4e, 0x9c, 0x01, 0x1e, 0xb2, 0x3f, 0x69, 0x3a, 0xb4, 0x6b, 0x81, 0xe1, 0x7d, 0x81, 0xbb, - 0xca, 0xd9, 0xc9, 0x26, 0xb7, 0x8b, 0xbe, 0x39, 0xc3, 0xba, 0x23, 0xa5, 0xb5, 0xb1, 0xe8, 0xba, 0x83, 0xb5, 0x66, - 0xd0, 0x16, 0xa1, 0xe4, 0x43, 0xee, 0xa4, 0xfd, 0x2b, 0xa0, 0xc1, 0x79, 0x96, 0xde, 0x59, 0xab, 0xfc, 0x8d, 0x16, - 0xe2, 0x44, 0x31, 0x75, 0xe2, 0x9b, 0x28, 0xd1, 0xe7, 0x67, 0x62, 0xdc, 0x40, 0x20, 0xf5, 0x7b, 0x8c, 0xaf, 0x51, - 0x84, 0x09, 0x5c, 0x07, 0xa2, 0xd8, 0x9e, 0xa8, 0x8d, 0xe5, 0x08, 0x3a, 0x21, 0xc4, 0x3b, 0x28, 0xc3, 0x58, 0x5d, - 0x1c, 0x68, 0x83, 0xa5, 0xaf, 0x5b, 0xeb, 0xdc, 0x10, 0x0a, 0xe3, 0x04, 0xa6, 0x18, 0x24, 0x75, 0xd6, 0x59, 0x26, - 0xa8, 0xb2, 0x63, 0xd2, 0x79, 0x1f, 0xa0, 0xbb, 0x6b, 0xd1, 0x14, 0x5f, 0x77, 0xee, 0xa0, 0x7d, 0x5c, 0xbf, 0xd6, - 0x22, 0x37, 0xf8, 0xf3, 0x96, 0x08, 0x8b, 0xc0, 0x59, 0x6b, 0xf2, 0x55, 0x23, 0x1c, 0x98, 0x92, 0x4c, 0xc3, 0x5e, - 0xa2, 0x6c, 0xba, 0xb7, 0xdb, 0x5e, 0x6f, 0xaf, 0x88, 0xab, 0xc7, 0x58, 0xe5, 0xdd, 0xcc, 0xed, 0x9d, 0x6a, 0x2d, - 0x76, 0x6f, 0xda, 0x7e, 0x8a, 0x1d, 0xb5, 0xd6, 0x6e, 0x37, 0x9c, 0x50, 0x43, 0xbe, 0x15, 0x55, 0x5a, 0x9d, 0x6e, - 0x0c, 0xda, 0x21, 0xb4, 0xb5, 0xc8, 0xe0, 0x46, 0xf9, 0xdc, 0x09, 0x9d, 0x54, 0xc8, 0x55, 0xa7, 0x2e, 0xd8, 0x5c, - 0xf3, 0x6a, 0x29, 0xd3, 0x48, 0x50, 0xb4, 0x39, 0x8f, 0x4a, 0x9a, 0xc8, 0xb5, 0xa8, 0x22, 0x59, 0xa3, 0x5e, 0xd4, - 0x6a, 0x0c, 0x10, 0x90, 0xe9, 0xbc, 0xe9, 0x41, 0x15, 0xcc, 0x86, 0x32, 0x92, 0xd3, 0xf7, 0x60, 0x69, 0x8f, 0x1c, - 0x6b, 0xbd, 0xaf, 0xce, 0x16, 0xdf, 0xea, 0x09, 0xc1, 0x14, 0x66, 0x0f, 0x44, 0x84, 0x6b, 0x1a, 0x43, 0x4e, 0xbb, - 0xc4, 0x65, 0x4d, 0xb7, 0x84, 0x3d, 0xdc, 0xae, 0x64, 0x27, 0x6e, 0x9e, 0x34, 0x37, 0x57, 0xb0, 0x93, 0x62, 0x3e, - 0x06, 0xed, 0x97, 0x54, 0xd7, 0x2e, 0xcd, 0xad, 0xc7, 0x83, 0x80, 0x06, 0x83, 0xc2, 0xf0, 0xaf, 0x13, 0xe3, 0xe1, - 0x49, 0x03, 0x82, 0xa4, 0x5c, 0x84, 0x63, 0xdf, 0x88, 0x7e, 0x32, 0x95, 0xc7, 0x1c, 0x2d, 0xde, 0xa1, 0xd5, 0x09, - 0x04, 0xf4, 0x12, 0xa1, 0x24, 0x46, 0x55, 0x68, 0x44, 0x50, 0x9e, 0x96, 0xbf, 0x54, 0xd5, 0x21, 0xa0, 0x90, 0xf6, - 0x15, 0x85, 0xb2, 0x4d, 0x62, 0x68, 0x86, 0x5f, 0xce, 0x27, 0x0b, 0x3d, 0x03, 0x03, 0x39, 0x3f, 0x5a, 0xe8, 0x59, - 0x18, 0xc8, 0xf9, 0x57, 0x8b, 0xda, 0xad, 0x03, 0x4d, 0x40, 0x3c, 0x17, 0x8e, 0x4e, 0x4a, 0xab, 0xb2, 0x05, 0x74, - 0xf3, 0x10, 0x41, 0xff, 0x87, 0x3d, 0x04, 0x9d, 0x5c, 0x68, 0x47, 0x6e, 0x40, 0xdb, 0x21, 0x09, 0xec, 0x15, 0x93, - 0x0a, 0x13, 0x8b, 0xe8, 0x98, 0x8d, 0xc1, 0x10, 0x5b, 0x7d, 0x70, 0xcc, 0xc6, 0x53, 0x9f, 0x04, 0x01, 0xa3, 0xfb, - 0xbd, 0x01, 0x07, 0xbf, 0xc3, 0xab, 0xf4, 0xc9, 0x46, 0xa0, 0x9b, 0xbe, 0xbb, 0x1b, 0x7a, 0x17, 0x57, 0x70, 0xaa, - 0x76, 0xf7, 0x24, 0x74, 0x93, 0x69, 0xc7, 0xea, 0x35, 0xc4, 0x0d, 0xf9, 0x95, 0xd1, 0x68, 0x64, 0x53, 0x42, 0x42, - 0x0c, 0xe7, 0xd0, 0xcc, 0x69, 0xb9, 0x7c, 0x75, 0xeb, 0xd9, 0x80, 0x0c, 0x33, 0xbd, 0x63, 0xb2, 0x7e, 0x80, 0xb2, - 0xea, 0x31, 0xb4, 0x43, 0xef, 0x91, 0xe3, 0x87, 0x07, 0xdf, 0x64, 0xfc, 0xc4, 0xe1, 0xda, 0xc3, 0xb9, 0xf0, 0x5d, - 0xd6, 0x8c, 0xcc, 0xa1, 0xf3, 0xec, 0xe3, 0x78, 0x0f, 0xe3, 0xe4, 0xd3, 0x2c, 0x94, 0x37, 0x5e, 0xd3, 0xff, 0xa8, - 0xf4, 0x66, 0x87, 0x43, 0x4e, 0x57, 0xb0, 0xe2, 0x66, 0x55, 0x68, 0xf8, 0x59, 0xe4, 0x8d, 0x23, 0x5e, 0x93, 0xa8, - 0xea, 0x3e, 0xef, 0x6d, 0xc4, 0xd2, 0x8e, 0x71, 0x00, 0x70, 0xa2, 0x56, 0x0d, 0xbb, 0xd2, 0xb8, 0x56, 0x07, 0x31, - 0x22, 0x25, 0x6c, 0x95, 0x38, 0x12, 0xca, 0xdf, 0x00, 0x84, 0xc5, 0x50, 0x1c, 0x6f, 0x0d, 0xeb, 0x03, 0xec, 0x87, - 0x2e, 0xd0, 0x34, 0xa7, 0x54, 0x33, 0x00, 0x48, 0x02, 0xfe, 0xe8, 0xe9, 0xa6, 0xa1, 0xb2, 0xcd, 0xf3, 0xd0, 0xb2, - 0xba, 0x82, 0x07, 0x7a, 0xea, 0x4a, 0x06, 0xc6, 0x55, 0x1d, 0x7b, 0x9b, 0xfd, 0xed, 0xd1, 0x2a, 0xf2, 0x9d, 0x4d, - 0x6a, 0x9a, 0x05, 0x90, 0xa2, 0x71, 0xe9, 0x0b, 0x3d, 0x9d, 0x00, 0xad, 0xd7, 0x96, 0x8a, 0xf6, 0xfb, 0x28, 0x46, - 0x8d, 0x0b, 0x05, 0x56, 0x61, 0x82, 0xc2, 0x21, 0xc2, 0x08, 0xa1, 0xdf, 0x95, 0xe1, 0xc6, 0x17, 0x64, 0x10, 0x0d, - 0xd7, 0xa2, 0x43, 0x11, 0x39, 0x5e, 0xb4, 0x2d, 0x55, 0x35, 0x27, 0x4d, 0x5b, 0x02, 0x6f, 0x22, 0x03, 0xb6, 0xf3, - 0x4f, 0x1b, 0x22, 0x57, 0xe1, 0x02, 0x86, 0xef, 0x88, 0x6b, 0x41, 0x74, 0x53, 0x9b, 0x7a, 0x1b, 0x76, 0x88, 0x8e, - 0xa6, 0x78, 0x74, 0xc8, 0x3d, 0x77, 0xcf, 0x6d, 0x11, 0xdf, 0x7e, 0x82, 0xdc, 0x35, 0x9d, 0xbd, 0x14, 0x61, 0x50, - 0xb7, 0x6c, 0xa0, 0x58, 0xc7, 0x4e, 0x50, 0x80, 0x01, 0x5c, 0xfe, 0x02, 0x3a, 0x36, 0x18, 0x54, 0x04, 0x9f, 0x14, - 0xb6, 0x4d, 0x83, 0xfc, 0x11, 0xef, 0x86, 0x0e, 0xaf, 0x2d, 0x79, 0x20, 0x5e, 0x61, 0x9f, 0x28, 0x61, 0xff, 0x82, - 0x82, 0xee, 0x28, 0x2f, 0x57, 0x85, 0xab, 0xd2, 0x00, 0x54, 0xd9, 0xf1, 0x5c, 0x6b, 0x4a, 0x5a, 0xc0, 0x4a, 0x49, - 0xdd, 0xf9, 0x4d, 0x70, 0xdc, 0x92, 0xa9, 0xf0, 0xad, 0xba, 0x51, 0xe5, 0xb1, 0x44, 0x91, 0x8e, 0x3d, 0xdb, 0x39, - 0x58, 0x03, 0xe0, 0x29, 0x6c, 0x2f, 0xce, 0x04, 0x7c, 0xee, 0xb4, 0xcb, 0x96, 0xb9, 0x04, 0x8a, 0xfa, 0x61, 0x9c, - 0x97, 0x1d, 0x5f, 0xee, 0x8e, 0xb6, 0xf7, 0xd0, 0x1b, 0xb1, 0x31, 0x5e, 0x9f, 0x47, 0x4d, 0x3f, 0x7b, 0x86, 0x2b, - 0x4b, 0x41, 0x1e, 0x68, 0xaa, 0x47, 0x18, 0x1d, 0x02, 0xd3, 0x94, 0x9f, 0xb0, 0xf1, 0x74, 0x38, 0x34, 0x64, 0xd0, - 0x6b, 0x26, 0x86, 0x02, 0xfb, 0x0c, 0x5a, 0x67, 0x26, 0xae, 0xf1, 0x69, 0xfb, 0x0a, 0x5a, 0xdd, 0xa1, 0x4c, 0xee, - 0x1c, 0x0c, 0x1f, 0x68, 0xc9, 0x14, 0x4c, 0x15, 0xde, 0x10, 0xa9, 0x64, 0x6f, 0x96, 0xd6, 0x61, 0xdf, 0x2e, 0x14, - 0x5a, 0x68, 0xe2, 0x57, 0x19, 0xe2, 0xa7, 0xae, 0x33, 0xff, 0x36, 0xed, 0x53, 0x83, 0x58, 0x38, 0x12, 0x83, 0x88, - 0x5f, 0x9c, 0x2a, 0xdb, 0x09, 0xa1, 0x62, 0xe3, 0xa1, 0x6b, 0xdd, 0x38, 0x92, 0x2a, 0x0c, 0xa5, 0xd0, 0x78, 0x6a, - 0xb8, 0xef, 0x85, 0x0e, 0x5f, 0x87, 0x59, 0xdc, 0x66, 0x8d, 0xa4, 0xc6, 0x38, 0x15, 0x26, 0x4e, 0xa5, 0x5c, 0x45, - 0x02, 0x03, 0xe5, 0xd9, 0xc2, 0x20, 0xc0, 0x24, 0x26, 0x19, 0x5b, 0x0b, 0x61, 0xc2, 0xd8, 0xb9, 0xc2, 0x34, 0x75, - 0x91, 0xfa, 0xcd, 0xc0, 0x64, 0x41, 0x43, 0x7e, 0x8f, 0x46, 0x6b, 0xaa, 0xa6, 0x00, 0xc3, 0x38, 0x4a, 0x35, 0xfe, - 0x2d, 0x42, 0x6d, 0x86, 0x01, 0x80, 0x6d, 0xde, 0xc9, 0x4c, 0x54, 0xaf, 0x04, 0x42, 0xa0, 0x39, 0xfb, 0xa9, 0xb8, - 0xda, 0x99, 0x05, 0xa3, 0x68, 0xb7, 0x57, 0x3e, 0x1f, 0x38, 0xa1, 0x3c, 0x55, 0x17, 0xa8, 0x97, 0xb2, 0x78, 0x2d, - 0x53, 0xde, 0x0a, 0x91, 0x79, 0x20, 0xd9, 0x87, 0x7c, 0x04, 0xe7, 0x15, 0x3a, 0x95, 0x9b, 0x6d, 0xa2, 0xcc, 0x92, - 0x24, 0x63, 0x81, 0xb1, 0x79, 0x09, 0x66, 0x52, 0x33, 0x63, 0xf8, 0x35, 0xc4, 0x19, 0xdb, 0x39, 0x09, 0x37, 0xfb, - 0x79, 0x60, 0x88, 0x52, 0x2e, 0x5a, 0xa2, 0x61, 0x6b, 0xc7, 0xeb, 0xc9, 0x35, 0xe1, 0x3e, 0x6c, 0xc4, 0x9a, 0x8c, - 0x31, 0xae, 0xcd, 0x8d, 0xac, 0x1f, 0x2d, 0xf0, 0x60, 0x4c, 0x59, 0x7f, 0x02, 0x99, 0x56, 0x52, 0xd6, 0xf9, 0xc2, - 0x88, 0x99, 0x54, 0xa2, 0x77, 0xfb, 0xc6, 0x67, 0x75, 0x17, 0x51, 0xbf, 0xb5, 0xdf, 0x93, 0x7a, 0xb8, 0xf7, 0x1f, - 0x14, 0xd6, 0xa0, 0x32, 0xe2, 0x32, 0xa2, 0x3c, 0x73, 0xa0, 0x9b, 0x26, 0x45, 0x9c, 0x9e, 0xaf, 0xe2, 0xa2, 0xe4, - 0x29, 0x54, 0xaa, 0xa9, 0x5b, 0xd4, 0x9b, 0x80, 0xbd, 0x21, 0x92, 0x24, 0x6b, 0x69, 0x6c, 0xc5, 0x2e, 0x0d, 0xd2, - 0xb3, 0x37, 0xe2, 0xd2, 0xcb, 0x0a, 0x0d, 0x69, 0xa9, 0x77, 0x16, 0x2a, 0x99, 0xbf, 0xe2, 0x3f, 0x83, 0x5a, 0x81, - 0x8e, 0x36, 0x29, 0xc6, 0x33, 0x60, 0xc4, 0x77, 0x83, 0x59, 0x3d, 0x40, 0x5c, 0x34, 0x41, 0xa9, 0x77, 0xc4, 0x8e, - 0x9f, 0x9a, 0x3c, 0xbc, 0x0b, 0x39, 0x67, 0xf0, 0xe9, 0xc3, 0x2c, 0x51, 0x6b, 0x1d, 0x89, 0x91, 0x9a, 0x01, 0x34, - 0x1d, 0x94, 0x39, 0x8f, 0x45, 0x30, 0xeb, 0x99, 0xc4, 0xa8, 0xc7, 0xf5, 0x2f, 0xd0, 0x50, 0xfb, 0xcd, 0xca, 0xf2, - 0xac, 0xba, 0xff, 0x1c, 0x0e, 0x6c, 0x6a, 0x2b, 0xe8, 0xf1, 0xba, 0x92, 0x57, 0x57, 0xaa, 0xdb, 0x7e, 0x21, 0x46, - 0x4e, 0xd7, 0xb8, 0x96, 0xce, 0xab, 0x05, 0xeb, 0x75, 0xa7, 0x9b, 0xc5, 0xdd, 0x2c, 0xa3, 0x81, 0xb0, 0xb6, 0xf3, - 0x89, 0xe6, 0xcf, 0x9a, 0x6d, 0xf7, 0xf1, 0x16, 0xc4, 0x2c, 0x00, 0x88, 0xf4, 0x20, 0x0a, 0x96, 0x59, 0xca, 0x03, - 0x2a, 0xf7, 0x71, 0x94, 0x85, 0xd2, 0xcb, 0x59, 0xc6, 0x4f, 0x9b, 0xc6, 0x5a, 0x67, 0x85, 0x32, 0xb4, 0x36, 0xba, - 0xd3, 0x55, 0x86, 0xd8, 0x7e, 0x12, 0x67, 0x0b, 0x70, 0x7f, 0xcc, 0x50, 0x68, 0xe8, 0x2c, 0x23, 0x4d, 0x34, 0x7c, - 0xd7, 0x9e, 0x41, 0x46, 0x71, 0xb2, 0xce, 0x2b, 0xe9, 0x46, 0x9f, 0xb5, 0x91, 0x30, 0xf7, 0x10, 0xfd, 0x2a, 0x06, - 0x8f, 0x72, 0x9f, 0xd7, 0x46, 0x27, 0xd3, 0x32, 0xd2, 0xee, 0xfc, 0xa4, 0x5e, 0x66, 0xa9, 0xd6, 0x61, 0xfb, 0x0c, - 0x7b, 0x6b, 0x4c, 0x7a, 0x13, 0x52, 0xc3, 0x48, 0x7c, 0x3a, 0xa3, 0x46, 0x08, 0x68, 0xcb, 0xf1, 0x77, 0xf8, 0x0c, - 0x43, 0x53, 0x60, 0xa9, 0xe2, 0x16, 0x76, 0xc3, 0xd7, 0x7c, 0xb2, 0x6a, 0x01, 0x08, 0x66, 0xe5, 0xeb, 0x5d, 0xbc, - 0x12, 0xea, 0x73, 0x6d, 0x06, 0x80, 0x2c, 0x28, 0xe5, 0x8e, 0x9f, 0x52, 0xe9, 0x60, 0x89, 0xa2, 0xed, 0xe5, 0xf4, - 0x8d, 0x8e, 0x8d, 0x1f, 0xd2, 0x73, 0x01, 0xdb, 0x85, 0xfc, 0xd6, 0x5e, 0xbd, 0x44, 0x45, 0x6a, 0xdb, 0xac, 0x07, - 0xf8, 0x72, 0x83, 0x26, 0x61, 0x04, 0x65, 0xca, 0x14, 0xc0, 0xe0, 0xa6, 0x1a, 0x05, 0x93, 0x56, 0x23, 0x61, 0x4b, - 0x3d, 0xc9, 0x72, 0xd3, 0x07, 0xa7, 0xda, 0x23, 0xe8, 0xd1, 0x0e, 0x27, 0x2d, 0xfb, 0xb5, 0x82, 0xa3, 0x93, 0xab, - 0x21, 0x6a, 0xe6, 0xbd, 0xb6, 0x23, 0x43, 0xca, 0x65, 0x18, 0x08, 0xa6, 0x1c, 0xf3, 0xf4, 0xd8, 0x7a, 0x46, 0x44, - 0x0f, 0x9c, 0x7d, 0xa6, 0x5b, 0x75, 0x25, 0x01, 0xd1, 0xf1, 0xeb, 0x27, 0xaf, 0xae, 0xe3, 0x2b, 0x83, 0xa2, 0xd4, - 0xb0, 0x88, 0x51, 0xa6, 0x7d, 0x95, 0x84, 0xc1, 0xfb, 0xf9, 0xfd, 0x8f, 0x2a, 0x4b, 0xed, 0xf7, 0x60, 0x63, 0x45, - 0x55, 0x3f, 0x97, 0xbc, 0x68, 0x0a, 0xb0, 0xf6, 0x59, 0xa2, 0x40, 0xee, 0xf7, 0x36, 0xcd, 0x7c, 0x13, 0x35, 0x6e, - 0x36, 0xac, 0x37, 0xae, 0xdb, 0xa5, 0xb6, 0x64, 0x47, 0x56, 0x22, 0x67, 0x16, 0x83, 0x19, 0x3f, 0x2a, 0x0c, 0x4a, - 0xc3, 0x06, 0x55, 0xa9, 0xf8, 0xbd, 0x11, 0xc1, 0xa9, 0x63, 0x55, 0x61, 0x4c, 0x03, 0x66, 0x5b, 0x51, 0x6b, 0x50, - 0x07, 0xa5, 0xb4, 0x35, 0x01, 0xd9, 0x7e, 0x65, 0x05, 0x35, 0xbf, 0xff, 0x65, 0x0c, 0xf9, 0x9a, 0x52, 0x50, 0x49, - 0xc0, 0xce, 0xa0, 0xd1, 0x53, 0x25, 0x0c, 0xa4, 0x20, 0x78, 0x02, 0x94, 0x2f, 0xa2, 0xc6, 0x6a, 0xb7, 0xaf, 0x4e, - 0x8d, 0xd1, 0x16, 0x10, 0x5a, 0x48, 0x8f, 0x2e, 0xfb, 0xb8, 0x8d, 0x75, 0x20, 0xf1, 0xe0, 0x04, 0xdb, 0xb9, 0xba, - 0x46, 0x23, 0xa1, 0xf9, 0x43, 0xa3, 0x01, 0xaf, 0x69, 0x05, 0x0a, 0xf5, 0x1c, 0x47, 0x43, 0x67, 0x87, 0x14, 0x44, - 0x6c, 0xd0, 0xc2, 0xbe, 0x3d, 0x1f, 0x9a, 0x7d, 0x3d, 0x4f, 0x16, 0xa4, 0xa6, 0xd2, 0x7d, 0xee, 0x96, 0x90, 0xb5, - 0xea, 0x50, 0x56, 0x1e, 0xe0, 0x78, 0xa1, 0x64, 0xfe, 0x0e, 0x93, 0x1a, 0xa5, 0x31, 0xa1, 0x31, 0x62, 0x01, 0x4b, - 0x82, 0xf6, 0x7a, 0xa0, 0x7e, 0x19, 0x84, 0x0a, 0x67, 0x7a, 0x22, 0xf1, 0x29, 0xe5, 0xea, 0xd3, 0x82, 0xd4, 0xd3, - 0x82, 0x39, 0xd0, 0x4b, 0xdf, 0xca, 0xaf, 0x6c, 0x7c, 0xb4, 0xbb, 0x77, 0xcd, 0x85, 0x75, 0x0c, 0x71, 0xb1, 0x85, - 0xdf, 0x9c, 0x9a, 0x02, 0xb0, 0xe1, 0xa9, 0x2e, 0xcb, 0x37, 0x6a, 0x22, 0xb3, 0x38, 0x24, 0x11, 0x48, 0xb6, 0x9b, - 0x9b, 0xdb, 0x08, 0xb6, 0xbd, 0x85, 0xda, 0x50, 0x7f, 0x79, 0xdb, 0x7d, 0xcf, 0xf0, 0x72, 0x4f, 0xee, 0xdd, 0xb4, - 0xa1, 0xfc, 0x7e, 0xff, 0x2a, 0xf9, 0xbf, 0xaa, 0x64, 0xbf, 0x55, 0x66, 0xdd, 0x16, 0xef, 0x77, 0x1d, 0xb7, 0x1c, - 0xa3, 0x41, 0x60, 0x4d, 0x81, 0x81, 0xf4, 0xa4, 0x31, 0x4d, 0x74, 0x74, 0x65, 0xc6, 0x0c, 0x1e, 0x5d, 0x80, 0xe6, - 0x30, 0x9d, 0xe7, 0x31, 0x00, 0x07, 0xf8, 0x47, 0x1e, 0xa1, 0xfe, 0xe9, 0x3c, 0x0f, 0xce, 0x83, 0x41, 0x39, 0x08, - 0xf4, 0x27, 0xae, 0x39, 0xc1, 0x02, 0x74, 0x6e, 0x31, 0x83, 0xb8, 0x93, 0xd6, 0xcc, 0x21, 0x3e, 0x4e, 0xa6, 0x83, - 0x41, 0x4c, 0x36, 0x00, 0xd2, 0x17, 0x2f, 0xac, 0x73, 0x50, 0xa1, 0x17, 0x64, 0xab, 0xee, 0xa2, 0x59, 0xb1, 0x57, - 0xed, 0x34, 0xef, 0xf7, 0xf3, 0x79, 0x39, 0x08, 0x1a, 0x15, 0x16, 0xc6, 0xfb, 0x8f, 0x36, 0xbf, 0x34, 0x3a, 0x69, - 0x82, 0x11, 0x6b, 0x4f, 0x51, 0xbd, 0xe2, 0x69, 0x46, 0x1b, 0xb7, 0x63, 0xa5, 0x7c, 0x01, 0x51, 0x3c, 0x30, 0x64, - 0xad, 0xbc, 0x3b, 0x07, 0xaf, 0xcb, 0x8d, 0x37, 0x47, 0x14, 0x60, 0x37, 0x85, 0x71, 0x52, 0x73, 0xd1, 0x45, 0x4d, - 0x3c, 0x83, 0x9d, 0xae, 0xde, 0x4a, 0xb4, 0x1a, 0xef, 0xc5, 0xbb, 0x66, 0xe3, 0x6f, 0xe4, 0x81, 0x2e, 0xf3, 0xe0, - 0x12, 0x10, 0x67, 0x0f, 0xe2, 0xea, 0x00, 0x4b, 0x3d, 0x08, 0x06, 0x16, 0x39, 0xa4, 0x5d, 0xad, 0x1e, 0x8a, 0x48, - 0x9d, 0xc7, 0x60, 0xc0, 0x64, 0x1a, 0x52, 0x93, 0x69, 0xaf, 0x50, 0x90, 0x36, 0xd6, 0x5a, 0x40, 0x1b, 0x0e, 0x8b, - 0x1d, 0xbb, 0x61, 0x77, 0xba, 0x75, 0x28, 0x94, 0x30, 0x90, 0x75, 0xdd, 0x3c, 0xd4, 0x1a, 0x9e, 0x08, 0x7a, 0x50, - 0x8d, 0xf6, 0xd3, 0x43, 0x79, 0xd2, 0x1e, 0x0b, 0x70, 0xd1, 0xc3, 0x97, 0x2f, 0x04, 0x5e, 0xb4, 0x77, 0x90, 0xe7, - 0xcc, 0xa7, 0xca, 0x07, 0xb1, 0xe1, 0x96, 0xe1, 0x43, 0xfb, 0xf8, 0x56, 0x20, 0x93, 0xba, 0xa3, 0xa9, 0xad, 0xdd, - 0xd1, 0x38, 0x26, 0xd0, 0x6f, 0xca, 0x51, 0xca, 0xc4, 0xd4, 0xb2, 0x64, 0x27, 0xbd, 0x5c, 0x79, 0x43, 0xa5, 0xec, - 0x64, 0xd9, 0xe6, 0xfc, 0xd2, 0x46, 0x42, 0xbf, 0xaf, 0xdd, 0x81, 0xf0, 0x8d, 0x5a, 0x6f, 0xc8, 0xcb, 0x86, 0x88, - 0xe5, 0x10, 0x33, 0x70, 0xbc, 0x90, 0xca, 0xb5, 0xbb, 0x68, 0xaa, 0xea, 0x76, 0xb6, 0x72, 0x41, 0x4b, 0xbc, 0x95, - 0x02, 0xab, 0x48, 0x9d, 0x5e, 0x4f, 0x25, 0xee, 0xfb, 0x28, 0xb6, 0x1f, 0x01, 0xdb, 0xd8, 0x38, 0x1a, 0x1b, 0xb7, - 0x88, 0x0d, 0xbe, 0x8a, 0x2a, 0x5a, 0x70, 0x80, 0xe0, 0x6e, 0x4b, 0x6a, 0x69, 0xe6, 0x10, 0xf7, 0x15, 0x0f, 0xd0, - 0xbe, 0x8b, 0x23, 0x4e, 0x05, 0xd8, 0xd6, 0xb5, 0xce, 0x59, 0x2d, 0x07, 0x6c, 0x26, 0x7a, 0xfe, 0x69, 0xd5, 0x48, - 0xc4, 0xb0, 0xca, 0x46, 0xca, 0x0a, 0xed, 0x41, 0xe9, 0x12, 0x2e, 0xbe, 0x00, 0x2f, 0xdb, 0xfb, 0x95, 0xdd, 0xe7, - 0x4b, 0xec, 0x1f, 0xe6, 0x55, 0x13, 0x3c, 0xf2, 0x1a, 0x6f, 0xef, 0x61, 0xe2, 0x73, 0xa5, 0x10, 0x5e, 0xa5, 0x34, - 0x94, 0x00, 0x0c, 0x92, 0xa0, 0x86, 0x2b, 0x6d, 0x9b, 0x41, 0x2a, 0x63, 0xd8, 0xdd, 0xea, 0xad, 0xfe, 0x4f, 0xab, - 0x70, 0x51, 0xc9, 0x62, 0x4c, 0x02, 0x9d, 0x53, 0x2d, 0x37, 0x81, 0x05, 0xcf, 0x77, 0xc9, 0x11, 0x28, 0xec, 0x04, - 0x70, 0x43, 0x09, 0xfb, 0x19, 0x6f, 0x43, 0x39, 0x7b, 0x69, 0x25, 0x4f, 0x6e, 0x5f, 0x52, 0x41, 0x13, 0x32, 0x15, - 0x76, 0xff, 0xb6, 0x36, 0xec, 0xf3, 0x50, 0x8e, 0xa4, 0xc0, 0xc5, 0x41, 0xe7, 0x00, 0xf6, 0x07, 0xb9, 0x8c, 0xcd, - 0x67, 0xd2, 0xef, 0xab, 0xf7, 0xcf, 0xf2, 0x2c, 0xf9, 0xb8, 0xf3, 0xde, 0xf0, 0x34, 0x4b, 0x06, 0x54, 0x22, 0xa6, - 0xd6, 0x55, 0x31, 0x5c, 0x6a, 0x17, 0xe3, 0x06, 0xc9, 0x88, 0xf7, 0x52, 0x87, 0x18, 0x31, 0xbe, 0xc8, 0x0e, 0x49, - 0xc9, 0xe9, 0xb2, 0xee, 0xec, 0xb9, 0x16, 0xcd, 0xa0, 0x31, 0xdc, 0x8e, 0xf7, 0x92, 0x5e, 0x01, 0x2a, 0x40, 0x74, - 0xcf, 0x02, 0xd7, 0xf0, 0xe6, 0x92, 0x68, 0x6c, 0xe9, 0x69, 0x4b, 0x34, 0xb0, 0x57, 0x26, 0x24, 0xd5, 0xc6, 0x01, - 0x16, 0xb1, 0xae, 0x3f, 0x86, 0x05, 0x00, 0xb5, 0x1a, 0xa4, 0x57, 0xfa, 0x92, 0x50, 0x95, 0x84, 0x60, 0x74, 0x22, - 0xe1, 0x65, 0x40, 0xe3, 0xcc, 0x24, 0x5a, 0xd8, 0xe0, 0x80, 0x3e, 0xaf, 0x4c, 0xa2, 0xb1, 0x21, 0x0f, 0x28, 0xb7, - 0x69, 0x00, 0x83, 0x0f, 0x92, 0x24, 0xfa, 0x6a, 0x69, 0x92, 0x40, 0x50, 0x82, 0xf2, 0x0d, 0xfa, 0x53, 0xe9, 0xf9, - 0x58, 0xfe, 0xe6, 0x1d, 0x4a, 0xdf, 0x87, 0x05, 0xc8, 0x14, 0x75, 0xc5, 0x34, 0x63, 0x27, 0x59, 0xb7, 0x31, 0x89, - 0xe7, 0x69, 0x77, 0x57, 0x28, 0x97, 0x2e, 0xf0, 0x2b, 0xcb, 0x10, 0xc7, 0xfa, 0x59, 0xbc, 0x62, 0xa7, 0x21, 0xd7, - 0x78, 0xe9, 0xcf, 0xe2, 0x15, 0xce, 0x10, 0xad, 0x5a, 0x09, 0x44, 0xf9, 0xaf, 0xda, 0xc0, 0x21, 0xee, 0x13, 0x0c, - 0x72, 0x51, 0x79, 0x0f, 0x04, 0xf2, 0xb6, 0x82, 0x88, 0x34, 0xb3, 0xeb, 0x30, 0x22, 0xd5, 0x4e, 0x92, 0xf9, 0xf2, - 0x07, 0x99, 0x09, 0xef, 0x1b, 0x78, 0x6c, 0x36, 0xcb, 0xa6, 0x98, 0x2f, 0x54, 0x30, 0x07, 0xf7, 0x89, 0x8a, 0x4b, - 0x51, 0xf9, 0x4f, 0xd8, 0x05, 0x2f, 0xc6, 0x83, 0xd7, 0x6b, 0x04, 0xd8, 0xaf, 0xfc, 0x27, 0x6f, 0xcc, 0xfe, 0xb2, - 0x6e, 0x7c, 0x99, 0x89, 0xf8, 0xc0, 0x47, 0x77, 0x94, 0x8f, 0xee, 0xbd, 0x4c, 0x7f, 0x34, 0xa0, 0x44, 0x46, 0x65, - 0xc5, 0x57, 0x2b, 0x9e, 0xce, 0xee, 0x92, 0x28, 0x1b, 0x55, 0x5c, 0xc0, 0xf4, 0x82, 0xe3, 0x5d, 0xb2, 0xbe, 0xc8, - 0x92, 0x57, 0x10, 0x7b, 0x60, 0x25, 0x15, 0x16, 0x3f, 0x2c, 0x33, 0xb5, 0x98, 0x85, 0xac, 0xa4, 0xe0, 0xc1, 0xec, - 0x26, 0x89, 0xfe, 0x5a, 0x7a, 0x48, 0x6a, 0x66, 0xca, 0x36, 0xb5, 0x23, 0xd4, 0xc6, 0xd7, 0x91, 0x6e, 0xb4, 0x05, - 0x00, 0xdc, 0xb3, 0x45, 0x1a, 0x49, 0x26, 0x86, 0x93, 0x9a, 0x71, 0x93, 0x5e, 0x60, 0x6a, 0x5c, 0xb3, 0x8a, 0x26, - 0xce, 0x42, 0x06, 0xf4, 0xfe, 0x80, 0x97, 0x83, 0xcf, 0x19, 0xdc, 0x7f, 0xd0, 0x1a, 0xb8, 0x3c, 0x2e, 0xfa, 0x7d, - 0x79, 0x5c, 0x6c, 0xb7, 0xe5, 0x49, 0xdc, 0xef, 0xcb, 0x93, 0xd8, 0xf0, 0x0f, 0x4a, 0xb1, 0x6d, 0xcc, 0x0d, 0x12, - 0x9a, 0x4b, 0x88, 0x5a, 0x34, 0x82, 0x3f, 0x34, 0xcb, 0xb9, 0x88, 0xf2, 0xe3, 0xa4, 0xdf, 0xef, 0x2d, 0x67, 0x62, - 0x90, 0x0f, 0x93, 0x28, 0x1f, 0x26, 0x9e, 0x13, 0xe2, 0xb7, 0x9e, 0x13, 0xa2, 0xa2, 0x81, 0x2b, 0x38, 0x33, 0x00, - 0x51, 0xc0, 0xa7, 0x7f, 0x54, 0xd7, 0x52, 0xe8, 0x5a, 0x62, 0x55, 0x4b, 0xa2, 0x2b, 0xa8, 0xd9, 0x4d, 0x11, 0x96, - 0x58, 0x0a, 0x5d, 0xb2, 0x3f, 0x96, 0xc0, 0x13, 0xe5, 0xbc, 0xda, 0x00, 0x03, 0x1b, 0xe1, 0x9d, 0xc3, 0x84, 0x93, - 0x58, 0xd7, 0x80, 0x76, 0xba, 0xa9, 0xe9, 0x25, 0x5d, 0xd1, 0x2b, 0xe4, 0x67, 0x2f, 0xc1, 0x60, 0xe9, 0x98, 0xe5, - 0xd3, 0xc1, 0xe0, 0x92, 0xac, 0x58, 0x39, 0x0f, 0xe3, 0x41, 0xb8, 0x9e, 0xe5, 0xc3, 0xcb, 0xe8, 0x92, 0x90, 0x2f, - 0x8a, 0x05, 0xed, 0xad, 0x46, 0xe5, 0xc7, 0x0c, 0xc2, 0xfb, 0xa5, 0xb3, 0x30, 0x33, 0x71, 0x3e, 0x56, 0xa3, 0x3b, - 0xba, 0x82, 0xf8, 0x35, 0x70, 0x23, 0x21, 0x11, 0x74, 0xe4, 0x8a, 0xae, 0xe8, 0x9a, 0x4a, 0x33, 0xc3, 0x18, 0xad, - 0xdb, 0x1e, 0x27, 0x09, 0x38, 0x26, 0xbb, 0xe2, 0xa3, 0xb1, 0x2a, 0xbc, 0xeb, 0x3b, 0x42, 0x7b, 0xbd, 0xc4, 0x0d, - 0xd2, 0x2f, 0xed, 0x41, 0x02, 0x46, 0x64, 0xa4, 0x06, 0xca, 0x8c, 0x8c, 0xa4, 0x66, 0x52, 0x71, 0x48, 0x62, 0x7f, - 0x48, 0xd4, 0x38, 0x24, 0xfe, 0x38, 0xe4, 0x7a, 0x1c, 0x90, 0xbb, 0x5f, 0xb2, 0x31, 0x4d, 0xd9, 0x98, 0xae, 0xd5, - 0xa8, 0xd0, 0x6b, 0x7a, 0xa1, 0xa9, 0xe3, 0x39, 0x7b, 0x0d, 0x07, 0xf6, 0x20, 0xcc, 0x67, 0xf1, 0xf0, 0x75, 0xf4, - 0x9a, 0x90, 0x2f, 0x24, 0xbd, 0x51, 0x97, 0x32, 0x08, 0x84, 0x78, 0x0d, 0xce, 0xa5, 0x2e, 0xd4, 0xc9, 0xb5, 0xd9, - 0x71, 0xf8, 0x74, 0xd5, 0x78, 0xba, 0x80, 0x88, 0x3e, 0x68, 0xa5, 0xd2, 0xef, 0x87, 0x97, 0xac, 0x9c, 0x9f, 0x87, - 0x63, 0x02, 0x38, 0x3c, 0x7a, 0x38, 0x2f, 0x47, 0x77, 0xf4, 0x72, 0x74, 0x4f, 0xc0, 0xc2, 0x6b, 0x3c, 0x5d, 0x1f, - 0xb3, 0x78, 0x3a, 0x18, 0xac, 0x91, 0xaa, 0xab, 0xdc, 0x6b, 0xb2, 0xa0, 0x97, 0x38, 0x11, 0x04, 0x18, 0xfa, 0x4c, - 0xac, 0x0d, 0x0d, 0x7f, 0xcd, 0xe0, 0xe3, 0x7b, 0x76, 0x39, 0xba, 0xa7, 0x77, 0xec, 0xf5, 0x76, 0x3c, 0x05, 0x66, - 0x6a, 0x35, 0x0b, 0xef, 0x8f, 0xaf, 0x66, 0x57, 0xec, 0x3e, 0xba, 0x3f, 0x81, 0x86, 0x5e, 0xb3, 0x7b, 0x04, 0x5c, - 0x4a, 0x1f, 0x2f, 0x07, 0xaf, 0xc9, 0xe1, 0x60, 0x90, 0x92, 0x28, 0xbc, 0x09, 0xbd, 0x56, 0xbe, 0xa6, 0xf7, 0x84, - 0xae, 0xd8, 0x1d, 0x8e, 0xc6, 0x15, 0xc3, 0x0f, 0x2e, 0xd8, 0x7d, 0x7d, 0x13, 0x7a, 0xbb, 0x39, 0x11, 0x9d, 0x20, - 0x46, 0xe8, 0x6b, 0xe0, 0x68, 0x96, 0x0b, 0x33, 0x01, 0x4f, 0xe6, 0x22, 0xa3, 0x45, 0xa1, 0x19, 0x88, 0xb3, 0x12, - 0x10, 0x4b, 0xa2, 0xee, 0x37, 0x1b, 0x9d, 0xc3, 0x72, 0xee, 0xf7, 0x7b, 0x95, 0xa1, 0x07, 0x88, 0x9c, 0xd9, 0x49, - 0x0f, 0x7a, 0x3e, 0x3d, 0xc0, 0x4f, 0xf4, 0xaa, 0x41, 0x9c, 0xcc, 0x5f, 0x96, 0xd1, 0xb7, 0x1e, 0x7d, 0xf8, 0xbe, - 0x9b, 0xf2, 0x88, 0xfc, 0xdf, 0xa7, 0x3c, 0x65, 0x1e, 0xbd, 0xae, 0x3c, 0x10, 0x3c, 0x6f, 0x4d, 0x2a, 0x8d, 0x44, - 0x35, 0x3a, 0x5f, 0xc5, 0xa0, 0x8d, 0x44, 0x6d, 0x83, 0x7e, 0x42, 0x0b, 0x2b, 0x88, 0x90, 0x73, 0xf4, 0x1c, 0x0c, - 0x52, 0x21, 0x54, 0x8e, 0x5a, 0x94, 0x68, 0x08, 0x92, 0xcb, 0x92, 0xab, 0xf0, 0x39, 0x84, 0xaa, 0xd3, 0xc7, 0x99, - 0x08, 0x1b, 0x7a, 0x1c, 0xfa, 0x00, 0xf0, 0x3f, 0xef, 0x90, 0x8b, 0x92, 0x5f, 0xe1, 0xd9, 0xdc, 0x26, 0x18, 0x05, - 0x4b, 0x44, 0x33, 0xb4, 0x0d, 0x62, 0x3f, 0x96, 0x04, 0xeb, 0x91, 0x34, 0x1e, 0x95, 0xe6, 0x88, 0xf0, 0xa3, 0xf8, - 0x28, 0x7a, 0x1a, 0x1b, 0x12, 0xc9, 0x91, 0x44, 0xf2, 0x01, 0x10, 0x4e, 0x82, 0xfe, 0xe2, 0xae, 0xc9, 0xae, 0x85, - 0xc4, 0xa0, 0x3f, 0x2d, 0x99, 0x96, 0xdd, 0xab, 0x1e, 0xfb, 0x8a, 0x20, 0x77, 0x4c, 0xff, 0xe9, 0xf5, 0xe1, 0x5f, - 0x4b, 0x9c, 0x41, 0xeb, 0xf9, 0xa2, 0x3a, 0x33, 0xf3, 0x06, 0x37, 0xf2, 0xba, 0xac, 0x5d, 0x97, 0x2f, 0xf9, 0x01, - 0xbf, 0xab, 0xb8, 0x48, 0xcb, 0x83, 0x9f, 0xaa, 0x36, 0x9e, 0x53, 0xb9, 0x5e, 0xb9, 0x38, 0x2b, 0xca, 0x38, 0xd5, - 0x93, 0xba, 0x18, 0x6b, 0xd8, 0x86, 0xdf, 0x23, 0xea, 0x4a, 0x5a, 0x8e, 0x9e, 0x52, 0xae, 0x9a, 0x29, 0x97, 0xeb, - 0x3c, 0xff, 0x71, 0x27, 0x15, 0xa7, 0xb8, 0x99, 0x82, 0x54, 0xa9, 0xe5, 0x02, 0xaa, 0xe7, 0xa8, 0xe5, 0x6e, 0x69, - 0x76, 0x80, 0x73, 0xdb, 0x54, 0x1f, 0x2b, 0xb3, 0x0b, 0x2f, 0xb9, 0x71, 0x7f, 0x32, 0x65, 0x58, 0x30, 0x0a, 0x6d, - 0x56, 0x5d, 0x69, 0xfb, 0x42, 0xeb, 0x34, 0x0c, 0x57, 0x7e, 0xbc, 0x80, 0x74, 0x01, 0xe3, 0x78, 0x51, 0x32, 0x31, - 0x6e, 0x8f, 0xde, 0x0a, 0xe2, 0x73, 0xb6, 0x02, 0xe9, 0xf7, 0x7b, 0xc2, 0xdb, 0x75, 0x1d, 0x6d, 0xf7, 0xc4, 0x29, - 0xa3, 0x72, 0x15, 0x8b, 0xef, 0xe2, 0x95, 0x81, 0x4c, 0x56, 0xc7, 0x63, 0x63, 0x4c, 0xa7, 0xff, 0x48, 0x42, 0xbf, - 0x10, 0x0a, 0x3e, 0xeb, 0xa5, 0x95, 0x27, 0xb7, 0x87, 0x65, 0x5c, 0xa3, 0x57, 0xe2, 0x4a, 0xf7, 0xcd, 0x48, 0x21, - 0xf5, 0xc8, 0x57, 0x4d, 0x01, 0xbd, 0x19, 0xfb, 0x66, 0x2a, 0xcc, 0xdb, 0x9e, 0x31, 0x57, 0x08, 0x56, 0xaa, 0xec, - 0xf6, 0x9d, 0x1a, 0x53, 0x31, 0x83, 0x29, 0xb6, 0x9d, 0xc5, 0xa4, 0x5b, 0xf9, 0xa7, 0x9d, 0xfb, 0x65, 0xde, 0xe1, - 0xae, 0xa8, 0xdf, 0x02, 0x17, 0x9a, 0x15, 0x65, 0xd5, 0x96, 0x0d, 0xdb, 0xc6, 0x1b, 0x59, 0x28, 0x36, 0xc0, 0xb2, - 0xe7, 0xbe, 0x85, 0x07, 0x88, 0x9b, 0x70, 0xcf, 0x2e, 0x6a, 0xb8, 0x31, 0x7c, 0x5e, 0x49, 0xbe, 0x2b, 0x8d, 0xb9, - 0xf4, 0xa9, 0xd2, 0xc4, 0x70, 0xb2, 0x18, 0x71, 0x91, 0x2e, 0xea, 0xcc, 0xae, 0x85, 0x4f, 0x78, 0x19, 0xce, 0xf9, - 0xc2, 0xe8, 0xa6, 0x74, 0xe9, 0x05, 0x8b, 0x75, 0xa7, 0x37, 0x2b, 0x8d, 0x95, 0x12, 0x71, 0x6b, 0x96, 0x09, 0x94, - 0xa5, 0xac, 0x95, 0xf0, 0xa6, 0x68, 0xd9, 0x4a, 0x1a, 0x79, 0xcf, 0x1c, 0xdc, 0xc7, 0xbe, 0x47, 0x4c, 0x64, 0x13, - 0x98, 0x14, 0x0d, 0x1d, 0xd0, 0xae, 0xba, 0xf0, 0xcd, 0xa8, 0x07, 0x83, 0xdc, 0x92, 0x44, 0xac, 0x20, 0xc5, 0x0a, - 0xd6, 0x35, 0x2b, 0xe6, 0xf9, 0x82, 0x5e, 0x32, 0x39, 0x4f, 0x17, 0x74, 0xc5, 0xe4, 0x7c, 0x8d, 0x37, 0xa1, 0x4b, - 0x38, 0x21, 0xc9, 0x26, 0x56, 0x0a, 0xd8, 0x4b, 0xbc, 0xbc, 0xe1, 0x99, 0xaa, 0x69, 0xd9, 0x95, 0xe2, 0x00, 0xe3, - 0x8b, 0x32, 0x0c, 0xcb, 0xe1, 0x25, 0x58, 0x4b, 0x1c, 0x86, 0xab, 0x39, 0x5f, 0xa8, 0xdf, 0x10, 0x75, 0x3e, 0x09, - 0x15, 0xbb, 0x60, 0xf7, 0x02, 0x99, 0x5e, 0xcf, 0xf9, 0x42, 0x8d, 0x84, 0x2e, 0xf8, 0xda, 0x1a, 0x9b, 0xc4, 0x9e, - 0xa0, 0x65, 0x16, 0xcf, 0xc7, 0x8b, 0x28, 0xae, 0x61, 0x19, 0x9e, 0xa9, 0x99, 0x69, 0xc9, 0x7f, 0x12, 0xb5, 0xa1, - 0x89, 0xbe, 0xc1, 0x2a, 0xf2, 0x87, 0xc7, 0x47, 0x97, 0x40, 0xc6, 0xce, 0xae, 0x64, 0xe6, 0x43, 0xdf, 0x47, 0x06, - 0xf7, 0xdc, 0x94, 0x33, 0xae, 0x82, 0x44, 0x19, 0xb8, 0x7b, 0x35, 0x4b, 0xc6, 0x5a, 0x84, 0xef, 0x1e, 0x15, 0x45, - 0x9f, 0x49, 0xd3, 0x80, 0xee, 0x23, 0xc1, 0x1c, 0xe8, 0xbd, 0x42, 0x87, 0xcb, 0x6a, 0x9b, 0x09, 0xf8, 0x8b, 0x04, - 0xf9, 0xad, 0xd0, 0xab, 0x1a, 0x83, 0x2a, 0xda, 0x45, 0x2c, 0xfd, 0xfb, 0x88, 0x1f, 0x65, 0xf3, 0x4f, 0x73, 0x8f, - 0x57, 0x12, 0x06, 0x3f, 0xa4, 0x66, 0x93, 0xcc, 0xdb, 0x2b, 0xf6, 0x1e, 0x3a, 0xea, 0x51, 0x6b, 0xbc, 0xaf, 0x5e, - 0x72, 0x0a, 0x31, 0x4a, 0x28, 0x3a, 0x09, 0x06, 0x70, 0xbb, 0x84, 0x14, 0x77, 0x83, 0xdd, 0x34, 0xaf, 0x79, 0x51, - 0x70, 0xb1, 0xae, 0xaa, 0xc0, 0x0f, 0x68, 0x38, 0x5f, 0xec, 0x86, 0x30, 0x1c, 0xd3, 0xd6, 0x35, 0x0c, 0xc2, 0x8c, - 0x61, 0x24, 0x04, 0xaf, 0x7f, 0xd1, 0x57, 0x34, 0x89, 0x57, 0xdf, 0xf2, 0xbf, 0x32, 0x5e, 0x28, 0x22, 0x0d, 0x22, - 0xa4, 0x6e, 0xe2, 0x1b, 0x99, 0x26, 0x05, 0x14, 0x02, 0x8c, 0x02, 0x2a, 0xb1, 0xa1, 0xa9, 0xf8, 0x5b, 0x2d, 0x3e, - 0xf8, 0xa9, 0xe9, 0x78, 0x34, 0xae, 0x5b, 0x9d, 0x51, 0x41, 0x67, 0xa0, 0x47, 0xad, 0xa8, 0xa7, 0x41, 0x2b, 0xc1, - 0x34, 0xd2, 0xbc, 0x75, 0x0f, 0x81, 0x57, 0xa6, 0xc5, 0x3b, 0x0f, 0xe8, 0xe6, 0xdc, 0x07, 0x4f, 0x1e, 0xd3, 0x73, - 0x87, 0x9e, 0x5c, 0xb1, 0x93, 0xaa, 0x87, 0xda, 0x7b, 0x33, 0x42, 0x41, 0xbf, 0x8f, 0x29, 0xd0, 0x8d, 0xa0, 0xf6, - 0xae, 0xee, 0x3f, 0x94, 0xbb, 0x1c, 0xbe, 0xe3, 0x2c, 0x37, 0x80, 0xa5, 0x22, 0x6b, 0x05, 0x1e, 0x05, 0xa8, 0x4b, - 0x65, 0x08, 0x5b, 0xcc, 0xe1, 0x50, 0xd9, 0xad, 0x5a, 0x0d, 0x25, 0x39, 0x2e, 0x47, 0xe0, 0x10, 0xba, 0x2e, 0x07, - 0xe5, 0x68, 0x99, 0x55, 0xef, 0xf1, 0xb7, 0x66, 0x1d, 0x92, 0x6c, 0x1f, 0xeb, 0xc0, 0x2d, 0xeb, 0x30, 0xfd, 0x68, - 0x90, 0x02, 0xd0, 0x64, 0x23, 0x70, 0x09, 0xc0, 0x7b, 0xfb, 0x8f, 0x08, 0xb5, 0x32, 0xdd, 0xcb, 0x58, 0xa8, 0xef, - 0x1b, 0x49, 0x50, 0x42, 0x33, 0xa1, 0x72, 0x2c, 0x05, 0xef, 0x3c, 0xd2, 0x39, 0xa9, 0x33, 0xf1, 0x1e, 0xc4, 0x69, - 0xe1, 0x03, 0x7b, 0x0b, 0x82, 0x73, 0x16, 0xf4, 0x1e, 0x6f, 0xb3, 0x5a, 0x6a, 0xa3, 0x07, 0x0a, 0xe0, 0x77, 0x83, - 0x7b, 0x04, 0xf9, 0x6a, 0x0c, 0xd7, 0x4a, 0xde, 0x86, 0x7c, 0x58, 0xd0, 0x23, 0x32, 0xb0, 0xcf, 0x62, 0x18, 0xd3, - 0x23, 0x72, 0x6c, 0x9f, 0xa5, 0x1b, 0xc0, 0x81, 0xd4, 0xa3, 0x4a, 0x8f, 0xa0, 0x41, 0xbf, 0xda, 0x16, 0x59, 0x92, - 0xf5, 0x43, 0x69, 0x14, 0x31, 0x50, 0x25, 0x88, 0xa8, 0xc5, 0xbf, 0x1e, 0xcc, 0x75, 0x8f, 0xb9, 0x40, 0x98, 0x83, - 0x01, 0x07, 0x71, 0x1b, 0x84, 0xe6, 0x80, 0xd9, 0xdc, 0x45, 0x82, 0xde, 0x5b, 0xc3, 0xcc, 0x8e, 0xfe, 0x70, 0x2b, - 0xc1, 0x37, 0x59, 0x6b, 0xd4, 0x79, 0x71, 0x08, 0x04, 0xc1, 0x9b, 0x42, 0x55, 0x7b, 0xd5, 0x03, 0x1b, 0x6f, 0xd5, - 0x8f, 0xed, 0x76, 0x3c, 0x15, 0xee, 0xda, 0x2f, 0x28, 0x9c, 0x7c, 0x4a, 0xfe, 0xf5, 0xde, 0x64, 0x70, 0x60, 0x64, - 0xf8, 0xd2, 0xdb, 0xbf, 0xf0, 0xb5, 0x96, 0xee, 0x89, 0x41, 0x49, 0x1e, 0x1f, 0x29, 0xfa, 0xb7, 0x57, 0x56, 0x3e, - 0xb5, 0xd3, 0xbf, 0xdd, 0x9a, 0xf5, 0x79, 0x3c, 0x9a, 0x6c, 0xb7, 0xbd, 0xb8, 0xd2, 0x1e, 0x6b, 0x7a, 0x41, 0xa0, - 0x73, 0x3d, 0x39, 0x3c, 0x82, 0xa8, 0x08, 0xcd, 0xb8, 0x9b, 0x65, 0x43, 0x22, 0xe3, 0xc7, 0xe9, 0x2c, 0x1b, 0x82, - 0x1d, 0xee, 0x45, 0x25, 0x2e, 0x47, 0xad, 0x0d, 0x4e, 0xcf, 0x93, 0x10, 0x42, 0x39, 0x60, 0x65, 0x77, 0xea, 0xcf, - 0xbd, 0x32, 0x13, 0x52, 0x93, 0xd5, 0xed, 0x94, 0xee, 0x61, 0x9a, 0x1f, 0x98, 0x11, 0x1c, 0x70, 0x6f, 0x7f, 0xd5, - 0x1f, 0xc3, 0x24, 0xd3, 0xe4, 0x14, 0xc9, 0x2f, 0xd2, 0x53, 0x48, 0xda, 0xa1, 0xa7, 0x8a, 0x00, 0x4e, 0xa8, 0xfd, - 0x18, 0x7e, 0xc3, 0xb8, 0x7f, 0xdb, 0x7c, 0xed, 0xa6, 0x22, 0x7a, 0x42, 0xb1, 0x4c, 0x4d, 0x4e, 0x93, 0xac, 0x48, - 0x20, 0x6a, 0xa3, 0x6a, 0x46, 0xf4, 0x95, 0x8b, 0xf9, 0xa8, 0x08, 0x9f, 0x57, 0xeb, 0xff, 0x0c, 0xe1, 0x33, 0x0a, - 0x37, 0x80, 0xcb, 0x2b, 0xae, 0x2e, 0xc2, 0xa7, 0x4f, 0xe8, 0xc1, 0xe4, 0xeb, 0x23, 0x7a, 0x70, 0xf4, 0xd5, 0x53, - 0x02, 0xb0, 0x68, 0x57, 0x17, 0xe1, 0xd1, 0xd3, 0xa7, 0xf4, 0xe0, 0x9b, 0x6f, 0xe8, 0xc1, 0xe4, 0xab, 0xa3, 0x46, - 0xda, 0xe4, 0xe9, 0x37, 0xf4, 0xe0, 0xeb, 0x27, 0x8d, 0xb4, 0xa3, 0xf1, 0x53, 0x7a, 0xf0, 0xf7, 0xaf, 0x4d, 0xda, - 0xdf, 0x20, 0xdb, 0x37, 0x47, 0xf8, 0x9f, 0x49, 0x9b, 0x3c, 0xfd, 0x8a, 0x1e, 0x4c, 0xc6, 0x50, 0xc9, 0x53, 0x57, - 0xc9, 0x78, 0x02, 0x1f, 0x7f, 0x05, 0xff, 0xfd, 0x8d, 0x04, 0x0b, 0x5a, 0x49, 0x96, 0x0b, 0xd4, 0x9f, 0xa1, 0x88, - 0x13, 0x55, 0x13, 0x09, 0x0f, 0x31, 0xb3, 0xfa, 0x26, 0x0e, 0x03, 0xe2, 0xd2, 0xa1, 0x20, 0x7a, 0x30, 0x1e, 0x3d, - 0x25, 0x81, 0x0f, 0x4f, 0x77, 0xeb, 0x83, 0x8c, 0xe5, 0x62, 0x9e, 0x7d, 0x91, 0x9b, 0xd8, 0x0a, 0x1e, 0x80, 0xd5, - 0x47, 0x3f, 0x57, 0x25, 0xe7, 0xd9, 0x17, 0x95, 0xdc, 0xcd, 0xf5, 0x6b, 0x0b, 0x50, 0xde, 0x5f, 0xb5, 0xec, 0xb6, - 0x50, 0xa1, 0xd3, 0x5a, 0xa3, 0xcf, 0x3e, 0x62, 0xfa, 0x60, 0xe0, 0xdd, 0xb0, 0xff, 0xb1, 0x53, 0x4e, 0xeb, 0x1b, - 0x8d, 0x42, 0x8d, 0xca, 0x43, 0xc2, 0x4e, 0xa0, 0xe8, 0xc1, 0x00, 0x78, 0x02, 0x0f, 0xf7, 0xed, 0xdf, 0x2c, 0xe3, - 0x63, 0x47, 0x19, 0x3f, 0xa1, 0x0c, 0x01, 0x8d, 0x7a, 0x98, 0xdd, 0xf4, 0xb0, 0xd1, 0xad, 0x5e, 0xb2, 0x54, 0x27, - 0x53, 0xd3, 0x33, 0xd8, 0xd7, 0xba, 0x96, 0x07, 0x46, 0x14, 0x2d, 0x2f, 0x0f, 0x52, 0x3e, 0xab, 0xd8, 0x3f, 0x96, - 0xa8, 0xde, 0x8a, 0x1a, 0x6f, 0x64, 0x36, 0xab, 0xd8, 0x77, 0xe6, 0x0d, 0x70, 0x33, 0xec, 0x57, 0xf5, 0xe4, 0x07, - 0xce, 0xe0, 0xd2, 0xb6, 0x47, 0x99, 0x18, 0x01, 0x56, 0x40, 0x06, 0x0e, 0x3c, 0x00, 0x3a, 0xe8, 0x8f, 0xf6, 0x76, - 0xab, 0x52, 0x9a, 0x7d, 0xb6, 0x30, 0x80, 0x86, 0x79, 0x9b, 0xb8, 0xb2, 0x7f, 0x6b, 0xc8, 0x4b, 0x50, 0xb8, 0xd5, - 0x2c, 0x6f, 0xa7, 0x30, 0x84, 0x10, 0xfc, 0x61, 0xc9, 0x00, 0x70, 0x20, 0xc0, 0x60, 0xac, 0x65, 0x40, 0xcd, 0x96, - 0x8f, 0x36, 0x5c, 0xa9, 0x27, 0x81, 0x33, 0xb8, 0x94, 0x45, 0xc2, 0xdf, 0x6a, 0xb1, 0x3f, 0x5a, 0x3f, 0xfa, 0xbe, - 0x3d, 0x1e, 0xac, 0x7d, 0x8f, 0x8f, 0xf4, 0x67, 0x8d, 0xeb, 0xc0, 0xa6, 0xe5, 0x1b, 0x2f, 0x6a, 0x2b, 0xf1, 0x28, - 0x81, 0x37, 0x30, 0x11, 0x29, 0x0c, 0x52, 0x2d, 0x70, 0x0c, 0xca, 0x1b, 0x0b, 0xb1, 0x54, 0x5d, 0xdd, 0x60, 0x0b, - 0x22, 0x43, 0xf0, 0x70, 0xfb, 0x6d, 0xa9, 0x02, 0x47, 0xf5, 0xfb, 0x5c, 0xfa, 0x6e, 0x4f, 0xc6, 0x8e, 0x1c, 0xa7, - 0x7e, 0x2a, 0x1c, 0xfc, 0x37, 0xa9, 0x6b, 0x63, 0xb9, 0x92, 0x32, 0xcb, 0xb2, 0xb0, 0x93, 0x50, 0xcb, 0x3d, 0x2a, - 0x0f, 0x92, 0x2f, 0xe4, 0x10, 0xc9, 0x02, 0xa3, 0x50, 0x90, 0xe1, 0x84, 0x8a, 0xd1, 0x5a, 0x94, 0xcb, 0xec, 0xb2, - 0x0a, 0x37, 0x4a, 0xa1, 0xcc, 0x29, 0xfa, 0x76, 0x83, 0x03, 0x09, 0x89, 0xb2, 0xf2, 0x4d, 0xfc, 0x26, 0x44, 0xb0, - 0x3a, 0xae, 0x6d, 0xa1, 0xb8, 0xb7, 0x3f, 0x79, 0xda, 0xc5, 0x1f, 0x19, 0x17, 0x50, 0x17, 0x8b, 0x69, 0x38, 0xb1, - 0xb1, 0x6f, 0xdc, 0x17, 0x56, 0xd3, 0x03, 0x50, 0xdf, 0xa5, 0x12, 0x23, 0xa8, 0xaf, 0x8c, 0x7d, 0x6c, 0x8f, 0x31, - 0x39, 0x83, 0x58, 0xc3, 0x2a, 0x67, 0xa6, 0xfa, 0x46, 0xd8, 0x09, 0x00, 0x37, 0x42, 0x6b, 0x74, 0x64, 0x92, 0x2a, - 0xc4, 0xf3, 0x52, 0x85, 0x6f, 0xcd, 0x08, 0x1d, 0x83, 0x37, 0x95, 0x6d, 0x64, 0x26, 0x7d, 0xc1, 0xa0, 0x39, 0xb6, - 0x75, 0x14, 0x56, 0x5b, 0x59, 0x76, 0x02, 0x70, 0x03, 0xd9, 0xb1, 0xb9, 0x78, 0xce, 0xaa, 0x79, 0xb6, 0x88, 0x4c, - 0x50, 0xc0, 0xa5, 0xb0, 0x0c, 0xda, 0x9b, 0x3d, 0xb2, 0x1d, 0x87, 0xd0, 0x0d, 0xf7, 0x11, 0x8c, 0xa7, 0xdd, 0x14, - 0xac, 0x20, 0x1a, 0x21, 0x1e, 0x66, 0xcc, 0xe2, 0x7b, 0xa5, 0x29, 0x4f, 0x55, 0x4b, 0x20, 0x70, 0x14, 0x42, 0x5d, - 0xec, 0x1a, 0x25, 0xb8, 0x4c, 0x8d, 0x60, 0x06, 0x3b, 0x76, 0xa4, 0xb6, 0x4b, 0xce, 0xe9, 0x50, 0x4d, 0x69, 0xa9, - 0xa7, 0x54, 0xfb, 0x1a, 0x8a, 0x79, 0x89, 0x1e, 0x7a, 0xe0, 0x7a, 0xa0, 0x1d, 0xf2, 0x4a, 0x3a, 0x31, 0x11, 0x74, - 0x5a, 0x6d, 0xc2, 0xce, 0x8d, 0x74, 0xcb, 0x6a, 0xe4, 0x1d, 0x43, 0xb3, 0x23, 0x5e, 0xf8, 0x81, 0xba, 0x00, 0x22, - 0x64, 0x6f, 0x8b, 0xcc, 0x11, 0xcd, 0xb2, 0xf2, 0x25, 0x94, 0xc5, 0x11, 0x5b, 0x57, 0xc0, 0xb5, 0x14, 0x4c, 0x2e, - 0x79, 0xc4, 0x53, 0x44, 0x04, 0x3c, 0x55, 0xda, 0xf5, 0x9d, 0x96, 0x10, 0x9a, 0xa5, 0x40, 0xdc, 0x5c, 0x14, 0xe7, - 0xda, 0x06, 0xb2, 0x00, 0xfa, 0xf6, 0x63, 0x76, 0xed, 0x85, 0x83, 0xdd, 0x5c, 0x67, 0xe2, 0x39, 0xbf, 0xcc, 0x04, - 0x4f, 0x11, 0xec, 0xea, 0xce, 0x3c, 0x70, 0xc7, 0xb6, 0x81, 0xe5, 0xdb, 0xb7, 0xb0, 0x60, 0xca, 0x50, 0x2b, 0x25, - 0x32, 0x11, 0x09, 0xc8, 0xec, 0x33, 0x77, 0xaf, 0x33, 0xf1, 0x3a, 0xbe, 0x03, 0x6f, 0x8a, 0x06, 0x3f, 0x3d, 0xba, - 0xc0, 0x2f, 0x11, 0x49, 0x14, 0x62, 0xd8, 0x62, 0x44, 0x2c, 0x44, 0x8e, 0x1d, 0x13, 0xca, 0x95, 0xa0, 0xb5, 0x35, - 0x04, 0x5e, 0xfc, 0x69, 0xd5, 0xbd, 0xeb, 0x4c, 0x18, 0xfb, 0x8c, 0xeb, 0xf8, 0x8e, 0x95, 0x0a, 0xcc, 0x02, 0xe3, - 0xdc, 0xb7, 0xa5, 0x24, 0xd7, 0x99, 0x30, 0x02, 0x92, 0xeb, 0xf8, 0x8e, 0x36, 0x65, 0x1c, 0xda, 0x8a, 0xce, 0x8b, - 0xf3, 0xbb, 0x3b, 0xfc, 0x12, 0x43, 0xad, 0x8c, 0xfb, 0x7d, 0x90, 0x98, 0x49, 0xdb, 0x94, 0x99, 0x8c, 0xa4, 0x46, - 0x0b, 0xa9, 0x28, 0x1f, 0x4c, 0xc8, 0xee, 0x4a, 0xb5, 0x8c, 0xa8, 0xfd, 0x2a, 0x14, 0xb3, 0x71, 0x34, 0x21, 0x74, - 0xd2, 0xb1, 0xde, 0x4d, 0x6b, 0x21, 0xd3, 0xe8, 0x69, 0xe4, 0xf9, 0x74, 0x16, 0xac, 0x9a, 0x16, 0xc7, 0x8c, 0x4f, - 0x8b, 0xc1, 0x80, 0x68, 0x97, 0xc2, 0x0d, 0xd6, 0x03, 0xa6, 0x34, 0x2e, 0xde, 0x9a, 0x69, 0xf5, 0x4b, 0xa9, 0x42, - 0xd2, 0x7b, 0x06, 0x24, 0x99, 0x74, 0xc1, 0x6e, 0x41, 0xa2, 0xe8, 0xf9, 0xdf, 0xa9, 0x2d, 0xb8, 0xeb, 0xc1, 0xd8, - 0x8c, 0xee, 0xeb, 0x19, 0xff, 0xa1, 0xb6, 0x05, 0x51, 0x9f, 0x4a, 0xd6, 0xeb, 0x48, 0x54, 0x21, 0x17, 0xe1, 0x67, - 0x47, 0x43, 0x0c, 0x51, 0xed, 0xb1, 0x40, 0xac, 0xaf, 0x2f, 0x78, 0x81, 0xd3, 0xcf, 0xdc, 0xe5, 0x0a, 0xb6, 0x05, - 0xad, 0x0c, 0x8d, 0x7a, 0x13, 0xbf, 0x89, 0xec, 0x65, 0x41, 0x17, 0xf9, 0x1c, 0x85, 0xac, 0x79, 0x18, 0x56, 0xc3, - 0xf6, 0x20, 0x92, 0xc3, 0xf6, 0x24, 0x34, 0x1a, 0x03, 0x0b, 0x64, 0x87, 0x46, 0xe0, 0x22, 0xb4, 0xf2, 0xb7, 0x63, - 0x70, 0xe1, 0xb2, 0x88, 0x2c, 0x43, 0x1d, 0xbf, 0xa9, 0xdd, 0x04, 0xd5, 0x2b, 0x74, 0x9a, 0xc2, 0xaa, 0x94, 0x49, - 0x3e, 0xfc, 0x7a, 0x29, 0x0b, 0xcc, 0xe4, 0x75, 0xd9, 0xa3, 0xaf, 0xed, 0xf6, 0x0e, 0x4c, 0xc1, 0xba, 0x4f, 0xde, - 0xd7, 0x8f, 0x3b, 0x7b, 0x02, 0x46, 0xb1, 0x2a, 0x47, 0x53, 0x48, 0xa9, 0x7d, 0x50, 0xea, 0x8f, 0xe1, 0x52, 0x68, - 0x8e, 0xdd, 0x02, 0x26, 0x01, 0xfb, 0x0c, 0xa9, 0x1e, 0xd3, 0x8e, 0x7d, 0x8e, 0x36, 0xb0, 0x24, 0xe0, 0xf0, 0x8f, - 0x32, 0x59, 0xfb, 0x57, 0x77, 0x91, 0x36, 0x43, 0xb6, 0xcc, 0x17, 0xc0, 0xe7, 0xc3, 0xae, 0x8d, 0x4a, 0x94, 0x4d, - 0x44, 0x92, 0xc2, 0x96, 0xc7, 0x20, 0xed, 0x51, 0x4c, 0x57, 0x05, 0x4f, 0x32, 0x94, 0x52, 0x24, 0xda, 0x27, 0x38, - 0x87, 0x37, 0xb8, 0x1f, 0x55, 0x40, 0x78, 0x15, 0x72, 0x3a, 0x4a, 0xa9, 0xb6, 0x80, 0x51, 0xd4, 0x03, 0x44, 0x79, - 0x19, 0xc8, 0xf1, 0xb6, 0xdb, 0x09, 0x5d, 0xb1, 0xe5, 0x70, 0x42, 0x91, 0x94, 0x5c, 0x61, 0xb9, 0xd7, 0xa0, 0xf3, - 0xb8, 0x60, 0xbd, 0x17, 0x80, 0x45, 0x70, 0x0e, 0x7f, 0x63, 0x42, 0x6f, 0xe0, 0x6f, 0x4e, 0xe8, 0x6b, 0x16, 0x5e, - 0x0f, 0xaf, 0xc8, 0x61, 0x98, 0x0e, 0x26, 0x4a, 0x30, 0x76, 0xcf, 0x96, 0x65, 0xa8, 0x12, 0x57, 0x87, 0x97, 0xe4, - 0xf1, 0x25, 0xbd, 0xa3, 0xb7, 0xf4, 0x8c, 0xbe, 0x05, 0xc2, 0x7f, 0x7f, 0x3c, 0xe1, 0xc3, 0xc9, 0x93, 0x7e, 0xbf, - 0x77, 0xd1, 0xef, 0xf7, 0xce, 0x8d, 0x01, 0x85, 0xde, 0x45, 0x57, 0x35, 0xd5, 0xbf, 0xae, 0xeb, 0xc5, 0xf4, 0xad, - 0xda, 0xb8, 0x09, 0xcf, 0xf2, 0xf0, 0xfa, 0xf0, 0x9e, 0x0c, 0xf1, 0xf1, 0x32, 0x97, 0xb2, 0x08, 0xaf, 0x0e, 0xef, - 0x09, 0x7d, 0x7b, 0x02, 0x7a, 0x53, 0xac, 0xef, 0xed, 0xe3, 0x7b, 0x5d, 0x1b, 0xa1, 0x2f, 0xc2, 0x04, 0xb6, 0xc9, - 0x1d, 0xb3, 0x77, 0xed, 0xc9, 0x18, 0x62, 0x99, 0xdc, 0x7b, 0xe5, 0xdd, 0x3f, 0xbe, 0x23, 0x87, 0x77, 0xe0, 0x29, - 0x6a, 0xc9, 0xdf, 0x2c, 0xbc, 0x65, 0xad, 0x1a, 0x1e, 0xdf, 0xd3, 0xb3, 0x56, 0x23, 0x1e, 0xdf, 0x93, 0x28, 0xbc, - 0x65, 0x57, 0xf4, 0x8c, 0x5d, 0x13, 0x7a, 0xd1, 0xef, 0x9f, 0xf7, 0xfb, 0xb2, 0xdf, 0xff, 0x47, 0x1c, 0x86, 0xf1, - 0xb0, 0x20, 0x87, 0x92, 0xde, 0x1f, 0x4e, 0xf8, 0x57, 0x64, 0x16, 0xea, 0xe6, 0xab, 0x05, 0x67, 0x55, 0xde, 0x2a, - 0xd7, 0x3d, 0x05, 0x6b, 0x85, 0x7b, 0xa6, 0x9e, 0xde, 0xd2, 0x5b, 0x56, 0xd0, 0x33, 0x16, 0x93, 0xe8, 0x06, 0x5a, - 0x71, 0x31, 0x2b, 0xa2, 0x5b, 0x7a, 0xc6, 0xce, 0x67, 0x71, 0x74, 0x46, 0xdf, 0xb2, 0x7c, 0x38, 0x81, 0xbc, 0x67, - 0xc3, 0x5b, 0x72, 0xf8, 0x96, 0x44, 0xe1, 0x5b, 0xfd, 0xfb, 0x9e, 0x5e, 0xf1, 0xf0, 0x2d, 0xf5, 0xaa, 0x79, 0x4b, - 0x4c, 0xf5, 0x8d, 0xda, 0xdf, 0x92, 0xc8, 0x1f, 0xcc, 0xb7, 0xd6, 0x9e, 0xe6, 0x91, 0xa3, 0x8d, 0x69, 0x19, 0x82, - 0xbe, 0xb9, 0x0c, 0x6f, 0x09, 0x99, 0x36, 0xc7, 0x0e, 0x06, 0x74, 0xf6, 0x28, 0x4a, 0x08, 0xbd, 0xf5, 0x4b, 0xbd, - 0xc5, 0x31, 0x34, 0x23, 0xa4, 0xd2, 0xce, 0x30, 0x0d, 0xd7, 0xc1, 0x2b, 0x0d, 0xd6, 0x71, 0xd1, 0xef, 0x87, 0xeb, - 0x7e, 0x1f, 0x22, 0xdd, 0x17, 0x33, 0x13, 0xdb, 0xcd, 0x91, 0x4d, 0x7a, 0x0b, 0xda, 0xff, 0x57, 0x83, 0x01, 0x74, - 0xc6, 0x2b, 0x29, 0xbc, 0x1d, 0xbc, 0x7a, 0x7c, 0x4f, 0x54, 0x1d, 0x05, 0x15, 0x32, 0x2c, 0xe8, 0x6b, 0x9a, 0x01, - 0xe0, 0xd7, 0xab, 0xc1, 0x80, 0x44, 0xe6, 0x33, 0x32, 0x7d, 0x75, 0xfc, 0x76, 0x3a, 0x18, 0xbc, 0x32, 0xdb, 0xe4, - 0x2f, 0xb6, 0xa7, 0x14, 0x58, 0x7f, 0xe7, 0xfd, 0xfe, 0x5f, 0x27, 0x31, 0xb9, 0x28, 0x78, 0xfc, 0x71, 0xda, 0x6c, - 0xcb, 0x5f, 0x2e, 0xaa, 0xda, 0x79, 0xbf, 0xbf, 0xee, 0xf7, 0xcf, 0x00, 0xbb, 0x68, 0xe6, 0x7c, 0x3d, 0x41, 0xda, - 0x32, 0x77, 0x14, 0x49, 0x93, 0x1c, 0x1a, 0x43, 0xdb, 0x62, 0xd5, 0xb6, 0x59, 0x47, 0x06, 0x16, 0x47, 0xcd, 0x8a, - 0xe2, 0x9a, 0x44, 0x61, 0xef, 0x7c, 0xbb, 0x3d, 0x63, 0x8c, 0xc5, 0x04, 0xa4, 0x1f, 0xfe, 0xeb, 0xb3, 0xba, 0x11, - 0x43, 0x4c, 0x48, 0x64, 0x36, 0x37, 0x4b, 0x7b, 0x08, 0x44, 0x1c, 0x36, 0xfd, 0x7b, 0x73, 0x2f, 0x17, 0xb5, 0xe3, - 0x5b, 0x7f, 0x03, 0x10, 0x22, 0xc9, 0x42, 0x3e, 0xc3, 0x31, 0x28, 0x33, 0x00, 0x32, 0x8f, 0xd4, 0xcc, 0x4b, 0x00, - 0x01, 0x26, 0xdb, 0xed, 0x68, 0x3c, 0x9e, 0xd0, 0x82, 0x8d, 0xfe, 0xf6, 0xf4, 0x71, 0xf5, 0x38, 0x0c, 0x82, 0x41, - 0x46, 0x5a, 0x7a, 0x0a, 0xbb, 0x58, 0xab, 0x43, 0x30, 0x82, 0xd7, 0xec, 0xe3, 0x4d, 0xf6, 0xd9, 0xec, 0x23, 0x12, - 0xd6, 0x06, 0xe3, 0xc8, 0x45, 0xda, 0xd2, 0xdb, 0xed, 0x61, 0x30, 0xb9, 0x48, 0x3f, 0xc1, 0x76, 0xfa, 0xfc, 0x9b, - 0x07, 0xe3, 0x09, 0x07, 0xa3, 0xbb, 0x28, 0xe8, 0x33, 0x6d, 0xbb, 0xad, 0xfc, 0x4b, 0xe0, 0x1b, 0x4c, 0x05, 0x1d, - 0x9b, 0x65, 0xe1, 0x06, 0x15, 0x51, 0x47, 0xcb, 0xa0, 0xaa, 0x95, 0xed, 0x1c, 0x50, 0x4b, 0xac, 0xca, 0xc4, 0x2d, - 0x30, 0x0c, 0x19, 0xea, 0x72, 0x4f, 0xab, 0xdf, 0x78, 0x21, 0x0d, 0x7c, 0x86, 0x13, 0x11, 0x7a, 0xdc, 0x1a, 0xf7, - 0xb9, 0x35, 0xf1, 0x09, 0x6e, 0xad, 0x44, 0x12, 0x6b, 0x60, 0x49, 0xcd, 0xe5, 0x28, 0x61, 0x27, 0x25, 0xe3, 0xb3, - 0x32, 0x4a, 0x68, 0x0c, 0x0f, 0x92, 0x89, 0x99, 0x8c, 0x12, 0xb4, 0x4f, 0x74, 0x11, 0x06, 0xff, 0x04, 0xcc, 0x7e, - 0x9a, 0xc3, 0x5f, 0x49, 0xa6, 0xc9, 0x31, 0x04, 0x84, 0x38, 0x1e, 0xcf, 0xe2, 0x70, 0x4c, 0xa2, 0xe4, 0x04, 0x9e, - 0xe0, 0xbf, 0x22, 0x1c, 0x93, 0x5a, 0xdf, 0x61, 0xa4, 0xba, 0xdc, 0x26, 0x0c, 0xe0, 0xca, 0xc6, 0xb3, 0x49, 0x64, - 0xa5, 0xbb, 0xf2, 0xf1, 0x68, 0xfc, 0x94, 0x4c, 0xe3, 0x50, 0x0e, 0x12, 0x42, 0xc1, 0xbb, 0x37, 0x2c, 0x87, 0x89, - 0x86, 0x67, 0x03, 0x36, 0xaf, 0x74, 0x6c, 0x9e, 0x84, 0x13, 0x10, 0x86, 0x09, 0x39, 0xd6, 0x3d, 0x48, 0x29, 0xfa, - 0x3c, 0xc7, 0x7e, 0xea, 0x23, 0x08, 0xb3, 0xa3, 0x96, 0x8a, 0xaf, 0x00, 0xe8, 0x12, 0x07, 0x87, 0xda, 0x33, 0x5f, - 0xcc, 0xc2, 0xd2, 0xa3, 0x52, 0xa6, 0xba, 0x43, 0xd1, 0xa0, 0xfc, 0xa6, 0x41, 0x87, 0x82, 0x0c, 0x26, 0xb4, 0x3c, - 0x99, 0xf0, 0xaf, 0x20, 0x80, 0x47, 0x23, 0xe2, 0x97, 0xc2, 0x89, 0x81, 0xf0, 0x2a, 0xc8, 0x40, 0xa5, 0xb5, 0x6a, - 0xcc, 0xc8, 0x56, 0x7c, 0x00, 0x61, 0x52, 0x0e, 0x6e, 0xe5, 0x3a, 0x4f, 0x21, 0x2a, 0xd8, 0x3a, 0xaf, 0x0e, 0xae, - 0xc0, 0x92, 0x3d, 0xae, 0x20, 0x4e, 0xd8, 0x7a, 0x05, 0xd8, 0xb9, 0x8f, 0x36, 0x65, 0x7d, 0xa0, 0xbe, 0x3b, 0xc0, - 0x96, 0xc3, 0xab, 0x4a, 0x1e, 0x4c, 0xc6, 0xe3, 0xf1, 0xe8, 0x77, 0x38, 0x3a, 0x80, 0xd0, 0x92, 0xc8, 0xf0, 0xc9, - 0x00, 0x8d, 0xbb, 0xae, 0xb8, 0x37, 0x2e, 0x14, 0x65, 0xa5, 0x93, 0x09, 0x01, 0xf1, 0xb3, 0xe9, 0x1b, 0xec, 0x2b, - 0xae, 0xe3, 0x9f, 0xec, 0x7e, 0x62, 0x56, 0xb4, 0x5a, 0xa9, 0xa3, 0x77, 0x6f, 0xcf, 0x5e, 0x7d, 0x78, 0xf5, 0xcb, - 0x8b, 0xf3, 0x57, 0x6f, 0x5e, 0xbe, 0x7a, 0xf3, 0xea, 0xc3, 0xbf, 0x1e, 0x60, 0xb0, 0x7d, 0x5b, 0x11, 0x3b, 0xf6, - 0xde, 0x3d, 0xc6, 0xab, 0xc5, 0x17, 0xce, 0x1e, 0xb9, 0x5b, 0x2c, 0xc0, 0x26, 0x18, 0x6e, 0x41, 0x50, 0xcd, 0x68, - 0x54, 0xfa, 0x9e, 0x80, 0x8c, 0x46, 0x85, 0x6c, 0x3c, 0xac, 0xd8, 0x0a, 0xb9, 0x78, 0xc7, 0x70, 0xf0, 0x91, 0xfd, - 0xad, 0x38, 0x13, 0x6e, 0x47, 0x5b, 0xb3, 0x22, 0xe0, 0xf3, 0xb5, 0x16, 0x95, 0xc7, 0x85, 0xa8, 0xbd, 0x6d, 0x9f, - 0x43, 0x42, 0x3d, 0x22, 0xd7, 0xc1, 0xfb, 0x36, 0xc8, 0x1e, 0x1f, 0x79, 0x4f, 0xca, 0x33, 0xd4, 0xe7, 0x68, 0xf8, - 0xa8, 0xf1, 0x8c, 0x4e, 0xcc, 0xb5, 0xd1, 0xa1, 0x9e, 0x17, 0xb0, 0xbf, 0x95, 0x18, 0x1b, 0xa2, 0x3d, 0xa4, 0x88, - 0xf5, 0xe1, 0x74, 0xbf, 0xbb, 0x37, 0xa3, 0xef, 0xe0, 0xf8, 0x51, 0xaa, 0x09, 0xa4, 0x45, 0x81, 0xd2, 0x95, 0x21, - 0xb7, 0x3d, 0x0b, 0x0b, 0xf3, 0x33, 0x6c, 0x10, 0x40, 0x7b, 0xd9, 0xb1, 0x24, 0xd0, 0x2c, 0x5e, 0xeb, 0xfa, 0xe7, - 0xe5, 0xcb, 0x44, 0x3b, 0x5f, 0x7c, 0x07, 0x21, 0x86, 0xfd, 0x2b, 0x42, 0x63, 0xc2, 0xdd, 0x24, 0xbb, 0x4b, 0x8b, - 0xb9, 0x57, 0x5d, 0xc7, 0x78, 0xdc, 0xed, 0xb9, 0x52, 0x34, 0x6f, 0x5d, 0x60, 0x0f, 0xd4, 0xbc, 0x8e, 0x97, 0x2c, - 0x04, 0x6c, 0xc6, 0x43, 0xbb, 0x48, 0x9c, 0xdf, 0x3b, 0x9d, 0x90, 0xc3, 0xa3, 0x29, 0x1f, 0xb2, 0x92, 0x8a, 0x01, - 0x2b, 0xeb, 0x1d, 0x6a, 0xce, 0xdb, 0x84, 0x5c, 0xec, 0xd2, 0x70, 0x31, 0xe4, 0x0f, 0x5d, 0x92, 0x3e, 0xf0, 0x86, - 0x43, 0xb5, 0x6d, 0x2e, 0x86, 0x34, 0xe5, 0x74, 0x97, 0xca, 0x80, 0x10, 0xe9, 0x3a, 0xae, 0x48, 0xad, 0x8f, 0xaa, - 0xd4, 0x49, 0x3a, 0x6e, 0xb2, 0xcd, 0x27, 0x2e, 0xd9, 0xea, 0x76, 0xed, 0x5f, 0xab, 0xdb, 0x17, 0x66, 0x20, 0x7f, - 0x7f, 0x20, 0xaa, 0x89, 0x81, 0xe8, 0x02, 0x2a, 0xf8, 0x07, 0x78, 0x79, 0xf2, 0x48, 0x2b, 0x40, 0xf7, 0x9d, 0x1d, - 0x5d, 0x7b, 0xbc, 0x31, 0x8b, 0xad, 0x25, 0xce, 0x59, 0xe5, 0x3b, 0xcb, 0xab, 0xb2, 0x15, 0xba, 0x8e, 0x60, 0xbf, - 0x85, 0x1d, 0x7d, 0xf7, 0xb6, 0x01, 0x10, 0xa5, 0xb0, 0x72, 0x67, 0xbf, 0xf0, 0xce, 0x7e, 0x61, 0xcf, 0x7e, 0xbb, - 0x09, 0x94, 0x0f, 0x2b, 0xb4, 0xec, 0xa5, 0x14, 0x95, 0x69, 0xf2, 0xb8, 0xa9, 0xcb, 0x42, 0x5a, 0xcc, 0x0f, 0x2d, - 0xed, 0x7a, 0x32, 0xa6, 0x12, 0xd5, 0x23, 0xdf, 0x63, 0xab, 0x0e, 0x4b, 0xf2, 0xf0, 0x3d, 0xf3, 0x7f, 0xf6, 0x06, - 0xb9, 0xef, 0x6e, 0xf7, 0x7f, 0x73, 0xa1, 0x83, 0xdb, 0x5a, 0x2a, 0x3c, 0x75, 0x75, 0x5c, 0xe0, 0x5d, 0x2d, 0x7d, - 0xf8, 0xae, 0xf6, 0x2e, 0xd3, 0xcb, 0xae, 0x02, 0xd4, 0x20, 0xb1, 0xbe, 0xe6, 0x45, 0x96, 0xd4, 0x56, 0xa1, 0xf1, - 0x96, 0x43, 0x68, 0x0f, 0xef, 0xe0, 0x02, 0x39, 0x2c, 0x21, 0xf4, 0x63, 0x65, 0x04, 0x80, 0x3e, 0x8b, 0xfd, 0x96, - 0x87, 0x19, 0x19, 0xf8, 0x12, 0xbf, 0x52, 0xfa, 0xe2, 0xe2, 0xc3, 0x9d, 0xcc, 0x04, 0xbd, 0x4a, 0x6c, 0x76, 0x29, - 0xdb, 0x31, 0x3f, 0xfc, 0x2f, 0x30, 0x1a, 0x84, 0xd7, 0x96, 0xec, 0x50, 0x74, 0xcc, 0x72, 0x05, 0x47, 0x6d, 0xe9, - 0xca, 0x2c, 0x5b, 0xd7, 0xcf, 0x6a, 0x98, 0xe9, 0x33, 0xe5, 0x2d, 0xc8, 0xbe, 0x90, 0xbb, 0x9f, 0xea, 0x8a, 0x05, - 0x39, 0x99, 0x8c, 0xa7, 0x44, 0x0c, 0x06, 0xad, 0xe4, 0x63, 0x4c, 0x1e, 0x0e, 0x77, 0x98, 0x4b, 0xa1, 0xfb, 0xe1, - 0xf5, 0x01, 0xea, 0x6b, 0x6c, 0x49, 0xb2, 0xa9, 0xd8, 0x9f, 0x60, 0x16, 0x0b, 0xc4, 0xd1, 0xc1, 0x2f, 0xce, 0x17, - 0x00, 0xb2, 0x0c, 0xcb, 0x4c, 0x0b, 0x8b, 0xca, 0x54, 0xf9, 0xc8, 0x16, 0x4c, 0x1e, 0x8f, 0x67, 0x7e, 0xcf, 0x1d, - 0x83, 0x43, 0x48, 0x34, 0xb1, 0xc6, 0x2f, 0x7e, 0x16, 0x8c, 0xe3, 0x50, 0x9e, 0xc8, 0xc6, 0x77, 0x25, 0x89, 0xc6, - 0xc6, 0x54, 0x59, 0x5f, 0x25, 0xaa, 0x61, 0x42, 0x1e, 0x17, 0xe4, 0xb0, 0xa0, 0x4b, 0x7f, 0x2c, 0x31, 0xfd, 0x30, - 0x3e, 0x9c, 0x8c, 0xc9, 0xe3, 0xf8, 0xf1, 0xc4, 0xc0, 0x0d, 0xfb, 0x39, 0xf2, 0xe1, 0x92, 0x1c, 0x36, 0xab, 0x04, - 0x53, 0x54, 0xd3, 0x33, 0xbf, 0x92, 0x64, 0xb0, 0x1c, 0xa4, 0x8f, 0x5b, 0x79, 0xb1, 0x56, 0x3d, 0xde, 0xeb, 0x63, - 0x3e, 0x25, 0xa2, 0x71, 0x63, 0x58, 0xd3, 0xeb, 0xf8, 0x0f, 0x59, 0x44, 0xa5, 0x04, 0x44, 0x42, 0x50, 0x6f, 0x67, - 0x97, 0x59, 0x12, 0x8b, 0x34, 0x4a, 0x6b, 0x42, 0xd3, 0x13, 0x36, 0x19, 0xcf, 0x52, 0x96, 0x1e, 0x4f, 0x9e, 0xce, - 0x26, 0x4f, 0xa3, 0xa3, 0x71, 0x94, 0x0e, 0x06, 0x90, 0x7c, 0x34, 0x06, 0x17, 0x3b, 0xf8, 0xcd, 0x8e, 0x60, 0xe8, - 0x4e, 0x90, 0x25, 0x2c, 0xa0, 0x69, 0x9f, 0xd7, 0x24, 0x3d, 0x9c, 0x97, 0xaa, 0x27, 0xf1, 0x1d, 0x5d, 0x7b, 0x0e, - 0x2e, 0x7e, 0x0b, 0x2f, 0x5d, 0x0b, 0x2f, 0x77, 0x5b, 0x28, 0x4c, 0xdc, 0x14, 0xf9, 0xff, 0xe3, 0x86, 0xb1, 0xef, - 0x2e, 0x61, 0x16, 0xd7, 0x4d, 0x36, 0x5a, 0x15, 0xb2, 0x92, 0x70, 0x9b, 0x50, 0xa2, 0xb0, 0x51, 0xbc, 0x5a, 0xe5, - 0xda, 0x45, 0x6c, 0x5e, 0x51, 0x00, 0x77, 0x81, 0x38, 0xc5, 0xc0, 0x42, 0x1b, 0x03, 0xb9, 0xbf, 0x78, 0x21, 0x99, - 0x55, 0xfb, 0x98, 0x7b, 0xe4, 0x1f, 0x21, 0x18, 0xa3, 0x8a, 0x93, 0xf1, 0x4c, 0x61, 0x5d, 0x7c, 0x4a, 0xde, 0xfb, - 0x6f, 0x1c, 0x45, 0xf6, 0x68, 0x06, 0x3d, 0x41, 0xe4, 0x3c, 0xe2, 0xec, 0xc9, 0xe4, 0x65, 0xe0, 0x7e, 0x06, 0x2b, - 0xfd, 0x75, 0xb7, 0x19, 0x6b, 0xdb, 0xa3, 0x7b, 0x61, 0x84, 0xa2, 0x7f, 0xe1, 0x3b, 0x53, 0x2f, 0xe0, 0x12, 0xaa, - 0x81, 0x5d, 0x5f, 0x5d, 0xf1, 0x12, 0x40, 0x84, 0x32, 0xd1, 0xef, 0xf7, 0xfe, 0x30, 0xd0, 0xa4, 0x25, 0x2f, 0x5e, - 0x67, 0xc2, 0x3a, 0xe3, 0x40, 0x53, 0x81, 0xfa, 0x7f, 0xac, 0xec, 0x33, 0x1d, 0x93, 0x99, 0xff, 0x38, 0x9c, 0x90, - 0xa8, 0xf9, 0x9a, 0x7c, 0xe2, 0x34, 0xfd, 0xc4, 0x15, 0xed, 0x3f, 0x90, 0x99, 0x1b, 0x0e, 0x19, 0xea, 0x2f, 0x1d, - 0xf3, 0x64, 0xf4, 0x3a, 0x31, 0x3b, 0x11, 0xac, 0x9a, 0x41, 0x14, 0xf6, 0x02, 0x1e, 0xd4, 0xb5, 0x2c, 0x9e, 0xc2, - 0xec, 0x83, 0x1a, 0x51, 0x1c, 0xb3, 0xf1, 0x2c, 0x94, 0xe1, 0x04, 0xec, 0x7b, 0x27, 0x63, 0xb8, 0x0f, 0xc8, 0xf0, - 0x63, 0x15, 0x62, 0xe7, 0x20, 0xed, 0x63, 0x85, 0x8a, 0x09, 0x80, 0x08, 0x84, 0xbc, 0xfd, 0xbe, 0x54, 0x49, 0xf8, - 0xba, 0xc4, 0x94, 0x42, 0x7d, 0xf0, 0x9f, 0x48, 0xd5, 0x1d, 0xd3, 0xaf, 0xd6, 0x8f, 0x3f, 0x13, 0x8a, 0x4f, 0x77, - 0x29, 0xf1, 0x1d, 0x04, 0x77, 0x96, 0xa0, 0x83, 0xa8, 0xd0, 0x8c, 0xed, 0x61, 0x7e, 0x57, 0xec, 0xe7, 0x77, 0xc5, - 0xff, 0x3b, 0x7e, 0x57, 0x3c, 0xc4, 0x18, 0x56, 0x16, 0x1a, 0x7e, 0x16, 0x8c, 0x83, 0xe8, 0x3f, 0xe7, 0x13, 0xf7, - 0xf2, 0xd4, 0xd7, 0x99, 0x98, 0xee, 0x61, 0x9a, 0x7d, 0x82, 0x82, 0xb0, 0x8a, 0xbb, 0xf4, 0x64, 0x5d, 0xd9, 0x5b, - 0x2b, 0x19, 0x62, 0x9e, 0x07, 0x58, 0xa3, 0xb0, 0xf2, 0x80, 0xee, 0x51, 0xb5, 0x41, 0x9c, 0x08, 0x1e, 0xc6, 0xcc, - 0x4a, 0xdf, 0xb7, 0x5b, 0xa3, 0xc2, 0x7c, 0x90, 0x8b, 0x82, 0xec, 0xe6, 0xe3, 0xd9, 0x38, 0x0a, 0xb1, 0x01, 0xff, - 0x31, 0x63, 0xd5, 0x90, 0xcd, 0x77, 0x32, 0x52, 0x3b, 0x26, 0x4f, 0x93, 0x5d, 0xd2, 0x3b, 0xe0, 0x1d, 0xf2, 0x73, - 0x70, 0x67, 0x93, 0x86, 0xdf, 0x92, 0x57, 0x71, 0x91, 0x55, 0xcb, 0xeb, 0x2c, 0x41, 0xa6, 0x0b, 0x5e, 0x7c, 0x36, - 0xd3, 0xe5, 0x7d, 0xac, 0x0f, 0x18, 0x4f, 0x29, 0x5e, 0x37, 0x44, 0xe9, 0xeb, 0x96, 0x67, 0x85, 0xba, 0x3c, 0xa9, - 0x98, 0xed, 0x59, 0x09, 0x4e, 0xa7, 0x60, 0x82, 0xaf, 0x7f, 0xba, 0xde, 0x27, 0x80, 0x0b, 0x0a, 0x35, 0xa7, 0x85, - 0x5c, 0x19, 0x2c, 0x27, 0x0b, 0xdd, 0x09, 0x98, 0xa1, 0x52, 0xe0, 0x05, 0x0a, 0xfe, 0xa2, 0x81, 0x11, 0x7d, 0xe9, - 0x7e, 0x93, 0x81, 0x41, 0xba, 0x34, 0x27, 0xc2, 0xd8, 0x71, 0x3b, 0x45, 0xda, 0x8a, 0x72, 0xc6, 0xd9, 0x7b, 0x75, - 0xa5, 0x00, 0x03, 0xbc, 0xcd, 0x6d, 0x74, 0x91, 0xa0, 0xd7, 0x82, 0xd2, 0x79, 0x03, 0x77, 0xb3, 0x8c, 0x8c, 0x70, - 0xf1, 0x71, 0xe5, 0xb1, 0xe0, 0x9e, 0xfd, 0x42, 0x2c, 0x8d, 0x66, 0x1a, 0x8c, 0xd9, 0xbc, 0x60, 0x81, 0x42, 0x05, - 0x0a, 0x2c, 0x67, 0xda, 0xd2, 0xb4, 0x1a, 0xf2, 0xc3, 0x23, 0xb4, 0x36, 0xad, 0x06, 0xfc, 0xf0, 0xa8, 0x8e, 0xb2, - 0x63, 0xc8, 0x72, 0xe2, 0x67, 0x50, 0xaf, 0xeb, 0xc8, 0xa4, 0x98, 0xec, 0x7e, 0x7d, 0xa9, 0x3f, 0xaa, 0x1b, 0x70, - 0xfd, 0x00, 0x04, 0xb0, 0x01, 0x38, 0x04, 0xaa, 0xc1, 0xd2, 0x88, 0x60, 0x51, 0xa6, 0xd0, 0xbe, 0x86, 0xde, 0x1b, - 0x0d, 0xff, 0x05, 0xee, 0x22, 0x72, 0xe5, 0x7f, 0x82, 0xc0, 0x5f, 0x51, 0xa6, 0x95, 0x29, 0xfe, 0x27, 0x5a, 0xbd, - 0x42, 0x39, 0x6b, 0x5a, 0xf3, 0x41, 0xb4, 0x26, 0x42, 0x35, 0x63, 0x08, 0xfe, 0xad, 0x2c, 0xd3, 0x96, 0xaa, 0x4a, - 0x7d, 0x68, 0xbc, 0xd6, 0x0a, 0x67, 0xf9, 0x38, 0xf2, 0x5e, 0x63, 0xe8, 0xd8, 0xc4, 0x59, 0xca, 0xa9, 0xd4, 0xd9, - 0x9b, 0x43, 0x19, 0x39, 0xc0, 0xe9, 0x84, 0x8d, 0xa7, 0xc9, 0xb1, 0x9c, 0x26, 0x0e, 0x32, 0x3f, 0x67, 0x18, 0x59, - 0xd5, 0x80, 0xb0, 0x28, 0x1b, 0x4a, 0x5b, 0x80, 0x49, 0x4e, 0x08, 0x99, 0x62, 0x28, 0x8a, 0x7c, 0xa4, 0xfb, 0x61, - 0xbd, 0x59, 0xdd, 0x17, 0xef, 0x34, 0xc0, 0x69, 0x98, 0x40, 0x20, 0xf0, 0x22, 0xbe, 0xcd, 0xc4, 0x15, 0x78, 0x0c, - 0x0f, 0xe0, 0x4b, 0x70, 0x93, 0x4b, 0xd9, 0xaf, 0x55, 0x98, 0xe3, 0xda, 0x02, 0x06, 0x0d, 0x56, 0x0f, 0xa2, 0xc3, - 0xa5, 0xb4, 0xd9, 0x55, 0x80, 0xd8, 0x98, 0x42, 0x2c, 0x0b, 0xb6, 0xb6, 0xec, 0xd9, 0x4f, 0xaa, 0x69, 0x68, 0x9d, - 0x70, 0x2a, 0xae, 0x72, 0x88, 0xa2, 0x32, 0x88, 0xc1, 0x1d, 0xc9, 0xe3, 0xf3, 0x1e, 0x89, 0xf0, 0x92, 0x80, 0x5b, - 0x59, 0x2c, 0xc3, 0x15, 0x5d, 0x8e, 0xee, 0xe8, 0x7a, 0x74, 0x4b, 0xc7, 0x74, 0xf2, 0xf7, 0x31, 0x58, 0x64, 0xeb, - 0xd4, 0x7b, 0xba, 0x1e, 0x2d, 0xe9, 0x37, 0x63, 0x7a, 0xf4, 0x37, 0x30, 0xe1, 0xc3, 0xc3, 0x84, 0x5e, 0x82, 0x63, - 0x17, 0xa9, 0xd1, 0x53, 0xd3, 0x37, 0x38, 0xac, 0x46, 0xf9, 0x90, 0x8f, 0x72, 0xca, 0x47, 0xc5, 0xb0, 0x1a, 0x81, - 0xa7, 0x63, 0x35, 0xe4, 0xa3, 0x8a, 0xf2, 0xd1, 0xc5, 0xb0, 0x1a, 0x5d, 0x90, 0x66, 0xd3, 0x5f, 0x55, 0xfc, 0xba, - 0x64, 0x29, 0x6c, 0x0b, 0x58, 0xbe, 0x9e, 0x57, 0x54, 0xea, 0xaf, 0x6a, 0x73, 0x32, 0x5b, 0xce, 0xde, 0x5e, 0x77, - 0x39, 0xb1, 0x78, 0xdc, 0x36, 0x1d, 0xae, 0xbe, 0x9c, 0xa8, 0x93, 0x5e, 0x21, 0x3f, 0x8c, 0xa7, 0x42, 0x9d, 0x43, - 0x60, 0x26, 0x31, 0x0b, 0x63, 0x86, 0xcd, 0xd4, 0x69, 0xa0, 0xc0, 0xc9, 0x46, 0x9e, 0x8b, 0x62, 0x36, 0xca, 0x29, - 0xbc, 0x8f, 0x09, 0x89, 0x04, 0x9c, 0x55, 0x27, 0xd5, 0xa8, 0x80, 0x98, 0x23, 0x2c, 0xc4, 0x47, 0xe8, 0x97, 0xfa, - 0xc8, 0x43, 0x02, 0xcf, 0xb0, 0xaf, 0xc5, 0x20, 0x86, 0x23, 0xde, 0x56, 0x56, 0xcd, 0xc2, 0x04, 0x2a, 0xab, 0x86, - 0xa5, 0xa9, 0xac, 0xa0, 0xd9, 0xa8, 0xf2, 0x2b, 0xab, 0x70, 0x8c, 0x12, 0x42, 0xa2, 0x52, 0x57, 0x06, 0xea, 0x93, - 0x84, 0x85, 0xa5, 0xae, 0xec, 0x42, 0x7d, 0x74, 0xe1, 0x57, 0x76, 0x01, 0x2e, 0xa4, 0x83, 0xc4, 0xbf, 0x4a, 0xe5, - 0x69, 0xfb, 0x3a, 0xd8, 0x58, 0x55, 0x74, 0xc3, 0xef, 0xaa, 0x22, 0x8e, 0x4a, 0xea, 0x62, 0x40, 0xe3, 0xc2, 0x88, - 0x24, 0xd5, 0x6b, 0x14, 0xfc, 0x21, 0x41, 0x54, 0x1a, 0x83, 0x57, 0x67, 0xd2, 0xb5, 0x52, 0x2b, 0x2a, 0x06, 0xe5, - 0xa0, 0x80, 0xfb, 0x53, 0xde, 0x5a, 0x48, 0x3f, 0x41, 0x44, 0x65, 0x28, 0x6f, 0xf0, 0x4f, 0x0c, 0x9e, 0xcc, 0x56, - 0x69, 0x98, 0x8c, 0xee, 0x69, 0x3c, 0x5a, 0x22, 0x1c, 0x0c, 0x5b, 0xa7, 0x0a, 0x6f, 0xfd, 0x12, 0xd2, 0xef, 0x68, - 0x3c, 0xba, 0xa5, 0xa9, 0xb5, 0x39, 0x35, 0x50, 0x57, 0xbd, 0x31, 0xbd, 0x8b, 0xe0, 0xf5, 0x7d, 0xb4, 0xa4, 0xb0, - 0x95, 0x4e, 0xf3, 0xec, 0x4a, 0x44, 0x29, 0x45, 0x04, 0xc2, 0x35, 0x22, 0x07, 0x2e, 0x35, 0xda, 0xe0, 0x7a, 0x00, - 0x65, 0x68, 0xb8, 0xc0, 0xe5, 0x20, 0x1e, 0x2d, 0x3d, 0x32, 0xb5, 0xd4, 0x17, 0x59, 0x84, 0x8f, 0x76, 0x36, 0x5a, - 0x8a, 0x67, 0xc4, 0xc2, 0xb8, 0x82, 0x21, 0xd4, 0x85, 0x95, 0xa6, 0x20, 0xe9, 0x02, 0x47, 0xf6, 0xc2, 0xb8, 0x0a, - 0x37, 0x60, 0x5a, 0x74, 0x0f, 0xe6, 0x51, 0xa0, 0x70, 0x70, 0x09, 0xd2, 0x4f, 0x28, 0xdb, 0x39, 0x4a, 0x93, 0xc3, - 0x9b, 0xa0, 0x74, 0x67, 0x82, 0x90, 0x76, 0x75, 0x93, 0x2d, 0xe9, 0x1b, 0x6c, 0xef, 0xd0, 0xa9, 0xa8, 0xa0, 0xfa, - 0xdc, 0x82, 0xc9, 0x92, 0x0d, 0xc2, 0x96, 0x30, 0x3d, 0xd3, 0x6b, 0xc0, 0x9e, 0x3e, 0x3c, 0xda, 0x99, 0xef, 0x62, - 0xf6, 0xe6, 0xb0, 0x8c, 0xc6, 0xca, 0x82, 0x37, 0xb7, 0xc4, 0x6e, 0xc9, 0xc6, 0xd3, 0xe5, 0x71, 0x39, 0x5d, 0x22, - 0xb1, 0x33, 0x74, 0x8b, 0xf1, 0xf9, 0x72, 0x41, 0x13, 0x3c, 0xdb, 0x58, 0x35, 0x5f, 0x1a, 0xb4, 0x94, 0x94, 0xe1, - 0x7a, 0x5b, 0xa2, 0xff, 0xbf, 0xba, 0xf8, 0xa5, 0x00, 0x2f, 0xc1, 0x58, 0x00, 0x08, 0xf7, 0x60, 0x5a, 0x90, 0xda, - 0x28, 0x1b, 0xcb, 0x34, 0x4c, 0x71, 0x11, 0x98, 0x94, 0x7e, 0x3f, 0xcc, 0x59, 0x4a, 0x3c, 0xe8, 0x50, 0x77, 0x6a, - 0xa7, 0xbe, 0x10, 0x04, 0x78, 0x24, 0x75, 0x8e, 0x4d, 0xfe, 0x3e, 0x9e, 0x05, 0x6a, 0x20, 0x82, 0x28, 0x3b, 0xc6, - 0x47, 0x0c, 0x5c, 0x14, 0xe9, 0xb8, 0x9d, 0xae, 0x88, 0xcb, 0xdd, 0x63, 0x16, 0xe2, 0x24, 0x61, 0xae, 0x59, 0x36, - 0x64, 0x55, 0x84, 0x09, 0xba, 0x30, 0x30, 0xcb, 0x1b, 0xb2, 0xea, 0xf0, 0x08, 0x22, 0xb5, 0xda, 0x32, 0x56, 0x5d, - 0x65, 0x7c, 0x03, 0x40, 0xd6, 0x8c, 0xb1, 0xa3, 0xbf, 0x8d, 0x67, 0xea, 0x9b, 0x28, 0xe4, 0x27, 0x47, 0x7f, 0x83, - 0xe4, 0xe3, 0x6f, 0x90, 0x99, 0x83, 0xe4, 0x46, 0x41, 0x57, 0xcd, 0x59, 0xd7, 0x50, 0x9a, 0xb8, 0xf6, 0x4a, 0xbd, - 0xf6, 0xa4, 0x59, 0x7b, 0x05, 0xba, 0x53, 0x1b, 0xde, 0x43, 0xd9, 0xce, 0x82, 0x09, 0x3a, 0x9a, 0xdd, 0x81, 0x0e, - 0xde, 0x29, 0x82, 0x5e, 0x24, 0xa1, 0xf1, 0x08, 0x55, 0x46, 0xbd, 0x18, 0x0f, 0xaa, 0x93, 0x75, 0xc9, 0x3c, 0x03, - 0xe6, 0xd8, 0x9e, 0x43, 0x62, 0x98, 0xab, 0x83, 0x3a, 0x65, 0xe5, 0x30, 0xc7, 0x03, 0x78, 0xcd, 0xe4, 0x50, 0x0c, - 0x72, 0x8d, 0xf2, 0x7d, 0xc9, 0x8a, 0x61, 0x39, 0xc8, 0x35, 0x37, 0x33, 0x6d, 0xc6, 0xa6, 0x4d, 0x74, 0x78, 0xe6, - 0x15, 0x3b, 0x59, 0xf5, 0x80, 0x8f, 0x05, 0x4f, 0x66, 0xdf, 0xf3, 0xf1, 0x35, 0x70, 0x32, 0x9b, 0xbb, 0x68, 0x49, - 0xef, 0xa3, 0x94, 0xde, 0x46, 0x6b, 0xba, 0x8c, 0x2e, 0x8d, 0x89, 0x71, 0x52, 0xc3, 0x39, 0x00, 0xad, 0x02, 0x48, - 0x3c, 0xf5, 0xeb, 0x3d, 0x4f, 0xaa, 0x70, 0x49, 0x53, 0x70, 0x1b, 0xf6, 0xed, 0x33, 0xaf, 0x7d, 0x89, 0xd4, 0x06, - 0x31, 0xd6, 0xac, 0xa1, 0xe2, 0xc6, 0x5b, 0xf7, 0x91, 0xa8, 0x61, 0xe7, 0xba, 0xd8, 0x44, 0xd5, 0x70, 0x32, 0x2d, - 0x01, 0xb1, 0xb5, 0x1c, 0x0e, 0xdd, 0x11, 0xb2, 0x7b, 0xfc, 0xe8, 0x40, 0xcf, 0x3d, 0x69, 0xb1, 0x6d, 0x5b, 0xfe, - 0xc0, 0x10, 0xa6, 0xf4, 0xd3, 0x47, 0x3e, 0x20, 0x56, 0x5c, 0xc1, 0xd9, 0x08, 0xd4, 0xd1, 0x0a, 0x9d, 0x7e, 0xad, - 0xc2, 0x42, 0x1f, 0xe0, 0x9b, 0xbb, 0x28, 0xa1, 0xf7, 0x51, 0xee, 0x91, 0xb5, 0x65, 0xcd, 0xe4, 0xf4, 0x3c, 0x0b, - 0x79, 0xfb, 0x40, 0x2f, 0x17, 0x00, 0xa2, 0x35, 0x88, 0x7d, 0xa9, 0xeb, 0x11, 0x38, 0x0d, 0xa1, 0x49, 0x68, 0x04, - 0x57, 0x15, 0x84, 0x11, 0x70, 0x25, 0xe1, 0x6f, 0x30, 0x51, 0x81, 0x2f, 0xc0, 0x45, 0x26, 0x4d, 0x73, 0x1e, 0xd4, - 0xfe, 0x48, 0x9e, 0x15, 0x6d, 0x6f, 0x57, 0x18, 0x4d, 0x30, 0xf6, 0x44, 0xfb, 0x3c, 0x52, 0x8e, 0xe2, 0x22, 0x09, - 0xb3, 0xd1, 0x9d, 0x3a, 0xcf, 0x69, 0x36, 0xba, 0xd7, 0xbf, 0x2a, 0x3a, 0xa6, 0xbf, 0xe8, 0x80, 0x36, 0x4a, 0xfa, - 0xd6, 0x71, 0x36, 0xa0, 0xf5, 0x62, 0x69, 0xfc, 0xaf, 0xe5, 0xe8, 0x8e, 0xca, 0xd1, 0xbd, 0x6f, 0x49, 0x35, 0x99, - 0x16, 0xc7, 0x02, 0x0d, 0xa9, 0x3a, 0xbf, 0x2f, 0x80, 0x9f, 0x2b, 0x8d, 0xef, 0xb4, 0xf9, 0xde, 0x6b, 0xff, 0x45, - 0x27, 0x4f, 0xa0, 0x58, 0xa2, 0x82, 0x55, 0x23, 0xb0, 0x63, 0x5f, 0xe7, 0x71, 0x61, 0x46, 0x29, 0xa6, 0xd6, 0xa4, - 0x1f, 0x03, 0x57, 0x4c, 0x7b, 0x05, 0xb8, 0x5a, 0x82, 0x93, 0x00, 0xc4, 0xd0, 0x84, 0x3d, 0x3b, 0x86, 0xa8, 0xe7, - 0xc6, 0x31, 0x4a, 0x36, 0xdc, 0x03, 0x62, 0x2d, 0xf3, 0x56, 0x2e, 0x01, 0x09, 0xbc, 0xf5, 0x30, 0x29, 0x00, 0x63, - 0xb0, 0x5c, 0x12, 0x9d, 0xc7, 0x43, 0x9f, 0x50, 0x2f, 0x34, 0xea, 0x84, 0x6c, 0x6c, 0x09, 0x1c, 0x7f, 0x58, 0x1f, - 0x02, 0xc1, 0xab, 0x3c, 0xd7, 0x5f, 0x69, 0x5d, 0x7f, 0xa9, 0xf4, 0xdc, 0xb1, 0x5c, 0xd7, 0xcf, 0xdb, 0xd4, 0xe8, - 0x25, 0x58, 0xf8, 0x6e, 0x94, 0x79, 0x24, 0xb7, 0x08, 0xa9, 0x0a, 0xac, 0xd4, 0x2d, 0x24, 0x98, 0x7f, 0x25, 0x67, - 0xab, 0x32, 0x5f, 0x3d, 0xf2, 0xa0, 0x9c, 0x4d, 0x4f, 0x7f, 0x43, 0x82, 0x76, 0xdf, 0x91, 0xe6, 0xf1, 0x16, 0x1d, - 0x3e, 0xbb, 0xd6, 0x12, 0x73, 0x27, 0x51, 0xf1, 0x7c, 0x0a, 0xd8, 0xea, 0x79, 0x76, 0xad, 0x7c, 0xac, 0x76, 0x71, - 0xfc, 0xcc, 0xf9, 0x93, 0x54, 0xe1, 0x5a, 0x34, 0x94, 0x20, 0xe0, 0xcd, 0x61, 0xec, 0x0a, 0x55, 0x40, 0x43, 0x73, - 0x03, 0xc7, 0xb9, 0x1a, 0x56, 0x9a, 0x80, 0x69, 0x29, 0x8f, 0x0e, 0x70, 0x68, 0xf2, 0xa8, 0xdd, 0x34, 0xac, 0x0c, - 0x5d, 0x6b, 0xf4, 0xb9, 0xad, 0x74, 0xc6, 0x9b, 0x0d, 0x3f, 0x3c, 0x1a, 0x54, 0xf8, 0x93, 0x34, 0x47, 0xa3, 0x9d, - 0x1b, 0xee, 0x34, 0x02, 0x33, 0x57, 0x72, 0x45, 0x76, 0x47, 0xc9, 0xcb, 0xef, 0xe9, 0x85, 0x05, 0xf4, 0xe7, 0x3f, - 0x17, 0x13, 0x4e, 0x5a, 0x62, 0x42, 0xb4, 0x74, 0xd0, 0xa2, 0x83, 0x1d, 0xe5, 0x95, 0x7d, 0x89, 0x97, 0xce, 0xf1, - 0xbf, 0xaf, 0xc7, 0xda, 0x55, 0x20, 0xb4, 0x3a, 0x79, 0xd8, 0x9e, 0x2c, 0x10, 0x35, 0xa0, 0x9a, 0x5d, 0x95, 0xa3, - 0x4c, 0x3b, 0x2b, 0xb2, 0x69, 0xc8, 0x5c, 0x77, 0xb3, 0x34, 0x6c, 0x26, 0x3b, 0x16, 0x96, 0x19, 0x06, 0x6b, 0xa7, - 0x8a, 0x3e, 0x07, 0x2d, 0x3f, 0x82, 0xe7, 0x4d, 0xe5, 0x99, 0xcf, 0x66, 0x19, 0xf1, 0x02, 0x9d, 0x73, 0x2a, 0x16, - 0x4d, 0xe9, 0x58, 0xb9, 0xdd, 0x96, 0x68, 0x2c, 0x51, 0x46, 0x41, 0x50, 0xdb, 0x20, 0xec, 0xba, 0x74, 0x4f, 0xfa, - 0xb4, 0x8b, 0x4f, 0x2b, 0xd0, 0xf7, 0x78, 0x9f, 0x81, 0xc4, 0xd4, 0x93, 0x3c, 0x54, 0x8d, 0xe6, 0xe8, 0xe4, 0x59, - 0x9c, 0x6a, 0x7c, 0x7e, 0x25, 0x3b, 0x6b, 0xde, 0xad, 0xc6, 0x14, 0xff, 0x91, 0xba, 0x7d, 0xe7, 0x32, 0x34, 0xd1, - 0x5f, 0xcb, 0x83, 0x96, 0xc2, 0x82, 0xe3, 0xb6, 0xf1, 0xd7, 0x6f, 0x33, 0x87, 0x18, 0x96, 0x2e, 0x87, 0x37, 0xa1, - 0x43, 0x77, 0x57, 0xd9, 0x99, 0xeb, 0x23, 0xea, 0xd4, 0xc5, 0xba, 0x0d, 0x28, 0x59, 0xf2, 0x6e, 0x9d, 0x9e, 0x58, - 0xe9, 0x97, 0xc3, 0x70, 0x67, 0x1e, 0x35, 0xbb, 0xbb, 0xdd, 0x4e, 0x48, 0xdb, 0x3e, 0x18, 0xef, 0x4b, 0x58, 0x88, - 0xf3, 0x0e, 0x3b, 0xf8, 0x29, 0xac, 0x1e, 0xf3, 0xc1, 0x6f, 0x38, 0xce, 0x30, 0xfa, 0x99, 0x32, 0xf4, 0x79, 0x59, - 0xc8, 0x6b, 0xd5, 0x29, 0x5f, 0xe8, 0xd6, 0x32, 0xf5, 0x7e, 0x13, 0xbf, 0x69, 0x05, 0x88, 0xf1, 0xba, 0x62, 0xa5, - 0x78, 0x43, 0x2b, 0x8c, 0x6b, 0xe0, 0x36, 0x39, 0xd4, 0x52, 0x2d, 0x10, 0x75, 0xf9, 0xc9, 0x63, 0x1e, 0x19, 0x75, - 0x26, 0x7c, 0xf7, 0x98, 0xfb, 0xd2, 0xb5, 0xdd, 0x26, 0x7e, 0xaa, 0x69, 0x87, 0xbb, 0x03, 0xdd, 0xd1, 0xba, 0x87, - 0x9b, 0x67, 0xf3, 0xf3, 0xc8, 0x7c, 0x31, 0xc0, 0x66, 0xed, 0x32, 0x2e, 0x3b, 0x86, 0xfb, 0xde, 0xf4, 0x60, 0x2c, - 0x20, 0x90, 0x98, 0xa1, 0x97, 0x81, 0x0b, 0x5c, 0xe0, 0xae, 0x30, 0x60, 0x88, 0x6b, 0x5a, 0x72, 0xae, 0xad, 0x6c, - 0x7d, 0xe4, 0x6d, 0x54, 0x08, 0xd6, 0x75, 0xc7, 0x4d, 0x92, 0x43, 0x70, 0xc2, 0x96, 0x7b, 0x5f, 0x7b, 0xed, 0x0c, - 0xff, 0x39, 0x10, 0xce, 0x2d, 0xd1, 0x33, 0x6a, 0x7b, 0xac, 0xd5, 0xbd, 0x86, 0x57, 0xb9, 0x8f, 0x3c, 0xeb, 0x37, - 0xf3, 0xd2, 0xb0, 0x2f, 0x78, 0x2d, 0x05, 0x87, 0xc6, 0x76, 0x2b, 0xdc, 0x62, 0xf1, 0x8e, 0x56, 0x2b, 0x6b, 0x6d, - 0xb5, 0xd7, 0x4a, 0x45, 0xf7, 0xaf, 0x39, 0x4e, 0x9c, 0xa5, 0xb0, 0xfd, 0xf0, 0xe1, 0x82, 0x5d, 0x13, 0xc0, 0xa0, - 0xc5, 0x64, 0x81, 0x12, 0x54, 0xb2, 0x56, 0xb5, 0xdb, 0x29, 0xf1, 0xcb, 0xfd, 0xac, 0xcb, 0x6c, 0xe7, 0xf1, 0xeb, - 0x26, 0xed, 0x13, 0x9f, 0xa3, 0x1f, 0xe6, 0xb7, 0xd6, 0x49, 0xc9, 0x19, 0xc6, 0xb5, 0xfc, 0xff, 0x2a, 0x7a, 0x55, - 0x64, 0x69, 0xb4, 0x31, 0x3c, 0x98, 0x0d, 0xb5, 0xe9, 0x43, 0x63, 0x54, 0x6e, 0xd9, 0x28, 0x22, 0x5a, 0xdd, 0x81, - 0x60, 0x46, 0x71, 0x5f, 0xa2, 0xcd, 0x2b, 0x55, 0x16, 0xde, 0xe1, 0x13, 0x1b, 0xbd, 0x61, 0x7b, 0x42, 0x28, 0xdf, - 0x3d, 0x2d, 0xcc, 0xaa, 0xa5, 0xa2, 0xc1, 0x76, 0x09, 0xef, 0x62, 0x54, 0xe9, 0x27, 0x4c, 0xb6, 0x2c, 0x98, 0xea, - 0xff, 0x77, 0x45, 0x96, 0xb6, 0x29, 0x3a, 0x30, 0x9d, 0x4d, 0x9f, 0x4e, 0xba, 0xc1, 0x75, 0x06, 0x2c, 0x22, 0xd8, - 0x52, 0xe1, 0x78, 0x94, 0xda, 0x0d, 0x12, 0x26, 0x82, 0x9b, 0xa8, 0x97, 0x1d, 0x2d, 0x53, 0xb2, 0x2a, 0xe0, 0xf9, - 0x95, 0xab, 0x4c, 0xc7, 0xd1, 0xd0, 0xef, 0x5f, 0xa7, 0x26, 0xf4, 0x2b, 0xf5, 0x52, 0x15, 0xe7, 0x61, 0x54, 0x1d, - 0x2a, 0x8c, 0xd1, 0x92, 0xa6, 0x70, 0x0c, 0x66, 0x97, 0x61, 0x8a, 0x97, 0xb3, 0x4d, 0xc2, 0x3e, 0x63, 0x20, 0x97, - 0xda, 0xa0, 0x5e, 0x53, 0xa2, 0x35, 0x6b, 0x6f, 0xe6, 0x94, 0xd0, 0x4b, 0x56, 0xfa, 0x77, 0xa1, 0x35, 0x08, 0x14, - 0x65, 0x33, 0x65, 0x7a, 0xa1, 0xdb, 0x79, 0x49, 0x13, 0x5a, 0xd0, 0x15, 0xa9, 0x41, 0xdf, 0xeb, 0xe4, 0xec, 0xe8, - 0x64, 0x67, 0x66, 0x3d, 0x66, 0xc5, 0x70, 0x32, 0x8d, 0xe1, 0x9a, 0x16, 0xbb, 0x6b, 0xda, 0xb2, 0x79, 0xe3, 0x6a, - 0x6c, 0x9c, 0x06, 0xed, 0x02, 0x69, 0x9b, 0xe6, 0xf6, 0x53, 0x8f, 0xdb, 0x5f, 0xd7, 0x6c, 0x39, 0xed, 0xad, 0xb7, - 0xdb, 0x5e, 0x0a, 0x36, 0xa2, 0x1e, 0x1f, 0xbf, 0x56, 0xd2, 0x75, 0xcb, 0xe5, 0xa7, 0xf0, 0xec, 0xf1, 0xf5, 0x4b, - 0x1f, 0x5c, 0x8e, 0x56, 0x6d, 0xee, 0x7e, 0xb9, 0x8b, 0x2c, 0xf7, 0x59, 0x43, 0xcb, 0xf5, 0x0c, 0x35, 0xc9, 0xb3, - 0xd1, 0xde, 0xa1, 0x16, 0x2c, 0x67, 0xdd, 0x84, 0x27, 0x06, 0x3b, 0xf6, 0xaa, 0xb1, 0x39, 0x2a, 0x73, 0xc9, 0x6a, - 0x90, 0x40, 0x9f, 0xe4, 0x99, 0xa6, 0x7f, 0x90, 0x61, 0x3e, 0xba, 0xa3, 0x39, 0xe0, 0x8a, 0x55, 0xf6, 0x92, 0x41, - 0xea, 0xaa, 0xbd, 0xc4, 0x95, 0xaf, 0x70, 0x48, 0x36, 0xf8, 0x64, 0x98, 0xaa, 0x4f, 0x2e, 0x79, 0xf0, 0xff, 0xb6, - 0x6a, 0x95, 0x9e, 0x9b, 0xe4, 0x86, 0xe3, 0x5f, 0x27, 0x6d, 0x1f, 0x13, 0x83, 0x04, 0x3c, 0xb5, 0x8b, 0xa1, 0x1a, - 0x55, 0x45, 0x2c, 0xca, 0xdc, 0xc4, 0x1c, 0xdb, 0xdb, 0x35, 0x74, 0x50, 0x06, 0xbf, 0x6e, 0xf8, 0xc4, 0xdc, 0x81, - 0xad, 0x40, 0x47, 0x27, 0x9a, 0xcb, 0x30, 0x33, 0x97, 0x61, 0xda, 0xb5, 0x55, 0x60, 0x78, 0xd5, 0x56, 0x49, 0x94, - 0xab, 0x51, 0x8f, 0x9b, 0x59, 0x6a, 0xf6, 0x22, 0xef, 0x5e, 0x93, 0x9e, 0xc4, 0x9f, 0x2e, 0x3d, 0x79, 0x3d, 0x0c, - 0x88, 0xfc, 0x9c, 0xa5, 0xe1, 0x1a, 0x05, 0xc1, 0xa9, 0xd5, 0x0e, 0xa4, 0xf9, 0x08, 0x90, 0xf9, 0x71, 0x1a, 0xbe, - 0xd5, 0xe2, 0x1c, 0xb2, 0x51, 0x1a, 0x27, 0xb6, 0x34, 0xea, 0x21, 0xb8, 0xf3, 0x5e, 0xf3, 0x18, 0x02, 0x1f, 0x7e, - 0xc0, 0xcd, 0xa0, 0xa2, 0xdb, 0x12, 0x13, 0xa5, 0xcd, 0xa3, 0x6e, 0xf9, 0xa8, 0x21, 0x54, 0xb2, 0x32, 0xbc, 0x04, - 0xda, 0xbb, 0x23, 0x30, 0xaa, 0x9c, 0x40, 0x66, 0x58, 0x1c, 0x1e, 0x0d, 0x53, 0x25, 0x28, 0x1a, 0xca, 0xe1, 0x12, - 0xe5, 0x80, 0x98, 0x04, 0x02, 0xa3, 0x62, 0x90, 0xea, 0xca, 0xd4, 0x8b, 0x41, 0xaa, 0x6f, 0x55, 0xa4, 0x3e, 0xcf, - 0xc2, 0x8a, 0xea, 0x16, 0xd1, 0x31, 0x1d, 0x4a, 0xba, 0x34, 0x3b, 0x35, 0xd7, 0xd2, 0x0b, 0xb5, 0x1c, 0x9f, 0xe9, - 0x34, 0x18, 0xc5, 0x33, 0x97, 0xa2, 0xdf, 0xaa, 0xfd, 0xec, 0xbf, 0xc5, 0x94, 0x1a, 0xb1, 0xa9, 0xbd, 0x45, 0x0c, - 0xab, 0xf6, 0x43, 0x56, 0xe5, 0xa0, 0xdd, 0x05, 0x65, 0x63, 0x65, 0x9c, 0xe7, 0x1b, 0xc1, 0xcc, 0x41, 0xdb, 0x58, - 0x35, 0x7d, 0xe8, 0x8d, 0x18, 0xb5, 0x37, 0xa6, 0x1a, 0xf7, 0x04, 0x7e, 0xda, 0xa0, 0xe9, 0x5e, 0xe4, 0x39, 0xea, - 0x91, 0x77, 0xff, 0x33, 0x47, 0x76, 0x26, 0x9f, 0xc4, 0x32, 0xa9, 0xdb, 0xc7, 0x24, 0x58, 0xa8, 0x3a, 0x46, 0x17, - 0x6e, 0x64, 0x4a, 0xfb, 0xb9, 0x33, 0xfd, 0x88, 0x67, 0xf2, 0xb0, 0x1d, 0x1a, 0xf5, 0xa5, 0x61, 0x2d, 0x29, 0xa2, - 0xbe, 0xa0, 0xb7, 0xa6, 0x3a, 0x3a, 0xa2, 0x5e, 0x47, 0x60, 0x75, 0x45, 0x1b, 0xd4, 0x00, 0x4c, 0xc6, 0xb5, 0xad, - 0xcd, 0xe7, 0x60, 0x6a, 0xab, 0x2a, 0x78, 0x4a, 0x77, 0x85, 0xd2, 0xbd, 0x49, 0x5d, 0xb7, 0x86, 0xd8, 0x02, 0x06, - 0x04, 0x6e, 0xf4, 0xd4, 0xf4, 0x07, 0x4d, 0x54, 0x00, 0x1a, 0x34, 0x6e, 0x67, 0x3a, 0x47, 0xa2, 0xdf, 0xa9, 0x4d, - 0xdb, 0x4c, 0xf5, 0xaa, 0xf2, 0x01, 0x54, 0xfc, 0x59, 0x3a, 0xbf, 0x34, 0x23, 0x16, 0xc0, 0xb8, 0x07, 0xce, 0x54, - 0xef, 0x34, 0x03, 0xeb, 0x89, 0x3c, 0xcf, 0x4a, 0x9e, 0x48, 0x01, 0x33, 0x22, 0xaf, 0xaf, 0xa5, 0x80, 0x61, 0x50, - 0x03, 0x80, 0x16, 0xcd, 0x65, 0x34, 0xe1, 0x5f, 0xd5, 0x74, 0x5f, 0x1e, 0xfe, 0x95, 0xce, 0xf5, 0xf5, 0xb8, 0x06, - 0x43, 0xe5, 0x75, 0xc5, 0x77, 0x32, 0x7d, 0xcd, 0x9f, 0x78, 0x99, 0x96, 0x72, 0x5d, 0xec, 0x64, 0xf9, 0xea, 0x6b, - 0xfe, 0x54, 0xe7, 0x39, 0x7a, 0x52, 0xd3, 0x34, 0xbe, 0xdf, 0xc9, 0xf2, 0xf7, 0xaf, 0x9f, 0xd8, 0x3c, 0x5f, 0x8d, - 0x6b, 0x7a, 0xcb, 0xf9, 0x47, 0x97, 0x69, 0xa2, 0xab, 0x1a, 0x3f, 0xf9, 0xbb, 0xcd, 0xf5, 0xa4, 0xa6, 0xd7, 0x52, - 0x54, 0xcb, 0x9d, 0xa2, 0x8e, 0xbe, 0x3e, 0xfa, 0x3b, 0xff, 0xda, 0x74, 0xef, 0xa8, 0xa6, 0x7f, 0xae, 0xe3, 0xa2, - 0xe2, 0xc5, 0x4e, 0x71, 0x7f, 0xfb, 0xfb, 0xdf, 0x9f, 0xd8, 0x8c, 0x4f, 0x6a, 0x7a, 0xcf, 0xe3, 0x8e, 0xb6, 0x4f, - 0x9e, 0x3e, 0xe1, 0x7f, 0xab, 0x6b, 0xfa, 0x33, 0xf3, 0x83, 0xa3, 0x9e, 0x66, 0x9e, 0x1e, 0x3e, 0x91, 0x4d, 0xd4, - 0x80, 0xa1, 0x87, 0x06, 0x90, 0x4b, 0xab, 0xa6, 0xd9, 0xe3, 0x95, 0x0b, 0x6e, 0xdf, 0xe7, 0x71, 0x1a, 0xaf, 0xe0, - 0x20, 0xd8, 0xa0, 0x71, 0x56, 0x01, 0x9c, 0x2a, 0xf0, 0x9e, 0x51, 0x49, 0xb3, 0x52, 0xfe, 0x93, 0xf3, 0x8f, 0x30, - 0x68, 0x08, 0x69, 0xa3, 0x22, 0x03, 0xbd, 0x5d, 0xe9, 0xc8, 0x46, 0xe8, 0xbf, 0xd9, 0x8c, 0x83, 0xe3, 0xc3, 0xe8, - 0xf5, 0xfb, 0x61, 0xc1, 0x44, 0x58, 0x10, 0x42, 0xff, 0x08, 0x0b, 0x70, 0x28, 0x29, 0x98, 0x97, 0xcf, 0xf8, 0x9e, - 0x6b, 0xa3, 0xb0, 0x10, 0x44, 0x77, 0x91, 0x7d, 0x40, 0xd5, 0xa3, 0xef, 0xd0, 0x0d, 0xf1, 0xb2, 0xc2, 0x82, 0xa1, - 0x55, 0x0d, 0xcc, 0x10, 0x14, 0xff, 0x9a, 0x87, 0x12, 0x7c, 0xe2, 0x01, 0x3e, 0x7a, 0x4c, 0x66, 0x5c, 0x5d, 0x6b, - 0xdf, 0x5e, 0x86, 0x05, 0x0d, 0x74, 0xdb, 0x21, 0xe8, 0x40, 0xe4, 0xbf, 0x00, 0x4f, 0x81, 0x81, 0x0f, 0x0b, 0xbb, - 0x94, 0xbb, 0xfe, 0xea, 0x3f, 0x1b, 0xd6, 0xd1, 0x85, 0x1f, 0xfd, 0xd9, 0xba, 0xb0, 0x67, 0x64, 0x2a, 0x8f, 0xcb, - 0xe1, 0x64, 0x3a, 0x18, 0x48, 0x17, 0xc7, 0xed, 0x34, 0x9b, 0xff, 0x3c, 0x97, 0x8b, 0x05, 0xea, 0xbe, 0x71, 0x5e, - 0x67, 0xfa, 0x6f, 0xa4, 0x9d, 0x0f, 0x5e, 0x9f, 0xfe, 0x7a, 0x7e, 0x76, 0xfa, 0x12, 0x9c, 0x0f, 0x3e, 0xbc, 0xf8, - 0xee, 0xc5, 0x7b, 0x15, 0xdc, 0x5d, 0xcd, 0x79, 0xbf, 0xef, 0xa4, 0x3e, 0x21, 0x1f, 0x56, 0xe4, 0x30, 0x8c, 0x1f, - 0x17, 0xca, 0xe8, 0x81, 0x1c, 0x33, 0x0b, 0x85, 0x0c, 0x55, 0xd4, 0xf6, 0x77, 0x39, 0x9c, 0x78, 0x60, 0x16, 0xf7, - 0x0d, 0x11, 0xae, 0xdf, 0x72, 0x1b, 0x64, 0x4d, 0x9e, 0x78, 0xfd, 0xe0, 0x64, 0x2a, 0x1d, 0x5b, 0x58, 0x30, 0x28, - 0x1b, 0xda, 0x74, 0x9a, 0xcd, 0x8b, 0x85, 0x6d, 0x97, 0x5b, 0x20, 0xa3, 0x34, 0xbb, 0xbc, 0x0c, 0x15, 0x74, 0xf5, - 0x09, 0x68, 0x00, 0x4c, 0xa3, 0x0a, 0xd7, 0x22, 0x3e, 0xf3, 0xcb, 0x8f, 0xc6, 0x5e, 0xf3, 0xee, 0x50, 0xf7, 0x64, - 0x9a, 0x55, 0x35, 0x06, 0x74, 0x30, 0xa1, 0xdc, 0x0d, 0xba, 0x09, 0x26, 0xa3, 0xda, 0xf2, 0xf3, 0xbc, 0x5a, 0x98, - 0xe6, 0xb8, 0x61, 0xa8, 0xbc, 0x92, 0xd7, 0xb2, 0x81, 0xc8, 0x40, 0x32, 0x0c, 0x7b, 0x34, 0x46, 0x91, 0xfa, 0xc1, - 0xae, 0x77, 0xfc, 0x26, 0x97, 0x10, 0x4d, 0x31, 0x03, 0xe9, 0xfc, 0xa9, 0x50, 0xce, 0xe5, 0x92, 0xf1, 0xb9, 0x58, - 0x9c, 0x80, 0xdb, 0xf9, 0x5c, 0x2c, 0x22, 0x0c, 0xca, 0x97, 0x41, 0xac, 0x12, 0xb0, 0x7b, 0x71, 0x10, 0xbe, 0x9d, - 0xd0, 0x06, 0x76, 0x03, 0x49, 0x36, 0x28, 0xed, 0x4a, 0x43, 0x94, 0x3b, 0xe5, 0xd1, 0x06, 0x91, 0x87, 0x58, 0x35, - 0xaf, 0xda, 0x9e, 0x6c, 0xe6, 0x62, 0x82, 0xab, 0x2c, 0x66, 0x72, 0x1a, 0x1f, 0xb3, 0x62, 0x1a, 0x43, 0x29, 0x71, - 0x9a, 0x86, 0x31, 0x9d, 0x50, 0x41, 0x48, 0xc2, 0xf8, 0x3c, 0x5e, 0xd0, 0x04, 0xa5, 0x04, 0x21, 0x84, 0xfc, 0x18, - 0xa1, 0x6d, 0x0e, 0x2c, 0x79, 0xbb, 0xfd, 0x3c, 0xfd, 0xdc, 0x8e, 0xe1, 0x32, 0x2a, 0x42, 0x37, 0xe8, 0xac, 0xe1, - 0xdf, 0x88, 0x0a, 0x1a, 0x63, 0xc5, 0x10, 0x04, 0xbc, 0xc0, 0xa8, 0x84, 0x05, 0x89, 0x59, 0x05, 0x51, 0x04, 0xca, - 0x79, 0xbc, 0x60, 0x05, 0x6d, 0xda, 0x9c, 0xc6, 0xda, 0x24, 0xa8, 0xe7, 0xb0, 0xd4, 0x0e, 0xa4, 0x52, 0x21, 0xf6, - 0xf8, 0x4c, 0x44, 0x37, 0xda, 0xd0, 0x00, 0x50, 0xa0, 0x94, 0x5c, 0xfc, 0xf6, 0xf3, 0x3d, 0xdc, 0x14, 0xf4, 0x3f, - 0xdb, 0x98, 0x68, 0x67, 0xb9, 0x3a, 0xf4, 0xe6, 0x0b, 0x1a, 0xe7, 0x39, 0x84, 0x62, 0x33, 0x08, 0xe4, 0x22, 0xab, - 0x20, 0xa2, 0xc5, 0x7d, 0x60, 0x42, 0xc2, 0x41, 0x9b, 0x7e, 0x86, 0xd4, 0x86, 0x98, 0x5c, 0x79, 0x62, 0x60, 0xb7, - 0x55, 0x82, 0x80, 0x23, 0x3d, 0xcf, 0xfe, 0x6a, 0x62, 0xac, 0x69, 0x6a, 0x66, 0xe2, 0x6d, 0x28, 0x44, 0x83, 0x16, - 0x44, 0x33, 0x78, 0xff, 0x5c, 0x73, 0xbc, 0xea, 0xc0, 0x0f, 0x78, 0xe7, 0xe2, 0xcc, 0xab, 0x99, 0x47, 0xe4, 0xd4, - 0x47, 0x39, 0xa2, 0x5f, 0xf2, 0xb0, 0x1a, 0xe9, 0x64, 0x8c, 0x95, 0xc4, 0x41, 0x6f, 0x83, 0x05, 0x73, 0x42, 0x57, - 0x3c, 0xb4, 0x7c, 0xfc, 0x4b, 0x64, 0x32, 0x4a, 0x6a, 0xac, 0xe8, 0x4a, 0x8b, 0x11, 0xe7, 0x35, 0xcc, 0xd2, 0x64, - 0x45, 0x17, 0x0b, 0x4d, 0x9a, 0x85, 0x32, 0x0d, 0xf0, 0x09, 0xb4, 0x18, 0xb9, 0x87, 0x9a, 0x36, 0x10, 0x1a, 0x76, - 0x87, 0x80, 0x8f, 0xdc, 0x43, 0x87, 0xff, 0x9f, 0x67, 0x17, 0x88, 0xb4, 0x37, 0x37, 0x91, 0xf1, 0x48, 0xdd, 0xc0, - 0x41, 0x31, 0x3e, 0xf6, 0xcd, 0xc4, 0xcf, 0x9c, 0xd1, 0x87, 0xa4, 0xf2, 0x1d, 0x3e, 0x58, 0xfe, 0x78, 0x53, 0x33, - 0x2b, 0x23, 0x58, 0x0f, 0xdb, 0x2d, 0x2e, 0x88, 0xb6, 0x0b, 0x20, 0xf5, 0x8c, 0x57, 0x0b, 0xdf, 0x78, 0x35, 0xde, - 0x63, 0xbc, 0xea, 0xce, 0xd4, 0x30, 0x27, 0x1b, 0xd4, 0x67, 0x29, 0x79, 0x7e, 0x8e, 0x32, 0xc1, 0xa6, 0xcb, 0x59, - 0x49, 0x55, 0x2a, 0xa1, 0xbd, 0xd8, 0xcf, 0x18, 0xdf, 0x11, 0x8c, 0xb3, 0xe2, 0x30, 0x12, 0xa8, 0x4a, 0x25, 0x75, - 0xd8, 0x2b, 0x40, 0x3d, 0x06, 0xef, 0x0d, 0x86, 0xa8, 0x91, 0xb1, 0x9b, 0x36, 0x10, 0x1a, 0x1a, 0xeb, 0xd1, 0x9e, - 0xb5, 0x1e, 0xdd, 0x6e, 0x2b, 0xe3, 0x6f, 0x27, 0xd7, 0x45, 0x82, 0xa8, 0xc2, 0x6a, 0x34, 0x01, 0xde, 0x34, 0xb1, - 0xb7, 0x25, 0xa7, 0xb4, 0xc0, 0xf0, 0xd9, 0x7f, 0x84, 0xa5, 0x53, 0x49, 0x94, 0x64, 0x56, 0x46, 0x03, 0x77, 0x0e, - 0x3e, 0x8f, 0x2b, 0x58, 0x03, 0x10, 0xc9, 0x11, 0x3d, 0x5c, 0xff, 0x08, 0xa5, 0xcb, 0x2c, 0xc9, 0x4c, 0x42, 0x66, - 0x2e, 0xd2, 0x76, 0xd6, 0xc1, 0xc4, 0x99, 0xd4, 0x7a, 0x63, 0x21, 0x87, 0x06, 0xf9, 0x01, 0x94, 0x21, 0x0e, 0x9f, - 0x7c, 0x30, 0xa1, 0x52, 0x85, 0x52, 0x6d, 0x74, 0xb3, 0x1b, 0x78, 0xe5, 0x43, 0x76, 0xcd, 0xcb, 0x2a, 0xbe, 0x5e, - 0x19, 0x4b, 0x62, 0xce, 0xf6, 0xb9, 0xed, 0x51, 0x61, 0x5e, 0xbd, 0x79, 0xf1, 0xdd, 0x69, 0xe3, 0xd5, 0x2e, 0xe2, - 0x68, 0x08, 0xb6, 0x15, 0x63, 0x8c, 0xde, 0xe2, 0xd3, 0x60, 0xa2, 0x5c, 0x23, 0xd0, 0xbb, 0x14, 0xf4, 0xdb, 0x9f, - 0xeb, 0x09, 0x78, 0xcd, 0xf5, 0xf2, 0x4b, 0x3e, 0x02, 0x96, 0xa8, 0xd0, 0xb3, 0xc2, 0xdc, 0xac, 0xcc, 0xf6, 0x76, - 0x2b, 0x32, 0xd3, 0xae, 0x34, 0x32, 0x10, 0xaf, 0xb6, 0xc3, 0x58, 0xb8, 0x74, 0x4d, 0xb7, 0x83, 0x5d, 0x2d, 0x3d, - 0x4b, 0xe4, 0xed, 0xb6, 0x84, 0x0e, 0xd9, 0x01, 0xf7, 0x5e, 0xc6, 0x77, 0xf0, 0xb2, 0xf4, 0xba, 0xd9, 0x0c, 0x9e, - 0x00, 0x66, 0xc2, 0x85, 0xb3, 0x2c, 0x8e, 0x19, 0x4f, 0x42, 0x15, 0x9b, 0xab, 0x21, 0xf2, 0x56, 0x84, 0xd6, 0xec, - 0xaf, 0x50, 0x8c, 0xc0, 0xee, 0xe4, 0xec, 0x63, 0xb6, 0x9a, 0x2d, 0x01, 0x35, 0xff, 0x3a, 0x13, 0x40, 0x73, 0xed, - 0x5a, 0xb0, 0x4d, 0xa1, 0xcd, 0x75, 0xfd, 0x2c, 0x5e, 0xc5, 0x09, 0xa8, 0x6e, 0xc0, 0x5b, 0xe4, 0x5e, 0x8b, 0xae, - 0x0c, 0xba, 0x28, 0x7d, 0xa0, 0x1c, 0x4b, 0x0a, 0x1d, 0x7d, 0xef, 0x09, 0x75, 0xee, 0x19, 0xc0, 0x25, 0x8d, 0x9a, - 0xa7, 0x5a, 0xca, 0x58, 0x00, 0x2c, 0x74, 0x30, 0x53, 0x64, 0x2b, 0xba, 0x33, 0x98, 0x14, 0xf0, 0xd6, 0x00, 0x7f, - 0x88, 0xac, 0x52, 0x77, 0xc5, 0x32, 0x2c, 0x3d, 0xfb, 0xeb, 0x7e, 0x3f, 0xf6, 0xec, 0xaf, 0x2f, 0x35, 0xad, 0x8b, - 0xdb, 0x0d, 0x20, 0x35, 0x06, 0x10, 0x39, 0xd5, 0x03, 0x61, 0x22, 0x8a, 0x35, 0x7d, 0xff, 0x4e, 0x4d, 0x16, 0x05, - 0x42, 0xbf, 0x53, 0xaf, 0x27, 0x25, 0x01, 0x9d, 0x5a, 0xc5, 0x4e, 0x06, 0xda, 0xec, 0x03, 0x02, 0xa2, 0xfa, 0x19, - 0xd9, 0x7c, 0xa1, 0x9c, 0x8b, 0x55, 0xf8, 0xf0, 0x31, 0x85, 0x80, 0xc2, 0x1d, 0x35, 0x3a, 0x6f, 0x43, 0x24, 0x50, - 0x56, 0x28, 0x62, 0xcd, 0x8b, 0xb5, 0x24, 0x64, 0x3e, 0x5e, 0xa0, 0xe0, 0xca, 0x01, 0xbb, 0x72, 0x36, 0x19, 0x96, - 0x11, 0x67, 0xe1, 0xfe, 0x6f, 0x26, 0x0b, 0x82, 0x9a, 0x2b, 0x3f, 0x90, 0xe3, 0x4e, 0xa6, 0xc6, 0x9e, 0x6a, 0xd4, - 0x20, 0x98, 0x8c, 0x20, 0x30, 0xdc, 0xf0, 0x33, 0x3e, 0x3e, 0x5a, 0x10, 0x50, 0x91, 0x59, 0xb3, 0x10, 0xf3, 0xe2, - 0xf8, 0x2b, 0x40, 0x8d, 0x19, 0x1d, 0x3d, 0x9d, 0x72, 0x06, 0x87, 0x28, 0x1d, 0x83, 0x8c, 0x56, 0xc0, 0x6f, 0xa1, - 0x7e, 0xb7, 0x4e, 0x7c, 0x1f, 0xfa, 0x55, 0xd0, 0xcb, 0x18, 0x18, 0x8e, 0x68, 0x72, 0x18, 0xf2, 0xc1, 0x64, 0x00, - 0xda, 0x12, 0x6f, 0xf7, 0xb5, 0xb4, 0xe2, 0xe6, 0x74, 0xe9, 0x74, 0xff, 0xa4, 0x4d, 0x90, 0x44, 0x2a, 0x59, 0xa9, - 0x88, 0x01, 0x84, 0xb2, 0x54, 0xdb, 0x64, 0x09, 0x96, 0x15, 0x66, 0x49, 0x73, 0x83, 0x92, 0xb8, 0xbb, 0x19, 0x38, - 0x46, 0xcd, 0x3a, 0x0d, 0xcb, 0x96, 0x1b, 0x35, 0xc0, 0xe7, 0x24, 0xac, 0xb0, 0x37, 0x9c, 0x99, 0xf4, 0xce, 0x74, - 0xb8, 0x3a, 0xe6, 0xec, 0x35, 0x47, 0x30, 0x8e, 0x04, 0x6f, 0x3c, 0x74, 0xc9, 0x34, 0x54, 0x64, 0xca, 0x38, 0x98, - 0xf6, 0x00, 0xf7, 0x9e, 0x83, 0x71, 0x18, 0x1b, 0x54, 0x96, 0xd4, 0xa7, 0xde, 0x5d, 0x08, 0x04, 0x69, 0xad, 0x97, - 0xf9, 0x0c, 0x4f, 0xcf, 0x08, 0x65, 0x7f, 0xc8, 0xe1, 0x0b, 0xb0, 0xa3, 0x20, 0x27, 0x13, 0xfe, 0xf4, 0xf1, 0x6e, - 0xa0, 0x2a, 0x3e, 0x08, 0x0e, 0x62, 0x91, 0x1e, 0x04, 0x03, 0x01, 0xbf, 0x0a, 0x7e, 0x50, 0x49, 0x79, 0x70, 0x19, - 0x17, 0x07, 0xf1, 0x2a, 0x2e, 0xaa, 0x83, 0xdb, 0xac, 0x5a, 0x1e, 0x98, 0x0e, 0x01, 0x34, 0x6f, 0x30, 0x88, 0x07, - 0xc1, 0x41, 0x30, 0x28, 0xcc, 0xd4, 0xae, 0x58, 0xd9, 0x38, 0xce, 0x4c, 0x88, 0xb2, 0xa0, 0x19, 0x20, 0xac, 0x71, - 0x1a, 0x00, 0x9f, 0xba, 0x66, 0x29, 0xbd, 0xc4, 0x70, 0x03, 0x62, 0xba, 0x86, 0x3e, 0x00, 0x8f, 0xbc, 0xa6, 0x31, - 0x2c, 0x81, 0xcb, 0xc1, 0x80, 0xac, 0x21, 0x72, 0xc1, 0x9a, 0xda, 0x20, 0x0e, 0xe1, 0x5a, 0xd9, 0x69, 0xef, 0x02, - 0x33, 0x6d, 0xb7, 0x80, 0xa8, 0x3c, 0x21, 0xfd, 0xbe, 0xfd, 0x86, 0xfa, 0x17, 0xec, 0x25, 0xd8, 0x5f, 0x15, 0x55, - 0x98, 0x48, 0xa5, 0xf9, 0xbe, 0x62, 0x27, 0x03, 0x15, 0x71, 0x78, 0xc7, 0x91, 0xa2, 0x8d, 0xca, 0x65, 0xd9, 0x93, - 0x65, 0xc3, 0x57, 0xe2, 0x9a, 0x3b, 0x3f, 0xae, 0x4a, 0xca, 0xbc, 0xca, 0x56, 0x8a, 0xfd, 0x9b, 0x71, 0xcd, 0xfd, - 0x81, 0xf5, 0x67, 0xf3, 0x15, 0x5c, 0x5b, 0xbd, 0x77, 0x4d, 0xae, 0x11, 0x39, 0x4b, 0x28, 0x97, 0xd4, 0x36, 0x0f, - 0x6f, 0xe9, 0xfb, 0xfc, 0xea, 0xdb, 0x4c, 0xa7, 0xf1, 0x59, 0x85, 0x85, 0x0b, 0xd1, 0x8a, 0xe0, 0xd0, 0x90, 0x8b, - 0xe6, 0x11, 0x60, 0xae, 0x7d, 0xb6, 0x82, 0x82, 0xd4, 0xe7, 0x15, 0x7a, 0xb7, 0x42, 0xc2, 0x4b, 0xcd, 0x2e, 0x3d, - 0x0c, 0xa4, 0x8c, 0xdb, 0x43, 0x4b, 0x98, 0xb4, 0xbc, 0x08, 0xef, 0xbd, 0xe6, 0x26, 0xf7, 0x3c, 0xc4, 0xe8, 0x45, - 0x9e, 0x9d, 0x80, 0xb1, 0xee, 0x92, 0x9d, 0x0d, 0x4f, 0xfc, 0x86, 0xe7, 0xac, 0x45, 0xa3, 0xe9, 0x92, 0x25, 0xfd, - 0x7e, 0x0c, 0x26, 0xde, 0x29, 0xcb, 0xe1, 0x57, 0xbe, 0xa0, 0x6b, 0x06, 0x98, 0x62, 0xf4, 0x12, 0x12, 0x52, 0x44, - 0x22, 0x59, 0xab, 0x93, 0xe4, 0x13, 0xdd, 0x05, 0x60, 0xf4, 0xcb, 0x59, 0x1a, 0x2d, 0xf7, 0x9a, 0x59, 0x20, 0x79, - 0x86, 0xbe, 0xeb, 0x60, 0x7b, 0x63, 0x1f, 0xa4, 0x9c, 0x1f, 0x8b, 0xe9, 0x60, 0xc0, 0x89, 0x86, 0x1b, 0x2f, 0x95, - 0xb8, 0x56, 0xb7, 0xb8, 0x63, 0x18, 0x4b, 0x7d, 0x5b, 0xc4, 0xe0, 0x80, 0x5d, 0xb4, 0xb2, 0xdb, 0x07, 0xd8, 0x57, - 0x8e, 0x77, 0xa9, 0xb2, 0x3b, 0x3d, 0x66, 0x9a, 0xcb, 0x56, 0x93, 0x4e, 0x2a, 0xf6, 0x13, 0xf9, 0x26, 0x77, 0xd0, - 0xe5, 0x72, 0xac, 0x79, 0xcb, 0x01, 0xa8, 0xe8, 0x47, 0x8a, 0xea, 0x7e, 0x86, 0x23, 0xcc, 0x83, 0x75, 0x9b, 0x4f, - 0x0e, 0x4d, 0x81, 0x43, 0xe4, 0x49, 0x1b, 0x4d, 0x01, 0xdd, 0xbb, 0x78, 0xdc, 0xd5, 0x6f, 0x4b, 0x77, 0x81, 0x12, - 0xed, 0x54, 0xdc, 0xf0, 0x63, 0xa2, 0x4e, 0x67, 0xda, 0x10, 0xfa, 0x57, 0x46, 0xdc, 0x5f, 0x1a, 0x57, 0xf1, 0xa6, - 0x77, 0xf9, 0x8c, 0x43, 0x9d, 0xdd, 0x10, 0x0a, 0xc0, 0x55, 0x7b, 0x3a, 0x75, 0x63, 0x48, 0xaf, 0x94, 0xe8, 0x36, - 0x38, 0xd8, 0x5e, 0x9f, 0x71, 0x14, 0xfd, 0x18, 0x35, 0xf2, 0x6d, 0x24, 0x1e, 0xcb, 0x41, 0xfc, 0xb8, 0xa0, 0xcb, - 0x48, 0x3c, 0x2e, 0x06, 0xf1, 0x63, 0x59, 0xd7, 0xbb, 0xe7, 0xca, 0xfe, 0x3e, 0x22, 0xcf, 0xba, 0xb3, 0x97, 0x4a, - 0xd8, 0x18, 0x78, 0x76, 0x2d, 0x20, 0x9c, 0x82, 0x27, 0xb2, 0xb5, 0xf4, 0xa1, 0x73, 0xbb, 0x8f, 0x2d, 0x93, 0x04, - 0x41, 0xcf, 0xdb, 0x6c, 0x12, 0xc5, 0xce, 0x36, 0x8f, 0x3e, 0x9c, 0x02, 0x09, 0xdd, 0x6e, 0x9b, 0x75, 0xb5, 0x06, - 0x14, 0xd3, 0x70, 0xcc, 0x0f, 0x8b, 0xd1, 0xad, 0xef, 0xae, 0x7f, 0x58, 0x8c, 0x96, 0x64, 0x38, 0x31, 0x93, 0x1f, - 0x9f, 0x8c, 0x67, 0x71, 0x34, 0xa9, 0x3b, 0x4e, 0x0b, 0x8d, 0x7f, 0xea, 0xdd, 0x42, 0x11, 0x38, 0x15, 0x23, 0x38, - 0x72, 0x2a, 0x94, 0x93, 0x52, 0x03, 0xc3, 0xff, 0xa0, 0xda, 0xd1, 0xa6, 0xbd, 0x8e, 0xab, 0x64, 0x99, 0x89, 0x2b, - 0x1d, 0x3e, 0x5c, 0x47, 0x17, 0xb7, 0x01, 0xed, 0xbc, 0xcb, 0xb4, 0xe3, 0xd7, 0x49, 0x83, 0x9e, 0xb8, 0x9a, 0x19, - 0x70, 0xeb, 0x7e, 0x84, 0x66, 0x08, 0x8c, 0x96, 0xe7, 0xef, 0x10, 0x73, 0xfb, 0x17, 0x65, 0xf3, 0xab, 0x68, 0x9f, - 0x23, 0x23, 0x65, 0x9b, 0x8c, 0x54, 0x60, 0x84, 0x29, 0x45, 0x12, 0x57, 0x21, 0x04, 0xb2, 0xff, 0x9c, 0xe2, 0x5a, - 0x2c, 0xbd, 0xd7, 0x20, 0x4c, 0xb0, 0x5d, 0xd0, 0x7e, 0x75, 0x3b, 0xb7, 0x95, 0x16, 0x7b, 0xa4, 0xbe, 0xcf, 0x9d, - 0xed, 0x8a, 0x26, 0x7f, 0x9f, 0x37, 0xa0, 0x0d, 0x20, 0xca, 0x7d, 0x7d, 0x54, 0x02, 0x27, 0x23, 0x6e, 0x28, 0x31, - 0x7a, 0x41, 0x57, 0x27, 0x72, 0xcf, 0x4e, 0xcd, 0x9b, 0x8a, 0x99, 0x8a, 0x2b, 0xdf, 0xec, 0x99, 0xff, 0x60, 0x28, - 0xa8, 0x00, 0x03, 0x6f, 0x73, 0xc6, 0xa3, 0x03, 0xdd, 0xad, 0xd1, 0x69, 0xc1, 0x66, 0x41, 0x5d, 0xd6, 0x6d, 0x1b, - 0x0f, 0x1a, 0x71, 0x50, 0x14, 0xab, 0x42, 0x8d, 0x84, 0x27, 0x02, 0x01, 0x53, 0x76, 0xcd, 0x23, 0x23, 0xa8, 0xe9, - 0x4d, 0x28, 0x6c, 0x28, 0xf8, 0xab, 0x44, 0x35, 0xbd, 0x09, 0x6d, 0x32, 0x71, 0x9a, 0x41, 0x04, 0x33, 0x62, 0xbb, - 0xdf, 0x02, 0xda, 0xdc, 0x9a, 0xd1, 0xa6, 0xae, 0xad, 0xb6, 0x0a, 0xb9, 0xa4, 0x48, 0x59, 0xfe, 0x3b, 0x35, 0x15, - 0x94, 0xd4, 0x72, 0xd1, 0x9b, 0x34, 0x5d, 0xf4, 0x78, 0x66, 0x24, 0x81, 0xca, 0x2d, 0x77, 0x8c, 0xfe, 0x10, 0x16, - 0x78, 0xc4, 0xc4, 0x89, 0x05, 0x73, 0xab, 0x13, 0x96, 0xcd, 0xc5, 0x62, 0xb4, 0x92, 0x10, 0x36, 0xf8, 0x98, 0x65, - 0xf3, 0x52, 0x3f, 0x84, 0xbe, 0xb0, 0xf4, 0x2d, 0xd8, 0xc5, 0x06, 0x2b, 0x59, 0x06, 0xe0, 0x7b, 0x41, 0x37, 0x2b, - 0x59, 0x46, 0x52, 0x75, 0x3f, 0xae, 0xb1, 0x04, 0x95, 0x56, 0xa8, 0xb4, 0xa4, 0xc6, 0x82, 0xc0, 0x57, 0x55, 0x97, - 0x0f, 0xc9, 0xae, 0x02, 0xf5, 0xd4, 0x51, 0x03, 0x4e, 0x81, 0xaa, 0x02, 0x0b, 0x92, 0xa0, 0x32, 0x74, 0x55, 0x60, - 0x5a, 0x81, 0x69, 0xa6, 0x0a, 0x17, 0x65, 0x76, 0x28, 0xcd, 0x7a, 0xc9, 0x67, 0xf1, 0x20, 0x4c, 0x86, 0x31, 0x79, - 0x8c, 0x50, 0xfb, 0x87, 0x79, 0x14, 0x6b, 0xb9, 0xe4, 0xca, 0xf9, 0xc5, 0xdf, 0x7e, 0xc2, 0x5e, 0xf7, 0x1c, 0x83, - 0x05, 0x38, 0x4b, 0xdb, 0xeb, 0x4c, 0xbc, 0x93, 0xad, 0xe0, 0x38, 0x98, 0x45, 0x39, 0xac, 0x7a, 0x72, 0x44, 0x73, - 0x91, 0x6b, 0xef, 0x22, 0x44, 0x0e, 0x32, 0x7b, 0x0c, 0xb0, 0x1b, 0xe1, 0xeb, 0xd0, 0xda, 0xdc, 0xea, 0x0a, 0xf1, - 0x37, 0x4a, 0x24, 0x7e, 0x94, 0xf2, 0xe3, 0x7a, 0xa5, 0x72, 0x55, 0x06, 0x8f, 0x55, 0x37, 0x83, 0x67, 0xda, 0xf7, - 0x58, 0xfb, 0xb7, 0xb6, 0x9b, 0xe3, 0xbd, 0x07, 0x0f, 0x5a, 0xff, 0x5b, 0x4f, 0x42, 0x68, 0xaf, 0x9c, 0xa4, 0xee, - 0xa8, 0xd1, 0x33, 0x93, 0x35, 0xa2, 0x12, 0xa6, 0x76, 0xa7, 0x72, 0x0c, 0xd4, 0x74, 0x00, 0xd7, 0x12, 0x35, 0x41, - 0x4f, 0x0a, 0x36, 0x86, 0x23, 0xce, 0xe2, 0xa0, 0x1d, 0xc7, 0x28, 0x5e, 0xce, 0x95, 0x78, 0x39, 0x3f, 0x61, 0x1c, - 0xa0, 0xb5, 0x00, 0xa9, 0x5e, 0xc3, 0x7e, 0xe6, 0x0a, 0x16, 0xd8, 0xdc, 0xf9, 0x8e, 0x2c, 0x90, 0x21, 0x4e, 0x36, - 0xc7, 0xc9, 0x1e, 0xd7, 0x7a, 0xee, 0x05, 0x3e, 0x4e, 0xea, 0x85, 0x57, 0x57, 0xd9, 0xae, 0x6b, 0xc9, 0xca, 0x79, - 0x31, 0x98, 0x40, 0x50, 0x96, 0x72, 0x5e, 0x0c, 0x27, 0x0b, 0x9a, 0xc3, 0x8f, 0x45, 0x03, 0x1d, 0x62, 0x39, 0x48, - 0xe0, 0xd2, 0xd9, 0x63, 0xc0, 0x1b, 0x4a, 0x2d, 0xee, 0xc6, 0x3a, 0x72, 0xac, 0xa3, 0x38, 0x0c, 0x63, 0xc0, 0x95, - 0x75, 0x02, 0xef, 0xfd, 0xd7, 0xc7, 0x26, 0x20, 0xab, 0x76, 0x85, 0x57, 0xa3, 0xdc, 0x75, 0xa5, 0xd1, 0x97, 0x94, - 0x9e, 0xf0, 0x82, 0xa7, 0x92, 0xed, 0xb6, 0x67, 0xe0, 0x6c, 0x89, 0x87, 0xc4, 0x3b, 0x46, 0xf4, 0x62, 0xda, 0xc8, - 0xcc, 0x09, 0x9c, 0xd9, 0xee, 0xb2, 0x8d, 0xf9, 0xb1, 0x03, 0x1c, 0x2c, 0x82, 0x90, 0xb8, 0x21, 0x0c, 0x13, 0x3b, - 0x29, 0x87, 0x5a, 0x08, 0xd7, 0xb5, 0xf0, 0x3a, 0x4e, 0xcb, 0x18, 0x5c, 0xa4, 0xb5, 0x6d, 0xe2, 0x1e, 0xba, 0xee, - 0xf9, 0x31, 0xb7, 0x3a, 0x46, 0x5b, 0x48, 0xbf, 0x1d, 0x9d, 0x3e, 0x70, 0x18, 0x80, 0xa6, 0x07, 0xb3, 0xaa, 0x7d, - 0x26, 0x71, 0x73, 0xda, 0x09, 0x42, 0x22, 0x10, 0x45, 0xe9, 0x8c, 0x30, 0xfd, 0x3b, 0xcd, 0x65, 0x15, 0xad, 0x1e, - 0xe4, 0x99, 0x43, 0x9e, 0x85, 0xde, 0xf6, 0xa0, 0x55, 0x73, 0x37, 0x18, 0x27, 0x6e, 0xb7, 0x77, 0xfe, 0xdf, 0xb2, - 0xae, 0xad, 0xd6, 0x88, 0xc7, 0xed, 0xea, 0x07, 0x8d, 0xbd, 0xda, 0x53, 0x31, 0x60, 0x56, 0xd2, 0x3b, 0xa3, 0x4a, - 0x5e, 0x64, 0xbc, 0xc4, 0x93, 0x6a, 0xd5, 0xf0, 0xf1, 0xbe, 0xcd, 0x46, 0xe6, 0x81, 0x4c, 0x01, 0xf1, 0xfc, 0x36, - 0x35, 0xea, 0xe3, 0x14, 0x25, 0xe0, 0xef, 0x74, 0x7c, 0x23, 0xfa, 0xd1, 0xbe, 0xb8, 0xe2, 0xd5, 0xdb, 0x5b, 0x61, - 0x5e, 0x3c, 0xb7, 0x3a, 0x7f, 0xfa, 0xba, 0xf0, 0xa1, 0xc3, 0x51, 0x7b, 0x07, 0x45, 0x96, 0x4c, 0x9c, 0x4c, 0x8c, - 0xac, 0x4d, 0xcc, 0x3e, 0x2a, 0xb8, 0x98, 0xa8, 0x42, 0xcf, 0x3a, 0x7b, 0xc2, 0x14, 0xa0, 0x6f, 0x1c, 0xa3, 0x92, - 0x31, 0x2c, 0x18, 0xa8, 0xd3, 0x94, 0x10, 0x3d, 0x14, 0x33, 0x8c, 0x57, 0x0c, 0xa0, 0x30, 0x85, 0x02, 0x51, 0x74, - 0xf6, 0xe1, 0x40, 0x13, 0xfa, 0xfd, 0xdb, 0x54, 0x67, 0xa0, 0x65, 0x3d, 0x2d, 0x40, 0x54, 0x07, 0xd1, 0x56, 0x79, - 0x11, 0xfe, 0xb0, 0xa4, 0x65, 0x46, 0x97, 0x82, 0xa6, 0x82, 0x26, 0x19, 0xbd, 0xe4, 0x4a, 0x54, 0x7c, 0x29, 0x98, - 0xa2, 0xed, 0x86, 0xb0, 0xff, 0xd0, 0xa0, 0xeb, 0xad, 0x58, 0x6b, 0x68, 0x77, 0x82, 0x8c, 0xd0, 0x7c, 0xa1, 0x83, - 0x90, 0xa1, 0x72, 0x12, 0xba, 0x56, 0x69, 0xbc, 0x02, 0x97, 0x4c, 0xb3, 0xd1, 0x32, 0x2e, 0xc3, 0xc0, 0x7e, 0x15, - 0x58, 0x4c, 0x0e, 0x4c, 0x3a, 0x5b, 0x5f, 0x3c, 0x93, 0xd7, 0x2b, 0x29, 0xb8, 0xa8, 0x14, 0x44, 0xbf, 0xc1, 0x7d, - 0x37, 0x71, 0xd5, 0x59, 0xb3, 0x56, 0xfa, 0xd0, 0xb7, 0x3e, 0x6b, 0xe3, 0xbe, 0x30, 0x38, 0x06, 0x3b, 0x1f, 0x11, - 0x03, 0x69, 0x50, 0xe9, 0x16, 0x87, 0x26, 0x40, 0x97, 0x0e, 0x29, 0x64, 0xc9, 0x54, 0xa6, 0x4a, 0x50, 0xf1, 0x8d, - 0xdf, 0x4b, 0x59, 0x8d, 0xfe, 0x5c, 0xf3, 0xe2, 0xfe, 0x8c, 0xe7, 0x1c, 0xc7, 0x28, 0x48, 0x62, 0x71, 0x13, 0x97, - 0x01, 0xf1, 0x2d, 0xaf, 0x82, 0xa3, 0xd4, 0x84, 0x8d, 0xd9, 0xa9, 0x1a, 0xb5, 0x5e, 0x05, 0xfa, 0xca, 0x28, 0xdf, - 0x18, 0x0c, 0x4d, 0x44, 0x15, 0xf4, 0xbd, 0x56, 0xf7, 0xb4, 0xba, 0x61, 0x01, 0xf1, 0xe7, 0x4a, 0x2f, 0xd4, 0x7a, - 0xdd, 0x8c, 0xb9, 0x61, 0x22, 0x04, 0x8d, 0xbe, 0xaa, 0x17, 0x0e, 0x3f, 0x7f, 0xa3, 0x2c, 0x89, 0xe0, 0xc5, 0x26, - 0x5d, 0x17, 0x26, 0x96, 0x06, 0xd5, 0x01, 0x73, 0xa3, 0x4d, 0xce, 0xaf, 0x40, 0xf4, 0xe7, 0xac, 0x88, 0x26, 0x75, - 0x4d, 0x15, 0x82, 0x61, 0xb4, 0xb9, 0x6b, 0xa4, 0xd3, 0x7b, 0xf0, 0x72, 0x33, 0xd6, 0x48, 0xda, 0xd3, 0xb1, 0xa6, - 0x05, 0x2f, 0x57, 0x52, 0x94, 0x10, 0xdd, 0xb9, 0x37, 0xa6, 0xd7, 0x71, 0x26, 0xaa, 0x38, 0x13, 0xa7, 0xe5, 0x8a, - 0x27, 0xd5, 0x7b, 0xa8, 0x50, 0x1b, 0xe3, 0x60, 0xeb, 0xd5, 0xa8, 0xab, 0x70, 0xc8, 0xaf, 0x2e, 0x5f, 0xdc, 0xad, - 0x62, 0x91, 0xc2, 0xa8, 0xd7, 0xfb, 0x5e, 0x34, 0xa7, 0x63, 0x15, 0x17, 0x5c, 0x98, 0xa8, 0xc5, 0xb4, 0x62, 0x01, - 0xd7, 0x19, 0x03, 0xca, 0x55, 0xec, 0xce, 0x4c, 0xc5, 0x32, 0x8c, 0xcb, 0xf2, 0xc7, 0xac, 0xc4, 0x3b, 0x00, 0xb4, - 0x06, 0x4e, 0x8b, 0x99, 0x01, 0x01, 0xb9, 0xcf, 0x0d, 0x2e, 0x02, 0x0b, 0x8e, 0x9e, 0x8c, 0x57, 0x77, 0x01, 0xf5, - 0xde, 0x48, 0x75, 0x3d, 0x64, 0xc1, 0x78, 0xf4, 0x34, 0x70, 0xc8, 0x21, 0xfe, 0x47, 0x4f, 0x8e, 0xf6, 0x7f, 0x33, - 0x09, 0x48, 0x3d, 0x05, 0x55, 0x85, 0x51, 0x88, 0xc2, 0xb4, 0xbf, 0x5e, 0xab, 0x5b, 0xee, 0xdb, 0x8b, 0x92, 0x17, - 0x37, 0x10, 0xad, 0x9d, 0x4c, 0x33, 0x20, 0xe7, 0x52, 0x25, 0xc0, 0xa2, 0x88, 0xab, 0xaa, 0xc8, 0x2e, 0xc0, 0x44, - 0x09, 0x0d, 0xc0, 0xcc, 0xd3, 0x4b, 0x74, 0xf8, 0x88, 0xe6, 0x01, 0xf6, 0x29, 0x58, 0xd4, 0xa4, 0x2e, 0xa1, 0xb0, - 0xe4, 0x00, 0x83, 0xd5, 0xa9, 0xb8, 0xd2, 0x8e, 0x53, 0xaf, 0x7e, 0x8f, 0x96, 0x12, 0x63, 0xcd, 0xea, 0x79, 0x8a, - 0x2f, 0x4a, 0x99, 0xaf, 0x2b, 0xd0, 0x9e, 0x5f, 0x56, 0xd1, 0xd1, 0x93, 0xd5, 0xdd, 0x54, 0x75, 0x23, 0x82, 0x5e, - 0x4c, 0x15, 0xce, 0x5b, 0x12, 0xe7, 0x49, 0x38, 0x19, 0x8f, 0xbf, 0x38, 0x18, 0x1e, 0x40, 0x32, 0x99, 0xfe, 0x35, - 0x54, 0x8e, 0x5c, 0xc3, 0xc9, 0x78, 0x5c, 0xff, 0x5e, 0x9b, 0x30, 0xdf, 0xa6, 0x9e, 0x67, 0xbf, 0x1f, 0xab, 0xf5, - 0x7f, 0x72, 0x7c, 0xa8, 0x7f, 0xfc, 0x5e, 0xd7, 0xd3, 0xd7, 0x45, 0x38, 0xff, 0x57, 0xa8, 0xd6, 0xf7, 0x69, 0x51, - 0xc4, 0xf7, 0x35, 0x44, 0x36, 0x15, 0xce, 0xbb, 0x86, 0x7a, 0x64, 0x81, 0x1e, 0x91, 0xe9, 0xa5, 0x60, 0xf0, 0xcd, - 0xfb, 0x2a, 0x0c, 0x78, 0xb9, 0x1a, 0x72, 0x51, 0x65, 0xd5, 0xfd, 0x10, 0xf3, 0x04, 0xf8, 0xa9, 0x19, 0xc7, 0x67, - 0x85, 0x21, 0xbe, 0x97, 0x05, 0xe7, 0x7f, 0xf1, 0x50, 0x19, 0x8b, 0x8f, 0xd1, 0x58, 0x7c, 0x4c, 0x55, 0x37, 0x26, - 0x5f, 0x53, 0xdd, 0xb7, 0xc9, 0xd7, 0x60, 0x92, 0x95, 0xb5, 0xbf, 0x51, 0xc6, 0x9a, 0xd1, 0x98, 0xde, 0xbc, 0xcc, - 0xb3, 0x15, 0x5c, 0x0a, 0x96, 0xfa, 0x47, 0x4d, 0xe8, 0x7b, 0xde, 0xce, 0x3e, 0x1a, 0x8d, 0x9e, 0x15, 0x74, 0x34, - 0x1a, 0x7d, 0xcc, 0x6a, 0x42, 0x57, 0xa2, 0xe3, 0xfd, 0x7b, 0x4e, 0x2f, 0x64, 0x7a, 0x1f, 0x05, 0x01, 0x5d, 0x66, - 0x69, 0xca, 0x85, 0x2a, 0xeb, 0x2c, 0x6d, 0xe7, 0x55, 0x2d, 0x44, 0x20, 0x24, 0xdd, 0x46, 0x84, 0x64, 0x22, 0xf4, - 0xed, 0x4e, 0xcf, 0x46, 0xa3, 0xd1, 0x59, 0x6a, 0xaa, 0x75, 0x17, 0x94, 0xd7, 0x68, 0x4e, 0xe1, 0xfc, 0x14, 0xc0, - 0x1a, 0xc9, 0x44, 0x7f, 0x39, 0xfc, 0xef, 0xe1, 0x6c, 0x3e, 0x1e, 0x7e, 0x33, 0x5a, 0x3c, 0x3e, 0xa4, 0x41, 0xe0, - 0x83, 0x78, 0x87, 0xda, 0xba, 0x65, 0x5a, 0x1e, 0x8f, 0xa7, 0xa4, 0x1c, 0xb0, 0x27, 0xd6, 0xb7, 0xe8, 0x8b, 0x27, - 0x80, 0xcc, 0x8a, 0x22, 0xe5, 0xc0, 0x49, 0x43, 0xf1, 0x6a, 0xf6, 0x4a, 0x00, 0x5e, 0x9c, 0x8d, 0xec, 0x60, 0xb4, - 0xa2, 0xe3, 0x08, 0xca, 0xab, 0xad, 0xa9, 0x48, 0x8f, 0xb1, 0xcc, 0x44, 0x49, 0x1d, 0x4f, 0xcb, 0xdb, 0xac, 0x4a, - 0x96, 0x18, 0xe8, 0x29, 0x2e, 0x79, 0xf0, 0x45, 0x10, 0x95, 0xec, 0xe8, 0xe9, 0x54, 0xc1, 0x1d, 0x63, 0x52, 0xca, - 0xaf, 0x20, 0xf1, 0x9b, 0x31, 0x42, 0xc2, 0x12, 0xed, 0xc1, 0x89, 0x35, 0xbe, 0xcc, 0x65, 0x0c, 0x1e, 0xad, 0xa5, - 0xe6, 0xe1, 0xec, 0xc9, 0x68, 0xed, 0x51, 0x5a, 0xcd, 0x91, 0xd0, 0x9c, 0x50, 0x32, 0x79, 0x58, 0x52, 0xf9, 0xc5, - 0x04, 0xbd, 0xa4, 0xc0, 0xcd, 0x3c, 0x82, 0xe3, 0xdf, 0x5a, 0x7a, 0xe8, 0xe5, 0x93, 0xb2, 0xc3, 0xf9, 0xff, 0x2e, - 0xe9, 0x62, 0x70, 0xe8, 0x86, 0xe6, 0xad, 0x76, 0xe7, 0xad, 0x90, 0x71, 0xac, 0xc2, 0x67, 0x29, 0xb1, 0xc6, 0xb8, - 0x9c, 0x9d, 0x6c, 0x4c, 0x77, 0x46, 0x55, 0x91, 0x5d, 0x87, 0x44, 0xf7, 0xca, 0x81, 0x84, 0x06, 0x51, 0x36, 0xc2, - 0xf5, 0x03, 0xd6, 0x33, 0x5e, 0x27, 0x6f, 0x78, 0x51, 0x65, 0x89, 0x7a, 0x7f, 0xd3, 0x78, 0x5f, 0xd7, 0x26, 0xa0, - 0xea, 0xbb, 0x82, 0xc1, 0x3c, 0xbf, 0x2d, 0x00, 0xc4, 0x14, 0x69, 0x80, 0x4f, 0x30, 0x83, 0xa0, 0x76, 0xcd, 0xbc, - 0x6a, 0x04, 0xdf, 0x80, 0xaf, 0xde, 0x15, 0x80, 0x41, 0x12, 0x82, 0x14, 0x19, 0x42, 0x03, 0x81, 0x40, 0xc3, 0x90, - 0x0b, 0x0c, 0x7e, 0xe2, 0xc5, 0x91, 0x54, 0x4e, 0x89, 0x3c, 0x0c, 0xf0, 0x47, 0x40, 0x55, 0x00, 0x12, 0xe3, 0x71, - 0x08, 0x2f, 0xd4, 0x2f, 0xf7, 0x46, 0xed, 0x11, 0xf6, 0x3a, 0x0d, 0x21, 0xd8, 0x10, 0x3e, 0x04, 0xb0, 0xa4, 0x08, - 0x7d, 0x8b, 0x5c, 0x46, 0x18, 0x5c, 0xe6, 0xd9, 0x4a, 0x27, 0x55, 0xa3, 0x8e, 0xe6, 0x43, 0xa9, 0x1d, 0xc9, 0x01, - 0xf5, 0xd2, 0x63, 0x4c, 0x2f, 0x54, 0xba, 0x2a, 0xca, 0x19, 0xe5, 0xbc, 0xd3, 0x13, 0xe3, 0xc2, 0x16, 0x72, 0x88, - 0x84, 0xf3, 0xae, 0x50, 0xa1, 0x70, 0xf8, 0x02, 0xc0, 0xc0, 0x40, 0xda, 0xb1, 0x1b, 0xef, 0x46, 0x65, 0x3f, 0xe7, - 0xec, 0xf0, 0xbf, 0xe7, 0xf1, 0xf0, 0xaf, 0xf1, 0xf0, 0x9b, 0xc5, 0x20, 0x1c, 0xda, 0x9f, 0xe4, 0xf1, 0xa3, 0x43, - 0xfa, 0x92, 0x5b, 0x2e, 0x0d, 0x16, 0x7e, 0x23, 0xd8, 0x8f, 0x5a, 0x09, 0x41, 0x14, 0xe0, 0x0d, 0xcb, 0xad, 0xc6, - 0x09, 0x00, 0x1e, 0x06, 0xff, 0x15, 0xa0, 0xd1, 0x94, 0xbb, 0x78, 0x81, 0xbe, 0x44, 0xfd, 0x3e, 0xf9, 0xaa, 0x61, - 0x30, 0x08, 0xe2, 0x1a, 0x15, 0x13, 0x86, 0xe8, 0x32, 0x26, 0x0a, 0x06, 0xd9, 0x66, 0xdf, 0x6e, 0x7b, 0x6d, 0x49, - 0x18, 0x7e, 0xe9, 0x67, 0x9a, 0x98, 0x79, 0x87, 0x1b, 0xdb, 0x4a, 0xae, 0x42, 0xc4, 0x0a, 0xd4, 0xbf, 0x72, 0x06, - 0xb1, 0x37, 0x6f, 0x32, 0xf0, 0xe9, 0xb0, 0x5f, 0x8c, 0x67, 0xc0, 0x46, 0xc1, 0x9d, 0xaf, 0xe0, 0x97, 0x19, 0xb8, - 0x79, 0x8b, 0x18, 0x05, 0x0e, 0x76, 0x49, 0xf4, 0xfb, 0xbd, 0x3c, 0x0b, 0x73, 0x8d, 0x3b, 0x9d, 0xd7, 0x46, 0x0d, - 0x81, 0x3a, 0x72, 0x50, 0x3f, 0xe8, 0x21, 0x18, 0xaa, 0x21, 0x28, 0x3a, 0xda, 0xe2, 0xea, 0xb5, 0xf5, 0x14, 0xa6, - 0xb7, 0xaa, 0xbe, 0x62, 0xf4, 0x87, 0xcc, 0x04, 0x16, 0xd2, 0xae, 0x39, 0xd6, 0x35, 0xc7, 0x48, 0x7b, 0xfa, 0x7d, - 0xd1, 0x20, 0x3f, 0x9d, 0x85, 0x07, 0x81, 0x2a, 0x55, 0xee, 0x94, 0x45, 0xb9, 0x2d, 0xcd, 0x1b, 0xc3, 0x9a, 0xe6, - 0x99, 0x8d, 0x73, 0x33, 0xeb, 0xf5, 0xc2, 0x10, 0x1d, 0x3c, 0xb1, 0x54, 0xac, 0x0d, 0xc2, 0x1d, 0x99, 0x84, 0xd1, - 0x35, 0xc8, 0x2e, 0xc3, 0x73, 0x4e, 0x90, 0x4f, 0x05, 0xf6, 0x41, 0x55, 0xeb, 0xe5, 0x84, 0xc7, 0x46, 0xbe, 0x6c, - 0x04, 0x0d, 0xf2, 0x92, 0xa2, 0xde, 0xc4, 0xed, 0xd8, 0x47, 0x2d, 0xe4, 0xca, 0x4d, 0x3d, 0xed, 0x69, 0x52, 0xd1, - 0x63, 0xbd, 0x4a, 0xfd, 0x02, 0x4b, 0x0b, 0x4b, 0x3e, 0x08, 0xed, 0x69, 0x5a, 0x81, 0x19, 0x6e, 0x6c, 0x06, 0x43, - 0x3f, 0x1c, 0x3f, 0x01, 0x9d, 0x51, 0xdb, 0x12, 0xc2, 0xd8, 0x0d, 0xc2, 0xca, 0x7b, 0x22, 0x5f, 0x3c, 0xf1, 0x2e, - 0x06, 0x21, 0x37, 0x9b, 0x59, 0x34, 0x30, 0xdd, 0xaf, 0x65, 0xb3, 0x79, 0xba, 0xb9, 0x5e, 0x94, 0x50, 0x01, 0xdb, - 0x6d, 0x25, 0x08, 0xfe, 0xfd, 0x98, 0xcd, 0xf0, 0x6f, 0xd6, 0xef, 0xf7, 0x42, 0xfc, 0xc5, 0x31, 0x98, 0xd1, 0x5c, - 0x2c, 0xd8, 0x47, 0x90, 0x31, 0x91, 0x08, 0x53, 0x95, 0x31, 0x20, 0xab, 0xc0, 0x22, 0xd0, 0x7c, 0xa0, 0x72, 0x61, - 0x26, 0x7b, 0x99, 0x73, 0x0d, 0x39, 0x6d, 0x8d, 0x53, 0x36, 0xca, 0x12, 0xe5, 0xca, 0x91, 0x8d, 0xe2, 0x3c, 0x8b, - 0x4b, 0x5e, 0x6e, 0xb7, 0xfa, 0x70, 0x4c, 0x0a, 0x0e, 0xec, 0xba, 0xa2, 0x52, 0x25, 0xeb, 0x48, 0x75, 0xe3, 0x2f, - 0xc3, 0x02, 0xf7, 0x29, 0x9f, 0x17, 0x86, 0x46, 0x1c, 0x80, 0x30, 0x83, 0xa9, 0x5b, 0x7a, 0x2f, 0x2c, 0xa0, 0x79, - 0x25, 0x21, 0x1b, 0x4c, 0xf5, 0x2c, 0x7c, 0x63, 0x26, 0xe6, 0xc5, 0x02, 0xc2, 0xea, 0x14, 0x0b, 0xcd, 0x6c, 0xd2, - 0x84, 0xc5, 0x00, 0x9b, 0x17, 0x93, 0x29, 0xc4, 0x77, 0x57, 0xe5, 0xc4, 0x0b, 0x73, 0xdf, 0x4e, 0x1c, 0x72, 0x08, - 0xbc, 0xaa, 0x0d, 0xba, 0x9a, 0x6d, 0x38, 0xea, 0x48, 0x39, 0x31, 0xf9, 0xfd, 0x54, 0x41, 0x88, 0x3b, 0x71, 0x24, - 0x5c, 0xde, 0x6c, 0x17, 0x5e, 0x74, 0x20, 0xe8, 0xa8, 0xc1, 0x29, 0x3f, 0x31, 0x38, 0x1a, 0x93, 0x74, 0xe3, 0x9d, - 0x20, 0x45, 0x18, 0x93, 0x8d, 0x64, 0xd7, 0x32, 0x14, 0xf3, 0x78, 0x01, 0xca, 0xcb, 0x78, 0x01, 0x96, 0x46, 0xc6, - 0x20, 0x15, 0xe4, 0x77, 0xdc, 0x0b, 0x85, 0x45, 0x71, 0x85, 0x48, 0xcf, 0xea, 0xf7, 0x51, 0xd1, 0x0e, 0x05, 0x82, - 0xe2, 0x0e, 0x65, 0x9e, 0x9c, 0xf5, 0x58, 0x20, 0xb1, 0x21, 0x60, 0x7c, 0xa5, 0xd3, 0x54, 0x6b, 0xdd, 0x1b, 0x33, - 0x0f, 0x7c, 0x9a, 0x8d, 0x84, 0xac, 0xce, 0x2f, 0x41, 0xa4, 0xe4, 0xa3, 0xe3, 0x23, 0xbf, 0x88, 0x3b, 0xcb, 0xbc, - 0xb5, 0x2d, 0x2a, 0xd9, 0xc9, 0x06, 0x40, 0x0b, 0x75, 0xf4, 0x2c, 0x25, 0xb7, 0x29, 0x49, 0xed, 0x36, 0x05, 0xac, - 0x24, 0x7f, 0x01, 0x43, 0xf0, 0xb5, 0x03, 0xe1, 0x74, 0xac, 0x10, 0xaf, 0x69, 0x8a, 0x48, 0x93, 0x61, 0x49, 0x71, - 0x6c, 0x4b, 0x44, 0x41, 0xb5, 0x65, 0xd9, 0xc1, 0x30, 0x51, 0x82, 0x9f, 0xa7, 0x1e, 0x25, 0x0a, 0x02, 0xaa, 0x87, - 0x1c, 0x24, 0xd8, 0xb6, 0x81, 0xf0, 0x80, 0x3c, 0xa2, 0x37, 0xd6, 0xdf, 0x65, 0x9d, 0x67, 0x17, 0x9a, 0xe7, 0x72, - 0xbd, 0x2b, 0xcc, 0x18, 0xe1, 0x49, 0x66, 0xc2, 0x06, 0x78, 0xe7, 0x99, 0x51, 0xdb, 0xf4, 0x3c, 0xbc, 0xb6, 0x53, - 0x8c, 0xd0, 0xb7, 0x67, 0xd0, 0x4d, 0x30, 0xaf, 0x0e, 0x9b, 0xf5, 0x4a, 0x41, 0x6a, 0x98, 0x5a, 0x34, 0x31, 0xeb, - 0x59, 0x83, 0xf2, 0xed, 0xb6, 0xa7, 0xe7, 0x6a, 0xff, 0xdc, 0x6d, 0xb7, 0x3d, 0xec, 0xd6, 0xf3, 0xb4, 0xdb, 0x2a, - 0xbe, 0x52, 0x1f, 0xb4, 0xc7, 0x9f, 0xbb, 0xf1, 0xe7, 0x06, 0xd9, 0xa4, 0x74, 0x34, 0xd3, 0xd6, 0x07, 0xe1, 0x81, - 0xd3, 0xfb, 0x46, 0x93, 0xbe, 0xcb, 0x42, 0x49, 0x57, 0xa2, 0x51, 0x5d, 0xed, 0x4c, 0x4c, 0x1f, 0x5c, 0xff, 0x0f, - 0xaf, 0x02, 0x3c, 0xe2, 0xd4, 0xce, 0xde, 0xdb, 0xa0, 0xa2, 0xd1, 0x16, 0x8e, 0x14, 0xa1, 0x07, 0x24, 0x61, 0x5f, - 0xcb, 0x5a, 0xdc, 0xe6, 0x59, 0xf6, 0x30, 0x7d, 0x7a, 0x95, 0xfa, 0x5e, 0x08, 0x6e, 0x99, 0x65, 0xe6, 0xc0, 0xab, - 0x28, 0x0e, 0x68, 0xd4, 0x45, 0xfb, 0xae, 0xb3, 0xb2, 0x04, 0xaf, 0x17, 0xb8, 0x57, 0x9e, 0x71, 0x1f, 0x7e, 0xef, - 0xaa, 0x6a, 0x6e, 0xd2, 0xb3, 0x6c, 0x9e, 0x2d, 0xb6, 0xdb, 0x10, 0xff, 0x76, 0xb5, 0xc8, 0xd1, 0xe4, 0x39, 0xe8, - 0x34, 0x31, 0x92, 0x11, 0xd3, 0x8d, 0xf3, 0x36, 0xff, 0x1b, 0xd1, 0x70, 0x9a, 0x38, 0x05, 0x7a, 0x31, 0x7b, 0x04, - 0x32, 0x29, 0x03, 0x72, 0x20, 0x66, 0x7a, 0xcd, 0x40, 0x34, 0x32, 0x11, 0x01, 0xae, 0x30, 0x36, 0x12, 0x8d, 0x4e, - 0x38, 0xa9, 0x09, 0x58, 0xb0, 0xda, 0xf2, 0x3e, 0x58, 0xda, 0x56, 0x15, 0xf7, 0xde, 0x92, 0xe6, 0xb8, 0x0e, 0x9c, - 0xaf, 0x83, 0x19, 0x62, 0x53, 0x76, 0xb5, 0x40, 0xee, 0x97, 0xd7, 0xb4, 0x37, 0xae, 0x13, 0x98, 0xb5, 0x4d, 0x6d, - 0x19, 0x3f, 0x5b, 0xfa, 0x8f, 0x7a, 0x70, 0x95, 0x31, 0xd8, 0xdc, 0x58, 0x69, 0xd8, 0x7d, 0xe3, 0xf9, 0x52, 0x40, - 0x78, 0x3a, 0x9f, 0x1e, 0x9f, 0x65, 0x1e, 0x3d, 0x06, 0xa2, 0x63, 0x3e, 0x2a, 0xdd, 0x47, 0x76, 0xf7, 0xfa, 0x01, - 0x01, 0xe7, 0x55, 0xbb, 0xa0, 0x79, 0xb9, 0x80, 0xc0, 0xaa, 0x5e, 0x79, 0x85, 0xe5, 0x33, 0x63, 0x76, 0x05, 0x64, - 0xa8, 0x20, 0x10, 0xb8, 0xbb, 0xeb, 0x5c, 0x88, 0x55, 0x87, 0x95, 0x39, 0x4d, 0xc2, 0x4e, 0x42, 0x34, 0x6f, 0x0d, - 0x66, 0xc1, 0x7f, 0x05, 0x83, 0x72, 0x10, 0x44, 0x41, 0x14, 0x04, 0x64, 0x50, 0xc0, 0x2f, 0xc4, 0x5d, 0x23, 0x18, - 0xb3, 0x05, 0x3a, 0xfc, 0x8e, 0x33, 0x9f, 0x11, 0x79, 0xd1, 0x08, 0xeb, 0xe9, 0x06, 0xe0, 0x42, 0xca, 0x9c, 0xc7, - 0xe8, 0x73, 0xf2, 0x8e, 0xb3, 0x8c, 0xd0, 0x77, 0xde, 0xa9, 0xfc, 0x88, 0x37, 0x82, 0xfd, 0xed, 0x0e, 0xdb, 0x4b, - 0x90, 0x57, 0xf4, 0xc6, 0xf4, 0x1d, 0x27, 0x51, 0xd6, 0x70, 0xa6, 0xe6, 0xd0, 0xb3, 0xca, 0xb2, 0x56, 0xd4, 0x90, - 0x1b, 0x14, 0x73, 0x23, 0xcb, 0xe4, 0x64, 0xda, 0x6a, 0x4e, 0x05, 0xae, 0x3b, 0xbb, 0x5e, 0x40, 0x72, 0x28, 0x34, - 0x4b, 0x67, 0xc3, 0x79, 0xdb, 0x96, 0x3d, 0x6f, 0x9d, 0x42, 0x5e, 0x43, 0x54, 0x34, 0x48, 0x47, 0x40, 0x0d, 0xad, - 0xb8, 0xaa, 0xc0, 0x85, 0xd9, 0xb4, 0x87, 0x9b, 0xf6, 0x98, 0x66, 0x7c, 0x80, 0x98, 0x79, 0x1c, 0x5b, 0x06, 0x76, - 0x24, 0x0e, 0xdf, 0xc7, 0xf9, 0x02, 0xed, 0xd2, 0x5b, 0x57, 0x8b, 0x47, 0x58, 0x7b, 0xde, 0x0a, 0x09, 0x01, 0xe2, - 0xd3, 0x54, 0xba, 0xdd, 0x06, 0x01, 0x0c, 0x70, 0xbf, 0xdf, 0x03, 0xae, 0xd5, 0xb0, 0x93, 0xe6, 0xd6, 0x6c, 0x89, - 0xbd, 0xa2, 0xf0, 0x18, 0x98, 0x53, 0xf3, 0x9f, 0x41, 0x40, 0xf1, 0xdc, 0x0d, 0xc1, 0xde, 0x94, 0x9d, 0x6c, 0x8a, - 0x7e, 0xff, 0x79, 0x81, 0x0f, 0x28, 0x17, 0x06, 0x31, 0xb7, 0x8e, 0xe3, 0x61, 0xd8, 0x27, 0xf5, 0x21, 0x8e, 0x45, - 0x9e, 0x85, 0x8e, 0xb0, 0x54, 0x86, 0xb0, 0x70, 0xc5, 0x48, 0x07, 0x71, 0x50, 0x93, 0xce, 0xc1, 0xaa, 0x5c, 0xf0, - 0xe5, 0x5e, 0xef, 0x0d, 0xc0, 0xa4, 0x67, 0xde, 0xb0, 0xbc, 0xf7, 0x00, 0xd1, 0x7a, 0x3d, 0x5c, 0x28, 0xee, 0xe5, - 0xcb, 0x06, 0x1a, 0x27, 0xbe, 0xb4, 0xec, 0xfa, 0x4c, 0xcb, 0x4a, 0x46, 0xa3, 0x51, 0x55, 0x2b, 0xc9, 0x87, 0x23, - 0x2f, 0x2d, 0x14, 0x4f, 0x19, 0xa7, 0x3c, 0x05, 0xcb, 0x77, 0x43, 0xe9, 0xe6, 0x0b, 0xba, 0xe2, 0x22, 0x55, 0x3f, - 0x3d, 0xf4, 0xcd, 0x06, 0x71, 0xcd, 0x9a, 0x3a, 0x1c, 0x3b, 0xfc, 0x10, 0x00, 0xd3, 0x3e, 0xcc, 0x5c, 0xba, 0x86, - 0xe9, 0x05, 0xf1, 0x6c, 0x5c, 0xf0, 0xd0, 0xe5, 0x01, 0xec, 0x43, 0x73, 0x48, 0xe2, 0xa7, 0xf0, 0x73, 0x66, 0xd2, - 0x3a, 0x3e, 0xc3, 0xd9, 0x8c, 0x4a, 0x75, 0x23, 0x68, 0xbf, 0x86, 0x44, 0x62, 0x90, 0x9e, 0x1b, 0x0c, 0x45, 0xeb, - 0x6e, 0x03, 0x57, 0x7e, 0x4b, 0xef, 0x7c, 0x1a, 0x04, 0x58, 0xdf, 0x58, 0x0c, 0x00, 0xa8, 0xe2, 0x0f, 0x54, 0x5d, - 0x99, 0x2b, 0x8a, 0x69, 0x98, 0x4a, 0xb4, 0x77, 0x1c, 0xd7, 0x51, 0xe3, 0x3a, 0x2c, 0x58, 0x69, 0x6d, 0x9b, 0xdd, - 0x5b, 0x5a, 0xd8, 0x12, 0x50, 0x2d, 0x88, 0x3b, 0x01, 0x7c, 0x68, 0xa4, 0x3a, 0x10, 0x64, 0xf7, 0xc1, 0x01, 0x00, - 0x6f, 0x78, 0x1e, 0x86, 0xf0, 0x07, 0x16, 0x0e, 0x2c, 0x4b, 0xd5, 0xcf, 0xe5, 0x34, 0x86, 0x73, 0x37, 0x57, 0x3b, - 0x7c, 0xb6, 0x04, 0xc5, 0xa6, 0x9a, 0x53, 0x73, 0xf9, 0xca, 0x1b, 0xfb, 0x3d, 0x26, 0x98, 0xc7, 0xcc, 0x36, 0xfc, - 0xd6, 0xd3, 0x6d, 0x7d, 0x83, 0xdd, 0xc0, 0x49, 0x7b, 0xe1, 0xb4, 0x17, 0xdb, 0xa5, 0x81, 0xfc, 0xab, 0x1b, 0x42, - 0x84, 0x57, 0x9a, 0x58, 0x64, 0x0d, 0x99, 0x8e, 0xc5, 0x0a, 0x51, 0x6d, 0x2a, 0x9e, 0x69, 0x03, 0x81, 0x72, 0xaa, - 0x2e, 0x4c, 0xad, 0x54, 0x26, 0x0c, 0xe2, 0x4e, 0x09, 0x8b, 0x2a, 0x03, 0x0c, 0x83, 0x0a, 0x29, 0xae, 0xad, 0xe7, - 0x2f, 0x5c, 0xbe, 0x99, 0x69, 0xb3, 0xfd, 0xf4, 0x65, 0x1e, 0x5f, 0x6d, 0xb7, 0x61, 0xf7, 0x0b, 0x30, 0x47, 0x2d, - 0x95, 0x86, 0x11, 0x9c, 0x40, 0x94, 0xe4, 0x7a, 0x4f, 0xce, 0x89, 0xe3, 0xe4, 0xda, 0xcd, 0x9b, 0xed, 0xa4, 0x18, - 0x81, 0x05, 0x9c, 0xb8, 0x48, 0x07, 0x5a, 0x2a, 0x49, 0xed, 0x29, 0xe0, 0x6d, 0x7a, 0x47, 0xa9, 0xf0, 0x6a, 0xa1, - 0x49, 0x48, 0xe5, 0xee, 0x25, 0x76, 0xd4, 0x80, 0x73, 0x52, 0x77, 0x10, 0x70, 0xda, 0xd3, 0x8d, 0xb5, 0x8a, 0x64, - 0x93, 0xe0, 0xbd, 0xd2, 0x43, 0x97, 0x68, 0xa7, 0x76, 0xb7, 0xad, 0xca, 0x16, 0x0a, 0xe6, 0x41, 0xce, 0x12, 0x75, - 0x3c, 0xa0, 0xd0, 0x45, 0x1d, 0x0d, 0xf9, 0x82, 0x14, 0x7a, 0xe5, 0x68, 0x55, 0xf3, 0xae, 0x64, 0xa0, 0x54, 0xab, - 0x20, 0xaf, 0x89, 0x75, 0x5f, 0xcb, 0x1a, 0x8b, 0x2b, 0x27, 0xa4, 0xb0, 0x09, 0x9f, 0x5b, 0x8a, 0x85, 0x59, 0xec, - 0x8d, 0xa9, 0x2f, 0x5c, 0x22, 0xb4, 0xdd, 0x6d, 0x88, 0xd1, 0x06, 0xeb, 0x66, 0xbb, 0x7d, 0x55, 0x84, 0xf3, 0x6c, - 0x41, 0xe5, 0x28, 0x4b, 0x11, 0x52, 0xcd, 0x78, 0x2c, 0xdb, 0x2e, 0x98, 0x89, 0xa1, 0xae, 0x3d, 0x5e, 0x92, 0x29, - 0xd6, 0x26, 0xc9, 0x51, 0x7c, 0x21, 0x0b, 0xb5, 0xd6, 0x08, 0xc1, 0xc3, 0xfd, 0x8f, 0x14, 0x62, 0xda, 0x99, 0x75, - 0xf7, 0xed, 0xce, 0x0d, 0xf1, 0x0f, 0x08, 0xac, 0x50, 0xb2, 0x57, 0xc5, 0xe8, 0x22, 0x13, 0x29, 0xee, 0x54, 0x15, - 0x25, 0x58, 0xad, 0x83, 0x66, 0xcb, 0xed, 0xbd, 0xd8, 0x12, 0x05, 0x88, 0xf3, 0x2c, 0x34, 0xe3, 0x59, 0x39, 0xcb, - 0x99, 0x8c, 0x62, 0x43, 0xa2, 0xd2, 0x8b, 0x12, 0xef, 0xf3, 0x34, 0xa6, 0x87, 0x6e, 0x0d, 0x82, 0xeb, 0xea, 0xce, - 0x46, 0x9a, 0x2f, 0x08, 0x51, 0x13, 0x20, 0x61, 0xa3, 0x9a, 0x53, 0xeb, 0x4a, 0x3c, 0xcc, 0x2a, 0x9f, 0xeb, 0x83, - 0xf8, 0x4a, 0x00, 0x0f, 0xeb, 0x6d, 0xef, 0x6b, 0xe1, 0xb1, 0x36, 0xf8, 0x76, 0xbb, 0xbd, 0x12, 0xf3, 0x20, 0xf0, - 0x18, 0xcd, 0x5f, 0x94, 0xc4, 0xbc, 0x37, 0xa6, 0xb0, 0xe2, 0x7d, 0x17, 0xbf, 0x6e, 0x52, 0x6b, 0x2d, 0x72, 0x77, - 0xb8, 0x3e, 0xe0, 0x79, 0x4a, 0x1c, 0xed, 0xa8, 0x9c, 0x4a, 0x6b, 0x3b, 0x80, 0x5d, 0x11, 0x18, 0x28, 0xfb, 0xfb, - 0x94, 0x6d, 0xc0, 0x3c, 0x11, 0xac, 0x8f, 0xd0, 0x6f, 0x4b, 0xe9, 0x4f, 0xc6, 0x68, 0xdc, 0x23, 0xd7, 0x55, 0x74, - 0xc4, 0x75, 0x34, 0x7b, 0x1e, 0xfd, 0xed, 0xe9, 0x98, 0x16, 0xb1, 0x48, 0xe5, 0x35, 0xa8, 0x20, 0x40, 0x19, 0x82, - 0x8e, 0x10, 0x9a, 0x1a, 0x80, 0x06, 0xc1, 0x0d, 0xc0, 0x3f, 0x3b, 0x9d, 0x28, 0x6d, 0x4d, 0x3e, 0x46, 0xab, 0x2a, - 0x72, 0xd6, 0x86, 0x76, 0x53, 0xc9, 0x21, 0x79, 0x5c, 0x02, 0xbe, 0x25, 0x36, 0x4b, 0xd9, 0xa0, 0xa8, 0xcd, 0xa6, - 0x5e, 0x2b, 0x76, 0xe4, 0xae, 0x51, 0xb4, 0x59, 0x8b, 0xda, 0x6e, 0x64, 0xbe, 0x98, 0xde, 0x59, 0x61, 0xe0, 0xd4, - 0xb4, 0xe6, 0x76, 0x07, 0x4a, 0xce, 0xd6, 0x67, 0x72, 0x13, 0x20, 0x0e, 0x30, 0x5c, 0x77, 0xf3, 0xdb, 0x05, 0xa1, - 0x77, 0xec, 0xce, 0x8a, 0x55, 0x6f, 0xad, 0x5c, 0xc4, 0xa4, 0xdd, 0x0e, 0x26, 0x70, 0x19, 0x67, 0x85, 0x7d, 0xa1, - 0xd5, 0x0d, 0x45, 0x47, 0xdb, 0xa4, 0xfd, 0xbc, 0xa3, 0xdd, 0x70, 0xc1, 0xb7, 0x62, 0x1d, 0xe7, 0x96, 0x35, 0x55, - 0x68, 0xda, 0x81, 0xde, 0x0e, 0x01, 0xcd, 0xd9, 0x98, 0x2e, 0x69, 0x8a, 0x17, 0x68, 0xba, 0x06, 0x33, 0x9d, 0x4b, - 0xe8, 0x6b, 0xb7, 0x8f, 0xf6, 0xa5, 0xea, 0x89, 0xf0, 0x96, 0x28, 0xf8, 0xb6, 0xa4, 0xe0, 0xa5, 0x96, 0xf3, 0xd8, - 0xcc, 0x21, 0xe0, 0xd3, 0xa8, 0x12, 0xbd, 0x93, 0xe2, 0x0a, 0xb4, 0x99, 0x70, 0x04, 0x9a, 0xaa, 0x11, 0x5b, 0x39, - 0xc0, 0xed, 0xc5, 0xd3, 0x80, 0x50, 0x90, 0xea, 0xae, 0xed, 0x8a, 0xbc, 0x63, 0x27, 0x9b, 0x3b, 0x30, 0x13, 0xae, - 0xd6, 0x65, 0xeb, 0x2b, 0x9b, 0xec, 0x3e, 0xae, 0x09, 0xb6, 0xdd, 0xdb, 0x20, 0xe1, 0x1d, 0xbd, 0x25, 0x9b, 0xdb, - 0x7e, 0x3f, 0x84, 0xfe, 0x10, 0xaa, 0x3b, 0x74, 0xd7, 0xd9, 0xa1, 0x3b, 0x9f, 0xf9, 0xb5, 0x7a, 0x3e, 0xe5, 0x1d, - 0xf2, 0x01, 0x4d, 0xd6, 0xe8, 0x2a, 0xbe, 0x87, 0x4d, 0x1d, 0x55, 0x54, 0x55, 0x1e, 0x25, 0x14, 0x54, 0xe2, 0x19, - 0x2f, 0xcf, 0x38, 0xc6, 0x7a, 0xd5, 0x4f, 0xef, 0x34, 0xaf, 0xb6, 0x36, 0x6b, 0xb3, 0x5c, 0x5f, 0x80, 0x85, 0xc4, - 0x05, 0x8f, 0xae, 0x35, 0x2d, 0xb9, 0xf2, 0x98, 0xfa, 0x73, 0x1c, 0x95, 0xe0, 0x32, 0xce, 0x72, 0x50, 0xe3, 0x5e, - 0x36, 0xfb, 0x1f, 0x6a, 0xdb, 0xb1, 0x65, 0xe3, 0xcc, 0xbd, 0x09, 0xc9, 0xe6, 0x7f, 0x6c, 0xa0, 0x5e, 0x87, 0x18, - 0x21, 0xd6, 0x2c, 0xe8, 0xb7, 0x0c, 0x62, 0x85, 0x06, 0xe5, 0x3a, 0x49, 0x78, 0x59, 0x06, 0x46, 0xa9, 0xb5, 0x66, - 0x6b, 0x73, 0x9e, 0x3d, 0x62, 0x27, 0x8f, 0x7a, 0x8c, 0xdd, 0x11, 0x9a, 0x68, 0x9d, 0x90, 0xa9, 0x31, 0xf2, 0xb4, - 0x40, 0xba, 0x43, 0x51, 0x76, 0x19, 0xbe, 0x45, 0x21, 0x4b, 0x7b, 0x9f, 0x9b, 0x13, 0x59, 0x7d, 0xa3, 0x8d, 0x50, - 0x22, 0x95, 0x08, 0xb2, 0xf1, 0x5b, 0x04, 0x30, 0x86, 0x66, 0x07, 0x64, 0xb3, 0x64, 0x67, 0xf4, 0xdc, 0x9a, 0x04, - 0xc1, 0xeb, 0xb7, 0x2a, 0xd1, 0x8c, 0xb2, 0x22, 0xba, 0xca, 0xe8, 0xe7, 0x3e, 0x24, 0xd1, 0x79, 0x48, 0xfc, 0xdc, - 0xb0, 0xb4, 0x6e, 0x42, 0x14, 0x33, 0x9b, 0x0d, 0xaf, 0xba, 0xfb, 0xa8, 0xb1, 0xad, 0x8c, 0x8f, 0xf9, 0x9d, 0x4d, - 0x23, 0x53, 0xe8, 0xeb, 0x70, 0xd2, 0xef, 0xc3, 0x5f, 0x4d, 0x3f, 0xf0, 0x96, 0x82, 0xbf, 0xd8, 0x23, 0x52, 0x27, - 0x2c, 0x00, 0x78, 0xc6, 0x9c, 0x57, 0xcd, 0x09, 0x7c, 0xc4, 0x4e, 0x36, 0x8f, 0xc2, 0xb3, 0xc6, 0xcc, 0xdd, 0x87, - 0x78, 0xa9, 0x4a, 0x7a, 0xde, 0x3c, 0x99, 0x81, 0x58, 0x59, 0xad, 0xf9, 0x1d, 0xb3, 0xfa, 0x04, 0x20, 0x52, 0x77, - 0xd6, 0xc1, 0x16, 0x3f, 0x36, 0x5d, 0x26, 0x9b, 0x94, 0xb5, 0x99, 0x28, 0xa5, 0x22, 0x69, 0x2e, 0x02, 0xe8, 0x37, - 0x0c, 0x47, 0x0d, 0x70, 0xe7, 0x7a, 0xec, 0xcd, 0xd0, 0x78, 0x63, 0x6a, 0xe8, 0xd9, 0x46, 0x2f, 0x6f, 0x47, 0x21, - 0xcc, 0x58, 0x44, 0x77, 0xee, 0x58, 0x0c, 0xcf, 0xe8, 0x5b, 0xa8, 0xf0, 0x75, 0x88, 0xd1, 0x85, 0x49, 0x5d, 0x4f, - 0xd7, 0x6a, 0x2b, 0xdd, 0x12, 0x9a, 0x63, 0x54, 0x23, 0xaf, 0x6d, 0xf7, 0xd4, 0x08, 0xed, 0x09, 0xe5, 0xe1, 0x1d, - 0xad, 0xe8, 0xad, 0x65, 0x11, 0x9c, 0xfc, 0xd8, 0xcb, 0x4f, 0xe8, 0x85, 0x27, 0x30, 0x29, 0xda, 0x1a, 0xc0, 0xef, - 0x51, 0x3f, 0x9c, 0xd5, 0x53, 0x2b, 0xe5, 0xf0, 0x14, 0xbe, 0x64, 0x03, 0x72, 0x05, 0xbd, 0x58, 0x63, 0x76, 0x12, - 0x83, 0x0e, 0x6a, 0x67, 0x77, 0x78, 0x93, 0x52, 0x86, 0x68, 0x8d, 0xe8, 0x20, 0xaf, 0xfe, 0x09, 0x9a, 0x3e, 0x48, - 0x0b, 0x53, 0xba, 0x46, 0x01, 0x0f, 0xe8, 0x9b, 0xfa, 0xfd, 0x1c, 0x9f, 0x6b, 0xcf, 0x32, 0x0d, 0x7b, 0xbc, 0x24, - 0x74, 0xe9, 0xc5, 0xd1, 0x02, 0x69, 0xb3, 0x63, 0x15, 0x80, 0x15, 0x49, 0xa0, 0x11, 0x09, 0x58, 0x2e, 0x79, 0xe2, - 0xb2, 0x0d, 0x1a, 0xd4, 0x44, 0x25, 0x85, 0x2c, 0x91, 0x04, 0x7e, 0x18, 0x41, 0x99, 0xa2, 0x18, 0xc4, 0xbd, 0x7a, - 0x79, 0xc5, 0x35, 0x35, 0x60, 0x4d, 0x11, 0x4c, 0xb0, 0x4e, 0xa7, 0x40, 0x6c, 0xc5, 0x7a, 0x05, 0x9e, 0xa8, 0x8e, - 0x13, 0x47, 0x96, 0x00, 0x0d, 0xf4, 0x7c, 0xe9, 0xb4, 0x5b, 0xde, 0x9e, 0x68, 0xa9, 0x62, 0x73, 0xef, 0xc5, 0xc2, - 0x72, 0x8f, 0x95, 0xbf, 0x1d, 0x68, 0x2f, 0xac, 0x76, 0x44, 0xd4, 0x60, 0x75, 0xd8, 0xb6, 0xf3, 0x43, 0x69, 0xa8, - 0xee, 0x95, 0x63, 0x02, 0x2a, 0xba, 0x8a, 0xab, 0x65, 0x94, 0x8d, 0xe0, 0xcf, 0x76, 0x1b, 0x1c, 0x06, 0x60, 0x11, - 0xfa, 0xf3, 0xfb, 0x1f, 0x23, 0x0c, 0x57, 0xf5, 0xf3, 0xfb, 0x1f, 0xb7, 0xdb, 0xa7, 0xe3, 0xb1, 0xe1, 0x0a, 0x9c, - 0x5a, 0x07, 0xf8, 0x03, 0xc3, 0x36, 0xd8, 0x25, 0xbb, 0xdd, 0x3e, 0x05, 0x0e, 0x42, 0xb1, 0x0d, 0x66, 0x17, 0x2b, - 0xc7, 0x36, 0xc5, 0x6a, 0xe8, 0x1d, 0x09, 0xd8, 0x7d, 0x3b, 0x2c, 0xc5, 0x2e, 0xf5, 0x51, 0x21, 0x29, 0xf5, 0xa2, - 0x7f, 0xd1, 0x29, 0xb0, 0xa4, 0x60, 0xca, 0x1b, 0x2c, 0xab, 0x6a, 0x55, 0x46, 0x87, 0x87, 0xf1, 0x2a, 0x1b, 0x95, - 0x19, 0x6c, 0xf3, 0xf2, 0xe6, 0x0a, 0x00, 0x26, 0x02, 0xda, 0x78, 0xb7, 0x16, 0x99, 0x79, 0xb1, 0xa0, 0xcb, 0x0c, - 0xd7, 0x24, 0x98, 0x1d, 0xe4, 0xdc, 0xea, 0x26, 0xa7, 0xc4, 0x3e, 0x80, 0x0d, 0xe6, 0x76, 0xdb, 0xe0, 0x17, 0x4e, - 0x46, 0x4f, 0x67, 0xcb, 0x4c, 0x1b, 0xb8, 0x72, 0xb3, 0xff, 0x49, 0xe4, 0xa5, 0xa1, 0xe2, 0x93, 0x4c, 0x5f, 0x64, - 0xc0, 0xe7, 0xb1, 0xbf, 0x44, 0xe8, 0xb3, 0x5c, 0x8d, 0xd6, 0x00, 0x1b, 0x9b, 0x5d, 0xde, 0x8f, 0x52, 0x0e, 0x11, - 0x3a, 0x02, 0xab, 0xae, 0x59, 0x66, 0xc4, 0xb7, 0xa9, 0xb8, 0x6f, 0xa9, 0xc2, 0xfe, 0x12, 0x9e, 0xf3, 0x0e, 0x37, - 0x8e, 0x43, 0xbd, 0x49, 0x14, 0xbe, 0x40, 0x21, 0x2a, 0x47, 0xe3, 0x42, 0x27, 0x90, 0xca, 0x3c, 0x26, 0x14, 0x73, - 0xb8, 0x77, 0x3f, 0xa7, 0xce, 0x5c, 0xc6, 0x17, 0xee, 0xbd, 0xf0, 0x65, 0x26, 0x77, 0x12, 0x40, 0xa2, 0x54, 0xed, - 0x3f, 0x7d, 0x42, 0x6a, 0xfc, 0xaf, 0x54, 0x6b, 0x00, 0x7a, 0x3f, 0x41, 0x4d, 0x8e, 0x20, 0x60, 0x2b, 0xa6, 0x7e, - 0x74, 0x01, 0x2b, 0x99, 0xff, 0x80, 0xba, 0x1d, 0xc1, 0x36, 0x2a, 0x9e, 0x50, 0x54, 0xd1, 0x82, 0xa7, 0x6b, 0x91, - 0xc6, 0x22, 0xb9, 0x8f, 0x78, 0x3d, 0xc5, 0x92, 0x98, 0x8d, 0x18, 0xf6, 0x53, 0xb3, 0x0b, 0x3f, 0x16, 0x0d, 0x93, - 0x78, 0x5a, 0xfa, 0xdb, 0xca, 0xdb, 0x4c, 0x96, 0x71, 0x46, 0xa6, 0x5c, 0x21, 0x98, 0x5b, 0x7d, 0x8f, 0x39, 0xc1, - 0x9f, 0x1c, 0x3d, 0x21, 0xf4, 0x4e, 0x4e, 0x4b, 0x04, 0xe9, 0x13, 0xa9, 0x75, 0x5d, 0xc5, 0x7e, 0x4d, 0x21, 0xaa, - 0x85, 0x60, 0x10, 0xca, 0xd4, 0xb4, 0x4f, 0xf1, 0x7d, 0xb6, 0xec, 0xbf, 0x4c, 0xd9, 0x92, 0x6c, 0x04, 0x74, 0x4c, - 0x3a, 0xef, 0x57, 0x6f, 0xcf, 0xce, 0xbc, 0xdf, 0xa0, 0x09, 0x07, 0xd5, 0x0d, 0xb4, 0xab, 0x20, 0xd3, 0x18, 0xc5, - 0x66, 0x31, 0xd6, 0x6e, 0x4d, 0x44, 0x10, 0x84, 0xbb, 0x9c, 0x85, 0xed, 0x76, 0x42, 0xbc, 0x0d, 0x24, 0x50, 0xe0, - 0xda, 0x46, 0x39, 0x09, 0x89, 0xba, 0x90, 0xe9, 0xc9, 0xba, 0x91, 0x2c, 0xd0, 0x6b, 0xec, 0x28, 0xa0, 0xa7, 0xdc, - 0x3e, 0x05, 0xf4, 0x7d, 0xc1, 0x4e, 0xf9, 0x20, 0x18, 0x62, 0xbc, 0xd9, 0x80, 0xde, 0x4a, 0xf5, 0x08, 0x1e, 0xd3, - 0xc0, 0x72, 0xd1, 0x97, 0x05, 0x43, 0x98, 0xa5, 0x3f, 0x53, 0x36, 0xf9, 0xfa, 0xef, 0x6e, 0x7e, 0x2f, 0xb4, 0x98, - 0x1d, 0x84, 0xe2, 0xf6, 0x7a, 0x02, 0xc4, 0xaf, 0xe2, 0xd7, 0x60, 0x6d, 0xae, 0x25, 0xde, 0x6e, 0x7a, 0xfe, 0x10, - 0xbe, 0x1c, 0xdd, 0x7e, 0x52, 0x9a, 0x4f, 0x20, 0x68, 0x8f, 0x93, 0x94, 0xbb, 0xef, 0x3e, 0x4a, 0x57, 0x11, 0x8c, - 0x16, 0x20, 0xf8, 0xed, 0xad, 0xe4, 0xbc, 0x29, 0xfc, 0xc7, 0x3a, 0xdf, 0x63, 0x2c, 0x15, 0x79, 0x86, 0xd3, 0xdf, - 0x00, 0x07, 0xbf, 0xf7, 0x6f, 0x65, 0xd6, 0x90, 0xe8, 0x42, 0x7d, 0x04, 0xf4, 0x7f, 0xac, 0xc7, 0xef, 0x14, 0x25, - 0x7d, 0x49, 0x9c, 0x23, 0x7c, 0x13, 0x2f, 0xd1, 0x74, 0xb1, 0x37, 0xae, 0xe9, 0x9b, 0xc2, 0xbc, 0xd0, 0x0a, 0x0e, - 0xfb, 0xd6, 0x28, 0x3c, 0xf0, 0xcc, 0xfb, 0x56, 0x34, 0x04, 0xdd, 0xbf, 0xe2, 0xde, 0xf8, 0x56, 0xb0, 0x0c, 0x6f, - 0xca, 0x59, 0x66, 0xee, 0x70, 0xb7, 0x99, 0x48, 0xe5, 0x2d, 0x63, 0xc1, 0x5a, 0x28, 0x73, 0xde, 0x34, 0x98, 0x6d, - 0xea, 0x48, 0x25, 0xbb, 0xef, 0xff, 0x6a, 0x9c, 0xb0, 0xd9, 0x20, 0x38, 0xab, 0x64, 0x11, 0x5f, 0xf1, 0x60, 0xaa, - 0x55, 0x14, 0x19, 0xd8, 0x15, 0x02, 0x52, 0x8e, 0xd3, 0xde, 0xc1, 0x93, 0xa5, 0x66, 0x26, 0xe4, 0xb7, 0xd5, 0x59, - 0xc0, 0x5b, 0x33, 0x9a, 0xa7, 0x15, 0xec, 0x32, 0x5f, 0x49, 0xf1, 0x47, 0x4b, 0x92, 0x8d, 0xf5, 0x37, 0x64, 0xd8, - 0x56, 0x3e, 0x73, 0x01, 0x98, 0x3b, 0xb7, 0x52, 0x05, 0xfd, 0xeb, 0x01, 0x23, 0x84, 0x44, 0x40, 0x38, 0x8b, 0x89, - 0x7b, 0x61, 0xc2, 0x61, 0xba, 0x40, 0x41, 0x31, 0x06, 0x0a, 0xfa, 0x28, 0x43, 0x4e, 0x4f, 0xf9, 0x20, 0x69, 0xcc, - 0xd6, 0x1f, 0xaa, 0x44, 0x7a, 0x23, 0x09, 0x3d, 0x87, 0xdf, 0xe3, 0x16, 0x0f, 0xd4, 0x08, 0xd6, 0xe9, 0x6e, 0x4e, - 0x87, 0x2f, 0x0b, 0x32, 0xfc, 0x13, 0xbc, 0xdd, 0x62, 0x7b, 0x59, 0x4e, 0x60, 0x71, 0xc7, 0x5e, 0xf1, 0x34, 0x57, - 0x2d, 0x4e, 0x88, 0x47, 0x2c, 0x72, 0x9f, 0x58, 0xc0, 0x88, 0x1a, 0x46, 0xe3, 0x87, 0xb3, 0xb7, 0x6f, 0x34, 0x86, - 0x55, 0xee, 0x7f, 0x00, 0x23, 0xaa, 0xa5, 0xed, 0x76, 0xc0, 0x97, 0x23, 0x34, 0x60, 0x4f, 0xdd, 0x60, 0xf7, 0xfb, - 0x26, 0xed, 0xa4, 0xf4, 0xb2, 0x39, 0x31, 0xe8, 0x8e, 0xd2, 0x66, 0xa9, 0x0c, 0x8c, 0xbb, 0x0a, 0x47, 0x73, 0x62, - 0x23, 0x56, 0xf5, 0x3e, 0x0c, 0x97, 0x34, 0xb6, 0xb2, 0x72, 0xbb, 0x9b, 0x70, 0x64, 0x13, 0xe0, 0xfa, 0x14, 0xb4, - 0x57, 0x73, 0x0e, 0x5a, 0x50, 0xa2, 0xc0, 0x11, 0x6d, 0xb7, 0x21, 0x44, 0x24, 0x29, 0x86, 0x93, 0x59, 0x58, 0x0c, - 0x87, 0x6a, 0xe0, 0x0b, 0x42, 0xa2, 0x37, 0xc5, 0x3c, 0x5b, 0x28, 0x04, 0x23, 0x7f, 0x27, 0x7d, 0x5b, 0x28, 0x4e, - 0xb9, 0xf7, 0xad, 0x20, 0x9b, 0x5f, 0x53, 0x8c, 0xc1, 0xe8, 0x34, 0x9b, 0x19, 0x48, 0x58, 0x4f, 0x2b, 0xa2, 0xd6, - 0x91, 0x9d, 0x0d, 0x50, 0xc5, 0xa2, 0x69, 0x30, 0xa8, 0x5b, 0x3c, 0xb1, 0x9e, 0xd1, 0x7b, 0x50, 0x09, 0xa2, 0x5a, - 0xb0, 0x1b, 0xc3, 0xb5, 0xf6, 0x46, 0x84, 0x92, 0x72, 0xd2, 0x64, 0x66, 0xac, 0x68, 0xb0, 0x00, 0x21, 0x69, 0x5c, - 0x56, 0xaf, 0x65, 0x9a, 0x5d, 0x66, 0x80, 0x20, 0xe1, 0xfc, 0x09, 0x65, 0xe3, 0xcd, 0x33, 0x35, 0x2f, 0x5d, 0x89, - 0x33, 0x0b, 0x7b, 0xd2, 0xf5, 0x96, 0x16, 0x24, 0x2a, 0x80, 0x46, 0xf9, 0x5a, 0x9e, 0x7f, 0xec, 0x58, 0x85, 0xec, - 0x7e, 0x38, 0x55, 0xb6, 0x43, 0xfc, 0x84, 0x55, 0xc4, 0x3b, 0xad, 0x2b, 0x25, 0xd2, 0xe8, 0x68, 0x1b, 0x10, 0xc3, - 0x96, 0x7d, 0x8b, 0x1a, 0x3e, 0x08, 0xbb, 0xe8, 0x24, 0x3f, 0xe8, 0x29, 0x1e, 0x5b, 0x03, 0x49, 0x5f, 0x8b, 0xe0, - 0x6b, 0x74, 0xa4, 0x13, 0x65, 0x1a, 0x89, 0x29, 0x24, 0xfa, 0xf5, 0x42, 0x6b, 0x2c, 0xa3, 0xec, 0x2b, 0xf2, 0xbf, - 0xd3, 0xdd, 0xfb, 0x56, 0x6c, 0xb7, 0x30, 0xc9, 0x9e, 0x07, 0x1a, 0x6c, 0x6a, 0xd4, 0x0a, 0xe1, 0xec, 0x9c, 0x56, - 0xa8, 0x1d, 0xeb, 0x85, 0x25, 0x90, 0x07, 0xb0, 0x15, 0x69, 0x50, 0x06, 0xc9, 0xde, 0x14, 0x73, 0xb1, 0x70, 0xa2, - 0x1c, 0xa9, 0xf0, 0xcf, 0xe4, 0x28, 0xe5, 0x70, 0x15, 0x0b, 0x0b, 0x86, 0xfc, 0xea, 0xe8, 0xb2, 0x90, 0xd7, 0x20, - 0x29, 0x31, 0x0c, 0x95, 0xe5, 0x75, 0x71, 0xd5, 0x96, 0x84, 0xf6, 0xce, 0x01, 0x94, 0xa6, 0x00, 0xc1, 0x4b, 0xa3, - 0x86, 0x98, 0x6d, 0xd4, 0xee, 0x8a, 0xf6, 0x92, 0x03, 0xea, 0x74, 0xd7, 0x6e, 0xbd, 0x29, 0x5b, 0x75, 0x2b, 0x2e, - 0xfc, 0x03, 0x4a, 0x3f, 0xe5, 0x83, 0xc2, 0xa7, 0x12, 0xb8, 0xf1, 0xd5, 0x26, 0xcb, 0x2e, 0xef, 0x71, 0xe9, 0x57, - 0x8d, 0xf1, 0xeb, 0xf7, 0x7b, 0x6a, 0x21, 0x34, 0x52, 0x81, 0xf9, 0xf6, 0x99, 0xa9, 0xca, 0x68, 0x4a, 0xed, 0x25, - 0xb8, 0x72, 0xf6, 0x23, 0xa8, 0x88, 0xeb, 0x8a, 0xd4, 0xa6, 0x06, 0xe8, 0xc0, 0xcb, 0x0a, 0xb7, 0xb2, 0x00, 0x8f, - 0x9d, 0x80, 0x6c, 0xb7, 0x3c, 0x0c, 0xf4, 0xa1, 0x13, 0xf8, 0x5b, 0xf2, 0x0c, 0x99, 0x35, 0xfb, 0xf8, 0x93, 0x16, - 0xfc, 0x63, 0x0b, 0x7e, 0x44, 0x71, 0xa7, 0x95, 0xf9, 0xb7, 0xd2, 0xba, 0xc5, 0xfd, 0x3b, 0x99, 0x26, 0x14, 0x95, - 0x09, 0xb5, 0x5f, 0xe9, 0x6f, 0x26, 0x78, 0x94, 0xca, 0xfe, 0x41, 0xc2, 0x07, 0xb3, 0xc6, 0x13, 0x6b, 0x3c, 0x19, - 0x4e, 0xb7, 0xd2, 0xb0, 0x0c, 0x28, 0xf4, 0xf3, 0x32, 0x57, 0x54, 0x3f, 0xff, 0xb4, 0xe6, 0x6b, 0xde, 0x6c, 0xb1, - 0x4d, 0x7a, 0xa0, 0xc1, 0x5e, 0x1e, 0x4d, 0x29, 0x9c, 0x44, 0x9d, 0x1b, 0x89, 0xba, 0xa8, 0x59, 0x86, 0xea, 0x04, - 0xaf, 0xe6, 0xa9, 0x1e, 0xf6, 0x66, 0x22, 0x5a, 0x2b, 0x29, 0x4b, 0x0c, 0x58, 0xeb, 0xc8, 0x43, 0x72, 0xb7, 0xd6, - 0x71, 0xa7, 0xa1, 0x2e, 0x4d, 0xa1, 0x26, 0x58, 0xe1, 0x02, 0x1c, 0x41, 0x3f, 0x16, 0x21, 0x87, 0x6b, 0xaa, 0xd2, - 0x2f, 0x68, 0x4a, 0x9e, 0x78, 0x8a, 0x5a, 0xad, 0x48, 0xb7, 0x1f, 0xe5, 0xd8, 0x0d, 0xdf, 0x38, 0x21, 0x27, 0x46, - 0xe8, 0xef, 0x8e, 0xa5, 0x9c, 0xa1, 0xc5, 0x83, 0x3a, 0xc1, 0x7a, 0x79, 0x4b, 0x81, 0x62, 0x8e, 0x2e, 0xab, 0xae, - 0x79, 0x85, 0xb6, 0x2f, 0xcb, 0x7e, 0x3f, 0xb7, 0xf5, 0xa4, 0xec, 0x64, 0xb3, 0x34, 0xfb, 0x10, 0x15, 0x53, 0xb8, - 0xeb, 0x13, 0xcd, 0x5f, 0x85, 0xfa, 0xaa, 0x2d, 0x73, 0x3e, 0xe2, 0x88, 0x13, 0x92, 0x93, 0xfa, 0x27, 0x35, 0xf5, - 0x4a, 0xdc, 0xaf, 0x2a, 0xf9, 0x45, 0x18, 0x2b, 0x46, 0x17, 0xb8, 0x20, 0x55, 0x2a, 0xef, 0x17, 0x05, 0xc0, 0x5f, - 0x09, 0xf6, 0x26, 0x0d, 0xb5, 0xf2, 0x5b, 0xb4, 0x05, 0xfc, 0x1b, 0xc5, 0x0d, 0x58, 0x05, 0x06, 0x18, 0x4d, 0xb6, - 0xe7, 0x34, 0x81, 0x03, 0x4e, 0x68, 0x15, 0x05, 0x15, 0x66, 0x68, 0xa8, 0x2d, 0x8c, 0x9e, 0xa1, 0x8c, 0x5b, 0x65, - 0xf6, 0x6e, 0x8c, 0x9d, 0x16, 0x78, 0x0d, 0xff, 0x46, 0x2f, 0x14, 0xb3, 0x51, 0x07, 0xe9, 0xd1, 0x49, 0x4c, 0x7f, - 0xdc, 0xc2, 0xc9, 0xcd, 0xc2, 0x59, 0xd6, 0x2c, 0x81, 0xee, 0xc0, 0x05, 0x31, 0xee, 0xf7, 0x73, 0x38, 0x32, 0xcd, - 0xc8, 0x17, 0x2c, 0xa7, 0x31, 0x5b, 0x52, 0xed, 0x79, 0x78, 0x55, 0x85, 0x39, 0x5d, 0x5a, 0x19, 0x6f, 0xca, 0x40, - 0x65, 0xb4, 0xdd, 0x86, 0xf0, 0xa7, 0xdb, 0xda, 0x25, 0x9d, 0x2f, 0x21, 0x03, 0xfc, 0x01, 0x89, 0x28, 0x62, 0x81, - 0xff, 0x5b, 0x8d, 0x53, 0x7a, 0xa2, 0xb4, 0x66, 0x09, 0x5d, 0x33, 0x5d, 0x3f, 0xbd, 0x64, 0xeb, 0xc6, 0x52, 0xd8, - 0x6e, 0xc3, 0x66, 0x02, 0xd3, 0x9c, 0x2b, 0x99, 0x5e, 0xa2, 0x4e, 0x0a, 0xa8, 0x58, 0x78, 0x89, 0xcb, 0x2f, 0x25, - 0x14, 0x9a, 0x3b, 0x5f, 0x2e, 0x8c, 0x12, 0x13, 0x5a, 0x25, 0x3f, 0x7f, 0xa8, 0xcc, 0xd7, 0xc6, 0x43, 0xf0, 0xb7, - 0x34, 0x4c, 0x4c, 0x91, 0xa8, 0x10, 0x9d, 0x7d, 0x0b, 0xb2, 0x1c, 0x01, 0xb8, 0x9e, 0x67, 0xb2, 0xa6, 0x3f, 0xa4, - 0x10, 0x17, 0x1e, 0x1a, 0xf4, 0xae, 0x90, 0xd7, 0x59, 0xc9, 0x43, 0xbc, 0x27, 0x78, 0x9a, 0xd1, 0xfd, 0x06, 0x1f, - 0xda, 0xda, 0xa3, 0x27, 0xc8, 0xc6, 0x53, 0xee, 0xd7, 0xbf, 0x88, 0x70, 0x0e, 0xd1, 0x3b, 0x17, 0x54, 0xab, 0xab, - 0x1d, 0x20, 0x97, 0x67, 0x7b, 0xf5, 0x08, 0x4e, 0x37, 0x7d, 0x7d, 0xab, 0x42, 0x67, 0x0e, 0x20, 0xed, 0x21, 0x59, - 0xd7, 0x5c, 0xef, 0x00, 0x77, 0x24, 0x56, 0x6b, 0xa0, 0xb1, 0x6e, 0x6b, 0x76, 0xda, 0xa3, 0x78, 0x4c, 0x64, 0x66, - 0x2c, 0x52, 0x8c, 0xb9, 0x5b, 0xa7, 0x45, 0xd1, 0x06, 0xcd, 0x10, 0x76, 0xef, 0x3a, 0x7c, 0xdd, 0x8a, 0x38, 0xbf, - 0xdf, 0xf6, 0x05, 0x46, 0xc3, 0x98, 0x6b, 0xf7, 0x3c, 0x43, 0x37, 0x6c, 0xb0, 0x8d, 0x24, 0x88, 0x48, 0x90, 0x99, - 0x3a, 0x10, 0x65, 0x6d, 0x0d, 0xd8, 0x1e, 0x71, 0xbd, 0x69, 0x15, 0x3f, 0xaf, 0x62, 0xf0, 0xf6, 0xac, 0x71, 0x4a, - 0xeb, 0x6b, 0x5c, 0x73, 0x5c, 0x15, 0x22, 0x6a, 0x8b, 0x14, 0x00, 0xc3, 0xce, 0x17, 0xb8, 0x33, 0x2b, 0x0c, 0xe6, - 0x84, 0xa5, 0x92, 0x9d, 0xca, 0xf5, 0xe7, 0xb0, 0xc5, 0x41, 0x2a, 0x5f, 0x7a, 0xfd, 0xfd, 0xcd, 0x17, 0x5f, 0xa0, - 0xdb, 0x9e, 0xf3, 0x23, 0x08, 0x32, 0x81, 0x0e, 0x6a, 0x4a, 0xf5, 0xf8, 0x4b, 0x01, 0xd4, 0x1e, 0xe6, 0xe1, 0x97, - 0x82, 0x89, 0xf8, 0x26, 0xbb, 0x8a, 0x2b, 0x59, 0x8c, 0x6e, 0xb8, 0x48, 0x65, 0x61, 0xa5, 0xc6, 0xc1, 0xe9, 0x6a, - 0x95, 0xf3, 0x00, 0x4c, 0xe5, 0x2d, 0xa3, 0x6c, 0x2b, 0xcb, 0xf4, 0xe0, 0x6a, 0x79, 0x7a, 0xa5, 0x45, 0xe7, 0xe5, - 0xcd, 0x55, 0x10, 0xe1, 0xaf, 0x0b, 0xf3, 0xe3, 0x3a, 0x2e, 0x3f, 0x06, 0x91, 0xb5, 0xa9, 0x33, 0x3f, 0x50, 0x2a, - 0x0f, 0xfe, 0x4e, 0x20, 0xd3, 0xfd, 0xa5, 0x00, 0xcb, 0x6c, 0x5b, 0xf1, 0x71, 0x8c, 0xb5, 0x0e, 0x27, 0x64, 0xa6, - 0x4a, 0xf4, 0xde, 0x25, 0xeb, 0x02, 0xac, 0xfd, 0x14, 0xb6, 0xb3, 0xca, 0x35, 0xc3, 0xca, 0x54, 0x45, 0x66, 0x56, - 0xd6, 0xec, 0x30, 0xb4, 0x4e, 0x34, 0x73, 0xf4, 0x16, 0xd0, 0x0f, 0xe4, 0xf0, 0x8a, 0x96, 0x6b, 0xe6, 0xf9, 0xd8, - 0x34, 0x5e, 0x3f, 0x3a, 0xbc, 0x72, 0x0b, 0xf6, 0xce, 0xde, 0xc9, 0x51, 0x98, 0x08, 0x9e, 0xc6, 0x66, 0x7c, 0x91, - 0x67, 0x05, 0xec, 0xa0, 0xc9, 0x78, 0x4c, 0xbd, 0xa5, 0xd5, 0xba, 0x39, 0x3a, 0x64, 0xdb, 0xec, 0x71, 0xf5, 0x98, - 0x93, 0x43, 0xde, 0x32, 0xb5, 0x6d, 0x5b, 0xc7, 0x79, 0x9a, 0x7c, 0x65, 0xba, 0x2f, 0xd6, 0x36, 0x42, 0xbc, 0x72, - 0x76, 0x74, 0x5e, 0xd2, 0xad, 0x6f, 0x4a, 0x43, 0xaf, 0x25, 0x00, 0xf3, 0x69, 0x03, 0xfe, 0x82, 0x95, 0xeb, 0x51, - 0xc5, 0xcb, 0x0a, 0x24, 0x2c, 0x28, 0xc2, 0x9b, 0x62, 0x6f, 0x0a, 0x77, 0xe3, 0xf4, 0x1c, 0x76, 0xe0, 0x62, 0x8a, - 0xee, 0x38, 0x31, 0x99, 0x95, 0x46, 0x2b, 0x1a, 0xe9, 0x5f, 0xae, 0x2f, 0xb1, 0xee, 0x8b, 0x56, 0xe6, 0xd9, 0x9c, - 0x0a, 0x9b, 0xde, 0x55, 0x2e, 0x9d, 0xa8, 0xdf, 0x32, 0xe1, 0xca, 0x95, 0x20, 0x20, 0xd3, 0x82, 0xf5, 0x0a, 0xb3, - 0x8b, 0xe4, 0x1a, 0x08, 0x19, 0x18, 0xbe, 0x06, 0x6b, 0x51, 0x72, 0x63, 0x05, 0xeb, 0xdd, 0xf3, 0x75, 0x82, 0x90, - 0x82, 0x07, 0x6e, 0x82, 0xbe, 0x6f, 0xdd, 0xbc, 0x1d, 0x25, 0xca, 0x20, 0x3e, 0xb9, 0x76, 0xca, 0x41, 0x02, 0x01, - 0x38, 0xb0, 0x2a, 0x24, 0x89, 0x02, 0x9d, 0x07, 0x57, 0x33, 0x8e, 0x60, 0xf3, 0xca, 0x99, 0x8b, 0x1b, 0xc0, 0x79, - 0xe5, 0xcf, 0x65, 0x83, 0x2d, 0xeb, 0x11, 0x55, 0xe6, 0x8c, 0x53, 0x0c, 0xea, 0x64, 0x09, 0xfa, 0xca, 0x52, 0xda, - 0x2b, 0xd0, 0x34, 0x5e, 0xb3, 0x95, 0xf2, 0x01, 0xa0, 0x17, 0x6c, 0xa5, 0x8c, 0xfd, 0xf1, 0xeb, 0x73, 0xb6, 0xd2, - 0xd2, 0xe0, 0xe9, 0xf5, 0xec, 0x62, 0x76, 0x3e, 0x60, 0x47, 0x51, 0xa8, 0x0d, 0x18, 0x02, 0x17, 0x99, 0x20, 0x18, - 0x84, 0x1a, 0xff, 0x65, 0xa0, 0x02, 0x84, 0x11, 0x8f, 0xc7, 0x46, 0x1c, 0xb1, 0x70, 0x3c, 0xc4, 0x60, 0x60, 0xcd, - 0x17, 0x24, 0x20, 0xd4, 0x94, 0x86, 0xbe, 0x9e, 0xe1, 0x70, 0x72, 0x30, 0x81, 0x54, 0xcc, 0xcc, 0x54, 0x61, 0x6c, - 0x4c, 0x22, 0x88, 0xff, 0xda, 0x59, 0x2f, 0x94, 0xdb, 0x5d, 0xa3, 0x81, 0xa0, 0x19, 0x7c, 0x56, 0xc5, 0x93, 0x83, - 0x61, 0x57, 0xc5, 0x38, 0x0a, 0x37, 0x46, 0xf9, 0x76, 0x7e, 0x0c, 0x60, 0xbe, 0xe7, 0x43, 0x5f, 0x2e, 0x71, 0x7e, - 0xf8, 0x84, 0x3c, 0x7e, 0x42, 0xe8, 0x39, 0x3b, 0xff, 0xe2, 0x09, 0x3d, 0x57, 0xe4, 0xe4, 0x60, 0x12, 0xdd, 0x30, - 0x8b, 0x81, 0x73, 0xa4, 0x9a, 0x40, 0xaf, 0x46, 0x6b, 0xa1, 0x16, 0x98, 0x76, 0x68, 0x0a, 0xbf, 0x19, 0x1f, 0x04, - 0x83, 0x9b, 0x76, 0xd3, 0x6f, 0xda, 0x6d, 0xf5, 0xbc, 0xba, 0x0e, 0x8e, 0xa2, 0xdd, 0x62, 0x26, 0x7f, 0x1f, 0x1f, - 0xb8, 0x39, 0xc0, 0xfa, 0x1e, 0x1e, 0x13, 0xd3, 0xa4, 0x9d, 0x51, 0xf1, 0x6b, 0xfa, 0x0a, 0xfb, 0xd0, 0x2c, 0xb2, - 0xa3, 0x0f, 0xc3, 0x7f, 0xab, 0x13, 0xf5, 0xf9, 0x17, 0x47, 0x40, 0x8e, 0x40, 0x06, 0x8a, 0x25, 0x82, 0x19, 0x0e, - 0x34, 0x05, 0x14, 0x64, 0x7a, 0xdc, 0xa9, 0x1e, 0x7e, 0x35, 0x6a, 0x6a, 0x46, 0x6e, 0x60, 0x6a, 0xb0, 0x2d, 0xf8, - 0x81, 0xea, 0x86, 0xfe, 0x46, 0xa3, 0x3d, 0x69, 0x27, 0x33, 0xf3, 0x92, 0xda, 0x38, 0x77, 0x37, 0x10, 0xd0, 0xd9, - 0xc1, 0x2d, 0x4a, 0xf6, 0xe5, 0xf1, 0xd5, 0x01, 0xae, 0x22, 0x40, 0x0d, 0x63, 0xc1, 0x97, 0x83, 0x2b, 0xbd, 0xb9, - 0x0f, 0x02, 0x32, 0xf8, 0x32, 0x38, 0xf9, 0x72, 0x20, 0x07, 0xc1, 0xf1, 0xe1, 0xd5, 0x49, 0xe0, 0x8c, 0xfb, 0x21, - 0xe4, 0xa5, 0xaa, 0x28, 0x66, 0xc2, 0x54, 0x91, 0xd8, 0xda, 0x73, 0x5b, 0xaf, 0x32, 0x3e, 0xa3, 0xe9, 0xd4, 0x22, - 0xa1, 0x87, 0x29, 0x8b, 0xcd, 0xef, 0x60, 0xc2, 0xaf, 0x83, 0xc8, 0x05, 0x85, 0x9d, 0xe5, 0x51, 0x4c, 0x97, 0xec, - 0x4e, 0x84, 0x29, 0x4d, 0x0e, 0x73, 0x42, 0xa2, 0x70, 0xa9, 0xc0, 0x04, 0xd5, 0xeb, 0x04, 0xe2, 0xda, 0xba, 0xcf, - 0xef, 0x44, 0xb8, 0xa4, 0xf9, 0x61, 0x42, 0x5a, 0x45, 0xb8, 0x08, 0x35, 0x9b, 0x9a, 0x5e, 0xb2, 0x70, 0x45, 0xaf, - 0x80, 0x99, 0x92, 0xeb, 0xf0, 0x0a, 0xb8, 0xbc, 0xf5, 0x7c, 0xb5, 0x60, 0x57, 0x0d, 0xe9, 0x9b, 0xe1, 0x8b, 0x2f, - 0xad, 0x4f, 0x1e, 0xf0, 0x90, 0xce, 0x0f, 0x2f, 0x05, 0x1b, 0x80, 0x9b, 0x8c, 0xdf, 0x7e, 0x2b, 0xef, 0xf4, 0xbc, - 0xb4, 0xa7, 0x18, 0x67, 0xa6, 0x9d, 0x98, 0xb4, 0x13, 0x72, 0xff, 0xbe, 0xed, 0xbb, 0x17, 0xaf, 0x95, 0xcb, 0xaa, - 0x65, 0x48, 0x8a, 0xb5, 0x72, 0x9d, 0x46, 0xc9, 0xa9, 0x15, 0x78, 0xb2, 0x4b, 0x5e, 0x25, 0x4b, 0xff, 0xa0, 0xb2, - 0x56, 0x03, 0xf6, 0x18, 0xb1, 0x2c, 0x14, 0x8e, 0xfd, 0xeb, 0x8c, 0x15, 0x6b, 0x5f, 0xa0, 0x11, 0x23, 0xf7, 0xf6, - 0x3a, 0x63, 0x5e, 0x0c, 0xda, 0x64, 0xed, 0x85, 0xee, 0xf3, 0xd2, 0xf3, 0x16, 0xef, 0xe5, 0x94, 0x1a, 0x46, 0x22, - 0x7a, 0x30, 0x56, 0x66, 0x94, 0x2a, 0x51, 0x6b, 0xd0, 0x88, 0x60, 0x63, 0x17, 0x0c, 0x14, 0x9c, 0x50, 0xb9, 0xa7, - 0xce, 0xf6, 0xed, 0x94, 0x4a, 0x0f, 0x68, 0x97, 0x1a, 0x55, 0xb9, 0x5b, 0x66, 0x92, 0x55, 0x83, 0x60, 0xf4, 0x47, - 0x29, 0xc5, 0x0c, 0xef, 0x8c, 0x2c, 0x98, 0x82, 0x95, 0xa0, 0xaa, 0x65, 0x58, 0x0e, 0x39, 0x6a, 0xf1, 0x8c, 0x4f, - 0xaa, 0xd4, 0x3f, 0x3a, 0x82, 0x06, 0x2f, 0xd7, 0xad, 0xa0, 0xc1, 0x4f, 0xc6, 0x4f, 0xf4, 0x40, 0xa7, 0x6b, 0xed, - 0x78, 0xe8, 0xf3, 0xdb, 0x88, 0x37, 0xae, 0x7b, 0x4f, 0xb5, 0x56, 0xa1, 0x0c, 0xb4, 0x58, 0x51, 0xb9, 0x52, 0x4b, - 0xba, 0xdf, 0x45, 0x00, 0x2c, 0x62, 0x63, 0x36, 0xde, 0xb5, 0xcd, 0x0a, 0x41, 0xa3, 0xcb, 0x4e, 0x36, 0xf1, 0x80, - 0x25, 0xba, 0xb5, 0x83, 0x09, 0x8d, 0x4f, 0x58, 0xd9, 0xef, 0xe7, 0x27, 0x40, 0x4f, 0xb5, 0x11, 0x53, 0x01, 0x47, - 0xfe, 0xe7, 0x56, 0x64, 0x8a, 0x02, 0x9b, 0x35, 0x75, 0xb7, 0xc6, 0x32, 0x12, 0x7d, 0x99, 0xd2, 0xe5, 0x09, 0xcf, - 0x80, 0x69, 0xbd, 0x6e, 0x39, 0xae, 0xec, 0x2a, 0x8e, 0x3c, 0x15, 0x96, 0x15, 0xe7, 0x55, 0x38, 0xde, 0x7a, 0x7c, - 0x83, 0x43, 0xc3, 0xa6, 0x5d, 0xfa, 0x43, 0x08, 0x0b, 0xe1, 0x75, 0x06, 0xb7, 0x11, 0x6d, 0x27, 0x81, 0xca, 0x1b, - 0x73, 0x9d, 0x50, 0x36, 0xb7, 0xeb, 0xb5, 0x67, 0x90, 0x4e, 0xcc, 0x81, 0x52, 0x8d, 0xa0, 0x35, 0x9a, 0x05, 0x55, - 0x23, 0x1e, 0x39, 0x1e, 0xde, 0x19, 0xc4, 0x6a, 0xf9, 0x92, 0xa6, 0x52, 0x34, 0x00, 0xe3, 0x02, 0xb8, 0x3c, 0xfd, - 0xfc, 0xfe, 0xc7, 0x33, 0x1e, 0x17, 0xc9, 0xf2, 0x5d, 0x5c, 0xc4, 0xd7, 0x65, 0xb8, 0x51, 0x63, 0x14, 0xd7, 0x64, - 0x2a, 0x06, 0x4c, 0x9a, 0x95, 0xd4, 0xdc, 0x95, 0x9a, 0x10, 0x63, 0x9d, 0xc9, 0xba, 0xac, 0xe4, 0x75, 0xa3, 0xd2, - 0x75, 0x91, 0xe1, 0xc7, 0x2d, 0x9f, 0xd3, 0x43, 0x00, 0x36, 0x35, 0x2e, 0xa4, 0x91, 0xd4, 0x85, 0x18, 0x73, 0x11, - 0xaf, 0xeb, 0xe3, 0x71, 0xa3, 0xeb, 0x25, 0x7b, 0x3a, 0xfe, 0x6a, 0xfa, 0x3a, 0x0b, 0xb3, 0x81, 0x20, 0xa3, 0x6a, - 0xc9, 0x45, 0xcb, 0x94, 0x53, 0x99, 0x04, 0xa0, 0x8f, 0x67, 0x8f, 0xb1, 0xa3, 0xf1, 0x98, 0x6c, 0xda, 0xe2, 0x01, - 0x1e, 0x2e, 0xd7, 0x61, 0x41, 0x66, 0xba, 0x8e, 0x28, 0x10, 0xfc, 0xae, 0x0a, 0x00, 0xd9, 0xd2, 0x56, 0x65, 0xb8, - 0x34, 0xf6, 0x74, 0x3c, 0xa1, 0x12, 0xbb, 0x1d, 0x92, 0xda, 0xab, 0xd0, 0xcd, 0xbc, 0xf4, 0x3d, 0x8a, 0xa4, 0x71, - 0x59, 0xda, 0xa9, 0x54, 0xaa, 0x3d, 0x33, 0x73, 0x5d, 0x83, 0x98, 0x14, 0xa1, 0xae, 0xbb, 0xf4, 0xea, 0xde, 0x6d, - 0xae, 0x35, 0xdb, 0x01, 0xef, 0x35, 0x68, 0x86, 0x92, 0xb7, 0x98, 0xb7, 0xae, 0x88, 0x9a, 0xae, 0xd6, 0x60, 0x56, - 0x8c, 0xb2, 0xa5, 0x28, 0x5d, 0x53, 0x50, 0x0a, 0x46, 0x97, 0x6b, 0x6f, 0xe1, 0xbe, 0x96, 0x8d, 0x0b, 0x4b, 0xa6, - 0x57, 0x8b, 0x92, 0x12, 0xaa, 0x9b, 0x8a, 0x91, 0x12, 0x46, 0x4a, 0xc3, 0x53, 0xf9, 0x5e, 0xe0, 0x71, 0x9e, 0x07, - 0x51, 0xcb, 0x0b, 0xec, 0xb4, 0x22, 0xa7, 0xe0, 0xe8, 0x65, 0x72, 0x1a, 0x0a, 0xfc, 0x43, 0xa6, 0x40, 0x5d, 0x87, - 0xea, 0x7e, 0x83, 0x9b, 0xff, 0x9f, 0x05, 0x0b, 0x3c, 0xbe, 0xf5, 0x0a, 0xb7, 0xd1, 0x3f, 0x0b, 0x9f, 0x96, 0x3e, - 0x93, 0xbe, 0xab, 0x8b, 0x27, 0xed, 0xcd, 0x46, 0xc9, 0x32, 0xcb, 0xd3, 0x37, 0x32, 0xe5, 0x20, 0x32, 0x43, 0x6b, - 0x50, 0x76, 0x22, 0x1a, 0x37, 0x3c, 0x30, 0x62, 0x6c, 0xdc, 0xf8, 0x7e, 0xc8, 0x40, 0x36, 0x0c, 0x56, 0xdf, 0x2c, - 0x95, 0xc9, 0x1a, 0x10, 0x36, 0xb4, 0xfc, 0x44, 0xe3, 0x6d, 0x84, 0xfa, 0xfa, 0x05, 0x6e, 0x73, 0xa5, 0xef, 0x73, - 0xfe, 0x43, 0x46, 0x7f, 0x40, 0xe0, 0x97, 0x78, 0x05, 0x72, 0x8f, 0x67, 0x50, 0x37, 0xc2, 0xf6, 0x72, 0x0c, 0x96, - 0x84, 0xe8, 0x28, 0xa2, 0x62, 0x81, 0x82, 0xa6, 0x30, 0x88, 0x22, 0xea, 0x82, 0x39, 0xbc, 0xc8, 0x65, 0xf2, 0x71, - 0x6a, 0x7c, 0xe6, 0x87, 0x31, 0xc6, 0x90, 0x0e, 0x06, 0x61, 0x35, 0x0b, 0x86, 0xe3, 0xd1, 0xe4, 0xe8, 0x29, 0x9c, - 0xdb, 0xc1, 0x38, 0x20, 0x83, 0xa0, 0x2e, 0x57, 0xb1, 0xa0, 0xe5, 0xcd, 0x95, 0x2d, 0x03, 0x3f, 0xae, 0x83, 0xc1, - 0x3f, 0x0b, 0x4f, 0xf1, 0x0e, 0x9a, 0x93, 0x73, 0x19, 0x82, 0x8d, 0xfd, 0x9a, 0x80, 0xa4, 0xac, 0xa7, 0xf9, 0x49, - 0x7d, 0xb8, 0x31, 0xa5, 0xfd, 0x33, 0x87, 0x17, 0x1c, 0x76, 0x48, 0xa0, 0x40, 0x1a, 0x4f, 0xb3, 0xd1, 0x2b, 0xa5, - 0xc8, 0x7d, 0x57, 0x70, 0xb8, 0x33, 0xf7, 0x9c, 0xe9, 0x91, 0x53, 0x48, 0x34, 0xb3, 0x80, 0x1b, 0xf9, 0x2b, 0x71, - 0x13, 0xe7, 0x59, 0x7a, 0xd0, 0x7c, 0x73, 0x50, 0xde, 0x8b, 0x2a, 0xbe, 0x1b, 0x05, 0xc6, 0x9a, 0x90, 0xfb, 0xaa, - 0x27, 0x40, 0x4f, 0x80, 0x2d, 0x00, 0x06, 0xc4, 0x3b, 0x66, 0x26, 0x33, 0x1e, 0x81, 0x47, 0x60, 0xd3, 0x07, 0xb2, - 0xb8, 0x77, 0x2e, 0x49, 0xfe, 0x66, 0x2a, 0xed, 0x55, 0xaf, 0xdc, 0x29, 0xc8, 0x7a, 0xb5, 0x95, 0xbb, 0x6e, 0x7d, - 0xf6, 0x4d, 0x87, 0x57, 0xe0, 0x85, 0x04, 0xb7, 0xc8, 0x7e, 0xbf, 0x29, 0xa8, 0x14, 0x46, 0x45, 0xbc, 0x93, 0x5c, - 0xa3, 0x7f, 0xbb, 0x37, 0x36, 0x8a, 0xe4, 0x96, 0x0f, 0x0f, 0xa0, 0xce, 0xe4, 0x5d, 0x71, 0x3b, 0x87, 0xa8, 0xad, - 0xbb, 0xf1, 0xc0, 0x7b, 0x83, 0x76, 0x59, 0x73, 0x04, 0x5b, 0x5e, 0x1c, 0x64, 0x30, 0x16, 0x38, 0x2b, 0x23, 0xa5, - 0xc6, 0x35, 0xa4, 0x16, 0x7c, 0x92, 0xa7, 0x7b, 0xc8, 0x52, 0x4f, 0x82, 0x22, 0xc7, 0xb3, 0x18, 0x32, 0x8d, 0xb7, - 0x81, 0xd8, 0xef, 0x65, 0x08, 0xd2, 0xb4, 0xed, 0xb6, 0x39, 0x02, 0x65, 0xf7, 0xc0, 0x94, 0xa4, 0xae, 0x8d, 0xa9, - 0x81, 0x86, 0x1e, 0x44, 0x8d, 0x54, 0xc4, 0xd9, 0xc9, 0x6b, 0xd0, 0x21, 0x82, 0xef, 0x77, 0x9a, 0x95, 0x1d, 0x2f, - 0x26, 0x04, 0x4f, 0xde, 0x17, 0x77, 0x59, 0x59, 0x95, 0xd1, 0xfb, 0x14, 0x0d, 0xa1, 0x12, 0x29, 0xa2, 0x97, 0x10, - 0x5f, 0xb0, 0xc4, 0xdf, 0x65, 0xf4, 0x63, 0x4a, 0xe3, 0x34, 0xc5, 0xf4, 0xe7, 0x05, 0xfc, 0x7c, 0x06, 0x28, 0x97, - 0xb8, 0x13, 0xa2, 0x0b, 0x09, 0xf6, 0x6a, 0x10, 0xdd, 0xab, 0xe2, 0x80, 0x29, 0x1a, 0xdd, 0x09, 0x8a, 0x98, 0x75, - 0x98, 0xfd, 0xfb, 0x02, 0x85, 0x42, 0xaa, 0x98, 0x5f, 0x84, 0x7d, 0x88, 0x7e, 0xc0, 0x22, 0x4f, 0xdf, 0xbd, 0x32, - 0x43, 0x1a, 0xdd, 0x4b, 0xaa, 0xb7, 0x36, 0x1e, 0x5b, 0x88, 0xd2, 0x13, 0x5d, 0xad, 0xe9, 0x79, 0xbc, 0xca, 0xa2, - 0x0d, 0xe0, 0x4f, 0xbc, 0x7b, 0xf5, 0x4c, 0x59, 0x98, 0x3c, 0xcf, 0x40, 0x71, 0x70, 0xfa, 0xee, 0xd5, 0x6b, 0x99, - 0xae, 0x73, 0x1e, 0x9d, 0x4b, 0x24, 0xad, 0xa7, 0xef, 0x5e, 0xfd, 0x84, 0xe6, 0x5e, 0x3f, 0x16, 0xf0, 0xfe, 0x25, - 0xf0, 0x96, 0x51, 0xbc, 0x86, 0x3e, 0xa9, 0xdf, 0xc9, 0x1a, 0x3b, 0xe5, 0xd5, 0x5a, 0x46, 0x3f, 0xa7, 0xb5, 0x27, - 0xad, 0xfa, 0x57, 0xe1, 0x53, 0x3b, 0x4f, 0xc0, 0x73, 0x97, 0x67, 0xe2, 0x63, 0x64, 0x45, 0x3b, 0x41, 0xf4, 0xe5, - 0xc1, 0xdd, 0x75, 0x2e, 0xca, 0x08, 0x5f, 0x30, 0xb4, 0x0b, 0x8a, 0x0e, 0x0f, 0x6f, 0x6f, 0x6f, 0x47, 0xb7, 0x5f, - 0x8d, 0x64, 0x71, 0x75, 0x38, 0xf9, 0xe6, 0x9b, 0x6f, 0x0e, 0xf1, 0x6d, 0xf0, 0x65, 0xdb, 0xed, 0xbd, 0x22, 0x7c, - 0xc0, 0x02, 0x44, 0xec, 0xfe, 0x12, 0xae, 0x28, 0xa0, 0x85, 0x1b, 0x7c, 0x19, 0x7c, 0xa9, 0x0f, 0x9d, 0x2f, 0x8f, - 0xcb, 0x9b, 0x2b, 0x55, 0x7e, 0x57, 0xc9, 0x47, 0xe3, 0xf1, 0xf8, 0x10, 0x24, 0x50, 0x5f, 0x0e, 0xf8, 0x20, 0x38, - 0x09, 0x06, 0x19, 0x5c, 0x68, 0xca, 0x9b, 0xab, 0x93, 0xc0, 0x33, 0xcd, 0x6d, 0xb0, 0x88, 0x0e, 0xc4, 0x25, 0x38, - 0xbc, 0xa2, 0xc1, 0x97, 0x01, 0x71, 0x29, 0x5f, 0x40, 0xca, 0x17, 0x47, 0x4f, 0xfd, 0xb4, 0xff, 0xa5, 0xd2, 0xbe, - 0xf2, 0xd3, 0x8e, 0x31, 0xed, 0xab, 0x67, 0x7e, 0xda, 0x89, 0x4a, 0x7b, 0xe1, 0xa7, 0xfd, 0xef, 0x72, 0x00, 0xa9, - 0x07, 0xbe, 0xf5, 0xdf, 0x85, 0xd7, 0x1a, 0x3c, 0x85, 0xa2, 0xec, 0x3a, 0xbe, 0xe2, 0xd0, 0xe8, 0xc1, 0xdd, 0x75, - 0x4e, 0x83, 0x01, 0xb6, 0xd7, 0x33, 0x09, 0xf1, 0x3e, 0xf8, 0x72, 0x5d, 0xe4, 0x61, 0xf0, 0xe5, 0x00, 0x0b, 0x19, - 0x7c, 0x19, 0x90, 0x2f, 0x8d, 0x81, 0x8c, 0x60, 0x9b, 0xc0, 0x85, 0x66, 0x1d, 0xda, 0x80, 0x69, 0xbe, 0x34, 0xae, - 0xa6, 0x7f, 0x16, 0xdd, 0xd9, 0xf0, 0x96, 0xa8, 0xdc, 0x74, 0x83, 0x9a, 0xbe, 0x05, 0xef, 0x04, 0x68, 0x54, 0x14, - 0xdc, 0xc4, 0x45, 0x38, 0x1c, 0x96, 0x37, 0x57, 0x04, 0xec, 0x32, 0x57, 0x3c, 0xae, 0xa2, 0x40, 0xc8, 0xa1, 0xfa, - 0x19, 0xa8, 0x48, 0x60, 0x01, 0x42, 0x19, 0xc1, 0x7f, 0x41, 0x4d, 0xdf, 0x49, 0xb6, 0x09, 0x86, 0xb7, 0xfc, 0xe2, - 0x63, 0x56, 0x0d, 0x95, 0x68, 0xf1, 0x46, 0x50, 0xf8, 0x01, 0x7f, 0x5d, 0xd5, 0xd1, 0x9f, 0xe0, 0xc6, 0xdd, 0xd4, - 0xb0, 0xbf, 0x93, 0x8e, 0x45, 0x7d, 0x27, 0xe7, 0xd9, 0x62, 0xda, 0x3a, 0xd0, 0xdf, 0x4a, 0x52, 0xcd, 0xb3, 0x41, - 0x30, 0x0c, 0x06, 0x7c, 0xc1, 0xde, 0xca, 0x39, 0xf7, 0xcc, 0xa7, 0x1e, 0x49, 0x7f, 0x9a, 0x67, 0xd9, 0x00, 0x7c, - 0x53, 0x90, 0x1f, 0x39, 0xfc, 0xef, 0xf9, 0x10, 0x85, 0x87, 0x83, 0x47, 0x87, 0x64, 0x16, 0xac, 0xee, 0xd0, 0xa3, - 0x33, 0x0a, 0x32, 0xb1, 0xe4, 0x45, 0x56, 0x79, 0x4b, 0xe5, 0x7e, 0xdd, 0xf6, 0xf2, 0xd8, 0x7b, 0x36, 0xaf, 0x62, - 0x11, 0xa8, 0x73, 0x0e, 0x14, 0x6f, 0x28, 0x7b, 0x2a, 0x9b, 0x12, 0x52, 0x6d, 0xc8, 0x1b, 0x96, 0x03, 0x16, 0x1c, - 0xf7, 0x86, 0xc3, 0x83, 0x60, 0xe0, 0xd4, 0xb9, 0x83, 0xe0, 0x60, 0x38, 0x3c, 0x09, 0xdc, 0x7d, 0x28, 0x1b, 0xb9, - 0x3b, 0x23, 0x2d, 0xd8, 0xbf, 0x8a, 0xb0, 0xa4, 0x20, 0x1e, 0x93, 0x5a, 0xfc, 0xa5, 0xc1, 0x65, 0x06, 0x00, 0x7d, - 0xa4, 0x24, 0x60, 0x06, 0x56, 0x66, 0x00, 0xa1, 0xca, 0x69, 0xcc, 0xce, 0x81, 0x79, 0x04, 0x8e, 0x59, 0xc1, 0x64, - 0x01, 0x62, 0x49, 0x80, 0x73, 0x17, 0x44, 0xb1, 0x2e, 0xe4, 0x11, 0x04, 0x01, 0xc0, 0x9f, 0xc4, 0x94, 0x82, 0x49, - 0x3a, 0x76, 0x23, 0x08, 0xe2, 0xf8, 0xec, 0x46, 0xb4, 0x26, 0x67, 0x89, 0x0e, 0x66, 0x24, 0x01, 0x36, 0xc4, 0xc0, - 0xf0, 0xc1, 0xfd, 0x1c, 0x94, 0x1e, 0x56, 0xef, 0x84, 0x5c, 0xf0, 0x3d, 0xf7, 0x64, 0xb3, 0x70, 0xf5, 0x84, 0x83, - 0xe0, 0x9e, 0x6b, 0x16, 0x60, 0x54, 0x15, 0xeb, 0xb2, 0xe2, 0xe9, 0x87, 0xfb, 0x15, 0xc4, 0x02, 0xc4, 0x01, 0x7d, - 0x27, 0xf3, 0x2c, 0xb9, 0x0f, 0x9d, 0x3d, 0xd7, 0x46, 0xa5, 0x7f, 0xff, 0xe1, 0xf5, 0x8f, 0x11, 0x88, 0x1c, 0x6b, - 0x43, 0xe9, 0xef, 0x39, 0x9e, 0x4d, 0x7e, 0xc4, 0x2b, 0x7f, 0x63, 0xdf, 0x73, 0x7b, 0x7a, 0xf4, 0xfb, 0x50, 0x37, - 0xbd, 0xe7, 0xb3, 0x7b, 0x3e, 0x72, 0xc5, 0xa1, 0xba, 0xc2, 0x7d, 0x7d, 0xbb, 0xf6, 0x8d, 0x90, 0x1e, 0x9e, 0x67, - 0xca, 0x1b, 0xf3, 0xa3, 0x1d, 0x0c, 0x83, 0x60, 0xaa, 0x85, 0x92, 0x10, 0x85, 0x84, 0x29, 0x01, 0x43, 0x74, 0xa0, - 0x97, 0xd5, 0x14, 0x39, 0x37, 0x35, 0xb2, 0xf0, 0x7e, 0xc0, 0xb4, 0xd0, 0xa1, 0x91, 0x43, 0xf9, 0xc1, 0xe1, 0x84, - 0x31, 0x0b, 0xbf, 0x55, 0xc2, 0xf4, 0xab, 0x45, 0xe5, 0x1c, 0x44, 0x0f, 0xc0, 0x18, 0x57, 0xf0, 0x02, 0xba, 0xc2, - 0x6e, 0xd6, 0x2a, 0x4a, 0x08, 0x82, 0xe9, 0x21, 0x07, 0xe8, 0x61, 0x17, 0xb4, 0xac, 0x2c, 0xd5, 0xad, 0xca, 0x59, - 0xaa, 0xa8, 0xcb, 0x50, 0x56, 0xc6, 0x0a, 0x03, 0xbf, 0x64, 0xdf, 0x17, 0xe8, 0x59, 0x3e, 0x15, 0x5d, 0xf0, 0x42, - 0x28, 0xc1, 0x72, 0x5d, 0xef, 0x44, 0x20, 0xea, 0xfc, 0xd0, 0xbb, 0xea, 0x6b, 0x5c, 0x3f, 0x9e, 0xbe, 0x96, 0x29, - 0xd7, 0x26, 0x14, 0x9a, 0xcf, 0x97, 0xbe, 0x62, 0xa2, 0x60, 0xb7, 0xd0, 0xaf, 0xb6, 0x8d, 0x3e, 0xbb, 0x5f, 0xeb, - 0xcd, 0xa0, 0x44, 0xc7, 0xbc, 0x46, 0xc1, 0xb5, 0x52, 0x28, 0x18, 0xed, 0x6d, 0xfc, 0x09, 0x8e, 0xdc, 0xea, 0xf6, - 0xd0, 0xfb, 0xad, 0x8a, 0xaf, 0xde, 0xa0, 0x6f, 0xa7, 0xfd, 0x39, 0xaa, 0xe4, 0xcf, 0xab, 0x15, 0xf8, 0x50, 0x41, - 0xa4, 0x15, 0x8b, 0xd3, 0x0b, 0xf5, 0x9c, 0xbd, 0x3b, 0x7d, 0x03, 0x7e, 0x94, 0xf8, 0xfb, 0x97, 0xef, 0x82, 0x9a, - 0x4c, 0xe3, 0x59, 0x61, 0x3e, 0xb4, 0x39, 0x20, 0x54, 0x8b, 0x4b, 0xb3, 0xef, 0x67, 0x71, 0x93, 0x7d, 0xd7, 0x6c, - 0x3d, 0x2d, 0x9a, 0x48, 0x52, 0x86, 0xdb, 0x07, 0x03, 0x02, 0x7d, 0x80, 0x28, 0xce, 0xbe, 0xa0, 0x31, 0xa4, 0xf9, - 0xcc, 0xbe, 0x1f, 0x21, 0xf0, 0xc5, 0x4e, 0x48, 0x35, 0xae, 0xb0, 0x68, 0xf4, 0x90, 0xcf, 0x78, 0xa4, 0x0c, 0x8b, - 0xde, 0x63, 0x02, 0x71, 0x86, 0xd3, 0xea, 0x3d, 0x62, 0x40, 0xe3, 0xdd, 0x40, 0xcb, 0x1e, 0xa2, 0x8c, 0xba, 0xec, - 0x0d, 0x8b, 0xef, 0x8f, 0xeb, 0x30, 0xb3, 0x96, 0x97, 0x43, 0xf8, 0x1b, 0x68, 0x03, 0x70, 0xca, 0x91, 0xe5, 0xab, - 0xcc, 0x46, 0x57, 0x4b, 0x4c, 0x6f, 0x22, 0x88, 0x4d, 0xa4, 0xd3, 0x61, 0xed, 0xea, 0x54, 0xbd, 0xab, 0x9d, 0xcf, - 0x44, 0xaf, 0x02, 0xad, 0x5c, 0xdb, 0x1e, 0x0f, 0xe1, 0x3f, 0xb5, 0xb4, 0xc2, 0x46, 0xd8, 0x73, 0xf1, 0x85, 0xe7, - 0xd8, 0x9c, 0x80, 0x06, 0xd7, 0x32, 0x05, 0xe0, 0x2c, 0xad, 0x46, 0xa3, 0x46, 0xd8, 0x67, 0xe5, 0x7c, 0x0e, 0x5b, - 0x0b, 0xf1, 0xb4, 0x00, 0x1c, 0xb8, 0x89, 0xc9, 0xc9, 0xbb, 0x31, 0x39, 0xa7, 0x1f, 0x15, 0xdc, 0x77, 0x70, 0x5e, - 0x2e, 0xe3, 0x54, 0xde, 0x02, 0x36, 0x65, 0xe0, 0xa7, 0x62, 0xa9, 0x5e, 0x42, 0xb2, 0xe4, 0xc9, 0x47, 0xb4, 0xda, - 0x48, 0x03, 0xe0, 0x2a, 0xa7, 0xc6, 0x72, 0x4f, 0x81, 0xa6, 0xba, 0x52, 0x54, 0x42, 0x5c, 0x55, 0x71, 0xb2, 0x3c, - 0xc3, 0xd4, 0x70, 0x03, 0xbd, 0x88, 0x02, 0xb9, 0xe2, 0x02, 0x48, 0x7a, 0xce, 0x7e, 0xcb, 0x34, 0xf6, 0xfa, 0x33, - 0x89, 0x02, 0x26, 0x8d, 0xa2, 0x8c, 0x95, 0xb2, 0x17, 0xd2, 0x44, 0xbf, 0x0b, 0x82, 0xda, 0xbd, 0xfc, 0x13, 0xea, - 0x7e, 0x06, 0xad, 0x08, 0x1b, 0xe0, 0x85, 0x1a, 0xfc, 0x30, 0xb5, 0x4b, 0xce, 0x03, 0x32, 0x74, 0xde, 0x67, 0xb5, - 0xdd, 0xea, 0xcf, 0x96, 0x80, 0xf5, 0x9a, 0x1a, 0x9f, 0xc2, 0x30, 0x21, 0x26, 0x56, 0xb2, 0x55, 0x56, 0xda, 0x0d, - 0x65, 0xda, 0x49, 0x97, 0xcc, 0x6b, 0xe1, 0x34, 0xef, 0x31, 0xb6, 0x1c, 0xa9, 0xdc, 0xfd, 0x7e, 0x68, 0x7e, 0xb2, - 0x9c, 0x3e, 0xd3, 0x21, 0xac, 0xbd, 0xf1, 0xa0, 0x39, 0xd1, 0xea, 0xaa, 0x8e, 0x7e, 0x40, 0x07, 0x60, 0xa6, 0x2d, - 0x42, 0xa5, 0x0b, 0xbe, 0xed, 0x2b, 0x51, 0x71, 0x49, 0xc2, 0x52, 0x49, 0x60, 0x67, 0x37, 0x25, 0x3b, 0x9b, 0x80, - 0x78, 0x86, 0xbb, 0x9e, 0x16, 0x3b, 0x21, 0x4d, 0x78, 0x8b, 0x83, 0x04, 0x44, 0x1d, 0xaa, 0xba, 0x84, 0x6c, 0x8c, - 0xa1, 0x8b, 0x7f, 0x51, 0x0a, 0x13, 0xd6, 0x32, 0xa9, 0x4a, 0x4c, 0x50, 0xa8, 0x72, 0xb7, 0x45, 0x60, 0x89, 0x82, - 0x1d, 0xc0, 0xde, 0xbb, 0x51, 0x37, 0xa3, 0xa6, 0xaa, 0x53, 0x2f, 0xc1, 0xc7, 0x69, 0xd6, 0x55, 0x90, 0x59, 0xd8, - 0x55, 0xb1, 0xe6, 0x81, 0x8e, 0xd5, 0xa5, 0x8c, 0x89, 0xbb, 0xb4, 0xc8, 0x10, 0x1f, 0x19, 0x63, 0x0b, 0x6b, 0x38, - 0xd2, 0xf6, 0xb8, 0xe9, 0x09, 0x42, 0x3f, 0x61, 0x43, 0x09, 0xdc, 0x74, 0xb6, 0xa7, 0xa6, 0x99, 0x0f, 0x88, 0x38, - 0x0c, 0x28, 0x90, 0x6c, 0x1c, 0xd2, 0x1c, 0xe9, 0x0b, 0x92, 0x26, 0x0c, 0x94, 0xad, 0x78, 0x4e, 0x90, 0x15, 0x85, - 0x9e, 0xad, 0xab, 0x36, 0xce, 0x95, 0x61, 0x8e, 0x96, 0x9c, 0x0a, 0x4f, 0x13, 0x64, 0x62, 0x7b, 0xda, 0x66, 0x26, - 0xc3, 0x51, 0xb2, 0xc0, 0xfc, 0x0a, 0xa2, 0xc4, 0x9d, 0x69, 0x56, 0xe5, 0x60, 0x5c, 0xc0, 0x02, 0xad, 0x7c, 0x0f, - 0xea, 0xc6, 0x1a, 0xda, 0x68, 0x58, 0x66, 0xb7, 0x3f, 0xc1, 0x7e, 0xad, 0x9d, 0xd6, 0x65, 0x8a, 0xe5, 0x65, 0x0a, - 0xd1, 0x5e, 0xc8, 0xfc, 0x46, 0x91, 0xe8, 0x4e, 0x11, 0x86, 0x84, 0x75, 0x94, 0x3d, 0x69, 0x53, 0x03, 0xe8, 0xa9, - 0x17, 0x00, 0xbe, 0x73, 0x2d, 0xc3, 0x2e, 0xd2, 0xfd, 0x55, 0xc1, 0xb8, 0x74, 0x83, 0x20, 0x45, 0x6f, 0x52, 0x30, - 0xe7, 0xf5, 0x28, 0xa9, 0x37, 0xa7, 0x2d, 0x33, 0xaa, 0x8e, 0x8a, 0x90, 0x72, 0x82, 0xff, 0xe4, 0x95, 0xd4, 0xc4, - 0x26, 0x4c, 0xf0, 0xc0, 0x87, 0x79, 0x86, 0x0d, 0xbc, 0xdd, 0xbe, 0x4b, 0xc3, 0xa4, 0xcd, 0x36, 0xa4, 0x20, 0xad, - 0x30, 0x71, 0x42, 0xa0, 0xb2, 0x57, 0xb8, 0x5f, 0xb0, 0x9d, 0x34, 0x05, 0x0f, 0xc2, 0x46, 0x03, 0x13, 0xb7, 0xba, - 0xf8, 0x3a, 0x4c, 0x68, 0xb8, 0xa4, 0xda, 0xd9, 0x49, 0x4b, 0x9a, 0xdb, 0xeb, 0xf2, 0xd2, 0xf6, 0x41, 0xc7, 0x52, - 0xeb, 0x1a, 0x1e, 0x68, 0x5e, 0xb3, 0x8b, 0x2b, 0xa6, 0x69, 0xa2, 0xb1, 0x1e, 0x52, 0x96, 0x1c, 0xeb, 0x7a, 0xba, - 0xc2, 0xd5, 0x32, 0xd3, 0x40, 0xf7, 0x12, 0x2f, 0xf4, 0x80, 0x0f, 0x1e, 0xae, 0x48, 0x74, 0x89, 0xcd, 0x66, 0xab, - 0x9a, 0x4c, 0xf3, 0x7d, 0xd9, 0x72, 0x13, 0x20, 0xcf, 0x52, 0xdf, 0xdc, 0x27, 0xc7, 0x9a, 0xb6, 0xf9, 0x49, 0x80, - 0x6b, 0xee, 0x15, 0x90, 0x74, 0x2c, 0x41, 0x17, 0xef, 0xd3, 0x1f, 0x44, 0x6a, 0xa6, 0x82, 0xee, 0x9d, 0x2f, 0x52, - 0x37, 0xbf, 0x00, 0xdb, 0xa8, 0x8d, 0x31, 0xcd, 0xca, 0xd6, 0x61, 0xa2, 0x2c, 0xac, 0x91, 0x85, 0x5c, 0x82, 0x0f, - 0xe6, 0x6e, 0x53, 0xa7, 0xa7, 0x1d, 0x44, 0xd8, 0xef, 0xa2, 0xc7, 0x23, 0x8c, 0x15, 0x6b, 0x90, 0x18, 0x56, 0x61, - 0x4d, 0x9b, 0xcb, 0x21, 0xca, 0xa9, 0x59, 0x32, 0xd1, 0x92, 0xfa, 0x94, 0x22, 0x4a, 0xc1, 0xdc, 0x78, 0x5a, 0x36, - 0x4c, 0x09, 0x11, 0xb2, 0x42, 0x3a, 0xa0, 0x5a, 0x0b, 0x2d, 0xd5, 0x04, 0x01, 0x0f, 0xbd, 0x2c, 0x34, 0xa6, 0x20, - 0xfa, 0x88, 0x0c, 0x37, 0xe2, 0xc8, 0xe8, 0xee, 0x18, 0xc5, 0x04, 0x42, 0x77, 0x7b, 0x79, 0x61, 0xf5, 0x69, 0xd9, - 0x56, 0x07, 0x71, 0x8d, 0x69, 0xb2, 0x87, 0xa0, 0xc6, 0x28, 0x68, 0x73, 0xba, 0xd1, 0x9f, 0x8b, 0xd0, 0xb7, 0x0b, - 0xc7, 0x6e, 0x14, 0x44, 0x42, 0x44, 0x5a, 0xaf, 0xa9, 0x18, 0xa0, 0x76, 0x1e, 0xbb, 0x88, 0x55, 0xba, 0x5b, 0x88, - 0xf2, 0x46, 0x65, 0xfd, 0x71, 0x1d, 0x92, 0xed, 0x16, 0xcb, 0x02, 0x5f, 0xf6, 0xb3, 0xf5, 0x1e, 0x08, 0xf4, 0xd7, - 0xeb, 0x4f, 0x42, 0xa0, 0xbf, 0xca, 0x3e, 0x07, 0x02, 0xfd, 0xf5, 0xfa, 0x7f, 0x1a, 0x02, 0xfd, 0x6c, 0xed, 0x41, - 0xa0, 0xab, 0xc1, 0xf8, 0xb5, 0x60, 0xc1, 0xdb, 0x37, 0x01, 0x7d, 0x2e, 0x59, 0xf0, 0xf6, 0xe5, 0x4b, 0xdf, 0x08, - 0x44, 0x68, 0x24, 0x7f, 0x23, 0x0b, 0x46, 0xdc, 0x16, 0x78, 0x85, 0x5a, 0x27, 0x1f, 0xa8, 0x28, 0x03, 0x20, 0xfa, - 0xf2, 0x9f, 0x59, 0xb5, 0x0c, 0x83, 0xc3, 0x80, 0xcc, 0x1c, 0x24, 0xe8, 0x70, 0x02, 0xb7, 0x37, 0x28, 0xe5, 0xbb, - 0xcf, 0x42, 0x53, 0x1f, 0x8d, 0x46, 0x71, 0x71, 0x85, 0x77, 0x3a, 0xb3, 0x8f, 0x10, 0xef, 0x38, 0xe3, 0xa5, 0x8d, - 0x98, 0xb1, 0x8c, 0xcb, 0x73, 0x1d, 0xaa, 0xa6, 0xb4, 0x3b, 0xb1, 0x5c, 0xca, 0xdb, 0x73, 0x80, 0xed, 0xb7, 0x5b, - 0x33, 0xc6, 0x6e, 0x28, 0x86, 0x58, 0xc7, 0xd3, 0x7d, 0xb6, 0xd6, 0xef, 0x2e, 0xe2, 0x92, 0xbf, 0x8b, 0xab, 0x25, - 0x83, 0x4e, 0xea, 0xed, 0x5a, 0xc8, 0xf5, 0xca, 0x55, 0x72, 0xbe, 0x16, 0x1f, 0x85, 0xbc, 0x15, 0x6a, 0x53, 0x9d, - 0xf3, 0x1b, 0x68, 0x11, 0xdb, 0xa0, 0x32, 0x42, 0xf0, 0xa4, 0xf2, 0x58, 0x2c, 0x05, 0xf2, 0x9e, 0x51, 0x03, 0xf3, - 0xde, 0x91, 0x83, 0x86, 0x76, 0x10, 0xb5, 0xc7, 0xb0, 0x91, 0x45, 0x67, 0x60, 0xe2, 0xf8, 0x02, 0x4a, 0x07, 0x28, - 0x6e, 0x88, 0x03, 0x01, 0x77, 0x0a, 0xe4, 0x79, 0x1b, 0x50, 0x2c, 0xb4, 0xf4, 0xfd, 0x40, 0xd4, 0x19, 0x6a, 0x60, - 0x0c, 0x1b, 0xc3, 0x84, 0xf7, 0x26, 0xf4, 0x05, 0x05, 0x8d, 0x6e, 0x01, 0x2e, 0x87, 0x7f, 0xae, 0xf9, 0x79, 0x96, - 0x22, 0xe0, 0x4d, 0x96, 0x2a, 0x6b, 0xa2, 0x1e, 0x0a, 0x39, 0xf0, 0xd9, 0x53, 0x3e, 0xe9, 0x78, 0x61, 0x9e, 0xbd, - 0xd5, 0x46, 0xa9, 0x58, 0xe7, 0x60, 0xeb, 0xe3, 0xd7, 0x32, 0x97, 0x3a, 0xe0, 0xf4, 0xb9, 0x58, 0x5f, 0xf3, 0x22, - 0x4b, 0xce, 0x97, 0x59, 0x59, 0xc9, 0xe2, 0x7e, 0x61, 0x70, 0x0c, 0x74, 0x59, 0xad, 0x49, 0xdc, 0xfb, 0x1d, 0x38, - 0x33, 0xab, 0xc8, 0x14, 0xc3, 0xa7, 0x63, 0x52, 0x6b, 0x33, 0x68, 0x68, 0x20, 0xb5, 0xbf, 0x53, 0x09, 0xc0, 0xe9, - 0xee, 0xd9, 0x76, 0x8d, 0x36, 0x0d, 0xd8, 0xdb, 0x35, 0x52, 0xb3, 0x94, 0x0a, 0xfe, 0xe7, 0x9a, 0x1b, 0x18, 0xfb, - 0xd0, 0x41, 0x34, 0x97, 0x3d, 0xad, 0x63, 0x50, 0xd8, 0x3e, 0x44, 0xf1, 0xf8, 0x69, 0xfa, 0x02, 0xa1, 0xb6, 0xe1, - 0x6e, 0x8b, 0xda, 0x73, 0x1b, 0xa9, 0xa9, 0x6b, 0x6d, 0xcc, 0xa1, 0xad, 0x8b, 0xd9, 0xa7, 0x32, 0x0c, 0x06, 0xd1, - 0xa7, 0xb2, 0xb0, 0xc9, 0x03, 0x4b, 0x50, 0x65, 0x39, 0x36, 0x16, 0x73, 0x5a, 0x05, 0x0e, 0x89, 0x1e, 0x26, 0x2d, - 0x60, 0xcf, 0x00, 0x52, 0x6d, 0x02, 0xa3, 0xaa, 0xb5, 0xa2, 0x0e, 0x6c, 0x76, 0x8a, 0x46, 0x0b, 0xe1, 0xef, 0x8f, - 0x36, 0xcd, 0xcd, 0x50, 0x1f, 0x3e, 0xda, 0xc4, 0xf0, 0x5f, 0x52, 0xcf, 0x52, 0x5e, 0xc5, 0x59, 0xce, 0xe2, 0x3c, - 0xff, 0x9d, 0x6e, 0xae, 0x79, 0xb5, 0x94, 0x69, 0x14, 0x7c, 0xf7, 0xe2, 0x43, 0x60, 0xb4, 0x96, 0xb9, 0xc6, 0xab, - 0xd1, 0x82, 0xfc, 0x5c, 0x5e, 0x85, 0x39, 0xa1, 0xbd, 0x7c, 0x24, 0x3f, 0xee, 0x04, 0x78, 0xfc, 0xfd, 0xfb, 0x0f, - 0x1f, 0xde, 0x1d, 0xa0, 0xac, 0xbf, 0x77, 0x70, 0xa6, 0x1c, 0xc7, 0x0f, 0x1e, 0x6d, 0x72, 0xad, 0x5d, 0xad, 0x7f, - 0x77, 0x17, 0xf7, 0x96, 0x6e, 0x34, 0xd7, 0x5b, 0xc0, 0xab, 0xa2, 0x35, 0x37, 0xb9, 0x53, 0x60, 0xfa, 0x99, 0x95, - 0x62, 0x21, 0x40, 0xb1, 0xb9, 0xaa, 0x39, 0x0a, 0x28, 0xe4, 0x05, 0x90, 0xfd, 0xb0, 0xda, 0xb3, 0x19, 0xab, 0xae, - 0xcd, 0x28, 0x8b, 0x2a, 0x13, 0x57, 0xe7, 0x48, 0x1f, 0x3e, 0x6b, 0x53, 0x9a, 0x65, 0xa2, 0x28, 0x4a, 0x7b, 0x3f, - 0x36, 0x50, 0xaa, 0xb4, 0x3d, 0xa6, 0xde, 0x65, 0x20, 0x2b, 0x29, 0xeb, 0xa9, 0xff, 0xb1, 0x31, 0x16, 0xf0, 0xd3, - 0x14, 0x86, 0x17, 0x1c, 0x7f, 0xec, 0x24, 0x1e, 0x99, 0xf6, 0xdd, 0xe2, 0x95, 0xf9, 0x38, 0x69, 0x25, 0xcc, 0x86, - 0x93, 0x68, 0x42, 0x6c, 0x68, 0x01, 0x4d, 0xe5, 0xbe, 0x1b, 0xbd, 0x78, 0xf3, 0xe1, 0xd5, 0x87, 0x7f, 0x9d, 0x3f, - 0x3b, 0xfd, 0xf0, 0xe2, 0xbb, 0xb7, 0xef, 0x5f, 0xbd, 0x38, 0x43, 0x1c, 0x3d, 0x8d, 0x55, 0x18, 0x6e, 0xb4, 0x41, - 0x6c, 0xb3, 0xac, 0x48, 0xd4, 0xa4, 0xd9, 0x14, 0x05, 0x16, 0x84, 0x99, 0x6d, 0x91, 0x3f, 0xbf, 0x79, 0xfe, 0xe2, - 0xe5, 0xab, 0x37, 0x2f, 0x9e, 0xb7, 0xbf, 0x1e, 0x4e, 0x6a, 0x52, 0xbb, 0x99, 0xd3, 0xc1, 0x31, 0xb8, 0x1d, 0xaf, - 0x0e, 0x0a, 0x86, 0x0a, 0x59, 0x9f, 0x82, 0x65, 0x40, 0xb1, 0x98, 0x12, 0xd1, 0xe2, 0x6f, 0x1d, 0x88, 0x2a, 0x6b, - 0x6d, 0x80, 0x12, 0x07, 0x33, 0xa3, 0x8a, 0x64, 0x44, 0x02, 0x76, 0x83, 0x2d, 0x07, 0x0c, 0x5f, 0x53, 0x0a, 0x48, - 0x3e, 0x1d, 0xbb, 0x83, 0x2a, 0x7c, 0xfd, 0xf3, 0x24, 0xae, 0xf8, 0x95, 0x2c, 0xee, 0xa3, 0x6c, 0xd4, 0x4a, 0xa1, - 0x8d, 0x25, 0x11, 0x85, 0x20, 0x65, 0x6c, 0x24, 0x11, 0x45, 0x4e, 0x66, 0xde, 0xa0, 0xb8, 0x71, 0x9e, 0x3b, 0xe8, - 0xf8, 0x76, 0xc1, 0x64, 0xb1, 0xdd, 0x76, 0x0c, 0x63, 0x27, 0xbd, 0x8c, 0xe6, 0x99, 0x22, 0xa4, 0x0b, 0xe0, 0xd2, - 0xe0, 0x48, 0x54, 0xe7, 0x1d, 0x33, 0x47, 0xe4, 0xa9, 0x0e, 0x01, 0x09, 0xa6, 0x69, 0xee, 0xb5, 0x89, 0x32, 0xd2, - 0x3c, 0x43, 0xc7, 0x2d, 0x2a, 0x6d, 0x00, 0x5f, 0x5b, 0xa9, 0x6a, 0xe1, 0x69, 0xa0, 0x3d, 0x98, 0x3b, 0x88, 0xcd, - 0x64, 0xe4, 0x78, 0x61, 0x0e, 0xe6, 0x12, 0x8d, 0x19, 0x37, 0xe3, 0x90, 0x47, 0xd2, 0x60, 0xa6, 0x81, 0xfd, 0xd8, - 0x9e, 0x5c, 0xcb, 0xa8, 0x68, 0xa0, 0x1f, 0xca, 0xe6, 0xa0, 0x1e, 0x17, 0xcd, 0x67, 0x58, 0xd8, 0xad, 0x2c, 0x28, - 0xbf, 0x6b, 0x66, 0x82, 0x7b, 0x66, 0x32, 0x53, 0xd5, 0x8f, 0x2a, 0xf9, 0xa3, 0xbc, 0x35, 0xb2, 0xc2, 0xe3, 0xa2, - 0x23, 0x11, 0x77, 0x4b, 0x14, 0x1f, 0x27, 0xea, 0xc7, 0xa4, 0xde, 0x73, 0x70, 0xd4, 0x6e, 0x80, 0xad, 0x2c, 0xfb, - 0x77, 0xc5, 0x3f, 0x9f, 0x3f, 0xda, 0x64, 0xfa, 0xa4, 0xaa, 0x7f, 0xcf, 0x6c, 0x24, 0xd0, 0x06, 0x33, 0x52, 0xeb, - 0xa1, 0xf7, 0x81, 0x27, 0x3b, 0xb2, 0xe9, 0xf5, 0xc1, 0xb2, 0x4e, 0x8e, 0x66, 0xa4, 0x1e, 0xb9, 0x0a, 0x8c, 0xd8, - 0x9d, 0x85, 0xdf, 0xf1, 0x24, 0xec, 0x6a, 0x98, 0x92, 0x35, 0x98, 0x2e, 0x20, 0x16, 0xef, 0xfe, 0x43, 0xc1, 0x7e, - 0x86, 0xbf, 0xb3, 0x14, 0xfe, 0x56, 0xb5, 0x77, 0x30, 0xbc, 0x7b, 0x7b, 0xf6, 0x01, 0x14, 0x1c, 0x31, 0x6a, 0x24, - 0x37, 0x81, 0x36, 0x66, 0x18, 0x82, 0xca, 0x20, 0x88, 0x82, 0x78, 0x05, 0x27, 0x3b, 0xb2, 0x8e, 0x87, 0x77, 0xc3, - 0xdb, 0xdb, 0xdb, 0xff, 0xd3, 0xdc, 0xb3, 0x6e, 0xb7, 0x6d, 0x23, 0xfd, 0xbf, 0x4f, 0xc1, 0x30, 0xd9, 0x94, 0x4c, - 0x48, 0x9a, 0x94, 0x2c, 0x5b, 0x91, 0x2c, 0xb9, 0xcd, 0xa5, 0x5b, 0x77, 0xdd, 0xa6, 0x27, 0x71, 0xfb, 0xed, 0xae, - 0xeb, 0x63, 0x51, 0x12, 0x24, 0x71, 0x43, 0x91, 0x3a, 0x24, 0xe5, 0x4b, 0x15, 0xee, 0xb3, 0xec, 0x23, 0x7c, 0xcf, - 0xd0, 0x27, 0xfb, 0xce, 0xcc, 0x00, 0x24, 0x78, 0x93, 0xe4, 0x4d, 0xda, 0x7e, 0xa7, 0x4d, 0x22, 0x82, 0x00, 0x08, - 0x0c, 0x80, 0xb9, 0x61, 0x2e, 0x26, 0x98, 0x36, 0x9a, 0xeb, 0xc8, 0x67, 0xc1, 0x24, 0x84, 0xc4, 0x24, 0xa9, 0x40, - 0xf8, 0xac, 0x84, 0xf0, 0x21, 0x2e, 0x2a, 0x4f, 0xac, 0xf1, 0x7e, 0x11, 0xde, 0x7e, 0xed, 0xfb, 0xb2, 0xfc, 0x2e, - 0x98, 0x3e, 0x2e, 0xd2, 0x16, 0x10, 0x88, 0x06, 0xd7, 0x10, 0x96, 0x17, 0x5f, 0xf3, 0x8b, 0xe3, 0xe9, 0xf5, 0xf8, - 0xfe, 0x9a, 0x2b, 0xa7, 0xb3, 0xc0, 0xb4, 0xaf, 0x46, 0x27, 0x53, 0xef, 0x46, 0x41, 0xce, 0x74, 0xa0, 0x82, 0x57, - 0x8f, 0xcf, 0xc6, 0xeb, 0x24, 0x09, 0x03, 0x33, 0x0a, 0x6f, 0xd5, 0xe1, 0x09, 0x3d, 0x88, 0x0a, 0x2e, 0x3d, 0xaa, - 0xca, 0x57, 0x13, 0xdf, 0x9b, 0x7c, 0x18, 0xa8, 0x4f, 0x36, 0xde, 0x60, 0x58, 0xe2, 0x3f, 0xed, 0x54, 0x1d, 0xc2, - 0x58, 0x95, 0xaf, 0x7d, 0xff, 0xe4, 0x80, 0x5a, 0x0c, 0x4f, 0x0e, 0xa6, 0xde, 0xcd, 0x50, 0xca, 0x11, 0xc2, 0x2f, - 0xd0, 0x06, 0x3c, 0x16, 0x63, 0x66, 0x72, 0x14, 0xa3, 0x73, 0xff, 0x84, 0x69, 0xb9, 0x14, 0x04, 0x41, 0x47, 0x68, - 0xbc, 0xda, 0x04, 0xf5, 0xaa, 0x3e, 0xf0, 0xfc, 0x1f, 0x3f, 0x6a, 0x99, 0x41, 0xe2, 0x42, 0x8a, 0xd6, 0x85, 0xf7, - 0x3d, 0x58, 0xc5, 0xc0, 0x90, 0x23, 0xba, 0x26, 0x62, 0x8a, 0xf9, 0xba, 0x31, 0x49, 0x0d, 0x4c, 0xb5, 0xe2, 0xae, - 0x80, 0x47, 0xe0, 0x3f, 0x25, 0xd1, 0x68, 0x02, 0xe9, 0x95, 0x25, 0x04, 0xaf, 0x4b, 0xca, 0x77, 0x3a, 0x9b, 0x3c, - 0x60, 0x1c, 0x68, 0xcd, 0xf1, 0x3b, 0xa4, 0x10, 0xd7, 0x7c, 0x1d, 0xd2, 0x7b, 0x65, 0x51, 0x5a, 0xdc, 0x54, 0x24, - 0xd4, 0x12, 0x70, 0x39, 0x2d, 0xac, 0x50, 0xaf, 0xbc, 0x5e, 0x22, 0x7c, 0xe0, 0xa3, 0xb8, 0x69, 0xc9, 0xe0, 0x32, - 0x47, 0x4b, 0x8c, 0x12, 0x3d, 0x06, 0xf7, 0x2e, 0xe9, 0x06, 0x81, 0x19, 0xda, 0x65, 0x6c, 0x84, 0x57, 0x39, 0x0d, - 0x8b, 0x09, 0x7d, 0xf6, 0xc2, 0x34, 0x8f, 0xe4, 0x4b, 0xab, 0x3e, 0x7c, 0xb2, 0x09, 0x90, 0xe8, 0xc5, 0x83, 0x61, - 0x71, 0x1f, 0x24, 0xee, 0xd8, 0xa4, 0xcd, 0xac, 0x2a, 0x5f, 0x4d, 0xc7, 0x7e, 0xb6, 0xd8, 0x74, 0x34, 0x16, 0x6e, - 0x30, 0xf5, 0xd9, 0x85, 0x3b, 0xfe, 0x16, 0xeb, 0xbc, 0x1e, 0xfb, 0xaf, 0xa0, 0x42, 0xaa, 0x0e, 0x9f, 0x6c, 0x88, - 0xac, 0xd7, 0xa1, 0xf1, 0x94, 0xb6, 0x40, 0xf9, 0x3b, 0x3c, 0xf7, 0x0e, 0x8b, 0xa8, 0x35, 0x0e, 0x96, 0x48, 0x31, - 0xe1, 0xd9, 0xe2, 0xc8, 0x78, 0xee, 0x17, 0xd8, 0x9b, 0x0a, 0x3f, 0x94, 0x30, 0xae, 0x50, 0x1c, 0x50, 0x79, 0x67, - 0xca, 0x83, 0x25, 0x92, 0xfb, 0x2e, 0xbc, 0x15, 0x23, 0xe5, 0x00, 0xa0, 0x58, 0x85, 0xa7, 0xaf, 0x46, 0x27, 0xf2, - 0xfd, 0x00, 0x2a, 0x51, 0xa9, 0x5f, 0xf8, 0x95, 0xaa, 0x4a, 0x9e, 0x09, 0x68, 0x75, 0xa7, 0x0e, 0x4f, 0x0e, 0xe4, - 0xda, 0xc3, 0x51, 0xef, 0x5c, 0x9a, 0x1c, 0xf6, 0x0a, 0x40, 0x28, 0x96, 0x55, 0xa8, 0x0e, 0x24, 0xc7, 0xcb, 0xe9, - 0x12, 0x6d, 0x0f, 0x81, 0x16, 0x43, 0xbd, 0x97, 0xad, 0x11, 0xd9, 0xe0, 0x89, 0xde, 0x46, 0xfc, 0xdf, 0x7c, 0xce, - 0xa8, 0xd3, 0x64, 0x41, 0x1c, 0x46, 0x2a, 0xcc, 0xa3, 0x9c, 0x21, 0x47, 0x91, 0x32, 0x73, 0xe1, 0x8c, 0x6a, 0xa9, - 0x29, 0x40, 0xe4, 0xa0, 0xdc, 0x54, 0x9a, 0xd8, 0x48, 0xcf, 0x7f, 0x28, 0x7c, 0x32, 0x25, 0xac, 0x94, 0x0d, 0xb0, - 0x39, 0xf3, 0xd0, 0xe5, 0x5b, 0xcf, 0xf8, 0x9f, 0xd0, 0x98, 0xbb, 0xc6, 0xd2, 0x35, 0xde, 0x07, 0x57, 0x69, 0xed, - 0xea, 0x64, 0x59, 0xc3, 0x0c, 0xd6, 0xd7, 0x20, 0xd6, 0x0e, 0xd7, 0x80, 0x70, 0xbd, 0x80, 0x67, 0x71, 0xeb, 0x80, - 0x0b, 0x37, 0x9a, 0x33, 0x91, 0xac, 0x4b, 0xbc, 0x4d, 0x38, 0x54, 0x74, 0x09, 0x2c, 0x10, 0x88, 0x4a, 0x08, 0x38, - 0x9e, 0x35, 0x49, 0x22, 0xff, 0x6f, 0xec, 0x1e, 0x24, 0xcf, 0x38, 0x09, 0x57, 0xa0, 0x9d, 0x70, 0xe7, 0x5c, 0xdb, - 0x6c, 0x00, 0x2f, 0xb3, 0xcf, 0xe7, 0x3e, 0x7e, 0x64, 0x52, 0xfe, 0xa8, 0x24, 0x9c, 0xcf, 0x7d, 0xa6, 0x49, 0x79, - 0xa6, 0xb2, 0xcf, 0x9c, 0x3e, 0xb2, 0x45, 0x8c, 0x62, 0x3d, 0x6d, 0x3a, 0x39, 0x39, 0x2b, 0x28, 0xee, 0x75, 0x49, - 0x58, 0xc7, 0xdb, 0xa8, 0x1b, 0xbc, 0xd0, 0xe5, 0xeb, 0x92, 0x9f, 0x4c, 0x73, 0x1a, 0xae, 0xc7, 0x3e, 0x33, 0x71, - 0xbb, 0xc3, 0x27, 0x37, 0xe3, 0xf5, 0x78, 0xec, 0x53, 0x62, 0x28, 0x88, 0xb4, 0x15, 0xc6, 0xa8, 0x01, 0x4b, 0xf5, - 0x3e, 0x32, 0x68, 0x49, 0x79, 0xf8, 0x60, 0x1d, 0x07, 0x62, 0x03, 0x7d, 0x20, 0x01, 0x6d, 0x57, 0xf5, 0xd0, 0x0e, - 0x54, 0x10, 0x57, 0x58, 0xac, 0xf6, 0x6b, 0x38, 0xb9, 0xc1, 0xa5, 0xfa, 0x1e, 0x21, 0x8c, 0xd9, 0xeb, 0x5f, 0xd1, - 0xde, 0x55, 0x0d, 0x95, 0x8c, 0x7c, 0x78, 0x1e, 0x31, 0xd5, 0x50, 0x5f, 0x7b, 0xee, 0x3c, 0x08, 0xe3, 0xc4, 0x9b, - 0xa8, 0x57, 0xfd, 0x33, 0x4f, 0xbb, 0x5c, 0x26, 0x9a, 0x7e, 0x65, 0xfc, 0x55, 0xce, 0xf8, 0x24, 0x50, 0x21, 0x26, - 0x7c, 0x6a, 0xa8, 0x23, 0x9f, 0x9e, 0x6d, 0xf5, 0x04, 0xca, 0xc5, 0x3a, 0x7f, 0x1d, 0x40, 0xad, 0x52, 0xee, 0x28, - 0x4c, 0x0a, 0x08, 0xb9, 0xa3, 0xfe, 0xaa, 0xf7, 0x49, 0x2b, 0xf3, 0x6a, 0xbd, 0x41, 0x5e, 0x21, 0xc9, 0xa9, 0x2b, - 0x86, 0x3b, 0x17, 0x3e, 0x82, 0xf4, 0xfc, 0x48, 0xb6, 0x6f, 0x2f, 0xd0, 0xe9, 0xd1, 0xd7, 0x45, 0xc6, 0x03, 0x18, - 0x04, 0x30, 0x2e, 0x0b, 0xc2, 0x44, 0x81, 0x18, 0x5e, 0xf0, 0xc1, 0x51, 0xd9, 0x1e, 0x96, 0xf7, 0xaa, 0xe9, 0x29, - 0xc7, 0x02, 0x2f, 0x91, 0x58, 0x8a, 0xec, 0xef, 0x18, 0x8e, 0xb2, 0x10, 0xb1, 0x87, 0x7b, 0x61, 0xc1, 0xf2, 0x15, - 0xd8, 0x36, 0x09, 0xb1, 0x17, 0x09, 0xf6, 0x93, 0x4d, 0x7c, 0x2a, 0xa8, 0xf6, 0x59, 0x8c, 0x6b, 0x09, 0xfc, 0x08, - 0x27, 0xe3, 0xa9, 0xaa, 0x9c, 0x0a, 0x52, 0x83, 0x75, 0x0b, 0xf8, 0x53, 0x13, 0x5c, 0xae, 0x48, 0xea, 0xae, 0xf1, - 0x14, 0xd4, 0x82, 0xef, 0x2a, 0x1d, 0x3d, 0x08, 0xcb, 0x93, 0xb1, 0x54, 0x09, 0xd8, 0xd6, 0x22, 0x45, 0x00, 0xcc, - 0xc5, 0x99, 0x80, 0x51, 0x7a, 0x0d, 0xfc, 0x23, 0xc4, 0xaa, 0x12, 0x73, 0x34, 0x42, 0x39, 0x5d, 0x98, 0x17, 0xac, - 0xd6, 0x09, 0xc6, 0x20, 0x87, 0x01, 0xb0, 0x54, 0x55, 0x50, 0x5a, 0x04, 0x64, 0x9e, 0x4b, 0x41, 0xa9, 0xaa, 0x78, - 0xd3, 0x6a, 0x19, 0x57, 0xdd, 0x00, 0x8e, 0xc3, 0x69, 0xa0, 0x06, 0x1f, 0x1e, 0x23, 0x3e, 0x8d, 0x89, 0x91, 0x27, - 0xf0, 0xd0, 0x26, 0x78, 0xd3, 0x5d, 0x83, 0x40, 0x26, 0xd4, 0x4f, 0x5f, 0xf3, 0x6b, 0x27, 0x0b, 0x71, 0x89, 0x0b, - 0xd3, 0x1c, 0x3d, 0xd9, 0x04, 0xe9, 0x29, 0xc0, 0x6e, 0xf0, 0x64, 0xe3, 0x66, 0x46, 0x54, 0xea, 0x85, 0x4a, 0x16, - 0x54, 0x23, 0x04, 0xc3, 0x28, 0xbd, 0xce, 0x5d, 0x1a, 0xf3, 0xf9, 0xc2, 0x96, 0xa4, 0x72, 0x05, 0x6d, 0x9a, 0x06, - 0xdc, 0x72, 0x69, 0x15, 0x79, 0x4b, 0x37, 0xba, 0x27, 0x43, 0x27, 0x43, 0xb6, 0x86, 0xd2, 0x55, 0x85, 0xe8, 0x01, - 0x01, 0x80, 0x48, 0x83, 0xaa, 0x7c, 0x95, 0x95, 0x31, 0x3e, 0xdb, 0xcc, 0xda, 0x03, 0xbe, 0x75, 0xad, 0x3e, 0x67, - 0x16, 0xa9, 0x34, 0xa8, 0x49, 0x5f, 0x8b, 0x1b, 0xa6, 0x17, 0x17, 0xa7, 0x17, 0x14, 0x37, 0x1a, 0x4e, 0x86, 0x28, - 0x05, 0x8d, 0x1b, 0x67, 0x86, 0xe9, 0x0e, 0xeb, 0x57, 0x94, 0xde, 0xfd, 0xa1, 0xcb, 0xc1, 0x60, 0x39, 0x02, 0x58, - 0x0e, 0xe2, 0xae, 0x7f, 0x7a, 0x77, 0x96, 0xe5, 0x57, 0x04, 0xd5, 0xf8, 0x88, 0x6f, 0xcc, 0x18, 0xd9, 0x8c, 0x08, - 0x59, 0x0c, 0xca, 0x84, 0xa8, 0x64, 0x5b, 0x28, 0x82, 0xa3, 0x41, 0x63, 0xa7, 0xa3, 0x11, 0x0d, 0x06, 0x21, 0xb6, - 0x8a, 0xd2, 0x93, 0x03, 0xaa, 0x4d, 0x44, 0x91, 0x2a, 0x01, 0x18, 0x22, 0x98, 0x61, 0x0e, 0x05, 0x48, 0x05, 0x3d, - 0x70, 0x72, 0xf9, 0xc6, 0x5a, 0xe2, 0x05, 0xa4, 0x73, 0x5a, 0xe4, 0x68, 0xb0, 0x95, 0x3a, 0x3c, 0xc1, 0xe4, 0x8e, - 0x40, 0xd6, 0x21, 0xfc, 0xd1, 0xc9, 0x01, 0x3d, 0x2a, 0xa5, 0x13, 0x91, 0x77, 0x22, 0x14, 0x94, 0x3d, 0xde, 0xc1, - 0x83, 0x8e, 0x4a, 0x9c, 0xb0, 0x15, 0x94, 0xba, 0xa9, 0xaa, 0x2c, 0x39, 0x07, 0xc5, 0xe3, 0xac, 0x41, 0x10, 0x16, - 0x1b, 0x8c, 0xdf, 0x55, 0x65, 0xe9, 0xde, 0xe1, 0xcc, 0xc5, 0x1b, 0xf7, 0x4e, 0x73, 0xf8, 0xab, 0xfc, 0xac, 0xc5, - 0xc5, 0xb3, 0x36, 0xe1, 0x8b, 0x0b, 0x1e, 0x56, 0x82, 0x73, 0xd6, 0x16, 0x68, 0xb9, 0x52, 0xb3, 0xb8, 0x0b, 0xb1, - 0xb8, 0xd3, 0x86, 0xc5, 0x9d, 0x6e, 0x59, 0x5c, 0x9f, 0x2f, 0xa4, 0x92, 0x81, 0x2e, 0x42, 0xaf, 0xd9, 0x0c, 0x78, - 0x9c, 0x1f, 0xe9, 0xf1, 0x73, 0x86, 0x70, 0x32, 0x63, 0x1f, 0xac, 0x46, 0x1b, 0x60, 0x55, 0x07, 0x17, 0x09, 0x10, - 0xd5, 0x89, 0x67, 0xa7, 0x6e, 0x22, 0x29, 0x04, 0x34, 0xbf, 0x3c, 0x5f, 0xd8, 0xa5, 0xd8, 0xd0, 0xd0, 0x16, 0x0d, - 0x33, 0x5d, 0x6c, 0x99, 0xe9, 0xa4, 0x70, 0x74, 0xf9, 0xb4, 0xe9, 0x10, 0xca, 0x93, 0x82, 0x3d, 0x08, 0x96, 0xf4, - 0xb8, 0x65, 0x8a, 0xfb, 0xb0, 0x19, 0xc7, 0x4a, 0x3b, 0x6a, 0xe5, 0xc6, 0xf1, 0x6d, 0x18, 0xc1, 0x55, 0x34, 0x74, - 0xf3, 0xb0, 0x2d, 0xb5, 0xf4, 0x02, 0x1e, 0xe5, 0xaa, 0x71, 0x33, 0xe5, 0xef, 0xe5, 0x2d, 0xd5, 0xea, 0x74, 0xa8, - 0xc6, 0xca, 0x4d, 0x12, 0x16, 0x21, 0xd0, 0x5d, 0x48, 0x87, 0xf0, 0xff, 0x64, 0x9b, 0xd5, 0xe0, 0x10, 0x5f, 0xc2, - 0xea, 0x88, 0xa1, 0x57, 0xc0, 0x82, 0xd1, 0xdd, 0x53, 0xa0, 0x6f, 0xa4, 0x88, 0x99, 0x51, 0x06, 0xf8, 0x1f, 0xf0, - 0xb8, 0x6a, 0x91, 0xe4, 0xd3, 0xe9, 0x1c, 0xe9, 0xd6, 0xca, 0x9d, 0xbe, 0x07, 0x8b, 0x07, 0xad, 0x65, 0x80, 0xf7, - 0x82, 0x1c, 0x1f, 0x33, 0x22, 0x9e, 0x70, 0x92, 0x23, 0x49, 0xc4, 0x92, 0xdc, 0x36, 0x14, 0xdc, 0xca, 0x5d, 0x73, - 0x76, 0xb5, 0x69, 0xa5, 0x07, 0x73, 0x4f, 0xaf, 0x60, 0x4d, 0x40, 0x6d, 0xfe, 0x60, 0x98, 0xe9, 0xda, 0x7c, 0xc3, - 0x39, 0xd2, 0xe1, 0x4a, 0xec, 0x12, 0x12, 0x5f, 0xdb, 0x42, 0x5a, 0x1e, 0x45, 0x40, 0xb5, 0x2e, 0xed, 0xab, 0xf4, - 0xe9, 0x1c, 0x7f, 0x39, 0x57, 0xe9, 0xd3, 0x31, 0xfe, 0x6a, 0x5d, 0x61, 0x4a, 0xcf, 0x1a, 0x35, 0x81, 0x34, 0x67, - 0x75, 0x58, 0xd8, 0x4f, 0x64, 0x98, 0xfb, 0x80, 0x6d, 0xc3, 0x17, 0xf8, 0xf1, 0x93, 0x4d, 0x0c, 0xae, 0xe8, 0xf2, - 0x1c, 0x02, 0x2b, 0xd2, 0xd3, 0xda, 0xf2, 0x79, 0x43, 0xf9, 0x58, 0xff, 0x83, 0x09, 0x3f, 0xee, 0x92, 0x30, 0xa7, - 0x29, 0x45, 0x25, 0xc7, 0xf5, 0xd8, 0x0b, 0xdc, 0xe8, 0xfe, 0x9a, 0xa4, 0x10, 0x4d, 0xd2, 0xf6, 0x3e, 0xca, 0xa5, - 0xff, 0xfb, 0xa2, 0x1d, 0x40, 0x22, 0xdd, 0x65, 0xdd, 0x73, 0x42, 0x3f, 0xf8, 0x7b, 0x24, 0xf1, 0x77, 0x05, 0x39, - 0x95, 0x2f, 0x48, 0xe1, 0x43, 0xd7, 0x4f, 0x36, 0x1a, 0xab, 0x76, 0x53, 0x9a, 0x6d, 0x89, 0x81, 0x84, 0xe5, 0x41, - 0x99, 0x77, 0x39, 0xf5, 0x7a, 0x78, 0xd1, 0x3f, 0x0e, 0xef, 0xcc, 0x27, 0x9b, 0xe4, 0x54, 0x5d, 0xba, 0xd1, 0x07, - 0x36, 0x35, 0x27, 0x5e, 0x34, 0xf1, 0x81, 0x79, 0x1c, 0xfb, 0x6e, 0xf0, 0x81, 0x3f, 0x9a, 0xe1, 0x3a, 0x41, 0xd3, - 0x9d, 0x9d, 0x22, 0xb2, 0x80, 0x09, 0xe9, 0x0f, 0x91, 0xab, 0xad, 0x81, 0x82, 0xf2, 0x2a, 0xd3, 0xbf, 0xe5, 0x8c, - 0x62, 0x5e, 0xcb, 0x00, 0xcb, 0x73, 0xb0, 0x26, 0x02, 0x57, 0x7e, 0x43, 0xc5, 0xf5, 0x52, 0x0d, 0x79, 0xaa, 0x74, - 0xe5, 0x96, 0xe5, 0xa2, 0xbd, 0xc6, 0x1e, 0xfe, 0xfb, 0xcf, 0x41, 0xc9, 0x43, 0x3e, 0x97, 0xf5, 0xf2, 0x69, 0x33, - 0x84, 0x52, 0x93, 0x5c, 0xc8, 0x1e, 0xf0, 0x71, 0xce, 0x60, 0x36, 0x7f, 0x5a, 0x6e, 0xec, 0xc6, 0xf1, 0x7a, 0xc9, - 0xa6, 0x74, 0xb5, 0x76, 0x9a, 0x0f, 0xaa, 0x28, 0x87, 0xc8, 0x03, 0xfb, 0x65, 0xdd, 0x3a, 0x3e, 0x7c, 0x05, 0xa6, - 0x5c, 0xc0, 0x50, 0x86, 0xb3, 0x99, 0x9a, 0xab, 0x02, 0x76, 0x34, 0x73, 0x0e, 0x7f, 0x59, 0x7f, 0xf3, 0xc6, 0xfe, - 0x26, 0x6b, 0x1c, 0x00, 0x63, 0x2c, 0xec, 0x52, 0x38, 0x5f, 0x2c, 0x8d, 0x57, 0xcc, 0x68, 0xe6, 0x06, 0xcd, 0xd3, - 0xb9, 0x2c, 0x6c, 0xf1, 0x15, 0x63, 0x53, 0x60, 0xb8, 0x8d, 0x4a, 0xe9, 0xb5, 0xcf, 0x6e, 0x58, 0x66, 0xf3, 0x52, - 0xfd, 0x58, 0x4d, 0x0b, 0x0c, 0xca, 0xc9, 0x6f, 0x32, 0x39, 0x57, 0x27, 0x4d, 0x69, 0x84, 0x73, 0xe0, 0x33, 0x97, - 0x8f, 0x58, 0xe9, 0x48, 0x8d, 0x0c, 0x55, 0x1a, 0x40, 0xe3, 0xc8, 0x4e, 0x1b, 0xca, 0x7b, 0x80, 0xa8, 0x1b, 0xc6, - 0x66, 0x38, 0x7a, 0x0f, 0x92, 0x18, 0x70, 0x38, 0xf9, 0x70, 0xf2, 0xb4, 0x5c, 0x6b, 0xd2, 0x04, 0xb1, 0x3a, 0x5d, - 0x9a, 0x4a, 0x4a, 0x1a, 0x61, 0x06, 0x8e, 0xfe, 0x10, 0x42, 0x5d, 0x55, 0xbb, 0x36, 0x4a, 0x71, 0xe6, 0x63, 0x4c, - 0xf1, 0x1d, 0xb0, 0x38, 0x6e, 0x04, 0x58, 0xb6, 0xe8, 0x86, 0x9a, 0xd7, 0x2e, 0xc2, 0x23, 0x2f, 0x37, 0x6c, 0x03, - 0x58, 0x02, 0x9c, 0x60, 0xf9, 0x5b, 0x48, 0x5e, 0xae, 0x97, 0xdc, 0x90, 0x2f, 0x9a, 0x8f, 0x55, 0x6e, 0x64, 0xd5, - 0xf4, 0xfe, 0x56, 0xe5, 0x83, 0x2a, 0x90, 0xe9, 0xda, 0xa1, 0x69, 0x05, 0xd4, 0x5b, 0xd1, 0x2a, 0x61, 0x07, 0x62, - 0x4c, 0x25, 0xfc, 0xca, 0x66, 0x33, 0x36, 0x49, 0x62, 0x5d, 0xe8, 0x98, 0xb2, 0xb0, 0xda, 0x70, 0x7b, 0xf7, 0x68, - 0xa0, 0xfe, 0x00, 0xc1, 0x45, 0x44, 0xf4, 0x39, 0x3e, 0x20, 0x21, 0x33, 0xd5, 0x83, 0x89, 0x7a, 0x2c, 0x82, 0x88, - 0x7f, 0x05, 0xd4, 0xcc, 0x35, 0xe5, 0x38, 0x34, 0x4e, 0x7f, 0xf2, 0x7d, 0x11, 0x66, 0xe6, 0x7e, 0xdb, 0x51, 0xd1, - 0xb6, 0xe3, 0xbb, 0x71, 0xbe, 0xe9, 0x38, 0x76, 0xaa, 0x1a, 0xe0, 0xd4, 0xfa, 0xa1, 0xb4, 0x8d, 0x89, 0x40, 0x0d, - 0xd4, 0xf3, 0xb7, 0xaf, 0xfe, 0xf6, 0xe6, 0xf5, 0xbe, 0x18, 0x01, 0xbb, 0x6c, 0x43, 0x97, 0xeb, 0x60, 0x4b, 0xa7, - 0x3f, 0xfd, 0xf0, 0xb0, 0x6e, 0x5b, 0xce, 0x0b, 0x47, 0x35, 0xc8, 0x0e, 0x59, 0xc2, 0x8b, 0x93, 0xf0, 0x86, 0x45, - 0x9f, 0x0c, 0x06, 0xb9, 0xf3, 0xfa, 0xe1, 0xbe, 0xfd, 0xf1, 0xcd, 0x0f, 0x7b, 0x0f, 0xf5, 0xc8, 0xb1, 0x01, 0xb7, - 0x27, 0xe1, 0xea, 0x01, 0xb3, 0x6b, 0xab, 0x86, 0x3a, 0xf1, 0xc3, 0x98, 0x35, 0x8c, 0xe0, 0xd5, 0xf9, 0xdb, 0xf7, - 0x08, 0xae, 0x9c, 0x05, 0xa1, 0xae, 0x3e, 0x6d, 0xf2, 0x3f, 0xbe, 0x7b, 0xf3, 0xfe, 0xbd, 0x6a, 0x60, 0x5a, 0xe6, - 0x58, 0xee, 0x9d, 0x6f, 0xe2, 0x1d, 0x14, 0xa7, 0x76, 0xaf, 0x13, 0x55, 0x23, 0x41, 0xba, 0x38, 0x1b, 0x2a, 0xab, - 0x6c, 0x73, 0x4e, 0xed, 0xf8, 0x97, 0x49, 0xfa, 0xdd, 0x6b, 0x5e, 0x35, 0xf8, 0x68, 0x3b, 0x49, 0x2d, 0x94, 0x2c, - 0xbd, 0xe0, 0xba, 0xa6, 0xd4, 0xbd, 0xab, 0x29, 0x05, 0xf1, 0xb1, 0x82, 0x1f, 0xd7, 0xe1, 0x52, 0x62, 0x47, 0xd8, - 0xdd, 0x6e, 0x70, 0x49, 0x32, 0xdc, 0x27, 0x0c, 0x9a, 0xa7, 0xd5, 0x28, 0x8f, 0xba, 0xa6, 0x98, 0x0b, 0x5e, 0x19, - 0x6c, 0x27, 0x3e, 0x58, 0x5f, 0x33, 0xf9, 0x9e, 0xb1, 0xc8, 0xaa, 0x72, 0xdf, 0x89, 0x41, 0x49, 0x2a, 0xa0, 0x66, - 0x74, 0x37, 0xc3, 0x69, 0xca, 0xca, 0x9d, 0x82, 0x49, 0xb3, 0x39, 0x0e, 0x93, 0x24, 0x5c, 0xf6, 0x1c, 0x7b, 0x75, - 0xa7, 0x2a, 0x7d, 0xa1, 0xec, 0xe0, 0x16, 0xd7, 0xbd, 0xdf, 0xfe, 0x53, 0x42, 0xf3, 0x54, 0x7e, 0x9d, 0xb0, 0xe5, - 0x8a, 0x45, 0x6e, 0xb2, 0x8e, 0x58, 0xaa, 0xfc, 0xf6, 0xbf, 0xaf, 0x4a, 0x82, 0x7d, 0x5f, 0x6e, 0x43, 0x2c, 0xbd, - 0xdc, 0xe4, 0xda, 0x0f, 0x6f, 0x1f, 0xe5, 0xbe, 0x55, 0x3b, 0x2a, 0x2f, 0xbc, 0xf9, 0x22, 0xab, 0x7d, 0x9a, 0x6c, - 0x99, 0x9b, 0x18, 0x3d, 0xdd, 0x07, 0x28, 0xe7, 0xe1, 0x6d, 0xef, 0xb7, 0xff, 0x64, 0x0a, 0x9b, 0x9d, 0xbb, 0xae, - 0x7e, 0xa0, 0xc5, 0x15, 0xad, 0xaf, 0x53, 0x59, 0x62, 0x78, 0x5f, 0x59, 0xe0, 0x4a, 0x21, 0xed, 0xca, 0xea, 0xe5, - 0xdb, 0x96, 0x39, 0x7d, 0xeb, 0xcd, 0x17, 0x9f, 0x3a, 0x29, 0x00, 0xe8, 0xce, 0x59, 0x41, 0xa5, 0xcf, 0x30, 0xad, - 0x51, 0x6f, 0xff, 0x05, 0xfb, 0xc4, 0x79, 0xed, 0x9a, 0xd2, 0xe7, 0x98, 0x0d, 0xd7, 0xdc, 0xbe, 0x1a, 0x8d, 0xb2, - 0xb4, 0xa4, 0x72, 0x7b, 0xf0, 0x0e, 0x3b, 0xad, 0x94, 0x70, 0xf6, 0xa2, 0x67, 0xeb, 0x14, 0xb6, 0x65, 0x0f, 0x80, - 0xa0, 0x9d, 0x73, 0x0d, 0x38, 0x9a, 0xf1, 0x35, 0xb9, 0x2b, 0x55, 0xbe, 0x5d, 0x41, 0xd6, 0x50, 0x8a, 0x29, 0x2d, - 0xb3, 0x5b, 0x43, 0xa3, 0x7e, 0x38, 0xb7, 0x91, 0xbb, 0xa2, 0x4b, 0x02, 0x05, 0x6f, 0x4c, 0x40, 0xe9, 0x52, 0x92, - 0xa2, 0x6f, 0x5c, 0xff, 0x66, 0x3f, 0x81, 0xaa, 0x99, 0x82, 0x21, 0x69, 0xfe, 0xf3, 0x88, 0x37, 0xd2, 0xe5, 0xfd, - 0x69, 0x37, 0xa6, 0x0a, 0x7b, 0xdb, 0x64, 0x5e, 0xfd, 0xe3, 0x6e, 0xf3, 0xea, 0x8b, 0xbd, 0xcc, 0xab, 0x7f, 0xfc, - 0xec, 0xe6, 0xd5, 0x6f, 0x65, 0xf3, 0x6a, 0xd8, 0xc4, 0x6f, 0xd8, 0x5e, 0x46, 0xcf, 0xc2, 0xc8, 0x28, 0xbc, 0x8d, - 0x07, 0x0e, 0x17, 0x7a, 0xe2, 0xc9, 0x82, 0x81, 0x16, 0x89, 0x83, 0xcb, 0x0f, 0xe7, 0x60, 0x9b, 0xdc, 0x6c, 0x7d, - 0xfc, 0xb9, 0x6c, 0x8f, 0xfd, 0x70, 0xae, 0x4a, 0xc1, 0xd2, 0x03, 0x11, 0x2c, 0x1d, 0x1c, 0xc4, 0x7f, 0xb9, 0x73, - 0x5e, 0x5e, 0x3a, 0xfd, 0xb6, 0x03, 0xc1, 0x46, 0x40, 0x31, 0x80, 0x05, 0x76, 0xbf, 0xdd, 0x86, 0x82, 0x5b, 0xa9, - 0xa0, 0x05, 0x05, 0x9e, 0x54, 0xd0, 0x81, 0x82, 0x89, 0x54, 0x70, 0x04, 0x05, 0x53, 0xa9, 0xe0, 0x18, 0x0a, 0x6e, - 0xd4, 0xf4, 0x32, 0xc8, 0x8c, 0xc7, 0x8f, 0xf5, 0xab, 0x42, 0x9e, 0x8c, 0x3c, 0xff, 0x3c, 0xaf, 0x72, 0x6c, 0x88, - 0xa0, 0x8d, 0xe6, 0xa1, 0xce, 0xcd, 0xff, 0x46, 0x5f, 0x8c, 0xc0, 0x9d, 0x1a, 0x94, 0x7a, 0x06, 0xa8, 0x44, 0xa9, - 0x66, 0x5b, 0xbc, 0x56, 0x7b, 0xaa, 0x9e, 0x7d, 0xa0, 0x25, 0xec, 0xfe, 0x7a, 0xe8, 0x4a, 0x23, 0x2a, 0x77, 0x9e, - 0x2f, 0xb2, 0x08, 0x4e, 0xeb, 0x41, 0xee, 0x91, 0xd6, 0x86, 0x38, 0xb6, 0x70, 0x35, 0xfd, 0x1a, 0xf9, 0x03, 0x2b, - 0x09, 0xc1, 0xe1, 0x48, 0x44, 0x2e, 0x12, 0x1f, 0x50, 0x54, 0xfd, 0xd2, 0xbe, 0xea, 0xbb, 0x79, 0x90, 0x29, 0x1e, - 0xef, 0x8c, 0x46, 0xbf, 0xcc, 0xc2, 0x48, 0x91, 0x98, 0xbb, 0x36, 0x12, 0x77, 0xde, 0x5b, 0x18, 0xa4, 0xe3, 0xee, - 0xcd, 0x21, 0x2e, 0xe8, 0xe9, 0xb4, 0xb7, 0x32, 0x6e, 0x17, 0x2c, 0xe8, 0xcd, 0xb8, 0x39, 0x28, 0xac, 0x3f, 0x59, - 0xf1, 0x2c, 0x75, 0x61, 0x94, 0x86, 0x7b, 0x22, 0x7f, 0x4b, 0xa3, 0x34, 0xb3, 0xad, 0x94, 0x5b, 0x4e, 0x69, 0xb2, - 0xfe, 0xfb, 0x73, 0xd8, 0xb9, 0xbc, 0x66, 0xe3, 0xf5, 0x5c, 0x39, 0x0f, 0xe7, 0x3b, 0x6d, 0x5a, 0xe4, 0x57, 0x30, - 0x4a, 0x95, 0x2e, 0xfa, 0x4c, 0xb1, 0xbd, 0xf9, 0xb7, 0xe8, 0x31, 0x2d, 0xd6, 0x4f, 0x60, 0x6c, 0x4a, 0x42, 0x28, - 0x1b, 0xbe, 0x03, 0xd0, 0x96, 0x8c, 0x4a, 0xce, 0x01, 0x7e, 0xd2, 0xf3, 0x85, 0x2b, 0x8d, 0x67, 0xf8, 0x3d, 0x8b, - 0x63, 0x77, 0x2e, 0xea, 0x57, 0xc7, 0x09, 0x3e, 0x36, 0x99, 0xa4, 0x8f, 0x00, 0x04, 0x9d, 0xb1, 0x57, 0xb1, 0x05, - 0x02, 0x53, 0x66, 0x30, 0x7b, 0x83, 0x45, 0xcb, 0x0d, 0x67, 0x3c, 0x0b, 0x96, 0xa7, 0x68, 0xe2, 0x02, 0x48, 0xe4, - 0x86, 0xf9, 0xe5, 0xc2, 0xc4, 0x9d, 0x97, 0x8b, 0x68, 0xad, 0x53, 0x79, 0x6c, 0x99, 0x85, 0x49, 0xa1, 0xf0, 0x53, - 0x4c, 0x26, 0xfc, 0x70, 0xfe, 0xbb, 0xda, 0x4b, 0x6c, 0xb1, 0x73, 0x79, 0x1f, 0x18, 0x41, 0x32, 0xb2, 0x10, 0xc6, - 0x8a, 0x05, 0x20, 0xec, 0x05, 0xc9, 0xc2, 0x44, 0xef, 0x6e, 0xad, 0x15, 0xe8, 0x86, 0x85, 0x6b, 0xbb, 0x29, 0xc7, - 0xb4, 0xe8, 0x45, 0xf3, 0xb1, 0xab, 0x39, 0xad, 0x63, 0x43, 0xfc, 0xb1, 0xec, 0x8e, 0x9e, 0x62, 0x0f, 0xca, 0xd4, - 0xbb, 0xd9, 0xcc, 0xc2, 0x20, 0x31, 0x67, 0xee, 0xd2, 0xf3, 0xef, 0x7b, 0xcb, 0x30, 0x08, 0xe3, 0x95, 0x3b, 0x61, - 0xfd, 0x5c, 0x75, 0xd3, 0xc7, 0x68, 0x49, 0xdc, 0x61, 0xdf, 0xb1, 0x5a, 0x11, 0x5b, 0x52, 0xeb, 0x2c, 0x18, 0xd2, - 0xcc, 0x67, 0x77, 0x29, 0xff, 0x7c, 0xa1, 0x32, 0x55, 0xc5, 0x2d, 0x47, 0x2d, 0x40, 0x0e, 0xe1, 0x91, 0x96, 0x20, - 0xbe, 0x60, 0x9f, 0x33, 0xf3, 0x3d, 0xab, 0xd5, 0x89, 0xd8, 0x52, 0xb1, 0x3a, 0x8d, 0x9d, 0x47, 0xe1, 0xed, 0x10, - 0x46, 0x8b, 0x8d, 0xcd, 0x98, 0xf9, 0x33, 0x7c, 0x63, 0xa2, 0x73, 0xa7, 0xe8, 0xc7, 0x44, 0x95, 0x0f, 0xf4, 0xc6, - 0x96, 0x7d, 0x78, 0xdd, 0x6b, 0x29, 0x76, 0x7f, 0xe9, 0x05, 0x26, 0x4d, 0xe7, 0xd8, 0x5e, 0x49, 0x7d, 0xc9, 0xf0, - 0xd3, 0x37, 0x58, 0xdd, 0x51, 0xec, 0x3e, 0x88, 0xf6, 0x33, 0x3f, 0xbc, 0xed, 0x2d, 0xbc, 0xe9, 0x94, 0x05, 0x7d, - 0x1c, 0x73, 0x56, 0xc8, 0x7c, 0xdf, 0x5b, 0xc5, 0x5e, 0xdc, 0x5f, 0xba, 0x77, 0xbc, 0xd7, 0xc3, 0xa6, 0x5e, 0xdb, - 0xbc, 0xd7, 0xf6, 0xde, 0xbd, 0x4a, 0xdd, 0x80, 0x23, 0x29, 0xf5, 0xc3, 0x87, 0xd6, 0x51, 0xec, 0xd2, 0x3c, 0xf7, - 0xee, 0x75, 0x15, 0xb1, 0xcd, 0xd2, 0x8d, 0xe6, 0x5e, 0xd0, 0xb3, 0x53, 0xeb, 0x66, 0x43, 0x1b, 0xe3, 0x71, 0xb7, - 0xdb, 0x4d, 0xad, 0xa9, 0x78, 0xb2, 0xa7, 0xd3, 0xd4, 0x9a, 0x88, 0xa7, 0xd9, 0xcc, 0xb6, 0x67, 0xb3, 0xd4, 0xf2, - 0x44, 0x41, 0xbb, 0x35, 0x99, 0xb6, 0x5b, 0xa9, 0x75, 0x2b, 0xd5, 0x48, 0x2d, 0xc6, 0x9f, 0x22, 0x36, 0xed, 0xe3, - 0x46, 0xe2, 0x76, 0xe9, 0xc7, 0xb6, 0x9d, 0x22, 0x06, 0xb8, 0x2c, 0xe0, 0x26, 0xd4, 0x2a, 0x5e, 0x6d, 0xf6, 0xae, - 0xa9, 0xe4, 0x9f, 0x9b, 0x4c, 0x6a, 0xeb, 0x4d, 0xdd, 0xe8, 0xc3, 0x95, 0x22, 0xcd, 0xc2, 0x75, 0xa9, 0xda, 0x46, - 0x80, 0xc1, 0xbc, 0xeb, 0x41, 0xd4, 0xcc, 0xfe, 0x38, 0x8c, 0xe0, 0xcc, 0x46, 0xee, 0xd4, 0x5b, 0xc7, 0x3d, 0xa7, - 0xb5, 0xba, 0x13, 0x45, 0x7c, 0xaf, 0xe7, 0x05, 0x78, 0xf6, 0x7a, 0x71, 0xe8, 0x7b, 0x53, 0x51, 0xd4, 0x74, 0x96, - 0x9c, 0x96, 0xde, 0xc7, 0x98, 0x31, 0x1e, 0x46, 0x3e, 0x72, 0x7d, 0x5f, 0xb1, 0xda, 0xb1, 0xc2, 0xdc, 0x18, 0x6f, - 0x32, 0x14, 0x3b, 0x26, 0xb8, 0x60, 0x7c, 0x18, 0xe7, 0x70, 0x75, 0x97, 0xed, 0x79, 0xe7, 0x68, 0x75, 0x97, 0x7e, - 0xb5, 0x64, 0x53, 0xcf, 0x55, 0xb4, 0x7c, 0x37, 0x39, 0x36, 0xdc, 0x76, 0xe8, 0x9b, 0x86, 0x6d, 0x2a, 0x8e, 0x05, - 0x44, 0x17, 0x7e, 0xe4, 0x2d, 0x57, 0x61, 0x94, 0xb8, 0x41, 0x92, 0xa6, 0xa3, 0xab, 0x34, 0xed, 0x5f, 0x78, 0xda, - 0xe5, 0x3f, 0x34, 0xa2, 0x85, 0x74, 0x3b, 0x98, 0xea, 0x57, 0xc6, 0x1b, 0x26, 0x5b, 0x32, 0x01, 0x19, 0x43, 0x2b, - 0x26, 0xb9, 0x32, 0xd1, 0xdb, 0x6a, 0x65, 0x02, 0x72, 0x56, 0x9d, 0x0c, 0xa3, 0x8a, 0x55, 0x90, 0x02, 0x41, 0x85, - 0x37, 0x6c, 0x70, 0x21, 0x99, 0x45, 0x01, 0xd3, 0x83, 0x95, 0xc9, 0xb5, 0xef, 0x49, 0x13, 0xef, 0xf9, 0xf5, 0x6e, - 0xde, 0xf3, 0x9f, 0xc9, 0x3e, 0xbc, 0xe7, 0xd7, 0x9f, 0x9d, 0xf7, 0x7c, 0x52, 0x75, 0xed, 0x3b, 0x0b, 0x07, 0x6a, - 0x76, 0x97, 0x05, 0xa4, 0x29, 0xa2, 0xa0, 0x79, 0x67, 0xc9, 0x7f, 0xeb, 0x8a, 0x27, 0x7a, 0xa3, 0x34, 0xb0, 0x44, - 0xb9, 0x81, 0x81, 0x7f, 0x1b, 0x0c, 0xfe, 0x1e, 0xc9, 0xcf, 0xb3, 0xd9, 0xe0, 0x75, 0x28, 0x15, 0x64, 0x4f, 0xdc, - 0xcc, 0xa7, 0x10, 0xe0, 0x88, 0xde, 0x64, 0x86, 0x58, 0x90, 0x02, 0x0a, 0xe2, 0xa3, 0x90, 0xb1, 0xfd, 0x34, 0x33, - 0x87, 0xec, 0x17, 0x87, 0xa0, 0x65, 0x06, 0xc6, 0xc2, 0x0b, 0xb6, 0xa2, 0xb4, 0x9e, 0xb3, 0x84, 0x87, 0xad, 0x78, - 0x79, 0x7f, 0x36, 0xd5, 0xce, 0x42, 0x3d, 0xf5, 0xe2, 0xb7, 0x65, 0x1f, 0x54, 0x21, 0x82, 0xc8, 0xd3, 0x49, 0xb9, - 0x49, 0xa3, 0x14, 0x6a, 0x06, 0x5f, 0x53, 0xf3, 0xd3, 0xc2, 0x4c, 0x7b, 0x72, 0x43, 0x9e, 0x6b, 0xb2, 0x42, 0x8c, - 0xb9, 0x4b, 0xdf, 0x86, 0x73, 0x79, 0x98, 0x3e, 0x13, 0x43, 0x77, 0x4c, 0xa9, 0xb9, 0x37, 0x4d, 0x53, 0xbd, 0x2f, - 0x00, 0x21, 0x11, 0x5a, 0xb6, 0x8b, 0x89, 0x8b, 0x73, 0x81, 0x96, 0xdf, 0x45, 0xd3, 0x45, 0xf3, 0x19, 0x98, 0x6e, - 0xf0, 0x6b, 0x69, 0x0e, 0x33, 0x55, 0x21, 0xf0, 0x91, 0x49, 0x8f, 0x34, 0x21, 0xb0, 0x35, 0x90, 0x0d, 0xe1, 0x0a, - 0x0b, 0x52, 0x35, 0x2a, 0x26, 0xe0, 0xa0, 0xed, 0x09, 0x04, 0xda, 0x11, 0xda, 0x2e, 0x42, 0x3b, 0xbc, 0x0e, 0x3e, - 0xa4, 0x6a, 0xc6, 0xfb, 0xe1, 0xf6, 0x1b, 0x9e, 0x1c, 0x40, 0x83, 0x61, 0x49, 0x93, 0xb5, 0xc3, 0x64, 0x16, 0x58, - 0x89, 0xf8, 0xd6, 0xb0, 0xe2, 0x5b, 0xe5, 0xd9, 0x46, 0x04, 0xa9, 0x4a, 0xdc, 0x95, 0x09, 0xea, 0x13, 0xc4, 0xbd, - 0x1c, 0xe3, 0x49, 0xf1, 0xb0, 0xfa, 0xeb, 0x18, 0x70, 0x23, 0x4a, 0xf2, 0x88, 0x7f, 0xfa, 0x93, 0x75, 0x14, 0x87, - 0x51, 0x6f, 0x15, 0x7a, 0x41, 0xc2, 0xa2, 0x14, 0x41, 0x75, 0x89, 0xf0, 0x11, 0xe0, 0xb9, 0xda, 0x84, 0x2b, 0x77, - 0xe2, 0x25, 0xf7, 0x3d, 0x9b, 0xb3, 0x14, 0x76, 0x9f, 0x73, 0x07, 0x76, 0x6d, 0xfd, 0x1e, 0x87, 0xe6, 0x73, 0x64, - 0xfc, 0xa2, 0x2a, 0x3b, 0x23, 0x6f, 0xf3, 0xbe, 0xf4, 0x96, 0xc2, 0x74, 0x01, 0xfb, 0xe1, 0x46, 0xe6, 0x1c, 0xb0, - 0x3c, 0x2c, 0xb5, 0x3d, 0x65, 0x73, 0x03, 0xb1, 0x36, 0xdc, 0x00, 0x89, 0x3f, 0x56, 0x47, 0x57, 0xec, 0xfa, 0x62, - 0xe0, 0x78, 0xf4, 0x7d, 0x46, 0xd6, 0x73, 0x21, 0xa9, 0xa5, 0xb1, 0x4f, 0xcd, 0x31, 0x9b, 0x85, 0x11, 0xa3, 0x90, - 0xee, 0x4e, 0x77, 0x75, 0xb7, 0x7f, 0xf7, 0xdb, 0xa7, 0x5f, 0xdf, 0x4f, 0x10, 0x26, 0x9a, 0xe8, 0x4c, 0xdf, 0xd1, - 0x5b, 0x95, 0x9e, 0x01, 0x6b, 0x48, 0x90, 0x9f, 0x90, 0xc3, 0x49, 0x4f, 0x55, 0xfb, 0xb5, 0x91, 0x33, 0x57, 0x21, - 0xa7, 0x79, 0x11, 0xf3, 0xdd, 0xc4, 0xbb, 0x11, 0x3c, 0x63, 0xfb, 0x68, 0x75, 0x27, 0xd6, 0x18, 0x09, 0xde, 0x03, - 0x16, 0xa9, 0x34, 0x14, 0xb1, 0x48, 0xe5, 0x62, 0x5c, 0xa4, 0x7e, 0x65, 0x36, 0x22, 0x98, 0x54, 0x89, 0xd2, 0x77, - 0x56, 0x77, 0x32, 0x89, 0xce, 0x9b, 0x65, 0x94, 0xba, 0x1c, 0x05, 0x74, 0xe9, 0x4d, 0xa7, 0x3e, 0x4b, 0x0b, 0x0b, - 0x5d, 0x5c, 0x4b, 0x09, 0x38, 0x19, 0x1c, 0xdc, 0x71, 0x1c, 0xfa, 0xeb, 0x84, 0xd5, 0x83, 0x8b, 0x80, 0xd3, 0xb2, - 0x73, 0xe0, 0xe0, 0xef, 0xe2, 0x58, 0x3b, 0xc0, 0x6e, 0xc3, 0x36, 0xb1, 0xfb, 0x10, 0xf4, 0xdf, 0x6c, 0x17, 0x87, - 0x0e, 0xaf, 0xb2, 0x41, 0x1b, 0x35, 0x13, 0x31, 0x80, 0x2c, 0x11, 0xf6, 0x56, 0x2c, 0x87, 0x97, 0x65, 0x81, 0xcf, - 0xb3, 0xa2, 0xb4, 0x38, 0x99, 0xdf, 0xe7, 0x8c, 0xbd, 0xa8, 0x3f, 0x63, 0x2f, 0xc4, 0x19, 0xdb, 0xbe, 0x33, 0x1f, - 0xcf, 0x1c, 0xf8, 0xaf, 0x9f, 0x4f, 0xa8, 0x67, 0x2b, 0xed, 0xd5, 0x9d, 0xe2, 0xac, 0xee, 0x14, 0xb3, 0xb5, 0xba, - 0x53, 0xb0, 0x6b, 0xb4, 0x3c, 0x32, 0xac, 0x96, 0x6e, 0xd8, 0x0a, 0x14, 0xc2, 0x1f, 0xbb, 0xf0, 0xca, 0x39, 0x84, - 0x77, 0xd0, 0xaa, 0x53, 0x7d, 0xd7, 0xda, 0x7e, 0xd4, 0xe9, 0x2c, 0x09, 0xa4, 0xad, 0x5b, 0x89, 0x3b, 0x1e, 0xb3, - 0x69, 0x6f, 0x16, 0x4e, 0xd6, 0xf1, 0xbf, 0xf9, 0xf8, 0x39, 0x10, 0xb7, 0x22, 0x82, 0x52, 0x3f, 0xa2, 0x29, 0x68, - 0xf7, 0x6e, 0x98, 0xe8, 0x61, 0x93, 0xad, 0x53, 0x8f, 0x32, 0x14, 0xb4, 0xac, 0xc3, 0x9a, 0x4d, 0x5e, 0x0f, 0xe8, - 0xdf, 0x6d, 0x95, 0x9a, 0x51, 0xcc, 0x27, 0x80, 0x65, 0x2b, 0x38, 0x1e, 0x0e, 0x0d, 0xbe, 0x9a, 0x76, 0xb7, 0x7e, - 0xb8, 0x97, 0xe2, 0x4b, 0x57, 0x82, 0xa8, 0x70, 0xba, 0xc5, 0xdd, 0xa0, 0xb6, 0xf7, 0xda, 0xb4, 0x47, 0x2a, 0xbd, - 0x6e, 0x21, 0x08, 0x79, 0xdd, 0x3d, 0xb1, 0xfc, 0xe3, 0x17, 0x87, 0xf0, 0x1f, 0x71, 0xf5, 0xff, 0x4c, 0xea, 0x18, - 0xf5, 0xb3, 0xa4, 0xc0, 0xa8, 0x13, 0xab, 0x84, 0x8c, 0xf8, 0xfe, 0xf5, 0x67, 0xb3, 0x87, 0x35, 0xd8, 0xbb, 0x36, - 0x19, 0xed, 0x95, 0x6b, 0xbf, 0x0c, 0x43, 0xc8, 0x9e, 0x5d, 0xad, 0x2e, 0xc0, 0x43, 0x1e, 0x18, 0xc9, 0x00, 0x1a, - 0x09, 0x39, 0x82, 0xec, 0x45, 0x54, 0x6c, 0x43, 0xa2, 0xc4, 0x9b, 0x26, 0x51, 0xe2, 0xf5, 0x6e, 0x51, 0xe2, 0xbb, - 0xbd, 0x44, 0x89, 0xd7, 0x9f, 0x5d, 0x94, 0x78, 0x53, 0x15, 0x25, 0x2e, 0x42, 0x61, 0xa9, 0x6d, 0x9c, 0xad, 0xf9, - 0xcf, 0x9f, 0xe9, 0x2a, 0xf6, 0x3c, 0x1c, 0x74, 0x6c, 0xca, 0x3a, 0x70, 0xf1, 0x5f, 0x0b, 0x16, 0xb8, 0x11, 0xdf, - 0xa1, 0xe1, 0x62, 0x2e, 0x5a, 0x70, 0xcc, 0x8e, 0xdf, 0x91, 0x8a, 0xfd, 0x30, 0x98, 0xff, 0x08, 0x57, 0xf1, 0xa0, - 0x0e, 0x8c, 0xa4, 0x17, 0x5e, 0xfc, 0x63, 0xb8, 0x5a, 0xaf, 0xce, 0xa0, 0xaf, 0x9f, 0xbd, 0xd8, 0x1b, 0xfb, 0x2c, - 0x8b, 0x06, 0x42, 0x86, 0x96, 0x5c, 0xb7, 0x0e, 0xb6, 0xcd, 0xe2, 0xa7, 0x7b, 0x27, 0x7e, 0xa2, 0xf5, 0x33, 0xff, - 0x4d, 0x16, 0x9c, 0x6a, 0xbd, 0x20, 0x02, 0x61, 0xf3, 0x4a, 0x83, 0x7e, 0xb8, 0x30, 0x72, 0x11, 0xea, 0x35, 0xb3, - 0x14, 0x96, 0x35, 0x8d, 0xfd, 0xb0, 0x8a, 0x50, 0xb3, 0xd6, 0x8d, 0x2c, 0x0a, 0x66, 0x55, 0x9d, 0xbf, 0x0c, 0xd7, - 0x31, 0x9b, 0x86, 0xb7, 0x81, 0x6a, 0x04, 0xdc, 0x1c, 0x94, 0x12, 0x09, 0x66, 0x6d, 0x30, 0x7f, 0xf3, 0x7b, 0x64, - 0x94, 0x21, 0x62, 0x02, 0xa4, 0x0f, 0x5f, 0xaf, 0x4c, 0x32, 0x30, 0x30, 0x71, 0x8a, 0x6a, 0x96, 0x68, 0xf0, 0x91, - 0xa6, 0x85, 0x83, 0x87, 0xb5, 0x14, 0x46, 0x41, 0xa1, 0xc5, 0xb5, 0xc2, 0xb1, 0x16, 0x08, 0xe5, 0xa2, 0x08, 0x45, - 0x55, 0xb3, 0x70, 0xfc, 0x0d, 0x85, 0xfa, 0xc8, 0xdf, 0x42, 0x64, 0x88, 0x74, 0xcd, 0xd7, 0x83, 0x07, 0x66, 0xa2, - 0xc7, 0x57, 0x12, 0x18, 0xdf, 0xde, 0xb0, 0xc8, 0x77, 0xef, 0x35, 0x3d, 0x0d, 0x83, 0xef, 0x01, 0x00, 0xaf, 0xc3, - 0xdb, 0x40, 0xae, 0x80, 0xf9, 0xd2, 0x6a, 0xf6, 0x52, 0x6d, 0x08, 0x31, 0x70, 0xa7, 0x92, 0x46, 0x00, 0x99, 0xea, - 0xe7, 0xec, 0xef, 0x06, 0xfd, 0xfb, 0x0f, 0x3d, 0x35, 0xce, 0xc3, 0xec, 0x43, 0x3f, 0xad, 0xf6, 0xf8, 0xcc, 0xd3, - 0xa7, 0x8f, 0x9a, 0xa7, 0xad, 0x4d, 0x7c, 0xe6, 0x8a, 0xfc, 0xf3, 0x5a, 0x4d, 0x6b, 0xbd, 0xf1, 0x14, 0xc0, 0x28, - 0x2e, 0xc2, 0xf5, 0x64, 0x81, 0x26, 0xd5, 0x9f, 0x6f, 0xbe, 0x09, 0xf4, 0x89, 0x89, 0xc2, 0xb3, 0xa9, 0x97, 0x8a, - 0x72, 0x28, 0xe0, 0xf7, 0xdf, 0x40, 0x0c, 0xec, 0x3f, 0x11, 0x0c, 0xd5, 0x5d, 0x93, 0xb9, 0x5b, 0x3f, 0x68, 0xf3, - 0xf6, 0x21, 0x9f, 0x35, 0x8f, 0x2e, 0x25, 0x2e, 0xe9, 0xea, 0x91, 0x4c, 0x5a, 0x06, 0x9a, 0x1c, 0xc9, 0xb5, 0x29, - 0x48, 0xad, 0xf8, 0x0a, 0xb3, 0x48, 0x4c, 0xe7, 0x2e, 0x2d, 0x06, 0xe3, 0xd8, 0xaa, 0x84, 0x64, 0xb8, 0xa1, 0x0b, - 0x43, 0xf4, 0x55, 0x7e, 0xb7, 0xf4, 0x02, 0x03, 0x13, 0xb1, 0x54, 0xdf, 0xb8, 0x77, 0x90, 0x8a, 0x00, 0x90, 0x5b, - 0xf9, 0x15, 0x14, 0x1a, 0xb2, 0x23, 0x27, 0x64, 0x5b, 0x54, 0x6b, 0x21, 0x21, 0x6e, 0x03, 0x47, 0x5f, 0x28, 0x8a, - 0xa2, 0x64, 0x62, 0x84, 0x92, 0xc9, 0x11, 0x58, 0x8e, 0xe2, 0x00, 0xdc, 0x96, 0xa4, 0xab, 0x3b, 0x2a, 0x01, 0xc9, - 0x00, 0xaf, 0xb6, 0x45, 0x01, 0x8f, 0xb6, 0xdb, 0xb1, 0x45, 0x81, 0x10, 0xe8, 0x21, 0x52, 0xaa, 0x1b, 0x41, 0x50, - 0xfe, 0x9e, 0x82, 0x02, 0x3b, 0xbe, 0xe5, 0x9a, 0x60, 0xc5, 0xa6, 0xc7, 0x51, 0x9f, 0xd5, 0x87, 0x65, 0x0d, 0x24, - 0x2c, 0x08, 0xb7, 0x0e, 0xa5, 0x2c, 0x0b, 0x06, 0xab, 0xc1, 0x8d, 0x28, 0x17, 0xdd, 0x25, 0x4b, 0x16, 0xac, 0x55, - 0x4c, 0xcb, 0x88, 0x61, 0x72, 0xa1, 0xce, 0x6b, 0x62, 0xb6, 0x00, 0xdb, 0xd4, 0xb7, 0x5c, 0x10, 0x2d, 0x8c, 0x39, - 0x4a, 0x75, 0x8d, 0x09, 0xf7, 0x4d, 0x8c, 0x39, 0x6e, 0x2b, 0x53, 0x08, 0xbe, 0xa4, 0x61, 0x11, 0x9b, 0x73, 0x6f, - 0x64, 0xe4, 0x14, 0x28, 0x42, 0x15, 0x57, 0x17, 0x09, 0xb0, 0x6b, 0x6e, 0x79, 0xd1, 0xb2, 0x1b, 0x19, 0xb7, 0xa4, - 0x28, 0x8a, 0xf4, 0x6a, 0x37, 0x7c, 0x9c, 0x10, 0x1b, 0xb0, 0xb1, 0x9f, 0x49, 0xa5, 0x9f, 0x86, 0x49, 0x7f, 0x60, - 0xf7, 0x44, 0x48, 0x08, 0x54, 0x1f, 0xd8, 0x3d, 0xdc, 0xdb, 0xbf, 0x01, 0x6d, 0x8a, 0xba, 0x05, 0x5d, 0x1b, 0x90, - 0x6d, 0x67, 0x02, 0xf1, 0x22, 0xb7, 0x1c, 0x20, 0x3b, 0xdd, 0x82, 0xc5, 0x11, 0xc4, 0x81, 0x11, 0xf7, 0xc5, 0x21, - 0xe6, 0xce, 0x24, 0x5a, 0x2d, 0x8c, 0xcd, 0x9a, 0xa3, 0xa1, 0x3f, 0x73, 0x6c, 0xfb, 0xa0, 0x52, 0x1f, 0x14, 0xd9, - 0x75, 0xb5, 0x75, 0x23, 0x19, 0x38, 0xb6, 0xe9, 0x3d, 0xb3, 0x5a, 0xfd, 0x0a, 0x8d, 0x96, 0xc2, 0x39, 0x8f, 0x50, - 0xfd, 0x35, 0x7c, 0xb2, 0xd1, 0x2a, 0x07, 0x52, 0x2f, 0x3b, 0x67, 0xe0, 0xd8, 0x52, 0xae, 0xff, 0x1a, 0x55, 0x49, - 0x3f, 0x05, 0x93, 0xa6, 0xd4, 0x62, 0x23, 0x48, 0x48, 0xa0, 0xc1, 0x31, 0xfa, 0x8b, 0xf2, 0x5c, 0xd1, 0xe8, 0xf8, - 0xe8, 0xfa, 0xa8, 0x2f, 0x30, 0x8a, 0xf0, 0x5e, 0x94, 0x3b, 0x28, 0x7d, 0x31, 0x2e, 0x63, 0x38, 0x1e, 0xfa, 0x9c, - 0xe5, 0x37, 0x7a, 0x5b, 0xb9, 0x05, 0xec, 0xbf, 0x81, 0x7c, 0x5a, 0x63, 0x88, 0xaf, 0x01, 0x35, 0x20, 0x7d, 0xc9, - 0xce, 0x0e, 0x21, 0x6c, 0x92, 0xdc, 0x5d, 0x91, 0x48, 0xee, 0xdf, 0x19, 0x12, 0x1d, 0xbc, 0x43, 0xcb, 0xfa, 0xab, - 0x27, 0x77, 0x0f, 0xec, 0x92, 0x05, 0xd3, 0x62, 0x87, 0x25, 0xfa, 0xb5, 0x7f, 0x77, 0x05, 0x8c, 0x02, 0x79, 0x7d, - 0xc2, 0x1a, 0x8c, 0x92, 0x86, 0x01, 0x6e, 0x7e, 0x3a, 0x6e, 0xde, 0x5e, 0x5c, 0x0c, 0x36, 0xa0, 0xa0, 0x9c, 0x59, - 0x33, 0x49, 0x29, 0x0e, 0xc9, 0x23, 0xd0, 0xb9, 0x59, 0x13, 0x8c, 0x68, 0xe3, 0x4e, 0x4c, 0x84, 0x25, 0x69, 0xde, - 0xc6, 0xe3, 0xe1, 0xa0, 0xf7, 0xd5, 0x5a, 0x7b, 0xbb, 0xb5, 0xd6, 0xc9, 0x2e, 0xad, 0x35, 0x39, 0xee, 0x91, 0xf9, - 0x53, 0xe6, 0xc0, 0x28, 0x98, 0x73, 0xd9, 0x05, 0xb4, 0xa0, 0xea, 0x46, 0x3f, 0x3f, 0xd1, 0xaa, 0xd2, 0x1b, 0xd9, - 0x86, 0xa2, 0xfa, 0x5b, 0x12, 0x50, 0xc4, 0x85, 0xba, 0xac, 0x1b, 0xbf, 0xc8, 0x75, 0xe3, 0x24, 0xd5, 0xe4, 0x2e, - 0x5b, 0x82, 0xfb, 0x97, 0xdc, 0x21, 0x33, 0xe9, 0x20, 0x77, 0x8b, 0xcc, 0x47, 0x2a, 0x39, 0xfa, 0xe5, 0x82, 0x86, - 0xe4, 0x3e, 0x2a, 0xa4, 0x8c, 0xa2, 0x17, 0x69, 0xb1, 0x6a, 0xee, 0xe7, 0x97, 0x97, 0x83, 0xd6, 0x1d, 0x87, 0x9c, - 0x15, 0xcb, 0xdb, 0xa6, 0xe8, 0xe8, 0x25, 0xbf, 0x96, 0x36, 0x49, 0xe6, 0x91, 0x45, 0x00, 0x16, 0x6a, 0xfa, 0xd2, - 0xbd, 0x76, 0x66, 0x03, 0x81, 0x83, 0xac, 0x71, 0x20, 0xdd, 0xad, 0x9d, 0xa7, 0x94, 0x45, 0xf9, 0xd5, 0xb5, 0x83, - 0xd4, 0x9d, 0x6e, 0x82, 0x65, 0x7d, 0x04, 0xc2, 0xfa, 0x4a, 0xd2, 0x20, 0xf4, 0x6c, 0xc5, 0xee, 0xd7, 0x30, 0x00, - 0x48, 0xff, 0xcb, 0xcf, 0x9c, 0x15, 0x00, 0x4d, 0xa4, 0x62, 0xcb, 0x77, 0xfe, 0x78, 0x88, 0x4d, 0x32, 0x3f, 0xc3, - 0xaa, 0xd5, 0x6f, 0x92, 0xbe, 0x67, 0xc3, 0xdd, 0xb5, 0x8a, 0xea, 0x7c, 0x5e, 0xa3, 0x27, 0xc6, 0xc1, 0x77, 0x59, - 0xb4, 0x0e, 0x30, 0x13, 0x8d, 0x99, 0x44, 0xee, 0xe4, 0xc3, 0x46, 0xfa, 0x1e, 0x57, 0x89, 0x82, 0xba, 0xb8, 0x78, - 0xa9, 0xd0, 0x77, 0x31, 0x70, 0x33, 0xeb, 0x59, 0xad, 0x58, 0x52, 0xd4, 0xf4, 0x1e, 0xdb, 0x6d, 0xf7, 0xc5, 0xec, - 0xb0, 0xa4, 0x3f, 0x6d, 0x75, 0x8a, 0xda, 0xf5, 0x6c, 0x1c, 0xcb, 0xf0, 0x57, 0xee, 0xd8, 0xfa, 0xc7, 0x7f, 0x3a, - 0xe6, 0xdf, 0x2c, 0xad, 0xd1, 0xa7, 0x0c, 0x01, 0xda, 0x17, 0x2e, 0xa6, 0xe5, 0x6b, 0x9a, 0x4a, 0x49, 0xd3, 0xb0, - 0x66, 0x9e, 0xef, 0x9b, 0x3e, 0xb8, 0x17, 0x6d, 0x3e, 0x69, 0x7a, 0xd8, 0xcf, 0x1a, 0x52, 0x06, 0x7c, 0x42, 0x3f, - 0xc5, 0x9d, 0x92, 0x2c, 0xd6, 0xcb, 0xf1, 0x46, 0x56, 0x94, 0x4b, 0xfa, 0xf3, 0xaa, 0xce, 0x5c, 0xfe, 0xec, 0x6c, - 0x36, 0x2b, 0x6a, 0x8d, 0x6d, 0xe5, 0x10, 0x35, 0xbf, 0x8f, 0x6d, 0xdb, 0x2e, 0xc3, 0xb7, 0xe9, 0xa0, 0xd0, 0xc1, - 0x30, 0x51, 0x09, 0xdf, 0xdd, 0xbd, 0xa7, 0xfe, 0xa0, 0xd1, 0x52, 0x57, 0x4d, 0xe7, 0x91, 0xb6, 0xda, 0xff, 0x8b, - 0xa1, 0x20, 0x6a, 0xd8, 0x75, 0xfc, 0xab, 0x7b, 0x65, 0x4b, 0x4f, 0xe5, 0x03, 0xfc, 0xb0, 0xc6, 0x3b, 0xf6, 0xfa, - 0x1e, 0x4d, 0x9b, 0xb6, 0x77, 0x6a, 0xe5, 0x64, 0xb7, 0x60, 0xb3, 0xd4, 0x27, 0x4b, 0x25, 0x2f, 0x61, 0xcb, 0xb8, - 0x37, 0x61, 0x78, 0x41, 0x6a, 0x49, 0xd4, 0x16, 0xad, 0x7a, 0xcc, 0x39, 0xd8, 0x71, 0x39, 0x02, 0x0f, 0xdb, 0x0a, - 0x5e, 0x56, 0x55, 0x6e, 0xd6, 0xc4, 0x47, 0x90, 0x8a, 0x6d, 0xaa, 0x17, 0x4e, 0xb8, 0x4d, 0x3b, 0xf6, 0x5f, 0x0a, - 0xf5, 0x14, 0xe0, 0x4e, 0x37, 0xc2, 0xda, 0x84, 0x2e, 0x4f, 0xf0, 0xef, 0xec, 0x72, 0xee, 0xc5, 0xea, 0xae, 0x68, - 0xdc, 0xd5, 0x85, 0xeb, 0xa6, 0x9c, 0x94, 0xd1, 0xa8, 0xeb, 0x50, 0x5f, 0x66, 0x02, 0x34, 0x93, 0xad, 0x5b, 0xc0, - 0x82, 0xa6, 0x90, 0x20, 0xaf, 0xe6, 0x6e, 0x0c, 0xc5, 0x59, 0xd8, 0x79, 0xb9, 0x7e, 0x3f, 0x4f, 0xef, 0x0c, 0x73, - 0x30, 0x9e, 0x77, 0xf1, 0x72, 0xaf, 0xb0, 0x55, 0xd1, 0x54, 0x06, 0xf7, 0x80, 0x90, 0x48, 0x95, 0x75, 0xe4, 0x9b, - 0x94, 0x3d, 0x4e, 0xd3, 0x37, 0xd5, 0x79, 0x37, 0x77, 0xef, 0x74, 0xe0, 0x5e, 0xa3, 0x0a, 0xaa, 0xbd, 0xae, 0xf6, - 0xca, 0x77, 0xd8, 0x62, 0x9c, 0xb0, 0x02, 0xe0, 0x8a, 0xa2, 0xa0, 0xd1, 0x90, 0x52, 0xc2, 0x7d, 0x34, 0xe9, 0xec, - 0xad, 0x8c, 0xac, 0xc5, 0x3c, 0xb1, 0xbb, 0xfa, 0x2a, 0xd4, 0xb7, 0xb8, 0x19, 0x04, 0xd8, 0x71, 0xec, 0x84, 0xcf, - 0x26, 0xec, 0x18, 0x19, 0x5d, 0x39, 0xb8, 0x83, 0xf0, 0x94, 0x9a, 0x14, 0xdf, 0x96, 0x4e, 0x29, 0xde, 0x25, 0x7c, - 0x57, 0xab, 0xbc, 0xbf, 0x28, 0x68, 0xe3, 0xb9, 0x3f, 0x50, 0x4b, 0xdf, 0xab, 0xf6, 0xd2, 0x0b, 0xf6, 0xaf, 0xeb, - 0xde, 0xed, 0x5d, 0x17, 0x98, 0xc3, 0xbd, 0x2b, 0x03, 0x77, 0x49, 0x56, 0x4a, 0xc9, 0xe0, 0x3b, 0xe9, 0xf2, 0x40, - 0x8e, 0x65, 0xa1, 0x62, 0x2b, 0x92, 0xe8, 0x2f, 0xd6, 0x83, 0xd1, 0xc9, 0xe9, 0xdd, 0xd2, 0x57, 0x6e, 0x58, 0x04, - 0x99, 0x34, 0x07, 0xaa, 0x63, 0xd9, 0xaa, 0x82, 0x91, 0x19, 0xbc, 0x60, 0x3e, 0x50, 0x7f, 0xba, 0xf8, 0xc6, 0xec, - 0xaa, 0xa7, 0x60, 0x8e, 0x71, 0x33, 0x47, 0x16, 0xf7, 0xdc, 0xbd, 0x67, 0xd1, 0x75, 0x4b, 0x55, 0x30, 0x61, 0x26, - 0x31, 0xb7, 0x58, 0xa6, 0xb4, 0xd4, 0x3d, 0xf2, 0xb2, 0x29, 0x22, 0xb5, 0xb2, 0x0a, 0x88, 0xd5, 0x69, 0x75, 0x15, - 0xa7, 0x75, 0x68, 0x1d, 0x75, 0xd5, 0xe1, 0x17, 0x8a, 0x72, 0x32, 0x65, 0xb3, 0x78, 0x88, 0xea, 0x98, 0x13, 0xe4, - 0x07, 0xe9, 0xb7, 0xa2, 0x58, 0x13, 0x3f, 0x36, 0x1d, 0x65, 0xc3, 0x1f, 0x15, 0x05, 0x90, 0x51, 0x4f, 0x79, 0x3c, - 0x6b, 0xcd, 0x0e, 0x67, 0x2f, 0xfa, 0xbc, 0x38, 0xfd, 0xa2, 0x50, 0xdd, 0xa0, 0x7f, 0x5b, 0x52, 0xb3, 0x38, 0x89, - 0xc2, 0x0f, 0x8c, 0xf3, 0x92, 0x4a, 0xa6, 0x28, 0x2a, 0x37, 0x6d, 0x55, 0xbf, 0xe4, 0x74, 0xc7, 0x93, 0x59, 0x2b, - 0xaf, 0x8e, 0x63, 0x3c, 0xc8, 0x06, 0x79, 0x72, 0x20, 0x86, 0x7e, 0x22, 0x83, 0xc9, 0x31, 0xeb, 0x00, 0xe5, 0xa8, - 0x7c, 0x8e, 0x73, 0x31, 0xbf, 0x13, 0x08, 0x7b, 0x9e, 0x7b, 0x70, 0xc4, 0xd8, 0x6c, 0xa0, 0x7e, 0xef, 0xb4, 0xba, - 0x86, 0xe3, 0x1c, 0x59, 0x47, 0xdd, 0x89, 0x6d, 0x1c, 0x5a, 0x87, 0x66, 0xdb, 0x3a, 0x32, 0xba, 0x66, 0xd7, 0xe8, - 0x7e, 0xdb, 0x9d, 0x98, 0x87, 0xd6, 0xa1, 0x61, 0x9b, 0x5d, 0x28, 0x34, 0xbb, 0x66, 0xf7, 0xc6, 0x3c, 0xec, 0x4e, - 0x6c, 0x2c, 0x6d, 0x59, 0x9d, 0x8e, 0xe9, 0xd8, 0x56, 0xa7, 0x63, 0x74, 0xac, 0xa3, 0x23, 0xd3, 0x69, 0x5b, 0x47, - 0x47, 0xe7, 0x9d, 0xae, 0xd5, 0x86, 0x77, 0xed, 0xf6, 0xa4, 0x6d, 0x39, 0x8e, 0x09, 0x7f, 0x19, 0x5d, 0xab, 0x45, - 0x3f, 0x1c, 0xc7, 0x6a, 0x3b, 0x86, 0xed, 0x77, 0x5a, 0xd6, 0xd1, 0x0b, 0x03, 0xff, 0xc6, 0x6a, 0x06, 0xfe, 0x05, - 0xdd, 0x18, 0x2f, 0xac, 0xd6, 0x11, 0xfd, 0xc2, 0x0e, 0x6f, 0x0e, 0xbb, 0xff, 0x54, 0x0f, 0x1a, 0xe7, 0xe0, 0xd0, - 0x1c, 0xba, 0x1d, 0xab, 0xdd, 0x36, 0x0e, 0x1d, 0xab, 0xdb, 0x5e, 0x98, 0x87, 0x2d, 0xeb, 0xe8, 0x78, 0x62, 0x3a, - 0xd6, 0xf1, 0xb1, 0x61, 0x9b, 0x6d, 0xab, 0x65, 0x38, 0xd6, 0x61, 0x1b, 0x7f, 0xb4, 0xad, 0xd6, 0xcd, 0xf1, 0x0b, - 0xeb, 0xa8, 0xb3, 0x38, 0xb2, 0x0e, 0x7f, 0x3e, 0xec, 0x5a, 0xad, 0xf6, 0xa2, 0x7d, 0x64, 0xb5, 0x8e, 0x6f, 0x8e, - 0xac, 0xc3, 0x85, 0xd9, 0x3a, 0xda, 0xda, 0xd2, 0x69, 0x59, 0x00, 0x23, 0x7c, 0x0d, 0x2f, 0x0c, 0xfe, 0x02, 0xfe, - 0x2c, 0xb0, 0xed, 0x1f, 0xd8, 0x4d, 0x5c, 0x6d, 0xfa, 0xc2, 0xea, 0x1e, 0x4f, 0xa8, 0x3a, 0x14, 0x98, 0xa2, 0x06, - 0x34, 0xb9, 0x31, 0xe9, 0xb3, 0xd8, 0x9d, 0x29, 0x3a, 0x12, 0x7f, 0xf8, 0xc7, 0x6e, 0x4c, 0xf8, 0x30, 0x7d, 0xf7, - 0x4f, 0xed, 0x27, 0x5b, 0x72, 0x48, 0x14, 0xff, 0x05, 0xff, 0x87, 0x72, 0x2c, 0x8e, 0x8c, 0xf3, 0xa6, 0x4b, 0xc9, - 0x77, 0xbb, 0x2f, 0x25, 0xbf, 0x59, 0xef, 0x73, 0x29, 0xf9, 0xee, 0xb3, 0x5f, 0x4a, 0x9e, 0x97, 0x7d, 0x6b, 0xde, - 0x95, 0x53, 0x41, 0x7d, 0xb7, 0x29, 0xab, 0x1c, 0x3c, 0x57, 0xbb, 0xbc, 0x58, 0x5f, 0x41, 0x68, 0xbf, 0x77, 0xe1, - 0xe0, 0x9b, 0x75, 0xc1, 0xe0, 0x33, 0x04, 0x1c, 0xfb, 0x2e, 0x24, 0x1c, 0xfb, 0xc3, 0x7a, 0x00, 0x56, 0x66, 0x9c, - 0xcd, 0xf1, 0xa6, 0xe6, 0xc2, 0xf5, 0x67, 0x19, 0x8b, 0x04, 0x25, 0x7d, 0x2c, 0x06, 0xc7, 0x35, 0x20, 0xcf, 0x20, - 0xc9, 0xac, 0x97, 0x41, 0x0c, 0x16, 0xc1, 0x60, 0xc9, 0x31, 0x8b, 0xd2, 0x52, 0x63, 0x4b, 0x04, 0x43, 0xbc, 0xe6, - 0x5e, 0x50, 0x8d, 0xef, 0xd1, 0x00, 0xb8, 0xbe, 0x77, 0xa7, 0xda, 0xaf, 0x02, 0x96, 0x75, 0xc2, 0x40, 0x1a, 0xb8, - 0xfd, 0xba, 0xf7, 0x45, 0x33, 0xdc, 0x92, 0xe1, 0x75, 0xf3, 0x48, 0x61, 0x24, 0xe5, 0xf6, 0x4e, 0xd1, 0x8c, 0x77, - 0xd7, 0x34, 0x6b, 0x3e, 0x5f, 0x68, 0xbe, 0xc5, 0x86, 0x38, 0xeb, 0xb8, 0x0c, 0xaa, 0x52, 0x22, 0xe3, 0x5a, 0x80, - 0xe4, 0x02, 0x6a, 0x6e, 0x68, 0x9c, 0x73, 0xaa, 0xb6, 0x82, 0xfc, 0x8e, 0x2d, 0xbd, 0x2b, 0xf4, 0x29, 0x1b, 0x27, - 0x3f, 0xdb, 0xa0, 0x5c, 0xe1, 0xfd, 0x0a, 0x9c, 0x28, 0xe7, 0x78, 0xc6, 0xa1, 0x0c, 0xe7, 0x8d, 0xd4, 0x2f, 0x69, - 0x23, 0xd2, 0x85, 0xb3, 0xa9, 0xf2, 0xa2, 0x8d, 0x6e, 0x09, 0x0e, 0x5b, 0x0a, 0x2e, 0x08, 0x3f, 0x4f, 0x4e, 0x00, - 0x29, 0x39, 0x6a, 0xa0, 0x9f, 0xc3, 0xb6, 0xce, 0x44, 0xbd, 0xc7, 0xb0, 0x89, 0x79, 0xd0, 0x65, 0x45, 0x8e, 0x36, - 0xb3, 0x99, 0xf9, 0xa1, 0x9b, 0xf4, 0x90, 0x4d, 0x93, 0x58, 0xde, 0x16, 0x7a, 0x2c, 0xf4, 0xb7, 0x18, 0xd3, 0xc9, - 0x1d, 0xf3, 0x4e, 0xd0, 0xf3, 0x61, 0x9b, 0xfd, 0x5d, 0xe6, 0x70, 0xb6, 0x29, 0x98, 0xa3, 0x38, 0x9d, 0x63, 0xc3, - 0x39, 0x32, 0xac, 0xe3, 0x8e, 0x9e, 0x8a, 0x03, 0x27, 0x77, 0x59, 0x00, 0x08, 0x38, 0x40, 0x64, 0xc3, 0xf4, 0x02, - 0x2f, 0xf1, 0x5c, 0x3f, 0x05, 0x7e, 0xb8, 0x28, 0xa4, 0xfc, 0x6b, 0x1d, 0x27, 0x30, 0x47, 0xc1, 0xf4, 0xa2, 0xf3, - 0x87, 0x39, 0x66, 0xc9, 0x2d, 0x63, 0x41, 0x83, 0x61, 0x4c, 0xd9, 0x97, 0xe4, 0xf7, 0xb3, 0xac, 0x4f, 0xc9, 0x6a, - 0x6d, 0x9c, 0x04, 0x7c, 0x7f, 0x08, 0xc7, 0x87, 0x74, 0x64, 0xfc, 0xda, 0x84, 0x70, 0xff, 0xb5, 0x1b, 0xe1, 0x26, - 0x6c, 0x1f, 0x84, 0xfb, 0xaf, 0xcf, 0x8e, 0x70, 0x7f, 0x95, 0x11, 0x6e, 0xc1, 0x7f, 0x30, 0xbf, 0x61, 0x7a, 0x8f, - 0xcf, 0x1a, 0x24, 0x51, 0x79, 0xae, 0x1e, 0x10, 0x03, 0x0f, 0xf9, 0x25, 0x44, 0x14, 0xaf, 0x97, 0x85, 0x64, 0x9d, - 0xa8, 0x00, 0xc5, 0x04, 0x1d, 0x94, 0x18, 0xd0, 0x03, 0x57, 0xb7, 0x2c, 0x39, 0x20, 0xbb, 0x55, 0xce, 0x82, 0xc4, - 0xb7, 0xde, 0x71, 0x39, 0x12, 0x2e, 0x74, 0xbf, 0x09, 0xa3, 0xa5, 0x8b, 0xd1, 0x5f, 0x55, 0x4c, 0xf2, 0x0d, 0x0f, - 0x36, 0x38, 0xe3, 0x4e, 0xc2, 0x60, 0x9a, 0xdd, 0x4a, 0xb2, 0xc1, 0x25, 0x71, 0xdc, 0xea, 0x3d, 0x73, 0x23, 0xd5, - 0xa0, 0xd7, 0xb0, 0xb8, 0xcf, 0xda, 0xf6, 0xb3, 0xd6, 0xe1, 0xb3, 0x23, 0x1b, 0xfe, 0x77, 0x58, 0x3b, 0x35, 0x78, - 0xc5, 0x65, 0x18, 0x40, 0x9e, 0x41, 0x51, 0xb3, 0xa9, 0xda, 0x2d, 0x63, 0x1f, 0xf2, 0x5a, 0xc7, 0xf5, 0x95, 0xa6, - 0xee, 0x7d, 0x5e, 0xa7, 0xb6, 0xc6, 0x22, 0x5c, 0x4b, 0xc3, 0xaa, 0x19, 0x8d, 0x17, 0xac, 0x41, 0xcf, 0x2e, 0xd5, - 0x90, 0x5f, 0xf3, 0xe9, 0xe6, 0xf3, 0x62, 0xed, 0xf4, 0x2a, 0x4f, 0x66, 0x2a, 0x92, 0x2a, 0xee, 0x84, 0x20, 0xbf, - 0xa2, 0xb4, 0x31, 0xde, 0x37, 0x66, 0x9c, 0x80, 0x68, 0xdf, 0x59, 0x0a, 0x4a, 0x97, 0x16, 0x28, 0x89, 0xd6, 0xc1, - 0x44, 0xc3, 0x9f, 0xee, 0x38, 0xd6, 0xbc, 0x83, 0xc8, 0xe2, 0x1f, 0xd6, 0x71, 0xd5, 0xdc, 0xa1, 0x9d, 0x67, 0x7e, - 0x8b, 0xc5, 0xaa, 0xb8, 0xcf, 0x12, 0x23, 0xc2, 0x7b, 0x6c, 0x5a, 0x5a, 0x73, 0xe0, 0x3e, 0xcb, 0x1a, 0x3e, 0x4b, - 0x8c, 0xe0, 0x39, 0xdc, 0x7d, 0x0e, 0xec, 0xa7, 0x4f, 0xa9, 0x16, 0x64, 0x51, 0xa7, 0x69, 0x9d, 0x4e, 0xf2, 0xa0, - 0xa1, 0x8a, 0x3b, 0x0f, 0x29, 0x6e, 0x68, 0x6f, 0x62, 0x84, 0xcf, 0x9f, 0x0f, 0x07, 0x8e, 0x8e, 0x59, 0x45, 0x45, - 0x76, 0x70, 0x9e, 0xb0, 0xf6, 0x7c, 0x3f, 0x43, 0x23, 0xbd, 0xd6, 0x95, 0x76, 0x05, 0x32, 0x93, 0x2d, 0xdc, 0x11, - 0x38, 0xf6, 0x82, 0x04, 0x72, 0x64, 0x50, 0xe0, 0x0a, 0x83, 0x1f, 0x51, 0x27, 0x93, 0xba, 0xda, 0x96, 0x6d, 0xd9, - 0x6a, 0xd6, 0x70, 0xe6, 0xcd, 0x07, 0x9b, 0x30, 0x71, 0x21, 0x15, 0xa7, 0x1f, 0xce, 0xc1, 0x8f, 0x2e, 0xf1, 0x12, - 0x1f, 0xf2, 0x3a, 0x82, 0x43, 0xdd, 0x92, 0xe4, 0xf2, 0x94, 0x7b, 0x37, 0xb8, 0xd1, 0x07, 0xcc, 0xed, 0x2d, 0x5c, - 0x71, 0x31, 0x8e, 0xdd, 0xf7, 0x40, 0x0c, 0x35, 0x55, 0x03, 0xdd, 0x00, 0x8b, 0x62, 0x53, 0xf6, 0x16, 0xea, 0x29, - 0xd0, 0x46, 0x57, 0xf9, 0x24, 0x66, 0x91, 0xbb, 0x84, 0x1c, 0x48, 0x9b, 0xd4, 0xe0, 0x98, 0x56, 0xe5, 0xa8, 0x56, - 0x71, 0x5e, 0x1c, 0x19, 0x4a, 0xcb, 0x31, 0x14, 0x1b, 0xd0, 0xad, 0x9a, 0x1a, 0x9b, 0xf4, 0xaa, 0xbf, 0xcb, 0xe0, - 0x81, 0xf0, 0xcb, 0x63, 0x9a, 0x07, 0x99, 0x3a, 0xf0, 0xab, 0xa4, 0x84, 0xe2, 0x17, 0x6b, 0x52, 0x66, 0x13, 0x8f, - 0x2e, 0x3d, 0x2f, 0xd8, 0x5d, 0xa2, 0x63, 0xde, 0x43, 0x5e, 0xc5, 0xd3, 0x37, 0xe8, 0x30, 0xec, 0x05, 0x8a, 0xf7, - 0xf1, 0xa3, 0xe6, 0x81, 0x33, 0xd3, 0x40, 0x82, 0x0f, 0x3c, 0xeb, 0x05, 0x80, 0x79, 0xf9, 0x35, 0x3d, 0x02, 0x0b, - 0x3c, 0x0d, 0xe1, 0xdf, 0xbc, 0x58, 0xfc, 0xe0, 0x66, 0x12, 0x96, 0xef, 0x06, 0x73, 0x40, 0x69, 0x6e, 0x30, 0xaf, - 0x98, 0x63, 0x91, 0xcf, 0x73, 0xa9, 0x34, 0xef, 0x2a, 0x37, 0x95, 0x8a, 0x5f, 0xde, 0x5f, 0x50, 0x5e, 0x57, 0x4d, - 0x05, 0x2a, 0x87, 0x2e, 0xba, 0xf9, 0x4d, 0xee, 0xf3, 0xc1, 0x97, 0x27, 0x4b, 0x96, 0xb8, 0x74, 0x0d, 0x04, 0xc2, - 0x2f, 0xb0, 0x03, 0x0a, 0x27, 0x34, 0x3c, 0x36, 0xd4, 0x60, 0xca, 0x6e, 0xbc, 0x09, 0x97, 0x4b, 0x0d, 0x85, 0xd3, - 0x29, 0x13, 0x2d, 0x3e, 0x07, 0x8e, 0x41, 0x0e, 0x07, 0x13, 0x17, 0x43, 0x1d, 0x0f, 0x82, 0x50, 0x1d, 0x7e, 0x99, - 0xf9, 0x66, 0x36, 0x2d, 0x02, 0x24, 0x57, 0xbf, 0x8c, 0x98, 0xff, 0xef, 0xc1, 0x97, 0x40, 0xb8, 0xbf, 0xbc, 0x52, - 0xf5, 0x7e, 0x62, 0x2d, 0x22, 0x36, 0x1b, 0x7c, 0x59, 0x93, 0x64, 0x1c, 0xc5, 0x7b, 0x1a, 0x8b, 0xda, 0x6e, 0xe5, - 0x21, 0xe7, 0xda, 0x7b, 0x09, 0xf5, 0x43, 0x2e, 0xad, 0x83, 0x04, 0xb8, 0x29, 0xc8, 0xd8, 0x4e, 0x1f, 0xe5, 0xe7, - 0xb1, 0xef, 0x4e, 0x3e, 0xf4, 0xe9, 0x4d, 0xe1, 0xc1, 0x04, 0x6a, 0x3d, 0x71, 0x57, 0x3d, 0x24, 0xaf, 0x72, 0x21, - 0x78, 0x4f, 0x53, 0x69, 0xc6, 0xd9, 0xd5, 0xee, 0x65, 0xdc, 0xca, 0x1b, 0xfc, 0x32, 0x7e, 0xea, 0x76, 0xe1, 0x25, - 0x4c, 0x7c, 0x0a, 0x1f, 0xd2, 0x54, 0x08, 0xea, 0x24, 0xa2, 0xa2, 0x60, 0x6d, 0xb5, 0x15, 0xa7, 0xfb, 0x6d, 0xe7, - 0xc6, 0xb1, 0x17, 0x2d, 0xc7, 0xea, 0xfe, 0xec, 0x74, 0x17, 0x6d, 0xeb, 0xd8, 0x37, 0xdb, 0xd6, 0x31, 0xfc, 0xf9, - 0xf9, 0xd8, 0xea, 0x2e, 0xcc, 0x96, 0x75, 0xf8, 0xb3, 0xd3, 0xf2, 0xcd, 0xae, 0x75, 0x0c, 0x7f, 0xce, 0xa9, 0x15, - 0x08, 0x40, 0x24, 0xef, 0x7c, 0x59, 0xc0, 0x02, 0xd2, 0xef, 0xec, 0x4e, 0xd6, 0x28, 0x90, 0xb7, 0x9a, 0x7b, 0x5d, - 0x40, 0x19, 0x94, 0xfa, 0x07, 0x4d, 0x11, 0xfa, 0x5a, 0x30, 0x60, 0x94, 0xec, 0x47, 0x98, 0xb7, 0x09, 0x3f, 0x74, - 0x91, 0x5f, 0xa5, 0xf6, 0x18, 0xf1, 0x36, 0xf5, 0x39, 0x45, 0x44, 0x22, 0x58, 0xba, 0x08, 0xfe, 0x69, 0x85, 0xa1, - 0xf1, 0x44, 0xfa, 0x2c, 0x09, 0x2b, 0xe5, 0xc9, 0xc8, 0xd3, 0xdd, 0x03, 0x47, 0x6f, 0x7e, 0x96, 0x25, 0xc3, 0xfc, - 0xac, 0x7d, 0x4b, 0x59, 0xca, 0x3e, 0xa9, 0x1f, 0xcc, 0xce, 0x94, 0x27, 0x56, 0x82, 0x88, 0xe2, 0x53, 0x2f, 0xca, - 0x86, 0x27, 0xa1, 0x68, 0xa7, 0x3e, 0x19, 0x8b, 0x0e, 0x59, 0x03, 0xcf, 0x80, 0x4b, 0xbe, 0x71, 0x7d, 0xc9, 0x90, - 0x4d, 0x6a, 0xf9, 0x28, 0xc3, 0xfc, 0x4f, 0x9f, 0xe6, 0x83, 0x33, 0x4b, 0xe3, 0x3e, 0x71, 0x3a, 0x40, 0x76, 0x3b, - 0xac, 0xbd, 0xd5, 0xa6, 0x72, 0x77, 0x2c, 0xfa, 0x3c, 0x08, 0xb5, 0xb0, 0x9b, 0x12, 0x16, 0x1b, 0x8d, 0x86, 0x9d, - 0x15, 0x7b, 0x0d, 0x88, 0xe2, 0x5f, 0x12, 0x75, 0x54, 0xbd, 0x1f, 0x08, 0xf3, 0x83, 0x60, 0x4b, 0xfc, 0x7d, 0x2e, - 0x8b, 0xa9, 0x00, 0x9a, 0x2d, 0xf3, 0xd8, 0xe1, 0x20, 0xfe, 0x67, 0x4f, 0x02, 0x9d, 0x35, 0xc1, 0x5e, 0xa2, 0x74, - 0x5a, 0x0b, 0xce, 0x7b, 0x19, 0x5d, 0x25, 0x82, 0xca, 0xe2, 0x53, 0x15, 0x8a, 0x20, 0x9d, 0x2c, 0x66, 0x90, 0xce, - 0x8c, 0x45, 0x33, 0x6a, 0x91, 0x17, 0x18, 0x1e, 0x26, 0x33, 0x11, 0x8e, 0xa3, 0xfa, 0xd3, 0xa7, 0x8d, 0x44, 0x88, - 0x8c, 0x73, 0x62, 0x96, 0x64, 0x39, 0x2e, 0x55, 0x19, 0xbf, 0xa9, 0x32, 0x8a, 0xc9, 0xfa, 0x45, 0xac, 0x21, 0x6c, - 0x5c, 0x69, 0xef, 0xe1, 0xcf, 0x31, 0x73, 0x13, 0x8b, 0x5f, 0x96, 0x6a, 0x12, 0x71, 0x37, 0x1c, 0xd6, 0x06, 0xeb, - 0x56, 0x1e, 0x41, 0x93, 0x47, 0xa8, 0x7d, 0xb2, 0x79, 0xb9, 0xe6, 0x51, 0x1d, 0xa0, 0x8f, 0x8f, 0x76, 0x1e, 0x80, - 0xec, 0x6d, 0xe2, 0x52, 0x60, 0x18, 0x99, 0xe4, 0x86, 0x89, 0x2b, 0xd2, 0x36, 0x02, 0x5f, 0xde, 0xaf, 0x35, 0xbf, - 0x90, 0x22, 0x3f, 0x0c, 0xdf, 0x5e, 0x7c, 0xad, 0xf0, 0xfd, 0x4f, 0xd6, 0x02, 0x28, 0xc8, 0x50, 0x6a, 0x9f, 0x01, - 0xa5, 0xf6, 0x51, 0x78, 0x6e, 0x29, 0xc8, 0x77, 0x93, 0x1e, 0x10, 0x04, 0x51, 0x01, 0x4d, 0x36, 0x14, 0xcb, 0xb5, - 0x9f, 0x78, 0x2b, 0x37, 0x4a, 0x0e, 0x30, 0xaf, 0x0f, 0x20, 0x39, 0xb5, 0x29, 0x1e, 0x04, 0x99, 0x61, 0x88, 0xc0, - 0xad, 0x49, 0x20, 0xec, 0x30, 0x66, 0x9e, 0x9f, 0x99, 0x61, 0x88, 0x0f, 0xb8, 0x93, 0x09, 0x5b, 0x25, 0x83, 0x42, - 0xfe, 0xa0, 0x70, 0x92, 0xb0, 0xc4, 0x8c, 0x93, 0x88, 0xb9, 0x4b, 0x35, 0x0b, 0x10, 0x5e, 0xed, 0x2f, 0x5e, 0x8f, - 0x97, 0x5e, 0x92, 0x45, 0xd8, 0xa5, 0x09, 0x82, 0x41, 0x04, 0x0c, 0x71, 0x38, 0x4a, 0x39, 0x08, 0xcf, 0xc3, 0x79, - 0x69, 0x47, 0xe5, 0x9c, 0xcb, 0x29, 0xc6, 0x6f, 0x27, 0x49, 0x06, 0xb4, 0xc5, 0x93, 0xd0, 0xbf, 0xe6, 0x31, 0x2c, - 0xb2, 0x40, 0xc0, 0xea, 0xf0, 0x84, 0x8b, 0xb7, 0x0a, 0x86, 0x6f, 0x51, 0x3b, 0x36, 0x44, 0xa8, 0x6f, 0x8a, 0x6e, - 0x71, 0xc0, 0x2b, 0x03, 0x69, 0xa2, 0x9e, 0x31, 0xc9, 0x08, 0x8d, 0xe5, 0x02, 0x18, 0xa1, 0x82, 0xc1, 0xcc, 0xc2, - 0x19, 0x66, 0xee, 0x94, 0x38, 0x2a, 0xe4, 0x95, 0x3e, 0x7e, 0x7c, 0x35, 0xfa, 0xed, 0x3f, 0x90, 0x09, 0x65, 0xe1, - 0x88, 0x98, 0x12, 0x97, 0x72, 0x2d, 0xce, 0x7d, 0x1a, 0x23, 0x34, 0x96, 0x62, 0x53, 0x11, 0xa2, 0x47, 0x6c, 0xad, - 0x74, 0x74, 0x25, 0x42, 0x34, 0x42, 0x92, 0x24, 0x5d, 0x44, 0xbe, 0x18, 0xc1, 0xf2, 0x8e, 0x44, 0x4c, 0x14, 0xe5, - 0x97, 0xbb, 0x97, 0xc7, 0x4a, 0x1e, 0xc3, 0xa8, 0xce, 0xa2, 0x87, 0xf6, 0xd0, 0xf0, 0xc4, 0x55, 0x90, 0x69, 0x41, - 0xf6, 0x23, 0xee, 0x1d, 0xc0, 0x34, 0x17, 0xe1, 0x92, 0x59, 0x5e, 0x78, 0x70, 0xcb, 0xc6, 0xa6, 0xbb, 0xf2, 0xc8, - 0x2e, 0x07, 0xf5, 0x6e, 0x0a, 0x71, 0x7e, 0x99, 0xb9, 0x0b, 0xf1, 0xd7, 0x69, 0x0e, 0xca, 0xb0, 0x18, 0x93, 0xb3, - 0xd3, 0xca, 0xef, 0x01, 0x21, 0x7e, 0x81, 0x04, 0xc7, 0x70, 0x78, 0x72, 0xe0, 0x0e, 0x8b, 0x41, 0x81, 0x2d, 0x91, - 0xbd, 0xa6, 0x48, 0x04, 0x4e, 0x29, 0xb6, 0xaf, 0x08, 0xe3, 0x9b, 0x3f, 0x98, 0xe1, 0x6c, 0x26, 0x07, 0xf2, 0xb5, - 0x8a, 0xc3, 0xcb, 0x80, 0x96, 0x6f, 0xe9, 0x70, 0x45, 0x5f, 0xaa, 0x7e, 0x22, 0xfb, 0xa9, 0xf6, 0x30, 0x82, 0x37, - 0xcc, 0x19, 0x8e, 0x7b, 0x25, 0x20, 0x70, 0x06, 0xb1, 0xc7, 0x54, 0x89, 0xe3, 0x91, 0x72, 0xfa, 0x89, 0x06, 0xce, - 0xe5, 0xd1, 0x60, 0x40, 0x68, 0xae, 0x8c, 0xed, 0x00, 0x88, 0x35, 0x99, 0x7c, 0x60, 0xb2, 0x09, 0x34, 0x34, 0xc9, - 0x5d, 0x16, 0x1b, 0x95, 0xa7, 0x53, 0x1d, 0xe3, 0x81, 0x2b, 0xb6, 0x5f, 0x61, 0x83, 0xc2, 0xc6, 0xe3, 0xeb, 0x0e, - 0xf8, 0x5d, 0xf4, 0x53, 0x42, 0xf3, 0xca, 0x57, 0x84, 0xd1, 0x4d, 0xdf, 0xbd, 0x0f, 0x25, 0x33, 0x26, 0x1e, 0xd1, - 0xe4, 0x1c, 0x4b, 0x2f, 0x84, 0x27, 0x71, 0xe5, 0xa0, 0x65, 0x09, 0x51, 0xaa, 0x87, 0x4d, 0x4e, 0x62, 0xb2, 0xeb, - 0xac, 0xc9, 0x75, 0x8b, 0x93, 0x41, 0xe4, 0x99, 0xe6, 0xe7, 0xb0, 0xf0, 0x12, 0xd1, 0x42, 0x7a, 0x72, 0x00, 0xf3, - 0x83, 0x28, 0x2c, 0x05, 0xc6, 0xc9, 0xd3, 0x21, 0xd4, 0x8b, 0x1b, 0x93, 0x29, 0xd6, 0xd9, 0x54, 0xf0, 0x7c, 0x28, - 0x58, 0x4a, 0xd9, 0xfd, 0xa4, 0x2a, 0x55, 0x5e, 0xc6, 0xae, 0x67, 0x02, 0x77, 0x67, 0x0f, 0xfa, 0x61, 0x8d, 0xa9, - 0x83, 0xd2, 0x7e, 0xc2, 0x44, 0x90, 0x83, 0xf3, 0xa4, 0x21, 0x0e, 0x42, 0x53, 0x15, 0xe2, 0x67, 0xb7, 0x54, 0xc8, - 0xf7, 0xf1, 0xb6, 0x5a, 0x39, 0xe7, 0x94, 0x55, 0x5b, 0xb9, 0x9a, 0xfa, 0x18, 0x77, 0x7c, 0xa5, 0x36, 0x96, 0x42, - 0xbd, 0xf3, 0x64, 0x00, 0x55, 0x85, 0x2e, 0xde, 0x5d, 0xad, 0xa8, 0xb2, 0xde, 0x3f, 0x39, 0x20, 0xb1, 0x74, 0x48, - 0x3b, 0x6c, 0x78, 0x02, 0xa6, 0xdc, 0xb4, 0xe8, 0xee, 0x6a, 0xc5, 0x97, 0x94, 0x7e, 0xd1, 0x9b, 0x83, 0x45, 0xb2, - 0xf4, 0x87, 0xff, 0x07, 0x52, 0xaf, 0x09, 0x6c, 0x30, 0x6a, 0x03, 0x00}; + 0x5e, 0x6e, 0x0b, 0x04, 0x92, 0x99, 0x22, 0xb2, 0xd9, 0xee, 0xa9, 0x2b, 0x90, 0xcb, 0x9a, 0x4d, 0xa4, 0x5d, 0xf0, + 0x72, 0xcc, 0x41, 0x26, 0x53, 0xd6, 0x93, 0xf6, 0xc0, 0x16, 0x04, 0xc9, 0x4d, 0x1a, 0x91, 0x6d, 0x7f, 0x29, 0x3e, + 0x89, 0x01, 0x8d, 0x90, 0x15, 0xf8, 0x42, 0x61, 0x91, 0x03, 0xd7, 0x59, 0xf8, 0x8e, 0xbf, 0xd1, 0x52, 0x31, 0x50, + 0xc3, 0x21, 0x2e, 0xcc, 0xf3, 0x18, 0xe5, 0x7c, 0xa8, 0x0a, 0x9e, 0x5b, 0x0a, 0x44, 0x14, 0xbe, 0x5e, 0x1f, 0xc2, + 0x6b, 0x46, 0xae, 0x4d, 0x70, 0xbd, 0x75, 0x3f, 0xab, 0x97, 0x4b, 0x60, 0x1c, 0x8c, 0xb4, 0xcc, 0x45, 0xa1, 0x93, + 0x37, 0xd9, 0xa5, 0xe8, 0x35, 0x1a, 0xcc, 0x04, 0x9a, 0x22, 0x10, 0x55, 0x0e, 0xfc, 0x22, 0xe1, 0x8f, 0x8d, 0x1d, + 0xa5, 0x98, 0x8d, 0xc0, 0x07, 0xa1, 0xc1, 0x6b, 0x09, 0xeb, 0xb5, 0xb2, 0x11, 0x5e, 0x4c, 0x8e, 0x8d, 0xf5, 0x52, + 0xf6, 0x53, 0x86, 0x92, 0xad, 0xcc, 0x38, 0xb8, 0xdb, 0xea, 0x6f, 0xab, 0xfd, 0x7c, 0xc0, 0xed, 0x35, 0x1e, 0x37, + 0x71, 0x13, 0x0c, 0xa0, 0x56, 0x5b, 0x1b, 0xdc, 0xda, 0xf9, 0xc7, 0xd6, 0x28, 0x99, 0x6d, 0x43, 0x50, 0x94, 0x71, + 0x02, 0xec, 0xcd, 0xad, 0x8f, 0x9b, 0xa8, 0xcc, 0x9c, 0x14, 0xd2, 0x03, 0x90, 0xa3, 0x87, 0x04, 0x3a, 0xb7, 0x3f, + 0x2b, 0xba, 0x50, 0xc9, 0xc4, 0xe5, 0x18, 0xff, 0x11, 0xdc, 0xe6, 0x0d, 0xa2, 0x4f, 0x9f, 0xcc, 0x26, 0xff, 0xf4, + 0x29, 0xc2, 0xa1, 0x71, 0x7d, 0x14, 0xf0, 0x82, 0xd1, 0xb0, 0x0c, 0xad, 0x65, 0x36, 0x7e, 0xb3, 0x5d, 0x35, 0xf6, + 0x81, 0x56, 0x78, 0x07, 0xcb, 0x63, 0x1a, 0xdf, 0x71, 0x46, 0x1d, 0x70, 0x80, 0x37, 0x1b, 0xf0, 0x61, 0xef, 0x4d, + 0xac, 0xd0, 0xd1, 0xd1, 0x9b, 0x58, 0xa2, 0xfe, 0x35, 0x33, 0x77, 0x6e, 0xe0, 0x8d, 0x3e, 0xe0, 0x66, 0xf8, 0x32, + 0x40, 0x80, 0x6b, 0xb6, 0x2d, 0xd9, 0xbc, 0x35, 0xb1, 0x3f, 0x52, 0x88, 0x2d, 0x0e, 0x11, 0x8e, 0x1d, 0x48, 0xa0, + 0xd7, 0x37, 0x21, 0xb4, 0x7b, 0x8c, 0x30, 0x60, 0xe1, 0x4b, 0x5f, 0x41, 0x96, 0xcc, 0x59, 0x31, 0x65, 0xc5, 0x7a, + 0xfd, 0x81, 0x5a, 0xff, 0xbf, 0xad, 0x50, 0x95, 0xaa, 0xd7, 0x68, 0x50, 0x33, 0x7e, 0x10, 0x1f, 0xe8, 0x10, 0x1f, + 0xbe, 0x89, 0x0b, 0x84, 0xc0, 0xc2, 0x88, 0x8b, 0xa5, 0xf7, 0x75, 0xcb, 0x6a, 0xeb, 0x52, 0xa0, 0xb2, 0x91, 0x9c, + 0xb4, 0xf0, 0x8c, 0x64, 0xe5, 0x1a, 0x5d, 0xce, 0x7a, 0x8d, 0x46, 0x8e, 0x64, 0x9c, 0x0d, 0xf2, 0x21, 0xe6, 0xb8, + 0x80, 0xcb, 0xd4, 0xdd, 0x75, 0x58, 0xb0, 0x1a, 0xe5, 0x72, 0xf3, 0x5d, 0xd9, 0xb1, 0xa6, 0xcf, 0xe9, 0x26, 0xdc, + 0xdd, 0x34, 0x20, 0x12, 0xfb, 0x80, 0x2c, 0x2c, 0x90, 0x95, 0x07, 0xb2, 0x30, 0x40, 0x56, 0xa8, 0xbf, 0x80, 0xa0, + 0x4d, 0x0a, 0xa5, 0x3b, 0x14, 0xbd, 0x1e, 0x5e, 0xd4, 0xb9, 0xae, 0x60, 0x6e, 0x22, 0x5c, 0xb8, 0xe5, 0x00, 0x37, + 0x16, 0x37, 0x77, 0x45, 0x56, 0x51, 0x64, 0x22, 0xed, 0xe2, 0x5b, 0xf3, 0x27, 0xb9, 0xc5, 0x77, 0xf6, 0xc7, 0x5d, + 0xa0, 0x4c, 0x7a, 0x5f, 0xd3, 0x36, 0x70, 0x17, 0x97, 0x2e, 0x4a, 0x22, 0x40, 0x6b, 0x17, 0x64, 0x51, 0xd4, 0xdf, + 0x9d, 0x53, 0x36, 0x1c, 0x86, 0x68, 0x10, 0x85, 0x45, 0x40, 0x3a, 0xff, 0xf8, 0x23, 0x42, 0x7d, 0x01, 0xd1, 0x8c, + 0xdc, 0xc9, 0xd6, 0x6c, 0xa3, 0x46, 0x94, 0x44, 0x69, 0xec, 0x83, 0x65, 0xc0, 0xce, 0x88, 0xa2, 0xe0, 0xcd, 0x99, + 0x2a, 0xca, 0x5a, 0x6d, 0x18, 0x66, 0x50, 0x55, 0xf8, 0x8f, 0xab, 0xd5, 0x76, 0xb0, 0x25, 0x03, 0x55, 0x61, 0x22, + 0xdd, 0x20, 0xfb, 0x10, 0x1b, 0x23, 0xec, 0xe8, 0x88, 0x0d, 0xc4, 0x30, 0x78, 0x59, 0xad, 0xb2, 0x20, 0xd1, 0xe1, + 0xc2, 0xc5, 0x19, 0x44, 0xbb, 0x5f, 0xaf, 0xed, 0x5f, 0xf2, 0x9b, 0x91, 0x66, 0xe0, 0x89, 0xbc, 0xe0, 0x8c, 0x15, + 0xfb, 0x65, 0xb1, 0x44, 0xcb, 0xf7, 0x60, 0xd9, 0xe7, 0x62, 0x17, 0x72, 0x37, 0xd5, 0x76, 0xe9, 0x82, 0x63, 0x34, + 0x0a, 0x41, 0xe4, 0xe0, 0xea, 0x48, 0xc3, 0x0b, 0x1d, 0xe6, 0xd5, 0x22, 0x00, 0xe7, 0xaa, 0x0c, 0xe4, 0x0a, 0x47, + 0x4a, 0x02, 0x96, 0xde, 0x86, 0x4e, 0xc2, 0x8f, 0x3a, 0x95, 0x74, 0x2c, 0x24, 0x40, 0x81, 0x23, 0x73, 0x39, 0x6f, + 0x02, 0xf5, 0x33, 0xb4, 0x87, 0xc8, 0x05, 0x26, 0x34, 0x75, 0xd9, 0xd2, 0x45, 0xd4, 0x8a, 0xe6, 0x72, 0xa9, 0xd8, + 0x72, 0x01, 0xe7, 0x7b, 0x99, 0x96, 0xe5, 0x3c, 0xfb, 0x52, 0x4f, 0x01, 0x83, 0xc8, 0x5b, 0x3d, 0x67, 0x62, 0x19, + 0xb9, 0x79, 0xbe, 0xb2, 0xe2, 0xfe, 0x9b, 0x17, 0xf8, 0x03, 0xe9, 0x1c, 0xbf, 0xc2, 0x7f, 0x51, 0xf2, 0xa1, 0xf1, + 0x0a, 0x4f, 0x39, 0xb1, 0xbc, 0x41, 0xf2, 0xe6, 0xf5, 0xf5, 0x8b, 0x77, 0x2f, 0x3e, 0x3c, 0xfd, 0xf4, 0xe2, 0xd5, + 0xb3, 0x17, 0xaf, 0x5e, 0xbc, 0xfb, 0x88, 0xff, 0x49, 0xc9, 0xab, 0x93, 0xf6, 0x45, 0x0b, 0xbf, 0x27, 0xaf, 0x4e, + 0x3a, 0xf8, 0x56, 0x93, 0x57, 0x27, 0x67, 0x78, 0xa6, 0xc8, 0xab, 0xe3, 0xce, 0xc9, 0x29, 0x5e, 0x6a, 0xdb, 0x64, + 0x2e, 0xa7, 0xed, 0x16, 0xfe, 0xcb, 0x7d, 0x81, 0x78, 0x1f, 0xb8, 0xe1, 0xb0, 0x2d, 0xe3, 0x07, 0x53, 0x86, 0x8e, + 0x94, 0x31, 0x44, 0xb9, 0x0c, 0xd0, 0x69, 0xac, 0x42, 0x74, 0xb2, 0xa1, 0xa4, 0xc1, 0x86, 0x11, 0xd0, 0x8a, 0x13, + 0xd7, 0x0e, 0x3f, 0x69, 0xb3, 0x53, 0xa0, 0x4f, 0xbc, 0x14, 0x8e, 0x4b, 0x15, 0x4e, 0xdb, 0x69, 0x31, 0x26, 0xb9, + 0x94, 0x45, 0xbc, 0x04, 0x46, 0xc0, 0x68, 0x2d, 0xf8, 0x49, 0x19, 0xb3, 0x4a, 0x5c, 0x92, 0x76, 0xbf, 0x9d, 0x8a, + 0x4b, 0xd2, 0xe9, 0x77, 0xe0, 0x4f, 0xb7, 0xdf, 0x4d, 0xdb, 0x2d, 0x74, 0x1c, 0x8c, 0xe3, 0xe7, 0x1a, 0x5a, 0x0f, + 0x86, 0xd8, 0x75, 0xa1, 0xfe, 0x2a, 0xb4, 0x57, 0xe9, 0x09, 0xa7, 0x8e, 0x6d, 0xf7, 0xc4, 0x25, 0x33, 0x7a, 0x58, + 0xfe, 0x03, 0xa0, 0xb6, 0x71, 0xab, 0x29, 0x37, 0x8e, 0xfb, 0xc5, 0x4f, 0x04, 0xaa, 0x05, 0xc6, 0x89, 0xd9, 0xba, + 0x85, 0x80, 0x69, 0x34, 0xd9, 0x60, 0x0e, 0x94, 0x28, 0x59, 0x68, 0x1f, 0xdc, 0x5f, 0x35, 0x25, 0x4a, 0x16, 0x72, + 0x11, 0xd7, 0x54, 0x0d, 0xbf, 0x04, 0x66, 0x8e, 0x87, 0x5c, 0xbd, 0xa2, 0xaf, 0xe2, 0x1a, 0xcf, 0x13, 0xb2, 0x76, + 0xe1, 0xb6, 0xf8, 0x87, 0xb3, 0xa2, 0xa8, 0x81, 0xab, 0x04, 0xac, 0x1f, 0x55, 0x53, 0x5f, 0xc2, 0x2b, 0x86, 0xac, + 0xa1, 0xaf, 0x48, 0x40, 0x3d, 0x7f, 0x2d, 0xcd, 0xb8, 0x4a, 0x65, 0xb4, 0x57, 0x44, 0x1b, 0xb3, 0x20, 0xaf, 0x88, + 0xbe, 0x54, 0x06, 0x08, 0x92, 0xf0, 0x81, 0x18, 0xc2, 0x81, 0x6f, 0x07, 0x28, 0x0d, 0x9d, 0x03, 0xb5, 0x52, 0x65, + 0x26, 0x64, 0x3e, 0x4d, 0x88, 0x06, 0xd0, 0x3c, 0x55, 0x2a, 0x28, 0xf3, 0x89, 0x25, 0x0a, 0x86, 0xfe, 0x7b, 0xb8, + 0x01, 0x8e, 0x63, 0x83, 0x8a, 0xa1, 0x5d, 0x8d, 0xa8, 0xe7, 0xb7, 0x2f, 0x5a, 0x27, 0xaf, 0x82, 0xfc, 0xa5, 0xf2, + 0xf6, 0x1e, 0x9f, 0x03, 0x4a, 0x6e, 0x83, 0x8a, 0xb5, 0xb1, 0x8f, 0x07, 0xd7, 0x0b, 0x01, 0x72, 0xac, 0xd1, 0x89, + 0x79, 0xd0, 0xb1, 0x87, 0xf4, 0x31, 0x69, 0xb7, 0x20, 0x88, 0xdb, 0x1e, 0xca, 0xf7, 0xeb, 0x16, 0x4c, 0x75, 0x72, + 0xdb, 0x04, 0x5a, 0x0d, 0x6f, 0x3c, 0xdd, 0x35, 0x79, 0x72, 0x87, 0x55, 0x80, 0x33, 0xec, 0x98, 0x35, 0xc4, 0xb1, + 0x40, 0x2e, 0xf8, 0xad, 0xdd, 0x00, 0x9a, 0x8a, 0x8e, 0x7d, 0x6b, 0xd0, 0x1b, 0x47, 0x5d, 0x36, 0x93, 0xee, 0xf1, + 0xab, 0xa3, 0xa3, 0x58, 0x36, 0xc8, 0x07, 0x84, 0x57, 0x14, 0x6c, 0xb6, 0xc1, 0xf7, 0x8e, 0x5b, 0x26, 0x3e, 0x55, + 0x01, 0x75, 0x9c, 0xa8, 0xda, 0xb1, 0x56, 0x75, 0x56, 0xee, 0x06, 0x3f, 0xa6, 0x0e, 0x6a, 0x04, 0x69, 0x76, 0x74, + 0x9d, 0x1a, 0x94, 0x6b, 0x8e, 0x72, 0xb0, 0x2d, 0x1b, 0x7f, 0x51, 0xf4, 0xc3, 0x87, 0xe6, 0xab, 0x60, 0xc2, 0x35, + 0xd3, 0xa4, 0x0f, 0x8d, 0x0f, 0xe8, 0x87, 0x0f, 0x81, 0xab, 0x23, 0xaf, 0xd8, 0x13, 0xcf, 0x8d, 0xfc, 0x6a, 0xb9, + 0xd2, 0x5f, 0x41, 0xb2, 0x2f, 0xc8, 0xaf, 0x80, 0xe5, 0x94, 0xfc, 0x1a, 0xcb, 0x26, 0x84, 0x80, 0x24, 0xbf, 0xc6, + 0x05, 0xfc, 0xc8, 0xc9, 0xaf, 0x31, 0x60, 0x3b, 0x9e, 0x99, 0x1f, 0x45, 0x09, 0x0c, 0x70, 0xaf, 0x93, 0xd6, 0xcb, + 0xae, 0x58, 0xaf, 0xc5, 0xd1, 0x91, 0xb4, 0xbf, 0xe8, 0x55, 0x76, 0x74, 0x94, 0x5f, 0xce, 0xaa, 0xbe, 0xb9, 0xde, + 0x47, 0x5f, 0x0c, 0x42, 0xe1, 0xc0, 0x34, 0x8d, 0x87, 0x33, 0xfe, 0xa9, 0x46, 0x59, 0xa1, 0x81, 0xe6, 0x69, 0xe7, + 0xfe, 0xf9, 0x05, 0x86, 0x7f, 0xef, 0x07, 0x05, 0x75, 0xe6, 0x27, 0x46, 0xda, 0xac, 0x79, 0x5e, 0xd5, 0xb9, 0x0a, + 0xf0, 0x19, 0x33, 0xd4, 0x14, 0x47, 0x47, 0xfc, 0x32, 0xc0, 0x65, 0xcc, 0x50, 0x23, 0xb0, 0xd8, 0x7b, 0x58, 0xda, + 0x93, 0x19, 0xae, 0x09, 0x1e, 0xf7, 0xe5, 0x83, 0x62, 0x78, 0xa9, 0x1d, 0x35, 0x09, 0x43, 0x80, 0x2b, 0xd2, 0x72, + 0x9b, 0xac, 0x27, 0x9a, 0xea, 0xaa, 0xdd, 0x43, 0x92, 0xa8, 0x86, 0xb8, 0xba, 0x6a, 0x63, 0x50, 0xc9, 0xf7, 0x15, + 0x91, 0xa9, 0x20, 0xde, 0x4d, 0x71, 0x95, 0xcb, 0x54, 0xe1, 0x19, 0x4f, 0x85, 0x97, 0xb3, 0x5f, 0x7b, 0xeb, 0x69, + 0xe3, 0x38, 0x6a, 0x7a, 0x66, 0x58, 0xf4, 0x55, 0xe9, 0xf0, 0x08, 0x9b, 0x54, 0x0d, 0xe1, 0xed, 0xc4, 0x12, 0xf3, + 0x98, 0xf5, 0xf2, 0x63, 0x10, 0x9b, 0x5a, 0x35, 0xda, 0x90, 0x09, 0x9f, 0x9b, 0x54, 0xc1, 0x40, 0x4d, 0xe1, 0x4b, + 0x08, 0x7b, 0x98, 0x55, 0x86, 0xd9, 0xbe, 0x61, 0x28, 0x20, 0xa0, 0xc0, 0x15, 0x61, 0x81, 0x04, 0xcf, 0xb3, 0x1a, + 0xe1, 0xa8, 0x93, 0x0b, 0x3b, 0xb9, 0x4b, 0x05, 0xdd, 0x89, 0xe1, 0xa5, 0xee, 0x21, 0xd1, 0x68, 0x38, 0x6e, 0xfb, + 0x4a, 0x98, 0x41, 0x34, 0xdb, 0xc3, 0x2b, 0xd6, 0x43, 0xaa, 0xd9, 0x2c, 0x0d, 0x20, 0xaf, 0x5a, 0xeb, 0xb5, 0xba, + 0xf4, 0x8d, 0xf4, 0xfd, 0x39, 0x6e, 0xf8, 0x2e, 0x2f, 0x78, 0xfe, 0x2e, 0xc9, 0x20, 0x02, 0xaa, 0x0a, 0x7c, 0xb6, + 0x5c, 0x44, 0x38, 0x32, 0xcf, 0xea, 0xc1, 0x5f, 0xf3, 0x1c, 0x5a, 0x84, 0x23, 0xf7, 0xd2, 0x5e, 0x34, 0xac, 0x06, + 0x2b, 0xb2, 0x32, 0x48, 0x3c, 0x4f, 0x3e, 0x01, 0xe3, 0xa0, 0x3f, 0x2b, 0xb4, 0xaa, 0x7e, 0x27, 0xb9, 0x0b, 0x97, + 0xa2, 0xfc, 0xe3, 0x6f, 0x6e, 0x54, 0x9b, 0xfd, 0x0e, 0xaa, 0x1c, 0x47, 0xbe, 0x2a, 0x3c, 0xa2, 0xf0, 0x9d, 0xd7, + 0x27, 0xdb, 0xee, 0xd1, 0xf3, 0x55, 0xd9, 0x03, 0x70, 0xde, 0x9b, 0x0d, 0xc2, 0xbf, 0xcb, 0xbd, 0x2f, 0x20, 0x47, + 0x9f, 0xa4, 0x78, 0x42, 0x35, 0x8d, 0x1a, 0x6f, 0x8c, 0xe1, 0x9b, 0x95, 0xb3, 0x7a, 0xdf, 0x1a, 0x07, 0xfb, 0xb7, + 0xba, 0x87, 0x00, 0x16, 0xb5, 0xc7, 0x9a, 0xac, 0xec, 0x6b, 0xc2, 0x96, 0xc8, 0xc0, 0xf4, 0x6d, 0x0f, 0x3c, 0xfc, + 0x18, 0x29, 0xb8, 0x55, 0x5b, 0x3e, 0x89, 0x42, 0x64, 0xd8, 0x9a, 0x33, 0x37, 0xa4, 0xd8, 0x3e, 0x8c, 0xe3, 0xef, + 0x06, 0x85, 0x5c, 0xf7, 0x42, 0xd5, 0x89, 0x69, 0xd5, 0x8d, 0x91, 0x3a, 0xd8, 0x36, 0x0b, 0xce, 0xaa, 0xde, 0x8d, + 0x84, 0x52, 0xbd, 0x6b, 0x67, 0xde, 0x26, 0x6d, 0xb6, 0xcd, 0x63, 0xcf, 0xf6, 0xf5, 0x3b, 0x05, 0x86, 0xbc, 0x87, + 0x65, 0xd0, 0xae, 0x2b, 0x38, 0x76, 0xe3, 0x00, 0xb2, 0x92, 0x5c, 0xad, 0xdc, 0xcb, 0x74, 0x7c, 0x20, 0x87, 0x9b, + 0xf2, 0x9d, 0xba, 0x00, 0x0f, 0xaa, 0x91, 0xaa, 0x2c, 0xe4, 0x0c, 0xfc, 0x23, 0x8f, 0x35, 0xfd, 0x10, 0xff, 0x1b, + 0x0e, 0xf8, 0x0a, 0x49, 0x53, 0xab, 0x7e, 0x82, 0xf7, 0xa3, 0x40, 0xe1, 0x6d, 0xeb, 0xfe, 0x24, 0x43, 0x47, 0xdd, + 0xba, 0x4e, 0xc5, 0xfa, 0xc2, 0xd6, 0x15, 0x2b, 0x65, 0xe1, 0x80, 0x6a, 0xc5, 0x68, 0x93, 0x3a, 0xbf, 0x59, 0xf7, + 0xe8, 0xd4, 0x43, 0x01, 0xbe, 0x31, 0x5c, 0x8a, 0x67, 0x05, 0x44, 0x11, 0x0b, 0xf5, 0x69, 0xba, 0x08, 0x5f, 0x55, + 0x1e, 0xc0, 0x3d, 0x61, 0xc9, 0x73, 0x96, 0x2f, 0x81, 0xc3, 0x02, 0x29, 0xa0, 0x50, 0x0a, 0x8b, 0xf5, 0x3a, 0x16, + 0x26, 0xb6, 0x84, 0x0b, 0x2d, 0xec, 0xde, 0x10, 0x31, 0xfa, 0x3b, 0xa8, 0x8b, 0xbd, 0x7a, 0xc4, 0x98, 0xb0, 0xa2, + 0xf0, 0xd2, 0x49, 0x66, 0x41, 0x5f, 0xfb, 0xfa, 0x10, 0xd5, 0x94, 0xfb, 0xb1, 0xd1, 0xf7, 0xbe, 0xe3, 0x73, 0x26, + 0x97, 0xf0, 0x78, 0x13, 0x66, 0x44, 0x31, 0xed, 0xbf, 0x81, 0x82, 0xc0, 0x0b, 0x40, 0x3c, 0xc4, 0x47, 0xe0, 0xab, + 0x3c, 0xad, 0x2b, 0x32, 0xff, 0x24, 0x48, 0x64, 0x42, 0x76, 0x46, 0xfd, 0x08, 0xbc, 0x88, 0x40, 0x84, 0x22, 0x24, + 0x62, 0x62, 0x1c, 0xf5, 0x23, 0xe3, 0x92, 0x15, 0x81, 0xd5, 0x18, 0x28, 0xb9, 0x23, 0x3c, 0x55, 0x15, 0x11, 0x0b, + 0x6b, 0xea, 0xa0, 0x12, 0x4b, 0x8d, 0x99, 0xf6, 0x49, 0xa7, 0x02, 0x21, 0xcd, 0xb6, 0x05, 0x65, 0xbd, 0xa5, 0x2e, + 0xc0, 0x92, 0x18, 0xd3, 0x5b, 0x9e, 0x7c, 0x02, 0x6e, 0x8e, 0x8d, 0x5d, 0xd1, 0x15, 0xbf, 0x06, 0xf5, 0x74, 0x5a, + 0xe0, 0x4f, 0x86, 0x61, 0x1b, 0xa7, 0x74, 0x43, 0x38, 0xce, 0x48, 0x91, 0xd0, 0x5b, 0x88, 0xad, 0x31, 0xe7, 0x22, + 0xcd, 0xf1, 0x9c, 0xde, 0xa6, 0x33, 0x3c, 0xe7, 0xe2, 0x89, 0x5d, 0xf6, 0x74, 0x0c, 0x49, 0xfe, 0x63, 0xb9, 0x21, + 0xe6, 0x69, 0xb0, 0xf7, 0x8a, 0x15, 0x8f, 0x80, 0x57, 0x51, 0x31, 0xea, 0x8d, 0x8d, 0x4d, 0x39, 0xd7, 0x95, 0xf1, + 0xfa, 0x6b, 0x1d, 0x53, 0x9c, 0xe1, 0x1c, 0x25, 0xb9, 0xc4, 0xac, 0x2f, 0xd2, 0xd7, 0x10, 0x57, 0x3b, 0xc3, 0xf6, + 0x59, 0x31, 0x7e, 0xcb, 0xf2, 0x67, 0xb2, 0xf8, 0x60, 0xb6, 0x7c, 0x8e, 0xa0, 0x10, 0xb8, 0xa8, 0x88, 0x26, 0xdc, + 0xee, 0x2d, 0xfb, 0xb2, 0x6a, 0x8a, 0xde, 0xda, 0xa6, 0xdc, 0x10, 0x67, 0x10, 0x90, 0x38, 0x99, 0xf1, 0x46, 0x1b, + 0xb3, 0x7e, 0xeb, 0x3b, 0x8d, 0xce, 0x50, 0x59, 0x12, 0x61, 0x58, 0xab, 0xa6, 0x4a, 0x25, 0x11, 0x4d, 0xe5, 0x24, + 0xbc, 0x95, 0x01, 0x76, 0xaa, 0x70, 0x26, 0x97, 0x42, 0xa7, 0x32, 0xc0, 0x9b, 0xac, 0xda, 0x5c, 0xab, 0x5b, 0x0b, + 0x31, 0x8d, 0xef, 0xec, 0x0f, 0x86, 0x3f, 0x19, 0x15, 0xff, 0x5b, 0x30, 0xec, 0x51, 0xa9, 0x00, 0xf8, 0x81, 0xe1, + 0x2c, 0x40, 0xce, 0xf2, 0x93, 0xb7, 0x00, 0x3e, 0xcb, 0x42, 0xde, 0x41, 0x2a, 0x33, 0xa9, 0x77, 0x90, 0xca, 0x20, + 0xd5, 0x78, 0xd4, 0x1f, 0x8a, 0x4a, 0x59, 0x14, 0x36, 0x48, 0x14, 0x2e, 0xd5, 0xc1, 0x92, 0x88, 0x04, 0xda, 0x35, + 0xa2, 0xdc, 0x9c, 0x0b, 0x08, 0xad, 0x08, 0x8d, 0xdb, 0x6f, 0x7a, 0x0b, 0xdf, 0x77, 0x36, 0x9f, 0xf9, 0xfc, 0x3b, + 0x9b, 0x6f, 0x3a, 0xf2, 0x18, 0x5f, 0xbf, 0xed, 0x34, 0x96, 0xf1, 0xd2, 0x61, 0xed, 0xfb, 0xf2, 0x21, 0x9b, 0x96, + 0x79, 0x30, 0x9c, 0xb4, 0xf1, 0x3c, 0x40, 0xca, 0x66, 0xc5, 0xc3, 0x75, 0x70, 0xbb, 0x75, 0x1c, 0xf3, 0x26, 0x69, + 0x23, 0x74, 0xec, 0x84, 0x2b, 0x11, 0x1b, 0xc9, 0xe9, 0xf8, 0xc3, 0x09, 0xdc, 0xbd, 0x8c, 0xd4, 0x96, 0xaf, 0x94, + 0xad, 0xd6, 0x6c, 0xb7, 0x8e, 0xf9, 0xde, 0x2a, 0x8d, 0x36, 0x9e, 0x33, 0xb2, 0x02, 0x0f, 0x34, 0x5a, 0x58, 0x55, + 0x03, 0xb8, 0xac, 0xbe, 0x10, 0xbf, 0x2e, 0xe9, 0xd8, 0x7c, 0x1f, 0xdb, 0x94, 0xd7, 0x4b, 0xed, 0x93, 0x9a, 0x1c, + 0x06, 0xd1, 0x41, 0xae, 0x64, 0x90, 0x13, 0xf3, 0x13, 0x92, 0x74, 0xd1, 0x65, 0xbb, 0x9f, 0x74, 0x8f, 0xf9, 0x31, + 0x4f, 0x81, 0x87, 0x8d, 0x9b, 0xbe, 0x42, 0xb3, 0xed, 0xeb, 0x3c, 0x5e, 0x8e, 0x78, 0xe6, 0x9a, 0xaf, 0x3a, 0x28, + 0x53, 0xed, 0x1c, 0x21, 0x0b, 0x50, 0xcc, 0xf7, 0x12, 0x64, 0xd7, 0xbb, 0x39, 0xe6, 0x29, 0xf4, 0x03, 0xb5, 0x3a, + 0xb6, 0x56, 0x39, 0xb8, 0x5f, 0x97, 0x80, 0x60, 0xbe, 0xa3, 0xda, 0x5c, 0x6c, 0x7a, 0x33, 0xae, 0x3a, 0x3b, 0xe6, + 0xd5, 0x08, 0xc3, 0x32, 0xbb, 0xfd, 0xf9, 0xa9, 0x55, 0x5d, 0x1e, 0x07, 0x10, 0xf9, 0x75, 0xc9, 0x45, 0xd8, 0x69, + 0xd8, 0xad, 0xcb, 0x09, 0x3b, 0xad, 0xcf, 0x32, 0x28, 0xb2, 0xdb, 0xeb, 0xce, 0x4c, 0xeb, 0xb3, 0xbd, 0x06, 0x47, + 0x42, 0x98, 0x94, 0x59, 0xe9, 0x4c, 0xaa, 0x98, 0x1f, 0xbf, 0x47, 0xae, 0xf5, 0xd7, 0x4b, 0xed, 0xf3, 0x4b, 0x44, + 0x80, 0xec, 0xaa, 0xeb, 0xb2, 0x3a, 0xf4, 0x51, 0x36, 0xf1, 0xea, 0x98, 0x07, 0x2b, 0xf7, 0xf4, 0x76, 0x21, 0x53, + 0x8f, 0xaf, 0xfd, 0x56, 0xba, 0x83, 0x9c, 0x40, 0x3c, 0x5c, 0x77, 0x61, 0x59, 0x90, 0xb3, 0x9b, 0x3b, 0x28, 0x19, + 0x4e, 0xdc, 0x97, 0x7e, 0xcf, 0xec, 0x75, 0x03, 0xbf, 0x4c, 0xba, 0x30, 0xf5, 0xed, 0x1e, 0x8e, 0x3b, 0xd0, 0x87, + 0x81, 0xc3, 0x76, 0x83, 0x3e, 0xb3, 0x82, 0xc8, 0x63, 0x5e, 0x58, 0x3c, 0xbb, 0x22, 0xed, 0x3e, 0x4f, 0xdd, 0x66, + 0x32, 0xa2, 0x51, 0xbb, 0xc9, 0x83, 0x99, 0x01, 0x7e, 0xb9, 0xb2, 0x61, 0x11, 0xbf, 0x4e, 0x01, 0x94, 0x7c, 0xb1, + 0x6a, 0x7d, 0x2a, 0x78, 0xd5, 0x1b, 0x4e, 0xb7, 0xd3, 0xfd, 0xba, 0xc1, 0xed, 0xae, 0x87, 0x27, 0x3c, 0x44, 0x63, + 0xd1, 0xda, 0x4f, 0x7c, 0x0e, 0x1c, 0x50, 0xd2, 0xba, 0xdf, 0x05, 0x17, 0xca, 0x12, 0x96, 0xbb, 0xe5, 0x46, 0x3b, + 0xe5, 0x2c, 0x1c, 0x6d, 0xc9, 0x80, 0x3b, 0xd8, 0x86, 0x28, 0x74, 0x70, 0xdc, 0xc1, 0x49, 0xbb, 0xdd, 0xe9, 0xe2, + 0xe4, 0xac, 0x0b, 0x03, 0x6d, 0x24, 0xdd, 0xe3, 0x91, 0xb2, 0x00, 0x0c, 0x72, 0x36, 0xae, 0xdd, 0x47, 0x10, 0xb4, + 0x2a, 0x14, 0xaf, 0xf9, 0x71, 0x1c, 0xb7, 0x93, 0xfb, 0xad, 0x76, 0xf7, 0xa2, 0x01, 0x00, 0x6a, 0xba, 0x0f, 0x57, + 0xe3, 0xf5, 0x52, 0xd7, 0xab, 0x94, 0x08, 0x5f, 0xaf, 0xd6, 0xf0, 0xd5, 0x1a, 0xed, 0x4d, 0x35, 0x05, 0x5f, 0xd5, + 0x09, 0xe7, 0xb6, 0x88, 0x57, 0xda, 0x84, 0xdb, 0x22, 0xb6, 0x03, 0x89, 0x41, 0x3a, 0x4f, 0xba, 0x9d, 0x2e, 0xb2, + 0x63, 0xd1, 0x0e, 0x3f, 0xca, 0x7d, 0xb2, 0x53, 0xa4, 0xa1, 0x01, 0x49, 0xca, 0xd9, 0xc9, 0x25, 0x48, 0xd4, 0x9c, + 0x5c, 0xb5, 0x9b, 0x73, 0x96, 0xf8, 0x09, 0x98, 0x54, 0x58, 0xce, 0x72, 0x15, 0x5c, 0x52, 0x00, 0x88, 0x4b, 0x30, + 0x2e, 0xba, 0xdf, 0xed, 0xdf, 0x4f, 0xba, 0xe7, 0x1d, 0x4b, 0xf4, 0xf8, 0x65, 0xa7, 0x96, 0x66, 0xa6, 0x9e, 0x74, + 0x4d, 0x1a, 0x74, 0x9d, 0xdc, 0xef, 0x42, 0x19, 0x97, 0x12, 0x96, 0x82, 0x60, 0x1b, 0x55, 0x31, 0x88, 0xb0, 0x91, + 0xd6, 0x72, 0xcf, 0x6b, 0xd9, 0x17, 0x67, 0xa7, 0xf7, 0xbb, 0x21, 0xd4, 0xca, 0x59, 0x98, 0x85, 0x76, 0x13, 0xf1, + 0xb3, 0x83, 0xa5, 0x45, 0xc7, 0x49, 0x37, 0xdd, 0x99, 0xa0, 0xdd, 0x34, 0xc7, 0x06, 0x07, 0x02, 0x85, 0xe3, 0x53, + 0xe1, 0xf4, 0x25, 0xc1, 0xfd, 0x58, 0x65, 0x68, 0x12, 0x2a, 0x9c, 0xfd, 0x3d, 0x65, 0xf0, 0x9e, 0x66, 0x78, 0x55, + 0xf9, 0x98, 0x8a, 0xaf, 0x54, 0xbd, 0xa1, 0x10, 0x41, 0x44, 0x0c, 0x23, 0x17, 0xdf, 0xbc, 0x9e, 0xfb, 0x0f, 0x70, + 0x11, 0x66, 0x02, 0x2e, 0x34, 0xbd, 0x12, 0xb4, 0xe2, 0x05, 0x3e, 0x85, 0x0e, 0xb5, 0x66, 0x58, 0x7d, 0x9e, 0x3a, + 0x93, 0x82, 0x50, 0xb7, 0xf5, 0x9c, 0x7f, 0xaf, 0x5c, 0x52, 0x5e, 0x65, 0x27, 0x5d, 0x94, 0xb8, 0xcb, 0xf2, 0xa4, + 0x8d, 0x92, 0xc0, 0x84, 0xc4, 0x1d, 0xc9, 0x79, 0x46, 0x06, 0xd1, 0x6d, 0x84, 0xa3, 0xbb, 0x08, 0x47, 0xd6, 0x87, + 0xf9, 0x37, 0xf0, 0xe3, 0x8e, 0x70, 0x64, 0x5d, 0x99, 0x23, 0x1c, 0x69, 0x26, 0x20, 0xb0, 0x58, 0x34, 0xc4, 0x33, + 0x28, 0x6d, 0x3c, 0xab, 0xcb, 0xd2, 0x8f, 0xfd, 0x57, 0xe9, 0x7a, 0x6d, 0x53, 0x02, 0x29, 0x73, 0x6c, 0x76, 0xa8, + 0x7d, 0x18, 0x3b, 0xa2, 0x9e, 0x59, 0x8f, 0x30, 0x08, 0x20, 0xf4, 0xce, 0x3f, 0xac, 0x57, 0xc5, 0x24, 0x61, 0xa7, + 0xb0, 0xd2, 0xe0, 0x8a, 0x1e, 0x85, 0x67, 0x58, 0x84, 0x27, 0xc2, 0x17, 0x06, 0xb1, 0xc2, 0xff, 0xce, 0xa5, 0x5c, + 0xf8, 0xdf, 0x5a, 0x96, 0xbf, 0xe0, 0x39, 0x16, 0x67, 0xd1, 0x02, 0x96, 0x5b, 0x36, 0x04, 0xd2, 0x88, 0xd5, 0x47, + 0xf0, 0x69, 0xe2, 0xc2, 0xd4, 0x81, 0x44, 0xf8, 0xc9, 0x08, 0x54, 0x5e, 0x3e, 0xfc, 0x64, 0x43, 0x26, 0x99, 0x4f, + 0x88, 0x99, 0x06, 0x61, 0x91, 0x25, 0x5c, 0x68, 0x4c, 0x0b, 0xa6, 0x54, 0x64, 0x63, 0x09, 0x46, 0x52, 0xf8, 0xc7, + 0x21, 0x7d, 0xca, 0x44, 0x44, 0xa6, 0xc3, 0xfa, 0x6c, 0xad, 0x38, 0x9c, 0xcb, 0x42, 0xa5, 0xf6, 0xa5, 0x18, 0x0f, + 0xc6, 0x45, 0xf9, 0x0c, 0x63, 0x3a, 0xcb, 0x36, 0xd8, 0xde, 0x61, 0x97, 0x85, 0xdc, 0x95, 0x76, 0x58, 0x2a, 0xcf, + 0x36, 0xdf, 0x9a, 0x90, 0xaa, 0xcd, 0x28, 0x98, 0x68, 0x35, 0xa0, 0x2a, 0x70, 0x07, 0x14, 0xb6, 0x41, 0x69, 0xd2, + 0x55, 0x59, 0x32, 0x5d, 0x95, 0xcb, 0x70, 0xd6, 0x6a, 0x6d, 0x36, 0xb8, 0x60, 0x26, 0x90, 0xcb, 0xde, 0x12, 0x90, + 0xaf, 0x66, 0xf2, 0x26, 0xc8, 0x55, 0x69, 0x39, 0x4b, 0xb3, 0x44, 0x51, 0x60, 0x04, 0x1b, 0x6d, 0xf0, 0x57, 0xae, + 0x38, 0xc0, 0xd3, 0xcd, 0x6e, 0x24, 0x65, 0xce, 0x28, 0xc4, 0x50, 0x0b, 0x9a, 0xdc, 0xe0, 0x19, 0x1f, 0xb3, 0xfd, + 0x6d, 0x82, 0x19, 0xf3, 0xbf, 0xd7, 0xa2, 0x47, 0x20, 0xcb, 0xee, 0x19, 0xd4, 0x81, 0x45, 0x5c, 0x43, 0x07, 0xa1, + 0x0c, 0xbe, 0x0c, 0x71, 0x33, 0xa7, 0x77, 0x72, 0xa9, 0x01, 0x2e, 0x4b, 0x2d, 0xdf, 0xb8, 0x70, 0x08, 0x87, 0x2d, + 0xec, 0x23, 0x23, 0xac, 0x20, 0x64, 0x40, 0x0b, 0xdb, 0x88, 0x18, 0x2d, 0xec, 0x02, 0x15, 0xb4, 0xb0, 0x09, 0x4f, + 0xd1, 0xda, 0x94, 0xb1, 0xcd, 0xee, 0xca, 0x27, 0x35, 0xab, 0x4d, 0x30, 0x71, 0xd2, 0xa1, 0x26, 0x3a, 0xb8, 0x3d, + 0x64, 0x84, 0x37, 0x7e, 0xba, 0x7e, 0xfd, 0xca, 0x45, 0xae, 0xe6, 0x13, 0x70, 0xd9, 0x74, 0xaa, 0xb1, 0x3b, 0x1b, + 0x62, 0xbe, 0x52, 0x94, 0x5a, 0xe1, 0xd4, 0x04, 0xfb, 0x14, 0x3a, 0x4f, 0xec, 0xe5, 0xc5, 0x33, 0x59, 0xcc, 0xa9, + 0xbd, 0x31, 0xc2, 0x77, 0xca, 0x3d, 0x3e, 0x6f, 0xde, 0xb7, 0xa9, 0x26, 0xf9, 0x6e, 0xfb, 0x2a, 0x62, 0x92, 0x19, + 0xf9, 0x15, 0xb4, 0x01, 0xa6, 0xb2, 0x1f, 0x38, 0x2b, 0x88, 0x8b, 0xff, 0x1f, 0x90, 0x97, 0xb7, 0x96, 0xba, 0x44, + 0x51, 0x83, 0x1b, 0xfc, 0x64, 0x45, 0xa5, 0xe3, 0xe2, 0xe6, 0xfd, 0x48, 0xd2, 0x72, 0xe2, 0x45, 0xd4, 0x8a, 0xea, + 0x6f, 0xef, 0x1a, 0x55, 0x82, 0x8f, 0x1d, 0x9b, 0xe4, 0x12, 0x44, 0x8f, 0xf2, 0x99, 0x3f, 0x0e, 0xa2, 0x89, 0xbf, + 0x7b, 0xbe, 0x6a, 0x7b, 0x3a, 0x9b, 0x57, 0xea, 0xc4, 0xf2, 0xca, 0x04, 0x3c, 0x1c, 0xed, 0x43, 0x3a, 0x08, 0x07, + 0x89, 0xac, 0xd4, 0x1e, 0xfa, 0x5c, 0xd4, 0x8b, 0xf3, 0xcb, 0x36, 0x6b, 0x9e, 0xad, 0xd7, 0xf9, 0x55, 0x9b, 0xb5, + 0xbb, 0xf6, 0xd9, 0xbd, 0x48, 0x65, 0x40, 0x73, 0xf9, 0x84, 0x67, 0x11, 0x68, 0x67, 0x17, 0x99, 0x09, 0xa7, 0xe0, + 0xa5, 0x69, 0xb2, 0xd4, 0x55, 0x5f, 0x12, 0x8c, 0x4b, 0x89, 0xd5, 0xe3, 0x17, 0xa8, 0xdf, 0x4e, 0x77, 0x5d, 0xa5, + 0x9b, 0xed, 0xe3, 0xe0, 0xc2, 0xa5, 0x40, 0xb8, 0x03, 0x21, 0x0f, 0x40, 0xbf, 0xbb, 0x12, 0x60, 0x1a, 0x04, 0xa8, + 0xac, 0x40, 0xa4, 0xe5, 0xf3, 0xe5, 0xfc, 0x59, 0x41, 0xcd, 0x32, 0x3c, 0xe1, 0x53, 0xae, 0x55, 0x4a, 0x41, 0xba, + 0xdd, 0x97, 0xbe, 0xd9, 0x2f, 0x41, 0x65, 0xb5, 0xf8, 0xbb, 0x89, 0xe6, 0xd9, 0x17, 0xe5, 0x16, 0x0e, 0x61, 0xb3, + 0xb2, 0x02, 0x67, 0x68, 0x83, 0x73, 0x39, 0xa5, 0x05, 0xd7, 0xb3, 0xf9, 0xbf, 0xb5, 0x3a, 0x6c, 0xa0, 0x87, 0xe6, + 0xc2, 0x0a, 0x40, 0x42, 0xc5, 0x78, 0xbd, 0xe6, 0x27, 0xdf, 0xbf, 0x4f, 0xf2, 0x3e, 0xe1, 0x6d, 0xdc, 0xc1, 0xa7, + 0xb8, 0x8b, 0xdb, 0x2d, 0xdc, 0xee, 0xc2, 0xd5, 0x7d, 0x96, 0x2f, 0xc7, 0x4c, 0xc5, 0xf0, 0xfe, 0x9a, 0xbe, 0x4a, + 0x2e, 0x8e, 0xab, 0x57, 0x07, 0x8a, 0xc4, 0xa1, 0x4b, 0x10, 0xfc, 0xde, 0x45, 0x0d, 0x8c, 0xa2, 0x30, 0x64, 0xdd, + 0x22, 0x54, 0x9d, 0x94, 0xfa, 0x85, 0xab, 0xd3, 0x3e, 0xd8, 0x73, 0xdb, 0x95, 0x6d, 0x82, 0xd9, 0xb7, 0xfd, 0x99, + 0x56, 0x3f, 0x9b, 0xba, 0x44, 0x0c, 0x0f, 0xbd, 0x0a, 0x3d, 0xd0, 0x15, 0x69, 0x1f, 0x1d, 0x81, 0xd5, 0x51, 0x30, + 0x1b, 0x6e, 0xa3, 0x1f, 0xf0, 0x66, 0x2d, 0x0d, 0x82, 0x15, 0x80, 0x71, 0xe7, 0x1b, 0x4e, 0x56, 0x16, 0xb6, 0x1a, + 0xa8, 0x30, 0x2b, 0xc2, 0xb8, 0x7a, 0x21, 0xa9, 0x30, 0x42, 0x34, 0x1c, 0x61, 0x2e, 0x18, 0xca, 0x61, 0x0b, 0xcb, + 0xc9, 0x44, 0x31, 0x0d, 0x47, 0x47, 0xc1, 0xbe, 0xb2, 0x42, 0x99, 0x53, 0x64, 0xc4, 0xa6, 0x5c, 0x3c, 0xd4, 0xbf, + 0xb3, 0x42, 0x9a, 0x4f, 0xa3, 0xc1, 0x48, 0x23, 0xb3, 0x8a, 0x11, 0xce, 0x72, 0xbe, 0x80, 0xaa, 0xd3, 0x02, 0x9c, + 0x7e, 0xe0, 0x2f, 0x1f, 0xa7, 0x61, 0x9b, 0x40, 0xbe, 0x7e, 0xb3, 0x31, 0x5d, 0xf0, 0xb8, 0xa0, 0x37, 0xaf, 0xc5, + 0x63, 0xd8, 0x51, 0x0f, 0x0b, 0x46, 0x21, 0x1b, 0x92, 0xde, 0x41, 0x53, 0xf0, 0x01, 0x6d, 0xbe, 0x34, 0x80, 0x4b, + 0x2f, 0xcc, 0x87, 0xad, 0xe8, 0x63, 0x37, 0x26, 0x65, 0x5b, 0x26, 0xd3, 0x9c, 0xd2, 0x55, 0xa6, 0x8d, 0x42, 0x55, + 0x4e, 0x61, 0x83, 0x5d, 0xd4, 0x93, 0x70, 0x30, 0x63, 0xaa, 0x66, 0xe9, 0x60, 0x68, 0xfe, 0xbe, 0xb6, 0x25, 0x5b, + 0xd8, 0x45, 0x9c, 0xd9, 0x60, 0xf3, 0x70, 0x6a, 0x50, 0xbe, 0x8d, 0xe1, 0x1e, 0x16, 0x5e, 0xef, 0xac, 0x91, 0xcf, + 0x33, 0x4f, 0x36, 0xcf, 0x36, 0x1b, 0x33, 0x10, 0x95, 0x82, 0x1e, 0xe8, 0xad, 0xdf, 0x36, 0x2d, 0xd8, 0x1e, 0xe5, + 0x57, 0xb7, 0x85, 0xe7, 0x1c, 0x1e, 0x23, 0xf5, 0xed, 0x5d, 0xeb, 0x42, 0x7e, 0x71, 0x20, 0x69, 0x05, 0x29, 0x76, + 0x3a, 0x41, 0x67, 0xa7, 0x38, 0x18, 0x39, 0xd0, 0xf3, 0xeb, 0x2f, 0x16, 0xd6, 0xfe, 0xf7, 0x9b, 0xb2, 0xa0, 0x89, + 0xa7, 0x53, 0x4e, 0x28, 0xf3, 0xe7, 0xe7, 0x1b, 0x9e, 0x54, 0xa8, 0xe0, 0x5e, 0xf1, 0x82, 0x3d, 0x6d, 0x03, 0x7d, + 0xce, 0xe9, 0x67, 0xfb, 0xc3, 0xc6, 0xf0, 0x29, 0xb5, 0x6c, 0x59, 0x21, 0x95, 0x7a, 0x68, 0xd3, 0xec, 0xd1, 0x03, + 0x47, 0xe4, 0x4b, 0xe8, 0x02, 0x78, 0xfd, 0x71, 0x21, 0x17, 0x06, 0x11, 0xdc, 0x6f, 0x37, 0x6e, 0xe3, 0x2b, 0x00, + 0xde, 0x0e, 0x07, 0xd5, 0x3f, 0x2d, 0x60, 0x7f, 0xa3, 0xb2, 0xa4, 0x1f, 0x6f, 0xc7, 0x1e, 0xff, 0x85, 0x84, 0xa8, + 0xf1, 0x16, 0x0f, 0x13, 0x87, 0x4e, 0x25, 0x6b, 0x56, 0xfe, 0xdc, 0x29, 0x09, 0x18, 0x56, 0x2f, 0x18, 0xb2, 0x71, + 0x3b, 0xc5, 0x6d, 0xe6, 0x7f, 0x50, 0xc1, 0x60, 0xc1, 0xb7, 0x46, 0x52, 0xb1, 0x2c, 0x7e, 0xfb, 0xd4, 0xf9, 0xaf, + 0x3a, 0xc7, 0x75, 0xa8, 0x6b, 0x2f, 0x85, 0x8e, 0x4c, 0x94, 0xe6, 0x08, 0x1d, 0x1d, 0x6d, 0x65, 0xd0, 0x09, 0x00, + 0x1e, 0x39, 0xf6, 0xcb, 0x2f, 0x9f, 0x67, 0xc7, 0x8c, 0xe6, 0xb1, 0x88, 0x42, 0xe6, 0xce, 0x73, 0x73, 0x76, 0x22, + 0x4f, 0xa8, 0x9a, 0xf9, 0xc2, 0x00, 0xc7, 0x47, 0x3b, 0xa9, 0x80, 0xef, 0xd1, 0x66, 0xcf, 0x04, 0xb6, 0xf8, 0x2d, + 0x3b, 0xa9, 0x7d, 0x05, 0xfd, 0x02, 0xad, 0xf6, 0x31, 0x95, 0x5b, 0x0b, 0x1c, 0x6d, 0x4f, 0x64, 0xef, 0xd0, 0xb7, + 0xea, 0x94, 0xac, 0xc7, 0x8b, 0xfd, 0x46, 0x5f, 0x52, 0xec, 0x4b, 0xae, 0x68, 0xdb, 0x88, 0x55, 0xaf, 0x05, 0xeb, + 0xca, 0xd4, 0xa9, 0xba, 0xe6, 0xad, 0x2c, 0x6d, 0x4a, 0xbb, 0x24, 0x7b, 0xb7, 0xc5, 0xc2, 0xab, 0xf0, 0x46, 0xa3, + 0xbc, 0x08, 0x05, 0x7b, 0x2c, 0x31, 0xec, 0x71, 0x02, 0xd7, 0x0b, 0xeb, 0x75, 0x0c, 0x7f, 0xf6, 0x8d, 0x61, 0x9f, + 0xe9, 0xd2, 0x7b, 0xbe, 0xc5, 0xaf, 0x04, 0x01, 0x8b, 0x9d, 0x1d, 0x24, 0x58, 0x77, 0xb9, 0x41, 0xc3, 0x71, 0xe2, + 0xbf, 0xe0, 0xb9, 0x6c, 0xed, 0x5d, 0x0e, 0xe6, 0xd9, 0x37, 0x9e, 0xd8, 0x2b, 0x59, 0xcb, 0x5a, 0xb4, 0xfb, 0x2d, + 0x09, 0x86, 0xd8, 0x4d, 0xe9, 0x1c, 0xb7, 0x92, 0x36, 0x8a, 0x5c, 0xb1, 0x0a, 0xfd, 0xbf, 0x55, 0x24, 0xb3, 0x99, + 0xff, 0x75, 0x7e, 0x7e, 0xee, 0x52, 0x9c, 0xcd, 0x9f, 0x32, 0x1e, 0x70, 0x26, 0x81, 0x7d, 0xe5, 0x19, 0x33, 0x3a, + 0xe4, 0xb7, 0x30, 0x14, 0x22, 0xc8, 0x95, 0x70, 0xec, 0x12, 0xbc, 0xf6, 0x08, 0x94, 0x07, 0xd8, 0xbf, 0x27, 0x5b, + 0xe5, 0xfc, 0x73, 0x51, 0x3e, 0x9c, 0x72, 0xd9, 0x20, 0xfb, 0x6a, 0x3e, 0x07, 0xd6, 0x4c, 0x06, 0x5e, 0x48, 0x88, + 0xb0, 0xfd, 0x6d, 0x58, 0x5a, 0x67, 0x29, 0x83, 0x23, 0x2d, 0x97, 0xd9, 0xcc, 0x6a, 0xfe, 0xdd, 0x87, 0x29, 0xeb, + 0x9e, 0x1a, 0x82, 0xc8, 0x5d, 0x64, 0xe5, 0xa2, 0x82, 0x46, 0x3f, 0x96, 0x01, 0x40, 0x0f, 0x5e, 0xb1, 0x25, 0xfb, + 0x11, 0x1f, 0x54, 0x29, 0xf0, 0xf1, 0xb0, 0xe0, 0x34, 0xff, 0x11, 0x1f, 0x54, 0x81, 0x40, 0xc1, 0x15, 0xd2, 0xc4, + 0xd2, 0xc4, 0xe6, 0x59, 0xed, 0x34, 0x12, 0x40, 0x41, 0xf3, 0xc8, 0x1c, 0x64, 0xcf, 0x5d, 0x8c, 0xc6, 0xa4, 0x83, + 0x5d, 0x70, 0x30, 0x1b, 0x11, 0xd6, 0x06, 0x52, 0x87, 0xb8, 0x75, 0xe5, 0x6c, 0xcc, 0xd7, 0xa3, 0xad, 0x05, 0x31, + 0xca, 0x64, 0x72, 0xf5, 0x9c, 0xc7, 0x3b, 0x8b, 0x85, 0xc2, 0x6a, 0xc1, 0x02, 0xd5, 0xaa, 0x54, 0xe9, 0x61, 0xf1, + 0xdd, 0x82, 0x59, 0x50, 0xc4, 0x6c, 0xbd, 0x87, 0xb7, 0x5c, 0x11, 0x90, 0x92, 0x5d, 0x12, 0xbc, 0x8c, 0x6e, 0x30, + 0x95, 0xac, 0xe6, 0x72, 0xcc, 0x2c, 0xa1, 0x67, 0x4a, 0x47, 0xd8, 0xe4, 0x29, 0x88, 0x24, 0x76, 0xd8, 0xc2, 0x8e, + 0x35, 0x7a, 0x21, 0xbc, 0x90, 0x02, 0xe7, 0xaa, 0x69, 0x62, 0x4e, 0xb9, 0x89, 0x2e, 0xf6, 0x50, 0x2d, 0x58, 0xa6, + 0x2d, 0x02, 0x1c, 0x3a, 0x34, 0x94, 0xe2, 0xb9, 0x01, 0x85, 0x79, 0xd2, 0xdb, 0xa5, 0x3c, 0x86, 0xc5, 0x0b, 0x52, + 0x80, 0xa8, 0x71, 0x31, 0x2d, 0xeb, 0x2c, 0xf2, 0xe5, 0x94, 0x8b, 0x0a, 0x19, 0x0a, 0xa6, 0x16, 0x52, 0xc0, 0x8b, + 0x1a, 0x65, 0x11, 0x43, 0x87, 0x6a, 0xf8, 0x6e, 0x49, 0x58, 0x59, 0xc7, 0x1c, 0x53, 0x5c, 0x54, 0x35, 0x80, 0xb9, + 0x78, 0x68, 0x04, 0x44, 0x1f, 0x5e, 0xf6, 0xb5, 0x78, 0x27, 0x17, 0x55, 0xbe, 0xa7, 0x71, 0x3e, 0x70, 0xbd, 0xb3, + 0x1b, 0x46, 0x1b, 0xf3, 0xe8, 0x55, 0xb0, 0x7d, 0xdf, 0xf3, 0xea, 0x21, 0xb8, 0x8d, 0x79, 0x36, 0xab, 0xcc, 0x1a, + 0xb1, 0xf2, 0x8d, 0x88, 0xaa, 0xbd, 0x7a, 0x55, 0x29, 0x6c, 0x45, 0x80, 0x4a, 0xc1, 0xc7, 0x3b, 0xf9, 0x2f, 0xb4, + 0xcd, 0xb7, 0xe7, 0x50, 0x19, 0x1e, 0xc8, 0x93, 0xa1, 0xaa, 0x07, 0x5c, 0x94, 0x1f, 0x02, 0x58, 0xfc, 0xc8, 0xc4, + 0x0f, 0xde, 0x77, 0x81, 0xcc, 0x99, 0x8a, 0x25, 0x5e, 0x0d, 0xe8, 0x30, 0xb5, 0xf2, 0x50, 0x2a, 0xc1, 0xb6, 0xe7, + 0xa6, 0xe0, 0xda, 0x07, 0x2a, 0xc6, 0x03, 0x36, 0x4c, 0x57, 0xf5, 0x60, 0xc6, 0x36, 0x9c, 0xb2, 0x37, 0xe7, 0x34, + 0xd1, 0x7f, 0xe9, 0x10, 0xe7, 0x04, 0x6c, 0x8f, 0x3d, 0x7b, 0xfa, 0x26, 0xce, 0x50, 0xbf, 0xce, 0xe1, 0xaf, 0x36, + 0x38, 0xc7, 0x19, 0x4a, 0x1f, 0xc6, 0x70, 0x81, 0xb5, 0xc1, 0x00, 0xbe, 0xcc, 0x92, 0x2a, 0xf0, 0x48, 0xcd, 0x8c, + 0xc4, 0xea, 0x2e, 0x02, 0xd1, 0x4a, 0x87, 0xb7, 0xe3, 0xcc, 0x87, 0x03, 0x37, 0xdc, 0xeb, 0x33, 0x23, 0x1c, 0xce, + 0xb3, 0xb8, 0x76, 0xce, 0x70, 0x72, 0x75, 0xc8, 0x6b, 0x27, 0x26, 0x58, 0x7b, 0x87, 0xa7, 0x0a, 0xe8, 0xd1, 0xe0, + 0x54, 0xb1, 0x34, 0x04, 0x62, 0x26, 0x80, 0x37, 0x73, 0x78, 0xb4, 0x05, 0x38, 0x1f, 0x6d, 0x70, 0xf0, 0x95, 0xd6, + 0xba, 0xda, 0x56, 0xa2, 0x6c, 0x36, 0x78, 0x30, 0xce, 0xf0, 0x32, 0xc3, 0xd3, 0x6c, 0x18, 0x1e, 0x37, 0x59, 0x68, + 0xd2, 0xb5, 0x5e, 0x3f, 0x75, 0x66, 0x84, 0xc8, 0xfe, 0xb4, 0xf4, 0x07, 0xf5, 0x01, 0xe1, 0x53, 0xc8, 0x02, 0x5a, + 0xd2, 0x77, 0x7f, 0x1b, 0xf6, 0xb5, 0x70, 0xd4, 0x88, 0x79, 0x62, 0xc9, 0x48, 0xdf, 0xff, 0x28, 0xb3, 0x6c, 0x6b, + 0x8d, 0x68, 0x71, 0x7b, 0x10, 0x35, 0x7c, 0x7b, 0xd5, 0xf9, 0x32, 0x2a, 0xcd, 0x76, 0x00, 0x51, 0xac, 0x71, 0x92, + 0x0e, 0xd6, 0x48, 0xae, 0xd7, 0xb1, 0x4d, 0x21, 0x3c, 0x99, 0x33, 0xaa, 0x96, 0x85, 0x79, 0x40, 0x2f, 0x56, 0x28, + 0x31, 0xfc, 0x2e, 0x76, 0x36, 0xa2, 0xf0, 0x5e, 0x9d, 0x04, 0xc3, 0x8d, 0x58, 0x10, 0x59, 0x13, 0xb9, 0x3f, 0x65, + 0x95, 0x65, 0x90, 0x20, 0xc2, 0x88, 0xfc, 0xf6, 0xba, 0x54, 0xd8, 0x27, 0xfa, 0xec, 0x1f, 0xe3, 0x0b, 0x08, 0x37, + 0x6f, 0x53, 0x5a, 0x8c, 0xe8, 0x14, 0xd8, 0x58, 0x88, 0x43, 0xb8, 0x93, 0xb0, 0x5e, 0x0f, 0x86, 0x3d, 0x61, 0xc8, + 0xb3, 0x7b, 0x40, 0xb0, 0x6c, 0x68, 0x7f, 0x03, 0x70, 0xd5, 0x6d, 0xa9, 0xb9, 0x36, 0xba, 0x1f, 0x6a, 0xde, 0x38, + 0xe3, 0x2e, 0xc9, 0x3d, 0x53, 0x52, 0xbd, 0x44, 0x5e, 0xb3, 0x00, 0x37, 0xa1, 0xab, 0xf0, 0x18, 0x2f, 0xad, 0x0d, + 0xa7, 0x79, 0xd0, 0x8a, 0x9a, 0x77, 0xac, 0xe0, 0xf9, 0x6c, 0xc2, 0x06, 0xd9, 0x10, 0x8f, 0x7d, 0xb8, 0xf3, 0xc3, + 0xb7, 0xf1, 0x18, 0xa1, 0x82, 0x18, 0x98, 0x5a, 0x97, 0xed, 0x71, 0x65, 0xb7, 0x6f, 0x32, 0x0d, 0xc3, 0x60, 0x8c, + 0x98, 0xc7, 0xa1, 0x11, 0x73, 0xde, 0x68, 0xa0, 0x25, 0x19, 0x83, 0x11, 0xf3, 0x32, 0x68, 0x6d, 0x69, 0x1f, 0x3b, + 0x0d, 0xda, 0x5b, 0x22, 0xd4, 0xe3, 0x40, 0xd3, 0x34, 0x3c, 0x6b, 0x52, 0x3d, 0x2b, 0xef, 0x1f, 0xd9, 0x3a, 0xe9, + 0x80, 0x22, 0x61, 0x72, 0xe5, 0x27, 0x61, 0x5d, 0xc3, 0xed, 0xb8, 0x27, 0x66, 0xdc, 0xce, 0xb6, 0x41, 0x0d, 0xe4, + 0x20, 0x1b, 0x0e, 0x7b, 0xd2, 0x5b, 0x49, 0xb4, 0xf0, 0xa4, 0x7a, 0x08, 0xa5, 0x5a, 0xbc, 0xaf, 0x7a, 0xfb, 0xca, + 0x9b, 0xfb, 0xf7, 0x55, 0xb7, 0xcf, 0x63, 0xe0, 0x80, 0x0e, 0xe1, 0x7e, 0xa8, 0x8a, 0x0f, 0x76, 0xd2, 0x81, 0x28, + 0x68, 0x69, 0xab, 0x26, 0x90, 0x5a, 0x33, 0xbb, 0x58, 0x37, 0x15, 0x3a, 0x16, 0x10, 0x86, 0x4c, 0x55, 0xdd, 0xdd, + 0xaa, 0x40, 0x35, 0xc4, 0xe1, 0xd4, 0x7f, 0x6c, 0x8d, 0x58, 0xe3, 0xa8, 0x33, 0x8e, 0x8c, 0x91, 0xa4, 0x5d, 0x3e, + 0x78, 0xfb, 0x08, 0xac, 0x04, 0x7c, 0x0c, 0x6a, 0x93, 0x64, 0x0c, 0x09, 0xde, 0xb2, 0x4c, 0x1b, 0x3e, 0x84, 0x3b, + 0x04, 0xe5, 0x89, 0x0d, 0x4a, 0xeb, 0x2a, 0x59, 0xc8, 0x55, 0x5d, 0xde, 0x05, 0xe8, 0x79, 0x5b, 0xfe, 0xc6, 0x86, + 0x23, 0x0b, 0x06, 0x96, 0xed, 0xec, 0x13, 0xf0, 0xc8, 0xc7, 0x15, 0x82, 0xf8, 0xa5, 0xd0, 0x89, 0x89, 0xd7, 0x7d, + 0x0d, 0x1b, 0x14, 0x2f, 0xc0, 0x41, 0xd0, 0x49, 0x70, 0x18, 0xbc, 0xcb, 0xac, 0x26, 0xd9, 0xe0, 0xd6, 0x9c, 0xc4, + 0x8b, 0xf5, 0xba, 0x85, 0x8e, 0xff, 0x69, 0x9e, 0xa4, 0x9e, 0x94, 0x0a, 0xf7, 0x49, 0xa5, 0x70, 0x07, 0x4b, 0x40, + 0x32, 0x09, 0x74, 0xed, 0x58, 0x86, 0x6a, 0x74, 0x88, 0x96, 0xfe, 0x02, 0x62, 0x67, 0xbb, 0x63, 0x09, 0xf4, 0xec, + 0x3b, 0x05, 0xac, 0xae, 0xbd, 0x2c, 0x81, 0x8c, 0xe0, 0xee, 0x37, 0x81, 0x51, 0x21, 0x1a, 0x9f, 0x3f, 0xf3, 0xaa, + 0x05, 0x4f, 0x9c, 0x3f, 0xd7, 0xdc, 0xb0, 0xee, 0x05, 0xbd, 0x31, 0xcd, 0xc7, 0x13, 0xdc, 0x9c, 0x58, 0x70, 0x9e, + 0x74, 0xe0, 0xa7, 0x85, 0xe8, 0x49, 0x07, 0xbb, 0x54, 0x3c, 0x29, 0x81, 0x1c, 0xa2, 0xa7, 0x33, 0x90, 0x02, 0x56, + 0x3a, 0xb6, 0x5a, 0xa4, 0x29, 0x5a, 0xaf, 0xa7, 0x97, 0xa4, 0x85, 0xd0, 0x4a, 0xdd, 0x70, 0x9d, 0xcd, 0xc0, 0x47, + 0x1a, 0x14, 0x03, 0x6f, 0xa8, 0x9e, 0xc5, 0x08, 0x4f, 0xd0, 0x6a, 0xcc, 0x26, 0x74, 0x99, 0xeb, 0x54, 0xf5, 0x79, + 0x62, 0x03, 0xf7, 0x32, 0x1b, 0x09, 0xee, 0xa4, 0x83, 0xa7, 0x86, 0xbf, 0xfc, 0x60, 0xcc, 0x41, 0x8a, 0xcc, 0x24, + 0x4f, 0x4d, 0x02, 0xe6, 0x49, 0x96, 0x4b, 0xc5, 0x6c, 0x33, 0x3d, 0x6b, 0x5b, 0x0e, 0x21, 0xc9, 0x23, 0x5d, 0x70, + 0x63, 0x45, 0x19, 0xa5, 0x33, 0xa2, 0xfa, 0xea, 0xa4, 0x93, 0x4e, 0x31, 0x4f, 0x80, 0xd3, 0x7b, 0x27, 0x63, 0xd6, + 0x28, 0x6f, 0x45, 0xe7, 0xe8, 0x78, 0x86, 0x45, 0x75, 0x89, 0x3a, 0x47, 0xc7, 0x53, 0x84, 0xe7, 0x0d, 0x32, 0x53, + 0xe0, 0x31, 0xcc, 0xc5, 0xff, 0x91, 0xf2, 0xdf, 0x1c, 0x36, 0x84, 0x98, 0x7e, 0x0b, 0x3b, 0x85, 0x8d, 0xa3, 0x34, + 0x27, 0xe0, 0xb5, 0xd8, 0x3e, 0xc7, 0x19, 0x99, 0x36, 0x73, 0x1f, 0x70, 0xcf, 0xb4, 0xd2, 0xb8, 0xd5, 0xe8, 0x38, + 0xc3, 0xe3, 0xed, 0xa4, 0xd8, 0xcc, 0xb5, 0x99, 0xa7, 0x19, 0x9c, 0xef, 0xd5, 0x28, 0x5c, 0xf9, 0xe5, 0x76, 0x52, + 0x58, 0xde, 0x01, 0xb7, 0x39, 0xc6, 0xa2, 0x49, 0x71, 0x8e, 0xe7, 0xcd, 0x57, 0x78, 0xde, 0x7c, 0x5f, 0x66, 0x34, + 0x96, 0x58, 0x40, 0xf0, 0x3e, 0x48, 0xc4, 0xf3, 0x2a, 0x79, 0x8c, 0x45, 0xc3, 0x94, 0xc7, 0xf3, 0x46, 0x55, 0xba, + 0xb9, 0xc4, 0xa2, 0x61, 0x4a, 0x37, 0xde, 0xe3, 0x79, 0xe3, 0xd5, 0xbf, 0x98, 0x74, 0x94, 0x02, 0xba, 0x2c, 0xd0, + 0x2a, 0xb3, 0x43, 0xbc, 0xfe, 0xf5, 0xed, 0xbb, 0xf6, 0xa7, 0xce, 0xf1, 0x14, 0xfb, 0xf5, 0xcb, 0x0c, 0x8e, 0x65, + 0x3a, 0x66, 0x4d, 0x80, 0x68, 0x86, 0x3b, 0xc7, 0x33, 0xdc, 0x39, 0xce, 0x5c, 0x53, 0x9b, 0x79, 0x83, 0xdc, 0xea, + 0x10, 0x8a, 0x3a, 0x4a, 0x43, 0xf8, 0xf8, 0xc9, 0xa6, 0x53, 0x54, 0x03, 0x25, 0x3a, 0x9e, 0xd6, 0x40, 0x05, 0xdf, + 0xcb, 0xda, 0x77, 0x55, 0xaf, 0xc2, 0x20, 0x0b, 0x25, 0x14, 0xae, 0xb9, 0x01, 0x4f, 0x2d, 0xc5, 0x40, 0x26, 0x4c, + 0xb1, 0x40, 0xf9, 0x0e, 0x28, 0x8c, 0xf2, 0xc4, 0x0c, 0x3d, 0x98, 0x8e, 0x49, 0xfc, 0xff, 0x79, 0x32, 0xe5, 0xd0, + 0xcb, 0x2d, 0xb3, 0x33, 0x3d, 0x37, 0x99, 0x70, 0xf8, 0xc0, 0x63, 0xfd, 0x5f, 0x3b, 0x50, 0x6c, 0x40, 0x8a, 0xff, + 0x2f, 0x1d, 0x5d, 0x08, 0x46, 0xc8, 0x8a, 0xd2, 0xc2, 0x21, 0xfe, 0xf7, 0x87, 0x15, 0x74, 0x5f, 0xec, 0x74, 0x5f, + 0x98, 0xee, 0xc3, 0xa6, 0x8d, 0x2a, 0x27, 0xad, 0x2a, 0x59, 0xf2, 0x5f, 0xa7, 0x5b, 0x3b, 0xa0, 0x11, 0x35, 0x7a, + 0x36, 0x0d, 0x1b, 0x3c, 0x6c, 0xa7, 0x7b, 0x90, 0x79, 0xc3, 0xed, 0x0b, 0xa9, 0x70, 0xf8, 0x06, 0x77, 0xaa, 0x57, + 0x2d, 0xf0, 0xde, 0x54, 0x46, 0x5f, 0x19, 0x87, 0x96, 0x83, 0x74, 0xdb, 0x94, 0xdb, 0x18, 0x4b, 0x27, 0x5d, 0x6c, + 0x5c, 0x11, 0xa1, 0xd2, 0xed, 0x15, 0x28, 0xc5, 0x27, 0xba, 0xc9, 0xcc, 0xd7, 0xa5, 0x4e, 0xcc, 0x25, 0x54, 0xc3, + 0x7c, 0xde, 0x5d, 0xe9, 0x44, 0xcb, 0x85, 0xcd, 0xbb, 0xbb, 0x84, 0x3e, 0x41, 0xc3, 0xda, 0x08, 0xec, 0xf6, 0xb9, + 0xb3, 0x83, 0x0c, 0x0e, 0xc1, 0xf0, 0x00, 0x72, 0xa4, 0xc5, 0xf6, 0x81, 0x4d, 0x6b, 0xd8, 0x75, 0xd1, 0x2c, 0x13, + 0x6d, 0xab, 0x4d, 0x93, 0x6b, 0xf7, 0x30, 0x5f, 0x84, 0x3c, 0x85, 0x28, 0xac, 0x7e, 0x7c, 0x0f, 0xbb, 0xf1, 0xb5, + 0xc6, 0x48, 0xd4, 0x95, 0x4c, 0x25, 0xf4, 0x93, 0x5b, 0xcc, 0x92, 0x3b, 0xe3, 0xc5, 0xa8, 0x8c, 0xbf, 0x8f, 0x89, + 0xcb, 0x1f, 0x55, 0x92, 0x1c, 0x58, 0xf6, 0x37, 0x58, 0x72, 0x0b, 0xe6, 0x89, 0x65, 0x35, 0x89, 0x75, 0x72, 0x17, + 0x2c, 0xa2, 0x34, 0x8d, 0x6c, 0x0c, 0x03, 0x6a, 0x9a, 0xb1, 0xea, 0xc1, 0x43, 0x08, 0xf4, 0xd0, 0x2f, 0x4b, 0x69, + 0xd7, 0x59, 0x5a, 0xeb, 0x5e, 0x9b, 0xee, 0xb7, 0x07, 0x54, 0x4d, 0xe3, 0x26, 0xe0, 0x9a, 0xfe, 0xd5, 0x24, 0x92, + 0x11, 0xfb, 0x9b, 0xb3, 0xe2, 0xf1, 0xb2, 0x30, 0x98, 0x26, 0xfa, 0x3a, 0xc9, 0x16, 0x6d, 0x30, 0xd5, 0xcb, 0x16, + 0x9d, 0x5b, 0xec, 0xbe, 0xef, 0xec, 0xf7, 0x1d, 0x16, 0x7d, 0x66, 0x32, 0x52, 0x66, 0x8a, 0xf9, 0xef, 0x3b, 0xfb, + 0x7d, 0x87, 0x77, 0x07, 0xf3, 0xc5, 0x5f, 0x28, 0x96, 0xec, 0x0c, 0x97, 0x60, 0x42, 0x1e, 0x70, 0x37, 0xb5, 0x2c, + 0x13, 0x04, 0xb6, 0x96, 0x00, 0x71, 0x3e, 0x9f, 0xc6, 0x15, 0xaf, 0x86, 0x80, 0xfb, 0xf4, 0xae, 0xed, 0x55, 0x2a, + 0xf0, 0x98, 0xa0, 0x11, 0x31, 0xb1, 0x6d, 0xcc, 0xeb, 0x66, 0xc0, 0xe5, 0x11, 0x5d, 0xea, 0x49, 0x12, 0xe0, 0x55, + 0x8d, 0xca, 0xdb, 0x14, 0x29, 0xbf, 0x48, 0x90, 0xe3, 0x8b, 0x3d, 0xa2, 0x8a, 0x01, 0xac, 0xca, 0x92, 0x3e, 0x81, + 0xd4, 0xf3, 0x43, 0x4f, 0xcd, 0x6d, 0xe4, 0xb1, 0xef, 0xfc, 0x7e, 0x61, 0x7a, 0x56, 0xc8, 0xe5, 0x74, 0x06, 0x3e, + 0xb4, 0xc0, 0x32, 0x14, 0xa6, 0x5e, 0x65, 0xeb, 0x5f, 0x93, 0xdc, 0x04, 0x50, 0x38, 0xdd, 0x94, 0x09, 0xcd, 0xf4, + 0x92, 0xe6, 0xc6, 0x92, 0x94, 0x8b, 0xe9, 0x23, 0x79, 0xfb, 0x12, 0xb0, 0x9b, 0x12, 0xdd, 0xd8, 0x93, 0xf7, 0x16, + 0x76, 0x00, 0xce, 0x08, 0xdb, 0x57, 0xf1, 0xa1, 0x02, 0x9d, 0x3f, 0xce, 0x09, 0xdb, 0x57, 0xf5, 0x09, 0xb3, 0xd9, + 0x33, 0xb2, 0x35, 0xdc, 0x7e, 0x9c, 0x35, 0x72, 0x74, 0xd2, 0x49, 0xf3, 0x9e, 0x27, 0x06, 0x16, 0xa0, 0x01, 0x70, + 0x77, 0xb6, 0x67, 0x79, 0x77, 0x43, 0x40, 0xef, 0x92, 0x49, 0x7b, 0x5d, 0x6e, 0x52, 0xd6, 0xeb, 0x4e, 0x45, 0x05, + 0x0b, 0x3c, 0x0b, 0xf6, 0x02, 0xb5, 0x5f, 0x7b, 0x28, 0xce, 0x2f, 0xd9, 0xb6, 0xe9, 0x79, 0xd9, 0x77, 0x6f, 0xcf, + 0x22, 0x63, 0x9b, 0xf6, 0x76, 0x0f, 0x91, 0xb0, 0x9c, 0xb0, 0x0e, 0x38, 0xe1, 0xaa, 0x76, 0x40, 0x80, 0x3e, 0x05, + 0x22, 0x37, 0x96, 0x64, 0xb5, 0xa9, 0x8c, 0xee, 0x03, 0xbf, 0x5b, 0x4a, 0xa4, 0x1b, 0x6d, 0x49, 0x30, 0x7d, 0x82, + 0x51, 0xd3, 0x99, 0xa7, 0xa9, 0x6b, 0xaf, 0x2e, 0x6f, 0x8b, 0xb6, 0xfe, 0x0d, 0x68, 0x6c, 0xb6, 0x87, 0x89, 0xa1, + 0x0c, 0x62, 0xa0, 0xf7, 0x11, 0xef, 0x35, 0x1a, 0x19, 0x02, 0x85, 0x4c, 0x36, 0xc4, 0x32, 0xf1, 0x5a, 0xf4, 0xa3, + 0x23, 0x03, 0x8f, 0x2a, 0x01, 0x61, 0x0a, 0x42, 0x48, 0xd8, 0xb5, 0x41, 0xd8, 0x70, 0xb9, 0x6a, 0xb9, 0xb0, 0x91, + 0x6a, 0x43, 0x07, 0xff, 0xaf, 0x70, 0xd9, 0xea, 0x99, 0xe5, 0xa2, 0x18, 0xdc, 0xcc, 0x0d, 0x58, 0x24, 0x48, 0x8f, + 0x36, 0xdb, 0x43, 0x71, 0x7f, 0x2e, 0x36, 0x1b, 0x02, 0x12, 0x73, 0x98, 0xa0, 0x68, 0x38, 0x37, 0xc6, 0x58, 0x25, + 0x95, 0x96, 0xb5, 0x26, 0x31, 0x07, 0x01, 0xa3, 0xc3, 0x75, 0x5f, 0xdd, 0xa6, 0x0c, 0xdf, 0xa5, 0x02, 0xdf, 0x80, + 0x27, 0x4d, 0x2a, 0xb1, 0x7b, 0xbc, 0xa0, 0xd8, 0x10, 0xdd, 0xf3, 0xec, 0x6d, 0x01, 0xeb, 0x6c, 0xf6, 0x88, 0x08, + 0x7e, 0x57, 0xbf, 0xda, 0xe0, 0xbb, 0x85, 0x5f, 0x81, 0xf5, 0x73, 0x70, 0x92, 0x62, 0xd1, 0x90, 0xcd, 0xc2, 0x1d, + 0x19, 0x50, 0xae, 0xe2, 0x97, 0xc3, 0xd4, 0x9d, 0x62, 0xb8, 0xf6, 0xf1, 0x0a, 0xbf, 0xdf, 0x6a, 0xb7, 0xa1, 0xca, + 0xe2, 0x76, 0x6f, 0x8a, 0x86, 0xac, 0x9a, 0xde, 0x93, 0xb9, 0x95, 0x52, 0xff, 0x7a, 0x8f, 0x5b, 0x3b, 0xed, 0xfb, + 0x69, 0xbe, 0xf5, 0xe8, 0x5c, 0x35, 0xed, 0x53, 0x6b, 0x45, 0x70, 0xf0, 0xb3, 0x85, 0x9b, 0x3b, 0x03, 0x0e, 0xe0, + 0xe7, 0xef, 0x68, 0x5e, 0x67, 0x10, 0x9d, 0xde, 0x6a, 0xc6, 0xd7, 0xf1, 0x1f, 0xe3, 0x46, 0xdc, 0x4f, 0xff, 0x48, + 0xfe, 0x18, 0x37, 0x50, 0x1f, 0xc5, 0x8b, 0xdb, 0x35, 0x9b, 0xaf, 0x21, 0xd8, 0xda, 0xbd, 0x13, 0xfc, 0x26, 0x2c, + 0xc9, 0x35, 0xcd, 0x79, 0xb6, 0x76, 0x0f, 0x02, 0xae, 0xdd, 0xab, 0x44, 0x6b, 0xf3, 0xc6, 0xd5, 0x3a, 0x96, 0xa3, + 0x1c, 0x02, 0x0b, 0xc7, 0x07, 0xcd, 0xfe, 0xa0, 0xd5, 0x7c, 0x30, 0xb4, 0xff, 0x9a, 0x08, 0xf7, 0xa8, 0x16, 0xb1, + 0xed, 0xde, 0xd6, 0xd6, 0x8f, 0xc1, 0xb0, 0x03, 0x42, 0x81, 0x83, 0x5c, 0xfa, 0x3a, 0x43, 0xd6, 0xf7, 0x64, 0xbd, + 0x66, 0x2e, 0x9a, 0xb5, 0xd3, 0xe0, 0x97, 0xb1, 0x99, 0x8e, 0xdb, 0x49, 0xa7, 0xe7, 0xc5, 0x58, 0xd2, 0x80, 0x48, + 0xd3, 0x98, 0x41, 0x20, 0xa9, 0x95, 0xe1, 0xb0, 0x16, 0xb7, 0x51, 0x5a, 0xdd, 0x1f, 0x41, 0xca, 0x0f, 0x51, 0xca, + 0x4f, 0x08, 0x04, 0xd0, 0xb6, 0xcc, 0x51, 0xd9, 0x90, 0xf7, 0x5d, 0x7a, 0x68, 0x9c, 0x19, 0x1a, 0x7c, 0xbd, 0x6e, + 0x55, 0xc3, 0x54, 0x45, 0x7d, 0x98, 0xab, 0x0d, 0x16, 0xe4, 0x0d, 0xe8, 0x9a, 0x15, 0x11, 0xfd, 0xd0, 0x55, 0x1e, + 0xde, 0x43, 0xc6, 0x92, 0x80, 0x93, 0x7e, 0x5f, 0xf4, 0x0b, 0x72, 0xf5, 0x30, 0x06, 0x1f, 0x33, 0xcc, 0x07, 0x7a, + 0x50, 0x0c, 0x87, 0x28, 0x75, 0x4e, 0x67, 0xa9, 0x89, 0xb8, 0x12, 0xf8, 0x25, 0x17, 0xe0, 0x97, 0xac, 0x10, 0x1b, + 0x14, 0x43, 0xf2, 0x30, 0x8b, 0x25, 0x38, 0xe5, 0xef, 0xf1, 0x79, 0x7c, 0x1a, 0x1a, 0x98, 0x9a, 0x61, 0x99, 0x8b, + 0x6c, 0xb0, 0x98, 0xb3, 0x96, 0x40, 0x70, 0x33, 0xe0, 0x2e, 0xb5, 0x21, 0xd1, 0x58, 0x03, 0x45, 0xb7, 0x51, 0x68, + 0x66, 0xf4, 0x62, 0xa7, 0x8d, 0x41, 0xe4, 0xf0, 0xc2, 0x5c, 0xc3, 0x58, 0x04, 0x32, 0x97, 0xab, 0x1e, 0xfb, 0xcb, + 0x0f, 0x9b, 0x15, 0x06, 0xaf, 0xc8, 0x74, 0xe8, 0x8e, 0x63, 0xc6, 0x57, 0x79, 0xe2, 0x18, 0x82, 0x4c, 0x2c, 0x95, + 0x6e, 0x38, 0x26, 0xae, 0xa4, 0xcf, 0xc4, 0x90, 0xed, 0x86, 0x67, 0xe6, 0x42, 0x37, 0xdb, 0x7f, 0x3a, 0xb7, 0x73, + 0x4e, 0xb8, 0xd1, 0x4a, 0x1a, 0x6d, 0xd4, 0x33, 0x43, 0x55, 0x5d, 0x30, 0xbf, 0x87, 0x4e, 0x4b, 0x8b, 0x9d, 0xab, + 0x77, 0x2f, 0x7c, 0x9d, 0xaf, 0x8c, 0xbf, 0xc5, 0xaa, 0xd0, 0x8a, 0x0c, 0xb7, 0x5b, 0xc8, 0x9b, 0x33, 0x3d, 0xf4, + 0x8a, 0x5c, 0xa8, 0x0e, 0x7f, 0x51, 0x4f, 0x98, 0x07, 0x3b, 0xa3, 0x86, 0xf0, 0xe8, 0xf7, 0x26, 0x03, 0xe5, 0x1f, + 0x4c, 0x4c, 0xe6, 0x2c, 0xb9, 0xa1, 0x85, 0x88, 0x7f, 0x7c, 0x21, 0x4c, 0xac, 0xaa, 0x03, 0x18, 0xc8, 0x81, 0xa9, + 0x78, 0x00, 0xb7, 0x26, 0x7c, 0xc2, 0xd9, 0x38, 0x3d, 0x88, 0x7e, 0x6c, 0x88, 0xc6, 0x8f, 0xd1, 0x8f, 0xe0, 0xee, + 0xec, 0x5e, 0x87, 0x2c, 0xe3, 0x42, 0xf8, 0x7b, 0xac, 0x87, 0xa5, 0x4a, 0x19, 0x6b, 0xaf, 0x5b, 0x0e, 0x2f, 0xa4, + 0xee, 0x65, 0xf1, 0x43, 0x47, 0xac, 0x6d, 0x0a, 0xd6, 0x21, 0x25, 0x85, 0x67, 0x57, 0xcc, 0xad, 0x16, 0x73, 0x97, + 0x5a, 0xc2, 0x5f, 0x5f, 0x3d, 0x2c, 0x55, 0xd0, 0x70, 0x10, 0xba, 0xd2, 0x16, 0x12, 0x60, 0xe0, 0x52, 0xfa, 0x74, + 0xba, 0x33, 0x89, 0x8c, 0xb2, 0x18, 0xde, 0x3d, 0x08, 0x02, 0x09, 0xb0, 0xad, 0xb0, 0x2a, 0x70, 0xb9, 0x52, 0x45, + 0xbd, 0x94, 0x04, 0x02, 0xd0, 0x97, 0xde, 0x83, 0xf2, 0xb2, 0xe8, 0x35, 0x1a, 0x12, 0xb4, 0xb0, 0xd4, 0x5c, 0xab, + 0x62, 0x7a, 0x18, 0xbe, 0x6a, 0x18, 0x7c, 0x78, 0x87, 0xb4, 0xad, 0xa7, 0x45, 0x29, 0xa1, 0x76, 0x07, 0x1d, 0x82, + 0x55, 0x76, 0x50, 0xfe, 0x6d, 0x4c, 0x91, 0xcd, 0x1f, 0xb0, 0x1f, 0xa8, 0xeb, 0x70, 0xe8, 0x0a, 0x56, 0xbd, 0x94, + 0x51, 0x30, 0x60, 0xe5, 0x14, 0xa8, 0xbd, 0x93, 0x8c, 0x66, 0x33, 0x06, 0xea, 0x7e, 0x5b, 0xb4, 0x9a, 0xdb, 0x93, + 0xba, 0xdf, 0x90, 0x71, 0xf6, 0x11, 0xc6, 0xd9, 0x47, 0x81, 0x17, 0x8b, 0x24, 0x3f, 0xcb, 0x58, 0xe3, 0x58, 0x35, + 0x05, 0x3a, 0xe9, 0x00, 0x77, 0x06, 0x0e, 0x3c, 0x60, 0x8b, 0x72, 0x74, 0x44, 0x9d, 0xc5, 0x3d, 0x6d, 0x64, 0xde, + 0xdb, 0x13, 0x6a, 0x17, 0xb1, 0xc0, 0xcd, 0x9a, 0x99, 0x16, 0xb4, 0x56, 0x18, 0xe7, 0xf1, 0x30, 0x22, 0x63, 0x2d, + 0x7e, 0xc2, 0x96, 0x35, 0x55, 0xfd, 0x06, 0x9a, 0xa3, 0x5a, 0x90, 0x9b, 0x17, 0xc6, 0x5b, 0x95, 0x0c, 0xa2, 0x68, + 0x68, 0x39, 0x15, 0x62, 0x48, 0xc6, 0xa0, 0x35, 0x0c, 0x6e, 0xb5, 0xd7, 0x6b, 0xee, 0x11, 0x5f, 0xd4, 0xbc, 0xd5, + 0xcc, 0x2d, 0x40, 0x56, 0xc4, 0x51, 0x79, 0x6f, 0x12, 0x81, 0xf7, 0x6d, 0x19, 0x21, 0x6d, 0x35, 0xb0, 0x4f, 0x57, + 0x96, 0x8a, 0xcd, 0x77, 0x74, 0x3a, 0x4c, 0x23, 0x3b, 0xa2, 0x08, 0x7f, 0x2a, 0x21, 0x09, 0x57, 0x49, 0x9f, 0x54, + 0x26, 0x17, 0x4c, 0xa5, 0x1c, 0x7f, 0x2a, 0xa4, 0xd4, 0xd7, 0xf6, 0x4b, 0xe2, 0xea, 0x4e, 0x46, 0xe0, 0x4f, 0x53, + 0xa6, 0xdf, 0xd1, 0x62, 0xca, 0xc0, 0xaf, 0xc8, 0xdf, 0x8e, 0xa5, 0x94, 0x5c, 0xbd, 0x10, 0xf1, 0x80, 0x62, 0x78, + 0x77, 0x75, 0x88, 0xb5, 0x09, 0x81, 0x52, 0xe2, 0x22, 0x5c, 0x10, 0xbd, 0x29, 0xe4, 0xed, 0x5d, 0x5c, 0x60, 0xe7, + 0x00, 0x58, 0x3a, 0x4d, 0x02, 0xfc, 0xcb, 0xc7, 0x7c, 0xac, 0xc6, 0x9c, 0x1a, 0x5d, 0xbf, 0xfb, 0x9d, 0x7c, 0x02, + 0x7a, 0x5b, 0x3a, 0x0a, 0x0e, 0x5a, 0x43, 0xc8, 0x85, 0xbb, 0x30, 0xb8, 0xf8, 0x0a, 0x6b, 0x17, 0x85, 0xf1, 0xc6, + 0x02, 0xe8, 0x3d, 0xca, 0xc0, 0x82, 0x0d, 0x73, 0x4c, 0xe1, 0xd1, 0xda, 0x29, 0xd3, 0x41, 0x54, 0x90, 0x27, 0xe5, + 0xb3, 0xa4, 0xb5, 0xda, 0x6f, 0xd9, 0x04, 0xee, 0x30, 0x92, 0x6f, 0x17, 0x4e, 0x1c, 0x78, 0x40, 0xa6, 0xc9, 0x6c, + 0xb3, 0x6f, 0x7c, 0xe4, 0x91, 0xd7, 0x93, 0x78, 0x5f, 0x4b, 0x61, 0xbe, 0x59, 0xd1, 0x0d, 0x86, 0x50, 0x14, 0x61, + 0xbf, 0x37, 0x2a, 0xa6, 0xa8, 0x32, 0x68, 0x83, 0x86, 0xe5, 0x8d, 0xf8, 0x19, 0xce, 0x18, 0x5a, 0x2f, 0x64, 0xef, + 0xe8, 0xac, 0xc3, 0x99, 0xc3, 0x8c, 0x19, 0x81, 0x51, 0x69, 0x59, 0xd0, 0x29, 0x38, 0x3a, 0x57, 0x1f, 0x44, 0xc5, + 0xd5, 0xb1, 0x02, 0xf0, 0x24, 0x33, 0xf8, 0x27, 0xdf, 0x06, 0xeb, 0x61, 0xab, 0x66, 0x98, 0xfa, 0xb3, 0xde, 0x75, + 0x2d, 0x5f, 0x85, 0x38, 0xd2, 0xc6, 0x10, 0x5a, 0xe7, 0xf6, 0x0e, 0x50, 0xc4, 0x05, 0xbd, 0x48, 0x35, 0xfe, 0xa4, + 0x96, 0x23, 0xb3, 0xbe, 0xc6, 0x75, 0x4c, 0x1b, 0x44, 0xb1, 0xee, 0x9a, 0xf8, 0x53, 0xf5, 0x0a, 0xac, 0x4a, 0x81, + 0x75, 0x06, 0xe5, 0x87, 0x2a, 0x2f, 0x1b, 0x52, 0x49, 0xae, 0x4c, 0xa7, 0xd2, 0x74, 0x5a, 0x21, 0x94, 0x4b, 0x4f, + 0xca, 0xfb, 0x57, 0x08, 0x61, 0x60, 0xca, 0xec, 0xc1, 0x2a, 0xb5, 0x83, 0x55, 0xf0, 0xea, 0xc5, 0x16, 0x56, 0x49, + 0x38, 0x9e, 0x4b, 0x34, 0x2a, 0x2a, 0x1c, 0x32, 0xa4, 0x2f, 0xc4, 0x22, 0x48, 0x00, 0x2c, 0x7a, 0x99, 0xb9, 0xbc, + 0xef, 0xe1, 0x50, 0xd8, 0x93, 0x4c, 0xc2, 0xe9, 0x26, 0x34, 0x87, 0xe7, 0x81, 0x55, 0xdf, 0x23, 0xc4, 0xcc, 0xc4, + 0x7f, 0x82, 0x67, 0xa1, 0xbf, 0xff, 0x1c, 0xad, 0xb3, 0x20, 0x4f, 0xff, 0x25, 0x4a, 0x42, 0x63, 0xff, 0x39, 0x1e, + 0x3a, 0x24, 0x0c, 0x07, 0xbe, 0x3d, 0xc2, 0x0a, 0x07, 0x77, 0x8a, 0xf8, 0x0c, 0xee, 0xf0, 0xb1, 0x0e, 0x3d, 0x00, + 0x2c, 0xa1, 0x38, 0x04, 0xf9, 0x16, 0x8a, 0x99, 0x61, 0x6b, 0xb2, 0x0a, 0x2f, 0x70, 0xc1, 0x6a, 0xa1, 0xbc, 0xbf, + 0x6d, 0x79, 0x29, 0xad, 0x76, 0xc9, 0x6b, 0xcc, 0x81, 0xca, 0xcf, 0xf0, 0xc2, 0x57, 0x98, 0xf7, 0xaa, 0xdd, 0x17, + 0xfe, 0xe4, 0x80, 0x9e, 0x42, 0xc0, 0x48, 0xf7, 0x7b, 0x43, 0xb8, 0xa7, 0xe8, 0x65, 0x2e, 0x0e, 0xdb, 0x0e, 0xba, + 0x17, 0x98, 0xab, 0xeb, 0x2a, 0x6b, 0x01, 0xa6, 0xd0, 0xe0, 0xa0, 0x0a, 0x67, 0x04, 0xe6, 0xea, 0x45, 0x59, 0x70, + 0x01, 0xe2, 0x7d, 0x5f, 0x98, 0x9c, 0x32, 0x1a, 0xc0, 0xbb, 0xac, 0x7c, 0x74, 0xaa, 0xcf, 0xc1, 0x65, 0xdc, 0xb0, + 0x89, 0x4f, 0x84, 0x4f, 0x05, 0x56, 0xd2, 0x1a, 0x87, 0x46, 0x74, 0x4c, 0x17, 0x60, 0xb6, 0x01, 0x14, 0xdc, 0x9d, + 0x0f, 0x5b, 0x0b, 0x15, 0x3c, 0xc9, 0x5b, 0x7b, 0x41, 0x9b, 0x10, 0x67, 0xd2, 0x14, 0xdc, 0x6d, 0x17, 0x45, 0x60, + 0x7e, 0xfb, 0x6f, 0x85, 0x45, 0x82, 0x01, 0x95, 0x9a, 0x24, 0x08, 0x4f, 0x50, 0x1a, 0xe9, 0x56, 0x6e, 0x26, 0x90, + 0x4e, 0x44, 0x78, 0xc3, 0xfc, 0x72, 0xeb, 0x7c, 0x75, 0xd4, 0x40, 0x54, 0xd4, 0x40, 0x05, 0xd4, 0x40, 0xd6, 0xb7, + 0x7f, 0x01, 0x0b, 0x61, 0x23, 0x54, 0x89, 0x20, 0x20, 0xc2, 0x42, 0x1b, 0x3e, 0xa0, 0x48, 0x42, 0xc8, 0x1b, 0x40, + 0xc5, 0x94, 0xbc, 0x05, 0xa3, 0x71, 0x78, 0xbd, 0x07, 0xdc, 0x2f, 0x2d, 0xc3, 0xe0, 0x39, 0x05, 0x93, 0xff, 0xcc, + 0xe7, 0x43, 0xf5, 0x72, 0x75, 0x10, 0xc2, 0x4f, 0x20, 0x56, 0x84, 0xe3, 0x2f, 0x7e, 0x06, 0xb2, 0xa9, 0xb0, 0x3c, + 0x3a, 0x92, 0x20, 0xf0, 0x43, 0x14, 0xe1, 0x80, 0x67, 0x78, 0x9b, 0x6d, 0x11, 0x3d, 0x3f, 0x2b, 0x55, 0xcd, 0x4a, + 0x06, 0xb3, 0x2a, 0x3c, 0x8d, 0xa3, 0x1b, 0xc2, 0x40, 0x70, 0xa1, 0x76, 0xdf, 0x20, 0x04, 0xca, 0x96, 0x1b, 0x43, + 0x97, 0x9e, 0x82, 0xf9, 0x68, 0x1c, 0xbd, 0x65, 0xf0, 0xb0, 0xb0, 0x71, 0x47, 0x61, 0x9a, 0x65, 0xda, 0x30, 0x8f, + 0x8d, 0xc0, 0x49, 0x9d, 0xa2, 0xe4, 0xb3, 0xe4, 0x22, 0x8e, 0x9a, 0x57, 0x11, 0x6a, 0xc0, 0xbf, 0x0d, 0x8e, 0x7a, + 0x34, 0xa1, 0xe3, 0xb1, 0x0f, 0x7e, 0x93, 0x11, 0xb3, 0xc9, 0xd6, 0x6b, 0x51, 0x11, 0xf4, 0xc4, 0x6e, 0x30, 0x60, + 0x25, 0x9e, 0x00, 0xfb, 0x60, 0x39, 0x58, 0xf2, 0x4e, 0xc4, 0xca, 0x9f, 0x52, 0x18, 0xac, 0x9e, 0x33, 0x84, 0x70, + 0x16, 0x30, 0x29, 0xff, 0xf9, 0x4c, 0xc3, 0xf5, 0xf3, 0xf3, 0x75, 0x8c, 0x88, 0xf4, 0x41, 0xe4, 0x6a, 0xec, 0x88, + 0x08, 0xc2, 0x96, 0xe9, 0x81, 0x2b, 0xf3, 0x83, 0xb7, 0xae, 0x1e, 0xda, 0x70, 0x71, 0x60, 0x40, 0x8d, 0x02, 0xa3, + 0x15, 0x9c, 0x93, 0x72, 0xe0, 0xa0, 0x84, 0xd0, 0xac, 0x88, 0x67, 0xe4, 0x0a, 0x22, 0xe1, 0x65, 0xa8, 0x07, 0x86, + 0x05, 0x81, 0x04, 0x35, 0x03, 0x09, 0x2a, 0xf3, 0xb5, 0xc7, 0x30, 0xeb, 0xdc, 0xcc, 0x76, 0x86, 0x7a, 0x2e, 0xc8, + 0xcf, 0xcf, 0x3a, 0x1e, 0x03, 0x4b, 0x7b, 0x74, 0x54, 0x40, 0x04, 0x31, 0xa0, 0xe0, 0xa5, 0x04, 0x18, 0x68, 0xc0, + 0x8b, 0x2d, 0x0d, 0xf8, 0x42, 0x1b, 0xaf, 0x03, 0x63, 0xeb, 0x53, 0x06, 0xb9, 0x78, 0x55, 0xed, 0x69, 0x42, 0xc8, + 0x61, 0xab, 0xaf, 0xd3, 0xdd, 0x08, 0x89, 0xfd, 0x8f, 0xda, 0x04, 0x1a, 0x73, 0xa4, 0xbb, 0xda, 0x98, 0x7f, 0xd7, + 0xf4, 0x88, 0xd5, 0x24, 0xa4, 0x0b, 0xd2, 0xe5, 0xf9, 0xb4, 0x57, 0x70, 0xc5, 0x2a, 0x8d, 0x1c, 0x5c, 0x80, 0x3e, + 0x1b, 0x10, 0xa0, 0x40, 0xa5, 0xa9, 0x04, 0x2d, 0xe2, 0x22, 0x29, 0xd9, 0x30, 0xcc, 0x20, 0x4c, 0x61, 0xb5, 0x12, + 0x74, 0x6b, 0x0d, 0x80, 0x77, 0x66, 0xf6, 0x4f, 0xe9, 0x83, 0x4d, 0x37, 0xde, 0x3c, 0x02, 0x08, 0xc8, 0x61, 0xbb, + 0x64, 0xd7, 0xc5, 0x56, 0x65, 0x16, 0xd6, 0x32, 0xb6, 0x72, 0xbb, 0x1e, 0x63, 0xef, 0xc4, 0x2e, 0x9f, 0x00, 0x21, + 0x6a, 0x4b, 0xa6, 0x11, 0x4b, 0x18, 0xb2, 0xae, 0x0d, 0xd9, 0x68, 0x43, 0xe1, 0xa9, 0x44, 0x0e, 0x5c, 0xa2, 0x09, + 0x92, 0xef, 0xb8, 0x04, 0x87, 0xf0, 0xc2, 0x23, 0xfc, 0x57, 0x60, 0x91, 0x0a, 0xcc, 0xb0, 0x5c, 0xaf, 0xa1, 0x9e, + 0xc7, 0xfb, 0x6c, 0x3b, 0x38, 0xa9, 0xdc, 0x1a, 0xbb, 0xb4, 0x13, 0x8f, 0xcb, 0x26, 0x24, 0xce, 0xa0, 0x5f, 0x5f, + 0x11, 0xf5, 0x0f, 0xdb, 0xe9, 0x0b, 0xff, 0x5e, 0x99, 0xdb, 0x81, 0xd8, 0xb0, 0xde, 0x60, 0xf5, 0x01, 0xb4, 0xfc, + 0x73, 0xe6, 0x1f, 0x2a, 0x0b, 0x6e, 0x12, 0xd4, 0xf6, 0x22, 0xf6, 0x58, 0x0f, 0x31, 0x52, 0x5b, 0xdc, 0x3d, 0x42, + 0xfc, 0xe7, 0x9d, 0x28, 0x06, 0x3c, 0xa9, 0xf8, 0xe7, 0x18, 0xf5, 0x20, 0x14, 0xb5, 0xf5, 0xb0, 0x01, 0x4a, 0xbb, + 0xda, 0x54, 0x62, 0x64, 0x48, 0x20, 0xdf, 0xba, 0xf0, 0x82, 0xe6, 0x24, 0x52, 0x20, 0x27, 0x57, 0x5d, 0x3c, 0xca, + 0xb6, 0x84, 0xb9, 0xde, 0x0e, 0x8e, 0x99, 0xab, 0x8d, 0xac, 0x88, 0xdf, 0x01, 0x3b, 0xc3, 0x8d, 0x64, 0xe9, 0xc0, + 0xa7, 0x6a, 0xe0, 0xf3, 0x6b, 0x6e, 0x28, 0x8a, 0x42, 0xfd, 0x77, 0xf6, 0x91, 0x39, 0xf8, 0x9d, 0x06, 0xe2, 0x63, + 0xe6, 0x74, 0x24, 0x5b, 0xa1, 0xd6, 0x9c, 0x1d, 0x2f, 0xdb, 0x8e, 0x30, 0x28, 0x6c, 0xf4, 0xbe, 0x0a, 0x59, 0xc5, + 0xde, 0x4e, 0x45, 0x30, 0xa7, 0x1b, 0x55, 0x39, 0xa7, 0x72, 0xcb, 0xa8, 0x96, 0x9a, 0x06, 0x88, 0x70, 0xe5, 0x13, + 0xc9, 0x87, 0xcc, 0x84, 0x7f, 0x30, 0x18, 0x57, 0x8f, 0x14, 0xfe, 0x61, 0x5f, 0xec, 0x90, 0xdd, 0xe8, 0x70, 0x5b, + 0x41, 0xf3, 0x42, 0x05, 0x0f, 0x38, 0x2a, 0x59, 0x42, 0xa4, 0xc8, 0xd5, 0xa1, 0xaa, 0x99, 0xb2, 0x7d, 0x8a, 0x10, + 0x42, 0xda, 0xe3, 0xac, 0x1b, 0x5a, 0x3d, 0xf4, 0x48, 0xe5, 0x34, 0xb9, 0x43, 0x73, 0x5d, 0x80, 0x0a, 0x23, 0x90, + 0xae, 0xbe, 0xb0, 0xbb, 0x54, 0x42, 0xf4, 0xf2, 0x8d, 0x0b, 0x61, 0xec, 0xac, 0x2c, 0x71, 0x61, 0x46, 0x6d, 0xc3, + 0xe8, 0xba, 0x8d, 0xe1, 0x6c, 0x60, 0xcc, 0x34, 0x28, 0x69, 0x41, 0xa8, 0xeb, 0x1e, 0xbd, 0xcc, 0x4c, 0xa0, 0xc7, + 0x9c, 0xd0, 0x06, 0xc3, 0x33, 0xa2, 0xc1, 0xb2, 0xa9, 0x00, 0x0b, 0xbe, 0x55, 0x91, 0x5a, 0x9b, 0x4d, 0x16, 0x7f, + 0xd4, 0xb1, 0x79, 0xda, 0x2f, 0xaf, 0x98, 0xe7, 0xc2, 0x47, 0x47, 0xc8, 0x7c, 0x3c, 0xba, 0xa7, 0x6f, 0xae, 0x5f, + 0xbc, 0x7c, 0xfd, 0x6a, 0xbd, 0x6e, 0xb3, 0x66, 0xfb, 0x0c, 0xff, 0x43, 0x97, 0xf1, 0x60, 0xcb, 0x28, 0x40, 0x47, + 0x47, 0x87, 0xdc, 0xb8, 0xf0, 0x7c, 0xe1, 0x0b, 0x88, 0x1b, 0xa4, 0x87, 0x38, 0x2f, 0xca, 0x98, 0x20, 0xb7, 0x51, + 0x3f, 0xba, 0x8b, 0x40, 0x09, 0x55, 0x91, 0xbf, 0xdf, 0xb6, 0x67, 0x7f, 0x00, 0x81, 0x89, 0xa0, 0x3e, 0x44, 0x00, + 0x81, 0x78, 0xa5, 0xb8, 0x20, 0xcc, 0x27, 0x40, 0x14, 0xef, 0x09, 0x70, 0xa6, 0x26, 0x6a, 0xd5, 0x44, 0xc5, 0x05, + 0x90, 0x44, 0x1b, 0x8e, 0x92, 0x9e, 0x98, 0x00, 0xde, 0x10, 0x94, 0xd2, 0xfe, 0xea, 0xe5, 0xce, 0x5d, 0x2a, 0x47, + 0xfd, 0x56, 0x9a, 0xe3, 0x99, 0xfb, 0x9c, 0xc1, 0xe7, 0xac, 0xe7, 0x4f, 0x07, 0x71, 0x9c, 0xe3, 0x25, 0x11, 0xc7, + 0xfe, 0x59, 0xc4, 0xd5, 0xa2, 0x60, 0x5f, 0xb9, 0x5c, 0xaa, 0x74, 0x75, 0x9b, 0xca, 0xe4, 0xb6, 0x39, 0x3e, 0x8e, + 0x8b, 0xe4, 0xb6, 0xa9, 0x92, 0x5b, 0x84, 0xef, 0x52, 0x99, 0xdc, 0xd9, 0x94, 0xbb, 0xa6, 0x82, 0x9b, 0x2f, 0x2c, + 0xe0, 0x50, 0xb4, 0x45, 0x1b, 0xcb, 0xed, 0xa2, 0x36, 0xc5, 0x15, 0x0d, 0xa3, 0x29, 0xee, 0xd9, 0xf8, 0x61, 0xf8, + 0x12, 0x5c, 0x9a, 0x34, 0x91, 0x7f, 0x80, 0xf4, 0xd3, 0xaa, 0x0c, 0xdc, 0x67, 0xa4, 0xd5, 0x9b, 0x5d, 0x8a, 0x66, + 0xbb, 0xd7, 0x68, 0xcc, 0x60, 0xef, 0x66, 0x24, 0xf7, 0xc5, 0x66, 0x0d, 0x13, 0x5f, 0xe7, 0x30, 0x5b, 0xaf, 0x0f, + 0x73, 0x64, 0x36, 0xdc, 0x94, 0xc5, 0x7a, 0x30, 0x1b, 0xe2, 0x16, 0x7e, 0x9f, 0x21, 0xb4, 0x62, 0x83, 0xd9, 0x90, + 0xb0, 0xc1, 0xac, 0xd1, 0x1e, 0x5a, 0x43, 0x3b, 0xb3, 0x15, 0x37, 0x10, 0x42, 0x73, 0x36, 0x3c, 0x31, 0x25, 0xa5, + 0xcb, 0xb7, 0x5f, 0xb4, 0x0a, 0xe8, 0xa7, 0x6a, 0xc1, 0xcb, 0x24, 0xee, 0x40, 0x5f, 0xf4, 0xd2, 0x3e, 0xdd, 0x5a, + 0x90, 0xd3, 0x93, 0xca, 0xd5, 0x9e, 0x22, 0x6c, 0x7a, 0x52, 0xc7, 0xc5, 0xb1, 0x69, 0xc6, 0x75, 0x29, 0xdd, 0x77, + 0xa8, 0x19, 0xf9, 0xcb, 0xc1, 0x02, 0x10, 0xa4, 0x82, 0x47, 0x5e, 0xb8, 0x70, 0x4a, 0x21, 0x5c, 0x1c, 0x54, 0x76, + 0x60, 0x92, 0x93, 0x56, 0x2f, 0x37, 0x96, 0xfe, 0xb9, 0x8b, 0x68, 0x4a, 0x31, 0x25, 0x99, 0x2f, 0x99, 0x1b, 0xb0, + 0xd0, 0x6d, 0xca, 0x33, 0x03, 0xbd, 0xd2, 0x10, 0x8f, 0x09, 0xc4, 0x43, 0xea, 0x15, 0xc6, 0xc0, 0x2b, 0x9e, 0x35, + 0x8b, 0x01, 0x1b, 0xa2, 0x93, 0x53, 0x4c, 0x07, 0x7f, 0x66, 0x8b, 0x36, 0x3c, 0x16, 0xf8, 0xe7, 0x90, 0xcc, 0x9a, + 0xb2, 0x4c, 0x10, 0x90, 0x30, 0x6e, 0xca, 0x63, 0xd8, 0x4b, 0x08, 0x67, 0xb6, 0x62, 0x36, 0x60, 0xc3, 0xe6, 0xac, + 0xac, 0xd8, 0xf1, 0x15, 0x1b, 0xb2, 0x4c, 0xb0, 0x15, 0x1b, 0xae, 0x62, 0xf8, 0x3a, 0x83, 0x01, 0x41, 0x08, 0x00, + 0x06, 0x00, 0xd0, 0x28, 0x88, 0xe6, 0x8b, 0x15, 0xf1, 0x9b, 0xdd, 0xde, 0xe3, 0xb7, 0xc0, 0x02, 0xad, 0xb6, 0xff, + 0xf7, 0xa1, 0x0c, 0xd8, 0x53, 0x16, 0x26, 0x66, 0x6e, 0x61, 0x55, 0x74, 0x00, 0x95, 0x12, 0x61, 0x0a, 0x03, 0x99, + 0xc3, 0xcc, 0x40, 0x2d, 0xd0, 0x1a, 0xe4, 0x03, 0x3d, 0x6c, 0x66, 0x70, 0xc4, 0xc0, 0x3b, 0x34, 0x64, 0x66, 0x8c, + 0x09, 0xe3, 0x1c, 0xa6, 0x98, 0x19, 0xf0, 0xcc, 0xd2, 0xd6, 0x46, 0x1a, 0x59, 0xae, 0x9f, 0xf7, 0xff, 0xd2, 0xb1, + 0x1a, 0x14, 0xcd, 0xf6, 0x10, 0x1d, 0x12, 0x62, 0x3f, 0x86, 0xb0, 0xc9, 0x5c, 0x6a, 0xc3, 0x7c, 0x9f, 0x74, 0x52, + 0xfb, 0x09, 0x7f, 0x86, 0x1b, 0xb3, 0x03, 0x40, 0x47, 0x86, 0xcd, 0xfa, 0xcb, 0x9a, 0xca, 0xeb, 0xe3, 0xde, 0x28, + 0x95, 0xfb, 0xde, 0x9d, 0x0e, 0x54, 0x13, 0xa1, 0xb7, 0x1e, 0x2e, 0x1f, 0xea, 0x21, 0x60, 0xc6, 0x60, 0x6e, 0x99, + 0xd1, 0xf7, 0x42, 0x24, 0x17, 0x44, 0x02, 0x4b, 0x82, 0x29, 0x61, 0xb0, 0xb7, 0x8e, 0x8e, 0x4c, 0x35, 0xd6, 0x80, + 0xe7, 0x49, 0x11, 0x08, 0x06, 0x3e, 0x82, 0x32, 0xa0, 0x89, 0x32, 0xb7, 0xe1, 0xe4, 0x23, 0x73, 0xbf, 0x70, 0x79, + 0xfb, 0x58, 0x38, 0x6d, 0xab, 0xb9, 0x1e, 0x2f, 0x0b, 0xdc, 0x95, 0xf7, 0x92, 0x56, 0xc1, 0x8d, 0xec, 0x4d, 0x9e, + 0x32, 0x77, 0xeb, 0xbe, 0x54, 0x67, 0x7f, 0x33, 0x9d, 0xb2, 0x99, 0xce, 0x6e, 0x33, 0x61, 0x5c, 0xc9, 0x6f, 0x59, + 0x45, 0x9a, 0x93, 0x35, 0x51, 0x0b, 0x2a, 0xfe, 0x41, 0x17, 0xa0, 0x1d, 0xe5, 0xf6, 0x5e, 0x15, 0x4e, 0xae, 0x9c, + 0x5c, 0x1d, 0xe6, 0x86, 0xb8, 0x22, 0x73, 0xa1, 0x0e, 0x01, 0x5e, 0x5e, 0x94, 0x8f, 0x0f, 0x70, 0x29, 0x7e, 0x91, + 0x63, 0x17, 0xe5, 0x54, 0x48, 0x2d, 0x05, 0x8b, 0x90, 0x41, 0x55, 0x17, 0x03, 0x7b, 0x65, 0xf7, 0x9e, 0xe8, 0xf3, + 0x41, 0x15, 0x31, 0x6f, 0x68, 0x9e, 0xfb, 0xf8, 0x9e, 0xa6, 0xd8, 0xa9, 0x89, 0x33, 0xf2, 0x5b, 0x16, 0xe7, 0x20, + 0x9b, 0x0d, 0xaa, 0xd7, 0x7e, 0x1b, 0x6d, 0x5c, 0x34, 0x63, 0xd1, 0x37, 0x4f, 0x9c, 0xfc, 0x50, 0x18, 0xe3, 0x00, + 0xeb, 0xe8, 0x8f, 0x30, 0xb5, 0x60, 0xcf, 0x12, 0x4f, 0xa1, 0x93, 0x5b, 0x9b, 0x76, 0x17, 0xa6, 0xdd, 0x99, 0xb4, + 0x0e, 0x94, 0x03, 0xd2, 0xec, 0xca, 0x74, 0xee, 0xfc, 0xf7, 0x1d, 0xbc, 0x74, 0xbb, 0x81, 0x48, 0xdc, 0x8b, 0x47, + 0xc6, 0x18, 0xe2, 0x0d, 0xd8, 0x88, 0xaa, 0xa3, 0xa3, 0x9f, 0x9d, 0xf7, 0x6d, 0x25, 0xcb, 0x7e, 0x2b, 0x1c, 0xd8, + 0x16, 0x53, 0xe9, 0xf2, 0xc6, 0x32, 0x5b, 0x82, 0x5d, 0xe7, 0xe1, 0x37, 0xe2, 0xe1, 0x8b, 0x90, 0x69, 0xb1, 0xae, + 0xe2, 0xaf, 0xe4, 0xb8, 0xf4, 0x10, 0xd5, 0x10, 0x81, 0xb4, 0xb2, 0x2e, 0x0d, 0x4d, 0x47, 0xaf, 0x67, 0x74, 0x2c, + 0x6f, 0xde, 0x4a, 0xa9, 0x87, 0xf6, 0x45, 0x6e, 0x9d, 0xc0, 0xa3, 0x85, 0x35, 0x86, 0xe6, 0xae, 0xf4, 0x4e, 0xb2, + 0x01, 0x51, 0xeb, 0xe3, 0x0e, 0x25, 0x91, 0x58, 0x54, 0x77, 0x21, 0x1c, 0xee, 0x42, 0x30, 0x2f, 0x83, 0xb6, 0x41, + 0xec, 0x76, 0x17, 0xb4, 0x0d, 0x9c, 0xba, 0x6d, 0xe0, 0xf6, 0x60, 0xb0, 0xb0, 0xf7, 0xe1, 0xe5, 0x58, 0x8e, 0x85, + 0xe3, 0x0f, 0xee, 0xd9, 0x07, 0x80, 0x40, 0xed, 0xc3, 0x8a, 0x27, 0x0e, 0x04, 0x89, 0x33, 0x1c, 0xfd, 0xc0, 0xd9, + 0x8d, 0xb5, 0x1c, 0x9e, 0x2f, 0x96, 0x9a, 0x8d, 0xcd, 0x1d, 0x35, 0xa8, 0xf8, 0xea, 0x7e, 0x5e, 0xbf, 0x66, 0x35, + 0xdd, 0xf8, 0x3d, 0x08, 0x23, 0xe1, 0x94, 0x1d, 0x46, 0x21, 0x61, 0x83, 0x59, 0x95, 0xf1, 0xda, 0x7e, 0x87, 0x78, + 0x0f, 0xda, 0x84, 0x13, 0x2c, 0x6a, 0x17, 0x54, 0x11, 0xb6, 0xf1, 0xc6, 0x82, 0x28, 0x0f, 0x6f, 0x76, 0x8c, 0xa6, + 0x57, 0x1b, 0x08, 0x74, 0xdc, 0x8f, 0x9a, 0x51, 0x83, 0xa5, 0x2e, 0x28, 0xb3, 0x8f, 0x30, 0xae, 0x2e, 0xcf, 0x4c, + 0x9c, 0xf6, 0x52, 0xaf, 0xfe, 0x7b, 0x06, 0x06, 0xf8, 0x02, 0xbc, 0xc4, 0xc2, 0xe8, 0xae, 0x03, 0xdd, 0x80, 0xfa, + 0xb2, 0xc1, 0x86, 0x68, 0xbd, 0x6e, 0x95, 0xcf, 0x40, 0xb9, 0x6b, 0x2e, 0x61, 0xaf, 0xb9, 0x84, 0xbb, 0xe6, 0x12, + 0xfe, 0x9a, 0x4b, 0x98, 0x6b, 0x2e, 0xe1, 0xaf, 0xb9, 0x3c, 0x08, 0x7f, 0x0a, 0xe2, 0x38, 0xc6, 0x1c, 0xe2, 0x2a, + 0x6a, 0x1b, 0x19, 0x0f, 0x2e, 0x3c, 0x0f, 0x59, 0xa2, 0xca, 0xe5, 0x0f, 0x63, 0xc8, 0xe5, 0xdb, 0xb6, 0x12, 0xc6, + 0x6d, 0x8a, 0x29, 0x88, 0x9c, 0x7e, 0x74, 0x54, 0xb9, 0x3b, 0x0f, 0x5a, 0xc3, 0x94, 0xe3, 0x95, 0x75, 0xa2, 0xfd, + 0x27, 0xe8, 0xe4, 0xcd, 0xaf, 0x8f, 0xa9, 0xdc, 0x10, 0xe1, 0x4c, 0xee, 0x0f, 0xdb, 0x9e, 0x52, 0xfc, 0x94, 0x99, + 0xf0, 0xe4, 0x3c, 0xd1, 0x46, 0x04, 0x41, 0x88, 0x12, 0xf5, 0xff, 0xb2, 0xf7, 0xae, 0xcb, 0x6d, 0x23, 0x59, 0xba, + 0xe8, 0xab, 0x48, 0x0c, 0x9b, 0x05, 0x98, 0x49, 0x8a, 0xf2, 0xde, 0x33, 0x11, 0x07, 0x54, 0x9a, 0xe1, 0x4b, 0xb9, + 0xcb, 0x5d, 0xe5, 0x4b, 0x5b, 0xae, 0x6a, 0x57, 0x33, 0x78, 0x54, 0x10, 0x90, 0x24, 0xe0, 0x02, 0x01, 0x16, 0x00, + 0x4a, 0xa4, 0x49, 0xbc, 0xfb, 0x8e, 0xb5, 0x56, 0x5e, 0x41, 0x50, 0x76, 0xcf, 0xec, 0xf9, 0x75, 0xce, 0x1f, 0x5b, + 0x4c, 0x24, 0x12, 0x79, 0xcf, 0x95, 0xeb, 0xf2, 0x7d, 0x2c, 0xe2, 0x05, 0xad, 0x77, 0x15, 0x0a, 0x8f, 0xaa, 0x28, + 0xe5, 0x56, 0xf2, 0x32, 0x83, 0x20, 0x76, 0xf4, 0xc2, 0xf0, 0x27, 0x10, 0x42, 0x10, 0x61, 0xc2, 0xe7, 0x61, 0x46, + 0xdb, 0x59, 0xa4, 0x93, 0x7e, 0x1f, 0x66, 0xb8, 0x81, 0x95, 0xfc, 0x5c, 0xf5, 0xd9, 0x7e, 0x1b, 0x84, 0x6c, 0x17, + 0x44, 0xec, 0xb6, 0xd8, 0x06, 0xa5, 0x75, 0x24, 0x5e, 0x2b, 0xc3, 0xdf, 0xc2, 0xeb, 0xe5, 0x21, 0xc4, 0xfb, 0xf4, + 0xd2, 0xfc, 0x2c, 0x6d, 0x45, 0x01, 0xee, 0x23, 0xf4, 0xa8, 0x0e, 0x04, 0x3b, 0xe1, 0x09, 0x0f, 0xe0, 0x64, 0x35, + 0xab, 0xf8, 0xa3, 0x14, 0xc4, 0x89, 0x82, 0x43, 0xc0, 0xd5, 0xf6, 0x3a, 0xfd, 0x0a, 0x86, 0x2f, 0x1d, 0x6c, 0x39, + 0xbc, 0x2d, 0xb6, 0x3d, 0x56, 0xf2, 0x0f, 0xc0, 0xbe, 0xd5, 0x93, 0xb1, 0xba, 0x3d, 0x70, 0xd6, 0xa5, 0x14, 0x1d, + 0x6f, 0x8a, 0xc3, 0xdb, 0xf3, 0xd9, 0x7e, 0x1b, 0x44, 0x6c, 0x17, 0x64, 0x58, 0xeb, 0xa4, 0xe1, 0x38, 0x18, 0xc2, + 0x67, 0x31, 0xc2, 0xfe, 0x2f, 0xea, 0x81, 0x97, 0x90, 0x1a, 0x0a, 0x5c, 0x0c, 0x36, 0x1c, 0xad, 0xed, 0x32, 0x0d, + 0xdc, 0xd4, 0xa0, 0xd7, 0xf7, 0x14, 0xa2, 0xbc, 0x60, 0x34, 0x37, 0x82, 0x75, 0x63, 0xc8, 0xc5, 0xe1, 0xb8, 0x59, + 0x0c, 0x79, 0x49, 0xd3, 0x69, 0x10, 0x4a, 0x77, 0x96, 0x35, 0x24, 0x51, 0xf6, 0x41, 0xa8, 0x5d, 0x5b, 0xf6, 0xdb, + 0xc0, 0xf6, 0xe5, 0x8f, 0x86, 0xb1, 0x7f, 0xb1, 0x78, 0x22, 0xa4, 0x8b, 0x78, 0x0e, 0x82, 0xa8, 0xfd, 0x3c, 0x1b, + 0x6e, 0xfc, 0x8b, 0xf5, 0x13, 0xa1, 0xfc, 0xc6, 0x73, 0x5b, 0x0e, 0x11, 0x59, 0x0b, 0x5f, 0x18, 0x0f, 0x0f, 0xae, + 0x0c, 0x6d, 0x87, 0x83, 0xd0, 0x7f, 0x9b, 0x35, 0x82, 0x1b, 0x1b, 0xda, 0xe7, 0x0b, 0x1f, 0xb6, 0x36, 0x1a, 0x6b, + 0x8a, 0xe9, 0x16, 0xfa, 0x37, 0x99, 0x2d, 0xed, 0x69, 0x54, 0xf2, 0xe2, 0xd4, 0x34, 0x62, 0x21, 0x0c, 0x18, 0xfa, + 0xc9, 0x7c, 0x00, 0xd5, 0xdc, 0xf1, 0x08, 0x64, 0xf2, 0x81, 0x1e, 0xac, 0x49, 0xad, 0xfa, 0x6b, 0x98, 0xc9, 0xff, + 0x23, 0x15, 0x16, 0xa3, 0xbb, 0x6d, 0x98, 0xa9, 0x3f, 0x22, 0xf9, 0x07, 0xcb, 0xf9, 0x2e, 0xf5, 0x42, 0xed, 0xc7, + 0xc2, 0x0a, 0x0c, 0x4a, 0x54, 0x0d, 0xe8, 0x81, 0x08, 0xaa, 0x32, 0x48, 0x33, 0xac, 0xce, 0x41, 0xbf, 0x7b, 0x5a, + 0x75, 0x24, 0x87, 0xb4, 0x56, 0x43, 0x2a, 0x98, 0x2a, 0x35, 0xc8, 0x0f, 0x87, 0x65, 0xca, 0x74, 0x19, 0x70, 0x49, + 0x5f, 0xa6, 0x4a, 0x29, 0xfc, 0x17, 0x02, 0xd0, 0x39, 0xb8, 0xc7, 0x97, 0x63, 0x20, 0xcd, 0xb0, 0xf0, 0x5b, 0xb3, + 0xe3, 0x6b, 0x12, 0x6e, 0x93, 0xe0, 0x62, 0x80, 0x73, 0x74, 0x15, 0x96, 0xcb, 0x14, 0x22, 0xa8, 0x4a, 0xa8, 0x6f, + 0x65, 0x1a, 0x94, 0xb6, 0x1a, 0x84, 0x35, 0x09, 0x75, 0x26, 0xd9, 0xa8, 0xb4, 0xdd, 0x28, 0xcc, 0x16, 0x71, 0x3d, + 0x23, 0xac, 0x39, 0x9b, 0xa9, 0x06, 0x26, 0x0d, 0xc7, 0x4d, 0xa3, 0xb5, 0xa8, 0x50, 0x53, 0x98, 0xd7, 0xb8, 0xaa, + 0x54, 0x75, 0x37, 0xa7, 0x96, 0xd2, 0xa2, 0xbd, 0xea, 0x26, 0xd9, 0x90, 0xcb, 0x50, 0x86, 0xc1, 0x46, 0x8e, 0x60, + 0x02, 0x49, 0x72, 0xe6, 0x6f, 0xe4, 0x1f, 0x6a, 0xd3, 0xb5, 0x80, 0x39, 0xc6, 0x2c, 0x1b, 0x16, 0xf4, 0x0a, 0xdc, + 0x03, 0xad, 0xf4, 0x7c, 0x9a, 0x5d, 0xe4, 0x41, 0x32, 0x2c, 0xf4, 0xb2, 0xc9, 0xf8, 0x5f, 0xc2, 0x48, 0x93, 0x19, + 0x2b, 0x59, 0x64, 0xbb, 0x3a, 0x25, 0xce, 0xe3, 0x04, 0xb6, 0x47, 0xd3, 0x5b, 0xbe, 0xcf, 0x20, 0x2a, 0x08, 0x14, + 0xcc, 0x98, 0x2f, 0xbb, 0x78, 0xea, 0xfb, 0xcc, 0x32, 0x75, 0x1f, 0x0e, 0xc6, 0x8c, 0xed, 0xf7, 0xfb, 0x79, 0xbf, + 0xaf, 0xe6, 0x5b, 0xbf, 0x9f, 0x3c, 0x33, 0x7f, 0x7b, 0xc0, 0xa0, 0x20, 0x27, 0xa2, 0xa9, 0x10, 0xc1, 0x3f, 0x24, + 0x4f, 0x90, 0x8c, 0xee, 0xb8, 0xcf, 0x2d, 0x67, 0xcb, 0xea, 0x08, 0x04, 0xf3, 0x70, 0xb8, 0x54, 0x60, 0xd7, 0x12, + 0x45, 0x42, 0x96, 0xff, 0x04, 0x8c, 0x67, 0xee, 0x03, 0x2c, 0x19, 0x80, 0xb0, 0x55, 0x9e, 0xae, 0xf7, 0x7c, 0x15, + 0xbc, 0xd3, 0xf1, 0xae, 0xb1, 0x22, 0x03, 0x71, 0x0b, 0x6c, 0xc4, 0x5a, 0x7b, 0x40, 0xce, 0x14, 0xe0, 0x78, 0x71, + 0x38, 0x9c, 0xcb, 0x5f, 0xba, 0xd9, 0x3a, 0x81, 0x4a, 0x81, 0xdb, 0xa3, 0x93, 0x83, 0xff, 0x01, 0x34, 0x83, 0x72, + 0x98, 0xd7, 0xdb, 0x3f, 0x98, 0x93, 0x9f, 0x9e, 0xe2, 0x9f, 0xf0, 0x10, 0x9d, 0x7e, 0xbb, 0x37, 0x7f, 0x50, 0x54, + 0x1e, 0x0e, 0x6a, 0xf1, 0x9f, 0x73, 0x5e, 0xc1, 0x2f, 0x7c, 0x13, 0x98, 0x4d, 0xa6, 0xde, 0xc9, 0x37, 0x79, 0xce, + 0xd4, 0x6b, 0xbc, 0x62, 0xf2, 0x1d, 0x0e, 0xe7, 0x62, 0x54, 0x6f, 0x47, 0x4e, 0xb4, 0x53, 0x8e, 0x71, 0x30, 0xf8, + 0x2f, 0xa2, 0x6d, 0x42, 0x80, 0xa1, 0x1c, 0x8e, 0xcc, 0xc6, 0x95, 0x25, 0x9e, 0xa5, 0xf3, 0xcb, 0x49, 0x5d, 0xee, + 0xb4, 0xe2, 0x69, 0x0f, 0x2c, 0x6e, 0x6b, 0xf0, 0x02, 0xb8, 0xb3, 0xd8, 0xba, 0x52, 0x70, 0xb8, 0x80, 0x38, 0xc5, + 0x09, 0x88, 0xa0, 0xfd, 0xbe, 0xc4, 0x7b, 0x05, 0x7d, 0xd2, 0x8f, 0x10, 0x0c, 0xf9, 0x8b, 0x04, 0xdc, 0xf5, 0x7a, + 0x35, 0xc6, 0xf7, 0x52, 0x08, 0xae, 0xcf, 0x34, 0x00, 0x2d, 0xf8, 0x5d, 0x3e, 0x94, 0xd3, 0x6f, 0x22, 0xf0, 0x6c, + 0xd9, 0x9b, 0x28, 0x77, 0x1b, 0x9e, 0xf6, 0xba, 0x85, 0x00, 0x2c, 0xc5, 0x33, 0x25, 0x58, 0x90, 0x53, 0xcc, 0xc5, + 0xff, 0x0b, 0x3e, 0x62, 0xbe, 0x27, 0x5d, 0xc4, 0xd6, 0xdb, 0x47, 0x17, 0x06, 0x12, 0x68, 0x3a, 0x00, 0x3f, 0x5e, + 0x05, 0x74, 0x65, 0xfc, 0x3b, 0x2d, 0xeb, 0xb1, 0x3e, 0xfe, 0x53, 0x70, 0x9f, 0x7e, 0xa2, 0xf0, 0xd1, 0xe1, 0xb8, + 0x4a, 0x47, 0x3b, 0x4a, 0x41, 0x74, 0x74, 0xfb, 0x7c, 0xaa, 0xb2, 0xef, 0x2a, 0x20, 0xb7, 0x1c, 0xb5, 0xa7, 0x02, + 0xb0, 0xd8, 0xd2, 0x11, 0xf8, 0x34, 0xcb, 0x27, 0xe4, 0x7b, 0x3d, 0x15, 0x57, 0x97, 0x3a, 0x5d, 0x3c, 0x1b, 0x4f, + 0xe1, 0x7f, 0x20, 0xf6, 0xb0, 0x4c, 0x91, 0x1d, 0xbb, 0x2e, 0x7e, 0x10, 0x6f, 0x6b, 0x3b, 0xfa, 0x63, 0x07, 0x91, + 0x8e, 0x7b, 0x72, 0xa1, 0xbe, 0x84, 0x54, 0x72, 0xa1, 0x6e, 0x20, 0x76, 0xa1, 0xc6, 0x3b, 0x2e, 0x62, 0xad, 0xbf, + 0xad, 0x51, 0xb0, 0x12, 0x70, 0xa6, 0xbd, 0x05, 0x83, 0x0d, 0xac, 0x5b, 0x96, 0xc1, 0xdf, 0x70, 0x4d, 0x13, 0xb8, + 0x61, 0x91, 0xf5, 0xde, 0x60, 0x2b, 0xbd, 0x05, 0x47, 0xcb, 0xc4, 0xb9, 0x94, 0x24, 0x65, 0x8b, 0x8c, 0xab, 0x47, + 0x21, 0x55, 0xd3, 0xfd, 0xad, 0xa8, 0xef, 0x85, 0xc8, 0x83, 0x55, 0xca, 0xa2, 0x62, 0x05, 0x32, 0x7b, 0xf0, 0xaf, + 0x90, 0x91, 0xa3, 0x1c, 0x38, 0x0a, 0xfd, 0xa3, 0x09, 0x74, 0x9e, 0x3a, 0xd2, 0x79, 0x24, 0xd8, 0x4a, 0x3d, 0x14, + 0x56, 0x5e, 0x40, 0x74, 0xb0, 0x1d, 0x73, 0x2b, 0x4f, 0x42, 0xc5, 0xa6, 0x4c, 0xe4, 0x71, 0x50, 0x4b, 0xc0, 0x58, + 0x41, 0x30, 0x67, 0xb9, 0x74, 0x41, 0xaa, 0x1a, 0x3d, 0x2c, 0x32, 0xf7, 0x63, 0x41, 0xf9, 0x1f, 0xab, 0x9c, 0x70, + 0x7d, 0x19, 0x02, 0x1c, 0xed, 0x63, 0x10, 0x25, 0xc6, 0xfa, 0x45, 0x8b, 0x77, 0x32, 0x73, 0x36, 0xb5, 0xbd, 0x04, + 0x19, 0xdb, 0xe1, 0x57, 0x08, 0xad, 0x16, 0x8a, 0x2c, 0x1a, 0x2e, 0x98, 0x6e, 0x4f, 0x69, 0xd5, 0x3d, 0x6c, 0x78, + 0x52, 0x7a, 0xa8, 0xd4, 0xb7, 0x31, 0x81, 0x65, 0x95, 0x32, 0x7c, 0x3b, 0xa1, 0xea, 0xc4, 0xa0, 0x62, 0xdd, 0xb0, + 0x05, 0x1c, 0x62, 0x31, 0x69, 0xac, 0xb3, 0x01, 0x8f, 0x58, 0x02, 0xff, 0x6c, 0xf8, 0x98, 0x2d, 0x78, 0x34, 0xd9, + 0x5c, 0x2d, 0xfa, 0xfd, 0xd2, 0x0b, 0xbd, 0x7a, 0x96, 0x3d, 0x8e, 0xe6, 0xb3, 0x7c, 0xee, 0xa3, 0xe2, 0x62, 0x32, + 0x18, 0x6c, 0xfc, 0x6c, 0x38, 0x64, 0xc9, 0x70, 0x38, 0xc9, 0x1e, 0xc3, 0x6b, 0x8f, 0x79, 0xa4, 0x96, 0x54, 0x72, + 0x95, 0xc1, 0xfe, 0x3e, 0xe0, 0x91, 0xcf, 0x3a, 0x3f, 0x2d, 0x9b, 0x2e, 0xdd, 0xcf, 0xec, 0xb8, 0x0b, 0xdd, 0x01, + 0x36, 0xde, 0x36, 0xe8, 0xc8, 0xbf, 0xdd, 0x21, 0xa5, 0x6e, 0x32, 0x00, 0xbb, 0xd1, 0x00, 0x87, 0x4c, 0xf5, 0x52, + 0x64, 0xf5, 0x52, 0xa6, 0x7a, 0x49, 0x56, 0x2e, 0xc1, 0x42, 0x62, 0xaa, 0xdc, 0x46, 0x56, 0x6e, 0xd1, 0x70, 0x3d, + 0x1c, 0x6c, 0xad, 0xb8, 0x6c, 0x96, 0x70, 0x5f, 0x58, 0x51, 0xe0, 0xff, 0x2d, 0xbb, 0x61, 0x77, 0xf2, 0x18, 0x78, + 0x8b, 0x8e, 0x49, 0x70, 0x81, 0xb8, 0x63, 0xb7, 0x60, 0x87, 0x85, 0xbf, 0xe0, 0x3a, 0x39, 0x66, 0x3b, 0x7c, 0x14, + 0x7a, 0x05, 0xbb, 0xf5, 0x09, 0x68, 0x17, 0x6c, 0x0d, 0x90, 0x8d, 0x6d, 0xf1, 0xd1, 0xf2, 0x70, 0x78, 0xeb, 0xf9, + 0xec, 0x1e, 0x7f, 0x9c, 0x2f, 0x0f, 0x87, 0x9d, 0x67, 0xd4, 0x7b, 0xd7, 0x3c, 0x61, 0xef, 0x79, 0x32, 0xb9, 0xbe, + 0xe2, 0xf1, 0x64, 0x30, 0xb8, 0xf6, 0x6f, 0x78, 0x3d, 0xbb, 0x06, 0xed, 0xc0, 0xf9, 0x8d, 0xd4, 0x35, 0x7b, 0xb7, + 0x3c, 0xf3, 0x6e, 0x70, 0x6c, 0x6e, 0xe1, 0xe8, 0xed, 0xf7, 0xbd, 0x25, 0x8f, 0xbc, 0x5b, 0x52, 0x31, 0xad, 0xb8, + 0xe2, 0x78, 0xdb, 0xe2, 0x7e, 0xba, 0xe2, 0x21, 0x3c, 0xc2, 0xaa, 0x4c, 0xaf, 0x83, 0xf7, 0x3e, 0x5b, 0x69, 0x16, + 0xb8, 0x7b, 0xcc, 0xb1, 0x26, 0x3b, 0xa1, 0x99, 0xf8, 0x2b, 0xec, 0x9f, 0x6b, 0xd5, 0x3f, 0x34, 0xff, 0x4b, 0xdd, + 0x4f, 0xe0, 0xf6, 0x45, 0x16, 0x24, 0xf6, 0x9e, 0x5f, 0xb3, 0x3b, 0x6e, 0xd8, 0x66, 0xcf, 0x4c, 0xd9, 0x27, 0x4a, + 0x8d, 0x1f, 0x28, 0x75, 0x6d, 0x19, 0x56, 0x5a, 0x57, 0x3e, 0x04, 0x0e, 0x07, 0xe4, 0xa7, 0x25, 0xe2, 0x20, 0xb4, + 0x6e, 0xb2, 0x9a, 0x2b, 0xca, 0xb9, 0xd0, 0x86, 0x99, 0x97, 0x03, 0x8b, 0x59, 0x4a, 0xa1, 0xb1, 0x00, 0x40, 0x30, + 0x29, 0xb4, 0xf6, 0x5e, 0x06, 0x90, 0x13, 0x34, 0xfc, 0xb1, 0xb9, 0x2a, 0xcb, 0x5a, 0xb6, 0x24, 0x44, 0xd9, 0xae, + 0x87, 0x97, 0x08, 0x99, 0xd6, 0xef, 0x9f, 0x13, 0xc9, 0xda, 0xa4, 0xba, 0xaa, 0xd1, 0x12, 0x50, 0x91, 0x25, 0x60, + 0xe2, 0x57, 0x9a, 0x4f, 0x00, 0x9e, 0x74, 0x3c, 0xa8, 0x1e, 0xf3, 0x9a, 0x09, 0x22, 0xdb, 0xa8, 0xfc, 0x49, 0xf1, + 0x0c, 0xc9, 0x08, 0x8a, 0xc7, 0xb5, 0xca, 0x58, 0x18, 0xe6, 0x81, 0x02, 0xf2, 0xee, 0xdd, 0xa9, 0x6f, 0xed, 0x8f, + 0x1d, 0x7b, 0xb6, 0x56, 0xa1, 0x16, 0x6a, 0x0a, 0x97, 0x1c, 0xa2, 0x2b, 0xd0, 0x40, 0x11, 0xc9, 0x78, 0xf2, 0x7a, + 0x70, 0x39, 0x89, 0xae, 0xb8, 0x40, 0x67, 0x7c, 0x7d, 0xd3, 0x4d, 0x67, 0xd1, 0xe3, 0x6a, 0x3e, 0x21, 0x25, 0xd9, + 0xe1, 0x90, 0x8d, 0xaa, 0xba, 0x58, 0x4f, 0x43, 0xf9, 0xd3, 0x43, 0xf0, 0xf5, 0x82, 0x7a, 0x4d, 0x56, 0xa9, 0x7e, + 0x4c, 0x95, 0xf2, 0xa2, 0xe1, 0xa5, 0xff, 0xb8, 0x92, 0xfb, 0x1e, 0x90, 0xd6, 0xf2, 0x92, 0xcb, 0xf7, 0x23, 0xc4, + 0x18, 0xf1, 0x03, 0xaf, 0xe4, 0x11, 0x0b, 0xd5, 0x14, 0xae, 0x79, 0x84, 0x20, 0x6f, 0x99, 0x0e, 0xfe, 0xd6, 0x13, + 0xa7, 0xfb, 0x13, 0xa5, 0x5d, 0x7c, 0x61, 0x51, 0xf7, 0x1c, 0xe9, 0x06, 0xe4, 0x60, 0xc3, 0x74, 0x51, 0x90, 0x6d, + 0x4a, 0x23, 0x68, 0xa3, 0xe5, 0xc0, 0x86, 0x53, 0xa9, 0x0d, 0x67, 0xae, 0x21, 0xb8, 0xcf, 0xcf, 0xd3, 0xd1, 0x0d, + 0x7c, 0x48, 0x75, 0x7b, 0x89, 0x9f, 0x0f, 0x1b, 0x8e, 0x64, 0x76, 0xc4, 0x67, 0x36, 0x91, 0x74, 0x52, 0xe7, 0x0a, + 0xd8, 0xed, 0xec, 0x25, 0xc8, 0x11, 0x33, 0xf7, 0x15, 0xaa, 0x6f, 0xd1, 0x80, 0x2b, 0x63, 0xed, 0x6b, 0x92, 0xb1, + 0xf0, 0xaa, 0x9c, 0x86, 0x03, 0x80, 0xa1, 0xcb, 0xe8, 0x6b, 0x8b, 0x4d, 0x96, 0xfd, 0x52, 0x40, 0x10, 0x44, 0x49, + 0x3c, 0x3e, 0xe0, 0x7d, 0x59, 0x0d, 0x35, 0x4a, 0x3e, 0x96, 0x9d, 0xc0, 0xd7, 0x4b, 0xf4, 0x77, 0x63, 0x2e, 0x31, + 0xe0, 0xcb, 0xaa, 0x2d, 0x28, 0x9c, 0xe7, 0x87, 0xc3, 0x79, 0x3e, 0x32, 0x9e, 0x65, 0xa0, 0x5a, 0x99, 0xd6, 0xc1, + 0xc6, 0xcc, 0x17, 0x0b, 0x7f, 0xb1, 0x73, 0x12, 0x11, 0x05, 0x81, 0x1d, 0x09, 0x0f, 0x22, 0xf5, 0xfb, 0xca, 0xd3, + 0x9d, 0xea, 0xb3, 0xfd, 0x8d, 0x4d, 0xa4, 0x17, 0x94, 0x4c, 0x3e, 0x09, 0xf6, 0xaa, 0xbf, 0x83, 0xb0, 0x21, 0xbc, + 0x79, 0xd5, 0xeb, 0x2c, 0x53, 0xb3, 0x12, 0x24, 0xcc, 0x98, 0x23, 0x78, 0x1c, 0x76, 0x1a, 0xdb, 0xf0, 0xd8, 0xc2, + 0x6a, 0xf4, 0xd6, 0x6c, 0xc9, 0x56, 0xec, 0x56, 0xd5, 0xe9, 0x86, 0x87, 0xd3, 0xe1, 0x65, 0x80, 0xab, 0x6f, 0x7d, + 0xce, 0xf9, 0x92, 0x4e, 0xb0, 0xf5, 0x80, 0x47, 0x13, 0x31, 0x5b, 0x3f, 0x8e, 0xd4, 0xe2, 0x59, 0x0f, 0xf9, 0x0d, + 0xad, 0x3f, 0x31, 0x5b, 0x9a, 0xe4, 0xe5, 0x80, 0xdf, 0x4c, 0xd6, 0x8f, 0x23, 0x78, 0xf5, 0x31, 0x58, 0x31, 0x32, + 0x67, 0x96, 0xad, 0x1f, 0x47, 0x38, 0x66, 0xcb, 0xc7, 0x11, 0x8d, 0xda, 0x4a, 0xee, 0x4b, 0xb7, 0x0d, 0x08, 0x2b, + 0xb7, 0x2c, 0x86, 0xd7, 0x40, 0x3c, 0xd3, 0x46, 0xd2, 0xb5, 0x34, 0xf4, 0xc6, 0x3c, 0x9c, 0xc6, 0xc1, 0x9a, 0x5a, + 0x21, 0xcf, 0x0c, 0x31, 0x8b, 0x1f, 0x47, 0x73, 0xb6, 0xc2, 0x8a, 0x6c, 0x78, 0x3c, 0xb8, 0x9c, 0x6c, 0xae, 0xf8, + 0x1a, 0xc8, 0xcf, 0x26, 0x1b, 0xb3, 0x45, 0xdd, 0x72, 0x31, 0xdb, 0x3c, 0x8e, 0xe6, 0x93, 0x15, 0xf4, 0xac, 0x3d, + 0x60, 0xde, 0x6b, 0x10, 0xa1, 0x24, 0xa4, 0xa6, 0xdc, 0xf4, 0x7a, 0x6c, 0x3d, 0x0e, 0x96, 0x6c, 0x7d, 0x19, 0xdc, + 0xb2, 0xf5, 0x18, 0x88, 0x38, 0xa8, 0xdf, 0xbd, 0x0d, 0x2c, 0xbe, 0x88, 0xad, 0x2f, 0x4d, 0xda, 0xe6, 0x71, 0xc4, + 0xdc, 0xc1, 0x69, 0xe0, 0x82, 0xb5, 0xc8, 0xbc, 0x15, 0x83, 0x4b, 0xc8, 0xc2, 0x8b, 0xd9, 0x66, 0x78, 0xc9, 0xd6, + 0x23, 0x9c, 0xea, 0x89, 0xcf, 0x96, 0xfc, 0x96, 0x25, 0x7c, 0xd5, 0xc4, 0x57, 0x1b, 0xd0, 0x88, 0x1e, 0x65, 0xd0, + 0x57, 0x50, 0x33, 0x73, 0xde, 0x5b, 0x18, 0x95, 0xfb, 0x16, 0x1c, 0x50, 0x90, 0xb6, 0x01, 0x82, 0x24, 0x9e, 0xdd, + 0xcb, 0x70, 0x7d, 0x2d, 0x85, 0x01, 0x37, 0x81, 0x19, 0x30, 0x30, 0xfd, 0x0c, 0x7e, 0x58, 0xe9, 0x12, 0x21, 0xce, + 0x7e, 0x4a, 0x49, 0x32, 0xcf, 0xdf, 0x8b, 0x34, 0x77, 0x0b, 0xd7, 0x29, 0xcc, 0x8a, 0x02, 0xd5, 0x4f, 0x49, 0x69, + 0x60, 0xa1, 0x12, 0x99, 0x4a, 0xc1, 0x2f, 0x9b, 0xf3, 0x28, 0x3b, 0x46, 0xe7, 0x3a, 0xbf, 0x9c, 0x38, 0xa7, 0x93, + 0xbe, 0xff, 0xc0, 0x31, 0x6c, 0x21, 0x03, 0x17, 0xfe, 0xd4, 0x13, 0xc6, 0xa9, 0x15, 0x88, 0xa9, 0xe4, 0xd9, 0x53, + 0xf8, 0x4c, 0x68, 0x75, 0x74, 0xe1, 0xfb, 0x41, 0xa1, 0x4d, 0xd2, 0x2d, 0x48, 0x52, 0xf0, 0x14, 0x3d, 0xe7, 0xbc, + 0x0d, 0x54, 0x8a, 0x11, 0x2d, 0x88, 0xb4, 0xb5, 0xce, 0x1c, 0xa4, 0x2d, 0xcd, 0x77, 0x4d, 0xfc, 0x1c, 0x16, 0x70, + 0x11, 0x2d, 0x6c, 0x0d, 0x8f, 0xaa, 0x58, 0xb9, 0x37, 0x79, 0x8e, 0x70, 0x46, 0x97, 0x32, 0x01, 0x70, 0xbd, 0x5f, + 0x85, 0xb5, 0xc2, 0x2b, 0x6a, 0x6e, 0xf2, 0xa2, 0xa6, 0x4f, 0xb6, 0xc0, 0x7d, 0x2c, 0x4a, 0x14, 0x38, 0x6b, 0xc1, + 0x80, 0xad, 0xb0, 0x64, 0x27, 0x85, 0x4d, 0xd1, 0x12, 0x7a, 0x7b, 0xfc, 0x74, 0x50, 0x33, 0x19, 0x40, 0x13, 0x40, + 0xe3, 0xf1, 0x2f, 0x00, 0x35, 0xbd, 0xae, 0xc5, 0xba, 0x0a, 0x4a, 0xa5, 0xdc, 0x84, 0x9f, 0x81, 0x61, 0x86, 0x1f, + 0x0a, 0xb9, 0x4d, 0x94, 0xc8, 0xf9, 0x71, 0x53, 0x8a, 0x45, 0x29, 0xaa, 0xa4, 0xdd, 0x50, 0xf0, 0x88, 0x70, 0x1b, + 0x34, 0x66, 0x6e, 0x4f, 0x74, 0xd1, 0x8a, 0x50, 0x8e, 0xcd, 0x3a, 0x46, 0x1a, 0x65, 0x76, 0xb2, 0xeb, 0x64, 0xa1, + 0xfd, 0xbe, 0xca, 0x21, 0xeb, 0x80, 0x35, 0x92, 0xaf, 0xd7, 0x1c, 0xba, 0x6d, 0x94, 0x17, 0xf7, 0x9e, 0xaf, 0xe0, + 0x34, 0xc7, 0x13, 0xbb, 0xeb, 0x75, 0xa7, 0x48, 0xc4, 0x2b, 0x9c, 0x54, 0xf9, 0x48, 0x16, 0x8e, 0x3b, 0x77, 0x5a, + 0x8b, 0x55, 0xe5, 0xb2, 0x9e, 0x5a, 0x1c, 0x11, 0xf8, 0x54, 0x1e, 0xed, 0x85, 0xb6, 0x45, 0xb1, 0x10, 0x46, 0x8f, + 0x4e, 0xf8, 0x49, 0x09, 0xac, 0xaf, 0xc3, 0x61, 0xe9, 0x47, 0x1c, 0xfd, 0x4e, 0xa3, 0xd1, 0x0d, 0x21, 0x0d, 0x4f, + 0xbd, 0x68, 0x74, 0x53, 0x17, 0x75, 0x98, 0x3d, 0xcb, 0xf5, 0x40, 0x61, 0x18, 0x81, 0xfa, 0xc1, 0x55, 0x06, 0x9f, + 0x45, 0x88, 0x9a, 0x07, 0xa6, 0xd9, 0x10, 0x8e, 0xba, 0xc0, 0x43, 0x2b, 0x68, 0x31, 0x33, 0x1f, 0x85, 0x18, 0x3e, + 0xa4, 0x8b, 0xf3, 0x27, 0x64, 0xe5, 0x03, 0xec, 0x0e, 0xdd, 0x85, 0x72, 0xce, 0x54, 0x0c, 0xf0, 0xa3, 0x80, 0x7c, + 0x94, 0x80, 0x9b, 0x01, 0xb2, 0x47, 0x96, 0x00, 0x62, 0xc5, 0xe8, 0x68, 0xf2, 0xb9, 0xef, 0x45, 0x0a, 0xde, 0xd9, + 0x67, 0xb9, 0x9a, 0x30, 0x14, 0x3e, 0x31, 0xd0, 0xcd, 0x6f, 0xfc, 0xf6, 0xbc, 0x05, 0x23, 0xbb, 0x24, 0xc5, 0x6b, + 0xcd, 0x70, 0xbf, 0x01, 0xb7, 0x23, 0xa0, 0xac, 0xa9, 0x8e, 0x49, 0xb6, 0x69, 0x88, 0x64, 0xc0, 0x8c, 0x18, 0x11, + 0x54, 0x96, 0x0b, 0xff, 0xbb, 0x97, 0x45, 0x81, 0x03, 0xb8, 0x9a, 0xc9, 0xe0, 0xb5, 0x0b, 0xa3, 0x02, 0xe0, 0x9c, + 0x86, 0x4e, 0x69, 0xaf, 0xaa, 0x0e, 0xc9, 0xaa, 0xf9, 0xc1, 0x6c, 0xde, 0x34, 0x4c, 0x8c, 0x08, 0xa2, 0x8b, 0x70, + 0x82, 0xe9, 0x15, 0xe9, 0x6b, 0x25, 0xa7, 0xa3, 0x55, 0x47, 0x6b, 0x89, 0x89, 0xb9, 0xa2, 0xf8, 0x6b, 0xc0, 0xe3, + 0x06, 0xaf, 0x4e, 0xd2, 0x74, 0xa2, 0x7a, 0xf4, 0xf8, 0x75, 0x9a, 0x4e, 0x4a, 0xdc, 0x15, 0x7e, 0x03, 0x2e, 0x9a, + 0x6d, 0x3e, 0xf4, 0xe3, 0x17, 0x14, 0x71, 0x51, 0x83, 0x2b, 0xef, 0x54, 0x5f, 0xa9, 0x3e, 0x82, 0x5a, 0x78, 0x62, + 0x64, 0x2d, 0x3c, 0xb9, 0x64, 0xad, 0x05, 0xc1, 0xcc, 0xe6, 0xc0, 0x85, 0xfc, 0x4a, 0x29, 0xe2, 0x4d, 0x24, 0xd4, + 0x62, 0xd0, 0x7a, 0xcc, 0x9c, 0x55, 0xa3, 0x1b, 0x95, 0x19, 0xa1, 0x7d, 0x5b, 0x8b, 0xce, 0x6f, 0xe4, 0xa7, 0x3c, + 0xb5, 0x2f, 0xdb, 0xe3, 0x7c, 0xbc, 0x47, 0x77, 0xd5, 0x59, 0x66, 0x52, 0xc6, 0x27, 0xb3, 0x04, 0x85, 0xbb, 0x04, + 0x1b, 0x90, 0x64, 0xbf, 0xd5, 0x01, 0x32, 0x6a, 0xaf, 0xfd, 0xae, 0xb3, 0x7c, 0x75, 0xb3, 0x35, 0x14, 0x95, 0x5a, + 0x49, 0x8a, 0x83, 0x0c, 0xd7, 0x6d, 0xe5, 0xc3, 0xc5, 0x05, 0xf4, 0x8c, 0x91, 0xc8, 0x3c, 0x7f, 0x22, 0x5f, 0x82, + 0x73, 0xc6, 0x59, 0x21, 0x30, 0x61, 0xac, 0xde, 0xb5, 0x96, 0x4a, 0x43, 0x8a, 0xb1, 0xa3, 0x51, 0x96, 0x55, 0x96, + 0x2e, 0xb3, 0xb5, 0x84, 0x2d, 0xab, 0xc8, 0x2d, 0x6c, 0x9d, 0xc9, 0x6a, 0x7e, 0xa8, 0xb8, 0x83, 0xf2, 0xcd, 0x96, + 0x19, 0xdf, 0x4b, 0x64, 0xef, 0x36, 0x50, 0xc2, 0xb3, 0xd1, 0x7f, 0x20, 0xfd, 0x36, 0xc3, 0x38, 0xe5, 0xb6, 0x92, + 0x16, 0xe0, 0xf4, 0x0f, 0x87, 0x0f, 0x15, 0x06, 0x0d, 0x8e, 0x30, 0x8e, 0xac, 0xdf, 0xbf, 0xa9, 0xbc, 0x1a, 0x13, + 0x75, 0x7c, 0x56, 0xbf, 0x5f, 0xd1, 0xc3, 0x69, 0x35, 0x5a, 0xa5, 0x5b, 0x64, 0x27, 0xb4, 0xb1, 0xf2, 0x83, 0x5a, + 0x01, 0xb3, 0xb7, 0x3e, 0x9f, 0x0e, 0x40, 0xc7, 0x02, 0x24, 0x9a, 0xcd, 0x44, 0x62, 0x4e, 0xba, 0x27, 0xe1, 0xf1, + 0x81, 0x05, 0x0e, 0x30, 0x15, 0xff, 0xa7, 0xf0, 0x66, 0x60, 0x83, 0x46, 0x89, 0xbe, 0x46, 0x57, 0xb5, 0xb9, 0xd1, + 0xf1, 0xd2, 0x53, 0x48, 0x64, 0x05, 0xab, 0xe6, 0xbe, 0xdc, 0xc0, 0x69, 0x0f, 0x35, 0x87, 0xca, 0x02, 0xfc, 0xed, + 0x17, 0x60, 0xf0, 0xc8, 0xa0, 0xb0, 0xdd, 0x5a, 0x68, 0x6f, 0xcc, 0x52, 0x0d, 0x15, 0xe1, 0xa0, 0xf3, 0x95, 0x98, + 0xd5, 0x23, 0xfa, 0x7b, 0x7e, 0x38, 0xac, 0x08, 0x0c, 0x38, 0x2c, 0x65, 0x26, 0x5a, 0x28, 0x96, 0xd6, 0xd9, 0x8c, + 0xea, 0xc0, 0x03, 0x13, 0x73, 0x16, 0xee, 0x00, 0xb4, 0x49, 0xad, 0x02, 0xbd, 0x8a, 0xe8, 0x27, 0xee, 0xd7, 0xf6, + 0xeb, 0xf5, 0xc8, 0x2c, 0x1d, 0xb9, 0x31, 0x16, 0x00, 0x1c, 0x78, 0x5e, 0x93, 0x3c, 0x27, 0x5f, 0x43, 0xbb, 0x27, + 0x17, 0xf2, 0x27, 0x28, 0x5b, 0x78, 0xae, 0x9a, 0x56, 0x16, 0x2b, 0xae, 0xaa, 0x57, 0x17, 0xbc, 0x32, 0x99, 0x56, + 0x69, 0x25, 0x2a, 0x25, 0x18, 0x50, 0x97, 0x78, 0xad, 0x69, 0x46, 0xa9, 0x8d, 0x3a, 0x13, 0x35, 0x60, 0x83, 0xfd, + 0x54, 0x6d, 0x74, 0x72, 0x2e, 0x9f, 0x5f, 0x1a, 0x87, 0x4f, 0xbb, 0x7a, 0x33, 0x53, 0x39, 0xf0, 0xd7, 0xca, 0x87, + 0x56, 0x8f, 0x81, 0x0e, 0xc8, 0xe9, 0x8f, 0x61, 0x31, 0xb1, 0x3b, 0x34, 0x6f, 0x77, 0x97, 0xd5, 0x45, 0x7a, 0xa7, + 0x29, 0x99, 0xd5, 0x5b, 0x3e, 0xb3, 0x7a, 0x74, 0xc0, 0x8b, 0x87, 0x7a, 0xaf, 0x30, 0x93, 0x08, 0x2e, 0x86, 0x6a, + 0x12, 0xd9, 0x1d, 0x68, 0xcd, 0xa3, 0x8a, 0x09, 0xf0, 0x83, 0x52, 0x6b, 0x7a, 0x6f, 0x77, 0x85, 0x3a, 0xa5, 0xf0, + 0xb8, 0xb5, 0xe4, 0x07, 0xe6, 0x4e, 0xbb, 0xd6, 0xf9, 0x78, 0x7e, 0xe9, 0xfb, 0x8d, 0x3c, 0xa1, 0xcd, 0xce, 0xe4, + 0xf4, 0x4f, 0xde, 0xea, 0x1f, 0xa6, 0xfa, 0x16, 0xba, 0x13, 0xf4, 0x19, 0xba, 0xaa, 0xba, 0x2b, 0xb1, 0x85, 0xa1, + 0x9e, 0x58, 0xe4, 0x85, 0x3c, 0x69, 0x8d, 0x1d, 0x07, 0x7b, 0x03, 0x9c, 0xf8, 0xe5, 0xe1, 0x20, 0xae, 0x72, 0x9f, + 0x9d, 0x77, 0x8d, 0xac, 0x1c, 0xc0, 0x0a, 0xa2, 0x60, 0xdc, 0x9a, 0x8f, 0x6d, 0x90, 0x2e, 0x71, 0x35, 0x3e, 0x7e, + 0x43, 0xb1, 0x4c, 0x36, 0x11, 0x17, 0x17, 0xf9, 0xe3, 0xa7, 0x40, 0x5a, 0xd6, 0xef, 0x47, 0xcf, 0x2e, 0xa7, 0x4f, + 0x87, 0x51, 0x00, 0x8e, 0x5d, 0xf6, 0xf2, 0x32, 0xe6, 0xab, 0x4b, 0x66, 0x99, 0xc2, 0x22, 0xdf, 0x0c, 0xa8, 0x2e, + 0x59, 0x2d, 0x5d, 0xaf, 0x00, 0x4b, 0x97, 0xdf, 0xdc, 0x87, 0xa9, 0x01, 0x8d, 0xac, 0xb9, 0x3b, 0xcd, 0xb5, 0x40, + 0xa9, 0xe7, 0xfd, 0xcc, 0x90, 0xaf, 0xcb, 0xa0, 0x2b, 0x48, 0xf7, 0x3c, 0x22, 0xbd, 0xdc, 0x4b, 0xa7, 0xfb, 0x7d, + 0x29, 0xc0, 0x52, 0x5f, 0x8a, 0x2f, 0xa0, 0xb0, 0x68, 0x7c, 0x23, 0x40, 0x5b, 0x43, 0x35, 0xed, 0x95, 0xa2, 0xea, + 0x05, 0xbd, 0x52, 0x7c, 0xe9, 0xe9, 0xa1, 0x32, 0x5f, 0x96, 0x8e, 0xfe, 0x27, 0xd4, 0x5c, 0x70, 0x42, 0xcc, 0xc4, + 0x1c, 0x40, 0x25, 0x68, 0xe3, 0xbb, 0x3d, 0xda, 0xf8, 0x54, 0xaf, 0xe2, 0xa6, 0xcf, 0x6b, 0x6b, 0x99, 0x13, 0xc2, + 0xa6, 0x7b, 0x09, 0x50, 0x91, 0x57, 0xc2, 0x23, 0x58, 0x7e, 0xf9, 0x43, 0x9e, 0xae, 0x10, 0xad, 0xe3, 0x9e, 0x65, + 0x2e, 0x8d, 0xfd, 0x6b, 0x83, 0xe9, 0xeb, 0xdb, 0x6d, 0x91, 0x9f, 0x9a, 0x98, 0xb0, 0x1e, 0x2b, 0xfa, 0xe6, 0x5d, + 0xb8, 0x12, 0x28, 0x70, 0x28, 0x91, 0xd8, 0xa6, 0x0a, 0x45, 0x3c, 0x48, 0xfa, 0x74, 0xd1, 0xfa, 0x34, 0xc0, 0xd4, + 0x5a, 0x0e, 0xcc, 0x21, 0x5c, 0xc5, 0x85, 0x8f, 0x9e, 0xbe, 0xc5, 0x2c, 0x9c, 0x4f, 0xbc, 0x8f, 0x5e, 0x31, 0x32, + 0x1f, 0xf7, 0x51, 0xa9, 0xa4, 0x7f, 0x1e, 0x0e, 0xb3, 0x6a, 0xee, 0x3b, 0xf4, 0x91, 0x1e, 0xaa, 0x5c, 0x50, 0xf6, + 0xc6, 0x98, 0x44, 0xa0, 0x34, 0xc6, 0xfb, 0x38, 0x38, 0xce, 0xfb, 0x34, 0x80, 0xd4, 0x3e, 0xf1, 0x9e, 0x94, 0x1c, + 0x9e, 0x73, 0xcc, 0x09, 0xa5, 0x15, 0x01, 0x13, 0x7a, 0x86, 0x72, 0xdd, 0x29, 0x05, 0x93, 0x1c, 0x12, 0x0c, 0x7f, + 0xd5, 0xbc, 0x89, 0x15, 0x08, 0xbb, 0x66, 0x5e, 0x8d, 0x1e, 0x55, 0x49, 0x58, 0x0a, 0x38, 0x2a, 0x33, 0xcf, 0xb0, + 0x37, 0x3c, 0x32, 0x8c, 0x1c, 0x2c, 0xf7, 0x47, 0x75, 0x22, 0x72, 0x8f, 0x2e, 0x30, 0x2a, 0x0b, 0xcf, 0x1b, 0xba, + 0xd2, 0xa0, 0x92, 0xec, 0xf8, 0x2b, 0xae, 0x01, 0xb5, 0x35, 0x46, 0x0c, 0x05, 0x8c, 0x82, 0xd7, 0xf6, 0x87, 0x90, + 0x45, 0xd9, 0xfa, 0x0d, 0x8e, 0xf9, 0xac, 0xe4, 0xae, 0x77, 0x38, 0x0b, 0x2d, 0x21, 0x4f, 0xee, 0x18, 0xa4, 0x69, + 0x2c, 0x8d, 0x80, 0x13, 0x91, 0x6c, 0x63, 0x29, 0x1c, 0x01, 0x04, 0x04, 0xba, 0x29, 0x33, 0x8c, 0xe9, 0x60, 0xe4, + 0x79, 0xd4, 0x33, 0xde, 0xab, 0xf0, 0x14, 0xd2, 0x64, 0xfb, 0x7a, 0xfe, 0xde, 0x08, 0xb2, 0x72, 0xcb, 0x39, 0x1e, + 0x16, 0xdf, 0x38, 0xfb, 0x2a, 0x27, 0x4f, 0x31, 0xcb, 0x48, 0xef, 0x14, 0xf3, 0x02, 0xfe, 0x54, 0x96, 0xfa, 0x1c, + 0xa5, 0xb7, 0xcc, 0x27, 0xab, 0x48, 0xba, 0xf0, 0x36, 0xfd, 0x7e, 0x3c, 0x52, 0x87, 0x9a, 0xbf, 0x8f, 0x47, 0xf2, + 0x0c, 0xdb, 0xb0, 0x84, 0x85, 0x56, 0xc1, 0x18, 0x40, 0x12, 0x1b, 0x11, 0x0d, 0x46, 0x7b, 0x73, 0x38, 0x9c, 0x6f, + 0xcc, 0x59, 0xb2, 0x07, 0xd7, 0x57, 0x9e, 0x98, 0x77, 0xe0, 0xcb, 0x3c, 0x26, 0x88, 0xd8, 0xcc, 0xdb, 0xb0, 0x1a, + 0x3c, 0xd8, 0xc1, 0xf5, 0x11, 0x5b, 0x14, 0x6b, 0x1d, 0x4b, 0x65, 0x1d, 0x9c, 0xd6, 0xb1, 0x69, 0x46, 0x4a, 0x91, + 0x7d, 0x8e, 0xfd, 0xbd, 0x1b, 0x5c, 0x5d, 0x1b, 0x83, 0x5a, 0xe3, 0x0e, 0x73, 0xe7, 0x54, 0x40, 0x3d, 0xa6, 0x2b, + 0xa8, 0x9e, 0x55, 0xe4, 0xcb, 0x6f, 0xed, 0x1c, 0x10, 0x34, 0x02, 0x81, 0x8b, 0x06, 0x4a, 0xa6, 0x4b, 0x39, 0xef, + 0x02, 0x42, 0x7c, 0x97, 0x82, 0x3e, 0x9d, 0xc1, 0x26, 0x36, 0x9f, 0x40, 0x2c, 0x9a, 0xee, 0x73, 0xad, 0x99, 0x2f, + 0x46, 0xb4, 0x33, 0xeb, 0x6e, 0x91, 0x5b, 0x2d, 0x44, 0x32, 0x7a, 0xb6, 0x99, 0x70, 0xd7, 0xa1, 0x9c, 0x91, 0x80, + 0x09, 0x5a, 0x5b, 0x29, 0xf9, 0x5c, 0xf7, 0x3a, 0x41, 0x7b, 0x20, 0x69, 0xdd, 0xbf, 0x59, 0x74, 0x46, 0xc9, 0xc9, + 0xf5, 0x26, 0x67, 0x90, 0x82, 0x05, 0xdb, 0xcb, 0x9c, 0x70, 0x03, 0x7c, 0x64, 0xb3, 0xe4, 0x34, 0x0d, 0xf2, 0x58, + 0x18, 0xa4, 0x8f, 0x36, 0xbf, 0x2c, 0xa0, 0x43, 0xc9, 0xa2, 0x11, 0xe2, 0x01, 0x76, 0x0e, 0xc9, 0x55, 0x81, 0xba, + 0x69, 0xa0, 0x2b, 0x57, 0xce, 0x14, 0x53, 0xe0, 0x42, 0x28, 0x88, 0xda, 0xd1, 0x49, 0x54, 0xce, 0xfb, 0xa4, 0xba, + 0xcc, 0xa7, 0x85, 0x34, 0x0d, 0xe4, 0xd3, 0xca, 0x31, 0x0f, 0x6c, 0x6d, 0xe3, 0x9a, 0xc0, 0x40, 0xa7, 0xf6, 0xb5, + 0x28, 0xe7, 0x58, 0x45, 0xf4, 0x3e, 0x7f, 0x54, 0xd9, 0xd3, 0x07, 0x11, 0x36, 0x2a, 0xd0, 0x58, 0x4a, 0x8c, 0x8d, + 0x1c, 0xff, 0x96, 0x28, 0x1b, 0x32, 0x04, 0x84, 0x90, 0x36, 0x72, 0xfa, 0x61, 0x7d, 0xf9, 0x2e, 0xd3, 0xfe, 0x9f, + 0x24, 0x7e, 0x1b, 0xec, 0xe5, 0xd4, 0x9f, 0x7a, 0xc4, 0xe3, 0xb5, 0x46, 0x8f, 0x29, 0xe9, 0x36, 0xc8, 0x53, 0xe5, + 0x29, 0x48, 0x26, 0x8c, 0x05, 0x04, 0x8b, 0x72, 0xc1, 0x73, 0x5e, 0x71, 0x09, 0xf7, 0x51, 0xcb, 0x8a, 0x08, 0x55, + 0x89, 0x9c, 0x3e, 0x5f, 0x01, 0xcf, 0x04, 0x04, 0x3a, 0xc6, 0x48, 0xa3, 0x0a, 0xbe, 0x04, 0xc6, 0x3a, 0x50, 0x76, + 0x9a, 0x91, 0xe0, 0xb2, 0x7b, 0x8d, 0x44, 0xa9, 0xaf, 0x48, 0x49, 0xfa, 0x56, 0xd4, 0x78, 0x25, 0x56, 0x11, 0x09, + 0x64, 0xa8, 0x21, 0x62, 0x55, 0x3d, 0x75, 0xaf, 0x8a, 0xc9, 0x60, 0x50, 0xf9, 0x72, 0x7a, 0xe2, 0x0d, 0x0d, 0x95, + 0x77, 0x5d, 0xd1, 0x4e, 0xcf, 0xb5, 0x52, 0xde, 0x42, 0x5a, 0x82, 0xa6, 0x61, 0xa4, 0x39, 0x94, 0xba, 0x92, 0xee, + 0xc6, 0x20, 0xbe, 0x64, 0xa2, 0x67, 0x3b, 0xb5, 0xa3, 0xb4, 0x25, 0xed, 0x21, 0xa4, 0xe7, 0x2e, 0xf9, 0x98, 0x85, + 0x5c, 0xdd, 0x29, 0x27, 0xe5, 0x55, 0x88, 0x4e, 0xee, 0x7b, 0x0c, 0x89, 0x40, 0x9f, 0x73, 0x0c, 0xeb, 0xa2, 0xa1, + 0xce, 0x61, 0x85, 0x98, 0x2d, 0x94, 0x30, 0x5f, 0x32, 0x9e, 0x4a, 0x06, 0x0d, 0x80, 0x0c, 0xf8, 0xe2, 0x65, 0x60, + 0xf9, 0x2b, 0x88, 0x1f, 0x6d, 0x7c, 0x38, 0xfc, 0x55, 0x53, 0x88, 0xed, 0x5f, 0xb0, 0x19, 0xc2, 0xa3, 0x7a, 0xc0, + 0x33, 0xdf, 0xc4, 0x09, 0x5a, 0x01, 0x49, 0x99, 0x1d, 0x4d, 0x64, 0xaf, 0x7a, 0x08, 0xa7, 0xb2, 0x02, 0x75, 0x94, + 0x75, 0x56, 0xc2, 0x8f, 0x30, 0xd5, 0xad, 0xc4, 0x5a, 0xa0, 0xcd, 0xd5, 0x8a, 0xb5, 0x00, 0x0e, 0xfc, 0x1c, 0x82, + 0x27, 0xf2, 0x39, 0xb8, 0x18, 0x14, 0xe0, 0x73, 0x00, 0xbc, 0xc8, 0x5d, 0x78, 0x30, 0x0f, 0x2c, 0xab, 0x11, 0x86, + 0xa3, 0x8a, 0x58, 0xbf, 0x66, 0x3b, 0xf2, 0x81, 0xdb, 0x31, 0x3e, 0xd7, 0x1e, 0x4b, 0x96, 0x83, 0x51, 0xe6, 0x5e, + 0x2d, 0xd1, 0xf3, 0x26, 0x8d, 0x9b, 0xd1, 0xa3, 0x7d, 0x2d, 0xff, 0x17, 0xf4, 0x32, 0xe8, 0x6f, 0xe1, 0x96, 0xd7, + 0xfc, 0x61, 0xb9, 0x70, 0x9a, 0x5e, 0x41, 0xa4, 0x8c, 0x1a, 0x91, 0x31, 0x84, 0x4d, 0xaa, 0x9b, 0xdb, 0xa4, 0xba, + 0x10, 0xf0, 0x74, 0x44, 0xaa, 0x6b, 0x21, 0x6d, 0xe4, 0xd3, 0x3a, 0x90, 0xb1, 0x48, 0xef, 0x7e, 0xfc, 0xdb, 0xf3, + 0x4f, 0x6f, 0x7e, 0xfb, 0xf1, 0xe6, 0xcd, 0xbb, 0xd7, 0x6f, 0xde, 0xbd, 0xf9, 0xf4, 0x3b, 0x41, 0x78, 0x4c, 0x85, + 0xca, 0xf0, 0xe1, 0xfd, 0xf5, 0x1b, 0x27, 0x83, 0xed, 0xcd, 0x90, 0xb5, 0x6f, 0xe4, 0x60, 0x08, 0x44, 0x36, 0x08, + 0x19, 0x64, 0xa7, 0x64, 0x8e, 0x99, 0x98, 0x63, 0xec, 0x9d, 0xc0, 0x64, 0x0b, 0x92, 0xc3, 0x32, 0x2f, 0x19, 0x91, + 0xab, 0x42, 0xeb, 0x07, 0xb4, 0xe0, 0x2d, 0xb8, 0xc8, 0xa4, 0xf9, 0xf2, 0x37, 0x82, 0xd8, 0xa7, 0x95, 0x94, 0xfb, + 0x6a, 0x5b, 0xf3, 0x7c, 0x7b, 0xbf, 0x97, 0x70, 0xfe, 0x73, 0x69, 0x44, 0x2d, 0xc0, 0x01, 0xf8, 0x1c, 0xfe, 0xb8, + 0xd2, 0x96, 0x34, 0x99, 0x45, 0xfb, 0x19, 0x43, 0xd0, 0xa5, 0x81, 0x34, 0xb1, 0x47, 0x5e, 0xea, 0x93, 0x85, 0x04, + 0xee, 0x88, 0xe1, 0xd3, 0x8a, 0xa0, 0x57, 0x8c, 0x28, 0x2e, 0xb9, 0x42, 0xa5, 0x94, 0xfc, 0x1b, 0x65, 0x17, 0x15, + 0x72, 0x56, 0xb0, 0x3b, 0x45, 0x8e, 0x8c, 0x1f, 0x04, 0x13, 0x5f, 0x0e, 0xee, 0xbf, 0xc4, 0x3b, 0x9c, 0x29, 0x8e, + 0xe4, 0x84, 0xff, 0x99, 0x61, 0x60, 0x7f, 0x0e, 0x3e, 0xaf, 0x0e, 0xf3, 0xf2, 0x46, 0x9f, 0x72, 0x0b, 0x3e, 0x9e, + 0x2c, 0xae, 0xc0, 0x60, 0xbf, 0x50, 0xcd, 0x5d, 0xf3, 0x7a, 0xb6, 0x98, 0xb3, 0xfd, 0x2c, 0x9a, 0x07, 0x4b, 0x36, + 0xcb, 0xe6, 0xc1, 0xaa, 0xe1, 0x6b, 0x76, 0xcb, 0xd7, 0x56, 0xd5, 0xd6, 0x76, 0xd5, 0x26, 0x1b, 0x7e, 0x0b, 0x12, + 0xc2, 0xdb, 0xcc, 0x03, 0xde, 0xe3, 0xa5, 0xcf, 0x36, 0x20, 0xd1, 0xae, 0xd8, 0x06, 0x2e, 0x62, 0x6b, 0xfe, 0xa6, + 0xf2, 0x36, 0xac, 0x64, 0xe7, 0x63, 0x96, 0xe3, 0xfc, 0xf3, 0xe1, 0x01, 0xed, 0x85, 0xfa, 0xd9, 0xa5, 0x7a, 0x36, + 0x51, 0x76, 0xb3, 0xcd, 0xe8, 0xe6, 0x2e, 0xad, 0x36, 0x61, 0x86, 0x9e, 0xe5, 0xf0, 0xd1, 0x56, 0x0a, 0x7e, 0xfa, + 0x06, 0xbf, 0x64, 0x4d, 0x9c, 0x7f, 0xa6, 0x6d, 0xbb, 0x2a, 0xb1, 0x15, 0xb4, 0x28, 0xb2, 0x5a, 0xe1, 0x81, 0x39, + 0x7f, 0x06, 0x0b, 0x18, 0x7b, 0x8e, 0x73, 0x5e, 0xfb, 0x23, 0x64, 0xbc, 0x77, 0x00, 0xd0, 0x32, 0xc7, 0x01, 0x1e, + 0xb1, 0x62, 0x14, 0x0d, 0xde, 0xf9, 0xa5, 0xb2, 0x5a, 0x69, 0x4e, 0x42, 0xdb, 0x88, 0x55, 0xcb, 0x91, 0xaa, 0x19, + 0x91, 0x3e, 0x48, 0xcf, 0xfb, 0x1e, 0x51, 0x0d, 0xf6, 0x64, 0x5e, 0x07, 0xf6, 0xe9, 0x7d, 0x6b, 0x55, 0x77, 0x7e, + 0x4f, 0x95, 0x2e, 0x39, 0xb2, 0xe5, 0xa7, 0xcb, 0xf0, 0x5e, 0xfd, 0x29, 0xb9, 0x3e, 0x14, 0x38, 0xc2, 0x43, 0x15, + 0x70, 0xbe, 0x5e, 0x89, 0x76, 0x27, 0xc2, 0xae, 0x5c, 0x02, 0x42, 0x7c, 0x49, 0xd3, 0x1c, 0x8f, 0x23, 0x9a, 0x88, + 0xb0, 0x89, 0xd1, 0x5f, 0xd8, 0x7d, 0x28, 0xb1, 0x9c, 0xe7, 0x1a, 0x94, 0x5c, 0x32, 0x78, 0x4f, 0xda, 0x6b, 0xd0, + 0x2c, 0xaf, 0x4a, 0x4d, 0x26, 0x72, 0x50, 0x3e, 0x1c, 0x0a, 0xd8, 0x4b, 0x8d, 0x9f, 0x26, 0xfc, 0x84, 0xe5, 0xad, + 0xbd, 0x35, 0xa5, 0xa8, 0xa4, 0x01, 0x2a, 0xf0, 0x31, 0x83, 0xff, 0xdd, 0x19, 0x62, 0xc1, 0x14, 0x1d, 0x3f, 0x9c, + 0x89, 0xb9, 0xf5, 0xdc, 0x2a, 0xeb, 0x28, 0x5b, 0xa3, 0x9c, 0x80, 0x7f, 0x4f, 0x75, 0x9c, 0x24, 0xc2, 0xa9, 0xf7, + 0x88, 0x8b, 0xba, 0x97, 0x43, 0xd4, 0x0d, 0xfb, 0x54, 0xe9, 0x60, 0xcb, 0x69, 0x1a, 0x1c, 0x89, 0x5f, 0xa9, 0xcf, + 0x3e, 0x64, 0x16, 0x8f, 0x3a, 0xb2, 0x11, 0x25, 0x69, 0x1c, 0x8b, 0x1c, 0xb6, 0xf7, 0x1b, 0xb9, 0xff, 0xf7, 0xfb, + 0x10, 0x4e, 0x5a, 0x05, 0x71, 0xe9, 0x09, 0x44, 0x84, 0xa3, 0xc3, 0x8f, 0x08, 0x4f, 0xa4, 0xaa, 0xf0, 0x51, 0x7d, + 0xe2, 0xc6, 0xec, 0x5e, 0x98, 0xa3, 0x7a, 0x0b, 0x30, 0x8c, 0xf5, 0xd6, 0x22, 0x24, 0xd1, 0x4a, 0x33, 0xda, 0x7a, + 0x40, 0x8c, 0x78, 0xbf, 0xb6, 0xc8, 0x60, 0xac, 0x2d, 0x89, 0x04, 0xf0, 0x25, 0x09, 0x19, 0xda, 0x36, 0x02, 0x33, + 0x86, 0xb7, 0xb3, 0xe2, 0xd2, 0x75, 0xd8, 0xe6, 0x1c, 0xbe, 0x90, 0x1b, 0xcd, 0x3a, 0xa2, 0x34, 0x41, 0xc8, 0x3f, + 0xe0, 0x64, 0xa1, 0x30, 0x9a, 0x57, 0x47, 0xe9, 0x24, 0xb1, 0xbe, 0xef, 0x2a, 0x15, 0x6c, 0x36, 0xd7, 0xa8, 0x2f, + 0x3b, 0x4a, 0x7e, 0x09, 0x4e, 0x3a, 0x4e, 0xb2, 0xc8, 0x41, 0xd4, 0xa2, 0x72, 0xae, 0x93, 0xb0, 0xb4, 0xab, 0x53, + 0x6d, 0xd6, 0xeb, 0xa2, 0xac, 0xab, 0x57, 0x22, 0x52, 0xf4, 0x3e, 0xea, 0xd1, 0x23, 0x09, 0xa9, 0xd0, 0xaa, 0xd4, + 0x2e, 0x8f, 0xc0, 0x6d, 0x53, 0x2b, 0xb6, 0xe5, 0x12, 0x96, 0xa8, 0xf1, 0x9f, 0xa0, 0x8f, 0x72, 0x71, 0x2f, 0x03, + 0x34, 0x3a, 0x9e, 0x9a, 0xb7, 0x1e, 0x78, 0xe5, 0x28, 0xbf, 0xb4, 0xda, 0xa4, 0x5f, 0x01, 0x99, 0xd1, 0xfe, 0xd1, + 0x52, 0x02, 0x99, 0x81, 0x99, 0xb4, 0x34, 0x24, 0x72, 0x14, 0xb3, 0x34, 0xff, 0x13, 0x57, 0x6c, 0x85, 0x48, 0xc3, + 0x6a, 0xee, 0xf1, 0x1f, 0x2b, 0xaf, 0x96, 0x6b, 0x99, 0x69, 0x6e, 0x96, 0x38, 0x56, 0x2c, 0x2e, 0xea, 0x75, 0x25, + 0xb2, 0x40, 0x88, 0x23, 0x4c, 0x63, 0x3d, 0xf5, 0x46, 0x69, 0xf5, 0x01, 0x09, 0x65, 0x7e, 0xc4, 0xde, 0x8e, 0xbd, + 0x1e, 0x64, 0x21, 0x8e, 0x2d, 0x07, 0x9b, 0xad, 0xf7, 0xa9, 0x4c, 0x45, 0x7c, 0x56, 0x17, 0x67, 0x9b, 0x4a, 0x9c, + 0xd5, 0x89, 0x38, 0xfb, 0x01, 0x72, 0xfe, 0x70, 0x46, 0x45, 0x9f, 0xdd, 0xa7, 0x75, 0x52, 0x6c, 0x6a, 0x7a, 0xf2, + 0x1a, 0xcb, 0xf8, 0xe1, 0x8c, 0xb8, 0x6a, 0xce, 0x68, 0x24, 0xe3, 0xd1, 0xd9, 0x87, 0x0c, 0x48, 0x5e, 0xcf, 0xd2, + 0x15, 0x0c, 0xde, 0x59, 0x98, 0xc7, 0x67, 0xa5, 0x58, 0x82, 0xc5, 0xa9, 0xec, 0x7c, 0x0f, 0x32, 0xac, 0xc2, 0x3f, + 0xc5, 0x19, 0x40, 0xbb, 0x9e, 0xa5, 0xf5, 0x59, 0x5a, 0x9d, 0xe5, 0x45, 0x7d, 0xa6, 0xa4, 0x70, 0x08, 0xe3, 0x87, + 0xf7, 0xf4, 0x95, 0x5d, 0xde, 0x66, 0x71, 0x97, 0x45, 0xfe, 0x14, 0xbd, 0x8a, 0x88, 0x49, 0xa3, 0x12, 0x5e, 0xbb, + 0xbf, 0x6d, 0xee, 0x1f, 0x5e, 0x37, 0x76, 0x3f, 0xbb, 0x63, 0x44, 0x17, 0xd4, 0xe3, 0x95, 0xa4, 0x54, 0x50, 0x40, + 0xe0, 0x44, 0xb3, 0xc6, 0x83, 0x3b, 0x0e, 0x78, 0x35, 0xb0, 0x05, 0x5b, 0xfb, 0xfc, 0x59, 0x2c, 0xc3, 0xb4, 0x37, + 0x01, 0xfe, 0x55, 0xf6, 0xa6, 0xeb, 0x60, 0x81, 0xf7, 0x2d, 0x64, 0x1b, 0x7a, 0xf3, 0x8a, 0x3f, 0xf7, 0x72, 0xf5, + 0x37, 0xfb, 0x27, 0x00, 0x61, 0x40, 0xcc, 0xaa, 0x8f, 0x26, 0xee, 0x9d, 0x95, 0x65, 0xe7, 0x64, 0xd9, 0xf5, 0xd0, + 0xaf, 0x49, 0x8c, 0x4a, 0x2b, 0x4b, 0xe9, 0x64, 0x29, 0x21, 0x0b, 0xf8, 0xc4, 0x68, 0x6a, 0x23, 0x80, 0xb0, 0x1d, + 0xa5, 0xf2, 0x85, 0xca, 0x8b, 0x28, 0x9c, 0x13, 0x3c, 0x4f, 0xc4, 0xe8, 0xce, 0x4a, 0x06, 0x0c, 0x87, 0x10, 0xcc, + 0x41, 0x5b, 0xec, 0x0d, 0xdd, 0x44, 0xfc, 0xf5, 0xba, 0x28, 0xdf, 0xc4, 0xe4, 0x53, 0xb0, 0x3b, 0xf9, 0xb8, 0x84, + 0xc7, 0xe5, 0xc9, 0xc7, 0x21, 0x7a, 0x24, 0x9c, 0x7c, 0x0c, 0xbe, 0x47, 0x72, 0x5e, 0x77, 0x3d, 0x4e, 0x90, 0x5b, + 0x48, 0xf7, 0xb7, 0x63, 0x12, 0xa0, 0x79, 0x0d, 0xcb, 0x51, 0x53, 0x71, 0xcd, 0xcc, 0x18, 0xcf, 0x1b, 0xbd, 0x3f, + 0x76, 0xbc, 0x65, 0x0a, 0xc5, 0x2c, 0xe6, 0x35, 0xfc, 0x9e, 0x55, 0x81, 0xba, 0xeb, 0x6d, 0x92, 0x5b, 0x66, 0xf5, + 0x1c, 0xed, 0xbe, 0xef, 0xeb, 0x44, 0x50, 0xfb, 0x3b, 0xec, 0x79, 0x66, 0xbd, 0xab, 0x62, 0xe0, 0x52, 0x25, 0x3b, + 0x64, 0xaa, 0x9a, 0x1e, 0xa8, 0x94, 0x06, 0x4f, 0x2f, 0xad, 0xcb, 0x97, 0x4a, 0x1b, 0x79, 0xa6, 0xf9, 0x0d, 0xe0, + 0xc5, 0xd4, 0x65, 0xb1, 0xfb, 0xe6, 0xbe, 0x82, 0xdb, 0x78, 0xbf, 0xbf, 0xae, 0x3c, 0xf3, 0x13, 0x17, 0x80, 0xbd, + 0xa9, 0xd0, 0x3a, 0x81, 0x52, 0xc3, 0x3a, 0x7c, 0x99, 0x88, 0xe8, 0xcf, 0x76, 0xb9, 0xce, 0x5c, 0x07, 0x8c, 0x28, + 0xe2, 0xb7, 0xf1, 0xe8, 0x0f, 0x50, 0x5c, 0x1b, 0x7b, 0x40, 0x58, 0x87, 0x84, 0x3e, 0x23, 0x00, 0xa9, 0x47, 0x1f, + 0x25, 0xf7, 0xa0, 0x59, 0xd1, 0xdc, 0x31, 0xf9, 0xb9, 0xbe, 0x52, 0xfa, 0xfb, 0x75, 0xe5, 0x91, 0x39, 0xa5, 0x6d, + 0xa6, 0xb1, 0x5a, 0x53, 0x09, 0x84, 0x57, 0x54, 0xb2, 0x0a, 0x9f, 0xcd, 0x1b, 0xd1, 0xef, 0xcb, 0x23, 0x3c, 0xad, + 0x7e, 0xdc, 0x62, 0x7c, 0x2b, 0x20, 0x1a, 0x09, 0x50, 0xb0, 0x02, 0xcc, 0x8b, 0x6c, 0x66, 0xf7, 0x71, 0x40, 0x95, + 0x12, 0x4d, 0xe3, 0x6c, 0x9e, 0xdf, 0xd3, 0x9b, 0xb2, 0x83, 0x4e, 0x9d, 0x2a, 0x70, 0xc1, 0x55, 0xc9, 0x78, 0x65, + 0x3d, 0x91, 0xcf, 0x6f, 0x6e, 0x37, 0x69, 0x16, 0xbf, 0x2f, 0x7f, 0xc5, 0xb1, 0xd5, 0x75, 0x78, 0x60, 0xea, 0x74, + 0xed, 0x3c, 0xd2, 0xda, 0x0b, 0x01, 0x11, 0xed, 0x1a, 0x6a, 0xbd, 0xb0, 0xd0, 0x23, 0x3d, 0x11, 0xce, 0x49, 0xa2, + 0xa6, 0x1d, 0x68, 0x69, 0x84, 0xbe, 0xbe, 0xe6, 0xf4, 0x17, 0x06, 0x6b, 0x9f, 0x8f, 0x19, 0x90, 0x95, 0xe8, 0xc7, + 0xea, 0xa1, 0xb1, 0x99, 0x43, 0xcf, 0x5a, 0x95, 0x67, 0x5e, 0x75, 0x38, 0x20, 0x3e, 0x8c, 0xfe, 0x92, 0xdf, 0xef, + 0xbf, 0xa2, 0xf9, 0xc7, 0x84, 0x1a, 0x3f, 0xdb, 0x0c, 0xd0, 0xb5, 0xef, 0xca, 0x03, 0x51, 0xcf, 0xb5, 0x4a, 0x10, + 0xe2, 0x0d, 0x62, 0xa2, 0x19, 0x31, 0x07, 0xa7, 0x1d, 0x6a, 0xfe, 0x49, 0x6a, 0x40, 0x88, 0x12, 0xaf, 0x63, 0xca, + 0x82, 0x9c, 0x36, 0x71, 0xa4, 0x1f, 0x85, 0x13, 0xf9, 0x51, 0x54, 0x45, 0x76, 0x07, 0x17, 0x0c, 0xa6, 0xde, 0xd3, + 0x7e, 0x89, 0x7e, 0x4b, 0x38, 0x72, 0x8e, 0x56, 0x85, 0x20, 0x72, 0x42, 0x58, 0x6b, 0x08, 0x13, 0xc4, 0x06, 0xf1, + 0xb2, 0xef, 0x92, 0x0c, 0x47, 0x0a, 0x2e, 0xeb, 0xd8, 0x31, 0xe6, 0xea, 0xa8, 0x7a, 0x0d, 0x60, 0xbc, 0x72, 0x04, + 0xcd, 0x46, 0x91, 0x5d, 0x42, 0x54, 0x91, 0xe3, 0x09, 0xa8, 0x1d, 0x94, 0xc6, 0x66, 0x7a, 0x3e, 0x0e, 0xf2, 0xd1, + 0x4d, 0x85, 0x3a, 0x27, 0x96, 0xf1, 0x1a, 0x80, 0xb5, 0x73, 0xd5, 0xcf, 0xb3, 0x1a, 0x3c, 0x69, 0x88, 0xcf, 0xc7, + 0x68, 0x7b, 0x65, 0x73, 0x50, 0x6d, 0xa7, 0xb3, 0xf2, 0x8a, 0xe9, 0x72, 0x60, 0xdc, 0x37, 0xbc, 0xa2, 0x38, 0xc3, + 0x8f, 0x1e, 0x6c, 0x71, 0xfe, 0x74, 0x43, 0xed, 0xc7, 0xdc, 0xa8, 0x87, 0x81, 0xd6, 0x82, 0x37, 0x05, 0xb1, 0xfe, + 0x7e, 0xe8, 0xc8, 0xf6, 0x5e, 0x8b, 0x8c, 0x26, 0x9f, 0xfd, 0xfc, 0x43, 0x99, 0xae, 0x52, 0xb8, 0x2f, 0x39, 0x59, + 0x34, 0xf3, 0x10, 0xd8, 0x1b, 0x62, 0xb8, 0x3e, 0x2a, 0x3c, 0xa2, 0xac, 0xdf, 0x87, 0xdf, 0x57, 0x19, 0x98, 0x62, + 0xe0, 0xba, 0x42, 0x30, 0x1e, 0x02, 0x41, 0x3c, 0x4c, 0xa3, 0x93, 0x41, 0x0d, 0xda, 0xf0, 0x0d, 0x40, 0x66, 0x80, + 0x47, 0xe6, 0xc2, 0x23, 0xe0, 0x2e, 0x70, 0xed, 0xc9, 0x78, 0xec, 0x4f, 0x4c, 0x43, 0xa3, 0xa6, 0x34, 0xd3, 0x73, + 0xe3, 0x37, 0x1d, 0xd5, 0x72, 0xed, 0xfc, 0xc7, 0x97, 0xfc, 0x06, 0xbd, 0xa0, 0xe5, 0xe5, 0x3e, 0x52, 0x97, 0xfb, + 0x8c, 0xe2, 0x32, 0x91, 0x1c, 0x16, 0xc4, 0xb2, 0x84, 0x03, 0x8f, 0x51, 0xc9, 0x62, 0x4b, 0x8f, 0x55, 0xd1, 0xf2, + 0x45, 0xb9, 0x41, 0x3a, 0x74, 0x42, 0xb0, 0x44, 0x05, 0xc1, 0x12, 0x18, 0x17, 0xb1, 0xe6, 0x9b, 0x41, 0xce, 0xe2, + 0xd9, 0x66, 0xce, 0x91, 0xb0, 0x2e, 0x39, 0x1c, 0x0a, 0x09, 0x36, 0x93, 0xcd, 0xd6, 0x73, 0xb6, 0xf6, 0x19, 0x28, + 0x01, 0x4a, 0x99, 0x26, 0x28, 0x4d, 0x2b, 0xb6, 0xe2, 0xa6, 0x35, 0x58, 0xad, 0xa6, 0x6c, 0x55, 0x53, 0x76, 0x4e, + 0x53, 0x8e, 0x2a, 0x28, 0x39, 0xa1, 0x14, 0x65, 0x18, 0xc0, 0x88, 0x4d, 0xa2, 0xab, 0x0c, 0x7d, 0xbc, 0x13, 0x1e, + 0x41, 0x15, 0x11, 0xf9, 0x84, 0x21, 0x04, 0x26, 0xa2, 0xb8, 0x50, 0x85, 0x62, 0x80, 0x8c, 0x48, 0x20, 0x98, 0xa8, + 0xd4, 0x29, 0x30, 0x1f, 0x4d, 0x15, 0xc3, 0xa6, 0x3d, 0x51, 0xbe, 0xa7, 0x8e, 0x7b, 0x94, 0x6d, 0x7e, 0x16, 0xbb, + 0x20, 0x44, 0xee, 0xc6, 0x9d, 0xfa, 0x19, 0xf1, 0xde, 0xee, 0x08, 0xe3, 0x27, 0x3b, 0x6e, 0x11, 0xae, 0x08, 0xb6, + 0x50, 0x73, 0x88, 0xc5, 0xbc, 0x9a, 0x24, 0xa8, 0x65, 0x49, 0xfc, 0x0d, 0x4f, 0x06, 0x39, 0x5b, 0x80, 0x07, 0xed, + 0x9c, 0x65, 0x80, 0xbf, 0x62, 0xb5, 0xe8, 0xf7, 0xda, 0x5b, 0x80, 0xfc, 0xb4, 0xb1, 0x1b, 0x85, 0x89, 0x11, 0x24, + 0xea, 0x76, 0x65, 0x20, 0x3f, 0x7c, 0xc0, 0xe9, 0x78, 0xec, 0x29, 0x63, 0x6e, 0x65, 0x7a, 0x99, 0xce, 0x95, 0x7c, + 0x23, 0xf7, 0xd2, 0x87, 0x5e, 0x82, 0x9d, 0x03, 0xde, 0x40, 0xda, 0xc0, 0x6b, 0xd8, 0x2e, 0xbc, 0x36, 0x48, 0x98, + 0x11, 0x60, 0x8b, 0xe3, 0x63, 0xa4, 0x04, 0x86, 0x70, 0x9c, 0xa5, 0x00, 0x4c, 0xa3, 0x2f, 0xb3, 0x95, 0x7d, 0x99, + 0xd5, 0x9a, 0x2d, 0x95, 0xd3, 0xbd, 0x73, 0xeb, 0x76, 0x3e, 0x97, 0x00, 0x60, 0x52, 0xe7, 0x40, 0x9c, 0x99, 0x60, + 0x97, 0x26, 0x91, 0xe5, 0x63, 0x98, 0x2f, 0xc5, 0xeb, 0xb2, 0x58, 0xa9, 0xae, 0x68, 0xfb, 0xcc, 0xe4, 0x33, 0xd2, + 0x49, 0xa8, 0x80, 0x82, 0x42, 0xae, 0xf5, 0xe9, 0xbb, 0xf0, 0x5d, 0x50, 0x68, 0x60, 0xb6, 0x0a, 0xf7, 0x34, 0x59, + 0x23, 0xf5, 0x46, 0xd5, 0xef, 0x93, 0x6b, 0x20, 0xd5, 0x99, 0x43, 0xcb, 0x9e, 0x57, 0x18, 0x20, 0x76, 0xd4, 0x67, + 0x24, 0xd4, 0x81, 0xd4, 0x03, 0x86, 0x10, 0x6d, 0xd3, 0xc7, 0x9f, 0x0c, 0x89, 0x2e, 0xc0, 0x16, 0xa2, 0x0d, 0xfc, + 0xf8, 0x13, 0xec, 0xb3, 0x20, 0x3c, 0xa6, 0xf9, 0x5b, 0x48, 0x3a, 0x36, 0x70, 0x5a, 0x7d, 0x0a, 0x3e, 0x48, 0x72, + 0x30, 0x51, 0x07, 0x2f, 0xf7, 0x97, 0x7e, 0x1f, 0xb6, 0xec, 0x5c, 0x4a, 0x75, 0xac, 0xd4, 0xdb, 0xb6, 0xf6, 0x83, + 0x68, 0x0b, 0x8e, 0x10, 0xac, 0x9d, 0x21, 0x22, 0x98, 0x19, 0x44, 0xd8, 0xb5, 0x50, 0x77, 0x7b, 0x4a, 0x2d, 0x8b, + 0x7a, 0xdb, 0x53, 0x4a, 0xdd, 0x86, 0xe1, 0xbb, 0x09, 0x66, 0x8a, 0x1b, 0x7e, 0x9d, 0x79, 0xa1, 0xde, 0x78, 0x2c, + 0x9e, 0x76, 0xcf, 0xdf, 0x2f, 0x78, 0x35, 0xdb, 0x28, 0x13, 0xe6, 0x92, 0x2f, 0x66, 0xa1, 0xec, 0x6a, 0x69, 0xdc, + 0xf9, 0xe2, 0x2d, 0xd4, 0x7c, 0xf0, 0x0f, 0x87, 0x04, 0xe2, 0x8d, 0xe2, 0xab, 0x65, 0x23, 0xb7, 0xae, 0xc9, 0xe6, + 0xaa, 0x04, 0xd4, 0xef, 0xf3, 0x35, 0xee, 0xb7, 0x58, 0xff, 0xee, 0x69, 0x90, 0xb1, 0x9a, 0xe1, 0x8a, 0x29, 0x7c, + 0x0a, 0x00, 0x83, 0xc3, 0xa9, 0x20, 0x2d, 0xf0, 0x86, 0x97, 0xc3, 0xcb, 0xc9, 0x86, 0x4c, 0xba, 0x1b, 0x1f, 0xb9, + 0xb3, 0x40, 0xd5, 0xfb, 0x1d, 0xc5, 0x49, 0x83, 0x44, 0x63, 0xaf, 0xc1, 0xe7, 0x59, 0x46, 0xb9, 0x68, 0xe2, 0x3e, + 0x24, 0x5f, 0xe9, 0x01, 0xcc, 0x55, 0x28, 0x01, 0xa2, 0xdf, 0x58, 0x16, 0x1b, 0xd1, 0xb6, 0xd8, 0xc0, 0x52, 0xaa, + 0xe6, 0x7a, 0x35, 0x7d, 0xf1, 0x4a, 0x34, 0xef, 0xa3, 0x19, 0xa7, 0x34, 0x1a, 0x70, 0x9c, 0x46, 0xe1, 0xf6, 0xfd, + 0x9d, 0x28, 0x17, 0x19, 0x58, 0xb2, 0x55, 0x38, 0xc5, 0x65, 0xa3, 0xce, 0x88, 0xe7, 0x79, 0xac, 0x00, 0x3a, 0x1e, + 0x12, 0x00, 0xd5, 0x05, 0x01, 0x15, 0xd1, 0x52, 0x7a, 0x2b, 0xb4, 0x58, 0xa8, 0x37, 0x1c, 0xa5, 0xf0, 0x47, 0xfa, + 0xf3, 0x20, 0x9f, 0x02, 0x10, 0xbb, 0x3e, 0x8e, 0x5e, 0x17, 0x25, 0x7d, 0xaa, 0x98, 0xe5, 0x72, 0x30, 0x81, 0x5d, + 0x9d, 0xc8, 0x50, 0x2b, 0xc8, 0x5b, 0x75, 0xe5, 0xad, 0x4c, 0xde, 0xc6, 0x38, 0x25, 0x3f, 0x70, 0xd3, 0xb1, 0x46, + 0x0c, 0xbc, 0xf2, 0xb4, 0x4e, 0x13, 0xa4, 0xc9, 0x1b, 0x60, 0x18, 0xe2, 0x77, 0x99, 0xf7, 0xdc, 0x73, 0xa4, 0x2a, + 0x48, 0x66, 0xdb, 0xcc, 0x53, 0x17, 0x51, 0x7d, 0xe5, 0xd4, 0xd2, 0x99, 0xd3, 0x8f, 0x00, 0xde, 0x63, 0x6a, 0xd2, + 0x90, 0x8f, 0x70, 0x5b, 0x8a, 0xaf, 0xb7, 0xea, 0x1a, 0x2f, 0x8d, 0xce, 0xdd, 0xcb, 0x97, 0xee, 0x34, 0xe8, 0xa7, + 0x20, 0x28, 0xe7, 0xf3, 0x52, 0xc0, 0x9e, 0x32, 0x9b, 0xeb, 0xd5, 0xaa, 0x15, 0x5a, 0x87, 0xc3, 0x58, 0x3b, 0x0a, + 0x69, 0x75, 0x16, 0xb0, 0xd5, 0x48, 0xa7, 0x04, 0x08, 0xc1, 0x71, 0x1a, 0x76, 0x82, 0x71, 0x97, 0x4e, 0x23, 0xb2, + 0x5e, 0x29, 0x49, 0x17, 0x66, 0x90, 0xfc, 0x93, 0xbc, 0x9e, 0x01, 0x2d, 0x01, 0x1c, 0x8a, 0x58, 0xc2, 0xc3, 0x49, + 0x72, 0x05, 0xd0, 0xe9, 0x70, 0x50, 0x69, 0x68, 0xce, 0x6a, 0x96, 0xcc, 0x27, 0xb1, 0x54, 0x55, 0x1e, 0x0e, 0x9e, + 0x72, 0x33, 0xe8, 0xf7, 0xb3, 0x69, 0xa9, 0x5c, 0x00, 0x82, 0x58, 0x17, 0x06, 0x88, 0x47, 0x5a, 0x78, 0xb2, 0xe8, + 0x53, 0x12, 0xbf, 0x9c, 0x25, 0x73, 0x93, 0x0d, 0xef, 0xc0, 0x08, 0x36, 0xe3, 0xba, 0xa4, 0x4c, 0x7b, 0x54, 0x7e, + 0xcf, 0xe8, 0xa9, 0xed, 0x6b, 0xad, 0xb6, 0x88, 0x75, 0x1d, 0x5c, 0x95, 0xa8, 0xa7, 0xf8, 0xa0, 0x24, 0xc1, 0xfb, + 0x95, 0x73, 0x33, 0x52, 0xbe, 0x16, 0xb9, 0x1f, 0xb4, 0x33, 0xb5, 0x72, 0xe0, 0x08, 0xe4, 0x58, 0x45, 0x25, 0xaf, + 0x77, 0x1d, 0x82, 0x47, 0x77, 0xa5, 0x02, 0xe5, 0xe0, 0x67, 0x20, 0x46, 0xd7, 0x57, 0x9d, 0x35, 0xd4, 0x4c, 0xa3, + 0xca, 0x23, 0xe8, 0xd4, 0x01, 0x3c, 0x29, 0x78, 0xa9, 0xd5, 0x8f, 0x87, 0x83, 0x67, 0x7e, 0xf0, 0xf7, 0x99, 0xbe, + 0x85, 0x98, 0x28, 0xa7, 0x1a, 0x21, 0x71, 0xa5, 0x24, 0x11, 0x1f, 0x2f, 0x5a, 0x56, 0x8c, 0xca, 0xf0, 0x9e, 0x57, + 0xaa, 0x7c, 0x75, 0xaa, 0xf2, 0x62, 0xa4, 0x6d, 0x09, 0xbc, 0x26, 0xff, 0x10, 0xb9, 0xe6, 0xad, 0xaf, 0xbb, 0xca, + 0xd0, 0x97, 0xb2, 0x02, 0x1d, 0xc1, 0x56, 0x96, 0x92, 0x03, 0x3e, 0xa9, 0xee, 0xaa, 0x55, 0xeb, 0x73, 0xca, 0x36, + 0xc2, 0x4d, 0x7e, 0x1d, 0x3b, 0x38, 0x52, 0x7e, 0x83, 0xe7, 0x02, 0xd8, 0x6b, 0xc0, 0xde, 0x9c, 0xb3, 0xa2, 0x79, + 0x70, 0x48, 0xdb, 0x02, 0x8d, 0xcc, 0xdc, 0xce, 0xd5, 0x7d, 0x5b, 0x1e, 0xa5, 0x31, 0x44, 0xa6, 0x3d, 0x30, 0x1d, + 0x6c, 0x46, 0xf9, 0xef, 0x29, 0xbf, 0x55, 0x38, 0x06, 0xbe, 0x9d, 0x7a, 0x07, 0x50, 0xf5, 0xb4, 0x41, 0xc6, 0x9a, + 0x61, 0x68, 0x65, 0x97, 0x4b, 0xa1, 0x25, 0x68, 0xa9, 0x9b, 0x20, 0x38, 0x3f, 0x22, 0xca, 0x11, 0x80, 0x2e, 0x52, + 0xc0, 0x04, 0x3f, 0xa5, 0xed, 0xee, 0xf7, 0xd7, 0xa9, 0x47, 0xee, 0x5d, 0xa1, 0xb2, 0x59, 0x7e, 0x22, 0x18, 0xfb, + 0x89, 0xc6, 0x0c, 0x3a, 0xba, 0x22, 0x27, 0x3c, 0x6b, 0x75, 0x58, 0xd7, 0x4d, 0x19, 0x94, 0xc5, 0x31, 0xaf, 0xa6, + 0xb3, 0x3f, 0x1e, 0xed, 0xeb, 0x06, 0x59, 0xc8, 0xff, 0x60, 0x3d, 0x24, 0x83, 0xee, 0x41, 0x28, 0x44, 0x6f, 0x1e, + 0xcc, 0xf0, 0x3f, 0xb6, 0xe1, 0xd9, 0x77, 0xdc, 0xa8, 0x13, 0xc0, 0x1c, 0x71, 0xbd, 0xf4, 0x14, 0x6d, 0x3d, 0xdc, + 0x02, 0xd9, 0x1a, 0x2f, 0x6f, 0xed, 0x35, 0x90, 0x53, 0x1c, 0xff, 0x92, 0x67, 0x6a, 0x65, 0x83, 0x9f, 0x9e, 0xb2, + 0x1d, 0x78, 0x78, 0x11, 0x02, 0x8a, 0x61, 0xd9, 0xf8, 0xa5, 0xe5, 0x38, 0xa3, 0xff, 0xe6, 0x11, 0xc3, 0x60, 0x11, + 0xf9, 0xf1, 0x45, 0x29, 0xc4, 0x57, 0xe1, 0x7d, 0xaa, 0xbc, 0x25, 0x39, 0x65, 0x2e, 0xf5, 0x30, 0xba, 0x2e, 0x49, + 0xdf, 0x25, 0x1f, 0x5b, 0xc3, 0xf6, 0x87, 0x76, 0xbf, 0x19, 0x22, 0x08, 0xa1, 0x1c, 0x3f, 0x67, 0x74, 0x42, 0xe3, + 0xc3, 0x6a, 0x76, 0x7a, 0xfd, 0xde, 0x39, 0x5e, 0xb0, 0x35, 0x1a, 0xe0, 0xf1, 0xd0, 0xc5, 0x3c, 0x51, 0x43, 0xa7, + 0xeb, 0xda, 0x39, 0x78, 0x60, 0x90, 0xe5, 0xc9, 0x77, 0x0c, 0x4b, 0xec, 0x4f, 0x22, 0x9e, 0xb4, 0x55, 0x1b, 0x9b, + 0x23, 0xd5, 0x46, 0xcd, 0xc0, 0x0f, 0x5e, 0x41, 0x81, 0xd1, 0x05, 0xe9, 0x16, 0x8c, 0xc3, 0x11, 0x80, 0xac, 0x18, + 0xc7, 0x23, 0x83, 0x09, 0x0c, 0xe9, 0x86, 0xa2, 0x00, 0x3c, 0x3c, 0x8e, 0x07, 0x21, 0x03, 0x48, 0x17, 0x3c, 0x34, + 0x6c, 0x93, 0x90, 0xf2, 0xf3, 0x3c, 0xaf, 0xd5, 0x10, 0xfa, 0xce, 0x42, 0x75, 0xec, 0x47, 0xda, 0x2b, 0xd6, 0xb5, + 0x2a, 0x1d, 0xd9, 0xea, 0x00, 0x7d, 0x43, 0x06, 0xbe, 0x75, 0x6c, 0x01, 0x10, 0x2d, 0xf1, 0x7b, 0xea, 0xd5, 0xbe, + 0x8c, 0x59, 0xa1, 0x5e, 0xbf, 0x31, 0xed, 0x7a, 0x25, 0x2d, 0x0a, 0xa8, 0xb8, 0x6d, 0xd5, 0xf6, 0x48, 0xce, 0x7f, + 0x78, 0xd7, 0xd1, 0x8e, 0xcf, 0x4e, 0x8d, 0x2d, 0xa1, 0xcc, 0x2d, 0x9e, 0xc8, 0xea, 0x68, 0x4b, 0x75, 0xaa, 0x0f, + 0xb8, 0xd4, 0xa4, 0x3a, 0x33, 0x30, 0xbc, 0x46, 0x80, 0x72, 0x0b, 0x91, 0x34, 0x0e, 0x7b, 0xe7, 0x93, 0x41, 0xc1, + 0xdc, 0x22, 0x01, 0x09, 0x6c, 0x63, 0x6b, 0x17, 0xcd, 0xf5, 0xeb, 0xf7, 0xd4, 0xab, 0xda, 0x54, 0xf5, 0xe0, 0x8d, + 0x17, 0x38, 0x7b, 0xa7, 0xb5, 0x80, 0x00, 0x0a, 0x5b, 0xcb, 0x72, 0x70, 0xee, 0x76, 0x55, 0x4b, 0x45, 0x19, 0xf5, + 0xfb, 0xe7, 0xbf, 0xa7, 0xa8, 0x88, 0x3d, 0x55, 0x9c, 0xb2, 0x7e, 0xbb, 0x65, 0xde, 0x54, 0x96, 0xbc, 0x41, 0x15, + 0xad, 0xd5, 0x51, 0x53, 0xb9, 0x6e, 0xae, 0x5a, 0x32, 0x41, 0x8c, 0xee, 0xd3, 0xb5, 0xce, 0x9d, 0x7a, 0xef, 0x55, + 0x1c, 0x31, 0x10, 0xdc, 0x74, 0x8f, 0x0f, 0x0e, 0x42, 0xa3, 0xa2, 0x5c, 0x70, 0xa3, 0xb4, 0xaa, 0xa4, 0x14, 0xf2, + 0x56, 0x45, 0x73, 0xa6, 0x8f, 0x00, 0x88, 0x00, 0xab, 0x44, 0xfd, 0x6f, 0xbe, 0x34, 0xc6, 0x83, 0x07, 0xbe, 0x26, + 0xd7, 0xb1, 0xf5, 0xfe, 0x69, 0x8d, 0xb4, 0xda, 0x38, 0x26, 0xb5, 0xea, 0x65, 0xab, 0x78, 0xd9, 0xbd, 0x4e, 0xc5, + 0xe0, 0xf9, 0xff, 0xdc, 0x07, 0xa8, 0x11, 0x2d, 0x65, 0x70, 0xeb, 0x6a, 0x80, 0xc6, 0x87, 0x63, 0xe1, 0x1b, 0x3f, + 0x64, 0x9c, 0x0f, 0x66, 0xe8, 0xa8, 0x36, 0x07, 0x07, 0x04, 0x47, 0x75, 0x8f, 0xc6, 0x84, 0x59, 0x38, 0xf7, 0x20, + 0x50, 0x7d, 0xe2, 0x3e, 0xe3, 0xda, 0x0b, 0xda, 0x04, 0x3e, 0x59, 0xd7, 0x35, 0x45, 0x80, 0x8b, 0xd8, 0x98, 0x88, + 0x21, 0x2e, 0x9b, 0x44, 0xea, 0x9b, 0x31, 0x28, 0x00, 0x8a, 0x67, 0x15, 0xc9, 0xa5, 0x37, 0x69, 0x5e, 0x89, 0xb2, + 0xd6, 0xcd, 0xa8, 0x58, 0x31, 0x04, 0x80, 0x87, 0xa0, 0xb8, 0xaa, 0xcc, 0x84, 0x46, 0x6c, 0x20, 0x95, 0xa5, 0x60, + 0xd5, 0xb0, 0xf0, 0x9b, 0xf6, 0x9b, 0xe4, 0xa4, 0x77, 0x3e, 0x6e, 0x9d, 0x3b, 0xf6, 0xbd, 0xa3, 0x90, 0xd2, 0x1e, + 0x8a, 0x09, 0x82, 0xe0, 0xa7, 0x75, 0x38, 0x7f, 0xc6, 0x9f, 0x11, 0x98, 0x8a, 0x6c, 0xc6, 0x80, 0x83, 0x10, 0x91, + 0x19, 0xbf, 0xe7, 0xf0, 0x19, 0x2f, 0x27, 0xe1, 0x70, 0xe8, 0x83, 0x3e, 0x94, 0x67, 0xb3, 0x70, 0x28, 0xe6, 0xd2, + 0x7b, 0x1d, 0xac, 0x75, 0x21, 0xaf, 0x27, 0x21, 0xa2, 0x85, 0x86, 0x3e, 0x38, 0xaf, 0xbb, 0xe6, 0x08, 0x4b, 0x00, + 0x9a, 0x38, 0xfa, 0xb2, 0x7e, 0x3f, 0xf2, 0xb4, 0xa1, 0x45, 0x8a, 0x8b, 0x46, 0x99, 0xcd, 0x72, 0xd9, 0x09, 0x1b, + 0xd7, 0x6e, 0x81, 0x50, 0x3c, 0x4c, 0x5b, 0xa8, 0x5a, 0x4f, 0xf5, 0x7a, 0x6e, 0xda, 0x7d, 0xf7, 0xa0, 0x5a, 0xe5, + 0x48, 0x67, 0x6d, 0xba, 0x52, 0xab, 0x5b, 0x46, 0xd5, 0x3a, 0x4b, 0x23, 0xaa, 0xdc, 0x24, 0x77, 0x8d, 0x5a, 0xf0, + 0xc9, 0x86, 0x2e, 0x53, 0x76, 0xb6, 0x06, 0x27, 0x8e, 0x3c, 0x97, 0xdc, 0xf2, 0xdd, 0x79, 0x45, 0x77, 0xa7, 0xda, + 0xb7, 0x00, 0xf7, 0x66, 0xd8, 0x90, 0x39, 0xaf, 0xb1, 0xd3, 0x20, 0x4c, 0x02, 0x3f, 0x62, 0x1f, 0x33, 0x64, 0x83, + 0x01, 0x1d, 0x85, 0xf4, 0xbf, 0xb6, 0xcc, 0x91, 0x80, 0xc9, 0x5f, 0xcf, 0xfd, 0xe6, 0xa6, 0xc8, 0x61, 0x31, 0x7e, + 0xd8, 0x60, 0xa4, 0xb1, 0x5a, 0x83, 0x61, 0xb9, 0x44, 0xe4, 0x4f, 0xed, 0x8e, 0x69, 0xaa, 0xe3, 0xcd, 0x7a, 0xad, + 0xf9, 0xd5, 0xd3, 0xa7, 0xba, 0x3e, 0xff, 0xed, 0xfb, 0xcb, 0xb0, 0x66, 0xf6, 0x87, 0x20, 0x94, 0x76, 0xef, 0x16, + 0xe7, 0x8e, 0x44, 0xef, 0x58, 0x69, 0x66, 0x97, 0x76, 0xc9, 0x2e, 0x4d, 0x69, 0xd7, 0xe4, 0x7a, 0xf5, 0x8d, 0xf2, + 0xc6, 0xce, 0x2b, 0xa6, 0xfb, 0xf7, 0x42, 0xef, 0x28, 0xa7, 0x6a, 0x02, 0x11, 0x4d, 0xda, 0x91, 0xb8, 0xdd, 0x2b, + 0xc3, 0xa7, 0x93, 0xbc, 0x5d, 0xc2, 0x51, 0xd7, 0xb0, 0xdc, 0x7c, 0xfb, 0xd7, 0xbc, 0xea, 0xac, 0x70, 0xfb, 0xa5, + 0x31, 0x6b, 0x7f, 0x0a, 0xe2, 0xaa, 0xfe, 0xf4, 0x1e, 0xd5, 0x4c, 0xc9, 0xff, 0x55, 0x8f, 0x81, 0xab, 0x9f, 0x4c, + 0x3b, 0xba, 0xa7, 0x10, 0x36, 0x98, 0xfd, 0xfc, 0xf8, 0xa1, 0x45, 0xd7, 0xe8, 0x02, 0x45, 0x72, 0x00, 0x9d, 0xbb, + 0x64, 0x84, 0xf7, 0x3b, 0xc6, 0xb9, 0x7f, 0xf5, 0x9b, 0x9a, 0x1c, 0x21, 0xa2, 0x5d, 0x84, 0x03, 0x80, 0xb8, 0xd3, + 0x54, 0xd6, 0xa1, 0x06, 0xe8, 0x03, 0x02, 0xeb, 0xd0, 0xb7, 0x19, 0xc0, 0x41, 0x1f, 0x6d, 0x9e, 0x45, 0x20, 0xaf, + 0x7b, 0x77, 0xec, 0x2d, 0xdb, 0xf9, 0xfc, 0xd9, 0x2a, 0xf5, 0xee, 0xd0, 0x21, 0xf8, 0x7c, 0xec, 0x4f, 0x2f, 0x03, + 0x83, 0x0b, 0xcd, 0xde, 0x3e, 0x11, 0x6c, 0xc7, 0x76, 0x4f, 0x10, 0xa9, 0xa8, 0x3b, 0xff, 0xf0, 0xd2, 0x44, 0xcf, + 0x3b, 0x2f, 0x2c, 0xf9, 0x02, 0xc0, 0x03, 0x59, 0x0c, 0x28, 0x3e, 0x0b, 0xef, 0x57, 0x96, 0x80, 0x9a, 0xfc, 0x96, + 0xaf, 0xbd, 0x77, 0x94, 0x7a, 0x03, 0x7f, 0x0e, 0x28, 0x7d, 0x92, 0x73, 0x6f, 0x39, 0xbc, 0xf5, 0x2f, 0x9e, 0x82, + 0xf3, 0xc4, 0x6a, 0x78, 0x03, 0x7f, 0x15, 0x7c, 0xe8, 0x2d, 0x07, 0x98, 0x58, 0xf2, 0xa1, 0xb7, 0x1a, 0x40, 0xaa, + 0xc2, 0x85, 0xc4, 0xd8, 0x87, 0xcf, 0x41, 0xce, 0xf0, 0x8f, 0xdf, 0x35, 0x06, 0xeb, 0xe7, 0xa0, 0xd0, 0x68, 0xac, + 0xa5, 0x0a, 0x59, 0x8a, 0xc5, 0x99, 0x00, 0x9b, 0x70, 0xdc, 0xed, 0x8b, 0x55, 0x6d, 0xd6, 0x82, 0xfe, 0x7c, 0xc0, + 0xf7, 0x68, 0xac, 0xae, 0xca, 0xb9, 0x28, 0x3f, 0x22, 0x7d, 0xaa, 0xe3, 0x63, 0x54, 0x6c, 0xea, 0xee, 0x74, 0xaa, + 0x55, 0x47, 0xda, 0xef, 0xca, 0x35, 0xd8, 0xf1, 0x3a, 0x39, 0xb2, 0x14, 0x9e, 0x75, 0xd8, 0x79, 0xe9, 0x94, 0xe8, + 0x30, 0x8c, 0x77, 0x5b, 0xf5, 0x8c, 0xa1, 0x3c, 0x37, 0x18, 0xd3, 0x05, 0x8f, 0xf8, 0xb3, 0x41, 0x2e, 0x43, 0x63, + 0x3e, 0x20, 0x1b, 0x86, 0xf2, 0xa1, 0x45, 0x86, 0x84, 0x88, 0xf7, 0x50, 0x09, 0xd8, 0xb6, 0xa0, 0x4c, 0x0a, 0x38, + 0x8b, 0x06, 0xbf, 0xd7, 0x5e, 0x0e, 0xbc, 0x07, 0x91, 0xdf, 0x48, 0x97, 0x72, 0x89, 0x8d, 0x4e, 0x1c, 0xcb, 0x42, + 0x3b, 0x8f, 0xeb, 0xaf, 0x63, 0x50, 0xbf, 0x57, 0xfa, 0x0d, 0xca, 0xd9, 0x1f, 0x25, 0xeb, 0xb4, 0xf1, 0xc4, 0xf8, + 0x97, 0xab, 0xfc, 0x53, 0xb4, 0xd4, 0xc3, 0xff, 0x67, 0x4c, 0xa1, 0xf4, 0x2f, 0xd3, 0x32, 0xda, 0xac, 0x16, 0xa2, + 0x14, 0x79, 0x24, 0x4e, 0xbe, 0x16, 0xd9, 0xb9, 0x7c, 0xe7, 0x53, 0xe8, 0x17, 0x80, 0x96, 0x7d, 0x82, 0x8c, 0xfe, + 0x8d, 0x09, 0x3e, 0xfc, 0x4d, 0x3b, 0xd7, 0xe6, 0x7c, 0x3c, 0xc9, 0xaf, 0xac, 0xbd, 0xdb, 0xf1, 0x22, 0x31, 0x8a, + 0xb1, 0xdc, 0x57, 0xdd, 0xac, 0x9c, 0xa8, 0xe4, 0xc0, 0x48, 0xd7, 0x64, 0x2f, 0x57, 0xb2, 0x6e, 0xa7, 0x5b, 0x09, + 0x44, 0x54, 0x81, 0xf7, 0x18, 0x57, 0xb1, 0x8f, 0x60, 0xba, 0xee, 0xb8, 0x8c, 0x76, 0xbc, 0x67, 0xbc, 0x3a, 0x51, + 0x56, 0x70, 0xbb, 0x11, 0xed, 0x09, 0x1d, 0xfd, 0x34, 0xa9, 0x2d, 0x0b, 0x07, 0x20, 0x77, 0x09, 0x63, 0xd9, 0x10, + 0xac, 0x18, 0x94, 0xbe, 0x5e, 0x53, 0xb2, 0x2c, 0xc0, 0xa2, 0xb3, 0xcb, 0x08, 0xc4, 0xb0, 0x6e, 0x9a, 0x13, 0x3a, + 0x5e, 0xba, 0x38, 0xef, 0xb5, 0x8a, 0x14, 0x3c, 0xa3, 0x45, 0xc7, 0xdc, 0x74, 0xa4, 0x1b, 0xa3, 0xbd, 0x7d, 0x61, + 0x10, 0x52, 0x3c, 0x7f, 0x60, 0xab, 0x75, 0x71, 0x91, 0x78, 0x85, 0x4c, 0xb4, 0x20, 0x96, 0x22, 0x30, 0xe3, 0x85, + 0xa6, 0x11, 0x26, 0x28, 0x53, 0x82, 0x45, 0x6b, 0x74, 0x68, 0x7f, 0x58, 0xc2, 0xee, 0x31, 0x46, 0x80, 0x40, 0x95, + 0xe9, 0x45, 0xd8, 0x9a, 0x30, 0x9b, 0xba, 0xd8, 0x00, 0x6d, 0x15, 0x43, 0x83, 0xb0, 0x36, 0xc4, 0x7c, 0x4c, 0xf3, + 0xe5, 0x3f, 0xb1, 0x18, 0xdb, 0x13, 0x88, 0xed, 0xdd, 0xae, 0x49, 0x98, 0xee, 0xb5, 0xb8, 0xb1, 0x5e, 0x6e, 0x4f, + 0x39, 0xa6, 0x76, 0xac, 0x8d, 0xda, 0xb1, 0x16, 0x7a, 0xc7, 0x5a, 0xeb, 0x1d, 0x6b, 0xd9, 0xf0, 0x47, 0x99, 0x17, + 0xb3, 0x04, 0xf4, 0xbb, 0x2b, 0xae, 0x1a, 0x04, 0xcd, 0xd8, 0xb0, 0x5b, 0xf8, 0x2d, 0xb1, 0x76, 0x4b, 0xff, 0x62, + 0xc1, 0x6e, 0x4c, 0x1f, 0xe8, 0xd6, 0x01, 0x96, 0x11, 0x35, 0xf9, 0x0e, 0x79, 0x37, 0x9d, 0x15, 0x85, 0xdb, 0x13, + 0xbb, 0xf1, 0xd9, 0x5b, 0xf3, 0xe6, 0xdd, 0x93, 0x08, 0x72, 0xef, 0xb8, 0x77, 0x37, 0x7c, 0xeb, 0x5f, 0xe8, 0x16, + 0xc8, 0xc9, 0x2c, 0x67, 0x20, 0x75, 0xc4, 0x27, 0x88, 0x56, 0xf6, 0x94, 0xef, 0x84, 0xdc, 0xd9, 0xd6, 0x4f, 0xee, + 0xdc, 0x6d, 0x6d, 0xf9, 0xe4, 0x8e, 0x55, 0x23, 0x8a, 0x15, 0xa7, 0x29, 0x12, 0x66, 0xd1, 0x06, 0x78, 0xea, 0xe5, + 0xfb, 0x1d, 0x3b, 0xe6, 0x70, 0xf7, 0xa4, 0xa3, 0xe3, 0xe5, 0x1c, 0xb0, 0xbb, 0xff, 0x68, 0x13, 0x36, 0x56, 0xba, + 0x56, 0xa1, 0xc3, 0xdd, 0x93, 0x4c, 0xe3, 0x39, 0x1c, 0xc9, 0xa7, 0x63, 0x8d, 0x0d, 0x82, 0xba, 0x3e, 0x67, 0x50, + 0x3b, 0x76, 0x5f, 0x13, 0x76, 0xd9, 0x31, 0xaf, 0x75, 0xcd, 0xdb, 0x2b, 0x4f, 0xc5, 0x86, 0x80, 0x0e, 0x5f, 0xab, + 0x1b, 0xe4, 0x5f, 0x02, 0xa7, 0x08, 0x00, 0x39, 0x1c, 0x2f, 0x79, 0xec, 0xfb, 0x34, 0x4b, 0xeb, 0x1d, 0x6a, 0x2d, + 0x2a, 0xcb, 0x32, 0xac, 0xbd, 0x1f, 0xb4, 0x62, 0x58, 0x6a, 0xfa, 0xa7, 0xe3, 0xc0, 0xed, 0x6c, 0xb7, 0x32, 0x76, + 0x19, 0x4f, 0x8a, 0x8b, 0xdf, 0x4e, 0x0b, 0xe5, 0xda, 0xcd, 0xdb, 0xf8, 0x4d, 0xab, 0x25, 0x4b, 0x6b, 0x3d, 0xe4, + 0xa5, 0x65, 0x11, 0x81, 0x00, 0x86, 0x23, 0x65, 0x17, 0x4b, 0xb8, 0x47, 0x58, 0xdd, 0x83, 0x50, 0x32, 0x2f, 0x5c, + 0x3c, 0x65, 0x31, 0x24, 0x02, 0x6c, 0x77, 0xa8, 0xd8, 0x16, 0x2e, 0x9e, 0xb2, 0x0d, 0x2f, 0xfa, 0xfd, 0x4c, 0x75, + 0x0a, 0x59, 0x77, 0x16, 0x7c, 0xa3, 0x9a, 0x63, 0x0d, 0x35, 0x5b, 0x9b, 0x64, 0x6b, 0x9c, 0xdb, 0x8a, 0x8f, 0x65, + 0x5b, 0xf1, 0xb1, 0xb2, 0xd6, 0xa5, 0x7b, 0xbd, 0x47, 0x75, 0x01, 0x6c, 0xfd, 0xb7, 0xc7, 0x2b, 0xd7, 0xf3, 0x19, + 0x01, 0x7c, 0xdd, 0xf0, 0xf1, 0xe4, 0x06, 0xbd, 0x4a, 0x6e, 0xfc, 0xdb, 0x81, 0x1a, 0x7f, 0xa7, 0x73, 0x6f, 0x00, + 0xba, 0x92, 0xf2, 0x0a, 0xc8, 0x3b, 0xc8, 0x31, 0xb7, 0xec, 0xca, 0xbb, 0x93, 0xef, 0xb0, 0xb7, 0xbc, 0x9e, 0xdd, + 0xcc, 0xd9, 0x0e, 0x9c, 0x0a, 0x92, 0x81, 0xbd, 0xac, 0xd8, 0x2e, 0x88, 0xed, 0x84, 0xdf, 0x09, 0x98, 0xf2, 0x39, + 0x04, 0x71, 0x05, 0xb7, 0x10, 0x87, 0x27, 0xff, 0x1c, 0xdc, 0xb5, 0x36, 0xeb, 0x3b, 0x66, 0x75, 0x4e, 0xb0, 0x66, + 0x56, 0x0f, 0x06, 0x8b, 0x66, 0xb2, 0xea, 0xf7, 0xbd, 0x9d, 0x76, 0x7c, 0x5a, 0x4a, 0x9d, 0xd8, 0x69, 0xad, 0xd6, + 0x0d, 0x7b, 0x2b, 0xb5, 0x2e, 0xc6, 0xd0, 0x03, 0xc4, 0x4f, 0xb7, 0x03, 0x7e, 0xd7, 0xb1, 0xb6, 0xbc, 0xb7, 0xec, + 0x86, 0xed, 0xe0, 0x12, 0xd4, 0xb4, 0x97, 0xfd, 0x49, 0xe5, 0x82, 0x76, 0xec, 0x92, 0x78, 0x38, 0x63, 0x56, 0x29, + 0x33, 0xeb, 0xa4, 0xba, 0x12, 0x9d, 0x31, 0x9d, 0xb5, 0x9e, 0xcf, 0xd5, 0x7c, 0x52, 0x68, 0x50, 0xbf, 0x73, 0xe2, + 0x23, 0x2a, 0x3a, 0x4f, 0x60, 0x6b, 0x59, 0x41, 0xac, 0xf6, 0x39, 0x58, 0x6b, 0xb5, 0x4b, 0xbf, 0x97, 0x0f, 0xb8, + 0x4d, 0x39, 0xac, 0x03, 0x83, 0x9a, 0x13, 0x2b, 0xea, 0x21, 0xdb, 0x31, 0x6e, 0x7e, 0x7a, 0xf9, 0x83, 0x13, 0x96, + 0xac, 0x58, 0xed, 0x4f, 0x7f, 0x7b, 0xe2, 0xe9, 0xef, 0xd4, 0xfe, 0x85, 0xf0, 0x83, 0xf1, 0xbf, 0x6b, 0xf7, 0xb5, + 0x16, 0xa3, 0xb2, 0x55, 0x8e, 0xd0, 0xb8, 0x5b, 0x49, 0x93, 0xe5, 0x27, 0xe1, 0x09, 0x6b, 0xc1, 0xb3, 0x5c, 0x2f, + 0xd1, 0xac, 0x80, 0x15, 0xd6, 0x32, 0x09, 0x57, 0x18, 0xab, 0xa5, 0xad, 0xbe, 0x45, 0xd3, 0x1c, 0x1f, 0xce, 0xb5, + 0x41, 0x99, 0x72, 0x76, 0x46, 0xac, 0x86, 0xcb, 0xb0, 0x34, 0xa1, 0x08, 0xd9, 0xbd, 0x1d, 0xdc, 0xd8, 0x29, 0x4b, + 0x29, 0xc3, 0x39, 0x06, 0x13, 0x1e, 0x89, 0x51, 0x95, 0xef, 0xef, 0x4b, 0x8a, 0x9c, 0xb6, 0xe5, 0xa0, 0x0a, 0x61, + 0x1f, 0x49, 0x94, 0xc0, 0xad, 0x48, 0x0b, 0x45, 0xca, 0xe2, 0x6f, 0x07, 0xe8, 0x02, 0x2f, 0xa0, 0xae, 0x46, 0xdd, + 0xfe, 0x70, 0xc4, 0xc3, 0x07, 0xa6, 0x3e, 0x30, 0x62, 0x49, 0xa0, 0xb6, 0xe7, 0x59, 0xba, 0x04, 0x15, 0x7e, 0x0f, + 0x57, 0x13, 0xb1, 0x9f, 0x5b, 0x52, 0x54, 0x64, 0x23, 0xbd, 0xa1, 0x35, 0x78, 0x84, 0xd6, 0x94, 0x17, 0x4e, 0xaa, + 0x4d, 0x3a, 0xef, 0x08, 0x39, 0x56, 0xdf, 0x5a, 0xc2, 0x68, 0x57, 0xf4, 0xe2, 0xde, 0xd1, 0x7b, 0x9e, 0xae, 0x7a, + 0xee, 0x4f, 0x5c, 0x31, 0x4f, 0x6e, 0x23, 0x50, 0xb7, 0x82, 0xea, 0xf6, 0x5e, 0x25, 0x58, 0xb0, 0xa4, 0xdd, 0xc7, + 0x6f, 0x67, 0xed, 0x40, 0x54, 0xc6, 0x2a, 0x7d, 0x4b, 0x12, 0xf6, 0xc4, 0xa0, 0x53, 0xa8, 0xca, 0xed, 0xee, 0x68, + 0x0b, 0x5c, 0xc7, 0x2c, 0x45, 0xcf, 0x6d, 0x91, 0xbb, 0xe5, 0xdf, 0x3d, 0x57, 0xe4, 0xec, 0x97, 0x80, 0xe0, 0xd4, + 0x7c, 0x43, 0x7c, 0x39, 0xc2, 0xa3, 0xea, 0x16, 0x38, 0x4e, 0xdf, 0x01, 0xfc, 0xc3, 0xe1, 0x12, 0x34, 0x01, 0xb1, + 0x60, 0xbd, 0x34, 0xee, 0xb1, 0x5e, 0x5c, 0x6c, 0x96, 0x49, 0xbe, 0x01, 0x67, 0x06, 0x4a, 0xb5, 0xf4, 0x03, 0xc7, + 0x6a, 0x01, 0x15, 0x0e, 0x66, 0x27, 0xf5, 0xc2, 0x32, 0xea, 0x31, 0x7d, 0x7e, 0x06, 0x7b, 0x47, 0x48, 0x00, 0xdc, + 0x2f, 0xfb, 0x80, 0x04, 0x3c, 0x74, 0x66, 0x07, 0x84, 0x13, 0x66, 0x51, 0x15, 0x48, 0x24, 0x47, 0xfa, 0xd9, 0x63, + 0x26, 0x92, 0x3f, 0x98, 0xf5, 0x9c, 0x53, 0xa2, 0xc7, 0x7a, 0xea, 0x08, 0xe9, 0xb1, 0x9e, 0x75, 0x44, 0xf4, 0x58, + 0xcf, 0x3a, 0x3e, 0x7a, 0xac, 0x67, 0x8e, 0x9d, 0x1e, 0x04, 0x26, 0x40, 0xe4, 0x01, 0xeb, 0xd1, 0x64, 0xea, 0x29, + 0xee, 0x01, 0xa2, 0x41, 0x60, 0x3d, 0x29, 0x9c, 0xf7, 0x00, 0x79, 0x8c, 0xc4, 0xea, 0xa0, 0xf7, 0x1f, 0xe3, 0xc7, + 0x3d, 0x23, 0x23, 0x8f, 0x5b, 0x87, 0xd5, 0xff, 0xfa, 0x4f, 0x08, 0x80, 0xc3, 0xb3, 0xa9, 0x77, 0x39, 0x86, 0xac, + 0xb2, 0x8c, 0x40, 0xf2, 0x13, 0x83, 0x2f, 0x5f, 0x00, 0x54, 0x7d, 0xa6, 0x6b, 0x35, 0x39, 0x6a, 0x8f, 0x39, 0x74, + 0xc5, 0x00, 0xb0, 0x0d, 0x4b, 0x54, 0xd5, 0xc2, 0x26, 0x2c, 0x6e, 0x3f, 0xc3, 0x68, 0x2e, 0x9b, 0x5e, 0xd0, 0x40, + 0x3d, 0x42, 0xf0, 0x4b, 0xeb, 0xa1, 0xb5, 0x96, 0x29, 0x87, 0xae, 0x8d, 0xa2, 0xca, 0x86, 0xba, 0x84, 0xd5, 0x5a, + 0x44, 0x35, 0x51, 0xa4, 0x5c, 0x32, 0x8a, 0x62, 0xa9, 0x82, 0x7d, 0x26, 0x96, 0x10, 0x35, 0x4f, 0x5b, 0x6d, 0x15, + 0xec, 0x97, 0x80, 0xb0, 0x16, 0xd6, 0x42, 0x3a, 0x83, 0xda, 0x3b, 0xfd, 0x48, 0xf9, 0xcb, 0x0b, 0xb9, 0x9d, 0x5b, + 0x28, 0xc2, 0xed, 0x39, 0x28, 0x6f, 0xea, 0xaa, 0x54, 0x44, 0xa3, 0x25, 0x50, 0xca, 0x9c, 0x20, 0xb2, 0x00, 0x01, + 0x1c, 0x37, 0x10, 0xf8, 0xbc, 0xc6, 0x27, 0xd0, 0x28, 0x04, 0xf2, 0x03, 0xab, 0x70, 0xed, 0x21, 0x2d, 0xb5, 0x46, + 0x44, 0x89, 0xf8, 0xd1, 0xd5, 0x73, 0x6c, 0x5f, 0x3d, 0x8d, 0xb5, 0xa5, 0x34, 0x41, 0xfc, 0xc4, 0x62, 0x0b, 0x31, + 0x41, 0x54, 0x87, 0xe8, 0x08, 0x96, 0x13, 0x42, 0x14, 0xfe, 0x14, 0xfa, 0xa9, 0x81, 0xbf, 0x64, 0x8b, 0x22, 0xaf, + 0x09, 0x16, 0xb3, 0x62, 0x80, 0x56, 0x45, 0xe0, 0x99, 0xce, 0x96, 0xca, 0x9c, 0xe6, 0xd1, 0x91, 0x1d, 0x9c, 0x77, + 0x1d, 0xec, 0xa5, 0x2f, 0x63, 0x27, 0xcb, 0xa6, 0x51, 0x1b, 0x1b, 0x22, 0xe1, 0x15, 0xf9, 0xcb, 0x2c, 0x35, 0xce, + 0x91, 0xb9, 0x5c, 0xdf, 0x75, 0xb1, 0x5c, 0xd2, 0x36, 0x61, 0x15, 0x22, 0xd4, 0x6d, 0x43, 0xe5, 0x52, 0x98, 0x8d, + 0x4d, 0xd3, 0x00, 0x5f, 0x28, 0x2a, 0x95, 0xaa, 0xd4, 0x56, 0x2a, 0x39, 0xe1, 0x5d, 0xdf, 0xd4, 0x22, 0x75, 0x45, + 0xb0, 0x8d, 0x19, 0xea, 0xa1, 0xdc, 0xa8, 0xb1, 0x6f, 0x3b, 0x56, 0xe9, 0x1d, 0x26, 0xc8, 0x19, 0x79, 0x91, 0x83, + 0x8b, 0x92, 0x82, 0xcc, 0xd5, 0x10, 0xe6, 0x0f, 0x1a, 0x3e, 0x2d, 0x2c, 0xf7, 0x50, 0x02, 0x66, 0x47, 0x0d, 0x0f, + 0x23, 0x04, 0x22, 0x2e, 0x95, 0x7d, 0xc5, 0xc4, 0xef, 0x29, 0x98, 0x25, 0x13, 0xba, 0x17, 0xb1, 0x28, 0x42, 0x1b, + 0x9f, 0x24, 0xc9, 0xd4, 0xd3, 0x14, 0xdc, 0xc8, 0x65, 0x98, 0xa3, 0x11, 0x5a, 0xf2, 0x91, 0x03, 0xe9, 0x6b, 0x39, + 0x95, 0xe0, 0x23, 0xea, 0x14, 0x70, 0x3c, 0x3f, 0x2f, 0xac, 0x9f, 0x2c, 0x97, 0x98, 0xcb, 0xda, 0xfc, 0x97, 0x1d, + 0x1d, 0x83, 0x5d, 0x9e, 0x26, 0x8e, 0xab, 0xff, 0xa8, 0x4a, 0x8a, 0xfb, 0x5f, 0xd2, 0x1c, 0x50, 0x04, 0x33, 0x7b, + 0x8a, 0xf1, 0xb1, 0xcf, 0x32, 0x05, 0xfc, 0xed, 0x7a, 0x6b, 0xc9, 0xc4, 0x2e, 0x69, 0x37, 0x57, 0xc6, 0x2f, 0xb5, + 0x61, 0xc7, 0xc1, 0xb9, 0x01, 0x28, 0xce, 0x1a, 0x1d, 0x96, 0xd7, 0xba, 0x6d, 0x55, 0xa8, 0x40, 0xad, 0xff, 0xbd, + 0x5b, 0x98, 0xf2, 0x36, 0x2f, 0x95, 0xb7, 0x79, 0x68, 0x02, 0x04, 0x22, 0x33, 0xe4, 0x59, 0xd3, 0x31, 0x49, 0xdc, + 0x3b, 0x52, 0xd2, 0xbe, 0x23, 0xc5, 0x0f, 0xde, 0x91, 0x90, 0x6f, 0x09, 0x1d, 0xd9, 0x17, 0x9c, 0x9c, 0x40, 0x99, + 0xc1, 0x5e, 0x5e, 0x33, 0xd9, 0x3f, 0xa0, 0xbd, 0x70, 0x2e, 0xcb, 0x2b, 0xfe, 0x56, 0x78, 0x6b, 0x7f, 0xba, 0x3e, + 0xed, 0xaa, 0x7a, 0xfb, 0x8d, 0x99, 0x79, 0x38, 0x14, 0x87, 0x43, 0x65, 0x82, 0x76, 0x6f, 0xb8, 0x18, 0xe4, 0xec, + 0xce, 0x8d, 0x8f, 0x7f, 0xcb, 0x51, 0xc4, 0x56, 0xca, 0x23, 0xe9, 0x42, 0x25, 0x86, 0x97, 0x06, 0x1e, 0x66, 0xc7, + 0xc7, 0x93, 0xdd, 0xd5, 0xdd, 0x64, 0x30, 0xd8, 0xa9, 0xbe, 0xdd, 0xf2, 0x7a, 0xb6, 0x9b, 0xb3, 0x7b, 0x7e, 0x3b, + 0xdd, 0x06, 0xfb, 0x06, 0xb6, 0xdd, 0xdd, 0x95, 0x38, 0x1c, 0x76, 0xcf, 0xf8, 0x8d, 0xbf, 0xbf, 0x47, 0x40, 0x67, + 0x7e, 0x3e, 0x6e, 0x63, 0xfc, 0x5c, 0xb7, 0x5d, 0xb5, 0x76, 0x00, 0x4f, 0xff, 0xa3, 0x77, 0x3d, 0x5b, 0xcc, 0x7d, + 0xf6, 0x88, 0xdf, 0x83, 0x7f, 0x3e, 0x6e, 0x92, 0x48, 0x7d, 0xa2, 0x5d, 0x26, 0xaf, 0xc1, 0x81, 0x7c, 0xe7, 0xb3, + 0x57, 0xfc, 0x7e, 0xb6, 0x98, 0xf3, 0xe2, 0x70, 0x78, 0x3f, 0x0d, 0x91, 0xac, 0x29, 0xac, 0x88, 0x25, 0xc5, 0xf3, + 0x83, 0xf0, 0xf8, 0xbd, 0x88, 0x0c, 0x91, 0x96, 0x7b, 0x77, 0xc8, 0xae, 0x59, 0xe4, 0x07, 0xf0, 0x41, 0xb6, 0xf3, + 0x27, 0xb2, 0xa6, 0x74, 0xbf, 0x78, 0xe4, 0x1f, 0x0e, 0xf4, 0xd7, 0x2b, 0xff, 0x70, 0x78, 0xcf, 0xee, 0x11, 0x1c, + 0x9d, 0xef, 0xa0, 0x7f, 0xf4, 0xad, 0x03, 0xaa, 0x32, 0x7c, 0x3b, 0xdb, 0xcc, 0xfd, 0x67, 0x2b, 0xb6, 0x04, 0x2e, + 0x14, 0xe5, 0x85, 0x76, 0xcd, 0xee, 0xd1, 0xeb, 0x8c, 0x9c, 0x88, 0x66, 0xbb, 0xb9, 0xcf, 0x62, 0x7c, 0xae, 0xee, + 0x8b, 0xc9, 0x37, 0xef, 0x8b, 0x3b, 0xb6, 0xed, 0xbe, 0x2f, 0xca, 0x37, 0xdd, 0xf5, 0xb3, 0x65, 0x3b, 0x76, 0x0f, + 0x33, 0xec, 0x2d, 0xbf, 0x6e, 0x8e, 0x1d, 0x63, 0xbf, 0x79, 0x63, 0x04, 0x50, 0x66, 0x0b, 0x16, 0x0b, 0x0e, 0x4a, + 0xb5, 0x6a, 0x5b, 0x12, 0x79, 0xa5, 0x03, 0xd5, 0x66, 0x04, 0xf7, 0xd5, 0x42, 0xce, 0x3c, 0x33, 0xd0, 0xb7, 0x15, + 0xa2, 0x85, 0xc3, 0x06, 0xfc, 0x8d, 0xb6, 0x8e, 0x31, 0x4c, 0xb3, 0x9a, 0x69, 0x5b, 0xd4, 0xe5, 0xf7, 0xbd, 0x67, + 0xf2, 0x1b, 0x19, 0xd8, 0x42, 0x24, 0x85, 0xe3, 0xf8, 0xe2, 0xe9, 0x09, 0xff, 0x55, 0xcb, 0xa3, 0x56, 0xfb, 0x85, + 0x52, 0x9f, 0xbe, 0xa4, 0x23, 0x9a, 0xb8, 0x17, 0x6d, 0x19, 0xd6, 0x28, 0x6b, 0x6a, 0xe9, 0x30, 0x8c, 0x6b, 0xd8, + 0x97, 0x07, 0x0e, 0x7d, 0x07, 0x04, 0xda, 0x2a, 0x95, 0x02, 0x2d, 0x1c, 0xc3, 0x28, 0xcc, 0x42, 0xca, 0xc3, 0xc2, + 0x2c, 0xe5, 0x3d, 0x16, 0x68, 0x71, 0xab, 0xee, 0x31, 0xb5, 0xdd, 0x82, 0x08, 0xab, 0xb7, 0x8c, 0xf3, 0xcb, 0x46, + 0x15, 0x6e, 0x0b, 0x50, 0x14, 0x41, 0x19, 0xec, 0x49, 0x6e, 0xbb, 0x51, 0xd2, 0x6c, 0x14, 0xd6, 0x62, 0x59, 0x94, + 0xbb, 0x5e, 0xc3, 0x6e, 0xf0, 0x82, 0xaa, 0x9f, 0x10, 0xb6, 0x65, 0xcf, 0x3a, 0x94, 0x8b, 0xf4, 0xdf, 0xb2, 0xf4, + 0x7c, 0xbf, 0x35, 0xe7, 0x7f, 0xfa, 0x8a, 0x3e, 0x2a, 0xff, 0xfd, 0x4b, 0xfa, 0xc9, 0x60, 0x19, 0x39, 0xa5, 0x7e, + 0x8a, 0x46, 0xb7, 0x69, 0x4e, 0x18, 0x5b, 0xbe, 0x7e, 0xfa, 0x1d, 0x32, 0x05, 0xc9, 0xa1, 0x94, 0xaa, 0x9c, 0xec, + 0xa1, 0x2f, 0xbc, 0xee, 0xc3, 0x4c, 0x30, 0x00, 0xe1, 0x35, 0xda, 0x54, 0x13, 0x26, 0xf1, 0xe0, 0x0a, 0xfe, 0x6f, + 0x04, 0x31, 0x68, 0x9f, 0x28, 0xea, 0xd8, 0x36, 0xd2, 0x75, 0xdb, 0x39, 0x48, 0xee, 0xd4, 0x95, 0x3f, 0x2a, 0x27, + 0xff, 0x8e, 0x86, 0xc8, 0x2b, 0xae, 0x10, 0x2b, 0x0b, 0x2e, 0xb1, 0x18, 0x2a, 0x52, 0x80, 0x6b, 0x08, 0x22, 0x65, + 0x51, 0x52, 0xb8, 0xe5, 0xa0, 0x2a, 0x02, 0x30, 0xae, 0x56, 0x47, 0x9d, 0x08, 0x1f, 0xb7, 0xd6, 0x22, 0x04, 0x2b, + 0x1a, 0xb5, 0xb2, 0x56, 0xe0, 0x0b, 0xd2, 0x97, 0x0e, 0x05, 0x31, 0x3d, 0x0a, 0xa9, 0x2a, 0x1d, 0x0a, 0xa4, 0x39, + 0x54, 0x7c, 0x63, 0xb0, 0x51, 0x54, 0xa4, 0xe7, 0x2f, 0x4d, 0x4a, 0x2e, 0x8d, 0x19, 0x1f, 0x44, 0x19, 0x89, 0xbc, + 0x0e, 0x97, 0x62, 0x5a, 0x20, 0xdf, 0xe8, 0xf1, 0x83, 0xe0, 0x12, 0xde, 0x0d, 0xb9, 0x57, 0x80, 0x2d, 0x01, 0x3b, + 0xc0, 0xbd, 0x32, 0xa3, 0x5c, 0xa7, 0x75, 0xfd, 0xd6, 0x7a, 0x28, 0x86, 0xe1, 0x13, 0x4b, 0x60, 0x3b, 0x5a, 0x47, + 0x47, 0x7a, 0xf8, 0xf0, 0xbf, 0xae, 0x6a, 0x8e, 0x3a, 0x95, 0xcb, 0xd9, 0xf1, 0x84, 0xa5, 0x88, 0x19, 0x74, 0x7f, + 0xdd, 0xbe, 0x14, 0x40, 0xb7, 0xcb, 0x62, 0x9e, 0x8d, 0x76, 0xf2, 0x6f, 0xe9, 0xc6, 0x8a, 0xd2, 0x26, 0xde, 0x65, + 0xbd, 0xb1, 0x3f, 0x1c, 0xfd, 0xc7, 0x93, 0x77, 0x13, 0x42, 0xd5, 0xd9, 0xb0, 0xb5, 0x8e, 0x73, 0xf9, 0x5f, 0xff, + 0x39, 0x26, 0x2b, 0x08, 0x0a, 0xc2, 0xb2, 0x53, 0x4c, 0x54, 0x30, 0x8a, 0x14, 0x6b, 0x3e, 0x9e, 0xac, 0x51, 0x27, + 0xbc, 0xf6, 0x17, 0x5a, 0x27, 0x4c, 0x8c, 0xac, 0x54, 0xfe, 0x9a, 0x55, 0x6c, 0xa9, 0x32, 0x0b, 0xc8, 0x3c, 0xc8, + 0x27, 0x6b, 0xa3, 0xc1, 0x5c, 0xf1, 0x7a, 0xb6, 0x9e, 0x4b, 0xe5, 0x33, 0x98, 0x72, 0x16, 0x83, 0x93, 0xa5, 0xb0, + 0x3b, 0x12, 0x28, 0x5a, 0x33, 0x74, 0xed, 0x4f, 0xb1, 0x55, 0xaf, 0xd2, 0xaa, 0x06, 0x78, 0x40, 0x88, 0x81, 0xa1, + 0xf6, 0x6a, 0xe1, 0xa1, 0xb5, 0x00, 0xd6, 0xfe, 0xa8, 0xf4, 0x83, 0xf1, 0x64, 0xc1, 0x6f, 0x90, 0x7f, 0x39, 0x72, + 0xd4, 0xee, 0xfd, 0xbe, 0x77, 0x07, 0x52, 0x70, 0xe4, 0x5a, 0x28, 0x90, 0x08, 0xe8, 0x86, 0x6f, 0x7c, 0xe5, 0x83, + 0xf1, 0x16, 0xb5, 0xd5, 0xa0, 0xa0, 0x76, 0x74, 0xcb, 0x63, 0x47, 0xef, 0x7c, 0x77, 0x42, 0x5f, 0x7d, 0xa3, 0x85, + 0xe3, 0x6f, 0x9c, 0x91, 0x6b, 0xb6, 0xea, 0x90, 0x23, 0x9a, 0x49, 0x87, 0x10, 0xb1, 0x62, 0x6b, 0xf6, 0x96, 0x54, + 0xce, 0x9d, 0x43, 0x76, 0xfa, 0x08, 0x55, 0x7a, 0xad, 0x87, 0xb7, 0x13, 0xa5, 0xbb, 0x3d, 0xde, 0x4d, 0xbe, 0x67, + 0x13, 0x11, 0x83, 0x01, 0x6d, 0x10, 0xce, 0xc8, 0x3a, 0x44, 0x2a, 0x1d, 0x20, 0x04, 0x8e, 0x09, 0x68, 0xfa, 0xaf, + 0x6f, 0x49, 0x14, 0x70, 0xa4, 0x8d, 0x90, 0xb5, 0xec, 0x70, 0xc8, 0x41, 0xa3, 0xdc, 0xfc, 0xe9, 0x15, 0xea, 0x34, + 0x07, 0xe6, 0xe9, 0x12, 0xf6, 0x1c, 0x3c, 0xd2, 0x8b, 0xe3, 0x23, 0xfd, 0xbf, 0xa3, 0x89, 0x1a, 0xff, 0xfb, 0x9a, + 0x28, 0xa5, 0x45, 0x72, 0x54, 0x4b, 0xdf, 0xa5, 0x8e, 0x82, 0x8b, 0xbc, 0xa3, 0x16, 0xb2, 0x67, 0xd9, 0xb8, 0x51, + 0xcd, 0xfb, 0xff, 0xb5, 0x32, 0xff, 0x5f, 0xd3, 0xca, 0x30, 0x25, 0x3b, 0x96, 0x6a, 0xe6, 0x81, 0x56, 0x31, 0xcc, + 0x7e, 0x21, 0x09, 0x91, 0xe1, 0xd2, 0x80, 0x1f, 0x55, 0xb0, 0x8f, 0xd3, 0x6a, 0x9d, 0x85, 0x3b, 0x54, 0xa2, 0xde, + 0x8a, 0x65, 0x9a, 0x3f, 0xaf, 0xff, 0x25, 0xca, 0x02, 0xa6, 0xf6, 0xb2, 0x4c, 0xe3, 0x80, 0x2c, 0xfc, 0x59, 0x58, + 0xe2, 0xe4, 0xc6, 0x36, 0xfe, 0x22, 0xc7, 0xd3, 0x7e, 0xd5, 0x99, 0x79, 0x20, 0x81, 0x1a, 0xe8, 0x42, 0x72, 0x2e, + 0x2b, 0x8b, 0x7b, 0x84, 0x6e, 0xfe, 0xb1, 0x2c, 0x8b, 0xd2, 0xeb, 0x7d, 0x4a, 0xd2, 0xea, 0x6c, 0x25, 0xea, 0xa4, + 0x88, 0x15, 0x94, 0x4d, 0x0a, 0x30, 0xfa, 0xb0, 0xf2, 0x44, 0x1c, 0x9c, 0x21, 0x50, 0xc3, 0x59, 0x9d, 0x84, 0x00, + 0x34, 0xac, 0x10, 0xf6, 0xcf, 0xa0, 0x85, 0x67, 0x61, 0x1c, 0xae, 0x01, 0x26, 0x27, 0xad, 0xce, 0xd6, 0x65, 0x71, + 0x97, 0xc6, 0x22, 0x1e, 0xf5, 0x14, 0x25, 0xcb, 0xeb, 0xdc, 0x95, 0x73, 0xfd, 0xfd, 0x9f, 0x14, 0xc0, 0x6e, 0xc0, + 0x6c, 0x5b, 0x60, 0x07, 0x00, 0x09, 0x0a, 0x64, 0x0b, 0x75, 0x1a, 0x9d, 0xa9, 0xa5, 0x02, 0xef, 0xb9, 0x1e, 0xe0, + 0xaf, 0x73, 0xc0, 0x32, 0xae, 0x0b, 0x19, 0x30, 0x82, 0x00, 0x46, 0xe0, 0xa0, 0x04, 0x0c, 0x9d, 0x21, 0x6e, 0xab, + 0x72, 0xd6, 0x42, 0x73, 0xa5, 0xdb, 0x92, 0x9b, 0x46, 0x39, 0x5b, 0x89, 0x00, 0xfa, 0xea, 0xa6, 0xc4, 0xe9, 0x62, + 0xd1, 0x4a, 0xc2, 0xbe, 0x7d, 0xdf, 0x4e, 0x15, 0x79, 0x7c, 0x94, 0x86, 0xbc, 0x02, 0xcf, 0x33, 0x8e, 0x24, 0x51, + 0x22, 0x78, 0x9d, 0x37, 0x66, 0x1c, 0x7e, 0x6c, 0x53, 0x4e, 0xed, 0xcd, 0x7a, 0x01, 0x38, 0x4f, 0xd0, 0x96, 0x01, + 0xc6, 0x02, 0x06, 0xe7, 0x42, 0x2c, 0x79, 0x8a, 0xe0, 0x97, 0x4e, 0xa4, 0x30, 0xee, 0x72, 0x18, 0xe6, 0x41, 0xd1, + 0xbb, 0xa4, 0xfe, 0xe8, 0xf7, 0x51, 0x9b, 0x0c, 0x86, 0xa0, 0x12, 0x40, 0x65, 0xdd, 0x20, 0x31, 0xb0, 0x2a, 0xdd, + 0x48, 0x5c, 0x42, 0xbc, 0xcc, 0x57, 0x53, 0x11, 0x05, 0xef, 0xeb, 0x09, 0x21, 0x9c, 0x60, 0x7c, 0x88, 0x1b, 0x20, + 0x60, 0xb0, 0x8a, 0x0b, 0x0c, 0x92, 0xe7, 0x12, 0xdd, 0x1f, 0xcf, 0x77, 0x0c, 0x70, 0xe5, 0xbc, 0xa7, 0xda, 0xd5, + 0x03, 0x7b, 0xb9, 0x4a, 0x97, 0x8c, 0x10, 0x56, 0xfc, 0x5f, 0x44, 0xde, 0xb7, 0xc3, 0x04, 0xd4, 0x36, 0xf2, 0xc7, + 0x20, 0x31, 0x97, 0x89, 0x22, 0x88, 0x47, 0x59, 0xc1, 0x92, 0x34, 0xd8, 0x8c, 0x92, 0x14, 0x34, 0x9a, 0x18, 0x43, + 0xa6, 0x42, 0x3b, 0x24, 0x8d, 0x66, 0x63, 0xb2, 0x8f, 0x21, 0xaf, 0xe1, 0x62, 0xb1, 0xc0, 0xfb, 0x7e, 0x11, 0xaa, + 0x83, 0x6d, 0x69, 0x0e, 0x01, 0x27, 0x09, 0xf6, 0xd4, 0x15, 0x29, 0x09, 0xb3, 0xd1, 0xa7, 0x90, 0x73, 0x03, 0x3a, + 0x4e, 0x1a, 0x43, 0xf5, 0x81, 0x49, 0x78, 0x15, 0xa1, 0x93, 0xb2, 0x42, 0x58, 0xc0, 0x7d, 0x23, 0xa3, 0xd1, 0x4a, + 0x1a, 0x04, 0xde, 0x66, 0xd8, 0x0a, 0x6c, 0x42, 0xc3, 0x7f, 0xcc, 0x3c, 0x4c, 0xab, 0x59, 0x09, 0xe6, 0x7c, 0x03, + 0x95, 0x18, 0x4f, 0x16, 0x57, 0x7c, 0xe3, 0x62, 0x25, 0x26, 0xb3, 0xc5, 0x7c, 0xb2, 0x96, 0x54, 0x73, 0xb9, 0xb7, + 0x66, 0x19, 0x5b, 0xc0, 0xfe, 0x61, 0x60, 0x28, 0x1d, 0xd8, 0xd1, 0x54, 0xd3, 0x26, 0x01, 0x26, 0xd3, 0x39, 0xe7, + 0xc3, 0x4b, 0x44, 0x93, 0xd5, 0xa9, 0x3b, 0x99, 0xaa, 0x76, 0x70, 0x4d, 0xce, 0xe4, 0xf4, 0x48, 0x3d, 0xd5, 0xba, + 0x97, 0x7c, 0xb4, 0x1d, 0x56, 0xa3, 0xad, 0x1f, 0x80, 0x5b, 0xa7, 0xb0, 0xd3, 0x77, 0xc3, 0x6a, 0xb4, 0xf3, 0x35, + 0xec, 0x2e, 0x29, 0x04, 0xaa, 0xbf, 0xca, 0x9a, 0xcc, 0xc5, 0xeb, 0xe2, 0xde, 0x2b, 0xd8, 0x53, 0x7f, 0xa0, 0x7f, + 0x95, 0xec, 0xa9, 0x6f, 0x33, 0xb9, 0xfe, 0x95, 0x76, 0x8d, 0xc6, 0x4c, 0xc7, 0x6b, 0x57, 0x60, 0x85, 0x06, 0xc8, + 0x2f, 0xd8, 0xd1, 0xde, 0xe4, 0x20, 0x10, 0xa0, 0x7b, 0x09, 0x8e, 0xa2, 0x80, 0xa8, 0x69, 0x55, 0x79, 0x74, 0xba, + 0xf7, 0xf7, 0xf8, 0x46, 0x08, 0xd8, 0xe4, 0xa9, 0x75, 0x6f, 0x19, 0xfb, 0x87, 0x03, 0x84, 0xd0, 0xcb, 0xe9, 0x37, + 0xda, 0xb2, 0x7a, 0xb4, 0x63, 0xb9, 0x6f, 0x18, 0xf5, 0x14, 0x8c, 0x61, 0xe8, 0xc2, 0x2a, 0x46, 0xf2, 0x0c, 0xc8, + 0x1a, 0xbf, 0x41, 0x74, 0x01, 0x8b, 0x5e, 0xef, 0xd5, 0x11, 0x0d, 0x22, 0xa0, 0xd2, 0x6b, 0xd2, 0x58, 0xe4, 0x73, + 0x55, 0x88, 0xde, 0x7b, 0x6b, 0xe7, 0xcd, 0x8c, 0x64, 0x99, 0x34, 0x52, 0xed, 0x56, 0x16, 0xeb, 0xca, 0x9b, 0x9d, + 0x90, 0x2e, 0xe6, 0x18, 0x2a, 0x83, 0xc7, 0x01, 0x28, 0x3d, 0xff, 0x11, 0x7a, 0x25, 0x43, 0xa6, 0x59, 0xa2, 0x99, + 0xdd, 0x35, 0xfe, 0x64, 0x95, 0x7a, 0x31, 0x22, 0x66, 0x03, 0x5b, 0x88, 0xdb, 0xa2, 0xd2, 0x6d, 0x51, 0x28, 0x5b, + 0x14, 0xe9, 0x43, 0xed, 0x4c, 0x77, 0x66, 0xe1, 0xb3, 0xca, 0xb4, 0xef, 0x53, 0x66, 0xc6, 0x06, 0x68, 0xbb, 0x08, + 0xdf, 0x40, 0x07, 0x2a, 0x84, 0xfc, 0x0d, 0x22, 0x22, 0x11, 0xb0, 0xcb, 0xa9, 0x3b, 0xb1, 0xe9, 0x90, 0xcc, 0x43, + 0xcc, 0x0a, 0x35, 0xca, 0x0b, 0x9e, 0x1c, 0x0d, 0x48, 0x45, 0xa8, 0xdb, 0xfd, 0xfe, 0xf9, 0xc2, 0x05, 0xb5, 0x5f, + 0x53, 0xec, 0x18, 0xdd, 0x14, 0x70, 0x2e, 0x78, 0x94, 0xf7, 0xdc, 0x3b, 0x07, 0x34, 0xc7, 0xf6, 0x14, 0x59, 0x03, + 0x4e, 0x6f, 0xbb, 0x10, 0x60, 0xfb, 0xac, 0xd9, 0xda, 0x9f, 0xac, 0xae, 0xa2, 0xa9, 0x57, 0xf2, 0x99, 0xee, 0xa2, + 0xc4, 0xed, 0xa2, 0x58, 0x76, 0xd1, 0xa6, 0x81, 0x60, 0xc7, 0x95, 0x1f, 0x00, 0x6f, 0x68, 0xd4, 0xef, 0x97, 0xad, + 0x9e, 0x3d, 0xf9, 0xda, 0x71, 0xcf, 0x66, 0x3e, 0x2b, 0x4d, 0xcf, 0x7e, 0x4e, 0xdd, 0x9e, 0x95, 0x93, 0xbd, 0xe8, + 0x9c, 0xec, 0xd3, 0xd9, 0x3c, 0x10, 0x5c, 0xee, 0xdc, 0xe7, 0xf9, 0x54, 0x4f, 0xbb, 0xca, 0x0f, 0x5a, 0x43, 0x64, + 0xed, 0x72, 0x55, 0xf7, 0xba, 0x82, 0x05, 0x2c, 0xc1, 0xdd, 0x7a, 0x69, 0xfe, 0x19, 0xbb, 0xbf, 0x17, 0xf4, 0xd2, + 0xfc, 0x77, 0xfa, 0x93, 0x02, 0x38, 0x00, 0x8d, 0xa9, 0xdd, 0x02, 0x0f, 0x31, 0x54, 0x50, 0xb8, 0x9b, 0x95, 0x73, + 0xaf, 0x06, 0x38, 0x4c, 0xd2, 0x37, 0xb4, 0x7a, 0xa5, 0xc5, 0xae, 0x97, 0xc9, 0x5e, 0x01, 0x1e, 0xaa, 0x90, 0x87, + 0x87, 0x43, 0xd4, 0x31, 0xec, 0xa0, 0x8e, 0x80, 0x61, 0x0f, 0xa1, 0xb1, 0x05, 0x9e, 0x8f, 0xbf, 0x64, 0x7c, 0x2f, + 0x40, 0x6d, 0x84, 0xf0, 0x78, 0xb5, 0x28, 0x43, 0x6c, 0xd9, 0x1b, 0xa4, 0x92, 0xfa, 0x45, 0x20, 0xca, 0x68, 0x15, + 0xd0, 0x56, 0x7b, 0xcc, 0xd2, 0x78, 0x0d, 0xa1, 0x62, 0xa9, 0x8f, 0x21, 0x34, 0x70, 0xf8, 0x1d, 0x0e, 0x20, 0xc1, + 0x97, 0x5c, 0x93, 0xcd, 0xbd, 0xc9, 0xef, 0x68, 0x9f, 0x3f, 0x1c, 0xce, 0x2f, 0x11, 0x94, 0x2e, 0x85, 0x8f, 0x54, + 0x22, 0xaa, 0xa7, 0xb8, 0x29, 0x21, 0x9b, 0x25, 0x2b, 0xfd, 0xe0, 0xb3, 0xfa, 0x05, 0x00, 0xb2, 0x10, 0x68, 0x13, + 0x99, 0xfd, 0xe9, 0x4c, 0x45, 0x17, 0x00, 0x87, 0xf8, 0xc3, 0x27, 0x88, 0xbe, 0xa1, 0x65, 0x5a, 0x3e, 0x4e, 0x78, + 0x08, 0x5a, 0x5b, 0xd2, 0x49, 0xc4, 0x4a, 0x81, 0x0d, 0x91, 0xf0, 0xfd, 0xfe, 0x79, 0x2c, 0xe9, 0x40, 0xa3, 0x56, + 0xf7, 0xc6, 0xad, 0xee, 0x95, 0xaf, 0xeb, 0x4e, 0x6e, 0x7c, 0x50, 0xb4, 0xcf, 0xe6, 0x8d, 0xca, 0xf7, 0x7d, 0x9d, + 0xb3, 0x3b, 0xdd, 0x3b, 0x72, 0x4e, 0x7c, 0x7f, 0x0f, 0xa1, 0xe8, 0xa1, 0x29, 0xb2, 0x2c, 0x09, 0x03, 0x5a, 0x6b, + 0xd7, 0x9e, 0x65, 0x74, 0xf0, 0xda, 0x37, 0x84, 0x88, 0x3c, 0xc5, 0x27, 0x21, 0xb7, 0x38, 0x3e, 0x28, 0xd0, 0x3f, + 0x33, 0xfe, 0xcc, 0x89, 0x1f, 0xb6, 0xfa, 0x05, 0x70, 0x6e, 0xba, 0xf7, 0xee, 0xc4, 0xac, 0xc7, 0x50, 0xca, 0xc6, + 0xff, 0xfd, 0x3e, 0x91, 0x05, 0x3a, 0x1d, 0xd1, 0x30, 0x10, 0xdc, 0x45, 0xf5, 0x7f, 0xaf, 0x78, 0xdd, 0xb3, 0x56, + 0xe7, 0xcb, 0x4f, 0x9d, 0x9e, 0xf4, 0x7a, 0xe9, 0x56, 0xf8, 0x32, 0x4c, 0x7c, 0xe7, 0x75, 0xbf, 0x61, 0xbb, 0xef, + 0x7e, 0x79, 0x77, 0xf4, 0x32, 0xb0, 0x49, 0xe1, 0x3b, 0x9b, 0x92, 0xcf, 0x7a, 0xa0, 0xf0, 0xeb, 0xb1, 0x5e, 0x5d, + 0xac, 0x7b, 0xac, 0x87, 0x5a, 0x40, 0xf4, 0xb0, 0x00, 0xf5, 0x5f, 0xcf, 0x3e, 0x0d, 0x85, 0x83, 0x6c, 0x9c, 0x2a, + 0x50, 0x64, 0xc1, 0x9f, 0x89, 0xd1, 0xba, 0x20, 0x40, 0x64, 0xb3, 0x7d, 0x7d, 0xac, 0x4e, 0x66, 0xdf, 0x94, 0x5a, + 0x92, 0xc1, 0x37, 0x01, 0x99, 0x1d, 0x58, 0x39, 0x41, 0xe9, 0xb8, 0x35, 0xe0, 0xca, 0x16, 0x91, 0x78, 0xfb, 0xd3, + 0x20, 0x3b, 0x6b, 0x4e, 0x1a, 0xed, 0xc3, 0x3e, 0xcd, 0x03, 0x04, 0x22, 0x99, 0x8a, 0x20, 0xd7, 0xdc, 0x5b, 0xd2, + 0x47, 0x87, 0x73, 0x5e, 0xc8, 0x3f, 0xa7, 0x52, 0x87, 0x38, 0x94, 0x58, 0x03, 0x81, 0xca, 0x33, 0x54, 0x39, 0x6c, + 0x90, 0xe3, 0x8f, 0x8e, 0x64, 0x26, 0x31, 0x59, 0xe4, 0x6e, 0xcd, 0x54, 0xf8, 0x81, 0xe0, 0x63, 0x96, 0x73, 0xe0, + 0x02, 0x9b, 0xcd, 0x7d, 0x35, 0xc5, 0xc5, 0x15, 0xf8, 0x63, 0x0a, 0xbf, 0xe2, 0x29, 0xec, 0xb4, 0xfb, 0x75, 0x51, + 0xa5, 0xa8, 0xdb, 0x28, 0x2c, 0x2a, 0x59, 0x30, 0xad, 0x21, 0x4d, 0x74, 0x18, 0xfd, 0x49, 0xce, 0x40, 0x41, 0xc8, + 0x2f, 0x9b, 0x06, 0x18, 0xa9, 0xe4, 0xf2, 0xa0, 0x4a, 0x02, 0x2f, 0xc0, 0x36, 0xa8, 0xd8, 0xba, 0x80, 0x20, 0xdb, + 0xa4, 0x28, 0xd3, 0xaf, 0x45, 0x5e, 0x87, 0x59, 0x50, 0x8d, 0xd2, 0xea, 0x27, 0xfd, 0x13, 0x98, 0xb7, 0xa9, 0x18, + 0xd5, 0x2a, 0x26, 0xbf, 0xd1, 0xef, 0x17, 0x83, 0xd6, 0x87, 0x0c, 0x3e, 0x7a, 0x6d, 0x1a, 0xfc, 0xda, 0x69, 0xb0, + 0xc3, 0x44, 0x23, 0x00, 0x92, 0x39, 0xb5, 0xe4, 0xa1, 0xe8, 0xcf, 0x20, 0xc7, 0x1a, 0x55, 0x4e, 0xc1, 0x60, 0xfd, + 0xc7, 0xa3, 0x1d, 0x98, 0x7a, 0x71, 0xb4, 0x25, 0x3b, 0x68, 0xe5, 0x1b, 0xe0, 0x7e, 0x8d, 0x6c, 0x31, 0xcb, 0x01, + 0x9a, 0xbd, 0x46, 0x64, 0x7c, 0xf2, 0x02, 0x18, 0xb3, 0x75, 0x16, 0x46, 0x22, 0x0e, 0xc6, 0xaa, 0x31, 0x63, 0x06, + 0x06, 0x2e, 0xd0, 0xb5, 0x4c, 0x4a, 0xd2, 0x90, 0x0e, 0x06, 0xac, 0x94, 0x2d, 0x1c, 0xf0, 0xa2, 0x39, 0x6e, 0xc7, + 0xbb, 0x16, 0x8d, 0x07, 0xb6, 0x8b, 0xed, 0xef, 0x5e, 0x14, 0xdb, 0xb7, 0xe1, 0x96, 0xf4, 0x0a, 0x39, 0x4b, 0xe8, + 0xe7, 0x4f, 0xb2, 0xcf, 0x1a, 0x4e, 0x4e, 0x85, 0x66, 0x68, 0x29, 0x12, 0x4a, 0xf1, 0x4e, 0x4f, 0x0a, 0x8c, 0x65, + 0x2c, 0xfc, 0x3d, 0x70, 0x4e, 0x17, 0x8a, 0xc8, 0x1d, 0x38, 0x8e, 0xaf, 0xa1, 0x82, 0xe0, 0xbf, 0x00, 0xb3, 0x18, + 0x20, 0x4f, 0x67, 0x21, 0xe1, 0x14, 0xc2, 0xc5, 0x2a, 0xeb, 0xf7, 0xe5, 0x2f, 0xea, 0xa2, 0x8b, 0x4c, 0xd6, 0x7d, + 0x12, 0x8e, 0xcc, 0x58, 0x4e, 0xbd, 0x90, 0x3c, 0xef, 0x79, 0x32, 0x4d, 0x9e, 0xe4, 0x41, 0x04, 0x90, 0xcf, 0xe1, + 0x5d, 0x98, 0x66, 0x60, 0x95, 0x26, 0xe5, 0x47, 0x28, 0x7d, 0xf1, 0x79, 0xe5, 0x07, 0x3a, 0x7b, 0x6e, 0x92, 0xe1, + 0xcd, 0xaa, 0xf5, 0x26, 0xb5, 0xae, 0x8b, 0x07, 0xfc, 0xab, 0x33, 0xd8, 0x38, 0xd7, 0x99, 0xe0, 0xc0, 0x8b, 0xa4, + 0xd6, 0x6b, 0xc6, 0x9f, 0x65, 0xb8, 0x2e, 0x55, 0x1b, 0x7d, 0x14, 0xa2, 0x73, 0xc8, 0x54, 0x80, 0x42, 0x91, 0xf6, + 0x0f, 0x4a, 0xad, 0x4c, 0x2a, 0x6d, 0x24, 0x80, 0xee, 0x61, 0xd2, 0x60, 0x8b, 0xa1, 0x8c, 0xa5, 0x49, 0x94, 0x3b, + 0x0d, 0xe2, 0xca, 0x7e, 0xac, 0x24, 0x0e, 0x2d, 0x8b, 0xe4, 0xdf, 0xbb, 0x9e, 0xbe, 0x42, 0xea, 0x4e, 0x16, 0xc8, + 0x8c, 0xf1, 0x3c, 0x8f, 0x3f, 0x01, 0x61, 0x36, 0x68, 0xa3, 0xa2, 0x10, 0x42, 0x36, 0x88, 0x41, 0xe3, 0x79, 0x1e, + 0xbf, 0x50, 0x34, 0x1e, 0xf2, 0x51, 0xe4, 0xab, 0xbf, 0x4a, 0xfd, 0x57, 0xe8, 0x33, 0x13, 0x3c, 0x42, 0x35, 0xd1, + 0xbf, 0x7b, 0x3e, 0xbb, 0x03, 0xb5, 0x61, 0x14, 0x66, 0xa6, 0xfc, 0xca, 0x37, 0xc5, 0xd9, 0xeb, 0xaf, 0xe8, 0x2a, + 0xdb, 0xba, 0x1f, 0xbd, 0x3e, 0x22, 0xb0, 0x36, 0x46, 0x57, 0xdc, 0x18, 0x40, 0x0e, 0x93, 0xf7, 0x2b, 0x4a, 0xcb, + 0x21, 0x0d, 0x42, 0x07, 0x0d, 0x41, 0xaf, 0x24, 0xfa, 0x40, 0x62, 0x11, 0x63, 0x78, 0x21, 0x9e, 0x91, 0x9a, 0x4c, + 0x34, 0xc4, 0x2b, 0x62, 0x3f, 0x44, 0x4b, 0x4e, 0x4d, 0x74, 0x23, 0x4c, 0x31, 0x90, 0xd8, 0x19, 0x24, 0x27, 0x49, + 0xad, 0xfc, 0xe2, 0x99, 0x24, 0x2c, 0xb1, 0xf3, 0x10, 0x83, 0x49, 0x2d, 0xdd, 0xe9, 0x4d, 0x95, 0xbe, 0x1c, 0x69, + 0x39, 0x68, 0x1f, 0x80, 0x5d, 0x4a, 0x7a, 0xff, 0xa4, 0x50, 0xc4, 0x87, 0x30, 0x8e, 0x21, 0x7c, 0x8b, 0xa8, 0xae, + 0xc0, 0xb9, 0x56, 0xa0, 0xb1, 0x1a, 0x78, 0x68, 0x66, 0xd5, 0x7c, 0xc8, 0xe9, 0xa7, 0xd2, 0xf2, 0xc7, 0x88, 0xc6, + 0x46, 0xeb, 0xe6, 0x70, 0xd8, 0xd3, 0xaa, 0x97, 0xce, 0x41, 0x97, 0xcd, 0x24, 0x26, 0x6e, 0x20, 0x5d, 0x3f, 0xfa, + 0xcd, 0x84, 0xbd, 0x88, 0x0a, 0xb9, 0x14, 0x82, 0x82, 0x56, 0x07, 0x02, 0x87, 0xc2, 0x5b, 0x94, 0xf9, 0x22, 0xa6, + 0x0d, 0x84, 0xc1, 0xe7, 0x07, 0xf2, 0xf3, 0x4d, 0x41, 0x2a, 0x76, 0xac, 0x6b, 0xbf, 0xbf, 0x28, 0x3d, 0xc0, 0x93, + 0x33, 0x49, 0x9e, 0x36, 0x43, 0x58, 0x11, 0x40, 0x63, 0x56, 0x93, 0xc5, 0x09, 0x57, 0xe6, 0xf0, 0x75, 0xe5, 0x95, + 0x2c, 0x65, 0xea, 0x3c, 0xd5, 0x0b, 0x20, 0xea, 0x78, 0x83, 0x56, 0xa4, 0x7e, 0x85, 0xce, 0x5e, 0xb3, 0x12, 0x32, + 0x1e, 0x9e, 0x73, 0x9e, 0x8e, 0xee, 0x59, 0xc2, 0x23, 0xfc, 0x2b, 0x99, 0xe8, 0xc3, 0xef, 0x9e, 0xc3, 0xcd, 0x38, + 0xe1, 0x91, 0xdb, 0xec, 0x7d, 0x15, 0xae, 0xe0, 0x66, 0x5a, 0x00, 0x92, 0x5b, 0x90, 0x34, 0x01, 0x25, 0x24, 0x32, + 0x21, 0xb3, 0xa6, 0xe4, 0x8b, 0x96, 0xb6, 0xc1, 0x1a, 0x26, 0x9d, 0x07, 0xbc, 0x68, 0xf5, 0xd1, 0x6a, 0xa2, 0x5d, + 0x66, 0xf9, 0x7c, 0x88, 0x33, 0x54, 0x73, 0xdc, 0x9d, 0xc1, 0xcf, 0x01, 0xaf, 0x58, 0xd5, 0xa4, 0xa3, 0xdd, 0x80, + 0x0b, 0x4f, 0xae, 0xf3, 0x74, 0xb4, 0xc5, 0x5f, 0x72, 0x7f, 0x00, 0xe8, 0x60, 0xea, 0x12, 0xf8, 0x53, 0xb5, 0xd5, + 0x54, 0xea, 0xb7, 0xd6, 0x7e, 0x5d, 0x77, 0x56, 0x2b, 0xf7, 0xac, 0xcb, 0xd0, 0x1e, 0x19, 0x72, 0xc6, 0x0c, 0xf8, + 0x73, 0xc6, 0x92, 0x3f, 0x67, 0xac, 0xf8, 0x73, 0xc6, 0x8d, 0x91, 0x01, 0x94, 0xe0, 0x5e, 0xf2, 0x67, 0x7b, 0xc4, + 0x0c, 0xb1, 0x1a, 0x54, 0x02, 0x2b, 0x4b, 0x39, 0xf7, 0x91, 0x53, 0x4c, 0x39, 0x65, 0x78, 0xe9, 0x74, 0xe6, 0x0e, + 0xe4, 0x3c, 0x98, 0xb9, 0xc3, 0x64, 0xaf, 0xcf, 0x8d, 0x38, 0x96, 0xc6, 0xa4, 0xa8, 0x20, 0x9d, 0xd3, 0xe1, 0xe6, + 0xd5, 0x71, 0x9e, 0xb0, 0x8c, 0x8f, 0xdb, 0x67, 0x0a, 0x84, 0xd8, 0xe2, 0x19, 0x12, 0x29, 0x55, 0xb3, 0xdc, 0xe6, + 0x0f, 0x87, 0x7a, 0x74, 0xaf, 0x77, 0x7a, 0xf8, 0x95, 0xb0, 0xdf, 0x32, 0xcf, 0x3e, 0x41, 0x00, 0x93, 0x44, 0x9e, + 0x49, 0x38, 0xfa, 0xb1, 0x1c, 0xfd, 0x4d, 0xc3, 0xbf, 0x64, 0xa8, 0xee, 0x0e, 0x81, 0x89, 0x2d, 0x3b, 0x70, 0x08, + 0x4e, 0x57, 0x95, 0x48, 0xc0, 0xc1, 0x66, 0xc3, 0x22, 0xbd, 0xc7, 0x43, 0x9c, 0x0f, 0x0a, 0x1f, 0xa1, 0x61, 0x46, + 0xef, 0xf7, 0x37, 0xc2, 0xab, 0x64, 0x2b, 0x0f, 0x87, 0xc4, 0xba, 0x0b, 0x3b, 0xfa, 0x38, 0xda, 0xa3, 0x84, 0xda, + 0x8f, 0x6a, 0xbd, 0xa9, 0xd4, 0x83, 0xdc, 0xec, 0x42, 0x62, 0x50, 0xb1, 0x54, 0x9f, 0x5e, 0xa9, 0x3e, 0xd4, 0xac, + 0xf3, 0xbb, 0x3a, 0xee, 0x53, 0x31, 0x5a, 0xcb, 0x09, 0x01, 0xae, 0x83, 0x44, 0xa3, 0x03, 0x60, 0x9c, 0x6d, 0xb6, + 0xbc, 0xd4, 0xd6, 0x89, 0xd2, 0x71, 0x9c, 0xeb, 0xe3, 0xf8, 0x70, 0x90, 0x62, 0xc6, 0xe5, 0x91, 0x98, 0x71, 0xd9, + 0x00, 0xbc, 0x59, 0xe7, 0x41, 0x7d, 0x38, 0x5c, 0xd2, 0xa5, 0xc8, 0x74, 0xb6, 0x51, 0x7e, 0xd6, 0xa3, 0xfb, 0x27, + 0x09, 0x9a, 0x7b, 0x2b, 0xec, 0xbd, 0x48, 0xb6, 0x67, 0xb2, 0x4e, 0xbd, 0x8c, 0x7c, 0x7a, 0xe1, 0x9e, 0x5d, 0x72, + 0xf5, 0xc3, 0xea, 0xeb, 0xe9, 0x67, 0xe1, 0x45, 0xac, 0xa2, 0xdd, 0xba, 0x64, 0xc2, 0xde, 0x52, 0x2a, 0x69, 0x95, + 0x97, 0x4f, 0x37, 0x7e, 0x80, 0x99, 0x69, 0x4f, 0x1f, 0x64, 0x23, 0xaa, 0x3f, 0x2b, 0x51, 0x2b, 0xc3, 0x64, 0xe1, + 0xbc, 0x64, 0xea, 0xc9, 0x80, 0xc7, 0xac, 0xe4, 0x91, 0xec, 0xf4, 0xc6, 0x20, 0x08, 0x60, 0x9d, 0x93, 0x56, 0x9d, + 0x71, 0x34, 0x5a, 0x55, 0x2e, 0x4e, 0x57, 0xb9, 0xc0, 0x70, 0xbb, 0x35, 0xdb, 0xa8, 0x3a, 0xcb, 0x4d, 0xad, 0x52, + 0xbe, 0x03, 0xf8, 0x58, 0x56, 0xb9, 0xa0, 0x63, 0xca, 0xd4, 0x79, 0x03, 0xc1, 0xd8, 0xaa, 0xc6, 0x85, 0x53, 0xe3, + 0x82, 0x47, 0xd4, 0xee, 0xa6, 0xa9, 0x47, 0x5b, 0x60, 0x29, 0x1d, 0xed, 0x78, 0x89, 0x2a, 0x85, 0x9f, 0x05, 0xdf, + 0x87, 0x71, 0xfc, 0xa2, 0xd8, 0xaa, 0x03, 0xf1, 0xb6, 0xd8, 0x22, 0xed, 0x8b, 0xfc, 0x0b, 0x71, 0xc0, 0x6b, 0x5d, + 0x53, 0x5e, 0x5b, 0x73, 0x1a, 0xd8, 0x1a, 0x46, 0x4a, 0x0a, 0xe7, 0xe6, 0xcf, 0xc3, 0x81, 0x56, 0x76, 0xad, 0xee, + 0x0a, 0xb5, 0x1e, 0x73, 0xd8, 0xb0, 0x6f, 0xb2, 0x70, 0x27, 0x4a, 0x70, 0xe4, 0x92, 0x7f, 0x1d, 0x0e, 0x5a, 0x65, + 0xa9, 0x8e, 0xf4, 0xd9, 0xfe, 0x6b, 0x30, 0x66, 0xe8, 0xd2, 0x04, 0x2c, 0x1b, 0x23, 0xf9, 0x57, 0xd3, 0xcc, 0x1b, + 0x26, 0x6b, 0xa6, 0x70, 0x1c, 0x1a, 0x46, 0x48, 0x03, 0xba, 0x0d, 0x6a, 0xc3, 0x93, 0xf9, 0xa6, 0x2a, 0xbf, 0xba, + 0x23, 0xd5, 0x7e, 0x30, 0xbc, 0x9c, 0x88, 0x73, 0xba, 0x24, 0xa9, 0xa7, 0x12, 0x4a, 0x42, 0xb0, 0x4b, 0x1f, 0xc8, + 0x89, 0x15, 0x90, 0xb5, 0x8c, 0xe5, 0xb7, 0x7a, 0x40, 0xe8, 0x3f, 0xed, 0xd6, 0x0b, 0xfd, 0xa7, 0x69, 0xb6, 0x50, + 0xd7, 0x1f, 0x26, 0xf7, 0x1d, 0xbd, 0xfe, 0xe0, 0xf0, 0x4e, 0x5d, 0x55, 0x5c, 0xc5, 0xa3, 0xda, 0x30, 0xc9, 0x8d, + 0xb2, 0x70, 0x57, 0x6c, 0x6a, 0xb5, 0x3c, 0x1d, 0x87, 0x11, 0x98, 0x11, 0x14, 0x20, 0xeb, 0xba, 0x8d, 0x88, 0x61, + 0x25, 0x97, 0x09, 0xf9, 0x84, 0x80, 0x2c, 0x4a, 0x8d, 0xf3, 0x71, 0x0b, 0x54, 0x22, 0x18, 0x9c, 0x86, 0xd6, 0xaa, + 0x9b, 0xfc, 0xa4, 0xb2, 0xb1, 0x25, 0x90, 0x43, 0x92, 0xc9, 0x62, 0x39, 0xba, 0x15, 0x8b, 0xa2, 0x14, 0xbf, 0x60, + 0x3d, 0x5c, 0xb3, 0x85, 0xfb, 0x0c, 0x08, 0xed, 0x27, 0x4a, 0x7b, 0x13, 0x69, 0x82, 0xee, 0x25, 0x5b, 0x01, 0xc8, + 0x00, 0x8a, 0xba, 0xda, 0xad, 0xcf, 0xf9, 0x39, 0x92, 0x66, 0x38, 0x8c, 0x6e, 0x9f, 0x2e, 0x83, 0xe5, 0xe0, 0x12, + 0xb5, 0xd2, 0x97, 0x2c, 0x6e, 0x61, 0x50, 0xed, 0xcd, 0x12, 0x0e, 0x6a, 0x66, 0xad, 0x8d, 0x40, 0x30, 0xd9, 0x43, + 0x41, 0xc5, 0x5c, 0xc1, 0x3e, 0x28, 0x58, 0x4b, 0x5e, 0x07, 0x87, 0x5b, 0xfb, 0xb2, 0x52, 0x5c, 0x3c, 0xbd, 0x48, + 0x5a, 0x17, 0x96, 0xf2, 0xe2, 0x69, 0x03, 0x06, 0x97, 0x23, 0x6c, 0x2a, 0x30, 0x49, 0x00, 0xe8, 0x56, 0x44, 0x11, + 0x2f, 0x4a, 0x61, 0xdb, 0xca, 0x67, 0x4e, 0xd8, 0x60, 0xc3, 0xee, 0xe1, 0x5e, 0x19, 0x94, 0x0c, 0x2e, 0xc4, 0xb8, + 0xdd, 0xec, 0x02, 0x5c, 0xc1, 0x50, 0x18, 0x5b, 0xf3, 0x77, 0x99, 0x17, 0x29, 0x01, 0x37, 0x43, 0x94, 0xaf, 0x0d, + 0x9c, 0x4c, 0x7a, 0x72, 0x2d, 0x58, 0x0c, 0x58, 0xd0, 0xe0, 0x3b, 0x6a, 0xfd, 0x9d, 0xc9, 0xbf, 0xf1, 0xf4, 0xd0, + 0x0f, 0x5e, 0x64, 0xde, 0xc2, 0x67, 0xef, 0x2a, 0x19, 0xad, 0x49, 0xa2, 0xbc, 0x7a, 0xb8, 0x00, 0xb9, 0x61, 0x31, + 0xba, 0x67, 0x0b, 0x10, 0x27, 0x16, 0xa3, 0x84, 0x32, 0xba, 0xc2, 0xbd, 0xca, 0x6c, 0x99, 0x08, 0xa4, 0x38, 0xb0, + 0x90, 0x72, 0x6f, 0xb1, 0x0e, 0x16, 0xb8, 0x3f, 0x91, 0x5c, 0x40, 0xc9, 0x03, 0x28, 0x57, 0x0a, 0x08, 0xf8, 0x74, + 0x00, 0xe5, 0x4b, 0x79, 0x11, 0xfe, 0xc4, 0x89, 0x1a, 0x2c, 0x46, 0xf7, 0x0d, 0xfb, 0xc9, 0x0b, 0x2d, 0xfb, 0xc3, + 0x52, 0x6b, 0x1a, 0x56, 0x7c, 0x09, 0xd3, 0x62, 0xe2, 0xf6, 0xe5, 0xca, 0xae, 0x8a, 0xcf, 0x56, 0xea, 0xec, 0xa6, + 0x86, 0x24, 0xec, 0x1b, 0xb2, 0x0a, 0x70, 0xb0, 0x2a, 0xe2, 0x9e, 0x75, 0xb9, 0x0f, 0xa3, 0xbf, 0x36, 0x69, 0x29, + 0x2c, 0x54, 0x49, 0x7f, 0xdf, 0x94, 0x02, 0xa9, 0x4c, 0x74, 0xa2, 0x85, 0xe0, 0x0a, 0x0c, 0x02, 0x77, 0x22, 0xaf, + 0x01, 0x30, 0x06, 0x5c, 0x0a, 0x94, 0x65, 0x5b, 0x42, 0x48, 0x75, 0x3f, 0x03, 0xb5, 0x9d, 0xb8, 0x4b, 0x23, 0xb2, + 0x16, 0xa2, 0xaf, 0x82, 0x31, 0x73, 0x5e, 0x4a, 0xb7, 0xd8, 0x74, 0xb5, 0x59, 0x5d, 0xa3, 0x73, 0x69, 0xcb, 0xcd, + 0x4f, 0xd8, 0x62, 0xad, 0x40, 0xd9, 0x84, 0xa4, 0xed, 0x9c, 0xe7, 0x28, 0x9b, 0xd0, 0xd2, 0xde, 0x53, 0x8f, 0x0a, + 0xd5, 0xc9, 0xd6, 0x4b, 0xd5, 0xd4, 0x22, 0xac, 0x16, 0x17, 0x95, 0x1f, 0x80, 0x6e, 0x2a, 0xad, 0x9e, 0xd7, 0x35, + 0x9a, 0x42, 0xad, 0x16, 0x8e, 0x1b, 0xed, 0x6c, 0xba, 0x48, 0x97, 0x88, 0xb3, 0x2a, 0xed, 0xd0, 0x3f, 0x65, 0xda, + 0xf5, 0xb2, 0xa3, 0xdf, 0x8c, 0xab, 0x0b, 0x5c, 0x88, 0x0d, 0xf8, 0x9c, 0xfb, 0xcb, 0xeb, 0x3d, 0x8d, 0x7b, 0xfe, + 0xe1, 0x80, 0xec, 0x49, 0xed, 0x0f, 0xd5, 0xc7, 0xae, 0x60, 0xc8, 0xc2, 0x28, 0xf5, 0x17, 0x29, 0xef, 0x3d, 0xc2, + 0x71, 0xff, 0x52, 0xf5, 0xd8, 0xaf, 0x19, 0xdf, 0xd7, 0xc5, 0x26, 0x4a, 0x28, 0xaa, 0xa1, 0xb7, 0x2a, 0x36, 0x95, + 0x88, 0x8b, 0xfb, 0xbc, 0xc7, 0x30, 0x19, 0xc6, 0x42, 0xa6, 0xc2, 0x9f, 0x32, 0x15, 0x3c, 0x42, 0x28, 0x71, 0xb3, + 0xee, 0x91, 0x76, 0x13, 0xe2, 0x94, 0x6a, 0x51, 0xca, 0x64, 0xfc, 0x5b, 0x3f, 0x81, 0xf2, 0x9c, 0xa2, 0x65, 0xfa, + 0x51, 0xe1, 0x32, 0x7d, 0xb3, 0x3e, 0x2e, 0x3d, 0x13, 0xa1, 0xce, 0x5c, 0x6c, 0x6a, 0x9d, 0x8e, 0xb1, 0x53, 0x3a, + 0xb5, 0x61, 0x5f, 0x2b, 0xc5, 0x65, 0x45, 0xe1, 0xdf, 0x48, 0x64, 0xd5, 0x33, 0xe2, 0xf8, 0x3f, 0xb3, 0xf6, 0x19, + 0x56, 0x81, 0x5f, 0x06, 0xf2, 0x7e, 0x01, 0xf0, 0x71, 0x5d, 0x97, 0xe9, 0xed, 0x06, 0x68, 0x43, 0x68, 0xf8, 0x7b, + 0x3e, 0x32, 0x60, 0xba, 0x8f, 0x70, 0x86, 0xf4, 0x50, 0xe7, 0x9c, 0xce, 0xca, 0x74, 0xce, 0x55, 0x58, 0x4b, 0xb0, + 0x97, 0x93, 0x26, 0x97, 0xeb, 0x12, 0xd4, 0x4c, 0xe0, 0xf6, 0xa1, 0x3d, 0x22, 0x84, 0xda, 0x94, 0xd5, 0xf4, 0x12, + 0x6a, 0xde, 0xc9, 0x69, 0x47, 0x93, 0x12, 0x5c, 0x35, 0x74, 0x56, 0xae, 0xff, 0x3a, 0x1c, 0x7a, 0xb7, 0x59, 0x11, + 0xfd, 0xd9, 0x43, 0x7f, 0xc7, 0xed, 0x75, 0xfa, 0x15, 0xa2, 0x65, 0xac, 0xbf, 0x21, 0x03, 0x3a, 0x9e, 0x0c, 0x6f, + 0x8b, 0x6d, 0x8f, 0x7d, 0x45, 0x0d, 0x96, 0xbe, 0x7e, 0x5c, 0x83, 0x84, 0xaa, 0x6b, 0x5f, 0x58, 0x3c, 0x61, 0x9e, + 0x12, 0x6d, 0x0b, 0x1f, 0xc2, 0x42, 0xbf, 0x42, 0x64, 0x24, 0x84, 0x9b, 0xca, 0xee, 0x51, 0xd2, 0x2e, 0xf4, 0xa5, + 0xaf, 0x65, 0x5f, 0xf9, 0xce, 0x05, 0xc0, 0xca, 0x3e, 0xb5, 0xe1, 0x9e, 0xf4, 0xa7, 0x54, 0x1f, 0xb6, 0xbf, 0x25, + 0x0b, 0x28, 0xb4, 0xb0, 0x9e, 0xca, 0xd9, 0xb9, 0x2c, 0x79, 0x9e, 0x4d, 0xf7, 0x6b, 0xd8, 0xa3, 0xee, 0xd0, 0x6b, + 0x2a, 0x38, 0xbf, 0x34, 0xa3, 0xf7, 0xbb, 0xa1, 0x50, 0x1d, 0x75, 0xee, 0x20, 0xcb, 0xd2, 0xba, 0xe4, 0xfc, 0x65, + 0xe5, 0x8e, 0xc2, 0xfc, 0x2e, 0x04, 0xcf, 0xb0, 0xee, 0xdd, 0xc5, 0x79, 0xef, 0x73, 0x6b, 0x8e, 0xfc, 0x9a, 0xcd, + 0x52, 0xc4, 0x22, 0x99, 0x83, 0xd5, 0x0f, 0xfd, 0x3c, 0xf6, 0xdb, 0x20, 0x87, 0xe3, 0xa6, 0x01, 0x1d, 0x36, 0x64, + 0xd6, 0xbe, 0x44, 0xe0, 0x54, 0x23, 0x48, 0x53, 0x13, 0xd4, 0x2c, 0x0f, 0x91, 0xd8, 0x2e, 0x65, 0xdb, 0x20, 0xd7, + 0x5d, 0x30, 0xcd, 0x91, 0xf6, 0x0c, 0xde, 0x37, 0x69, 0x92, 0x0a, 0xcd, 0xa2, 0x8b, 0x95, 0x8c, 0x7f, 0x47, 0xda, + 0x4c, 0xc9, 0x1e, 0x5b, 0x03, 0xef, 0x25, 0x28, 0x27, 0xc3, 0x14, 0xc3, 0x77, 0x7c, 0xbd, 0xf3, 0xe8, 0x22, 0x7e, + 0x3e, 0x66, 0x9b, 0x94, 0x1d, 0xc1, 0x24, 0xd9, 0xf8, 0x86, 0xe2, 0x0d, 0xdf, 0xdf, 0x56, 0xa2, 0x04, 0xd0, 0xcb, + 0x82, 0x3f, 0x93, 0x36, 0x57, 0xe8, 0x76, 0xf7, 0x8e, 0x52, 0xf8, 0x25, 0x2f, 0x0f, 0x87, 0x6d, 0xea, 0x85, 0xd0, + 0xf9, 0x22, 0x7e, 0x07, 0xe6, 0x30, 0x86, 0xd8, 0x8c, 0x00, 0x61, 0x8e, 0x0f, 0xa8, 0x83, 0xf5, 0x23, 0x00, 0x8d, + 0x13, 0x28, 0xc0, 0xe8, 0xab, 0x6d, 0x41, 0xdf, 0xf2, 0xe2, 0x22, 0x42, 0xd4, 0x28, 0xc0, 0x44, 0x49, 0xb3, 0x18, + 0x86, 0x03, 0x9d, 0xdf, 0x37, 0xb7, 0x75, 0x29, 0x70, 0xe8, 0x1d, 0xcb, 0xf0, 0xdf, 0xfe, 0xc7, 0xda, 0xd2, 0xaa, + 0xb2, 0xdd, 0x1a, 0xa7, 0x99, 0xff, 0xed, 0xb6, 0xd0, 0xf7, 0x5f, 0x0a, 0xc5, 0xf3, 0x8e, 0xd7, 0xed, 0x2f, 0x10, + 0xbd, 0xaf, 0x5b, 0xb9, 0x2a, 0xb5, 0x1b, 0x66, 0xca, 0xef, 0xd3, 0x3c, 0x2e, 0xee, 0x47, 0x71, 0xeb, 0xc8, 0x9b, + 0xa4, 0xe7, 0x9c, 0x7f, 0xa9, 0xfa, 0x7d, 0xef, 0x0b, 0x90, 0xf1, 0xbe, 0x14, 0xc6, 0x11, 0x93, 0x38, 0xf8, 0xf6, + 0x62, 0x14, 0x6d, 0x4a, 0xd8, 0x90, 0xdb, 0xa7, 0x25, 0x68, 0x66, 0xfa, 0x7d, 0x94, 0x28, 0xad, 0xf9, 0xfe, 0x0f, + 0x39, 0xdf, 0x5f, 0x0a, 0x79, 0xb3, 0x92, 0x1f, 0x3e, 0x5a, 0x61, 0xe0, 0x7b, 0x9c, 0x7e, 0x15, 0x3d, 0xb6, 0x2a, + 0x7d, 0xf8, 0xae, 0xb4, 0xf4, 0x59, 0x45, 0xfd, 0x0b, 0x15, 0x35, 0x2f, 0xc5, 0x88, 0x88, 0x07, 0x41, 0x3b, 0xdb, + 0x2e, 0xb5, 0x6b, 0x09, 0xda, 0x05, 0x9b, 0xc2, 0xfe, 0xfe, 0xe0, 0x90, 0xf7, 0xfb, 0x1f, 0x73, 0xaf, 0xc5, 0xeb, + 0x6e, 0xe0, 0x2e, 0x4b, 0x0f, 0x21, 0x80, 0xb5, 0x0c, 0x94, 0x71, 0x84, 0x49, 0x17, 0x79, 0x8d, 0xb2, 0xe9, 0x44, + 0xe0, 0x63, 0x96, 0x5d, 0x39, 0xc9, 0x34, 0xc0, 0x8c, 0x6a, 0x0a, 0x33, 0x01, 0x46, 0xea, 0x23, 0xd6, 0x4d, 0x4f, + 0xab, 0xd0, 0xf2, 0x35, 0x04, 0xeb, 0x22, 0xcb, 0x38, 0x8a, 0x99, 0x00, 0x60, 0xf3, 0x11, 0xe4, 0x2b, 0xba, 0x3a, + 0x24, 0xad, 0x54, 0x79, 0xbf, 0xce, 0x88, 0x8c, 0x26, 0x21, 0x9a, 0xdf, 0xc2, 0x03, 0xfb, 0xb6, 0x99, 0x51, 0xa5, + 0x9e, 0x51, 0x95, 0xcf, 0x70, 0x58, 0x0a, 0xc7, 0x88, 0xff, 0x73, 0xaa, 0x7a, 0x44, 0xa0, 0x57, 0x65, 0x5a, 0x45, + 0x45, 0x9e, 0x8b, 0x08, 0x11, 0xaa, 0xa5, 0x73, 0x38, 0xf4, 0x63, 0xbf, 0x8f, 0x03, 0x61, 0x5e, 0xac, 0x93, 0x07, + 0xba, 0xb2, 0xa6, 0xb5, 0x92, 0x02, 0xa7, 0xa2, 0x46, 0x88, 0x10, 0xde, 0x67, 0xe0, 0x59, 0x4d, 0x7d, 0xbf, 0xb1, + 0x4c, 0x74, 0xbf, 0x67, 0x40, 0xf9, 0x03, 0xf2, 0x75, 0x25, 0xc5, 0x19, 0x91, 0x3c, 0x24, 0xce, 0x38, 0x00, 0x31, + 0xdf, 0x96, 0x68, 0x34, 0xf6, 0x3f, 0x20, 0xc1, 0x50, 0xfd, 0x60, 0xa7, 0x9b, 0x7a, 0xff, 0xcc, 0x24, 0x8e, 0xa2, + 0x4f, 0xdb, 0xe4, 0xb1, 0x64, 0x69, 0xb4, 0x70, 0xf4, 0x1e, 0x31, 0x8c, 0xc3, 0xe9, 0x7c, 0x4c, 0xb2, 0x8d, 0xc9, + 0x2a, 0x80, 0x74, 0x32, 0x53, 0xc7, 0x94, 0x3a, 0x1a, 0xe7, 0x7a, 0x41, 0x15, 0x7a, 0xac, 0x4b, 0x9e, 0x83, 0xf5, + 0xe4, 0x47, 0xaf, 0xf4, 0xa7, 0x42, 0xce, 0x61, 0x23, 0x11, 0x14, 0x7e, 0x80, 0xab, 0xc1, 0x4a, 0x01, 0x83, 0xa9, + 0x6f, 0xe1, 0x6b, 0xe2, 0x39, 0x0a, 0x1e, 0x85, 0x5d, 0x8c, 0xad, 0x95, 0xef, 0x7c, 0x52, 0x50, 0xee, 0x59, 0x31, + 0xe7, 0x15, 0x70, 0x2e, 0x83, 0x42, 0x98, 0x8e, 0x67, 0xf9, 0x3f, 0x93, 0xbc, 0x9e, 0xd8, 0x10, 0x20, 0x83, 0x3f, + 0x25, 0x4e, 0x4b, 0x77, 0xe8, 0xce, 0x43, 0xcf, 0x22, 0x0e, 0x1b, 0x3d, 0x5a, 0x97, 0xc5, 0x36, 0x45, 0xbd, 0x84, + 0xf9, 0x81, 0xfc, 0xbc, 0x25, 0xdf, 0x87, 0x28, 0xde, 0x06, 0x3f, 0x67, 0x2c, 0x16, 0xf8, 0xd7, 0xdf, 0x32, 0x46, + 0x13, 0x2d, 0xf8, 0x7b, 0xd6, 0x20, 0x51, 0x31, 0x60, 0x45, 0x00, 0x97, 0xa9, 0xfa, 0xf0, 0x29, 0x31, 0xde, 0x9a, + 0x0d, 0x0f, 0x7c, 0xb3, 0x02, 0x9d, 0xfa, 0xdc, 0x5d, 0xd9, 0x9e, 0xae, 0x46, 0xaa, 0xaa, 0xf1, 0x73, 0xaa, 0xaa, + 0xf1, 0x73, 0x4a, 0xd5, 0xf8, 0x2b, 0xa3, 0xf8, 0x9d, 0xca, 0x67, 0xc8, 0x9c, 0x6c, 0x62, 0x92, 0x4e, 0xdf, 0x1b, + 0x4e, 0xec, 0xb2, 0xdf, 0xba, 0x4d, 0xa4, 0x99, 0x89, 0x14, 0x72, 0x6f, 0x00, 0x6a, 0x26, 0x7e, 0xcc, 0x0d, 0xa7, + 0xc4, 0xf9, 0xb9, 0x87, 0x2b, 0x36, 0xad, 0x5e, 0xd2, 0x82, 0x05, 0x36, 0x2f, 0xb3, 0x3c, 0xd3, 0x04, 0xb6, 0x4d, + 0x99, 0xf5, 0x97, 0xdc, 0x03, 0x08, 0x66, 0x52, 0x13, 0x00, 0xd2, 0x42, 0x54, 0x0a, 0x91, 0xbf, 0xc4, 0x59, 0x7d, + 0xce, 0x7b, 0x9b, 0x3c, 0x26, 0xd2, 0xea, 0x5e, 0xbf, 0x9f, 0x9e, 0xa5, 0x39, 0x05, 0x35, 0x1c, 0x67, 0x9d, 0xfe, + 0x94, 0x05, 0x22, 0x91, 0xab, 0xf4, 0x1f, 0x6e, 0x90, 0x97, 0xf1, 0x7d, 0xdd, 0xf6, 0xfc, 0x89, 0xfa, 0x7b, 0x67, + 0xfd, 0x6d, 0x81, 0xe0, 0x4e, 0x8e, 0xfd, 0x64, 0x55, 0xca, 0x23, 0xe3, 0xd2, 0xde, 0xf3, 0x9b, 0xba, 0x28, 0xb2, + 0x3a, 0x5d, 0x7f, 0x90, 0x7a, 0x1a, 0xdd, 0x17, 0x7b, 0x30, 0x06, 0xef, 0x00, 0xf0, 0x4c, 0x87, 0x06, 0x48, 0xdf, + 0x33, 0xf2, 0x70, 0x9f, 0x5b, 0xf2, 0x93, 0xca, 0xda, 0x24, 0x61, 0x45, 0xb1, 0x19, 0xc6, 0x08, 0x25, 0xe3, 0x34, + 0xb6, 0x7e, 0xbf, 0xaf, 0xfe, 0xde, 0x61, 0x14, 0x15, 0x15, 0x77, 0x8c, 0x46, 0x65, 0x55, 0x8f, 0xb6, 0x83, 0xc3, + 0xe1, 0x3c, 0xb7, 0x71, 0xb4, 0xf5, 0x0a, 0xd8, 0x5b, 0xa1, 0x52, 0xf6, 0x4a, 0x84, 0xe5, 0x87, 0x2b, 0xbf, 0xdf, + 0x87, 0x7f, 0x65, 0xa4, 0x85, 0xe7, 0x4f, 0xf1, 0xd7, 0x4d, 0x5d, 0x60, 0x78, 0x06, 0xad, 0xd1, 0x0a, 0x82, 0x09, + 0xfe, 0xd1, 0x81, 0x7a, 0x69, 0xa5, 0x7d, 0x04, 0xdd, 0x0a, 0xf4, 0xa0, 0xb1, 0x0f, 0x24, 0xed, 0x0b, 0x89, 0xba, + 0xbd, 0xd5, 0x69, 0xf4, 0x67, 0xc5, 0x72, 0x5e, 0xc1, 0xe4, 0x70, 0x43, 0x9f, 0x56, 0xe1, 0xf6, 0x13, 0x3c, 0xfd, + 0x05, 0x28, 0xb7, 0x0e, 0x87, 0x1c, 0xc4, 0x16, 0x70, 0xf3, 0x58, 0x85, 0x5f, 0x8a, 0x52, 0x46, 0xd4, 0xc7, 0xd3, + 0x12, 0xb4, 0x77, 0x01, 0x3a, 0x60, 0x69, 0x10, 0xaf, 0x90, 0x3c, 0x67, 0x23, 0x80, 0x65, 0x07, 0x96, 0xb3, 0x8c, + 0x53, 0x98, 0x67, 0xf9, 0xac, 0xd2, 0xf8, 0xec, 0x89, 0x57, 0xb3, 0x0c, 0x9c, 0x05, 0x2e, 0x2a, 0x9f, 0x65, 0x5a, + 0xf5, 0x54, 0x24, 0xe8, 0xf3, 0x4a, 0x4e, 0x70, 0x25, 0x38, 0xd9, 0x80, 0xfc, 0x02, 0x24, 0x69, 0x4a, 0x59, 0x53, + 0x3e, 0xbb, 0xa4, 0x1b, 0x32, 0x7a, 0xce, 0x7b, 0x5e, 0x34, 0x0c, 0xfd, 0x0b, 0xaf, 0x84, 0xf0, 0x4d, 0xdc, 0xb6, + 0x51, 0x0a, 0xfb, 0x9b, 0xc0, 0xe2, 0x13, 0xf6, 0xa3, 0xb7, 0xf0, 0xa7, 0xe3, 0x20, 0x1c, 0x22, 0x37, 0x54, 0xcc, + 0x81, 0x3d, 0x0d, 0x58, 0x6c, 0xe2, 0xab, 0xcd, 0x24, 0x1e, 0x0c, 0x7c, 0x9d, 0xb1, 0x98, 0xc5, 0x40, 0x83, 0x1c, + 0x0f, 0x2e, 0xe7, 0xfa, 0x84, 0xd0, 0x0f, 0x23, 0x2a, 0x47, 0x05, 0x3a, 0x07, 0xd1, 0x60, 0x01, 0x78, 0xea, 0xad, + 0x6c, 0x90, 0x64, 0x68, 0xa0, 0x13, 0xd7, 0x9a, 0xa4, 0x3a, 0x9c, 0xd0, 0x3a, 0xd0, 0x71, 0xf5, 0x06, 0x3a, 0x1f, + 0xd7, 0xbd, 0x8f, 0x57, 0xc3, 0x1b, 0x2a, 0xfd, 0x42, 0x0c, 0xbc, 0x7a, 0x3a, 0x0e, 0x2e, 0xe9, 0x56, 0x78, 0xb3, + 0x0a, 0xb7, 0xbf, 0xc8, 0x07, 0x8e, 0x3b, 0x2a, 0x69, 0x08, 0x0c, 0xde, 0x1e, 0xba, 0x9b, 0x19, 0xc7, 0x94, 0xa3, + 0xc3, 0x38, 0x92, 0x43, 0xac, 0x5a, 0x71, 0x21, 0xbd, 0x11, 0x7c, 0xbb, 0x50, 0x8c, 0x65, 0x63, 0x97, 0x86, 0xa2, + 0xf0, 0x67, 0x00, 0x3b, 0xd4, 0xfe, 0x4a, 0x25, 0x1f, 0x23, 0xa3, 0x9a, 0x06, 0x3a, 0x06, 0x60, 0xc9, 0xd2, 0x44, + 0x52, 0x45, 0x1a, 0x89, 0x3f, 0x32, 0x63, 0x1d, 0x35, 0x5d, 0x5f, 0xb0, 0x1c, 0x59, 0x92, 0x6e, 0x67, 0x12, 0xcb, + 0x89, 0x24, 0xb5, 0xdd, 0x47, 0xc4, 0x60, 0xe0, 0x83, 0x8d, 0x98, 0x66, 0x22, 0x1c, 0xf1, 0xa8, 0x44, 0x16, 0x5d, + 0x7e, 0x1b, 0x61, 0xd2, 0xf6, 0x65, 0x45, 0xb6, 0x20, 0x98, 0x9e, 0x44, 0x1f, 0x24, 0x29, 0xa7, 0x22, 0x91, 0x66, + 0x84, 0x00, 0x3f, 0x9e, 0x94, 0x57, 0xfa, 0x73, 0xd0, 0xb4, 0x12, 0xbc, 0x64, 0x90, 0x3c, 0x12, 0x3f, 0x93, 0x82, + 0x59, 0x8c, 0x55, 0x83, 0x01, 0x96, 0x53, 0x3d, 0x71, 0x4c, 0xd2, 0x7f, 0xeb, 0x74, 0xc2, 0x7e, 0xee, 0xe5, 0xb6, + 0x96, 0x37, 0xcd, 0xbd, 0xe7, 0x5e, 0xc5, 0x52, 0x0d, 0xcb, 0xa0, 0xff, 0x9a, 0x68, 0x17, 0x6c, 0x6d, 0x19, 0x13, + 0x56, 0xfd, 0x00, 0xd2, 0x1e, 0xe9, 0xf2, 0xaa, 0x61, 0xce, 0x04, 0x8f, 0x2e, 0xac, 0x79, 0x10, 0x5d, 0x08, 0x1f, + 0xb9, 0xec, 0x26, 0xc9, 0xd5, 0x78, 0xe2, 0x87, 0x83, 0x81, 0x02, 0xa0, 0xa5, 0x75, 0x52, 0x0c, 0xc2, 0x27, 0x42, + 0x0e, 0xa4, 0xd1, 0x51, 0x15, 0x60, 0xb1, 0xcc, 0xae, 0xca, 0x49, 0x36, 0x18, 0xf8, 0x20, 0x36, 0x26, 0x76, 0x43, + 0xb3, 0xb9, 0xcf, 0x4e, 0x14, 0x64, 0xb5, 0x39, 0x6a, 0xcd, 0x74, 0x0b, 0x0c, 0x00, 0x06, 0x11, 0xc1, 0x72, 0x9f, + 0x1a, 0xf9, 0x88, 0x3a, 0x3d, 0x85, 0x11, 0x10, 0xfc, 0x72, 0x22, 0x10, 0xb9, 0x48, 0xa0, 0x1e, 0x60, 0x26, 0xc0, + 0x8c, 0x2a, 0x86, 0x97, 0xc0, 0x2e, 0x9e, 0x9b, 0x57, 0x0c, 0xfa, 0x17, 0x89, 0xd9, 0x89, 0xa6, 0x12, 0x47, 0x63, + 0xe4, 0x54, 0x1a, 0x23, 0x03, 0x62, 0x17, 0xc7, 0xbf, 0xa7, 0xf4, 0x28, 0x48, 0xd9, 0x8b, 0xca, 0x10, 0x87, 0xa3, + 0xf8, 0x0a, 0x56, 0x8d, 0xc3, 0xa1, 0x36, 0xaf, 0xa7, 0xb3, 0x7a, 0x3e, 0x10, 0x01, 0xfc, 0x37, 0x14, 0xec, 0x37, + 0x4d, 0x45, 0x6e, 0x90, 0x3a, 0x0f, 0x87, 0x14, 0xe4, 0x53, 0xdd, 0xe4, 0x9f, 0x2a, 0x77, 0x3f, 0x9d, 0xcd, 0xad, + 0x39, 0x7a, 0x51, 0xe3, 0xba, 0xb5, 0xba, 0xa1, 0x90, 0x68, 0x4d, 0x93, 0xe2, 0xaa, 0x9a, 0x14, 0x03, 0x9e, 0xfb, + 0x42, 0x75, 0xb1, 0x35, 0x82, 0x85, 0x3f, 0xb7, 0x40, 0x98, 0xf4, 0xb7, 0x92, 0x0e, 0xa9, 0x1a, 0x77, 0x6d, 0xb5, + 0xdb, 0x56, 0x36, 0xa4, 0x68, 0x3e, 0xbc, 0x84, 0x5d, 0x3a, 0x45, 0xb4, 0xed, 0x92, 0xe0, 0x0b, 0xd0, 0xb2, 0x7a, + 0x23, 0xf2, 0x98, 0x7e, 0x85, 0xfc, 0x52, 0x0c, 0xff, 0x53, 0xba, 0x37, 0xa7, 0x36, 0xc8, 0x01, 0x6c, 0xf7, 0x1e, + 0x6e, 0xc7, 0xe8, 0x81, 0x0c, 0xde, 0x08, 0x39, 0xe7, 0xfc, 0x72, 0x6a, 0xcd, 0x98, 0x68, 0x58, 0xb0, 0x72, 0x18, + 0xf9, 0x01, 0x32, 0x5e, 0x4e, 0x81, 0x95, 0xfd, 0xa8, 0x88, 0x4b, 0x7f, 0x18, 0xf9, 0x17, 0x4f, 0x83, 0x8c, 0x7b, + 0xd1, 0xb0, 0xe3, 0x0b, 0xb0, 0x57, 0x5f, 0x3c, 0x65, 0xd1, 0x80, 0x57, 0x57, 0xf5, 0x34, 0x0b, 0x86, 0x19, 0x8b, + 0xae, 0x8a, 0x21, 0xf8, 0xd0, 0x3e, 0x2b, 0x07, 0xa1, 0xef, 0x9b, 0x9d, 0x43, 0x77, 0x43, 0x2c, 0x8f, 0xb0, 0x9f, + 0xc0, 0x6d, 0x57, 0x4b, 0xcc, 0x60, 0xb2, 0x59, 0x46, 0xcc, 0x60, 0xcb, 0x5f, 0x3c, 0x35, 0x5c, 0x42, 0xd5, 0x33, + 0xa9, 0xd9, 0x28, 0xd0, 0x9c, 0x5c, 0xa1, 0x39, 0x59, 0x09, 0xb5, 0xe4, 0x93, 0x0a, 0x27, 0xec, 0x7c, 0x92, 0x2b, + 0xbb, 0xd1, 0x18, 0x03, 0x17, 0xad, 0xb9, 0x1d, 0x0a, 0x23, 0x33, 0x9d, 0xa5, 0x68, 0xc0, 0xc2, 0x33, 0x71, 0x4a, + 0x63, 0x40, 0xfb, 0x72, 0x60, 0x69, 0x43, 0x7e, 0x95, 0x33, 0x03, 0x6d, 0x43, 0x4a, 0xa3, 0x66, 0xe0, 0xcf, 0xd4, + 0x84, 0xf9, 0x0c, 0x56, 0x22, 0x88, 0xea, 0x02, 0x4c, 0x92, 0x9c, 0x8c, 0x46, 0xca, 0x4a, 0x24, 0xe7, 0x80, 0xf7, + 0x11, 0x3c, 0x59, 0xc4, 0xb6, 0xf6, 0xa7, 0xf4, 0xbf, 0x3a, 0x7c, 0x2e, 0xfd, 0x27, 0x02, 0x58, 0xc8, 0xa5, 0x41, + 0x64, 0xa0, 0x70, 0x48, 0x2d, 0xc3, 0x7b, 0xe2, 0x78, 0x06, 0xbe, 0x86, 0x0b, 0x34, 0x05, 0xf4, 0x07, 0x35, 0xa3, + 0x88, 0x2c, 0xfc, 0xd5, 0xb3, 0x9b, 0xba, 0xd0, 0xf3, 0xcc, 0x79, 0x0d, 0x9a, 0x19, 0x08, 0xe9, 0x71, 0xaa, 0xde, + 0x86, 0x44, 0xe7, 0xe5, 0xb5, 0x7e, 0x99, 0x10, 0xc9, 0xca, 0xc8, 0xd3, 0xf7, 0x39, 0x98, 0x47, 0x14, 0xa1, 0x83, + 0x2b, 0xf3, 0x70, 0x38, 0x17, 0x14, 0xbe, 0xa3, 0x3c, 0x1f, 0x70, 0x9a, 0x65, 0x09, 0x68, 0x03, 0x59, 0x6e, 0xca, + 0x5c, 0x26, 0x2d, 0x53, 0xf7, 0x1e, 0xac, 0x04, 0x15, 0xba, 0x39, 0x05, 0x85, 0x32, 0x12, 0x94, 0xd2, 0x6a, 0x10, + 0x4a, 0x75, 0x58, 0x04, 0x91, 0x43, 0x16, 0x02, 0x6e, 0xa6, 0xa2, 0xd1, 0x92, 0x86, 0x47, 0x38, 0x37, 0x50, 0x08, + 0x40, 0x62, 0x4f, 0x15, 0x65, 0x5c, 0x0e, 0x01, 0x1f, 0x25, 0x1c, 0xe2, 0xac, 0x49, 0x5b, 0x9e, 0x83, 0x38, 0x96, + 0x0b, 0xbe, 0xac, 0x10, 0x0c, 0x22, 0xf4, 0x19, 0xf2, 0x27, 0xcb, 0xf9, 0x77, 0xeb, 0x30, 0xed, 0x08, 0x1f, 0x76, + 0xb5, 0x1b, 0x2e, 0x66, 0xb7, 0xf3, 0x09, 0xc4, 0xb7, 0xdc, 0xce, 0x8f, 0x31, 0x44, 0x6e, 0xfc, 0xc1, 0x72, 0x28, + 0xb9, 0xa2, 0xd0, 0x65, 0x3d, 0x22, 0x45, 0xf6, 0x74, 0xcd, 0x11, 0x04, 0x07, 0x5a, 0x35, 0xc8, 0xd0, 0x48, 0x7c, + 0xf1, 0x14, 0xb2, 0x06, 0x6b, 0xfe, 0xa2, 0x22, 0x67, 0x75, 0x7f, 0xb2, 0x81, 0x6a, 0x92, 0xc9, 0x5a, 0x51, 0x39, + 0x7f, 0xbb, 0x2a, 0x8b, 0x93, 0x55, 0x19, 0xae, 0x06, 0x5d, 0x55, 0x59, 0x70, 0xa4, 0x36, 0x40, 0x6b, 0xba, 0x42, + 0x0c, 0x85, 0xac, 0xc1, 0xc2, 0xaa, 0xca, 0x9a, 0xfa, 0x04, 0x02, 0x7d, 0x80, 0x65, 0xd4, 0xec, 0xa7, 0xc3, 0x5f, + 0x83, 0x5f, 0x55, 0xc8, 0x52, 0x9d, 0xd6, 0x99, 0xf8, 0x1c, 0x2c, 0x18, 0xfe, 0xf1, 0x7b, 0xb0, 0x06, 0x2c, 0x01, + 0xb2, 0xdc, 0x6d, 0x6c, 0xb4, 0x5e, 0x79, 0x85, 0x78, 0x57, 0xeb, 0x8b, 0x7e, 0xeb, 0x36, 0x51, 0x2b, 0xc0, 0x08, + 0x85, 0x16, 0x01, 0xb6, 0x7a, 0xe0, 0x9e, 0x82, 0x1f, 0x88, 0xe1, 0x5c, 0x93, 0xd6, 0xd4, 0x09, 0xaf, 0xb3, 0x71, + 0x24, 0xa2, 0x7a, 0x0b, 0x17, 0xf7, 0x7a, 0x6b, 0xf1, 0x37, 0x2a, 0x10, 0x00, 0x59, 0x4c, 0xb1, 0x76, 0xde, 0x90, + 0x5e, 0x19, 0x76, 0x12, 0x7a, 0x6f, 0xd8, 0x09, 0xe4, 0xc5, 0x61, 0xa7, 0xd0, 0x25, 0xda, 0x4e, 0x91, 0x9a, 0x68, + 0x3b, 0xe9, 0x66, 0x15, 0x96, 0x10, 0xfc, 0xaa, 0xbd, 0x75, 0x94, 0xed, 0x8b, 0x2c, 0x61, 0xda, 0x02, 0x46, 0xb9, + 0x55, 0x9f, 0x39, 0x45, 0xac, 0x94, 0xbd, 0xd3, 0x49, 0x95, 0xbb, 0xc8, 0xa7, 0x56, 0x53, 0x64, 0xf2, 0x8b, 0xe3, + 0x16, 0xc9, 0x27, 0xbf, 0xb4, 0x1b, 0x26, 0xd3, 0x3f, 0x1e, 0x7d, 0x01, 0x5d, 0x91, 0x9d, 0x3e, 0x81, 0x80, 0x4c, + 0x05, 0xd5, 0xea, 0x56, 0x31, 0xcd, 0xdb, 0x55, 0x76, 0x7b, 0xa1, 0xc4, 0x70, 0x3a, 0x3b, 0x09, 0x8f, 0x36, 0x43, + 0x06, 0x0e, 0x41, 0xa0, 0x10, 0x2a, 0x8a, 0xe1, 0x11, 0xa8, 0x35, 0x92, 0x0f, 0xf0, 0xa3, 0xdd, 0xa9, 0x20, 0x52, + 0xbb, 0xa9, 0xb8, 0x71, 0x72, 0xd3, 0xf5, 0x52, 0xa0, 0xd6, 0x29, 0x59, 0x01, 0x94, 0x10, 0xf5, 0x27, 0xb1, 0xad, + 0x5f, 0xc2, 0x15, 0x9b, 0xef, 0x1b, 0x45, 0x4f, 0xae, 0x4f, 0x51, 0xb7, 0xe2, 0xea, 0x34, 0x6d, 0x35, 0xc7, 0x8e, + 0x33, 0xe4, 0xe0, 0x59, 0x41, 0xb0, 0x1d, 0x95, 0x28, 0xdf, 0xb6, 0x9b, 0x8e, 0x89, 0xad, 0xfe, 0xb9, 0xa9, 0x36, + 0x4b, 0xa8, 0x88, 0x88, 0x8f, 0xb2, 0x9b, 0x27, 0xed, 0x77, 0xb0, 0xc7, 0x5a, 0x0d, 0x22, 0xfb, 0x0c, 0xae, 0x72, + 0x9d, 0x16, 0xb9, 0x2d, 0x83, 0xf3, 0x0f, 0xaf, 0x76, 0x15, 0x36, 0x39, 0xd6, 0xd5, 0xd5, 0x4c, 0x75, 0x52, 0xb1, + 0x81, 0xb1, 0xa6, 0xb5, 0x54, 0xf3, 0x18, 0x92, 0xee, 0xca, 0xe2, 0xac, 0x4a, 0xba, 0xe9, 0xb9, 0x71, 0xa6, 0x10, + 0x03, 0x67, 0xab, 0xd1, 0x72, 0x86, 0x21, 0xba, 0x3e, 0xcc, 0x12, 0xbf, 0xd5, 0x53, 0xee, 0xf3, 0x70, 0xeb, 0x77, + 0xf5, 0x82, 0x93, 0xc9, 0x7e, 0x72, 0x9c, 0xbb, 0x5d, 0xa4, 0xfd, 0xc4, 0xb7, 0x61, 0xfe, 0xf5, 0x0d, 0x62, 0x29, + 0xea, 0x5f, 0x2b, 0x00, 0x1a, 0xdc, 0xe4, 0xb1, 0x44, 0xa9, 0xdf, 0xab, 0xea, 0x07, 0x35, 0x53, 0x35, 0x0d, 0x04, + 0x73, 0x2a, 0x05, 0xfc, 0xe1, 0x76, 0xe1, 0x8a, 0x47, 0xdc, 0xb0, 0x30, 0xfe, 0xe5, 0xd5, 0xec, 0x54, 0x50, 0x19, + 0xb8, 0x19, 0xff, 0xe5, 0x09, 0x76, 0x0a, 0x6b, 0x05, 0x64, 0x85, 0xbf, 0xbc, 0xfc, 0x81, 0xf7, 0x2b, 0xfe, 0x97, + 0x57, 0x3d, 0xf0, 0x3e, 0xe2, 0xbc, 0xfc, 0x85, 0xa4, 0x4e, 0x88, 0xea, 0xf2, 0x17, 0x61, 0x8a, 0xad, 0xd2, 0xfc, + 0x15, 0x29, 0x7c, 0x82, 0x2f, 0xc0, 0x77, 0xb8, 0x0a, 0xb7, 0xe6, 0x37, 0x78, 0xec, 0x58, 0x6c, 0xbb, 0xd4, 0x17, + 0x50, 0x8e, 0xc0, 0x22, 0x72, 0xfb, 0xed, 0xca, 0x7e, 0xb5, 0x30, 0xca, 0x18, 0xbb, 0x2f, 0x59, 0x89, 0xd2, 0x59, + 0xbf, 0x5f, 0x48, 0xc1, 0xc8, 0x2e, 0xac, 0xd1, 0x1e, 0xa5, 0xea, 0xd5, 0xb7, 0x61, 0x1d, 0x25, 0x69, 0xbe, 0x94, + 0xd1, 0x47, 0x32, 0xec, 0x48, 0x5f, 0x49, 0x89, 0xf6, 0x5a, 0x85, 0xe5, 0x68, 0xf6, 0xeb, 0x92, 0x03, 0xe5, 0x75, + 0x2b, 0x28, 0x5f, 0x35, 0x01, 0xf4, 0x4a, 0xb5, 0xcf, 0x40, 0x2b, 0x28, 0x2c, 0x95, 0x07, 0x2b, 0x71, 0x2e, 0xfa, + 0xac, 0x38, 0x1c, 0xd4, 0xc5, 0x90, 0x50, 0xa0, 0x4a, 0x9c, 0x84, 0x46, 0x3c, 0x87, 0x0b, 0xa1, 0x78, 0x96, 0x63, + 0x6c, 0x45, 0x0e, 0x1c, 0xc8, 0xf0, 0x03, 0x02, 0xef, 0x65, 0xff, 0x0a, 0x06, 0xc3, 0x04, 0x37, 0x32, 0xea, 0xe4, + 0x9c, 0xfd, 0x85, 0x81, 0x19, 0xd4, 0x93, 0xda, 0x7d, 0x76, 0xaf, 0x02, 0x7b, 0xe1, 0x0c, 0x68, 0xef, 0xc6, 0xe8, + 0x67, 0x55, 0xac, 0x9d, 0xf4, 0x4f, 0xc5, 0x1a, 0x92, 0xe9, 0xb0, 0x38, 0xda, 0xa6, 0xe1, 0x91, 0x3c, 0x39, 0x8e, + 0x37, 0xfd, 0xc3, 0x61, 0x8c, 0x1f, 0x47, 0xf9, 0xb5, 0x05, 0xbc, 0x8a, 0x5b, 0x48, 0x63, 0x91, 0xa2, 0x77, 0x20, + 0xe6, 0x50, 0xf4, 0x92, 0xfd, 0x96, 0xf1, 0x72, 0x22, 0x28, 0x25, 0x89, 0x0d, 0xef, 0x48, 0x4f, 0xd3, 0x7a, 0xb4, + 0x95, 0x01, 0xfb, 0xf5, 0x68, 0x47, 0x7f, 0x81, 0xe2, 0xd1, 0xc2, 0x5f, 0xd2, 0xdf, 0xc5, 0xdd, 0xdc, 0x73, 0xbe, + 0x69, 0x7c, 0x47, 0x5c, 0xa0, 0x58, 0xb3, 0xfb, 0x6b, 0x5a, 0x3a, 0xeb, 0x40, 0x70, 0xc0, 0x5b, 0xec, 0xa2, 0x7d, + 0xbf, 0x71, 0x9d, 0x9e, 0xf6, 0xdf, 0xbb, 0x35, 0xca, 0xf7, 0x7e, 0x95, 0x28, 0x07, 0xfb, 0x37, 0x2e, 0x9a, 0xbf, + 0xfd, 0x94, 0x21, 0xa9, 0xd0, 0xdc, 0x60, 0x3b, 0xd9, 0x22, 0xac, 0x8d, 0x71, 0x50, 0xb1, 0x65, 0x19, 0x46, 0xc0, + 0xa0, 0x8e, 0xfd, 0x8f, 0x3e, 0x9b, 0x36, 0x64, 0x1f, 0x00, 0x2a, 0x57, 0x21, 0x60, 0x0f, 0xc0, 0x89, 0x46, 0xb8, + 0x01, 0x6e, 0x35, 0x5a, 0xd2, 0x41, 0xdd, 0x16, 0x0c, 0x44, 0x4b, 0xd8, 0xc8, 0xdb, 0xae, 0x4e, 0xdf, 0x10, 0x3e, + 0xd4, 0x4e, 0x4a, 0x87, 0xf2, 0x37, 0xcf, 0xd9, 0x7f, 0xef, 0xb0, 0xa6, 0xa6, 0x5c, 0x03, 0x66, 0xce, 0x4a, 0xe4, + 0x15, 0x42, 0xa7, 0xc8, 0xef, 0x55, 0x5d, 0x89, 0xe1, 0xa2, 0x16, 0x65, 0x67, 0x76, 0xeb, 0x44, 0xef, 0x9c, 0x82, + 0x5a, 0x2a, 0x1b, 0xe4, 0x24, 0xd5, 0xe6, 0x23, 0x6b, 0x05, 0x25, 0xea, 0x1a, 0x05, 0x8e, 0x4f, 0xb9, 0x76, 0xff, + 0xef, 0x9c, 0x09, 0x6a, 0xb6, 0x51, 0xdd, 0x5f, 0xe9, 0xa7, 0xaa, 0x26, 0xb1, 0x00, 0x97, 0x93, 0x34, 0xef, 0x78, + 0x84, 0xd5, 0x3f, 0x4e, 0x96, 0x22, 0xd0, 0xab, 0x88, 0x76, 0x25, 0x20, 0x41, 0x3b, 0x39, 0x0b, 0x15, 0x81, 0x02, + 0x7d, 0xfd, 0xc5, 0x26, 0xcd, 0x62, 0xb9, 0x9a, 0xed, 0x61, 0xa2, 0x2c, 0xd6, 0x43, 0x04, 0x39, 0x33, 0x75, 0xb0, + 0xdf, 0xd3, 0x8c, 0x66, 0xe1, 0x95, 0x29, 0xc1, 0xa5, 0xb8, 0x8a, 0x8a, 0x1c, 0x7c, 0x0e, 0xf1, 0x85, 0x4f, 0x85, + 0xdc, 0x20, 0xa2, 0xe9, 0x4f, 0x12, 0xd5, 0x8e, 0x14, 0xc8, 0xa1, 0xe4, 0x27, 0xc4, 0x5f, 0xb2, 0x36, 0xc6, 0xfd, + 0xd2, 0xa9, 0xf6, 0x4b, 0x85, 0xe0, 0xfe, 0x8b, 0x2d, 0x36, 0xaa, 0x3c, 0xd1, 0x83, 0x4f, 0xb1, 0xfe, 0x27, 0x0b, + 0x28, 0xd5, 0x7d, 0x1b, 0x9c, 0x8a, 0x47, 0xe1, 0xa6, 0x2e, 0xae, 0x11, 0x5a, 0xa0, 0x1c, 0x55, 0xc5, 0xa6, 0x8c, + 0x88, 0x13, 0x76, 0x53, 0x17, 0x3d, 0xcd, 0x81, 0x2e, 0xe7, 0x75, 0x22, 0x4f, 0x84, 0x76, 0x0b, 0xba, 0xa7, 0x39, + 0x56, 0xe2, 0xb9, 0x2c, 0x1d, 0x64, 0x9d, 0x48, 0x13, 0x2a, 0x77, 0x75, 0xd5, 0x51, 0xa9, 0xd4, 0x0d, 0xaf, 0x53, + 0xcd, 0xf8, 0xbb, 0x30, 0x7f, 0x62, 0xd9, 0xaf, 0x5b, 0xbf, 0xd5, 0x6a, 0x6f, 0xac, 0x1e, 0x95, 0xac, 0x39, 0xce, + 0x26, 0x24, 0xa5, 0x4f, 0xd8, 0x6e, 0x26, 0x5d, 0xeb, 0xc0, 0x93, 0xe0, 0x72, 0xe8, 0x09, 0xa8, 0x18, 0x34, 0xf1, + 0x76, 0x17, 0xa8, 0x47, 0xe0, 0x19, 0x28, 0x9f, 0xa8, 0x75, 0xc0, 0xcf, 0x6b, 0x2d, 0x4f, 0x19, 0x61, 0x58, 0xed, + 0x2c, 0x5a, 0x0e, 0xce, 0x3b, 0x45, 0xe0, 0xda, 0x95, 0xc0, 0xf3, 0xa1, 0x7a, 0x2f, 0x04, 0x0c, 0xf7, 0x4f, 0x85, + 0xca, 0x66, 0x37, 0xc3, 0x79, 0xd4, 0x38, 0x3d, 0xd0, 0xde, 0x76, 0xad, 0x87, 0x7a, 0xd7, 0xed, 0xdc, 0x56, 0xba, + 0xf7, 0x6b, 0x27, 0x93, 0x2e, 0xa0, 0xb5, 0xf9, 0xec, 0x3b, 0xbb, 0xd2, 0xba, 0xe9, 0x39, 0x7b, 0xb0, 0x75, 0x4b, + 0x74, 0x2e, 0x88, 0x26, 0xbf, 0x1f, 0x78, 0xd6, 0xb6, 0xa3, 0xdf, 0xa6, 0x1d, 0xdb, 0xdc, 0x43, 0xdd, 0x2b, 0xa8, + 0xf5, 0x86, 0xe6, 0xfd, 0x33, 0xd7, 0xb6, 0xe3, 0xab, 0x5f, 0xd7, 0x1d, 0xae, 0xf3, 0x26, 0x38, 0x6e, 0xba, 0xb6, + 0xd5, 0xce, 0x7e, 0xee, 0xee, 0xad, 0x9b, 0x28, 0xcc, 0xb2, 0x9f, 0x8a, 0xe2, 0xcf, 0x4a, 0xdf, 0x11, 0xe8, 0xe8, + 0xce, 0x8b, 0x3a, 0x5d, 0xec, 0x3e, 0x10, 0xc6, 0x93, 0x57, 0x1f, 0x11, 0xdd, 0xfa, 0x3e, 0x73, 0xbf, 0x02, 0xdc, + 0x08, 0xee, 0x20, 0xda, 0xbb, 0xa5, 0x3e, 0xa9, 0xd5, 0xd7, 0x7a, 0xed, 0x3c, 0x3d, 0xbf, 0xe9, 0xdc, 0x7e, 0xf7, + 0xcd, 0xd1, 0xd6, 0x7b, 0x5c, 0x58, 0x2b, 0x4b, 0x4f, 0x55, 0xc1, 0xde, 0x2c, 0x4f, 0x55, 0xc1, 0xe4, 0x81, 0xd7, + 0xec, 0x17, 0x34, 0xb8, 0xd2, 0xd1, 0xc6, 0x7b, 0xa2, 0x06, 0x6e, 0x51, 0x58, 0x3a, 0xfc, 0x92, 0x9b, 0xc9, 0x4b, + 0xdc, 0x5f, 0x2a, 0x72, 0xb1, 0xef, 0x9c, 0xd1, 0x9d, 0x99, 0x75, 0xaf, 0x2a, 0x5c, 0x2d, 0xc8, 0xd5, 0x81, 0xad, + 0x65, 0x17, 0x87, 0x1b, 0x16, 0x51, 0x80, 0x40, 0x4c, 0xaf, 0xd4, 0xda, 0x1f, 0xd1, 0x20, 0xe4, 0x83, 0x81, 0x5f, + 0x60, 0xb0, 0x2a, 0x50, 0xf8, 0x40, 0x91, 0xfc, 0x8d, 0x27, 0x60, 0x17, 0xcf, 0x00, 0xdd, 0x8a, 0xcd, 0x8a, 0x11, + 0x22, 0x64, 0xb2, 0x9c, 0xd5, 0x74, 0x06, 0xf9, 0xd4, 0x17, 0xdf, 0xd9, 0xaa, 0xd3, 0x79, 0x5b, 0x53, 0xe5, 0xd4, + 0xa1, 0xd0, 0xdd, 0x4d, 0xdd, 0xb9, 0x75, 0x91, 0xa7, 0x0e, 0x21, 0x57, 0x2a, 0x56, 0x62, 0x1a, 0x6a, 0x9e, 0xa4, + 0x19, 0xf5, 0x37, 0x7b, 0xbf, 0xd7, 0x28, 0x9c, 0xf2, 0xa7, 0x63, 0x50, 0x85, 0xab, 0x1a, 0xe2, 0x58, 0xaa, 0xe2, + 0x91, 0x0d, 0x02, 0xcd, 0xab, 0x5b, 0x95, 0x34, 0x21, 0x93, 0x1b, 0xe1, 0x53, 0x93, 0x52, 0x9e, 0xa6, 0x4d, 0x5a, + 0x29, 0x52, 0x07, 0x1f, 0xd4, 0xa9, 0xc6, 0x73, 0xb3, 0x7a, 0x06, 0x60, 0xc6, 0xf9, 0x15, 0xbf, 0x54, 0x5c, 0x46, + 0x6d, 0x65, 0x26, 0xed, 0x4f, 0x8e, 0xc6, 0x46, 0x5d, 0x4e, 0x1b, 0x65, 0x84, 0x95, 0xd2, 0x9c, 0x14, 0xcb, 0xf1, + 0xfc, 0x03, 0x06, 0x6b, 0x9e, 0xc0, 0x0e, 0x26, 0x2a, 0xe5, 0x7d, 0x04, 0xc4, 0xd7, 0x49, 0xba, 0x4c, 0x20, 0x45, + 0xfa, 0x97, 0x2e, 0x78, 0xea, 0x30, 0x36, 0x10, 0x63, 0x56, 0xcc, 0x8c, 0xfe, 0x07, 0x77, 0x49, 0x7f, 0x12, 0x02, + 0xe0, 0x26, 0x9a, 0x42, 0xa7, 0xce, 0x93, 0x8b, 0x3c, 0x58, 0x5c, 0x78, 0x68, 0xc5, 0x88, 0x07, 0xff, 0xf9, 0x2c, + 0x44, 0x10, 0x73, 0x4c, 0xf1, 0xf4, 0x0b, 0xa3, 0xff, 0x08, 0x2e, 0x31, 0x82, 0xd0, 0xdd, 0x3b, 0x87, 0x21, 0xdc, + 0xec, 0x41, 0x06, 0xf5, 0x87, 0x3a, 0x24, 0x6a, 0xf8, 0x6b, 0xe5, 0x41, 0xff, 0xd7, 0x99, 0xb0, 0xd4, 0x7e, 0x7a, + 0x3a, 0x80, 0x0a, 0xde, 0x57, 0xbc, 0x8d, 0x88, 0xef, 0x13, 0x3f, 0x89, 0x07, 0x9b, 0x27, 0x1b, 0xb0, 0xd6, 0x3d, + 0xca, 0x8d, 0x75, 0x95, 0xb0, 0x81, 0x80, 0xaf, 0x31, 0xad, 0x3d, 0xaf, 0xdd, 0xee, 0xc1, 0x7f, 0xfa, 0x17, 0x21, + 0x03, 0x26, 0x4e, 0xdf, 0x67, 0x4e, 0xd6, 0xe8, 0x22, 0x93, 0xe9, 0x43, 0x27, 0x7d, 0xa3, 0xd3, 0x7d, 0x27, 0xfc, + 0xa3, 0x62, 0x16, 0x1f, 0x6e, 0xe9, 0x2b, 0x4d, 0x8a, 0x3b, 0x60, 0x65, 0xf3, 0xa0, 0x20, 0xd4, 0xb9, 0x88, 0xbe, + 0x31, 0xe5, 0x5b, 0x42, 0xcd, 0xbe, 0xb1, 0xa4, 0x94, 0xee, 0x35, 0xf4, 0x3a, 0xad, 0xf5, 0xdb, 0x28, 0xc1, 0x98, + 0xe8, 0x78, 0xf2, 0x32, 0x1e, 0x2b, 0xef, 0xe3, 0x71, 0x23, 0x15, 0xf2, 0x00, 0x44, 0xa0, 0x62, 0xfc, 0xe9, 0xca, + 0x93, 0x93, 0x5e, 0x18, 0xaf, 0x42, 0x29, 0x28, 0x0c, 0xe8, 0x0a, 0xa4, 0x80, 0x47, 0xed, 0x89, 0xce, 0xc2, 0x2e, + 0xe1, 0x1e, 0xdd, 0x04, 0x8c, 0xf5, 0xf9, 0x57, 0x40, 0x73, 0x17, 0xee, 0xf0, 0x62, 0x80, 0xda, 0xd4, 0xab, 0xbb, + 0x8f, 0x6b, 0x75, 0x0e, 0x87, 0xe0, 0x60, 0x35, 0x88, 0xe0, 0x74, 0x3e, 0x75, 0x34, 0xcb, 0x02, 0x54, 0x4e, 0x96, + 0x1b, 0x79, 0xf3, 0x68, 0xd1, 0xab, 0xfb, 0xde, 0x22, 0x2d, 0xab, 0x3a, 0xc8, 0x58, 0x16, 0x56, 0x80, 0xab, 0x43, + 0xeb, 0x07, 0xe1, 0xb2, 0x70, 0xfe, 0x40, 0x08, 0x62, 0xf7, 0x6a, 0x5b, 0xf0, 0x5c, 0xcd, 0xe1, 0x27, 0x4f, 0xd9, + 0x9a, 0x4b, 0xd4, 0x49, 0x67, 0x22, 0x00, 0xb1, 0xa7, 0x66, 0x15, 0x5d, 0x03, 0x49, 0x9d, 0x66, 0x15, 0x5d, 0x53, + 0xb3, 0x8d, 0x71, 0x20, 0x1f, 0xad, 0x52, 0xc0, 0xbe, 0x9b, 0x8e, 0x83, 0xd5, 0x93, 0x58, 0x5e, 0x87, 0x96, 0x4f, + 0x36, 0xca, 0x67, 0x50, 0xb7, 0xda, 0x18, 0x13, 0xdb, 0xcd, 0x97, 0x73, 0xfd, 0x76, 0xb0, 0xf0, 0xed, 0xa0, 0x39, + 0xa7, 0xec, 0xa5, 0x2e, 0x7b, 0x65, 0x97, 0x4d, 0x3d, 0x77, 0x54, 0xb4, 0x1a, 0x03, 0x7a, 0x03, 0x0b, 0xd6, 0xe7, + 0x22, 0xcd, 0x56, 0xa5, 0x2a, 0x01, 0x2f, 0x8c, 0x15, 0x5b, 0xfa, 0x8d, 0xcc, 0x90, 0x84, 0x79, 0x9c, 0x89, 0xb7, + 0x74, 0xaf, 0x85, 0xc9, 0x71, 0x2c, 0x92, 0x29, 0xa1, 0x53, 0xba, 0xb3, 0x0d, 0x9d, 0xab, 0x30, 0x8a, 0x68, 0xad, + 0xa4, 0xd2, 0x48, 0x60, 0x6a, 0x06, 0x28, 0x99, 0x2b, 0x70, 0x4a, 0x97, 0xfb, 0xdf, 0x91, 0x18, 0x67, 0xbe, 0x28, + 0x99, 0x01, 0xdd, 0xf2, 0xeb, 0x62, 0xdd, 0x4a, 0x91, 0x11, 0xe6, 0xcd, 0x71, 0x7b, 0x5d, 0x1f, 0x02, 0xb9, 0x5a, + 0xf6, 0x28, 0x1a, 0x07, 0x85, 0x0e, 0x97, 0x2a, 0x01, 0xf6, 0x45, 0xe2, 0x67, 0x84, 0x2d, 0xed, 0x81, 0xdc, 0x1e, + 0x9d, 0x09, 0x73, 0xce, 0x49, 0x59, 0x76, 0x2e, 0xcd, 0xe0, 0x72, 0xe2, 0x4a, 0x70, 0x91, 0xde, 0xb6, 0xa7, 0x49, + 0x4b, 0xdb, 0xc7, 0x86, 0x73, 0x34, 0xb4, 0x0d, 0xba, 0x63, 0x7f, 0x68, 0x2e, 0x16, 0xb1, 0x75, 0xb1, 0x18, 0x76, + 0x66, 0x3f, 0x5a, 0x2c, 0x40, 0x0e, 0x00, 0x47, 0xdd, 0x86, 0x8f, 0xd9, 0x02, 0x38, 0xad, 0xa6, 0xd9, 0xd4, 0xdb, + 0xf0, 0xea, 0x89, 0xea, 0xe9, 0x05, 0xcf, 0x9f, 0x08, 0x33, 0x16, 0x1b, 0x9e, 0x3f, 0xb1, 0x8e, 0x9c, 0xea, 0x89, + 0x50, 0xa2, 0x75, 0x01, 0xcd, 0xc0, 0x6b, 0x0a, 0x18, 0xb1, 0x64, 0x32, 0xa5, 0x8a, 0x3c, 0xee, 0x4d, 0x37, 0x6a, + 0xf0, 0x82, 0xc2, 0x21, 0x90, 0xd2, 0xe9, 0x17, 0x4f, 0x99, 0x7e, 0xef, 0xe2, 0x69, 0x87, 0xac, 0x6d, 0x98, 0x2e, + 0x37, 0xc3, 0x64, 0x50, 0xfa, 0x4f, 0xcc, 0xc4, 0xb8, 0xb0, 0x26, 0x09, 0x20, 0xfe, 0x8d, 0xfd, 0x0e, 0x29, 0xdc, + 0xbc, 0xbf, 0x18, 0xc6, 0x0f, 0xbc, 0x1f, 0x23, 0x7b, 0x92, 0x66, 0x88, 0x35, 0x93, 0x0a, 0xb9, 0xfb, 0x6a, 0xfd, + 0x63, 0x62, 0x37, 0xd9, 0x03, 0x0b, 0x40, 0x6c, 0x4d, 0x5b, 0xdd, 0xf2, 0x7e, 0xdf, 0x33, 0x45, 0x80, 0x1f, 0x94, + 0x7f, 0x74, 0x67, 0x48, 0x06, 0x65, 0xd7, 0x0d, 0x21, 0x1e, 0x94, 0x4d, 0xd3, 0x5e, 0x6f, 0x7b, 0x67, 0x1e, 0xab, + 0xeb, 0xb4, 0xb3, 0xb8, 0x5a, 0x64, 0x90, 0x56, 0x1f, 0xb2, 0xe3, 0xcc, 0x3e, 0x3b, 0x5a, 0x2a, 0xdd, 0xef, 0x43, + 0x44, 0xdc, 0x51, 0xd6, 0xf6, 0xdb, 0x2d, 0xb8, 0x86, 0xa3, 0x41, 0xe8, 0xca, 0xde, 0x2e, 0xa3, 0x8d, 0x0b, 0x71, + 0xdc, 0x33, 0x9d, 0x2f, 0xf8, 0xf2, 0x28, 0xed, 0x3c, 0x38, 0xd5, 0x13, 0x7d, 0x6e, 0xba, 0xab, 0x4c, 0xae, 0x75, + 0x58, 0x8d, 0x41, 0x6d, 0x16, 0xb6, 0x70, 0x17, 0xb6, 0xd1, 0x41, 0x6b, 0x5f, 0x16, 0xfc, 0x53, 0x06, 0xe0, 0x4b, + 0xcf, 0x96, 0x6d, 0xaf, 0x49, 0xab, 0xd7, 0x32, 0x0a, 0xb1, 0xa5, 0xed, 0xd5, 0xa7, 0xa3, 0x7c, 0xdc, 0x9c, 0x50, + 0x5c, 0xc8, 0x51, 0x7e, 0xf0, 0x1a, 0xa2, 0xae, 0x75, 0x1d, 0x17, 0x8b, 0x0e, 0x37, 0xae, 0xba, 0xed, 0xc6, 0xf5, + 0x23, 0xe2, 0xad, 0xd1, 0x26, 0x85, 0x5a, 0x19, 0x3b, 0x82, 0x97, 0xe5, 0xc3, 0x21, 0x13, 0xc3, 0xa1, 0x84, 0x4c, + 0x7d, 0xe8, 0xde, 0xd0, 0xb4, 0xcf, 0x4f, 0x5b, 0x3f, 0x62, 0xa9, 0x71, 0x14, 0x1b, 0xde, 0xe9, 0x3b, 0x8f, 0xad, + 0x71, 0x25, 0x5f, 0x06, 0xb3, 0x5d, 0x41, 0xb5, 0x35, 0xde, 0xb0, 0x97, 0xf3, 0x9f, 0x2a, 0xa9, 0xe4, 0x6f, 0x7f, + 0x86, 0x6b, 0x78, 0x6b, 0x4b, 0x07, 0x4d, 0x35, 0xcb, 0x59, 0xae, 0xef, 0x05, 0xc7, 0x1f, 0x77, 0xaf, 0x08, 0x06, + 0xbf, 0xa7, 0xa3, 0x20, 0x17, 0x4b, 0xb5, 0x06, 0x14, 0xa4, 0x23, 0x3b, 0xa6, 0xb2, 0xc0, 0x30, 0x80, 0x37, 0x64, + 0x80, 0x3c, 0xa6, 0x70, 0x37, 0x54, 0x78, 0xe1, 0x6f, 0x15, 0xd9, 0x25, 0xb0, 0xad, 0x19, 0x1f, 0x33, 0xdc, 0x41, + 0xc8, 0x3f, 0x82, 0x2d, 0xd9, 0x8a, 0xdd, 0xb2, 0x1b, 0x86, 0x64, 0xe3, 0x38, 0x8c, 0x31, 0x1f, 0x4f, 0xe2, 0x2b, + 0x31, 0x89, 0x07, 0x3c, 0x42, 0xc7, 0x88, 0x35, 0xaf, 0x67, 0xb1, 0x1c, 0x40, 0xb6, 0xe4, 0x4a, 0x07, 0x84, 0xd0, + 0xd8, 0xd0, 0x92, 0xd7, 0x85, 0xc1, 0xc5, 0x8e, 0x7d, 0x46, 0x22, 0x19, 0x87, 0x60, 0xd1, 0xaa, 0x06, 0x16, 0x26, + 0x76, 0xcb, 0x8b, 0xd9, 0x6a, 0x8e, 0xff, 0x1c, 0x0e, 0x08, 0x80, 0x1d, 0xec, 0x1b, 0xb6, 0x8c, 0x10, 0xe9, 0xed, + 0x86, 0x2f, 0x2d, 0x4f, 0x17, 0x76, 0xc7, 0xdf, 0xf2, 0x31, 0x3b, 0xff, 0xd1, 0x83, 0xc8, 0xd9, 0xf3, 0x8f, 0x80, + 0x86, 0x78, 0xc7, 0x6f, 0x53, 0xaf, 0x62, 0xb7, 0x44, 0x41, 0x78, 0x0b, 0xce, 0x40, 0x77, 0x10, 0x01, 0xfb, 0x96, + 0xdf, 0x60, 0xac, 0xd8, 0x59, 0xba, 0xf0, 0x30, 0x23, 0xd4, 0x9e, 0xce, 0x97, 0xb5, 0x9a, 0x84, 0x9b, 0xab, 0xc5, + 0x64, 0x30, 0xd8, 0xf8, 0x3b, 0xbe, 0x06, 0x3e, 0x98, 0xf3, 0x1f, 0xbd, 0x1d, 0x95, 0x0b, 0xff, 0x79, 0x9d, 0x25, + 0xef, 0x7c, 0xf6, 0x76, 0xc0, 0x6f, 0x00, 0x6f, 0x09, 0x1d, 0xb8, 0xee, 0x7c, 0x26, 0xf1, 0xda, 0xde, 0xea, 0x6b, + 0x04, 0x12, 0xf9, 0x02, 0x30, 0x62, 0x62, 0x7e, 0xbf, 0x85, 0x08, 0x8c, 0x18, 0x7c, 0x5b, 0xb5, 0x47, 0xfc, 0x96, + 0x1b, 0xc0, 0xaf, 0xcc, 0x67, 0xf7, 0x3c, 0xd4, 0x3f, 0x13, 0x9f, 0x5d, 0xf3, 0xf7, 0xfc, 0x99, 0x27, 0x25, 0xe9, + 0x72, 0xf6, 0x7e, 0x0e, 0xd7, 0x43, 0x29, 0x4f, 0x87, 0xf4, 0xb3, 0x31, 0x18, 0x40, 0x28, 0x64, 0x5e, 0x7b, 0xc0, + 0x9a, 0x14, 0xe2, 0x5f, 0xc0, 0xb7, 0xa3, 0x84, 0xcd, 0x6b, 0x6f, 0xeb, 0x6b, 0x79, 0xf3, 0xda, 0xbb, 0xf7, 0x29, + 0x0a, 0xb0, 0x0a, 0x4a, 0x59, 0x60, 0x15, 0x84, 0x8d, 0x36, 0xc2, 0x18, 0xb8, 0x7a, 0xd7, 0x18, 0xea, 0x7a, 0x8e, + 0xd8, 0xb6, 0xd2, 0x77, 0xe1, 0x3b, 0xc8, 0x80, 0x0f, 0x5e, 0x17, 0x25, 0xd1, 0xe7, 0xd4, 0x14, 0x49, 0xeb, 0x9e, + 0xfb, 0xad, 0x75, 0x47, 0x6b, 0x4a, 0x7d, 0xe4, 0x6a, 0x7c, 0x38, 0xd4, 0xcf, 0x84, 0x16, 0x09, 0xa6, 0xa0, 0x71, + 0x0d, 0xda, 0x02, 0x04, 0x7d, 0x1e, 0x20, 0x6b, 0x49, 0xb1, 0xe0, 0xdb, 0x5f, 0x21, 0x06, 0xaf, 0x4c, 0xef, 0x5c, + 0xae, 0x32, 0x12, 0xb6, 0x17, 0x7e, 0x39, 0xac, 0xfd, 0x89, 0x53, 0x0b, 0x4b, 0xab, 0x39, 0xa8, 0x9f, 0xd8, 0x72, + 0x9c, 0xaa, 0xda, 0xdf, 0x25, 0x49, 0xb5, 0xab, 0xb4, 0x9c, 0xde, 0xd9, 0x37, 0x5d, 0x26, 0xd8, 0xd8, 0x0f, 0xa8, + 0x3a, 0xb2, 0x1a, 0x76, 0x5f, 0xa8, 0x2f, 0x7a, 0x4a, 0x26, 0x34, 0x1f, 0x55, 0x34, 0xcf, 0xee, 0x37, 0x3b, 0xea, + 0x3f, 0xbd, 0x1c, 0x8a, 0x00, 0xc9, 0x2a, 0x2d, 0x96, 0x22, 0x67, 0x63, 0x3f, 0x1e, 0x26, 0x99, 0x0a, 0x2f, 0x48, + 0x47, 0x77, 0xbf, 0x71, 0x7f, 0xcb, 0x0d, 0x64, 0x85, 0x56, 0x6d, 0x30, 0x56, 0x8a, 0x96, 0xc1, 0xfa, 0x6a, 0xdc, + 0xef, 0x8b, 0xab, 0xf1, 0x54, 0x04, 0x35, 0x10, 0x17, 0x89, 0x67, 0xe3, 0x69, 0x4d, 0x2c, 0xa9, 0x5d, 0x81, 0x31, + 0x7a, 0x5c, 0x15, 0xb5, 0x4f, 0xfd, 0x0c, 0x42, 0x91, 0x6a, 0xcd, 0x1c, 0x6b, 0xdc, 0x18, 0x11, 0x77, 0x58, 0xb9, + 0x76, 0x6a, 0xaf, 0x03, 0xb0, 0xbc, 0x1a, 0x17, 0x84, 0x45, 0x72, 0xec, 0x5c, 0xc0, 0x6a, 0x34, 0xa4, 0xda, 0x0d, + 0xb7, 0x5e, 0x76, 0x7e, 0xf3, 0x4d, 0x62, 0x6b, 0x23, 0xdc, 0x52, 0x40, 0x19, 0xe5, 0x37, 0x96, 0x13, 0x76, 0xa7, + 0x7a, 0x47, 0xaa, 0x76, 0xc4, 0x89, 0x0b, 0x58, 0x6e, 0x78, 0x6a, 0xf5, 0x4d, 0x0c, 0x4e, 0x84, 0xaa, 0x95, 0x0e, + 0x77, 0x32, 0x81, 0xb8, 0x5f, 0xdd, 0xd7, 0xbd, 0x12, 0xfc, 0x24, 0xe4, 0xf5, 0x5b, 0xde, 0x01, 0x60, 0xc5, 0x87, + 0xbc, 0x98, 0x16, 0x8e, 0xd6, 0x65, 0x50, 0x06, 0x88, 0xd0, 0x0c, 0x80, 0x4e, 0xae, 0x0e, 0xa2, 0x34, 0x70, 0xc5, + 0x1d, 0x22, 0xfc, 0x34, 0x7a, 0x92, 0x3f, 0x0b, 0x9f, 0x54, 0xd3, 0xf0, 0x22, 0x0f, 0xa2, 0x8b, 0x2a, 0x88, 0x9e, + 0x54, 0x57, 0xe1, 0x93, 0x7c, 0x1a, 0x5d, 0xe4, 0x41, 0x78, 0x51, 0x35, 0xf6, 0x5d, 0xbb, 0xbb, 0x27, 0xe4, 0x6d, + 0x57, 0x7f, 0xe4, 0x5c, 0xd9, 0x53, 0xa6, 0xe7, 0xe7, 0xb5, 0x5e, 0xa9, 0xdd, 0xe6, 0x7a, 0x8d, 0x9a, 0xa9, 0x8f, + 0xb2, 0xbf, 0xd9, 0xc6, 0xc2, 0xa3, 0x39, 0x84, 0x3e, 0x23, 0x2d, 0xe6, 0x1e, 0xe7, 0x7a, 0xb3, 0x27, 0x85, 0x81, + 0x11, 0x93, 0x4a, 0x46, 0x4e, 0x2f, 0x70, 0x11, 0xaa, 0x10, 0xc3, 0x5a, 0xba, 0xda, 0x67, 0x5d, 0x7a, 0x03, 0x75, + 0x4d, 0xb1, 0xaf, 0x21, 0x03, 0x2f, 0x9a, 0x5e, 0x06, 0x63, 0x40, 0x8e, 0xc0, 0x3b, 0x3e, 0x5b, 0xc0, 0x81, 0xb9, + 0x06, 0xe8, 0x9b, 0x07, 0x7d, 0x5d, 0x96, 0x7c, 0xad, 0xfa, 0x66, 0xba, 0x1e, 0x29, 0xe5, 0xc7, 0x8a, 0x2f, 0x2f, + 0x9e, 0xb2, 0x5b, 0xae, 0x51, 0x51, 0x5e, 0xe8, 0xc5, 0x7a, 0x07, 0x5c, 0x75, 0x2f, 0xe0, 0x36, 0x8b, 0xc7, 0xae, + 0x3c, 0x60, 0xd9, 0x96, 0xdd, 0xb3, 0x6b, 0xf6, 0x9e, 0x3d, 0x62, 0xaf, 0xd8, 0x57, 0x56, 0x23, 0x44, 0x79, 0xa9, + 0xa4, 0x3c, 0xff, 0x86, 0xdf, 0x4a, 0xdb, 0xa3, 0x84, 0x25, 0xbb, 0xb7, 0xed, 0x34, 0xc3, 0x0d, 0x7b, 0xcf, 0x6f, + 0x86, 0x2b, 0xf6, 0x0a, 0xb2, 0xa1, 0x50, 0x3c, 0x58, 0xb1, 0x1a, 0xae, 0xb0, 0x94, 0x41, 0x9f, 0x86, 0xa5, 0x25, + 0x2c, 0x9a, 0x42, 0x51, 0x8a, 0x7e, 0xc5, 0x6b, 0xc2, 0x4e, 0xab, 0xb1, 0x10, 0xf9, 0xa1, 0xe1, 0x8a, 0xdd, 0xf3, + 0x9b, 0xc1, 0x8a, 0xbd, 0xd7, 0x36, 0xa2, 0xc1, 0xc6, 0x2d, 0x8e, 0xc0, 0xac, 0x74, 0x61, 0x52, 0xa0, 0xde, 0xda, + 0x37, 0xc1, 0x0d, 0xbb, 0xc6, 0xfa, 0x3d, 0xc2, 0xa2, 0x51, 0xe6, 0x1f, 0xac, 0xd8, 0x57, 0x2e, 0x31, 0xd4, 0xdc, + 0xf2, 0xa4, 0x63, 0xa8, 0x2e, 0x90, 0xae, 0x08, 0x8f, 0x38, 0xbd, 0xc8, 0xbe, 0x62, 0x19, 0xf4, 0x95, 0xe1, 0x8a, + 0x6d, 0xb1, 0x76, 0xd7, 0xc6, 0xb8, 0x65, 0x55, 0x4f, 0x82, 0x02, 0xa3, 0xac, 0x52, 0x5a, 0x2e, 0x8e, 0x58, 0x36, + 0x75, 0xd4, 0xa0, 0x36, 0x0c, 0xe8, 0x83, 0xd1, 0x7f, 0xf8, 0xfa, 0xdd, 0x0f, 0x5e, 0xa9, 0x6f, 0xbe, 0x2f, 0x1c, + 0xef, 0xca, 0x12, 0xbd, 0x2b, 0x3f, 0xf3, 0x72, 0xf6, 0x62, 0x3e, 0xd1, 0xb5, 0xa4, 0x4d, 0x86, 0xdc, 0x4d, 0x67, + 0x2f, 0x3a, 0xfc, 0x2d, 0x3f, 0xfb, 0x7e, 0x63, 0xf5, 0xb1, 0xfa, 0xae, 0xee, 0xde, 0xfb, 0xc1, 0xa6, 0x71, 0x2a, + 0xbe, 0x3b, 0x5d, 0x71, 0x6c, 0x67, 0xad, 0xbd, 0x33, 0xff, 0x87, 0x6b, 0xbd, 0xc5, 0xb1, 0xbb, 0xe6, 0xdb, 0xe1, + 0xc6, 0x1e, 0x06, 0xf9, 0x7d, 0xe5, 0x97, 0x5f, 0xf3, 0xe7, 0x5e, 0xa7, 0x24, 0x0b, 0xa8, 0x46, 0x9f, 0x8c, 0x34, + 0x74, 0xc9, 0x4c, 0x4c, 0x43, 0x7c, 0x91, 0x01, 0x3a, 0x17, 0x88, 0x67, 0x77, 0x7c, 0x3c, 0xb9, 0xbb, 0x8a, 0x27, + 0x77, 0x03, 0xfe, 0xc9, 0xb4, 0xa0, 0xbd, 0xe0, 0xee, 0x7c, 0xf6, 0x99, 0x17, 0xf6, 0x92, 0x7c, 0xe1, 0xb3, 0x77, + 0xc2, 0x5d, 0xa5, 0x2f, 0x7c, 0xf6, 0x55, 0xf0, 0xcf, 0x23, 0x4d, 0x96, 0xc1, 0xbe, 0xd6, 0xfc, 0xf3, 0x08, 0x59, + 0x3f, 0xd8, 0x17, 0xc1, 0xdf, 0x81, 0xff, 0x77, 0x95, 0xa0, 0x65, 0xfc, 0x4b, 0xad, 0x7e, 0xbe, 0x97, 0xb1, 0x39, + 0xf0, 0x26, 0xb4, 0x82, 0xde, 0xbc, 0xad, 0xe5, 0x4f, 0xe2, 0xe2, 0x48, 0xd5, 0x53, 0xc3, 0x41, 0x8b, 0xc5, 0xdc, + 0xd4, 0x47, 0xe9, 0x54, 0xde, 0xe4, 0x2d, 0x4f, 0xa4, 0x85, 0xf9, 0x0e, 0xc2, 0x81, 0xdf, 0xda, 0x30, 0x05, 0x3b, + 0x8e, 0x9b, 0xc1, 0x5b, 0x06, 0x10, 0x92, 0xd9, 0x74, 0xcb, 0xaf, 0xf9, 0x23, 0xfe, 0x95, 0xef, 0x82, 0x7b, 0xfe, + 0x9e, 0xbf, 0xe2, 0x75, 0xcd, 0x77, 0x6c, 0x21, 0x21, 0x4f, 0xeb, 0xed, 0x65, 0xb0, 0x65, 0xf5, 0xee, 0x32, 0xb8, + 0x67, 0xf5, 0xf6, 0x69, 0x70, 0xcd, 0xea, 0xdd, 0xd3, 0xe0, 0x3d, 0xdb, 0x5e, 0x06, 0x8f, 0xd8, 0xee, 0x32, 0x78, + 0xc5, 0xb6, 0x4f, 0x83, 0xaf, 0x6c, 0xf7, 0x34, 0xa8, 0x15, 0xd2, 0xc3, 0x57, 0x21, 0x99, 0x4e, 0xbe, 0xd6, 0xcc, + 0xb0, 0xea, 0x06, 0x5f, 0x84, 0xf5, 0x8b, 0x6a, 0x19, 0x7c, 0xa9, 0x99, 0x6e, 0x73, 0x20, 0x04, 0xd3, 0x2d, 0x0e, + 0x6e, 0xe9, 0x89, 0x69, 0x57, 0x90, 0x0a, 0xd6, 0xd5, 0xd2, 0xe0, 0xa6, 0x6e, 0x5a, 0x27, 0xb3, 0xe3, 0x9d, 0x18, + 0x77, 0x78, 0x27, 0xde, 0xb0, 0x45, 0xd3, 0xe9, 0xaa, 0x73, 0xfa, 0x3c, 0xd0, 0x47, 0x80, 0xde, 0xfb, 0x2b, 0xe9, + 0x41, 0x53, 0x34, 0x3c, 0x57, 0xba, 0xe3, 0xd6, 0x7e, 0x1f, 0x5a, 0xfb, 0x3d, 0x93, 0x8a, 0xb4, 0x88, 0x45, 0x65, + 0x51, 0x55, 0xc8, 0x27, 0x1e, 0x64, 0x5a, 0xab, 0x96, 0x30, 0x52, 0x67, 0x02, 0x26, 0x7d, 0x41, 0x87, 0x41, 0x4e, + 0x76, 0x05, 0xb6, 0xe0, 0x9b, 0x41, 0xc2, 0xd6, 0x3c, 0x9e, 0x0e, 0x93, 0x60, 0xc1, 0x96, 0x7c, 0xd8, 0x2d, 0x16, + 0xac, 0x54, 0x18, 0x93, 0xbe, 0x3e, 0x1d, 0xed, 0xee, 0xbc, 0xb7, 0x4a, 0xe3, 0x38, 0x13, 0xa8, 0x73, 0xab, 0xf4, + 0x36, 0xbf, 0x75, 0x76, 0xf5, 0xb5, 0xda, 0xe5, 0x41, 0x60, 0xf8, 0x0c, 0x44, 0x3b, 0xc4, 0x7b, 0x07, 0x35, 0x46, + 0xba, 0x25, 0xb3, 0xee, 0x2b, 0x7b, 0x5f, 0xdf, 0x9a, 0xad, 0xfa, 0xdf, 0x2d, 0x82, 0xf6, 0x72, 0xd9, 0xfb, 0x9f, + 0xcc, 0xab, 0xbf, 0x77, 0xbc, 0xba, 0xf1, 0x27, 0xf7, 0xfc, 0x13, 0x46, 0x27, 0x60, 0x22, 0xdb, 0xf1, 0x4f, 0xa3, + 0x6d, 0xe3, 0x94, 0x27, 0xf7, 0xf2, 0xff, 0x2b, 0x05, 0xda, 0xbb, 0x79, 0x65, 0x6f, 0x8a, 0x5b, 0xde, 0xb1, 0x97, + 0x2f, 0xac, 0x3d, 0xd1, 0x20, 0x94, 0x7c, 0xe2, 0x6e, 0x50, 0x34, 0xec, 0x89, 0x2f, 0x78, 0x35, 0xfb, 0x34, 0x9f, + 0x6c, 0xf9, 0xf1, 0x8e, 0xf8, 0xa9, 0x63, 0x47, 0x7c, 0xe1, 0x0f, 0x16, 0xcd, 0xb7, 0x7a, 0xb5, 0x73, 0x27, 0x77, + 0x2a, 0xbd, 0xe3, 0xc7, 0xfb, 0xf8, 0xf0, 0xdf, 0xae, 0xf4, 0xee, 0xbb, 0x2b, 0x6d, 0x57, 0xb9, 0xbb, 0xf3, 0x4d, + 0xc7, 0x37, 0xb2, 0xd6, 0x18, 0x6e, 0x66, 0x14, 0x8c, 0x30, 0x6d, 0x61, 0x9a, 0x06, 0x91, 0xa5, 0x58, 0x84, 0x44, + 0x8d, 0xd2, 0x39, 0xd1, 0x67, 0x41, 0xa7, 0xa0, 0x8b, 0x1b, 0xfd, 0x2d, 0x1f, 0xb3, 0x1b, 0xe3, 0xb2, 0x79, 0x7b, + 0x75, 0x33, 0x19, 0x0c, 0x6e, 0xfd, 0xfd, 0x1d, 0x0f, 0x67, 0xb7, 0x73, 0xf6, 0x96, 0xdf, 0xd1, 0x7a, 0x9a, 0xa8, + 0xc6, 0x17, 0x0f, 0x49, 0x60, 0xb7, 0xbe, 0x3f, 0xb1, 0x88, 0x60, 0xed, 0x1b, 0xe7, 0xad, 0x3f, 0x90, 0x66, 0x69, + 0xb9, 0xb5, 0xbf, 0x7f, 0x58, 0x43, 0x71, 0x0b, 0x42, 0xc6, 0x7b, 0x5b, 0xe5, 0xf0, 0x8a, 0x7f, 0xf4, 0xde, 0xfa, + 0xd3, 0xb7, 0x3a, 0xf8, 0x66, 0xa2, 0xce, 0xa5, 0x57, 0x17, 0x4f, 0xd9, 0x67, 0xfe, 0x49, 0x9e, 0x29, 0xef, 0x84, + 0x9c, 0xb6, 0xd7, 0x48, 0xe2, 0x44, 0x47, 0xc5, 0x57, 0x37, 0x91, 0x40, 0x21, 0x60, 0x57, 0xf8, 0x5a, 0xf3, 0xfb, + 0x49, 0x39, 0xf5, 0x76, 0x40, 0xf2, 0xca, 0x6d, 0x45, 0xf4, 0x2d, 0xe7, 0xfc, 0x66, 0x78, 0x39, 0xfd, 0xda, 0xed, + 0xdb, 0xa3, 0xc2, 0xda, 0x54, 0xc4, 0xdb, 0x2d, 0x06, 0x61, 0x9d, 0xcc, 0x2c, 0x73, 0xc9, 0x97, 0xbe, 0xd6, 0x66, + 0xee, 0x31, 0xbd, 0xe3, 0x4c, 0x33, 0x64, 0xf4, 0x05, 0x66, 0xa6, 0xc3, 0x61, 0x79, 0x8e, 0xe5, 0xf1, 0xe1, 0xab, + 0x27, 0x8f, 0x06, 0x8f, 0x30, 0x84, 0xcb, 0x0a, 0x0b, 0xf9, 0xca, 0x87, 0x59, 0xdd, 0xba, 0x76, 0x5c, 0x3c, 0x1d, + 0xbe, 0x80, 0xbc, 0x41, 0xd7, 0x43, 0x53, 0x44, 0xab, 0xfc, 0x8e, 0xa2, 0x4f, 0x94, 0x1c, 0x74, 0x3c, 0x81, 0xda, + 0x21, 0x17, 0xee, 0xd7, 0x27, 0x1c, 0x14, 0x1d, 0x58, 0x6a, 0xbf, 0x7f, 0xfe, 0x89, 0x08, 0xa5, 0x61, 0xbc, 0x5f, + 0x84, 0xd1, 0x9f, 0x71, 0x59, 0xac, 0xe1, 0x88, 0x1d, 0xc0, 0xe7, 0x9e, 0xe8, 0x6b, 0xd8, 0xd2, 0xf7, 0xfd, 0xc0, + 0xdb, 0xf2, 0x6b, 0xf6, 0x95, 0x7b, 0x97, 0xc3, 0x57, 0xfe, 0x93, 0x47, 0x20, 0x3f, 0x21, 0x4e, 0x0a, 0x86, 0xc4, + 0x76, 0x14, 0xa3, 0xd6, 0xe1, 0x97, 0x1a, 0x62, 0xb5, 0x3e, 0x21, 0x75, 0x17, 0xa4, 0x7f, 0x50, 0xc8, 0x7e, 0x42, + 0x60, 0x35, 0x49, 0x9f, 0x02, 0x93, 0xf8, 0xb6, 0x86, 0x04, 0xd2, 0xb4, 0x40, 0x0c, 0x0e, 0x14, 0x9f, 0x0a, 0xfe, + 0x75, 0xf8, 0x85, 0xe4, 0xbf, 0x9b, 0x9a, 0x8f, 0xe1, 0x6f, 0x18, 0x9a, 0x49, 0x75, 0x9f, 0xd6, 0x51, 0xe2, 0xd5, + 0x70, 0xea, 0x85, 0x95, 0x50, 0x27, 0x43, 0x90, 0x8a, 0x21, 0x17, 0xe2, 0xe2, 0xe9, 0xe4, 0xb6, 0x14, 0xe1, 0x9f, + 0x13, 0x7c, 0x26, 0x57, 0x9a, 0x7c, 0x46, 0x4f, 0x1a, 0x59, 0xc0, 0xbd, 0x7c, 0x5f, 0xf6, 0x6a, 0x70, 0x53, 0x0f, + 0xf9, 0x6d, 0xed, 0xbe, 0x2f, 0xe7, 0x04, 0x3d, 0xb2, 0x1f, 0xd0, 0x1c, 0x0c, 0xd4, 0x0c, 0xa4, 0x0c, 0xc1, 0x2d, + 0x5c, 0xfa, 0x3d, 0x55, 0x90, 0x2f, 0xbf, 0xf7, 0x45, 0xc8, 0xc0, 0x95, 0x1b, 0xc2, 0x94, 0x4b, 0x85, 0x14, 0x38, + 0x6e, 0xeb, 0xc1, 0x17, 0x8d, 0x4e, 0x22, 0xc1, 0xa7, 0x04, 0x24, 0x49, 0xcb, 0x03, 0x49, 0x23, 0xa6, 0x03, 0x71, + 0xa1, 0x34, 0xcd, 0x4a, 0x8a, 0x38, 0xc4, 0xae, 0xfa, 0x16, 0x09, 0xcf, 0x82, 0xf7, 0x0c, 0xd6, 0x8e, 0x14, 0x2d, + 0xbe, 0x1a, 0xd3, 0xb1, 0x0e, 0x1b, 0x5a, 0xca, 0xe2, 0x3e, 0x4b, 0xea, 0x34, 0x12, 0x57, 0xde, 0x09, 0xf9, 0xf3, + 0x9f, 0x4a, 0x04, 0xd2, 0xbb, 0x1a, 0x88, 0x41, 0xf0, 0x03, 0xf4, 0x1f, 0xb0, 0xc8, 0x41, 0x50, 0xaa, 0xcb, 0x30, + 0xaf, 0x32, 0x2a, 0x70, 0xb6, 0x63, 0xdb, 0x39, 0x53, 0x75, 0x0b, 0xbe, 0x08, 0xc3, 0x90, 0x76, 0xb6, 0x6a, 0x4e, + 0x6e, 0xf5, 0x06, 0xea, 0x99, 0xc4, 0x91, 0x5a, 0x8a, 0x23, 0x6d, 0xcd, 0x7d, 0xba, 0xf0, 0xba, 0xe5, 0x05, 0x0d, + 0x17, 0xa0, 0x17, 0xa5, 0xbb, 0xce, 0x27, 0x14, 0xba, 0xac, 0xc6, 0xd5, 0x50, 0xd4, 0xa1, 0x1c, 0x63, 0xed, 0xcf, + 0x95, 0x3c, 0xbf, 0x03, 0xeb, 0x11, 0x1a, 0xbe, 0x2a, 0x75, 0x10, 0xdb, 0x4f, 0xf4, 0xae, 0x53, 0xa9, 0xbf, 0x01, + 0x60, 0xe0, 0xd4, 0xf1, 0x50, 0x1f, 0xb5, 0x53, 0xc8, 0x76, 0xee, 0x2d, 0x31, 0x2a, 0x57, 0xc2, 0x53, 0xa5, 0xe5, + 0x29, 0x65, 0xd5, 0xd7, 0x82, 0x5b, 0xd9, 0x7d, 0x36, 0x80, 0x8c, 0x36, 0x28, 0x90, 0x67, 0xd4, 0xd6, 0x78, 0x90, + 0x6a, 0x9a, 0x25, 0x8e, 0xe1, 0x83, 0x22, 0xcd, 0x2a, 0xb0, 0x78, 0x99, 0x4b, 0xe6, 0xa0, 0x60, 0xb9, 0xde, 0x6c, + 0xa6, 0x99, 0xea, 0x8b, 0xdc, 0xde, 0x68, 0xbc, 0x4c, 0xff, 0xcd, 0x92, 0x01, 0x8f, 0x2e, 0x9e, 0xfa, 0x01, 0xa4, + 0x49, 0x8a, 0x07, 0x48, 0x82, 0xed, 0xc1, 0x2e, 0x76, 0x18, 0xb6, 0x8a, 0x95, 0x3d, 0x79, 0xba, 0xdc, 0xa1, 0x29, + 0x97, 0xe0, 0x92, 0x13, 0x73, 0x39, 0xf5, 0x7d, 0xc9, 0x7a, 0x43, 0x71, 0xca, 0xa6, 0x09, 0x28, 0x09, 0xb4, 0x5b, + 0xf0, 0x5f, 0xf8, 0xd4, 0xd0, 0x69, 0x01, 0x96, 0xda, 0x6e, 0xc0, 0x7f, 0xa1, 0x5f, 0x6c, 0x77, 0x51, 0x3f, 0x30, + 0x0f, 0xf6, 0x66, 0x71, 0x65, 0x0c, 0x38, 0x49, 0x5c, 0x69, 0x1e, 0xb9, 0x7e, 0x50, 0xf4, 0xe9, 0xb2, 0x76, 0xe0, + 0x4c, 0x71, 0x61, 0x95, 0xda, 0x24, 0xbd, 0xf6, 0x5b, 0x6a, 0xe2, 0x4d, 0x94, 0x54, 0x85, 0xed, 0x90, 0xf6, 0x2f, + 0x29, 0x67, 0xaa, 0xb8, 0x43, 0xf4, 0x64, 0x37, 0x71, 0x15, 0x78, 0x61, 0x55, 0xb1, 0x11, 0x6a, 0x33, 0xb2, 0x9c, + 0xc0, 0xe9, 0x1e, 0xab, 0x0b, 0x3e, 0xb6, 0xab, 0xd9, 0x05, 0x2b, 0xd9, 0x9a, 0x49, 0xf7, 0x79, 0x3b, 0xe6, 0x42, + 0x5e, 0xe9, 0x65, 0xd1, 0x0a, 0x68, 0x0f, 0x02, 0x87, 0x5f, 0x68, 0xba, 0x47, 0xcf, 0x36, 0xdb, 0xd4, 0x66, 0x63, + 0x6b, 0x11, 0x42, 0x06, 0xa2, 0xa1, 0x2f, 0xe4, 0x8c, 0x22, 0x5f, 0xa5, 0xe5, 0x5a, 0x6d, 0xac, 0x32, 0x5e, 0x60, + 0x22, 0xc8, 0x70, 0x16, 0xde, 0xa1, 0xa7, 0xf5, 0x48, 0x53, 0x4c, 0x82, 0x93, 0x2e, 0xfe, 0x02, 0x6c, 0x28, 0x4f, + 0x72, 0x73, 0x40, 0x0e, 0xa0, 0x72, 0x29, 0x4a, 0xa5, 0x0c, 0xfe, 0x45, 0xdd, 0x91, 0x6d, 0xd5, 0x7f, 0xa7, 0x81, + 0x0c, 0xee, 0x40, 0xdf, 0xf6, 0x42, 0x6b, 0x47, 0x3b, 0x57, 0xb6, 0xa6, 0x6d, 0x91, 0xe6, 0x31, 0xb2, 0xd8, 0x00, + 0xf2, 0x89, 0x74, 0x0e, 0x44, 0x5e, 0x13, 0x8d, 0x77, 0xf6, 0x8c, 0x8f, 0xa7, 0xe2, 0x21, 0x79, 0xaf, 0xf2, 0x7d, + 0x73, 0xaf, 0x0f, 0xc6, 0xd8, 0xb7, 0xa0, 0x4c, 0x7c, 0xb0, 0xda, 0x5a, 0x97, 0x58, 0x6f, 0x95, 0x26, 0xd1, 0x0d, + 0x57, 0xd0, 0x71, 0x24, 0x6e, 0x10, 0x83, 0x63, 0xc6, 0x6b, 0xab, 0x2c, 0x7d, 0x85, 0x65, 0xae, 0x63, 0x96, 0x0c, + 0x99, 0xd4, 0x79, 0xa2, 0xe0, 0xc9, 0xcf, 0x13, 0x92, 0x11, 0x51, 0xb3, 0x2d, 0x47, 0x29, 0x37, 0x2d, 0xe0, 0x32, + 0x23, 0x03, 0xf8, 0x26, 0x4d, 0x00, 0xca, 0xe5, 0x4b, 0x90, 0x4a, 0x43, 0x04, 0xd7, 0x6c, 0x2f, 0x19, 0xdd, 0x3a, + 0x5a, 0x07, 0x55, 0x92, 0xb9, 0x83, 0x73, 0x3b, 0x8b, 0x94, 0x7a, 0xf3, 0x11, 0x86, 0x9d, 0x7c, 0x08, 0xeb, 0x04, + 0xbf, 0x0d, 0xa8, 0x49, 0x9f, 0x0a, 0x2f, 0x1a, 0x01, 0x9a, 0xfa, 0x4e, 0x95, 0xf1, 0xa9, 0xf0, 0xb2, 0xd1, 0x96, + 0x65, 0x94, 0x42, 0x75, 0xc1, 0xec, 0xd6, 0x74, 0x21, 0xe6, 0x55, 0x35, 0xd0, 0x06, 0xb9, 0x5d, 0xc7, 0x0c, 0x68, + 0xd4, 0x76, 0xe5, 0x91, 0x05, 0xb8, 0x35, 0x13, 0x81, 0x91, 0xf3, 0xef, 0xf3, 0x97, 0x2a, 0x9c, 0xa7, 0xdf, 0x0f, + 0xbd, 0xfd, 0x36, 0x88, 0x46, 0xdb, 0x4b, 0xb6, 0x0b, 0xa2, 0xd1, 0xee, 0xb2, 0x61, 0xf4, 0xfb, 0x29, 0xfd, 0x7e, + 0xda, 0x80, 0xaa, 0x44, 0x98, 0x88, 0x7b, 0xfd, 0x46, 0x2d, 0x5f, 0xa9, 0xf5, 0x3b, 0xb5, 0x7c, 0xa9, 0x86, 0xb7, + 0xf6, 0x24, 0x12, 0x44, 0x96, 0xc6, 0xe6, 0x5e, 0xb2, 0xa5, 0x5a, 0x2a, 0x1d, 0xa3, 0xca, 0x88, 0x5a, 0x3a, 0x9b, + 0x63, 0xc5, 0x48, 0x3b, 0x07, 0x25, 0x03, 0x32, 0x2d, 0xae, 0x6a, 0x4c, 0x37, 0x2b, 0x5a, 0x62, 0x32, 0xc2, 0xca, + 0xb6, 0xbc, 0xdd, 0xa4, 0x6a, 0x3a, 0x27, 0x37, 0xb7, 0x4a, 0xb9, 0xb9, 0x15, 0x3c, 0xff, 0x86, 0x6e, 0xb9, 0xe4, + 0xda, 0xcb, 0x6c, 0x5a, 0x28, 0xdd, 0x32, 0xae, 0xc1, 0xd6, 0xbe, 0x09, 0x64, 0x99, 0x0f, 0x14, 0x35, 0xb6, 0x17, + 0x8d, 0xf2, 0x0d, 0xb2, 0x15, 0x31, 0xea, 0x94, 0x05, 0xe3, 0x6f, 0x77, 0xf4, 0x40, 0x06, 0xaa, 0xaa, 0xda, 0x38, + 0xb8, 0xb3, 0xd2, 0x1f, 0x96, 0x17, 0x4f, 0x59, 0x62, 0xa5, 0x93, 0x0b, 0x55, 0xe8, 0x0f, 0x42, 0x74, 0x53, 0xd9, + 0x70, 0x70, 0xa8, 0x8b, 0xad, 0x0c, 0x08, 0x3d, 0x4c, 0xef, 0x6d, 0xac, 0x64, 0xb9, 0x6b, 0xca, 0x17, 0x33, 0x9e, + 0x70, 0x1c, 0x7d, 0xb9, 0x5a, 0x84, 0xb5, 0x5a, 0x64, 0x27, 0xc0, 0x43, 0x6b, 0xb5, 0x14, 0x72, 0xb5, 0x08, 0x67, + 0xa6, 0x0b, 0x35, 0xd3, 0x33, 0x50, 0x40, 0x0a, 0x35, 0xcb, 0x13, 0x80, 0x85, 0x17, 0x66, 0x86, 0x0b, 0x33, 0xc3, + 0x71, 0x48, 0x8d, 0xff, 0x83, 0xde, 0xeb, 0xdc, 0x73, 0xcb, 0xdd, 0xe8, 0x34, 0xe2, 0xdb, 0xd1, 0x06, 0x73, 0x7c, + 0x10, 0x4e, 0xaa, 0x7e, 0x3f, 0x2d, 0x11, 0xab, 0xc7, 0xc0, 0x08, 0xca, 0xa1, 0x72, 0xb4, 0x5f, 0x16, 0x96, 0x64, + 0x49, 0x58, 0x92, 0x7b, 0x35, 0xce, 0xa5, 0xe5, 0xe2, 0x55, 0x12, 0x88, 0x44, 0xc6, 0x4b, 0x69, 0x82, 0x4f, 0x78, + 0x39, 0x32, 0x52, 0xf3, 0xe4, 0x26, 0xf5, 0x72, 0x96, 0xb1, 0x31, 0x62, 0x18, 0x85, 0x7e, 0x53, 0xf5, 0xfb, 0x79, + 0xe9, 0xe5, 0xd4, 0xce, 0x4f, 0xe0, 0x7a, 0x79, 0xea, 0x2c, 0x72, 0x84, 0xbc, 0x1a, 0x49, 0x85, 0xe5, 0xb5, 0x52, + 0x4f, 0x5f, 0x82, 0x0f, 0xea, 0xee, 0x8d, 0x02, 0x20, 0x2e, 0x72, 0xe9, 0x5f, 0x5b, 0xc2, 0xa5, 0x29, 0x37, 0x30, + 0xe8, 0x21, 0xcf, 0x49, 0x08, 0x95, 0x20, 0x24, 0x85, 0x75, 0xe3, 0xbe, 0x78, 0x3a, 0x71, 0xdd, 0x59, 0x6c, 0x60, + 0x82, 0xc3, 0x01, 0x10, 0x0f, 0xa6, 0x5e, 0x34, 0xe0, 0xa5, 0x9a, 0x33, 0x1f, 0xbd, 0x9c, 0x60, 0x32, 0x40, 0x55, + 0x31, 0x70, 0xca, 0x7a, 0x22, 0x1f, 0x19, 0x37, 0x33, 0xdf, 0x0f, 0xf0, 0xdd, 0xba, 0x90, 0xe8, 0x0f, 0x0a, 0xa0, + 0x20, 0x53, 0x00, 0x05, 0x89, 0x01, 0x28, 0x88, 0x0d, 0x40, 0xc1, 0xa6, 0xe1, 0x4b, 0xa9, 0xc3, 0x8d, 0x80, 0x2e, + 0xc2, 0x87, 0x9e, 0x85, 0x8d, 0x15, 0x8a, 0x67, 0x63, 0x36, 0x66, 0x85, 0xda, 0x79, 0x72, 0x39, 0x15, 0x3b, 0x8b, + 0xb1, 0xae, 0x22, 0xeb, 0xc4, 0x0b, 0x09, 0x45, 0xce, 0xb9, 0x91, 0xa8, 0xbb, 0x9f, 0x7b, 0x2f, 0xc9, 0x58, 0x32, + 0x6f, 0x68, 0xd4, 0x60, 0x5e, 0x76, 0x1d, 0xc0, 0xb4, 0xe4, 0xdb, 0x82, 0x06, 0xd3, 0xa9, 0xf2, 0x88, 0x34, 0x09, + 0x6a, 0xe7, 0x32, 0x29, 0x72, 0x42, 0x98, 0x04, 0xbd, 0x12, 0xfc, 0x46, 0xa2, 0xfc, 0x7f, 0xd3, 0x09, 0x1e, 0xe0, + 0x98, 0x68, 0x95, 0x7c, 0x05, 0x03, 0x66, 0xce, 0x9f, 0x4b, 0xa7, 0x6c, 0x84, 0x62, 0x2c, 0xd3, 0x78, 0xf4, 0x95, + 0x0d, 0x11, 0xda, 0xea, 0x39, 0x9a, 0x98, 0xa0, 0x0e, 0xf0, 0x88, 0xfe, 0x1a, 0x7d, 0x35, 0x14, 0x2a, 0x5d, 0x8d, + 0xd4, 0x35, 0x3b, 0xe7, 0xfc, 0x5d, 0x6d, 0x38, 0x91, 0x31, 0x6d, 0x0a, 0x7c, 0x03, 0x02, 0xf9, 0x06, 0x02, 0xc0, + 0x55, 0xd3, 0x99, 0xbd, 0x02, 0x38, 0x07, 0x02, 0x78, 0x9c, 0x77, 0x3c, 0x7e, 0xa0, 0xbf, 0x8a, 0xe3, 0xde, 0x69, + 0x1a, 0xb6, 0xff, 0x0a, 0x8c, 0xc5, 0x50, 0x8e, 0xe7, 0x3b, 0x05, 0xc9, 0x1e, 0xa5, 0x2c, 0x5d, 0x35, 0x91, 0x1d, + 0x8a, 0xf5, 0x69, 0x4e, 0x19, 0x4b, 0xdb, 0x72, 0x8c, 0x36, 0x5e, 0x3f, 0xc4, 0xe3, 0x9b, 0x1b, 0x3d, 0xf9, 0xa0, + 0x07, 0xb7, 0xb7, 0x37, 0xaf, 0x7a, 0xcc, 0xe6, 0x5b, 0xb1, 0x78, 0x56, 0xc4, 0x89, 0xd3, 0x3a, 0xe4, 0x00, 0x07, + 0x39, 0x09, 0x81, 0x74, 0x8c, 0x4b, 0x2d, 0x3a, 0xa8, 0x59, 0xce, 0x6b, 0x60, 0x99, 0x45, 0x90, 0x0d, 0x10, 0xd5, + 0x34, 0x15, 0xab, 0xe1, 0x41, 0xa9, 0x9a, 0x53, 0x2a, 0xb5, 0x6f, 0x38, 0x5b, 0x9d, 0x3e, 0xb1, 0x6a, 0x13, 0x6e, + 0xfd, 0xb9, 0xf6, 0x04, 0x6d, 0x25, 0x0d, 0x84, 0x7a, 0xbe, 0x4a, 0x97, 0x14, 0xc5, 0xe3, 0xcc, 0xc4, 0x53, 0x15, + 0x18, 0xfb, 0xd6, 0x8e, 0xa0, 0x20, 0x69, 0xba, 0x0e, 0x38, 0x4c, 0xa3, 0x13, 0x16, 0xff, 0x94, 0x3e, 0x94, 0x17, + 0xb5, 0x02, 0x27, 0xf9, 0x87, 0x70, 0x11, 0x49, 0x2c, 0xf4, 0x4b, 0x02, 0x20, 0x91, 0xc1, 0xab, 0x51, 0xb1, 0x16, + 0x2a, 0x40, 0x4e, 0x51, 0x7a, 0xab, 0xf8, 0xb8, 0x14, 0xa5, 0x4a, 0xa9, 0xcc, 0x8d, 0x4a, 0x01, 0x61, 0x6d, 0xe0, + 0xe8, 0x02, 0xbe, 0x80, 0xa0, 0xb5, 0xdc, 0xad, 0x6d, 0xcf, 0x1b, 0x99, 0xcf, 0x4c, 0xf3, 0xb4, 0xfa, 0xa0, 0xfe, + 0x7e, 0xbf, 0xc0, 0x30, 0x1b, 0x4f, 0x7f, 0xdf, 0x66, 0x08, 0x37, 0x7f, 0xc3, 0x10, 0x2d, 0x01, 0x1c, 0xb3, 0xb4, + 0x87, 0x42, 0x16, 0x4c, 0xb0, 0x86, 0xaa, 0x3c, 0xe5, 0xb3, 0x97, 0x4f, 0x6e, 0x00, 0x4d, 0x0d, 0x5d, 0xdc, 0xe8, + 0x54, 0x57, 0x25, 0x08, 0xdf, 0x77, 0x85, 0x7a, 0x6c, 0x0e, 0x38, 0x35, 0x00, 0x14, 0x8b, 0xbc, 0xd6, 0x63, 0xfb, + 0x07, 0xbd, 0x51, 0x6f, 0x80, 0x78, 0x3a, 0xe7, 0x85, 0x7f, 0x44, 0xbf, 0x4e, 0xfd, 0x19, 0x17, 0x82, 0xa8, 0xd7, + 0x93, 0xf0, 0x4e, 0x9c, 0xa5, 0x71, 0x70, 0xd6, 0x1b, 0x98, 0x8b, 0x40, 0x71, 0x96, 0xe6, 0x67, 0x20, 0x96, 0x23, + 0x3c, 0x62, 0xcd, 0x56, 0x80, 0x18, 0x58, 0xea, 0x90, 0x64, 0xd5, 0xb1, 0xfd, 0xfe, 0xeb, 0x91, 0xe1, 0x4d, 0x47, + 0x44, 0x18, 0xfd, 0xbb, 0x02, 0x01, 0x0a, 0x96, 0x99, 0xed, 0xcc, 0xa4, 0xab, 0x3d, 0xab, 0xe7, 0xcd, 0x26, 0xef, + 0xea, 0x1d, 0xab, 0x69, 0x39, 0x35, 0xad, 0xb2, 0x9a, 0x36, 0xc9, 0xa1, 0x66, 0xa2, 0xdf, 0xd7, 0xf8, 0xa8, 0xf9, + 0x1c, 0x70, 0xd9, 0x30, 0xf9, 0xf5, 0xac, 0x9a, 0xf7, 0xfb, 0x9e, 0x7c, 0x04, 0xbf, 0x90, 0xb8, 0xcc, 0xad, 0xb1, + 0x7c, 0xfa, 0x86, 0xf8, 0xcc, 0x0c, 0xe2, 0xd1, 0xea, 0x08, 0xea, 0xeb, 0x5a, 0x78, 0x1d, 0x73, 0x85, 0xcd, 0xc4, + 0xf4, 0x35, 0x0c, 0x9e, 0x27, 0x7c, 0xf0, 0x96, 0xa3, 0xbf, 0x91, 0xce, 0x4c, 0xc1, 0x42, 0xce, 0xfd, 0xc9, 0x6b, + 0x84, 0x4e, 0x46, 0xa4, 0x07, 0x9d, 0x4e, 0xd0, 0x90, 0xfd, 0xfe, 0x2d, 0x74, 0x66, 0x2b, 0x95, 0xb2, 0x55, 0x51, + 0x99, 0xae, 0xeb, 0xa2, 0xac, 0xa0, 0x63, 0xe9, 0xe7, 0xad, 0x90, 0x99, 0xf5, 0x33, 0x0b, 0xf9, 0xe9, 0x56, 0x62, + 0x4d, 0xd9, 0xf6, 0x89, 0xda, 0x20, 0xcd, 0xba, 0x50, 0x5d, 0xe0, 0xdc, 0x59, 0x7b, 0xbd, 0x11, 0xea, 0x9f, 0xf3, + 0xd1, 0xba, 0x58, 0x7b, 0xe0, 0x12, 0x33, 0x4b, 0xe7, 0x8a, 0x43, 0x23, 0xf7, 0x47, 0x5f, 0x8a, 0x34, 0xa7, 0x3c, + 0x40, 0x83, 0x28, 0xe6, 0xf6, 0x5b, 0x20, 0xfd, 0xd0, 0x5b, 0x20, 0xfb, 0xe8, 0x9c, 0x93, 0xd7, 0x00, 0x4e, 0x87, + 0x88, 0xb8, 0x15, 0x09, 0x3a, 0x56, 0x0d, 0x6f, 0x2c, 0xdc, 0xd3, 0x5e, 0x1a, 0xf7, 0xd2, 0xfc, 0x2c, 0xed, 0xf7, + 0x0d, 0x80, 0x66, 0x8a, 0xc8, 0xf0, 0x38, 0x23, 0x77, 0x49, 0x0b, 0xc1, 0x94, 0xf6, 0x5f, 0x8d, 0x21, 0x41, 0x20, + 0xe0, 0xff, 0x10, 0xde, 0x23, 0x40, 0xdb, 0xa4, 0x0d, 0xb8, 0xea, 0x31, 0x1d, 0x98, 0x2d, 0x39, 0x5b, 0x75, 0x36, + 0x00, 0xe5, 0x54, 0x69, 0x3d, 0xe5, 0x71, 0x4d, 0x11, 0x91, 0x2a, 0x0b, 0xf5, 0x1b, 0xeb, 0xc9, 0x64, 0x95, 0x8b, + 0x0c, 0x39, 0x2a, 0xd3, 0xbb, 0x9a, 0x11, 0x62, 0x97, 0x7e, 0x7e, 0x03, 0x4b, 0x36, 0xfe, 0x88, 0x93, 0xb7, 0x04, + 0x48, 0xdb, 0x59, 0xbb, 0xaa, 0x76, 0x39, 0x6e, 0xed, 0xe6, 0x80, 0xe4, 0xeb, 0x8d, 0x46, 0x23, 0xed, 0x27, 0x27, + 0x60, 0xa8, 0x7a, 0x6a, 0x29, 0xf4, 0x58, 0xad, 0xb0, 0x75, 0x3b, 0x72, 0x99, 0x25, 0x83, 0xf9, 0xc2, 0x38, 0x7e, + 0x69, 0x3e, 0xfa, 0x70, 0xa9, 0xac, 0x5d, 0x47, 0x7c, 0xfd, 0x47, 0x59, 0xad, 0xef, 0x79, 0x57, 0x35, 0x01, 0x5f, + 0x54, 0xb1, 0xa5, 0xdf, 0xf1, 0x9e, 0xec, 0x5d, 0x7c, 0xed, 0x1a, 0xbb, 0xe4, 0x7b, 0xde, 0xa2, 0xce, 0xf3, 0x95, + 0xaf, 0x1b, 0x55, 0xba, 0xbd, 0x97, 0xdc, 0xe0, 0xda, 0x3b, 0x6a, 0x1a, 0xeb, 0x99, 0x1f, 0x3d, 0x2c, 0x42, 0xb6, + 0xf3, 0xa1, 0xf7, 0x55, 0xf3, 0xf4, 0xac, 0xa1, 0x37, 0xa9, 0xa1, 0x0f, 0xbd, 0x28, 0xdb, 0xa7, 0xa6, 0x11, 0xbd, + 0x86, 0x0d, 0x7d, 0xe8, 0x2d, 0x39, 0x39, 0x24, 0x18, 0x9c, 0x1a, 0xf3, 0x87, 0x87, 0xd3, 0x19, 0xfe, 0x8e, 0x01, + 0x95, 0x98, 0xcc, 0xa7, 0xc7, 0xb4, 0xa3, 0x00, 0x33, 0xaa, 0xf4, 0xf6, 0xe9, 0x81, 0xed, 0x78, 0x59, 0x0f, 0x2d, + 0xbd, 0x7b, 0x72, 0x74, 0x3b, 0x5e, 0x55, 0xe3, 0x4b, 0x39, 0xe4, 0x79, 0x3e, 0x1b, 0x8d, 0x46, 0xc2, 0xa0, 0x73, + 0x57, 0x7a, 0x03, 0x2b, 0x90, 0xc1, 0x45, 0xf5, 0xa1, 0x5c, 0x7a, 0x3b, 0x75, 0x68, 0x57, 0xfe, 0x24, 0x3f, 0x1c, + 0x8a, 0x91, 0x39, 0xc6, 0x01, 0xe7, 0xa4, 0x50, 0x72, 0x94, 0xac, 0x25, 0x88, 0x4e, 0x69, 0x3c, 0x95, 0xf5, 0xda, + 0x8a, 0xc8, 0xab, 0x11, 0xf2, 0x21, 0xf8, 0xc9, 0x03, 0xb5, 0xf8, 0x33, 0x2d, 0x88, 0x3d, 0xf4, 0xa9, 0x52, 0x3a, + 0xc4, 0xab, 0x02, 0x42, 0x84, 0x01, 0x6f, 0xa0, 0x1d, 0x94, 0xe0, 0xb0, 0xc3, 0x7d, 0x40, 0x84, 0xe8, 0x37, 0x5e, + 0x3e, 0x93, 0xe1, 0xca, 0xbd, 0x41, 0x35, 0x67, 0x80, 0x58, 0xe9, 0x33, 0x70, 0xc1, 0x04, 0xd4, 0x53, 0x7c, 0x8a, + 0xfe, 0xf5, 0xe6, 0x61, 0xd3, 0xf5, 0x69, 0x09, 0xa8, 0x88, 0x9e, 0xfd, 0x7c, 0x0c, 0xe0, 0x9d, 0x5d, 0x9b, 0x91, + 0xf6, 0xf2, 0x37, 0xc0, 0xb0, 0x52, 0x92, 0x68, 0xe7, 0x94, 0x08, 0xdc, 0xf9, 0xc8, 0x96, 0x7e, 0x94, 0x02, 0x31, + 0x77, 0x3c, 0x49, 0x64, 0x0f, 0x36, 0x72, 0x02, 0xb7, 0x18, 0xf0, 0xe8, 0x00, 0x54, 0xae, 0x14, 0xe4, 0x5e, 0x73, + 0x24, 0x77, 0xfc, 0xd0, 0xfb, 0x61, 0x50, 0x0f, 0x7e, 0xe8, 0x9d, 0xa5, 0x24, 0x77, 0x84, 0x67, 0x6a, 0x4a, 0x88, + 0xf8, 0xec, 0x87, 0x41, 0x3e, 0xc0, 0xb3, 0x44, 0x8b, 0xb4, 0xc8, 0xad, 0x26, 0x6a, 0xdc, 0x84, 0x77, 0x89, 0xa4, + 0x21, 0xda, 0x76, 0x1e, 0x11, 0x37, 0x00, 0x92, 0xc5, 0x67, 0xf3, 0x86, 0xa2, 0xde, 0x4d, 0xf8, 0x16, 0xdd, 0x65, + 0xb1, 0xdf, 0xdf, 0xe4, 0x69, 0xdd, 0xd3, 0xa1, 0x32, 0xf8, 0x82, 0x54, 0x13, 0xe0, 0xd1, 0xfe, 0xca, 0x1c, 0xaf, + 0x5e, 0x6d, 0x8e, 0x94, 0x1b, 0x55, 0xa2, 0x7e, 0x8b, 0xd5, 0xac, 0x87, 0x88, 0xdc, 0x59, 0x66, 0xec, 0xed, 0x05, + 0xaf, 0xe4, 0xac, 0x8a, 0xed, 0x72, 0x7c, 0x45, 0x58, 0x5b, 0x49, 0x80, 0x8e, 0xd6, 0x63, 0x6d, 0x8a, 0x91, 0x5f, + 0x29, 0x24, 0xe0, 0xa2, 0x63, 0x6b, 0xa1, 0xd8, 0x78, 0x01, 0xfa, 0x92, 0x9d, 0x69, 0x80, 0xf5, 0x46, 0xaf, 0x22, + 0x6e, 0xcb, 0x07, 0x2a, 0xbc, 0xc9, 0x4d, 0x95, 0x59, 0xd9, 0xdc, 0xb4, 0xfb, 0xa9, 0xe2, 0x15, 0xe2, 0xd6, 0x1b, + 0xb5, 0x47, 0x01, 0x6a, 0x0f, 0x2d, 0x94, 0x01, 0xba, 0x34, 0xcd, 0x00, 0x90, 0x01, 0x40, 0xa6, 0x8a, 0xf8, 0x4c, + 0x80, 0x4a, 0x5b, 0xdd, 0x28, 0x70, 0x22, 0xbd, 0x01, 0xc6, 0x05, 0x56, 0xfa, 0xc8, 0x46, 0x06, 0x8b, 0x2d, 0x02, + 0xdc, 0x72, 0xa4, 0x0f, 0xd3, 0x70, 0xb2, 0x8d, 0xe6, 0x30, 0x49, 0xf3, 0xbb, 0x30, 0x4b, 0x25, 0xb4, 0xc4, 0x8f, + 0xb2, 0xc6, 0x88, 0x05, 0xa4, 0xef, 0xd3, 0x37, 0x45, 0x16, 0x13, 0x24, 0x9c, 0xf5, 0xd4, 0x01, 0x54, 0x93, 0x73, + 0xad, 0x69, 0xf5, 0xac, 0x36, 0x79, 0xc8, 0x02, 0x9d, 0x3d, 0x18, 0x93, 0x5a, 0x6e, 0xe8, 0x91, 0xfd, 0x95, 0xe3, + 0x19, 0xe1, 0xbb, 0x9e, 0xe1, 0xd4, 0x7f, 0xd7, 0x35, 0x90, 0x32, 0x25, 0x80, 0x20, 0x83, 0xa3, 0x09, 0xa1, 0x3c, + 0x1d, 0x93, 0xa9, 0xcd, 0x8f, 0x40, 0x38, 0x22, 0x78, 0x05, 0xcf, 0x0d, 0xad, 0x5b, 0x6e, 0xec, 0x2c, 0xf2, 0x34, + 0x01, 0x64, 0xf1, 0x82, 0xdf, 0x01, 0x32, 0xa7, 0x5e, 0x15, 0xb2, 0x67, 0xcf, 0xc5, 0x74, 0x36, 0x0f, 0xfe, 0x4c, + 0x68, 0xff, 0x62, 0xc2, 0x6f, 0xba, 0xab, 0xe4, 0xca, 0xd4, 0xba, 0x37, 0xd1, 0x63, 0x2e, 0x77, 0xfa, 0xb4, 0xe2, + 0x18, 0xf1, 0x0c, 0x56, 0x01, 0x39, 0x67, 0x43, 0xfe, 0xec, 0x1c, 0xb0, 0x5b, 0x56, 0xc2, 0x8b, 0xf8, 0xb3, 0x50, + 0x56, 0x0b, 0x90, 0x1f, 0x39, 0x8f, 0xcc, 0x2f, 0x5f, 0x6d, 0x87, 0x72, 0x4e, 0x51, 0x44, 0xcb, 0xa9, 0x69, 0x49, + 0x21, 0x3b, 0xf4, 0x14, 0x4c, 0xa6, 0xb6, 0xfc, 0x7d, 0x97, 0xb8, 0x24, 0xdf, 0x4c, 0x22, 0xfb, 0x3a, 0xc0, 0x9a, + 0xb5, 0xea, 0x1e, 0xba, 0x21, 0x18, 0x20, 0x32, 0x42, 0x99, 0xcd, 0xf5, 0xdd, 0x7a, 0x30, 0x50, 0x30, 0xbf, 0x82, + 0x6e, 0x5a, 0x74, 0x8a, 0x03, 0xe4, 0xac, 0x75, 0x8d, 0x4a, 0x55, 0x71, 0xe8, 0x30, 0xef, 0x96, 0x55, 0xd9, 0x65, + 0xe9, 0x85, 0x20, 0x35, 0xea, 0x2a, 0x58, 0xa4, 0x54, 0x44, 0xf1, 0x9e, 0xfc, 0x1a, 0x98, 0x78, 0x66, 0xe5, 0x28, + 0x8d, 0xe7, 0x80, 0x18, 0xa4, 0x80, 0x38, 0xe5, 0x57, 0x80, 0x26, 0xba, 0x88, 0xc2, 0xec, 0x4d, 0x5c, 0x05, 0xb5, + 0xd5, 0xf4, 0x7b, 0x07, 0x32, 0xf6, 0xbc, 0xee, 0xf7, 0x53, 0x62, 0xf4, 0xc3, 0x28, 0x0c, 0xfc, 0x7b, 0x3c, 0xdd, + 0x37, 0x41, 0x6a, 0x5e, 0xf9, 0x13, 0x5e, 0xd1, 0xe5, 0xd6, 0xa6, 0x5c, 0xd1, 0xb8, 0xf0, 0xd7, 0x08, 0x0e, 0x9f, + 0x3a, 0x8a, 0xed, 0x36, 0x55, 0x4e, 0x6d, 0x0c, 0x06, 0x21, 0xdc, 0xb7, 0x32, 0x7e, 0x9f, 0x78, 0xf9, 0x2c, 0x9a, + 0x83, 0xa2, 0x34, 0xd3, 0x7c, 0x21, 0x85, 0x74, 0x13, 0xa0, 0x8f, 0x06, 0xa1, 0x56, 0x57, 0x5e, 0x27, 0x5e, 0xaa, + 0xa6, 0xb5, 0x79, 0x8a, 0x35, 0x0a, 0xc4, 0x2c, 0x9a, 0x37, 0x2c, 0xa3, 0x43, 0x52, 0x5d, 0x2e, 0x4d, 0x33, 0xae, + 0xad, 0x66, 0xa8, 0x56, 0x1c, 0x35, 0x41, 0x8d, 0xd2, 0x35, 0x5c, 0x00, 0x7f, 0xa6, 0x3b, 0x8e, 0x6a, 0x14, 0x29, + 0x1a, 0xf0, 0x09, 0x62, 0xc4, 0x9a, 0xcd, 0x13, 0xd6, 0x9a, 0xba, 0x66, 0xf4, 0xfb, 0x32, 0x64, 0xc8, 0x24, 0x21, + 0x4f, 0x1f, 0x2e, 0xd7, 0x8f, 0xa4, 0xba, 0x00, 0x7e, 0xe5, 0x8a, 0xcd, 0x7a, 0xbd, 0x39, 0xc0, 0xf5, 0xc2, 0xfa, + 0x85, 0x8d, 0x2b, 0x38, 0xbf, 0x24, 0xf8, 0x5d, 0xf5, 0x23, 0xcc, 0x32, 0xa8, 0x02, 0x32, 0xfe, 0x58, 0x28, 0xea, + 0x79, 0x8b, 0xd9, 0x7d, 0xa4, 0x2e, 0x28, 0xb3, 0x74, 0x6e, 0x71, 0x82, 0x80, 0xf3, 0xb0, 0x7a, 0x02, 0xc9, 0xbe, + 0x7c, 0xec, 0xd3, 0x8c, 0x02, 0xd5, 0x11, 0xe0, 0xb3, 0x59, 0x3f, 0x84, 0xfd, 0x03, 0x22, 0x0b, 0xf5, 0x37, 0xdf, + 0xca, 0x59, 0x43, 0xf2, 0x40, 0xaa, 0xb9, 0x8f, 0xe1, 0xd4, 0xb8, 0xc1, 0x97, 0x6e, 0x7a, 0x53, 0xc1, 0x6b, 0x42, + 0xe6, 0xbe, 0x41, 0x6b, 0xdf, 0x0d, 0x1c, 0x21, 0x82, 0xcb, 0x28, 0xc5, 0x69, 0x6f, 0xd7, 0x0b, 0x90, 0xdb, 0xdc, + 0x82, 0xbc, 0x7e, 0xe9, 0xe2, 0x17, 0xa7, 0x48, 0xcf, 0xa2, 0x0b, 0x0c, 0x74, 0x41, 0xe6, 0x8d, 0x7f, 0x56, 0xb0, + 0x72, 0x01, 0xbd, 0x97, 0x8a, 0x95, 0x9c, 0x6c, 0x3b, 0xf5, 0x47, 0xa9, 0xec, 0xb7, 0x67, 0xd6, 0x04, 0x7e, 0x9f, + 0xd8, 0x2f, 0x91, 0xc9, 0x37, 0x3d, 0x36, 0xf9, 0xca, 0xb0, 0xe8, 0xd4, 0x32, 0x38, 0xa7, 0x47, 0x06, 0xe7, 0xde, + 0xce, 0xaa, 0x4d, 0x08, 0x43, 0x41, 0x12, 0x68, 0xba, 0xf0, 0xb0, 0x6e, 0xfa, 0xf3, 0x93, 0x16, 0xd5, 0x56, 0xed, + 0x5b, 0xf7, 0xe3, 0x10, 0xbb, 0xf8, 0x7d, 0xe2, 0x19, 0x22, 0x52, 0x1f, 0xe8, 0xc0, 0x64, 0xf0, 0xc4, 0x65, 0xbf, + 0x0f, 0x85, 0xcd, 0xc6, 0xf3, 0x51, 0x5d, 0xfc, 0x52, 0xdc, 0x03, 0xaa, 0x43, 0x05, 0x76, 0x39, 0x94, 0xa1, 0x8c, + 0xd8, 0xd4, 0x96, 0x7b, 0xfe, 0x78, 0x19, 0xe6, 0x20, 0xef, 0x68, 0x78, 0x9c, 0x33, 0x10, 0xc3, 0xe0, 0xeb, 0x3f, + 0x3c, 0xda, 0xa7, 0xcd, 0x0f, 0x67, 0xf0, 0xdd, 0xd1, 0xd9, 0x07, 0xa4, 0xbb, 0x39, 0x5b, 0x97, 0xc5, 0x5d, 0x1a, + 0x8b, 0xb3, 0x1f, 0x20, 0xf5, 0x87, 0xb3, 0xa2, 0x3c, 0xfb, 0x41, 0x55, 0xe6, 0x87, 0x33, 0x5a, 0x70, 0xa3, 0x3f, + 0xac, 0x89, 0xf7, 0x7b, 0xa5, 0x19, 0xd0, 0x16, 0x10, 0x99, 0xa5, 0xd5, 0x8f, 0xa0, 0x44, 0x54, 0xfc, 0xa8, 0x32, + 0xaa, 0xd5, 0xda, 0x71, 0x3e, 0x24, 0x1a, 0x29, 0x9b, 0x26, 0x24, 0xae, 0x96, 0xb0, 0x0e, 0xf5, 0xec, 0xb4, 0xf9, + 0x76, 0x9c, 0x07, 0xea, 0x80, 0xc8, 0xf9, 0xb3, 0x7c, 0xb4, 0xa5, 0xaf, 0xc1, 0xb7, 0x0e, 0x87, 0x7c, 0xb4, 0x33, + 0x3f, 0x7d, 0xb2, 0x56, 0xca, 0xb8, 0x23, 0x45, 0x2e, 0x84, 0x9c, 0x71, 0xdb, 0x1e, 0x03, 0x0e, 0x00, 0xff, 0x70, + 0xa0, 0xdf, 0x3b, 0xf9, 0x5b, 0xed, 0x96, 0x56, 0x3d, 0x1f, 0xb5, 0xb8, 0x33, 0xde, 0xd4, 0x86, 0xa8, 0x6d, 0x2f, + 0xb1, 0xa5, 0xf7, 0x4d, 0x83, 0x9a, 0x22, 0xfa, 0x09, 0xab, 0x89, 0x55, 0x1c, 0x16, 0xa4, 0x84, 0x24, 0x86, 0x63, + 0xb4, 0x43, 0x8f, 0xd3, 0xc5, 0xd2, 0x93, 0xfb, 0x0e, 0x2f, 0xb7, 0xbe, 0x0f, 0x48, 0x5a, 0x85, 0xf3, 0x0f, 0x5e, + 0x68, 0xe0, 0xd1, 0x8b, 0xbc, 0x2a, 0x32, 0x31, 0x12, 0x34, 0xca, 0x6f, 0x48, 0x9c, 0x39, 0xc3, 0x5a, 0x9c, 0x29, + 0xb0, 0xb0, 0x90, 0xd0, 0xbd, 0x8b, 0x92, 0xd2, 0x83, 0xb3, 0x47, 0xfb, 0xb2, 0xf9, 0x83, 0xe0, 0x21, 0x46, 0x37, + 0xc0, 0x88, 0xb3, 0x6b, 0x97, 0x77, 0x1f, 0x96, 0xb9, 0xf7, 0xc7, 0x9b, 0x65, 0x5e, 0x40, 0x88, 0xe6, 0x99, 0x54, + 0xac, 0x96, 0x67, 0xc0, 0x98, 0x27, 0xe2, 0xb3, 0xb0, 0x92, 0xd3, 0xa0, 0xea, 0x28, 0x56, 0x6f, 0xe3, 0xb9, 0x07, + 0x14, 0xdf, 0x1f, 0x12, 0xe0, 0x72, 0xf7, 0xd9, 0x6b, 0xe5, 0x9a, 0x4a, 0x7a, 0xe4, 0x39, 0x44, 0x4b, 0xbe, 0x4c, + 0x80, 0xe2, 0x19, 0xe2, 0x24, 0x85, 0xd5, 0x73, 0x13, 0xa4, 0x22, 0x5f, 0x9f, 0x50, 0x7c, 0xd1, 0x3c, 0x8a, 0x1a, + 0x16, 0xb2, 0x04, 0x8e, 0x87, 0x64, 0x96, 0xcd, 0x91, 0xa5, 0x3c, 0x6d, 0x4f, 0x91, 0x8e, 0x4e, 0x2c, 0xf1, 0xdb, + 0x9a, 0x5f, 0x2f, 0x52, 0x11, 0x98, 0xb4, 0xb3, 0x95, 0xb9, 0x17, 0xc2, 0x50, 0x25, 0xdc, 0x7b, 0x53, 0xcf, 0x42, + 0xb9, 0x29, 0x5a, 0x15, 0xb3, 0x87, 0x29, 0x31, 0xc3, 0x14, 0xeb, 0x2f, 0x6c, 0xf8, 0xdb, 0xc4, 0x8b, 0xc1, 0x70, + 0xbd, 0xe0, 0xe5, 0x6c, 0x63, 0x16, 0xc2, 0xe1, 0xb0, 0x99, 0x14, 0xb3, 0x05, 0x84, 0xb9, 0x2e, 0xe6, 0x87, 0x43, + 0x57, 0xcb, 0xd6, 0xc2, 0x83, 0x87, 0xaa, 0x85, 0x9b, 0x86, 0xe5, 0xf0, 0x33, 0x99, 0xc5, 0xd8, 0xbe, 0xc6, 0x67, + 0xf6, 0xe7, 0x8b, 0xee, 0x59, 0x82, 0xe4, 0x1b, 0x6b, 0xa0, 0x1d, 0x9b, 0xb5, 0x3b, 0x5c, 0x8d, 0x80, 0xa4, 0x74, + 0x37, 0xfa, 0xbb, 0xb2, 0x93, 0xa7, 0x04, 0xb9, 0xa3, 0x15, 0xd8, 0xef, 0xbe, 0xf1, 0x27, 0x5a, 0xec, 0x41, 0xbb, + 0x8d, 0x2d, 0x21, 0xaa, 0x69, 0xcf, 0xe5, 0x4a, 0xb1, 0x34, 0x6f, 0xa5, 0x8d, 0x9e, 0x0f, 0xeb, 0x73, 0xdf, 0xc8, + 0x81, 0x82, 0x31, 0xe2, 0xa9, 0x75, 0x10, 0xcd, 0xe6, 0x40, 0x83, 0x81, 0xe6, 0x11, 0x9e, 0x5a, 0xe8, 0xa0, 0xcc, + 0xda, 0xb0, 0x9f, 0x27, 0x27, 0xcb, 0xe3, 0xf0, 0x2d, 0xfc, 0xcb, 0x67, 0xd8, 0x24, 0xa6, 0xd8, 0x1e, 0xff, 0xaa, + 0x14, 0x15, 0x1e, 0xdb, 0x11, 0xd7, 0xda, 0xb5, 0xa8, 0x0d, 0x95, 0xc3, 0xbf, 0x84, 0x7d, 0x84, 0xfd, 0x85, 0x26, + 0x08, 0x83, 0x5d, 0x7f, 0x26, 0x10, 0x22, 0x16, 0xe2, 0x05, 0xff, 0xaa, 0x24, 0x15, 0x9d, 0xf0, 0xd9, 0xae, 0x04, + 0xde, 0x3a, 0x0c, 0xe8, 0x13, 0xf2, 0x33, 0x91, 0x30, 0x34, 0x13, 0x7a, 0x47, 0xff, 0x9d, 0xd8, 0xc9, 0x26, 0xb9, + 0x15, 0xf2, 0x81, 0xa4, 0x92, 0x60, 0x82, 0x95, 0x17, 0xca, 0x1f, 0xdd, 0x0b, 0xa5, 0xd6, 0x5a, 0xd0, 0xfa, 0xe5, + 0xcf, 0x13, 0xcf, 0xe0, 0xef, 0x81, 0x8c, 0x41, 0xb7, 0x11, 0xd5, 0x24, 0xc7, 0xf4, 0x51, 0x3a, 0xcf, 0x40, 0x05, + 0x74, 0xb6, 0xce, 0xc2, 0x7a, 0x51, 0x94, 0xab, 0x56, 0xa4, 0xa8, 0x2c, 0x7d, 0xa4, 0x1e, 0x63, 0x5e, 0x98, 0x27, + 0x27, 0xf2, 0xc1, 0x23, 0x00, 0xc6, 0xa3, 0x3c, 0xad, 0x3a, 0x4a, 0xeb, 0x07, 0x96, 0x01, 0x23, 0x70, 0xa2, 0x0c, + 0x78, 0x84, 0x65, 0x60, 0x9e, 0x76, 0x19, 0x6a, 0x10, 0x6b, 0x54, 0x5d, 0xa9, 0x0d, 0xe6, 0x44, 0x51, 0xf2, 0x29, + 0x96, 0x56, 0x18, 0x43, 0x53, 0x57, 0x1e, 0x59, 0x2f, 0x39, 0x61, 0x4f, 0x76, 0x03, 0xe9, 0x16, 0x36, 0x0a, 0x67, + 0xd0, 0xb5, 0x2c, 0x51, 0x2e, 0xba, 0x65, 0x44, 0x99, 0x08, 0xa9, 0x9f, 0x3d, 0x9c, 0x69, 0xb5, 0xdf, 0xd8, 0x49, + 0xfb, 0xf6, 0x48, 0xd1, 0x0b, 0x06, 0xed, 0xd3, 0x1e, 0x29, 0xf5, 0xac, 0x91, 0xcb, 0xc0, 0x96, 0x2e, 0x55, 0x3d, + 0xff, 0x05, 0xca, 0x77, 0x30, 0x33, 0xce, 0x66, 0x7f, 0xe8, 0xcd, 0xed, 0xd1, 0xbe, 0x6e, 0xfe, 0x60, 0xbd, 0x1e, + 0x6c, 0x0d, 0x32, 0xf1, 0xb9, 0x62, 0xa1, 0xb2, 0x0a, 0xb1, 0x82, 0xb4, 0xff, 0x25, 0xbc, 0x3f, 0xe0, 0xad, 0x11, + 0x9a, 0x95, 0xf1, 0x30, 0x1f, 0x3d, 0xda, 0x8b, 0xe6, 0x8f, 0xce, 0xb2, 0xad, 0x5c, 0x95, 0xcc, 0xf6, 0xc7, 0x51, + 0xd2, 0x9c, 0x3d, 0x5c, 0x23, 0xa9, 0x03, 0x7c, 0xb8, 0x3e, 0xc3, 0x07, 0x2a, 0xa1, 0xd4, 0x82, 0xaa, 0x06, 0xad, + 0x8f, 0xfd, 0xd1, 0x7a, 0x4e, 0x1f, 0x3f, 0x96, 0xd3, 0x2d, 0x29, 0xc2, 0xf8, 0x81, 0xc1, 0x94, 0x9d, 0x38, 0x75, + 0xc9, 0x9b, 0x21, 0xbd, 0xeb, 0x56, 0x49, 0x5d, 0xf6, 0x28, 0x11, 0x84, 0x3a, 0x58, 0xbf, 0xd8, 0x0f, 0x61, 0x66, + 0x8b, 0xfe, 0xb0, 0x59, 0xcd, 0x09, 0x10, 0x11, 0xd0, 0x5a, 0xe5, 0x7d, 0xe0, 0x98, 0x2f, 0xcc, 0x9a, 0x1b, 0xd2, + 0xad, 0x37, 0x57, 0xda, 0x2b, 0x29, 0xa0, 0x9f, 0x83, 0xcc, 0xed, 0xa3, 0x5b, 0xae, 0x5a, 0xe6, 0xb9, 0xb4, 0xe5, + 0x80, 0x45, 0x0b, 0x81, 0x9a, 0x9d, 0x4b, 0x87, 0x03, 0x05, 0xa1, 0xae, 0x44, 0x15, 0x71, 0x75, 0x14, 0x2d, 0x44, + 0xad, 0x56, 0xed, 0x72, 0xb2, 0xa9, 0x90, 0x2d, 0x89, 0x20, 0xa3, 0x14, 0x43, 0x97, 0x3e, 0xca, 0xd5, 0x9e, 0x69, + 0x38, 0x40, 0x13, 0xb0, 0x69, 0x83, 0xbf, 0x05, 0xee, 0x65, 0x70, 0x66, 0xda, 0xa7, 0x61, 0x04, 0x9c, 0xe6, 0x10, + 0xf3, 0xe7, 0x77, 0x3d, 0xa8, 0xe0, 0x41, 0x47, 0xfa, 0x9b, 0x7a, 0x56, 0xe0, 0x99, 0x7b, 0xe2, 0xf9, 0xeb, 0x13, + 0xe9, 0x45, 0x0e, 0x0f, 0x34, 0x0d, 0x62, 0xc6, 0x9f, 0x97, 0x65, 0xb8, 0x1b, 0x2d, 0xca, 0x62, 0xe5, 0x45, 0x7a, + 0x1f, 0xcf, 0xa4, 0x18, 0x48, 0xcc, 0x98, 0x19, 0x5d, 0xc5, 0x3a, 0xce, 0x61, 0xdc, 0xdb, 0x93, 0xb0, 0x42, 0xfb, + 0x67, 0x89, 0xbd, 0x2e, 0x00, 0xcb, 0x21, 0x6b, 0xd0, 0x0a, 0xef, 0x74, 0x7b, 0xbb, 0xc7, 0x25, 0x3b, 0x8a, 0x1b, + 0x40, 0x3f, 0xab, 0xa1, 0x65, 0x82, 0x5a, 0x66, 0xdd, 0xc9, 0x64, 0x8a, 0xe4, 0xf2, 0x6d, 0xd8, 0x6b, 0x56, 0xe4, + 0xf3, 0x46, 0x6e, 0x0f, 0xef, 0xc2, 0x95, 0x88, 0xb5, 0x05, 0x9d, 0x74, 0x64, 0x1c, 0xee, 0x85, 0xe6, 0x46, 0xba, + 0x7f, 0x54, 0x25, 0x61, 0x29, 0x62, 0xb8, 0x05, 0xb2, 0xbd, 0xda, 0x56, 0x82, 0x12, 0xf8, 0x60, 0x3f, 0x94, 0x62, + 0x91, 0x6e, 0x05, 0xe0, 0x3a, 0xf0, 0xcf, 0x12, 0x91, 0xd0, 0xdd, 0x79, 0x88, 0x62, 0x8d, 0xbc, 0x6f, 0x10, 0x8d, + 0xfd, 0x15, 0xc8, 0x69, 0x40, 0x26, 0x52, 0x8c, 0x64, 0xc1, 0xc0, 0x07, 0x90, 0xf3, 0x35, 0x98, 0xe4, 0xa6, 0xb9, + 0xe7, 0x07, 0xb9, 0xee, 0x60, 0xda, 0x07, 0xdd, 0x8b, 0x6b, 0xcd, 0x72, 0xf0, 0x8a, 0x89, 0xf8, 0xcf, 0xb5, 0x57, + 0xb2, 0x9c, 0x65, 0x7e, 0x63, 0x2e, 0x3a, 0x19, 0x5c, 0x35, 0x84, 0x5f, 0xcc, 0xb2, 0x39, 0x8f, 0x66, 0x99, 0x8e, + 0xfa, 0x2f, 0x9a, 0xa3, 0x52, 0x00, 0x4e, 0x1d, 0x2f, 0xc0, 0x1a, 0xfa, 0x4a, 0x37, 0xad, 0x78, 0xa0, 0x31, 0x46, + 0x41, 0x85, 0x0e, 0x42, 0x3f, 0xd7, 0x80, 0xb4, 0xc1, 0x24, 0x4d, 0x42, 0xe5, 0x83, 0x0b, 0xba, 0x61, 0x5e, 0xae, + 0x5c, 0xae, 0x9a, 0x54, 0x2d, 0xbf, 0x1c, 0x51, 0xdf, 0xd5, 0x92, 0x4b, 0xb5, 0xf9, 0xd4, 0x28, 0x6b, 0x04, 0x99, + 0x1c, 0xa5, 0xdf, 0xa7, 0x5c, 0xb8, 0x95, 0x31, 0x59, 0x1f, 0x0e, 0x5e, 0xc1, 0x4d, 0x8d, 0xdf, 0xe4, 0x44, 0x28, + 0x6a, 0x0f, 0x89, 0xb0, 0xb5, 0x5b, 0xa1, 0x7b, 0x8f, 0x1b, 0xa5, 0x79, 0x94, 0x6d, 0x62, 0x51, 0x79, 0xbd, 0x04, + 0xac, 0xc5, 0x3d, 0xe0, 0x45, 0xa5, 0xa5, 0x5f, 0xb1, 0x02, 0xd0, 0x03, 0xa4, 0xb0, 0xf1, 0x06, 0x19, 0xb0, 0x3e, + 0x78, 0xa9, 0xdf, 0xef, 0x1b, 0x53, 0xfe, 0xfb, 0xfb, 0x1c, 0x48, 0x0a, 0x45, 0x59, 0xef, 0x60, 0x02, 0xc1, 0xb5, + 0x93, 0xb4, 0x67, 0x35, 0x7f, 0xb6, 0xae, 0x3d, 0xe0, 0xb7, 0xf2, 0x2d, 0x12, 0xab, 0x57, 0xf6, 0xc5, 0x66, 0x9f, + 0x56, 0xd7, 0x46, 0xe3, 0x20, 0x58, 0x5a, 0xbd, 0xd1, 0x2a, 0x87, 0xbc, 0xe1, 0x05, 0x88, 0x54, 0xd6, 0xd5, 0xb5, + 0x72, 0xae, 0xae, 0x05, 0x47, 0x2e, 0xd9, 0x92, 0xe7, 0xf0, 0x5f, 0xc8, 0xbd, 0xf2, 0x70, 0x28, 0xfc, 0x7e, 0x3f, + 0x9d, 0x91, 0x56, 0x16, 0xd8, 0xd3, 0xd6, 0xb5, 0x17, 0xfa, 0x87, 0xc3, 0x1b, 0xf0, 0x1a, 0xf1, 0x0f, 0x87, 0xb2, + 0xdf, 0xff, 0x68, 0x6e, 0x32, 0xe7, 0x63, 0xa5, 0x94, 0xbd, 0x44, 0xa5, 0xfb, 0xa7, 0x84, 0xf7, 0xfe, 0xf7, 0xe8, + 0x7f, 0x8f, 0x2e, 0x7b, 0xb2, 0xeb, 0x7f, 0x49, 0xf8, 0x0c, 0x6f, 0xe8, 0x4c, 0x5d, 0xce, 0x99, 0x74, 0x77, 0x57, + 0x7e, 0xe8, 0x3d, 0x0d, 0x15, 0xdf, 0x9b, 0x9b, 0x36, 0xfe, 0x5c, 0x1d, 0x69, 0x12, 0x3a, 0x2e, 0xfa, 0x87, 0xc3, + 0x2f, 0x89, 0xd6, 0xa7, 0xa5, 0x4a, 0x9f, 0xa6, 0x70, 0x94, 0x0c, 0xb9, 0x9b, 0x5b, 0x98, 0x0e, 0xec, 0xc7, 0xcd, + 0x57, 0xc9, 0x8b, 0xb3, 0x14, 0xae, 0xbd, 0xf9, 0x2c, 0x9d, 0x4f, 0xc1, 0xba, 0x32, 0xcc, 0x67, 0xf5, 0x3c, 0x80, + 0xd4, 0x21, 0xa4, 0x59, 0xd3, 0xf0, 0x1f, 0x95, 0x2b, 0x78, 0x6b, 0x8f, 0x77, 0x03, 0x17, 0xa5, 0x8e, 0xf4, 0x49, + 0x1b, 0x4d, 0x97, 0x54, 0xf2, 0x1f, 0x45, 0x1e, 0x63, 0xcc, 0xc6, 0x1b, 0xe2, 0xfd, 0x2c, 0xf2, 0x97, 0x05, 0x60, + 0x17, 0x01, 0x18, 0x72, 0x3a, 0x77, 0x24, 0xf1, 0x8f, 0xc9, 0xf7, 0x7f, 0x4c, 0x97, 0xf6, 0xa1, 0x2c, 0x96, 0xa5, + 0xa8, 0xaa, 0xa3, 0xd2, 0xb6, 0xb6, 0x5c, 0x0f, 0x4c, 0xa2, 0xfd, 0xbe, 0x64, 0x12, 0x4d, 0x31, 0x14, 0x05, 0x6e, + 0x8d, 0xbd, 0x69, 0xca, 0x15, 0x63, 0xf5, 0xc8, 0x58, 0x3f, 0x5f, 0xec, 0xde, 0xc4, 0x5e, 0xea, 0x07, 0x29, 0x08, + 0xc2, 0x1a, 0x4a, 0x29, 0x45, 0x3e, 0x38, 0x9f, 0x61, 0x2a, 0x51, 0xeb, 0x52, 0xaa, 0xfc, 0x61, 0xa4, 0xf9, 0x30, + 0x05, 0xbd, 0xec, 0xbf, 0x2a, 0x98, 0xff, 0xba, 0x3d, 0x58, 0x9f, 0xd6, 0x65, 0x1a, 0x55, 0x44, 0x95, 0x17, 0xa6, + 0xda, 0x04, 0x22, 0xf8, 0x33, 0x61, 0xf1, 0xfd, 0xfa, 0xe4, 0x48, 0xd0, 0x98, 0xc9, 0xf2, 0xfa, 0xc8, 0xfd, 0xc2, + 0xbe, 0x72, 0x1d, 0xcf, 0xff, 0xdc, 0xcc, 0xff, 0x01, 0x3a, 0x43, 0x16, 0xcf, 0xb8, 0x65, 0xb0, 0xc0, 0xd9, 0x2f, + 0x5d, 0x3d, 0xe0, 0x6f, 0xe6, 0x89, 0x67, 0x40, 0xc7, 0xfc, 0x0c, 0x5d, 0x15, 0xd3, 0x59, 0x31, 0x00, 0x2e, 0x5b, + 0xbf, 0xb1, 0xe6, 0xc4, 0x3b, 0x8b, 0xf2, 0x4a, 0x2e, 0x08, 0x7d, 0x5d, 0x85, 0xd9, 0xb8, 0x2a, 0x36, 0x95, 0x28, + 0x36, 0x75, 0x8f, 0xd4, 0xb2, 0xf9, 0xb4, 0xb6, 0x15, 0xb2, 0x7f, 0x17, 0x2d, 0x06, 0x2f, 0xc3, 0x3a, 0x19, 0x65, + 0xe9, 0x7a, 0x0a, 0xfc, 0x7a, 0x01, 0x9c, 0x45, 0xe6, 0x95, 0xaf, 0xce, 0x1e, 0xb0, 0x45, 0xe3, 0x29, 0x90, 0xa3, + 0xd2, 0x1f, 0x79, 0x63, 0x74, 0x7a, 0xa2, 0xdf, 0xcf, 0xa7, 0x14, 0xf3, 0xf5, 0x77, 0x80, 0xe7, 0xaa, 0xe5, 0x02, + 0xf4, 0x65, 0xa8, 0x83, 0x4a, 0x94, 0x5a, 0x31, 0x8c, 0x58, 0xf8, 0xbb, 0x40, 0x22, 0x67, 0x0a, 0x6c, 0x56, 0x51, + 0x12, 0x2a, 0x51, 0x29, 0xd9, 0x9a, 0xa0, 0x96, 0xde, 0x17, 0x65, 0xbd, 0xaf, 0xc0, 0x51, 0x32, 0xd2, 0x66, 0x39, + 0x69, 0xc6, 0x15, 0x28, 0x73, 0xd1, 0x0f, 0xf6, 0xf7, 0xca, 0xf3, 0x1b, 0x99, 0xcf, 0x72, 0xdf, 0xd1, 0x39, 0x6d, + 0xc7, 0x05, 0xca, 0xdc, 0x72, 0xda, 0x6a, 0xc9, 0x63, 0xf2, 0x9e, 0x85, 0xda, 0xb2, 0x04, 0x29, 0x16, 0x61, 0x3e, + 0xa1, 0xca, 0xe6, 0x5f, 0x10, 0x6a, 0x8b, 0x03, 0x7b, 0xec, 0xc2, 0x44, 0xfc, 0xb7, 0x60, 0x49, 0x0c, 0xb3, 0x52, + 0x84, 0xf1, 0x0e, 0xbc, 0x7f, 0x36, 0x95, 0x18, 0x9d, 0xa1, 0x93, 0xfb, 0xd9, 0x7d, 0x5a, 0x27, 0x67, 0x6f, 0x5e, + 0x9d, 0xfd, 0xd0, 0x1b, 0x14, 0xa3, 0x34, 0x1e, 0xf4, 0x7e, 0x38, 0x5b, 0x6d, 0x00, 0x2d, 0x53, 0x9c, 0xc5, 0x64, + 0x4a, 0x13, 0xf1, 0x19, 0x19, 0x06, 0xcf, 0xea, 0x44, 0x9c, 0xd1, 0xc4, 0x74, 0x5f, 0xa3, 0x34, 0xf9, 0x76, 0x14, + 0xe6, 0xf0, 0x72, 0x29, 0x36, 0x95, 0x88, 0xc1, 0x4e, 0xa9, 0xe6, 0x59, 0xde, 0x3e, 0x8b, 0xf3, 0x51, 0x87, 0xac, + 0xd2, 0x81, 0xbf, 0x3d, 0x91, 0x76, 0x55, 0xba, 0x02, 0x42, 0x0f, 0x80, 0x93, 0xae, 0xfc, 0x79, 0x38, 0xa4, 0x09, + 0x84, 0x5a, 0x30, 0x27, 0xd3, 0x88, 0x6e, 0x48, 0x2f, 0xb1, 0xcf, 0xc0, 0x2c, 0xa4, 0x34, 0x0f, 0x6e, 0xae, 0x16, + 0x43, 0x77, 0xc5, 0xca, 0x51, 0x58, 0xad, 0x45, 0x54, 0x23, 0xeb, 0x31, 0x38, 0xef, 0x40, 0x04, 0x80, 0x22, 0x07, + 0xcf, 0x78, 0xd4, 0xef, 0x47, 0x2a, 0x28, 0x27, 0xa1, 0x5f, 0x14, 0xfa, 0xa5, 0xe1, 0x28, 0x63, 0xfe, 0x25, 0xd4, + 0x1c, 0x01, 0xf5, 0x96, 0x87, 0x8a, 0x2e, 0x00, 0x97, 0x73, 0xc4, 0x8c, 0xf3, 0xde, 0xff, 0xe1, 0xed, 0x4b, 0xb8, + 0xdb, 0xb6, 0xb5, 0x75, 0xff, 0x8a, 0xc5, 0x97, 0xaa, 0x44, 0x04, 0xc9, 0x92, 0x93, 0xf4, 0x9c, 0x52, 0x86, 0x75, + 0xdd, 0x0c, 0x6d, 0x7a, 0x9a, 0xa1, 0x71, 0xd2, 0x49, 0x4f, 0xd7, 0xa5, 0x49, 0xd8, 0x62, 0x43, 0x03, 0x2a, 0x49, + 0x79, 0x88, 0xc4, 0xff, 0xfe, 0xd6, 0xde, 0x18, 0x49, 0xd1, 0x4e, 0xce, 0x79, 0xf7, 0xbd, 0x95, 0xb5, 0x62, 0x11, + 0x04, 0x31, 0x63, 0x63, 0x63, 0x0f, 0xdf, 0x66, 0x4d, 0x60, 0x4e, 0x13, 0x82, 0xc2, 0x5c, 0x07, 0x0b, 0x03, 0x40, + 0xef, 0xda, 0xa3, 0x2d, 0x27, 0x5d, 0x82, 0xc5, 0x73, 0x03, 0x8b, 0x57, 0x17, 0x8b, 0xea, 0x92, 0x6b, 0xb9, 0x85, + 0x4d, 0x29, 0xab, 0x18, 0x02, 0x08, 0x34, 0x63, 0x86, 0xdd, 0x70, 0x97, 0x23, 0x59, 0x17, 0x05, 0x17, 0x3b, 0x81, + 0xa1, 0x9b, 0x71, 0xc9, 0xcc, 0xc1, 0xd5, 0x0c, 0xeb, 0xa4, 0xa2, 0x00, 0xbb, 0xba, 0x00, 0xd9, 0x0b, 0x43, 0x5d, + 0x37, 0xb3, 0xe5, 0x3a, 0xf0, 0x75, 0xe9, 0xc2, 0x97, 0x14, 0xbc, 0x5c, 0x49, 0x51, 0x66, 0x57, 0xfc, 0x27, 0xfb, + 0xb2, 0x19, 0x4b, 0x0a, 0xed, 0x48, 0x5f, 0xb5, 0xbb, 0xa3, 0xc5, 0x38, 0xb6, 0x1c, 0xdf, 0x52, 0xe9, 0x46, 0x8f, + 0xaa, 0x17, 0x42, 0x5b, 0xe7, 0x5a, 0x66, 0x69, 0xca, 0xc5, 0x4b, 0x91, 0x66, 0x89, 0x97, 0x1c, 0xeb, 0x58, 0xd5, + 0x2e, 0x08, 0x96, 0x0b, 0x93, 0xfc, 0x2c, 0x2b, 0x31, 0x76, 0x70, 0xa3, 0x51, 0xad, 0xa8, 0x53, 0x26, 0x06, 0x86, + 0x7c, 0x87, 0xc1, 0xb7, 0x99, 0x4c, 0x80, 0xe1, 0xc7, 0x44, 0x7d, 0x49, 0x4f, 0x21, 0xe0, 0x83, 0x0a, 0xcd, 0xfd, + 0x8c, 0x23, 0xf8, 0xb5, 0x55, 0x99, 0x03, 0x93, 0xad, 0x55, 0x90, 0x88, 0x7b, 0x97, 0xcd, 0xf5, 0x22, 0x5a, 0xa8, + 0xbb, 0x50, 0x2f, 0xde, 0x6e, 0x7b, 0x89, 0xa2, 0x03, 0x4e, 0x7e, 0x1a, 0xbc, 0x88, 0xb3, 0x9c, 0xa7, 0x7b, 0x95, + 0xdc, 0x53, 0x1b, 0x6a, 0x4f, 0x39, 0x73, 0xc0, 0xce, 0xfb, 0xba, 0xda, 0xd3, 0x6b, 0x7a, 0x4f, 0xb7, 0x73, 0x0f, + 0x2e, 0x18, 0xb8, 0x73, 0x2f, 0xb2, 0x2b, 0x2e, 0xf6, 0x40, 0x19, 0x68, 0x8d, 0x07, 0xea, 0xb2, 0x1a, 0xa9, 0x89, + 0xd1, 0x31, 0xac, 0x13, 0x7d, 0x30, 0x07, 0xf4, 0x67, 0x08, 0x6b, 0xdf, 0x7a, 0xbb, 0xd2, 0x07, 0x6d, 0x40, 0xdf, + 0x2d, 0x4d, 0x1f, 0x74, 0xe0, 0x78, 0x15, 0x1d, 0xb8, 0x31, 0xa4, 0x1a, 0xb4, 0xd5, 0xc8, 0x2a, 0x50, 0xbc, 0xe1, + 0x2d, 0xde, 0x9d, 0x6b, 0xc9, 0xc6, 0x7b, 0x89, 0x18, 0x5f, 0x99, 0xa8, 0xe2, 0x4c, 0x1c, 0x7b, 0xa9, 0xbc, 0xd6, + 0x4e, 0x32, 0xc2, 0xf8, 0x96, 0x95, 0xd4, 0xdf, 0x21, 0xe6, 0x16, 0x69, 0x0e, 0x83, 0xe7, 0x61, 0x45, 0x66, 0xbc, + 0xdf, 0x97, 0x33, 0x19, 0x95, 0x33, 0xb1, 0x5f, 0x46, 0x0a, 0xac, 0xed, 0x2e, 0x11, 0xd0, 0xbd, 0x12, 0x20, 0x5f, + 0x00, 0x54, 0xdd, 0x27, 0xfc, 0xb9, 0x4f, 0xea, 0xd3, 0x29, 0xf4, 0x29, 0xb4, 0xf5, 0x8a, 0x2b, 0x88, 0x57, 0x75, + 0x63, 0x64, 0x1b, 0x15, 0xb4, 0x78, 0x2c, 0xcf, 0x6a, 0xc3, 0xd8, 0x9c, 0x5a, 0xff, 0x7a, 0xb3, 0xc1, 0x94, 0xcd, + 0x85, 0x5a, 0x85, 0x21, 0x89, 0x3e, 0x96, 0x5e, 0x24, 0x11, 0x0b, 0x9b, 0xd5, 0xda, 0xfc, 0x26, 0x0c, 0x48, 0x26, + 0x52, 0xdc, 0xcf, 0x96, 0x38, 0x77, 0xf1, 0x78, 0x5e, 0xf5, 0xb5, 0x96, 0x16, 0x99, 0x36, 0xdf, 0xe8, 0xcb, 0x90, + 0xa6, 0xa2, 0x86, 0x34, 0xea, 0xcc, 0xa0, 0xfb, 0x76, 0x79, 0xcb, 0x6a, 0x84, 0x09, 0xf0, 0x4a, 0x67, 0xd0, 0x8d, + 0xc6, 0x03, 0xb1, 0xac, 0x46, 0xc5, 0x5a, 0x08, 0x04, 0x1e, 0x86, 0x1c, 0x33, 0x4b, 0x48, 0xb2, 0x4f, 0xfc, 0x3b, + 0x15, 0x67, 0xa1, 0x88, 0xaf, 0x0d, 0xb2, 0x77, 0x65, 0x5d, 0xbb, 0xeb, 0xc8, 0xcf, 0x89, 0x85, 0xd5, 0xfe, 0x43, + 0xf3, 0xa8, 0x35, 0xce, 0x02, 0xda, 0x9a, 0x56, 0x37, 0x1c, 0xee, 0x51, 0x1d, 0x8b, 0xd2, 0x60, 0x13, 0x7b, 0x64, + 0xb9, 0x68, 0x1d, 0x33, 0x68, 0x40, 0x7f, 0x93, 0x5d, 0xae, 0x2f, 0x11, 0xc0, 0xad, 0x44, 0xd6, 0x49, 0x2a, 0xff, + 0x92, 0xf6, 0xa8, 0x6b, 0x7b, 0x2a, 0xff, 0xdb, 0x36, 0x55, 0x0e, 0x2d, 0xa6, 0x3c, 0x76, 0x73, 0x16, 0xa8, 0x8e, + 0x04, 0x51, 0xa0, 0xb6, 0x5e, 0x30, 0xf5, 0x4e, 0x99, 0xa2, 0x03, 0x04, 0xba, 0x30, 0x67, 0xd8, 0x17, 0x1c, 0x31, + 0x66, 0xa9, 0xc4, 0x60, 0xea, 0x63, 0x8c, 0x6a, 0x5a, 0x2b, 0x40, 0xd7, 0x4f, 0x37, 0xf0, 0x27, 0x2a, 0x6a, 0x34, + 0xd4, 0x1a, 0x49, 0xa1, 0x68, 0xa2, 0x42, 0x91, 0xa5, 0x85, 0x8e, 0xab, 0xd0, 0x49, 0x24, 0x2c, 0x01, 0x0d, 0x13, + 0xa2, 0x93, 0x0a, 0xbc, 0x35, 0x80, 0x33, 0x1f, 0x17, 0xe5, 0xba, 0xd0, 0x06, 0x73, 0x3f, 0xc4, 0x57, 0xfc, 0xe5, + 0x33, 0x67, 0x54, 0xdf, 0xb2, 0xd6, 0xf7, 0xb4, 0x20, 0x3f, 0x84, 0x9c, 0xa2, 0x03, 0x13, 0x3b, 0xda, 0xa0, 0x31, + 0x46, 0x59, 0xeb, 0xa8, 0x17, 0x6f, 0x74, 0x28, 0x16, 0x6d, 0x82, 0x77, 0x8f, 0xa7, 0x88, 0x36, 0x3c, 0x14, 0xc6, + 0xaa, 0x1a, 0x9f, 0x4a, 0xd6, 0xd2, 0x83, 0x15, 0x3c, 0x5d, 0x27, 0x3c, 0x04, 0x3d, 0x12, 0x61, 0x47, 0x61, 0x31, + 0x8f, 0x17, 0x70, 0x9c, 0x14, 0x04, 0xd4, 0x0e, 0xfa, 0x0a, 0x3e, 0x5f, 0xa0, 0xfb, 0xab, 0x44, 0x0f, 0x30, 0xb4, + 0x20, 0x6e, 0x46, 0x41, 0x1d, 0x5d, 0xc6, 0xab, 0x86, 0x8a, 0x84, 0xcf, 0x0b, 0xb0, 0x1d, 0x52, 0xea, 0x29, 0xd0, + 0x42, 0x25, 0x4a, 0x3f, 0x0c, 0x7c, 0x87, 0xc6, 0xc0, 0xd6, 0x3a, 0x40, 0x43, 0x3f, 0x63, 0x9a, 0x5a, 0x67, 0xa8, + 0x7c, 0xe6, 0xdd, 0x33, 0xa3, 0xe5, 0xcc, 0xa2, 0x31, 0xe8, 0xdb, 0x68, 0x8a, 0xe2, 0x9c, 0x7c, 0x16, 0x14, 0x71, + 0x9a, 0xc5, 0x39, 0xf8, 0x6d, 0xc6, 0x05, 0x66, 0x4c, 0xe2, 0x8a, 0x5f, 0xc8, 0x02, 0xb4, 0xdd, 0xb9, 0x4a, 0xad, + 0x6b, 0x10, 0x90, 0xfd, 0x00, 0x56, 0x2f, 0x0d, 0x1d, 0x95, 0xf3, 0xee, 0xd2, 0xa6, 0x10, 0xb1, 0x08, 0xc1, 0xa6, + 0x99, 0x2e, 0xd9, 0x71, 0xa8, 0xb4, 0x39, 0x10, 0xea, 0x08, 0x8d, 0xfb, 0xa7, 0x61, 0x6c, 0x35, 0xc5, 0xd6, 0xee, + 0x6d, 0xbb, 0xfd, 0x57, 0xe9, 0xa5, 0xd3, 0x9c, 0xf4, 0x18, 0xfb, 0x57, 0x19, 0x16, 0x23, 0xdb, 0x11, 0x02, 0x4b, + 0xce, 0xfb, 0xd4, 0x7f, 0x45, 0xcb, 0x79, 0x02, 0xa6, 0x23, 0x3a, 0x58, 0x2e, 0x50, 0x76, 0x0c, 0xe8, 0x0e, 0x0c, + 0xae, 0xe8, 0xf7, 0xc1, 0x2a, 0xc3, 0x5c, 0x48, 0x96, 0x24, 0x65, 0xf0, 0x3c, 0xf5, 0xe0, 0xe0, 0xd7, 0x4c, 0x99, + 0xbb, 0x28, 0xeb, 0xd3, 0x25, 0x99, 0xa6, 0xc8, 0x40, 0xac, 0xc3, 0x4d, 0x96, 0x46, 0x89, 0x12, 0x91, 0x2d, 0xd1, + 0x3f, 0xd2, 0x50, 0x2c, 0x1d, 0xb9, 0x17, 0xa9, 0x12, 0xa1, 0x62, 0x9e, 0xe2, 0x49, 0x9d, 0xd6, 0xe9, 0x08, 0x43, + 0x4f, 0x82, 0x52, 0xae, 0x86, 0x81, 0x2a, 0xa9, 0x5e, 0x0a, 0x9b, 0x62, 0xbb, 0xd5, 0x17, 0x2b, 0x31, 0x8f, 0x17, + 0xf8, 0x52, 0xe0, 0x28, 0xfe, 0x8b, 0x7b, 0x61, 0xa7, 0xd4, 0xf6, 0xa0, 0x76, 0x44, 0x09, 0xfd, 0x17, 0x87, 0x8b, + 0xc4, 0x77, 0x52, 0x87, 0x00, 0x44, 0x8b, 0x90, 0x53, 0x75, 0x90, 0x1a, 0x6e, 0x68, 0x47, 0xf8, 0x6f, 0xb8, 0x3e, + 0xe3, 0x8c, 0xde, 0x54, 0x33, 0x6a, 0x28, 0x5f, 0x0f, 0xda, 0x18, 0xf5, 0xd9, 0xc0, 0x61, 0x85, 0x28, 0xb4, 0x61, + 0x47, 0xa5, 0x12, 0x2d, 0x0c, 0xa5, 0xfa, 0x4b, 0xa8, 0x38, 0xe2, 0xce, 0x8c, 0xb2, 0x64, 0x7c, 0x5a, 0x1e, 0x8a, + 0xe9, 0x60, 0x50, 0x92, 0xca, 0x58, 0xe8, 0xc1, 0xf5, 0xc0, 0xf3, 0xef, 0x81, 0x5b, 0x88, 0x87, 0x8c, 0x2c, 0x86, + 0xdc, 0xe0, 0xe4, 0xb7, 0x38, 0xb9, 0x6a, 0x54, 0xaa, 0x38, 0xd6, 0x44, 0xb5, 0xe0, 0xfb, 0x32, 0x0c, 0xd0, 0x27, + 0x29, 0x00, 0x93, 0xc1, 0x94, 0xdf, 0x80, 0x44, 0xe9, 0x54, 0xdd, 0x90, 0x3e, 0x88, 0x82, 0x9f, 0xf3, 0x82, 0x8b, + 0xc4, 0x15, 0x60, 0x79, 0x07, 0xdb, 0xeb, 0xa8, 0xa2, 0x0a, 0x93, 0xd7, 0xf4, 0x38, 0xe2, 0xc6, 0xfb, 0xcf, 0xf4, + 0xd8, 0x62, 0xb6, 0x5a, 0xc7, 0x06, 0x9f, 0x39, 0x06, 0x17, 0x74, 0x2d, 0xb1, 0x35, 0x54, 0xc3, 0x8a, 0xc0, 0xc0, + 0x05, 0x1c, 0x84, 0x25, 0x8a, 0x63, 0x2b, 0x79, 0x45, 0x1a, 0x52, 0xda, 0x7b, 0x86, 0xa3, 0x4d, 0x72, 0x7c, 0x9b, + 0x65, 0x37, 0x81, 0xf3, 0x45, 0xe7, 0xa4, 0x99, 0xb0, 0x36, 0x78, 0x9f, 0x37, 0xe7, 0xd7, 0xdd, 0x43, 0x42, 0x55, + 0xdc, 0x1b, 0xde, 0x8e, 0x7b, 0xe3, 0x84, 0x5f, 0x73, 0xb1, 0xd0, 0xa1, 0x5a, 0xcc, 0x25, 0xcb, 0x6f, 0xad, 0x77, + 0x4b, 0x92, 0x5a, 0x01, 0xed, 0xb3, 0x2c, 0xa8, 0x89, 0x00, 0x90, 0x3f, 0xfc, 0x05, 0x42, 0x67, 0xf8, 0xdb, 0x63, + 0x70, 0x45, 0x0a, 0xef, 0x1c, 0x02, 0x61, 0x4d, 0x37, 0x77, 0x6a, 0x03, 0xbe, 0x18, 0xf7, 0x67, 0x4c, 0x3d, 0xfd, + 0x36, 0x93, 0xbb, 0xba, 0x6e, 0x8f, 0x2c, 0xc3, 0x47, 0xb8, 0x52, 0x00, 0x37, 0x13, 0xfe, 0x62, 0x98, 0x49, 0xf5, + 0x09, 0x60, 0xaa, 0xe9, 0xe0, 0x3e, 0x41, 0x60, 0x00, 0x95, 0x68, 0x31, 0xba, 0x52, 0x8e, 0x68, 0x06, 0x6e, 0x4d, + 0xb7, 0xc2, 0x78, 0xeb, 0x41, 0x0b, 0x3d, 0xd3, 0x70, 0xe2, 0x3f, 0x68, 0xe6, 0x55, 0x01, 0x01, 0xb4, 0x32, 0x82, + 0xb7, 0xd6, 0x47, 0x73, 0x84, 0xf8, 0x84, 0x25, 0xd1, 0x84, 0xc5, 0x33, 0xc5, 0x8f, 0x09, 0xdd, 0x34, 0xb5, 0x4d, + 0xef, 0x91, 0xfe, 0xe2, 0x9a, 0xf5, 0x53, 0x96, 0xb5, 0x6f, 0x0f, 0x15, 0x2f, 0xa6, 0xcd, 0x38, 0x88, 0x89, 0x2a, + 0xc6, 0xff, 0x82, 0xfb, 0x52, 0x2b, 0x40, 0x64, 0xee, 0xaa, 0xa7, 0xdf, 0x6f, 0x66, 0xcb, 0x81, 0x50, 0xf9, 0x9d, + 0x41, 0xd2, 0xa7, 0x43, 0xfb, 0x81, 0x4d, 0xa2, 0xb6, 0xd0, 0xf3, 0xc7, 0xa5, 0x6e, 0xe2, 0xe5, 0xb5, 0xa9, 0x11, + 0xad, 0x90, 0xa1, 0xb2, 0x75, 0xc0, 0xfa, 0xfe, 0x21, 0xdc, 0x5d, 0xd4, 0x34, 0xd4, 0xba, 0xe7, 0xae, 0x45, 0xc1, + 0x89, 0x3f, 0xc0, 0x58, 0x5c, 0x48, 0x6a, 0x1d, 0x8f, 0x49, 0x3f, 0x5a, 0xc8, 0xe4, 0x46, 0x5d, 0x9d, 0x9c, 0x29, + 0xe6, 0x09, 0x5c, 0x80, 0xcb, 0xb6, 0xbf, 0xa2, 0x52, 0x97, 0x72, 0x7b, 0x45, 0x69, 0x7a, 0x48, 0xdb, 0xab, 0x38, + 0x6f, 0x0b, 0x2e, 0xf8, 0x17, 0x0a, 0x2e, 0xac, 0x83, 0x75, 0xc7, 0x9d, 0xb2, 0x27, 0x3c, 0x51, 0xa6, 0xb5, 0xc1, + 0x5d, 0x37, 0x18, 0x13, 0x63, 0xbf, 0xbb, 0xe4, 0xc9, 0x47, 0x64, 0xc1, 0xbf, 0xcb, 0x04, 0x78, 0x26, 0xbb, 0x57, + 0x2a, 0xff, 0x0f, 0xfe, 0xd5, 0xd6, 0xbe, 0xb3, 0xe6, 0x9f, 0x9e, 0xf5, 0x70, 0xe7, 0x30, 0xf9, 0xb1, 0x3a, 0x03, + 0xba, 0xb9, 0x94, 0x29, 0x07, 0x64, 0x00, 0x6b, 0x91, 0x8c, 0x06, 0x7c, 0x68, 0x65, 0xd9, 0xf6, 0x9d, 0x56, 0x17, + 0x84, 0x3b, 0x09, 0xdc, 0xf4, 0xee, 0xda, 0xcc, 0xcc, 0xe9, 0x5a, 0x89, 0xa6, 0x4b, 0x63, 0x6b, 0x59, 0xaa, 0x30, + 0xde, 0xef, 0x3c, 0xc9, 0xa6, 0xf9, 0xe1, 0x72, 0x9a, 0x5b, 0xea, 0xb6, 0x71, 0xcb, 0x06, 0xd0, 0x10, 0xbb, 0xd6, + 0x56, 0x0e, 0x78, 0xb9, 0x3d, 0x88, 0xe6, 0x6b, 0x45, 0xe8, 0xa9, 0x12, 0xa1, 0x4f, 0xd3, 0x66, 0x1f, 0xec, 0xaa, + 0x5a, 0x37, 0x42, 0x1e, 0x0d, 0x52, 0xcd, 0xc8, 0xbf, 0xb9, 0xe2, 0xc5, 0x79, 0x2e, 0xaf, 0x01, 0x0e, 0x99, 0xd4, + 0x46, 0x61, 0x79, 0x09, 0xee, 0xfc, 0xe8, 0x38, 0xce, 0xc4, 0x28, 0xc7, 0xb8, 0xad, 0x88, 0x94, 0xac, 0x13, 0x67, + 0x80, 0x87, 0xec, 0x4f, 0x9a, 0x0e, 0xed, 0x5a, 0x60, 0x78, 0x5f, 0xe0, 0xae, 0x72, 0x76, 0xb4, 0xc9, 0xed, 0xa2, + 0x6f, 0xce, 0xb0, 0xee, 0x48, 0x69, 0x6d, 0x2c, 0xba, 0xee, 0x60, 0xad, 0x19, 0xb4, 0x45, 0x28, 0xf9, 0x90, 0x3b, + 0x69, 0x3f, 0x05, 0x34, 0x38, 0xcd, 0xd2, 0x1b, 0x6b, 0x95, 0xbf, 0xd1, 0x42, 0x9c, 0x28, 0xa6, 0x4e, 0x7c, 0x13, + 0x25, 0xfa, 0xfc, 0x4c, 0x8c, 0x1b, 0x08, 0xa4, 0xfe, 0x80, 0xf1, 0x35, 0x8a, 0x30, 0x81, 0xeb, 0x40, 0x14, 0xdb, + 0x13, 0xb5, 0xb1, 0x1c, 0x41, 0x27, 0x84, 0x78, 0x07, 0x65, 0x18, 0xab, 0x8b, 0x03, 0x6d, 0xb0, 0xf4, 0x75, 0x6b, + 0x9d, 0x1b, 0x42, 0x61, 0x9c, 0xc0, 0x14, 0x83, 0xa4, 0xce, 0x3a, 0xcb, 0x04, 0x55, 0x76, 0x4c, 0x3a, 0xef, 0x03, + 0x74, 0x77, 0x2d, 0x9a, 0xe2, 0xeb, 0xce, 0x1d, 0x74, 0x17, 0xd7, 0xaf, 0xb5, 0xc8, 0x0d, 0xfe, 0xbc, 0x25, 0xc2, + 0x22, 0x70, 0xd6, 0x9a, 0x7c, 0xd5, 0x08, 0x07, 0xa6, 0x24, 0xd3, 0xb0, 0x97, 0x2b, 0x9b, 0xee, 0xed, 0xb6, 0xd7, + 0xbb, 0x53, 0xc4, 0xd5, 0x63, 0xac, 0xf2, 0x6e, 0xe6, 0xf6, 0x4e, 0xb5, 0x16, 0xbb, 0x37, 0x6d, 0x3f, 0xc5, 0x8e, + 0x5a, 0x6b, 0xb7, 0x1b, 0x4e, 0xa8, 0x21, 0xdf, 0x8a, 0x2a, 0xad, 0x4e, 0x37, 0x06, 0xed, 0x10, 0xda, 0x5a, 0x64, + 0x70, 0xa3, 0x7c, 0xe6, 0x84, 0x4e, 0x2a, 0xe4, 0xaa, 0x53, 0x17, 0x6c, 0x2e, 0x79, 0xb5, 0x94, 0x69, 0x24, 0x28, + 0xda, 0x9c, 0x47, 0x25, 0x4d, 0xe4, 0x5a, 0x54, 0x91, 0xac, 0x51, 0x2f, 0x6a, 0x35, 0x06, 0x08, 0xc8, 0x74, 0xda, + 0xf4, 0xa0, 0x0a, 0x66, 0x43, 0x19, 0xc9, 0xe9, 0x0b, 0xb0, 0xb4, 0x47, 0x8e, 0xb5, 0xbe, 0xab, 0xce, 0x16, 0xdf, + 0xea, 0x09, 0xc1, 0x14, 0x66, 0x0f, 0x44, 0x84, 0x6b, 0x1a, 0x43, 0x4e, 0xbb, 0xc4, 0x65, 0x4d, 0xb7, 0x84, 0x3b, + 0xb8, 0x5d, 0xc9, 0x8e, 0xdc, 0x3c, 0x69, 0x6e, 0xae, 0x60, 0x47, 0xc5, 0x7c, 0x0c, 0xda, 0x2f, 0xa9, 0xae, 0x5d, + 0x9a, 0x5b, 0x8f, 0x07, 0x01, 0x0d, 0x06, 0x85, 0xe1, 0x5f, 0x27, 0xc6, 0xc3, 0x93, 0x06, 0x04, 0x49, 0xb9, 0x08, + 0xc7, 0xbe, 0x11, 0xfd, 0x64, 0x2a, 0x0f, 0x39, 0x5a, 0xbc, 0x43, 0xab, 0x73, 0x08, 0xe8, 0x25, 0x42, 0x49, 0x8c, + 0xaa, 0xd0, 0x88, 0xa0, 0x3c, 0x2d, 0x7f, 0xa9, 0xaa, 0x43, 0x40, 0x21, 0xed, 0x2b, 0x0a, 0x65, 0x9b, 0xc4, 0xd0, + 0x0c, 0xbf, 0x9c, 0x4f, 0x16, 0x7a, 0x06, 0x06, 0x72, 0x7e, 0xb0, 0xd0, 0xb3, 0x30, 0x90, 0xf3, 0x47, 0x8b, 0xda, + 0xad, 0x03, 0x4d, 0x40, 0x3c, 0x17, 0x8e, 0x4e, 0x4a, 0xab, 0xb2, 0x05, 0x74, 0x73, 0x1f, 0x41, 0xff, 0x97, 0x3d, + 0x04, 0x9d, 0x5c, 0x68, 0x47, 0x6e, 0x40, 0xdb, 0x21, 0x09, 0xec, 0x15, 0x93, 0x0a, 0x13, 0x8b, 0xe8, 0x90, 0x8d, + 0xc1, 0x10, 0x5b, 0x7d, 0x70, 0xc8, 0xc6, 0x53, 0x9f, 0x04, 0x01, 0xa3, 0xfb, 0x83, 0x01, 0x07, 0xbf, 0xc1, 0xab, + 0xf4, 0xd1, 0x46, 0xa0, 0x9b, 0xbe, 0xbb, 0x1b, 0x7a, 0x17, 0x57, 0x70, 0xaa, 0x76, 0xf7, 0x24, 0x74, 0x93, 0x69, + 0xc7, 0xea, 0x35, 0xc4, 0x0d, 0xf9, 0x95, 0xd1, 0x68, 0x64, 0x53, 0x42, 0x42, 0x0c, 0xe7, 0xd0, 0xcc, 0x69, 0xb9, + 0x7c, 0x75, 0xeb, 0xd9, 0x80, 0x0c, 0x33, 0xbd, 0x61, 0xb2, 0xbe, 0x87, 0xb2, 0xea, 0x31, 0xb4, 0x43, 0xef, 0x91, + 0xe3, 0xfb, 0x07, 0xdf, 0x64, 0xfc, 0xcc, 0xe1, 0xda, 0xc3, 0xb9, 0xf0, 0x5d, 0xd6, 0x8c, 0xcc, 0xa1, 0xf3, 0xec, + 0xe3, 0x78, 0x0f, 0xe3, 0xe4, 0xf3, 0x2c, 0x94, 0x37, 0x5e, 0xd3, 0xff, 0xa8, 0xf4, 0x66, 0x87, 0x43, 0x4e, 0x57, + 0xb0, 0xe2, 0x66, 0x55, 0x68, 0xf8, 0x59, 0xe4, 0x8d, 0x23, 0x5e, 0x93, 0xa8, 0xea, 0x3e, 0xef, 0x6d, 0xc4, 0xd2, + 0x8e, 0x71, 0x00, 0x70, 0xa2, 0x56, 0x0d, 0xbb, 0xd2, 0xb8, 0x56, 0x07, 0x31, 0x22, 0x25, 0x6c, 0x95, 0x38, 0x12, + 0xca, 0xdf, 0x00, 0x84, 0xc5, 0x50, 0x1c, 0x6f, 0x0d, 0xeb, 0x3d, 0xec, 0x87, 0x2e, 0xd0, 0x34, 0xa7, 0x54, 0x33, + 0x00, 0x48, 0x02, 0xfe, 0xe8, 0xe9, 0xa6, 0xa1, 0xb2, 0xcd, 0xf3, 0xd0, 0xb2, 0xba, 0x82, 0x7b, 0x7a, 0xea, 0x4a, + 0x06, 0xc6, 0x55, 0x1d, 0x7b, 0x9b, 0xbb, 0xdb, 0xa3, 0x55, 0xe4, 0x3b, 0x9b, 0xd4, 0x34, 0x0b, 0x20, 0x45, 0xe3, + 0xd2, 0x17, 0x7a, 0x3a, 0x01, 0x5a, 0xaf, 0x2d, 0x15, 0xed, 0xf7, 0x51, 0x8c, 0x1a, 0x17, 0x0a, 0xac, 0xc2, 0x04, + 0x85, 0x43, 0x84, 0x11, 0x42, 0x7f, 0x2e, 0xc3, 0x8d, 0x2f, 0xc8, 0x20, 0x1a, 0xae, 0x45, 0x87, 0x22, 0x72, 0xbc, + 0x68, 0x5b, 0xaa, 0x6a, 0x4e, 0x9a, 0xb6, 0x04, 0xde, 0x44, 0x06, 0x6c, 0xe7, 0x9f, 0x36, 0x44, 0xae, 0xc2, 0x05, + 0x0c, 0xdf, 0x11, 0xd7, 0x82, 0xe8, 0xa6, 0x36, 0xf5, 0x36, 0xec, 0x10, 0x1d, 0x4d, 0xf1, 0xe8, 0x90, 0x7b, 0xee, + 0x9e, 0xdb, 0x22, 0xbe, 0xfe, 0x0c, 0xb9, 0x6b, 0x3a, 0x7b, 0x29, 0xc2, 0xa0, 0x6e, 0xd9, 0x40, 0xb1, 0x0e, 0x9d, + 0xa0, 0x00, 0x03, 0xb8, 0x7c, 0x02, 0x3a, 0x36, 0x18, 0x54, 0x04, 0x9f, 0x14, 0xb6, 0x4d, 0x83, 0xfc, 0x11, 0xef, + 0x86, 0x0e, 0xaf, 0x2d, 0x79, 0x20, 0x5e, 0x61, 0x9f, 0x29, 0xe1, 0xee, 0x05, 0x05, 0xdd, 0x51, 0x5e, 0xae, 0x0a, + 0x57, 0xa5, 0x01, 0xa8, 0xb2, 0xe3, 0xb9, 0xd6, 0x94, 0xb4, 0x80, 0x95, 0x92, 0xba, 0xf3, 0x9b, 0xe0, 0xb8, 0x25, + 0x53, 0xe1, 0x5b, 0x75, 0xa3, 0xca, 0x43, 0x89, 0x22, 0x1d, 0x7b, 0xb6, 0x73, 0xb0, 0x06, 0xc0, 0x53, 0xd8, 0x5e, + 0x9c, 0x09, 0xf8, 0xdc, 0x69, 0x97, 0x2d, 0x73, 0x09, 0x14, 0xf5, 0xfd, 0x38, 0x2f, 0x3b, 0xbe, 0xdc, 0x1d, 0x6d, + 0xef, 0xa1, 0x37, 0x62, 0x63, 0xbc, 0xbe, 0x8c, 0x9a, 0x7e, 0xf1, 0x0c, 0x57, 0x96, 0x82, 0xdc, 0xd3, 0x54, 0x8f, + 0x30, 0x3a, 0x04, 0xa6, 0x29, 0x3f, 0x62, 0xe3, 0xe9, 0x70, 0x68, 0xc8, 0xa0, 0xd7, 0x4c, 0x0c, 0x05, 0xf6, 0x05, + 0xb4, 0xce, 0x4c, 0x5c, 0xe3, 0xd3, 0xf6, 0x15, 0xb4, 0xba, 0x41, 0x99, 0xdc, 0x29, 0x18, 0x3e, 0xd0, 0x92, 0x29, + 0x98, 0x2a, 0xbc, 0x21, 0x52, 0xc9, 0x3e, 0x2d, 0xad, 0xc3, 0xbe, 0x5d, 0x28, 0xb4, 0xd0, 0xc4, 0xaf, 0x32, 0xc4, + 0x4f, 0x5d, 0x67, 0xfe, 0x6d, 0xda, 0xa7, 0x06, 0xb1, 0x70, 0x24, 0x06, 0x11, 0xbf, 0x38, 0x55, 0xb6, 0x13, 0x42, + 0xc5, 0xc6, 0x43, 0xd7, 0xba, 0x71, 0x24, 0x55, 0x18, 0x4a, 0xa1, 0xf1, 0xd4, 0x70, 0xdf, 0x0b, 0x1d, 0xbe, 0x0e, + 0xb3, 0xb8, 0xcd, 0x1a, 0x49, 0x8d, 0x71, 0x2a, 0x4c, 0x9c, 0x4a, 0xb9, 0x8a, 0x04, 0x06, 0xca, 0xb3, 0x85, 0x41, + 0x80, 0x49, 0x4c, 0x32, 0xb6, 0x16, 0xc2, 0x84, 0xb1, 0x73, 0x85, 0x69, 0xea, 0x22, 0xf5, 0x9b, 0x81, 0xc9, 0x82, + 0x86, 0xfc, 0x1e, 0x8d, 0xd6, 0x54, 0x4d, 0x01, 0x86, 0x71, 0x94, 0x6a, 0xfc, 0x5b, 0x84, 0xda, 0x0c, 0x03, 0x00, + 0xdb, 0xbc, 0x95, 0x99, 0xa8, 0x5e, 0x0a, 0x84, 0x40, 0x73, 0xf6, 0x53, 0x71, 0xb5, 0x33, 0x0b, 0x46, 0xd1, 0x6e, + 0xaf, 0x7c, 0x3e, 0x70, 0x42, 0x79, 0xac, 0x2e, 0x50, 0x2f, 0x64, 0xf1, 0x4a, 0xa6, 0xbc, 0x15, 0x22, 0x73, 0x4f, + 0xb2, 0x9f, 0xf2, 0x11, 0x9c, 0x57, 0xe8, 0x54, 0x6e, 0xb6, 0x89, 0x32, 0x4b, 0x92, 0x8c, 0x05, 0xc6, 0xe6, 0x25, + 0x98, 0x49, 0xcd, 0x8c, 0xe1, 0xd7, 0x10, 0x67, 0x6c, 0xe7, 0x24, 0xdc, 0xdc, 0xcd, 0x03, 0x43, 0x94, 0x72, 0xd1, + 0x12, 0x0d, 0x5b, 0x3b, 0x5e, 0x4f, 0xae, 0x09, 0xf7, 0x61, 0x23, 0xd6, 0x64, 0x8c, 0x71, 0x6d, 0x6e, 0x64, 0xfd, + 0x68, 0x81, 0x07, 0x63, 0xca, 0xfa, 0x13, 0xc8, 0xb4, 0x92, 0xb2, 0xce, 0x17, 0x46, 0xcc, 0xa4, 0x12, 0xbd, 0xdb, + 0x37, 0x3e, 0xab, 0xbb, 0x88, 0xfa, 0xad, 0xfd, 0x9e, 0xd4, 0xc3, 0xad, 0xff, 0xa0, 0xb0, 0x06, 0x95, 0x11, 0x97, + 0x11, 0xe5, 0x99, 0x03, 0xdd, 0x34, 0x29, 0xe2, 0xf4, 0x74, 0x15, 0x17, 0x25, 0x4f, 0xa1, 0x52, 0x4d, 0xdd, 0xa2, + 0xde, 0x04, 0xec, 0x0d, 0x91, 0x24, 0x59, 0x4b, 0x63, 0x2b, 0x76, 0x69, 0x90, 0x9e, 0x3b, 0x23, 0x2e, 0xbd, 0xa8, + 0xd0, 0x90, 0x96, 0x7a, 0x67, 0xa1, 0x92, 0xf9, 0x2b, 0xfe, 0x33, 0xa8, 0x15, 0xe8, 0x68, 0x93, 0x62, 0x3c, 0x05, + 0x46, 0x7c, 0x37, 0x98, 0xd5, 0x3d, 0xc4, 0x45, 0x13, 0x94, 0x7a, 0x47, 0xec, 0xf8, 0xb9, 0xc9, 0xc3, 0xbb, 0x90, + 0x73, 0x06, 0x9f, 0xde, 0xcf, 0x12, 0xb5, 0xd6, 0x91, 0x18, 0xa9, 0x19, 0x40, 0xd3, 0x41, 0x99, 0xf3, 0x58, 0x04, + 0xb3, 0x9e, 0x49, 0x8c, 0x7a, 0x5c, 0xff, 0x02, 0x0d, 0xb5, 0xdf, 0xac, 0x2c, 0xcf, 0xaa, 0xdb, 0x2f, 0xe1, 0xc0, + 0xa6, 0xb6, 0x82, 0x1e, 0xaf, 0x2b, 0x79, 0x71, 0xa1, 0xba, 0xed, 0x17, 0x62, 0xe4, 0x74, 0x8d, 0x6b, 0xe9, 0xbc, + 0x5a, 0xb0, 0x5e, 0x77, 0xba, 0x59, 0xdc, 0xcd, 0x32, 0x1a, 0x08, 0x6b, 0x3b, 0x9f, 0x68, 0xfe, 0xac, 0xd9, 0x76, + 0x1f, 0x6f, 0x41, 0xcc, 0x02, 0x80, 0x48, 0x0f, 0xa2, 0x60, 0x99, 0xa5, 0x3c, 0xa0, 0xf2, 0x2e, 0x8e, 0xb2, 0x50, + 0x7a, 0x39, 0xcb, 0xf8, 0x69, 0xd3, 0x58, 0xeb, 0xac, 0x50, 0x86, 0xd6, 0x46, 0x77, 0xba, 0xca, 0x10, 0xdb, 0x4f, + 0xe2, 0x6c, 0x01, 0xee, 0x8f, 0x19, 0x0a, 0x0d, 0x9d, 0x65, 0xa4, 0x89, 0x86, 0xef, 0xba, 0x63, 0x90, 0x51, 0x9c, + 0xac, 0xf3, 0x4a, 0xba, 0xd1, 0x67, 0x6d, 0x24, 0xcc, 0x3d, 0x44, 0xbf, 0x8a, 0xc1, 0xa3, 0xdc, 0xe7, 0xb5, 0xd1, + 0xc9, 0xb4, 0x8c, 0xb4, 0x3b, 0x3f, 0xa9, 0x97, 0x59, 0xaa, 0x75, 0xd8, 0x3e, 0xc3, 0xde, 0x1a, 0x93, 0xde, 0x84, + 0xd4, 0x30, 0x12, 0x9f, 0xcf, 0xa8, 0x11, 0x02, 0xda, 0x72, 0xfc, 0x1d, 0x3e, 0xc3, 0xd0, 0x14, 0x58, 0xaa, 0xb8, + 0x85, 0xdd, 0xf0, 0x35, 0x9f, 0xac, 0x5a, 0x00, 0x82, 0x59, 0xf9, 0x7a, 0x17, 0xaf, 0x84, 0xfa, 0x54, 0x9b, 0x01, + 0x20, 0x0b, 0x4a, 0xb9, 0xe3, 0xa7, 0x54, 0x3a, 0x58, 0xa2, 0x68, 0x7b, 0x39, 0x7d, 0xa3, 0x63, 0xe3, 0xfb, 0xf4, + 0x5c, 0xc0, 0x76, 0x21, 0xbf, 0x75, 0xa7, 0x5e, 0xa2, 0x22, 0xb5, 0x6d, 0xd6, 0x3d, 0x7c, 0xb9, 0x41, 0x93, 0x30, + 0x82, 0x32, 0x65, 0x0a, 0x60, 0x70, 0x53, 0x8d, 0x82, 0x49, 0xab, 0x91, 0xb0, 0xa5, 0x9e, 0x64, 0xb9, 0xe9, 0x83, + 0x53, 0xdd, 0x21, 0xe8, 0xb9, 0x51, 0xce, 0x17, 0x2d, 0xfb, 0xb5, 0x82, 0xa3, 0x93, 0xab, 0x21, 0x6a, 0xe6, 0xbd, + 0xb6, 0x23, 0x43, 0xca, 0x65, 0x18, 0x08, 0xa6, 0x1c, 0xf3, 0xf4, 0xd8, 0x7a, 0x46, 0x44, 0xf7, 0x9c, 0x7d, 0xa6, + 0x5b, 0x75, 0x25, 0x01, 0xd1, 0xf1, 0x9b, 0xc7, 0x2f, 0x2f, 0xe3, 0x0b, 0x83, 0xa2, 0xd4, 0xb0, 0x88, 0x51, 0xa6, + 0x7d, 0x95, 0x84, 0xc1, 0xfb, 0xf0, 0xee, 0x27, 0x95, 0xa5, 0xf6, 0x7b, 0xb0, 0xb1, 0xa2, 0xaa, 0x0f, 0x25, 0x2f, + 0x9a, 0x02, 0xac, 0xbb, 0x2c, 0x51, 0x20, 0xf7, 0x3b, 0x9b, 0x66, 0xbe, 0x89, 0x1a, 0x37, 0x1b, 0xd6, 0x1b, 0xd7, + 0xed, 0x52, 0x5b, 0xb2, 0x23, 0x2b, 0x91, 0x33, 0x8b, 0xc1, 0x8c, 0x1f, 0x15, 0x06, 0xa5, 0x61, 0x83, 0xaa, 0x54, + 0xfc, 0xde, 0x88, 0xe0, 0xd4, 0xb1, 0xaa, 0x30, 0xa6, 0x01, 0xb3, 0xad, 0xa8, 0x35, 0xa8, 0x83, 0x52, 0xda, 0x9a, + 0x80, 0x6c, 0xbf, 0xb1, 0x82, 0x9a, 0xdf, 0xbf, 0x1b, 0x43, 0xbe, 0xa6, 0x14, 0x54, 0x12, 0xb0, 0x33, 0x68, 0xf4, + 0x54, 0x09, 0x03, 0x29, 0x08, 0x9e, 0x00, 0xe5, 0x8b, 0xa8, 0xb1, 0xda, 0xed, 0xab, 0x53, 0x63, 0xb4, 0x05, 0x84, + 0x16, 0xd2, 0xa3, 0xcb, 0x3e, 0x6e, 0x63, 0x1d, 0x48, 0x3c, 0x38, 0xc1, 0x76, 0xae, 0xae, 0xd1, 0x48, 0x68, 0x7e, + 0xdf, 0x68, 0xc0, 0x6b, 0x5a, 0x81, 0x42, 0x3d, 0xc7, 0xd1, 0xd0, 0xd9, 0x21, 0x05, 0x11, 0x1b, 0xb4, 0xb0, 0xef, + 0x8e, 0x0f, 0xcd, 0xbe, 0x9e, 0x27, 0x0b, 0x52, 0x53, 0xe9, 0x3e, 0x77, 0x4b, 0xc8, 0x5a, 0x75, 0x28, 0x2b, 0x0f, + 0x70, 0xbc, 0x50, 0x32, 0x7f, 0x87, 0x49, 0x8d, 0xd2, 0x98, 0xd0, 0x18, 0xb1, 0x80, 0x25, 0x41, 0x7b, 0x3d, 0x50, + 0xbf, 0x0c, 0x42, 0x85, 0x33, 0x3d, 0x91, 0xf8, 0x94, 0x72, 0xf5, 0x69, 0x41, 0xea, 0x69, 0xc1, 0x1c, 0xe8, 0xa5, + 0x6f, 0xe5, 0x57, 0x36, 0x3e, 0xda, 0xdd, 0xbb, 0xe6, 0xc2, 0x3a, 0x86, 0xb8, 0xd8, 0xc2, 0x6f, 0x4e, 0x4d, 0x01, + 0xd8, 0xf0, 0x58, 0x97, 0xe5, 0x1b, 0x35, 0x91, 0x59, 0x1c, 0x92, 0x08, 0x24, 0xdb, 0xcd, 0xcd, 0x6d, 0x04, 0xdb, + 0xde, 0x42, 0x6d, 0xa8, 0xbf, 0xbc, 0xed, 0x7e, 0xc7, 0xf0, 0x72, 0x4f, 0xee, 0xdd, 0xb4, 0xa1, 0xfc, 0xe1, 0xee, + 0x55, 0xf2, 0x7f, 0x55, 0xc9, 0xdd, 0x56, 0x99, 0x75, 0x5b, 0xbc, 0xdf, 0x75, 0xdc, 0x72, 0x8c, 0x06, 0x81, 0x35, + 0x05, 0x06, 0xd2, 0x93, 0xc6, 0x34, 0xd1, 0xd1, 0x95, 0x19, 0x33, 0x78, 0x74, 0x01, 0x9a, 0xc3, 0x74, 0x9e, 0xc7, + 0x00, 0x1c, 0xe0, 0x1f, 0x79, 0x84, 0xfa, 0xa7, 0xf3, 0x3c, 0x38, 0x0d, 0x06, 0xe5, 0x20, 0xd0, 0x9f, 0xb8, 0xe6, + 0x04, 0x0b, 0xd0, 0xb9, 0xc5, 0x0c, 0xe2, 0x4e, 0x5a, 0x33, 0x87, 0xf8, 0x30, 0x99, 0x0e, 0x06, 0x31, 0xd9, 0x00, + 0x48, 0x5f, 0xbc, 0xb0, 0xce, 0x41, 0x85, 0x5e, 0x90, 0xad, 0xba, 0x8b, 0x66, 0xc5, 0x5e, 0xb5, 0xd3, 0xbc, 0xdf, + 0xcf, 0xe7, 0xe5, 0x20, 0x68, 0x54, 0x58, 0x18, 0xef, 0x3f, 0xda, 0xfc, 0xd2, 0xe8, 0xa4, 0x09, 0x46, 0xac, 0x3d, + 0x46, 0xf5, 0x8a, 0xa7, 0x19, 0x6d, 0xdc, 0x8e, 0x95, 0xf2, 0x05, 0x44, 0xf1, 0xc0, 0x90, 0xb5, 0xf2, 0xee, 0x1c, + 0xbc, 0x2e, 0x37, 0xde, 0x1c, 0x51, 0x80, 0xdd, 0x14, 0xc6, 0x49, 0xcd, 0x45, 0x17, 0x35, 0xf1, 0x0c, 0x76, 0xba, + 0x7a, 0x2b, 0xd1, 0x6a, 0xbc, 0x17, 0xef, 0x9a, 0x8d, 0xbf, 0x96, 0x7b, 0xba, 0xcc, 0xbd, 0x73, 0x40, 0x9c, 0xdd, + 0x8b, 0xab, 0x3d, 0x2c, 0x75, 0x2f, 0x18, 0x58, 0xe4, 0x90, 0x76, 0xb5, 0x7a, 0x28, 0x22, 0x75, 0x1e, 0x83, 0x01, + 0x93, 0x69, 0x48, 0x4d, 0xa6, 0xbd, 0x58, 0x41, 0xda, 0x58, 0x6b, 0x01, 0x6d, 0x38, 0x2c, 0x76, 0xec, 0x86, 0xdd, + 0xe9, 0xd6, 0xa1, 0x50, 0xc2, 0x40, 0xd6, 0x75, 0xf3, 0x50, 0x6b, 0x78, 0x22, 0xe8, 0x41, 0x35, 0xda, 0x4f, 0x0f, + 0xe5, 0x49, 0x7b, 0x2c, 0xc0, 0x45, 0x0f, 0x5f, 0x3e, 0x17, 0x78, 0xd1, 0xde, 0x41, 0x9e, 0x33, 0x9f, 0x2a, 0x1f, + 0xc4, 0x86, 0x5b, 0x86, 0x0f, 0xed, 0xe3, 0x5b, 0x81, 0x4c, 0xea, 0x8e, 0xa6, 0xb6, 0x76, 0x47, 0xe3, 0x98, 0x40, + 0xbf, 0x29, 0x47, 0x29, 0x13, 0x53, 0xcb, 0x92, 0x1d, 0xf5, 0x72, 0xe5, 0x0d, 0x95, 0xb2, 0xa3, 0x65, 0x9b, 0xf3, + 0x4b, 0x1b, 0x09, 0xfd, 0xbe, 0x76, 0x07, 0xc2, 0x37, 0x6a, 0xbd, 0x21, 0x2f, 0x1b, 0x22, 0x96, 0x43, 0xcc, 0xc0, + 0xf1, 0x42, 0x2a, 0xd7, 0xee, 0xa2, 0xa9, 0xaa, 0xdb, 0xd9, 0xca, 0x05, 0x2d, 0xf1, 0x56, 0x0a, 0xac, 0x22, 0x75, + 0x7a, 0x3d, 0x95, 0x78, 0xd7, 0x47, 0xb1, 0xfd, 0x08, 0xd8, 0xc6, 0xc6, 0xd1, 0xd8, 0xb8, 0x45, 0x6c, 0xf0, 0x55, + 0x54, 0xd1, 0x82, 0x03, 0x04, 0x77, 0x5b, 0x52, 0x4b, 0x33, 0x87, 0xb8, 0xaf, 0x78, 0x80, 0xf6, 0x5d, 0x1c, 0x71, + 0x2a, 0xc0, 0xb6, 0xae, 0x75, 0xce, 0x6a, 0x39, 0x60, 0x33, 0xd1, 0xf3, 0x4f, 0xab, 0x46, 0x22, 0x86, 0x55, 0x36, + 0x52, 0x56, 0x68, 0xf7, 0x4a, 0x97, 0x70, 0xf1, 0x05, 0x78, 0xd9, 0xbe, 0x5b, 0xd9, 0x7d, 0xba, 0xc4, 0xfe, 0x61, + 0x5e, 0x35, 0xc1, 0x23, 0xaf, 0xf1, 0xf6, 0x1e, 0x26, 0xbe, 0x54, 0x0a, 0xe1, 0x55, 0x4a, 0x43, 0x09, 0xc0, 0x20, + 0x09, 0x6a, 0xb8, 0xd2, 0xb6, 0x19, 0xa4, 0x32, 0x86, 0xdd, 0xad, 0xde, 0xea, 0xff, 0xb4, 0x0a, 0x17, 0x95, 0x2c, + 0xc6, 0x24, 0xd0, 0x39, 0xd5, 0x72, 0x13, 0x58, 0xf0, 0x74, 0x97, 0x1c, 0x81, 0xc2, 0x4e, 0x00, 0x37, 0x94, 0xb0, + 0xdf, 0xf1, 0x36, 0x94, 0xb3, 0xd7, 0x56, 0xf2, 0xe4, 0xf6, 0x25, 0x15, 0x34, 0x21, 0x53, 0x61, 0xf7, 0x6f, 0x6b, + 0xc3, 0xbe, 0x0c, 0xe5, 0x48, 0x0a, 0x5c, 0x1c, 0x74, 0x0e, 0x60, 0x7f, 0x90, 0xcb, 0xd8, 0x7c, 0x26, 0xfd, 0xbe, + 0x7a, 0xff, 0x34, 0xcf, 0x92, 0x8f, 0x3b, 0xef, 0x0d, 0x4f, 0xb3, 0x64, 0x40, 0x25, 0x62, 0x6a, 0x5d, 0x15, 0xc3, + 0xa5, 0x76, 0x31, 0x6e, 0x90, 0x8c, 0xf8, 0x4e, 0xea, 0x10, 0x23, 0xc6, 0x17, 0xd9, 0x21, 0x29, 0x39, 0x5d, 0xd6, + 0x9d, 0x3d, 0xd7, 0xa2, 0x19, 0x34, 0x86, 0xdb, 0xf1, 0x5e, 0xd2, 0x2b, 0x40, 0x05, 0x88, 0xee, 0x59, 0xe0, 0x1a, + 0xde, 0x5c, 0x12, 0x8d, 0x2d, 0x3d, 0x6d, 0x89, 0x06, 0xee, 0x94, 0x09, 0x49, 0xb5, 0x71, 0x80, 0x45, 0xac, 0xeb, + 0x8f, 0x61, 0x01, 0x40, 0xad, 0x06, 0xe9, 0x95, 0xbe, 0x20, 0x54, 0x25, 0x21, 0x18, 0x9d, 0x48, 0x78, 0x19, 0xd0, + 0x38, 0x33, 0x89, 0x16, 0x36, 0x38, 0xa0, 0x2f, 0x2b, 0x93, 0x68, 0x6c, 0xc8, 0x03, 0xca, 0x6d, 0x1a, 0xc0, 0xe0, + 0x83, 0x24, 0x89, 0xbe, 0x5f, 0x9a, 0x24, 0x10, 0x94, 0xa0, 0x7c, 0x83, 0xfe, 0x51, 0x7a, 0x3e, 0x96, 0x3f, 0x7a, + 0x87, 0xd2, 0x0f, 0x61, 0x01, 0x32, 0x45, 0x5d, 0x31, 0xcd, 0xd8, 0x51, 0xd6, 0x6d, 0x4c, 0xe2, 0x79, 0xda, 0x5d, + 0x15, 0xca, 0xa5, 0x0b, 0xfc, 0xca, 0x32, 0xc4, 0xb1, 0x7e, 0x1a, 0xaf, 0xd8, 0x71, 0xc8, 0x35, 0x5e, 0xfa, 0xd3, + 0x78, 0x85, 0x33, 0x44, 0xab, 0x56, 0x02, 0x51, 0xfe, 0xab, 0x36, 0x70, 0x88, 0xfb, 0x04, 0x83, 0x5c, 0x54, 0xde, + 0x03, 0x81, 0xbc, 0xad, 0x20, 0x22, 0xcd, 0xec, 0x3a, 0x8c, 0x48, 0xb5, 0x93, 0x64, 0xbe, 0xfc, 0x51, 0x66, 0xc2, + 0xfb, 0x06, 0x1e, 0x9b, 0xcd, 0xb2, 0x29, 0xe6, 0x0b, 0x15, 0xcc, 0xc1, 0x7d, 0xa2, 0xe2, 0x52, 0x54, 0xfe, 0x13, + 0x76, 0xc1, 0x8b, 0xf1, 0xe0, 0xf5, 0x1a, 0x01, 0xf6, 0x2b, 0xff, 0xc9, 0x1b, 0xb3, 0xbf, 0xac, 0x1b, 0x5f, 0x66, + 0x22, 0x3e, 0xf0, 0xd1, 0x0d, 0xe5, 0xa3, 0x5b, 0x2f, 0xd3, 0x77, 0x0d, 0x28, 0x91, 0x51, 0x59, 0xf1, 0xd5, 0x8a, + 0xa7, 0xb3, 0xab, 0x24, 0xca, 0x46, 0x15, 0x17, 0x30, 0xbd, 0xe0, 0x78, 0x97, 0xac, 0xcf, 0xb2, 0xe4, 0x25, 0xc4, + 0x1e, 0x58, 0x49, 0x85, 0xc5, 0x0f, 0xcb, 0x4c, 0x2d, 0x66, 0x21, 0x2b, 0x29, 0x78, 0x30, 0xbb, 0x4e, 0xa2, 0xbf, + 0x96, 0x1e, 0x92, 0x9a, 0x99, 0xb2, 0x4d, 0xed, 0x08, 0xb5, 0xf1, 0x75, 0xa4, 0x1b, 0x6d, 0x01, 0x00, 0xf7, 0x6c, + 0x91, 0x46, 0x92, 0x89, 0xe1, 0xa4, 0x66, 0xdc, 0xa4, 0x17, 0x98, 0x1a, 0xd7, 0xac, 0xa2, 0x89, 0xb3, 0x90, 0x01, + 0xbd, 0x3f, 0xcd, 0xf5, 0x73, 0x06, 0xf7, 0x1f, 0xb4, 0x06, 0x2e, 0x0f, 0x8b, 0x7e, 0x5f, 0x1e, 0x16, 0xdb, 0x6d, + 0x79, 0x14, 0xf7, 0xfb, 0xf2, 0x28, 0x36, 0xfc, 0x83, 0x52, 0x6c, 0x1b, 0x73, 0x83, 0x84, 0xe6, 0x12, 0xa2, 0x16, + 0x8d, 0xe0, 0x0f, 0xcd, 0x72, 0x2e, 0xa2, 0xfc, 0x30, 0xe9, 0xf7, 0x7b, 0xcb, 0x99, 0x18, 0xe4, 0xc3, 0x24, 0xca, + 0x87, 0x89, 0xe7, 0x84, 0xf8, 0x8b, 0xe7, 0x84, 0xa8, 0x68, 0xe0, 0x0a, 0xce, 0x0c, 0x40, 0x14, 0xf0, 0xe9, 0x1f, + 0xd5, 0xb5, 0x14, 0xba, 0x96, 0x58, 0xd5, 0x92, 0xe8, 0x0a, 0x6a, 0x76, 0x5d, 0x84, 0x25, 0x96, 0x42, 0x97, 0xec, + 0xbb, 0x25, 0xf0, 0x44, 0x39, 0xaf, 0x36, 0xc0, 0xc0, 0x46, 0x78, 0xe7, 0x30, 0xe1, 0x24, 0xd6, 0x35, 0xa0, 0x9d, + 0x6e, 0x6a, 0x7a, 0x4e, 0x57, 0xf4, 0x02, 0xf9, 0xd9, 0x73, 0x30, 0x58, 0x3a, 0x64, 0xf9, 0x74, 0x30, 0x38, 0x27, + 0x2b, 0x56, 0xce, 0xc3, 0x78, 0x10, 0xae, 0x67, 0xf9, 0xf0, 0x3c, 0x3a, 0x27, 0xe4, 0xab, 0x62, 0x41, 0x7b, 0xab, + 0x51, 0xf9, 0x31, 0x83, 0xf0, 0x7e, 0xe9, 0x2c, 0xcc, 0x4c, 0x9c, 0x8f, 0xd5, 0xe8, 0x86, 0xae, 0x20, 0x7e, 0x0d, + 0xdc, 0x48, 0x48, 0x04, 0x1d, 0xb9, 0xa0, 0x2b, 0xba, 0xa6, 0xd2, 0xcc, 0x30, 0x46, 0xeb, 0xb6, 0xc7, 0x49, 0x02, + 0x8e, 0xc9, 0xae, 0xf8, 0x68, 0xac, 0x0a, 0xef, 0xfa, 0x8e, 0xd0, 0x5e, 0x2f, 0x71, 0x83, 0xf4, 0x43, 0x7b, 0x90, + 0x80, 0x11, 0x19, 0xa9, 0x81, 0x32, 0x23, 0x23, 0xa9, 0x99, 0x54, 0x1c, 0x92, 0xd8, 0x1f, 0x12, 0x35, 0x0e, 0x89, + 0x3f, 0x0e, 0xb9, 0x1e, 0x07, 0xe4, 0xee, 0x97, 0x6c, 0x4c, 0x53, 0x36, 0xa6, 0x6b, 0x35, 0x2a, 0xf4, 0x92, 0x9e, + 0x69, 0xea, 0x78, 0xca, 0x5e, 0xc1, 0x81, 0x3d, 0x08, 0xf3, 0x59, 0x3c, 0x7c, 0x15, 0xbd, 0x22, 0xe4, 0x2b, 0x49, + 0xaf, 0xd4, 0xa5, 0x0c, 0x02, 0x21, 0x5e, 0x82, 0x73, 0xa9, 0x0b, 0x75, 0x72, 0x69, 0x76, 0x1c, 0x3e, 0x5d, 0x34, + 0x9e, 0xce, 0x20, 0xa2, 0x0f, 0x5a, 0xa9, 0xf4, 0xfb, 0xe1, 0x39, 0x2b, 0xe7, 0xa7, 0xe1, 0x98, 0x00, 0x0e, 0x8f, + 0x1e, 0xce, 0xf3, 0xd1, 0x0d, 0x3d, 0x1f, 0xdd, 0x12, 0xb0, 0xf0, 0x1a, 0x4f, 0xd7, 0x87, 0x2c, 0x9e, 0x0e, 0x06, + 0x6b, 0xa4, 0xea, 0x2a, 0xf7, 0x9a, 0x2c, 0xe8, 0x39, 0x4e, 0x04, 0x01, 0x86, 0x3e, 0x13, 0x6b, 0x43, 0xc3, 0x5f, + 0x31, 0xf8, 0xf8, 0x96, 0x9d, 0x8f, 0x6e, 0xe9, 0x0d, 0x7b, 0xb5, 0x1d, 0x4f, 0x81, 0x99, 0x5a, 0xcd, 0xc2, 0xdb, + 0xc3, 0x8b, 0xd9, 0x05, 0xbb, 0x8d, 0x6e, 0x8f, 0xa0, 0xa1, 0x97, 0xec, 0x16, 0x01, 0x97, 0xd2, 0x87, 0xcb, 0xc1, + 0x2b, 0xb2, 0x3f, 0x18, 0xa4, 0x24, 0x0a, 0xaf, 0x42, 0xaf, 0x95, 0xaf, 0xe8, 0x2d, 0xa1, 0x2b, 0x76, 0x83, 0xa3, + 0x71, 0xc1, 0xf0, 0x83, 0x33, 0x76, 0x5b, 0x5f, 0x85, 0xde, 0x6e, 0x4e, 0x44, 0x27, 0x88, 0x11, 0xfa, 0x1a, 0x38, + 0x9a, 0xe5, 0xc2, 0x4c, 0xc0, 0x93, 0xb9, 0xc8, 0x68, 0x51, 0x68, 0x06, 0xe2, 0xac, 0x04, 0xc4, 0x92, 0xa8, 0xfb, + 0xcd, 0x46, 0xa7, 0xb0, 0x9c, 0xfb, 0xfd, 0x5e, 0x65, 0xe8, 0x01, 0x22, 0x67, 0x76, 0xd2, 0x83, 0x9e, 0x4f, 0x0f, + 0xf0, 0x13, 0xbd, 0x6a, 0x10, 0x27, 0xf3, 0x87, 0x65, 0xf4, 0x8b, 0x47, 0x1f, 0x3e, 0x74, 0x53, 0x9e, 0x32, 0xff, + 0xf7, 0x29, 0x8f, 0xcc, 0xa3, 0x57, 0x95, 0x07, 0x82, 0xe7, 0xad, 0x49, 0xa5, 0x91, 0xa8, 0x46, 0xa7, 0xab, 0x18, + 0xb4, 0x91, 0xa8, 0x6d, 0xd0, 0x4f, 0x68, 0x61, 0x05, 0x11, 0x72, 0x0e, 0x9e, 0x81, 0x41, 0x2a, 0x84, 0xca, 0x51, + 0x8b, 0x12, 0x0d, 0x41, 0x72, 0x59, 0x72, 0x15, 0x3e, 0x87, 0x50, 0x75, 0xfa, 0x38, 0x13, 0x61, 0x43, 0x8f, 0x43, + 0x1f, 0x00, 0xfe, 0xf7, 0x1d, 0x72, 0x51, 0xf2, 0x0b, 0x3c, 0x9b, 0xdb, 0x04, 0xa3, 0x60, 0x89, 0x68, 0x86, 0xb6, + 0x41, 0xec, 0xc7, 0x92, 0x60, 0x3d, 0x92, 0xc6, 0xa3, 0xd2, 0x1c, 0x11, 0x7e, 0x14, 0x1f, 0x45, 0x4f, 0x63, 0x43, + 0x22, 0x39, 0x92, 0x48, 0x3e, 0x00, 0xc2, 0x49, 0xd0, 0x5f, 0xdc, 0x35, 0xd9, 0xb5, 0x90, 0x18, 0xf4, 0xa7, 0x25, + 0xd3, 0xb2, 0x7b, 0xd5, 0x63, 0x5f, 0x11, 0xe4, 0x8e, 0xe9, 0xdf, 0xbc, 0x3e, 0xfc, 0xbd, 0xc4, 0x19, 0xb4, 0x9e, + 0x2f, 0xaa, 0x33, 0x33, 0x6f, 0x70, 0x23, 0xaf, 0xcb, 0xda, 0x75, 0xf9, 0x9c, 0xef, 0xf1, 0x9b, 0x8a, 0x8b, 0xb4, + 0xdc, 0xfb, 0xb9, 0x6a, 0xe3, 0x39, 0x95, 0xeb, 0x95, 0x8b, 0xb3, 0xa2, 0x8c, 0x53, 0x3d, 0xa9, 0x8b, 0xb1, 0x86, + 0x6d, 0xf8, 0x3d, 0xa2, 0xae, 0xa4, 0xe5, 0xe8, 0x29, 0xe5, 0xaa, 0x99, 0x72, 0xbe, 0xce, 0xf3, 0x9f, 0x76, 0x52, + 0x71, 0x8a, 0x9b, 0x29, 0x48, 0x95, 0x5a, 0x2e, 0xa0, 0x7a, 0x8e, 0x5a, 0xee, 0x96, 0x66, 0x07, 0x38, 0xb7, 0x4d, + 0xf5, 0xb1, 0x32, 0xbb, 0xf0, 0x92, 0x1b, 0xf7, 0x27, 0x53, 0x86, 0x05, 0xa3, 0xd0, 0x66, 0xd5, 0x95, 0xb6, 0x2f, + 0xb4, 0x4e, 0xc3, 0x70, 0xe5, 0xc7, 0x0b, 0x48, 0x17, 0x30, 0x8e, 0x17, 0x25, 0x13, 0xe3, 0xf6, 0xe8, 0xad, 0x20, + 0xbe, 0x64, 0x2b, 0x90, 0x7e, 0xbf, 0x27, 0xbc, 0x5d, 0xd7, 0xd1, 0x76, 0x4f, 0x9c, 0x32, 0x2a, 0x57, 0xb1, 0xf8, + 0x3e, 0x5e, 0x19, 0xc8, 0x64, 0x75, 0x3c, 0x36, 0xc6, 0x74, 0xfa, 0x7d, 0x12, 0xfa, 0x85, 0x50, 0xf0, 0x59, 0x2f, + 0xad, 0x3c, 0xb9, 0x3d, 0x2c, 0xe3, 0x1a, 0xbd, 0x12, 0x57, 0xba, 0x6f, 0x46, 0x0a, 0xa9, 0x47, 0xbe, 0x6a, 0x0a, + 0xe8, 0xcd, 0xd8, 0x37, 0x53, 0x61, 0xde, 0xee, 0x18, 0x73, 0x85, 0x60, 0xa5, 0xca, 0x6e, 0xdf, 0xa9, 0x31, 0x15, + 0x33, 0x98, 0x62, 0xdb, 0x59, 0x4c, 0xba, 0x95, 0x7f, 0xda, 0xb9, 0x4f, 0xf3, 0x0e, 0x77, 0x45, 0xfd, 0x16, 0xb8, + 0xd0, 0xac, 0x28, 0xab, 0xb6, 0x6c, 0xd8, 0x36, 0xde, 0xc8, 0x42, 0xb1, 0x01, 0x96, 0x3d, 0xf7, 0x2d, 0x3c, 0x40, + 0xdc, 0x84, 0x7b, 0x76, 0x51, 0xc3, 0x8d, 0xe1, 0xcb, 0x4a, 0xf2, 0x5d, 0x69, 0xcc, 0xa5, 0x4f, 0x95, 0x26, 0x86, + 0x93, 0xc5, 0x88, 0x8b, 0x74, 0x51, 0x67, 0x76, 0x2d, 0x7c, 0xc6, 0xcb, 0x70, 0xce, 0x17, 0x46, 0x37, 0xa5, 0x4b, + 0x2f, 0x58, 0xa2, 0x3b, 0xbd, 0x59, 0x69, 0xac, 0x94, 0x88, 0x5b, 0xb3, 0x4c, 0xa0, 0x2c, 0x65, 0xad, 0x84, 0x37, + 0x45, 0xcb, 0x56, 0xd2, 0xc8, 0x7b, 0xe6, 0xe0, 0x3e, 0xf6, 0x01, 0x31, 0x91, 0x4d, 0x60, 0x52, 0x34, 0x74, 0x40, + 0xbb, 0xea, 0xc2, 0x37, 0xa3, 0x1e, 0x0c, 0x72, 0x4b, 0x12, 0xb1, 0x82, 0x14, 0x2b, 0x58, 0xd7, 0xac, 0x98, 0xe7, + 0x0b, 0x7a, 0xce, 0xe4, 0x3c, 0x5d, 0xd0, 0x15, 0x93, 0xf3, 0x35, 0xde, 0x84, 0xce, 0xe1, 0x84, 0x24, 0x9b, 0x58, + 0x29, 0x60, 0xcf, 0xf1, 0xf2, 0x86, 0x67, 0xaa, 0xa6, 0x65, 0x17, 0x8a, 0x03, 0x8c, 0xcf, 0xca, 0x30, 0x2c, 0x87, + 0xe7, 0x60, 0x2d, 0xb1, 0x1f, 0xae, 0xe6, 0x7c, 0xa1, 0x7e, 0x43, 0xd4, 0xf9, 0x24, 0x54, 0xec, 0x82, 0xdd, 0x0b, + 0x64, 0x7a, 0x39, 0xe7, 0x0b, 0x35, 0x12, 0xba, 0xe0, 0x4b, 0x6b, 0x6c, 0x12, 0x7b, 0x82, 0x96, 0x59, 0x3c, 0x1f, + 0x2f, 0xa2, 0xb8, 0x86, 0x65, 0x78, 0xa2, 0x66, 0xa6, 0x25, 0xff, 0x49, 0xd4, 0x86, 0x26, 0xfa, 0x06, 0xab, 0xc8, + 0x1f, 0x1e, 0x1f, 0x5d, 0x02, 0x19, 0x3b, 0xbb, 0x92, 0x99, 0x0f, 0x7d, 0x1f, 0x19, 0xdc, 0x73, 0x53, 0xce, 0xb8, + 0x0a, 0x12, 0x65, 0xe0, 0xee, 0xd5, 0x2c, 0x19, 0x6b, 0x11, 0xbe, 0x7b, 0x54, 0x14, 0x7d, 0x26, 0x4d, 0x03, 0xba, + 0x8f, 0x04, 0x73, 0xa0, 0xf7, 0x0a, 0x1d, 0x2e, 0xab, 0x6d, 0x26, 0xe0, 0x2f, 0x12, 0xe4, 0xb7, 0x42, 0xaf, 0x6a, + 0x0c, 0xaa, 0x68, 0x17, 0xb1, 0xf4, 0xef, 0x23, 0x7e, 0x94, 0xcd, 0xdf, 0xcc, 0x3d, 0x5e, 0x49, 0x18, 0xfc, 0x90, + 0x9a, 0x4d, 0x32, 0x6f, 0xaf, 0xd8, 0x77, 0xd0, 0x51, 0x8f, 0x5a, 0xe3, 0x7d, 0xf5, 0x9c, 0x53, 0x88, 0x51, 0x42, + 0xd1, 0x49, 0x30, 0x80, 0xdb, 0x25, 0xa4, 0xb8, 0x1b, 0xec, 0xa6, 0x79, 0xcd, 0x8b, 0x82, 0xb3, 0x75, 0x55, 0x05, + 0x7e, 0x40, 0xc3, 0xf9, 0x62, 0x37, 0x84, 0xe1, 0x98, 0xb6, 0xae, 0x61, 0x10, 0x66, 0x0c, 0x23, 0x21, 0x78, 0xfd, + 0x8b, 0x1e, 0xd1, 0x24, 0x5e, 0x7d, 0xc7, 0x3f, 0x65, 0xbc, 0x50, 0x44, 0x1a, 0x44, 0x48, 0xdd, 0xc4, 0x37, 0x32, + 0x4d, 0x0a, 0x28, 0x04, 0x18, 0x05, 0x54, 0x62, 0x43, 0x53, 0xf1, 0xb7, 0x5a, 0x7c, 0xf0, 0x53, 0xd3, 0xf1, 0x68, + 0x5c, 0xb7, 0x3a, 0xa3, 0x82, 0xce, 0x40, 0x8f, 0x5a, 0x51, 0x4f, 0x83, 0x56, 0x82, 0x69, 0xa4, 0x79, 0xeb, 0x1e, + 0x02, 0xaf, 0x4c, 0x8b, 0x77, 0x1e, 0xd0, 0xcd, 0xa9, 0x0f, 0x9e, 0x3c, 0xa6, 0xa7, 0x0e, 0x3d, 0xb9, 0x62, 0x47, + 0x55, 0x0f, 0xb5, 0xf7, 0x66, 0x84, 0x82, 0x7e, 0x1f, 0x53, 0xa0, 0x1b, 0x41, 0xed, 0x5d, 0xdd, 0x2b, 0xb9, 0xcb, + 0xe1, 0x3b, 0xce, 0x72, 0x03, 0x58, 0x2a, 0xb2, 0x56, 0xe0, 0x51, 0x80, 0xba, 0x54, 0x86, 0xb0, 0xc5, 0x1c, 0x0e, + 0x95, 0xdd, 0xaa, 0xd5, 0x50, 0x92, 0xc3, 0x72, 0x04, 0x0e, 0xa1, 0xeb, 0x72, 0x50, 0x8e, 0x96, 0x59, 0xf5, 0x0e, + 0x7f, 0x6b, 0xd6, 0x21, 0xc9, 0xee, 0x62, 0x1d, 0xb8, 0x65, 0x1d, 0xa6, 0x1f, 0x0d, 0x52, 0x00, 0x9a, 0x6c, 0x04, + 0x2e, 0x01, 0x78, 0x6f, 0xff, 0x11, 0xa1, 0x56, 0xa6, 0x77, 0x32, 0x16, 0xea, 0xfb, 0x46, 0x12, 0x94, 0xd0, 0x4c, + 0xa8, 0x1c, 0x4b, 0xc1, 0x3b, 0x8f, 0x74, 0x4e, 0xea, 0x4c, 0xbc, 0x03, 0x71, 0x5a, 0x78, 0xcf, 0xde, 0x82, 0xe0, + 0x9c, 0x05, 0xbd, 0xc5, 0xdb, 0xac, 0x96, 0xda, 0xe8, 0x81, 0x02, 0xf8, 0xdd, 0xe0, 0x16, 0x41, 0xbe, 0x1a, 0xc3, + 0xb5, 0x92, 0xd7, 0x21, 0x1f, 0x16, 0xf4, 0x80, 0x0c, 0xec, 0xb3, 0x18, 0xc6, 0xf4, 0x80, 0x1c, 0xda, 0x67, 0xe9, + 0x06, 0x70, 0x20, 0xf5, 0xa8, 0xd2, 0x03, 0x68, 0xd0, 0x6f, 0xb6, 0x45, 0xee, 0x00, 0x94, 0x46, 0x11, 0x03, 0x55, + 0x82, 0x88, 0x5a, 0xfc, 0x7e, 0x6f, 0xae, 0x5b, 0xcc, 0x05, 0xc2, 0x1c, 0x0c, 0x38, 0x88, 0xdb, 0x20, 0x34, 0x07, + 0xcc, 0xe6, 0x26, 0x12, 0xf4, 0xd6, 0x1a, 0x66, 0x76, 0xf4, 0x87, 0x5b, 0x09, 0xbe, 0xc9, 0x5a, 0xa3, 0xce, 0x8b, + 0x43, 0x20, 0x08, 0xde, 0x14, 0xaa, 0xda, 0xab, 0x1e, 0xd8, 0x78, 0xab, 0x7e, 0x6c, 0xb7, 0xe3, 0xa9, 0x70, 0xd7, + 0x7e, 0x41, 0xe1, 0xe4, 0x53, 0xf2, 0xaf, 0x77, 0x26, 0x83, 0x03, 0x23, 0xc3, 0x97, 0xde, 0xfe, 0x85, 0xaf, 0xb5, + 0x74, 0x4f, 0x0c, 0x4a, 0xf2, 0xf0, 0x40, 0xd1, 0xbf, 0x3b, 0x65, 0xe5, 0x53, 0x3b, 0xfd, 0xdb, 0xad, 0x59, 0x9f, + 0x87, 0xa3, 0xc9, 0x76, 0xdb, 0x8b, 0x2b, 0xed, 0xb1, 0xa6, 0x17, 0x04, 0x3a, 0xd7, 0x93, 0xfd, 0x03, 0x88, 0x8a, + 0xd0, 0x8c, 0xbb, 0x59, 0x36, 0x24, 0x32, 0x7e, 0x9c, 0xce, 0xb2, 0x21, 0xd8, 0xe1, 0x5e, 0x54, 0xe2, 0x72, 0xd4, + 0xda, 0xe0, 0xf4, 0x36, 0x09, 0x21, 0x94, 0x03, 0x56, 0x76, 0xa3, 0xfe, 0xdc, 0x2a, 0x33, 0x21, 0x35, 0x59, 0xdd, + 0x4e, 0xe9, 0x1e, 0xa6, 0xf9, 0x9e, 0x19, 0xc1, 0x01, 0xf7, 0xf6, 0x57, 0xfd, 0x31, 0x4c, 0x32, 0x4d, 0x4e, 0x91, + 0xfc, 0x22, 0x3d, 0x85, 0xa4, 0x1d, 0x7a, 0xaa, 0x08, 0xe0, 0x84, 0xda, 0x8f, 0xe1, 0x37, 0x8c, 0xfb, 0x77, 0xcd, + 0xd7, 0x6e, 0x2a, 0xa2, 0xc7, 0x14, 0xcb, 0xd4, 0xe4, 0x34, 0xc9, 0x8a, 0x04, 0xa2, 0x36, 0xaa, 0x66, 0x44, 0x8f, + 0x5c, 0xcc, 0x47, 0x45, 0xf8, 0xbc, 0x5a, 0xff, 0x67, 0x08, 0x9f, 0x51, 0xb8, 0x01, 0x5c, 0x5e, 0x71, 0x71, 0x16, + 0x3e, 0x79, 0x4c, 0xf7, 0x26, 0xdf, 0x1c, 0xd0, 0xbd, 0x83, 0x47, 0x4f, 0x08, 0xc0, 0xa2, 0x5d, 0x9c, 0x85, 0x07, + 0x4f, 0x9e, 0xd0, 0xbd, 0x6f, 0xbf, 0xa5, 0x7b, 0x93, 0x47, 0x07, 0x8d, 0xb4, 0xc9, 0x93, 0x6f, 0xe9, 0xde, 0x37, + 0x8f, 0x1b, 0x69, 0x07, 0xe3, 0x27, 0x74, 0xef, 0x9f, 0xdf, 0x98, 0xb4, 0x7f, 0x40, 0xb6, 0x6f, 0x0f, 0xf0, 0x3f, + 0x93, 0x36, 0x79, 0xf2, 0x88, 0xee, 0x4d, 0xc6, 0x50, 0xc9, 0x13, 0x57, 0xc9, 0x78, 0x02, 0x1f, 0x3f, 0x82, 0xff, + 0xfe, 0x41, 0x60, 0x13, 0x48, 0x96, 0x0b, 0xd4, 0x9f, 0xa1, 0x88, 0x13, 0x55, 0x13, 0x09, 0x0f, 0x31, 0xb3, 0xfa, + 0x26, 0x0e, 0x03, 0xe2, 0xd2, 0xa1, 0x20, 0xba, 0x37, 0x1e, 0x3d, 0x21, 0x81, 0x0f, 0x4f, 0xf7, 0xd1, 0x07, 0x19, + 0xcb, 0xc5, 0x3c, 0xfb, 0x2a, 0x37, 0xb1, 0x15, 0x3c, 0x00, 0xab, 0x13, 0x3f, 0x17, 0x97, 0xf3, 0xec, 0x2b, 0x2e, + 0x77, 0x73, 0xfd, 0xab, 0x05, 0x28, 0xef, 0xaf, 0x5a, 0xf6, 0xb1, 0x50, 0xa1, 0xd3, 0x5a, 0xa3, 0xcf, 0x4e, 0x30, + 0x7d, 0x30, 0xf0, 0x6e, 0xd8, 0xdf, 0xef, 0x94, 0xd3, 0xfa, 0x46, 0xa3, 0x50, 0xa3, 0xf2, 0x90, 0xb0, 0x23, 0x28, + 0x7a, 0x30, 0x00, 0x9e, 0xc0, 0xc3, 0x7d, 0xfb, 0x37, 0xcb, 0x38, 0xe9, 0x28, 0xe3, 0x0f, 0x94, 0x21, 0xa0, 0x51, + 0x0f, 0xb3, 0x9b, 0x1e, 0x36, 0xba, 0xd5, 0x4b, 0x96, 0xea, 0x64, 0x6a, 0x7a, 0x06, 0xfb, 0x5a, 0xd7, 0x72, 0xcf, + 0x88, 0xa2, 0xe5, 0xf9, 0x5e, 0xca, 0x67, 0x15, 0xfb, 0x7e, 0x89, 0xea, 0xad, 0xa8, 0xf1, 0x46, 0x66, 0xb3, 0x8a, + 0xfd, 0x6c, 0xde, 0x00, 0x37, 0xc3, 0xfe, 0xa5, 0x9e, 0xfc, 0xc0, 0x19, 0x99, 0xb4, 0xed, 0x51, 0x26, 0x46, 0x80, + 0x15, 0x90, 0x81, 0x03, 0x0f, 0x80, 0x0e, 0xfa, 0xa3, 0xbd, 0xdd, 0xaa, 0x94, 0x66, 0x9f, 0x2d, 0x0c, 0xa0, 0x61, + 0xde, 0x26, 0x1e, 0xaa, 0x59, 0x43, 0x5e, 0x82, 0xc2, 0xad, 0x66, 0x79, 0x3b, 0x85, 0x21, 0x84, 0x60, 0x95, 0x32, + 0x00, 0x1c, 0x08, 0x30, 0x18, 0x6b, 0x19, 0x50, 0xb3, 0xe5, 0xa3, 0x0d, 0x57, 0xea, 0x49, 0xe0, 0x0c, 0xce, 0x65, + 0x91, 0xf0, 0x37, 0x5a, 0xec, 0x8f, 0xd6, 0x8f, 0xbe, 0x6f, 0x8f, 0x07, 0x6b, 0xdf, 0xe3, 0x23, 0xfd, 0x59, 0xe3, + 0x3a, 0xb0, 0x69, 0xf9, 0xc6, 0x8b, 0xda, 0x4a, 0x3c, 0x4a, 0xe0, 0x0d, 0x4c, 0x44, 0x0a, 0x83, 0x54, 0x0b, 0x1c, + 0x83, 0xf2, 0xc6, 0x42, 0x2c, 0x55, 0x57, 0x37, 0x74, 0x4b, 0x86, 0xe0, 0xe1, 0xf6, 0xe3, 0x52, 0x05, 0x8e, 0xea, + 0xf7, 0x33, 0xe9, 0xbb, 0x3d, 0x19, 0x3b, 0x72, 0x9c, 0xfa, 0xa9, 0x70, 0xf0, 0xdf, 0xa4, 0xae, 0x8d, 0xdd, 0x7d, + 0xca, 0x2c, 0xcb, 0xc2, 0x8e, 0x42, 0x2d, 0xf7, 0xa8, 0x3c, 0x48, 0xbe, 0x90, 0x43, 0x24, 0x0b, 0x8c, 0x42, 0x41, + 0x86, 0x13, 0x2a, 0x46, 0x6b, 0x51, 0x2e, 0xb3, 0xf3, 0x2a, 0xdc, 0x28, 0x85, 0x32, 0xa7, 0xe8, 0xdb, 0x0d, 0x0e, + 0x24, 0x24, 0xca, 0xca, 0xd7, 0xf1, 0xeb, 0x10, 0xc1, 0xea, 0xb8, 0xb6, 0x85, 0xe2, 0xde, 0xfe, 0xcc, 0xd2, 0x2e, + 0xfe, 0xc8, 0xb8, 0x80, 0xba, 0x58, 0x4c, 0xc3, 0x89, 0xd5, 0xef, 0xb8, 0x2f, 0xac, 0xa6, 0x07, 0xa0, 0xbe, 0x4b, + 0x25, 0x46, 0x50, 0x5f, 0x19, 0xfb, 0xd8, 0x1e, 0x63, 0x72, 0x06, 0xb1, 0x86, 0xf5, 0xdd, 0x4e, 0xf5, 0x8d, 0xb0, + 0x23, 0x00, 0x6e, 0x84, 0xd6, 0xe8, 0xc8, 0x24, 0x55, 0x88, 0xe7, 0xa5, 0x0a, 0xdf, 0x9a, 0x11, 0x3a, 0x06, 0x6f, + 0x2a, 0xdb, 0x48, 0x21, 0x7d, 0xc1, 0xa0, 0x39, 0xb6, 0x75, 0x14, 0x56, 0x5b, 0x59, 0x76, 0x04, 0x70, 0x03, 0xd9, + 0xa1, 0xb9, 0x78, 0xce, 0xaa, 0x79, 0xb6, 0x88, 0x4c, 0x50, 0xc0, 0xa5, 0xb0, 0x0c, 0xda, 0xeb, 0x3b, 0x64, 0x3b, + 0x0e, 0xa1, 0x1b, 0xee, 0x23, 0x18, 0x4f, 0xbb, 0x29, 0x58, 0x41, 0x34, 0x42, 0x3c, 0xcc, 0x98, 0xc5, 0xf7, 0x4a, + 0x53, 0x9e, 0xaa, 0x96, 0x40, 0xe0, 0x28, 0x84, 0xba, 0xd8, 0x35, 0x4a, 0x70, 0x99, 0x1a, 0xc1, 0x0c, 0x76, 0xec, + 0x48, 0x6d, 0x97, 0x9c, 0xd3, 0xa1, 0x9a, 0xd2, 0x52, 0x4f, 0xa9, 0xf6, 0x35, 0x14, 0xf3, 0x12, 0x3d, 0xf4, 0xc0, + 0xf5, 0x40, 0x3b, 0xe4, 0x95, 0x74, 0x62, 0x22, 0xe8, 0xb4, 0xda, 0x84, 0x9d, 0x1b, 0xe9, 0x96, 0xd5, 0xc8, 0x3b, + 0x86, 0x66, 0x47, 0x3c, 0xf7, 0x03, 0x75, 0x01, 0x44, 0xc8, 0x9d, 0x2d, 0x32, 0xb3, 0xcf, 0xb2, 0xf2, 0x05, 0x94, + 0xc5, 0x11, 0x5b, 0x57, 0xc0, 0xb5, 0x14, 0x4c, 0x2e, 0x79, 0x94, 0xa5, 0x88, 0x08, 0x78, 0xac, 0xb4, 0xeb, 0x3b, + 0x2d, 0x21, 0x54, 0xa4, 0x40, 0xdc, 0x5c, 0x14, 0xe7, 0xda, 0x06, 0xb2, 0x00, 0xfa, 0xf6, 0x53, 0x76, 0xe9, 0x85, + 0x83, 0xdd, 0x5c, 0x66, 0xe2, 0x19, 0x3f, 0xcf, 0x04, 0x4f, 0x11, 0xec, 0xea, 0xc6, 0x3c, 0x70, 0xc7, 0xb6, 0x81, + 0xe5, 0xdb, 0x77, 0xb0, 0x60, 0xca, 0x50, 0x2b, 0x25, 0x32, 0x11, 0x09, 0xc8, 0xec, 0x33, 0x77, 0xaf, 0x32, 0xf1, + 0x2a, 0xbe, 0x01, 0x6f, 0x8a, 0x06, 0x3f, 0x3d, 0x3a, 0xc3, 0x2f, 0x11, 0x49, 0x14, 0x62, 0xd8, 0x62, 0x44, 0x2c, + 0x44, 0x8e, 0x1d, 0x13, 0xca, 0x95, 0xa0, 0xb5, 0x35, 0x04, 0x5e, 0xfc, 0x69, 0xd5, 0xbd, 0xcb, 0x4c, 0x18, 0xfb, + 0x8c, 0xcb, 0xf8, 0x86, 0x95, 0x0a, 0xcc, 0x02, 0xe3, 0xdc, 0xb7, 0xa5, 0x24, 0x97, 0x99, 0x30, 0x02, 0x92, 0xcb, + 0xf8, 0x86, 0x36, 0x65, 0x1c, 0xda, 0x8a, 0xce, 0x8b, 0xf3, 0xbb, 0x3b, 0xfc, 0x12, 0x43, 0xad, 0x8c, 0xfb, 0x7d, + 0x90, 0x98, 0x49, 0xdb, 0x94, 0x99, 0x8c, 0xa4, 0x46, 0x0b, 0xa9, 0x28, 0x1f, 0x4c, 0xc8, 0xee, 0x4a, 0xb5, 0x8c, + 0xa8, 0xfd, 0x2a, 0x14, 0xb3, 0x71, 0x34, 0x21, 0x74, 0xd2, 0xb1, 0xde, 0x4d, 0x6b, 0x21, 0xd3, 0xe8, 0x49, 0xe4, + 0xf9, 0x74, 0x16, 0xac, 0x9a, 0x16, 0x87, 0x8c, 0x4f, 0x8b, 0xc1, 0x80, 0x68, 0x97, 0xc2, 0x0d, 0xd6, 0x03, 0xa6, + 0x34, 0x2e, 0xde, 0x9a, 0x69, 0xf5, 0x0b, 0xa9, 0x42, 0xd2, 0x7b, 0x06, 0x24, 0x42, 0xba, 0x60, 0xb7, 0x20, 0x51, + 0xf4, 0xfc, 0xef, 0xd4, 0x16, 0xdc, 0xf5, 0x60, 0x6c, 0x46, 0xf7, 0xf5, 0x8c, 0xff, 0x50, 0xdb, 0x82, 0xa8, 0x4f, + 0x25, 0xeb, 0x75, 0x24, 0xaa, 0x90, 0x8b, 0xf0, 0xb3, 0xa3, 0x21, 0x86, 0xa8, 0xf6, 0x58, 0x20, 0xd6, 0x97, 0x67, + 0xbc, 0xc0, 0xe9, 0x67, 0xee, 0x72, 0x05, 0xdb, 0x82, 0x56, 0x86, 0x46, 0xbd, 0x8e, 0x5f, 0x47, 0xf6, 0xb2, 0xa0, + 0x8b, 0x7c, 0x86, 0x42, 0xd6, 0x3c, 0x0c, 0xab, 0x61, 0x7b, 0x10, 0xc9, 0x7e, 0x7b, 0x12, 0x1a, 0x8d, 0x81, 0x05, + 0xb2, 0x43, 0x23, 0x70, 0x11, 0x5a, 0xf9, 0xdb, 0x21, 0xb8, 0x70, 0x59, 0x44, 0x96, 0xa1, 0x8e, 0xdf, 0xd4, 0x6e, + 0x82, 0xea, 0x15, 0x3a, 0x4d, 0x61, 0x55, 0xca, 0x24, 0x1f, 0x7e, 0xbd, 0x90, 0x05, 0x66, 0xf2, 0xba, 0xec, 0xd1, + 0xd7, 0x76, 0x7b, 0x07, 0xa6, 0x60, 0xdd, 0x27, 0xef, 0xeb, 0x87, 0x9d, 0x3d, 0x01, 0xa3, 0x58, 0x95, 0xa3, 0x29, + 0xa4, 0xd4, 0x3e, 0x28, 0xf5, 0xc7, 0x70, 0x29, 0x34, 0xc7, 0x6e, 0x01, 0x93, 0x80, 0x7d, 0x86, 0x54, 0x8f, 0x69, + 0xc7, 0x3e, 0x47, 0x1b, 0x58, 0x12, 0x70, 0xf8, 0x47, 0x42, 0xd6, 0xfe, 0xd5, 0xbd, 0x4c, 0x9b, 0x21, 0x5b, 0xe6, + 0x0b, 0xe0, 0xf3, 0x61, 0xd7, 0x46, 0x25, 0xca, 0x26, 0x22, 0x49, 0x61, 0xcb, 0x63, 0x90, 0xf6, 0x28, 0xa6, 0xab, + 0x82, 0x27, 0x19, 0x4a, 0x29, 0x12, 0xed, 0x13, 0x9c, 0xc3, 0x1b, 0xdc, 0x8f, 0x2a, 0x20, 0xbc, 0x0a, 0x39, 0x1d, + 0xa5, 0x54, 0x5b, 0xc0, 0x28, 0xea, 0x01, 0xa2, 0xbc, 0x0c, 0xe4, 0x78, 0xdb, 0xed, 0x84, 0xae, 0xd8, 0x72, 0x38, + 0xa1, 0x48, 0x4a, 0x2e, 0xb0, 0xdc, 0x4b, 0xd0, 0x79, 0x9c, 0xb1, 0xde, 0x73, 0xc0, 0x22, 0x38, 0x85, 0xbf, 0x31, + 0xa1, 0x57, 0xf0, 0x37, 0x27, 0xf4, 0x15, 0x0b, 0x2f, 0x87, 0x17, 0x64, 0x3f, 0x4c, 0x07, 0x13, 0x25, 0x18, 0xbb, + 0x65, 0x69, 0x19, 0xaa, 0xc4, 0xd5, 0xfe, 0x39, 0x79, 0x78, 0x4e, 0x6f, 0xe8, 0x35, 0x3d, 0xa1, 0x6f, 0x80, 0xf0, + 0xdf, 0x1e, 0x4e, 0xf8, 0x70, 0xf2, 0xb8, 0xdf, 0xef, 0x9d, 0xf5, 0xfb, 0xbd, 0x53, 0x63, 0x40, 0xa1, 0x77, 0xd1, + 0x45, 0x4d, 0xf5, 0xaf, 0xcb, 0x7a, 0x31, 0x7d, 0xa3, 0x36, 0x6e, 0xc2, 0xb3, 0x3c, 0xbc, 0xdc, 0xbf, 0x25, 0x43, + 0x7c, 0x3c, 0xcf, 0xa5, 0x2c, 0xc2, 0x8b, 0xfd, 0x5b, 0x42, 0xdf, 0x1c, 0x81, 0xde, 0x14, 0xeb, 0x7b, 0xf3, 0xf0, + 0x56, 0xd7, 0x46, 0xe8, 0xf3, 0x30, 0x81, 0x6d, 0x72, 0xc3, 0xec, 0x5d, 0x7b, 0x32, 0x86, 0x58, 0x26, 0xb7, 0x5e, + 0x79, 0xb7, 0x0f, 0x6f, 0xc8, 0xfe, 0x0d, 0x78, 0x8a, 0x5a, 0xf2, 0x37, 0x0b, 0xaf, 0x59, 0xab, 0x86, 0x87, 0xb7, + 0xf4, 0xa4, 0xd5, 0x88, 0x87, 0xb7, 0x24, 0x0a, 0xaf, 0xd9, 0x05, 0x3d, 0x61, 0x97, 0x84, 0x9e, 0xf5, 0xfb, 0xa7, + 0xfd, 0xbe, 0xec, 0xf7, 0xbf, 0x8f, 0xc3, 0x30, 0x1e, 0x16, 0x64, 0x5f, 0xd2, 0xdb, 0xfd, 0x09, 0x7f, 0x44, 0x66, + 0xa1, 0x6e, 0xbe, 0x5a, 0x70, 0x56, 0xe5, 0xad, 0x72, 0xdd, 0x52, 0xb0, 0x56, 0xb8, 0x65, 0xea, 0xe9, 0x0d, 0xbd, + 0x66, 0x05, 0x3d, 0x61, 0x31, 0x89, 0xae, 0xa0, 0x15, 0x67, 0xb3, 0x22, 0xba, 0xa6, 0x27, 0xec, 0x74, 0x16, 0x47, + 0x27, 0xf4, 0x0d, 0xcb, 0x87, 0x13, 0xc8, 0x7b, 0x32, 0xbc, 0x26, 0xfb, 0x6f, 0x48, 0x14, 0xbe, 0xd1, 0xbf, 0x6f, + 0xe9, 0x05, 0x0f, 0xdf, 0x50, 0xaf, 0x9a, 0x37, 0xc4, 0x54, 0xdf, 0xa8, 0xfd, 0x0d, 0x89, 0xfc, 0xc1, 0x7c, 0x63, + 0xed, 0x69, 0x1e, 0x38, 0xda, 0xb8, 0x2e, 0xc3, 0x5b, 0x42, 0xd7, 0x65, 0x78, 0x4d, 0xc8, 0xb4, 0x39, 0x76, 0x30, + 0xa0, 0xb3, 0x07, 0x51, 0x42, 0xe8, 0xb5, 0x5f, 0xea, 0x35, 0x8e, 0xa1, 0x19, 0x21, 0x95, 0x76, 0x82, 0x69, 0xb8, + 0x0e, 0x9e, 0x69, 0xb0, 0x8e, 0xb3, 0x7e, 0x3f, 0x5c, 0xf7, 0xfb, 0x10, 0xe9, 0xbe, 0x98, 0x99, 0xd8, 0x6e, 0x8e, + 0x6c, 0xd2, 0x6b, 0xd0, 0xfe, 0x3f, 0x1b, 0x0c, 0xa0, 0x33, 0x5e, 0x49, 0xe1, 0xf5, 0xe0, 0xd9, 0xc3, 0x5b, 0xa2, + 0xea, 0x28, 0x68, 0x29, 0xc3, 0x82, 0xbe, 0xa2, 0x19, 0x00, 0x7e, 0x3d, 0x1b, 0x0c, 0x48, 0x64, 0x3e, 0x23, 0xd3, + 0x67, 0x87, 0x6f, 0xa6, 0x83, 0xc1, 0x33, 0xb3, 0x4d, 0x3e, 0xb1, 0x3b, 0x4a, 0x81, 0xf5, 0x77, 0xda, 0xef, 0x7f, + 0x3a, 0x8a, 0xc9, 0x59, 0xc1, 0xe3, 0x8f, 0xd3, 0x66, 0x5b, 0x3e, 0xb9, 0xa8, 0x6a, 0xa7, 0xfd, 0xfe, 0xba, 0xdf, + 0x3f, 0x01, 0xec, 0xa2, 0x99, 0xf3, 0xf5, 0x04, 0x69, 0xcb, 0xdc, 0x51, 0x24, 0x4d, 0x72, 0x68, 0x0c, 0x6d, 0x8b, + 0x55, 0xdb, 0x66, 0x1d, 0x19, 0x58, 0x1c, 0x35, 0x2b, 0x8a, 0x6b, 0x12, 0x85, 0xbd, 0xd3, 0xed, 0xf6, 0x84, 0x31, + 0x16, 0x13, 0x90, 0x7e, 0xf8, 0xaf, 0x4f, 0xea, 0x46, 0x0c, 0xb1, 0x52, 0x89, 0xef, 0x36, 0x4b, 0x7b, 0x08, 0x44, + 0x1c, 0x36, 0xfd, 0x3b, 0x73, 0x2f, 0x17, 0xb5, 0xe3, 0x5b, 0xff, 0x00, 0x10, 0x22, 0xc9, 0x42, 0x3e, 0xc3, 0x31, + 0x28, 0x33, 0x00, 0x32, 0x8f, 0xd4, 0xcc, 0x4b, 0x00, 0x01, 0x26, 0xdb, 0xed, 0x68, 0x3c, 0x9e, 0xd0, 0x82, 0x8d, + 0xfe, 0xf1, 0xe4, 0x61, 0xf5, 0x30, 0x0c, 0x82, 0x41, 0x46, 0x5a, 0x7a, 0x0a, 0xbb, 0x58, 0xab, 0x7d, 0x30, 0x82, + 0xd7, 0xec, 0xe3, 0x55, 0xf6, 0xc5, 0xec, 0x23, 0x12, 0xd6, 0x06, 0xe3, 0xc8, 0x45, 0xda, 0xd2, 0xdb, 0xdd, 0xc1, + 0x60, 0x72, 0x91, 0x7e, 0x86, 0xed, 0xf4, 0xf9, 0x37, 0x0f, 0xc6, 0x13, 0x0e, 0x46, 0x77, 0x51, 0xd0, 0x67, 0xda, + 0x76, 0x5b, 0xf9, 0x97, 0xc0, 0xd7, 0x98, 0x0a, 0x3a, 0x36, 0xcb, 0xc2, 0x0d, 0x2a, 0xa2, 0x8e, 0x96, 0x41, 0x55, + 0x2b, 0xdb, 0x39, 0xa0, 0x96, 0x58, 0x95, 0x89, 0x5b, 0x60, 0x18, 0x32, 0xd4, 0xe5, 0x1e, 0x57, 0x7f, 0xf0, 0x42, + 0x1a, 0xf8, 0x0c, 0x27, 0x22, 0xf4, 0xb8, 0x35, 0xee, 0x73, 0x6b, 0xe2, 0x33, 0xdc, 0x5a, 0x89, 0x24, 0xd6, 0xc0, + 0x92, 0x9a, 0xcb, 0x51, 0xc2, 0x8e, 0x4a, 0xc6, 0x67, 0x65, 0x94, 0xd0, 0x18, 0x1e, 0x24, 0x13, 0x33, 0x19, 0x25, + 0x68, 0x9f, 0xe8, 0x22, 0x0c, 0xfe, 0x0d, 0x98, 0xfd, 0x34, 0x87, 0xbf, 0x92, 0x4c, 0x93, 0x43, 0x08, 0x08, 0x71, + 0x38, 0x9e, 0xc5, 0xe1, 0x98, 0x44, 0xc9, 0x11, 0x3c, 0xc1, 0x7f, 0x45, 0x38, 0x26, 0xb5, 0xbe, 0xc3, 0x48, 0x75, + 0xb9, 0x4d, 0x18, 0xc0, 0x95, 0x8d, 0x67, 0x93, 0xc8, 0x4a, 0x77, 0xe5, 0xc3, 0xd1, 0xf8, 0x09, 0x99, 0xc6, 0xa1, + 0x1c, 0x24, 0x84, 0x82, 0x77, 0x6f, 0x58, 0x0e, 0x13, 0x0d, 0xcf, 0x06, 0x6c, 0x5e, 0xe9, 0xd8, 0x3c, 0x09, 0x27, + 0x20, 0x0c, 0x13, 0x72, 0xac, 0x77, 0x20, 0xa5, 0xe8, 0xf3, 0x1c, 0xfb, 0xa9, 0x8f, 0x20, 0xcc, 0x8e, 0x5a, 0x2a, + 0xbe, 0x02, 0xa0, 0x4b, 0x1c, 0x1c, 0x6a, 0xcf, 0x7c, 0x31, 0x0b, 0x4b, 0x8f, 0x4a, 0x99, 0xea, 0xf6, 0x45, 0x83, + 0xf2, 0x9b, 0x06, 0xed, 0x0b, 0x32, 0x98, 0xd0, 0xf2, 0x68, 0xc2, 0x1f, 0x41, 0x00, 0x8f, 0x46, 0xc4, 0x2f, 0x85, + 0x13, 0x03, 0xe1, 0x55, 0x90, 0x81, 0x4a, 0x6b, 0xd5, 0x98, 0x91, 0xad, 0x78, 0x0f, 0xc2, 0xa4, 0xec, 0x5d, 0xcb, + 0x75, 0x9e, 0x42, 0x54, 0xb0, 0x75, 0x5e, 0xed, 0x5d, 0x80, 0x25, 0x7b, 0x5c, 0x41, 0x9c, 0xb0, 0xf5, 0x0a, 0xb0, + 0x73, 0x1f, 0x6c, 0xca, 0x7a, 0x4f, 0x7d, 0xb7, 0x87, 0x2d, 0x87, 0x57, 0x95, 0xdc, 0x9b, 0x8c, 0xc7, 0xe3, 0xd1, + 0x9f, 0x70, 0x74, 0x00, 0xa1, 0x25, 0x91, 0xe1, 0x93, 0x01, 0x1a, 0x77, 0x5d, 0x71, 0x6f, 0x5c, 0x28, 0xca, 0x4a, + 0x27, 0x13, 0x02, 0xe2, 0x67, 0xd3, 0x37, 0xd8, 0x57, 0x5c, 0xc7, 0x3f, 0xd9, 0xfd, 0xc4, 0xac, 0x68, 0xb5, 0x52, + 0x47, 0x6f, 0xdf, 0x9c, 0xbc, 0x7c, 0xff, 0xf2, 0x97, 0xe7, 0xa7, 0x2f, 0x5f, 0xbf, 0x78, 0xf9, 0xfa, 0xe5, 0xfb, + 0xdf, 0xef, 0x61, 0xb0, 0x7d, 0x5b, 0x11, 0x3b, 0xf6, 0xde, 0x3d, 0xc6, 0xab, 0xc5, 0x17, 0xce, 0x1e, 0xb8, 0x5b, + 0x2c, 0xc0, 0x26, 0x18, 0x6e, 0x41, 0x50, 0xcd, 0x68, 0x54, 0xfa, 0x9e, 0x80, 0x8c, 0x46, 0x85, 0x6c, 0x3c, 0xac, + 0xd8, 0x0a, 0xb9, 0x78, 0xc7, 0x70, 0xf0, 0x91, 0xfd, 0xad, 0x38, 0x13, 0x6e, 0x47, 0x5b, 0xb3, 0x22, 0xe0, 0xf3, + 0xb5, 0x16, 0x95, 0xc7, 0x85, 0xa8, 0xbd, 0x6d, 0x9f, 0x43, 0x42, 0x3d, 0x22, 0xd7, 0xc1, 0xfb, 0x36, 0xc8, 0x1e, + 0x1f, 0x79, 0x4f, 0xca, 0x33, 0xd4, 0xe7, 0x68, 0xf8, 0xa8, 0xf1, 0x8c, 0x4e, 0xcc, 0xb5, 0xd1, 0xa1, 0x9e, 0x16, + 0xb0, 0xbf, 0x95, 0x18, 0x9b, 0x16, 0xac, 0x4c, 0x11, 0xeb, 0xc3, 0xe9, 0x7e, 0x77, 0x6f, 0x46, 0x3f, 0xc3, 0xf1, + 0xa3, 0x54, 0x13, 0x48, 0x8b, 0x02, 0xa5, 0x2b, 0x43, 0x6e, 0x7b, 0x16, 0x16, 0xe6, 0x67, 0xd8, 0x20, 0x80, 0xf6, + 0xb2, 0x63, 0x49, 0xa0, 0x59, 0xbc, 0xd6, 0xf5, 0xcf, 0xcb, 0x97, 0x89, 0x76, 0xbe, 0xf8, 0x06, 0x42, 0x0c, 0xfb, + 0x57, 0x84, 0xc6, 0x84, 0xbb, 0x49, 0x76, 0x97, 0x16, 0x73, 0xaf, 0xba, 0x8c, 0xf1, 0xb8, 0xbb, 0xe3, 0x4a, 0xd1, + 0xbc, 0x75, 0x81, 0x3d, 0x50, 0xf3, 0x3a, 0x5e, 0xb2, 0x10, 0xb0, 0x19, 0xf7, 0xed, 0x22, 0x71, 0x7e, 0xef, 0x74, + 0x42, 0xf6, 0x0f, 0xa6, 0x7c, 0xc8, 0x4a, 0x2a, 0x06, 0xac, 0xac, 0x77, 0xa8, 0x39, 0x6f, 0x13, 0x72, 0xb1, 0x4b, + 0xc3, 0xc5, 0x90, 0xdf, 0x77, 0x49, 0x7a, 0xcf, 0x1b, 0x0e, 0xd5, 0xb6, 0xb9, 0x18, 0xd2, 0x94, 0xd3, 0x5d, 0x2a, + 0x03, 0x42, 0xa4, 0xcb, 0xb8, 0x22, 0xb5, 0x3e, 0xaa, 0x52, 0x27, 0xe9, 0xb8, 0xca, 0x36, 0x9f, 0xb9, 0x64, 0xab, + 0xdb, 0xb5, 0x7f, 0xad, 0x6e, 0x5f, 0x98, 0x81, 0xfc, 0xfd, 0x85, 0xa8, 0x26, 0x06, 0xa2, 0x0b, 0xa8, 0xe0, 0x5f, + 0xe0, 0xe5, 0xc9, 0x23, 0xad, 0x00, 0xbd, 0xeb, 0xec, 0xe8, 0xda, 0xe3, 0x8d, 0x59, 0x6c, 0x2d, 0x71, 0xce, 0x2a, + 0xdf, 0x59, 0x5e, 0x95, 0xad, 0xd0, 0x75, 0x04, 0xfb, 0x23, 0xec, 0xe8, 0xbb, 0xb7, 0x0d, 0x80, 0x28, 0x85, 0x95, + 0x3b, 0xfb, 0x85, 0x77, 0xf6, 0x0b, 0x7b, 0xf6, 0xdb, 0x4d, 0xa0, 0x7c, 0x58, 0xa1, 0x65, 0x2f, 0xa4, 0xa8, 0x4c, + 0x93, 0xc7, 0x4d, 0x5d, 0x16, 0xd2, 0x62, 0xbe, 0x6f, 0x69, 0xd7, 0xe3, 0x31, 0x95, 0xa8, 0x1e, 0xf9, 0x01, 0x5b, + 0xb5, 0x5f, 0x92, 0xfb, 0xef, 0x99, 0xff, 0xb3, 0x37, 0xc8, 0xbb, 0xee, 0x76, 0xff, 0x37, 0x17, 0x3a, 0xb8, 0xad, + 0xa5, 0xc2, 0x53, 0x57, 0xc7, 0x05, 0xde, 0xd5, 0xd2, 0xfb, 0xef, 0x6a, 0x6f, 0x33, 0xbd, 0xec, 0x2a, 0x40, 0x0d, + 0x12, 0xeb, 0x4b, 0x5e, 0x64, 0x49, 0x6d, 0x15, 0x1a, 0x6f, 0x38, 0x84, 0xf6, 0xf0, 0x0e, 0x2e, 0x90, 0xc3, 0x12, + 0x42, 0x3f, 0x56, 0x46, 0x00, 0xe8, 0xb3, 0xd8, 0x6f, 0x78, 0x98, 0x91, 0x81, 0x2f, 0xf1, 0x93, 0xd2, 0x17, 0x17, + 0xef, 0xef, 0x64, 0x26, 0xe8, 0x55, 0xe2, 0xa2, 0xe6, 0xca, 0x76, 0xcc, 0x0f, 0xff, 0x0b, 0x8c, 0x06, 0xe1, 0xb5, + 0x25, 0xdb, 0x17, 0x1d, 0xb3, 0x5c, 0xc1, 0x51, 0x5b, 0xba, 0x32, 0x65, 0xeb, 0xfa, 0x59, 0x0d, 0x33, 0x7d, 0xa6, + 0xbc, 0x01, 0xd9, 0x17, 0x72, 0xf7, 0x53, 0x5d, 0xb1, 0x20, 0x47, 0x93, 0xf1, 0x94, 0x88, 0xc1, 0xa0, 0x95, 0x7c, + 0x88, 0xc9, 0xc3, 0xe1, 0x0e, 0x73, 0x29, 0x74, 0x3f, 0xbc, 0x3e, 0x40, 0x7d, 0x8d, 0x2d, 0x49, 0x36, 0x15, 0xfb, + 0x1b, 0xcc, 0x62, 0x81, 0x38, 0x3a, 0xf8, 0xc5, 0xf9, 0x02, 0x40, 0x96, 0x61, 0x99, 0x69, 0x61, 0x91, 0x4c, 0x95, + 0x8f, 0x6c, 0xc1, 0xe4, 0xe1, 0x78, 0xe6, 0xf7, 0xdc, 0x31, 0x38, 0x84, 0x44, 0x13, 0x6b, 0xfc, 0xe2, 0x67, 0xc1, + 0x38, 0x0e, 0xe5, 0x91, 0x6c, 0x7c, 0x57, 0x92, 0x68, 0x6c, 0x4c, 0x95, 0xf5, 0x55, 0xa2, 0x1a, 0x26, 0xe4, 0x61, + 0x41, 0xf6, 0x0b, 0xba, 0xf4, 0xc7, 0x12, 0xd3, 0xf7, 0xe3, 0xfd, 0xc9, 0x98, 0x3c, 0x8c, 0x1f, 0x4e, 0x0c, 0xdc, + 0xb0, 0x9f, 0x23, 0x1f, 0x2e, 0xc9, 0x7e, 0xb3, 0x4a, 0x30, 0x45, 0x35, 0x3d, 0xf3, 0x2b, 0x49, 0x06, 0xcb, 0x41, + 0xfa, 0xb0, 0x95, 0x17, 0x6b, 0xd5, 0xe3, 0xbd, 0x3e, 0xe4, 0x53, 0x22, 0x1a, 0x37, 0x86, 0x35, 0xbd, 0x8c, 0xff, + 0x92, 0x45, 0x24, 0x25, 0x20, 0x12, 0x82, 0x7a, 0x3b, 0x3b, 0xcf, 0x92, 0x58, 0xa4, 0x51, 0x5a, 0x13, 0x9a, 0x1e, + 0xb1, 0xc9, 0x78, 0x96, 0xb2, 0xf4, 0x70, 0xf2, 0x64, 0x36, 0x79, 0x12, 0x1d, 0x8c, 0xa3, 0x74, 0x30, 0x80, 0xe4, + 0x83, 0x31, 0xb8, 0xd8, 0xc1, 0x6f, 0x76, 0x00, 0x43, 0x77, 0x84, 0x2c, 0x61, 0x01, 0x4d, 0xfb, 0xb2, 0x26, 0xe9, + 0xe1, 0x3c, 0x57, 0x3d, 0x89, 0x6f, 0xe8, 0xda, 0x73, 0x70, 0xf1, 0x5b, 0x78, 0xee, 0x5a, 0x78, 0xbe, 0xdb, 0x42, + 0xa1, 0xc9, 0x76, 0x2c, 0xff, 0x7f, 0xdc, 0x30, 0xee, 0xba, 0x4b, 0x98, 0xc5, 0x75, 0x95, 0x8d, 0x56, 0x85, 0xac, + 0x24, 0xdc, 0x26, 0x94, 0x28, 0x6c, 0x14, 0xaf, 0x56, 0xb9, 0x76, 0x11, 0x9b, 0x57, 0x14, 0xc0, 0x5d, 0x20, 0x4e, + 0x31, 0xb0, 0xd0, 0xc6, 0x40, 0xee, 0x13, 0x2f, 0x24, 0xb3, 0x6a, 0x1f, 0x73, 0x8f, 0xfc, 0x2b, 0x04, 0x63, 0x54, + 0x71, 0x34, 0x9e, 0x29, 0xac, 0x8b, 0xcf, 0xc9, 0x7b, 0xff, 0x8d, 0xa3, 0xc8, 0x1e, 0xcd, 0xa0, 0x27, 0x88, 0x9c, + 0x47, 0x9c, 0x3d, 0x99, 0xbc, 0x0c, 0xdc, 0xcf, 0x60, 0xa5, 0xbf, 0xee, 0x36, 0x63, 0x6d, 0x7b, 0x74, 0x2f, 0x8c, + 0x50, 0xf4, 0x13, 0xbe, 0x33, 0xf5, 0x02, 0x2e, 0xa1, 0x1a, 0xd8, 0xf5, 0xc5, 0x05, 0x2f, 0x01, 0x44, 0x28, 0x13, + 0xfd, 0x7e, 0xef, 0x2f, 0x03, 0x4d, 0x5a, 0xf2, 0xe2, 0x55, 0x26, 0xac, 0x33, 0x0e, 0x34, 0x15, 0xa8, 0xff, 0xc7, + 0xca, 0x3e, 0xd3, 0x31, 0x99, 0xf9, 0x8f, 0xc3, 0x09, 0x89, 0x9a, 0xaf, 0xc9, 0x67, 0x4e, 0xd3, 0xcf, 0x5c, 0xd1, + 0xfe, 0x03, 0x99, 0xb9, 0xe1, 0x90, 0xa1, 0xfe, 0xd2, 0x31, 0x4f, 0x46, 0xaf, 0x13, 0xb3, 0x23, 0xc1, 0xaa, 0x19, + 0x44, 0x61, 0x2f, 0xe0, 0x41, 0x5d, 0xcb, 0xe2, 0x29, 0xcc, 0x3e, 0xa8, 0x11, 0xc5, 0x21, 0x1b, 0xcf, 0x42, 0x19, + 0x4e, 0xc0, 0xbe, 0x77, 0x32, 0x86, 0xfb, 0x80, 0x0c, 0x3f, 0x56, 0x21, 0x76, 0x0e, 0xd2, 0x3e, 0x56, 0xa8, 0x98, + 0x00, 0x88, 0x40, 0xc8, 0xdb, 0xef, 0x4b, 0x95, 0x84, 0xaf, 0x4b, 0x4c, 0x29, 0xd4, 0x07, 0xff, 0x89, 0x54, 0xdd, + 0x31, 0xfd, 0x6a, 0xfd, 0xf8, 0x33, 0xa1, 0xf8, 0x74, 0x97, 0x12, 0xdf, 0x40, 0x70, 0xe7, 0x02, 0x74, 0x10, 0x15, + 0x9a, 0xb1, 0xdd, 0xcf, 0xef, 0x8a, 0xbb, 0xf9, 0x5d, 0xf1, 0xff, 0x8e, 0xdf, 0x15, 0xf7, 0x31, 0x86, 0x95, 0x85, + 0x86, 0x9f, 0x05, 0xe3, 0x20, 0xfa, 0xcf, 0xf9, 0xc4, 0x3b, 0x79, 0xea, 0xcb, 0x4c, 0x4c, 0xef, 0x60, 0x9a, 0x7d, + 0x82, 0x82, 0xb0, 0x8a, 0xbb, 0xf4, 0x64, 0x5d, 0xd9, 0x5b, 0x2b, 0x19, 0x62, 0x9e, 0x7b, 0x58, 0xa3, 0xb0, 0xf2, + 0x80, 0xee, 0x51, 0xb5, 0x41, 0x9c, 0x08, 0x1e, 0xc6, 0xcc, 0x4a, 0xdf, 0xb7, 0x5b, 0xa3, 0xc2, 0xbc, 0x97, 0x8b, + 0x82, 0xec, 0xe6, 0xe3, 0xd9, 0x38, 0x0a, 0xb1, 0x01, 0xff, 0x31, 0x63, 0xd5, 0x90, 0xcd, 0x77, 0x32, 0x52, 0x3b, + 0x26, 0x4f, 0x93, 0x5d, 0xd2, 0x3b, 0xe0, 0x1d, 0xf2, 0xf3, 0xfa, 0x63, 0x18, 0x4b, 0xc3, 0x6f, 0xc9, 0x8b, 0xb8, + 0xc8, 0xaa, 0xe5, 0x65, 0x96, 0x20, 0xd3, 0x05, 0x2f, 0xbe, 0x98, 0xe9, 0xf2, 0x3e, 0xd6, 0x07, 0x8c, 0xa7, 0x14, + 0xaf, 0x1b, 0xa2, 0xf4, 0x75, 0xcb, 0xb3, 0x42, 0x5d, 0x9e, 0x54, 0xcc, 0xf6, 0xac, 0x04, 0xa7, 0x53, 0x30, 0xc1, + 0xd7, 0x3f, 0x5d, 0xef, 0x13, 0xc0, 0x05, 0x85, 0x9a, 0xd3, 0x42, 0xae, 0x0c, 0x96, 0x93, 0x85, 0xee, 0x04, 0xcc, + 0x50, 0x29, 0xf0, 0x02, 0x05, 0x7f, 0xd1, 0xc0, 0x88, 0xbe, 0x70, 0xbf, 0xc9, 0xc0, 0x20, 0x5d, 0x9a, 0x13, 0x61, + 0xec, 0xb8, 0x9d, 0x38, 0x6d, 0x45, 0x39, 0xe3, 0xec, 0x9d, 0xba, 0x52, 0x80, 0x01, 0xde, 0xe6, 0x3a, 0x3a, 0x4d, + 0xd0, 0x6b, 0x41, 0xe9, 0xbc, 0x81, 0xbb, 0x59, 0x46, 0x46, 0xb8, 0xf8, 0xb0, 0xf2, 0x58, 0x70, 0xcf, 0x7e, 0x21, + 0xb1, 0xb6, 0x7e, 0x60, 0xcc, 0xe6, 0x05, 0x0b, 0x14, 0x2a, 0x50, 0x60, 0x39, 0xd3, 0x96, 0xa6, 0xd5, 0x90, 0xef, + 0x1f, 0xa0, 0xb5, 0x69, 0x35, 0xe0, 0xfb, 0x07, 0x75, 0x94, 0x1d, 0x42, 0x96, 0x23, 0x3f, 0x83, 0x7a, 0x5d, 0x47, + 0x26, 0xc5, 0x64, 0xf7, 0xeb, 0x4b, 0xfd, 0x51, 0xdd, 0x80, 0xeb, 0x07, 0x20, 0x80, 0x0d, 0xc0, 0x21, 0x50, 0x0d, + 0x96, 0x46, 0x04, 0x8b, 0x32, 0x85, 0xf6, 0x35, 0xf4, 0xde, 0x68, 0xf8, 0x2f, 0x70, 0x17, 0x91, 0x2b, 0xff, 0x13, + 0x04, 0xfe, 0x8a, 0x32, 0xad, 0x4c, 0xf1, 0x3f, 0xd1, 0xea, 0x15, 0xca, 0x59, 0xd3, 0x9a, 0x0f, 0xa2, 0x35, 0x11, + 0xaa, 0x19, 0x43, 0xf0, 0x6f, 0x65, 0x99, 0xb6, 0x54, 0x55, 0xea, 0x43, 0xe3, 0xb5, 0x56, 0x38, 0xcb, 0xc7, 0x91, + 0xf7, 0x1a, 0x43, 0xc7, 0x26, 0xce, 0x52, 0x4e, 0xa5, 0xce, 0x5e, 0xef, 0xcb, 0xc8, 0x01, 0x4e, 0x27, 0x6c, 0x3c, + 0x4d, 0x0e, 0xe5, 0x34, 0x71, 0x90, 0xf9, 0x39, 0xc3, 0xc8, 0xaa, 0x06, 0x84, 0x45, 0xd9, 0x50, 0xda, 0x02, 0x4c, + 0x72, 0x42, 0xc8, 0x14, 0x43, 0x51, 0xe4, 0x23, 0xdd, 0x0f, 0xeb, 0xcd, 0xea, 0xbe, 0x78, 0xab, 0x01, 0x4e, 0xc3, + 0x04, 0x02, 0x81, 0x17, 0xf1, 0x75, 0x26, 0x2e, 0xc0, 0x63, 0x78, 0x00, 0x5f, 0x82, 0x9b, 0x5c, 0xca, 0x7e, 0xab, + 0xc2, 0x1c, 0xd7, 0x16, 0x30, 0x68, 0xb0, 0x7a, 0x10, 0x1d, 0x2e, 0xa5, 0xcd, 0xae, 0x02, 0xc4, 0xc6, 0x14, 0x62, + 0x59, 0xb0, 0xb5, 0x65, 0xcf, 0x7e, 0x56, 0x4d, 0x43, 0xeb, 0x84, 0x63, 0x71, 0x91, 0x43, 0x14, 0x95, 0x41, 0x0c, + 0xee, 0x48, 0x1e, 0x9f, 0xf7, 0x40, 0x84, 0xe7, 0x04, 0xdc, 0xca, 0x12, 0x19, 0xae, 0xe8, 0x72, 0x74, 0x43, 0xd7, + 0xa3, 0x6b, 0x3a, 0xa6, 0x93, 0x7f, 0x8e, 0xd1, 0x22, 0x5b, 0xa5, 0xde, 0xd2, 0xf5, 0x68, 0x49, 0xbf, 0x1d, 0xd3, + 0x83, 0x7f, 0x8c, 0xc9, 0x34, 0xc7, 0xc3, 0x84, 0x9e, 0x83, 0x63, 0x17, 0xa9, 0xd1, 0x53, 0xd3, 0x37, 0x38, 0xac, + 0x46, 0xf9, 0x90, 0x8f, 0x72, 0xca, 0x47, 0xc5, 0xb0, 0x1a, 0x81, 0xa7, 0x63, 0x35, 0xe4, 0xa3, 0x8a, 0xf2, 0xd1, + 0xd9, 0xb0, 0x1a, 0x9d, 0x91, 0x66, 0xd3, 0x5f, 0x56, 0xfc, 0xb2, 0x64, 0x6b, 0xd8, 0x16, 0xb0, 0x7c, 0xdd, 0x2a, + 0xcb, 0x53, 0x7f, 0x55, 0x9b, 0x93, 0xd9, 0x72, 0xf6, 0xf6, 0xba, 0xcb, 0x89, 0xc5, 0xe3, 0xb6, 0xe9, 0x70, 0xf5, + 0xe5, 0x44, 0x9d, 0xf4, 0x0a, 0xf9, 0x61, 0x3c, 0x15, 0xea, 0x1c, 0x02, 0x33, 0x89, 0x59, 0x18, 0x33, 0x6c, 0xa6, + 0x4e, 0x03, 0x05, 0x4e, 0x36, 0xf2, 0x5c, 0x14, 0xb3, 0x51, 0x4e, 0xe1, 0x7d, 0x4c, 0x48, 0x24, 0xe0, 0xac, 0x3a, + 0xaa, 0x46, 0x05, 0xc4, 0x1c, 0x61, 0x21, 0x3e, 0x42, 0xbf, 0xd4, 0x47, 0x1e, 0x12, 0x78, 0x86, 0x7d, 0x2d, 0x06, + 0x31, 0x1c, 0xf1, 0xb6, 0xb2, 0x6a, 0x16, 0x26, 0x50, 0x59, 0x35, 0x2c, 0x4d, 0x65, 0x05, 0xcd, 0x46, 0x95, 0x5f, + 0x59, 0x85, 0x63, 0x94, 0x10, 0x12, 0x95, 0xba, 0x32, 0x50, 0x9f, 0x24, 0x2c, 0x2c, 0x75, 0x65, 0x67, 0xea, 0xa3, + 0x33, 0xbf, 0xb2, 0x33, 0x70, 0x21, 0x1d, 0x24, 0xfe, 0x55, 0x6a, 0x99, 0xb6, 0xaf, 0x83, 0x8d, 0x55, 0x45, 0x37, + 0xfc, 0xa6, 0x2a, 0xe2, 0xa8, 0xa4, 0x2e, 0x06, 0x34, 0x2e, 0x8c, 0x48, 0x52, 0xbd, 0x46, 0xc1, 0x1f, 0x12, 0x44, + 0xa5, 0x31, 0x78, 0x75, 0x26, 0x5d, 0x2b, 0xb5, 0xa2, 0x62, 0x50, 0x0e, 0x0a, 0xb8, 0x3f, 0xe5, 0xad, 0x85, 0xf4, + 0x33, 0x44, 0x54, 0x86, 0xf2, 0x06, 0x1f, 0x30, 0x78, 0x32, 0xbb, 0x48, 0xc3, 0x64, 0x74, 0x4b, 0xe3, 0xd1, 0x12, + 0xe1, 0x60, 0xd8, 0x79, 0xaa, 0xf0, 0xd6, 0x57, 0x90, 0x7e, 0x43, 0xe3, 0xd1, 0x35, 0x4d, 0xad, 0xcd, 0xa9, 0x81, + 0xba, 0xea, 0x8d, 0xe9, 0x4d, 0x04, 0xaf, 0x6f, 0xa3, 0x25, 0x85, 0xad, 0x74, 0x9c, 0x67, 0x17, 0x22, 0x4a, 0x29, + 0x22, 0x10, 0xae, 0x11, 0x39, 0x70, 0xa9, 0xd1, 0x06, 0xd7, 0x03, 0x28, 0x43, 0xc3, 0x05, 0x2e, 0x07, 0xf1, 0x68, + 0xe9, 0x91, 0xa9, 0x54, 0x5f, 0x64, 0x11, 0x3e, 0xda, 0xd9, 0x68, 0x29, 0x9e, 0x11, 0x0b, 0xe3, 0x0a, 0x86, 0x50, + 0x17, 0x56, 0x9a, 0x82, 0xa4, 0x0b, 0x1c, 0xd9, 0x0b, 0xe3, 0x2a, 0xdc, 0x80, 0x69, 0xd1, 0x2d, 0x98, 0x47, 0x81, + 0xc2, 0xc1, 0x25, 0x48, 0x3f, 0xa1, 0x6c, 0xe7, 0x28, 0x4d, 0x0e, 0x6f, 0x82, 0xd6, 0x3b, 0x13, 0x84, 0xb4, 0xab, + 0x9b, 0x6c, 0x49, 0xdf, 0x60, 0x7b, 0x87, 0x4e, 0x45, 0x05, 0xd5, 0xe7, 0x16, 0x4c, 0x96, 0x6c, 0x10, 0xb6, 0x84, + 0xe9, 0x99, 0x5e, 0x03, 0xf6, 0xf4, 0xfe, 0xc1, 0xce, 0x7c, 0x17, 0xb3, 0xd7, 0xfb, 0x65, 0x34, 0x56, 0x16, 0xbc, + 0xb9, 0x25, 0x76, 0x4b, 0x36, 0x9e, 0x2e, 0x0f, 0xcb, 0xe9, 0x12, 0x89, 0x9d, 0xa1, 0x5b, 0x8c, 0xcf, 0x97, 0x0b, + 0x9a, 0xe0, 0xd9, 0xc6, 0xaa, 0xf9, 0xd2, 0xa0, 0xa5, 0xa4, 0x0c, 0xd7, 0xdb, 0x12, 0xfd, 0xff, 0xd5, 0xc5, 0x2f, + 0x05, 0x78, 0x09, 0xc6, 0x02, 0x40, 0xb8, 0x07, 0xd3, 0x82, 0xd4, 0x46, 0xd9, 0x48, 0xd3, 0x30, 0xc5, 0x45, 0x60, + 0x52, 0xfa, 0xfd, 0x30, 0x67, 0x29, 0xf1, 0xa0, 0x43, 0xed, 0x28, 0x9d, 0xa7, 0xbe, 0x10, 0x04, 0x78, 0x24, 0x75, + 0x8e, 0x4d, 0xfe, 0x39, 0x9e, 0x05, 0x6a, 0x20, 0x82, 0x28, 0x3b, 0xc4, 0x47, 0x0c, 0x5c, 0x14, 0xe9, 0xb8, 0x9d, + 0xae, 0x88, 0xd5, 0xee, 0x31, 0x0b, 0x71, 0x92, 0x30, 0xd7, 0x2c, 0x1b, 0xb2, 0x2a, 0xc2, 0x04, 0x5d, 0x18, 0xd8, + 0xaf, 0x0d, 0x59, 0xb5, 0x7f, 0x00, 0x91, 0x5a, 0x6d, 0x19, 0x17, 0x5d, 0x65, 0x7c, 0x0b, 0x40, 0xd6, 0x8c, 0xb1, + 0x83, 0x7f, 0x8c, 0x67, 0xea, 0x9b, 0x28, 0xe4, 0x47, 0x07, 0xff, 0x80, 0xe4, 0xc3, 0x6f, 0x91, 0x99, 0x83, 0xe4, + 0x46, 0x41, 0x97, 0xcd, 0x59, 0xd7, 0x50, 0x9a, 0xb8, 0xf6, 0x4a, 0xbd, 0xf6, 0xa4, 0x59, 0x7b, 0x05, 0xba, 0x53, + 0x1b, 0xde, 0x43, 0xd9, 0xce, 0x82, 0x09, 0x3a, 0x9a, 0xdd, 0x81, 0x0e, 0xde, 0x29, 0x82, 0x5e, 0x26, 0xa1, 0xf1, + 0x08, 0x55, 0x46, 0xbd, 0x18, 0x0f, 0xaa, 0x93, 0x75, 0xc9, 0x3c, 0x03, 0xe6, 0xd8, 0x9e, 0x43, 0x62, 0x98, 0xab, + 0x83, 0x3a, 0x65, 0xe5, 0x30, 0xc7, 0x03, 0x78, 0xcd, 0xe4, 0x50, 0x0c, 0x72, 0x8d, 0xf2, 0x7d, 0xce, 0x8a, 0x61, + 0x39, 0xc8, 0x35, 0x37, 0x33, 0x6d, 0xc6, 0xa6, 0x4d, 0x74, 0x78, 0xe6, 0x15, 0x3b, 0x5a, 0xf5, 0x80, 0x8f, 0x05, + 0x4f, 0x66, 0xdf, 0xf3, 0xf1, 0x29, 0x70, 0x32, 0x9b, 0x9b, 0x68, 0x49, 0x6f, 0xa3, 0x94, 0x5e, 0x47, 0x6b, 0xba, + 0x8c, 0xce, 0x8d, 0x89, 0x71, 0x52, 0xc3, 0x39, 0x00, 0xad, 0x02, 0x48, 0x3c, 0xf5, 0xeb, 0x1d, 0x4f, 0xaa, 0x70, + 0x49, 0x53, 0x70, 0x1b, 0xf6, 0xed, 0x33, 0xcf, 0x7c, 0x89, 0xd4, 0x06, 0x31, 0xd6, 0xac, 0xa1, 0xe2, 0xc6, 0x5b, + 0xf7, 0x91, 0xa8, 0x61, 0xe7, 0xba, 0xd8, 0x44, 0xd5, 0x70, 0x32, 0x2d, 0x01, 0xb1, 0xb5, 0x1c, 0x0e, 0xdd, 0x11, + 0xb2, 0x7b, 0xfc, 0xe8, 0x40, 0xcf, 0x3d, 0x69, 0xb1, 0x6d, 0x5b, 0xfe, 0xc0, 0x10, 0xa6, 0xf4, 0xf3, 0x47, 0x3e, + 0x20, 0x56, 0x5c, 0xc2, 0xd9, 0x08, 0xd4, 0xd1, 0x0a, 0x9d, 0x7e, 0xab, 0xc2, 0x42, 0x1f, 0xe0, 0x9b, 0x9b, 0x28, + 0xa1, 0xb7, 0x51, 0xee, 0x91, 0xb5, 0x65, 0xcd, 0xe4, 0xf4, 0x34, 0x0b, 0x79, 0xfb, 0x40, 0x2f, 0x17, 0x00, 0xa2, + 0x35, 0x88, 0x7d, 0xa9, 0xeb, 0x01, 0x38, 0x0d, 0xa1, 0x49, 0x68, 0x04, 0x57, 0x15, 0x84, 0x11, 0x70, 0x25, 0xe1, + 0x6f, 0x30, 0x51, 0x81, 0x2f, 0xc0, 0x45, 0x26, 0x4d, 0x73, 0x1e, 0xd4, 0xfe, 0x48, 0xbe, 0x2a, 0xda, 0xde, 0xae, + 0x30, 0x9a, 0x60, 0xec, 0x89, 0xf6, 0x79, 0xa4, 0x1c, 0xc5, 0x45, 0x12, 0x66, 0xa3, 0x1b, 0x75, 0x9e, 0xd3, 0x6c, + 0x74, 0xab, 0x7f, 0x55, 0x74, 0x4c, 0x7f, 0xd1, 0x01, 0x6d, 0x94, 0xf4, 0xad, 0xe3, 0x6c, 0x40, 0xeb, 0xc5, 0xd2, + 0xf8, 0x5f, 0xcb, 0xd1, 0x0d, 0x95, 0xa3, 0x5b, 0xdf, 0x92, 0x6a, 0x32, 0x2d, 0x0e, 0x05, 0x1a, 0x52, 0x75, 0x7e, + 0x5f, 0x00, 0x3f, 0x57, 0x1a, 0xdf, 0x69, 0xf3, 0xbd, 0xd7, 0xfe, 0xd3, 0x4e, 0x9e, 0x40, 0xb1, 0x44, 0x05, 0xab, + 0x46, 0x60, 0xc7, 0xbe, 0xce, 0xe3, 0xc2, 0x8c, 0x52, 0x4c, 0xad, 0x49, 0x3f, 0x06, 0xae, 0x98, 0xf6, 0x0a, 0x70, + 0xb5, 0x04, 0x27, 0x01, 0x88, 0xa1, 0x09, 0x7b, 0x76, 0x0c, 0x51, 0xcf, 0x8d, 0x63, 0x94, 0x6c, 0xb8, 0x07, 0xc4, + 0x5a, 0xe6, 0xad, 0x5c, 0x02, 0x12, 0x78, 0xeb, 0x61, 0x52, 0x00, 0xc6, 0x60, 0xb9, 0x24, 0x3a, 0x8f, 0x87, 0x3e, + 0xa1, 0x5e, 0x68, 0xd4, 0x09, 0xd9, 0xd8, 0x12, 0x38, 0xfe, 0xb0, 0x3e, 0x04, 0x82, 0x57, 0x79, 0xae, 0xbf, 0xd2, + 0xba, 0xfe, 0x52, 0xe9, 0xb9, 0x63, 0xb9, 0xae, 0xdf, 0xb6, 0xa9, 0xd1, 0x0b, 0xb0, 0xf0, 0xdd, 0x28, 0xf3, 0x48, + 0x6e, 0x11, 0x52, 0x15, 0x58, 0xa9, 0x5b, 0x48, 0x30, 0xff, 0x4a, 0xce, 0x56, 0x65, 0xbe, 0x7a, 0xe4, 0x5e, 0x39, + 0x9b, 0x9e, 0xfe, 0x86, 0x04, 0xed, 0xb6, 0x23, 0xcd, 0xe3, 0x2d, 0x3a, 0x7c, 0x76, 0xad, 0x25, 0xe6, 0x4e, 0xa2, + 0xe2, 0xf9, 0x14, 0xb0, 0xd5, 0xb3, 0xec, 0x52, 0xf9, 0x58, 0xed, 0xe2, 0xf8, 0x99, 0xf3, 0x27, 0xa9, 0xc2, 0xb5, + 0x68, 0x28, 0x41, 0xc0, 0x9b, 0xc3, 0xd8, 0x15, 0xaa, 0x80, 0x86, 0xe6, 0x06, 0x8e, 0x73, 0x35, 0xac, 0x34, 0x01, + 0xd3, 0x52, 0x1e, 0x1d, 0xe0, 0xd0, 0xe4, 0x51, 0xbb, 0x69, 0x58, 0x19, 0xba, 0xd6, 0xe8, 0x73, 0x5b, 0xe9, 0x8c, + 0x37, 0x1b, 0xbe, 0x7f, 0x30, 0xa8, 0xf0, 0x27, 0x69, 0x8e, 0x46, 0x3b, 0x37, 0xdc, 0x69, 0x04, 0x66, 0xae, 0xe4, + 0x8a, 0xec, 0x8e, 0x92, 0x97, 0xdf, 0xd3, 0x0b, 0x0b, 0xe8, 0xcf, 0x7f, 0x2e, 0x26, 0x9c, 0xb4, 0xc4, 0x84, 0x68, + 0xe9, 0xa0, 0x45, 0x07, 0x3b, 0xca, 0x2b, 0xfb, 0x12, 0x2f, 0x9d, 0xe3, 0x7f, 0x5f, 0x8f, 0xb5, 0xab, 0x40, 0x68, + 0x75, 0x72, 0xbf, 0x3d, 0x59, 0x20, 0x6a, 0x40, 0x35, 0xbb, 0x2a, 0x47, 0x99, 0x76, 0x56, 0x64, 0xd3, 0x90, 0xb9, + 0xee, 0x66, 0x69, 0xd8, 0x4c, 0x76, 0x2c, 0x2c, 0x33, 0x0c, 0xd6, 0x4e, 0x15, 0x7d, 0x0e, 0x5a, 0x7e, 0x04, 0x2f, + 0x9b, 0xca, 0x33, 0x9f, 0xcd, 0x32, 0xe2, 0x05, 0x3a, 0xe7, 0x54, 0x2c, 0x9a, 0xd2, 0xb1, 0x72, 0xbb, 0x2d, 0xd1, + 0x58, 0xa2, 0x8c, 0x82, 0xa0, 0xb6, 0x41, 0xd8, 0x75, 0xe9, 0x9e, 0xf4, 0x69, 0x17, 0x9f, 0x56, 0xa0, 0xef, 0xf1, + 0x5d, 0x06, 0x12, 0x53, 0x4f, 0xf2, 0x50, 0x35, 0x9a, 0xa3, 0x93, 0x67, 0x49, 0xaa, 0xf1, 0xf9, 0x95, 0xec, 0xac, + 0x79, 0xb7, 0x1a, 0x53, 0xfc, 0x47, 0xea, 0xf6, 0x9d, 0xcb, 0xd0, 0x44, 0x7f, 0x2d, 0x0f, 0x5a, 0x0a, 0x0b, 0x8e, + 0xdb, 0xc6, 0x5f, 0xbf, 0xcd, 0x1c, 0x62, 0x58, 0xba, 0x1c, 0xde, 0x84, 0x0e, 0xdd, 0x5d, 0x65, 0x67, 0xae, 0x0f, + 0xa8, 0x53, 0x17, 0xeb, 0x36, 0xa0, 0x64, 0xc9, 0xbb, 0x75, 0x7a, 0x62, 0xa5, 0x5f, 0xf6, 0xc3, 0x9d, 0x79, 0xd4, + 0xec, 0xee, 0x76, 0x3b, 0x21, 0x6d, 0xfb, 0x60, 0xbc, 0x2f, 0x61, 0x21, 0xce, 0x3b, 0x6c, 0xef, 0xe7, 0xb0, 0x7a, + 0xc8, 0x07, 0x7f, 0xe0, 0x38, 0xc3, 0xe8, 0x67, 0xca, 0xd0, 0xe7, 0x45, 0x21, 0x2f, 0x55, 0xa7, 0x7c, 0xa1, 0x5b, + 0xcb, 0xd4, 0xfb, 0x75, 0xfc, 0xba, 0x15, 0x20, 0xc6, 0xeb, 0x8a, 0x95, 0xe2, 0x0d, 0xad, 0x30, 0xae, 0x81, 0xdb, + 0xe4, 0x50, 0x4b, 0xb5, 0x40, 0xd4, 0xe5, 0x27, 0x0f, 0x79, 0x64, 0xd4, 0x99, 0xf0, 0xdd, 0x43, 0xee, 0x4b, 0xd7, + 0x76, 0x9b, 0xf8, 0xb9, 0xa6, 0xed, 0xef, 0x0e, 0x74, 0x47, 0xeb, 0xee, 0x6f, 0x9e, 0xcd, 0xcf, 0x23, 0xf3, 0xc5, + 0x00, 0x9b, 0xb5, 0xcb, 0xb8, 0xec, 0x18, 0xee, 0x7b, 0xd3, 0x83, 0xb1, 0x80, 0x40, 0x62, 0x86, 0x5e, 0x06, 0x2e, + 0x70, 0x81, 0xbb, 0xc2, 0x80, 0x21, 0xae, 0x69, 0xc9, 0xad, 0xb6, 0xb2, 0xf5, 0x91, 0xb7, 0x51, 0x21, 0x58, 0xd7, + 0x1d, 0x37, 0x49, 0x0e, 0xc1, 0x09, 0x5b, 0xee, 0x7d, 0xed, 0xb5, 0x33, 0xfc, 0x30, 0x10, 0xce, 0x2d, 0xd1, 0x33, + 0x6a, 0x7b, 0xa8, 0xd5, 0xbd, 0x86, 0x57, 0xb9, 0x8d, 0x3c, 0xeb, 0x37, 0xf3, 0xd2, 0xb0, 0x2f, 0x78, 0x2d, 0x05, + 0x87, 0xc6, 0x76, 0x2b, 0xdc, 0x62, 0xf1, 0x8e, 0x56, 0x2b, 0x6b, 0x6d, 0xb5, 0xd7, 0x4a, 0x45, 0xef, 0x5e, 0x73, + 0x9c, 0x38, 0x4b, 0x61, 0xfb, 0xe1, 0xfd, 0x05, 0xbb, 0x26, 0x80, 0x41, 0x8b, 0xc9, 0x02, 0x25, 0xa8, 0x64, 0xad, + 0x6a, 0xb7, 0x53, 0xe2, 0x97, 0xfb, 0x45, 0x97, 0xd9, 0xce, 0xe3, 0xd7, 0x4d, 0xda, 0x67, 0x3e, 0x47, 0x3f, 0xcc, + 0xef, 0xac, 0x93, 0x92, 0x33, 0x8c, 0x6b, 0xf9, 0xff, 0x55, 0xf4, 0xa2, 0xc8, 0xd2, 0x68, 0x63, 0x78, 0x30, 0x1b, + 0x6a, 0xd3, 0x87, 0xc6, 0xa8, 0xdc, 0xb2, 0x51, 0x44, 0xb4, 0xba, 0x01, 0xc1, 0x8c, 0xe2, 0xbe, 0x44, 0x9b, 0x57, + 0xaa, 0x2c, 0xbc, 0xc3, 0x67, 0x36, 0x7a, 0xc3, 0xf6, 0x84, 0x50, 0xbe, 0x7b, 0x5a, 0x98, 0x55, 0x4b, 0x45, 0x83, + 0xed, 0x12, 0xde, 0xc5, 0xa8, 0xd2, 0x4f, 0x98, 0x6c, 0x59, 0x30, 0xd5, 0xff, 0xef, 0x8b, 0x2c, 0x6d, 0x53, 0x74, + 0x60, 0x3a, 0x9b, 0x3e, 0x9d, 0x74, 0x83, 0xeb, 0x0c, 0x58, 0x44, 0xb0, 0xa5, 0xc2, 0xf1, 0x28, 0xb5, 0x1b, 0x24, + 0x4c, 0x04, 0x37, 0x51, 0x2f, 0x3b, 0x5a, 0xa6, 0x64, 0x55, 0xc0, 0xf3, 0x2b, 0x57, 0x99, 0x8e, 0xa3, 0xa1, 0xdf, + 0x3f, 0x4b, 0x4d, 0xe8, 0x57, 0xea, 0xa5, 0x2a, 0xce, 0xc3, 0xa8, 0x3a, 0x54, 0x18, 0xa3, 0x25, 0x4d, 0xe1, 0x18, + 0xcc, 0xce, 0xc3, 0x14, 0x2f, 0x67, 0x9b, 0x84, 0x7d, 0xc1, 0x40, 0x2e, 0xb5, 0x41, 0xbd, 0xa6, 0x44, 0x6b, 0xd6, + 0xde, 0xcc, 0x29, 0xa1, 0xe7, 0xac, 0xf4, 0xef, 0x42, 0x6b, 0x10, 0x28, 0xca, 0x66, 0xca, 0xf4, 0x54, 0xb7, 0xf3, + 0x9c, 0x26, 0xb4, 0xa0, 0x2b, 0x52, 0x83, 0xbe, 0xd7, 0xc9, 0xd9, 0xd1, 0xc9, 0xce, 0xcc, 0x7a, 0xcc, 0x8a, 0xe1, + 0x64, 0x1a, 0xc3, 0x35, 0x2d, 0x76, 0xd7, 0xb4, 0x65, 0xf3, 0xc6, 0xd5, 0xd8, 0x38, 0x0d, 0xda, 0x05, 0xd2, 0x36, + 0xcd, 0xed, 0xa7, 0x1e, 0xb7, 0xbf, 0xae, 0xd9, 0x72, 0xda, 0x5b, 0x6f, 0xb7, 0xbd, 0x14, 0x6c, 0x44, 0x3d, 0x3e, + 0x7e, 0xad, 0xa4, 0xeb, 0x96, 0xcb, 0x4f, 0xe1, 0xd9, 0xe3, 0xeb, 0x97, 0x3e, 0xb8, 0x1c, 0xad, 0xda, 0xdc, 0xfd, + 0x72, 0x17, 0x59, 0xee, 0x8b, 0x86, 0x96, 0xeb, 0x19, 0x6a, 0x92, 0x67, 0xa3, 0xbd, 0x43, 0x2d, 0x58, 0xce, 0xba, + 0x09, 0x4f, 0x0c, 0x76, 0xec, 0x55, 0x63, 0x73, 0x54, 0xe6, 0x92, 0xd5, 0x20, 0x81, 0x3e, 0xc9, 0x33, 0x4d, 0x7f, + 0x2f, 0xc3, 0x7c, 0x74, 0x43, 0x73, 0xc0, 0x15, 0xab, 0xec, 0x25, 0x83, 0xd4, 0x55, 0x7b, 0x89, 0x2b, 0x5f, 0xe1, + 0x90, 0x6c, 0xf0, 0xc9, 0x30, 0x55, 0x9f, 0x5d, 0xf2, 0xe0, 0xff, 0x6d, 0xd5, 0x2a, 0x3d, 0x37, 0xc9, 0x0d, 0xc7, + 0xbf, 0x4e, 0xda, 0x3e, 0x26, 0x06, 0x09, 0x78, 0x6a, 0x17, 0x43, 0x35, 0xaa, 0x8a, 0x58, 0x94, 0xb9, 0x89, 0x39, + 0x76, 0x67, 0xd7, 0xd0, 0x41, 0x19, 0xfc, 0xba, 0xe1, 0x13, 0x73, 0x07, 0xb6, 0x02, 0x1d, 0x9d, 0x68, 0x2e, 0xc3, + 0xcc, 0x5c, 0x86, 0x69, 0xd7, 0x56, 0x81, 0xe1, 0x55, 0x5b, 0x25, 0x51, 0xae, 0x46, 0x3d, 0x6e, 0x66, 0xa9, 0xd9, + 0x8b, 0xbc, 0x7b, 0x4d, 0x7a, 0x12, 0x7f, 0xba, 0xf4, 0xe4, 0xf5, 0x30, 0x20, 0xf2, 0x4b, 0x96, 0x86, 0x6b, 0x14, + 0x04, 0xa7, 0x56, 0x3b, 0x90, 0xe6, 0x23, 0x40, 0xe6, 0xc7, 0x69, 0xf8, 0x4e, 0x8b, 0x73, 0xc8, 0x46, 0x69, 0x9c, + 0xd8, 0xd2, 0xa8, 0x87, 0xe0, 0xce, 0x7b, 0xc9, 0x63, 0x08, 0x7c, 0xf8, 0x1e, 0x37, 0x83, 0x8a, 0x6e, 0x4b, 0x4c, + 0x94, 0x36, 0x8f, 0xba, 0xe5, 0xa3, 0x86, 0x50, 0xc9, 0xca, 0xf0, 0x12, 0x68, 0xef, 0x8e, 0xc0, 0xa8, 0x72, 0x02, + 0x99, 0x61, 0xb1, 0x7f, 0x30, 0x4c, 0x95, 0xa0, 0x68, 0x28, 0x87, 0x4b, 0x94, 0x03, 0x62, 0x12, 0x08, 0x8c, 0x8a, + 0x41, 0xaa, 0x2b, 0x53, 0x2f, 0x06, 0xa9, 0xbe, 0x55, 0x91, 0xfa, 0x34, 0x0b, 0x2b, 0xaa, 0x5b, 0x44, 0xc7, 0x74, + 0x28, 0xe9, 0xd2, 0xec, 0xd4, 0x5c, 0x4b, 0x2f, 0xd4, 0x72, 0x7c, 0xaa, 0xd3, 0x60, 0x14, 0x4f, 0x5c, 0x8a, 0x7e, + 0xab, 0xf6, 0xb3, 0xff, 0x16, 0x53, 0x6a, 0xc4, 0xa6, 0xf6, 0x16, 0x31, 0xac, 0xda, 0xf7, 0x59, 0x95, 0x83, 0x76, + 0x17, 0x94, 0x8d, 0x95, 0x71, 0x9e, 0x6f, 0x04, 0x33, 0x07, 0x6d, 0x63, 0xd5, 0xf4, 0xa1, 0x37, 0x62, 0xd4, 0xde, + 0x98, 0x6a, 0xdc, 0x13, 0xf8, 0x69, 0x83, 0xa6, 0x7b, 0x91, 0xe7, 0xa8, 0x47, 0xde, 0xfd, 0xcf, 0x1c, 0xd9, 0x99, + 0x7c, 0x16, 0xcb, 0xa4, 0x6e, 0x1f, 0x93, 0x60, 0xa1, 0xea, 0x18, 0x5d, 0xb8, 0x91, 0x29, 0xed, 0xe7, 0xce, 0xf4, + 0x23, 0x9e, 0xc9, 0xfd, 0x76, 0x68, 0xd4, 0x97, 0x86, 0xb5, 0xa4, 0x88, 0xfa, 0x82, 0xde, 0x9a, 0xea, 0xe8, 0x80, + 0x7a, 0x1d, 0x81, 0xd5, 0x15, 0x6d, 0x50, 0x03, 0x30, 0x19, 0xd7, 0xb6, 0x36, 0x9f, 0x83, 0xa9, 0xad, 0xaa, 0xe0, + 0x09, 0xdd, 0x15, 0x4a, 0xf7, 0x26, 0x75, 0xdd, 0x1a, 0x62, 0x0b, 0x18, 0x10, 0xb8, 0xd1, 0x53, 0xd3, 0x1f, 0x34, + 0x51, 0x01, 0x68, 0xd0, 0xb8, 0x9d, 0xe9, 0x1c, 0x89, 0x7e, 0xa7, 0x36, 0x6d, 0x33, 0xd5, 0xab, 0xca, 0x07, 0x50, + 0xf1, 0x67, 0xe9, 0xf4, 0xdc, 0x8c, 0x58, 0x00, 0xe3, 0x1e, 0x38, 0x53, 0xbd, 0xe3, 0x0c, 0xac, 0x27, 0xf2, 0x3c, + 0x2b, 0x79, 0x22, 0x05, 0xcc, 0x88, 0xbc, 0xbc, 0x94, 0x02, 0x86, 0x41, 0x0d, 0x00, 0x5a, 0x34, 0x97, 0xd1, 0x84, + 0x3f, 0xaa, 0xe9, 0x5d, 0x79, 0xf8, 0x23, 0x9d, 0xeb, 0x9b, 0x71, 0x0d, 0x86, 0xca, 0xeb, 0x8a, 0xef, 0x64, 0xfa, + 0x86, 0x3f, 0xf6, 0x32, 0x2d, 0xe5, 0xba, 0xd8, 0xc9, 0xf2, 0xe8, 0x1b, 0xfe, 0x44, 0xe7, 0x39, 0x78, 0x5c, 0xd3, + 0x34, 0xbe, 0xdd, 0xc9, 0xf2, 0xcf, 0x6f, 0x1e, 0xdb, 0x3c, 0x8f, 0xc6, 0x35, 0xbd, 0xe6, 0xfc, 0xa3, 0xcb, 0x34, + 0xd1, 0x55, 0x8d, 0x1f, 0xff, 0xd3, 0xe6, 0x7a, 0x5c, 0xd3, 0x4b, 0x29, 0xaa, 0xe5, 0x4e, 0x51, 0x07, 0xdf, 0x1c, + 0xfc, 0x93, 0x7f, 0x63, 0xba, 0x77, 0x50, 0xd3, 0xbf, 0xd7, 0x71, 0x51, 0xf1, 0x62, 0xa7, 0xb8, 0x7f, 0xfc, 0xf3, + 0x9f, 0x8f, 0x6d, 0xc6, 0xc7, 0x35, 0xbd, 0xe5, 0x71, 0x47, 0xdb, 0x27, 0x4f, 0x1e, 0xf3, 0x7f, 0xd4, 0x35, 0xfd, + 0x95, 0xf9, 0xc1, 0x51, 0x8f, 0x33, 0x4f, 0x0f, 0x9f, 0xcb, 0x26, 0x6a, 0xc0, 0xd0, 0x43, 0x03, 0x58, 0x4a, 0xab, + 0xa6, 0xb9, 0xc3, 0x2b, 0x17, 0xdc, 0xbe, 0x4f, 0xe3, 0x34, 0x5e, 0xc1, 0x41, 0xb0, 0x41, 0xe3, 0xac, 0x02, 0x38, + 0x55, 0xe0, 0x3d, 0xa3, 0x92, 0x66, 0xa5, 0xfc, 0x95, 0xf3, 0x8f, 0x30, 0x68, 0x08, 0x69, 0xa3, 0x22, 0x03, 0xbd, + 0x59, 0xe9, 0xc8, 0x46, 0xe8, 0xbf, 0xd9, 0x8c, 0x83, 0xe3, 0xc3, 0xe8, 0xf5, 0xfb, 0x61, 0xc1, 0x44, 0x58, 0x10, + 0x42, 0xff, 0x0a, 0x0b, 0x70, 0x28, 0x29, 0x98, 0x97, 0xcf, 0xf8, 0x9e, 0x6b, 0xa3, 0xb0, 0x10, 0x44, 0x77, 0x91, + 0x7d, 0x40, 0xd5, 0xa3, 0xef, 0xd0, 0x0d, 0xf1, 0xb2, 0xc2, 0x82, 0xa1, 0x55, 0x0d, 0xcc, 0x10, 0x14, 0xff, 0x8a, + 0x87, 0x12, 0x7c, 0xe2, 0x01, 0x3e, 0x7a, 0x4c, 0x66, 0x5c, 0x5d, 0x6b, 0xdf, 0x9c, 0x87, 0x05, 0x0d, 0x74, 0xdb, + 0x21, 0xe8, 0x40, 0xe4, 0xbf, 0x00, 0x4f, 0x81, 0x81, 0x0f, 0x0b, 0xbb, 0xee, 0xc0, 0xf3, 0xf9, 0xd5, 0xb0, 0x8e, + 0x2e, 0xfc, 0xe8, 0xaf, 0xd6, 0x85, 0x3d, 0x23, 0x53, 0x79, 0x58, 0x0e, 0x27, 0xd3, 0xc1, 0x40, 0xba, 0x38, 0x6e, + 0xc7, 0xd9, 0xfc, 0xd7, 0xb9, 0x5c, 0x2c, 0x50, 0xf7, 0x8d, 0xf3, 0x3a, 0xd3, 0x7f, 0x23, 0xed, 0x7c, 0xf0, 0xea, + 0xf8, 0xb7, 0xd3, 0x93, 0xe3, 0x17, 0xe0, 0x7c, 0xf0, 0xfe, 0xf9, 0xf7, 0xcf, 0xdf, 0xa9, 0xe0, 0xee, 0x6a, 0xce, + 0xfb, 0x7d, 0x27, 0xf5, 0x09, 0xf9, 0xb0, 0x22, 0xfb, 0x61, 0xfc, 0xb0, 0x50, 0x46, 0x0f, 0xe4, 0x90, 0x59, 0x28, + 0x64, 0xa8, 0xa2, 0xb6, 0xbf, 0xcb, 0xe1, 0xc4, 0x03, 0xb3, 0xb8, 0x69, 0x88, 0x70, 0xfd, 0x96, 0xdb, 0x20, 0x6b, + 0xf2, 0xc8, 0xeb, 0x07, 0x27, 0x53, 0xe9, 0xd8, 0xc2, 0x82, 0x41, 0xd9, 0xd0, 0xa6, 0xe3, 0x6c, 0x5e, 0x2c, 0x6c, + 0xbb, 0xdc, 0x02, 0x19, 0xa5, 0xd9, 0xf9, 0x79, 0xa8, 0xa0, 0xab, 0x8f, 0x40, 0x03, 0x60, 0x1a, 0x55, 0xb8, 0x16, + 0xf1, 0x99, 0x5f, 0x7e, 0x34, 0xf6, 0x9a, 0x77, 0x85, 0xba, 0x27, 0xd3, 0xac, 0xaa, 0x31, 0xa0, 0x83, 0x09, 0xe5, + 0x6e, 0xd0, 0x4d, 0x30, 0x19, 0xd5, 0x96, 0x5f, 0xe7, 0xd5, 0xc2, 0x34, 0xc7, 0x0d, 0x43, 0xe5, 0x95, 0x7c, 0x2e, + 0x1b, 0x88, 0x0c, 0x24, 0xc3, 0xb0, 0x47, 0x63, 0x14, 0xa9, 0xef, 0xed, 0x7a, 0xc7, 0x6f, 0x72, 0x09, 0xd1, 0x14, + 0x33, 0x90, 0xce, 0x1f, 0x0b, 0xe5, 0x5c, 0x2e, 0x19, 0x9f, 0x8b, 0xc5, 0x11, 0xb8, 0x9d, 0xcf, 0xc5, 0x22, 0xc2, + 0xa0, 0x7c, 0x19, 0xc4, 0x2a, 0x01, 0xbb, 0x17, 0x07, 0xe1, 0xdb, 0x09, 0x6d, 0x60, 0x37, 0x90, 0x64, 0x83, 0xd2, + 0xae, 0x34, 0x44, 0xb9, 0x53, 0x1e, 0x6d, 0x10, 0x79, 0x88, 0x55, 0xf3, 0xaa, 0xed, 0xc9, 0x66, 0x2e, 0x26, 0xb8, + 0xca, 0x62, 0x26, 0xa7, 0xf1, 0x21, 0x2b, 0xa6, 0x31, 0x94, 0x12, 0xa7, 0x69, 0x18, 0xd3, 0x09, 0x15, 0x84, 0x24, + 0x8c, 0xcf, 0xe3, 0x05, 0x4d, 0x50, 0x4a, 0x10, 0x42, 0xc8, 0x8f, 0x11, 0xda, 0xe6, 0xc0, 0x92, 0xb7, 0xdb, 0xcf, + 0xd3, 0xcf, 0xed, 0x18, 0x2e, 0xa3, 0x22, 0x74, 0x83, 0xce, 0x1a, 0xfe, 0x8d, 0xa8, 0xa0, 0x31, 0x56, 0x0c, 0x41, + 0xc0, 0x0b, 0x8c, 0x4a, 0x58, 0x90, 0x98, 0x55, 0x10, 0x45, 0xa0, 0x9c, 0xc7, 0x0b, 0x56, 0xd0, 0xa6, 0xcd, 0x69, + 0xac, 0x4d, 0x82, 0x7a, 0x0e, 0x4b, 0x6d, 0x4f, 0x2a, 0x15, 0x62, 0x8f, 0xcf, 0x44, 0x74, 0xad, 0x0d, 0x0d, 0x00, + 0x05, 0x4a, 0xc9, 0xc5, 0xaf, 0xbf, 0xdc, 0xc3, 0x4d, 0x41, 0xff, 0xb3, 0x8d, 0x89, 0x76, 0x96, 0xab, 0x43, 0x6f, + 0xbe, 0xa0, 0x71, 0x9e, 0x43, 0x28, 0x36, 0x83, 0x40, 0x2e, 0xb2, 0x0a, 0x22, 0x5a, 0xdc, 0x06, 0x26, 0x24, 0x1c, + 0xb4, 0xe9, 0x03, 0xa4, 0x36, 0xc4, 0xe4, 0xca, 0x13, 0x03, 0xbb, 0xad, 0x12, 0x04, 0x1c, 0xe9, 0x79, 0xf6, 0xa9, + 0x89, 0xb1, 0xa6, 0xa9, 0x99, 0x89, 0xb7, 0xa1, 0x10, 0x0d, 0x5a, 0x10, 0xcd, 0xe0, 0xfd, 0x73, 0xc9, 0xf1, 0xaa, + 0x03, 0x3f, 0xe0, 0x9d, 0x8b, 0x33, 0xaf, 0x66, 0x1e, 0x91, 0x53, 0x8f, 0x73, 0x44, 0xbf, 0xe4, 0x61, 0x35, 0xd2, + 0xc9, 0x18, 0x2b, 0x89, 0x83, 0xde, 0x06, 0x0b, 0xe6, 0x84, 0xae, 0x78, 0x68, 0xf9, 0xf8, 0x17, 0xc8, 0x64, 0x94, + 0xd4, 0x58, 0xd1, 0x95, 0x16, 0x23, 0xce, 0x6b, 0x98, 0xa5, 0xc9, 0x8a, 0x2e, 0x16, 0x9a, 0x34, 0x0b, 0x65, 0x1a, + 0xe0, 0x13, 0x68, 0x31, 0x72, 0x0f, 0x35, 0x6d, 0x20, 0x34, 0xec, 0x0e, 0x01, 0x1f, 0xb9, 0x87, 0x0e, 0xff, 0x3f, + 0xcf, 0x2e, 0x10, 0x69, 0xef, 0xd2, 0x44, 0xc6, 0x23, 0x75, 0x03, 0x07, 0xc5, 0xf8, 0xd8, 0x37, 0x13, 0xbf, 0x70, + 0x46, 0xef, 0x93, 0xca, 0x77, 0xf8, 0x60, 0xf9, 0xe3, 0x4d, 0xcd, 0xac, 0x8c, 0x60, 0x3d, 0x6c, 0xb7, 0xb8, 0x20, + 0xda, 0x2e, 0x80, 0xd4, 0x33, 0x5e, 0x2d, 0x7c, 0xe3, 0xd5, 0xf8, 0x0e, 0xe3, 0x55, 0x67, 0x85, 0x15, 0xe6, 0x64, + 0x83, 0xfa, 0x2c, 0x25, 0xcf, 0xcf, 0x51, 0x26, 0xd8, 0x74, 0x39, 0x2b, 0xa9, 0x4a, 0x25, 0xb4, 0x17, 0xfb, 0x19, + 0xe3, 0x1b, 0x82, 0x71, 0x56, 0x1c, 0x46, 0x02, 0x55, 0xa9, 0xa4, 0x0e, 0x7b, 0x05, 0xa8, 0xc7, 0xe0, 0xbd, 0xc1, + 0x10, 0x35, 0x32, 0x76, 0xd3, 0x06, 0x42, 0x43, 0x63, 0x3d, 0xda, 0xb3, 0xd6, 0xa3, 0xdb, 0x6d, 0x65, 0xfc, 0xed, + 0xe4, 0xba, 0x48, 0x10, 0x55, 0x58, 0x8d, 0x26, 0xc0, 0x9b, 0x26, 0xf6, 0xb6, 0xe4, 0x94, 0x16, 0x18, 0x3e, 0xfb, + 0xaf, 0xb0, 0x74, 0x2a, 0x89, 0x92, 0xcc, 0xca, 0x68, 0xe0, 0xce, 0xc1, 0x67, 0x71, 0x05, 0x6b, 0x00, 0x22, 0x39, + 0xa2, 0x87, 0xeb, 0x5f, 0xa1, 0x74, 0x99, 0x25, 0x99, 0x49, 0xc8, 0xcc, 0x45, 0xda, 0xce, 0x3a, 0x98, 0x38, 0x93, + 0x5a, 0x6f, 0x2c, 0xe4, 0xd0, 0x20, 0x3f, 0x80, 0x32, 0xc4, 0xe1, 0x93, 0x0f, 0x26, 0x54, 0xaa, 0x50, 0xaa, 0x8d, + 0x6e, 0x76, 0x03, 0xaf, 0xbc, 0xcf, 0x2e, 0x79, 0x59, 0xc5, 0x97, 0x2b, 0x63, 0x49, 0xcc, 0xd9, 0x5d, 0x6e, 0x7b, + 0x54, 0x98, 0x57, 0xaf, 0x9f, 0x7f, 0x7f, 0xdc, 0x78, 0xb5, 0x8b, 0x38, 0x1a, 0x82, 0x6d, 0xc5, 0x18, 0xa3, 0xb7, + 0xf8, 0x34, 0x98, 0x28, 0xd7, 0x08, 0xf4, 0x2e, 0x05, 0xfd, 0xf6, 0x97, 0x7a, 0x02, 0x5e, 0x72, 0xbd, 0xfc, 0x92, + 0x8f, 0x80, 0x25, 0x2a, 0xf4, 0xac, 0x30, 0x37, 0x2b, 0xb3, 0x3b, 0xbb, 0x15, 0x99, 0x69, 0x57, 0x1a, 0x19, 0x88, + 0x57, 0xdb, 0x61, 0x2c, 0x5c, 0xba, 0xa6, 0xdb, 0xc1, 0xae, 0x96, 0x9e, 0x25, 0xf2, 0x76, 0x5b, 0x42, 0x87, 0xec, + 0x80, 0x7b, 0x2f, 0xe3, 0x1b, 0x78, 0x59, 0x7a, 0xdd, 0x6c, 0x06, 0x4f, 0x00, 0x33, 0xe1, 0xc2, 0x59, 0x16, 0xc7, + 0x2c, 0x4b, 0x42, 0x15, 0x9b, 0xab, 0x21, 0xf2, 0x56, 0x84, 0xd6, 0xec, 0xaf, 0x50, 0x8c, 0xc0, 0xee, 0xe4, 0xe4, + 0x63, 0xb6, 0x9a, 0xad, 0x01, 0x35, 0xff, 0x32, 0x13, 0x40, 0x73, 0xed, 0x5a, 0xb0, 0x4d, 0xa1, 0xcd, 0x75, 0xfd, + 0x34, 0x5e, 0xc5, 0x09, 0xa8, 0x6e, 0xc0, 0x5b, 0xe4, 0x46, 0x8b, 0xae, 0x0c, 0xba, 0x28, 0xbd, 0xa7, 0x1c, 0x4b, + 0x0a, 0x1d, 0x7d, 0xef, 0x09, 0x75, 0xee, 0x19, 0xc0, 0x25, 0x8d, 0x9a, 0xa7, 0x5a, 0xca, 0x58, 0x00, 0x2c, 0x74, + 0x30, 0x53, 0x64, 0x2b, 0xba, 0x32, 0x98, 0x14, 0xf0, 0xd6, 0x00, 0x7f, 0x88, 0xac, 0x52, 0x77, 0xc5, 0x32, 0x2c, + 0x3d, 0xfb, 0xeb, 0x7e, 0x3f, 0xf6, 0xec, 0xaf, 0x57, 0x9a, 0xd6, 0xc5, 0xed, 0x06, 0x90, 0x1a, 0x03, 0x88, 0x1c, + 0xeb, 0x81, 0x30, 0x11, 0xc5, 0x9a, 0xbe, 0x7f, 0xc7, 0x26, 0x8b, 0x02, 0xa1, 0xdf, 0xa9, 0xd7, 0x93, 0x92, 0x80, + 0x4e, 0xad, 0x62, 0x47, 0x03, 0x6d, 0xf6, 0x01, 0x01, 0x51, 0xfd, 0x8c, 0x6c, 0xbe, 0x50, 0xce, 0xc5, 0x2a, 0x7c, + 0xf8, 0x98, 0x42, 0x40, 0xe1, 0x8e, 0x1a, 0x9d, 0xb7, 0x21, 0x12, 0x28, 0x2b, 0x14, 0xb1, 0xe6, 0xc5, 0x5a, 0x12, + 0x32, 0x1f, 0x2f, 0x50, 0x70, 0xe5, 0x80, 0x5d, 0x39, 0x9b, 0x0c, 0xcb, 0x88, 0xb3, 0xf0, 0xee, 0x6f, 0x26, 0x0b, + 0x82, 0x9a, 0x2b, 0x3f, 0x90, 0xe3, 0x4e, 0xa6, 0xc6, 0x9e, 0x6a, 0xd4, 0x20, 0x98, 0x8c, 0x20, 0x30, 0xdc, 0xf0, + 0x0b, 0x3e, 0x3e, 0x58, 0x10, 0x50, 0x91, 0x59, 0xb3, 0x10, 0xf3, 0xe2, 0xf0, 0x11, 0xa0, 0xc6, 0x8c, 0x0e, 0x9e, + 0x4c, 0x39, 0x83, 0x43, 0x94, 0x8e, 0x41, 0x46, 0x2b, 0xe0, 0xb7, 0x50, 0xbf, 0x5b, 0x27, 0xbe, 0x0f, 0xfd, 0x2a, + 0xe8, 0x79, 0x0c, 0x0c, 0x47, 0x34, 0xd9, 0x0f, 0xf9, 0x60, 0x32, 0x00, 0x6d, 0x89, 0xb7, 0xfb, 0x5a, 0x5a, 0x71, + 0x73, 0xba, 0x74, 0xba, 0x7f, 0xd2, 0x26, 0x48, 0x22, 0x95, 0xac, 0x54, 0xc4, 0x00, 0x42, 0x59, 0xaa, 0x6d, 0xb2, + 0x06, 0xcb, 0x0a, 0xb3, 0xa4, 0xb9, 0x41, 0x49, 0xdc, 0xdd, 0x0c, 0x1c, 0xa3, 0x66, 0x1d, 0x87, 0x65, 0xcb, 0x8d, + 0x1a, 0xe0, 0x73, 0x12, 0x56, 0xd8, 0x1b, 0xce, 0x4c, 0x7a, 0x67, 0x3a, 0x5c, 0x1d, 0x73, 0xf6, 0x8a, 0x23, 0x18, + 0x47, 0x82, 0x37, 0x1e, 0xba, 0x64, 0x1a, 0x2a, 0x32, 0x65, 0x1c, 0x4c, 0x7b, 0x80, 0x7b, 0xcf, 0xc1, 0x38, 0x8c, + 0x0d, 0x2a, 0x4b, 0xea, 0x53, 0xef, 0x2e, 0x04, 0x82, 0xb4, 0xd6, 0xcb, 0x7c, 0x86, 0xa7, 0x67, 0x84, 0xb2, 0x3f, + 0xe4, 0xf0, 0x05, 0xd8, 0x51, 0x90, 0xa3, 0x09, 0x7f, 0xf2, 0x70, 0x37, 0x50, 0x15, 0x1f, 0x04, 0x7b, 0xb1, 0x48, + 0xf7, 0x82, 0x81, 0x80, 0x5f, 0x05, 0xdf, 0xab, 0xa4, 0xdc, 0x3b, 0x8f, 0x8b, 0xbd, 0x78, 0x15, 0x17, 0xd5, 0xde, + 0x75, 0x56, 0x2d, 0xf7, 0x4c, 0x87, 0x00, 0x9a, 0x37, 0x18, 0xc4, 0x83, 0x60, 0x2f, 0x18, 0x14, 0x66, 0x6a, 0x57, + 0xac, 0x6c, 0x1c, 0x67, 0x26, 0x44, 0x59, 0xd0, 0x0c, 0x10, 0xd6, 0x38, 0x0d, 0x80, 0x4f, 0x5d, 0xb3, 0x94, 0x9e, + 0x63, 0xb8, 0x01, 0x31, 0x5d, 0x43, 0x1f, 0x80, 0x47, 0x5e, 0xd3, 0x18, 0x96, 0xc0, 0xf9, 0x60, 0x40, 0xce, 0x21, + 0x72, 0xc1, 0x9a, 0xda, 0x20, 0x0e, 0xe1, 0x5a, 0xd9, 0x69, 0xef, 0x02, 0x33, 0x6d, 0xb7, 0x80, 0xa8, 0x3c, 0x21, + 0xfd, 0xbe, 0xfd, 0x86, 0xfa, 0x17, 0xec, 0x25, 0xd8, 0x5f, 0x15, 0x55, 0x98, 0x4b, 0xa5, 0xf9, 0xbe, 0x60, 0x47, + 0x03, 0x15, 0x71, 0x78, 0xc7, 0x91, 0xa2, 0x8d, 0xca, 0x65, 0xd9, 0x93, 0x65, 0xc3, 0x57, 0xe2, 0x92, 0x3b, 0x3f, + 0xae, 0x4a, 0xca, 0xbc, 0xca, 0x56, 0x8a, 0xfd, 0x9b, 0x71, 0xcd, 0xfd, 0x81, 0xf5, 0x67, 0xf3, 0x15, 0x5c, 0x5b, + 0xbd, 0x77, 0x4d, 0xae, 0x11, 0x39, 0x4b, 0x28, 0x97, 0xd4, 0x36, 0x0f, 0x6f, 0xe9, 0xfb, 0xfc, 0xea, 0xdb, 0x4c, + 0xa7, 0xf1, 0x59, 0x85, 0x85, 0x0b, 0xd1, 0x8a, 0xe0, 0xd0, 0x90, 0x8b, 0xe6, 0x11, 0x60, 0xae, 0x7d, 0xb6, 0x82, + 0x82, 0xd4, 0xa7, 0x15, 0x7a, 0xb7, 0x42, 0xc2, 0x0b, 0xcd, 0x2e, 0xdd, 0x0f, 0xa4, 0x8c, 0xdb, 0x43, 0x4b, 0x98, + 0xb4, 0xbc, 0x08, 0xef, 0xbd, 0xe6, 0x26, 0xf7, 0x32, 0xc4, 0xe8, 0x45, 0x9e, 0x9d, 0x80, 0xb1, 0xee, 0x92, 0x9d, + 0x0d, 0x4f, 0xfc, 0x86, 0xe7, 0xac, 0x45, 0xa3, 0xe9, 0x92, 0x25, 0xfd, 0x7e, 0x0c, 0x26, 0xde, 0x29, 0xcb, 0xe1, + 0x57, 0xbe, 0xa0, 0x6b, 0x06, 0x98, 0x62, 0xf4, 0x1c, 0x12, 0x52, 0x44, 0x22, 0x59, 0xab, 0x93, 0xe4, 0x33, 0xdd, + 0x05, 0x60, 0xf4, 0xf3, 0x59, 0x1a, 0x2d, 0xef, 0x34, 0xb3, 0x40, 0xf2, 0x0c, 0x7d, 0xd7, 0xc1, 0xf6, 0xc6, 0x3e, + 0x48, 0x39, 0x3f, 0x14, 0xd3, 0xc1, 0x80, 0x13, 0x0d, 0x37, 0x5e, 0x2a, 0x71, 0xad, 0x6e, 0x71, 0xc7, 0x30, 0x96, + 0xfa, 0xb6, 0x88, 0xc1, 0x01, 0xbb, 0x68, 0x65, 0xb7, 0x0f, 0xb0, 0xaf, 0x1c, 0xef, 0x52, 0x65, 0x77, 0x7a, 0xcc, + 0x34, 0x97, 0xad, 0x26, 0x9d, 0x54, 0xdc, 0x4d, 0xe4, 0x9b, 0xdc, 0x41, 0x97, 0xcb, 0xb1, 0xe6, 0x2d, 0x07, 0xa0, + 0xa2, 0x1f, 0x29, 0xaa, 0xfb, 0x05, 0x8e, 0x30, 0xf7, 0xd6, 0x6d, 0x3e, 0xd9, 0x37, 0x05, 0x0e, 0x91, 0x27, 0x6d, + 0x34, 0x05, 0x74, 0xef, 0xe2, 0x61, 0x57, 0xbf, 0x2d, 0xdd, 0x05, 0x4a, 0xb4, 0x53, 0x71, 0xc3, 0x8f, 0x89, 0x3a, + 0x9d, 0x69, 0x43, 0xe8, 0x5f, 0x19, 0x71, 0x7f, 0x69, 0x5c, 0xc5, 0x9b, 0xde, 0xe5, 0x33, 0x0e, 0x75, 0x76, 0x43, + 0x28, 0x00, 0x57, 0xed, 0xe9, 0xd4, 0x8d, 0x21, 0xbd, 0x52, 0xa2, 0xdb, 0xe0, 0x60, 0x77, 0xfa, 0x8c, 0xa3, 0xe8, + 0xc7, 0xa8, 0x91, 0xaf, 0x23, 0xf1, 0x50, 0x0e, 0xe2, 0x87, 0x05, 0x5d, 0x46, 0xe2, 0x61, 0x31, 0x88, 0x1f, 0xca, + 0xba, 0xde, 0x3d, 0x57, 0xee, 0xee, 0x23, 0xf2, 0xac, 0x3b, 0x7b, 0xa9, 0x84, 0x8d, 0x81, 0x67, 0xd7, 0x02, 0xc2, + 0x29, 0x78, 0x22, 0x5b, 0x4b, 0x1f, 0x3a, 0xb7, 0xfb, 0xd8, 0x32, 0x49, 0x10, 0xf4, 0xbc, 0xcd, 0x26, 0x51, 0xec, + 0x6c, 0xf3, 0xe8, 0xc3, 0x29, 0x90, 0xd0, 0xed, 0xb6, 0x59, 0x57, 0x6b, 0x40, 0x31, 0x0d, 0xc7, 0x7c, 0xbf, 0x18, + 0x5d, 0xfb, 0xee, 0xfa, 0xfb, 0xc5, 0x68, 0x49, 0x86, 0x13, 0x33, 0xf9, 0xf1, 0xd1, 0x78, 0x16, 0x47, 0x93, 0xba, + 0xe3, 0xb4, 0xd0, 0xf8, 0xa7, 0xde, 0x2d, 0x14, 0x81, 0x53, 0x31, 0x82, 0x23, 0xa7, 0x42, 0x39, 0x29, 0x35, 0x30, + 0xfc, 0xf7, 0xaa, 0x1d, 0x6d, 0xda, 0xab, 0xb8, 0x4a, 0x96, 0x99, 0xb8, 0xd0, 0xe1, 0xc3, 0x75, 0x74, 0x71, 0x1b, + 0xd0, 0xce, 0xbb, 0x4c, 0x3b, 0x7e, 0x9d, 0x34, 0xe8, 0x89, 0xab, 0x99, 0x01, 0xb7, 0xee, 0x47, 0x68, 0x86, 0xc0, + 0x68, 0x79, 0xfe, 0x16, 0x31, 0xb7, 0x7f, 0x51, 0x36, 0xbf, 0x8a, 0xf6, 0x39, 0x32, 0x52, 0xb6, 0xc9, 0x48, 0x05, + 0x46, 0x98, 0x52, 0x24, 0x71, 0x15, 0x42, 0x20, 0xfb, 0x2f, 0x29, 0xae, 0xc5, 0xd2, 0x7b, 0x0d, 0xc2, 0x04, 0xdb, + 0x05, 0xed, 0x57, 0xb7, 0x73, 0x5b, 0x69, 0xb1, 0x47, 0xea, 0xfb, 0xdc, 0xd9, 0xae, 0x68, 0xf2, 0xf7, 0x65, 0x03, + 0xda, 0x00, 0xa2, 0xbc, 0xab, 0x8f, 0x4a, 0xe0, 0x64, 0xc4, 0x0d, 0x25, 0x46, 0x2f, 0xe8, 0xea, 0x44, 0xee, 0xd9, + 0xa9, 0x79, 0x53, 0x31, 0x53, 0x71, 0xe5, 0x9b, 0x3d, 0xf3, 0x1f, 0x0c, 0x05, 0x2d, 0xc1, 0xc0, 0xdb, 0x9c, 0xf1, + 0xe8, 0x40, 0x77, 0x6d, 0x74, 0x5a, 0xb0, 0x59, 0x50, 0x97, 0x75, 0xdd, 0xc6, 0x83, 0x46, 0x1c, 0x14, 0xc5, 0xaa, + 0x50, 0x23, 0xe1, 0x89, 0x40, 0xc0, 0x94, 0x5d, 0xf2, 0xc8, 0x08, 0x6a, 0x7a, 0x13, 0x0a, 0x1b, 0x0a, 0xfe, 0x2a, + 0x51, 0x4d, 0x6f, 0x42, 0x9b, 0x4c, 0x9c, 0x66, 0x10, 0xc1, 0x8c, 0xd8, 0xee, 0xb7, 0x80, 0x36, 0xb7, 0x66, 0xb4, + 0xa9, 0x6b, 0xab, 0xad, 0x42, 0x2e, 0x29, 0x52, 0x96, 0xff, 0x4e, 0x4d, 0x05, 0x25, 0xb5, 0x5c, 0xf4, 0x26, 0x4d, + 0x17, 0x3d, 0x9e, 0x19, 0x49, 0xa0, 0x72, 0xcb, 0x1d, 0xa3, 0x3f, 0x84, 0x05, 0x1e, 0x31, 0x71, 0x62, 0xc1, 0xdc, + 0xea, 0x88, 0x65, 0x73, 0xb1, 0x18, 0xad, 0x24, 0x84, 0x0d, 0x3e, 0x64, 0xd9, 0xbc, 0xd4, 0x0f, 0xa1, 0x2f, 0x2c, + 0x7d, 0x03, 0x76, 0xb1, 0xc1, 0x4a, 0x96, 0x01, 0xf8, 0x5e, 0xd0, 0xcd, 0x4a, 0x96, 0x91, 0x54, 0xdd, 0x8f, 0x6b, + 0x2c, 0x41, 0xa5, 0x15, 0x2a, 0x2d, 0xa9, 0xb1, 0x20, 0xf0, 0x55, 0xd5, 0xe5, 0x43, 0xb2, 0xab, 0x40, 0x3d, 0x75, + 0xd4, 0x80, 0x53, 0xa0, 0xaa, 0xc0, 0x82, 0x24, 0xa8, 0x0c, 0x5d, 0x15, 0x98, 0x56, 0x60, 0x9a, 0xa9, 0xc2, 0x45, + 0x99, 0x1d, 0x4a, 0xb3, 0x5e, 0xf2, 0x59, 0x3c, 0x08, 0x93, 0x61, 0x4c, 0x1e, 0x22, 0xd4, 0xfe, 0x7e, 0x1e, 0xc5, + 0x5a, 0x2e, 0x79, 0xe1, 0xfc, 0xe2, 0xaf, 0x3f, 0x63, 0xaf, 0x7b, 0x8a, 0xc1, 0x02, 0x9c, 0xa5, 0xed, 0x65, 0x26, + 0xde, 0xca, 0x56, 0x70, 0x1c, 0xcc, 0xa2, 0x1c, 0x56, 0x3d, 0x39, 0xa2, 0xb9, 0xc8, 0xb5, 0x77, 0x11, 0x22, 0x07, + 0x99, 0x3d, 0x06, 0xd8, 0x8d, 0xf0, 0x75, 0x68, 0x6d, 0x6e, 0x75, 0x85, 0xf8, 0x1b, 0x25, 0x12, 0x3f, 0x49, 0xf9, + 0x71, 0xbd, 0x52, 0xb9, 0x2a, 0x83, 0xc7, 0xaa, 0x9b, 0xc1, 0x33, 0xed, 0x7b, 0xac, 0xfd, 0x5b, 0xdb, 0xcd, 0xf1, + 0xde, 0x83, 0x07, 0xad, 0xff, 0xad, 0x27, 0x21, 0xb4, 0x57, 0x4e, 0x52, 0x77, 0xd4, 0xe8, 0x99, 0xc9, 0x1a, 0x51, + 0x09, 0x53, 0xbb, 0x53, 0x39, 0x06, 0x6a, 0x3a, 0x80, 0x6b, 0x89, 0x9a, 0xa0, 0x27, 0x05, 0x1b, 0xc3, 0x11, 0x67, + 0x71, 0xd0, 0x0e, 0x63, 0x14, 0x2f, 0xe7, 0x4a, 0xbc, 0x9c, 0x1f, 0x31, 0x0e, 0xd0, 0x5a, 0x80, 0x54, 0xaf, 0x61, + 0x3f, 0x73, 0x05, 0x0b, 0x6c, 0xee, 0x7c, 0x07, 0x16, 0xc8, 0x10, 0x27, 0x9b, 0xe3, 0x64, 0x8f, 0x6b, 0x3d, 0xf7, + 0x02, 0x1f, 0x27, 0xf5, 0xc2, 0xab, 0xab, 0x6c, 0xd7, 0xb5, 0x64, 0xe5, 0xbc, 0x18, 0x4c, 0x20, 0x28, 0x4b, 0x39, + 0x2f, 0x86, 0x93, 0x05, 0xcd, 0xe1, 0xc7, 0xa2, 0x81, 0x0e, 0xb1, 0x1c, 0x24, 0x70, 0xe9, 0xec, 0x31, 0xe0, 0x0d, + 0xa5, 0x16, 0x77, 0x63, 0x1d, 0x39, 0xd6, 0x51, 0xec, 0x87, 0x31, 0xe0, 0xca, 0x3a, 0x81, 0xf7, 0xdd, 0xd7, 0xc7, + 0x26, 0x20, 0xab, 0x76, 0x85, 0x57, 0xa3, 0xdc, 0x75, 0xa5, 0xd1, 0x97, 0x94, 0x9e, 0xf0, 0x82, 0xa7, 0x92, 0xed, + 0xb6, 0x67, 0xe0, 0x6c, 0x89, 0x87, 0xc4, 0x3b, 0x46, 0xf4, 0x62, 0xda, 0xc8, 0xcc, 0x09, 0x9c, 0xd9, 0xee, 0xb2, + 0x8d, 0xf9, 0xb1, 0x03, 0x1c, 0x2c, 0x82, 0x90, 0xb8, 0x21, 0x0c, 0x13, 0x3b, 0x2a, 0x87, 0x5a, 0x08, 0xd7, 0xb5, + 0xf0, 0x3a, 0x4e, 0xcb, 0x18, 0x5c, 0xa4, 0xb5, 0x6d, 0xe2, 0x1d, 0x74, 0xdd, 0xf3, 0x63, 0x6e, 0x75, 0x8c, 0xb6, + 0x90, 0x7e, 0x3b, 0x3a, 0xbd, 0xe7, 0x30, 0x00, 0x4d, 0x0f, 0x66, 0x55, 0xfb, 0x4c, 0xe2, 0xe6, 0xb4, 0x13, 0x84, + 0x44, 0x20, 0x8a, 0xd2, 0x19, 0x61, 0xfa, 0x77, 0x9a, 0xcb, 0x2a, 0x5a, 0xdd, 0xcb, 0x33, 0x87, 0x3c, 0x0b, 0xbd, + 0xed, 0x41, 0xab, 0xe6, 0x6e, 0x30, 0x4e, 0xdc, 0x6e, 0xef, 0xfc, 0xbf, 0x65, 0x5d, 0x5b, 0xad, 0x11, 0x0f, 0xdb, + 0xd5, 0x0f, 0x1a, 0x7b, 0xb5, 0xa7, 0x62, 0xc0, 0x5c, 0x48, 0xef, 0x8c, 0x2a, 0x79, 0x91, 0xf1, 0x12, 0x4f, 0xaa, + 0x8b, 0x86, 0x8f, 0xf7, 0x75, 0x36, 0x32, 0x0f, 0x64, 0x0a, 0x88, 0xe7, 0x1f, 0x53, 0xa3, 0x3e, 0x4e, 0x51, 0x02, + 0xfe, 0x56, 0xc7, 0x37, 0xa2, 0x27, 0xf6, 0xc5, 0x05, 0xaf, 0xde, 0x5c, 0x0b, 0xf3, 0xe2, 0x99, 0xd5, 0xf9, 0xd3, + 0xa7, 0x85, 0x0f, 0x1d, 0x8e, 0xda, 0x3b, 0x28, 0xb2, 0x64, 0xe2, 0x68, 0x62, 0x64, 0x6d, 0x62, 0x76, 0xa2, 0xe0, + 0x62, 0xa2, 0x0a, 0x3d, 0xeb, 0xec, 0x09, 0x53, 0x80, 0xbe, 0x71, 0x8c, 0x4a, 0xc6, 0xb0, 0x60, 0xa0, 0x4e, 0x53, + 0x42, 0xf4, 0x50, 0xcc, 0x30, 0x5e, 0x31, 0x80, 0xc2, 0x14, 0x0a, 0x44, 0xd1, 0xd9, 0x87, 0x03, 0x4d, 0xe8, 0xf7, + 0x3f, 0xa6, 0x3a, 0x03, 0x2d, 0xeb, 0x69, 0x01, 0xa2, 0x3a, 0x88, 0xb6, 0x0a, 0x84, 0x39, 0xa5, 0x65, 0x46, 0x97, + 0x82, 0xa6, 0x82, 0x26, 0x19, 0x3d, 0xe7, 0x4a, 0x54, 0x7c, 0x2e, 0x98, 0xa2, 0xed, 0x86, 0xb0, 0xff, 0xd8, 0xa0, + 0xeb, 0xad, 0x58, 0x6b, 0x68, 0x77, 0x82, 0x8c, 0xd0, 0x7c, 0xa1, 0x83, 0x90, 0xa1, 0x72, 0x12, 0xf1, 0xe1, 0x35, + 0x5e, 0x81, 0x4b, 0xa6, 0xd9, 0x68, 0x19, 0x97, 0x61, 0x60, 0xbf, 0x0a, 0x2c, 0x26, 0x07, 0x26, 0x9d, 0xac, 0xcf, + 0x9e, 0xca, 0xcb, 0x95, 0x14, 0x5c, 0x54, 0x0a, 0xa2, 0xdf, 0xe0, 0xbe, 0x9b, 0xb8, 0xea, 0xac, 0x59, 0x2b, 0xbd, + 0xef, 0x5b, 0x9f, 0xb5, 0x71, 0x5f, 0x18, 0x1c, 0x83, 0x9d, 0x8f, 0x88, 0x81, 0x34, 0xa8, 0x74, 0x8b, 0x43, 0x13, + 0xa0, 0x4b, 0x87, 0x14, 0xb2, 0x64, 0x2a, 0x53, 0x25, 0xa8, 0xf8, 0xc6, 0xef, 0xa4, 0xac, 0x46, 0x7f, 0xaf, 0x79, + 0x71, 0x7b, 0xc2, 0x73, 0x8e, 0x63, 0x14, 0x24, 0xb1, 0xb8, 0x8a, 0xcb, 0x80, 0xf8, 0x96, 0x57, 0xc1, 0x41, 0x6a, + 0xc2, 0xc6, 0xec, 0x54, 0x8d, 0x5a, 0xaf, 0x02, 0x7d, 0x65, 0x94, 0x6f, 0x0c, 0x86, 0x26, 0xa2, 0x0a, 0xfa, 0x5e, + 0xab, 0x7b, 0x5a, 0xdd, 0xb0, 0x80, 0xf8, 0x73, 0xa5, 0x17, 0x6a, 0xbd, 0x6e, 0xc6, 0xdc, 0x30, 0x11, 0x82, 0x46, + 0x8f, 0xea, 0x85, 0xc3, 0xcf, 0xdf, 0x28, 0x4b, 0x22, 0x78, 0xb1, 0x49, 0xd7, 0x85, 0x89, 0xa5, 0x41, 0x75, 0xc0, + 0xdc, 0x68, 0x93, 0xf3, 0x0b, 0x10, 0xfd, 0x39, 0x2b, 0xa2, 0x49, 0x5d, 0x53, 0x85, 0x60, 0x18, 0x6d, 0x6e, 0x1a, + 0xe9, 0xf4, 0x16, 0xbc, 0xdc, 0x8c, 0x35, 0x92, 0xf6, 0x74, 0xac, 0x69, 0xc1, 0xcb, 0x95, 0x14, 0x25, 0x44, 0x77, + 0xee, 0x8d, 0xe9, 0x65, 0x9c, 0x89, 0x2a, 0xce, 0xc4, 0x71, 0xb9, 0xe2, 0x49, 0xf5, 0x0e, 0x2a, 0xd4, 0xc6, 0x38, + 0xd8, 0x7a, 0x35, 0xea, 0x2a, 0x1c, 0xf2, 0xcb, 0xf3, 0xe7, 0x37, 0xab, 0x58, 0xa4, 0x30, 0xea, 0xf5, 0x5d, 0x2f, + 0x9a, 0xd3, 0xb1, 0x8a, 0x0b, 0x2e, 0x4c, 0xd4, 0x62, 0x5a, 0xb1, 0x80, 0xeb, 0x8c, 0x01, 0xe5, 0x2a, 0x76, 0x67, + 0xa6, 0x62, 0x19, 0xc6, 0x65, 0xf9, 0x53, 0x56, 0xe2, 0x1d, 0x00, 0x5a, 0x03, 0xa7, 0xc5, 0xcc, 0x80, 0x80, 0xdc, + 0xe6, 0x06, 0x17, 0x81, 0x05, 0x07, 0x8f, 0xc7, 0xab, 0x9b, 0x80, 0x7a, 0x6f, 0xa4, 0xba, 0x1e, 0xb2, 0x60, 0x3c, + 0x7a, 0x12, 0x38, 0xe4, 0x10, 0xff, 0xa3, 0xc7, 0x07, 0x77, 0x7f, 0x33, 0x09, 0x48, 0x3d, 0x05, 0x55, 0x85, 0x51, + 0x88, 0xc2, 0xb4, 0xbf, 0x5a, 0xab, 0x5b, 0xee, 0x9b, 0xb3, 0x92, 0x17, 0x57, 0x10, 0xad, 0x9d, 0x4c, 0x33, 0x20, + 0xe7, 0x52, 0x25, 0xc0, 0xa2, 0x88, 0xab, 0xaa, 0xc8, 0xce, 0xc0, 0x44, 0x09, 0x0d, 0xc0, 0xcc, 0xd3, 0x0b, 0x74, + 0xf8, 0x88, 0xe6, 0x01, 0xf6, 0x29, 0x58, 0xd4, 0xa4, 0x2e, 0xa1, 0xb0, 0x64, 0x0f, 0x83, 0xd5, 0xa9, 0xb8, 0xd2, + 0x0e, 0xe0, 0xbb, 0xfa, 0x33, 0x5a, 0x4a, 0x8c, 0x35, 0xab, 0xe7, 0x29, 0x3e, 0x2b, 0x65, 0xbe, 0xae, 0x40, 0x7b, + 0x7e, 0x5e, 0x45, 0x07, 0x8f, 0x57, 0x37, 0x53, 0xd5, 0x8d, 0x08, 0x7a, 0x31, 0x55, 0x38, 0x6f, 0x49, 0x9c, 0x27, + 0xe1, 0x64, 0x3c, 0xfe, 0x6a, 0x6f, 0xb8, 0x07, 0xc9, 0x64, 0xfa, 0x69, 0xa8, 0x1c, 0xb9, 0x86, 0x93, 0xf1, 0xb8, + 0xfe, 0xb3, 0x36, 0x61, 0xbe, 0x4d, 0x3d, 0x4f, 0xff, 0x3c, 0x54, 0xeb, 0xff, 0xe8, 0x70, 0x5f, 0xff, 0xf8, 0xb3, + 0xae, 0xa7, 0x4f, 0x8b, 0x70, 0xfe, 0x7b, 0xa8, 0xd6, 0xf7, 0x71, 0x51, 0xc4, 0xb7, 0x35, 0x44, 0x36, 0x15, 0xce, + 0xbb, 0x86, 0x7a, 0x64, 0x81, 0x1e, 0x90, 0xe9, 0xb9, 0x60, 0xf0, 0xcd, 0xbb, 0x2a, 0x0c, 0x78, 0xb9, 0x1a, 0x72, + 0x51, 0x65, 0xd5, 0xed, 0x10, 0xf3, 0x04, 0xf8, 0xa9, 0xc5, 0x33, 0x2b, 0x0c, 0xf1, 0x3d, 0x2f, 0x38, 0xff, 0xc4, + 0x43, 0x65, 0x2c, 0x3e, 0x46, 0x63, 0xf1, 0x31, 0x55, 0xdd, 0x98, 0x7c, 0x43, 0x75, 0xdf, 0x26, 0xdf, 0x80, 0x49, + 0x56, 0xd6, 0xfe, 0x46, 0x19, 0x6b, 0x46, 0x63, 0x7a, 0xf5, 0x22, 0xcf, 0x56, 0x70, 0x29, 0x58, 0xea, 0x1f, 0x35, + 0xa1, 0xef, 0x78, 0x3b, 0xfb, 0x68, 0x34, 0x7a, 0x53, 0xd0, 0xd1, 0x68, 0xf4, 0x31, 0xab, 0x09, 0x5d, 0x89, 0x8e, + 0xf7, 0xef, 0x38, 0x3d, 0x93, 0xe9, 0x6d, 0x14, 0x04, 0x74, 0x99, 0xa5, 0x29, 0x17, 0xaa, 0xac, 0x57, 0x69, 0x3b, + 0xaf, 0x6a, 0x21, 0x02, 0x21, 0xe9, 0x36, 0x22, 0x24, 0x13, 0xa1, 0x6f, 0x77, 0x7a, 0x36, 0x1a, 0x8d, 0x5e, 0xa5, + 0xa6, 0x5a, 0x77, 0x41, 0x79, 0x8a, 0xe6, 0x14, 0xce, 0x4f, 0x01, 0xac, 0x91, 0x4c, 0xf4, 0x97, 0xfd, 0xff, 0x1e, + 0xce, 0xe6, 0xe3, 0xe1, 0xb7, 0xa3, 0xc5, 0xc3, 0x7d, 0x1a, 0x04, 0x7e, 0xe8, 0x86, 0x50, 0x5b, 0xb7, 0x4c, 0xcb, + 0xc3, 0xf1, 0x94, 0x94, 0x03, 0xf6, 0xd8, 0xfa, 0x16, 0x7d, 0xf5, 0x18, 0x90, 0x59, 0x51, 0xa4, 0x1c, 0x38, 0x69, + 0x28, 0x5e, 0xcd, 0x5e, 0x0a, 0xc0, 0x8b, 0xb3, 0x91, 0x1d, 0x8c, 0x56, 0x74, 0x1c, 0x41, 0x79, 0xb5, 0x35, 0x15, + 0xe9, 0x31, 0x96, 0x99, 0x28, 0xa9, 0xe3, 0x69, 0x79, 0x9d, 0x55, 0xc9, 0x12, 0x03, 0x3d, 0xc5, 0x25, 0x0f, 0xbe, + 0x0a, 0xa2, 0x92, 0x1d, 0x3c, 0x99, 0x2a, 0xb8, 0x63, 0x4c, 0x4a, 0xf9, 0x05, 0x24, 0x7e, 0x3b, 0x46, 0x48, 0x58, + 0xa2, 0x3d, 0x38, 0xb1, 0xc6, 0x17, 0xb9, 0x8c, 0xc1, 0xa3, 0xb5, 0xd4, 0x3c, 0x9c, 0x3d, 0x19, 0xad, 0x3d, 0x4a, + 0xab, 0x39, 0x12, 0x9a, 0x13, 0x4a, 0x26, 0xf7, 0x4b, 0x2a, 0xbf, 0x9a, 0xa0, 0x97, 0x14, 0xb8, 0x99, 0x47, 0x70, + 0xfc, 0x5b, 0x4b, 0x0f, 0xbd, 0x7c, 0x52, 0xb6, 0x3f, 0xff, 0xdf, 0x25, 0x5d, 0x0c, 0xf6, 0xdd, 0xd0, 0xbc, 0xd5, + 0xee, 0xbc, 0x15, 0x32, 0x8e, 0x55, 0xf8, 0x26, 0x25, 0xd6, 0x18, 0x97, 0xb3, 0xa3, 0x8d, 0xe9, 0xce, 0xa8, 0x2a, + 0xb2, 0xcb, 0x90, 0xe8, 0x5e, 0x39, 0x90, 0xd0, 0x20, 0xca, 0x46, 0xb8, 0x7e, 0xc0, 0x7a, 0xc6, 0xeb, 0xe4, 0x15, + 0x2f, 0xaa, 0x2c, 0x51, 0xef, 0xaf, 0x1a, 0xef, 0xeb, 0xda, 0x04, 0x54, 0x7d, 0x50, 0x30, 0x98, 0xe7, 0xb7, 0x05, + 0x80, 0x98, 0x22, 0x0d, 0xf0, 0x09, 0x66, 0x10, 0xd4, 0xae, 0x99, 0x97, 0x8d, 0xe0, 0x1b, 0xf0, 0xd5, 0x83, 0x02, + 0x30, 0x48, 0x42, 0x90, 0x22, 0x43, 0x68, 0x20, 0x10, 0x68, 0x18, 0x72, 0x81, 0xc1, 0x4f, 0xbc, 0x38, 0x92, 0xca, + 0x29, 0x91, 0x87, 0x01, 0xfe, 0x08, 0xa8, 0x0a, 0x40, 0x62, 0x3c, 0x0e, 0xe1, 0x85, 0xfa, 0xe5, 0xde, 0xa8, 0x3d, + 0xc2, 0x9e, 0xa6, 0x21, 0x04, 0x1b, 0xc2, 0x87, 0x00, 0x96, 0x14, 0xa1, 0x6f, 0x91, 0xcb, 0x08, 0x83, 0xf3, 0x3c, + 0x5b, 0xe9, 0xa4, 0x6a, 0xd4, 0xd1, 0x7c, 0x28, 0xb5, 0x23, 0x39, 0xa0, 0x5e, 0x7a, 0x8c, 0xe9, 0x85, 0x4a, 0x57, + 0x45, 0x39, 0xa3, 0x9c, 0x07, 0x7a, 0x62, 0x5c, 0xd8, 0x42, 0x0e, 0x91, 0x70, 0x1e, 0x14, 0x2a, 0x14, 0x0e, 0x5f, + 0x00, 0x18, 0x18, 0x48, 0x3b, 0x76, 0xe3, 0xdd, 0xa8, 0xec, 0xa7, 0x9c, 0xed, 0xff, 0xf7, 0x3c, 0x1e, 0x7e, 0x1a, + 0x0f, 0xbf, 0x5d, 0x0c, 0xc2, 0xa1, 0xfd, 0x49, 0x1e, 0x3e, 0xd8, 0xa7, 0x2f, 0xb8, 0xe5, 0xd2, 0x60, 0xe1, 0x37, + 0x82, 0xfd, 0xa8, 0x95, 0x10, 0x44, 0x01, 0xde, 0xb0, 0xdc, 0x6a, 0x9c, 0x00, 0xe0, 0x61, 0xf0, 0x5f, 0x01, 0x1a, + 0x4d, 0xb9, 0x8b, 0x17, 0xe8, 0x4b, 0xd4, 0xef, 0xa3, 0x47, 0x0d, 0x83, 0x41, 0x10, 0xd7, 0xa8, 0x98, 0x30, 0x44, + 0x97, 0x31, 0x51, 0x30, 0xc8, 0x36, 0xfb, 0x76, 0xdb, 0x6b, 0x4b, 0xc2, 0xf0, 0x4b, 0x3f, 0xd3, 0xc4, 0xcc, 0x3b, + 0xdc, 0xd8, 0x56, 0x72, 0x15, 0x22, 0x56, 0xa0, 0xfe, 0x95, 0x33, 0x88, 0xbd, 0x79, 0x95, 0x81, 0x4f, 0x87, 0xfd, + 0x62, 0x3c, 0x03, 0x36, 0x0a, 0xee, 0x7c, 0x05, 0x3f, 0xcf, 0xc0, 0xcd, 0x5b, 0xc4, 0x28, 0x70, 0xb0, 0x4b, 0xa2, + 0xdf, 0xef, 0xe5, 0x59, 0x98, 0x6b, 0xdc, 0xe9, 0xbc, 0x36, 0x6a, 0x08, 0xd4, 0x91, 0x83, 0xfa, 0x41, 0x0f, 0xc1, + 0x50, 0x0d, 0x41, 0xd1, 0xd1, 0x16, 0x57, 0xaf, 0xad, 0xa7, 0x30, 0xbd, 0x55, 0xf5, 0x15, 0xa3, 0xbf, 0x64, 0x26, + 0xb0, 0x90, 0x76, 0xcd, 0xb1, 0xae, 0x39, 0x46, 0xda, 0xd3, 0xef, 0x8b, 0x06, 0xf9, 0xe9, 0x2c, 0x3c, 0x08, 0x54, + 0xa9, 0x72, 0xa7, 0x2c, 0xca, 0x6d, 0x69, 0xde, 0x18, 0xd6, 0x34, 0xcf, 0x6c, 0x9c, 0x9b, 0x59, 0xaf, 0x17, 0x86, + 0xe8, 0xe0, 0x89, 0xa5, 0x62, 0x6d, 0x10, 0xee, 0xc8, 0x24, 0x8c, 0x2e, 0x41, 0x76, 0x19, 0x9e, 0x72, 0x82, 0x7c, + 0x2a, 0xb0, 0x0f, 0xaa, 0x5a, 0x2f, 0x27, 0x3c, 0x36, 0xf2, 0x65, 0x23, 0x68, 0x90, 0x97, 0x14, 0xf5, 0x26, 0x6e, + 0xc7, 0x1e, 0xb7, 0x90, 0x2b, 0x37, 0xf5, 0xb4, 0xa7, 0x49, 0x45, 0x8f, 0xf5, 0x2a, 0xf5, 0x0b, 0x2c, 0x2d, 0x2c, + 0xf9, 0x20, 0xb4, 0xa7, 0x69, 0x05, 0x66, 0xb8, 0xb2, 0x19, 0x0c, 0xfd, 0x70, 0xfc, 0x04, 0x74, 0x46, 0x6d, 0x4b, + 0x08, 0x63, 0x37, 0x08, 0x2b, 0xef, 0x89, 0x7c, 0xf5, 0xd8, 0xbb, 0x18, 0x84, 0xdc, 0x6c, 0x66, 0xd1, 0xc0, 0x74, + 0x3f, 0x93, 0xcd, 0xe6, 0xe9, 0xe6, 0x7a, 0x51, 0x42, 0x05, 0x6c, 0xb7, 0x95, 0x20, 0xf8, 0xf7, 0x63, 0x36, 0xc3, + 0xbf, 0x59, 0xbf, 0xdf, 0x0b, 0xf1, 0x17, 0xc7, 0x60, 0x46, 0x73, 0xb1, 0x60, 0x1f, 0x41, 0xc6, 0x44, 0x22, 0x4c, + 0x55, 0xc6, 0x80, 0xac, 0x02, 0x8b, 0x40, 0xf3, 0x81, 0xca, 0x85, 0x99, 0xec, 0x65, 0xce, 0x35, 0xe4, 0x79, 0x6b, + 0x9c, 0xb2, 0x51, 0x96, 0x28, 0x57, 0x8e, 0x6c, 0x14, 0xe7, 0x59, 0x5c, 0xf2, 0x72, 0xbb, 0xd5, 0x87, 0x63, 0x52, + 0x70, 0x60, 0xd7, 0x15, 0x95, 0x2a, 0x59, 0x47, 0xaa, 0x07, 0x5e, 0x1a, 0x16, 0xb8, 0x4f, 0xf9, 0xbc, 0x30, 0x34, + 0x62, 0x0f, 0x84, 0x19, 0x4c, 0xdd, 0xd2, 0x7b, 0x61, 0x01, 0xcd, 0x2b, 0x09, 0xd9, 0x60, 0xaa, 0x67, 0xe1, 0x1b, + 0x33, 0x31, 0x2f, 0x16, 0x10, 0x56, 0xa7, 0x58, 0x68, 0x66, 0x93, 0x26, 0x2c, 0x06, 0xd8, 0xbc, 0x98, 0x4c, 0x21, + 0xbe, 0xbb, 0x2a, 0x27, 0x5e, 0x98, 0xfb, 0x76, 0xe2, 0x90, 0x43, 0xe0, 0x55, 0x6d, 0xd0, 0xd5, 0x6c, 0xc3, 0x51, + 0x47, 0xca, 0x89, 0xc9, 0xef, 0xa7, 0x0a, 0x42, 0xdc, 0x89, 0x23, 0xe1, 0xf2, 0x66, 0xbb, 0xf0, 0xb2, 0x03, 0x41, + 0x47, 0x0d, 0x4e, 0xf9, 0x99, 0xc1, 0xd1, 0x98, 0xa4, 0x1b, 0xef, 0x04, 0x29, 0xc2, 0x98, 0x6c, 0x24, 0x3b, 0x93, + 0xa1, 0x98, 0xc7, 0x0b, 0x50, 0x5e, 0xc6, 0x0b, 0xb0, 0x34, 0x32, 0x06, 0xa9, 0x20, 0xbf, 0xe3, 0x5e, 0x28, 0x2c, + 0x8a, 0x2b, 0x44, 0x7a, 0x56, 0xbf, 0xc7, 0x45, 0x3b, 0x14, 0x08, 0x8a, 0x3b, 0x94, 0x79, 0x72, 0xd6, 0x63, 0x81, + 0xc4, 0x86, 0x80, 0xf1, 0x95, 0x4e, 0x53, 0xad, 0x75, 0x6f, 0x6c, 0xf4, 0xaa, 0x69, 0x36, 0x12, 0xb2, 0x3a, 0x3d, + 0x07, 0x91, 0x92, 0x8f, 0x8e, 0x8f, 0xfc, 0x22, 0xee, 0x2c, 0xf3, 0xd6, 0xb6, 0xa8, 0x64, 0x47, 0x1b, 0x00, 0x2d, + 0xd4, 0xd1, 0xb3, 0x94, 0xdc, 0xa6, 0x24, 0xb5, 0xdb, 0x14, 0xb0, 0x92, 0xfc, 0x05, 0x0c, 0xc1, 0xd7, 0xf6, 0x84, + 0xd3, 0xb1, 0x42, 0xbc, 0xa6, 0x29, 0x22, 0x4d, 0x86, 0x25, 0xc5, 0xb1, 0x2d, 0x11, 0x05, 0xd5, 0x96, 0x65, 0x07, + 0xc3, 0x44, 0x09, 0x7e, 0x96, 0x7a, 0x94, 0x28, 0x08, 0xa8, 0x1e, 0x72, 0x90, 0x60, 0xdb, 0x06, 0xc2, 0x03, 0xf2, + 0x88, 0xde, 0x58, 0x7f, 0x9f, 0x75, 0x9e, 0x5d, 0x68, 0x9e, 0xcb, 0xf5, 0xae, 0x30, 0x63, 0x84, 0x27, 0x99, 0x09, + 0x1b, 0xe0, 0x9d, 0x67, 0x46, 0x6d, 0xd3, 0xf3, 0xf0, 0xda, 0x9e, 0x63, 0x84, 0xbe, 0x3b, 0x06, 0xdd, 0x04, 0xf3, + 0xea, 0xb0, 0x59, 0xaf, 0x14, 0xa4, 0x86, 0xa9, 0x45, 0x13, 0xb3, 0x9e, 0x35, 0x28, 0xdf, 0x6e, 0x7b, 0x7a, 0xae, + 0xee, 0x9e, 0xbb, 0xed, 0xb6, 0x87, 0xdd, 0x7a, 0x96, 0x76, 0x5b, 0xc5, 0x57, 0xea, 0x83, 0xf6, 0xf8, 0x73, 0x37, + 0xfe, 0xdc, 0x20, 0x9b, 0x94, 0x8e, 0x66, 0xda, 0xfa, 0x20, 0x3c, 0x70, 0x7a, 0xdb, 0x68, 0xd2, 0xf7, 0x59, 0x28, + 0xe9, 0x4a, 0x34, 0xaa, 0x33, 0x21, 0xcc, 0x58, 0x75, 0xff, 0xfa, 0xbf, 0x7f, 0x15, 0xe0, 0x11, 0xa7, 0x76, 0xf6, + 0x9d, 0x0d, 0x2a, 0x1a, 0x6d, 0xe1, 0x48, 0x11, 0x7a, 0x40, 0x12, 0xee, 0x6a, 0x59, 0x8b, 0xdb, 0x3c, 0xc9, 0xee, + 0xa7, 0x4f, 0xef, 0x53, 0xdf, 0x0b, 0xc1, 0x2d, 0xb3, 0xcc, 0x1c, 0x78, 0x15, 0xc5, 0x01, 0x8d, 0xba, 0x68, 0xdf, + 0x65, 0x56, 0x96, 0xe0, 0xf5, 0x02, 0xf7, 0xca, 0x13, 0xee, 0xc3, 0xef, 0x5d, 0x54, 0xcd, 0x4d, 0x7a, 0x92, 0xcd, + 0xb3, 0xc5, 0x76, 0x1b, 0xe2, 0xdf, 0xae, 0x16, 0x39, 0x9a, 0x3c, 0x07, 0x9d, 0x26, 0x46, 0x32, 0x62, 0xba, 0x71, + 0xde, 0xe6, 0x7f, 0x2d, 0x1a, 0x4e, 0x13, 0xcf, 0x81, 0x5e, 0xcc, 0x8e, 0x41, 0x26, 0x65, 0x40, 0x0e, 0xc4, 0x4c, + 0xaf, 0x19, 0x88, 0x46, 0x26, 0x22, 0xc0, 0x15, 0xc6, 0x46, 0xa2, 0xd1, 0x09, 0x27, 0x35, 0x01, 0x0b, 0x56, 0x5b, + 0xde, 0x4f, 0x96, 0xb6, 0x55, 0xc5, 0xad, 0xb7, 0xa4, 0x39, 0xae, 0x03, 0xe7, 0xeb, 0x60, 0x86, 0xd8, 0x94, 0x5d, + 0x2d, 0x90, 0xfb, 0xe5, 0x35, 0xed, 0x8d, 0xeb, 0x04, 0x66, 0x6d, 0x53, 0x5b, 0xc6, 0xcf, 0x96, 0xfe, 0x4e, 0x0f, + 0xae, 0x32, 0x06, 0x9b, 0x1b, 0x2b, 0x0d, 0xbb, 0x6f, 0x3c, 0x5f, 0x0a, 0x08, 0x4f, 0xe7, 0xd3, 0xe3, 0x93, 0xcc, + 0xa3, 0xc7, 0x40, 0x74, 0xcc, 0x47, 0xa5, 0xfb, 0xc8, 0xee, 0x5e, 0x3f, 0x20, 0xe0, 0xbc, 0x6a, 0x17, 0x34, 0x2f, + 0x17, 0x10, 0x58, 0xd5, 0x2b, 0xaf, 0xb0, 0x7c, 0x66, 0xcc, 0x2e, 0x80, 0x0c, 0x15, 0x04, 0x02, 0x77, 0x77, 0x9d, + 0x0b, 0xb1, 0xea, 0xb0, 0x32, 0xa7, 0x49, 0xd8, 0x51, 0x88, 0xe6, 0xad, 0xc1, 0x2c, 0xf8, 0xaf, 0x60, 0x50, 0x0e, + 0x82, 0x28, 0x88, 0x82, 0x80, 0x0c, 0x0a, 0xf8, 0x85, 0xb8, 0x6b, 0x04, 0x63, 0xb6, 0x40, 0x87, 0xdf, 0x72, 0xe6, + 0x33, 0x22, 0x2f, 0xfd, 0xb0, 0x9e, 0xde, 0x00, 0x9c, 0x49, 0x99, 0xf3, 0x18, 0x7d, 0x4e, 0xde, 0x72, 0x96, 0x11, + 0xfa, 0xd6, 0x3b, 0x95, 0x1f, 0xf0, 0x46, 0xb0, 0xbf, 0xdd, 0x61, 0x7b, 0x01, 0xf2, 0x8a, 0xde, 0x98, 0xbe, 0xe5, + 0x24, 0xca, 0x1a, 0xce, 0xd4, 0x1c, 0x7a, 0x56, 0x59, 0xd6, 0x8a, 0x1a, 0x72, 0x83, 0x62, 0x6e, 0x64, 0x99, 0x9c, + 0x4c, 0x5b, 0xcd, 0xa9, 0xc0, 0x75, 0x67, 0xd7, 0x0b, 0x48, 0x0e, 0x85, 0x66, 0xe9, 0x6c, 0x38, 0x6f, 0xdb, 0xb2, + 0x67, 0xad, 0x53, 0xc8, 0x6b, 0x88, 0x8a, 0x06, 0xe9, 0x08, 0xa8, 0xa1, 0x15, 0x17, 0x15, 0xb8, 0x30, 0x9b, 0xf6, + 0x70, 0xd3, 0x1e, 0xd3, 0x8c, 0x9f, 0x20, 0x66, 0x1e, 0xc7, 0x96, 0x81, 0x1d, 0x89, 0xc3, 0xf7, 0x71, 0xbe, 0x40, + 0xbb, 0xf4, 0xd6, 0xd5, 0xe2, 0x11, 0xd6, 0x9e, 0xb7, 0x42, 0x42, 0x80, 0xf8, 0x34, 0x95, 0x6e, 0xb7, 0x41, 0x00, + 0x03, 0xdc, 0xef, 0xf7, 0x80, 0x6b, 0x35, 0xec, 0xa4, 0xb9, 0x35, 0x5b, 0x62, 0xaf, 0x28, 0x3c, 0x06, 0xe6, 0xd4, + 0xfc, 0x67, 0x10, 0x50, 0x3c, 0x77, 0x43, 0xb0, 0x37, 0x65, 0x47, 0x1b, 0x88, 0x38, 0x54, 0xe0, 0x03, 0xca, 0x85, + 0x41, 0xcc, 0xad, 0xe3, 0x78, 0x18, 0xf6, 0x49, 0x7d, 0x88, 0x63, 0x91, 0x67, 0xa1, 0x23, 0x2c, 0x95, 0x21, 0x2c, + 0x5c, 0x31, 0xd2, 0x41, 0x1c, 0xd4, 0xa4, 0x73, 0xb0, 0x2a, 0x17, 0x7c, 0xb9, 0xd7, 0x7b, 0x0d, 0x30, 0xe9, 0x99, + 0x37, 0x2c, 0x2f, 0x3c, 0x40, 0xb4, 0x5e, 0x0f, 0x17, 0x8a, 0x47, 0x26, 0x1a, 0x68, 0x9c, 0xf8, 0xd2, 0xb2, 0xeb, + 0x33, 0x2d, 0x2b, 0x19, 0x8d, 0x46, 0x55, 0xad, 0x24, 0x1f, 0xf6, 0xbb, 0x4f, 0x2d, 0x14, 0x4f, 0x19, 0xa7, 0x3c, + 0x05, 0xcb, 0x77, 0x43, 0xe9, 0xe6, 0x0b, 0xba, 0xe2, 0x22, 0x55, 0x3f, 0x3d, 0xf4, 0xcd, 0x06, 0x71, 0xcd, 0x9a, + 0x3a, 0x1c, 0x3b, 0xfc, 0x10, 0x00, 0xd3, 0x3e, 0xcc, 0x5c, 0xba, 0x86, 0xe9, 0x05, 0xf1, 0x6c, 0x5c, 0xf0, 0xd0, + 0xe5, 0x01, 0xec, 0x43, 0x73, 0x48, 0xe2, 0xa7, 0xf0, 0x73, 0x66, 0xd2, 0x3a, 0x3e, 0xc3, 0xd9, 0x8c, 0x4a, 0x75, + 0x23, 0x68, 0xbf, 0x86, 0x44, 0x62, 0x90, 0x9e, 0x1b, 0x0c, 0x45, 0xeb, 0x6e, 0x03, 0x57, 0x7e, 0x4b, 0xef, 0x7c, + 0x1a, 0x04, 0x58, 0xdf, 0x58, 0x0c, 0x00, 0xa8, 0xe2, 0x0f, 0x54, 0x5d, 0x99, 0x2b, 0x8a, 0x69, 0x98, 0x4a, 0xb4, + 0x77, 0x1c, 0xd7, 0x51, 0xe3, 0x3a, 0x2c, 0x58, 0x69, 0x6d, 0x9b, 0xdd, 0x5b, 0x88, 0x3f, 0xa2, 0x4b, 0x40, 0xb5, + 0x20, 0xee, 0x04, 0xf0, 0xa1, 0x91, 0xea, 0x40, 0x90, 0xdd, 0x07, 0x07, 0x00, 0xbc, 0xe1, 0x79, 0x18, 0xc2, 0x1f, + 0x58, 0x38, 0xb0, 0x2c, 0x55, 0x3f, 0x97, 0xd3, 0x18, 0xce, 0xdd, 0x5c, 0xed, 0xf0, 0xd9, 0x12, 0x14, 0x9b, 0x6a, + 0x4e, 0xcd, 0xe5, 0x2b, 0x6f, 0xec, 0xf7, 0x98, 0x60, 0x1e, 0x33, 0xdb, 0xf0, 0x5b, 0x4f, 0xb7, 0xf5, 0x0d, 0x76, + 0x03, 0x27, 0xed, 0x85, 0xd3, 0x5e, 0x6c, 0x97, 0x06, 0xf2, 0xaf, 0x6e, 0x08, 0x11, 0xde, 0x6b, 0x62, 0x91, 0x35, + 0x64, 0x3a, 0x16, 0x2b, 0x44, 0xb5, 0xa9, 0x78, 0xaa, 0x0d, 0x04, 0xca, 0xa9, 0xba, 0x30, 0xb5, 0x52, 0x99, 0x30, + 0x88, 0x3b, 0x25, 0x2c, 0xaa, 0x0c, 0x30, 0x0c, 0x2a, 0xa4, 0xb8, 0xb6, 0x9e, 0xbf, 0x70, 0xf9, 0x66, 0xa6, 0xcd, + 0xf6, 0xd3, 0x17, 0x79, 0x7c, 0xb1, 0xdd, 0x86, 0xdd, 0x2f, 0xc0, 0x1c, 0xb5, 0x54, 0x1a, 0x46, 0x70, 0x02, 0x51, + 0x92, 0xeb, 0x3b, 0x72, 0x4e, 0x1c, 0x27, 0xd7, 0x6e, 0xde, 0x6c, 0x27, 0xc5, 0x08, 0x2c, 0xe0, 0xc4, 0x45, 0x3a, + 0xd0, 0x52, 0x49, 0x6a, 0x4f, 0x01, 0x6f, 0xd3, 0x3b, 0x4a, 0x85, 0x57, 0x0b, 0x4d, 0x42, 0x2a, 0x77, 0x2f, 0xb1, + 0xa3, 0x06, 0x9c, 0x93, 0xba, 0x83, 0x80, 0xd3, 0x9e, 0x6e, 0xac, 0x55, 0x24, 0x9b, 0x04, 0xef, 0x95, 0x1e, 0xba, + 0x44, 0x3b, 0xb5, 0xbb, 0x6d, 0x55, 0xb6, 0x50, 0x30, 0xf7, 0x72, 0x96, 0xa8, 0xe3, 0x01, 0x85, 0x2e, 0xea, 0x68, + 0xc8, 0x17, 0xa4, 0xd0, 0x2b, 0x47, 0xab, 0x9a, 0x77, 0x25, 0x03, 0xa5, 0x5a, 0x05, 0x79, 0x4d, 0xac, 0xfb, 0x5a, + 0xd6, 0x58, 0x5c, 0x39, 0x21, 0x85, 0x4d, 0xf8, 0xd2, 0x52, 0x2c, 0xcc, 0x62, 0x6f, 0x4c, 0x7d, 0xe1, 0x12, 0xa1, + 0xed, 0x6e, 0x43, 0x8c, 0x36, 0x58, 0x37, 0xdb, 0xed, 0xfb, 0x22, 0x9c, 0x67, 0x0b, 0x2a, 0x47, 0x59, 0x8a, 0x90, + 0x6a, 0xc6, 0x63, 0xd9, 0x76, 0xc1, 0x4c, 0x0c, 0x75, 0xed, 0xf1, 0x92, 0x4c, 0xb1, 0x36, 0x49, 0x8e, 0xe2, 0x33, + 0x59, 0xa8, 0xb5, 0x46, 0x08, 0x1e, 0xee, 0xdf, 0xa5, 0x10, 0xd3, 0xce, 0xac, 0xbb, 0x5f, 0x76, 0x6e, 0x88, 0xdf, + 0x41, 0x60, 0x85, 0x92, 0xbd, 0x2f, 0x46, 0x67, 0x99, 0x48, 0x71, 0xa7, 0xaa, 0x28, 0xc1, 0x6a, 0x1d, 0x34, 0x5b, + 0x6e, 0xef, 0xc5, 0x96, 0x28, 0x40, 0x9c, 0x67, 0xa1, 0x19, 0xcf, 0xca, 0x59, 0xce, 0x64, 0x14, 0x1b, 0x12, 0x95, + 0x5e, 0x94, 0x78, 0x9f, 0xa7, 0x31, 0x3d, 0x74, 0x6b, 0x10, 0x5c, 0x57, 0x77, 0x36, 0xd2, 0x7c, 0x41, 0x88, 0x9a, + 0x00, 0x09, 0x1b, 0xd5, 0x9c, 0x5a, 0x17, 0xe2, 0x7e, 0x56, 0xf9, 0x56, 0x1f, 0xc4, 0x17, 0x02, 0x78, 0x58, 0x6f, + 0x7b, 0x5f, 0x0a, 0x8f, 0xb5, 0xc1, 0xb7, 0xdb, 0xed, 0x85, 0x98, 0x07, 0x81, 0xc7, 0x68, 0xfe, 0xa0, 0x24, 0xe6, + 0xbd, 0x31, 0x85, 0x15, 0xef, 0xbb, 0xf8, 0x75, 0x93, 0x5a, 0x6b, 0x91, 0xbb, 0xc3, 0xf5, 0x01, 0xcf, 0x53, 0xe2, + 0x68, 0x47, 0xe5, 0x54, 0x5a, 0xdb, 0x01, 0xec, 0x8a, 0xc0, 0x40, 0xd9, 0x1f, 0x52, 0xb6, 0x01, 0xf3, 0x44, 0xb0, + 0x3e, 0x42, 0xbf, 0x2d, 0xa5, 0x3f, 0x19, 0xa3, 0x71, 0x8f, 0x5c, 0x57, 0xd1, 0x01, 0xd7, 0xd1, 0xec, 0x79, 0xf4, + 0x8f, 0x27, 0x63, 0x5a, 0xc4, 0x22, 0x95, 0x97, 0xa0, 0x82, 0x00, 0x65, 0x08, 0x3a, 0x42, 0x68, 0x6a, 0x00, 0x1a, + 0x04, 0x37, 0x00, 0xbf, 0x76, 0x3a, 0x51, 0xda, 0x9a, 0x7c, 0x8c, 0x56, 0x55, 0xe4, 0xac, 0x0d, 0xed, 0xa6, 0x92, + 0x43, 0xf2, 0xb0, 0x04, 0x7c, 0x4b, 0x6c, 0x96, 0xb2, 0x41, 0x51, 0x9b, 0x4d, 0xbd, 0x56, 0xec, 0xc8, 0x4d, 0xa3, + 0x68, 0xb3, 0x16, 0xb5, 0xdd, 0xc8, 0x7c, 0x31, 0xbd, 0xb1, 0xc2, 0xc0, 0xa9, 0x69, 0xcd, 0xf5, 0x0e, 0x94, 0x9c, + 0xad, 0xcf, 0xe4, 0x26, 0x40, 0x1c, 0x60, 0xb8, 0x6e, 0xe6, 0xd7, 0x0b, 0x42, 0x6f, 0xd8, 0x8d, 0x15, 0xab, 0x5e, + 0x5b, 0xb9, 0x88, 0x49, 0xbb, 0x1e, 0x4c, 0xe0, 0x32, 0xce, 0x0a, 0xfb, 0x42, 0xab, 0x1b, 0x8a, 0x8e, 0xb6, 0x49, + 0xfb, 0x79, 0x47, 0xbb, 0xe1, 0x82, 0x6f, 0xc5, 0x3a, 0xce, 0x2d, 0x6b, 0xaa, 0xd0, 0xb4, 0x03, 0xbd, 0x1d, 0x02, + 0x9a, 0xb3, 0x31, 0x5d, 0xd2, 0x14, 0x2f, 0xd0, 0x74, 0x0d, 0x66, 0x3a, 0xe7, 0xd0, 0xd7, 0x6e, 0x1f, 0xed, 0x73, + 0xd5, 0x13, 0xe1, 0x2d, 0x51, 0xf0, 0x6d, 0x49, 0xc1, 0x4b, 0x2d, 0xe7, 0xb1, 0x99, 0x43, 0xc0, 0xa7, 0x51, 0x25, + 0x7a, 0x27, 0xc5, 0x05, 0x68, 0x33, 0xe1, 0x08, 0x34, 0x55, 0x23, 0xb6, 0x72, 0x80, 0xdb, 0x8b, 0xa7, 0x01, 0xa1, + 0x20, 0xd5, 0x5d, 0xdb, 0x15, 0x79, 0xc3, 0x8e, 0x36, 0x37, 0x60, 0x26, 0x5c, 0xad, 0xcb, 0xd6, 0x57, 0x36, 0xd9, + 0x7d, 0x5c, 0x13, 0x6c, 0xbb, 0xb7, 0x41, 0xc2, 0x1b, 0x7a, 0x4d, 0x36, 0xd7, 0xfd, 0x7e, 0x08, 0xfd, 0x21, 0x54, + 0x77, 0xe8, 0xa6, 0xb3, 0x43, 0x37, 0x5e, 0x3b, 0xcf, 0xac, 0x9e, 0x4f, 0x79, 0x87, 0xbc, 0x47, 0x93, 0x35, 0xba, + 0x8a, 0x6f, 0x61, 0x53, 0x47, 0x15, 0x55, 0x95, 0x47, 0x09, 0x05, 0x95, 0x78, 0xc6, 0xcb, 0x13, 0x8e, 0xb1, 0x5e, + 0xf5, 0xd3, 0x5b, 0xcd, 0xab, 0xad, 0xcd, 0xda, 0x2c, 0xd7, 0x67, 0x60, 0x21, 0x71, 0xc6, 0xa3, 0x4b, 0x4d, 0x4b, + 0x2e, 0x7c, 0x28, 0x4d, 0x1c, 0x95, 0xe0, 0x3c, 0xce, 0x72, 0x50, 0xe3, 0x9e, 0x37, 0xfb, 0x1f, 0x6a, 0xdb, 0xb1, + 0x65, 0xe3, 0xcc, 0xbd, 0x0a, 0xc9, 0xe6, 0x7f, 0x6c, 0xa0, 0x5e, 0x85, 0x18, 0x21, 0xd6, 0x2c, 0xe8, 0x37, 0x0c, + 0x62, 0x85, 0x06, 0xe5, 0x3a, 0x49, 0x78, 0x59, 0x06, 0x46, 0xa9, 0xb5, 0x66, 0x6b, 0x73, 0x9e, 0x3d, 0x60, 0x47, + 0x0f, 0x7a, 0x8c, 0xdd, 0x10, 0x9a, 0x68, 0x9d, 0x90, 0xa9, 0x31, 0xf2, 0xb4, 0x40, 0xba, 0x43, 0x51, 0x76, 0x1e, + 0xbe, 0x41, 0x21, 0x4b, 0x7b, 0x9f, 0x9b, 0x13, 0x59, 0x7d, 0xa3, 0x8d, 0x50, 0x22, 0x95, 0x08, 0xb2, 0xf1, 0x6b, + 0x04, 0x30, 0x86, 0x66, 0x07, 0x64, 0xb3, 0x64, 0x27, 0xf4, 0xd4, 0x9a, 0x04, 0xc1, 0xeb, 0x37, 0x2a, 0xd1, 0x8c, + 0xb2, 0x22, 0xba, 0xca, 0xe8, 0xe7, 0x36, 0x24, 0xd1, 0x69, 0x48, 0xfc, 0xdc, 0xb0, 0xb4, 0xae, 0x42, 0x14, 0x33, + 0x9b, 0x0d, 0xaf, 0x15, 0x51, 0x8d, 0x6d, 0x65, 0x7c, 0xcc, 0x6f, 0x6c, 0x1a, 0x99, 0x42, 0x5f, 0x87, 0x93, 0x7e, + 0x1f, 0xfe, 0x6a, 0xfa, 0x81, 0xb7, 0x14, 0xfc, 0xc5, 0x1e, 0x90, 0x3a, 0x61, 0x01, 0xc0, 0x33, 0xe6, 0xbc, 0x6a, + 0x4e, 0xe0, 0x03, 0x76, 0xb4, 0x79, 0x10, 0x9e, 0x34, 0x66, 0xee, 0x36, 0xc4, 0x4b, 0x55, 0xd2, 0xf3, 0xe6, 0xc9, + 0x0c, 0xc4, 0xca, 0x6a, 0xcd, 0x6f, 0x98, 0xd5, 0x27, 0x00, 0x91, 0xba, 0xb1, 0x0e, 0xb6, 0xf8, 0xb1, 0xe9, 0x32, + 0xd9, 0xa4, 0xac, 0xcd, 0x44, 0x29, 0x15, 0x49, 0x73, 0x11, 0x40, 0xbf, 0x61, 0x38, 0x6a, 0x80, 0x3b, 0xd7, 0x63, + 0x6f, 0x86, 0xc6, 0x1b, 0x53, 0x43, 0xcf, 0x36, 0x7a, 0x79, 0x3b, 0x0a, 0x61, 0xc6, 0x22, 0xba, 0x71, 0xc7, 0x62, + 0x78, 0x42, 0xdf, 0x40, 0x85, 0xaf, 0x42, 0x8c, 0x2e, 0x4c, 0xea, 0x7a, 0xba, 0x56, 0x5b, 0xe9, 0x9a, 0xd0, 0x1c, + 0xa3, 0x1a, 0x79, 0x6d, 0xbb, 0xa5, 0x46, 0x68, 0x4f, 0x28, 0x0f, 0x6f, 0x68, 0x45, 0xaf, 0x2d, 0x8b, 0xe0, 0xe4, + 0xc7, 0x5e, 0x7e, 0x42, 0xcf, 0x3c, 0x81, 0x49, 0xd1, 0xd6, 0x00, 0x7e, 0x40, 0xfd, 0x70, 0x56, 0x4f, 0xad, 0x94, + 0xc3, 0x53, 0xf8, 0x92, 0x0d, 0xc8, 0x15, 0xf4, 0x62, 0x8d, 0xd9, 0x51, 0x0c, 0x3a, 0xa8, 0x9d, 0xdd, 0xe1, 0x4d, + 0x4a, 0x19, 0xa2, 0x35, 0xa2, 0x83, 0xbc, 0xfa, 0x15, 0x34, 0x7d, 0x90, 0x16, 0xa6, 0x74, 0x8d, 0x02, 0x1e, 0xd0, + 0x37, 0xf5, 0xfb, 0x39, 0x3e, 0xd7, 0x9e, 0x65, 0x9a, 0xb2, 0x40, 0x26, 0x74, 0xe9, 0xc5, 0xed, 0x02, 0x69, 0xb3, + 0x63, 0x15, 0x80, 0x15, 0x49, 0xa0, 0x11, 0x09, 0x58, 0x2e, 0x79, 0xe2, 0xb2, 0x0d, 0x1a, 0xd4, 0x44, 0x25, 0x85, + 0x2c, 0x91, 0x04, 0x7e, 0x18, 0x41, 0x99, 0xa2, 0x18, 0xc4, 0xbd, 0x7a, 0x79, 0xc5, 0x35, 0x35, 0x60, 0x4d, 0x11, + 0x4c, 0xb0, 0x4e, 0xa7, 0x40, 0x6c, 0xc5, 0x7a, 0x05, 0x9e, 0xa8, 0xee, 0x22, 0x89, 0x2c, 0x01, 0x1a, 0xe8, 0xf9, + 0xd2, 0x69, 0xb7, 0xbc, 0x3d, 0xd1, 0x52, 0xc5, 0xe6, 0xde, 0x8b, 0x85, 0xe5, 0x1e, 0x2b, 0x7f, 0x3b, 0xd0, 0x5e, + 0x58, 0xed, 0x88, 0xa8, 0xc1, 0xea, 0xb0, 0x6d, 0xe7, 0x87, 0xd2, 0x50, 0xdd, 0x2b, 0xc7, 0x04, 0x54, 0x74, 0x15, + 0x57, 0xcb, 0x28, 0x1b, 0xc1, 0x9f, 0xed, 0x36, 0xd8, 0x0f, 0xc0, 0x22, 0xf4, 0xc3, 0xbb, 0x9f, 0x22, 0x0c, 0x57, + 0xf5, 0xe1, 0xdd, 0x4f, 0xdb, 0xed, 0x93, 0xf1, 0xd8, 0x70, 0x05, 0x4e, 0xad, 0x03, 0xfc, 0x81, 0x61, 0x1b, 0xec, + 0x92, 0xdd, 0x6e, 0x9f, 0x00, 0x07, 0xa1, 0xd8, 0x06, 0xb3, 0x8b, 0x95, 0x63, 0x9b, 0x62, 0x35, 0xf4, 0x8e, 0x04, + 0xec, 0xbe, 0x1d, 0x96, 0x62, 0x97, 0xfa, 0xa8, 0x90, 0x94, 0x7a, 0xd1, 0x3f, 0xef, 0x14, 0x58, 0x52, 0x30, 0xe5, + 0x0d, 0x96, 0x55, 0xb5, 0x2a, 0xa3, 0xfd, 0xfd, 0x78, 0x95, 0x8d, 0xca, 0x0c, 0xb6, 0x79, 0x79, 0x75, 0x01, 0x00, + 0x13, 0x01, 0x6d, 0xbc, 0x5b, 0x8b, 0xcc, 0xbc, 0x58, 0xd0, 0x65, 0x86, 0x6b, 0x12, 0xcc, 0x0e, 0x72, 0x6e, 0x75, + 0x93, 0x53, 0x62, 0x1f, 0xc0, 0x06, 0x73, 0xbb, 0x6d, 0xf0, 0x0b, 0x47, 0xa3, 0x27, 0xb3, 0x65, 0xa6, 0x0d, 0x5c, + 0xb9, 0xd9, 0xff, 0x24, 0xf2, 0xd2, 0x50, 0xf1, 0x49, 0xa6, 0xcf, 0x33, 0xe0, 0xf3, 0xd8, 0x27, 0x11, 0xfa, 0x2c, + 0x57, 0xa3, 0x35, 0xc0, 0xc6, 0x66, 0xe7, 0xb7, 0xa3, 0x94, 0x43, 0x84, 0x8e, 0xc0, 0xaa, 0x6b, 0x96, 0x19, 0xf1, + 0x6d, 0x2a, 0x6e, 0x5a, 0xaa, 0xb0, 0x4f, 0xc2, 0x73, 0xde, 0xe1, 0xc6, 0x71, 0xa8, 0x37, 0x89, 0xc2, 0xe7, 0x28, + 0x44, 0xe5, 0x68, 0x5c, 0xe8, 0xe4, 0x6b, 0x99, 0xc7, 0x84, 0x62, 0x0e, 0xf7, 0xee, 0xf7, 0xd4, 0x99, 0xcb, 0xf8, + 0xc2, 0xbd, 0xe7, 0xbe, 0xcc, 0xe4, 0x4a, 0x02, 0x48, 0x94, 0xaa, 0xfd, 0xe7, 0xcf, 0x48, 0x8d, 0xff, 0x4e, 0xb5, + 0x06, 0xa0, 0xf7, 0x33, 0xd4, 0xe4, 0x08, 0x02, 0xb6, 0x62, 0xea, 0x47, 0x17, 0xb0, 0x92, 0xf9, 0x9f, 0x50, 0xb7, + 0x23, 0xd8, 0x46, 0xc5, 0x13, 0x8a, 0x2a, 0x5a, 0xf0, 0x74, 0x2d, 0xd2, 0x58, 0x24, 0xb7, 0x11, 0xaf, 0xa7, 0x58, + 0x12, 0xb3, 0x11, 0xc3, 0x7e, 0x6e, 0x76, 0xe1, 0x5d, 0xd1, 0x30, 0x89, 0xa7, 0xa5, 0xbf, 0xad, 0xbc, 0xcd, 0x64, + 0x19, 0x67, 0x64, 0xca, 0x15, 0x82, 0xb9, 0xd5, 0xf7, 0x98, 0x13, 0xfc, 0xf1, 0xc1, 0x63, 0x42, 0xaf, 0xe4, 0xb4, + 0x44, 0x90, 0x3e, 0x91, 0x5a, 0xd7, 0x55, 0xec, 0xd7, 0x14, 0xa2, 0x5a, 0x08, 0x06, 0xa1, 0x4c, 0x4d, 0xfb, 0x14, + 0xdf, 0x67, 0xcb, 0xfe, 0x64, 0xca, 0x96, 0x64, 0x23, 0xa0, 0x63, 0xd2, 0x79, 0xbf, 0x7a, 0x7b, 0x76, 0xe6, 0xfd, + 0x06, 0x4d, 0x38, 0xa8, 0x6e, 0xa0, 0x5d, 0x05, 0x99, 0xc6, 0x28, 0x36, 0x8b, 0xb1, 0x76, 0x6b, 0x22, 0x82, 0x20, + 0xdc, 0xe5, 0x2c, 0x6c, 0xb7, 0x13, 0xe2, 0x6d, 0x20, 0x81, 0x02, 0xd7, 0x36, 0xca, 0x49, 0x48, 0xd4, 0x85, 0xcc, + 0x1c, 0x13, 0x92, 0x05, 0x7a, 0x8d, 0x1d, 0x04, 0xf4, 0x98, 0xdb, 0xa7, 0x80, 0xbe, 0x28, 0xd8, 0x31, 0x1f, 0x04, + 0x43, 0x8c, 0x37, 0x1b, 0xd0, 0x8f, 0x52, 0x3d, 0x82, 0xc7, 0x34, 0xb0, 0x5c, 0xf4, 0x75, 0xc1, 0x10, 0x66, 0xe9, + 0xb7, 0x94, 0x4d, 0xbe, 0xf9, 0xa7, 0x9b, 0xdf, 0x33, 0x2d, 0x66, 0x07, 0xa1, 0xb8, 0xbd, 0x9e, 0x00, 0xf1, 0xab, + 0xf8, 0x25, 0x58, 0x9b, 0x6b, 0x89, 0xb7, 0x27, 0x79, 0x10, 0xbe, 0x1c, 0xdd, 0x7e, 0x52, 0x9a, 0x4f, 0x20, 0x68, + 0x8f, 0x93, 0x94, 0xbb, 0xef, 0x4e, 0xa4, 0xab, 0x08, 0x46, 0x0b, 0x10, 0xfc, 0xee, 0xac, 0xe4, 0xb4, 0x29, 0xfc, + 0xc7, 0x3a, 0x5f, 0x60, 0x2c, 0x15, 0x79, 0x82, 0xd3, 0xdf, 0x04, 0x07, 0xf7, 0x6f, 0x65, 0xd6, 0x90, 0xe8, 0x4c, + 0x7d, 0x04, 0xf4, 0x7f, 0xac, 0xc7, 0xef, 0x18, 0x25, 0x7d, 0x49, 0x9c, 0x23, 0x7c, 0x13, 0x2f, 0xd1, 0x74, 0xb1, + 0x37, 0xae, 0xe9, 0xa7, 0xc2, 0xbc, 0xd0, 0x0a, 0x0e, 0xfb, 0xd6, 0x28, 0x3c, 0xf0, 0xcc, 0xfb, 0x4e, 0x34, 0x04, + 0xdd, 0xff, 0xc2, 0xbd, 0xf1, 0x9d, 0x60, 0x19, 0xde, 0x94, 0xb3, 0xcc, 0xdc, 0xe1, 0xae, 0x33, 0x91, 0xca, 0x6b, + 0xc6, 0x82, 0xb5, 0x50, 0xe6, 0xbc, 0x69, 0x30, 0xdb, 0xd4, 0x91, 0x4a, 0x76, 0xdf, 0xff, 0xd5, 0x38, 0x61, 0xb3, + 0x41, 0x70, 0x52, 0xc9, 0x22, 0xbe, 0xe0, 0xc1, 0x54, 0xab, 0x28, 0x32, 0xb0, 0x2b, 0x04, 0xa4, 0x1c, 0xa7, 0xbd, + 0x83, 0x27, 0x4b, 0xcd, 0x4c, 0xc8, 0x6f, 0xab, 0xb3, 0x80, 0xb7, 0x66, 0x34, 0x8f, 0x2b, 0xd8, 0x65, 0xbe, 0x92, + 0xe2, 0xbb, 0x96, 0x24, 0x1b, 0xeb, 0x6f, 0xc8, 0xb0, 0xad, 0x7c, 0xe6, 0x0c, 0x30, 0x77, 0x3e, 0x4a, 0x15, 0xf4, + 0xaf, 0xc7, 0xd8, 0xb5, 0x44, 0x22, 0x20, 0x9c, 0xc5, 0xc4, 0xad, 0x30, 0xe1, 0x30, 0x5d, 0xa0, 0xa0, 0x18, 0x03, + 0x05, 0x9d, 0xc8, 0x90, 0xd3, 0x63, 0x3e, 0x48, 0x1a, 0xb3, 0xf5, 0x97, 0x2a, 0x91, 0x5e, 0x4b, 0x42, 0x4f, 0xe1, + 0xf7, 0xb8, 0xc5, 0x03, 0x35, 0x82, 0x75, 0xba, 0x9b, 0xd3, 0xfe, 0xeb, 0x82, 0x0c, 0x7f, 0x03, 0x6f, 0xb7, 0xd8, + 0x5e, 0x96, 0x13, 0x58, 0xdc, 0xb1, 0x57, 0x3c, 0xcd, 0x55, 0x8b, 0x13, 0xe2, 0x11, 0x8b, 0xdc, 0x27, 0x16, 0x30, + 0xa2, 0x86, 0xd1, 0xf8, 0xf1, 0xe4, 0xcd, 0x6b, 0x8d, 0x61, 0x95, 0xfb, 0x1f, 0xc0, 0x88, 0x6a, 0x69, 0xbb, 0x1d, + 0xf0, 0xe5, 0x08, 0x0d, 0xd8, 0x53, 0x37, 0xd8, 0xfd, 0xbe, 0x49, 0x3b, 0x2a, 0xbd, 0x6c, 0x4e, 0x0c, 0xba, 0xa3, + 0xb4, 0x59, 0x2a, 0x03, 0xe3, 0xae, 0xc2, 0xd1, 0x9c, 0xd8, 0x88, 0x55, 0xbd, 0x0f, 0xc3, 0x25, 0x8d, 0xad, 0xac, + 0xdc, 0xee, 0x26, 0x1c, 0xd9, 0x04, 0xb8, 0x3e, 0x05, 0xed, 0xd5, 0x9c, 0x83, 0x16, 0x94, 0x28, 0x70, 0x44, 0xdb, + 0x6d, 0x08, 0x11, 0x49, 0x8a, 0xe1, 0x64, 0x16, 0x16, 0xc3, 0xa1, 0x1a, 0xf8, 0x82, 0x90, 0xe8, 0x53, 0x31, 0xcf, + 0x16, 0x0a, 0xc1, 0xc8, 0xdf, 0x49, 0xbf, 0x14, 0x8a, 0x53, 0xee, 0x7d, 0x27, 0xc8, 0xe6, 0x5f, 0x29, 0xc6, 0x60, + 0x74, 0x9a, 0xcd, 0x0c, 0x24, 0xac, 0xc7, 0x15, 0x51, 0xeb, 0xc8, 0xce, 0x06, 0xa8, 0x62, 0xd1, 0x34, 0x18, 0xd4, + 0x2d, 0x9e, 0x58, 0xcf, 0xe8, 0x3d, 0xa8, 0x04, 0x51, 0x2d, 0xd8, 0x8d, 0xe1, 0x5a, 0x7b, 0x2d, 0x42, 0x49, 0x39, + 0x69, 0x32, 0x33, 0x56, 0x34, 0x58, 0x80, 0x90, 0x34, 0x2e, 0xab, 0x57, 0x32, 0xcd, 0xce, 0x33, 0x40, 0x90, 0x70, + 0xfe, 0x84, 0xb2, 0xf1, 0xe6, 0xa9, 0x9a, 0x97, 0xae, 0xc4, 0x99, 0x85, 0x3d, 0xe9, 0x7a, 0x4b, 0x0b, 0x12, 0x15, + 0x40, 0xa3, 0x7c, 0x2d, 0xcf, 0xf7, 0x3b, 0x56, 0x21, 0xbb, 0x1f, 0x4e, 0x95, 0xed, 0x10, 0x3f, 0x62, 0x15, 0xf1, + 0x4e, 0xeb, 0x4a, 0x89, 0x34, 0x3a, 0xda, 0x06, 0xc4, 0xb0, 0x65, 0xdf, 0xa2, 0x86, 0x0f, 0xc2, 0x2e, 0x3a, 0xc9, + 0x0f, 0x7a, 0x8a, 0xc7, 0xd6, 0x40, 0xd2, 0xd7, 0x22, 0xf8, 0x1a, 0x1d, 0xe9, 0x44, 0x99, 0x46, 0x62, 0x0a, 0x89, + 0x7e, 0xbd, 0xd0, 0x1a, 0xcb, 0x28, 0xfb, 0x8a, 0xfc, 0x9f, 0x75, 0xf7, 0xbe, 0x13, 0xdb, 0x2d, 0x4c, 0xb2, 0xe7, + 0x81, 0x06, 0x9b, 0x1a, 0xb5, 0x42, 0x38, 0x3b, 0xc7, 0x15, 0x6a, 0xc7, 0x7a, 0x61, 0x09, 0xe4, 0x01, 0x6c, 0x45, + 0x1a, 0x94, 0x41, 0xb2, 0x4f, 0xc5, 0x5c, 0x2c, 0x9c, 0x28, 0x47, 0x2a, 0xfc, 0x33, 0x39, 0x4a, 0x39, 0x5c, 0xc5, + 0xc2, 0x82, 0x21, 0xbf, 0x3a, 0x3a, 0x2f, 0xe4, 0x25, 0x48, 0x4a, 0x0c, 0x43, 0x65, 0x79, 0x5d, 0x5c, 0xb5, 0x25, + 0xa1, 0xbd, 0x53, 0x00, 0xa5, 0x29, 0x40, 0xf0, 0xd2, 0xa8, 0x21, 0x66, 0x1b, 0xb5, 0xbb, 0xa2, 0x3b, 0xc9, 0x01, + 0x75, 0xba, 0x6b, 0xb7, 0xde, 0x94, 0xad, 0xba, 0x15, 0x17, 0xfe, 0x05, 0xa5, 0x1f, 0xf3, 0x41, 0xe1, 0x53, 0x09, + 0xdc, 0xf8, 0x6a, 0x93, 0x65, 0xe7, 0xb7, 0xb8, 0xf4, 0xab, 0xc6, 0xf8, 0xf5, 0xfb, 0x3d, 0xb5, 0x10, 0x1a, 0xa9, + 0xc0, 0x7c, 0xfb, 0xcc, 0x54, 0x65, 0x34, 0xa5, 0xf6, 0x12, 0x5c, 0x39, 0xfb, 0x11, 0x54, 0xc4, 0x75, 0x45, 0x6a, + 0x53, 0x03, 0xb4, 0xe7, 0x65, 0x85, 0x5b, 0x59, 0x80, 0xc7, 0x4e, 0x40, 0xb6, 0x5b, 0x1e, 0x06, 0xfa, 0xd0, 0x09, + 0xfc, 0x2d, 0xf9, 0x0a, 0x99, 0x35, 0xfb, 0xf8, 0x87, 0x16, 0xfc, 0x63, 0x0b, 0x7e, 0x42, 0x71, 0xa7, 0x95, 0xf9, + 0xb7, 0xd2, 0xba, 0xc5, 0xfd, 0x3b, 0x99, 0x26, 0x14, 0x95, 0x09, 0xb5, 0x5f, 0xe9, 0x8f, 0x26, 0x78, 0x94, 0xca, + 0xfe, 0x5e, 0xc2, 0x07, 0xb3, 0xc6, 0x13, 0x6b, 0x3c, 0x19, 0x4e, 0xb7, 0xd2, 0xb0, 0x0c, 0x28, 0xf4, 0xf3, 0x32, + 0x57, 0x54, 0x3f, 0xff, 0xbc, 0xe6, 0x6b, 0xde, 0x6c, 0xb1, 0x4d, 0xba, 0xa7, 0xc1, 0x5e, 0x1e, 0x4d, 0x29, 0x9c, + 0x44, 0x9d, 0x1b, 0x89, 0xba, 0xa8, 0x59, 0x86, 0xea, 0x04, 0xaf, 0xe6, 0xa9, 0x1e, 0xf6, 0x66, 0x22, 0x5a, 0x2b, + 0x29, 0x4b, 0x0c, 0x58, 0xeb, 0xc8, 0x43, 0x72, 0xb7, 0xd6, 0x71, 0xa7, 0xa1, 0x2e, 0x4d, 0xa1, 0x26, 0x58, 0xe1, + 0x02, 0x1c, 0x41, 0xef, 0x8a, 0x90, 0xc3, 0x35, 0x55, 0xe9, 0x17, 0x34, 0x25, 0x4f, 0x3c, 0x45, 0xad, 0x56, 0xa4, + 0xdb, 0x8f, 0x72, 0xec, 0x86, 0x6f, 0x9c, 0x90, 0x13, 0x23, 0xf4, 0x77, 0xc7, 0x52, 0xce, 0xd0, 0xe2, 0x41, 0x9d, + 0x60, 0xbd, 0xbc, 0xa5, 0x40, 0x31, 0x47, 0x97, 0x55, 0xd7, 0xbc, 0x44, 0xdb, 0x97, 0x65, 0xbf, 0x9f, 0xdb, 0x7a, + 0x52, 0x76, 0xb4, 0x59, 0x9a, 0x7d, 0x88, 0x8a, 0x29, 0xdc, 0xf5, 0x89, 0xe6, 0xaf, 0x42, 0x7d, 0xd5, 0x96, 0x39, + 0x1f, 0x71, 0xc4, 0x09, 0xc9, 0x49, 0xfd, 0x87, 0x9a, 0x7a, 0x25, 0xee, 0x57, 0x95, 0xfc, 0x22, 0x8c, 0x15, 0xa3, + 0x25, 0x86, 0x28, 0xd2, 0xee, 0x8d, 0xe9, 0xcb, 0x02, 0xe0, 0xaf, 0x04, 0xfb, 0x94, 0x86, 0x5a, 0xf9, 0x2d, 0xda, + 0x02, 0xfe, 0x8d, 0xe2, 0x06, 0xac, 0x02, 0x03, 0x8c, 0x26, 0xdb, 0x73, 0x9a, 0xc0, 0x01, 0x27, 0xb4, 0x8a, 0x82, + 0x0a, 0x33, 0x34, 0xd4, 0x16, 0x46, 0x5f, 0xa1, 0x8c, 0x5b, 0x65, 0xf6, 0x6e, 0x8c, 0x9d, 0x16, 0x78, 0x0d, 0xff, + 0x46, 0x2f, 0x14, 0xb3, 0x51, 0x07, 0xe9, 0xd1, 0x49, 0x4c, 0x7f, 0xdc, 0xc2, 0xc9, 0xcd, 0xc2, 0x59, 0xd6, 0x2c, + 0x81, 0xee, 0xc0, 0x05, 0x31, 0xee, 0xf7, 0x73, 0x38, 0x32, 0xcd, 0xc8, 0x17, 0x2c, 0xa7, 0x31, 0x5b, 0x52, 0xed, + 0x79, 0x78, 0x51, 0x85, 0x39, 0x5d, 0x5a, 0x19, 0x6f, 0xca, 0x40, 0x65, 0xb4, 0xdd, 0x86, 0xf0, 0xa7, 0xdb, 0xda, + 0x25, 0x9d, 0x2f, 0x21, 0x03, 0xfc, 0x01, 0x89, 0x28, 0x62, 0x81, 0xff, 0x5b, 0x8d, 0x53, 0x7a, 0xa2, 0xb4, 0x66, + 0x09, 0x5d, 0x33, 0x5d, 0x3f, 0x3d, 0x67, 0xeb, 0xc6, 0x52, 0xd8, 0x6e, 0xc3, 0x66, 0x02, 0xd3, 0x9c, 0x2b, 0x99, + 0x9e, 0xa3, 0x4e, 0x0a, 0xa8, 0x58, 0x78, 0x8e, 0xcb, 0x2f, 0x25, 0x14, 0x9a, 0x3b, 0x5f, 0x2e, 0x8c, 0x12, 0x13, + 0x5a, 0x25, 0xbf, 0x7c, 0xa8, 0xcc, 0xd7, 0xc6, 0x43, 0xf0, 0xc7, 0x34, 0x4c, 0x4c, 0x91, 0xa8, 0x10, 0x9d, 0xfd, + 0x02, 0xb2, 0x1c, 0x01, 0xb8, 0x9e, 0xaf, 0x20, 0x0a, 0xdc, 0x1a, 0xe2, 0xc2, 0x43, 0x83, 0xde, 0x16, 0xf2, 0x32, + 0x2b, 0x79, 0x88, 0xf7, 0x04, 0x4f, 0x33, 0x7a, 0xb7, 0xc1, 0x87, 0xb6, 0xf6, 0xe8, 0x09, 0xb2, 0xf1, 0x94, 0xfb, + 0xf5, 0x2f, 0x22, 0x9c, 0x43, 0xf4, 0xce, 0x05, 0xd5, 0xea, 0x6a, 0x07, 0xc8, 0xe5, 0xd9, 0x5e, 0x3d, 0x80, 0xd3, + 0x4d, 0x5f, 0xdf, 0xaa, 0xd0, 0x99, 0x03, 0x48, 0x7b, 0x48, 0xd6, 0x35, 0xd7, 0x3b, 0xc0, 0x3b, 0x12, 0xd7, 0x40, + 0x63, 0xdd, 0xd6, 0xec, 0xb4, 0x47, 0xf1, 0x98, 0xc8, 0xcc, 0x58, 0xa4, 0x18, 0x73, 0xb7, 0x4e, 0x8b, 0xa2, 0x0d, + 0x9a, 0x21, 0xec, 0xde, 0x75, 0xb2, 0x75, 0x2b, 0xe2, 0xfc, 0xdd, 0xb6, 0x2f, 0x30, 0x1a, 0xc6, 0x5c, 0xbb, 0xe7, + 0x1b, 0xba, 0xad, 0xdd, 0xc8, 0x68, 0x24, 0xc8, 0x4c, 0x1d, 0x88, 0xb2, 0xb6, 0x06, 0x6c, 0x0f, 0xb8, 0xde, 0xb4, + 0xc0, 0xcf, 0x9b, 0x18, 0xbc, 0x3d, 0x6b, 0x9c, 0xd2, 0xfa, 0x1a, 0xd7, 0x1c, 0x57, 0x85, 0x88, 0xda, 0x22, 0x05, + 0xc0, 0xb0, 0xf3, 0x05, 0xee, 0xcc, 0x0a, 0x83, 0x39, 0x61, 0xa9, 0x64, 0xa7, 0x72, 0xfd, 0x39, 0x6c, 0x71, 0x90, + 0xca, 0x97, 0x5e, 0x7f, 0xff, 0xf0, 0xc5, 0x17, 0xe8, 0xb6, 0xe7, 0xfc, 0x08, 0x82, 0x4c, 0xa0, 0x83, 0x9a, 0x52, + 0x3d, 0xfe, 0x50, 0x00, 0xb5, 0x87, 0x79, 0xf8, 0xa1, 0x60, 0x22, 0xbe, 0xca, 0x2e, 0xe2, 0x4a, 0x16, 0xa3, 0x2b, + 0x2e, 0x52, 0x59, 0x58, 0xa9, 0x71, 0x70, 0xbc, 0x5a, 0xe5, 0x3c, 0x00, 0x53, 0x79, 0xcb, 0x28, 0x3b, 0xb9, 0xa4, + 0x1e, 0x5c, 0x2d, 0x4f, 0xaf, 0xb4, 0xe8, 0xbc, 0xbc, 0xba, 0x08, 0x22, 0xfc, 0x75, 0x66, 0x7e, 0x5c, 0xc6, 0xe5, + 0xc7, 0x20, 0xb2, 0x36, 0x75, 0xe6, 0x07, 0x4a, 0xe5, 0xc1, 0xdf, 0x09, 0x64, 0xba, 0x3f, 0x14, 0x60, 0x99, 0x6d, + 0x2b, 0x3e, 0x8c, 0xb1, 0xd6, 0xe1, 0x84, 0xcc, 0x54, 0x89, 0xde, 0xbb, 0x64, 0x5d, 0x80, 0xb5, 0x9f, 0xc2, 0x76, + 0x56, 0xb9, 0x66, 0x58, 0x99, 0xaa, 0xc8, 0x10, 0xb4, 0x35, 0xdb, 0x0f, 0xad, 0x13, 0xcd, 0x1c, 0xbd, 0x05, 0xf4, + 0x03, 0xd9, 0xbf, 0xa0, 0x72, 0xcd, 0x3c, 0x1f, 0x9b, 0xc6, 0xeb, 0x07, 0xfb, 0x17, 0x9e, 0x40, 0xc9, 0xde, 0xc9, + 0x51, 0x98, 0x08, 0x9e, 0xc6, 0x66, 0x7c, 0x91, 0x67, 0x05, 0xec, 0xa0, 0xc9, 0x78, 0x4c, 0xbd, 0xa5, 0xd5, 0xba, + 0x39, 0x3a, 0x64, 0xdb, 0xec, 0x61, 0xf5, 0x90, 0x93, 0x7d, 0xde, 0x32, 0xb5, 0x6d, 0x5b, 0xc7, 0x79, 0x9a, 0x7c, + 0x65, 0xba, 0x5f, 0xae, 0x6d, 0x84, 0x78, 0xe5, 0xec, 0xe8, 0xbc, 0xa4, 0x5b, 0xdf, 0x94, 0x86, 0x5e, 0x4b, 0x00, + 0xe6, 0xd3, 0x06, 0xfc, 0x05, 0x93, 0xeb, 0x51, 0xc5, 0xcb, 0x0a, 0x24, 0x2c, 0x28, 0xc2, 0x9b, 0x62, 0x6f, 0x0a, + 0x77, 0xe3, 0xf4, 0x1c, 0x76, 0xe0, 0x62, 0x8a, 0xee, 0x38, 0x31, 0x99, 0x95, 0x46, 0x2b, 0x1a, 0xe9, 0x5f, 0xae, + 0x2f, 0xb1, 0xee, 0x8b, 0x56, 0xe6, 0xd9, 0x9c, 0x0a, 0x9b, 0xde, 0x55, 0x2e, 0x9d, 0xa8, 0xdf, 0x32, 0xe1, 0xca, + 0x95, 0x20, 0x20, 0xd3, 0x82, 0xf5, 0x0a, 0xb3, 0x8b, 0x0a, 0x24, 0x64, 0x60, 0xf8, 0x1a, 0xac, 0x45, 0xc9, 0x8d, + 0x15, 0xac, 0x77, 0xcf, 0xd7, 0x09, 0x42, 0x0a, 0x1e, 0xb8, 0x09, 0xfa, 0xd0, 0xba, 0x79, 0x3b, 0x4a, 0x94, 0x41, + 0x7c, 0x72, 0xed, 0x94, 0x83, 0x04, 0x02, 0x70, 0x60, 0x55, 0x48, 0x12, 0x05, 0x3a, 0x0f, 0xae, 0x66, 0x1c, 0xc1, + 0xe6, 0x95, 0x33, 0x17, 0x37, 0x80, 0xf3, 0xca, 0x9f, 0xcb, 0x06, 0x5b, 0xd6, 0x23, 0xaa, 0xcc, 0x19, 0xa7, 0x18, + 0xd4, 0xc9, 0x12, 0xf4, 0x95, 0xa5, 0xb4, 0x17, 0xa0, 0x69, 0xbc, 0x64, 0x2b, 0xe5, 0x03, 0x40, 0xcf, 0xd8, 0x4a, + 0x19, 0xfb, 0xe3, 0xd7, 0xa7, 0x6c, 0xa5, 0xa5, 0xc1, 0xd3, 0xcb, 0xd9, 0xd9, 0xec, 0x74, 0xc0, 0x0e, 0xa2, 0x50, + 0x1b, 0x30, 0x04, 0x2e, 0x32, 0x41, 0x30, 0x08, 0x35, 0xfe, 0xcb, 0x40, 0x05, 0x08, 0x23, 0x1e, 0x8f, 0x8d, 0x38, + 0x62, 0xe1, 0x78, 0x88, 0xc1, 0xc0, 0x9a, 0x2f, 0x48, 0x40, 0xa8, 0x29, 0x0d, 0x7d, 0x3d, 0xc3, 0xe1, 0x64, 0x6f, + 0x02, 0xa9, 0x98, 0x99, 0xa9, 0xc2, 0xd8, 0x98, 0x44, 0x10, 0xff, 0xb5, 0xb3, 0x5e, 0x28, 0xb7, 0xbb, 0x46, 0x03, + 0x41, 0x33, 0xf8, 0xa2, 0x8a, 0x27, 0x7b, 0xc3, 0xae, 0x8a, 0x71, 0x14, 0xae, 0x8c, 0xf2, 0xed, 0xf4, 0x10, 0xc0, + 0x7c, 0x4f, 0x87, 0xbe, 0x5c, 0xe2, 0x74, 0xff, 0x31, 0x79, 0xf8, 0x98, 0xd0, 0x53, 0x76, 0xfa, 0xd5, 0x63, 0x7a, + 0xaa, 0xc8, 0xc9, 0xde, 0x24, 0xba, 0x62, 0x16, 0x03, 0xe7, 0x40, 0x35, 0x81, 0x5e, 0x8c, 0xd6, 0x42, 0x2d, 0x30, + 0xed, 0xd0, 0x14, 0x7e, 0x3b, 0xde, 0x0b, 0x06, 0x57, 0xed, 0xa6, 0x5f, 0xb5, 0xdb, 0xea, 0x79, 0x75, 0xed, 0x1d, + 0x44, 0xbb, 0xc5, 0x4c, 0xfe, 0x39, 0xde, 0x73, 0x73, 0x80, 0xf5, 0xdd, 0x3f, 0x26, 0xa6, 0x49, 0x3b, 0xa3, 0xe2, + 0xd7, 0xf4, 0x08, 0xfb, 0xd0, 0x2c, 0xb2, 0xa3, 0x0f, 0xc3, 0x7f, 0xab, 0x13, 0xf5, 0xe9, 0x57, 0x07, 0x40, 0x8e, + 0x40, 0x06, 0x8a, 0x25, 0x82, 0x19, 0x0e, 0x34, 0x05, 0x14, 0x64, 0x7a, 0xdc, 0xa9, 0x1e, 0x7e, 0x35, 0x6a, 0x6a, + 0x46, 0xae, 0x60, 0x6a, 0xb0, 0x2d, 0xf8, 0x81, 0xea, 0x86, 0xfe, 0x46, 0xa3, 0x3d, 0x69, 0x27, 0x33, 0xf3, 0x92, + 0xda, 0x38, 0x77, 0x57, 0x10, 0xd0, 0xd9, 0xc1, 0x2d, 0x4a, 0xf6, 0xf5, 0xe1, 0xc5, 0x1e, 0xae, 0x22, 0x40, 0x0d, + 0x63, 0xc1, 0xd7, 0x83, 0x0b, 0xbd, 0xb9, 0xf7, 0x02, 0x32, 0xf8, 0x3a, 0x38, 0xfa, 0x7a, 0x20, 0x07, 0xc1, 0xe1, + 0xfe, 0xc5, 0x51, 0xe0, 0x8c, 0xfb, 0x21, 0xe4, 0xa5, 0xaa, 0x28, 0x66, 0xc2, 0x54, 0x91, 0xd8, 0xda, 0x73, 0x5b, + 0xaf, 0x32, 0x3e, 0xa3, 0xe9, 0xd4, 0x22, 0xa1, 0x87, 0x29, 0x8b, 0xcd, 0xef, 0x60, 0xc2, 0x2f, 0x83, 0xc8, 0x05, + 0x85, 0x9d, 0xe5, 0x51, 0x4c, 0x97, 0xec, 0x46, 0x84, 0x29, 0x4d, 0xf6, 0x73, 0x42, 0xa2, 0x70, 0xa9, 0xc0, 0x04, + 0xd5, 0xeb, 0x04, 0xe2, 0xda, 0xba, 0xcf, 0x6f, 0x44, 0xb8, 0xa4, 0xf9, 0x7e, 0x42, 0x5a, 0x45, 0xb8, 0x08, 0x35, + 0x9b, 0x9a, 0x9e, 0xb3, 0x70, 0x45, 0x2f, 0xd0, 0x54, 0x73, 0x1d, 0x5e, 0x00, 0x97, 0xb7, 0x9e, 0xaf, 0x16, 0xec, + 0xa2, 0x21, 0x7d, 0x33, 0x7c, 0xf1, 0xb9, 0xf5, 0xc9, 0x03, 0x1e, 0xd2, 0xf9, 0xe1, 0xa5, 0x60, 0x03, 0x70, 0x95, + 0xf1, 0xeb, 0xef, 0xe4, 0x8d, 0x9e, 0x97, 0xf6, 0x14, 0xe3, 0xcc, 0xb4, 0x13, 0x93, 0x76, 0x42, 0xee, 0xdf, 0xb7, + 0x7d, 0xf7, 0xe2, 0xb5, 0x72, 0x59, 0xb5, 0x0c, 0x49, 0xbc, 0x56, 0xae, 0xd3, 0x28, 0x39, 0xb5, 0x02, 0x4f, 0x76, + 0xce, 0xab, 0x64, 0xe9, 0x1f, 0x54, 0xd6, 0x6a, 0xc0, 0x1e, 0x23, 0x96, 0x85, 0xc2, 0xb1, 0x7f, 0x95, 0xb1, 0x78, + 0xdd, 0x40, 0x06, 0x46, 0xee, 0xed, 0x55, 0xc6, 0xbc, 0x18, 0xb4, 0xf9, 0xda, 0x0b, 0xdd, 0xe7, 0xa5, 0x2f, 0x5b, + 0xbc, 0x97, 0x53, 0x6a, 0x18, 0x89, 0xe8, 0xde, 0x58, 0x99, 0x51, 0xaa, 0x44, 0xad, 0x41, 0x23, 0x82, 0x8d, 0x5d, + 0x30, 0x50, 0x70, 0x42, 0xe5, 0x9e, 0x3a, 0xdb, 0xb7, 0x53, 0x2a, 0x3d, 0xa0, 0x5d, 0x6a, 0x54, 0xe5, 0x6e, 0x99, + 0x49, 0x56, 0x0d, 0x82, 0xd1, 0x5f, 0xa5, 0x14, 0x33, 0xbc, 0x33, 0xb2, 0x60, 0x0a, 0x56, 0x82, 0xaa, 0x96, 0x61, + 0x39, 0xe4, 0xa8, 0xc5, 0x33, 0x3e, 0xa9, 0x52, 0xff, 0xe8, 0x08, 0x1a, 0x9c, 0xae, 0x5b, 0x41, 0x83, 0x1f, 0x8f, + 0x1f, 0xeb, 0x81, 0x5e, 0xaf, 0xb5, 0xe3, 0xa1, 0xcf, 0x6f, 0x23, 0xde, 0xb8, 0xee, 0x3d, 0xd5, 0x5a, 0x85, 0x32, + 0xd0, 0x62, 0x45, 0xe5, 0x4a, 0x2d, 0xe9, 0xdd, 0x2e, 0x02, 0x60, 0x11, 0x1b, 0xb3, 0xf1, 0xae, 0x6d, 0x56, 0x08, + 0x1a, 0x5d, 0x76, 0xb4, 0x89, 0x07, 0x2c, 0xd1, 0xad, 0x1d, 0x4c, 0x68, 0x7c, 0xc4, 0xca, 0x7e, 0x3f, 0x3f, 0x02, + 0x7a, 0xaa, 0x8d, 0x98, 0x0a, 0x38, 0xf2, 0xbf, 0xb4, 0x22, 0x53, 0x14, 0xd8, 0xac, 0xa9, 0xbb, 0x35, 0x96, 0x91, + 0xe8, 0xcb, 0x94, 0x2e, 0x4f, 0x78, 0x06, 0x4c, 0xe7, 0xeb, 0x96, 0xe3, 0xca, 0xae, 0xe2, 0xc8, 0x53, 0x61, 0x59, + 0x71, 0x5e, 0x85, 0xe3, 0xad, 0xc7, 0x37, 0xd8, 0x37, 0x6c, 0xda, 0xca, 0x1f, 0x42, 0x58, 0x08, 0xaf, 0x32, 0xb8, + 0x8d, 0x68, 0x3b, 0x09, 0x54, 0xde, 0x98, 0xeb, 0x84, 0xb2, 0xb9, 0x3d, 0x5f, 0x7b, 0x06, 0xe9, 0xc4, 0x1c, 0x28, + 0xd5, 0x08, 0x5a, 0xa3, 0x59, 0x50, 0x35, 0xe2, 0x91, 0x33, 0xff, 0x72, 0x06, 0xb1, 0x5a, 0xbe, 0xa4, 0xa9, 0x14, + 0x0d, 0xc0, 0xb8, 0x00, 0x2e, 0x4f, 0x1f, 0xde, 0xfd, 0x74, 0xc2, 0xe3, 0x22, 0x59, 0xbe, 0x8d, 0x8b, 0xf8, 0xb2, + 0x0c, 0x37, 0x6a, 0x8c, 0xe2, 0x9a, 0x4c, 0xc5, 0x80, 0x49, 0xb3, 0x92, 0x9a, 0xbb, 0x52, 0x13, 0x62, 0xac, 0x33, + 0x59, 0x97, 0x95, 0xbc, 0x6c, 0x54, 0xba, 0x2e, 0x32, 0xfc, 0xb8, 0xe5, 0x73, 0xba, 0x0f, 0xc0, 0xa6, 0xc6, 0x85, + 0x34, 0x92, 0xba, 0x10, 0x63, 0x2e, 0xe2, 0x75, 0x7d, 0x3c, 0x6e, 0x74, 0xbd, 0x64, 0x4f, 0xc6, 0x8f, 0xa6, 0xaf, + 0xb2, 0x30, 0x1b, 0x08, 0x32, 0xaa, 0x96, 0x5c, 0xb4, 0x4c, 0x39, 0x95, 0x49, 0x00, 0xfa, 0x78, 0xf6, 0x18, 0x3b, + 0x18, 0x8f, 0xc9, 0xa6, 0x2d, 0x1e, 0xe0, 0x61, 0xba, 0x0e, 0x0b, 0x32, 0xd3, 0x75, 0x44, 0x81, 0xe0, 0x37, 0x55, + 0x00, 0xc8, 0x96, 0xb6, 0x2a, 0xc3, 0xa5, 0xb1, 0x27, 0xe3, 0x09, 0x95, 0xd8, 0xed, 0x90, 0xd4, 0x5e, 0x85, 0x6e, + 0xe6, 0xa5, 0xef, 0x51, 0x24, 0x8d, 0xcb, 0xd2, 0x4e, 0xa5, 0x52, 0xed, 0x99, 0x99, 0xeb, 0x1a, 0xc4, 0xa4, 0x08, + 0x75, 0xdd, 0xa5, 0x57, 0xf7, 0x6e, 0x73, 0xad, 0xd9, 0x0e, 0x78, 0xaf, 0x41, 0x33, 0x94, 0xbc, 0xc5, 0xbc, 0x75, + 0x45, 0xd4, 0xf4, 0x62, 0x0d, 0x66, 0xc5, 0x28, 0x5b, 0x8a, 0xd6, 0x6b, 0x0a, 0x4a, 0xc1, 0x68, 0xb5, 0xf6, 0x16, + 0xee, 0x53, 0xd9, 0xb8, 0xb0, 0x64, 0x7a, 0xb5, 0x28, 0x29, 0xa1, 0xba, 0xa9, 0x18, 0x29, 0x61, 0xa4, 0x34, 0x3c, + 0x95, 0xef, 0x05, 0x1e, 0xe7, 0x79, 0x10, 0xb5, 0xbc, 0xc0, 0x8e, 0x2b, 0x72, 0x0c, 0x8e, 0x5e, 0x26, 0xa7, 0xa1, + 0xc0, 0x3f, 0x66, 0x0a, 0xd4, 0x75, 0xa8, 0xee, 0x37, 0xb8, 0xf9, 0x7f, 0x2d, 0x58, 0xe0, 0xf1, 0xad, 0x97, 0xb8, + 0x8d, 0x7e, 0x2d, 0x7c, 0x5a, 0xfa, 0x46, 0xfa, 0xae, 0x2e, 0x9e, 0xb4, 0x37, 0x1b, 0x25, 0xcb, 0x2c, 0x4f, 0x5f, + 0xcb, 0x94, 0x83, 0xc8, 0x0c, 0xad, 0x41, 0xd9, 0x91, 0x68, 0xdc, 0xf0, 0xc0, 0x88, 0xb1, 0x71, 0xe3, 0xfb, 0x31, + 0x03, 0xd9, 0x30, 0x58, 0x7d, 0xb3, 0x54, 0x26, 0x6b, 0x40, 0xd8, 0xd0, 0xf2, 0x13, 0x8d, 0xb7, 0x11, 0xea, 0xeb, + 0x17, 0xb8, 0xcd, 0x95, 0xbe, 0xcf, 0xf9, 0x8f, 0x19, 0xfd, 0x11, 0x81, 0x5f, 0xe2, 0x15, 0xc8, 0x3d, 0x9e, 0x42, + 0xdd, 0x08, 0xdb, 0xcb, 0x31, 0x58, 0x12, 0xa2, 0xa3, 0x88, 0x8a, 0x05, 0x0a, 0x9a, 0xc2, 0x20, 0x8a, 0xa8, 0x0b, + 0xe6, 0xf0, 0x2c, 0x97, 0xc9, 0xc7, 0xa9, 0xf1, 0x99, 0x1f, 0xc6, 0x18, 0x43, 0x3a, 0x18, 0x84, 0xd5, 0x2c, 0x18, + 0x8e, 0x47, 0x93, 0x83, 0x27, 0x70, 0x6e, 0x07, 0xe3, 0x80, 0x0c, 0x82, 0xba, 0x5c, 0xc5, 0x82, 0x96, 0x57, 0x17, + 0xb6, 0x0c, 0xfc, 0xb8, 0x0e, 0x06, 0xbf, 0x16, 0x9e, 0xe2, 0x1d, 0x34, 0x27, 0xb7, 0x32, 0x0c, 0x02, 0x7a, 0xb1, + 0x26, 0x20, 0x29, 0xeb, 0x69, 0x7e, 0x52, 0x1f, 0x6e, 0x4c, 0x69, 0xff, 0xcc, 0xe1, 0x05, 0x87, 0x1d, 0x12, 0x28, + 0x90, 0xc6, 0xd3, 0x6c, 0xf4, 0x52, 0x29, 0x72, 0xdf, 0x16, 0x1c, 0xee, 0xcc, 0x3d, 0x67, 0x7a, 0xe4, 0x14, 0x12, + 0xcd, 0x2c, 0xe0, 0x46, 0xfe, 0x52, 0x5c, 0xc5, 0x79, 0x96, 0xee, 0x35, 0xdf, 0xec, 0x95, 0xb7, 0xa2, 0x8a, 0x6f, + 0x46, 0x81, 0xb1, 0x26, 0xe4, 0xbe, 0xea, 0x09, 0xd0, 0x13, 0x60, 0x0b, 0x80, 0x01, 0xf1, 0x8e, 0x99, 0xc9, 0x8c, + 0x47, 0xe0, 0x11, 0xd8, 0xf4, 0x81, 0x2c, 0x6e, 0x9d, 0x4b, 0x92, 0xbf, 0x99, 0x4a, 0x7b, 0xd5, 0x2b, 0x77, 0x0a, + 0xb2, 0x5e, 0x6d, 0xe5, 0xae, 0x5b, 0x9f, 0x7d, 0xd3, 0xe1, 0x15, 0x78, 0x2a, 0xc1, 0x2d, 0xb2, 0xdf, 0x6f, 0x0a, + 0x2a, 0x85, 0x51, 0x11, 0xef, 0x24, 0xd7, 0xe8, 0xdf, 0xee, 0x8d, 0x8d, 0x22, 0xb9, 0xe5, 0xfd, 0x03, 0xa8, 0x33, + 0x79, 0x57, 0xdc, 0xce, 0x21, 0x6a, 0xeb, 0x6e, 0x3c, 0xf0, 0xde, 0xa0, 0x5d, 0xd6, 0x1c, 0xc1, 0x96, 0x17, 0x7b, + 0x19, 0x8c, 0x05, 0xce, 0xca, 0x48, 0xa9, 0x71, 0xad, 0x8c, 0x06, 0xd4, 0x26, 0x77, 0x90, 0xa5, 0x9e, 0x04, 0x45, + 0x8e, 0x67, 0x31, 0x64, 0x1a, 0x6f, 0x03, 0xb1, 0xdf, 0xc8, 0x10, 0xa4, 0x69, 0xdb, 0x6d, 0x73, 0x04, 0xca, 0xee, + 0x81, 0x29, 0x49, 0x5d, 0x1b, 0x53, 0x03, 0x0d, 0x3d, 0x88, 0x1a, 0xa9, 0x88, 0xb3, 0xa3, 0xa7, 0xa0, 0x43, 0x04, + 0xdf, 0xef, 0x34, 0x2b, 0x3b, 0x5e, 0x4c, 0x08, 0x9e, 0xbc, 0xcf, 0x6f, 0xb2, 0xb2, 0x2a, 0xa3, 0x17, 0x29, 0x1a, + 0x42, 0x25, 0x52, 0x44, 0xaf, 0x21, 0xbe, 0x60, 0x89, 0xbf, 0xcb, 0xe8, 0x5d, 0x4a, 0xe3, 0x34, 0xc5, 0xf4, 0x67, + 0x05, 0xfc, 0x7c, 0x0a, 0x28, 0x97, 0xb8, 0x13, 0xa2, 0x53, 0x09, 0xf6, 0x6a, 0x10, 0xdd, 0xab, 0xe2, 0x80, 0x29, + 0x1a, 0xdd, 0x08, 0x8a, 0x98, 0x75, 0x98, 0xfd, 0x43, 0x81, 0x42, 0x21, 0x55, 0xcc, 0x2f, 0xc2, 0x3e, 0x44, 0xd5, + 0x1a, 0xca, 0x39, 0x7e, 0xfb, 0xd2, 0x0c, 0x69, 0x74, 0x23, 0xa9, 0xde, 0xda, 0x78, 0x6c, 0x21, 0x4a, 0x4f, 0x74, + 0xb9, 0xa6, 0xa7, 0xf1, 0x2a, 0x8b, 0x36, 0x80, 0x3f, 0xf1, 0xf6, 0xe5, 0x53, 0x65, 0x61, 0xf2, 0x32, 0x03, 0xc5, + 0xc1, 0xf1, 0xdb, 0x97, 0xaf, 0x64, 0xba, 0xce, 0x79, 0x74, 0x2b, 0x91, 0xb4, 0x1e, 0xbf, 0x7d, 0xf9, 0x33, 0x9a, + 0x7b, 0xbd, 0x2b, 0xe0, 0xfd, 0x0b, 0xe0, 0x2d, 0xa3, 0x64, 0x0d, 0x7d, 0x52, 0xbf, 0xf3, 0x35, 0x76, 0xca, 0xab, + 0xb5, 0x8c, 0x7e, 0x4f, 0x6b, 0x4f, 0x5a, 0xf5, 0x77, 0xe1, 0x53, 0x3b, 0x4f, 0xc0, 0x73, 0x93, 0x67, 0xe2, 0x63, + 0x64, 0x45, 0x3b, 0x41, 0xf4, 0xf5, 0xde, 0xcd, 0x65, 0x2e, 0xca, 0x08, 0x5f, 0x30, 0xb4, 0x0b, 0x8a, 0xf6, 0xf7, + 0xaf, 0xaf, 0xaf, 0x47, 0xd7, 0x8f, 0x46, 0xb2, 0xb8, 0xd8, 0x9f, 0x7c, 0xfb, 0xed, 0xb7, 0xfb, 0xf8, 0x36, 0xf8, + 0xba, 0xed, 0xf6, 0x5e, 0x11, 0x3e, 0x60, 0x01, 0x22, 0x76, 0x7f, 0x0d, 0x57, 0x14, 0xd0, 0xc2, 0x0d, 0xbe, 0x0e, + 0xbe, 0xd6, 0x87, 0xce, 0xd7, 0x87, 0xe5, 0xd5, 0x85, 0x2a, 0xbf, 0xab, 0xe4, 0x83, 0xf1, 0x78, 0xbc, 0x0f, 0x12, + 0xa8, 0xaf, 0x07, 0x7c, 0x10, 0x1c, 0x05, 0x83, 0x0c, 0x2e, 0x34, 0xe5, 0xd5, 0xc5, 0x51, 0xe0, 0x19, 0xd8, 0x36, + 0x58, 0x44, 0x07, 0xe2, 0x12, 0xec, 0x5f, 0xd0, 0xe0, 0xeb, 0x80, 0xb8, 0x94, 0xaf, 0x20, 0xe5, 0xab, 0x83, 0x27, + 0x7e, 0xda, 0xff, 0x52, 0x69, 0x8f, 0xfc, 0xb4, 0x43, 0x4c, 0x7b, 0xf4, 0xd4, 0x4f, 0x3b, 0x52, 0x69, 0xcf, 0xfd, + 0xb4, 0xff, 0x5d, 0x0e, 0x20, 0x75, 0xcf, 0xb7, 0xfe, 0x3b, 0xf5, 0x5a, 0x83, 0xa7, 0x50, 0x94, 0x5d, 0xc6, 0x17, + 0x1c, 0x1a, 0x3d, 0xb8, 0xb9, 0xcc, 0x69, 0x30, 0xc0, 0xf6, 0x7a, 0x46, 0x1e, 0xde, 0x07, 0x5f, 0xaf, 0x8b, 0x3c, + 0x0c, 0xbe, 0x1e, 0x60, 0x21, 0x83, 0xaf, 0x03, 0xf2, 0xb5, 0x3e, 0xd2, 0xae, 0x04, 0xdb, 0x04, 0x2e, 0x34, 0xeb, + 0xd0, 0x06, 0x4c, 0xf3, 0xa5, 0x71, 0x35, 0xfd, 0xad, 0xe8, 0xce, 0x86, 0xb7, 0x44, 0xe5, 0xa6, 0x1b, 0xd4, 0xf4, + 0x2d, 0x78, 0x27, 0x40, 0xa3, 0xa2, 0xe0, 0x2a, 0x2e, 0xc2, 0xe1, 0xb0, 0xbc, 0xba, 0x20, 0x60, 0x97, 0xb9, 0xe2, + 0x71, 0x15, 0x05, 0x42, 0x0e, 0xd5, 0xcf, 0x40, 0x45, 0x02, 0x0b, 0x10, 0xca, 0x08, 0xfe, 0x0b, 0x6a, 0xfa, 0x40, + 0xb2, 0x4d, 0x30, 0xbc, 0xe6, 0x67, 0x1f, 0xb3, 0x6a, 0xa8, 0x44, 0x8b, 0x57, 0x82, 0xc2, 0x0f, 0xf8, 0xeb, 0xaa, + 0x8e, 0x7e, 0x03, 0x37, 0xee, 0xa6, 0x86, 0xfd, 0x81, 0xf4, 0x1c, 0xda, 0xe4, 0x3c, 0x5b, 0x4c, 0x5b, 0x07, 0xfa, + 0x5b, 0x49, 0xaa, 0x79, 0x36, 0x08, 0x86, 0xc1, 0x80, 0x2f, 0xd8, 0x5b, 0x39, 0xe7, 0x9e, 0xf9, 0xd4, 0xb1, 0xf4, + 0xa7, 0x79, 0x96, 0x0d, 0xc0, 0x37, 0x05, 0xf9, 0x91, 0xfd, 0xff, 0x9e, 0x0f, 0x51, 0x78, 0x38, 0x78, 0xb0, 0x4f, + 0x66, 0xc1, 0xea, 0x06, 0x3d, 0x3a, 0xa3, 0x20, 0x13, 0x4b, 0x5e, 0x64, 0x95, 0xb7, 0x54, 0x6e, 0xd6, 0x6d, 0x2f, + 0x8f, 0x3b, 0xcf, 0xe6, 0x55, 0x2c, 0x02, 0x75, 0xce, 0x81, 0xe2, 0x0d, 0x65, 0x4f, 0x65, 0x53, 0x42, 0xaa, 0x0d, + 0x79, 0xc3, 0x72, 0xc0, 0x82, 0xc3, 0xde, 0x70, 0xb8, 0x17, 0x0c, 0x9c, 0x3a, 0x77, 0x10, 0xec, 0x0d, 0x87, 0x47, + 0x81, 0xbb, 0x0f, 0x65, 0x23, 0x77, 0x67, 0xa4, 0x05, 0xfb, 0xbb, 0x08, 0x4b, 0x0a, 0xe2, 0x31, 0xa9, 0xc5, 0x5f, + 0x1a, 0x5c, 0x66, 0x00, 0xd0, 0x47, 0x4a, 0x02, 0x66, 0x60, 0x65, 0x06, 0x10, 0xaa, 0x9c, 0xc6, 0xec, 0x16, 0x98, + 0x47, 0xe0, 0x98, 0x15, 0x4c, 0x16, 0x20, 0x96, 0x04, 0x38, 0x77, 0x41, 0x14, 0xeb, 0x42, 0x8e, 0x21, 0x08, 0x00, + 0xfe, 0x24, 0xa6, 0x14, 0x4c, 0xd2, 0xb1, 0x1b, 0x41, 0x10, 0xc7, 0x67, 0x57, 0xa2, 0x35, 0x39, 0x4b, 0x74, 0x30, + 0x23, 0x09, 0xb0, 0x21, 0x06, 0x86, 0x0f, 0xee, 0xe7, 0xa0, 0xf4, 0xb0, 0x7a, 0x27, 0xe4, 0x82, 0x6f, 0xb9, 0x63, + 0xa1, 0xae, 0xe0, 0xea, 0x09, 0x07, 0xc1, 0x2d, 0xd7, 0x2c, 0xc0, 0xa8, 0x2a, 0xd6, 0x65, 0xc5, 0xd3, 0xf7, 0xb7, + 0x2b, 0x88, 0x05, 0x88, 0x03, 0xfa, 0x56, 0xe6, 0x59, 0x72, 0x1b, 0x3a, 0x7b, 0xae, 0x8d, 0x4a, 0xff, 0xe1, 0xfd, + 0xab, 0x9f, 0x22, 0x10, 0x39, 0xd6, 0x86, 0xd2, 0xdf, 0x72, 0x3c, 0x9b, 0xfc, 0x88, 0x57, 0xfe, 0xc6, 0xbe, 0xe5, + 0xf6, 0xf4, 0xe8, 0xf7, 0xa1, 0x6e, 0x7a, 0xcb, 0x67, 0xb7, 0x7c, 0xe4, 0x8a, 0x43, 0x75, 0x85, 0xfb, 0xfa, 0xe3, + 0xda, 0x37, 0x42, 0xba, 0x7f, 0x9e, 0x29, 0x6f, 0xcc, 0x8f, 0x76, 0x30, 0x0c, 0x82, 0xa9, 0x16, 0x4a, 0x42, 0x14, + 0x12, 0xa6, 0x04, 0x0c, 0xd1, 0x9e, 0x5e, 0x56, 0x53, 0xe4, 0xdc, 0xd4, 0xc8, 0xc2, 0xfb, 0x01, 0xd3, 0x42, 0x87, + 0x46, 0x0e, 0xe5, 0x07, 0x87, 0x13, 0xc6, 0x2c, 0xfc, 0x56, 0x09, 0xd3, 0xaf, 0x16, 0x95, 0x73, 0x10, 0xdd, 0x03, + 0x63, 0x5c, 0xc1, 0x0b, 0xe8, 0x0a, 0xbb, 0x5e, 0xab, 0x28, 0x21, 0x08, 0xa6, 0x87, 0x1c, 0xa0, 0x87, 0x5d, 0xd0, + 0xb2, 0xb2, 0x54, 0xb7, 0x2a, 0x67, 0xa9, 0xa2, 0x2e, 0x43, 0x59, 0x19, 0x2b, 0x0c, 0xfc, 0x92, 0x7d, 0x28, 0xd0, + 0xb3, 0x7c, 0x2a, 0xba, 0xe0, 0x85, 0x50, 0x82, 0xe5, 0xba, 0xde, 0x89, 0x40, 0xd4, 0xf9, 0xa1, 0x77, 0xd5, 0xd7, + 0xb8, 0x7e, 0x3c, 0x7d, 0x25, 0x53, 0xae, 0x4d, 0x28, 0x34, 0x9f, 0x2f, 0x7d, 0xc5, 0x44, 0xc1, 0x3e, 0x42, 0xbf, + 0xda, 0x36, 0xfa, 0xec, 0x66, 0xad, 0x37, 0x83, 0x12, 0x1d, 0xf3, 0x1a, 0x05, 0xd7, 0x4a, 0xa1, 0x60, 0xb4, 0xb7, + 0xf1, 0x67, 0x38, 0x72, 0xab, 0xdb, 0x43, 0xef, 0xb7, 0x2a, 0xbe, 0x78, 0x8d, 0xbe, 0x9d, 0xf6, 0xe7, 0xa8, 0x92, + 0x1f, 0x56, 0x2b, 0xf0, 0xa1, 0x82, 0x48, 0x2b, 0x16, 0xa7, 0x17, 0xea, 0x39, 0x79, 0x7b, 0xfc, 0x1a, 0xfc, 0x28, + 0xf1, 0xf7, 0x2f, 0xdf, 0x07, 0x35, 0x99, 0xc6, 0xb3, 0xc2, 0x7c, 0x68, 0x73, 0x40, 0xa8, 0x16, 0x97, 0x66, 0xdf, + 0xcf, 0xe2, 0x26, 0xfb, 0xae, 0xd9, 0x7a, 0x5a, 0x34, 0x91, 0xa4, 0x0c, 0xb7, 0x0f, 0x06, 0x04, 0xfa, 0x00, 0x51, + 0x9c, 0x7d, 0x41, 0x63, 0x48, 0xf3, 0x99, 0x7d, 0x3f, 0x42, 0xe0, 0xcb, 0x9d, 0x90, 0x6a, 0x5c, 0x61, 0xd1, 0xe8, + 0x21, 0x9f, 0xf1, 0x48, 0x19, 0x16, 0xbd, 0xc3, 0x04, 0xe2, 0x0c, 0xa7, 0xd5, 0x7b, 0xc4, 0x80, 0xc6, 0xbb, 0x81, + 0x96, 0x3d, 0x44, 0x19, 0x75, 0xd9, 0x1b, 0x16, 0xdf, 0x27, 0xeb, 0x30, 0xb3, 0x96, 0x97, 0x43, 0xf8, 0x1b, 0x68, + 0x03, 0x70, 0xca, 0x91, 0xe5, 0xab, 0xcc, 0x46, 0x57, 0x4b, 0x4c, 0x6f, 0x22, 0x88, 0x4d, 0xa4, 0xd3, 0x61, 0xed, + 0xea, 0x54, 0xbd, 0xab, 0x9d, 0xcf, 0x44, 0xaf, 0x02, 0xad, 0x5c, 0xdb, 0x1e, 0x0f, 0xe1, 0x3f, 0xb5, 0xb4, 0xc2, + 0x46, 0xd8, 0x73, 0xf1, 0x85, 0xe7, 0xd8, 0x9c, 0x80, 0x06, 0x97, 0x32, 0x05, 0xe0, 0x2c, 0xad, 0x46, 0xa3, 0x46, + 0xd8, 0x67, 0xe5, 0x7c, 0x0e, 0x5b, 0x0b, 0xf1, 0xb4, 0x00, 0x1c, 0xb8, 0x89, 0xc9, 0xc9, 0xbb, 0x31, 0x39, 0xa7, + 0x1f, 0x15, 0xdc, 0x77, 0x70, 0x5a, 0x2e, 0xe3, 0x54, 0x5e, 0x03, 0x36, 0x65, 0xe0, 0xa7, 0x62, 0xa9, 0x5e, 0x42, + 0xb2, 0xe4, 0xc9, 0x47, 0xb4, 0xda, 0x48, 0x03, 0xe0, 0x2a, 0xa7, 0xc6, 0x72, 0x4f, 0x81, 0xa6, 0xba, 0x52, 0x54, + 0x42, 0x5c, 0x55, 0x71, 0xb2, 0x3c, 0xc1, 0xd4, 0x70, 0x03, 0xbd, 0x88, 0x02, 0xb9, 0xe2, 0x02, 0x48, 0x7a, 0xce, + 0xfe, 0xc8, 0x34, 0xf6, 0xfa, 0x1b, 0x89, 0x02, 0x26, 0x8d, 0xa2, 0x8c, 0x95, 0xb2, 0x97, 0xd2, 0x44, 0xbf, 0x0b, + 0x82, 0xda, 0xbd, 0xfc, 0x1b, 0xea, 0x7e, 0x0a, 0xad, 0x08, 0x1b, 0xe0, 0x85, 0x1a, 0xfc, 0x30, 0xb5, 0x4b, 0xce, + 0x03, 0x32, 0x74, 0xde, 0x67, 0xb5, 0xdd, 0xea, 0x4f, 0x97, 0x80, 0xf5, 0x9a, 0x1a, 0x9f, 0xc2, 0x30, 0x21, 0x26, + 0x56, 0xb2, 0x55, 0x56, 0xda, 0x0d, 0x65, 0xda, 0x49, 0x97, 0xcc, 0x6b, 0xe1, 0x34, 0xef, 0x31, 0xb6, 0x1c, 0xa9, + 0xdc, 0xfd, 0x7e, 0x68, 0x7e, 0xb2, 0x9c, 0xbe, 0xd1, 0x21, 0xac, 0xbd, 0xf1, 0xa0, 0x39, 0xd1, 0xea, 0xaa, 0x8e, + 0x7e, 0x40, 0x07, 0x60, 0xa6, 0x2d, 0x42, 0xa5, 0x0b, 0xbe, 0xed, 0x2b, 0x51, 0x71, 0x49, 0xc2, 0x52, 0x49, 0x60, + 0x67, 0x37, 0x25, 0x3b, 0x9b, 0x80, 0x78, 0x86, 0xbb, 0x9e, 0x16, 0x3b, 0x21, 0x4d, 0x78, 0x8b, 0xbd, 0x04, 0x44, + 0x1d, 0xaa, 0xba, 0x84, 0x6c, 0x8c, 0xa1, 0x8b, 0x7f, 0x51, 0x0a, 0x13, 0xd6, 0x32, 0xa9, 0x4a, 0x4c, 0x50, 0xa8, + 0x72, 0xb7, 0x45, 0x60, 0x89, 0x82, 0x1d, 0xc0, 0xde, 0xbb, 0x51, 0x37, 0xa3, 0xa6, 0xaa, 0x53, 0x2f, 0xc1, 0xc7, + 0x69, 0xd6, 0x55, 0x90, 0x59, 0xd8, 0x55, 0xb1, 0xe6, 0x81, 0x8e, 0xd5, 0xa5, 0x8c, 0x89, 0xbb, 0xb4, 0xc8, 0x10, + 0x1f, 0x19, 0x63, 0x0b, 0x6b, 0x38, 0xd2, 0xf6, 0xb8, 0xe9, 0x09, 0x42, 0x3f, 0x61, 0x43, 0x09, 0xdc, 0x74, 0xb6, + 0xa7, 0xa6, 0x99, 0x0f, 0x88, 0x38, 0x0c, 0x28, 0x90, 0x6c, 0x1c, 0xd2, 0x1c, 0xe9, 0x0b, 0x92, 0x26, 0x0c, 0x94, + 0xad, 0x78, 0x4e, 0x90, 0x15, 0x85, 0x9e, 0xad, 0xab, 0x1a, 0xe2, 0xe7, 0x32, 0xcc, 0xd1, 0x92, 0x53, 0xe1, 0x69, + 0x82, 0x4c, 0xec, 0x8e, 0xb6, 0x99, 0xc9, 0x70, 0x94, 0x2c, 0x30, 0xbf, 0x82, 0x28, 0x71, 0x67, 0x9a, 0x55, 0x39, + 0x18, 0x17, 0xb0, 0x40, 0x2b, 0xdf, 0x83, 0xba, 0xb1, 0x86, 0x36, 0x1a, 0x96, 0xd9, 0xed, 0x4f, 0xb0, 0x5f, 0x6b, + 0xa7, 0x75, 0x99, 0x62, 0x79, 0x99, 0x42, 0xb4, 0x17, 0x32, 0xbf, 0x51, 0x24, 0xba, 0x53, 0x84, 0x21, 0x61, 0x1d, + 0x65, 0x4f, 0xda, 0xd4, 0x00, 0x7a, 0xea, 0x05, 0x80, 0xef, 0x5c, 0xcb, 0xb0, 0x8b, 0x74, 0x7f, 0x55, 0x30, 0x2e, + 0xdd, 0x20, 0x48, 0xd1, 0x9b, 0x14, 0xcc, 0x79, 0x3d, 0x4a, 0xea, 0xcd, 0x69, 0xcb, 0x8c, 0xaa, 0xa3, 0x22, 0xa4, + 0x9c, 0xe0, 0x3f, 0x79, 0x29, 0x35, 0xb1, 0x09, 0x13, 0x3c, 0xf0, 0x61, 0x9e, 0x61, 0x03, 0x6f, 0xb7, 0x0f, 0xd2, + 0x30, 0x69, 0xb3, 0x0d, 0x29, 0x48, 0x2b, 0x4c, 0x9c, 0x10, 0xa8, 0xec, 0x25, 0xee, 0x17, 0x6c, 0x27, 0x4d, 0xc1, + 0x83, 0xb0, 0xd1, 0xc0, 0xc4, 0xad, 0xae, 0x6c, 0x1d, 0x26, 0x34, 0x5c, 0x52, 0xed, 0xec, 0xa4, 0x92, 0xcf, 0xdb, + 0xeb, 0xf2, 0xdc, 0xf6, 0x41, 0xc7, 0x52, 0xeb, 0x1a, 0x1e, 0x68, 0x5e, 0xb3, 0x8b, 0x2b, 0xa6, 0x69, 0xa2, 0xb1, + 0x1e, 0x52, 0x96, 0x1c, 0xeb, 0x7a, 0xba, 0xc2, 0xd5, 0x32, 0xd3, 0x40, 0xf7, 0x12, 0x2f, 0xf4, 0x80, 0x0f, 0x1e, + 0xae, 0x48, 0x74, 0x8e, 0xcd, 0x66, 0xab, 0x9a, 0x4c, 0xf3, 0xbb, 0xb2, 0xe5, 0x26, 0x40, 0x9e, 0xa5, 0xbe, 0xb9, + 0x4f, 0x8e, 0x35, 0x6d, 0xf3, 0x93, 0x00, 0xd7, 0xdc, 0x2b, 0x20, 0xe9, 0x58, 0x82, 0x2e, 0xde, 0xa7, 0x3f, 0x88, + 0xd4, 0x4c, 0x05, 0xbd, 0x73, 0xbe, 0x48, 0xdd, 0xfc, 0x02, 0x6c, 0xa3, 0x36, 0xc6, 0x34, 0x4b, 0xac, 0xc3, 0x44, + 0x59, 0x58, 0x23, 0x0b, 0xb9, 0x04, 0x1f, 0xcc, 0xdd, 0xa6, 0x4e, 0x9f, 0x77, 0x10, 0x61, 0xbf, 0x8b, 0x1e, 0x8f, + 0x30, 0x56, 0xac, 0x41, 0x62, 0x58, 0x85, 0x35, 0x6d, 0x2e, 0x87, 0x28, 0xa7, 0x66, 0xc9, 0x44, 0x4b, 0xea, 0x53, + 0x8a, 0x28, 0x05, 0x73, 0xe3, 0x69, 0xd9, 0x30, 0x25, 0x44, 0xc8, 0x0a, 0xe9, 0x80, 0x6a, 0x2d, 0xb4, 0x54, 0x13, + 0x04, 0x3c, 0xf4, 0xb2, 0xd0, 0x98, 0x82, 0xe8, 0x23, 0x32, 0xdc, 0x88, 0x23, 0xa3, 0xbb, 0x63, 0x14, 0x13, 0x08, + 0xdd, 0xed, 0xe5, 0x85, 0xd5, 0xa7, 0x65, 0x5b, 0x1d, 0xc4, 0x35, 0xa6, 0xc9, 0x1d, 0x04, 0x35, 0x46, 0x41, 0x9b, + 0xd3, 0x8d, 0xfe, 0x5e, 0x84, 0xbe, 0x5d, 0x38, 0x76, 0xa3, 0x20, 0x12, 0x22, 0xd2, 0x7a, 0x4d, 0xc5, 0x00, 0xb5, + 0xf3, 0xd8, 0x45, 0xac, 0xd2, 0xdd, 0x42, 0x94, 0x37, 0x2a, 0xeb, 0x93, 0x75, 0x48, 0xb6, 0x5b, 0x2c, 0x0b, 0x7c, + 0xd9, 0x5f, 0xad, 0xef, 0x80, 0x40, 0x7f, 0xba, 0xfe, 0x2c, 0x04, 0xfa, 0xb3, 0xec, 0x4b, 0x20, 0xd0, 0x9f, 0xae, + 0xff, 0xa7, 0x21, 0xd0, 0x5f, 0xad, 0x3d, 0x08, 0x74, 0x35, 0x18, 0xff, 0x2a, 0x58, 0xf0, 0xe6, 0x75, 0x40, 0x9f, + 0x49, 0x16, 0xbc, 0x79, 0xf1, 0xc2, 0x13, 0xa6, 0xff, 0x20, 0x34, 0x92, 0xbf, 0x91, 0x05, 0x23, 0x6e, 0x0b, 0xbc, + 0x42, 0xad, 0x93, 0x0f, 0x54, 0x94, 0x01, 0x10, 0x7d, 0xf9, 0x6b, 0x56, 0x2d, 0xc3, 0x60, 0x3f, 0x20, 0x33, 0x07, + 0x09, 0x3a, 0x9c, 0x34, 0x6e, 0x6f, 0x1f, 0x44, 0x43, 0xa8, 0x63, 0x23, 0x0f, 0xc0, 0x57, 0x9e, 0xc8, 0xde, 0xbf, + 0x21, 0xe2, 0x27, 0x33, 0x0b, 0x3a, 0xba, 0x1f, 0x10, 0xf0, 0x58, 0xca, 0x3c, 0x04, 0xce, 0xb9, 0x1f, 0x12, 0xfa, + 0xed, 0xda, 0xb3, 0x2d, 0xfa, 0x20, 0xc2, 0x0a, 0x7c, 0xee, 0xfe, 0x5e, 0xf3, 0xd3, 0x2c, 0x25, 0x4e, 0x1e, 0xca, + 0x45, 0x22, 0x53, 0xfe, 0xe1, 0xdd, 0x4b, 0x8b, 0x3c, 0x1e, 0x2a, 0xe8, 0x25, 0x82, 0x21, 0x8d, 0x53, 0x7e, 0x95, + 0x25, 0x7c, 0xf6, 0xe7, 0x83, 0x4d, 0x67, 0x46, 0xf5, 0x9a, 0xd4, 0xfb, 0x7f, 0x46, 0x41, 0xa0, 0xc7, 0xe0, 0xcf, + 0x07, 0x9b, 0xac, 0xde, 0x7f, 0xb0, 0xa9, 0x46, 0xa9, 0x04, 0x78, 0x6f, 0xf8, 0x2d, 0xeb, 0x07, 0x9b, 0x12, 0x7e, + 0xf0, 0xfa, 0x4f, 0x0f, 0x98, 0xcd, 0x36, 0xc8, 0xeb, 0x83, 0x55, 0x5e, 0x39, 0x4c, 0xd0, 0x7b, 0x0a, 0x16, 0xa6, + 0x50, 0x87, 0x47, 0xb5, 0xf6, 0xe4, 0x7e, 0x53, 0xdd, 0x75, 0x42, 0xe0, 0x1a, 0xe9, 0x06, 0x0e, 0xa1, 0xb2, 0x04, + 0x3b, 0xea, 0xe8, 0x94, 0x20, 0xa6, 0xe6, 0xfd, 0x40, 0xd9, 0xfa, 0x7a, 0xc1, 0x8a, 0x5d, 0x33, 0x31, 0xbe, 0xd3, + 0x18, 0xd8, 0x70, 0xd1, 0xd5, 0x62, 0xce, 0xfe, 0x34, 0x3d, 0xde, 0xad, 0x42, 0x12, 0xc4, 0xc8, 0xf6, 0xfb, 0xc4, + 0xeb, 0x59, 0xca, 0xab, 0x38, 0xcb, 0x59, 0x9c, 0xe7, 0x7f, 0xa2, 0x2c, 0xe2, 0xfb, 0x2f, 0x02, 0xdd, 0x1f, 0x8d, + 0x46, 0x71, 0x71, 0x81, 0x57, 0x7f, 0x43, 0x6e, 0x11, 0x16, 0x3b, 0xe3, 0xa5, 0x0d, 0xac, 0xb2, 0x8c, 0xcb, 0x53, + 0x1d, 0xd1, 0xa8, 0xb4, 0x04, 0xbb, 0x5c, 0xca, 0xeb, 0x53, 0x88, 0xee, 0x60, 0x29, 0x78, 0x8c, 0x03, 0xa8, 0xee, + 0x4d, 0x26, 0xec, 0xf2, 0x5a, 0xbf, 0x3b, 0x8b, 0x4b, 0xfe, 0x36, 0xae, 0x96, 0x0c, 0xf6, 0x82, 0xa6, 0xea, 0x85, + 0x5c, 0xaf, 0x5c, 0x25, 0xa7, 0x6b, 0xf1, 0x51, 0xc8, 0x6b, 0xa1, 0x68, 0xef, 0x29, 0xbf, 0x82, 0x16, 0xb1, 0x0d, + 0xea, 0xac, 0x04, 0x4f, 0x2a, 0x8f, 0x13, 0x57, 0xb1, 0x00, 0x32, 0x6a, 0xa2, 0x01, 0x74, 0xe4, 0xa0, 0xa1, 0xdd, + 0x6b, 0xda, 0xb1, 0xdc, 0xa8, 0x2c, 0x32, 0xb0, 0x84, 0x7d, 0x0e, 0xa5, 0x03, 0x62, 0x3b, 0x84, 0x0b, 0x81, 0xab, + 0x27, 0x5e, 0x8d, 0x1a, 0x88, 0x3d, 0xb4, 0xf4, 0xdd, 0x85, 0x14, 0xab, 0x65, 0xd0, 0x2e, 0x1b, 0xc3, 0x84, 0xd7, + 0x6b, 0x74, 0x19, 0x06, 0xc5, 0x7f, 0xe1, 0x16, 0x25, 0xe2, 0x22, 0x65, 0xa9, 0x32, 0x3a, 0xeb, 0xa1, 0x2c, 0x0c, + 0x9f, 0x3d, 0x1d, 0xa5, 0x0e, 0x2b, 0xe7, 0x99, 0xe5, 0x6d, 0x94, 0x26, 0x7e, 0x0e, 0x26, 0x61, 0x7e, 0x2d, 0x73, + 0xa9, 0xe3, 0x92, 0x9f, 0x8a, 0xf5, 0x25, 0x2f, 0xb2, 0xe4, 0x74, 0x99, 0x95, 0x95, 0x2c, 0x6e, 0x17, 0x06, 0xee, + 0x42, 0x97, 0xd5, 0x9a, 0xc4, 0x3b, 0xbf, 0x03, 0x9f, 0x77, 0x15, 0xc0, 0x64, 0xf8, 0x64, 0x4c, 0x6a, 0x6d, 0x2d, + 0x0f, 0x0d, 0xa4, 0xf6, 0xb7, 0xda, 0x27, 0xee, 0xd9, 0x76, 0x8d, 0x36, 0xfd, 0x1c, 0xda, 0x35, 0x52, 0xb3, 0x94, + 0x0a, 0xfe, 0xf7, 0x9a, 0x9b, 0x68, 0x07, 0xa1, 0x43, 0xf2, 0x0e, 0x4b, 0x7d, 0x18, 0x69, 0x12, 0xad, 0x90, 0xa0, + 0x14, 0xf5, 0x6d, 0xbd, 0x50, 0x6d, 0x20, 0x44, 0xdd, 0x16, 0xd3, 0xf4, 0x39, 0x82, 0xb6, 0x83, 0x94, 0x04, 0xf7, + 0x96, 0x8d, 0xf9, 0xd5, 0xb5, 0x7c, 0xe6, 0xd0, 0x9d, 0xc5, 0xec, 0x73, 0x19, 0x06, 0x83, 0xe8, 0x73, 0x59, 0xd8, + 0xe4, 0x9e, 0x55, 0xaa, 0xb2, 0x1c, 0x1a, 0xdb, 0xcb, 0x29, 0x9a, 0xb2, 0x84, 0x0f, 0xd6, 0x61, 0x73, 0xed, 0x53, + 0x9c, 0x7d, 0xba, 0xb9, 0xe4, 0xd5, 0x52, 0xa6, 0x51, 0xf0, 0xfd, 0xf3, 0xf7, 0x81, 0x51, 0x5d, 0x17, 0x1a, 0xb4, + 0x48, 0x6b, 0x73, 0x72, 0x79, 0x01, 0xb2, 0xcc, 0x5e, 0x31, 0x92, 0x1f, 0x77, 0xa2, 0x7c, 0xfe, 0xf9, 0xc3, 0xfb, + 0xf7, 0x6f, 0xf7, 0x50, 0xe1, 0xd3, 0xdb, 0x3b, 0x51, 0xe8, 0x01, 0x7b, 0x0f, 0x36, 0x85, 0x56, 0xb1, 0xd7, 0x7f, + 0xda, 0xb3, 0xaa, 0x68, 0x29, 0xc8, 0x0d, 0x28, 0xa0, 0x57, 0x45, 0x6b, 0x58, 0x0b, 0xa7, 0xc5, 0xf6, 0x33, 0x2b, + 0xed, 0x52, 0x80, 0xba, 0x13, 0x55, 0x73, 0xa4, 0xf4, 0xf2, 0x10, 0x69, 0x21, 0xac, 0xee, 0xd8, 0x6a, 0x55, 0xd7, + 0x56, 0x93, 0x45, 0x95, 0x89, 0x8b, 0x53, 0xdc, 0xfd, 0x5f, 0xb4, 0xe5, 0xcc, 0x0c, 0x2b, 0x7a, 0xd1, 0xde, 0x6d, + 0x0d, 0xa8, 0x32, 0x6d, 0x94, 0xab, 0xf7, 0x10, 0x08, 0xcc, 0xca, 0x7a, 0xea, 0x7f, 0x6c, 0x2c, 0x46, 0xfc, 0x34, + 0x05, 0xe4, 0x06, 0x3c, 0x10, 0x3b, 0x8a, 0x47, 0xa6, 0x7d, 0xd7, 0x28, 0x37, 0x39, 0x4c, 0x5a, 0x09, 0xb3, 0xe1, + 0x24, 0x9a, 0x10, 0x1b, 0x5f, 0x42, 0xd3, 0xb0, 0xef, 0x47, 0xcf, 0x5f, 0xbf, 0x7f, 0xf9, 0xfe, 0xf7, 0xd3, 0xa7, + 0xc7, 0xef, 0x9f, 0x7f, 0xff, 0xe6, 0xdd, 0xcb, 0xe7, 0x27, 0x78, 0x42, 0x68, 0xc0, 0xca, 0x70, 0xa3, 0xad, 0xa2, + 0x9b, 0x65, 0x45, 0xa2, 0x26, 0xcd, 0xa6, 0x28, 0xc4, 0x28, 0xcc, 0x6c, 0x8b, 0xfc, 0xf0, 0xfa, 0xd9, 0xf3, 0x17, + 0x2f, 0x5f, 0x3f, 0x7f, 0xd6, 0xfe, 0x7a, 0x38, 0xa9, 0x49, 0xed, 0x66, 0x4e, 0x47, 0x48, 0xe1, 0x76, 0xbc, 0x3a, + 0xe8, 0x13, 0x6a, 0xe5, 0x7d, 0xfa, 0x94, 0xc1, 0x8a, 0x64, 0x4a, 0x4e, 0x8f, 0xbf, 0x3d, 0xfc, 0x5f, 0xb5, 0xf1, + 0xb6, 0x5b, 0xe0, 0x21, 0x90, 0x8c, 0x29, 0x59, 0x3f, 0x8c, 0x6a, 0x46, 0xd5, 0xcb, 0x48, 0x50, 0x5b, 0x1a, 0xd8, + 0x40, 0xa7, 0x54, 0x85, 0x54, 0x38, 0x4d, 0xe2, 0x8a, 0x5f, 0xc8, 0xe2, 0x36, 0xca, 0x46, 0xad, 0x14, 0xda, 0x58, + 0x00, 0x51, 0x08, 0x82, 0xe5, 0x46, 0x12, 0xe9, 0x29, 0x02, 0xe0, 0x0d, 0x81, 0x1b, 0xd5, 0xb9, 0x8b, 0x16, 0xd0, + 0x2e, 0x98, 0x2c, 0xb6, 0xdb, 0x8e, 0x41, 0xeb, 0xa4, 0x7d, 0xd1, 0x3c, 0x53, 0x44, 0x71, 0x01, 0x8c, 0x39, 0x1c, + 0x6f, 0xea, 0xec, 0x62, 0xe6, 0xb8, 0x3b, 0xd6, 0x51, 0x3f, 0xc1, 0x1a, 0xd1, 0xbd, 0x36, 0x81, 0x65, 0x9a, 0xe7, + 0xe1, 0xb8, 0x45, 0x71, 0x0d, 0xc6, 0x6f, 0x2b, 0x55, 0x2d, 0x33, 0x8d, 0xad, 0x08, 0x33, 0x05, 0xe1, 0xb8, 0x8c, + 0xe8, 0x36, 0xcc, 0xc1, 0x42, 0xa6, 0x31, 0xbf, 0x66, 0x1c, 0xf2, 0x48, 0x1a, 0x98, 0x3c, 0x30, 0x19, 0xbc, 0x23, + 0xd7, 0x32, 0x2a, 0x1a, 0x80, 0x97, 0xb2, 0x39, 0xa8, 0x87, 0xff, 0xa7, 0xb9, 0xa7, 0xdd, 0x6e, 0xdb, 0x46, 0xf6, + 0x7f, 0x9f, 0x82, 0x61, 0xb2, 0x29, 0x99, 0x90, 0x34, 0x29, 0x59, 0xb6, 0x22, 0x59, 0x72, 0x9b, 0xaf, 0x6d, 0x5a, + 0xb7, 0xe9, 0x49, 0xdc, 0xec, 0xdd, 0xf5, 0xfa, 0x58, 0x94, 0x04, 0x49, 0xdc, 0x50, 0xa4, 0x0e, 0x49, 0xf9, 0xa3, + 0x0a, 0xf7, 0x59, 0xf6, 0x11, 0xee, 0x33, 0xf4, 0xc9, 0xee, 0x99, 0x19, 0x80, 0x04, 0xbf, 0x24, 0x79, 0x93, 0xb6, + 0xf7, 0xb4, 0x49, 0x44, 0x10, 0x00, 0x81, 0x01, 0x30, 0x33, 0x98, 0xcf, 0xa8, 0xf8, 0x0c, 0xdb, 0xb8, 0x54, 0x05, + 0x45, 0xb6, 0xc5, 0x4a, 0x20, 0x5a, 0x98, 0x9c, 0xd2, 0xe7, 0xad, 0x24, 0x3c, 0x0b, 0x6f, 0x84, 0x78, 0xf8, 0x24, + 0xaa, 0x29, 0xc4, 0xb3, 0xd1, 0x73, 0x4f, 0x26, 0xf4, 0xc3, 0x49, 0x1b, 0x88, 0x40, 0x9a, 0x03, 0x38, 0x63, 0x4e, + 0x47, 0x74, 0x65, 0xba, 0x7a, 0xb4, 0x11, 0x1b, 0x2f, 0x1d, 0x79, 0x59, 0xf2, 0xd7, 0x02, 0x63, 0x91, 0x72, 0xd0, + 0xcb, 0xb1, 0x46, 0x6b, 0xaa, 0xf1, 0xfd, 0x31, 0xf0, 0x6a, 0xb9, 0x13, 0x8b, 0x1e, 0x19, 0xe5, 0xc2, 0xac, 0xaf, + 0xc2, 0x6e, 0xd9, 0x44, 0xab, 0x1b, 0x18, 0x89, 0x97, 0xc4, 0x14, 0x30, 0xfc, 0x32, 0x62, 0xfc, 0x9f, 0x2b, 0x18, + 0x1f, 0xad, 0xec, 0x32, 0x84, 0xff, 0xf3, 0xdb, 0xf7, 0xe7, 0xa0, 0xbd, 0x72, 0x51, 0xdd, 0xbc, 0x51, 0xb9, 0xa5, + 0x8a, 0x09, 0xfa, 0x20, 0xb5, 0xa7, 0xba, 0x2b, 0xa0, 0xc7, 0x78, 0x2f, 0x38, 0xb8, 0x35, 0x6f, 0x6e, 0x6e, 0x4c, + 0xb0, 0x5b, 0x35, 0xd7, 0x91, 0x4f, 0x3c, 0xe0, 0x54, 0x4d, 0x05, 0x22, 0x67, 0x25, 0x44, 0x0e, 0x41, 0x6f, 0x79, + 0xd6, 0x94, 0xf7, 0x8b, 0xf0, 0xe6, 0x5b, 0xdf, 0x97, 0x85, 0x33, 0x82, 0x55, 0xe3, 0xf2, 0x8a, 0x02, 0x62, 0xd0, + 0x40, 0xc7, 0x64, 0x79, 0xf1, 0x15, 0xb7, 0x0a, 0x98, 0x5e, 0x8d, 0xef, 0xae, 0xb8, 0xe6, 0x21, 0x8b, 0x3a, 0xfc, + 0x62, 0x74, 0x32, 0xf5, 0xae, 0x15, 0xe4, 0x27, 0x07, 0x2a, 0xb8, 0x6c, 0xf9, 0x6c, 0xbc, 0x4e, 0x92, 0x30, 0x30, + 0xa3, 0xf0, 0x46, 0x1d, 0x9e, 0xd0, 0x83, 0xa8, 0xe0, 0xd2, 0xa3, 0xaa, 0x7c, 0x33, 0xf1, 0xbd, 0xc9, 0xc7, 0x81, + 0xfa, 0x68, 0xe3, 0x0d, 0x86, 0x25, 0xae, 0xd1, 0x4e, 0xd5, 0x21, 0x8c, 0x55, 0xf9, 0xd6, 0xf7, 0x4f, 0x0e, 0xa8, + 0xc5, 0xf0, 0xe4, 0x60, 0xea, 0x5d, 0x0f, 0xa5, 0x04, 0x30, 0x5c, 0x3b, 0x3a, 0xe0, 0x81, 0x36, 0x33, 0x7b, 0xb2, + 0x18, 0x23, 0x37, 0x4c, 0x98, 0x96, 0x5f, 0x71, 0x21, 0xa2, 0x0c, 0x8d, 0x57, 0x9b, 0xa0, 0xd0, 0xdc, 0x87, 0x0b, + 0xdd, 0xa7, 0x4f, 0x5a, 0x66, 0x6d, 0xba, 0x90, 0x42, 0xb1, 0xa1, 0x32, 0x0f, 0xab, 0x18, 0x18, 0x4f, 0x46, 0xd7, + 0x44, 0xc0, 0x38, 0x5f, 0x37, 0x26, 0xa9, 0x81, 0x79, 0x74, 0xdc, 0x15, 0xe8, 0x15, 0xf9, 0x4f, 0xe9, 0xde, 0x3b, + 0x81, 0xdc, 0xd9, 0x12, 0xe2, 0xd6, 0x25, 0xcd, 0x0a, 0x9d, 0x42, 0x1e, 0x0d, 0x10, 0x54, 0x22, 0xf8, 0x1d, 0xd2, + 0x76, 0x68, 0xbe, 0x0e, 0xb9, 0xdb, 0xb2, 0x10, 0x3c, 0x6e, 0x2a, 0xb2, 0xa5, 0x09, 0xb8, 0x9c, 0x16, 0x56, 0xa8, + 0x57, 0x5e, 0x2f, 0x11, 0x1b, 0xf2, 0x41, 0xdc, 0xb4, 0x64, 0xa0, 0xa9, 0xd3, 0x12, 0xa3, 0x44, 0x67, 0xc1, 0x77, + 0x4f, 0x52, 0x0f, 0x31, 0x43, 0xbb, 0x88, 0x8d, 0xf0, 0x32, 0xa7, 0x4d, 0x31, 0x21, 0xca, 0x5e, 0x98, 0xe6, 0x61, + 0x9a, 0x69, 0xd5, 0x87, 0x8f, 0x36, 0x01, 0x12, 0xb3, 0x78, 0x30, 0x2c, 0xee, 0x83, 0xc4, 0x1d, 0x9b, 0xb4, 0x99, + 0x55, 0xe5, 0x9b, 0xe9, 0xd8, 0xcf, 0x16, 0x9b, 0x0e, 0xc1, 0xc2, 0x0d, 0xa6, 0x3e, 0x3b, 0x77, 0xc7, 0xdf, 0x61, + 0x9d, 0x97, 0x63, 0xff, 0x05, 0x54, 0x48, 0xd5, 0xe1, 0xa3, 0x0d, 0x91, 0xeb, 0x3a, 0x84, 0x9d, 0xd2, 0x16, 0x28, + 0x7f, 0x87, 0x27, 0x56, 0x62, 0x11, 0xb5, 0xc6, 0xc1, 0x12, 0x89, 0x25, 0x8c, 0x5a, 0x1c, 0x19, 0x4f, 0xec, 0x03, + 0x7b, 0x53, 0xe1, 0xa7, 0x16, 0xc6, 0x15, 0x8a, 0x13, 0x2c, 0xef, 0x4c, 0x79, 0xb0, 0x44, 0x4a, 0xdf, 0x85, 0x37, + 0x62, 0xa4, 0x1c, 0x00, 0x14, 0x88, 0xf2, 0xf4, 0xc5, 0xe8, 0x44, 0x56, 0xfe, 0xa0, 0x84, 0x9c, 0xfa, 0x85, 0x5f, + 0xa9, 0xaa, 0xe4, 0x69, 0x9e, 0x56, 0xb7, 0xea, 0xf0, 0xe4, 0x40, 0xae, 0x3d, 0x1c, 0xf5, 0xce, 0xa4, 0xc9, 0x61, + 0xaf, 0xe2, 0x76, 0x7c, 0x91, 0x3f, 0xa4, 0x97, 0x0a, 0xdc, 0x85, 0x53, 0x28, 0x01, 0x18, 0x15, 0x9b, 0x54, 0xc8, + 0x0f, 0x24, 0x46, 0xcc, 0x09, 0x14, 0xed, 0x1e, 0x81, 0x1f, 0x43, 0xbd, 0x97, 0x2d, 0x21, 0xd9, 0x5f, 0x8a, 0xde, + 0x46, 0xfc, 0xdf, 0x1c, 0x24, 0x28, 0xcf, 0x66, 0x41, 0x1c, 0x46, 0x2a, 0x4c, 0xb3, 0x9c, 0x1d, 0x49, 0x91, 0xb2, + 0xb2, 0xe1, 0x84, 0x6b, 0xc9, 0x2a, 0x00, 0xec, 0xa0, 0xdc, 0x54, 0x9a, 0xf7, 0x48, 0xcf, 0x7f, 0x28, 0x7c, 0x32, + 0x25, 0xa4, 0x95, 0x0d, 0xb0, 0x39, 0xeb, 0xd4, 0xc5, 0x5b, 0xcf, 0xf8, 0x5b, 0x68, 0x2c, 0x5d, 0x63, 0xec, 0x1a, + 0xef, 0x83, 0xcb, 0xb4, 0x76, 0xf1, 0xb2, 0x8c, 0x71, 0x06, 0xeb, 0x6b, 0x10, 0x67, 0xa9, 0x78, 0xaf, 0xf0, 0x2c, + 0x6e, 0x19, 0x72, 0xee, 0x46, 0x73, 0x26, 0x12, 0xb5, 0x89, 0xb7, 0x42, 0x42, 0xa0, 0x4b, 0x60, 0x81, 0x20, 0x64, + 0x0f, 0xb8, 0x01, 0x9d, 0x67, 0x4d, 0x92, 0xc8, 0xff, 0x81, 0xdd, 0xc1, 0x75, 0x32, 0x4e, 0xc2, 0x15, 0x48, 0xa6, + 0xdc, 0x39, 0xd7, 0x34, 0x18, 0xc0, 0xd4, 0xec, 0xf3, 0xb9, 0x4f, 0x9f, 0x98, 0x94, 0x3b, 0x2c, 0x09, 0xe7, 0x73, + 0x9f, 0x69, 0x52, 0x8e, 0xb1, 0xec, 0x33, 0xa7, 0x0f, 0x6c, 0x11, 0x9f, 0x5a, 0x4f, 0x9b, 0x0e, 0x56, 0xce, 0x01, + 0x0a, 0x9d, 0x3e, 0x20, 0x2e, 0x32, 0xa1, 0x42, 0x26, 0x5c, 0x13, 0xe7, 0x22, 0x3f, 0xb8, 0xe6, 0x34, 0x5c, 0x8f, + 0x7d, 0x66, 0xe2, 0x69, 0x80, 0x4f, 0x6e, 0xc6, 0xeb, 0xf1, 0xd8, 0xa7, 0xa4, 0x60, 0x10, 0x65, 0x2d, 0x8c, 0x51, + 0xfa, 0x99, 0xea, 0x7d, 0xe4, 0xd4, 0x92, 0xf2, 0xf0, 0xc1, 0x32, 0x12, 0x6e, 0x0b, 0xf4, 0x81, 0x04, 0x24, 0x9d, + 0xd5, 0x33, 0x3d, 0x50, 0xe1, 0x96, 0xc2, 0x62, 0xb5, 0x5f, 0xc3, 0xd2, 0x0d, 0x2e, 0xd4, 0xf7, 0x08, 0x61, 0xc5, + 0x0d, 0xa6, 0xca, 0x0b, 0xda, 0xbb, 0xaa, 0xa1, 0x92, 0x81, 0x17, 0xcf, 0x21, 0xa7, 0x1a, 0xea, 0x4b, 0xcf, 0x9d, + 0x07, 0x61, 0x9c, 0x78, 0x13, 0xf5, 0xb2, 0xff, 0xd2, 0xd3, 0x2e, 0x96, 0x89, 0xa6, 0x5f, 0x1a, 0x7f, 0x95, 0xb3, + 0x7d, 0x09, 0x4c, 0x89, 0xc9, 0xbe, 0x1a, 0xea, 0xc8, 0xa7, 0x67, 0x5b, 0x3d, 0x81, 0x91, 0xb1, 0xce, 0x5f, 0x07, + 0x50, 0xab, 0x94, 0x37, 0x0c, 0x13, 0x42, 0x42, 0xde, 0xb0, 0xbf, 0xea, 0x7d, 0x12, 0xb5, 0x7c, 0xbb, 0xde, 0x20, + 0xd3, 0x90, 0xe4, 0xc4, 0x17, 0x43, 0xdd, 0x0b, 0xff, 0x50, 0x7a, 0x7e, 0x20, 0xfb, 0x36, 0x14, 0xc8, 0xf8, 0xe8, + 0xdb, 0x22, 0x07, 0xf2, 0x68, 0x93, 0xa4, 0x60, 0x58, 0x18, 0x84, 0x89, 0x02, 0xf1, 0xdb, 0xe0, 0x83, 0xa3, 0xb2, + 0x2d, 0x34, 0xef, 0x55, 0xd3, 0x53, 0x8e, 0x05, 0x9e, 0x23, 0x2d, 0x45, 0xf9, 0x24, 0x84, 0x9b, 0x80, 0x50, 0xa4, + 0x85, 0x68, 0x4d, 0xdc, 0x03, 0x0f, 0x96, 0xaf, 0xc0, 0xbf, 0x49, 0x78, 0xbf, 0x48, 0xcf, 0x1f, 0x6d, 0xe2, 0x53, + 0x41, 0xd4, 0xdf, 0xc4, 0xb8, 0x96, 0xc0, 0xae, 0x70, 0x2a, 0x9f, 0xaa, 0xca, 0xa9, 0xa0, 0x44, 0x58, 0xb7, 0x80, + 0x5e, 0x35, 0xc1, 0xee, 0x46, 0x22, 0x32, 0x3e, 0x4f, 0x3f, 0x2e, 0x18, 0xb0, 0xd2, 0xd1, 0x83, 0x90, 0x4c, 0x19, + 0x6f, 0x95, 0x80, 0x5d, 0x35, 0x12, 0x0c, 0xc0, 0x5c, 0x9c, 0x47, 0x18, 0xa5, 0x57, 0xc0, 0x48, 0x42, 0x9c, 0x32, + 0x31, 0x47, 0x23, 0x94, 0x53, 0xc5, 0x79, 0xc1, 0x6a, 0x9d, 0x60, 0xfc, 0x79, 0x18, 0x00, 0x4b, 0x55, 0x05, 0x2f, + 0x89, 0x80, 0xeb, 0xf3, 0xcb, 0x4f, 0xaa, 0x2a, 0xde, 0xb4, 0x5a, 0xc6, 0xe5, 0x31, 0x80, 0xe3, 0x70, 0x1a, 0xa8, + 0xbd, 0x81, 0xc7, 0x88, 0x4f, 0x63, 0x62, 0xe4, 0xc9, 0x5b, 0xb4, 0x09, 0x5a, 0x39, 0xd4, 0x20, 0x90, 0x09, 0xf5, + 0xd3, 0xd7, 0xfc, 0xda, 0xc9, 0x42, 0x4c, 0xea, 0xc2, 0x34, 0x47, 0x20, 0x89, 0x3c, 0x05, 0xd8, 0x0d, 0x1e, 0x6d, + 0xdc, 0xcc, 0x80, 0x4e, 0x3d, 0x57, 0xc9, 0x7a, 0x6e, 0x84, 0x60, 0x18, 0xa5, 0x57, 0xb9, 0x3b, 0x6b, 0x3e, 0x5f, + 0xd8, 0x92, 0x54, 0xae, 0xa0, 0x3d, 0xdb, 0x80, 0x5b, 0xad, 0xad, 0x22, 0x6f, 0xe9, 0x46, 0x77, 0x64, 0xe4, 0x66, + 0xc8, 0x96, 0x70, 0xba, 0xaa, 0x10, 0x3d, 0x20, 0x00, 0x10, 0x69, 0x50, 0x95, 0x6f, 0xb2, 0x32, 0xc6, 0x67, 0x9b, + 0x59, 0xfa, 0xc0, 0xb7, 0xae, 0xd4, 0xa7, 0xcc, 0x22, 0x29, 0x23, 0x35, 0xe9, 0x6b, 0x71, 0xc3, 0xf4, 0xe2, 0xe2, + 0xf4, 0x82, 0xe2, 0x46, 0xc3, 0xc9, 0x10, 0xa5, 0xa0, 0x71, 0xe3, 0xcc, 0x30, 0xd5, 0x65, 0xfd, 0x8a, 0xd2, 0xbb, + 0x3f, 0x74, 0x39, 0x18, 0x2c, 0x47, 0x00, 0xcb, 0x51, 0x23, 0x80, 0x75, 0xc5, 0x8a, 0x00, 0x2f, 0x02, 0x5c, 0x48, + 0x84, 0x1c, 0x08, 0x65, 0xc1, 0x54, 0xb2, 0x2d, 0x14, 0xc1, 0xd1, 0xa0, 0xb1, 0xd3, 0xd1, 0x88, 0x06, 0x83, 0x10, + 0x5b, 0x45, 0xe9, 0xc9, 0x01, 0xd5, 0x26, 0xa2, 0x48, 0x95, 0x00, 0x0c, 0x11, 0xcc, 0x30, 0x87, 0x02, 0xa4, 0x01, + 0x1f, 0x38, 0xf9, 0x45, 0xc7, 0x5a, 0xa2, 0xf2, 0xd9, 0x39, 0x2d, 0x32, 0x3c, 0xd8, 0x4a, 0x1d, 0x9e, 0x60, 0x62, + 0x4f, 0x20, 0xeb, 0x10, 0xfa, 0xea, 0xe4, 0x80, 0x1e, 0x95, 0xd2, 0x89, 0xc8, 0x3b, 0x11, 0x52, 0xc7, 0x1e, 0xef, + 0xe0, 0x5e, 0x47, 0x25, 0x4e, 0xd8, 0x0a, 0x4a, 0xdd, 0x54, 0x55, 0x96, 0x9c, 0xc1, 0xe2, 0x31, 0xf6, 0x20, 0x00, + 0x8f, 0x0d, 0x8e, 0x0f, 0xaa, 0xb2, 0x74, 0x6f, 0x71, 0xe6, 0xe2, 0x8d, 0x7b, 0xab, 0x39, 0xfc, 0x55, 0x7e, 0xd6, + 0xe2, 0xe2, 0x59, 0x9b, 0xf0, 0xc5, 0x05, 0xef, 0x3a, 0xc1, 0x58, 0x6b, 0x0b, 0xb4, 0x5a, 0xaa, 0x59, 0xdc, 0x85, + 0x58, 0xdc, 0x69, 0xc3, 0xe2, 0x4e, 0xb7, 0x2c, 0xae, 0xcf, 0x17, 0x52, 0xc9, 0x40, 0x17, 0xa1, 0xc7, 0x74, 0x06, + 0x3c, 0xce, 0x8f, 0xf4, 0xf8, 0x39, 0x43, 0x38, 0x99, 0xb1, 0x0f, 0x16, 0xc3, 0x0d, 0xb0, 0xaa, 0x83, 0x8b, 0x04, + 0x88, 0xea, 0xc4, 0xb3, 0x53, 0x37, 0x91, 0x24, 0x03, 0x9a, 0x5f, 0x9e, 0x2f, 0xec, 0x52, 0x6c, 0x68, 0x68, 0x8b, + 0x86, 0x99, 0x2e, 0xb6, 0xcc, 0x74, 0x52, 0x38, 0xba, 0x7c, 0xda, 0x74, 0x08, 0xe5, 0x49, 0xc1, 0x1e, 0x04, 0x2f, + 0x0a, 0xdc, 0x32, 0xc5, 0x7d, 0xd8, 0x8c, 0x63, 0xa5, 0x1d, 0xb5, 0x72, 0xe3, 0xf8, 0x26, 0x8c, 0xc0, 0x0c, 0x01, + 0xba, 0xb9, 0xdf, 0x96, 0x5a, 0x7a, 0x01, 0x8f, 0x70, 0xd6, 0xb8, 0x99, 0xf2, 0xf7, 0xf2, 0x96, 0x6a, 0x75, 0x3a, + 0x54, 0x63, 0xe5, 0x26, 0x09, 0x8b, 0x10, 0xe8, 0x2e, 0xa4, 0xc2, 0xf8, 0x7f, 0xb2, 0xcd, 0x6a, 0x70, 0x88, 0x2f, + 0x61, 0x75, 0xc4, 0xd0, 0x2b, 0x60, 0xc1, 0x48, 0xef, 0x18, 0xe8, 0x1b, 0x29, 0x5a, 0x6a, 0x94, 0x01, 0xfe, 0x27, + 0x3c, 0xae, 0x5a, 0x24, 0xf9, 0xf3, 0x3a, 0x47, 0xba, 0xb5, 0x72, 0xa7, 0xef, 0xc1, 0xda, 0x45, 0x6b, 0x19, 0xe0, + 0xb9, 0x22, 0xc7, 0x46, 0x8d, 0x88, 0x27, 0x9c, 0xe4, 0x48, 0x12, 0xb1, 0x24, 0xb7, 0x0b, 0x86, 0x90, 0x02, 0xae, + 0x39, 0xbb, 0xdc, 0xb4, 0xd2, 0x83, 0xb9, 0xa7, 0x57, 0xb0, 0x26, 0xa0, 0x36, 0x7f, 0x30, 0xcc, 0x84, 0x6e, 0xbe, + 0xe1, 0x1c, 0xe9, 0xa0, 0x0e, 0xbd, 0x80, 0xa4, 0xe7, 0xb6, 0xb8, 0x4c, 0x8f, 0x22, 0xa0, 0x5a, 0xa0, 0x3c, 0x7c, + 0x3c, 0xc7, 0x5f, 0xce, 0x65, 0xfa, 0x78, 0x8c, 0xbf, 0x5a, 0x97, 0x99, 0xaa, 0xaa, 0x24, 0x45, 0x90, 0xe6, 0xac, + 0x0e, 0x0b, 0xfb, 0x89, 0x8c, 0xb2, 0xef, 0xb1, 0x6d, 0xf8, 0x02, 0x3f, 0x7c, 0xb4, 0x89, 0x21, 0x0c, 0x81, 0x3c, + 0x87, 0xc0, 0x8a, 0xf4, 0xb4, 0xb6, 0x7c, 0xde, 0x50, 0x3e, 0xd6, 0xff, 0x60, 0xc2, 0x8f, 0xbb, 0x24, 0xcc, 0x69, + 0x4a, 0x51, 0x06, 0x72, 0x35, 0xf6, 0x02, 0x37, 0xba, 0xbb, 0xa2, 0x5b, 0x88, 0x26, 0x09, 0x79, 0x1f, 0xe4, 0xc2, + 0x81, 0xbb, 0xa2, 0x0d, 0x48, 0x22, 0x29, 0xa8, 0xee, 0x38, 0xa1, 0x1f, 0xfc, 0x10, 0x49, 0xfc, 0x5d, 0xe1, 0x1a, + 0xcb, 0x17, 0xa4, 0xf0, 0xa1, 0xab, 0x47, 0x1b, 0x8d, 0x55, 0xbb, 0x29, 0xcd, 0xb6, 0xc4, 0x40, 0xc2, 0xf2, 0xe0, + 0x95, 0x78, 0x39, 0xf5, 0x7a, 0x68, 0xe4, 0x31, 0x0e, 0x6f, 0xcd, 0x47, 0x9b, 0xe4, 0x54, 0x5d, 0xba, 0xd1, 0x47, + 0x36, 0x35, 0x27, 0x5e, 0x34, 0xf1, 0x81, 0x79, 0x1c, 0xfb, 0x6e, 0xf0, 0x91, 0x3f, 0x9a, 0xe1, 0x3a, 0x41, 0xb3, + 0xad, 0x9d, 0x37, 0x68, 0x01, 0x13, 0x12, 0x24, 0x22, 0x57, 0x5b, 0x03, 0x05, 0xe5, 0xc5, 0x48, 0x5c, 0xeb, 0x73, + 0x46, 0x31, 0xaf, 0x65, 0x80, 0xd7, 0x01, 0x58, 0x92, 0x41, 0x18, 0x07, 0x43, 0xc5, 0xf5, 0x52, 0x0d, 0x79, 0xaa, + 0xa4, 0x47, 0xcb, 0xf2, 0x10, 0x5f, 0x61, 0x0f, 0xff, 0xfd, 0xe7, 0xa0, 0xe4, 0x3e, 0x9f, 0xcb, 0x7a, 0xf9, 0xbc, + 0x19, 0x42, 0xa9, 0x49, 0xee, 0x83, 0xf7, 0xf8, 0x38, 0x67, 0x30, 0x9b, 0x3f, 0x2d, 0x37, 0x76, 0xe3, 0x78, 0xbd, + 0x64, 0x53, 0x52, 0x86, 0x9d, 0xe6, 0x83, 0x2a, 0xde, 0x43, 0xe4, 0x81, 0xfd, 0x73, 0xdd, 0x3a, 0x3e, 0x7c, 0x01, + 0x66, 0x7c, 0xc0, 0x50, 0x86, 0xb3, 0x99, 0x9a, 0x8b, 0x02, 0x76, 0x34, 0x73, 0x0e, 0xff, 0xb9, 0x7e, 0xfd, 0xca, + 0x7e, 0x9d, 0x35, 0x0e, 0x80, 0x31, 0x16, 0x36, 0x49, 0x9c, 0x2f, 0x96, 0xc6, 0x2b, 0x66, 0x34, 0x73, 0x83, 0xe6, + 0xe9, 0x5c, 0x14, 0xb6, 0xf8, 0x8a, 0xb1, 0x29, 0x30, 0xdc, 0x46, 0xa5, 0xf4, 0xca, 0x67, 0xd7, 0x2c, 0xb3, 0x77, + 0xaa, 0x7e, 0xac, 0xa6, 0x05, 0x06, 0x64, 0xe5, 0xba, 0x47, 0xce, 0xd5, 0x49, 0x53, 0x1a, 0xe1, 0x1c, 0xf8, 0xcc, + 0xe5, 0x23, 0x56, 0x3a, 0x52, 0x23, 0x43, 0x95, 0x06, 0xd0, 0x38, 0xb2, 0xd3, 0x86, 0xf2, 0x1e, 0x20, 0xea, 0x86, + 0xb1, 0x19, 0x8e, 0xde, 0x83, 0x04, 0x16, 0x1c, 0x4e, 0x3e, 0x9c, 0x3c, 0x2d, 0x97, 0x9a, 0x34, 0x41, 0xac, 0x4e, + 0xd4, 0xa6, 0x92, 0x90, 0x46, 0xb8, 0x00, 0xa0, 0x2f, 0x8c, 0x10, 0x57, 0xd5, 0xae, 0x8d, 0x52, 0x9c, 0xf9, 0x18, + 0xd3, 0xbb, 0x07, 0x2c, 0x8e, 0x1b, 0x01, 0x96, 0x2d, 0xba, 0xa1, 0xe6, 0xb5, 0x8b, 0xf0, 0xc8, 0xcb, 0x0d, 0xdb, + 0x00, 0x96, 0x00, 0x27, 0x58, 0xfe, 0x16, 0x92, 0x97, 0xab, 0x25, 0x37, 0xe2, 0x8c, 0xe6, 0x63, 0x95, 0x1b, 0xd8, + 0x35, 0xbd, 0xbf, 0x51, 0xf9, 0xa0, 0x0a, 0x64, 0xba, 0x76, 0x68, 0x5a, 0x01, 0xf5, 0x56, 0xa4, 0x4a, 0xd8, 0x81, + 0x18, 0x53, 0x09, 0xbf, 0xb2, 0xd9, 0x8c, 0x4d, 0x92, 0x58, 0x17, 0x32, 0xa6, 0x2c, 0xa4, 0x3a, 0x28, 0xed, 0x1e, + 0x0c, 0xd4, 0x9f, 0x20, 0xb0, 0x8c, 0x88, 0x3c, 0xc8, 0x07, 0x24, 0xee, 0x4c, 0xf5, 0x60, 0xa2, 0x1e, 0x8b, 0x20, + 0xe2, 0x5f, 0x01, 0x29, 0x74, 0x4d, 0x39, 0x0e, 0x8d, 0xd3, 0x9f, 0x7c, 0x5f, 0x84, 0x99, 0xa9, 0xe7, 0x76, 0x54, + 0xb4, 0xed, 0xf8, 0x6e, 0x9c, 0xd7, 0x1d, 0xc7, 0x4e, 0x55, 0x03, 0x1c, 0x9a, 0x3f, 0x96, 0xb6, 0x31, 0x11, 0xa8, + 0x81, 0x7a, 0xf6, 0xf6, 0xc5, 0x0f, 0xaf, 0x5e, 0xee, 0x8b, 0x11, 0xb0, 0xcb, 0x36, 0x74, 0xb9, 0x0e, 0xb6, 0x74, + 0xfa, 0xcb, 0x4f, 0xf7, 0xeb, 0xb6, 0xe5, 0x3c, 0x73, 0x54, 0x83, 0x6c, 0xd0, 0x25, 0xbc, 0x38, 0x09, 0xaf, 0x59, + 0xf4, 0xd9, 0x60, 0x90, 0x3b, 0xaf, 0x1f, 0xee, 0xdb, 0x9f, 0x5f, 0xfd, 0xb4, 0xf7, 0x50, 0x8f, 0x1c, 0x1b, 0x70, + 0x7b, 0x12, 0xae, 0xee, 0x31, 0xbb, 0xb6, 0x6a, 0xa8, 0x13, 0x3f, 0x8c, 0x59, 0xc3, 0x08, 0x5e, 0x9c, 0xbd, 0x7d, + 0x8f, 0xe0, 0xca, 0x59, 0x10, 0xea, 0xea, 0xf3, 0x26, 0xff, 0xf3, 0xbb, 0x57, 0xef, 0xdf, 0xab, 0x06, 0xa6, 0xe4, + 0x8e, 0xe5, 0xde, 0xf9, 0x26, 0xde, 0x41, 0x71, 0x6a, 0xf7, 0x3a, 0x51, 0x35, 0xba, 0x48, 0x17, 0x67, 0x43, 0x65, + 0x95, 0x6d, 0xce, 0xa9, 0x1d, 0xff, 0x32, 0xdd, 0x7e, 0xf7, 0x9a, 0x57, 0x0d, 0x3e, 0xda, 0x4e, 0x52, 0x0b, 0x25, + 0x4b, 0x2f, 0xb8, 0xaa, 0x29, 0x75, 0x6f, 0x6b, 0x4a, 0xe1, 0xfa, 0x58, 0xc1, 0x8f, 0xeb, 0x70, 0x29, 0xb1, 0x23, + 0xec, 0x76, 0x37, 0xb8, 0xa4, 0x3b, 0xdc, 0x67, 0x0c, 0x9a, 0xa7, 0x54, 0x29, 0x8f, 0xba, 0xa6, 0x98, 0x5f, 0xbc, + 0x32, 0xd8, 0x4e, 0x7c, 0xb0, 0xbc, 0x67, 0xb2, 0x1a, 0xb2, 0xc8, 0xaa, 0x72, 0xbf, 0x99, 0x41, 0xe9, 0x56, 0x40, + 0xcd, 0x48, 0x75, 0xc3, 0x69, 0xca, 0xca, 0x9d, 0x82, 0x39, 0xbb, 0x39, 0x0e, 0x93, 0x24, 0x5c, 0xf6, 0x1c, 0x7b, + 0x75, 0xab, 0x2a, 0x7d, 0x21, 0xec, 0xe0, 0xd6, 0xf6, 0xbd, 0xdf, 0xfe, 0x53, 0x42, 0xf3, 0x54, 0x7e, 0x95, 0xb0, + 0xe5, 0x8a, 0x45, 0x6e, 0xb2, 0x8e, 0x58, 0xaa, 0xfc, 0xf6, 0xbf, 0x2f, 0x4a, 0x17, 0xfb, 0xbe, 0xdc, 0x86, 0x58, + 0x7a, 0xb9, 0xc9, 0x95, 0x1f, 0xde, 0x3c, 0xc8, 0xfd, 0xea, 0x76, 0x54, 0x5e, 0x78, 0xf3, 0x45, 0x56, 0xfb, 0x34, + 0xd9, 0x32, 0x37, 0x31, 0x7a, 0xd2, 0x07, 0x28, 0x67, 0xe1, 0x4d, 0xef, 0xb7, 0xff, 0x64, 0x02, 0x9b, 0x9d, 0xbb, + 0xae, 0x7e, 0xa0, 0xc5, 0x15, 0xad, 0xaf, 0x53, 0x59, 0x62, 0x78, 0x5f, 0x59, 0xe0, 0x4a, 0x21, 0xed, 0xca, 0xaa, + 0x6e, 0x6e, 0xcb, 0x9c, 0xbe, 0xf3, 0xe6, 0x8b, 0xcf, 0x9d, 0x14, 0x00, 0x74, 0xe7, 0xac, 0xa0, 0xd2, 0x17, 0x98, + 0xd6, 0xa8, 0xb7, 0xff, 0x82, 0x7d, 0xe6, 0xbc, 0x76, 0x4d, 0xe9, 0x4b, 0xcc, 0x86, 0x4b, 0x6e, 0x5f, 0x8c, 0x46, + 0x59, 0x4a, 0x5a, 0xb9, 0x3d, 0x78, 0x06, 0x9e, 0x56, 0x4a, 0x38, 0x7b, 0xd1, 0xb3, 0x75, 0x0a, 0xd9, 0xb3, 0x07, + 0x40, 0xd0, 0xc6, 0xbd, 0x06, 0x1c, 0xcd, 0xf8, 0x9a, 0x5c, 0xd5, 0x2a, 0xdf, 0xae, 0x20, 0x6b, 0x28, 0xc5, 0x74, + 0xa6, 0x99, 0xd6, 0xd0, 0xa8, 0x1f, 0xce, 0x4d, 0xe4, 0xae, 0x48, 0x49, 0xa0, 0xa0, 0xc6, 0x04, 0x84, 0x2e, 0xa5, + 0x5b, 0xf4, 0xb5, 0xeb, 0x5f, 0xef, 0x77, 0xa1, 0x6a, 0xa6, 0x60, 0x48, 0x9a, 0xff, 0x3c, 0xe2, 0x8d, 0x74, 0x79, + 0x7f, 0xda, 0x8d, 0x69, 0xe2, 0x5e, 0x35, 0x99, 0xd6, 0xbf, 0xd9, 0x6d, 0x5a, 0x7f, 0xbe, 0x97, 0x69, 0xfd, 0x9b, + 0x2f, 0x6e, 0x5a, 0xff, 0x4a, 0x36, 0xad, 0x87, 0x4d, 0xfc, 0x8a, 0xed, 0x65, 0xc9, 0x2c, 0xac, 0x8d, 0xc2, 0x9b, + 0x78, 0xe0, 0xf0, 0x4b, 0x4f, 0x3c, 0x59, 0x30, 0x90, 0x22, 0x71, 0x70, 0xf9, 0xe1, 0x1c, 0x0c, 0x8e, 0x9b, 0x4d, + 0x8a, 0xbf, 0x94, 0x41, 0xb1, 0x1f, 0xce, 0x55, 0x29, 0x50, 0x7e, 0x20, 0x02, 0xe5, 0x43, 0x70, 0x80, 0x7f, 0xde, + 0x3a, 0xcf, 0x2f, 0x9c, 0x7e, 0xdb, 0x81, 0x40, 0x33, 0x20, 0x18, 0xc0, 0x02, 0xbb, 0xdf, 0x6e, 0x43, 0xc1, 0x8d, + 0x54, 0xd0, 0x82, 0x02, 0x4f, 0x2a, 0xe8, 0x40, 0xc1, 0x44, 0x2a, 0x38, 0x82, 0x82, 0xa9, 0x54, 0x70, 0x0c, 0x05, + 0xd7, 0x6a, 0x7a, 0x11, 0x64, 0x8e, 0x03, 0xc7, 0xfa, 0x65, 0x21, 0x47, 0x4a, 0x26, 0xc5, 0x12, 0x55, 0x8e, 0x0d, + 0x11, 0xb0, 0xd3, 0x3c, 0xd4, 0xb9, 0x89, 0xfa, 0xe8, 0xab, 0x11, 0xb8, 0xd2, 0x83, 0x50, 0xcf, 0x00, 0x91, 0x28, + 0xd5, 0x6c, 0x8b, 0xd7, 0x6a, 0x2f, 0x33, 0xb4, 0xb7, 0x8d, 0x96, 0x30, 0x5c, 0xef, 0xa1, 0x1b, 0x95, 0xa8, 0xdc, + 0x79, 0xba, 0xc8, 0xa2, 0x77, 0xad, 0x07, 0xb9, 0x37, 0x62, 0x1b, 0x62, 0x18, 0x83, 0x6a, 0xfa, 0x25, 0xf2, 0x07, + 0x56, 0x12, 0x82, 0xb3, 0x99, 0x88, 0x5a, 0x25, 0x3e, 0xa0, 0xa0, 0x37, 0x42, 0xdf, 0xcd, 0x03, 0x8c, 0xf1, 0x58, + 0x77, 0x34, 0xfa, 0x65, 0x16, 0x42, 0x8c, 0xae, 0xb9, 0x6b, 0x23, 0x71, 0xe7, 0xbd, 0x85, 0x41, 0x32, 0xee, 0xde, + 0x1c, 0x62, 0xc2, 0x9e, 0x4e, 0x7b, 0x2b, 0xe3, 0x66, 0xc1, 0x82, 0xde, 0x8c, 0x5b, 0x81, 0xc2, 0xfa, 0x93, 0x91, + 0xcf, 0x52, 0x17, 0xd6, 0x69, 0xb8, 0x27, 0xf2, 0xb7, 0x34, 0x4a, 0x33, 0xdb, 0x4a, 0xb9, 0x61, 0x95, 0x26, 0xcb, + 0xbf, 0xbf, 0x84, 0x19, 0xcc, 0x4b, 0x36, 0x5e, 0xcf, 0x95, 0xb3, 0x70, 0xbe, 0xd3, 0xe4, 0x45, 0x7e, 0x05, 0xa3, + 0x54, 0x49, 0xd1, 0x67, 0x8a, 0xed, 0xcd, 0xbf, 0x45, 0x8f, 0x69, 0xb1, 0x7e, 0x02, 0x63, 0x53, 0x12, 0x42, 0xd9, + 0xf0, 0x1d, 0x80, 0xb6, 0x64, 0x54, 0x72, 0x06, 0xf0, 0x93, 0x9e, 0xcf, 0x5d, 0x69, 0x3c, 0xc3, 0x1f, 0x59, 0x1c, + 0xbb, 0x73, 0x51, 0xbf, 0x3a, 0x4e, 0xf0, 0xaf, 0xca, 0x6e, 0xfa, 0x08, 0x40, 0x90, 0x19, 0x7b, 0x15, 0x53, 0x21, + 0xb0, 0x60, 0x06, 0x13, 0x3a, 0x58, 0xb4, 0xdc, 0xae, 0xc6, 0xb3, 0x60, 0x79, 0x8a, 0x26, 0x2e, 0x80, 0x44, 0xae, + 0x99, 0x5f, 0x2e, 0x4c, 0xdc, 0x79, 0xb9, 0x88, 0xd6, 0x3a, 0x95, 0xc7, 0x96, 0x59, 0x98, 0x14, 0x0a, 0x3f, 0xc7, + 0x64, 0xc2, 0x0f, 0xe7, 0xbf, 0xab, 0xbd, 0xc4, 0x16, 0x3b, 0x97, 0xf7, 0x81, 0x11, 0x24, 0x23, 0x0b, 0x61, 0xac, + 0x58, 0x00, 0xc2, 0x5e, 0x90, 0x2c, 0x4c, 0xf4, 0xec, 0xd7, 0x5a, 0x81, 0x6e, 0x58, 0xb8, 0xb6, 0x9b, 0x72, 0x3c, + 0x93, 0x5e, 0x34, 0x1f, 0xbb, 0x9a, 0xd3, 0x3a, 0x36, 0xc4, 0x1f, 0xcb, 0xee, 0xe8, 0x29, 0xf6, 0xa0, 0x4c, 0xbd, + 0xeb, 0xcd, 0x2c, 0x0c, 0x12, 0x73, 0xe6, 0x2e, 0x3d, 0xff, 0xae, 0xb7, 0x0c, 0x83, 0x30, 0x5e, 0xb9, 0x13, 0xd6, + 0xcf, 0x45, 0x37, 0x7d, 0x8c, 0x94, 0xc5, 0x83, 0x35, 0x38, 0x56, 0x2b, 0x62, 0x4b, 0x6a, 0x9d, 0x05, 0xc2, 0x9a, + 0xf9, 0xec, 0x36, 0xe5, 0x9f, 0x2f, 0x54, 0xa6, 0xaa, 0xb8, 0xe5, 0xa8, 0x05, 0xdc, 0x43, 0x78, 0x94, 0x2d, 0x88, + 0x2d, 0xd9, 0xe7, 0xcc, 0x7c, 0xcf, 0x6a, 0x75, 0x22, 0xb6, 0x54, 0xac, 0x4e, 0x63, 0xe7, 0x51, 0x78, 0x33, 0x84, + 0xd1, 0x62, 0x63, 0x33, 0x66, 0xfe, 0x0c, 0xdf, 0x98, 0xe8, 0xd8, 0x2b, 0xfa, 0x31, 0x51, 0xe4, 0x03, 0xbd, 0xb1, + 0x65, 0x1f, 0x5e, 0xf7, 0x5a, 0x8a, 0xdd, 0x5f, 0x7a, 0x81, 0x49, 0xd3, 0x39, 0xb6, 0x57, 0x52, 0x5f, 0x32, 0xfc, + 0xf4, 0x0d, 0x56, 0x77, 0x14, 0xbb, 0x0f, 0x57, 0xfb, 0x99, 0x1f, 0xde, 0xf4, 0x16, 0xde, 0x74, 0xca, 0x82, 0x3e, + 0x8e, 0x39, 0x2b, 0x64, 0xbe, 0xef, 0xad, 0x62, 0x2f, 0xee, 0x2f, 0xdd, 0x5b, 0xde, 0xeb, 0x61, 0x53, 0xaf, 0x6d, + 0xde, 0x6b, 0x7b, 0xef, 0x5e, 0xa5, 0x6e, 0xc0, 0x89, 0x98, 0xfa, 0xe1, 0x43, 0xeb, 0x28, 0x76, 0x69, 0x9e, 0x7b, + 0xf7, 0xba, 0x8a, 0xd8, 0x66, 0xe9, 0x46, 0x73, 0x2f, 0xe8, 0xd9, 0xa9, 0x75, 0xbd, 0xa1, 0x8d, 0xf1, 0xb0, 0xdb, + 0xed, 0xa6, 0xd6, 0x54, 0x3c, 0xd9, 0xd3, 0x69, 0x6a, 0x4d, 0xc4, 0xd3, 0x6c, 0x66, 0xdb, 0xb3, 0x59, 0x6a, 0x79, + 0xa2, 0xa0, 0xdd, 0x9a, 0x4c, 0xdb, 0xad, 0xd4, 0xba, 0x91, 0x6a, 0xa4, 0x16, 0xe3, 0x4f, 0x11, 0x9b, 0xf6, 0x71, + 0x23, 0x71, 0x73, 0xf4, 0x63, 0xdb, 0x4e, 0x11, 0x03, 0x5c, 0x14, 0x70, 0x13, 0x4a, 0x15, 0x2f, 0x37, 0x7b, 0xd7, + 0x54, 0xf2, 0xcf, 0x4d, 0x26, 0xb5, 0xf5, 0xa6, 0x6e, 0xf4, 0xf1, 0x52, 0x91, 0x66, 0xe1, 0xba, 0x54, 0x6d, 0x23, + 0xc0, 0x60, 0xde, 0xf6, 0x20, 0x62, 0x6a, 0x7f, 0x1c, 0x46, 0x70, 0x66, 0x23, 0x77, 0xea, 0xad, 0xe3, 0x9e, 0xd3, + 0x5a, 0xdd, 0x8a, 0x22, 0xbe, 0xd7, 0xf3, 0x02, 0x3c, 0x7b, 0xbd, 0x38, 0xf4, 0xbd, 0xa9, 0x28, 0x6a, 0x3a, 0x4b, + 0x4e, 0x4b, 0xef, 0x63, 0xbc, 0x20, 0x0f, 0xa3, 0x5e, 0xb9, 0xbe, 0xaf, 0x58, 0xed, 0x58, 0x61, 0x6e, 0x8c, 0x9a, + 0x0c, 0xc5, 0x8e, 0x09, 0x2e, 0x18, 0x1b, 0xc8, 0x39, 0x5c, 0xdd, 0x66, 0x7b, 0xde, 0x39, 0x5a, 0xdd, 0xa6, 0xdf, + 0x2c, 0xd9, 0xd4, 0x73, 0x15, 0x2d, 0xdf, 0x4d, 0x8e, 0x0d, 0xda, 0x0e, 0x7d, 0xd3, 0xb0, 0x4d, 0xc5, 0xb1, 0x80, + 0xc8, 0xd2, 0x0f, 0xbc, 0xe5, 0x2a, 0x8c, 0x12, 0x37, 0x48, 0xd2, 0x74, 0x74, 0x99, 0xa6, 0xfd, 0x73, 0x4f, 0xbb, + 0xf8, 0xbb, 0x46, 0xb4, 0x90, 0xb4, 0x83, 0xa9, 0x7e, 0x69, 0xbc, 0x62, 0xb2, 0x25, 0x13, 0x90, 0x31, 0xb4, 0x62, + 0x92, 0x2b, 0x13, 0xbd, 0xad, 0x56, 0x26, 0x20, 0x67, 0xd5, 0xc9, 0x30, 0xaa, 0x58, 0x05, 0x29, 0x10, 0x54, 0x78, + 0xc5, 0x06, 0xe7, 0x92, 0x59, 0x14, 0x30, 0x3d, 0x58, 0x99, 0xdc, 0x3a, 0x5f, 0x36, 0xf1, 0x9e, 0xe7, 0xbb, 0x79, + 0xcf, 0x7f, 0x24, 0xfb, 0xf0, 0x9e, 0xe7, 0x5f, 0x9c, 0xf7, 0x7c, 0x59, 0x75, 0xeb, 0x3c, 0x0f, 0x07, 0x6a, 0xa6, + 0xcb, 0x02, 0xd2, 0x14, 0x51, 0xc0, 0xc4, 0x97, 0xc9, 0x7f, 0xeb, 0x5f, 0x27, 0x7a, 0xa3, 0x14, 0xc0, 0x44, 0xb9, + 0x81, 0x81, 0x7f, 0x1b, 0x0c, 0x7e, 0x88, 0xe4, 0xe7, 0xd9, 0x6c, 0xf0, 0x32, 0x94, 0x0a, 0xb2, 0x27, 0x6e, 0xe6, + 0x53, 0x08, 0x6e, 0x45, 0x6f, 0x32, 0x43, 0x2c, 0x48, 0xff, 0x05, 0xb1, 0x71, 0xc8, 0xea, 0x7e, 0x9a, 0x99, 0x43, + 0xf6, 0x8b, 0x43, 0xd0, 0x32, 0xfb, 0x63, 0xe1, 0x01, 0x5d, 0x11, 0x5a, 0xcf, 0x59, 0xc2, 0x43, 0x96, 0x3c, 0xbf, + 0x7b, 0x33, 0xd5, 0xce, 0x43, 0x3d, 0xf5, 0xe2, 0xb7, 0x65, 0xff, 0x63, 0x71, 0x05, 0x91, 0xa7, 0x93, 0x72, 0x93, + 0x46, 0x29, 0xcc, 0x10, 0xbe, 0xa6, 0xe6, 0xa7, 0x85, 0x99, 0xf6, 0xe4, 0x86, 0x3c, 0xcf, 0x68, 0x85, 0x18, 0x73, + 0x3f, 0xbd, 0x0d, 0xe7, 0xf2, 0x30, 0x75, 0x2a, 0x86, 0x6d, 0x99, 0x52, 0x73, 0x6f, 0x9a, 0xa6, 0x7a, 0x5f, 0x00, + 0x42, 0x22, 0xb4, 0x6c, 0x17, 0x13, 0x17, 0xe7, 0x17, 0x5a, 0xae, 0x8b, 0x26, 0x45, 0xf3, 0x39, 0x98, 0x6e, 0x70, + 0xb5, 0x34, 0x87, 0x99, 0xaa, 0x10, 0xf8, 0xc8, 0xa4, 0x47, 0x9a, 0x10, 0xd8, 0x1a, 0xc8, 0x86, 0x70, 0x85, 0x05, + 0xa9, 0xda, 0x1c, 0x13, 0x70, 0xd0, 0xf6, 0x04, 0x82, 0x2c, 0x09, 0x69, 0x17, 0xa1, 0x1d, 0x5e, 0x07, 0x1f, 0x52, + 0x35, 0xe3, 0xfd, 0x70, 0xfb, 0x0d, 0x4f, 0x0e, 0xa0, 0xc1, 0xb0, 0x24, 0xc9, 0xda, 0x61, 0x32, 0x0b, 0xac, 0x44, + 0x7c, 0x63, 0x58, 0xf1, 0x8d, 0xf2, 0x64, 0x23, 0x02, 0x94, 0x25, 0xee, 0xca, 0x04, 0xf1, 0x09, 0xe2, 0x5e, 0x8e, + 0xf1, 0xa4, 0x58, 0x68, 0xfd, 0x75, 0x0c, 0xb8, 0x11, 0x6f, 0xf2, 0x88, 0x7f, 0xfa, 0x93, 0x75, 0x14, 0x87, 0x51, + 0x6f, 0x15, 0x7a, 0x41, 0xc2, 0xa2, 0x14, 0x41, 0x75, 0x81, 0xf0, 0x11, 0xe0, 0xb9, 0xdc, 0x84, 0x2b, 0x77, 0xe2, + 0x25, 0x77, 0x3d, 0x9b, 0xb3, 0x14, 0x76, 0x9f, 0x73, 0x07, 0x76, 0x6d, 0xfd, 0x1e, 0x87, 0xe6, 0x53, 0x64, 0xfc, + 0xa2, 0x2a, 0x3b, 0x23, 0x6f, 0xf3, 0xbe, 0xf4, 0x96, 0x42, 0xb4, 0x01, 0xfb, 0xe1, 0x46, 0xe6, 0x1c, 0xb0, 0x3c, + 0x2c, 0xb5, 0x3d, 0x65, 0x73, 0x03, 0xb1, 0x36, 0x68, 0x80, 0xc4, 0x1f, 0xab, 0xa3, 0x2b, 0x76, 0x7d, 0x31, 0x70, + 0x3c, 0xfa, 0x3e, 0x23, 0xeb, 0xb9, 0x90, 0xd0, 0xd4, 0xd8, 0xa7, 0xe6, 0x98, 0xcd, 0xc2, 0x88, 0x51, 0x38, 0x7f, + 0xa7, 0xbb, 0xba, 0xdd, 0xbf, 0xfb, 0xed, 0xd3, 0xaf, 0xef, 0x27, 0x08, 0x13, 0x4d, 0x74, 0xa6, 0xef, 0xe8, 0xad, + 0x4a, 0xcf, 0x80, 0x35, 0x24, 0xc8, 0x4f, 0xc8, 0x1f, 0x05, 0x5c, 0xb1, 0x6b, 0xa3, 0xa6, 0xae, 0x42, 0x4e, 0xf3, + 0x22, 0xe6, 0xbb, 0x89, 0x77, 0x2d, 0x78, 0xc6, 0xf6, 0xd1, 0xea, 0x56, 0xac, 0x31, 0x12, 0xbc, 0x7b, 0x2c, 0x52, + 0x69, 0x28, 0x62, 0x91, 0xca, 0xc5, 0xb8, 0x48, 0xfd, 0xca, 0x6c, 0x44, 0x20, 0xb1, 0x12, 0xa5, 0xef, 0xac, 0x6e, + 0x65, 0x12, 0x9d, 0x37, 0xcb, 0x28, 0x75, 0x39, 0x02, 0xec, 0xd2, 0x9b, 0x4e, 0x7d, 0x96, 0x16, 0x16, 0xba, 0xb8, + 0x96, 0x12, 0x70, 0x32, 0x38, 0xb8, 0xe3, 0x38, 0xf4, 0xd7, 0x09, 0xab, 0x07, 0x17, 0x01, 0xa7, 0x65, 0xe7, 0xc0, + 0xc1, 0xdf, 0xc5, 0xb1, 0x76, 0x80, 0xdd, 0x86, 0x6d, 0x62, 0xf7, 0x21, 0xe1, 0x83, 0xd9, 0x2e, 0x0e, 0x1d, 0x5e, + 0x65, 0x83, 0x36, 0x6a, 0x26, 0x62, 0x00, 0x59, 0x22, 0xec, 0xad, 0x58, 0x0e, 0x2f, 0xcb, 0x82, 0xde, 0x67, 0x45, + 0x69, 0x71, 0x32, 0xbf, 0xcf, 0x19, 0x7b, 0x56, 0x7f, 0xc6, 0x9e, 0x89, 0x33, 0xb6, 0x7d, 0x67, 0x3e, 0x9c, 0x39, + 0xf0, 0x5f, 0x3f, 0x9f, 0x50, 0xcf, 0x56, 0xda, 0xab, 0x5b, 0xc5, 0x59, 0xdd, 0x2a, 0x66, 0x6b, 0x75, 0xab, 0x60, + 0xd7, 0x68, 0x79, 0x64, 0x58, 0x2d, 0xdd, 0xb0, 0x15, 0x28, 0x84, 0x3f, 0x76, 0xe1, 0x95, 0x73, 0x08, 0xef, 0xa0, + 0x55, 0xa7, 0xfa, 0xae, 0xb5, 0xfd, 0xa8, 0xd3, 0x59, 0x12, 0x48, 0x5b, 0xb7, 0x12, 0x77, 0x3c, 0x66, 0xd3, 0xde, + 0x2c, 0x9c, 0xac, 0xe3, 0x7f, 0xf3, 0xf1, 0x73, 0x20, 0x6e, 0x45, 0x04, 0xa5, 0x7e, 0x44, 0x53, 0x90, 0xee, 0x5d, + 0x33, 0xd1, 0xc3, 0x26, 0x5b, 0xa7, 0x1e, 0x65, 0xa7, 0x68, 0x59, 0x87, 0x35, 0x9b, 0xbc, 0x1e, 0xd0, 0xbf, 0xdb, + 0x2a, 0x35, 0xa3, 0x98, 0xcf, 0x00, 0xcb, 0x56, 0x70, 0xdc, 0x1f, 0x1a, 0x7c, 0x35, 0xed, 0x6e, 0xfd, 0x70, 0x2f, + 0xc4, 0x97, 0x2e, 0x05, 0x51, 0xe1, 0x74, 0x8b, 0x7b, 0x49, 0x6d, 0xef, 0xb5, 0x69, 0x8f, 0x54, 0x7a, 0xdd, 0x42, + 0x10, 0xf2, 0xba, 0x7b, 0x62, 0xf9, 0x87, 0xcf, 0x0e, 0xe1, 0x3f, 0xe2, 0xea, 0xff, 0x91, 0xd4, 0x31, 0xea, 0x2f, + 0x93, 0x02, 0xa3, 0x4e, 0xac, 0x12, 0x32, 0xe2, 0xfb, 0xd7, 0x9f, 0xcd, 0xee, 0xd7, 0x60, 0xef, 0xda, 0x64, 0xb4, + 0x57, 0xae, 0xfd, 0x3c, 0x0c, 0x21, 0x73, 0x7a, 0xb5, 0xba, 0x00, 0x0f, 0x79, 0x60, 0x24, 0x03, 0x68, 0x24, 0xee, + 0x11, 0x64, 0x2f, 0xa2, 0x62, 0x1b, 0xba, 0x4a, 0x9c, 0x35, 0x5d, 0x25, 0xde, 0xed, 0xbe, 0x4a, 0x7c, 0xbf, 0xd7, + 0x55, 0xe2, 0xdd, 0x17, 0xbf, 0x4a, 0x9c, 0x55, 0xaf, 0x12, 0x67, 0xa1, 0xb0, 0xd4, 0x36, 0x5e, 0xaf, 0xf9, 0xcf, + 0x0f, 0xa4, 0x8a, 0x7d, 0x17, 0x0e, 0x3a, 0x36, 0x65, 0x9c, 0x38, 0xff, 0xaf, 0x2f, 0x16, 0xb8, 0x11, 0xdf, 0xa1, + 0xe1, 0x62, 0x7e, 0xb5, 0xe0, 0x98, 0x1d, 0xbf, 0x23, 0x15, 0xfb, 0x61, 0x30, 0xff, 0x19, 0x54, 0xf1, 0x20, 0x0e, + 0x8c, 0xa4, 0x17, 0x5e, 0xfc, 0x73, 0xb8, 0x5a, 0xaf, 0xde, 0x40, 0x5f, 0x1f, 0xbc, 0xd8, 0x1b, 0xfb, 0x2c, 0x0b, + 0xf1, 0x41, 0x86, 0x96, 0x5c, 0xb6, 0x0e, 0xb6, 0xcd, 0xe2, 0xa7, 0x7b, 0x2b, 0x7e, 0xa2, 0xf5, 0x33, 0xff, 0x4d, + 0x16, 0x9c, 0x6a, 0xfd, 0x45, 0x04, 0x42, 0x26, 0x96, 0x06, 0x7d, 0xff, 0xcb, 0xc8, 0x59, 0xa8, 0xd7, 0xcc, 0x52, + 0x58, 0xd6, 0x34, 0xf6, 0xc3, 0xca, 0xfd, 0xbc, 0x5e, 0xeb, 0x46, 0x16, 0x01, 0xb5, 0x2a, 0xce, 0x5f, 0x86, 0xeb, + 0x98, 0x4d, 0xc3, 0x9b, 0x40, 0x35, 0x02, 0x6e, 0x0e, 0x4a, 0x49, 0x24, 0xb3, 0x36, 0x98, 0xbb, 0xfb, 0x3d, 0x32, + 0xca, 0x10, 0x28, 0x01, 0x52, 0xc7, 0xaf, 0x57, 0x26, 0x19, 0x18, 0x98, 0x38, 0x45, 0x35, 0x4b, 0x32, 0xf9, 0x40, + 0xd3, 0xc2, 0xc1, 0xfd, 0x5a, 0x0a, 0xa3, 0xa0, 0xd0, 0xe2, 0x52, 0xe1, 0x58, 0x0b, 0x84, 0x70, 0x51, 0x84, 0x21, + 0xab, 0x59, 0x38, 0xfe, 0x86, 0xe2, 0x77, 0xe4, 0x6f, 0x21, 0x20, 0x44, 0xba, 0xe6, 0xeb, 0xc1, 0x83, 0x72, 0xd1, + 0xe3, 0x0b, 0x09, 0x8c, 0x6f, 0xaf, 0x59, 0xe4, 0xbb, 0x77, 0x9a, 0x9e, 0x86, 0xc1, 0x8f, 0x00, 0x80, 0x97, 0xe1, + 0x4d, 0x20, 0x57, 0xc0, 0x5c, 0x79, 0x35, 0x7b, 0xa9, 0x36, 0x7c, 0x1c, 0xb8, 0x53, 0x49, 0x23, 0xf0, 0xac, 0x95, + 0x3b, 0x67, 0xff, 0x63, 0xd0, 0xbf, 0x7f, 0xd7, 0x53, 0xe3, 0x5d, 0x98, 0x7d, 0xe8, 0x97, 0xd5, 0x1e, 0x9f, 0x79, + 0xfc, 0xf8, 0x41, 0xf3, 0xb4, 0xb5, 0x89, 0xcf, 0xdc, 0x48, 0x8c, 0xa2, 0xa6, 0xb5, 0xde, 0x78, 0x0a, 0x60, 0x14, + 0xe7, 0xe1, 0x7a, 0xb2, 0x40, 0x93, 0xea, 0x2f, 0x37, 0xdf, 0x04, 0xfa, 0xc4, 0x24, 0xf1, 0xd9, 0xd4, 0x4b, 0x45, + 0x39, 0x14, 0xf0, 0xfb, 0xaf, 0x20, 0xfe, 0xf9, 0x9f, 0x08, 0x86, 0xea, 0xae, 0xc9, 0xbc, 0xb1, 0xef, 0xb5, 0x79, + 0xfb, 0x90, 0xcb, 0x9c, 0x47, 0x16, 0x13, 0x4a, 0xba, 0x7a, 0x24, 0x93, 0x96, 0x81, 0x26, 0x47, 0xf1, 0x6d, 0x0a, + 0x50, 0x2c, 0xbe, 0xc2, 0x2c, 0xba, 0xa6, 0x73, 0x97, 0x16, 0x83, 0x71, 0x6c, 0x55, 0x42, 0x32, 0xdc, 0xd0, 0x85, + 0x21, 0xfa, 0x2a, 0xbf, 0x5b, 0x7a, 0x81, 0x81, 0x49, 0x78, 0xaa, 0x6f, 0xdc, 0x5b, 0x48, 0x43, 0x01, 0xc8, 0xad, + 0xfc, 0x0a, 0x0a, 0x0d, 0xd9, 0x91, 0x13, 0x32, 0x6d, 0xaa, 0xb5, 0x90, 0x10, 0xda, 0xc0, 0xd1, 0x57, 0x8a, 0xa2, + 0x28, 0xd9, 0x35, 0x42, 0xc9, 0xee, 0x11, 0x58, 0x8e, 0xd7, 0x01, 0xd0, 0x96, 0xa4, 0xab, 0x5b, 0x2a, 0x81, 0x9b, + 0x01, 0xaa, 0xb6, 0x45, 0x01, 0x8f, 0xb4, 0xdc, 0xb1, 0x45, 0x81, 0xb8, 0xd0, 0x43, 0x94, 0x5c, 0x37, 0x82, 0x84, + 0x0c, 0x3d, 0x05, 0x2f, 0xec, 0xf8, 0x96, 0x4b, 0x82, 0x15, 0x9b, 0x1e, 0x47, 0x7d, 0x56, 0x1f, 0x92, 0x37, 0x90, + 0xb0, 0x20, 0x68, 0x1d, 0x4a, 0x19, 0x36, 0x0c, 0x56, 0x83, 0x1b, 0xf1, 0x5e, 0x74, 0x9b, 0x2c, 0x59, 0xb0, 0x56, + 0x31, 0x25, 0x27, 0x86, 0x48, 0x86, 0x3a, 0x2f, 0x89, 0xd9, 0x02, 0x6c, 0x53, 0xdf, 0x72, 0x41, 0xb4, 0x30, 0xe6, + 0x28, 0xd5, 0x35, 0x26, 0xdc, 0x37, 0x31, 0xe6, 0xb8, 0xad, 0x4c, 0x21, 0xf8, 0x92, 0x86, 0x45, 0x6c, 0xce, 0xbd, + 0x91, 0x91, 0x53, 0xa0, 0xb0, 0x53, 0x5c, 0x5c, 0x24, 0xc0, 0xae, 0xb9, 0xe5, 0x45, 0xcb, 0x34, 0x32, 0x6e, 0x49, + 0x50, 0x14, 0xe9, 0xd5, 0x6e, 0xf8, 0x38, 0x21, 0x2e, 0x64, 0x63, 0x3f, 0x93, 0x4a, 0x3f, 0x0d, 0x93, 0xfe, 0xc8, + 0xee, 0x88, 0x90, 0x10, 0xa8, 0x3e, 0xb2, 0x3b, 0xd0, 0xdb, 0xbf, 0x02, 0x69, 0x8a, 0xba, 0x05, 0x5d, 0x1b, 0x90, + 0x69, 0x69, 0x02, 0xb1, 0x42, 0xb7, 0x1c, 0x20, 0x3b, 0xdd, 0x82, 0xc5, 0x11, 0xc4, 0x81, 0x11, 0xf7, 0xc5, 0x21, + 0xe6, 0xce, 0x24, 0x5a, 0x2d, 0x8c, 0xcd, 0x9a, 0xa3, 0xa1, 0x3f, 0x71, 0x6c, 0xfb, 0xa0, 0x52, 0x1f, 0x04, 0xd9, + 0x75, 0xb5, 0x75, 0x23, 0x19, 0x38, 0xb6, 0xe9, 0x3d, 0xb1, 0x5a, 0xfd, 0x0a, 0x8d, 0x96, 0x42, 0x79, 0x8f, 0x50, + 0xfc, 0x35, 0x7c, 0xb4, 0xd1, 0x2a, 0x07, 0x52, 0x2f, 0x3b, 0x67, 0xe0, 0xd8, 0x52, 0x2e, 0xff, 0x1a, 0x55, 0x49, + 0x3f, 0x05, 0x12, 0xa7, 0xb4, 0x72, 0x23, 0x48, 0x46, 0xa1, 0xc1, 0x31, 0xfa, 0x8b, 0xf2, 0x54, 0xd1, 0xe8, 0xf8, + 0xe8, 0xfa, 0xa8, 0x2f, 0x30, 0x8a, 0xf0, 0x5e, 0x94, 0x3b, 0x28, 0x7d, 0x31, 0x2e, 0x63, 0x38, 0x1e, 0xf6, 0x9e, + 0xe5, 0x1a, 0xbd, 0xad, 0xdc, 0x02, 0xf6, 0xdf, 0x40, 0x3e, 0xad, 0x31, 0x84, 0xdf, 0x80, 0x1a, 0x90, 0xba, 0x66, + 0x67, 0x87, 0x10, 0x2d, 0x49, 0xee, 0xae, 0x48, 0x24, 0xf7, 0xef, 0x0c, 0x89, 0x0e, 0xea, 0xd0, 0xb2, 0xfe, 0xea, + 0xc9, 0xdd, 0x3d, 0xbb, 0x64, 0xc1, 0xb4, 0xd8, 0x61, 0x89, 0x7e, 0xed, 0xdf, 0x5d, 0x01, 0xa3, 0x40, 0x4e, 0xa7, + 0xb0, 0x06, 0xa3, 0xa4, 0x61, 0x80, 0x9b, 0x9f, 0x8e, 0x9b, 0xb7, 0x17, 0x17, 0x83, 0x0d, 0x28, 0x20, 0x6b, 0xd6, + 0x4c, 0x12, 0x8a, 0x43, 0xe2, 0x10, 0x74, 0x6e, 0xd6, 0x04, 0x23, 0xda, 0xb8, 0x13, 0x13, 0x61, 0x49, 0x9a, 0xb7, + 0xf1, 0x78, 0x28, 0xf0, 0x7d, 0xa5, 0xd6, 0xde, 0x6e, 0xa9, 0x75, 0xb2, 0x4b, 0x6a, 0x4d, 0x8e, 0x7b, 0x64, 0xfe, + 0x94, 0x39, 0x30, 0x0a, 0xe6, 0x5c, 0x76, 0x01, 0x2d, 0x88, 0xba, 0xd1, 0xcf, 0x4f, 0xb4, 0xaa, 0xf4, 0x46, 0xb6, + 0xa1, 0x28, 0xfe, 0x96, 0x2e, 0x28, 0x42, 0xa1, 0x2e, 0xcb, 0xc6, 0xcf, 0x72, 0xd9, 0x38, 0xdd, 0x6a, 0x72, 0x97, + 0x2d, 0xc1, 0xfd, 0x4b, 0xee, 0x90, 0xd9, 0xed, 0x20, 0x77, 0x8b, 0xcc, 0x47, 0x2a, 0x39, 0xfa, 0xe5, 0x17, 0x0d, + 0xc9, 0x7d, 0x54, 0xdc, 0x32, 0x8a, 0x5e, 0xa4, 0xc5, 0xaa, 0xb9, 0x9f, 0x5f, 0x5e, 0x0e, 0x52, 0x77, 0x1c, 0x72, + 0x56, 0x2c, 0x6f, 0x9b, 0xa2, 0xa3, 0x97, 0xfc, 0x5a, 0xda, 0x24, 0x99, 0x47, 0x16, 0x01, 0x58, 0x88, 0xe9, 0x4b, + 0x7a, 0xed, 0xcc, 0x06, 0x02, 0x07, 0x59, 0xe3, 0x40, 0xba, 0x5b, 0x3a, 0x4f, 0xe9, 0xaa, 0x72, 0xd5, 0xb5, 0x83, + 0xd4, 0x9d, 0x34, 0xc1, 0xb2, 0x3c, 0x02, 0x61, 0x7d, 0x29, 0x49, 0x10, 0x7a, 0xb6, 0x62, 0xf7, 0x6b, 0x18, 0x00, + 0xa4, 0xff, 0xe5, 0x67, 0xce, 0x0a, 0x80, 0x24, 0x52, 0xb1, 0x65, 0x9d, 0x3f, 0x1e, 0x62, 0x93, 0xcc, 0xcf, 0xb0, + 0x6a, 0xf5, 0x9b, 0x24, 0xef, 0xd9, 0x70, 0x77, 0xad, 0xa2, 0x38, 0x9f, 0xd7, 0xe8, 0x89, 0x71, 0xf0, 0x5d, 0x16, + 0xad, 0x03, 0xcc, 0x42, 0x64, 0x26, 0x91, 0x3b, 0xf9, 0xb8, 0x91, 0xbe, 0xc7, 0x45, 0xa2, 0x20, 0x2e, 0x2e, 0x2a, + 0x15, 0xfa, 0x2e, 0x06, 0xed, 0x66, 0x3d, 0xab, 0x15, 0x4b, 0x82, 0x9a, 0xde, 0x43, 0xbb, 0xed, 0x3e, 0x9b, 0x1d, + 0x96, 0xe4, 0xa7, 0xad, 0x4e, 0x51, 0xba, 0x9e, 0x8d, 0x63, 0x19, 0xfe, 0xca, 0x1d, 0x5b, 0xff, 0xf8, 0x4f, 0xc7, + 0xfc, 0x9b, 0xa5, 0x35, 0xfa, 0x9c, 0x21, 0x40, 0xfb, 0x82, 0x62, 0x5a, 0x56, 0xd3, 0x54, 0x4a, 0x9a, 0x86, 0x35, + 0xf3, 0x7c, 0xdf, 0xf4, 0xc1, 0xbd, 0x68, 0xf3, 0x59, 0xd3, 0xc3, 0x7e, 0xd6, 0x90, 0x2e, 0xe2, 0x33, 0xfa, 0x29, + 0xee, 0x94, 0x64, 0xb1, 0x5e, 0x8e, 0x37, 0xb2, 0xa0, 0x5c, 0x92, 0x9f, 0x57, 0x65, 0xe6, 0xf2, 0x67, 0x67, 0xb3, + 0x59, 0x51, 0x6a, 0x6c, 0x2b, 0x87, 0x28, 0xf9, 0x7d, 0x68, 0xdb, 0x76, 0x19, 0xbe, 0x4d, 0x07, 0x85, 0x0e, 0x86, + 0x89, 0x42, 0xf8, 0xee, 0xee, 0x3d, 0xf5, 0x07, 0x8d, 0x96, 0xba, 0x6a, 0x3a, 0x8f, 0xb4, 0xd5, 0xfe, 0x5f, 0x0c, + 0x05, 0x51, 0xc3, 0xae, 0xe3, 0x5f, 0xdd, 0x2b, 0x5b, 0x7a, 0x2a, 0x1f, 0xe0, 0xfb, 0x35, 0xde, 0xb1, 0xd7, 0xf7, + 0x68, 0xda, 0xb4, 0xbd, 0x53, 0x2b, 0x27, 0xbb, 0x05, 0x9b, 0xa5, 0x3e, 0x59, 0x2a, 0x79, 0x09, 0x5b, 0xc6, 0xbd, + 0x09, 0x43, 0x05, 0xa9, 0x25, 0x51, 0x5b, 0xb4, 0xea, 0x31, 0xe7, 0x60, 0xc7, 0xe5, 0x08, 0x3c, 0x6c, 0x2b, 0xa8, + 0xac, 0xaa, 0x68, 0xd6, 0xc4, 0x47, 0x90, 0x8a, 0x6d, 0xaa, 0x0a, 0x27, 0xdc, 0xa6, 0x1d, 0xfb, 0x2f, 0x85, 0x7a, + 0x0a, 0x70, 0xa7, 0x1b, 0x61, 0x6d, 0x42, 0xca, 0x13, 0xfc, 0x3b, 0x53, 0xce, 0x3d, 0x5b, 0xdd, 0x16, 0x8d, 0xbb, + 0xba, 0xa0, 0x6e, 0xca, 0x49, 0x19, 0x8d, 0xba, 0x0e, 0xf5, 0x65, 0x26, 0x40, 0x33, 0xd9, 0xba, 0x05, 0x2c, 0x68, + 0x0a, 0xc9, 0x11, 0x6b, 0x74, 0x63, 0x78, 0x9d, 0x85, 0x9d, 0x97, 0xcb, 0xf7, 0xf3, 0xd4, 0xde, 0x30, 0x07, 0xe3, + 0x69, 0x17, 0x95, 0x7b, 0x85, 0xad, 0x8a, 0xa6, 0x32, 0xb8, 0x07, 0xc4, 0x8d, 0x54, 0x59, 0x47, 0xbe, 0x49, 0x99, + 0x03, 0x35, 0x7d, 0x53, 0x9d, 0x77, 0x73, 0xf7, 0x4e, 0x07, 0xf4, 0x1a, 0x55, 0x50, 0xed, 0xa5, 0xda, 0x2b, 0xeb, + 0xb0, 0xc5, 0x38, 0x61, 0x05, 0xc0, 0x15, 0x45, 0x41, 0xa3, 0x21, 0xa5, 0x84, 0xfb, 0x68, 0xd2, 0xd9, 0x5b, 0x19, + 0x59, 0x8b, 0x79, 0x62, 0x77, 0xf5, 0x55, 0xa8, 0x6f, 0xa1, 0x19, 0x04, 0xd8, 0x71, 0xec, 0x84, 0xcf, 0x26, 0xec, + 0x18, 0x19, 0x5d, 0x39, 0xb8, 0x83, 0xf0, 0x94, 0x9a, 0x14, 0x91, 0x96, 0x4e, 0x29, 0xea, 0x12, 0xbe, 0xaf, 0x15, + 0xde, 0x9f, 0x17, 0xa4, 0xf1, 0xdc, 0x1f, 0xa8, 0xa5, 0xef, 0x55, 0x7b, 0xe9, 0x05, 0xfb, 0xd7, 0x75, 0x6f, 0xf7, + 0xae, 0x0b, 0xcc, 0xe1, 0xde, 0x95, 0x81, 0xbb, 0x24, 0x2b, 0xa5, 0x64, 0xf0, 0xbd, 0xa4, 0x3c, 0x90, 0x63, 0x59, + 0xa8, 0xd8, 0x8a, 0x6e, 0xf4, 0x3f, 0xad, 0x07, 0xa3, 0x93, 0xd3, 0xdb, 0xa5, 0xaf, 0x5c, 0xb3, 0x08, 0xb2, 0xa8, + 0x0e, 0x54, 0xc7, 0xb2, 0x55, 0x05, 0x23, 0x33, 0x78, 0xc1, 0x7c, 0xa0, 0xfe, 0x72, 0xfe, 0xda, 0xec, 0xaa, 0xa7, + 0x60, 0x8e, 0x71, 0x3d, 0x47, 0x16, 0xf7, 0xcc, 0xbd, 0x63, 0xd1, 0x55, 0x4b, 0x55, 0x30, 0x59, 0x2a, 0x31, 0xb7, + 0x58, 0xa6, 0xb4, 0xd4, 0x3d, 0x72, 0xf2, 0x29, 0x22, 0xad, 0xb6, 0x0a, 0x88, 0xd5, 0x69, 0x75, 0x15, 0xa7, 0x75, + 0x68, 0x1d, 0x75, 0xd5, 0xe1, 0x57, 0x8a, 0x72, 0x32, 0x65, 0xb3, 0x78, 0x88, 0xe2, 0x98, 0x13, 0xe4, 0x07, 0xe9, + 0xb7, 0xa2, 0x58, 0x13, 0x3f, 0x36, 0x1d, 0x65, 0xc3, 0x1f, 0x15, 0x05, 0x90, 0x51, 0x4f, 0x79, 0x38, 0x6b, 0xcd, + 0x0e, 0x67, 0xcf, 0xfa, 0xbc, 0x38, 0xfd, 0xaa, 0x50, 0xdd, 0xa0, 0x7f, 0x5b, 0x52, 0xb3, 0x38, 0x89, 0xc2, 0x8f, + 0x8c, 0xf3, 0x92, 0x4a, 0x26, 0x28, 0x2a, 0x37, 0x6d, 0x55, 0xbf, 0xe4, 0x74, 0xc7, 0x93, 0x59, 0x2b, 0xaf, 0x8e, + 0x63, 0x3c, 0xc8, 0x06, 0x79, 0x72, 0x20, 0x86, 0x7e, 0x22, 0x83, 0xc9, 0x31, 0xeb, 0x00, 0xe5, 0xa8, 0x7c, 0x8e, + 0x73, 0x31, 0xbf, 0x13, 0x08, 0x79, 0x9f, 0x7b, 0x70, 0xc4, 0xd8, 0x6c, 0xa0, 0xfe, 0xe8, 0xb4, 0xba, 0x86, 0xe3, + 0x1c, 0x59, 0x47, 0xdd, 0x89, 0x6d, 0x1c, 0x5a, 0x87, 0x66, 0xdb, 0x3a, 0x32, 0xba, 0x66, 0xd7, 0xe8, 0x7e, 0xd7, + 0x9d, 0x98, 0x87, 0xd6, 0xa1, 0x61, 0x9b, 0x5d, 0x28, 0x34, 0xbb, 0x66, 0xf7, 0xda, 0x3c, 0xec, 0x4e, 0x6c, 0x2c, + 0x6d, 0x59, 0x9d, 0x8e, 0xe9, 0xd8, 0x56, 0xa7, 0x63, 0x74, 0xac, 0xa3, 0x23, 0xd3, 0x69, 0x5b, 0x47, 0x47, 0x67, + 0x9d, 0xae, 0xd5, 0x86, 0x77, 0xed, 0xf6, 0xa4, 0x6d, 0x39, 0x8e, 0x09, 0x7f, 0x19, 0x5d, 0xab, 0x45, 0x3f, 0x1c, + 0xc7, 0x6a, 0x3b, 0x86, 0xed, 0x77, 0x5a, 0xd6, 0xd1, 0x33, 0x03, 0xff, 0xc6, 0x6a, 0x06, 0xfe, 0x05, 0xdd, 0x18, + 0xcf, 0xac, 0xd6, 0x11, 0xfd, 0xc2, 0x0e, 0xaf, 0x0f, 0xbb, 0xff, 0x50, 0x0f, 0x1a, 0xe7, 0xe0, 0xd0, 0x1c, 0xba, + 0x1d, 0xab, 0xdd, 0x36, 0x0e, 0x1d, 0xab, 0xdb, 0x5e, 0x98, 0x87, 0x2d, 0xeb, 0xe8, 0x78, 0x62, 0x3a, 0xd6, 0xf1, + 0xb1, 0x61, 0x9b, 0x6d, 0xab, 0x65, 0x38, 0xd6, 0x61, 0x1b, 0x7f, 0xb4, 0xad, 0xd6, 0xf5, 0xf1, 0x33, 0xeb, 0xa8, + 0xb3, 0x38, 0xb2, 0x0e, 0x3f, 0x1c, 0x76, 0xad, 0x56, 0x7b, 0xd1, 0x3e, 0xb2, 0x5a, 0xc7, 0xd7, 0x47, 0xd6, 0xe1, + 0xc2, 0x6c, 0x1d, 0x6d, 0x6d, 0xe9, 0xb4, 0x2c, 0x80, 0x11, 0xbe, 0x86, 0x17, 0x06, 0x7f, 0x01, 0x7f, 0x16, 0xd8, + 0xf6, 0x0f, 0xec, 0x26, 0xae, 0x36, 0x7d, 0x66, 0x75, 0x8f, 0x27, 0x54, 0x1d, 0x0a, 0x4c, 0x51, 0x03, 0x9a, 0x5c, + 0x9b, 0xf4, 0x59, 0xec, 0xce, 0x14, 0x1d, 0x89, 0x3f, 0xfc, 0x63, 0xd7, 0x26, 0x7c, 0x98, 0xbe, 0xfb, 0xa7, 0xf6, + 0x93, 0x2d, 0xf9, 0xc9, 0xc1, 0x9c, 0xb6, 0xfe, 0x7c, 0xf8, 0x15, 0xe5, 0xd7, 0x1c, 0x19, 0xbf, 0x36, 0x29, 0x25, + 0xff, 0xb5, 0x5b, 0x29, 0xf9, 0x7c, 0xbd, 0x8f, 0x52, 0xf2, 0x5f, 0x5f, 0x5c, 0x29, 0xf9, 0x6b, 0xd9, 0xb7, 0xe6, + 0x75, 0x39, 0x0d, 0xd8, 0xf7, 0x9b, 0xb2, 0xc8, 0x21, 0x70, 0xb5, 0x8b, 0x9f, 0xd6, 0x97, 0x10, 0xda, 0xef, 0x75, + 0x38, 0x78, 0xbe, 0x2e, 0x18, 0x7c, 0x86, 0x80, 0x63, 0x5f, 0x87, 0x84, 0x63, 0x3f, 0xac, 0x07, 0x60, 0x65, 0xc6, + 0xd9, 0x1c, 0x6f, 0x6a, 0x2e, 0x5c, 0x7f, 0x96, 0xb1, 0x48, 0x50, 0xd2, 0xc7, 0x62, 0x70, 0x5c, 0x03, 0xf2, 0x0c, + 0x37, 0x99, 0xf5, 0x32, 0x88, 0xc1, 0x22, 0x18, 0x2c, 0x39, 0x66, 0x51, 0x5a, 0x6a, 0x6c, 0x89, 0x60, 0x88, 0x57, + 0xdc, 0x0b, 0xaa, 0xf1, 0x3d, 0x1a, 0x00, 0xd7, 0xf7, 0xee, 0x54, 0xfb, 0x55, 0xc0, 0xb2, 0x4e, 0x18, 0x48, 0x03, + 0xb7, 0x5f, 0xf7, 0xbe, 0x68, 0x86, 0x5b, 0x32, 0xbc, 0x6e, 0x1e, 0x29, 0x8c, 0xa4, 0xdc, 0xde, 0x29, 0x9a, 0xf1, + 0xee, 0x9a, 0x66, 0xcd, 0xe7, 0x0b, 0xcd, 0xb7, 0xd8, 0x10, 0x67, 0x1d, 0x97, 0x41, 0x55, 0x4a, 0x62, 0x5d, 0x0b, + 0x90, 0xfc, 0x82, 0x9a, 0x1b, 0x1a, 0xe7, 0x9c, 0xaa, 0xad, 0x20, 0xbf, 0x63, 0x4b, 0xef, 0x0a, 0x7d, 0xca, 0xc6, + 0xc9, 0x4f, 0x36, 0x78, 0xaf, 0xf0, 0x7e, 0x05, 0x4e, 0x94, 0x73, 0x3c, 0xe3, 0x50, 0x86, 0xf3, 0x46, 0xea, 0x97, + 0xa4, 0x11, 0xe9, 0xc2, 0xd9, 0x54, 0x79, 0xd1, 0x46, 0xb7, 0x04, 0x87, 0x2d, 0x05, 0x17, 0x84, 0x9f, 0x27, 0x27, + 0x80, 0x94, 0x1c, 0x35, 0xd0, 0xcf, 0x61, 0x5b, 0x67, 0xa2, 0xde, 0x43, 0xd8, 0xc4, 0x3c, 0x26, 0xb3, 0x22, 0x47, + 0x9b, 0xd9, 0xcc, 0xfc, 0xd0, 0x4d, 0x7a, 0xc8, 0xa6, 0x49, 0x2c, 0x6f, 0x0b, 0x3d, 0x16, 0xfa, 0x5b, 0x8c, 0xe9, + 0xe4, 0x8e, 0x79, 0x27, 0xe8, 0xf9, 0xb0, 0xcd, 0xfe, 0x2e, 0x73, 0x38, 0xdb, 0x14, 0xcc, 0x51, 0x9c, 0xce, 0xb1, + 0xe1, 0x1c, 0x19, 0xd6, 0x71, 0x47, 0x4f, 0xc5, 0x81, 0x93, 0xbb, 0x2c, 0x00, 0x04, 0x1c, 0x20, 0xb2, 0x61, 0x7a, + 0x81, 0x97, 0x78, 0xae, 0x9f, 0x02, 0x3f, 0x5c, 0xbc, 0xa4, 0xfc, 0x6b, 0x1d, 0x27, 0x30, 0x47, 0xc1, 0xf4, 0xa2, + 0xf3, 0x87, 0x39, 0x66, 0xc9, 0x0d, 0x63, 0x41, 0x83, 0x61, 0x4c, 0xd9, 0x97, 0xe4, 0xf7, 0xb3, 0xac, 0x4f, 0xc9, + 0x6a, 0x6d, 0x9c, 0x04, 0x7c, 0x7f, 0x08, 0xc7, 0x87, 0x74, 0x64, 0x7c, 0xd7, 0x84, 0x70, 0x7f, 0xd9, 0x8d, 0x70, + 0x13, 0xb6, 0x0f, 0xc2, 0xfd, 0xe5, 0x8b, 0x23, 0xdc, 0xef, 0x64, 0x84, 0x5b, 0xf0, 0x1f, 0xcc, 0x35, 0x4c, 0xef, + 0xf1, 0x59, 0x83, 0xcc, 0x28, 0x4f, 0xd5, 0x03, 0x62, 0xe0, 0x55, 0x3d, 0x4f, 0x1f, 0xf4, 0xb7, 0x42, 0xa2, 0x56, + 0x14, 0x80, 0x62, 0xd6, 0x0d, 0x4a, 0x0a, 0xe9, 0x81, 0xab, 0x5b, 0x96, 0x18, 0x92, 0xdd, 0x28, 0x6f, 0x82, 0xc4, + 0xb7, 0xde, 0xf1, 0x7b, 0x24, 0x28, 0x74, 0x5f, 0x87, 0xd1, 0xd2, 0xc5, 0xe8, 0xaf, 0x2a, 0x26, 0x78, 0x87, 0x07, + 0x1b, 0x9c, 0x71, 0x27, 0x61, 0x30, 0xcd, 0xb4, 0x92, 0x6c, 0x70, 0x41, 0x1c, 0xb7, 0x7a, 0xc7, 0xdc, 0x48, 0x35, + 0xe8, 0x35, 0x2c, 0xee, 0x93, 0xb6, 0xfd, 0xa4, 0x75, 0xf8, 0xe4, 0xc8, 0x86, 0xff, 0x1d, 0xd6, 0x4e, 0x0d, 0x5e, + 0x71, 0x19, 0x06, 0x90, 0x63, 0x52, 0xd4, 0x6c, 0xaa, 0x76, 0xc3, 0xd8, 0xc7, 0xbc, 0xd6, 0x71, 0x7d, 0xa5, 0xa9, + 0x7b, 0x97, 0xd7, 0xa9, 0xad, 0xb1, 0x08, 0xd7, 0xd2, 0xb0, 0x6a, 0x46, 0xe3, 0x05, 0x6b, 0x90, 0xb3, 0x4b, 0x35, + 0xe4, 0xd7, 0x7c, 0xba, 0xf9, 0xbc, 0x58, 0x3b, 0xbd, 0xcc, 0x13, 0xd9, 0x8a, 0x7c, 0x46, 0x3b, 0x21, 0xc8, 0x55, + 0x94, 0x36, 0x86, 0x03, 0xc7, 0x44, 0x13, 0x10, 0x0c, 0x3c, 0x4b, 0x3f, 0xea, 0xd2, 0x02, 0x25, 0xd1, 0x3a, 0x98, + 0x68, 0xf8, 0xd3, 0x1d, 0xc7, 0x9a, 0x77, 0x10, 0x59, 0xfc, 0xc3, 0x3a, 0xae, 0x9a, 0x3b, 0xb4, 0xf3, 0xac, 0x7f, + 0xb1, 0x58, 0x15, 0xf7, 0x49, 0x62, 0x44, 0xa8, 0xc7, 0xa6, 0xa5, 0x35, 0x07, 0xee, 0x93, 0xac, 0xe1, 0x93, 0xc4, + 0x08, 0x9e, 0x82, 0xee, 0x73, 0x60, 0x3f, 0x7e, 0x4c, 0xb5, 0x1e, 0x0c, 0xc4, 0xb4, 0x4e, 0x27, 0x79, 0xd0, 0x50, + 0xc5, 0x9d, 0x87, 0x14, 0x37, 0xb4, 0x37, 0x31, 0xc2, 0xa7, 0x4f, 0x87, 0x03, 0x47, 0xc7, 0x8c, 0xb2, 0x22, 0x33, + 0x3c, 0x4f, 0x56, 0x7c, 0xb6, 0x9f, 0xa1, 0x91, 0x5e, 0xeb, 0x4a, 0xbb, 0x82, 0x3b, 0x93, 0x2d, 0xdc, 0x11, 0x38, + 0xf6, 0x82, 0xe4, 0x81, 0x64, 0x50, 0xe0, 0x0a, 0x83, 0x1f, 0x51, 0x27, 0xbb, 0x75, 0xb5, 0x2d, 0xdb, 0xb2, 0xd5, + 0xac, 0xe1, 0xcc, 0x9b, 0x0f, 0x36, 0x61, 0xe2, 0x42, 0x1a, 0x56, 0x3f, 0x9c, 0x83, 0x1f, 0x5d, 0xe2, 0x25, 0x3e, + 0xe4, 0xf4, 0x04, 0x87, 0xba, 0x25, 0xdd, 0xcb, 0x53, 0xee, 0xdd, 0xe0, 0x46, 0x1f, 0x31, 0xaf, 0xbb, 0x70, 0xc5, + 0xc5, 0x38, 0x76, 0x3f, 0x02, 0x31, 0xd4, 0x54, 0x0d, 0x64, 0x03, 0x2c, 0x8a, 0x4d, 0xd9, 0x5b, 0xa8, 0xa7, 0x40, + 0x1b, 0x5d, 0xe5, 0x93, 0x98, 0x45, 0xee, 0x12, 0x12, 0x1b, 0x6d, 0x52, 0x83, 0x63, 0x5a, 0x95, 0xa3, 0x5a, 0xc5, + 0x79, 0x76, 0x64, 0x28, 0x2d, 0xc7, 0x50, 0x6c, 0x40, 0xb7, 0x6a, 0x6a, 0x6c, 0xd2, 0xcb, 0xfe, 0x2e, 0x83, 0x07, + 0xc2, 0x2f, 0x0f, 0x69, 0x1e, 0x64, 0xea, 0xc0, 0x55, 0x49, 0x09, 0xc5, 0x2f, 0xd6, 0xa4, 0x84, 0x26, 0x1e, 0x29, + 0x3d, 0xcf, 0xd9, 0x6d, 0xa2, 0x63, 0xce, 0x4b, 0x5e, 0xc5, 0xd3, 0x37, 0xe8, 0x30, 0xec, 0x05, 0x8a, 0xf7, 0xe9, + 0x93, 0xe6, 0x81, 0x33, 0xd3, 0x40, 0x82, 0x0f, 0x3c, 0xeb, 0x05, 0x80, 0x79, 0xb9, 0x9a, 0x1e, 0x81, 0x05, 0x9e, + 0x86, 0xf0, 0x6f, 0x5e, 0x2c, 0x7e, 0x70, 0x33, 0x09, 0xcb, 0x77, 0x83, 0x39, 0xa0, 0x34, 0x37, 0x98, 0x57, 0xcc, + 0xb1, 0xc8, 0xe7, 0xb9, 0x54, 0x9a, 0x77, 0x95, 0x9b, 0x4a, 0xc5, 0xcf, 0xef, 0xce, 0x29, 0xa7, 0xaf, 0xa6, 0x02, + 0x95, 0x43, 0x17, 0xdd, 0x5c, 0x93, 0xfb, 0x74, 0xf0, 0xf5, 0xc9, 0x92, 0x25, 0x2e, 0xa9, 0x81, 0xe0, 0xf2, 0x0b, + 0xec, 0x80, 0xc2, 0x09, 0x0d, 0x8f, 0x0d, 0x35, 0xa0, 0x30, 0xe7, 0x44, 0x27, 0x0c, 0x85, 0xd3, 0x29, 0x13, 0x2d, + 0x3e, 0x07, 0x8e, 0x41, 0x0e, 0x07, 0x13, 0x17, 0x43, 0x1d, 0x0f, 0x82, 0x50, 0x1d, 0x7e, 0x9d, 0xf9, 0x66, 0x36, + 0x2d, 0x82, 0xef, 0x05, 0x1f, 0x2f, 0x22, 0xe6, 0xff, 0x7b, 0xf0, 0x35, 0x10, 0xee, 0xaf, 0x2f, 0x55, 0xbd, 0x9f, + 0x58, 0x8b, 0x88, 0xcd, 0x06, 0x5f, 0xd7, 0x24, 0x98, 0xc7, 0xeb, 0x3d, 0x8d, 0x45, 0x6d, 0xb7, 0xf2, 0x90, 0x73, + 0xed, 0xbd, 0x2e, 0xf5, 0x43, 0x7e, 0x5b, 0x87, 0x1b, 0xe0, 0xa6, 0x70, 0xc7, 0x76, 0xfa, 0x78, 0x7f, 0x1e, 0xfb, + 0xee, 0xe4, 0x63, 0x9f, 0xde, 0x14, 0x1e, 0x4c, 0xa0, 0xd6, 0x13, 0x77, 0xd5, 0x43, 0xf2, 0x2a, 0x17, 0x82, 0xf7, + 0x34, 0x95, 0x66, 0x9c, 0x5d, 0xed, 0x5e, 0xc6, 0xad, 0xbc, 0xc1, 0x2f, 0xe3, 0xa7, 0x6e, 0x16, 0x5e, 0xc2, 0xc4, + 0xa7, 0xf0, 0x21, 0x4d, 0xc5, 0x45, 0x9d, 0xae, 0xa8, 0x78, 0xb1, 0xb6, 0xda, 0x8a, 0xd3, 0xfd, 0xae, 0x73, 0xed, + 0xd8, 0x8b, 0x96, 0x63, 0x75, 0x3f, 0x38, 0xdd, 0x45, 0xdb, 0x3a, 0xf6, 0xcd, 0xb6, 0x75, 0x0c, 0x7f, 0x3e, 0x1c, + 0x5b, 0xdd, 0x85, 0xd9, 0xb2, 0x0e, 0x3f, 0x38, 0x2d, 0xdf, 0xec, 0x5a, 0xc7, 0xf0, 0xe7, 0x8c, 0x5a, 0xc1, 0x05, + 0x88, 0xee, 0x3b, 0x5f, 0x17, 0xb0, 0x80, 0xf4, 0x3b, 0xd3, 0xc9, 0x1a, 0x05, 0xf2, 0x56, 0xa3, 0xd7, 0x05, 0x94, + 0x41, 0x19, 0x7f, 0xd0, 0x14, 0xa1, 0xaf, 0x05, 0x03, 0x46, 0x39, 0x7e, 0x84, 0x79, 0x9b, 0xf0, 0x43, 0x17, 0x89, + 0x56, 0x6a, 0x8f, 0x11, 0x6f, 0x53, 0x9f, 0x5c, 0x44, 0x24, 0x01, 0x26, 0x45, 0xf0, 0x2f, 0x2b, 0x0c, 0x8d, 0x27, + 0x72, 0x62, 0x49, 0x58, 0x29, 0x4f, 0x44, 0x9f, 0xee, 0x1e, 0x38, 0x7a, 0xf3, 0xb3, 0x2c, 0x11, 0xea, 0x17, 0xed, + 0x5b, 0x4a, 0x3d, 0xf6, 0x59, 0xfd, 0x60, 0x52, 0xa6, 0x3c, 0x9f, 0x12, 0x44, 0x14, 0x9f, 0x7a, 0x51, 0x36, 0x3c, + 0x09, 0x45, 0x3b, 0xf5, 0x59, 0x59, 0x74, 0xc8, 0x18, 0xf9, 0x06, 0xb8, 0xe4, 0x6b, 0xd7, 0x97, 0x0c, 0xd9, 0xa4, + 0x96, 0x0f, 0x32, 0xcc, 0xff, 0xf8, 0x71, 0x3e, 0x38, 0xb3, 0x34, 0xee, 0x13, 0xa7, 0x03, 0x64, 0xb7, 0xc3, 0xda, + 0x5b, 0x6d, 0x2a, 0x77, 0xc7, 0xa2, 0xcf, 0x83, 0x50, 0x0b, 0xbb, 0x29, 0x61, 0xb1, 0xd1, 0x68, 0xd8, 0x59, 0xb1, + 0xd7, 0x80, 0x28, 0xfe, 0xa5, 0xab, 0x8e, 0xaa, 0xf7, 0x03, 0x61, 0x7e, 0x10, 0x6c, 0x89, 0xbf, 0xcf, 0xef, 0x62, + 0x2a, 0x80, 0x66, 0xcb, 0x3c, 0x76, 0x38, 0x88, 0xff, 0xd9, 0x93, 0x40, 0x67, 0x4d, 0xb0, 0x97, 0x28, 0x9d, 0xd6, + 0x82, 0xf3, 0x5e, 0x46, 0x57, 0x89, 0xa0, 0xb2, 0xf8, 0x54, 0x85, 0x22, 0x48, 0x25, 0x8c, 0xd9, 0xc3, 0x33, 0x63, + 0xd1, 0x8c, 0x5a, 0xe4, 0x05, 0x86, 0x87, 0xb9, 0x4e, 0x84, 0xe3, 0xa8, 0xfe, 0xf8, 0x71, 0x23, 0x11, 0x22, 0xe3, + 0x9c, 0x98, 0x25, 0x59, 0x7e, 0x53, 0x55, 0xc6, 0x6f, 0xaa, 0x8c, 0x62, 0xb2, 0x7e, 0x11, 0x6b, 0x08, 0x1b, 0x57, + 0xda, 0x7b, 0xf8, 0x73, 0xcc, 0xdc, 0xc4, 0xe2, 0xca, 0x52, 0x4d, 0x22, 0xee, 0x86, 0xc3, 0xda, 0x60, 0xdd, 0xca, + 0x23, 0x68, 0x66, 0x69, 0x12, 0xff, 0xb6, 0xe6, 0x51, 0x1d, 0xa0, 0x8f, 0x4f, 0x76, 0x1e, 0x80, 0xec, 0x6d, 0xe2, + 0x52, 0x60, 0x18, 0x99, 0xe4, 0x86, 0x89, 0x2b, 0x52, 0x76, 0x02, 0x5f, 0xde, 0xaf, 0x35, 0xbf, 0x90, 0x22, 0x3f, + 0x0c, 0xdf, 0x9e, 0x7f, 0xab, 0xf0, 0xfd, 0x4f, 0xd6, 0x02, 0x78, 0x91, 0xa1, 0xcc, 0x3f, 0x03, 0xca, 0xfc, 0xa3, + 0xf0, 0x24, 0x53, 0x2a, 0xe6, 0x6c, 0x24, 0x08, 0xa2, 0x00, 0x9a, 0x6c, 0x28, 0x96, 0x6b, 0x3f, 0xf1, 0x56, 0x6e, + 0x94, 0x1c, 0x60, 0xda, 0x1f, 0x40, 0x72, 0x6a, 0x53, 0x3c, 0x08, 0x32, 0xc3, 0x10, 0x81, 0x5b, 0x93, 0x40, 0xd8, + 0x61, 0xcc, 0x3c, 0x3f, 0x33, 0xc3, 0x10, 0x1f, 0x70, 0x27, 0x13, 0xb6, 0x4a, 0x06, 0x85, 0xf4, 0x42, 0xe1, 0x24, + 0x61, 0x89, 0x19, 0x27, 0x11, 0x73, 0x97, 0x6a, 0x16, 0x20, 0xbc, 0xda, 0x5f, 0xbc, 0x1e, 0x2f, 0xbd, 0x24, 0x8b, + 0xb0, 0x4b, 0x13, 0x04, 0x83, 0x08, 0x18, 0xe2, 0x70, 0x94, 0x72, 0x10, 0x9e, 0x85, 0xf3, 0xd2, 0x8e, 0xca, 0x39, + 0x97, 0x53, 0x8c, 0xdf, 0x4e, 0x37, 0x19, 0x90, 0x16, 0x4f, 0x42, 0xff, 0x8a, 0xc7, 0xb0, 0xc8, 0x02, 0x01, 0xab, + 0xc3, 0x13, 0x7e, 0xbd, 0x55, 0x30, 0x7c, 0x8b, 0xda, 0xb1, 0x21, 0x42, 0x7d, 0x53, 0x74, 0x8b, 0x03, 0x5e, 0x19, + 0x48, 0x13, 0xf5, 0x8c, 0x49, 0x46, 0x68, 0x2c, 0xe7, 0xc0, 0x08, 0x15, 0x0c, 0x66, 0x16, 0xce, 0x30, 0x73, 0xa7, + 0xc4, 0x51, 0x21, 0xaf, 0xf4, 0xe9, 0xd3, 0x8b, 0xd1, 0x6f, 0xff, 0x81, 0x4c, 0x28, 0x0b, 0x47, 0xc4, 0x94, 0xb8, + 0x90, 0x6b, 0x71, 0xee, 0xd3, 0x18, 0xa1, 0xb1, 0x14, 0x9b, 0x8a, 0x10, 0x3d, 0x62, 0x6b, 0xa5, 0xa3, 0x4b, 0x11, + 0xa2, 0x11, 0x72, 0x28, 0xe9, 0x22, 0xf2, 0x05, 0xa6, 0xe4, 0x1c, 0x89, 0x98, 0x28, 0xca, 0x3f, 0x6f, 0x9f, 0x1f, + 0x2b, 0x79, 0x0c, 0xa3, 0x3a, 0x8b, 0x1e, 0xda, 0x43, 0xc3, 0x13, 0x57, 0x41, 0xa6, 0x05, 0xd9, 0x8f, 0xb8, 0x77, + 0x00, 0xd3, 0x5c, 0x84, 0x4b, 0x66, 0x79, 0xe1, 0xc1, 0x0d, 0x1b, 0x9b, 0xee, 0xca, 0x23, 0xbb, 0x1c, 0x94, 0xbb, + 0x29, 0xc4, 0xf9, 0x65, 0xe6, 0x2e, 0xc4, 0x5f, 0xa7, 0x39, 0x28, 0xc3, 0x62, 0x4c, 0xce, 0x4e, 0x2b, 0xd7, 0x03, + 0x42, 0xfc, 0x02, 0x09, 0x8e, 0xe1, 0xf0, 0xe4, 0xc0, 0x1d, 0x16, 0x83, 0x02, 0x5b, 0x22, 0xb9, 0x4d, 0x91, 0x08, + 0x9c, 0x52, 0x6c, 0x5f, 0x11, 0xc6, 0x37, 0x7f, 0x30, 0xc3, 0xd9, 0x4c, 0x0e, 0xe4, 0x6b, 0x15, 0x87, 0x97, 0x01, + 0x2d, 0xdf, 0xd2, 0xe1, 0x8a, 0xbe, 0x54, 0xfd, 0x44, 0xf6, 0x53, 0xed, 0x61, 0x04, 0x6f, 0x98, 0x33, 0x1c, 0xf7, + 0x4a, 0x40, 0xe0, 0x0c, 0x62, 0x0f, 0xa9, 0x12, 0xc7, 0x23, 0xe5, 0xf4, 0x13, 0x0d, 0x9c, 0xcb, 0x83, 0xc1, 0x80, + 0xd0, 0x5c, 0x19, 0xdb, 0x01, 0x10, 0x6b, 0x12, 0xfd, 0xc0, 0x64, 0x13, 0x68, 0x68, 0x92, 0xbb, 0x2c, 0x36, 0x2a, + 0x4f, 0xa7, 0x3a, 0xc6, 0x03, 0x57, 0x6c, 0xbf, 0xc2, 0x06, 0x85, 0x8d, 0xc7, 0xd7, 0x1d, 0xf0, 0xbb, 0xe8, 0xa7, + 0x84, 0xe6, 0x95, 0x6f, 0x08, 0xa3, 0x9b, 0xbe, 0x7b, 0x17, 0x4a, 0x66, 0x4c, 0x3c, 0xa2, 0xc9, 0x19, 0x96, 0x9e, + 0x0b, 0x4f, 0xe2, 0xca, 0x41, 0xcb, 0x12, 0xa2, 0x54, 0x0f, 0x9b, 0x9c, 0xc4, 0x64, 0xd7, 0x59, 0x93, 0xeb, 0x16, + 0x27, 0x83, 0xc8, 0x33, 0xcd, 0xcf, 0x61, 0xe1, 0x25, 0xa2, 0x85, 0xf4, 0xe4, 0x00, 0xe6, 0x07, 0x51, 0x58, 0x0a, + 0x8c, 0x93, 0xa7, 0x43, 0xa8, 0x17, 0x37, 0x26, 0x53, 0xac, 0x37, 0x53, 0xc1, 0xf3, 0xe1, 0xc5, 0x52, 0x4a, 0xf3, + 0x27, 0x55, 0xa9, 0xf2, 0x32, 0x76, 0x3d, 0x13, 0xb8, 0x3b, 0x7b, 0xd0, 0x87, 0x35, 0xa6, 0x0e, 0x4a, 0xfb, 0x09, + 0x13, 0x41, 0x0e, 0xce, 0x92, 0x86, 0x38, 0x08, 0x4d, 0x55, 0x88, 0x9f, 0xdd, 0x52, 0x21, 0xdf, 0xc7, 0xdb, 0x6a, + 0xe5, 0x9c, 0x53, 0x56, 0x6d, 0xee, 0x6a, 0xea, 0x43, 0xdc, 0xf1, 0x95, 0xda, 0x58, 0x0a, 0xf5, 0xce, 0x92, 0x01, + 0x54, 0x15, 0xb2, 0x78, 0x77, 0xb5, 0xa2, 0xca, 0x7a, 0xff, 0xe4, 0x80, 0xae, 0xa5, 0x43, 0xda, 0x61, 0xc3, 0x13, + 0x30, 0xe5, 0xa6, 0x45, 0x77, 0x57, 0x2b, 0xbe, 0xa4, 0xf4, 0x8b, 0xde, 0x1c, 0x2c, 0x92, 0xa5, 0x3f, 0xfc, 0x3f, + 0x04, 0xdf, 0xdf, 0xf4, 0x2c, 0x6c, 0x03, 0x00}; -} // namespace esphome::web_server +} // namespace web_server +} // namespace esphome #endif #endif From e7001c5eea1ce79110a97ed7bc33b6b18ab5ec30 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:05:37 -1000 Subject: [PATCH 0863/1145] [api] Auto-generate zero-copy pointer access for incoming API bytes fields (#12654) --- esphome/components/api/api.proto | 8 +++---- esphome/components/api/api_pb2.h | 8 +++---- script/api_protobuf/api_protobuf.py | 35 +++++++++++++---------------- 3 files changed, 23 insertions(+), 28 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index debea5808c..fc05947774 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -747,7 +747,7 @@ message NoiseEncryptionSetKeyRequest { option (source) = SOURCE_CLIENT; option (ifdef) = "USE_API_NOISE"; - bytes key = 1 [(pointer_to_buffer) = true]; + bytes key = 1; } message NoiseEncryptionSetKeyResponse { @@ -796,7 +796,7 @@ message HomeassistantActionResponse { uint32 call_id = 1; // Matches the call_id from HomeassistantActionRequest bool success = 2; // Whether the service call succeeded string error_message = 3; // Error message if success = false - bytes response_data = 4 [(pointer_to_buffer) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"]; + bytes response_data = 4 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"]; } // ==================== IMPORT HOME ASSISTANT STATES ==================== @@ -1692,7 +1692,7 @@ message BluetoothGATTWriteRequest { uint32 handle = 2; bool response = 3; - bytes data = 4 [(pointer_to_buffer) = true]; + bytes data = 4; } message BluetoothGATTReadDescriptorRequest { @@ -1712,7 +1712,7 @@ message BluetoothGATTWriteDescriptorRequest { uint64 address = 1; uint32 handle = 2; - bytes data = 3 [(pointer_to_buffer) = true]; + bytes data = 3; } message BluetoothGATTNotifyRequest { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 9d7a1eb9cb..2579ebbae2 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1069,7 +1069,7 @@ class SubscribeLogsResponse final : public ProtoMessage { class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 124; - static constexpr uint8_t ESTIMATED_SIZE = 19; + static constexpr uint8_t ESTIMATED_SIZE = 9; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "noise_encryption_set_key_request"; } #endif @@ -1161,7 +1161,7 @@ class HomeassistantActionRequest final : public ProtoMessage { class HomeassistantActionResponse final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 130; - static constexpr uint8_t ESTIMATED_SIZE = 34; + static constexpr uint8_t ESTIMATED_SIZE = 24; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "homeassistant_action_response"; } #endif @@ -2146,7 +2146,7 @@ class BluetoothGATTReadResponse final : public ProtoMessage { class BluetoothGATTWriteRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 75; - static constexpr uint8_t ESTIMATED_SIZE = 29; + static constexpr uint8_t ESTIMATED_SIZE = 19; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_write_request"; } #endif @@ -2182,7 +2182,7 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage { class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 77; - static constexpr uint8_t ESTIMATED_SIZE = 27; + static constexpr uint8_t ESTIMATED_SIZE = 17; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; } #endif diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index f22b248747..5b68c6a3d2 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -354,28 +354,23 @@ def create_field_type_info( return FixedArrayRepeatedType(field, size_define) return RepeatedTypeInfo(field) - # Check for mutually exclusive options on bytes fields - if field.type == 12: - has_pointer_to_buffer = get_field_opt(field, pb.pointer_to_buffer, False) - fixed_size = get_field_opt(field, pb.fixed_array_size, None) - - if has_pointer_to_buffer and fixed_size is not None: - raise ValueError( - f"Field '{field.name}' has both pointer_to_buffer and fixed_array_size. " - "These options are mutually exclusive. Use pointer_to_buffer for zero-copy " - "or fixed_array_size for traditional array storage." - ) - - if has_pointer_to_buffer: - # Zero-copy pointer approach - no size needed, will use size_t for length - return PointerToBytesBufferType(field, None) - - if fixed_size is not None: - # Traditional fixed array approach with copy - return FixedArrayBytesType(field, fixed_size) - # Special handling for bytes fields if field.type == 12: + fixed_size = get_field_opt(field, pb.fixed_array_size, None) + + if fixed_size is not None: + # Traditional fixed array approach with copy (takes priority) + return FixedArrayBytesType(field, fixed_size) + + # For SOURCE_CLIENT only messages (decode but no encode), use pointer + # for zero-copy access to the receive buffer + if needs_decode and not needs_encode: + return PointerToBytesBufferType(field, None) + + # For SOURCE_BOTH/SOURCE_SERVER, explicit annotation is still needed + if get_field_opt(field, pb.pointer_to_buffer, False): + return PointerToBytesBufferType(field, None) + return BytesType(field, needs_decode, needs_encode) # Special handling for string fields From 4cb066bcbf3308664a8f0eddefdbca66adfc28b2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:05:50 -1000 Subject: [PATCH 0864/1145] [api] Use StringRef in handle_action_response to avoid temporary string (#12655) --- esphome/components/api/api_server.cpp | 4 ++-- esphome/components/api/api_server.h | 6 +++--- esphome/components/api/homeassistant_service.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 7a03d8f8ad..23cecd2663 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -394,7 +394,7 @@ void APIServer::register_action_response_callback(uint32_t call_id, ActionRespon this->action_response_callbacks_.push_back({call_id, std::move(callback)}); } -void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message) { +void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message) { for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) { if (it->call_id == call_id) { auto callback = std::move(it->callback); @@ -406,7 +406,7 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std } } #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON -void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message, +void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data, size_t response_data_len) { for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) { if (it->call_id == call_id) { diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 96c56fd08a..11d726a40a 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -143,10 +143,10 @@ class APIServer : public Component, // Action response handling using ActionResponseCallback = std::function; void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback); - void handle_action_response(uint32_t call_id, bool success, const std::string &error_message); + void handle_action_response(uint32_t call_id, bool success, StringRef error_message); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - void handle_action_response(uint32_t call_id, bool success, const std::string &error_message, - const uint8_t *response_data, size_t response_data_len); + void handle_action_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data, + size_t response_data_len); #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_SERVICES diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 2da6e15362..1fdcc51803 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -67,10 +67,10 @@ template class TemplatableKeyValuePair { // the callback is invoked synchronously while the message is on the stack). class ActionResponse { public: - ActionResponse(bool success, const std::string &error_message) : success_(success), error_message_(error_message) {} + ActionResponse(bool success, StringRef error_message) : success_(success), error_message_(error_message) {} #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - ActionResponse(bool success, const std::string &error_message, const uint8_t *data, size_t data_len) + ActionResponse(bool success, StringRef error_message, const uint8_t *data, size_t data_len) : success_(success), error_message_(error_message) { if (data == nullptr || data_len == 0) return; From f394cf3f4d8b2382a40bb5cd6451f392b0c8d330 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:06:03 -1000 Subject: [PATCH 0865/1145] [packet_transport] Use stack-based format_hex_pretty_to for logging (#12791) --- .../components/packet_transport/packet_transport.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/packet_transport/packet_transport.cpp b/esphome/components/packet_transport/packet_transport.cpp index da7f5f8bff..4a53ab110b 100644 --- a/esphome/components/packet_transport/packet_transport.cpp +++ b/esphome/components/packet_transport/packet_transport.cpp @@ -1,11 +1,15 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/helpers.h" #include "packet_transport.h" #include "esphome/components/xxtea/xxtea.h" namespace esphome { namespace packet_transport { + +// Maximum bytes to log in hex output (168 * 3 = 504, under TX buffer size of 512) +static constexpr size_t PACKET_MAX_LOG_BYTES = 168; /** * Structure of a data packet; everything is little-endian * @@ -263,7 +267,8 @@ void PacketTransport::flush_() { xxtea::encrypt((uint32_t *) (encode_buffer.data() + header_len), len / 4, (uint32_t *) this->encryption_key_.data()); } - ESP_LOGVV(TAG, "Sending packet %s", format_hex_pretty(encode_buffer.data(), encode_buffer.size()).c_str()); + char hex_buf[format_hex_pretty_size(PACKET_MAX_LOG_BYTES)]; + ESP_LOGVV(TAG, "Sending packet %s", format_hex_pretty_to(hex_buf, encode_buffer.data(), encode_buffer.size())); this->send_packet(encode_buffer); } @@ -505,8 +510,9 @@ void PacketTransport::process_(const std::vector &data) { } if (decoder.get(byte) == DECODE_OK) { ESP_LOGW(TAG, "Unknown key %X", byte); + char hex_buf[format_hex_pretty_size(PACKET_MAX_LOG_BYTES)]; ESP_LOGD(TAG, "Buffer pos: %zu contents: %s", data.size() - decoder.get_remaining_size(), - format_hex_pretty(data).c_str()); + format_hex_pretty_to(hex_buf, data.data(), data.size())); } break; } From 0b7ff09657f16707206e878c03a6440261e3958a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:09:40 -1000 Subject: [PATCH 0866/1145] [api] Use pointer to FixedVector for siren tones field (#12657) --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_pb2.cpp | 10 +++++----- esphome/components/api/api_pb2.h | 2 +- esphome/components/api/api_pb2_dump.cpp | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index fc05947774..b5aaec430c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1292,7 +1292,7 @@ message ListEntitiesSirenResponse { string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; bool disabled_by_default = 6; - repeated string tones = 7; + repeated string tones = 7 [(container_pointer_no_template) = "FixedVector"]; bool supports_duration = 8; bool supports_volume = 9; EntityCategory entity_category = 10; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index edd6dfc6a9..1147cd986e 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1710,8 +1710,8 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon_ref_); #endif buffer.encode_bool(6, this->disabled_by_default); - for (auto &it : this->tones) { - buffer.encode_string(7, it, true); + for (const char *it : *this->tones) { + buffer.encode_string(7, it, strlen(it), true); } buffer.encode_bool(8, this->supports_duration); buffer.encode_bool(9, this->supports_volume); @@ -1728,9 +1728,9 @@ void ListEntitiesSirenResponse::calculate_size(ProtoSize &size) const { size.add_length(1, this->icon_ref_.size()); #endif size.add_bool(1, this->disabled_by_default); - if (!this->tones.empty()) { - for (const auto &it : this->tones) { - size.add_length_force(1, it.size()); + if (!this->tones->empty()) { + for (const char *it : *this->tones) { + size.add_length_force(1, strlen(it)); } } size.add_bool(1, this->supports_duration); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 2579ebbae2..4b14697181 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1708,7 +1708,7 @@ class ListEntitiesSirenResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_siren_response"; } #endif - std::vector tones{}; + const FixedVector *tones{}; bool supports_duration{false}; bool supports_volume{false}; void encode(ProtoWriteBuffer buffer) const override; diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 567f10fcc0..12df109a3d 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1579,7 +1579,7 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const { dump_field(out, "icon", this->icon_ref_); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); - for (const auto &it : this->tones) { + for (const auto &it : *this->tones) { dump_field(out, "tones", it, 4); } dump_field(out, "supports_duration", this->supports_duration); From 51259888bf1a3fb640bb299f8dfd4b9a5ff48fe2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:10:21 -1000 Subject: [PATCH 0867/1145] [voice_assistant] Use zero-copy buffer access for audio data (#12656) --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_pb2.cpp | 10 ++++++---- esphome/components/api/api_pb2.h | 11 +++-------- esphome/components/api/api_pb2_dump.cpp | 6 +----- .../voice_assistant/voice_assistant.cpp | 15 ++++++++------- 5 files changed, 19 insertions(+), 25 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index b5aaec430c..43b721c2d5 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1937,7 +1937,7 @@ message VoiceAssistantAudio { option (source) = SOURCE_BOTH; option (ifdef) = "USE_VOICE_ASSISTANT"; - bytes data = 1; + bytes data = 1 [(pointer_to_buffer) = true]; bool end = 2; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 1147cd986e..698e08f9b3 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2527,20 +2527,22 @@ bool VoiceAssistantAudio::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->data = value.as_string(); + case 1: { + this->data = value.data(); + this->data_len = value.size(); break; + } default: return false; } return true; } void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const { - buffer.encode_bytes(1, this->data_ptr_, this->data_len_); + buffer.encode_bytes(1, this->data, this->data_len); buffer.encode_bool(2, this->end); } void VoiceAssistantAudio::calculate_size(ProtoSize &size) const { - size.add_length(1, this->data_len_); + size.add_length(1, this->data_len); size.add_bool(1, this->end); } bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 4b14697181..6275b4c211 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -2521,17 +2521,12 @@ class VoiceAssistantEventResponse final : public ProtoDecodableMessage { class VoiceAssistantAudio final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 106; - static constexpr uint8_t ESTIMATED_SIZE = 11; + static constexpr uint8_t ESTIMATED_SIZE = 21; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_audio"; } #endif - std::string data{}; - const uint8_t *data_ptr_{nullptr}; - size_t data_len_{0}; - void set_data(const uint8_t *data, size_t len) { - this->data_ptr_ = data; - this->data_len_ = len; - } + const uint8_t *data{nullptr}; + uint16_t data_len{0}; bool end{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 12df109a3d..1ec6645b3f 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1978,11 +1978,7 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const { void VoiceAssistantAudio::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantAudio"); out.append(" data: "); - if (this->data_ptr_ != nullptr) { - out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); - } else { - out.append(format_hex_pretty(reinterpret_cast(this->data.data()), this->data.size())); - } + out.append(format_hex_pretty(this->data, this->data_len)); out.append("\n"); dump_field(out, "end", this->end); } diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 9bb5393be2..8101d210b3 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -272,7 +272,8 @@ void VoiceAssistant::loop() { size_t read_bytes = this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0); if (this->audio_mode_ == AUDIO_MODE_API) { api::VoiceAssistantAudio msg; - msg.set_data(this->send_buffer_, read_bytes); + msg.data = this->send_buffer_; + msg.data_len = read_bytes; this->api_client_->send_message(msg, api::VoiceAssistantAudio::MESSAGE_TYPE); } else { if (!this->udp_socket_running_) { @@ -841,12 +842,12 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) { #ifdef USE_SPEAKER // We should never get to this function if there is no speaker anyway if ((this->speaker_ != nullptr) && (this->speaker_buffer_ != nullptr)) { - if (this->speaker_buffer_index_ + msg.data.length() < SPEAKER_BUFFER_SIZE) { - memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data.data(), msg.data.length()); - this->speaker_buffer_index_ += msg.data.length(); - this->speaker_buffer_size_ += msg.data.length(); - this->speaker_bytes_received_ += msg.data.length(); - ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data.length()); + if (this->speaker_buffer_index_ + msg.data_len < SPEAKER_BUFFER_SIZE) { + memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data, msg.data_len); + this->speaker_buffer_index_ += msg.data_len; + this->speaker_buffer_size_ += msg.data_len; + this->speaker_bytes_received_ += msg.data_len; + ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data_len); } else { ESP_LOGE(TAG, "Cannot receive audio, buffer is full"); } From 0ef49a8b7306ff6ce2150229f36d8462903f7c38 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:11:31 -1000 Subject: [PATCH 0868/1145] [ld2410][ld2412][ld2450] Use stack buffers for hex logging (#12688) --- esphome/components/ld2410/ld2410.cpp | 13 ++++++++++--- esphome/components/ld2412/ld2412.cpp | 13 ++++++++++--- esphome/components/ld2450/ld2450.cpp | 13 ++++++++++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/esphome/components/ld2410/ld2410.cpp b/esphome/components/ld2410/ld2410.cpp index bb2e4e2f4c..5ea47d5084 100644 --- a/esphome/components/ld2410/ld2410.cpp +++ b/esphome/components/ld2410/ld2410.cpp @@ -413,7 +413,8 @@ bool LD2410Component::handle_ack_data_() { return true; } if (!ld2410::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) { - ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str()); + char hex_buf[format_hex_pretty_size(HEADER_FOOTER_SIZE)]; + ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, HEADER_FOOTER_SIZE)); return true; } if (this->buffer_data_[COMMAND_STATUS] != 0x01) { @@ -597,11 +598,17 @@ void LD2410Component::readline_(int readch) { return; // Not enough data to process yet } if (ld2410::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) { - ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)]; + ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_)); +#endif this->handle_periodic_data_(); this->buffer_pos_ = 0; // Reset position index for next message } else if (ld2410::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) { - ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)]; + ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_)); +#endif if (this->handle_ack_data_()) { this->buffer_pos_ = 0; // Reset position index for next message } else { diff --git a/esphome/components/ld2412/ld2412.cpp b/esphome/components/ld2412/ld2412.cpp index 0f6fe62d30..3d51800065 100644 --- a/esphome/components/ld2412/ld2412.cpp +++ b/esphome/components/ld2412/ld2412.cpp @@ -457,7 +457,8 @@ bool LD2412Component::handle_ack_data_() { return true; } if (!ld2412::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) { - ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str()); + char hex_buf[format_hex_pretty_size(HEADER_FOOTER_SIZE)]; + ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, HEADER_FOOTER_SIZE)); return true; } if (this->buffer_data_[COMMAND_STATUS] != 0x01) { @@ -670,11 +671,17 @@ void LD2412Component::readline_(int readch) { return; // Not enough data to process yet } if (ld2412::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) { - ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)]; + ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_)); +#endif this->handle_periodic_data_(); this->buffer_pos_ = 0; // Reset position index for next message } else if (ld2412::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) { - ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)]; + ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_)); +#endif if (this->handle_ack_data_()) { this->buffer_pos_ = 0; // Reset position index for next message } else { diff --git a/esphome/components/ld2450/ld2450.cpp b/esphome/components/ld2450/ld2450.cpp index e69ef31d4f..2c137c3578 100644 --- a/esphome/components/ld2450/ld2450.cpp +++ b/esphome/components/ld2450/ld2450.cpp @@ -607,7 +607,8 @@ bool LD2450Component::handle_ack_data_() { return true; } if (!ld2450::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) { - ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str()); + char hex_buf[format_hex_pretty_size(HEADER_FOOTER_SIZE)]; + ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, HEADER_FOOTER_SIZE)); return true; } if (this->buffer_data_[COMMAND_STATUS] != 0x01) { @@ -758,11 +759,17 @@ void LD2450Component::readline_(int readch) { } if (this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[0] && this->buffer_data_[this->buffer_pos_ - 1] == DATA_FRAME_FOOTER[1]) { - ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)]; + ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_)); +#endif this->handle_periodic_data_(); this->buffer_pos_ = 0; // Reset position index for next frame } else if (ld2450::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) { - ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)]; + ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_)); +#endif if (this->handle_ack_data_()) { this->buffer_pos_ = 0; // Reset position index for next message } else { From 167a42aa27b7a55444cb517fb167749b84e248e8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:11:45 -1000 Subject: [PATCH 0869/1145] [api] Use StringRef in send_action_response and send_execute_service_response (#12658) --- esphome/components/api/api_connection.cpp | 8 ++++---- esphome/components/api/api_connection.h | 4 ++-- esphome/components/api/api_server.cpp | 4 ++-- esphome/components/api/api_server.h | 4 ++-- esphome/components/api/user_services.h | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 26ddb16e9a..8588651968 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1749,20 +1749,20 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) { // the action list. This ensures async actions (delays, waits) complete first. } #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES -void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message) { +void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message) { ExecuteServiceResponse resp; resp.call_id = call_id; resp.success = success; - resp.set_error_message(StringRef(error_message)); + resp.set_error_message(error_message); this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE); } #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON -void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message, +void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data, size_t response_data_len) { ExecuteServiceResponse resp; resp.call_id = call_id; resp.success = success; - resp.set_error_message(StringRef(error_message)); + resp.set_error_message(error_message); resp.response_data = response_data; resp.response_data_len = response_data_len; this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE); diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 6363116900..47609f79b6 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -233,9 +233,9 @@ class APIConnection final : public APIServerConnection { #ifdef USE_API_USER_DEFINED_ACTIONS void execute_service(const ExecuteServiceRequest &msg) override; #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES - void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message); + void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON - void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message, + void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data, size_t response_data_len); #endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #endif // USE_API_USER_DEFINED_ACTION_RESPONSES diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 23cecd2663..8b76740e4d 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -678,7 +678,7 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con } } -void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message) { +void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message) { for (auto &call : this->active_action_calls_) { if (call.action_call_id == action_call_id) { call.connection->send_execute_service_response(call.client_call_id, success, error_message); @@ -688,7 +688,7 @@ void APIServer::send_action_response(uint32_t action_call_id, bool success, cons ESP_LOGW(TAG, "Cannot send response: no active call found for action_call_id %u", action_call_id); } #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON -void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message, +void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message, const uint8_t *response_data, size_t response_data_len) { for (auto &call : this->active_action_calls_) { if (call.action_call_id == action_call_id) { diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 11d726a40a..b855d2cce0 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -165,9 +165,9 @@ class APIServer : public Component, void unregister_active_action_call(uint32_t action_call_id); void unregister_active_action_calls_for_connection(APIConnection *conn); // Send response for a specific action call (uses action_call_id, sends client_call_id in response) - void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message); + void send_action_response(uint32_t action_call_id, bool success, StringRef error_message); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON - void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message, + void send_action_response(uint32_t action_call_id, bool success, StringRef error_message, const uint8_t *response_data, size_t response_data_len); #endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #endif // USE_API_USER_DEFINED_ACTION_RESPONSES diff --git a/esphome/components/api/user_services.h b/esphome/components/api/user_services.h index 001add626f..8e3a61b279 100644 --- a/esphome/components/api/user_services.h +++ b/esphome/components/api/user_services.h @@ -255,7 +255,7 @@ template class APIRespondAction : public Action { bool return_response = std::get<1>(args); if (!return_response) { // Client doesn't want response data, just send success/error - this->parent_->send_action_response(call_id, success, error_message); + this->parent_->send_action_response(call_id, success, StringRef(error_message)); return; } } @@ -265,12 +265,12 @@ template class APIRespondAction : public Action { json::JsonBuilder builder; this->json_builder_(x..., builder.root()); std::string json_str = builder.serialize(); - this->parent_->send_action_response(call_id, success, error_message, + this->parent_->send_action_response(call_id, success, StringRef(error_message), reinterpret_cast(json_str.data()), json_str.size()); return; } #endif - this->parent_->send_action_response(call_id, success, error_message); + this->parent_->send_action_response(call_id, success, StringRef(error_message)); } protected: From 25e60d62cf549c1fe65a0829d86361932e53498b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:12:04 -1000 Subject: [PATCH 0870/1145] [mqtt] Avoid heap allocations when logging IP addresses (#12686) --- esphome/components/mqtt/mqtt_client.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index ba701b90a3..c650c99f62 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -153,13 +153,14 @@ void MQTTClientComponent::on_log(uint8_t level, const char *tag, const char *mes #endif void MQTTClientComponent::dump_config() { + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, "MQTT:\n" " Server Address: %s:%u (%s)\n" " Username: " LOG_SECRET("'%s'") "\n" " Client ID: " LOG_SECRET("'%s'") "\n" " Clean Session: %s", - this->credentials_.address.c_str(), this->credentials_.port, this->ip_.str().c_str(), + this->credentials_.address.c_str(), this->credentials_.port, this->ip_.str_to(ip_buf), this->credentials_.username.c_str(), this->credentials_.client_id.c_str(), YESNO(this->credentials_.clean_session)); if (this->is_discovery_ip_enabled()) { @@ -246,7 +247,8 @@ void MQTTClientComponent::check_dnslookup_() { return; } - ESP_LOGD(TAG, "Resolved broker IP address to %s", this->ip_.str().c_str()); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGD(TAG, "Resolved broker IP address to %s", this->ip_.str_to(ip_buf)); this->start_connect_(); } #if defined(USE_ESP8266) && LWIP_VERSION_MAJOR == 1 From 023be88a87b228eafcc79a5243b2c427d65e8d0e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:13:08 -1000 Subject: [PATCH 0871/1145] [tuya] Use stack buffers for hex logging to avoid heap allocations (#12689) --- esphome/components/tuya/tuya.cpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 12b14be9ff..2812fb6ad6 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -20,6 +20,8 @@ static const char *const TAG = "tuya"; static const int COMMAND_DELAY = 10; static const int RECEIVE_TIMEOUT = 300; static const int MAX_RETRIES = 5; +// Max bytes to log for datapoint values (larger values are truncated) +static constexpr size_t MAX_DATAPOINT_LOG_BYTES = 16; void Tuya::setup() { this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); @@ -51,7 +53,9 @@ void Tuya::dump_config() { } for (auto &info : this->datapoints_) { if (info.type == TuyaDatapointType::RAW) { - ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, format_hex_pretty(info.value_raw).c_str()); + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; + ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, + format_hex_pretty_to(hex_buf, info.value_raw.data(), info.value_raw.size())); } else if (info.type == TuyaDatapointType::BOOLEAN) { ESP_LOGCONFIG(TAG, " Datapoint %u: switch (value: %s)", info.id, ONOFF(info.value_bool)); } else if (info.type == TuyaDatapointType::INTEGER) { @@ -122,8 +126,11 @@ bool Tuya::validate_message_() { // valid message const uint8_t *message_data = data + 6; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version, - format_hex_pretty(message_data, length).c_str(), static_cast(this->init_state_)); + format_hex_pretty_to(hex_buf, message_data, length), static_cast(this->init_state_)); +#endif this->handle_command_(command, version, message_data, length); // return false to reset rx buffer @@ -349,7 +356,11 @@ void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) { switch (datapoint.type) { case TuyaDatapointType::RAW: datapoint.value_raw = std::vector(data, data + data_size); - ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str()); + { + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, + format_hex_pretty_to(hex_buf, datapoint.value_raw.data(), datapoint.value_raw.size())); + } break; case TuyaDatapointType::BOOLEAN: if (data_size != 1) { @@ -460,8 +471,12 @@ void Tuya::send_raw_command_(TuyaCommand command) { break; } +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast(command.cmd), - version, format_hex_pretty(command.payload).c_str(), static_cast(this->init_state_)); + version, format_hex_pretty_to(hex_buf, command.payload.data(), command.payload.size()), + static_cast(this->init_state_)); +#endif this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo}); if (!command.payload.empty()) @@ -675,7 +690,8 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType } void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced) { - ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str()); + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty_to(hex_buf, value.data(), value.size())); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); From e732f8469efced5214ea86ff7221982ffb2e1516 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:13:26 -1000 Subject: [PATCH 0872/1145] [udp] Avoid heap allocations when joining multicast groups (#12685) --- esphome/components/udp/udp_component.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/udp/udp_component.cpp b/esphome/components/udp/udp_component.cpp index daa6c52f98..4474efeb77 100644 --- a/esphome/components/udp/udp_component.cpp +++ b/esphome/components/udp/udp_component.cpp @@ -65,11 +65,14 @@ void UDPComponent::setup() { server.sin_port = htons(this->listen_port_); if (this->listen_address_.has_value()) { + // Only 16 bytes needed for IPv4, but use standard size for consistency + char addr_buf[network::IP_ADDRESS_BUFFER_SIZE]; + this->listen_address_.value().str_to(addr_buf); struct ip_mreq imreq = {}; imreq.imr_interface.s_addr = ESPHOME_INADDR_ANY; - inet_aton(this->listen_address_.value().str().c_str(), &imreq.imr_multiaddr); + inet_aton(addr_buf, &imreq.imr_multiaddr); server.sin_addr.s_addr = imreq.imr_multiaddr.s_addr; - ESP_LOGD(TAG, "Join multicast %s", this->listen_address_.value().str().c_str()); + ESP_LOGD(TAG, "Join multicast %s", addr_buf); err = this->listen_socket_->setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq)); if (err < 0) { ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno); From 00ab64a3c7c74df024482ada1e8ba6a4ff1395fd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:13:43 -1000 Subject: [PATCH 0873/1145] [wifi] Use wifi_ssid_to() to avoid heap allocations in automation and connection checks (#12678) --- esphome/components/wifi/automation.h | 6 ++++-- esphome/components/wifi/wifi_component.cpp | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/wifi/automation.h b/esphome/components/wifi/automation.h index 7997baff65..fb0e71bcf6 100644 --- a/esphome/components/wifi/automation.h +++ b/esphome/components/wifi/automation.h @@ -45,7 +45,8 @@ template class WiFiConfigureAction : public Action, publi if (this->connecting_) return; // If already connected to the same AP, do nothing - if (global_wifi_component->wifi_ssid() == ssid) { + char ssid_buf[SSID_BUFFER_SIZE]; + if (strcmp(global_wifi_component->wifi_ssid_to(ssid_buf), ssid.c_str()) == 0) { // Callback to notify the user that the connection was successful this->connect_trigger_->trigger(); return; @@ -94,7 +95,8 @@ template class WiFiConfigureAction : public Action, publi this->cancel_timeout("wifi-connect-timeout"); this->cancel_timeout("wifi-fallback-timeout"); this->connecting_ = false; - if (global_wifi_component->wifi_ssid() == this->new_sta_.get_ssid()) { + char ssid_buf[SSID_BUFFER_SIZE]; + if (strcmp(global_wifi_component->wifi_ssid_to(ssid_buf), this->new_sta_.get_ssid().c_str()) == 0) { // Callback to notify the user that the connection was successful this->connect_trigger_->trigger(); } else { diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 50c0938cf1..001d5f254a 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1167,7 +1167,8 @@ void WiFiComponent::check_connecting_finished() { auto status = this->wifi_sta_connect_status_(); if (status == WiFiSTAConnectStatus::CONNECTED) { - if (wifi_ssid().empty()) { + char ssid_buf[SSID_BUFFER_SIZE]; + if (wifi_ssid_to(ssid_buf)[0] == '\0') { ESP_LOGW(TAG, "Connection incomplete"); this->retry_connect(); return; From ddb6c6cfd468bf16277a2d4b8d69b38228145270 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:13:59 -1000 Subject: [PATCH 0874/1145] [captive_portal] Use stack buffer for IP address logging in DNS server (#12679) --- esphome/components/captive_portal/dns_server_esp32_idf.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/captive_portal/dns_server_esp32_idf.cpp b/esphome/components/captive_portal/dns_server_esp32_idf.cpp index 5188b2047f..5743cbd671 100644 --- a/esphome/components/captive_portal/dns_server_esp32_idf.cpp +++ b/esphome/components/captive_portal/dns_server_esp32_idf.cpp @@ -47,7 +47,10 @@ struct DNSAnswer { void DNSServer::start(const network::IPAddress &ip) { this->server_ip_ = ip; - ESP_LOGV(TAG, "Starting DNS server on %s", ip.str().c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGV(TAG, "Starting DNS server on %s", ip.str_to(ip_buf)); +#endif // Create loop-monitored UDP socket this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP); From 2ff9535f5fb91908b4a8b8b95b28c911bfd7d807 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:14:12 -1000 Subject: [PATCH 0875/1145] [esp32_improv] Use stack buffer for URL formatting to avoid heap allocation (#12682) --- .../components/esp32_improv/esp32_improv_component.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 0ad54bbb15..05a30e2941 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -403,8 +403,12 @@ void ESP32ImprovComponent::check_wifi_connection_() { #ifdef USE_WEBSERVER for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) { if (ip.is_ip4()) { - char url_buffer[64]; - snprintf(url_buffer, sizeof(url_buffer), "http://%s:%d", ip.str().c_str(), USE_WEBSERVER_PORT); + // "http://" (7) + IPv4 max (15) + ":" (1) + port max (5) + null = 29 + char url_buffer[32]; + memcpy(url_buffer, "http://", 7); // NOLINT(bugprone-not-null-terminated-result) - str_to null-terminates + ip.str_to(url_buffer + 7); + size_t len = strlen(url_buffer); + snprintf(url_buffer + len, sizeof(url_buffer) - len, ":%d", USE_WEBSERVER_PORT); url_strings[url_count++] = url_buffer; break; } From 2230e5634787af2ebb26a0978fd834a8856eac2c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:14:24 -1000 Subject: [PATCH 0876/1145] [wifi] Use stack buffers for IP address logging to avoid heap allocations (#12680) --- esphome/components/wifi/wifi_component.cpp | 17 +++++++++++++---- .../components/wifi/wifi_component_esp8266.cpp | 7 +++++-- .../components/wifi/wifi_component_esp_idf.cpp | 13 +++++++++---- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 001d5f254a..a0a7d3d946 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -655,12 +655,15 @@ void WiFiComponent::setup_ap_config_() { #ifdef USE_WIFI_MANUAL_IP auto manual_ip = this->ap_.get_manual_ip(); if (manual_ip.has_value()) { + char static_ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, " AP Static IP: '%s'\n" " AP Gateway: '%s'\n" " AP Subnet: '%s'", - manual_ip->static_ip.str().c_str(), manual_ip->gateway.str().c_str(), - manual_ip->subnet.str().c_str()); + manual_ip->static_ip.str_to(static_ip_buf), manual_ip->gateway.str_to(gateway_buf), + manual_ip->subnet.str_to(subnet_buf)); } #endif @@ -816,8 +819,14 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { #ifdef USE_WIFI_MANUAL_IP if (ap.get_manual_ip().has_value()) { ManualIP m = *ap.get_manual_ip(); - ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.str().c_str(), - m.gateway.str().c_str(), m.subnet.str().c_str(), m.dns1.str().c_str(), m.dns2.str().c_str()); + char static_ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.str_to(static_ip_buf), + m.gateway.str_to(gateway_buf), m.subnet.str_to(subnet_buf), m.dns1.str_to(dns1_buf), + m.dns2.str_to(dns2_buf)); } else #endif { diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 1c744648bb..86c8a8891b 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -810,10 +810,13 @@ bool WiFiComponent::wifi_ap_ip_config_(const optional &manual_ip) { network::IPAddress start_address = network::IPAddress(&info.ip); start_address += 99; lease.start_ip = start_address; - ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; +#endif + ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str_to(ip_buf)); start_address += 10; lease.end_ip = start_address; - ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); + ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str_to(ip_buf)); if (!wifi_softap_set_dhcps_lease(&lease)) { ESP_LOGE(TAG, "Set SoftAP DHCP lease failed"); return false; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 5d4d003d62..d9b5e7c114 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -969,10 +969,13 @@ bool WiFiComponent::wifi_ap_ip_config_(const optional &manual_ip) { network::IPAddress start_address = network::IPAddress(&info.ip); start_address += 99; lease.start_ip = start_address; - ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; +#endif + ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str_to(ip_buf)); start_address += 10; lease.end_ip = start_address; - ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); + ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str_to(ip_buf)); err = esp_netif_dhcps_option(s_ap_netif, ESP_NETIF_OP_SET, ESP_NETIF_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); if (err != ESP_OK) { @@ -984,8 +987,10 @@ bool WiFiComponent::wifi_ap_ip_config_(const optional &manual_ip) { // Configure DHCP Option 114 (Captive Portal URI) if captive portal is enabled // This provides a standards-compliant way for clients to discover the captive portal if (captive_portal::global_captive_portal != nullptr) { - static char captive_portal_uri[32]; - snprintf(captive_portal_uri, sizeof(captive_portal_uri), "http://%s", network::IPAddress(&info.ip).str().c_str()); + // Buffer must be static - dhcps_set_option_info stores pointer, doesn't copy + static char captive_portal_uri[24]; // "http://" (7) + IPv4 max (15) + null + memcpy(captive_portal_uri, "http://", 7); // NOLINT(bugprone-not-null-terminated-result) - str_to null-terminates + network::IPAddress(&info.ip).str_to(captive_portal_uri + 7); err = esp_netif_dhcps_option(s_ap_netif, ESP_NETIF_OP_SET, ESP_NETIF_CAPTIVEPORTAL_URI, captive_portal_uri, strlen(captive_portal_uri)); if (err != ESP_OK) { From 0e108c2178231f3bd76cf39ea5d4bc4dd55dc84f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:14:52 -1000 Subject: [PATCH 0877/1145] [esp32] Add minimum_chip_revision setting and log chip revision at startup (#12696) --- esphome/components/esp32/__init__.py | 39 ++++++++++++++++++++++++++++ esphome/core/application.cpp | 16 ++++++++++++ esphome/core/defines.h | 1 + 3 files changed, 56 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index d8397a87cc..da550e58dc 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -85,6 +85,7 @@ CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features" CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert" CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback" CONF_EXECUTE_FROM_PSRAM = "execute_from_psram" +CONF_MINIMUM_CHIP_REVISION = "minimum_chip_revision" CONF_RELEASE = "release" LOG_LEVELS_IDF = [ @@ -109,6 +110,21 @@ COMPILER_OPTIMIZATIONS = { "SIZE": "CONFIG_COMPILER_OPTIMIZATION_SIZE", } +# ESP32 (original) chip revision options +# Setting minimum revision to 3.0 or higher: +# - Reduces flash size by excluding workaround code for older chip bugs +# - For PSRAM users: disables CONFIG_SPIRAM_CACHE_WORKAROUND, which saves significant +# IRAM by keeping C library functions in ROM instead of recompiling them +# See: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/chip_revision.html +ESP32_CHIP_REVISIONS = { + "0.0": "CONFIG_ESP32_REV_MIN_0", + "1.0": "CONFIG_ESP32_REV_MIN_1", + "1.1": "CONFIG_ESP32_REV_MIN_1_1", + "2.0": "CONFIG_ESP32_REV_MIN_2", + "3.0": "CONFIG_ESP32_REV_MIN_3", + "3.1": "CONFIG_ESP32_REV_MIN_3_1", +} + # Socket limit configuration for ESP-IDF # ESP-IDF CONFIG_LWIP_MAX_SOCKETS has range 1-253, default 10 DEFAULT_MAX_SOCKETS = 10 # ESP-IDF default @@ -566,6 +582,16 @@ def final_validate(config): path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_IGNORE_EFUSE_MAC_CRC], ) ) + if ( + config[CONF_VARIANT] != VARIANT_ESP32 + and advanced.get(CONF_MINIMUM_CHIP_REVISION) is not None + ): + errs.append( + cv.Invalid( + f"'{CONF_MINIMUM_CHIP_REVISION}' is only supported on {VARIANT_ESP32}", + path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_MINIMUM_CHIP_REVISION], + ) + ) if advanced[CONF_EXECUTE_FROM_PSRAM]: if config[CONF_VARIANT] != VARIANT_ESP32S3: errs.append( @@ -694,6 +720,9 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean, cv.Optional(CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False): cv.boolean, cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean, + cv.Optional(CONF_MINIMUM_CHIP_REVISION): cv.one_of( + *ESP32_CHIP_REVISIONS + ), # DHCP server is needed for WiFi AP mode. When WiFi component is used, # it will handle disabling DHCP server when AP is not configured. # Default to false (disabled) when WiFi is not used. @@ -1017,6 +1046,16 @@ async def to_code(config): add_idf_sdkconfig_option( f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True ) + + # Set minimum chip revision for ESP32 variant + # Setting this to 3.0 or higher reduces flash size by excluding workaround code, + # and for PSRAM users saves significant IRAM by keeping C library functions in ROM. + if variant == VARIANT_ESP32: + min_rev = conf[CONF_ADVANCED].get(CONF_MINIMUM_CHIP_REVISION) + if min_rev is not None: + for rev, flag in ESP32_CHIP_REVISIONS.items(): + add_idf_sdkconfig_option(flag, rev == min_rev) + cg.add_define("USE_ESP32_MIN_CHIP_REVISION_SET") add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv") diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 4c9cc6b2b6..f8fa3b333e 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -7,6 +7,9 @@ #ifdef USE_ESP8266 #include #endif +#ifdef USE_ESP32 +#include +#endif #include "esphome/core/version.h" #include "esphome/core/hal.h" #include @@ -203,6 +206,19 @@ void Application::loop() { ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", build_time_str); #ifdef ESPHOME_PROJECT_NAME ESP_LOGI(TAG, "Project " ESPHOME_PROJECT_NAME " version " ESPHOME_PROJECT_VERSION); +#endif +#ifdef USE_ESP32 + esp_chip_info_t chip_info; + esp_chip_info(&chip_info); + ESP_LOGI(TAG, "ESP32 Chip: %s r%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100, + chip_info.revision % 100, chip_info.cores); +#if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_MIN_CHIP_REVISION_SET) + // Suggest optimization for chips that don't need the PSRAM cache workaround + if (chip_info.revision >= 300) { + ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to reduce binary size", chip_info.revision / 100, + chip_info.revision % 100); + } +#endif #endif } diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 579edc065a..1fddc426d4 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -166,6 +166,7 @@ #define USE_MQTT_IDF_ENQUEUE #define USE_ESPHOME_TASK_LOG_BUFFER #define USE_OTA_ROLLBACK +#define USE_ESP32_MIN_CHIP_REVISION_SET #define USE_BLUETOOTH_PROXY #define BLUETOOTH_PROXY_MAX_CONNECTIONS 3 From 0c4184b129cde19b3fd1aaf83a572628d92c8efd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:20:17 -1000 Subject: [PATCH 0878/1145] [cse7766] Use stack buffer for hex formatting in debug logging (#12732) --- esphome/components/cse7766/cse7766.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index fe81ae91fe..71fe15f0ae 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -1,11 +1,13 @@ #include "cse7766.h" -#include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" namespace esphome { namespace cse7766 { static const char *const TAG = "cse7766"; +static constexpr size_t CSE7766_RAW_DATA_SIZE = 24; void CSE7766Component::loop() { const uint32_t now = App.get_loop_component_start_time(); @@ -70,8 +72,8 @@ bool CSE7766Component::check_byte_() { void CSE7766Component::parse_data_() { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE { - std::string s = format_hex_pretty(this->raw_data_, sizeof(this->raw_data_)); - ESP_LOGVV(TAG, "Raw data: %s", s.c_str()); + char hex_buf[format_hex_pretty_size(CSE7766_RAW_DATA_SIZE)]; + ESP_LOGVV(TAG, "Raw data: %s", format_hex_pretty_to(hex_buf, this->raw_data_, sizeof(this->raw_data_))); } #endif From b711172b33a898b5c46f6eed0df19bc8f800e1da Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:21:09 -1000 Subject: [PATCH 0879/1145] [wifi] Use precision format specifier for SSID logging to avoid stack copy (#12704) --- .../wifi/wifi_component_esp8266.cpp | 19 ++++++--------- .../wifi/wifi_component_esp_idf.cpp | 24 +++++++------------ .../wifi/wifi_component_libretiny.cpp | 20 +++++++--------- 3 files changed, 24 insertions(+), 39 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 86c8a8891b..055a13afc8 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -518,15 +518,12 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { switch (event->event) { case EVENT_STAMODE_CONNECTED: { auto it = event->event_info.connected; - char buf[33]; - memcpy(buf, it.ssid, it.ssid_len); - buf[it.ssid_len] = '\0'; - ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(), - it.channel); + ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=%s channel=%u", it.ssid_len, (const char *) it.ssid, + format_mac_address_pretty(it.bssid).c_str(), it.channel); s_sta_connected = true; #ifdef USE_WIFI_LISTENERS for (auto *listener : global_wifi_component->connect_state_listeners_) { - listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid); + listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid); } // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here #ifdef USE_WIFI_MANUAL_IP @@ -543,17 +540,15 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { } case EVENT_STAMODE_DISCONNECTED: { auto it = event->event_info.disconnected; - char buf[33]; - memcpy(buf, it.ssid, it.ssid_len); - buf[it.ssid_len] = '\0'; if (it.reason == REASON_NO_AP_FOUND) { - ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); + ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len, + (const char *) it.ssid); s_sta_connect_not_found = true; } else { char bssid_s[18]; format_mac_addr_upper(it.bssid, bssid_s); - ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s, - LOG_STR_ARG(get_disconnect_reason_str(it.reason))); + ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len, + (const char *) it.ssid, bssid_s, LOG_STR_ARG(get_disconnect_reason_str(it.reason))); s_sta_connect_error = true; } s_sta_connected = false; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index d9b5e7c114..f68a095bff 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -734,16 +734,13 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_CONNECTED) { const auto &it = data->data.sta_connected; - char buf[33]; - assert(it.ssid_len <= 32); - memcpy(buf, it.ssid, it.ssid_len); - buf[it.ssid_len] = '\0'; - ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, - format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); + ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", it.ssid_len, + (const char *) it.ssid, format_mac_address_pretty(it.bssid).c_str(), it.channel, + get_auth_mode_str(it.authmode)); s_sta_connected = true; #ifdef USE_WIFI_LISTENERS for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid); + listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid); } // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here #ifdef USE_WIFI_MANUAL_IP @@ -757,21 +754,18 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) { const auto &it = data->data.sta_disconnected; - char buf[33]; - assert(it.ssid_len <= 32); - memcpy(buf, it.ssid, it.ssid_len); - buf[it.ssid_len] = '\0'; if (it.reason == WIFI_REASON_NO_AP_FOUND) { - ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); + ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len, + (const char *) it.ssid); s_sta_connect_not_found = true; } else if (it.reason == WIFI_REASON_ROAMING) { - ESP_LOGI(TAG, "Disconnected ssid='%s' reason='Station Roaming'", buf); + ESP_LOGI(TAG, "Disconnected ssid='%.*s' reason='Station Roaming'", it.ssid_len, (const char *) it.ssid); return; } else { char bssid_s[18]; format_mac_addr_upper(it.bssid, bssid_s); - ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s, - get_disconnect_reason_str(it.reason)); + ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len, + (const char *) it.ssid, bssid_s, get_disconnect_reason_str(it.reason)); s_sta_connect_error = true; } s_sta_connected = false; diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 9b8653d0db..9bbd319f33 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -296,14 +296,12 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { auto it = info.wifi_sta_connected; - char buf[33]; - memcpy(buf, it.ssid, it.ssid_len); - buf[it.ssid_len] = '\0'; - ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, - format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); + ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", it.ssid_len, + (const char *) it.ssid, format_mac_address_pretty(it.bssid).c_str(), it.channel, + get_auth_mode_str(it.authmode)); #ifdef USE_WIFI_LISTENERS for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid); + listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid); } // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here #ifdef USE_WIFI_MANUAL_IP @@ -318,9 +316,6 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: { auto it = info.wifi_sta_disconnected; - char buf[33]; - memcpy(buf, it.ssid, it.ssid_len); - buf[it.ssid_len] = '\0'; // LibreTiny can send spurious disconnect events with empty ssid/bssid during connection. // These are typically "Association Leave" events that don't indicate actual failures: @@ -339,12 +334,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } if (it.reason == WIFI_REASON_NO_AP_FOUND) { - ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); + ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len, + (const char *) it.ssid); } else { char bssid_s[18]; format_mac_addr_upper(it.bssid, bssid_s); - ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s, - get_disconnect_reason_str(it.reason)); + ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len, + (const char *) it.ssid, bssid_s, get_disconnect_reason_str(it.reason)); } uint8_t reason = it.reason; From 20b66cba238b03549aacf5c897e7d42ca9ce7b13 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:21:23 -1000 Subject: [PATCH 0880/1145] [shelly_dimmer] Use stack buffer for hex formatting in command logging (#12721) --- esphome/components/shelly_dimmer/shelly_dimmer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/shelly_dimmer/shelly_dimmer.cpp b/esphome/components/shelly_dimmer/shelly_dimmer.cpp index b336bbcb65..3b5307805e 100644 --- a/esphome/components/shelly_dimmer/shelly_dimmer.cpp +++ b/esphome/components/shelly_dimmer/shelly_dimmer.cpp @@ -270,7 +270,10 @@ void ShellyDimmer::send_settings_() { } bool ShellyDimmer::send_command_(uint8_t cmd, const uint8_t *const payload, uint8_t len) { - ESP_LOGD(TAG, "Sending command: 0x%02x (%d bytes) payload 0x%s", cmd, len, format_hex(payload, len).c_str()); + // Buffer for hex formatting: max frame size * 2 + null (covers any payload) + char hex_buf[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE * 2 + 1]; + ESP_LOGD(TAG, "Sending command: 0x%02x (%d bytes) payload 0x%s", cmd, len, + format_hex_to(hex_buf, sizeof(hex_buf), payload, len)); // Prepare a command frame. uint8_t frame[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE]; From 9ccb100cca3afd8c32d7f0d62d71b5cc83425a05 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:21:42 -1000 Subject: [PATCH 0881/1145] [remote_base] Use stack buffer for hex formatting in mirage protocol logging (#12722) --- esphome/components/remote_base/mirage_protocol.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/esphome/components/remote_base/mirage_protocol.cpp b/esphome/components/remote_base/mirage_protocol.cpp index 10d644a1cd..2ae877f193 100644 --- a/esphome/components/remote_base/mirage_protocol.cpp +++ b/esphome/components/remote_base/mirage_protocol.cpp @@ -1,4 +1,5 @@ #include "mirage_protocol.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -13,9 +14,12 @@ constexpr uint32_t BIT_ONE_SPACE_US = 1592; constexpr uint32_t BIT_ZERO_SPACE_US = 545; constexpr unsigned int MIRAGE_IR_PACKET_BIT_SIZE = 120; +// Max data bytes in packet (excluding checksum) +constexpr size_t MIRAGE_MAX_DATA_BYTES = (MIRAGE_IR_PACKET_BIT_SIZE / 8); void MirageProtocol::encode(RemoteTransmitData *dst, const MirageData &data) { - ESP_LOGI(TAG, "Transive Mirage: %s", format_hex_pretty(data.data).c_str()); + char hex_buf[format_hex_pretty_size(MIRAGE_MAX_DATA_BYTES)]; + ESP_LOGI(TAG, "Transmit Mirage: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size())); dst->set_carrier_frequency(38000); dst->reserve(5 + ((data.data.size() + 1) * 2)); dst->mark(HEADER_MARK_US); @@ -77,7 +81,8 @@ optional MirageProtocol::decode(RemoteReceiveData src) { } void MirageProtocol::dump(const MirageData &data) { - ESP_LOGI(TAG, "Received Mirage: %s", format_hex_pretty(data.data).c_str()); + char hex_buf[format_hex_pretty_size(MIRAGE_MAX_DATA_BYTES)]; + ESP_LOGI(TAG, "Received Mirage: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size())); } } // namespace remote_base From f9b4e0e489c7c022258c185d7011e3c6949c8e0c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:22:26 -1000 Subject: [PATCH 0882/1145] [remote_base] Use stack buffer for hex formatting in haier protocol logging (#12723) --- esphome/components/remote_base/haier_protocol.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/remote_base/haier_protocol.cpp b/esphome/components/remote_base/haier_protocol.cpp index ec5cb5775c..734f3c7789 100644 --- a/esphome/components/remote_base/haier_protocol.cpp +++ b/esphome/components/remote_base/haier_protocol.cpp @@ -1,4 +1,5 @@ #include "haier_protocol.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -12,6 +13,8 @@ constexpr uint32_t BIT_MARK_US = 540; constexpr uint32_t BIT_ONE_SPACE_US = 1650; constexpr uint32_t BIT_ZERO_SPACE_US = 580; constexpr unsigned int HAIER_IR_PACKET_BIT_SIZE = 112; +// Max data bytes in packet (excluding checksum) +constexpr size_t HAIER_MAX_DATA_BYTES = (HAIER_IR_PACKET_BIT_SIZE / 8); void HaierProtocol::encode_byte_(RemoteTransmitData *dst, uint8_t item) { for (uint8_t mask = 1 << 7; mask != 0; mask >>= 1) { @@ -77,7 +80,8 @@ optional HaierProtocol::decode(RemoteReceiveData src) { } void HaierProtocol::dump(const HaierData &data) { - ESP_LOGI(TAG, "Received Haier: %s", format_hex_pretty(data.data).c_str()); + char hex_buf[format_hex_pretty_size(HAIER_MAX_DATA_BYTES)]; + ESP_LOGI(TAG, "Received Haier: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size())); } } // namespace remote_base From b4e5e0bc9b6f28b81c1892f07a5192d20596c9d8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:22:58 -1000 Subject: [PATCH 0883/1145] [rc522] Use stack buffers for hex formatting in tag logging (#12725) --- esphome/components/rc522/rc522.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index fa8564f614..8f8740c925 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -12,6 +12,9 @@ static const uint8_t WAIT_I_RQ = 0x30; // RxIRq and IdleIRq static const char *const TAG = "rc522"; +// Max UID size for RFID tags (4, 7, or 10 bytes) +static constexpr size_t RC522_MAX_UID_SIZE = 10; + static const uint8_t RESET_COUNT = 5; void RC522::setup() { @@ -191,8 +194,9 @@ void RC522::loop() { if (status == STATUS_TIMEOUT) { ESP_LOGV(TAG, "STATE_READ_SERIAL_DONE -> TIMEOUT (no tag present) %d", status); } else { + char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)]; ESP_LOGW(TAG, "Unexpected response. Read status is %d. Read bytes: %d (%s)", status, back_length_, - format_hex_pretty(buffer_, back_length_, '-', false).c_str()); + format_hex_pretty_to(hex_buf, buffer_, back_length_, '-')); } state_ = STATE_DONE; @@ -237,13 +241,18 @@ void RC522::loop() { trigger->process(rfid_uid); if (report) { - ESP_LOGD(TAG, "Found new tag '%s'", format_hex_pretty(rfid_uid, '-', false).c_str()); + char uid_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)]; + ESP_LOGD(TAG, "Found new tag '%s'", format_hex_pretty_to(uid_buf, rfid_uid.data(), rfid_uid.size(), '-')); } break; } case STATE_DONE: { if (!this->current_uid_.empty()) { - ESP_LOGV(TAG, "Tag '%s' removed", format_hex_pretty(this->current_uid_, '-', false).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uid_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)]; + ESP_LOGV(TAG, "Tag '%s' removed", + format_hex_pretty_to(uid_buf, this->current_uid_.data(), this->current_uid_.size(), '-')); +#endif for (auto *trigger : this->triggers_ontagremoved_) trigger->process(this->current_uid_); } @@ -338,7 +347,10 @@ void RC522::pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to * @return STATUS_OK on success, STATUS_??? otherwise. */ void RC522::pcd_transceive_data_(uint8_t send_len) { - ESP_LOGV(TAG, "PCD TRANSCEIVE: RX: %s", format_hex_pretty(buffer_, send_len, '-', false).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)]; + ESP_LOGV(TAG, "PCD TRANSCEIVE: RX: %s", format_hex_pretty_to(hex_buf, buffer_, send_len, '-')); +#endif delayMicroseconds(1000); // we need 1 ms delay between antenna on and those communication commands send_len_ = send_len; // Prepare values for BitFramingReg @@ -412,8 +424,11 @@ RC522::StatusCode RC522::await_transceive_() { error_reg_value); // TODO: is this always due to collissions? return STATUS_ERROR; } +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)]; ESP_LOGV(TAG, "received %d bytes: %s", back_length_, - format_hex_pretty(buffer_ + send_len_, back_length_, '-', false).c_str()); + format_hex_pretty_to(hex_buf, buffer_ + send_len_, back_length_, '-')); +#endif return STATUS_OK; } From 61b6476de4491406d07793e797b98a1b6bcf919e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:23:18 -1000 Subject: [PATCH 0884/1145] [opentherm] Replace heap-allocating format calls with printf format specifiers in debug_error (#12726) --- esphome/components/opentherm/opentherm.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index d59b9584d1..750ef08b33 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -7,6 +7,7 @@ #include "opentherm.h" #include "esphome/core/helpers.h" +#include #ifdef USE_ESP32 #include "driver/timer.h" #include "esp_err.h" @@ -569,8 +570,8 @@ void OpenTherm::debug_data(OpenthermData &data) { to_string(data.f88()).c_str()); } void OpenTherm::debug_error(OpenThermError &error) const { - ESP_LOGD(TAG, "data: %s; clock: %s; capture: %s; bit_pos: %s", format_hex(error.data).c_str(), - to_string(clock_).c_str(), format_bin(error.capture).c_str(), to_string(error.bit_pos).c_str()); + ESP_LOGD(TAG, "data: 0x%08" PRIx32 "; clock: %u; capture: 0x%08" PRIx32 "; bit_pos: %u", error.data, this->clock_, + error.capture, error.bit_pos); } float OpenthermData::f88() { return ((float) this->s16()) / 256.0; } From 7fa04b6c25e75c995406ad66d6e5653ec8ec69f7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:23:33 -1000 Subject: [PATCH 0885/1145] [a01nyub] Use stack buffer for hex formatting in error logging (#12727) --- esphome/components/a01nyub/a01nyub.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/a01nyub/a01nyub.cpp b/esphome/components/a01nyub/a01nyub.cpp index d0bc89a0c9..210c3557b3 100644 --- a/esphome/components/a01nyub/a01nyub.cpp +++ b/esphome/components/a01nyub/a01nyub.cpp @@ -30,7 +30,9 @@ void A01nyubComponent::check_buffer_() { ESP_LOGV(TAG, "Distance from sensor: %f mm, %f m", distance, meters); this->publish_state(meters); } else { - ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str()); + char hex_buf[format_hex_pretty_size(4)]; + ESP_LOGW(TAG, "Invalid data read from sensor: %s", + format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size())); } } else { ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]); From 17033436947c4b72532311409793556a5fd6886d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:24:30 -1000 Subject: [PATCH 0886/1145] [a02yyuw] Use stack buffer for hex formatting in error logging (#12728) --- esphome/components/a02yyuw/a02yyuw.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/a02yyuw/a02yyuw.cpp b/esphome/components/a02yyuw/a02yyuw.cpp index ee378c3283..a2aad0cef1 100644 --- a/esphome/components/a02yyuw/a02yyuw.cpp +++ b/esphome/components/a02yyuw/a02yyuw.cpp @@ -29,7 +29,9 @@ void A02yyuwComponent::check_buffer_() { ESP_LOGV(TAG, "Distance from sensor: %f mm", distance); this->publish_state(distance); } else { - ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str()); + char hex_buf[format_hex_pretty_size(4)]; + ESP_LOGW(TAG, "Invalid data read from sensor: %s", + format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size())); } } else { ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]); From 30efd7fb0732a947ad93b4822f7841b205f0fe6e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:24:47 -1000 Subject: [PATCH 0887/1145] [jsn_sr04t] Use stack buffer for hex formatting in error logging (#12729) --- esphome/components/jsn_sr04t/jsn_sr04t.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/jsn_sr04t/jsn_sr04t.cpp b/esphome/components/jsn_sr04t/jsn_sr04t.cpp index 84181dac48..6fd8b1bd65 100644 --- a/esphome/components/jsn_sr04t/jsn_sr04t.cpp +++ b/esphome/components/jsn_sr04t/jsn_sr04t.cpp @@ -39,7 +39,9 @@ void Jsnsr04tComponent::check_buffer_() { ESP_LOGV(TAG, "Distance from sensor: %umm, %.3fm", distance, meters); this->publish_state(meters); } else { - ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str()); + char hex_buf[format_hex_pretty_size(4)]; + ESP_LOGW(TAG, "Invalid data read from sensor: %s", + format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size())); } } else { ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]); From c8241b01223eb320fed49ef0fe2f817fe406317a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:25:02 -1000 Subject: [PATCH 0888/1145] [sonoff_d1] Use stack buffer for hex formatting in logging (#12730) --- esphome/components/sonoff_d1/sonoff_d1.cpp | 23 ++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/esphome/components/sonoff_d1/sonoff_d1.cpp b/esphome/components/sonoff_d1/sonoff_d1.cpp index cd09f31dd7..0ecde83b8b 100644 --- a/esphome/components/sonoff_d1/sonoff_d1.cpp +++ b/esphome/components/sonoff_d1/sonoff_d1.cpp @@ -42,12 +42,17 @@ * M 6C - CRC over bytes 2 to F (Addition) \*********************************************************************************************/ #include "sonoff_d1.h" +#include "esphome/core/helpers.h" namespace esphome { namespace sonoff_d1 { static const char *const TAG = "sonoff_d1"; +// Protocol constants +static constexpr size_t SONOFF_D1_ACK_SIZE = 7; +static constexpr size_t SONOFF_D1_MAX_CMD_SIZE = 17; + uint8_t SonoffD1Output::calc_checksum_(const uint8_t *cmd, const size_t len) { uint8_t crc = 0; for (size_t i = 2; i < len - 1; i++) { @@ -86,8 +91,11 @@ bool SonoffD1Output::read_command_(uint8_t *cmd, size_t &len) { // Read a minimal packet if (this->read_array(cmd, 6)) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(6)]; ESP_LOGV(TAG, "[%04d] Reading from dimmer:", this->write_count_); - ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, 6).c_str()); + ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf, cmd, 6)); +#endif if (cmd[0] != 0xAA || cmd[1] != 0x55) { ESP_LOGW(TAG, "[%04d] RX: wrong header (%x%x, must be AA55)", this->write_count_, cmd[0], cmd[1]); @@ -101,7 +109,10 @@ bool SonoffD1Output::read_command_(uint8_t *cmd, size_t &len) { return false; } if (this->read_array(&cmd[6], cmd[5] + 1 /*checksum suffix*/)) { - ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(&cmd[6], cmd[5] + 1).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf2[format_hex_pretty_size(SONOFF_D1_MAX_CMD_SIZE)]; + ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf2, &cmd[6], cmd[5] + 1)); +#endif // Check the checksum uint8_t valid_checksum = this->calc_checksum_(cmd, cmd[5] + 7); @@ -145,9 +156,10 @@ bool SonoffD1Output::read_ack_(const uint8_t *cmd, const size_t len) { ESP_LOGD(TAG, "[%04d] Acknowledge received", this->write_count_); return true; } else { + char hex_buf[format_hex_pretty_size(SONOFF_D1_ACK_SIZE)]; ESP_LOGW(TAG, "[%04d] Unexpected acknowledge received (possible clash of RF/HA commands), expected ack was:", this->write_count_); - ESP_LOGW(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(ref_buffer, sizeof(ref_buffer)).c_str()); + ESP_LOGW(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf, ref_buffer, sizeof(ref_buffer))); } return false; } @@ -174,8 +186,11 @@ bool SonoffD1Output::write_command_(uint8_t *cmd, const size_t len, bool needs_a // 2. UART command initiated by this component can clash with a command initiated by RF uint32_t retries = 10; do { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(SONOFF_D1_MAX_CMD_SIZE)]; ESP_LOGV(TAG, "[%04d] Writing to the dimmer:", this->write_count_); - ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, len).c_str()); + ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf, cmd, len)); +#endif this->write_array(cmd, len); this->write_count_++; if (!needs_ack) From 56ed5af27df7119adf0da80c18d4ea24be76a4b3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:26:14 -1000 Subject: [PATCH 0889/1145] [nextion] Use stack buffers for hex formatting in upload logging (#12733) --- .../nextion/nextion_upload_arduino.cpp | 17 ++++++++++++----- .../components/nextion/nextion_upload_esp32.cpp | 17 ++++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/esphome/components/nextion/nextion_upload_arduino.cpp b/esphome/components/nextion/nextion_upload_arduino.cpp index dfbb5a497e..d210bad004 100644 --- a/esphome/components/nextion/nextion_upload_arduino.cpp +++ b/esphome/components/nextion/nextion_upload_arduino.cpp @@ -7,12 +7,14 @@ #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/util.h" namespace esphome { namespace nextion { static const char *const TAG = "nextion.upload.arduino"; +static constexpr size_t NEXTION_MAX_RESPONSE_LOG_BYTES = 16; // Followed guide // https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 @@ -89,8 +91,10 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) { EspClass::getFreeHeap()); upload_first_chunk_sent_ = true; if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request - ESP_LOGD(TAG, "Recv: [%s]", - format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)]; + ESP_LOGD( + TAG, "Recv: [%s]", + format_hex_pretty_to(hex_buf, reinterpret_cast(recv_string.data()), recv_string.size())); uint32_t result = 0; for (int j = 0; j < 4; ++j) { result += static_cast(recv_string[j + 1]) << (8 * j); @@ -107,8 +111,10 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) { buffer = nullptr; return range_end + 1; } else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) { // 0x05 == "ok" - ESP_LOGE(TAG, "Invalid response: [%s]", - format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)]; + ESP_LOGE( + TAG, "Invalid response: [%s]", + format_hex_pretty_to(hex_buf, reinterpret_cast(recv_string.data()), recv_string.size())); // Deallocate buffer allocator.deallocate(buffer, 4096); buffer = nullptr; @@ -274,8 +280,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { this->recv_ret_string_(response, 5000, true); // This can take some time to return // The Nextion display will, if it's ready to accept data, send a 0x05 byte. + char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)]; ESP_LOGD(TAG, "Upload resp: [%s] %zu B", - format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str(), + format_hex_pretty_to(hex_buf, reinterpret_cast(response.data()), response.size()), response.length()); ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap()); diff --git a/esphome/components/nextion/nextion_upload_esp32.cpp b/esphome/components/nextion/nextion_upload_esp32.cpp index 29a7e3c8d7..712fa8e78e 100644 --- a/esphome/components/nextion/nextion_upload_esp32.cpp +++ b/esphome/components/nextion/nextion_upload_esp32.cpp @@ -9,12 +9,14 @@ #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/util.h" namespace esphome { namespace nextion { static const char *const TAG = "nextion.upload.esp32"; +static constexpr size_t NEXTION_MAX_RESPONSE_LOG_BYTES = 16; // Followed guide // https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 @@ -110,8 +112,10 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r #endif upload_first_chunk_sent_ = true; if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request - ESP_LOGD(TAG, "Recv: [%s]", - format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)]; + ESP_LOGD( + TAG, "Recv: [%s]", + format_hex_pretty_to(hex_buf, reinterpret_cast(recv_string.data()), recv_string.size())); uint32_t result = 0; for (int j = 0; j < 4; ++j) { result += static_cast(recv_string[j + 1]) << (8 * j); @@ -128,8 +132,10 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r buffer = nullptr; return range_end + 1; } else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) { // 0x05 == "ok" - ESP_LOGE(TAG, "Invalid response: [%s]", - format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)]; + ESP_LOGE( + TAG, "Invalid response: [%s]", + format_hex_pretty_to(hex_buf, reinterpret_cast(recv_string.data()), recv_string.size())); // Deallocate buffer allocator.deallocate(buffer, 4096); buffer = nullptr; @@ -287,8 +293,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { this->recv_ret_string_(response, 5000, true); // This can take some time to return // The Nextion display will, if it's ready to accept data, send a 0x05 byte. + char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)]; ESP_LOGD(TAG, "Upload resp: [%s] %zu B", - format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str(), + format_hex_pretty_to(hex_buf, reinterpret_cast(response.data()), response.size()), response.length()); ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size()); From 7d21411ca43744bad02365ea9235553fe0458bd0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:27:00 -1000 Subject: [PATCH 0890/1145] [epaper_spi] Use stack buffer for hex formatting in command logging (#12734) --- esphome/components/epaper_spi/epaper_spi.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/epaper_spi/epaper_spi.cpp b/esphome/components/epaper_spi/epaper_spi.cpp index b2e58694c8..4e6b4a7fd6 100644 --- a/esphome/components/epaper_spi/epaper_spi.cpp +++ b/esphome/components/epaper_spi/epaper_spi.cpp @@ -7,6 +7,7 @@ namespace esphome::epaper_spi { static const char *const TAG = "epaper_spi"; +static constexpr size_t EPAPER_MAX_CMD_LOG_BYTES = 128; static constexpr const char *const EPAPER_STATE_STRINGS[] = { "IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE", @@ -68,8 +69,11 @@ void EPaperBase::data(uint8_t value) { // The command is the first byte, length is the length of data only in the second byte, followed by the data. // [COMMAND, LENGTH, DATA...] void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(EPAPER_MAX_CMD_LOG_BYTES)]; ESP_LOGV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length, - format_hex_pretty(ptr, length, '.', false).c_str()); + format_hex_pretty_to(hex_buf, ptr, length, '.')); +#endif this->dc_pin_->digital_write(false); this->enable(); From e2f45c590e5e2b833e8e4acb061f1ff99dd49879 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:28:38 -1000 Subject: [PATCH 0891/1145] [esp32_improv] Use stack buffer for hex formatting in verbose logging (#12737) --- .../components/esp32_improv/esp32_improv_component.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 05a30e2941..4a6aec1892 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -4,6 +4,7 @@ #include "esphome/components/esp32_ble/ble.h" #include "esphome/components/esp32_ble_server/ble_2902.h" #include "esphome/core/application.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -14,6 +15,7 @@ namespace esp32_improv { using namespace bytebuffer; static const char *const TAG = "esp32_improv.component"; +static constexpr size_t IMPROV_MAX_LOG_BYTES = 128; static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; static constexpr uint16_t STOP_ADVERTISING_DELAY = 10000; // Delay (ms) before stopping service to allow BLE clients to read the final state @@ -314,7 +316,11 @@ void ESP32ImprovComponent::dump_config() { void ESP32ImprovComponent::process_incoming_data_() { uint8_t length = this->incoming_data_[1]; - ESP_LOGV(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(IMPROV_MAX_LOG_BYTES)]; + ESP_LOGV(TAG, "Processing bytes - %s", + format_hex_pretty_to(hex_buf, this->incoming_data_.data(), this->incoming_data_.size())); +#endif if (this->incoming_data_.size() - 3 == length) { this->set_error_(improv::ERROR_NONE); improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_); From 916370a943a1ff7d4a65b6f385497dd85a863033 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 15:42:56 -1000 Subject: [PATCH 0892/1145] [gpio] Avoid heap allocation in dump_summary (#12760) --- esphome/components/ch422g/ch422g.cpp | 4 +- esphome/components/ch422g/ch422g.h | 2 +- esphome/components/esp32/gpio.cpp | 6 +- esphome/components/esp32/gpio.h | 2 +- esphome/components/esp8266/gpio.cpp | 6 +- esphome/components/esp8266/gpio.h | 2 +- esphome/components/hlw8012/hlw8012.cpp | 6 +- esphome/components/host/gpio.cpp | 6 +- esphome/components/host/gpio.h | 2 +- esphome/components/libretiny/gpio_arduino.cpp | 6 +- esphome/components/libretiny/gpio_arduino.h | 2 +- esphome/components/max6956/max6956.cpp | 6 +- esphome/components/max6956/max6956.h | 2 +- esphome/components/mcp23016/mcp23016.cpp | 6 +- esphome/components/mcp23016/mcp23016.h | 2 +- .../mcp23xxx_base/mcp23xxx_base.cpp | 4 +- .../components/mcp23xxx_base/mcp23xxx_base.h | 2 +- esphome/components/mpr121/mpr121.cpp | 6 +- esphome/components/mpr121/mpr121.h | 2 +- esphome/components/pca6416a/pca6416a.cpp | 6 +- esphome/components/pca6416a/pca6416a.h | 2 +- esphome/components/pca9554/pca9554.cpp | 6 +- esphome/components/pca9554/pca9554.h | 2 +- esphome/components/pcf8574/pcf8574.cpp | 6 +- esphome/components/pcf8574/pcf8574.h | 2 +- .../components/pi4ioe5v6408/pi4ioe5v6408.cpp | 4 +- .../components/pi4ioe5v6408/pi4ioe5v6408.h | 2 +- esphome/components/rp2040/gpio.cpp | 6 +- esphome/components/rp2040/gpio.h | 2 +- esphome/components/sn74hc165/sn74hc165.cpp | 4 +- esphome/components/sn74hc165/sn74hc165.h | 2 +- esphome/components/sn74hc595/sn74hc595.cpp | 4 +- esphome/components/sn74hc595/sn74hc595.h | 2 +- esphome/components/spi/spi.cpp | 6 +- esphome/components/spi/spi.h | 6 +- esphome/components/sx1509/sx1509_gpio_pin.cpp | 6 +- esphome/components/sx1509/sx1509_gpio_pin.h | 2 +- esphome/components/tca9555/tca9555.cpp | 4 +- esphome/components/tca9555/tca9555.h | 2 +- .../waveshare_epaper/waveshare_213v3.cpp | 8 +-- esphome/components/weikai/weikai.cpp | 6 +- esphome/components/weikai/weikai.h | 2 +- esphome/components/xl9535/xl9535.cpp | 4 +- esphome/components/xl9535/xl9535.h | 2 +- esphome/components/zephyr/gpio.cpp | 6 +- esphome/components/zephyr/gpio.h | 2 +- esphome/core/gpio.cpp | 24 +++++++ esphome/core/gpio.h | 66 +++++++++++++++++-- 48 files changed, 168 insertions(+), 102 deletions(-) create mode 100644 esphome/core/gpio.cpp diff --git a/esphome/components/ch422g/ch422g.cpp b/esphome/components/ch422g/ch422g.cpp index 9a4e342525..f47b67da6f 100644 --- a/esphome/components/ch422g/ch422g.cpp +++ b/esphome/components/ch422g/ch422g.cpp @@ -128,7 +128,9 @@ void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this-> 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_); } +size_t CH422GGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "EXIO%u via CH422G", this->pin_); +} void CH422GGPIOPin::set_flags(gpio::Flags flags) { flags_ = flags; this->parent_->pin_mode(this->pin_, flags); diff --git a/esphome/components/ch422g/ch422g.h b/esphome/components/ch422g/ch422g.h index 1193a3db27..8ed63db90a 100644 --- a/esphome/components/ch422g/ch422g.h +++ b/esphome/components/ch422g/ch422g.h @@ -50,7 +50,7 @@ class CH422GGPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(CH422GComponent *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/esp32/gpio.cpp b/esphome/components/esp32/gpio.cpp index a98245b889..4b53d3a172 100644 --- a/esphome/components/esp32/gpio.cpp +++ b/esphome/components/esp32/gpio.cpp @@ -97,10 +97,8 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi gpio_isr_handler_add(this->get_pin_num(), func, arg); } -std::string ESP32InternalGPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast(this->pin_)); - return buffer; +size_t ESP32InternalGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "GPIO%" PRIu32, static_cast(this->pin_)); } void ESP32InternalGPIOPin::setup() { diff --git a/esphome/components/esp32/gpio.h b/esphome/components/esp32/gpio.h index d30f4bdcba..3c13bd9b4f 100644 --- a/esphome/components/esp32/gpio.h +++ b/esphome/components/esp32/gpio.h @@ -24,7 +24,7 @@ class ESP32InternalGPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return this->pin_; } diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 124df39ce3..7a5ee08984 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -98,10 +98,8 @@ void ESP8266GPIOPin::pin_mode(gpio::Flags flags) { pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT } -std::string ESP8266GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); - return buffer; +size_t ESP8266GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "GPIO%u", this->pin_); } bool ESP8266GPIOPin::digital_read() { diff --git a/esphome/components/esp8266/gpio.h b/esphome/components/esp8266/gpio.h index 213a5c54be..ff149abfbe 100644 --- a/esphome/components/esp8266/gpio.h +++ b/esphome/components/esp8266/gpio.h @@ -17,7 +17,7 @@ class ESP8266GPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return pin_; } diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index 73696bd2a5..70a05e4f72 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -34,9 +34,9 @@ void HLW8012Component::setup() { } void HLW8012Component::dump_config() { ESP_LOGCONFIG(TAG, "HLW8012:"); - LOG_PIN(" SEL Pin: ", this->sel_pin_) - LOG_PIN(" CF Pin: ", this->cf_pin_) - LOG_PIN(" CF1 Pin: ", this->cf1_pin_) + LOG_PIN(" SEL Pin: ", this->sel_pin_); + LOG_PIN(" CF Pin: ", this->cf_pin_); + LOG_PIN(" CF1 Pin: ", this->cf1_pin_); ESP_LOGCONFIG(TAG, " Change measurement mode every %" PRIu32 "\n" " Current resistor: %.1f mΩ\n" diff --git a/esphome/components/host/gpio.cpp b/esphome/components/host/gpio.cpp index e46f158513..f99b82bcc2 100644 --- a/esphome/components/host/gpio.cpp +++ b/esphome/components/host/gpio.cpp @@ -25,11 +25,7 @@ void HostGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Interr } void HostGPIOPin::pin_mode(gpio::Flags flags) { ESP_LOGD(TAG, "Setting pin %d mode to %02X", pin_, (uint32_t) flags); } -std::string HostGPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); - return buffer; -} +size_t HostGPIOPin::dump_summary(char *buffer, size_t len) const { return snprintf(buffer, len, "GPIO%u", this->pin_); } bool HostGPIOPin::digital_read() { return inverted_; } void HostGPIOPin::digital_write(bool value) { diff --git a/esphome/components/host/gpio.h b/esphome/components/host/gpio.h index ae677291b9..ea6b13f436 100644 --- a/esphome/components/host/gpio.h +++ b/esphome/components/host/gpio.h @@ -17,7 +17,7 @@ class HostGPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return pin_; } diff --git a/esphome/components/libretiny/gpio_arduino.cpp b/esphome/components/libretiny/gpio_arduino.cpp index 7a1e014ea4..0b14c77cf2 100644 --- a/esphome/components/libretiny/gpio_arduino.cpp +++ b/esphome/components/libretiny/gpio_arduino.cpp @@ -63,10 +63,8 @@ void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) { pinMode(pin_, flags_to_mode(flags)); // NOLINT } -std::string ArduinoInternalGPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u", pin_); - return buffer; +size_t ArduinoInternalGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u", this->pin_); } bool ArduinoInternalGPIOPin::digital_read() { diff --git a/esphome/components/libretiny/gpio_arduino.h b/esphome/components/libretiny/gpio_arduino.h index 3674748c18..30c7c33869 100644 --- a/esphome/components/libretiny/gpio_arduino.h +++ b/esphome/components/libretiny/gpio_arduino.h @@ -16,7 +16,7 @@ class ArduinoInternalGPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return pin_; } diff --git a/esphome/components/max6956/max6956.cpp b/esphome/components/max6956/max6956.cpp index a377a1a192..13fe5a5323 100644 --- a/esphome/components/max6956/max6956.cpp +++ b/esphome/components/max6956/max6956.cpp @@ -161,10 +161,8 @@ void MAX6956GPIOPin::setup() { pin_mode(flags_); } void MAX6956GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool MAX6956GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void MAX6956GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string MAX6956GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via Max6956", pin_); - return buffer; +size_t MAX6956GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via Max6956", this->pin_); } } // namespace max6956 diff --git a/esphome/components/max6956/max6956.h b/esphome/components/max6956/max6956.h index 0a1fd5e4b5..0c609b0b43 100644 --- a/esphome/components/max6956/max6956.h +++ b/esphome/components/max6956/max6956.h @@ -76,7 +76,7 @@ class MAX6956GPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(MAX6956 *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/mcp23016/mcp23016.cpp b/esphome/components/mcp23016/mcp23016.cpp index be86cb2256..87c2668962 100644 --- a/esphome/components/mcp23016/mcp23016.cpp +++ b/esphome/components/mcp23016/mcp23016.cpp @@ -99,10 +99,8 @@ void MCP23016GPIOPin::setup() { pin_mode(flags_); } void MCP23016GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool MCP23016GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void MCP23016GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string MCP23016GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via MCP23016", pin_); - return buffer; +size_t MCP23016GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via MCP23016", this->pin_); } } // namespace mcp23016 diff --git a/esphome/components/mcp23016/mcp23016.h b/esphome/components/mcp23016/mcp23016.h index 781c207de0..c2bc885c95 100644 --- a/esphome/components/mcp23016/mcp23016.h +++ b/esphome/components/mcp23016/mcp23016.h @@ -60,7 +60,7 @@ class MCP23016GPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(MCP23016 *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp index 81324e794f..302f6b8280 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp @@ -16,8 +16,8 @@ template bool MCP23XXXGPIOPin::digital_read() { template void MCP23XXXGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -template std::string MCP23XXXGPIOPin::dump_summary() const { - return str_snprintf("%u via MCP23XXX", 15, pin_); +template size_t MCP23XXXGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via MCP23XXX", this->pin_); } template class MCP23XXXGPIOPin<8>; diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.h b/esphome/components/mcp23xxx_base/mcp23xxx_base.h index cf0ef5d41c..fb992466d5 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.h +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.h @@ -36,7 +36,7 @@ template class MCP23XXXGPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(MCP23XXXBase *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/mpr121/mpr121.cpp b/esphome/components/mpr121/mpr121.cpp index 5a8a8e7205..4b358e384c 100644 --- a/esphome/components/mpr121/mpr121.cpp +++ b/esphome/components/mpr121/mpr121.cpp @@ -153,10 +153,8 @@ void MPR121GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_ - 4, value != this->inverted_); } -std::string MPR121GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "ELE%u on MPR121", this->pin_); - return buffer; +size_t MPR121GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "ELE%u on MPR121", this->pin_); } } // namespace mpr121 diff --git a/esphome/components/mpr121/mpr121.h b/esphome/components/mpr121/mpr121.h index 6dd2c38309..085018fff0 100644 --- a/esphome/components/mpr121/mpr121.h +++ b/esphome/components/mpr121/mpr121.h @@ -109,7 +109,7 @@ class MPR121GPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(MPR121Component *parent) { this->parent_ = parent; } void set_pin(uint8_t pin) { this->pin_ = pin; } diff --git a/esphome/components/pca6416a/pca6416a.cpp b/esphome/components/pca6416a/pca6416a.cpp index c0056e780b..909bac5f05 100644 --- a/esphome/components/pca6416a/pca6416a.cpp +++ b/esphome/components/pca6416a/pca6416a.cpp @@ -180,10 +180,8 @@ void PCA6416AGPIOPin::setup() { pin_mode(flags_); } void PCA6416AGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool PCA6416AGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void PCA6416AGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string PCA6416AGPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via PCA6416A", pin_); - return buffer; +size_t PCA6416AGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via PCA6416A", this->pin_); } } // namespace pca6416a diff --git a/esphome/components/pca6416a/pca6416a.h b/esphome/components/pca6416a/pca6416a.h index 10a4a64e9b..138a51cc20 100644 --- a/esphome/components/pca6416a/pca6416a.h +++ b/esphome/components/pca6416a/pca6416a.h @@ -52,7 +52,7 @@ class PCA6416AGPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(PCA6416AComponent *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/pca9554/pca9554.cpp b/esphome/components/pca9554/pca9554.cpp index e8d49f66e2..a6f9c2396c 100644 --- a/esphome/components/pca9554/pca9554.cpp +++ b/esphome/components/pca9554/pca9554.cpp @@ -129,10 +129,8 @@ void PCA9554GPIOPin::setup() { pin_mode(flags_); } void PCA9554GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool PCA9554GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void PCA9554GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string PCA9554GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via PCA9554", pin_); - return buffer; +size_t PCA9554GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via PCA9554", this->pin_); } } // namespace pca9554 diff --git a/esphome/components/pca9554/pca9554.h b/esphome/components/pca9554/pca9554.h index 7b356b4068..bf752e50c9 100644 --- a/esphome/components/pca9554/pca9554.h +++ b/esphome/components/pca9554/pca9554.h @@ -59,7 +59,7 @@ class PCA9554GPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(PCA9554Component *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/pcf8574/pcf8574.cpp b/esphome/components/pcf8574/pcf8574.cpp index 72d8865d7f..15418bfee5 100644 --- a/esphome/components/pcf8574/pcf8574.cpp +++ b/esphome/components/pcf8574/pcf8574.cpp @@ -104,10 +104,8 @@ void PCF8574GPIOPin::setup() { pin_mode(flags_); } void PCF8574GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool PCF8574GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void PCF8574GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string PCF8574GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via PCF8574", pin_); - return buffer; +size_t PCF8574GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via PCF8574", this->pin_); } } // namespace pcf8574 diff --git a/esphome/components/pcf8574/pcf8574.h b/esphome/components/pcf8574/pcf8574.h index fd1ea8af63..5203030142 100644 --- a/esphome/components/pcf8574/pcf8574.h +++ b/esphome/components/pcf8574/pcf8574.h @@ -54,7 +54,7 @@ class PCF8574GPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(PCF8574Component *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp index 517ca833e6..f3a1f013d9 100644 --- a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp +++ b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp @@ -164,7 +164,9 @@ bool PI4IOE5V6408GPIOPin::digital_read() { return this->parent_->digital_read(th void PI4IOE5V6408GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string PI4IOE5V6408GPIOPin::dump_summary() const { return str_sprintf("%u via PI4IOE5V6408", this->pin_); } +size_t PI4IOE5V6408GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via PI4IOE5V6408", this->pin_); +} } // namespace pi4ioe5v6408 } // namespace esphome diff --git a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.h b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.h index 82b3076fab..4dc31201ce 100644 --- a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.h +++ b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.h @@ -52,7 +52,7 @@ class PI4IOE5V6408GPIOPin : public GPIOPin, public Parentedpin_ = pin; } void set_inverted(bool inverted) { this->inverted_ = inverted; } diff --git a/esphome/components/rp2040/gpio.cpp b/esphome/components/rp2040/gpio.cpp index 3927815e46..2b1699f888 100644 --- a/esphome/components/rp2040/gpio.cpp +++ b/esphome/components/rp2040/gpio.cpp @@ -64,10 +64,8 @@ void RP2040GPIOPin::pin_mode(gpio::Flags flags) { pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT } -std::string RP2040GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); - return buffer; +size_t RP2040GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "GPIO%u", this->pin_); } bool RP2040GPIOPin::digital_read() { diff --git a/esphome/components/rp2040/gpio.h b/esphome/components/rp2040/gpio.h index 47a6fe17f2..a98e1dab14 100644 --- a/esphome/components/rp2040/gpio.h +++ b/esphome/components/rp2040/gpio.h @@ -18,7 +18,7 @@ class RP2040GPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return pin_; } diff --git a/esphome/components/sn74hc165/sn74hc165.cpp b/esphome/components/sn74hc165/sn74hc165.cpp index 416d9db293..718e0b86ed 100644 --- a/esphome/components/sn74hc165/sn74hc165.cpp +++ b/esphome/components/sn74hc165/sn74hc165.cpp @@ -64,7 +64,9 @@ float SN74HC165Component::get_setup_priority() const { return setup_priority::IO bool SN74HC165GPIOPin::digital_read() { return this->parent_->digital_read_(this->pin_) != this->inverted_; } -std::string SN74HC165GPIOPin::dump_summary() const { return str_snprintf("%u via SN74HC165", 18, pin_); } +size_t SN74HC165GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via SN74HC165", this->pin_); +} } // namespace sn74hc165 } // namespace esphome diff --git a/esphome/components/sn74hc165/sn74hc165.h b/esphome/components/sn74hc165/sn74hc165.h index 4684844687..5a3f3fe8ef 100644 --- a/esphome/components/sn74hc165/sn74hc165.h +++ b/esphome/components/sn74hc165/sn74hc165.h @@ -47,7 +47,7 @@ class SN74HC165GPIOPin : public GPIOPin, public Parented { void pin_mode(gpio::Flags flags) override {} bool digital_read() override; void digital_write(bool value) override{}; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_pin(uint16_t pin) { pin_ = pin; } void set_inverted(bool inverted) { inverted_ = inverted; } diff --git a/esphome/components/sn74hc595/sn74hc595.cpp b/esphome/components/sn74hc595/sn74hc595.cpp index a9ada432e4..6b5c5d9fc4 100644 --- a/esphome/components/sn74hc595/sn74hc595.cpp +++ b/esphome/components/sn74hc595/sn74hc595.cpp @@ -93,7 +93,9 @@ float SN74HC595Component::get_setup_priority() const { return setup_priority::IO void SN74HC595GPIOPin::digital_write(bool value) { this->parent_->digital_write_(this->pin_, value != this->inverted_); } -std::string SN74HC595GPIOPin::dump_summary() const { return str_snprintf("%u via SN74HC595", 18, pin_); } +size_t SN74HC595GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via SN74HC595", this->pin_); +} } // namespace sn74hc595 } // namespace esphome diff --git a/esphome/components/sn74hc595/sn74hc595.h b/esphome/components/sn74hc595/sn74hc595.h index 181015b1e6..1cf70c86b5 100644 --- a/esphome/components/sn74hc595/sn74hc595.h +++ b/esphome/components/sn74hc595/sn74hc595.h @@ -54,7 +54,7 @@ class SN74HC595GPIOPin : public GPIOPin, public Parented { void pin_mode(gpio::Flags flags) override {} bool digital_read() override { return false; } void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_pin(uint16_t pin) { pin_ = pin; } void set_inverted(bool inverted) { inverted_ = inverted; } diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index c4876d1a74..36344a6d38 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -64,9 +64,9 @@ void SPIComponent::setup() { void SPIComponent::dump_config() { ESP_LOGCONFIG(TAG, "SPI bus:"); - LOG_PIN(" CLK Pin: ", this->clk_pin_) - LOG_PIN(" SDI Pin: ", this->sdi_pin_) - LOG_PIN(" SDO Pin: ", this->sdo_pin_) + LOG_PIN(" CLK Pin: ", this->clk_pin_); + LOG_PIN(" SDI Pin: ", this->sdi_pin_); + LOG_PIN(" SDO Pin: ", this->sdo_pin_); for (size_t i = 0; i != this->data_pins_.size(); i++) { ESP_LOGCONFIG(TAG, " Data pin %u: GPIO%d", i, this->data_pins_[i]); } diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 256cbcc65f..e237cf44f4 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -120,7 +120,11 @@ class NullPin : public GPIOPin { void digital_write(bool value) override {} - std::string dump_summary() const override { return std::string(); } + size_t dump_summary(char *buffer, size_t len) const override { + if (len > 0) + buffer[0] = '\0'; + return 0; + } protected: static GPIOPin *const NULL_PIN; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/sx1509/sx1509_gpio_pin.cpp b/esphome/components/sx1509/sx1509_gpio_pin.cpp index a74c8b60b8..41a99eba4b 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.cpp +++ b/esphome/components/sx1509/sx1509_gpio_pin.cpp @@ -12,10 +12,8 @@ void SX1509GPIOPin::setup() { pin_mode(flags_); } void SX1509GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool SX1509GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void SX1509GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string SX1509GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via sx1509", this->pin_); - return buffer; +size_t SX1509GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via sx1509", this->pin_); } } // namespace sx1509 diff --git a/esphome/components/sx1509/sx1509_gpio_pin.h b/esphome/components/sx1509/sx1509_gpio_pin.h index eb9207e882..5903af9d12 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.h +++ b/esphome/components/sx1509/sx1509_gpio_pin.h @@ -13,7 +13,7 @@ class SX1509GPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(SX1509Component *parent) { this->parent_ = parent; } void set_pin(uint8_t pin) { this->pin_ = pin; } diff --git a/esphome/components/tca9555/tca9555.cpp b/esphome/components/tca9555/tca9555.cpp index c3449ce254..376de6a370 100644 --- a/esphome/components/tca9555/tca9555.cpp +++ b/esphome/components/tca9555/tca9555.cpp @@ -138,7 +138,9 @@ void TCA9555GPIOPin::setup() { this->pin_mode(this->flags_); } void TCA9555GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool TCA9555GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void TCA9555GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string TCA9555GPIOPin::dump_summary() const { return str_sprintf("%u via TCA9555", this->pin_); } +size_t TCA9555GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via TCA9555", this->pin_); +} } // namespace tca9555 } // namespace esphome diff --git a/esphome/components/tca9555/tca9555.h b/esphome/components/tca9555/tca9555.h index 0c236ae4e3..9f7273b1e7 100644 --- a/esphome/components/tca9555/tca9555.h +++ b/esphome/components/tca9555/tca9555.h @@ -48,7 +48,7 @@ class TCA9555GPIOPin : public GPIOPin, public Parented { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_pin(uint8_t pin) { this->pin_ = pin; } void set_inverted(bool inverted) { this->inverted_ = inverted; } diff --git a/esphome/components/waveshare_epaper/waveshare_213v3.cpp b/esphome/components/waveshare_epaper/waveshare_213v3.cpp index 068cb91d31..b55f3c8d26 100644 --- a/esphome/components/waveshare_epaper/waveshare_213v3.cpp +++ b/esphome/components/waveshare_epaper/waveshare_213v3.cpp @@ -177,10 +177,10 @@ uint32_t WaveshareEPaper2P13InV3::idle_timeout_() { return 5000; } void WaveshareEPaper2P13InV3::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this) ESP_LOGCONFIG(TAG, " Model: 2.13inV3"); - LOG_PIN(" CS Pin: ", this->cs_) - LOG_PIN(" Reset Pin: ", this->reset_pin_) - LOG_PIN(" DC Pin: ", this->dc_pin_) - LOG_PIN(" Busy Pin: ", this->busy_pin_) + LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/weikai/weikai.cpp b/esphome/components/weikai/weikai.cpp index ebe987cc65..3384a0572f 100644 --- a/esphome/components/weikai/weikai.cpp +++ b/esphome/components/weikai/weikai.cpp @@ -245,10 +245,8 @@ void WeikaiGPIOPin::setup() { this->pin_mode(this->flags_); } -std::string WeikaiGPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via WeiKai %s", this->pin_, this->parent_->get_name()); - return buffer; +size_t WeikaiGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via WeiKai %s", this->pin_, this->parent_->get_name()); } /////////////////////////////////////////////////////////////////////////////// diff --git a/esphome/components/weikai/weikai.h b/esphome/components/weikai/weikai.h index 987278213a..a27c14106d 100644 --- a/esphome/components/weikai/weikai.h +++ b/esphome/components/weikai/weikai.h @@ -278,7 +278,7 @@ class WeikaiGPIOPin : public GPIOPin { gpio::Flags get_flags() const override { return this->flags_; } void setup() override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void pin_mode(gpio::Flags flags) override { this->parent_->set_pin_direction_(this->pin_, flags); } bool digital_read() override { return this->parent_->read_pin_val_(this->pin_) != this->inverted_; } void digital_write(bool value) override { this->parent_->write_pin_val_(this->pin_, value != this->inverted_); } diff --git a/esphome/components/xl9535/xl9535.cpp b/esphome/components/xl9535/xl9535.cpp index 958fc5eede..dd6c8188eb 100644 --- a/esphome/components/xl9535/xl9535.cpp +++ b/esphome/components/xl9535/xl9535.cpp @@ -110,7 +110,9 @@ void XL9535Component::pin_mode(uint8_t pin, gpio::Flags mode) { void XL9535GPIOPin::setup() { this->pin_mode(this->flags_); } -std::string XL9535GPIOPin::dump_summary() const { return str_snprintf("%u via XL9535", 15, this->pin_); } +size_t XL9535GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via XL9535", this->pin_); +} void XL9535GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool XL9535GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } diff --git a/esphome/components/xl9535/xl9535.h b/esphome/components/xl9535/xl9535.h index 3b511fd9b3..be0e2fbd82 100644 --- a/esphome/components/xl9535/xl9535.h +++ b/esphome/components/xl9535/xl9535.h @@ -39,7 +39,7 @@ class XL9535GPIOPin : public GPIOPin { gpio::Flags get_flags() const override { return this->flags_; } void setup() override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; diff --git a/esphome/components/zephyr/gpio.cpp b/esphome/components/zephyr/gpio.cpp index 41b983535c..8041c361cc 100644 --- a/esphome/components/zephyr/gpio.cpp +++ b/esphome/components/zephyr/gpio.cpp @@ -85,10 +85,8 @@ void ZephyrGPIOPin::pin_mode(gpio::Flags flags) { } } -std::string ZephyrGPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "GPIO%u, P%u.%u", this->pin_, this->pin_ / 32, this->pin_ % 32); - return buffer; +size_t ZephyrGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "GPIO%u, P%u.%u", this->pin_, this->pin_ / 32, this->pin_ % 32); } bool ZephyrGPIOPin::digital_read() { diff --git a/esphome/components/zephyr/gpio.h b/esphome/components/zephyr/gpio.h index 6e8f81857a..b405f385bc 100644 --- a/esphome/components/zephyr/gpio.h +++ b/esphome/components/zephyr/gpio.h @@ -16,7 +16,7 @@ class ZephyrGPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return this->pin_; } diff --git a/esphome/core/gpio.cpp b/esphome/core/gpio.cpp new file mode 100644 index 0000000000..21e88b5b6d --- /dev/null +++ b/esphome/core/gpio.cpp @@ -0,0 +1,24 @@ +#include "esphome/core/gpio.h" +#include "esphome/core/log.h" + +namespace esphome { + +#ifdef USE_ESP8266 +void log_pin(const char *tag, const __FlashStringHelper *prefix, GPIOPin *pin) { + if (pin == nullptr) + return; + static constexpr size_t LOG_PIN_PREFIX_MAX_LEN = 32; + char prefix_buf[LOG_PIN_PREFIX_MAX_LEN]; + strncpy_P(prefix_buf, reinterpret_cast(prefix), sizeof(prefix_buf) - 1); + prefix_buf[sizeof(prefix_buf) - 1] = '\0'; + log_pin_with_prefix(tag, prefix_buf, pin); +} +#else +void log_pin(const char *tag, const char *prefix, GPIOPin *pin) { + if (pin == nullptr) + return; + log_pin_with_prefix(tag, prefix, pin); +} +#endif + +} // namespace esphome diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h index dd6f14fef9..f2f85e18bc 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -1,13 +1,22 @@ #pragma once +#include #include +#include #include +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + namespace esphome { -#define LOG_PIN(prefix, pin) \ - if ((pin) != nullptr) { \ - ESP_LOGCONFIG(TAG, prefix "%s", (pin)->dump_summary().c_str()); \ - } +/// Maximum buffer size for dump_summary output +inline constexpr size_t GPIO_SUMMARY_MAX_LEN = 48; + +#ifdef USE_ESP8266 +#define LOG_PIN(prefix, pin) log_pin(TAG, F(prefix), pin) +#else +#define LOG_PIN(prefix, pin) log_pin(TAG, prefix, pin) +#endif // put GPIO flags in a namespace to not pollute esphome namespace namespace gpio { @@ -64,7 +73,17 @@ class GPIOPin { virtual void digital_write(bool value) = 0; - virtual std::string dump_summary() const = 0; + /// Write a summary of this pin to the provided buffer. + /// @param buffer The buffer to write to + /// @param len The size of the buffer (must be > 0) + /// @return The number of characters that would be written (excluding null terminator), + /// which may exceed len-1 if truncation occurred (snprintf semantics) + virtual size_t dump_summary(char *buffer, size_t len) const; + + /// Get a summary of this pin as a string. + /// @deprecated Use dump_summary(char*, size_t) instead. Will be removed in 2026.7.0. + ESPDEPRECATED("Override dump_summary(char*, size_t) instead. Will be removed in 2026.7.0.", "2026.1.0") + virtual std::string dump_summary() const; virtual bool is_internal() { return false; } }; @@ -103,4 +122,41 @@ class InternalGPIOPin : public GPIOPin { virtual void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const = 0; }; +// Inline default implementations for GPIOPin virtual methods. +// These provide bridge functionality for backwards compatibility with external components. + +// Default implementation bridges to old std::string method for backwards compatibility. +inline size_t GPIOPin::dump_summary(char *buffer, size_t len) const { + if (len == 0) + return 0; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + std::string s = this->dump_summary(); +#pragma GCC diagnostic pop + size_t copy_len = std::min(s.size(), len - 1); + memcpy(buffer, s.c_str(), copy_len); + buffer[copy_len] = '\0'; + return s.size(); // Return would-be length (snprintf semantics) +} + +// Default implementation returns empty string. +// External components should override this if they haven't migrated to buffer-based version. +// Remove before 2026.7.0 +inline std::string GPIOPin::dump_summary() const { return {}; } + +// Inline helper for log_pin - allows compiler to inline into log_pin in gpio.cpp +inline void log_pin_with_prefix(const char *tag, const char *prefix, GPIOPin *pin) { + char buffer[GPIO_SUMMARY_MAX_LEN]; + size_t len = pin->dump_summary(buffer, sizeof(buffer)); + len = std::min(len, sizeof(buffer) - 1); + esp_log_printf_(ESPHOME_LOG_LEVEL_CONFIG, tag, __LINE__, "%s%.*s", prefix, (int) len, buffer); +} + +// log_pin function declarations - implementation in gpio.cpp +#ifdef USE_ESP8266 +void log_pin(const char *tag, const __FlashStringHelper *prefix, GPIOPin *pin); +#else +void log_pin(const char *tag, const char *prefix, GPIOPin *pin); +#endif + } // namespace esphome From 3cc6810be52702b0668b489c99af24376fe19921 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 15:46:01 -1000 Subject: [PATCH 0893/1145] [core] Remove object_id RAM storage - no longer in hot path after #12627 (#12631) --- esphome/core/entity_base.cpp | 84 ++++----- esphome/core/entity_base.h | 11 +- esphome/core/entity_helpers.py | 34 +--- esphome/core/helpers.h | 14 ++ esphome/helpers.py | 28 ++- .../binary_sensor/test_binary_sensor.py | 2 +- tests/component_tests/button/test_button.py | 2 +- tests/component_tests/text/test_text.py | 2 +- .../text_sensor/test_text_sensor.py | 15 +- tests/integration/conftest.py | 3 + tests/integration/entity_utils.py | 145 +++++++++++++++ .../fixtures/fnv1_hash_object_id.yaml | 76 ++++++++ .../fixtures/object_id_api_verification.yaml | 125 +++++++++++++ ...object_id_friendly_name_no_mac_suffix.yaml | 27 +++ ...ect_id_no_friendly_name_no_mac_suffix.yaml | 25 +++ ...t_id_no_friendly_name_with_mac_suffix.yaml | 26 +++ tests/integration/test_fnv1_hash_object_id.py | 75 ++++++++ .../test_object_id_api_verification.py | 176 ++++++++++++++++++ ...t_object_id_friendly_name_no_mac_suffix.py | 81 ++++++++ .../test_object_id_no_friendly_name.py | 140 ++++++++++++++ tests/unit_tests/core/test_entity_helpers.py | 168 +++++++++++++++-- tests/unit_tests/test_helpers.py | 71 +++++++ 22 files changed, 1213 insertions(+), 117 deletions(-) create mode 100644 tests/integration/entity_utils.py create mode 100644 tests/integration/fixtures/fnv1_hash_object_id.yaml create mode 100644 tests/integration/fixtures/object_id_api_verification.yaml create mode 100644 tests/integration/fixtures/object_id_friendly_name_no_mac_suffix.yaml create mode 100644 tests/integration/fixtures/object_id_no_friendly_name_no_mac_suffix.yaml create mode 100644 tests/integration/fixtures/object_id_no_friendly_name_with_mac_suffix.yaml create mode 100644 tests/integration/test_fnv1_hash_object_id.py create mode 100644 tests/integration/test_object_id_api_verification.py create mode 100644 tests/integration/test_object_id_friendly_name_no_mac_suffix.py create mode 100644 tests/integration/test_object_id_no_friendly_name.py diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index b7616a9ad3..8508b93411 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -9,7 +9,8 @@ static const char *const TAG = "entity_base"; // Entity Name const StringRef &EntityBase::get_name() const { return this->name_; } -void EntityBase::set_name(const char *name) { +void EntityBase::set_name(const char *name) { this->set_name(name, 0); } +void EntityBase::set_name(const char *name, uint32_t object_id_hash) { this->name_ = StringRef(name); if (this->name_.empty()) { #ifdef USE_DEVICES @@ -18,11 +19,29 @@ void EntityBase::set_name(const char *name) { } else #endif { - this->name_ = StringRef(App.get_friendly_name()); + // Bug-for-bug compatibility with OLD behavior: + // - With MAC suffix: OLD code used App.get_friendly_name() directly (no fallback) + // - Without MAC suffix: OLD code used pre-computed object_id with fallback to device name + const std::string &friendly = App.get_friendly_name(); + if (App.is_name_add_mac_suffix_enabled()) { + // MAC suffix enabled - use friendly_name directly (even if empty) for compatibility + this->name_ = StringRef(friendly); + } else { + // No MAC suffix - fallback to device name if friendly_name is empty + this->name_ = StringRef(!friendly.empty() ? friendly : App.get_name()); + } } this->flags_.has_own_name = false; + // Dynamic name - must calculate hash at runtime + this->calc_object_id_(); } else { this->flags_.has_own_name = true; + // Static name - use pre-computed hash if provided + if (object_id_hash != 0) { + this->object_id_hash_ = object_id_hash; + } else { + this->calc_object_id_(); + } } } @@ -45,69 +64,30 @@ void EntityBase::set_icon(const char *icon) { #endif } -// Check if the object_id is dynamic (changes with MAC suffix) -bool EntityBase::is_object_id_dynamic_() const { - return !this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled(); -} - -// Entity Object ID +// Entity Object ID - computed on-demand from name std::string EntityBase::get_object_id() const { - // Check if `App.get_friendly_name()` is constant or dynamic. - if (this->is_object_id_dynamic_()) { - // `App.get_friendly_name()` is dynamic. - return str_sanitize(str_snake_case(App.get_friendly_name())); - } - // `App.get_friendly_name()` is constant. - return this->object_id_c_str_ == nullptr ? "" : this->object_id_c_str_; -} -void EntityBase::set_object_id(const char *object_id) { - this->object_id_c_str_ = object_id; - this->calc_object_id_(); -} - -void EntityBase::set_name_and_object_id(const char *name, const char *object_id) { - this->set_name(name); - this->object_id_c_str_ = object_id; - this->calc_object_id_(); -} - -// Calculate Object ID Hash from Entity Name -void EntityBase::calc_object_id_() { char buf[OBJECT_ID_MAX_LEN]; - StringRef object_id = this->get_object_id_to(buf); - this->object_id_hash_ = fnv1_hash(object_id.c_str()); + size_t len = this->write_object_id_to(buf, sizeof(buf)); + return std::string(buf, len); } -// Format dynamic object_id: sanitized snake_case of friendly_name -static size_t format_dynamic_object_id(char *buf, size_t buf_size) { - const std::string &name = App.get_friendly_name(); - size_t len = std::min(name.size(), buf_size - 1); - for (size_t i = 0; i < len; i++) { - buf[i] = to_sanitized_char(to_snake_case_char(name[i])); - } - buf[len] = '\0'; - return len; +// Calculate Object ID Hash directly from name using snake_case + sanitize +void EntityBase::calc_object_id_() { + this->object_id_hash_ = fnv1_hash_object_id(this->name_.c_str(), this->name_.size()); } size_t EntityBase::write_object_id_to(char *buf, size_t buf_size) const { - if (this->is_object_id_dynamic_()) { - return format_dynamic_object_id(buf, buf_size); + size_t len = std::min(this->name_.size(), buf_size - 1); + for (size_t i = 0; i < len; i++) { + buf[i] = to_sanitized_char(to_snake_case_char(this->name_[i])); } - const char *src = this->object_id_c_str_ == nullptr ? "" : this->object_id_c_str_; - size_t len = strlen(src); - if (len >= buf_size) - len = buf_size - 1; - memcpy(buf, src, len); buf[len] = '\0'; return len; } StringRef EntityBase::get_object_id_to(std::span buf) const { - if (this->is_object_id_dynamic_()) { - size_t len = format_dynamic_object_id(buf.data(), buf.size()); - return StringRef(buf.data(), len); - } - return this->object_id_c_str_ == nullptr ? StringRef() : StringRef(this->object_id_c_str_); + size_t len = this->write_object_id_to(buf.data(), buf.size()); + return StringRef(buf.data(), len); } uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index a5c69f132c..a45c7795bf 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -28,6 +28,9 @@ class EntityBase { // Get/set the name of this Entity const StringRef &get_name() const; void set_name(const char *name); + /// Set name with pre-computed object_id hash (avoids runtime hash calculation) + /// Use hash=0 for dynamic names that need runtime calculation + void set_name(const char *name, uint32_t object_id_hash); // Get whether this Entity has its own name or it should use the device friendly_name. bool has_own_name() const { return this->flags_.has_own_name; } @@ -43,10 +46,6 @@ class EntityBase { "which will remain available longer. get_object_id() will be removed in 2026.7.0", "2025.12.0") std::string get_object_id() const; - void set_object_id(const char *object_id); - - // Set both name and object_id in one call (reduces generated code size) - void set_name_and_object_id(const char *name, const char *object_id); // Get the unique Object ID of this Entity uint32_t get_object_id_hash(); @@ -142,11 +141,7 @@ class EntityBase { protected: void calc_object_id_(); - /// Check if the object_id is dynamic (changes with MAC suffix) - bool is_object_id_dynamic_() const; - StringRef name_; - const char *object_id_c_str_{nullptr}; #ifdef USE_ENTITY_ICON const char *icon_c_str_{nullptr}; #endif diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index f360b4d809..c1801c0bda 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -15,7 +15,7 @@ from esphome.const import ( from esphome.core import CORE, ID from esphome.cpp_generator import MockObj, add, get_variable import esphome.final_validate as fv -from esphome.helpers import sanitize, snake_case +from esphome.helpers import fnv1_hash_object_id, sanitize, snake_case from esphome.types import ConfigType, EntityMetadata _LOGGER = logging.getLogger(__name__) @@ -75,34 +75,18 @@ async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None: config: Configuration dictionary containing entity settings platform: The platform name (e.g., "sensor", "binary_sensor") """ - # Get device info - device_name: str | None = None - device_id_obj: ID | None + # Get device info if configured if device_id_obj := config.get(CONF_DEVICE_ID): device: MockObj = await get_variable(device_id_obj) add(var.set_device(device)) - # Get device name for object ID calculation - device_name = device_id_obj.id - # Calculate base object_id using the same logic as C++ - # This must match the C++ behavior in esphome/core/entity_base.cpp - base_object_id = get_base_entity_object_id( - config[CONF_NAME], CORE.friendly_name, device_name - ) - - if not config[CONF_NAME]: - _LOGGER.debug( - "Entity has empty name, using '%s' as object_id base", base_object_id - ) - - # Set both name and object_id in one call to reduce generated code size - add(var.set_name_and_object_id(config[CONF_NAME], base_object_id)) - _LOGGER.debug( - "Setting object_id '%s' for entity '%s' on platform '%s'", - base_object_id, - config[CONF_NAME], - platform, - ) + # Set the entity name with pre-computed object_id hash + # For named entities: pre-compute hash from entity name + # For empty-name entities: pass 0, C++ calculates hash at runtime from + # device name, friendly_name, or app name (bug-for-bug compatibility) + entity_name = config[CONF_NAME] + object_id_hash = fnv1_hash_object_id(entity_name) if entity_name else 0 + add(var.set_name(entity_name, object_id_hash)) # Only set disabled_by_default if True (default is False) if config[CONF_DISABLED_BY_DEFAULT]: add(var.set_disabled_by_default(True)) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index ac7a96a8c8..f7a14ed2ec 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -529,6 +529,20 @@ constexpr char to_sanitized_char(char c) { /// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores. std::string str_sanitize(const std::string &str); +/// Calculate FNV-1 hash of a string while applying snake_case + sanitize transformations. +/// This computes object_id hashes directly from names without creating an intermediate buffer. +/// IMPORTANT: Must match Python fnv1_hash_object_id() in esphome/helpers.py. +/// If you modify this function, update the Python version and tests in both places. +inline uint32_t fnv1_hash_object_id(const char *str, size_t len) { + uint32_t hash = FNV1_OFFSET_BASIS; + for (size_t i = 0; i < len; i++) { + hash *= FNV1_PRIME; + // Apply snake_case (space->underscore, uppercase->lowercase) then sanitize + hash ^= static_cast(to_sanitized_char(to_snake_case_char(str[i]))); + } + return hash; +} + /// snprintf-like function returning std::string of maximum length \p len (excluding null terminator). std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...); diff --git a/esphome/helpers.py b/esphome/helpers.py index d1623d1d3c..ae142b7f8b 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -35,6 +35,10 @@ IS_MACOS = platform.system() == "Darwin" IS_WINDOWS = platform.system() == "Windows" IS_LINUX = platform.system() == "Linux" +# FNV-1 hash constants (must match C++ in esphome/core/helpers.h) +FNV1_OFFSET_BASIS = 2166136261 +FNV1_PRIME = 16777619 + def ensure_unique_string(preferred_string, current_strings): test_string = preferred_string @@ -49,8 +53,17 @@ def ensure_unique_string(preferred_string, current_strings): return test_string +def fnv1_hash(string: str) -> int: + """FNV-1 32-bit hash function (multiply then XOR).""" + hash_value = FNV1_OFFSET_BASIS + for char in string: + hash_value = (hash_value * FNV1_PRIME) & 0xFFFFFFFF + hash_value ^= ord(char) + return hash_value + + def fnv1a_32bit_hash(string: str) -> int: - """FNV-1a 32-bit hash function. + """FNV-1a 32-bit hash function (XOR then multiply). Note: This uses 32-bit hash instead of 64-bit for several reasons: 1. ESPHome targets 32-bit microcontrollers with limited RAM (often <320KB) @@ -63,13 +76,22 @@ def fnv1a_32bit_hash(string: str) -> int: a handful of area_ids and device_ids (typically <10 areas and <100 devices), making collisions virtually impossible. """ - hash_value = 2166136261 + hash_value = FNV1_OFFSET_BASIS for char in string: hash_value ^= ord(char) - hash_value = (hash_value * 16777619) & 0xFFFFFFFF + hash_value = (hash_value * FNV1_PRIME) & 0xFFFFFFFF return hash_value +def fnv1_hash_object_id(name: str) -> int: + """Compute FNV-1 hash of name with snake_case + sanitize transformations. + + IMPORTANT: Must produce same result as C++ fnv1_hash_object_id() in helpers.h. + Used for pre-computing entity object_id hashes at code generation time. + """ + return fnv1_hash(sanitize(snake_case(name))) + + def strip_accents(value: str) -> str: """Remove accents from a string.""" import unicodedata diff --git a/tests/component_tests/binary_sensor/test_binary_sensor.py b/tests/component_tests/binary_sensor/test_binary_sensor.py index 86e0705023..ce4e64681f 100644 --- a/tests/component_tests/binary_sensor/test_binary_sensor.py +++ b/tests/component_tests/binary_sensor/test_binary_sensor.py @@ -29,7 +29,7 @@ def test_binary_sensor_sets_mandatory_fields(generate_main): ) # Then - assert 'bs_1->set_name_and_object_id("test bs1", "test_bs1");' in main_cpp + assert 'bs_1->set_name("test bs1",' in main_cpp assert "bs_1->set_pin(" in main_cpp diff --git a/tests/component_tests/button/test_button.py b/tests/component_tests/button/test_button.py index b21665288c..797b6fb1a4 100644 --- a/tests/component_tests/button/test_button.py +++ b/tests/component_tests/button/test_button.py @@ -26,7 +26,7 @@ def test_button_sets_mandatory_fields(generate_main): main_cpp = generate_main("tests/component_tests/button/test_button.yaml") # Then - assert 'wol_1->set_name_and_object_id("wol_test_1", "wol_test_1");' in main_cpp + assert 'wol_1->set_name("wol_test_1",' in main_cpp assert "wol_2->set_macaddr(18, 52, 86, 120, 144, 171);" in main_cpp diff --git a/tests/component_tests/text/test_text.py b/tests/component_tests/text/test_text.py index bfc3131f6d..6b047bc62f 100644 --- a/tests/component_tests/text/test_text.py +++ b/tests/component_tests/text/test_text.py @@ -25,7 +25,7 @@ def test_text_sets_mandatory_fields(generate_main): main_cpp = generate_main("tests/component_tests/text/test_text.yaml") # Then - assert 'it_1->set_name_and_object_id("test 1 text", "test_1_text");' in main_cpp + assert 'it_1->set_name("test 1 text",' in main_cpp def test_text_config_value_internal_set(generate_main): diff --git a/tests/component_tests/text_sensor/test_text_sensor.py b/tests/component_tests/text_sensor/test_text_sensor.py index 934ee67cef..1593d0b6d8 100644 --- a/tests/component_tests/text_sensor/test_text_sensor.py +++ b/tests/component_tests/text_sensor/test_text_sensor.py @@ -25,18 +25,9 @@ def test_text_sensor_sets_mandatory_fields(generate_main): main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") # Then - assert ( - 'ts_1->set_name_and_object_id("Template Text Sensor 1", "template_text_sensor_1");' - in main_cpp - ) - assert ( - 'ts_2->set_name_and_object_id("Template Text Sensor 2", "template_text_sensor_2");' - in main_cpp - ) - assert ( - 'ts_3->set_name_and_object_id("Template Text Sensor 3", "template_text_sensor_3");' - in main_cpp - ) + assert 'ts_1->set_name("Template Text Sensor 1",' in main_cpp + assert 'ts_2->set_name("Template Text Sensor 2",' in main_cpp + assert 'ts_3->set_name("Template Text Sensor 3",' in main_cpp def test_text_sensor_config_value_internal_set(generate_main): diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 965363972f..50e8d4122b 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -51,6 +51,9 @@ if platform.system() == "Windows": import pty # not available on Windows +# Register assert rewrite for entity_utils so assertions have proper error messages +pytest.register_assert_rewrite("tests.integration.entity_utils") + def _get_platformio_env(cache_dir: Path) -> dict[str, str]: """Get environment variables for PlatformIO with shared cache.""" diff --git a/tests/integration/entity_utils.py b/tests/integration/entity_utils.py new file mode 100644 index 0000000000..7596983ee2 --- /dev/null +++ b/tests/integration/entity_utils.py @@ -0,0 +1,145 @@ +"""Utilities for computing entity object_id in integration tests. + +This module contains the algorithm that aioesphomeapi will use to compute +object_id client-side from API data. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from esphome.helpers import fnv1_hash_object_id, sanitize, snake_case + +if TYPE_CHECKING: + from aioesphomeapi import DeviceInfo, EntityInfo + + +def compute_object_id(name: str) -> str: + """Compute object_id from name using snake_case + sanitize.""" + return sanitize(snake_case(name)) + + +def infer_name_add_mac_suffix(device_info: DeviceInfo) -> bool: + """Infer name_add_mac_suffix from device name ending with MAC suffix.""" + mac_suffix = device_info.mac_address.replace(":", "")[-6:].lower() + return device_info.name.endswith(f"-{mac_suffix}") + + +def _get_name_for_object_id( + entity: EntityInfo, + device_info: DeviceInfo, + device_id_to_name: dict[int, str], +) -> str: + """Get the name used for object_id computation. + + This is the algorithm that aioesphomeapi will use to determine which + name to use for computing object_id client-side from API data. + + Args: + entity: The entity to get name for + device_info: Device info from the API + device_id_to_name: Mapping of device_id to device name for sub-devices + + Returns: + The name to use for object_id computation + """ + if entity.name: + # Named entity: use entity name + return entity.name + if entity.device_id != 0: + # Empty name on sub-device: use sub-device name + return device_id_to_name[entity.device_id] + if infer_name_add_mac_suffix(device_info) or device_info.friendly_name: + # Empty name on main device with MAC suffix or friendly_name: use friendly_name + # (even if empty - this is bug-for-bug compatibility for MAC suffix case) + return device_info.friendly_name + # Empty name on main device, no friendly_name: use device name + return device_info.name + + +def compute_entity_object_id( + entity: EntityInfo, + device_info: DeviceInfo, + device_id_to_name: dict[int, str], +) -> str: + """Compute expected object_id for an entity. + + Args: + entity: The entity to compute object_id for + device_info: Device info from the API + device_id_to_name: Mapping of device_id to device name for sub-devices + + Returns: + The computed object_id string + """ + name_for_id = _get_name_for_object_id(entity, device_info, device_id_to_name) + return compute_object_id(name_for_id) + + +def compute_entity_hash( + entity: EntityInfo, + device_info: DeviceInfo, + device_id_to_name: dict[int, str], +) -> int: + """Compute expected object_id hash for an entity. + + Args: + entity: The entity to compute hash for + device_info: Device info from the API + device_id_to_name: Mapping of device_id to device name for sub-devices + + Returns: + The computed FNV-1 hash + """ + name_for_id = _get_name_for_object_id(entity, device_info, device_id_to_name) + return fnv1_hash_object_id(name_for_id) + + +def verify_entity_object_id( + entity: EntityInfo, + device_info: DeviceInfo, + device_id_to_name: dict[int, str], +) -> None: + """Verify an entity's object_id and hash match the expected values. + + Args: + entity: The entity to verify + device_info: Device info from the API + device_id_to_name: Mapping of device_id to device name for sub-devices + + Raises: + AssertionError: If object_id or hash doesn't match expected value + """ + expected_object_id = compute_entity_object_id( + entity, device_info, device_id_to_name + ) + assert entity.object_id == expected_object_id, ( + f"object_id mismatch for entity '{entity.name}': " + f"expected '{expected_object_id}', got '{entity.object_id}'" + ) + + expected_hash = compute_entity_hash(entity, device_info, device_id_to_name) + assert entity.key == expected_hash, ( + f"hash mismatch for entity '{entity.name}': " + f"expected {expected_hash:#x}, got {entity.key:#x}" + ) + + +def verify_all_entities( + entities: list[EntityInfo], + device_info: DeviceInfo, +) -> None: + """Verify all entities have correct object_id and hash values. + + Args: + entities: List of entities to verify + device_info: Device info from the API + + Raises: + AssertionError: If any entity's object_id or hash doesn't match + """ + # Build device_id -> name lookup from sub-devices + device_id_to_name = {d.device_id: d.name for d in device_info.devices} + + for entity in entities: + verify_entity_object_id(entity, device_info, device_id_to_name) diff --git a/tests/integration/fixtures/fnv1_hash_object_id.yaml b/tests/integration/fixtures/fnv1_hash_object_id.yaml new file mode 100644 index 0000000000..2097b2fbf9 --- /dev/null +++ b/tests/integration/fixtures/fnv1_hash_object_id.yaml @@ -0,0 +1,76 @@ +esphome: + name: fnv1-hash-object-id-test + platformio_options: + build_flags: + - "-DDEBUG" + on_boot: + - lambda: |- + using esphome::fnv1_hash_object_id; + + // Test basic lowercase (hash matches Python fnv1_hash_object_id("foo")) + uint32_t hash_foo = fnv1_hash_object_id("foo", 3); + if (hash_foo == 0x408f5e13) { + ESP_LOGI("FNV1_OID", "foo PASSED"); + } else { + ESP_LOGE("FNV1_OID", "foo FAILED: 0x%08x != 0x408f5e13", hash_foo); + } + + // Test uppercase conversion (should match lowercase) + uint32_t hash_Foo = fnv1_hash_object_id("Foo", 3); + if (hash_Foo == 0x408f5e13) { + ESP_LOGI("FNV1_OID", "upper PASSED"); + } else { + ESP_LOGE("FNV1_OID", "upper FAILED: 0x%08x != 0x408f5e13", hash_Foo); + } + + // Test space to underscore conversion ("foo bar" -> "foo_bar") + uint32_t hash_space = fnv1_hash_object_id("foo bar", 7); + if (hash_space == 0x3ae35aa1) { + ESP_LOGI("FNV1_OID", "space PASSED"); + } else { + ESP_LOGE("FNV1_OID", "space FAILED: 0x%08x != 0x3ae35aa1", hash_space); + } + + // Test underscore preserved ("foo_bar") + uint32_t hash_underscore = fnv1_hash_object_id("foo_bar", 7); + if (hash_underscore == 0x3ae35aa1) { + ESP_LOGI("FNV1_OID", "underscore PASSED"); + } else { + ESP_LOGE("FNV1_OID", "underscore FAILED: 0x%08x != 0x3ae35aa1", hash_underscore); + } + + // Test hyphen preserved ("foo-bar") + uint32_t hash_hyphen = fnv1_hash_object_id("foo-bar", 7); + if (hash_hyphen == 0x438b12e3) { + ESP_LOGI("FNV1_OID", "hyphen PASSED"); + } else { + ESP_LOGE("FNV1_OID", "hyphen FAILED: 0x%08x != 0x438b12e3", hash_hyphen); + } + + // Test special chars become underscore ("foo!bar" -> "foo_bar") + uint32_t hash_special = fnv1_hash_object_id("foo!bar", 7); + if (hash_special == 0x3ae35aa1) { + ESP_LOGI("FNV1_OID", "special PASSED"); + } else { + ESP_LOGE("FNV1_OID", "special FAILED: 0x%08x != 0x3ae35aa1", hash_special); + } + + // Test complex name ("My Sensor Name" -> "my_sensor_name") + uint32_t hash_complex = fnv1_hash_object_id("My Sensor Name", 14); + if (hash_complex == 0x2760962a) { + ESP_LOGI("FNV1_OID", "complex PASSED"); + } else { + ESP_LOGE("FNV1_OID", "complex FAILED: 0x%08x != 0x2760962a", hash_complex); + } + + // Test empty string returns FNV1_OFFSET_BASIS + uint32_t hash_empty = fnv1_hash_object_id("", 0); + if (hash_empty == 0x811c9dc5) { + ESP_LOGI("FNV1_OID", "empty PASSED"); + } else { + ESP_LOGE("FNV1_OID", "empty FAILED: 0x%08x != 0x811c9dc5", hash_empty); + } + +host: +api: +logger: diff --git a/tests/integration/fixtures/object_id_api_verification.yaml b/tests/integration/fixtures/object_id_api_verification.yaml new file mode 100644 index 0000000000..386270fc2c --- /dev/null +++ b/tests/integration/fixtures/object_id_api_verification.yaml @@ -0,0 +1,125 @@ +esphome: + name: object-id-test + friendly_name: Test Device + # Enable MAC suffix - host MAC is 98:35:69:ab:f6:79, suffix is "abf679" + # friendly_name becomes "Test Device abf679" + name_add_mac_suffix: true + # Sub-devices for testing empty-name entities on devices + devices: + - id: sub_device_1 + name: Sub Device One + - id: sub_device_2 + name: Sub Device Two + +host: + +api: + +logger: + +sensor: + # Test 1: Basic name -> object_id = "temperature_sensor" + - platform: template + name: "Temperature Sensor" + id: sensor_basic + lambda: return 42.0; + update_interval: 60s + + # Test 2: Uppercase name -> object_id = "uppercase_name" + - platform: template + name: "UPPERCASE NAME" + id: sensor_uppercase + lambda: return 43.0; + update_interval: 60s + + # Test 3: Special characters -> object_id = "special__chars_" + - platform: template + name: "Special!@Chars#" + id: sensor_special + lambda: return 44.0; + update_interval: 60s + + # Test 4: Hyphen preserved -> object_id = "temp-sensor" + - platform: template + name: "Temp-Sensor" + id: sensor_hyphen + lambda: return 45.0; + update_interval: 60s + + # Test 5: Underscore preserved -> object_id = "temp_sensor" + - platform: template + name: "Temp_Sensor" + id: sensor_underscore + lambda: return 46.0; + update_interval: 60s + + # Test 6: Mixed case with spaces -> object_id = "living_room_temperature" + - platform: template + name: "Living Room Temperature" + id: sensor_mixed + lambda: return 47.0; + update_interval: 60s + + # Test 7: Empty name - uses friendly_name with MAC suffix + # friendly_name = "Test Device abf679" -> object_id = "test_device_abf679" + - platform: template + name: "" + id: sensor_empty_name + lambda: return 48.0; + update_interval: 60s + +binary_sensor: + # Test 8: Different platform same conversion rules + - platform: template + name: "Door Open" + id: binary_door + lambda: return true; + + # Test 9: Numbers in name -> object_id = "sensor_123" + - platform: template + name: "Sensor 123" + id: binary_numbers + lambda: return false; + +switch: + # Test 10: Long name with multiple spaces + - platform: template + name: "My Very Long Switch Name Here" + id: switch_long + lambda: return false; + turn_on_action: + - logger.log: "on" + turn_off_action: + - logger.log: "off" + +text_sensor: + # Test 11: Name starting with number (should work fine) + - platform: template + name: "123 Start" + id: text_num_start + lambda: return {"test"}; + update_interval: 60s + +button: + # Test 12: Named entity on sub-device -> object_id from entity name + - platform: template + name: "Device Button" + id: button_on_device + device_id: sub_device_1 + on_press: [] + + # Test 13: Empty name on sub-device -> object_id from device name + # Device name "Sub Device One" -> object_id = "sub_device_one" + - platform: template + name: "" + id: button_empty_on_device1 + device_id: sub_device_1 + on_press: [] + + # Test 14: Empty name on different sub-device + # Device name "Sub Device Two" -> object_id = "sub_device_two" + - platform: template + name: "" + id: button_empty_on_device2 + device_id: sub_device_2 + on_press: [] diff --git a/tests/integration/fixtures/object_id_friendly_name_no_mac_suffix.yaml b/tests/integration/fixtures/object_id_friendly_name_no_mac_suffix.yaml new file mode 100644 index 0000000000..7a86e37d08 --- /dev/null +++ b/tests/integration/fixtures/object_id_friendly_name_no_mac_suffix.yaml @@ -0,0 +1,27 @@ +esphome: + name: test-device + # friendly_name set but NO MAC suffix + # Empty-name entity should use friendly_name for object_id + friendly_name: My Friendly Device + +host: + +api: + +logger: + +sensor: + # Empty name entity - should use friendly_name for object_id + # friendly_name = "My Friendly Device" -> object_id = "my_friendly_device" + - platform: template + name: "" + id: sensor_empty_name + lambda: return 42.0; + update_interval: 60s + + # Named entity for comparison + - platform: template + name: "Temperature" + id: sensor_named + lambda: return 43.0; + update_interval: 60s diff --git a/tests/integration/fixtures/object_id_no_friendly_name_no_mac_suffix.yaml b/tests/integration/fixtures/object_id_no_friendly_name_no_mac_suffix.yaml new file mode 100644 index 0000000000..4a947e0f6a --- /dev/null +++ b/tests/integration/fixtures/object_id_no_friendly_name_no_mac_suffix.yaml @@ -0,0 +1,25 @@ +esphome: + name: test-device + # No friendly_name set, no MAC suffix + # OLD behavior: object_id = device name because Python pre-computed with fallback + +host: + +api: + +logger: + +sensor: + # Empty name entity - OLD behavior used device name as fallback + - platform: template + name: "" + id: sensor_empty_name + lambda: return 42.0; + update_interval: 60s + + # Named entity for comparison + - platform: template + name: "Temperature" + id: sensor_named + lambda: return 43.0; + update_interval: 60s diff --git a/tests/integration/fixtures/object_id_no_friendly_name_with_mac_suffix.yaml b/tests/integration/fixtures/object_id_no_friendly_name_with_mac_suffix.yaml new file mode 100644 index 0000000000..ab12e670a0 --- /dev/null +++ b/tests/integration/fixtures/object_id_no_friendly_name_with_mac_suffix.yaml @@ -0,0 +1,26 @@ +esphome: + name: test-device + # No friendly_name set, MAC suffix enabled + # OLD behavior: object_id = "" (empty) because is_object_id_dynamic_() used App.get_friendly_name() directly + name_add_mac_suffix: true + +host: + +api: + +logger: + +sensor: + # Empty name entity - OLD behavior produced empty object_id when MAC suffix enabled + - platform: template + name: "" + id: sensor_empty_name + lambda: return 42.0; + update_interval: 60s + + # Named entity for comparison + - platform: template + name: "Temperature" + id: sensor_named + lambda: return 43.0; + update_interval: 60s diff --git a/tests/integration/test_fnv1_hash_object_id.py b/tests/integration/test_fnv1_hash_object_id.py new file mode 100644 index 0000000000..23e8ca04c2 --- /dev/null +++ b/tests/integration/test_fnv1_hash_object_id.py @@ -0,0 +1,75 @@ +"""Integration test for fnv1_hash_object_id function. + +This test verifies that the C++ fnv1_hash_object_id() function in +esphome/core/helpers.h produces the same hash values as the Python +fnv1_hash_object_id() function in esphome/helpers.py. + +If this test fails, one of the implementations has diverged and needs +to be updated to match the other. +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_fnv1_hash_object_id( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that C++ fnv1_hash_object_id matches Python implementation.""" + + test_results: dict[str, str] = {} + all_tests_complete = asyncio.Event() + expected_tests = { + "foo", + "upper", + "space", + "underscore", + "hyphen", + "special", + "complex", + "empty", + } + + def on_log_line(line: str) -> None: + """Capture log lines with test results.""" + # Strip ANSI escape codes + clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line) + # Look for our test result messages + # Format: "[timestamp][level][FNV1_OID:line]: test_name PASSED" + match = re.search(r"\[FNV1_OID:\d+\]:\s+(\w+)\s+(PASSED|FAILED)", clean_line) + if match: + test_name = match.group(1) + result = match.group(2) + test_results[test_name] = result + if set(test_results.keys()) >= expected_tests: + all_tests_complete.set() + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + api_client_connected() as client, + ): + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "fnv1-hash-object-id-test" + + # Wait for all tests to complete or timeout + try: + await asyncio.wait_for(all_tests_complete.wait(), timeout=2.0) + except TimeoutError: + pytest.fail(f"Tests timed out. Got results for: {set(test_results.keys())}") + + # Verify all tests passed + for test_name in expected_tests: + assert test_name in test_results, f"{test_name} test not found" + assert test_results[test_name] == "PASSED", ( + f"{test_name} test failed - C++ and Python hash mismatch" + ) diff --git a/tests/integration/test_object_id_api_verification.py b/tests/integration/test_object_id_api_verification.py new file mode 100644 index 0000000000..c8603e0682 --- /dev/null +++ b/tests/integration/test_object_id_api_verification.py @@ -0,0 +1,176 @@ +"""Integration test to verify object_id from API matches Python computation. + +This test verifies a three-way match between: +1. C++ object_id generation (get_object_id_to using to_sanitized_char/to_snake_case_char) +2. C++ hash generation (fnv1_hash_object_id in helpers.h) +3. Python computation (sanitize/snake_case in helpers.py, fnv1_hash_object_id) + +The API response contains C++ computed values, so verifying API == Python +implicitly verifies C++ == Python == API for both object_id and hash. + +This is important for the planned migration to remove object_id from the API +protocol and have clients (like aioesphomeapi) compute it from the name. +See: https://github.com/esphome/backlog/issues/76 + +Test cases covered: +- Named entities with various characters (uppercase, special chars, hyphens, etc.) +- Empty-name entities on main device (uses device's friendly_name with MAC suffix) +- Empty-name entities on sub-devices (uses sub-device's name) +- Named entities on sub-devices (uses entity name, not device name) +- MAC suffix handling (name_add_mac_suffix modifies friendly_name at runtime) +- Both object_id string and hash (key) verification +""" + +from __future__ import annotations + +import pytest + +from esphome.helpers import fnv1_hash_object_id + +from .entity_utils import compute_object_id, verify_all_entities +from .types import APIClientConnectedFactory, RunCompiledFunction + +# Host platform default MAC: 98:35:69:ab:f6:79 -> suffix "abf679" +MAC_SUFFIX = "abf679" + + +# Expected entities with their own names and expected object_ids +# Format: (entity_name, expected_object_id) +NAMED_ENTITIES = [ + # sensor platform + ("Temperature Sensor", "temperature_sensor"), + ("UPPERCASE NAME", "uppercase_name"), + ("Special!@Chars#", "special__chars_"), + ("Temp-Sensor", "temp-sensor"), + ("Temp_Sensor", "temp_sensor"), + ("Living Room Temperature", "living_room_temperature"), + # binary_sensor platform + ("Door Open", "door_open"), + ("Sensor 123", "sensor_123"), + # switch platform + ("My Very Long Switch Name Here", "my_very_long_switch_name_here"), + # text_sensor platform + ("123 Start", "123_start"), + # button platform - named entity on sub-device (uses entity name, not device name) + ("Device Button", "device_button"), +] + +# Sub-device names and their expected object_ids for empty-name entities +# Format: (device_name, expected_object_id) +SUB_DEVICE_EMPTY_NAME_ENTITIES = [ + ("Sub Device One", "sub_device_one"), + ("Sub Device Two", "sub_device_two"), +] + + +@pytest.mark.asyncio +async def test_object_id_api_verification( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that object_id from API matches Python computation. + + Tests: + 1. Named entities - object_id computed from entity name + 2. Empty-name entities - object_id computed from friendly_name (with MAC suffix) + 3. Hash verification - key can be computed from name + 4. Generic verification - all entities can have object_id computed from API data + """ + async with run_compiled(yaml_config), api_client_connected() as client: + # Get device info + device_info = await client.device_info() + assert device_info is not None + + # Device name should include MAC suffix (hyphen separator) + assert device_info.name == f"object-id-test-{MAC_SUFFIX}", ( + f"Device name mismatch: got '{device_info.name}'" + ) + # Friendly name should include MAC suffix (space separator) + expected_friendly_name = f"Test Device {MAC_SUFFIX}" + assert device_info.friendly_name == expected_friendly_name, ( + f"Friendly name mismatch: got '{device_info.friendly_name}'" + ) + + # Get all entities + entities, _ = await client.list_entities_services() + + # Create a map of entity names to entity info + entity_map = {} + for entity in entities: + entity_map[entity.name] = entity + + # === Test 1: Verify each named entity === + for entity_name, expected_object_id in NAMED_ENTITIES: + assert entity_name in entity_map, ( + f"Entity '{entity_name}' not found in API response. " + f"Available: {list(entity_map.keys())}" + ) + + entity = entity_map[entity_name] + + # Verify object_id matches expected + assert entity.object_id == expected_object_id, ( + f"Entity '{entity_name}': object_id mismatch. " + f"API returned '{entity.object_id}', expected '{expected_object_id}'" + ) + + # Verify Python computation matches + computed = compute_object_id(entity_name) + assert computed == expected_object_id, ( + f"Entity '{entity_name}': Python computation mismatch. " + f"Computed '{computed}', expected '{expected_object_id}'" + ) + + # Verify hash can be computed from the name + hash_from_name = fnv1_hash_object_id(entity_name) + assert hash_from_name == entity.key, ( + f"Entity '{entity_name}': hash mismatch. " + f"Python hash {hash_from_name:#x}, API key {entity.key:#x}" + ) + + # === Test 2: Verify empty-name entities === + # Empty-name entities have name="" in API, object_id comes from: + # - Main device: friendly_name (with MAC suffix) + # - Sub-device: device name + + # Get all empty-name entities + empty_name_entities = [e for e in entities if e.name == ""] + # We expect 3: 1 on main device, 2 on sub-devices + assert len(empty_name_entities) == 3, ( + f"Expected 3 empty-name entities, got {len(empty_name_entities)}" + ) + + # Build device_id -> device_name map from device_info + device_id_to_name = {d.device_id: d.name for d in device_info.devices} + + # Verify each empty-name entity + for entity in empty_name_entities: + if entity.device_id == 0: + # Main device - uses friendly_name with MAC suffix + expected_name = expected_friendly_name + else: + # Sub-device - uses device name + assert entity.device_id in device_id_to_name, ( + f"Entity device_id {entity.device_id} not found in devices" + ) + expected_name = device_id_to_name[entity.device_id] + + expected_object_id = compute_object_id(expected_name) + assert entity.object_id == expected_object_id, ( + f"Empty-name entity (device_id={entity.device_id}): object_id mismatch. " + f"API: '{entity.object_id}', expected: '{expected_object_id}' " + f"(from name '{expected_name}')" + ) + + # Verify hash matches + expected_hash = fnv1_hash_object_id(expected_name) + assert entity.key == expected_hash, ( + f"Empty-name entity (device_id={entity.device_id}): hash mismatch. " + f"API key: {entity.key:#x}, expected: {expected_hash:#x}" + ) + + # === Test 3: Verify ALL entities using the algorithm from entity_utils === + # This uses the algorithm that aioesphomeapi will use to compute object_id + # client-side from API data. + verify_all_entities(entities, device_info) diff --git a/tests/integration/test_object_id_friendly_name_no_mac_suffix.py b/tests/integration/test_object_id_friendly_name_no_mac_suffix.py new file mode 100644 index 0000000000..7199a2b371 --- /dev/null +++ b/tests/integration/test_object_id_friendly_name_no_mac_suffix.py @@ -0,0 +1,81 @@ +"""Integration test for object_id with friendly_name but no MAC suffix. + +This test covers Branch 4 of the algorithm: +- Empty name on main device +- NO MAC suffix enabled +- friendly_name IS set +- Result: use friendly_name for object_id +""" + +from __future__ import annotations + +import pytest + +from esphome.helpers import fnv1_hash_object_id + +from .entity_utils import ( + compute_object_id, + infer_name_add_mac_suffix, + verify_all_entities, +) +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_object_id_friendly_name_no_mac_suffix( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test object_id when friendly_name is set but no MAC suffix. + + This covers Branch 4 of the algorithm: + - Empty name entity + - name_add_mac_suffix = false (or not set) + - friendly_name = "My Friendly Device" + - Expected: object_id = "my_friendly_device" + """ + async with run_compiled(yaml_config), api_client_connected() as client: + device_info = await client.device_info() + assert device_info is not None + + # Device name should NOT include MAC suffix + assert device_info.name == "test-device" + + # Friendly name should be set + assert device_info.friendly_name == "My Friendly Device" + + entities, _ = await client.list_entities_services() + + # Find the empty-name entity + empty_name_entities = [e for e in entities if e.name == ""] + assert len(empty_name_entities) == 1 + + entity = empty_name_entities[0] + + # Should use friendly_name for object_id (Branch 4) + expected_object_id = compute_object_id("My Friendly Device") + assert expected_object_id == "my_friendly_device" # Verify our expectation + assert entity.object_id == expected_object_id, ( + f"Expected object_id '{expected_object_id}' from friendly_name, " + f"got '{entity.object_id}'" + ) + + # Hash should match friendly_name + expected_hash = fnv1_hash_object_id("My Friendly Device") + assert entity.key == expected_hash, ( + f"Expected hash {expected_hash:#x}, got {entity.key:#x}" + ) + + # Named entity should work normally + named_entities = [e for e in entities if e.name == "Temperature"] + assert len(named_entities) == 1 + assert named_entities[0].object_id == "temperature" + + # Verify our inference: no MAC suffix in this test + assert not infer_name_add_mac_suffix(device_info), ( + "Device name should NOT have MAC suffix" + ) + + # Verify the full algorithm from entity_utils works for ALL entities + verify_all_entities(entities, device_info) diff --git a/tests/integration/test_object_id_no_friendly_name.py b/tests/integration/test_object_id_no_friendly_name.py new file mode 100644 index 0000000000..b548f02fde --- /dev/null +++ b/tests/integration/test_object_id_no_friendly_name.py @@ -0,0 +1,140 @@ +"""Integration tests for object_id when friendly_name is not set. + +These tests verify bug-for-bug compatibility with the old behavior: + +1. With MAC suffix enabled + no friendly_name: + - OLD: is_object_id_dynamic_() was true, used App.get_friendly_name() directly + - OLD: object_id = "" (empty) because friendly_name was empty + - NEW: Must maintain same behavior for compatibility + +2. Without MAC suffix + no friendly_name: + - OLD: is_object_id_dynamic_() was false, used pre-computed object_id_c_str_ + - OLD: Python computed object_id with fallback to device name + - NEW: Must maintain same behavior (object_id = device name) +""" + +from __future__ import annotations + +import pytest + +from esphome.helpers import fnv1_hash_object_id + +from .entity_utils import compute_object_id, verify_all_entities +from .types import APIClientConnectedFactory, RunCompiledFunction + +# Host platform default MAC: 98:35:69:ab:f6:79 -> suffix "abf679" +MAC_SUFFIX = "abf679" + +# FNV1 offset basis - hash of empty string +FNV1_OFFSET_BASIS = 2166136261 + + +@pytest.mark.asyncio +async def test_object_id_no_friendly_name_with_mac_suffix( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test object_id when friendly_name not set but MAC suffix enabled. + + OLD behavior (bug-for-bug compatibility): + - is_object_id_dynamic_() returned true (no own name AND mac suffix enabled) + - format_dynamic_object_id() used App.get_friendly_name() directly + - Since friendly_name was empty, object_id was empty + + This was arguably a bug, but we maintain it for compatibility. + """ + async with run_compiled(yaml_config), api_client_connected() as client: + device_info = await client.device_info() + assert device_info is not None + + # Device name should include MAC suffix + expected_device_name = f"test-device-{MAC_SUFFIX}" + assert device_info.name == expected_device_name + + # Friendly name should be empty (not set in config) + assert device_info.friendly_name == "" + + entities, _ = await client.list_entities_services() + + # Find the empty-name entity + empty_name_entities = [e for e in entities if e.name == ""] + assert len(empty_name_entities) == 1 + + entity = empty_name_entities[0] + + # OLD behavior: object_id was empty because App.get_friendly_name() was empty + # This is bug-for-bug compatibility + assert entity.object_id == "", ( + f"Expected empty object_id for bug-for-bug compatibility, " + f"got '{entity.object_id}'" + ) + + # Hash should be FNV1_OFFSET_BASIS (hash of empty string) + assert entity.key == FNV1_OFFSET_BASIS, ( + f"Expected hash of empty string ({FNV1_OFFSET_BASIS:#x}), " + f"got {entity.key:#x}" + ) + + # Named entity should work normally + named_entities = [e for e in entities if e.name == "Temperature"] + assert len(named_entities) == 1 + assert named_entities[0].object_id == "temperature" + + # Verify the full algorithm from entity_utils works for ALL entities + verify_all_entities(entities, device_info) + + +@pytest.mark.asyncio +async def test_object_id_no_friendly_name_no_mac_suffix( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test object_id when friendly_name not set and no MAC suffix. + + OLD behavior: + - is_object_id_dynamic_() returned false (mac suffix not enabled) + - Used object_id_c_str_ which was pre-computed in Python + - Python used get_base_entity_object_id() with fallback to CORE.name + + Result: object_id = sanitize(snake_case(device_name)) + """ + async with run_compiled(yaml_config), api_client_connected() as client: + device_info = await client.device_info() + assert device_info is not None + + # Device name should NOT include MAC suffix + assert device_info.name == "test-device" + + # Friendly name should be empty (not set in config) + assert device_info.friendly_name == "" + + entities, _ = await client.list_entities_services() + + # Find the empty-name entity + empty_name_entities = [e for e in entities if e.name == ""] + assert len(empty_name_entities) == 1 + + entity = empty_name_entities[0] + + # OLD behavior: object_id was computed from device name + expected_object_id = compute_object_id("test-device") + assert entity.object_id == expected_object_id, ( + f"Expected object_id '{expected_object_id}' from device name, " + f"got '{entity.object_id}'" + ) + + # Hash should match device name + expected_hash = fnv1_hash_object_id("test-device") + assert entity.key == expected_hash, ( + f"Expected hash {expected_hash:#x}, got {entity.key:#x}" + ) + + # Named entity should work normally + named_entities = [e for e in entities if e.name == "Temperature"] + assert len(named_entities) == 1 + assert named_entities[0].object_id == "temperature" + + # Verify the full algorithm from entity_utils works for ALL entities + verify_all_entities(entities, device_info) diff --git a/tests/unit_tests/core/test_entity_helpers.py b/tests/unit_tests/core/test_entity_helpers.py index 01de0f27f9..a58d4784ce 100644 --- a/tests/unit_tests/core/test_entity_helpers.py +++ b/tests/unit_tests/core/test_entity_helpers.py @@ -27,13 +27,9 @@ from esphome.helpers import sanitize, snake_case from .common import load_config_from_fixture -# Pre-compiled regex patterns for extracting object IDs from expressions -# Matches both old format: .set_object_id("obj_id") -# and new format: .set_name_and_object_id("name", "obj_id") -OBJECT_ID_PATTERN = re.compile(r'\.set_object_id\(["\'](.*?)["\']\)') -COMBINED_PATTERN = re.compile( - r'\.set_name_and_object_id\(["\'].*?["\']\s*,\s*["\'](.*?)["\']\)' -) +# Pre-compiled regex pattern for extracting names from set_name calls +# Matches: .set_name("name", hash) or .set_name("name") +SET_NAME_PATTERN = re.compile(r'\.set_name\(["\']([^"\']*)["\']') FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "entity_helpers" @@ -276,14 +272,21 @@ def setup_test_environment() -> Generator[list[str], None, None]: def extract_object_id_from_expressions(expressions: list[str]) -> str | None: - """Extract the object ID that was set from the generated expressions.""" + """Extract the object ID that would be computed from set_name calls. + + Since object_id is now computed from the name (via snake_case + sanitize), + we extract the name from set_name() calls and compute the expected object_id. + For empty names, we fall back to CORE.friendly_name or CORE.name. + """ for expr in expressions: - # First try new combined format: .set_name_and_object_id("name", "obj_id") - if match := COMBINED_PATTERN.search(expr): - return match.group(1) - # Fall back to old format: .set_object_id("obj_id") - if match := OBJECT_ID_PATTERN.search(expr): - return match.group(1) + if match := SET_NAME_PATTERN.search(expr): + name = match.group(1) + if name: + return sanitize(snake_case(name)) + # Empty name - fall back to friendly_name or device name + if CORE.friendly_name: + return sanitize(snake_case(CORE.friendly_name)) + return sanitize(snake_case(CORE.name)) if CORE.name else None return None @@ -757,3 +760,140 @@ def test_entity_duplicate_validator_same_name_no_enhanced_message() -> None: r"Each entity on a device must have a unique name within its platform\.$", ): validator(config2) + + +@pytest.mark.asyncio +async def test_setup_entity_empty_name_with_device( + setup_test_environment: list[str], +) -> None: + """Test setup_entity with empty entity name on a sub-device. + + For empty-name entities, Python passes 0 and C++ calculates the hash + at runtime from the device's actual name. + """ + added_expressions = setup_test_environment + + # Mock get_variable to return a mock device + original_get_variable = entity_helpers.get_variable + + async def mock_get_variable(id_: ID) -> MockObj: + return MockObj("sub_device_1") + + entity_helpers.get_variable = mock_get_variable + + var = MockObj("sensor1") + device_id = ID("sub_device_1", type="Device") + + config = { + CONF_NAME: "", + CONF_DISABLED_BY_DEFAULT: False, + CONF_DEVICE_ID: device_id, + } + + await setup_entity(var, config, "sensor") + + entity_helpers.get_variable = original_get_variable + + # Check that set_device was called + assert any("sensor1.set_device" in expr for expr in added_expressions) + + # For empty-name entities, Python passes 0 - C++ calculates hash at runtime + assert any('set_name("", 0)' in expr for expr in added_expressions), ( + f"Expected set_name with hash 0, got {added_expressions}" + ) + + +@pytest.mark.asyncio +async def test_setup_entity_empty_name_with_mac_suffix( + setup_test_environment: list[str], +) -> None: + """Test setup_entity with empty name and MAC suffix enabled. + + For empty-name entities, Python passes 0 and C++ calculates the hash + at runtime from friendly_name (bug-for-bug compatibility). + """ + added_expressions = setup_test_environment + + # Set up CORE.config with name_add_mac_suffix enabled + CORE.config = {"name_add_mac_suffix": True} + # Set friendly_name to a specific value + CORE.friendly_name = "My Device" + + var = MockObj("sensor1") + + config = { + CONF_NAME: "", + CONF_DISABLED_BY_DEFAULT: False, + } + + await setup_entity(var, config, "sensor") + + # For empty-name entities, Python passes 0 - C++ calculates hash at runtime + assert any('set_name("", 0)' in expr for expr in added_expressions), ( + f"Expected set_name with hash 0, got {added_expressions}" + ) + + +@pytest.mark.asyncio +async def test_setup_entity_empty_name_with_mac_suffix_no_friendly_name( + setup_test_environment: list[str], +) -> None: + """Test setup_entity with empty name, MAC suffix enabled, but no friendly_name. + + For empty-name entities, Python passes 0 and C++ calculates the hash + at runtime. In this case C++ will hash the empty friendly_name + (bug-for-bug compatibility). + """ + added_expressions = setup_test_environment + + # Set up CORE.config with name_add_mac_suffix enabled + CORE.config = {"name_add_mac_suffix": True} + # Set friendly_name to empty + CORE.friendly_name = "" + + var = MockObj("sensor1") + + config = { + CONF_NAME: "", + CONF_DISABLED_BY_DEFAULT: False, + } + + await setup_entity(var, config, "sensor") + + # For empty-name entities, Python passes 0 - C++ calculates hash at runtime + assert any('set_name("", 0)' in expr for expr in added_expressions), ( + f"Expected set_name with hash 0, got {added_expressions}" + ) + + +@pytest.mark.asyncio +async def test_setup_entity_empty_name_no_mac_suffix_no_friendly_name( + setup_test_environment: list[str], +) -> None: + """Test setup_entity with empty name, no MAC suffix, and no friendly_name. + + For empty-name entities, Python passes 0 and C++ calculates the hash + at runtime from the device name. + """ + added_expressions = setup_test_environment + + # No MAC suffix (either not set or False) + CORE.config = {} + # No friendly_name + CORE.friendly_name = "" + # Device name is set + CORE.name = "my-test-device" + + var = MockObj("sensor1") + + config = { + CONF_NAME: "", + CONF_DISABLED_BY_DEFAULT: False, + } + + await setup_entity(var, config, "sensor") + + # For empty-name entities, Python passes 0 - C++ calculates hash at runtime + assert any('set_name("", 0)' in expr for expr in added_expressions), ( + f"Expected set_name with hash 0, got {added_expressions}" + ) diff --git a/tests/unit_tests/test_helpers.py b/tests/unit_tests/test_helpers.py index 47b945e0eb..159d3230ab 100644 --- a/tests/unit_tests/test_helpers.py +++ b/tests/unit_tests/test_helpers.py @@ -279,6 +279,77 @@ def test_sanitize(text, expected): assert actual == expected +@pytest.mark.parametrize( + ("name", "expected_hash"), + ( + # Basic strings - hash of sanitize(snake_case(name)) + ("foo", 0x408F5E13), + ("Foo", 0x408F5E13), # Same as "foo" (lowercase) + ("FOO", 0x408F5E13), # Same as "foo" (lowercase) + # Spaces become underscores + ("foo bar", 0x3AE35AA1), # Transforms to "foo_bar" + ("Foo Bar", 0x3AE35AA1), # Same (lowercase + underscore) + # Already snake_case + ("foo_bar", 0x3AE35AA1), + # Special chars become underscores + ("foo!bar", 0x3AE35AA1), # Transforms to "foo_bar" + ("foo@bar", 0x3AE35AA1), # Transforms to "foo_bar" + # Hyphens are preserved + ("foo-bar", 0x438B12E3), + # Numbers are preserved + ("foo123", 0xF3B0067D), + # Empty string + ("", 0x811C9DC5), # FNV1_OFFSET_BASIS (no chars processed) + # Single char + ("a", 0x050C5D7E), + # Mixed case and spaces + ("My Sensor Name", 0x2760962A), # Transforms to "my_sensor_name" + ), +) +def test_fnv1_hash_object_id(name, expected_hash): + """Test fnv1_hash_object_id produces expected hashes. + + These expected values were computed to match the C++ implementation + in esphome/core/helpers.h. If this test fails after modifying either + implementation, ensure both Python and C++ versions stay in sync. + """ + actual = helpers.fnv1_hash_object_id(name) + + assert actual == expected_hash + + +def _fnv1_hash_py(s: str) -> int: + """Python implementation of FNV-1 hash for verification.""" + hash_val = 2166136261 # FNV1_OFFSET_BASIS + for c in s: + hash_val = (hash_val * 16777619) & 0xFFFFFFFF # FNV1_PRIME + hash_val ^= ord(c) + return hash_val + + +@pytest.mark.parametrize( + "name", + ( + "Simple", + "With Space", + "MixedCase", + "special!@#chars", + "already_snake_case", + "123numbers", + ), +) +def test_fnv1_hash_object_id_matches_manual_calculation(name): + """Verify fnv1_hash_object_id matches snake_case + sanitize + standard FNV-1.""" + # Manual calculation: snake_case -> sanitize -> fnv1_hash + transformed = helpers.sanitize(helpers.snake_case(name)) + expected = _fnv1_hash_py(transformed) + + # Direct calculation via fnv1_hash_object_id + actual = helpers.fnv1_hash_object_id(name) + + assert actual == expected + + @pytest.mark.parametrize( "text, expected", ((["127.0.0.1", "fe80::1", "2001::2"], ["2001::2", "127.0.0.1", "fe80::1"]),), From f0391f02138d4df25505310fba08d699efdf0106 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 16:32:46 -1000 Subject: [PATCH 0894/1145] [api] Remove object_id from API protocol - clients compute it from name #12698 (#12818) --- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_connection.h | 31 +++++++++++++++++------ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8588651968..2ecd54bb00 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1530,7 +1530,7 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) { HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 13; + resp.api_version_minor = 14; // Send only the version string - the client only logs this for debugging and doesn't use it otherwise resp.set_server_info(ESPHOME_VERSION_REF); resp.set_name(StringRef(App.get_name())); diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 47609f79b6..59c42aa033 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -24,9 +24,10 @@ struct ClientInfo { // Keepalive timeout in milliseconds static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; // Maximum number of entities to process in a single batch during initial state/info sending -// This was increased from 20 to 24 after removing the unique_id field from entity info messages, -// which reduced message sizes allowing more entities per batch without exceeding packet limits -static constexpr size_t MAX_INITIAL_PER_BATCH = 24; +// API 1.14+ clients compute object_id client-side, so messages are smaller and we can fit more per batch +// TODO: Remove MAX_INITIAL_PER_BATCH_LEGACY before 2026.7.0 - all clients should support API 1.14 by then +static constexpr size_t MAX_INITIAL_PER_BATCH_LEGACY = 24; // For clients < API 1.14 (includes object_id) +static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= API 1.14 (no object_id) // Maximum number of packets to process in a single batch (platform-dependent) // This limit exists to prevent stack overflow from the PacketInfo array in process_batch_ // Each PacketInfo is 8 bytes, so 64 * 8 = 512 bytes, 32 * 8 = 256 bytes @@ -323,10 +324,16 @@ class APIConnection final : public APIServerConnection { APIConnection *conn, uint32_t remaining_size, bool is_single) { // Set common fields that are shared by all entity types msg.key = entity->get_object_id_hash(); - // Get object_id with zero heap allocation - // Static case returns direct reference, dynamic case uses buffer + + // API 1.14+ clients compute object_id client-side from the entity name + // For older clients, we must send object_id for backward compatibility + // See: https://github.com/esphome/backlog/issues/76 + // TODO: Remove this backward compat code before 2026.7.0 - all clients should support API 1.14 by then + // Buffer must remain in scope until encode_message_to_buffer is called char object_id_buf[OBJECT_ID_MAX_LEN]; - msg.set_object_id(entity->get_object_id_to(object_id_buf)); + if (!conn->client_supports_api_version(1, 14)) { + msg.set_object_id(entity->get_object_id_to(object_id_buf)); + } if (entity->has_own_name()) { msg.set_name(entity->get_name()); @@ -349,16 +356,24 @@ class APIConnection final : public APIServerConnection { inline bool check_voice_assistant_api_connection_() const; #endif + // Get the max batch size based on client API version + // API 1.14+ clients don't receive object_id, so messages are smaller and more fit per batch + // TODO: Remove this method before 2026.7.0 and use MAX_INITIAL_PER_BATCH directly + size_t get_max_batch_size_() const { + return this->client_supports_api_version(1, 14) ? MAX_INITIAL_PER_BATCH : MAX_INITIAL_PER_BATCH_LEGACY; + } + // Helper method to process multiple entities from an iterator in a batch template void process_iterator_batch_(Iterator &iterator) { size_t initial_size = this->deferred_batch_.size(); - while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < MAX_INITIAL_PER_BATCH) { + size_t max_batch = this->get_max_batch_size_(); + while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) { iterator.advance(); } // If the batch is full, process it immediately // Note: iterator.advance() already calls schedule_batch_() via schedule_message_() - if (this->deferred_batch_.size() >= MAX_INITIAL_PER_BATCH) { + if (this->deferred_batch_.size() >= max_batch) { this->process_batch_(); } } From 1240e7907ecfb0a06f295419ac0874af07e3fdfd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 16:35:44 -1000 Subject: [PATCH 0895/1145] [api] Use stack-based format_hex_pretty_to for packet logging macros (#12788) --- esphome/components/api/api_frame_helper.cpp | 18 ++++++++++++++++-- .../components/api/api_frame_helper_noise.cpp | 18 ++++++++++++++++-- .../api/api_frame_helper_plaintext.cpp | 18 ++++++++++++++++-- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 20f8fcaf61..420f42a90a 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -13,12 +13,26 @@ namespace esphome::api { static const char *const TAG = "api.frame_helper"; +// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) +static constexpr size_t API_MAX_LOG_BYTES = 168; + #define HELPER_LOG(msg, ...) \ ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) #ifdef HELPER_LOG_PACKETS -#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) -#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str()) +#define LOG_PACKET_RECEIVED(buffer) \ + do { \ + char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ + ESP_LOGVV(TAG, "Received frame: %s", \ + format_hex_pretty_to(hex_buf_, (buffer).data(), \ + (buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \ + } while (0) +#define LOG_PACKET_SENDING(data, len) \ + do { \ + char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ + ESP_LOGVV(TAG, "Sending raw: %s", \ + format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \ + } while (0) #else #define LOG_PACKET_RECEIVED(buffer) ((void) 0) #define LOG_PACKET_SENDING(data, len) ((void) 0) diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index 1d6f32ee9d..37b497e2a1 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -24,12 +24,26 @@ static const char *const PROLOGUE_INIT = "NoiseAPIInit"; #endif static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit") +// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) +static constexpr size_t API_MAX_LOG_BYTES = 168; + #define HELPER_LOG(msg, ...) \ ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) #ifdef HELPER_LOG_PACKETS -#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) -#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str()) +#define LOG_PACKET_RECEIVED(buffer) \ + do { \ + char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ + ESP_LOGVV(TAG, "Received frame: %s", \ + format_hex_pretty_to(hex_buf_, (buffer).data(), \ + (buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \ + } while (0) +#define LOG_PACKET_SENDING(data, len) \ + do { \ + char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ + ESP_LOGVV(TAG, "Sending raw: %s", \ + format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \ + } while (0) #else #define LOG_PACKET_RECEIVED(buffer) ((void) 0) #define LOG_PACKET_SENDING(data, len) ((void) 0) diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index b5d90b2429..8b7d002d7c 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -18,12 +18,26 @@ namespace esphome::api { static const char *const TAG = "api.plaintext"; +// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) +static constexpr size_t API_MAX_LOG_BYTES = 168; + #define HELPER_LOG(msg, ...) \ ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) #ifdef HELPER_LOG_PACKETS -#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) -#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str()) +#define LOG_PACKET_RECEIVED(buffer) \ + do { \ + char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ + ESP_LOGVV(TAG, "Received frame: %s", \ + format_hex_pretty_to(hex_buf_, (buffer).data(), \ + (buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \ + } while (0) +#define LOG_PACKET_SENDING(data, len) \ + do { \ + char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ + ESP_LOGVV(TAG, "Sending raw: %s", \ + format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \ + } while (0) #else #define LOG_PACKET_RECEIVED(buffer) ((void) 0) #define LOG_PACKET_SENDING(data, len) ((void) 0) From a57011b50bc4e4a4e22bab83f2605c7003a030c0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 16:36:57 -1000 Subject: [PATCH 0896/1145] [kuntze] Use stack buffer for hex formatting in verbose logging (#12775) --- esphome/components/kuntze/kuntze.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/kuntze/kuntze.cpp b/esphome/components/kuntze/kuntze.cpp index 30f98aaa99..1b772d062c 100644 --- a/esphome/components/kuntze/kuntze.cpp +++ b/esphome/components/kuntze/kuntze.cpp @@ -1,4 +1,5 @@ #include "kuntze.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/application.h" @@ -10,11 +11,17 @@ static const char *const TAG = "kuntze"; static const uint8_t CMD_READ_REG = 0x03; static const uint16_t REGISTER[] = {4136, 4160, 4680, 6000, 4688, 4728, 5832}; +// Maximum bytes to log for Modbus responses (2 registers = 4, plus count = 5) +static constexpr size_t KUNTZE_MAX_LOG_BYTES = 8; + void Kuntze::on_modbus_data(const std::vector &data) { auto get_16bit = [&](int i) -> uint16_t { return (uint16_t(data[i * 2]) << 8) | uint16_t(data[i * 2 + 1]); }; this->waiting_ = false; - ESP_LOGV(TAG, "Data: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(KUNTZE_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Data: %s", format_hex_pretty_to(hex_buf, data.data(), data.size())); float value = (float) get_16bit(0); for (int i = 0; i < data[3]; i++) From d946ddabfd312b552890d5516ca2445a8e6641d8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 16:37:16 -1000 Subject: [PATCH 0897/1145] [xiaomi_ble] Use stack-based hex formatting in verbose logging (#12793) --- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 32 ++++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 564870d74e..9f25063133 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -12,6 +12,9 @@ namespace xiaomi_ble { static const char *const TAG = "xiaomi_ble"; +// Maximum bytes to log in very verbose hex output (covers largest packet of ~24 bytes) +static constexpr size_t XIAOMI_MAX_LOG_BYTES = 32; + bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result) { // button pressed, 3 bytes, only byte 3 is used for supported devices so far if ((value_type == 0x1001) && (value_length == 3)) { @@ -263,7 +266,10 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address) { if ((raw.size() != 19) && ((raw.size() < 22) || (raw.size() > 24))) { ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size()); - ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(XIAOMI_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty_to(hex_buf, raw.data(), raw.size())); return false; } @@ -320,12 +326,17 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c memcpy(mac_address + 4, mac_reverse + 1, 1); memcpy(mac_address + 5, mac_reverse, 1); ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed."); - ESP_LOGVV(TAG, " MAC address : %s", format_mac_address_pretty(mac_address).c_str()); - ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); - ESP_LOGVV(TAG, " Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str()); - ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str()); - ESP_LOGVV(TAG, " Cipher : %s", format_hex_pretty(vector.ciphertext, vector.datasize).c_str()); - ESP_LOGVV(TAG, " Tag : %s", format_hex_pretty(vector.tag, vector.tagsize).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(mac_address, mac_buf); + char hex_buf[format_hex_pretty_size(XIAOMI_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, " MAC address : %s", mac_buf); + ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty_to(hex_buf, raw.data(), raw.size())); + ESP_LOGVV(TAG, " Key : %s", format_hex_pretty_to(hex_buf, vector.key, vector.keysize)); + ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty_to(hex_buf, vector.iv, vector.ivsize)); + ESP_LOGVV(TAG, " Cipher : %s", format_hex_pretty_to(hex_buf, vector.ciphertext, vector.datasize)); + ESP_LOGVV(TAG, " Tag : %s", format_hex_pretty_to(hex_buf, vector.tag, vector.tagsize)); mbedtls_ccm_free(&ctx); return false; } @@ -341,8 +352,11 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c raw[0] &= ~0x08; ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption passed."); - ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", format_hex_pretty(raw.data() + cipher_pos, vector.datasize).c_str(), - static_cast(raw[4])); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(XIAOMI_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", + format_hex_pretty_to(hex_buf, raw.data() + cipher_pos, vector.datasize), static_cast(raw[4])); mbedtls_ccm_free(&ctx); return true; From 64ba37633019b46e18e011b87285ee5922a90c72 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 16:37:38 -1000 Subject: [PATCH 0898/1145] [hte501] Use stack-based hex formatting in verbose logging (#12794) --- esphome/components/hte501/hte501.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/hte501/hte501.cpp b/esphome/components/hte501/hte501.cpp index b7d3be63fe..cde6886109 100644 --- a/esphome/components/hte501/hte501.cpp +++ b/esphome/components/hte501/hte501.cpp @@ -7,6 +7,8 @@ namespace hte501 { static const char *const TAG = "hte501"; +static constexpr size_t HTE501_SERIAL_NUMBER_SIZE = 7; + void HTE501Component::setup() { uint8_t address[] = {0x70, 0x29}; uint8_t identification[9]; @@ -16,7 +18,10 @@ void HTE501Component::setup() { this->mark_failed(); return; } - ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(identification + 0, 7).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char serial_hex[format_hex_size(HTE501_SERIAL_NUMBER_SIZE)]; +#endif + ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex_to(serial_hex, identification, HTE501_SERIAL_NUMBER_SIZE)); } void HTE501Component::dump_config() { From ace48464a8f435f43369a3dd791dede7f2b83647 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 16:39:44 -1000 Subject: [PATCH 0899/1145] [addressable_light] Use StringRef to avoid allocation when saving effect name (#12759) --- .../addressable_light/addressable_light_display.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/addressable_light/addressable_light_display.h b/esphome/components/addressable_light/addressable_light_display.h index f47389fd05..53f8604b7d 100644 --- a/esphome/components/addressable_light/addressable_light_display.h +++ b/esphome/components/addressable_light/addressable_light_display.h @@ -25,11 +25,13 @@ class AddressableLightDisplay : public display::DisplayBuffer { if (enabled_ && !enabled) { // enabled -> disabled // - Tell the parent light to refresh, effectively wiping the display. Also // restores the previous effect (if any). - light_state_->make_call().set_effect(this->last_effect_).perform(); + if (this->last_effect_index_.has_value()) { + light_state_->make_call().set_effect(*this->last_effect_index_).perform(); + } } else if (!enabled_ && enabled) { // disabled -> enabled - // - Save the current effect. - this->last_effect_ = light_state_->get_effect_name(); + // - Save the current effect index. + this->last_effect_index_ = light_state_->get_current_effect_index(); // - Disable any current effect. light_state_->make_call().set_effect(0).perform(); } @@ -56,7 +58,7 @@ class AddressableLightDisplay : public display::DisplayBuffer { int32_t width_; int32_t height_; std::vector addressable_light_buffer_; - optional last_effect_; + optional last_effect_index_; optional> pixel_mapper_f_; }; } // namespace addressable_light From 016eeef04afcdb5665ad7a882d97ee4e01b4ffc4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 16:40:06 -1000 Subject: [PATCH 0900/1145] [tee501] Use stack-based hex formatting in verbose logging (#12795) --- esphome/components/tee501/tee501.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/tee501/tee501.cpp b/esphome/components/tee501/tee501.cpp index d6513dbbe0..06481b628b 100644 --- a/esphome/components/tee501/tee501.cpp +++ b/esphome/components/tee501/tee501.cpp @@ -7,6 +7,8 @@ namespace tee501 { static const char *const TAG = "tee501"; +static constexpr size_t TEE501_SERIAL_NUMBER_SIZE = 7; + void TEE501Component::setup() { uint8_t address[] = {0x70, 0x29}; uint8_t identification[9]; @@ -17,7 +19,10 @@ void TEE501Component::setup() { this->mark_failed(); return; } - ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(identification + 0, 7).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char serial_hex[format_hex_size(TEE501_SERIAL_NUMBER_SIZE)]; +#endif + ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex_to(serial_hex, identification, TEE501_SERIAL_NUMBER_SIZE)); } void TEE501Component::dump_config() { From c3ffc1635d90fdf73258b4140c8c682b7693b7f7 Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Sat, 3 Jan 2026 03:40:28 +0100 Subject: [PATCH 0901/1145] =?UTF-8?q?[gps]=20add=20icon=20for=20HDOP=20and?= =?UTF-8?q?=20use=20correct=20state=5Fclass=20for=20longitude=20and?= =?UTF-8?q?=E2=80=A6=20(#12718)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- esphome/components/gps/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index a872cf7015..2135189bd5 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( CONF_SPEED, DEVICE_CLASS_SPEED, STATE_CLASS_MEASUREMENT, + STATE_CLASS_MEASUREMENT_ANGLE, UNIT_DEGREES, UNIT_KILOMETER_PER_HOUR, UNIT_METER, @@ -21,6 +22,7 @@ CONF_HDOP = "hdop" ICON_ALTIMETER = "mdi:altimeter" ICON_COMPASS = "mdi:compass" +ICON_CIRCLE_DOUBLE = "mdi:circle-double" ICON_LATITUDE = "mdi:latitude" ICON_LONGITUDE = "mdi:longitude" ICON_SATELLITE = "mdi:satellite-variant" @@ -50,7 +52,7 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_DEGREES, icon=ICON_LONGITUDE, accuracy_decimals=6, - state_class=STATE_CLASS_MEASUREMENT, + state_class=STATE_CLASS_MEASUREMENT_ANGLE, ), cv.Optional(CONF_SPEED): sensor.sensor_schema( unit_of_measurement=UNIT_KILOMETER_PER_HOUR, @@ -63,7 +65,7 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_DEGREES, icon=ICON_COMPASS, accuracy_decimals=2, - state_class=STATE_CLASS_MEASUREMENT, + state_class=STATE_CLASS_MEASUREMENT_ANGLE, ), cv.Optional(CONF_ALTITUDE): sensor.sensor_schema( unit_of_measurement=UNIT_METER, @@ -72,11 +74,14 @@ CONFIG_SCHEMA = cv.All( state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_SATELLITES): sensor.sensor_schema( + # no unit_of_measurement icon=ICON_SATELLITE, accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HDOP): sensor.sensor_schema( + # no unit_of_measurement + icon=ICON_CIRCLE_DOUBLE, accuracy_decimals=3, state_class=STATE_CLASS_MEASUREMENT, ), From bc1af007b44d2bb67a72f966d58eba556253e6d4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 16:40:47 -1000 Subject: [PATCH 0902/1145] [vbus] Use stack-based hex formatting in verbose logging (#12796) --- esphome/components/vbus/vbus.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/vbus/vbus.cpp b/esphome/components/vbus/vbus.cpp index e474dcfe17..b9496a08de 100644 --- a/esphome/components/vbus/vbus.cpp +++ b/esphome/components/vbus/vbus.cpp @@ -8,6 +8,9 @@ namespace vbus { static const char *const TAG = "vbus"; +// Maximum bytes to log in verbose hex output (16 frames * 4 bytes = 64 bytes typical) +static constexpr size_t VBUS_MAX_LOG_BYTES = 64; + void VBus::dump_config() { ESP_LOGCONFIG(TAG, "VBus:"); check_uart_settings(9600); @@ -101,8 +104,11 @@ void VBus::loop() { this->buffer_.push_back(this->fbytes_[i]); if (++this->cframe_ < this->frames_) continue; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_size(VBUS_MAX_LOG_BYTES)]; +#endif ESP_LOGV(TAG, "P2 C%04x %04x->%04x: %s", this->command_, this->source_, this->dest_, - format_hex(this->buffer_).c_str()); + format_hex_to(hex_buf, this->buffer_.data(), this->buffer_.size())); for (auto &listener : this->listeners_) listener->on_message(this->command_, this->source_, this->dest_, this->buffer_); this->state_ = 0; From 6409970f6e5e64c2694f685138ee1888f2140f58 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 16:41:02 -1000 Subject: [PATCH 0903/1145] [uponor_smatrix] Use stack-based hex formatting in verbose logging (#12797) Co-authored-by: Stefan Rado <628587+kroimon@users.noreply.github.com> --- esphome/components/uponor_smatrix/uponor_smatrix.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/uponor_smatrix/uponor_smatrix.cpp b/esphome/components/uponor_smatrix/uponor_smatrix.cpp index 221f07c80e..4c3a4b05df 100644 --- a/esphome/components/uponor_smatrix/uponor_smatrix.cpp +++ b/esphome/components/uponor_smatrix/uponor_smatrix.cpp @@ -8,6 +8,9 @@ namespace uponor_smatrix { static const char *const TAG = "uponor_smatrix"; +// Maximum bytes to log in verbose hex output +static constexpr size_t UPONOR_MAX_LOG_BYTES = 36; + void UponorSmatrixComponent::setup() { #ifdef USE_TIME if (this->time_id_ != nullptr) { @@ -97,8 +100,11 @@ bool UponorSmatrixComponent::parse_byte_(uint8_t byte) { return false; } +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_size(UPONOR_MAX_LOG_BYTES)]; +#endif ESP_LOGV(TAG, "Received packet: addr=%08X, data=%s, crc=%04X", device_address, - format_hex(&packet[4], packet_len - 6).c_str(), crc); + format_hex_to(hex_buf, &packet[4], packet_len - 6), crc); // Handle packet size_t data_len = (packet_len - 6) / 3; From c4d339a4c9be9035f1a4d6a1d5df213252cbba9d Mon Sep 17 00:00:00 2001 From: Robert Klep Date: Sat, 3 Jan 2026 05:42:18 +0100 Subject: [PATCH 0904/1145] [core] Add CONF_ON_START (#12439) (#12440) --- esphome/components/esp32_improv/__init__.py | 3 +-- esphome/components/voice_assistant/__init__.py | 2 +- esphome/const.py | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 2e69d400ca..ad2f057163 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -3,7 +3,7 @@ import esphome.codegen as cg from esphome.components import binary_sensor, esp32_ble, improv_base, output from esphome.components.esp32_ble import BTLoggers import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID +from esphome.const import CONF_ID, CONF_ON_START, CONF_ON_STATE, CONF_TRIGGER_ID AUTO_LOAD = ["esp32_ble_server", "improv_base"] CODEOWNERS = ["@jesserockz"] @@ -15,7 +15,6 @@ CONF_BLE_SERVER_ID = "ble_server_id" CONF_IDENTIFY_DURATION = "identify_duration" CONF_ON_PROVISIONED = "on_provisioned" CONF_ON_PROVISIONING = "on_provisioning" -CONF_ON_START = "on_start" CONF_ON_STOP = "on_stop" CONF_STATUS_INDICATOR = "status_indicator" CONF_WIFI_TIMEOUT = "wifi_timeout" diff --git a/esphome/components/voice_assistant/__init__.py b/esphome/components/voice_assistant/__init__.py index 59c7ec8383..d28c786dd8 100644 --- a/esphome/components/voice_assistant/__init__.py +++ b/esphome/components/voice_assistant/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( CONF_ON_CLIENT_DISCONNECTED, CONF_ON_ERROR, CONF_ON_IDLE, + CONF_ON_START, CONF_SPEAKER, ) @@ -24,7 +25,6 @@ CONF_ON_INTENT_END = "on_intent_end" CONF_ON_INTENT_PROGRESS = "on_intent_progress" CONF_ON_INTENT_START = "on_intent_start" CONF_ON_LISTENING = "on_listening" -CONF_ON_START = "on_start" CONF_ON_STT_END = "on_stt_end" CONF_ON_STT_VAD_END = "on_stt_vad_end" CONF_ON_STT_VAD_START = "on_stt_vad_start" diff --git a/esphome/const.py b/esphome/const.py index 1d46e81f9d..518247aa60 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -710,6 +710,7 @@ CONF_ON_RELEASE = "on_release" CONF_ON_RESPONSE = "on_response" CONF_ON_SHUTDOWN = "on_shutdown" CONF_ON_SPEED_SET = "on_speed_set" +CONF_ON_START = "on_start" CONF_ON_STATE = "on_state" CONF_ON_SUCCESS = "on_success" CONF_ON_TAG = "on_tag" From 2a5be725c8d807715e6ea748a4ee2ea85c91cf7d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 19:50:30 -1000 Subject: [PATCH 0905/1145] [api] Enable zero-copy bytes SOURCE_BOTH messages (#12816) --- esphome/components/api/api.proto | 4 ++-- esphome/components/api/api_pb2.h | 16 ++++++++-------- script/api_protobuf/api_protobuf.py | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 43b721c2d5..5efd839673 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -2425,7 +2425,7 @@ message ZWaveProxyFrame { option (ifdef) = "USE_ZWAVE_PROXY"; option (no_delay) = true; - bytes data = 1 [(pointer_to_buffer) = true]; + bytes data = 1; } enum ZWaveProxyRequestType { @@ -2439,5 +2439,5 @@ message ZWaveProxyRequest { option (ifdef) = "USE_ZWAVE_PROXY"; ZWaveProxyRequestType type = 1; - bytes data = 2 [(pointer_to_buffer) = true]; + bytes data = 2; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 6275b4c211..e08cf65bb9 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1046,7 +1046,7 @@ class SubscribeLogsRequest final : public ProtoDecodableMessage { class SubscribeLogsResponse final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 29; - static constexpr uint8_t ESTIMATED_SIZE = 11; + static constexpr uint8_t ESTIMATED_SIZE = 21; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "subscribe_logs_response"; } #endif @@ -1069,7 +1069,7 @@ class SubscribeLogsResponse final : public ProtoMessage { class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 124; - static constexpr uint8_t ESTIMATED_SIZE = 9; + static constexpr uint8_t ESTIMATED_SIZE = 19; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "noise_encryption_set_key_request"; } #endif @@ -1161,7 +1161,7 @@ class HomeassistantActionRequest final : public ProtoMessage { class HomeassistantActionResponse final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 130; - static constexpr uint8_t ESTIMATED_SIZE = 24; + static constexpr uint8_t ESTIMATED_SIZE = 34; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "homeassistant_action_response"; } #endif @@ -1388,7 +1388,7 @@ class ListEntitiesCameraResponse final : public InfoResponseProtoMessage { class CameraImageResponse final : public StateResponseProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 44; - static constexpr uint8_t ESTIMATED_SIZE = 20; + static constexpr uint8_t ESTIMATED_SIZE = 30; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "camera_image_response"; } #endif @@ -2123,7 +2123,7 @@ class BluetoothGATTReadRequest final : public ProtoDecodableMessage { class BluetoothGATTReadResponse final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 74; - static constexpr uint8_t ESTIMATED_SIZE = 17; + static constexpr uint8_t ESTIMATED_SIZE = 27; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_read_response"; } #endif @@ -2146,7 +2146,7 @@ class BluetoothGATTReadResponse final : public ProtoMessage { class BluetoothGATTWriteRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 75; - static constexpr uint8_t ESTIMATED_SIZE = 19; + static constexpr uint8_t ESTIMATED_SIZE = 29; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_write_request"; } #endif @@ -2182,7 +2182,7 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage { class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 77; - static constexpr uint8_t ESTIMATED_SIZE = 17; + static constexpr uint8_t ESTIMATED_SIZE = 27; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; } #endif @@ -2218,7 +2218,7 @@ class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage { class BluetoothGATTNotifyDataResponse final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 79; - static constexpr uint8_t ESTIMATED_SIZE = 17; + static constexpr uint8_t ESTIMATED_SIZE = 27; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_notify_data_response"; } #endif diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 5b68c6a3d2..7293f2abbc 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -362,12 +362,12 @@ def create_field_type_info( # Traditional fixed array approach with copy (takes priority) return FixedArrayBytesType(field, fixed_size) - # For SOURCE_CLIENT only messages (decode but no encode), use pointer + # For messages that decode (SOURCE_CLIENT or SOURCE_BOTH), use pointer # for zero-copy access to the receive buffer - if needs_decode and not needs_encode: + if needs_decode: return PointerToBytesBufferType(field, None) - # For SOURCE_BOTH/SOURCE_SERVER, explicit annotation is still needed + # For SOURCE_SERVER (encode only), explicit annotation is still needed if get_field_opt(field, pb.pointer_to_buffer, False): return PointerToBytesBufferType(field, None) From 00fd4f2fdd316a58ce3f104d7ea296592e10b0d9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 19:51:07 -1000 Subject: [PATCH 0906/1145] [esp8266] Exclude unused waveform code to save ~596 bytes RAM (#12690) --- esphome/components/esp8266/__init__.py | 29 ++++++++++- esphome/components/esp8266/const.py | 20 ++++++++ .../esp8266/exclude_waveform.py.script | 50 +++++++++++++++++++ esphome/components/esp8266/waveform_stubs.cpp | 34 +++++++++++++ esphome/components/esp8266_pwm/output.py | 5 +- script/ci-custom.py | 2 + 6 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 esphome/components/esp8266/exclude_waveform.py.script create mode 100644 esphome/components/esp8266/waveform_stubs.cpp diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index c4969a79b2..77ccaf52c1 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -28,6 +28,7 @@ from .const import ( KEY_ESP8266, KEY_FLASH_SIZE, KEY_PIN_INITIAL_STATES, + KEY_WAVEFORM_REQUIRED, esp8266_ns, ) from .gpio import PinInitialState, add_pin_initial_states_array @@ -192,7 +193,12 @@ async def to_code(config): cg.add_platformio_option( "extra_scripts", - ["pre:testing_mode.py", "pre:exclude_updater.py", "post:post_build.py"], + [ + "pre:testing_mode.py", + "pre:exclude_updater.py", + "pre:exclude_waveform.py", + "post:post_build.py", + ], ) conf = config[CONF_FRAMEWORK] @@ -264,10 +270,24 @@ async def to_code(config): cg.add_platformio_option("board_build.ldscript", ld_script) CORE.add_job(add_pin_initial_states_array) + CORE.add_job(finalize_waveform_config) + + +@coroutine_with_priority(CoroPriority.WORKAROUNDS) +async def finalize_waveform_config() -> None: + """Add waveform stubs define if waveform is not required. + + This runs at WORKAROUNDS priority (-999) to ensure all components + have had a chance to call require_waveform() first. + """ + if not CORE.data.get(KEY_ESP8266, {}).get(KEY_WAVEFORM_REQUIRED, False): + # No component needs waveform - enable stubs and exclude Arduino waveform code + # Use build flag (visible to both C++ code and PlatformIO script) + cg.add_build_flag("-DUSE_ESP8266_WAVEFORM_STUBS") # Called by writer.py -def copy_files(): +def copy_files() -> None: dir = Path(__file__).parent post_build_file = dir / "post_build.py.script" copy_file_if_changed( @@ -284,3 +304,8 @@ def copy_files(): exclude_updater_file, CORE.relative_build_path("exclude_updater.py"), ) + exclude_waveform_file = dir / "exclude_waveform.py.script" + copy_file_if_changed( + exclude_waveform_file, + CORE.relative_build_path("exclude_waveform.py"), + ) diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py index b718306b01..14425cde68 100644 --- a/esphome/components/esp8266/const.py +++ b/esphome/components/esp8266/const.py @@ -1,4 +1,5 @@ import esphome.codegen as cg +from esphome.core import CORE KEY_ESP8266 = "esp8266" KEY_BOARD = "board" @@ -6,6 +7,25 @@ KEY_PIN_INITIAL_STATES = "pin_initial_states" CONF_RESTORE_FROM_FLASH = "restore_from_flash" CONF_EARLY_PIN_INIT = "early_pin_init" KEY_FLASH_SIZE = "flash_size" +KEY_WAVEFORM_REQUIRED = "waveform_required" # esp8266 namespace is already defined by arduino, manually prefix esphome esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266") + + +def require_waveform() -> None: + """Mark that Arduino waveform/PWM support is required. + + Call this from components that need the Arduino waveform generator + (startWaveform, stopWaveform, analogWrite, Tone, Servo). + + If no component calls this, the waveform code is excluded from the build + to save ~596 bytes of RAM and 464 bytes of flash. + + Example: + from esphome.components.esp8266.const import require_waveform + + async def to_code(config): + require_waveform() + """ + CORE.data.setdefault(KEY_ESP8266, {})[KEY_WAVEFORM_REQUIRED] = True diff --git a/esphome/components/esp8266/exclude_waveform.py.script b/esphome/components/esp8266/exclude_waveform.py.script new file mode 100644 index 0000000000..35d6bc31f6 --- /dev/null +++ b/esphome/components/esp8266/exclude_waveform.py.script @@ -0,0 +1,50 @@ +# pylint: disable=E0602 +Import("env") # noqa + +import os + +# Filter out waveform/PWM code from the Arduino core build +# This saves ~596 bytes of RAM and 464 bytes of flash by not +# instantiating the waveform generator state structures (wvfState + pwmState). +# +# The waveform code is used by: analogWrite, Tone, Servo, and direct +# startWaveform/stopWaveform calls. ESPHome's esp8266_pwm component +# calls require_waveform() to keep this code when needed. +# +# When excluded, we provide stub implementations of stopWaveform() and +# _stopPWM() since digitalWrite() calls these unconditionally. + + +def has_define_flag(env, name): + """Check if a define exists in the build flags.""" + define_flag = f"-D{name}" + # Check BUILD_FLAGS (where ESPHome puts its defines) + for flag in env.get("BUILD_FLAGS", []): + if flag == define_flag or flag.startswith(f"{define_flag}="): + return True + # Also check CPPDEFINES list (parsed defines) + for define in env.get("CPPDEFINES", []): + if isinstance(define, tuple): + if define[0] == name: + return True + elif define == name: + return True + return False + +# USE_ESP8266_WAVEFORM_STUBS is defined when no component needs waveform +if has_define_flag(env, "USE_ESP8266_WAVEFORM_STUBS"): + + def filter_waveform_from_core(env, node): + """Filter callback to exclude waveform files from framework build.""" + path = node.get_path() + filename = os.path.basename(path) + if filename in ( + "core_esp8266_waveform_pwm.cpp", + "core_esp8266_waveform_phase.cpp", + ): + print(f"ESPHome: Excluding {filename} from build (waveform not required)") + return None + return node + + # Apply the filter to framework sources + env.AddBuildMiddleware(filter_waveform_from_core, "**/cores/esp8266/*.cpp") diff --git a/esphome/components/esp8266/waveform_stubs.cpp b/esphome/components/esp8266/waveform_stubs.cpp new file mode 100644 index 0000000000..686e03c6a9 --- /dev/null +++ b/esphome/components/esp8266/waveform_stubs.cpp @@ -0,0 +1,34 @@ +#ifdef USE_ESP8266_WAVEFORM_STUBS + +// Stub implementations for Arduino waveform/PWM functions. +// +// When the waveform generator is not needed (no esp8266_pwm component), +// we exclude core_esp8266_waveform_pwm.cpp from the build to save ~596 bytes +// of RAM and 464 bytes of flash. +// +// These stubs satisfy calls from the Arduino GPIO code when the real +// waveform implementation is excluded. They must be in the global namespace +// with C linkage to match the Arduino core function declarations. + +#include + +// Empty namespace to satisfy linter - actual stubs must be at global scope +namespace esphome::esp8266 {} // namespace esphome::esp8266 + +extern "C" { + +// Called by Arduino GPIO code to stop any waveform on a pin +int stopWaveform(uint8_t pin) { + (void) pin; + return 1; // Success (no waveform to stop) +} + +// Called by Arduino GPIO code to stop any PWM on a pin +bool _stopPWM(uint8_t pin) { + (void) pin; + return false; // No PWM was running +} + +} // extern "C" + +#endif // USE_ESP8266_WAVEFORM_STUBS diff --git a/esphome/components/esp8266_pwm/output.py b/esphome/components/esp8266_pwm/output.py index 2ddf4b9014..a78831c516 100644 --- a/esphome/components/esp8266_pwm/output.py +++ b/esphome/components/esp8266_pwm/output.py @@ -1,6 +1,7 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import output +from esphome.components.esp8266.const import require_waveform import esphome.config_validation as cv from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_NUMBER, CONF_PIN @@ -34,7 +35,9 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config) -> None: + require_waveform() + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await output.register_output(var, config) diff --git a/script/ci-custom.py b/script/ci-custom.py index 609d89403f..f0676d594b 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -552,6 +552,8 @@ def convert_path_to_relative(abspath, current): exclude=[ "esphome/components/libretiny/generate_components.py", "esphome/components/web_server/__init__.py", + # const.py has absolute import in docstring example for external components + "esphome/components/esp8266/const.py", ], ) def lint_relative_py_import(fname: Path, line, col, content): From 98e3695c897974ae746232192947df5c079bbb2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 3 Jan 2026 06:45:17 +0000 Subject: [PATCH 0907/1145] Bump aioesphomeapi from 43.9.1 to 43.10.0 (#12821) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d457be9cd2..63cc7e4a16 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.9.1 +aioesphomeapi==43.10.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.17 # dashboard_import From 538c6544a0430d20f60e17215d3050e601caff3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 20:51:56 -1000 Subject: [PATCH 0908/1145] Bump ruamel-yaml from 0.18.17 to 0.19.1 (#12768) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 63cc7e4a16..833ccbb0ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ esphome-dashboard==20251013.0 aioesphomeapi==43.10.0 zeroconf==0.148.0 puremagic==1.30 -ruamel.yaml==0.18.17 # dashboard_import +ruamel.yaml==0.19.1 # dashboard_import ruamel.yaml.clib==0.2.15 # dashboard_import esphome-glyphsets==0.2.0 pillow==11.3.0 From 89b550b74a429b13d5c8e145a24b5e6c17dbfa4e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 21:00:46 -1000 Subject: [PATCH 0909/1145] [tests] Remove reserved / character from entity names in component tests (#12820) --- tests/components/micronova/common.yaml | 2 +- tests/components/opentherm/common.yaml | 8 ++++---- tests/components/zhlt01/common.yaml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/components/micronova/common.yaml b/tests/components/micronova/common.yaml index 660970350a..5870d3726f 100644 --- a/tests/components/micronova/common.yaml +++ b/tests/components/micronova/common.yaml @@ -41,7 +41,7 @@ sensor: switch: - platform: micronova stove: - name: Stove on/off + name: Stove text_sensor: - platform: micronova diff --git a/tests/components/opentherm/common.yaml b/tests/components/opentherm/common.yaml index 1e58a04bf0..fb5fb39eb8 100644 --- a/tests/components/opentherm/common.yaml +++ b/tests/components/opentherm/common.yaml @@ -92,7 +92,7 @@ sensor: ch_pump_starts: name: "Boiler Number of starts CH pump" dhw_pump_valve_starts: - name: "Boiler Number of starts DHW pump/valve" + name: "Boiler Number of starts DHW pump valve" dhw_burner_starts: name: "Boiler Number of starts burner during DHW mode" burner_operation_hours: @@ -139,7 +139,7 @@ binary_sensor: dhw_present: name: "Boiler DHW present" control_type_on_off: - name: "Boiler Control type is on/off" + name: "Boiler Control type is on-off" cooling_supported: name: "Boiler Cooling supported" dhw_storage_tank: @@ -153,9 +153,9 @@ binary_sensor: max_ch_setpoint_transfer_enabled: name: "Boiler CH maximum setpoint transfer enabled" dhw_setpoint_rw: - name: "Boiler DHW setpoint read/write" + name: "Boiler DHW setpoint read-write" max_ch_setpoint_rw: - name: "Boiler CH maximum setpoint read/write" + name: "Boiler CH maximum setpoint read-write" switch: - platform: opentherm diff --git a/tests/components/zhlt01/common.yaml b/tests/components/zhlt01/common.yaml index d0fd531c87..483f9f3c4e 100644 --- a/tests/components/zhlt01/common.yaml +++ b/tests/components/zhlt01/common.yaml @@ -1,4 +1,4 @@ climate: - platform: zhlt01 - name: ZH/LT-01 Climate + name: ZH-LT-01 Climate transmitter_id: xmitr From 95a7356ea08e7ba3a9c577b40339c1d7df1dda50 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Sat, 3 Jan 2026 10:43:17 +0100 Subject: [PATCH 0910/1145] [uart] make sure that all variables are initialized (#12823) --- esphome/components/uart/uart_component.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index fd528e228f..ea6e1562f4 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -189,10 +189,10 @@ class UARTComponent { size_t rx_buffer_size_; size_t rx_full_threshold_{1}; size_t rx_timeout_{0}; - uint32_t baud_rate_; - uint8_t stop_bits_; - uint8_t data_bits_; - UARTParityOptions parity_; + uint32_t baud_rate_{0}; + uint8_t stop_bits_{0}; + uint8_t data_bits_{0}; + UARTParityOptions parity_{UART_CONFIG_PARITY_NONE}; #ifdef USE_UART_DEBUGGER CallbackManager debug_callback_{}; #endif From 1d323c2d71f7566e413c052318338902c822b996 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 07:14:48 -1000 Subject: [PATCH 0911/1145] [api] Remove deprecated password authentication (#12819) --- esphome/components/api/__init__.py | 42 +++-------- esphome/components/api/api.proto | 30 ++++---- esphome/components/api/api_connection.cpp | 27 +------- esphome/components/api/api_connection.h | 6 -- esphome/components/api/api_pb2.cpp | 21 ------ esphome/components/api/api_pb2.h | 38 +--------- esphome/components/api/api_pb2_dump.cpp | 15 ---- esphome/components/api/api_pb2_service.cpp | 23 +------ esphome/components/api/api_pb2_service.h | 10 --- esphome/components/api/api_server.cpp | 36 ---------- esphome/components/api/api_server.h | 7 -- esphome/components/api/proto.h | 18 +---- .../fixtures/host_mode_api_password.yaml | 14 ---- .../test_host_mode_api_password.py | 69 ------------------- 14 files changed, 28 insertions(+), 328 deletions(-) delete mode 100644 tests/integration/fixtures/host_mode_api_password.yaml delete mode 100644 tests/integration/test_host_mode_api_password.py diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 88618acef4..0e2c612279 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -226,32 +226,6 @@ def _encryption_schema(config): return ENCRYPTION_SCHEMA(config) -def _validate_api_config(config: ConfigType) -> ConfigType: - """Validate API configuration with mutual exclusivity check and deprecation warning.""" - # Check if both password and encryption are configured - has_password = CONF_PASSWORD in config and config[CONF_PASSWORD] - has_encryption = CONF_ENCRYPTION in config - - if has_password and has_encryption: - raise cv.Invalid( - "The 'password' and 'encryption' options are mutually exclusive. " - "The API client only supports one authentication method at a time. " - "Please remove one of them. " - "Note: 'password' authentication is deprecated and will be removed in version 2026.1.0. " - "We strongly recommend using 'encryption' instead for better security." - ) - - # Warn about password deprecation - if has_password: - _LOGGER.warning( - "API 'password' authentication has been deprecated since May 2022 and will be removed in version 2026.1.0. " - "Please migrate to the 'encryption' configuration. " - "See https://esphome.io/components/api/#configuration-variables" - ) - - return config - - def _consume_api_sockets(config: ConfigType) -> ConfigType: """Register socket needs for API component.""" from esphome.components import socket @@ -268,7 +242,17 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(APIServer), cv.Optional(CONF_PORT, default=6053): cv.port, - cv.Optional(CONF_PASSWORD, default=""): cv.string_strict, + # Removed in 2026.1.0 - kept to provide helpful error message + cv.Optional(CONF_PASSWORD): cv.invalid( + "The 'password' option has been removed in ESPHome 2026.1.0.\n" + "Password authentication was deprecated in May 2022.\n" + "Please migrate to encryption for secure API communication:\n\n" + "api:\n" + " encryption:\n" + " key: !secret api_encryption_key\n\n" + "Generate a key with: openssl rand -base64 32\n" + "Or visit https://esphome.io/components/api/#configuration-variables" + ), cv.Optional( CONF_REBOOT_TIMEOUT, default="15min" ): cv.positive_time_period_milliseconds, @@ -330,7 +314,6 @@ CONFIG_SCHEMA = cv.All( } ).extend(cv.COMPONENT_SCHEMA), cv.rename_key(CONF_SERVICES, CONF_ACTIONS), - _validate_api_config, _consume_api_sockets, ) @@ -344,9 +327,6 @@ async def to_code(config: ConfigType) -> None: CORE.register_controller() cg.add(var.set_port(config[CONF_PORT])) - if config[CONF_PASSWORD]: - cg.add_define("USE_API_PASSWORD") - cg.add(var.set_password(config[CONF_PASSWORD])) cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY])) if CONF_LISTEN_BACKLOG in config: diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 5efd839673..652b456850 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -7,10 +7,7 @@ service APIConnection { option (needs_setup_connection) = false; option (needs_authentication) = false; } - rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse) { - option (needs_setup_connection) = false; - option (needs_authentication) = false; - } + // REMOVED in ESPHome 2026.1.0: rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse) rpc disconnect (DisconnectRequest) returns (DisconnectResponse) { option (needs_setup_connection) = false; option (needs_authentication) = false; @@ -82,14 +79,13 @@ service APIConnection { // * VarInt denoting the type of message. // * The message object encoded as a ProtoBuf message -// The connection is established in 4 steps: +// The connection is established in 2 steps: // * First, the client connects to the server and sends a "Hello Request" identifying itself -// * The server responds with a "Hello Response" and selects the protocol version -// * After receiving this message, the client attempts to authenticate itself using -// the password and a "Connect Request" -// * The server responds with a "Connect Response" and notifies of invalid password. +// * The server responds with a "Hello Response" and the connection is authenticated // If anything in this initial process fails, the connection must immediately closed // by both sides and _no_ disconnection message is to be sent. +// Note: Password authentication via AuthenticationRequest/AuthenticationResponse (message IDs 3, 4) +// was removed in ESPHome 2026.1.0. Those message IDs are reserved and should not be reused. // Message sent at the beginning of each connection // Can only be sent by the client and only at the beginning of the connection @@ -130,25 +126,23 @@ message HelloResponse { string name = 4; } -// Message sent at the beginning of each connection to authenticate the client -// Can only be sent by the client and only at the beginning of the connection +// DEPRECATED in ESPHome 2026.1.0 - Password authentication is no longer supported. +// These messages are kept for protocol documentation but are not processed by the server. +// Use noise encryption instead: https://esphome.io/components/api/#configuration-variables message AuthenticationRequest { option (id) = 3; option (source) = SOURCE_CLIENT; option (no_delay) = true; - option (ifdef) = "USE_API_PASSWORD"; + option deprecated = true; - // The password to log in with string password = 1; } -// Confirmation of successful connection. After this the connection is available for all traffic. -// Can only be sent by the server and only at the beginning of the connection message AuthenticationResponse { option (id) = 4; option (source) = SOURCE_SERVER; option (no_delay) = true; - option (ifdef) = "USE_API_PASSWORD"; + option deprecated = true; bool invalid_password = 1; } @@ -205,7 +199,9 @@ message DeviceInfoResponse { option (id) = 10; option (source) = SOURCE_SERVER; - bool uses_password = 1 [(field_ifdef) = "USE_API_PASSWORD"]; + // Deprecated in ESPHome 2026.1.0, but kept for backward compatibility + // with older ESPHome versions that still send this field. + bool uses_password = 1 [deprecated = true]; // The name of the node, given by "App.set_name()" string name = 2; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2ecd54bb00..3ded5e4408 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1535,27 +1535,11 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) { resp.set_server_info(ESPHOME_VERSION_REF); resp.set_name(StringRef(App.get_name())); -#ifdef USE_API_PASSWORD - // Password required - wait for authentication - this->flags_.connection_state = static_cast(ConnectionState::CONNECTED); -#else - // No password configured - auto-authenticate + // Auto-authenticate - password auth was removed in ESPHome 2026.1.0 this->complete_authentication_(); -#endif return this->send_message(resp, HelloResponse::MESSAGE_TYPE); } -#ifdef USE_API_PASSWORD -bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) { - AuthenticationResponse resp; - // bool invalid_password = 1; - resp.invalid_password = !this->parent_->check_password(msg.password.byte(), msg.password.size()); - if (!resp.invalid_password) { - this->complete_authentication_(); - } - return this->send_message(resp, AuthenticationResponse::MESSAGE_TYPE); -} -#endif // USE_API_PASSWORD bool APIConnection::send_ping_response(const PingRequest &msg) { PingResponse resp; @@ -1564,9 +1548,6 @@ bool APIConnection::send_ping_response(const PingRequest &msg) { bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { DeviceInfoResponse resp{}; -#ifdef USE_API_PASSWORD - resp.uses_password = true; -#endif resp.set_name(StringRef(App.get_name())); resp.set_friendly_name(StringRef(App.get_friendly_name())); #ifdef USE_AREAS @@ -1845,12 +1826,6 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { // Do not set last_traffic_ on send return true; } -#ifdef USE_API_PASSWORD -void APIConnection::on_unauthenticated_access() { - this->on_fatal_error(); - ESP_LOGD(TAG, "%s (%s) no authentication", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); -} -#endif void APIConnection::on_no_setup_connection() { this->on_fatal_error(); ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 59c42aa033..ffe3614f20 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -203,9 +203,6 @@ class APIConnection final : public APIServerConnection { void on_get_time_response(const GetTimeResponse &value) override; #endif bool send_hello_response(const HelloRequest &msg) override; -#ifdef USE_API_PASSWORD - bool send_authenticate_response(const AuthenticationRequest &msg) override; -#endif bool send_disconnect_response(const DisconnectRequest &msg) override; bool send_ping_response(const PingRequest &msg) override; bool send_device_info_response(const DeviceInfoRequest &msg) override; @@ -261,9 +258,6 @@ class APIConnection final : public APIServerConnection { } void on_fatal_error() override; -#ifdef USE_API_PASSWORD - void on_unauthenticated_access() override; -#endif void on_no_setup_connection() override; ProtoWriteBuffer create_buffer(uint32_t reserve_size) override { // FIXME: ensure no recursive writes can happen diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 698e08f9b3..d26b309552 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -43,21 +43,6 @@ void HelloResponse::calculate_size(ProtoSize &size) const { size.add_length(1, this->server_info_ref_.size()); size.add_length(1, this->name_ref_.size()); } -#ifdef USE_API_PASSWORD -bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->password = StringRef(reinterpret_cast(value.data()), value.size()); - break; - } - default: - return false; - } - return true; -} -void AuthenticationResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } -void AuthenticationResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); } -#endif #ifdef USE_AREAS void AreaInfo::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->area_id); @@ -81,9 +66,6 @@ void DeviceInfo::calculate_size(ProtoSize &size) const { } #endif void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { -#ifdef USE_API_PASSWORD - buffer.encode_bool(1, this->uses_password); -#endif buffer.encode_string(2, this->name_ref_); buffer.encode_string(3, this->mac_address_ref_); buffer.encode_string(4, this->esphome_version_ref_); @@ -139,9 +121,6 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { #endif } void DeviceInfoResponse::calculate_size(ProtoSize &size) const { -#ifdef USE_API_PASSWORD - size.add_bool(1, this->uses_password); -#endif size.add_length(1, this->name_ref_.size()); size.add_length(1, this->mac_address_ref_.size()); size.add_length(1, this->esphome_version_ref_.size()); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index e08cf65bb9..0605051e29 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -393,39 +393,6 @@ class HelloResponse final : public ProtoMessage { protected: }; -#ifdef USE_API_PASSWORD -class AuthenticationRequest final : public ProtoDecodableMessage { - public: - static constexpr uint8_t MESSAGE_TYPE = 3; - static constexpr uint8_t ESTIMATED_SIZE = 9; -#ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "authentication_request"; } -#endif - StringRef password{}; -#ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; -#endif - - protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; -}; -class AuthenticationResponse final : public ProtoMessage { - public: - static constexpr uint8_t MESSAGE_TYPE = 4; - static constexpr uint8_t ESTIMATED_SIZE = 2; -#ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "authentication_response"; } -#endif - bool invalid_password{false}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(ProtoSize &size) const override; -#ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; -#endif - - protected: -}; -#endif class DisconnectRequest final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 5; @@ -525,12 +492,9 @@ class DeviceInfo final : public ProtoMessage { class DeviceInfoResponse final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 10; - static constexpr uint16_t ESTIMATED_SIZE = 257; + static constexpr uint8_t ESTIMATED_SIZE = 255; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "device_info_response"; } -#endif -#ifdef USE_API_PASSWORD - bool uses_password{false}; #endif StringRef name_ref_{}; void set_name(const StringRef &ref) { this->name_ref_ = ref; } diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 1ec6645b3f..ac5f04f3fb 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -748,18 +748,6 @@ void HelloResponse::dump_to(std::string &out) const { dump_field(out, "server_info", this->server_info_ref_); dump_field(out, "name", this->name_ref_); } -#ifdef USE_API_PASSWORD -void AuthenticationRequest::dump_to(std::string &out) const { - MessageDumpHelper helper(out, "AuthenticationRequest"); - out.append(" password: "); - out.append("'").append(this->password.c_str(), this->password.size()).append("'"); - out.append("\n"); -} -void AuthenticationResponse::dump_to(std::string &out) const { - MessageDumpHelper helper(out, "AuthenticationResponse"); - dump_field(out, "invalid_password", this->invalid_password); -} -#endif void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); } void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); } void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); } @@ -782,9 +770,6 @@ void DeviceInfo::dump_to(std::string &out) const { #endif void DeviceInfoResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "DeviceInfoResponse"); -#ifdef USE_API_PASSWORD - dump_field(out, "uses_password", this->uses_password); -#endif dump_field(out, "name", this->name_ref_); dump_field(out, "mac_address", this->mac_address_ref_); dump_field(out, "esphome_version", this->esphome_version_ref_); diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 984cb0bb6e..c9bf638ad7 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -24,17 +24,6 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, this->on_hello_request(msg); break; } -#ifdef USE_API_PASSWORD - case AuthenticationRequest::MESSAGE_TYPE: { - AuthenticationRequest msg; - msg.decode(msg_data, msg_size); -#ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_authentication_request: %s", msg.dump().c_str()); -#endif - this->on_authentication_request(msg); - break; - } -#endif case DisconnectRequest::MESSAGE_TYPE: { DisconnectRequest msg; // Empty message: no decode needed @@ -643,13 +632,6 @@ void APIServerConnection::on_hello_request(const HelloRequest &msg) { this->on_fatal_error(); } } -#ifdef USE_API_PASSWORD -void APIServerConnection::on_authentication_request(const AuthenticationRequest &msg) { - if (!this->send_authenticate_response(msg)) { - this->on_fatal_error(); - } -} -#endif void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) { if (!this->send_disconnect_response(msg)) { this->on_fatal_error(); @@ -841,10 +823,7 @@ void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) { // Check authentication/connection requirements for messages switch (msg_type) { - case HelloRequest::MESSAGE_TYPE: // No setup required -#ifdef USE_API_PASSWORD - case AuthenticationRequest::MESSAGE_TYPE: // No setup required -#endif + case HelloRequest::MESSAGE_TYPE: // No setup required case DisconnectRequest::MESSAGE_TYPE: // No setup required case PingRequest::MESSAGE_TYPE: // No setup required break; // Skip all checks for these messages diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 261d9fbd27..e2a23827dc 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -26,10 +26,6 @@ class APIServerConnectionBase : public ProtoService { virtual void on_hello_request(const HelloRequest &value){}; -#ifdef USE_API_PASSWORD - virtual void on_authentication_request(const AuthenticationRequest &value){}; -#endif - virtual void on_disconnect_request(const DisconnectRequest &value){}; virtual void on_disconnect_response(const DisconnectResponse &value){}; virtual void on_ping_request(const PingRequest &value){}; @@ -228,9 +224,6 @@ class APIServerConnectionBase : public ProtoService { class APIServerConnection : public APIServerConnectionBase { public: virtual bool send_hello_response(const HelloRequest &msg) = 0; -#ifdef USE_API_PASSWORD - virtual bool send_authenticate_response(const AuthenticationRequest &msg) = 0; -#endif virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0; virtual bool send_ping_response(const PingRequest &msg) = 0; virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0; @@ -357,9 +350,6 @@ class APIServerConnection : public APIServerConnectionBase { #endif protected: void on_hello_request(const HelloRequest &msg) override; -#ifdef USE_API_PASSWORD - void on_authentication_request(const AuthenticationRequest &msg) override; -#endif void on_disconnect_request(const DisconnectRequest &msg) override; void on_ping_request(const PingRequest &msg) override; void on_device_info_request(const DeviceInfoRequest &msg) override; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 8b76740e4d..eedf8c7172 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -224,38 +224,6 @@ void APIServer::dump_config() { #endif } -#ifdef USE_API_PASSWORD -bool APIServer::check_password(const uint8_t *password_data, size_t password_len) const { - // depend only on input password length - const char *a = this->password_.c_str(); - uint32_t len_a = this->password_.length(); - const char *b = reinterpret_cast(password_data); - uint32_t len_b = password_len; - - // disable optimization with volatile - volatile uint32_t length = len_b; - volatile const char *left = nullptr; - volatile const char *right = b; - uint8_t result = 0; - - if (len_a == length) { - left = *((volatile const char **) &a); - result = 0; - } - if (len_a != length) { - left = b; - result = 1; - } - - for (size_t i = 0; i < length; i++) { - result |= *left++ ^ *right++; // NOLINT - } - - return result == 0; -} - -#endif - void APIServer::handle_disconnect(APIConnection *conn) {} // Macro for controller update dispatch @@ -377,10 +345,6 @@ float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; void APIServer::set_port(uint16_t port) { this->port_ = port; } -#ifdef USE_API_PASSWORD -void APIServer::set_password(const std::string &password) { this->password_ = password; } -#endif - void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; } #ifdef USE_API_HOMEASSISTANT_SERVICES diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index b855d2cce0..2b2e8bae73 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -59,10 +59,6 @@ class APIServer : public Component, #endif #ifdef USE_CAMERA void on_camera_image(const std::shared_ptr &image) override; -#endif -#ifdef USE_API_PASSWORD - bool check_password(const uint8_t *password_data, size_t password_len) const; - void set_password(const std::string &password); #endif void set_port(uint16_t port); void set_reboot_timeout(uint32_t reboot_timeout); @@ -256,9 +252,6 @@ class APIServer : public Component, // Vectors and strings (12 bytes each on 32-bit) std::vector> clients_; -#ifdef USE_API_PASSWORD - std::string password_; -#endif std::vector shared_write_buffer_; // Shared proto write buffer for all connections #ifdef USE_API_HOMEASSISTANT_STATES std::vector state_subs_; diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index efdab9341c..f8b5cd9a5d 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -833,9 +833,6 @@ class ProtoService { virtual bool is_authenticated() = 0; virtual bool is_connection_setup() = 0; virtual void on_fatal_error() = 0; -#ifdef USE_API_PASSWORD - virtual void on_unauthenticated_access() = 0; -#endif virtual void on_no_setup_connection() = 0; /** * Create a buffer with a reserved size. @@ -873,20 +870,7 @@ class ProtoService { return true; } - inline bool check_authenticated_() { -#ifdef USE_API_PASSWORD - if (!this->check_connection_setup_()) { - return false; - } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return false; - } - return true; -#else - return this->check_connection_setup_(); -#endif - } + inline bool check_authenticated_() { return this->check_connection_setup_(); } }; } // namespace esphome::api diff --git a/tests/integration/fixtures/host_mode_api_password.yaml b/tests/integration/fixtures/host_mode_api_password.yaml deleted file mode 100644 index 038b6871e0..0000000000 --- a/tests/integration/fixtures/host_mode_api_password.yaml +++ /dev/null @@ -1,14 +0,0 @@ -esphome: - name: host-mode-api-password -host: -api: - password: "test_password_123" -logger: - level: DEBUG -# Test sensor to verify connection works -sensor: - - platform: template - name: Test Sensor - id: test_sensor - lambda: return 42.0; - update_interval: 0.1s diff --git a/tests/integration/test_host_mode_api_password.py b/tests/integration/test_host_mode_api_password.py deleted file mode 100644 index 5c5e689e45..0000000000 --- a/tests/integration/test_host_mode_api_password.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Integration test for API password authentication.""" - -from __future__ import annotations - -import asyncio - -from aioesphomeapi import APIConnectionError, InvalidAuthAPIError -import pytest - -from .types import APIClientConnectedFactory, RunCompiledFunction - - -@pytest.mark.asyncio -async def test_host_mode_api_password( - yaml_config: str, - run_compiled: RunCompiledFunction, - api_client_connected: APIClientConnectedFactory, -) -> None: - """Test API authentication with password.""" - async with run_compiled(yaml_config): - # Connect with correct password - async with api_client_connected(password="test_password_123") as client: - # Verify we can get device info - device_info = await client.device_info() - assert device_info is not None - assert device_info.uses_password is True - assert device_info.name == "host-mode-api-password" - - # Subscribe to states to ensure authenticated connection works - loop = asyncio.get_running_loop() - state_future: asyncio.Future[bool] = loop.create_future() - states = {} - - def on_state(state): - states[state.key] = state - if not state_future.done(): - state_future.set_result(True) - - client.subscribe_states(on_state) - - # Wait for at least one state with timeout - try: - await asyncio.wait_for(state_future, timeout=5.0) - except TimeoutError: - pytest.fail("No states received within timeout") - - # Should have received at least one state (the test sensor) - assert len(states) > 0 - - # Test with wrong password - should fail - # Try connecting with wrong password - try: - async with api_client_connected( - password="wrong_password", timeout=5 - ) as client: - # If we get here without exception, try to use the connection - # which should fail if auth failed - await client.device_info_and_list_entities() - # If we successfully got device info and entities, auth didn't fail properly - pytest.fail("Connection succeeded with wrong password") - except (InvalidAuthAPIError, APIConnectionError) as e: - # Expected - auth should fail - # Accept either InvalidAuthAPIError or generic APIConnectionError - # since the client might not always distinguish - assert ( - "password" in str(e).lower() - or "auth" in str(e).lower() - or "invalid" in str(e).lower() - ) From 69867bf8187a1bfc491ec657ed5a42b195c37283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20Kry=C5=84ski?= Date: Sat, 3 Jan 2026 19:58:56 +0100 Subject: [PATCH 0912/1145] [nrf52, zephyr] move nrf52-specific code to nrf52 component (#12582) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston --- esphome/components/logger/__init__.py | 2 +- esphome/components/nrf52/__init__.py | 16 +++++++++++- esphome/components/nrf52/gpio.py | 9 ++++++- esphome/components/zephyr/__init__.py | 30 ++++++++--------------- esphome/components/zephyr/core.cpp | 9 ++++++- esphome/components/zephyr/gpio.cpp | 29 +++++----------------- esphome/components/zephyr/gpio.h | 13 +++++++--- esphome/components/zephyr/preferences.cpp | 2 ++ 8 files changed, 60 insertions(+), 50 deletions(-) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 8968a5eab8..7132cd8956 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -386,7 +386,7 @@ async def to_code(config): except cv.Invalid: pass - if CORE.using_zephyr: + if CORE.is_nrf52: if config[CONF_HARDWARE_UART] == UART0: zephyr_add_overlay("""&uart0 { status = "okay";};""") if config[CONF_HARDWARE_UART] == UART1: diff --git a/esphome/components/nrf52/__init__.py b/esphome/components/nrf52/__init__.py index 03927e8ea2..bf90a41df5 100644 --- a/esphome/components/nrf52/__init__.py +++ b/esphome/components/nrf52/__init__.py @@ -12,6 +12,7 @@ from esphome.components.zephyr import ( zephyr_add_prj_conf, zephyr_data, zephyr_set_core_data, + zephyr_setup_preferences, zephyr_to_code, ) from esphome.components.zephyr.const import ( @@ -49,7 +50,7 @@ from .const import ( from .gpio import nrf52_pin_to_code # noqa CODEOWNERS = ["@tomaszduda23"] -AUTO_LOAD = ["zephyr"] +AUTO_LOAD = ["zephyr", "preferences"] IS_TARGET_PLATFORM = True _LOGGER = logging.getLogger(__name__) @@ -194,6 +195,7 @@ async def to_code(config: ConfigType) -> None: cg.add_platformio_option("board_upload.require_upload_port", "true") cg.add_platformio_option("board_upload.wait_for_upload_port", "true") + zephyr_setup_preferences() zephyr_to_code(config) if dfu_config := config.get(CONF_DFU): @@ -206,6 +208,18 @@ async def to_code(config: ConfigType) -> None: if reg0_config[CONF_UICR_ERASE]: cg.add_define("USE_NRF52_UICR_ERASE") + # c++ support + zephyr_add_prj_conf("CPLUSPLUS", True) + zephyr_add_prj_conf("LIB_CPLUSPLUS", True) + # watchdog + zephyr_add_prj_conf("WATCHDOG", True) + zephyr_add_prj_conf("WDT_DISABLE_AT_BOOT", False) + # disable console + zephyr_add_prj_conf("UART_CONSOLE", False) + zephyr_add_prj_conf("CONSOLE", False) + # use NFC pins as GPIO + zephyr_add_prj_conf("NFCT_PINS_AS_GPIOS", True) + @coroutine_with_priority(CoroPriority.DIAGNOSTICS) async def _dfu_to_code(dfu_config): diff --git a/esphome/components/nrf52/gpio.py b/esphome/components/nrf52/gpio.py index 17329042b2..498e8cc330 100644 --- a/esphome/components/nrf52/gpio.py +++ b/esphome/components/nrf52/gpio.py @@ -71,8 +71,15 @@ NRF52_PIN_SCHEMA = cv.All( @pins.PIN_SCHEMA_REGISTRY.register(PLATFORM_NRF52, NRF52_PIN_SCHEMA) async def nrf52_pin_to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) num = config[CONF_NUMBER] + port = num // 32 + pin_name_prefix = f"P{port}." + var = cg.new_Pvariable( + config[CONF_ID], + cg.RawExpression(f"DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpio{port}))"), + 32, + pin_name_prefix, + ) cg.add(var.set_pin(num)) # Only set if true to avoid bloating setup() function # (inverted bit in pin_flags_ bitfield is zero-initialized to false) diff --git a/esphome/components/zephyr/__init__.py b/esphome/components/zephyr/__init__.py index 0381fbcba9..a91d976e6b 100644 --- a/esphome/components/zephyr/__init__.py +++ b/esphome/components/zephyr/__init__.py @@ -21,7 +21,6 @@ from .const import ( ) CODEOWNERS = ["@tomaszduda23"] -AUTO_LOAD = ["preferences"] PrjConfValueType = bool | str | int @@ -111,32 +110,15 @@ def add_extra_script(stage: str, filename: str, path: Path) -> None: def zephyr_to_code(config): - cg.add(zephyr_ns.setup_preferences()) cg.add_build_flag("-DUSE_ZEPHYR") cg.set_cpp_standard("gnu++20") # build is done by west so bypass board checking in platformio cg.add_platformio_option("boards_dir", CORE.relative_build_path("boards")) - # c++ support zephyr_add_prj_conf("NEWLIB_LIBC", True) - zephyr_add_prj_conf("CONFIG_FPU", True) + zephyr_add_prj_conf("FPU", True) zephyr_add_prj_conf("NEWLIB_LIBC_FLOAT_PRINTF", True) - zephyr_add_prj_conf("CPLUSPLUS", True) - zephyr_add_prj_conf("CONFIG_STD_CPP20", True) - zephyr_add_prj_conf("LIB_CPLUSPLUS", True) - # preferences - zephyr_add_prj_conf("SETTINGS", True) - zephyr_add_prj_conf("NVS", True) - zephyr_add_prj_conf("FLASH_MAP", True) - zephyr_add_prj_conf("CONFIG_FLASH", True) - # watchdog - zephyr_add_prj_conf("WATCHDOG", True) - zephyr_add_prj_conf("WDT_DISABLE_AT_BOOT", False) - # disable console - zephyr_add_prj_conf("UART_CONSOLE", False) - zephyr_add_prj_conf("CONSOLE", False, False) - # use NFC pins as GPIO - zephyr_add_prj_conf("NFCT_PINS_AS_GPIOS", True) + zephyr_add_prj_conf("STD_CPP20", True) # os: ***** USAGE FAULT ***** # os: Illegal load of EXC_RETURN into PC @@ -149,6 +131,14 @@ def zephyr_to_code(config): ) +def zephyr_setup_preferences(): + cg.add(zephyr_ns.setup_preferences()) + zephyr_add_prj_conf("SETTINGS", True) + zephyr_add_prj_conf("NVS", True) + zephyr_add_prj_conf("FLASH_MAP", True) + zephyr_add_prj_conf("FLASH", True) + + def _format_prj_conf_val(value: PrjConfValueType) -> str: if isinstance(value, bool): return "y" if value else "n" diff --git a/esphome/components/zephyr/core.cpp b/esphome/components/zephyr/core.cpp index d5427a0ebf..46589cdb62 100644 --- a/esphome/components/zephyr/core.cpp +++ b/esphome/components/zephyr/core.cpp @@ -10,8 +10,10 @@ namespace esphome { +#ifdef CONFIG_WATCHDOG static int wdt_channel_id = -1; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static const device *const WDT = DEVICE_DT_GET(DT_ALIAS(watchdog0)); +#endif void yield() { ::k_yield(); } uint32_t millis() { return k_ticks_to_ms_floor32(k_uptime_ticks()); } @@ -20,6 +22,7 @@ void delayMicroseconds(uint32_t us) { ::k_usleep(us); } void delay(uint32_t ms) { ::k_msleep(ms); } void arch_init() { +#ifdef CONFIG_WATCHDOG if (device_is_ready(WDT)) { static wdt_timeout_cfg wdt_config{}; wdt_config.flags = WDT_FLAG_RESET_SOC; @@ -36,12 +39,15 @@ void arch_init() { wdt_setup(WDT, options); } } +#endif } void arch_feed_wdt() { +#ifdef CONFIG_WATCHDOG if (wdt_channel_id >= 0) { wdt_feed(WDT, wdt_channel_id); } +#endif } void arch_restart() { sys_reboot(SYS_REBOOT_COLD); } @@ -72,6 +78,7 @@ bool random_bytes(uint8_t *data, size_t len) { return true; } +#ifdef USE_NRF52 void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) mac[0] = ((NRF_FICR->DEVICEADDR[1] & 0xFFFF) >> 8) | 0xC0; mac[1] = NRF_FICR->DEVICEADDR[1] & 0xFFFF; @@ -80,7 +87,7 @@ void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parame mac[4] = NRF_FICR->DEVICEADDR[0] >> 8; mac[5] = NRF_FICR->DEVICEADDR[0]; } - +#endif } // namespace esphome void setup(); diff --git a/esphome/components/zephyr/gpio.cpp b/esphome/components/zephyr/gpio.cpp index 8041c361cc..1d5b0f282b 100644 --- a/esphome/components/zephyr/gpio.cpp +++ b/esphome/components/zephyr/gpio.cpp @@ -50,25 +50,7 @@ void ZephyrGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Inte } void ZephyrGPIOPin::setup() { - const struct device *gpio = nullptr; - if (this->pin_ < 32) { -#define GPIO0 DT_NODELABEL(gpio0) -#if DT_NODE_HAS_STATUS(GPIO0, okay) - gpio = DEVICE_DT_GET(GPIO0); -#else -#error "gpio0 is disabled" -#endif - } else { -#define GPIO1 DT_NODELABEL(gpio1) -#if DT_NODE_HAS_STATUS(GPIO1, okay) - gpio = DEVICE_DT_GET(GPIO1); -#else -#error "gpio1 is disabled" -#endif - } - if (device_is_ready(gpio)) { - this->gpio_ = gpio; - } else { + if (!device_is_ready(this->gpio_)) { ESP_LOGE(TAG, "gpio %u is not ready.", this->pin_); return; } @@ -79,21 +61,22 @@ void ZephyrGPIOPin::pin_mode(gpio::Flags flags) { if (nullptr == this->gpio_) { return; } - auto ret = gpio_pin_configure(this->gpio_, this->pin_ % 32, flags_to_mode(flags, this->inverted_, this->value_)); + auto ret = gpio_pin_configure(this->gpio_, this->pin_ % this->gpio_size_, + flags_to_mode(flags, this->inverted_, this->value_)); if (ret != 0) { ESP_LOGE(TAG, "gpio %u cannot be configured %d.", this->pin_, ret); } } size_t ZephyrGPIOPin::dump_summary(char *buffer, size_t len) const { - return snprintf(buffer, len, "GPIO%u, P%u.%u", this->pin_, this->pin_ / 32, this->pin_ % 32); + return snprintf(buffer, len, "GPIO%u, %s%u", this->pin_, this->pin_name_prefix_, this->pin_ % this->gpio_size_); } bool ZephyrGPIOPin::digital_read() { if (nullptr == this->gpio_) { return false; } - return bool(gpio_pin_get(this->gpio_, this->pin_ % 32) != this->inverted_); + return bool(gpio_pin_get(this->gpio_, this->pin_ % this->gpio_size_) != this->inverted_); } void ZephyrGPIOPin::digital_write(bool value) { @@ -103,7 +86,7 @@ void ZephyrGPIOPin::digital_write(bool value) { if (nullptr == this->gpio_) { return; } - gpio_pin_set(this->gpio_, this->pin_ % 32, value != this->inverted_ ? 1 : 0); + gpio_pin_set(this->gpio_, this->pin_ % this->gpio_size_, value != this->inverted_ ? 1 : 0); } void ZephyrGPIOPin::detach_interrupt() const { // TODO diff --git a/esphome/components/zephyr/gpio.h b/esphome/components/zephyr/gpio.h index b405f385bc..c9540f4f01 100644 --- a/esphome/components/zephyr/gpio.h +++ b/esphome/components/zephyr/gpio.h @@ -8,6 +8,11 @@ namespace zephyr { class ZephyrGPIOPin : public InternalGPIOPin { public: + ZephyrGPIOPin(const device *gpio, int gpio_size, const char *pin_name_prefix) { + this->gpio_ = gpio; + this->gpio_size_ = gpio_size; + this->pin_name_prefix_ = pin_name_prefix; + } void set_pin(uint8_t pin) { this->pin_ = pin; } void set_inverted(bool inverted) { this->inverted_ = inverted; } void set_flags(gpio::Flags flags) { this->flags_ = flags; } @@ -25,10 +30,12 @@ class ZephyrGPIOPin : public InternalGPIOPin { protected: void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; - uint8_t pin_; - bool inverted_{}; - gpio::Flags flags_{}; const device *gpio_{nullptr}; + const char *pin_name_prefix_{nullptr}; + gpio::Flags flags_{}; + uint8_t pin_; + uint8_t gpio_size_{}; + bool inverted_{}; bool value_{false}; }; diff --git a/esphome/components/zephyr/preferences.cpp b/esphome/components/zephyr/preferences.cpp index d702366044..08b361b8fb 100644 --- a/esphome/components/zephyr/preferences.cpp +++ b/esphome/components/zephyr/preferences.cpp @@ -1,4 +1,5 @@ #ifdef USE_ZEPHYR +#ifdef CONFIG_SETTINGS #include #include "esphome/core/preferences.h" @@ -154,3 +155,4 @@ ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const } // namespace esphome #endif +#endif From c34665f6500bb6449236ef583790249d88647d1a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 09:13:07 -1000 Subject: [PATCH 0913/1145] [api] Fix KeyError when running logs after password removal (#12831) --- esphome/components/api/client.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index ca1fc089fa..200d0938bd 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -16,7 +16,7 @@ with warnings.catch_warnings(): import contextlib -from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__ +from esphome.const import CONF_KEY, CONF_PORT, __version__ from esphome.core import CORE from . import CONF_ENCRYPTION @@ -35,7 +35,6 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None: conf = config["api"] name = config["esphome"]["name"] port: int = int(conf[CONF_PORT]) - password: str = conf[CONF_PASSWORD] noise_psk: str | None = None if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)): noise_psk = key @@ -50,7 +49,7 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None: cli = APIClient( addresses[0], # Primary address for compatibility port, - password, + "", # Password auth removed in 2026.1.0 client_info=f"ESPHome Logs {__version__}", noise_psk=noise_psk, addresses=addresses, # Pass all addresses for automatic retry From 5cfcf8d104fae1b3c77e34c4278cd9510252e640 Mon Sep 17 00:00:00 2001 From: Jasper van der Neut - Stulen Date: Sat, 3 Jan 2026 21:51:48 +0100 Subject: [PATCH 0914/1145] [mhz19] Make detection range configurable (#12677) Co-authored-by: Fabio Pugliese Ornellas --- esphome/components/mhz19/mhz19.cpp | 44 ++++++++++++++++++++++++++++++ esphome/components/mhz19/mhz19.h | 29 +++++++++++++++++++- esphome/components/mhz19/sensor.py | 41 ++++++++++++++++++++++++++++ tests/components/mhz19/common.yaml | 1 + 4 files changed, 114 insertions(+), 1 deletion(-) diff --git a/esphome/components/mhz19/mhz19.cpp b/esphome/components/mhz19/mhz19.cpp index c3c8120362..00e6e14d85 100644 --- a/esphome/components/mhz19/mhz19.cpp +++ b/esphome/components/mhz19/mhz19.cpp @@ -13,6 +13,9 @@ static const uint8_t MHZ19_COMMAND_GET_PPM[] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x static const uint8_t MHZ19_COMMAND_ABC_ENABLE[] = {0xFF, 0x01, 0x79, 0xA0, 0x00, 0x00, 0x00, 0x00}; static const uint8_t MHZ19_COMMAND_ABC_DISABLE[] = {0xFF, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00}; static const uint8_t MHZ19_COMMAND_CALIBRATE_ZERO[] = {0xFF, 0x01, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00}; +static const uint8_t MHZ19_COMMAND_DETECTION_RANGE_0_2000PPM[] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x07, 0xD0}; +static const uint8_t MHZ19_COMMAND_DETECTION_RANGE_0_5000PPM[] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x13, 0x88}; +static const uint8_t MHZ19_COMMAND_DETECTION_RANGE_0_10000PPM[] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x27, 0x10}; uint8_t mhz19_checksum(const uint8_t *command) { uint8_t sum = 0; @@ -28,6 +31,8 @@ void MHZ19Component::setup() { } else if (this->abc_boot_logic_ == MHZ19_ABC_DISABLED) { this->abc_disable(); } + + this->range_set(this->detection_range_); } void MHZ19Component::update() { @@ -86,6 +91,26 @@ void MHZ19Component::abc_disable() { this->mhz19_write_command_(MHZ19_COMMAND_ABC_DISABLE, nullptr); } +void MHZ19Component::range_set(MHZ19DetectionRange detection_ppm) { + switch (detection_ppm) { + case MHZ19_DETECTION_RANGE_DEFAULT: + ESP_LOGV(TAG, "Using previously set detection range (no change)"); + break; + case MHZ19_DETECTION_RANGE_0_2000PPM: + ESP_LOGD(TAG, "Setting detection range to 0 to 2000ppm"); + this->mhz19_write_command_(MHZ19_COMMAND_DETECTION_RANGE_0_2000PPM, nullptr); + break; + case MHZ19_DETECTION_RANGE_0_5000PPM: + ESP_LOGD(TAG, "Setting detection range to 0 to 5000ppm"); + this->mhz19_write_command_(MHZ19_COMMAND_DETECTION_RANGE_0_5000PPM, nullptr); + break; + case MHZ19_DETECTION_RANGE_0_10000PPM: + ESP_LOGD(TAG, "Setting detection range to 0 to 10000ppm"); + this->mhz19_write_command_(MHZ19_COMMAND_DETECTION_RANGE_0_10000PPM, nullptr); + break; + } +} + bool MHZ19Component::mhz19_write_command_(const uint8_t *command, uint8_t *response) { // Empty RX Buffer while (this->available()) @@ -99,7 +124,9 @@ bool MHZ19Component::mhz19_write_command_(const uint8_t *command, uint8_t *respo return this->read_array(response, MHZ19_RESPONSE_LENGTH); } + float MHZ19Component::get_setup_priority() const { return setup_priority::DATA; } + void MHZ19Component::dump_config() { ESP_LOGCONFIG(TAG, "MH-Z19:"); LOG_SENSOR(" ", "CO2", this->co2_sensor_); @@ -113,6 +140,23 @@ void MHZ19Component::dump_config() { } ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_); + + const char *range_str; + switch (this->detection_range_) { + case MHZ19_DETECTION_RANGE_DEFAULT: + range_str = "default"; + break; + case MHZ19_DETECTION_RANGE_0_2000PPM: + range_str = "0 to 2000ppm"; + break; + case MHZ19_DETECTION_RANGE_0_5000PPM: + range_str = "0 to 5000ppm"; + break; + case MHZ19_DETECTION_RANGE_0_10000PPM: + range_str = "0 to 10000ppm"; + break; + } + ESP_LOGCONFIG(TAG, " Detection range: %s", range_str); } } // namespace mhz19 diff --git a/esphome/components/mhz19/mhz19.h b/esphome/components/mhz19/mhz19.h index be36886d62..feb67bdd82 100644 --- a/esphome/components/mhz19/mhz19.h +++ b/esphome/components/mhz19/mhz19.h @@ -8,7 +8,18 @@ namespace esphome { namespace mhz19 { -enum MHZ19ABCLogic { MHZ19_ABC_NONE = 0, MHZ19_ABC_ENABLED, MHZ19_ABC_DISABLED }; +enum MHZ19ABCLogic { + MHZ19_ABC_NONE = 0, + MHZ19_ABC_ENABLED, + MHZ19_ABC_DISABLED, +}; + +enum MHZ19DetectionRange { + MHZ19_DETECTION_RANGE_DEFAULT = 0, + MHZ19_DETECTION_RANGE_0_2000PPM, + MHZ19_DETECTION_RANGE_0_5000PPM, + MHZ19_DETECTION_RANGE_0_10000PPM, +}; class MHZ19Component : public PollingComponent, public uart::UARTDevice { public: @@ -21,11 +32,13 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice { void calibrate_zero(); void abc_enable(); void abc_disable(); + void range_set(MHZ19DetectionRange detection_ppm); void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; } void set_abc_enabled(bool abc_enabled) { abc_boot_logic_ = abc_enabled ? MHZ19_ABC_ENABLED : MHZ19_ABC_DISABLED; } void set_warmup_seconds(uint32_t seconds) { warmup_seconds_ = seconds; } + void set_detection_range(MHZ19DetectionRange detection_range) { detection_range_ = detection_range; } protected: bool mhz19_write_command_(const uint8_t *command, uint8_t *response); @@ -33,7 +46,10 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice { sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *co2_sensor_{nullptr}; MHZ19ABCLogic abc_boot_logic_{MHZ19_ABC_NONE}; + uint32_t warmup_seconds_; + + MHZ19DetectionRange detection_range_{MHZ19_DETECTION_RANGE_DEFAULT}; }; template class MHZ19CalibrateZeroAction : public Action { @@ -66,5 +82,16 @@ template class MHZ19ABCDisableAction : public Action { MHZ19Component *mhz19_; }; +template class MHZ19DetectionRangeSetAction : public Action { + public: + MHZ19DetectionRangeSetAction(MHZ19Component *mhz19) : mhz19_(mhz19) {} + TEMPLATABLE_VALUE(MHZ19DetectionRange, detection_range) + + void play(const Ts &...x) override { this->mhz19_->range_set(this->detection_range_.value(x...)); } + + protected: + MHZ19Component *mhz19_; +}; + } // namespace mhz19 } // namespace esphome diff --git a/esphome/components/mhz19/sensor.py b/esphome/components/mhz19/sensor.py index 106636a6ba..0156ee4be0 100644 --- a/esphome/components/mhz19/sensor.py +++ b/esphome/components/mhz19/sensor.py @@ -19,6 +19,7 @@ DEPENDENCIES = ["uart"] CONF_AUTOMATIC_BASELINE_CALIBRATION = "automatic_baseline_calibration" CONF_WARMUP_TIME = "warmup_time" +CONF_DETECTION_RANGE = "detection_range" mhz19_ns = cg.esphome_ns.namespace("mhz19") MHZ19Component = mhz19_ns.class_("MHZ19Component", cg.PollingComponent, uart.UARTDevice) @@ -27,6 +28,18 @@ MHZ19CalibrateZeroAction = mhz19_ns.class_( ) MHZ19ABCEnableAction = mhz19_ns.class_("MHZ19ABCEnableAction", automation.Action) MHZ19ABCDisableAction = mhz19_ns.class_("MHZ19ABCDisableAction", automation.Action) +MHZ19DetectionRangeSetAction = mhz19_ns.class_( + "MHZ19DetectionRangeSetAction", automation.Action +) + +mhz19_detection_range = mhz19_ns.enum("MHZ19DetectionRange") +MHZ19_DETECTION_RANGE_ENUM = { + 2000: mhz19_detection_range.MHZ19_DETECTION_RANGE_0_2000PPM, + 5000: mhz19_detection_range.MHZ19_DETECTION_RANGE_0_5000PPM, + 10000: mhz19_detection_range.MHZ19_DETECTION_RANGE_0_10000PPM, +} + +_validate_ppm = cv.float_with_unit("parts per million", "ppm") CONFIG_SCHEMA = ( cv.Schema( @@ -49,6 +62,9 @@ CONFIG_SCHEMA = ( cv.Optional( CONF_WARMUP_TIME, default="75s" ): cv.positive_time_period_seconds, + cv.Optional(CONF_DETECTION_RANGE): cv.All( + _validate_ppm, cv.enum(MHZ19_DETECTION_RANGE_ENUM) + ), } ) .extend(cv.polling_component_schema("60s")) @@ -78,6 +94,9 @@ async def to_code(config): cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME])) + if CONF_DETECTION_RANGE in config: + cg.add(var.set_detection_range(config[CONF_DETECTION_RANGE])) + CALIBRATION_ACTION_SCHEMA = maybe_simple_id( { @@ -98,3 +117,25 @@ CALIBRATION_ACTION_SCHEMA = maybe_simple_id( async def mhz19_calibration_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) + + +RANGE_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(MHZ19Component), + cv.Required(CONF_DETECTION_RANGE): cv.All( + _validate_ppm, cv.enum(MHZ19_DETECTION_RANGE_ENUM) + ), + } +) + + +@automation.register_action( + "mhz19.detection_range_set", MHZ19DetectionRangeSetAction, RANGE_ACTION_SCHEMA +) +async def mhz19_detection_range_set_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + detection_range = config.get(CONF_DETECTION_RANGE) + template_ = await cg.templatable(detection_range, args, mhz19_detection_range) + cg.add(var.set_detection_range(template_)) + return var diff --git a/tests/components/mhz19/common.yaml b/tests/components/mhz19/common.yaml index 94989fecbe..b12ca50197 100644 --- a/tests/components/mhz19/common.yaml +++ b/tests/components/mhz19/common.yaml @@ -6,3 +6,4 @@ sensor: name: MH-Z19 Temperature automatic_baseline_calibration: false update_interval: 15s + detection_range: 5000ppm From ede7391582cc2842f89de62efb105d2836481a3b Mon Sep 17 00:00:00 2001 From: Conrad Juhl Andersen Date: Sat, 3 Jan 2026 23:06:33 +0100 Subject: [PATCH 0915/1145] [wts01] Fix negative values for WTS01 sensor (#12835) --- esphome/components/wts01/wts01.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/esphome/components/wts01/wts01.cpp b/esphome/components/wts01/wts01.cpp index cb910d89cf..a7948c805a 100644 --- a/esphome/components/wts01/wts01.cpp +++ b/esphome/components/wts01/wts01.cpp @@ -71,17 +71,20 @@ void WTS01Sensor::process_packet_() { } // Extract temperature value - int8_t temp = this->buffer_[6]; - int32_t sign = 1; + const uint8_t raw = this->buffer_[6]; - // Handle negative temperatures - if (temp < 0) { - sign = -1; + // WTS01 encodes sign in bit 7, magnitude in bits 0-6 + const bool negative = (raw & 0x80) != 0; + const uint8_t magnitude = raw & 0x7F; + + const float decimal = static_cast(this->buffer_[7]) / 100.0f; + + float temperature = static_cast(magnitude) + decimal; + + if (negative) { + temperature = -temperature; } - // Calculate temperature (temp + decimal/100) - float temperature = static_cast(temp) + (sign * static_cast(this->buffer_[7]) / 100.0f); - ESP_LOGV(TAG, "Received new temperature: %.2f°C", temperature); this->publish_state(temperature); From a6e9aa78765b4659fcbb51c893faa6b9c9f42753 Mon Sep 17 00:00:00 2001 From: Jasper van der Neut - Stulen Date: Sat, 3 Jan 2026 23:11:02 +0100 Subject: [PATCH 0916/1145] [mhz19] Refactor Actions to Parented (#12837) --- esphome/components/mhz19/mhz19.h | 35 +++++++----------------------- esphome/components/mhz19/sensor.py | 33 +++++++++++++++++----------- 2 files changed, 28 insertions(+), 40 deletions(-) diff --git a/esphome/components/mhz19/mhz19.h b/esphome/components/mhz19/mhz19.h index feb67bdd82..5898bab649 100644 --- a/esphome/components/mhz19/mhz19.h +++ b/esphome/components/mhz19/mhz19.h @@ -52,45 +52,26 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice { MHZ19DetectionRange detection_range_{MHZ19_DETECTION_RANGE_DEFAULT}; }; -template class MHZ19CalibrateZeroAction : public Action { +template class MHZ19CalibrateZeroAction : public Action, public Parented { public: - MHZ19CalibrateZeroAction(MHZ19Component *mhz19) : mhz19_(mhz19) {} - - void play(const Ts &...x) override { this->mhz19_->calibrate_zero(); } - - protected: - MHZ19Component *mhz19_; + void play(const Ts &...x) override { this->parent_->calibrate_zero(); } }; -template class MHZ19ABCEnableAction : public Action { +template class MHZ19ABCEnableAction : public Action, public Parented { public: - MHZ19ABCEnableAction(MHZ19Component *mhz19) : mhz19_(mhz19) {} - - void play(const Ts &...x) override { this->mhz19_->abc_enable(); } - - protected: - MHZ19Component *mhz19_; + void play(const Ts &...x) override { this->parent_->abc_enable(); } }; -template class MHZ19ABCDisableAction : public Action { +template class MHZ19ABCDisableAction : public Action, public Parented { public: - MHZ19ABCDisableAction(MHZ19Component *mhz19) : mhz19_(mhz19) {} - - void play(const Ts &...x) override { this->mhz19_->abc_disable(); } - - protected: - MHZ19Component *mhz19_; + void play(const Ts &...x) override { this->parent_->abc_disable(); } }; -template class MHZ19DetectionRangeSetAction : public Action { +template class MHZ19DetectionRangeSetAction : public Action, public Parented { public: - MHZ19DetectionRangeSetAction(MHZ19Component *mhz19) : mhz19_(mhz19) {} TEMPLATABLE_VALUE(MHZ19DetectionRange, detection_range) - void play(const Ts &...x) override { this->mhz19_->range_set(this->detection_range_.value(x...)); } - - protected: - MHZ19Component *mhz19_; + void play(const Ts &...x) override { this->parent_->range_set(this->detection_range_.value(x...)); } }; } // namespace mhz19 diff --git a/esphome/components/mhz19/sensor.py b/esphome/components/mhz19/sensor.py index 0156ee4be0..1f698be404 100644 --- a/esphome/components/mhz19/sensor.py +++ b/esphome/components/mhz19/sensor.py @@ -24,12 +24,18 @@ CONF_DETECTION_RANGE = "detection_range" mhz19_ns = cg.esphome_ns.namespace("mhz19") MHZ19Component = mhz19_ns.class_("MHZ19Component", cg.PollingComponent, uart.UARTDevice) MHZ19CalibrateZeroAction = mhz19_ns.class_( - "MHZ19CalibrateZeroAction", automation.Action + "MHZ19CalibrateZeroAction", automation.Action, cg.Parented.template(MHZ19Component) +) +MHZ19ABCEnableAction = mhz19_ns.class_( + "MHZ19ABCEnableAction", automation.Action, cg.Parented.template(MHZ19Component) +) +MHZ19ABCDisableAction = mhz19_ns.class_( + "MHZ19ABCDisableAction", automation.Action, cg.Parented.template(MHZ19Component) ) -MHZ19ABCEnableAction = mhz19_ns.class_("MHZ19ABCEnableAction", automation.Action) -MHZ19ABCDisableAction = mhz19_ns.class_("MHZ19ABCDisableAction", automation.Action) MHZ19DetectionRangeSetAction = mhz19_ns.class_( - "MHZ19DetectionRangeSetAction", automation.Action + "MHZ19DetectionRangeSetAction", + automation.Action, + cg.Parented.template(MHZ19Component), ) mhz19_detection_range = mhz19_ns.enum("MHZ19DetectionRange") @@ -98,7 +104,7 @@ async def to_code(config): cg.add(var.set_detection_range(config[CONF_DETECTION_RANGE])) -CALIBRATION_ACTION_SCHEMA = maybe_simple_id( +NO_ARGS_ACTION_SCHEMA = maybe_simple_id( { cv.Required(CONF_ID): cv.use_id(MHZ19Component), } @@ -106,17 +112,18 @@ CALIBRATION_ACTION_SCHEMA = maybe_simple_id( @automation.register_action( - "mhz19.calibrate_zero", MHZ19CalibrateZeroAction, CALIBRATION_ACTION_SCHEMA + "mhz19.calibrate_zero", MHZ19CalibrateZeroAction, NO_ARGS_ACTION_SCHEMA ) @automation.register_action( - "mhz19.abc_enable", MHZ19ABCEnableAction, CALIBRATION_ACTION_SCHEMA + "mhz19.abc_enable", MHZ19ABCEnableAction, NO_ARGS_ACTION_SCHEMA ) @automation.register_action( - "mhz19.abc_disable", MHZ19ABCDisableAction, CALIBRATION_ACTION_SCHEMA + "mhz19.abc_disable", MHZ19ABCDisableAction, NO_ARGS_ACTION_SCHEMA ) -async def mhz19_calibration_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 mhz19_no_args_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var RANGE_ACTION_SCHEMA = maybe_simple_id( @@ -133,8 +140,8 @@ RANGE_ACTION_SCHEMA = maybe_simple_id( "mhz19.detection_range_set", MHZ19DetectionRangeSetAction, RANGE_ACTION_SCHEMA ) async def mhz19_detection_range_set_to_code(config, action_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - var = cg.new_Pvariable(action_id, template_arg, paren) + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) detection_range = config.get(CONF_DETECTION_RANGE) template_ = await cg.templatable(detection_range, args, mhz19_detection_range) cg.add(var.set_detection_range(template_)) From 0a0501c1403206c2f7ee6e6f68252e3680142dce Mon Sep 17 00:00:00 2001 From: John Hollowell Date: Sat, 3 Jan 2026 17:11:48 -0500 Subject: [PATCH 0917/1145] Fix comment typos (#12828) --- esphome/components/i2c/__init__.py | 2 +- esphome/components/one_wire/__init__.py | 2 +- esphome/components/uart/__init__.py | 2 +- esphome/cpp_generator.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 56e0c8e4ab..19efda0b49 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -250,7 +250,7 @@ async def register_i2c_device(var, config): Sets the i2c bus to use and the i2c address. - This is a coroutine, you need to await it with a 'yield' expression! + This is a coroutine, you need to await it with an 'await' expression! """ parent = await cg.get_variable(config[CONF_I2C_ID]) cg.add(var.set_i2c_bus(parent)) diff --git a/esphome/components/one_wire/__init__.py b/esphome/components/one_wire/__init__.py index e12cca3e27..9173b7014b 100644 --- a/esphome/components/one_wire/__init__.py +++ b/esphome/components/one_wire/__init__.py @@ -32,7 +32,7 @@ async def register_one_wire_device(var, config): Sets the 1-wire bus to use and the 1-wire address. - This is a coroutine, you need to await it with a 'yield' expression! + This is a coroutine, you need to await it with an 'await' expression! """ parent = await cg.get_variable(config[CONF_ONE_WIRE_ID]) cg.add(var.set_one_wire_bus(parent)) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 9baa6ebd81..9ec95964ec 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -482,7 +482,7 @@ def final_validate_device_schema( async def register_uart_device(var, config): """Register a UART device, setting up all the internal values. - This is a coroutine, you need to await it with a 'yield' expression! + This is a coroutine, you need to await it with an 'await' expression! """ parent = await cg.get_variable(config[CONF_UART_ID]) cg.add(var.set_uart_parent(parent)) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index ddccb574e4..cff0748c95 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -643,7 +643,7 @@ async def get_variable(id_: ID) -> "MockObj": Wait for the given ID to be defined in the code generation and return it as a MockObj. - This is a coroutine, you need to await it with a 'await' expression! + This is a coroutine, you need to await it with an 'await' expression! :param id_: The ID to retrieve :return: The variable as a MockObj. @@ -656,7 +656,7 @@ async def get_variable_with_full_id(id_: ID) -> tuple[ID, "MockObj"]: Wait for the given ID to be defined in the code generation and return it as a MockObj. - This is a coroutine, you need to await it with a 'await' expression! + This is a coroutine, you need to await it with an 'await' expression! :param id_: The ID to retrieve :return: The variable as a MockObj. From 9781073f2a78aa0f416a5c7ea09090bfc8f53223 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 12:31:38 -1000 Subject: [PATCH 0918/1145] [espnow] Use stack-based MAC formatting and remove dead code (#12836) --- .../components/espnow/espnow_component.cpp | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/esphome/components/espnow/espnow_component.cpp b/esphome/components/espnow/espnow_component.cpp index 16e2331937..991803d870 100644 --- a/esphome/components/espnow/espnow_component.cpp +++ b/esphome/components/espnow/espnow_component.cpp @@ -64,18 +64,6 @@ static const LogString *espnow_error_to_str(esp_err_t error) { } } -std::string peer_str(uint8_t *peer) { - if (peer == nullptr || peer[0] == 0) { - return "[Not Set]"; - } else if (memcmp(peer, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) { - return "[Broadcast]"; - } else if (memcmp(peer, ESPNOW_MULTICAST_ADDR, ESP_NOW_ETH_ALEN) == 0) { - return "[Multicast]"; - } else { - return format_mac_address_pretty(peer); - } -} - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) void on_send_report(const esp_now_send_info_t *info, esp_now_send_status_t status) #else @@ -140,11 +128,13 @@ void ESPNowComponent::dump_config() { ESP_LOGCONFIG(TAG, " Disabled"); return; } + char own_addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(this->own_address_, own_addr_buf); ESP_LOGCONFIG(TAG, " Own address: %s\n" " Version: v%" PRIu32 "\n" " Wi-Fi channel: %d", - format_mac_address_pretty(this->own_address_).c_str(), version, this->wifi_channel_); + own_addr_buf, version, this->wifi_channel_); #ifdef USE_WIFI ESP_LOGCONFIG(TAG, " Wi-Fi enabled: %s", YESNO(this->is_wifi_enabled())); #endif @@ -300,9 +290,12 @@ void ESPNowComponent::loop() { // Intentionally left as if instead of else in case the peer is added above if (esp_now_is_peer_exist(info.src_addr)) { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char src_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + char dst_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; char hex_buf[format_hex_pretty_size(ESP_NOW_MAX_DATA_LEN)]; - ESP_LOGV(TAG, "<<< [%s -> %s] %s", format_mac_address_pretty(info.src_addr).c_str(), - format_mac_address_pretty(info.des_addr).c_str(), + format_mac_addr_upper(info.src_addr, src_buf); + format_mac_addr_upper(info.des_addr, dst_buf); + ESP_LOGV(TAG, "<<< [%s -> %s] %s", src_buf, dst_buf, format_hex_pretty_to(hex_buf, packet->packet_.receive.data, packet->packet_.receive.size)); #endif if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) { @@ -321,8 +314,9 @@ void ESPNowComponent::loop() { } case ESPNowPacket::SENT: { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE - ESP_LOGV(TAG, ">>> [%s] %s", format_mac_address_pretty(packet->packet_.sent.address).c_str(), - LOG_STR_ARG(espnow_error_to_str(packet->packet_.sent.status))); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(packet->packet_.sent.address, addr_buf); + ESP_LOGV(TAG, ">>> [%s] %s", addr_buf, LOG_STR_ARG(espnow_error_to_str(packet->packet_.sent.status))); #endif if (this->current_send_packet_ != nullptr) { this->current_send_packet_->callback_(packet->packet_.sent.status); @@ -409,8 +403,9 @@ void ESPNowComponent::send_() { this->current_send_packet_ = packet; esp_err_t err = esp_now_send(packet->address_, packet->data_, packet->size_); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send packet to %s - %s", format_mac_address_pretty(packet->address_).c_str(), - LOG_STR_ARG(espnow_error_to_str(err))); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(packet->address_, addr_buf); + ESP_LOGE(TAG, "Failed to send packet to %s - %s", addr_buf, LOG_STR_ARG(espnow_error_to_str(err))); if (packet->callback_ != nullptr) { packet->callback_(err); } @@ -439,8 +434,9 @@ esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) { esp_err_t err = esp_now_add_peer(&peer_info); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to add peer %s - %s", format_mac_address_pretty(peer).c_str(), - LOG_STR_ARG(espnow_error_to_str(err))); + char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(peer, peer_buf); + ESP_LOGE(TAG, "Failed to add peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err))); this->status_momentary_warning("peer-add-failed"); return err; } @@ -468,8 +464,9 @@ esp_err_t ESPNowComponent::del_peer(const uint8_t *peer) { if (esp_now_is_peer_exist(peer)) { esp_err_t err = esp_now_del_peer(peer); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to delete peer %s - %s", format_mac_address_pretty(peer).c_str(), - LOG_STR_ARG(espnow_error_to_str(err))); + char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(peer, peer_buf); + ESP_LOGE(TAG, "Failed to delete peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err))); this->status_momentary_warning("peer-del-failed"); return err; } From d505f0316b7df4ccd2afc0fe3f58a3c991130c91 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 12:31:58 -1000 Subject: [PATCH 0919/1145] [wifi] Combine scan result log lines to reduce loop blocking with many matching APs (#12830) --- esphome/components/wifi/wifi_component.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index a0a7d3d946..0738a76777 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1049,15 +1049,27 @@ template static void insertion_sort_scan_results(VectorType } // Helper function to log matching scan results - marked noinline to prevent re-inlining into loop +// +// IMPORTANT: This function deliberately uses a SINGLE log call to minimize blocking. +// In environments with many matching networks (e.g., 18+ mesh APs), multiple log calls +// per network would block the main loop for an unacceptable duration. Each log call +// has overhead from UART transmission, so combining INFO+DEBUG into one line halves +// the blocking time. Do NOT split this into separate ESP_LOGI/ESP_LOGD calls. __attribute__((noinline)) static void log_scan_result(const WiFiScanResult &res) { char bssid_s[18]; auto bssid = res.get_bssid(); format_mac_addr_upper(bssid.data(), bssid_s); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG + // Single combined log line with all details when DEBUG enabled + ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s Ch:%2u %3ddB P:%d", res.get_ssid().c_str(), + res.get_is_hidden() ? LOG_STR_LITERAL("(HIDDEN) ") : LOG_STR_LITERAL(""), bssid_s, + LOG_STR_ARG(get_signal_bars(res.get_rssi())), res.get_channel(), res.get_rssi(), res.get_priority()); +#else ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), res.get_is_hidden() ? LOG_STR_LITERAL("(HIDDEN) ") : LOG_STR_LITERAL(""), bssid_s, LOG_STR_ARG(get_signal_bars(res.get_rssi()))); - ESP_LOGD(TAG, " Channel: %2u, RSSI: %3d dB, Priority: %4d", res.get_channel(), res.get_rssi(), res.get_priority()); +#endif } #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE From 6685fa1da98a4c422c3aa5455eb28fe02ec64518 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 12:32:10 -1000 Subject: [PATCH 0920/1145] [core] Fix startup delay from setup timing logs when console connected (#12832) --- esphome/core/component.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 97ab2edb5a..90be6cf646 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -205,7 +205,13 @@ void Component::call() { this->call_setup(); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG uint32_t setup_time = millis() - start_time; - ESP_LOGCONFIG(TAG, "Setup %s took %ums", LOG_STR_ARG(this->get_component_log_str()), (unsigned) setup_time); + // Only log at CONFIG level if setup took longer than the blocking threshold + // to avoid spamming the log and blocking the event loop + if (setup_time >= WARN_IF_BLOCKING_OVER_MS) { + ESP_LOGCONFIG(TAG, "Setup %s took %ums", LOG_STR_ARG(this->get_component_log_str()), (unsigned) setup_time); + } else { + ESP_LOGV(TAG, "Setup %s took %ums", LOG_STR_ARG(this->get_component_log_str()), (unsigned) setup_time); + } #endif break; } From cb3edfc654af364e9a6cf2bb2b59acaa284b42c2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 12:32:22 -1000 Subject: [PATCH 0921/1145] [wifi] Use stack-based MAC formatting in ESP8266 and IDF event handlers (#12834) --- .../wifi/wifi_component_esp8266.cpp | 33 +++++++++++++++---- .../wifi/wifi_component_esp_idf.cpp | 25 +++++++++++--- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 055a13afc8..9d99e0b94c 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -518,8 +518,12 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { switch (event->event) { case EVENT_STAMODE_CONNECTED: { auto it = event->event_info.connected; - ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=%s channel=%u", it.ssid_len, (const char *) it.ssid, - format_mac_address_pretty(it.bssid).c_str(), it.channel); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, bssid_buf); + ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=%s channel=%u", it.ssid_len, (const char *) it.ssid, bssid_buf, + it.channel); +#endif s_sta_connected = true; #ifdef USE_WIFI_LISTENERS for (auto *listener : global_wifi_component->connect_state_listeners_) { @@ -594,18 +598,30 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { break; } case EVENT_SOFTAPMODE_STACONNECTED: { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE auto it = event->event_info.sta_connected; - ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", mac_buf, it.aid); +#endif break; } case EVENT_SOFTAPMODE_STADISCONNECTED: { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE auto it = event->event_info.sta_disconnected; - ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", mac_buf, it.aid); +#endif break; } case EVENT_SOFTAPMODE_PROBEREQRECVED: { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE auto it = event->event_info.ap_probereqrecved; - ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", mac_buf, it.rssi); +#endif break; } #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) @@ -616,9 +632,12 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { break; } case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE auto it = event->event_info.distribute_sta_ip; - ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), - format_ip_addr(it.ip).c_str(), it.aid); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", mac_buf, format_ip_addr(it.ip).c_str(), it.aid); +#endif break; } #endif diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index f68a095bff..820725ed31 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -734,9 +734,12 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_CONNECTED) { const auto &it = data->data.sta_connected; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, bssid_buf); ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", it.ssid_len, - (const char *) it.ssid, format_mac_address_pretty(it.bssid).c_str(), it.channel, - get_auth_mode_str(it.authmode)); + (const char *) it.ssid, bssid_buf, it.channel, get_auth_mode_str(it.authmode)); +#endif s_sta_connected = true; #ifdef USE_WIFI_LISTENERS for (auto *listener : this->connect_state_listeners_) { @@ -855,16 +858,28 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { this->ap_started_ = false; } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_PROBEREQRECVED) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE const auto &it = data->data.ap_probe_req_rx; - ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", mac_buf, it.rssi); +#endif } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STACONNECTED) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE const auto &it = data->data.ap_staconnected; - ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(it.mac).c_str()); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGV(TAG, "AP client connected MAC=%s", mac_buf); +#endif } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STADISCONNECTED) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE const auto &it = data->data.ap_stadisconnected; - ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(it.mac).c_str()); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGV(TAG, "AP client disconnected MAC=%s", mac_buf); +#endif } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_AP_STAIPASSIGNED) { const auto &it = data->data.ip_ap_staipassigned; From c29aa61e2a0fef32cc9bf3bbdfa33c8a8b5a3b44 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sun, 4 Jan 2026 09:08:47 +1000 Subject: [PATCH 0922/1145] [image] Use alternative version of CairoSVG on Windows (#12811) --- requirements.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 833ccbb0ed..bada581f56 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,7 +19,13 @@ ruamel.yaml==0.19.1 # dashboard_import ruamel.yaml.clib==0.2.15 # dashboard_import esphome-glyphsets==0.2.0 pillow==11.3.0 -cairosvg==2.8.2 + +# pycairo fork for Windows +cairosvg @ git+https://github.com/clydebarrow/cairosvg.git@release ; sys_platform == 'win32' + +# Original for everything else +cairosvg==2.8.2 ; sys_platform != 'win32' + freetype-py==2.5.1 jinja2==3.1.6 bleak==2.1.1 From 2e2e54811ac3f98b9eaccd4eac919e1ed0690004 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 13:52:23 -1000 Subject: [PATCH 0923/1145] [absolute_humidity] Combine log statements to reduce loop blocking (#12838) --- esphome/components/absolute_humidity/absolute_humidity.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/absolute_humidity/absolute_humidity.cpp b/esphome/components/absolute_humidity/absolute_humidity.cpp index 74d675b80b..b13fcd519a 100644 --- a/esphome/components/absolute_humidity/absolute_humidity.cpp +++ b/esphome/components/absolute_humidity/absolute_humidity.cpp @@ -90,13 +90,16 @@ void AbsoluteHumidityComponent::loop() { this->status_set_error(LOG_STR("Invalid saturation vapor pressure equation selection!")); return; } - ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es); // Calculate absolute humidity const float absolute_humidity = vapor_density(es, hr, temperature_k); + ESP_LOGD(TAG, + "Saturation vapor pressure %f kPa\n" + "Publishing absolute humidity %f g/m³", + es, absolute_humidity); + // Publish absolute humidity - ESP_LOGD(TAG, "Publishing absolute humidity %f g/m³", absolute_humidity); this->status_clear_warning(); this->publish_state(absolute_humidity); } From ec05692f0db5394c1d2d7c9097be9196c991a781 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Sun, 4 Jan 2026 01:12:31 +0100 Subject: [PATCH 0924/1145] [nrf52] add printk doc (#12839) --- esphome/components/logger/logger_zephyr.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/logger/logger_zephyr.cpp b/esphome/components/logger/logger_zephyr.cpp index 330dfa96ec..41f53beec0 100644 --- a/esphome/components/logger/logger_zephyr.cpp +++ b/esphome/components/logger/logger_zephyr.cpp @@ -66,6 +66,8 @@ void Logger::pre_setup() { void HOT Logger::write_msg_(const char *msg, size_t len) { // Single write with newline already in buffer (added by caller) #ifdef CONFIG_PRINTK + // Requires the debug component and an active SWD connection. + // It is used for pyocd rtt -t nrf52840 k_str_out(const_cast(msg), len); #endif if (this->uart_dev_ == nullptr) { From f11abc7dbfb6b0d2c8c8d48baca54094fd534297 Mon Sep 17 00:00:00 2001 From: Douwe <61123717+dhoeben@users.noreply.github.com> Date: Sun, 4 Jan 2026 01:45:49 +0100 Subject: [PATCH 0925/1145] [water_heater] (2/4) Implement template for new water_heater component (#12516) Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- .../template/water_heater/__init__.py | 123 ++++++++++++++++++ .../template/water_heater/automation.h | 35 +++++ .../water_heater/template_water_heater.cpp | 88 +++++++++++++ .../water_heater/template_water_heater.h | 53 ++++++++ tests/components/template/common-base.yaml | 30 +++++ 5 files changed, 329 insertions(+) create mode 100644 esphome/components/template/water_heater/__init__.py create mode 100644 esphome/components/template/water_heater/automation.h create mode 100644 esphome/components/template/water_heater/template_water_heater.cpp create mode 100644 esphome/components/template/water_heater/template_water_heater.h diff --git a/esphome/components/template/water_heater/__init__.py b/esphome/components/template/water_heater/__init__.py new file mode 100644 index 0000000000..716289035a --- /dev/null +++ b/esphome/components/template/water_heater/__init__.py @@ -0,0 +1,123 @@ +from esphome import automation +import esphome.codegen as cg +from esphome.components import water_heater +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_MODE, + CONF_OPTIMISTIC, + CONF_RESTORE_MODE, + CONF_SET_ACTION, + CONF_SUPPORTED_MODES, + CONF_TARGET_TEMPERATURE, +) +from esphome.core import ID +from esphome.cpp_generator import MockObj, TemplateArgsType +from esphome.types import ConfigType + +from .. import template_ns + +CONF_CURRENT_TEMPERATURE = "current_temperature" + +TemplateWaterHeater = template_ns.class_( + "TemplateWaterHeater", water_heater.WaterHeater +) + +TemplateWaterHeaterPublishAction = template_ns.class_( + "TemplateWaterHeaterPublishAction", + automation.Action, + cg.Parented.template(TemplateWaterHeater), +) + +TemplateWaterHeaterRestoreMode = template_ns.enum("TemplateWaterHeaterRestoreMode") +RESTORE_MODES = { + "NO_RESTORE": TemplateWaterHeaterRestoreMode.WATER_HEATER_NO_RESTORE, + "RESTORE": TemplateWaterHeaterRestoreMode.WATER_HEATER_RESTORE, + "RESTORE_AND_CALL": TemplateWaterHeaterRestoreMode.WATER_HEATER_RESTORE_AND_CALL, +} + +CONFIG_SCHEMA = water_heater.water_heater_schema(TemplateWaterHeater).extend( + { + cv.Optional(CONF_OPTIMISTIC, default=True): cv.boolean, + cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_RESTORE_MODE, default="NO_RESTORE"): cv.enum( + RESTORE_MODES, upper=True + ), + cv.Optional(CONF_CURRENT_TEMPERATURE): cv.returning_lambda, + cv.Optional(CONF_MODE): cv.returning_lambda, + cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list( + water_heater.validate_water_heater_mode + ), + } +) + + +async def to_code(config: ConfigType) -> None: + var = cg.new_Pvariable(config[CONF_ID]) + await water_heater.register_water_heater(var, config) + + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + + if CONF_SET_ACTION in config: + await automation.build_automation( + var.get_set_trigger(), [], config[CONF_SET_ACTION] + ) + + cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) + + if CONF_CURRENT_TEMPERATURE in config: + template_ = await cg.process_lambda( + config[CONF_CURRENT_TEMPERATURE], + [], + return_type=cg.optional.template(cg.float_), + ) + cg.add(var.set_current_temperature_lambda(template_)) + + if CONF_MODE in config: + template_ = await cg.process_lambda( + config[CONF_MODE], + [], + return_type=cg.optional.template(water_heater.WaterHeaterMode), + ) + cg.add(var.set_mode_lambda(template_)) + + if CONF_SUPPORTED_MODES in config: + cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) + + +@automation.register_action( + "water_heater.template.publish", + TemplateWaterHeaterPublishAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(TemplateWaterHeater), + cv.Optional(CONF_CURRENT_TEMPERATURE): cv.templatable(cv.temperature), + cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), + cv.Optional(CONF_MODE): cv.templatable( + water_heater.validate_water_heater_mode + ), + } + ), +) +async def water_heater_template_publish_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + + if current_temp := config.get(CONF_CURRENT_TEMPERATURE): + template_ = await cg.templatable(current_temp, args, float) + cg.add(var.set_current_temperature(template_)) + + if target_temp := config.get(CONF_TARGET_TEMPERATURE): + template_ = await cg.templatable(target_temp, args, float) + cg.add(var.set_target_temperature(template_)) + + if mode := config.get(CONF_MODE): + template_ = await cg.templatable(mode, args, water_heater.WaterHeaterMode) + cg.add(var.set_mode(template_)) + + return var diff --git a/esphome/components/template/water_heater/automation.h b/esphome/components/template/water_heater/automation.h new file mode 100644 index 0000000000..3dad2b85ae --- /dev/null +++ b/esphome/components/template/water_heater/automation.h @@ -0,0 +1,35 @@ +#pragma once + +#include "template_water_heater.h" +#include "esphome/core/automation.h" + +namespace esphome::template_ { + +template +class TemplateWaterHeaterPublishAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(float, current_temperature) + TEMPLATABLE_VALUE(float, target_temperature) + TEMPLATABLE_VALUE(water_heater::WaterHeaterMode, mode) + + void play(const Ts &...x) override { + if (this->current_temperature_.has_value()) { + this->parent_->set_current_temperature(this->current_temperature_.value(x...)); + } + bool needs_call = this->target_temperature_.has_value() || this->mode_.has_value(); + if (needs_call) { + auto call = this->parent_->make_call(); + if (this->target_temperature_.has_value()) { + call.set_target_temperature(this->target_temperature_.value(x...)); + } + if (this->mode_.has_value()) { + call.set_mode(this->mode_.value(x...)); + } + call.perform(); + } else { + this->parent_->publish_state(); + } + } +}; + +} // namespace esphome::template_ diff --git a/esphome/components/template/water_heater/template_water_heater.cpp b/esphome/components/template/water_heater/template_water_heater.cpp new file mode 100644 index 0000000000..18ef8d3f06 --- /dev/null +++ b/esphome/components/template/water_heater/template_water_heater.cpp @@ -0,0 +1,88 @@ +#include "template_water_heater.h" +#include "esphome/core/log.h" + +namespace esphome::template_ { + +static const char *const TAG = "template.water_heater"; + +TemplateWaterHeater::TemplateWaterHeater() : set_trigger_(new Trigger<>()) {} + +void TemplateWaterHeater::setup() { + if (this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE || + this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE_AND_CALL) { + auto restore = this->restore_state(); + + if (restore.has_value()) { + restore->perform(); + } + } + if (!this->current_temperature_f_.has_value() && !this->mode_f_.has_value()) + this->disable_loop(); +} + +water_heater::WaterHeaterTraits TemplateWaterHeater::traits() { + auto traits = water_heater::WaterHeater::get_traits(); + + if (!this->supported_modes_.empty()) { + traits.set_supported_modes(this->supported_modes_); + } + + traits.set_supports_current_temperature(true); + return traits; +} + +void TemplateWaterHeater::loop() { + bool changed = false; + + auto curr_temp = this->current_temperature_f_.call(); + if (curr_temp.has_value()) { + if (*curr_temp != this->current_temperature_) { + this->current_temperature_ = *curr_temp; + changed = true; + } + } + + auto new_mode = this->mode_f_.call(); + if (new_mode.has_value()) { + if (*new_mode != this->mode_) { + this->mode_ = *new_mode; + changed = true; + } + } + + if (changed) { + this->publish_state(); + } +} + +void TemplateWaterHeater::dump_config() { + LOG_WATER_HEATER("", "Template Water Heater", this); + ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); +} + +float TemplateWaterHeater::get_setup_priority() const { return setup_priority::HARDWARE; } + +water_heater::WaterHeaterCallInternal TemplateWaterHeater::make_call() { + return water_heater::WaterHeaterCallInternal(this); +} + +void TemplateWaterHeater::control(const water_heater::WaterHeaterCall &call) { + if (call.get_mode().has_value()) { + if (this->optimistic_) { + this->mode_ = *call.get_mode(); + } + } + if (!std::isnan(call.get_target_temperature())) { + if (this->optimistic_) { + this->target_temperature_ = call.get_target_temperature(); + } + } + + this->set_trigger_->trigger(); + + if (this->optimistic_) { + this->publish_state(); + } +} + +} // namespace esphome::template_ diff --git a/esphome/components/template/water_heater/template_water_heater.h b/esphome/components/template/water_heater/template_water_heater.h new file mode 100644 index 0000000000..e5f51b72dc --- /dev/null +++ b/esphome/components/template/water_heater/template_water_heater.h @@ -0,0 +1,53 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/core/template_lambda.h" +#include "esphome/components/water_heater/water_heater.h" + +namespace esphome::template_ { + +enum TemplateWaterHeaterRestoreMode { + WATER_HEATER_NO_RESTORE, + WATER_HEATER_RESTORE, + WATER_HEATER_RESTORE_AND_CALL, +}; + +class TemplateWaterHeater : public water_heater::WaterHeater { + public: + TemplateWaterHeater(); + + template void set_current_temperature_lambda(F &&f) { + this->current_temperature_f_.set(std::forward(f)); + } + template void set_mode_lambda(F &&f) { this->mode_f_.set(std::forward(f)); } + + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } + void set_restore_mode(TemplateWaterHeaterRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } + void set_supported_modes(const std::initializer_list &modes) { + this->supported_modes_ = modes; + } + + Trigger<> *get_set_trigger() const { return this->set_trigger_; } + + void setup() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override; + + water_heater::WaterHeaterCallInternal make_call() override; + + protected: + void control(const water_heater::WaterHeaterCall &call) override; + water_heater::WaterHeaterTraits traits() override; + + // Ordered to minimize padding on 32-bit: 4-byte members first, then smaller + Trigger<> *set_trigger_; + TemplateLambda current_temperature_f_; + TemplateLambda mode_f_; + TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE}; + water_heater::WaterHeaterModeMask supported_modes_; + bool optimistic_{true}; +}; + +} // namespace esphome::template_ diff --git a/tests/components/template/common-base.yaml b/tests/components/template/common-base.yaml index f101eea942..e050c0b307 100644 --- a/tests/components/template/common-base.yaml +++ b/tests/components/template/common-base.yaml @@ -9,6 +9,18 @@ esphome: id: template_sens state: !lambda "return 42.0;" + - water_heater.template.publish: + id: template_water_heater + target_temperature: 50.0 + mode: ECO + + # Templated + - water_heater.template.publish: + id: template_water_heater + current_temperature: !lambda "return 45.0;" + target_temperature: !lambda "return 55.0;" + mode: !lambda "return water_heater::WATER_HEATER_MODE_GAS;" + # Test C++ API: set_template() with stateless lambda (no captures) # NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break. - lambda: |- @@ -299,6 +311,24 @@ alarm_control_panel: codes: - "1234" +water_heater: + - platform: template + id: template_water_heater + name: "Template Water Heater" + optimistic: true + current_temperature: !lambda "return 42.0f;" + mode: !lambda "return water_heater::WATER_HEATER_MODE_ECO;" + supported_modes: + - "OFF" + - ECO + - GAS + - ELECTRIC + - HEAT_PUMP + - HIGH_DEMAND + - PERFORMANCE + set_action: + - logger.log: "set_action" + datetime: - platform: template name: Date From d7a1ac83ca5e76e4d157385d706b4c07726894ab Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:00:51 -1000 Subject: [PATCH 0926/1145] [esp32_ble_tracker] Combine log statements to reduce loop blocking (#12860) --- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 63675ec377..73a5dfb187 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -657,8 +657,10 @@ void ESP32BLETracker::dump_config() { " Continuous Scanning: %s", this->scan_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f, this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_)); - ESP_LOGCONFIG(TAG, " Scanner State: %s", this->scanner_state_to_string_(this->scanner_state_)); - ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, disconnecting: %d", this->client_state_counts_.connecting, + ESP_LOGCONFIG(TAG, + " Scanner State: %s\n" + " Connecting: %d, discovered: %d, disconnecting: %d", + this->scanner_state_to_string_(this->scanner_state_), this->client_state_counts_.connecting, this->client_state_counts_.discovered, this->client_state_counts_.disconnecting); if (this->scan_start_fail_count_) { ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_); From 5e24469ce34d64c99f78352f6fb0342baf84883d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:01:01 -1000 Subject: [PATCH 0927/1145] [http_request] Combine log statements to reduce loop blocking (#12859) --- esphome/components/http_request/ota/ota_http_request.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 2cd7489e38..2a7db9137f 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -105,9 +105,8 @@ uint8_t OtaHttpRequestComponent::do_ota_() { // we will compute MD5 on the fly for verification -- Arduino OTA seems to ignore it md5_receive.init(); - ESP_LOGV(TAG, "MD5Digest initialized"); - - ESP_LOGV(TAG, "OTA backend begin"); + ESP_LOGV(TAG, "MD5Digest initialized\n" + "OTA backend begin"); auto backend = ota::make_ota_backend(); auto error_code = backend->begin(container->content_length); if (error_code != ota::OTA_RESPONSE_OK) { From 5f5edf90e9ff45b86089e3eda127c298f10c74f5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:01:12 -1000 Subject: [PATCH 0928/1145] [water_heater] Combine log statements to reduce loop blocking (#12858) --- esphome/components/water_heater/water_heater.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/water_heater/water_heater.cpp b/esphome/components/water_heater/water_heater.cpp index 441872ec00..d092203d06 100644 --- a/esphome/components/water_heater/water_heater.cpp +++ b/esphome/components/water_heater/water_heater.cpp @@ -152,8 +152,10 @@ void WaterHeater::setup() { void WaterHeater::publish_state() { auto traits = this->get_traits(); - ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str()); - ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(water_heater_mode_to_string(this->mode_))); + ESP_LOGD(TAG, + "'%s' - Sending state:\n" + " Mode: %s", + this->name_.c_str(), LOG_STR_ARG(water_heater_mode_to_string(this->mode_))); if (!std::isnan(this->current_temperature_)) { ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature_); } From 07a581e13a68682d86d17e11cc1fa66d4743fb22 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:01:24 -1000 Subject: [PATCH 0929/1145] [update] Combine log statements to reduce loop blocking (#12857) --- esphome/components/update/update_entity.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/update/update_entity.cpp b/esphome/components/update/update_entity.cpp index 567fc9fc8e..6d13341a8a 100644 --- a/esphome/components/update/update_entity.cpp +++ b/esphome/components/update/update_entity.cpp @@ -9,8 +9,10 @@ namespace update { static const char *const TAG = "update"; void UpdateEntity::publish_state() { - ESP_LOGD(TAG, "'%s' - Publishing:", this->name_.c_str()); - ESP_LOGD(TAG, " Current Version: %s", this->update_info_.current_version.c_str()); + ESP_LOGD(TAG, + "'%s' - Publishing:\n" + " Current Version: %s", + this->name_.c_str(), this->update_info_.current_version.c_str()); if (!this->update_info_.md5.empty()) { ESP_LOGD(TAG, " Latest Version: %s", this->update_info_.latest_version.c_str()); From 2a6b192af8a330cef9714ca6aeaf624fd8568451 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:01:35 -1000 Subject: [PATCH 0930/1145] [ethernet] Combine log statements to reduce loop blocking (#12854) --- esphome/components/ethernet/ethernet_component.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index af4f652d8b..896c5cc874 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -813,8 +813,10 @@ void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister regi ESPHL_ERROR_CHECK(err, "Select PHY Register page failed"); } - ESP_LOGD(TAG, "Writing to PHY Register Address: 0x%02" PRIX32, register_data.address); - ESP_LOGD(TAG, "Writing to PHY Register Value: 0x%04" PRIX32, register_data.value); + ESP_LOGD(TAG, + "Writing to PHY Register Address: 0x%02" PRIX32 "\n" + "Writing to PHY Register Value: 0x%04" PRIX32, + register_data.address, register_data.value); err = mac->write_phy_reg(mac, this->phy_addr_, register_data.address, register_data.value); ESPHL_ERROR_CHECK(err, "Writing PHY Register failed"); From d364432e3a095d85a63b18a17aa266a9cd8c4cb3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:02:12 -1000 Subject: [PATCH 0931/1145] [uart] Combine log statements to reduce loop blocking (#12855) --- esphome/components/uart/uart_component_libretiny.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/uart/uart_component_libretiny.cpp b/esphome/components/uart/uart_component_libretiny.cpp index 01c7063fe8..863732c88d 100644 --- a/esphome/components/uart/uart_component_libretiny.cpp +++ b/esphome/components/uart/uart_component_libretiny.cpp @@ -120,8 +120,10 @@ void LibreTinyUARTComponent::setup() { void LibreTinyUARTComponent::dump_config() { bool is_software = this->hardware_idx_ == -1; - ESP_LOGCONFIG(TAG, "UART Bus:"); - ESP_LOGCONFIG(TAG, " Type: %s", UART_TYPE[is_software]); + ESP_LOGCONFIG(TAG, + "UART Bus:\n" + " Type: %s", + UART_TYPE[is_software]); if (!is_software) { ESP_LOGCONFIG(TAG, " Port number: %d", this->hardware_idx_); } From 8ddfeb2d38588e1d1f99b58f318f6e1bb8f5e931 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:02:26 -1000 Subject: [PATCH 0932/1145] [captive_portal] Combine log statements to reduce loop blocking (#12853) --- esphome/components/captive_portal/captive_portal.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 749aa705df..d0515166b6 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -49,9 +49,11 @@ void CaptivePortal::handle_config(AsyncWebServerRequest *request) { void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { std::string ssid = request->arg("ssid").c_str(); // NOLINT(readability-redundant-string-cstr) std::string psk = request->arg("psk").c_str(); // NOLINT(readability-redundant-string-cstr) - ESP_LOGI(TAG, "Requested WiFi Settings Change:"); - ESP_LOGI(TAG, " SSID='%s'", ssid.c_str()); - ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str()); + ESP_LOGI(TAG, + "Requested WiFi Settings Change:\n" + " SSID='%s'\n" + " Password=" LOG_SECRET("'%s'"), + ssid.c_str(), psk.c_str()); // Defer save to main loop thread to avoid NVS operations from HTTP thread this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid, psk); }); request->redirect(ESPHOME_F("/?save")); From 41a188ac3589bf646e4297840da316869fb72335 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:03:01 -1000 Subject: [PATCH 0933/1145] [ac_dimmer] Fix ESP8266 build by requiring waveform support (#12852) --- esphome/components/ac_dimmer/output.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/components/ac_dimmer/output.py b/esphome/components/ac_dimmer/output.py index 5e24779510..9f9afb6d80 100644 --- a/esphome/components/ac_dimmer/output.py +++ b/esphome/components/ac_dimmer/output.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import output import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_METHOD, CONF_MIN_POWER +from esphome.core import CORE CODEOWNERS = ["@glmnet"] @@ -36,6 +37,12 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): + if CORE.is_esp8266: + # ac_dimmer uses setTimer1Callback which requires the waveform generator + from esphome.components.esp8266.const import require_waveform + + require_waveform() + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) From ea848db683867152fe731202438ce1efb98ae227 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:03:20 -1000 Subject: [PATCH 0934/1145] [bp1658cj] Combine log statements to reduce loop blocking (#12851) --- esphome/components/bp1658cj/bp1658cj.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/bp1658cj/bp1658cj.cpp b/esphome/components/bp1658cj/bp1658cj.cpp index b8ad5dc3d2..d5516384ff 100644 --- a/esphome/components/bp1658cj/bp1658cj.cpp +++ b/esphome/components/bp1658cj/bp1658cj.cpp @@ -22,13 +22,13 @@ void BP1658CJ::setup() { this->pwm_amounts_.resize(5, 0); } void BP1658CJ::dump_config() { - ESP_LOGCONFIG(TAG, "BP1658CJ:"); - LOG_PIN(" Data Pin: ", this->data_pin_); - LOG_PIN(" Clock Pin: ", this->clock_pin_); ESP_LOGCONFIG(TAG, + "BP1658CJ:\n" " Color Channels Max Power: %u\n" " White Channels Max Power: %u", this->max_power_color_channels_, this->max_power_white_channels_); + LOG_PIN(" Data Pin: ", this->data_pin_); + LOG_PIN(" Clock Pin: ", this->clock_pin_); } void BP1658CJ::loop() { From 0196d6ee5573c338f3823a7a649c23dcd46cac45 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:03:44 -1000 Subject: [PATCH 0935/1145] [ble_nus] Combine log statements to reduce loop blocking (#12850) --- esphome/components/ble_nus/ble_nus.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/ble_nus/ble_nus.cpp b/esphome/components/ble_nus/ble_nus.cpp index bd80592d89..0de65b623f 100644 --- a/esphome/components/ble_nus/ble_nus.cpp +++ b/esphome/components/ble_nus/ble_nus.cpp @@ -103,8 +103,10 @@ void BLENUS::on_log(uint8_t level, const char *tag, const char *message, size_t #endif void BLENUS::dump_config() { - ESP_LOGCONFIG(TAG, "ble nus:"); - ESP_LOGCONFIG(TAG, " log: %s", YESNO(this->expose_log_)); + ESP_LOGCONFIG(TAG, + "ble nus:\n" + " log: %s", + YESNO(this->expose_log_)); uint32_t mtu = 0; bt_conn *conn = this->conn_.load(); if (conn) { From 41e7ecb29fd86a7468b6f93ac6eff3a64a504b57 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:04:21 -1000 Subject: [PATCH 0936/1145] [bedjet] Combine log statements to reduce loop blocking (#12848) --- esphome/components/bedjet/bedjet_hub.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/esphome/components/bedjet/bedjet_hub.cpp b/esphome/components/bedjet/bedjet_hub.cpp index 38fcf29b3b..a3054cf48e 100644 --- a/esphome/components/bedjet/bedjet_hub.cpp +++ b/esphome/components/bedjet/bedjet_hub.cpp @@ -216,11 +216,14 @@ bool BedJetHub::discover_characteristics_() { } } - ESP_LOGI(TAG, "[%s] Discovered service characteristics: ", this->get_name().c_str()); - ESP_LOGI(TAG, " - Command char: 0x%x", this->char_handle_cmd_); - ESP_LOGI(TAG, " - Status char: 0x%x", this->char_handle_status_); - ESP_LOGI(TAG, " - config descriptor: 0x%x", this->config_descr_status_); - ESP_LOGI(TAG, " - Name char: 0x%x", this->char_handle_name_); + ESP_LOGI(TAG, + "[%s] Discovered service characteristics:\n" + " - Command char: 0x%x\n" + " - Status char: 0x%x\n" + " - config descriptor: 0x%x\n" + " - Name char: 0x%x", + this->get_name().c_str(), this->char_handle_cmd_, this->char_handle_status_, this->config_descr_status_, + this->char_handle_name_); return result; } From 6bbee3cfc67a522a26c785fb90831500f5bfe37f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:04:38 -1000 Subject: [PATCH 0937/1145] [as3935] Combine log statements to reduce loop blocking (#12846) --- esphome/components/as3935/as3935.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/as3935/as3935.cpp b/esphome/components/as3935/as3935.cpp index 2609af07d3..93a0bff5b3 100644 --- a/esphome/components/as3935/as3935.cpp +++ b/esphome/components/as3935/as3935.cpp @@ -305,12 +305,14 @@ bool AS3935Component::calibrate_oscillator() { } void AS3935Component::tune_antenna() { - ESP_LOGI(TAG, "Starting antenna tuning"); uint8_t div_ratio = this->read_div_ratio(); uint8_t tune_val = this->read_capacitance(); - ESP_LOGI(TAG, "Division Ratio is set to: %d", div_ratio); - ESP_LOGI(TAG, "Internal Capacitor is set to: %d", tune_val); - ESP_LOGI(TAG, "Displaying oscillator on INT pin. Measure its frequency - multiply value by Division Ratio"); + ESP_LOGI(TAG, + "Starting antenna tuning\n" + "Division Ratio is set to: %d\n" + "Internal Capacitor is set to: %d\n" + "Displaying oscillator on INT pin. Measure its frequency - multiply value by Division Ratio", + div_ratio, tune_val); this->display_oscillator(true, ANTFREQ); } From d84562f87882b7cee98701a13830a62b24d67b10 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:04:57 -1000 Subject: [PATCH 0938/1145] [anova] Combine log statements to reduce loop blocking (#12845) --- esphome/components/anova/anova.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index 2693224a97..5054488089 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -67,8 +67,10 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ case ESP_GATTC_SEARCH_CMPL_EVT: { auto *chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID); if (chr == nullptr) { - ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str()); - ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str()); + ESP_LOGW(TAG, + "[%s] No control service found at device, not an Anova..?\n" + "[%s] Note, this component does not currently support Anova Nano.", + this->get_name().c_str(), this->get_name().c_str()); break; } this->char_handle_ = chr->handle; From 9cb265347ce27d809704c6c01d8bfd4190911c13 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:05:15 -1000 Subject: [PATCH 0939/1145] [ads1118] Combine log statements to reduce loop blocking (#12844) --- esphome/components/ads1118/sensor/ads1118_sensor.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/ads1118/sensor/ads1118_sensor.cpp b/esphome/components/ads1118/sensor/ads1118_sensor.cpp index c3ce3bdc9c..7193c3c880 100644 --- a/esphome/components/ads1118/sensor/ads1118_sensor.cpp +++ b/esphome/components/ads1118/sensor/ads1118_sensor.cpp @@ -9,8 +9,10 @@ static const char *const TAG = "ads1118.sensor"; void ADS1118Sensor::dump_config() { LOG_SENSOR(" ", "ADS1118 Sensor", this); - ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_); - ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_); + ESP_LOGCONFIG(TAG, + " Multiplexer: %u\n" + " Gain: %u", + this->multiplexer_, this->gain_); } float ADS1118Sensor::sample() { From 102862e99dceea007fbb14ba2dcccf24c224a491 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:05:29 -1000 Subject: [PATCH 0940/1145] [ads1115] Combine log statements to reduce loop blocking (#12843) --- esphome/components/ads1115/sensor/ads1115_sensor.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/ads1115/sensor/ads1115_sensor.cpp b/esphome/components/ads1115/sensor/ads1115_sensor.cpp index 6de95f1d12..fac6b60d0a 100644 --- a/esphome/components/ads1115/sensor/ads1115_sensor.cpp +++ b/esphome/components/ads1115/sensor/ads1115_sensor.cpp @@ -21,10 +21,12 @@ void ADS1115Sensor::update() { void ADS1115Sensor::dump_config() { LOG_SENSOR(" ", "ADS1115 Sensor", this); - ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_); - ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_); - ESP_LOGCONFIG(TAG, " Resolution: %u", this->resolution_); - ESP_LOGCONFIG(TAG, " Sample rate: %u", this->samplerate_); + ESP_LOGCONFIG(TAG, + " Multiplexer: %u\n" + " Gain: %u\n" + " Resolution: %u\n" + " Sample rate: %u", + this->multiplexer_, this->gain_, this->resolution_, this->samplerate_); } } // namespace ads1115 From 723ccd7547f73eb0647f54e91631958b5fcb2ff0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:05:41 -1000 Subject: [PATCH 0941/1145] [ade7880] Combine log statements to reduce loop blocking (#12842) --- esphome/components/ade7880/ade7880.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/ade7880/ade7880.cpp b/esphome/components/ade7880/ade7880.cpp index fd560e0676..f6a15190cd 100644 --- a/esphome/components/ade7880/ade7880.cpp +++ b/esphome/components/ade7880/ade7880.cpp @@ -162,11 +162,13 @@ void ADE7880::update() { } void ADE7880::dump_config() { - ESP_LOGCONFIG(TAG, "ADE7880:"); + ESP_LOGCONFIG(TAG, + "ADE7880:\n" + " Frequency: %.0f Hz", + this->frequency_); LOG_PIN(" IRQ0 Pin: ", this->irq0_pin_); LOG_PIN(" IRQ1 Pin: ", this->irq1_pin_); LOG_PIN(" RESET Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " Frequency: %.0f Hz", this->frequency_); if (this->channel_a_ != nullptr) { ESP_LOGCONFIG(TAG, " Phase A:"); From ee65f2f0cd9bf93b63053fe7d335c4458f383be7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:24:41 -1000 Subject: [PATCH 0942/1145] [adc] Combine log statements to reduce loop blocking (#12841) --- esphome/components/adc/adc_sensor_esp32.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/esphome/components/adc/adc_sensor_esp32.cpp b/esphome/components/adc/adc_sensor_esp32.cpp index 120cb1c926..ea1263db5f 100644 --- a/esphome/components/adc/adc_sensor_esp32.cpp +++ b/esphome/components/adc/adc_sensor_esp32.cpp @@ -121,23 +121,21 @@ void ADCSensor::setup() { void ADCSensor::dump_config() { LOG_SENSOR("", "ADC Sensor", this); LOG_PIN(" Pin: ", this->pin_); - ESP_LOGCONFIG(TAG, - " Channel: %d\n" - " Unit: %s\n" - " Attenuation: %s\n" - " Samples: %i\n" - " Sampling mode: %s", - this->channel_, LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)), - this->autorange_ ? "Auto" : LOG_STR_ARG(attenuation_to_str(this->attenuation_)), this->sample_count_, - LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_))); - ESP_LOGCONFIG( TAG, + " Channel: %d\n" + " Unit: %s\n" + " Attenuation: %s\n" + " Samples: %i\n" + " Sampling mode: %s\n" " Setup Status:\n" " Handle Init: %s\n" " Config: %s\n" " Calibration: %s\n" " Overall Init: %s", + this->channel_, LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)), + this->autorange_ ? "Auto" : LOG_STR_ARG(attenuation_to_str(this->attenuation_)), this->sample_count_, + LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)), this->setup_flags_.handle_init_complete ? "OK" : "FAILED", this->setup_flags_.config_complete ? "OK" : "FAILED", this->setup_flags_.calibration_complete ? "OK" : "FAILED", this->setup_flags_.init_complete ? "OK" : "FAILED"); From 8b80fe9c6bb73746b881e09baf3959853c9779f4 Mon Sep 17 00:00:00 2001 From: Frederic Meeuwissen <13856291+Frederic98@users.noreply.github.com> Date: Sun, 4 Jan 2026 02:32:27 +0100 Subject: [PATCH 0943/1145] [esp32_rmt_led_strip] Support inverted logic (#12825) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/esp32_rmt_led_strip/led_strip.cpp | 2 +- esphome/components/esp32_rmt_led_strip/led_strip.h | 2 ++ esphome/components/esp32_rmt_led_strip/light.py | 8 ++++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.cpp b/esphome/components/esp32_rmt_led_strip/led_strip.cpp index 2c7963b366..4ca0b998b1 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.cpp +++ b/esphome/components/esp32_rmt_led_strip/led_strip.cpp @@ -98,7 +98,7 @@ void ESP32RMTLEDStripLightOutput::setup() { channel.trans_queue_depth = 1; channel.flags.io_loop_back = 0; channel.flags.io_od_mode = 0; - channel.flags.invert_out = 0; + channel.flags.invert_out = this->invert_out_; channel.flags.with_dma = this->use_dma_; channel.intr_priority = 0; if (rmt_new_tx_channel(&channel, &this->channel_) != ESP_OK) { diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.h b/esphome/components/esp32_rmt_led_strip/led_strip.h index 72ce659b4f..6f3aea9878 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.h +++ b/esphome/components/esp32_rmt_led_strip/led_strip.h @@ -49,6 +49,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { } void set_pin(uint8_t pin) { this->pin_ = pin; } + void set_inverted(bool inverted) { this->invert_out_ = inverted; } void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; } void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; } void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; } @@ -93,6 +94,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { bool is_wrgb_{false}; bool use_dma_{false}; bool use_psram_{false}; + bool invert_out_{false}; RGBOrder rgb_order_{ORDER_RGB}; diff --git a/esphome/components/esp32_rmt_led_strip/light.py b/esphome/components/esp32_rmt_led_strip/light.py index f020d02e86..3be3c758f1 100644 --- a/esphome/components/esp32_rmt_led_strip/light.py +++ b/esphome/components/esp32_rmt_led_strip/light.py @@ -8,9 +8,11 @@ from esphome.components.const import CONF_USE_PSRAM import esphome.config_validation as cv from esphome.const import ( CONF_CHIPSET, + CONF_INVERTED, CONF_IS_RGBW, CONF_MAX_REFRESH_RATE, CONF_NUM_LEDS, + CONF_NUMBER, CONF_OUTPUT_ID, CONF_PIN, CONF_RGB_ORDER, @@ -71,7 +73,7 @@ CONFIG_SCHEMA = cv.All( light.ADDRESSABLE_LIGHT_SCHEMA.extend( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ESP32RMTLEDStripLightOutput), - cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), cv.SplitDefault( @@ -132,7 +134,9 @@ async def to_code(config): await cg.register_component(var, config) cg.add(var.set_num_leds(config[CONF_NUM_LEDS])) - cg.add(var.set_pin(config[CONF_PIN])) + cg.add(var.set_pin(config[CONF_PIN][CONF_NUMBER])) + if config[CONF_PIN][CONF_INVERTED]: + cg.add(var.set_inverted(True)) if CONF_MAX_REFRESH_RATE in config: cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE])) From 997ab553c1b80148433f911e0cac50fefc3de07c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:36:08 -1000 Subject: [PATCH 0944/1145] [ac_dimmer] Combine log statements to reduce loop blocking (#12840) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/ac_dimmer/ac_dimmer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index e6f7a1214a..04c01948c8 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -211,13 +211,13 @@ void AcDimmer::write_state(float state) { this->store_.value = new_value; } void AcDimmer::dump_config() { - ESP_LOGCONFIG(TAG, "AcDimmer:"); - LOG_PIN(" Output Pin: ", this->gate_pin_); - LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_); ESP_LOGCONFIG(TAG, + "AcDimmer:\n" " Min Power: %.1f%%\n" " Init with half cycle: %s", this->store_.min_power / 10.0f, YESNO(this->init_with_half_cycle_)); + LOG_PIN(" Output Pin: ", this->gate_pin_); + LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_); if (method_ == DIM_METHOD_LEADING_PULSE) { ESP_LOGCONFIG(TAG, " Method: leading pulse"); } else if (method_ == DIM_METHOD_LEADING) { From 7b74f94360603e18e230036b04544694369d5bb3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:54:56 -1000 Subject: [PATCH 0945/1145] [wifi] Combine log statements to reduce loop blocking (#12856) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/wifi/wifi_component.cpp | 28 +++++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 0738a76777..ca7b1ba9cc 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -781,8 +781,10 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { get_max_retries_for_phase(this->retry_phase_), LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_))); #ifdef ESPHOME_LOG_HAS_VERBOSE - ESP_LOGV(TAG, "Connection Params:"); - ESP_LOGV(TAG, " SSID: '%s'", ap.get_ssid().c_str()); + ESP_LOGV(TAG, + "Connection Params:\n" + " SSID: '%s'", + ap.get_ssid().c_str()); if (ap.has_bssid()) { ESP_LOGV(TAG, " BSSID: %s", bssid_s); } else { @@ -791,20 +793,28 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { #ifdef USE_WIFI_WPA2_EAP if (ap.get_eap().has_value()) { - ESP_LOGV(TAG, " WPA2 Enterprise authentication configured:"); EAPAuth eap_config = ap.get_eap().value(); - ESP_LOGV(TAG, " Identity: " LOG_SECRET("'%s'"), eap_config.identity.c_str()); - ESP_LOGV(TAG, " Username: " LOG_SECRET("'%s'"), eap_config.username.c_str()); - ESP_LOGV(TAG, " Password: " LOG_SECRET("'%s'"), eap_config.password.c_str()); + // clang-format off + ESP_LOGV( + TAG, + " WPA2 Enterprise authentication configured:\n" + " Identity: " LOG_SECRET("'%s'") "\n" + " Username: " LOG_SECRET("'%s'") "\n" + " Password: " LOG_SECRET("'%s'"), + eap_config.identity.c_str(), eap_config.username.c_str(), eap_config.password.c_str()); + // clang-format on #if defined(USE_ESP32) && defined(USE_WIFI_WPA2_EAP) && ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE ESP_LOGV(TAG, " TTLS Phase 2: " LOG_SECRET("'%s'"), eap_phase2_to_str(eap_config.ttls_phase_2)); #endif bool ca_cert_present = eap_config.ca_cert != nullptr && strlen(eap_config.ca_cert); bool client_cert_present = eap_config.client_cert != nullptr && strlen(eap_config.client_cert); bool client_key_present = eap_config.client_key != nullptr && strlen(eap_config.client_key); - ESP_LOGV(TAG, " CA Cert: %s", ca_cert_present ? "present" : "not present"); - ESP_LOGV(TAG, " Client Cert: %s", client_cert_present ? "present" : "not present"); - ESP_LOGV(TAG, " Client Key: %s", client_key_present ? "present" : "not present"); + ESP_LOGV(TAG, + " CA Cert: %s\n" + " Client Cert: %s\n" + " Client Key: %s", + ca_cert_present ? "present" : "not present", client_cert_present ? "present" : "not present", + client_key_present ? "present" : "not present"); } else { #endif ESP_LOGV(TAG, " Password: " LOG_SECRET("'%s'"), ap.get_password().c_str()); From 6b4b1272db27b7292a6f2adf1cad1e269d6a6eeb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 15:56:52 -1000 Subject: [PATCH 0946/1145] [binary_sensor] Combine log statements to reduce loop blocking (#12849) --- esphome/components/binary_sensor/automation.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/binary_sensor/automation.cpp b/esphome/components/binary_sensor/automation.cpp index 66d8d6e90f..dfe911a2f8 100644 --- a/esphome/components/binary_sensor/automation.cpp +++ b/esphome/components/binary_sensor/automation.cpp @@ -21,8 +21,10 @@ void MultiClickTrigger::on_state_(bool state) { // Start matching MultiClickTriggerEvent evt = this->timing_[0]; if (evt.state == state) { - ESP_LOGV(TAG, "START min=%" PRIu32 " max=%" PRIu32, evt.min_length, evt.max_length); - ESP_LOGV(TAG, "Multi Click: Starting multi click action!"); + ESP_LOGV(TAG, + "START min=%" PRIu32 " max=%" PRIu32 "\n" + "Multi Click: Starting multi click action!", + evt.min_length, evt.max_length); this->at_index_ = 1; if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) { this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); }); From 32562ca9916794c6450ccff4d7bb2af5182f7a74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Jan 2026 01:59:03 +0000 Subject: [PATCH 0947/1145] Bump aioesphomeapi from 43.10.0 to 43.10.1 (#12865) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bada581f56..6631cb55bd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.10.0 +aioesphomeapi==43.10.1 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.19.1 # dashboard_import From 5d384c77c545c4452e21f0737353eba34db585b0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 16:00:50 -1000 Subject: [PATCH 0948/1145] [esp32] Move heap functions to flash, saving ~6KB (#12862) --- esphome/components/esp32/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index da550e58dc..aa7d215c06 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -644,6 +644,7 @@ CONF_DISABLE_VFS_SUPPORT_SELECT = "disable_vfs_support_select" CONF_DISABLE_VFS_SUPPORT_DIR = "disable_vfs_support_dir" CONF_FREERTOS_IN_IRAM = "freertos_in_iram" CONF_RINGBUF_IN_IRAM = "ringbuf_in_iram" +CONF_HEAP_IN_IRAM = "heap_in_iram" CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size" # VFS requirement tracking @@ -745,6 +746,7 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean, cv.Optional(CONF_FREERTOS_IN_IRAM, default=False): cv.boolean, cv.Optional(CONF_RINGBUF_IN_IRAM, default=False): cv.boolean, + cv.Optional(CONF_HEAP_IN_IRAM, default=False): cv.boolean, cv.Optional(CONF_EXECUTE_FROM_PSRAM, default=False): cv.boolean, cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range( min=8192, max=32768 @@ -1090,6 +1092,12 @@ async def to_code(config): # Place in flash to save IRAM (default) add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH", True) + # Place heap functions into flash to save IRAM (~4-6KB savings) + # Safe as long as heap functions are not called from ISRs (which they shouldn't be) + # Users can set heap_in_iram: true as an escape hatch if needed + if not conf[CONF_ADVANCED][CONF_HEAP_IN_IRAM]: + add_idf_sdkconfig_option("CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH", True) + # Setup watchdog add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True) add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True) From 5f1eacf4ecf8fcc73775311508a92d4d7ff3296b Mon Sep 17 00:00:00 2001 From: Douwe <61123717+dhoeben@users.noreply.github.com> Date: Sun, 4 Jan 2026 03:43:31 +0100 Subject: [PATCH 0949/1145] [water_heater] (4/4) Implement tests for new water_heater component (#12517) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- .../water_heater/template_water_heater.cpp | 2 +- tests/components/water_heater/common.yaml | 16 +++ tests/components/web_server/common.yaml | 1 + .../fixtures/water_heater_template.yaml | 23 ++++ .../integration/test_water_heater_template.py | 109 ++++++++++++++++++ 5 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 tests/components/water_heater/common.yaml create mode 100644 tests/integration/fixtures/water_heater_template.yaml create mode 100644 tests/integration/test_water_heater_template.py diff --git a/esphome/components/template/water_heater/template_water_heater.cpp b/esphome/components/template/water_heater/template_water_heater.cpp index 18ef8d3f06..5ae5c30f36 100644 --- a/esphome/components/template/water_heater/template_water_heater.cpp +++ b/esphome/components/template/water_heater/template_water_heater.cpp @@ -21,7 +21,7 @@ void TemplateWaterHeater::setup() { } water_heater::WaterHeaterTraits TemplateWaterHeater::traits() { - auto traits = water_heater::WaterHeater::get_traits(); + water_heater::WaterHeaterTraits traits; if (!this->supported_modes_.empty()) { traits.set_supported_modes(this->supported_modes_); diff --git a/tests/components/water_heater/common.yaml b/tests/components/water_heater/common.yaml new file mode 100644 index 0000000000..8ec2b1b297 --- /dev/null +++ b/tests/components/water_heater/common.yaml @@ -0,0 +1,16 @@ +water_heater: + - platform: template + id: my_boiler + name: "Test Boiler" + min_temperature: 10 + max_temperature: 85 + target_temperature_step: 0.5 + current_temperature_step: 0.1 + optimistic: true + current_temperature: 45.0 + mode: !lambda "return water_heater::WATER_HEATER_MODE_ECO;" + visual: + min_temperature: 10 + max_temperature: 85 + target_temperature_step: 0.5 + current_temperature_step: 0.1 diff --git a/tests/components/web_server/common.yaml b/tests/components/web_server/common.yaml index eb768eeb91..82307c189c 100644 --- a/tests/components/web_server/common.yaml +++ b/tests/components/web_server/common.yaml @@ -36,3 +36,4 @@ datetime: optimistic: yes event: update: +water_heater: diff --git a/tests/integration/fixtures/water_heater_template.yaml b/tests/integration/fixtures/water_heater_template.yaml new file mode 100644 index 0000000000..b54ebed789 --- /dev/null +++ b/tests/integration/fixtures/water_heater_template.yaml @@ -0,0 +1,23 @@ +esphome: + name: water-heater-template-test +host: +api: +logger: + +water_heater: + - platform: template + id: test_boiler + name: Test Boiler + optimistic: true + current_temperature: !lambda "return 45.0f;" + # Note: No mode lambda - we want optimistic mode changes to stick + # A mode lambda would override mode changes in loop() + supported_modes: + - "off" + - eco + - gas + - performance + visual: + min_temperature: 30.0 + max_temperature: 85.0 + target_temperature_step: 0.5 diff --git a/tests/integration/test_water_heater_template.py b/tests/integration/test_water_heater_template.py new file mode 100644 index 0000000000..b5f1fb64c0 --- /dev/null +++ b/tests/integration/test_water_heater_template.py @@ -0,0 +1,109 @@ +"""Integration test for template water heater component.""" + +from __future__ import annotations + +import asyncio + +import aioesphomeapi +from aioesphomeapi import WaterHeaterInfo, WaterHeaterMode, WaterHeaterState +import pytest + +from .state_utils import InitialStateHelper +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_water_heater_template( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test template water heater basic state and mode changes.""" + loop = asyncio.get_running_loop() + async with run_compiled(yaml_config), api_client_connected() as client: + states: dict[int, aioesphomeapi.EntityState] = {} + gas_mode_future: asyncio.Future[WaterHeaterState] = loop.create_future() + eco_mode_future: asyncio.Future[WaterHeaterState] = loop.create_future() + + def on_state(state: aioesphomeapi.EntityState) -> None: + states[state.key] = state + if isinstance(state, WaterHeaterState): + # Wait for GAS mode + if state.mode == WaterHeaterMode.GAS and not gas_mode_future.done(): + gas_mode_future.set_result(state) + # Wait for ECO mode (we start at OFF, so test transitioning to ECO) + elif state.mode == WaterHeaterMode.ECO and not eco_mode_future.done(): + eco_mode_future.set_result(state) + + # Get entities and set up state synchronization + entities, services = await client.list_entities_services() + initial_state_helper = InitialStateHelper(entities) + water_heater_infos = [e for e in entities if isinstance(e, WaterHeaterInfo)] + assert len(water_heater_infos) == 1, ( + f"Expected exactly 1 water heater entity, got {len(water_heater_infos)}. Entity types: {[type(e).__name__ for e in entities]}" + ) + + test_water_heater = water_heater_infos[0] + + # Verify water heater entity info + assert test_water_heater.object_id == "test_boiler" + assert test_water_heater.name == "Test Boiler" + assert test_water_heater.min_temperature == 30.0 + assert test_water_heater.max_temperature == 85.0 + assert test_water_heater.target_temperature_step == 0.5 + + # Verify supported modes + supported_modes = test_water_heater.supported_modes + assert WaterHeaterMode.OFF in supported_modes, "Expected OFF in supported modes" + assert WaterHeaterMode.ECO in supported_modes, "Expected ECO in supported modes" + assert WaterHeaterMode.GAS in supported_modes, "Expected GAS in supported modes" + assert WaterHeaterMode.PERFORMANCE in supported_modes, ( + "Expected PERFORMANCE in supported modes" + ) + assert len(supported_modes) == 4, ( + f"Expected 4 supported modes, got {len(supported_modes)}: {supported_modes}" + ) + + # Subscribe with the wrapper that filters initial states + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + # Wait for all initial states to be broadcast + try: + await initial_state_helper.wait_for_initial_states() + except TimeoutError: + pytest.fail("Timeout waiting for initial states") + + # Get initial state and verify + initial_state = initial_state_helper.initial_states.get(test_water_heater.key) + assert initial_state is not None, "Water heater initial state not found" + assert isinstance(initial_state, WaterHeaterState) + # Initial mode is OFF (default) since we don't have a mode lambda + # A mode lambda would override optimistic mode changes + assert initial_state.mode == WaterHeaterMode.OFF, ( + f"Expected initial mode OFF, got {initial_state.mode}" + ) + assert initial_state.current_temperature == 45.0, ( + f"Expected current temp 45.0, got {initial_state.current_temperature}" + ) + + # Test changing to GAS mode + client.water_heater_command(test_water_heater.key, mode=WaterHeaterMode.GAS) + + try: + gas_state = await asyncio.wait_for(gas_mode_future, timeout=5.0) + except TimeoutError: + pytest.fail("GAS mode change not received within 5 seconds") + + assert isinstance(gas_state, WaterHeaterState) + assert gas_state.mode == WaterHeaterMode.GAS + + # Test changing to ECO mode (from GAS) + client.water_heater_command(test_water_heater.key, mode=WaterHeaterMode.ECO) + + try: + eco_state = await asyncio.wait_for(eco_mode_future, timeout=5.0) + except TimeoutError: + pytest.fail("ECO mode change not received within 5 seconds") + + assert isinstance(eco_state, WaterHeaterState) + assert eco_state.mode == WaterHeaterMode.ECO From 12c6f5749e22bbaf4c8bfba02d452b11f1b4219b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 16:46:29 -1000 Subject: [PATCH 0950/1145] [cst816] Combine log statements to reduce loop blocking (#12872) --- .../components/cst816/touchscreen/cst816_touchscreen.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp index 5be93692c0..d18d4e7c94 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp @@ -83,14 +83,14 @@ void CST816Touchscreen::update_touches() { } void CST816Touchscreen::dump_config() { - ESP_LOGCONFIG(TAG, "CST816 Touchscreen:"); - LOG_I2C_DEVICE(this); - LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); - LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, + "CST816 Touchscreen:\n" " X Raw Min: %d, X Raw Max: %d\n" " Y Raw Min: %d, Y Raw Max: %d", this->x_raw_min_, this->x_raw_max_, this->y_raw_min_, this->y_raw_max_); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); const char *name; switch (this->chip_id_) { case CST716_CHIP_ID: From c96d0015a003f0a918bd648059b32e6980709cdf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 16:48:04 -1000 Subject: [PATCH 0951/1145] [esp_ldo] Combine log statements to reduce loop blocking (#12886) --- esphome/components/esp_ldo/esp_ldo.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp_ldo/esp_ldo.cpp b/esphome/components/esp_ldo/esp_ldo.cpp index 5e3d4159f3..2eee855b46 100644 --- a/esphome/components/esp_ldo/esp_ldo.cpp +++ b/esphome/components/esp_ldo/esp_ldo.cpp @@ -21,9 +21,11 @@ void EspLdo::setup() { } } void EspLdo::dump_config() { - ESP_LOGCONFIG(TAG, "ESP LDO Channel %d:", this->channel_); - ESP_LOGCONFIG(TAG, " Voltage: %fV", this->voltage_); - ESP_LOGCONFIG(TAG, " Adjustable: %s", YESNO(this->adjustable_)); + ESP_LOGCONFIG(TAG, + "ESP LDO Channel %d:\n" + " Voltage: %fV\n" + " Adjustable: %s", + this->channel_, this->voltage_, YESNO(this->adjustable_)); } void EspLdo::adjust_voltage(float voltage) { From 16ada4d477146f2ff33caa445f295c4817d1825b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 16:48:39 -1000 Subject: [PATCH 0952/1145] [epaper_spi] Combine log statements to reduce loop blocking (#12881) --- esphome/components/epaper_spi/epaper_spi.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/esphome/components/epaper_spi/epaper_spi.cpp b/esphome/components/epaper_spi/epaper_spi.cpp index 4e6b4a7fd6..0b600feeae 100644 --- a/esphome/components/epaper_spi/epaper_spi.cpp +++ b/esphome/components/epaper_spi/epaper_spi.cpp @@ -331,20 +331,21 @@ void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) { void EPaperBase::dump_config() { LOG_DISPLAY("", "E-Paper SPI", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->name_); - LOG_PIN(" Reset Pin: ", this->reset_pin_); - LOG_PIN(" DC Pin: ", this->dc_pin_); - LOG_PIN(" Busy Pin: ", this->busy_pin_); - LOG_PIN(" CS Pin: ", this->cs_); - LOG_UPDATE_INTERVAL(this); ESP_LOGCONFIG(TAG, + " Model: %s\n" " SPI Data Rate: %uMHz\n" " Full update every: %d\n" " Swap X/Y: %s\n" " Mirror X: %s\n" " Mirror Y: %s", - (unsigned) (this->data_rate_ / 1000000), this->full_update_every_, YESNO(this->transform_ & SWAP_XY), - YESNO(this->transform_ & MIRROR_X), YESNO(this->transform_ & MIRROR_Y)); + this->name_, (unsigned) (this->data_rate_ / 1000000), this->full_update_every_, + YESNO(this->transform_ & SWAP_XY), YESNO(this->transform_ & MIRROR_X), + YESNO(this->transform_ & MIRROR_Y)); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_PIN(" CS Pin: ", this->cs_); + LOG_UPDATE_INTERVAL(this); } } // namespace esphome::epaper_spi From cf93b66306a7e7f941a8a204709856be9eebb4fb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 16:59:55 -1000 Subject: [PATCH 0953/1145] [chsc6x] Combine log statements to reduce loop blocking (#12871) --- esphome/components/chsc6x/chsc6x_touchscreen.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/chsc6x/chsc6x_touchscreen.cpp b/esphome/components/chsc6x/chsc6x_touchscreen.cpp index 31c9466691..941144e451 100644 --- a/esphome/components/chsc6x/chsc6x_touchscreen.cpp +++ b/esphome/components/chsc6x/chsc6x_touchscreen.cpp @@ -32,14 +32,14 @@ void CHSC6XTouchscreen::update_touches() { } void CHSC6XTouchscreen::dump_config() { - ESP_LOGCONFIG(TAG, "CHSC6X Touchscreen:"); - LOG_I2C_DEVICE(this); - LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); ESP_LOGCONFIG(TAG, + "CHSC6X Touchscreen:\n" " Touch timeout: %d\n" " x_raw_max_: %d\n" " y_raw_max_: %d", this->touch_timeout_, this->x_raw_max_, this->y_raw_max_); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); } } // namespace chsc6x From bc9093127e5fff67c901b29691c4c4dae000bc2f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 17:00:14 -1000 Subject: [PATCH 0954/1145] [cap1188] Combine log statements to reduce loop blocking (#12868) --- esphome/components/cap1188/cap1188.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/cap1188/cap1188.cpp b/esphome/components/cap1188/cap1188.cpp index 683e5cf487..9e8c87d147 100644 --- a/esphome/components/cap1188/cap1188.cpp +++ b/esphome/components/cap1188/cap1188.cpp @@ -63,14 +63,14 @@ void CAP1188Component::finish_setup_() { } void CAP1188Component::dump_config() { - ESP_LOGCONFIG(TAG, "CAP1188:"); - LOG_I2C_DEVICE(this); - LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, + "CAP1188:\n" " Product ID: 0x%x\n" " Manufacture ID: 0x%x\n" " Revision ID: 0x%x", this->cap1188_product_id_, this->cap1188_manufacture_id_, this->cap1188_revision_); + LOG_I2C_DEVICE(this); + LOG_PIN(" Reset Pin: ", this->reset_pin_); switch (this->error_code_) { case COMMUNICATION_FAILED: From 44fa6bae95bf3a60962c1752c187532251ebe5c2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 17:57:53 -1000 Subject: [PATCH 0955/1145] [dht] Combine log statements to reduce loop blocking (#12877) --- esphome/components/dht/dht.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index e0abb7c5f0..6cb204c8de 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -17,11 +17,14 @@ void DHT::setup() { } void DHT::dump_config() { - ESP_LOGCONFIG(TAG, "DHT:"); + ESP_LOGCONFIG(TAG, + "DHT:\n" + " %sModel: %s\n" + " Internal pull-up: %s", + this->is_auto_detect_ ? "Auto-detected " : "", + this->model_ == DHT_MODEL_DHT11 ? "DHT11" : "DHT22 or equivalent", + ONOFF(this->t_pin_->get_flags() & gpio::FLAG_PULLUP)); LOG_PIN(" Pin: ", this->t_pin_); - ESP_LOGCONFIG(TAG, " %sModel: %s", this->is_auto_detect_ ? "Auto-detected " : "", - this->model_ == DHT_MODEL_DHT11 ? "DHT11" : "DHT22 or equivalent"); - ESP_LOGCONFIG(TAG, " Internal pull-up: %s", ONOFF(this->t_pin_->get_flags() & gpio::FLAG_PULLUP)); LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); From 9f06f046d67f64041eb472bb017e0b03d223b341 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 17:59:53 -1000 Subject: [PATCH 0956/1145] [espnow] Combine log statements to reduce loop blocking (#12887) --- .../espnow/packet_transport/espnow_transport.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/espnow/packet_transport/espnow_transport.cpp b/esphome/components/espnow/packet_transport/espnow_transport.cpp index c1252acc9d..3d16f28c7d 100644 --- a/esphome/components/espnow/packet_transport/espnow_transport.cpp +++ b/esphome/components/espnow/packet_transport/espnow_transport.cpp @@ -21,9 +21,11 @@ void ESPNowTransport::setup() { return; } - ESP_LOGI(TAG, "Registering ESP-NOW handlers"); - ESP_LOGI(TAG, "Peer address: %02X:%02X:%02X:%02X:%02X:%02X", this->peer_address_[0], this->peer_address_[1], - this->peer_address_[2], this->peer_address_[3], this->peer_address_[4], this->peer_address_[5]); + ESP_LOGI(TAG, + "Registering ESP-NOW handlers\n" + "Peer address: %02X:%02X:%02X:%02X:%02X:%02X", + this->peer_address_[0], this->peer_address_[1], this->peer_address_[2], this->peer_address_[3], + this->peer_address_[4], this->peer_address_[5]); // Register received handler this->parent_->register_received_handler(this); From 6e8817cbc47f1dcf116eb2bdd06fbbfdb8b8f892 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 18:00:11 -1000 Subject: [PATCH 0957/1145] [esp8266_pwm] Combine log statements to reduce loop blocking (#12885) --- esphome/components/esp8266_pwm/esp8266_pwm.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.cpp b/esphome/components/esp8266_pwm/esp8266_pwm.cpp index 0aaef597d3..cc6bfbc8a8 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.cpp +++ b/esphome/components/esp8266_pwm/esp8266_pwm.cpp @@ -18,9 +18,11 @@ void ESP8266PWM::setup() { this->turn_off(); } void ESP8266PWM::dump_config() { - ESP_LOGCONFIG(TAG, "ESP8266 PWM:"); + ESP_LOGCONFIG(TAG, + "ESP8266 PWM:\n" + " Frequency: %.1f Hz", + this->frequency_); LOG_PIN(" Pin: ", this->pin_); - ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); LOG_FLOAT_OUTPUT(this); } void HOT ESP8266PWM::write_state(float state) { From cb598c43e891c6887efd1ede490e621041a72a0a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 18:00:31 -1000 Subject: [PATCH 0958/1145] [endstop] Combine log statements to reduce loop blocking (#12879) --- esphome/components/endstop/endstop_cover.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/endstop/endstop_cover.cpp b/esphome/components/endstop/endstop_cover.cpp index 381f098eb5..2c281ea2e6 100644 --- a/esphome/components/endstop/endstop_cover.cpp +++ b/esphome/components/endstop/endstop_cover.cpp @@ -104,10 +104,12 @@ void EndstopCover::loop() { } void EndstopCover::dump_config() { LOG_COVER("", "Endstop Cover", this); + ESP_LOGCONFIG(TAG, + " Open Duration: %.1fs\n" + " Close Duration: %.1fs", + this->open_duration_ / 1e3f, this->close_duration_ / 1e3f); LOG_BINARY_SENSOR(" ", "Open Endstop", this->open_endstop_); - ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f); LOG_BINARY_SENSOR(" ", "Close Endstop", this->close_endstop_); - ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f); } float EndstopCover::get_setup_priority() const { return setup_priority::DATA; } void EndstopCover::stop_prev_trigger_() { From e94158a12f303543bea4e901601373a60d471af6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 18:00:52 -1000 Subject: [PATCH 0959/1145] [fan] Combine log statements to reduce loop blocking (#12889) --- esphome/components/fan/fan.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index bf5506da4b..0ffb60e50d 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -179,8 +179,10 @@ void Fan::add_on_state_callback(std::function &&callback) { this->state_ void Fan::publish_state() { auto traits = this->get_traits(); - ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str()); - ESP_LOGD(TAG, " State: %s", ONOFF(this->state)); + ESP_LOGD(TAG, + "'%s' - Sending state:\n" + " State: %s", + this->name_.c_str(), ONOFF(this->state)); if (traits.supports_speed()) { ESP_LOGD(TAG, " Speed: %d", this->speed); } From c59314ec09c5e311ca313d9c4baa37c29ccf2e7f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 18:01:28 -1000 Subject: [PATCH 0960/1145] [debug] Combine log statements to reduce loop blocking (#12875) --- esphome/components/debug/debug_esp8266.cpp | 19 +++++---- esphome/components/debug/debug_libretiny.cpp | 15 ++++--- esphome/components/debug/debug_zephyr.cpp | 43 +++++++++++--------- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/esphome/components/debug/debug_esp8266.cpp b/esphome/components/debug/debug_esp8266.cpp index 3395d9db12..7427b32290 100644 --- a/esphome/components/debug/debug_esp8266.cpp +++ b/esphome/components/debug/debug_esp8266.cpp @@ -47,14 +47,17 @@ void DebugComponent::get_device_info_(std::string &device_info) { #if !defined(CLANG_TIDY) auto reset_reason = get_reset_reason_(); - ESP_LOGD(TAG, "Chip ID: 0x%08X", ESP.getChipId()); - ESP_LOGD(TAG, "SDK Version: %s", ESP.getSdkVersion()); - ESP_LOGD(TAG, "Core Version: %s", ESP.getCoreVersion().c_str()); - ESP_LOGD(TAG, "Boot Version=%u Mode=%u", ESP.getBootVersion(), ESP.getBootMode()); - ESP_LOGD(TAG, "CPU Frequency: %u", ESP.getCpuFreqMHz()); - ESP_LOGD(TAG, "Flash Chip ID=0x%08X", ESP.getFlashChipId()); - ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); - ESP_LOGD(TAG, "Reset Info: %s", ESP.getResetInfo().c_str()); + ESP_LOGD(TAG, + "Chip ID: 0x%08X\n" + "SDK Version: %s\n" + "Core Version: %s\n" + "Boot Version=%u Mode=%u\n" + "CPU Frequency: %u\n" + "Flash Chip ID=0x%08X\n" + "Reset Reason: %s\n" + "Reset Info: %s", + ESP.getChipId(), ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), ESP.getBootVersion(), ESP.getBootMode(), + ESP.getCpuFreqMHz(), ESP.getFlashChipId(), reset_reason.c_str(), ESP.getResetInfo().c_str()); device_info += "|Chip: 0x" + format_hex(ESP.getChipId()); device_info += "|SDK: "; diff --git a/esphome/components/debug/debug_libretiny.cpp b/esphome/components/debug/debug_libretiny.cpp index b5e2a5b310..e823ac6c77 100644 --- a/esphome/components/debug/debug_libretiny.cpp +++ b/esphome/components/debug/debug_libretiny.cpp @@ -13,12 +13,15 @@ uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); } void DebugComponent::get_device_info_(std::string &device_info) { std::string reset_reason = get_reset_reason_(); - ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version()); - ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz()); - ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id()); - ESP_LOGD(TAG, "Board: %s", lt_get_board_code()); - ESP_LOGD(TAG, "Flash: %u KiB / RAM: %u KiB", lt_flash_get_size() / 1024, lt_ram_get_size() / 1024); - ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); + ESP_LOGD(TAG, + "LibreTiny Version: %s\n" + "Chip: %s (%04x) @ %u MHz\n" + "Chip ID: 0x%06X\n" + "Board: %s\n" + "Flash: %u KiB / RAM: %u KiB\n" + "Reset Reason: %s", + lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), lt_cpu_get_mac_id(), + lt_get_board_code(), lt_flash_get_size() / 1024, lt_ram_get_size() / 1024, reset_reason.c_str()); device_info += "|Version: "; device_info += LT_BANNER_STR + 10; diff --git a/esphome/components/debug/debug_zephyr.cpp b/esphome/components/debug/debug_zephyr.cpp index c888c41a78..6abf983e9e 100644 --- a/esphome/components/debug/debug_zephyr.cpp +++ b/esphome/components/debug/debug_zephyr.cpp @@ -106,13 +106,13 @@ static void fa_cb(const struct flash_area *fa, void *user_data) { void DebugComponent::log_partition_info_() { #if CONFIG_FLASH_MAP_LABELS ESP_LOGCONFIG(TAG, "ID | Device | Device Name " - "| Label | Offset | Size"); - ESP_LOGCONFIG(TAG, "--------------------------------------------" + "| Label | Offset | Size\n" + "--------------------------------------------" "-----------------------------------------------"); #else ESP_LOGCONFIG(TAG, "ID | Device | Device Name " - "| Offset | Size"); - ESP_LOGCONFIG(TAG, "-----------------------------------------" + "| Offset | Size\n" + "-----------------------------------------" "------------------------------"); #endif flash_area_foreach(fa_cb, nullptr); @@ -300,18 +300,18 @@ void DebugComponent::get_device_info_(std::string &device_info) { return "Unspecified"; }; - ESP_LOGD(TAG, "Code page size: %u, code size: %u, device id: 0x%08x%08x", NRF_FICR->CODEPAGESIZE, NRF_FICR->CODESIZE, - NRF_FICR->DEVICEID[1], NRF_FICR->DEVICEID[0]); - ESP_LOGD(TAG, "Encryption root: 0x%08x%08x%08x%08x, Identity Root: 0x%08x%08x%08x%08x", NRF_FICR->ER[0], + ESP_LOGD(TAG, + "Code page size: %u, code size: %u, device id: 0x%08x%08x\n" + "Encryption root: 0x%08x%08x%08x%08x, Identity Root: 0x%08x%08x%08x%08x\n" + "Device address type: %s, address: %s\n" + "Part code: nRF%x, version: %c%c%c%c, package: %s\n" + "RAM: %ukB, Flash: %ukB, production test: %sdone", + NRF_FICR->CODEPAGESIZE, NRF_FICR->CODESIZE, NRF_FICR->DEVICEID[1], NRF_FICR->DEVICEID[0], NRF_FICR->ER[0], NRF_FICR->ER[1], NRF_FICR->ER[2], NRF_FICR->ER[3], NRF_FICR->IR[0], NRF_FICR->IR[1], NRF_FICR->IR[2], - NRF_FICR->IR[3]); - ESP_LOGD(TAG, "Device address type: %s, address: %s", (NRF_FICR->DEVICEADDRTYPE & 0x1 ? "Random" : "Public"), - get_mac_address_pretty().c_str()); - ESP_LOGD(TAG, "Part code: nRF%x, version: %c%c%c%c, package: %s", NRF_FICR->INFO.PART, - NRF_FICR->INFO.VARIANT >> 24 & 0xFF, NRF_FICR->INFO.VARIANT >> 16 & 0xFF, NRF_FICR->INFO.VARIANT >> 8 & 0xFF, - NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE)); - ESP_LOGD(TAG, "RAM: %ukB, Flash: %ukB, production test: %sdone", NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH, - (NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not ")); + NRF_FICR->IR[3], (NRF_FICR->DEVICEADDRTYPE & 0x1 ? "Random" : "Public"), get_mac_address_pretty().c_str(), + NRF_FICR->INFO.PART, NRF_FICR->INFO.VARIANT >> 24 & 0xFF, NRF_FICR->INFO.VARIANT >> 16 & 0xFF, + NRF_FICR->INFO.VARIANT >> 8 & 0xFF, NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE), + NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH, (NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not ")); bool n_reset_enabled = NRF_UICR->PSELRESET[0] == NRF_UICR->PSELRESET[1] && (NRF_UICR->PSELRESET[0] & UICR_PSELRESET_CONNECT_Msk) == UICR_PSELRESET_CONNECT_Connected << UICR_PSELRESET_CONNECT_Pos; @@ -329,9 +329,10 @@ void DebugComponent::get_device_info_(std::string &device_info) { #else ESP_LOGD(TAG, "bootloader: Adafruit, version %u.%u.%u", (BOOTLOADER_VERSION_REGISTER >> 16) & 0xFF, (BOOTLOADER_VERSION_REGISTER >> 8) & 0xFF, BOOTLOADER_VERSION_REGISTER & 0xFF); - ESP_LOGD(TAG, "MBR bootloader addr 0x%08x, UICR bootloader addr 0x%08x", read_mem_u32(MBR_BOOTLOADER_ADDR), - NRF_UICR->NRFFW[0]); - ESP_LOGD(TAG, "MBR param page addr 0x%08x, UICR param page addr 0x%08x", read_mem_u32(MBR_PARAM_PAGE_ADDR), + ESP_LOGD(TAG, + "MBR bootloader addr 0x%08x, UICR bootloader addr 0x%08x\n" + "MBR param page addr 0x%08x, UICR param page addr 0x%08x", + read_mem_u32(MBR_BOOTLOADER_ADDR), NRF_UICR->NRFFW[0], read_mem_u32(MBR_PARAM_PAGE_ADDR), NRF_UICR->NRFFW[1]); if (is_sd_present()) { uint32_t const sd_id = sd_id_get(); @@ -368,8 +369,10 @@ void DebugComponent::get_device_info_(std::string &device_info) { } return res; }; - ESP_LOGD(TAG, "NRFFW %s", uicr(NRF_UICR->NRFFW, 13).c_str()); - ESP_LOGD(TAG, "NRFHW %s", uicr(NRF_UICR->NRFHW, 12).c_str()); + ESP_LOGD(TAG, + "NRFFW %s\n" + "NRFHW %s", + uicr(NRF_UICR->NRFFW, 13).c_str(), uicr(NRF_UICR->NRFHW, 12).c_str()); } void DebugComponent::update_platform_() {} From 096de869b6c67cb72126da67ca4d4a6699bc78e7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 18:01:55 -1000 Subject: [PATCH 0961/1145] [esp32_ble_client] Combine log statements to reduce loop blocking (#12883) --- .../components/esp32_ble_client/ble_client_base.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 8017b577f4..26eb5dd092 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -70,9 +70,9 @@ float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_B void BLEClientBase::dump_config() { ESP_LOGCONFIG(TAG, " Address: %s\n" - " Auto-Connect: %s", - this->address_str(), TRUEFALSE(this->auto_connect_)); - ESP_LOGCONFIG(TAG, " State: %s", espbt::client_state_to_string(this->state())); + " Auto-Connect: %s\n" + " State: %s", + this->address_str(), TRUEFALSE(this->auto_connect_), espbt::client_state_to_string(this->state())); if (this->status_ == ESP_GATT_NO_RESOURCES) { ESP_LOGE(TAG, " Failed due to no resources. Try to reduce number of BLE clients in config."); } else if (this->status_ != ESP_GATT_OK) { @@ -415,8 +415,10 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ for (auto &svc : this->services_) { char uuid_buf[espbt::UUID_STR_LEN]; svc->uuid.to_str(uuid_buf); - ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_, uuid_buf); - ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_, this->address_str_, + ESP_LOGV(TAG, + "[%d] [%s] Service UUID: %s\n" + "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", + this->connection_index_, this->address_str_, uuid_buf, this->connection_index_, this->address_str_, svc->start_handle, svc->end_handle); } #endif From facf4777a400327b1120e1898425d06a025b9c11 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Jan 2026 18:04:00 -1000 Subject: [PATCH 0962/1145] [ezo_pmp] Combine log statements to reduce loop blocking (#12888) --- esphome/components/ezo_pmp/ezo_pmp.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/esphome/components/ezo_pmp/ezo_pmp.cpp b/esphome/components/ezo_pmp/ezo_pmp.cpp index 9ec41cce30..61b601328a 100644 --- a/esphome/components/ezo_pmp/ezo_pmp.cpp +++ b/esphome/components/ezo_pmp/ezo_pmp.cpp @@ -148,10 +148,13 @@ void EzoPMP::read_command_result_() { char current_char = response_buffer[i]; if (current_char == '\0') { - ESP_LOGV(TAG, "Read Response from device: %s", (char *) response_buffer); - ESP_LOGV(TAG, "First Component: %s", (char *) first_parameter_buffer); - ESP_LOGV(TAG, "Second Component: %s", (char *) second_parameter_buffer); - ESP_LOGV(TAG, "Third Component: %s", (char *) third_parameter_buffer); + ESP_LOGV(TAG, + "Read Response from device: %s\n" + "First Component: %s\n" + "Second Component: %s\n" + "Third Component: %s", + (char *) response_buffer, (char *) first_parameter_buffer, (char *) second_parameter_buffer, + (char *) third_parameter_buffer); break; } From b1f9c08f51682c928a78d7997d279af5e4c7734c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:11:36 -1000 Subject: [PATCH 0963/1145] [esp32_ble_tracker] Make start_scan action idempotent (#12864) --- esphome/components/esp32_ble_tracker/automation.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index bbf7992fa4..6d26040ccb 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -98,7 +98,13 @@ template class ESP32BLEStartScanAction : public Action { TEMPLATABLE_VALUE(bool, continuous) void play(const Ts &...x) override { this->parent_->set_scan_continuous(this->continuous_.value(x...)); - this->parent_->start_scan(); + // Only call start_scan() if scanner is IDLE + // For other states (STARTING, RUNNING, STOPPING, FAILED), the normal state + // machine flow will eventually transition back to IDLE, at which point + // loop() will see scan_continuous_ and restart scanning if it is true. + if (this->parent_->get_scanner_state() == ScannerState::IDLE) { + this->parent_->start_scan(); + } } protected: From 8a4ee19c0b99fadde31e66ff577378c19121e567 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:26:19 -1000 Subject: [PATCH 0964/1145] [es8388] Combine log statements to reduce loop blocking (#12882) --- esphome/components/es8388/es8388.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/es8388/es8388.cpp b/esphome/components/es8388/es8388.cpp index 5abe7a5e5f..d1834e7043 100644 --- a/esphome/components/es8388/es8388.cpp +++ b/esphome/components/es8388/es8388.cpp @@ -210,9 +210,11 @@ bool ES8388::set_dac_output(DacOutputLine line) { return false; }; - ESP_LOGV(TAG, "Setting ES8388_DACPOWER to 0x%02X", dac_power); - ESP_LOGV(TAG, "Setting ES8388_DACCONTROL24 / ES8388_DACCONTROL25 to 0x%02X", reg_out1); - ESP_LOGV(TAG, "Setting ES8388_DACCONTROL26 / ES8388_DACCONTROL27 to 0x%02X", reg_out2); + ESP_LOGV(TAG, + "Setting ES8388_DACPOWER to 0x%02X\n" + "Setting ES8388_DACCONTROL24 / ES8388_DACCONTROL25 to 0x%02X\n" + "Setting ES8388_DACCONTROL26 / ES8388_DACCONTROL27 to 0x%02X", + dac_power, reg_out1, reg_out2); ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL24, reg_out1)); // LOUT1VOL ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL25, reg_out1)); // ROUT1VOL From 766826cc9cc12732b289edfb8b34e0f226e2ef93 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:28:01 -1000 Subject: [PATCH 0965/1145] [esp32][libretiny] Reuse preference buffer to avoid heap churn (#12890) --- esphome/components/esp32/preferences.cpp | 6 ++++-- esphome/components/libretiny/preferences.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index 5e1e8734e5..08439746b6 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -23,9 +23,11 @@ struct NVSData { size_t len; void set_data(const uint8_t *src, size_t size) { - this->data = std::make_unique(size); + if (!this->data || this->len != size) { + this->data = std::make_unique(size); + this->len = size; + } memcpy(this->data.get(), src, size); - this->len = size; } }; diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index e47e88c6f3..68bc279767 100644 --- a/esphome/components/libretiny/preferences.cpp +++ b/esphome/components/libretiny/preferences.cpp @@ -22,9 +22,11 @@ struct NVSData { size_t len; void set_data(const uint8_t *src, size_t size) { - this->data = std::make_unique(size); + if (!this->data || this->len != size) { + this->data = std::make_unique(size); + this->len = size; + } memcpy(this->data.get(), src, size); - this->len = size; } }; From 1e70091a27f8c6ea45da8cf2586359823d7679e2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:28:17 -1000 Subject: [PATCH 0966/1145] [esp32_hosted] Combine log statements to reduce loop blocking (#12884) --- .../esp32_hosted/update/esp32_hosted_update.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp index de130ca71f..626bda3af3 100644 --- a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp @@ -41,11 +41,13 @@ void Esp32HostedUpdate::setup() { if (this->firmware_size_ >= app_desc_offset + sizeof(esp_app_desc_t)) { esp_app_desc_t *app_desc = (esp_app_desc_t *) (this->firmware_data_ + app_desc_offset); if (app_desc->magic_word == ESP_APP_DESC_MAGIC_WORD) { - ESP_LOGD(TAG, "Firmware version: %s", app_desc->version); - ESP_LOGD(TAG, "Project name: %s", app_desc->project_name); - ESP_LOGD(TAG, "Build date: %s", app_desc->date); - ESP_LOGD(TAG, "Build time: %s", app_desc->time); - ESP_LOGD(TAG, "IDF version: %s", app_desc->idf_ver); + ESP_LOGD(TAG, + "Firmware version: %s\n" + "Project name: %s\n" + "Build date: %s\n" + "Build time: %s\n" + "IDF version: %s", + app_desc->version, app_desc->project_name, app_desc->date, app_desc->time, app_desc->idf_ver); this->update_info_.latest_version = app_desc->version; if (this->update_info_.latest_version != this->update_info_.current_version) { this->state_ = update::UPDATE_STATE_AVAILABLE; From cf513975f3a4ea61c546f8ca5040aeb4a062f9ea Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:30:45 -1000 Subject: [PATCH 0967/1145] [ens160_base] Combine log statements to reduce loop blocking (#12880) --- esphome/components/ens160_base/ens160_base.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/esphome/components/ens160_base/ens160_base.cpp b/esphome/components/ens160_base/ens160_base.cpp index 6ffaac9588..785b053f04 100644 --- a/esphome/components/ens160_base/ens160_base.cpp +++ b/esphome/components/ens160_base/ens160_base.cpp @@ -151,14 +151,16 @@ void ENS160Component::update() { } // verbose status logging - ESP_LOGV(TAG, "Status: ENS160 STATAS bit 0x%x", - (ENS160_DATA_STATUS_STATAS & (status_value)) == ENS160_DATA_STATUS_STATAS); - ESP_LOGV(TAG, "Status: ENS160 STATER bit 0x%x", - (ENS160_DATA_STATUS_STATER & (status_value)) == ENS160_DATA_STATUS_STATER); - ESP_LOGV(TAG, "Status: ENS160 VALIDITY FLAG 0x%02x", (ENS160_DATA_STATUS_VALIDITY & status_value) >> 2); - ESP_LOGV(TAG, "Status: ENS160 NEWDAT bit 0x%x", - (ENS160_DATA_STATUS_NEWDAT & (status_value)) == ENS160_DATA_STATUS_NEWDAT); - ESP_LOGV(TAG, "Status: ENS160 NEWGPR bit 0x%x", + ESP_LOGV(TAG, + "Status: ENS160 STATAS bit 0x%x\n" + "Status: ENS160 STATER bit 0x%x\n" + "Status: ENS160 VALIDITY FLAG 0x%02x\n" + "Status: ENS160 NEWDAT bit 0x%x\n" + "Status: ENS160 NEWGPR bit 0x%x", + (ENS160_DATA_STATUS_STATAS & (status_value)) == ENS160_DATA_STATUS_STATAS, + (ENS160_DATA_STATUS_STATER & (status_value)) == ENS160_DATA_STATUS_STATER, + (ENS160_DATA_STATUS_VALIDITY & status_value) >> 2, + (ENS160_DATA_STATUS_NEWDAT & (status_value)) == ENS160_DATA_STATUS_NEWDAT, (ENS160_DATA_STATUS_NEWGPR & (status_value)) == ENS160_DATA_STATUS_NEWGPR); data_ready = ENS160_DATA_STATUS_NEWDAT & status_value; From 9e5dbb073a6015f45c7eaf07f15d69f79be27716 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:31:14 -1000 Subject: [PATCH 0968/1145] [emmeti] Combine log statements to reduce loop blocking (#12878) --- esphome/components/emmeti/emmeti.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/esphome/components/emmeti/emmeti.cpp b/esphome/components/emmeti/emmeti.cpp index 3cb184f868..5286f962b8 100644 --- a/esphome/components/emmeti/emmeti.cpp +++ b/esphome/components/emmeti/emmeti.cpp @@ -153,8 +153,10 @@ void EmmetiClimate::reverse_add_(T val, size_t len, esphome::remote_base::Remote bool EmmetiClimate::check_checksum_(uint8_t checksum) { uint8_t expected = this->gen_checksum_(); - ESP_LOGV(TAG, "Expected checksum: %X", expected); - ESP_LOGV(TAG, "Checksum received: %X", checksum); + ESP_LOGV(TAG, + "Expected checksum: %X\n" + "Checksum received: %X", + expected, checksum); return checksum == expected; } @@ -264,8 +266,10 @@ bool EmmetiClimate::on_receive(remote_base::RemoteReceiveData data) { } } - ESP_LOGD(TAG, "Swing: %d", (curr_state.bitmap >> 1) & 0x01); - ESP_LOGD(TAG, "Sleep: %d", (curr_state.bitmap >> 2) & 0x01); + ESP_LOGD(TAG, + "Swing: %d\n" + "Sleep: %d", + (curr_state.bitmap >> 1) & 0x01, (curr_state.bitmap >> 2) & 0x01); for (size_t pos = 0; pos < 4; pos++) { if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { @@ -291,10 +295,13 @@ bool EmmetiClimate::on_receive(remote_base::RemoteReceiveData data) { } } - ESP_LOGD(TAG, "Turbo: %d", (curr_state.bitmap >> 3) & 0x01); - ESP_LOGD(TAG, "Light: %d", (curr_state.bitmap >> 4) & 0x01); - ESP_LOGD(TAG, "Tree: %d", (curr_state.bitmap >> 5) & 0x01); - ESP_LOGD(TAG, "Blow: %d", (curr_state.bitmap >> 6) & 0x01); + ESP_LOGD(TAG, + "Turbo: %d\n" + "Light: %d\n" + "Tree: %d\n" + "Blow: %d", + (curr_state.bitmap >> 3) & 0x01, (curr_state.bitmap >> 4) & 0x01, (curr_state.bitmap >> 5) & 0x01, + (curr_state.bitmap >> 6) & 0x01); uint16_t control_data = 0; for (size_t pos = 0; pos < 11; pos++) { From a6db5a2ed836d643a6e47cb8e12422e5c54e20a4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:33:38 -1000 Subject: [PATCH 0969/1145] [dfrobot_sen0395] Combine log statements to reduce loop blocking (#12876) --- esphome/components/dfrobot_sen0395/commands.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/esphome/components/dfrobot_sen0395/commands.cpp b/esphome/components/dfrobot_sen0395/commands.cpp index 42074c80cf..8bb6ddf942 100644 --- a/esphome/components/dfrobot_sen0395/commands.cpp +++ b/esphome/components/dfrobot_sen0395/commands.cpp @@ -179,8 +179,10 @@ uint8_t DetRangeCfgCommand::on_message(std::string &message) { ESP_LOGE(TAG, "Cannot configure range config. Sensor is not stopped!"); return 1; // Command done } else if (message == "Done") { - ESP_LOGI(TAG, "Updated detection area config:"); - ESP_LOGI(TAG, "Detection area 1 from %.02fm to %.02fm.", this->min1_, this->max1_); + ESP_LOGI(TAG, + "Updated detection area config:\n" + "Detection area 1 from %.02fm to %.02fm.", + this->min1_, this->max1_); if (this->min2_ >= 0 && this->max2_ >= 0) { ESP_LOGI(TAG, "Detection area 2 from %.02fm to %.02fm.", this->min2_, this->max2_); } @@ -209,9 +211,11 @@ uint8_t SetLatencyCommand::on_message(std::string &message) { ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!"); return 1; // Command done } else if (message == "Done") { - ESP_LOGI(TAG, "Updated output latency config:"); - ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.03f s.", this->delay_after_detection_); - ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.03f s.", this->delay_after_disappear_); + ESP_LOGI(TAG, + "Updated output latency config:\n" + "Signal that someone was detected is delayed by %.03f s.\n" + "Signal that nobody is detected anymore is delayed by %.03f s.", + this->delay_after_detection_, this->delay_after_disappear_); ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str()); return 1; // Command done } From 25a325da617c4df23999d91ead7f805d2f7a6ffd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:33:49 -1000 Subject: [PATCH 0970/1145] [current_based] Combine log statements to reduce loop blocking (#12873) --- esphome/components/current_based/current_based_cover.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/current_based/current_based_cover.cpp b/esphome/components/current_based/current_based_cover.cpp index 895b5515cb..cb3f65c9cd 100644 --- a/esphome/components/current_based/current_based_cover.cpp +++ b/esphome/components/current_based/current_based_cover.cpp @@ -146,8 +146,10 @@ void CurrentBasedCover::dump_config() { if (this->close_obstacle_current_threshold_ != FLT_MAX) { ESP_LOGCONFIG(TAG, " Close obstacle current threshold: %.11fA", this->close_obstacle_current_threshold_); } - ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f); - ESP_LOGCONFIG(TAG, "Obstacle Rollback: %.1f%%", this->obstacle_rollback_ * 100); + ESP_LOGCONFIG(TAG, + " Close Duration: %.1fs\n" + "Obstacle Rollback: %.1f%%", + this->close_duration_ / 1e3f, this->obstacle_rollback_ * 100); if (this->max_duration_ != UINT32_MAX) { ESP_LOGCONFIG(TAG, "Maximum duration: %.1fs", this->max_duration_ / 1e3f); } From 5a8b0f59b88651b2ec32c1bb08417001eea405f9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:33:58 -1000 Subject: [PATCH 0971/1145] [cd74hc4067] Combine log statements to reduce loop blocking (#12870) --- esphome/components/cd74hc4067/cd74hc4067.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/cd74hc4067/cd74hc4067.cpp b/esphome/components/cd74hc4067/cd74hc4067.cpp index 174dc676f9..4293d7af07 100644 --- a/esphome/components/cd74hc4067/cd74hc4067.cpp +++ b/esphome/components/cd74hc4067/cd74hc4067.cpp @@ -21,12 +21,14 @@ void CD74HC4067Component::setup() { } void CD74HC4067Component::dump_config() { - ESP_LOGCONFIG(TAG, "CD74HC4067 Multiplexer:"); + ESP_LOGCONFIG(TAG, + "CD74HC4067 Multiplexer:\n" + " switch delay: %" PRIu32, + this->switch_delay_); LOG_PIN(" S0 Pin: ", this->pin_s0_); LOG_PIN(" S1 Pin: ", this->pin_s1_); LOG_PIN(" S2 Pin: ", this->pin_s2_); LOG_PIN(" S3 Pin: ", this->pin_s3_); - ESP_LOGCONFIG(TAG, "switch delay: %" PRIu32, this->switch_delay_); } void CD74HC4067Component::activate_pin(uint8_t pin) { From dff8dc0ed1e325556d84a08d95aa3778eb248324 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:34:07 -1000 Subject: [PATCH 0972/1145] [cc1101] Combine log statements to reduce loop blocking (#12869) --- esphome/components/cc1101/cc1101.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 7e5309e165..10f72018f9 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -212,9 +212,8 @@ void CC1101Component::dump_config() { XTAL_FREQUENCY / (1 << 16); float symbol_rate = (((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY; float bw = XTAL_FREQUENCY / (8.0f * (4 + this->state_.CHANBW_M) * (1 << this->state_.CHANBW_E)); - ESP_LOGCONFIG(TAG, "CC1101:"); - LOG_PIN(" CS Pin: ", this->cs_); ESP_LOGCONFIG(TAG, + "CC1101:\n" " Chip ID: 0x%04X\n" " Frequency: %" PRId32 " Hz\n" " Channel: %u\n" @@ -224,6 +223,7 @@ void CC1101Component::dump_config() { " Output Power: %.1f dBm", this->chip_id_, freq, this->state_.CHANNR, MODULATION_NAMES[this->state_.MOD_FORMAT & 0x07], symbol_rate, bw, this->output_power_effective_); + LOG_PIN(" CS Pin: ", this->cs_); } void CC1101Component::begin_tx() { From 77b3ffee001305d85cd79ab89268c9757a9db8e7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 08:34:16 -1000 Subject: [PATCH 0973/1145] [factory_reset] Combine log statements to reduce loop blocking (#12866) --- esphome/components/factory_reset/factory_reset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/factory_reset/factory_reset.cpp b/esphome/components/factory_reset/factory_reset.cpp index bbbe399148..2e3f802343 100644 --- a/esphome/components/factory_reset/factory_reset.cpp +++ b/esphome/components/factory_reset/factory_reset.cpp @@ -30,8 +30,8 @@ static bool was_power_cycled() { void FactoryResetComponent::dump_config() { uint8_t count = 0; this->flash_.load(&count); - ESP_LOGCONFIG(TAG, "Factory Reset by Reset:"); ESP_LOGCONFIG(TAG, + "Factory Reset by Reset:\n" " Max interval between resets: %u seconds\n" " Current count: %u\n" " Factory reset after %u resets", From 9ae19d53dca62ed83f2630672e208480bbe7ab35 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sun, 4 Jan 2026 13:39:56 -0500 Subject: [PATCH 0974/1145] [ultrasonic] Fix timeout issues and deprecate timeout option (#12897) Co-authored-by: Claude --- esphome/components/ultrasonic/sensor.py | 14 ++++++++++++-- .../components/ultrasonic/ultrasonic_sensor.cpp | 17 +++++------------ .../components/ultrasonic/ultrasonic_sensor.h | 4 ---- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/esphome/components/ultrasonic/sensor.py b/esphome/components/ultrasonic/sensor.py index d341acb9d1..4b04ee7578 100644 --- a/esphome/components/ultrasonic/sensor.py +++ b/esphome/components/ultrasonic/sensor.py @@ -1,3 +1,5 @@ +import logging + from esphome import pins import esphome.codegen as cg from esphome.components import sensor @@ -11,6 +13,8 @@ from esphome.const import ( UNIT_METER, ) +_LOGGER = logging.getLogger(__name__) + CONF_PULSE_TIME = "pulse_time" ultrasonic_ns = cg.esphome_ns.namespace("ultrasonic") @@ -30,7 +34,7 @@ CONFIG_SCHEMA = ( { cv.Required(CONF_TRIGGER_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_ECHO_PIN): pins.internal_gpio_input_pin_schema, - cv.Optional(CONF_TIMEOUT, default="2m"): cv.distance, + cv.Optional(CONF_TIMEOUT): cv.distance, cv.Optional( CONF_PULSE_TIME, default="10us" ): cv.positive_time_period_microseconds, @@ -49,5 +53,11 @@ async def to_code(config): echo = await cg.gpio_pin_expression(config[CONF_ECHO_PIN]) cg.add(var.set_echo_pin(echo)) - cg.add(var.set_timeout_us(config[CONF_TIMEOUT] / (0.000343 / 2))) + # Remove before 2026.8.0 + if CONF_TIMEOUT in config: + _LOGGER.warning( + "'timeout' option is deprecated and will be removed in 2026.8.0. " + "The option has no effect and can be safely removed." + ) + cg.add(var.set_pulse_time_us(config[CONF_PULSE_TIME])) diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.cpp b/esphome/components/ultrasonic/ultrasonic_sensor.cpp index 184d93f189..369a10edbd 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.cpp +++ b/esphome/components/ultrasonic/ultrasonic_sensor.cpp @@ -6,8 +6,8 @@ namespace esphome::ultrasonic { static const char *const TAG = "ultrasonic.sensor"; -static constexpr uint32_t DEBOUNCE_US = 50; // Ignore edges within 50us (noise filtering) -static constexpr uint32_t TIMEOUT_MARGIN_US = 1000; // Extra margin for sensor processing time +static constexpr uint32_t DEBOUNCE_US = 50; // Ignore edges within 50us (noise filtering) +static constexpr uint32_t MEASUREMENT_TIMEOUT_US = 80000; // Maximum time to wait for measurement completion void IRAM_ATTR UltrasonicSensorStore::gpio_intr(UltrasonicSensorStore *arg) { uint32_t now = micros(); @@ -64,12 +64,8 @@ void UltrasonicSensorComponent::loop() { } uint32_t elapsed = micros() - this->measurement_start_us_; - if (elapsed >= this->timeout_us_ + TIMEOUT_MARGIN_US) { - ESP_LOGD(TAG, - "'%s' - Timeout after %" PRIu32 "us (measurement_start=%" PRIu32 ", echo_start=%" PRIu32 - ", echo_end=%" PRIu32 ")", - this->name_.c_str(), elapsed, this->measurement_start_us_, this->store_.echo_start_us, - this->store_.echo_end_us); + if (elapsed >= MEASUREMENT_TIMEOUT_US) { + ESP_LOGD(TAG, "'%s' - Measurement timed out after %" PRIu32 "us", this->name_.c_str(), elapsed); this->publish_state(NAN); this->measurement_pending_ = false; } @@ -79,10 +75,7 @@ void UltrasonicSensorComponent::dump_config() { LOG_SENSOR("", "Ultrasonic Sensor", this); LOG_PIN(" Echo Pin: ", this->echo_pin_); LOG_PIN(" Trigger Pin: ", this->trigger_pin_); - ESP_LOGCONFIG(TAG, - " Pulse time: %" PRIu32 " us\n" - " Timeout: %" PRIu32 " us", - this->pulse_time_us_, this->timeout_us_); + ESP_LOGCONFIG(TAG, " Pulse time: %" PRIu32 " us", this->pulse_time_us_); LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.h b/esphome/components/ultrasonic/ultrasonic_sensor.h index e2266543ce..b0c00e51f0 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.h +++ b/esphome/components/ultrasonic/ultrasonic_sensor.h @@ -22,9 +22,6 @@ class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent void set_trigger_pin(InternalGPIOPin *trigger_pin) { this->trigger_pin_ = trigger_pin; } void set_echo_pin(InternalGPIOPin *echo_pin) { this->echo_pin_ = echo_pin; } - /// Set the timeout for waiting for the echo in µs. - void set_timeout_us(uint32_t timeout_us) { this->timeout_us_ = timeout_us; } - void setup() override; void loop() override; void dump_config() override; @@ -44,7 +41,6 @@ class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent ISRInternalGPIOPin trigger_pin_isr_; InternalGPIOPin *echo_pin_; UltrasonicSensorStore store_; - uint32_t timeout_us_{}; uint32_t pulse_time_us_{}; uint32_t measurement_start_us_{0}; From 449e478becae951c293991e713c70ab7123db93d Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Sun, 4 Jan 2026 12:50:10 -0800 Subject: [PATCH 0975/1145] [hub75] Bump esp-hub75 version to 0.2.2 (#12674) --- esphome/components/hub75/display.py | 33 +++++++++++++--- esphome/components/hub75/hub75.cpp | 19 +++++++-- esphome/components/hub75/hub75_component.h | 4 +- esphome/idf_component.yml | 2 +- .../hub75/test.esp32-s3-idf-rotate.yaml | 39 +++++++++++++++++++ 5 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 tests/components/hub75/test.esp32-s3-idf-rotate.yaml diff --git a/esphome/components/hub75/display.py b/esphome/components/hub75/display.py index 7736319330..40202e52ca 100644 --- a/esphome/components/hub75/display.py +++ b/esphome/components/hub75/display.py @@ -15,6 +15,7 @@ from esphome.const import ( CONF_ID, CONF_LAMBDA, CONF_OE_PIN, + CONF_ROTATION, CONF_UPDATE_INTERVAL, ) from esphome.core import ID @@ -134,6 +135,14 @@ CLOCK_SPEEDS = { "20MHZ": Hub75ClockSpeed.HZ_20M, } +Hub75Rotation = cg.global_ns.enum("Hub75Rotation", is_class=True) +ROTATIONS = { + 0: Hub75Rotation.ROTATE_0, + 90: Hub75Rotation.ROTATE_90, + 180: Hub75Rotation.ROTATE_180, + 270: Hub75Rotation.ROTATE_270, +} + HUB75Display = hub75_ns.class_("HUB75Display", cg.PollingComponent, display.Display) Hub75Config = cg.global_ns.struct("Hub75Config") Hub75Pins = cg.global_ns.struct("Hub75Pins") @@ -361,6 +370,8 @@ CONFIG_SCHEMA = cv.All( display.FULL_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(HUB75Display), + # Override rotation - store Hub75Rotation directly (driver handles rotation) + cv.Optional(CONF_ROTATION): cv.enum(ROTATIONS, int=True), # Board preset (optional - provides default pin mappings) cv.Optional(CONF_BOARD): cv.one_of(*BOARDS.keys(), lower=True), # Panel dimensions @@ -378,7 +389,7 @@ CONFIG_SCHEMA = cv.All( # Display configuration cv.Optional(CONF_DOUBLE_BUFFER): cv.boolean, cv.Optional(CONF_BRIGHTNESS): cv.int_range(min=0, max=255), - cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=6, max=12), + cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=4, max=12), cv.Optional(CONF_GAMMA_CORRECT): cv.enum( {"LINEAR": 0, "CIE1931": 1, "GAMMA_2_2": 2}, upper=True ), @@ -490,10 +501,11 @@ def _build_config_struct( Fields must be added in declaration order (see hub75_types.h) to satisfy C++ designated initializer requirements. The order is: 1. fields_before_pins (panel_width through layout) - 2. pins - 3. output_clock_speed - 4. min_refresh_rate - 5. fields_after_min_refresh (latch_blanking through brightness) + 2. rotation + 3. pins + 4. output_clock_speed + 5. min_refresh_rate + 6. fields_after_min_refresh (latch_blanking through brightness) """ fields_before_pins = [ (CONF_PANEL_WIDTH, "panel_width"), @@ -516,6 +528,10 @@ def _build_config_struct( _append_config_fields(config, fields_before_pins, config_fields) + # Rotation - config already contains Hub75Rotation enum from cv.enum + if CONF_ROTATION in config: + config_fields.append(("rotation", config[CONF_ROTATION])) + config_fields.append(("pins", pins_struct)) if CONF_CLOCK_SPEED in config: @@ -531,7 +547,7 @@ def _build_config_struct( async def to_code(config: ConfigType) -> None: add_idf_component( name="esphome/esp-hub75", - ref="0.1.7", + ref="0.2.2", ) # Set compile-time configuration via defines @@ -570,6 +586,11 @@ async def to_code(config: ConfigType) -> None: pins_struct = _build_pins_struct(pin_expressions, e_pin_num) hub75_config = _build_config_struct(config, pins_struct, min_refresh) + # Rotation is handled by the hub75 driver (config_.rotation already set above). + # Force rotation to 0 for ESPHome's Display base class to avoid double-rotation. + if CONF_ROTATION in config: + config[CONF_ROTATION] = 0 + # Create display and register var = cg.new_Pvariable(config[CONF_ID], hub75_config) await display.register_display(var, config) diff --git a/esphome/components/hub75/hub75.cpp b/esphome/components/hub75/hub75.cpp index e29f1a898c..cf8661b2b3 100644 --- a/esphome/components/hub75/hub75.cpp +++ b/esphome/components/hub75/hub75.cpp @@ -92,14 +92,25 @@ void HUB75Display::fill(Color color) { if (!this->enabled_) [[unlikely]] return; - // Special case: black (off) - use fast hardware clear - if (!color.is_on()) { + // Start with full display rect + display::Rect fill_rect(0, 0, this->get_width_internal(), this->get_height_internal()); + + // Apply clipping using Rect::shrink() to intersect + display::Rect clip = this->get_clipping(); + if (clip.is_set()) { + fill_rect.shrink(clip); + if (!fill_rect.is_set()) + return; // Completely clipped + } + + // Fast path: black filling entire display + if (!color.is_on() && fill_rect.x == 0 && fill_rect.y == 0 && fill_rect.w == this->get_width_internal() && + fill_rect.h == this->get_height_internal()) { driver_->clear(); return; } - // For non-black colors, fall back to base class (pixel-by-pixel) - Display::fill(color); + driver_->fill(fill_rect.x, fill_rect.y, fill_rect.w, fill_rect.h, color.r, color.g, color.b); } void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) { diff --git a/esphome/components/hub75/hub75_component.h b/esphome/components/hub75/hub75_component.h index f0e7ea10d5..ab7e3fc5b1 100644 --- a/esphome/components/hub75/hub75_component.h +++ b/esphome/components/hub75/hub75_component.h @@ -39,8 +39,8 @@ class HUB75Display : public display::Display { protected: // Display internal methods - int get_width_internal() override { return config_.panel_width * config_.layout_cols; } - int get_height_internal() override { return config_.panel_height * config_.layout_rows; } + int get_width_internal() override { return this->driver_ != nullptr ? this->driver_->get_width() : 0; } + int get_height_internal() override { return this->driver_ != nullptr ? this->driver_->get_height() : 0; } // Member variables Hub75Driver *driver_{nullptr}; diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 4573391bc1..36aa77c524 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -28,6 +28,6 @@ dependencies: rules: - if: "target in [esp32s2, esp32s3, esp32p4]" esphome/esp-hub75: - version: 0.1.7 + version: 0.2.2 rules: - if: "target in [esp32, esp32s2, esp32s3, esp32p4]" diff --git a/tests/components/hub75/test.esp32-s3-idf-rotate.yaml b/tests/components/hub75/test.esp32-s3-idf-rotate.yaml new file mode 100644 index 0000000000..9855fcb4e6 --- /dev/null +++ b/tests/components/hub75/test.esp32-s3-idf-rotate.yaml @@ -0,0 +1,39 @@ +display: + - platform: hub75 + id: my_hub75 + board: apollo-automation-rev6 + panel_width: 64 + panel_height: 64 + layout_rows: 1 + layout_cols: 2 + rotation: 90 + bit_depth: 4 + double_buffer: true + auto_clear_enabled: true + update_interval: 16ms + latch_blanking: 1 + clock_speed: 20MHz + lambda: |- + // Test clipping: 8 columns x 4 rows of 16x16 colored squares + Color colors[32] = { + Color(255, 0, 0), Color(0, 255, 0), Color(0, 0, 255), Color(255, 255, 0), + Color(255, 0, 255), Color(0, 255, 255), Color(255, 128, 0), Color(128, 0, 255), + Color(0, 128, 255), Color(255, 0, 128), Color(128, 255, 0), Color(0, 255, 128), + Color(255, 128, 128), Color(128, 255, 128), Color(128, 128, 255), Color(255, 255, 128), + Color(255, 128, 255), Color(128, 255, 255), Color(192, 64, 0), Color(64, 192, 0), + Color(0, 64, 192), Color(192, 0, 64), Color(64, 0, 192), Color(0, 192, 64), + Color(128, 64, 64), Color(64, 128, 64), Color(64, 64, 128), Color(128, 128, 64), + Color(128, 64, 128), Color(64, 128, 128), Color(255, 255, 255), Color(128, 128, 128) + }; + int idx = 0; + for (int row = 0; row < 4; row++) { + for (int col = 0; col < 8; col++) { + // Clipping mode: clip to square bounds, then fill "entire screen" + it.start_clipping(col * 16, row * 16, (col + 1) * 16, (row + 1) * 16); + it.fill(colors[idx]); + it.end_clipping(); + idx++; + } + } + +<<: !include common.yaml From dd8259b2ce5fa2f45571bb69c2059a38e7637cd2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:24:36 -1000 Subject: [PATCH 0976/1145] [gcja5] Combine log statements to reduce loop blocking (#12898) --- esphome/components/gcja5/gcja5.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/gcja5/gcja5.cpp b/esphome/components/gcja5/gcja5.cpp index a7342bc828..f7f7f8d02c 100644 --- a/esphome/components/gcja5/gcja5.cpp +++ b/esphome/components/gcja5/gcja5.cpp @@ -95,11 +95,13 @@ void GCJA5Component::parse_data_() { if (!this->first_status_log_) { this->first_status_log_ = true; - ESP_LOGI(TAG, "GCJA5 Status"); - ESP_LOGI(TAG, "Overall Status : %i", (status >> 6) & 0x03); - ESP_LOGI(TAG, "PD Status : %i", (status >> 4) & 0x03); - ESP_LOGI(TAG, "LD Status : %i", (status >> 2) & 0x03); - ESP_LOGI(TAG, "Fan Status : %i", (status >> 0) & 0x03); + ESP_LOGI(TAG, + "GCJA5 Status\n" + "Overall Status : %i\n" + "PD Status : %i\n" + "LD Status : %i\n" + "Fan Status : %i", + (status >> 6) & 0x03, (status >> 4) & 0x03, (status >> 2) & 0x03, (status >> 0) & 0x03); } } From 8287484a36880b45fdf88e8bbabd931cd1ad5e7d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:24:51 -1000 Subject: [PATCH 0977/1145] [gl_r01_i2c] Combine log statements to reduce loop blocking (#12899) --- esphome/components/gl_r01_i2c/gl_r01_i2c.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp b/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp index e2a64b6877..38328c4b03 100644 --- a/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp +++ b/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp @@ -27,8 +27,10 @@ void GLR01I2CComponent::setup() { } void GLR01I2CComponent::dump_config() { - ESP_LOGCONFIG(TAG, "GL-R01 I2C:"); - ESP_LOGCONFIG(TAG, " Firmware Version: 0x%04X", this->version_); + ESP_LOGCONFIG(TAG, + "GL-R01 I2C:\n" + " Firmware Version: 0x%04X", + this->version_); LOG_I2C_DEVICE(this); LOG_SENSOR(" ", "Distance", this); } From 7e758260647761466a9c357fd726080c6a1df534 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:25:24 -1000 Subject: [PATCH 0978/1145] [wifi] Fix LibreTiny thread safety with queue-based event handling (#12833) --- esphome/components/wifi/wifi_component.h | 5 + .../wifi/wifi_component_libretiny.cpp | 288 +++++++++++++++--- 2 files changed, 248 insertions(+), 45 deletions(-) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 5bf1f444e8..1906b672b8 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -245,6 +245,10 @@ enum WifiMinAuthMode : uint8_t { struct IDFWiFiEvent; #endif +#ifdef USE_LIBRETINY +struct LTWiFiEvent; +#endif + /** Listener interface for WiFi IP state changes. * * Components can implement this interface to receive IP address updates @@ -583,6 +587,7 @@ class WiFiComponent : public Component { #ifdef USE_LIBRETINY void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info); + void wifi_process_event_(LTWiFiEvent *event); void wifi_scan_done_callback_(); #endif diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 9bbd319f33..e9ccb86871 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -3,12 +3,16 @@ #ifdef USE_WIFI #ifdef USE_LIBRETINY +#include #include #include #include "lwip/ip_addr.h" #include "lwip/err.h" #include "lwip/dns.h" +#include +#include + #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -19,7 +23,68 @@ namespace esphome::wifi { static const char *const TAG = "wifi_lt"; -static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +// Thread-safe event handling for LibreTiny WiFi +// +// LibreTiny's WiFi.onEvent() callback runs in the WiFi driver's thread context, +// not the main ESPHome loop. Without synchronization, modifying shared state +// (like connection status flags) from the callback causes race conditions: +// - The main loop may never see state changes (values cached in registers) +// - State changes may be visible in inconsistent order +// - LibreTiny targets (BK7231, RTL8720) lack atomic instructions (no LDREX/STREX) +// +// Solution: Queue events in the callback and process them in the main loop. +// This is the same approach used by ESP32 IDF's wifi_process_event_(). +// All state modifications happen in the main loop context, eliminating races. + +static constexpr size_t EVENT_QUEUE_SIZE = 16; // Max pending WiFi events before overflow +static QueueHandle_t s_event_queue = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static volatile uint32_t s_event_queue_overflow_count = + 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +// Event structure for queued WiFi events - contains a copy of event data +// to avoid lifetime issues with the original event data from the callback +struct LTWiFiEvent { + arduino_event_id_t event_id; + union { + struct { + uint8_t ssid[33]; + uint8_t ssid_len; + uint8_t bssid[6]; + uint8_t channel; + uint8_t authmode; + } sta_connected; + struct { + uint8_t ssid[33]; + uint8_t ssid_len; + uint8_t bssid[6]; + uint8_t reason; + } sta_disconnected; + struct { + uint8_t old_mode; + uint8_t new_mode; + } sta_authmode_change; + struct { + uint32_t status; + uint8_t number; + uint8_t scan_id; + } scan_done; + struct { + uint8_t mac[6]; + int rssi; + } ap_probe_req; + } data; +}; + +// Connection state machine - only modified from main loop after queue processing +enum class LTWiFiSTAState : uint8_t { + IDLE, // Not connecting + CONNECTING, // Connection in progress + CONNECTED, // Successfully connected with IP + ERROR_NOT_FOUND, // AP not found (probe failed) + ERROR_FAILED, // Connection failed (auth, timeout, etc.) +}; + +static LTWiFiSTAState s_sta_state = LTWiFiSTAState::IDLE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) bool WiFiComponent::wifi_mode_(optional sta, optional ap) { uint8_t current_mode = WiFi.getMode(); @@ -136,7 +201,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { this->wifi_apply_hostname_(); - s_sta_connecting = true; + // Reset state machine before connecting + s_sta_state = LTWiFiSTAState::CONNECTING; WiFiStatus status = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().empty() ? NULL : ap.get_password().c_str(), ap.get_channel(), // 0 = auto @@ -271,16 +337,101 @@ const char *get_disconnect_reason_str(uint8_t reason) { using esphome_wifi_event_id_t = arduino_event_id_t; using esphome_wifi_event_info_t = arduino_event_info_t; +// Event callback - runs in WiFi driver thread context +// Only queues events for processing in main loop, no logging or state changes here void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_wifi_event_info_t info) { + if (s_event_queue == nullptr) { + return; + } + + // Allocate on heap and fill directly to avoid extra memcpy + auto *to_send = new LTWiFiEvent{}; // NOLINT(cppcoreguidelines-owning-memory) + to_send->event_id = event; + + // Copy event-specific data switch (event) { + case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { + auto &it = info.wifi_sta_connected; + to_send->data.sta_connected.ssid_len = it.ssid_len; + memcpy(to_send->data.sta_connected.ssid, it.ssid, + std::min(static_cast(it.ssid_len), sizeof(to_send->data.sta_connected.ssid) - 1)); + memcpy(to_send->data.sta_connected.bssid, it.bssid, 6); + to_send->data.sta_connected.channel = it.channel; + to_send->data.sta_connected.authmode = it.authmode; + break; + } + case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: { + auto &it = info.wifi_sta_disconnected; + to_send->data.sta_disconnected.ssid_len = it.ssid_len; + memcpy(to_send->data.sta_disconnected.ssid, it.ssid, + std::min(static_cast(it.ssid_len), sizeof(to_send->data.sta_disconnected.ssid) - 1)); + memcpy(to_send->data.sta_disconnected.bssid, it.bssid, 6); + to_send->data.sta_disconnected.reason = it.reason; + break; + } + case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: { + auto &it = info.wifi_sta_authmode_change; + to_send->data.sta_authmode_change.old_mode = it.old_mode; + to_send->data.sta_authmode_change.new_mode = it.new_mode; + break; + } + case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: { + auto &it = info.wifi_scan_done; + to_send->data.scan_done.status = it.status; + to_send->data.scan_done.number = it.number; + to_send->data.scan_done.scan_id = it.scan_id; + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { + auto &it = info.wifi_ap_probereqrecved; + memcpy(to_send->data.ap_probe_req.mac, it.mac, 6); + to_send->data.ap_probe_req.rssi = it.rssi; + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { + auto &it = info.wifi_sta_connected; + memcpy(to_send->data.sta_connected.bssid, it.bssid, 6); + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { + auto &it = info.wifi_sta_disconnected; + memcpy(to_send->data.sta_disconnected.bssid, it.bssid, 6); + break; + } + case ESPHOME_EVENT_ID_WIFI_READY: + case ESPHOME_EVENT_ID_WIFI_STA_START: + case ESPHOME_EVENT_ID_WIFI_STA_STOP: + case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: + case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: + case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: + case ESPHOME_EVENT_ID_WIFI_AP_START: + case ESPHOME_EVENT_ID_WIFI_AP_STOP: + case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: + // No additional data needed + break; + default: + // Unknown event, don't queue + delete to_send; // NOLINT(cppcoreguidelines-owning-memory) + return; + } + + // Queue event (don't block if queue is full) + if (xQueueSend(s_event_queue, &to_send, 0) != pdPASS) { + delete to_send; // NOLINT(cppcoreguidelines-owning-memory) + s_event_queue_overflow_count++; + } +} + +// Process a single event from the queue - runs in main loop context +void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { + switch (event->event_id) { case ESPHOME_EVENT_ID_WIFI_READY: { ESP_LOGV(TAG, "Ready"); break; } case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: { - auto it = info.wifi_scan_done; - ESP_LOGV(TAG, "Scan done: status=%u number=%u scan_id=%u", it.status, it.number, it.scan_id); - + auto &it = event->data.scan_done; + ESP_LOGV(TAG, "Scan done: status=%" PRIu32 " number=%u scan_id=%u", it.status, it.number, it.scan_id); this->wifi_scan_done_callback_(); break; } @@ -291,14 +442,18 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } case ESPHOME_EVENT_ID_WIFI_STA_STOP: { ESP_LOGV(TAG, "STA stop"); - s_sta_connecting = false; + s_sta_state = LTWiFiSTAState::IDLE; break; } case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { - auto it = info.wifi_sta_connected; + auto &it = event->data.sta_connected; + char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, bssid_buf); ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", it.ssid_len, - (const char *) it.ssid, format_mac_address_pretty(it.bssid).c_str(), it.channel, - get_auth_mode_str(it.authmode)); + (const char *) it.ssid, bssid_buf, it.channel, get_auth_mode_str(it.authmode)); + // Note: We don't set CONNECTED state here yet - wait for GOT_IP + // This matches ESP32 IDF behavior where s_sta_connected is set but + // wifi_sta_connect_status_() also checks got_ipv4_address_ #ifdef USE_WIFI_LISTENERS for (auto *listener : this->connect_state_listeners_) { listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid); @@ -306,6 +461,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here #ifdef USE_WIFI_MANUAL_IP if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { + s_sta_state = LTWiFiSTAState::CONNECTED; for (auto *listener : this->ip_state_listeners_) { listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); } @@ -315,19 +471,18 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: { - auto it = info.wifi_sta_disconnected; + auto &it = event->data.sta_disconnected; // LibreTiny can send spurious disconnect events with empty ssid/bssid during connection. // These are typically "Association Leave" events that don't indicate actual failures: // [W][wifi_lt]: Disconnected ssid='' bssid=00:00:00:00:00:00 reason='Association Leave' // [W][wifi_lt]: Disconnected ssid='' bssid=00:00:00:00:00:00 reason='Association Leave' // [V][wifi_lt]: Connected ssid='WIFI' bssid=... channel=3, authmode=WPA2 PSK - // Without this check, the spurious events set s_sta_connecting=false, causing - // wifi_sta_connect_status_() to return IDLE. The main loop then sees - // "Unknown connection status 0" (wifi_component.cpp check_connecting_finished) - // and calls retry_connect(), aborting a connection that may succeed moments later. - // Real connection failures will have ssid/bssid populated, or we'll hit the connection timeout. - if (it.ssid_len == 0 && s_sta_connecting) { + // Without this check, the spurious events would transition state to ERROR_FAILED, + // causing wifi_sta_connect_status_() to return an error. The main loop would then + // call retry_connect(), aborting a connection that may succeed moments later. + // Only ignore benign reasons - real failures like NO_AP_FOUND should still be processed. + if (it.ssid_len == 0 && s_sta_state == LTWiFiSTAState::CONNECTING && it.reason != WIFI_REASON_NO_AP_FOUND) { ESP_LOGV(TAG, "Ignoring disconnect event with empty ssid while connecting (reason=%s)", get_disconnect_reason_str(it.reason)); break; @@ -336,11 +491,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ if (it.reason == WIFI_REASON_NO_AP_FOUND) { ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len, (const char *) it.ssid); + s_sta_state = LTWiFiSTAState::ERROR_NOT_FOUND; } else { - char bssid_s[18]; + char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; format_mac_addr_upper(it.bssid, bssid_s); ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len, (const char *) it.ssid, bssid_s, get_disconnect_reason_str(it.reason)); + s_sta_state = LTWiFiSTAState::ERROR_FAILED; } uint8_t reason = it.reason; @@ -351,7 +508,6 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ this->error_from_callback_ = true; } - s_sta_connecting = false; #ifdef USE_WIFI_LISTENERS static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : this->connect_state_listeners_) { @@ -361,24 +517,22 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: { - auto it = info.wifi_sta_authmode_change; + auto &it = event->data.sta_authmode_change; ESP_LOGV(TAG, "Authmode Change old=%s new=%s", get_auth_mode_str(it.old_mode), get_auth_mode_str(it.new_mode)); // Mitigate CVE-2020-12638 // https://lbsfilm.at/blog/wpa2-authenticationmode-downgrade-in-espressif-microprocessors if (it.old_mode != WIFI_AUTH_OPEN && it.new_mode == WIFI_AUTH_OPEN) { ESP_LOGW(TAG, "Potential Authmode downgrade detected, disconnecting"); - // we can't call retry_connect() from this context, so disconnect immediately - // and notify main thread with error_from_callback_ WiFi.disconnect(); this->error_from_callback_ = true; + s_sta_state = LTWiFiSTAState::ERROR_FAILED; } break; } case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: { - // auto it = info.got_ip.ip_info; ESP_LOGV(TAG, "static_ip=%s gateway=%s", format_ip4_addr(WiFi.localIP()).c_str(), format_ip4_addr(WiFi.gatewayIP()).c_str()); - s_sta_connecting = false; + s_sta_state = LTWiFiSTAState::CONNECTED; #ifdef USE_WIFI_LISTENERS for (auto *listener : this->ip_state_listeners_) { listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); @@ -387,7 +541,6 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: { - // auto it = info.got_ip.ip_info; ESP_LOGV(TAG, "Got IPv6"); #ifdef USE_WIFI_LISTENERS for (auto *listener : this->ip_state_listeners_) { @@ -398,6 +551,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: { ESP_LOGV(TAG, "Lost IP"); + // Don't change state to IDLE - let the disconnect event handle that break; } case ESPHOME_EVENT_ID_WIFI_AP_START: { @@ -409,15 +563,21 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { - auto it = info.wifi_sta_connected; - auto &mac = it.bssid; - ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + auto &it = event->data.sta_connected; + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, mac_buf); + ESP_LOGV(TAG, "AP client connected MAC=%s", mac_buf); +#endif break; } case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { - auto it = info.wifi_sta_disconnected; - auto &mac = it.bssid; - ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + auto &it = event->data.sta_disconnected; + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, mac_buf); + ESP_LOGV(TAG, "AP client disconnected MAC=%s", mac_buf); +#endif break; } case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: { @@ -425,8 +585,12 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { - auto it = info.wifi_ap_probereqrecved; - ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + auto &it = event->data.ap_probe_req; + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", mac_buf, it.rssi); +#endif break; } default: @@ -434,23 +598,35 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } } void WiFiComponent::wifi_pre_setup_() { + // Create event queue for thread-safe event handling + // Events are pushed from WiFi callback thread and processed in main loop + s_event_queue = xQueueCreate(EVENT_QUEUE_SIZE, sizeof(LTWiFiEvent *)); + if (s_event_queue == nullptr) { + ESP_LOGE(TAG, "Failed to create event queue"); + return; + } + auto f = std::bind(&WiFiComponent::wifi_event_callback_, this, std::placeholders::_1, std::placeholders::_2); WiFi.onEvent(f); // Make sure WiFi is in clean state before anything starts this->wifi_mode_(false, false); } WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { - auto status = WiFi.status(); - if (status == WL_CONNECTED) { - return WiFiSTAConnectStatus::CONNECTED; - } else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) { - return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; - } else if (status == WL_NO_SSID_AVAIL) { - return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; - } else if (s_sta_connecting) { - return WiFiSTAConnectStatus::CONNECTING; + // Use state machine instead of querying WiFi.status() directly + // State is updated in main loop from queued events, ensuring thread safety + switch (s_sta_state) { + case LTWiFiSTAState::CONNECTED: + return WiFiSTAConnectStatus::CONNECTED; + case LTWiFiSTAState::ERROR_NOT_FOUND: + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + case LTWiFiSTAState::ERROR_FAILED: + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; + case LTWiFiSTAState::CONNECTING: + return WiFiSTAConnectStatus::CONNECTING; + case LTWiFiSTAState::IDLE: + default: + return WiFiSTAConnectStatus::IDLE; } - return WiFiSTAConnectStatus::IDLE; } bool WiFiComponent::wifi_scan_start_(bool passive) { // enable STA @@ -534,9 +710,9 @@ network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.softAPIP()}; #endif // USE_WIFI_AP bool WiFiComponent::wifi_disconnect_() { - // Clear connecting flag first so disconnect events aren't ignored + // Reset state first so disconnect events aren't ignored // and wifi_sta_connect_status_() returns IDLE instead of CONNECTING - s_sta_connecting = false; + s_sta_state = LTWiFiSTAState::IDLE; return WiFi.disconnect(); } @@ -563,7 +739,29 @@ int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } -void WiFiComponent::wifi_loop_() {} +void WiFiComponent::wifi_loop_() { + // Process all pending events from the queue + if (s_event_queue == nullptr) { + return; + } + + // Check for dropped events due to queue overflow + if (s_event_queue_overflow_count > 0) { + ESP_LOGW(TAG, "Event queue overflow, %" PRIu32 " events dropped", s_event_queue_overflow_count); + s_event_queue_overflow_count = 0; + } + + while (true) { + LTWiFiEvent *event; + if (xQueueReceive(s_event_queue, &event, 0) != pdTRUE) { + // No more events + break; + } + + wifi_process_event_(event); + delete event; // NOLINT(cppcoreguidelines-owning-memory) + } +} } // namespace esphome::wifi #endif // USE_LIBRETINY From 61ecfb5f2b6421a3824e060ebbad4c35d71c1113 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:25:52 -1000 Subject: [PATCH 0979/1145] [openthread] Combine log statements to reduce loop blocking (#12917) --- esphome/components/openthread/openthread_esp.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/esphome/components/openthread/openthread_esp.cpp b/esphome/components/openthread/openthread_esp.cpp index 1f18e51496..a9aff3cce4 100644 --- a/esphome/components/openthread/openthread_esp.cpp +++ b/esphome/components/openthread/openthread_esp.cpp @@ -126,9 +126,12 @@ void OpenThreadComponent::ot_main() { ESP_LOGE(TAG, "Failed to set OpenThread linkmode."); } link_mode_config = otThreadGetLinkMode(esp_openthread_get_instance()); - ESP_LOGD(TAG, "Link Mode Device Type: %s", link_mode_config.mDeviceType ? "true" : "false"); - ESP_LOGD(TAG, "Link Mode Network Data: %s", link_mode_config.mNetworkData ? "true" : "false"); - ESP_LOGD(TAG, "Link Mode RX On When Idle: %s", link_mode_config.mRxOnWhenIdle ? "true" : "false"); + ESP_LOGD(TAG, + "Link Mode Device Type: %s\n" + "Link Mode Network Data: %s\n" + "Link Mode RX On When Idle: %s", + link_mode_config.mDeviceType ? "true" : "false", link_mode_config.mNetworkData ? "true" : "false", + link_mode_config.mRxOnWhenIdle ? "true" : "false"); // Run the main loop #if CONFIG_OPENTHREAD_CLI @@ -144,8 +147,8 @@ void OpenThreadComponent::ot_main() { // Make sure the length is 0 so we fallback to the configuration dataset.mLength = 0; } else { - ESP_LOGI(TAG, "Found OpenThread-managed dataset, ignoring esphome configuration"); - ESP_LOGI(TAG, "(set force_dataset: true to override)"); + ESP_LOGI(TAG, "Found OpenThread-managed dataset, ignoring esphome configuration\n" + "(set force_dataset: true to override)"); } #endif From fc9683f024e69886fd54844e77170222e476af98 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:26:13 -1000 Subject: [PATCH 0980/1145] [opentherm] Combine log statements to reduce loop blocking (#12916) --- esphome/components/opentherm/hub.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/opentherm/hub.cpp b/esphome/components/opentherm/hub.cpp index b23792fc7a..7a0cdc7f80 100644 --- a/esphome/components/opentherm/hub.cpp +++ b/esphome/components/opentherm/hub.cpp @@ -395,10 +395,8 @@ void OpenthermHub::dump_config() { this->write_initial_messages_(initial_messages); this->write_repeating_messages_(repeating_messages); - ESP_LOGCONFIG(TAG, "OpenTherm:"); - LOG_PIN(" In: ", this->in_pin_); - LOG_PIN(" Out: ", this->out_pin_); ESP_LOGCONFIG(TAG, + "OpenTherm:\n" " Sync mode: %s\n" " Sensors: %s\n" " Binary sensors: %s\n" @@ -409,6 +407,8 @@ void OpenthermHub::dump_config() { YESNO(this->sync_mode_), SHOW(OPENTHERM_SENSOR_LIST(ID, )), SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, )), SHOW(OPENTHERM_SWITCH_LIST(ID, )), SHOW(OPENTHERM_INPUT_SENSOR_LIST(ID, )), SHOW(OPENTHERM_OUTPUT_LIST(ID, )), SHOW(OPENTHERM_NUMBER_LIST(ID, ))); + LOG_PIN(" In: ", this->in_pin_); + LOG_PIN(" Out: ", this->out_pin_); ESP_LOGCONFIG(TAG, " Initial requests:"); for (auto type : initial_messages) { ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type)); From 6d9d593e12c76a799ae3b944b9262c852186e909 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:27:14 -1000 Subject: [PATCH 0981/1145] [my9231] Combine log statements to reduce loop blocking (#12915) --- esphome/components/my9231/my9231.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/my9231/my9231.cpp b/esphome/components/my9231/my9231.cpp index fba7ac2bf3..5b77a49e72 100644 --- a/esphome/components/my9231/my9231.cpp +++ b/esphome/components/my9231/my9231.cpp @@ -58,14 +58,14 @@ void MY9231OutputComponent::setup() { } } void MY9231OutputComponent::dump_config() { - ESP_LOGCONFIG(TAG, "MY9231:"); - LOG_PIN(" DI Pin: ", this->pin_di_); - LOG_PIN(" DCKI Pin: ", this->pin_dcki_); ESP_LOGCONFIG(TAG, + "MY9231:\n" " Total number of channels: %u\n" " Number of chips: %u\n" " Bit depth: %u", this->num_channels_, this->num_chips_, this->bit_depth_); + LOG_PIN(" DI Pin: ", this->pin_di_); + LOG_PIN(" DCKI Pin: ", this->pin_dcki_); } void MY9231OutputComponent::loop() { if (!this->update_) From ccc9d95c9d8a66384c153d6dd2e543ab0f99214b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:28:14 -1000 Subject: [PATCH 0982/1145] [mqtt] Combine log statements to reduce loop blocking (#12914) --- esphome/components/mqtt/mqtt_backend_esp32.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index e3105f4860..3838d6df26 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -166,10 +166,12 @@ void MQTTBackendESP32::mqtt_event_handler_(const Event &event) { case MQTT_EVENT_ERROR: ESP_LOGE(TAG, "MQTT_EVENT_ERROR"); if (event.error_handle.error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { - ESP_LOGE(TAG, "Last error code reported from esp-tls: 0x%x", event.error_handle.esp_tls_last_esp_err); - ESP_LOGE(TAG, "Last tls stack error number: 0x%x", event.error_handle.esp_tls_stack_err); - ESP_LOGE(TAG, "Last captured errno : %d (%s)", event.error_handle.esp_transport_sock_errno, - strerror(event.error_handle.esp_transport_sock_errno)); + ESP_LOGE(TAG, + "Last error code reported from esp-tls: 0x%x\n" + "Last tls stack error number: 0x%x\n" + "Last captured errno : %d (%s)", + event.error_handle.esp_tls_last_esp_err, event.error_handle.esp_tls_stack_err, + event.error_handle.esp_transport_sock_errno, strerror(event.error_handle.esp_transport_sock_errno)); } else if (event.error_handle.error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) { ESP_LOGE(TAG, "Connection refused error: 0x%x", event.error_handle.connect_return_code); } else { From d1d5c942ec81a7dc8d2cb1f2566faf1253ce2423 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:51:01 -1000 Subject: [PATCH 0983/1145] [mcp9600] Combine log statements to reduce loop blocking (#12913) --- esphome/components/mcp9600/mcp9600.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/mcp9600/mcp9600.cpp b/esphome/components/mcp9600/mcp9600.cpp index e1a88988c4..ff411bef7a 100644 --- a/esphome/components/mcp9600/mcp9600.cpp +++ b/esphome/components/mcp9600/mcp9600.cpp @@ -63,12 +63,12 @@ void MCP9600Component::setup() { } void MCP9600Component::dump_config() { - ESP_LOGCONFIG(TAG, "MCP9600:"); + ESP_LOGCONFIG(TAG, + "MCP9600:\n" + " Device ID: 0x%x", + this->device_id_); LOG_I2C_DEVICE(this); LOG_UPDATE_INTERVAL(this); - - ESP_LOGCONFIG(TAG, " Device ID: 0x%x", this->device_id_); - LOG_SENSOR(" ", "Hot Junction Temperature", this->hot_junction_sensor_); LOG_SENSOR(" ", "Cold Junction Temperature", this->cold_junction_sensor_); From aa4b274b3c69f228463bdefd09c812b488990240 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:51:18 -1000 Subject: [PATCH 0984/1145] [mcp3204] Combine log statements to reduce loop blocking (#12912) --- esphome/components/mcp3204/mcp3204.cpp | 6 ++++-- esphome/components/mcp3204/sensor/mcp3204_sensor.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/mcp3204/mcp3204.cpp b/esphome/components/mcp3204/mcp3204.cpp index f0dd171a14..abefcad0eb 100644 --- a/esphome/components/mcp3204/mcp3204.cpp +++ b/esphome/components/mcp3204/mcp3204.cpp @@ -11,9 +11,11 @@ float MCP3204::get_setup_priority() const { return setup_priority::HARDWARE; } void MCP3204::setup() { this->spi_setup(); } void MCP3204::dump_config() { - ESP_LOGCONFIG(TAG, "MCP3204:"); + ESP_LOGCONFIG(TAG, + "MCP3204:\n" + " Reference Voltage: %.2fV", + this->reference_voltage_); LOG_PIN(" CS Pin:", this->cs_); - ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_); } float MCP3204::read_data(uint8_t pin, bool differential) { diff --git a/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp b/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp index 4c4abef4a7..e673537be1 100644 --- a/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp +++ b/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp @@ -11,8 +11,10 @@ float MCP3204Sensor::get_setup_priority() const { return setup_priority::DATA; } void MCP3204Sensor::dump_config() { LOG_SENSOR("", "MCP3204 Sensor", this); - ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); - ESP_LOGCONFIG(TAG, " Differential Mode: %s", YESNO(this->differential_mode_)); + ESP_LOGCONFIG(TAG, + " Pin: %u\n" + " Differential Mode: %s", + this->pin_, YESNO(this->differential_mode_)); LOG_UPDATE_INTERVAL(this); } float MCP3204Sensor::sample() { return this->parent_->read_data(this->pin_, this->differential_mode_); } From 9b2a36a313be718bb7b6425a14d670d3f5d09dfc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:51:33 -1000 Subject: [PATCH 0985/1145] [hc8] Combine log statements to reduce loop blocking (#12900) --- esphome/components/hc8/hc8.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/components/hc8/hc8.cpp b/esphome/components/hc8/hc8.cpp index 5b649c2735..4d0f77df1b 100644 --- a/esphome/components/hc8/hc8.cpp +++ b/esphome/components/hc8/hc8.cpp @@ -89,11 +89,12 @@ void HC8Component::calibrate(uint16_t baseline) { float HC8Component::get_setup_priority() const { return setup_priority::DATA; } void HC8Component::dump_config() { - ESP_LOGCONFIG(TAG, "HC8:"); + ESP_LOGCONFIG(TAG, + "HC8:\n" + " Warmup time: %" PRIu32 " s", + this->warmup_seconds_); LOG_SENSOR(" ", "CO2", this->co2_sensor_); this->check_uart_settings(9600); - - ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_); } } // namespace esphome::hc8 From 8ae1f26b6adefc02e40e0a1033808e68452b6a0c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:51:45 -1000 Subject: [PATCH 0986/1145] [hlw8012] Combine log statements to reduce loop blocking (#12901) --- esphome/components/hlw8012/hlw8012.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index 70a05e4f72..f037ee9d8b 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -33,15 +33,15 @@ void HLW8012Component::setup() { } } void HLW8012Component::dump_config() { - ESP_LOGCONFIG(TAG, "HLW8012:"); - LOG_PIN(" SEL Pin: ", this->sel_pin_); - LOG_PIN(" CF Pin: ", this->cf_pin_); - LOG_PIN(" CF1 Pin: ", this->cf1_pin_); ESP_LOGCONFIG(TAG, + "HLW8012:\n" " Change measurement mode every %" PRIu32 "\n" " Current resistor: %.1f mΩ\n" " Voltage Divider: %.1f", this->change_mode_every_, this->current_resistor_ * 1000.0f, this->voltage_divider_); + LOG_PIN(" SEL Pin: ", this->sel_pin_); + LOG_PIN(" CF Pin: ", this->cf_pin_); + LOG_PIN(" CF1 Pin: ", this->cf1_pin_); LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_); From 9bbfad4a081f83a257ae7b7ad12182199dbbf204 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:52:08 -1000 Subject: [PATCH 0987/1145] [honeywellabp] Combine log statements to reduce loop blocking (#12902) --- esphome/components/honeywellabp/honeywellabp.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/honeywellabp/honeywellabp.cpp b/esphome/components/honeywellabp/honeywellabp.cpp index 4c00f034aa..c204325dfc 100644 --- a/esphome/components/honeywellabp/honeywellabp.cpp +++ b/esphome/components/honeywellabp/honeywellabp.cpp @@ -35,8 +35,10 @@ uint8_t HONEYWELLABPSensor::readsensor_() { pressure_count_ = ((uint16_t) (buf_[0]) << 8 & 0x3F00) | ((uint16_t) (buf_[1]) & 0xFF); // 11 - bit temperature is all of byte 2 (lowest 8 bits) and the first three bits of byte 3 temperature_count_ = (((uint16_t) (buf_[2]) << 3) & 0x7F8) | (((uint16_t) (buf_[3]) >> 5) & 0x7); - ESP_LOGV(TAG, "Sensor pressure_count_ %d", pressure_count_); - ESP_LOGV(TAG, "Sensor temperature_count_ %d", temperature_count_); + ESP_LOGV(TAG, + "Sensor pressure_count_ %d\n" + "Sensor temperature_count_ %d", + pressure_count_, temperature_count_); } return status_; } From 548600b47a30783c534abb883cf5ea0aaab65e11 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:52:34 -1000 Subject: [PATCH 0988/1145] [ina260] Combine log statements to reduce loop blocking (#12903) --- esphome/components/ina260/ina260.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/ina260/ina260.cpp b/esphome/components/ina260/ina260.cpp index 9dd922cec2..4d6acf400c 100644 --- a/esphome/components/ina260/ina260.cpp +++ b/esphome/components/ina260/ina260.cpp @@ -61,13 +61,13 @@ void INA260Component::setup() { } void INA260Component::dump_config() { - ESP_LOGCONFIG(TAG, "INA260:"); + ESP_LOGCONFIG(TAG, + "INA260:\n" + " Manufacture ID: 0x%x\n" + " Device ID: 0x%x", + this->manufacture_id_, this->device_id_); LOG_I2C_DEVICE(this); LOG_UPDATE_INTERVAL(this); - - ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->manufacture_id_); - ESP_LOGCONFIG(TAG, " Device ID: 0x%x", this->device_id_); - LOG_SENSOR(" ", "Bus Voltage", this->bus_voltage_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_); LOG_SENSOR(" ", "Power", this->power_sensor_); From 1fccddf67f3ad46bdc45c72df1717cada4fcf4d2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:52:56 -1000 Subject: [PATCH 0989/1145] [ina2xx_base] Combine log statements to reduce loop blocking (#12904) --- esphome/components/ina2xx_base/ina2xx_base.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/ina2xx_base/ina2xx_base.cpp b/esphome/components/ina2xx_base/ina2xx_base.cpp index 4ab02703e8..7185d21810 100644 --- a/esphome/components/ina2xx_base/ina2xx_base.cpp +++ b/esphome/components/ina2xx_base/ina2xx_base.cpp @@ -364,8 +364,10 @@ bool INA2XX::configure_shunt_() { ESP_LOGW(TAG, "Shunt value too high"); } this->shunt_cal_ &= 0x7FFF; - ESP_LOGV(TAG, "Given Rshunt=%f Ohm and Max_current=%.3f", this->shunt_resistance_ohm_, this->max_current_a_); - ESP_LOGV(TAG, "New CURRENT_LSB=%f, SHUNT_CAL=%u", this->current_lsb_, this->shunt_cal_); + ESP_LOGV(TAG, + "Given Rshunt=%f Ohm and Max_current=%.3f\n" + "New CURRENT_LSB=%f, SHUNT_CAL=%u", + this->shunt_resistance_ohm_, this->max_current_a_, this->current_lsb_, this->shunt_cal_); return this->write_unsigned_16_(RegisterMap::REG_SHUNT_CAL, this->shunt_cal_); } From b0855b4a0e68dc1483c733eaffd2714744b8941b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:53:50 -1000 Subject: [PATCH 0990/1145] [lc709203f] Combine log statements to reduce loop blocking (#12905) --- esphome/components/lc709203f/lc709203f.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/esphome/components/lc709203f/lc709203f.cpp b/esphome/components/lc709203f/lc709203f.cpp index 7e6ac878f8..ad9d6b3098 100644 --- a/esphome/components/lc709203f/lc709203f.cpp +++ b/esphome/components/lc709203f/lc709203f.cpp @@ -146,19 +146,14 @@ void Lc709203f::update() { } void Lc709203f::dump_config() { - ESP_LOGCONFIG(TAG, "LC709203F:"); - LOG_I2C_DEVICE(this); - - LOG_UPDATE_INTERVAL(this); ESP_LOGCONFIG(TAG, + "LC709203F:\n" " Pack Size: %d mAH\n" - " Pack APA: 0x%02X", - this->pack_size_, this->apa_); - - // This is only true if the pack_voltage_ is either 0x0000 or 0x0001. The config validator - // should have already verified this. - ESP_LOGCONFIG(TAG, " Pack Rated Voltage: 3.%sV", this->pack_voltage_ == 0x0000 ? "8" : "7"); - + " Pack APA: 0x%02X\n" + " Pack Rated Voltage: 3.%sV", + this->pack_size_, this->apa_, this->pack_voltage_ == 0x0000 ? "8" : "7"); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); LOG_SENSOR(" ", "Battery Remaining", this->battery_remaining_sensor_); From ca574a15509c83b68ade2c32aabb437532839be0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:54:14 -1000 Subject: [PATCH 0991/1145] [ledc] Combine log statements to reduce loop blocking (#12906) --- esphome/components/ledc/ledc_output.cpp | 37 +++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index aaa4794586..a203dde115 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -130,8 +130,10 @@ void LEDCOutput::setup() { } int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_); - ESP_LOGV(TAG, "Configured frequency %f with a bit depth of %u bits", this->frequency_, this->bit_depth_); - ESP_LOGV(TAG, "Angle of %.1f° results in hpoint %u", this->phase_angle_, hpoint); + ESP_LOGV(TAG, + "Configured frequency %f with a bit depth of %u bits\n" + "Angle of %.1f° results in hpoint %u", + this->frequency_, this->bit_depth_, this->phase_angle_, hpoint); ledc_channel_config_t chan_conf{}; chan_conf.gpio_num = this->pin_->get_pin(); @@ -147,25 +149,30 @@ void LEDCOutput::setup() { } void LEDCOutput::dump_config() { - ESP_LOGCONFIG(TAG, "Output:"); - LOG_PIN(" Pin ", this->pin_); ESP_LOGCONFIG(TAG, + "Output:\n" " Channel: %u\n" " PWM Frequency: %.1f Hz\n" " Phase angle: %.1f°\n" " Bit depth: %u", this->channel_, this->frequency_, this->phase_angle_, this->bit_depth_); - ESP_LOGV(TAG, " Max frequency for bit depth: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_)); - ESP_LOGV(TAG, " Min frequency for bit depth: %f", - ledc_min_frequency_for_bit_depth(this->bit_depth_, (this->frequency_ < 100))); - ESP_LOGV(TAG, " Max frequency for bit depth-1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ - 1)); - ESP_LOGV(TAG, " Min frequency for bit depth-1: %f", - ledc_min_frequency_for_bit_depth(this->bit_depth_ - 1, (this->frequency_ < 100))); - ESP_LOGV(TAG, " Max frequency for bit depth+1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ + 1)); - ESP_LOGV(TAG, " Min frequency for bit depth+1: %f", - ledc_min_frequency_for_bit_depth(this->bit_depth_ + 1, (this->frequency_ < 100))); - ESP_LOGV(TAG, " Max res bits: %d", MAX_RES_BITS); - ESP_LOGV(TAG, " Clock frequency: %f", CLOCK_FREQUENCY); + LOG_PIN(" Pin ", this->pin_); + ESP_LOGV(TAG, + " Max frequency for bit depth: %f\n" + " Min frequency for bit depth: %f\n" + " Max frequency for bit depth-1: %f\n" + " Min frequency for bit depth-1: %f\n" + " Max frequency for bit depth+1: %f\n" + " Min frequency for bit depth+1: %f\n" + " Max res bits: %d\n" + " Clock frequency: %f", + ledc_max_frequency_for_bit_depth(this->bit_depth_), + ledc_min_frequency_for_bit_depth(this->bit_depth_, (this->frequency_ < 100)), + ledc_max_frequency_for_bit_depth(this->bit_depth_ - 1), + ledc_min_frequency_for_bit_depth(this->bit_depth_ - 1, (this->frequency_ < 100)), + ledc_max_frequency_for_bit_depth(this->bit_depth_ + 1), + ledc_min_frequency_for_bit_depth(this->bit_depth_ + 1, (this->frequency_ < 100)), MAX_RES_BITS, + CLOCK_FREQUENCY); } void LEDCOutput::update_frequency(float frequency) { From b8d93f2150759a9bbb01de105e215ec3652882ff Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:54:31 -1000 Subject: [PATCH 0992/1145] [mopeka_std_check] Combine log statements to reduce loop blocking (#12911) --- .../components/mopeka_std_check/mopeka_std_check.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/mopeka_std_check/mopeka_std_check.cpp b/esphome/components/mopeka_std_check/mopeka_std_check.cpp index 986a9a9fdc..231d09b909 100644 --- a/esphome/components/mopeka_std_check/mopeka_std_check.cpp +++ b/esphome/components/mopeka_std_check/mopeka_std_check.cpp @@ -17,10 +17,12 @@ static const uint16_t MANUFACTURER_ID = 0x000D; static constexpr size_t MOPEKA_MAX_LOG_BYTES = 32; void MopekaStdCheck::dump_config() { - ESP_LOGCONFIG(TAG, "Mopeka Std Check"); - ESP_LOGCONFIG(TAG, " Propane Butane mix: %.0f%%", this->propane_butane_mix_ * 100); - ESP_LOGCONFIG(TAG, " Tank distance empty: %" PRIi32 "mm", this->empty_mm_); - ESP_LOGCONFIG(TAG, " Tank distance full: %" PRIi32 "mm", this->full_mm_); + ESP_LOGCONFIG(TAG, + "Mopeka Std Check\n" + " Propane Butane mix: %.0f%%\n" + " Tank distance empty: %" PRIi32 "mm\n" + " Tank distance full: %" PRIi32 "mm", + this->propane_butane_mix_ * 100, this->empty_mm_, this->full_mm_); LOG_SENSOR(" ", "Level", this->level_); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); From a5368d1d95b5dc4dcf612b087cfb885a1fa80f9d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:54:47 -1000 Subject: [PATCH 0993/1145] [modbus] Combine log statements to reduce loop blocking (#12910) --- esphome/components/modbus/modbus.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 457dff4075..5e9387b843 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -196,12 +196,12 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { } void Modbus::dump_config() { - ESP_LOGCONFIG(TAG, "Modbus:"); - LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_); ESP_LOGCONFIG(TAG, + "Modbus:\n" " Send Wait Time: %d ms\n" " CRC Disabled: %s", this->send_wait_time_, YESNO(this->disable_crc_)); + LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_); } float Modbus::get_setup_priority() const { // After UART bus From f2308c77c67e9b2f0d4c898435542472603ea983 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:55:18 -1000 Subject: [PATCH 0994/1145] [libretiny_pwm] Combine log statements to reduce loop blocking (#12907) --- esphome/components/libretiny_pwm/libretiny_pwm.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/libretiny_pwm/libretiny_pwm.cpp b/esphome/components/libretiny_pwm/libretiny_pwm.cpp index 92e4097c0e..4e4a16d761 100644 --- a/esphome/components/libretiny_pwm/libretiny_pwm.cpp +++ b/esphome/components/libretiny_pwm/libretiny_pwm.cpp @@ -31,9 +31,11 @@ void LibreTinyPWM::setup() { } void LibreTinyPWM::dump_config() { - ESP_LOGCONFIG(TAG, "PWM Output:"); + ESP_LOGCONFIG(TAG, + "PWM Output:\n" + " Frequency: %.1f Hz", + this->frequency_); LOG_PIN(" Pin ", this->pin_); - ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); } void LibreTinyPWM::update_frequency(float frequency) { From 05695affff57afb8bbf00da629dca2d042b71365 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 11:55:31 -1000 Subject: [PATCH 0995/1145] [m5stack_8angle] Combine log statements to reduce loop blocking (#12908) --- esphome/components/m5stack_8angle/m5stack_8angle.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/m5stack_8angle/m5stack_8angle.cpp b/esphome/components/m5stack_8angle/m5stack_8angle.cpp index c542b4459e..5a9a5e8c9d 100644 --- a/esphome/components/m5stack_8angle/m5stack_8angle.cpp +++ b/esphome/components/m5stack_8angle/m5stack_8angle.cpp @@ -26,9 +26,11 @@ void M5Stack8AngleComponent::setup() { } void M5Stack8AngleComponent::dump_config() { - ESP_LOGCONFIG(TAG, "M5STACK_8ANGLE:"); + ESP_LOGCONFIG(TAG, + "M5STACK_8ANGLE:\n" + " Firmware version: %d", + this->fw_version_); LOG_I2C_DEVICE(this); - ESP_LOGCONFIG(TAG, " Firmware version: %d ", this->fw_version_); } float M5Stack8AngleComponent::read_knob_pos(uint8_t channel, AnalogBits bits) { From 71940acc492f8141d3daf9feba4b58265889e0a6 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 5 Jan 2026 08:37:44 +1000 Subject: [PATCH 0996/1145] [esp32_ble] Remove requirement for configured network (#12891) --- esphome/components/esp32_ble/__init__.py | 1 - esphome/components/socket/__init__.py | 7 ++- esphome/core/__init__.py | 19 ++++++ .../socket/test_wake_loop_threadsafe.py | 35 +++++++++++ tests/unit_tests/test_core.py | 62 +++++++++++++++++++ 5 files changed, 122 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index ced7e3fec9..dcc3ce71cf 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -22,7 +22,6 @@ from esphome.core import CORE, CoroPriority, TimePeriod, coroutine_with_priority import esphome.final_validate as fv DEPENDENCIES = ["esp32"] -AUTO_LOAD = ["socket"] CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"] DOMAIN = "esp32_ble" diff --git a/esphome/components/socket/__init__.py b/esphome/components/socket/__init__.py index 49e074a6ee..e364da78f8 100644 --- a/esphome/components/socket/__init__.py +++ b/esphome/components/socket/__init__.py @@ -47,6 +47,8 @@ def require_wake_loop_threadsafe() -> None: This enables the shared UDP loopback socket mechanism (~208 bytes RAM). The socket is shared across all components that use this feature. + This call is a no-op if networking is not enabled in the configuration. + IMPORTANT: This is for background thread context only, NOT ISR context. Socket operations are not safe to call from ISR handlers. @@ -56,8 +58,11 @@ def require_wake_loop_threadsafe() -> None: async def to_code(config): socket.require_wake_loop_threadsafe() """ + # Only set up once (idempotent - multiple components can call this) - if not CORE.data.get(KEY_WAKE_LOOP_THREADSAFE_REQUIRED, False): + if CORE.has_networking and not CORE.data.get( + KEY_WAKE_LOOP_THREADSAFE_REQUIRED, False + ): CORE.data[KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True cg.add_define("USE_WAKE_LOOP_THREADSAFE") # Consume 1 socket for the shared wake notification socket diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 3baec93186..70593d8153 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -721,6 +721,25 @@ class EsphomeCore: def config_filename(self) -> str: return self.config_path.name + def has_at_least_one_component(self, *components: str) -> bool: + """ + Are any of the given components configured? + :param components: component names + :return: true if so + """ + if self.config is None: + raise ValueError("Config has not been loaded yet") + + return any(component in self.config for component in components) + + @property + def has_networking(self) -> bool: + """ + Is a network component configured? + :return: true if so + """ + return self.has_at_least_one_component("wifi", "ethernet", "openthread") + def relative_config_path(self, *path: str | Path) -> Path: path_ = Path(*path).expanduser() return self.config_dir / path_ diff --git a/tests/components/socket/test_wake_loop_threadsafe.py b/tests/components/socket/test_wake_loop_threadsafe.py index 45e5ea2211..b4bc95176d 100644 --- a/tests/components/socket/test_wake_loop_threadsafe.py +++ b/tests/components/socket/test_wake_loop_threadsafe.py @@ -4,6 +4,7 @@ from esphome.core import CORE def test_require_wake_loop_threadsafe__first_call() -> None: """Test that first call sets up define and consumes socket.""" + CORE.config = {"wifi": True} socket.require_wake_loop_threadsafe() # Verify CORE.data was updated @@ -17,6 +18,7 @@ def test_require_wake_loop_threadsafe__idempotent() -> None: """Test that subsequent calls are idempotent.""" # Set up initial state as if already called CORE.data[socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True + CORE.config = {"ethernet": True} # Call again - should not raise or fail socket.require_wake_loop_threadsafe() @@ -31,6 +33,7 @@ def test_require_wake_loop_threadsafe__idempotent() -> None: def test_require_wake_loop_threadsafe__multiple_calls() -> None: """Test that multiple calls only set up once.""" # Call three times + CORE.config = {"openthread": True} socket.require_wake_loop_threadsafe() socket.require_wake_loop_threadsafe() socket.require_wake_loop_threadsafe() @@ -40,3 +43,35 @@ def test_require_wake_loop_threadsafe__multiple_calls() -> None: # Verify the define was added (only once, but we can just check it exists) assert any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines) + + +def test_require_wake_loop_threadsafe__no_networking() -> None: + """Test that wake loop is NOT configured when no networking is configured.""" + # Set up config without any networking components + CORE.config = {"esphome": {"name": "test"}, "logger": {}} + + # Call require_wake_loop_threadsafe + socket.require_wake_loop_threadsafe() + + # Verify CORE.data flag was NOT set (since has_networking returns False) + assert socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED not in CORE.data + + # Verify the define was NOT added + assert not any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines) + + +def test_require_wake_loop_threadsafe__no_networking_does_not_consume_socket() -> None: + """Test that no socket is consumed when no networking is configured.""" + # Set up config without any networking components + CORE.config = {"logger": {}} + + # Track initial socket consumer state + initial_consumers = CORE.data.get(socket.KEY_SOCKET_CONSUMERS, {}) + + # Call require_wake_loop_threadsafe + socket.require_wake_loop_threadsafe() + + # Verify no socket was consumed + consumers = CORE.data.get(socket.KEY_SOCKET_CONSUMERS, {}) + assert "socket.wake_loop_threadsafe" not in consumers + assert consumers == initial_consumers diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index e52cb24831..1fc8dab358 100644 --- a/tests/unit_tests/test_core.py +++ b/tests/unit_tests/test_core.py @@ -718,3 +718,65 @@ class TestEsphomeCore: # Even though "web_server" is in loaded_integrations due to the platform, # web_port must return None because the full web_server component is not configured assert target.web_port is None + + def test_has_at_least_one_component__none_configured(self, target): + """Test has_at_least_one_component returns False when none of the components are configured.""" + target.config = {const.CONF_ESPHOME: {"name": "test"}, "logger": {}} + + assert target.has_at_least_one_component("wifi", "ethernet") is False + + def test_has_at_least_one_component__one_configured(self, target): + """Test has_at_least_one_component returns True when one component is configured.""" + target.config = {const.CONF_WIFI: {}, "logger": {}} + + assert target.has_at_least_one_component("wifi", "ethernet") is True + + def test_has_at_least_one_component__multiple_configured(self, target): + """Test has_at_least_one_component returns True when multiple components are configured.""" + target.config = { + const.CONF_WIFI: {}, + const.CONF_ETHERNET: {}, + "logger": {}, + } + + assert ( + target.has_at_least_one_component("wifi", "ethernet", "bluetooth") is True + ) + + def test_has_at_least_one_component__single_component(self, target): + """Test has_at_least_one_component works with a single component.""" + target.config = {const.CONF_MQTT: {}} + + assert target.has_at_least_one_component("mqtt") is True + assert target.has_at_least_one_component("wifi") is False + + def test_has_at_least_one_component__config_not_loaded(self, target): + """Test has_at_least_one_component raises ValueError when config is not loaded.""" + target.config = None + + with pytest.raises(ValueError, match="Config has not been loaded yet"): + target.has_at_least_one_component("wifi") + + def test_has_networking__with_wifi(self, target): + """Test has_networking returns True when wifi is configured.""" + target.config = {const.CONF_WIFI: {}} + + assert target.has_networking is True + + def test_has_networking__with_ethernet(self, target): + """Test has_networking returns True when ethernet is configured.""" + target.config = {const.CONF_ETHERNET: {}} + + assert target.has_networking is True + + def test_has_networking__with_openthread(self, target): + """Test has_networking returns True when openthread is configured.""" + target.config = {const.CONF_OPENTHREAD: {}} + + assert target.has_networking is True + + def test_has_networking__without_networking(self, target): + """Test has_networking returns False when no networking component is configured.""" + target.config = {const.CONF_ESPHOME: {"name": "test"}, "logger": {}} + + assert target.has_networking is False From 25ef9aff044e6adc265eec5c2aa6f724c78b39c2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 12:53:46 -1000 Subject: [PATCH 0997/1145] [vl53l0x] Combine log statements to reduce loop blocking (#12929) --- esphome/components/vl53l0x/vl53l0x_sensor.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.cpp b/esphome/components/vl53l0x/vl53l0x_sensor.cpp index d2548a5bbd..e833657fc4 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.cpp +++ b/esphome/components/vl53l0x/vl53l0x_sensor.cpp @@ -27,8 +27,10 @@ void VL53L0XSensor::dump_config() { if (this->enable_pin_ != nullptr) { LOG_PIN(" Enable Pin: ", this->enable_pin_); } - ESP_LOGCONFIG(TAG, " Timeout: %u%s", this->timeout_us_, this->timeout_us_ > 0 ? "us" : " (no timeout)"); - ESP_LOGCONFIG(TAG, " Timing Budget %uus ", this->measurement_timing_budget_us_); + ESP_LOGCONFIG(TAG, + " Timeout: %u%s\n" + " Timing Budget %uus ", + this->timeout_us_, this->timeout_us_ > 0 ? "us" : " (no timeout)", this->measurement_timing_budget_us_); } void VL53L0XSensor::setup() { From 022c42f9ca0f19158426fcc5b04509e712307442 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 12:53:58 -1000 Subject: [PATCH 0998/1145] [tuya] Combine log statements to reduce loop blocking (#12924) --- .../components/tuya/binary_sensor/tuya_binary_sensor.cpp | 6 ++++-- esphome/components/tuya/text_sensor/tuya_text_sensor.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/tuya/binary_sensor/tuya_binary_sensor.cpp b/esphome/components/tuya/binary_sensor/tuya_binary_sensor.cpp index edfbb2ac60..a63e9c8318 100644 --- a/esphome/components/tuya/binary_sensor/tuya_binary_sensor.cpp +++ b/esphome/components/tuya/binary_sensor/tuya_binary_sensor.cpp @@ -14,8 +14,10 @@ void TuyaBinarySensor::setup() { } void TuyaBinarySensor::dump_config() { - ESP_LOGCONFIG(TAG, "Tuya Binary Sensor:"); - ESP_LOGCONFIG(TAG, " Binary Sensor has datapoint ID %u", this->sensor_id_); + ESP_LOGCONFIG(TAG, + "Tuya Binary Sensor:\n" + " Binary Sensor has datapoint ID %u", + this->sensor_id_); } } // namespace tuya diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp index fbe511811f..c71bf176a4 100644 --- a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp @@ -33,8 +33,10 @@ void TuyaTextSensor::setup() { } void TuyaTextSensor::dump_config() { - ESP_LOGCONFIG(TAG, "Tuya Text Sensor:"); - ESP_LOGCONFIG(TAG, " Text Sensor has datapoint ID %u", this->sensor_id_); + ESP_LOGCONFIG(TAG, + "Tuya Text Sensor:\n" + " Text Sensor has datapoint ID %u", + this->sensor_id_); } } // namespace tuya From e9cab96cb79644ebbf105d78da71153b5f190779 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 12:56:50 -1000 Subject: [PATCH 0999/1145] [sx1509] Combine log statements to reduce loop blocking (#12920) --- esphome/components/sx1509/output/sx1509_float_output.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/sx1509/output/sx1509_float_output.cpp b/esphome/components/sx1509/output/sx1509_float_output.cpp index 1d2541bb46..4a24d78478 100644 --- a/esphome/components/sx1509/output/sx1509_float_output.cpp +++ b/esphome/components/sx1509/output/sx1509_float_output.cpp @@ -22,8 +22,10 @@ void SX1509FloatOutputChannel::setup() { } void SX1509FloatOutputChannel::dump_config() { - ESP_LOGCONFIG(TAG, "SX1509 PWM:"); - ESP_LOGCONFIG(TAG, " sx1509 pin: %d", this->pin_); + ESP_LOGCONFIG(TAG, + "SX1509 PWM:\n" + " sx1509 pin: %d", + this->pin_); LOG_FLOAT_OUTPUT(this); } From 56d1d928f9400935007b9bffdc16ee5a9bca448a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 12:57:10 -1000 Subject: [PATCH 1000/1145] [tlc5947] Combine log statements to reduce loop blocking (#12921) --- esphome/components/tlc5947/tlc5947.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/tlc5947/tlc5947.cpp b/esphome/components/tlc5947/tlc5947.cpp index 6d4e099f7a..0a278bbaf6 100644 --- a/esphome/components/tlc5947/tlc5947.cpp +++ b/esphome/components/tlc5947/tlc5947.cpp @@ -21,12 +21,14 @@ void TLC5947::setup() { this->pwm_amounts_.resize(this->num_chips_ * N_CHANNELS_PER_CHIP, 0); } void TLC5947::dump_config() { - ESP_LOGCONFIG(TAG, "TLC5947:"); + ESP_LOGCONFIG(TAG, + "TLC5947:\n" + " Number of chips: %u", + this->num_chips_); LOG_PIN(" Data Pin: ", this->data_pin_); LOG_PIN(" Clock Pin: ", this->clock_pin_); LOG_PIN(" LAT Pin: ", this->lat_pin_); LOG_PIN(" OE Pin: ", this->outenable_pin_); - ESP_LOGCONFIG(TAG, " Number of chips: %u", this->num_chips_); } void TLC5947::loop() { From 88cb5d9671d3975a2a966de1066ff423f0ecf69a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 12:58:20 -1000 Subject: [PATCH 1001/1145] [tmp1075] Combine log statements to reduce loop blocking (#12923) --- esphome/components/tmp1075/tmp1075.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/esphome/components/tmp1075/tmp1075.cpp b/esphome/components/tmp1075/tmp1075.cpp index 1d9b384c66..9eb1e86c75 100644 --- a/esphome/components/tmp1075/tmp1075.cpp +++ b/esphome/components/tmp1075/tmp1075.cpp @@ -73,12 +73,15 @@ void TMP1075Sensor::set_fault_count(const int faults) { } void TMP1075Sensor::log_config_() { - ESP_LOGV(TAG, " oneshot : %d", config_.fields.oneshot); - ESP_LOGV(TAG, " rate : %d", config_.fields.rate); - ESP_LOGV(TAG, " faults : %d", config_.fields.faults); - ESP_LOGV(TAG, " polarity : %d", config_.fields.polarity); - ESP_LOGV(TAG, " alert_mode: %d", config_.fields.alert_mode); - ESP_LOGV(TAG, " shutdown : %d", config_.fields.shutdown); + ESP_LOGV(TAG, + " oneshot : %d\n" + " rate : %d\n" + " faults : %d\n" + " polarity : %d\n" + " alert_mode: %d\n" + " shutdown : %d", + config_.fields.oneshot, config_.fields.rate, config_.fields.faults, config_.fields.polarity, + config_.fields.alert_mode, config_.fields.shutdown); } void TMP1075Sensor::write_config() { From 32b3d27c7c1c8701a950fe43281422f2a67ed5b2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 12:58:42 -1000 Subject: [PATCH 1002/1145] [uln2003] Combine log statements to reduce loop blocking (#12926) --- esphome/components/uln2003/uln2003.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/uln2003/uln2003.cpp b/esphome/components/uln2003/uln2003.cpp index 991fe53487..11e1c3d4c0 100644 --- a/esphome/components/uln2003/uln2003.cpp +++ b/esphome/components/uln2003/uln2003.cpp @@ -34,12 +34,14 @@ void ULN2003::loop() { this->write_step_(this->current_uln_pos_); } void ULN2003::dump_config() { - ESP_LOGCONFIG(TAG, "ULN2003:"); + ESP_LOGCONFIG(TAG, + "ULN2003:\n" + " Sleep when done: %s", + YESNO(this->sleep_when_done_)); LOG_PIN(" Pin A: ", this->pin_a_); LOG_PIN(" Pin B: ", this->pin_b_); LOG_PIN(" Pin C: ", this->pin_c_); LOG_PIN(" Pin D: ", this->pin_d_); - ESP_LOGCONFIG(TAG, " Sleep when done: %s", YESNO(this->sleep_when_done_)); const char *step_mode_s; switch (this->step_mode_) { case ULN2003_STEP_MODE_FULL_STEP: From c59455e445bc4347ac9eb8687a9d2a9530cff3bd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 12:59:47 -1000 Subject: [PATCH 1003/1145] [mqtt] Combine log statements to reduce loop blocking (#12938) --- esphome/components/mqtt/mqtt_client.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index c650c99f62..d26548acfc 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -154,15 +154,17 @@ void MQTTClientComponent::on_log(uint8_t level, const char *tag, const char *mes void MQTTClientComponent::dump_config() { char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + // clang-format off ESP_LOGCONFIG(TAG, "MQTT:\n" " Server Address: %s:%u (%s)\n" " Username: " LOG_SECRET("'%s'") "\n" - " Client ID: " LOG_SECRET("'%s'") "\n" - " Clean Session: %s", + " Client ID: " LOG_SECRET("'%s'") "\n" + " Clean Session: %s", this->credentials_.address.c_str(), this->credentials_.port, this->ip_.str_to(ip_buf), this->credentials_.username.c_str(), this->credentials_.client_id.c_str(), YESNO(this->credentials_.clean_session)); + // clang-format on if (this->is_discovery_ip_enabled()) { ESP_LOGCONFIG(TAG, " Discovery IP enabled"); } From 6e633f7f3bb3ae1fa191f3ecce7e5939283c0d78 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:00:03 -1000 Subject: [PATCH 1004/1145] [usb_uart] Combine log statements to reduce loop blocking (#12928) --- esphome/components/usb_uart/cp210x.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/usb_uart/cp210x.cpp b/esphome/components/usb_uart/cp210x.cpp index be024d1ba2..483286560a 100644 --- a/esphome/components/usb_uart/cp210x.cpp +++ b/esphome/components/usb_uart/cp210x.cpp @@ -58,8 +58,10 @@ std::vector USBUartTypeCP210X::parse_descriptors(usb_device_handle_t dev ESP_LOGE(TAG, "get_active_config_descriptor failed"); return {}; } - ESP_LOGD(TAG, "bDeviceClass: %u, bDeviceSubClass: %u", device_desc->bDeviceClass, device_desc->bDeviceSubClass); - ESP_LOGD(TAG, "bNumInterfaces: %u", config_desc->bNumInterfaces); + ESP_LOGD(TAG, + "bDeviceClass: %u, bDeviceSubClass: %u\n" + "bNumInterfaces: %u", + device_desc->bDeviceClass, device_desc->bDeviceSubClass, config_desc->bNumInterfaces); if (device_desc->bDeviceClass != 0) { ESP_LOGE(TAG, "bDeviceClass != 0"); return {}; From 557b6a9ef0657dec888cf9347fbaaca61908d84e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:00:59 -1000 Subject: [PATCH 1005/1145] [sun] Combine log statements to reduce loop blocking (#12919) --- esphome/components/sun/sun.cpp | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/esphome/components/sun/sun.cpp b/esphome/components/sun/sun.cpp index df7030461b..e8fc4e44d1 100644 --- a/esphome/components/sun/sun.cpp +++ b/esphome/components/sun/sun.cpp @@ -172,21 +172,25 @@ struct SunAtTime { void debug() const { // debug output like in example 25.a, p. 165 - ESP_LOGV(TAG, "jde: %f", jde); - ESP_LOGV(TAG, "T: %f", t); - ESP_LOGV(TAG, "L_0: %f", mean_longitude()); - ESP_LOGV(TAG, "M: %f", mean_anomaly()); - ESP_LOGV(TAG, "e: %f", eccentricity()); - ESP_LOGV(TAG, "C: %f", equation_of_center()); - ESP_LOGV(TAG, "Odot: %f", true_longitude()); - ESP_LOGV(TAG, "Omega: %f", omega()); - ESP_LOGV(TAG, "lambda: %f", apparent_longitude()); - ESP_LOGV(TAG, "epsilon_0: %f", mean_obliquity()); - ESP_LOGV(TAG, "epsilon: %f", true_obliquity()); - ESP_LOGV(TAG, "v: %f", true_anomaly()); auto eq = equatorial_coordinate(); - ESP_LOGV(TAG, "right_ascension: %f", eq.right_ascension); - ESP_LOGV(TAG, "declination: %f", eq.declination); + ESP_LOGV(TAG, + "jde: %f\n" + "T: %f\n" + "L_0: %f\n" + "M: %f\n" + "e: %f\n" + "C: %f\n" + "Odot: %f\n" + "Omega: %f\n" + "lambda: %f\n" + "epsilon_0: %f\n" + "epsilon: %f\n" + "v: %f\n" + "right_ascension: %f\n" + "declination: %f", + jde, t, mean_longitude(), mean_anomaly(), eccentricity(), equation_of_center(), true_longitude(), omega(), + apparent_longitude(), mean_obliquity(), true_obliquity(), true_anomaly(), eq.right_ascension, + eq.declination); } }; From 0b996616b87261d6758e57c8faedb486d9e20236 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:01:33 -1000 Subject: [PATCH 1006/1145] [waveshare_epaper] Combine log statements to reduce loop blocking (#12931) --- .../waveshare_epaper/waveshare_epaper.cpp | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 9ab050395d..4db9438206 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -1825,8 +1825,10 @@ void WaveshareEPaper2P9InV2R2::write_lut_(const uint8_t *lut, const uint8_t size void WaveshareEPaper2P9InV2R2::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); - ESP_LOGCONFIG(TAG, " Model: 2.9inV2R2"); - ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_); + ESP_LOGCONFIG(TAG, + " Model: 2.9inV2R2\n" + " Full Update Every: %" PRIu32, + this->full_update_every_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); @@ -2528,8 +2530,10 @@ int GDEY042T81::get_height_internal() { return 300; } uint32_t GDEY042T81::idle_timeout_() { return 5000; } void GDEY042T81::dump_config() { LOG_DISPLAY("", "GoodDisplay E-Paper", this); - ESP_LOGCONFIG(TAG, " Model: 4.2in B/W GDEY042T81"); - ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_); + ESP_LOGCONFIG(TAG, + " Model: 4.2in B/W GDEY042T81\n" + " Full Update Every: %" PRIu32, + this->full_update_every_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); @@ -3159,8 +3163,10 @@ int GDEY0583T81::get_height_internal() { return 480; } uint32_t GDEY0583T81::idle_timeout_() { return 5000; } void GDEY0583T81::dump_config() { LOG_DISPLAY("", "GoodDisplay E-Paper", this); - ESP_LOGCONFIG(TAG, " Model: 5.83in B/W GDEY0583T81"); - ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_); + ESP_LOGCONFIG(TAG, + " Model: 5.83in B/W GDEY0583T81\n" + " Full Update Every: %" PRIu32, + this->full_update_every_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); @@ -4340,8 +4346,10 @@ int WaveshareEPaper7P5InV2P::get_height_internal() { return 480; } uint32_t WaveshareEPaper7P5InV2P::idle_timeout_() { return 10000; } void WaveshareEPaper7P5InV2P::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); - ESP_LOGCONFIG(TAG, " Model: 7.50inv2p"); - ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_); + ESP_LOGCONFIG(TAG, + " Model: 7.50inv2p\n" + " Full Update Every: %" PRIu32, + this->full_update_every_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); From c44d095f8ac6d2fc5250b5270a6f48df6a55e94c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:01:49 -1000 Subject: [PATCH 1007/1145] [usb_host] Combine log statements to reduce loop blocking (#12927) --- .../components/usb_host/usb_host_client.cpp | 95 +++++++++++-------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/esphome/components/usb_host/usb_host_client.cpp b/esphome/components/usb_host/usb_host_client.cpp index 664f49d137..09da6e3b73 100644 --- a/esphome/components/usb_host/usb_host_client.cpp +++ b/esphome/components/usb_host/usb_host_client.cpp @@ -39,37 +39,46 @@ static void print_ep_desc(const usb_ep_desc_t *ep_desc) { break; } - ESP_LOGV(TAG, "\t\t*** Endpoint descriptor ***"); - ESP_LOGV(TAG, "\t\tbLength %d", ep_desc->bLength); - ESP_LOGV(TAG, "\t\tbDescriptorType %d", ep_desc->bDescriptorType); - ESP_LOGV(TAG, "\t\tbEndpointAddress 0x%x\tEP %d %s", ep_desc->bEndpointAddress, USB_EP_DESC_GET_EP_NUM(ep_desc), - USB_EP_DESC_GET_EP_DIR(ep_desc) ? "IN" : "OUT"); - ESP_LOGV(TAG, "\t\tbmAttributes 0x%x\t%s", ep_desc->bmAttributes, ep_type_str); - ESP_LOGV(TAG, "\t\twMaxPacketSize %d", ep_desc->wMaxPacketSize); - ESP_LOGV(TAG, "\t\tbInterval %d", ep_desc->bInterval); + ESP_LOGV(TAG, + "\t\t*** Endpoint descriptor ***\n" + "\t\tbLength %d\n" + "\t\tbDescriptorType %d\n" + "\t\tbEndpointAddress 0x%x\tEP %d %s\n" + "\t\tbmAttributes 0x%x\t%s\n" + "\t\twMaxPacketSize %d\n" + "\t\tbInterval %d", + ep_desc->bLength, ep_desc->bDescriptorType, ep_desc->bEndpointAddress, USB_EP_DESC_GET_EP_NUM(ep_desc), + USB_EP_DESC_GET_EP_DIR(ep_desc) ? "IN" : "OUT", ep_desc->bmAttributes, ep_type_str, ep_desc->wMaxPacketSize, + ep_desc->bInterval); } static void usbh_print_intf_desc(const usb_intf_desc_t *intf_desc) { - ESP_LOGV(TAG, "\t*** Interface descriptor ***"); - ESP_LOGV(TAG, "\tbLength %d", intf_desc->bLength); - ESP_LOGV(TAG, "\tbDescriptorType %d", intf_desc->bDescriptorType); - ESP_LOGV(TAG, "\tbInterfaceNumber %d", intf_desc->bInterfaceNumber); - ESP_LOGV(TAG, "\tbAlternateSetting %d", intf_desc->bAlternateSetting); - ESP_LOGV(TAG, "\tbNumEndpoints %d", intf_desc->bNumEndpoints); - ESP_LOGV(TAG, "\tbInterfaceClass 0x%x", intf_desc->bInterfaceProtocol); - ESP_LOGV(TAG, "\tiInterface %d", intf_desc->iInterface); + ESP_LOGV(TAG, + "\t*** Interface descriptor ***\n" + "\tbLength %d\n" + "\tbDescriptorType %d\n" + "\tbInterfaceNumber %d\n" + "\tbAlternateSetting %d\n" + "\tbNumEndpoints %d\n" + "\tbInterfaceClass 0x%x\n" + "\tiInterface %d", + intf_desc->bLength, intf_desc->bDescriptorType, intf_desc->bInterfaceNumber, intf_desc->bAlternateSetting, + intf_desc->bNumEndpoints, intf_desc->bInterfaceProtocol, intf_desc->iInterface); } static void usbh_print_cfg_desc(const usb_config_desc_t *cfg_desc) { - ESP_LOGV(TAG, "*** Configuration descriptor ***"); - ESP_LOGV(TAG, "bLength %d", cfg_desc->bLength); - ESP_LOGV(TAG, "bDescriptorType %d", cfg_desc->bDescriptorType); - ESP_LOGV(TAG, "wTotalLength %d", cfg_desc->wTotalLength); - ESP_LOGV(TAG, "bNumInterfaces %d", cfg_desc->bNumInterfaces); - ESP_LOGV(TAG, "bConfigurationValue %d", cfg_desc->bConfigurationValue); - ESP_LOGV(TAG, "iConfiguration %d", cfg_desc->iConfiguration); - ESP_LOGV(TAG, "bmAttributes 0x%x", cfg_desc->bmAttributes); - ESP_LOGV(TAG, "bMaxPower %dmA", cfg_desc->bMaxPower * 2); + ESP_LOGV(TAG, + "*** Configuration descriptor ***\n" + "bLength %d\n" + "bDescriptorType %d\n" + "wTotalLength %d\n" + "bNumInterfaces %d\n" + "bConfigurationValue %d\n" + "iConfiguration %d\n" + "bmAttributes 0x%x\n" + "bMaxPower %dmA", + cfg_desc->bLength, cfg_desc->bDescriptorType, cfg_desc->wTotalLength, cfg_desc->bNumInterfaces, + cfg_desc->bConfigurationValue, cfg_desc->iConfiguration, cfg_desc->bmAttributes, cfg_desc->bMaxPower * 2); } static void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) { @@ -77,21 +86,27 @@ static void usb_client_print_device_descriptor(const usb_device_desc_t *devc_des return; } - ESP_LOGV(TAG, "*** Device descriptor ***"); - ESP_LOGV(TAG, "bLength %d", devc_desc->bLength); - ESP_LOGV(TAG, "bDescriptorType %d", devc_desc->bDescriptorType); - ESP_LOGV(TAG, "bcdUSB %d.%d0", ((devc_desc->bcdUSB >> 8) & 0xF), ((devc_desc->bcdUSB >> 4) & 0xF)); - ESP_LOGV(TAG, "bDeviceClass 0x%x", devc_desc->bDeviceClass); - ESP_LOGV(TAG, "bDeviceSubClass 0x%x", devc_desc->bDeviceSubClass); - ESP_LOGV(TAG, "bDeviceProtocol 0x%x", devc_desc->bDeviceProtocol); - ESP_LOGV(TAG, "bMaxPacketSize0 %d", devc_desc->bMaxPacketSize0); - ESP_LOGV(TAG, "idVendor 0x%x", devc_desc->idVendor); - ESP_LOGV(TAG, "idProduct 0x%x", devc_desc->idProduct); - ESP_LOGV(TAG, "bcdDevice %d.%d0", ((devc_desc->bcdDevice >> 8) & 0xF), ((devc_desc->bcdDevice >> 4) & 0xF)); - ESP_LOGV(TAG, "iManufacturer %d", devc_desc->iManufacturer); - ESP_LOGV(TAG, "iProduct %d", devc_desc->iProduct); - ESP_LOGV(TAG, "iSerialNumber %d", devc_desc->iSerialNumber); - ESP_LOGV(TAG, "bNumConfigurations %d", devc_desc->bNumConfigurations); + ESP_LOGV(TAG, + "*** Device descriptor ***\n" + "bLength %d\n" + "bDescriptorType %d\n" + "bcdUSB %d.%d0\n" + "bDeviceClass 0x%x\n" + "bDeviceSubClass 0x%x\n" + "bDeviceProtocol 0x%x\n" + "bMaxPacketSize0 %d\n" + "idVendor 0x%x\n" + "idProduct 0x%x\n" + "bcdDevice %d.%d0\n" + "iManufacturer %d\n" + "iProduct %d\n" + "iSerialNumber %d\n" + "bNumConfigurations %d", + devc_desc->bLength, devc_desc->bDescriptorType, ((devc_desc->bcdUSB >> 8) & 0xF), + ((devc_desc->bcdUSB >> 4) & 0xF), devc_desc->bDeviceClass, devc_desc->bDeviceSubClass, + devc_desc->bDeviceProtocol, devc_desc->bMaxPacketSize0, devc_desc->idVendor, devc_desc->idProduct, + ((devc_desc->bcdDevice >> 8) & 0xF), ((devc_desc->bcdDevice >> 4) & 0xF), devc_desc->iManufacturer, + devc_desc->iProduct, devc_desc->iSerialNumber, devc_desc->bNumConfigurations); } static void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc, From 29d332af920876a5bf1894d0325ffa204308295d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:02:02 -1000 Subject: [PATCH 1008/1145] [wireguard] Combine log statements to reduce loop blocking (#12932) --- esphome/components/wireguard/wireguard.cpp | 24 ++++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/esphome/components/wireguard/wireguard.cpp b/esphome/components/wireguard/wireguard.cpp index 2de6f0d2e3..7810a40ae1 100644 --- a/esphome/components/wireguard/wireguard.cpp +++ b/esphome/components/wireguard/wireguard.cpp @@ -131,15 +131,21 @@ void Wireguard::update() { } void Wireguard::dump_config() { - ESP_LOGCONFIG(TAG, "WireGuard:"); - ESP_LOGCONFIG(TAG, " Address: %s", this->address_.c_str()); - ESP_LOGCONFIG(TAG, " Netmask: %s", this->netmask_.c_str()); - ESP_LOGCONFIG(TAG, " Private Key: " LOG_SECRET("%s"), mask_key(this->private_key_).c_str()); - ESP_LOGCONFIG(TAG, " Peer Endpoint: " LOG_SECRET("%s"), this->peer_endpoint_.c_str()); - ESP_LOGCONFIG(TAG, " Peer Port: " LOG_SECRET("%d"), this->peer_port_); - ESP_LOGCONFIG(TAG, " Peer Public Key: " LOG_SECRET("%s"), this->peer_public_key_.c_str()); - ESP_LOGCONFIG(TAG, " Peer Pre-shared Key: " LOG_SECRET("%s"), - (!this->preshared_key_.empty() ? mask_key(this->preshared_key_).c_str() : "NOT IN USE")); + // clang-format off + ESP_LOGCONFIG( + TAG, + "WireGuard:\n" + " Address: %s\n" + " Netmask: %s\n" + " Private Key: " LOG_SECRET("%s") "\n" + " Peer Endpoint: " LOG_SECRET("%s") "\n" + " Peer Port: " LOG_SECRET("%d") "\n" + " Peer Public Key: " LOG_SECRET("%s") "\n" + " Peer Pre-shared Key: " LOG_SECRET("%s"), + this->address_.c_str(), this->netmask_.c_str(), mask_key(this->private_key_).c_str(), + this->peer_endpoint_.c_str(), this->peer_port_, this->peer_public_key_.c_str(), + (!this->preshared_key_.empty() ? mask_key(this->preshared_key_).c_str() : "NOT IN USE")); + // clang-format on ESP_LOGCONFIG(TAG, " Peer Allowed IPs:"); for (auto &allowed_ip : this->allowed_ips_) { ESP_LOGCONFIG(TAG, " - %s/%s", std::get<0>(allowed_ip).c_str(), std::get<1>(allowed_ip).c_str()); From 9d9f9c3c8498e2bdf481b967e14f6d1a9a153bd0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:02:52 -1000 Subject: [PATCH 1009/1145] [xiaomi_xmwsdj04mmc] Combine log statements to reduce loop blocking (#12936) --- .../components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp index d3fec6cc9e..31e426f0cc 100644 --- a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp +++ b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp @@ -13,8 +13,10 @@ static constexpr size_t XMWSDJ04MMC_BINDKEY_SIZE = 16; void XiaomiXMWSDJ04MMC::dump_config() { char bindkey_hex[format_hex_pretty_size(XMWSDJ04MMC_BINDKEY_SIZE)]; - ESP_LOGCONFIG(TAG, "Xiaomi XMWSDJ04MMC"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty_to(bindkey_hex, this->bindkey_, XMWSDJ04MMC_BINDKEY_SIZE, '.')); + ESP_LOGCONFIG(TAG, + "Xiaomi XMWSDJ04MMC\n" + " Bindkey: %s", + format_hex_pretty_to(bindkey_hex, this->bindkey_, XMWSDJ04MMC_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); From 5713d69efecb26619cd1fae57050095fdceeb858 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:03:06 -1000 Subject: [PATCH 1010/1145] [ufire_ec] Combine log statements to reduce loop blocking (#12925) --- esphome/components/ufire_ec/ufire_ec.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/ufire_ec/ufire_ec.cpp b/esphome/components/ufire_ec/ufire_ec.cpp index 0a57ecc67b..3868dc92b7 100644 --- a/esphome/components/ufire_ec/ufire_ec.cpp +++ b/esphome/components/ufire_ec/ufire_ec.cpp @@ -102,16 +102,16 @@ void UFireECComponent::write_data_(uint8_t reg, float data) { } void UFireECComponent::dump_config() { - ESP_LOGCONFIG(TAG, "uFire-EC"); + ESP_LOGCONFIG(TAG, + "uFire-EC:\n" + " Temperature Compensation: %f\n" + " Temperature Coefficient: %f", + this->temperature_compensation_, this->temperature_coefficient_); LOG_I2C_DEVICE(this) LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "EC Sensor", this->ec_sensor_); LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_); - ESP_LOGCONFIG(TAG, - " Temperature Compensation: %f\n" - " Temperature Coefficient: %f", - this->temperature_compensation_, this->temperature_coefficient_); } } // namespace ufire_ec From 3ea11d4e5983679b706c0abcc2510d242ff337ca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:03:22 -1000 Subject: [PATCH 1011/1145] [xpt2046] Combine log statements to reduce loop blocking (#12937) --- esphome/components/xpt2046/touchscreen/xpt2046.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/esphome/components/xpt2046/touchscreen/xpt2046.cpp b/esphome/components/xpt2046/touchscreen/xpt2046.cpp index fa99e3afa7..84d3daf823 100644 --- a/esphome/components/xpt2046/touchscreen/xpt2046.cpp +++ b/esphome/components/xpt2046/touchscreen/xpt2046.cpp @@ -59,10 +59,8 @@ void XPT2046Component::update_touches() { } void XPT2046Component::dump_config() { - ESP_LOGCONFIG(TAG, "XPT2046:"); - - LOG_PIN(" IRQ Pin: ", this->irq_pin_); ESP_LOGCONFIG(TAG, + "XPT2046:\n" " X min: %d\n" " X max: %d\n" " Y min: %d\n" @@ -73,7 +71,7 @@ void XPT2046Component::dump_config() { " threshold: %d", this->x_raw_min_, this->x_raw_max_, this->y_raw_min_, this->y_raw_max_, YESNO(this->swap_x_y_), YESNO(this->invert_x_), YESNO(this->invert_y_), this->threshold_); - + LOG_PIN(" IRQ Pin: ", this->irq_pin_); LOG_UPDATE_INTERVAL(this); } From 161545584d23811f547ace48ead7dcc82e1cf746 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:03:42 -1000 Subject: [PATCH 1012/1145] [wl_134] Combine log statements to reduce loop blocking (#12933) --- esphome/components/wl_134/wl_134.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/esphome/components/wl_134/wl_134.cpp b/esphome/components/wl_134/wl_134.cpp index 403f8bd1b3..20a145d183 100644 --- a/esphome/components/wl_134/wl_134.cpp +++ b/esphome/components/wl_134/wl_134.cpp @@ -68,12 +68,15 @@ Wl134Component::Rfid134Error Wl134Component::read_packet_() { reading.reserved1 = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_RESERVED1]), RFID134_PACKET_CHECKSUM - RFID134_PACKET_RESERVED1); - ESP_LOGV(TAG, "Tag id: %012lld", reading.id); - ESP_LOGV(TAG, "Country: %03d", reading.country); - ESP_LOGV(TAG, "isData: %s", reading.isData ? "true" : "false"); - ESP_LOGV(TAG, "isAnimal: %s", reading.isAnimal ? "true" : "false"); - ESP_LOGV(TAG, "Reserved0: %d", reading.reserved0); - ESP_LOGV(TAG, "Reserved1: %" PRId32, reading.reserved1); + ESP_LOGV(TAG, + "Tag id: %012lld\n" + "Country: %03d\n" + "isData: %s\n" + "isAnimal: %s\n" + "Reserved0: %d\n" + "Reserved1: %" PRId32, + reading.id, reading.country, reading.isData ? "true" : "false", reading.isAnimal ? "true" : "false", + reading.reserved0, reading.reserved1); char buf[20]; sprintf(buf, "%03d%012lld", reading.country, reading.id); From b291f359ae6656ed585dc288fb645a87e20898e3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:03:56 -1000 Subject: [PATCH 1013/1145] [x9c] Combine log statements to reduce loop blocking (#12934) --- esphome/components/x9c/x9c.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/x9c/x9c.cpp b/esphome/components/x9c/x9c.cpp index 5cd4fba8c0..8f66c46015 100644 --- a/esphome/components/x9c/x9c.cpp +++ b/esphome/components/x9c/x9c.cpp @@ -62,14 +62,14 @@ void X9cOutput::write_state(float state) { } void X9cOutput::dump_config() { - ESP_LOGCONFIG(TAG, "X9C Potentiometer Output:"); - LOG_PIN(" Chip Select Pin: ", this->cs_pin_); - LOG_PIN(" Increment Pin: ", this->inc_pin_); - LOG_PIN(" Up/Down Pin: ", this->ud_pin_); ESP_LOGCONFIG(TAG, + "X9C Potentiometer Output:\n" " Initial Value: %f\n" " Step Delay: %d", this->initial_value_, this->step_delay_); + LOG_PIN(" Chip Select Pin: ", this->cs_pin_); + LOG_PIN(" Increment Pin: ", this->inc_pin_); + LOG_PIN(" Up/Down Pin: ", this->ud_pin_); LOG_FLOAT_OUTPUT(this); } From 9ed107bc331af53665381551d2f7546b3c33f022 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:04:08 -1000 Subject: [PATCH 1014/1145] [xgzp68xx] Combine log statements to reduce loop blocking (#12935) --- esphome/components/xgzp68xx/xgzp68xx.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/xgzp68xx/xgzp68xx.cpp b/esphome/components/xgzp68xx/xgzp68xx.cpp index 2b0824de0a..b5b786c105 100644 --- a/esphome/components/xgzp68xx/xgzp68xx.cpp +++ b/esphome/components/xgzp68xx/xgzp68xx.cpp @@ -72,8 +72,10 @@ void XGZP68XXComponent::update() { temperature_raw = encode_uint16(data[3], data[4]); // Convert the pressure data to hPa - ESP_LOGV(TAG, "Got raw pressure=%" PRIu32 ", raw temperature=%u", pressure_raw, temperature_raw); - ESP_LOGV(TAG, "K value is %u", this->k_value_); + ESP_LOGV(TAG, + "Got raw pressure=%" PRIu32 ", raw temperature=%u\n" + "K value is %u", + pressure_raw, temperature_raw, this->k_value_); // Sign extend the pressure float pressure_in_pa = (float) (((int32_t) pressure_raw << 8) >> 8); From 7fde110ac5125de0ff6da54b7010b662b2e6b7c0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:04:22 -1000 Subject: [PATCH 1015/1145] [voice_assistant] Combine log statements to reduce loop blocking (#12930) --- .../voice_assistant/voice_assistant.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 8101d210b3..de683113bb 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -429,10 +429,12 @@ void VoiceAssistant::client_subscription(api::APIConnection *client, bool subscr } if (this->api_client_ != nullptr) { - ESP_LOGE(TAG, "Multiple API Clients attempting to connect to Voice Assistant"); - ESP_LOGE(TAG, "Current client: %s (%s)", this->api_client_->get_name().c_str(), - this->api_client_->get_peername().c_str()); - ESP_LOGE(TAG, "New client: %s (%s)", client->get_name().c_str(), client->get_peername().c_str()); + ESP_LOGE(TAG, + "Multiple API Clients attempting to connect to Voice Assistant\n" + "Current client: %s (%s)\n" + "New client: %s (%s)", + this->api_client_->get_name().c_str(), this->api_client_->get_peername().c_str(), + client->get_name().c_str(), client->get_peername().c_str()); return; } @@ -864,9 +866,11 @@ void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse .is_active = msg.is_active, }; this->timers_[timer.id] = timer; - ESP_LOGD(TAG, "Timer Event"); - ESP_LOGD(TAG, " Type: %" PRId32, msg.event_type); - ESP_LOGD(TAG, " %s", timer.to_string().c_str()); + ESP_LOGD(TAG, + "Timer Event\n" + " Type: %" PRId32 "\n" + " %s", + msg.event_type, timer.to_string().c_str()); switch (msg.event_type) { case api::enums::VOICE_ASSISTANT_TIMER_STARTED: From 7309a651671e6b76ded484ea238adb41908a6146 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:11:08 -1000 Subject: [PATCH 1016/1145] [tlc5971] Combine log statements to reduce loop blocking (#12922) --- esphome/components/tlc5971/tlc5971.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/tlc5971/tlc5971.cpp b/esphome/components/tlc5971/tlc5971.cpp index 719ab7c2b3..be17780f8c 100644 --- a/esphome/components/tlc5971/tlc5971.cpp +++ b/esphome/components/tlc5971/tlc5971.cpp @@ -15,10 +15,12 @@ void TLC5971::setup() { this->pwm_amounts_.resize(this->num_chips_ * N_CHANNELS_PER_CHIP, 0); } void TLC5971::dump_config() { - ESP_LOGCONFIG(TAG, "TLC5971:"); + ESP_LOGCONFIG(TAG, + "TLC5971:\n" + " Number of chips: %u", + this->num_chips_); LOG_PIN(" Data Pin: ", this->data_pin_); LOG_PIN(" Clock Pin: ", this->clock_pin_); - ESP_LOGCONFIG(TAG, " Number of chips: %u", this->num_chips_); } void TLC5971::loop() { From a37d4b17eb66e8e96d252fc330e1d4b267a34dec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:11:48 -1000 Subject: [PATCH 1017/1145] [wifi] Combine log statements to reduce loop blocking (#12939) --- esphome/components/wifi/wifi_component.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index ca7b1ba9cc..ba25bc9f76 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -932,19 +932,21 @@ void WiFiComponent::print_connect_params_() { char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE]; char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE]; char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE]; + // clang-format off ESP_LOGCONFIG(TAG, " SSID: " LOG_SECRET("'%s'") "\n" - " BSSID: " LOG_SECRET("%s") "\n" - " Hostname: '%s'\n" - " Signal strength: %d dB %s\n" - " Channel: %" PRId32 "\n" - " Subnet: %s\n" - " Gateway: %s\n" - " DNS1: %s\n" - " DNS2: %s", + " BSSID: " LOG_SECRET("%s") "\n" + " Hostname: '%s'\n" + " Signal strength: %d dB %s\n" + " Channel: %" PRId32 "\n" + " Subnet: %s\n" + " Gateway: %s\n" + " DNS1: %s\n" + " DNS2: %s", wifi_ssid_to(ssid_buf), bssid_s, App.get_name().c_str(), rssi, LOG_STR_ARG(get_signal_bars(rssi)), get_wifi_channel(), wifi_subnet_mask_().str_to(subnet_buf), wifi_gateway_ip_().str_to(gateway_buf), wifi_dns_ip_(0).str_to(dns1_buf), wifi_dns_ip_(1).str_to(dns2_buf)); + // clang-format on #ifdef ESPHOME_LOG_HAS_VERBOSE if (const WiFiAP *config = this->get_selected_sta_(); config && config->has_bssid()) { ESP_LOGV(TAG, " Priority: %d", this->get_sta_priority(config->get_bssid())); From 850f18922595650c223cc856887e0639fe94d756 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 13:44:49 -1000 Subject: [PATCH 1018/1145] [api] Fix message batch size mismatch and improve naming consistency (#12940) --- esphome/components/api/api_connection.cpp | 32 +++++++------- esphome/components/api/api_connection.h | 11 ++--- esphome/components/api/api_frame_helper.h | 23 +++++----- .../components/api/api_frame_helper_noise.cpp | 44 +++++++++---------- .../components/api/api_frame_helper_noise.h | 2 +- .../api/api_frame_helper_plaintext.cpp | 37 ++++++++-------- .../api/api_frame_helper_plaintext.h | 2 +- esphome/core/helpers.h | 4 ++ 8 files changed, 76 insertions(+), 79 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 3ded5e4408..b173ebc8cb 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1874,9 +1874,9 @@ bool APIConnection::schedule_batch_() { } void APIConnection::process_batch_() { - // Ensure PacketInfo remains trivially destructible for our placement new approach - static_assert(std::is_trivially_destructible::value, - "PacketInfo must remain trivially destructible with this placement-new approach"); + // Ensure MessageInfo remains trivially destructible for our placement new approach + static_assert(std::is_trivially_destructible::value, + "MessageInfo must remain trivially destructible with this placement-new approach"); if (this->deferred_batch_.empty()) { this->flags_.batch_scheduled = false; @@ -1916,12 +1916,12 @@ void APIConnection::process_batch_() { return; } - size_t packets_to_process = std::min(num_items, MAX_PACKETS_PER_BATCH); + size_t messages_to_process = std::min(num_items, MAX_MESSAGES_PER_BATCH); - // Stack-allocated array for packet info - alignas(PacketInfo) char packet_info_storage[MAX_PACKETS_PER_BATCH * sizeof(PacketInfo)]; - PacketInfo *packet_info = reinterpret_cast(packet_info_storage); - size_t packet_count = 0; + // Stack-allocated array for message info + alignas(MessageInfo) char message_info_storage[MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo)]; + MessageInfo *message_info = reinterpret_cast(message_info_storage); + size_t message_count = 0; // Cache these values to avoid repeated virtual calls const uint8_t header_padding = this->helper_->frame_header_padding(); @@ -1952,7 +1952,7 @@ void APIConnection::process_batch_() { uint32_t current_offset = 0; // Process items and encode directly to buffer (up to our limit) - for (size_t i = 0; i < packets_to_process; i++) { + for (size_t i = 0; i < messages_to_process; i++) { const auto &item = this->deferred_batch_[i]; // Try to encode message // The creator will calculate overhead to determine if the message fits @@ -1966,11 +1966,11 @@ void APIConnection::process_batch_() { // Message was encoded successfully // payload_size is header_padding + actual payload size + footer_size uint16_t proto_payload_size = payload_size - header_padding - footer_size; - // Use placement new to construct PacketInfo in pre-allocated stack array - // This avoids default-constructing all MAX_PACKETS_PER_BATCH elements - // Explicit destruction is not needed because PacketInfo is trivially destructible, + // Use placement new to construct MessageInfo in pre-allocated stack array + // This avoids default-constructing all MAX_MESSAGES_PER_BATCH elements + // Explicit destruction is not needed because MessageInfo is trivially destructible, // as ensured by the static_assert in its definition. - new (&packet_info[packet_count++]) PacketInfo(item.message_type, current_offset, proto_payload_size); + new (&message_info[message_count++]) MessageInfo(item.message_type, current_offset, proto_payload_size); // Update tracking variables items_processed++; @@ -1994,9 +1994,9 @@ void APIConnection::process_batch_() { shared_buf.resize(shared_buf.size() + footer_size); } - // Send all collected packets - APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf}, - std::span(packet_info, packet_count)); + // Send all collected messages + APIError err = this->helper_->write_protobuf_messages(ProtoWriteBuffer{&shared_buf}, + std::span(message_info, message_count)); if (err != APIError::OK && err != APIError::WOULD_BLOCK) { this->fatal_error_with_log_(LOG_STR("Batch write failed"), err); } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index ffe3614f20..cffd52bfdb 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -28,14 +28,9 @@ static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; // TODO: Remove MAX_INITIAL_PER_BATCH_LEGACY before 2026.7.0 - all clients should support API 1.14 by then static constexpr size_t MAX_INITIAL_PER_BATCH_LEGACY = 24; // For clients < API 1.14 (includes object_id) static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= API 1.14 (no object_id) -// Maximum number of packets to process in a single batch (platform-dependent) -// This limit exists to prevent stack overflow from the PacketInfo array in process_batch_ -// Each PacketInfo is 8 bytes, so 64 * 8 = 512 bytes, 32 * 8 = 256 bytes -#if defined(USE_ESP32) || defined(USE_HOST) -static constexpr size_t MAX_PACKETS_PER_BATCH = 64; // ESP32 has 8KB+ stack, HOST has plenty -#else -static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks -#endif +// Verify MAX_MESSAGES_PER_BATCH (defined in api_frame_helper.h) can hold the initial batch +static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH, + "MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH"); class APIConnection final : public APIServerConnection { public: diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index b582bcea9a..383e763e6d 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -29,6 +29,10 @@ static constexpr uint16_t MAX_MESSAGE_SIZE = 8192; // 8 KiB for ESP8266 static constexpr uint16_t MAX_MESSAGE_SIZE = 32768; // 32 KiB for ESP32 and other platforms #endif +// Maximum number of messages to batch in a single write operation +// Must be >= MAX_INITIAL_PER_BATCH in api_connection.h (enforced by static_assert there) +static constexpr size_t MAX_MESSAGES_PER_BATCH = 34; + // Forward declaration struct ClientInfo; @@ -40,13 +44,13 @@ struct ReadPacketBuffer { uint16_t type; }; -// Packed packet info structure to minimize memory usage -struct PacketInfo { +// Packed message info structure to minimize memory usage +struct MessageInfo { uint16_t offset; // Offset in buffer where message starts uint16_t payload_size; // Size of the message payload uint8_t message_type; // Message type (0-255) - PacketInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {} + MessageInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {} }; enum class APIError : uint16_t { @@ -108,10 +112,10 @@ class APIFrameHelper { return APIError::OK; } virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0; - // Write multiple protobuf packets in a single operation - // packets contains (message_type, offset, length) for each message in the buffer + // Write multiple protobuf messages in a single operation + // messages contains (message_type, offset, length) for each message in the buffer // The buffer contains all messages with appropriate padding before each - virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span packets) = 0; + virtual APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span messages) = 0; // Get the frame header padding required by this protocol uint8_t frame_header_padding() const { return frame_header_padding_; } // Get the frame footer size required by this protocol @@ -127,12 +131,6 @@ class APIFrameHelper { // Use swap trick since shrink_to_fit() is non-binding and may be ignored std::vector().swap(this->rx_buf_); } - // reusable_iovs_: Safe to release unconditionally. - // Only used within write_protobuf_packets() calls - cleared at start, - // populated with pointers, used for writev(), then function returns. - // The iovecs contain stale pointers after the call (data was either sent - // or copied to tx_buf_), and are cleared on next write_protobuf_packets(). - std::vector().swap(this->reusable_iovs_); } protected: @@ -186,7 +184,6 @@ class APIFrameHelper { // Containers (size varies, but typically 12+ bytes on 32-bit) std::array, API_MAX_SEND_QUEUE> tx_buf_; - std::vector reusable_iovs_; std::vector rx_buf_; // Pointer to client info (4 bytes on 32-bit) diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index 37b497e2a1..be8d93fbf9 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -429,12 +429,12 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) { // Resize to include MAC space (required for Noise encryption) buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_); - PacketInfo packet{type, 0, - static_cast(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)}; - return write_protobuf_packets(buffer, std::span(&packet, 1)); + MessageInfo msg{type, 0, + static_cast(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)}; + return write_protobuf_messages(buffer, std::span(&msg, 1)); } -APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span packets) { +APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, std::span messages) { APIError aerr = state_action_(); if (aerr != APIError::OK) { return aerr; @@ -444,20 +444,20 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st return APIError::WOULD_BLOCK; } - if (packets.empty()) { + if (messages.empty()) { return APIError::OK; } uint8_t *buffer_data = buffer.get_buffer()->data(); - this->reusable_iovs_.clear(); - this->reusable_iovs_.reserve(packets.size()); + // Stack-allocated iovec array - no heap allocation + StaticVector iovs; uint16_t total_write_len = 0; - // We need to encrypt each packet in place - for (const auto &packet : packets) { + // We need to encrypt each message in place + for (const auto &msg : messages) { // The buffer already has padding at offset - uint8_t *buf_start = buffer_data + packet.offset; + uint8_t *buf_start = buffer_data + msg.offset; // Write noise header buf_start[0] = 0x01; // indicator @@ -465,10 +465,10 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st // Write message header (to be encrypted) const uint8_t msg_offset = 3; - buf_start[msg_offset] = static_cast(packet.message_type >> 8); // type high byte - buf_start[msg_offset + 1] = static_cast(packet.message_type); // type low byte - buf_start[msg_offset + 2] = static_cast(packet.payload_size >> 8); // data_len high byte - buf_start[msg_offset + 3] = static_cast(packet.payload_size); // data_len low byte + buf_start[msg_offset] = static_cast(msg.message_type >> 8); // type high byte + buf_start[msg_offset + 1] = static_cast(msg.message_type); // type low byte + buf_start[msg_offset + 2] = static_cast(msg.payload_size >> 8); // data_len high byte + buf_start[msg_offset + 3] = static_cast(msg.payload_size); // data_len low byte // payload data is already in the buffer starting at offset + 7 // Make sure we have space for MAC @@ -477,8 +477,8 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st // Encrypt the message in place NoiseBuffer mbuf; noise_buffer_init(mbuf); - noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + packet.payload_size, - 4 + packet.payload_size + frame_footer_size_); + noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + msg.payload_size, + 4 + msg.payload_size + frame_footer_size_); int err = noise_cipherstate_encrypt(send_cipher_, &mbuf); APIError aerr = @@ -490,14 +490,14 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st buf_start[1] = static_cast(mbuf.size >> 8); buf_start[2] = static_cast(mbuf.size); - // Add iovec for this encrypted packet - size_t packet_len = static_cast(3 + mbuf.size); // indicator + size + encrypted data - this->reusable_iovs_.push_back({buf_start, packet_len}); - total_write_len += packet_len; + // Add iovec for this encrypted message + size_t msg_len = static_cast(3 + mbuf.size); // indicator + size + encrypted data + iovs.push_back({buf_start, msg_len}); + total_write_len += msg_len; } - // Send all encrypted packets in one writev call - return this->write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len); + // Send all encrypted messages in one writev call + return this->write_raw_(iovs.data(), iovs.size(), total_write_len); } APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) { diff --git a/esphome/components/api/api_frame_helper_noise.h b/esphome/components/api/api_frame_helper_noise.h index 7eb01058db..1268086194 100644 --- a/esphome/components/api/api_frame_helper_noise.h +++ b/esphome/components/api/api_frame_helper_noise.h @@ -23,7 +23,7 @@ class APINoiseFrameHelper final : public APIFrameHelper { APIError loop() override; APIError read_packet(ReadPacketBuffer *buffer) override; APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override; - APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span packets) override; + APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span messages) override; protected: APIError state_action_(); diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index 8b7d002d7c..a974a2458e 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -230,29 +230,30 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { return APIError::OK; } APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) { - PacketInfo packet{type, 0, static_cast(buffer.get_buffer()->size() - frame_header_padding_)}; - return write_protobuf_packets(buffer, std::span(&packet, 1)); + MessageInfo msg{type, 0, static_cast(buffer.get_buffer()->size() - frame_header_padding_)}; + return write_protobuf_messages(buffer, std::span(&msg, 1)); } -APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span packets) { +APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, + std::span messages) { if (state_ != State::DATA) { return APIError::BAD_STATE; } - if (packets.empty()) { + if (messages.empty()) { return APIError::OK; } uint8_t *buffer_data = buffer.get_buffer()->data(); - this->reusable_iovs_.clear(); - this->reusable_iovs_.reserve(packets.size()); + // Stack-allocated iovec array - no heap allocation + StaticVector iovs; uint16_t total_write_len = 0; - for (const auto &packet : packets) { + for (const auto &msg : messages) { // Calculate varint sizes for header layout - uint8_t size_varint_len = api::ProtoSize::varint(static_cast(packet.payload_size)); - uint8_t type_varint_len = api::ProtoSize::varint(static_cast(packet.message_type)); + uint8_t size_varint_len = api::ProtoSize::varint(static_cast(msg.payload_size)); + uint8_t type_varint_len = api::ProtoSize::varint(static_cast(msg.message_type)); uint8_t total_header_len = 1 + size_varint_len + type_varint_len; // Calculate where to start writing the header @@ -280,25 +281,25 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer // // The message starts at offset + frame_header_padding_ // So we write the header starting at offset + frame_header_padding_ - total_header_len - uint8_t *buf_start = buffer_data + packet.offset; + uint8_t *buf_start = buffer_data + msg.offset; uint32_t header_offset = frame_header_padding_ - total_header_len; // Write the plaintext header buf_start[header_offset] = 0x00; // indicator // Encode varints directly into buffer - ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len); - ProtoVarInt(packet.message_type) + ProtoVarInt(msg.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len); + ProtoVarInt(msg.message_type) .encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len); - // Add iovec for this packet (header + payload) - size_t packet_len = static_cast(total_header_len + packet.payload_size); - this->reusable_iovs_.push_back({buf_start + header_offset, packet_len}); - total_write_len += packet_len; + // Add iovec for this message (header + payload) + size_t msg_len = static_cast(total_header_len + msg.payload_size); + iovs.push_back({buf_start + header_offset, msg_len}); + total_write_len += msg_len; } - // Send all packets in one writev call - return write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len); + // Send all messages in one writev call + return write_raw_(iovs.data(), iovs.size(), total_write_len); } } // namespace esphome::api diff --git a/esphome/components/api/api_frame_helper_plaintext.h b/esphome/components/api/api_frame_helper_plaintext.h index bba981d26b..7af9fc64b9 100644 --- a/esphome/components/api/api_frame_helper_plaintext.h +++ b/esphome/components/api/api_frame_helper_plaintext.h @@ -21,7 +21,7 @@ class APIPlaintextFrameHelper final : public APIFrameHelper { APIError loop() override; APIError read_packet(ReadPacketBuffer *buffer) override; APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override; - APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span packets) override; + APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span messages) override; protected: APIError try_read_frame_(); diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index f7a14ed2ec..6c338797a9 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -162,6 +162,10 @@ template class StaticVector { size_t size() const { return count_; } bool empty() const { return count_ == 0; } + // Direct access to underlying data + T *data() { return data_.data(); } + const T *data() const { return data_.data(); } + T &operator[](size_t i) { return data_[i]; } const T &operator[](size_t i) const { return data_[i]; } From f41f0506c1df3f452268b6a5b3e957ec48321a60 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 14:05:17 -1000 Subject: [PATCH 1019/1145] [pcf8574] Combine log statements to reduce loop blocking (#12941) --- esphome/components/pcf8574/pcf8574.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/pcf8574/pcf8574.cpp b/esphome/components/pcf8574/pcf8574.cpp index 15418bfee5..8bdd312ab9 100644 --- a/esphome/components/pcf8574/pcf8574.cpp +++ b/esphome/components/pcf8574/pcf8574.cpp @@ -21,9 +21,11 @@ void PCF8574Component::loop() { this->reset_pin_cache_(); } void PCF8574Component::dump_config() { - ESP_LOGCONFIG(TAG, "PCF8574:"); + ESP_LOGCONFIG(TAG, + "PCF8574:\n" + " Is PCF8575: %s", + YESNO(this->pcf8575_)); LOG_I2C_DEVICE(this) - ESP_LOGCONFIG(TAG, " Is PCF8575: %s", YESNO(this->pcf8575_)); if (this->is_failed()) { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); } From 6c809583d386e65a0828aee9bdb06aff7f09d20f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 14:45:22 -1000 Subject: [PATCH 1020/1145] [qspi_dbi] Combine log statements to reduce loop blocking (#12948) Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/qspi_dbi/qspi_dbi.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/qspi_dbi/qspi_dbi.cpp b/esphome/components/qspi_dbi/qspi_dbi.cpp index 00a4a375eb..d42f95dca3 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.cpp +++ b/esphome/components/qspi_dbi/qspi_dbi.cpp @@ -216,12 +216,14 @@ void QspiDbi::write_sequence_(const std::vector &vec) { void QspiDbi::dump_config() { ESP_LOGCONFIG("", "QSPI_DBI Display"); ESP_LOGCONFIG("", "Model: %s", this->model_); - ESP_LOGCONFIG(TAG, " Height: %u", this->height_); - ESP_LOGCONFIG(TAG, " Width: %u", this->width_); - ESP_LOGCONFIG(TAG, " Draw rounding: %u", this->draw_rounding_); + ESP_LOGCONFIG(TAG, + " Height: %u\n" + " Width: %u\n" + " Draw rounding: %u\n" + " SPI Data rate: %uMHz", + this->height_, this->width_, this->draw_rounding_, (unsigned) (this->data_rate_ / 1000000)); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); } } // namespace qspi_dbi From 50f27cdd77eaeb66274d28d87e4971aeb91c5260 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 14:45:38 -1000 Subject: [PATCH 1021/1145] [pn7160] Combine log statements to reduce loop blocking (#12945) --- esphome/components/pn7160/pn7160.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/esphome/components/pn7160/pn7160.cpp b/esphome/components/pn7160/pn7160.cpp index a8edfadd8e..8c8028b04a 100644 --- a/esphome/components/pn7160/pn7160.cpp +++ b/esphome/components/pn7160/pn7160.cpp @@ -262,9 +262,12 @@ uint8_t PN7160::reset_core_(const bool reset_config, const bool power) { return nfc::STATUS_FAILED; } - ESP_LOGD(TAG, "Configuration %s", rx.get_message()[4] ? "reset" : "retained"); - ESP_LOGD(TAG, "NCI version: %s", rx.get_message()[5] == 0x20 ? "2.0" : "1.0"); - ESP_LOGD(TAG, "Manufacturer ID: 0x%02X", rx.get_message()[6]); + ESP_LOGD(TAG, + "Configuration %s\n" + "NCI version: %s\n" + "Manufacturer ID: 0x%02X", + rx.get_message()[4] ? "reset" : "retained", rx.get_message()[5] == 0x20 ? "2.0" : "1.0", + rx.get_message()[6]); rx.get_message().erase(rx.get_message().begin(), rx.get_message().begin() + 8); ESP_LOGD(TAG, "Manufacturer info: %s", nfc::format_bytes(rx.get_message()).c_str()); @@ -291,11 +294,13 @@ uint8_t PN7160::init_core_() { uint8_t flash_minor_version = rx.get_message()[20 + rx.get_message()[8]]; std::vector features(rx.get_message().begin() + 4, rx.get_message().begin() + 8); - ESP_LOGD(TAG, "Hardware version: %u", hw_version); - ESP_LOGD(TAG, "ROM code version: %u", rom_code_version); - ESP_LOGD(TAG, "FLASH major version: %u", flash_major_version); - ESP_LOGD(TAG, "FLASH minor version: %u", flash_minor_version); - ESP_LOGD(TAG, "Features: %s", nfc::format_bytes(features).c_str()); + ESP_LOGD(TAG, + "Hardware version: %u\n" + "ROM code version: %u\n" + "FLASH major version: %u\n" + "FLASH minor version: %u\n" + "Features: %s", + hw_version, rom_code_version, flash_major_version, flash_minor_version, nfc::format_bytes(features).c_str()); return rx.get_simple_status_response(); } @@ -871,8 +876,8 @@ void PN7160::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi case EP_WRITE: if (this->next_task_message_to_write_ != nullptr) { - ESP_LOGD(TAG, " Tag writing"); - ESP_LOGD(TAG, " Tag formatting"); + ESP_LOGD(TAG, " Tag writing\n" + " Tag formatting"); if (this->format_endpoint_(working_endpoint.tag->get_uid()) != nfc::STATUS_OK) { ESP_LOGE(TAG, " Tag could not be formatted for writing"); } else { From 6d8142c539b8b12baf43e8281d680112fa791ef9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 14:45:52 -1000 Subject: [PATCH 1022/1145] [rpi_dpi_rgb] Combine log statements to reduce loop blocking (#12953) --- esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp index 042b8877e6..a81bb17dfc 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp @@ -126,8 +126,10 @@ void RpiDpiRgb::draw_pixel_at(int x, int y, Color color) { void RpiDpiRgb::dump_config() { ESP_LOGCONFIG("", "RPI_DPI_RGB LCD"); - ESP_LOGCONFIG(TAG, " Height: %u", this->height_); - ESP_LOGCONFIG(TAG, " Width: %u", this->width_); + ESP_LOGCONFIG(TAG, + " Height: %u\n" + " Width: %u", + this->height_, this->width_); LOG_PIN(" DE Pin: ", this->de_pin_); LOG_PIN(" Enable Pin: ", this->enable_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); From 2d8abbb2ac81093474021f0a68d2610242231723 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 14:46:09 -1000 Subject: [PATCH 1023/1145] [pn7150] Combine log statements to reduce loop blocking (#12944) --- esphome/components/pn7150/pn7150.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/esphome/components/pn7150/pn7150.cpp b/esphome/components/pn7150/pn7150.cpp index f827bd151a..f6ddcb0767 100644 --- a/esphome/components/pn7150/pn7150.cpp +++ b/esphome/components/pn7150/pn7150.cpp @@ -240,8 +240,11 @@ uint8_t PN7150::reset_core_(const bool reset_config, const bool power) { return nfc::STATUS_FAILED; } - ESP_LOGD(TAG, "Configuration %s", rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 2] ? "reset" : "retained"); - ESP_LOGD(TAG, "NCI version: %s", rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 1] == 0x20 ? "2.0" : "1.0"); + ESP_LOGD(TAG, + "Configuration %s\n" + "NCI version: %s", + rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 2] ? "reset" : "retained", + rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 1] == 0x20 ? "2.0" : "1.0"); return nfc::STATUS_OK; } @@ -266,11 +269,13 @@ uint8_t PN7150::init_core_() { uint8_t flash_major_version = rx.get_message()[18 + rx.get_message()[8]]; uint8_t flash_minor_version = rx.get_message()[19 + rx.get_message()[8]]; - ESP_LOGD(TAG, "Manufacturer ID: 0x%02X", manf_id); - ESP_LOGD(TAG, "Hardware version: 0x%02X", hw_version); - ESP_LOGD(TAG, "ROM code version: 0x%02X", rom_code_version); - ESP_LOGD(TAG, "FLASH major version: 0x%02X", flash_major_version); - ESP_LOGD(TAG, "FLASH minor version: 0x%02X", flash_minor_version); + ESP_LOGD(TAG, + "Manufacturer ID: 0x%02X\n" + "Hardware version: 0x%02X\n" + "ROM code version: 0x%02X\n" + "FLASH major version: 0x%02X\n" + "FLASH minor version: 0x%02X", + manf_id, hw_version, rom_code_version, flash_major_version, flash_minor_version); return rx.get_simple_status_response(); } @@ -847,8 +852,8 @@ void PN7150::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi case EP_WRITE: if (this->next_task_message_to_write_ != nullptr) { - ESP_LOGD(TAG, " Tag writing"); - ESP_LOGD(TAG, " Tag formatting"); + ESP_LOGD(TAG, " Tag writing\n" + " Tag formatting"); if (this->format_endpoint_(working_endpoint.tag->get_uid()) != nfc::STATUS_OK) { ESP_LOGE(TAG, " Tag could not be formatted for writing"); } else { From 0b9fcf9ed375f63b2f2bbcfdfdf107961b463eca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 14:46:21 -1000 Subject: [PATCH 1024/1145] [pn532] Combine log statements to reduce loop blocking (#12943) --- esphome/components/pn532/pn532.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index ef4022db4b..d5e892a576 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -31,8 +31,10 @@ void PN532::setup() { this->mark_failed(); return; } - ESP_LOGD(TAG, "Found chip PN5%02X", version_data[0]); - ESP_LOGD(TAG, "Firmware ver. %d.%d", version_data[1], version_data[2]); + ESP_LOGD(TAG, + "Found chip PN5%02X\n" + "Firmware ver. %d.%d", + version_data[0], version_data[1], version_data[2]); if (!this->write_command_({ PN532_COMMAND_SAMCONFIGURATION, From a635c8283096859221201d1ba11750c7cb185e97 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:04:50 -1000 Subject: [PATCH 1025/1145] [pid] Combine log statements to reduce loop blocking (#12942) --- esphome/components/pid/pid_autotuner.cpp | 67 ++++++++++++++---------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/esphome/components/pid/pid_autotuner.cpp b/esphome/components/pid/pid_autotuner.cpp index 28d16e17ab..d1d9c200cf 100644 --- a/esphome/components/pid/pid_autotuner.cpp +++ b/esphome/components/pid/pid_autotuner.cpp @@ -138,20 +138,21 @@ PIDAutotuner::PIDAutotuneResult PIDAutotuner::update(float setpoint, float proce } void PIDAutotuner::dump_config() { if (this->state_ == AUTOTUNE_SUCCEEDED) { - ESP_LOGI(TAG, "%s: PID Autotune:", this->id_.c_str()); - ESP_LOGI(TAG, " State: Succeeded!"); + ESP_LOGI(TAG, + "%s: PID Autotune:\n" + " State: Succeeded!", + this->id_.c_str()); bool has_issue = false; if (!this->amplitude_detector_.is_amplitude_convergent()) { - ESP_LOGW(TAG, " Could not reliably determine oscillation amplitude, PID parameters may be inaccurate!"); - ESP_LOGW(TAG, " Please make sure you eliminate all outside influences on the measured temperature."); + ESP_LOGW(TAG, " Could not reliably determine oscillation amplitude, PID parameters may be inaccurate!\n" + " Please make sure you eliminate all outside influences on the measured temperature."); has_issue = true; } if (!this->frequency_detector_.is_increase_decrease_symmetrical()) { - ESP_LOGW(TAG, " Oscillation Frequency is not symmetrical. PID parameters may be inaccurate!"); - ESP_LOGW( - TAG, - " This is usually because the heat and cool processes do not change the temperature at the same rate."); ESP_LOGW(TAG, + " Oscillation Frequency is not symmetrical. PID parameters may be inaccurate!\n" + " This is usually because the heat and cool processes do not change the temperature at the same " + "rate.\n" " Please try reducing the positive_output value (or increase negative_output in case of a cooler)"); has_issue = true; } @@ -160,18 +161,23 @@ void PIDAutotuner::dump_config() { } auto fac = get_ziegler_nichols_pid_(); - ESP_LOGI(TAG, " Calculated PID parameters (\"Ziegler-Nichols PID\" rule):"); - ESP_LOGI(TAG, " "); - ESP_LOGI(TAG, " control_parameters:"); - ESP_LOGI(TAG, " kp: %.5f", fac.kp); - ESP_LOGI(TAG, " ki: %.5f", fac.ki); - ESP_LOGI(TAG, " kd: %.5f", fac.kd); - ESP_LOGI(TAG, " "); - ESP_LOGI(TAG, " Please copy these values into your YAML configuration! They will reset on the next reboot."); + ESP_LOGI(TAG, + " Calculated PID parameters (\"Ziegler-Nichols PID\" rule):\n" + "\n" + " control_parameters:\n" + " kp: %.5f\n" + " ki: %.5f\n" + " kd: %.5f\n" + "\n" + " Please copy these values into your YAML configuration! They will reset on the next reboot.", + fac.kp, fac.ki, fac.kd); - ESP_LOGV(TAG, " Oscillation Period: %f", this->frequency_detector_.get_mean_oscillation_period()); - ESP_LOGV(TAG, " Oscillation Amplitude: %f", this->amplitude_detector_.get_mean_oscillation_amplitude()); - ESP_LOGV(TAG, " Ku: %f, Pu: %f", this->ku_, this->pu_); + ESP_LOGV(TAG, + " Oscillation Period: %f\n" + " Oscillation Amplitude: %f\n" + " Ku: %f, Pu: %f", + this->frequency_detector_.get_mean_oscillation_period(), + this->amplitude_detector_.get_mean_oscillation_amplitude(), this->ku_, this->pu_); ESP_LOGD(TAG, " Alternative Rules:"); // http://www.mstarlabs.com/control/znrule.html @@ -183,13 +189,16 @@ void PIDAutotuner::dump_config() { } if (this->state_ == AUTOTUNE_RUNNING) { - ESP_LOGD(TAG, "%s: PID Autotune:", this->id_.c_str()); - ESP_LOGD(TAG, " Autotune is still running!"); - ESP_LOGD(TAG, " Status: Trying to reach %.2f °C", setpoint_ - relay_function_.current_target_error()); - ESP_LOGD(TAG, " Stats so far:"); - ESP_LOGD(TAG, " Phases: %" PRIu32, relay_function_.phase_count); - ESP_LOGD(TAG, " Detected %zu zero-crossings", frequency_detector_.zerocrossing_intervals.size()); - ESP_LOGD(TAG, " Current Phase Min: %.2f, Max: %.2f", amplitude_detector_.phase_min, + ESP_LOGD(TAG, + "%s: PID Autotune:\n" + " Autotune is still running!\n" + " Status: Trying to reach %.2f °C\n" + " Stats so far:\n" + " Phases: %" PRIu32 "\n" + " Detected %zu zero-crossings\n" + " Current Phase Min: %.2f, Max: %.2f", + this->id_.c_str(), setpoint_ - relay_function_.current_target_error(), relay_function_.phase_count, + frequency_detector_.zerocrossing_intervals.size(), amplitude_detector_.phase_min, amplitude_detector_.phase_max); } } @@ -205,8 +214,10 @@ PIDAutotuner::PIDResult PIDAutotuner::calculate_pid_(float kp_factor, float ki_f } void PIDAutotuner::print_rule_(const char *name, float kp_factor, float ki_factor, float kd_factor) { auto fac = calculate_pid_(kp_factor, ki_factor, kd_factor); - ESP_LOGD(TAG, " Rule '%s':", name); - ESP_LOGD(TAG, " kp: %.5f, ki: %.5f, kd: %.5f", fac.kp, fac.ki, fac.kd); + ESP_LOGD(TAG, + " Rule '%s':\n" + " kp: %.5f, ki: %.5f, kd: %.5f", + name, fac.kp, fac.ki, fac.kd); } // ================== RelayFunction ================== From 3c8fd5c5c0715740e4ef3e346f541c7b9b3d3694 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:05:05 -1000 Subject: [PATCH 1026/1145] [pulse_counter] Combine log statements to reduce loop blocking (#12946) --- esphome/components/pulse_counter/pulse_counter_sensor.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index 6300d6fe96..c0d74cef4a 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -68,8 +68,10 @@ bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { next_pcnt_channel = pcnt_channel_t(int(next_pcnt_channel) + 1); } - ESP_LOGCONFIG(TAG, " PCNT Unit Number: %u", this->pcnt_unit); - ESP_LOGCONFIG(TAG, " PCNT Channel Number: %u", this->pcnt_channel); + ESP_LOGCONFIG(TAG, + " PCNT Unit Number: %u\n" + " PCNT Channel Number: %u", + this->pcnt_unit, this->pcnt_channel); pcnt_count_mode_t rising = PCNT_COUNT_DIS, falling = PCNT_COUNT_DIS; switch (this->rising_edge_mode) { From e6a630ae647cd3dc60f30023ca5452d569097d0a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:06:34 -1000 Subject: [PATCH 1027/1145] [qmp6988] Combine log statements to reduce loop blocking (#12947) --- esphome/components/qmp6988/qmp6988.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/esphome/components/qmp6988/qmp6988.cpp b/esphome/components/qmp6988/qmp6988.cpp index 57f54b6432..4e1ef27d5e 100644 --- a/esphome/components/qmp6988/qmp6988.cpp +++ b/esphome/components/qmp6988/qmp6988.cpp @@ -127,9 +127,11 @@ bool QMP6988Component::get_calibration_data_() { qmp6988_data_.qmp6988_cali.COE_b21 = (int16_t) encode_uint16(a_data_uint8_tr[14], a_data_uint8_tr[15]); qmp6988_data_.qmp6988_cali.COE_bp3 = (int16_t) encode_uint16(a_data_uint8_tr[16], a_data_uint8_tr[17]); - ESP_LOGV(TAG, "<-----------calibration data-------------->\r\n"); - ESP_LOGV(TAG, "COE_a0[%d] COE_a1[%d] COE_a2[%d] COE_b00[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_a0, - qmp6988_data_.qmp6988_cali.COE_a1, qmp6988_data_.qmp6988_cali.COE_a2, qmp6988_data_.qmp6988_cali.COE_b00); + ESP_LOGV(TAG, + "<-----------calibration data-------------->\n" + "COE_a0[%d] COE_a1[%d] COE_a2[%d] COE_b00[%d]", + qmp6988_data_.qmp6988_cali.COE_a0, qmp6988_data_.qmp6988_cali.COE_a1, qmp6988_data_.qmp6988_cali.COE_a2, + qmp6988_data_.qmp6988_cali.COE_b00); ESP_LOGV(TAG, "COE_bt1[%d] COE_bt2[%d] COE_bp1[%d] COE_b11[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_bt1, qmp6988_data_.qmp6988_cali.COE_bt2, qmp6988_data_.qmp6988_cali.COE_bp1, qmp6988_data_.qmp6988_cali.COE_b11); ESP_LOGV(TAG, "COE_bp2[%d] COE_b12[%d] COE_b21[%d] COE_bp3[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_bp2, @@ -150,9 +152,10 @@ bool QMP6988Component::get_calibration_data_() { qmp6988_data_.ik.b12 = 6846L * (int64_t) qmp6988_data_.qmp6988_cali.COE_b12 + 85590281L; // 29Q53 qmp6988_data_.ik.b21 = 13836L * (int64_t) qmp6988_data_.qmp6988_cali.COE_b21 + 79333336L; // 29Q60 qmp6988_data_.ik.bp3 = 2915L * (int64_t) qmp6988_data_.qmp6988_cali.COE_bp3 + 157155561L; // 28Q65 - ESP_LOGV(TAG, "<----------- int calibration data -------------->\r\n"); - ESP_LOGV(TAG, "a0[%d] a1[%d] a2[%d] b00[%d]\r\n", qmp6988_data_.ik.a0, qmp6988_data_.ik.a1, qmp6988_data_.ik.a2, - qmp6988_data_.ik.b00); + ESP_LOGV(TAG, + "<----------- int calibration data -------------->\n" + "a0[%d] a1[%d] a2[%d] b00[%d]", + qmp6988_data_.ik.a0, qmp6988_data_.ik.a1, qmp6988_data_.ik.a2, qmp6988_data_.ik.b00); ESP_LOGV(TAG, "bt1[%lld] bt2[%lld] bp1[%lld] b11[%lld]\r\n", qmp6988_data_.ik.bt1, qmp6988_data_.ik.bt2, qmp6988_data_.ik.bp1, qmp6988_data_.ik.b11); ESP_LOGV(TAG, "bp2[%lld] b12[%lld] b21[%lld] bp3[%lld]\r\n", qmp6988_data_.ik.bp2, qmp6988_data_.ik.b12, @@ -330,8 +333,10 @@ void QMP6988Component::dump_config() { LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); ESP_LOGCONFIG(TAG, " Temperature Oversampling: %s", oversampling_to_str(this->temperature_oversampling_)); LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); - ESP_LOGCONFIG(TAG, " Pressure Oversampling: %s", oversampling_to_str(this->pressure_oversampling_)); - ESP_LOGCONFIG(TAG, " IIR Filter: %s", iir_filter_to_str(this->iir_filter_)); + ESP_LOGCONFIG(TAG, + " Pressure Oversampling: %s\n" + " IIR Filter: %s", + oversampling_to_str(this->pressure_oversampling_), iir_filter_to_str(this->iir_filter_)); } void QMP6988Component::update() { From 3ec05a5a13f96855b7c1ba1c207a453dba1992c1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:06:55 -1000 Subject: [PATCH 1028/1145] [radon_eye_rd200] Combine log statements to reduce loop blocking (#12949) --- esphome/components/radon_eye_rd200/radon_eye_rd200.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp index 3959178b94..3ccb7bf082 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -118,10 +118,11 @@ void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) { radon_long_term_sensor_->publish_state(radon_day); } - ESP_LOGV(TAG, " Measurements (Bq/m³) now: %0.03f, day: %0.03f, month: %0.03f", radon_now, radon_day, radon_month); - - ESP_LOGV(TAG, " Measurements (pCi/L) now: %0.03f, day: %0.03f, month: %0.03f", radon_now / convert_to_bwpm3, - radon_day / convert_to_bwpm3, radon_month / convert_to_bwpm3); + ESP_LOGV(TAG, + " Measurements (Bq/m³) now: %0.03f, day: %0.03f, month: %0.03f\n" + " Measurements (pCi/L) now: %0.03f, day: %0.03f, month: %0.03f", + radon_now, radon_day, radon_month, radon_now / convert_to_bwpm3, radon_day / convert_to_bwpm3, + radon_month / convert_to_bwpm3); // This instance must not stay connected // so other clients can connect to it (e.g. the From 44fc156ef6f238b1f9053c719d5f98b8a0df6647 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:07:50 -1000 Subject: [PATCH 1029/1145] [remote_base] Combine log statements to reduce loop blocking (#12950) --- esphome/components/remote_base/pronto_protocol.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index 9fbc9e85ba..401a0976b2 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -104,8 +104,10 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::vector Date: Sun, 4 Jan 2026 16:08:45 -1000 Subject: [PATCH 1030/1145] [remote_receiver] Combine log statements to reduce loop blocking (#12951) --- esphome/components/remote_receiver/remote_receiver.cpp | 4 ++-- esphome/components/remote_receiver/remote_receiver_esp32.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/remote_receiver/remote_receiver.cpp b/esphome/components/remote_receiver/remote_receiver.cpp index a7ac74199d..de47457dac 100644 --- a/esphome/components/remote_receiver/remote_receiver.cpp +++ b/esphome/components/remote_receiver/remote_receiver.cpp @@ -76,9 +76,8 @@ void RemoteReceiverComponent::setup() { } void RemoteReceiverComponent::dump_config() { - ESP_LOGCONFIG(TAG, "Remote Receiver:"); - LOG_PIN(" Pin: ", this->pin_); ESP_LOGCONFIG(TAG, + "Remote Receiver:\n" " Buffer Size: %u\n" " Tolerance: %u%s\n" " Filter out pulses shorter than: %u us\n" @@ -86,6 +85,7 @@ void RemoteReceiverComponent::dump_config() { this->buffer_size_, this->tolerance_, (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%", this->filter_us_, this->idle_us_); + LOG_PIN(" Pin: ", this->pin_); } void RemoteReceiverComponent::loop() { diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index bd0bc8e57b..eda8365169 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -117,9 +117,8 @@ void RemoteReceiverComponent::setup() { } void RemoteReceiverComponent::dump_config() { - ESP_LOGCONFIG(TAG, "Remote Receiver:"); - LOG_PIN(" Pin: ", this->pin_); ESP_LOGCONFIG(TAG, + "Remote Receiver:\n" " Clock resolution: %" PRIu32 " hz\n" " RMT symbols: %" PRIu32 "\n" " Filter symbols: %" PRIu32 "\n" @@ -132,6 +131,7 @@ void RemoteReceiverComponent::dump_config() { this->clock_resolution_, this->rmt_symbols_, this->filter_symbols_, this->receive_symbols_, this->tolerance_, (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%", this->carrier_frequency_, this->carrier_duty_percent_, this->filter_us_, this->idle_us_); + LOG_PIN(" Pin: ", this->pin_); if (this->is_failed()) { ESP_LOGE(TAG, "Configuring RMT driver failed: %s (%s)", esp_err_to_name(this->error_code_), this->error_string_.c_str()); From 4f20c1ceb17aad02d8c5aec2dfbbb1f00cf5ffe9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:09:51 -1000 Subject: [PATCH 1031/1145] [rp2040_pwm] Combine log statements to reduce loop blocking (#12952) --- esphome/components/rp2040_pwm/rp2040_pwm.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/rp2040_pwm/rp2040_pwm.cpp b/esphome/components/rp2040_pwm/rp2040_pwm.cpp index ec164b3c05..90a507b14f 100644 --- a/esphome/components/rp2040_pwm/rp2040_pwm.cpp +++ b/esphome/components/rp2040_pwm/rp2040_pwm.cpp @@ -36,9 +36,11 @@ void RP2040PWM::setup_pwm_() { } void RP2040PWM::dump_config() { - ESP_LOGCONFIG(TAG, "RP2040 PWM:"); + ESP_LOGCONFIG(TAG, + "RP2040 PWM:\n" + " Frequency: %.1f Hz", + this->frequency_); LOG_PIN(" Pin: ", this->pin_); - ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); LOG_FLOAT_OUTPUT(this); } void HOT RP2040PWM::write_state(float state) { From 7449421cea5b8fa76dfa5e713bdaae30a4850847 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:10:06 -1000 Subject: [PATCH 1032/1145] [shelly_dimmer] Combine log statements to reduce loop blocking (#12956) --- .../shelly_dimmer/shelly_dimmer.cpp | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/esphome/components/shelly_dimmer/shelly_dimmer.cpp b/esphome/components/shelly_dimmer/shelly_dimmer.cpp index 3b5307805e..bdb33d31af 100644 --- a/esphome/components/shelly_dimmer/shelly_dimmer.cpp +++ b/esphome/components/shelly_dimmer/shelly_dimmer.cpp @@ -113,26 +113,20 @@ void ShellyDimmer::setup() { void ShellyDimmer::update() { this->send_command_(SHELLY_DIMMER_PROTO_CMD_POLL, nullptr, 0); } void ShellyDimmer::dump_config() { - ESP_LOGCONFIG(TAG, "ShellyDimmer:"); - LOG_PIN(" NRST Pin: ", this->pin_nrst_); - LOG_PIN(" BOOT0 Pin: ", this->pin_boot0_); - ESP_LOGCONFIG(TAG, + "ShellyDimmer:\n" " Leading Edge: %s\n" " Warmup Brightness: %d\n" " Minimum Brightness: %d\n" - " Maximum Brightness: %d", - YESNO(this->leading_edge_), this->warmup_brightness_, this->min_brightness_, this->max_brightness_); - // ESP_LOGCONFIG(TAG, " Warmup Time: %d", this->warmup_time_); - // ESP_LOGCONFIG(TAG, " Fade Rate: %d", this->fade_rate_); - - LOG_UPDATE_INTERVAL(this); - - ESP_LOGCONFIG(TAG, - " STM32 current firmware version: %d.%d \n" + " Maximum Brightness: %d\n" + " STM32 current firmware version: %d.%d\n" " STM32 required firmware version: %d.%d", + YESNO(this->leading_edge_), this->warmup_brightness_, this->min_brightness_, this->max_brightness_, this->version_major_, this->version_minor_, USE_SHD_FIRMWARE_MAJOR_VERSION, USE_SHD_FIRMWARE_MINOR_VERSION); + LOG_PIN(" NRST Pin: ", this->pin_nrst_); + LOG_PIN(" BOOT0 Pin: ", this->pin_boot0_); + LOG_UPDATE_INTERVAL(this); if (this->version_major_ != USE_SHD_FIRMWARE_MAJOR_VERSION || this->version_minor_ != USE_SHD_FIRMWARE_MINOR_VERSION) { @@ -439,13 +433,15 @@ bool ShellyDimmer::handle_frame_() { current = CURRENT_SCALING_FACTOR / static_cast(current_raw); } - ESP_LOGI(TAG, "Got dimmer data:"); - ESP_LOGI(TAG, " HW version: %d", hw_version); - ESP_LOGI(TAG, " Brightness: %d", brightness); - ESP_LOGI(TAG, " Fade rate: %d", fade_rate); - ESP_LOGI(TAG, " Power: %f W", power); - ESP_LOGI(TAG, " Voltage: %f V", voltage); - ESP_LOGI(TAG, " Current: %f A", current); + ESP_LOGI(TAG, + "Got dimmer data:\n" + " HW version: %d\n" + " Brightness: %d\n" + " Fade rate: %d\n" + " Power: %f W\n" + " Voltage: %f V\n" + " Current: %f A", + hw_version, brightness, fade_rate, power, voltage, current); // Update sensors. if (this->power_sensor_ != nullptr) { From 9f7925c1d589b54fa857bd9b379d9fbbc9cc82cc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:10:19 -1000 Subject: [PATCH 1033/1145] [safe_mode] Combine log statements to reduce loop blocking (#12955) --- esphome/components/safe_mode/safe_mode.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/safe_mode/safe_mode.cpp b/esphome/components/safe_mode/safe_mode.cpp index c933222273..c7bd8748f5 100644 --- a/esphome/components/safe_mode/safe_mode.cpp +++ b/esphome/components/safe_mode/safe_mode.cpp @@ -40,8 +40,10 @@ void SafeModeComponent::dump_config() { #ifdef USE_OTA_ROLLBACK const esp_partition_t *last_invalid = esp_ota_get_last_invalid_partition(); if (last_invalid != nullptr) { - ESP_LOGW(TAG, "OTA rollback detected! Rolled back from partition '%s'", last_invalid->label); - ESP_LOGW(TAG, "The device reset before the boot was marked successful"); + ESP_LOGW(TAG, + "OTA rollback detected! Rolled back from partition '%s'\n" + "The device reset before the boot was marked successful", + last_invalid->label); } #endif } From ab0e15e4bbd6f31371e280395ab9b1c14ab8c948 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:10:51 -1000 Subject: [PATCH 1034/1145] [runtime_stats] Combine log statements to reduce loop blocking (#12954) --- esphome/components/runtime_stats/runtime_stats.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/runtime_stats/runtime_stats.cpp b/esphome/components/runtime_stats/runtime_stats.cpp index f95be5291f..7e837a18e8 100644 --- a/esphome/components/runtime_stats/runtime_stats.cpp +++ b/esphome/components/runtime_stats/runtime_stats.cpp @@ -27,8 +27,10 @@ void RuntimeStatsCollector::record_component_time(Component *component, uint32_t } void RuntimeStatsCollector::log_stats_() { - ESP_LOGI(TAG, "Component Runtime Statistics"); - ESP_LOGI(TAG, "Period stats (last %" PRIu32 "ms):", this->log_interval_); + ESP_LOGI(TAG, + "Component Runtime Statistics\n" + "Period stats (last %" PRIu32 "ms):", + this->log_interval_); // First collect stats we want to display std::vector stats_to_display; From 12027569d33e7069bca7ed7986ba332ae3f55617 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Mon, 5 Jan 2026 03:11:14 +0100 Subject: [PATCH 1035/1145] [nrf52,zigbee] add support for binary_input (#11535) Co-authored-by: J. Nick Koston Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: J. Nick Koston --- CODEOWNERS | 1 + esphome/components/binary_sensor/__init__.py | 6 +- esphome/components/zephyr/core.cpp | 5 + esphome/components/zigbee/__init__.py | 124 ++++++++ esphome/components/zigbee/automation.h | 16 ++ esphome/components/zigbee/const_zephyr.py | 24 ++ .../zigbee/zigbee_binary_sensor_zephyr.cpp | 37 +++ .../zigbee/zigbee_binary_sensor_zephyr.h | 45 +++ esphome/components/zigbee/zigbee_zephyr.cpp | 190 +++++++++++++ esphome/components/zigbee/zigbee_zephyr.h | 104 +++++++ esphome/components/zigbee/zigbee_zephyr.py | 265 ++++++++++++++++++ esphome/core/defines.h | 3 + script/helpers_zephyr.py | 12 +- tests/components/zigbee/common.yaml | 34 +++ .../zigbee/test.nrf52-adafruit.yaml | 1 + .../components/zigbee/test.nrf52-mcumgr.yaml | 1 + .../zigbee/test.nrf52-xiao-ble.yaml | 1 + 17 files changed, 866 insertions(+), 3 deletions(-) create mode 100644 esphome/components/zigbee/__init__.py create mode 100644 esphome/components/zigbee/automation.h create mode 100644 esphome/components/zigbee/const_zephyr.py create mode 100644 esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp create mode 100644 esphome/components/zigbee/zigbee_binary_sensor_zephyr.h create mode 100644 esphome/components/zigbee/zigbee_zephyr.cpp create mode 100644 esphome/components/zigbee/zigbee_zephyr.h create mode 100644 esphome/components/zigbee/zigbee_zephyr.py create mode 100644 tests/components/zigbee/common.yaml create mode 100644 tests/components/zigbee/test.nrf52-adafruit.yaml create mode 100644 tests/components/zigbee/test.nrf52-mcumgr.yaml create mode 100644 tests/components/zigbee/test.nrf52-xiao-ble.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 0d9396aa6f..00db5a3c79 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -575,5 +575,6 @@ esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68 esphome/components/xxtea/* @clydebarrow esphome/components/zephyr/* @tomaszduda23 esphome/components/zhlt01/* @cfeenstra1024 +esphome/components/zigbee/* @tomaszduda23 esphome/components/zio_ultrasonic/* @kahrendt esphome/components/zwave_proxy/* @kbx81 diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index cbf935a501..c38d6b78d3 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -3,7 +3,7 @@ from logging import getLogger from esphome import automation, core from esphome.automation import Condition, maybe_simple_id import esphome.codegen as cg -from esphome.components import mqtt, web_server +from esphome.components import mqtt, web_server, zigbee from esphome.components.const import CONF_ON_STATE_CHANGE import esphome.config_validation as cv from esphome.const import ( @@ -439,6 +439,7 @@ def validate_publish_initial_state(value): _BINARY_SENSOR_SCHEMA = ( cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) .extend(cv.MQTT_COMPONENT_SCHEMA) + .extend(zigbee.BINARY_SENSOR_SCHEMA) .extend( { cv.GenerateID(): cv.declare_id(BinarySensor), @@ -520,6 +521,7 @@ _BINARY_SENSOR_SCHEMA = ( _BINARY_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("binary_sensor")) +_BINARY_SENSOR_SCHEMA.add_extra(zigbee.validate_binary_sensor) def binary_sensor_schema( @@ -621,6 +623,8 @@ async def setup_binary_sensor_core_(var, config): if web_server_config := config.get(CONF_WEB_SERVER): await web_server.add_entity_config(var, web_server_config) + await zigbee.setup_binary_sensor(var, config) + async def register_binary_sensor(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/zephyr/core.cpp b/esphome/components/zephyr/core.cpp index 46589cdb62..d7027b33f5 100644 --- a/esphome/components/zephyr/core.cpp +++ b/esphome/components/zephyr/core.cpp @@ -26,7 +26,12 @@ void arch_init() { if (device_is_ready(WDT)) { static wdt_timeout_cfg wdt_config{}; wdt_config.flags = WDT_FLAG_RESET_SOC; +#ifdef USE_ZIGBEE + // zboss thread use a lot of cpu cycles during start + wdt_config.window.max = 10000; +#else wdt_config.window.max = 2000; +#endif wdt_channel_id = wdt_install_timeout(WDT, &wdt_config); if (wdt_channel_id >= 0) { uint8_t options = 0; diff --git a/esphome/components/zigbee/__init__.py b/esphome/components/zigbee/__init__.py new file mode 100644 index 0000000000..2009f92d2e --- /dev/null +++ b/esphome/components/zigbee/__init__.py @@ -0,0 +1,124 @@ +from typing import Any + +from esphome import automation, core +import esphome.codegen as cg +from esphome.components.nrf52.boards import BOOTLOADER_CONFIG, Section +from esphome.components.zephyr import zephyr_add_pm_static, zephyr_data +from esphome.components.zephyr.const import KEY_BOOTLOADER +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_INTERNAL +from esphome.core import CORE +from esphome.types import ConfigType + +from .const_zephyr import ( + CONF_MAX_EP_NUMBER, + CONF_ON_JOIN, + CONF_WIPE_ON_BOOT, + CONF_ZIGBEE_ID, + KEY_EP_NUMBER, + KEY_ZIGBEE, + ZigbeeComponent, + zigbee_ns, +) +from .zigbee_zephyr import zephyr_binary_sensor + +CODEOWNERS = ["@tomaszduda23"] + + +def zigbee_set_core_data(config: ConfigType) -> ConfigType: + if zephyr_data()[KEY_BOOTLOADER] in BOOTLOADER_CONFIG: + zephyr_add_pm_static( + [Section("empty_after_zboss_offset", 0xF4000, 0xC000, "flash_primary")] + ) + + return config + + +BINARY_SENSOR_SCHEMA = cv.Schema({}).extend(zephyr_binary_sensor) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(ZigbeeComponent), + cv.Optional(CONF_ON_JOIN): automation.validate_automation(single=True), + cv.Optional(CONF_WIPE_ON_BOOT, default=False): cv.All( + cv.boolean, + cv.requires_component("nrf52"), + ), + } + ).extend(cv.COMPONENT_SCHEMA), + zigbee_set_core_data, + cv.only_with_framework("zephyr"), +) + + +def validate_number_of_ep(config: ConfigType) -> None: + if KEY_ZIGBEE not in CORE.data: + raise cv.Invalid("At least one zigbee device need to be included") + count = len(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER]) + if count == 1: + raise cv.Invalid( + "Single endpoint is not supported https://github.com/Koenkk/zigbee2mqtt/issues/29888" + ) + if count > CONF_MAX_EP_NUMBER: + raise cv.Invalid(f"Maximum number of end points is {CONF_MAX_EP_NUMBER}") + + +FINAL_VALIDATE_SCHEMA = cv.All( + validate_number_of_ep, +) + + +async def to_code(config: ConfigType) -> None: + cg.add_define("USE_ZIGBEE") + if CORE.using_zephyr: + from .zigbee_zephyr import zephyr_to_code + + await zephyr_to_code(config) + + +async def setup_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None: + if not config.get(CONF_ZIGBEE_ID) or config.get(CONF_INTERNAL): + return + if CORE.using_zephyr: + from .zigbee_zephyr import zephyr_setup_binary_sensor + + await zephyr_setup_binary_sensor(entity, config) + + +def validate_binary_sensor(config: ConfigType) -> ConfigType: + if not config.get(CONF_ZIGBEE_ID) or config.get(CONF_INTERNAL): + return config + data: dict[str, Any] = CORE.data.setdefault(KEY_ZIGBEE, {}) + slots: list[str] = data.setdefault(KEY_EP_NUMBER, []) + slots.extend([""]) + return config + + +ZIGBEE_ACTION_SCHEMA = automation.maybe_simple_id( + cv.Schema( + { + cv.GenerateID(): cv.use_id(ZigbeeComponent), + } + ) +) + +FactoryResetAction = zigbee_ns.class_( + "FactoryResetAction", automation.Action, cg.Parented.template(ZigbeeComponent) +) + + +@automation.register_action( + "zigbee.factory_reset", + FactoryResetAction, + ZIGBEE_ACTION_SCHEMA, +) +async def reset_zigbee_to_code( + config: ConfigType, + action_id: core.ID, + template_arg: cg.TemplateArguments, + args: list[tuple], +) -> cg.Pvariable: + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var diff --git a/esphome/components/zigbee/automation.h b/esphome/components/zigbee/automation.h new file mode 100644 index 0000000000..1822e6a029 --- /dev/null +++ b/esphome/components/zigbee/automation.h @@ -0,0 +1,16 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_ZIGBEE +#ifdef USE_NRF52 +#include "zigbee_zephyr.h" +#endif +namespace esphome::zigbee { + +template class FactoryResetAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->factory_reset(); } +}; + +} // namespace esphome::zigbee + +#endif diff --git a/esphome/components/zigbee/const_zephyr.py b/esphome/components/zigbee/const_zephyr.py new file mode 100644 index 0000000000..ecd08f1f0a --- /dev/null +++ b/esphome/components/zigbee/const_zephyr.py @@ -0,0 +1,24 @@ +import esphome.codegen as cg + +zigbee_ns = cg.esphome_ns.namespace("zigbee") +ZigbeeComponent = zigbee_ns.class_("ZigbeeComponent", cg.Component) +BinaryAttrs = zigbee_ns.struct("BinaryAttrs") + +CONF_MAX_EP_NUMBER = 8 +CONF_ZIGBEE_ID = "zigbee_id" +CONF_ON_JOIN = "on_join" +CONF_WIPE_ON_BOOT = "wipe_on_boot" +CONF_ZIGBEE_BINARY_SENSOR = "zigbee_binary_sensor" + +# Keys for CORE.data storage +KEY_ZIGBEE = "zigbee" +KEY_EP_NUMBER = "ep_number" + +# External ZBOSS SDK types (just strings for codegen) +ZB_ZCL_BASIC_ATTRS_EXT_T = "zb_zcl_basic_attrs_ext_t" +ZB_ZCL_IDENTIFY_ATTRS_T = "zb_zcl_identify_attrs_t" + +# Cluster IDs +ZB_ZCL_CLUSTER_ID_BASIC = "ZB_ZCL_CLUSTER_ID_BASIC" +ZB_ZCL_CLUSTER_ID_IDENTIFY = "ZB_ZCL_CLUSTER_ID_IDENTIFY" +ZB_ZCL_CLUSTER_ID_BINARY_INPUT = "ZB_ZCL_CLUSTER_ID_BINARY_INPUT" diff --git a/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp b/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp new file mode 100644 index 0000000000..744d04adc5 --- /dev/null +++ b/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp @@ -0,0 +1,37 @@ +#include "zigbee_binary_sensor_zephyr.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_BINARY_SENSOR) +#include "esphome/core/log.h" +extern "C" { +#include +#include +#include +#include +#include +} +namespace esphome::zigbee { + +static const char *const TAG = "zigbee.binary_sensor"; + +ZigbeeBinarySensor::ZigbeeBinarySensor(binary_sensor::BinarySensor *binary_sensor) : binary_sensor_(binary_sensor) {} + +void ZigbeeBinarySensor::setup() { + this->binary_sensor_->add_on_state_callback([this](bool state) { + this->cluster_attributes_->present_value = state ? ZB_TRUE : ZB_FALSE; + ESP_LOGD(TAG, "Set attribute end point: %d, present_value %d", this->end_point_, + this->cluster_attributes_->present_value); + ZB_ZCL_SET_ATTRIBUTE(this->end_point_, ZB_ZCL_CLUSTER_ID_BINARY_INPUT, ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_BINARY_INPUT_PRESENT_VALUE_ID, &this->cluster_attributes_->present_value, + ZB_FALSE); + this->parent_->flush(); + }); +} + +void ZigbeeBinarySensor::dump_config() { + ESP_LOGCONFIG(TAG, + "Zigbee Binary Sensor\n" + " End point: %d, present_value %u", + this->end_point_, this->cluster_attributes_->present_value); +} + +} // namespace esphome::zigbee +#endif diff --git a/esphome/components/zigbee/zigbee_binary_sensor_zephyr.h b/esphome/components/zigbee/zigbee_binary_sensor_zephyr.h new file mode 100644 index 0000000000..aae79fa289 --- /dev/null +++ b/esphome/components/zigbee/zigbee_binary_sensor_zephyr.h @@ -0,0 +1,45 @@ +#pragma once +#include "esphome/core/defines.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_BINARY_SENSOR) +#include "esphome/components/zigbee/zigbee_zephyr.h" +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +extern "C" { +#include +#include +} + +// it should have been defined inside of sdk. It is missing though +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BINARY_INPUT_DESCRIPTION_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_BINARY_INPUT_DESCRIPTION_ID, ZB_ZCL_ATTR_TYPE_CHAR_STRING, ZB_ZCL_ATTR_ACCESS_READ_ONLY, \ + (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \ + } + +// copy of ZB_ZCL_DECLARE_BINARY_INPUT_ATTRIB_LIST + description +#define ESPHOME_ZB_ZCL_DECLARE_BINARY_INPUT_ATTRIB_LIST(attr_list, out_of_service, present_value, status_flag, \ + description) \ + ZB_ZCL_START_DECLARE_ATTRIB_LIST_CLUSTER_REVISION(attr_list, ZB_ZCL_BINARY_INPUT) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_INPUT_OUT_OF_SERVICE_ID, (out_of_service)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_INPUT_PRESENT_VALUE_ID, (present_value)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_INPUT_STATUS_FLAG_ID, (status_flag)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_INPUT_DESCRIPTION_ID, (description)) \ + ZB_ZCL_FINISH_DECLARE_ATTRIB_LIST + +namespace esphome::zigbee { + +class ZigbeeBinarySensor : public ZigbeeEntity, public Component { + public: + explicit ZigbeeBinarySensor(binary_sensor::BinarySensor *binary_sensor); + void set_cluster_attributes(BinaryAttrs &cluster_attributes) { this->cluster_attributes_ = &cluster_attributes; } + + void setup() override; + void dump_config() override; + + protected: + BinaryAttrs *cluster_attributes_{nullptr}; + binary_sensor::BinarySensor *binary_sensor_; +}; + +} // namespace esphome::zigbee +#endif diff --git a/esphome/components/zigbee/zigbee_zephyr.cpp b/esphome/components/zigbee/zigbee_zephyr.cpp new file mode 100644 index 0000000000..c9027d0a74 --- /dev/null +++ b/esphome/components/zigbee/zigbee_zephyr.cpp @@ -0,0 +1,190 @@ +#include "zigbee_zephyr.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) +#include "esphome/core/log.h" +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +namespace esphome::zigbee { + +static const char *const TAG = "zigbee"; + +ZigbeeComponent *global_zigbee = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +const uint8_t IEEE_ADDR_BUF_SIZE = 17; + +void ZigbeeComponent::zboss_signal_handler_esphome(zb_bufid_t bufid) { + zb_zdo_app_signal_hdr_t *sig_hndler = nullptr; + zb_zdo_app_signal_type_t sig = zb_get_app_signal(bufid, &sig_hndler); + zb_ret_t status = ZB_GET_APP_SIGNAL_STATUS(bufid); + + switch (sig) { + case ZB_ZDO_SIGNAL_SKIP_STARTUP: + ESP_LOGD(TAG, "ZB_ZDO_SIGNAL_SKIP_STARTUP, status: %d", status); + break; + case ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY: + ESP_LOGD(TAG, "ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY, status: %d", status); + break; + case ZB_ZDO_SIGNAL_LEAVE: + ESP_LOGD(TAG, "ZB_ZDO_SIGNAL_LEAVE, status: %d", status); + break; + case ZB_BDB_SIGNAL_DEVICE_REBOOT: + ESP_LOGD(TAG, "ZB_BDB_SIGNAL_DEVICE_REBOOT, status: %d", status); + if (status == RET_OK) { + on_join_(); + } + break; + case ZB_BDB_SIGNAL_STEERING: + break; + case ZB_COMMON_SIGNAL_CAN_SLEEP: + ESP_LOGV(TAG, "ZB_COMMON_SIGNAL_CAN_SLEEP, status: %d", status); + break; + case ZB_BDB_SIGNAL_DEVICE_FIRST_START: + ESP_LOGD(TAG, "ZB_BDB_SIGNAL_DEVICE_FIRST_START, status: %d", status); + break; + case ZB_NLME_STATUS_INDICATION: + ESP_LOGD(TAG, "ZB_NLME_STATUS_INDICATION, status: %d", status); + break; + case ZB_BDB_SIGNAL_TC_REJOIN_DONE: + ESP_LOGD(TAG, "ZB_BDB_SIGNAL_TC_REJOIN_DONE, status: %d", status); + break; + default: + ESP_LOGD(TAG, "zboss_signal_handler sig: %d, status: %d", sig, status); + break; + } + + auto err = zigbee_default_signal_handler(bufid); + if (err != RET_OK) { + ESP_LOGE(TAG, "Zigbee_default_signal_handler ERROR %u [%s]", err, zb_error_to_string_get(err)); + } + + switch (sig) { + case ZB_BDB_SIGNAL_STEERING: + ESP_LOGD(TAG, "ZB_BDB_SIGNAL_STEERING, status: %d", status); + if (status == RET_OK) { + zb_ext_pan_id_t extended_pan_id; + char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = {0}; + int addr_len; + + zb_get_extended_pan_id(extended_pan_id); + addr_len = ieee_addr_to_str(ieee_addr_buf, sizeof(ieee_addr_buf), extended_pan_id); + + for (int i = 0; i < addr_len; ++i) { + if (ieee_addr_buf[i] != '0') { + on_join_(); + break; + } + } + } + break; + } + + /* All callbacks should either reuse or free passed buffers. + * If bufid == 0, the buffer is invalid (not passed). + */ + if (bufid) { + zb_buf_free(bufid); + } +} + +void ZigbeeComponent::zcl_device_cb(zb_bufid_t bufid) { + zb_zcl_device_callback_param_t *p_device_cb_param = ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t); + zb_zcl_device_callback_id_t device_cb_id = p_device_cb_param->device_cb_id; + zb_uint16_t cluster_id = p_device_cb_param->cb_param.set_attr_value_param.cluster_id; + zb_uint16_t attr_id = p_device_cb_param->cb_param.set_attr_value_param.attr_id; + auto endpoint = p_device_cb_param->endpoint; + + ESP_LOGI(TAG, "Zcl_device_cb %s id %hd, cluster_id %d, attr_id %d, endpoint: %d", __func__, device_cb_id, cluster_id, + attr_id, endpoint); + + // endpoints are enumerated from 1 + if (global_zigbee->callbacks_.size() >= endpoint) { + global_zigbee->callbacks_[endpoint - 1](bufid); + return; + } + p_device_cb_param->status = RET_ERROR; +} + +void ZigbeeComponent::on_join_() { + this->defer([this]() { + ESP_LOGD(TAG, "Joined the network"); + this->join_trigger_.trigger(); + this->join_cb_.call(); + }); +} + +#ifdef USE_ZIGBEE_WIPE_ON_BOOT +void ZigbeeComponent::erase_flash_(int area) { + const struct flash_area *fap; + flash_area_open(area, &fap); + flash_area_erase(fap, 0, fap->fa_size); + flash_area_close(fap); +} +#endif + +void ZigbeeComponent::setup() { + global_zigbee = this; + auto err = settings_subsys_init(); + if (err) { + ESP_LOGE(TAG, "Failed to initialize settings subsystem, err: %d", err); + return; + } + +#ifdef USE_ZIGBEE_WIPE_ON_BOOT + erase_flash_(FIXED_PARTITION_ID(ZBOSS_NVRAM)); + erase_flash_(FIXED_PARTITION_ID(ZBOSS_PRODUCT_CONFIG)); + erase_flash_(FIXED_PARTITION_ID(SETTINGS_STORAGE)); +#endif + + ZB_ZCL_REGISTER_DEVICE_CB(zcl_device_cb); + err = settings_load(); + if (err) { + ESP_LOGE(TAG, "Cannot load settings, err: %d", err); + return; + } + zigbee_enable(); +} + +void ZigbeeComponent::dump_config() { + bool wipe = false; +#ifdef USE_ZIGBEE_WIPE_ON_BOOT + wipe = true; +#endif + ESP_LOGCONFIG(TAG, + "Zigbee\n" + " Wipe on boot: %s", + YESNO(wipe)); +} + +static void send_attribute_report(zb_bufid_t bufid, zb_uint16_t cmd_id) { + ESP_LOGD(TAG, "Force zboss scheduler to wake and send attribute report"); + zb_buf_free(bufid); +} + +void ZigbeeComponent::flush() { this->need_flush_ = true; } + +void ZigbeeComponent::loop() { + if (this->need_flush_) { + this->need_flush_ = false; + zb_buf_get_out_delayed_ext(send_attribute_report, 0, 0); + } +} + +void ZigbeeComponent::factory_reset() { + ESP_LOGD(TAG, "Factory reset"); + ZB_SCHEDULE_APP_CALLBACK(zb_bdb_reset_via_local_action, 0); +} + +} // namespace esphome::zigbee + +extern "C" void zboss_signal_handler(zb_uint8_t param) { + esphome::zigbee::global_zigbee->zboss_signal_handler_esphome(param); +} +#endif diff --git a/esphome/components/zigbee/zigbee_zephyr.h b/esphome/components/zigbee/zigbee_zephyr.h new file mode 100644 index 0000000000..853c6deb4d --- /dev/null +++ b/esphome/components/zigbee/zigbee_zephyr.h @@ -0,0 +1,104 @@ +#pragma once +#include "esphome/core/defines.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +extern "C" { +#include +#include +} + +// copy of ZB_DECLARE_SIMPLE_DESC. Due to https://github.com/nrfconnect/sdk-nrfxlib/pull/666 +#define ESPHOME_ZB_DECLARE_SIMPLE_DESC(ep_name, in_clusters_count, out_clusters_count) \ + typedef ZB_PACKED_PRE struct zb_af_simple_desc_##ep_name##_##in_clusters_count##_##out_clusters_count##_s { \ + zb_uint8_t endpoint; /* Endpoint */ \ + zb_uint16_t app_profile_id; /* Application profile identifier */ \ + zb_uint16_t app_device_id; /* Application device identifier */ \ + zb_bitfield_t app_device_version : 4; /* Application device version */ \ + zb_bitfield_t reserved : 4; /* Reserved */ \ + zb_uint8_t app_input_cluster_count; /* Application input cluster count */ \ + zb_uint8_t app_output_cluster_count; /* Application output cluster count */ \ + /* Application input and output cluster list */ \ + zb_uint16_t app_cluster_list[(in_clusters_count) + (out_clusters_count)]; \ + } ZB_PACKED_STRUCT zb_af_simple_desc_##ep_name##_##in_clusters_count##_##out_clusters_count##_t + +#define ESPHOME_CAT7(a, b, c, d, e, f, g) a##b##c##d##e##f##g +// needed to use ESPHOME_ZB_DECLARE_SIMPLE_DESC +#define ESPHOME_ZB_AF_SIMPLE_DESC_TYPE(ep_name, in_num, out_num) \ + ESPHOME_CAT7(zb_af_simple_desc_, ep_name, _, in_num, _, out_num, _t) + +// needed to use ESPHOME_ZB_DECLARE_SIMPLE_DESC +#define ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_clust_num, out_clust_num, ...) \ + ESPHOME_ZB_DECLARE_SIMPLE_DESC(ep_name, in_clust_num, out_clust_num); \ + ESPHOME_ZB_AF_SIMPLE_DESC_TYPE(ep_name, in_clust_num, out_clust_num) \ + simple_desc_##ep_name = {ep_id, ZB_AF_HA_PROFILE_ID, ZB_HA_SIMPLE_SENSOR_DEVICE_ID, 0, 0, in_clust_num, \ + out_clust_num, {__VA_ARGS__}} + +// needed to use ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC +#define ESPHOME_ZB_HA_DECLARE_EP(ep_name, ep_id, cluster_list, in_cluster_num, out_cluster_num, report_attr_count, \ + ...) \ + ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_cluster_num, out_cluster_num, __VA_ARGS__); \ + ZBOSS_DEVICE_DECLARE_REPORTING_CTX(reporting_info##ep_name, report_attr_count); \ + ZB_AF_DECLARE_ENDPOINT_DESC(ep_name, ep_id, ZB_AF_HA_PROFILE_ID, 0, NULL, \ + ZB_ZCL_ARRAY_SIZE(cluster_list, zb_zcl_cluster_desc_t), cluster_list, \ + (zb_af_simple_desc_1_1_t *) &simple_desc_##ep_name, report_attr_count, \ + reporting_info##ep_name, 0, NULL) + +namespace esphome::zigbee { + +struct BinaryAttrs { + zb_bool_t out_of_service; + zb_bool_t present_value; + zb_uint8_t status_flags; + zb_uchar_t description[ZB_ZCL_MAX_STRING_SIZE]; +}; + +struct AnalogAttrs { + zb_bool_t out_of_service; + float present_value; + zb_uint8_t status_flags; + zb_uchar_t description[ZB_ZCL_MAX_STRING_SIZE]; + float max_present_value; + float min_present_value; + float resolution; +}; + +class ZigbeeComponent : public Component { + public: + void setup() override; + void dump_config() override; + void add_callback(zb_uint8_t endpoint, std::function &&cb) { + // endpoints are enumerated from 1 + this->callbacks_[endpoint - 1] = std::move(cb); + } + void add_join_callback(std::function &&cb) { this->join_cb_.add(std::move(cb)); } + void zboss_signal_handler_esphome(zb_bufid_t bufid); + void factory_reset(); + Trigger<> *get_join_trigger() { return &this->join_trigger_; }; + void flush(); + void loop() override; + + protected: + static void zcl_device_cb(zb_bufid_t bufid); + void on_join_(); +#ifdef USE_ZIGBEE_WIPE_ON_BOOT + void erase_flash_(int area); +#endif + StaticVector, ZIGBEE_ENDPOINTS_COUNT> callbacks_; + CallbackManager join_cb_; + Trigger<> join_trigger_; + bool need_flush_{false}; +}; + +class ZigbeeEntity { + public: + void set_parent(ZigbeeComponent *parent) { this->parent_ = parent; } + void set_end_point(zb_uint8_t end_point) { this->end_point_ = end_point; } + + protected: + zb_uint8_t end_point_{0}; + ZigbeeComponent *parent_{nullptr}; +}; + +} // namespace esphome::zigbee +#endif diff --git a/esphome/components/zigbee/zigbee_zephyr.py b/esphome/components/zigbee/zigbee_zephyr.py new file mode 100644 index 0000000000..ce55675c41 --- /dev/null +++ b/esphome/components/zigbee/zigbee_zephyr.py @@ -0,0 +1,265 @@ +from datetime import datetime + +from esphome import automation +import esphome.codegen as cg +from esphome.components.zephyr import zephyr_add_prj_conf +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_NAME, __version__ +from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.cpp_generator import ( + AssignmentExpression, + MockObj, + VariableDeclarationExpression, +) +from esphome.types import ConfigType + +from .const_zephyr import ( + CONF_ON_JOIN, + CONF_WIPE_ON_BOOT, + CONF_ZIGBEE_BINARY_SENSOR, + CONF_ZIGBEE_ID, + KEY_EP_NUMBER, + KEY_ZIGBEE, + ZB_ZCL_BASIC_ATTRS_EXT_T, + ZB_ZCL_CLUSTER_ID_BASIC, + ZB_ZCL_CLUSTER_ID_BINARY_INPUT, + ZB_ZCL_CLUSTER_ID_IDENTIFY, + ZB_ZCL_IDENTIFY_ATTRS_T, + BinaryAttrs, + ZigbeeComponent, + zigbee_ns, +) + +ZigbeeBinarySensor = zigbee_ns.class_("ZigbeeBinarySensor", cg.Component) + +zephyr_binary_sensor = cv.Schema( + { + cv.OnlyWith(CONF_ZIGBEE_ID, ["nrf52", "zigbee"]): cv.use_id(ZigbeeComponent), + cv.OnlyWith(CONF_ZIGBEE_BINARY_SENSOR, ["nrf52", "zigbee"]): cv.declare_id( + ZigbeeBinarySensor + ), + } +) + + +async def zephyr_to_code(config: ConfigType) -> None: + zephyr_add_prj_conf("ZIGBEE", True) + zephyr_add_prj_conf("ZIGBEE_APP_UTILS", True) + zephyr_add_prj_conf("ZIGBEE_ROLE_END_DEVICE", True) + + zephyr_add_prj_conf("ZIGBEE_CHANNEL_SELECTION_MODE_MULTI", True) + + zephyr_add_prj_conf("CRYPTO", True) + + zephyr_add_prj_conf("NET_IPV6", False) + zephyr_add_prj_conf("NET_IP_ADDR_CHECK", False) + zephyr_add_prj_conf("NET_UDP", False) + + if config[CONF_WIPE_ON_BOOT]: + cg.add_define("USE_ZIGBEE_WIPE_ON_BOOT") + var = cg.new_Pvariable(config[CONF_ID]) + + if on_join_config := config.get(CONF_ON_JOIN): + await automation.build_automation(var.get_join_trigger(), [], on_join_config) + + await cg.register_component(var, config) + + await _attr_to_code(config) + CORE.add_job(_ctx_to_code, config) + + +async def _attr_to_code(config: ConfigType) -> None: + # Create the basic attributes structure and attribute list + basic_attrs = zigbee_new_variable("zigbee_basic_attrs", ZB_ZCL_BASIC_ATTRS_EXT_T) + zigbee_new_attr_list( + "zigbee_basic_attrib_list", + "ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT", + zigbee_assign(basic_attrs.zcl_version, cg.RawExpression("ZB_ZCL_VERSION")), + zigbee_assign(basic_attrs.app_version, 0), + zigbee_assign(basic_attrs.stack_version, 0), + zigbee_assign(basic_attrs.hw_version, 0), + zigbee_set_string(basic_attrs.mf_name, "esphome"), + zigbee_set_string(basic_attrs.model_id, CORE.name), + zigbee_set_string( + basic_attrs.date_code, datetime.now().strftime("%d/%m/%y %H:%M") + ), + zigbee_assign( + basic_attrs.power_source, + cg.RawExpression("ZB_ZCL_BASIC_POWER_SOURCE_DC_SOURCE"), + ), + zigbee_set_string(basic_attrs.location_id, ""), + zigbee_assign( + basic_attrs.ph_env, cg.RawExpression("ZB_ZCL_BASIC_ENV_UNSPECIFIED") + ), + zigbee_set_string(basic_attrs.sw_ver, __version__), + ) + + # Create the identify attributes structure and attribute list + identify_attrs = zigbee_new_variable( + "zigbee_identify_attrs", ZB_ZCL_IDENTIFY_ATTRS_T + ) + zigbee_new_attr_list( + "zigbee_identify_attrib_list", + "ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST", + zigbee_assign( + identify_attrs.identify_time, + cg.RawExpression("ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE"), + ), + ) + + +def zigbee_new_variable(name: str, type_: str) -> cg.MockObj: + """Create a global variable with the given name and type.""" + decl = VariableDeclarationExpression(type_, "", name) + CORE.add_global(decl) + return MockObj(name, ".") + + +def zigbee_assign(target: cg.MockObj, expression: cg.RawExpression | int) -> str: + """Assign an expression to a target and return a reference to it.""" + cg.add(AssignmentExpression("", "", target, expression)) + return f"&{target}" + + +def zigbee_set_string(target: cg.MockObj, value: str) -> str: + """Set a ZCL string value and return the target name (arrays decay to pointers).""" + cg.add( + cg.RawExpression( + f"ZB_ZCL_SET_STRING_VAL({target}, {cg.safe_exp(value)}, ZB_ZCL_STRING_CONST_SIZE({cg.safe_exp(value)}))" + ) + ) + return str(target) + + +def zigbee_new_attr_list(name: str, macro: str, *args: str) -> str: + """Create an attribute list using a ZBOSS macro and return the name.""" + obj = cg.RawExpression(f"{macro}({name}, {', '.join(args)})") + CORE.add_global(obj) + return name + + +class ZigbeeClusterDesc: + """Represents a Zigbee cluster descriptor for code generation.""" + + def __init__(self, cluster_id: str, attr_list_name: str | None = None) -> None: + self._cluster_id = cluster_id + self._attr_list_name = attr_list_name + + @property + def cluster_id(self) -> str: + return self._cluster_id + + @property + def has_attrs(self) -> bool: + return self._attr_list_name is not None + + def __str__(self) -> str: + role = ( + "ZB_ZCL_CLUSTER_SERVER_ROLE" + if self._attr_list_name + else "ZB_ZCL_CLUSTER_CLIENT_ROLE" + ) + if self._attr_list_name: + attr_count = f"ZB_ZCL_ARRAY_SIZE({self._attr_list_name}, zb_zcl_attr_t)" + return f"ZB_ZCL_CLUSTER_DESC({self._cluster_id}, {attr_count}, {self._attr_list_name}, {role}, ZB_ZCL_MANUF_CODE_INVALID)" + return f"ZB_ZCL_CLUSTER_DESC({self._cluster_id}, 0, NULL, {role}, ZB_ZCL_MANUF_CODE_INVALID)" + + +def zigbee_new_cluster_list( + name: str, clusters: list[ZigbeeClusterDesc] +) -> tuple[str, list[ZigbeeClusterDesc]]: + """Create a cluster list array and return its name and the clusters.""" + # Always include basic and identify clusters first + all_clusters = [ + ZigbeeClusterDesc(ZB_ZCL_CLUSTER_ID_BASIC, "zigbee_basic_attrib_list"), + ZigbeeClusterDesc(ZB_ZCL_CLUSTER_ID_IDENTIFY, "zigbee_identify_attrib_list"), + ] + all_clusters.extend(clusters) + + cluster_strs = [str(c) for c in all_clusters] + CORE.add_global( + cg.RawExpression( + f"zb_zcl_cluster_desc_t {name}[] = {{{', '.join(cluster_strs)}}}" + ) + ) + return (name, all_clusters) + + +def zigbee_register_ep( + ep_name: str, + cluster_list_name: str, + report_attr_count: int, + clusters: list[ZigbeeClusterDesc], + slot_index: int, +) -> None: + """Register a Zigbee endpoint.""" + in_cluster_num = sum(1 for c in clusters if c.has_attrs) + out_cluster_num = len(clusters) - in_cluster_num + cluster_ids = [c.cluster_id for c in clusters] + + # Store endpoint name for device context generation + CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER][slot_index] = ep_name + + # Generate the endpoint declaration + ep_id = slot_index + 1 # Endpoints are 1-indexed + obj = cg.RawExpression( + f"ESPHOME_ZB_HA_DECLARE_EP({ep_name}, {ep_id}, {cluster_list_name}, " + f"{in_cluster_num}, {out_cluster_num}, {report_attr_count}, {', '.join(cluster_ids)})" + ) + CORE.add_global(obj) + + +@coroutine_with_priority(CoroPriority.LATE) +async def _ctx_to_code(config: ConfigType) -> None: + cg.add_define("ZIGBEE_ENDPOINTS_COUNT", len(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER])) + cg.add_global( + cg.RawExpression( + f"ZBOSS_DECLARE_DEVICE_CTX_EP_VA(zb_device_ctx, &{', &'.join(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER])})" + ) + ) + cg.add(cg.RawExpression("ZB_AF_REGISTER_DEVICE_CTX(&zb_device_ctx)")) + + +async def zephyr_setup_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None: + CORE.add_job(_add_binary_sensor, entity, config) + + +async def _add_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None: + # Find the next available endpoint slot + slot_index = next( + (i for i, v in enumerate(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER]) if v == ""), None + ) + + # Create unique names for this sensor's variables based on slot index + prefix = f"zigbee_ep{slot_index + 1}" + attrs_name = f"{prefix}_binary_attrs" + attr_list_name = f"{prefix}_binary_input_attrib_list" + cluster_list_name = f"{prefix}_cluster_list" + ep_name = f"{prefix}_ep" + + # Create the binary attributes structure + binary_attrs = zigbee_new_variable(attrs_name, BinaryAttrs) + attr_list = zigbee_new_attr_list( + attr_list_name, + "ESPHOME_ZB_ZCL_DECLARE_BINARY_INPUT_ATTRIB_LIST", + zigbee_assign(binary_attrs.out_of_service, 0), + zigbee_assign(binary_attrs.present_value, 0), + zigbee_assign(binary_attrs.status_flags, 0), + zigbee_set_string(binary_attrs.description, config[CONF_NAME]), + ) + + # Create cluster list and register endpoint + cluster_list_name, clusters = zigbee_new_cluster_list( + cluster_list_name, + [ZigbeeClusterDesc(ZB_ZCL_CLUSTER_ID_BINARY_INPUT, attr_list)], + ) + zigbee_register_ep(ep_name, cluster_list_name, 2, clusters, slot_index) + + # Create the ZigbeeBinarySensor component + var = cg.new_Pvariable(config[CONF_ZIGBEE_BINARY_SENSOR], entity) + await cg.register_component(var, config) + + cg.add(var.set_end_point(slot_index + 1)) + cg.add(var.set_cluster_attributes(binary_attrs)) + hub = await cg.get_variable(config[CONF_ZIGBEE_ID]) + cg.add(var.set_parent(hub)) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 1fddc426d4..cee46a2df0 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -299,6 +299,9 @@ #define USE_NRF52_UICR_ERASE #define USE_SOFTDEVICE_ID 7 #define USE_SOFTDEVICE_VERSION 1 +#define USE_ZIGBEE +#define USE_ZIGBEE_WIPE_ON_BOOT +#define ZIGBEE_ENDPOINTS_COUNT 8 #endif // Disabled feature flags diff --git a/script/helpers_zephyr.py b/script/helpers_zephyr.py index f72b335e64..1242a60cf4 100644 --- a/script/helpers_zephyr.py +++ b/script/helpers_zephyr.py @@ -17,6 +17,7 @@ def load_idedata(environment, temp_folder, platformio_ini): """ #include int main() { return 0;} +extern "C" void zboss_signal_handler() {}; """, encoding="utf-8", ) @@ -27,6 +28,12 @@ int main() { return 0;} CONFIG_NEWLIB_LIBC=y CONFIG_BT=y CONFIG_ADC=y +#zigbee begin +CONFIG_ZIGBEE=y +CONFIG_CRYPTO=y +CONFIG_NVS=y +CONFIG_SETTINGS=y +#zigbee end """, encoding="utf-8", ) @@ -44,10 +51,11 @@ CONFIG_ADC=y def extract_defines(command): define_pattern = re.compile(r"-D\s*([^\s]+)") + ignore_prefixes = ("_ASMLANGUAGE", "NRF_802154_ECB_PRIORITY=") return [ - match + match.replace("\\", "") for match in define_pattern.findall(command) - if match not in ("_ASMLANGUAGE") + if not any(match.startswith(prefix) for prefix in ignore_prefixes) ] def find_cxx_path(commands): diff --git a/tests/components/zigbee/common.yaml b/tests/components/zigbee/common.yaml new file mode 100644 index 0000000000..eb30205446 --- /dev/null +++ b/tests/components/zigbee/common.yaml @@ -0,0 +1,34 @@ +--- +binary_sensor: + - platform: template + name: "Garage Door Open 1" + - platform: template + name: "Garage Door Open 2" + - platform: template + name: "Garage Door Open 3" + - platform: template + name: "Garage Door Open 4" + - platform: template + name: "Garage Door Open 5" + - platform: template + name: "Garage Door Open 6" + - platform: template + name: "Garage Door Open 7" + internal: True + - platform: template + name: "Garage Door Open 8" + - platform: template + name: "Garage Door Open 9" + +zigbee: + wipe_on_boot: true + on_join: + then: + - logger.log: "Joined network" + +output: + - platform: template + id: output_factory + type: binary + write_action: + - zigbee.factory_reset diff --git a/tests/components/zigbee/test.nrf52-adafruit.yaml b/tests/components/zigbee/test.nrf52-adafruit.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/zigbee/test.nrf52-adafruit.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/zigbee/test.nrf52-mcumgr.yaml b/tests/components/zigbee/test.nrf52-mcumgr.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/zigbee/test.nrf52-mcumgr.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/zigbee/test.nrf52-xiao-ble.yaml b/tests/components/zigbee/test.nrf52-xiao-ble.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/zigbee/test.nrf52-xiao-ble.yaml @@ -0,0 +1 @@ +<<: !include common.yaml From a011d5ea9628cf955111d34a70353c2aa40a963f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:14:57 -1000 Subject: [PATCH 1036/1145] [sht3xd] Combine log statements to reduce loop blocking (#12957) --- esphome/components/sht3xd/sht3xd.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index 79f1674020..d473df43c7 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -60,8 +60,10 @@ void SHT3XDComponent::dump_config() { ESP_LOGE(TAG, " Communication with SHT3xD failed!"); return; } - ESP_LOGD(TAG, " Serial Number: 0x%08" PRIX32, this->serial_number_); - ESP_LOGD(TAG, " Heater Enabled: %s", this->heater_enabled_ ? "true" : "false"); + ESP_LOGD(TAG, + " Serial Number: 0x%08" PRIX32 "\n" + " Heater Enabled: %s", + this->serial_number_, TRUEFALSE(this->heater_enabled_)); LOG_I2C_DEVICE(this); LOG_UPDATE_INTERVAL(this); From 2295f57dec2dc5fd31851f7faff0bb976d9d496c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:51:11 -1000 Subject: [PATCH 1037/1145] [st7567_i2c] Combine log statements to reduce loop blocking (#12975) --- esphome/components/st7567_i2c/st7567_i2c.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/st7567_i2c/st7567_i2c.cpp b/esphome/components/st7567_i2c/st7567_i2c.cpp index 14c21d5148..3214339571 100644 --- a/esphome/components/st7567_i2c/st7567_i2c.cpp +++ b/esphome/components/st7567_i2c/st7567_i2c.cpp @@ -20,14 +20,14 @@ void I2CST7567::setup() { void I2CST7567::dump_config() { LOG_DISPLAY("", "I2CST7567", this); - LOG_I2C_DEVICE(this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); - LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, + " Model: %s\n" " Mirror X: %s\n" " Mirror Y: %s\n" " Invert Colors: %s", - YESNO(this->mirror_x_), YESNO(this->mirror_y_), YESNO(this->invert_colors_)); + this->model_str_(), YESNO(this->mirror_x_), YESNO(this->mirror_y_), YESNO(this->invert_colors_)); + LOG_I2C_DEVICE(this); + LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_UPDATE_INTERVAL(this); if (this->error_code_ == COMMUNICATION_FAILED) { From 405b26426c4a7334c130ecf18c2c3b5e640bf139 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:51:24 -1000 Subject: [PATCH 1038/1145] [st7567_spi] Combine log statements to reduce loop blocking (#12976) --- esphome/components/st7567_spi/st7567_spi.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/st7567_spi/st7567_spi.cpp b/esphome/components/st7567_spi/st7567_spi.cpp index 813afcf682..7476fd7c8d 100644 --- a/esphome/components/st7567_spi/st7567_spi.cpp +++ b/esphome/components/st7567_spi/st7567_spi.cpp @@ -18,13 +18,15 @@ void SPIST7567::setup() { void SPIST7567::dump_config() { LOG_DISPLAY("", "SPI ST7567", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); + ESP_LOGCONFIG(TAG, + " Model: %s\n" + " Mirror X: %s\n" + " Mirror Y: %s\n" + " Invert Colors: %s", + this->model_str_(), YESNO(this->mirror_x_), YESNO(this->mirror_y_), YESNO(this->invert_colors_)); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " Mirror X: %s", YESNO(this->mirror_x_)); - ESP_LOGCONFIG(TAG, " Mirror Y: %s", YESNO(this->mirror_y_)); - ESP_LOGCONFIG(TAG, " Invert Colors: %s", YESNO(this->invert_colors_)); LOG_UPDATE_INTERVAL(this); } From 1d0f36ba35b7d0fc21e0bc2306580e915c2cbadc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:51:37 -1000 Subject: [PATCH 1039/1145] [st7789v] Combine log statements to reduce loop blocking (#12978) --- esphome/components/st7789v/st7789v.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/st7789v/st7789v.cpp b/esphome/components/st7789v/st7789v.cpp index ade9c1126f..cd0b6cabc3 100644 --- a/esphome/components/st7789v/st7789v.cpp +++ b/esphome/components/st7789v/st7789v.cpp @@ -127,15 +127,15 @@ void ST7789V::dump_config() { " Width: %u\n" " Height Offset: %u\n" " Width Offset: %u\n" - " 8-bit color mode: %s", + " 8-bit color mode: %s\n" + " Data rate: %dMHz", this->model_str_, this->height_, this->width_, this->offset_height_, this->offset_width_, - YESNO(this->eightbitcolor_)); + YESNO(this->eightbitcolor_), (unsigned) (this->data_rate_ / 1000000)); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" B/L Pin: ", this->backlight_pin_); LOG_UPDATE_INTERVAL(this); - ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); #ifdef USE_POWER_SUPPLY ESP_LOGCONFIG(TAG, " Power Supply Configured: yes"); #endif From 4bc1a02fc2a5e9f2a0e8dde600a552fb6a0ecfb5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:52:03 -1000 Subject: [PATCH 1040/1145] [shtcx] Combine log statements to reduce loop blocking (#12960) --- esphome/components/shtcx/shtcx.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index d532bd7f44..933dd9bde9 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -49,8 +49,10 @@ void SHTCXComponent::setup() { } void SHTCXComponent::dump_config() { - ESP_LOGCONFIG(TAG, "SHTCx:"); - ESP_LOGCONFIG(TAG, " Model: %s (%04x)", to_string(this->type_), this->sensor_id_); + ESP_LOGCONFIG(TAG, + "SHTCx:\n" + " Model: %s (%04x)", + to_string(this->type_), this->sensor_id_); LOG_I2C_DEVICE(this); if (this->is_failed()) { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); From c742db48b8857d684f6e291271a39cb8069f34f0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:52:57 -1000 Subject: [PATCH 1041/1145] [sim800l] Combine log statements to reduce loop blocking (#12961) --- esphome/components/sim800l/sim800l.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index 55cadcf182..e3edda0e72 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -323,8 +323,10 @@ void Sim800LComponent::parse_cmd_(std::string message) { kick ESPHome callback now */ if (ok || message.compare(0, 6, "+CMGL:") == 0) { - ESP_LOGD(TAG, "Received SMS from: %s", this->sender_.c_str()); - ESP_LOGD(TAG, "%s", this->message_.c_str()); + ESP_LOGD(TAG, + "Received SMS from: %s\n" + "%s", + this->sender_.c_str(), this->message_.c_str()); this->sms_received_callback_.call(this->message_, this->sender_); this->state_ = STATE_RECEIVED_SMS; } else { From 9128fc312076812f0a8f0b0b04f96956f4829441 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:54:03 -1000 Subject: [PATCH 1042/1145] [sm16716] Combine log statements to reduce loop blocking (#12962) --- esphome/components/sm16716/sm16716.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/sm16716/sm16716.cpp b/esphome/components/sm16716/sm16716.cpp index aa33b7b679..b8e293929b 100644 --- a/esphome/components/sm16716/sm16716.cpp +++ b/esphome/components/sm16716/sm16716.cpp @@ -14,11 +14,13 @@ void SM16716::setup() { this->pwm_amounts_.resize(this->num_channels_, 0); } void SM16716::dump_config() { - ESP_LOGCONFIG(TAG, "SM16716:"); + ESP_LOGCONFIG(TAG, + "SM16716:\n" + " Total number of channels: %u\n" + " Number of chips: %u", + this->num_channels_, this->num_chips_); LOG_PIN(" Data Pin: ", this->data_pin_); LOG_PIN(" Clock Pin: ", this->clock_pin_); - ESP_LOGCONFIG(TAG, " Total number of channels: %u", this->num_channels_); - ESP_LOGCONFIG(TAG, " Number of chips: %u", this->num_chips_); } void SM16716::loop() { if (!this->update_) From 47223965b6924afae80c40c0084a4ee2c9228245 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:54:17 -1000 Subject: [PATCH 1043/1145] [sm2135] Combine log statements to reduce loop blocking (#12963) --- esphome/components/sm2135/sm2135.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/sm2135/sm2135.cpp b/esphome/components/sm2135/sm2135.cpp index e55f836929..1293c3f321 100644 --- a/esphome/components/sm2135/sm2135.cpp +++ b/esphome/components/sm2135/sm2135.cpp @@ -34,11 +34,13 @@ void SM2135::setup() { } void SM2135::dump_config() { - ESP_LOGCONFIG(TAG, "SM2135:"); + ESP_LOGCONFIG(TAG, + "SM2135:\n" + " CW Current: %dmA\n" + " RGB Current: %dmA", + 10 + (this->cw_current_ * 5), 10 + (this->rgb_current_ * 5)); LOG_PIN(" Data Pin: ", this->data_pin_); LOG_PIN(" Clock Pin: ", this->clock_pin_); - ESP_LOGCONFIG(TAG, " CW Current: %dmA", 10 + (this->cw_current_ * 5)); - ESP_LOGCONFIG(TAG, " RGB Current: %dmA", 10 + (this->rgb_current_ * 5)); } void SM2135::write_byte_(uint8_t data) { From f67a8d0d1fa9f5d10149f1be76f417d21cb45c68 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:55:11 -1000 Subject: [PATCH 1044/1145] [sonoff_d1] Combine log statements to reduce loop blocking (#12966) --- esphome/components/sonoff_d1/sonoff_d1.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/sonoff_d1/sonoff_d1.cpp b/esphome/components/sonoff_d1/sonoff_d1.cpp index 0ecde83b8b..7b99086546 100644 --- a/esphome/components/sonoff_d1/sonoff_d1.cpp +++ b/esphome/components/sonoff_d1/sonoff_d1.cpp @@ -93,8 +93,10 @@ bool SonoffD1Output::read_command_(uint8_t *cmd, size_t &len) { if (this->read_array(cmd, 6)) { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE char hex_buf[format_hex_pretty_size(6)]; - ESP_LOGV(TAG, "[%04d] Reading from dimmer:", this->write_count_); - ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf, cmd, 6)); + ESP_LOGV(TAG, + "[%04d] Reading from dimmer:\n" + "[%04d] %s", + this->write_count_, this->write_count_, format_hex_pretty_to(hex_buf, cmd, 6)); #endif if (cmd[0] != 0xAA || cmd[1] != 0x55) { @@ -188,8 +190,10 @@ bool SonoffD1Output::write_command_(uint8_t *cmd, const size_t len, bool needs_a do { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE char hex_buf[format_hex_pretty_size(SONOFF_D1_MAX_CMD_SIZE)]; - ESP_LOGV(TAG, "[%04d] Writing to the dimmer:", this->write_count_); - ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf, cmd, len)); + ESP_LOGV(TAG, + "[%04d] Writing to the dimmer:\n" + "[%04d] %s", + this->write_count_, this->write_count_, format_hex_pretty_to(hex_buf, cmd, len)); #endif this->write_array(cmd, len); this->write_count_++; From 9cd003034c1324b631d562deb9ef0d82153df0c7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:55:31 -1000 Subject: [PATCH 1045/1145] [spi_device] Combine log statements to reduce loop blocking (#12967) --- esphome/components/spi_device/spi_device.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/spi_device/spi_device.cpp b/esphome/components/spi_device/spi_device.cpp index dbfbc9eccb..4cc7286ba9 100644 --- a/esphome/components/spi_device/spi_device.cpp +++ b/esphome/components/spi_device/spi_device.cpp @@ -11,9 +11,11 @@ static const char *const TAG = "spi_device"; void SPIDeviceComponent::setup() { this->spi_setup(); } void SPIDeviceComponent::dump_config() { - ESP_LOGCONFIG(TAG, "SPIDevice"); + ESP_LOGCONFIG(TAG, + "SPIDevice\n" + " Mode: %d", + this->mode_); LOG_PIN(" CS pin: ", this->cs_); - ESP_LOGCONFIG(TAG, " Mode: %d", this->mode_); if (this->data_rate_ < 1000000) { ESP_LOGCONFIG(TAG, " Data rate: %" PRId32 "kHz", this->data_rate_ / 1000); } else { From ae3cdeda99f9803a7916e4a27890af7222fdb74f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:55:55 -1000 Subject: [PATCH 1046/1145] [ssd1325_spi] Combine log statements to reduce loop blocking (#12972) --- esphome/components/ssd1325_spi/ssd1325_spi.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/ssd1325_spi/ssd1325_spi.cpp b/esphome/components/ssd1325_spi/ssd1325_spi.cpp index 3c9dfd3324..07a5119d8f 100644 --- a/esphome/components/ssd1325_spi/ssd1325_spi.cpp +++ b/esphome/components/ssd1325_spi/ssd1325_spi.cpp @@ -19,12 +19,14 @@ void SPISSD1325::setup() { } void SPISSD1325::dump_config() { LOG_DISPLAY("", "SPI SSD1325", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); + ESP_LOGCONFIG(TAG, + " Model: %s\n" + " Initial Brightness: %.2f\n" + " External VCC: %s", + this->model_str_(), this->brightness_, YESNO(this->external_vcc_)); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " Initial Brightness: %.2f", this->brightness_); - ESP_LOGCONFIG(TAG, " External VCC: %s", YESNO(this->external_vcc_)); LOG_UPDATE_INTERVAL(this); } void SPISSD1325::command(uint8_t value) { From d8387799d9dfb9fc1d7d09a48f990e1fa8e08d3d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:56:30 -1000 Subject: [PATCH 1047/1145] [sm2335] Combine log statements to reduce loop blocking (#12965) --- esphome/components/sm2335/sm2335.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/sm2335/sm2335.cpp b/esphome/components/sm2335/sm2335.cpp index 0580a782f5..f860517021 100644 --- a/esphome/components/sm2335/sm2335.cpp +++ b/esphome/components/sm2335/sm2335.cpp @@ -15,13 +15,13 @@ void SM2335::setup() { } void SM2335::dump_config() { - ESP_LOGCONFIG(TAG, "sm2335:"); - LOG_PIN(" Data Pin: ", this->data_pin_); - LOG_PIN(" Clock Pin: ", this->clock_pin_); ESP_LOGCONFIG(TAG, + "sm2335:\n" " Color Channels Max Power: %u\n" " White Channels Max Power: %u", this->max_power_color_channels_, this->max_power_white_channels_); + LOG_PIN(" Data Pin: ", this->data_pin_); + LOG_PIN(" Clock Pin: ", this->clock_pin_); } } // namespace sm2335 From a2bb9468ff3fa6c60498884d58b7c98c09e624c4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:57:43 -1000 Subject: [PATCH 1048/1145] [sm2235] Combine log statements to reduce loop blocking (#12964) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/sm2235/sm2235.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/sm2235/sm2235.cpp b/esphome/components/sm2235/sm2235.cpp index 820fcb521a..4476862318 100644 --- a/esphome/components/sm2235/sm2235.cpp +++ b/esphome/components/sm2235/sm2235.cpp @@ -15,13 +15,13 @@ void SM2235::setup() { } void SM2235::dump_config() { - ESP_LOGCONFIG(TAG, "sm2235:"); - LOG_PIN(" Data Pin: ", this->data_pin_); - LOG_PIN(" Clock Pin: ", this->clock_pin_); ESP_LOGCONFIG(TAG, + "SM2235:\n" " Color Channels Max Power: %u\n" " White Channels Max Power: %u", this->max_power_color_channels_, this->max_power_white_channels_); + LOG_PIN(" Data Pin: ", this->data_pin_); + LOG_PIN(" Clock Pin: ", this->clock_pin_); } } // namespace sm2235 From ed332a034b87e09fb041965cec0f359edab6cd3b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:59:36 -1000 Subject: [PATCH 1049/1145] [ssd1351_spi] Combine log statements to reduce loop blocking (#12974) --- esphome/components/ssd1351_spi/ssd1351_spi.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/ssd1351_spi/ssd1351_spi.cpp b/esphome/components/ssd1351_spi/ssd1351_spi.cpp index ffac07b82b..b046f0adcb 100644 --- a/esphome/components/ssd1351_spi/ssd1351_spi.cpp +++ b/esphome/components/ssd1351_spi/ssd1351_spi.cpp @@ -19,11 +19,13 @@ void SPISSD1351::setup() { } void SPISSD1351::dump_config() { LOG_DISPLAY("", "SPI SSD1351", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); + ESP_LOGCONFIG(TAG, + " Model: %s\n" + " Initial Brightness: %.2f", + this->model_str_(), this->brightness_); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " Initial Brightness: %.2f", this->brightness_); LOG_UPDATE_INTERVAL(this); } void SPISSD1351::command(uint8_t value) { From 06101c54a5ff2b72efd3dbd1045ff080b83f1bc6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 16:59:52 -1000 Subject: [PATCH 1050/1145] [ssd1327_spi] Combine log statements to reduce loop blocking (#12973) --- esphome/components/ssd1327_spi/ssd1327_spi.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/ssd1327_spi/ssd1327_spi.cpp b/esphome/components/ssd1327_spi/ssd1327_spi.cpp index c26238ae19..54d1a51100 100644 --- a/esphome/components/ssd1327_spi/ssd1327_spi.cpp +++ b/esphome/components/ssd1327_spi/ssd1327_spi.cpp @@ -19,11 +19,13 @@ void SPISSD1327::setup() { } void SPISSD1327::dump_config() { LOG_DISPLAY("", "SPI SSD1327", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); + ESP_LOGCONFIG(TAG, + " Model: %s\n" + " Initial Brightness: %.2f", + this->model_str_(), this->brightness_); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " Initial Brightness: %.2f", this->brightness_); LOG_UPDATE_INTERVAL(this); } void SPISSD1327::command(uint8_t value) { From 2381ea7ff5a62b41e416fbdb8dc363bdcb3dc17a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 17:00:09 -1000 Subject: [PATCH 1051/1145] [ssd1322_spi] Combine log statements to reduce loop blocking (#12971) --- esphome/components/ssd1322_spi/ssd1322_spi.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/ssd1322_spi/ssd1322_spi.cpp b/esphome/components/ssd1322_spi/ssd1322_spi.cpp index 6a8918353b..bc7d298922 100644 --- a/esphome/components/ssd1322_spi/ssd1322_spi.cpp +++ b/esphome/components/ssd1322_spi/ssd1322_spi.cpp @@ -19,11 +19,13 @@ void SPISSD1322::setup() { } void SPISSD1322::dump_config() { LOG_DISPLAY("", "SPI SSD1322", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); + ESP_LOGCONFIG(TAG, + " Model: %s\n" + " Initial Brightness: %.2f", + this->model_str_(), this->brightness_); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " Initial Brightness: %.2f", this->brightness_); LOG_UPDATE_INTERVAL(this); } void SPISSD1322::command(uint8_t value) { From 0bd8a7e1a02c8d1960b77031f2a3c1434daf83a6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 17:00:21 -1000 Subject: [PATCH 1052/1145] [ssd1306_spi] Combine log statements to reduce loop blocking (#12970) --- esphome/components/ssd1306_spi/ssd1306_spi.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/ssd1306_spi/ssd1306_spi.cpp b/esphome/components/ssd1306_spi/ssd1306_spi.cpp index d93742c0e5..db28dfc564 100644 --- a/esphome/components/ssd1306_spi/ssd1306_spi.cpp +++ b/esphome/components/ssd1306_spi/ssd1306_spi.cpp @@ -16,19 +16,19 @@ void SPISSD1306::setup() { } void SPISSD1306::dump_config() { LOG_DISPLAY("", "SPI SSD1306", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); - LOG_PIN(" CS Pin: ", this->cs_); - LOG_PIN(" DC Pin: ", this->dc_pin_); - LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, + " Model: %s\n" " External VCC: %s\n" " Flip X: %s\n" " Flip Y: %s\n" " Offset X: %d\n" " Offset Y: %d\n" " Inverted Color: %s", - YESNO(this->external_vcc_), YESNO(this->flip_x_), YESNO(this->flip_y_), this->offset_x_, - this->offset_y_, YESNO(this->invert_)); + this->model_str_(), YESNO(this->external_vcc_), YESNO(this->flip_x_), YESNO(this->flip_y_), + this->offset_x_, this->offset_y_, YESNO(this->invert_)); + LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_UPDATE_INTERVAL(this); } void SPISSD1306::command(uint8_t value) { From 28d30fdddbdd3272ac60f7395f55552be7ac688b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 17:00:40 -1000 Subject: [PATCH 1053/1145] [ssd1306_i2c] Combine log statements to reduce loop blocking (#12969) --- esphome/components/ssd1306_i2c/ssd1306_i2c.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp index 8e490834bc..ab6fee7b02 100644 --- a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +++ b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp @@ -20,18 +20,18 @@ void I2CSSD1306::setup() { } void I2CSSD1306::dump_config() { LOG_DISPLAY("", "I2C SSD1306", this); - LOG_I2C_DEVICE(this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); - LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, + " Model: %s\n" " External VCC: %s\n" " Flip X: %s\n" " Flip Y: %s\n" " Offset X: %d\n" " Offset Y: %d\n" " Inverted Color: %s", - YESNO(this->external_vcc_), YESNO(this->flip_x_), YESNO(this->flip_y_), this->offset_x_, - this->offset_y_, YESNO(this->invert_)); + this->model_str_(), YESNO(this->external_vcc_), YESNO(this->flip_x_), YESNO(this->flip_y_), + this->offset_x_, this->offset_y_, YESNO(this->invert_)); + LOG_I2C_DEVICE(this); + LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_UPDATE_INTERVAL(this); if (this->error_code_ == COMMUNICATION_FAILED) { From 80ab9485e00c2ea9debe20ccfc5b3290bafe4cf0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 17:00:59 -1000 Subject: [PATCH 1054/1145] [spi_led_strip] Combine log statements to reduce loop blocking (#12968) --- esphome/components/spi_led_strip/spi_led_strip.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/spi_led_strip/spi_led_strip.cpp b/esphome/components/spi_led_strip/spi_led_strip.cpp index 85c10ee87d..afb51afe3a 100644 --- a/esphome/components/spi_led_strip/spi_led_strip.cpp +++ b/esphome/components/spi_led_strip/spi_led_strip.cpp @@ -34,8 +34,10 @@ light::LightTraits SpiLedStrip::get_traits() { return traits; } void SpiLedStrip::dump_config() { - esph_log_config(TAG, "SPI LED Strip:"); - esph_log_config(TAG, " LEDs: %d", this->num_leds_); + esph_log_config(TAG, + "SPI LED Strip:\n" + " LEDs: %d", + this->num_leds_); if (this->data_rate_ >= spi::DATA_RATE_1MHZ) { esph_log_config(TAG, " Data rate: %uMHz", (unsigned) (this->data_rate_ / 1000000)); } else { From d107b37d3b6534b5aa3dc5b16d9bd3587c1c7fef Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Jan 2026 17:51:02 -1000 Subject: [PATCH 1055/1145] [st7735] Combine log statements to reduce loop blocking (#12977) --- esphome/components/st7735/st7735.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index 160ba151f7..1a74b5ce1e 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -373,15 +373,18 @@ void ST7735::display_init_(const uint8_t *addr) { void ST7735::dump_config() { LOG_DISPLAY("", "ST7735", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGD(TAG, " Buffer Size: %zu", this->get_buffer_length()); - ESP_LOGD(TAG, " Height: %d", this->height_); - ESP_LOGD(TAG, " Width: %d", this->width_); - ESP_LOGD(TAG, " ColStart: %d", this->colstart_); - ESP_LOGD(TAG, " RowStart: %d", this->rowstart_); + ESP_LOGCONFIG(TAG, + " Model: %s\n" + " Buffer Size: %zu\n" + " Height: %d\n" + " Width: %d\n" + " ColStart: %d\n" + " RowStart: %d", + this->model_str_(), this->get_buffer_length(), this->height_, this->width_, this->colstart_, + this->rowstart_); LOG_UPDATE_INTERVAL(this); } From 086eb4b93017b7030e22f9c753671093e1dbf308 Mon Sep 17 00:00:00 2001 From: Samuel Schultze Date: Mon, 5 Jan 2026 13:45:32 -0300 Subject: [PATCH 1056/1145] [whirlpool] support for 14 byte whirlpool IR receiver messages (#12774) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/whirlpool/whirlpool.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/esphome/components/whirlpool/whirlpool.cpp b/esphome/components/whirlpool/whirlpool.cpp index 1ac32f30da..6fe735362d 100644 --- a/esphome/components/whirlpool/whirlpool.cpp +++ b/esphome/components/whirlpool/whirlpool.cpp @@ -163,6 +163,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { } uint8_t remote_state[WHIRLPOOL_STATE_LENGTH] = {0}; + bool skip_footer = false; // Read all bytes. for (int i = 0; i < WHIRLPOOL_STATE_LENGTH; i++) { // Read bit @@ -170,6 +171,13 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { if (!data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_GAP)) return false; } + if (i == 14 && !data.is_valid()) { + // Remote control only sent 14 bytes, nothing more to read, not even the footer + ESP_LOGV(TAG, "Remote control only sent %d bytes", i); + skip_footer = true; + break; + } + for (int j = 0; j < 8; j++) { if (data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ONE_SPACE)) { remote_state[i] |= 1 << j; @@ -183,7 +191,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { ESP_LOGVV(TAG, "Byte %d %02X", i, remote_state[i]); } // Validate footer - if (!data.expect_mark(WHIRLPOOL_BIT_MARK)) { + if (!data.expect_mark(WHIRLPOOL_BIT_MARK) && !skip_footer) { ESP_LOGV(TAG, "Footer fail"); return false; } @@ -196,7 +204,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { for (uint8_t i = 14; i < 20; i++) checksum20 ^= remote_state[i]; - if (checksum13 != remote_state[13] || checksum20 != remote_state[20]) { + if (checksum13 != remote_state[13] || (!skip_footer && checksum20 != remote_state[20])) { ESP_LOGVV(TAG, "Checksum fail"); return false; } From 0990a9c2b0515ce46f7fb477d64615409309885e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 07:39:24 -1000 Subject: [PATCH 1057/1145] [esp32_ble] Avoid heap allocation in ESPBTUUID::from_raw for string literals (#12980) --- esphome/components/alpha3/alpha3.h | 6 ++--- esphome/components/esp32_ble/ble_uuid.cpp | 28 +++++++++++------------ esphome/components/esp32_ble/ble_uuid.h | 8 ++++++- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/esphome/components/alpha3/alpha3.h b/esphome/components/alpha3/alpha3.h index 7189ecbc33..19d8e99331 100644 --- a/esphome/components/alpha3/alpha3.h +++ b/esphome/components/alpha3/alpha3.h @@ -15,10 +15,8 @@ namespace alpha3 { namespace espbt = esphome::esp32_ble_tracker; static const espbt::ESPBTUUID ALPHA3_GENI_SERVICE_UUID = espbt::ESPBTUUID::from_uint16(0xfe5d); -static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID = - espbt::ESPBTUUID::from_raw({static_cast(0xa9), 0x7b, static_cast(0xb8), static_cast(0x85), 0x0, - 0x1a, 0x28, static_cast(0xaa), 0x2a, 0x43, 0x6e, 0x3, static_cast(0xd1), - static_cast(0xff), static_cast(0x9c), static_cast(0x85)}); +static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID = espbt::ESPBTUUID::from_raw( + {0xa9, 0x7b, 0xb8, 0x85, 0x00, 0x1a, 0x28, 0xaa, 0x2a, 0x43, 0x6e, 0x03, 0xd1, 0xff, 0x9c, 0x85}); static const int16_t GENI_RESPONSE_HEADER_LENGTH = 13; static const size_t GENI_RESPONSE_TYPE_LENGTH = 8; diff --git a/esphome/components/esp32_ble/ble_uuid.cpp b/esphome/components/esp32_ble/ble_uuid.cpp index c6b27f3bb9..7bad8d1866 100644 --- a/esphome/components/esp32_ble/ble_uuid.cpp +++ b/esphome/components/esp32_ble/ble_uuid.cpp @@ -39,36 +39,36 @@ ESPBTUUID ESPBTUUID::from_raw_reversed(const uint8_t *data) { ret.uuid_.uuid.uuid128[ESP_UUID_LEN_128 - 1 - i] = data[i]; return ret; } -ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { +ESPBTUUID ESPBTUUID::from_raw(const char *data, size_t length) { ESPBTUUID ret; - if (data.length() == 4) { + if (length == 4) { // 16-bit UUID as 4-character hex string - auto parsed = parse_hex(data); + auto parsed = parse_hex(data, length); if (parsed.has_value()) { ret.uuid_.len = ESP_UUID_LEN_16; ret.uuid_.uuid.uuid16 = parsed.value(); } - } else if (data.length() == 8) { + } else if (length == 8) { // 32-bit UUID as 8-character hex string - auto parsed = parse_hex(data); + auto parsed = parse_hex(data, length); if (parsed.has_value()) { ret.uuid_.len = ESP_UUID_LEN_32; ret.uuid_.uuid.uuid32 = parsed.value(); } - } else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be - // investigated (lack of time) + } else if (length == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be + // investigated (lack of time) ret.uuid_.len = ESP_UUID_LEN_128; - memcpy(ret.uuid_.uuid.uuid128, (uint8_t *) data.data(), 16); - } else if (data.length() == 36) { + memcpy(ret.uuid_.uuid.uuid128, reinterpret_cast(data), 16); + } else if (length == 36) { // If the length of the string is 36 bytes then we will assume it is a long hex string in // UUID format. ret.uuid_.len = ESP_UUID_LEN_128; int n = 0; - for (uint i = 0; i < data.length(); i += 2) { - if (data.c_str()[i] == '-') + for (size_t i = 0; i < length; i += 2) { + if (data[i] == '-') i++; - uint8_t msb = data.c_str()[i]; - uint8_t lsb = data.c_str()[i + 1]; + uint8_t msb = data[i]; + uint8_t lsb = data[i + 1]; if (msb > '9') msb -= 7; @@ -77,7 +77,7 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F); } } else { - ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data.c_str()); + ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data); } return ret; } diff --git a/esphome/components/esp32_ble/ble_uuid.h b/esphome/components/esp32_ble/ble_uuid.h index ed561d70e4..2d8c69e44e 100644 --- a/esphome/components/esp32_ble/ble_uuid.h +++ b/esphome/components/esp32_ble/ble_uuid.h @@ -7,6 +7,7 @@ #ifdef USE_ESP32 #ifdef USE_ESP32_BLE_UUID +#include #include #include #include @@ -27,7 +28,12 @@ class ESPBTUUID { static ESPBTUUID from_raw(const uint8_t *data); static ESPBTUUID from_raw_reversed(const uint8_t *data); - static ESPBTUUID from_raw(const std::string &data); + static ESPBTUUID from_raw(const char *data, size_t length); + static ESPBTUUID from_raw(const char *data) { return from_raw(data, strlen(data)); } + static ESPBTUUID from_raw(const std::string &data) { return from_raw(data.c_str(), data.length()); } + static ESPBTUUID from_raw(std::initializer_list data) { + return from_raw(reinterpret_cast(data.begin()), data.size()); + } static ESPBTUUID from_uuid(esp_bt_uuid_t uuid); From 1bb4be435c6b9c384a5e9a77f566053aac18f08f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 07:39:37 -1000 Subject: [PATCH 1058/1145] [esp32_ble_tracker, ble_client] Reduce heap allocations with stack-based string formatting (#12982) --- esphome/components/ble_client/automation.h | 10 ++++-- .../ble_client/output/ble_binary_output.cpp | 32 ++++++++++++------- .../ble_client/sensor/ble_sensor.cpp | 23 +++++++++---- .../text_sensor/ble_text_sensor.cpp | 20 ++++++++---- esphome/components/esp32_ble/ble_uuid.cpp | 8 ++--- esphome/components/esp32_ble/ble_uuid.h | 2 +- .../esp32_ble_client/ble_client_base.cpp | 2 +- .../esp32_ble_client/ble_client_base.h | 5 ++- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 8 ++--- .../esp32_ble_tracker/esp32_ble_tracker.h | 7 ++++ 10 files changed, 77 insertions(+), 40 deletions(-) diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index f9f613ae76..01590d1d53 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -186,8 +186,10 @@ template class BLEClientWriteAction : public Action, publ case ESP_GATTC_SEARCH_CMPL_EVT: { auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { + char char_buf[esp32_ble::UUID_STR_LEN]; + char service_buf[esp32_ble::UUID_STR_LEN]; esph_log_w("ble_write_action", "Characteristic %s was not found in service %s", - this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str()); + this->char_uuid_.to_str(char_buf), this->service_uuid_.to_str(service_buf)); break; } this->char_handle_ = chr->handle; @@ -199,11 +201,13 @@ template class BLEClientWriteAction : public Action, publ this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP; esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP"); } else { - esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str()); + char char_buf[esp32_ble::UUID_STR_LEN]; + esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_str(char_buf)); break; } this->node_state = espbt::ClientState::ESTABLISHED; - esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), + char char_buf[esp32_ble::UUID_STR_LEN]; + esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf), ble_client_->address_str()); break; } diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp index 1d874a65e4..1cb83b9d8b 100644 --- a/esphome/components/ble_client/output/ble_binary_output.cpp +++ b/esphome/components/ble_client/output/ble_binary_output.cpp @@ -9,12 +9,15 @@ static const char *const TAG = "ble_binary_output"; void BLEBinaryOutput::dump_config() { ESP_LOGCONFIG(TAG, "BLE Binary Output:"); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + this->service_uuid_.to_str(service_buf); + this->char_uuid_.to_str(char_buf); ESP_LOGCONFIG(TAG, " MAC address : %s\n" " Service UUID : %s\n" " Characteristic UUID: %s", - this->parent_->address_str(), this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str()); + this->parent_->address_str(), service_buf, char_buf); LOG_BINARY_OUTPUT(this); } @@ -24,8 +27,10 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i case ESP_GATTC_SEARCH_CMPL_EVT: { auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { - ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_string().c_str(), - this->service_uuid_.to_string().c_str()); + char char_buf[esp32_ble::UUID_STR_LEN]; + char service_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_str(char_buf), + this->service_uuid_.to_str(service_buf)); break; } this->char_handle_ = chr->handle; @@ -37,20 +42,24 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP; ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP"); } else { - ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_string().c_str(), + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_str(char_buf), this->require_response_ ? "" : "out"); break; } this->node_state = espbt::ClientState::ESTABLISHED; - ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf), this->parent()->address_str()); this->node_state = espbt::ClientState::ESTABLISHED; break; } case ESP_GATTC_WRITE_CHAR_EVT: { if (param->write.handle == this->char_handle_) { - if (param->write.status != 0) - ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status); + if (param->write.status != 0) { + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_str(char_buf), param->write.status); + } } break; } @@ -60,18 +69,19 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i } void BLEBinaryOutput::write_state(bool state) { + char char_buf[esp32_ble::UUID_STR_LEN]; if (this->node_state != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.", - this->char_uuid_.to_string().c_str()); + this->char_uuid_.to_str(char_buf)); return; } uint8_t state_as_uint = (uint8_t) state; - ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint); + ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_str(char_buf), state_as_uint); esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, sizeof(state_as_uint), &state_as_uint, this->write_type_, ESP_GATT_AUTH_REQ_NONE); if (err != ESP_GATT_OK) - ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_string().c_str(), err); + ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_str(char_buf), err); } } // namespace esphome::ble_client diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index 38d90faff0..fe5f11bbc2 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -18,14 +18,17 @@ void BLESensor::loop() { void BLESensor::dump_config() { LOG_SENSOR("", "BLE Sensor", this); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + char descr_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGCONFIG(TAG, " MAC address : %s\n" " Service UUID : %s\n" " Characteristic UUID: %s\n" " Descriptor UUID : %s\n" " Notifications : %s", - this->parent()->address_str(), this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_)); + this->parent()->address_str(), this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf), this->descr_uuid_.to_str(descr_buf), YESNO(this->notify_)); LOG_UPDATE_INTERVAL(this); } @@ -51,8 +54,10 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga if (chr == nullptr) { this->status_set_warning(); this->publish_state(NAN); - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str()); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf)); break; } this->handle = chr->handle; @@ -61,9 +66,12 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga if (descr == nullptr) { this->status_set_warning(); this->publish_state(NAN); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + char descr_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s", - this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(), - this->descr_uuid_.to_string().c_str()); + this->service_uuid_.to_str(service_buf), this->char_uuid_.to_str(char_buf), + this->descr_uuid_.to_str(descr_buf)); break; } this->handle = descr->handle; @@ -109,7 +117,8 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } this->node_state = espbt::ClientState::ESTABLISHED; - ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_string().c_str()); + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_str(char_buf)); } break; } diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index 415981a1ba..53c9a9d10e 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -21,14 +21,17 @@ void BLETextSensor::loop() { void BLETextSensor::dump_config() { LOG_TEXT_SENSOR("", "BLE Text Sensor", this); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + char descr_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGCONFIG(TAG, " MAC address : %s\n" " Service UUID : %s\n" " Characteristic UUID: %s\n" " Descriptor UUID : %s\n" " Notifications : %s", - this->parent()->address_str(), this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_)); + this->parent()->address_str(), this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf), this->descr_uuid_.to_str(descr_buf), YESNO(this->notify_)); LOG_UPDATE_INTERVAL(this); } @@ -53,8 +56,10 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ if (chr == nullptr) { this->status_set_warning(); this->publish_state(EMPTY); - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str()); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf)); break; } this->handle = chr->handle; @@ -63,9 +68,12 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ if (descr == nullptr) { this->status_set_warning(); this->publish_state(EMPTY); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + char descr_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s", - this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(), - this->descr_uuid_.to_string().c_str()); + this->service_uuid_.to_str(service_buf), this->char_uuid_.to_str(char_buf), + this->descr_uuid_.to_str(descr_buf)); break; } this->handle = descr->handle; diff --git a/esphome/components/esp32_ble/ble_uuid.cpp b/esphome/components/esp32_ble/ble_uuid.cpp index 7bad8d1866..334780e3b8 100644 --- a/esphome/components/esp32_ble/ble_uuid.cpp +++ b/esphome/components/esp32_ble/ble_uuid.cpp @@ -143,7 +143,7 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const { return this->as_128bit() == uuid.as_128bit(); } esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; } -void ESPBTUUID::to_str(std::span output) const { +const char *ESPBTUUID::to_str(std::span output) const { char *pos = output.data(); switch (this->uuid_.len) { @@ -155,7 +155,7 @@ void ESPBTUUID::to_str(std::span output) const { *pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid16 >> 4) & 0x0F); *pos++ = format_hex_pretty_char(this->uuid_.uuid.uuid16 & 0x0F); *pos = '\0'; - return; + return output.data(); case ESP_UUID_LEN_32: *pos++ = '0'; @@ -164,7 +164,7 @@ void ESPBTUUID::to_str(std::span output) const { *pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid32 >> shift) & 0x0F); } *pos = '\0'; - return; + return output.data(); default: case ESP_UUID_LEN_128: @@ -178,7 +178,7 @@ void ESPBTUUID::to_str(std::span output) const { } } *pos = '\0'; - return; + return output.data(); } } std::string ESPBTUUID::to_string() const { diff --git a/esphome/components/esp32_ble/ble_uuid.h b/esphome/components/esp32_ble/ble_uuid.h index 2d8c69e44e..ae593955a4 100644 --- a/esphome/components/esp32_ble/ble_uuid.h +++ b/esphome/components/esp32_ble/ble_uuid.h @@ -47,7 +47,7 @@ class ESPBTUUID { esp_bt_uuid_t get_uuid() const; std::string to_string() const; - void to_str(std::span output) const; + const char *to_str(std::span output) const; protected: esp_bt_uuid_t uuid_; diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 26eb5dd092..149fcc79d5 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -529,7 +529,7 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ case ESP_GAP_BLE_AUTH_CMPL_EVT: if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr)) return; - char addr_str[MAC_ADDR_STR_LEN]; + char addr_str[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; format_mac_addr_upper(param->ble_security.auth_cmpl.bd_addr, addr_str); ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_, addr_str); if (!param->ble_security.auth_cmpl.success) { diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index 7786495915..92c7444ee1 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -22,7 +22,6 @@ namespace esphome::esp32_ble_client { namespace espbt = esphome::esp32_ble_tracker; static const int UNSET_CONN_ID = 0xFFFF; -static constexpr size_t MAC_ADDR_STR_LEN = 18; // "AA:BB:CC:DD:EE:FF\0" class BLEClientBase : public espbt::ESPBTClient, public Component { public: @@ -111,8 +110,8 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { esp_gatt_status_t status_{ESP_GATT_OK}; // Group 4: Arrays - char address_str_[MAC_ADDR_STR_LEN]{}; // 18 bytes: "AA:BB:CC:DD:EE:FF\0" - esp_bd_addr_t remote_bda_; // 6 bytes + char address_str_[MAC_ADDRESS_PRETTY_BUFFER_SIZE]{}; + esp_bd_addr_t remote_bda_; // 6 bytes // Group 5: 2-byte types uint16_t conn_id_{UNSET_CONN_ID}; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 73a5dfb187..995755ac84 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -639,9 +639,8 @@ void ESPBTDevice::parse_adv_(const uint8_t *payload, uint8_t len) { } std::string ESPBTDevice::address_str() const { - char mac[18]; - format_mac_addr_upper(this->address_, mac); - return mac; + char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + return this->address_str_to(buf); } uint64_t ESPBTDevice::address_uint64() const { return esp32_ble::ble_addr_to_uint64(this->address_); } @@ -676,7 +675,8 @@ void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { } this->already_discovered_.push_back(address); - ESP_LOGD(TAG, "Found device %s RSSI=%d", device.address_str().c_str(), device.get_rssi()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGD(TAG, "Found device %s RSSI=%d", device.address_str_to(addr_buf), device.get_rssi()); const char *address_type_s; switch (device.get_address_type()) { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index b64e36279c..f538a0eddc 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -6,6 +6,7 @@ #include "esphome/core/helpers.h" #include +#include #include #include @@ -73,6 +74,12 @@ class ESPBTDevice { std::string address_str() const; + /// Format MAC address into provided buffer, returns pointer to buffer for convenience + const char *address_str_to(std::span buf) const { + format_mac_addr_upper(this->address_, buf.data()); + return buf.data(); + } + uint64_t address_uint64() const; const uint8_t *address() const { return address_; } From 3fb5b289309a662db2c2ac26852ca01317480df9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 07:40:04 -1000 Subject: [PATCH 1059/1145] [captive_portal] Avoid defer overhead on ESP8266 when saving WiFi credentials (#12981) --- esphome/components/captive_portal/captive_portal.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index d0515166b6..5ba70bcc50 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -54,8 +54,13 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { " SSID='%s'\n" " Password=" LOG_SECRET("'%s'"), ssid.c_str(), psk.c_str()); +#ifdef USE_ESP8266 + // ESP8266 is single-threaded, call directly + wifi::global_wifi_component->save_wifi_sta(ssid, psk); +#else // Defer save to main loop thread to avoid NVS operations from HTTP thread this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid, psk); }); +#endif request->redirect(ESPHOME_F("/?save")); } From e87a3b39160c29fc622df614cdea35794b026bb7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 07:40:24 -1000 Subject: [PATCH 1060/1145] [light] Use zero-copy set_effect overload in JSON schema parsing (#12979) --- esphome/components/light/light_json_schema.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 7679002e74..98b03f9458 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -160,7 +160,7 @@ void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject if (root[ESPHOME_F("effect")].is()) { const char *effect = root[ESPHOME_F("effect")]; - call.set_effect(effect); + call.set_effect(effect, strlen(effect)); } if (root[ESPHOME_F("effect_index")].is()) { From 6aaaae5d0e81a177c4b692d71d5427a672b3a4ca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 07:40:49 -1000 Subject: [PATCH 1061/1145] [ci] Add LibreTiny (BK72XX) to memory impact analysis (#12983) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/analyze_memory/const.py | 88 +++++++++++++++++++++++++++++- script/ci_memory_impact_extract.py | 9 ++- script/determine-jobs.py | 14 +++-- 3 files changed, 103 insertions(+), 8 deletions(-) diff --git a/esphome/analyze_memory/const.py b/esphome/analyze_memory/const.py index 78af82059f..8dd6664bc0 100644 --- a/esphome/analyze_memory/const.py +++ b/esphome/analyze_memory/const.py @@ -88,6 +88,77 @@ SYMBOL_PATTERNS = { "sys_mbox_new", "sys_arch_mbox_tryfetch", ], + # LibreTiny/Beken BK7231 radio calibration + "bk_radio_cal": [ + "bk7011_", + "calibration_main", + "gcali_", + "rwnx_cal", + ], + # LibreTiny/Beken WiFi MAC layer + "bk_wifi_mac": [ + "rxu_", # RX upper layer + "txu_", # TX upper layer + "txl_", # TX lower layer + "rxl_", # RX lower layer + "scanu_", # Scan unit + "mm_hw_", # MAC management hardware + "mm_bcn", # MAC management beacon + "mm_tim", # MAC management TIM + "mm_check", # MAC management checks + "sm_connect", # Station management + "me_beacon", # Management entity beacon + "me_build", # Management entity build + "hapd_", # Host AP daemon + "chan_pre_", # Channel management + "handle_probe_", # Probe handling + ], + # LibreTiny/Beken system control + "bk_system": [ + "sctrl_", # System control + "icu_ctrl", # Interrupt control unit + "gdma_ctrl", # DMA control + "mpb_ctrl", # MPB control + "uf2_", # UF2 OTA + "bkreg_", # Beken registers + ], + # LibreTiny/Beken BLE stack + "bk_ble": [ + "gapc_", # GAP client + "gattc_", # GATT client + "attc_", # ATT client + "attmdb_", # ATT database + "atts_", # ATT server + "l2cc_", # L2CAP + "prf_env", # Profile environment + ], + # LibreTiny/Beken scheduler + "bk_scheduler": [ + "sch_plan_", # Scheduler plan + "sch_prog_", # Scheduler program + "sch_arb_", # Scheduler arbiter + ], + # LibreTiny/Beken DMA descriptors + "bk_dma": [ + "rx_payload_desc", + "rx_dma_hdrdesc", + "tx_hw_desc", + "host_event_data", + "host_cmd_data", + ], + # ARM EABI compiler runtime (LibreTiny uses ARM Cortex-M) + "arm_runtime": [ + "__aeabi_", + "__adddf3", + "__subdf3", + "__muldf3", + "__divdf3", + "__addsf3", + "__subsf3", + "__mulsf3", + "__divsf3", + "__gnu_unwind", + ], "xtensa": ["xt_", "_xt_", "xPortEnterCriticalTimeout"], "heap": ["heap_", "multi_heap"], "spi_flash": ["spi_flash"], @@ -782,7 +853,22 @@ SYMBOL_PATTERNS = { "math_internal": ["__mdiff", "__lshift", "__mprec_tens", "quorem"], "character_class": ["__chclass"], "camellia": ["camellia_", "camellia_feistel"], - "crypto_tables": ["FSb", "FSb2", "FSb3", "FSb4"], + "crypto_tables": [ + "FSb", + "FSb2", + "FSb3", + "FSb4", + "Te0", # AES encryption table + "Td0", # AES decryption table + "crc32_table", # CRC32 lookup table + "crc_tab", # CRC lookup table + ], + "crypto_hash": [ + "SHA1Transform", # SHA1 hash function + "MD5Transform", # MD5 hash function + "SHA256", + "SHA512", + ], "event_buffer": ["g_eb_list_desc", "eb_space"], "base_node": ["base_node_", "base_node_add_handler"], "file_descriptor": ["s_fd_table"], diff --git a/script/ci_memory_impact_extract.py b/script/ci_memory_impact_extract.py index 77d59417e3..dd91fa861c 100755 --- a/script/ci_memory_impact_extract.py +++ b/script/ci_memory_impact_extract.py @@ -92,18 +92,23 @@ def run_detailed_analysis(build_dir: str) -> dict | None: print(f"Build directory not found: {build_dir}", file=sys.stderr) return None - # Find firmware.elf + # Find firmware.elf (or raw_firmware.elf for LibreTiny) elf_path = None for elf_candidate in [ build_path / "firmware.elf", build_path / ".pioenvs" / build_path.name / "firmware.elf", + # LibreTiny uses raw_firmware.elf + build_path / "raw_firmware.elf", + build_path / ".pioenvs" / build_path.name / "raw_firmware.elf", ]: if elf_candidate.exists(): elf_path = str(elf_candidate) break if not elf_path: - print(f"firmware.elf not found in {build_dir}", file=sys.stderr) + print( + f"firmware.elf/raw_firmware.elf not found in {build_dir}", file=sys.stderr + ) return None # Find idedata.json - check multiple locations diff --git a/script/determine-jobs.py b/script/determine-jobs.py index 5cc3f2570a..44e8e4b5ab 100755 --- a/script/determine-jobs.py +++ b/script/determine-jobs.py @@ -89,6 +89,7 @@ class Platform(StrEnum): ESP32_C6_IDF = "esp32-c6-idf" ESP32_S2_IDF = "esp32-s2-idf" ESP32_S3_IDF = "esp32-s3-idf" + BK72XX_ARD = "bk72xx-ard" # LibreTiny BK7231N # Memory impact analysis constants @@ -120,6 +121,7 @@ PLATFORM_SPECIFIC_COMPONENTS = frozenset( # fastest build times, most sensitive to code size changes # 3. ESP32 IDF - Primary ESP32 platform, most representative of modern ESPHome # 4-6. Other ESP32 variants - Less commonly used but still supported +# 7. BK72XX - LibreTiny platform (good for detecting LibreTiny-specific changes) MEMORY_IMPACT_PLATFORM_PREFERENCE = [ Platform.ESP32_C6_IDF, # ESP32-C6 IDF (newest, supports Thread/Zigbee) Platform.ESP8266_ARD, # ESP8266 Arduino (most memory constrained, fastest builds) @@ -127,6 +129,7 @@ MEMORY_IMPACT_PLATFORM_PREFERENCE = [ Platform.ESP32_C3_IDF, # ESP32-C3 IDF Platform.ESP32_S2_IDF, # ESP32-S2 IDF Platform.ESP32_S3_IDF, # ESP32-S3 IDF + Platform.BK72XX_ARD, # LibreTiny BK7231N ] @@ -404,7 +407,7 @@ def _detect_platform_hint_from_filename(filename: str) -> Platform | None: - wifi_component_esp_idf.cpp, *_idf.h -> ESP32 IDF variants - wifi_component_esp8266.cpp, *_esp8266.h -> ESP8266_ARD - *_esp32*.cpp -> ESP32 IDF (generic) - - *_libretiny.cpp, *_retiny.* -> LibreTiny (not in preference list) + - *_libretiny.cpp, *_bk72*.* -> BK72XX (LibreTiny) - *_pico.cpp, *_rp2040.* -> RP2040 (not in preference list) Args: @@ -438,10 +441,11 @@ def _detect_platform_hint_from_filename(filename: str) -> Platform | None: if "esp32" in filename_lower: return Platform.ESP32_IDF - # LibreTiny and RP2040 are not in MEMORY_IMPACT_PLATFORM_PREFERENCE - # so we don't return them as hints - # if "retiny" in filename_lower or "libretiny" in filename_lower: - # return None # No specific LibreTiny platform preference + # LibreTiny (via 'libretiny' pattern or BK72xx-specific files) + if "libretiny" in filename_lower or "bk72" in filename_lower: + return Platform.BK72XX_ARD + + # RP2040 is not in MEMORY_IMPACT_PLATFORM_PREFERENCE # if "pico" in filename_lower or "rp2040" in filename_lower: # return None # No RP2040 platform preference From fc7e55bfdc68f61c3cacb28ebdd32f0cfad3e9e8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 07:42:18 -1000 Subject: [PATCH 1062/1145] [api] Avoid heap string copies in Home Assistant state subscription callbacks (#12506) --- esphome/components/api/api_connection.cpp | 16 +++-- esphome/components/api/api_server.cpp | 36 ++++++++--- esphome/components/api/api_server.h | 26 +++++--- esphome/components/api/custom_api_device.h | 59 ++++++++++++++++--- .../homeassistant_binary_sensor.cpp | 50 ++++++++-------- .../number/homeassistant_number.cpp | 17 +++--- .../number/homeassistant_number.h | 12 ++-- .../sensor/homeassistant_sensor.cpp | 32 +++++----- .../switch/homeassistant_switch.cpp | 3 +- .../text_sensor/homeassistant_text_sensor.cpp | 20 +++---- 10 files changed, 177 insertions(+), 94 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b173ebc8cb..27344a53ec 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1692,10 +1692,18 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes continue; } - // Create temporary string for callback (callback takes const std::string &) - // Handle empty state - std::string state(!msg.state.empty() ? msg.state.c_str() : "", msg.state.size()); - it.callback(state); + // Create null-terminated state for callback (parse_number needs null-termination) + // HA state max length is 255, so 256 byte buffer covers all cases + char state_buf[256]; + size_t copy_len = msg.state.size(); + if (copy_len >= sizeof(state_buf)) { + copy_len = sizeof(state_buf) - 1; // Truncate to leave space for null terminator + } + if (copy_len > 0) { + memcpy(state_buf, msg.state.c_str(), copy_len); + } + state_buf[copy_len] = '\0'; + it.callback(StringRef(state_buf, copy_len)); } } #endif diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index eedf8c7172..a7b046447d 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -388,8 +388,8 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef #ifdef USE_API_HOMEASSISTANT_STATES // Helper to add subscription (reduces duplication) -void APIServer::add_state_subscription_(const char *entity_id, const char *attribute, - std::function f, bool once) { +void APIServer::add_state_subscription_(const char *entity_id, const char *attribute, std::function f, + bool once) { this->state_subs_.push_back(HomeAssistantStateSubscription{ .entity_id = entity_id, .attribute = attribute, .callback = std::move(f), .once = once, // entity_id_dynamic_storage and attribute_dynamic_storage remain nullptr (no heap allocation) @@ -398,7 +398,7 @@ void APIServer::add_state_subscription_(const char *entity_id, const char *attri // Helper to add subscription with heap-allocated strings (reduces duplication) void APIServer::add_state_subscription_(std::string entity_id, optional attribute, - std::function f, bool once) { + std::function f, bool once) { HomeAssistantStateSubscription sub; // Allocate heap storage for the strings sub.entity_id_dynamic_storage = std::make_unique(std::move(entity_id)); @@ -418,23 +418,43 @@ void APIServer::add_state_subscription_(std::string entity_id, optional f) { + std::function f) { this->add_state_subscription_(entity_id, attribute, std::move(f), false); } void APIServer::get_home_assistant_state(const char *entity_id, const char *attribute, - std::function f) { + std::function f) { this->add_state_subscription_(entity_id, attribute, std::move(f), true); } -// Existing std::string overload (for custom_api_device.h - heap allocation) +// std::string overload with StringRef callback (zero-allocation callback) void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, - std::function f) { + std::function f) { this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false); } void APIServer::get_home_assistant_state(std::string entity_id, optional attribute, - std::function f) { + std::function f) { + this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true); +} + +// Legacy helper: wraps std::string callback and delegates to StringRef version +void APIServer::add_state_subscription_(std::string entity_id, optional attribute, + std::function f, bool once) { + // Wrap callback to convert StringRef -> std::string, then delegate + this->add_state_subscription_(std::move(entity_id), std::move(attribute), + std::function([f = std::move(f)](StringRef state) { f(state.str()); }), + once); +} + +// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string) +void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, + std::function f) { + this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false); +} + +void APIServer::get_home_assistant_state(std::string entity_id, optional attribute, + std::function f) { this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true); } diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 2b2e8bae73..f5b57f994a 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -10,6 +10,7 @@ #include "esphome/core/component.h" #include "esphome/core/controller.h" #include "esphome/core/log.h" +#include "esphome/core/string_ref.h" #include "list_entities.h" #include "subscribe_state.h" #ifdef USE_LOGGER @@ -191,7 +192,7 @@ class APIServer : public Component, struct HomeAssistantStateSubscription { const char *entity_id; // Pointer to flash (internal) or heap (external) const char *attribute; // Pointer to flash or nullptr (nullptr means no attribute) - std::function callback; + std::function callback; bool once; // Dynamic storage for external components using std::string API (custom_api_device.h) @@ -201,14 +202,20 @@ class APIServer : public Component, }; // New const char* overload (for internal components - zero allocation) - void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function f); - void get_home_assistant_state(const char *entity_id, const char *attribute, std::function f); + void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function f); + void get_home_assistant_state(const char *entity_id, const char *attribute, std::function f); - // Existing std::string overload (for custom_api_device.h - heap allocation) + // std::string overload with StringRef callback (for custom_api_device.h with zero-allocation callback) void subscribe_home_assistant_state(std::string entity_id, optional attribute, - std::function f); + std::function f); void get_home_assistant_state(std::string entity_id, optional attribute, - std::function f); + std::function f); + + // Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string for callback) + void subscribe_home_assistant_state(std::string entity_id, optional attribute, + std::function f); + void get_home_assistant_state(std::string entity_id, optional attribute, + std::function f); const std::vector &get_state_subs() const; #endif @@ -232,10 +239,13 @@ class APIServer : public Component, #endif // USE_API_NOISE #ifdef USE_API_HOMEASSISTANT_STATES // Helper methods to reduce code duplication - void add_state_subscription_(const char *entity_id, const char *attribute, std::function f, + void add_state_subscription_(const char *entity_id, const char *attribute, std::function f, bool once); + void add_state_subscription_(std::string entity_id, optional attribute, std::function f, + bool once); + // Legacy helper: wraps std::string callback and delegates to StringRef version void add_state_subscription_(std::string entity_id, optional attribute, - std::function f, bool once); + std::function f, bool once); #endif // USE_API_HOMEASSISTANT_STATES // Pointers and pointer-like types first (4 bytes each) std::unique_ptr socket_ = nullptr; diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index 5e9165326d..7f655a2479 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -122,21 +122,36 @@ class CustomAPIDevice { * subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "climate.kitchen", "current_temperature"); * } * - * void on_state_changed(std::string state) { - * // State of sensor.weather_forecast is `state` + * void on_state_changed(StringRef state) { + * // State of climate.kitchen current_temperature is `state` + * // Use state.c_str() for C string, state.str() for std::string * } * ``` * * @tparam T The class type creating the service, automatically deduced from the function pointer. - * @param callback The member function to call when the entity state changes. + * @param callback The member function to call when the entity state changes (zero-allocation). * @param entity_id The entity_id to track. * @param attribute The entity state attribute to track. */ template + void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id, + const std::string &attribute = "") { + auto f = std::bind(callback, (T *) this, std::placeholders::_1); + global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), std::move(f)); + } + + /** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version). + * + * @deprecated Use the StringRef overload for zero-allocation callbacks. Will be removed in 2027.1.0. + */ + template + ESPDEPRECATED("Use void callback(StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0") void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id, const std::string &attribute = "") { auto f = std::bind(callback, (T *) this, std::placeholders::_1); - global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), f); + // Explicit type to disambiguate overload resolution + global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), + std::function(f)); } /** Subscribe to the state (or attribute state) of an entity from Home Assistant. @@ -148,23 +163,45 @@ class CustomAPIDevice { * subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast"); * } * - * void on_state_changed(std::string entity_id, std::string state) { + * void on_state_changed(const std::string &entity_id, StringRef state) { * // State of `entity_id` is `state` * } * ``` * * @tparam T The class type creating the service, automatically deduced from the function pointer. - * @param callback The member function to call when the entity state changes. + * @param callback The member function to call when the entity state changes (zero-allocation for state). * @param entity_id The entity_id to track. * @param attribute The entity state attribute to track. */ template + void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id, + const std::string &attribute = "") { + auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1); + global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), std::move(f)); + } + + /** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version). + * + * @deprecated Use the StringRef overload for zero-allocation callbacks. Will be removed in 2027.1.0. + */ + template + ESPDEPRECATED("Use void callback(const std::string &, StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0") void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id, const std::string &attribute = "") { auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1); - global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), f); + // Explicit type to disambiguate overload resolution + global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), + std::function(f)); } #else + template + void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id, + const std::string &attribute = "") { + static_assert(sizeof(T) == 0, + "subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section " + "of your YAML configuration"); + } + template void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id, const std::string &attribute = "") { @@ -173,6 +210,14 @@ class CustomAPIDevice { "of your YAML configuration"); } + template + void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id, + const std::string &attribute = "") { + static_assert(sizeof(T) == 0, + "subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section " + "of your YAML configuration"); + } + template void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id, const std::string &attribute = "") { diff --git a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp index 5652e7d603..b0d9135822 100644 --- a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp +++ b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp @@ -1,6 +1,7 @@ #include "homeassistant_binary_sensor.h" -#include "esphome/core/log.h" #include "esphome/components/api/api_server.h" +#include "esphome/core/log.h" +#include "esphome/core/string_ref.h" namespace esphome { namespace homeassistant { @@ -8,31 +9,30 @@ namespace homeassistant { static const char *const TAG = "homeassistant.binary_sensor"; void HomeassistantBinarySensor::setup() { - api::global_api_server->subscribe_home_assistant_state( - this->entity_id_, this->attribute_, [this](const std::string &state) { - auto val = parse_on_off(state.c_str()); - switch (val) { - case PARSE_NONE: - case PARSE_TOGGLE: - ESP_LOGW(TAG, "Can't convert '%s' to binary state!", state.c_str()); - break; - case PARSE_ON: - case PARSE_OFF: - bool new_state = val == PARSE_ON; - if (this->attribute_ != nullptr) { - ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_, this->attribute_, ONOFF(new_state)); - } else { - ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, ONOFF(new_state)); - } - if (this->initial_) { - this->publish_initial_state(new_state); - } else { - this->publish_state(new_state); - } - break; + api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) { + auto val = parse_on_off(state.c_str()); + switch (val) { + case PARSE_NONE: + case PARSE_TOGGLE: + ESP_LOGW(TAG, "Can't convert '%s' to binary state!", state.c_str()); + break; + case PARSE_ON: + case PARSE_OFF: + bool new_state = val == PARSE_ON; + if (this->attribute_ != nullptr) { + ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_, this->attribute_, ONOFF(new_state)); + } else { + ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, ONOFF(new_state)); } - this->initial_ = false; - }); + if (this->initial_) { + this->publish_initial_state(new_state); + } else { + this->publish_state(new_state); + } + break; + } + this->initial_ = false; + }); } void HomeassistantBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Homeassistant Binary Sensor", this); diff --git a/esphome/components/homeassistant/number/homeassistant_number.cpp b/esphome/components/homeassistant/number/homeassistant_number.cpp index 1ca90180eb..8c0d415c23 100644 --- a/esphome/components/homeassistant/number/homeassistant_number.cpp +++ b/esphome/components/homeassistant/number/homeassistant_number.cpp @@ -3,14 +3,15 @@ #include "esphome/components/api/api_pb2.h" #include "esphome/components/api/api_server.h" #include "esphome/core/log.h" +#include "esphome/core/string_ref.h" namespace esphome { namespace homeassistant { static const char *const TAG = "homeassistant.number"; -void HomeassistantNumber::state_changed_(const std::string &state) { - auto number_value = parse_number(state); +void HomeassistantNumber::state_changed_(StringRef state) { + auto number_value = parse_number(state.c_str()); if (!number_value.has_value()) { ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str()); this->publish_state(NAN); @@ -23,8 +24,8 @@ void HomeassistantNumber::state_changed_(const std::string &state) { this->publish_state(number_value.value()); } -void HomeassistantNumber::min_retrieved_(const std::string &min) { - auto min_value = parse_number(min); +void HomeassistantNumber::min_retrieved_(StringRef min) { + auto min_value = parse_number(min.c_str()); if (!min_value.has_value()) { ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_, min.c_str()); return; @@ -33,8 +34,8 @@ void HomeassistantNumber::min_retrieved_(const std::string &min) { this->traits.set_min_value(min_value.value()); } -void HomeassistantNumber::max_retrieved_(const std::string &max) { - auto max_value = parse_number(max); +void HomeassistantNumber::max_retrieved_(StringRef max) { + auto max_value = parse_number(max.c_str()); if (!max_value.has_value()) { ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_, max.c_str()); return; @@ -43,8 +44,8 @@ void HomeassistantNumber::max_retrieved_(const std::string &max) { this->traits.set_max_value(max_value.value()); } -void HomeassistantNumber::step_retrieved_(const std::string &step) { - auto step_value = parse_number(step); +void HomeassistantNumber::step_retrieved_(StringRef step) { + auto step_value = parse_number(step.c_str()); if (!step_value.has_value()) { ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_, step.c_str()); return; diff --git a/esphome/components/homeassistant/number/homeassistant_number.h b/esphome/components/homeassistant/number/homeassistant_number.h index 0dffc108cb..275d2d5f03 100644 --- a/esphome/components/homeassistant/number/homeassistant_number.h +++ b/esphome/components/homeassistant/number/homeassistant_number.h @@ -1,10 +1,8 @@ #pragma once -#include -#include - #include "esphome/components/number/number.h" #include "esphome/core/component.h" +#include "esphome/core/string_ref.h" namespace esphome { namespace homeassistant { @@ -18,10 +16,10 @@ class HomeassistantNumber : public number::Number, public Component { float get_setup_priority() const override; protected: - void state_changed_(const std::string &state); - void min_retrieved_(const std::string &min); - void max_retrieved_(const std::string &max); - void step_retrieved_(const std::string &step); + void state_changed_(StringRef state); + void min_retrieved_(StringRef min); + void max_retrieved_(StringRef max); + void step_retrieved_(StringRef step); void control(float value) override; diff --git a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp index 78da47f9a1..66300ebba5 100644 --- a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp +++ b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp @@ -1,6 +1,7 @@ #include "homeassistant_sensor.h" -#include "esphome/core/log.h" #include "esphome/components/api/api_server.h" +#include "esphome/core/log.h" +#include "esphome/core/string_ref.h" namespace esphome { namespace homeassistant { @@ -8,22 +9,21 @@ namespace homeassistant { static const char *const TAG = "homeassistant.sensor"; void HomeassistantSensor::setup() { - api::global_api_server->subscribe_home_assistant_state( - this->entity_id_, this->attribute_, [this](const std::string &state) { - auto val = parse_number(state); - if (!val.has_value()) { - ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str()); - this->publish_state(NAN); - return; - } + api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) { + auto val = parse_number(state.c_str()); + if (!val.has_value()) { + ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str()); + this->publish_state(NAN); + return; + } - if (this->attribute_ != nullptr) { - ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_, this->attribute_, *val); - } else { - ESP_LOGD(TAG, "'%s': Got state %.2f", this->entity_id_, *val); - } - this->publish_state(*val); - }); + if (this->attribute_ != nullptr) { + ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_, this->attribute_, *val); + } else { + ESP_LOGD(TAG, "'%s': Got state %.2f", this->entity_id_, *val); + } + this->publish_state(*val); + }); } void HomeassistantSensor::dump_config() { LOG_SENSOR("", "Homeassistant Sensor", this); diff --git a/esphome/components/homeassistant/switch/homeassistant_switch.cpp b/esphome/components/homeassistant/switch/homeassistant_switch.cpp index c4abf2295d..d08d761442 100644 --- a/esphome/components/homeassistant/switch/homeassistant_switch.cpp +++ b/esphome/components/homeassistant/switch/homeassistant_switch.cpp @@ -1,6 +1,7 @@ #include "homeassistant_switch.h" #include "esphome/components/api/api_server.h" #include "esphome/core/log.h" +#include "esphome/core/string_ref.h" namespace esphome { namespace homeassistant { @@ -10,7 +11,7 @@ static const char *const TAG = "homeassistant.switch"; using namespace esphome::switch_; void HomeassistantSwitch::setup() { - api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr, [this](const std::string &state) { + api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr, [this](StringRef state) { auto val = parse_on_off(state.c_str()); switch (val) { case PARSE_NONE: diff --git a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp index 6154330a4e..6f77349535 100644 --- a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp +++ b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp @@ -1,6 +1,7 @@ #include "homeassistant_text_sensor.h" -#include "esphome/core/log.h" #include "esphome/components/api/api_server.h" +#include "esphome/core/log.h" +#include "esphome/core/string_ref.h" namespace esphome { namespace homeassistant { @@ -8,15 +9,14 @@ namespace homeassistant { static const char *const TAG = "homeassistant.text_sensor"; void HomeassistantTextSensor::setup() { - api::global_api_server->subscribe_home_assistant_state( - this->entity_id_, this->attribute_, [this](const std::string &state) { - if (this->attribute_ != nullptr) { - ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_, this->attribute_, state.c_str()); - } else { - ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str()); - } - this->publish_state(state); - }); + api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) { + if (this->attribute_ != nullptr) { + ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_, this->attribute_, state.c_str()); + } else { + ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str()); + } + this->publish_state(state.str()); + }); } void HomeassistantTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Homeassistant Text Sensor", this); From c8f5a97cef2e67356642abe127744e484fc2541f Mon Sep 17 00:00:00 2001 From: guillempages Date: Tue, 6 Jan 2026 00:33:06 +0100 Subject: [PATCH 1063/1145] [esphome OTA] Allow compilation on host platform (#11655) Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- .../components/esphome/ota/ota_esphome.cpp | 8 +++---- esphome/components/ota/__init__.py | 8 +++++++ esphome/components/ota/ota_backend_host.cpp | 24 +++++++++++++++++++ esphome/components/ota/ota_backend_host.h | 21 ++++++++++++++++ tests/components/ota/test.host.yaml | 4 ++++ 5 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 esphome/components/ota/ota_backend_host.cpp create mode 100644 esphome/components/ota/ota_backend_host.h create mode 100644 tests/components/ota/test.host.yaml diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 16d7089f02..ba25c69fae 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -387,14 +387,14 @@ bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) { while (len - at > 0) { uint32_t now = millis(); if (now - start > OTA_SOCKET_TIMEOUT_DATA) { - ESP_LOGW(TAG, "Timeout reading %d bytes", len); + ESP_LOGW(TAG, "Timeout reading %zu bytes", len); return false; } ssize_t read = this->client_->read(buf + at, len - at); if (read == -1) { if (!this->would_block_(errno)) { - ESP_LOGW(TAG, "Read err %d bytes, errno %d", len, errno); + ESP_LOGW(TAG, "Read err %zu bytes, errno %d", len, errno); return false; } } else if (read == 0) { @@ -414,14 +414,14 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) { while (len - at > 0) { uint32_t now = millis(); if (now - start > OTA_SOCKET_TIMEOUT_DATA) { - ESP_LOGW(TAG, "Timeout writing %d bytes", len); + ESP_LOGW(TAG, "Timeout writing %zu bytes", len); return false; } ssize_t written = this->client_->write(buf + at, len - at); if (written == -1) { if (!this->would_block_(errno)) { - ESP_LOGW(TAG, "Write err %d bytes, errno %d", len, errno); + ESP_LOGW(TAG, "Write err %zu bytes, errno %d", len, errno); return false; } } else { diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index a514a7482f..ee54d5f8d3 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -1,3 +1,5 @@ +import logging + from esphome import automation import esphome.codegen as cg from esphome.config_helpers import filter_source_files_from_platform @@ -27,6 +29,8 @@ CONF_ON_PROGRESS = "on_progress" CONF_ON_STATE_CHANGE = "on_state_change" +_LOGGER = logging.getLogger(__name__) + ota_ns = cg.esphome_ns.namespace("ota") OTAComponent = ota_ns.class_("OTAComponent", cg.Component) OTAState = ota_ns.enum("OTAState") @@ -45,6 +49,10 @@ def _ota_final_validate(config): raise cv.Invalid( f"At least one platform must be specified for '{CONF_OTA}'; add '{CONF_PLATFORM}: {CONF_ESPHOME}' for original OTA functionality" ) + if CORE.is_host: + _LOGGER.warning( + "OTA not available for platform 'host'. OTA functionality disabled." + ) FINAL_VALIDATE_SCHEMA = _ota_final_validate diff --git a/esphome/components/ota/ota_backend_host.cpp b/esphome/components/ota/ota_backend_host.cpp new file mode 100644 index 0000000000..ddab174bed --- /dev/null +++ b/esphome/components/ota/ota_backend_host.cpp @@ -0,0 +1,24 @@ +#ifdef USE_HOST +#include "ota_backend_host.h" + +#include "esphome/core/defines.h" + +namespace esphome::ota { + +// Stub implementation - OTA is not supported on host platform. +// All methods return error codes to allow compilation of configs with OTA triggers. + +std::unique_ptr make_ota_backend() { return make_unique(); } + +OTAResponseTypes HostOTABackend::begin(size_t image_size) { return OTA_RESPONSE_ERROR_UPDATE_PREPARE; } + +void HostOTABackend::set_update_md5(const char *expected_md5) {} + +OTAResponseTypes HostOTABackend::write(uint8_t *data, size_t len) { return OTA_RESPONSE_ERROR_WRITING_FLASH; } + +OTAResponseTypes HostOTABackend::end() { return OTA_RESPONSE_ERROR_UPDATE_END; } + +void HostOTABackend::abort() {} + +} // namespace esphome::ota +#endif diff --git a/esphome/components/ota/ota_backend_host.h b/esphome/components/ota/ota_backend_host.h new file mode 100644 index 0000000000..ae7d0cb0b3 --- /dev/null +++ b/esphome/components/ota/ota_backend_host.h @@ -0,0 +1,21 @@ +#pragma once +#ifdef USE_HOST +#include "ota_backend.h" + +namespace esphome::ota { + +/// Stub OTA backend for host platform - allows compilation but does not implement OTA. +/// All operations return error codes immediately. This enables configurations with +/// OTA triggers to compile for host platform during development. +class HostOTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + bool supports_compression() override { return false; } +}; + +} // namespace esphome::ota +#endif diff --git a/tests/components/ota/test.host.yaml b/tests/components/ota/test.host.yaml new file mode 100644 index 0000000000..ae7c4d0add --- /dev/null +++ b/tests/components/ota/test.host.yaml @@ -0,0 +1,4 @@ +<<: !include common.yaml + +#host platform does not support wifi / network is automatically included +wifi: !remove From 94bedd83be56b0111e244cdb13aea5a355414338 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Tue, 6 Jan 2026 00:37:38 +0100 Subject: [PATCH 1064/1145] async_tcp: Add AsyncClient for ESP-IDF and host (#12337) Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/async_tcp/__init__.py | 57 ++++--- esphome/components/async_tcp/async_tcp.h | 17 ++ .../components/async_tcp/async_tcp_socket.cpp | 161 ++++++++++++++++++ .../components/async_tcp/async_tcp_socket.h | 73 ++++++++ script/ci-custom.py | 1 + 5 files changed, 287 insertions(+), 22 deletions(-) create mode 100644 esphome/components/async_tcp/async_tcp.h create mode 100644 esphome/components/async_tcp/async_tcp_socket.cpp create mode 100644 esphome/components/async_tcp/async_tcp_socket.h diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index f2d8895b39..4b6c6a275c 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -1,37 +1,50 @@ -# Dummy integration to allow relying on AsyncTCP +# Async TCP client support for all platforms import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import ( - PLATFORM_BK72XX, - PLATFORM_ESP32, - PLATFORM_ESP8266, - PLATFORM_LN882X, - PLATFORM_RTL87XX, -) from esphome.core import CORE, CoroPriority, coroutine_with_priority CODEOWNERS = ["@esphome/core"] +DEPENDENCIES = ["network"] -CONFIG_SCHEMA = cv.All( - cv.Schema({}), - cv.only_with_arduino, - cv.only_on( - [ - PLATFORM_ESP32, - PLATFORM_ESP8266, - PLATFORM_BK72XX, - PLATFORM_LN882X, - PLATFORM_RTL87XX, - ] - ), -) + +def AUTO_LOAD() -> list[str]: + # Socket component needed for platforms using socket-based implementation + # ESP32, ESP8266, RP2040, and LibreTiny use AsyncTCP libraries, others use sockets + if ( + not CORE.is_esp32 + and not CORE.is_esp8266 + and not CORE.is_rp2040 + and not CORE.is_libretiny + ): + return ["socket"] + return [] + + +# Support all platforms - Arduino/ESP-IDF get libraries, other platforms use socket implementation +CONFIG_SCHEMA = cv.Schema({}) @coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT) async def to_code(config): - if CORE.is_esp32 or CORE.is_libretiny: + if CORE.using_esp_idf: + # ESP-IDF needs the IDF component + from esphome.components.esp32 import add_idf_component + + add_idf_component(name="esp32async/asynctcp", ref="3.4.91") + elif CORE.is_esp32 or CORE.is_libretiny: # https://github.com/ESP32Async/AsyncTCP cg.add_library("ESP32Async/AsyncTCP", "3.4.5") elif CORE.is_esp8266: # https://github.com/ESP32Async/ESPAsyncTCP cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0") + elif CORE.is_rp2040: + # https://github.com/khoih-prog/AsyncTCP_RP2040W + cg.add_library("khoih-prog/AsyncTCP_RP2040W", "1.2.0") + # Other platforms (host, etc) use socket-based implementation + + +def FILTER_SOURCE_FILES() -> list[str]: + # Exclude socket implementation for platforms that use AsyncTCP libraries + if CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny: + return ["async_tcp_socket.cpp"] + return [] diff --git a/esphome/components/async_tcp/async_tcp.h b/esphome/components/async_tcp/async_tcp.h new file mode 100644 index 0000000000..362f603451 --- /dev/null +++ b/esphome/components/async_tcp/async_tcp.h @@ -0,0 +1,17 @@ +#pragma once +#include "esphome/core/defines.h" + +#if (defined(USE_ESP32) || defined(USE_LIBRETINY)) && !defined(CLANG_TIDY) +// Use AsyncTCP library for ESP32 (Arduino or ESP-IDF) and LibreTiny +// But not for clang-tidy as the header file isn't present in that case +#include +#elif defined(USE_ESP8266) +// Use ESPAsyncTCP library for ESP8266 (always Arduino) +#include +#elif defined(USE_RP2040) +// Use AsyncTCP_RP2040W library for RP2040 +#include +#else +// Use socket-based implementation for other platforms and clang-tidy +#include "async_tcp_socket.h" +#endif diff --git a/esphome/components/async_tcp/async_tcp_socket.cpp b/esphome/components/async_tcp/async_tcp_socket.cpp new file mode 100644 index 0000000000..6c13f346e9 --- /dev/null +++ b/esphome/components/async_tcp/async_tcp_socket.cpp @@ -0,0 +1,161 @@ +#include "async_tcp_socket.h" + +#if defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) + +#include "esphome/components/network/util.h" +#include "esphome/core/log.h" +#include +#include + +namespace esphome::async_tcp { + +static const char *const TAG = "async_tcp"; + +// Read buffer size matches TCP MSS (1500 MTU - 40 bytes IP/TCP headers). +// This implementation only runs on ESP-IDF and host which have ample stack. +static constexpr size_t READ_BUFFER_SIZE = 1460; + +bool AsyncClient::connect(const char *host, uint16_t port) { + if (connected_ || connecting_) { + ESP_LOGW(TAG, "Already connected/connecting"); + return false; + } + + // Resolve address + struct sockaddr_storage addr; + socklen_t addrlen = esphome::socket::set_sockaddr((struct sockaddr *) &addr, sizeof(addr), host, port); + if (addrlen == 0) { + ESP_LOGE(TAG, "Invalid address: %s", host); + if (error_cb_) + error_cb_(error_arg_, this, -1); + return false; + } + + // Create socket with loop monitoring + int family = ((struct sockaddr *) &addr)->sa_family; + socket_ = esphome::socket::socket_loop_monitored(family, SOCK_STREAM, IPPROTO_TCP); + if (!socket_) { + ESP_LOGE(TAG, "Failed to create socket"); + if (error_cb_) + error_cb_(error_arg_, this, -1); + return false; + } + + socket_->setblocking(false); + + int err = socket_->connect((struct sockaddr *) &addr, addrlen); + if (err == 0) { + // Connection succeeded immediately (rare, but possible for localhost) + connected_ = true; + if (connect_cb_) + connect_cb_(connect_arg_, this); + return true; + } + if (errno != EINPROGRESS) { + ESP_LOGE(TAG, "Connect failed: %d", errno); + close(); + if (error_cb_) + error_cb_(error_arg_, this, errno); + return false; + } + + connecting_ = true; + return true; +} + +void AsyncClient::close() { + socket_.reset(); + bool was_connected = connected_; + connected_ = false; + connecting_ = false; + if (was_connected && disconnect_cb_) + disconnect_cb_(disconnect_arg_, this); +} + +size_t AsyncClient::write(const char *data, size_t len) { + if (!socket_ || !connected_) + return 0; + + ssize_t sent = socket_->write(data, len); + if (sent < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + ESP_LOGE(TAG, "Write error: %d", errno); + close(); + if (error_cb_) + error_cb_(error_arg_, this, errno); + } + return 0; + } + return sent; +} + +void AsyncClient::loop() { + if (!socket_) + return; + + if (connecting_) { + // For connecting, we need to check writability, not readability + // The Application's select() only monitors read FDs, so we do our own check here + // For ESP platforms lwip_select() might be faster, but this code isn't used + // on those platforms anyway. If it was, we'd fix the Application select() + // to report writability instead of doing it this way. + int fd = socket_->get_fd(); + if (fd < 0) { + ESP_LOGW(TAG, "Invalid socket fd"); + close(); + return; + } + + fd_set writefds; + FD_ZERO(&writefds); + FD_SET(fd, &writefds); + + struct timeval tv = {0, 0}; + int ret = select(fd + 1, nullptr, &writefds, nullptr, &tv); + + if (ret > 0 && FD_ISSET(fd, &writefds)) { + int error = 0; + socklen_t len = sizeof(error); + if (socket_->getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) == 0 && error == 0) { + connecting_ = false; + connected_ = true; + if (connect_cb_) + connect_cb_(connect_arg_, this); + } else { + ESP_LOGW(TAG, "Connection failed: %d", error); + close(); + if (error_cb_) + error_cb_(error_arg_, this, error); + } + } else if (ret < 0) { + ESP_LOGE(TAG, "Select error: %d", errno); + close(); + if (error_cb_) + error_cb_(error_arg_, this, errno); + } + } else if (connected_) { + // For connected sockets, use the Application's select() results + if (!socket_->ready()) + return; + + uint8_t buf[READ_BUFFER_SIZE]; + ssize_t len = socket_->read(buf, READ_BUFFER_SIZE); + + if (len == 0) { + ESP_LOGI(TAG, "Connection closed by peer"); + close(); + } else if (len > 0) { + if (data_cb_) + data_cb_(data_arg_, this, buf, len); + } else if (errno != EAGAIN && errno != EWOULDBLOCK) { + ESP_LOGW(TAG, "Read error: %d", errno); + close(); + if (error_cb_) + error_cb_(error_arg_, this, errno); + } + } +} + +} // namespace esphome::async_tcp + +#endif // defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) diff --git a/esphome/components/async_tcp/async_tcp_socket.h b/esphome/components/async_tcp/async_tcp_socket.h new file mode 100644 index 0000000000..ca3bf19d67 --- /dev/null +++ b/esphome/components/async_tcp/async_tcp_socket.h @@ -0,0 +1,73 @@ +#pragma once + +#include "esphome/core/defines.h" + +#if defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) + +#include "esphome/components/socket/socket.h" +#include +#include +#include +#include + +namespace esphome::async_tcp { + +/// AsyncClient API for platforms using sockets (ESP-IDF, host, etc.) +/// NOTE: This class is NOT thread-safe. All methods must be called from the main loop. +class AsyncClient { + public: + using AcConnectHandler = std::function; + using AcDataHandler = std::function; + using AcErrorHandler = std::function; + + AsyncClient() = default; + ~AsyncClient() = default; + + [[nodiscard]] bool connect(const char *host, uint16_t port); + void close(); + [[nodiscard]] bool connected() const { return connected_; } + size_t write(const char *data, size_t len); + + void onConnect(AcConnectHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming) + connect_cb_ = std::move(cb); + connect_arg_ = arg; + } + void onDisconnect(AcConnectHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming) + disconnect_cb_ = std::move(cb); + disconnect_arg_ = arg; + } + /// Set data callback. NOTE: data pointer is only valid during callback execution. + void onData(AcDataHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming) + data_cb_ = std::move(cb); + data_arg_ = arg; + } + void onError(AcErrorHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming) + error_cb_ = std::move(cb); + error_arg_ = arg; + } + + // Must be called from loop() + void loop(); + + private: + std::unique_ptr socket_; + + AcConnectHandler connect_cb_{nullptr}; + void *connect_arg_{nullptr}; + AcConnectHandler disconnect_cb_{nullptr}; + void *disconnect_arg_{nullptr}; + AcDataHandler data_cb_{nullptr}; + void *data_arg_{nullptr}; + AcErrorHandler error_cb_{nullptr}; + void *error_arg_{nullptr}; + + bool connected_{false}; + bool connecting_{false}; +}; + +} // namespace esphome::async_tcp + +// Expose AsyncClient in global namespace to match library behavior +using esphome::async_tcp::AsyncClient; // NOLINT(google-global-names-in-headers) +#define ESPHOME_ASYNC_TCP_SOCKET_IMPL +#endif // defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) diff --git a/script/ci-custom.py b/script/ci-custom.py index f0676d594b..cf59c3883b 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -580,6 +580,7 @@ def lint_relative_py_import(fname: Path, line, col, content): ], exclude=[ "esphome/components/socket/headers.h", + "esphome/components/async_tcp/async_tcp.h", "esphome/components/esp32/core.cpp", "esphome/components/esp8266/core.cpp", "esphome/components/rp2040/core.cpp", From 21aa245cffdf4c92ccdf1e5ae7aff097ac1dfad3 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 6 Jan 2026 09:56:59 +1000 Subject: [PATCH 1065/1145] [image] Replace use of cairosvg with resvg-py (#12863) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/image/__init__.py | 82 +++++++++------------------- requirements.txt | 8 +-- 2 files changed, 28 insertions(+), 62 deletions(-) diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index bf25a7cd92..a7b788bf91 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -374,23 +374,6 @@ def is_svg_file(file): return " 500 or height > 500): _LOGGER.warning( diff --git a/requirements.txt b/requirements.txt index 6631cb55bd..56df559cd7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,13 +19,7 @@ ruamel.yaml==0.19.1 # dashboard_import ruamel.yaml.clib==0.2.15 # dashboard_import esphome-glyphsets==0.2.0 pillow==11.3.0 - -# pycairo fork for Windows -cairosvg @ git+https://github.com/clydebarrow/cairosvg.git@release ; sys_platform == 'win32' - -# Original for everything else -cairosvg==2.8.2 ; sys_platform != 'win32' - +resvg-py==0.2.5 freetype-py==2.5.1 jinja2==3.1.6 bleak==2.1.1 From 7ed4922d286cc51aefaf80f92f2c88c7e48515cf Mon Sep 17 00:00:00 2001 From: PolarGoose <35307286+PolarGoose@users.noreply.github.com> Date: Tue, 6 Jan 2026 01:18:54 +0100 Subject: [PATCH 1066/1145] [dsmr] Remove dependency on Arduino framework. Various bug fixes. Add missing sensors. (#11036) Co-authored-by: J. Nick Koston --- .clang-tidy.hash | 2 +- CODEOWNERS | 2 +- esphome/components/dsmr/__init__.py | 7 +- esphome/components/dsmr/dsmr.cpp | 16 +- esphome/components/dsmr/dsmr.h | 24 +- esphome/components/dsmr/sensor.py | 472 +++++++++++++++++++++- esphome/components/dsmr/text_sensor.py | 4 + platformio.ini | 4 +- tests/components/dsmr/test.esp32-idf.yaml | 7 + 9 files changed, 495 insertions(+), 43 deletions(-) create mode 100644 tests/components/dsmr/test.esp32-idf.yaml diff --git a/.clang-tidy.hash b/.clang-tidy.hash index a14b44ef96..59caddf59b 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -94557f94be073390342833aff12ef8676a8b597db5fa770a5a1232e9425cb48f +97fb425f1d681a5994ed1cc6187910f5d2c37ee577b6dc07eb3f4d8862a011de diff --git a/CODEOWNERS b/CODEOWNERS index 00db5a3c79..a2267621e7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -135,7 +135,7 @@ esphome/components/display_menu_base/* @numo68 esphome/components/dps310/* @kbx81 esphome/components/ds1307/* @badbadc0ffee esphome/components/ds2484/* @mrk-its -esphome/components/dsmr/* @glmnet @zuidwijk +esphome/components/dsmr/* @glmnet @PolarGoose @zuidwijk esphome/components/duty_time/* @dudanov esphome/components/ee895/* @Stock-M esphome/components/ektf2232/touchscreen/* @jesserockz diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index 017a11673f..0ba68daf5d 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -4,7 +4,7 @@ from esphome.components import uart import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_RECEIVE_TIMEOUT, CONF_UART_ID -CODEOWNERS = ["@glmnet", "@zuidwijk"] +CODEOWNERS = ["@glmnet", "@zuidwijk", "@PolarGoose"] MULTI_CONF = True @@ -61,7 +61,6 @@ CONFIG_SCHEMA = cv.All( ): cv.positive_time_period_milliseconds, } ).extend(uart.UART_DEVICE_SCHEMA), - cv.only_with_arduino, ) @@ -83,7 +82,7 @@ async def to_code(config): cg.add_build_flag("-DDSMR_WATER_MBUS_ID=" + str(config[CONF_WATER_MBUS_ID])) # DSMR Parser - cg.add_library("glmnet/Dsmr", "0.8") + cg.add_library("esphome/dsmr_parser", "1.0.0") # Crypto - cg.add_library("rweather/Crypto", "0.4.0") + cg.add_library("polargoose/Crypto-no-arduino", "0.4.0") diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index d99cf5e7a9..41fc2f0d85 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -1,5 +1,3 @@ -#ifdef USE_ARDUINO - #include "dsmr.h" #include "esphome/core/log.h" @@ -7,8 +5,7 @@ #include #include -namespace esphome { -namespace dsmr { +namespace esphome::dsmr { static const char *const TAG = "dsmr"; @@ -257,9 +254,9 @@ bool Dsmr::parse_telegram() { ESP_LOGV(TAG, "Trying to parse telegram"); this->stop_requesting_data_(); - ::dsmr::ParseResult res = - ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false, - this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. + const auto &res = dsmr_parser::P1Parser::parse( + data, this->telegram_, this->bytes_read_, false, + this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. if (res.err) { // Parsing error, show it auto err_str = res.fullError(this->telegram_, this->telegram_ + this->bytes_read_); @@ -329,7 +326,4 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { } } -} // namespace dsmr -} // namespace esphome - -#endif // USE_ARDUINO +} // namespace esphome::dsmr diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index 7304737b50..56ba75b5fa 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -1,24 +1,17 @@ #pragma once -#ifdef USE_ARDUINO - #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/uart/uart.h" #include "esphome/core/log.h" -#include "esphome/core/defines.h" - -// don't include because it puts everything in global namespace -#include -#include - +#include +#include #include -namespace esphome { -namespace dsmr { +namespace esphome::dsmr { -using namespace ::dsmr::fields; +using namespace dsmr_parser::fields; // DSMR_**_LIST generated by ESPHome and written in esphome/core/defines @@ -44,8 +37,8 @@ using namespace ::dsmr::fields; #define DSMR_DATA_SENSOR(s) s #define DSMR_COMMA , -using MyData = ::dsmr::ParsedData; +using MyData = dsmr_parser::ParsedData; class Dsmr : public Component, public uart::UARTDevice { public: @@ -140,7 +133,4 @@ class Dsmr : public Component, public uart::UARTDevice { std::vector decryption_key_{}; bool crc_check_; }; -} // namespace dsmr -} // namespace esphome - -#endif // USE_ARDUINO +} // namespace esphome::dsmr diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 0696fccdf7..7d69f79530 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -3,27 +3,34 @@ from esphome.components import sensor import esphome.config_validation as cv from esphome.const import ( CONF_ID, + DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_CURRENT, + DEVICE_CLASS_DURATION, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, + DEVICE_CLASS_REACTIVE_POWER, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_WATER, STATE_CLASS_MEASUREMENT, STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_CUBIC_METER, + UNIT_HERTZ, + UNIT_KILOVOLT_AMPS, UNIT_KILOVOLT_AMPS_REACTIVE, UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, UNIT_KILOWATT, UNIT_KILOWATT_HOURS, + UNIT_SECOND, UNIT_VOLT, ) from . import CONF_DSMR_ID, Dsmr AUTO_LOAD = ["dsmr"] - +UNIT_GIGA_JOULE = "GJ" CONFIG_SCHEMA = cv.Schema( { @@ -46,6 +53,18 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional("energy_delivered_tariff3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_delivered_tariff4"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), cv.Optional("energy_returned_lux"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=3, @@ -64,14 +83,82 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional("energy_returned_tariff3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_returned_tariff4"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_delivered_tariff1_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_delivered_tariff2_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_returned_tariff1_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_returned_tariff2_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), cv.Optional("total_imported_energy"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, ), + cv.Optional("reactive_energy_delivered_tariff1"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_delivered_tariff2"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_delivered_tariff3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_delivered_tariff4"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), cv.Optional("total_exported_energy"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, ), + cv.Optional("reactive_energy_returned_tariff1"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_returned_tariff2"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_returned_tariff3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_returned_tariff4"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), cv.Optional("power_delivered"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, @@ -84,61 +171,195 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional("power_delivered_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("power_returned_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional("reactive_power_delivered"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_threshold"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_switch_position"): sensor.sensor_schema( accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_failures"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_long_failures"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_sags_l1"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_sags_l2"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_sags_l3"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_swells_l1"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_swells_l2"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_swells_l3"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_time_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_time_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_time_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_time_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_time_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_time_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("current_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("current_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("current_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=1, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_n"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_sum"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_fuse_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_fuse_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_fuse_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, ), @@ -181,51 +402,93 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional("voltage_avg_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_avg_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_avg_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("frequency"): sensor.sensor_schema( + unit_of_measurement=UNIT_HERTZ, + accuracy_decimals=3, + device_class=DEVICE_CLASS_FREQUENCY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("abs_power"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional("gas_delivered"): sensor.sensor_schema( unit_of_measurement=UNIT_CUBIC_METER, accuracy_decimals=3, @@ -244,6 +507,109 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_WATER, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional("thermal_delivered"): sensor.sensor_schema( + unit_of_measurement=UNIT_GIGA_JOULE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("sub_delivered"): sensor.sensor_schema( + unit_of_measurement=UNIT_CUBIC_METER, + accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("gas_device_type"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("gas_valve_position"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("thermal_device_type"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("thermal_valve_position"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("water_device_type"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("water_valve_position"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("sub_device_type"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("sub_valve_position"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_delivery_power"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_delivery_power_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_delivery_power_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_delivery_power_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_return_power"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_return_power_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_return_power_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_return_power_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_demand_power"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_demand_abs"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional( "active_energy_import_current_average_demand" ): sensor.sensor_schema( @@ -252,6 +618,90 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional( + "active_energy_export_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "reactive_energy_import_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "reactive_energy_export_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "apparent_energy_import_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "apparent_energy_export_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_energy_import_last_completed_demand"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_energy_export_last_completed_demand"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "reactive_energy_import_last_completed_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "reactive_energy_export_last_completed_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "apparent_energy_import_last_completed_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "apparent_energy_export_last_completed_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional( "active_energy_import_maximum_demand_running_month" ): sensor.sensor_schema( @@ -268,6 +718,14 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional("fw_core_version"): sensor.sensor_schema( + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("fw_module_version"): sensor.sensor_schema( + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/dsmr/text_sensor.py b/esphome/components/dsmr/text_sensor.py index 3223d943be..4c7455a38f 100644 --- a/esphome/components/dsmr/text_sensor.py +++ b/esphome/components/dsmr/text_sensor.py @@ -18,11 +18,15 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional("electricity_failure_log"): text_sensor.text_sensor_schema(), cv.Optional("message_short"): text_sensor.text_sensor_schema(), cv.Optional("message_long"): text_sensor.text_sensor_schema(), + cv.Optional("equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("gas_equipment_id"): text_sensor.text_sensor_schema(), + cv.Optional("gas_equipment_id_be"): text_sensor.text_sensor_schema(), cv.Optional("thermal_equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(), + cv.Optional("fw_core_checksum"): text_sensor.text_sensor_schema(), + cv.Optional("fw_module_checksum"): text_sensor.text_sensor_schema(), cv.Optional("telegram"): text_sensor.text_sensor_schema().extend( {cv.Optional(CONF_INTERNAL, default=True): cv.boolean} ), diff --git a/platformio.ini b/platformio.ini index e58989c566..dd9eb566c5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -38,6 +38,8 @@ lib_deps_base = wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 pavlodn/HaierProtocol@0.9.31 ; haier + esphome/dsmr_parser@1.0.0 ; dsmr + polargoose/Crypto-no-arduino@0.4.0 ; dsmr https://github.com/esphome/TinyGPSPlus.git#v1.1.0 ; gps ; This is using the repository until a new release is published to PlatformIO https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library @@ -82,8 +84,6 @@ lib_deps = heman/AsyncMqttClient-esphome@1.0.0 ; mqtt fastled/FastLED@3.9.16 ; fastled_base freekode/TM1651@1.0.1 ; tm1651 - glmnet/Dsmr@0.7 ; dsmr - rweather/Crypto@0.4.0 ; dsmr dudanov/MideaUART@1.1.9 ; midea tonia/HeatpumpIR@1.0.37 ; heatpumpir build_flags = diff --git a/tests/components/dsmr/test.esp32-idf.yaml b/tests/components/dsmr/test.esp32-idf.yaml new file mode 100644 index 0000000000..522f60db49 --- /dev/null +++ b/tests/components/dsmr/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + request_pin: GPIO15 + +packages: + uart: !include ../../test_build_components/common/uart/esp32-idf.yaml + +<<: !include common.yaml From b2c22a02b1dd9a9afc68cee1e7d92d406362d6d7 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:08:07 -0500 Subject: [PATCH 1067/1145] [cc1101] Add freq_offset to on_packet trigger (#13008) --- esphome/components/cc1101/__init__.py | 1 + esphome/components/cc1101/cc1101.cpp | 4 +++- esphome/components/cc1101/cc1101.h | 5 +++-- tests/components/cc1101/common.yaml | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index c205ff2f69..fbdd7010b4 100644 --- a/esphome/components/cc1101/__init__.py +++ b/esphome/components/cc1101/__init__.py @@ -264,6 +264,7 @@ async def to_code(config): var.get_packet_trigger(), [ (cg.std_vector.template(cg.uint8), "x"), + (cg.float_, "freq_offset"), (cg.float_, "rssi"), (cg.uint8, "lqi"), ], diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 10f72018f9..5727d485f6 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -189,13 +189,15 @@ void CC1101Component::loop() { this->read_(Register::FIFO, this->packet_.data(), payload_length); // Read status from registers (more reliable than FIFO status bytes due to timing issues) + this->read_(Register::FREQEST); this->read_(Register::RSSI); this->read_(Register::LQI); + float freq_offset = static_cast(this->state_.FREQEST) * (XTAL_FREQUENCY / (1 << 14)); float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET; bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0; uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK; if (this->state_.CRC_EN == 0 || crc_ok) { - this->packet_trigger_->trigger(this->packet_, rssi, lqi); + this->packet_trigger_->trigger(this->packet_, freq_offset, rssi, lqi); } // Return to rx diff --git a/esphome/components/cc1101/cc1101.h b/esphome/components/cc1101/cc1101.h index b896f7e974..9b8d4e56a8 100644 --- a/esphome/components/cc1101/cc1101.h +++ b/esphome/components/cc1101/cc1101.h @@ -73,7 +73,7 @@ class CC1101Component : public Component, // Packet mode operations CC1101Error transmit_packet(const std::vector &packet); - Trigger, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; } + Trigger, float, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; } protected: uint16_t chip_id_{0}; @@ -89,7 +89,8 @@ class CC1101Component : public Component, InternalGPIOPin *gdo0_pin_{nullptr}; // Packet handling - Trigger, float, uint8_t> *packet_trigger_{new Trigger, float, uint8_t>()}; + Trigger, float, float, uint8_t> *packet_trigger_{ + new Trigger, float, float, uint8_t>()}; std::vector packet_; // Low-level Helpers diff --git a/tests/components/cc1101/common.yaml b/tests/components/cc1101/common.yaml index 93f03e582e..42ec50911f 100644 --- a/tests/components/cc1101/common.yaml +++ b/tests/components/cc1101/common.yaml @@ -20,7 +20,7 @@ cc1101: on_packet: then: - lambda: |- - ESP_LOGD("cc1101", "packet %s rssi %.1f dBm lqi %u", format_hex(x).c_str(), rssi, lqi); + ESP_LOGD("cc1101", "packet %s freq_offset %.0f Hz rssi %.1f dBm lqi %u", format_hex(x).c_str(), freq_offset, rssi, lqi); button: - platform: template From b402e403a04b3daf3662d7bb233c010f41a14984 Mon Sep 17 00:00:00 2001 From: Evaldas Auryla Date: Tue, 6 Jan 2026 03:34:23 +0100 Subject: [PATCH 1068/1145] [radon_eye_rd200] update Radon Eye RD200 with v2/v3 support (#7962) Co-authored-by: Artem Butusov Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- .../radon_eye_ble/radon_eye_listener.cpp | 19 +- .../radon_eye_rd200/radon_eye_rd200.cpp | 184 ++++++++++-------- .../radon_eye_rd200/radon_eye_rd200.h | 14 +- 3 files changed, 115 insertions(+), 102 deletions(-) diff --git a/esphome/components/radon_eye_ble/radon_eye_listener.cpp b/esphome/components/radon_eye_ble/radon_eye_listener.cpp index 0c6165c691..2c3ef77add 100644 --- a/esphome/components/radon_eye_ble/radon_eye_listener.cpp +++ b/esphome/components/radon_eye_ble/radon_eye_listener.cpp @@ -1,7 +1,6 @@ #include "radon_eye_listener.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include -#include #ifdef USE_ESP32 @@ -11,17 +10,11 @@ namespace radon_eye_ble { static const char *const TAG = "radon_eye_ble"; bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { - if (not device.get_name().empty()) { - // Vector containing the prefixes to search for - std::vector prefixes = {"FR:R", "FR:I", "FR:H"}; - - // Check if the device name starts with any of the prefixes - if (std::any_of(prefixes.begin(), prefixes.end(), - [&](const std::string &prefix) { return device.get_name().starts_with(prefix); })) { - // Device found - ESP_LOGD(TAG, "Found Radon Eye device Name: %s (MAC: %s)", device.get_name().c_str(), - device.address_str().c_str()); - } + // Radon Eye devices have names starting with "FR:" + if (device.get_name().starts_with("FR:")) { + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGD(TAG, "Found Radon Eye device Name: %s (MAC: %s)", device.get_name().c_str(), + device.address_str_to(addr_buf)); } return false; } diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp index 3ccb7bf082..1bd0b842fe 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -1,5 +1,7 @@ #include "radon_eye_rd200.h" +#include + #ifdef USE_ESP32 namespace esphome { @@ -7,6 +9,22 @@ namespace radon_eye_rd200 { static const char *const TAG = "radon_eye_rd200"; +static const esp32_ble_tracker::ESPBTUUID SERVICE_UUID_V1 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001523-1212-efde-1523-785feabcd123"); +static const esp32_ble_tracker::ESPBTUUID WRITE_CHARACTERISTIC_UUID_V1 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001524-1212-efde-1523-785feabcd123"); +static const esp32_ble_tracker::ESPBTUUID READ_CHARACTERISTIC_UUID_V1 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001525-1212-efde-1523-785feabcd123"); +static const uint8_t WRITE_COMMAND_V1 = 0x50; + +static const esp32_ble_tracker::ESPBTUUID SERVICE_UUID_V2 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001523-0000-1000-8000-00805f9b34fb"); +static const esp32_ble_tracker::ESPBTUUID WRITE_CHARACTERISTIC_UUID_V2 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001524-0000-1000-8000-00805f9b34fb"); +static const esp32_ble_tracker::ESPBTUUID READ_CHARACTERISTIC_UUID_V2 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001525-0000-1000-8000-00805f9b34fb"); +static const uint8_t WRITE_COMMAND_V2 = 0x40; + void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { switch (event) { @@ -23,6 +41,22 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } case ESP_GATTC_SEARCH_CMPL_EVT: { + if (this->parent()->get_service(SERVICE_UUID_V1) != nullptr) { + service_uuid_ = SERVICE_UUID_V1; + sensors_write_characteristic_uuid_ = WRITE_CHARACTERISTIC_UUID_V1; + sensors_read_characteristic_uuid_ = READ_CHARACTERISTIC_UUID_V1; + write_command_ = WRITE_COMMAND_V1; + } else if (this->parent()->get_service(SERVICE_UUID_V2) != nullptr) { + service_uuid_ = SERVICE_UUID_V2; + sensors_write_characteristic_uuid_ = WRITE_CHARACTERISTIC_UUID_V2; + sensors_read_characteristic_uuid_ = READ_CHARACTERISTIC_UUID_V2; + write_command_ = WRITE_COMMAND_V2; + } else { + ESP_LOGW(TAG, "No supported device has been found, disconnecting"); + parent()->set_enabled(false); + break; + } + this->read_handle_ = 0; auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_read_characteristic_uuid_); if (chr == nullptr) { @@ -32,90 +66,114 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } this->read_handle_ = chr->handle; - // Write a 0x50 to the write characteristic. auto *write_chr = this->parent()->get_characteristic(service_uuid_, sensors_write_characteristic_uuid_); if (write_chr == nullptr) { ESP_LOGW(TAG, "No sensor write characteristic found at service %s char %s", service_uuid_.to_string().c_str(), - sensors_read_characteristic_uuid_.to_string().c_str()); + sensors_write_characteristic_uuid_.to_string().c_str()); break; } this->write_handle_ = write_chr->handle; - this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; - - write_query_message_(); - - request_read_values_(); + esp_err_t status = + esp_ble_gattc_register_for_notify(gattc_if, this->parent()->get_remote_bda(), this->read_handle_); + if (status) { + ESP_LOGW(TAG, "Error registering for sensor notify, status=%d", status); + } break; } - case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->get_conn_id()) - break; - if (param->read.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); + case ESP_GATTC_WRITE_DESCR_EVT: { + if (param->write.status != ESP_GATT_OK) { + ESP_LOGE(TAG, "write descr failed, error status = %x", param->write.status); break; } - if (param->read.handle == this->read_handle_) { - read_sensors_(param->read.value, param->read.value_len); + ESP_LOGV(TAG, "Write descr success, writing 0x%02X at write_handle=%d", this->write_command_, + this->write_handle_); + esp_err_t status = + esp_ble_gattc_write_char(gattc_if, this->parent()->get_conn_id(), this->write_handle_, sizeof(write_command_), + (uint8_t *) &write_command_, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error writing 0x%02x command, status=%d", write_command_, status); } break; } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.is_notify) { + ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT, receive notify value, %d bytes", param->notify.value_len); + } else { + ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT, receive indicate value, %d bytes", param->notify.value_len); + } + read_sensors_(param->notify.value, param->notify.value_len); + break; + } + default: break; } } void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) { - if (value_len < 20) { - ESP_LOGD(TAG, "Invalid read"); + if (value_len < 1) { + ESP_LOGW(TAG, "Unexpected empty message"); return; } - // Example data - // [13:08:47][D][radon_eye_rd200:107]: result bytes: 5010 85EBB940 00000000 00000000 2200 2500 0000 - ESP_LOGV(TAG, "result bytes: %02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X %02X%02X %02X%02X", - value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9], - value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18], - value[19]); + uint8_t command = value[0]; - if (value[0] != 0x50) { - // This isn't a sensor reading. + if ((command == WRITE_COMMAND_V1 && value_len < 20) || (command == WRITE_COMMAND_V2 && value_len < 68)) { + ESP_LOGW(TAG, "Unexpected command 0x%02X message length %d", command, value_len); return; } + // Example data V1: + // 501085EBB9400000000000000000220025000000 + // Example data V2: + // 4042323230313033525532303338330652443230304e56322e302e3200014a00060a00080000000300010079300000e01108001c00020000003822005c8f423fa4709d3f + ESP_LOGV(TAG, "radon sensors raw bytes"); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, value, value_len, ESP_LOG_VERBOSE); + // Convert from pCi/L to Bq/m³ constexpr float convert_to_bwpm3 = 37.0; - RadonValue radon_value; - radon_value.chars[0] = value[2]; - radon_value.chars[1] = value[3]; - radon_value.chars[2] = value[4]; - radon_value.chars[3] = value[5]; - float radon_now = radon_value.number * convert_to_bwpm3; - if (is_valid_radon_value_(radon_now)) { - radon_sensor_->publish_state(radon_now); + float radon_now; // in Bq/m³ + float radon_day; // in Bq/m³ + float radon_month; // in Bq/m³ + if (command == WRITE_COMMAND_V1) { + // Use memcpy to avoid unaligned memory access + float temp; + memcpy(&temp, value + 2, sizeof(float)); + radon_now = temp * convert_to_bwpm3; + memcpy(&temp, value + 6, sizeof(float)); + radon_day = temp * convert_to_bwpm3; + memcpy(&temp, value + 10, sizeof(float)); + radon_month = temp * convert_to_bwpm3; + } else if (command == WRITE_COMMAND_V2) { + // Use memcpy to avoid unaligned memory access + uint16_t temp; + memcpy(&temp, value + 33, sizeof(uint16_t)); + radon_now = temp; + memcpy(&temp, value + 35, sizeof(uint16_t)); + radon_day = temp; + memcpy(&temp, value + 37, sizeof(uint16_t)); + radon_month = temp; + } else { + ESP_LOGW(TAG, "Unexpected command value: 0x%02X", command); + return; } - radon_value.chars[0] = value[6]; - radon_value.chars[1] = value[7]; - radon_value.chars[2] = value[8]; - radon_value.chars[3] = value[9]; - float radon_day = radon_value.number * convert_to_bwpm3; + if (this->radon_sensor_ != nullptr) { + this->radon_sensor_->publish_state(radon_now); + } - radon_value.chars[0] = value[10]; - radon_value.chars[1] = value[11]; - radon_value.chars[2] = value[12]; - radon_value.chars[3] = value[13]; - float radon_month = radon_value.number * convert_to_bwpm3; - - if (is_valid_radon_value_(radon_month)) { - ESP_LOGV(TAG, "Radon Long Term based on month"); - radon_long_term_sensor_->publish_state(radon_month); - } else if (is_valid_radon_value_(radon_day)) { - ESP_LOGV(TAG, "Radon Long Term based on day"); - radon_long_term_sensor_->publish_state(radon_day); + if (this->radon_long_term_sensor_ != nullptr) { + if (radon_month > 0) { + ESP_LOGV(TAG, "Radon Long Term based on month"); + this->radon_long_term_sensor_->publish_state(radon_month); + } else { + ESP_LOGV(TAG, "Radon Long Term based on day"); + this->radon_long_term_sensor_->publish_state(radon_day); + } } ESP_LOGV(TAG, @@ -130,49 +188,23 @@ void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) { parent()->set_enabled(false); } -bool RadonEyeRD200::is_valid_radon_value_(float radon) { return radon > 0.0 and radon < 37000; } - void RadonEyeRD200::update() { if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { if (!parent()->enabled) { ESP_LOGW(TAG, "Reconnecting to device"); parent()->set_enabled(true); - parent()->connect(); } else { ESP_LOGW(TAG, "Connection in progress"); } } } -void RadonEyeRD200::write_query_message_() { - ESP_LOGV(TAG, "writing 0x50 to write service"); - int request = 0x50; - auto status = esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), - this->write_handle_, sizeof(request), (uint8_t *) &request, - ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); - if (status) { - ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status); - } -} - -void RadonEyeRD200::request_read_values_() { - auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), - this->read_handle_, ESP_GATT_AUTH_REQ_NONE); - if (status) { - ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); - } -} - void RadonEyeRD200::dump_config() { LOG_SENSOR(" ", "Radon", this->radon_sensor_); LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); } -RadonEyeRD200::RadonEyeRD200() - : PollingComponent(10000), - service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), - sensors_write_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(WRITE_CHARACTERISTIC_UUID)), - sensors_read_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(READ_CHARACTERISTIC_UUID)) {} +RadonEyeRD200::RadonEyeRD200() : PollingComponent(10000) {} } // namespace radon_eye_rd200 } // namespace esphome diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.h b/esphome/components/radon_eye_rd200/radon_eye_rd200.h index 7b29be7bd8..f874c815f8 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.h +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.h @@ -14,10 +14,6 @@ namespace esphome { namespace radon_eye_rd200 { -static const char *const SERVICE_UUID = "00001523-1212-efde-1523-785feabcd123"; -static const char *const WRITE_CHARACTERISTIC_UUID = "00001524-1212-efde-1523-785feabcd123"; -static const char *const READ_CHARACTERISTIC_UUID = "00001525-1212-efde-1523-785feabcd123"; - class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode { public: RadonEyeRD200(); @@ -32,25 +28,17 @@ class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } protected: - bool is_valid_radon_value_(float radon); - void read_sensors_(uint8_t *value, uint16_t value_len); - void write_query_message_(); - void request_read_values_(); sensor::Sensor *radon_sensor_{nullptr}; sensor::Sensor *radon_long_term_sensor_{nullptr}; + uint8_t write_command_; uint16_t read_handle_; uint16_t write_handle_; esp32_ble_tracker::ESPBTUUID service_uuid_; esp32_ble_tracker::ESPBTUUID sensors_write_characteristic_uuid_; esp32_ble_tracker::ESPBTUUID sensors_read_characteristic_uuid_; - - union RadonValue { - char chars[4]; - float number; - }; }; } // namespace radon_eye_rd200 From 0290ed5d23172a73c2d930f6a86805ff6eeec3e0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:14:33 -1000 Subject: [PATCH 1069/1145] [voice_assistant] Reduce heap allocation with stack-based timer formatting (#13001) --- .../voice_assistant/voice_assistant.cpp | 3 ++- .../components/voice_assistant/voice_assistant.h | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index de683113bb..05c356ae4c 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -866,11 +866,12 @@ void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse .is_active = msg.is_active, }; this->timers_[timer.id] = timer; + char timer_buf[Timer::TO_STR_BUFFER_SIZE]; ESP_LOGD(TAG, "Timer Event\n" " Type: %" PRId32 "\n" " %s", - msg.event_type, timer.to_string().c_str()); + msg.event_type, timer.to_str(timer_buf)); switch (msg.event_type) { case api::enums::VOICE_ASSISTANT_TIMER_STARTED: diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 8d3d3497ec..b1b3df7bbd 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -23,6 +23,7 @@ #endif #include "esphome/components/socket/socket.h" +#include #include #include @@ -71,10 +72,18 @@ struct Timer { uint32_t seconds_left; bool is_active; + /// Buffer size for to_str() - sufficient for typical timer names + static constexpr size_t TO_STR_BUFFER_SIZE = 128; + /// Format to buffer, returns pointer to buffer (may truncate long names) + const char *to_str(std::span buffer) const { + snprintf(buffer.data(), buffer.size(), + "Timer(id=%s, name=%s, total_seconds=%" PRIu32 ", seconds_left=%" PRIu32 ", is_active=%s)", + this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left, YESNO(this->is_active)); + return buffer.data(); + } std::string to_string() const { - return str_sprintf("Timer(id=%s, name=%s, total_seconds=%" PRIu32 ", seconds_left=%" PRIu32 ", is_active=%s)", - this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left, - YESNO(this->is_active)); + char buffer[TO_STR_BUFFER_SIZE]; + return this->to_str(buffer); } }; From 2d4cd4ce7e34dae4632b20cf13e42ec957820b21 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:15:50 -1000 Subject: [PATCH 1070/1145] [midea] Reduce heap allocations with stack-based string formatting (#13000) --- esphome/components/midea_ir/midea_ir.cpp | 3 ++- esphome/components/remote_base/midea_protocol.cpp | 5 ++++- esphome/components/remote_base/midea_protocol.h | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/esphome/components/midea_ir/midea_ir.cpp b/esphome/components/midea_ir/midea_ir.cpp index c269b2f7d9..eaee1c731c 100644 --- a/esphome/components/midea_ir/midea_ir.cpp +++ b/esphome/components/midea_ir/midea_ir.cpp @@ -165,7 +165,8 @@ bool MideaIR::on_receive(remote_base::RemoteReceiveData data) { } bool MideaIR::on_midea_(const MideaData &data) { - ESP_LOGV(TAG, "Decoded Midea IR data: %s", data.to_string().c_str()); + char buf[MideaData::TO_STR_BUFFER_SIZE]; + ESP_LOGV(TAG, "Decoded Midea IR data: %s", data.to_str(buf)); if (data.type() == MideaData::MIDEA_TYPE_CONTROL) { const ControlData status = data; if (status.get_mode() != climate::CLIMATE_MODE_FAN_ONLY) diff --git a/esphome/components/remote_base/midea_protocol.cpp b/esphome/components/remote_base/midea_protocol.cpp index 8006fe4048..4fa717cf08 100644 --- a/esphome/components/remote_base/midea_protocol.cpp +++ b/esphome/components/remote_base/midea_protocol.cpp @@ -70,7 +70,10 @@ optional MideaProtocol::decode(RemoteReceiveData src) { return {}; } -void MideaProtocol::dump(const MideaData &data) { ESP_LOGI(TAG, "Received Midea: %s", data.to_string().c_str()); } +void MideaProtocol::dump(const MideaData &data) { + char buf[MideaData::TO_STR_BUFFER_SIZE]; + ESP_LOGI(TAG, "Received Midea: %s", data.to_str(buf)); +} } // namespace remote_base } // namespace esphome diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 94fb6f3d94..0a5de8e9df 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -30,6 +30,13 @@ class MideaData { void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); } bool is_compliment(const MideaData &rhs) const; std::string to_string() const { return format_hex_pretty(this->data_.data(), this->data_.size()); } + /// Buffer size for to_str(): 6 bytes = "AA.BB.CC.DD.EE.FF\0" + static constexpr size_t TO_STR_BUFFER_SIZE = format_hex_pretty_size(6); + /// Format to buffer, returns pointer to buffer + const char *to_str(char *buffer) const { + format_hex_pretty_to(buffer, TO_STR_BUFFER_SIZE, this->data_.data(), this->data_.size(), '.'); + return buffer; + } // compare only 40-bits bool operator==(const MideaData &rhs) const { return std::equal(this->data_.begin(), this->data_.begin() + OFFSET_CS, rhs.data_.begin()); From c3e6a4178ccb267f08b342e0d3a889a95763acc0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:16:14 -1000 Subject: [PATCH 1071/1145] [thermopro_ble] Reduce heap allocation with stack-based string formatting (#12999) --- esphome/components/thermopro_ble/thermopro_ble.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/thermopro_ble/thermopro_ble.cpp b/esphome/components/thermopro_ble/thermopro_ble.cpp index 4b43c9b39e..2c90ee23f8 100644 --- a/esphome/components/thermopro_ble/thermopro_ble.cpp +++ b/esphome/components/thermopro_ble/thermopro_ble.cpp @@ -47,7 +47,8 @@ bool ThermoProBLE::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf)); // publish signal strength float signal_strength = float(device.get_rssi()); From 18217fbe101b67261dd4d2256d65a2f4e572aaa7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:16:47 -1000 Subject: [PATCH 1072/1145] [atc_mithermometer] Reduce heap allocations with stack-based string formatting (#12996) --- .../components/atc_mithermometer/atc_mithermometer.cpp | 10 ++++++---- .../components/atc_mithermometer/atc_mithermometer.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.cpp b/esphome/components/atc_mithermometer/atc_mithermometer.cpp index 9d550fcf8c..b4d2929742 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.cpp +++ b/esphome/components/atc_mithermometer/atc_mithermometer.cpp @@ -21,7 +21,9 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -32,7 +34,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device if (!(parse_message_(service_data.data, *res))) { continue; } - if (!(report_results_(res, device.address_str()))) { + if (!(report_results_(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -103,13 +105,13 @@ bool ATCMiThermometer::parse_message_(const std::vector &message, Parse return true; } -bool ATCMiThermometer::report_results_(const optional &result, const std::string &address) { +bool ATCMiThermometer::report_results_(const optional &result, const char *address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; } - ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address.c_str()); + ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address); if (result->temperature.has_value()) { ESP_LOGD(TAG, " Temperature: %.1f °C", *result->temperature); diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.h b/esphome/components/atc_mithermometer/atc_mithermometer.h index d22e3f069b..e37b5f4350 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.h +++ b/esphome/components/atc_mithermometer/atc_mithermometer.h @@ -41,7 +41,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); bool parse_message_(const std::vector &message, ParseResult &result); - bool report_results_(const optional &result, const std::string &address); + bool report_results_(const optional &result, const char *address); }; } // namespace atc_mithermometer From 9b9a341db09996e98ddc2b3224314d77966dba74 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:17:37 -1000 Subject: [PATCH 1073/1145] [b_parasite] Reduce heap allocation with stack-based string formatting (#12998) --- esphome/components/b_parasite/b_parasite.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/b_parasite/b_parasite.cpp b/esphome/components/b_parasite/b_parasite.cpp index 2e548a8072..356f396476 100644 --- a/esphome/components/b_parasite/b_parasite.cpp +++ b/esphome/components/b_parasite/b_parasite.cpp @@ -22,7 +22,8 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf)); const auto &service_datas = device.get_service_datas(); if (service_datas.size() != 1) { ESP_LOGE(TAG, "Unexpected service_datas size (%d)", service_datas.size()); From 64da6d46e99e7d25ee132c2a5a30c64d46045b58 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:18:06 -1000 Subject: [PATCH 1074/1145] [ruuvi_ble] Reduce heap allocation with stack-based string formatting (#12997) --- esphome/components/ruuvi_ble/ruuvi_ble.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/ruuvi_ble/ruuvi_ble.cpp b/esphome/components/ruuvi_ble/ruuvi_ble.cpp index bdd012cf5c..1b126bdef0 100644 --- a/esphome/components/ruuvi_ble/ruuvi_ble.cpp +++ b/esphome/components/ruuvi_ble/ruuvi_ble.cpp @@ -99,7 +99,8 @@ bool RuuviListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!res.has_value()) return false; - ESP_LOGD(TAG, "Got RuuviTag (%s):", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGD(TAG, "Got RuuviTag (%s):", device.address_str_to(addr_buf)); if (res->humidity.has_value()) { ESP_LOGD(TAG, " Humidity: %.2f%%", *res->humidity); From e6e0be3345ce6eb19c4d85adb43702ab951dce37 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:18:58 -1000 Subject: [PATCH 1075/1145] [bthome_mithermometer] Reduce heap allocations with stack-based string formatting (#12995) --- .../bthome_mithermometer/bthome_ble.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/esphome/components/bthome_mithermometer/bthome_ble.cpp b/esphome/components/bthome_mithermometer/bthome_ble.cpp index b8da51a783..d1c5165896 100644 --- a/esphome/components/bthome_mithermometer/bthome_ble.cpp +++ b/esphome/components/bthome_mithermometer/bthome_ble.cpp @@ -4,6 +4,7 @@ #include "esphome/core/log.h" #include +#include #ifdef USE_ESP32 @@ -12,15 +13,14 @@ namespace bthome_mithermometer { static const char *const TAG = "bthome_mithermometer"; -static std::string format_mac_address(uint64_t address) { +static const char *format_mac_address(std::span buffer, uint64_t address) { std::array mac{}; for (size_t i = 0; i < MAC_ADDRESS_SIZE; i++) { mac[i] = (address >> ((MAC_ADDRESS_SIZE - 1 - i) * 8)) & 0xFF; } - char buffer[MAC_ADDRESS_SIZE * 3]; - format_mac_addr_upper(mac.data(), buffer); - return buffer; + format_mac_addr_upper(mac.data(), buffer.data()); + return buffer.data(); } static bool get_bthome_value_length(uint8_t obj_type, size_t &value_length) { @@ -127,8 +127,9 @@ static bool get_bthome_value_length(uint8_t obj_type, size_t &value_length) { } void BTHomeMiThermometer::dump_config() { + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, "BTHome MiThermometer"); - ESP_LOGCONFIG(TAG, " MAC Address: %s", format_mac_address(this->address_).c_str()); + ESP_LOGCONFIG(TAG, " MAC Address: %s", format_mac_address(addr_buf, this->address_)); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); @@ -172,8 +173,9 @@ bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceD return false; } + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; if (is_encrypted) { - ESP_LOGV(TAG, "Ignoring encrypted BTHome frame from %s", device.address_str().c_str()); + ESP_LOGV(TAG, "Ignoring encrypted BTHome frame from %s", device.address_str_to(addr_buf)); return false; } @@ -193,7 +195,7 @@ bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceD } if (source_address != this->address_) { - ESP_LOGVV(TAG, "BTHome frame from unexpected device %s", format_mac_address(source_address).c_str()); + ESP_LOGVV(TAG, "BTHome frame from unexpected device %s", format_mac_address(addr_buf, source_address)); return false; } @@ -286,7 +288,7 @@ bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceD } if (reported) { - ESP_LOGD(TAG, "BTHome data%sfrom %s", is_trigger_based ? " (triggered) " : " ", device.address_str().c_str()); + ESP_LOGD(TAG, "BTHome data%sfrom %s", is_trigger_based ? " (triggered) " : " ", device.address_str_to(addr_buf)); } return reported; From 82515135567840eabcb620be6ff6e82482d8a0d1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:19:34 -1000 Subject: [PATCH 1076/1145] [bedjet] Use stack-based UUID formatting in logging (#12993) --- esphome/components/bedjet/bedjet_hub.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/bedjet/bedjet_hub.cpp b/esphome/components/bedjet/bedjet_hub.cpp index a3054cf48e..fec34c5b2a 100644 --- a/esphome/components/bedjet/bedjet_hub.cpp +++ b/esphome/components/bedjet/bedjet_hub.cpp @@ -193,8 +193,9 @@ bool BedJetHub::discover_characteristics_() { result = false; } else if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 || descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { + char uuid_buf[espbt::UUID_STR_LEN]; ESP_LOGW(TAG, "Config descriptor 0x%x (uuid %s) is not a client config char uuid", this->char_handle_status_, - descr->uuid.to_string().c_str()); + descr->uuid.to_str(uuid_buf)); result = false; } else { this->config_descr_status_ = descr->handle; From a6adc29b141e8d5188c726b1ce65622b9b413b91 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:20:51 -1000 Subject: [PATCH 1077/1145] [xiaomi_ble] Reduce heap allocations with stack-based string formatting (#12992) --- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 4 ++-- esphome/components/xiaomi_ble/xiaomi_ble.h | 2 +- esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp | 6 ++++-- esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp | 6 ++++-- esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp | 6 ++++-- esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp | 6 ++++-- .../components/xiaomi_gcls002/xiaomi_gcls002.cpp | 6 ++++-- .../xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp | 6 ++++-- .../xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp | 3 ++- .../xiaomi_hhccpot002/xiaomi_hhccpot002.cpp | 6 ++++-- .../xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp | 6 ++++-- .../components/xiaomi_lywsd02/xiaomi_lywsd02.cpp | 6 ++++-- .../xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp | 6 ++++-- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp | 6 ++++-- .../components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp | 6 ++++-- .../components/xiaomi_mhoc303/xiaomi_mhoc303.cpp | 6 ++++-- .../components/xiaomi_mhoc401/xiaomi_mhoc401.cpp | 6 ++++-- .../components/xiaomi_miscale/xiaomi_miscale.cpp | 14 +++++++++----- esphome/components/xiaomi_miscale/xiaomi_miscale.h | 2 +- .../xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp | 6 ++++-- .../xiaomi_mue4094rt/xiaomi_mue4094rt.cpp | 6 ++++-- .../xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp | 6 ++++-- esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp | 6 ++++-- .../xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp | 6 ++++-- 24 files changed, 91 insertions(+), 48 deletions(-) diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 9f25063133..0018d35f1f 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -362,13 +362,13 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c return true; } -bool report_xiaomi_results(const optional &result, const std::string &address) { +bool report_xiaomi_results(const optional &result, const char *address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_xiaomi_results(): no results available."); return false; } - ESP_LOGD(TAG, "Got Xiaomi %s (%s):", result->name.c_str(), address.c_str()); + ESP_LOGD(TAG, "Got Xiaomi %s (%s):", result->name.c_str(), address); if (result->temperature.has_value()) { ESP_LOGD(TAG, " Temperature: %.1f°C", *result->temperature); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.h b/esphome/components/xiaomi_ble/xiaomi_ble.h index 77fb04fd78..42609a998b 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.h +++ b/esphome/components/xiaomi_ble/xiaomi_ble.h @@ -71,7 +71,7 @@ bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_ bool parse_xiaomi_message(const std::vector &message, XiaomiParseResult &result); optional parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data); bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address); -bool report_xiaomi_results(const optional &result, const std::string &address); +bool report_xiaomi_results(const optional &result, const char *address); class XiaomiListener : public esp32_ble_tracker::ESPBTDeviceListener { public: diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index d7f1ec3782..1aa542633a 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -27,7 +27,9 @@ bool XiaomiCGD1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -46,7 +48,7 @@ bool XiaomiCGD1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index 9151cbde41..a049854935 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -27,7 +27,9 @@ bool XiaomiCGDK2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -46,7 +48,7 @@ bool XiaomiCGDK2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index 54b50a2eee..da4bab6623 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -27,7 +27,9 @@ bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -46,7 +48,7 @@ bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp index db63beea89..2048c786d3 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp @@ -21,7 +21,9 @@ bool XiaomiCGPR1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -40,7 +42,7 @@ bool XiaomiCGPR1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->idle_time.has_value() && this->idle_time_ != nullptr) diff --git a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp index 990346e01e..159b6df80b 100644 --- a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp +++ b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp @@ -21,7 +21,9 @@ bool XiaomiGCLS002::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -39,7 +41,7 @@ bool XiaomiGCLS002::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp index 30990b121d..e10754d832 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp @@ -22,7 +22,9 @@ bool XiaomiHHCCJCY01::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -40,7 +42,7 @@ bool XiaomiHHCCJCY01::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp b/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp index 2bc52b8085..028d797ac1 100644 --- a/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp +++ b/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp @@ -23,7 +23,8 @@ bool XiaomiHHCCJCY10::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf)); bool success = false; for (auto &service_data : device.get_service_datas()) { diff --git a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp index 3ae29088bb..2d2447db27 100644 --- a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp +++ b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp @@ -19,7 +19,9 @@ bool XiaomiHHCCPOT002::parse_device(const esp32_ble_tracker::ESPBTDevice &device ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -37,7 +39,7 @@ bool XiaomiHHCCPOT002::parse_device(const esp32_ble_tracker::ESPBTDevice &device if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->moisture.has_value() && this->moisture_ != nullptr) diff --git a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp index 1efebc2849..8216a92e54 100644 --- a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp +++ b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp @@ -21,7 +21,9 @@ bool XiaomiJQJCY01YM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -39,7 +41,7 @@ bool XiaomiJQJCY01YM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp index a6f27c58b9..e140835d03 100644 --- a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp +++ b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp @@ -20,7 +20,9 @@ bool XiaomiLYWSD02::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -38,7 +40,7 @@ bool XiaomiLYWSD02::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp index da5229c100..edd9f67f56 100644 --- a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp +++ b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp @@ -27,7 +27,9 @@ bool XiaomiLYWSD02MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -46,7 +48,7 @@ bool XiaomiLYWSD02MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index 44fdb3b816..2b4b67c92f 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -27,7 +27,9 @@ bool XiaomiLYWSD03MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -50,7 +52,7 @@ bool XiaomiLYWSD03MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device // see https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595948254 *res->humidity = trunc(*res->humidity); } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp index 749ca83afb..65991ffa0e 100644 --- a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp +++ b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp @@ -20,7 +20,9 @@ bool XiaomiLYWSDCGQ::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -38,7 +40,7 @@ bool XiaomiLYWSDCGQ::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp index e613faec7e..1097b9c1e8 100644 --- a/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp +++ b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp @@ -20,7 +20,9 @@ bool XiaomiMHOC303::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -38,7 +40,7 @@ bool XiaomiMHOC303::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index 55b81b301e..e1b808c54e 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -27,7 +27,9 @@ bool XiaomiMHOC401::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -50,7 +52,7 @@ bool XiaomiMHOC401::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { // see https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595948254 *res->humidity = trunc(*res->humidity); } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index 29c9de1652..e4f77fb915 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -1,4 +1,5 @@ #include "xiaomi_miscale.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -19,7 +20,9 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -30,7 +33,7 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!parse_message_(service_data.data, *res)) continue; - if (!report_results_(res, device.address_str())) + if (!report_results_(res, addr_str)) continue; if (res->weight.has_value() && this->weight_ != nullptr) @@ -61,9 +64,10 @@ optional XiaomiMiscale::parse_header_(const esp32_ble_tracker::Serv } else if (service_data.uuid == esp32_ble_tracker::ESPBTUUID::from_uint16(0x181B) && service_data.data.size() == 13) { result.version = 2; } else { + char uuid_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGVV(TAG, "parse_header(): Couldn't identify scale version or data size was not correct. UUID: %s, data_size: %d", - service_data.uuid.to_string().c_str(), service_data.data.size()); + service_data.uuid.to_str(uuid_buf), service_data.data.size()); return {}; } @@ -145,13 +149,13 @@ bool XiaomiMiscale::parse_message_v2_(const std::vector &message, Parse return true; } -bool XiaomiMiscale::report_results_(const optional &result, const std::string &address) { +bool XiaomiMiscale::report_results_(const optional &result, const char *address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; } - ESP_LOGD(TAG, "Got Xiaomi Miscale v%d (%s):", result->version, address.c_str()); + ESP_LOGD(TAG, "Got Xiaomi Miscale v%d (%s):", result->version, address); if (result->weight.has_value()) { ESP_LOGD(TAG, " Weight: %.2fkg", *result->weight); diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.h b/esphome/components/xiaomi_miscale/xiaomi_miscale.h index 10d308ef6c..3d793e07ac 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.h +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.h @@ -37,7 +37,7 @@ class XiaomiMiscale : public Component, public esp32_ble_tracker::ESPBTDeviceLis bool parse_message_(const std::vector &message, ParseResult &result); bool parse_message_v1_(const std::vector &message, ParseResult &result); bool parse_message_v2_(const std::vector &message, ParseResult &result); - bool report_results_(const optional &result, const std::string &address); + bool report_results_(const optional &result, const char *address); }; } // namespace xiaomi_miscale diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp index 16c0b42279..eb4862a7e9 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp @@ -22,7 +22,9 @@ bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -41,7 +43,7 @@ bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->idle_time.has_value() && this->idle_time_ != nullptr) diff --git a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp index 1a8e72bd2c..a3f9325946 100644 --- a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp +++ b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp @@ -18,7 +18,9 @@ bool XiaomiMUE4094RT::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -36,7 +38,7 @@ bool XiaomiMUE4094RT::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->has_motion.has_value()) { diff --git a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp index 112bf442e0..d5b89507fe 100644 --- a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp +++ b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp @@ -30,7 +30,9 @@ bool XiaomiRTCGQ02LM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -50,7 +52,7 @@ bool XiaomiRTCGQ02LM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } #ifdef USE_BINARY_SENSOR diff --git a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp index b57bf5cd05..b0e02e2372 100644 --- a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp +++ b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp @@ -20,7 +20,9 @@ bool XiaomiWX08ZM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -38,7 +40,7 @@ bool XiaomiWX08ZM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->is_active.has_value()) { diff --git a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp index 31e426f0cc..f126e8bdfd 100644 --- a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp +++ b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp @@ -27,7 +27,9 @@ bool XiaomiXMWSDJ04MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &devic ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -50,7 +52,7 @@ bool XiaomiXMWSDJ04MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &devic // see https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595948254 *res->humidity = trunc(*res->humidity); } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) From 95573bc1063b5482a9af6643029719aa419ce02c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:21:54 -1000 Subject: [PATCH 1078/1145] [mopeka] Reduce heap allocations with stack-based string formatting (#12990) --- esphome/components/mopeka_ble/mopeka_ble.cpp | 5 +- .../mopeka_pro_check/mopeka_pro_check.cpp | 3 +- .../mopeka_std_check/mopeka_std_check.cpp | 52 ++++++++++--------- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/esphome/components/mopeka_ble/mopeka_ble.cpp b/esphome/components/mopeka_ble/mopeka_ble.cpp index 07c8ac5d71..b926beaff2 100644 --- a/esphome/components/mopeka_ble/mopeka_ble.cpp +++ b/esphome/components/mopeka_ble/mopeka_ble.cpp @@ -36,6 +36,7 @@ static const uint8_t MANUFACTURER_NRF52_DATA_LENGTH = 10; */ bool MopekaListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; // Fetch information about BLE device. const auto &service_uuids = device.get_service_uuids(); if (service_uuids.size() != 1) { @@ -62,7 +63,7 @@ bool MopekaListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const bool sync_button_pressed = (manu_data.data[3] & 0x80) != 0; if (this->show_sensors_without_sync_ || sync_button_pressed) { - ESP_LOGI(TAG, "MOPEKA STD (CC2540) SENSOR FOUND: %s", device.address_str().c_str()); + ESP_LOGI(TAG, "MOPEKA STD (CC2540) SENSOR FOUND: %s", device.address_str_to(addr_buf)); } // Is the device maybe a Mopeka Pro (NRF52) sensor. @@ -78,7 +79,7 @@ bool MopekaListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const bool sync_button_pressed = (manu_data.data[2] & 0x80) != 0; if (this->show_sensors_without_sync_ || sync_button_pressed) { - ESP_LOGI(TAG, "MOPEKA PRO (NRF52) SENSOR FOUND: %s", device.address_str().c_str()); + ESP_LOGI(TAG, "MOPEKA PRO (NRF52) SENSOR FOUND: %s", device.address_str_to(addr_buf)); } } diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp index 42d61f81a3..9bc9900a5a 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp @@ -31,7 +31,8 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf)); const auto &manu_datas = device.get_manufacturer_datas(); diff --git a/esphome/components/mopeka_std_check/mopeka_std_check.cpp b/esphome/components/mopeka_std_check/mopeka_std_check.cpp index 231d09b909..6322b550c9 100644 --- a/esphome/components/mopeka_std_check/mopeka_std_check.cpp +++ b/esphome/components/mopeka_std_check/mopeka_std_check.cpp @@ -35,15 +35,17 @@ void MopekaStdCheck::dump_config() { * update the sensor state data. */ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { - { - // Validate address. - if (device.address_uint64() != this->address_) { - return false; - } - - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + // Validate address. + if (device.address_uint64() != this->address_) { + return false; } + // Stack buffer for MAC address formatting - reused throughout function + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); + { // Validate service uuid const auto &service_uuids = device.get_service_uuids(); @@ -59,7 +61,7 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const auto &manu_datas = device.get_manufacturer_datas(); if (manu_datas.size() != 1) { - ESP_LOGE(TAG, "[%s] Unexpected manu_datas size (%d)", device.address_str().c_str(), manu_datas.size()); + ESP_LOGE(TAG, "[%s] Unexpected manu_datas size (%d)", addr_str, manu_datas.size()); return false; } @@ -68,11 +70,11 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE char hex_buf[format_hex_pretty_size(MOPEKA_MAX_LOG_BYTES)]; #endif - ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", device.address_str().c_str(), + ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", addr_str, format_hex_pretty_to(hex_buf, manu_data.data.data(), manu_data.data.size())); if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) { - ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", device.address_str().c_str(), manu_data.data.size()); + ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", addr_str, manu_data.data.size()); return false; } @@ -82,21 +84,21 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const u_int8_t hardware_id = mopeka_data->data_1 & 0xCF; if (static_cast(hardware_id) != STANDARD && static_cast(hardware_id) != XL && static_cast(hardware_id) != ETRAILER && static_cast(hardware_id) != STANDARD_ALT) { - ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", device.address_str().c_str(), hardware_id); + ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", addr_str, hardware_id); return false; } - ESP_LOGVV(TAG, "[%s] Sensor slow update rate: %d", device.address_str().c_str(), mopeka_data->slow_update_rate); - ESP_LOGVV(TAG, "[%s] Sensor sync pressed: %d", device.address_str().c_str(), mopeka_data->sync_pressed); + ESP_LOGVV(TAG, "[%s] Sensor slow update rate: %d", addr_str, mopeka_data->slow_update_rate); + ESP_LOGVV(TAG, "[%s] Sensor sync pressed: %d", addr_str, mopeka_data->sync_pressed); for (u_int8_t i = 0; i < 3; i++) { - ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 1, - mopeka_data->val[i].value_0, mopeka_data->val[i].time_0); - ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 2, - mopeka_data->val[i].value_1, mopeka_data->val[i].time_1); - ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 3, - mopeka_data->val[i].value_2, mopeka_data->val[i].time_2); - ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 4, - mopeka_data->val[i].value_3, mopeka_data->val[i].time_3); + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 1, mopeka_data->val[i].value_0, + mopeka_data->val[i].time_0); + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 2, mopeka_data->val[i].value_1, + mopeka_data->val[i].time_1); + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 3, mopeka_data->val[i].value_2, + mopeka_data->val[i].time_2); + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 4, mopeka_data->val[i].value_3, + mopeka_data->val[i].time_3); } // Get battery level first @@ -163,12 +165,12 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) } } - ESP_LOGV(TAG, "[%s] Found %u values with best data %u time %u.", device.address_str().c_str(), - number_of_usable_values, best_value, best_time); + ESP_LOGV(TAG, "[%s] Found %u values with best data %u time %u.", addr_str, number_of_usable_values, best_value, + best_time); if (number_of_usable_values < 1 || best_value < 2 || best_time < 2) { // At least two measurement values must be present. - ESP_LOGW(TAG, "[%s] Poor read quality. Setting distance to 0.", device.address_str().c_str()); + ESP_LOGW(TAG, "[%s] Poor read quality. Setting distance to 0.", addr_str); if (this->distance_ != nullptr) { this->distance_->publish_state(0); } @@ -177,7 +179,7 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) } } else { float lpg_speed_of_sound = this->get_lpg_speed_of_sound_(temp_in_c); - ESP_LOGV(TAG, "[%s] Speed of sound in current fluid %f m/s", device.address_str().c_str(), lpg_speed_of_sound); + ESP_LOGV(TAG, "[%s] Speed of sound in current fluid %f m/s", addr_str, lpg_speed_of_sound); uint32_t distance_value = lpg_speed_of_sound * best_time / 100.0f; From 7ba4dc0f1a82b77140ea07dac7efbfaa058d4558 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:22:27 -1000 Subject: [PATCH 1079/1145] [airthings_wave_base, airthings_ble] Use stack-based string formatting in logging (#12989) --- .../airthings_ble/airthings_listener.cpp | 3 ++- .../airthings_wave_base.cpp | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/esphome/components/airthings_ble/airthings_listener.cpp b/esphome/components/airthings_ble/airthings_listener.cpp index a36d614df5..58faf923f5 100644 --- a/esphome/components/airthings_ble/airthings_listener.cpp +++ b/esphome/components/airthings_ble/airthings_listener.cpp @@ -20,7 +20,8 @@ bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &devic sn |= ((uint32_t) it.data[2] << 16); sn |= ((uint32_t) it.data[3] << 24); - ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str_to(addr_buf)); return true; } } diff --git a/esphome/components/airthings_wave_base/airthings_wave_base.cpp b/esphome/components/airthings_wave_base/airthings_wave_base.cpp index 16789ff454..e4c7d2a81d 100644 --- a/esphome/components/airthings_wave_base/airthings_wave_base.cpp +++ b/esphome/components/airthings_wave_base/airthings_wave_base.cpp @@ -1,4 +1,5 @@ #include "airthings_wave_base.h" +#include "esphome/components/esp32_ble/ble_uuid.h" // All information related to reading battery information came from the sensors.airthings_wave // project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave) @@ -93,8 +94,10 @@ void AirthingsWaveBase::update() { bool AirthingsWaveBase::request_read_values_() { auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_); if (chr == nullptr) { - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), - this->sensors_data_characteristic_uuid_.to_string().c_str()); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf), + this->sensors_data_characteristic_uuid_.to_str(char_buf)); return false; } @@ -117,17 +120,20 @@ bool AirthingsWaveBase::request_battery_() { auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->access_control_point_characteristic_uuid_); if (chr == nullptr) { + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s", - this->service_uuid_.to_string().c_str(), - this->access_control_point_characteristic_uuid_.to_string().c_str()); + this->service_uuid_.to_str(service_buf), this->access_control_point_characteristic_uuid_.to_str(char_buf)); return false; } auto *descr = this->parent()->get_descriptor(this->service_uuid_, this->access_control_point_characteristic_uuid_, CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID); if (descr == nullptr) { - ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_string().c_str(), - this->access_control_point_characteristic_uuid_.to_string().c_str()); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_str(service_buf), + this->access_control_point_characteristic_uuid_.to_str(char_buf)); return false; } From 8518424a88a303ba6fa49cd7a511f47a920673fc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:26:49 -1000 Subject: [PATCH 1080/1145] [esp8266] Add enable_serial/enable_serial1 helpers to exclude unused Serial objects (#12736) --- esphome/components/esp8266/__init__.py | 33 ++++++++++++ esphome/components/esp8266/const.py | 36 +++++++++++++ esphome/components/logger/__init__.py | 12 +++++ esphome/components/logger/logger_esp8266.cpp | 50 +++++++++---------- esphome/components/uart/__init__.py | 22 ++++++++ .../uart/uart_component_esp8266.cpp | 10 +++- esphome/core/defines.h | 4 ++ 7 files changed, 138 insertions(+), 29 deletions(-) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 77ccaf52c1..c7b5d5c130 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -23,12 +23,18 @@ from esphome.helpers import copy_file_if_changed from .boards import BOARDS, ESP8266_LD_SCRIPTS from .const import ( CONF_EARLY_PIN_INIT, + CONF_ENABLE_SERIAL, + CONF_ENABLE_SERIAL1, CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266, KEY_FLASH_SIZE, KEY_PIN_INITIAL_STATES, + KEY_SERIAL1_REQUIRED, + KEY_SERIAL_REQUIRED, KEY_WAVEFORM_REQUIRED, + enable_serial, + enable_serial1, esp8266_ns, ) from .gpio import PinInitialState, add_pin_initial_states_array @@ -171,6 +177,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_BOARD_FLASH_MODE, default="dout"): cv.one_of( *BUILD_FLASH_MODES, lower=True ), + cv.Optional(CONF_ENABLE_SERIAL): cv.boolean, + cv.Optional(CONF_ENABLE_SERIAL1): cv.boolean, } ), set_core_data, @@ -231,6 +239,12 @@ async def to_code(config): if config[CONF_EARLY_PIN_INIT]: cg.add_define("USE_ESP8266_EARLY_PIN_INIT") + # Allow users to force-enable Serial objects for use in lambdas or external libraries + if config.get(CONF_ENABLE_SERIAL): + enable_serial() + if config.get(CONF_ENABLE_SERIAL1): + enable_serial1() + # Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when # out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make # new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of @@ -271,6 +285,7 @@ async def to_code(config): CORE.add_job(add_pin_initial_states_array) CORE.add_job(finalize_waveform_config) + CORE.add_job(finalize_serial_config) @coroutine_with_priority(CoroPriority.WORKAROUNDS) @@ -286,6 +301,24 @@ async def finalize_waveform_config() -> None: cg.add_build_flag("-DUSE_ESP8266_WAVEFORM_STUBS") +@coroutine_with_priority(CoroPriority.WORKAROUNDS) +async def finalize_serial_config() -> None: + """Exclude unused Arduino Serial objects from the build. + + This runs at WORKAROUNDS priority (-999) to ensure all components + have had a chance to call enable_serial() or enable_serial1() first. + + The Arduino ESP8266 core defines two global Serial objects (32 bytes each). + By adding NO_GLOBAL_SERIAL or NO_GLOBAL_SERIAL1 build flags, we prevent + unused Serial objects from being linked, saving 32 bytes each. + """ + esp8266_data = CORE.data.get(KEY_ESP8266, {}) + if not esp8266_data.get(KEY_SERIAL_REQUIRED, False): + cg.add_build_flag("-DNO_GLOBAL_SERIAL") + if not esp8266_data.get(KEY_SERIAL1_REQUIRED, False): + cg.add_build_flag("-DNO_GLOBAL_SERIAL1") + + # Called by writer.py def copy_files() -> None: dir = Path(__file__).parent diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py index 14425cde68..229ac61f24 100644 --- a/esphome/components/esp8266/const.py +++ b/esphome/components/esp8266/const.py @@ -6,8 +6,12 @@ KEY_BOARD = "board" KEY_PIN_INITIAL_STATES = "pin_initial_states" CONF_RESTORE_FROM_FLASH = "restore_from_flash" CONF_EARLY_PIN_INIT = "early_pin_init" +CONF_ENABLE_SERIAL = "enable_serial" +CONF_ENABLE_SERIAL1 = "enable_serial1" KEY_FLASH_SIZE = "flash_size" KEY_WAVEFORM_REQUIRED = "waveform_required" +KEY_SERIAL_REQUIRED = "serial_required" +KEY_SERIAL1_REQUIRED = "serial1_required" # esp8266 namespace is already defined by arduino, manually prefix esphome esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266") @@ -29,3 +33,35 @@ def require_waveform() -> None: require_waveform() """ CORE.data.setdefault(KEY_ESP8266, {})[KEY_WAVEFORM_REQUIRED] = True + + +def enable_serial() -> None: + """Mark that Arduino Serial (UART0) is required. + + Call this from components that use the global Serial object. + If no component calls this, Serial is excluded from the build + to save 32 bytes of RAM. + + Example: + from esphome.components.esp8266.const import enable_serial + + async def to_code(config): + enable_serial() + """ + CORE.data.setdefault(KEY_ESP8266, {})[KEY_SERIAL_REQUIRED] = True + + +def enable_serial1() -> None: + """Mark that Arduino Serial1 (UART1) is required. + + Call this from components that use the global Serial1 object. + If no component calls this, Serial1 is excluded from the build + to save 32 bytes of RAM. + + Example: + from esphome.components.esp8266.const import enable_serial1 + + async def to_code(config): + enable_serial1() + """ + CORE.data.setdefault(KEY_ESP8266, {})[KEY_SERIAL1_REQUIRED] = True diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 7132cd8956..0a6035f8d1 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -337,6 +337,18 @@ async def to_code(config): is_at_least_very_verbose = this_severity >= very_verbose_severity has_serial_logging = baud_rate != 0 + # Add defines for which Serial object is needed (allows linker to exclude unused) + if CORE.is_esp8266: + from esphome.components.esp8266.const import enable_serial, enable_serial1 + + hw_uart = config.get(CONF_HARDWARE_UART, UART0) + if has_serial_logging and hw_uart in (UART0, UART0_SWAP): + cg.add_define("USE_ESP8266_LOGGER_SERIAL") + enable_serial() + elif has_serial_logging and hw_uart == UART1: + cg.add_define("USE_ESP8266_LOGGER_SERIAL1") + enable_serial1() + if ( (CORE.is_esp8266 or CORE.is_rp2040) and has_serial_logging diff --git a/esphome/components/logger/logger_esp8266.cpp b/esphome/components/logger/logger_esp8266.cpp index 0fc73b747a..6cee1baca5 100644 --- a/esphome/components/logger/logger_esp8266.cpp +++ b/esphome/components/logger/logger_esp8266.cpp @@ -7,26 +7,21 @@ namespace esphome::logger { static const char *const TAG = "logger"; void Logger::pre_setup() { - if (this->baud_rate_ > 0) { - switch (this->uart_) { - case UART_SELECTION_UART0: - case UART_SELECTION_UART0_SWAP: - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); - if (this->uart_ == UART_SELECTION_UART0_SWAP) { - Serial.swap(); - } - Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); - break; - case UART_SELECTION_UART1: - this->hw_serial_ = &Serial1; - Serial1.begin(this->baud_rate_); - Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); - break; - } - } else { - uart_set_debug(UART_NO); +#if defined(USE_ESP8266_LOGGER_SERIAL) + this->hw_serial_ = &Serial; + Serial.begin(this->baud_rate_); + if (this->uart_ == UART_SELECTION_UART0_SWAP) { + Serial.swap(); } + Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); +#elif defined(USE_ESP8266_LOGGER_SERIAL1) + this->hw_serial_ = &Serial1; + Serial1.begin(this->baud_rate_); + Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); +#else + // No serial logging - disable debug output + uart_set_debug(UART_NO); +#endif global_logger = this; @@ -39,15 +34,16 @@ void HOT Logger::write_msg_(const char *msg, size_t len) { } const LogString *Logger::get_uart_selection_() { - switch (this->uart_) { - case UART_SELECTION_UART0: - return LOG_STR("UART0"); - case UART_SELECTION_UART1: - return LOG_STR("UART1"); - case UART_SELECTION_UART0_SWAP: - default: - return LOG_STR("UART0_SWAP"); +#if defined(USE_ESP8266_LOGGER_SERIAL) + if (this->uart_ == UART_SELECTION_UART0_SWAP) { + return LOG_STR("UART0_SWAP"); } + return LOG_STR("UART0"); +#elif defined(USE_ESP8266_LOGGER_SERIAL1) + return LOG_STR("UART1"); +#else + return LOG_STR("NONE"); +#endif } } // namespace esphome::logger diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 9ec95964ec..31e37a06e0 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -378,6 +378,28 @@ async def to_code(config): if CONF_DEBUG in config: await debug_to_code(config[CONF_DEBUG], var) + # ESP8266: Enable the Arduino Serial objects that might be used based on pin config + # The C++ code selects hardware serial at runtime based on these pin combinations: + # - Serial (UART0): TX=1 or null, RX=3 or null + # - Serial (UART0 swap): TX=15 or null, RX=13 or null + # - Serial1: TX=2 or null, RX=8 or null + if CORE.is_esp8266: + from esphome.components.esp8266.const import enable_serial, enable_serial1 + + tx_num = config[CONF_TX_PIN][CONF_NUMBER] if CONF_TX_PIN in config else None + rx_num = config[CONF_RX_PIN][CONF_NUMBER] if CONF_RX_PIN in config else None + + # Check if this config could use Serial (UART0 regular or swap) + if (tx_num is None or tx_num in (1, 15)) and ( + rx_num is None or rx_num in (3, 13) + ): + enable_serial() + cg.add_define("USE_ESP8266_UART_SERIAL") + # Check if this config could use Serial1 + if (tx_num is None or tx_num == 2) and (rx_num is None or rx_num == 8): + enable_serial1() + cg.add_define("USE_ESP8266_UART_SERIAL1") + CORE.add_job(final_step) diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index c78daa7462..504d494e2e 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -75,6 +75,7 @@ void ESP8266UartComponent::setup() { // is 1 we still want to use Serial. SerialConfig config = static_cast(get_config()); +#ifdef USE_ESP8266_UART_SERIAL if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 1) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 3) #ifdef USE_LOGGER @@ -100,11 +101,16 @@ void ESP8266UartComponent::setup() { this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); this->hw_serial_->swap(); ESP8266UartComponent::serial0_in_use = true; - } else if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) { + } else +#endif // USE_ESP8266_UART_SERIAL +#ifdef USE_ESP8266_UART_SERIAL1 + if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) { this->hw_serial_ = &Serial1; this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); - } else { + } else +#endif // USE_ESP8266_UART_SERIAL1 + { this->sw_serial_ = new ESP8266SoftwareSerial(); // NOLINT this->sw_serial_->setup(tx_pin_, rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_, this->rx_buffer_size_); diff --git a/esphome/core/defines.h b/esphome/core/defines.h index cee46a2df0..69684fd5c9 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -248,7 +248,11 @@ #define USE_ADC_SENSOR_VCC #define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 1, 2) #define USE_CAPTIVE_PORTAL +#define USE_ESP8266_LOGGER_SERIAL +#define USE_ESP8266_LOGGER_SERIAL1 #define USE_ESP8266_PREFERENCES_FLASH +#define USE_ESP8266_UART_SERIAL +#define USE_ESP8266_UART_SERIAL1 #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_HTTP_REQUEST_RESPONSE #define USE_I2C From 110c892c3c6f6a663838a94eee43be4921202351 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:35:04 -1000 Subject: [PATCH 1081/1145] [esp8266] Avoid heap allocation in preferences save/load (#12465) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/esp8266/preferences.cpp | 131 ++++++++++++--------- 1 file changed, 77 insertions(+), 54 deletions(-) diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index 197d244dc4..47987b4a95 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -1,7 +1,6 @@ #ifdef USE_ESP8266 #include -#include extern "C" { #include "spi_flash.h" } @@ -27,6 +26,16 @@ static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; +// RTC memory layout for preferences: +// - Eboot region: RTC words 0-31 (reserved, mapped from preference offset 96-127) +// - Normal region: RTC words 32-127 (mapped from preference offset 0-95) +static constexpr uint32_t RTC_EBOOT_REGION_WORDS = 32; // Words 0-31 reserved for eboot +static constexpr uint32_t RTC_NORMAL_REGION_WORDS = 96; // Words 32-127 for normal prefs +static constexpr uint32_t PREF_TOTAL_WORDS = RTC_EBOOT_REGION_WORDS + RTC_NORMAL_REGION_WORDS; // 128 + +// Maximum preference size in words (limited by uint8_t length_words field) +static constexpr uint32_t MAX_PREFERENCE_WORDS = 255; + #define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START) #ifdef USE_ESP8266_PREFERENCES_FLASH @@ -118,6 +127,10 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) { return true; } +// Stack buffer size - 16 words total: up to 15 words of preference data + 1 word CRC (60 bytes of preference data) +// This handles virtually all real-world preferences without heap allocation +static constexpr size_t PREF_BUFFER_WORDS = 16; + class ESP8266PreferenceBackend : public ESPPreferenceBackend { public: uint32_t type = 0; @@ -126,36 +139,54 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend { bool in_flash = false; bool save(const uint8_t *data, size_t len) override { - if (bytes_to_words(len) != length_words) { + if (bytes_to_words(len) != this->length_words) return false; - } - size_t buffer_size = static_cast(length_words) + 1; - std::unique_ptr buffer(new uint32_t[buffer_size]()); // Note the () for zero-initialization - memcpy(buffer.get(), data, len); - buffer[length_words] = calculate_crc(buffer.get(), buffer.get() + length_words, type); - if (in_flash) { - return save_to_flash(offset, buffer.get(), buffer_size); + const size_t buffer_size = static_cast(this->length_words) + 1; + uint32_t stack_buffer[PREF_BUFFER_WORDS]; + std::unique_ptr heap_buffer; + uint32_t *buffer; + + if (buffer_size <= PREF_BUFFER_WORDS) { + buffer = stack_buffer; + } else { + heap_buffer = make_unique(buffer_size); + buffer = heap_buffer.get(); } - return save_to_rtc(offset, buffer.get(), buffer_size); + memset(buffer, 0, buffer_size * sizeof(uint32_t)); + + memcpy(buffer, data, len); + buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type); + + return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size) + : save_to_rtc(this->offset, buffer, buffer_size); } + bool load(uint8_t *data, size_t len) override { - if (bytes_to_words(len) != length_words) { + if (bytes_to_words(len) != this->length_words) return false; + + const size_t buffer_size = static_cast(this->length_words) + 1; + uint32_t stack_buffer[PREF_BUFFER_WORDS]; + std::unique_ptr heap_buffer; + uint32_t *buffer; + + if (buffer_size <= PREF_BUFFER_WORDS) { + buffer = stack_buffer; + } else { + heap_buffer = make_unique(buffer_size); + buffer = heap_buffer.get(); } - size_t buffer_size = static_cast(length_words) + 1; - std::unique_ptr buffer(new uint32_t[buffer_size]()); - bool ret = in_flash ? load_from_flash(offset, buffer.get(), buffer_size) - : load_from_rtc(offset, buffer.get(), buffer_size); + + bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size) + : load_from_rtc(this->offset, buffer, buffer_size); if (!ret) return false; - uint32_t crc = calculate_crc(buffer.get(), buffer.get() + length_words, type); - if (buffer[length_words] != crc) { + if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type)) return false; - } - memcpy(data, buffer.get(), len); + memcpy(data, buffer, len); return true; } }; @@ -176,50 +207,42 @@ class ESP8266Preferences : public ESPPreferences { } ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - uint32_t length_words = bytes_to_words(length); - if (length_words > 255) { - ESP_LOGE(TAG, "Preference too large: %" PRIu32 " words > 255", length_words); + const uint32_t length_words = bytes_to_words(length); + if (length_words > MAX_PREFERENCE_WORDS) { + ESP_LOGE(TAG, "Preference too large: %u words", static_cast(length_words)); return {}; } + + const uint32_t total_words = length_words + 1; // +1 for CRC + uint16_t offset; + if (in_flash) { - uint32_t start = current_flash_offset; - uint32_t end = start + length_words + 1; - if (end > ESP8266_FLASH_STORAGE_SIZE) + if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE) return {}; - auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->offset = static_cast(start); - pref->type = type; - pref->length_words = static_cast(length_words); - pref->in_flash = true; - current_flash_offset = end; - return {pref}; + offset = static_cast(this->current_flash_offset); + this->current_flash_offset += total_words; + } else { + uint32_t start = this->current_offset; + bool in_normal = start < RTC_NORMAL_REGION_WORDS; + // Normal: offset 0-95 maps to RTC offset 32-127 + // Eboot: offset 96-127 maps to RTC offset 0-31 + if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) { + // start is in normal but end is not -> switch to Eboot + this->current_offset = start = RTC_NORMAL_REGION_WORDS; + in_normal = false; + } + if (start + total_words > PREF_TOTAL_WORDS) + return {}; // Doesn't fit in RTC memory + // Convert preference offset to RTC memory offset + offset = static_cast(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS); + this->current_offset = start + total_words; } - uint32_t start = current_offset; - uint32_t end = start + length_words + 1; - bool in_normal = start < 96; - // Normal: offset 0-95 maps to RTC offset 32 - 127, - // Eboot: offset 96-127 maps to RTC offset 0 - 31 words - if (in_normal && end > 96) { - // start is in normal but end is not -> switch to Eboot - current_offset = start = 96; - end = start + length_words + 1; - in_normal = false; - } - - if (end > 128) { - // Doesn't fit in data, return uninitialized preference obj. - return {}; - } - - uint32_t rtc_offset = in_normal ? start + 32 : start - 96; - auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->offset = static_cast(rtc_offset); + pref->offset = offset; pref->type = type; pref->length_words = static_cast(length_words); - pref->in_flash = false; - current_offset += length_words + 1; + pref->in_flash = in_flash; return pref; } From 84dd17187ddfdcf45325ceb936d6b7be09e39919 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:35:22 -1000 Subject: [PATCH 1082/1145] [pvvx_mithermometer] Reduce heap allocations with stack-based string formatting (#12994) --- .../pvvx_mithermometer/display/pvvx_display.cpp | 7 +++++-- .../pvvx_mithermometer/pvvx_mithermometer.cpp | 10 ++++++---- .../components/pvvx_mithermometer/pvvx_mithermometer.h | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp index 8436633619..4d4a5466bb 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp @@ -1,4 +1,5 @@ #include "pvvx_display.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,14 +9,16 @@ namespace pvvx_mithermometer { static const char *const TAG = "display.pvvx_mithermometer"; void PVVXDisplay::dump_config() { + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGCONFIG(TAG, "PVVX MiThermometer display:\n" " MAC address : %s\n" " Service UUID : %s\n" " Characteristic UUID : %s\n" " Auto clear : %s", - this->parent_->address_str(), this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str(), YESNO(this->auto_clear_enabled_)); + this->parent_->address_str(), this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf), YESNO(this->auto_clear_enabled_)); #ifdef USE_TIME ESP_LOGCONFIG(TAG, " Set time on connection: %s", YESNO(this->time_ != nullptr)); #endif diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp index 6975109952..5712447909 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp @@ -21,7 +21,9 @@ bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &devic ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -32,7 +34,7 @@ bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &devic if (!(parse_message_(service_data.data, *res))) { continue; } - if (!(report_results_(res, device.address_str()))) { + if (!(report_results_(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -111,13 +113,13 @@ bool PVVXMiThermometer::parse_message_(const std::vector &message, Pars return true; } -bool PVVXMiThermometer::report_results_(const optional &result, const std::string &address) { +bool PVVXMiThermometer::report_results_(const optional &result, const char *address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; } - ESP_LOGD(TAG, "Got PVVX MiThermometer (%s):", address.c_str()); + ESP_LOGD(TAG, "Got PVVX MiThermometer (%s):", address); if (result->temperature.has_value()) { ESP_LOGD(TAG, " Temperature: %.2f °C", *result->temperature); diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h index 9614a3c586..c15e1e7e22 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h @@ -41,7 +41,7 @@ class PVVXMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevic optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); bool parse_message_(const std::vector &message, ParseResult &result); - bool report_results_(const optional &result, const std::string &address); + bool report_results_(const optional &result, const char *address); }; } // namespace pvvx_mithermometer From 28cf3b7a9b94fe5720ad720abe3d3055be6ab539 Mon Sep 17 00:00:00 2001 From: Jas Strong Date: Mon, 5 Jan 2026 19:35:32 -0800 Subject: [PATCH 1083/1145] [rd03d] Add Ai-Thinker RD-03D mmWave radar component (#12764) Co-authored-by: jas Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/rd03d/__init__.py | 50 ++++ esphome/components/rd03d/binary_sensor.py | 39 +++ esphome/components/rd03d/rd03d.cpp | 243 +++++++++++++++++++ esphome/components/rd03d/rd03d.h | 93 +++++++ esphome/components/rd03d/sensor.py | 105 ++++++++ tests/components/rd03d/common.yaml | 59 +++++ tests/components/rd03d/test.esp32-idf.yaml | 4 + tests/components/rd03d/test.esp8266-ard.yaml | 4 + tests/components/rd03d/test.rp2040-ard.yaml | 4 + 10 files changed, 602 insertions(+) create mode 100644 esphome/components/rd03d/__init__.py create mode 100644 esphome/components/rd03d/binary_sensor.py create mode 100644 esphome/components/rd03d/rd03d.cpp create mode 100644 esphome/components/rd03d/rd03d.h create mode 100644 esphome/components/rd03d/sensor.py create mode 100644 tests/components/rd03d/common.yaml create mode 100644 tests/components/rd03d/test.esp32-idf.yaml create mode 100644 tests/components/rd03d/test.esp8266-ard.yaml create mode 100644 tests/components/rd03d/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index a2267621e7..bdcc86ef0c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -395,6 +395,7 @@ esphome/components/radon_eye_rd200/* @jeffeb3 esphome/components/rc522/* @glmnet esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_spi/* @glmnet +esphome/components/rd03d/* @jasstrong esphome/components/resampler/speaker/* @kahrendt esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz diff --git a/esphome/components/rd03d/__init__.py b/esphome/components/rd03d/__init__.py new file mode 100644 index 0000000000..52e9a2c09a --- /dev/null +++ b/esphome/components/rd03d/__init__.py @@ -0,0 +1,50 @@ +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_THROTTLE + +CODEOWNERS = ["@jasstrong"] +DEPENDENCIES = ["uart"] +MULTI_CONF = True + +CONF_RD03D_ID = "rd03d_id" +CONF_TRACKING_MODE = "tracking_mode" + +rd03d_ns = cg.esphome_ns.namespace("rd03d") +RD03DComponent = rd03d_ns.class_("RD03DComponent", cg.Component, uart.UARTDevice) +TrackingMode = rd03d_ns.enum("TrackingMode", is_class=True) + +TRACKING_MODES = { + "single": TrackingMode.SINGLE_TARGET, + "multi": TrackingMode.MULTI_TARGET, +} + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(RD03DComponent), + cv.Optional(CONF_TRACKING_MODE): cv.enum(TRACKING_MODES, lower=True), + cv.Optional(CONF_THROTTLE): cv.positive_time_period_milliseconds, + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "rd03d", + require_tx=False, + require_rx=True, +) + + +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 CONF_TRACKING_MODE in config: + cg.add(var.set_tracking_mode(config[CONF_TRACKING_MODE])) + + if CONF_THROTTLE in config: + cg.add(var.set_throttle(config[CONF_THROTTLE])) diff --git a/esphome/components/rd03d/binary_sensor.py b/esphome/components/rd03d/binary_sensor.py new file mode 100644 index 0000000000..afb7527aa1 --- /dev/null +++ b/esphome/components/rd03d/binary_sensor.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import CONF_TARGET, DEVICE_CLASS_OCCUPANCY + +from . import CONF_RD03D_ID, RD03DComponent + +DEPENDENCIES = ["rd03d"] + +MAX_TARGETS = 3 + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_RD03D_ID): cv.use_id(RD03DComponent), + cv.Optional(CONF_TARGET): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY, + ), + } +).extend( + { + cv.Optional(f"target_{i + 1}"): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY, + ) + for i in range(MAX_TARGETS) + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_RD03D_ID]) + + if target_config := config.get(CONF_TARGET): + sens = await binary_sensor.new_binary_sensor(target_config) + cg.add(hub.set_target_binary_sensor(sens)) + + for i in range(MAX_TARGETS): + if target_config := config.get(f"target_{i + 1}"): + sens = await binary_sensor.new_binary_sensor(target_config) + cg.add(hub.set_target_binary_sensor(i, sens)) diff --git a/esphome/components/rd03d/rd03d.cpp b/esphome/components/rd03d/rd03d.cpp new file mode 100644 index 0000000000..44e479c153 --- /dev/null +++ b/esphome/components/rd03d/rd03d.cpp @@ -0,0 +1,243 @@ +#include "rd03d.h" +#include "esphome/core/log.h" +#include + +namespace esphome::rd03d { + +static const char *const TAG = "rd03d"; + +// Delay before sending configuration commands to allow radar to initialize +static constexpr uint32_t SETUP_TIMEOUT_MS = 100; + +// Data frame format (radar -> host) +static constexpr uint8_t FRAME_HEADER[] = {0xAA, 0xFF, 0x03, 0x00}; +static constexpr uint8_t FRAME_FOOTER[] = {0x55, 0xCC}; + +// Command frame format (host -> radar) +static constexpr uint8_t CMD_FRAME_HEADER[] = {0xFD, 0xFC, 0xFB, 0xFA}; +static constexpr uint8_t CMD_FRAME_FOOTER[] = {0x04, 0x03, 0x02, 0x01}; + +// RD-03D tracking mode commands +static constexpr uint16_t CMD_SINGLE_TARGET = 0x0080; +static constexpr uint16_t CMD_MULTI_TARGET = 0x0090; + +// Decode coordinate/speed value from RD-03D format +// Per datasheet: MSB=1 means positive, MSB=0 means negative +static constexpr int16_t decode_value(uint8_t low_byte, uint8_t high_byte) { + int16_t value = ((high_byte & 0x7F) << 8) | low_byte; + if ((high_byte & 0x80) == 0) { + value = -value; + } + return value; +} + +void RD03DComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up RD-03D..."); + this->set_timeout(SETUP_TIMEOUT_MS, [this]() { this->apply_config_(); }); +} + +void RD03DComponent::dump_config() { + ESP_LOGCONFIG(TAG, "RD-03D:"); + if (this->tracking_mode_.has_value()) { + ESP_LOGCONFIG(TAG, " Tracking Mode: %s", + *this->tracking_mode_ == TrackingMode::SINGLE_TARGET ? "single" : "multi"); + } + if (this->throttle_ > 0) { + ESP_LOGCONFIG(TAG, " Throttle: %ums", this->throttle_); + } +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Target Count", this->target_count_sensor_); +#endif +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_); +#endif + for (uint8_t i = 0; i < MAX_TARGETS; i++) { + ESP_LOGCONFIG(TAG, " Target %d:", i + 1); +#ifdef USE_SENSOR + LOG_SENSOR(" ", "X", this->targets_[i].x); + LOG_SENSOR(" ", "Y", this->targets_[i].y); + LOG_SENSOR(" ", "Speed", this->targets_[i].speed); + LOG_SENSOR(" ", "Distance", this->targets_[i].distance); + LOG_SENSOR(" ", "Resolution", this->targets_[i].resolution); + LOG_SENSOR(" ", "Angle", this->targets_[i].angle); +#endif +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Presence", this->target_presence_[i]); +#endif + } +} + +void RD03DComponent::loop() { + while (this->available()) { + uint8_t byte = this->read(); + ESP_LOGVV(TAG, "Received byte: 0x%02X, buffer_pos: %d", byte, this->buffer_pos_); + + // Check if we're looking for frame header + if (this->buffer_pos_ < FRAME_HEADER_SIZE) { + if (byte == FRAME_HEADER[this->buffer_pos_]) { + this->buffer_[this->buffer_pos_++] = byte; + } else if (byte == FRAME_HEADER[0]) { + // Start over if we see a potential new header + this->buffer_[0] = byte; + this->buffer_pos_ = 1; + } else { + this->buffer_pos_ = 0; + } + continue; + } + + // Accumulate data bytes + this->buffer_[this->buffer_pos_++] = byte; + + // Check if we have a complete frame + if (this->buffer_pos_ == FRAME_SIZE) { + // Validate footer + if (this->buffer_[FRAME_SIZE - 2] == FRAME_FOOTER[0] && this->buffer_[FRAME_SIZE - 1] == FRAME_FOOTER[1]) { + this->process_frame_(); + } else { + ESP_LOGW(TAG, "Invalid frame footer: 0x%02X 0x%02X (expected 0x55 0xCC)", this->buffer_[FRAME_SIZE - 2], + this->buffer_[FRAME_SIZE - 1]); + } + this->buffer_pos_ = 0; + } + } +} + +void RD03DComponent::process_frame_() { + // Apply throttle if configured + if (this->throttle_ > 0) { + uint32_t now = millis(); + if (now - this->last_publish_time_ < this->throttle_) { + return; + } + this->last_publish_time_ = now; + } + + uint8_t target_count = 0; + + for (uint8_t i = 0; i < MAX_TARGETS; i++) { + // Calculate offset for this target's data + // Header is 4 bytes, each target is 8 bytes + uint8_t offset = FRAME_HEADER_SIZE + (i * TARGET_DATA_SIZE); + + // Extract raw bytes for this target + uint8_t x_low = this->buffer_[offset + 0]; + uint8_t x_high = this->buffer_[offset + 1]; + uint8_t y_low = this->buffer_[offset + 2]; + uint8_t y_high = this->buffer_[offset + 3]; + uint8_t speed_low = this->buffer_[offset + 4]; + uint8_t speed_high = this->buffer_[offset + 5]; + uint8_t res_low = this->buffer_[offset + 6]; + uint8_t res_high = this->buffer_[offset + 7]; + + // Decode values per RD-03D format + int16_t x = decode_value(x_low, x_high); + int16_t y = decode_value(y_low, y_high); + int16_t speed = decode_value(speed_low, speed_high); + uint16_t resolution = (res_high << 8) | res_low; + + // Check if target is present (non-zero coordinates) + bool target_present = (x != 0 || y != 0); + if (target_present) { + target_count++; + } + +#ifdef USE_SENSOR + this->publish_target_(i, x, y, speed, resolution); +#endif + +#ifdef USE_BINARY_SENSOR + if (this->target_presence_[i] != nullptr) { + this->target_presence_[i]->publish_state(target_present); + } +#endif + } + +#ifdef USE_SENSOR + if (this->target_count_sensor_ != nullptr) { + this->target_count_sensor_->publish_state(target_count); + } +#endif + +#ifdef USE_BINARY_SENSOR + if (this->target_binary_sensor_ != nullptr) { + this->target_binary_sensor_->publish_state(target_count > 0); + } +#endif +} + +#ifdef USE_SENSOR +void RD03DComponent::publish_target_(uint8_t target_num, int16_t x, int16_t y, int16_t speed, uint16_t resolution) { + TargetSensor &target = this->targets_[target_num]; + + // Publish X coordinate (mm) + if (target.x != nullptr) { + target.x->publish_state(x); + } + + // Publish Y coordinate (mm) + if (target.y != nullptr) { + target.y->publish_state(y); + } + + // Publish speed (convert from cm/s to mm/s) + if (target.speed != nullptr) { + target.speed->publish_state(static_cast(speed) * 10.0f); + } + + // Publish resolution (mm) + if (target.resolution != nullptr) { + target.resolution->publish_state(resolution); + } + + // Calculate and publish distance (mm) + if (target.distance != nullptr) { + float distance = std::hypot(static_cast(x), static_cast(y)); + target.distance->publish_state(distance); + } + + // Calculate and publish angle (degrees) + // Angle is measured from the Y axis (radar forward direction) + if (target.angle != nullptr) { + if (x == 0 && y == 0) { + target.angle->publish_state(0); + } else { + float angle = std::atan2(static_cast(x), static_cast(y)) * 180.0f / M_PI; + target.angle->publish_state(angle); + } + } +} +#endif + +void RD03DComponent::send_command_(uint16_t command, const uint8_t *data, uint8_t data_len) { + // Send header + this->write_array(CMD_FRAME_HEADER, sizeof(CMD_FRAME_HEADER)); + + // Send length (command word + data) + uint16_t len = 2 + data_len; + this->write_byte(len & 0xFF); + this->write_byte((len >> 8) & 0xFF); + + // Send command word (little-endian) + this->write_byte(command & 0xFF); + this->write_byte((command >> 8) & 0xFF); + + // Send data if any + if (data != nullptr && data_len > 0) { + this->write_array(data, data_len); + } + + // Send footer + this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)); + + ESP_LOGD(TAG, "Sent command 0x%04X with %d bytes of data", command, data_len); +} + +void RD03DComponent::apply_config_() { + if (this->tracking_mode_.has_value()) { + uint16_t mode_cmd = (*this->tracking_mode_ == TrackingMode::SINGLE_TARGET) ? CMD_SINGLE_TARGET : CMD_MULTI_TARGET; + this->send_command_(mode_cmd); + } +} + +} // namespace esphome::rd03d diff --git a/esphome/components/rd03d/rd03d.h b/esphome/components/rd03d/rd03d.h new file mode 100644 index 0000000000..7413fe38f2 --- /dev/null +++ b/esphome/components/rd03d/rd03d.h @@ -0,0 +1,93 @@ +#pragma once + +#include "esphome/core/defines.h" +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif + +#include + +namespace esphome::rd03d { + +static constexpr uint8_t MAX_TARGETS = 3; +static constexpr uint8_t FRAME_HEADER_SIZE = 4; +static constexpr uint8_t FRAME_FOOTER_SIZE = 2; +static constexpr uint8_t TARGET_DATA_SIZE = 8; +static constexpr uint8_t FRAME_SIZE = + FRAME_HEADER_SIZE + (MAX_TARGETS * TARGET_DATA_SIZE) + FRAME_FOOTER_SIZE; // 30 bytes + +enum class TrackingMode : uint8_t { + SINGLE_TARGET = 0, + MULTI_TARGET = 1, +}; + +#ifdef USE_SENSOR +struct TargetSensor { + sensor::Sensor *x{nullptr}; + sensor::Sensor *y{nullptr}; + sensor::Sensor *speed{nullptr}; + sensor::Sensor *distance{nullptr}; + sensor::Sensor *resolution{nullptr}; + sensor::Sensor *angle{nullptr}; +}; +#endif + +class RD03DComponent : public Component, public uart::UARTDevice { + public: + void setup() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + +#ifdef USE_SENSOR + void set_target_count_sensor(sensor::Sensor *sensor) { this->target_count_sensor_ = sensor; } + void set_x_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].x = sensor; } + void set_y_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].y = sensor; } + void set_speed_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].speed = sensor; } + void set_distance_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].distance = sensor; } + void set_resolution_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].resolution = sensor; } + void set_angle_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].angle = sensor; } +#endif +#ifdef USE_BINARY_SENSOR + void set_target_binary_sensor(binary_sensor::BinarySensor *sensor) { this->target_binary_sensor_ = sensor; } + void set_target_binary_sensor(uint8_t target, binary_sensor::BinarySensor *sensor) { + this->target_presence_[target] = sensor; + } +#endif + + // Configuration setters (called from code generation) + void set_tracking_mode(TrackingMode mode) { this->tracking_mode_ = mode; } + void set_throttle(uint32_t throttle) { this->throttle_ = throttle; } + + protected: + void apply_config_(); + void send_command_(uint16_t command, const uint8_t *data = nullptr, uint8_t data_len = 0); + void process_frame_(); +#ifdef USE_SENSOR + void publish_target_(uint8_t target_num, int16_t x, int16_t y, int16_t speed, uint16_t resolution); +#endif + +#ifdef USE_SENSOR + std::array targets_{}; + sensor::Sensor *target_count_sensor_{nullptr}; +#endif +#ifdef USE_BINARY_SENSOR + std::array target_presence_{}; + binary_sensor::BinarySensor *target_binary_sensor_{nullptr}; +#endif + + // Configuration (only sent if explicitly set) + optional tracking_mode_{}; + uint32_t throttle_{0}; + uint32_t last_publish_time_{0}; + + std::array buffer_{}; + uint8_t buffer_pos_{0}; +}; + +} // namespace esphome::rd03d diff --git a/esphome/components/rd03d/sensor.py b/esphome/components/rd03d/sensor.py new file mode 100644 index 0000000000..4b4fcfd4e4 --- /dev/null +++ b/esphome/components/rd03d/sensor.py @@ -0,0 +1,105 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_ANGLE, + CONF_DISTANCE, + CONF_RESOLUTION, + CONF_SPEED, + CONF_X, + CONF_Y, + DEVICE_CLASS_DISTANCE, + DEVICE_CLASS_SPEED, + STATE_CLASS_MEASUREMENT, + UNIT_DEGREES, + UNIT_MILLIMETER, +) + +from . import CONF_RD03D_ID, RD03DComponent + +DEPENDENCIES = ["rd03d"] + +CONF_TARGET_COUNT = "target_count" + +MAX_TARGETS = 3 + +UNIT_MILLIMETER_PER_SECOND = "mm/s" + +TARGET_SCHEMA = cv.Schema( + { + cv.Optional(CONF_X): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_Y): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_SPEED): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER_PER_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_SPEED, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DISTANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_RESOLUTION): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ANGLE): sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREES, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ), + } +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_RD03D_ID): cv.use_id(RD03DComponent), + cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + } +).extend({cv.Optional(f"target_{i + 1}"): TARGET_SCHEMA for i in range(MAX_TARGETS)}) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_RD03D_ID]) + + if target_count_config := config.get(CONF_TARGET_COUNT): + sens = await sensor.new_sensor(target_count_config) + cg.add(hub.set_target_count_sensor(sens)) + + for i in range(MAX_TARGETS): + if target_config := config.get(f"target_{i + 1}"): + if x_config := target_config.get(CONF_X): + sens = await sensor.new_sensor(x_config) + cg.add(hub.set_x_sensor(i, sens)) + if y_config := target_config.get(CONF_Y): + sens = await sensor.new_sensor(y_config) + cg.add(hub.set_y_sensor(i, sens)) + if speed_config := target_config.get(CONF_SPEED): + sens = await sensor.new_sensor(speed_config) + cg.add(hub.set_speed_sensor(i, sens)) + if distance_config := target_config.get(CONF_DISTANCE): + sens = await sensor.new_sensor(distance_config) + cg.add(hub.set_distance_sensor(i, sens)) + if resolution_config := target_config.get(CONF_RESOLUTION): + sens = await sensor.new_sensor(resolution_config) + cg.add(hub.set_resolution_sensor(i, sens)) + if angle_config := target_config.get(CONF_ANGLE): + sens = await sensor.new_sensor(angle_config) + cg.add(hub.set_angle_sensor(i, sens)) diff --git a/tests/components/rd03d/common.yaml b/tests/components/rd03d/common.yaml new file mode 100644 index 0000000000..b13d899ab3 --- /dev/null +++ b/tests/components/rd03d/common.yaml @@ -0,0 +1,59 @@ +rd03d: + id: rd03d_radar + +sensor: + - platform: rd03d + rd03d_id: rd03d_radar + target_count: + name: Target Count + target_1: + x: + name: Target-1 X + y: + name: Target-1 Y + speed: + name: Target-1 Speed + angle: + name: Target-1 Angle + distance: + name: Target-1 Distance + resolution: + name: Target-1 Resolution + target_2: + x: + name: Target-2 X + y: + name: Target-2 Y + speed: + name: Target-2 Speed + angle: + name: Target-2 Angle + distance: + name: Target-2 Distance + resolution: + name: Target-2 Resolution + target_3: + x: + name: Target-3 X + y: + name: Target-3 Y + speed: + name: Target-3 Speed + angle: + name: Target-3 Angle + distance: + name: Target-3 Distance + resolution: + name: Target-3 Resolution + +binary_sensor: + - platform: rd03d + rd03d_id: rd03d_radar + target: + name: Presence + target_1: + name: Target-1 Presence + target_2: + name: Target-2 Presence + target_3: + name: Target-3 Presence diff --git a/tests/components/rd03d/test.esp32-idf.yaml b/tests/components/rd03d/test.esp32-idf.yaml new file mode 100644 index 0000000000..2d29656c94 --- /dev/null +++ b/tests/components/rd03d/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/rd03d/test.esp8266-ard.yaml b/tests/components/rd03d/test.esp8266-ard.yaml new file mode 100644 index 0000000000..5a05efa259 --- /dev/null +++ b/tests/components/rd03d/test.esp8266-ard.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/rd03d/test.rp2040-ard.yaml b/tests/components/rd03d/test.rp2040-ard.yaml new file mode 100644 index 0000000000..f1df2daf83 --- /dev/null +++ b/tests/components/rd03d/test.rp2040-ard.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml + +<<: !include common.yaml From 22cb0da903aea0c372144b1eb1563d66f79ab1f0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Jan 2026 17:45:51 -1000 Subject: [PATCH 1084/1145] [radon_eye_rd200, radon_eye_ble] Use stack-based string formatting in logging (#12991) --- .../components/radon_eye_rd200/radon_eye_rd200.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp index 1bd0b842fe..f2d32d51de 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -1,4 +1,5 @@ #include "radon_eye_rd200.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #include @@ -60,16 +61,20 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->read_handle_ = 0; auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_read_characteristic_uuid_); if (chr == nullptr) { - ESP_LOGW(TAG, "No sensor read characteristic found at service %s char %s", service_uuid_.to_string().c_str(), - sensors_read_characteristic_uuid_.to_string().c_str()); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No sensor read characteristic found at service %s char %s", service_uuid_.to_str(service_buf), + sensors_read_characteristic_uuid_.to_str(char_buf)); break; } this->read_handle_ = chr->handle; auto *write_chr = this->parent()->get_characteristic(service_uuid_, sensors_write_characteristic_uuid_); if (write_chr == nullptr) { - ESP_LOGW(TAG, "No sensor write characteristic found at service %s char %s", service_uuid_.to_string().c_str(), - sensors_write_characteristic_uuid_.to_string().c_str()); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No sensor write characteristic found at service %s char %s", service_uuid_.to_str(service_buf), + sensors_write_characteristic_uuid_.to_str(char_buf)); break; } this->write_handle_ = write_chr->handle; From 484f4b3aadbcf9e97c21bbdaf35265946b7fda9c Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 6 Jan 2026 09:34:28 -0500 Subject: [PATCH 1085/1145] [cc1101] Add PLL lock verification and retry support (#13006) --- esphome/components/cc1101/cc1101.cpp | 67 +++++++++++++++++++------- esphome/components/cc1101/cc1101.h | 5 +- esphome/components/cc1101/cc1101defs.h | 3 ++ 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 5727d485f6..c4507a54e5 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -140,7 +140,10 @@ void CC1101Component::setup() { this->write_(static_cast(i)); } this->set_output_power(this->output_power_requested_); - this->strobe_(Command::RX); + if (!this->enter_rx_()) { + this->mark_failed(); + return; + } // Defer pin mode setup until after all components have completed setup() // This handles the case where remote_transmitter runs after CC1101 and changes pin mode @@ -163,8 +166,7 @@ void CC1101Component::loop() { ESP_LOGW(TAG, "RX FIFO overflow, flushing"); this->enter_idle_(); this->strobe_(Command::FRX); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return; } @@ -181,8 +183,7 @@ void CC1101Component::loop() { ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length); this->enter_idle_(); this->strobe_(Command::FRX); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return; } this->packet_.resize(payload_length); @@ -203,8 +204,7 @@ void CC1101Component::loop() { // Return to rx this->enter_idle_(); this->strobe_(Command::FRX); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); } void CC1101Component::dump_config() { @@ -235,9 +235,8 @@ void CC1101Component::begin_tx() { if (this->gdo0_pin_ != nullptr) { this->gdo0_pin_->pin_mode(gpio::FLAG_OUTPUT); } - this->strobe_(Command::TX); - if (!this->wait_for_state_(State::TX, 50)) { - ESP_LOGW(TAG, "Timed out waiting for TX state!"); + if (!this->enter_tx_()) { + ESP_LOGW(TAG, "Failed to enter TX state!"); } } @@ -246,7 +245,9 @@ void CC1101Component::begin_rx() { if (this->gdo0_pin_ != nullptr) { this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT); } - this->strobe_(Command::RX); + if (!this->enter_rx_()) { + ESP_LOGW(TAG, "Failed to enter RX state!"); + } } void CC1101Component::reset() { @@ -272,11 +273,33 @@ bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) { return false; } +bool CC1101Component::enter_calibrated_(State target_state, Command cmd) { + // The PLL must be recalibrated until PLL lock is achieved + for (uint8_t retries = PLL_LOCK_RETRIES; retries > 0; retries--) { + this->strobe_(cmd); + if (!this->wait_for_state_(target_state)) { + return false; + } + this->read_(Register::FSCAL1); + if (this->state_.FSCAL1 != FSCAL1_PLL_NOT_LOCKED) { + return true; + } + ESP_LOGW(TAG, "PLL lock failed, retrying calibration"); + this->enter_idle_(); + } + ESP_LOGE(TAG, "PLL lock failed after retries"); + return false; +} + void CC1101Component::enter_idle_() { this->strobe_(Command::IDLE); this->wait_for_state_(State::IDLE); } +bool CC1101Component::enter_rx_() { return this->enter_calibrated_(State::RX, Command::RX); } + +bool CC1101Component::enter_tx_() { return this->enter_calibrated_(State::TX, Command::TX); } + uint8_t CC1101Component::strobe_(Command cmd) { uint8_t index = static_cast(cmd); if (cmd < Command::RES || cmd > Command::NOP) { @@ -338,18 +361,26 @@ CC1101Error CC1101Component::transmit_packet(const std::vector &packet) this->write_(Register::FIFO, static_cast(packet.size())); } this->write_(Register::FIFO, packet.data(), packet.size()); + + // Calibrate PLL + if (!this->enter_calibrated_(State::FSTXON, Command::FSTXON)) { + ESP_LOGW(TAG, "PLL lock failed during TX"); + this->enter_idle_(); + this->enter_rx_(); + return CC1101Error::PLL_LOCK; + } + + // Transmit packet this->strobe_(Command::TX); if (!this->wait_for_state_(State::IDLE, 1000)) { ESP_LOGW(TAG, "TX timeout"); this->enter_idle_(); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return CC1101Error::TIMEOUT; } // Return to rx - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return CC1101Error::NONE; } @@ -406,7 +437,7 @@ void CC1101Component::set_frequency(float value) { this->write_(Register::FREQ2); this->write_(Register::FREQ1); this->write_(Register::FREQ0); - this->strobe_(Command::RX); + this->enter_rx_(); } } @@ -433,7 +464,7 @@ void CC1101Component::set_channel(uint8_t value) { if (this->initialized_) { this->enter_idle_(); this->write_(Register::CHANNR); - this->strobe_(Command::RX); + this->enter_rx_(); } } @@ -502,7 +533,7 @@ void CC1101Component::set_modulation_type(Modulation value) { this->set_output_power(this->output_power_requested_); this->write_(Register::MDMCFG2); this->write_(Register::FREND0); - this->strobe_(Command::RX); + this->enter_rx_(); } } diff --git a/esphome/components/cc1101/cc1101.h b/esphome/components/cc1101/cc1101.h index 9b8d4e56a8..43ae5b3612 100644 --- a/esphome/components/cc1101/cc1101.h +++ b/esphome/components/cc1101/cc1101.h @@ -9,7 +9,7 @@ namespace esphome::cc1101 { -enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW }; +enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW, PLL_LOCK }; class CC1101Component : public Component, public spi::SPIDevice Date: Thu, 1 Jan 2026 13:44:21 +1000 Subject: [PATCH 1086/1145] [lvgl] Fix arc background angles (#12773) --- esphome/components/lvgl/widgets/arc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/lvgl/widgets/arc.py b/esphome/components/lvgl/widgets/arc.py index 21530441f8..34ac9c51f7 100644 --- a/esphome/components/lvgl/widgets/arc.py +++ b/esphome/components/lvgl/widgets/arc.py @@ -85,11 +85,11 @@ class ArcType(NumberType): lv.arc_set_range(w.obj, min_value, max_value) await w.set_property( - CONF_START_ANGLE, + "bg_start_angle", await lv_angle_degrees.process(config.get(CONF_START_ANGLE)), ) await w.set_property( - CONF_END_ANGLE, await lv_angle_degrees.process(config.get(CONF_END_ANGLE)) + "bg_end_angle", await lv_angle_degrees.process(config.get(CONF_END_ANGLE)) ) await w.set_property( CONF_ROTATION, await lv_angle_degrees.process(config.get(CONF_ROTATION)) From 178a61b6fd8fc298914bc6b8dd3d3b8d1e0e6f81 Mon Sep 17 00:00:00 2001 From: Artur <130101347+aanikei@users.noreply.github.com> Date: Fri, 2 Jan 2026 04:28:10 +0000 Subject: [PATCH 1087/1145] [sn74hc595]: fix 'Attempted read from write-only channel' when using esp-idf framework (#12801) --- esphome/components/sn74hc595/sn74hc595.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sn74hc595/sn74hc595.cpp b/esphome/components/sn74hc595/sn74hc595.cpp index fc47a6dc5e..a9ada432e4 100644 --- a/esphome/components/sn74hc595/sn74hc595.cpp +++ b/esphome/components/sn74hc595/sn74hc595.cpp @@ -70,7 +70,7 @@ void SN74HC595GPIOComponent::write_gpio() { void SN74HC595SPIComponent::write_gpio() { for (uint8_t &output_byte : std::ranges::reverse_view(this->output_bytes_)) { this->enable(); - this->transfer_byte(output_byte); + this->write_byte(output_byte); this->disable(); } SN74HC595Component::write_gpio(); From 8b4ba8dfe66fe73957e272c9ea23ac8e1f44a315 Mon Sep 17 00:00:00 2001 From: Conrad Juhl Andersen Date: Sat, 3 Jan 2026 23:06:33 +0100 Subject: [PATCH 1088/1145] [wts01] Fix negative values for WTS01 sensor (#12835) --- esphome/components/wts01/wts01.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/esphome/components/wts01/wts01.cpp b/esphome/components/wts01/wts01.cpp index cb910d89cf..a7948c805a 100644 --- a/esphome/components/wts01/wts01.cpp +++ b/esphome/components/wts01/wts01.cpp @@ -71,17 +71,20 @@ void WTS01Sensor::process_packet_() { } // Extract temperature value - int8_t temp = this->buffer_[6]; - int32_t sign = 1; + const uint8_t raw = this->buffer_[6]; - // Handle negative temperatures - if (temp < 0) { - sign = -1; + // WTS01 encodes sign in bit 7, magnitude in bits 0-6 + const bool negative = (raw & 0x80) != 0; + const uint8_t magnitude = raw & 0x7F; + + const float decimal = static_cast(this->buffer_[7]) / 100.0f; + + float temperature = static_cast(magnitude) + decimal; + + if (negative) { + temperature = -temperature; } - // Calculate temperature (temp + decimal/100) - float temperature = static_cast(temp) + (sign * static_cast(this->buffer_[7]) / 100.0f); - ESP_LOGV(TAG, "Received new temperature: %.2f°C", temperature); this->publish_state(temperature); From 8255c02d5d4c619e0e7ab1ec6278bbde5f9b2102 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 5 Jan 2026 08:37:44 +1000 Subject: [PATCH 1089/1145] [esp32_ble] Remove requirement for configured network (#12891) --- esphome/components/esp32_ble/__init__.py | 1 - esphome/components/socket/__init__.py | 7 ++- esphome/core/__init__.py | 19 ++++++ .../socket/test_wake_loop_threadsafe.py | 35 +++++++++++ tests/unit_tests/test_core.py | 62 +++++++++++++++++++ 5 files changed, 122 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index ced7e3fec9..dcc3ce71cf 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -22,7 +22,6 @@ from esphome.core import CORE, CoroPriority, TimePeriod, coroutine_with_priority import esphome.final_validate as fv DEPENDENCIES = ["esp32"] -AUTO_LOAD = ["socket"] CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"] DOMAIN = "esp32_ble" diff --git a/esphome/components/socket/__init__.py b/esphome/components/socket/__init__.py index 49e074a6ee..e364da78f8 100644 --- a/esphome/components/socket/__init__.py +++ b/esphome/components/socket/__init__.py @@ -47,6 +47,8 @@ def require_wake_loop_threadsafe() -> None: This enables the shared UDP loopback socket mechanism (~208 bytes RAM). The socket is shared across all components that use this feature. + This call is a no-op if networking is not enabled in the configuration. + IMPORTANT: This is for background thread context only, NOT ISR context. Socket operations are not safe to call from ISR handlers. @@ -56,8 +58,11 @@ def require_wake_loop_threadsafe() -> None: async def to_code(config): socket.require_wake_loop_threadsafe() """ + # Only set up once (idempotent - multiple components can call this) - if not CORE.data.get(KEY_WAKE_LOOP_THREADSAFE_REQUIRED, False): + if CORE.has_networking and not CORE.data.get( + KEY_WAKE_LOOP_THREADSAFE_REQUIRED, False + ): CORE.data[KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True cg.add_define("USE_WAKE_LOOP_THREADSAFE") # Consume 1 socket for the shared wake notification socket diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 721cd5787d..0e09d97fed 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -703,6 +703,25 @@ class EsphomeCore: def config_filename(self) -> str: return self.config_path.name + def has_at_least_one_component(self, *components: str) -> bool: + """ + Are any of the given components configured? + :param components: component names + :return: true if so + """ + if self.config is None: + raise ValueError("Config has not been loaded yet") + + return any(component in self.config for component in components) + + @property + def has_networking(self) -> bool: + """ + Is a network component configured? + :return: true if so + """ + return self.has_at_least_one_component("wifi", "ethernet", "openthread") + def relative_config_path(self, *path: str | Path) -> Path: path_ = Path(*path).expanduser() return self.config_dir / path_ diff --git a/tests/components/socket/test_wake_loop_threadsafe.py b/tests/components/socket/test_wake_loop_threadsafe.py index 45e5ea2211..b4bc95176d 100644 --- a/tests/components/socket/test_wake_loop_threadsafe.py +++ b/tests/components/socket/test_wake_loop_threadsafe.py @@ -4,6 +4,7 @@ from esphome.core import CORE def test_require_wake_loop_threadsafe__first_call() -> None: """Test that first call sets up define and consumes socket.""" + CORE.config = {"wifi": True} socket.require_wake_loop_threadsafe() # Verify CORE.data was updated @@ -17,6 +18,7 @@ def test_require_wake_loop_threadsafe__idempotent() -> None: """Test that subsequent calls are idempotent.""" # Set up initial state as if already called CORE.data[socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True + CORE.config = {"ethernet": True} # Call again - should not raise or fail socket.require_wake_loop_threadsafe() @@ -31,6 +33,7 @@ def test_require_wake_loop_threadsafe__idempotent() -> None: def test_require_wake_loop_threadsafe__multiple_calls() -> None: """Test that multiple calls only set up once.""" # Call three times + CORE.config = {"openthread": True} socket.require_wake_loop_threadsafe() socket.require_wake_loop_threadsafe() socket.require_wake_loop_threadsafe() @@ -40,3 +43,35 @@ def test_require_wake_loop_threadsafe__multiple_calls() -> None: # Verify the define was added (only once, but we can just check it exists) assert any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines) + + +def test_require_wake_loop_threadsafe__no_networking() -> None: + """Test that wake loop is NOT configured when no networking is configured.""" + # Set up config without any networking components + CORE.config = {"esphome": {"name": "test"}, "logger": {}} + + # Call require_wake_loop_threadsafe + socket.require_wake_loop_threadsafe() + + # Verify CORE.data flag was NOT set (since has_networking returns False) + assert socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED not in CORE.data + + # Verify the define was NOT added + assert not any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines) + + +def test_require_wake_loop_threadsafe__no_networking_does_not_consume_socket() -> None: + """Test that no socket is consumed when no networking is configured.""" + # Set up config without any networking components + CORE.config = {"logger": {}} + + # Track initial socket consumer state + initial_consumers = CORE.data.get(socket.KEY_SOCKET_CONSUMERS, {}) + + # Call require_wake_loop_threadsafe + socket.require_wake_loop_threadsafe() + + # Verify no socket was consumed + consumers = CORE.data.get(socket.KEY_SOCKET_CONSUMERS, {}) + assert "socket.wake_loop_threadsafe" not in consumers + assert consumers == initial_consumers diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index e52cb24831..1fc8dab358 100644 --- a/tests/unit_tests/test_core.py +++ b/tests/unit_tests/test_core.py @@ -718,3 +718,65 @@ class TestEsphomeCore: # Even though "web_server" is in loaded_integrations due to the platform, # web_port must return None because the full web_server component is not configured assert target.web_port is None + + def test_has_at_least_one_component__none_configured(self, target): + """Test has_at_least_one_component returns False when none of the components are configured.""" + target.config = {const.CONF_ESPHOME: {"name": "test"}, "logger": {}} + + assert target.has_at_least_one_component("wifi", "ethernet") is False + + def test_has_at_least_one_component__one_configured(self, target): + """Test has_at_least_one_component returns True when one component is configured.""" + target.config = {const.CONF_WIFI: {}, "logger": {}} + + assert target.has_at_least_one_component("wifi", "ethernet") is True + + def test_has_at_least_one_component__multiple_configured(self, target): + """Test has_at_least_one_component returns True when multiple components are configured.""" + target.config = { + const.CONF_WIFI: {}, + const.CONF_ETHERNET: {}, + "logger": {}, + } + + assert ( + target.has_at_least_one_component("wifi", "ethernet", "bluetooth") is True + ) + + def test_has_at_least_one_component__single_component(self, target): + """Test has_at_least_one_component works with a single component.""" + target.config = {const.CONF_MQTT: {}} + + assert target.has_at_least_one_component("mqtt") is True + assert target.has_at_least_one_component("wifi") is False + + def test_has_at_least_one_component__config_not_loaded(self, target): + """Test has_at_least_one_component raises ValueError when config is not loaded.""" + target.config = None + + with pytest.raises(ValueError, match="Config has not been loaded yet"): + target.has_at_least_one_component("wifi") + + def test_has_networking__with_wifi(self, target): + """Test has_networking returns True when wifi is configured.""" + target.config = {const.CONF_WIFI: {}} + + assert target.has_networking is True + + def test_has_networking__with_ethernet(self, target): + """Test has_networking returns True when ethernet is configured.""" + target.config = {const.CONF_ETHERNET: {}} + + assert target.has_networking is True + + def test_has_networking__with_openthread(self, target): + """Test has_networking returns True when openthread is configured.""" + target.config = {const.CONF_OPENTHREAD: {}} + + assert target.has_networking is True + + def test_has_networking__without_networking(self, target): + """Test has_networking returns False when no networking component is configured.""" + target.config = {const.CONF_ESPHOME: {"name": "test"}, "logger": {}} + + assert target.has_networking is False From 47d0d3cfeb1c447c133950ceb90ef6834e9c1a27 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 6 Jan 2026 09:34:28 -0500 Subject: [PATCH 1090/1145] [cc1101] Add PLL lock verification and retry support (#13006) --- esphome/components/cc1101/cc1101.cpp | 67 +++++++++++++++++++------- esphome/components/cc1101/cc1101.h | 5 +- esphome/components/cc1101/cc1101defs.h | 3 ++ 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 7e5309e165..fe9238b141 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -140,7 +140,10 @@ void CC1101Component::setup() { this->write_(static_cast(i)); } this->set_output_power(this->output_power_requested_); - this->strobe_(Command::RX); + if (!this->enter_rx_()) { + this->mark_failed(); + return; + } // Defer pin mode setup until after all components have completed setup() // This handles the case where remote_transmitter runs after CC1101 and changes pin mode @@ -163,8 +166,7 @@ void CC1101Component::loop() { ESP_LOGW(TAG, "RX FIFO overflow, flushing"); this->enter_idle_(); this->strobe_(Command::FRX); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return; } @@ -181,8 +183,7 @@ void CC1101Component::loop() { ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length); this->enter_idle_(); this->strobe_(Command::FRX); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return; } this->packet_.resize(payload_length); @@ -201,8 +202,7 @@ void CC1101Component::loop() { // Return to rx this->enter_idle_(); this->strobe_(Command::FRX); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); } void CC1101Component::dump_config() { @@ -233,9 +233,8 @@ void CC1101Component::begin_tx() { if (this->gdo0_pin_ != nullptr) { this->gdo0_pin_->pin_mode(gpio::FLAG_OUTPUT); } - this->strobe_(Command::TX); - if (!this->wait_for_state_(State::TX, 50)) { - ESP_LOGW(TAG, "Timed out waiting for TX state!"); + if (!this->enter_tx_()) { + ESP_LOGW(TAG, "Failed to enter TX state!"); } } @@ -244,7 +243,9 @@ void CC1101Component::begin_rx() { if (this->gdo0_pin_ != nullptr) { this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT); } - this->strobe_(Command::RX); + if (!this->enter_rx_()) { + ESP_LOGW(TAG, "Failed to enter RX state!"); + } } void CC1101Component::reset() { @@ -270,11 +271,33 @@ bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) { return false; } +bool CC1101Component::enter_calibrated_(State target_state, Command cmd) { + // The PLL must be recalibrated until PLL lock is achieved + for (uint8_t retries = PLL_LOCK_RETRIES; retries > 0; retries--) { + this->strobe_(cmd); + if (!this->wait_for_state_(target_state)) { + return false; + } + this->read_(Register::FSCAL1); + if (this->state_.FSCAL1 != FSCAL1_PLL_NOT_LOCKED) { + return true; + } + ESP_LOGW(TAG, "PLL lock failed, retrying calibration"); + this->enter_idle_(); + } + ESP_LOGE(TAG, "PLL lock failed after retries"); + return false; +} + void CC1101Component::enter_idle_() { this->strobe_(Command::IDLE); this->wait_for_state_(State::IDLE); } +bool CC1101Component::enter_rx_() { return this->enter_calibrated_(State::RX, Command::RX); } + +bool CC1101Component::enter_tx_() { return this->enter_calibrated_(State::TX, Command::TX); } + uint8_t CC1101Component::strobe_(Command cmd) { uint8_t index = static_cast(cmd); if (cmd < Command::RES || cmd > Command::NOP) { @@ -336,18 +359,26 @@ CC1101Error CC1101Component::transmit_packet(const std::vector &packet) this->write_(Register::FIFO, static_cast(packet.size())); } this->write_(Register::FIFO, packet.data(), packet.size()); + + // Calibrate PLL + if (!this->enter_calibrated_(State::FSTXON, Command::FSTXON)) { + ESP_LOGW(TAG, "PLL lock failed during TX"); + this->enter_idle_(); + this->enter_rx_(); + return CC1101Error::PLL_LOCK; + } + + // Transmit packet this->strobe_(Command::TX); if (!this->wait_for_state_(State::IDLE, 1000)) { ESP_LOGW(TAG, "TX timeout"); this->enter_idle_(); - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return CC1101Error::TIMEOUT; } // Return to rx - this->strobe_(Command::RX); - this->wait_for_state_(State::RX); + this->enter_rx_(); return CC1101Error::NONE; } @@ -404,7 +435,7 @@ void CC1101Component::set_frequency(float value) { this->write_(Register::FREQ2); this->write_(Register::FREQ1); this->write_(Register::FREQ0); - this->strobe_(Command::RX); + this->enter_rx_(); } } @@ -431,7 +462,7 @@ void CC1101Component::set_channel(uint8_t value) { if (this->initialized_) { this->enter_idle_(); this->write_(Register::CHANNR); - this->strobe_(Command::RX); + this->enter_rx_(); } } @@ -500,7 +531,7 @@ void CC1101Component::set_modulation_type(Modulation value) { this->set_output_power(this->output_power_requested_); this->write_(Register::MDMCFG2); this->write_(Register::FREND0); - this->strobe_(Command::RX); + this->enter_rx_(); } } diff --git a/esphome/components/cc1101/cc1101.h b/esphome/components/cc1101/cc1101.h index b896f7e974..fe4898660e 100644 --- a/esphome/components/cc1101/cc1101.h +++ b/esphome/components/cc1101/cc1101.h @@ -9,7 +9,7 @@ namespace esphome::cc1101 { -enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW }; +enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW, PLL_LOCK }; class CC1101Component : public Component, public spi::SPIDevice Date: Tue, 6 Jan 2026 09:35:38 -0500 Subject: [PATCH 1091/1145] Bump version to 2025.12.5 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index ff74757639..079606c501 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.4 +PROJECT_NUMBER = 2025.12.5 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 3fbdb69215..d1a7104ca4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.4" +__version__ = "2025.12.5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From a8a26f4ea8428ec86bbe5b26b7f1b55925120b88 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:32:43 -1000 Subject: [PATCH 1092/1145] [opentherm][nau7802] Use direct format specifiers instead of to_string().c_str() (#13019) --- esphome/components/nau7802/nau7802.cpp | 6 +++--- esphome/components/opentherm/opentherm.cpp | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/esphome/components/nau7802/nau7802.cpp b/esphome/components/nau7802/nau7802.cpp index 11f63a9a33..5edbc79862 100644 --- a/esphome/components/nau7802/nau7802.cpp +++ b/esphome/components/nau7802/nau7802.cpp @@ -131,9 +131,9 @@ void NAU7802Sensor::dump_config() { } // Note these may differ from the values on the device if calbration has been run ESP_LOGCONFIG(TAG, - " Offset Calibration: %s\n" + " Offset Calibration: %" PRId32 "\n" " Gain Calibration: %f", - to_string(this->offset_calibration_).c_str(), this->gain_calibration_); + this->offset_calibration_, this->gain_calibration_); std::string voltage = "unknown"; switch (this->ldo_) { @@ -289,7 +289,7 @@ void NAU7802Sensor::loop() { this->status_clear_error(); int32_t ocal = this->read_value_(OCAL1_B2_REG, 3); - ESP_LOGI(TAG, "New Offset: %s", to_string(ocal).c_str()); + ESP_LOGI(TAG, "New Offset: %" PRId32, ocal); uint32_t gcal = this->read_value_(GCAL1_B3_REG, 4); float gcal_f = ((float) gcal / (float) (1 << GCAL1_FRACTIONAL)); ESP_LOGI(TAG, "New Gain: %f", gcal_f); diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index 750ef08b33..c6443f1282 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -21,7 +21,6 @@ namespace esphome { namespace opentherm { using std::string; -using std::to_string; static const char *const TAG = "opentherm"; @@ -564,10 +563,9 @@ const char *OpenTherm::message_id_to_str(MessageId id) { void OpenTherm::debug_data(OpenthermData &data) { ESP_LOGD(TAG, "%s %s %s %s", format_bin(data.type).c_str(), format_bin(data.id).c_str(), format_bin(data.valueHB).c_str(), format_bin(data.valueLB).c_str()); - ESP_LOGD(TAG, "type: %s; id: %s; HB: %s; LB: %s; uint_16: %s; float: %s", - this->message_type_to_str((MessageType) data.type), to_string(data.id).c_str(), - to_string(data.valueHB).c_str(), to_string(data.valueLB).c_str(), to_string(data.u16()).c_str(), - to_string(data.f88()).c_str()); + ESP_LOGD(TAG, "type: %s; id: %u; HB: %u; LB: %u; uint_16: %u; float: %f", + this->message_type_to_str((MessageType) data.type), data.id, data.valueHB, data.valueLB, data.u16(), + data.f88()); } void OpenTherm::debug_error(OpenThermError &error) const { ESP_LOGD(TAG, "data: 0x%08" PRIx32 "; clock: %u; capture: 0x%08" PRIx32 "; bit_pos: %u", error.data, this->clock_, From 1e56325b33a9301ceed9d6e607364d6715367762 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:33:32 -1000 Subject: [PATCH 1093/1145] [improv_base] Optimize next_url to avoid STL string operations (#13015) --- .../esp32_improv/esp32_improv_component.cpp | 9 ++- .../components/improv_base/improv_base.cpp | 56 ++++++++++++------- esphome/components/improv_base/improv_base.h | 9 +-- .../improv_serial/improv_serial_component.cpp | 8 ++- 4 files changed, 54 insertions(+), 28 deletions(-) diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 4a6aec1892..1a19472c87 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -398,9 +398,12 @@ void ESP32ImprovComponent::check_wifi_connection_() { #ifdef USE_ESP32_IMPROV_NEXT_URL // Add next_url if configured (should be first per Improv BLE spec) - std::string next_url = this->get_formatted_next_url_(); - if (!next_url.empty()) { - url_strings[url_count++] = std::move(next_url); + { + char url_buffer[384]; + size_t len = this->get_formatted_next_url_(url_buffer, sizeof(url_buffer)); + if (len > 0) { + url_strings[url_count++] = std::string(url_buffer, len); + } } #endif diff --git a/esphome/components/improv_base/improv_base.cpp b/esphome/components/improv_base/improv_base.cpp index 2091390f95..d0340344a6 100644 --- a/esphome/components/improv_base/improv_base.cpp +++ b/esphome/components/improv_base/improv_base.cpp @@ -1,5 +1,6 @@ #include "improv_base.h" +#include #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" @@ -13,37 +14,54 @@ static constexpr size_t DEVICE_NAME_PLACEHOLDER_LEN = sizeof(DEVICE_NAME_PLACEHO static constexpr const char IP_ADDRESS_PLACEHOLDER[] = "{{ip_address}}"; static constexpr size_t IP_ADDRESS_PLACEHOLDER_LEN = sizeof(IP_ADDRESS_PLACEHOLDER) - 1; -static void replace_all_in_place(std::string &str, const char *placeholder, size_t placeholder_len, - const std::string &replacement) { - size_t pos = 0; - const size_t replacement_len = replacement.length(); - while ((pos = str.find(placeholder, pos)) != std::string::npos) { - str.replace(pos, placeholder_len, replacement); - pos += replacement_len; +/// Copy src to dest, returning pointer past last written char. Stops at end or if src is null. +static char *copy_to_buffer(char *dest, const char *end, const char *src) { + if (src == nullptr) { + return dest; } + while (*src != '\0' && dest < end) { + *dest++ = *src++; + } + return dest; } -std::string ImprovBase::get_formatted_next_url_() { - if (this->next_url_.empty()) { - return ""; +size_t ImprovBase::get_formatted_next_url_(char *buffer, size_t buffer_size) { + if (this->next_url_ == nullptr || buffer_size == 0) { + if (buffer_size > 0) { + buffer[0] = '\0'; + } + return 0; } - std::string formatted_url = this->next_url_; - - // Replace all occurrences of {{device_name}} - replace_all_in_place(formatted_url, DEVICE_NAME_PLACEHOLDER, DEVICE_NAME_PLACEHOLDER_LEN, App.get_name()); - - // Replace all occurrences of {{ip_address}} + // Get IP address once for replacement + const char *ip_str = nullptr; + char ip_buffer[network::IP_ADDRESS_BUFFER_SIZE]; for (auto &ip : network::get_ip_addresses()) { if (ip.is_ip4()) { - replace_all_in_place(formatted_url, IP_ADDRESS_PLACEHOLDER, IP_ADDRESS_PLACEHOLDER_LEN, ip.str()); + ip.str_to(ip_buffer); + ip_str = ip_buffer; break; } } - // Note: {{esphome_version}} is replaced at code generation time in Python + const char *device_name = App.get_name().c_str(); + char *out = buffer; + const char *end = buffer + buffer_size - 1; - return formatted_url; + // Note: {{esphome_version}} is replaced at code generation time in Python + for (const char *p = this->next_url_; *p != '\0' && out < end;) { + if (strncmp(p, DEVICE_NAME_PLACEHOLDER, DEVICE_NAME_PLACEHOLDER_LEN) == 0) { + out = copy_to_buffer(out, end, device_name); + p += DEVICE_NAME_PLACEHOLDER_LEN; + } else if (ip_str != nullptr && strncmp(p, IP_ADDRESS_PLACEHOLDER, IP_ADDRESS_PLACEHOLDER_LEN) == 0) { + out = copy_to_buffer(out, end, ip_str); + p += IP_ADDRESS_PLACEHOLDER_LEN; + } else { + *out++ = *p++; + } + } + *out = '\0'; + return out - buffer; } #endif diff --git a/esphome/components/improv_base/improv_base.h b/esphome/components/improv_base/improv_base.h index e4138479df..ebc8f38d60 100644 --- a/esphome/components/improv_base/improv_base.h +++ b/esphome/components/improv_base/improv_base.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "esphome/core/defines.h" namespace esphome { @@ -9,13 +9,14 @@ namespace improv_base { class ImprovBase { public: #if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL) - void set_next_url(const std::string &next_url) { this->next_url_ = next_url; } + void set_next_url(const char *next_url) { this->next_url_ = next_url; } #endif protected: #if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL) - std::string get_formatted_next_url_(); - std::string next_url_; + /// Format next_url_ into buffer, replacing placeholders. Returns length written. + size_t get_formatted_next_url_(char *buffer, size_t buffer_size); + const char *next_url_{nullptr}; #endif }; diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index 281e95d12b..936ff414b1 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -182,8 +182,12 @@ void ImprovSerialComponent::write_data_(const uint8_t *data, const size_t size) std::vector ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) { std::vector urls; #ifdef USE_IMPROV_SERIAL_NEXT_URL - if (!this->next_url_.empty()) { - urls.push_back(this->get_formatted_next_url_()); + { + char url_buffer[384]; + size_t len = this->get_formatted_next_url_(url_buffer, sizeof(url_buffer)); + if (len > 0) { + urls.emplace_back(url_buffer, len); + } } #endif #ifdef USE_WEBSERVER From e0981323bd72dfee9043926d9badb3e9bb026355 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:33:56 -1000 Subject: [PATCH 1094/1145] [mqtt] Move Home Assistant discovery keys to PROGMEM on ESP8266 (#13011) --- .../components/mqtt/custom_mqtt_device.cpp | 6 +- esphome/components/mqtt/custom_mqtt_device.h | 6 +- .../mqtt/mqtt_alarm_control_panel.cpp | 6 +- .../mqtt/mqtt_alarm_control_panel.h | 6 +- esphome/components/mqtt/mqtt_backend.h | 6 +- .../components/mqtt/mqtt_backend_esp32.cpp | 6 +- esphome/components/mqtt/mqtt_backend_esp32.h | 6 +- .../components/mqtt/mqtt_backend_esp8266.h | 6 +- .../components/mqtt/mqtt_backend_libretiny.h | 6 +- .../components/mqtt/mqtt_binary_sensor.cpp | 6 +- esphome/components/mqtt/mqtt_binary_sensor.h | 6 +- esphome/components/mqtt/mqtt_button.cpp | 6 +- esphome/components/mqtt/mqtt_button.h | 6 +- esphome/components/mqtt/mqtt_client.cpp | 6 +- esphome/components/mqtt/mqtt_client.h | 6 +- esphome/components/mqtt/mqtt_climate.cpp | 6 +- esphome/components/mqtt/mqtt_climate.h | 6 +- esphome/components/mqtt/mqtt_component.cpp | 6 +- esphome/components/mqtt/mqtt_component.h | 6 +- esphome/components/mqtt/mqtt_const.h | 851 +++++++----------- esphome/components/mqtt/mqtt_cover.cpp | 6 +- esphome/components/mqtt/mqtt_cover.h | 6 +- esphome/components/mqtt/mqtt_date.cpp | 6 +- esphome/components/mqtt/mqtt_date.h | 6 +- esphome/components/mqtt/mqtt_datetime.cpp | 6 +- esphome/components/mqtt/mqtt_datetime.h | 6 +- esphome/components/mqtt/mqtt_event.cpp | 6 +- esphome/components/mqtt/mqtt_event.h | 6 +- esphome/components/mqtt/mqtt_fan.cpp | 6 +- esphome/components/mqtt/mqtt_fan.h | 6 +- esphome/components/mqtt/mqtt_light.cpp | 6 +- esphome/components/mqtt/mqtt_light.h | 6 +- esphome/components/mqtt/mqtt_lock.cpp | 6 +- esphome/components/mqtt/mqtt_lock.h | 6 +- esphome/components/mqtt/mqtt_number.cpp | 6 +- esphome/components/mqtt/mqtt_number.h | 6 +- esphome/components/mqtt/mqtt_select.cpp | 6 +- esphome/components/mqtt/mqtt_select.h | 6 +- esphome/components/mqtt/mqtt_sensor.cpp | 6 +- esphome/components/mqtt/mqtt_sensor.h | 6 +- esphome/components/mqtt/mqtt_switch.cpp | 6 +- esphome/components/mqtt/mqtt_switch.h | 6 +- esphome/components/mqtt/mqtt_text.cpp | 6 +- esphome/components/mqtt/mqtt_text.h | 6 +- esphome/components/mqtt/mqtt_text_sensor.cpp | 6 +- esphome/components/mqtt/mqtt_text_sensor.h | 6 +- esphome/components/mqtt/mqtt_time.cpp | 6 +- esphome/components/mqtt/mqtt_time.h | 6 +- esphome/components/mqtt/mqtt_update.cpp | 6 +- esphome/components/mqtt/mqtt_update.h | 6 +- esphome/components/mqtt/mqtt_valve.cpp | 6 +- esphome/components/mqtt/mqtt_valve.h | 6 +- 52 files changed, 420 insertions(+), 737 deletions(-) diff --git a/esphome/components/mqtt/custom_mqtt_device.cpp b/esphome/components/mqtt/custom_mqtt_device.cpp index 787cc1153f..25a8a82066 100644 --- a/esphome/components/mqtt/custom_mqtt_device.cpp +++ b/esphome/components/mqtt/custom_mqtt_device.cpp @@ -4,8 +4,7 @@ #include "esphome/core/log.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.custom"; @@ -29,7 +28,6 @@ bool CustomMQTTDevice::publish_json(const std::string &topic, const json::json_b } bool CustomMQTTDevice::is_connected() { return global_mqtt_client != nullptr && global_mqtt_client->is_connected(); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/custom_mqtt_device.h b/esphome/components/mqtt/custom_mqtt_device.h index 0852a17cf1..09ed7bd6d1 100644 --- a/esphome/components/mqtt/custom_mqtt_device.h +++ b/esphome/components/mqtt/custom_mqtt_device.h @@ -6,8 +6,7 @@ #include "esphome/core/component.h" #include "mqtt_client.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { /** This class is a helper class for custom components that communicate using * MQTT. It has 5 helper functions that you can use (square brackets indicate optional): @@ -214,7 +213,6 @@ void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callba global_mqtt_client->subscribe_json(topic, f, qos); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index dd3df5f8aa..8c570d1472 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_ALARM_CONTROL_PANEL -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.alarm_control_panel"; @@ -123,8 +122,7 @@ bool MQTTAlarmControlPanelComponent::publish_state() { return this->publish(this->get_state_topic_(), state_s); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.h b/esphome/components/mqtt/mqtt_alarm_control_panel.h index 4ad37b7314..cf4fac1511 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.h +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.h @@ -8,8 +8,7 @@ #include "mqtt_component.h" #include "esphome/components/alarm_control_panel/alarm_control_panel.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTAlarmControlPanelComponent : public mqtt::MQTTComponent { public: @@ -32,8 +31,7 @@ class MQTTAlarmControlPanelComponent : public mqtt::MQTTComponent { alarm_control_panel::AlarmControlPanel *alarm_control_panel_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_backend.h b/esphome/components/mqtt/mqtt_backend.h index 0c1720ec34..a7e3f1013d 100644 --- a/esphome/components/mqtt/mqtt_backend.h +++ b/esphome/components/mqtt/mqtt_backend.h @@ -6,8 +6,7 @@ #include "esphome/components/network/ip_address.h" #include "esphome/core/helpers.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { enum class MQTTClientDisconnectReason : int8_t { TCP_DISCONNECTED = 0, @@ -67,6 +66,5 @@ class MQTTBackend { virtual void loop() {} }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index 3838d6df26..c12c79499f 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -8,8 +8,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.idf"; @@ -270,7 +269,6 @@ bool MQTTBackendESP32::enqueue_(MqttQueueTypeT type, const char *topic, int qos, } #endif // USE_MQTT_IDF_ENQUEUE -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_ESP32 #endif diff --git a/esphome/components/mqtt/mqtt_backend_esp32.h b/esphome/components/mqtt/mqtt_backend_esp32.h index a24e75eaf9..bd2d2a67b2 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.h +++ b/esphome/components/mqtt/mqtt_backend_esp32.h @@ -15,8 +15,7 @@ #include "esphome/core/lock_free_queue.h" #include "esphome/core/event_pool.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { struct Event { esp_mqtt_event_id_t event_id; @@ -273,8 +272,7 @@ class MQTTBackendESP32 final : public MQTTBackend { #endif }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif diff --git a/esphome/components/mqtt/mqtt_backend_esp8266.h b/esphome/components/mqtt/mqtt_backend_esp8266.h index a979634bf4..470d1e6a8b 100644 --- a/esphome/components/mqtt/mqtt_backend_esp8266.h +++ b/esphome/components/mqtt/mqtt_backend_esp8266.h @@ -6,8 +6,7 @@ #include -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTBackendESP8266 final : public MQTTBackend { public: @@ -67,8 +66,7 @@ class MQTTBackendESP8266 final : public MQTTBackend { AsyncMqttClient mqtt_client_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // defined(USE_ESP8266) #endif diff --git a/esphome/components/mqtt/mqtt_backend_libretiny.h b/esphome/components/mqtt/mqtt_backend_libretiny.h index 2578ae9941..24bf018a90 100644 --- a/esphome/components/mqtt/mqtt_backend_libretiny.h +++ b/esphome/components/mqtt/mqtt_backend_libretiny.h @@ -6,8 +6,7 @@ #include -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTBackendLibreTiny final : public MQTTBackend { public: @@ -67,8 +66,7 @@ class MQTTBackendLibreTiny final : public MQTTBackend { AsyncMqttClient mqtt_client_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // defined(USE_LIBRETINY) #endif diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 479cee205a..146ca46f68 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_BINARY_SENSOR -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.binary_sensor"; @@ -57,8 +56,7 @@ bool MQTTBinarySensorComponent::publish_state(bool state) { return this->publish(this->get_state_topic_(), state_s); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_binary_sensor.h b/esphome/components/mqtt/mqtt_binary_sensor.h index f6579fcd19..82176ec97b 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.h +++ b/esphome/components/mqtt/mqtt_binary_sensor.h @@ -7,8 +7,7 @@ #include "mqtt_component.h" #include "esphome/components/binary_sensor/binary_sensor.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTBinarySensorComponent : public mqtt::MQTTComponent { public: @@ -36,8 +35,7 @@ class MQTTBinarySensorComponent : public mqtt::MQTTComponent { binary_sensor::BinarySensor *binary_sensor_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index f8eb0eab2d..2b700a4962 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_BUTTON -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.button"; @@ -43,8 +42,7 @@ void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon std::string MQTTButtonComponent::component_type() const { return "button"; } const EntityBase *MQTTButtonComponent::get_entity() const { return this->button_; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_button.h b/esphome/components/mqtt/mqtt_button.h index 42389caecc..ec802664df 100644 --- a/esphome/components/mqtt/mqtt_button.h +++ b/esphome/components/mqtt/mqtt_button.h @@ -8,8 +8,7 @@ #include "esphome/components/button/button.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTButtonComponent : public mqtt::MQTTComponent { public: @@ -33,8 +32,7 @@ class MQTTButtonComponent : public mqtt::MQTTComponent { button::Button *button_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index d26548acfc..aecf809c8b 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -22,8 +22,7 @@ #include "esphome/components/dashboard_import/dashboard_import.h" #endif -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt"; @@ -751,7 +750,6 @@ void MQTTMessageTrigger::dump_config() { } float MQTTMessageTrigger::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 8547fe337f..4189e7ae77 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -24,8 +24,7 @@ #include -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { /** Callback for MQTT events. */ @@ -462,7 +461,6 @@ template class MQTTDisableAction : public Action { MQTTClientComponent *parent_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index aee2b38942..9d9ca012a8 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_CLIMATE -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.climate"; @@ -460,8 +459,7 @@ bool MQTTClimateComponent::publish_state_() { return success; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_climate.h b/esphome/components/mqtt/mqtt_climate.h index 4e54230e68..f561627ac9 100644 --- a/esphome/components/mqtt/mqtt_climate.h +++ b/esphome/components/mqtt/mqtt_climate.h @@ -8,8 +8,7 @@ #include "esphome/components/climate/climate.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTClimateComponent : public mqtt::MQTTComponent { public: @@ -49,8 +48,7 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { climate::Climate *device_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 9db1b1f7c8..ccbdb2ea91 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -9,8 +9,7 @@ #include "mqtt_const.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.component"; @@ -306,7 +305,6 @@ bool MQTTComponent::is_internal() { return this->get_entity()->is_internal(); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 2f8dfcf64e..e5f9664f77 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -11,8 +11,7 @@ #include "esphome/core/string_ref.h" #include "mqtt_client.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { /// Simple Helper struct used for Home Assistant MQTT send_discovery(). struct SendDiscoveryConfig { @@ -205,7 +204,6 @@ class MQTTComponent : public Component { bool resend_state_{false}; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTt diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index 3ddd8fc5cc..221af00371 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -1,547 +1,332 @@ #pragma once #include "esphome/core/defines.h" +#include "esphome/core/progmem.h" #ifdef USE_MQTT -namespace esphome { -namespace mqtt { +// MQTT JSON Key Constants for Home Assistant Discovery +// +// This file defines string constants used as JSON keys in MQTT discovery payloads +// for Home Assistant integration. These are used exclusively with ArduinoJson as: +// root[MQTT_DEVICE_CLASS] = "temperature"; +// +// Implementation: +// - ESP8266: Stores strings in PROGMEM (flash) using __FlashStringHelper* pointers. +// ArduinoJson recognizes this type and reads from flash memory. +// - Other platforms: Uses constexpr const char* for compile-time optimization. +// - USE_MQTT_ABBREVIATIONS: When defined, uses shortened key names to reduce message size. +// +// Adding new keys: +// Add a single line to MQTT_KEYS_LIST: X(MQTT_NEW_KEY, "abbr", "full_name") +// The X-macro will generate the appropriate constants for each platform. +// +// Note: Other MQTT_* constants (e.g., MQTT_CLIENT_CONNECTED, MQTT_LEGACY_UNIQUE_ID_GENERATOR) +// are C++ enums defined in mqtt_client.h and mqtt_backend*.h - unrelated to these JSON keys. +// X-macro list: MQTT_KEYS_LIST(X) calls X(name, abbr, full) for each key +// clang-format off +#define MQTT_KEYS_LIST(X) \ + X(MQTT_ACTION_TEMPLATE, "act_tpl", "action_template") \ + X(MQTT_ACTION_TOPIC, "act_t", "action_topic") \ + X(MQTT_AUTOMATION_TYPE, "atype", "automation_type") \ + X(MQTT_AUX_COMMAND_TOPIC, "aux_cmd_t", "aux_command_topic") \ + X(MQTT_AUX_STATE_TEMPLATE, "aux_stat_tpl", "aux_state_template") \ + X(MQTT_AUX_STATE_TOPIC, "aux_stat_t", "aux_state_topic") \ + X(MQTT_AVAILABILITY, "avty", "availability") \ + X(MQTT_AVAILABILITY_MODE, "avty_mode", "availability_mode") \ + X(MQTT_AVAILABILITY_TOPIC, "avty_t", "availability_topic") \ + X(MQTT_AWAY_MODE_COMMAND_TOPIC, "away_mode_cmd_t", "away_mode_command_topic") \ + X(MQTT_AWAY_MODE_STATE_TEMPLATE, "away_mode_stat_tpl", "away_mode_state_template") \ + X(MQTT_AWAY_MODE_STATE_TOPIC, "away_mode_stat_t", "away_mode_state_topic") \ + X(MQTT_BATTERY_LEVEL_TEMPLATE, "bat_lev_tpl", "battery_level_template") \ + X(MQTT_BATTERY_LEVEL_TOPIC, "bat_lev_t", "battery_level_topic") \ + X(MQTT_BLUE_TEMPLATE, "b_tpl", "blue_template") \ + X(MQTT_BRIGHTNESS_COMMAND_TOPIC, "bri_cmd_t", "brightness_command_topic") \ + X(MQTT_BRIGHTNESS_SCALE, "bri_scl", "brightness_scale") \ + X(MQTT_BRIGHTNESS_STATE_TOPIC, "bri_stat_t", "brightness_state_topic") \ + X(MQTT_BRIGHTNESS_TEMPLATE, "bri_tpl", "brightness_template") \ + X(MQTT_BRIGHTNESS_VALUE_TEMPLATE, "bri_val_tpl", "brightness_value_template") \ + X(MQTT_CHARGING_TEMPLATE, "chrg_tpl", "charging_template") \ + X(MQTT_CHARGING_TOPIC, "chrg_t", "charging_topic") \ + X(MQTT_CLEANING_TEMPLATE, "cln_tpl", "cleaning_template") \ + X(MQTT_CLEANING_TOPIC, "cln_t", "cleaning_topic") \ + X(MQTT_CODE_ARM_REQUIRED, "cod_arm_req", "code_arm_required") \ + X(MQTT_CODE_DISARM_REQUIRED, "cod_dis_req", "code_disarm_required") \ + X(MQTT_COLOR_MODE, "clrm", "color_mode") \ + X(MQTT_COLOR_MODE_STATE_TOPIC, "clrm_stat_t", "color_mode_state_topic") \ + X(MQTT_COLOR_MODE_VALUE_TEMPLATE, "clrm_val_tpl", "color_mode_value_template") \ + X(MQTT_COLOR_TEMP_COMMAND_TEMPLATE, "clr_temp_cmd_tpl", "color_temp_command_template") \ + X(MQTT_COLOR_TEMP_COMMAND_TOPIC, "clr_temp_cmd_t", "color_temp_command_topic") \ + X(MQTT_COLOR_TEMP_STATE_TOPIC, "clr_temp_stat_t", "color_temp_state_topic") \ + X(MQTT_COLOR_TEMP_TEMPLATE, "clr_temp_tpl", "color_temp_template") \ + X(MQTT_COLOR_TEMP_VALUE_TEMPLATE, "clr_temp_val_tpl", "color_temp_value_template") \ + X(MQTT_COMMAND_OFF_TEMPLATE, "cmd_off_tpl", "command_off_template") \ + X(MQTT_COMMAND_ON_TEMPLATE, "cmd_on_tpl", "command_on_template") \ + X(MQTT_COMMAND_RETAIN, "ret", "retain") \ + X(MQTT_COMMAND_TEMPLATE, "cmd_tpl", "command_template") \ + X(MQTT_COMMAND_TOPIC, "cmd_t", "command_topic") \ + X(MQTT_CONFIGURATION_URL, "cu", "configuration_url") \ + X(MQTT_CURRENT_HUMIDITY_TEMPLATE, "curr_hum_tpl", "current_humidity_template") \ + X(MQTT_CURRENT_HUMIDITY_TOPIC, "curr_hum_t", "current_humidity_topic") \ + X(MQTT_CURRENT_TEMPERATURE_STEP, "precision", "precision") \ + X(MQTT_CURRENT_TEMPERATURE_TEMPLATE, "curr_temp_tpl", "current_temperature_template") \ + X(MQTT_CURRENT_TEMPERATURE_TOPIC, "curr_temp_t", "current_temperature_topic") \ + X(MQTT_DEVICE, "dev", "device") \ + X(MQTT_DEVICE_CLASS, "dev_cla", "device_class") \ + X(MQTT_DEVICE_CONNECTIONS, "cns", "connections") \ + X(MQTT_DEVICE_IDENTIFIERS, "ids", "identifiers") \ + X(MQTT_DEVICE_MANUFACTURER, "mf", "manufacturer") \ + X(MQTT_DEVICE_MODEL, "mdl", "model") \ + X(MQTT_DEVICE_NAME, "name", "name") \ + X(MQTT_DEVICE_SUGGESTED_AREA, "sa", "suggested_area") \ + X(MQTT_DEVICE_SW_VERSION, "sw", "sw_version") \ + X(MQTT_DEVICE_HW_VERSION, "hw", "hw_version") \ + X(MQTT_DIRECTION_COMMAND_TOPIC, "dir_cmd_t", "direction_command_topic") \ + X(MQTT_DIRECTION_STATE_TOPIC, "dir_stat_t", "direction_state_topic") \ + X(MQTT_DOCKED_TEMPLATE, "dock_tpl", "docked_template") \ + X(MQTT_DOCKED_TOPIC, "dock_t", "docked_topic") \ + X(MQTT_EFFECT_COMMAND_TOPIC, "fx_cmd_t", "effect_command_topic") \ + X(MQTT_EFFECT_LIST, "fx_list", "effect_list") \ + X(MQTT_EFFECT_STATE_TOPIC, "fx_stat_t", "effect_state_topic") \ + X(MQTT_EFFECT_TEMPLATE, "fx_tpl", "effect_template") \ + X(MQTT_EFFECT_VALUE_TEMPLATE, "fx_val_tpl", "effect_value_template") \ + X(MQTT_ENABLED_BY_DEFAULT, "en", "enabled_by_default") \ + X(MQTT_ENTITY_CATEGORY, "ent_cat", "entity_category") \ + X(MQTT_ERROR_TEMPLATE, "err_tpl", "error_template") \ + X(MQTT_ERROR_TOPIC, "err_t", "error_topic") \ + X(MQTT_EVENT_TYPE, "event_type", "event_type") \ + X(MQTT_EVENT_TYPES, "evt_typ", "event_types") \ + X(MQTT_EXPIRE_AFTER, "exp_aft", "expire_after") \ + X(MQTT_FAN_MODE_COMMAND_TEMPLATE, "fan_mode_cmd_tpl", "fan_mode_command_template") \ + X(MQTT_FAN_MODE_COMMAND_TOPIC, "fan_mode_cmd_t", "fan_mode_command_topic") \ + X(MQTT_FAN_MODE_STATE_TEMPLATE, "fan_mode_stat_tpl", "fan_mode_state_template") \ + X(MQTT_FAN_MODE_STATE_TOPIC, "fan_mode_stat_t", "fan_mode_state_topic") \ + X(MQTT_FAN_SPEED_LIST, "fanspd_lst", "fan_speed_list") \ + X(MQTT_FAN_SPEED_TEMPLATE, "fanspd_tpl", "fan_speed_template") \ + X(MQTT_FAN_SPEED_TOPIC, "fanspd_t", "fan_speed_topic") \ + X(MQTT_FLASH_TIME_LONG, "flsh_tlng", "flash_time_long") \ + X(MQTT_FLASH_TIME_SHORT, "flsh_tsht", "flash_time_short") \ + X(MQTT_FORCE_UPDATE, "frc_upd", "force_update") \ + X(MQTT_GREEN_TEMPLATE, "g_tpl", "green_template") \ + X(MQTT_HOLD_COMMAND_TEMPLATE, "hold_cmd_tpl", "hold_command_template") \ + X(MQTT_HOLD_COMMAND_TOPIC, "hold_cmd_t", "hold_command_topic") \ + X(MQTT_HOLD_STATE_TEMPLATE, "hold_stat_tpl", "hold_state_template") \ + X(MQTT_HOLD_STATE_TOPIC, "hold_stat_t", "hold_state_topic") \ + X(MQTT_HS_COMMAND_TOPIC, "hs_cmd_t", "hs_command_topic") \ + X(MQTT_HS_STATE_TOPIC, "hs_stat_t", "hs_state_topic") \ + X(MQTT_HS_VALUE_TEMPLATE, "hs_val_tpl", "hs_value_template") \ + X(MQTT_ICON, "ic", "icon") \ + X(MQTT_INITIAL, "init", "initial") \ + X(MQTT_JSON_ATTRIBUTES, "json_attr", "json_attributes") \ + X(MQTT_JSON_ATTRIBUTES_TEMPLATE, "json_attr_tpl", "json_attributes_template") \ + X(MQTT_JSON_ATTRIBUTES_TOPIC, "json_attr_t", "json_attributes_topic") \ + X(MQTT_LAST_RESET_TOPIC, "lrst_t", "last_reset_topic") \ + X(MQTT_LAST_RESET_VALUE_TEMPLATE, "lrst_val_tpl", "last_reset_value_template") \ + X(MQTT_MAX, "max", "max") \ + X(MQTT_MAX_HUMIDITY, "max_hum", "max_humidity") \ + X(MQTT_MAX_MIREDS, "max_mirs", "max_mireds") \ + X(MQTT_MAX_TEMP, "max_temp", "max_temp") \ + X(MQTT_MIN, "min", "min") \ + X(MQTT_MIN_HUMIDITY, "min_hum", "min_humidity") \ + X(MQTT_MIN_MIREDS, "min_mirs", "min_mireds") \ + X(MQTT_MIN_TEMP, "min_temp", "min_temp") \ + X(MQTT_MODE, "mode", "mode") \ + X(MQTT_MODE_COMMAND_TEMPLATE, "mode_cmd_tpl", "mode_command_template") \ + X(MQTT_MODE_COMMAND_TOPIC, "mode_cmd_t", "mode_command_topic") \ + X(MQTT_MODE_STATE_TEMPLATE, "mode_stat_tpl", "mode_state_template") \ + X(MQTT_MODE_STATE_TOPIC, "mode_stat_t", "mode_state_topic") \ + X(MQTT_MODES, "modes", "modes") \ + X(MQTT_NAME, "name", "name") \ + X(MQTT_OBJECT_ID, "obj_id", "object_id") \ + X(MQTT_OFF_DELAY, "off_dly", "off_delay") \ + X(MQTT_ON_COMMAND_TYPE, "on_cmd_type", "on_command_type") \ + X(MQTT_OPTIMISTIC, "opt", "optimistic") \ + X(MQTT_OPTIONS, "ops", "options") \ + X(MQTT_OSCILLATION_COMMAND_TEMPLATE, "osc_cmd_tpl", "oscillation_command_template") \ + X(MQTT_OSCILLATION_COMMAND_TOPIC, "osc_cmd_t", "oscillation_command_topic") \ + X(MQTT_OSCILLATION_STATE_TOPIC, "osc_stat_t", "oscillation_state_topic") \ + X(MQTT_OSCILLATION_VALUE_TEMPLATE, "osc_val_tpl", "oscillation_value_template") \ + X(MQTT_PAYLOAD, "pl", "payload") \ + X(MQTT_PAYLOAD_ARM_AWAY, "pl_arm_away", "payload_arm_away") \ + X(MQTT_PAYLOAD_ARM_CUSTOM_BYPASS, "pl_arm_custom_b", "payload_arm_custom_bypass") \ + X(MQTT_PAYLOAD_ARM_HOME, "pl_arm_home", "payload_arm_home") \ + X(MQTT_PAYLOAD_ARM_NIGHT, "pl_arm_nite", "payload_arm_night") \ + X(MQTT_PAYLOAD_ARM_VACATION, "pl_arm_vacation", "payload_arm_vacation") \ + X(MQTT_PAYLOAD_AVAILABLE, "pl_avail", "payload_available") \ + X(MQTT_PAYLOAD_CLEAN_SPOT, "pl_cln_sp", "payload_clean_spot") \ + X(MQTT_PAYLOAD_CLOSE, "pl_cls", "payload_close") \ + X(MQTT_PAYLOAD_DISARM, "pl_disarm", "payload_disarm") \ + X(MQTT_PAYLOAD_HIGH_SPEED, "pl_hi_spd", "payload_high_speed") \ + X(MQTT_PAYLOAD_HOME, "pl_home", "payload_home") \ + X(MQTT_PAYLOAD_INSTALL, "pl_inst", "payload_install") \ + X(MQTT_PAYLOAD_LOCATE, "pl_loc", "payload_locate") \ + X(MQTT_PAYLOAD_LOCK, "pl_lock", "payload_lock") \ + X(MQTT_PAYLOAD_LOW_SPEED, "pl_lo_spd", "payload_low_speed") \ + X(MQTT_PAYLOAD_MEDIUM_SPEED, "pl_med_spd", "payload_medium_speed") \ + X(MQTT_PAYLOAD_NOT_AVAILABLE, "pl_not_avail", "payload_not_available") \ + X(MQTT_PAYLOAD_NOT_HOME, "pl_not_home", "payload_not_home") \ + X(MQTT_PAYLOAD_OFF, "pl_off", "payload_off") \ + X(MQTT_PAYLOAD_OFF_SPEED, "pl_off_spd", "payload_off_speed") \ + X(MQTT_PAYLOAD_ON, "pl_on", "payload_on") \ + X(MQTT_PAYLOAD_OPEN, "pl_open", "payload_open") \ + X(MQTT_PAYLOAD_OSCILLATION_OFF, "pl_osc_off", "payload_oscillation_off") \ + X(MQTT_PAYLOAD_OSCILLATION_ON, "pl_osc_on", "payload_oscillation_on") \ + X(MQTT_PAYLOAD_PAUSE, "pl_paus", "payload_pause") \ + X(MQTT_PAYLOAD_RESET, "pl_rst", "payload_reset") \ + X(MQTT_PAYLOAD_RESET_HUMIDITY, "pl_rst_hum", "payload_reset_humidity") \ + X(MQTT_PAYLOAD_RESET_MODE, "pl_rst_mode", "payload_reset_mode") \ + X(MQTT_PAYLOAD_RESET_PERCENTAGE, "pl_rst_pct", "payload_reset_percentage") \ + X(MQTT_PAYLOAD_RESET_PRESET_MODE, "pl_rst_pr_mode", "payload_reset_preset_mode") \ + X(MQTT_PAYLOAD_RETURN_TO_BASE, "pl_ret", "payload_return_to_base") \ + X(MQTT_PAYLOAD_START, "pl_strt", "payload_start") \ + X(MQTT_PAYLOAD_START_PAUSE, "pl_stpa", "payload_start_pause") \ + X(MQTT_PAYLOAD_STOP, "pl_stop", "payload_stop") \ + X(MQTT_PAYLOAD_TURN_OFF, "pl_toff", "payload_turn_off") \ + X(MQTT_PAYLOAD_TURN_ON, "pl_ton", "payload_turn_on") \ + X(MQTT_PAYLOAD_UNLOCK, "pl_unlk", "payload_unlock") \ + X(MQTT_PERCENTAGE_COMMAND_TEMPLATE, "pct_cmd_tpl", "percentage_command_template") \ + X(MQTT_PERCENTAGE_COMMAND_TOPIC, "pct_cmd_t", "percentage_command_topic") \ + X(MQTT_PERCENTAGE_STATE_TOPIC, "pct_stat_t", "percentage_state_topic") \ + X(MQTT_PERCENTAGE_VALUE_TEMPLATE, "pct_val_tpl", "percentage_value_template") \ + X(MQTT_POSITION_CLOSED, "pos_clsd", "position_closed") \ + X(MQTT_POSITION_OPEN, "pos_open", "position_open") \ + X(MQTT_POSITION_TEMPLATE, "pos_tpl", "position_template") \ + X(MQTT_POSITION_TOPIC, "pos_t", "position_topic") \ + X(MQTT_POWER_COMMAND_TOPIC, "pow_cmd_t", "power_command_topic") \ + X(MQTT_POWER_STATE_TEMPLATE, "pow_stat_tpl", "power_state_template") \ + X(MQTT_POWER_STATE_TOPIC, "pow_stat_t", "power_state_topic") \ + X(MQTT_PRESET_MODE_COMMAND_TEMPLATE, "pr_mode_cmd_tpl", "preset_mode_command_template") \ + X(MQTT_PRESET_MODE_COMMAND_TOPIC, "pr_mode_cmd_t", "preset_mode_command_topic") \ + X(MQTT_PRESET_MODE_STATE_TOPIC, "pr_mode_stat_t", "preset_mode_state_topic") \ + X(MQTT_PRESET_MODE_VALUE_TEMPLATE, "pr_mode_val_tpl", "preset_mode_value_template") \ + X(MQTT_PRESET_MODES, "pr_modes", "preset_modes") \ + X(MQTT_QOS, "qos", "qos") \ + X(MQTT_RED_TEMPLATE, "r_tpl", "red_template") \ + X(MQTT_RETAIN, "ret", "retain") \ + X(MQTT_RGB_COMMAND_TEMPLATE, "rgb_cmd_tpl", "rgb_command_template") \ + X(MQTT_RGB_COMMAND_TOPIC, "rgb_cmd_t", "rgb_command_topic") \ + X(MQTT_RGB_STATE_TOPIC, "rgb_stat_t", "rgb_state_topic") \ + X(MQTT_RGB_VALUE_TEMPLATE, "rgb_val_tpl", "rgb_value_template") \ + X(MQTT_RGBW_COMMAND_TEMPLATE, "rgbw_cmd_tpl", "rgbw_command_template") \ + X(MQTT_RGBW_COMMAND_TOPIC, "rgbw_cmd_t", "rgbw_command_topic") \ + X(MQTT_RGBW_STATE_TOPIC, "rgbw_stat_t", "rgbw_state_topic") \ + X(MQTT_RGBW_VALUE_TEMPLATE, "rgbw_val_tpl", "rgbw_value_template") \ + X(MQTT_RGBWW_COMMAND_TEMPLATE, "rgbww_cmd_tpl", "rgbww_command_template") \ + X(MQTT_RGBWW_COMMAND_TOPIC, "rgbww_cmd_t", "rgbww_command_topic") \ + X(MQTT_RGBWW_STATE_TOPIC, "rgbww_stat_t", "rgbww_state_topic") \ + X(MQTT_RGBWW_VALUE_TEMPLATE, "rgbww_val_tpl", "rgbww_value_template") \ + X(MQTT_SEND_COMMAND_TOPIC, "send_cmd_t", "send_command_topic") \ + X(MQTT_SEND_IF_OFF, "send_if_off", "send_if_off") \ + X(MQTT_SET_FAN_SPEED_TOPIC, "set_fan_spd_t", "set_fan_speed_topic") \ + X(MQTT_SET_POSITION_TEMPLATE, "set_pos_tpl", "set_position_template") \ + X(MQTT_SET_POSITION_TOPIC, "set_pos_t", "set_position_topic") \ + X(MQTT_SOURCE_TYPE, "src_type", "source_type") \ + X(MQTT_SPEED_COMMAND_TOPIC, "spd_cmd_t", "speed_command_topic") \ + X(MQTT_SPEED_RANGE_MAX, "spd_rng_max", "speed_range_max") \ + X(MQTT_SPEED_RANGE_MIN, "spd_rng_min", "speed_range_min") \ + X(MQTT_SPEED_STATE_TOPIC, "spd_stat_t", "speed_state_topic") \ + X(MQTT_SPEED_VALUE_TEMPLATE, "spd_val_tpl", "speed_value_template") \ + X(MQTT_SPEEDS, "spds", "speeds") \ + X(MQTT_STATE_CLASS, "stat_cla", "state_class") \ + X(MQTT_STATE_CLOSED, "stat_clsd", "state_closed") \ + X(MQTT_STATE_CLOSING, "stat_closing", "state_closing") \ + X(MQTT_STATE_LOCKED, "stat_locked", "state_locked") \ + X(MQTT_STATE_OFF, "stat_off", "state_off") \ + X(MQTT_STATE_ON, "stat_on", "state_on") \ + X(MQTT_STATE_OPEN, "stat_open", "state_open") \ + X(MQTT_STATE_OPENING, "stat_opening", "state_opening") \ + X(MQTT_STATE_STOPPED, "stat_stopped", "state_stopped") \ + X(MQTT_STATE_TEMPLATE, "stat_tpl", "state_template") \ + X(MQTT_STATE_TOPIC, "stat_t", "state_topic") \ + X(MQTT_STATE_UNLOCKED, "stat_unlocked", "state_unlocked") \ + X(MQTT_STATE_VALUE_TEMPLATE, "stat_val_tpl", "state_value_template") \ + X(MQTT_STEP, "step", "step") \ + X(MQTT_SUBTYPE, "stype", "subtype") \ + X(MQTT_SUPPORTED_COLOR_MODES, "sup_clrm", "supported_color_modes") \ + X(MQTT_SUPPORTED_FEATURES, "sup_feat", "supported_features") \ + X(MQTT_SWING_MODE_COMMAND_TEMPLATE, "swing_mode_cmd_tpl", "swing_mode_command_template") \ + X(MQTT_SWING_MODE_COMMAND_TOPIC, "swing_mode_cmd_t", "swing_mode_command_topic") \ + X(MQTT_SWING_MODE_STATE_TEMPLATE, "swing_mode_stat_tpl", "swing_mode_state_template") \ + X(MQTT_SWING_MODE_STATE_TOPIC, "swing_mode_stat_t", "swing_mode_state_topic") \ + X(MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE, "hum_cmd_tpl", "target_humidity_command_template") \ + X(MQTT_TARGET_HUMIDITY_COMMAND_TOPIC, "hum_cmd_t", "target_humidity_command_topic") \ + X(MQTT_TARGET_HUMIDITY_STATE_TEMPLATE, "hum_state_tpl", "target_humidity_state_template") \ + X(MQTT_TARGET_HUMIDITY_STATE_TOPIC, "hum_stat_t", "target_humidity_state_topic") \ + X(MQTT_TARGET_TEMPERATURE_STEP, "temp_step", "temp_step") \ + X(MQTT_TEMPERATURE_COMMAND_TEMPLATE, "temp_cmd_tpl", "temperature_command_template") \ + X(MQTT_TEMPERATURE_COMMAND_TOPIC, "temp_cmd_t", "temperature_command_topic") \ + X(MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE, "temp_hi_cmd_tpl", "temperature_high_command_template") \ + X(MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC, "temp_hi_cmd_t", "temperature_high_command_topic") \ + X(MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE, "temp_hi_stat_tpl", "temperature_high_state_template") \ + X(MQTT_TEMPERATURE_HIGH_STATE_TOPIC, "temp_hi_stat_t", "temperature_high_state_topic") \ + X(MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE, "temp_lo_cmd_tpl", "temperature_low_command_template") \ + X(MQTT_TEMPERATURE_LOW_COMMAND_TOPIC, "temp_lo_cmd_t", "temperature_low_command_topic") \ + X(MQTT_TEMPERATURE_LOW_STATE_TEMPLATE, "temp_lo_stat_tpl", "temperature_low_state_template") \ + X(MQTT_TEMPERATURE_LOW_STATE_TOPIC, "temp_lo_stat_t", "temperature_low_state_topic") \ + X(MQTT_TEMPERATURE_STATE_TEMPLATE, "temp_stat_tpl", "temperature_state_template") \ + X(MQTT_TEMPERATURE_STATE_TOPIC, "temp_stat_t", "temperature_state_topic") \ + X(MQTT_TEMPERATURE_UNIT, "temp_unit", "temperature_unit") \ + X(MQTT_TILT_CLOSED_VALUE, "tilt_clsd_val", "tilt_closed_value") \ + X(MQTT_TILT_COMMAND_TEMPLATE, "tilt_cmd_tpl", "tilt_command_template") \ + X(MQTT_TILT_COMMAND_TOPIC, "tilt_cmd_t", "tilt_command_topic") \ + X(MQTT_TILT_INVERT_STATE, "tilt_inv_stat", "tilt_invert_state") \ + X(MQTT_TILT_MAX, "tilt_max", "tilt_max") \ + X(MQTT_TILT_MIN, "tilt_min", "tilt_min") \ + X(MQTT_TILT_OPENED_VALUE, "tilt_opnd_val", "tilt_opened_value") \ + X(MQTT_TILT_OPTIMISTIC, "tilt_opt", "tilt_optimistic") \ + X(MQTT_TILT_STATUS_TEMPLATE, "tilt_status_tpl", "tilt_status_template") \ + X(MQTT_TILT_STATUS_TOPIC, "tilt_status_t", "tilt_status_topic") \ + X(MQTT_TOPIC, "t", "topic") \ + X(MQTT_UNIQUE_ID, "uniq_id", "unique_id") \ + X(MQTT_UNIT_OF_MEASUREMENT, "unit_of_meas", "unit_of_measurement") \ + X(MQTT_VALUE_TEMPLATE, "val_tpl", "value_template") \ + X(MQTT_WHITE_COMMAND_TOPIC, "whit_cmd_t", "white_command_topic") \ + X(MQTT_WHITE_SCALE, "whit_scl", "white_scale") \ + X(MQTT_WHITE_VALUE_COMMAND_TOPIC, "whit_val_cmd_t", "white_value_command_topic") \ + X(MQTT_WHITE_VALUE_SCALE, "whit_val_scl", "white_value_scale") \ + X(MQTT_WHITE_VALUE_STATE_TOPIC, "whit_val_stat_t", "white_value_state_topic") \ + X(MQTT_WHITE_VALUE_TEMPLATE, "whit_val_tpl", "white_value_template") \ + X(MQTT_XY_COMMAND_TOPIC, "xy_cmd_t", "xy_command_topic") \ + X(MQTT_XY_STATE_TOPIC, "xy_stat_t", "xy_state_topic") \ + X(MQTT_XY_VALUE_TEMPLATE, "xy_val_tpl", "xy_value_template") +// clang-format on + +#ifdef USE_ESP8266 +// ESP8266: Store strings in PROGMEM (flash) and expose as __FlashStringHelper* pointers. +// ArduinoJson recognizes this type and reads from flash memory. +namespace esphome::mqtt { + +// Generate PROGMEM data arrays #ifdef USE_MQTT_ABBREVIATIONS - -constexpr const char *const MQTT_ACTION_TEMPLATE = "act_tpl"; -constexpr const char *const MQTT_ACTION_TOPIC = "act_t"; -constexpr const char *const MQTT_AUTOMATION_TYPE = "atype"; -constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_cmd_t"; -constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_stat_tpl"; -constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_stat_t"; -constexpr const char *const MQTT_AVAILABILITY = "avty"; -constexpr const char *const MQTT_AVAILABILITY_MODE = "avty_mode"; -constexpr const char *const MQTT_AVAILABILITY_TOPIC = "avty_t"; -constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_cmd_t"; -constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_stat_tpl"; -constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_stat_t"; -constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "bat_lev_tpl"; -constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "bat_lev_t"; -constexpr const char *const MQTT_BLUE_TEMPLATE = "b_tpl"; -constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "bri_cmd_t"; -constexpr const char *const MQTT_BRIGHTNESS_SCALE = "bri_scl"; -constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "bri_stat_t"; -constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "bri_tpl"; -constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "bri_val_tpl"; -constexpr const char *const MQTT_CHARGING_TEMPLATE = "chrg_tpl"; -constexpr const char *const MQTT_CHARGING_TOPIC = "chrg_t"; -constexpr const char *const MQTT_CLEANING_TEMPLATE = "cln_tpl"; -constexpr const char *const MQTT_CLEANING_TOPIC = "cln_t"; -constexpr const char *const MQTT_CODE_ARM_REQUIRED = "cod_arm_req"; -constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "cod_dis_req"; -constexpr const char *const MQTT_COLOR_MODE = "clrm"; -constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "clrm_stat_t"; -constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "clrm_val_tpl"; -constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "clr_temp_cmd_tpl"; -constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "clr_temp_cmd_t"; -constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "clr_temp_stat_t"; -constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "clr_temp_tpl"; -constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "clr_temp_val_tpl"; -constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "cmd_off_tpl"; -constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "cmd_on_tpl"; -constexpr const char *const MQTT_COMMAND_RETAIN = "ret"; -constexpr const char *const MQTT_COMMAND_TEMPLATE = "cmd_tpl"; -constexpr const char *const MQTT_COMMAND_TOPIC = "cmd_t"; -constexpr const char *const MQTT_CONFIGURATION_URL = "cu"; -constexpr const char *const MQTT_CURRENT_HUMIDITY_TEMPLATE = "curr_hum_tpl"; -constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "curr_hum_t"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_STEP = "precision"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "curr_temp_tpl"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t"; -constexpr const char *const MQTT_DEVICE = "dev"; -constexpr const char *const MQTT_DEVICE_CLASS = "dev_cla"; -constexpr const char *const MQTT_DEVICE_CONNECTIONS = "cns"; -constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "ids"; -constexpr const char *const MQTT_DEVICE_MANUFACTURER = "mf"; -constexpr const char *const MQTT_DEVICE_MODEL = "mdl"; -constexpr const char *const MQTT_DEVICE_NAME = "name"; -constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "sa"; -constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw"; -constexpr const char *const MQTT_DEVICE_HW_VERSION = "hw"; -constexpr const char *const MQTT_DIRECTION_COMMAND_TOPIC = "dir_cmd_t"; -constexpr const char *const MQTT_DIRECTION_STATE_TOPIC = "dir_stat_t"; -constexpr const char *const MQTT_DOCKED_TEMPLATE = "dock_tpl"; -constexpr const char *const MQTT_DOCKED_TOPIC = "dock_t"; -constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "fx_cmd_t"; -constexpr const char *const MQTT_EFFECT_LIST = "fx_list"; -constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "fx_stat_t"; -constexpr const char *const MQTT_EFFECT_TEMPLATE = "fx_tpl"; -constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "fx_val_tpl"; -constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "en"; -constexpr const char *const MQTT_ENTITY_CATEGORY = "ent_cat"; -constexpr const char *const MQTT_ERROR_TEMPLATE = "err_tpl"; -constexpr const char *const MQTT_ERROR_TOPIC = "err_t"; -constexpr const char *const MQTT_EVENT_TYPE = "event_type"; -constexpr const char *const MQTT_EVENT_TYPES = "evt_typ"; -constexpr const char *const MQTT_EXPIRE_AFTER = "exp_aft"; -constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_cmd_tpl"; -constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_cmd_t"; -constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_stat_tpl"; -constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_stat_t"; -constexpr const char *const MQTT_FAN_SPEED_LIST = "fanspd_lst"; -constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fanspd_tpl"; -constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fanspd_t"; -constexpr const char *const MQTT_FLASH_TIME_LONG = "flsh_tlng"; -constexpr const char *const MQTT_FLASH_TIME_SHORT = "flsh_tsht"; -constexpr const char *const MQTT_FORCE_UPDATE = "frc_upd"; -constexpr const char *const MQTT_GREEN_TEMPLATE = "g_tpl"; -constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_cmd_tpl"; -constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_cmd_t"; -constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_stat_tpl"; -constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_stat_t"; -constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_cmd_t"; -constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_stat_t"; -constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_val_tpl"; -constexpr const char *const MQTT_ICON = "ic"; -constexpr const char *const MQTT_INITIAL = "init"; -constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attr"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attr_tpl"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attr_t"; -constexpr const char *const MQTT_LAST_RESET_TOPIC = "lrst_t"; -constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "lrst_val_tpl"; -constexpr const char *const MQTT_MAX = "max"; -constexpr const char *const MQTT_MAX_HUMIDITY = "max_hum"; -constexpr const char *const MQTT_MAX_MIREDS = "max_mirs"; -constexpr const char *const MQTT_MAX_TEMP = "max_temp"; -constexpr const char *const MQTT_MIN = "min"; -constexpr const char *const MQTT_MIN_HUMIDITY = "min_hum"; -constexpr const char *const MQTT_MIN_MIREDS = "min_mirs"; -constexpr const char *const MQTT_MIN_TEMP = "min_temp"; -constexpr const char *const MQTT_MODE = "mode"; -constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_cmd_tpl"; -constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_cmd_t"; -constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_stat_tpl"; -constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_stat_t"; -constexpr const char *const MQTT_MODES = "modes"; -constexpr const char *const MQTT_NAME = "name"; -constexpr const char *const MQTT_OBJECT_ID = "obj_id"; -constexpr const char *const MQTT_OFF_DELAY = "off_dly"; -constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_cmd_type"; -constexpr const char *const MQTT_OPTIMISTIC = "opt"; -constexpr const char *const MQTT_OPTIONS = "ops"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "osc_cmd_tpl"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "osc_cmd_t"; -constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "osc_stat_t"; -constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "osc_val_tpl"; -constexpr const char *const MQTT_PAYLOAD = "pl"; -constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "pl_arm_away"; -constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "pl_arm_custom_b"; -constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "pl_arm_home"; -constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "pl_arm_nite"; -constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "pl_arm_vacation"; -constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "pl_avail"; -constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "pl_cln_sp"; -constexpr const char *const MQTT_PAYLOAD_CLOSE = "pl_cls"; -constexpr const char *const MQTT_PAYLOAD_DISARM = "pl_disarm"; -constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "pl_hi_spd"; -constexpr const char *const MQTT_PAYLOAD_HOME = "pl_home"; -constexpr const char *const MQTT_PAYLOAD_INSTALL = "pl_inst"; -constexpr const char *const MQTT_PAYLOAD_LOCATE = "pl_loc"; -constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock"; -constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "pl_lo_spd"; -constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "pl_med_spd"; -constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "pl_not_avail"; -constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "pl_not_home"; -constexpr const char *const MQTT_PAYLOAD_OFF = "pl_off"; -constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "pl_off_spd"; -constexpr const char *const MQTT_PAYLOAD_ON = "pl_on"; -constexpr const char *const MQTT_PAYLOAD_OPEN = "pl_open"; -constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "pl_osc_off"; -constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "pl_osc_on"; -constexpr const char *const MQTT_PAYLOAD_PAUSE = "pl_paus"; -constexpr const char *const MQTT_PAYLOAD_RESET = "pl_rst"; -constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "pl_rst_hum"; -constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "pl_rst_mode"; -constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "pl_rst_pct"; -constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "pl_rst_pr_mode"; -constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "pl_ret"; -constexpr const char *const MQTT_PAYLOAD_START = "pl_strt"; -constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "pl_stpa"; -constexpr const char *const MQTT_PAYLOAD_STOP = "pl_stop"; -constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "pl_toff"; -constexpr const char *const MQTT_PAYLOAD_TURN_ON = "pl_ton"; -constexpr const char *const MQTT_PAYLOAD_UNLOCK = "pl_unlk"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "pct_cmd_tpl"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "pct_cmd_t"; -constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "pct_stat_t"; -constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "pct_val_tpl"; -constexpr const char *const MQTT_POSITION_CLOSED = "pos_clsd"; -constexpr const char *const MQTT_POSITION_OPEN = "pos_open"; -constexpr const char *const MQTT_POSITION_TEMPLATE = "pos_tpl"; -constexpr const char *const MQTT_POSITION_TOPIC = "pos_t"; -constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "pow_cmd_t"; -constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "pow_stat_tpl"; -constexpr const char *const MQTT_POWER_STATE_TOPIC = "pow_stat_t"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "pr_mode_cmd_tpl"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "pr_mode_cmd_t"; -constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "pr_mode_stat_t"; -constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "pr_mode_val_tpl"; -constexpr const char *const MQTT_PRESET_MODES = "pr_modes"; -constexpr const char *const MQTT_QOS = "qos"; -constexpr const char *const MQTT_RED_TEMPLATE = "r_tpl"; -constexpr const char *const MQTT_RETAIN = "ret"; -constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_cmd_tpl"; -constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_cmd_t"; -constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_stat_t"; -constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_val_tpl"; -constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_cmd_tpl"; -constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_cmd_t"; -constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_stat_t"; -constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_val_tpl"; -constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_cmd_tpl"; -constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_cmd_t"; -constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_stat_t"; -constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_val_tpl"; -constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_cmd_t"; -constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off"; -constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_spd_t"; -constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_pos_tpl"; -constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_pos_t"; -constexpr const char *const MQTT_SOURCE_TYPE = "src_type"; -constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "spd_cmd_t"; -constexpr const char *const MQTT_SPEED_RANGE_MAX = "spd_rng_max"; -constexpr const char *const MQTT_SPEED_RANGE_MIN = "spd_rng_min"; -constexpr const char *const MQTT_SPEED_STATE_TOPIC = "spd_stat_t"; -constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "spd_val_tpl"; -constexpr const char *const MQTT_SPEEDS = "spds"; -constexpr const char *const MQTT_STATE_CLASS = "stat_cla"; -constexpr const char *const MQTT_STATE_CLOSED = "stat_clsd"; -constexpr const char *const MQTT_STATE_CLOSING = "stat_closing"; -constexpr const char *const MQTT_STATE_LOCKED = "stat_locked"; -constexpr const char *const MQTT_STATE_OFF = "stat_off"; -constexpr const char *const MQTT_STATE_ON = "stat_on"; -constexpr const char *const MQTT_STATE_OPEN = "stat_open"; -constexpr const char *const MQTT_STATE_OPENING = "stat_opening"; -constexpr const char *const MQTT_STATE_STOPPED = "stat_stopped"; -constexpr const char *const MQTT_STATE_TEMPLATE = "stat_tpl"; -constexpr const char *const MQTT_STATE_TOPIC = "stat_t"; -constexpr const char *const MQTT_STATE_UNLOCKED = "stat_unlocked"; -constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "stat_val_tpl"; -constexpr const char *const MQTT_STEP = "step"; -constexpr const char *const MQTT_SUBTYPE = "stype"; -constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "sup_clrm"; -constexpr const char *const MQTT_SUPPORTED_FEATURES = "sup_feat"; -constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_cmd_tpl"; -constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_cmd_t"; -constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_stat_tpl"; -constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_stat_t"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "hum_cmd_tpl"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "hum_cmd_t"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "hum_state_tpl"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "hum_stat_t"; -constexpr const char *const MQTT_TARGET_TEMPERATURE_STEP = "temp_step"; -constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temp_cmd_tpl"; -constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temp_cmd_t"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temp_hi_cmd_tpl"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temp_hi_cmd_t"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temp_hi_stat_tpl"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temp_hi_stat_t"; -constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temp_lo_cmd_tpl"; -constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temp_lo_cmd_t"; -constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temp_lo_stat_tpl"; -constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temp_lo_stat_t"; -constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temp_stat_tpl"; -constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temp_stat_t"; -constexpr const char *const MQTT_TEMPERATURE_UNIT = "temp_unit"; -constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_clsd_val"; -constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_cmd_tpl"; -constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_cmd_t"; -constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_inv_stat"; -constexpr const char *const MQTT_TILT_MAX = "tilt_max"; -constexpr const char *const MQTT_TILT_MIN = "tilt_min"; -constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opnd_val"; -constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_opt"; -constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_tpl"; -constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_t"; -constexpr const char *const MQTT_TOPIC = "t"; -constexpr const char *const MQTT_UNIQUE_ID = "uniq_id"; -constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_meas"; -constexpr const char *const MQTT_VALUE_TEMPLATE = "val_tpl"; -constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "whit_cmd_t"; -constexpr const char *const MQTT_WHITE_SCALE = "whit_scl"; -constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "whit_val_cmd_t"; -constexpr const char *const MQTT_WHITE_VALUE_SCALE = "whit_val_scl"; -constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "whit_val_stat_t"; -constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "whit_val_tpl"; -constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_cmd_t"; -constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_stat_t"; -constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_val_tpl"; - +#define MQTT_DATA(name, abbr, full) static const char name##_data[] PROGMEM = abbr; #else +#define MQTT_DATA(name, abbr, full) static const char name##_data[] PROGMEM = full; +#endif +MQTT_KEYS_LIST(MQTT_DATA) +#undef MQTT_DATA -constexpr const char *const MQTT_ACTION_TEMPLATE = "action_template"; -constexpr const char *const MQTT_ACTION_TOPIC = "action_topic"; -constexpr const char *const MQTT_AUTOMATION_TYPE = "automation_type"; -constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_command_topic"; -constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_state_template"; -constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_state_topic"; -constexpr const char *const MQTT_AVAILABILITY = "availability"; -constexpr const char *const MQTT_AVAILABILITY_MODE = "availability_mode"; -constexpr const char *const MQTT_AVAILABILITY_TOPIC = "availability_topic"; -constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_command_topic"; -constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_state_template"; -constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_state_topic"; -constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "battery_level_template"; -constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "battery_level_topic"; -constexpr const char *const MQTT_BLUE_TEMPLATE = "blue_template"; -constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "brightness_command_topic"; -constexpr const char *const MQTT_BRIGHTNESS_SCALE = "brightness_scale"; -constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "brightness_state_topic"; -constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "brightness_template"; -constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "brightness_value_template"; -constexpr const char *const MQTT_CHARGING_TEMPLATE = "charging_template"; -constexpr const char *const MQTT_CHARGING_TOPIC = "charging_topic"; -constexpr const char *const MQTT_CLEANING_TEMPLATE = "cleaning_template"; -constexpr const char *const MQTT_CLEANING_TOPIC = "cleaning_topic"; -constexpr const char *const MQTT_CODE_ARM_REQUIRED = "code_arm_required"; -constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "code_disarm_required"; -constexpr const char *const MQTT_COLOR_MODE = "color_mode"; -constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "color_mode_state_topic"; -constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "color_mode_value_template"; -constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "color_temp_command_template"; -constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "color_temp_command_topic"; -constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "color_temp_state_topic"; -constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "color_temp_template"; -constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "color_temp_value_template"; -constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "command_off_template"; -constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "command_on_template"; -constexpr const char *const MQTT_COMMAND_RETAIN = "retain"; -constexpr const char *const MQTT_COMMAND_TEMPLATE = "command_template"; -constexpr const char *const MQTT_COMMAND_TOPIC = "command_topic"; -constexpr const char *const MQTT_CONFIGURATION_URL = "configuration_url"; -constexpr const char *const MQTT_CURRENT_HUMIDITY_TEMPLATE = "current_humidity_template"; -constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "current_humidity_topic"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_STEP = "precision"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "current_temperature_template"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic"; -constexpr const char *const MQTT_DEVICE = "device"; -constexpr const char *const MQTT_DEVICE_CLASS = "device_class"; -constexpr const char *const MQTT_DEVICE_CONNECTIONS = "connections"; -constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "identifiers"; -constexpr const char *const MQTT_DEVICE_MANUFACTURER = "manufacturer"; -constexpr const char *const MQTT_DEVICE_MODEL = "model"; -constexpr const char *const MQTT_DEVICE_NAME = "name"; -constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; -constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version"; -constexpr const char *const MQTT_DEVICE_HW_VERSION = "hw_version"; -constexpr const char *const MQTT_DIRECTION_COMMAND_TOPIC = "direction_command_topic"; -constexpr const char *const MQTT_DIRECTION_STATE_TOPIC = "direction_state_topic"; -constexpr const char *const MQTT_DOCKED_TEMPLATE = "docked_template"; -constexpr const char *const MQTT_DOCKED_TOPIC = "docked_topic"; -constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "effect_command_topic"; -constexpr const char *const MQTT_EFFECT_LIST = "effect_list"; -constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "effect_state_topic"; -constexpr const char *const MQTT_EFFECT_TEMPLATE = "effect_template"; -constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "effect_value_template"; -constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "enabled_by_default"; -constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category"; -constexpr const char *const MQTT_ERROR_TEMPLATE = "error_template"; -constexpr const char *const MQTT_ERROR_TOPIC = "error_topic"; -constexpr const char *const MQTT_EVENT_TYPE = "event_type"; -constexpr const char *const MQTT_EVENT_TYPES = "event_types"; -constexpr const char *const MQTT_EXPIRE_AFTER = "expire_after"; -constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_command_template"; -constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic"; -constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_state_template"; -constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic"; -constexpr const char *const MQTT_FAN_SPEED_LIST = "fan_speed_list"; -constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fan_speed_template"; -constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fan_speed_topic"; -constexpr const char *const MQTT_FLASH_TIME_LONG = "flash_time_long"; -constexpr const char *const MQTT_FLASH_TIME_SHORT = "flash_time_short"; -constexpr const char *const MQTT_FORCE_UPDATE = "force_update"; -constexpr const char *const MQTT_GREEN_TEMPLATE = "green_template"; -constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_command_template"; -constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_command_topic"; -constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_state_template"; -constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_state_topic"; -constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_command_topic"; -constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_state_topic"; -constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_value_template"; -constexpr const char *const MQTT_ICON = "icon"; -constexpr const char *const MQTT_INITIAL = "initial"; -constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attributes"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attributes_template"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attributes_topic"; -constexpr const char *const MQTT_LAST_RESET_TOPIC = "last_reset_topic"; -constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template"; -constexpr const char *const MQTT_MAX = "max"; -constexpr const char *const MQTT_MAX_HUMIDITY = "max_humidity"; -constexpr const char *const MQTT_MAX_MIREDS = "max_mireds"; -constexpr const char *const MQTT_MAX_TEMP = "max_temp"; -constexpr const char *const MQTT_MIN = "min"; -constexpr const char *const MQTT_MIN_HUMIDITY = "min_humidity"; -constexpr const char *const MQTT_MIN_MIREDS = "min_mireds"; -constexpr const char *const MQTT_MIN_TEMP = "min_temp"; -constexpr const char *const MQTT_MODE = "mode"; -constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_command_template"; -constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_command_topic"; -constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_state_template"; -constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_state_topic"; -constexpr const char *const MQTT_MODES = "modes"; -constexpr const char *const MQTT_NAME = "name"; -constexpr const char *const MQTT_OBJECT_ID = "object_id"; -constexpr const char *const MQTT_OFF_DELAY = "off_delay"; -constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_command_type"; -constexpr const char *const MQTT_OPTIMISTIC = "optimistic"; -constexpr const char *const MQTT_OPTIONS = "options"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "oscillation_command_template"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic"; -constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "oscillation_state_topic"; -constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template"; -constexpr const char *const MQTT_PAYLOAD = "payload"; -constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "payload_arm_away"; -constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "payload_arm_custom_bypass"; -constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "payload_arm_home"; -constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "payload_arm_night"; -constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "payload_arm_vacation"; -constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "payload_available"; -constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "payload_clean_spot"; -constexpr const char *const MQTT_PAYLOAD_CLOSE = "payload_close"; -constexpr const char *const MQTT_PAYLOAD_DISARM = "payload_disarm"; -constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "payload_high_speed"; -constexpr const char *const MQTT_PAYLOAD_HOME = "payload_home"; -constexpr const char *const MQTT_PAYLOAD_INSTALL = "payload_install"; -constexpr const char *const MQTT_PAYLOAD_LOCATE = "payload_locate"; -constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock"; -constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "payload_low_speed"; -constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "payload_medium_speed"; -constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "payload_not_available"; -constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "payload_not_home"; -constexpr const char *const MQTT_PAYLOAD_OFF = "payload_off"; -constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "payload_off_speed"; -constexpr const char *const MQTT_PAYLOAD_ON = "payload_on"; -constexpr const char *const MQTT_PAYLOAD_OPEN = "payload_open"; -constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "payload_oscillation_off"; -constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "payload_oscillation_on"; -constexpr const char *const MQTT_PAYLOAD_PAUSE = "payload_pause"; -constexpr const char *const MQTT_PAYLOAD_RESET = "payload_reset"; -constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "payload_reset_humidity"; -constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "payload_reset_mode"; -constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "payload_reset_percentage"; -constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "payload_reset_preset_mode"; -constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "payload_return_to_base"; -constexpr const char *const MQTT_PAYLOAD_START = "payload_start"; -constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "payload_start_pause"; -constexpr const char *const MQTT_PAYLOAD_STOP = "payload_stop"; -constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "payload_turn_off"; -constexpr const char *const MQTT_PAYLOAD_TURN_ON = "payload_turn_on"; -constexpr const char *const MQTT_PAYLOAD_UNLOCK = "payload_unlock"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "percentage_command_template"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic"; -constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "percentage_state_topic"; -constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template"; -constexpr const char *const MQTT_POSITION_CLOSED = "position_closed"; -constexpr const char *const MQTT_POSITION_OPEN = "position_open"; -constexpr const char *const MQTT_POSITION_TEMPLATE = "position_template"; -constexpr const char *const MQTT_POSITION_TOPIC = "position_topic"; -constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "power_command_topic"; -constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "power_state_template"; -constexpr const char *const MQTT_POWER_STATE_TOPIC = "power_state_topic"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "preset_mode_command_template"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic"; -constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic"; -constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template"; -constexpr const char *const MQTT_PRESET_MODES = "preset_modes"; -constexpr const char *const MQTT_QOS = "qos"; -constexpr const char *const MQTT_RED_TEMPLATE = "red_template"; -constexpr const char *const MQTT_RETAIN = "retain"; -constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_command_template"; -constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_command_topic"; -constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_state_topic"; -constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_value_template"; -constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_command_template"; -constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_command_topic"; -constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_state_topic"; -constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_value_template"; -constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_command_template"; -constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_command_topic"; -constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_state_topic"; -constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_value_template"; -constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_command_topic"; -constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off"; -constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_speed_topic"; -constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_position_template"; -constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_position_topic"; -constexpr const char *const MQTT_SOURCE_TYPE = "source_type"; -constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "speed_command_topic"; -constexpr const char *const MQTT_SPEED_RANGE_MAX = "speed_range_max"; -constexpr const char *const MQTT_SPEED_RANGE_MIN = "speed_range_min"; -constexpr const char *const MQTT_SPEED_STATE_TOPIC = "speed_state_topic"; -constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "speed_value_template"; -constexpr const char *const MQTT_SPEEDS = "speeds"; -constexpr const char *const MQTT_STATE_CLASS = "state_class"; -constexpr const char *const MQTT_STATE_CLOSED = "state_closed"; -constexpr const char *const MQTT_STATE_CLOSING = "state_closing"; -constexpr const char *const MQTT_STATE_LOCKED = "state_locked"; -constexpr const char *const MQTT_STATE_OFF = "state_off"; -constexpr const char *const MQTT_STATE_ON = "state_on"; -constexpr const char *const MQTT_STATE_OPEN = "state_open"; -constexpr const char *const MQTT_STATE_OPENING = "state_opening"; -constexpr const char *const MQTT_STATE_STOPPED = "state_stopped"; -constexpr const char *const MQTT_STATE_TEMPLATE = "state_template"; -constexpr const char *const MQTT_STATE_TOPIC = "state_topic"; -constexpr const char *const MQTT_STATE_UNLOCKED = "state_unlocked"; -constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "state_value_template"; -constexpr const char *const MQTT_STEP = "step"; -constexpr const char *const MQTT_SUBTYPE = "subtype"; -constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "supported_color_modes"; -constexpr const char *const MQTT_SUPPORTED_FEATURES = "supported_features"; -constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_command_template"; -constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_command_topic"; -constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_state_template"; -constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_state_topic"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humidity_command_template"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "target_humidity_state_template"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic"; -constexpr const char *const MQTT_TARGET_TEMPERATURE_STEP = "temp_step"; -constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temperature_command_template"; -constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temperature_command_topic"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temperature_high_command_template"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temperature_high_command_topic"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temperature_high_state_template"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temperature_high_state_topic"; -constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temperature_low_command_template"; -constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temperature_low_command_topic"; -constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temperature_low_state_template"; -constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temperature_low_state_topic"; -constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temperature_state_template"; -constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temperature_state_topic"; -constexpr const char *const MQTT_TEMPERATURE_UNIT = "temperature_unit"; -constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_closed_value"; -constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_command_template"; -constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_command_topic"; -constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_invert_state"; -constexpr const char *const MQTT_TILT_MAX = "tilt_max"; -constexpr const char *const MQTT_TILT_MIN = "tilt_min"; -constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opened_value"; -constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_optimistic"; -constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_template"; -constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_topic"; -constexpr const char *const MQTT_TOPIC = "topic"; -constexpr const char *const MQTT_UNIQUE_ID = "unique_id"; -constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_measurement"; -constexpr const char *const MQTT_VALUE_TEMPLATE = "value_template"; -constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "white_command_topic"; -constexpr const char *const MQTT_WHITE_SCALE = "white_scale"; -constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "white_value_command_topic"; -constexpr const char *const MQTT_WHITE_VALUE_SCALE = "white_value_scale"; -constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "white_value_state_topic"; -constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "white_value_template"; -constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_command_topic"; -constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_state_topic"; -constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_value_template"; +// Generate flash string pointers from the PROGMEM data +// NOLINTNEXTLINE(bugprone-macro-parentheses) +#define MQTT_PTR(name, abbr, full) \ + static const __FlashStringHelper *const name = reinterpret_cast(name##_data); +MQTT_KEYS_LIST(MQTT_PTR) +#undef MQTT_PTR +} // namespace esphome::mqtt +#else +// Other platforms: constexpr in namespace +namespace esphome::mqtt { +#ifdef USE_MQTT_ABBREVIATIONS +// NOLINTNEXTLINE(bugprone-macro-parentheses) +#define MQTT_CONST(name, abbr, full) constexpr const char *name = abbr; +#else +// NOLINTNEXTLINE(bugprone-macro-parentheses) +#define MQTT_CONST(name, abbr, full) constexpr const char *name = full; +#endif +MQTT_KEYS_LIST(MQTT_CONST) +#undef MQTT_CONST +} // namespace esphome::mqtt #endif -} // namespace mqtt -} // namespace esphome - #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index b63aa66d29..e628ac37a9 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_COVER -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.cover"; @@ -119,8 +118,7 @@ bool MQTTCoverComponent::publish_state() { return success; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_cover.h b/esphome/components/mqtt/mqtt_cover.h index f3e6053d0b..6b874af16a 100644 --- a/esphome/components/mqtt/mqtt_cover.h +++ b/esphome/components/mqtt/mqtt_cover.h @@ -8,8 +8,7 @@ #include "esphome/components/cover/cover.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTCoverComponent : public mqtt::MQTTComponent { public: @@ -36,8 +35,7 @@ class MQTTCoverComponent : public mqtt::MQTTComponent { cover::Cover *cover_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp index 0f0a334ae7..c5a17abdfd 100644 --- a/esphome/components/mqtt/mqtt_date.cpp +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -8,8 +8,7 @@ #ifdef USE_MQTT #ifdef USE_DATETIME_DATE -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.datetime"; @@ -62,8 +61,7 @@ bool MQTTDateComponent::publish_state(uint16_t year, uint8_t month, uint8_t day) }); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_date.h b/esphome/components/mqtt/mqtt_date.h index 5147afe7e7..380bb69e0e 100644 --- a/esphome/components/mqtt/mqtt_date.h +++ b/esphome/components/mqtt/mqtt_date.h @@ -8,8 +8,7 @@ #include "esphome/components/datetime/date_entity.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTDateComponent : public mqtt::MQTTComponent { public: @@ -38,8 +37,7 @@ class MQTTDateComponent : public mqtt::MQTTComponent { datetime::DateEntity *date_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_datetime.cpp b/esphome/components/mqtt/mqtt_datetime.cpp index 5c56baabe0..d2feddcb00 100644 --- a/esphome/components/mqtt/mqtt_datetime.cpp +++ b/esphome/components/mqtt/mqtt_datetime.cpp @@ -8,8 +8,7 @@ #ifdef USE_MQTT #ifdef USE_DATETIME_DATETIME -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.datetime.datetime"; @@ -78,8 +77,7 @@ bool MQTTDateTimeComponent::publish_state(uint16_t year, uint8_t month, uint8_t }); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATETIME #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_datetime.h b/esphome/components/mqtt/mqtt_datetime.h index ba81c06cb3..8706bfcf75 100644 --- a/esphome/components/mqtt/mqtt_datetime.h +++ b/esphome/components/mqtt/mqtt_datetime.h @@ -8,8 +8,7 @@ #include "esphome/components/datetime/datetime_entity.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTDateTimeComponent : public mqtt::MQTTComponent { public: @@ -38,8 +37,7 @@ class MQTTDateTimeComponent : public mqtt::MQTTComponent { datetime::DateTimeEntity *datetime_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATETIME #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp index fd095ea041..67a7aab5bd 100644 --- a/esphome/components/mqtt/mqtt_event.cpp +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_EVENT -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.event"; @@ -54,8 +53,7 @@ bool MQTTEventComponent::publish_event_(const std::string &event_type) { std::string MQTTEventComponent::component_type() const { return "event"; } const EntityBase *MQTTEventComponent::get_entity() const { return this->event_; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_event.h b/esphome/components/mqtt/mqtt_event.h index 4335820e53..fc6e778d44 100644 --- a/esphome/components/mqtt/mqtt_event.h +++ b/esphome/components/mqtt/mqtt_event.h @@ -8,8 +8,7 @@ #include "esphome/components/event/event.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTEventComponent : public mqtt::MQTTComponent { public: @@ -32,8 +31,7 @@ class MQTTEventComponent : public mqtt::MQTTComponent { event::Event *event_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 2aefc3a4db..ffecd9c663 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_FAN -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.fan"; @@ -182,8 +181,7 @@ bool MQTTFanComponent::publish_state() { return !failed; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index 78641d224f..16ce246853 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -8,8 +8,7 @@ #include "esphome/components/fan/fan.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTFanComponent : public mqtt::MQTTComponent { public: @@ -47,8 +46,7 @@ class MQTTFanComponent : public mqtt::MQTTComponent { fan::Fan *state_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index fe911bfba2..6a040e4b1c 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -8,8 +8,7 @@ #ifdef USE_LIGHT #include "esphome/components/light/light_json_schema.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.light"; @@ -92,8 +91,7 @@ void MQTTJSONLightComponent::dump_config() { LOG_MQTT_COMPONENT(true, true) } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_light.h b/esphome/components/mqtt/mqtt_light.h index a105f3d7b8..2cc631c901 100644 --- a/esphome/components/mqtt/mqtt_light.h +++ b/esphome/components/mqtt/mqtt_light.h @@ -8,8 +8,7 @@ #include "mqtt_component.h" #include "esphome/components/light/light_state.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTJSONLightComponent : public mqtt::MQTTComponent, public light::LightRemoteValuesListener { public: @@ -37,8 +36,7 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent, public light::LightRe light::LightState *state_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index 95efbf60e1..58fa675eb7 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_LOCK -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.lock"; @@ -58,8 +57,7 @@ bool MQTTLockComponent::publish_state() { #endif } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_lock.h b/esphome/components/mqtt/mqtt_lock.h index 789f74c795..6fb4998b25 100644 --- a/esphome/components/mqtt/mqtt_lock.h +++ b/esphome/components/mqtt/mqtt_lock.h @@ -8,8 +8,7 @@ #include "esphome/components/lock/lock.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTLockComponent : public mqtt::MQTTComponent { public: @@ -34,8 +33,7 @@ class MQTTLockComponent : public mqtt::MQTTComponent { lock::Lock *lock_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index f419eac130..381574ae56 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_NUMBER -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.number"; @@ -80,8 +79,7 @@ bool MQTTNumberComponent::publish_state(float value) { return this->publish(this->get_state_topic_(), buffer); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_number.h b/esphome/components/mqtt/mqtt_number.h index 10500c8333..b89e78a454 100644 --- a/esphome/components/mqtt/mqtt_number.h +++ b/esphome/components/mqtt/mqtt_number.h @@ -8,8 +8,7 @@ #include "esphome/components/number/number.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTNumberComponent : public mqtt::MQTTComponent { public: @@ -39,8 +38,7 @@ class MQTTNumberComponent : public mqtt::MQTTComponent { number::Number *number_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index e48af980c8..5edc5c50dc 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_SELECT -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.select"; @@ -53,8 +52,7 @@ bool MQTTSelectComponent::publish_state(const std::string &value) { return this->publish(this->get_state_topic_(), value); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_select.h b/esphome/components/mqtt/mqtt_select.h index e0d8ac2417..19aad662e5 100644 --- a/esphome/components/mqtt/mqtt_select.h +++ b/esphome/components/mqtt/mqtt_select.h @@ -8,8 +8,7 @@ #include "esphome/components/select/select.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTSelectComponent : public mqtt::MQTTComponent { public: @@ -39,8 +38,7 @@ class MQTTSelectComponent : public mqtt::MQTTComponent { select::Select *select_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 010ac3013e..bd79ae40fe 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -11,8 +11,7 @@ #include "esphome/components/deep_sleep/deep_sleep_component.h" #endif -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.sensor"; @@ -86,8 +85,7 @@ bool MQTTSensorComponent::publish_state(float value) { return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_sensor.h b/esphome/components/mqtt/mqtt_sensor.h index 15ea703ad4..8c60199e1b 100644 --- a/esphome/components/mqtt/mqtt_sensor.h +++ b/esphome/components/mqtt/mqtt_sensor.h @@ -8,8 +8,7 @@ #include "esphome/components/sensor/sensor.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTSensorComponent : public mqtt::MQTTComponent { public: @@ -51,8 +50,7 @@ class MQTTSensorComponent : public mqtt::MQTTComponent { optional expire_after_; // Override the expire after advertised to Home Assistant }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index b3a35420b9..a35ae8f9b6 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_SWITCH -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.switch"; @@ -57,8 +56,7 @@ bool MQTTSwitchComponent::publish_state(bool state) { return this->publish(this->get_state_topic_(), state_s); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_switch.h b/esphome/components/mqtt/mqtt_switch.h index c4d3f7164c..fb6a13f172 100644 --- a/esphome/components/mqtt/mqtt_switch.h +++ b/esphome/components/mqtt/mqtt_switch.h @@ -8,8 +8,7 @@ #include "esphome/components/switch/switch.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTSwitchComponent : public mqtt::MQTTComponent { public: @@ -34,8 +33,7 @@ class MQTTSwitchComponent : public mqtt::MQTTComponent { switch_::Switch *switch_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text.cpp b/esphome/components/mqtt/mqtt_text.cpp index 5ab0ca9688..3cb851fd38 100644 --- a/esphome/components/mqtt/mqtt_text.cpp +++ b/esphome/components/mqtt/mqtt_text.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_TEXT -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.text"; @@ -57,8 +56,7 @@ bool MQTTTextComponent::publish_state(const std::string &value) { return this->publish(this->get_state_topic_(), value); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text.h b/esphome/components/mqtt/mqtt_text.h index d9486fcbf8..0480b89395 100644 --- a/esphome/components/mqtt/mqtt_text.h +++ b/esphome/components/mqtt/mqtt_text.h @@ -8,8 +8,7 @@ #include "esphome/components/text/text.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTTextComponent : public mqtt::MQTTComponent { public: @@ -39,8 +38,7 @@ class MQTTTextComponent : public mqtt::MQTTComponent { text::Text *text_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index e6e7cf04e8..c87f22fb8e 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_TEXT_SENSOR -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.text_sensor"; @@ -43,8 +42,7 @@ bool MQTTTextSensor::send_initial_state() { std::string MQTTTextSensor::component_type() const { return "sensor"; } const EntityBase *MQTTTextSensor::get_entity() const { return this->sensor_; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text_sensor.h b/esphome/components/mqtt/mqtt_text_sensor.h index 9a14efdd16..d4d38d7eb2 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.h +++ b/esphome/components/mqtt/mqtt_text_sensor.h @@ -8,8 +8,7 @@ #include "esphome/components/text_sensor/text_sensor.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTTextSensor : public mqtt::MQTTComponent { public: @@ -32,8 +31,7 @@ class MQTTTextSensor : public mqtt::MQTTComponent { text_sensor::TextSensor *sensor_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_time.cpp b/esphome/components/mqtt/mqtt_time.cpp index 0c95bd8147..c97a463858 100644 --- a/esphome/components/mqtt/mqtt_time.cpp +++ b/esphome/components/mqtt/mqtt_time.cpp @@ -8,8 +8,7 @@ #ifdef USE_MQTT #ifdef USE_DATETIME_TIME -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.datetime.time"; @@ -62,8 +61,7 @@ bool MQTTTimeComponent::publish_state(uint8_t hour, uint8_t minute, uint8_t seco }); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_TIME #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_time.h b/esphome/components/mqtt/mqtt_time.h index b9dd822a73..60345c37ae 100644 --- a/esphome/components/mqtt/mqtt_time.h +++ b/esphome/components/mqtt/mqtt_time.h @@ -8,8 +8,7 @@ #include "esphome/components/datetime/time_entity.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTTimeComponent : public mqtt::MQTTComponent { public: @@ -38,8 +37,7 @@ class MQTTTimeComponent : public mqtt::MQTTComponent { datetime::TimeEntity *time_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_update.cpp b/esphome/components/mqtt/mqtt_update.cpp index 20f3a69a9e..150ddbf745 100644 --- a/esphome/components/mqtt/mqtt_update.cpp +++ b/esphome/components/mqtt/mqtt_update.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_UPDATE -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.update"; @@ -56,8 +55,7 @@ void MQTTUpdateComponent::dump_config() { std::string MQTTUpdateComponent::component_type() const { return "update"; } const EntityBase *MQTTUpdateComponent::get_entity() const { return this->update_; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_UPDATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_update.h b/esphome/components/mqtt/mqtt_update.h index 6fe04c4ea7..d04d22d25f 100644 --- a/esphome/components/mqtt/mqtt_update.h +++ b/esphome/components/mqtt/mqtt_update.h @@ -8,8 +8,7 @@ #include "esphome/components/update/update_entity.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTUpdateComponent : public mqtt::MQTTComponent { public: @@ -34,8 +33,7 @@ class MQTTUpdateComponent : public mqtt::MQTTComponent { update::UpdateEntity *update_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_UPDATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_valve.cpp b/esphome/components/mqtt/mqtt_valve.cpp index ae60670748..8ee693121b 100644 --- a/esphome/components/mqtt/mqtt_valve.cpp +++ b/esphome/components/mqtt/mqtt_valve.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_VALVE -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.valve"; @@ -89,8 +88,7 @@ bool MQTTValveComponent::publish_state() { return success; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_valve.h b/esphome/components/mqtt/mqtt_valve.h index 63a0462193..9e5221e495 100644 --- a/esphome/components/mqtt/mqtt_valve.h +++ b/esphome/components/mqtt/mqtt_valve.h @@ -8,8 +8,7 @@ #include "esphome/components/valve/valve.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTValveComponent : public mqtt::MQTTComponent { public: @@ -34,8 +33,7 @@ class MQTTValveComponent : public mqtt::MQTTComponent { valve::Valve *valve_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT From 11aed601b85d6f4e1605fa4cfc783af2e4593f41 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:34:38 -1000 Subject: [PATCH 1095/1145] [ble_scanner] Use stack-based string formatting to reduce heap allocations (#13013) --- esphome/components/ble_scanner/ble_scanner.h | 21 +++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/esphome/components/ble_scanner/ble_scanner.h b/esphome/components/ble_scanner/ble_scanner.h index 8bb51fcff2..7061b6d336 100644 --- a/esphome/components/ble_scanner/ble_scanner.h +++ b/esphome/components/ble_scanner/ble_scanner.h @@ -1,7 +1,8 @@ #pragma once +#include +#include #include -#include #include "esphome/core/component.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" @@ -15,17 +16,13 @@ namespace ble_scanner { class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component { public: bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { - this->publish_state("{\"timestamp\":" + to_string(::time(nullptr)) + - "," - "\"address\":\"" + - device.address_str() + - "\"," - "\"rssi\":" + - to_string(device.get_rssi()) + - "," - "\"name\":\"" + - device.get_name() + "\"}"); - + // Format JSON using stack buffer to avoid heap allocations from string concatenation + char buf[128]; + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + snprintf(buf, sizeof(buf), "{\"timestamp\":%" PRId64 ",\"address\":\"%s\",\"rssi\":%d,\"name\":\"%s\"}", + static_cast(::time(nullptr)), device.address_str_to(addr_buf), device.get_rssi(), + device.get_name().c_str()); + this->publish_state(buf); return true; } void dump_config() override; From d3e193cd7102c6f48fc6c9bf270aeafb0b4df98c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:34:58 -1000 Subject: [PATCH 1096/1145] [ota] Fix ESP32-S3 OTA crash with hardware SHA acceleration on IDF 5.5.x (#13021) --- esphome/components/esphome/ota/ota_esphome.cpp | 8 ++++++-- esphome/components/sha256/sha256.cpp | 15 +++++++++------ esphome/components/sha256/sha256.h | 18 ++++++++++++++---- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index ba25c69fae..f71163f79e 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -560,7 +560,9 @@ bool ESPHomeOTAComponent::handle_auth_send_() { // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame // (no passing to other functions). All hash operations must happen in this function. - sha256::SHA256 hasher; + // NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for + // hardware SHA acceleration DMA operations. + alignas(32) sha256::SHA256 hasher; const size_t hex_size = hasher.get_size() * 2; const size_t nonce_len = hasher.get_size() / 4; @@ -634,7 +636,9 @@ bool ESPHomeOTAComponent::handle_auth_read_() { // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame // (no passing to other functions). All hash operations must happen in this function. - sha256::SHA256 hasher; + // NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for + // hardware SHA acceleration DMA operations. + alignas(32) sha256::SHA256 hasher; hasher.init(); hasher.add(this->password_.c_str(), this->password_.length()); diff --git a/esphome/components/sha256/sha256.cpp b/esphome/components/sha256/sha256.cpp index 32abbd739d..48559d7c73 100644 --- a/esphome/components/sha256/sha256.cpp +++ b/esphome/components/sha256/sha256.cpp @@ -10,23 +10,26 @@ namespace esphome::sha256 { #if defined(USE_ESP32) || defined(USE_LIBRETINY) -// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS: +// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS (IDF 5.5.x): // // The ESP32-S3 uses hardware DMA for SHA acceleration. The mbedtls_sha256_context structure contains -// internal state that the DMA engine references. This imposes two critical constraints: +// internal state that the DMA engine references. This imposes three critical constraints: // -// 1. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to +// 1. ALIGNMENT: The SHA256 object MUST be declared with `alignas(32)` for proper DMA alignment. +// Without this, the DMA engine may crash with an abort in sha_hal_read_digest(). +// +// 2. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to // write to incorrect memory locations. This results in null pointer dereferences and crashes. // ALWAYS use fixed-size arrays (e.g., char buf[65], not char buf[size+1]). // -// 2. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same +// 3. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same // function. NEVER pass the SHA256 object or HashBase pointer to another function. When the stack // frame changes (function call/return), the DMA references become invalid and will produce // truncated hash output (20 bytes instead of 32) or corrupt memory. // // CORRECT USAGE: // void my_function() { -// sha256::SHA256 hasher; // Created locally +// alignas(32) sha256::SHA256 hasher; // Created locally with proper alignment // hasher.init(); // hasher.add(data, len); // Any size, no chunking needed // hasher.calculate(); @@ -36,7 +39,7 @@ namespace esphome::sha256 { // // INCORRECT USAGE (WILL FAIL ON ESP32-S3): // void my_function() { -// sha256::SHA256 hasher; +// sha256::SHA256 hasher; // WRONG: Missing alignas(32) // helper(&hasher); // WRONG: Passed to different stack frame // } // void helper(HashBase *h) { diff --git a/esphome/components/sha256/sha256.h b/esphome/components/sha256/sha256.h index a2b62799e1..17d80636f1 100644 --- a/esphome/components/sha256/sha256.h +++ b/esphome/components/sha256/sha256.h @@ -22,6 +22,18 @@ namespace esphome::sha256 { +/// SHA256 hash implementation. +/// +/// CRITICAL for ESP32-S3 with IDF 5.5.x hardware SHA acceleration: +/// 1. SHA256 objects MUST be declared with `alignas(32)` for proper DMA alignment +/// 2. The object MUST stay in the same stack frame (no passing to other functions) +/// 3. NO Variable Length Arrays (VLAs) in the same function +/// +/// Example usage: +/// alignas(32) sha256::SHA256 hasher; +/// hasher.init(); +/// hasher.add(data, len); +/// hasher.calculate(); class SHA256 : public esphome::HashBase { public: SHA256() = default; @@ -39,10 +51,8 @@ class SHA256 : public esphome::HashBase { protected: #if defined(USE_ESP32) || defined(USE_LIBRETINY) - // CRITICAL: The mbedtls context MUST be stack-allocated (not a pointer) for ESP32-S3 hardware SHA acceleration. - // The ESP32-S3 DMA engine references this structure's memory addresses. If the context is passed to another - // function (crossing stack frames) or if VLAs are present, the DMA operations will corrupt memory and produce - // truncated/incorrect hash results. + // The mbedtls context for ESP32-S3 hardware SHA requires proper alignment and stack frame constraints. + // See class documentation above for critical requirements. mbedtls_sha256_context ctx_{}; #elif defined(USE_ESP8266) || defined(USE_RP2040) br_sha256_context ctx_{}; From c1ad39a0724baf685de035ffa833375372fa1e98 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:35:16 -1000 Subject: [PATCH 1097/1145] [wifi] Clean up duplicate and empty logging output (#13018) --- esphome/components/wifi/wifi_component.cpp | 39 ++++++++-------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index ba25bc9f76..e1dc2d17d6 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -317,7 +317,6 @@ void WiFiComponent::start_initial_connection_() { WiFiAP params = this->build_params_for_current_phase_(); this->start_connecting(params); } else { - ESP_LOGI(TAG, "Starting scan"); this->start_scanning(); } } @@ -369,11 +368,7 @@ void WiFiComponent::setup() { } void WiFiComponent::start() { - char mac_s[18]; - ESP_LOGCONFIG(TAG, - "Starting\n" - " Local MAC: %s", - get_mac_address_pretty_into_buffer(mac_s)); + ESP_LOGCONFIG(TAG, "Starting"); this->last_connected_ = millis(); uint32_t hash = this->has_sta() ? App.get_config_version_hash() : 88491487UL; @@ -857,14 +852,6 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { } const LogString *get_signal_bars(int8_t rssi) { - // Check for disconnected sentinel value first - if (rssi == WIFI_RSSI_DISCONNECTED) { - // MULTIPLICATION SIGN - // Unicode: U+00D7, UTF-8: C3 97 - return LOG_STR("\033[0;31m" // red - "\xc3\x97\xc3\x97\xc3\x97\xc3\x97" - "\033[0m"); - } // LOWER ONE QUARTER BLOCK // Unicode: U+2582, UTF-8: E2 96 82 // LOWER HALF BLOCK @@ -909,15 +896,8 @@ const LogString *get_signal_bars(int8_t rssi) { void WiFiComponent::print_connect_params_() { bssid_t bssid = wifi_bssid(); - char bssid_s[18]; + char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; format_mac_addr_upper(bssid.data(), bssid_s); - - char mac_s[18]; - ESP_LOGCONFIG(TAG, " Local MAC: %s", get_mac_address_pretty_into_buffer(mac_s)); - if (this->is_disabled()) { - ESP_LOGCONFIG(TAG, " Disabled"); - return; - } // Use stack buffers for IP address formatting to avoid heap allocations char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; for (auto &ip : wifi_sta_ip_addresses()) { @@ -1189,11 +1169,19 @@ void WiFiComponent::check_scanning_finished() { } void WiFiComponent::dump_config() { + char mac_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, "WiFi:\n" + " Local MAC: %s\n" " Connected: %s", - YESNO(this->is_connected())); - this->print_connect_params_(); + get_mac_address_pretty_into_buffer(mac_s), YESNO(this->is_connected())); + if (this->is_disabled()) { + ESP_LOGCONFIG(TAG, " Disabled"); + return; + } + if (this->is_connected()) { + this->print_connect_params_(); + } } void WiFiComponent::check_connecting_finished() { @@ -1223,8 +1211,6 @@ void WiFiComponent::check_connecting_finished() { // the first connection as a failure. this->error_from_callback_ = false; - this->print_connect_params_(); - if (this->has_ap()) { #ifdef USE_CAPTIVE_PORTAL if (this->is_captive_portal_active_()) { @@ -1242,6 +1228,7 @@ void WiFiComponent::check_connecting_finished() { this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED; this->num_retried_ = 0; + this->print_connect_params_(); // Clear priority tracking if all priorities are at minimum this->clear_priorities_if_all_min_(); From 2c6584baf50a65b26e6d1b072810bf7a4f5ad2bb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:35:40 -1000 Subject: [PATCH 1098/1145] [xiaomi_ble] Simplify set_bindkey using parse_hex and const char* (#13014) --- esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp | 12 +----------- esphome/components/xiaomi_cgd1/xiaomi_cgd1.h | 2 +- esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp | 12 +----------- esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h | 2 +- esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp | 12 +----------- esphome/components/xiaomi_cgg1/xiaomi_cgg1.h | 2 +- esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp | 13 ++----------- esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h | 2 +- .../xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp | 12 +----------- .../xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h | 2 +- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp | 12 +----------- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h | 2 +- .../components/xiaomi_mhoc401/xiaomi_mhoc401.cpp | 12 +----------- esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h | 2 +- .../xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp | 13 ++----------- .../components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h | 2 +- .../xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp | 12 +----------- .../components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h | 2 +- .../xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp | 12 +----------- .../xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h | 2 +- 20 files changed, 22 insertions(+), 120 deletions(-) diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index 1aa542633a..82a04f0d6e 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -63,17 +63,7 @@ bool XiaomiCGD1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiCGD1::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiCGD1::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_cgd1 } // namespace esphome diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h index 393795439b..4a34eea32a 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h @@ -13,7 +13,7 @@ namespace xiaomi_cgd1 { class XiaomiCGD1 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index a049854935..39ece3e091 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -63,17 +63,7 @@ bool XiaomiCGDK2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiCGDK2::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiCGDK2::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_cgdk2 } // namespace esphome diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h index 1f5ef89869..ed917e2bbd 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h @@ -13,7 +13,7 @@ namespace xiaomi_cgdk2 { class XiaomiCGDK2 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index da4bab6623..448592db16 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -63,17 +63,7 @@ bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiCGG1::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiCGG1::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_cgg1 } // namespace esphome diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h index 52904fd75e..c560bddd69 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h @@ -13,7 +13,7 @@ namespace xiaomi_cgg1 { class XiaomiCGG1 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp index 2048c786d3..8813f6479b 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp @@ -1,4 +1,5 @@ #include "xiaomi_cgpr1.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -59,17 +60,7 @@ bool XiaomiCGPR1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiCGPR1::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiCGPR1::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_cgpr1 } // namespace esphome diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h index 124f9411a1..82bbbfa58d 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h @@ -16,7 +16,7 @@ class XiaomiCGPR1 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; diff --git a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp index edd9f67f56..2dd60d4ecb 100644 --- a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp +++ b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp @@ -63,17 +63,7 @@ bool XiaomiLYWSD02MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device return success; } -void XiaomiLYWSD02MMC::set_bindkey(const std::string &bindkey) { - memset(this->bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - this->bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiLYWSD02MMC::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_lywsd02mmc } // namespace esphome diff --git a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h index e1e0fcae40..968604fee6 100644 --- a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h +++ b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h @@ -13,7 +13,7 @@ namespace xiaomi_lywsd02mmc { class XiaomiLYWSD02MMC : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { this->address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index 2b4b67c92f..b11bbdc40c 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -67,17 +67,7 @@ bool XiaomiLYWSD03MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device return success; } -void XiaomiLYWSD03MMC::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiLYWSD03MMC::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_lywsd03mmc } // namespace esphome diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h index 3c7907479a..d890e5ed12 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h @@ -13,7 +13,7 @@ namespace xiaomi_lywsd03mmc { class XiaomiLYWSD03MMC : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index e1b808c54e..10cd15ddbd 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -67,17 +67,7 @@ bool XiaomiMHOC401::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiMHOC401::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiMHOC401::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_mhoc401 } // namespace esphome diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h index 1acdaa88af..13547e45d9 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h @@ -13,7 +13,7 @@ namespace xiaomi_mhoc401 { class XiaomiMHOC401 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp index eb4862a7e9..ec03c851cd 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp @@ -1,4 +1,5 @@ #include "xiaomi_mjyd02yla.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -62,17 +63,7 @@ bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device) return success; } -void XiaomiMJYD02YLA::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiMJYD02YLA::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_mjyd02yla } // namespace esphome diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h index e1b4055696..bf9dcaf844 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h @@ -16,7 +16,7 @@ class XiaomiMJYD02YLA : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; diff --git a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp index d5b89507fe..ee3ad316e1 100644 --- a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp +++ b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp @@ -79,17 +79,7 @@ bool XiaomiRTCGQ02LM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) return success; } -void XiaomiRTCGQ02LM::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiRTCGQ02LM::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_rtcgq02lm } // namespace esphome diff --git a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h index ae00a28ac9..87dfc0b62b 100644 --- a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h +++ b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h @@ -19,7 +19,7 @@ namespace xiaomi_rtcgq02lm { class XiaomiRTCGQ02LM : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp index f126e8bdfd..50cf5f2d76 100644 --- a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp +++ b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp @@ -67,17 +67,7 @@ bool XiaomiXMWSDJ04MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &devic return success; } -void XiaomiXMWSDJ04MMC::set_bindkey(const std::string &bindkey) { - memset(this->bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - this->bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiXMWSDJ04MMC::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_xmwsdj04mmc } // namespace esphome diff --git a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h index ed0458ce49..22cac63059 100644 --- a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h +++ b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h @@ -13,7 +13,7 @@ namespace xiaomi_xmwsdj04mmc { class XiaomiXMWSDJ04MMC : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { this->address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; From ac42102320a722fe144bfa57ca6dc4d050dd2e80 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 07:36:01 -1000 Subject: [PATCH 1099/1145] [core] Auto-replace / in entity names with Unicode fraction slash during deprecation period (#13016) --- esphome/config_validation.py | 21 ++++++++++++--- tests/unit_tests/test_config_validation.py | 31 +++++++++++++++++----- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index b0da88c50d..81a30cb0b7 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1981,16 +1981,31 @@ MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( ) +# Unicode FRACTION SLASH (U+2044) - visually similar to '/' but URL-safe +FRACTION_SLASH = "\u2044" + + def _validate_no_slash(value): """Validate that a name does not contain '/' characters. The '/' character is used as a path separator in web server URLs, so it cannot be used in entity or device names. + + During the deprecation period, '/' is automatically replaced with + the visually similar Unicode FRACTION SLASH (U+2044) character. """ if "/" in value: - raise Invalid( - f"Name cannot contain '/' character (used as URL path separator): {value}" + # Remove before 2026.7.0 + new_value = value.replace("/", FRACTION_SLASH) + _LOGGER.warning( + "'%s' contains '/' which is reserved as a URL path separator. " + "Automatically replacing with '%s' (Unicode FRACTION SLASH). " + "Please update your configuration. " + "This will become an error in ESPHome 2026.7.0.", + value, + new_value, ) + return new_value return value @@ -2019,7 +2034,7 @@ def _validate_entity_name(value): f"Maximum length is {NAME_MAX_LENGTH} characters." ) # Validate no '/' in name for web server URL compatibility - _validate_no_slash(value) + value = _validate_no_slash(value) return value diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 94224f2364..9602010ad3 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -510,10 +510,23 @@ def test_string_no_slash__valid(value: str) -> None: assert actual == value -@pytest.mark.parametrize("value", ("has/slash", "a/b/c", "/leading", "trailing/")) -def test_string_no_slash__slash_rejected(value: str) -> None: - with pytest.raises(Invalid, match="cannot contain '/' character"): - config_validation.string_no_slash(value) +@pytest.mark.parametrize( + ("value", "expected"), + ( + ("has/slash", "has⁄slash"), + ("a/b/c", "a⁄b⁄c"), + ("/leading", "⁄leading"), + ("trailing/", "trailing⁄"), + ), +) +def test_string_no_slash__slash_replaced_with_warning( + value: str, expected: str, caplog: pytest.LogCaptureFixture +) -> None: + """Test that '/' is auto-replaced with fraction slash and warning is logged.""" + actual = config_validation.string_no_slash(value) + assert actual == expected + assert "reserved as a URL path separator" in caplog.text + assert "will become an error in ESPHome 2026.7.0" in caplog.text def test_string_no_slash__long_string_allowed() -> None: @@ -532,9 +545,13 @@ def test_validate_entity_name__valid(value: str) -> None: assert actual == value -def test_validate_entity_name__slash_rejected() -> None: - with pytest.raises(Invalid, match="cannot contain '/' character"): - config_validation._validate_entity_name("has/slash") +def test_validate_entity_name__slash_replaced_with_warning( + caplog: pytest.LogCaptureFixture, +) -> None: + """Test that '/' in entity names is auto-replaced with fraction slash.""" + actual = config_validation._validate_entity_name("has/slash") + assert actual == "has⁄slash" + assert "reserved as a URL path separator" in caplog.text def test_validate_entity_name__max_length() -> None: From d6c2dd3c26fb26fce9d7dfa495b594e2ba4687c7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 08:21:16 -1000 Subject: [PATCH 1100/1145] [wifi] Eliminate heap allocations in IP address logging (#13017) --- esphome/components/wifi/wifi_component.cpp | 4 +-- .../wifi/wifi_component_esp8266.cpp | 28 ++++++------------- .../wifi/wifi_component_libretiny.cpp | 13 ++------- 3 files changed, 14 insertions(+), 31 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e1dc2d17d6..2d635d893f 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -639,13 +639,13 @@ void WiFiComponent::setup_ap_config_() { } this->ap_setup_ = this->wifi_start_ap_(this->ap_); - auto ip_address = this->wifi_soft_ap_ip().str(); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, "Setting up AP:\n" " AP SSID: '%s'\n" " AP Password: '%s'\n" " IP Address: %s", - this->ap_.get_ssid().c_str(), this->ap_.get_password().c_str(), ip_address.c_str()); + this->ap_.get_ssid().c_str(), this->ap_.get_password().c_str(), this->wifi_soft_ap_ip().str_to(ip_buf)); #ifdef USE_WIFI_MANUAL_IP auto manual_ip = this->ap_.get_manual_ip(); diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 9d99e0b94c..b7d820413c 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -371,7 +371,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { while (!connected) { uint8_t ipv6_addr_count = 0; for (auto addr : addrList) { - ESP_LOGV(TAG, "Address %s", addr.toString().c_str()); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGV(TAG, "Address %s", network::IPAddress(addr.ipFromNetifNum()).str_to(ip_buf)); if (addr.isV6()) { ipv6_addr_count++; } @@ -413,21 +414,6 @@ const LogString *get_auth_mode_str(uint8_t mode) { return LOG_STR("UNKNOWN"); } } -#ifdef ipv4_addr -std::string format_ip_addr(struct ipv4_addr ip) { - char buf[20]; - sprintf(buf, "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16), - uint8_t(ip.addr >> 24)); - return buf; -} -#else -std::string format_ip_addr(struct ip_addr ip) { - char buf[20]; - sprintf(buf, "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16), - uint8_t(ip.addr >> 24)); - return buf; -} -#endif const LogString *get_op_mode_str(uint8_t mode) { switch (mode) { case WIFI_OFF: @@ -582,8 +568,10 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { } case EVENT_STAMODE_GOT_IP: { auto it = event->event_info.got_ip; - ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", format_ip_addr(it.ip).c_str(), format_ip_addr(it.gw).c_str(), - format_ip_addr(it.mask).c_str()); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE], gw_buf[network::IP_ADDRESS_BUFFER_SIZE], + mask_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", network::IPAddress(&it.ip).str_to(ip_buf), + network::IPAddress(&it.gw).str_to(gw_buf), network::IPAddress(&it.mask).str_to(mask_buf)); s_sta_got_ip = true; #ifdef USE_WIFI_LISTENERS for (auto *listener : global_wifi_component->ip_state_listeners_) { @@ -635,8 +623,10 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE auto it = event->event_info.distribute_sta_ip; char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; format_mac_addr_upper(it.mac, mac_buf); - ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", mac_buf, format_ip_addr(it.ip).c_str(), it.aid); + ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", mac_buf, network::IPAddress(&it.ip).str_to(ip_buf), + it.aid); #endif break; } diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index e9ccb86871..68fcc3577d 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -231,14 +231,6 @@ const char *get_auth_mode_str(uint8_t mode) { } } -using esphome_ip4_addr_t = IPAddress; - -std::string format_ip4_addr(const esphome_ip4_addr_t &ip) { - char buf[20]; - uint32_t addr = ip; - sprintf(buf, "%u.%u.%u.%u", uint8_t(addr >> 0), uint8_t(addr >> 8), uint8_t(addr >> 16), uint8_t(addr >> 24)); - return buf; -} const char *get_op_mode_str(uint8_t mode) { switch (mode) { case WIFI_OFF: @@ -530,8 +522,9 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { break; } case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: { - ESP_LOGV(TAG, "static_ip=%s gateway=%s", format_ip4_addr(WiFi.localIP()).c_str(), - format_ip4_addr(WiFi.gatewayIP()).c_str()); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE], gw_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGV(TAG, "static_ip=%s gateway=%s", network::IPAddress(WiFi.localIP()).str_to(ip_buf), + network::IPAddress(WiFi.gatewayIP()).str_to(gw_buf)); s_sta_state = LTWiFiSTAState::CONNECTED; #ifdef USE_WIFI_LISTENERS for (auto *listener : this->ip_state_listeners_) { From 8eb28a7724798fae4a1b6d0ee5203ee8221fdffb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 08:38:39 -1000 Subject: [PATCH 1101/1145] [neopixelbus] Fix ESP8266 compilation by enabling Serial/Serial1 for NeoPixelBus library (#13027) --- esphome/components/neopixelbus/light.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index d071059185..c77217243c 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -194,6 +194,14 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): + if CORE.is_esp8266: + # NeoPixelBus library unconditionally includes NeoEsp8266UartMethod.h + # which references Serial and Serial1, so we must enable both + from esphome.components.esp8266.const import enable_serial, enable_serial1 + + enable_serial() + enable_serial1() + has_white = "W" in config[CONF_TYPE] method = config[CONF_METHOD] From 4419bf02b1864976d67fdcc0407f70f80cdbd7fe Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Tue, 6 Jan 2026 21:26:27 +0100 Subject: [PATCH 1102/1145] [async_tcp] Fix build conflicts and use IDF component for ESP32 (#13025) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- .clang-tidy.hash | 2 +- esphome/components/async_tcp/__init__.py | 6 +++--- esphome/components/async_tcp/async_tcp.h | 5 ++--- esphome/components/async_tcp/async_tcp_socket.cpp | 5 +++-- esphome/components/async_tcp/async_tcp_socket.h | 6 +++--- esphome/idf_component.yml | 2 ++ platformio.ini | 1 - 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 59caddf59b..0a71b6859f 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -97fb425f1d681a5994ed1cc6187910f5d2c37ee577b6dc07eb3f4d8862a011de +191a0e6ab5842d153dd77a2023bc5742f9d4333c334de8d81b57f2b8d4d4b65e diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 4b6c6a275c..1ff4805f03 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -26,12 +26,12 @@ CONFIG_SCHEMA = cv.Schema({}) @coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT) async def to_code(config): - if CORE.using_esp_idf: - # ESP-IDF needs the IDF component + if CORE.is_esp32: + # https://github.com/ESP32Async/AsyncTCP from esphome.components.esp32 import add_idf_component add_idf_component(name="esp32async/asynctcp", ref="3.4.91") - elif CORE.is_esp32 or CORE.is_libretiny: + elif CORE.is_libretiny: # https://github.com/ESP32Async/AsyncTCP cg.add_library("ESP32Async/AsyncTCP", "3.4.5") elif CORE.is_esp8266: diff --git a/esphome/components/async_tcp/async_tcp.h b/esphome/components/async_tcp/async_tcp.h index 362f603451..6d9211f023 100644 --- a/esphome/components/async_tcp/async_tcp.h +++ b/esphome/components/async_tcp/async_tcp.h @@ -1,9 +1,8 @@ #pragma once #include "esphome/core/defines.h" -#if (defined(USE_ESP32) || defined(USE_LIBRETINY)) && !defined(CLANG_TIDY) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) // Use AsyncTCP library for ESP32 (Arduino or ESP-IDF) and LibreTiny -// But not for clang-tidy as the header file isn't present in that case #include #elif defined(USE_ESP8266) // Use ESPAsyncTCP library for ESP8266 (always Arduino) @@ -12,6 +11,6 @@ // Use AsyncTCP_RP2040W library for RP2040 #include #else -// Use socket-based implementation for other platforms and clang-tidy +// Use socket-based implementation for other platforms #include "async_tcp_socket.h" #endif diff --git a/esphome/components/async_tcp/async_tcp_socket.cpp b/esphome/components/async_tcp/async_tcp_socket.cpp index 6c13f346e9..f64e494f5f 100644 --- a/esphome/components/async_tcp/async_tcp_socket.cpp +++ b/esphome/components/async_tcp/async_tcp_socket.cpp @@ -1,6 +1,7 @@ #include "async_tcp_socket.h" -#if defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) +#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \ + (defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)) #include "esphome/components/network/util.h" #include "esphome/core/log.h" @@ -158,4 +159,4 @@ void AsyncClient::loop() { } // namespace esphome::async_tcp -#endif // defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) +#endif diff --git a/esphome/components/async_tcp/async_tcp_socket.h b/esphome/components/async_tcp/async_tcp_socket.h index ca3bf19d67..28714a7752 100644 --- a/esphome/components/async_tcp/async_tcp_socket.h +++ b/esphome/components/async_tcp/async_tcp_socket.h @@ -2,7 +2,8 @@ #include "esphome/core/defines.h" -#if defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) +#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \ + (defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)) #include "esphome/components/socket/socket.h" #include @@ -69,5 +70,4 @@ class AsyncClient { // Expose AsyncClient in global namespace to match library behavior using esphome::async_tcp::AsyncClient; // NOLINT(google-global-names-in-headers) -#define ESPHOME_ASYNC_TCP_SOCKET_IMPL -#endif // defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) +#endif diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 36aa77c524..2dc5b94847 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -31,3 +31,5 @@ dependencies: version: 0.2.2 rules: - if: "target in [esp32, esp32s2, esp32s3, esp32p4]" + esp32async/asynctcp: + version: 3.4.91 diff --git a/platformio.ini b/platformio.ini index dd9eb566c5..d96e9ad2cc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -146,7 +146,6 @@ lib_deps = WiFi ; wifi,web_server_base,ethernet (Arduino built-in) Update ; ota,web_server_base (Arduino built-in) ${common:arduino.lib_deps} - ESP32Async/AsyncTCP@3.4.5 ; async_tcp NetworkClientSecure ; http_request,nextion (Arduino built-in) HTTPClient ; http_request,nextion (Arduino built-in) ESPmDNS ; mdns (Arduino built-in) From 412ab5dbbf681f47f99cad7a69f9b8854dff34cb Mon Sep 17 00:00:00 2001 From: Jas Strong Date: Tue, 6 Jan 2026 13:31:50 -0800 Subject: [PATCH 1103/1145] [aqi] Implement a sensor that computes AQI (#12958) Co-authored-by: jas --- esphome/components/aqi/aqi_calculator.h | 27 ++++++----- esphome/components/aqi/aqi_sensor.cpp | 52 ++++++++++++++++++++++ esphome/components/aqi/aqi_sensor.h | 31 +++++++++++++ esphome/components/aqi/caqi_calculator.h | 25 +++++------ esphome/components/aqi/sensor.py | 51 +++++++++++++++++++++ esphome/components/hm3301/sensor.py | 9 ++++ esphome/components/pmsx003/pmsx003.cpp | 7 --- esphome/components/pmsx003/pmsx003.h | 11 ----- esphome/components/pmsx003/sensor.py | 26 ----------- tests/components/aqi/common.yaml | 22 +++++++++ tests/components/aqi/test.esp32-idf.yaml | 1 + tests/components/aqi/test.esp8266-ard.yaml | 1 + tests/components/aqi/test.rp2040-ard.yaml | 1 + tests/components/pmsx003/common.yaml | 3 -- 14 files changed, 193 insertions(+), 74 deletions(-) create mode 100644 esphome/components/aqi/aqi_sensor.cpp create mode 100644 esphome/components/aqi/aqi_sensor.h create mode 100644 esphome/components/aqi/sensor.py create mode 100644 tests/components/aqi/common.yaml create mode 100644 tests/components/aqi/test.esp32-idf.yaml create mode 100644 tests/components/aqi/test.esp8266-ard.yaml create mode 100644 tests/components/aqi/test.rp2040-ard.yaml diff --git a/esphome/components/aqi/aqi_calculator.h b/esphome/components/aqi/aqi_calculator.h index 959d6a2438..35dc35a44a 100644 --- a/esphome/components/aqi/aqi_calculator.h +++ b/esphome/components/aqi/aqi_calculator.h @@ -10,38 +10,37 @@ namespace esphome::aqi { class AQICalculator : public AbstractAQICalculator { public: uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { - int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); - int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); + int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID); + int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID); return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index; } protected: - static const int AMOUNT_OF_LEVELS = 6; + static constexpr int NUM_LEVELS = 6; - int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}}; + static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}}; - int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, - {56, 125}, {126, 225}, {226, INT_MAX}}; + static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, {56, 125}, {126, 225}, {226, INT_MAX}}; - int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, - {255, 354}, {355, 424}, {425, INT_MAX}}; + static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, + {255, 354}, {355, 424}, {425, INT_MAX}}; - int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { - int grid_index = get_grid_index_(value, array); + static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) { + int grid_index = get_grid_index(value, array); if (grid_index == -1) { return -1; } - int aqi_lo = index_grid_[grid_index][0]; - int aqi_hi = index_grid_[grid_index][1]; + int aqi_lo = INDEX_GRID[grid_index][0]; + int aqi_hi = INDEX_GRID[grid_index][1]; int conc_lo = array[grid_index][0]; int conc_hi = array[grid_index][1]; return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo; } - int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { - for (int i = 0; i < AMOUNT_OF_LEVELS; i++) { + static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) { + for (int i = 0; i < NUM_LEVELS; i++) { if (value >= array[i][0] && value <= array[i][1]) { return i; } diff --git a/esphome/components/aqi/aqi_sensor.cpp b/esphome/components/aqi/aqi_sensor.cpp new file mode 100644 index 0000000000..cdc9f35ba6 --- /dev/null +++ b/esphome/components/aqi/aqi_sensor.cpp @@ -0,0 +1,52 @@ +#include "aqi_sensor.h" +#include "esphome/core/log.h" + +namespace esphome::aqi { + +static const char *const TAG = "aqi"; + +void AQISensor::setup() { + if (this->pm_2_5_sensor_ != nullptr) { + this->pm_2_5_sensor_->add_on_state_callback([this](float value) { + this->pm_2_5_value_ = value; + // Defer calculation to avoid double-publishing if both sensors update in the same loop + this->defer("update", [this]() { this->calculate_aqi_(); }); + }); + } + if (this->pm_10_0_sensor_ != nullptr) { + this->pm_10_0_sensor_->add_on_state_callback([this](float value) { + this->pm_10_0_value_ = value; + this->defer("update", [this]() { this->calculate_aqi_(); }); + }); + } +} + +void AQISensor::dump_config() { + ESP_LOGCONFIG(TAG, "AQI Sensor:"); + ESP_LOGCONFIG(TAG, " Calculation Type: %s", this->aqi_calc_type_ == AQI_TYPE ? "AQI" : "CAQI"); + if (this->pm_2_5_sensor_ != nullptr) { + ESP_LOGCONFIG(TAG, " PM2.5 Sensor: '%s'", this->pm_2_5_sensor_->get_name().c_str()); + } + if (this->pm_10_0_sensor_ != nullptr) { + ESP_LOGCONFIG(TAG, " PM10 Sensor: '%s'", this->pm_10_0_sensor_->get_name().c_str()); + } + LOG_SENSOR(" ", "AQI", this); +} + +void AQISensor::calculate_aqi_() { + if (std::isnan(this->pm_2_5_value_) || std::isnan(this->pm_10_0_value_)) { + return; + } + + AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_); + if (calculator == nullptr) { + ESP_LOGW(TAG, "Unknown AQI calculator type"); + return; + } + + uint16_t aqi = + calculator->get_aqi(static_cast(this->pm_2_5_value_), static_cast(this->pm_10_0_value_)); + this->publish_state(aqi); +} + +} // namespace esphome::aqi diff --git a/esphome/components/aqi/aqi_sensor.h b/esphome/components/aqi/aqi_sensor.h new file mode 100644 index 0000000000..a990f815fe --- /dev/null +++ b/esphome/components/aqi/aqi_sensor.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "aqi_calculator_factory.h" + +namespace esphome::aqi { + +class AQISensor : public sensor::Sensor, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + void set_pm_2_5_sensor(sensor::Sensor *sensor) { this->pm_2_5_sensor_ = sensor; } + void set_pm_10_0_sensor(sensor::Sensor *sensor) { this->pm_10_0_sensor_ = sensor; } + void set_aqi_calculation_type(AQICalculatorType type) { this->aqi_calc_type_ = type; } + + protected: + void calculate_aqi_(); + + sensor::Sensor *pm_2_5_sensor_{nullptr}; + sensor::Sensor *pm_10_0_sensor_{nullptr}; + AQICalculatorType aqi_calc_type_{AQI_TYPE}; + AQICalculatorFactory aqi_calculator_factory_; + + float pm_2_5_value_{NAN}; + float pm_10_0_value_{NAN}; +}; + +} // namespace esphome::aqi diff --git a/esphome/components/aqi/caqi_calculator.h b/esphome/components/aqi/caqi_calculator.h index d493dcdf39..9906c179f6 100644 --- a/esphome/components/aqi/caqi_calculator.h +++ b/esphome/components/aqi/caqi_calculator.h @@ -1,6 +1,5 @@ #pragma once -#include "esphome/core/log.h" #include "abstract_aqi_calculator.h" namespace esphome::aqi { @@ -8,37 +7,37 @@ namespace esphome::aqi { class CAQICalculator : public AbstractAQICalculator { public: uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { - int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); - int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); + int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID); + int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID); return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index; } protected: - static const int AMOUNT_OF_LEVELS = 5; + static constexpr int NUM_LEVELS = 5; - int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}}; + static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}}; - int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}}; + static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}}; - int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}}; + static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}}; - int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { - int grid_index = get_grid_index_(value, array); + static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) { + int grid_index = get_grid_index(value, array); if (grid_index == -1) { return -1; } - int aqi_lo = index_grid_[grid_index][0]; - int aqi_hi = index_grid_[grid_index][1]; + int aqi_lo = INDEX_GRID[grid_index][0]; + int aqi_hi = INDEX_GRID[grid_index][1]; int conc_lo = array[grid_index][0]; int conc_hi = array[grid_index][1]; return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo; } - int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { - for (int i = 0; i < AMOUNT_OF_LEVELS; i++) { + static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) { + for (int i = 0; i < NUM_LEVELS; i++) { if (value >= array[i][0] && value <= array[i][1]) { return i; } diff --git a/esphome/components/aqi/sensor.py b/esphome/components/aqi/sensor.py new file mode 100644 index 0000000000..0b5ee8d75a --- /dev/null +++ b/esphome/components/aqi/sensor.py @@ -0,0 +1,51 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_PM_2_5, + CONF_PM_10_0, + DEVICE_CLASS_AQI, + STATE_CLASS_MEASUREMENT, +) + +from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, aqi_ns + +CODEOWNERS = ["@jasstrong"] +DEPENDENCIES = ["sensor"] + +UNIT_INDEX = "index" + +AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + AQISensor, + unit_of_measurement=UNIT_INDEX, + accuracy_decimals=0, + device_class=DEVICE_CLASS_AQI, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.Required(CONF_PM_2_5): cv.use_id(sensor.Sensor), + cv.Required(CONF_PM_10_0): cv.use_id(sensor.Sensor), + cv.Required(CONF_CALCULATION_TYPE): cv.enum( + AQI_CALCULATION_TYPE, upper=True + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + + pm_2_5_sensor = await cg.get_variable(config[CONF_PM_2_5]) + cg.add(var.set_pm_2_5_sensor(pm_2_5_sensor)) + + pm_10_0_sensor = await cg.get_variable(config[CONF_PM_10_0]) + cg.add(var.set_pm_10_0_sensor(pm_10_0_sensor)) + + cg.add(var.set_aqi_calculation_type(config[CONF_CALCULATION_TYPE])) diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index 389da97b1e..9546ae1c3c 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -1,3 +1,5 @@ +import logging + import esphome.codegen as cg from esphome.components import i2c, sensor from esphome.components.aqi import AQI_CALCULATION_TYPE, CONF_AQI, CONF_CALCULATION_TYPE @@ -16,6 +18,8 @@ from esphome.const import ( UNIT_MICROGRAMS_PER_CUBIC_METER, ) +_LOGGER = logging.getLogger(__name__) + DEPENDENCIES = ["i2c"] AUTO_LOAD = ["aqi"] CODEOWNERS = ["@freekode"] @@ -99,7 +103,12 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_PM_10_0]) cg.add(var.set_pm_10_0_sensor(sens)) + # Remove before 2026.12.0 if CONF_AQI in config: + _LOGGER.warning( + "The 'aqi' option in hm3301 is deprecated, " + "please use the standalone 'aqi' sensor platform instead." + ) sens = await sensor.new_sensor(config[CONF_AQI]) cg.add(var.set_aqi_sensor(sens)) cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index 3bdb5219ed..bb167033d1 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -265,13 +265,6 @@ void PMSX003Component::parse_data_() { if (this->pm_particles_25um_sensor_ != nullptr) this->pm_particles_25um_sensor_->publish_state(pm_particles_25um); - // Calculate and publish AQI if sensor is configured - if (this->aqi_sensor_ != nullptr) { - aqi::AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_); - int32_t aqi_value = calculator->get_aqi(pm_2_5_concentration, pm_10_0_concentration); - this->aqi_sensor_->publish_state(aqi_value); - } - if (this->type_ == PMSX003_TYPE_5003T) { ESP_LOGD(TAG, "Got PM0.3 Particles: %u Count/0.1L, PM0.5 Particles: %u Count/0.1L, PM1.0 Particles: %u Count/0.1L, " diff --git a/esphome/components/pmsx003/pmsx003.h b/esphome/components/pmsx003/pmsx003.h index 229972e2e5..f48121800e 100644 --- a/esphome/components/pmsx003/pmsx003.h +++ b/esphome/components/pmsx003/pmsx003.h @@ -4,7 +4,6 @@ #include "esphome/core/helpers.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/uart/uart.h" -#include "esphome/components/aqi/aqi_calculator_factory.h" namespace esphome { namespace pmsx003 { @@ -74,10 +73,6 @@ class PMSX003Component : public uart::UARTDevice, public Component { void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } - void set_aqi_sensor(sensor::Sensor *aqi_sensor) { aqi_sensor_ = aqi_sensor; } - - void set_aqi_calculation_type(aqi::AQICalculatorType aqi_calc_type) { aqi_calc_type_ = aqi_calc_type; } - protected: optional check_byte_(); void parse_data_(); @@ -121,12 +116,6 @@ class PMSX003Component : public uart::UARTDevice, public Component { // Temperature and Humidity sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; - - // AQI - sensor::Sensor *aqi_sensor_{nullptr}; - - aqi::AQICalculatorType aqi_calc_type_; - aqi::AQICalculatorFactory aqi_calculator_factory_ = aqi::AQICalculatorFactory(); }; } // namespace pmsx003 diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index b2d6744547..bebd3a01ee 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -1,6 +1,5 @@ import esphome.codegen as cg from esphome.components import sensor, uart -from esphome.components.aqi import AQI_CALCULATION_TYPE, CONF_AQI, CONF_CALCULATION_TYPE import esphome.config_validation as cv from esphome.const import ( CONF_FORMALDEHYDE, @@ -21,7 +20,6 @@ from esphome.const import ( CONF_TEMPERATURE, CONF_TYPE, CONF_UPDATE_INTERVAL, - DEVICE_CLASS_AQI, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PM1, DEVICE_CLASS_PM10, @@ -37,13 +35,11 @@ from esphome.const import ( CODEOWNERS = ["@ximex"] DEPENDENCIES = ["uart"] -AUTO_LOAD = ["aqi"] pmsx003_ns = cg.esphome_ns.namespace("pmsx003") PMSX003Component = pmsx003_ns.class_("PMSX003Component", uart.UARTDevice, cg.Component) PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor) -UNIT_INDEX = "index" TYPE_PMSX003 = "PMSX003" TYPE_PMS5003T = "PMS5003T" TYPE_PMS5003ST = "PMS5003ST" @@ -81,10 +77,6 @@ def validate_pmsx003_sensors(value): for key, types in SENSORS_TO_TYPE.items(): if key in value and value[CONF_TYPE] not in types: raise cv.Invalid(f"{value[CONF_TYPE]} does not have {key} sensor!") - if CONF_AQI in value and CONF_PM_2_5 not in value: - raise cv.Invalid("AQI computation requires PM 2.5 sensor") - if CONF_AQI in value and CONF_PM_10_0 not in value: - raise cv.Invalid("AQI computation requires PM 10 sensor") return value @@ -200,19 +192,6 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, ), - cv.Optional(CONF_AQI): sensor.sensor_schema( - unit_of_measurement=UNIT_INDEX, - icon=ICON_CHEMICAL_WEAPON, - accuracy_decimals=0, - device_class=DEVICE_CLASS_AQI, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Required(CONF_CALCULATION_TYPE): cv.enum( - AQI_CALCULATION_TYPE, upper=True - ), - } - ), cv.Optional(CONF_UPDATE_INTERVAL, default="0s"): validate_update_interval, } ) @@ -299,9 +278,4 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_HUMIDITY]) cg.add(var.set_humidity_sensor(sens)) - if CONF_AQI in config: - sens = await sensor.new_sensor(config[CONF_AQI]) - cg.add(var.set_aqi_sensor(sens)) - cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) - cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL])) diff --git a/tests/components/aqi/common.yaml b/tests/components/aqi/common.yaml new file mode 100644 index 0000000000..4c8cbbfa3f --- /dev/null +++ b/tests/components/aqi/common.yaml @@ -0,0 +1,22 @@ +sensor: + - platform: template + id: pm25_sensor + name: "PM2.5" + lambda: "return 25.0;" + + - platform: template + id: pm10_sensor + name: "PM10" + lambda: "return 50.0;" + + - platform: aqi + name: "Air Quality Index (AQI)" + pm_2_5: pm25_sensor + pm_10_0: pm10_sensor + calculation_type: AQI + + - platform: aqi + name: "Air Quality Index (CAQI)" + pm_2_5: pm25_sensor + pm_10_0: pm10_sensor + calculation_type: CAQI diff --git a/tests/components/aqi/test.esp32-idf.yaml b/tests/components/aqi/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/aqi/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/aqi/test.esp8266-ard.yaml b/tests/components/aqi/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/aqi/test.esp8266-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/aqi/test.rp2040-ard.yaml b/tests/components/aqi/test.rp2040-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/aqi/test.rp2040-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pmsx003/common.yaml b/tests/components/pmsx003/common.yaml index 9dd79723d1..3c60995804 100644 --- a/tests/components/pmsx003/common.yaml +++ b/tests/components/pmsx003/common.yaml @@ -25,7 +25,4 @@ sensor: name: Particulate Count >5.0um pm_10_0um: name: Particulate Count >10.0um - aqi: - name: AQI - calculation_type: AQI update_interval: 30s From 2147ddf8c7f25fac1a941b1980afdd69ac9349bd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 11:32:23 -1000 Subject: [PATCH 1104/1145] [api] Eliminate std::string from ClientInfo struct (#12566) Co-authored-by: Keith Burzinski --- esphome/components/api/api_connection.cpp | 42 +++++----- esphome/components/api/api_connection.h | 19 ++--- esphome/components/api/api_frame_helper.cpp | 10 ++- esphome/components/api/api_frame_helper.h | 28 ++++--- .../components/api/api_frame_helper_noise.cpp | 7 +- .../components/api/api_frame_helper_noise.h | 4 +- .../api/api_frame_helper_plaintext.cpp | 8 +- .../api/api_frame_helper_plaintext.h | 3 +- esphome/components/api/api_server.cpp | 14 ++-- .../components/esphome/ota/ota_esphome.cpp | 5 +- .../components/socket/bsd_sockets_impl.cpp | 40 +-------- .../components/socket/lwip_raw_tcp_impl.cpp | 57 ++++--------- .../components/socket/lwip_sockets_impl.cpp | 38 +-------- esphome/components/socket/socket.cpp | 81 +++++++++++++++++++ esphome/components/socket/socket.h | 20 ++++- .../voice_assistant/voice_assistant.cpp | 5 +- 16 files changed, 200 insertions(+), 181 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 27344a53ec..30f7b5710c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -101,16 +101,14 @@ APIConnection::APIConnection(std::unique_ptr sock, APIServer *pa #if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE) auto &noise_ctx = parent->get_noise_ctx(); if (noise_ctx.has_psk()) { - this->helper_ = - std::unique_ptr{new APINoiseFrameHelper(std::move(sock), noise_ctx, &this->client_info_)}; + this->helper_ = std::unique_ptr{new APINoiseFrameHelper(std::move(sock), noise_ctx)}; } else { - this->helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)}; + this->helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock))}; } #elif defined(USE_API_PLAINTEXT) - this->helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)}; + this->helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock))}; #elif defined(USE_API_NOISE) - this->helper_ = std::unique_ptr{ - new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx(), &this->client_info_)}; + this->helper_ = std::unique_ptr{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())}; #else #error "No frame helper defined" #endif @@ -131,8 +129,9 @@ void APIConnection::start() { this->fatal_error_with_log_(LOG_STR("Helper init failed"), err); return; } - this->client_info_.peername = helper_->getpeername(); - this->client_info_.name = this->client_info_.peername; + // Initialize client name with peername (IP address) until Hello message provides actual name + const char *peername = this->helper_->get_client_peername(); + this->helper_->set_client_name(peername, strlen(peername)); } APIConnection::~APIConnection() { @@ -252,8 +251,7 @@ void APIConnection::loop() { // Disconnect if not responded within 2.5*keepalive if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) { on_fatal_error(); - ESP_LOGW(TAG, "%s (%s) is unresponsive; disconnecting", this->client_info_.name.c_str(), - this->client_info_.peername.c_str()); + this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("is unresponsive; disconnecting")); } } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) { // Only send ping if we're not disconnecting @@ -287,7 +285,7 @@ bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) { // remote initiated disconnect_client // don't close yet, we still need to send the disconnect response // close will happen on next loop - ESP_LOGD(TAG, "%s (%s) disconnected", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); + this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("disconnected")); this->flags_.next_close = true; DisconnectResponse resp; return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE); @@ -1504,9 +1502,10 @@ void APIConnection::complete_authentication_() { } this->flags_.connection_state = static_cast(ConnectionState::AUTHENTICATED); - ESP_LOGD(TAG, "%s (%s) connected", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); + this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("connected")); #ifdef USE_API_CLIENT_CONNECTED_TRIGGER - this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername); + this->parent_->get_client_connected_trigger()->trigger(std::string(this->helper_->get_client_name()), + std::string(this->helper_->get_client_peername())); #endif #ifdef USE_HOMEASSISTANT_TIME if (homeassistant::global_homeassistant_time != nullptr) { @@ -1521,12 +1520,12 @@ void APIConnection::complete_authentication_() { } bool APIConnection::send_hello_response(const HelloRequest &msg) { - this->client_info_.name.assign(msg.client_info.c_str(), msg.client_info.size()); - this->client_info_.peername = this->helper_->getpeername(); + // Copy client name with truncation if needed (set_client_name handles truncation) + this->helper_->set_client_name(msg.client_info.c_str(), msg.client_info.size()); this->client_api_version_major_ = msg.api_version_major; this->client_api_version_minor_ = msg.api_version_minor; - ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.name.c_str(), - this->client_info_.peername.c_str(), this->client_api_version_major_, this->client_api_version_minor_); + ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->helper_->get_client_name(), + this->helper_->get_client_peername(), this->client_api_version_major_, this->client_api_version_minor_); HelloResponse resp; resp.api_version_major = 1; @@ -1836,7 +1835,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { } void APIConnection::on_no_setup_connection() { this->on_fatal_error(); - ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); + this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("no connection setup")); } void APIConnection::on_fatal_error() { this->helper_->close(); @@ -2084,8 +2083,13 @@ void APIConnection::process_state_subscriptions_() { } #endif // USE_API_HOMEASSISTANT_STATES +void APIConnection::log_client_(int level, const LogString *message) { + esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->helper_->get_client_name(), + this->helper_->get_client_peername(), LOG_STR_ARG(message)); +} + void APIConnection::log_warning_(const LogString *message, APIError err) { - ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), this->client_info_.peername.c_str(), + ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->helper_->get_client_name(), this->helper_->get_client_peername(), LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno); } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index cffd52bfdb..802681f32f 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -9,18 +9,13 @@ #include "esphome/core/application.h" #include "esphome/core/component.h" #include "esphome/core/entity_base.h" +#include "esphome/core/string_ref.h" #include #include namespace esphome::api { -// Client information structure -struct ClientInfo { - std::string name; // Client name from Hello message - std::string peername; // IP:port from socket -}; - // Keepalive timeout in milliseconds static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; // Maximum number of entities to process in a single batch during initial state/info sending @@ -279,8 +274,9 @@ class APIConnection final : public APIServerConnection { bool try_to_clear_buffer(bool log_out_of_space); bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override; - const std::string &get_name() const { return this->client_info_.name; } - const std::string &get_peername() const { return this->client_info_.peername; } + const char *get_name() const { return this->helper_->get_client_name(); } + /// Get peer name (IP address) - cached at connection init time + const char *get_peername() const { return this->helper_->get_client_peername(); } protected: // Helper function to handle authentication completion @@ -526,10 +522,7 @@ class APIConnection final : public APIServerConnection { std::unique_ptr image_reader_; #endif - // Group 3: Client info struct (24 bytes on 32-bit: 2 strings × 12 bytes each) - ClientInfo client_info_; - - // Group 4: 4-byte types + // Group 3: 4-byte types uint32_t last_traffic_; #ifdef USE_API_HOMEASSISTANT_STATES int state_subs_at_ = -1; @@ -756,6 +749,8 @@ class APIConnection final : public APIServerConnection { return this->schedule_batch_(); } + // Helper function to log client messages with name and peername + void log_client_(int level, const LogString *message); // Helper function to log API errors with errno void log_warning_(const LogString *message, APIError err); // Helper to handle fatal errors with logging diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 420f42a90a..dd44fe9e17 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -1,6 +1,5 @@ #include "api_frame_helper.h" #ifdef USE_API -#include "api_connection.h" // For ClientInfo struct #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -16,8 +15,11 @@ static const char *const TAG = "api.frame_helper"; // Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) static constexpr size_t API_MAX_LOG_BYTES = 168; -#define HELPER_LOG(msg, ...) \ - ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE +#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__) +#else +#define HELPER_LOG(msg, ...) ((void) 0) +#endif #ifdef HELPER_LOG_PACKETS #define LOG_PACKET_RECEIVED(buffer) \ @@ -243,6 +245,8 @@ APIError APIFrameHelper::init_common_() { HELPER_LOG("Bad state for init %d", (int) state_); return APIError::BAD_STATE; } + // Cache peername now while socket is valid - needed for error logging after socket failure + this->socket_->getpeername_to(this->client_peername_); int err = this->socket_->setblocking(false); if (err != 0) { state_ = State::FAILED; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 383e763e6d..76a93d094e 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -33,11 +33,11 @@ static constexpr uint16_t MAX_MESSAGE_SIZE = 32768; // 32 KiB for ESP32 and oth // Must be >= MAX_INITIAL_PER_BATCH in api_connection.h (enforced by static_assert there) static constexpr size_t MAX_MESSAGES_PER_BATCH = 34; -// Forward declaration -struct ClientInfo; - class ProtoWriteBuffer; +// Max client name length (e.g., "Home Assistant 2026.1.0.dev0" = 28 chars) +static constexpr size_t CLIENT_INFO_NAME_MAX_LEN = 32; + struct ReadPacketBuffer { const uint8_t *data; // Points directly into frame helper's rx_buf_ (valid until next read_packet call) uint16_t data_len; @@ -86,14 +86,23 @@ const LogString *api_error_to_logstr(APIError err); class APIFrameHelper { public: APIFrameHelper() = default; - explicit APIFrameHelper(std::unique_ptr socket, const ClientInfo *client_info) - : socket_(std::move(socket)), client_info_(client_info) {} + explicit APIFrameHelper(std::unique_ptr socket) : socket_(std::move(socket)) {} + + // Get client name (null-terminated) + const char *get_client_name() const { return this->client_name_; } + // Get client peername/IP (null-terminated, cached at init time for availability after socket failure) + const char *get_client_peername() const { return this->client_peername_; } + // Set client name from buffer with length (truncates if needed) + void set_client_name(const char *name, size_t len) { + size_t copy_len = std::min(len, sizeof(this->client_name_) - 1); + memcpy(this->client_name_, name, copy_len); + this->client_name_[copy_len] = '\0'; + } virtual ~APIFrameHelper() = default; virtual APIError init() = 0; virtual APIError loop(); virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; } - std::string getpeername() { return socket_->getpeername(); } int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); } APIError close() { state_ = State::CLOSED; @@ -186,9 +195,10 @@ class APIFrameHelper { std::array, API_MAX_SEND_QUEUE> tx_buf_; std::vector rx_buf_; - // Pointer to client info (4 bytes on 32-bit) - // Note: The pointed-to ClientInfo object must outlive this APIFrameHelper instance. - const ClientInfo *client_info_{nullptr}; + // Client name buffer - stores name from Hello message or initial peername + char client_name_[CLIENT_INFO_NAME_MAX_LEN]{}; + // Cached peername/IP address - captured at init time for availability after socket failure + char client_peername_[socket::SOCKADDR_STR_LEN]{}; // Group smaller types together uint16_t rx_buf_len_ = 0; diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index be8d93fbf9..21b0463dfe 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -27,8 +27,11 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit") // Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) static constexpr size_t API_MAX_LOG_BYTES = 168; -#define HELPER_LOG(msg, ...) \ - ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE +#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__) +#else +#define HELPER_LOG(msg, ...) ((void) 0) +#endif #ifdef HELPER_LOG_PACKETS #define LOG_PACKET_RECEIVED(buffer) \ diff --git a/esphome/components/api/api_frame_helper_noise.h b/esphome/components/api/api_frame_helper_noise.h index 1268086194..183b8c8a51 100644 --- a/esphome/components/api/api_frame_helper_noise.h +++ b/esphome/components/api/api_frame_helper_noise.h @@ -9,8 +9,8 @@ namespace esphome::api { class APINoiseFrameHelper final : public APIFrameHelper { public: - APINoiseFrameHelper(std::unique_ptr socket, APINoiseContext &ctx, const ClientInfo *client_info) - : APIFrameHelper(std::move(socket), client_info), ctx_(ctx) { + APINoiseFrameHelper(std::unique_ptr socket, APINoiseContext &ctx) + : APIFrameHelper(std::move(socket)), ctx_(ctx) { // Noise header structure: // Pos 0: indicator (0x01) // Pos 1-2: encrypted payload size (16-bit big-endian) diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index a974a2458e..3dfd683929 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -1,7 +1,6 @@ #include "api_frame_helper_plaintext.h" #ifdef USE_API #ifdef USE_API_PLAINTEXT -#include "api_connection.h" // For ClientInfo struct #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -21,8 +20,11 @@ static const char *const TAG = "api.plaintext"; // Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) static constexpr size_t API_MAX_LOG_BYTES = 168; -#define HELPER_LOG(msg, ...) \ - ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE +#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__) +#else +#define HELPER_LOG(msg, ...) ((void) 0) +#endif #ifdef HELPER_LOG_PACKETS #define LOG_PACKET_RECEIVED(buffer) \ diff --git a/esphome/components/api/api_frame_helper_plaintext.h b/esphome/components/api/api_frame_helper_plaintext.h index 7af9fc64b9..96d47e9c7b 100644 --- a/esphome/components/api/api_frame_helper_plaintext.h +++ b/esphome/components/api/api_frame_helper_plaintext.h @@ -7,8 +7,7 @@ namespace esphome::api { class APIPlaintextFrameHelper final : public APIFrameHelper { public: - APIPlaintextFrameHelper(std::unique_ptr socket, const ClientInfo *client_info) - : APIFrameHelper(std::move(socket), client_info) { + explicit APIPlaintextFrameHelper(std::unique_ptr socket) : APIFrameHelper(std::move(socket)) { // Plaintext header structure (worst case): // Pos 0: indicator (0x00) // Pos 1-3: payload size varint (up to 3 bytes) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index a7b046447d..4ececfec94 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -125,15 +125,18 @@ void APIServer::loop() { if (!sock) break; + char peername[socket::SOCKADDR_STR_LEN]; + sock->getpeername_to(peername); + // Check if we're at the connection limit if (this->clients_.size() >= this->max_connections_) { - ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, sock->getpeername().c_str()); + ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, peername); // Immediately close - socket destructor will handle cleanup sock.reset(); continue; } - ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str()); + ESP_LOGD(TAG, "Accept %s", peername); auto *conn = new APIConnection(std::move(sock), this); this->clients_.emplace_back(conn); @@ -166,8 +169,7 @@ void APIServer::loop() { // Network is down - disconnect all clients for (auto &client : this->clients_) { client->on_fatal_error(); - ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(), - client->client_info_.peername.c_str()); + client->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("Network down; disconnect")); } // Continue to process and clean up the clients below } @@ -185,12 +187,12 @@ void APIServer::loop() { // Rare case: handle disconnection #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER - this->client_disconnected_trigger_->trigger(client->client_info_.name, client->client_info_.peername); + this->client_disconnected_trigger_->trigger(std::string(client->get_name()), std::string(client->get_peername())); #endif #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES this->unregister_active_action_calls_for_connection(client.get()); #endif - ESP_LOGV(TAG, "Remove connection %s", client->client_info_.name.c_str()); + ESP_LOGV(TAG, "Remove connection %s", client->get_name()); // Swap with the last element and pop (avoids expensive vector shifts) if (client_index < this->clients_.size() - 1) { diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index f71163f79e..dfa637f701 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -4,6 +4,7 @@ #include "esphome/components/sha256/sha256.h" #endif #include "esphome/components/network/util.h" +#include "esphome/components/socket/socket.h" #include "esphome/components/ota/ota_backend.h" #include "esphome/components/ota/ota_backend_esp8266.h" #include "esphome/components/ota/ota_backend_arduino_libretiny.h" @@ -443,7 +444,9 @@ void ESPHomeOTAComponent::log_socket_error_(const LogString *msg) { void ESPHomeOTAComponent::log_read_error_(const LogString *what) { ESP_LOGW(TAG, "Read %s failed", LOG_STR_ARG(what)); } void ESPHomeOTAComponent::log_start_(const LogString *phase) { - ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), this->client_->getpeername().c_str()); + char peername[socket::SOCKADDR_STR_LEN]; + this->client_->getpeername_to(peername); + ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), peername); } void ESPHomeOTAComponent::log_remote_closed_(const LogString *during) { diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 09cd81752a..73be025376 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -14,31 +14,7 @@ namespace esphome::socket { -std::string format_sockaddr(const struct sockaddr_storage &storage) { - if (storage.ss_family == AF_INET) { - const struct sockaddr_in *addr = reinterpret_cast(&storage); - char buf[INET_ADDRSTRLEN]; - if (inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)) != nullptr) - return std::string{buf}; - } -#if LWIP_IPV6 - else if (storage.ss_family == AF_INET6) { - const struct sockaddr_in6 *addr = reinterpret_cast(&storage); - char buf[INET6_ADDRSTRLEN]; - // Format IPv4-mapped IPv6 addresses as regular IPv4 addresses - if (addr->sin6_addr.un.u32_addr[0] == 0 && addr->sin6_addr.un.u32_addr[1] == 0 && - addr->sin6_addr.un.u32_addr[2] == htonl(0xFFFF) && - inet_ntop(AF_INET, &addr->sin6_addr.un.u32_addr[3], buf, sizeof(buf)) != nullptr) { - return std::string{buf}; - } - if (inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)) != nullptr) - return std::string{buf}; - } -#endif - return {}; -} - -class BSDSocketImpl : public Socket { +class BSDSocketImpl final : public Socket { public: BSDSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) { #ifdef USE_SOCKET_SELECT_SUPPORT @@ -93,23 +69,9 @@ class BSDSocketImpl : public Socket { int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { return ::getpeername(this->fd_, addr, addrlen); } - std::string getpeername() override { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - if (::getpeername(this->fd_, (struct sockaddr *) &storage, &len) != 0) - return {}; - return format_sockaddr(storage); - } int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { return ::getsockname(this->fd_, addr, addrlen); } - std::string getsockname() override { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - if (::getsockname(this->fd_, (struct sockaddr *) &storage, &len) != 0) - return {}; - return format_sockaddr(storage); - } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { return ::getsockopt(this->fd_, level, optname, optval, optlen); } diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index cb5d17d5af..429f59ceca 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -71,7 +71,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return nullptr; } - int bind(const struct sockaddr *name, socklen_t addrlen) override { + int bind(const struct sockaddr *name, socklen_t addrlen) final { if (pcb_ == nullptr) { errno = EBADF; return -1; @@ -135,7 +135,7 @@ class LWIPRawImpl : public Socket { } return 0; } - int close() override { + int close() final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -152,7 +152,7 @@ class LWIPRawImpl : public Socket { pcb_ = nullptr; return 0; } - int shutdown(int how) override { + int shutdown(int how) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -178,7 +178,7 @@ class LWIPRawImpl : public Socket { return 0; } - int getpeername(struct sockaddr *name, socklen_t *addrlen) override { + int getpeername(struct sockaddr *name, socklen_t *addrlen) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -189,14 +189,7 @@ class LWIPRawImpl : public Socket { } return this->ip2sockaddr_(&pcb_->remote_ip, pcb_->remote_port, name, addrlen); } - std::string getpeername() override { - if (pcb_ == nullptr) { - errno = ECONNRESET; - return ""; - } - return this->format_ip_address_(pcb_->remote_ip); - } - int getsockname(struct sockaddr *name, socklen_t *addrlen) override { + int getsockname(struct sockaddr *name, socklen_t *addrlen) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -207,14 +200,7 @@ class LWIPRawImpl : public Socket { } return this->ip2sockaddr_(&pcb_->local_ip, pcb_->local_port, name, addrlen); } - std::string getsockname() override { - if (pcb_ == nullptr) { - errno = ECONNRESET; - return ""; - } - return this->format_ip_address_(pcb_->local_ip); - } - int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { + int getsockopt(int level, int optname, void *optval, socklen_t *optlen) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -248,7 +234,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } - int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { + int setsockopt(int level, int optname, const void *optval, socklen_t optlen) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -282,7 +268,7 @@ class LWIPRawImpl : public Socket { errno = EOPNOTSUPP; return -1; } - ssize_t read(void *buf, size_t len) override { + ssize_t read(void *buf, size_t len) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -340,7 +326,7 @@ class LWIPRawImpl : public Socket { return read; } - ssize_t readv(const struct iovec *iov, int iovcnt) override { + ssize_t readv(const struct iovec *iov, int iovcnt) final { ssize_t ret = 0; for (int i = 0; i < iovcnt; i++) { ssize_t err = read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); @@ -358,7 +344,7 @@ class LWIPRawImpl : public Socket { return ret; } - ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override { + ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) final { errno = ENOTSUP; return -1; } @@ -412,7 +398,7 @@ class LWIPRawImpl : public Socket { } return 0; } - ssize_t write(const void *buf, size_t len) override { + ssize_t write(const void *buf, size_t len) final { ssize_t written = internal_write(buf, len); if (written == -1) return -1; @@ -427,7 +413,7 @@ class LWIPRawImpl : public Socket { } return written; } - ssize_t writev(const struct iovec *iov, int iovcnt) override { + ssize_t writev(const struct iovec *iov, int iovcnt) final { ssize_t written = 0; for (int i = 0; i < iovcnt; i++) { ssize_t err = internal_write(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); @@ -453,12 +439,12 @@ class LWIPRawImpl : public Socket { } return written; } - ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override { + ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) final { // return ::sendto(fd_, buf, len, flags, to, tolen); errno = ENOSYS; return -1; } - int setblocking(bool blocking) override { + int setblocking(bool blocking) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -517,19 +503,6 @@ class LWIPRawImpl : public Socket { } protected: - std::string format_ip_address_(const ip_addr_t &ip) { - char buffer[50] = {}; - if (IP_IS_V4_VAL(ip)) { - inet_ntoa_r(ip, buffer, sizeof(buffer)); - } -#if LWIP_IPV6 - else if (IP_IS_V6_VAL(ip)) { - inet6_ntoa_r(ip, buffer, sizeof(buffer)); - } -#endif - return std::string(buffer); - } - int ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) { if (family_ == AF_INET) { if (*addrlen < sizeof(struct sockaddr_in)) { @@ -584,7 +557,7 @@ class LWIPRawImpl : public Socket { // Listening socket class - only allocates accept queue when needed (for bind+listen sockets) // This saves 16 bytes (12 bytes array + 1 byte count + 3 bytes padding) for regular connected sockets on ESP8266/RP2040 -class LWIPRawListenImpl : public LWIPRawImpl { +class LWIPRawListenImpl final : public LWIPRawImpl { public: LWIPRawListenImpl(sa_family_t family, struct tcp_pcb *pcb) : LWIPRawImpl(family, pcb) {} diff --git a/esphome/components/socket/lwip_sockets_impl.cpp b/esphome/components/socket/lwip_sockets_impl.cpp index 23fb1a7f6f..a885f243f3 100644 --- a/esphome/components/socket/lwip_sockets_impl.cpp +++ b/esphome/components/socket/lwip_sockets_impl.cpp @@ -9,29 +9,7 @@ namespace esphome::socket { -std::string format_sockaddr(const struct sockaddr_storage &storage) { - if (storage.ss_family == AF_INET) { - const struct sockaddr_in *addr = reinterpret_cast(&storage); - char buf[INET_ADDRSTRLEN]; - const char *ret = lwip_inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)); - if (ret == nullptr) - return {}; - return std::string{buf}; - } -#if LWIP_IPV6 - else if (storage.ss_family == AF_INET6) { - const struct sockaddr_in6 *addr = reinterpret_cast(&storage); - char buf[INET6_ADDRSTRLEN]; - const char *ret = lwip_inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)); - if (ret == nullptr) - return {}; - return std::string{buf}; - } -#endif - return {}; -} - -class LwIPSocketImpl : public Socket { +class LwIPSocketImpl final : public Socket { public: LwIPSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) { #ifdef USE_SOCKET_SELECT_SUPPORT @@ -88,23 +66,9 @@ class LwIPSocketImpl : public Socket { int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { return lwip_getpeername(this->fd_, addr, addrlen); } - std::string getpeername() override { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - if (lwip_getpeername(this->fd_, (struct sockaddr *) &storage, &len) != 0) - return {}; - return format_sockaddr(storage); - } int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { return lwip_getsockname(this->fd_, addr, addrlen); } - std::string getsockname() override { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - if (lwip_getsockname(this->fd_, (struct sockaddr *) &storage, &len) != 0) - return {}; - return format_sockaddr(storage); - } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { return lwip_getsockopt(this->fd_, level, optname, optval, optlen); } diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp index ffe0233abc..c92e33393b 100644 --- a/esphome/components/socket/socket.cpp +++ b/esphome/components/socket/socket.cpp @@ -10,6 +10,87 @@ namespace esphome::socket { Socket::~Socket() {} +// Platform-specific inet_ntop wrappers +#if defined(USE_SOCKET_IMPL_LWIP_TCP) +// LWIP raw TCP (ESP8266) uses inet_ntoa_r which takes struct by value +static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) { + inet_ntoa_r(*reinterpret_cast(addr), buf, size); + return buf; +} +#if USE_NETWORK_IPV6 +static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) { + inet6_ntoa_r(*reinterpret_cast(addr), buf, size); + return buf; +} +#endif +#elif defined(USE_SOCKET_IMPL_LWIP_SOCKETS) +// LWIP sockets (LibreTiny, ESP32 Arduino) +static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) { + return lwip_inet_ntop(AF_INET, addr, buf, size); +} +#if USE_NETWORK_IPV6 +static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) { + return lwip_inet_ntop(AF_INET6, addr, buf, size); +} +#endif +#else +// BSD sockets (host, ESP32-IDF) +static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) { + return inet_ntop(AF_INET, addr, buf, size); +} +#if USE_NETWORK_IPV6 +static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) { + return inet_ntop(AF_INET6, addr, buf, size); +} +#endif +#endif + +// Format sockaddr into caller-provided buffer, returns length written (excluding null) +static size_t format_sockaddr_to(const struct sockaddr_storage &storage, std::span buf) { + if (storage.ss_family == AF_INET) { + const auto *addr = reinterpret_cast(&storage); + if (esphome_inet_ntop4(&addr->sin_addr, buf.data(), buf.size()) != nullptr) + return strlen(buf.data()); + } +#if USE_NETWORK_IPV6 + else if (storage.ss_family == AF_INET6) { + const auto *addr = reinterpret_cast(&storage); +#ifndef USE_SOCKET_IMPL_LWIP_TCP + // Format IPv4-mapped IPv6 addresses as regular IPv4 (not supported on ESP8266 raw TCP) + if (addr->sin6_addr.un.u32_addr[0] == 0 && addr->sin6_addr.un.u32_addr[1] == 0 && + addr->sin6_addr.un.u32_addr[2] == htonl(0xFFFF) && + esphome_inet_ntop4(&addr->sin6_addr.un.u32_addr[3], buf.data(), buf.size()) != nullptr) { + return strlen(buf.data()); + } +#endif + if (esphome_inet_ntop6(&addr->sin6_addr, buf.data(), buf.size()) != nullptr) + return strlen(buf.data()); + } +#endif + buf[0] = '\0'; + return 0; +} + +size_t Socket::getpeername_to(std::span buf) { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + if (this->getpeername(reinterpret_cast(&storage), &len) != 0) { + buf[0] = '\0'; + return 0; + } + return format_sockaddr_to(storage, buf); +} + +size_t Socket::getsockname_to(std::span buf) { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + if (this->getsockname(reinterpret_cast(&storage), &len) != 0) { + buf[0] = '\0'; + return 0; + } + return format_sockaddr_to(storage, buf); +} + std::unique_ptr socket_ip(int type, int protocol) { #if USE_NETWORK_IPV6 return socket(AF_INET6, type, protocol); diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 75eb07de4a..9f9f61de85 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include "esphome/core/optional.h" @@ -8,6 +9,15 @@ #if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) namespace esphome::socket { +// Maximum length for formatted socket address string (IP address without port) +// IPv4: "255.255.255.255" = 15 chars + null = 16 +// IPv6: full address = 45 chars + null = 46 +#if USE_NETWORK_IPV6 +static constexpr size_t SOCKADDR_STR_LEN = 46; // INET6_ADDRSTRLEN +#else +static constexpr size_t SOCKADDR_STR_LEN = 16; // INET_ADDRSTRLEN +#endif + class Socket { public: Socket() = default; @@ -31,9 +41,15 @@ class Socket { virtual int shutdown(int how) = 0; virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0; - virtual std::string getpeername() = 0; virtual int getsockname(struct sockaddr *addr, socklen_t *addrlen) = 0; - virtual std::string getsockname() = 0; + + /// Format peer address into a fixed-size buffer (no heap allocation) + /// Non-virtual wrapper around getpeername() - can be optimized away if unused + /// Returns number of characters written (excluding null terminator), or 0 on error + size_t getpeername_to(std::span buf); + /// Format local address into a fixed-size buffer (no heap allocation) + /// Non-virtual wrapper around getsockname() - can be optimized away if unused + size_t getsockname_to(std::span buf); virtual int getsockopt(int level, int optname, void *optval, socklen_t *optlen) = 0; virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0; virtual int listen(int backlog) = 0; diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 05c356ae4c..0e0616c508 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -3,6 +3,7 @@ #ifdef USE_VOICE_ASSISTANT +#include "esphome/components/socket/socket.h" #include "esphome/core/log.h" #include @@ -433,8 +434,8 @@ void VoiceAssistant::client_subscription(api::APIConnection *client, bool subscr "Multiple API Clients attempting to connect to Voice Assistant\n" "Current client: %s (%s)\n" "New client: %s (%s)", - this->api_client_->get_name().c_str(), this->api_client_->get_peername().c_str(), - client->get_name().c_str(), client->get_peername().c_str()); + this->api_client_->get_name(), this->api_client_->get_peername(), client->get_name(), + client->get_peername()); return; } From a19597626b714d2c92a94cbb7437ab7748e42e59 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 16:16:37 -1000 Subject: [PATCH 1105/1145] [text_sensor][text] Add const char* overloads to publish_state to eliminate heap churn (#13030) --- esphome/components/text/text.cpp | 18 +++++--- esphome/components/text/text.h | 2 + .../components/text_sensor/text_sensor.cpp | 46 +++++++++++++------ esphome/components/text_sensor/text_sensor.h | 5 ++ 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index d06c350832..3824c5004d 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -2,22 +2,26 @@ #include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" +#include namespace esphome { namespace text { static const char *const TAG = "text"; -void Text::publish_state(const std::string &state) { - this->set_has_state(true); - this->state = state; - if (this->traits.get_mode() == TEXT_MODE_PASSWORD) { - ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), state.c_str()); +void Text::publish_state(const std::string &state) { this->publish_state(state.data(), state.size()); } +void Text::publish_state(const char *state) { this->publish_state(state, strlen(state)); } + +void Text::publish_state(const char *state, size_t len) { + this->set_has_state(true); + this->state.assign(state, len); + if (this->traits.get_mode() == TEXT_MODE_PASSWORD) { + ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), this->state.c_str()); } else { - ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state.c_str()); + ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), this->state.c_str()); } - this->state_callback_.call(state); + this->state_callback_.call(this->state); #if defined(USE_TEXT) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_text_update(this); #endif diff --git a/esphome/components/text/text.h b/esphome/components/text/text.h index b8881c59e6..e4ad64334b 100644 --- a/esphome/components/text/text.h +++ b/esphome/components/text/text.h @@ -27,6 +27,8 @@ class Text : public EntityBase { TextTraits traits; void publish_state(const std::string &state); + void publish_state(const char *state); + void publish_state(const char *state, size_t len); /// Instantiate a TextCall object to modify this text component's state. TextCall make_call() { return TextCall(this); } diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 8dfb9dad05..174a98054f 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" +#include namespace esphome { namespace text_sensor { @@ -24,20 +25,26 @@ void log_text_sensor(const char *tag, const char *prefix, const char *type, Text } } -void TextSensor::publish_state(const std::string &state) { -// Suppress deprecation warning - we need to populate raw_state for backwards compatibility +void TextSensor::publish_state(const std::string &state) { this->publish_state(state.data(), state.size()); } + +void TextSensor::publish_state(const char *state) { this->publish_state(state, strlen(state)); } + +void TextSensor::publish_state(const char *state, size_t len) { + if (this->filter_list_ == nullptr) { + // No filters: raw_state == state, store once and use for both callbacks + this->state.assign(state, len); + this->raw_callback_.call(this->state); + ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), this->state.c_str()); + this->notify_frontend_(); + } else { + // Has filters: need separate raw storage #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - this->raw_state = state; + this->raw_state.assign(state, len); + this->raw_callback_.call(this->raw_state); + ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), this->raw_state.c_str()); + this->filter_list_->input(this->raw_state); #pragma GCC diagnostic pop - this->raw_callback_.call(state); - - ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str()); - - if (this->filter_list_ == nullptr) { - this->internal_send_state_to_frontend(state); - } else { - this->filter_list_->input(state); } } @@ -80,6 +87,9 @@ void TextSensor::add_on_raw_state_callback(std::functionstate; } const std::string &TextSensor::get_raw_state() const { + if (this->filter_list_ == nullptr) { + return this->state; // No filters, raw == filtered + } // Suppress deprecation warning - get_raw_state() is the replacement API #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" @@ -87,10 +97,18 @@ const std::string &TextSensor::get_raw_state() const { #pragma GCC diagnostic pop } void TextSensor::internal_send_state_to_frontend(const std::string &state) { - this->state = state; + this->internal_send_state_to_frontend(state.data(), state.size()); +} + +void TextSensor::internal_send_state_to_frontend(const char *state, size_t len) { + this->state.assign(state, len); + this->notify_frontend_(); +} + +void TextSensor::notify_frontend_() { this->set_has_state(true); - ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str()); - this->callback_.call(state); + ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), this->state.c_str()); + this->callback_.call(this->state); #if defined(USE_TEXT_SENSOR) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_text_sensor_update(this); #endif diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 2cd8a65e87..1352a8c1e4 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -42,6 +42,8 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { const std::string &get_raw_state() const; void publish_state(const std::string &state); + void publish_state(const char *state); + void publish_state(const char *state, size_t len); /// Add a filter to the filter chain. Will be appended to the back. void add_filter(Filter *filter); @@ -63,8 +65,11 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { // (In most use cases you won't need these) void internal_send_state_to_frontend(const std::string &state); + void internal_send_state_to_frontend(const char *state, size_t len); protected: + /// Notify frontend that state has changed (assumes this->state is already set) + void notify_frontend_(); LazyCallbackManager raw_callback_; ///< Storage for raw state callbacks. LazyCallbackManager callback_; ///< Storage for filtered state callbacks. From b052c9f562cb21fa5996235d814ed6b103b2135e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:22:10 -1000 Subject: [PATCH 1106/1145] [esp32_camera][uart] Add missing wake_loop_threadsafe() preprocessor guards (#13043) --- esphome/components/esp32_camera/esp32_camera.cpp | 2 ++ esphome/components/uart/uart_component_esp_idf.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 06ba7ff16f..5466d2e7ef 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -429,9 +429,11 @@ void ESP32Camera::framebuffer_task(void *pv) { camera_fb_t *framebuffer = esp_camera_fb_get(); xQueueSend(that->framebuffer_get_queue_, &framebuffer, portMAX_DELAY); // Only wake the main loop if there's a pending request to consume the frame +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) if (that->has_requested_image_()) { App.wake_loop_threadsafe(); } +#endif // return is no-op for config with 1 fb xQueueReceive(that->framebuffer_return_queue_, &framebuffer, portMAX_DELAY); esp_camera_fb_return(framebuffer); diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index b4f6eedf91..90997787aa 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -398,14 +398,18 @@ void IDFUARTComponent::rx_event_task_func(void *param) { case UART_DATA: // Data available in UART RX buffer - wake the main loop ESP_LOGVV(TAG, "Data event: %d bytes", event.size); +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) App.wake_loop_threadsafe(); +#endif break; case UART_FIFO_OVF: case UART_BUFFER_FULL: ESP_LOGW(TAG, "FIFO overflow or ring buffer full - clearing"); uart_flush_input(self->uart_num_); +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) App.wake_loop_threadsafe(); +#endif break; default: From 68b4bc9d9ed321449b93a425f4525964fc0650c0 Mon Sep 17 00:00:00 2001 From: Kyrill <1942093+poolski@users.noreply.github.com> Date: Wed, 7 Jan 2026 03:28:41 +0000 Subject: [PATCH 1107/1145] Map `HEAT_COOL` to `MODE_AUTO` in HeatpumpIR component (#12058) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/heatpumpir/heatpumpir.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp index f4d2ca6c1d..67447a3123 100644 --- a/esphome/components/heatpumpir/heatpumpir.cpp +++ b/esphome/components/heatpumpir/heatpumpir.cpp @@ -181,6 +181,11 @@ void HeatpumpIRClimate::transmit_state() { power_mode_cmd = POWER_ON; operating_mode_cmd = MODE_HEAT; break; + // Map HEAT_COOL to hardware AUTO mode (automatic heat/cool changeover based on temperature). + // In hardware AUTO mode, the device automatically switches between heating and cooling + // based on the current temperature versus the target temperature. + // See https://github.com/esphome/esphome/issues/11161 for further discussion. + case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_AUTO: power_mode_cmd = POWER_ON; operating_mode_cmd = MODE_AUTO; From 4391457a9650718cfbfa697ce764bd8132a3f103 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:51:26 -1000 Subject: [PATCH 1108/1145] [sml] Eliminate heap allocations in text sensor (#13039) --- .../sml/text_sensor/sml_text_sensor.cpp | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/esphome/components/sml/text_sensor/sml_text_sensor.cpp b/esphome/components/sml/text_sensor/sml_text_sensor.cpp index 64f10698f0..6ceff26fe5 100644 --- a/esphome/components/sml/text_sensor/sml_text_sensor.cpp +++ b/esphome/components/sml/text_sensor/sml_text_sensor.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "sml_text_sensor.h" #include "../sml_parser.h" +#include namespace esphome { namespace sml { @@ -21,22 +22,33 @@ void SmlTextSensor::publish_val(const ObisInfo &obis_info) { switch (value_type) { case SML_HEX: { - publish_state("0x" + bytes_repr(obis_info.value)); + // Buffer for "0x" + up to 32 bytes as hex + null + char buf[67]; + buf[0] = '0'; + buf[1] = 'x'; + // Max 32 bytes of data fit in remaining buffer ((65-1)/2) + size_t hex_bytes = std::min(obis_info.value.size(), size_t(32)); + format_hex_to(buf + 2, sizeof(buf) - 2, obis_info.value.begin(), hex_bytes); + publish_state(buf, 2 + hex_bytes * 2); break; } case SML_INT: { - publish_state(to_string(bytes_to_int(obis_info.value))); + char buf[21]; // Enough for int64_t (-9223372036854775808) + int len = snprintf(buf, sizeof(buf), "%" PRId64, bytes_to_int(obis_info.value)); + publish_state(buf, static_cast(len)); break; } case SML_BOOL: publish_state(bytes_to_uint(obis_info.value) ? "True" : "False"); break; case SML_UINT: { - publish_state(to_string(bytes_to_uint(obis_info.value))); + char buf[21]; // Enough for uint64_t (18446744073709551615) + int len = snprintf(buf, sizeof(buf), "%" PRIu64, bytes_to_uint(obis_info.value)); + publish_state(buf, static_cast(len)); break; } case SML_OCTET: { - publish_state(std::string(obis_info.value.begin(), obis_info.value.end())); + publish_state(reinterpret_cast(obis_info.value.begin()), obis_info.value.size()); break; } } From 3a84e4a0b4f44ea15b325ff3ca79bcf1e74436be Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:53:00 -1000 Subject: [PATCH 1109/1145] [openthread_info] Eliminate heap allocations in text sensors (#13036) --- .../openthread_info_text_sensor.h | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/esphome/components/openthread_info/openthread_info_text_sensor.h b/esphome/components/openthread_info/openthread_info_text_sensor.h index 35e46212cb..ac5623e0c1 100644 --- a/esphome/components/openthread_info/openthread_info_text_sensor.h +++ b/esphome/components/openthread_info/openthread_info_text_sensor.h @@ -33,13 +33,12 @@ class IPAddressOpenThreadInfo : public PollingComponent, public text_sensor::Tex return; } - char address_as_string[40]; - otIp6AddressToString(&*address, address_as_string, 40); - std::string ip = address_as_string; + char buf[OT_IP6_ADDRESS_STRING_SIZE]; + otIp6AddressToString(&*address, buf, sizeof(buf)); - if (this->last_ip_ != ip) { - this->last_ip_ = ip; - this->publish_state(this->last_ip_); + if (this->last_ip_ != buf) { + this->last_ip_ = buf; + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -89,7 +88,9 @@ class ExtAddrOpenThreadInfo : public OpenThreadInstancePollingComponent, public const auto *extaddr = otLinkGetExtendedAddress(instance); if (!std::equal(this->last_extaddr_.begin(), this->last_extaddr_.end(), extaddr->m8)) { std::copy(extaddr->m8, extaddr->m8 + 8, this->last_extaddr_.begin()); - this->publish_state(format_hex(extaddr->m8, 8)); + char buf[format_hex_size(8)]; + format_hex_to(buf, extaddr->m8, 8); + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -107,7 +108,9 @@ class Eui64OpenThreadInfo : public OpenThreadInstancePollingComponent, public te if (!std::equal(this->last_eui64_.begin(), this->last_eui64_.end(), addr.m8)) { std::copy(addr.m8, addr.m8 + 8, this->last_eui64_.begin()); - this->publish_state(format_hex(this->last_eui64_.begin(), 8)); + char buf[format_hex_size(8)]; + format_hex_to(buf, this->last_eui64_.data(), 8); + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -123,7 +126,9 @@ class ChannelOpenThreadInfo : public OpenThreadInstancePollingComponent, public uint8_t channel = otLinkGetChannel(instance); if (this->last_channel_ != channel) { this->last_channel_ = channel; - this->publish_state(std::to_string(this->last_channel_)); + char buf[4]; // max "255" + null + snprintf(buf, sizeof(buf), "%u", channel); + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -168,7 +173,9 @@ class NetworkKeyOpenThreadInfo : public DatasetOpenThreadInfo, public text_senso void update_dataset(otOperationalDataset *dataset) override { if (!std::equal(this->last_key_.begin(), this->last_key_.end(), dataset->mNetworkKey.m8)) { std::copy(dataset->mNetworkKey.m8, dataset->mNetworkKey.m8 + 16, this->last_key_.begin()); - this->publish_state(format_hex(dataset->mNetworkKey.m8, 16)); + char buf[format_hex_size(16)]; + format_hex_to(buf, dataset->mNetworkKey.m8, 16); + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -201,7 +208,9 @@ class ExtPanIdOpenThreadInfo : public DatasetOpenThreadInfo, public text_sensor: void update_dataset(otOperationalDataset *dataset) override { if (!std::equal(this->last_extpanid_.begin(), this->last_extpanid_.end(), dataset->mExtendedPanId.m8)) { std::copy(dataset->mExtendedPanId.m8, dataset->mExtendedPanId.m8 + 8, this->last_extpanid_.begin()); - this->publish_state(format_hex(this->last_extpanid_.begin(), 8)); + char buf[format_hex_size(8)]; + format_hex_to(buf, this->last_extpanid_.data(), 8); + this->publish_state(buf); } } From 498477c5a2a26cc76d227f66f71bdf7781502286 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:53:14 -1000 Subject: [PATCH 1110/1145] [homeassistant] Eliminate heap allocation in text sensor state updates (#13035) --- .../homeassistant/text_sensor/homeassistant_text_sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp index 6f77349535..109574e0c8 100644 --- a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp +++ b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp @@ -15,7 +15,7 @@ void HomeassistantTextSensor::setup() { } else { ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str()); } - this->publish_state(state.str()); + this->publish_state(state.c_str(), state.size()); }); } void HomeassistantTextSensor::dump_config() { From 35118da606e3b0dce213675ae04e8d3f5c98e0d6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:53:29 -1000 Subject: [PATCH 1111/1145] [ethernet_info] Eliminate heap allocations in text sensors (#13034) --- .../ethernet_info/ethernet_info_text_sensor.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.h b/esphome/components/ethernet_info/ethernet_info_text_sensor.h index b49ddc263d..5b858b772f 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.h +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.h @@ -14,12 +14,15 @@ class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextS auto ips = ethernet::global_eth_component->get_ip_addresses(); if (ips != this->last_ips_) { this->last_ips_ = ips; - this->publish_state(ips[0].str()); + char buf[network::IP_ADDRESS_BUFFER_SIZE]; + ips[0].str_to(buf); + this->publish_state(buf); uint8_t sensor = 0; for (auto &ip : ips) { if (ip.is_set()) { if (this->ip_sensors_[sensor] != nullptr) { - this->ip_sensors_[sensor]->publish_state(ip.str()); + ip.str_to(buf); + this->ip_sensors_[sensor]->publish_state(buf); } sensor++; } @@ -64,7 +67,10 @@ class DNSAddressEthernetInfo : public PollingComponent, public text_sensor::Text class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor { public: - void setup() override { this->publish_state(ethernet::global_eth_component->get_eth_mac_address_pretty()); } + void setup() override { + char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + this->publish_state(ethernet::global_eth_component->get_eth_mac_address_pretty_into_buffer(buf)); + } float get_setup_priority() const override { return setup_priority::ETHERNET; } void dump_config() override; }; From f9ed2aa17f1cf0786b69464fc30625f2dd98bf59 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:53:42 -1000 Subject: [PATCH 1112/1145] [pylontech] Eliminate heap allocations in text sensors (#13033) --- .../pylontech/text_sensor/pylontech_text_sensor.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp b/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp index 55e02f3e33..8175477cb2 100644 --- a/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp +++ b/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp @@ -25,16 +25,16 @@ void PylontechTextSensor::on_line_read(PylontechListener::LineContents *line) { return; } if (this->base_state_text_sensor_ != nullptr) { - this->base_state_text_sensor_->publish_state(std::string(line->base_st)); + this->base_state_text_sensor_->publish_state(line->base_st); } if (this->voltage_state_text_sensor_ != nullptr) { - this->voltage_state_text_sensor_->publish_state(std::string(line->volt_st)); + this->voltage_state_text_sensor_->publish_state(line->volt_st); } if (this->current_state_text_sensor_ != nullptr) { - this->current_state_text_sensor_->publish_state(std::string(line->curr_st)); + this->current_state_text_sensor_->publish_state(line->curr_st); } if (this->temperature_state_text_sensor_ != nullptr) { - this->temperature_state_text_sensor_->publish_state(std::string(line->temp_st)); + this->temperature_state_text_sensor_->publish_state(line->temp_st); } } From 6d1f6a1084be5787b148c908eecf083bcd50b8ce Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:53:54 -1000 Subject: [PATCH 1113/1145] [wifi_info] Eliminate heap churn in text sensors (#13031) --- esphome/components/wifi_info/wifi_info_text_sensor.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index 0cca3e16ef..2c0e66eeaf 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -24,12 +24,15 @@ void IPAddressWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "IP Address", this); void IPAddressWiFiInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, const network::IPAddress &dns2) { - this->publish_state(ips[0].str()); + char buf[network::IP_ADDRESS_BUFFER_SIZE]; + ips[0].str_to(buf); + this->publish_state(buf); uint8_t sensor = 0; for (const auto &ip : ips) { if (ip.is_set()) { if (this->ip_sensors_[sensor] != nullptr) { - this->ip_sensors_[sensor]->publish_state(ip.str()); + ip.str_to(buf); + this->ip_sensors_[sensor]->publish_state(buf); } sensor++; } @@ -104,7 +107,7 @@ void SSIDWiFiInfo::setup() { wifi::global_wifi_component->add_connect_state_list void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); } void SSIDWiFiInfo::on_wifi_connect_state(StringRef ssid, std::span bssid) { - this->publish_state(ssid.str()); + this->publish_state(ssid.c_str(), ssid.size()); } /**************** From 5b9be7c1695018162462e6b953b6b71db5099174 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:54:04 -1000 Subject: [PATCH 1114/1145] [ci] Add lint check to prevent usage of deprecated CORE.using_esp_idf (#13029) --- script/ci-custom.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/script/ci-custom.py b/script/ci-custom.py index cf59c3883b..77d2ab287d 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -619,6 +619,19 @@ def lint_esphome_h(fname, line, col, content): ) +@lint_content_find_check( + "CORE.using_esp_idf", + include=py_include, + exclude=["esphome/core/__init__.py", "script/ci-custom.py"], +) +def lint_using_esp_idf_deprecated(fname, line, col, content): + return ( + f"{highlight('CORE.using_esp_idf')} is deprecated and will change behavior in 2026.6. " + "ESP32 Arduino builds on top of ESP-IDF, so ESP-IDF features are available in both frameworks. " + f"Please use {highlight('CORE.is_esp32')} and/or {highlight('CORE.using_arduino')} instead." + ) + + @lint_content_check(include=["*.h"]) def lint_pragma_once(fname, content): if "#pragma once" not in content: From fb47bfe92acf70e25bceb4b6bdece129f51d75e6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 17:54:20 -1000 Subject: [PATCH 1115/1145] [dsmr] Eliminate heap allocation when publishing telegram (#13032) --- esphome/components/dsmr/dsmr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 41fc2f0d85..5c62aa93ab 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -268,7 +268,7 @@ bool Dsmr::parse_telegram() { // publish the telegram, after publishing the sensors so it can also trigger action based on latest values if (this->s_telegram_ != nullptr) { - this->s_telegram_->publish_state(std::string(this->telegram_, this->bytes_read_)); + this->s_telegram_->publish_state(this->telegram_, this->bytes_read_); } return true; } From c387c03944d0d21af655687345da91337eb57c32 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 19:22:04 -1000 Subject: [PATCH 1116/1145] [text_sensor][text] Avoid heap allocation when state unchanged (#13044) --- esphome/components/text/text.cpp | 5 ++++- esphome/components/text_sensor/text_sensor.cpp | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index 3824c5004d..c2ade56f69 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -15,7 +15,10 @@ void Text::publish_state(const char *state) { this->publish_state(state, strlen( void Text::publish_state(const char *state, size_t len) { this->set_has_state(true); - this->state.assign(state, len); + // Only assign if changed to avoid heap allocation + if (len != this->state.size() || memcmp(state, this->state.data(), len) != 0) { + this->state.assign(state, len); + } if (this->traits.get_mode() == TEXT_MODE_PASSWORD) { ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), this->state.c_str()); } else { diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 174a98054f..66301564a4 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -32,7 +32,10 @@ void TextSensor::publish_state(const char *state) { this->publish_state(state, s void TextSensor::publish_state(const char *state, size_t len) { if (this->filter_list_ == nullptr) { // No filters: raw_state == state, store once and use for both callbacks - this->state.assign(state, len); + // Only assign if changed to avoid heap allocation + if (len != this->state.size() || memcmp(state, this->state.data(), len) != 0) { + this->state.assign(state, len); + } this->raw_callback_.call(this->state); ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), this->state.c_str()); this->notify_frontend_(); @@ -40,7 +43,10 @@ void TextSensor::publish_state(const char *state, size_t len) { // Has filters: need separate raw storage #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - this->raw_state.assign(state, len); + // Only assign if changed to avoid heap allocation + if (len != this->raw_state.size() || memcmp(state, this->raw_state.data(), len) != 0) { + this->raw_state.assign(state, len); + } this->raw_callback_.call(this->raw_state); ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), this->raw_state.c_str()); this->filter_list_->input(this->raw_state); @@ -101,7 +107,10 @@ void TextSensor::internal_send_state_to_frontend(const std::string &state) { } void TextSensor::internal_send_state_to_frontend(const char *state, size_t len) { - this->state.assign(state, len); + // Only assign if changed to avoid heap allocation + if (len != this->state.size() || memcmp(state, this->state.data(), len) != 0) { + this->state.assign(state, len); + } this->notify_frontend_(); } From ac672e4b8f1107664dfe53dabf1d60a4cfd5559b Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 7 Jan 2026 17:19:46 +1000 Subject: [PATCH 1117/1145] [esp32] Don't warn about no ota rollback if no ota at all (#13045) --- esphome/components/esp32/__init__.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index aa7d215c06..45fe8d1c26 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -20,6 +20,7 @@ from esphome.const import ( CONF_IGNORE_EFUSE_MAC_CRC, CONF_LOG_LEVEL, CONF_NAME, + CONF_OTA, CONF_PATH, CONF_PLATFORM_VERSION, CONF_PLATFORMIO_OPTIONS, @@ -620,11 +621,18 @@ def final_validate(config): ) ) if advanced[CONF_ENABLE_OTA_ROLLBACK]: - safe_mode_config = full_config.get(CONF_SAFE_MODE) - if safe_mode_config is None or safe_mode_config.get(CONF_DISABLED, False): - _LOGGER.warning( - "OTA rollback requires safe_mode, disabling rollback support" - ) + # "disabled: false" means safe mode *is* enabled. + safe_mode_config = full_config.get(CONF_SAFE_MODE, {CONF_DISABLED: True}) + safe_mode_enabled = not safe_mode_config[CONF_DISABLED] + ota_enabled = CONF_OTA in full_config + # Both need to be enabled for rollback to work + if not (ota_enabled and safe_mode_enabled): + # But only warn if ota is even possible + if ota_enabled: + _LOGGER.warning( + "OTA rollback requires safe_mode, disabling rollback support" + ) + # disable the rollback feature anyway since it can't be used. advanced[CONF_ENABLE_OTA_ROLLBACK] = False if errs: raise cv.MultipleInvalid(errs) From f8309b007c73cfb22147d7e52ad749078fc33edf Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Wed, 7 Jan 2026 01:41:33 -0600 Subject: [PATCH 1118/1145] [zwave_proxy] Add logging if client sends zero-length message (#13052) --- esphome/components/zwave_proxy/zwave_proxy.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp index c1fde4de6b..f8e0f580e9 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.cpp +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -182,11 +182,15 @@ void ZWaveProxy::send_frame(const uint8_t *data, size_t length) { ESP_LOGV(TAG, "Skipping sending duplicate response: 0x%02X", data[0]); return; } + if (length && data != nullptr) { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE - char hex_buf[format_hex_pretty_size(ZWAVE_MAX_LOG_BYTES)]; + char hex_buf[format_hex_pretty_size(ZWAVE_MAX_LOG_BYTES)]; #endif - ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty_to(hex_buf, data, length)); - this->write_array(data, length); + ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty_to(hex_buf, data, length)); + this->write_array(data, length); + } else { + ESP_LOGE(TAG, "Null pointer or length 0"); + } } void ZWaveProxy::send_homeid_changed_msg_(api::APIConnection *conn) { From b083c3385798125d3a8160b5eb60c598847991d6 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Wed, 7 Jan 2026 00:41:24 -0800 Subject: [PATCH 1119/1145] [espnow] fix channel validation (#13057) --- esphome/components/espnow/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/espnow/__init__.py b/esphome/components/espnow/__init__.py index cc2c02d4c0..1f5ca1104a 100644 --- a/esphome/components/espnow/__init__.py +++ b/esphome/components/espnow/__init__.py @@ -66,11 +66,17 @@ CONF_WAIT_FOR_SENT = "wait_for_sent" MAX_ESPNOW_PACKET_SIZE = 250 # Maximum size of the payload in bytes +def validate_channel(value): + if value is None: + raise cv.Invalid("channel is required if wifi is not configured") + return wifi.validate_channel(value) + + CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(ESPNowComponent), - cv.OnlyWithout(CONF_CHANNEL, CONF_WIFI): wifi.validate_channel, + cv.OnlyWithout(CONF_CHANNEL, CONF_WIFI): validate_channel, cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, cv.Optional(CONF_AUTO_ADD_PEER, default=False): cv.boolean, cv.Optional(CONF_PEERS): cv.ensure_list(cv.mac_address), From d6554702d8233a8e35cce246e272d03219fa2668 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Wed, 7 Jan 2026 02:54:08 -0600 Subject: [PATCH 1120/1145] [zwave_proxy] Make `send_frame` safer, make `set_home_id` protected (#13055) --- .../components/zwave_proxy/zwave_proxy.cpp | 33 ++++++++++++------- esphome/components/zwave_proxy/zwave_proxy.h | 2 +- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp index f8e0f580e9..8506b19e7f 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.cpp +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -105,7 +105,7 @@ void ZWaveProxy::process_uart_() { this->buffer_[1] >= ZWAVE_MIN_GET_NETWORK_IDS_LENGTH && this->buffer_[0] == ZWAVE_FRAME_TYPE_START) { // Store the 4-byte Home ID, which starts at offset 4, and notify connected clients if it changed // The frame parser has already validated the checksum and ensured all bytes are present - if (this->set_home_id(&this->buffer_[4])) { + if (this->set_home_id_(&this->buffer_[4])) { this->send_homeid_changed_msg_(); } } @@ -165,7 +165,7 @@ void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::en } } -bool ZWaveProxy::set_home_id(const uint8_t *new_home_id) { +bool ZWaveProxy::set_home_id_(const uint8_t *new_home_id) { if (std::memcmp(this->home_id_.data(), new_home_id, this->home_id_.size()) == 0) { ESP_LOGV(TAG, "Home ID unchanged"); return false; // No change @@ -178,19 +178,28 @@ bool ZWaveProxy::set_home_id(const uint8_t *new_home_id) { } void ZWaveProxy::send_frame(const uint8_t *data, size_t length) { - if (length == 1 && data[0] == this->last_response_) { - ESP_LOGV(TAG, "Skipping sending duplicate response: 0x%02X", data[0]); + // Safety: validate pointer before any access + if (data == nullptr) { + ESP_LOGE(TAG, "Null data pointer"); return; } - if (length && data != nullptr) { -#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE - char hex_buf[format_hex_pretty_size(ZWAVE_MAX_LOG_BYTES)]; -#endif - ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty_to(hex_buf, data, length)); - this->write_array(data, length); - } else { - ESP_LOGE(TAG, "Null pointer or length 0"); + if (length == 0) { + ESP_LOGE(TAG, "Length 0"); + return; } + + // Skip duplicate single-byte responses (ACK/NAK/CAN) + if (length == 1 && data[0] == this->last_response_) { + ESP_LOGV(TAG, "Response already sent: 0x%02X", data[0]); + return; + } + +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(ZWAVE_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty_to(hex_buf, data, length)); + + this->write_array(data, length); } void ZWaveProxy::send_homeid_changed_msg_(api::APIConnection *conn) { diff --git a/esphome/components/zwave_proxy/zwave_proxy.h b/esphome/components/zwave_proxy/zwave_proxy.h index f36287d32a..eb26316f49 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.h +++ b/esphome/components/zwave_proxy/zwave_proxy.h @@ -60,11 +60,11 @@ class ZWaveProxy : public uart::UARTDevice, public Component { uint32_t get_home_id() { return encode_uint32(this->home_id_[0], this->home_id_[1], this->home_id_[2], this->home_id_[3]); } - bool set_home_id(const uint8_t *new_home_id); // Store a new home ID. Returns true if it changed. void send_frame(const uint8_t *data, size_t length); protected: + bool set_home_id_(const uint8_t *new_home_id); // Store a new home ID. Returns true if it changed. void send_homeid_changed_msg_(api::APIConnection *conn = nullptr); void send_simple_command_(uint8_t command_id); bool parse_byte_(uint8_t byte); // Returns true if frame parsing was completed (a frame is ready in the buffer) From ada4e6d5e946dc91bb3702eb94d2f96c2d2bf729 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Wed, 7 Jan 2026 19:20:01 +0100 Subject: [PATCH 1121/1145] [nrf52, zigbee] Add sensor (#12187) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/sensor/__init__.py | 6 +- esphome/components/zigbee/__init__.py | 32 ++- esphome/components/zigbee/const_zephyr.py | 13 ++ .../zigbee/zigbee_binary_sensor_zephyr.cpp | 8 +- .../zigbee/zigbee_sensor_zephyr.cpp | 76 +++++++ .../components/zigbee/zigbee_sensor_zephyr.h | 86 ++++++++ esphome/components/zigbee/zigbee_zephyr.cpp | 72 ++++++- esphome/components/zigbee/zigbee_zephyr.h | 17 +- esphome/components/zigbee/zigbee_zephyr.py | 202 +++++++++++++++--- esphome/core/defines.h | 1 + tests/components/zigbee/common.yaml | 8 +- .../zigbee/test.nrf52-xiao-ble.yaml | 4 + 12 files changed, 471 insertions(+), 54 deletions(-) create mode 100644 esphome/components/zigbee/zigbee_sensor_zephyr.cpp create mode 100644 esphome/components/zigbee/zigbee_sensor_zephyr.h diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 83b2656661..2ac45a55ac 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -3,7 +3,7 @@ import math from esphome import automation import esphome.codegen as cg -from esphome.components import mqtt, web_server +from esphome.components import mqtt, web_server, zigbee import esphome.config_validation as cv from esphome.const import ( CONF_ABOVE, @@ -295,6 +295,7 @@ validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") _SENSOR_SCHEMA = ( cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) .extend(cv.MQTT_COMPONENT_SCHEMA) + .extend(zigbee.SENSOR_SCHEMA) .extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent), @@ -335,6 +336,7 @@ _SENSOR_SCHEMA = ( ) _SENSOR_SCHEMA.add_extra(entity_duplicate_validator("sensor")) +_SENSOR_SCHEMA.add_extra(zigbee.validate_sensor) def sensor_schema( @@ -918,6 +920,8 @@ async def setup_sensor_core_(var, config): if web_server_config := config.get(CONF_WEB_SERVER): await web_server.add_entity_config(var, web_server_config) + await zigbee.setup_sensor(var, config) + async def register_sensor(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/zigbee/__init__.py b/esphome/components/zigbee/__init__.py index 2009f92d2e..1a017f2ab2 100644 --- a/esphome/components/zigbee/__init__.py +++ b/esphome/components/zigbee/__init__.py @@ -13,14 +13,16 @@ from esphome.types import ConfigType from .const_zephyr import ( CONF_MAX_EP_NUMBER, CONF_ON_JOIN, + CONF_POWER_SOURCE, CONF_WIPE_ON_BOOT, CONF_ZIGBEE_ID, KEY_EP_NUMBER, KEY_ZIGBEE, + POWER_SOURCE, ZigbeeComponent, zigbee_ns, ) -from .zigbee_zephyr import zephyr_binary_sensor +from .zigbee_zephyr import zephyr_binary_sensor, zephyr_sensor CODEOWNERS = ["@tomaszduda23"] @@ -35,6 +37,7 @@ def zigbee_set_core_data(config: ConfigType) -> ConfigType: BINARY_SENSOR_SCHEMA = cv.Schema({}).extend(zephyr_binary_sensor) +SENSOR_SCHEMA = cv.Schema({}).extend(zephyr_sensor) CONFIG_SCHEMA = cv.All( cv.Schema( @@ -42,9 +45,15 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(CONF_ID): cv.declare_id(ZigbeeComponent), cv.Optional(CONF_ON_JOIN): automation.validate_automation(single=True), cv.Optional(CONF_WIPE_ON_BOOT, default=False): cv.All( - cv.boolean, + cv.Any( + cv.boolean, + cv.one_of(*["once"], lower=True), + ), cv.requires_component("nrf52"), ), + cv.Optional(CONF_POWER_SOURCE, default="DC_SOURCE"): cv.enum( + POWER_SOURCE, upper=True + ), } ).extend(cv.COMPONENT_SCHEMA), zigbee_set_core_data, @@ -86,7 +95,16 @@ async def setup_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None: await zephyr_setup_binary_sensor(entity, config) -def validate_binary_sensor(config: ConfigType) -> ConfigType: +async def setup_sensor(entity: cg.MockObj, config: ConfigType) -> None: + if not config.get(CONF_ZIGBEE_ID) or config.get(CONF_INTERNAL): + return + if CORE.using_zephyr: + from .zigbee_zephyr import zephyr_setup_sensor + + await zephyr_setup_sensor(entity, config) + + +def consume_endpoint(config: ConfigType) -> ConfigType: if not config.get(CONF_ZIGBEE_ID) or config.get(CONF_INTERNAL): return config data: dict[str, Any] = CORE.data.setdefault(KEY_ZIGBEE, {}) @@ -95,6 +113,14 @@ def validate_binary_sensor(config: ConfigType) -> ConfigType: return config +def validate_binary_sensor(config: ConfigType) -> ConfigType: + return consume_endpoint(config) + + +def validate_sensor(config: ConfigType) -> ConfigType: + return consume_endpoint(config) + + ZIGBEE_ACTION_SCHEMA = automation.maybe_simple_id( cv.Schema( { diff --git a/esphome/components/zigbee/const_zephyr.py b/esphome/components/zigbee/const_zephyr.py index ecd08f1f0a..8d1f229b6e 100644 --- a/esphome/components/zigbee/const_zephyr.py +++ b/esphome/components/zigbee/const_zephyr.py @@ -3,12 +3,24 @@ import esphome.codegen as cg zigbee_ns = cg.esphome_ns.namespace("zigbee") ZigbeeComponent = zigbee_ns.class_("ZigbeeComponent", cg.Component) BinaryAttrs = zigbee_ns.struct("BinaryAttrs") +AnalogAttrs = zigbee_ns.struct("AnalogAttrs") CONF_MAX_EP_NUMBER = 8 CONF_ZIGBEE_ID = "zigbee_id" CONF_ON_JOIN = "on_join" CONF_WIPE_ON_BOOT = "wipe_on_boot" CONF_ZIGBEE_BINARY_SENSOR = "zigbee_binary_sensor" +CONF_ZIGBEE_SENSOR = "zigbee_sensor" +CONF_POWER_SOURCE = "power_source" +POWER_SOURCE = { + "UNKNOWN": "ZB_ZCL_BASIC_POWER_SOURCE_UNKNOWN", + "MAINS_SINGLE_PHASE": "ZB_ZCL_BASIC_POWER_SOURCE_MAINS_SINGLE_PHASE", + "MAINS_THREE_PHASE": "ZB_ZCL_BASIC_POWER_SOURCE_MAINS_THREE_PHASE", + "BATTERY": "ZB_ZCL_BASIC_POWER_SOURCE_BATTERY", + "DC_SOURCE": "ZB_ZCL_BASIC_POWER_SOURCE_DC_SOURCE", + "EMERGENCY_MAINS_CONST": "ZB_ZCL_BASIC_POWER_SOURCE_EMERGENCY_MAINS_CONST", + "EMERGENCY_MAINS_TRANSF": "ZB_ZCL_BASIC_POWER_SOURCE_EMERGENCY_MAINS_TRANSF", +} # Keys for CORE.data storage KEY_ZIGBEE = "zigbee" @@ -22,3 +34,4 @@ ZB_ZCL_IDENTIFY_ATTRS_T = "zb_zcl_identify_attrs_t" ZB_ZCL_CLUSTER_ID_BASIC = "ZB_ZCL_CLUSTER_ID_BASIC" ZB_ZCL_CLUSTER_ID_IDENTIFY = "ZB_ZCL_CLUSTER_ID_IDENTIFY" ZB_ZCL_CLUSTER_ID_BINARY_INPUT = "ZB_ZCL_CLUSTER_ID_BINARY_INPUT" +ZB_ZCL_CLUSTER_ID_ANALOG_INPUT = "ZB_ZCL_CLUSTER_ID_ANALOG_INPUT" diff --git a/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp b/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp index 744d04adc5..8b7aff70a8 100644 --- a/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp +++ b/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp @@ -17,9 +17,9 @@ ZigbeeBinarySensor::ZigbeeBinarySensor(binary_sensor::BinarySensor *binary_senso void ZigbeeBinarySensor::setup() { this->binary_sensor_->add_on_state_callback([this](bool state) { this->cluster_attributes_->present_value = state ? ZB_TRUE : ZB_FALSE; - ESP_LOGD(TAG, "Set attribute end point: %d, present_value %d", this->end_point_, + ESP_LOGD(TAG, "Set attribute endpoint: %d, present_value %d", this->endpoint_, this->cluster_attributes_->present_value); - ZB_ZCL_SET_ATTRIBUTE(this->end_point_, ZB_ZCL_CLUSTER_ID_BINARY_INPUT, ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_SET_ATTRIBUTE(this->endpoint_, ZB_ZCL_CLUSTER_ID_BINARY_INPUT, ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_ATTR_BINARY_INPUT_PRESENT_VALUE_ID, &this->cluster_attributes_->present_value, ZB_FALSE); this->parent_->flush(); @@ -29,8 +29,8 @@ void ZigbeeBinarySensor::setup() { void ZigbeeBinarySensor::dump_config() { ESP_LOGCONFIG(TAG, "Zigbee Binary Sensor\n" - " End point: %d, present_value %u", - this->end_point_, this->cluster_attributes_->present_value); + " Endpoint: %d, present_value %u", + this->endpoint_, this->cluster_attributes_->present_value); } } // namespace esphome::zigbee diff --git a/esphome/components/zigbee/zigbee_sensor_zephyr.cpp b/esphome/components/zigbee/zigbee_sensor_zephyr.cpp new file mode 100644 index 0000000000..74550d6487 --- /dev/null +++ b/esphome/components/zigbee/zigbee_sensor_zephyr.cpp @@ -0,0 +1,76 @@ +#include "zigbee_sensor_zephyr.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_SENSOR) +#include "esphome/core/log.h" +extern "C" { +#include +#include +#include +#include +#include +} +namespace esphome::zigbee { + +static const char *const TAG = "zigbee.sensor"; + +ZigbeeSensor::ZigbeeSensor(sensor::Sensor *sensor) : sensor_(sensor) {} + +void ZigbeeSensor::setup() { + this->sensor_->add_on_state_callback([this](float state) { + this->cluster_attributes_->present_value = state; + ESP_LOGD(TAG, "Set attribute endpoint: %d, present_value %f", this->endpoint_, state); + ZB_ZCL_SET_ATTRIBUTE(this->endpoint_, ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID, + (zb_uint8_t *) &this->cluster_attributes_->present_value, ZB_FALSE); + this->parent_->flush(); + }); +} + +void ZigbeeSensor::dump_config() { + ESP_LOGCONFIG(TAG, + "Zigbee Sensor\n" + " Endpoint: %d, present_value %f", + this->endpoint_, this->cluster_attributes_->present_value); +} + +const zb_uint8_t ZB_ZCL_ANALOG_INPUT_STATUS_FLAG_MAX_VALUE = 0x0F; + +static zb_ret_t check_value_analog_server(zb_uint16_t attr_id, zb_uint8_t endpoint, + zb_uint8_t *value) { // NOLINT(readability-non-const-parameter) + zb_ret_t ret = RET_OK; + ZVUNUSED(endpoint); + + switch (attr_id) { + case ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID: + ret = ZB_ZCL_CHECK_BOOL_VALUE(*value) ? RET_OK : RET_ERROR; + break; + case ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID: + break; + + case ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID: + if (*value > ZB_ZCL_ANALOG_INPUT_STATUS_FLAG_MAX_VALUE) { + ret = RET_ERROR; + } + break; + + default: + break; + } + + return ret; +} + +} // namespace esphome::zigbee + +void zb_zcl_analog_input_init_server() { + zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, ZB_ZCL_CLUSTER_SERVER_ROLE, + esphome::zigbee::check_value_analog_server, (zb_zcl_cluster_write_attr_hook_t) NULL, + (zb_zcl_cluster_handler_t) NULL); +} + +void zb_zcl_analog_input_init_client() { + zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, ZB_ZCL_CLUSTER_CLIENT_ROLE, + (zb_zcl_cluster_check_value_t) NULL, (zb_zcl_cluster_write_attr_hook_t) NULL, + (zb_zcl_cluster_handler_t) NULL); +} + +#endif diff --git a/esphome/components/zigbee/zigbee_sensor_zephyr.h b/esphome/components/zigbee/zigbee_sensor_zephyr.h new file mode 100644 index 0000000000..37406f21d0 --- /dev/null +++ b/esphome/components/zigbee/zigbee_sensor_zephyr.h @@ -0,0 +1,86 @@ +#pragma once + +#include "esphome/components/zigbee/zigbee_zephyr.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_SENSOR) +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +extern "C" { +#include +#include +} + +enum { + ZB_ZCL_ATTR_ANALOG_INPUT_DESCRIPTION_ID = 0x001C, + ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID = 0x0051, + ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID = 0x0055, + ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID = 0x006F, + ZB_ZCL_ATTR_ANALOG_INPUT_ENGINEERING_UNITS_ID = 0x0075, +}; + +#define ZB_ZCL_ANALOG_INPUT_CLUSTER_REVISION_DEFAULT ((zb_uint16_t) 0x0001u) + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_DESCRIPTION_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_ANALOG_INPUT_DESCRIPTION_ID, ZB_ZCL_ATTR_TYPE_CHAR_STRING, ZB_ZCL_ATTR_ACCESS_READ_ONLY, \ + (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \ + } + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID, ZB_ZCL_ATTR_TYPE_BOOL, \ + ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_WRITE_OPTIONAL, (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), \ + (void *) (data_ptr) \ + } + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID, ZB_ZCL_ATTR_TYPE_SINGLE, \ + ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_WRITE_OPTIONAL | ZB_ZCL_ATTR_ACCESS_REPORTING, \ + (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \ + } + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID, ZB_ZCL_ATTR_TYPE_8BITMAP, \ + ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_REPORTING, (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), \ + (void *) (data_ptr) \ + } + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_ENGINEERING_UNITS_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_ANALOG_INPUT_ENGINEERING_UNITS_ID, ZB_ZCL_ATTR_TYPE_16BIT_ENUM, ZB_ZCL_ATTR_ACCESS_READ_ONLY, \ + (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \ + } + +#define ESPHOME_ZB_ZCL_DECLARE_ANALOG_INPUT_ATTRIB_LIST(attr_list, out_of_service, present_value, status_flag, \ + engineering_units, description) \ + ZB_ZCL_START_DECLARE_ATTRIB_LIST_CLUSTER_REVISION(attr_list, ZB_ZCL_ANALOG_INPUT) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID, (out_of_service)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID, (present_value)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID, (status_flag)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_ENGINEERING_UNITS_ID, (engineering_units)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_DESCRIPTION_ID, (description)) \ + ZB_ZCL_FINISH_DECLARE_ATTRIB_LIST + +void zb_zcl_analog_input_init_server(); +void zb_zcl_analog_input_init_client(); +#define ZB_ZCL_CLUSTER_ID_ANALOG_INPUT_SERVER_ROLE_INIT zb_zcl_analog_input_init_server +#define ZB_ZCL_CLUSTER_ID_ANALOG_INPUT_CLIENT_ROLE_INIT zb_zcl_analog_input_init_client + +namespace esphome::zigbee { + +class ZigbeeSensor : public ZigbeeEntity, public Component { + public: + explicit ZigbeeSensor(sensor::Sensor *sensor); + void set_cluster_attributes(AnalogAttrs &cluster_attributes) { this->cluster_attributes_ = &cluster_attributes; } + + void setup() override; + void dump_config() override; + + protected: + AnalogAttrs *cluster_attributes_{nullptr}; + sensor::Sensor *sensor_{nullptr}; +}; + +} // namespace esphome::zigbee +#endif diff --git a/esphome/components/zigbee/zigbee_zephyr.cpp b/esphome/components/zigbee/zigbee_zephyr.cpp index c9027d0a74..9a421aaec1 100644 --- a/esphome/components/zigbee/zigbee_zephyr.cpp +++ b/esphome/components/zigbee/zigbee_zephyr.cpp @@ -138,9 +138,26 @@ void ZigbeeComponent::setup() { } #ifdef USE_ZIGBEE_WIPE_ON_BOOT - erase_flash_(FIXED_PARTITION_ID(ZBOSS_NVRAM)); - erase_flash_(FIXED_PARTITION_ID(ZBOSS_PRODUCT_CONFIG)); - erase_flash_(FIXED_PARTITION_ID(SETTINGS_STORAGE)); + bool wipe = true; +#ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC + // unique hash to store preferences for this component + uint32_t hash = 88498616UL; + uint32_t wipe_value = 0; + auto wipe_pref = global_preferences->make_preference(hash, true); + if (wipe_pref.load(&wipe_value)) { + wipe = wipe_value != USE_ZIGBEE_WIPE_ON_BOOT_MAGIC; + ESP_LOGD(TAG, "Wipe value in preferences %u, in firmware %u", wipe_value, USE_ZIGBEE_WIPE_ON_BOOT_MAGIC); + } +#endif + if (wipe) { + erase_flash_(FIXED_PARTITION_ID(ZBOSS_NVRAM)); + erase_flash_(FIXED_PARTITION_ID(ZBOSS_PRODUCT_CONFIG)); + erase_flash_(FIXED_PARTITION_ID(SETTINGS_STORAGE)); +#ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC + wipe_value = USE_ZIGBEE_WIPE_ON_BOOT_MAGIC; + wipe_pref.save(&wipe_value); +#endif + } #endif ZB_ZCL_REGISTER_DEVICE_CB(zcl_device_cb); @@ -152,15 +169,54 @@ void ZigbeeComponent::setup() { zigbee_enable(); } -void ZigbeeComponent::dump_config() { - bool wipe = false; +static const char *role() { + switch (zb_get_network_role()) { + case ZB_NWK_DEVICE_TYPE_COORDINATOR: + return "coordinator"; + case ZB_NWK_DEVICE_TYPE_ROUTER: + return "router"; + case ZB_NWK_DEVICE_TYPE_ED: + return "end device"; + } + return "unknown"; +} + +static const char *get_wipe_on_boot() { #ifdef USE_ZIGBEE_WIPE_ON_BOOT - wipe = true; +#ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC + return "ONCE"; +#else + return "YES"; #endif +#else + return "NO"; +#endif +} + +void ZigbeeComponent::dump_config() { + char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = {0}; + zb_ieee_addr_t addr; + zb_get_long_address(addr); + ieee_addr_to_str(ieee_addr_buf, sizeof(ieee_addr_buf), addr); + zb_ext_pan_id_t extended_pan_id; + char extended_pan_id_buf[IEEE_ADDR_BUF_SIZE] = {0}; + zb_get_extended_pan_id(extended_pan_id); + ieee_addr_to_str(extended_pan_id_buf, sizeof(extended_pan_id_buf), extended_pan_id); ESP_LOGCONFIG(TAG, "Zigbee\n" - " Wipe on boot: %s", - YESNO(wipe)); + " Wipe on boot: %s\n" + " Device is joined to the network: %s\n" + " Current channel: %d\n" + " Current page: %d\n" + " Sleep threshold: %ums\n" + " Role: %s\n" + " Long addr: 0x%s\n" + " Short addr: 0x%04X\n" + " Long pan id: 0x%s\n" + " Short pan id: 0x%04X", + get_wipe_on_boot(), YESNO(zb_zdo_joined()), zb_get_current_channel(), zb_get_current_page(), + zb_get_sleep_threshold(), role(), ieee_addr_buf, zb_get_short_address(), extended_pan_id_buf, + zb_get_pan_id()); } static void send_attribute_report(zb_bufid_t bufid, zb_uint16_t cmd_id) { diff --git a/esphome/components/zigbee/zigbee_zephyr.h b/esphome/components/zigbee/zigbee_zephyr.h index 853c6deb4d..fa23907bf4 100644 --- a/esphome/components/zigbee/zigbee_zephyr.h +++ b/esphome/components/zigbee/zigbee_zephyr.h @@ -28,16 +28,15 @@ extern "C" { ESPHOME_CAT7(zb_af_simple_desc_, ep_name, _, in_num, _, out_num, _t) // needed to use ESPHOME_ZB_DECLARE_SIMPLE_DESC -#define ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_clust_num, out_clust_num, ...) \ +#define ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_clust_num, out_clust_num, app_device_id, ...) \ ESPHOME_ZB_DECLARE_SIMPLE_DESC(ep_name, in_clust_num, out_clust_num); \ ESPHOME_ZB_AF_SIMPLE_DESC_TYPE(ep_name, in_clust_num, out_clust_num) \ - simple_desc_##ep_name = {ep_id, ZB_AF_HA_PROFILE_ID, ZB_HA_SIMPLE_SENSOR_DEVICE_ID, 0, 0, in_clust_num, \ - out_clust_num, {__VA_ARGS__}} + simple_desc_##ep_name = {ep_id, ZB_AF_HA_PROFILE_ID, app_device_id, 0, 0, in_clust_num, out_clust_num, {__VA_ARGS__}} // needed to use ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC #define ESPHOME_ZB_HA_DECLARE_EP(ep_name, ep_id, cluster_list, in_cluster_num, out_cluster_num, report_attr_count, \ - ...) \ - ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_cluster_num, out_cluster_num, __VA_ARGS__); \ + app_device_id, ...) \ + ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_cluster_num, out_cluster_num, app_device_id, __VA_ARGS__); \ ZBOSS_DEVICE_DECLARE_REPORTING_CTX(reporting_info##ep_name, report_attr_count); \ ZB_AF_DECLARE_ENDPOINT_DESC(ep_name, ep_id, ZB_AF_HA_PROFILE_ID, 0, NULL, \ ZB_ZCL_ARRAY_SIZE(cluster_list, zb_zcl_cluster_desc_t), cluster_list, \ @@ -57,10 +56,8 @@ struct AnalogAttrs { zb_bool_t out_of_service; float present_value; zb_uint8_t status_flags; + zb_uint16_t engineering_units; zb_uchar_t description[ZB_ZCL_MAX_STRING_SIZE]; - float max_present_value; - float min_present_value; - float resolution; }; class ZigbeeComponent : public Component { @@ -93,10 +90,10 @@ class ZigbeeComponent : public Component { class ZigbeeEntity { public: void set_parent(ZigbeeComponent *parent) { this->parent_ = parent; } - void set_end_point(zb_uint8_t end_point) { this->end_point_ = end_point; } + void set_endpoint(zb_uint8_t endpoint) { this->endpoint_ = endpoint; } protected: - zb_uint8_t end_point_{0}; + zb_uint8_t endpoint_{0}; ZigbeeComponent *parent_{nullptr}; }; diff --git a/esphome/components/zigbee/zigbee_zephyr.py b/esphome/components/zigbee/zigbee_zephyr.py index ce55675c41..d8a2716603 100644 --- a/esphome/components/zigbee/zigbee_zephyr.py +++ b/esphome/components/zigbee/zigbee_zephyr.py @@ -1,10 +1,45 @@ from datetime import datetime +import random from esphome import automation import esphome.codegen as cg from esphome.components.zephyr import zephyr_add_prj_conf import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_NAME, __version__ +from esphome.const import ( + CONF_ID, + CONF_NAME, + CONF_UNIT_OF_MEASUREMENT, + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_CENTIMETER, + UNIT_DECIBEL, + UNIT_HECTOPASCAL, + UNIT_HERTZ, + UNIT_HOUR, + UNIT_KELVIN, + UNIT_KILOMETER, + UNIT_KILOWATT, + UNIT_KILOWATT_HOURS, + UNIT_LUX, + UNIT_METER, + UNIT_MICROGRAMS_PER_CUBIC_METER, + UNIT_MILLIAMP, + UNIT_MILLIGRAMS_PER_CUBIC_METER, + UNIT_MILLIMETER, + UNIT_MILLISECOND, + UNIT_MILLIVOLT, + UNIT_MINUTE, + UNIT_OHM, + UNIT_PARTS_PER_BILLION, + UNIT_PARTS_PER_MILLION, + UNIT_PASCAL, + UNIT_PERCENT, + UNIT_SECOND, + UNIT_VOLT, + UNIT_WATT, + UNIT_WATT_HOURS, + __version__, +) from esphome.core import CORE, CoroPriority, coroutine_with_priority from esphome.cpp_generator import ( AssignmentExpression, @@ -15,22 +50,63 @@ from esphome.types import ConfigType from .const_zephyr import ( CONF_ON_JOIN, + CONF_POWER_SOURCE, CONF_WIPE_ON_BOOT, CONF_ZIGBEE_BINARY_SENSOR, CONF_ZIGBEE_ID, + CONF_ZIGBEE_SENSOR, KEY_EP_NUMBER, KEY_ZIGBEE, + POWER_SOURCE, ZB_ZCL_BASIC_ATTRS_EXT_T, + ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, ZB_ZCL_CLUSTER_ID_BASIC, ZB_ZCL_CLUSTER_ID_BINARY_INPUT, ZB_ZCL_CLUSTER_ID_IDENTIFY, ZB_ZCL_IDENTIFY_ATTRS_T, + AnalogAttrs, BinaryAttrs, ZigbeeComponent, zigbee_ns, ) ZigbeeBinarySensor = zigbee_ns.class_("ZigbeeBinarySensor", cg.Component) +ZigbeeSensor = zigbee_ns.class_("ZigbeeSensor", cg.Component) + +# BACnet engineering units mapping (ZCL uses BACnet unit codes) +# See: https://github.com/zigpy/zha/blob/dev/zha/application/platforms/number/bacnet.py +BACNET_UNITS = { + UNIT_CELSIUS: 62, + UNIT_KELVIN: 63, + UNIT_VOLT: 5, + UNIT_MILLIVOLT: 124, + UNIT_AMPERE: 3, + UNIT_MILLIAMP: 2, + UNIT_OHM: 4, + UNIT_WATT: 47, + UNIT_KILOWATT: 48, + UNIT_WATT_HOURS: 18, + UNIT_KILOWATT_HOURS: 19, + UNIT_PASCAL: 53, + UNIT_HECTOPASCAL: 133, + UNIT_HERTZ: 27, + UNIT_MILLIMETER: 30, + UNIT_CENTIMETER: 118, + UNIT_METER: 31, + UNIT_KILOMETER: 193, + UNIT_MILLISECOND: 159, + UNIT_SECOND: 73, + UNIT_MINUTE: 72, + UNIT_HOUR: 71, + UNIT_PARTS_PER_MILLION: 96, + UNIT_PARTS_PER_BILLION: 97, + UNIT_MICROGRAMS_PER_CUBIC_METER: 219, + UNIT_MILLIGRAMS_PER_CUBIC_METER: 218, + UNIT_LUX: 37, + UNIT_DECIBEL: 199, + UNIT_PERCENT: 98, +} +BACNET_UNIT_NO_UNITS = 95 zephyr_binary_sensor = cv.Schema( { @@ -41,6 +117,15 @@ zephyr_binary_sensor = cv.Schema( } ) +zephyr_sensor = cv.Schema( + { + cv.OnlyWith(CONF_ZIGBEE_ID, ["nrf52", "zigbee"]): cv.use_id(ZigbeeComponent), + cv.OnlyWith(CONF_ZIGBEE_SENSOR, ["nrf52", "zigbee"]): cv.declare_id( + ZigbeeSensor + ), + } +) + async def zephyr_to_code(config: ConfigType) -> None: zephyr_add_prj_conf("ZIGBEE", True) @@ -56,6 +141,10 @@ async def zephyr_to_code(config: ConfigType) -> None: zephyr_add_prj_conf("NET_UDP", False) if config[CONF_WIPE_ON_BOOT]: + if config[CONF_WIPE_ON_BOOT] == "once": + cg.add_define( + "USE_ZIGBEE_WIPE_ON_BOOT_MAGIC", random.randint(0x000001, 0xFFFFFF) + ) cg.add_define("USE_ZIGBEE_WIPE_ON_BOOT") var = cg.new_Pvariable(config[CONF_ID]) @@ -85,7 +174,7 @@ async def _attr_to_code(config: ConfigType) -> None: ), zigbee_assign( basic_attrs.power_source, - cg.RawExpression("ZB_ZCL_BASIC_POWER_SOURCE_DC_SOURCE"), + cg.RawExpression(POWER_SOURCE[config[CONF_POWER_SOURCE]]), ), zigbee_set_string(basic_attrs.location_id, ""), zigbee_assign( @@ -191,6 +280,7 @@ def zigbee_register_ep( report_attr_count: int, clusters: list[ZigbeeClusterDesc], slot_index: int, + app_device_id: str, ) -> None: """Register a Zigbee endpoint.""" in_cluster_num = sum(1 for c in clusters if c.has_attrs) @@ -204,7 +294,7 @@ def zigbee_register_ep( ep_id = slot_index + 1 # Endpoints are 1-indexed obj = cg.RawExpression( f"ESPHOME_ZB_HA_DECLARE_EP({ep_name}, {ep_id}, {cluster_list_name}, " - f"{in_cluster_num}, {out_cluster_num}, {report_attr_count}, {', '.join(cluster_ids)})" + f"{in_cluster_num}, {out_cluster_num}, {report_attr_count}, {app_device_id}, {', '.join(cluster_ids)})" ) CORE.add_global(obj) @@ -224,42 +314,102 @@ async def zephyr_setup_binary_sensor(entity: cg.MockObj, config: ConfigType) -> CORE.add_job(_add_binary_sensor, entity, config) -async def _add_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None: - # Find the next available endpoint slot - slot_index = next( +async def zephyr_setup_sensor(entity: cg.MockObj, config: ConfigType) -> None: + CORE.add_job(_add_sensor, entity, config) + + +def _slot_index() -> int: + """Find the next available endpoint slot""" + slot = next( (i for i, v in enumerate(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER]) if v == ""), None ) + if slot is None: + raise cv.Invalid( + f"Not found empty slot, size ({len(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER])})" + ) + return slot + + +async def _add_zigbee_input( + entity: cg.MockObj, + config: ConfigType, + component_key, + attrs_type, + zcl_macro: str, + cluster_id: str, + app_device_id: str, + extra_field_values: dict[str, int] | None = None, +) -> None: + slot_index = _slot_index() - # Create unique names for this sensor's variables based on slot index prefix = f"zigbee_ep{slot_index + 1}" - attrs_name = f"{prefix}_binary_attrs" - attr_list_name = f"{prefix}_binary_input_attrib_list" + attrs_name = f"{prefix}_attrs" + attr_list_name = f"{prefix}_attrib_list" cluster_list_name = f"{prefix}_cluster_list" ep_name = f"{prefix}_ep" - # Create the binary attributes structure - binary_attrs = zigbee_new_variable(attrs_name, BinaryAttrs) - attr_list = zigbee_new_attr_list( - attr_list_name, - "ESPHOME_ZB_ZCL_DECLARE_BINARY_INPUT_ATTRIB_LIST", - zigbee_assign(binary_attrs.out_of_service, 0), - zigbee_assign(binary_attrs.present_value, 0), - zigbee_assign(binary_attrs.status_flags, 0), - zigbee_set_string(binary_attrs.description, config[CONF_NAME]), - ) + # Create attribute struct + attrs = zigbee_new_variable(attrs_name, attrs_type) + + # Build attribute list args + attr_args = [ + zigbee_assign(attrs.out_of_service, 0), + zigbee_assign(attrs.present_value, 0), + zigbee_assign(attrs.status_flags, 0), + ] + # Add extra field assignments (e.g., engineering_units for sensors) + if extra_field_values: + for field_name, value in extra_field_values.items(): + attr_args.append(zigbee_assign(getattr(attrs, field_name), value)) + attr_args.append(zigbee_set_string(attrs.description, config[CONF_NAME])) + + # Create attribute list + attr_list = zigbee_new_attr_list(attr_list_name, zcl_macro, *attr_args) # Create cluster list and register endpoint cluster_list_name, clusters = zigbee_new_cluster_list( cluster_list_name, - [ZigbeeClusterDesc(ZB_ZCL_CLUSTER_ID_BINARY_INPUT, attr_list)], + [ZigbeeClusterDesc(cluster_id, attr_list)], + ) + zigbee_register_ep( + ep_name, cluster_list_name, 2, clusters, slot_index, app_device_id ) - zigbee_register_ep(ep_name, cluster_list_name, 2, clusters, slot_index) - # Create the ZigbeeBinarySensor component - var = cg.new_Pvariable(config[CONF_ZIGBEE_BINARY_SENSOR], entity) - await cg.register_component(var, config) + # Create ESPHome component + var = cg.new_Pvariable(config[component_key], entity) + await cg.register_component(var, {}) + + cg.add(var.set_endpoint(slot_index + 1)) + cg.add(var.set_cluster_attributes(attrs)) - cg.add(var.set_end_point(slot_index + 1)) - cg.add(var.set_cluster_attributes(binary_attrs)) hub = await cg.get_variable(config[CONF_ZIGBEE_ID]) cg.add(var.set_parent(hub)) + + +async def _add_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None: + await _add_zigbee_input( + entity, + config, + CONF_ZIGBEE_BINARY_SENSOR, + BinaryAttrs, + "ESPHOME_ZB_ZCL_DECLARE_BINARY_INPUT_ATTRIB_LIST", + ZB_ZCL_CLUSTER_ID_BINARY_INPUT, + "ZB_HA_SIMPLE_SENSOR_DEVICE_ID", + ) + + +async def _add_sensor(entity: cg.MockObj, config: ConfigType) -> None: + # Get BACnet engineering unit from unit_of_measurement + unit = config.get(CONF_UNIT_OF_MEASUREMENT, "") + bacnet_unit = BACNET_UNITS.get(unit, BACNET_UNIT_NO_UNITS) + + await _add_zigbee_input( + entity, + config, + CONF_ZIGBEE_SENSOR, + AnalogAttrs, + "ESPHOME_ZB_ZCL_DECLARE_ANALOG_INPUT_ATTRIB_LIST", + ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, + "ZB_HA_CUSTOM_ATTR_DEVICE_ID", + extra_field_values={"engineering_units": bacnet_unit}, + ) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 69684fd5c9..f8f86e8c55 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -305,6 +305,7 @@ #define USE_SOFTDEVICE_VERSION 1 #define USE_ZIGBEE #define USE_ZIGBEE_WIPE_ON_BOOT +#define USE_ZIGBEE_WIPE_ON_BOOT_MAGIC 1 #define ZIGBEE_ENDPOINTS_COUNT 8 #endif diff --git a/tests/components/zigbee/common.yaml b/tests/components/zigbee/common.yaml index eb30205446..c91569bdbe 100644 --- a/tests/components/zigbee/common.yaml +++ b/tests/components/zigbee/common.yaml @@ -15,10 +15,14 @@ binary_sensor: - platform: template name: "Garage Door Open 7" internal: True + +sensor: - platform: template - name: "Garage Door Open 8" + name: "Analog 1" + lambda: return 10.0; - platform: template - name: "Garage Door Open 9" + name: "Analog 2" + lambda: return 11.0; zigbee: wipe_on_boot: true diff --git a/tests/components/zigbee/test.nrf52-xiao-ble.yaml b/tests/components/zigbee/test.nrf52-xiao-ble.yaml index dade44d145..d2ce552de3 100644 --- a/tests/components/zigbee/test.nrf52-xiao-ble.yaml +++ b/tests/components/zigbee/test.nrf52-xiao-ble.yaml @@ -1 +1,5 @@ <<: !include common.yaml + +zigbee: + wipe_on_boot: once + power_source: battery From 546cdbde0dfccbcd70730049c683215a5481f42e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:23:28 -1000 Subject: [PATCH 1122/1145] [api] Simplify string handling by removing bifurcated client/server storage (#12822) --- esphome/components/api/api_connection.cpp | 108 ++-- esphome/components/api/api_connection.h | 6 +- esphome/components/api/api_pb2.cpp | 504 +++++++++--------- esphome/components/api/api_pb2.h | 171 ++---- esphome/components/api/api_pb2_dump.cpp | 383 ++++++------- esphome/components/api/custom_api_device.h | 16 +- .../components/api/homeassistant_service.h | 4 +- esphome/components/api/proto.h | 8 +- esphome/components/api/user_services.h | 8 +- .../number/homeassistant_number.cpp | 6 +- .../switch/homeassistant_switch.cpp | 6 +- .../voice_assistant/voice_assistant.cpp | 4 +- script/api_protobuf/api_protobuf.py | 36 +- 13 files changed, 577 insertions(+), 683 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 30f7b5710c..649ed31283 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -376,7 +376,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne bool is_single) { auto *binary_sensor = static_cast(entity); ListEntitiesBinarySensorResponse msg; - msg.set_device_class(binary_sensor->get_device_class_ref()); + msg.device_class = binary_sensor->get_device_class_ref(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); return fill_and_encode_entity_info(binary_sensor, msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -408,7 +408,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c msg.supports_position = traits.get_supports_position(); msg.supports_tilt = traits.get_supports_tilt(); msg.supports_stop = traits.get_supports_stop(); - msg.set_device_class(cover->get_device_class_ref()); + msg.device_class = cover->get_device_class_ref(); return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -443,7 +443,7 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co if (traits.supports_direction()) msg.direction = static_cast(fan->direction); if (traits.supports_preset_modes() && fan->has_preset_mode()) - msg.set_preset_mode(StringRef(fan->get_preset_mode())); + msg.preset_mode = StringRef(fan->get_preset_mode()); return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -499,7 +499,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection * resp.cold_white = values.get_cold_white(); resp.warm_white = values.get_warm_white(); if (light->supports_effects()) { - resp.set_effect(light->get_effect_name_ref()); + resp.effect = light->get_effect_name_ref(); } return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -581,10 +581,10 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection * bool is_single) { auto *sensor = static_cast(entity); ListEntitiesSensorResponse msg; - msg.set_unit_of_measurement(sensor->get_unit_of_measurement_ref()); + msg.unit_of_measurement = sensor->get_unit_of_measurement_ref(); msg.accuracy_decimals = sensor->get_accuracy_decimals(); msg.force_update = sensor->get_force_update(); - msg.set_device_class(sensor->get_device_class_ref()); + msg.device_class = sensor->get_device_class_ref(); msg.state_class = static_cast(sensor->get_state_class()); return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -611,7 +611,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection * auto *a_switch = static_cast(entity); ListEntitiesSwitchResponse msg; msg.assumed_state = a_switch->assumed_state(); - msg.set_device_class(a_switch->get_device_class_ref()); + msg.device_class = a_switch->get_device_class_ref(); return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -636,7 +636,7 @@ uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnec bool is_single) { auto *text_sensor = static_cast(entity); TextSensorStateResponse resp; - resp.set_state(StringRef(text_sensor->state)); + resp.state = StringRef(text_sensor->state); resp.missing_state = !text_sensor->has_state(); return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -645,7 +645,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect bool is_single) { auto *text_sensor = static_cast(entity); ListEntitiesTextSensorResponse msg; - msg.set_device_class(text_sensor->get_device_class_ref()); + msg.device_class = text_sensor->get_device_class_ref(); return fill_and_encode_entity_info(text_sensor, msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -675,13 +675,13 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection if (traits.get_supports_fan_modes() && climate->fan_mode.has_value()) resp.fan_mode = static_cast(climate->fan_mode.value()); if (!traits.get_supported_custom_fan_modes().empty() && climate->has_custom_fan_mode()) { - resp.set_custom_fan_mode(StringRef(climate->get_custom_fan_mode())); + resp.custom_fan_mode = StringRef(climate->get_custom_fan_mode()); } if (traits.get_supports_presets() && climate->preset.has_value()) { resp.preset = static_cast(climate->preset.value()); } if (!traits.get_supported_custom_presets().empty() && climate->has_custom_preset()) { - resp.set_custom_preset(StringRef(climate->get_custom_preset())); + resp.custom_preset = StringRef(climate->get_custom_preset()); } if (traits.get_supports_swing_modes()) resp.swing_mode = static_cast(climate->swing_mode); @@ -766,9 +766,9 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection * bool is_single) { auto *number = static_cast(entity); ListEntitiesNumberResponse msg; - msg.set_unit_of_measurement(number->traits.get_unit_of_measurement_ref()); + msg.unit_of_measurement = number->traits.get_unit_of_measurement_ref(); msg.mode = static_cast(number->traits.get_mode()); - msg.set_device_class(number->traits.get_device_class_ref()); + msg.device_class = number->traits.get_device_class_ref(); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); msg.step = number->traits.get_step(); @@ -881,7 +881,7 @@ uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *c bool is_single) { auto *text = static_cast(entity); TextStateResponse resp; - resp.set_state(StringRef(text->state)); + resp.state = StringRef(text->state); resp.missing_state = !text->has_state(); return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -893,7 +893,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co msg.mode = static_cast(text->traits.get_mode()); msg.min_length = text->traits.get_min_length(); msg.max_length = text->traits.get_max_length(); - msg.set_pattern(text->traits.get_pattern_ref()); + msg.pattern = text->traits.get_pattern_ref(); return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -914,7 +914,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection bool is_single) { auto *select = static_cast(entity); SelectStateResponse resp; - resp.set_state(StringRef(select->current_option())); + resp.state = StringRef(select->current_option()); resp.missing_state = !select->has_state(); return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -939,7 +939,7 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection * bool is_single) { auto *button = static_cast(entity); ListEntitiesButtonResponse msg; - msg.set_device_class(button->get_device_class_ref()); + msg.device_class = button->get_device_class_ref(); return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -1008,7 +1008,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c auto *valve = static_cast(entity); ListEntitiesValveResponse msg; auto traits = valve->get_traits(); - msg.set_device_class(valve->get_device_class_ref()); + msg.device_class = valve->get_device_class_ref(); msg.assumed_state = traits.get_is_assumed_state(); msg.supports_position = traits.get_supports_position(); msg.supports_stop = traits.get_supports_stop(); @@ -1053,7 +1053,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec for (auto &supported_format : traits.get_supported_formats()) { msg.supported_formats.emplace_back(); auto &media_format = msg.supported_formats.back(); - media_format.set_format(StringRef(supported_format.format)); + media_format.format = StringRef(supported_format.format); media_format.sample_rate = supported_format.sample_rate; media_format.num_channels = supported_format.num_channels; media_format.purpose = static_cast(supported_format.purpose); @@ -1263,8 +1263,8 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA for (auto &wake_word : config.available_wake_words) { resp.available_wake_words.emplace_back(); auto &resp_wake_word = resp.available_wake_words.back(); - resp_wake_word.set_id(StringRef(wake_word.id)); - resp_wake_word.set_wake_word(StringRef(wake_word.wake_word)); + resp_wake_word.id = StringRef(wake_word.id); + resp_wake_word.wake_word = StringRef(wake_word.wake_word); for (const auto &lang : wake_word.trained_languages) { resp_wake_word.trained_languages.push_back(lang); } @@ -1279,8 +1279,8 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA resp.available_wake_words.emplace_back(); auto &resp_wake_word = resp.available_wake_words.back(); - resp_wake_word.set_id(StringRef(wake_word.id)); - resp_wake_word.set_wake_word(StringRef(wake_word.wake_word)); + resp_wake_word.id = StringRef(wake_word.id); + resp_wake_word.wake_word = StringRef(wake_word.wake_word); for (const auto &lang : wake_word.trained_languages) { resp_wake_word.trained_languages.push_back(lang); } @@ -1421,7 +1421,7 @@ void APIConnection::send_event(event::Event *event, const char *event_type) { uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn, uint32_t remaining_size, bool is_single) { EventResponse resp; - resp.set_event_type(StringRef(event_type)); + resp.event_type = StringRef(event_type); return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -1429,7 +1429,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c bool is_single) { auto *event = static_cast(entity); ListEntitiesEventResponse msg; - msg.set_device_class(event->get_device_class_ref()); + msg.device_class = event->get_device_class_ref(); msg.event_types = &event->get_event_types(); return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -1452,11 +1452,11 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection resp.has_progress = true; resp.progress = update->update_info.progress; } - resp.set_current_version(StringRef(update->update_info.current_version)); - resp.set_latest_version(StringRef(update->update_info.latest_version)); - resp.set_title(StringRef(update->update_info.title)); - resp.set_release_summary(StringRef(update->update_info.summary)); - resp.set_release_url(StringRef(update->update_info.release_url)); + resp.current_version = StringRef(update->update_info.current_version); + resp.latest_version = StringRef(update->update_info.latest_version); + resp.title = StringRef(update->update_info.title); + resp.release_summary = StringRef(update->update_info.summary); + resp.release_url = StringRef(update->update_info.release_url); } return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -1464,7 +1464,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection * bool is_single) { auto *update = static_cast(entity); ListEntitiesUpdateResponse msg; - msg.set_device_class(update->get_device_class_ref()); + msg.device_class = update->get_device_class_ref(); return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -1531,8 +1531,8 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) { resp.api_version_major = 1; resp.api_version_minor = 14; // Send only the version string - the client only logs this for debugging and doesn't use it otherwise - resp.set_server_info(ESPHOME_VERSION_REF); - resp.set_name(StringRef(App.get_name())); + resp.server_info = ESPHOME_VERSION_REF; + resp.name = StringRef(App.get_name()); // Auto-authenticate - password auth was removed in ESPHome 2026.1.0 this->complete_authentication_(); @@ -1547,24 +1547,24 @@ bool APIConnection::send_ping_response(const PingRequest &msg) { bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { DeviceInfoResponse resp{}; - resp.set_name(StringRef(App.get_name())); - resp.set_friendly_name(StringRef(App.get_friendly_name())); + resp.name = StringRef(App.get_name()); + resp.friendly_name = StringRef(App.get_friendly_name()); #ifdef USE_AREAS - resp.set_suggested_area(StringRef(App.get_area())); + resp.suggested_area = StringRef(App.get_area()); #endif // Stack buffer for MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes) char mac_address[18]; uint8_t mac[6]; get_mac_address_raw(mac); format_mac_addr_upper(mac, mac_address); - resp.set_mac_address(StringRef(mac_address)); + resp.mac_address = StringRef(mac_address); - resp.set_esphome_version(ESPHOME_VERSION_REF); + resp.esphome_version = ESPHOME_VERSION_REF; // Stack buffer for build time string char build_time_str[Application::BUILD_TIME_STR_SIZE]; App.get_build_time_string(build_time_str); - resp.set_compilation_time(StringRef(build_time_str)); + resp.compilation_time = StringRef(build_time_str); // Manufacturer string - define once, handle ESP8266 PROGMEM separately #if defined(USE_ESP8266) || defined(USE_ESP32) @@ -1588,10 +1588,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { static const char MANUFACTURER_PROGMEM[] PROGMEM = ESPHOME_MANUFACTURER; char manufacturer_buf[sizeof(MANUFACTURER_PROGMEM)]; memcpy_P(manufacturer_buf, MANUFACTURER_PROGMEM, sizeof(MANUFACTURER_PROGMEM)); - resp.set_manufacturer(StringRef(manufacturer_buf, sizeof(MANUFACTURER_PROGMEM) - 1)); + resp.manufacturer = StringRef(manufacturer_buf, sizeof(MANUFACTURER_PROGMEM) - 1); #else static constexpr auto MANUFACTURER = StringRef::from_lit(ESPHOME_MANUFACTURER); - resp.set_manufacturer(MANUFACTURER); + resp.manufacturer = MANUFACTURER; #endif #undef ESPHOME_MANUFACTURER @@ -1599,10 +1599,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { static const char MODEL_PROGMEM[] PROGMEM = ESPHOME_BOARD; char model_buf[sizeof(MODEL_PROGMEM)]; memcpy_P(model_buf, MODEL_PROGMEM, sizeof(MODEL_PROGMEM)); - resp.set_model(StringRef(model_buf, sizeof(MODEL_PROGMEM) - 1)); + resp.model = StringRef(model_buf, sizeof(MODEL_PROGMEM) - 1); #else static constexpr auto MODEL = StringRef::from_lit(ESPHOME_BOARD); - resp.set_model(MODEL); + resp.model = MODEL; #endif #ifdef USE_DEEP_SLEEP resp.has_deep_sleep = deep_sleep::global_has_deep_sleep; @@ -1615,13 +1615,13 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { char project_version_buf[sizeof(PROJECT_VERSION_PROGMEM)]; memcpy_P(project_name_buf, PROJECT_NAME_PROGMEM, sizeof(PROJECT_NAME_PROGMEM)); memcpy_P(project_version_buf, PROJECT_VERSION_PROGMEM, sizeof(PROJECT_VERSION_PROGMEM)); - resp.set_project_name(StringRef(project_name_buf, sizeof(PROJECT_NAME_PROGMEM) - 1)); - resp.set_project_version(StringRef(project_version_buf, sizeof(PROJECT_VERSION_PROGMEM) - 1)); + resp.project_name = StringRef(project_name_buf, sizeof(PROJECT_NAME_PROGMEM) - 1); + resp.project_version = StringRef(project_version_buf, sizeof(PROJECT_VERSION_PROGMEM) - 1); #else static constexpr auto PROJECT_NAME = StringRef::from_lit(ESPHOME_PROJECT_NAME); static constexpr auto PROJECT_VERSION = StringRef::from_lit(ESPHOME_PROJECT_VERSION); - resp.set_project_name(PROJECT_NAME); - resp.set_project_version(PROJECT_VERSION); + resp.project_name = PROJECT_NAME; + resp.project_version = PROJECT_VERSION; #endif #endif #ifdef USE_WEBSERVER @@ -1632,7 +1632,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { // Stack buffer for Bluetooth MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes) char bluetooth_mac[18]; bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(bluetooth_mac); - resp.set_bluetooth_mac_address(StringRef(bluetooth_mac)); + resp.bluetooth_mac_address = StringRef(bluetooth_mac); #endif #ifdef USE_VOICE_ASSISTANT resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags(); @@ -1651,7 +1651,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { break; auto &device_info = resp.devices[device_index++]; device_info.device_id = device->get_device_id(); - device_info.set_name(StringRef(device->get_name())); + device_info.name = StringRef(device->get_name()); device_info.area_id = device->get_area_id(); } #endif @@ -1662,7 +1662,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { break; auto &area_info = resp.areas[area_index++]; area_info.area_id = area->get_area_id(); - area_info.set_name(StringRef(area->get_name())); + area_info.name = StringRef(area->get_name()); } #endif @@ -1741,7 +1741,7 @@ void APIConnection::send_execute_service_response(uint32_t call_id, bool success ExecuteServiceResponse resp; resp.call_id = call_id; resp.success = success; - resp.set_error_message(error_message); + resp.error_message = error_message; this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE); } #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON @@ -1750,7 +1750,7 @@ void APIConnection::send_execute_service_response(uint32_t call_id, bool success ExecuteServiceResponse resp; resp.call_id = call_id; resp.success = success; - resp.set_error_message(error_message); + resp.error_message = error_message; resp.response_data = response_data; resp.response_data_len = response_data_len; this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE); @@ -2071,10 +2071,10 @@ void APIConnection::process_state_subscriptions_() { const auto &it = subs[this->state_subs_at_]; SubscribeHomeAssistantStateResponse resp; - resp.set_entity_id(StringRef(it.entity_id)); + resp.entity_id = StringRef(it.entity_id); // Avoid string copy by using the const char* pointer if it exists - resp.set_attribute(it.attribute != nullptr ? StringRef(it.attribute) : StringRef("")); + resp.attribute = it.attribute != nullptr ? StringRef(it.attribute) : StringRef(""); resp.once = it.once; if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) { diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 802681f32f..15d79a25ec 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -317,16 +317,16 @@ class APIConnection final : public APIServerConnection { // Buffer must remain in scope until encode_message_to_buffer is called char object_id_buf[OBJECT_ID_MAX_LEN]; if (!conn->client_supports_api_version(1, 14)) { - msg.set_object_id(entity->get_object_id_to(object_id_buf)); + msg.object_id = entity->get_object_id_to(object_id_buf); } if (entity->has_own_name()) { - msg.set_name(entity->get_name()); + msg.name = entity->get_name(); } // Set common EntityBase properties #ifdef USE_ENTITY_ICON - msg.set_icon(entity->get_icon_ref()); + msg.icon = entity->get_icon_ref(); #endif msg.disabled_by_default = entity->is_disabled_by_default(); msg.entity_category = static_cast(entity->get_entity_category()); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index d26b309552..03a6639b5e 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -34,51 +34,51 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) void HelloResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->api_version_major); buffer.encode_uint32(2, this->api_version_minor); - buffer.encode_string(3, this->server_info_ref_); - buffer.encode_string(4, this->name_ref_); + buffer.encode_string(3, this->server_info); + buffer.encode_string(4, this->name); } void HelloResponse::calculate_size(ProtoSize &size) const { size.add_uint32(1, this->api_version_major); size.add_uint32(1, this->api_version_minor); - size.add_length(1, this->server_info_ref_.size()); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->server_info.size()); + size.add_length(1, this->name.size()); } #ifdef USE_AREAS void AreaInfo::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->area_id); - buffer.encode_string(2, this->name_ref_); + buffer.encode_string(2, this->name); } void AreaInfo::calculate_size(ProtoSize &size) const { size.add_uint32(1, this->area_id); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); } #endif #ifdef USE_DEVICES void DeviceInfo::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->device_id); - buffer.encode_string(2, this->name_ref_); + buffer.encode_string(2, this->name); buffer.encode_uint32(3, this->area_id); } void DeviceInfo::calculate_size(ProtoSize &size) const { size.add_uint32(1, this->device_id); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_uint32(1, this->area_id); } #endif void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(2, this->name_ref_); - buffer.encode_string(3, this->mac_address_ref_); - buffer.encode_string(4, this->esphome_version_ref_); - buffer.encode_string(5, this->compilation_time_ref_); - buffer.encode_string(6, this->model_ref_); + buffer.encode_string(2, this->name); + buffer.encode_string(3, this->mac_address); + buffer.encode_string(4, this->esphome_version); + buffer.encode_string(5, this->compilation_time); + buffer.encode_string(6, this->model); #ifdef USE_DEEP_SLEEP buffer.encode_bool(7, this->has_deep_sleep); #endif #ifdef ESPHOME_PROJECT_NAME - buffer.encode_string(8, this->project_name_ref_); + buffer.encode_string(8, this->project_name); #endif #ifdef ESPHOME_PROJECT_NAME - buffer.encode_string(9, this->project_version_ref_); + buffer.encode_string(9, this->project_version); #endif #ifdef USE_WEBSERVER buffer.encode_uint32(10, this->webserver_port); @@ -86,16 +86,16 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { #ifdef USE_BLUETOOTH_PROXY buffer.encode_uint32(15, this->bluetooth_proxy_feature_flags); #endif - buffer.encode_string(12, this->manufacturer_ref_); - buffer.encode_string(13, this->friendly_name_ref_); + buffer.encode_string(12, this->manufacturer); + buffer.encode_string(13, this->friendly_name); #ifdef USE_VOICE_ASSISTANT buffer.encode_uint32(17, this->voice_assistant_feature_flags); #endif #ifdef USE_AREAS - buffer.encode_string(16, this->suggested_area_ref_); + buffer.encode_string(16, this->suggested_area); #endif #ifdef USE_BLUETOOTH_PROXY - buffer.encode_string(18, this->bluetooth_mac_address_ref_); + buffer.encode_string(18, this->bluetooth_mac_address); #endif #ifdef USE_API_NOISE buffer.encode_bool(19, this->api_encryption_supported); @@ -121,19 +121,19 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { #endif } void DeviceInfoResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->name_ref_.size()); - size.add_length(1, this->mac_address_ref_.size()); - size.add_length(1, this->esphome_version_ref_.size()); - size.add_length(1, this->compilation_time_ref_.size()); - size.add_length(1, this->model_ref_.size()); + size.add_length(1, this->name.size()); + size.add_length(1, this->mac_address.size()); + size.add_length(1, this->esphome_version.size()); + size.add_length(1, this->compilation_time.size()); + size.add_length(1, this->model.size()); #ifdef USE_DEEP_SLEEP size.add_bool(1, this->has_deep_sleep); #endif #ifdef ESPHOME_PROJECT_NAME - size.add_length(1, this->project_name_ref_.size()); + size.add_length(1, this->project_name.size()); #endif #ifdef ESPHOME_PROJECT_NAME - size.add_length(1, this->project_version_ref_.size()); + size.add_length(1, this->project_version.size()); #endif #ifdef USE_WEBSERVER size.add_uint32(1, this->webserver_port); @@ -141,16 +141,16 @@ void DeviceInfoResponse::calculate_size(ProtoSize &size) const { #ifdef USE_BLUETOOTH_PROXY size.add_uint32(1, this->bluetooth_proxy_feature_flags); #endif - size.add_length(1, this->manufacturer_ref_.size()); - size.add_length(1, this->friendly_name_ref_.size()); + size.add_length(1, this->manufacturer.size()); + size.add_length(1, this->friendly_name.size()); #ifdef USE_VOICE_ASSISTANT size.add_uint32(2, this->voice_assistant_feature_flags); #endif #ifdef USE_AREAS - size.add_length(2, this->suggested_area_ref_.size()); + size.add_length(2, this->suggested_area.size()); #endif #ifdef USE_BLUETOOTH_PROXY - size.add_length(2, this->bluetooth_mac_address_ref_.size()); + size.add_length(2, this->bluetooth_mac_address.size()); #endif #ifdef USE_API_NOISE size.add_bool(2, this->api_encryption_supported); @@ -177,14 +177,14 @@ void DeviceInfoResponse::calculate_size(ProtoSize &size) const { } #ifdef USE_BINARY_SENSOR void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); - buffer.encode_string(5, this->device_class_ref_); + buffer.encode_string(3, this->name); + buffer.encode_string(5, this->device_class); buffer.encode_bool(6, this->is_status_binary_sensor); buffer.encode_bool(7, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(8, this->icon_ref_); + buffer.encode_string(8, this->icon); #endif buffer.encode_uint32(9, static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -192,14 +192,14 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesBinarySensorResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->name.size()); + size.add_length(1, this->device_class.size()); size.add_bool(1, this->is_status_binary_sensor); size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -225,16 +225,16 @@ void BinarySensorStateResponse::calculate_size(ProtoSize &size) const { #endif #ifdef USE_COVER void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); buffer.encode_bool(5, this->assumed_state); buffer.encode_bool(6, this->supports_position); buffer.encode_bool(7, this->supports_tilt); - buffer.encode_string(8, this->device_class_ref_); + buffer.encode_string(8, this->device_class); buffer.encode_bool(9, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(10, this->icon_ref_); + buffer.encode_string(10, this->icon); #endif buffer.encode_uint32(11, static_cast(this->entity_category)); buffer.encode_bool(12, this->supports_stop); @@ -243,16 +243,16 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesCoverResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_bool(1, this->assumed_state); size.add_bool(1, this->supports_position); size.add_bool(1, this->supports_tilt); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_uint32(1, static_cast(this->entity_category)); size.add_bool(1, this->supports_stop); @@ -318,16 +318,16 @@ bool CoverCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_FAN void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); buffer.encode_bool(5, this->supports_oscillation); buffer.encode_bool(6, this->supports_speed); buffer.encode_bool(7, this->supports_direction); buffer.encode_int32(8, this->supported_speed_count); buffer.encode_bool(9, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(10, this->icon_ref_); + buffer.encode_string(10, this->icon); #endif buffer.encode_uint32(11, static_cast(this->entity_category)); for (const char *it : *this->supported_preset_modes) { @@ -338,16 +338,16 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesFanResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_bool(1, this->supports_oscillation); size.add_bool(1, this->supports_speed); size.add_bool(1, this->supports_direction); size.add_int32(1, this->supported_speed_count); size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_uint32(1, static_cast(this->entity_category)); if (!this->supported_preset_modes->empty()) { @@ -365,7 +365,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(3, this->oscillating); buffer.encode_uint32(5, static_cast(this->direction)); buffer.encode_int32(6, this->speed_level); - buffer.encode_string(7, this->preset_mode_ref_); + buffer.encode_string(7, this->preset_mode); #ifdef USE_DEVICES buffer.encode_uint32(8, this->device_id); #endif @@ -376,7 +376,7 @@ void FanStateResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->oscillating); size.add_uint32(1, static_cast(this->direction)); size.add_int32(1, this->speed_level); - size.add_length(1, this->preset_mode_ref_.size()); + size.add_length(1, this->preset_mode.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -444,9 +444,9 @@ bool FanCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_LIGHT void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); for (const auto &it : *this->supported_color_modes) { buffer.encode_uint32(12, static_cast(it), true); } @@ -457,7 +457,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(13, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(14, this->icon_ref_); + buffer.encode_string(14, this->icon); #endif buffer.encode_uint32(15, static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -465,9 +465,9 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); if (!this->supported_color_modes->empty()) { for (const auto &it : *this->supported_color_modes) { size.add_uint32_force(1, static_cast(it)); @@ -482,7 +482,7 @@ void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const { } size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -502,7 +502,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(8, this->color_temperature); buffer.encode_float(12, this->cold_white); buffer.encode_float(13, this->warm_white); - buffer.encode_string(9, this->effect_ref_); + buffer.encode_string(9, this->effect); #ifdef USE_DEVICES buffer.encode_uint32(14, this->device_id); #endif @@ -520,7 +520,7 @@ void LightStateResponse::calculate_size(ProtoSize &size) const { size.add_float(1, this->color_temperature); size.add_float(1, this->cold_white); size.add_float(1, this->warm_white); - size.add_length(1, this->effect_ref_.size()); + size.add_length(1, this->effect.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -636,16 +636,16 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_SENSOR void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif - buffer.encode_string(6, this->unit_of_measurement_ref_); + buffer.encode_string(6, this->unit_of_measurement); buffer.encode_int32(7, this->accuracy_decimals); buffer.encode_bool(8, this->force_update); - buffer.encode_string(9, this->device_class_ref_); + buffer.encode_string(9, this->device_class); buffer.encode_uint32(10, static_cast(this->state_class)); buffer.encode_bool(12, this->disabled_by_default); buffer.encode_uint32(13, static_cast(this->entity_category)); @@ -654,16 +654,16 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesSensorResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif - size.add_length(1, this->unit_of_measurement_ref_.size()); + size.add_length(1, this->unit_of_measurement.size()); size.add_int32(1, this->accuracy_decimals); size.add_bool(1, this->force_update); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); size.add_uint32(1, static_cast(this->state_class)); size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); @@ -690,31 +690,31 @@ void SensorStateResponse::calculate_size(ProtoSize &size) const { #endif #ifdef USE_SWITCH void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->assumed_state); buffer.encode_bool(7, this->disabled_by_default); buffer.encode_uint32(8, static_cast(this->entity_category)); - buffer.encode_string(9, this->device_class_ref_); + buffer.encode_string(9, this->device_class); #ifdef USE_DEVICES buffer.encode_uint32(10, this->device_id); #endif } void ListEntitiesSwitchResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->assumed_state); size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -761,36 +761,36 @@ bool SwitchCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_TEXT_SENSOR void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class_ref_); + buffer.encode_string(8, this->device_class); #ifdef USE_DEVICES buffer.encode_uint32(9, this->device_id); #endif } void ListEntitiesTextSensorResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif } void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->state_ref_); + buffer.encode_string(2, this->state); buffer.encode_bool(3, this->missing_state); #ifdef USE_DEVICES buffer.encode_uint32(4, this->device_id); @@ -798,7 +798,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { } void TextSensorStateResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->key); - size.add_length(1, this->state_ref_.size()); + size.add_length(1, this->state.size()); size.add_bool(1, this->missing_state); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); @@ -844,15 +844,15 @@ void NoiseEncryptionSetKeyResponse::calculate_size(ProtoSize &size) const { size #endif #ifdef USE_API_HOMEASSISTANT_SERVICES void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->key_ref_); + buffer.encode_string(1, this->key); buffer.encode_string(2, this->value); } void HomeassistantServiceMap::calculate_size(ProtoSize &size) const { - size.add_length(1, this->key_ref_.size()); + size.add_length(1, this->key.size()); size.add_length(1, this->value.size()); } void HomeassistantActionRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->service_ref_); + buffer.encode_string(1, this->service); for (auto &it : this->data) { buffer.encode_message(2, it); } @@ -874,7 +874,7 @@ void HomeassistantActionRequest::encode(ProtoWriteBuffer buffer) const { #endif } void HomeassistantActionRequest::calculate_size(ProtoSize &size) const { - size.add_length(1, this->service_ref_.size()); + size.add_length(1, this->service.size()); size.add_repeated_message(1, this->data); size.add_repeated_message(1, this->data_template); size.add_repeated_message(1, this->variables); @@ -925,13 +925,13 @@ bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDe #endif #ifdef USE_API_HOMEASSISTANT_STATES void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->entity_id_ref_); - buffer.encode_string(2, this->attribute_ref_); + buffer.encode_string(1, this->entity_id); + buffer.encode_string(2, this->attribute); buffer.encode_bool(3, this->once); } void SubscribeHomeAssistantStateResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->entity_id_ref_.size()); - size.add_length(1, this->attribute_ref_.size()); + size.add_length(1, this->entity_id.size()); + size.add_length(1, this->attribute.size()); size.add_bool(1, this->once); } bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { @@ -977,15 +977,15 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { } #ifdef USE_API_USER_DEFINED_ACTIONS void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->name_ref_); + buffer.encode_string(1, this->name); buffer.encode_uint32(2, static_cast(this->type)); } void ListEntitiesServicesArgument::calculate_size(ProtoSize &size) const { - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_uint32(1, static_cast(this->type)); } void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->name_ref_); + buffer.encode_string(1, this->name); buffer.encode_fixed32(2, this->key); for (auto &it : this->args) { buffer.encode_message(3, it); @@ -993,7 +993,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, static_cast(this->supports_response)); } void ListEntitiesServicesResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_fixed32(1, this->key); size.add_repeated_message(1, this->args); size.add_uint32(1, static_cast(this->supports_response)); @@ -1106,7 +1106,7 @@ void ExecuteServiceRequest::decode(const uint8_t *buffer, size_t length) { void ExecuteServiceResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->call_id); buffer.encode_bool(2, this->success); - buffer.encode_string(3, this->error_message_ref_); + buffer.encode_string(3, this->error_message); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON buffer.encode_bytes(4, this->response_data, this->response_data_len); #endif @@ -1114,7 +1114,7 @@ void ExecuteServiceResponse::encode(ProtoWriteBuffer buffer) const { void ExecuteServiceResponse::calculate_size(ProtoSize &size) const { size.add_uint32(1, this->call_id); size.add_bool(1, this->success); - size.add_length(1, this->error_message_ref_.size()); + size.add_length(1, this->error_message.size()); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON size.add_length(1, this->response_data_len); #endif @@ -1122,12 +1122,12 @@ void ExecuteServiceResponse::calculate_size(ProtoSize &size) const { #endif #ifdef USE_CAMERA void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); buffer.encode_bool(5, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(6, this->icon_ref_); + buffer.encode_string(6, this->icon); #endif buffer.encode_uint32(7, static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -1135,12 +1135,12 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesCameraResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -1179,9 +1179,9 @@ bool CameraImageRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { #endif #ifdef USE_CLIMATE void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); buffer.encode_bool(5, this->supports_current_temperature); buffer.encode_bool(6, this->supports_two_point_target_temperature); for (const auto &it : *this->supported_modes) { @@ -1208,7 +1208,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(18, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(19, this->icon_ref_); + buffer.encode_string(19, this->icon); #endif buffer.encode_uint32(20, static_cast(this->entity_category)); buffer.encode_float(21, this->visual_current_temperature_step); @@ -1222,9 +1222,9 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(27, this->feature_flags); } void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_bool(1, this->supports_current_temperature); size.add_bool(1, this->supports_two_point_target_temperature); if (!this->supported_modes->empty()) { @@ -1263,7 +1263,7 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const { } size.add_bool(2, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size.add_length(2, this->icon_ref_.size()); + size.add_length(2, this->icon.size()); #endif size.add_uint32(2, static_cast(this->entity_category)); size.add_float(2, this->visual_current_temperature_step); @@ -1286,9 +1286,9 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(8, static_cast(this->action)); buffer.encode_uint32(9, static_cast(this->fan_mode)); buffer.encode_uint32(10, static_cast(this->swing_mode)); - buffer.encode_string(11, this->custom_fan_mode_ref_); + buffer.encode_string(11, this->custom_fan_mode); buffer.encode_uint32(12, static_cast(this->preset)); - buffer.encode_string(13, this->custom_preset_ref_); + buffer.encode_string(13, this->custom_preset); buffer.encode_float(14, this->current_humidity); buffer.encode_float(15, this->target_humidity); #ifdef USE_DEVICES @@ -1305,9 +1305,9 @@ void ClimateStateResponse::calculate_size(ProtoSize &size) const { size.add_uint32(1, static_cast(this->action)); size.add_uint32(1, static_cast(this->fan_mode)); size.add_uint32(1, static_cast(this->swing_mode)); - size.add_length(1, this->custom_fan_mode_ref_.size()); + size.add_length(1, this->custom_fan_mode.size()); size.add_uint32(1, static_cast(this->preset)); - size.add_length(1, this->custom_preset_ref_.size()); + size.add_length(1, this->custom_preset.size()); size.add_float(1, this->current_humidity); size.add_float(1, this->target_humidity); #ifdef USE_DEVICES @@ -1408,11 +1408,11 @@ bool ClimateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_WATER_HEATER void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(4, this->icon_ref_); + buffer.encode_string(4, this->icon); #endif buffer.encode_bool(5, this->disabled_by_default); buffer.encode_uint32(6, static_cast(this->entity_category)); @@ -1428,11 +1428,11 @@ void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(12, this->supported_features); } void ListEntitiesWaterHeaterResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); @@ -1516,39 +1516,39 @@ bool WaterHeaterCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value #endif #ifdef USE_NUMBER void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_float(6, this->min_value); buffer.encode_float(7, this->max_value); buffer.encode_float(8, this->step); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_uint32(10, static_cast(this->entity_category)); - buffer.encode_string(11, this->unit_of_measurement_ref_); + buffer.encode_string(11, this->unit_of_measurement); buffer.encode_uint32(12, static_cast(this->mode)); - buffer.encode_string(13, this->device_class_ref_); + buffer.encode_string(13, this->device_class); #ifdef USE_DEVICES buffer.encode_uint32(14, this->device_id); #endif } void ListEntitiesNumberResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_float(1, this->min_value); size.add_float(1, this->max_value); size.add_float(1, this->step); size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->unit_of_measurement_ref_.size()); + size.add_length(1, this->unit_of_measurement.size()); size.add_uint32(1, static_cast(this->mode)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -1597,11 +1597,11 @@ bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_SELECT void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif for (const char *it : *this->options) { buffer.encode_string(6, it, strlen(it), true); @@ -1613,11 +1613,11 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif if (!this->options->empty()) { for (const char *it : *this->options) { @@ -1632,7 +1632,7 @@ void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const { } void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->state_ref_); + buffer.encode_string(2, this->state); buffer.encode_bool(3, this->missing_state); #ifdef USE_DEVICES buffer.encode_uint32(4, this->device_id); @@ -1640,7 +1640,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { } void SelectStateResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->key); - size.add_length(1, this->state_ref_.size()); + size.add_length(1, this->state.size()); size.add_bool(1, this->missing_state); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); @@ -1682,11 +1682,11 @@ bool SelectCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_SIREN void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); for (const char *it : *this->tones) { @@ -1700,11 +1700,11 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesSirenResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); if (!this->tones->empty()) { @@ -1790,35 +1790,35 @@ bool SirenCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_LOCK void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_bool(8, this->assumed_state); buffer.encode_bool(9, this->supports_open); buffer.encode_bool(10, this->requires_code); - buffer.encode_string(11, this->code_format_ref_); + buffer.encode_string(11, this->code_format); #ifdef USE_DEVICES buffer.encode_uint32(12, this->device_id); #endif } void ListEntitiesLockResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); size.add_bool(1, this->assumed_state); size.add_bool(1, this->supports_open); size.add_bool(1, this->requires_code); - size.add_length(1, this->code_format_ref_.size()); + size.add_length(1, this->code_format.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -1879,29 +1879,29 @@ bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_BUTTON void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class_ref_); + buffer.encode_string(8, this->device_class); #ifdef USE_DEVICES buffer.encode_uint32(9, this->device_id); #endif } void ListEntitiesButtonResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -1931,25 +1931,25 @@ bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_MEDIA_PLAYER void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->format_ref_); + buffer.encode_string(1, this->format); buffer.encode_uint32(2, this->sample_rate); buffer.encode_uint32(3, this->num_channels); buffer.encode_uint32(4, static_cast(this->purpose)); buffer.encode_uint32(5, this->sample_bytes); } void MediaPlayerSupportedFormat::calculate_size(ProtoSize &size) const { - size.add_length(1, this->format_ref_.size()); + size.add_length(1, this->format.size()); size.add_uint32(1, this->sample_rate); size.add_uint32(1, this->num_channels); size.add_uint32(1, static_cast(this->purpose)); size.add_uint32(1, this->sample_bytes); } void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); @@ -1963,11 +1963,11 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(11, this->feature_flags); } void ListEntitiesMediaPlayerResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); @@ -2433,17 +2433,17 @@ void VoiceAssistantAudioSettings::calculate_size(ProtoSize &size) const { } void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->start); - buffer.encode_string(2, this->conversation_id_ref_); + buffer.encode_string(2, this->conversation_id); buffer.encode_uint32(3, this->flags); buffer.encode_message(4, this->audio_settings); - buffer.encode_string(5, this->wake_word_phrase_ref_); + buffer.encode_string(5, this->wake_word_phrase); } void VoiceAssistantRequest::calculate_size(ProtoSize &size) const { size.add_bool(1, this->start); - size.add_length(1, this->conversation_id_ref_.size()); + size.add_length(1, this->conversation_id.size()); size.add_uint32(1, this->flags); size.add_message_object(1, this->audio_settings); - size.add_length(1, this->wake_word_phrase_ref_.size()); + size.add_length(1, this->wake_word_phrase.size()); } bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -2590,15 +2590,15 @@ bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLength void VoiceAssistantAnnounceFinished::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->success); } void VoiceAssistantAnnounceFinished::calculate_size(ProtoSize &size) const { size.add_bool(1, this->success); } void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->id_ref_); - buffer.encode_string(2, this->wake_word_ref_); + buffer.encode_string(1, this->id); + buffer.encode_string(2, this->wake_word); for (auto &it : this->trained_languages) { buffer.encode_string(3, it, true); } } void VoiceAssistantWakeWord::calculate_size(ProtoSize &size) const { - size.add_length(1, this->id_ref_.size()); - size.add_length(1, this->wake_word_ref_.size()); + size.add_length(1, this->id.size()); + size.add_length(1, this->wake_word.size()); if (!this->trained_languages.empty()) { for (const auto &it : this->trained_languages) { size.add_length_force(1, it.size()); @@ -2687,11 +2687,11 @@ bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengt #endif #ifdef USE_ALARM_CONTROL_PANEL void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); @@ -2703,11 +2703,11 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) cons #endif } void ListEntitiesAlarmControlPanelResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); @@ -2771,34 +2771,34 @@ bool AlarmControlPanelCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit #endif #ifdef USE_TEXT void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_uint32(8, this->min_length); buffer.encode_uint32(9, this->max_length); - buffer.encode_string(10, this->pattern_ref_); + buffer.encode_string(10, this->pattern); buffer.encode_uint32(11, static_cast(this->mode)); #ifdef USE_DEVICES buffer.encode_uint32(12, this->device_id); #endif } void ListEntitiesTextResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); size.add_uint32(1, this->min_length); size.add_uint32(1, this->max_length); - size.add_length(1, this->pattern_ref_.size()); + size.add_length(1, this->pattern.size()); size.add_uint32(1, static_cast(this->mode)); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); @@ -2806,7 +2806,7 @@ void ListEntitiesTextResponse::calculate_size(ProtoSize &size) const { } void TextStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->state_ref_); + buffer.encode_string(2, this->state); buffer.encode_bool(3, this->missing_state); #ifdef USE_DEVICES buffer.encode_uint32(4, this->device_id); @@ -2814,7 +2814,7 @@ void TextStateResponse::encode(ProtoWriteBuffer buffer) const { } void TextStateResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->key); - size.add_length(1, this->state_ref_.size()); + size.add_length(1, this->state.size()); size.add_bool(1, this->missing_state); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); @@ -2856,11 +2856,11 @@ bool TextCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_DATETIME_DATE void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); @@ -2869,11 +2869,11 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesDateResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); @@ -2935,11 +2935,11 @@ bool DateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_DATETIME_TIME void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); @@ -2948,11 +2948,11 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesTimeResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); @@ -3014,15 +3014,15 @@ bool TimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_EVENT void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class_ref_); + buffer.encode_string(8, this->device_class); for (const char *it : *this->event_types) { buffer.encode_string(9, it, strlen(it), true); } @@ -3031,15 +3031,15 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesEventResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); if (!this->event_types->empty()) { for (const char *it : *this->event_types) { size.add_length_force(1, strlen(it)); @@ -3051,14 +3051,14 @@ void ListEntitiesEventResponse::calculate_size(ProtoSize &size) const { } void EventResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->event_type_ref_); + buffer.encode_string(2, this->event_type); #ifdef USE_DEVICES buffer.encode_uint32(3, this->device_id); #endif } void EventResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->key); - size.add_length(1, this->event_type_ref_.size()); + size.add_length(1, this->event_type.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -3066,15 +3066,15 @@ void EventResponse::calculate_size(ProtoSize &size) const { #endif #ifdef USE_VALVE void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class_ref_); + buffer.encode_string(8, this->device_class); buffer.encode_bool(9, this->assumed_state); buffer.encode_bool(10, this->supports_position); buffer.encode_bool(11, this->supports_stop); @@ -3083,15 +3083,15 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesValveResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); size.add_bool(1, this->assumed_state); size.add_bool(1, this->supports_position); size.add_bool(1, this->supports_stop); @@ -3149,11 +3149,11 @@ bool ValveCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_DATETIME_DATETIME void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); @@ -3162,11 +3162,11 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesDateTimeResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); @@ -3218,29 +3218,29 @@ bool DateTimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_UPDATE void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class_ref_); + buffer.encode_string(8, this->device_class); #ifdef USE_DEVICES buffer.encode_uint32(9, this->device_id); #endif } void ListEntitiesUpdateResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -3251,11 +3251,11 @@ void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(3, this->in_progress); buffer.encode_bool(4, this->has_progress); buffer.encode_float(5, this->progress); - buffer.encode_string(6, this->current_version_ref_); - buffer.encode_string(7, this->latest_version_ref_); - buffer.encode_string(8, this->title_ref_); - buffer.encode_string(9, this->release_summary_ref_); - buffer.encode_string(10, this->release_url_ref_); + buffer.encode_string(6, this->current_version); + buffer.encode_string(7, this->latest_version); + buffer.encode_string(8, this->title); + buffer.encode_string(9, this->release_summary); + buffer.encode_string(10, this->release_url); #ifdef USE_DEVICES buffer.encode_uint32(11, this->device_id); #endif @@ -3266,11 +3266,11 @@ void UpdateStateResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->in_progress); size.add_bool(1, this->has_progress); size.add_float(1, this->progress); - size.add_length(1, this->current_version_ref_.size()); - size.add_length(1, this->latest_version_ref_.size()); - size.add_length(1, this->title_ref_.size()); - size.add_length(1, this->release_summary_ref_.size()); - size.add_length(1, this->release_url_ref_.size()); + size.add_length(1, this->current_version.size()); + size.add_length(1, this->latest_version.size()); + size.add_length(1, this->title.size()); + size.add_length(1, this->release_summary.size()); + size.add_length(1, this->release_url.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 0605051e29..01fe44d7c7 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -315,15 +315,12 @@ enum ZWaveProxyRequestType : uint32_t { class InfoResponseProtoMessage : public ProtoMessage { public: ~InfoResponseProtoMessage() override = default; - StringRef object_id_ref_{}; - void set_object_id(const StringRef &ref) { this->object_id_ref_ = ref; } + StringRef object_id{}; uint32_t key{0}; - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } + StringRef name{}; bool disabled_by_default{false}; #ifdef USE_ENTITY_ICON - StringRef icon_ref_{}; - void set_icon(const StringRef &ref) { this->icon_ref_ = ref; } + StringRef icon{}; #endif enums::EntityCategory entity_category{}; #ifdef USE_DEVICES @@ -381,10 +378,8 @@ class HelloResponse final : public ProtoMessage { #endif uint32_t api_version_major{0}; uint32_t api_version_minor{0}; - StringRef server_info_ref_{}; - void set_server_info(const StringRef &ref) { this->server_info_ref_ = ref; } - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } + StringRef server_info{}; + StringRef name{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -462,8 +457,7 @@ class DeviceInfoRequest final : public ProtoMessage { class AreaInfo final : public ProtoMessage { public: uint32_t area_id{0}; - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } + StringRef name{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -477,8 +471,7 @@ class AreaInfo final : public ProtoMessage { class DeviceInfo final : public ProtoMessage { public: uint32_t device_id{0}; - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } + StringRef name{}; uint32_t area_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -496,26 +489,19 @@ class DeviceInfoResponse final : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "device_info_response"; } #endif - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } - StringRef mac_address_ref_{}; - void set_mac_address(const StringRef &ref) { this->mac_address_ref_ = ref; } - StringRef esphome_version_ref_{}; - void set_esphome_version(const StringRef &ref) { this->esphome_version_ref_ = ref; } - StringRef compilation_time_ref_{}; - void set_compilation_time(const StringRef &ref) { this->compilation_time_ref_ = ref; } - StringRef model_ref_{}; - void set_model(const StringRef &ref) { this->model_ref_ = ref; } + StringRef name{}; + StringRef mac_address{}; + StringRef esphome_version{}; + StringRef compilation_time{}; + StringRef model{}; #ifdef USE_DEEP_SLEEP bool has_deep_sleep{false}; #endif #ifdef ESPHOME_PROJECT_NAME - StringRef project_name_ref_{}; - void set_project_name(const StringRef &ref) { this->project_name_ref_ = ref; } + StringRef project_name{}; #endif #ifdef ESPHOME_PROJECT_NAME - StringRef project_version_ref_{}; - void set_project_version(const StringRef &ref) { this->project_version_ref_ = ref; } + StringRef project_version{}; #endif #ifdef USE_WEBSERVER uint32_t webserver_port{0}; @@ -523,20 +509,16 @@ class DeviceInfoResponse final : public ProtoMessage { #ifdef USE_BLUETOOTH_PROXY uint32_t bluetooth_proxy_feature_flags{0}; #endif - StringRef manufacturer_ref_{}; - void set_manufacturer(const StringRef &ref) { this->manufacturer_ref_ = ref; } - StringRef friendly_name_ref_{}; - void set_friendly_name(const StringRef &ref) { this->friendly_name_ref_ = ref; } + StringRef manufacturer{}; + StringRef friendly_name{}; #ifdef USE_VOICE_ASSISTANT uint32_t voice_assistant_feature_flags{0}; #endif #ifdef USE_AREAS - StringRef suggested_area_ref_{}; - void set_suggested_area(const StringRef &ref) { this->suggested_area_ref_ = ref; } + StringRef suggested_area{}; #endif #ifdef USE_BLUETOOTH_PROXY - StringRef bluetooth_mac_address_ref_{}; - void set_bluetooth_mac_address(const StringRef &ref) { this->bluetooth_mac_address_ref_ = ref; } + StringRef bluetooth_mac_address{}; #endif #ifdef USE_API_NOISE bool api_encryption_supported{false}; @@ -611,8 +593,7 @@ class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_binary_sensor_response"; } #endif - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; bool is_status_binary_sensor{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -651,8 +632,7 @@ class ListEntitiesCoverResponse final : public InfoResponseProtoMessage { bool assumed_state{false}; bool supports_position{false}; bool supports_tilt{false}; - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; bool supports_stop{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -733,8 +713,7 @@ class FanStateResponse final : public StateResponseProtoMessage { bool oscillating{false}; enums::FanDirection direction{}; int32_t speed_level{0}; - StringRef preset_mode_ref_{}; - void set_preset_mode(const StringRef &ref) { this->preset_mode_ref_ = ref; } + StringRef preset_mode{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -808,8 +787,7 @@ class LightStateResponse final : public StateResponseProtoMessage { float color_temperature{0.0f}; float cold_white{0.0f}; float warm_white{0.0f}; - StringRef effect_ref_{}; - void set_effect(const StringRef &ref) { this->effect_ref_ = ref; } + StringRef effect{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -869,12 +847,10 @@ class ListEntitiesSensorResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_sensor_response"; } #endif - StringRef unit_of_measurement_ref_{}; - void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; } + StringRef unit_of_measurement{}; int32_t accuracy_decimals{0}; bool force_update{false}; - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; enums::SensorStateClass state_class{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -911,8 +887,7 @@ class ListEntitiesSwitchResponse final : public InfoResponseProtoMessage { const char *message_name() const override { return "list_entities_switch_response"; } #endif bool assumed_state{false}; - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -962,8 +937,7 @@ class ListEntitiesTextSensorResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_text_sensor_response"; } #endif - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -979,8 +953,7 @@ class TextSensorStateResponse final : public StateResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "text_sensor_state_response"; } #endif - StringRef state_ref_{}; - void set_state(const StringRef &ref) { this->state_ref_ = ref; } + StringRef state{}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -1079,8 +1052,7 @@ class SubscribeHomeassistantServicesRequest final : public ProtoMessage { }; class HomeassistantServiceMap final : public ProtoMessage { public: - StringRef key_ref_{}; - void set_key(const StringRef &ref) { this->key_ref_ = ref; } + StringRef key{}; std::string value{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -1097,8 +1069,7 @@ class HomeassistantActionRequest final : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "homeassistant_action_request"; } #endif - StringRef service_ref_{}; - void set_service(const StringRef &ref) { this->service_ref_ = ref; } + StringRef service{}; FixedVector data{}; FixedVector data_template{}; FixedVector variables{}; @@ -1166,10 +1137,8 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "subscribe_home_assistant_state_response"; } #endif - StringRef entity_id_ref_{}; - void set_entity_id(const StringRef &ref) { this->entity_id_ref_ = ref; } - StringRef attribute_ref_{}; - void set_attribute(const StringRef &ref) { this->attribute_ref_ = ref; } + StringRef entity_id{}; + StringRef attribute{}; bool once{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -1230,8 +1199,7 @@ class GetTimeResponse final : public ProtoDecodableMessage { #ifdef USE_API_USER_DEFINED_ACTIONS class ListEntitiesServicesArgument final : public ProtoMessage { public: - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } + StringRef name{}; enums::ServiceArgType type{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -1248,8 +1216,7 @@ class ListEntitiesServicesResponse final : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_services_response"; } #endif - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } + StringRef name{}; uint32_t key{0}; FixedVector args{}; enums::SupportsResponseType supports_response{}; @@ -1318,8 +1285,7 @@ class ExecuteServiceResponse final : public ProtoMessage { #endif uint32_t call_id{0}; bool success{false}; - StringRef error_message_ref_{}; - void set_error_message(const StringRef &ref) { this->error_message_ref_ = ref; } + StringRef error_message{}; #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON const uint8_t *response_data{nullptr}; uint16_t response_data_len{0}; @@ -1437,11 +1403,9 @@ class ClimateStateResponse final : public StateResponseProtoMessage { enums::ClimateAction action{}; enums::ClimateFanMode fan_mode{}; enums::ClimateSwingMode swing_mode{}; - StringRef custom_fan_mode_ref_{}; - void set_custom_fan_mode(const StringRef &ref) { this->custom_fan_mode_ref_ = ref; } + StringRef custom_fan_mode{}; enums::ClimatePreset preset{}; - StringRef custom_preset_ref_{}; - void set_custom_preset(const StringRef &ref) { this->custom_preset_ref_ = ref; } + StringRef custom_preset{}; float current_humidity{0.0f}; float target_humidity{0.0f}; void encode(ProtoWriteBuffer buffer) const override; @@ -1564,11 +1528,9 @@ class ListEntitiesNumberResponse final : public InfoResponseProtoMessage { float min_value{0.0f}; float max_value{0.0f}; float step{0.0f}; - StringRef unit_of_measurement_ref_{}; - void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; } + StringRef unit_of_measurement{}; enums::NumberMode mode{}; - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1635,8 +1597,7 @@ class SelectStateResponse final : public StateResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "select_state_response"; } #endif - StringRef state_ref_{}; - void set_state(const StringRef &ref) { this->state_ref_ = ref; } + StringRef state{}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -1735,8 +1696,7 @@ class ListEntitiesLockResponse final : public InfoResponseProtoMessage { bool assumed_state{false}; bool supports_open{false}; bool requires_code{false}; - StringRef code_format_ref_{}; - void set_code_format(const StringRef &ref) { this->code_format_ref_ = ref; } + StringRef code_format{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1789,8 +1749,7 @@ class ListEntitiesButtonResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_button_response"; } #endif - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1818,8 +1777,7 @@ class ButtonCommandRequest final : public CommandProtoMessage { #ifdef USE_MEDIA_PLAYER class MediaPlayerSupportedFormat final : public ProtoMessage { public: - StringRef format_ref_{}; - void set_format(const StringRef &ref) { this->format_ref_ = ref; } + StringRef format{}; uint32_t sample_rate{0}; uint32_t num_channels{0}; enums::MediaPlayerFormatPurpose purpose{}; @@ -2424,12 +2382,10 @@ class VoiceAssistantRequest final : public ProtoMessage { const char *message_name() const override { return "voice_assistant_request"; } #endif bool start{false}; - StringRef conversation_id_ref_{}; - void set_conversation_id(const StringRef &ref) { this->conversation_id_ref_ = ref; } + StringRef conversation_id{}; uint32_t flags{0}; VoiceAssistantAudioSettings audio_settings{}; - StringRef wake_word_phrase_ref_{}; - void set_wake_word_phrase(const StringRef &ref) { this->wake_word_phrase_ref_ = ref; } + StringRef wake_word_phrase{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2560,10 +2516,8 @@ class VoiceAssistantAnnounceFinished final : public ProtoMessage { }; class VoiceAssistantWakeWord final : public ProtoMessage { public: - StringRef id_ref_{}; - void set_id(const StringRef &ref) { this->id_ref_ = ref; } - StringRef wake_word_ref_{}; - void set_wake_word(const StringRef &ref) { this->wake_word_ref_ = ref; } + StringRef id{}; + StringRef wake_word{}; std::vector trained_languages{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -2703,8 +2657,7 @@ class ListEntitiesTextResponse final : public InfoResponseProtoMessage { #endif uint32_t min_length{0}; uint32_t max_length{0}; - StringRef pattern_ref_{}; - void set_pattern(const StringRef &ref) { this->pattern_ref_ = ref; } + StringRef pattern{}; enums::TextMode mode{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -2721,8 +2674,7 @@ class TextStateResponse final : public StateResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "text_state_response"; } #endif - StringRef state_ref_{}; - void set_state(const StringRef &ref) { this->state_ref_ = ref; } + StringRef state{}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -2866,8 +2818,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_event_response"; } #endif - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; const FixedVector *event_types{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -2884,8 +2835,7 @@ class EventResponse final : public StateResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "event_response"; } #endif - StringRef event_type_ref_{}; - void set_event_type(const StringRef &ref) { this->event_type_ref_ = ref; } + StringRef event_type{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2903,8 +2853,7 @@ class ListEntitiesValveResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_valve_response"; } #endif - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; bool assumed_state{false}; bool supports_position{false}; bool supports_stop{false}; @@ -3010,8 +2959,7 @@ class ListEntitiesUpdateResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_update_response"; } #endif - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -3031,16 +2979,11 @@ class UpdateStateResponse final : public StateResponseProtoMessage { bool in_progress{false}; bool has_progress{false}; float progress{0.0f}; - StringRef current_version_ref_{}; - void set_current_version(const StringRef &ref) { this->current_version_ref_ = ref; } - StringRef latest_version_ref_{}; - void set_latest_version(const StringRef &ref) { this->latest_version_ref_ = ref; } - StringRef title_ref_{}; - void set_title(const StringRef &ref) { this->title_ref_ = ref; } - StringRef release_summary_ref_{}; - void set_release_summary(const StringRef &ref) { this->release_summary_ref_ = ref; } - StringRef release_url_ref_{}; - void set_release_url(const StringRef &ref) { this->release_url_ref_ = ref; } + StringRef current_version{}; + StringRef latest_version{}; + StringRef title{}; + StringRef release_summary{}; + StringRef release_url{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index ac5f04f3fb..160a9a93c9 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -735,9 +735,7 @@ template<> const char *proto_enum_to_string(enums: void HelloRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HelloRequest"); - out.append(" client_info: "); - out.append("'").append(this->client_info.c_str(), this->client_info.size()).append("'"); - out.append("\n"); + dump_field(out, "client_info", this->client_info); dump_field(out, "api_version_major", this->api_version_major); dump_field(out, "api_version_minor", this->api_version_minor); } @@ -745,8 +743,8 @@ void HelloResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HelloResponse"); dump_field(out, "api_version_major", this->api_version_major); dump_field(out, "api_version_minor", this->api_version_minor); - dump_field(out, "server_info", this->server_info_ref_); - dump_field(out, "name", this->name_ref_); + dump_field(out, "server_info", this->server_info); + dump_field(out, "name", this->name); } void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); } void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); } @@ -757,32 +755,32 @@ void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfo void AreaInfo::dump_to(std::string &out) const { MessageDumpHelper helper(out, "AreaInfo"); dump_field(out, "area_id", this->area_id); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); } #endif #ifdef USE_DEVICES void DeviceInfo::dump_to(std::string &out) const { MessageDumpHelper helper(out, "DeviceInfo"); dump_field(out, "device_id", this->device_id); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "area_id", this->area_id); } #endif void DeviceInfoResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "DeviceInfoResponse"); - dump_field(out, "name", this->name_ref_); - dump_field(out, "mac_address", this->mac_address_ref_); - dump_field(out, "esphome_version", this->esphome_version_ref_); - dump_field(out, "compilation_time", this->compilation_time_ref_); - dump_field(out, "model", this->model_ref_); + dump_field(out, "name", this->name); + dump_field(out, "mac_address", this->mac_address); + dump_field(out, "esphome_version", this->esphome_version); + dump_field(out, "compilation_time", this->compilation_time); + dump_field(out, "model", this->model); #ifdef USE_DEEP_SLEEP dump_field(out, "has_deep_sleep", this->has_deep_sleep); #endif #ifdef ESPHOME_PROJECT_NAME - dump_field(out, "project_name", this->project_name_ref_); + dump_field(out, "project_name", this->project_name); #endif #ifdef ESPHOME_PROJECT_NAME - dump_field(out, "project_version", this->project_version_ref_); + dump_field(out, "project_version", this->project_version); #endif #ifdef USE_WEBSERVER dump_field(out, "webserver_port", this->webserver_port); @@ -790,16 +788,16 @@ void DeviceInfoResponse::dump_to(std::string &out) const { #ifdef USE_BLUETOOTH_PROXY dump_field(out, "bluetooth_proxy_feature_flags", this->bluetooth_proxy_feature_flags); #endif - dump_field(out, "manufacturer", this->manufacturer_ref_); - dump_field(out, "friendly_name", this->friendly_name_ref_); + dump_field(out, "manufacturer", this->manufacturer); + dump_field(out, "friendly_name", this->friendly_name); #ifdef USE_VOICE_ASSISTANT dump_field(out, "voice_assistant_feature_flags", this->voice_assistant_feature_flags); #endif #ifdef USE_AREAS - dump_field(out, "suggested_area", this->suggested_area_ref_); + dump_field(out, "suggested_area", this->suggested_area); #endif #ifdef USE_BLUETOOTH_PROXY - dump_field(out, "bluetooth_mac_address", this->bluetooth_mac_address_ref_); + dump_field(out, "bluetooth_mac_address", this->bluetooth_mac_address); #endif #ifdef USE_API_NOISE dump_field(out, "api_encryption_supported", this->api_encryption_supported); @@ -836,14 +834,14 @@ void SubscribeStatesRequest::dump_to(std::string &out) const { out.append("Subsc #ifdef USE_BINARY_SENSOR void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesBinarySensorResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "name", this->name); + dump_field(out, "device_class", this->device_class); dump_field(out, "is_status_binary_sensor", this->is_status_binary_sensor); dump_field(out, "disabled_by_default", this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "entity_category", static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -863,16 +861,16 @@ void BinarySensorStateResponse::dump_to(std::string &out) const { #ifdef USE_COVER void ListEntitiesCoverResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesCoverResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "assumed_state", this->assumed_state); dump_field(out, "supports_position", this->supports_position); dump_field(out, "supports_tilt", this->supports_tilt); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); dump_field(out, "disabled_by_default", this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "entity_category", static_cast(this->entity_category)); dump_field(out, "supports_stop", this->supports_stop); @@ -906,16 +904,16 @@ void CoverCommandRequest::dump_to(std::string &out) const { #ifdef USE_FAN void ListEntitiesFanResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesFanResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "supports_oscillation", this->supports_oscillation); dump_field(out, "supports_speed", this->supports_speed); dump_field(out, "supports_direction", this->supports_direction); dump_field(out, "supported_speed_count", this->supported_speed_count); dump_field(out, "disabled_by_default", this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "entity_category", static_cast(this->entity_category)); for (const auto &it : *this->supported_preset_modes) { @@ -932,7 +930,7 @@ void FanStateResponse::dump_to(std::string &out) const { dump_field(out, "oscillating", this->oscillating); dump_field(out, "direction", static_cast(this->direction)); dump_field(out, "speed_level", this->speed_level); - dump_field(out, "preset_mode", this->preset_mode_ref_); + dump_field(out, "preset_mode", this->preset_mode); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -949,9 +947,7 @@ void FanCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_speed_level", this->has_speed_level); dump_field(out, "speed_level", this->speed_level); dump_field(out, "has_preset_mode", this->has_preset_mode); - out.append(" preset_mode: "); - out.append("'").append(this->preset_mode.c_str(), this->preset_mode.size()).append("'"); - out.append("\n"); + dump_field(out, "preset_mode", this->preset_mode); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -960,9 +956,9 @@ void FanCommandRequest::dump_to(std::string &out) const { #ifdef USE_LIGHT void ListEntitiesLightResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesLightResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); for (const auto &it : *this->supported_color_modes) { dump_field(out, "supported_color_modes", static_cast(it), 4); } @@ -973,7 +969,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { } dump_field(out, "disabled_by_default", this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "entity_category", static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -994,7 +990,7 @@ void LightStateResponse::dump_to(std::string &out) const { dump_field(out, "color_temperature", this->color_temperature); dump_field(out, "cold_white", this->cold_white); dump_field(out, "warm_white", this->warm_white); - dump_field(out, "effect", this->effect_ref_); + dump_field(out, "effect", this->effect); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -1027,9 +1023,7 @@ void LightCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_flash_length", this->has_flash_length); dump_field(out, "flash_length", this->flash_length); dump_field(out, "has_effect", this->has_effect); - out.append(" effect: "); - out.append("'").append(this->effect.c_str(), this->effect.size()).append("'"); - out.append("\n"); + dump_field(out, "effect", this->effect); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -1038,16 +1032,16 @@ void LightCommandRequest::dump_to(std::string &out) const { #ifdef USE_SENSOR void ListEntitiesSensorResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesSensorResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif - dump_field(out, "unit_of_measurement", this->unit_of_measurement_ref_); + dump_field(out, "unit_of_measurement", this->unit_of_measurement); dump_field(out, "accuracy_decimals", this->accuracy_decimals); dump_field(out, "force_update", this->force_update); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); dump_field(out, "state_class", static_cast(this->state_class)); dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); @@ -1068,16 +1062,16 @@ void SensorStateResponse::dump_to(std::string &out) const { #ifdef USE_SWITCH void ListEntitiesSwitchResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesSwitchResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "assumed_state", this->assumed_state); dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -1102,15 +1096,15 @@ void SwitchCommandRequest::dump_to(std::string &out) const { #ifdef USE_TEXT_SENSOR void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesTextSensorResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -1118,7 +1112,7 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { void TextSensorStateResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "TextSensorStateResponse"); dump_field(out, "key", this->key); - dump_field(out, "state", this->state_ref_); + dump_field(out, "state", this->state); dump_field(out, "missing_state", this->missing_state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); @@ -1144,7 +1138,10 @@ void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const { out.append(format_hex_pretty(this->key, this->key_len)); out.append("\n"); } -void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); } +void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { + MessageDumpHelper helper(out, "NoiseEncryptionSetKeyResponse"); + dump_field(out, "success", this->success); +} #endif #ifdef USE_API_HOMEASSISTANT_SERVICES void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const { @@ -1152,12 +1149,12 @@ void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const { } void HomeassistantServiceMap::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HomeassistantServiceMap"); - dump_field(out, "key", this->key_ref_); + dump_field(out, "key", this->key); dump_field(out, "value", this->value); } void HomeassistantActionRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HomeassistantActionRequest"); - dump_field(out, "service", this->service_ref_); + dump_field(out, "service", this->service); for (const auto &it : this->data) { out.append(" data: "); it.dump_to(out); @@ -1190,9 +1187,7 @@ void HomeassistantActionResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HomeassistantActionResponse"); dump_field(out, "call_id", this->call_id); dump_field(out, "success", this->success); - out.append(" error_message: "); - out.append("'").append(this->error_message.c_str(), this->error_message.size()).append("'"); - out.append("\n"); + dump_field(out, "error_message", this->error_message); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON out.append(" response_data: "); out.append(format_hex_pretty(this->response_data, this->response_data_len)); @@ -1206,40 +1201,32 @@ void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const { } void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "SubscribeHomeAssistantStateResponse"); - dump_field(out, "entity_id", this->entity_id_ref_); - dump_field(out, "attribute", this->attribute_ref_); + dump_field(out, "entity_id", this->entity_id); + dump_field(out, "attribute", this->attribute); dump_field(out, "once", this->once); } void HomeAssistantStateResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HomeAssistantStateResponse"); - out.append(" entity_id: "); - out.append("'").append(this->entity_id.c_str(), this->entity_id.size()).append("'"); - out.append("\n"); - out.append(" state: "); - out.append("'").append(this->state.c_str(), this->state.size()).append("'"); - out.append("\n"); - out.append(" attribute: "); - out.append("'").append(this->attribute.c_str(), this->attribute.size()).append("'"); - out.append("\n"); + dump_field(out, "entity_id", this->entity_id); + dump_field(out, "state", this->state); + dump_field(out, "attribute", this->attribute); } #endif void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); } void GetTimeResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "GetTimeResponse"); dump_field(out, "epoch_seconds", this->epoch_seconds); - out.append(" timezone: "); - out.append("'").append(this->timezone.c_str(), this->timezone.size()).append("'"); - out.append("\n"); + dump_field(out, "timezone", this->timezone); } #ifdef USE_API_USER_DEFINED_ACTIONS void ListEntitiesServicesArgument::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesServicesArgument"); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "type", static_cast(this->type)); } void ListEntitiesServicesResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesServicesResponse"); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "key", this->key); for (const auto &it : this->args) { out.append(" args: "); @@ -1253,9 +1240,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { dump_field(out, "bool_", this->bool_); dump_field(out, "legacy_int", this->legacy_int); dump_field(out, "float_", this->float_); - out.append(" string_: "); - out.append("'").append(this->string_.c_str(), this->string_.size()).append("'"); - out.append("\n"); + dump_field(out, "string_", this->string_); dump_field(out, "int_", this->int_); for (const auto it : this->bool_array) { dump_field(out, "bool_array", static_cast(it), 4); @@ -1291,7 +1276,7 @@ void ExecuteServiceResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ExecuteServiceResponse"); dump_field(out, "call_id", this->call_id); dump_field(out, "success", this->success); - dump_field(out, "error_message", this->error_message_ref_); + dump_field(out, "error_message", this->error_message); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON out.append(" response_data: "); out.append(format_hex_pretty(this->response_data, this->response_data_len)); @@ -1302,12 +1287,12 @@ void ExecuteServiceResponse::dump_to(std::string &out) const { #ifdef USE_CAMERA void ListEntitiesCameraResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesCameraResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "disabled_by_default", this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "entity_category", static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -1334,9 +1319,9 @@ void CameraImageRequest::dump_to(std::string &out) const { #ifdef USE_CLIMATE void ListEntitiesClimateResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesClimateResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "supports_current_temperature", this->supports_current_temperature); dump_field(out, "supports_two_point_target_temperature", this->supports_two_point_target_temperature); for (const auto &it : *this->supported_modes) { @@ -1363,7 +1348,7 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { } dump_field(out, "disabled_by_default", this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "entity_category", static_cast(this->entity_category)); dump_field(out, "visual_current_temperature_step", this->visual_current_temperature_step); @@ -1387,9 +1372,9 @@ void ClimateStateResponse::dump_to(std::string &out) const { dump_field(out, "action", static_cast(this->action)); dump_field(out, "fan_mode", static_cast(this->fan_mode)); dump_field(out, "swing_mode", static_cast(this->swing_mode)); - dump_field(out, "custom_fan_mode", this->custom_fan_mode_ref_); + dump_field(out, "custom_fan_mode", this->custom_fan_mode); dump_field(out, "preset", static_cast(this->preset)); - dump_field(out, "custom_preset", this->custom_preset_ref_); + dump_field(out, "custom_preset", this->custom_preset); dump_field(out, "current_humidity", this->current_humidity); dump_field(out, "target_humidity", this->target_humidity); #ifdef USE_DEVICES @@ -1412,15 +1397,11 @@ void ClimateCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_swing_mode", this->has_swing_mode); dump_field(out, "swing_mode", static_cast(this->swing_mode)); dump_field(out, "has_custom_fan_mode", this->has_custom_fan_mode); - out.append(" custom_fan_mode: "); - out.append("'").append(this->custom_fan_mode.c_str(), this->custom_fan_mode.size()).append("'"); - out.append("\n"); + dump_field(out, "custom_fan_mode", this->custom_fan_mode); dump_field(out, "has_preset", this->has_preset); dump_field(out, "preset", static_cast(this->preset)); dump_field(out, "has_custom_preset", this->has_custom_preset); - out.append(" custom_preset: "); - out.append("'").append(this->custom_preset.c_str(), this->custom_preset.size()).append("'"); - out.append("\n"); + dump_field(out, "custom_preset", this->custom_preset); dump_field(out, "has_target_humidity", this->has_target_humidity); dump_field(out, "target_humidity", this->target_humidity); #ifdef USE_DEVICES @@ -1431,11 +1412,11 @@ void ClimateCommandRequest::dump_to(std::string &out) const { #ifdef USE_WATER_HEATER void ListEntitiesWaterHeaterResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesWaterHeaterResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); @@ -1480,20 +1461,20 @@ void WaterHeaterCommandRequest::dump_to(std::string &out) const { #ifdef USE_NUMBER void ListEntitiesNumberResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesNumberResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "min_value", this->min_value); dump_field(out, "max_value", this->max_value); dump_field(out, "step", this->step); dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "unit_of_measurement", this->unit_of_measurement_ref_); + dump_field(out, "unit_of_measurement", this->unit_of_measurement); dump_field(out, "mode", static_cast(this->mode)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -1519,11 +1500,11 @@ void NumberCommandRequest::dump_to(std::string &out) const { #ifdef USE_SELECT void ListEntitiesSelectResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesSelectResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif for (const auto &it : *this->options) { dump_field(out, "options", it, 4); @@ -1537,7 +1518,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { void SelectStateResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "SelectStateResponse"); dump_field(out, "key", this->key); - dump_field(out, "state", this->state_ref_); + dump_field(out, "state", this->state); dump_field(out, "missing_state", this->missing_state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); @@ -1546,9 +1527,7 @@ void SelectStateResponse::dump_to(std::string &out) const { void SelectCommandRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "SelectCommandRequest"); dump_field(out, "key", this->key); - out.append(" state: "); - out.append("'").append(this->state.c_str(), this->state.size()).append("'"); - out.append("\n"); + dump_field(out, "state", this->state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -1557,11 +1536,11 @@ void SelectCommandRequest::dump_to(std::string &out) const { #ifdef USE_SIREN void ListEntitiesSirenResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesSirenResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); for (const auto &it : *this->tones) { @@ -1588,9 +1567,7 @@ void SirenCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_state", this->has_state); dump_field(out, "state", this->state); dump_field(out, "has_tone", this->has_tone); - out.append(" tone: "); - out.append("'").append(this->tone.c_str(), this->tone.size()).append("'"); - out.append("\n"); + dump_field(out, "tone", this->tone); dump_field(out, "has_duration", this->has_duration); dump_field(out, "duration", this->duration); dump_field(out, "has_volume", this->has_volume); @@ -1603,18 +1580,18 @@ void SirenCommandRequest::dump_to(std::string &out) const { #ifdef USE_LOCK void ListEntitiesLockResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesLockResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); dump_field(out, "assumed_state", this->assumed_state); dump_field(out, "supports_open", this->supports_open); dump_field(out, "requires_code", this->requires_code); - dump_field(out, "code_format", this->code_format_ref_); + dump_field(out, "code_format", this->code_format); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -1632,9 +1609,7 @@ void LockCommandRequest::dump_to(std::string &out) const { dump_field(out, "key", this->key); dump_field(out, "command", static_cast(this->command)); dump_field(out, "has_code", this->has_code); - out.append(" code: "); - out.append("'").append(this->code.c_str(), this->code.size()).append("'"); - out.append("\n"); + dump_field(out, "code", this->code); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -1643,15 +1618,15 @@ void LockCommandRequest::dump_to(std::string &out) const { #ifdef USE_BUTTON void ListEntitiesButtonResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesButtonResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -1667,7 +1642,7 @@ void ButtonCommandRequest::dump_to(std::string &out) const { #ifdef USE_MEDIA_PLAYER void MediaPlayerSupportedFormat::dump_to(std::string &out) const { MessageDumpHelper helper(out, "MediaPlayerSupportedFormat"); - dump_field(out, "format", this->format_ref_); + dump_field(out, "format", this->format); dump_field(out, "sample_rate", this->sample_rate); dump_field(out, "num_channels", this->num_channels); dump_field(out, "purpose", static_cast(this->purpose)); @@ -1675,11 +1650,11 @@ void MediaPlayerSupportedFormat::dump_to(std::string &out) const { } void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesMediaPlayerResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); @@ -1712,9 +1687,7 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_volume", this->has_volume); dump_field(out, "volume", this->volume); dump_field(out, "has_media_url", this->has_media_url); - out.append(" media_url: "); - out.append("'").append(this->media_url.c_str(), this->media_url.size()).append("'"); - out.append("\n"); + dump_field(out, "media_url", this->media_url); dump_field(out, "has_announcement", this->has_announcement); dump_field(out, "announcement", this->announcement); #ifdef USE_DEVICES @@ -1758,7 +1731,10 @@ void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const { dump_field(out, "mtu", this->mtu); dump_field(out, "error", this->error); } -void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const { dump_field(out, "address", this->address); } +void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const { + MessageDumpHelper helper(out, "BluetoothGATTGetServicesRequest"); + dump_field(out, "address", this->address); +} void BluetoothGATTDescriptor::dump_to(std::string &out) const { MessageDumpHelper helper(out, "BluetoothGATTDescriptor"); for (const auto &it : this->uuid) { @@ -1930,12 +1906,12 @@ void VoiceAssistantAudioSettings::dump_to(std::string &out) const { void VoiceAssistantRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantRequest"); dump_field(out, "start", this->start); - dump_field(out, "conversation_id", this->conversation_id_ref_); + dump_field(out, "conversation_id", this->conversation_id); dump_field(out, "flags", this->flags); out.append(" audio_settings: "); this->audio_settings.dump_to(out); out.append("\n"); - dump_field(out, "wake_word_phrase", this->wake_word_phrase_ref_); + dump_field(out, "wake_word_phrase", this->wake_word_phrase); } void VoiceAssistantResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantResponse"); @@ -1944,12 +1920,8 @@ void VoiceAssistantResponse::dump_to(std::string &out) const { } void VoiceAssistantEventData::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantEventData"); - out.append(" name: "); - out.append("'").append(this->name.c_str(), this->name.size()).append("'"); - out.append("\n"); - out.append(" value: "); - out.append("'").append(this->value.c_str(), this->value.size()).append("'"); - out.append("\n"); + dump_field(out, "name", this->name); + dump_field(out, "value", this->value); } void VoiceAssistantEventResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantEventResponse"); @@ -1970,59 +1942,42 @@ void VoiceAssistantAudio::dump_to(std::string &out) const { void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantTimerEventResponse"); dump_field(out, "event_type", static_cast(this->event_type)); - out.append(" timer_id: "); - out.append("'").append(this->timer_id.c_str(), this->timer_id.size()).append("'"); - out.append("\n"); - out.append(" name: "); - out.append("'").append(this->name.c_str(), this->name.size()).append("'"); - out.append("\n"); + dump_field(out, "timer_id", this->timer_id); + dump_field(out, "name", this->name); dump_field(out, "total_seconds", this->total_seconds); dump_field(out, "seconds_left", this->seconds_left); dump_field(out, "is_active", this->is_active); } void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantAnnounceRequest"); - out.append(" media_id: "); - out.append("'").append(this->media_id.c_str(), this->media_id.size()).append("'"); - out.append("\n"); - out.append(" text: "); - out.append("'").append(this->text.c_str(), this->text.size()).append("'"); - out.append("\n"); - out.append(" preannounce_media_id: "); - out.append("'").append(this->preannounce_media_id.c_str(), this->preannounce_media_id.size()).append("'"); - out.append("\n"); + dump_field(out, "media_id", this->media_id); + dump_field(out, "text", this->text); + dump_field(out, "preannounce_media_id", this->preannounce_media_id); dump_field(out, "start_conversation", this->start_conversation); } -void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { dump_field(out, "success", this->success); } +void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { + MessageDumpHelper helper(out, "VoiceAssistantAnnounceFinished"); + dump_field(out, "success", this->success); +} void VoiceAssistantWakeWord::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantWakeWord"); - dump_field(out, "id", this->id_ref_); - dump_field(out, "wake_word", this->wake_word_ref_); + dump_field(out, "id", this->id); + dump_field(out, "wake_word", this->wake_word); for (const auto &it : this->trained_languages) { dump_field(out, "trained_languages", it, 4); } } void VoiceAssistantExternalWakeWord::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantExternalWakeWord"); - out.append(" id: "); - out.append("'").append(this->id.c_str(), this->id.size()).append("'"); - out.append("\n"); - out.append(" wake_word: "); - out.append("'").append(this->wake_word.c_str(), this->wake_word.size()).append("'"); - out.append("\n"); + dump_field(out, "id", this->id); + dump_field(out, "wake_word", this->wake_word); for (const auto &it : this->trained_languages) { dump_field(out, "trained_languages", it, 4); } - out.append(" model_type: "); - out.append("'").append(this->model_type.c_str(), this->model_type.size()).append("'"); - out.append("\n"); + dump_field(out, "model_type", this->model_type); dump_field(out, "model_size", this->model_size); - out.append(" model_hash: "); - out.append("'").append(this->model_hash.c_str(), this->model_hash.size()).append("'"); - out.append("\n"); - out.append(" url: "); - out.append("'").append(this->url.c_str(), this->url.size()).append("'"); - out.append("\n"); + dump_field(out, "model_hash", this->model_hash); + dump_field(out, "url", this->url); } void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest"); @@ -2054,11 +2009,11 @@ void VoiceAssistantSetConfiguration::dump_to(std::string &out) const { #ifdef USE_ALARM_CONTROL_PANEL void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesAlarmControlPanelResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); @@ -2081,9 +2036,7 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "AlarmControlPanelCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "command", static_cast(this->command)); - out.append(" code: "); - out.append("'").append(this->code.c_str(), this->code.size()).append("'"); - out.append("\n"); + dump_field(out, "code", this->code); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -2092,17 +2045,17 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const { #ifdef USE_TEXT void ListEntitiesTextResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesTextResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); dump_field(out, "min_length", this->min_length); dump_field(out, "max_length", this->max_length); - dump_field(out, "pattern", this->pattern_ref_); + dump_field(out, "pattern", this->pattern); dump_field(out, "mode", static_cast(this->mode)); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); @@ -2111,7 +2064,7 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const { void TextStateResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "TextStateResponse"); dump_field(out, "key", this->key); - dump_field(out, "state", this->state_ref_); + dump_field(out, "state", this->state); dump_field(out, "missing_state", this->missing_state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); @@ -2120,9 +2073,7 @@ void TextStateResponse::dump_to(std::string &out) const { void TextCommandRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "TextCommandRequest"); dump_field(out, "key", this->key); - out.append(" state: "); - out.append("'").append(this->state.c_str(), this->state.size()).append("'"); - out.append("\n"); + dump_field(out, "state", this->state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -2131,11 +2082,11 @@ void TextCommandRequest::dump_to(std::string &out) const { #ifdef USE_DATETIME_DATE void ListEntitiesDateResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesDateResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); @@ -2168,11 +2119,11 @@ void DateCommandRequest::dump_to(std::string &out) const { #ifdef USE_DATETIME_TIME void ListEntitiesTimeResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesTimeResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); @@ -2205,15 +2156,15 @@ void TimeCommandRequest::dump_to(std::string &out) const { #ifdef USE_EVENT void ListEntitiesEventResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesEventResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); for (const auto &it : *this->event_types) { dump_field(out, "event_types", it, 4); } @@ -2224,7 +2175,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const { void EventResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "EventResponse"); dump_field(out, "key", this->key); - dump_field(out, "event_type", this->event_type_ref_); + dump_field(out, "event_type", this->event_type); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -2233,15 +2184,15 @@ void EventResponse::dump_to(std::string &out) const { #ifdef USE_VALVE void ListEntitiesValveResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesValveResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); dump_field(out, "assumed_state", this->assumed_state); dump_field(out, "supports_position", this->supports_position); dump_field(out, "supports_stop", this->supports_stop); @@ -2272,11 +2223,11 @@ void ValveCommandRequest::dump_to(std::string &out) const { #ifdef USE_DATETIME_DATETIME void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesDateTimeResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); @@ -2305,15 +2256,15 @@ void DateTimeCommandRequest::dump_to(std::string &out) const { #ifdef USE_UPDATE void ListEntitiesUpdateResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "ListEntitiesUpdateResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -2325,11 +2276,11 @@ void UpdateStateResponse::dump_to(std::string &out) const { dump_field(out, "in_progress", this->in_progress); dump_field(out, "has_progress", this->has_progress); dump_field(out, "progress", this->progress); - dump_field(out, "current_version", this->current_version_ref_); - dump_field(out, "latest_version", this->latest_version_ref_); - dump_field(out, "title", this->title_ref_); - dump_field(out, "release_summary", this->release_summary_ref_); - dump_field(out, "release_url", this->release_url_ref_); + dump_field(out, "current_version", this->current_version); + dump_field(out, "latest_version", this->latest_version); + dump_field(out, "title", this->title); + dump_field(out, "release_summary", this->release_summary); + dump_field(out, "release_url", this->release_url); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index 7f655a2479..b16164270b 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -240,7 +240,7 @@ class CustomAPIDevice { */ void call_homeassistant_service(const std::string &service_name) { HomeassistantActionRequest resp; - resp.set_service(StringRef(service_name)); + resp.service = StringRef(service_name); global_api_server->send_homeassistant_action(resp); } @@ -260,12 +260,12 @@ class CustomAPIDevice { */ void call_homeassistant_service(const std::string &service_name, const std::map &data) { HomeassistantActionRequest resp; - resp.set_service(StringRef(service_name)); + resp.service = StringRef(service_name); resp.data.init(data.size()); for (auto &it : data) { auto &kv = resp.data.emplace_back(); - kv.set_key(StringRef(it.first)); - kv.value = it.second; + kv.key = StringRef(it.first); + kv.value = it.second; // value is std::string (no_zero_copy), assign directly } global_api_server->send_homeassistant_action(resp); } @@ -282,7 +282,7 @@ class CustomAPIDevice { */ void fire_homeassistant_event(const std::string &event_name) { HomeassistantActionRequest resp; - resp.set_service(StringRef(event_name)); + resp.service = StringRef(event_name); resp.is_event = true; global_api_server->send_homeassistant_action(resp); } @@ -302,13 +302,13 @@ class CustomAPIDevice { */ void fire_homeassistant_event(const std::string &service_name, const std::map &data) { HomeassistantActionRequest resp; - resp.set_service(StringRef(service_name)); + resp.service = StringRef(service_name); resp.is_event = true; resp.data.init(data.size()); for (auto &it : data) { auto &kv = resp.data.emplace_back(); - kv.set_key(StringRef(it.first)); - kv.value = it.second; + kv.key = StringRef(it.first); + kv.value = it.second; // value is std::string (no_zero_copy), assign directly } global_api_server->send_homeassistant_action(resp); } diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 1fdcc51803..a17c99b8ba 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -147,7 +147,7 @@ template class HomeAssistantServiceCallAction : public Actionservice_.value(x...); - resp.set_service(StringRef(service_value)); + resp.service = StringRef(service_value); resp.is_event = this->flags_.is_event; this->populate_service_map(resp.data, this->data_, x...); this->populate_service_map(resp.data_template, this->data_template_, x...); @@ -209,7 +209,7 @@ template class HomeAssistantServiceCallAction : public Actionsend_message(msg); // temp is valid during encoding * * Unsafe Patterns (WILL cause crashes/corruption): - * 1. Temporaries: msg.set_field(StringRef(obj.get_string())) // get_string() returns by value - * 2. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary + * 1. Temporaries: msg.field = StringRef(obj.get_string()) // get_string() returns by value + * 2. Concatenation: msg.field = StringRef(str1 + str2) // Result is temporary * * For unsafe patterns, store in a local variable first: * std::string temp = get_string(); // or str1 + str2 - * msg.set_field(StringRef(temp)); + * msg.field = StringRef(temp); * * The send_*_response pattern ensures proper lifetime management by encoding * within the same function scope where temporaries are created. diff --git a/esphome/components/api/user_services.h b/esphome/components/api/user_services.h index 8e3a61b279..85fba2a435 100644 --- a/esphome/components/api/user_services.h +++ b/esphome/components/api/user_services.h @@ -46,7 +46,7 @@ template class UserServiceBase : public UserServiceDescriptor { ListEntitiesServicesResponse encode_list_service_response() override { ListEntitiesServicesResponse msg; - msg.set_name(StringRef(this->name_)); + msg.name = StringRef(this->name_); msg.key = this->key_; msg.supports_response = this->supports_response_; std::array arg_types = {to_service_arg_type()...}; @@ -54,7 +54,7 @@ template class UserServiceBase : public UserServiceDescriptor { for (size_t i = 0; i < sizeof...(Ts); i++) { auto &arg = msg.args.emplace_back(); arg.type = arg_types[i]; - arg.set_name(StringRef(this->arg_names_[i])); + arg.name = StringRef(this->arg_names_[i]); } return msg; } @@ -108,7 +108,7 @@ template class UserServiceDynamic : public UserServiceDescriptor ListEntitiesServicesResponse encode_list_service_response() override { ListEntitiesServicesResponse msg; - msg.set_name(StringRef(this->name_)); + msg.name = StringRef(this->name_); msg.key = this->key_; msg.supports_response = enums::SUPPORTS_RESPONSE_NONE; // Dynamic services don't support responses yet std::array arg_types = {to_service_arg_type()...}; @@ -116,7 +116,7 @@ template class UserServiceDynamic : public UserServiceDescriptor for (size_t i = 0; i < sizeof...(Ts); i++) { auto &arg = msg.args.emplace_back(); arg.type = arg_types[i]; - arg.set_name(StringRef(this->arg_names_[i])); + arg.name = StringRef(this->arg_names_[i]); } return msg; } diff --git a/esphome/components/homeassistant/number/homeassistant_number.cpp b/esphome/components/homeassistant/number/homeassistant_number.cpp index 8c0d415c23..82387a81e9 100644 --- a/esphome/components/homeassistant/number/homeassistant_number.cpp +++ b/esphome/components/homeassistant/number/homeassistant_number.cpp @@ -86,15 +86,15 @@ void HomeassistantNumber::control(float value) { static constexpr auto VALUE_KEY = StringRef::from_lit("value"); api::HomeassistantActionRequest resp; - resp.set_service(SERVICE_NAME); + resp.service = SERVICE_NAME; resp.data.init(2); auto &entity_id = resp.data.emplace_back(); - entity_id.set_key(ENTITY_ID_KEY); + entity_id.key = ENTITY_ID_KEY; entity_id.value = this->entity_id_; auto &entity_value = resp.data.emplace_back(); - entity_value.set_key(VALUE_KEY); + entity_value.key = VALUE_KEY; entity_value.value = to_string(value); api::global_api_server->send_homeassistant_action(resp); diff --git a/esphome/components/homeassistant/switch/homeassistant_switch.cpp b/esphome/components/homeassistant/switch/homeassistant_switch.cpp index d08d761442..79d17eb290 100644 --- a/esphome/components/homeassistant/switch/homeassistant_switch.cpp +++ b/esphome/components/homeassistant/switch/homeassistant_switch.cpp @@ -47,14 +47,14 @@ void HomeassistantSwitch::write_state(bool state) { api::HomeassistantActionRequest resp; if (state) { - resp.set_service(SERVICE_ON); + resp.service = SERVICE_ON; } else { - resp.set_service(SERVICE_OFF); + resp.service = SERVICE_OFF; } resp.data.init(1); auto &entity_id_kv = resp.data.emplace_back(); - entity_id_kv.set_key(ENTITY_ID_KEY); + entity_id_kv.key = ENTITY_ID_KEY; entity_id_kv.value = this->entity_id_; api::global_api_server->send_homeassistant_action(resp); diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 0e0616c508..e2516d5fb8 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -239,10 +239,10 @@ void VoiceAssistant::loop() { api::VoiceAssistantRequest msg; msg.start = true; - msg.set_conversation_id(StringRef(this->conversation_id_)); + msg.conversation_id = StringRef(this->conversation_id_); msg.flags = flags; msg.audio_settings = audio_settings; - msg.set_wake_word_phrase(StringRef(this->wake_word_)); + msg.wake_word_phrase = StringRef(this->wake_word_); // Reset media player state tracking #ifdef USE_MEDIA_PLAYER diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 7293f2abbc..274a672c7c 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -373,13 +373,11 @@ def create_field_type_info( return BytesType(field, needs_decode, needs_encode) - # Special handling for string fields + # Special handling for string fields - use StringRef for zero-copy unless no_zero_copy is set if field.type == 9: - # For SOURCE_CLIENT only messages (decode but no encode), use StringRef - # for zero-copy access to the receive buffer - if needs_decode and not needs_encode: - return PointerToStringBufferType(field, None) - return StringType(field, needs_decode, needs_encode) + if get_field_opt(field, pb.no_zero_copy, False): + return StringType(field, needs_decode, needs_encode) + return PointerToStringBufferType(field, None) validate_field_type(field.type, field.name) return TYPE_INFO[field.type](field) @@ -915,6 +913,10 @@ class PointerToStringBufferType(PointerToBufferTypeBase): reference_type = "StringRef &" const_reference_type = "const StringRef &" + @classmethod + def can_use_dump_field(cls) -> bool: + return True + @property def public_content(self) -> list[str]: return [f"StringRef {self.field_name}{{}};"] @@ -931,19 +933,19 @@ class PointerToStringBufferType(PointerToBufferTypeBase): }}""" def dump(self, name: str) -> str: - return f'out.append("\'").append(this->{self.field_name}.c_str(), this->{self.field_name}.size()).append("\'");' + # Not used since we use dump_field, but required by abstract base class + return f'out.append("\'").append({name}.c_str(), {name}.size()).append("\'");' @property def dump_content(self) -> str: - return ( - f'out.append(" {self.name}: ");\n' - + f"{self.dump(self.field_name)}\n" - + 'out.append("\\n");' - ) + return f'dump_field(out, "{self.name}", this->{self.field_name});' def get_size_calculation(self, name: str, force: bool = False) -> str: return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}.size());" + def get_estimated_size(self) -> int: + return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string + class FixedArrayBytesType(TypeInfo): """Special type for fixed-size byte arrays.""" @@ -2149,12 +2151,10 @@ def build_message_type( # dump_to implementation will go in dump_cpp dump_impl = f"void {desc.name}::dump_to(std::string &out) const {{" if dump: - if len(dump) == 1 and len(dump[0]) + len(dump_impl) + 3 < 120: - dump_impl += f" {dump[0]} " - else: - dump_impl += "\n" - dump_impl += f' MessageDumpHelper helper(out, "{desc.name}");\n' - dump_impl += indent("\n".join(dump)) + "\n" + # Always use MessageDumpHelper for consistent output formatting + dump_impl += "\n" + dump_impl += f' MessageDumpHelper helper(out, "{desc.name}");\n' + dump_impl += indent("\n".join(dump)) + "\n" else: o2 = f'out.append("{desc.name} {{}}");' if len(dump_impl) + len(o2) + 3 < 120: From 8464307a43f07eb1fa5fc95ac01bd8c4fc69e2b1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:23:50 -1000 Subject: [PATCH 1123/1145] [api] Coalesce log packets to reduce buffer pressure and prevent dropped state updates (#13026) --- esphome/components/api/api_connection.cpp | 22 ++++++++++++++++++- esphome/components/api/api_frame_helper.h | 26 ++++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 649ed31283..fb3548d117 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1819,10 +1819,30 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { return false; } bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { - if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse + const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE); + + if (!this->try_to_clear_buffer(!is_log_message)) { return false; } + // Toggle Nagle's algorithm based on message type to prevent log messages from + // filling the TCP send buffer and crowding out important state updates. + // + // This honors the `no_delay` proto option - SubscribeLogsResponse is the only + // message with `option (no_delay) = false;` in api.proto, indicating it should + // allow Nagle coalescing. This option existed since 2019 but was never implemented. + // + // - Log messages: Enable Nagle (NODELAY=false) so small log packets coalesce + // into fewer, larger packets. They flush naturally via TCP delayed ACK timer + // (~200ms), buffer filling, or when a state update triggers a flush. + // + // - All other messages (state updates, responses): Disable Nagle (NODELAY=true) + // for immediate delivery. These are time-sensitive and should not be delayed. + // + // This must be done proactively BEFORE the buffer fills up - checking buffer + // state here would be too late since we'd already be in a degraded state. + this->helper_->set_nodelay(!is_log_message); + APIError err = this->helper_->write_protobuf_packet(message_type, buffer); if (err == APIError::WOULD_BLOCK) return false; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 76a93d094e..27ec1ff915 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -120,6 +120,27 @@ class APIFrameHelper { } return APIError::OK; } + /// Toggle TCP_NODELAY socket option to control Nagle's algorithm. + /// + /// This is used to allow log messages to coalesce (Nagle enabled) while keeping + /// state updates low-latency (NODELAY enabled). Without this, many small log + /// packets fill the TCP send buffer, crowding out important state updates. + /// + /// State is tracked to minimize setsockopt() overhead - on lwip_raw (ESP8266/RP2040) + /// this is just a boolean assignment; on other platforms it's a lightweight syscall. + /// + /// @param enable true to enable NODELAY (disable Nagle), false to enable Nagle + /// @return true if successful or already in desired state + bool set_nodelay(bool enable) { + if (this->nodelay_enabled_ == enable) + return true; + int val = enable ? 1 : 0; + int err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int)); + if (err == 0) { + this->nodelay_enabled_ = enable; + } + return err == 0; + } virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0; // Write multiple protobuf messages in a single operation // messages contains (message_type, offset, length) for each message in the buffer @@ -208,7 +229,10 @@ class APIFrameHelper { uint8_t tx_buf_head_{0}; uint8_t tx_buf_tail_{0}; uint8_t tx_buf_count_{0}; - // 8 bytes total, 0 bytes padding + // Tracks TCP_NODELAY state to minimize setsockopt() calls. Initialized to true + // since init_common_() enables NODELAY. Used by set_nodelay() to allow log + // messages to coalesce while keeping state updates low-latency. + bool nodelay_enabled_{true}; // Common initialization for both plaintext and noise protocols APIError init_common_(); From 20927674da69d21148d9a8ee72a995e93a34c2ff Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:24:09 -1000 Subject: [PATCH 1124/1145] [sun] Eliminate heap allocation in text sensor (#13037) --- .../sun/text_sensor/sun_text_sensor.h | 4 +- esphome/core/time.cpp | 23 ++-- esphome/core/time.h | 16 ++- tests/integration/fixtures/strftime_to.yaml | 53 +++++++++ tests/integration/test_strftime_to.py | 111 ++++++++++++++++++ 5 files changed, 197 insertions(+), 10 deletions(-) create mode 100644 tests/integration/fixtures/strftime_to.yaml create mode 100644 tests/integration/test_strftime_to.py diff --git a/esphome/components/sun/text_sensor/sun_text_sensor.h b/esphome/components/sun/text_sensor/sun_text_sensor.h index ce7d21fb86..9345a32223 100644 --- a/esphome/components/sun/text_sensor/sun_text_sensor.h +++ b/esphome/components/sun/text_sensor/sun_text_sensor.h @@ -28,7 +28,9 @@ class SunTextSensor : public text_sensor::TextSensor, public PollingComponent { return; } - this->publish_state(res->strftime(this->format_)); + char buf[ESPTime::STRFTIME_BUFFER_SIZE]; + size_t len = res->strftime_to(buf, this->format_.c_str()); + this->publish_state(buf, len); } void dump_config() override; diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index d30dac4394..4047033f84 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -1,6 +1,7 @@ #include "time.h" // NOLINT #include "helpers.h" +#include #include namespace esphome { @@ -17,6 +18,18 @@ size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) { return ::strftime(buffer, buffer_len, format, &c_tm); } +size_t ESPTime::strftime_to(std::span buffer, const char *format) { + struct tm c_tm = this->to_c_tm(); + size_t len = ::strftime(buffer.data(), buffer.size(), format, &c_tm); + if (len > 0) { + return len; + } + // Write "ERROR" to buffer on failure for consistent behavior + constexpr char error_str[] = "ERROR"; + std::copy_n(error_str, sizeof(error_str), buffer.data()); + return sizeof(error_str) - 1; // Length excluding null terminator +} + ESPTime ESPTime::from_c_tm(struct tm *c_tm, time_t c_time) { ESPTime res{}; res.second = uint8_t(c_tm->tm_sec); @@ -47,13 +60,9 @@ struct tm ESPTime::to_c_tm() { } std::string ESPTime::strftime(const char *format) { - struct tm c_tm = this->to_c_tm(); - char buf[128]; - size_t len = ::strftime(buf, sizeof(buf), format, &c_tm); - if (len > 0) { - return std::string(buf, len); - } - return "ERROR"; + char buf[STRFTIME_BUFFER_SIZE]; + size_t len = this->strftime_to(buf, format); + return std::string(buf, len); } std::string ESPTime::strftime(const std::string &format) { return this->strftime(format.c_str()); } diff --git a/esphome/core/time.h b/esphome/core/time.h index 68826dabdc..f6f1d57dbb 100644 --- a/esphome/core/time.h +++ b/esphome/core/time.h @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace esphome { @@ -13,6 +14,9 @@ uint8_t days_in_month(uint8_t month, uint16_t year); /// A more user-friendly version of struct tm from time.h struct ESPTime { + /// Buffer size required for strftime output + static constexpr size_t STRFTIME_BUFFER_SIZE = 128; + /** seconds after the minute [0-60] * @note second is generally 0-59; the extra range is to accommodate leap seconds. */ @@ -43,14 +47,22 @@ struct ESPTime { */ size_t strftime(char *buffer, size_t buffer_len, const char *format); + /** Format time into a fixed-size buffer, returns length written. + * + * This is the preferred method for avoiding heap allocations. The buffer size is enforced at compile-time. + * On format error, writes "ERROR" to the buffer and returns 5. + * @see https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime + */ + size_t strftime_to(std::span buffer, const char *format); + /** Convert this ESPTime struct to a string as specified by the format argument. * @see https://en.cppreference.com/w/c/chrono/strftime * * @warning This method returns a dynamically allocated string which can cause heap fragmentation with some - * microcontrollers. + * microcontrollers. Prefer strftime_to() for heap-free formatting. * * @warning This method can return "ERROR" when the underlying strftime() call fails or when the - * output exceeds 128 bytes. + * output exceeds STRFTIME_BUFFER_SIZE bytes. */ std::string strftime(const std::string &format); diff --git a/tests/integration/fixtures/strftime_to.yaml b/tests/integration/fixtures/strftime_to.yaml new file mode 100644 index 0000000000..bd157e110c --- /dev/null +++ b/tests/integration/fixtures/strftime_to.yaml @@ -0,0 +1,53 @@ +esphome: + name: strftime-to-test +host: +api: +logger: + +time: + - platform: homeassistant + id: ha_time + +text_sensor: + # Test strftime_to with a valid format + - platform: template + name: "Time Format Test" + id: time_format_test + update_interval: 100ms + lambda: |- + auto now = ESPTime::from_epoch_local(1704067200); // 2024-01-01 00:00:00 UTC + char buf[ESPTime::STRFTIME_BUFFER_SIZE]; + size_t len = now.strftime_to(buf, "%Y-%m-%d %H:%M:%S"); + return std::string(buf, len); + + # Test strftime_to with a short format + - platform: template + name: "Time Short Format" + id: time_short_format + update_interval: 100ms + lambda: |- + auto now = ESPTime::from_epoch_local(1704067200); + char buf[ESPTime::STRFTIME_BUFFER_SIZE]; + size_t len = now.strftime_to(buf, "%H:%M"); + return std::string(buf, len); + + # Test strftime (std::string version) still works + - platform: template + name: "Time String Format" + id: time_string_format + update_interval: 100ms + lambda: |- + auto now = ESPTime::from_epoch_local(1704067200); + return now.strftime("%Y-%m-%d"); + + # Test strftime_to with empty/invalid format returns ERROR + - platform: template + name: "Time Error Format" + id: time_error_format + update_interval: 100ms + lambda: |- + auto now = ESPTime::from_epoch_local(1704067200); + char buf[ESPTime::STRFTIME_BUFFER_SIZE]; + // Empty format string causes strftime to return 0 + size_t len = now.strftime_to(buf, ""); + return std::string(buf, len); diff --git a/tests/integration/test_strftime_to.py b/tests/integration/test_strftime_to.py new file mode 100644 index 0000000000..9220da148b --- /dev/null +++ b/tests/integration/test_strftime_to.py @@ -0,0 +1,111 @@ +"""Integration test for ESPTime::strftime_to() method.""" + +from __future__ import annotations + +import asyncio + +from aioesphomeapi import EntityState, TextSensorState +import pytest + +from .state_utils import InitialStateHelper, require_entity +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_strftime_to( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test ESPTime::strftime_to() formats time correctly.""" + async with run_compiled(yaml_config), api_client_connected() as client: + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "strftime-to-test" + + # Get entities + entities, _ = await client.list_entities_services() + + # Find our text sensors + format_test = require_entity( + entities, "time_format_test", description="Time Format Test sensor" + ) + short_format = require_entity( + entities, "time_short_format", description="Time Short Format sensor" + ) + string_format = require_entity( + entities, "time_string_format", description="Time String Format sensor" + ) + error_format = require_entity( + entities, "time_error_format", description="Time Error Format sensor" + ) + + # Set up state tracking with InitialStateHelper + loop = asyncio.get_running_loop() + states: dict[int, TextSensorState] = {} + all_received = loop.create_future() + expected_keys = { + format_test.key, + short_format.key, + string_format.key, + error_format.key, + } + initial_state_helper = InitialStateHelper(entities) + + def on_state(state: EntityState) -> None: + if isinstance(state, TextSensorState) and not state.missing_state: + states[state.key] = state + if expected_keys <= states.keys() and not all_received.done(): + all_received.set_result(True) + + # Subscribe with the wrapper that filters initial states + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + # Wait for initial states to be broadcast + try: + await initial_state_helper.wait_for_initial_states() + except TimeoutError: + pytest.fail("Timeout waiting for initial states") + + # Wait for all expected states + try: + await asyncio.wait_for(all_received, timeout=5.0) + except TimeoutError: + pytest.fail( + f"Timeout waiting for text sensor states. Got: {list(states.keys())}" + ) + + # Validate strftime_to with full format + # Note: The exact output depends on timezone, but should contain date components + format_test_state = states[format_test.key].state + assert "2024" in format_test_state or "2023" in format_test_state, ( + f"Expected year in format test output, got: {format_test_state}" + ) + # Should have format like "YYYY-MM-DD HH:MM:SS" + assert len(format_test_state) == 19, ( + f"Expected 19 chars for datetime format, got {len(format_test_state)}: {format_test_state}" + ) + + # Validate short format (HH:MM) + short_format_state = states[short_format.key].state + assert len(short_format_state) == 5, ( + f"Expected 5 chars for HH:MM format, got {len(short_format_state)}: {short_format_state}" + ) + assert ":" in short_format_state, ( + f"Expected colon in HH:MM format, got: {short_format_state}" + ) + + # Validate string format (the std::string returning version) + string_format_state = states[string_format.key].state + assert len(string_format_state) == 10, ( + f"Expected 10 chars for YYYY-MM-DD format, got {len(string_format_state)}: {string_format_state}" + ) + assert string_format_state.count("-") == 2, ( + f"Expected two dashes in YYYY-MM-DD format, got: {string_format_state}" + ) + + # Validate error format returns "ERROR" + error_format_state = states[error_format.key].state + assert error_format_state == "ERROR", ( + f"Expected 'ERROR' for empty format string, got: {error_format_state}" + ) From 8e40a55d5d2f20e468099a8545431609a1ca5aaf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:24:22 -1000 Subject: [PATCH 1125/1145] [ble_client] Eliminate heap allocations in text sensor (#13038) --- .../ble_client/text_sensor/automation.h | 2 +- .../text_sensor/ble_text_sensor.cpp | 19 ++++++------------- .../ble_client/text_sensor/ble_text_sensor.h | 1 - 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/esphome/components/ble_client/text_sensor/automation.h b/esphome/components/ble_client/text_sensor/automation.h index f7b077926b..d4114cd1ba 100644 --- a/esphome/components/ble_client/text_sensor/automation.h +++ b/esphome/components/ble_client/text_sensor/automation.h @@ -21,7 +21,7 @@ class BLETextSensorNotifyTrigger : public Trigger, public BLETextSe if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() || param->notify.handle != this->sensor_->handle) break; - this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len)); + this->trigger(std::string(reinterpret_cast(param->notify.value), param->notify.value_len)); } default: break; diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index 53c9a9d10e..cacf1b4835 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -11,8 +11,6 @@ namespace esphome::ble_client { static const char *const TAG = "ble_text_sensor"; -static const std::string EMPTY = ""; - void BLETextSensor::loop() { // Parent BLEClientNode has a loop() method, but this component uses // polling via update() and BLE callbacks so loop isn't needed @@ -47,7 +45,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } case ESP_GATTC_CLOSE_EVT: { this->status_set_warning(); - this->publish_state(EMPTY); + this->publish_state(""); break; } case ESP_GATTC_SEARCH_CMPL_EVT: { @@ -55,7 +53,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { this->status_set_warning(); - this->publish_state(EMPTY); + this->publish_state(""); char service_buf[esp32_ble::UUID_STR_LEN]; char char_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf), @@ -67,7 +65,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ auto *descr = chr->get_descriptor(this->descr_uuid_); if (descr == nullptr) { this->status_set_warning(); - this->publish_state(EMPTY); + this->publish_state(""); char service_buf[esp32_ble::UUID_STR_LEN]; char char_buf[esp32_ble::UUID_STR_LEN]; char descr_buf[esp32_ble::UUID_STR_LEN]; @@ -99,7 +97,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } this->status_clear_warning(); - this->publish_state(this->parse_data(param->read.value, param->read.value_len)); + this->publish_state(reinterpret_cast(param->read.value), param->read.value_len); } break; } @@ -108,7 +106,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); - this->publish_state(this->parse_data(param->notify.value, param->notify.value_len)); + this->publish_state(reinterpret_cast(param->notify.value), param->notify.value_len); break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { @@ -121,11 +119,6 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } } -std::string BLETextSensor::parse_data(uint8_t *value, uint16_t value_len) { - std::string text(value, value + value_len); - return text; -} - void BLETextSensor::update() { if (this->node_state != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str()); @@ -140,7 +133,7 @@ void BLETextSensor::update() { ESP_GATT_AUTH_REQ_NONE); if (status) { this->status_set_warning(); - this->publish_state(EMPTY); + this->publish_state(""); ESP_LOGW(TAG, "[%s] Error sending read request for sensor, status=%d", this->get_name().c_str(), status); } } diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.h b/esphome/components/ble_client/text_sensor/ble_text_sensor.h index 3fbd64389c..b4374e4016 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.h +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.h @@ -29,7 +29,6 @@ class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, p void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } void set_enable_notify(bool notify) { this->notify_ = notify; } - std::string parse_data(uint8_t *value, uint16_t value_len); uint16_t handle; protected: From 39526e5360b8553de94b8ff3a729a7ee3874e109 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:24:44 -1000 Subject: [PATCH 1126/1145] [analyze-memory] Add RAM symbol analysis by component (#13040) --- esphome/__main__.py | 1 + esphome/analyze_memory/__init__.py | 287 +++++++++++++++++++++++++++- esphome/analyze_memory/cli.py | 227 +++++++++++++++++----- esphome/analyze_memory/const.py | 4 +- esphome/analyze_memory/toolchain.py | 36 ++++ esphome/platformio_api.py | 5 + tests/unit_tests/test_main.py | 2 + 7 files changed, 514 insertions(+), 48 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 3822af0330..73fdef6735 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -1017,6 +1017,7 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int: idedata.objdump_path, idedata.readelf_path, external_components, + idedata=idedata, ) analyzer.analyze() diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index 9632a68913..9c935c78fa 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -22,6 +22,7 @@ from .helpers import ( map_section_name, parse_symbol_line, ) +from .toolchain import find_tool, run_tool if TYPE_CHECKING: from esphome.platformio_api import IDEData @@ -53,6 +54,9 @@ _NAMESPACE_STD = "std::" # Type alias for symbol information: (symbol_name, size, component) SymbolInfoType = tuple[str, int, str] +# RAM sections - symbols in these sections consume RAM +RAM_SECTIONS = frozenset([".data", ".bss"]) + @dataclass class MemorySection: @@ -60,7 +64,20 @@ class MemorySection: name: str symbols: list[SymbolInfoType] = field(default_factory=list) - total_size: int = 0 + total_size: int = 0 # Actual section size from ELF headers + symbol_size: int = 0 # Sum of symbol sizes (may be less than total_size) + + +@dataclass +class SDKSymbol: + """Represents a symbol from an SDK library that's not in the ELF symbol table.""" + + name: str + size: int + library: str # Name of the .a file (e.g., "libpp.a") + section: str # ".bss" or ".data" + is_local: bool # True if static/local symbol (lowercase in nm output) + demangled: str = "" # Demangled name (populated after analysis) @dataclass @@ -118,6 +135,10 @@ class MemoryAnalyzer: self.objdump_path = objdump_path or "objdump" self.readelf_path = readelf_path or "readelf" self.external_components = external_components or set() + self._idedata = idedata + + # Derive nm path from objdump path using shared toolchain utility + self.nm_path = find_tool("nm", self.objdump_path) self.sections: dict[str, MemorySection] = {} self.components: dict[str, ComponentMemory] = defaultdict( @@ -128,15 +149,25 @@ class MemoryAnalyzer: self._esphome_core_symbols: list[ tuple[str, str, int] ] = [] # Track core symbols - self._component_symbols: dict[str, list[tuple[str, str, int]]] = defaultdict( + # Track symbols for all components: (symbol_name, demangled, size, section) + self._component_symbols: dict[str, list[tuple[str, str, int, str]]] = ( + defaultdict(list) + ) + # Track RAM symbols separately for detailed analysis: (symbol_name, demangled, size, section) + self._ram_symbols: dict[str, list[tuple[str, str, int, str]]] = defaultdict( list - ) # Track symbols for all components + ) + # Track ELF symbol names for SDK cross-reference + self._elf_symbol_names: set[str] = set() + # SDK symbols not in ELF (static/local symbols from closed-source libs) + self._sdk_symbols: list[SDKSymbol] = [] def analyze(self) -> dict[str, ComponentMemory]: """Analyze the ELF file and return component memory usage.""" self._parse_sections() self._parse_symbols() self._categorize_symbols() + self._analyze_sdk_libraries() return dict(self.components) def _parse_sections(self) -> None: @@ -190,6 +221,8 @@ class MemoryAnalyzer: continue self.sections[section].symbols.append((name, size, "")) + self.sections[section].symbol_size += size + self._elf_symbol_names.add(name) seen_addresses.add(address) def _categorize_symbols(self) -> None: @@ -233,8 +266,13 @@ class MemoryAnalyzer: if size > 0: demangled = self._demangle_symbol(symbol_name) self._component_symbols[component].append( - (symbol_name, demangled, size) + (symbol_name, demangled, size, section_name) ) + # Track RAM symbols separately for detailed RAM analysis + if section_name in RAM_SECTIONS: + self._ram_symbols[component].append( + (symbol_name, demangled, size, section_name) + ) def _identify_component(self, symbol_name: str) -> str: """Identify which component a symbol belongs to.""" @@ -328,6 +366,247 @@ class MemoryAnalyzer: return "Other Core" + def get_unattributed_ram(self) -> tuple[int, int, int]: + """Get unattributed RAM sizes (SDK/framework overhead). + + Returns: + Tuple of (unattributed_bss, unattributed_data, total_unattributed) + These are bytes in RAM sections that have no corresponding symbols. + """ + bss_section = self.sections.get(".bss") + data_section = self.sections.get(".data") + + unattributed_bss = 0 + unattributed_data = 0 + + if bss_section: + unattributed_bss = max(0, bss_section.total_size - bss_section.symbol_size) + if data_section: + unattributed_data = max( + 0, data_section.total_size - data_section.symbol_size + ) + + return unattributed_bss, unattributed_data, unattributed_bss + unattributed_data + + def _find_sdk_library_dirs(self) -> list[Path]: + """Find SDK library directories based on platform. + + Returns: + List of paths to SDK library directories containing .a files. + """ + sdk_dirs: list[Path] = [] + + if self._idedata is None: + return sdk_dirs + + # Get the CC path to determine the framework location + cc_path = getattr(self._idedata, "cc_path", None) + if not cc_path: + return sdk_dirs + + cc_path = Path(cc_path) + + # For ESP8266 Arduino framework + # CC is like: ~/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc + # SDK libs are in: ~/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lib/ + if "xtensa-lx106" in str(cc_path): + platformio_dir = cc_path.parent.parent.parent + esp8266_sdk = ( + platformio_dir + / "framework-arduinoespressif8266" + / "tools" + / "sdk" + / "lib" + ) + if esp8266_sdk.exists(): + sdk_dirs.append(esp8266_sdk) + # Also check for NONOSDK subdirectories (closed-source libs) + sdk_dirs.extend( + subdir + for subdir in esp8266_sdk.iterdir() + if subdir.is_dir() and subdir.name.startswith("NONOSDK") + ) + + # For ESP32 IDF framework + # CC is like: ~/.platformio/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32-elf-gcc + # or: ~/.platformio/packages/toolchain-riscv32-esp/bin/riscv32-esp-elf-gcc + elif "xtensa-esp" in str(cc_path) or "riscv32-esp" in str(cc_path): + # Detect ESP32 variant from CC path or defines + variant = self._detect_esp32_variant() + if variant: + platformio_dir = cc_path.parent.parent.parent + espidf_dir = platformio_dir / "framework-espidf" / "components" + if espidf_dir.exists(): + # Find all directories named after the variant that contain .a files + # This handles various ESP-IDF library layouts: + # - components/*/lib// + # - components/*// + # - components/*/lib/lib// + # - components/*/*/lib_*// + sdk_dirs.extend( + variant_dir + for variant_dir in espidf_dir.rglob(variant) + if variant_dir.is_dir() and any(variant_dir.glob("*.a")) + ) + + return sdk_dirs + + def _detect_esp32_variant(self) -> str | None: + """Detect ESP32 variant from idedata defines. + + Returns: + Variant string like 'esp32', 'esp32s2', 'esp32c3', etc. or None. + """ + if self._idedata is None: + return None + + defines = getattr(self._idedata, "defines", []) + if not defines: + return None + + # ESPHome always adds USE_ESP32_VARIANT_xxx defines + variant_prefix = "USE_ESP32_VARIANT_" + for define in defines: + if define.startswith(variant_prefix): + # Extract variant name and convert to lowercase + # USE_ESP32_VARIANT_ESP32 -> esp32 + # USE_ESP32_VARIANT_ESP32S3 -> esp32s3 + return define[len(variant_prefix) :].lower() + + return None + + def _parse_sdk_library( + self, lib_path: Path + ) -> tuple[list[tuple[str, int, str, bool]], set[str]]: + """Parse a single SDK library for symbols. + + Args: + lib_path: Path to the .a library file + + Returns: + Tuple of: + - List of BSS/DATA symbols: (symbol_name, size, section, is_local) + - Set of global BSS/DATA symbol names (for checking if RAM is linked) + """ + ram_symbols: list[tuple[str, int, str, bool]] = [] + global_ram_symbols: set[str] = set() + + result = run_tool([self.nm_path, "--size-sort", str(lib_path)], timeout=10) + if result is None: + return ram_symbols, global_ram_symbols + + for line in result.stdout.splitlines(): + parts = line.split() + if len(parts) < 3: + continue + + try: + size = int(parts[0], 16) + sym_type = parts[1] + name = parts[2] + + # Only collect BSS (b/B) and DATA (d/D) for RAM analysis + if sym_type in ("b", "B"): + section = ".bss" + is_local = sym_type == "b" + ram_symbols.append((name, size, section, is_local)) + # Track global RAM symbols (B/D) for linking check + if sym_type == "B": + global_ram_symbols.add(name) + elif sym_type in ("d", "D"): + section = ".data" + is_local = sym_type == "d" + ram_symbols.append((name, size, section, is_local)) + if sym_type == "D": + global_ram_symbols.add(name) + except (ValueError, IndexError): + continue + + return ram_symbols, global_ram_symbols + + def _analyze_sdk_libraries(self) -> None: + """Analyze SDK libraries to find symbols not in the ELF. + + This finds static/local symbols from closed-source SDK libraries + that consume RAM but don't appear in the final ELF symbol table. + Only includes symbols from libraries that have RAM actually linked + (at least one global BSS/DATA symbol in the ELF). + """ + sdk_dirs = self._find_sdk_library_dirs() + if not sdk_dirs: + _LOGGER.debug("No SDK library directories found") + return + + _LOGGER.debug("Analyzing SDK libraries in %d directories", len(sdk_dirs)) + + # Track seen symbols to avoid duplicates from multiple SDK versions + seen_symbols: set[str] = set() + + for sdk_dir in sdk_dirs: + for lib_path in sorted(sdk_dir.glob("*.a")): + lib_name = lib_path.name + ram_symbols, global_ram_symbols = self._parse_sdk_library(lib_path) + + # Check if this library's RAM is actually linked by seeing if any + # of its global BSS/DATA symbols appear in the ELF + if not global_ram_symbols & self._elf_symbol_names: + # No RAM from this library is in the ELF - skip it + continue + + for name, size, section, is_local in ram_symbols: + # Skip if already in ELF or already seen from another lib + if name in self._elf_symbol_names or name in seen_symbols: + continue + + # Only track symbols with non-zero size + if size > 0: + self._sdk_symbols.append( + SDKSymbol( + name=name, + size=size, + library=lib_name, + section=section, + is_local=is_local, + ) + ) + seen_symbols.add(name) + + # Demangle SDK symbols for better readability + if self._sdk_symbols: + sdk_names = [sym.name for sym in self._sdk_symbols] + demangled_map = batch_demangle(sdk_names, objdump_path=self.objdump_path) + for sym in self._sdk_symbols: + sym.demangled = demangled_map.get(sym.name, sym.name) + + # Sort by size descending for reporting + self._sdk_symbols.sort(key=lambda s: s.size, reverse=True) + + total_sdk_ram = sum(s.size for s in self._sdk_symbols) + _LOGGER.debug( + "Found %d SDK symbols not in ELF, totaling %d bytes", + len(self._sdk_symbols), + total_sdk_ram, + ) + + def get_sdk_ram_symbols(self) -> list[SDKSymbol]: + """Get SDK symbols that consume RAM but aren't in the ELF symbol table. + + Returns: + List of SDKSymbol objects sorted by size descending. + """ + return self._sdk_symbols + + def get_sdk_ram_by_library(self) -> dict[str, list[SDKSymbol]]: + """Get SDK RAM symbols grouped by library. + + Returns: + Dictionary mapping library name to list of symbols. + """ + by_lib: dict[str, list[SDKSymbol]] = defaultdict(list) + for sym in self._sdk_symbols: + by_lib[sym.library].append(sym) + return dict(by_lib) + if __name__ == "__main__": from .cli import main diff --git a/esphome/analyze_memory/cli.py b/esphome/analyze_memory/cli.py index 44ade221f8..a77e17afce 100644 --- a/esphome/analyze_memory/cli.py +++ b/esphome/analyze_memory/cli.py @@ -1,16 +1,24 @@ """CLI interface for memory analysis with report generation.""" +from __future__ import annotations + from collections import defaultdict +from collections.abc import Callable import sys +from typing import TYPE_CHECKING from . import ( _COMPONENT_API, _COMPONENT_CORE, _COMPONENT_PREFIX_ESPHOME, _COMPONENT_PREFIX_EXTERNAL, + RAM_SECTIONS, MemoryAnalyzer, ) +if TYPE_CHECKING: + from . import ComponentMemory + class MemoryAnalyzerCLI(MemoryAnalyzer): """Memory analyzer with CLI-specific report generation.""" @@ -19,6 +27,8 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): SYMBOL_SIZE_THRESHOLD: int = ( 100 # Show symbols larger than this in detailed analysis ) + # Lower threshold for RAM symbols (RAM is more constrained) + RAM_SYMBOL_SIZE_THRESHOLD: int = 24 # Column width constants COL_COMPONENT: int = 29 @@ -83,6 +93,60 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): COL_CORE_PERCENT, ) + def _add_section_header(self, lines: list[str], title: str) -> None: + """Add a section header with title centered between separator lines.""" + lines.append("") + lines.append("=" * self.TABLE_WIDTH) + lines.append(title.center(self.TABLE_WIDTH)) + lines.append("=" * self.TABLE_WIDTH) + lines.append("") + + def _add_top_consumers( + self, + lines: list[str], + title: str, + components: list[tuple[str, ComponentMemory]], + get_size: Callable[[ComponentMemory], int], + total: int, + memory_type: str, + limit: int = 25, + ) -> None: + """Add a formatted list of top memory consumers to the report. + + Args: + lines: List of report lines to append the output to. + title: Section title to print before the list. + components: Sequence of (name, ComponentMemory) tuples to analyze. + get_size: Callable that takes a ComponentMemory and returns the + size in bytes to use for ranking and display. + total: Total size in bytes for computing percentage usage. + memory_type: Label for the memory region (e.g., "flash" or "RAM"). + limit: Maximum number of components to include in the list. + """ + lines.append("") + lines.append(f"{title}:") + for i, (name, mem) in enumerate(components[:limit]): + size = get_size(mem) + if size > 0: + percentage = (size / total * 100) if total > 0 else 0 + lines.append( + f"{i + 1}. {name} ({size:,} B) - {percentage:.1f}% of analyzed {memory_type}" + ) + + def _format_symbol_with_section( + self, demangled: str, size: int, section: str | None = None + ) -> str: + """Format a symbol entry, optionally adding a RAM section label. + + If section is one of the RAM sections (.data or .bss), a label like + " [data]" or " [bss]" is appended. For non-RAM sections or when + section is None, no section label is added. + """ + section_label = "" + if section in RAM_SECTIONS: + section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss] + return f"{demangled} ({size:,} B){section_label}" + def generate_report(self, detailed: bool = False) -> str: """Generate a formatted memory report.""" components = sorted( @@ -123,43 +187,70 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): f"{total_flash:>{self.COL_TOTAL_FLASH - 2},} B | {total_ram:>{self.COL_TOTAL_RAM - 2},} B" ) - # Top consumers - lines.append("") - lines.append("Top Flash Consumers:") - for i, (name, mem) in enumerate(components[:25]): - if mem.flash_total > 0: - percentage = ( - (mem.flash_total / total_flash * 100) if total_flash > 0 else 0 - ) - lines.append( - f"{i + 1}. {name} ({mem.flash_total:,} B) - {percentage:.1f}% of analyzed flash" - ) - - lines.append("") - lines.append("Top RAM Consumers:") - ram_components = sorted(components, key=lambda x: x[1].ram_total, reverse=True) - for i, (name, mem) in enumerate(ram_components[:25]): - if mem.ram_total > 0: - percentage = (mem.ram_total / total_ram * 100) if total_ram > 0 else 0 - lines.append( - f"{i + 1}. {name} ({mem.ram_total:,} B) - {percentage:.1f}% of analyzed RAM" - ) - - lines.append("") - lines.append( - "Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included." + # Show unattributed RAM (SDK/framework overhead) + unattributed_bss, unattributed_data, unattributed_total = ( + self.get_unattributed_ram() + ) + if unattributed_total > 0: + lines.append("") + lines.append( + f"Unattributed RAM: {unattributed_total:,} B (SDK/framework overhead)" + ) + if unattributed_bss > 0 and unattributed_data > 0: + lines.append( + f" .bss: {unattributed_bss:,} B | .data: {unattributed_data:,} B" + ) + + # Show SDK symbol breakdown if available + sdk_by_lib = self.get_sdk_ram_by_library() + if sdk_by_lib: + lines.append("") + lines.append("SDK library breakdown (static symbols not in ELF):") + # Sort libraries by total size + lib_totals = [ + (lib, sum(s.size for s in syms), syms) + for lib, syms in sdk_by_lib.items() + ] + lib_totals.sort(key=lambda x: x[1], reverse=True) + + for lib_name, lib_total, syms in lib_totals: + if lib_total == 0: + continue + lines.append(f" {lib_name}: {lib_total:,} B") + # Show top symbols from this library + for sym in sorted(syms, key=lambda s: s.size, reverse=True)[:3]: + section_label = sym.section.lstrip(".") + # Use demangled name (falls back to original if not demangled) + display_name = sym.demangled or sym.name + if len(display_name) > 50: + display_name = f"{display_name[:47]}..." + lines.append( + f" {sym.size:>6,} B [{section_label}] {display_name}" + ) + + # Top consumers + self._add_top_consumers( + lines, + "Top Flash Consumers", + components, + lambda m: m.flash_total, + total_flash, + "flash", + ) + + ram_components = sorted(components, key=lambda x: x[1].ram_total, reverse=True) + self._add_top_consumers( + lines, + "Top RAM Consumers", + ram_components, + lambda m: m.ram_total, + total_ram, + "RAM", ) - lines.append("=" * self.TABLE_WIDTH) # Add ESPHome core detailed analysis if there are core symbols if self._esphome_core_symbols: - lines.append("") - lines.append("=" * self.TABLE_WIDTH) - lines.append( - f"{_COMPONENT_CORE} Detailed Analysis".center(self.TABLE_WIDTH) - ) - lines.append("=" * self.TABLE_WIDTH) - lines.append("") + self._add_section_header(lines, f"{_COMPONENT_CORE} Detailed Analysis") # Group core symbols by subcategory core_subcategories: dict[str, list[tuple[str, str, int]]] = defaultdict( @@ -211,7 +302,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): f"{_COMPONENT_CORE} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_core_symbols)} symbols):" ) for i, (symbol, demangled, size) in enumerate(large_core_symbols): - lines.append(f"{i + 1}. {demangled} ({size:,} B)") + # Core symbols only track (symbol, demangled, size) without section info, + # so we don't show section labels here + lines.append( + f"{i + 1}. {self._format_symbol_with_section(demangled, size)}" + ) lines.append("=" * self.TABLE_WIDTH) @@ -267,11 +362,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): for comp_name, comp_mem in components_to_analyze: if not (comp_symbols := self._component_symbols.get(comp_name, [])): continue - lines.append("") - lines.append("=" * self.TABLE_WIDTH) - lines.append(f"{comp_name} Detailed Analysis".center(self.TABLE_WIDTH)) - lines.append("=" * self.TABLE_WIDTH) - lines.append("") + self._add_section_header(lines, f"{comp_name} Detailed Analysis") # Sort symbols by size sorted_symbols = sorted(comp_symbols, key=lambda x: x[2], reverse=True) @@ -282,19 +373,69 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): # Show all symbols above threshold for better visibility large_symbols = [ - (sym, dem, size) - for sym, dem, size in sorted_symbols + (sym, dem, size, sec) + for sym, dem, size, sec in sorted_symbols if size > self.SYMBOL_SIZE_THRESHOLD ] lines.append( f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_symbols)} symbols):" ) - for i, (symbol, demangled, size) in enumerate(large_symbols): - lines.append(f"{i + 1}. {demangled} ({size:,} B)") + for i, (symbol, demangled, size, section) in enumerate(large_symbols): + lines.append( + f"{i + 1}. {self._format_symbol_with_section(demangled, size, section)}" + ) lines.append("=" * self.TABLE_WIDTH) + # Detailed RAM analysis by component (at end, before RAM strings analysis) + self._add_section_header(lines, "RAM Symbol Analysis by Component") + + # Show top 15 RAM consumers with their large symbols + for name, mem in ram_components[:15]: + if mem.ram_total == 0: + continue + ram_syms = self._ram_symbols.get(name, []) + if not ram_syms: + continue + + # Sort by size descending + sorted_ram_syms = sorted(ram_syms, key=lambda x: x[2], reverse=True) + large_ram_syms = [ + s for s in sorted_ram_syms if s[2] > self.RAM_SYMBOL_SIZE_THRESHOLD + ] + + lines.append(f"{name} ({mem.ram_total:,} B total RAM):") + + # Show breakdown by section type + data_size = sum(s[2] for s in ram_syms if s[3] == ".data") + bss_size = sum(s[2] for s in ram_syms if s[3] == ".bss") + lines.append(f" .data (initialized): {data_size:,} B") + lines.append(f" .bss (uninitialized): {bss_size:,} B") + + if large_ram_syms: + lines.append( + f" Symbols > {self.RAM_SYMBOL_SIZE_THRESHOLD} B ({len(large_ram_syms)}):" + ) + for symbol, demangled, size, section in large_ram_syms[:10]: + # Format section label consistently by stripping leading dot + section_label = section.lstrip(".") if section else "" + # Add ellipsis if name is truncated + demangled_display = ( + f"{demangled[:70]}..." if len(demangled) > 70 else demangled + ) + lines.append( + f" {size:>6,} B [{section_label}] {demangled_display}" + ) + if len(large_ram_syms) > 10: + lines.append(f" ... and {len(large_ram_syms) - 10} more") + lines.append("") + + lines.append( + "Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included." + ) + lines.append("=" * self.TABLE_WIDTH) + return "\n".join(lines) def dump_uncategorized_symbols(self, output_file: str | None = None) -> None: diff --git a/esphome/analyze_memory/const.py b/esphome/analyze_memory/const.py index 8dd6664bc0..9933bd77fd 100644 --- a/esphome/analyze_memory/const.py +++ b/esphome/analyze_memory/const.py @@ -7,11 +7,13 @@ ESPHOME_COMPONENT_PATTERN = re.compile(r"esphome::([a-zA-Z0-9_]+)::") # Section mapping for ELF file sections # Maps standard section names to their various platform-specific variants +# Note: Order matters! More specific patterns (.bss) must come before general ones (.dram) +# because ESP-IDF uses names like ".dram0.bss" which would match ".dram" otherwise SECTION_MAPPING = { ".text": frozenset([".text", ".iram"]), ".rodata": frozenset([".rodata"]), + ".bss": frozenset([".bss"]), # Must be before .data to catch ".dram0.bss" ".data": frozenset([".data", ".dram"]), - ".bss": frozenset([".bss"]), } # Section to ComponentMemory attribute mapping diff --git a/esphome/analyze_memory/toolchain.py b/esphome/analyze_memory/toolchain.py index e766252412..23d85e9700 100644 --- a/esphome/analyze_memory/toolchain.py +++ b/esphome/analyze_memory/toolchain.py @@ -5,6 +5,10 @@ from __future__ import annotations import logging from pathlib import Path import subprocess +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Sequence _LOGGER = logging.getLogger(__name__) @@ -55,3 +59,35 @@ def find_tool( _LOGGER.warning("Could not find %s tool", tool_name) return None + + +def run_tool( + cmd: Sequence[str], + timeout: int = 30, +) -> subprocess.CompletedProcess[str] | None: + """Run a toolchain command and return the result. + + Args: + cmd: Command and arguments to run + timeout: Timeout in seconds + + Returns: + CompletedProcess on success, None on failure + """ + try: + return subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=timeout, + check=False, + ) + except subprocess.TimeoutExpired: + _LOGGER.warning("Command timed out: %s", " ".join(cmd)) + return None + except FileNotFoundError: + _LOGGER.warning("Command not found: %s", cmd[0]) + return None + except OSError as e: + _LOGGER.warning("Failed to run command %s: %s", cmd[0], e) + return None diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 4d795ea5d9..e66f9a2c97 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -420,3 +420,8 @@ class IDEData: if path.endswith(".exe") else f"{path[:-3]}readelf" ) + + @property + def defines(self) -> list[str]: + """Return the list of preprocessor defines from idedata.""" + return self.raw.get("defines", []) diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index 36a284c382..f173c53636 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -2478,6 +2478,7 @@ def test_command_analyze_memory_success( "/path/to/objdump", "/path/to/readelf", set(), # No external components + idedata=mock_get_idedata.return_value, ) # Verify analysis was run @@ -2547,6 +2548,7 @@ def test_command_analyze_memory_with_external_components( "/path/to/objdump", "/path/to/readelf", {"my_custom_component"}, # External component detected + idedata=mock_get_idedata.return_value, ) From bf75f77eeec36963d8a374eda987a5a6bbbc290b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:25:08 -1000 Subject: [PATCH 1127/1145] [preferences] Fix preferences not syncing in safe mode due to component registration order (#13041) --- esphome/components/preferences/__init__.py | 3 +++ esphome/coroutine.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/esphome/components/preferences/__init__.py b/esphome/components/preferences/__init__.py index 1da6d02045..c6bede891a 100644 --- a/esphome/components/preferences/__init__.py +++ b/esphome/components/preferences/__init__.py @@ -1,6 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.core import coroutine_with_priority +from esphome.coroutine import CoroPriority CODEOWNERS = ["@esphome/core"] @@ -16,6 +18,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) +@coroutine_with_priority(CoroPriority.PREFERENCES) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_write_interval(config[CONF_FLASH_WRITE_INTERVAL])) diff --git a/esphome/coroutine.py b/esphome/coroutine.py index 0331c602c5..f5d512e510 100644 --- a/esphome/coroutine.py +++ b/esphome/coroutine.py @@ -114,6 +114,14 @@ class CoroPriority(enum.IntEnum): # Examples: web_server_ota (52) WEB_SERVER_OTA = 52 + # Preferences - must run before APPLICATION (safe_mode) because safe_mode + # uses an early return when entering safe mode, skipping all lower priority + # component registration. Without IntervalSyncer registered, preferences + # cannot be synced during shutdown in safe mode, causing issues like the + # boot counter never being cleared and devices getting stuck in safe mode. + # Examples: preferences (51) + PREFERENCES = 51 + # Application-level services # Examples: safe_mode (50) APPLICATION = 50 From 21687a1f586bb3495ba7a88486dabf7287413a4b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:25:33 -1000 Subject: [PATCH 1128/1145] [sun_gtil2] Eliminate heap allocations in text sensor publishing (#13047) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/sun_gtil2/sun_gtil2.cpp | 12 ++++++------ esphome/components/sun_gtil2/sun_gtil2.h | 6 +++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/esphome/components/sun_gtil2/sun_gtil2.cpp b/esphome/components/sun_gtil2/sun_gtil2.cpp index 46b4902654..d416d9a636 100644 --- a/esphome/components/sun_gtil2/sun_gtil2.cpp +++ b/esphome/components/sun_gtil2/sun_gtil2.cpp @@ -47,14 +47,15 @@ void SunGTIL2::loop() { } } -std::string SunGTIL2::state_to_string_(uint8_t state) { +const char *SunGTIL2::state_to_string_(uint8_t state, std::span buffer) { switch (state) { case 0x02: return "Starting voltage too low"; case 0x07: return "Working"; default: - return str_sprintf("Unknown (0x%02x)", state); + snprintf(buffer.data(), buffer.size(), "Unknown (0x%02x)", state); + return buffer.data(); } } @@ -106,12 +107,11 @@ void SunGTIL2::handle_char_(uint8_t c) { #endif #ifdef USE_TEXT_SENSOR if (this->state_ != nullptr) { - this->state_->publish_state(this->state_to_string_(msg.state)); + char state_buffer[STATE_BUFFER_SIZE]; + this->state_->publish_state(this->state_to_string_(msg.state, state_buffer)); } if (this->serial_number_ != nullptr) { - std::string serial_number; - serial_number.assign(msg.serial_number, 10); - this->serial_number_->publish_state(serial_number); + this->serial_number_->publish_state(msg.serial_number, 10); } #endif } diff --git a/esphome/components/sun_gtil2/sun_gtil2.h b/esphome/components/sun_gtil2/sun_gtil2.h index 0c29ae695d..3e28527cf7 100644 --- a/esphome/components/sun_gtil2/sun_gtil2.h +++ b/esphome/components/sun_gtil2/sun_gtil2.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/component.h" #include "esphome/core/defines.h" @@ -34,8 +36,10 @@ class SunGTIL2 : public Component, public uart::UARTDevice { void set_serial_number(text_sensor::TextSensor *text_sensor) { serial_number_ = text_sensor; } #endif + static constexpr size_t STATE_BUFFER_SIZE = 32; + protected: - std::string state_to_string_(uint8_t state); + const char *state_to_string_(uint8_t state, std::span buffer); #ifdef USE_SENSOR sensor::Sensor *ac_voltage_{nullptr}; sensor::Sensor *dc_voltage_{nullptr}; From ed39a130a8e4d507b85b31a55ee792c1e984f7de Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:26:04 -1000 Subject: [PATCH 1129/1145] [http_request] Store JSON keys in flash for ESP8266 (#13048) --- .../update/http_request_update.cpp | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index a9392ad736..82b391e01f 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -93,35 +93,36 @@ void HttpRequestUpdate::update_task(void *params) { container.reset(); // Release ownership of the container's shared_ptr valid = json::parse_json(response, [this_update](JsonObject root) -> bool { - if (!root["name"].is() || !root["version"].is() || !root["builds"].is()) { + if (!root[ESPHOME_F("name")].is() || !root[ESPHOME_F("version")].is() || + !root[ESPHOME_F("builds")].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - this_update->update_info_.title = root["name"].as(); - this_update->update_info_.latest_version = root["version"].as(); + this_update->update_info_.title = root[ESPHOME_F("name")].as(); + this_update->update_info_.latest_version = root[ESPHOME_F("version")].as(); - for (auto build : root["builds"].as()) { - if (!build["chipFamily"].is()) { + for (auto build : root[ESPHOME_F("builds")].as()) { + if (!build[ESPHOME_F("chipFamily")].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - if (build["chipFamily"] == ESPHOME_VARIANT) { - if (!build["ota"].is()) { + if (build[ESPHOME_F("chipFamily")] == ESPHOME_VARIANT) { + if (!build[ESPHOME_F("ota")].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - JsonObject ota = build["ota"].as(); - if (!ota["path"].is() || !ota["md5"].is()) { + JsonObject ota = build[ESPHOME_F("ota")].as(); + if (!ota[ESPHOME_F("path")].is() || !ota[ESPHOME_F("md5")].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - this_update->update_info_.firmware_url = ota["path"].as(); - this_update->update_info_.md5 = ota["md5"].as(); + this_update->update_info_.firmware_url = ota[ESPHOME_F("path")].as(); + this_update->update_info_.md5 = ota[ESPHOME_F("md5")].as(); - if (ota["summary"].is()) - this_update->update_info_.summary = ota["summary"].as(); - if (ota["release_url"].is()) - this_update->update_info_.release_url = ota["release_url"].as(); + if (ota[ESPHOME_F("summary")].is()) + this_update->update_info_.summary = ota[ESPHOME_F("summary")].as(); + if (ota[ESPHOME_F("release_url")].is()) + this_update->update_info_.release_url = ota[ESPHOME_F("release_url")].as(); return true; } From ef64226ed0e3a9d69ae0d086b83ed801ba56f678 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:26:21 -1000 Subject: [PATCH 1130/1145] [mqtt] Use ESPHOME_F() for JSON strings to reduce ESP8266 RAM usage (#13049) --- .../mqtt/mqtt_alarm_control_panel.cpp | 12 ++-- esphome/components/mqtt/mqtt_client.cpp | 33 +++++----- esphome/components/mqtt/mqtt_climate.cpp | 62 +++++++++---------- esphome/components/mqtt/mqtt_date.cpp | 18 +++--- esphome/components/mqtt/mqtt_datetime.cpp | 36 +++++------ esphome/components/mqtt/mqtt_light.cpp | 24 +++---- esphome/components/mqtt/mqtt_time.cpp | 18 +++--- esphome/components/mqtt/mqtt_update.cpp | 14 ++--- 8 files changed, 109 insertions(+), 108 deletions(-) diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index 8c570d1472..eb46c3b10c 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -58,22 +58,22 @@ void MQTTAlarmControlPanelComponent::send_discovery(JsonObject root, mqtt::SendD JsonArray supported_features = root[MQTT_SUPPORTED_FEATURES].to(); const uint32_t acp_supported_features = this->alarm_control_panel_->get_supported_features(); if (acp_supported_features & ACP_FEAT_ARM_AWAY) { - supported_features.add("arm_away"); + supported_features.add(ESPHOME_F("arm_away")); } if (acp_supported_features & ACP_FEAT_ARM_HOME) { - supported_features.add("arm_home"); + supported_features.add(ESPHOME_F("arm_home")); } if (acp_supported_features & ACP_FEAT_ARM_NIGHT) { - supported_features.add("arm_night"); + supported_features.add(ESPHOME_F("arm_night")); } if (acp_supported_features & ACP_FEAT_ARM_VACATION) { - supported_features.add("arm_vacation"); + supported_features.add(ESPHOME_F("arm_vacation")); } if (acp_supported_features & ACP_FEAT_ARM_CUSTOM_BYPASS) { - supported_features.add("arm_custom_bypass"); + supported_features.add(ESPHOME_F("arm_custom_bypass")); } if (acp_supported_features & ACP_FEAT_TRIGGER) { - supported_features.add("trigger"); + supported_features.add(ESPHOME_F("trigger")); } root[MQTT_CODE_DISARM_REQUIRED] = this->alarm_control_panel_->get_requires_code(); root[MQTT_CODE_ARM_REQUIRED] = this->alarm_control_panel_->get_requires_code_to_arm(); diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index aecf809c8b..652f55734b 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -94,45 +94,46 @@ void MQTTClientComponent::send_device_info_() { index++; } } - root["name"] = App.get_name(); + root[ESPHOME_F("name")] = App.get_name(); if (!App.get_friendly_name().empty()) { - root["friendly_name"] = App.get_friendly_name(); + root[ESPHOME_F("friendly_name")] = App.get_friendly_name(); } #ifdef USE_API - root["port"] = api::global_api_server->get_port(); + root[ESPHOME_F("port")] = api::global_api_server->get_port(); #endif - root["version"] = ESPHOME_VERSION; - root["mac"] = get_mac_address(); + root[ESPHOME_F("version")] = ESPHOME_VERSION; + root[ESPHOME_F("mac")] = get_mac_address(); #ifdef USE_ESP8266 - root["platform"] = "ESP8266"; + root[ESPHOME_F("platform")] = ESPHOME_F("ESP8266"); #endif #ifdef USE_ESP32 - root["platform"] = "ESP32"; + root[ESPHOME_F("platform")] = ESPHOME_F("ESP32"); #endif #ifdef USE_LIBRETINY - root["platform"] = lt_cpu_get_model_name(); + root[ESPHOME_F("platform")] = lt_cpu_get_model_name(); #endif - root["board"] = ESPHOME_BOARD; + root[ESPHOME_F("board")] = ESPHOME_BOARD; #if defined(USE_WIFI) - root["network"] = "wifi"; + root[ESPHOME_F("network")] = ESPHOME_F("wifi"); #elif defined(USE_ETHERNET) - root["network"] = "ethernet"; + root[ESPHOME_F("network")] = ESPHOME_F("ethernet"); #endif #ifdef ESPHOME_PROJECT_NAME - root["project_name"] = ESPHOME_PROJECT_NAME; - root["project_version"] = ESPHOME_PROJECT_VERSION; + root[ESPHOME_F("project_name")] = ESPHOME_PROJECT_NAME; + root[ESPHOME_F("project_version")] = ESPHOME_PROJECT_VERSION; #endif // ESPHOME_PROJECT_NAME #ifdef USE_DASHBOARD_IMPORT - root["package_import_url"] = dashboard_import::get_package_import_url(); + root[ESPHOME_F("package_import_url")] = dashboard_import::get_package_import_url(); #endif #ifdef USE_API_NOISE - root[api::global_api_server->get_noise_ctx().has_psk() ? "api_encryption" : "api_encryption_supported"] = - "Noise_NNpsk0_25519_ChaChaPoly_SHA256"; + root[api::global_api_server->get_noise_ctx().has_psk() ? ESPHOME_F("api_encryption") + : ESPHOME_F("api_encryption_supported")] = + ESPHOME_F("Noise_NNpsk0_25519_ChaChaPoly_SHA256"); #endif }, 2, this->discovery_info_.retain); diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 9d9ca012a8..d402fff6e6 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -31,18 +31,18 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo JsonArray modes = root[MQTT_MODES].to(); // sort array for nice UI in HA if (traits.supports_mode(CLIMATE_MODE_AUTO)) - modes.add("auto"); - modes.add("off"); + modes.add(ESPHOME_F("auto")); + modes.add(ESPHOME_F("off")); if (traits.supports_mode(CLIMATE_MODE_COOL)) - modes.add("cool"); + modes.add(ESPHOME_F("cool")); if (traits.supports_mode(CLIMATE_MODE_HEAT)) - modes.add("heat"); + modes.add(ESPHOME_F("heat")); if (traits.supports_mode(CLIMATE_MODE_FAN_ONLY)) - modes.add("fan_only"); + modes.add(ESPHOME_F("fan_only")); if (traits.supports_mode(CLIMATE_MODE_DRY)) - modes.add("dry"); + modes.add(ESPHOME_F("dry")); if (traits.supports_mode(CLIMATE_MODE_HEAT_COOL)) - modes.add("heat_cool"); + modes.add(ESPHOME_F("heat_cool")); if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { @@ -90,21 +90,21 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // preset_mode_state_topic root[MQTT_PRESET_MODE_STATE_TOPIC] = this->get_preset_state_topic(); // presets - JsonArray presets = root["preset_modes"].to(); + JsonArray presets = root[ESPHOME_F("preset_modes")].to(); if (traits.supports_preset(CLIMATE_PRESET_HOME)) - presets.add("home"); + presets.add(ESPHOME_F("home")); if (traits.supports_preset(CLIMATE_PRESET_AWAY)) - presets.add("away"); + presets.add(ESPHOME_F("away")); if (traits.supports_preset(CLIMATE_PRESET_BOOST)) - presets.add("boost"); + presets.add(ESPHOME_F("boost")); if (traits.supports_preset(CLIMATE_PRESET_COMFORT)) - presets.add("comfort"); + presets.add(ESPHOME_F("comfort")); if (traits.supports_preset(CLIMATE_PRESET_ECO)) - presets.add("eco"); + presets.add(ESPHOME_F("eco")); if (traits.supports_preset(CLIMATE_PRESET_SLEEP)) - presets.add("sleep"); + presets.add(ESPHOME_F("sleep")); if (traits.supports_preset(CLIMATE_PRESET_ACTIVITY)) - presets.add("activity"); + presets.add(ESPHOME_F("activity")); for (const auto &preset : traits.get_supported_custom_presets()) presets.add(preset); } @@ -120,27 +120,27 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // fan_mode_state_topic root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic(); // fan_modes - JsonArray fan_modes = root["fan_modes"].to(); + JsonArray fan_modes = root[ESPHOME_F("fan_modes")].to(); if (traits.supports_fan_mode(CLIMATE_FAN_ON)) - fan_modes.add("on"); + fan_modes.add(ESPHOME_F("on")); if (traits.supports_fan_mode(CLIMATE_FAN_OFF)) - fan_modes.add("off"); + fan_modes.add(ESPHOME_F("off")); if (traits.supports_fan_mode(CLIMATE_FAN_AUTO)) - fan_modes.add("auto"); + fan_modes.add(ESPHOME_F("auto")); if (traits.supports_fan_mode(CLIMATE_FAN_LOW)) - fan_modes.add("low"); + fan_modes.add(ESPHOME_F("low")); if (traits.supports_fan_mode(CLIMATE_FAN_MEDIUM)) - fan_modes.add("medium"); + fan_modes.add(ESPHOME_F("medium")); if (traits.supports_fan_mode(CLIMATE_FAN_HIGH)) - fan_modes.add("high"); + fan_modes.add(ESPHOME_F("high")); if (traits.supports_fan_mode(CLIMATE_FAN_MIDDLE)) - fan_modes.add("middle"); + fan_modes.add(ESPHOME_F("middle")); if (traits.supports_fan_mode(CLIMATE_FAN_FOCUS)) - fan_modes.add("focus"); + fan_modes.add(ESPHOME_F("focus")); if (traits.supports_fan_mode(CLIMATE_FAN_DIFFUSE)) - fan_modes.add("diffuse"); + fan_modes.add(ESPHOME_F("diffuse")); if (traits.supports_fan_mode(CLIMATE_FAN_QUIET)) - fan_modes.add("quiet"); + fan_modes.add(ESPHOME_F("quiet")); for (const auto &fan_mode : traits.get_supported_custom_fan_modes()) fan_modes.add(fan_mode); } @@ -151,15 +151,15 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // swing_mode_state_topic root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic(); // swing_modes - JsonArray swing_modes = root["swing_modes"].to(); + JsonArray swing_modes = root[ESPHOME_F("swing_modes")].to(); if (traits.supports_swing_mode(CLIMATE_SWING_OFF)) - swing_modes.add("off"); + swing_modes.add(ESPHOME_F("off")); if (traits.supports_swing_mode(CLIMATE_SWING_BOTH)) - swing_modes.add("both"); + swing_modes.add(ESPHOME_F("both")); if (traits.supports_swing_mode(CLIMATE_SWING_VERTICAL)) - swing_modes.add("vertical"); + swing_modes.add(ESPHOME_F("vertical")); if (traits.supports_swing_mode(CLIMATE_SWING_HORIZONTAL)) - swing_modes.add("horizontal"); + swing_modes.add(ESPHOME_F("horizontal")); } config.state_topic = false; diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp index c5a17abdfd..1715384c5f 100644 --- a/esphome/components/mqtt/mqtt_date.cpp +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -19,14 +19,14 @@ MQTTDateComponent::MQTTDateComponent(DateEntity *date) : date_(date) {} void MQTTDateComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->date_->make_call(); - if (root["year"].is()) { - call.set_year(root["year"]); + if (root[ESPHOME_F("year")].is()) { + call.set_year(root[ESPHOME_F("year")]); } - if (root["month"].is()) { - call.set_month(root["month"]); + if (root[ESPHOME_F("month")].is()) { + call.set_month(root[ESPHOME_F("month")]); } - if (root["day"].is()) { - call.set_day(root["day"]); + if (root[ESPHOME_F("day")].is()) { + call.set_day(root[ESPHOME_F("day")]); } call.perform(); }); @@ -55,9 +55,9 @@ bool MQTTDateComponent::send_initial_state() { bool MQTTDateComponent::publish_state(uint16_t year, uint8_t month, uint8_t day) { return this->publish_json(this->get_state_topic_(), [year, month, day](JsonObject root) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - root["year"] = year; - root["month"] = month; - root["day"] = day; + root[ESPHOME_F("year")] = year; + root[ESPHOME_F("month")] = month; + root[ESPHOME_F("day")] = day; }); } diff --git a/esphome/components/mqtt/mqtt_datetime.cpp b/esphome/components/mqtt/mqtt_datetime.cpp index d2feddcb00..79a2c82180 100644 --- a/esphome/components/mqtt/mqtt_datetime.cpp +++ b/esphome/components/mqtt/mqtt_datetime.cpp @@ -19,23 +19,23 @@ MQTTDateTimeComponent::MQTTDateTimeComponent(DateTimeEntity *datetime) : datetim void MQTTDateTimeComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->datetime_->make_call(); - if (root["year"].is()) { - call.set_year(root["year"]); + if (root[ESPHOME_F("year")].is()) { + call.set_year(root[ESPHOME_F("year")]); } - if (root["month"].is()) { - call.set_month(root["month"]); + if (root[ESPHOME_F("month")].is()) { + call.set_month(root[ESPHOME_F("month")]); } - if (root["day"].is()) { - call.set_day(root["day"]); + if (root[ESPHOME_F("day")].is()) { + call.set_day(root[ESPHOME_F("day")]); } - if (root["hour"].is()) { - call.set_hour(root["hour"]); + if (root[ESPHOME_F("hour")].is()) { + call.set_hour(root[ESPHOME_F("hour")]); } - if (root["minute"].is()) { - call.set_minute(root["minute"]); + if (root[ESPHOME_F("minute")].is()) { + call.set_minute(root[ESPHOME_F("minute")]); } - if (root["second"].is()) { - call.set_second(root["second"]); + if (root[ESPHOME_F("second")].is()) { + call.set_second(root[ESPHOME_F("second")]); } call.perform(); }); @@ -68,12 +68,12 @@ bool MQTTDateTimeComponent::publish_state(uint16_t year, uint8_t month, uint8_t uint8_t second) { return this->publish_json(this->get_state_topic_(), [year, month, day, hour, minute, second](JsonObject root) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - root["year"] = year; - root["month"] = month; - root["day"] = day; - root["hour"] = hour; - root["minute"] = minute; - root["second"] = second; + root[ESPHOME_F("year")] = year; + root[ESPHOME_F("month")] = month; + root[ESPHOME_F("day")] = day; + root[ESPHOME_F("hour")] = hour; + root[ESPHOME_F("minute")] = minute; + root[ESPHOME_F("second")] = second; }); } diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index 6a040e4b1c..0dafe487ff 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -43,33 +43,33 @@ LightState *MQTTJSONLightComponent::get_state() const { return this->state_; } void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - root["schema"] = "json"; + root[ESPHOME_F("schema")] = ESPHOME_F("json"); auto traits = this->state_->get_traits(); root[MQTT_COLOR_MODE] = true; // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - JsonArray color_modes = root["supported_color_modes"].to(); + JsonArray color_modes = root[ESPHOME_F("supported_color_modes")].to(); if (traits.supports_color_mode(ColorMode::ON_OFF)) - color_modes.add("onoff"); + color_modes.add(ESPHOME_F("onoff")); if (traits.supports_color_mode(ColorMode::BRIGHTNESS)) - color_modes.add("brightness"); + color_modes.add(ESPHOME_F("brightness")); if (traits.supports_color_mode(ColorMode::WHITE)) - color_modes.add("white"); + color_modes.add(ESPHOME_F("white")); if (traits.supports_color_mode(ColorMode::COLOR_TEMPERATURE) || traits.supports_color_mode(ColorMode::COLD_WARM_WHITE)) - color_modes.add("color_temp"); + color_modes.add(ESPHOME_F("color_temp")); if (traits.supports_color_mode(ColorMode::RGB)) - color_modes.add("rgb"); + color_modes.add(ESPHOME_F("rgb")); if (traits.supports_color_mode(ColorMode::RGB_WHITE) || // HA doesn't support RGBCT, and there's no CWWW->CT emulation in ESPHome yet, so ignore CT control for now traits.supports_color_mode(ColorMode::RGB_COLOR_TEMPERATURE)) - color_modes.add("rgbw"); + color_modes.add(ESPHOME_F("rgbw")); if (traits.supports_color_mode(ColorMode::RGB_COLD_WARM_WHITE)) - color_modes.add("rgbww"); + color_modes.add(ESPHOME_F("rgbww")); // legacy API if (traits.supports_color_capability(ColorCapability::BRIGHTNESS)) - root["brightness"] = true; + root[ESPHOME_F("brightness")] = true; if (traits.supports_color_mode(ColorMode::COLOR_TEMPERATURE) || traits.supports_color_mode(ColorMode::COLD_WARM_WHITE)) { @@ -78,11 +78,11 @@ void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscovery } if (this->state_->supports_effects()) { - root["effect"] = true; + root[ESPHOME_F("effect")] = true; JsonArray effect_list = root[MQTT_EFFECT_LIST].to(); for (auto *effect : this->state_->get_effects()) effect_list.add(effect->get_name()); - effect_list.add("None"); + effect_list.add(ESPHOME_F("None")); } } bool MQTTJSONLightComponent::send_initial_state() { return this->publish_state_(); } diff --git a/esphome/components/mqtt/mqtt_time.cpp b/esphome/components/mqtt/mqtt_time.cpp index c97a463858..01b8dd3483 100644 --- a/esphome/components/mqtt/mqtt_time.cpp +++ b/esphome/components/mqtt/mqtt_time.cpp @@ -19,14 +19,14 @@ MQTTTimeComponent::MQTTTimeComponent(TimeEntity *time) : time_(time) {} void MQTTTimeComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->time_->make_call(); - if (root["hour"].is()) { - call.set_hour(root["hour"]); + if (root[ESPHOME_F("hour")].is()) { + call.set_hour(root[ESPHOME_F("hour")]); } - if (root["minute"].is()) { - call.set_minute(root["minute"]); + if (root[ESPHOME_F("minute")].is()) { + call.set_minute(root[ESPHOME_F("minute")]); } - if (root["second"].is()) { - call.set_second(root["second"]); + if (root[ESPHOME_F("second")].is()) { + call.set_second(root[ESPHOME_F("second")]); } call.perform(); }); @@ -55,9 +55,9 @@ bool MQTTTimeComponent::send_initial_state() { bool MQTTTimeComponent::publish_state(uint8_t hour, uint8_t minute, uint8_t second) { return this->publish_json(this->get_state_topic_(), [hour, minute, second](JsonObject root) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - root["hour"] = hour; - root["minute"] = minute; - root["second"] = second; + root[ESPHOME_F("hour")] = hour; + root[ESPHOME_F("minute")] = minute; + root[ESPHOME_F("second")] = second; }); } diff --git a/esphome/components/mqtt/mqtt_update.cpp b/esphome/components/mqtt/mqtt_update.cpp index 150ddbf745..aedf2414c1 100644 --- a/esphome/components/mqtt/mqtt_update.cpp +++ b/esphome/components/mqtt/mqtt_update.cpp @@ -29,20 +29,20 @@ void MQTTUpdateComponent::setup() { bool MQTTUpdateComponent::publish_state() { return this->publish_json(this->get_state_topic_(), [this](JsonObject root) { - root["installed_version"] = this->update_->update_info.current_version; - root["latest_version"] = this->update_->update_info.latest_version; - root["title"] = this->update_->update_info.title; + root[ESPHOME_F("installed_version")] = this->update_->update_info.current_version; + root[ESPHOME_F("latest_version")] = this->update_->update_info.latest_version; + root[ESPHOME_F("title")] = this->update_->update_info.title; if (!this->update_->update_info.summary.empty()) - root["release_summary"] = this->update_->update_info.summary; + root[ESPHOME_F("release_summary")] = this->update_->update_info.summary; if (!this->update_->update_info.release_url.empty()) - root["release_url"] = this->update_->update_info.release_url; + root[ESPHOME_F("release_url")] = this->update_->update_info.release_url; }); } void MQTTUpdateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - root["schema"] = "json"; - root[MQTT_PAYLOAD_INSTALL] = "INSTALL"; + root[ESPHOME_F("schema")] = ESPHOME_F("json"); + root[MQTT_PAYLOAD_INSTALL] = ESPHOME_F("INSTALL"); } bool MQTTUpdateComponent::send_initial_state() { return this->publish_state(); } From a03c13f304d8f2ac7050b4948c0a65d5994ba837 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:26:49 -1000 Subject: [PATCH 1131/1145] [esp32_hosted] Add SHA256 alignment for hardware DMA compatibility (#13050) --- esphome/components/esp32_hosted/update/esp32_hosted_update.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp index 626bda3af3..3598a2e69c 100644 --- a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp @@ -90,7 +90,8 @@ void Esp32HostedUpdate::perform(bool force) { return; } - sha256::SHA256 hasher; + // ESP32-S3 hardware SHA acceleration requires 32-byte DMA alignment (IDF 5.5.x+) + alignas(32) sha256::SHA256 hasher; hasher.init(); hasher.add(this->firmware_data_, this->firmware_size_); hasher.calculate(); From 2830c7dab8b510dd520ab608a46d9342cc14b28c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:27:39 -1000 Subject: [PATCH 1132/1145] [ld2410/ld2412/ld2450] Use index-based select publish_state to avoid heap allocations (#13051) --- esphome/components/ld2410/ld2410.cpp | 7 +++++-- esphome/components/ld2412/ld2412.cpp | 7 +++++-- esphome/components/ld2450/ld2450.cpp | 15 +++++++++------ esphome/components/ld24xx/ld24xx.h | 9 +++++++++ 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/esphome/components/ld2410/ld2410.cpp b/esphome/components/ld2410/ld2410.cpp index 5ea47d5084..c9b4333f7e 100644 --- a/esphome/components/ld2410/ld2410.cpp +++ b/esphome/components/ld2410/ld2410.cpp @@ -117,6 +117,8 @@ constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[] = { {OUT_PIN_LEVEL_HIGH, "high"}, }; +constexpr uint32_t BAUD_RATES[] = {9600, 19200, 38400, 57600, 115200, 230400, 256000, 460800}; + // Helper functions for lookups template uint8_t find_uint8(const StringToUint8 (&arr)[N], const char *str) { for (const auto &entry : arr) { @@ -258,9 +260,10 @@ void LD2410Component::read_all_info() { this->query_parameters_(); this->set_config_mode_(false); #ifdef USE_SELECT - const auto baud_rate = std::to_string(this->parent_->get_baud_rate()); if (this->baud_rate_select_ != nullptr) { - this->baud_rate_select_->publish_state(baud_rate); + if (auto index = ld24xx::find_index(BAUD_RATES, this->parent_->get_baud_rate())) { + this->baud_rate_select_->publish_state(*index); + } } #endif } diff --git a/esphome/components/ld2412/ld2412.cpp b/esphome/components/ld2412/ld2412.cpp index 3d51800065..620ac9886b 100644 --- a/esphome/components/ld2412/ld2412.cpp +++ b/esphome/components/ld2412/ld2412.cpp @@ -128,6 +128,8 @@ constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[] = { {OUT_PIN_LEVEL_HIGH, "high"}, }; +constexpr uint32_t BAUD_RATES[] = {9600, 19200, 38400, 57600, 115200, 230400, 256000, 460800}; + // Helper functions for lookups template uint8_t find_uint8(const StringToUint8 (&arr)[N], const char *str) { for (const auto &entry : arr) { @@ -293,9 +295,10 @@ void LD2412Component::read_all_info() { #endif this->set_config_mode_(false); #ifdef USE_SELECT - const auto baud_rate = std::to_string(this->parent_->get_baud_rate()); if (this->baud_rate_select_ != nullptr) { - this->baud_rate_select_->publish_state(baud_rate); + if (auto index = ld24xx::find_index(BAUD_RATES, this->parent_->get_baud_rate())) { + this->baud_rate_select_->publish_state(*index); + } } #endif } diff --git a/esphome/components/ld2450/ld2450.cpp b/esphome/components/ld2450/ld2450.cpp index 2c137c3578..3b85694bc0 100644 --- a/esphome/components/ld2450/ld2450.cpp +++ b/esphome/components/ld2450/ld2450.cpp @@ -88,6 +88,9 @@ constexpr StringToUint8 ZONE_TYPE_BY_STR[] = { {"Filter", ZONE_FILTER}, }; +// Baud rates in the same order as BAUD_RATES_BY_STR for index-based lookup +constexpr uint32_t BAUD_RATES[] = {9600, 19200, 38400, 57600, 115200, 230400, 256000, 460800}; + // Helper functions for lookups template uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) { for (const auto &entry : arr) { @@ -376,9 +379,10 @@ void LD2450Component::read_all_info() { this->query_zone_(); this->set_config_mode_(false); #ifdef USE_SELECT - const auto baud_rate = std::to_string(this->parent_->get_baud_rate()); - if (this->baud_rate_select_ != nullptr && strcmp(this->baud_rate_select_->current_option(), baud_rate.c_str()) != 0) { - this->baud_rate_select_->publish_state(baud_rate); + if (this->baud_rate_select_ != nullptr) { + if (auto index = ld24xx::find_index(BAUD_RATES, this->parent_->get_baud_rate())) { + this->baud_rate_select_->publish_state(*index); + } } this->publish_zone_type(); #endif @@ -710,7 +714,7 @@ bool LD2450Component::handle_ack_data_() { case CMD_QUERY_ZONE: ESP_LOGV(TAG, "Query zone conf"); - this->zone_type_ = std::stoi(std::to_string(this->buffer_data_[10]), nullptr, 16); + this->zone_type_ = this->buffer_data_[10]; this->publish_zone_type(); #ifdef USE_SELECT if (this->zone_type_select_ != nullptr) { @@ -812,9 +816,8 @@ void LD2450Component::set_zone_type(const char *state) { // Publish Zone Type to Select component void LD2450Component::publish_zone_type() { #ifdef USE_SELECT - std::string zone_type = find_str(ZONE_TYPE_BY_UINT, this->zone_type_); if (this->zone_type_select_ != nullptr) { - this->zone_type_select_->publish_state(zone_type); + this->zone_type_select_->publish_state(find_str(ZONE_TYPE_BY_UINT, this->zone_type_)); } #endif } diff --git a/esphome/components/ld24xx/ld24xx.h b/esphome/components/ld24xx/ld24xx.h index cbd86e4e40..fd55167974 100644 --- a/esphome/components/ld24xx/ld24xx.h +++ b/esphome/components/ld24xx/ld24xx.h @@ -39,6 +39,15 @@ namespace esphome::ld24xx { +// Helper to find index of value in constexpr array +template optional find_index(const uint32_t (&arr)[N], uint32_t value) { + for (size_t i = 0; i < N; i++) { + if (arr[i] == value) + return i; + } + return {}; +} + static const char *const UNKNOWN_MAC = "unknown"; static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X"; From 0948e0359fd31fd96ddd4d1480e6f7b616186d86 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:27:58 -1000 Subject: [PATCH 1133/1145] [core] Add integer overload for fnv1a_hash_extend (#13054) --- .../components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp | 6 +++++- esphome/components/sen5x/sen5x.cpp | 2 +- esphome/components/sgp30/sgp30.cpp | 2 +- esphome/components/sgp4x/sgp4x.cpp | 2 +- esphome/core/helpers.h | 10 ++++++++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp b/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp index 50eaf33add..2d74ba6b12 100644 --- a/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp +++ b/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp @@ -31,7 +31,11 @@ void BME68xBSEC2I2CComponent::dump_config() { BME68xBSEC2Component::dump_config(); } -uint32_t BME68xBSEC2I2CComponent::get_hash() { return fnv1_hash("bme68x_bsec_state_" + to_string(this->address_)); } +uint32_t BME68xBSEC2I2CComponent::get_hash() { + char buf[22]; // "bme68x_bsec_state_" (18) + uint8_t max (3) + null + snprintf(buf, sizeof(buf), "bme68x_bsec_state_%u", this->address_); + return fnv1_hash(buf); +} int8_t BME68xBSEC2I2CComponent::read_bytes_wrapper(uint8_t a_register, uint8_t *data, uint32_t len, void *intfPtr) { ESP_LOGVV(TAG, "read_bytes_wrapper: reg = %u", a_register); diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index c72ccf2595..d5c9dfa3ae 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -158,7 +158,7 @@ void SEN5XComponent::setup() { // Hash with config hash, version, and serial number // This ensures the baseline storage is cleared after OTA // Serial numbers are unique to each sensor, so multiple sensors can be used without conflict - uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), std::to_string(combined_serial)); + uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), combined_serial); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 1326356437..18814405d4 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -75,7 +75,7 @@ void SGP30Component::setup() { // Hash with config hash, version, and serial number // This ensures the baseline storage is cleared after OTA // Serial numbers are unique to each sensor, so multiple sensors can be used without conflict - uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), std::to_string(this->serial_number_)); + uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), this->serial_number_); this->pref_ = global_preferences->make_preference(hash, true); if (this->store_baseline_ && this->pref_.load(&this->baselines_storage_)) { diff --git a/esphome/components/sgp4x/sgp4x.cpp b/esphome/components/sgp4x/sgp4x.cpp index 7c0f51c782..23589265ca 100644 --- a/esphome/components/sgp4x/sgp4x.cpp +++ b/esphome/components/sgp4x/sgp4x.cpp @@ -60,7 +60,7 @@ void SGP4xComponent::setup() { // Hash with config hash, version, and serial number // This ensures the baseline storage is cleared after OTA // Serial numbers are unique to each sensor, so multiple sensors can be used without conflict - uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), std::to_string(this->serial_number_)); + uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), this->serial_number_); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 6c338797a9..9916078efb 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -404,6 +404,16 @@ constexpr uint32_t fnv1a_hash_extend(uint32_t hash, const char *str) { inline uint32_t fnv1a_hash_extend(uint32_t hash, const std::string &str) { return fnv1a_hash_extend(hash, str.c_str()); } +/// Extend a FNV-1a hash with an integer (hashes each byte). +template constexpr uint32_t fnv1a_hash_extend(uint32_t hash, T value) { + using UnsignedT = std::make_unsigned_t; + UnsignedT uvalue = static_cast(value); + for (size_t i = 0; i < sizeof(T); i++) { + hash ^= (uvalue >> (i * 8)) & 0xFF; + hash *= FNV1_PRIME; + } + return hash; +} /// Calculate a FNV-1a hash of \p str. constexpr uint32_t fnv1a_hash(const char *str) { return fnv1a_hash_extend(FNV1_OFFSET_BASIS, str); } inline uint32_t fnv1a_hash(const std::string &str) { return fnv1a_hash(str.c_str()); } From 815543b77eb69c95292c438fe6969dfbeafa6b0a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:28:14 -1000 Subject: [PATCH 1134/1145] [tuya] Avoid heap allocation in text sensor enum publish (#13056) --- esphome/components/tuya/text_sensor/tuya_text_sensor.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp index c71bf176a4..3c492d609d 100644 --- a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp @@ -20,9 +20,10 @@ void TuyaTextSensor::setup() { break; } case TuyaDatapointType::ENUM: { - std::string data = to_string(datapoint.value_enum); - ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, data.c_str()); - this->publish_state(data); + char buf[4]; // uint8_t max is 3 digits + null + snprintf(buf, sizeof(buf), "%u", datapoint.value_enum); + ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, buf); + this->publish_state(buf); break; } default: From b7dbda497a9da5e12bf96496999dd291f1f64d5f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:28:31 -1000 Subject: [PATCH 1135/1145] [core] Improve log timestamp accuracy by batching serial reads (#12750) --- esphome/__main__.py | 43 ++-- tests/unit_tests/test_main.py | 368 ++++++++++++++++++++++++++++++++++ 2 files changed, 397 insertions(+), 14 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 73fdef6735..3849a585ca 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -62,6 +62,9 @@ from esphome.util import ( _LOGGER = logging.getLogger(__name__) +# Maximum buffer size for serial log reading to prevent unbounded memory growth +SERIAL_BUFFER_MAX_SIZE = 65536 + # Special non-component keys that appear in configs _NON_COMPONENT_KEYS = frozenset( { @@ -431,25 +434,37 @@ def run_miniterm(config: ConfigType, port: str, args) -> int: while tries < 5: try: with ser: + buffer = b"" + ser.timeout = 0.1 # 100ms timeout for non-blocking reads while True: try: - raw = ser.readline() + # Read all available data and timestamp it + chunk = ser.read(ser.in_waiting or 1) + if not chunk: + continue + time_ = datetime.now() + milliseconds = time_.microsecond // 1000 + time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{milliseconds:03}]" + + # Add to buffer and process complete lines + # Limit buffer size to prevent unbounded memory growth + # if device sends data without newlines + buffer += chunk + if len(buffer) > SERIAL_BUFFER_MAX_SIZE: + buffer = buffer[-SERIAL_BUFFER_MAX_SIZE:] + while b"\n" in buffer: + raw_line, buffer = buffer.split(b"\n", 1) + line = raw_line.replace(b"\r", b"").decode( + "utf8", "backslashreplace" + ) + safe_print(parser.parse_line(line, time_str)) + + backtrace_state = platformio_api.process_stacktrace( + config, line, backtrace_state=backtrace_state + ) except serial.SerialException: _LOGGER.error("Serial port closed!") return 0 - line = ( - raw.replace(b"\r", b"") - .replace(b"\n", b"") - .decode("utf8", "backslashreplace") - ) - time_ = datetime.now() - nanoseconds = time_.microsecond // 1000 - time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]" - safe_print(parser.parse_line(line, time_str)) - - backtrace_state = platformio_api.process_stacktrace( - config, line, backtrace_state=backtrace_state - ) except serial.SerialException: tries += 1 time.sleep(1) diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index f173c53636..fd8f04ded5 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -34,6 +34,7 @@ from esphome.__main__ import ( has_non_ip_address, has_resolvable_address, mqtt_get_ip, + run_miniterm, show_logs, upload_program, upload_using_esptool, @@ -41,11 +42,13 @@ from esphome.__main__ import ( from esphome.components.esp32 import KEY_ESP32, KEY_VARIANT, VARIANT_ESP32 from esphome.const import ( CONF_API, + CONF_BAUD_RATE, CONF_BROKER, CONF_DISABLED, CONF_ESPHOME, CONF_LEVEL, CONF_LOG_TOPIC, + CONF_LOGGER, CONF_MDNS, CONF_MQTT, CONF_NAME, @@ -838,6 +841,7 @@ class MockArgs: configuration: str | None = None name: str | None = None dashboard: bool = False + reset: bool = False def test_upload_program_serial_esp32( @@ -2804,3 +2808,367 @@ def test_compile_program_no_build_info_when_json_missing_keys( assert result == 0 assert "Build Info:" not in caplog.text + + +# Tests for run_miniterm serial log batching + + +# Sentinel to signal end of mock serial data (raises SerialException) +MOCK_SERIAL_END = object() + + +class MockSerial: + """Mock serial port for testing run_miniterm.""" + + def __init__(self, chunks: list[bytes | object]) -> None: + """Initialize with a list of chunks to return from read(). + + Args: + chunks: List of byte chunks to return sequentially. + Use MOCK_SERIAL_END sentinel to signal end of data. + Empty bytes b"" simulate timeout (no data available). + """ + self.chunks = list(chunks) + self.chunk_index = 0 + self.baudrate = 0 + self.port = "" + self.dtr = True + self.rts = True + self.timeout = 0.1 + self._is_open = False + + def __enter__(self) -> MockSerial: + self._is_open = True + return self + + def __exit__(self, *args: Any) -> None: + self._is_open = False + + @property + def in_waiting(self) -> int: + """Return number of bytes available.""" + if self.chunk_index < len(self.chunks): + chunk = self.chunks[self.chunk_index] + if chunk is MOCK_SERIAL_END: + return 0 + return len(chunk) # type: ignore[arg-type] + return 0 + + def read(self, size: int = 1) -> bytes: + """Read up to size bytes from the current chunk. + + This method respects the size argument and keeps any unconsumed + bytes in the current chunk so that subsequent calls to in_waiting + and read see the remaining data. + """ + if self.chunk_index < len(self.chunks): + chunk = self.chunks[self.chunk_index] + if chunk is MOCK_SERIAL_END: + # Sentinel means we're done - simulate port closed + import serial + + raise serial.SerialException("Port closed") + # Respect the requested size and keep any remaining bytes + if size <= 0: + return b"" + data = chunk[:size] # type: ignore[index] + remaining = chunk[size:] # type: ignore[index] + if remaining: + # Keep remaining bytes for the next read + self.chunks[self.chunk_index] = remaining # type: ignore[assignment] + else: + # Entire chunk consumed; advance to the next one + self.chunk_index += 1 + return data # type: ignore[return-value] + import serial + + raise serial.SerialException("Port closed") + + +def test_run_miniterm_batches_lines_with_same_timestamp( + capfd: CaptureFixture[str], +) -> None: + """Test that lines from the same chunk get the same timestamp.""" + # Simulate receiving multiple log lines in a single chunk + # This is how data arrives over USB - many lines at once + chunk = b"[I][app:100]: Line 1\r\n[I][app:100]: Line 2\r\n[I][app:100]: Line 3\r\n" + + mock_serial = MockSerial([chunk, MOCK_SERIAL_END]) + + config = { + CONF_LOGGER: { + CONF_BAUD_RATE: 115200, + "deassert_rts_dtr": False, + } + } + args = MockArgs() + + with ( + patch("serial.Serial", return_value=mock_serial), + patch.object(platformio_api, "process_stacktrace") as mock_bt, + ): + mock_bt.return_value = False + result = run_miniterm(config, "/dev/ttyUSB0", args) + + assert result == 0 + + captured = capfd.readouterr() + lines = [line for line in captured.out.strip().split("\n") if line] + + # All 3 lines should have the same timestamp (first 13 chars like "[HH:MM:SS.mmm]") + assert len(lines) == 3 + timestamps = [line[:13] for line in lines] + assert timestamps[0] == timestamps[1] == timestamps[2], ( + f"Lines from same chunk should have same timestamp: {timestamps}" + ) + + +def test_run_miniterm_different_chunks_different_timestamps( + capfd: CaptureFixture[str], +) -> None: + """Test that lines from different chunks can have different timestamps.""" + # Two separate chunks - could have different timestamps + chunk1 = b"[I][app:100]: Chunk 1 Line\r\n" + chunk2 = b"[I][app:100]: Chunk 2 Line\r\n" + + mock_serial = MockSerial([chunk1, chunk2, MOCK_SERIAL_END]) + + config = { + CONF_LOGGER: { + CONF_BAUD_RATE: 115200, + "deassert_rts_dtr": False, + } + } + args = MockArgs() + + with ( + patch("serial.Serial", return_value=mock_serial), + patch.object(platformio_api, "process_stacktrace") as mock_bt, + ): + mock_bt.return_value = False + result = run_miniterm(config, "/dev/ttyUSB0", args) + + assert result == 0 + + captured = capfd.readouterr() + lines = [line for line in captured.out.strip().split("\n") if line] + assert len(lines) == 2 + + +def test_run_miniterm_handles_split_lines() -> None: + """Test that partial lines are buffered until complete.""" + # Line split across two chunks + chunk1 = b"[I][app:100]: Start of " + chunk2 = b"line\r\n" + + mock_serial = MockSerial([chunk1, chunk2, MOCK_SERIAL_END]) + + config = { + CONF_LOGGER: { + CONF_BAUD_RATE: 115200, + "deassert_rts_dtr": False, + } + } + args = MockArgs() + + with ( + patch("serial.Serial", return_value=mock_serial), + patch.object(platformio_api, "process_stacktrace") as mock_bt, + patch("esphome.__main__.safe_print") as mock_print, + ): + mock_bt.return_value = False + run_miniterm(config, "/dev/ttyUSB0", args) + + # Should have printed exactly one complete line + assert mock_print.call_count == 1 + printed_line = mock_print.call_args[0][0] + assert "Start of line" in printed_line + + +def test_run_miniterm_backtrace_state_maintained() -> None: + """Test that backtrace_state is properly maintained across lines. + + ESP8266 backtraces span multiple lines between >>>stack>>> and <<>>stack>>>\r\n" + b"3ffffe90: 40220ef8 b66aa8c0 3fff0a4c 40204c84\r\n" + b"3ffffea0: 00000005 0000a635 3fff191c 4020413c\r\n" + b"<< bool: + """Track the backtrace_state progression.""" + backtrace_states.append((line, backtrace_state)) + # Simulate actual behavior + if ">>>stack>>>" in line: + return True + if "<<>>stack>>> - state should be False (before processing) + assert ">>>stack>>>" in backtrace_states[0][0] + assert backtrace_states[0][1] is False + + # Line 2: stack data - state should be True (after >>>stack>>>) + assert "40220ef8" in backtrace_states[1][0] + assert backtrace_states[1][1] is True + + # Line 3: more stack data - state should be True + assert "4020413c" in backtrace_states[2][0] + assert backtrace_states[2][1] is True + + # Line 4: << None: + """Test that empty reads (timeouts) are handled correctly. + + When read() returns empty bytes, the code should continue waiting + for more data without processing anything. + """ + # Simulate: empty read (timeout), then data, then empty read, then end + chunk = b"[I][app:100]: Test line\r\n" + + mock_serial = MockSerial([b"", chunk, b"", MOCK_SERIAL_END]) + + config = { + CONF_LOGGER: { + CONF_BAUD_RATE: 115200, + "deassert_rts_dtr": False, + } + } + args = MockArgs() + + with ( + patch("serial.Serial", return_value=mock_serial), + patch.object(platformio_api, "process_stacktrace") as mock_bt, + ): + mock_bt.return_value = False + result = run_miniterm(config, "/dev/ttyUSB0", args) + + assert result == 0 + + captured = capfd.readouterr() + lines = [line for line in captured.out.strip().split("\n") if line] + # Should have exactly one line despite empty reads + assert len(lines) == 1 + assert "Test line" in lines[0] + + +def test_run_miniterm_no_logger_returns_early( + caplog: pytest.LogCaptureFixture, +) -> None: + """Test that run_miniterm returns early if logger is not configured.""" + config: dict[str, Any] = {} # No logger config + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = run_miniterm(config, "/dev/ttyUSB0", args) + + assert result == 1 + assert "Logger is not enabled" in caplog.text + + +def test_run_miniterm_baud_rate_zero_returns_early( + caplog: pytest.LogCaptureFixture, +) -> None: + """Test that run_miniterm returns early if baud_rate is 0.""" + config = { + CONF_LOGGER: { + CONF_BAUD_RATE: 0, + "deassert_rts_dtr": False, + } + } + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = run_miniterm(config, "/dev/ttyUSB0", args) + + assert result == 1 + assert "UART logging is disabled" in caplog.text + + +def test_run_miniterm_buffer_limit_prevents_unbounded_growth() -> None: + """Test that buffer is limited to prevent unbounded memory growth. + + If a device sends data without newlines, the buffer should be truncated + to SERIAL_BUFFER_MAX_SIZE to prevent memory exhaustion. + """ + # Use a small buffer limit for testing + test_buffer_limit = 100 + + # Create data larger than the limit without newlines + large_data_no_newline = b"X" * 150 # 150 bytes, no newline + final_line = b"END\r\n" + + mock_serial = MockSerial([large_data_no_newline, final_line, MOCK_SERIAL_END]) + + config = { + CONF_LOGGER: { + CONF_BAUD_RATE: 115200, + "deassert_rts_dtr": False, + } + } + args = MockArgs() + + with ( + patch("serial.Serial", return_value=mock_serial), + patch.object(platformio_api, "process_stacktrace") as mock_bt, + patch("esphome.__main__.safe_print") as mock_print, + patch("esphome.__main__.SERIAL_BUFFER_MAX_SIZE", test_buffer_limit), + ): + mock_bt.return_value = False + run_miniterm(config, "/dev/ttyUSB0", args) + + # Should have printed exactly one line + assert mock_print.call_count == 1 + printed_line = mock_print.call_args[0][0] + + # The line should contain "END" and some X's, but not all 150 X's + # because the buffer was truncated + assert "END" in printed_line + assert "X" in printed_line + # Verify truncation happened - we shouldn't have all 150 X's + # The buffer logic is: + # 1. Add 150 X's -> buffer = 150 bytes -> truncate to last 100 = 100 X's + # 2. Add "END\r\n" (5 bytes) -> buffer = 105 bytes -> truncate to last 100 + # = 95 X's + "END\r\n" + # 3. Find newline, extract line = "95 X's + END" + x_count = printed_line.count("X") + assert x_count < 150, f"Expected truncation but got {x_count} X's" + assert x_count == 95, f"Expected 95 X's after truncation but got {x_count}" From fd19280df9a45e5238d5b982ca8fe3292029313f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:29:00 -1000 Subject: [PATCH 1136/1145] [es8388] Use index-based select publish_state to avoid heap allocations (#13053) --- esphome/components/es8388/es8388.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/esphome/components/es8388/es8388.cpp b/esphome/components/es8388/es8388.cpp index d1834e7043..9deb29416f 100644 --- a/esphome/components/es8388/es8388.cpp +++ b/esphome/components/es8388/es8388.cpp @@ -116,9 +116,8 @@ void ES8388::setup() { if (this->dac_output_select_ != nullptr) { auto dac_power = this->get_dac_power(); if (dac_power.has_value()) { - auto dac_power_str = this->dac_output_select_->at(dac_power.value()); - if (dac_power_str.has_value()) { - this->dac_output_select_->publish_state(dac_power_str.value()); + if (this->dac_output_select_->has_index(dac_power.value())) { + this->dac_output_select_->publish_state(dac_power.value()); } else { ESP_LOGW(TAG, "Unknown DAC output power value: %d", dac_power.value()); } @@ -127,9 +126,8 @@ void ES8388::setup() { if (this->adc_input_mic_select_ != nullptr) { auto mic_input = this->get_mic_input(); if (mic_input.has_value()) { - auto mic_input_str = this->adc_input_mic_select_->at(mic_input.value()); - if (mic_input_str.has_value()) { - this->adc_input_mic_select_->publish_state(mic_input_str.value()); + if (this->adc_input_mic_select_->has_index(mic_input.value())) { + this->adc_input_mic_select_->publish_state(mic_input.value()); } else { ESP_LOGW(TAG, "Unknown ADC input mic value: %d", mic_input.value()); } From d86d1f9f52022b8ed5f99e7ba7faca822f23584c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:29:28 -1000 Subject: [PATCH 1137/1145] [modbus_controller] Replace format_hex_pretty with stack-based format_hex_pretty_to (#12781) --- .../number/modbus_number.cpp | 10 ++++- .../output/modbus_output.cpp | 9 +++- .../switch/modbus_switch.cpp | 10 ++++- esphome/core/helpers.cpp | 44 ++++++++++++++----- esphome/core/helpers.h | 25 +++++++++++ 5 files changed, 85 insertions(+), 13 deletions(-) diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index ea8467d5a3..4a3ec1fc41 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -1,5 +1,6 @@ #include #include "modbus_number.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -7,6 +8,9 @@ namespace modbus_controller { static const char *const TAG = "modbus.number"; +// Maximum uint16_t registers to log in verbose hex output +static constexpr size_t MODBUS_NUMBER_MAX_LOG_REGISTERS = 32; + void ModbusNumber::parse_and_publish(const std::vector &data) { float result = payload_to_float(data, *this) / this->multiply_by_; @@ -47,7 +51,11 @@ void ModbusNumber::control(float value) { } if (!data.empty()) { - ESP_LOGV(TAG, "Modbus Number write raw: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_uint16_size(MODBUS_NUMBER_MAX_LOG_REGISTERS)]; +#endif + ESP_LOGV(TAG, "Modbus Number write raw: %s", + format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size())); write_cmd = ModbusCommandItem::create_custom_command( this->parent_, data, [this, write_cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index 45e786a704..f02d9397ca 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -7,6 +7,9 @@ namespace modbus_controller { static const char *const TAG = "modbus_controller.output"; +// Maximum bytes to log in verbose hex output +static constexpr size_t MODBUS_OUTPUT_MAX_LOG_BYTES = 64; + /** Write a value to the device * */ @@ -80,7 +83,11 @@ void ModbusBinaryOutput::write_state(bool state) { } } if (!data.empty()) { - ESP_LOGV(TAG, "Modbus binary output write raw: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MODBUS_OUTPUT_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Modbus binary output write raw: %s", + format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size())); cmd = ModbusCommandItem::create_custom_command( this->parent_, data, [this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp index 21c4c1718d..68aa37c9ed 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -1,11 +1,15 @@ #include "modbus_switch.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { namespace modbus_controller { static const char *const TAG = "modbus_controller.switch"; +// Maximum bytes to log in verbose hex output +static constexpr size_t MODBUS_SWITCH_MAX_LOG_BYTES = 64; + void ModbusSwitch::setup() { optional initial_state = Switch::get_initial_state_with_restore_mode(); if (initial_state.has_value()) { @@ -71,7 +75,11 @@ void ModbusSwitch::write_state(bool state) { } } if (!data.empty()) { - ESP_LOGV(TAG, "Modbus Switch write raw: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MODBUS_SWITCH_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Modbus Switch write raw: %s", + format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size())); cmd = ModbusCommandItem::create_custom_command( this->parent_, data, [this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 1c68f1a021..8671dc7f82 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -332,6 +332,37 @@ char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data return format_hex_internal(buffer, buffer_size, data, length, separator, 'A'); } +char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint16_t *data, size_t length, char separator) { + if (length == 0 || buffer_size == 0) { + if (buffer_size > 0) + buffer[0] = '\0'; + return buffer; + } + // With separator: each uint16_t needs 5 chars (4 hex + 1 sep), except last has no separator + // Without separator: each uint16_t needs 4 chars, plus null terminator + uint8_t stride = separator ? 5 : 4; + size_t max_values = separator ? (buffer_size / stride) : ((buffer_size - 1) / stride); + if (max_values == 0) { + buffer[0] = '\0'; + return buffer; + } + if (length > max_values) { + length = max_values; + } + for (size_t i = 0; i < length; i++) { + size_t pos = i * stride; + buffer[pos] = format_hex_pretty_char((data[i] & 0xF000) >> 12); + buffer[pos + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8); + buffer[pos + 2] = format_hex_pretty_char((data[i] & 0x00F0) >> 4); + buffer[pos + 3] = format_hex_pretty_char(data[i] & 0x000F); + if (separator && i < length - 1) { + buffer[pos + 4] = separator; + } + } + buffer[length * stride - (separator ? 1 : 0)] = '\0'; + return buffer; +} + // Shared implementation for uint8_t and string hex formatting static std::string format_hex_pretty_uint8(const uint8_t *data, size_t length, char separator, bool show_length) { if (data == nullptr || length == 0) @@ -356,16 +387,9 @@ std::string format_hex_pretty(const uint16_t *data, size_t length, char separato if (data == nullptr || length == 0) return ""; std::string ret; - uint8_t multiple = separator ? 5 : 4; // 5 if separator is not \0, 4 otherwise - ret.resize(multiple * length - (separator ? 1 : 0)); - for (size_t i = 0; i < length; i++) { - ret[multiple * i] = format_hex_pretty_char((data[i] & 0xF000) >> 12); - ret[multiple * i + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8); - ret[multiple * i + 2] = format_hex_pretty_char((data[i] & 0x00F0) >> 4); - ret[multiple * i + 3] = format_hex_pretty_char(data[i] & 0x000F); - if (separator && i != length - 1) - ret[multiple * i + 4] = separator; - } + size_t hex_len = separator ? (length * 5 - 1) : (length * 4); + ret.resize(hex_len); + format_hex_pretty_to(&ret[0], hex_len + 1, data, length, separator); if (show_length && length > 4) return ret + " (" + std::to_string(length) + ")"; return ret; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 9916078efb..acba420d3e 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -782,6 +782,31 @@ inline char *format_hex_pretty_to(char (&buffer)[N], const uint8_t *data, size_t return format_hex_pretty_to(buffer, N, data, length, separator); } +/// Calculate buffer size needed for format_hex_pretty_to with uint16_t data: "XXXX:XXXX:...:XXXX\0" +constexpr size_t format_hex_pretty_uint16_size(size_t count) { return count * 5; } + +/** + * Format uint16_t array as uppercase hex with separator to pre-allocated buffer. + * Each uint16_t is formatted as 4 hex chars in big-endian order. + * + * @param buffer Output buffer to write to. + * @param buffer_size Size of the output buffer. + * @param data Pointer to uint16_t array. + * @param length Number of uint16_t values. + * @param separator Character to use between values, or '\0' for no separator. + * @return Pointer to buffer. + * + * Buffer size needed: length * 5 with separator (for "XXXX:XXXX\0"), length * 4 + 1 without. + */ +char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint16_t *data, size_t length, char separator = ':'); + +/// Format uint16_t array as uppercase hex with separator to buffer. Automatically deduces buffer size. +template +inline char *format_hex_pretty_to(char (&buffer)[N], const uint16_t *data, size_t length, char separator = ':') { + static_assert(N >= 5, "Buffer must hold at least one hex uint16_t"); + return format_hex_pretty_to(buffer, N, data, length, separator); +} + /// MAC address size in bytes static constexpr size_t MAC_ADDRESS_SIZE = 6; /// Buffer size for MAC address with separators: "XX:XX:XX:XX:XX:XX\0" From 25ac89e9b59a900e566d9a81a8646047706c8847 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:29:50 -1000 Subject: [PATCH 1138/1145] [logger] Add thread-safe logging for host platform (#13010) --- esphome/components/logger/__init__.py | 7 +- esphome/components/logger/logger.cpp | 94 +++++---- esphome/components/logger/logger.h | 62 +++++- esphome/components/logger/logger_host.cpp | 5 +- ...g_buffer.cpp => task_log_buffer_esp32.cpp} | 4 +- ...k_log_buffer.h => task_log_buffer_esp32.h} | 19 ++ .../logger/task_log_buffer_host.cpp | 157 +++++++++++++++ .../components/logger/task_log_buffer_host.h | 122 ++++++++++++ .../fixtures/host_logger_thread_safety.yaml | 91 +++++++++ .../test_host_logger_thread_safety.py | 182 ++++++++++++++++++ 10 files changed, 698 insertions(+), 45 deletions(-) rename esphome/components/logger/{task_log_buffer.cpp => task_log_buffer_esp32.cpp} (98%) rename esphome/components/logger/{task_log_buffer.h => task_log_buffer_esp32.h} (77%) create mode 100644 esphome/components/logger/task_log_buffer_host.cpp create mode 100644 esphome/components/logger/task_log_buffer_host.h create mode 100644 tests/integration/fixtures/host_logger_thread_safety.yaml create mode 100644 tests/integration/test_host_logger_thread_safety.py diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 0a6035f8d1..79a9a4208c 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -310,6 +310,10 @@ async def to_code(config): if task_log_buffer_size > 0: cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER") cg.add(log.init_log_buffer(task_log_buffer_size)) + elif CORE.is_host: + cg.add(log.create_pthread_key()) + cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER") + cg.add(log.init_log_buffer(64)) # Fixed 64 slots for host cg.add(log.set_log_level(initial_level)) if CONF_HARDWARE_UART in config: @@ -520,10 +524,11 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( PlatformFramework.LN882X_ARDUINO, }, "logger_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR}, - "task_log_buffer.cpp": { + "task_log_buffer_esp32.cpp": { PlatformFramework.ESP32_ARDUINO, PlatformFramework.ESP32_IDF, }, + "task_log_buffer_host.cpp": {PlatformFramework.HOST_NATIVE}, } ) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 474eb9ec38..e633f9fd7d 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -12,14 +12,14 @@ namespace esphome::logger { static const char *const TAG = "logger"; -#ifdef USE_ESP32 -// Implementation for ESP32 (multi-task platform with task-specific tracking) -// Main task always uses direct buffer access for console output and callbacks +#if defined(USE_ESP32) || defined(USE_HOST) +// Implementation for multi-threaded platforms (ESP32 with FreeRTOS, Host with pthreads) +// Main thread/task always uses direct buffer access for console output and callbacks // -// For non-main tasks: +// For non-main threads/tasks: // - WITH task log buffer: Prefer sending to ring buffer for async processing // - Avoids allocating stack memory for console output in normal operation -// - Prevents console corruption from concurrent writes by multiple tasks +// - Prevents console corruption from concurrent writes by multiple threads // - Messages are serialized through main loop for proper console output // - Fallback to emergency console logging only if ring buffer is full // - WITHOUT task log buffer: Only emergency console output, no callbacks @@ -27,15 +27,20 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch if (level > this->level_for(tag)) return; +#ifdef USE_ESP32 TaskHandle_t current_task = xTaskGetCurrentTaskHandle(); bool is_main_task = (current_task == main_task_); +#else // USE_HOST + pthread_t current_thread = pthread_self(); + bool is_main_task = pthread_equal(current_thread, main_thread_); +#endif - // Check and set recursion guard - uses pthread TLS for per-task state + // Check and set recursion guard - uses pthread TLS for per-thread/task state if (this->check_and_set_task_log_recursion_(is_main_task)) { return; // Recursion detected } - // Main task uses the shared buffer for efficiency + // Main thread/task uses the shared buffer for efficiency if (is_main_task) { this->log_message_to_buffer_and_send_(level, tag, line, format, args); this->reset_task_log_recursion_(is_main_task); @@ -44,9 +49,13 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch bool message_sent = false; #ifdef USE_ESPHOME_TASK_LOG_BUFFER - // For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered + // For non-main threads/tasks, queue the message for callbacks +#ifdef USE_ESP32 message_sent = this->log_buffer_->send_message_thread_safe(level, tag, static_cast(line), current_task, format, args); +#else // USE_HOST + message_sent = this->log_buffer_->send_message_thread_safe(level, tag, static_cast(line), format, args); +#endif if (message_sent) { // Enable logger loop to process the buffered message // This is safe to call from any context including ISRs @@ -54,13 +63,19 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch } #endif // USE_ESPHOME_TASK_LOG_BUFFER - // Emergency console logging for non-main tasks when ring buffer is full or disabled + // Emergency console logging for non-main threads when ring buffer is full or disabled // This is a fallback mechanism to ensure critical log messages are visible - // Note: This may cause interleaved/corrupted console output if multiple tasks + // Note: This may cause interleaved/corrupted console output if multiple threads // log simultaneously, but it's better than losing important messages entirely +#ifdef USE_HOST + if (!message_sent) { + // Host always has console output - no baud_rate check needed + static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 512; +#else if (!message_sent && this->baud_rate_ > 0) { // If logging is enabled, write to console // Maximum size for console log messages (includes null terminator) static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144; +#endif char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety uint16_t buffer_at = 0; // Initialize buffer position this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at, @@ -70,7 +85,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch this->write_msg_(console_buffer, buffer_at); } - // Reset the recursion guard for this task + // Reset the recursion guard for this thread/task this->reset_task_log_recursion_(is_main_task); } #else @@ -86,7 +101,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch global_recursion_guard_ = false; } -#endif // !USE_ESP32 +#endif // USE_ESP32 / USE_HOST #ifdef USE_STORE_LOG_STR_IN_FLASH // Implementation for ESP8266 with flash string support. @@ -167,15 +182,24 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate this->main_task_ = xTaskGetCurrentTaskHandle(); #elif defined(USE_ZEPHYR) this->main_task_ = k_current_get(); +#elif defined(USE_HOST) + this->main_thread_ = pthread_self(); #endif } #ifdef USE_ESPHOME_TASK_LOG_BUFFER void Logger::init_log_buffer(size_t total_buffer_size) { +#ifdef USE_HOST + // Host uses slot count instead of byte size + this->log_buffer_ = esphome::make_unique(total_buffer_size); +#else this->log_buffer_ = esphome::make_unique(total_buffer_size); +#endif +#ifdef USE_ESP32 // Start with loop disabled when using task buffer (unless using USB CDC) // The loop will be enabled automatically when messages arrive this->disable_loop_when_buffer_empty_(); +#endif } #endif @@ -187,41 +211,37 @@ void Logger::process_messages_() { #ifdef USE_ESPHOME_TASK_LOG_BUFFER // Process any buffered messages when available if (this->log_buffer_->has_messages()) { +#ifdef USE_HOST + logger::TaskLogBufferHost::LogMessage *message; + while (this->log_buffer_->get_message_main_loop(&message)) { + const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; + this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, message->text, + message->text_length); + this->log_buffer_->release_message_main_loop(); + this->write_tx_buffer_to_console_(); + } +#else // USE_ESP32 logger::TaskLogBuffer::LogMessage *message; const char *text; void *received_token; - - // Process messages from the buffer while (this->log_buffer_->borrow_message_main_loop(&message, &text, &received_token)) { - this->tx_buffer_at_ = 0; - // Use the thread name that was stored when the message was created - // This avoids potential crashes if the task no longer exists const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; - this->write_header_to_buffer_(message->level, message->tag, message->line, thread_name, this->tx_buffer_, - &this->tx_buffer_at_, this->tx_buffer_size_); - this->write_body_to_buffer_(text, message->text_length, this->tx_buffer_, &this->tx_buffer_at_, - this->tx_buffer_size_); - this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); - this->tx_buffer_[this->tx_buffer_at_] = '\0'; - size_t msg_len = this->tx_buffer_at_; // We already know the length from tx_buffer_at_ - for (auto *listener : this->log_listeners_) - listener->on_log(message->level, message->tag, this->tx_buffer_, msg_len); - // At this point all the data we need from message has been transferred to the tx_buffer - // so we can release the message to allow other tasks to use it as soon as possible. + this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, text, + message->text_length); + // Release the message to allow other tasks to use it as soon as possible this->log_buffer_->release_message_main_loop(received_token); - - // Write to console from the main loop to prevent corruption from concurrent writes - // This ensures all log messages appear on the console in a clean, serialized manner - // Note: Messages may appear slightly out of order due to async processing, but - // this is preferred over corrupted/interleaved console output this->write_tx_buffer_to_console_(); } - } else { +#endif + } +#ifdef USE_ESP32 + else { // No messages to process, disable loop if appropriate // This reduces overhead when there's no async logging activity this->disable_loop_when_buffer_empty_(); } #endif +#endif // USE_ESPHOME_TASK_LOG_BUFFER } void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } @@ -271,7 +291,11 @@ void Logger::dump_config() { #endif #ifdef USE_ESPHOME_TASK_LOG_BUFFER if (this->log_buffer_) { - ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u", this->log_buffer_->size()); +#ifdef USE_HOST + ESP_LOGCONFIG(TAG, " Task Log Buffer Slots: %u", static_cast(this->log_buffer_->size())); +#else + ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u bytes", static_cast(this->log_buffer_->size())); +#endif } #endif diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index ba8d4667b6..86d2943135 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -2,7 +2,7 @@ #include #include -#ifdef USE_ESP32 +#if defined(USE_ESP32) || defined(USE_HOST) #include #endif #include "esphome/core/automation.h" @@ -12,7 +12,11 @@ #include "esphome/core/log.h" #ifdef USE_ESPHOME_TASK_LOG_BUFFER -#include "task_log_buffer.h" +#ifdef USE_HOST +#include "task_log_buffer_host.h" +#elif defined(USE_ESP32) +#include "task_log_buffer_esp32.h" +#endif #endif #ifdef USE_ARDUINO @@ -181,6 +185,9 @@ class Logger : public Component { uart_port_t get_uart_num() const { return uart_num_; } void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); } #endif +#ifdef USE_HOST + void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); } +#endif #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; } /// Get the UART used by the logger. @@ -228,7 +235,7 @@ class Logger : public Component { inline void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format, va_list args, char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_HOST) this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size); #elif defined(USE_ZEPHYR) char buff[MAX_POINTER_REPRESENTATION]; @@ -291,6 +298,22 @@ class Logger : public Component { this->write_tx_buffer_to_console_(); } +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + // Helper to format a pre-formatted message from the task log buffer and notify listeners + // Used by process_messages_ to avoid code duplication between ESP32 and host platforms + inline void HOT format_buffered_message_and_notify_(uint8_t level, const char *tag, uint16_t line, + const char *thread_name, const char *text, size_t text_length) { + this->tx_buffer_at_ = 0; + this->write_header_to_buffer_(level, tag, line, thread_name, this->tx_buffer_, &this->tx_buffer_at_, + this->tx_buffer_size_); + this->write_body_to_buffer_(text, text_length, this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); + this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); + this->tx_buffer_[this->tx_buffer_at_] = '\0'; + for (auto *listener : this->log_listeners_) + listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_); + } +#endif + // Write the body of the log message to the buffer inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { @@ -325,6 +348,9 @@ class Logger : public Component { #if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) void *main_task_ = nullptr; // Only used for thread name identification #endif +#ifdef USE_HOST + pthread_t main_thread_{}; // Main thread for identification +#endif #ifdef USE_ESP32 // Task-specific recursion guards: // - Main task uses a dedicated member variable for efficiency @@ -332,6 +358,10 @@ class Logger : public Component { pthread_key_t log_recursion_key_; // 4 bytes uart_port_t uart_num_; // 4 bytes (enum defaults to int size) #endif +#ifdef USE_HOST + // Thread-specific recursion guards using pthread TLS + pthread_key_t log_recursion_key_; +#endif // Large objects (internally aligned) #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS @@ -342,7 +372,11 @@ class Logger : public Component { std::vector level_listeners_; // Log level change listeners #endif #ifdef USE_ESPHOME_TASK_LOG_BUFFER +#ifdef USE_HOST + std::unique_ptr log_buffer_; // Will be initialized with init_log_buffer +#elif defined(USE_ESP32) std::unique_ptr log_buffer_; // Will be initialized with init_log_buffer +#endif #endif // Group smaller types together at the end @@ -355,7 +389,7 @@ class Logger : public Component { #ifdef USE_LIBRETINY UARTSelection uart_{UART_SELECTION_DEFAULT}; #endif -#ifdef USE_ESP32 +#if defined(USE_ESP32) || defined(USE_HOST) bool main_task_recursion_guard_{false}; #else bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms @@ -392,7 +426,7 @@ class Logger : public Component { } #endif -#ifdef USE_ESP32 +#if defined(USE_ESP32) || defined(USE_HOST) inline bool HOT check_and_set_task_log_recursion_(bool is_main_task) { if (is_main_task) { const bool was_recursive = main_task_recursion_guard_; @@ -418,6 +452,22 @@ class Logger : public Component { } #endif +#ifdef USE_HOST + const char *HOT get_thread_name_() { + pthread_t current_thread = pthread_self(); + if (pthread_equal(current_thread, main_thread_)) { + return nullptr; // Main thread + } + // For non-main threads, return the thread name + // We store it in thread-local storage to avoid allocation + static thread_local char thread_name_buf[32]; + if (pthread_getname_np(current_thread, thread_name_buf, sizeof(thread_name_buf)) == 0) { + return thread_name_buf; + } + return nullptr; + } +#endif + static inline void copy_string(char *buffer, uint16_t &pos, const char *str) { const size_t len = strlen(str); // Intentionally no null terminator, building larger string @@ -475,7 +525,7 @@ class Logger : public Component { buffer[pos++] = '0' + (remainder - tens * 10); buffer[pos++] = ']'; -#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) || defined(USE_HOST) if (thread_name != nullptr) { write_ansi_color_for_level(buffer, pos, 1); // Always use bold red for thread name buffer[pos++] = '['; diff --git a/esphome/components/logger/logger_host.cpp b/esphome/components/logger/logger_host.cpp index cbca06e431..874cdabd22 100644 --- a/esphome/components/logger/logger_host.cpp +++ b/esphome/components/logger/logger_host.cpp @@ -10,8 +10,9 @@ void HOT Logger::write_msg_(const char *msg, size_t len) { time_t rawtime; time(&rawtime); - struct tm *timeinfo = localtime(&rawtime); - size_t pos = strftime(buffer, TIMESTAMP_LEN + 1, "[%H:%M:%S]", timeinfo); + struct tm timeinfo; + localtime_r(&rawtime, &timeinfo); // Thread-safe version + size_t pos = strftime(buffer, TIMESTAMP_LEN + 1, "[%H:%M:%S]", &timeinfo); // Copy message (with newline already included by caller) size_t copy_len = std::min(len, sizeof(buffer) - pos); diff --git a/esphome/components/logger/task_log_buffer.cpp b/esphome/components/logger/task_log_buffer_esp32.cpp similarity index 98% rename from esphome/components/logger/task_log_buffer.cpp rename to esphome/components/logger/task_log_buffer_esp32.cpp index b5dd9f0239..b9dfe45b7f 100644 --- a/esphome/components/logger/task_log_buffer.cpp +++ b/esphome/components/logger/task_log_buffer_esp32.cpp @@ -1,5 +1,6 @@ +#ifdef USE_ESP32 -#include "task_log_buffer.h" +#include "task_log_buffer_esp32.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -134,3 +135,4 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin } // namespace esphome::logger #endif // USE_ESPHOME_TASK_LOG_BUFFER +#endif // USE_ESP32 diff --git a/esphome/components/logger/task_log_buffer.h b/esphome/components/logger/task_log_buffer_esp32.h similarity index 77% rename from esphome/components/logger/task_log_buffer.h rename to esphome/components/logger/task_log_buffer_esp32.h index fdda07190d..fde9bd60d5 100644 --- a/esphome/components/logger/task_log_buffer.h +++ b/esphome/components/logger/task_log_buffer_esp32.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ESP32 + #include "esphome/core/defines.h" #include "esphome/core/helpers.h" @@ -13,6 +15,22 @@ namespace esphome::logger { +/** + * @brief Task log buffer for ESP32 platform using FreeRTOS ring buffer. + * + * Threading Model: Multi-Producer Single-Consumer (MPSC) + * - Multiple FreeRTOS tasks can safely call send_message_thread_safe() concurrently + * - Only the main loop task calls borrow_message_main_loop() and release_message_main_loop() + * + * This uses the FreeRTOS ring buffer (RINGBUF_TYPE_NOSPLIT) which provides + * built-in thread-safety for the MPSC pattern. The ring buffer ensures + * message integrity - each message is stored contiguously. + * + * Design: + * - Variable-size messages with header + text stored contiguously + * - FreeRTOS ring buffer handles synchronization internally + * - Atomic counter for fast has_messages() check without ring buffer lock + */ class TaskLogBuffer { public: // Structure for a log message header (text data follows immediately after) @@ -65,3 +83,4 @@ class TaskLogBuffer { } // namespace esphome::logger #endif // USE_ESPHOME_TASK_LOG_BUFFER +#endif // USE_ESP32 diff --git a/esphome/components/logger/task_log_buffer_host.cpp b/esphome/components/logger/task_log_buffer_host.cpp new file mode 100644 index 0000000000..0660aeb061 --- /dev/null +++ b/esphome/components/logger/task_log_buffer_host.cpp @@ -0,0 +1,157 @@ +#ifdef USE_HOST + +#include "task_log_buffer_host.h" + +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + +#include "esphome/core/log.h" +#include +#include + +namespace esphome::logger { + +TaskLogBufferHost::TaskLogBufferHost(size_t slot_count) : slot_count_(slot_count) { + // Allocate message slots + this->slots_ = std::make_unique(slot_count); +} + +TaskLogBufferHost::~TaskLogBufferHost() { + // unique_ptr handles cleanup automatically +} + +int TaskLogBufferHost::acquire_write_slot_() { + // Try to reserve a slot using compare-and-swap + size_t current_reserve = this->reserve_index_.load(std::memory_order_relaxed); + + while (true) { + // Calculate next index (with wrap-around) + size_t next_reserve = (current_reserve + 1) % this->slot_count_; + + // Check if buffer would be full + // Buffer is full when next write position equals read position + size_t current_read = this->read_index_.load(std::memory_order_acquire); + if (next_reserve == current_read) { + return -1; // Buffer full + } + + // Try to claim this slot + if (this->reserve_index_.compare_exchange_weak(current_reserve, next_reserve, std::memory_order_acq_rel, + std::memory_order_relaxed)) { + return static_cast(current_reserve); + } + // If CAS failed, current_reserve was updated, retry with new value + } +} + +void TaskLogBufferHost::commit_write_slot_(int slot_index) { + // Mark the slot as ready for reading + this->slots_[slot_index].ready.store(true, std::memory_order_release); + + // Try to advance the write_index if we're the next expected commit + // This ensures messages are read in order + size_t expected = slot_index; + size_t next = (slot_index + 1) % this->slot_count_; + + // We only advance write_index if this slot is the next one expected + // This handles out-of-order commits correctly + while (true) { + if (!this->write_index_.compare_exchange_weak(expected, next, std::memory_order_release, + std::memory_order_relaxed)) { + // Someone else advanced it or we're not next in line, that's fine + break; + } + + // Successfully advanced, check if next slot is also ready + expected = next; + next = (next + 1) % this->slot_count_; + if (!this->slots_[expected].ready.load(std::memory_order_acquire)) { + break; + } + } +} + +bool TaskLogBufferHost::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *format, + va_list args) { + // Acquire a slot + int slot_index = this->acquire_write_slot_(); + if (slot_index < 0) { + return false; // Buffer full + } + + LogMessage &msg = this->slots_[slot_index]; + + // Fill in the message header + msg.level = level; + msg.tag = tag; + msg.line = line; + + // Get thread name using pthread + char thread_name_buf[LogMessage::MAX_THREAD_NAME_SIZE]; + // pthread_getname_np works the same on Linux and macOS + if (pthread_getname_np(pthread_self(), thread_name_buf, sizeof(thread_name_buf)) == 0) { + strncpy(msg.thread_name, thread_name_buf, sizeof(msg.thread_name) - 1); + msg.thread_name[sizeof(msg.thread_name) - 1] = '\0'; + } else { + msg.thread_name[0] = '\0'; + } + + // Format the message text + int ret = vsnprintf(msg.text, sizeof(msg.text), format, args); + if (ret < 0) { + // Formatting error - still commit the slot but with empty text + msg.text[0] = '\0'; + msg.text_length = 0; + } else { + msg.text_length = static_cast(std::min(static_cast(ret), sizeof(msg.text) - 1)); + } + + // Remove trailing newlines + while (msg.text_length > 0 && msg.text[msg.text_length - 1] == '\n') { + msg.text_length--; + } + msg.text[msg.text_length] = '\0'; + + // Commit the slot + this->commit_write_slot_(slot_index); + + return true; +} + +bool TaskLogBufferHost::get_message_main_loop(LogMessage **message) { + if (message == nullptr) { + return false; + } + + size_t current_read = this->read_index_.load(std::memory_order_relaxed); + size_t current_write = this->write_index_.load(std::memory_order_acquire); + + // Check if buffer is empty + if (current_read == current_write) { + return false; + } + + // Check if the slot is ready (should always be true if write_index advanced) + LogMessage &msg = this->slots_[current_read]; + if (!msg.ready.load(std::memory_order_acquire)) { + return false; + } + + *message = &msg; + return true; +} + +void TaskLogBufferHost::release_message_main_loop() { + size_t current_read = this->read_index_.load(std::memory_order_relaxed); + + // Clear the ready flag + this->slots_[current_read].ready.store(false, std::memory_order_release); + + // Advance read index + size_t next_read = (current_read + 1) % this->slot_count_; + this->read_index_.store(next_read, std::memory_order_release); +} + +} // namespace esphome::logger + +#endif // USE_ESPHOME_TASK_LOG_BUFFER +#endif // USE_HOST diff --git a/esphome/components/logger/task_log_buffer_host.h b/esphome/components/logger/task_log_buffer_host.h new file mode 100644 index 0000000000..d421d50ec6 --- /dev/null +++ b/esphome/components/logger/task_log_buffer_host.h @@ -0,0 +1,122 @@ +#pragma once + +#ifdef USE_HOST + +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" + +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + +#include +#include +#include +#include +#include +#include + +namespace esphome::logger { + +/** + * @brief Lock-free task log buffer for host platform. + * + * Threading Model: Multi-Producer Single-Consumer (MPSC) + * - Multiple threads can safely call send_message_thread_safe() concurrently + * - Only the main loop thread calls get_message_main_loop() and release_message_main_loop() + * + * Producers (multiple threads) Consumer (main loop only) + * │ │ + * ▼ ▼ + * acquire_write_slot_() get_message_main_loop() + * CAS on reserve_index_ read write_index_ + * │ check ready flag + * ▼ │ + * write to slot (exclusive) ▼ + * │ read slot data + * ▼ │ + * commit_write_slot_() ▼ + * set ready=true release_message_main_loop() + * advance write_index_ set ready=false + * advance read_index_ + * + * This implements a lock-free ring buffer for log messages on the host platform. + * It uses atomic compare-and-swap (CAS) operations for thread-safe slot reservation + * without requiring mutexes in the hot path. + * + * Design: + * - Fixed number of pre-allocated message slots to avoid dynamic allocation + * - Each slot contains a header and fixed-size text buffer + * - Atomic CAS for slot reservation allows multiple producers without locks + * - Single consumer (main loop) processes messages in order + */ +class TaskLogBufferHost { + public: + // Default number of message slots - host has plenty of memory + static constexpr size_t DEFAULT_SLOT_COUNT = 64; + + // Structure for a log message (fixed size for lock-free operation) + struct LogMessage { + // Size constants + static constexpr size_t MAX_THREAD_NAME_SIZE = 32; + static constexpr size_t MAX_TEXT_SIZE = 512; + + const char *tag; // Pointer to static tag string + char thread_name[MAX_THREAD_NAME_SIZE]; // Thread name (copied) + char text[MAX_TEXT_SIZE + 1]; // Message text with null terminator + uint16_t text_length; // Actual length of text + uint16_t line; // Source line number + uint8_t level; // Log level + std::atomic ready; // Message is ready to be consumed + + LogMessage() : tag(nullptr), text_length(0), line(0), level(0), ready(false) { + thread_name[0] = '\0'; + text[0] = '\0'; + } + }; + + /// Constructor that takes the number of message slots + explicit TaskLogBufferHost(size_t slot_count); + ~TaskLogBufferHost(); + + // NOT thread-safe - get next message from buffer, only call from main loop + // Returns true if a message was retrieved, false if buffer is empty + bool get_message_main_loop(LogMessage **message); + + // NOT thread-safe - release the message after processing, only call from main loop + void release_message_main_loop(); + + // Thread-safe - send a message to the buffer from any thread + // Returns true if message was queued, false if buffer is full + bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *format, va_list args); + + // Check if there are messages ready to be processed + inline bool HOT has_messages() const { + return read_index_.load(std::memory_order_acquire) != write_index_.load(std::memory_order_acquire); + } + + // Get the buffer size (number of slots) + inline size_t size() const { return slot_count_; } + + private: + // Acquire a slot for writing (thread-safe) + // Returns slot index or -1 if buffer is full + int acquire_write_slot_(); + + // Commit a slot after writing (thread-safe) + void commit_write_slot_(int slot_index); + + std::unique_ptr slots_; // Pre-allocated message slots + size_t slot_count_; // Number of slots + + // Lock-free indices using atomics + // - reserve_index_: Next slot to reserve (producers CAS this to claim slots) + // - write_index_: Boundary of committed/ready slots (consumer reads up to this) + // - read_index_: Next slot to read (only consumer modifies this) + std::atomic reserve_index_{0}; // Next slot to reserve for writing + std::atomic write_index_{0}; // Last committed slot boundary + std::atomic read_index_{0}; // Next slot to read from +}; + +} // namespace esphome::logger + +#endif // USE_ESPHOME_TASK_LOG_BUFFER +#endif // USE_HOST diff --git a/tests/integration/fixtures/host_logger_thread_safety.yaml b/tests/integration/fixtures/host_logger_thread_safety.yaml new file mode 100644 index 0000000000..e44a217b2b --- /dev/null +++ b/tests/integration/fixtures/host_logger_thread_safety.yaml @@ -0,0 +1,91 @@ +esphome: + name: host-logger-thread-test +host: +api: +logger: + +button: + - platform: template + name: "Start Thread Race Test" + id: start_test_button + on_press: + - lambda: |- + // Number of threads and messages per thread + static const int NUM_THREADS = 3; + static const int MESSAGES_PER_THREAD = 100; + + // Counters + static std::atomic total_messages_logged{0}; + + // Thread function - must be a regular function pointer for pthread + struct ThreadTest { + static void *thread_func(void *arg) { + int thread_id = *static_cast(arg); + + // Set thread name (different signatures on macOS vs Linux) + char thread_name[16]; + snprintf(thread_name, sizeof(thread_name), "LogThread%d", thread_id); + #ifdef __APPLE__ + pthread_setname_np(thread_name); + #else + pthread_setname_np(pthread_self(), thread_name); + #endif + + // Log messages with different log levels + for (int i = 0; i < MESSAGES_PER_THREAD; i++) { + switch (i % 4) { + case 0: + ESP_LOGI("thread_test", "THREAD%d_MSG%03d_INFO_MESSAGE_WITH_DATA_%08X", + thread_id, i, i * 12345); + break; + case 1: + ESP_LOGD("thread_test", "THREAD%d_MSG%03d_DEBUG_MESSAGE_WITH_DATA_%08X", + thread_id, i, i * 12345); + break; + case 2: + ESP_LOGW("thread_test", "THREAD%d_MSG%03d_WARN_MESSAGE_WITH_DATA_%08X", + thread_id, i, i * 12345); + break; + case 3: + ESP_LOGE("thread_test", "THREAD%d_MSG%03d_ERROR_MESSAGE_WITH_DATA_%08X", + thread_id, i, i * 12345); + break; + } + total_messages_logged.fetch_add(1, std::memory_order_relaxed); + + // Small busy loop to vary timing between threads + int delay_count = (thread_id + 1) * 10; + while (delay_count-- > 0) { + asm volatile("" ::: "memory"); // Prevent optimization + } + } + return nullptr; + } + }; + + ESP_LOGI("thread_test", "RACE_TEST_START: Starting %d threads with %d messages each", + NUM_THREADS, MESSAGES_PER_THREAD); + + // Reset counter for this test run + total_messages_logged.store(0, std::memory_order_relaxed); + + pthread_t threads[NUM_THREADS]; + int thread_ids[NUM_THREADS]; + + // Create all threads + for (int i = 0; i < NUM_THREADS; i++) { + thread_ids[i] = i; + int ret = pthread_create(&threads[i], nullptr, ThreadTest::thread_func, &thread_ids[i]); + if (ret != 0) { + ESP_LOGE("thread_test", "RACE_TEST_ERROR: Failed to create thread %d", i); + return; + } + } + + // Wait for all threads to complete + for (int i = 0; i < NUM_THREADS; i++) { + pthread_join(threads[i], nullptr); + } + + ESP_LOGI("thread_test", "RACE_TEST_COMPLETE: All threads finished, total messages: %d", + total_messages_logged.load(std::memory_order_relaxed)); diff --git a/tests/integration/test_host_logger_thread_safety.py b/tests/integration/test_host_logger_thread_safety.py new file mode 100644 index 0000000000..922ce00155 --- /dev/null +++ b/tests/integration/test_host_logger_thread_safety.py @@ -0,0 +1,182 @@ +"""Integration test for host logger thread safety. + +This test verifies that the logger's MPSC ring buffer correctly handles +multiple threads racing to log messages without corruption or data loss. +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + +# Expected pattern for log messages from threads +# Format: THREADn_MSGnnn_LEVEL_MESSAGE_WITH_DATA_xxxxxxxx +THREAD_MSG_PATTERN = re.compile( + r"THREAD(\d+)_MSG(\d{3})_(INFO|DEBUG|WARN|ERROR)_MESSAGE_WITH_DATA_([0-9A-F]{8})" +) + +# Pattern for test start/complete markers +TEST_START_PATTERN = re.compile(r"RACE_TEST_START.*Starting (\d+) threads") +TEST_COMPLETE_PATTERN = re.compile(r"RACE_TEST_COMPLETE.*total messages: (\d+)") + +# Expected values +NUM_THREADS = 3 +MESSAGES_PER_THREAD = 100 +EXPECTED_TOTAL_MESSAGES = NUM_THREADS * MESSAGES_PER_THREAD + + +@pytest.mark.asyncio +async def test_host_logger_thread_safety( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that multiple threads can log concurrently without corruption. + + This test: + 1. Spawns 3 threads that each log 100 messages + 2. Collects all log output + 3. Verifies no lines are corrupted (partially written or interleaved) + 4. Verifies all expected messages were received + """ + collected_lines: list[str] = [] + test_complete_event = asyncio.Event() + + def line_callback(line: str) -> None: + """Collect log lines and detect test completion.""" + collected_lines.append(line) + if "RACE_TEST_COMPLETE" in line: + test_complete_event.set() + + # Run the test binary and collect output + async with ( + run_compiled(yaml_config, line_callback=line_callback), + api_client_connected() as client, + ): + # Verify connection works + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "host-logger-thread-test" + + # Get the button entity - find by name + entities, _ = await client.list_entities_services() + button_entities = [e for e in entities if e.name == "Start Thread Race Test"] + assert button_entities, "Could not find Start Thread Race Test button" + button_key = button_entities[0].key + + # Press the button to start the thread race test + client.button_command(button_key) + + # Wait for test to complete (with timeout) + try: + await asyncio.wait_for(test_complete_event.wait(), timeout=30.0) + except TimeoutError: + pytest.fail( + "Test did not complete within timeout. " + f"Collected {len(collected_lines)} lines." + ) + + # Give a bit more time for any remaining buffered messages + await asyncio.sleep(0.5) + + # Analyze collected log lines + thread_messages: dict[int, set[int]] = {i: set() for i in range(NUM_THREADS)} + corrupted_lines: list[str] = [] + test_started = False + test_completed = False + reported_total = 0 + + for line in collected_lines: + # Check for test start + start_match = TEST_START_PATTERN.search(line) + if start_match: + test_started = True + assert int(start_match.group(1)) == NUM_THREADS, ( + f"Unexpected thread count: {start_match.group(1)}" + ) + continue + + # Check for test completion + complete_match = TEST_COMPLETE_PATTERN.search(line) + if complete_match: + test_completed = True + reported_total = int(complete_match.group(1)) + continue + + # Check for thread messages + msg_match = THREAD_MSG_PATTERN.search(line) + if msg_match: + thread_id = int(msg_match.group(1)) + msg_num = int(msg_match.group(2)) + # level = msg_match.group(3) # INFO, DEBUG, WARN, ERROR + data_hex = msg_match.group(4) + + # Verify data value matches expected calculation + expected_data = f"{msg_num * 12345:08X}" + if data_hex != expected_data: + corrupted_lines.append( + f"Data mismatch in line: {line} " + f"(expected {expected_data}, got {data_hex})" + ) + continue + + # Track which messages we received from each thread + if 0 <= thread_id < NUM_THREADS: + thread_messages[thread_id].add(msg_num) + else: + corrupted_lines.append(f"Invalid thread ID in line: {line}") + continue + + # Check for partial/corrupted thread messages + # If a line contains part of a thread message pattern but doesn't match fully + # This could indicate line corruption from interleaving + if ( + "THREAD" in line + and "MSG" in line + and not msg_match + and "_MESSAGE_WITH_DATA_" in line + ): + corrupted_lines.append(f"Possibly corrupted line: {line}") + + # Assertions + assert test_started, "Test start marker not found in output" + assert test_completed, "Test completion marker not found in output" + assert reported_total == EXPECTED_TOTAL_MESSAGES, ( + f"Reported total {reported_total} != expected {EXPECTED_TOTAL_MESSAGES}" + ) + + # Check for corrupted lines + assert not corrupted_lines, ( + f"Found {len(corrupted_lines)} corrupted lines:\n" + + "\n".join(corrupted_lines[:10]) # Show first 10 + ) + + # Count total messages received + total_received = sum(len(msgs) for msgs in thread_messages.values()) + + # We may not receive all messages due to ring buffer overflow when buffer is full + # The test primarily verifies no corruption, not that we receive every message + # However, we should receive a reasonable number of messages + min_expected = EXPECTED_TOTAL_MESSAGES // 2 # At least 50% + assert total_received >= min_expected, ( + f"Received only {total_received} messages, expected at least {min_expected}. " + f"Per-thread breakdown: " + + ", ".join(f"Thread{i}: {len(msgs)}" for i, msgs in thread_messages.items()) + ) + + # Verify we got messages from all threads (proves concurrent logging worked) + for thread_id in range(NUM_THREADS): + assert thread_messages[thread_id], ( + f"No messages received from thread {thread_id}" + ) + + # Log summary for debugging + print("\nThread safety test summary:") + print(f" Total messages received: {total_received}/{EXPECTED_TOTAL_MESSAGES}") + for thread_id in range(NUM_THREADS): + received = len(thread_messages[thread_id]) + print(f" Thread {thread_id}: {received}/{MESSAGES_PER_THREAD} messages") From 050e9b0d4a1d9639cb6fe6b960f4c7eaab4ce373 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:30:23 -1000 Subject: [PATCH 1139/1145] [wifi] Add basic post-connect roaming support for stationary devices (#12809) --- esphome/components/wifi/__init__.py | 11 + esphome/components/wifi/wifi_component.cpp | 223 ++++++++++++++++++-- esphome/components/wifi/wifi_component.h | 29 ++- tests/components/wifi/test.esp32-idf.yaml | 1 + tests/components/wifi/test.esp8266-ard.yaml | 1 + 5 files changed, 244 insertions(+), 21 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 232e8d4f27..824944d4a2 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -64,6 +64,7 @@ _LOGGER = logging.getLogger(__name__) NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2, const.VARIANT_ESP32P4] CONF_SAVE = "save" CONF_MIN_AUTH_MODE = "min_auth_mode" +CONF_POST_CONNECT_ROAMING = "post_connect_roaming" # Maximum number of WiFi networks that can be configured # Limited to 127 because selected_sta_index_ is int8_t in C++ @@ -349,6 +350,7 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_PASSIVE_SCAN, default=False): cv.boolean, cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, + cv.Optional(CONF_POST_CONNECT_ROAMING, default=True): cv.boolean, cv.Optional(CONF_ON_CONNECT): automation.validate_automation(single=True), cv.Optional(CONF_ON_DISCONNECT): automation.validate_automation( single=True @@ -491,6 +493,15 @@ async def to_code(config): if not config[CONF_ENABLE_ON_BOOT]: cg.add(var.set_enable_on_boot(False)) + # post_connect_roaming defaults to true in C++ - disable if user disabled it + # or if 802.11k/v is enabled (driver handles roaming natively) + if ( + not config[CONF_POST_CONNECT_ROAMING] + or config.get(CONF_ENABLE_BTM) + or config.get(CONF_ENABLE_RRM) + ): + cg.add(var.set_post_connect_roaming(False)) + if CORE.is_esp8266: cg.add_library("ESP8266WiFi", None) elif CORE.is_rp2040: diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 2d635d893f..6654474329 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -28,6 +28,7 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/core/string_ref.h" #include "esphome/core/util.h" #ifdef USE_CAPTIVE_PORTAL @@ -143,6 +144,56 @@ static const char *const TAG = "wifi"; /// - Networks not in scan results → Tried in RETRY_HIDDEN phase /// - Networks visible in scan + not marked hidden → Skipped in RETRY_HIDDEN phase /// - Networks marked 'hidden: true' always use hidden mode, even if broadcasting SSID +/// +/// ┌──────────────────────────────────────────────────────────────────────┐ +/// │ Post-Connect Roaming (for stationary devices) │ +/// ├──────────────────────────────────────────────────────────────────────┤ +/// │ Purpose: Handle AP reboot or power loss scenarios where device │ +/// │ connects to suboptimal AP and never switches back │ +/// │ │ +/// │ Loop call site: roaming enabled && attempts < 3 && 5 min elapsed │ +/// │ ↓ │ +/// │ ┌─────────────────┐ Hidden? ┌──────────────────────────┐ │ +/// │ │ check_roaming_ ├───────────→│ attempts = MAX, stop │ │ +/// │ └────────┬────────┘ └──────────────────────────┘ │ +/// │ ↓ │ +/// │ attempts++, update last_check │ +/// │ ↓ │ +/// │ RSSI > -49 dBm? ────Yes────→ Skip scan (excellent signal)─┐ │ +/// │ ↓ No │ │ +/// │ ┌─────────────────┐ │ │ +/// │ │ Start scan │ │ │ +/// │ └────────┬────────┘ │ │ +/// │ ↓ │ │ +/// │ ┌────────────────────────┐ │ │ +/// │ │ process_roaming_scan_ │ │ │ +/// │ └────────┬───────────────┘ │ │ +/// │ ↓ │ │ +/// │ ┌─────────────────┐ No ┌───────────────┐ │ │ +/// │ │ +10 dB better AP├────────→│ Stay connected│───────────────┤ │ +/// │ └────────┬────────┘ └───────────────┘ │ │ +/// │ │ Yes │ │ +/// │ ↓ │ │ +/// │ ┌─────────────────┐ │ │ +/// │ │ start_connecting│ (roaming_connect_active_ = true) │ │ +/// │ └────────┬────────┘ │ │ +/// │ ↓ │ │ +/// │ ┌────┴────┐ │ │ +/// │ ↓ ↓ │ │ +/// │ ┌───────┐ ┌───────┐ │ │ +/// │ │SUCCESS│ │FAILED │ │ │ +/// │ └───┬───┘ └───┬───┘ │ │ +/// │ ↓ ↓ │ │ +/// │ Keep counter retry_connect() → normal reconnect flow │ │ +/// │ (no reset) (keeps counter, handles retries) │ │ +/// │ │ │ │ │ +/// │ └──────────────┴────────────────────────────────────────┘ │ +/// │ │ +/// │ After 3 checks: attempts >= 3, stop checking │ +/// │ Non-roaming disconnect: clear_roaming_state_() resets counter │ +/// │ Roaming success: counter preserved (prevents ping-pong) │ +/// │ Roaming fail: normal flow handles reconnection, counter preserved │ +/// └──────────────────────────────────────────────────────────────────────┘ static const LogString *retry_phase_to_log_string(WiFiRetryPhase phase) { switch (phase) { @@ -484,7 +535,7 @@ void WiFiComponent::loop() { // Skip cooldown if new credentials were provided while connecting if (this->skip_cooldown_next_cycle_) { this->skip_cooldown_next_cycle_ = false; - this->check_connecting_finished(); + this->check_connecting_finished(now); break; } // Use longer cooldown when captive portal/improv is active to avoid disrupting user config @@ -495,7 +546,7 @@ void WiFiComponent::loop() { // a failure, or something tried to connect over and over // so we entered cooldown. In both cases we call // check_connecting_finished to continue the state machine. - this->check_connecting_finished(); + this->check_connecting_finished(now); } break; } @@ -506,7 +557,7 @@ void WiFiComponent::loop() { } case WIFI_COMPONENT_STATE_STA_CONNECTING: { this->status_set_warning(LOG_STR("associating to network")); - this->check_connecting_finished(); + this->check_connecting_finished(now); break; } @@ -520,6 +571,19 @@ void WiFiComponent::loop() { } else { this->status_clear_warning(); this->last_connected_ = now; + + // Post-connect roaming: check for better AP + if (this->post_connect_roaming_) { + if (this->roaming_scan_active_) { + if (this->scan_done_) { + this->process_roaming_scan_(); + } + // else: scan in progress, wait + } else if (this->roaming_attempts_ < ROAMING_MAX_ATTEMPTS && + now - this->roaming_last_check_ >= ROAMING_CHECK_INTERVAL) { + this->check_roaming_(now); + } + } } break; } @@ -679,8 +743,14 @@ float WiFiComponent::get_loop_priority() const { void WiFiComponent::init_sta(size_t count) { this->sta_.init(count); } void WiFiComponent::add_sta(const WiFiAP &ap) { this->sta_.push_back(ap); } +void WiFiComponent::clear_sta() { + // Clear roaming state - no more configured networks + this->clear_roaming_state_(); + this->sta_.clear(); + this->selected_sta_index_ = -1; +} void WiFiComponent::set_sta(const WiFiAP &ap) { - this->clear_sta(); + this->clear_sta(); // Also clears roaming state this->init_sta(1); this->add_sta(ap); this->selected_sta_index_ = 0; @@ -1184,7 +1254,7 @@ void WiFiComponent::dump_config() { } } -void WiFiComponent::check_connecting_finished() { +void WiFiComponent::check_connecting_finished(uint32_t now) { auto status = this->wifi_sta_connect_status_(); if (status == WiFiSTAConnectStatus::CONNECTED) { @@ -1230,23 +1300,28 @@ void WiFiComponent::check_connecting_finished() { this->num_retried_ = 0; this->print_connect_params_(); - // Clear priority tracking if all priorities are at minimum - this->clear_priorities_if_all_min_(); + // Reset roaming state on successful connection + this->roaming_last_check_ = now; + // Only reset attempts if this wasn't a roaming-triggered connection + // (prevents ping-pong between APs) + if (!this->roaming_connect_active_) { + this->roaming_attempts_ = 0; + } + this->roaming_connect_active_ = false; + + // Clear all priority penalties - the next reconnect will happen when an AP disconnects, + // which means the landscape has likely changed and previous tracked failures are stale + this->clear_all_bssid_priorities_(); #ifdef USE_WIFI_FAST_CONNECT this->save_fast_connect_settings_(); #endif - // Free scan results memory unless a component needs them - if (!this->keep_scan_results_) { - this->scan_result_.clear(); - this->scan_result_.shrink_to_fit(); - } + this->release_scan_results_(); return; } - uint32_t now = millis(); if (now - this->action_started_ > WIFI_CONNECT_TIMEOUT_MS) { ESP_LOGW(TAG, "Connection timeout, aborting connection attempt"); this->wifi_disconnect_(); @@ -1490,9 +1565,15 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) { return false; // Did not start scan, can proceed with connection } +void WiFiComponent::clear_all_bssid_priorities_() { + if (!this->sta_priorities_.empty()) { + decltype(this->sta_priorities_)().swap(this->sta_priorities_); + } +} + /// Clear BSSID priority tracking if all priorities are at minimum (saves memory) /// At minimum priority, all BSSIDs are equally bad, so priority tracking is useless -/// Called after successful connection or after failed connection attempts +/// Called after failed connection attempts void WiFiComponent::clear_priorities_if_all_min_() { if (this->sta_priorities_.empty()) { return; @@ -1514,8 +1595,7 @@ void WiFiComponent::clear_priorities_if_all_min_() { // All priorities are at minimum - clear the vector to save memory and reset ESP_LOGD(TAG, "Clearing BSSID priorities (all at minimum)"); - this->sta_priorities_.clear(); - this->sta_priorities_.shrink_to_fit(); + this->clear_all_bssid_priorities_(); } /// Log failed connection attempt and decrease BSSID priority to avoid repeated failures @@ -1653,6 +1733,17 @@ void WiFiComponent::advance_to_next_target_or_increment_retry_() { } void WiFiComponent::retry_connect() { + // If this was a roaming attempt, preserve roaming_attempts_ count + // (so we stop roaming after ROAMING_MAX_ATTEMPTS failures) + // Otherwise reset all roaming state + if (this->roaming_connect_active_) { + this->roaming_connect_active_ = false; + this->roaming_scan_active_ = false; + // Keep roaming_attempts_ - will prevent further roaming after max failures + } else { + this->clear_roaming_state_(); + } + this->log_and_adjust_priority_for_failed_connect_(); // Determine next retry phase based on current state @@ -1895,6 +1986,106 @@ bool WiFiScanResult::get_is_hidden() const { return this->is_hidden_; } bool WiFiScanResult::operator==(const WiFiScanResult &rhs) const { return this->bssid_ == rhs.bssid_; } +void WiFiComponent::clear_roaming_state_() { + this->roaming_attempts_ = 0; + this->roaming_last_check_ = 0; + this->roaming_scan_active_ = false; + this->roaming_connect_active_ = false; +} + +void WiFiComponent::release_scan_results_() { + if (!this->keep_scan_results_) { +#ifdef USE_RP2040 + // std::vector - use swap trick since shrink_to_fit is non-binding + decltype(this->scan_result_)().swap(this->scan_result_); +#else + // FixedVector::shrink_to_fit() actually frees all memory + this->scan_result_.shrink_to_fit(); +#endif + } +} + +void WiFiComponent::check_roaming_(uint32_t now) { + // Guard: not for hidden networks (may not appear in scan) + const WiFiAP *selected = this->get_selected_sta_(); + if (selected == nullptr || selected->get_hidden()) { + this->roaming_attempts_ = ROAMING_MAX_ATTEMPTS; // Stop checking forever + return; + } + + this->roaming_last_check_ = now; + this->roaming_attempts_++; + + // Guard: skip scan if signal is already good (no meaningful improvement possible) + int8_t rssi = this->wifi_rssi(); + if (rssi > ROAMING_GOOD_RSSI) + return; + + ESP_LOGD(TAG, "Roam scan (%d dBm)", rssi); + this->roaming_scan_active_ = true; + this->wifi_scan_start_(this->passive_scan_); +} + +void WiFiComponent::process_roaming_scan_() { + this->scan_done_ = false; + this->roaming_scan_active_ = false; + + // Get current connection info + int8_t current_rssi = this->wifi_rssi(); + // Guard: must still be connected (RSSI may have become invalid during scan) + if (current_rssi == WIFI_RSSI_DISCONNECTED) { + this->release_scan_results_(); + return; + } + + char ssid_buf[SSID_BUFFER_SIZE]; + StringRef current_ssid(this->wifi_ssid_to(ssid_buf)); + bssid_t current_bssid = this->wifi_bssid(); + + // Find best candidate: same SSID, different BSSID + const WiFiScanResult *best = nullptr; + char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + + for (const auto &result : this->scan_result_) { + // Must be same SSID, different BSSID + if (current_ssid != result.get_ssid() || result.get_bssid() == current_bssid) + continue; + +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + format_mac_addr_upper(result.get_bssid().data(), bssid_buf); + ESP_LOGV(TAG, "Roam candidate %s %d dBm", bssid_buf, result.get_rssi()); +#endif + + // Track the best candidate + if (best == nullptr || result.get_rssi() > best->get_rssi()) { + best = &result; + } + } + + // Check if best candidate meets minimum improvement threshold + const WiFiAP *selected = this->get_selected_sta_(); + int8_t improvement = (best == nullptr) ? 0 : best->get_rssi() - current_rssi; + if (selected == nullptr || improvement < ROAMING_MIN_IMPROVEMENT) { + ESP_LOGV(TAG, "Roam best %+d dB (need +%d)", improvement, ROAMING_MIN_IMPROVEMENT); + this->release_scan_results_(); + return; + } + + format_mac_addr_upper(best->get_bssid().data(), bssid_buf); + ESP_LOGI(TAG, "Roaming to %s (%+d dB)", bssid_buf, improvement); + + WiFiAP roam_params = *selected; + apply_scan_result_to_params(roam_params, *best); + this->release_scan_results_(); + + // Mark as roaming attempt - affects retry behavior if connection fails + this->roaming_connect_active_ = true; + + // Connect directly - wifi_sta_connect_ handles disconnect internally + this->error_from_callback_ = false; + this->start_connecting(roam_params); +} + WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esphome::wifi diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 1906b672b8..09af384725 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -301,10 +301,7 @@ class WiFiComponent : public Component { WiFiAP get_sta() const; void init_sta(size_t count); void add_sta(const WiFiAP &ap); - void clear_sta() { - this->sta_.clear(); - this->selected_sta_index_ = -1; - } + void clear_sta(); #ifdef USE_WIFI_AP /** Setup an Access Point that should be created if no connection to a station can be made. @@ -328,7 +325,7 @@ class WiFiComponent : public Component { // Backward compatibility overload - ignores 'two' parameter void start_connecting(const WiFiAP &ap, bool /* two */) { this->start_connecting(ap); } - void check_connecting_finished(); + void check_connecting_finished(uint32_t now); void retry_connect(); @@ -418,6 +415,7 @@ class WiFiComponent : public Component { void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; } void set_keep_scan_results(bool keep_scan_results) { this->keep_scan_results_ = keep_scan_results; } + void set_post_connect_roaming(bool enabled) { this->post_connect_roaming_ = enabled; } Trigger<> *get_connect_trigger() const { return this->connect_trigger_; }; Trigger<> *get_disconnect_trigger() const { return this->disconnect_trigger_; }; @@ -507,6 +505,8 @@ class WiFiComponent : public Component { int8_t find_next_hidden_sta_(int8_t start_index); /// Log failed connection and decrease BSSID priority to avoid repeated attempts void log_and_adjust_priority_for_failed_connect_(); + /// Clear all BSSID priority penalties after successful connection (stale after disconnect) + void clear_all_bssid_priorities_(); /// Clear BSSID priority tracking if all priorities are at minimum (saves memory) void clear_priorities_if_all_min_(); /// Advance to next target (AP/SSID) within current phase, or increment retry counter @@ -570,6 +570,14 @@ class WiFiComponent : public Component { void save_fast_connect_settings_(); #endif + // Post-connect roaming methods + void check_roaming_(uint32_t now); + void process_roaming_scan_(); + void clear_roaming_state_(); + + /// Free scan results memory unless a component needs them + void release_scan_results_(); + #ifdef USE_ESP8266 static void wifi_event_callback(System_Event_t *event); void wifi_scan_done_callback_(void *arg, STATUS status); @@ -609,10 +617,17 @@ class WiFiComponent : public Component { ESPPreferenceObject fast_connect_pref_; #endif + // Post-connect roaming constants + static constexpr uint32_t ROAMING_CHECK_INTERVAL = 5 * 60 * 1000; // 5 minutes + static constexpr int8_t ROAMING_MIN_IMPROVEMENT = 10; // dB + static constexpr int8_t ROAMING_GOOD_RSSI = -49; // Skip scan if signal is excellent + static constexpr uint8_t ROAMING_MAX_ATTEMPTS = 3; + // Group all 32-bit integers together uint32_t action_started_; uint32_t last_connected_{0}; uint32_t reboot_timeout_{}; + uint32_t roaming_last_check_{0}; #ifdef USE_WIFI_AP uint32_t ap_timeout_{}; #endif @@ -627,6 +642,7 @@ class WiFiComponent : public Component { // Used to access password, manual_ip, priority, EAP settings, and hidden flag // int8_t limits to 127 APs (enforced in __init__.py via MAX_WIFI_NETWORKS) int8_t selected_sta_index_{-1}; + uint8_t roaming_attempts_{0}; #if USE_NETWORK_IPV6 uint8_t num_ipv6_addresses_{0}; @@ -650,6 +666,9 @@ class WiFiComponent : public Component { bool keep_scan_results_{false}; bool did_scan_this_cycle_{false}; bool skip_cooldown_next_cycle_{false}; + bool post_connect_roaming_{true}; // Enabled by default + bool roaming_scan_active_{false}; + bool roaming_connect_active_{false}; // True during roaming connection attempt (preserves roaming_attempts_) #if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE) WiFiPowerSaveMode configured_power_save_{WIFI_POWER_SAVE_NONE}; bool is_high_performance_mode_{false}; diff --git a/tests/components/wifi/test.esp32-idf.yaml b/tests/components/wifi/test.esp32-idf.yaml index 3e01d7f990..b2b2233ef3 100644 --- a/tests/components/wifi/test.esp32-idf.yaml +++ b/tests/components/wifi/test.esp32-idf.yaml @@ -14,6 +14,7 @@ esphome: wifi: use_psram: true min_auth_mode: WPA + post_connect_roaming: false manual_ip: static_ip: 192.168.1.100 gateway: 192.168.1.1 diff --git a/tests/components/wifi/test.esp8266-ard.yaml b/tests/components/wifi/test.esp8266-ard.yaml index 9cb0e3cf48..709a639ad6 100644 --- a/tests/components/wifi/test.esp8266-ard.yaml +++ b/tests/components/wifi/test.esp8266-ard.yaml @@ -1,5 +1,6 @@ wifi: min_auth_mode: WPA2 + post_connect_roaming: true packages: - !include common.yaml From 44eac36e05a7d89bbe90890319e01e12bb184288 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 10:19:10 -1000 Subject: [PATCH 1140/1145] [debug] Use stack buffers with buf_append helper instead of std::string (#13020) --- esphome/components/debug/debug_component.cpp | 15 +- esphome/components/debug/debug_component.h | 51 ++++- esphome/components/debug/debug_esp32.cpp | 105 ++++----- esphome/components/debug/debug_esp8266.cpp | 73 ++++--- esphome/components/debug/debug_host.cpp | 6 +- esphome/components/debug/debug_libretiny.cpp | 44 ++-- esphome/components/debug/debug_rp2040.cpp | 16 +- esphome/components/debug/debug_zephyr.cpp | 218 ++++++++++--------- 8 files changed, 320 insertions(+), 208 deletions(-) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index f54bf82eae..ae38fb2ccd 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -28,24 +28,23 @@ void DebugComponent::dump_config() { #endif // defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) #endif // USE_SENSOR - std::string device_info; - device_info.reserve(256); + char device_info_buffer[DEVICE_INFO_BUFFER_SIZE]; ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); - device_info += ESPHOME_VERSION; + size_t pos = buf_append(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, "%s", ESPHOME_VERSION); this->free_heap_ = get_free_heap_(); ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_); - get_device_info_(device_info); + pos = get_device_info_(std::span(device_info_buffer), pos); #ifdef USE_TEXT_SENSOR if (this->device_info_ != nullptr) { - if (device_info.length() > 255) - device_info.resize(255); - this->device_info_->publish_state(device_info); + this->device_info_->publish_state(device_info_buffer, pos); } if (this->reset_reason_ != nullptr) { - this->reset_reason_->publish_state(get_reset_reason_()); + char reset_reason_buffer[RESET_REASON_BUFFER_SIZE]; + this->reset_reason_->publish_state( + get_reset_reason_(std::span(reset_reason_buffer))); } #endif // USE_TEXT_SENSOR diff --git a/esphome/components/debug/debug_component.h b/esphome/components/debug/debug_component.h index 96306f7cdf..5783bc5418 100644 --- a/esphome/components/debug/debug_component.h +++ b/esphome/components/debug/debug_component.h @@ -4,6 +4,13 @@ #include "esphome/core/defines.h" #include "esphome/core/helpers.h" #include "esphome/core/macros.h" +#include +#include +#include +#include +#ifdef USE_ESP8266 +#include +#endif #ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" @@ -15,6 +22,44 @@ namespace esphome { namespace debug { +static constexpr size_t DEVICE_INFO_BUFFER_SIZE = 256; +static constexpr size_t RESET_REASON_BUFFER_SIZE = 128; + +#ifdef USE_ESP8266 +// ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM) +// Format strings must be wrapped with PSTR() macro +inline size_t buf_append_p(char *buf, size_t size, size_t pos, PGM_P fmt, ...) { + if (pos >= size) { + return size; + } + va_list args; + va_start(args, fmt); + int written = vsnprintf_P(buf + pos, size - pos, fmt, args); + va_end(args); + if (written < 0) { + return pos; // encoding error + } + return std::min(pos + static_cast(written), size); +} +#define buf_append(buf, size, pos, fmt, ...) buf_append_p(buf, size, pos, PSTR(fmt), ##__VA_ARGS__) +#else +/// Safely append formatted string to buffer, returning new position (capped at size) +__attribute__((format(printf, 4, 5))) inline size_t buf_append(char *buf, size_t size, size_t pos, const char *fmt, + ...) { + if (pos >= size) { + return size; + } + va_list args; + va_start(args, fmt); + int written = vsnprintf(buf + pos, size - pos, fmt, args); + va_end(args); + if (written < 0) { + return pos; // encoding error + } + return std::min(pos + static_cast(written), size); +} +#endif + class DebugComponent : public PollingComponent { public: void loop() override; @@ -81,10 +126,10 @@ class DebugComponent : public PollingComponent { text_sensor::TextSensor *reset_reason_{nullptr}; #endif // USE_TEXT_SENSOR - std::string get_reset_reason_(); - std::string get_wakeup_cause_(); + const char *get_reset_reason_(std::span buffer); + const char *get_wakeup_cause_(std::span buffer); uint32_t get_free_heap_(); - void get_device_info_(std::string &device_info); + size_t get_device_info_(std::span buffer, size_t pos); void update_platform_(); }; diff --git a/esphome/components/debug/debug_esp32.cpp b/esphome/components/debug/debug_esp32.cpp index 25852b32a7..ebb6abf4da 100644 --- a/esphome/components/debug/debug_esp32.cpp +++ b/esphome/components/debug/debug_esp32.cpp @@ -58,24 +58,29 @@ void DebugComponent::on_shutdown() { global_preferences->sync(); } -std::string DebugComponent::get_reset_reason_() { - std::string reset_reason; +const char *DebugComponent::get_reset_reason_(std::span buffer) { + char *buf = buffer.data(); + const size_t size = RESET_REASON_BUFFER_SIZE; + unsigned reason = esp_reset_reason(); if (reason < sizeof(RESET_REASONS) / sizeof(RESET_REASONS[0])) { - reset_reason = RESET_REASONS[reason]; if (reason == ESP_RST_SW) { auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name())); - char buffer[REBOOT_MAX_LEN]{}; - if (pref.load(&buffer)) { - buffer[REBOOT_MAX_LEN - 1] = '\0'; - reset_reason = "Reboot request from " + std::string(buffer); + char reboot_source[REBOOT_MAX_LEN]{}; + if (pref.load(&reboot_source)) { + reboot_source[REBOOT_MAX_LEN - 1] = '\0'; + snprintf(buf, size, "Reboot request from %s", reboot_source); + } else { + snprintf(buf, size, "%s", RESET_REASONS[reason]); } + } else { + snprintf(buf, size, "%s", RESET_REASONS[reason]); } } else { - reset_reason = "unknown source"; + snprintf(buf, size, "unknown source"); } - ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); - return reset_reason; + ESP_LOGD(TAG, "Reset Reason: %s", buf); + return buf; } static const char *const WAKEUP_CAUSES[] = { @@ -94,7 +99,7 @@ static const char *const WAKEUP_CAUSES[] = { "BT", }; -std::string DebugComponent::get_wakeup_cause_() { +const char *DebugComponent::get_wakeup_cause_(std::span buffer) { const char *wake_reason; unsigned reason = esp_sleep_get_wakeup_cause(); if (reason < sizeof(WAKEUP_CAUSES) / sizeof(WAKEUP_CAUSES[0])) { @@ -103,6 +108,7 @@ std::string DebugComponent::get_wakeup_cause_() { wake_reason = "unknown source"; } ESP_LOGD(TAG, "Wakeup Reason: %s", wake_reason); + // Return the static string directly - no need to copy to buffer return wake_reason; } @@ -136,7 +142,10 @@ static constexpr ChipFeature CHIP_FEATURES[] = { {CHIP_FEATURE_WIFI_BGN, "2.4GHz WiFi"}, }; -void DebugComponent::get_device_info_(std::string &device_info) { +size_t DebugComponent::get_device_info_(std::span buffer, size_t pos) { + constexpr size_t size = DEVICE_INFO_BUFFER_SIZE; + char *buf = buffer.data(); + #if defined(USE_ARDUINO) const char *flash_mode; switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) @@ -161,68 +170,66 @@ void DebugComponent::get_device_info_(std::string &device_info) { default: flash_mode = "UNKNOWN"; } - ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", - ESP.getFlashChipSize() / 1024, // NOLINT - ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT - device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT - "kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT - device_info += flash_mode; + uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT + uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT + ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode); + pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed, + flash_mode); #endif esp_chip_info_t info; esp_chip_info(&info); const char *model = ESPHOME_VARIANT; - std::string features; - // Check each known feature bit + // Build features string + pos = buf_append(buf, size, pos, "|Chip: %s Features:", model); + bool first_feature = true; for (const auto &feature : CHIP_FEATURES) { if (info.features & feature.bit) { - features += feature.name; - features += ", "; + pos = buf_append(buf, size, pos, "%s%s", first_feature ? "" : ", ", feature.name); + first_feature = false; info.features &= ~feature.bit; } } - if (info.features != 0) - features += "Other:" + format_hex(info.features); - ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores, - info.revision); - device_info += "|Chip: "; - device_info += model; - device_info += " Features:"; - device_info += features; - device_info += " Cores:" + to_string(info.cores); - device_info += " Revision:" + to_string(info.revision); - device_info += str_sprintf("|CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000); - ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000); + if (info.features != 0) { + pos = buf_append(buf, size, pos, "%sOther:0x%" PRIx32, first_feature ? "" : ", ", info.features); + } + ESP_LOGD(TAG, "Chip: Model=%s, Cores=%u, Revision=%u", model, info.cores, info.revision); + pos = buf_append(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision); + + uint32_t cpu_freq_mhz = arch_get_cpu_freq_hz() / 1000000; + ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz); + pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz); // Framework detection - device_info += "|Framework: "; #ifdef USE_ARDUINO ESP_LOGD(TAG, "Framework: Arduino"); - device_info += "Arduino"; + pos = buf_append(buf, size, pos, "|Framework: Arduino"); #elif defined(USE_ESP32) ESP_LOGD(TAG, "Framework: ESP-IDF"); - device_info += "ESP-IDF"; + pos = buf_append(buf, size, pos, "|Framework: ESP-IDF"); #else ESP_LOGW(TAG, "Framework: UNKNOWN"); - device_info += "UNKNOWN"; + pos = buf_append(buf, size, pos, "|Framework: UNKNOWN"); #endif ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); - device_info += "|ESP-IDF: "; - device_info += esp_get_idf_version(); + pos = buf_append(buf, size, pos, "|ESP-IDF: %s", esp_get_idf_version()); - std::string mac = get_mac_address_pretty(); - ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str()); - device_info += "|EFuse MAC: "; - device_info += mac; + uint8_t mac[6]; + get_mac_address_raw(mac); + ESP_LOGD(TAG, "EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + pos = buf_append(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], + mac[5]); - device_info += "|Reset: "; - device_info += get_reset_reason_(); + char reason_buffer[RESET_REASON_BUFFER_SIZE]; + const char *reset_reason = get_reset_reason_(std::span(reason_buffer)); + pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason); - std::string wakeup_reason = this->get_wakeup_cause_(); - device_info += "|Wakeup: "; - device_info += wakeup_reason; + const char *wakeup_cause = get_wakeup_cause_(std::span(reason_buffer)); + pos = buf_append(buf, size, pos, "|Wakeup: %s", wakeup_cause); + + return pos; } void DebugComponent::update_platform_() { diff --git a/esphome/components/debug/debug_esp8266.cpp b/esphome/components/debug/debug_esp8266.cpp index 7427b32290..274f77e20d 100644 --- a/esphome/components/debug/debug_esp8266.cpp +++ b/esphome/components/debug/debug_esp8266.cpp @@ -8,19 +8,31 @@ namespace debug { static const char *const TAG = "debug"; -std::string DebugComponent::get_reset_reason_() { +const char *DebugComponent::get_reset_reason_(std::span buffer) { + char *buf = buffer.data(); #if !defined(CLANG_TIDY) - return ESP.getResetReason().c_str(); + String reason = ESP.getResetReason(); // NOLINT + snprintf_P(buf, RESET_REASON_BUFFER_SIZE, PSTR("%s"), reason.c_str()); + return buf; #else - return ""; + buf[0] = '\0'; + return buf; #endif } +const char *DebugComponent::get_wakeup_cause_(std::span buffer) { + // ESP8266 doesn't have detailed wakeup cause like ESP32 + return ""; +} + uint32_t DebugComponent::get_free_heap_() { return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) } -void DebugComponent::get_device_info_(std::string &device_info) { +size_t DebugComponent::get_device_info_(std::span buffer, size_t pos) { + constexpr size_t size = DEVICE_INFO_BUFFER_SIZE; + char *buf = buffer.data(); + const char *flash_mode; switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) case FM_QIO: @@ -38,42 +50,45 @@ void DebugComponent::get_device_info_(std::string &device_info) { default: flash_mode = "UNKNOWN"; } - ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", - ESP.getFlashChipSize() / 1024, // NOLINT - ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT - device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT - "kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT - device_info += flash_mode; + uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT + uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT + ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode); + pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed, + flash_mode); #if !defined(CLANG_TIDY) - auto reset_reason = get_reset_reason_(); + char reason_buffer[RESET_REASON_BUFFER_SIZE]; + const char *reset_reason = get_reset_reason_(std::span(reason_buffer)); + uint32_t chip_id = ESP.getChipId(); + uint8_t boot_version = ESP.getBootVersion(); + uint8_t boot_mode = ESP.getBootMode(); + uint8_t cpu_freq = ESP.getCpuFreqMHz(); + uint32_t flash_chip_id = ESP.getFlashChipId(); + ESP_LOGD(TAG, - "Chip ID: 0x%08X\n" + "Chip ID: 0x%08" PRIX32 "\n" "SDK Version: %s\n" "Core Version: %s\n" "Boot Version=%u Mode=%u\n" "CPU Frequency: %u\n" - "Flash Chip ID=0x%08X\n" + "Flash Chip ID=0x%08" PRIX32 "\n" "Reset Reason: %s\n" "Reset Info: %s", - ESP.getChipId(), ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), ESP.getBootVersion(), ESP.getBootMode(), - ESP.getCpuFreqMHz(), ESP.getFlashChipId(), reset_reason.c_str(), ESP.getResetInfo().c_str()); + chip_id, ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), boot_version, boot_mode, cpu_freq, flash_chip_id, + reset_reason, ESP.getResetInfo().c_str()); - device_info += "|Chip: 0x" + format_hex(ESP.getChipId()); - device_info += "|SDK: "; - device_info += ESP.getSdkVersion(); - device_info += "|Core: "; - device_info += ESP.getCoreVersion().c_str(); - device_info += "|Boot: "; - device_info += to_string(ESP.getBootVersion()); - device_info += "|Mode: " + to_string(ESP.getBootMode()); - device_info += "|CPU: " + to_string(ESP.getCpuFreqMHz()); - device_info += "|Flash: 0x" + format_hex(ESP.getFlashChipId()); - device_info += "|Reset: "; - device_info += reset_reason; - device_info += "|"; - device_info += ESP.getResetInfo().c_str(); + pos = buf_append(buf, size, pos, "|Chip: 0x%08" PRIX32, chip_id); + pos = buf_append(buf, size, pos, "|SDK: %s", ESP.getSdkVersion()); + pos = buf_append(buf, size, pos, "|Core: %s", ESP.getCoreVersion().c_str()); + pos = buf_append(buf, size, pos, "|Boot: %u", boot_version); + pos = buf_append(buf, size, pos, "|Mode: %u", boot_mode); + pos = buf_append(buf, size, pos, "|CPU: %u", cpu_freq); + pos = buf_append(buf, size, pos, "|Flash: 0x%08" PRIX32, flash_chip_id); + pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason); + pos = buf_append(buf, size, pos, "|%s", ESP.getResetInfo().c_str()); #endif + + return pos; } void DebugComponent::update_platform_() { diff --git a/esphome/components/debug/debug_host.cpp b/esphome/components/debug/debug_host.cpp index 09ad34ef88..2fa88f0909 100644 --- a/esphome/components/debug/debug_host.cpp +++ b/esphome/components/debug/debug_host.cpp @@ -5,11 +5,13 @@ namespace esphome { namespace debug { -std::string DebugComponent::get_reset_reason_() { return ""; } +const char *DebugComponent::get_reset_reason_(std::span buffer) { return ""; } + +const char *DebugComponent::get_wakeup_cause_(std::span buffer) { return ""; } uint32_t DebugComponent::get_free_heap_() { return INT_MAX; } -void DebugComponent::get_device_info_(std::string &device_info) {} +size_t DebugComponent::get_device_info_(std::span buffer, size_t pos) { return pos; } void DebugComponent::update_platform_() {} diff --git a/esphome/components/debug/debug_libretiny.cpp b/esphome/components/debug/debug_libretiny.cpp index e823ac6c77..4f07a4cc17 100644 --- a/esphome/components/debug/debug_libretiny.cpp +++ b/esphome/components/debug/debug_libretiny.cpp @@ -7,31 +7,43 @@ namespace debug { static const char *const TAG = "debug"; -std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_name(lt_get_reboot_reason()); } +const char *DebugComponent::get_reset_reason_(std::span buffer) { + // Return the static string directly + return lt_get_reboot_reason_name(lt_get_reboot_reason()); +} + +const char *DebugComponent::get_wakeup_cause_(std::span buffer) { return ""; } uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); } -void DebugComponent::get_device_info_(std::string &device_info) { - std::string reset_reason = get_reset_reason_(); +size_t DebugComponent::get_device_info_(std::span buffer, size_t pos) { + constexpr size_t size = DEVICE_INFO_BUFFER_SIZE; + char *buf = buffer.data(); + + char reason_buffer[RESET_REASON_BUFFER_SIZE]; + const char *reset_reason = get_reset_reason_(std::span(reason_buffer)); + uint32_t flash_kib = lt_flash_get_size() / 1024; + uint32_t ram_kib = lt_ram_get_size() / 1024; + uint32_t mac_id = lt_cpu_get_mac_id(); + ESP_LOGD(TAG, "LibreTiny Version: %s\n" "Chip: %s (%04x) @ %u MHz\n" - "Chip ID: 0x%06X\n" + "Chip ID: 0x%06" PRIX32 "\n" "Board: %s\n" - "Flash: %u KiB / RAM: %u KiB\n" + "Flash: %" PRIu32 " KiB / RAM: %" PRIu32 " KiB\n" "Reset Reason: %s", - lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), lt_cpu_get_mac_id(), - lt_get_board_code(), lt_flash_get_size() / 1024, lt_ram_get_size() / 1024, reset_reason.c_str()); + lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), mac_id, + lt_get_board_code(), flash_kib, ram_kib, reset_reason); - device_info += "|Version: "; - device_info += LT_BANNER_STR + 10; - device_info += "|Reset Reason: "; - device_info += reset_reason; - device_info += "|Chip Name: "; - device_info += lt_cpu_get_model_name(); - device_info += "|Chip ID: 0x" + format_hex(lt_cpu_get_mac_id()); - device_info += "|Flash: " + to_string(lt_flash_get_size() / 1024) + " KiB"; - device_info += "|RAM: " + to_string(lt_ram_get_size() / 1024) + " KiB"; + pos = buf_append(buf, size, pos, "|Version: %s", LT_BANNER_STR + 10); + pos = buf_append(buf, size, pos, "|Reset Reason: %s", reset_reason); + pos = buf_append(buf, size, pos, "|Chip Name: %s", lt_cpu_get_model_name()); + pos = buf_append(buf, size, pos, "|Chip ID: 0x%06" PRIX32, mac_id); + pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 " KiB", flash_kib); + pos = buf_append(buf, size, pos, "|RAM: %" PRIu32 " KiB", ram_kib); + + return pos; } void DebugComponent::update_platform_() { diff --git a/esphome/components/debug/debug_rp2040.cpp b/esphome/components/debug/debug_rp2040.cpp index 497547e30d..a426a73bc2 100644 --- a/esphome/components/debug/debug_rp2040.cpp +++ b/esphome/components/debug/debug_rp2040.cpp @@ -7,13 +7,21 @@ namespace debug { static const char *const TAG = "debug"; -std::string DebugComponent::get_reset_reason_() { return ""; } +const char *DebugComponent::get_reset_reason_(std::span buffer) { return ""; } + +const char *DebugComponent::get_wakeup_cause_(std::span buffer) { return ""; } uint32_t DebugComponent::get_free_heap_() { return rp2040.getFreeHeap(); } -void DebugComponent::get_device_info_(std::string &device_info) { - ESP_LOGD(TAG, "CPU Frequency: %u", rp2040.f_cpu()); - device_info += "CPU Frequency: " + to_string(rp2040.f_cpu()); +size_t DebugComponent::get_device_info_(std::span buffer, size_t pos) { + constexpr size_t size = DEVICE_INFO_BUFFER_SIZE; + char *buf = buffer.data(); + + uint32_t cpu_freq = rp2040.f_cpu(); + ESP_LOGD(TAG, "CPU Frequency: %" PRIu32, cpu_freq); + pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq); + + return pos; } void DebugComponent::update_platform_() {} diff --git a/esphome/components/debug/debug_zephyr.cpp b/esphome/components/debug/debug_zephyr.cpp index 6abf983e9e..85880595b6 100644 --- a/esphome/components/debug/debug_zephyr.cpp +++ b/esphome/components/debug/debug_zephyr.cpp @@ -15,14 +15,14 @@ static const char *const TAG = "debug"; constexpr std::uintptr_t MBR_PARAM_PAGE_ADDR = 0xFFC; constexpr std::uintptr_t MBR_BOOTLOADER_ADDR = 0xFF8; -static void show_reset_reason(std::string &reset_reason, bool set, const char *reason) { +static size_t append_reset_reason(char *buf, size_t size, size_t pos, bool set, const char *reason) { if (!set) { - return; + return pos; } - if (!reset_reason.empty()) { - reset_reason += ", "; + if (pos > 0) { + pos = buf_append(buf, size, pos, ", "); } - reset_reason += reason; + return buf_append(buf, size, pos, "%s", reason); } static inline uint32_t read_mem_u32(uintptr_t addr) { @@ -56,33 +56,47 @@ static inline uint32_t sd_version_get() { return 0; } -std::string DebugComponent::get_reset_reason_() { +const char *DebugComponent::get_reset_reason_(std::span buffer) { + char *buf = buffer.data(); + const size_t size = RESET_REASON_BUFFER_SIZE; + uint32_t cause; auto ret = hwinfo_get_reset_cause(&cause); if (ret) { ESP_LOGE(TAG, "Unable to get reset cause: %d", ret); - return ""; + buf[0] = '\0'; + return buf; } - std::string reset_reason; + size_t pos = 0; - show_reset_reason(reset_reason, cause & RESET_PIN, "External pin"); - show_reset_reason(reset_reason, cause & RESET_SOFTWARE, "Software reset"); - show_reset_reason(reset_reason, cause & RESET_BROWNOUT, "Brownout (drop in voltage)"); - show_reset_reason(reset_reason, cause & RESET_POR, "Power-on reset (POR)"); - show_reset_reason(reset_reason, cause & RESET_WATCHDOG, "Watchdog timer expiration"); - show_reset_reason(reset_reason, cause & RESET_DEBUG, "Debug event"); - show_reset_reason(reset_reason, cause & RESET_SECURITY, "Security violation"); - show_reset_reason(reset_reason, cause & RESET_LOW_POWER_WAKE, "Waking up from low power mode"); - show_reset_reason(reset_reason, cause & RESET_CPU_LOCKUP, "CPU lock-up detected"); - show_reset_reason(reset_reason, cause & RESET_PARITY, "Parity error"); - show_reset_reason(reset_reason, cause & RESET_PLL, "PLL error"); - show_reset_reason(reset_reason, cause & RESET_CLOCK, "Clock error"); - show_reset_reason(reset_reason, cause & RESET_HARDWARE, "Hardware reset"); - show_reset_reason(reset_reason, cause & RESET_USER, "User reset"); - show_reset_reason(reset_reason, cause & RESET_TEMPERATURE, "Temperature reset"); + pos = append_reset_reason(buf, size, pos, cause & RESET_PIN, "External pin"); + pos = append_reset_reason(buf, size, pos, cause & RESET_SOFTWARE, "Software reset"); + pos = append_reset_reason(buf, size, pos, cause & RESET_BROWNOUT, "Brownout (drop in voltage)"); + pos = append_reset_reason(buf, size, pos, cause & RESET_POR, "Power-on reset (POR)"); + pos = append_reset_reason(buf, size, pos, cause & RESET_WATCHDOG, "Watchdog timer expiration"); + pos = append_reset_reason(buf, size, pos, cause & RESET_DEBUG, "Debug event"); + pos = append_reset_reason(buf, size, pos, cause & RESET_SECURITY, "Security violation"); + pos = append_reset_reason(buf, size, pos, cause & RESET_LOW_POWER_WAKE, "Waking up from low power mode"); + pos = append_reset_reason(buf, size, pos, cause & RESET_CPU_LOCKUP, "CPU lock-up detected"); + pos = append_reset_reason(buf, size, pos, cause & RESET_PARITY, "Parity error"); + pos = append_reset_reason(buf, size, pos, cause & RESET_PLL, "PLL error"); + pos = append_reset_reason(buf, size, pos, cause & RESET_CLOCK, "Clock error"); + pos = append_reset_reason(buf, size, pos, cause & RESET_HARDWARE, "Hardware reset"); + pos = append_reset_reason(buf, size, pos, cause & RESET_USER, "User reset"); + pos = append_reset_reason(buf, size, pos, cause & RESET_TEMPERATURE, "Temperature reset"); - ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); - return reset_reason; + // Ensure null termination if nothing was written + if (pos == 0) { + buf[0] = '\0'; + } + + ESP_LOGD(TAG, "Reset Reason: %s", buf); + return buf; +} + +const char *DebugComponent::get_wakeup_cause_(std::span buffer) { + // Zephyr doesn't have detailed wakeup cause like ESP32 + return ""; } uint32_t DebugComponent::get_free_heap_() { return INT_MAX; } @@ -118,175 +132,183 @@ void DebugComponent::log_partition_info_() { flash_area_foreach(fa_cb, nullptr); } -void DebugComponent::get_device_info_(std::string &device_info) { - std::string supply = "Main supply status: "; - if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) { - supply += "Normal voltage."; - } else { - supply += "High voltage."; - } - ESP_LOGD(TAG, "%s", supply.c_str()); - device_info += "|" + supply; +size_t DebugComponent::get_device_info_(std::span buffer, size_t pos) { + constexpr size_t size = DEVICE_INFO_BUFFER_SIZE; + char *buf = buffer.data(); - std::string reg0 = "Regulator stage 0: "; + // Main supply status + const char *supply_status = + (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) ? "Normal voltage." : "High voltage."; + ESP_LOGD(TAG, "Main supply status: %s", supply_status); + pos = buf_append(buf, size, pos, "|Main supply status: %s", supply_status); + + // Regulator stage 0 if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_HIGH) { - reg0 += nrf_power_dcdcen_vddh_get(NRF_POWER) ? "DC/DC" : "LDO"; - reg0 += ", "; + const char *reg0_type = nrf_power_dcdcen_vddh_get(NRF_POWER) ? "DC/DC" : "LDO"; + const char *reg0_voltage; switch (NRF_UICR->REGOUT0 & UICR_REGOUT0_VOUT_Msk) { case (UICR_REGOUT0_VOUT_DEFAULT << UICR_REGOUT0_VOUT_Pos): - reg0 += "1.8V (default)"; + reg0_voltage = "1.8V (default)"; break; case (UICR_REGOUT0_VOUT_1V8 << UICR_REGOUT0_VOUT_Pos): - reg0 += "1.8V"; + reg0_voltage = "1.8V"; break; case (UICR_REGOUT0_VOUT_2V1 << UICR_REGOUT0_VOUT_Pos): - reg0 += "2.1V"; + reg0_voltage = "2.1V"; break; case (UICR_REGOUT0_VOUT_2V4 << UICR_REGOUT0_VOUT_Pos): - reg0 += "2.4V"; + reg0_voltage = "2.4V"; break; case (UICR_REGOUT0_VOUT_2V7 << UICR_REGOUT0_VOUT_Pos): - reg0 += "2.7V"; + reg0_voltage = "2.7V"; break; case (UICR_REGOUT0_VOUT_3V0 << UICR_REGOUT0_VOUT_Pos): - reg0 += "3.0V"; + reg0_voltage = "3.0V"; break; case (UICR_REGOUT0_VOUT_3V3 << UICR_REGOUT0_VOUT_Pos): - reg0 += "3.3V"; + reg0_voltage = "3.3V"; break; default: - reg0 += "???V"; + reg0_voltage = "???V"; } + ESP_LOGD(TAG, "Regulator stage 0: %s, %s", reg0_type, reg0_voltage); + pos = buf_append(buf, size, pos, "|Regulator stage 0: %s, %s", reg0_type, reg0_voltage); } else { - reg0 += "disabled"; + ESP_LOGD(TAG, "Regulator stage 0: disabled"); + pos = buf_append(buf, size, pos, "|Regulator stage 0: disabled"); } - ESP_LOGD(TAG, "%s", reg0.c_str()); - device_info += "|" + reg0; - std::string reg1 = "Regulator stage 1: "; - reg1 += nrf_power_dcdcen_get(NRF_POWER) ? "DC/DC" : "LDO"; - ESP_LOGD(TAG, "%s", reg1.c_str()); - device_info += "|" + reg1; + // Regulator stage 1 + const char *reg1_type = nrf_power_dcdcen_get(NRF_POWER) ? "DC/DC" : "LDO"; + ESP_LOGD(TAG, "Regulator stage 1: %s", reg1_type); + pos = buf_append(buf, size, pos, "|Regulator stage 1: %s", reg1_type); - std::string usb_power = "USB power state: "; + // USB power state + const char *usb_state; if (nrf_power_usbregstatus_vbusdet_get(NRF_POWER)) { if (nrf_power_usbregstatus_outrdy_get(NRF_POWER)) { - /**< From the power viewpoint, USB is ready for working. */ - usb_power += "ready"; + usb_state = "ready"; } else { - /**< The USB power is detected, but USB power regulator is not ready. */ - usb_power += "connected (regulator is not ready)"; + usb_state = "connected (regulator is not ready)"; } } else { - /**< No power on USB lines detected. */ - usb_power += "disconected"; + usb_state = "disconnected"; } - ESP_LOGD(TAG, "%s", usb_power.c_str()); - device_info += "|" + usb_power; + ESP_LOGD(TAG, "USB power state: %s", usb_state); + pos = buf_append(buf, size, pos, "|USB power state: %s", usb_state); + // Power-fail comparator bool enabled; - nrf_power_pof_thr_t pof_thr; - - pof_thr = nrf_power_pofcon_get(NRF_POWER, &enabled); - std::string pof = "Power-fail comparator: "; + nrf_power_pof_thr_t pof_thr = nrf_power_pofcon_get(NRF_POWER, &enabled); if (enabled) { + const char *pof_voltage; switch (pof_thr) { case POWER_POFCON_THRESHOLD_V17: - pof += "1.7V"; + pof_voltage = "1.7V"; break; case POWER_POFCON_THRESHOLD_V18: - pof += "1.8V"; + pof_voltage = "1.8V"; break; case POWER_POFCON_THRESHOLD_V19: - pof += "1.9V"; + pof_voltage = "1.9V"; break; case POWER_POFCON_THRESHOLD_V20: - pof += "2.0V"; + pof_voltage = "2.0V"; break; case POWER_POFCON_THRESHOLD_V21: - pof += "2.1V"; + pof_voltage = "2.1V"; break; case POWER_POFCON_THRESHOLD_V22: - pof += "2.2V"; + pof_voltage = "2.2V"; break; case POWER_POFCON_THRESHOLD_V23: - pof += "2.3V"; + pof_voltage = "2.3V"; break; case POWER_POFCON_THRESHOLD_V24: - pof += "2.4V"; + pof_voltage = "2.4V"; break; case POWER_POFCON_THRESHOLD_V25: - pof += "2.5V"; + pof_voltage = "2.5V"; break; case POWER_POFCON_THRESHOLD_V26: - pof += "2.6V"; + pof_voltage = "2.6V"; break; case POWER_POFCON_THRESHOLD_V27: - pof += "2.7V"; + pof_voltage = "2.7V"; break; case POWER_POFCON_THRESHOLD_V28: - pof += "2.8V"; + pof_voltage = "2.8V"; + break; + default: + pof_voltage = "???V"; break; } if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_HIGH) { - pof += ", VDDH: "; + const char *vddh_voltage; switch (nrf_power_pofcon_vddh_get(NRF_POWER)) { case NRF_POWER_POFTHRVDDH_V27: - pof += "2.7V"; + vddh_voltage = "2.7V"; break; case NRF_POWER_POFTHRVDDH_V28: - pof += "2.8V"; + vddh_voltage = "2.8V"; break; case NRF_POWER_POFTHRVDDH_V29: - pof += "2.9V"; + vddh_voltage = "2.9V"; break; case NRF_POWER_POFTHRVDDH_V30: - pof += "3.0V"; + vddh_voltage = "3.0V"; break; case NRF_POWER_POFTHRVDDH_V31: - pof += "3.1V"; + vddh_voltage = "3.1V"; break; case NRF_POWER_POFTHRVDDH_V32: - pof += "3.2V"; + vddh_voltage = "3.2V"; break; case NRF_POWER_POFTHRVDDH_V33: - pof += "3.3V"; + vddh_voltage = "3.3V"; break; case NRF_POWER_POFTHRVDDH_V34: - pof += "3.4V"; + vddh_voltage = "3.4V"; break; case NRF_POWER_POFTHRVDDH_V35: - pof += "3.5V"; + vddh_voltage = "3.5V"; break; case NRF_POWER_POFTHRVDDH_V36: - pof += "3.6V"; + vddh_voltage = "3.6V"; break; case NRF_POWER_POFTHRVDDH_V37: - pof += "3.7V"; + vddh_voltage = "3.7V"; break; case NRF_POWER_POFTHRVDDH_V38: - pof += "3.8V"; + vddh_voltage = "3.8V"; break; case NRF_POWER_POFTHRVDDH_V39: - pof += "3.9V"; + vddh_voltage = "3.9V"; break; case NRF_POWER_POFTHRVDDH_V40: - pof += "4.0V"; + vddh_voltage = "4.0V"; break; case NRF_POWER_POFTHRVDDH_V41: - pof += "4.1V"; + vddh_voltage = "4.1V"; break; case NRF_POWER_POFTHRVDDH_V42: - pof += "4.2V"; + vddh_voltage = "4.2V"; + break; + default: + vddh_voltage = "???V"; break; } + ESP_LOGD(TAG, "Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage); + pos = buf_append(buf, size, pos, "|Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage); + } else { + ESP_LOGD(TAG, "Power-fail comparator: %s", pof_voltage); + pos = buf_append(buf, size, pos, "|Power-fail comparator: %s", pof_voltage); } } else { - pof += "disabled"; + ESP_LOGD(TAG, "Power-fail comparator: disabled"); + pos = buf_append(buf, size, pos, "|Power-fail comparator: disabled"); } - ESP_LOGD(TAG, "%s", pof.c_str()); - device_info += "|" + pof; auto package = [](uint32_t value) { switch (value) { @@ -373,6 +395,8 @@ void DebugComponent::get_device_info_(std::string &device_info) { "NRFFW %s\n" "NRFHW %s", uicr(NRF_UICR->NRFFW, 13).c_str(), uicr(NRF_UICR->NRFHW, 12).c_str()); + + return pos; } void DebugComponent::update_platform_() {} From e29523e248a7709aac9062bca47dc6f47ef37c5b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 10:31:19 -1000 Subject: [PATCH 1141/1145] [abbwelcome] Use stack-based formatting to eliminate heap allocations (#12799) --- .../remote_base/abbwelcome_protocol.cpp | 14 +++-- .../remote_base/abbwelcome_protocol.h | 52 +++++++++++++------ 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/esphome/components/remote_base/abbwelcome_protocol.cpp b/esphome/components/remote_base/abbwelcome_protocol.cpp index 88f928901b..352ae10ed7 100644 --- a/esphome/components/remote_base/abbwelcome_protocol.cpp +++ b/esphome/components/remote_base/abbwelcome_protocol.cpp @@ -51,7 +51,8 @@ void ABBWelcomeProtocol::encode(RemoteTransmitData *dst, const ABBWelcomeData &s dst->reserve(reserve_count); for (size_t i = 0; i < src.size(); i++) this->encode_byte_(dst, src[i]); - ESP_LOGD(TAG, "Transmitting: %s", src.to_string().c_str()); + char buf[ABBWelcomeData::FORMAT_BUFFER_SIZE]; + ESP_LOGD(TAG, "Transmitting: %s", src.format_to(buf)); } bool ABBWelcomeProtocol::decode_byte_(RemoteReceiveData &src, bool &done, uint8_t &data) { @@ -94,7 +95,8 @@ optional ABBWelcomeProtocol::decode(RemoteReceiveData src) { for (; (received_bytes < length) && !done; received_bytes++) { uint8_t data = 0; if (!this->decode_byte_(src, done, data)) { - ESP_LOGW(TAG, "Received incomplete packet: %s", out.to_string(received_bytes).c_str()); + char buf[ABBWelcomeData::FORMAT_BUFFER_SIZE]; + ESP_LOGW(TAG, "Received incomplete packet: %s", out.format_to(buf, received_bytes)); return {}; } if (received_bytes == 2) { @@ -106,17 +108,19 @@ optional ABBWelcomeProtocol::decode(RemoteReceiveData src) { ESP_LOGVV(TAG, "Received Byte: 0x%02X", data); out[received_bytes] = data; } + char buf[ABBWelcomeData::FORMAT_BUFFER_SIZE]; if (out.is_valid()) { - ESP_LOGI(TAG, "Received: %s", out.to_string().c_str()); + ESP_LOGI(TAG, "Received: %s", out.format_to(buf)); return out; } - ESP_LOGW(TAG, "Received malformed packet: %s", out.to_string(received_bytes).c_str()); + ESP_LOGW(TAG, "Received malformed packet: %s", out.format_to(buf, received_bytes)); } return {}; } void ABBWelcomeProtocol::dump(const ABBWelcomeData &data) { - ESP_LOGD(TAG, "Received ABBWelcome: %s", data.to_string().c_str()); + char buf[ABBWelcomeData::FORMAT_BUFFER_SIZE]; + ESP_LOGD(TAG, "Received ABBWelcome: %s", data.format_to(buf)); } } // namespace remote_base diff --git a/esphome/components/remote_base/abbwelcome_protocol.h b/esphome/components/remote_base/abbwelcome_protocol.h index b8d9293c11..1dddedf8ce 100644 --- a/esphome/components/remote_base/abbwelcome_protocol.h +++ b/esphome/components/remote_base/abbwelcome_protocol.h @@ -136,22 +136,12 @@ class ABBWelcomeData { this->data_[1] = 0xff; this->data_[this->size() - 1] = this->calc_cs_(); } - std::string to_string(uint8_t max_print_bytes = 255) const { - std::string info; - if (this->is_valid()) { - info = str_sprintf(this->get_three_byte_address() ? "[%06" PRIX32 " %s %06" PRIX32 "] Type: %02X" - : "[%04" PRIX32 " %s %04" PRIX32 "] Type: %02X", - this->get_source_address(), this->get_retransmission() ? "»" : ">", - this->get_destination_address(), this->get_message_type()); - if (this->get_data_size()) - info += str_sprintf(", Data: %s", format_hex_pretty(this->get_data()).c_str()); - } else { - info = "[Invalid]"; - } - uint8_t print_bytes = std::min(this->size(), max_print_bytes); - if (print_bytes) - info = str_sprintf("%s %s", format_hex_pretty(this->data_.data(), print_bytes).c_str(), info.c_str()); - return info; + // Buffer size: max raw hex output (27*3-1=80) + space(1) + type_info(27) + data(52) + null(1) = 161, rounded up + static constexpr size_t FORMAT_BUFFER_SIZE = 192; + + template char *format_to(char (&buffer)[N], uint8_t max_print_bytes = 255) const { + static_assert(N >= FORMAT_BUFFER_SIZE, "Buffer too small for format_to()"); + return this->format_to_internal_(buffer, max_print_bytes); } bool operator==(const ABBWelcomeData &rhs) const { if (std::equal(this->data_.begin(), this->data_.begin() + this->size(), rhs.data_.begin())) @@ -168,6 +158,36 @@ class ABBWelcomeData { std::array data_; // Calculate checksum uint8_t calc_cs_() const; + // Internal format implementation - buffer guaranteed >= FORMAT_BUFFER_SIZE by caller + char *format_to_internal_(char *buffer, uint8_t max_print_bytes) const { + char *ptr = buffer; + char *end = buffer + FORMAT_BUFFER_SIZE; + + uint8_t print_bytes = std::min(this->size(), max_print_bytes); + if (print_bytes) { + char raw_hex[format_hex_pretty_size(12 + MAX_DATA_LENGTH)]; + format_hex_pretty_to(raw_hex, this->data_.data(), print_bytes, '.'); + ptr += snprintf(ptr, end - ptr, "%s ", raw_hex); + } + + if (this->is_valid()) { + ptr += snprintf(ptr, end - ptr, + this->get_three_byte_address() ? "[%06" PRIX32 " %s %06" PRIX32 "] Type: %02X" + : "[%04" PRIX32 " %s %04" PRIX32 "] Type: %02X", + this->get_source_address(), this->get_retransmission() ? "»" : ">", + this->get_destination_address(), this->get_message_type()); + if (this->get_data_size()) { + char data_hex[format_hex_pretty_size(MAX_DATA_LENGTH)]; + format_hex_pretty_to(data_hex, this->data_.data() + 5 + 2 * this->get_address_length(), this->get_data_size(), + '.'); + snprintf(ptr, end - ptr, ", Data: %s", data_hex); + } + } else { + snprintf(ptr, end - ptr, "[Invalid]"); + } + + return buffer; + } }; class ABBWelcomeProtocol : public RemoteProtocol { From 1339f3e77e85e75f11816f2b10f9279467f45b9a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 11:49:23 -1000 Subject: [PATCH 1142/1145] [web_server][captive_portal] Add Brotli compression (saves ~11KB flash when using local) (#12959) --- esphome/components/captive_portal/__init__.py | 5 + .../components/captive_portal/captive_index.h | 228 +- .../captive_portal/captive_portal.cpp | 4 + esphome/components/web_server/__init__.py | 4 + .../components/web_server/server_index_v2.h | 1858 ++- .../components/web_server/server_index_v3.h | 11626 ++++++++++------ esphome/components/web_server/web_server.cpp | 4 + esphome/const.py | 1 + esphome/core/defines.h | 2 + tests/components/captive_portal/common.yaml | 1 + tests/components/web_server/common_v2.yaml | 1 + 11 files changed, 8961 insertions(+), 4773 deletions(-) diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 232b868e82..4b30dc5d16 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -7,6 +7,7 @@ from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_AP, + CONF_COMPRESSION, CONF_ID, PLATFORM_BK72XX, PLATFORM_ESP32, @@ -43,6 +44,7 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( web_server_base.WebServerBase ), + cv.Optional(CONF_COMPRESSION, default="br"): cv.one_of("br", "gzip"), } ).extend(cv.COMPONENT_SCHEMA), cv.only_on( @@ -96,6 +98,9 @@ async def to_code(config): await cg.register_component(var, config) cg.add_define("USE_CAPTIVE_PORTAL") + if config[CONF_COMPRESSION] == "gzip": + cg.add_define("USE_CAPTIVE_PORTAL_GZIP") + if CORE.using_arduino: if CORE.is_esp8266: cg.add_library("DNSServer", None) diff --git a/esphome/components/captive_portal/captive_index.h b/esphome/components/captive_portal/captive_index.h index 3122f27558..ad74c395f3 100644 --- a/esphome/components/captive_portal/captive_index.h +++ b/esphome/components/captive_portal/captive_index.h @@ -3,87 +3,153 @@ #include "esphome/core/hal.h" -namespace esphome { -namespace captive_portal { +namespace esphome::captive_portal { +#ifdef USE_CAPTIVE_PORTAL_GZIP const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x95, 0x16, 0x6b, 0x8f, 0xdb, 0x36, 0xf2, 0x7b, 0x7e, - 0x05, 0x8f, 0x49, 0xbb, 0x52, 0xb3, 0x7a, 0x7a, 0xed, 0x6c, 0x24, 0x51, 0x45, 0x9a, 0xbb, 0xa2, 0x05, 0x9a, 0x36, - 0xc0, 0x6e, 0x73, 0x1f, 0x82, 0x00, 0x4b, 0x53, 0x23, 0x8b, 0x31, 0x45, 0xea, 0x48, 0xca, 0x8f, 0x18, 0xbe, 0xdf, - 0x7e, 0xa0, 0x24, 0x7b, 0x9d, 0x45, 0x73, 0xb8, 0xb3, 0x60, 0x61, 0x38, 0xef, 0x19, 0xcd, 0x83, 0xc5, 0xdf, 0x2a, - 0xc5, 0xec, 0xbe, 0x03, 0xd4, 0xd8, 0x56, 0x94, 0x85, 0x7b, 0x23, 0x41, 0xe5, 0x8a, 0x80, 0x2c, 0x8b, 0x06, 0x68, - 0x55, 0x16, 0x2d, 0x58, 0x8a, 0x58, 0x43, 0xb5, 0x01, 0x4b, 0xfe, 0xbc, 0xff, 0x39, 0xb8, 0x2d, 0x0b, 0xc1, 0xe5, - 0x1a, 0x69, 0x10, 0x84, 0x33, 0x25, 0x51, 0xa3, 0xa1, 0x26, 0x15, 0xb5, 0x34, 0xe3, 0x2d, 0x5d, 0xc1, 0x24, 0x22, - 0x69, 0x0b, 0x64, 0xc3, 0x61, 0xdb, 0x29, 0x6d, 0x11, 0x53, 0xd2, 0x82, 0xb4, 0x04, 0x6f, 0x79, 0x65, 0x1b, 0x52, - 0xc1, 0x86, 0x33, 0x08, 0x86, 0xc3, 0x35, 0x97, 0xdc, 0x72, 0x2a, 0x02, 0xc3, 0xa8, 0x00, 0x92, 0x5c, 0xf7, 0x06, - 0xf4, 0x70, 0xa0, 0x4b, 0x01, 0x44, 0x2a, 0x5c, 0x16, 0x86, 0x69, 0xde, 0x59, 0xe4, 0x5c, 0x25, 0xad, 0xaa, 0x7a, - 0x01, 0x65, 0x14, 0x51, 0x63, 0xc0, 0x9a, 0x88, 0xcb, 0x0a, 0x76, 0xe1, 0x32, 0x66, 0x2c, 0x86, 0xdb, 0xdb, 0xf0, - 0xb3, 0x79, 0x56, 0x29, 0xd6, 0xb7, 0x20, 0x6d, 0x28, 0x14, 0xa3, 0x96, 0x2b, 0x19, 0x1a, 0xa0, 0x9a, 0x35, 0x84, - 0x10, 0xfc, 0xa3, 0xa1, 0x1b, 0xc0, 0xdf, 0x7f, 0xef, 0x9d, 0x99, 0x56, 0x60, 0xff, 0x21, 0xc0, 0x81, 0xe6, 0xa7, - 0xfd, 0x3d, 0x5d, 0xfd, 0x4e, 0x5b, 0xf0, 0x30, 0x35, 0xbc, 0x02, 0xec, 0x7f, 0x8c, 0x3f, 0x85, 0xc6, 0xee, 0x05, - 0x84, 0x15, 0x37, 0x9d, 0xa0, 0x7b, 0x82, 0x97, 0x42, 0xb1, 0x35, 0xf6, 0xf3, 0xba, 0x97, 0xcc, 0x29, 0x47, 0xc6, - 0x03, 0xff, 0x20, 0xc0, 0x22, 0x4b, 0xde, 0x51, 0xdb, 0x84, 0x2d, 0xdd, 0x79, 0x23, 0xc0, 0xa5, 0x97, 0xfe, 0xe0, - 0xc1, 0xcb, 0x24, 0x8e, 0xfd, 0xeb, 0xe1, 0x15, 0xfb, 0x51, 0x12, 0xc7, 0xb9, 0x06, 0xdb, 0x6b, 0x89, 0xa8, 0xf7, - 0x50, 0x74, 0xd4, 0x36, 0xa8, 0x22, 0xf8, 0x5d, 0x92, 0xa2, 0xe4, 0x75, 0x98, 0xce, 0x7f, 0x0b, 0x5f, 0xa1, 0x9b, - 0x30, 0x9d, 0xb3, 0x57, 0xc1, 0x1c, 0x25, 0x37, 0xc1, 0x1c, 0xa5, 0x69, 0x38, 0x47, 0xf1, 0x17, 0x8c, 0x6a, 0x2e, - 0x04, 0xc1, 0x52, 0x49, 0xc0, 0xc8, 0x58, 0xad, 0xd6, 0x40, 0x30, 0xeb, 0xb5, 0x06, 0x69, 0xdf, 0x2a, 0xa1, 0x34, - 0x8e, 0xca, 0x67, 0xff, 0x97, 0x42, 0xab, 0xa9, 0x34, 0xb5, 0xd2, 0x2d, 0xc1, 0x43, 0xf6, 0xbd, 0x17, 0x07, 0x7b, - 0x44, 0xee, 0xe5, 0x5f, 0x10, 0x03, 0xa5, 0xf9, 0x8a, 0x4b, 0x82, 0x9d, 0xc6, 0x5b, 0x1c, 0x95, 0x0f, 0xfe, 0xf1, - 0x1c, 0x3d, 0x75, 0xd1, 0x4f, 0xf1, 0x28, 0xef, 0xe3, 0x43, 0x61, 0x36, 0x2b, 0xb4, 0x6b, 0x85, 0x34, 0x04, 0x37, - 0xd6, 0x76, 0x59, 0x14, 0x6d, 0xb7, 0xdb, 0x70, 0x3b, 0x0b, 0x95, 0x5e, 0x45, 0x69, 0x1c, 0xc7, 0x91, 0xd9, 0xac, - 0x30, 0x1a, 0x0b, 0x01, 0xa7, 0x37, 0x18, 0x35, 0xc0, 0x57, 0x8d, 0x1d, 0xe0, 0xf2, 0xc5, 0x01, 0x8e, 0x85, 0xe3, - 0x28, 0x1f, 0x3e, 0x5d, 0x58, 0xe1, 0x17, 0x56, 0xe0, 0x47, 0xea, 0xe1, 0x53, 0x98, 0x57, 0x43, 0x98, 0xaf, 0x68, - 0x8a, 0x52, 0x14, 0x0f, 0x4f, 0x1a, 0x38, 0x78, 0x3a, 0x05, 0x4f, 0x4e, 0xe8, 0xe2, 0xe4, 0xa0, 0x76, 0x11, 0xbc, - 0x3e, 0xcb, 0x26, 0x0e, 0xb3, 0x49, 0xe2, 0x47, 0x84, 0x13, 0xf8, 0x65, 0x71, 0x79, 0x0e, 0xd2, 0x0f, 0x97, 0x0c, - 0xce, 0x5a, 0x93, 0x7c, 0x58, 0xd0, 0x39, 0x9a, 0x4f, 0x98, 0x79, 0xe0, 0xe0, 0xf3, 0x09, 0xcd, 0x37, 0x69, 0x93, - 0xb4, 0xc1, 0x22, 0x98, 0xd3, 0x19, 0x9a, 0x4d, 0x8e, 0xcc, 0xd0, 0x6c, 0x93, 0x36, 0x8b, 0x0f, 0x8b, 0x4b, 0x5c, - 0x30, 0xfb, 0x72, 0x15, 0x95, 0xd8, 0xcf, 0x30, 0x7e, 0x8c, 0x5c, 0x5d, 0x46, 0x1e, 0x7e, 0x56, 0x5c, 0x7a, 0x18, - 0xfb, 0xc7, 0x1a, 0x2c, 0x6b, 0x3c, 0x1c, 0x31, 0x25, 0x6b, 0xbe, 0x0a, 0x3f, 0x1b, 0x25, 0xb1, 0x1f, 0xda, 0x06, - 0xa4, 0x77, 0x12, 0x75, 0x82, 0x30, 0x50, 0xbc, 0xa7, 0x14, 0xeb, 0x1f, 0xce, 0xf5, 0x6f, 0xb9, 0x15, 0x40, 0x6c, - 0xe8, 0x1a, 0xf6, 0xfa, 0x8c, 0x5d, 0xaa, 0x6a, 0xff, 0x8d, 0xd6, 0x68, 0x92, 0xb1, 0x2f, 0xb8, 0x94, 0xa0, 0xef, - 0x61, 0x67, 0x09, 0x7e, 0xf7, 0xe6, 0x2d, 0x7a, 0x53, 0x55, 0x1a, 0x8c, 0xc9, 0x10, 0x7e, 0x69, 0xc3, 0x96, 0xb2, - 0xff, 0x5d, 0x57, 0xf2, 0x95, 0xae, 0x7f, 0xf2, 0x9f, 0x39, 0xfa, 0x1d, 0xec, 0x56, 0xe9, 0xf5, 0xa4, 0xcd, 0xb9, - 0x96, 0xbb, 0x0e, 0xd3, 0xc4, 0x86, 0xb4, 0x33, 0xa1, 0x11, 0x9c, 0x81, 0x97, 0xf8, 0x61, 0x4b, 0xbb, 0xc7, 0xa8, - 0xe4, 0x29, 0x51, 0x0f, 0x45, 0xc5, 0x37, 0x88, 0x09, 0x6a, 0x0c, 0xc1, 0x72, 0x54, 0x85, 0xd1, 0x33, 0x34, 0xfc, - 0x94, 0x64, 0x82, 0xb3, 0x35, 0xc1, 0x7f, 0x31, 0x01, 0x7e, 0xda, 0xff, 0x5a, 0x79, 0x57, 0xc6, 0xf0, 0xea, 0xca, - 0x0f, 0x37, 0x54, 0xf4, 0x80, 0x08, 0xb2, 0x0d, 0x37, 0x8f, 0x0e, 0xe6, 0xdf, 0x14, 0xeb, 0xcc, 0xfa, 0xca, 0x0f, - 0x6b, 0xc5, 0x7a, 0xe3, 0xf9, 0xb8, 0x9c, 0xcc, 0x15, 0x74, 0x1c, 0x90, 0xf8, 0x39, 0x7e, 0xe2, 0x51, 0x20, 0xa0, - 0xb6, 0x67, 0x3e, 0x84, 0x5e, 0x1c, 0x8c, 0x27, 0x43, 0x6d, 0x0c, 0xf7, 0x8f, 0x67, 0x64, 0x61, 0x3a, 0x2a, 0x9f, - 0x0a, 0x3a, 0x07, 0x5d, 0xab, 0xc8, 0xd0, 0x41, 0xae, 0x5f, 0x3a, 0x2a, 0xcf, 0x06, 0x23, 0x7a, 0x02, 0x5f, 0x1c, - 0xb8, 0x27, 0xdd, 0x14, 0x5c, 0x9f, 0x35, 0x16, 0x51, 0xc5, 0x37, 0xe5, 0xc3, 0xd1, 0x7f, 0x8c, 0xe3, 0x5f, 0x3d, - 0xe8, 0xfd, 0x1d, 0x08, 0x60, 0x56, 0x69, 0x0f, 0x3f, 0x97, 0x60, 0xb1, 0x3f, 0x06, 0xfc, 0xcb, 0xfd, 0xbb, 0xdf, - 0x88, 0xf2, 0xb4, 0x7f, 0xfd, 0x2d, 0x6e, 0xb7, 0x0a, 0x3e, 0x6a, 0x10, 0xff, 0x26, 0x57, 0x6e, 0x19, 0x5c, 0x7d, - 0xc2, 0x7e, 0x38, 0xc4, 0xfb, 0xf0, 0xb8, 0x11, 0x5c, 0x3b, 0xbf, 0xdc, 0xb5, 0xe2, 0xda, 0x45, 0x18, 0x2c, 0xe6, - 0xfe, 0xf1, 0xe1, 0xe8, 0x1f, 0xfd, 0xbc, 0x88, 0xc6, 0xb9, 0x5e, 0x16, 0xc3, 0x88, 0x2d, 0x7f, 0x38, 0x2c, 0xd5, - 0x2e, 0x30, 0xfc, 0x0b, 0x97, 0xab, 0x8c, 0xcb, 0x06, 0x34, 0xb7, 0xc7, 0x8a, 0x6f, 0xae, 0xb9, 0xec, 0x7a, 0x7b, - 0xe8, 0x68, 0x55, 0x39, 0xca, 0xbc, 0xdb, 0xe5, 0xb5, 0x92, 0xd6, 0x71, 0x42, 0x96, 0x40, 0x7b, 0x1c, 0xe9, 0xc3, - 0x44, 0xc9, 0x5e, 0xcf, 0xbf, 0x3b, 0xba, 0x82, 0x3b, 0x58, 0xd8, 0xd9, 0x80, 0x0a, 0xbe, 0x92, 0x19, 0x03, 0x69, - 0x41, 0x8f, 0x42, 0x35, 0x6d, 0xb9, 0xd8, 0x67, 0x86, 0x4a, 0x13, 0x18, 0xd0, 0xbc, 0x3e, 0x2e, 0x7b, 0x6b, 0x95, - 0x3c, 0x2c, 0x95, 0xae, 0x40, 0x67, 0x71, 0x3e, 0x02, 0x81, 0xa6, 0x15, 0xef, 0x4d, 0x16, 0xce, 0x34, 0xb4, 0xf9, - 0x92, 0xb2, 0xf5, 0x4a, 0xab, 0x5e, 0x56, 0x01, 0x73, 0x93, 0x36, 0x7b, 0x9e, 0xd4, 0x74, 0x06, 0x2c, 0x9f, 0x4e, - 0x75, 0x5d, 0xe7, 0x82, 0x4b, 0x08, 0xc6, 0x59, 0x96, 0xa5, 0xe1, 0x8d, 0x13, 0xbb, 0x70, 0x33, 0x4c, 0x1d, 0x62, - 0xf4, 0x31, 0x89, 0xe3, 0xef, 0xf2, 0x53, 0x38, 0x71, 0xce, 0x7a, 0x6d, 0x94, 0xce, 0x3a, 0xc5, 0x9d, 0x9b, 0xc7, - 0x96, 0x72, 0x79, 0xe9, 0xbd, 0x2b, 0x93, 0x7c, 0x5a, 0x3f, 0x19, 0x97, 0x83, 0x99, 0x61, 0x09, 0xe5, 0x2d, 0x97, - 0xe3, 0x0e, 0xcd, 0xd2, 0x45, 0xdc, 0xed, 0x8e, 0xe1, 0x54, 0x20, 0x87, 0x13, 0x77, 0x2d, 0x60, 0x97, 0x7f, 0xee, - 0x8d, 0xe5, 0xf5, 0x3e, 0x98, 0x76, 0x70, 0x66, 0x3a, 0xca, 0x20, 0x58, 0x82, 0xdd, 0x02, 0xc8, 0x7c, 0xb0, 0x11, - 0x70, 0x0b, 0xad, 0x99, 0xf2, 0x74, 0x56, 0x33, 0x14, 0xe8, 0xd7, 0xba, 0xfe, 0x1b, 0xb7, 0xab, 0xc5, 0x43, 0x4b, - 0xf5, 0x8a, 0xcb, 0x60, 0xa9, 0xac, 0x55, 0x6d, 0x16, 0xbc, 0xea, 0x76, 0xf9, 0x84, 0x72, 0xca, 0xb2, 0xc4, 0xb9, - 0x39, 0xec, 0xd6, 0x53, 0xbe, 0x93, 0x6e, 0x87, 0x8c, 0x12, 0xbc, 0x9a, 0xf8, 0x06, 0x16, 0x14, 0x9f, 0xd3, 0x93, - 0xcc, 0xbb, 0x1d, 0x72, 0xb8, 0x53, 0xaa, 0x6f, 0xea, 0x5b, 0x9a, 0xc4, 0x7f, 0xf1, 0x45, 0xaa, 0xba, 0x4e, 0x97, - 0xf5, 0x39, 0x53, 0x6e, 0x4d, 0xba, 0xd6, 0x18, 0x4a, 0xab, 0x88, 0xc6, 0xdb, 0x8c, 0xab, 0x8c, 0xb2, 0x70, 0x19, - 0x2e, 0x8b, 0x26, 0x41, 0xbc, 0x22, 0x2d, 0x65, 0xe5, 0xc5, 0xf8, 0x2a, 0xa2, 0x26, 0x39, 0x91, 0x9a, 0xa4, 0xfc, - 0x6a, 0x18, 0x8d, 0xb4, 0xc1, 0xfb, 0xf2, 0xad, 0x92, 0x12, 0x98, 0xe5, 0x72, 0x85, 0xac, 0x42, 0x53, 0x0a, 0xc2, - 0x30, 0x2c, 0x96, 0xba, 0x7c, 0x2f, 0x80, 0x1a, 0x40, 0x5b, 0xca, 0x6d, 0x58, 0x44, 0x23, 0xff, 0xd8, 0xc7, 0xbc, - 0x22, 0x12, 0x6c, 0x39, 0x35, 0x6c, 0xd1, 0xcc, 0x46, 0x03, 0x77, 0x60, 0x9d, 0x26, 0x67, 0x60, 0x56, 0x16, 0x6e, - 0xe5, 0x22, 0x3a, 0x8c, 0x34, 0x12, 0x6d, 0x79, 0xcd, 0xdd, 0x95, 0xa5, 0x2c, 0x86, 0x22, 0x77, 0x1a, 0x5c, 0x9e, - 0xc7, 0xeb, 0xd5, 0x00, 0x09, 0x90, 0x2b, 0xdb, 0x90, 0x59, 0x8a, 0x3a, 0x41, 0x19, 0x34, 0x4a, 0x54, 0xa0, 0xc9, - 0xdd, 0xdd, 0xaf, 0x7f, 0x2f, 0x9d, 0x33, 0x8f, 0x72, 0x9d, 0x59, 0x8f, 0x62, 0x0e, 0x98, 0xa4, 0x16, 0x37, 0xe3, - 0xa5, 0xaa, 0xa3, 0xc6, 0x6c, 0x95, 0xae, 0xbe, 0xd2, 0xf1, 0x7e, 0x42, 0x8e, 0x7a, 0x86, 0xff, 0xd0, 0x2a, 0xe5, - 0x1d, 0xdd, 0x40, 0x11, 0x4d, 0x87, 0x22, 0x72, 0x0e, 0x8f, 0xf4, 0x66, 0xe2, 0x6b, 0x92, 0xf2, 0x8f, 0xfb, 0x37, - 0xe8, 0xcf, 0xae, 0xa2, 0x16, 0xc6, 0xb4, 0x0d, 0x51, 0xb5, 0x60, 0x1b, 0x55, 0x91, 0xf7, 0x7f, 0xdc, 0xdd, 0x9f, - 0x23, 0xec, 0x07, 0x26, 0x04, 0x92, 0x8d, 0xd7, 0xbb, 0x5e, 0x58, 0xde, 0x51, 0x6d, 0x07, 0xb5, 0x81, 0x9b, 0x22, - 0xa7, 0x18, 0x06, 0x7a, 0xcd, 0x05, 0x8c, 0x61, 0x8c, 0x82, 0x25, 0x3a, 0x79, 0x75, 0xb2, 0xf6, 0xc4, 0xaf, 0x68, - 0xfc, 0xda, 0xd1, 0xf8, 0xe9, 0xa3, 0xe1, 0xa6, 0xfb, 0x1f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00}; + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x95, 0x56, 0xeb, 0x6f, 0xd4, 0x38, 0x10, 0xff, 0xce, + 0x5f, 0xe1, 0x33, 0x8f, 0x26, 0xd0, 0x3c, 0xb7, 0xdb, 0x96, 0x6c, 0x12, 0x04, 0xdc, 0x21, 0x90, 0x28, 0x20, 0xb5, + 0x70, 0x1f, 0x10, 0x52, 0xbd, 0xc9, 0x64, 0x63, 0x9a, 0x38, 0x39, 0xdb, 0xfb, 0x62, 0xb5, 0xf7, 0xb7, 0xdf, 0x38, + 0xc9, 0x6e, 0xb7, 0x15, 0x9c, 0xee, 0x5a, 0x35, 0x1d, 0xdb, 0xf3, 0xf8, 0xcd, 0x78, 0x1e, 0x8e, 0x7f, 0xcb, 0x9b, + 0x4c, 0xaf, 0x5b, 0x20, 0xa5, 0xae, 0xab, 0x34, 0x36, 0x5f, 0x52, 0x31, 0x31, 0x4b, 0x40, 0xe0, 0x0a, 0x58, 0x9e, + 0xc6, 0x35, 0x68, 0x46, 0xb2, 0x92, 0x49, 0x05, 0x3a, 0xf9, 0x7c, 0xf5, 0xc6, 0x39, 0x4f, 0xe3, 0x8a, 0x8b, 0x1b, + 0x22, 0xa1, 0x4a, 0x78, 0xd6, 0x08, 0x52, 0x4a, 0x28, 0x92, 0x9c, 0x69, 0x16, 0xf1, 0x9a, 0xcd, 0x60, 0x10, 0x11, + 0xac, 0x86, 0x64, 0xc1, 0x61, 0xd9, 0x36, 0x52, 0x13, 0xe4, 0xd3, 0x20, 0x74, 0x42, 0x97, 0x3c, 0xd7, 0x65, 0x92, + 0xc3, 0x82, 0x67, 0xe0, 0x74, 0x8b, 0x63, 0x2e, 0xb8, 0xe6, 0xac, 0x72, 0x54, 0xc6, 0x2a, 0x48, 0x82, 0xe3, 0xb9, + 0x02, 0xd9, 0x2d, 0xd8, 0x14, 0xd7, 0xa2, 0xa1, 0x69, 0xac, 0x32, 0xc9, 0x5b, 0x4d, 0x0c, 0xd4, 0xa4, 0x6e, 0xf2, + 0x79, 0x05, 0xa9, 0xe7, 0x31, 0x85, 0x90, 0x94, 0xc7, 0x45, 0x0e, 0x2b, 0x77, 0xea, 0x67, 0x99, 0x0f, 0xe7, 0xe7, + 0xee, 0x77, 0xf5, 0x00, 0x9d, 0x9a, 0xd7, 0x68, 0xcd, 0xad, 0x9a, 0x8c, 0x69, 0xde, 0x08, 0x57, 0x01, 0x93, 0x59, + 0x99, 0x24, 0x09, 0x7d, 0xa1, 0xd8, 0x02, 0xe8, 0x93, 0x27, 0xd6, 0x9e, 0x69, 0x06, 0xfa, 0x8f, 0x0a, 0x0c, 0xa9, + 0x5e, 0xad, 0xaf, 0xd8, 0xec, 0x03, 0x02, 0xb7, 0x28, 0x53, 0x3c, 0x07, 0x6a, 0x7f, 0xf5, 0xbf, 0xb9, 0x4a, 0xaf, + 0x2b, 0x70, 0x73, 0xae, 0xda, 0x8a, 0xad, 0x13, 0x3a, 0x45, 0xad, 0x37, 0xd4, 0x9e, 0x14, 0x73, 0x91, 0x19, 0xe5, + 0x44, 0x59, 0x60, 0x6f, 0x2a, 0x40, 0x78, 0xc9, 0x05, 0xd3, 0xa5, 0x5b, 0xb3, 0x95, 0xd5, 0x13, 0x5c, 0x58, 0xe1, + 0x53, 0x0b, 0x9e, 0x05, 0xbe, 0x6f, 0x1f, 0x77, 0x1f, 0xdf, 0xf6, 0xf0, 0xff, 0x44, 0x82, 0x9e, 0x4b, 0x41, 0x98, + 0x75, 0x1d, 0xb7, 0xc8, 0x49, 0xf2, 0x84, 0x5e, 0x04, 0x21, 0x09, 0x9e, 0xbb, 0xe1, 0xf8, 0xbd, 0x7b, 0x46, 0x4e, + 0xf0, 0x7f, 0x76, 0xe6, 0x8c, 0x49, 0x70, 0x82, 0x9f, 0x30, 0x74, 0xc7, 0xc4, 0xff, 0x41, 0x49, 0xc1, 0xab, 0x2a, + 0xa1, 0xa2, 0x11, 0x40, 0x89, 0xd2, 0xb2, 0xb9, 0x81, 0x84, 0x66, 0x73, 0x29, 0x11, 0xfb, 0xeb, 0xa6, 0x6a, 0x24, + 0xf5, 0xd2, 0x07, 0xff, 0x4b, 0xa1, 0x96, 0x4c, 0xa8, 0xa2, 0x91, 0x75, 0x42, 0xbb, 0xe8, 0x5b, 0x8f, 0x36, 0x7a, + 0x4b, 0xcc, 0xc7, 0x3e, 0x38, 0x74, 0x1a, 0xc9, 0x67, 0x5c, 0x24, 0xd4, 0x68, 0x3c, 0x47, 0x23, 0xd7, 0xf6, 0x76, + 0xef, 0x3d, 0x33, 0xde, 0x0f, 0xfe, 0x34, 0xd6, 0xd7, 0xeb, 0x58, 0x2d, 0x66, 0x64, 0x55, 0x57, 0x42, 0x25, 0xb4, + 0xd4, 0xba, 0x8d, 0x3c, 0x6f, 0xb9, 0x5c, 0xba, 0xcb, 0x91, 0xdb, 0xc8, 0x99, 0x17, 0xfa, 0xbe, 0xef, 0x21, 0x07, + 0x25, 0x7d, 0x22, 0xd0, 0xf0, 0x84, 0x92, 0x12, 0xf8, 0xac, 0xd4, 0x1d, 0x9d, 0x3e, 0xda, 0xc0, 0x36, 0x36, 0x1c, + 0xe9, 0xf5, 0xb7, 0x03, 0x2b, 0xfc, 0xc0, 0x0a, 0xbc, 0x60, 0x16, 0xdd, 0xb9, 0x79, 0xd4, 0xb9, 0x79, 0xc6, 0x42, + 0x12, 0x12, 0xbf, 0xfb, 0x0d, 0x1d, 0x43, 0x0f, 0x2b, 0xe7, 0xde, 0x8a, 0x1c, 0xac, 0x0c, 0x55, 0x9f, 0x3a, 0xcf, + 0xf7, 0xb2, 0x81, 0xd9, 0x59, 0x04, 0xfe, 0xed, 0x86, 0x11, 0x78, 0x7b, 0x7a, 0xb8, 0x76, 0xc2, 0x2f, 0x87, 0x0c, + 0xc6, 0x5a, 0x19, 0x7c, 0x39, 0x65, 0x63, 0x32, 0x1e, 0x76, 0xc6, 0x8e, 0xa1, 0xf7, 0x2b, 0x32, 0x5e, 0x20, 0x47, + 0xed, 0x9c, 0x3a, 0x63, 0x36, 0x22, 0xa3, 0x01, 0x08, 0x52, 0xb8, 0x7d, 0x8a, 0x82, 0x07, 0x7b, 0xce, 0xe8, 0xc7, + 0x91, 0x97, 0x52, 0x3b, 0xa2, 0xf4, 0xd6, 0xf3, 0xe6, 0xd0, 0x73, 0xf7, 0x7b, 0x83, 0x39, 0x45, 0x29, 0x46, 0x06, + 0x74, 0x56, 0x5a, 0xd4, 0xc3, 0xc2, 0x2a, 0xf8, 0x0c, 0xb3, 0xbe, 0x11, 0xd4, 0x76, 0x75, 0x09, 0xc2, 0xda, 0x89, + 0x1a, 0x41, 0xe8, 0x4e, 0xac, 0xfb, 0x27, 0xda, 0xde, 0xec, 0xf3, 0x5f, 0x73, 0x8d, 0x65, 0xa6, 0x5d, 0x53, 0xb0, + 0xc7, 0xfb, 0xdd, 0x69, 0x93, 0xaf, 0x7f, 0x51, 0x1a, 0x65, 0xd0, 0xd7, 0x05, 0x17, 0x02, 0xe4, 0x15, 0xac, 0xf0, + 0xe6, 0x2e, 0x5e, 0xbe, 0x26, 0x2f, 0xf3, 0x5c, 0x82, 0x52, 0x11, 0xa1, 0xcf, 0x34, 0xd6, 0x40, 0xf6, 0xdf, 0x75, + 0x05, 0x77, 0x74, 0xfd, 0xc9, 0xdf, 0x70, 0xf2, 0x01, 0xf4, 0xb2, 0x91, 0x37, 0x83, 0x36, 0x03, 0x6d, 0x62, 0x2a, + 0x4c, 0x22, 0x4e, 0xd6, 0x2a, 0x57, 0x55, 0xd8, 0x3e, 0xac, 0xc0, 0x46, 0x3b, 0xed, 0xad, 0x57, 0x62, 0x17, 0xa8, + 0xeb, 0x38, 0xe7, 0x0b, 0x92, 0x55, 0xd8, 0x21, 0xb0, 0x5c, 0x7a, 0x55, 0x94, 0x3c, 0x20, 0xdd, 0x4f, 0x23, 0x32, + 0x94, 0xbe, 0x49, 0xe8, 0x4f, 0x3a, 0xc0, 0xab, 0xf5, 0xbb, 0xdc, 0x3a, 0x52, 0x58, 0xfb, 0x47, 0xb6, 0xbb, 0x60, + 0xd5, 0x1c, 0x48, 0x42, 0x74, 0xc9, 0xd5, 0x2d, 0xc0, 0xc9, 0x2f, 0xc5, 0x5a, 0x75, 0x83, 0x52, 0x05, 0x1e, 0x2b, + 0xcb, 0xa6, 0xe9, 0x60, 0x2e, 0x66, 0x7d, 0x83, 0xa4, 0x0f, 0xe9, 0x3d, 0x44, 0x4e, 0x05, 0x85, 0xde, 0xf3, 0x11, + 0x2c, 0x3b, 0x65, 0x09, 0x57, 0xa2, 0x75, 0x7b, 0xbb, 0xdf, 0x8c, 0x55, 0xcb, 0xc4, 0x7d, 0x41, 0x03, 0xd0, 0x94, + 0x0a, 0x36, 0x36, 0xa4, 0x4c, 0xbd, 0x20, 0xd3, 0xde, 0xa0, 0xc7, 0x76, 0xe4, 0xa3, 0x0d, 0x47, 0x8d, 0xa6, 0x5f, + 0xed, 0x35, 0xc6, 0x1e, 0x86, 0x26, 0xbd, 0xde, 0xda, 0xb7, 0x7e, 0xfc, 0x35, 0x07, 0xb9, 0xbe, 0x84, 0x0a, 0x32, + 0xdd, 0x48, 0x8b, 0x3e, 0x44, 0x2b, 0x98, 0x4a, 0x9d, 0xc3, 0x6f, 0xaf, 0x2e, 0xde, 0x27, 0x8d, 0x25, 0xed, 0xe3, + 0x5f, 0x71, 0x9b, 0x51, 0xf0, 0x15, 0x47, 0xc1, 0xdf, 0xc9, 0x91, 0x19, 0x06, 0x47, 0xdf, 0x50, 0xb4, 0xf3, 0xf7, + 0xfa, 0x76, 0x22, 0x98, 0x72, 0x7e, 0x86, 0x2d, 0xe1, 0xd8, 0x78, 0xe8, 0x9c, 0x8e, 0xed, 0x2d, 0xda, 0x47, 0x04, + 0x88, 0xbb, 0xeb, 0xeb, 0xd8, 0xdf, 0x4d, 0x8b, 0x4d, 0x9f, 0x6e, 0xa6, 0xcd, 0xca, 0x51, 0xfc, 0x07, 0x17, 0xb3, + 0x88, 0x8b, 0x12, 0x24, 0xd7, 0x5b, 0x84, 0x8b, 0x13, 0xa2, 0x9d, 0xeb, 0x4d, 0xcb, 0xf2, 0xdc, 0x9c, 0x8c, 0xdb, + 0xd5, 0xa4, 0xc0, 0x79, 0x62, 0x38, 0x21, 0x0a, 0xa0, 0xde, 0xf6, 0xe7, 0x5d, 0x47, 0x89, 0x9e, 0x8f, 0x1f, 0x6f, + 0x4d, 0xc2, 0x6d, 0x34, 0x5e, 0x96, 0xc3, 0x2a, 0x3e, 0x13, 0x51, 0x86, 0xc0, 0x41, 0xf6, 0x42, 0x05, 0xab, 0x79, + 0xb5, 0x8e, 0x14, 0xf6, 0x36, 0x07, 0x07, 0x0d, 0x2f, 0xb6, 0xd3, 0xb9, 0xd6, 0x8d, 0x40, 0xdb, 0x32, 0x07, 0x19, + 0xf9, 0x93, 0x9e, 0x70, 0x24, 0xcb, 0xf9, 0x5c, 0x45, 0xee, 0x48, 0x42, 0x3d, 0x99, 0xb2, 0xec, 0x66, 0x26, 0x9b, + 0xb9, 0xc8, 0x9d, 0xcc, 0x74, 0xda, 0xe8, 0x61, 0x50, 0xb0, 0x11, 0x64, 0x93, 0x61, 0x55, 0x14, 0xc5, 0x04, 0x43, + 0x01, 0x4e, 0xdf, 0xcb, 0xa2, 0xd0, 0x3d, 0x31, 0x62, 0x07, 0x30, 0xdd, 0xd0, 0x6c, 0xf4, 0x18, 0x71, 0x04, 0x3c, + 0x9e, 0xec, 0xdc, 0xf1, 0x27, 0xd8, 0xc2, 0x15, 0x2a, 0x69, 0xb1, 0xb6, 0x11, 0xe6, 0xb6, 0x66, 0x5c, 0x1c, 0xa2, + 0x37, 0x69, 0x32, 0x19, 0xc6, 0x0f, 0x86, 0xa5, 0x33, 0xd3, 0x0d, 0xa1, 0x09, 0x0e, 0x98, 0x7e, 0x86, 0x46, 0xe1, + 0xa9, 0xdf, 0xae, 0xb6, 0xee, 0x90, 0x20, 0x9b, 0x1d, 0x77, 0x51, 0xc1, 0x6a, 0xf2, 0x7d, 0xae, 0x34, 0x2f, 0xd6, + 0xce, 0x30, 0x83, 0x23, 0x4c, 0x16, 0x9c, 0xbd, 0x53, 0x64, 0x05, 0x10, 0x93, 0xce, 0x86, 0xc3, 0x35, 0xd4, 0x6a, + 0x88, 0xd3, 0x5e, 0x4d, 0x97, 0xa0, 0x77, 0x75, 0xfd, 0x1b, 0xb7, 0xc9, 0xc5, 0x4d, 0xcd, 0x24, 0x8e, 0x0a, 0x67, + 0xda, 0x60, 0x4c, 0xeb, 0xc8, 0x39, 0xc3, 0xbb, 0x1a, 0xb6, 0x8c, 0x32, 0xf4, 0x1c, 0x61, 0x76, 0xb3, 0x75, 0x17, + 0xef, 0xa0, 0x5d, 0x11, 0xd5, 0x54, 0x3c, 0x1f, 0xf8, 0x3a, 0x16, 0xe2, 0xef, 0xc3, 0x13, 0xe0, 0x75, 0x13, 0xb3, + 0xb7, 0x0b, 0xf5, 0x49, 0x71, 0xce, 0x02, 0xff, 0x27, 0x37, 0x92, 0x17, 0x45, 0x38, 0x2d, 0xf6, 0x91, 0x32, 0x63, + 0xd2, 0x94, 0x46, 0x97, 0x5a, 0xb1, 0xd7, 0xbf, 0x66, 0x4c, 0x66, 0xe0, 0x03, 0x05, 0x23, 0x8c, 0xef, 0x9b, 0x80, + 0xf0, 0x3c, 0xc1, 0x4e, 0x95, 0x1e, 0xb4, 0x2f, 0x64, 0x0c, 0x76, 0x47, 0x48, 0xdd, 0x69, 0x46, 0xfd, 0x59, 0x87, + 0x3e, 0x7d, 0xdd, 0x60, 0x7d, 0x60, 0xdb, 0x11, 0x33, 0xa2, 0x1b, 0x32, 0x84, 0xc0, 0x75, 0xdd, 0x78, 0x2a, 0xd3, + 0x4f, 0x15, 0x30, 0x05, 0x64, 0xc9, 0xb8, 0x76, 0xb1, 0x1a, 0x3b, 0xfe, 0xbe, 0x8e, 0x51, 0x29, 0xb2, 0xa6, 0x43, + 0xc1, 0xc6, 0xe5, 0xa8, 0x37, 0x70, 0x09, 0xda, 0x68, 0x32, 0x06, 0x46, 0x69, 0x6c, 0x46, 0x2e, 0x61, 0x5d, 0x4b, + 0x4b, 0xbc, 0x25, 0x2f, 0xb8, 0x79, 0xb2, 0xa4, 0x71, 0x97, 0xe4, 0x46, 0x83, 0x89, 0x73, 0xff, 0xbc, 0xea, 0xa8, + 0x0a, 0xc4, 0x0c, 0x27, 0xe9, 0x28, 0x24, 0xe8, 0x76, 0x06, 0x65, 0x53, 0x61, 0x58, 0x93, 0xcb, 0xcb, 0x77, 0xbf, + 0xa7, 0x06, 0xcc, 0xad, 0x1c, 0xf6, 0xa7, 0x5e, 0xcc, 0x10, 0x83, 0xd4, 0xe9, 0x49, 0xff, 0xa8, 0x6a, 0xb1, 0xbf, + 0xa0, 0x07, 0xf9, 0x1d, 0x1d, 0x9f, 0x86, 0xcd, 0x5e, 0x4f, 0xf7, 0xd7, 0x95, 0x4a, 0x7a, 0x89, 0x80, 0x62, 0x6f, + 0x58, 0xc4, 0x9e, 0x01, 0xdc, 0x9f, 0x97, 0x03, 0x1f, 0xc6, 0xe9, 0xe3, 0xd5, 0x4b, 0xf2, 0xb9, 0xc5, 0x26, 0x00, + 0x7d, 0xd8, 0x3a, 0xaf, 0xf0, 0x65, 0x58, 0x36, 0x79, 0xf2, 0xe9, 0xe3, 0xe5, 0xd5, 0xde, 0xc3, 0x79, 0xc7, 0x44, + 0x40, 0x64, 0xfd, 0xf3, 0x6e, 0x5e, 0x69, 0xde, 0x32, 0xa9, 0x3b, 0xb5, 0x8e, 0xe9, 0x22, 0x3b, 0x1f, 0xba, 0x73, + 0x7c, 0x03, 0x41, 0xef, 0x46, 0x2f, 0x98, 0x92, 0x1d, 0xaa, 0x9d, 0xb5, 0x7b, 0xb8, 0xbc, 0xfe, 0xb6, 0xbd, 0xfe, + 0xea, 0xbd, 0xee, 0xa5, 0xfb, 0x0f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00}; -} // namespace captive_portal -} // namespace esphome +#else // Brotli (default, smaller) +const uint8_t INDEX_BR[] PROGMEM = { + 0x1b, 0xf8, 0x0a, 0x00, 0x64, 0x5a, 0xd3, 0xfa, 0xe7, 0xf3, 0x62, 0xd8, 0x06, 0x1b, 0xe9, 0x6a, 0x8a, 0x81, 0x2b, + 0xb5, 0x49, 0x14, 0x37, 0xdc, 0x9e, 0x1a, 0xcb, 0x56, 0x87, 0xfb, 0xff, 0xf7, 0x73, 0x75, 0x12, 0x0a, 0xd6, 0x48, + 0x84, 0xc6, 0x21, 0xa4, 0x6d, 0xb5, 0x71, 0xef, 0x13, 0xbe, 0x4e, 0x54, 0xf1, 0x64, 0x8f, 0x3f, 0xcc, 0x9a, 0x78, + 0xa5, 0x89, 0x25, 0xb3, 0xda, 0x2c, 0xa2, 0x32, 0x9c, 0x57, 0x07, 0x56, 0xbc, 0x34, 0x13, 0xff, 0x5c, 0x0a, 0xa1, + 0x67, 0x82, 0xb8, 0x6b, 0x4c, 0x76, 0x31, 0x6c, 0xe3, 0x40, 0x46, 0xea, 0xb0, 0xd4, 0xf4, 0x3b, 0x02, 0x65, 0x18, + 0xa4, 0xaf, 0xac, 0x6d, 0x55, 0xd6, 0xbe, 0x59, 0x66, 0x7a, 0x7c, 0x60, 0xb2, 0x83, 0x33, 0x23, 0xc9, 0x79, 0x82, + 0x47, 0xb4, 0x28, 0xf4, 0x24, 0xb5, 0x23, 0x5a, 0x44, 0xe1, 0xc3, 0x27, 0x04, 0xe8, 0x0c, 0xdd, 0xb4, 0xd0, 0x8c, + 0xfb, 0x10, 0x39, 0x93, 0x04, 0x2a, 0x66, 0x18, 0x4b, 0x74, 0xca, 0x31, 0x7f, 0xb2, 0xe5, 0x45, 0xc1, 0xdd, 0x72, + 0x49, 0xff, 0x0e, 0xb3, 0xf0, 0x93, 0x18, 0xab, 0x68, 0xad, 0xe1, 0x9d, 0xe4, 0x29, 0xc0, 0xe3, 0x63, 0x54, 0x61, + 0x1b, 0x45, 0xb9, 0x6c, 0x23, 0x0f, 0x99, 0x7f, 0x8e, 0x69, 0xaa, 0xc1, 0xb8, 0x4e, 0x42, 0x9c, 0xc5, 0x6e, 0x69, + 0x40, 0x0e, 0x4f, 0x97, 0xd3, 0x23, 0x18, 0xf5, 0xc8, 0x75, 0x73, 0xb5, 0xbd, 0x46, 0x8a, 0x97, 0x7d, 0x83, 0xe4, + 0x29, 0x72, 0x73, 0xc1, 0x39, 0x8e, 0x7e, 0x84, 0x39, 0x66, 0x57, 0xc6, 0x85, 0x19, 0x8b, 0xf2, 0x4d, 0xd9, 0xfe, + 0x75, 0xa9, 0xe1, 0x2b, 0x21, 0x81, 0x58, 0x51, 0x99, 0xbc, 0xa4, 0x0b, 0x10, 0x6f, 0x86, 0x17, 0x0b, 0x92, 0x00, + 0x11, 0x6f, 0x3b, 0xa4, 0xa4, 0x11, 0x7e, 0x0b, 0x97, 0x85, 0x23, 0x0c, 0x01, 0x6f, 0x2a, 0x18, 0xc6, 0xbe, 0x3d, + 0x77, 0x1a, 0xe6, 0x00, 0x5c, 0x1a, 0x14, 0x47, 0xc6, 0xcc, 0xcc, 0x52, 0xbe, 0x04, 0x19, 0x31, 0x05, 0x46, 0xa0, + 0xc3, 0x69, 0x0c, 0x60, 0xb7, 0x14, 0x57, 0xa0, 0x92, 0xbf, 0xb7, 0x0c, 0xd8, 0x3a, 0x79, 0x09, 0x99, 0xc9, 0x71, + 0x88, 0x01, 0x8b, 0xa5, 0x61, 0x0a, 0xb5, 0xe8, 0xc7, 0x71, 0xe7, 0x70, 0x79, 0xb6, 0xe4, 0x01, 0xfc, 0x1a, 0x4a, + 0x7b, 0x60, 0x6e, 0xef, 0x95, 0x62, 0x59, 0x28, 0xb5, 0x25, 0x56, 0x15, 0xe7, 0xca, 0xad, 0x32, 0xe6, 0xf7, 0x01, + 0x31, 0x34, 0x87, 0x93, 0x0b, 0x9b, 0x9d, 0x26, 0xff, 0xe5, 0x92, 0xad, 0x6f, 0xb8, 0x3b, 0x16, 0xc1, 0xa0, 0x5a, + 0x4f, 0x52, 0x0b, 0x2b, 0xc1, 0xa7, 0x95, 0x7b, 0x24, 0x51, 0xd3, 0xb3, 0x23, 0x62, 0x0b, 0xcc, 0xa0, 0x58, 0xa7, + 0x64, 0x45, 0x2f, 0x0b, 0xdd, 0x1d, 0x97, 0x82, 0x1f, 0xcc, 0x64, 0xdb, 0xd3, 0xf4, 0xb0, 0x8b, 0xc8, 0xcf, 0x15, + 0x81, 0x8b, 0xa1, 0x9d, 0xf8, 0xfc, 0xec, 0x49, 0x40, 0x12, 0x01, 0x09, 0x51, 0xf3, 0x73, 0x18, 0x24, 0x97, 0x55, + 0x85, 0x6a, 0x92, 0x1a, 0xf5, 0x5a, 0x05, 0x54, 0x1f, 0x27, 0x0a, 0xa8, 0xa1, 0x94, 0x58, 0x78, 0x7d, 0x87, 0xa8, + 0xdb, 0x13, 0x66, 0x20, 0x5e, 0x43, 0x18, 0x7a, 0xbb, 0x16, 0x16, 0x07, 0xc8, 0xab, 0x10, 0xe2, 0x50, 0xb9, 0xb1, + 0xd8, 0x21, 0xc8, 0x4a, 0x2e, 0x99, 0x0e, 0x23, 0x52, 0xc6, 0xcb, 0x29, 0x84, 0x91, 0x03, 0xb1, 0xe2, 0x4c, 0x1d, + 0x22, 0xd3, 0xc8, 0x79, 0x00, 0x8b, 0x8b, 0x88, 0x1e, 0x29, 0xd3, 0xae, 0x10, 0x15, 0x22, 0x6d, 0xb0, 0x87, 0x6f, + 0x27, 0x2e, 0x7c, 0xc2, 0x7a, 0x61, 0xbd, 0x22, 0xe5, 0x5f, 0xdd, 0x7b, 0x00, 0x04, 0xf2, 0x7d, 0x5a, 0x03, 0x38, + 0x1f, 0x69, 0x6d, 0x0b, 0xfb, 0xec, 0x45, 0xfe, 0x8b, 0x7f, 0xec, 0x7b, 0xad, 0xc2, 0x33, 0xf1, 0x9e, 0x9c, 0x71, + 0xd9, 0xe8, 0x5e, 0x8f, 0xd4, 0xee, 0x87, 0x45, 0x6c, 0xe2, 0x12, 0xf8, 0xb8, 0xc5, 0xee, 0x43, 0xa6, 0x37, 0x91, + 0xb5, 0x2c, 0x2f, 0xe9, 0xe8, 0x24, 0xd0, 0x45, 0xc1, 0x0c, 0x7c, 0xf0, 0xb2, 0xb5, 0x2d, 0x10, 0x36, 0x7e, 0x18, + 0x7c, 0x79, 0x82, 0x69, 0x3d, 0x35, 0xca, 0x52, 0xee, 0xc9, 0xb5, 0x65, 0xa4, 0xa1, 0xfd, 0x70, 0x7e, 0xe0, 0x7d, + 0x67, 0xf9, 0xa1, 0x71, 0xd2, 0x08, 0x74, 0x33, 0x5f, 0x69, 0xa4, 0x59, 0x03, 0xfd, 0xf8, 0xf0, 0x70, 0x1a, 0x50, + 0x43, 0xfb, 0x61, 0xf0, 0x38, 0x18, 0x88, 0x85, 0x36, 0x23, 0x06, 0x4f, 0x02, 0xbb, 0x78, 0x1a, 0xaa, 0xd2, 0x02, + 0x5e, 0xa0, 0x74, 0x30, 0xc8, 0x7a, 0x66, 0xab, 0xd9, 0x43, 0x99, 0x45, 0xb7, 0x0c, 0x5c, 0xec, 0xc8, 0x03, 0x0e, + 0x0b, 0xca, 0x4a, 0x22, 0x48, 0xfb, 0xb7, 0x3d, 0x82, 0x07, 0x8d, 0x1b, 0x21, 0x87, 0x4d, 0x57, 0xa4, 0x5b, 0xd4, + 0xe3, 0x88, 0x02, 0xc4, 0x81, 0xf9, 0x47, 0xe4, 0xbf, 0x3e, 0x39, 0xbb, 0x4f, 0x7e, 0x91, 0x63, 0x98, 0x97, 0xe4, + 0x52, 0x01, 0x58, 0xba, 0x32, 0xbf, 0xae, 0xff, 0x45, 0xa1, 0xbc, 0x9b, 0xa4, 0x09, 0x0e, 0x79, 0xc0, 0x41, 0x86, + 0x52, 0x88, 0x55, 0x39, 0x9d, 0xb6, 0xed, 0x35, 0x68, 0x29, 0xfa, 0xe6, 0x6c, 0x3d, 0x0a, 0xcd, 0x6a, 0x28, 0xfd, + 0x65, 0x24, 0xce, 0x38, 0x98, 0x01, 0xd9, 0x3f, 0x1b, 0x4c, 0xc4, 0x5c, 0x1d, 0xaa, 0x21, 0x78, 0x67, 0xaf, 0x55, + 0x72, 0x34, 0xf8, 0x1b, 0x03, 0x21, 0x27, 0x08, 0xbd, 0x59, 0x60, 0x48, 0x0d, 0xe2, 0x56, 0x9b, 0x30, 0x92, 0x8f, + 0x67, 0x8a, 0x7f, 0x20, 0xbd, 0x2d, 0xfd, 0xc5, 0xb0, 0xa6, 0xaa, 0x77, 0x75, 0x26, 0x33, 0x2f, 0x20, 0x2a, 0xab, + 0x5c, 0xd1, 0x3b, 0xda, 0xb2, 0x4c, 0xa4, 0x86, 0x25, 0x8d, 0x49, 0x05, 0xaf, 0x7a, 0xa8, 0xd4, 0x9c, 0x0d, 0xd3, + 0x38, 0xa6, 0x5c, 0x29, 0x6b, 0x16, 0x27, 0x07, 0xf1, 0xbe, 0xe2, 0x24, 0xc1, 0x8d, 0x25, 0x76, 0xbc, 0xf6, 0x0d, + 0xc2, 0x94, 0x25, 0xb8, 0xf3, 0x07, 0x9a, 0x49, 0xf4, 0x89, 0x82, 0x4d, 0x51, 0xb1, 0x96, 0x61, 0x62, 0x8d, 0xc8, + 0x61, 0x65, 0x0d, 0x14, 0x34, 0x02, 0x65, 0x94, 0xcc, 0x1d, 0x85, 0x00, 0x0f, 0x1a, 0x57, 0x68, 0x15, 0xcf, 0xa4, + 0xa2, 0x7d, 0x6d, 0x53, 0x60, 0xce, 0x5c, 0x61, 0x82, 0x17, 0x32, 0xc1, 0x87, 0x02, 0x0c, 0x91, 0x85, 0x57, 0x51, + 0xbe, 0xb2, 0x38, 0x9f, 0x3d, 0x2a, 0x52, 0x5a, 0xad, 0xba, 0x46, 0x9e, 0x3c, 0x8a, 0xa0, 0x46, 0x15, 0xf4, 0x59, + 0x74, 0x5f, 0x2a, 0xae, 0x96, 0x56, 0xf0, 0x54, 0x39, 0xaf, 0xac, 0x2a, 0xb9, 0xad, 0x32, 0x50, 0xc9, 0xc1, 0xee, + 0xd2, 0x0d, 0x34, 0xaa, 0x98, 0x4d, 0x6d, 0x3d, 0xc6, 0xb9, 0x5b, 0x00, 0x5f, 0xea, 0xda, 0x16, 0xa6, 0x08, 0x43, + 0x58, 0x4d, 0x8d, 0x07, 0x55, 0x62, 0x81, 0x44, 0xcc, 0x31, 0x04, 0x4b, 0x4c, 0x8b, 0x3e, 0xff, 0xd8, 0xf6, 0x65, + 0x19, 0xa1, 0x94, 0x62, 0x65, 0x0a, 0xdd, 0x60, 0x38, 0xd3, 0xbe, 0x0d, 0xa3, 0x99, 0xd5, 0x37, 0x68, 0xa1, 0x71, + 0xa3, 0x41, 0xe7, 0xbe, 0x9d, 0x72, 0x84, 0x75, 0xb6, 0x8d, 0x98, 0xd6, 0xb8, 0x2d, 0x43, 0x85, 0x5d, 0xf9, 0xca, + 0xc3, 0x96, 0xa5, 0xa6, 0xe7, 0x50, 0x88, 0x6b, 0x84, 0x58, 0x44, 0x45, 0x20, 0xdf, 0x1e, 0x5a, 0xc9, 0xce, 0x42, + 0x2a, 0x1f, 0x3e, 0x3c, 0x7b, 0x68, 0x3c, 0x34, 0x8b, 0x36, 0xba, 0x1f, 0xce, 0x0f, 0xa0, 0x60, 0x37, 0x5f, 0x1a, + 0x03, 0x2b, 0x86, 0x29, 0x45, 0x7b, 0xb4, 0xb7, 0x06, 0x68, 0x17, 0x7e, 0x13, 0x76, 0x91, 0x4d, 0x27, 0xee, 0xbc, + 0x7e, 0x80, 0xc2, 0x66, 0xac, 0xc6, 0xbf, 0xeb, 0x7f, 0xd7, 0x84, 0x79, 0xf3, 0xf1, 0xde, 0xec, 0xa6, 0x93, 0xa8, + 0x13, 0x3b, 0x4a, 0x81, 0xfa, 0x11, 0x1e, 0x4a, 0xd2, 0x50, 0x2a, 0xea, 0x9a, 0xc2, 0x37, 0x08, 0xed, 0x01, 0xf5, + 0xa2, 0xd5, 0x32, 0x29, 0x49, 0xc4, 0x1a, 0x11, 0xc0, 0xda, 0x24, 0x28, 0x84, 0x38, 0x60, 0x80, 0xcf, 0xd0, 0x45, + 0x83, 0xa7, 0xca, 0x52, 0x5c, 0xac, 0x23, 0x01}; + +// Backwards compatibility alias +#define INDEX_GZ INDEX_BR + +#endif // USE_CAPTIVE_PORTAL_GZIP + +} // namespace esphome::captive_portal diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 5ba70bcc50..bf65ae67c0 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -112,7 +112,11 @@ void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { #else auto *response = req->beginResponse_P(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ)); #endif +#ifdef USE_CAPTIVE_PORTAL_GZIP response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip")); +#else + response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("br")); +#endif req->send(response); } diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 17ad496f30..7937e7a540 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -8,6 +8,7 @@ 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_COMPRESSION, CONF_CSS_INCLUDE, CONF_CSS_URL, CONF_ENABLE_PRIVATE_NETWORK_ACCESS, @@ -201,6 +202,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_OTA): cv.boolean, cv.Optional(CONF_LOG, default=True): cv.boolean, cv.Optional(CONF_LOCAL): cv.boolean, + cv.Optional(CONF_COMPRESSION, default="br"): cv.one_of("br", "gzip"), cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group), } ).extend(cv.COMPONENT_SCHEMA), @@ -330,6 +332,8 @@ async def to_code(config): cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) if CONF_LOCAL in config and config[CONF_LOCAL]: cg.add_define("USE_WEBSERVER_LOCAL") + if config[CONF_COMPRESSION] == "gzip": + cg.add_define("USE_WEBSERVER_GZIP") if (sorting_group_config := config.get(CONF_SORTING_GROUPS)) is not None: cg.add_define("USE_WEBSERVER_SORTING") diff --git a/esphome/components/web_server/server_index_v2.h b/esphome/components/web_server/server_index_v2.h index 4f2ea8a6ab..5f6d11865b 100644 --- a/esphome/components/web_server/server_index_v2.h +++ b/esphome/components/web_server/server_index_v2.h @@ -6,652 +6,1224 @@ #include "esphome/core/hal.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { +#ifdef USE_WEBSERVER_GZIP const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcd, 0x7d, 0xdb, 0x72, 0xdb, 0xc6, 0xb6, 0xe0, 0xf3, - 0xe4, 0x2b, 0x20, 0x44, 0x5b, 0x41, 0x6f, 0x36, 0xc1, 0x8b, 0x2e, 0x96, 0x41, 0x36, 0x19, 0x59, 0x76, 0xe2, 0x64, - 0xfb, 0x16, 0xcb, 0x4e, 0x76, 0xc2, 0x70, 0x4b, 0x10, 0xd1, 0x24, 0xda, 0x06, 0x01, 0x06, 0x68, 0x52, 0x52, 0x48, - 0x9c, 0x9a, 0x0f, 0x98, 0xaa, 0xa9, 0x9a, 0xa7, 0x79, 0x99, 0x9a, 0xf3, 0x30, 0x1f, 0x31, 0xcf, 0xe7, 0x53, 0xce, - 0x0f, 0xcc, 0x7c, 0xc2, 0xd4, 0xea, 0x0b, 0xd0, 0xe0, 0x45, 0x56, 0x2e, 0xe7, 0x9c, 0x29, 0x97, 0x6d, 0xa2, 0xd1, - 0x97, 0xd5, 0xab, 0x57, 0xaf, 0x7b, 0x37, 0xba, 0x7b, 0x41, 0x32, 0xe2, 0x77, 0x33, 0x6a, 0x85, 0x7c, 0x1a, 0xf5, - 0xba, 0xea, 0x5f, 0xea, 0x07, 0xbd, 0x6e, 0xc4, 0xe2, 0x8f, 0x56, 0x4a, 0x23, 0xc2, 0x46, 0x49, 0x6c, 0x85, 0x29, - 0x1d, 0x93, 0xc0, 0xe7, 0xbe, 0xc7, 0xa6, 0xfe, 0x84, 0x5a, 0x8d, 0x5e, 0x77, 0x4a, 0xb9, 0x6f, 0x8d, 0x42, 0x3f, - 0xcd, 0x28, 0x27, 0xef, 0xdf, 0x7d, 0x55, 0x3f, 0xed, 0x75, 0xb3, 0x51, 0xca, 0x66, 0xdc, 0x82, 0x2e, 0xc9, 0x34, - 0x09, 0xe6, 0x11, 0xed, 0x35, 0x1a, 0x37, 0x37, 0x37, 0xee, 0x87, 0xec, 0xb3, 0x51, 0x12, 0x67, 0xdc, 0x7a, 0x41, - 0x6e, 0x58, 0x1c, 0x24, 0x37, 0x98, 0x71, 0xf2, 0xc2, 0xbd, 0x08, 0xfd, 0x20, 0xb9, 0x79, 0x9b, 0x24, 0xfc, 0xe0, - 0xc0, 0x91, 0x8f, 0x77, 0xe7, 0x17, 0x17, 0x84, 0x90, 0x45, 0xc2, 0x02, 0xab, 0xb9, 0x5a, 0x95, 0x85, 0x6e, 0xec, - 0x73, 0xb6, 0xa0, 0xb2, 0x09, 0x3a, 0x38, 0xb0, 0xfd, 0x20, 0x99, 0x71, 0x1a, 0x5c, 0xf0, 0xbb, 0x88, 0x5e, 0x84, - 0x94, 0xf2, 0xcc, 0x66, 0xb1, 0xf5, 0x34, 0x19, 0xcd, 0xa7, 0x34, 0xe6, 0xee, 0x2c, 0x4d, 0x78, 0x02, 0x90, 0x1c, - 0x1c, 0xd8, 0x29, 0x9d, 0x45, 0xfe, 0x88, 0xc2, 0xfb, 0xf3, 0x8b, 0x8b, 0xb2, 0x45, 0x59, 0x09, 0x67, 0x9c, 0x5c, - 0xdc, 0x4d, 0xaf, 0x93, 0xc8, 0x41, 0xd8, 0xe7, 0x24, 0xa6, 0x37, 0xd6, 0x0f, 0xd4, 0xff, 0xf8, 0xd2, 0x9f, 0x75, - 0x46, 0x91, 0x9f, 0x65, 0xd6, 0x2d, 0x5f, 0x8a, 0x29, 0xa4, 0xf3, 0x11, 0x4f, 0x52, 0x87, 0x63, 0x8a, 0x19, 0x5a, - 0xb2, 0xb1, 0xc3, 0x43, 0x96, 0xb9, 0x97, 0xfb, 0xa3, 0x2c, 0x7b, 0x4b, 0xb3, 0x79, 0xc4, 0xf7, 0xc9, 0x5e, 0x13, - 0xb3, 0x3d, 0x42, 0x32, 0x8e, 0x78, 0x98, 0x26, 0x37, 0xd6, 0xb3, 0x34, 0x4d, 0x52, 0xc7, 0x3e, 0xbf, 0xb8, 0x90, - 0x35, 0x2c, 0x96, 0x59, 0x71, 0xc2, 0xad, 0xa2, 0x3f, 0xff, 0x3a, 0xa2, 0xae, 0xf5, 0x3e, 0xa3, 0xd6, 0xd5, 0x3c, - 0xce, 0xfc, 0x31, 0x3d, 0xbf, 0xb8, 0xb8, 0xb2, 0x92, 0xd4, 0xba, 0x1a, 0x65, 0xd9, 0x95, 0xc5, 0xe2, 0x8c, 0x53, - 0x3f, 0x70, 0x6d, 0xd4, 0x11, 0x83, 0x8d, 0xb2, 0xec, 0x1d, 0xbd, 0xe5, 0x84, 0x63, 0xf1, 0xc8, 0x09, 0xcd, 0x27, - 0x94, 0x5b, 0x59, 0x31, 0x2f, 0x07, 0x2d, 0x23, 0xca, 0x2d, 0x4e, 0xc4, 0xfb, 0xa4, 0x23, 0x71, 0x4f, 0xe5, 0x23, - 0xef, 0xb0, 0xb1, 0xc3, 0xf8, 0xc1, 0x01, 0x2f, 0xf0, 0x8c, 0xe4, 0xd4, 0x2c, 0x46, 0xe8, 0x9e, 0x2e, 0x3b, 0x38, - 0xa0, 0x6e, 0x44, 0xe3, 0x09, 0x0f, 0x09, 0x21, 0xad, 0x0e, 0x3b, 0x38, 0x70, 0x38, 0xf1, 0xb9, 0x3b, 0xa1, 0xdc, - 0xa1, 0x08, 0xe1, 0xb2, 0xf5, 0xc1, 0x81, 0x23, 0x91, 0x90, 0x10, 0x89, 0xb8, 0x0a, 0x8e, 0x91, 0xab, 0xb0, 0x7f, - 0x71, 0x17, 0x8f, 0x1c, 0x13, 0x7e, 0x84, 0xd9, 0xc1, 0x81, 0xcf, 0xdd, 0x0c, 0x7a, 0xc4, 0x1c, 0xa1, 0x3c, 0xa5, - 0x7c, 0x9e, 0xc6, 0x16, 0xcf, 0x79, 0x72, 0xc1, 0x53, 0x16, 0x4f, 0x1c, 0xb4, 0xd4, 0x65, 0x46, 0xc3, 0x3c, 0x97, - 0xe0, 0xbe, 0xe2, 0x24, 0x26, 0x3d, 0x18, 0xf1, 0x96, 0x3b, 0xb0, 0x8a, 0xc9, 0xd8, 0x8a, 0x09, 0xb1, 0x33, 0xd1, - 0xd6, 0xee, 0xc7, 0x5e, 0x5c, 0xb3, 0x6d, 0x2c, 0xa1, 0xc4, 0x19, 0x47, 0xf8, 0x35, 0x71, 0x62, 0xec, 0xba, 0x2e, - 0x47, 0xa4, 0xb7, 0xd4, 0x58, 0x89, 0x8d, 0x79, 0xf6, 0xe3, 0x41, 0x73, 0xe8, 0x71, 0x37, 0xa5, 0xc1, 0x7c, 0x44, - 0x1d, 0x87, 0xe1, 0x0c, 0xa7, 0x88, 0xf4, 0x58, 0xcd, 0x49, 0x48, 0x0f, 0x96, 0x3b, 0xa9, 0xae, 0x35, 0x21, 0x7b, - 0x4d, 0xa4, 0x60, 0x4c, 0x34, 0x80, 0x80, 0x61, 0x05, 0x4f, 0x42, 0x88, 0x1d, 0xcf, 0xa7, 0xd7, 0x34, 0xb5, 0x8b, - 0x6a, 0x9d, 0x0a, 0x59, 0xcc, 0x33, 0x6a, 0x8d, 0xb2, 0xcc, 0x1a, 0xcf, 0xe3, 0x11, 0x67, 0x49, 0x6c, 0xd9, 0xb5, - 0xa4, 0x66, 0x4b, 0x72, 0x28, 0xa8, 0xc1, 0x46, 0x39, 0x72, 0x32, 0x54, 0x8b, 0x07, 0x69, 0xad, 0x35, 0xc4, 0x00, - 0x25, 0xea, 0xa8, 0xfe, 0x14, 0x02, 0x28, 0x8e, 0x61, 0x8e, 0x39, 0x7e, 0xcb, 0x61, 0x96, 0x62, 0x8a, 0x8c, 0xf7, - 0x63, 0x77, 0x73, 0xa3, 0x10, 0xee, 0x4e, 0xfd, 0x99, 0x43, 0x49, 0x8f, 0x0a, 0xe2, 0xf2, 0xe3, 0x11, 0xc0, 0x5a, - 0x59, 0xb7, 0x3e, 0xf5, 0xa8, 0x5b, 0x92, 0x14, 0xf2, 0xb8, 0x3b, 0x4e, 0xd2, 0x67, 0xfe, 0x28, 0x84, 0x76, 0x05, - 0xc1, 0x04, 0x7a, 0xbf, 0x8d, 0x52, 0xea, 0x73, 0xfa, 0x2c, 0xa2, 0xf0, 0xe4, 0xd8, 0xa2, 0xa5, 0x8d, 0x70, 0x46, - 0x5e, 0xb8, 0x11, 0xe3, 0xaf, 0x92, 0x78, 0x44, 0x3b, 0x99, 0x41, 0x5d, 0x0c, 0xd6, 0xfd, 0x8c, 0xf3, 0x94, 0x5d, - 0xcf, 0x39, 0x75, 0xec, 0x18, 0x6a, 0xd8, 0x38, 0x43, 0x98, 0xb9, 0x9c, 0xde, 0xf2, 0xf3, 0x24, 0xe6, 0x34, 0xe6, - 0x84, 0x6a, 0xa4, 0xe2, 0xd8, 0xf5, 0x67, 0x33, 0x1a, 0x07, 0xe7, 0x21, 0x8b, 0x02, 0x87, 0xa1, 0x1c, 0xe5, 0x38, - 0xe4, 0x04, 0xe6, 0x48, 0x7a, 0xb1, 0x07, 0xff, 0xec, 0x9e, 0x8d, 0xc3, 0x49, 0x4f, 0x6c, 0x0a, 0x4a, 0x6c, 0xbb, - 0x33, 0x4e, 0x52, 0x47, 0xcd, 0xc0, 0x4a, 0xc6, 0x16, 0x87, 0x31, 0xde, 0xce, 0x23, 0x9a, 0x21, 0x5a, 0x23, 0xac, - 0x58, 0x46, 0x85, 0xe0, 0x57, 0x40, 0xf1, 0x39, 0x72, 0x62, 0xe4, 0xc5, 0x9d, 0x85, 0x9f, 0x5a, 0x3f, 0xa8, 0x1d, - 0xf5, 0x54, 0x73, 0xb3, 0x11, 0x27, 0x4f, 0x5d, 0x9e, 0xce, 0x33, 0x4e, 0x83, 0x77, 0x77, 0x33, 0x9a, 0xe1, 0xe7, - 0x9c, 0x8c, 0x78, 0x7f, 0xc4, 0x5d, 0x3a, 0x9d, 0xf1, 0xbb, 0x0b, 0xc1, 0x18, 0x3d, 0xdb, 0xc6, 0x01, 0xd4, 0x4c, - 0xa9, 0x3f, 0x02, 0x66, 0xa6, 0xb0, 0xf5, 0x26, 0x89, 0xee, 0xc6, 0x2c, 0x8a, 0x2e, 0xe6, 0xb3, 0x59, 0x92, 0x72, - 0xfc, 0x77, 0xb2, 0xe4, 0x49, 0x89, 0x1a, 0x58, 0xcb, 0x65, 0x76, 0xc3, 0xf8, 0x28, 0x74, 0x38, 0x5a, 0x8e, 0xfc, - 0x8c, 0x5a, 0x4f, 0x92, 0x24, 0xa2, 0x3e, 0x4c, 0x3a, 0xee, 0x3f, 0xe7, 0x5e, 0x3c, 0x8f, 0xa2, 0xce, 0x75, 0x4a, - 0xfd, 0x8f, 0x1d, 0xf1, 0xfa, 0xf5, 0xf5, 0x07, 0x3a, 0xe2, 0x9e, 0xf8, 0x7d, 0x96, 0xa6, 0xfe, 0x1d, 0x54, 0x24, - 0x04, 0xaa, 0xf5, 0x63, 0xef, 0xdb, 0x8b, 0xd7, 0xaf, 0x5c, 0xb9, 0x49, 0xd8, 0xf8, 0xce, 0x89, 0x8b, 0x8d, 0x17, - 0xe7, 0x78, 0x9c, 0x26, 0xd3, 0xb5, 0xa1, 0x25, 0xd6, 0xe2, 0xce, 0x0e, 0x10, 0x28, 0x89, 0xf7, 0x64, 0xd7, 0x26, - 0x04, 0xaf, 0x04, 0xcd, 0xc3, 0x4b, 0xa2, 0xc7, 0x9d, 0x47, 0x91, 0x27, 0x8b, 0x9d, 0x18, 0xdd, 0x0f, 0x2d, 0x4f, - 0xef, 0x96, 0x94, 0x08, 0x38, 0x67, 0x20, 0x61, 0x00, 0xc6, 0x91, 0xcf, 0x47, 0xe1, 0x92, 0x8a, 0xce, 0x72, 0x0d, - 0x31, 0xcd, 0x73, 0x7c, 0x56, 0xd0, 0x3b, 0x07, 0x40, 0x04, 0xa3, 0x22, 0x7c, 0xb5, 0x82, 0x09, 0x23, 0xfc, 0x13, - 0x59, 0xfa, 0x7a, 0x3e, 0xde, 0x5e, 0x13, 0xc3, 0xbe, 0xf4, 0x24, 0x77, 0xc1, 0xa3, 0x24, 0x5e, 0xd0, 0x94, 0xd3, - 0xd4, 0xfb, 0x3b, 0x4e, 0xe9, 0x38, 0x02, 0x28, 0xf6, 0x5a, 0x38, 0xf4, 0xb3, 0xf3, 0xd0, 0x8f, 0x27, 0x34, 0xf0, - 0xce, 0x78, 0x8e, 0x39, 0x27, 0xf6, 0x98, 0xc5, 0x7e, 0xc4, 0x7e, 0xa5, 0x81, 0xad, 0xc4, 0xc1, 0x33, 0x8b, 0xde, - 0x72, 0x1a, 0x07, 0x99, 0xf5, 0xfc, 0xdd, 0xcb, 0x17, 0x6a, 0x21, 0x2b, 0x12, 0x02, 0x2d, 0xb3, 0xf9, 0x8c, 0xa6, - 0x0e, 0xc2, 0x4a, 0x42, 0x3c, 0x63, 0x82, 0x3b, 0xbe, 0xf4, 0x67, 0xb2, 0x84, 0x65, 0xef, 0x67, 0x81, 0xcf, 0xe9, - 0x1b, 0x1a, 0x07, 0x2c, 0x9e, 0x90, 0xbd, 0x96, 0x2c, 0x0f, 0x7d, 0xf5, 0x22, 0x28, 0x8a, 0x2e, 0xf7, 0x9f, 0x45, - 0x62, 0xe2, 0xc5, 0xe3, 0xdc, 0x41, 0x79, 0xc6, 0x7d, 0xce, 0x46, 0x96, 0x1f, 0x04, 0xdf, 0xc4, 0x8c, 0x33, 0x01, - 0x60, 0x0a, 0xeb, 0x03, 0x34, 0x4a, 0xa5, 0xac, 0xd0, 0x80, 0x3b, 0x08, 0x3b, 0x8e, 0x92, 0x00, 0x21, 0x52, 0x0b, - 0x76, 0x70, 0x50, 0xf2, 0xfb, 0x3e, 0xf5, 0xe4, 0x4b, 0x32, 0x18, 0x22, 0x77, 0x36, 0xcf, 0x60, 0xa5, 0xf5, 0x10, - 0x20, 0x5e, 0x92, 0xeb, 0x8c, 0xa6, 0x0b, 0x1a, 0x14, 0xd4, 0x91, 0x39, 0x68, 0xb9, 0x36, 0x86, 0xda, 0x17, 0x9c, - 0x0c, 0x86, 0x1d, 0x93, 0x71, 0x53, 0x45, 0xe8, 0x69, 0x32, 0xa3, 0x29, 0x67, 0x34, 0x2b, 0x78, 0x89, 0x03, 0x62, - 0xb4, 0xe0, 0x27, 0x19, 0xd1, 0xf3, 0x9b, 0x39, 0x0c, 0x53, 0x54, 0xe1, 0x18, 0x5a, 0xd2, 0x3e, 0x5b, 0x08, 0x91, - 0x91, 0x61, 0x86, 0x30, 0x97, 0x90, 0x66, 0x08, 0xe5, 0x08, 0x73, 0x0d, 0xae, 0xe4, 0x45, 0x6a, 0xb4, 0x3b, 0x90, - 0xd5, 0xe4, 0x27, 0x21, 0xab, 0x81, 0xa3, 0xf9, 0x9c, 0x1e, 0x1c, 0x38, 0xd4, 0x2d, 0xa8, 0x82, 0xec, 0xb5, 0xd4, - 0x1a, 0x19, 0xc8, 0xda, 0x01, 0x36, 0x0c, 0xcc, 0x31, 0x45, 0x78, 0x8f, 0xba, 0x71, 0x72, 0x36, 0x1a, 0xd1, 0x2c, - 0x4b, 0xd2, 0x83, 0x83, 0x3d, 0x51, 0xbf, 0x50, 0x27, 0x60, 0x0d, 0x5f, 0xdf, 0xc4, 0x25, 0x04, 0xa8, 0x14, 0xb1, - 0x4a, 0x30, 0x70, 0x10, 0x54, 0x42, 0xe3, 0xb0, 0xfb, 0x5a, 0xf3, 0xf0, 0xec, 0xcb, 0x4b, 0xbb, 0xc6, 0xb1, 0x42, - 0xc3, 0x84, 0xea, 0xa1, 0xef, 0x9e, 0x52, 0xa9, 0x5b, 0x09, 0xcd, 0x63, 0x03, 0x33, 0x72, 0x03, 0xb9, 0x01, 0x1d, - 0xb3, 0xd8, 0x98, 0x76, 0x05, 0x24, 0xcc, 0x71, 0x86, 0x72, 0x63, 0x41, 0xb7, 0x76, 0x2d, 0x94, 0x1a, 0xb9, 0x72, - 0xcb, 0x89, 0x50, 0x24, 0x8c, 0x65, 0x1c, 0xd0, 0x61, 0x8e, 0x05, 0xea, 0xf5, 0x6c, 0x52, 0x01, 0xe8, 0x80, 0x0f, - 0x3b, 0xea, 0x3d, 0xc9, 0x24, 0xe6, 0x52, 0xfa, 0xcb, 0x9c, 0x66, 0x5c, 0xd2, 0xb1, 0xc3, 0x71, 0x8a, 0x19, 0xca, - 0x61, 0xbf, 0x8d, 0xd9, 0x64, 0x9e, 0x82, 0xbe, 0x03, 0x7b, 0x91, 0xc6, 0xf3, 0x29, 0xd5, 0x4f, 0xdb, 0x60, 0x7b, - 0x3d, 0x03, 0x89, 0x98, 0x01, 0x4d, 0xdf, 0x4f, 0x4e, 0x00, 0x2b, 0x47, 0xab, 0xd5, 0x4f, 0xba, 0x93, 0x72, 0x29, - 0x0b, 0x1d, 0x6d, 0x7d, 0x4d, 0x38, 0x52, 0x12, 0x79, 0xaf, 0x25, 0xc1, 0xe7, 0x7c, 0x48, 0xf6, 0x9a, 0x05, 0x0d, - 0x2b, 0xac, 0x4a, 0x70, 0x24, 0x12, 0x5f, 0xcb, 0xae, 0x90, 0x10, 0xf0, 0x15, 0x72, 0x71, 0xc3, 0x0d, 0x4a, 0x0d, - 0xc9, 0x00, 0x54, 0x0d, 0x37, 0x1c, 0xee, 0x22, 0x27, 0xcd, 0x0f, 0x1c, 0xbe, 0xf9, 0xae, 0x64, 0x1b, 0x8b, 0x2a, - 0xdb, 0x58, 0x9b, 0x86, 0x3d, 0x2b, 0x9a, 0xd8, 0x05, 0x95, 0xa9, 0x8d, 0x5e, 0xbe, 0xc2, 0x4c, 0x00, 0x53, 0x4e, - 0xc9, 0xe8, 0xe2, 0x95, 0x3f, 0xa5, 0x99, 0x43, 0x11, 0xde, 0x55, 0x41, 0x92, 0x27, 0x54, 0x19, 0x1a, 0x92, 0x33, - 0x03, 0xc9, 0xc9, 0x90, 0x54, 0xcc, 0xaa, 0x1b, 0x2e, 0xc3, 0x74, 0x90, 0x0d, 0x4b, 0x7d, 0xce, 0x98, 0xbc, 0x10, - 0xc9, 0x8a, 0xbe, 0x35, 0xfe, 0x64, 0x99, 0x44, 0x9a, 0xd0, 0x1b, 0x32, 0x84, 0xf7, 0x9a, 0xeb, 0x2b, 0xa9, 0x6b, - 0x95, 0x73, 0x1c, 0x0c, 0x61, 0x1d, 0x84, 0xc4, 0x70, 0x59, 0x26, 0xfe, 0xaf, 0xec, 0x34, 0x40, 0xdb, 0x05, 0x10, - 0x86, 0x3b, 0x8e, 0x7c, 0xee, 0xb4, 0x1a, 0x4d, 0x50, 0x46, 0x17, 0x14, 0x04, 0x0a, 0x42, 0x9b, 0x53, 0xa1, 0xee, - 0x3c, 0xce, 0x42, 0x36, 0xe6, 0x4e, 0xc8, 0x05, 0x4b, 0xa1, 0x51, 0x46, 0x2d, 0x5e, 0x51, 0x89, 0x05, 0xbb, 0x09, - 0x81, 0xd8, 0x0a, 0xfd, 0x8b, 0x6a, 0x48, 0x05, 0xdb, 0x02, 0xee, 0x50, 0xaa, 0xd3, 0x25, 0x97, 0xd1, 0xb5, 0x19, - 0xa8, 0x8c, 0xad, 0xbe, 0xec, 0xd1, 0x53, 0xcc, 0x80, 0x19, 0x5a, 0x2b, 0xf3, 0x4c, 0x0e, 0xa1, 0x0a, 0xb9, 0xcb, - 0x93, 0x17, 0xc9, 0x0d, 0x4d, 0xcf, 0x7d, 0x00, 0xde, 0x93, 0xcd, 0x73, 0x29, 0x08, 0x04, 0xbf, 0xe7, 0x1d, 0x4d, - 0x2f, 0x97, 0x62, 0xe2, 0x6f, 0xd2, 0x64, 0xca, 0x32, 0x0a, 0xca, 0x9a, 0xc4, 0x7f, 0x0c, 0xfb, 0x4c, 0x6c, 0x48, - 0x10, 0x36, 0xb4, 0xa0, 0xaf, 0xb3, 0x17, 0x55, 0xfa, 0xba, 0xdc, 0x7f, 0x36, 0xd1, 0x0c, 0xb0, 0xba, 0x8d, 0x11, - 0x76, 0x94, 0x49, 0x61, 0xc8, 0x39, 0x37, 0x44, 0x4a, 0xc2, 0xaf, 0x56, 0xdc, 0xb0, 0xdc, 0x2a, 0xea, 0x22, 0x95, - 0xdb, 0x06, 0xe5, 0x7e, 0x10, 0x80, 0x62, 0x97, 0x26, 0x51, 0x64, 0x88, 0x2a, 0xcc, 0x3a, 0x85, 0x70, 0xba, 0xdc, - 0x7f, 0x76, 0x71, 0x9f, 0x7c, 0x82, 0xf7, 0xa6, 0x88, 0xd2, 0x80, 0xc6, 0x01, 0x4d, 0xc1, 0x92, 0x34, 0x56, 0x4b, - 0x49, 0xd9, 0xf3, 0x24, 0x8e, 0xe9, 0x88, 0xd3, 0x00, 0x0c, 0x15, 0x46, 0xb8, 0x1b, 0x26, 0x19, 0x2f, 0x0a, 0x4b, - 0xe8, 0x99, 0x01, 0x3d, 0x73, 0x47, 0x7e, 0x14, 0x39, 0xd2, 0x28, 0x99, 0x26, 0x0b, 0xba, 0x05, 0xea, 0x4e, 0x05, - 0xe4, 0xa2, 0x1b, 0x6a, 0x74, 0x43, 0xdd, 0x6c, 0x16, 0xb1, 0x11, 0x2d, 0x44, 0xd7, 0x85, 0xcb, 0xe2, 0x80, 0xde, - 0x02, 0x1f, 0x41, 0xbd, 0x5e, 0xaf, 0x89, 0x5b, 0x28, 0x97, 0x08, 0x5f, 0x6e, 0x20, 0xf6, 0x1e, 0xa1, 0x09, 0x44, - 0x46, 0x7a, 0xcb, 0x6d, 0xfc, 0x80, 0x22, 0x43, 0x52, 0x32, 0x6d, 0x5c, 0x49, 0xee, 0x8c, 0x70, 0x40, 0x23, 0xca, - 0xa9, 0xe6, 0xe6, 0xa0, 0x42, 0xcb, 0xad, 0xfb, 0xb6, 0xc0, 0x5f, 0x41, 0x4e, 0x7a, 0x97, 0xe9, 0x35, 0xcf, 0x0a, - 0x63, 0xbd, 0x5c, 0x9e, 0x12, 0xdb, 0x7d, 0x2e, 0x97, 0xc7, 0xe7, 0xdc, 0x1f, 0x85, 0xd2, 0x4a, 0x77, 0x36, 0xa6, - 0x54, 0xf6, 0xa1, 0x38, 0x7b, 0xb1, 0x89, 0xde, 0x6a, 0x30, 0xb7, 0xa1, 0xe0, 0x42, 0x31, 0x05, 0x0a, 0x86, 0x9f, - 0x5c, 0xb6, 0x73, 0x3f, 0x8a, 0xae, 0xfd, 0xd1, 0xc7, 0x2a, 0xf5, 0x97, 0x64, 0x40, 0xd6, 0xb9, 0xb1, 0xf1, 0xca, - 0x60, 0x59, 0xe6, 0xbc, 0x35, 0x97, 0xae, 0x6c, 0x14, 0x67, 0xaf, 0x59, 0x92, 0x7d, 0x75, 0xa1, 0x77, 0x52, 0xbb, - 0x80, 0x88, 0xa9, 0x99, 0x39, 0xc0, 0x05, 0x3e, 0x49, 0x71, 0x9a, 0x1f, 0x28, 0xba, 0x03, 0x73, 0x23, 0x5f, 0x03, - 0x84, 0xa3, 0x65, 0x1e, 0xb0, 0x6c, 0x37, 0x06, 0xfe, 0x14, 0x28, 0x9f, 0x1a, 0x23, 0x3c, 0x14, 0xd0, 0x82, 0xc7, - 0x29, 0xad, 0xb9, 0x80, 0x4c, 0xe9, 0x13, 0x9a, 0xd1, 0xfc, 0x0d, 0x74, 0x17, 0x41, 0xef, 0xaf, 0xe5, 0x2b, 0xd0, - 0xca, 0x00, 0x8a, 0xac, 0x63, 0xaa, 0x13, 0x15, 0x0a, 0x50, 0x3c, 0x95, 0x09, 0x91, 0x9b, 0x56, 0xec, 0x47, 0xa5, - 0xb1, 0x4b, 0x13, 0x5c, 0xb1, 0xdc, 0x84, 0x38, 0x8e, 0x93, 0x81, 0x09, 0xa7, 0x55, 0xfb, 0x72, 0x12, 0xd9, 0xc6, - 0x24, 0x32, 0xd7, 0xb0, 0xb3, 0x50, 0x49, 0xcb, 0x46, 0x73, 0xef, 0xef, 0xc8, 0xac, 0x04, 0xea, 0xaa, 0x0b, 0xfc, - 0x19, 0x15, 0xec, 0x36, 0x22, 0x1c, 0x27, 0xca, 0xc6, 0x51, 0x94, 0x06, 0x0c, 0xa3, 0x6c, 0x92, 0x22, 0xb9, 0x35, - 0x2a, 0xf6, 0x6e, 0x8a, 0x13, 0xb4, 0xa6, 0xdb, 0xe7, 0xb9, 0xc2, 0x11, 0x45, 0x6a, 0x6d, 0x2a, 0x4a, 0xb1, 0x81, - 0x15, 0x9c, 0x12, 0xa5, 0x08, 0x4b, 0xbd, 0x67, 0x1d, 0x37, 0x45, 0xbf, 0x7b, 0x84, 0xa4, 0x25, 0x6a, 0x2a, 0x1a, - 0xa5, 0x56, 0xad, 0x52, 0x84, 0x43, 0xad, 0x93, 0x26, 0xe5, 0xbc, 0x09, 0xb1, 0xb5, 0x43, 0xc2, 0xee, 0x2f, 0x2b, - 0x56, 0xa1, 0x67, 0x54, 0xcb, 0x3d, 0x60, 0xa9, 0xc9, 0x36, 0x74, 0x6f, 0xa3, 0x99, 0x4a, 0x3f, 0x06, 0xc2, 0x13, - 0x13, 0xe1, 0x06, 0x66, 0x53, 0xc9, 0xb9, 0xd2, 0x21, 0x09, 0xab, 0x6d, 0x1d, 0x8a, 0x13, 0xb9, 0x0e, 0x1b, 0x48, - 0x5c, 0x57, 0x3d, 0x05, 0x09, 0x82, 0x0d, 0x9b, 0x81, 0x72, 0x67, 0xca, 0x07, 0x07, 0x60, 0x67, 0xab, 0xd5, 0x06, - 0xd1, 0x6d, 0xd5, 0x40, 0x91, 0x5b, 0xda, 0x85, 0xab, 0xd5, 0x19, 0x47, 0x8e, 0xd2, 0x7d, 0x31, 0x45, 0x7d, 0xcd, - 0x71, 0xcf, 0x5e, 0x40, 0x2d, 0xa1, 0x8a, 0x96, 0x25, 0x85, 0xd1, 0x50, 0xa5, 0xd9, 0xea, 0x3a, 0x71, 0x83, 0x6d, - 0x9f, 0x6f, 0x70, 0x2f, 0x51, 0xa8, 0xc4, 0x74, 0x39, 0xe5, 0x73, 0xd5, 0x35, 0x43, 0x08, 0x79, 0x99, 0xb0, 0x63, - 0xf6, 0xb6, 0x99, 0x96, 0x07, 0x07, 0x99, 0xd1, 0xd1, 0x65, 0xc1, 0x26, 0x3e, 0x38, 0x20, 0x92, 0xb3, 0xbb, 0x58, - 0xe8, 0x2e, 0x1f, 0xb4, 0x10, 0xda, 0x30, 0x4c, 0x9b, 0x1d, 0x30, 0xc8, 0xfd, 0x1b, 0x9f, 0x71, 0xab, 0xe8, 0x45, - 0x1a, 0xe4, 0x0e, 0x45, 0x4b, 0xa5, 0x6a, 0xb8, 0x29, 0x05, 0xe5, 0x11, 0x78, 0x82, 0x56, 0xa1, 0x25, 0xdd, 0x8f, - 0x42, 0x0a, 0xbe, 0x60, 0xad, 0x45, 0x14, 0x96, 0xe1, 0x9e, 0x92, 0x22, 0xaa, 0xe3, 0xed, 0xb0, 0xe7, 0xeb, 0xcd, - 0x2b, 0x96, 0xc0, 0x8c, 0xa6, 0xe3, 0x24, 0x9d, 0xea, 0x77, 0xf9, 0xda, 0xb3, 0xe2, 0x8c, 0x6c, 0xec, 0x6c, 0xed, - 0x5b, 0xe9, 0xff, 0x9d, 0x35, 0xb3, 0xbb, 0x34, 0xd8, 0x2b, 0xa2, 0xb4, 0x90, 0xbe, 0xd2, 0x25, 0xa8, 0x29, 0x33, - 0x33, 0x0d, 0x7c, 0xe5, 0x4f, 0xed, 0x48, 0x9f, 0xc9, 0x5e, 0xab, 0x53, 0x58, 0x7d, 0x9a, 0x1a, 0x3a, 0xd2, 0xb7, - 0xa1, 0x44, 0x6a, 0x32, 0x8f, 0x02, 0x05, 0x2c, 0x43, 0x98, 0x2a, 0x3a, 0xba, 0x61, 0x51, 0x54, 0x96, 0xfe, 0x16, - 0xbe, 0x9e, 0x29, 0xbe, 0x9e, 0x6a, 0xbe, 0x0e, 0x9c, 0x02, 0xf8, 0xba, 0xec, 0xae, 0x6c, 0x9e, 0x6e, 0xec, 0xce, - 0x54, 0x72, 0xf4, 0x4c, 0x58, 0xd2, 0x30, 0xde, 0x5c, 0x43, 0x80, 0x0a, 0xcd, 0xeb, 0xa3, 0xa3, 0xfc, 0x30, 0x60, - 0x02, 0x4a, 0x2f, 0x26, 0x35, 0x9d, 0x14, 0x1f, 0x1d, 0x84, 0xb3, 0x9c, 0x16, 0x94, 0x7d, 0xf6, 0x0c, 0xfc, 0x74, - 0xc6, 0x74, 0x40, 0x88, 0x89, 0xe2, 0xdf, 0xa4, 0x44, 0xe9, 0xd9, 0x31, 0x35, 0xbb, 0x4c, 0xcf, 0x0e, 0x38, 0x7d, - 0x39, 0xbb, 0xe0, 0x7e, 0x5e, 0x2f, 0xa6, 0xc7, 0x8a, 0xe9, 0x95, 0xeb, 0xbd, 0x5a, 0x39, 0x6b, 0x25, 0xe0, 0xc2, - 0x57, 0x26, 0x4a, 0x5a, 0xf4, 0x0e, 0x3c, 0xc0, 0xc4, 0x0c, 0x14, 0xe4, 0x72, 0xd2, 0x85, 0x88, 0x7b, 0xf1, 0x29, - 0x17, 0x8f, 0xf0, 0xd4, 0xcb, 0xf6, 0xe7, 0xc9, 0x74, 0x06, 0xda, 0xd8, 0x1a, 0x49, 0x4f, 0xa8, 0x1a, 0xb0, 0x7c, - 0x9f, 0x6f, 0x29, 0xab, 0xb4, 0x11, 0xfb, 0xb1, 0x42, 0x4d, 0x85, 0xc5, 0xbc, 0xd7, 0xcc, 0xe7, 0x45, 0x51, 0xc1, - 0x38, 0xb6, 0xb9, 0x55, 0xce, 0xd7, 0x9d, 0x32, 0xfa, 0xc5, 0x6b, 0x87, 0x49, 0x3e, 0xcc, 0x80, 0xd7, 0x19, 0xec, - 0x47, 0x93, 0xbb, 0xb9, 0xfe, 0x79, 0x89, 0x9c, 0x65, 0xbe, 0x86, 0xbe, 0x65, 0x9e, 0x3f, 0x53, 0x56, 0x36, 0x7e, - 0xb6, 0xdb, 0x1c, 0x2e, 0xdf, 0x29, 0x6b, 0x71, 0x30, 0xc4, 0xcf, 0x36, 0x75, 0x47, 0xb2, 0x9c, 0x26, 0x01, 0xf5, - 0xec, 0x64, 0x46, 0x63, 0x3b, 0x07, 0xcf, 0xaa, 0x5a, 0xfc, 0x80, 0x3b, 0xcb, 0xb7, 0x55, 0x17, 0xab, 0xf7, 0x2c, - 0x07, 0x07, 0xd8, 0x0f, 0x9b, 0xce, 0xd7, 0xef, 0x69, 0x9a, 0x09, 0x4d, 0xb4, 0x50, 0x6a, 0x7f, 0x28, 0xe5, 0xd2, - 0x0f, 0xde, 0xce, 0xfa, 0xa5, 0x0d, 0x62, 0xb7, 0xdc, 0x13, 0xf7, 0xd0, 0x46, 0xc2, 0x35, 0xfc, 0xad, 0xda, 0xf1, - 0x1f, 0xb4, 0x6b, 0xf8, 0x82, 0x7c, 0xa8, 0x7a, 0x86, 0xe7, 0x9c, 0x5c, 0xf4, 0x2f, 0xb4, 0xc9, 0x9c, 0x44, 0x6c, - 0x74, 0xe7, 0xd8, 0x11, 0xe3, 0x75, 0x08, 0xbf, 0xd9, 0x78, 0x29, 0x5f, 0x80, 0x57, 0x51, 0xb8, 0xb4, 0x73, 0x6d, - 0xec, 0x61, 0xca, 0x89, 0xbd, 0x1f, 0x31, 0xbe, 0x6f, 0xe3, 0x29, 0xb9, 0x82, 0x1f, 0xfb, 0x4b, 0xe7, 0xa5, 0xcf, - 0x43, 0x37, 0xf5, 0xe3, 0x20, 0x99, 0x3a, 0xa8, 0x66, 0xdb, 0xc8, 0xcd, 0x84, 0xc1, 0xf1, 0x18, 0xe5, 0xfb, 0x57, - 0xf8, 0x19, 0x27, 0x76, 0xdf, 0xae, 0x4d, 0xf1, 0x13, 0x4e, 0xae, 0xba, 0xfb, 0xcb, 0x67, 0x3c, 0xef, 0x5d, 0xe1, - 0xdb, 0xc2, 0x6b, 0x8f, 0xdf, 0x13, 0x07, 0x91, 0xde, 0xad, 0x82, 0xe6, 0x3c, 0x99, 0x4a, 0xef, 0xbd, 0x8d, 0xf0, - 0x3b, 0x11, 0x5b, 0x29, 0xd9, 0x8d, 0x0a, 0xaf, 0xec, 0x11, 0x3b, 0x11, 0x3e, 0x02, 0xfb, 0xe0, 0xc0, 0x28, 0x2b, - 0x74, 0x05, 0x7c, 0xc1, 0x49, 0xc5, 0x22, 0xc7, 0x2f, 0x45, 0x94, 0xe6, 0x82, 0x3b, 0x31, 0xd2, 0xdd, 0x38, 0xda, - 0x17, 0xad, 0xf6, 0x66, 0x3c, 0x90, 0x2e, 0x06, 0x97, 0x71, 0x9a, 0xfa, 0x3c, 0x49, 0x87, 0xc8, 0xd4, 0x3f, 0xf0, - 0xdf, 0xc8, 0xd5, 0xc0, 0xfa, 0x4f, 0x9f, 0xfd, 0x3c, 0xfe, 0x39, 0x1d, 0x5e, 0xe1, 0x37, 0xa4, 0xd1, 0x75, 0xfa, - 0x9e, 0xb3, 0x57, 0xaf, 0xaf, 0x7e, 0x6e, 0x0c, 0xfe, 0xe1, 0xd7, 0x7f, 0x3d, 0xab, 0xff, 0x34, 0x44, 0x2b, 0xe7, - 0xe7, 0x46, 0x7f, 0xa0, 0x9e, 0x06, 0xff, 0xe8, 0xfd, 0x9c, 0x0d, 0xff, 0x2a, 0x0b, 0xf7, 0x11, 0x6a, 0x4c, 0xf0, - 0x8c, 0x93, 0x46, 0xbd, 0xde, 0x6b, 0x4c, 0xf0, 0x84, 0x93, 0x06, 0xfc, 0x7f, 0x4d, 0xde, 0xd2, 0xc9, 0xb3, 0xdb, - 0x99, 0x73, 0xd5, 0x5b, 0xed, 0x2f, 0xff, 0x96, 0x43, 0xaf, 0x83, 0x7f, 0xfc, 0xfc, 0x73, 0x66, 0x7f, 0xd1, 0x23, - 0x8d, 0x61, 0x0d, 0x39, 0x50, 0xfa, 0x57, 0x22, 0xfe, 0x75, 0xfa, 0xde, 0xe0, 0x1f, 0x0a, 0x0a, 0xfb, 0x8b, 0x9f, - 0xaf, 0xba, 0x3d, 0x32, 0x5c, 0x39, 0xf6, 0xea, 0x0b, 0xb4, 0x42, 0x68, 0xb5, 0x8f, 0xae, 0xb0, 0x3d, 0xb1, 0x11, - 0x5e, 0x70, 0xd2, 0xf8, 0xa2, 0x31, 0xc1, 0x63, 0x4e, 0x1a, 0x76, 0x63, 0x82, 0xcf, 0x39, 0x69, 0xfc, 0xc3, 0xe9, - 0x7b, 0xd2, 0xc9, 0xb6, 0x12, 0xfe, 0x8d, 0x15, 0x04, 0x38, 0xfc, 0x94, 0xfa, 0x2b, 0xce, 0x78, 0x44, 0xd1, 0x7e, - 0x83, 0xe1, 0x8f, 0x02, 0x4d, 0x0e, 0x07, 0x2f, 0x0c, 0x18, 0x77, 0xce, 0xf2, 0x12, 0x16, 0x1b, 0x68, 0x66, 0xdf, - 0x83, 0xc8, 0x0e, 0x38, 0x02, 0x32, 0x8f, 0xe3, 0x85, 0x1f, 0xcd, 0x69, 0xe6, 0xd1, 0x1c, 0xe1, 0x11, 0xf9, 0xc8, - 0x9d, 0x16, 0xc2, 0x2f, 0x38, 0xfc, 0x68, 0x23, 0x7c, 0xae, 0x82, 0x98, 0xb0, 0x93, 0x25, 0x51, 0xc5, 0x89, 0x54, - 0x59, 0x6c, 0x84, 0x67, 0x5b, 0x5e, 0xf2, 0x10, 0xdc, 0x0b, 0x08, 0xef, 0x57, 0x42, 0x9e, 0xf8, 0x86, 0x68, 0x92, - 0x78, 0x97, 0x52, 0xfa, 0x83, 0x1f, 0x7d, 0xa4, 0xa9, 0x73, 0x8b, 0x5b, 0xed, 0xc7, 0x58, 0x78, 0xa1, 0xf7, 0x5a, - 0xa8, 0x53, 0xc4, 0xab, 0x5e, 0x73, 0x19, 0x27, 0x00, 0x29, 0x5b, 0x75, 0xc6, 0xc0, 0x8a, 0xef, 0xc5, 0x1b, 0x1e, - 0xab, 0xd4, 0xbf, 0xb1, 0x51, 0x35, 0x36, 0xca, 0xe2, 0x85, 0x1f, 0xb1, 0xc0, 0xe2, 0x74, 0x3a, 0x8b, 0x7c, 0x4e, - 0x2d, 0x35, 0x5f, 0xcb, 0x87, 0x8e, 0xec, 0x42, 0x67, 0x98, 0x1b, 0x16, 0xe7, 0x5c, 0x07, 0x9d, 0x60, 0xaf, 0x38, - 0x10, 0xa1, 0x52, 0x7a, 0xc7, 0xd3, 0x32, 0x00, 0xb6, 0x1e, 0xe3, 0xab, 0xb7, 0xc0, 0x13, 0x36, 0x14, 0xf2, 0x39, - 0xc3, 0x29, 0x01, 0x29, 0xda, 0xee, 0xdb, 0xdd, 0x6c, 0x31, 0xe9, 0xd9, 0x10, 0x9f, 0x49, 0xc8, 0x1b, 0xe1, 0x18, - 0x82, 0x0a, 0x21, 0x69, 0x76, 0xc2, 0x2e, 0xed, 0x84, 0xb5, 0x9a, 0x56, 0xa2, 0x23, 0x12, 0x0f, 0x42, 0xd9, 0xdc, - 0xc7, 0x01, 0x9e, 0x93, 0x7a, 0x0b, 0x4f, 0x48, 0x53, 0x34, 0xe9, 0x4c, 0xba, 0x91, 0x1a, 0xe6, 0xe0, 0xc0, 0x49, - 0xdc, 0xc8, 0xcf, 0xf8, 0x37, 0x60, 0xed, 0x93, 0x09, 0x0e, 0x48, 0xe2, 0xd2, 0x5b, 0x3a, 0x72, 0x22, 0x84, 0x03, - 0xc5, 0x69, 0x50, 0x07, 0x4d, 0x88, 0x51, 0x0d, 0xac, 0x08, 0xf2, 0xa6, 0x1f, 0x0c, 0x5a, 0x43, 0x42, 0x88, 0xbd, - 0x57, 0xaf, 0xdb, 0xfd, 0x84, 0xcc, 0xb8, 0x07, 0x25, 0x86, 0xae, 0x4c, 0x26, 0x50, 0xd4, 0x36, 0x8a, 0x9c, 0x73, - 0xee, 0x72, 0x9a, 0x71, 0x07, 0x8a, 0xc1, 0xfe, 0xcf, 0x34, 0x61, 0xdb, 0xdd, 0x86, 0x5d, 0x83, 0x52, 0x41, 0x9c, - 0x08, 0x27, 0xe4, 0x1a, 0x79, 0xc1, 0xe0, 0x70, 0x68, 0x0a, 0x00, 0x51, 0x08, 0x83, 0x5f, 0xf7, 0x83, 0x41, 0x53, - 0x0c, 0xde, 0xb3, 0xfb, 0x4e, 0x42, 0x32, 0xa9, 0xa1, 0xf5, 0x33, 0xef, 0x8d, 0x98, 0x2a, 0xf2, 0x14, 0x70, 0x7a, - 0x05, 0x48, 0xbd, 0xed, 0x39, 0x73, 0x73, 0x12, 0x75, 0x18, 0x4c, 0x61, 0x01, 0xfb, 0x04, 0xea, 0xe3, 0x84, 0xc0, - 0x88, 0x65, 0xb3, 0x6b, 0x4f, 0x3d, 0x7f, 0x61, 0x7f, 0xd1, 0x1f, 0x73, 0x6f, 0xc1, 0xe5, 0xf0, 0x63, 0xbe, 0x5a, - 0xc1, 0xff, 0x0b, 0xde, 0x4f, 0xc8, 0xb5, 0x28, 0x9a, 0xa9, 0xa2, 0x09, 0x14, 0xbd, 0xf1, 0x00, 0x54, 0x9c, 0x15, - 0x5a, 0x96, 0x5c, 0x93, 0x05, 0x11, 0xb0, 0x1f, 0x1c, 0xc4, 0x83, 0xb0, 0xd6, 0x1a, 0x82, 0x8b, 0x3f, 0xe5, 0xd9, - 0x0f, 0x8c, 0x87, 0x8e, 0xdd, 0xe8, 0xd9, 0xa8, 0x6f, 0x5b, 0xb0, 0xb4, 0x9d, 0xb4, 0x46, 0x24, 0x86, 0xa3, 0xda, - 0x13, 0xee, 0xcd, 0x7b, 0xa4, 0xd9, 0x77, 0x98, 0x64, 0xe1, 0x3e, 0xc2, 0x91, 0x62, 0x9c, 0x4d, 0x3c, 0x47, 0x35, - 0xca, 0x6b, 0xfa, 0x79, 0x8e, 0x6a, 0xd3, 0xda, 0x02, 0x79, 0x51, 0x6d, 0x5a, 0x73, 0xe6, 0x84, 0x90, 0x7a, 0xbb, - 0x68, 0xa6, 0xc5, 0x5f, 0x88, 0xbc, 0x85, 0xf6, 0x76, 0x0e, 0xc4, 0x76, 0x48, 0x6b, 0x4e, 0x3c, 0xa0, 0xc3, 0xd5, - 0xca, 0xee, 0xf6, 0x7b, 0x36, 0xaa, 0x39, 0x9a, 0xd0, 0x1a, 0x9a, 0xd2, 0x10, 0xc2, 0x6c, 0x98, 0xab, 0x68, 0xd2, - 0xab, 0x4a, 0xe4, 0x68, 0x59, 0x6e, 0x76, 0x83, 0x07, 0xd0, 0xbc, 0x30, 0x64, 0xa4, 0xc2, 0x3a, 0x83, 0x69, 0x6a, - 0x62, 0x4e, 0x49, 0x13, 0x27, 0x44, 0x3b, 0xaf, 0x43, 0xc2, 0x4b, 0x82, 0x8f, 0x48, 0x59, 0x1d, 0x0f, 0x7c, 0x1c, - 0x0c, 0xc9, 0x53, 0x69, 0x90, 0x74, 0xb4, 0x6b, 0x9c, 0x46, 0xe4, 0xd5, 0x5a, 0x04, 0xd7, 0x87, 0xf0, 0xca, 0x8d, - 0x3b, 0x9a, 0xa7, 0x29, 0x8d, 0xf9, 0xab, 0x24, 0x50, 0x7a, 0x1a, 0x8d, 0xc0, 0x54, 0x82, 0xd0, 0x2c, 0x06, 0x25, - 0xad, 0xad, 0x77, 0xc6, 0x7c, 0xe3, 0xf5, 0x84, 0xcc, 0xa5, 0xfe, 0x24, 0x02, 0xb6, 0x9d, 0x89, 0x32, 0x8c, 0x1d, - 0x84, 0xe7, 0x2a, 0x92, 0xeb, 0xb8, 0xae, 0x3b, 0x71, 0x47, 0xf0, 0x1a, 0x06, 0xc8, 0x50, 0x2e, 0xf6, 0x91, 0x93, - 0x91, 0x1b, 0x37, 0xa6, 0xb7, 0x62, 0x54, 0x07, 0x95, 0x92, 0x59, 0x6f, 0xaf, 0x6e, 0xd8, 0x11, 0xec, 0x26, 0x73, - 0xe3, 0x24, 0xa0, 0x80, 0x1e, 0x88, 0xdd, 0xab, 0xa2, 0xd0, 0xcf, 0xcc, 0x10, 0x55, 0x09, 0xdf, 0xc0, 0xf4, 0x5e, - 0x4f, 0xc0, 0xe5, 0x2b, 0x94, 0xad, 0xa2, 0xb2, 0xf4, 0x83, 0x23, 0xc4, 0xc6, 0xce, 0xc4, 0x85, 0xd0, 0x9e, 0x20, - 0x21, 0x0a, 0xb6, 0xdc, 0xc4, 0x24, 0xaa, 0x69, 0xd1, 0xe7, 0x82, 0x04, 0x83, 0xa4, 0x56, 0x13, 0x6e, 0xe8, 0xb9, - 0x24, 0x89, 0x09, 0xc2, 0x8b, 0x62, 0x6f, 0xe9, 0x7a, 0x5f, 0x91, 0xea, 0x48, 0xce, 0xa2, 0xea, 0xce, 0xad, 0x41, - 0x9a, 0x04, 0x78, 0x0a, 0xb9, 0x33, 0x45, 0xf8, 0x8c, 0x34, 0x9c, 0x81, 0xdb, 0xff, 0x72, 0x88, 0xfa, 0x8e, 0xfb, - 0x57, 0xd4, 0x90, 0x8c, 0x63, 0x81, 0x3a, 0x91, 0x1c, 0x62, 0x29, 0x42, 0x98, 0x2d, 0x2c, 0x3c, 0x89, 0x5e, 0x8a, - 0x63, 0x7f, 0x4a, 0xbd, 0x33, 0xd8, 0xe3, 0x9a, 0x6e, 0xbe, 0xc2, 0x40, 0x47, 0xde, 0x99, 0xe2, 0x24, 0xae, 0xdd, - 0xff, 0x86, 0x17, 0x4f, 0x7d, 0xbb, 0xff, 0x6b, 0xf9, 0xf4, 0xa5, 0xdd, 0xff, 0x9e, 0x7b, 0xbf, 0xe6, 0xca, 0xd9, - 0x5d, 0x19, 0xe2, 0x44, 0x0f, 0x91, 0xcb, 0x85, 0x31, 0x30, 0x37, 0x47, 0x9b, 0x7e, 0x8e, 0x09, 0xca, 0xd9, 0xb8, - 0x60, 0x45, 0x99, 0xcb, 0xfd, 0x09, 0xa0, 0xd4, 0x58, 0x81, 0xcc, 0x8c, 0xec, 0x97, 0x13, 0x06, 0x42, 0xd1, 0xd4, - 0x0a, 0xa8, 0x9c, 0xf4, 0x9a, 0x68, 0x59, 0xa9, 0x2b, 0x34, 0xa6, 0x6a, 0x24, 0xbd, 0xe0, 0xd2, 0x0b, 0xd2, 0xec, - 0x2c, 0xba, 0x93, 0xce, 0xa2, 0x56, 0x43, 0x99, 0x26, 0xac, 0xf9, 0x60, 0x31, 0xc4, 0xef, 0xc1, 0xa7, 0x67, 0x52, - 0x12, 0xae, 0x4c, 0xaf, 0xad, 0xa6, 0x57, 0xab, 0xa5, 0x39, 0xea, 0x18, 0x4d, 0x27, 0xb2, 0x69, 0x9e, 0x4b, 0x9c, - 0xac, 0x13, 0xda, 0x29, 0x12, 0x25, 0x90, 0x0e, 0x45, 0x08, 0x79, 0xc6, 0xd1, 0xd6, 0x5e, 0xa1, 0x4f, 0x68, 0x2e, - 0x76, 0x2c, 0x30, 0x4f, 0x29, 0x23, 0x1c, 0xc0, 0x02, 0x34, 0x2d, 0x1c, 0xc1, 0x53, 0x3c, 0xaf, 0xb5, 0x04, 0x91, - 0xd7, 0x5b, 0x9d, 0x6a, 0x5f, 0x8f, 0xca, 0xbe, 0xf0, 0xbc, 0x46, 0xa6, 0x05, 0x96, 0xf2, 0xb4, 0x56, 0xcb, 0xab, - 0xd1, 0x4e, 0xbd, 0x6f, 0x2b, 0xf1, 0x87, 0xdb, 0xf5, 0xb4, 0x0c, 0x2d, 0x5f, 0x4b, 0x89, 0xca, 0x5c, 0x16, 0xc7, - 0x34, 0x05, 0x19, 0x4a, 0x38, 0x66, 0x79, 0x5e, 0xc8, 0xf5, 0x8f, 0x20, 0x44, 0x31, 0x25, 0x31, 0xf0, 0x1d, 0x61, - 0x76, 0xe1, 0x14, 0x27, 0x38, 0x14, 0x5c, 0x83, 0x10, 0x72, 0xae, 0x13, 0x5a, 0xb8, 0xe0, 0x40, 0x11, 0x61, 0x86, - 0x44, 0xca, 0x08, 0x75, 0x2f, 0xf7, 0xcf, 0x93, 0x7b, 0x4d, 0xb2, 0x01, 0x1b, 0x7a, 0xa2, 0x5a, 0xa4, 0xf8, 0x96, - 0x4f, 0xde, 0x39, 0x1c, 0x15, 0xc1, 0x11, 0x57, 0xb0, 0xbf, 0xa7, 0x2c, 0xa5, 0x42, 0x03, 0xdf, 0xd7, 0x66, 0x5f, - 0x54, 0x55, 0x1f, 0x23, 0xd3, 0x79, 0x03, 0x88, 0xf4, 0xc1, 0xb7, 0x93, 0x92, 0x8d, 0x6a, 0x97, 0xfb, 0x67, 0xaf, - 0xb7, 0x99, 0xc0, 0xab, 0x95, 0x32, 0x7e, 0x85, 0x66, 0x83, 0xfd, 0x12, 0xd2, 0x48, 0xfd, 0xf0, 0x9c, 0x48, 0x28, - 0x48, 0xbe, 0x13, 0x03, 0x15, 0x5d, 0xee, 0x9f, 0xbd, 0x73, 0x62, 0xe1, 0x5a, 0x42, 0xd8, 0x9c, 0xb6, 0x93, 0x10, - 0x27, 0x24, 0x14, 0xc9, 0xb9, 0x17, 0x8c, 0x2b, 0x31, 0xc4, 0xb7, 0x17, 0x8a, 0x97, 0x60, 0x3f, 0x0c, 0xd8, 0x90, - 0x44, 0x0a, 0x03, 0x24, 0x42, 0x38, 0xaa, 0x98, 0x65, 0x04, 0x16, 0x40, 0x8c, 0x75, 0x01, 0x2b, 0xe1, 0x4a, 0xc5, - 0x0f, 0xe1, 0x48, 0x8c, 0xca, 0x73, 0x29, 0x3a, 0x3e, 0x6c, 0xe4, 0xa5, 0x95, 0xd6, 0xe8, 0xf7, 0x60, 0x39, 0xe9, - 0x87, 0x57, 0xaa, 0xeb, 0xa2, 0xe0, 0xa9, 0x4e, 0x20, 0xbb, 0xdc, 0x3f, 0x7b, 0xa9, 0x72, 0xc8, 0x66, 0xbe, 0xe6, - 0xf6, 0x1b, 0x16, 0xe6, 0xd9, 0x4b, 0xb7, 0x7c, 0x2b, 0x2a, 0x5f, 0xee, 0x9f, 0xbd, 0xdf, 0x56, 0x0d, 0xca, 0xf3, - 0x79, 0x69, 0xe2, 0x0b, 0xf8, 0x96, 0x34, 0xf2, 0x96, 0x4a, 0x34, 0x78, 0x2c, 0xc7, 0x42, 0x1c, 0x79, 0x59, 0x5e, - 0x78, 0x46, 0x9e, 0xe2, 0x94, 0x88, 0x28, 0x50, 0x75, 0xd5, 0x94, 0x92, 0xc7, 0x92, 0xf8, 0x62, 0x94, 0xcc, 0xe8, - 0x8e, 0xd0, 0xd0, 0x2d, 0x72, 0xd9, 0x14, 0x92, 0x67, 0x04, 0xe8, 0x0c, 0xef, 0x35, 0x51, 0xa7, 0x2a, 0xbc, 0x52, - 0x41, 0xa4, 0x49, 0x45, 0xb2, 0xe0, 0x90, 0x34, 0x71, 0x44, 0x9a, 0xd8, 0x27, 0xd9, 0xa0, 0x29, 0xc5, 0x43, 0xc7, - 0x2f, 0xfa, 0x95, 0x42, 0x06, 0xf2, 0xc2, 0xd4, 0x6e, 0x95, 0xe2, 0x37, 0xe8, 0xf8, 0xc2, 0xf5, 0x28, 0x24, 0x7a, - 0x20, 0xc8, 0xe2, 0xb9, 0x93, 0xe0, 0x44, 0x74, 0x7c, 0xc1, 0xae, 0x23, 0x48, 0x2d, 0x81, 0x59, 0x61, 0x8e, 0xbc, - 0xa2, 0x6a, 0x4b, 0x55, 0xf5, 0x5d, 0xb1, 0x4e, 0x09, 0xf6, 0x5d, 0x60, 0xdc, 0xd8, 0x57, 0x99, 0x38, 0xd9, 0x66, - 0x93, 0x93, 0x83, 0x03, 0x47, 0x36, 0xfa, 0x8a, 0x3b, 0x89, 0x7e, 0x5f, 0x06, 0xee, 0xbe, 0x97, 0xbc, 0x22, 0x40, - 0x02, 0xfe, 0x5a, 0x2d, 0x1a, 0xe6, 0x10, 0x85, 0x76, 0xfc, 0x2a, 0x06, 0x35, 0xf0, 0x42, 0xd3, 0xab, 0x4e, 0xbf, - 0x56, 0x2b, 0x82, 0xb4, 0x55, 0x6c, 0xdd, 0xe2, 0x34, 0x5f, 0x38, 0x45, 0xf2, 0x4f, 0x73, 0x23, 0x63, 0x4a, 0x83, - 0x80, 0x98, 0x49, 0xb3, 0x4c, 0x4f, 0xc6, 0xd8, 0x12, 0x0c, 0xea, 0x7d, 0xa3, 0xd2, 0x16, 0xb0, 0xc8, 0xaf, 0x52, - 0x95, 0x34, 0x3b, 0x6b, 0x23, 0x4f, 0x57, 0x82, 0xa0, 0x14, 0x54, 0xaa, 0xe5, 0x8a, 0xbc, 0x9f, 0x6f, 0x66, 0x5d, - 0xe2, 0x0c, 0x29, 0x1f, 0x97, 0x80, 0x42, 0x20, 0xab, 0x5d, 0x20, 0xe5, 0x39, 0x99, 0xed, 0x26, 0xf9, 0x33, 0x83, - 0xe4, 0x9f, 0x10, 0x6a, 0x90, 0xbf, 0xf4, 0x70, 0xb8, 0x89, 0x72, 0x2d, 0x64, 0xfa, 0xd5, 0xf9, 0x8c, 0x80, 0x0f, - 0xad, 0x8a, 0xd1, 0x4a, 0x54, 0x71, 0x07, 0x43, 0x31, 0x77, 0x88, 0xf0, 0x42, 0x62, 0x1d, 0x02, 0x76, 0xca, 0x98, - 0x1a, 0x0c, 0xbd, 0xcd, 0xa5, 0x67, 0x72, 0xc0, 0xb3, 0xf7, 0xf7, 0x87, 0x43, 0xcf, 0x67, 0x9b, 0x3b, 0xd7, 0xc8, - 0xfe, 0x84, 0x59, 0x1b, 0x1b, 0xb7, 0x9a, 0x0b, 0x0a, 0xe3, 0x17, 0x61, 0xec, 0x2a, 0xf3, 0x59, 0xdb, 0x84, 0x5a, - 0xfe, 0x01, 0xb4, 0xad, 0x96, 0xa8, 0x41, 0x8d, 0x6e, 0x81, 0x1f, 0xc9, 0x1c, 0x54, 0x3f, 0xdd, 0xc1, 0x3e, 0xce, - 0x44, 0x05, 0x1a, 0x07, 0xdb, 0x5f, 0x3f, 0xc9, 0x15, 0x99, 0x48, 0xd0, 0xd0, 0x12, 0xf8, 0x9f, 0x24, 0x79, 0xa0, - 0x1b, 0x21, 0x17, 0x00, 0x41, 0x33, 0x81, 0xa7, 0x12, 0x61, 0xb6, 0x5d, 0x3a, 0xdf, 0x9f, 0xef, 0x11, 0x32, 0x2b, - 0x9d, 0x8f, 0x6f, 0xcb, 0xdc, 0x2b, 0x20, 0x0b, 0xe4, 0x81, 0xf1, 0x58, 0x14, 0xc8, 0xe8, 0xe5, 0xb9, 0xae, 0x2e, - 0x0c, 0x48, 0xb7, 0xd4, 0xb7, 0x8d, 0xc8, 0xa6, 0xf0, 0xca, 0xc9, 0xf7, 0x1a, 0x0d, 0x6b, 0x6f, 0xf7, 0xe1, 0xed, - 0x4b, 0x2e, 0x60, 0x84, 0xe7, 0x77, 0xa2, 0xb6, 0xee, 0x37, 0xff, 0xb8, 0x9e, 0xc0, 0xb2, 0xb6, 0x28, 0x2e, 0x8b, - 0x33, 0x9a, 0xf2, 0x27, 0x74, 0x9c, 0xa4, 0x10, 0xb2, 0x28, 0x70, 0x82, 0xf2, 0x7d, 0xc3, 0x6d, 0x27, 0xe6, 0x67, - 0xc4, 0x09, 0xd6, 0x26, 0x28, 0x7e, 0x7d, 0x14, 0x31, 0xeb, 0xcb, 0xf5, 0x56, 0xb3, 0x83, 0x83, 0x77, 0x25, 0x9a, - 0x14, 0x94, 0x02, 0x0a, 0x83, 0x69, 0x49, 0x95, 0x46, 0x05, 0x72, 0xf7, 0x9d, 0xc2, 0x05, 0xa0, 0x19, 0x86, 0xc9, - 0x7b, 0x9e, 0x13, 0x9e, 0x4f, 0xd6, 0x59, 0xbc, 0x72, 0x4d, 0x30, 0xd3, 0x6c, 0x01, 0x0e, 0x0f, 0x86, 0xb6, 0xf4, - 0x15, 0x65, 0x65, 0x3a, 0x6c, 0x01, 0xc3, 0x39, 0x20, 0xcb, 0x11, 0x46, 0x88, 0x41, 0x81, 0x5b, 0x8d, 0x92, 0xd7, - 0xa0, 0x57, 0x86, 0x38, 0x73, 0x43, 0x48, 0x80, 0xad, 0x6c, 0x59, 0x84, 0xb0, 0xcc, 0xcb, 0x31, 0x32, 0x09, 0xce, - 0x9e, 0x6f, 0xf3, 0x28, 0x6b, 0xa2, 0xa6, 0x42, 0xea, 0x40, 0x8d, 0x14, 0x15, 0x0d, 0xdc, 0x85, 0xc3, 0x94, 0xe2, - 0xa6, 0xc3, 0x66, 0xc0, 0x80, 0x3f, 0x70, 0x47, 0xc6, 0xa2, 0x40, 0x66, 0x24, 0xee, 0xdc, 0xa9, 0x0c, 0xdd, 0x49, - 0x44, 0x33, 0xac, 0x10, 0x17, 0x9a, 0x68, 0x4a, 0x44, 0x58, 0xef, 0xbc, 0xe4, 0xa5, 0xfb, 0x32, 0x87, 0x9a, 0x6b, - 0x2e, 0x58, 0xe6, 0x91, 0x18, 0xd3, 0xdf, 0x97, 0x69, 0xd1, 0x45, 0x25, 0x50, 0xc3, 0xe8, 0x8d, 0xf5, 0x4a, 0xac, - 0x01, 0xcd, 0x81, 0xbe, 0x96, 0x17, 0xdc, 0x58, 0x51, 0xed, 0xc3, 0x16, 0x63, 0x1a, 0x52, 0xff, 0x2d, 0x64, 0xba, - 0xac, 0xef, 0xf9, 0xe7, 0x42, 0x16, 0x32, 0x9c, 0x55, 0x18, 0x7b, 0x2a, 0x18, 0x3b, 0x02, 0x3d, 0x4d, 0xa7, 0x7e, - 0xf7, 0x55, 0xc2, 0x0b, 0x53, 0x52, 0x4e, 0x91, 0xd8, 0xfb, 0x22, 0x58, 0x6e, 0xfc, 0x5e, 0x5b, 0x0d, 0x8f, 0x11, - 0x48, 0x02, 0xc2, 0x8a, 0xb3, 0xa7, 0x08, 0x67, 0xb5, 0x5a, 0x27, 0xeb, 0xd2, 0xd2, 0x45, 0x52, 0xc2, 0xc8, 0x20, - 0x9e, 0x0b, 0x04, 0x5f, 0x91, 0xa1, 0x10, 0xf1, 0xd7, 0xb9, 0xd9, 0x19, 0xb8, 0xda, 0xcf, 0xde, 0x3a, 0x26, 0x57, - 0x33, 0xeb, 0x16, 0x31, 0x53, 0x98, 0x8f, 0x53, 0xc6, 0x5b, 0xde, 0xdc, 0x9f, 0xdf, 0x01, 0x70, 0xef, 0xb5, 0x30, - 0xe4, 0xa2, 0xa1, 0x0e, 0x97, 0x2c, 0xa1, 0xd8, 0x7d, 0x1d, 0x54, 0xa6, 0x25, 0x9a, 0x83, 0x75, 0x78, 0x69, 0xca, - 0x72, 0x92, 0xe5, 0x79, 0x46, 0xcb, 0xe8, 0xfe, 0x5a, 0xfe, 0xa5, 0x10, 0x2e, 0x9b, 0xce, 0xf6, 0xf3, 0x19, 0xe1, - 0xd8, 0x20, 0xd4, 0x37, 0xbb, 0x42, 0x1f, 0x25, 0x98, 0xb0, 0xaf, 0x95, 0x50, 0xfc, 0x75, 0x9b, 0x50, 0xc4, 0xa9, - 0xda, 0xf2, 0x42, 0x20, 0xb6, 0x1e, 0x20, 0x10, 0x95, 0x93, 0x5d, 0xcb, 0x44, 0x50, 0x47, 0x2a, 0x32, 0xb1, 0xba, - 0xa4, 0x24, 0xc5, 0x4c, 0xad, 0x46, 0xaf, 0xbd, 0x5a, 0xb1, 0x41, 0x13, 0x9c, 0x48, 0xb6, 0x0d, 0x3f, 0x5b, 0xf2, - 0xa7, 0xc1, 0x89, 0xa5, 0x13, 0xd8, 0x61, 0x85, 0xc9, 0x82, 0x5c, 0x48, 0x71, 0x76, 0x44, 0x4e, 0x96, 0xa0, 0x69, - 0x45, 0x41, 0x8a, 0xc0, 0x09, 0x2b, 0xa2, 0x4c, 0x00, 0xb1, 0x90, 0x15, 0xca, 0x80, 0x74, 0xb6, 0x26, 0xff, 0x69, - 0xf3, 0xf2, 0xd3, 0x9a, 0x68, 0x45, 0xae, 0x48, 0xf5, 0xa1, 0x92, 0x6e, 0xa0, 0x20, 0x50, 0xfa, 0xe1, 0x9e, 0x30, - 0x41, 0x4b, 0x51, 0x8e, 0x4c, 0x39, 0x84, 0x9b, 0xe0, 0x42, 0xdb, 0x7b, 0x27, 0x03, 0xbc, 0x5b, 0xa4, 0x09, 0x4e, - 0x0c, 0xba, 0x7e, 0x4e, 0x78, 0x85, 0x95, 0x84, 0x44, 0x59, 0x4a, 0xd8, 0x17, 0x64, 0xca, 0x49, 0x3a, 0x68, 0x0e, - 0x41, 0x01, 0xed, 0x44, 0xdd, 0xb4, 0x34, 0x81, 0xa3, 0x5a, 0x0d, 0xf9, 0x7a, 0xd4, 0x70, 0xc0, 0x6a, 0xd1, 0x10, - 0x53, 0x1c, 0x49, 0xc3, 0xe4, 0xfc, 0xe0, 0xc0, 0xf1, 0xcb, 0x71, 0x07, 0xd1, 0x10, 0xe1, 0x64, 0xb5, 0x72, 0x04, - 0x58, 0x3e, 0x5a, 0xad, 0x7c, 0x13, 0x2c, 0xf1, 0x1a, 0x9a, 0xcd, 0xfa, 0x9c, 0xcc, 0x84, 0x00, 0x9c, 0x01, 0x84, - 0x35, 0xe2, 0xf8, 0xca, 0xb9, 0xe7, 0x83, 0x33, 0xaa, 0x96, 0x0e, 0xa2, 0x5a, 0x6b, 0x68, 0x30, 0xae, 0x41, 0x34, - 0x24, 0x7e, 0x9e, 0x1c, 0x1c, 0xec, 0x65, 0x4a, 0x44, 0x7e, 0x00, 0x51, 0xf6, 0x41, 0x48, 0x16, 0xd9, 0xa1, 0xb9, - 0x1a, 0xeb, 0xce, 0x80, 0x82, 0xa2, 0xd4, 0xb2, 0xea, 0x7a, 0x95, 0x24, 0x88, 0xa2, 0x12, 0x56, 0xb1, 0xe0, 0x3e, - 0x58, 0xf6, 0x05, 0x99, 0x7f, 0xc3, 0x8b, 0x24, 0xeb, 0x5f, 0xb7, 0xa6, 0x56, 0xbb, 0xae, 0xeb, 0xa7, 0x13, 0x11, - 0xc9, 0xd0, 0x51, 0x58, 0x41, 0xfc, 0x87, 0x0a, 0x4c, 0x63, 0xe0, 0x41, 0x31, 0xd6, 0x90, 0x48, 0xf0, 0xb5, 0x6a, - 0xa3, 0x4f, 0x93, 0xfc, 0xb2, 0xd5, 0xcb, 0xa0, 0x36, 0xdc, 0xef, 0x85, 0xe4, 0x48, 0x41, 0x22, 0xc9, 0x63, 0x0d, - 0x67, 0x3b, 0x70, 0xf1, 0x0b, 0x5f, 0xc3, 0xd9, 0x6e, 0xdc, 0x6a, 0x4c, 0x7d, 0xbf, 0x0b, 0x3e, 0x83, 0x37, 0x48, - 0x40, 0xcb, 0x02, 0x03, 0xca, 0xe3, 0x75, 0xdd, 0x4b, 0xb2, 0x52, 0x10, 0xa6, 0x9c, 0x38, 0xac, 0xba, 0x01, 0x4a, - 0x6d, 0xd4, 0x30, 0x7c, 0x99, 0x37, 0x43, 0x86, 0x4b, 0xa0, 0x9a, 0xb9, 0x02, 0xe4, 0xa4, 0x7c, 0xed, 0xb3, 0x83, - 0x03, 0xb0, 0x0d, 0x40, 0x89, 0x73, 0x47, 0xfe, 0x8c, 0xcf, 0x53, 0x50, 0xa5, 0x32, 0xfd, 0x1b, 0x8a, 0xe1, 0x1c, - 0x88, 0x28, 0x83, 0x1f, 0x50, 0x30, 0xf3, 0xb3, 0x8c, 0x2d, 0x64, 0x99, 0xfa, 0x8d, 0x13, 0xa2, 0x49, 0x39, 0x93, - 0x3a, 0x61, 0x8a, 0x3a, 0xa9, 0xa2, 0xd3, 0x2a, 0xda, 0x9e, 0x2d, 0x68, 0xcc, 0x5f, 0xb0, 0x8c, 0xd3, 0x18, 0xa6, - 0x5f, 0x52, 0x1c, 0xcc, 0x28, 0x43, 0xb0, 0x61, 0x2b, 0xad, 0xfc, 0x20, 0xb8, 0xb7, 0x09, 0xaf, 0xea, 0x40, 0xa1, - 0x1f, 0x07, 0x91, 0x1c, 0xc4, 0x4c, 0x67, 0xd4, 0x29, 0x9c, 0x45, 0x4d, 0x33, 0x9d, 0xa6, 0x54, 0x36, 0x04, 0x77, - 0x77, 0x18, 0xd1, 0x92, 0x40, 0x4b, 0xcf, 0x7b, 0xb5, 0x16, 0x08, 0x78, 0xef, 0x58, 0x04, 0x73, 0x26, 0x98, 0x1b, - 0x1c, 0xd5, 0xad, 0xc2, 0xa9, 0xe9, 0xe6, 0xab, 0xad, 0x87, 0xda, 0xb6, 0x09, 0x07, 0x41, 0x27, 0x27, 0xbb, 0x2d, - 0xab, 0x97, 0x5a, 0x72, 0x68, 0x69, 0xc1, 0x1e, 0xca, 0x98, 0xd1, 0x52, 0x93, 0x17, 0xd2, 0x5b, 0xf1, 0x92, 0x93, - 0x0f, 0x70, 0x6a, 0xe8, 0x39, 0x9f, 0x46, 0x6b, 0x87, 0x63, 0x3a, 0x97, 0x85, 0xf6, 0x7f, 0xc9, 0x9d, 0x57, 0xf8, - 0x39, 0x84, 0x75, 0xbf, 0x2d, 0xab, 0x6f, 0x86, 0x73, 0xbf, 0x2d, 0x11, 0xf4, 0xad, 0xb7, 0x51, 0xcf, 0x08, 0xe3, - 0xb6, 0xdd, 0x53, 0xb7, 0x69, 0x6b, 0x6d, 0xe9, 0x07, 0x19, 0x44, 0x92, 0x89, 0x96, 0x62, 0x3f, 0xe0, 0x32, 0x4d, - 0x0d, 0xd2, 0xe5, 0xaa, 0x16, 0x12, 0x55, 0x09, 0x86, 0x52, 0x87, 0xdf, 0xb5, 0x3c, 0x4a, 0xc6, 0xa4, 0xd2, 0xce, - 0x78, 0xe3, 0xa7, 0x7c, 0x1f, 0x76, 0x59, 0xb2, 0x71, 0x12, 0x2f, 0x24, 0xe0, 0x41, 0x7b, 0xd8, 0x10, 0x86, 0xb1, - 0x9d, 0xc9, 0x93, 0x40, 0x66, 0xff, 0x24, 0xd1, 0xba, 0x5b, 0xd5, 0xca, 0x78, 0x0f, 0xf6, 0x3f, 0xc2, 0xa1, 0x3e, - 0x1e, 0x47, 0x15, 0x07, 0xa6, 0xde, 0x32, 0x2f, 0x9c, 0x02, 0x89, 0x54, 0xde, 0x62, 0x84, 0x93, 0x5c, 0x84, 0xb7, - 0xbf, 0xc3, 0x3f, 0x2a, 0x96, 0x38, 0x2e, 0x38, 0xce, 0xb3, 0x87, 0x72, 0x44, 0x09, 0x7e, 0x11, 0xbd, 0x07, 0x3a, - 0x16, 0x14, 0x9a, 0x6b, 0x2a, 0x7a, 0x9a, 0xa8, 0x89, 0xec, 0xcc, 0x4a, 0xc5, 0xb4, 0xc8, 0xa8, 0x11, 0xc3, 0x6c, - 0x49, 0xe3, 0xd4, 0x56, 0x36, 0x2f, 0x76, 0x55, 0x65, 0x5c, 0xb4, 0x03, 0x8b, 0x65, 0x60, 0x71, 0xb5, 0x72, 0xaa, - 0xa8, 0x26, 0xcc, 0x88, 0x63, 0x20, 0xcc, 0x8c, 0x84, 0x8a, 0x8a, 0x66, 0x2d, 0xdb, 0x38, 0x68, 0x3d, 0x9f, 0x48, - 0xeb, 0xe6, 0x15, 0x38, 0x4c, 0x17, 0x82, 0x6c, 0x6e, 0xfa, 0x14, 0xb0, 0x9c, 0x5d, 0x31, 0x90, 0x81, 0xa1, 0x1f, - 0x8a, 0x4c, 0xd9, 0x32, 0xa5, 0x75, 0x0b, 0x7e, 0xd1, 0x3d, 0xb9, 0xb2, 0x0a, 0x75, 0x9b, 0xef, 0x8d, 0x5c, 0xa3, - 0xa7, 0xc9, 0xae, 0x5c, 0xa3, 0x8a, 0xb6, 0xbb, 0xd7, 0x44, 0xf7, 0x67, 0xa5, 0xca, 0xb1, 0xb6, 0x57, 0xf9, 0x1d, - 0xc3, 0xb5, 0x80, 0x36, 0x25, 0x9a, 0x35, 0x57, 0x39, 0xcf, 0xf3, 0x71, 0x71, 0x96, 0x40, 0xa4, 0xee, 0x8c, 0x25, - 0xfd, 0x2b, 0xab, 0x51, 0x1c, 0xc8, 0x75, 0xbe, 0x23, 0x93, 0x28, 0xb9, 0xf6, 0xa3, 0x77, 0x30, 0x5e, 0xf9, 0xf2, - 0xf9, 0x5d, 0x90, 0xfa, 0x9c, 0x2a, 0xee, 0x52, 0xc2, 0xf0, 0x9d, 0x01, 0xc3, 0x77, 0x92, 0x4f, 0x97, 0xed, 0xf1, - 0xf2, 0x45, 0xd1, 0x81, 0x37, 0xce, 0x35, 0xcb, 0x98, 0xf2, 0xed, 0x63, 0xac, 0xb3, 0xb0, 0x69, 0xc1, 0xc2, 0xa6, - 0xdc, 0x59, 0xef, 0xca, 0x71, 0x7e, 0xdc, 0xde, 0xcb, 0x26, 0x67, 0xfb, 0xb1, 0xdc, 0xf8, 0x3f, 0x7a, 0xf7, 0xb6, - 0x31, 0xb8, 0xdc, 0xa1, 0x7b, 0x28, 0x92, 0x55, 0x24, 0xc8, 0x4f, 0x20, 0xe9, 0x80, 0x93, 0x9e, 0x71, 0xe4, 0xa0, - 0x94, 0x53, 0x3a, 0x0f, 0xc8, 0x19, 0xcd, 0x33, 0x9e, 0x4c, 0x55, 0x9f, 0x99, 0x3a, 0x67, 0x24, 0x5e, 0x82, 0x2b, - 0x5a, 0xc4, 0xda, 0xbd, 0xea, 0x49, 0xae, 0xe5, 0x47, 0x16, 0x07, 0x5e, 0x86, 0x95, 0x14, 0xc9, 0xbc, 0x34, 0x27, - 0x3a, 0xd7, 0x78, 0xf3, 0x1d, 0x1e, 0xb3, 0x98, 0x65, 0x21, 0x4d, 0x9d, 0x04, 0x2d, 0x77, 0x0d, 0x96, 0x40, 0x40, - 0x46, 0x0e, 0x86, 0x7f, 0x2a, 0x8f, 0xfc, 0xb9, 0xd0, 0x1b, 0xf8, 0x81, 0xa6, 0x94, 0x87, 0x49, 0x00, 0x69, 0x29, - 0x6e, 0x50, 0x1c, 0x69, 0x3a, 0x38, 0xd8, 0x73, 0x6c, 0xe1, 0x96, 0x80, 0xc3, 0xdf, 0xe6, 0x1b, 0xd4, 0x5f, 0xc2, - 0xe9, 0x9c, 0x72, 0x68, 0x8a, 0x96, 0x74, 0xfd, 0x20, 0x0b, 0x77, 0x3f, 0xd2, 0x3b, 0x1c, 0xa3, 0x3c, 0xf7, 0x24, - 0xd4, 0xf6, 0x98, 0xd1, 0x28, 0xb0, 0xf1, 0x47, 0x7a, 0xe7, 0x15, 0xe7, 0xc5, 0xc5, 0xf1, 0x66, 0xb1, 0x80, 0x76, - 0x72, 0x13, 0xdb, 0xb8, 0x1c, 0xc4, 0x5b, 0xe6, 0x38, 0x49, 0xd9, 0x04, 0x88, 0xf3, 0x6f, 0xf4, 0xce, 0x93, 0xfd, - 0x31, 0xe3, 0xb4, 0x1e, 0x5a, 0x6a, 0xd4, 0xbb, 0x46, 0xb1, 0xb9, 0x0c, 0xca, 0xa0, 0x18, 0x88, 0xb6, 0x43, 0x52, - 0xa9, 0x57, 0x9a, 0x87, 0x08, 0xe5, 0x0f, 0x9d, 0x0a, 0xfe, 0xd6, 0x14, 0x6d, 0xbc, 0x92, 0xf9, 0xba, 0xd6, 0x88, - 0x42, 0x83, 0x32, 0xd3, 0xe3, 0xd2, 0x89, 0xf5, 0xae, 0x53, 0x47, 0x10, 0x0c, 0x47, 0xd8, 0xb7, 0x5c, 0x75, 0xea, - 0xfd, 0x24, 0x13, 0x42, 0xca, 0x48, 0xd2, 0xcb, 0xb2, 0x9d, 0x75, 0xe9, 0x00, 0xde, 0x21, 0xa1, 0xc5, 0x17, 0x07, - 0x32, 0x73, 0x9d, 0x2d, 0xfa, 0x37, 0x4e, 0x9c, 0xa5, 0x9e, 0x82, 0x17, 0x9b, 0x58, 0xe4, 0x39, 0x50, 0xa1, 0xa2, - 0x2f, 0x99, 0x00, 0x08, 0x67, 0xd8, 0x37, 0xa4, 0x66, 0x2a, 0xa4, 0xa6, 0x6b, 0x60, 0x7c, 0x87, 0x94, 0xa4, 0x02, - 0x19, 0x42, 0x89, 0x14, 0x42, 0x4f, 0x2d, 0xae, 0x22, 0x21, 0x73, 0x41, 0x8b, 0xf3, 0x73, 0x72, 0xcd, 0xd3, 0x0a, - 0x58, 0x8e, 0xe8, 0x07, 0xe5, 0x1e, 0x4c, 0x89, 0xca, 0x0a, 0x79, 0x71, 0x2c, 0x5b, 0xa7, 0xb7, 0x3a, 0x89, 0xab, - 0xa7, 0x45, 0x34, 0x4a, 0x9c, 0x10, 0x2d, 0x63, 0x27, 0xc4, 0x29, 0xa4, 0x23, 0x26, 0x79, 0x01, 0x3f, 0x35, 0x57, - 0xa3, 0x92, 0xac, 0xbc, 0xfd, 0x8c, 0x1f, 0x28, 0xf3, 0x1c, 0x52, 0x34, 0x71, 0xac, 0x79, 0x4a, 0xec, 0x88, 0xc3, - 0x76, 0xc6, 0xb2, 0x7d, 0xa7, 0x12, 0x74, 0x14, 0x60, 0x7f, 0xe3, 0xce, 0xd2, 0x98, 0x85, 0x79, 0x9a, 0x5b, 0x9d, - 0xf9, 0x53, 0xc1, 0xbe, 0x32, 0x87, 0xd4, 0xc9, 0xc8, 0x9a, 0xc4, 0xb9, 0x3f, 0xd5, 0xf2, 0x97, 0x39, 0x4d, 0xef, - 0x2e, 0x28, 0xa4, 0x3a, 0x27, 0x70, 0xda, 0xb7, 0x5c, 0x86, 0x32, 0x4d, 0xbd, 0x9f, 0x0a, 0x65, 0x25, 0xaf, 0x9e, - 0x02, 0x5c, 0x3f, 0x23, 0x98, 0x8b, 0x68, 0xa3, 0xe1, 0x88, 0x91, 0xbb, 0x85, 0xee, 0x3c, 0x3d, 0x49, 0x3b, 0x0c, - 0xfc, 0x6b, 0x25, 0xa6, 0x55, 0xb0, 0x00, 0x27, 0xe6, 0x89, 0xd4, 0x41, 0x36, 0x5c, 0xf7, 0xca, 0x40, 0x11, 0x84, - 0xef, 0xd2, 0xdd, 0x53, 0xdd, 0x96, 0x34, 0xbb, 0x7b, 0xaa, 0x95, 0xa0, 0x9f, 0x48, 0xf8, 0xc1, 0x6a, 0x9c, 0xe2, - 0xf8, 0x32, 0xcb, 0x73, 0x94, 0x03, 0x78, 0x5f, 0x77, 0x1c, 0xe7, 0x6b, 0x95, 0x32, 0xe8, 0x42, 0x2c, 0xf6, 0x22, - 0x4a, 0x34, 0x13, 0x2f, 0xc7, 0xff, 0x7a, 0x63, 0xfc, 0xaf, 0x8d, 0x33, 0xa7, 0x60, 0x1a, 0x4d, 0x62, 0x1a, 0x68, - 0xd6, 0x89, 0x24, 0x01, 0x0a, 0xbd, 0x2d, 0xe6, 0xe4, 0xf5, 0x95, 0x07, 0x1a, 0xd7, 0x72, 0x9c, 0xc4, 0xbc, 0x3e, - 0xf6, 0xa7, 0x2c, 0xba, 0xf3, 0xe6, 0xac, 0x3e, 0x4d, 0xe2, 0x24, 0x9b, 0xf9, 0x23, 0x8a, 0xb3, 0xbb, 0x8c, 0xd3, - 0x69, 0x7d, 0xce, 0xf0, 0x73, 0x1a, 0x2d, 0x28, 0x67, 0x23, 0x1f, 0xdb, 0x67, 0x29, 0xf3, 0x23, 0xeb, 0x95, 0x9f, - 0xa6, 0xc9, 0x8d, 0x8d, 0xdf, 0x26, 0xd7, 0x09, 0x4f, 0xf0, 0xeb, 0xdb, 0xbb, 0x09, 0x8d, 0xf1, 0xfb, 0xeb, 0x79, - 0xcc, 0xe7, 0x38, 0xf3, 0xe3, 0xac, 0x9e, 0xd1, 0x94, 0x8d, 0x3b, 0xa3, 0x24, 0x4a, 0xd2, 0x3a, 0x64, 0x6c, 0x4f, - 0xa9, 0x17, 0xb1, 0x49, 0xc8, 0xad, 0xc0, 0x4f, 0x3f, 0x76, 0xea, 0xf5, 0x59, 0xca, 0xa6, 0x7e, 0x7a, 0x57, 0x17, - 0x35, 0xbc, 0xcf, 0x9b, 0x87, 0xfe, 0xe3, 0xf1, 0x51, 0x87, 0xa7, 0x7e, 0x9c, 0x31, 0x58, 0x26, 0xcf, 0x8f, 0x22, - 0xeb, 0xf0, 0xb8, 0x39, 0xcd, 0xf6, 0x64, 0x20, 0xcf, 0x8f, 0x79, 0x7e, 0x85, 0xdf, 0x00, 0xdc, 0xee, 0x35, 0x8f, - 0xf1, 0xf5, 0x9c, 0xf3, 0x24, 0x5e, 0x8e, 0xe6, 0x69, 0x96, 0xa4, 0xde, 0x2c, 0x61, 0x31, 0xa7, 0x69, 0xe7, 0x3a, - 0x49, 0x03, 0x9a, 0xd6, 0x53, 0x3f, 0x60, 0xf3, 0xcc, 0x3b, 0x9a, 0xdd, 0x76, 0x40, 0xb3, 0x98, 0xa4, 0xc9, 0x3c, - 0x0e, 0xd4, 0x58, 0x2c, 0x0e, 0x69, 0xca, 0xb8, 0xf9, 0x42, 0x5c, 0x62, 0xe2, 0x45, 0x2c, 0xa6, 0x7e, 0x5a, 0x9f, - 0x40, 0x63, 0x30, 0x8b, 0x9a, 0x01, 0x9d, 0xe0, 0x74, 0x72, 0xed, 0x3b, 0xad, 0xf6, 0x23, 0xac, 0xff, 0xba, 0xc7, - 0xc8, 0x6a, 0x6e, 0x2f, 0x6e, 0x35, 0x9b, 0x7f, 0x41, 0x9d, 0xb5, 0x51, 0x04, 0x40, 0x5e, 0x6b, 0x76, 0x6b, 0x65, - 0x09, 0x64, 0xb4, 0x6d, 0x6b, 0xd9, 0x99, 0xf9, 0x01, 0xe4, 0x03, 0x7b, 0xed, 0xd9, 0x6d, 0x0e, 0xb3, 0xf3, 0x64, - 0x8a, 0xa9, 0x9a, 0xa4, 0x7a, 0x5a, 0xfe, 0x5e, 0x88, 0x4f, 0xb7, 0x43, 0xdc, 0xd6, 0x10, 0x97, 0x58, 0xaf, 0x07, - 0xf3, 0x54, 0xc4, 0x56, 0xbd, 0x56, 0x26, 0x01, 0x09, 0x93, 0x05, 0x4d, 0x35, 0x1c, 0xe2, 0xe1, 0x77, 0x83, 0xd1, - 0xde, 0x0e, 0xc6, 0xe9, 0xa7, 0xc0, 0x48, 0xe3, 0x60, 0x59, 0x5d, 0xd7, 0x56, 0x4a, 0xa7, 0x9d, 0x90, 0x02, 0x3d, - 0x79, 0x6d, 0xf8, 0x7d, 0xc3, 0x02, 0x1e, 0xca, 0x9f, 0x82, 0x9c, 0x6f, 0xe4, 0xbb, 0xe3, 0x66, 0x53, 0x3e, 0x67, - 0xec, 0x57, 0xea, 0xb5, 0x5c, 0xa8, 0x90, 0x5f, 0xe1, 0x1f, 0x8b, 0xb3, 0xbc, 0x55, 0xee, 0x89, 0xbf, 0x36, 0x0f, - 0xf9, 0x1a, 0x29, 0x8a, 0xe5, 0x91, 0x68, 0x9c, 0x6a, 0x59, 0x29, 0x85, 0x0f, 0xb8, 0xed, 0x04, 0x77, 0x24, 0xac, - 0x57, 0x1c, 0xe2, 0x64, 0xfd, 0xaf, 0x65, 0xde, 0x85, 0x07, 0x91, 0x0e, 0x23, 0xd5, 0x30, 0xe9, 0xa4, 0x3d, 0xd2, - 0xec, 0xa4, 0xf5, 0x3a, 0x72, 0x12, 0x12, 0x0f, 0x52, 0x95, 0x9c, 0xe7, 0xb0, 0x7e, 0x22, 0x8c, 0xed, 0x0c, 0x79, - 0x09, 0x9c, 0x34, 0x5d, 0xad, 0xca, 0x30, 0x00, 0x13, 0xa7, 0x35, 0x7e, 0xe4, 0xaa, 0x02, 0xce, 0x0c, 0x4e, 0x9e, - 0xe8, 0xab, 0x5d, 0x62, 0xcd, 0x2b, 0xa2, 0x64, 0x24, 0x30, 0xe7, 0xce, 0x7c, 0x1e, 0x82, 0x97, 0xa2, 0x10, 0x3f, - 0x65, 0x0a, 0x93, 0xdd, 0xb0, 0x51, 0x3f, 0x2e, 0xf2, 0xdb, 0x20, 0x8f, 0x2f, 0xce, 0xa1, 0x97, 0x3b, 0x4e, 0x84, - 0xc5, 0x54, 0xf4, 0xff, 0x9e, 0x1b, 0x92, 0x3a, 0x76, 0x59, 0x3c, 0x8a, 0xe6, 0x01, 0xcd, 0x44, 0x0f, 0xa5, 0x38, - 0xff, 0xbb, 0x59, 0x4b, 0x34, 0x81, 0xde, 0x45, 0x36, 0x0f, 0x54, 0x84, 0x1b, 0x54, 0x8a, 0xe7, 0xba, 0x78, 0x2e, - 0xdb, 0xea, 0x4b, 0x25, 0xd8, 0xd8, 0x81, 0x96, 0xee, 0x3c, 0x66, 0xbf, 0xcc, 0xe9, 0x25, 0x0b, 0x8c, 0x73, 0xbb, - 0x34, 0x1e, 0x25, 0x01, 0x7d, 0xff, 0xf6, 0x1b, 0xc8, 0x76, 0x4f, 0x62, 0x20, 0xb1, 0x58, 0xfa, 0xbb, 0x70, 0x46, - 0x62, 0x37, 0xa0, 0x0b, 0x36, 0xa2, 0xfd, 0xab, 0xfd, 0xe5, 0xd6, 0x8a, 0xf2, 0x35, 0xca, 0x1b, 0x57, 0x22, 0xe9, - 0x4f, 0x40, 0x79, 0xb5, 0xbf, 0xbc, 0xe3, 0x79, 0x63, 0x7f, 0x19, 0xbb, 0x41, 0x32, 0xf5, 0x59, 0x0c, 0xbf, 0xb3, - 0x7c, 0x7f, 0xc9, 0xe0, 0x07, 0xcf, 0xaf, 0xf2, 0x32, 0x51, 0xb4, 0x80, 0xc8, 0x98, 0x82, 0xc2, 0x5d, 0x0b, 0xb9, - 0x1f, 0x12, 0x16, 0x8b, 0xa2, 0xfb, 0x7a, 0xa6, 0xba, 0x57, 0x40, 0xf2, 0x37, 0x44, 0x1a, 0xcc, 0xda, 0x5c, 0x1e, - 0x3f, 0xd4, 0x5c, 0xa6, 0x31, 0x67, 0x22, 0x2d, 0x5e, 0x87, 0x73, 0x42, 0x3f, 0xbb, 0x1c, 0xc9, 0x73, 0xa8, 0x59, - 0x79, 0xea, 0xc2, 0x17, 0x88, 0x95, 0x16, 0x30, 0x4d, 0x85, 0xb1, 0x4f, 0x77, 0x1f, 0x94, 0x8c, 0xef, 0x33, 0xfe, - 0x0a, 0xaa, 0xca, 0x92, 0x79, 0x3a, 0x82, 0x58, 0xaf, 0x52, 0x29, 0x36, 0xbd, 0x62, 0xb6, 0xd0, 0xdf, 0x6c, 0xcc, - 0x8d, 0x24, 0x5b, 0x8e, 0x99, 0x79, 0x67, 0x07, 0x15, 0xf1, 0x44, 0x79, 0x16, 0x46, 0xe9, 0x0f, 0x7a, 0x4a, 0xa0, - 0x10, 0x05, 0x22, 0x5f, 0xd4, 0x49, 0x49, 0x2f, 0x2d, 0x71, 0x4e, 0x08, 0x61, 0x2e, 0x0b, 0x44, 0x20, 0x0f, 0x14, - 0x8b, 0x7a, 0x0b, 0x22, 0x43, 0x2c, 0x28, 0x35, 0x3c, 0xa6, 0xf0, 0xbc, 0x5a, 0xfd, 0x9d, 0x3b, 0xb2, 0xae, 0x74, - 0xaa, 0x80, 0x0e, 0xc6, 0xb0, 0x7c, 0xe9, 0xa5, 0xb8, 0xe8, 0xd2, 0x83, 0x4a, 0x79, 0x27, 0x11, 0xe8, 0x93, 0xc8, - 0x22, 0x1a, 0x9d, 0x67, 0x52, 0x45, 0x48, 0x10, 0x36, 0x5f, 0x17, 0x07, 0xf8, 0x2b, 0xf8, 0x6e, 0xae, 0x2d, 0x8b, - 0xb4, 0xa7, 0x92, 0xf5, 0xd2, 0x2c, 0x49, 0xb9, 0xe3, 0x84, 0x38, 0x42, 0xa4, 0x17, 0x0a, 0xaa, 0xed, 0x46, 0xe2, - 0xbf, 0x7e, 0xbd, 0xe5, 0xb5, 0x0a, 0x4f, 0x48, 0xe5, 0x5c, 0xb5, 0xcc, 0x33, 0x53, 0x67, 0x73, 0x01, 0x5c, 0x5c, - 0xfc, 0x96, 0xf3, 0x29, 0x9f, 0x8b, 0x69, 0x61, 0xc5, 0xb9, 0xa4, 0xd4, 0x77, 0x2a, 0x40, 0x88, 0xb8, 0xdb, 0x8e, - 0xa1, 0x50, 0x5e, 0xce, 0xbb, 0xd8, 0xc5, 0x57, 0x52, 0xdb, 0xb9, 0x34, 0xc8, 0xf8, 0x8a, 0x69, 0x7f, 0x5d, 0x95, - 0xc0, 0x72, 0x85, 0x11, 0x83, 0x05, 0x6c, 0xab, 0x26, 0x61, 0xb9, 0x23, 0xf1, 0x56, 0x2a, 0x75, 0xe5, 0x23, 0x95, - 0xba, 0xd6, 0xf6, 0x2a, 0x22, 0xeb, 0x71, 0x1b, 0x60, 0xe0, 0x01, 0xc8, 0xb8, 0x9e, 0x02, 0x30, 0x93, 0x31, 0x15, - 0x17, 0xd3, 0x48, 0xd6, 0x82, 0x97, 0x52, 0x8d, 0xf7, 0xec, 0x37, 0xaf, 0x2f, 0xde, 0xd9, 0x18, 0xee, 0x33, 0xa3, - 0x69, 0xe6, 0x2d, 0x6d, 0x95, 0x4c, 0x58, 0x87, 0xc0, 0xb4, 0xed, 0xd9, 0xfe, 0x0c, 0xce, 0x66, 0x0b, 0xee, 0xd9, - 0xb8, 0xad, 0xdf, 0xdc, 0xdc, 0xd4, 0xe1, 0xe8, 0x58, 0x7d, 0x9e, 0x46, 0x92, 0xaf, 0x04, 0x76, 0x9e, 0x23, 0x97, - 0x87, 0x34, 0x2e, 0x6e, 0x3c, 0x4a, 0x22, 0xea, 0x46, 0xc9, 0x44, 0x1e, 0x7b, 0x5d, 0xf7, 0x43, 0x8c, 0xae, 0xba, - 0xe2, 0x26, 0xaf, 0x5e, 0x97, 0xcb, 0x3b, 0xd4, 0x78, 0x0a, 0x3f, 0x7b, 0x10, 0xa5, 0xea, 0x36, 0x78, 0x28, 0x1e, - 0x2e, 0x60, 0xdb, 0x88, 0xa7, 0xfd, 0xe5, 0x06, 0x91, 0xf5, 0xa1, 0x8b, 0xb0, 0x27, 0xa7, 0x96, 0x89, 0x5a, 0x57, - 0xde, 0xe8, 0xea, 0x2a, 0xef, 0x36, 0xa0, 0xaf, 0x86, 0xee, 0xf7, 0x3a, 0x09, 0xee, 0x74, 0xfb, 0x82, 0xf0, 0xe0, - 0x46, 0xa7, 0x98, 0xf4, 0xa0, 0x0b, 0x18, 0x37, 0xe8, 0x09, 0x9c, 0x29, 0x5e, 0x39, 0x28, 0x1f, 0xf2, 0xa1, 0x05, - 0x9c, 0x31, 0x87, 0x12, 0xa0, 0x4b, 0xe8, 0x3c, 0x28, 0x1a, 0x88, 0x6d, 0x2d, 0x8b, 0x76, 0x01, 0x28, 0x2b, 0x96, - 0xdb, 0x45, 0xfa, 0xb3, 0x4b, 0xb2, 0xd0, 0x10, 0x07, 0x26, 0xf0, 0x57, 0x08, 0xfe, 0x17, 0x80, 0x77, 0x1b, 0x12, - 0x4d, 0x57, 0xe6, 0xed, 0x32, 0xf2, 0xde, 0x87, 0x02, 0x99, 0x83, 0x98, 0xe3, 0x37, 0x1c, 0xbf, 0xbe, 0x12, 0x55, - 0xb5, 0x3a, 0x00, 0x7a, 0x2a, 0xa8, 0x4d, 0x4d, 0xad, 0xf7, 0x8d, 0x92, 0x28, 0xf2, 0x67, 0x19, 0xf5, 0xf4, 0x0f, - 0xa5, 0x19, 0x80, 0x82, 0xb1, 0xa9, 0x8a, 0xa9, 0x04, 0xa7, 0x73, 0x50, 0xd8, 0x36, 0xf5, 0xc4, 0x85, 0x9f, 0x3a, - 0xf5, 0xfa, 0xa8, 0x7e, 0x3d, 0x41, 0x39, 0x0f, 0x97, 0xa6, 0x5e, 0x71, 0xd2, 0x6c, 0x76, 0x20, 0x1b, 0xb5, 0xee, - 0x47, 0x6c, 0x12, 0x7b, 0x11, 0x1d, 0xf3, 0x9c, 0xc3, 0x31, 0xc1, 0xa5, 0x56, 0xe4, 0xdc, 0xf6, 0x71, 0x4a, 0xa7, - 0x96, 0x0b, 0xff, 0xde, 0x3f, 0x70, 0xce, 0x03, 0x2f, 0xe6, 0x61, 0x5d, 0x64, 0x3d, 0xc3, 0x99, 0x0d, 0x1e, 0x56, - 0x9e, 0x97, 0xc6, 0x40, 0x23, 0x0a, 0x4a, 0x6e, 0xce, 0x53, 0x8b, 0x87, 0x98, 0xa7, 0x66, 0xbd, 0x18, 0x2d, 0x37, - 0x66, 0xb0, 0xa9, 0x6b, 0x1d, 0xa2, 0x3c, 0x13, 0xa6, 0xc9, 0x66, 0x65, 0xad, 0xb0, 0x56, 0x9f, 0x36, 0xd0, 0x67, - 0xa8, 0xd6, 0xb9, 0x74, 0xed, 0x2f, 0x65, 0x8b, 0x87, 0x20, 0xb3, 0xa2, 0xf4, 0x63, 0xb3, 0x05, 0xca, 0x59, 0x3c, - 0x9b, 0xf3, 0x81, 0x08, 0x2b, 0xa4, 0x70, 0x40, 0x65, 0x88, 0x8d, 0x12, 0xc0, 0xc1, 0x70, 0x29, 0x81, 0x19, 0xf9, - 0xd1, 0xc8, 0x01, 0x88, 0xac, 0xba, 0x75, 0x9a, 0xd2, 0x29, 0xea, 0x4c, 0x59, 0x5c, 0x97, 0xef, 0x8e, 0x0d, 0xc5, - 0xd0, 0x7d, 0x04, 0x4f, 0xb9, 0x2b, 0x7a, 0xc3, 0x22, 0x7b, 0x78, 0x0b, 0x2e, 0xaf, 0x86, 0x79, 0xde, 0x49, 0xb9, - 0x33, 0x78, 0xe9, 0xa0, 0x21, 0xfe, 0xc6, 0xb8, 0x1f, 0xc7, 0xd6, 0x3b, 0xc9, 0xc6, 0x6d, 0xb4, 0xa3, 0x8a, 0xb9, - 0x17, 0x44, 0xb5, 0x6f, 0x08, 0x54, 0x7c, 0xe2, 0xd8, 0x34, 0x9b, 0xd5, 0x25, 0xcb, 0xab, 0x0b, 0x92, 0xb5, 0xa1, - 0x29, 0x52, 0xbe, 0x72, 0x4a, 0x97, 0x82, 0x9b, 0xa9, 0x43, 0x32, 0xd2, 0x9d, 0x33, 0x2c, 0x0e, 0x55, 0xa9, 0x67, - 0xf3, 0x18, 0x15, 0xaa, 0xb0, 0x9b, 0xab, 0xb3, 0x2a, 0x6b, 0x04, 0xe5, 0xa2, 0xb8, 0x44, 0xd0, 0x8f, 0x22, 0x18, - 0xf0, 0x4a, 0x6b, 0x24, 0xe6, 0xad, 0x2b, 0x03, 0x3e, 0x74, 0x50, 0xae, 0xf6, 0xe9, 0x13, 0xa1, 0xd4, 0x1b, 0x37, - 0x17, 0xee, 0x71, 0x1d, 0xae, 0x93, 0x22, 0x9a, 0x41, 0xc2, 0x41, 0x25, 0x31, 0xbd, 0x53, 0xb2, 0x36, 0x69, 0x12, - 0x58, 0x62, 0x42, 0xc4, 0x4e, 0xe3, 0xc0, 0xb6, 0xbe, 0x1c, 0x45, 0x6c, 0xf4, 0x91, 0xd8, 0xfb, 0x4b, 0x07, 0x6d, - 0x9e, 0x3b, 0x15, 0x5c, 0x41, 0xf3, 0x79, 0x54, 0x0d, 0x65, 0xa4, 0xae, 0xc1, 0xc2, 0xe5, 0xc5, 0x44, 0x76, 0x0f, - 0xf4, 0xa6, 0x6e, 0x43, 0x8e, 0xd3, 0xbb, 0xca, 0x2f, 0xcb, 0xfb, 0xc6, 0x4a, 0x28, 0x00, 0xcd, 0xb2, 0xdc, 0x12, - 0x44, 0x45, 0xec, 0x4f, 0x52, 0x9a, 0x6d, 0x49, 0xa6, 0x06, 0x70, 0x72, 0xc5, 0xdf, 0x6c, 0xeb, 0xcb, 0xa2, 0x8c, - 0x16, 0x3e, 0x25, 0x91, 0x14, 0x43, 0x6c, 0x18, 0x0b, 0x1c, 0x09, 0x6e, 0x40, 0xb9, 0xcf, 0x22, 0xd9, 0xa4, 0xa3, - 0x5d, 0x20, 0x6b, 0x33, 0x5a, 0xad, 0xb2, 0xea, 0x5c, 0x58, 0x15, 0x83, 0x62, 0x66, 0xdd, 0x46, 0x09, 0xb7, 0x98, - 0x99, 0xd8, 0x93, 0x66, 0x70, 0xb6, 0x9c, 0xa1, 0x7c, 0x67, 0x7d, 0x39, 0x12, 0xc7, 0xb6, 0x00, 0xc0, 0x44, 0x01, - 0x08, 0x69, 0x03, 0xf2, 0x58, 0x92, 0x13, 0x91, 0xc4, 0xe5, 0x7e, 0x3a, 0xa1, 0x7c, 0x0d, 0xb1, 0x91, 0xcc, 0x12, - 0xee, 0xe8, 0x14, 0x81, 0x0d, 0x68, 0xfd, 0x2a, 0xb4, 0xa0, 0x44, 0xe7, 0x7d, 0xd0, 0x83, 0xc9, 0x56, 0x75, 0x3a, - 0x44, 0x20, 0x6f, 0xc5, 0xe2, 0x48, 0x09, 0x93, 0x08, 0x09, 0x23, 0x39, 0x81, 0x25, 0xc6, 0x12, 0x20, 0xe6, 0xb6, - 0xd5, 0x97, 0x90, 0xd3, 0x40, 0xc2, 0x4c, 0x52, 0xd1, 0x2a, 0xc9, 0xbb, 0x0d, 0x59, 0x5b, 0x8a, 0x00, 0x59, 0x09, - 0x90, 0x20, 0xf6, 0x69, 0x89, 0x03, 0xc8, 0x2c, 0x37, 0xf1, 0x10, 0xb0, 0x45, 0x41, 0x6c, 0xe2, 0x00, 0x5b, 0xaf, - 0x1b, 0xf9, 0xd7, 0x34, 0xea, 0xed, 0x2f, 0xd3, 0xd5, 0xaa, 0x99, 0x77, 0x1b, 0xf2, 0xd1, 0xea, 0x0a, 0xbe, 0x21, - 0x2f, 0x1d, 0x15, 0x4b, 0x0c, 0xa7, 0x42, 0x21, 0xdf, 0x56, 0x27, 0x9a, 0x79, 0xaa, 0x83, 0xdc, 0xb6, 0x44, 0x8a, - 0x8b, 0xa8, 0x54, 0xe8, 0x51, 0xb9, 0x6d, 0xb1, 0x60, 0xb3, 0x2c, 0xe3, 0x74, 0x06, 0xa5, 0xe1, 0x6a, 0xd5, 0xca, - 0x6d, 0x6b, 0xca, 0x62, 0x78, 0x4a, 0x57, 0x2b, 0x71, 0xe0, 0x72, 0xca, 0x62, 0xa7, 0x09, 0x64, 0x6b, 0x5b, 0x53, - 0xff, 0x56, 0x4c, 0x58, 0xbf, 0xf1, 0x6f, 0x9d, 0x96, 0x7a, 0xe5, 0x16, 0xf8, 0xc9, 0x80, 0xe2, 0xca, 0x15, 0x8d, - 0xd4, 0x8a, 0x06, 0x78, 0x2e, 0x8f, 0x92, 0x11, 0x27, 0x20, 0xd1, 0xf6, 0x15, 0x0d, 0xf4, 0x8a, 0xce, 0x77, 0xac, - 0xe8, 0xfc, 0x9e, 0x15, 0xf5, 0xd5, 0xea, 0x59, 0x05, 0xee, 0x92, 0xd5, 0xaa, 0xd5, 0x2c, 0xb1, 0xd7, 0x6d, 0x04, - 0x6c, 0x01, 0xab, 0x01, 0xda, 0x21, 0x67, 0x53, 0xba, 0x9d, 0x28, 0xab, 0x28, 0xa6, 0xbf, 0x09, 0x93, 0x25, 0x16, - 0xd2, 0x2a, 0x16, 0x4c, 0xba, 0x2e, 0xa2, 0x9e, 0x7f, 0x26, 0x65, 0x33, 0xc0, 0x43, 0x06, 0x78, 0x08, 0xf5, 0x25, - 0xa4, 0x8e, 0xfd, 0xce, 0xc6, 0xb6, 0x65, 0x6b, 0xb2, 0xbe, 0xca, 0x2f, 0x41, 0x46, 0x88, 0xf9, 0x3d, 0x88, 0x16, - 0xa1, 0xb6, 0xdd, 0xdb, 0x4d, 0x73, 0x90, 0xa0, 0x70, 0x93, 0xa4, 0x81, 0xed, 0xc9, 0xaa, 0xbf, 0x09, 0x55, 0x53, - 0x16, 0xab, 0x74, 0xb7, 0x9d, 0xb4, 0x56, 0xbe, 0x37, 0x29, 0xae, 0x7d, 0x7c, 0x2c, 0x6b, 0xcc, 0x7c, 0xce, 0x69, - 0x1a, 0x2b, 0xca, 0xb5, 0xed, 0xff, 0x2f, 0xa8, 0x70, 0x0b, 0x5f, 0xf1, 0xf5, 0x02, 0x68, 0x02, 0x54, 0x7a, 0xbe, - 0xe2, 0xf9, 0x52, 0x3c, 0xed, 0x95, 0x0a, 0xee, 0x1d, 0x32, 0x6d, 0x0d, 0x59, 0x04, 0xa6, 0xcf, 0x7c, 0x4a, 0x83, - 0x4b, 0xc1, 0xa0, 0xfb, 0xa3, 0x2b, 0xa5, 0xb0, 0xae, 0x89, 0xbb, 0xb2, 0x01, 0xb6, 0x7f, 0x9e, 0xb7, 0x1f, 0x1d, - 0x9d, 0xdb, 0x58, 0xf2, 0xf8, 0x64, 0x3c, 0xb6, 0x51, 0x6e, 0x3d, 0xac, 0x59, 0xeb, 0xe8, 0xe7, 0xf9, 0x57, 0xcf, - 0x9a, 0x5f, 0x15, 0x8d, 0x63, 0x20, 0x22, 0x95, 0x61, 0xa1, 0x45, 0x95, 0x01, 0xaf, 0x9e, 0xd1, 0xd8, 0x8f, 0x77, - 0x4f, 0x67, 0x60, 0x4e, 0x27, 0x9b, 0x51, 0x1a, 0x00, 0x71, 0xe2, 0x8d, 0xd2, 0xcb, 0x88, 0x2e, 0xa8, 0xbe, 0xfc, - 0x71, 0xcb, 0x60, 0x5b, 0x5a, 0x8c, 0x92, 0x79, 0xcc, 0x55, 0xaa, 0x89, 0x62, 0xb5, 0xc6, 0x94, 0xae, 0xc4, 0x1c, - 0x4c, 0x13, 0xe2, 0x4e, 0xca, 0xb9, 0xaa, 0xf4, 0xca, 0xaf, 0xb0, 0x6d, 0x00, 0xb0, 0x13, 0xb2, 0xfe, 0x8e, 0x72, - 0xaf, 0x89, 0x9b, 0xbb, 0x60, 0xc3, 0x2d, 0xe4, 0xd9, 0xf6, 0x50, 0xe3, 0x49, 0x78, 0x8b, 0x2b, 0x37, 0x76, 0xec, - 0xc4, 0xd7, 0x27, 0x31, 0x70, 0x9d, 0x42, 0x67, 0x31, 0xcd, 0xb2, 0x9d, 0x08, 0x28, 0x16, 0x11, 0xdb, 0x65, 0x6d, - 0x7b, 0x47, 0x2f, 0xb8, 0x89, 0x61, 0x87, 0x09, 0x80, 0x8b, 0x98, 0xb5, 0xaa, 0x45, 0xc7, 0x63, 0x3a, 0x2a, 0x9c, - 0xed, 0x10, 0x7d, 0x1c, 0xb3, 0x88, 0x43, 0x10, 0x4e, 0x44, 0xc7, 0xec, 0x57, 0x49, 0x4c, 0x6d, 0xa4, 0xf3, 0x69, - 0x15, 0xfc, 0x4a, 0xfe, 0x6f, 0x87, 0x47, 0xf6, 0x58, 0x85, 0x45, 0x8d, 0xb2, 0x5a, 0x69, 0x5f, 0x50, 0xa5, 0xbc, - 0x8a, 0xc8, 0x44, 0x38, 0x7b, 0x76, 0x6d, 0xa0, 0x87, 0x6d, 0x93, 0x65, 0xeb, 0xab, 0xe3, 0x56, 0x33, 0xb7, 0xb1, - 0x0d, 0xdd, 0x3d, 0x74, 0x97, 0x88, 0x56, 0x87, 0xd0, 0x6a, 0x1e, 0xff, 0x96, 0x76, 0xed, 0xd6, 0xe3, 0x96, 0x8d, - 0xe5, 0x45, 0x0e, 0x28, 0x2f, 0x98, 0xc1, 0x08, 0xdc, 0xcf, 0x7f, 0x78, 0x2a, 0xd5, 0xce, 0x1f, 0x06, 0xcf, 0x49, - 0xab, 0x69, 0x63, 0x3b, 0xe3, 0xc9, 0xec, 0x37, 0x4c, 0xe1, 0xd0, 0xc6, 0xf6, 0x28, 0x4a, 0x32, 0x6a, 0xce, 0x41, - 0xaa, 0xb3, 0x7f, 0x7c, 0x12, 0x12, 0xa2, 0x59, 0x4a, 0xb3, 0xcc, 0x32, 0xfb, 0x57, 0xa4, 0xf4, 0x09, 0x86, 0xb9, - 0x95, 0xe2, 0x32, 0xca, 0x05, 0x5e, 0xe4, 0x1d, 0x0b, 0x26, 0x55, 0xc9, 0xb2, 0x0d, 0x62, 0x13, 0x22, 0xa0, 0x60, - 0x6c, 0x52, 0xbb, 0xfa, 0xe4, 0xc8, 0x5b, 0xb6, 0x9e, 0x1c, 0x58, 0x46, 0xe5, 0x37, 0x07, 0xa8, 0x94, 0x4c, 0x59, - 0x7c, 0xb9, 0xa5, 0xd4, 0xbf, 0xdd, 0x52, 0x0a, 0x2a, 0x5b, 0x01, 0x9d, 0xba, 0xff, 0xe7, 0xd3, 0x58, 0x2f, 0x15, - 0x1f, 0x13, 0xc4, 0x40, 0x38, 0x37, 0x3f, 0x01, 0xa9, 0xb1, 0x0c, 0xa2, 0x87, 0xdf, 0x3f, 0x1c, 0x94, 0xfc, 0x96, - 0xe1, 0x8a, 0x5e, 0xfe, 0xd8, 0x0c, 0xa1, 0xb4, 0x0e, 0x11, 0x84, 0xe8, 0x37, 0xcd, 0x95, 0xde, 0x7e, 0x9a, 0xe0, - 0x0c, 0xad, 0xea, 0x0f, 0x2c, 0xbd, 0xba, 0x47, 0x60, 0x7d, 0xed, 0xb7, 0x14, 0x2b, 0xc5, 0xa7, 0x58, 0xff, 0x51, - 0xc4, 0xa6, 0x25, 0x09, 0x6c, 0x82, 0x29, 0x34, 0x1e, 0x48, 0x27, 0x33, 0x3b, 0x91, 0xaa, 0xcf, 0x25, 0x1c, 0x92, - 0x85, 0x7b, 0x48, 0xe6, 0x29, 0xbd, 0x8c, 0x92, 0x9b, 0xf5, 0x8b, 0xd5, 0x76, 0x57, 0x0e, 0xd9, 0x24, 0x34, 0x4e, - 0xbe, 0x51, 0x52, 0x2c, 0xc2, 0xbd, 0x03, 0xe4, 0xff, 0xf2, 0xcf, 0xae, 0xfb, 0x2f, 0xff, 0xfc, 0xc9, 0xaa, 0xd0, - 0x7d, 0x7e, 0x85, 0x79, 0xd9, 0xed, 0xee, 0xdd, 0xb5, 0x7d, 0xa4, 0x2a, 0xce, 0xb7, 0xd7, 0xd9, 0x58, 0x04, 0x78, - 0xbf, 0xb1, 0x04, 0x1b, 0x85, 0x72, 0xf7, 0x59, 0xbf, 0x07, 0x30, 0x98, 0xd7, 0x27, 0x21, 0x83, 0x4a, 0x7f, 0x08, - 0xb4, 0x2b, 0xe4, 0x3d, 0x68, 0x45, 0x7e, 0x3f, 0x86, 0x3f, 0x35, 0x87, 0x3f, 0x08, 0xbe, 0xf2, 0x4f, 0x8c, 0xae, - 0xae, 0x8a, 0x14, 0x47, 0xb3, 0x29, 0x5c, 0xa0, 0xd0, 0xdf, 0x28, 0x51, 0x8a, 0x87, 0xd7, 0x44, 0x3d, 0x71, 0x40, - 0x93, 0x8c, 0xae, 0x5e, 0xc2, 0xad, 0x49, 0xdd, 0xeb, 0x54, 0x3b, 0x78, 0xef, 0x11, 0x0e, 0xd0, 0x45, 0x75, 0x56, - 0xa2, 0xd3, 0x0d, 0xc9, 0x00, 0xa5, 0x60, 0x6e, 0x00, 0x98, 0x78, 0x74, 0xa5, 0xac, 0xcd, 0x73, 0xe9, 0x86, 0xf1, - 0xd6, 0x49, 0x5b, 0xb9, 0x67, 0x2a, 0x48, 0xc7, 0xd6, 0x3b, 0x81, 0x2f, 0x51, 0x99, 0x96, 0xd6, 0xbd, 0x70, 0x75, - 0x81, 0x1d, 0x51, 0xb0, 0x9f, 0x85, 0x1f, 0x2d, 0x1e, 0xc6, 0xf8, 0x76, 0x0b, 0xd4, 0x95, 0xb5, 0xfa, 0xb7, 0x56, - 0x09, 0x56, 0xf5, 0x55, 0x45, 0x1f, 0x10, 0x69, 0x1e, 0x8c, 0xee, 0x88, 0x44, 0x67, 0xf4, 0x93, 0x91, 0xe8, 0xe8, - 0x41, 0x91, 0xe8, 0x8c, 0xfe, 0xd9, 0x91, 0x68, 0x46, 0x8d, 0x48, 0x34, 0x90, 0xe0, 0x2f, 0x0f, 0x0a, 0x68, 0xea, - 0xf0, 0x53, 0x72, 0x93, 0x91, 0x96, 0x32, 0x02, 0xa2, 0x64, 0x02, 0xd1, 0xcc, 0x7f, 0xfb, 0xe0, 0x64, 0x94, 0x4c, - 0xcc, 0xd0, 0x24, 0x5c, 0xfa, 0x0b, 0xb1, 0x48, 0x9c, 0x92, 0xa5, 0xfd, 0xf3, 0x6d, 0xeb, 0xc9, 0xa0, 0xd5, 0x39, - 0x6c, 0x4d, 0x6d, 0xcf, 0x06, 0xa9, 0x2b, 0x0a, 0x9a, 0x9d, 0xc3, 0x43, 0x28, 0xb8, 0x31, 0x0a, 0xda, 0x50, 0xc0, - 0x8c, 0x82, 0x63, 0x28, 0x18, 0x19, 0x05, 0x27, 0x50, 0x10, 0x18, 0x05, 0x8f, 0xa0, 0x60, 0x61, 0xe7, 0x03, 0x56, - 0x84, 0xdb, 0x1f, 0x21, 0x71, 0x3f, 0xc8, 0x5e, 0x5a, 0x3d, 0x1b, 0x11, 0x12, 0x5d, 0xe5, 0x51, 0x71, 0xae, 0xaa, - 0x7e, 0xa4, 0xaf, 0x01, 0xb9, 0xfa, 0xec, 0x0a, 0xe1, 0x88, 0xc0, 0x31, 0x47, 0x0c, 0x46, 0xb9, 0xac, 0x79, 0xa8, - 0x5f, 0xdb, 0x5e, 0x11, 0x93, 0x6e, 0xe2, 0xb6, 0x8e, 0x4a, 0x7b, 0x36, 0xc2, 0xf3, 0xa2, 0xf2, 0x71, 0x2d, 0x50, - 0xdd, 0xc2, 0x0d, 0x1b, 0xe5, 0xf5, 0x36, 0x87, 0x08, 0xcb, 0x1b, 0xc5, 0x9f, 0x0a, 0xf9, 0xe8, 0xf2, 0xe4, 0x1d, - 0x9b, 0x52, 0xfd, 0xbd, 0x15, 0x3d, 0x80, 0x25, 0xe2, 0xf6, 0x9d, 0xb0, 0xbc, 0x13, 0xee, 0x2b, 0x7c, 0x56, 0xde, - 0xa8, 0xf4, 0x8e, 0x13, 0x79, 0x45, 0x45, 0x8a, 0xa5, 0xa1, 0x37, 0xc1, 0xdc, 0x9f, 0x78, 0x10, 0xb8, 0x04, 0x9f, - 0xa9, 0x77, 0x46, 0x08, 0x69, 0xf6, 0xe7, 0xde, 0x57, 0xf8, 0x26, 0xa4, 0xb1, 0xb7, 0xc8, 0x3b, 0x05, 0x01, 0xc8, - 0xb8, 0xe9, 0x3b, 0x5e, 0x5c, 0xc4, 0x27, 0xa8, 0xa2, 0x7c, 0x2d, 0xe1, 0xac, 0x17, 0xd4, 0xb3, 0x23, 0xd4, 0x66, - 0xf8, 0x64, 0xc6, 0x51, 0x72, 0x53, 0xbf, 0xb5, 0x7b, 0xdb, 0xc3, 0x6f, 0x30, 0xbb, 0x22, 0xfc, 0xf6, 0x02, 0x80, - 0x2d, 0x9e, 0xde, 0xf9, 0x93, 0xe2, 0xf7, 0x4b, 0x9a, 0x65, 0xfe, 0x44, 0xd5, 0xdc, 0x1d, 0x6e, 0x13, 0x20, 0x9a, - 0xa1, 0x36, 0x0d, 0x04, 0xc4, 0xc4, 0x00, 0x23, 0xe0, 0xd3, 0x50, 0x21, 0x32, 0x98, 0x7a, 0x35, 0xba, 0x26, 0x70, - 0x55, 0x2d, 0xe2, 0xfe, 0xa4, 0x2c, 0xe8, 0xce, 0x52, 0xaa, 0xe2, 0x76, 0x80, 0xc6, 0xbc, 0xdb, 0x80, 0x02, 0xf9, - 0x7a, 0x47, 0x14, 0x4d, 0x3b, 0x50, 0x76, 0xc7, 0xd2, 0x2c, 0x1d, 0x45, 0x33, 0x33, 0xbf, 0x8a, 0xb4, 0xaf, 0xcd, - 0xd8, 0xcd, 0xe7, 0xad, 0x11, 0xfc, 0x51, 0x91, 0xa1, 0xcf, 0xc7, 0xe3, 0xf1, 0xbd, 0x51, 0xb5, 0xcf, 0x83, 0x31, - 0x6d, 0xd3, 0xe3, 0x0e, 0x64, 0x05, 0xd5, 0x55, 0x2c, 0xa6, 0x95, 0x0b, 0xdc, 0x2d, 0x1f, 0x56, 0x19, 0xc2, 0x36, - 0x3c, 0x5c, 0x3e, 0x3c, 0xc2, 0x96, 0xcf, 0x52, 0xba, 0x9c, 0xfa, 0xe9, 0x84, 0xc5, 0x5e, 0x33, 0x77, 0x17, 0x2a, - 0x24, 0xf5, 0xf9, 0xe9, 0xe9, 0x69, 0xee, 0x06, 0xfa, 0xa9, 0x19, 0x04, 0xb9, 0x3b, 0x5a, 0x16, 0xd3, 0x68, 0x36, - 0xc7, 0xe3, 0xdc, 0x65, 0xba, 0xe0, 0xb0, 0x3d, 0x0a, 0x0e, 0xdb, 0xb9, 0x7b, 0x63, 0xd4, 0xc8, 0x5d, 0xaa, 0x9e, - 0x52, 0x1a, 0x54, 0x52, 0x8b, 0x1e, 0x35, 0x9b, 0xb9, 0x2b, 0x09, 0x6d, 0x09, 0x66, 0xa9, 0xfc, 0xe9, 0xf9, 0x73, - 0x9e, 0x00, 0x73, 0xef, 0x44, 0xdc, 0x19, 0x5c, 0xaa, 0x6b, 0x5b, 0xe4, 0x47, 0x4e, 0x72, 0x34, 0xc4, 0xbf, 0x98, - 0xc1, 0x23, 0x20, 0x66, 0x11, 0x34, 0x8a, 0x74, 0x6c, 0xa9, 0xf2, 0x1a, 0x28, 0x4b, 0xbc, 0xfe, 0x85, 0x44, 0x65, - 0x4c, 0x09, 0x38, 0x19, 0xd4, 0x94, 0xb7, 0x0b, 0xc6, 0xbb, 0xe4, 0x47, 0xfa, 0x69, 0xf9, 0x71, 0xf7, 0x10, 0xf1, - 0x91, 0xfe, 0xe9, 0xe2, 0x23, 0x36, 0xc5, 0x87, 0x64, 0x1e, 0xd7, 0x9c, 0xd8, 0xa3, 0x90, 0x8e, 0x3e, 0x5e, 0x27, - 0xb7, 0x75, 0xd8, 0x12, 0xa9, 0x2d, 0x04, 0xcb, 0xfe, 0xef, 0xcd, 0x94, 0xd1, 0x9d, 0x19, 0x9f, 0x48, 0x11, 0xea, - 0xc3, 0xeb, 0x98, 0xd8, 0xaf, 0xb5, 0x6d, 0x2b, 0x4b, 0xc6, 0x63, 0x62, 0xbf, 0x1e, 0x8f, 0x6d, 0x7d, 0xf8, 0xd4, - 0xe7, 0x54, 0xd4, 0x7a, 0x55, 0x29, 0x11, 0xb5, 0xbe, 0xfa, 0xca, 0x2c, 0x33, 0x0b, 0x54, 0xe8, 0xc9, 0x0c, 0x33, - 0xa9, 0x37, 0x01, 0xcb, 0x60, 0xab, 0xc1, 0x97, 0x5b, 0xaa, 0x97, 0x5f, 0xc6, 0x95, 0x7b, 0xca, 0x0b, 0x80, 0xb7, - 0x5c, 0xae, 0xbe, 0x7e, 0xf3, 0xc2, 0x84, 0xea, 0x44, 0xd0, 0x27, 0x77, 0xdf, 0x04, 0xce, 0x35, 0x47, 0x39, 0xcb, - 0x5e, 0xc7, 0x6b, 0xa7, 0xaa, 0x24, 0x8c, 0x84, 0x98, 0xd3, 0xca, 0x79, 0x32, 0x99, 0x44, 0xf0, 0xf1, 0x9c, 0x65, - 0xe5, 0x42, 0x5e, 0xd9, 0xbc, 0x5f, 0x99, 0xaf, 0x67, 0x36, 0x54, 0xd7, 0xd7, 0x8a, 0x6f, 0x79, 0xc9, 0x6c, 0xfc, - 0x85, 0xfa, 0xa8, 0x93, 0x30, 0x8b, 0x97, 0x8a, 0xc9, 0x2f, 0x65, 0x0e, 0x37, 0xc7, 0x2c, 0x90, 0xcd, 0x59, 0x90, - 0xe7, 0xea, 0xf4, 0x4b, 0xc0, 0xb2, 0x19, 0x5c, 0x14, 0x2b, 0x5b, 0xd2, 0x4f, 0xb1, 0xf0, 0xec, 0xc6, 0x88, 0xef, - 0x54, 0x96, 0x2b, 0xd7, 0x01, 0x1e, 0xe9, 0x30, 0xbf, 0xe6, 0xb9, 0xad, 0xfc, 0xee, 0x1a, 0x89, 0xb6, 0x25, 0xf1, - 0x29, 0x23, 0x4f, 0xc6, 0x0c, 0xc1, 0xf9, 0x5d, 0x2c, 0x88, 0x7e, 0xa5, 0x0b, 0x72, 0x33, 0x7e, 0x29, 0xde, 0x48, - 0x6c, 0x89, 0x68, 0x49, 0x36, 0xf3, 0x63, 0xc9, 0x46, 0x89, 0x2d, 0xf9, 0xc1, 0xfe, 0xb2, 0x5c, 0xf9, 0xdc, 0xd6, - 0x60, 0x4b, 0xe2, 0xed, 0x75, 0x1b, 0xd0, 0xa0, 0x67, 0x55, 0x40, 0x8f, 0x37, 0x82, 0x2c, 0xf7, 0xa7, 0x3b, 0xbc, - 0xbe, 0x72, 0xb3, 0x1b, 0xec, 0x66, 0x37, 0xd6, 0x5f, 0x97, 0xf5, 0x1b, 0x7a, 0xfd, 0x91, 0xf1, 0x3a, 0xf7, 0x67, - 0x75, 0x30, 0x7c, 0x84, 0x73, 0x54, 0xb1, 0x67, 0x91, 0x36, 0x29, 0xef, 0x8e, 0xe8, 0xcc, 0x33, 0xc8, 0x8a, 0x10, - 0xea, 0xbb, 0x17, 0x27, 0x31, 0xed, 0x54, 0xd3, 0x63, 0xcd, 0x20, 0xbb, 0xc6, 0xd6, 0x70, 0x99, 0x40, 0x16, 0x05, - 0xbf, 0xf3, 0x9a, 0x8a, 0xad, 0x37, 0x75, 0x04, 0xbd, 0xb9, 0xb5, 0xbe, 0xa7, 0x90, 0x5b, 0x13, 0xd2, 0x2b, 0xdd, - 0xcc, 0x24, 0xd8, 0x95, 0x09, 0xf0, 0xa9, 0x64, 0x51, 0x70, 0xa9, 0xea, 0xbf, 0x46, 0x96, 0xed, 0x7a, 0xb1, 0x48, - 0x16, 0x7d, 0x08, 0x64, 0x9e, 0x3f, 0xe6, 0x34, 0xc5, 0x0f, 0xa9, 0x79, 0x2d, 0xce, 0x75, 0x2d, 0x41, 0xcc, 0x78, - 0xad, 0xd3, 0xd9, 0xed, 0xc3, 0xbb, 0xbf, 0x7f, 0xfa, 0xb9, 0xc2, 0x91, 0xbe, 0xe7, 0xc8, 0xb6, 0x3b, 0xb0, 0x11, - 0x22, 0xff, 0xce, 0x63, 0xb1, 0x90, 0x79, 0xd7, 0xe0, 0x17, 0xed, 0xcc, 0x12, 0x95, 0xf5, 0x9c, 0xd2, 0x48, 0x7c, - 0xd6, 0x50, 0x2d, 0xc5, 0xe1, 0xc9, 0xec, 0x56, 0xaf, 0x46, 0x6b, 0x2d, 0x9b, 0xf9, 0x4f, 0x4d, 0x5a, 0xde, 0x9d, - 0x25, 0x5d, 0x4d, 0xbc, 0x3d, 0x9e, 0xdd, 0x76, 0xa4, 0xa0, 0xad, 0xa7, 0x12, 0xaa, 0xe6, 0xec, 0xd6, 0x4c, 0xdb, - 0x2e, 0x3b, 0xb2, 0xdc, 0xc3, 0xcc, 0xa2, 0x7e, 0x46, 0x3b, 0x70, 0x91, 0x3b, 0x1b, 0xf9, 0x91, 0x12, 0xe6, 0x53, - 0x16, 0x04, 0x11, 0xed, 0x68, 0x79, 0x6d, 0xb5, 0x4e, 0x20, 0xeb, 0xd9, 0x5c, 0xb2, 0xea, 0xaa, 0x18, 0xc8, 0x2b, - 0xf0, 0xe4, 0x5f, 0x67, 0x49, 0x04, 0x5f, 0x51, 0xd9, 0x8a, 0x4e, 0x95, 0x0e, 0xdc, 0x2c, 0x91, 0x27, 0x7e, 0x57, - 0xe7, 0x72, 0xdc, 0xfc, 0x4b, 0x47, 0x2c, 0x78, 0xb3, 0xc3, 0x93, 0x99, 0x57, 0x3f, 0xac, 0x4e, 0x04, 0x5e, 0x15, - 0x53, 0xc0, 0x5b, 0xa6, 0x85, 0x41, 0x5a, 0x49, 0x3e, 0x6d, 0xb9, 0x2d, 0x55, 0x26, 0x3a, 0x80, 0xb4, 0xb1, 0xa2, - 0x28, 0xaf, 0x4e, 0xe6, 0xdf, 0x66, 0xb7, 0x3c, 0xde, 0xbe, 0x5b, 0x1e, 0xeb, 0xdd, 0x72, 0x3f, 0xc5, 0x7e, 0x3e, - 0x6e, 0xc1, 0x9f, 0x4e, 0x39, 0x21, 0xaf, 0x69, 0x1d, 0xce, 0x6e, 0x2d, 0xd0, 0xd3, 0xea, 0xed, 0xd9, 0xad, 0x4c, - 0x5a, 0x87, 0xd8, 0x4d, 0x13, 0xd2, 0xb8, 0x71, 0xd3, 0x82, 0x42, 0xf8, 0xdb, 0xac, 0xbc, 0x6a, 0x1d, 0xc1, 0x3b, - 0x68, 0x75, 0xbc, 0xf9, 0xae, 0x7d, 0xff, 0xa6, 0xf5, 0xe2, 0x84, 0x3b, 0x9e, 0xe6, 0xc6, 0xc8, 0xe5, 0xfe, 0xf5, - 0x35, 0x0d, 0xbc, 0x71, 0x32, 0x9a, 0x67, 0xff, 0xa4, 0xe0, 0x57, 0x48, 0xbc, 0x77, 0x4b, 0xaf, 0xf5, 0xa3, 0x9b, - 0xca, 0x14, 0x7a, 0xdd, 0xc3, 0xb2, 0x58, 0x27, 0x2f, 0x1b, 0xf9, 0x11, 0x75, 0xda, 0xee, 0xd1, 0x96, 0x4d, 0xf0, - 0xef, 0xb2, 0x36, 0x5b, 0x27, 0xf3, 0x47, 0x91, 0x71, 0x2f, 0x12, 0x7e, 0x13, 0x0e, 0xcc, 0x35, 0x6c, 0x9e, 0x6e, - 0x07, 0x77, 0xa0, 0x47, 0x1a, 0x6a, 0xa1, 0xa0, 0xe4, 0x4e, 0x40, 0xc7, 0xfe, 0x3c, 0xe2, 0xf7, 0xf7, 0xba, 0x8b, - 0x32, 0x36, 0x7a, 0xbd, 0x87, 0xa1, 0x97, 0x75, 0x1f, 0xc8, 0xa5, 0x3f, 0x7f, 0x7c, 0x04, 0x7f, 0x64, 0xfe, 0xd7, - 0x5d, 0xa9, 0xab, 0x4b, 0xbb, 0x17, 0x74, 0xf5, 0xfd, 0x8a, 0x32, 0x2e, 0x45, 0xb8, 0xd0, 0xc7, 0x1f, 0x5a, 0x1b, - 0xb4, 0xca, 0x07, 0x55, 0x57, 0x5a, 0xd6, 0x6f, 0xaa, 0xfd, 0xdb, 0x3a, 0x7f, 0x60, 0xdd, 0x91, 0xd4, 0x5c, 0xab, - 0x75, 0xd5, 0x77, 0x1d, 0x37, 0x2a, 0x6b, 0x8c, 0x8b, 0xfa, 0xfb, 0xe4, 0xae, 0x30, 0x51, 0x64, 0x34, 0x16, 0xac, - 0x94, 0x7d, 0x69, 0xa5, 0x24, 0x94, 0x5c, 0x75, 0xfb, 0xb7, 0xd3, 0xc8, 0x5a, 0xc8, 0xf3, 0xa7, 0xc4, 0x6e, 0xb9, - 0x4d, 0xdb, 0x12, 0x79, 0x00, 0x70, 0x0d, 0xbe, 0x2d, 0xbe, 0x17, 0x6c, 0xf7, 0x41, 0xd3, 0x5a, 0x4c, 0x84, 0x66, - 0xf7, 0xc2, 0xbf, 0xa3, 0xe9, 0x65, 0xdb, 0xb6, 0xc0, 0x4f, 0x53, 0x97, 0x29, 0x13, 0xa2, 0xcc, 0x6a, 0xdb, 0xd6, - 0xed, 0x34, 0x8a, 0x33, 0x62, 0x87, 0x9c, 0xcf, 0x3c, 0xf9, 0x41, 0xe1, 0x9b, 0x43, 0x37, 0x49, 0x27, 0x8d, 0x76, - 0xb3, 0xd9, 0x84, 0x1b, 0x75, 0x6d, 0x6b, 0xc1, 0xe8, 0xcd, 0x93, 0xe4, 0x96, 0xd8, 0x4d, 0xab, 0x69, 0xb5, 0xda, - 0xa7, 0x56, 0xab, 0x7d, 0xe4, 0x9e, 0x9c, 0xda, 0xbd, 0xcf, 0x2c, 0xab, 0x1b, 0xd0, 0x71, 0x06, 0x3f, 0x2c, 0xab, - 0x2b, 0x14, 0x2f, 0xf9, 0xdb, 0xb2, 0xdc, 0x51, 0x94, 0xd5, 0x5b, 0xd6, 0x52, 0x3d, 0x5a, 0x16, 0x9c, 0xd2, 0xf5, - 0xac, 0xcf, 0xc7, 0xed, 0xf1, 0xd1, 0xf8, 0x71, 0x47, 0x15, 0xe7, 0x9f, 0x55, 0xaa, 0x63, 0xf9, 0x7f, 0xdb, 0x68, - 0x96, 0xf1, 0x34, 0xf9, 0x48, 0x55, 0x4e, 0xa2, 0x05, 0xa2, 0x67, 0x6b, 0xd3, 0xf6, 0xe6, 0x48, 0xad, 0xd3, 0xeb, - 0xd1, 0xb8, 0x5d, 0x56, 0x17, 0x30, 0x36, 0x0a, 0x20, 0xbb, 0x0d, 0x0d, 0x7a, 0xd7, 0x44, 0x53, 0xab, 0xbe, 0x0d, - 0x51, 0x2d, 0x5b, 0xcd, 0x71, 0xa2, 0xe7, 0xd7, 0x85, 0x43, 0x21, 0x5a, 0x57, 0x15, 0x10, 0xd8, 0x56, 0x40, 0xec, - 0x97, 0xad, 0xf6, 0x29, 0x6e, 0xb5, 0x4e, 0xdc, 0x93, 0xd3, 0x51, 0x13, 0x1f, 0xb9, 0x47, 0xf5, 0x43, 0xf7, 0x04, - 0x9f, 0xd6, 0x4f, 0xf1, 0xe9, 0xf3, 0xd3, 0x51, 0xfd, 0xc8, 0x3d, 0xc2, 0xcd, 0xfa, 0x29, 0x14, 0xd6, 0x4f, 0xeb, - 0xa7, 0x8b, 0xfa, 0xd1, 0xe9, 0xa8, 0x29, 0x4a, 0xdb, 0xee, 0xf1, 0x71, 0xbd, 0xd5, 0x74, 0x8f, 0x8f, 0xf1, 0xb1, - 0x7b, 0x72, 0x52, 0x6f, 0x1d, 0xba, 0x27, 0x27, 0x2f, 0x8e, 0x4f, 0xdd, 0x43, 0x78, 0x77, 0x78, 0x38, 0x3a, 0x74, - 0x5b, 0xad, 0x3a, 0xfc, 0x83, 0x4f, 0xdd, 0xb6, 0xfc, 0xd1, 0x6a, 0xb9, 0x87, 0x2d, 0xdc, 0x8c, 0x8e, 0xdb, 0xee, - 0xc9, 0x63, 0x2c, 0xfe, 0x15, 0xd5, 0xb0, 0xf8, 0x07, 0xba, 0xc1, 0x8f, 0xdd, 0xf6, 0x89, 0xfc, 0x25, 0x3a, 0x5c, - 0x1c, 0x9d, 0xfe, 0x64, 0x37, 0x76, 0xce, 0xa1, 0x25, 0xe7, 0x70, 0x7a, 0xec, 0x1e, 0x1e, 0xe2, 0xa3, 0x96, 0x7b, - 0x7a, 0x18, 0xd6, 0x8f, 0xda, 0xee, 0xc9, 0xa3, 0x51, 0xbd, 0xe5, 0x3e, 0x7a, 0x84, 0x9b, 0xf5, 0x43, 0xb7, 0x8d, - 0x5b, 0xee, 0xd1, 0xa1, 0xf8, 0x71, 0xe8, 0xb6, 0x17, 0x8f, 0x1e, 0xbb, 0x27, 0xc7, 0xe1, 0x89, 0x7b, 0xf4, 0xfd, - 0xd1, 0xa9, 0xdb, 0x3e, 0x0c, 0x0f, 0x4f, 0xdc, 0xf6, 0xa3, 0xc5, 0x89, 0x7b, 0x14, 0xd6, 0xdb, 0x27, 0xf7, 0xb6, - 0x6c, 0xb5, 0x5d, 0xc0, 0x91, 0x78, 0x0d, 0x2f, 0xb0, 0x7a, 0x01, 0x7f, 0x43, 0xd1, 0xf6, 0xdf, 0xb1, 0x9b, 0x6c, - 0xb3, 0xe9, 0x63, 0xf7, 0xf4, 0xd1, 0x48, 0x56, 0x87, 0x82, 0xba, 0xae, 0x01, 0x4d, 0x16, 0x75, 0x39, 0xac, 0xe8, - 0xae, 0xae, 0x3b, 0xd2, 0x7f, 0xd5, 0x60, 0x8b, 0x3a, 0x0c, 0x2c, 0xc7, 0xfd, 0x0f, 0xed, 0xa7, 0x58, 0xf2, 0x6e, - 0x63, 0x22, 0x49, 0x7f, 0xd2, 0xfb, 0x4c, 0x5e, 0x97, 0xfd, 0xd9, 0x15, 0x8e, 0x76, 0x39, 0x3e, 0xfc, 0x4f, 0x3b, - 0x3e, 0x42, 0xfa, 0x10, 0xcf, 0x87, 0xff, 0xa7, 0x7b, 0x3e, 0xa2, 0x75, 0xc7, 0xf9, 0x0d, 0xdf, 0x70, 0x70, 0xac, - 0x5b, 0xc5, 0x2f, 0xb8, 0x33, 0x48, 0xe0, 0xc3, 0x6c, 0x79, 0xe7, 0x86, 0x93, 0x90, 0x9a, 0x7e, 0xa0, 0x04, 0x58, - 0xec, 0x0d, 0x97, 0x3c, 0x76, 0xb4, 0x0b, 0x21, 0xc1, 0xa7, 0x11, 0xf2, 0xfd, 0x43, 0xf0, 0x11, 0xfc, 0xe9, 0xf8, - 0x18, 0x99, 0xf8, 0xa8, 0xf8, 0xf2, 0x85, 0xa7, 0x41, 0x78, 0x0a, 0x2e, 0xc4, 0xb3, 0x03, 0xa7, 0xd2, 0x6a, 0x76, - 0x83, 0x42, 0x51, 0x66, 0xcb, 0xc8, 0xd7, 0xdb, 0xdf, 0x12, 0x76, 0x90, 0x47, 0x50, 0x89, 0xad, 0xdc, 0x32, 0x33, - 0x21, 0x75, 0xd4, 0x43, 0x21, 0x94, 0xda, 0x6e, 0xd3, 0x6d, 0x16, 0x2e, 0x1d, 0x38, 0x76, 0x4c, 0x96, 0x09, 0xf7, - 0xe1, 0x13, 0xc0, 0x51, 0x32, 0x11, 0x1f, 0x0b, 0x86, 0xcf, 0x33, 0x40, 0xd2, 0xcf, 0x48, 0x7e, 0x19, 0x03, 0xce, - 0x4d, 0x28, 0x47, 0x8f, 0x9f, 0x7e, 0xfc, 0x0e, 0x8e, 0xfe, 0xea, 0xa8, 0xc4, 0x14, 0xbc, 0x1d, 0x2f, 0x69, 0xc0, - 0x7c, 0xc7, 0x76, 0x66, 0x29, 0x1d, 0xd3, 0x34, 0xab, 0x57, 0xce, 0xc3, 0x8a, 0xa3, 0xb0, 0xc8, 0xd6, 0xdf, 0x9a, - 0x4d, 0xe1, 0xba, 0x71, 0x32, 0x50, 0xfe, 0x46, 0x5b, 0x19, 0x60, 0x76, 0x8e, 0x75, 0x49, 0x0a, 0xb2, 0xb6, 0x54, - 0xda, 0x6c, 0xa9, 0xb5, 0xb5, 0xdc, 0xf6, 0x31, 0xb2, 0x44, 0x31, 0x5c, 0xe4, 0xfc, 0xa3, 0x53, 0x3f, 0x6c, 0xfe, - 0x05, 0x19, 0xcd, 0x8a, 0x8e, 0x86, 0xca, 0xdd, 0x16, 0x97, 0x1f, 0xe9, 0xae, 0x1e, 0x56, 0xb6, 0x25, 0x45, 0x7c, - 0x2e, 0xe7, 0x6e, 0xa3, 0x4e, 0xac, 0x22, 0xdc, 0xf2, 0xca, 0x8d, 0x31, 0x9b, 0x38, 0xe6, 0x27, 0x98, 0xe5, 0x45, - 0xd1, 0xe2, 0xcb, 0xed, 0x28, 0x2f, 0xab, 0xc4, 0x68, 0x29, 0xe2, 0x2d, 0x2c, 0xb6, 0xe2, 0xd5, 0xca, 0x89, 0xc1, - 0x45, 0x4e, 0x0c, 0x9c, 0xc2, 0x33, 0xaa, 0x20, 0x39, 0xc6, 0x05, 0x40, 0x02, 0xc1, 0x24, 0x96, 0xff, 0x97, 0xc5, - 0xfa, 0x87, 0x72, 0x7c, 0xb9, 0x91, 0x1f, 0x4f, 0x80, 0x0a, 0xfd, 0x78, 0xb2, 0xe1, 0x56, 0x93, 0x21, 0xa3, 0xb5, - 0xd2, 0xb2, 0xab, 0xd2, 0x7d, 0x96, 0x3d, 0xb9, 0x7b, 0xa7, 0x6e, 0x94, 0xb3, 0xc1, 0x3b, 0x2d, 0x22, 0x1c, 0xe5, - 0xed, 0xd7, 0x35, 0xf2, 0x45, 0x77, 0x4a, 0xb9, 0x2f, 0xf3, 0x35, 0x41, 0x9f, 0x80, 0x63, 0xc8, 0x96, 0xb2, 0x46, - 0x89, 0x2d, 0xa4, 0x3b, 0x91, 0x67, 0x68, 0xa4, 0xa8, 0xc7, 0x96, 0xba, 0x8a, 0xa1, 0x2e, 0x96, 0x86, 0xb4, 0xb0, - 0xf4, 0xc7, 0x8c, 0x7c, 0x91, 0x91, 0x4f, 0xe2, 0xc4, 0xee, 0x7d, 0x51, 0x7c, 0x4e, 0x76, 0xd7, 0x22, 0x44, 0x2c, - 0xfe, 0x38, 0x48, 0x69, 0xf4, 0x4f, 0xe4, 0x0b, 0x36, 0x4a, 0xe2, 0x2f, 0x86, 0x36, 0xea, 0x70, 0x37, 0x4c, 0xe9, - 0x98, 0x7c, 0x01, 0x32, 0xde, 0x13, 0xd6, 0x07, 0x30, 0xc2, 0xda, 0xed, 0x34, 0xc2, 0x42, 0x63, 0x7a, 0x80, 0x42, - 0x24, 0xc1, 0xb5, 0xdb, 0xc7, 0xb6, 0x25, 0x6d, 0x62, 0xf1, 0xbb, 0x27, 0xc5, 0xa9, 0x50, 0x02, 0xac, 0x56, 0xdb, - 0x3d, 0x0e, 0xdb, 0xee, 0xe3, 0xc5, 0x23, 0xf7, 0x34, 0x6c, 0x3d, 0x5a, 0xd4, 0xe1, 0xff, 0xb6, 0xfb, 0x38, 0xaa, - 0xb7, 0xdd, 0xc7, 0xf0, 0xf7, 0xfb, 0x23, 0xf7, 0x38, 0xac, 0xb7, 0xdc, 0xd3, 0xc5, 0xa1, 0x7b, 0xf8, 0xa2, 0xd5, - 0x76, 0x0f, 0xad, 0x96, 0x25, 0xdb, 0x01, 0xbb, 0x96, 0xdc, 0xf9, 0x8b, 0xb5, 0x0d, 0xb1, 0x25, 0x1c, 0x27, 0x73, - 0x4e, 0x6d, 0xec, 0x14, 0x1f, 0xad, 0x54, 0xfb, 0x53, 0x39, 0xeb, 0x9e, 0xfa, 0x29, 0x7c, 0x39, 0xa8, 0xba, 0x77, - 0x2b, 0xef, 0x70, 0x85, 0x5f, 0x6c, 0x19, 0x02, 0x76, 0xb8, 0x8d, 0xcd, 0xbb, 0x0c, 0xe0, 0x22, 0x00, 0x71, 0xd1, - 0xba, 0xbe, 0x6f, 0x72, 0x37, 0x69, 0xcb, 0x8a, 0xfa, 0x4e, 0x4b, 0xc1, 0x2c, 0x98, 0xf8, 0xa4, 0x85, 0x18, 0xe4, - 0x9b, 0x20, 0x5f, 0x1f, 0x1f, 0x52, 0x5f, 0xd3, 0xc4, 0xb8, 0xce, 0x81, 0x96, 0x07, 0x36, 0x02, 0x06, 0x17, 0x70, - 0xe4, 0xb9, 0x06, 0xbd, 0xe2, 0xa6, 0x2d, 0xb1, 0x24, 0xf8, 0x05, 0xcd, 0xfa, 0x36, 0x14, 0xd9, 0x9e, 0x2d, 0x5c, - 0x7c, 0x76, 0xf1, 0xf5, 0xa4, 0x82, 0xb0, 0xcb, 0x02, 0x2c, 0x0e, 0x5d, 0xc1, 0xae, 0x05, 0xfc, 0xd8, 0xe8, 0xe0, - 0x60, 0xe7, 0x7e, 0x11, 0x0a, 0x24, 0xcc, 0xb5, 0xfc, 0xe8, 0x8a, 0xc9, 0x8a, 0x6c, 0x13, 0xd1, 0x45, 0xbf, 0x02, - 0x85, 0x48, 0xe1, 0xe9, 0x9a, 0xfa, 0xdc, 0xf5, 0x63, 0x99, 0x44, 0x63, 0x30, 0x2c, 0xdc, 0xa2, 0x87, 0x28, 0x4f, - 0xb8, 0x6f, 0x7c, 0x58, 0x59, 0xed, 0xf3, 0x84, 0xfb, 0xfa, 0x70, 0xb2, 0x71, 0x0f, 0x13, 0x38, 0x7a, 0xc3, 0x76, - 0xef, 0xf5, 0xbb, 0x33, 0x4b, 0x6e, 0xcf, 0x6e, 0x23, 0x6c, 0xf7, 0xba, 0xc2, 0x67, 0x22, 0x0f, 0xea, 0x11, 0x79, - 0x50, 0xcf, 0x52, 0x67, 0x33, 0x21, 0x92, 0x96, 0x37, 0xe4, 0xb4, 0x85, 0xcd, 0x20, 0xbd, 0xbd, 0xd3, 0x79, 0xc4, - 0x19, 0x5c, 0x1a, 0xde, 0x10, 0xa7, 0xf4, 0x60, 0xc1, 0x8a, 0x3c, 0x6c, 0xa5, 0x1d, 0x5e, 0xf3, 0x58, 0xfb, 0x86, - 0xc7, 0x2c, 0xa2, 0x3a, 0xf3, 0x5a, 0x75, 0x55, 0x9c, 0x14, 0xd8, 0xac, 0x9d, 0xcd, 0xaf, 0xa7, 0x8c, 0xdb, 0xfa, - 0x3c, 0xc3, 0x7b, 0xd5, 0xa0, 0x2b, 0x86, 0xea, 0x5d, 0xe5, 0xca, 0x79, 0xad, 0x3f, 0x8f, 0x54, 0x5d, 0x52, 0x35, - 0x7b, 0x25, 0x21, 0xe0, 0x84, 0x5c, 0x78, 0xd8, 0x2b, 0xdc, 0xc5, 0xe6, 0xbb, 0xbc, 0xdb, 0x08, 0x0f, 0x7b, 0x57, - 0xde, 0x4c, 0xf5, 0xf7, 0x22, 0x99, 0x6c, 0xef, 0x2b, 0x4a, 0x26, 0x7d, 0x71, 0x14, 0x44, 0x9e, 0x99, 0xd6, 0xca, - 0x6f, 0x12, 0xd9, 0xbd, 0xae, 0x52, 0x06, 0x2c, 0x11, 0x58, 0xb7, 0x8f, 0x9b, 0xfa, 0x74, 0x49, 0x94, 0x4c, 0x60, - 0x43, 0xca, 0x26, 0xc6, 0x20, 0x15, 0x8f, 0x7b, 0xd8, 0xea, 0x75, 0x7d, 0x4b, 0xf0, 0x16, 0xc1, 0x3c, 0x32, 0xaf, - 0x01, 0x8d, 0xc3, 0x64, 0x4a, 0x5d, 0x96, 0x34, 0x6e, 0xe8, 0x75, 0xdd, 0x9f, 0xb1, 0xd2, 0xbd, 0x0d, 0x4a, 0x47, - 0x31, 0x64, 0xa2, 0x3d, 0xe2, 0xea, 0xec, 0x55, 0xbb, 0x74, 0xb7, 0x1d, 0x81, 0xcd, 0xa3, 0x5d, 0x73, 0xc2, 0x27, - 0x67, 0x80, 0x95, 0xf4, 0xba, 0x0d, 0x7f, 0x0d, 0x23, 0x82, 0xdf, 0xe7, 0xca, 0xd1, 0x0e, 0x86, 0x0d, 0xd0, 0x9b, - 0x6d, 0x49, 0x71, 0xa0, 0x1d, 0xf2, 0x4a, 0x50, 0xe7, 0x76, 0xef, 0x5f, 0xff, 0xc7, 0xff, 0x52, 0x3e, 0xf6, 0x6e, - 0x23, 0x6c, 0xe9, 0xbe, 0xd6, 0x56, 0x25, 0xef, 0xc2, 0xf9, 0xd0, 0x32, 0x28, 0x4c, 0x6f, 0xeb, 0x93, 0x94, 0x05, - 0xf5, 0xd0, 0x8f, 0xc6, 0x76, 0x6f, 0x37, 0x36, 0xcd, 0x63, 0x5b, 0x0a, 0xea, 0x6a, 0x11, 0xd0, 0xeb, 0xef, 0x3a, - 0x78, 0xa4, 0xcf, 0xaf, 0x88, 0xad, 0x6d, 0x1e, 0x43, 0x2a, 0x77, 0x5f, 0xe5, 0x28, 0x52, 0xac, 0xbe, 0xb9, 0xa6, - 0x38, 0x60, 0x5c, 0x39, 0x81, 0x94, 0xdb, 0x56, 0x11, 0xd4, 0xfa, 0xbf, 0xff, 0xf3, 0xbf, 0xfc, 0x37, 0xfd, 0x08, - 0xb1, 0xaa, 0x7f, 0xfd, 0xef, 0xff, 0xf9, 0xff, 0xfc, 0xef, 0xff, 0x0a, 0xa7, 0x56, 0x54, 0x3c, 0x4b, 0x30, 0x15, - 0xab, 0x0c, 0x66, 0x49, 0xee, 0x62, 0x41, 0x62, 0xe7, 0x94, 0x65, 0x9c, 0x8d, 0xaa, 0x67, 0x92, 0x2e, 0xc4, 0x80, - 0x62, 0x67, 0x2a, 0xe8, 0xc4, 0x0e, 0xcf, 0x4b, 0x82, 0xaa, 0xa0, 0x5c, 0x10, 0x6e, 0xde, 0x6d, 0x00, 0xbe, 0x1f, - 0x76, 0x8c, 0xd3, 0x2d, 0x96, 0x63, 0xa9, 0xc9, 0x04, 0x4a, 0xf2, 0xb2, 0xdc, 0x82, 0xd8, 0xca, 0x12, 0x1e, 0xbd, - 0xb6, 0x51, 0x2c, 0x56, 0xaf, 0xd2, 0xa6, 0xf3, 0x61, 0x9e, 0x71, 0x36, 0x06, 0x94, 0x4b, 0x3f, 0xb1, 0x08, 0x63, - 0xd7, 0x41, 0x57, 0x8c, 0xee, 0x72, 0xd1, 0x8b, 0x24, 0xd0, 0xa3, 0xd3, 0xbf, 0xe4, 0x5f, 0x4e, 0x41, 0x23, 0xb3, - 0x9c, 0xa9, 0x7f, 0xab, 0xcc, 0xf3, 0x93, 0x66, 0x73, 0x76, 0x8b, 0x96, 0xe5, 0x08, 0x78, 0xd7, 0x60, 0x82, 0x8e, - 0xcd, 0x0e, 0x45, 0xfc, 0xbb, 0x70, 0x63, 0x37, 0x2d, 0xf0, 0x85, 0x5b, 0xcd, 0x3c, 0xff, 0xeb, 0x52, 0x78, 0x52, - 0xd9, 0xaf, 0x10, 0xa7, 0x56, 0x4e, 0xe7, 0xeb, 0xc4, 0x9c, 0xdc, 0xd2, 0x68, 0xd5, 0x96, 0xad, 0xc2, 0xd6, 0xe6, - 0xe9, 0x44, 0x33, 0xce, 0x6e, 0x46, 0xc8, 0x8f, 0x20, 0xe6, 0x1d, 0xb6, 0x70, 0xd8, 0x5e, 0x16, 0xdd, 0x73, 0x9e, - 0x4c, 0xcd, 0xc0, 0x3a, 0xf5, 0xe9, 0x88, 0x8e, 0xb5, 0xb3, 0x5e, 0xbd, 0x97, 0x41, 0xf3, 0x3c, 0x3c, 0xdc, 0x32, - 0x96, 0x02, 0x49, 0x04, 0xd4, 0xad, 0x66, 0xfe, 0x39, 0xec, 0xc0, 0xe5, 0x38, 0x4a, 0x7c, 0xee, 0x09, 0x82, 0xed, - 0x98, 0xe1, 0x79, 0x1f, 0x78, 0x52, 0xb2, 0x34, 0xe0, 0xe9, 0xc8, 0xaa, 0xe0, 0x36, 0xaf, 0x9e, 0x21, 0xcd, 0x5d, - 0xd1, 0xdc, 0xec, 0x4a, 0x7a, 0xdd, 0xbe, 0x57, 0x51, 0xef, 0xb7, 0x15, 0x77, 0x95, 0x12, 0x48, 0x6d, 0xb4, 0xfd, - 0xbd, 0x94, 0xeb, 0xf2, 0xed, 0x77, 0xdc, 0xb1, 0x05, 0x98, 0xf6, 0x7a, 0x2d, 0x51, 0x08, 0xb5, 0xde, 0x92, 0xef, - 0x0b, 0x93, 0xc9, 0x9f, 0xcd, 0x44, 0x45, 0xd4, 0xe9, 0x36, 0xa4, 0xa6, 0x0b, 0xdc, 0x43, 0xa4, 0x74, 0xc8, 0x0c, - 0x0a, 0x55, 0x49, 0x6d, 0x05, 0xf9, 0x4b, 0xe5, 0x56, 0xc0, 0xb7, 0xf8, 0x7a, 0xff, 0x0f, 0x85, 0xa3, 0x0b, 0x12, - 0x20, 0x8b, 0x00, 0x00}; + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xcd, 0x7d, 0xdb, 0x76, 0xdb, 0xc6, 0xb2, 0xe0, 0xf3, + 0xe4, 0x2b, 0x20, 0x44, 0xc7, 0x06, 0xb6, 0x9a, 0x10, 0x49, 0x49, 0xb6, 0x0c, 0x0a, 0xe4, 0xb6, 0x65, 0x3b, 0x76, + 0xe2, 0x5b, 0x2c, 0x3b, 0xd9, 0x89, 0xa2, 0x6d, 0x41, 0x64, 0x93, 0x84, 0x0d, 0x02, 0x0c, 0xd0, 0xd4, 0x25, 0x14, + 0xce, 0x9a, 0x0f, 0x98, 0xb5, 0x66, 0xad, 0x79, 0x9a, 0x97, 0x59, 0x73, 0x1e, 0xe6, 0x23, 0xe6, 0xf9, 0x7c, 0xca, + 0xf9, 0x81, 0x99, 0x4f, 0x98, 0xaa, 0xea, 0x6e, 0xa0, 0xc1, 0x8b, 0x2c, 0x27, 0xd9, 0xe7, 0xcc, 0x4a, 0x2c, 0x12, + 0x7d, 0xad, 0xae, 0xae, 0xae, 0x7b, 0x83, 0x07, 0x1b, 0x83, 0xb4, 0x2f, 0xae, 0xa6, 0xdc, 0x1a, 0x8b, 0x49, 0xdc, + 0x3d, 0x50, 0x7f, 0x79, 0x38, 0xe8, 0x1e, 0xc4, 0x51, 0xf2, 0xc9, 0xca, 0x78, 0x1c, 0x44, 0xfd, 0x34, 0xb1, 0xc6, + 0x19, 0x1f, 0x06, 0x83, 0x50, 0x84, 0x7e, 0x34, 0x09, 0x47, 0xdc, 0xda, 0xee, 0x1e, 0x4c, 0xb8, 0x08, 0xad, 0xfe, + 0x38, 0xcc, 0x72, 0x2e, 0x82, 0xf7, 0xef, 0x9e, 0x36, 0xf6, 0xbb, 0x07, 0x79, 0x3f, 0x8b, 0xa6, 0xc2, 0xc2, 0x21, + 0x83, 0x49, 0x3a, 0x98, 0xc5, 0xbc, 0xbb, 0xbd, 0x7d, 0x71, 0x71, 0xe1, 0x7d, 0xcc, 0xbf, 0x82, 0x61, 0x72, 0x61, + 0xbd, 0x08, 0x2e, 0xa2, 0x64, 0x90, 0x5e, 0xb0, 0x48, 0x04, 0x2f, 0xbc, 0xa3, 0x71, 0x08, 0xdf, 0xdf, 0xa6, 0xa9, + 0xb8, 0x73, 0xc7, 0x91, 0x8f, 0x57, 0x87, 0x47, 0x47, 0x41, 0x10, 0x9c, 0xa7, 0xd1, 0xc0, 0x6a, 0x5e, 0x5f, 0x57, + 0x85, 0x5e, 0x12, 0x8a, 0xe8, 0x9c, 0xcb, 0x2e, 0xee, 0x9d, 0x3b, 0x36, 0x7c, 0x4e, 0x05, 0x1f, 0x1c, 0x89, 0xab, + 0x18, 0x4a, 0x39, 0x17, 0xb9, 0x1d, 0x25, 0xd6, 0xe3, 0xb4, 0x3f, 0x9b, 0xf0, 0x44, 0x78, 0xd3, 0x2c, 0x15, 0x29, + 0x42, 0x02, 0x4d, 0x33, 0x3e, 0x8d, 0xc3, 0x3e, 0xc7, 0x7a, 0x18, 0xa9, 0xea, 0x51, 0x35, 0x62, 0xb9, 0x08, 0x8e, + 0xae, 0x26, 0x67, 0x69, 0xec, 0xb8, 0x2c, 0x14, 0x41, 0xc2, 0x2f, 0xac, 0x1f, 0x79, 0xf8, 0xe9, 0x65, 0x38, 0xed, + 0xf4, 0xe3, 0x30, 0xcf, 0xad, 0x4b, 0x31, 0xa7, 0x25, 0x64, 0xb3, 0xbe, 0x48, 0x33, 0x47, 0x30, 0xce, 0x22, 0x77, + 0x1e, 0x0d, 0x1d, 0x31, 0x8e, 0x72, 0xef, 0xc3, 0x66, 0x3f, 0xcf, 0xdf, 0xf2, 0x7c, 0x16, 0x8b, 0xcd, 0x60, 0xa3, + 0xc9, 0xa2, 0x8d, 0x20, 0xc8, 0x85, 0x2b, 0xc6, 0x59, 0x7a, 0x61, 0x3d, 0xc9, 0x32, 0xe8, 0x61, 0xc3, 0xd4, 0xb2, + 0x85, 0x15, 0xe5, 0x56, 0x92, 0x0a, 0xab, 0x1c, 0x2f, 0x3c, 0x8b, 0xb9, 0x67, 0xbd, 0xcf, 0xb9, 0x75, 0x3a, 0x4b, + 0xf2, 0x70, 0xc8, 0xa1, 0xe9, 0xa9, 0x95, 0x66, 0xd6, 0x29, 0x8c, 0x7a, 0x6a, 0x45, 0xd0, 0x0c, 0x36, 0xc5, 0xb3, + 0xdd, 0x0e, 0x4d, 0x06, 0x85, 0xef, 0xf8, 0xa5, 0x08, 0x04, 0xa3, 0x47, 0x11, 0xf0, 0x62, 0xc4, 0x85, 0x95, 0x97, + 0xeb, 0x72, 0xdc, 0x79, 0x0c, 0x05, 0xd0, 0x02, 0xeb, 0xd3, 0x8e, 0xc4, 0x3d, 0x97, 0x8f, 0xa2, 0x03, 0x40, 0x47, + 0x80, 0x71, 0x51, 0xe2, 0xd9, 0x95, 0x4b, 0xb3, 0xa2, 0x80, 0x6f, 0xe8, 0xb2, 0x3b, 0x77, 0xb8, 0x17, 0xf3, 0x64, + 0x24, 0xc6, 0xd0, 0xac, 0xd5, 0x89, 0x60, 0x87, 0x44, 0x10, 0x0a, 0x0f, 0x66, 0x72, 0xb8, 0xeb, 0xb2, 0xaa, 0x37, + 0xd4, 0x48, 0x24, 0xa4, 0x81, 0x44, 0x5c, 0x0d, 0xc7, 0xae, 0xa7, 0xb0, 0x7f, 0x74, 0x95, 0xf4, 0x1d, 0x13, 0x7e, + 0x97, 0xc1, 0xa0, 0x30, 0x62, 0x8e, 0x23, 0x32, 0xe1, 0xba, 0x45, 0xc6, 0xc5, 0x2c, 0x4b, 0x2c, 0x51, 0x88, 0xf4, + 0x48, 0x64, 0x51, 0x32, 0x82, 0x85, 0xe8, 0x32, 0xa3, 0x63, 0x51, 0x48, 0x70, 0x5f, 0xc1, 0x74, 0x41, 0x17, 0x67, + 0xbc, 0x14, 0x0e, 0xee, 0x62, 0x3a, 0xb4, 0x92, 0x20, 0xb0, 0x73, 0xea, 0x6b, 0xf7, 0x12, 0x3f, 0xd9, 0xb2, 0x6d, + 0x26, 0xa1, 0x84, 0x1d, 0x76, 0xd9, 0xeb, 0xc0, 0x49, 0x98, 0xe7, 0x79, 0xc2, 0x0d, 0xba, 0x73, 0x8d, 0x95, 0xc4, + 0x58, 0x67, 0x2f, 0x39, 0x6e, 0x9e, 0xf8, 0x02, 0x60, 0x1e, 0xcc, 0xfa, 0xdc, 0x71, 0x22, 0x96, 0xb3, 0x0c, 0x1a, + 0x47, 0x5b, 0x4e, 0x0a, 0x5d, 0x00, 0x73, 0x69, 0x7d, 0xaf, 0x03, 0xd8, 0x6d, 0x57, 0xc1, 0x98, 0x6a, 0x00, 0x11, + 0xc3, 0x0a, 0x9e, 0x14, 0xe0, 0x49, 0x66, 0x93, 0x33, 0x9e, 0xd9, 0x65, 0xb3, 0x4e, 0x8d, 0x2c, 0x66, 0xb0, 0xed, + 0xd0, 0xcf, 0x1a, 0xce, 0x92, 0xbe, 0x88, 0xe0, 0xb0, 0xd9, 0x5b, 0xe9, 0x96, 0x2d, 0xc9, 0xa1, 0xa4, 0x06, 0xdb, + 0x2d, 0x5c, 0x27, 0x77, 0xb7, 0x92, 0xe3, 0x6c, 0xab, 0x75, 0xc2, 0x10, 0x4a, 0xb7, 0xa3, 0xc6, 0x53, 0x08, 0xe0, + 0x2c, 0xc1, 0x35, 0x16, 0xec, 0xad, 0xc0, 0x55, 0xd2, 0x12, 0x23, 0xd1, 0x4b, 0xbc, 0xe5, 0x83, 0x12, 0x08, 0x6f, + 0x12, 0x4e, 0x1d, 0x1e, 0x74, 0x39, 0x11, 0x57, 0x98, 0xf4, 0x11, 0xd6, 0xda, 0xbe, 0xf5, 0xb8, 0xcf, 0xbd, 0x8a, + 0xa4, 0x5c, 0x40, 0xca, 0x30, 0xcd, 0x9e, 0x84, 0xfd, 0x31, 0xf6, 0x2b, 0x09, 0x66, 0xa0, 0xcf, 0x5b, 0x3f, 0xe3, + 0xa1, 0xe0, 0x4f, 0x62, 0x8e, 0x4f, 0x8e, 0x4d, 0x3d, 0x6d, 0x97, 0xe5, 0x70, 0xcc, 0xe3, 0x48, 0xbc, 0x4a, 0x61, + 0x8a, 0x4e, 0x6e, 0x50, 0x57, 0x84, 0xfb, 0xfe, 0x50, 0xc0, 0x56, 0x9d, 0xcd, 0x04, 0x77, 0xec, 0x04, 0x5b, 0xd8, + 0x2c, 0x07, 0xaa, 0xf0, 0x04, 0xe0, 0xf0, 0x30, 0x4d, 0x04, 0x8c, 0x14, 0x70, 0x8d, 0x54, 0x06, 0x2b, 0x99, 0x4e, + 0x79, 0x32, 0x38, 0x1c, 0x47, 0xf1, 0xc0, 0x89, 0x00, 0x23, 0x05, 0x1b, 0x8b, 0x00, 0xd7, 0x08, 0x54, 0xe0, 0xe3, + 0x9f, 0xf5, 0xab, 0x01, 0xe2, 0xed, 0xd2, 0xa1, 0xe0, 0x81, 0x6d, 0x77, 0x60, 0x25, 0x8e, 0x5a, 0x81, 0x05, 0x4d, + 0x05, 0xce, 0xf1, 0x16, 0xd8, 0x55, 0xee, 0xf2, 0xad, 0x20, 0x2a, 0xb7, 0x51, 0x21, 0xf8, 0x15, 0x52, 0x3c, 0xe0, + 0x3f, 0x71, 0xfd, 0xa4, 0x73, 0x1e, 0x66, 0xd6, 0x8f, 0xea, 0x44, 0x3d, 0xd6, 0xdc, 0xac, 0x2f, 0x82, 0xc7, 0x1e, + 0x1c, 0x65, 0x38, 0xa7, 0x83, 0x77, 0xb0, 0xf1, 0x39, 0x7b, 0x26, 0x82, 0xbe, 0xe8, 0xf5, 0x85, 0xc7, 0x27, 0x53, + 0x71, 0x75, 0x44, 0x8c, 0xd1, 0x07, 0x62, 0x1c, 0x60, 0x4b, 0x40, 0x55, 0x1f, 0x99, 0x99, 0xc2, 0xd6, 0x9b, 0x34, + 0xbe, 0x1a, 0x46, 0x71, 0x7c, 0x34, 0x9b, 0x4e, 0xd3, 0x4c, 0xb0, 0xbf, 0x05, 0x73, 0x91, 0x56, 0xa8, 0xc1, 0xbd, + 0x9c, 0xe7, 0x17, 0x91, 0x00, 0xd4, 0xc3, 0xb7, 0x7e, 0x08, 0x84, 0xf1, 0x28, 0x4d, 0x63, 0x1e, 0xe2, 0xa2, 0x93, + 0xde, 0x33, 0xe1, 0x27, 0xb3, 0x38, 0xee, 0x9c, 0xc1, 0xb0, 0x9f, 0x3a, 0x54, 0xfd, 0xfa, 0xec, 0x23, 0xef, 0x0b, + 0x9f, 0xbe, 0x3f, 0xcc, 0xb2, 0xf0, 0x0a, 0x1b, 0x06, 0x01, 0x36, 0x83, 0x53, 0xf1, 0xed, 0xd1, 0xeb, 0x57, 0x9e, + 0x3c, 0x24, 0xd1, 0xf0, 0x0a, 0x96, 0xa5, 0x0f, 0x5e, 0x52, 0xb0, 0x61, 0x96, 0x4e, 0x16, 0xa6, 0x96, 0x58, 0x4b, + 0x3a, 0x6b, 0x40, 0x80, 0xaa, 0x0d, 0x39, 0xb4, 0x09, 0xc1, 0x2b, 0xa2, 0x79, 0xac, 0x0c, 0xf4, 0xbc, 0xf0, 0xc7, + 0x97, 0xc5, 0x30, 0xe5, 0xcd, 0xd0, 0x8a, 0xec, 0x6a, 0xce, 0x03, 0x82, 0x73, 0x8a, 0x12, 0x06, 0x61, 0xec, 0x87, + 0x30, 0x3b, 0x94, 0xe2, 0x38, 0x85, 0x86, 0x98, 0x17, 0x05, 0x7b, 0x58, 0xd2, 0xbb, 0x40, 0x40, 0x88, 0x51, 0x05, + 0xe2, 0xfa, 0x1a, 0x17, 0xec, 0xb2, 0x9f, 0x83, 0x79, 0xa8, 0xd7, 0xe3, 0x03, 0x67, 0xc6, 0x73, 0xe9, 0x4b, 0xee, + 0xc2, 0x60, 0x17, 0xcf, 0x79, 0x26, 0x00, 0xce, 0xbf, 0x31, 0x90, 0x70, 0x31, 0x42, 0xb1, 0xd1, 0x62, 0xe3, 0x30, + 0x3f, 0x1c, 0x87, 0xc9, 0x88, 0x0f, 0xfc, 0x87, 0xa2, 0x60, 0x42, 0x04, 0xf6, 0x30, 0x4a, 0xc2, 0x38, 0xfa, 0x8d, + 0x0f, 0x6c, 0x25, 0x0e, 0x9e, 0x58, 0x40, 0x20, 0x40, 0x8c, 0xb9, 0xf5, 0xec, 0xdd, 0xcb, 0x17, 0x6a, 0x23, 0x6b, + 0x12, 0x02, 0xf6, 0x6c, 0x36, 0x85, 0xb5, 0xba, 0x4c, 0x49, 0x88, 0x27, 0x11, 0x71, 0x47, 0x10, 0x29, 0xb2, 0x24, + 0xca, 0xdf, 0x4f, 0x41, 0xa6, 0xf2, 0x37, 0x30, 0x0c, 0x40, 0x13, 0xc0, 0xcc, 0x54, 0x0e, 0xd3, 0xcb, 0x8a, 0x41, + 0x59, 0x04, 0x9d, 0x63, 0x5a, 0x78, 0xf9, 0x38, 0x73, 0xdc, 0x02, 0x48, 0x5d, 0x44, 0x7d, 0x2b, 0x1c, 0x0c, 0x9e, + 0x27, 0x91, 0x88, 0x08, 0xc0, 0x0c, 0xf7, 0x07, 0x69, 0x94, 0x4b, 0x59, 0xa1, 0x01, 0x07, 0x30, 0x1c, 0x47, 0x49, + 0x80, 0xb1, 0xab, 0x36, 0x0c, 0x78, 0x7c, 0x79, 0x22, 0xe1, 0xbc, 0xcb, 0xca, 0xe0, 0xf8, 0xc4, 0xf5, 0xa6, 0xb3, + 0x1c, 0x77, 0x5a, 0x4f, 0x81, 0xe2, 0x25, 0x3d, 0xcb, 0x79, 0x76, 0xce, 0x07, 0x25, 0x75, 0xe4, 0xb0, 0xc4, 0x85, + 0x39, 0xd4, 0xb9, 0x10, 0x30, 0x46, 0xc7, 0x64, 0xdc, 0x5c, 0x11, 0x7a, 0x96, 0x02, 0x46, 0x44, 0xc4, 0xf3, 0x92, + 0x97, 0x38, 0x28, 0x46, 0x4b, 0x7e, 0x92, 0x07, 0x7a, 0x7d, 0x53, 0x60, 0xbd, 0xdc, 0xad, 0x71, 0x0c, 0x2d, 0x69, + 0x9f, 0x9c, 0x93, 0xc8, 0xc8, 0xa1, 0x23, 0x13, 0x12, 0xd2, 0x1c, 0x84, 0x07, 0x3c, 0x68, 0x70, 0x25, 0x2f, 0x52, + 0xb3, 0x5d, 0xa1, 0xac, 0x0e, 0x7e, 0x26, 0x59, 0x8d, 0x1c, 0x0d, 0x6a, 0x60, 0x2c, 0xee, 0x95, 0x54, 0x01, 0x58, + 0x56, 0x7b, 0x64, 0x20, 0x6b, 0x0d, 0xd8, 0x38, 0x31, 0x0c, 0xe7, 0xb2, 0x0d, 0xee, 0x25, 0xe9, 0xc3, 0x7e, 0x9f, + 0xe7, 0x79, 0x9a, 0xdd, 0xb9, 0xb3, 0x41, 0xed, 0x4b, 0x75, 0x02, 0xf7, 0xf0, 0xf5, 0x45, 0x52, 0x41, 0xe0, 0x56, + 0x22, 0x56, 0x09, 0x06, 0x81, 0x82, 0x8a, 0x34, 0x0e, 0xbb, 0xa7, 0x35, 0x0f, 0xdf, 0xfe, 0xf0, 0xc1, 0xde, 0x12, + 0x4c, 0xa1, 0x01, 0xb0, 0xae, 0x47, 0x78, 0xcc, 0xa5, 0x6e, 0x45, 0x9a, 0xc7, 0x12, 0x66, 0xe4, 0x01, 0xf2, 0x06, + 0x1c, 0x16, 0x60, 0x2c, 0xbb, 0x06, 0x12, 0x83, 0x61, 0xdd, 0xc2, 0xd8, 0xd0, 0x95, 0x43, 0x93, 0x52, 0x23, 0x77, + 0x6e, 0x3e, 0x22, 0x45, 0xc2, 0xd8, 0xc6, 0x63, 0x7e, 0x52, 0x30, 0x42, 0xbd, 0x5e, 0x4d, 0x46, 0x80, 0x1e, 0x8b, + 0x93, 0x8e, 0xaa, 0x0f, 0x72, 0x89, 0xb9, 0x8c, 0xff, 0x3a, 0xe3, 0xb9, 0x90, 0x74, 0x0c, 0xe3, 0x66, 0x30, 0x6e, + 0x81, 0xe7, 0x6d, 0x18, 0x8d, 0x66, 0x19, 0xea, 0x3b, 0x78, 0x16, 0x39, 0x48, 0x46, 0xae, 0x9f, 0x56, 0xc1, 0xf6, + 0x7a, 0x8a, 0x12, 0x31, 0x47, 0x9a, 0xbe, 0x99, 0x9c, 0x10, 0x56, 0xe1, 0x5e, 0x5f, 0xff, 0xac, 0x07, 0xa9, 0xb6, + 0xb2, 0xd4, 0xd1, 0x16, 0xf7, 0x04, 0x36, 0x45, 0x0e, 0xba, 0xd1, 0x92, 0xe0, 0x0b, 0x71, 0x02, 0xd2, 0xbc, 0xa4, + 0x61, 0x85, 0x55, 0x09, 0x8e, 0x44, 0xe2, 0x6b, 0x39, 0x94, 0x4b, 0x02, 0xbe, 0x46, 0x2e, 0xde, 0x78, 0x89, 0x52, + 0xe1, 0x24, 0xa1, 0xaa, 0xe1, 0x8d, 0x4f, 0xd6, 0x91, 0x93, 0xe6, 0x07, 0x30, 0xd6, 0x52, 0x5d, 0xc5, 0x36, 0xce, + 0xeb, 0x6c, 0x63, 0x61, 0x19, 0xf6, 0xb4, 0xec, 0x62, 0x97, 0x54, 0xa6, 0x0e, 0x7a, 0x55, 0xc5, 0x22, 0x02, 0xa6, + 0x5a, 0x92, 0x31, 0xc4, 0xab, 0x70, 0x02, 0x67, 0x19, 0x68, 0x7a, 0x5d, 0x03, 0x49, 0x9e, 0xd8, 0xe4, 0xc4, 0x90, + 0x9c, 0x39, 0x4a, 0xce, 0xc8, 0x95, 0x8a, 0x59, 0xfd, 0xc0, 0xe5, 0x8c, 0x1f, 0xe7, 0x27, 0x95, 0x3e, 0x67, 0x2c, + 0x9e, 0x44, 0xb2, 0xa2, 0x6f, 0x8d, 0x3f, 0x59, 0x26, 0x91, 0x46, 0x7a, 0x03, 0x2c, 0x1e, 0xe8, 0x61, 0x61, 0x27, + 0x75, 0xab, 0x6a, 0x8d, 0xc0, 0x64, 0x60, 0x1f, 0x48, 0x62, 0x00, 0x33, 0xa5, 0xcf, 0xda, 0x49, 0x43, 0xb4, 0x1d, + 0x21, 0x61, 0x78, 0xc3, 0x38, 0x14, 0x4e, 0x6b, 0xbb, 0x89, 0xca, 0x28, 0x70, 0x7c, 0x10, 0x28, 0xae, 0xbb, 0xbc, + 0x14, 0xee, 0x81, 0xbe, 0x35, 0x8e, 0x86, 0xc2, 0x19, 0x0b, 0x62, 0x29, 0x3c, 0x06, 0x89, 0x24, 0x6a, 0x2a, 0x31, + 0xb1, 0x9b, 0x31, 0x12, 0x5b, 0xa9, 0x7f, 0x71, 0x0d, 0x29, 0xb1, 0x2d, 0xe4, 0x0e, 0x95, 0x3a, 0x5d, 0x71, 0x19, + 0xdd, 0x3a, 0x42, 0x95, 0xb1, 0xd5, 0x93, 0x23, 0xfa, 0x8a, 0x19, 0x44, 0x86, 0xd6, 0x1a, 0xf9, 0x26, 0x87, 0x50, + 0x85, 0xc2, 0x13, 0xe9, 0x8b, 0xf4, 0x82, 0x67, 0x87, 0x21, 0x02, 0xef, 0xcb, 0xee, 0x85, 0x14, 0x04, 0xc4, 0xef, + 0x45, 0x47, 0xd3, 0xcb, 0x07, 0x5a, 0x38, 0x6c, 0xc6, 0x24, 0x82, 0xb6, 0xa0, 0xac, 0x49, 0xfc, 0x27, 0x78, 0xce, + 0xe8, 0x40, 0xa2, 0xb0, 0xe1, 0x25, 0x7d, 0x3d, 0x7c, 0x51, 0xa7, 0x2f, 0x18, 0x61, 0xa4, 0x19, 0x60, 0xfd, 0x18, + 0x83, 0x08, 0x51, 0x26, 0x85, 0x21, 0xe7, 0x40, 0x9a, 0x28, 0x09, 0x7f, 0x7d, 0x2d, 0x0c, 0xcb, 0xad, 0xa6, 0x2e, + 0x72, 0x79, 0x6c, 0xdc, 0x02, 0x64, 0x15, 0x2a, 0x76, 0x59, 0x1a, 0xc7, 0x86, 0xa8, 0x62, 0x51, 0xa7, 0x14, 0x4e, + 0x30, 0xfd, 0xd1, 0x4d, 0xf2, 0x09, 0xeb, 0x4d, 0x11, 0xa5, 0x01, 0x4d, 0x06, 0x3c, 0x43, 0x4b, 0xd2, 0xd8, 0x2d, + 0x25, 0x65, 0x61, 0xc2, 0x04, 0x88, 0x9a, 0x0f, 0xd0, 0x50, 0x01, 0xfe, 0xeb, 0x8d, 0xd3, 0x5c, 0x94, 0x85, 0x15, + 0xf4, 0x91, 0x01, 0x3d, 0xe8, 0x80, 0x61, 0x1c, 0x3b, 0xd2, 0x28, 0x99, 0xa4, 0xe7, 0x7c, 0x05, 0xd4, 0x9d, 0x1a, + 0xc8, 0xe5, 0x30, 0xdc, 0x18, 0x06, 0xe4, 0xcd, 0x34, 0x8e, 0xfa, 0xbc, 0x14, 0x5d, 0x47, 0x1e, 0x28, 0x8c, 0xfc, + 0x12, 0xf9, 0x88, 0xdb, 0xed, 0x76, 0x9b, 0xac, 0xe5, 0x16, 0x12, 0xe1, 0xf3, 0x25, 0xc4, 0xde, 0x20, 0x34, 0x91, + 0xc8, 0x40, 0x68, 0xae, 0xe2, 0x07, 0xdc, 0x35, 0x24, 0x65, 0xa4, 0x8d, 0x2b, 0xc9, 0x9d, 0x5d, 0x36, 0x80, 0x41, + 0x05, 0xd7, 0xdc, 0x1c, 0x55, 0x68, 0x79, 0x74, 0xdf, 0x96, 0xf8, 0x2b, 0xc9, 0x49, 0x9f, 0x32, 0xbd, 0xe7, 0x79, + 0x69, 0xac, 0x57, 0xdb, 0x53, 0x61, 0xbb, 0x27, 0xe4, 0xf6, 0x00, 0xbd, 0x03, 0x84, 0xd2, 0x4a, 0x77, 0x96, 0x96, + 0x54, 0x8d, 0xa1, 0x38, 0x7b, 0x79, 0x88, 0xde, 0x6a, 0x30, 0x57, 0xa1, 0xe0, 0x48, 0x31, 0x05, 0x8e, 0x86, 0x9f, + 0xdc, 0xb6, 0x43, 0xd8, 0x9e, 0xb3, 0xb0, 0xff, 0xa9, 0x4e, 0xfd, 0x15, 0x19, 0x04, 0x8b, 0xdc, 0xd8, 0xa8, 0x32, + 0x58, 0x96, 0xb9, 0x6e, 0xcd, 0xa5, 0x6b, 0x07, 0xc5, 0x01, 0xf3, 0xae, 0x24, 0xfb, 0xfa, 0x46, 0xaf, 0xa5, 0x76, + 0x82, 0x28, 0x52, 0x2b, 0x73, 0x90, 0x0b, 0x7c, 0x96, 0xe2, 0x34, 0x3f, 0x50, 0x74, 0x87, 0xe6, 0x46, 0xb1, 0x00, + 0x08, 0x90, 0x5d, 0x31, 0x88, 0xf2, 0xf5, 0x18, 0xf8, 0x53, 0xa0, 0x7c, 0x6c, 0xcc, 0x70, 0x5b, 0x40, 0x4b, 0x1e, + 0xa7, 0xb4, 0xe6, 0x12, 0x32, 0xa5, 0x4f, 0x68, 0x46, 0xf3, 0x1d, 0xea, 0x2e, 0x44, 0xef, 0xaf, 0x65, 0x15, 0x6a, + 0x65, 0x08, 0x45, 0xde, 0x31, 0xd5, 0x89, 0x1a, 0x05, 0x28, 0x9e, 0x1a, 0x91, 0xc8, 0xcd, 0x6a, 0xf6, 0xa3, 0xd2, + 0xd8, 0xa5, 0x09, 0xae, 0x58, 0x6e, 0x1a, 0x38, 0x8e, 0x93, 0xa3, 0x09, 0xa7, 0x55, 0xfb, 0x6a, 0x11, 0xf9, 0xd2, + 0x22, 0x72, 0xcf, 0xb0, 0xb3, 0xdc, 0x8a, 0x96, 0x8d, 0xee, 0xfe, 0xdf, 0x5c, 0xb3, 0x11, 0xaa, 0xab, 0x1e, 0xf2, + 0x67, 0xb7, 0x64, 0xb7, 0x71, 0x20, 0x58, 0xaa, 0x6c, 0x1c, 0x45, 0x69, 0xc8, 0x30, 0xaa, 0x2e, 0x99, 0x2b, 0x8f, + 0x46, 0xcd, 0xde, 0xcd, 0x58, 0xea, 0x2e, 0xe8, 0xf6, 0x45, 0xa1, 0x70, 0xc4, 0x5d, 0xb5, 0x37, 0x35, 0xa5, 0xd8, + 0xc0, 0x0a, 0xcb, 0x02, 0xa5, 0x08, 0x4b, 0xbd, 0x67, 0x11, 0x37, 0xe5, 0xb8, 0x50, 0x96, 0x55, 0xa8, 0xa9, 0x69, + 0x94, 0x5a, 0xb5, 0xca, 0x5c, 0x36, 0xd6, 0x3a, 0x69, 0x5a, 0xad, 0x1b, 0x64, 0x8f, 0x76, 0x48, 0xd8, 0xbd, 0x79, + 0xcd, 0x2a, 0xf4, 0x8d, 0x66, 0x85, 0x8f, 0x2c, 0x35, 0x5d, 0x85, 0xee, 0x55, 0x34, 0x53, 0x1b, 0xc7, 0x40, 0x78, + 0x6a, 0x22, 0xdc, 0xc0, 0x6c, 0x26, 0x39, 0x57, 0x76, 0x12, 0x8c, 0xeb, 0x7d, 0x61, 0x1f, 0x52, 0xb9, 0x0f, 0x4b, + 0x48, 0x5c, 0x54, 0x3d, 0x89, 0x04, 0xd1, 0x86, 0xcd, 0x51, 0xb9, 0x33, 0xe5, 0x83, 0x83, 0xb0, 0x47, 0x70, 0x2c, + 0x16, 0x89, 0x6e, 0xa5, 0x06, 0xea, 0x7a, 0x95, 0x5d, 0x78, 0x7d, 0xfd, 0x50, 0xb8, 0x8e, 0xd2, 0x7d, 0x61, 0xbf, + 0x7a, 0x9a, 0xe3, 0x3e, 0x7c, 0x81, 0xad, 0x48, 0x15, 0xad, 0x4a, 0x4a, 0xa3, 0xa1, 0x4e, 0xb3, 0xf5, 0x7d, 0x12, + 0x06, 0xdb, 0x3e, 0x5c, 0xe2, 0x5e, 0x54, 0xa8, 0xc4, 0x74, 0xb5, 0xe4, 0x43, 0x35, 0x74, 0xe4, 0xba, 0xae, 0x9f, + 0x93, 0x1d, 0xb3, 0xb1, 0xca, 0xb4, 0xbc, 0x73, 0x27, 0x37, 0x06, 0xfa, 0x50, 0xb2, 0x89, 0x8f, 0x0e, 0x8a, 0xe4, + 0xfc, 0x2a, 0x21, 0xdd, 0xe5, 0xa3, 0x16, 0x42, 0x4b, 0x86, 0x29, 0xa0, 0x0d, 0x0c, 0xf2, 0xf0, 0x22, 0x8c, 0x84, + 0x55, 0x8e, 0x22, 0x0d, 0x72, 0xe0, 0x00, 0x73, 0xa5, 0x6a, 0xc0, 0xe2, 0x50, 0x79, 0x44, 0x9e, 0xa0, 0x55, 0x68, + 0x49, 0xf7, 0xfd, 0x31, 0x47, 0x5f, 0xb0, 0xd6, 0x22, 0x4a, 0xcb, 0x70, 0x43, 0x49, 0x11, 0x35, 0xf0, 0x6a, 0xd8, + 0x8b, 0xc5, 0xee, 0x35, 0x4b, 0x00, 0xf6, 0x08, 0x58, 0xda, 0x44, 0xd7, 0x15, 0x0b, 0xcf, 0x8a, 0x33, 0xc2, 0xf1, + 0x58, 0x39, 0xb6, 0xd2, 0xff, 0x3b, 0x0b, 0x66, 0x77, 0x65, 0xb0, 0xd7, 0x44, 0x69, 0x29, 0x7d, 0xa5, 0x4b, 0x50, + 0x53, 0x66, 0x6e, 0x1a, 0xf8, 0xca, 0x9f, 0xda, 0x91, 0x3e, 0x13, 0x30, 0x2c, 0x4a, 0xab, 0x4f, 0x53, 0x43, 0x47, + 0xfa, 0x36, 0x94, 0x48, 0x4d, 0x67, 0xf1, 0x40, 0x01, 0x0b, 0xd6, 0x2c, 0x57, 0x74, 0x74, 0x11, 0xc5, 0x71, 0x55, + 0xfa, 0x25, 0x7c, 0x3d, 0x57, 0x7c, 0x3d, 0xd3, 0x7c, 0x1d, 0x39, 0x05, 0xf2, 0x75, 0x39, 0x5c, 0xd5, 0x3d, 0x5b, + 0x3a, 0x9d, 0x99, 0xe4, 0xe8, 0x39, 0x59, 0xd2, 0x38, 0xdf, 0x4c, 0x43, 0xe0, 0x96, 0x9a, 0x17, 0x08, 0x1b, 0xb5, + 0xed, 0x39, 0xd2, 0x0a, 0x7a, 0x31, 0xb9, 0xe9, 0xa4, 0x80, 0x7a, 0x96, 0x17, 0xbc, 0xa4, 0xec, 0x87, 0x4f, 0xd0, + 0x4f, 0x67, 0x2c, 0x07, 0x85, 0x18, 0x15, 0x7f, 0x91, 0x12, 0xa5, 0x57, 0x17, 0xa9, 0xd5, 0xe5, 0x7a, 0x75, 0xc8, + 0xe9, 0xab, 0xd5, 0x0d, 0x6e, 0xe6, 0xf5, 0xb4, 0xbc, 0xa8, 0x5c, 0x5e, 0xb5, 0xdf, 0xd7, 0xd7, 0xce, 0x42, 0x09, + 0xba, 0xf0, 0x95, 0x89, 0x92, 0x95, 0xa3, 0x23, 0x0f, 0x30, 0x31, 0x83, 0x05, 0x85, 0x5c, 0x74, 0x29, 0xe2, 0x5e, + 0x7c, 0xce, 0xc5, 0x43, 0x9e, 0x7a, 0xd9, 0xff, 0x30, 0x9d, 0x4c, 0x51, 0x1b, 0x5b, 0x20, 0x69, 0x68, 0xf0, 0x7e, + 0xa1, 0xbe, 0x58, 0x51, 0x56, 0xeb, 0x43, 0xe7, 0xb1, 0x46, 0x4d, 0xa5, 0xc5, 0x0c, 0x86, 0xd4, 0xac, 0x2c, 0x2a, + 0x19, 0xc7, 0x2a, 0xb7, 0xca, 0xe1, 0xa2, 0x53, 0x46, 0x57, 0xbc, 0x76, 0x22, 0xc9, 0x87, 0x23, 0xe4, 0x75, 0x06, + 0xfb, 0xd1, 0xe4, 0x6e, 0xee, 0x7f, 0x51, 0x21, 0x67, 0x5e, 0x2c, 0xa0, 0x6f, 0x5e, 0x14, 0x4f, 0x94, 0x95, 0xcd, + 0x9e, 0xac, 0x37, 0x87, 0xab, 0x3a, 0x65, 0x2d, 0x1e, 0x9f, 0x40, 0xd1, 0x92, 0xee, 0x18, 0xcc, 0x27, 0xe9, 0x80, + 0xfb, 0x36, 0x74, 0x4f, 0xec, 0x02, 0x3d, 0xab, 0x6a, 0xf3, 0x07, 0xc2, 0x99, 0xbf, 0xad, 0xbb, 0x58, 0xfd, 0x27, + 0x05, 0x3a, 0xc0, 0x7e, 0x5c, 0x76, 0xbe, 0xfe, 0x00, 0xe6, 0x20, 0x69, 0xa2, 0xa5, 0x52, 0xfb, 0x63, 0x25, 0x97, + 0x7e, 0xf4, 0xd7, 0xb6, 0xaf, 0x6c, 0x10, 0xbb, 0xe5, 0xdd, 0xf3, 0x76, 0x6c, 0x97, 0x5c, 0xc3, 0xdf, 0xaa, 0x13, + 0xff, 0x51, 0xbb, 0x86, 0x8f, 0x82, 0x8f, 0x75, 0xcf, 0xf0, 0x4c, 0x04, 0x47, 0xbd, 0x23, 0x6d, 0x32, 0xa7, 0x60, + 0x1e, 0x80, 0x11, 0x1f, 0x47, 0xa2, 0x81, 0xe1, 0x37, 0x9b, 0xcd, 0x65, 0x05, 0x7a, 0x15, 0xc9, 0xa5, 0x5d, 0x68, + 0x63, 0x8f, 0x71, 0x11, 0xd8, 0x9b, 0xd0, 0x70, 0xd3, 0x66, 0x93, 0xe0, 0x14, 0xbf, 0x6c, 0xce, 0x9d, 0x97, 0xa1, + 0x18, 0x7b, 0x59, 0x08, 0x53, 0x4d, 0x1c, 0x77, 0xcb, 0xb6, 0x5d, 0x2f, 0x27, 0x83, 0xe3, 0x81, 0x5b, 0x6c, 0x9e, + 0xb2, 0x27, 0xd0, 0xa5, 0x67, 0x6f, 0x4d, 0xd8, 0x23, 0x11, 0x9c, 0x1e, 0x6c, 0xce, 0x9f, 0x88, 0xa2, 0x7b, 0xca, + 0x2e, 0x4b, 0xaf, 0x3d, 0x7b, 0x1f, 0x38, 0xb0, 0xd1, 0x97, 0x0a, 0x1a, 0xa0, 0x2e, 0xe9, 0xbd, 0xb7, 0x5d, 0xf6, + 0x8e, 0x62, 0x2b, 0x15, 0xbb, 0x51, 0xe1, 0x95, 0x8d, 0xc0, 0x4e, 0xc9, 0x47, 0x60, 0xc3, 0x21, 0xaf, 0xca, 0x4a, + 0x5d, 0x81, 0x1d, 0x89, 0xa0, 0x66, 0x91, 0xb3, 0x97, 0x14, 0xa5, 0x39, 0x12, 0x4e, 0xe2, 0xea, 0x61, 0x1c, 0xed, + 0x8b, 0x56, 0x67, 0x33, 0x39, 0x96, 0x2e, 0x06, 0x2f, 0x02, 0x05, 0x20, 0x04, 0x09, 0x7c, 0xe2, 0x9a, 0xfa, 0x07, + 0xfb, 0x2e, 0x38, 0x3d, 0xb6, 0xfe, 0xd3, 0x57, 0xbf, 0x0c, 0x7f, 0xc9, 0x4e, 0x4e, 0xd9, 0x9b, 0x60, 0xfb, 0xc0, + 0xe9, 0xf9, 0xce, 0x46, 0xa3, 0x71, 0xfd, 0xcb, 0xf6, 0xf1, 0xdf, 0xc3, 0xc6, 0x6f, 0x0f, 0x1b, 0x3f, 0x9f, 0xb8, + 0xd7, 0xce, 0x2f, 0xdb, 0xbd, 0x63, 0xf5, 0x74, 0xfc, 0xf7, 0xee, 0x2f, 0xf9, 0xc9, 0x5f, 0x64, 0xe1, 0xa6, 0xeb, + 0x6e, 0x8f, 0xd8, 0x54, 0x04, 0xdb, 0x8d, 0x46, 0x17, 0xbe, 0x8d, 0xe0, 0x1b, 0x7e, 0x9e, 0x05, 0x6f, 0xf9, 0xe8, + 0xc9, 0xe5, 0xd4, 0x39, 0xed, 0x5e, 0x6f, 0xce, 0xbf, 0x2b, 0x70, 0xd4, 0xe3, 0xbf, 0xff, 0xf2, 0x4b, 0x6e, 0xdf, + 0xed, 0x06, 0xdb, 0x27, 0x5b, 0xae, 0x83, 0xa5, 0x7f, 0x09, 0xe8, 0x2f, 0x54, 0x1e, 0xff, 0x5d, 0x41, 0x61, 0xdf, + 0xfd, 0xe5, 0xf4, 0xa0, 0x1b, 0x9c, 0x5c, 0x3b, 0xf6, 0xf5, 0x5d, 0xf7, 0xda, 0x75, 0xaf, 0x37, 0xdd, 0x53, 0x66, + 0x8f, 0x00, 0x6f, 0xe7, 0x30, 0xf6, 0x5d, 0x18, 0x7b, 0x08, 0x9f, 0x36, 0x7c, 0x1e, 0xc2, 0xe7, 0xdf, 0xa1, 0xaf, + 0x74, 0xb2, 0x5d, 0x93, 0x7f, 0xe3, 0x1a, 0x03, 0x1c, 0x21, 0xa0, 0xfc, 0x5a, 0x44, 0x22, 0xe6, 0xee, 0xe6, 0x76, + 0xc4, 0x3e, 0x11, 0x9a, 0x40, 0x98, 0x7b, 0x9e, 0x87, 0xc6, 0x9d, 0x33, 0xff, 0x80, 0x9b, 0x8d, 0x34, 0xb3, 0xe9, + 0x63, 0x64, 0x07, 0x1d, 0x01, 0xb9, 0x2f, 0xd8, 0x79, 0x18, 0x83, 0x7e, 0xe3, 0x73, 0x20, 0xe8, 0x7e, 0xf0, 0x49, + 0x38, 0x20, 0xf4, 0x5f, 0x08, 0xfc, 0xd2, 0x76, 0xd9, 0xa1, 0x0a, 0x62, 0xe2, 0x49, 0x96, 0x44, 0x95, 0xa4, 0x52, + 0x65, 0x01, 0xc8, 0xa6, 0x2b, 0x2a, 0xe1, 0xe0, 0x26, 0x08, 0xf5, 0x66, 0x2d, 0xe4, 0xc9, 0x2e, 0x02, 0x4d, 0x12, + 0xef, 0x32, 0xce, 0x7f, 0x0c, 0xe3, 0x4f, 0x60, 0xf7, 0x5e, 0xb2, 0x56, 0xfb, 0x01, 0x23, 0x2f, 0x34, 0x68, 0x1a, + 0x9d, 0x32, 0x5e, 0xf5, 0x5a, 0xc8, 0x38, 0x01, 0x4a, 0xd9, 0xba, 0x33, 0x06, 0x77, 0x7c, 0x23, 0x59, 0xf2, 0x58, + 0x65, 0xe1, 0x85, 0xed, 0xd6, 0x63, 0xa3, 0x51, 0x02, 0xcb, 0x02, 0x5a, 0x10, 0x1c, 0xf8, 0x1b, 0x4c, 0x6b, 0xa9, + 0xf5, 0x5a, 0x21, 0x0e, 0x64, 0x97, 0x3a, 0xc3, 0xcc, 0xb0, 0x38, 0x67, 0x3a, 0xe8, 0x84, 0x67, 0xc5, 0xc1, 0x08, + 0x95, 0xd2, 0x3b, 0x1e, 0x57, 0x01, 0xb0, 0xc5, 0x18, 0x5f, 0xa3, 0x85, 0x9e, 0xb0, 0x13, 0x92, 0xcf, 0x20, 0xc6, + 0x03, 0x94, 0xa2, 0xed, 0x9e, 0x7d, 0x90, 0x9f, 0x8f, 0xba, 0x36, 0xc6, 0x67, 0xd2, 0xe0, 0x0d, 0x39, 0x86, 0xb0, + 0xc1, 0x38, 0x68, 0x76, 0xc6, 0x07, 0xbc, 0x33, 0xde, 0xda, 0xd2, 0x4a, 0x34, 0x28, 0x99, 0xc7, 0x63, 0xd9, 0x3d, + 0x64, 0x03, 0x36, 0x0b, 0x60, 0xc0, 0x11, 0x34, 0xc3, 0x2e, 0x9d, 0xd1, 0x41, 0xac, 0xa6, 0x01, 0xae, 0x9a, 0x7a, + 0x71, 0x98, 0x8b, 0xe7, 0x68, 0xed, 0x07, 0x23, 0x36, 0x00, 0x1d, 0x99, 0x5f, 0xf2, 0xbe, 0x13, 0x83, 0x0d, 0xae, + 0x38, 0x8d, 0xdb, 0x71, 0x47, 0x81, 0xd1, 0x0c, 0xad, 0x88, 0xe0, 0x4d, 0x6f, 0x70, 0xdc, 0x3a, 0x81, 0x2f, 0x36, + 0x90, 0xb7, 0xdd, 0x4b, 0x83, 0xa9, 0xf0, 0xb1, 0xc4, 0xd0, 0x95, 0x83, 0x11, 0x16, 0xb5, 0x8d, 0x22, 0xe7, 0x50, + 0x78, 0x02, 0x74, 0x5e, 0x07, 0x8b, 0xd1, 0xfe, 0xcf, 0x35, 0x61, 0xdb, 0x07, 0xdb, 0xf6, 0x16, 0x96, 0x12, 0x71, + 0xba, 0x30, 0xc5, 0x99, 0x0b, 0x9d, 0x77, 0x4e, 0x4c, 0x01, 0x40, 0x85, 0x38, 0xf9, 0x19, 0x4c, 0xde, 0xa4, 0xc9, + 0xbb, 0x76, 0x0f, 0x8a, 0x73, 0xa9, 0xa1, 0xf5, 0x72, 0xff, 0x0d, 0x2d, 0xd5, 0xf5, 0x15, 0x70, 0x7a, 0x07, 0x82, + 0x46, 0xdb, 0x77, 0x66, 0xe6, 0x22, 0x1a, 0x38, 0x99, 0xc2, 0x02, 0x0b, 0x03, 0x6c, 0x0f, 0x93, 0xe2, 0x8c, 0x55, + 0xb7, 0x33, 0x5f, 0x3d, 0xdf, 0xb5, 0xef, 0xf6, 0x86, 0xc2, 0x3f, 0x17, 0x72, 0xfa, 0xa1, 0xb8, 0xbe, 0xc6, 0xcf, + 0x73, 0x01, 0x8b, 0x3c, 0xa3, 0xa2, 0xa9, 0x2a, 0x1a, 0x61, 0xd1, 0x1b, 0x1f, 0x41, 0x65, 0x79, 0xa9, 0x65, 0xc9, + 0x3d, 0x39, 0x0f, 0x08, 0xf6, 0x3b, 0x77, 0x60, 0x6b, 0xb6, 0x5a, 0x27, 0xe8, 0xe2, 0xcf, 0x44, 0xfe, 0x63, 0x24, + 0x80, 0x35, 0x6f, 0x77, 0x6d, 0xb7, 0x67, 0x5b, 0xb8, 0xb5, 0x9d, 0x6c, 0x2b, 0x90, 0x18, 0x8e, 0xb7, 0x1e, 0x09, + 0x7f, 0xd6, 0x0d, 0x00, 0x71, 0x91, 0x64, 0xe1, 0xa1, 0xcb, 0x62, 0xc5, 0x38, 0x9b, 0x6c, 0xe6, 0x6e, 0x71, 0xb1, + 0xa5, 0x9f, 0xe1, 0x69, 0xb2, 0x75, 0xee, 0xfa, 0x31, 0x7c, 0xc0, 0x52, 0x03, 0x58, 0x72, 0xd9, 0x4d, 0x8b, 0xbf, + 0x31, 0xf0, 0x68, 0xed, 0xed, 0x3c, 0xa6, 0xe3, 0x90, 0x6d, 0x39, 0xc9, 0x31, 0x3f, 0xb9, 0xbe, 0xb6, 0x0f, 0x7a, + 0x00, 0xc2, 0x96, 0xa3, 0x09, 0x6d, 0x5b, 0x53, 0x1a, 0x6c, 0x46, 0x74, 0x52, 0xa8, 0x68, 0xd2, 0xab, 0x5a, 0xe4, + 0x68, 0x5e, 0x1d, 0x76, 0x83, 0x07, 0xf0, 0xa2, 0x34, 0x64, 0xa4, 0xc2, 0x3a, 0xc5, 0x65, 0x6a, 0x62, 0xce, 0x82, + 0x26, 0xe0, 0x59, 0x3b, 0xaf, 0xc1, 0xa2, 0xab, 0x08, 0x3e, 0x0e, 0xaa, 0xe6, 0xec, 0x18, 0xc8, 0xf6, 0x24, 0x78, + 0x2c, 0x0d, 0x92, 0x8e, 0x76, 0x8d, 0xf3, 0x38, 0x78, 0xb5, 0x10, 0xc1, 0x0d, 0x31, 0xbc, 0x72, 0xe1, 0xf5, 0x67, + 0x59, 0x06, 0x8f, 0xaf, 0x40, 0xd2, 0x06, 0xaa, 0x29, 0x9a, 0x4a, 0x18, 0x9a, 0x65, 0xa8, 0xa4, 0xb5, 0xf5, 0xc9, + 0x98, 0x2d, 0x55, 0x8f, 0x82, 0x99, 0xd4, 0x9f, 0x28, 0x60, 0xdb, 0x19, 0x29, 0xc3, 0x18, 0x54, 0xc4, 0x99, 0x8a, + 0xe4, 0x3a, 0xc0, 0xeb, 0x46, 0x5e, 0x1f, 0xab, 0x71, 0x02, 0x50, 0x3d, 0xe9, 0x1c, 0x01, 0xf9, 0x5e, 0x78, 0x09, + 0xb0, 0x48, 0x2c, 0x04, 0x13, 0xa5, 0x94, 0xcc, 0xfa, 0x78, 0x1d, 0x8c, 0x3b, 0xc4, 0x6e, 0x72, 0x2f, 0x81, 0x16, + 0x88, 0x1e, 0x8c, 0xdd, 0xab, 0x22, 0xe0, 0x36, 0x66, 0x88, 0xaa, 0x82, 0xef, 0xd8, 0xf4, 0x5e, 0x8f, 0xd0, 0xe5, + 0x4b, 0xca, 0x56, 0xd9, 0x58, 0xfa, 0xc1, 0x5d, 0x17, 0x46, 0x19, 0x79, 0x18, 0xda, 0x23, 0x12, 0xe2, 0x68, 0xcb, + 0x8d, 0x4c, 0xa2, 0x9a, 0x94, 0x63, 0x9e, 0x03, 0x61, 0xa7, 0x5b, 0x5b, 0xe4, 0x86, 0x9e, 0x49, 0x92, 0x18, 0x81, + 0x04, 0x28, 0xcf, 0x96, 0x6e, 0xf7, 0x34, 0xa8, 0xcf, 0xe4, 0x9c, 0xd7, 0xdd, 0xb9, 0x5b, 0x98, 0x26, 0x81, 0x9e, + 0x42, 0x01, 0x83, 0xb3, 0x87, 0xc1, 0xb6, 0x73, 0xec, 0xf5, 0xfe, 0x7a, 0x02, 0x66, 0xa5, 0xf7, 0x17, 0x77, 0x5b, + 0x32, 0x8e, 0x73, 0x30, 0x2a, 0xe4, 0x14, 0x73, 0x0a, 0x61, 0x02, 0x23, 0xc3, 0xf3, 0xe6, 0x67, 0x2c, 0x01, 0xb8, + 0xfd, 0x87, 0x78, 0xc6, 0x35, 0xdd, 0x3c, 0x65, 0x48, 0x47, 0x50, 0x26, 0x39, 0x89, 0x67, 0xf7, 0x9e, 0x8b, 0xf2, + 0xa9, 0x67, 0xf7, 0x7e, 0xab, 0x9e, 0xfe, 0x6a, 0xf7, 0x7e, 0x10, 0xfe, 0x6f, 0x85, 0x72, 0x76, 0xd7, 0xa6, 0xb8, + 0xa7, 0xa7, 0x28, 0xe4, 0xc6, 0x18, 0x98, 0x9b, 0xb9, 0xcb, 0x7e, 0x8e, 0x91, 0x5b, 0x00, 0x1e, 0x34, 0x2b, 0xca, + 0x3d, 0x11, 0x8e, 0x10, 0xa5, 0xc6, 0x0e, 0xe4, 0x66, 0x64, 0xbf, 0x5a, 0x30, 0x12, 0x8a, 0xa6, 0x56, 0x44, 0xe5, + 0xa8, 0x0b, 0x98, 0xab, 0xb5, 0x25, 0x8d, 0xa9, 0x1e, 0x49, 0x2f, 0xb9, 0xf4, 0x39, 0x50, 0xfd, 0xf9, 0xc1, 0xa8, + 0x73, 0x0e, 0x5c, 0x3a, 0xd7, 0x84, 0x35, 0x3b, 0x3e, 0x3f, 0x61, 0xef, 0xd1, 0xa7, 0x67, 0x52, 0x12, 0xab, 0x2d, + 0xaf, 0xad, 0x96, 0xb7, 0xb5, 0x05, 0x0b, 0xec, 0x18, 0x5d, 0x47, 0xb2, 0x6b, 0x51, 0x48, 0x9c, 0x2c, 0x12, 0xda, + 0xbe, 0x4b, 0x25, 0x98, 0x0e, 0x05, 0x4f, 0x4f, 0x84, 0xbb, 0x72, 0x54, 0x1c, 0x13, 0xbb, 0xd3, 0x89, 0x45, 0xe6, + 0x29, 0x65, 0x84, 0x83, 0x58, 0xc0, 0xae, 0xa5, 0x23, 0x78, 0xc2, 0x66, 0x5b, 0x2d, 0x22, 0x72, 0x68, 0x53, 0x1f, + 0xeb, 0x7e, 0x35, 0x16, 0x34, 0x0a, 0x26, 0x25, 0x96, 0x8a, 0x6c, 0x6b, 0xab, 0xa8, 0x47, 0x3b, 0xf5, 0xb9, 0xad, + 0xc5, 0x1f, 0x2e, 0x17, 0xd3, 0x32, 0xb4, 0x7c, 0xad, 0x24, 0x6a, 0x04, 0x80, 0x24, 0x3c, 0x43, 0x19, 0x1a, 0x08, + 0x16, 0x15, 0x45, 0x29, 0xd7, 0x3f, 0xa1, 0x10, 0x85, 0x43, 0x9e, 0x20, 0xdf, 0x21, 0xb3, 0x8b, 0x65, 0x2c, 0x65, + 0x63, 0xe2, 0x1a, 0xb0, 0xf2, 0x43, 0x9d, 0xd0, 0x22, 0x88, 0x03, 0xc5, 0x41, 0x64, 0x48, 0xa4, 0x3c, 0xe0, 0x60, + 0x10, 0x1c, 0xa6, 0x37, 0x9a, 0x64, 0x60, 0x50, 0xf8, 0xd4, 0x2c, 0x56, 0x7c, 0x2b, 0x0c, 0xde, 0x81, 0x20, 0x2f, + 0x83, 0x23, 0x1e, 0xb1, 0xbf, 0xc7, 0x51, 0xc6, 0x49, 0x03, 0xdf, 0xd4, 0x66, 0x5f, 0x5c, 0x57, 0x1f, 0x63, 0xd3, + 0x79, 0x83, 0x88, 0x0c, 0xd1, 0xb7, 0x93, 0x05, 0x4b, 0xcd, 0xc0, 0x40, 0x7b, 0xbd, 0xca, 0x04, 0x86, 0xef, 0xd2, + 0x3a, 0x24, 0xcd, 0x86, 0x85, 0x15, 0xa4, 0xb1, 0xfa, 0xe2, 0xc3, 0x9c, 0xa8, 0x20, 0x85, 0xa0, 0xd3, 0x30, 0x1a, + 0xe8, 0x1d, 0x60, 0x07, 0xcd, 0x24, 0x97, 0x99, 0xcb, 0x06, 0x01, 0xe5, 0x8c, 0x03, 0xee, 0xca, 0xb5, 0x97, 0x8c, + 0x2b, 0x35, 0xc4, 0xb7, 0x3f, 0xa6, 0x4a, 0xb4, 0x1f, 0x60, 0xfd, 0x41, 0xac, 0x30, 0x10, 0x80, 0x66, 0x10, 0xd7, + 0xcc, 0xb2, 0x00, 0x37, 0x80, 0xe6, 0x3a, 0xc2, 0x9d, 0xf0, 0xa4, 0xe2, 0x07, 0xad, 0x68, 0x56, 0x50, 0x76, 0x48, + 0x74, 0x7c, 0x5c, 0xca, 0x4b, 0xab, 0xac, 0xd1, 0x1f, 0xd0, 0x72, 0xd2, 0x0f, 0xaf, 0xd4, 0xd0, 0x65, 0xc1, 0x63, + 0x9d, 0x40, 0x06, 0xdf, 0x5f, 0xaa, 0x1c, 0x32, 0x10, 0x12, 0x8a, 0xdb, 0x2f, 0x59, 0x98, 0x0f, 0x5f, 0x7a, 0x55, + 0x2d, 0x35, 0x86, 0xb2, 0xf7, 0xab, 0x9a, 0x61, 0x79, 0x31, 0xab, 0x4c, 0x7c, 0x82, 0x6f, 0xce, 0x63, 0x7f, 0xae, + 0x44, 0x83, 0x1f, 0x15, 0x8c, 0xc4, 0x91, 0x9f, 0x17, 0xa5, 0x67, 0xe4, 0x31, 0xa8, 0x63, 0x14, 0x05, 0xaa, 0xef, + 0x9a, 0x52, 0xf2, 0x80, 0x20, 0x8f, 0xfa, 0xa0, 0x40, 0xae, 0x09, 0x0d, 0x5d, 0xba, 0x5e, 0x34, 0xc1, 0xe4, 0x19, + 0x02, 0x3d, 0x62, 0x1b, 0xa0, 0x1d, 0xd4, 0x85, 0x57, 0x46, 0x44, 0x9a, 0xd6, 0x24, 0x0b, 0x03, 0x0d, 0x0f, 0xc4, + 0x63, 0x13, 0x76, 0x3c, 0x07, 0xc5, 0x47, 0x9e, 0xd0, 0xb0, 0x1c, 0x57, 0x0a, 0x19, 0xcc, 0x0b, 0x53, 0xa7, 0x55, + 0x8a, 0xdf, 0x41, 0x27, 0x24, 0xd7, 0x23, 0x49, 0xf4, 0x01, 0x91, 0xc5, 0x33, 0x27, 0x65, 0x29, 0x0d, 0x7c, 0x14, + 0x9d, 0xc5, 0x98, 0x5a, 0x82, 0xab, 0x02, 0x15, 0xd4, 0x2f, 0x9b, 0xb6, 0x54, 0xd3, 0xd0, 0xa3, 0x7d, 0x4a, 0x59, + 0xe8, 0x21, 0xe3, 0x86, 0x0f, 0xc5, 0xb5, 0x97, 0xbb, 0xdc, 0x03, 0x2a, 0x90, 0x9d, 0x9e, 0x0a, 0xe8, 0xa0, 0xea, + 0xab, 0xc0, 0xdd, 0x0f, 0x92, 0x57, 0x0c, 0x5c, 0x82, 0x7f, 0x6b, 0x2b, 0x3e, 0x29, 0x30, 0x0a, 0xed, 0x84, 0x75, + 0x0c, 0x6a, 0xe0, 0x49, 0xd3, 0xab, 0x2f, 0x1f, 0x58, 0xa6, 0x0e, 0xd2, 0xd6, 0xb1, 0x75, 0xc9, 0xb2, 0xe2, 0xdc, + 0x29, 0x93, 0x7f, 0x9a, 0x4b, 0x19, 0x53, 0x1a, 0x04, 0x37, 0x32, 0x69, 0x36, 0xd2, 0x8b, 0x31, 0x8e, 0x44, 0x84, + 0xed, 0x9e, 0xab, 0xb4, 0x05, 0x46, 0xf9, 0x55, 0xaa, 0x91, 0x66, 0x67, 0x6d, 0xd7, 0xd7, 0x8d, 0x30, 0x28, 0x85, + 0x8d, 0x80, 0xbb, 0x49, 0xf2, 0x7e, 0xb6, 0x9c, 0x75, 0xc9, 0x72, 0x57, 0xf9, 0xb8, 0x08, 0x0a, 0x42, 0x56, 0xbb, + 0x44, 0xca, 0xb3, 0x60, 0xba, 0x9e, 0xe4, 0x1f, 0x1a, 0x24, 0xff, 0x28, 0xe0, 0x06, 0xf9, 0x4b, 0x0f, 0x87, 0x97, + 0x2a, 0xd7, 0x42, 0xae, 0xab, 0x0e, 0xa7, 0x01, 0xfa, 0xd0, 0xea, 0x18, 0xad, 0x45, 0x15, 0xd7, 0x30, 0x14, 0xf3, + 0x84, 0x90, 0x17, 0x92, 0xe9, 0x10, 0xb0, 0x53, 0xc5, 0xd4, 0x70, 0xea, 0x55, 0x2e, 0x3d, 0x93, 0x03, 0x3e, 0x7c, + 0x7f, 0x73, 0x38, 0xf4, 0x70, 0xba, 0x7c, 0x72, 0x8d, 0xec, 0x4f, 0x5c, 0xb5, 0x71, 0x70, 0xeb, 0xb9, 0xa0, 0x38, + 0x7f, 0x19, 0xc6, 0xae, 0x33, 0x9f, 0x85, 0x43, 0xa8, 0xe5, 0x1f, 0x42, 0xdb, 0x6a, 0x51, 0x0b, 0x6e, 0x0c, 0x8b, + 0xfc, 0x48, 0xe6, 0xa0, 0x86, 0xd9, 0x1a, 0xf6, 0xf1, 0x90, 0x1a, 0x80, 0x84, 0x5d, 0x5d, 0xfd, 0xa8, 0x50, 0x64, + 0x22, 0x41, 0x03, 0x26, 0x06, 0xfc, 0x4f, 0x92, 0x3c, 0xd2, 0x0d, 0xc9, 0x05, 0x44, 0xd0, 0x94, 0xf0, 0x54, 0x21, + 0xcc, 0xb6, 0x2b, 0xe7, 0xfb, 0x33, 0x58, 0xc2, 0xb4, 0x72, 0x3e, 0xbe, 0xad, 0x72, 0xaf, 0x90, 0x2c, 0xc0, 0x40, + 0x44, 0x3f, 0xbb, 0x2e, 0x90, 0xd1, 0xcb, 0x43, 0xdd, 0x9c, 0x0c, 0x48, 0xaf, 0xd2, 0xb7, 0x8d, 0xc8, 0x26, 0x79, + 0xe5, 0x64, 0xbd, 0x46, 0xc3, 0x42, 0xed, 0x26, 0xd6, 0xbe, 0x14, 0x04, 0x23, 0x3e, 0xbf, 0xa3, 0xd6, 0x7a, 0xdc, + 0xe2, 0xd3, 0x62, 0x02, 0xcb, 0xc2, 0xa6, 0xc0, 0x01, 0xcd, 0xc1, 0x34, 0x7e, 0xc4, 0xe1, 0x94, 0x61, 0xc8, 0xa2, + 0xc4, 0x89, 0x5b, 0x6c, 0x1a, 0x6e, 0x3b, 0x5a, 0x9f, 0x11, 0x27, 0x58, 0x58, 0x20, 0x7d, 0xfb, 0x44, 0x31, 0xeb, + 0x0f, 0x8b, 0xbd, 0x00, 0x2b, 0xef, 0x2a, 0x34, 0x29, 0x28, 0x09, 0x0a, 0x83, 0x69, 0x49, 0x95, 0x46, 0x05, 0x72, + 0x37, 0x9d, 0xd2, 0x05, 0xa0, 0x19, 0x86, 0xc9, 0x7b, 0x60, 0xba, 0x62, 0xb4, 0xc8, 0xe2, 0x95, 0x6b, 0x22, 0x32, + 0xcd, 0x16, 0xe4, 0xf0, 0x68, 0x68, 0x4b, 0x5f, 0x51, 0x5e, 0xa5, 0xc3, 0x96, 0x30, 0x1c, 0x22, 0xb2, 0x1c, 0x32, + 0x42, 0x0c, 0x0a, 0x5c, 0x69, 0x94, 0xbc, 0x46, 0xbd, 0x72, 0xcc, 0xe0, 0x1f, 0x26, 0xc0, 0xd6, 0x8e, 0x2c, 0xc0, + 0x26, 0xf3, 0x72, 0x8c, 0x4c, 0x02, 0x58, 0xe9, 0x0a, 0x8f, 0xb2, 0x26, 0x6a, 0x4e, 0x52, 0x07, 0x5b, 0x64, 0x6e, + 0xd9, 0xc1, 0x3b, 0x77, 0x22, 0xa5, 0xb8, 0xe9, 0xb0, 0x19, 0x32, 0xe0, 0x8f, 0xc2, 0x91, 0xb1, 0x28, 0x94, 0x19, + 0xa9, 0x37, 0x73, 0x6a, 0x53, 0x77, 0x52, 0xea, 0xc6, 0x14, 0xe2, 0xc6, 0x26, 0x9a, 0x52, 0x0a, 0xeb, 0x1d, 0x56, + 0xbc, 0x74, 0x53, 0xe6, 0x50, 0x0b, 0xcd, 0x05, 0xab, 0x3c, 0x12, 0x63, 0xf9, 0x9b, 0x32, 0x2d, 0xba, 0x6c, 0x84, + 0x6a, 0x18, 0x80, 0xf1, 0x8a, 0xf6, 0x80, 0x17, 0x48, 0x5f, 0xf3, 0x23, 0x61, 0xec, 0xa8, 0xf6, 0x61, 0xd3, 0x9c, + 0x86, 0xd4, 0x7f, 0x8b, 0x99, 0x2e, 0x8b, 0x67, 0xfe, 0x19, 0xc9, 0x42, 0x60, 0xa4, 0x35, 0xc6, 0x9e, 0x11, 0x63, + 0x77, 0x51, 0x4f, 0xd3, 0xa9, 0xdf, 0x3d, 0x95, 0xf0, 0x12, 0x29, 0x29, 0xa7, 0x48, 0xec, 0x7d, 0x19, 0x2c, 0x37, + 0xbe, 0x2f, 0xec, 0x86, 0x1f, 0x05, 0x98, 0x04, 0xc4, 0x14, 0x67, 0xcf, 0x60, 0x7b, 0xb6, 0xb6, 0x3a, 0xf9, 0x01, + 0xaf, 0x5c, 0x24, 0x15, 0x8c, 0x11, 0xc6, 0x73, 0x91, 0xe0, 0x6b, 0x32, 0x14, 0x23, 0xfe, 0x3a, 0x37, 0x3b, 0x47, + 0x57, 0x3b, 0xb4, 0x34, 0xb9, 0x9a, 0xd9, 0xb6, 0x8c, 0x99, 0xe2, 0x7a, 0x9c, 0x2a, 0xde, 0xf2, 0xe6, 0xe6, 0xfc, + 0x0e, 0x84, 0x7b, 0xa3, 0xc5, 0x30, 0x17, 0xcd, 0xed, 0x08, 0xc9, 0x12, 0xca, 0xd3, 0xd7, 0xd1, 0x8a, 0x34, 0x26, + 0x4c, 0x1b, 0x93, 0x75, 0x44, 0x65, 0xca, 0x8a, 0x20, 0x07, 0x45, 0x9c, 0x57, 0xd1, 0xfd, 0x85, 0xfc, 0x4b, 0x12, + 0x2e, 0xcb, 0xce, 0x76, 0x10, 0x2b, 0x82, 0x19, 0x84, 0xfa, 0x66, 0x5d, 0xe8, 0xa3, 0x02, 0x13, 0xcf, 0xb5, 0x12, + 0x8a, 0xbf, 0xad, 0x12, 0x8a, 0x2c, 0x53, 0x47, 0x9e, 0x04, 0x62, 0xeb, 0x16, 0x02, 0x51, 0x39, 0xd9, 0xb5, 0x4c, + 0x44, 0x75, 0xa4, 0x26, 0x13, 0xeb, 0x5b, 0x1a, 0x64, 0xb0, 0x97, 0x72, 0x37, 0xba, 0x6d, 0xc0, 0x20, 0x9c, 0xc0, + 0x0d, 0x64, 0xbf, 0xf8, 0xb5, 0x25, 0xbf, 0x1a, 0x9c, 0x58, 0x3a, 0x81, 0x9d, 0xa8, 0x34, 0x59, 0x5c, 0x0f, 0x53, + 0x9c, 0x1d, 0xca, 0xc9, 0x22, 0x9a, 0x56, 0x14, 0xa4, 0x08, 0x3c, 0x88, 0xca, 0x28, 0x13, 0x42, 0x4c, 0xb2, 0x42, + 0x19, 0x90, 0xce, 0xca, 0xe4, 0x3f, 0x6d, 0x5e, 0x7e, 0x5e, 0x13, 0xad, 0xc9, 0x15, 0xa9, 0x3e, 0xd4, 0xd2, 0x0d, + 0x14, 0x04, 0x4a, 0x3f, 0xdc, 0x20, 0x13, 0xb4, 0x12, 0xe5, 0xae, 0x29, 0x87, 0x58, 0x13, 0x5d, 0x68, 0x1b, 0xef, + 0x64, 0x80, 0x77, 0x85, 0x34, 0x61, 0xa9, 0x41, 0xd7, 0xc0, 0x23, 0x6b, 0xac, 0x64, 0x1c, 0x28, 0x4b, 0x89, 0x85, + 0x44, 0xa6, 0x22, 0xc8, 0x00, 0x89, 0xa8, 0x80, 0x76, 0xe2, 0x83, 0xac, 0x32, 0x81, 0x63, 0x50, 0xcb, 0x42, 0x3d, + 0xeb, 0xf8, 0x38, 0x02, 0xc5, 0x0e, 0xa6, 0x8e, 0xa5, 0x61, 0x02, 0x02, 0x0b, 0x14, 0xbc, 0x72, 0x8a, 0xe3, 0x18, + 0xf8, 0x60, 0x0a, 0xa7, 0x9f, 0xc0, 0x0a, 0x01, 0xac, 0xd0, 0x04, 0x8b, 0xaa, 0xb1, 0xdb, 0x14, 0x84, 0xe7, 0x94, + 0x04, 0xe0, 0x14, 0x21, 0xdc, 0x02, 0x2d, 0x51, 0x39, 0xf7, 0x42, 0x74, 0x46, 0x6d, 0x65, 0xc7, 0xf1, 0x56, 0xeb, + 0xc4, 0x60, 0x5c, 0xd0, 0x33, 0x08, 0x0b, 0x58, 0xce, 0x46, 0xae, 0x44, 0xe4, 0x47, 0x14, 0x65, 0x1f, 0x49, 0xb2, + 0xc8, 0x01, 0xcd, 0xdd, 0x58, 0x74, 0x06, 0x94, 0x14, 0xa5, 0xb6, 0x55, 0xb7, 0xab, 0x25, 0x41, 0x94, 0x8d, 0x98, + 0x8a, 0x05, 0xf7, 0xd0, 0xb2, 0x2f, 0xc9, 0xfc, 0xb9, 0x28, 0x93, 0xac, 0xeb, 0x14, 0xaf, 0x53, 0xab, 0x3d, 0xcf, + 0x0b, 0xb3, 0x11, 0x45, 0x32, 0x74, 0x14, 0x96, 0x88, 0x7f, 0x47, 0x81, 0x69, 0x4c, 0x7c, 0x5c, 0xce, 0x75, 0x12, + 0x48, 0xf0, 0xb5, 0x6a, 0xa3, 0x6f, 0x93, 0xfc, 0xba, 0xd2, 0xcb, 0xa0, 0x0e, 0xdc, 0xef, 0x85, 0x64, 0x57, 0x41, + 0x22, 0xc9, 0x63, 0x01, 0x67, 0x6b, 0x70, 0xf1, 0xab, 0x58, 0xc0, 0xd9, 0x7a, 0xdc, 0x6a, 0x4c, 0xfd, 0xb0, 0x0e, + 0x3e, 0x83, 0x37, 0x48, 0x40, 0xab, 0x02, 0x03, 0xca, 0xbd, 0x45, 0xdd, 0x4b, 0xb2, 0x52, 0x14, 0xa6, 0x22, 0x00, + 0x66, 0x5a, 0x3b, 0x00, 0x95, 0x36, 0x6a, 0x18, 0xbe, 0x91, 0x3f, 0x75, 0x0d, 0x97, 0x40, 0x3d, 0x73, 0x05, 0xc9, + 0x49, 0xf9, 0xda, 0x81, 0xf8, 0xd0, 0x36, 0x40, 0x25, 0x0e, 0x58, 0xdb, 0x14, 0xda, 0xa2, 0x2a, 0x95, 0xeb, 0xef, + 0x58, 0x8c, 0xf7, 0x40, 0xa8, 0x0c, 0xbf, 0x60, 0xc1, 0x14, 0x56, 0x08, 0xd6, 0x3f, 0x95, 0xa9, 0xef, 0x70, 0x08, + 0x35, 0x29, 0xe7, 0x52, 0x27, 0xcc, 0x40, 0x8c, 0x2a, 0x3a, 0xad, 0xa3, 0xed, 0xc9, 0x39, 0x7c, 0x7f, 0x11, 0xe5, + 0x80, 0x1d, 0x5c, 0x7e, 0x45, 0x71, 0xb8, 0x22, 0xd8, 0xab, 0x74, 0xa1, 0x57, 0x38, 0x18, 0xdc, 0xd8, 0x45, 0xd4, + 0x75, 0xa0, 0x71, 0x98, 0x0c, 0x62, 0x39, 0x89, 0x99, 0xce, 0xa8, 0x53, 0x38, 0xcb, 0x96, 0x66, 0x3a, 0x4d, 0xa5, + 0x6c, 0x10, 0x77, 0x07, 0x04, 0x6b, 0x49, 0xa0, 0xa5, 0xe7, 0x8d, 0x5a, 0x0b, 0x06, 0xbc, 0xd7, 0x6c, 0x82, 0xb9, + 0x12, 0x26, 0x0c, 0x8e, 0xea, 0xd5, 0xe1, 0xd4, 0x74, 0xf3, 0x74, 0xe5, 0xa5, 0xb6, 0x55, 0xc2, 0x81, 0xe8, 0xe4, + 0xde, 0x7a, 0xcb, 0xea, 0xa5, 0x96, 0x1c, 0x5a, 0x5a, 0x44, 0xb7, 0x65, 0xcc, 0xee, 0x5c, 0x93, 0x97, 0xab, 0x8f, + 0xe2, 0x07, 0x11, 0x7c, 0xc4, 0x5b, 0x43, 0xcf, 0xc4, 0x24, 0x5e, 0xb8, 0x1c, 0xd3, 0xf9, 0x50, 0x6a, 0xff, 0x1f, + 0x84, 0xf3, 0x8a, 0x3d, 0xc3, 0xb0, 0xee, 0xb7, 0x55, 0xf3, 0xe5, 0x70, 0xee, 0xb7, 0x15, 0x82, 0xbe, 0xf5, 0x97, + 0xda, 0x19, 0x61, 0xdc, 0xb6, 0xb7, 0xef, 0x35, 0x6d, 0xad, 0x2d, 0xfd, 0x28, 0x83, 0x48, 0x32, 0xd1, 0x92, 0xce, + 0x03, 0xab, 0xd2, 0xd4, 0x30, 0x5d, 0xae, 0x6e, 0x21, 0x71, 0x95, 0x60, 0x28, 0x75, 0xf8, 0x75, 0xdb, 0xa3, 0x64, + 0x4c, 0x26, 0xed, 0x8c, 0x37, 0x60, 0x2b, 0x6d, 0xe2, 0x29, 0x4b, 0x97, 0x6e, 0xe2, 0x8d, 0x03, 0xf4, 0xa0, 0xdd, + 0x6e, 0x0a, 0xc3, 0xd8, 0xce, 0xe5, 0x4d, 0x20, 0x73, 0xfc, 0x20, 0xd5, 0xba, 0x5b, 0xdd, 0xca, 0x78, 0x8f, 0xf6, + 0x3f, 0xfc, 0xaf, 0xaf, 0xc7, 0x71, 0xc5, 0x81, 0xb9, 0x3f, 0x2f, 0x4a, 0xa7, 0x40, 0x2a, 0x95, 0xb7, 0x04, 0x8e, + 0x49, 0x41, 0xe1, 0xed, 0xef, 0xd9, 0x4f, 0x8a, 0x25, 0x0e, 0x4b, 0x8e, 0xf3, 0xe4, 0xb6, 0x1c, 0x51, 0x82, 0x5f, + 0x46, 0xef, 0x91, 0x8e, 0x89, 0x42, 0x0b, 0x4d, 0x45, 0x8f, 0x53, 0xb5, 0x90, 0xb5, 0x59, 0xa9, 0x4c, 0x1b, 0xb0, + 0x51, 0x40, 0xd3, 0xac, 0x48, 0xe3, 0xd4, 0x56, 0xb6, 0x28, 0x4f, 0x55, 0x6d, 0x5e, 0x77, 0x0d, 0x16, 0xab, 0xc0, + 0x22, 0x08, 0xd3, 0x3a, 0xaa, 0x83, 0xc8, 0x88, 0x63, 0xb8, 0x2c, 0x32, 0x12, 0x2a, 0x6a, 0x9a, 0xb5, 0xec, 0xe3, + 0xb8, 0x8b, 0xf9, 0x44, 0x5a, 0x37, 0xaf, 0xc1, 0x61, 0xba, 0x10, 0x64, 0x77, 0xd3, 0xa7, 0xc0, 0xe4, 0xea, 0xca, + 0x89, 0x0c, 0x0c, 0xfd, 0x58, 0x66, 0xca, 0x56, 0x29, 0xad, 0x2b, 0xf0, 0xeb, 0xde, 0x90, 0x2b, 0xab, 0x50, 0xb7, + 0x5c, 0x6f, 0xe4, 0x1a, 0x3d, 0x4e, 0xd7, 0xe5, 0x1a, 0xd5, 0xb4, 0xdd, 0x8d, 0xa6, 0x7b, 0x73, 0x56, 0xaa, 0x9c, + 0x6b, 0x75, 0x93, 0xdf, 0x31, 0x5d, 0x0b, 0x69, 0x53, 0xa2, 0x59, 0x73, 0x95, 0xc3, 0xa2, 0x18, 0x96, 0x77, 0x09, + 0x28, 0x75, 0x67, 0x28, 0xe9, 0x5f, 0x59, 0x8d, 0x74, 0x21, 0xd7, 0xf9, 0x3e, 0x18, 0xc5, 0xe9, 0x59, 0x18, 0xbf, + 0xc3, 0xf9, 0xaa, 0xca, 0x67, 0x57, 0x83, 0x0c, 0x50, 0xac, 0xb8, 0x4b, 0x05, 0xc3, 0xf7, 0x06, 0x0c, 0xdf, 0x4b, + 0x3e, 0x5d, 0xf5, 0x67, 0xf3, 0x17, 0xe5, 0x00, 0xfe, 0xb0, 0xd0, 0x2c, 0x63, 0x22, 0x56, 0xcf, 0xb1, 0xc8, 0xc2, + 0x26, 0x25, 0x0b, 0x9b, 0x08, 0x67, 0x71, 0x28, 0xc7, 0xf9, 0x69, 0xf5, 0x28, 0xcb, 0x9c, 0xed, 0xa7, 0xea, 0xe0, + 0xff, 0xe4, 0xdf, 0xd8, 0xc7, 0xe0, 0x72, 0x3b, 0xde, 0x0e, 0x25, 0xab, 0x48, 0x90, 0x1f, 0x61, 0xd2, 0x81, 0x80, + 0xff, 0xab, 0x2b, 0x07, 0x95, 0x9c, 0xd2, 0x79, 0x40, 0x4e, 0x7f, 0x96, 0x8b, 0x74, 0xa2, 0xc6, 0xcc, 0xd5, 0x3d, + 0x23, 0xaa, 0x44, 0x57, 0x34, 0xc5, 0xda, 0xfd, 0xfa, 0x4d, 0xae, 0xf9, 0xa7, 0x28, 0x19, 0xf8, 0x60, 0xb6, 0xaa, + 0x3e, 0x7e, 0x56, 0x04, 0x3a, 0xd7, 0x78, 0xb9, 0x8e, 0xc1, 0x78, 0x51, 0x3e, 0x86, 0x4d, 0x4d, 0xe1, 0x48, 0xad, + 0x99, 0x2c, 0xc5, 0x80, 0x8c, 0x9c, 0x8c, 0xfd, 0x5c, 0x5d, 0xf9, 0xf3, 0x70, 0x34, 0xf4, 0x03, 0x4d, 0xb8, 0x18, + 0xa7, 0x03, 0x4c, 0x4b, 0x81, 0x3e, 0xfa, 0x4a, 0x13, 0xa8, 0xaf, 0x8e, 0x4d, 0x6e, 0x09, 0xbc, 0xfc, 0x6d, 0xd6, + 0xb8, 0xbd, 0x39, 0xde, 0xce, 0xa9, 0xa6, 0x06, 0x0b, 0x92, 0x2f, 0x5e, 0x64, 0x81, 0xd1, 0xf9, 0x15, 0x4b, 0x60, + 0x66, 0x5f, 0x42, 0x6d, 0x0f, 0x23, 0x1e, 0x0f, 0x6c, 0x06, 0xc5, 0x7e, 0x79, 0x5f, 0x9c, 0xae, 0x37, 0xd3, 0x06, + 0xda, 0xe9, 0x45, 0x62, 0xb3, 0x6a, 0x12, 0xe0, 0xa5, 0x2c, 0xcd, 0xa2, 0x11, 0x12, 0xe7, 0x77, 0xd0, 0x45, 0x8e, + 0x17, 0x19, 0xb7, 0xf5, 0xdc, 0xb9, 0x46, 0xbd, 0x67, 0x14, 0x9b, 0xdb, 0xa0, 0x0c, 0x8a, 0x63, 0xea, 0x0b, 0xca, + 0xab, 0xd9, 0xae, 0x32, 0x0f, 0xc1, 0x38, 0xbc, 0xed, 0x52, 0xd8, 0xb7, 0xa6, 0x68, 0x13, 0xb5, 0xcc, 0xd7, 0x85, + 0x4e, 0x1c, 0x3b, 0x54, 0x99, 0x1e, 0x1f, 0x40, 0x12, 0xcc, 0x35, 0x7b, 0xa5, 0xee, 0x86, 0x23, 0xec, 0x5b, 0xa1, + 0x06, 0xf5, 0x7f, 0x96, 0x09, 0x21, 0x55, 0x24, 0xe9, 0x65, 0xd5, 0x0f, 0xc6, 0x40, 0xbc, 0x63, 0x42, 0x4b, 0x48, + 0x17, 0x32, 0x0b, 0x9d, 0x2d, 0xfa, 0x1d, 0x40, 0x35, 0xd7, 0x4b, 0xf0, 0x13, 0x13, 0x8b, 0xa2, 0x40, 0x2a, 0x54, + 0xf4, 0x25, 0x13, 0x00, 0xf1, 0x0e, 0xfb, 0x92, 0xd4, 0xcc, 0x48, 0x6a, 0x7a, 0x06, 0xc6, 0xd7, 0x48, 0x49, 0x4e, + 0xc8, 0x20, 0x25, 0x92, 0x84, 0x9e, 0xda, 0x5c, 0x45, 0x42, 0xe6, 0x86, 0x96, 0xf7, 0xe7, 0xe4, 0x9e, 0x67, 0x35, + 0xb0, 0x1c, 0x1a, 0xc7, 0x05, 0xe2, 0xc0, 0xa4, 0x1d, 0xd9, 0xa0, 0x28, 0xaf, 0x65, 0xeb, 0xf4, 0x56, 0x27, 0xf5, + 0xf4, 0xb2, 0x02, 0x8d, 0x12, 0x67, 0xec, 0xce, 0xe1, 0x0f, 0xa8, 0xe1, 0x05, 0xca, 0xd6, 0x12, 0x7e, 0x6e, 0xee, + 0x46, 0x2d, 0x59, 0x79, 0xf5, 0x1d, 0x3f, 0x54, 0xe6, 0x05, 0xa6, 0x68, 0xb2, 0x44, 0xf3, 0x94, 0xc4, 0xa1, 0xcb, + 0x76, 0xc6, 0xb6, 0x7d, 0xaf, 0x12, 0x74, 0x14, 0x60, 0xdf, 0x01, 0xd3, 0x31, 0x56, 0x61, 0xde, 0xe6, 0x56, 0x77, + 0xfe, 0x54, 0xb0, 0xaf, 0xca, 0x21, 0x75, 0xf2, 0x60, 0x41, 0xe2, 0xdc, 0x9c, 0x6a, 0xf9, 0xeb, 0x8c, 0x67, 0x57, + 0x47, 0x1c, 0x53, 0x9d, 0x53, 0xbc, 0xed, 0x5b, 0x6d, 0x43, 0x95, 0xa6, 0xde, 0xcb, 0x48, 0x59, 0x29, 0xea, 0xb7, + 0x00, 0x17, 0xef, 0x08, 0x16, 0x14, 0x6d, 0x34, 0x1c, 0x31, 0xf2, 0xb4, 0xf0, 0xb5, 0xb7, 0x27, 0x79, 0x27, 0x42, + 0xff, 0x5a, 0x85, 0x69, 0x15, 0x2c, 0x60, 0xa9, 0x79, 0x23, 0xf5, 0x38, 0x3f, 0x59, 0xf4, 0xca, 0x60, 0x11, 0x86, + 0xef, 0xb2, 0xf5, 0x4b, 0x5d, 0x95, 0x34, 0xbb, 0x7e, 0xa9, 0xb5, 0xa0, 0x1f, 0x25, 0xfc, 0x30, 0x35, 0x4f, 0x79, + 0x7d, 0x39, 0x02, 0x8e, 0x56, 0x20, 0x78, 0xdf, 0x00, 0xdf, 0xff, 0x46, 0xa5, 0x0c, 0x7a, 0x18, 0x8b, 0x3d, 0x8a, + 0x53, 0xcd, 0xc4, 0xab, 0xf9, 0xbf, 0x59, 0x9a, 0xff, 0x1b, 0xe3, 0xce, 0x29, 0x9a, 0x46, 0xa3, 0x84, 0x0f, 0x34, + 0xeb, 0x74, 0x25, 0x01, 0x92, 0xde, 0x06, 0x8a, 0xfc, 0xeb, 0x53, 0x1f, 0x35, 0xae, 0xf9, 0x30, 0x4d, 0x44, 0x63, + 0x18, 0x4e, 0xa2, 0xf8, 0xca, 0x9f, 0x45, 0x8d, 0x49, 0x9a, 0xa4, 0xf9, 0x14, 0xe8, 0x9d, 0xe5, 0x57, 0x60, 0xf1, + 0x4c, 0x1a, 0xb3, 0x88, 0x3d, 0xe3, 0xf1, 0x39, 0x17, 0x51, 0x3f, 0x64, 0xf6, 0xc3, 0x0c, 0x58, 0x8d, 0xf5, 0x2a, + 0xcc, 0xb2, 0xf4, 0xc2, 0x66, 0x6f, 0xd3, 0x33, 0x98, 0x8d, 0xbd, 0xbe, 0xbc, 0x1a, 0xf1, 0x84, 0xbd, 0x3f, 0x9b, + 0x25, 0x62, 0xc6, 0xf2, 0x30, 0xc9, 0x1b, 0xa0, 0x59, 0x46, 0x43, 0x10, 0x2a, 0x71, 0x9a, 0x35, 0x30, 0x63, 0x7b, + 0xc2, 0xfd, 0x38, 0x1a, 0x8d, 0x85, 0x35, 0x08, 0xb3, 0x4f, 0x9d, 0x46, 0x63, 0x9a, 0x45, 0x93, 0x30, 0xbb, 0x6a, + 0x50, 0x0b, 0xff, 0xeb, 0xe6, 0x4e, 0xf8, 0x60, 0xb8, 0xdb, 0x11, 0x19, 0xf4, 0x8d, 0x70, 0x9b, 0x7c, 0x60, 0x64, + 0xd6, 0xce, 0x5e, 0x73, 0x92, 0x6f, 0xc8, 0x40, 0x5e, 0x98, 0x88, 0xe2, 0x94, 0xbd, 0x41, 0xb8, 0xbd, 0x33, 0x91, + 0x30, 0xb0, 0x7c, 0x45, 0x9a, 0x80, 0x74, 0xc8, 0x72, 0x18, 0x60, 0x9a, 0x46, 0x89, 0xe0, 0x59, 0xe7, 0x2c, 0xcd, + 0x60, 0x97, 0x1a, 0x59, 0x38, 0x88, 0x66, 0xb9, 0xbf, 0x3b, 0xbd, 0xec, 0xa0, 0x66, 0x31, 0xca, 0xd2, 0x59, 0x32, + 0x50, 0x73, 0x45, 0x09, 0x1c, 0xbc, 0x48, 0x98, 0x15, 0xf4, 0x12, 0x13, 0x80, 0x2f, 0xe1, 0x61, 0xd6, 0x18, 0x61, + 0x67, 0x34, 0x8b, 0x9a, 0x03, 0x3e, 0x62, 0xd9, 0xe8, 0x2c, 0x74, 0x5a, 0xed, 0xfb, 0x4c, 0xff, 0xf3, 0xf6, 0x5c, + 0xd0, 0x8e, 0x57, 0x16, 0xb7, 0x9a, 0xcd, 0x7f, 0x72, 0x3b, 0x0b, 0xb3, 0x10, 0x40, 0x7e, 0x6b, 0x7a, 0x69, 0xe5, + 0x29, 0x66, 0xb4, 0xad, 0xea, 0xd9, 0x99, 0x82, 0x95, 0x19, 0x25, 0x23, 0xbf, 0x3d, 0xbd, 0x2c, 0x70, 0x75, 0xbe, + 0x4c, 0x31, 0x55, 0x8b, 0x54, 0x4f, 0xf3, 0xdf, 0x0b, 0xf1, 0xfe, 0x6a, 0x88, 0xdb, 0x1a, 0xe2, 0x0a, 0xeb, 0x8d, + 0x01, 0x1c, 0x34, 0x42, 0x7f, 0x2b, 0x97, 0x80, 0x8c, 0xc1, 0x64, 0xce, 0x34, 0x1c, 0xf4, 0xf0, 0xbb, 0xc1, 0x68, + 0xaf, 0x06, 0x63, 0xff, 0x73, 0x60, 0x64, 0xc9, 0x60, 0x5e, 0xdf, 0xd7, 0x16, 0x98, 0xf2, 0x9d, 0x31, 0x47, 0x7a, + 0xf2, 0xdb, 0xf8, 0xfd, 0x22, 0x1a, 0x88, 0xb1, 0xfc, 0x4a, 0xe4, 0x7c, 0x21, 0xeb, 0xf6, 0x9a, 0x4d, 0xf9, 0x9c, + 0x83, 0x70, 0xf4, 0x5b, 0x1e, 0x36, 0x00, 0x22, 0xfa, 0xa9, 0xbc, 0xcb, 0x5b, 0xe7, 0x9e, 0xec, 0x1b, 0xf3, 0x92, + 0xaf, 0x91, 0xa2, 0x58, 0x5d, 0x89, 0x66, 0x99, 0x96, 0x95, 0x52, 0xf8, 0xa0, 0xdb, 0x8e, 0xb8, 0x63, 0x10, 0x75, + 0xcb, 0x4b, 0x9c, 0x51, 0xef, 0x1b, 0x99, 0x77, 0xe1, 0x63, 0xa4, 0xc3, 0x48, 0x35, 0x04, 0x96, 0xd3, 0x0d, 0x9a, + 0x9d, 0xac, 0xd1, 0x70, 0x81, 0xb3, 0x24, 0xc7, 0x99, 0x4a, 0xce, 0x73, 0xa2, 0x5e, 0x4a, 0xc6, 0x76, 0xee, 0xfa, + 0x29, 0xde, 0x34, 0x05, 0x2e, 0x5a, 0x25, 0x64, 0xd0, 0x6d, 0x8d, 0x9f, 0x84, 0x6a, 0xc0, 0x72, 0x83, 0x93, 0xa7, + 0xfa, 0xd5, 0x2e, 0x89, 0xe6, 0x15, 0x71, 0xda, 0x27, 0xcc, 0x79, 0xd3, 0x50, 0x8c, 0xd1, 0x4b, 0x51, 0x8a, 0x9f, + 0x2a, 0x85, 0xc9, 0xde, 0xb6, 0xdd, 0x5e, 0x52, 0xe6, 0xb7, 0x61, 0x1e, 0x5f, 0x52, 0xe0, 0x28, 0x57, 0x22, 0x20, + 0x8b, 0xa9, 0x1c, 0xff, 0xbd, 0x30, 0x24, 0x75, 0x02, 0x9a, 0x46, 0x3f, 0x9e, 0x81, 0xa8, 0xa0, 0x11, 0x2a, 0x71, + 0xfe, 0x37, 0xb3, 0x15, 0x75, 0xc1, 0xd1, 0x29, 0x9b, 0x07, 0x1b, 0xe2, 0x1b, 0x54, 0xca, 0xe7, 0x06, 0x3d, 0x57, + 0x7d, 0xf5, 0x4b, 0x25, 0x80, 0xab, 0x63, 0x4f, 0x6f, 0x96, 0x44, 0xc0, 0x41, 0x3f, 0x44, 0x03, 0xe3, 0xde, 0x2e, + 0x4f, 0xfa, 0xe9, 0x80, 0xbf, 0x7f, 0xfb, 0x1c, 0xb3, 0xdd, 0xd3, 0x04, 0x49, 0x2c, 0x91, 0xfe, 0x2e, 0x96, 0x03, + 0x7a, 0x07, 0xfc, 0x1c, 0x16, 0xd2, 0x3b, 0xdd, 0x9c, 0xaf, 0x6c, 0x28, 0xab, 0xdd, 0x62, 0xfb, 0x94, 0x92, 0xfe, + 0x08, 0x4a, 0x68, 0x7b, 0x25, 0x8a, 0xed, 0xcd, 0x39, 0x54, 0xa7, 0x93, 0x30, 0x4a, 0xf0, 0x7b, 0x5e, 0x6c, 0xce, + 0x23, 0xfc, 0x02, 0x8c, 0xa6, 0xa8, 0x12, 0x45, 0x4b, 0x88, 0x8c, 0x25, 0x28, 0xdc, 0xb5, 0x5c, 0xef, 0x23, 0x30, + 0x1e, 0x2a, 0xba, 0x69, 0x64, 0xae, 0x47, 0x45, 0x24, 0x3f, 0x0f, 0xa4, 0xc1, 0xac, 0xcd, 0xe5, 0xe1, 0x6d, 0xcd, + 0x65, 0xf8, 0x1a, 0x51, 0x5a, 0xbc, 0x0e, 0xe7, 0x80, 0x45, 0xf9, 0xa1, 0x2f, 0xef, 0xa1, 0xe6, 0xd5, 0xad, 0x8b, + 0x90, 0x10, 0x2b, 0x2d, 0x60, 0xd0, 0x30, 0xd0, 0xd8, 0xe7, 0xeb, 0x2f, 0x4a, 0x26, 0x37, 0x19, 0x7f, 0x25, 0x55, + 0xe5, 0xe9, 0x2c, 0xeb, 0x63, 0xac, 0x57, 0xa9, 0x14, 0xcb, 0x5e, 0x31, 0x9b, 0xf4, 0x37, 0x9b, 0x09, 0x23, 0xc9, + 0x56, 0xb0, 0xc8, 0x7c, 0x67, 0x07, 0xa7, 0x78, 0xa2, 0xbc, 0x0b, 0xa3, 0xf4, 0x07, 0xbd, 0x24, 0x54, 0x88, 0x06, + 0x94, 0x2f, 0x0a, 0xe2, 0xb6, 0x9b, 0x55, 0x38, 0x07, 0x01, 0x17, 0x79, 0x40, 0x0c, 0x28, 0xf5, 0x51, 0xb1, 0x68, + 0xb4, 0x30, 0x32, 0x04, 0x05, 0xa5, 0x86, 0x14, 0x29, 0x3c, 0x5f, 0x5f, 0x03, 0x19, 0xca, 0xb6, 0xd2, 0xa9, 0x82, + 0x3a, 0x58, 0xc4, 0x64, 0x25, 0xe8, 0x69, 0xe5, 0x90, 0x3e, 0x36, 0x2a, 0x3a, 0x29, 0xa1, 0x4f, 0x22, 0x2b, 0xd0, + 0xe8, 0x7c, 0x28, 0x55, 0x84, 0x14, 0x74, 0x30, 0xa3, 0xba, 0xbc, 0xc0, 0x5f, 0xc3, 0x77, 0x73, 0x61, 0x5b, 0xa4, + 0x3d, 0x95, 0x2e, 0x96, 0x82, 0x74, 0x12, 0x0e, 0x68, 0x76, 0x31, 0xb0, 0x8b, 0x31, 0x51, 0xed, 0x41, 0x4c, 0x1f, + 0xbd, 0x46, 0xcb, 0x6f, 0x95, 0x9e, 0x90, 0xda, 0xbd, 0x6a, 0x99, 0x67, 0xa6, 0xee, 0xe6, 0x22, 0xb8, 0xac, 0xfc, + 0x2e, 0xd7, 0x53, 0x3d, 0x97, 0xcb, 0x62, 0x8a, 0x73, 0x49, 0xa9, 0xef, 0xd4, 0x80, 0xa0, 0xb8, 0xdb, 0x9a, 0xa9, + 0xdc, 0xa2, 0x5a, 0x77, 0x79, 0x8a, 0x4f, 0xa5, 0xb6, 0xf3, 0xc1, 0x20, 0xe3, 0xd3, 0x48, 0xfb, 0xeb, 0xea, 0x04, + 0x56, 0x28, 0x8c, 0x18, 0x2c, 0x60, 0x55, 0x33, 0x09, 0xcb, 0x55, 0x90, 0xac, 0xa4, 0x52, 0x4f, 0x3e, 0x72, 0xa9, + 0x6b, 0xad, 0x6e, 0x42, 0x59, 0x8f, 0xab, 0x00, 0x43, 0x0f, 0x40, 0x2e, 0xf4, 0x12, 0x90, 0x99, 0x0c, 0x39, 0xbd, + 0x98, 0x46, 0xb2, 0x16, 0x36, 0x97, 0x6a, 0xbc, 0x6f, 0xbf, 0x79, 0x7d, 0xf4, 0xce, 0x66, 0xf8, 0x3e, 0x33, 0x30, + 0x83, 0xfd, 0xb9, 0xad, 0x92, 0x09, 0x1b, 0x18, 0x98, 0xb6, 0x7d, 0x3b, 0x9c, 0xe2, 0xdd, 0x6c, 0xe2, 0x9e, 0xdb, + 0x97, 0x8d, 0x8b, 0x8b, 0x8b, 0x06, 0x5e, 0x1d, 0x6b, 0xcc, 0xb2, 0x58, 0xf2, 0x95, 0x81, 0x0d, 0xda, 0x99, 0x27, + 0xc6, 0x3c, 0x29, 0xdf, 0x78, 0x94, 0xc6, 0x1c, 0x38, 0xee, 0x48, 0x5e, 0x7b, 0x5d, 0xf4, 0x43, 0xf4, 0x4f, 0x0f, + 0xe8, 0x4d, 0x5e, 0xdd, 0x03, 0x21, 0xdf, 0xa1, 0x26, 0x32, 0xfc, 0xda, 0xc5, 0x28, 0xd5, 0xc1, 0x36, 0x7c, 0xc1, + 0x87, 0x23, 0x3c, 0x36, 0xf4, 0xb4, 0x39, 0x5f, 0x22, 0xb2, 0x1e, 0x0e, 0x31, 0xee, 0xca, 0xa5, 0xe5, 0xd4, 0xea, + 0xd4, 0xef, 0x9f, 0x9e, 0x16, 0xf0, 0x15, 0xc6, 0xda, 0xd6, 0xe3, 0x9e, 0xa5, 0x83, 0x2b, 0xdd, 0xbf, 0x24, 0x3c, + 0x7c, 0xa3, 0x13, 0x18, 0xf3, 0x38, 0x04, 0xce, 0x3b, 0xe8, 0x12, 0xce, 0x14, 0xaf, 0x3c, 0xae, 0x1e, 0x8a, 0x13, + 0x0b, 0x39, 0x63, 0x81, 0x25, 0x48, 0x97, 0x38, 0xf8, 0xa0, 0xec, 0x40, 0xc7, 0x5a, 0x16, 0xad, 0x03, 0x50, 0x36, + 0xac, 0x8e, 0x8b, 0xf4, 0x67, 0x57, 0x64, 0xa1, 0x21, 0x1e, 0x98, 0xc0, 0xc3, 0xae, 0xc1, 0x27, 0x01, 0x0e, 0x9f, + 0x84, 0xa6, 0x53, 0xf3, 0xed, 0x32, 0xf2, 0xbd, 0x0f, 0x25, 0x32, 0x8f, 0x13, 0x01, 0xba, 0x1f, 0x7b, 0x7d, 0x4a, + 0x4d, 0xb5, 0x3a, 0x80, 0x7a, 0x2a, 0xaa, 0x4d, 0x4d, 0xad, 0xf7, 0x81, 0xee, 0x15, 0x87, 0xd3, 0x9c, 0xfb, 0xfa, + 0x8b, 0xd2, 0x0c, 0x50, 0xc1, 0x58, 0x56, 0xc5, 0x54, 0x82, 0xd3, 0x21, 0x2a, 0x6c, 0xcb, 0x7a, 0x22, 0x70, 0x47, + 0xa7, 0xd1, 0xe8, 0x37, 0xce, 0x46, 0x6e, 0x21, 0xc6, 0x73, 0x53, 0xaf, 0xb8, 0x07, 0x7a, 0x05, 0x66, 0xa3, 0x36, + 0xc0, 0xea, 0x1e, 0x25, 0x7e, 0xcc, 0x87, 0xa2, 0x10, 0x78, 0x4d, 0x70, 0xae, 0x15, 0x39, 0xaf, 0xbd, 0x07, 0xba, + 0x86, 0xe5, 0xe1, 0xdf, 0x9b, 0x27, 0x86, 0x8e, 0x7e, 0x02, 0xda, 0x01, 0x65, 0x3d, 0xe3, 0x9d, 0x0d, 0x00, 0xd7, + 0x7c, 0x9e, 0x1b, 0x13, 0xf5, 0x39, 0x2a, 0xb9, 0x85, 0xc8, 0xe0, 0x88, 0x31, 0x91, 0x99, 0xed, 0xe0, 0xf4, 0x2d, + 0xad, 0x60, 0x59, 0xd7, 0xda, 0x71, 0x8b, 0x9c, 0x4c, 0x93, 0xe5, 0xc6, 0x5a, 0x61, 0xad, 0x3f, 0x2d, 0xa1, 0xcf, + 0x50, 0xad, 0x0b, 0xe9, 0xda, 0x9f, 0xcb, 0x1e, 0xb7, 0x41, 0x66, 0x4d, 0xe9, 0x67, 0x66, 0x0f, 0xb7, 0x88, 0x92, + 0xe9, 0x4c, 0x1c, 0x53, 0x58, 0x21, 0xc3, 0x0b, 0x2a, 0xc0, 0xb1, 0xaa, 0x12, 0xc4, 0xc1, 0xc9, 0x5c, 0x02, 0xd3, + 0x0f, 0xe3, 0xbe, 0x83, 0x10, 0x59, 0x0d, 0x6b, 0x1f, 0xd0, 0xeb, 0x76, 0x26, 0x51, 0xd2, 0x90, 0x75, 0x7b, 0x86, + 0x62, 0xe8, 0xdd, 0xc7, 0xa7, 0xc2, 0xa3, 0xd1, 0x18, 0x65, 0x0f, 0xaf, 0xc0, 0xe5, 0x29, 0x18, 0x5f, 0x1d, 0xe0, + 0xd0, 0xc7, 0x2f, 0x1d, 0xf7, 0x84, 0x3d, 0x37, 0xde, 0x8f, 0x63, 0xeb, 0x93, 0x64, 0xb3, 0xb6, 0xbb, 0xa6, 0x89, + 0x79, 0x16, 0xa8, 0xd9, 0xf3, 0x00, 0x1b, 0x3e, 0x72, 0x6c, 0x9e, 0x4f, 0x1b, 0x92, 0xe5, 0x35, 0x88, 0x64, 0x6d, + 0xec, 0xea, 0x2a, 0x5f, 0x39, 0xe7, 0x73, 0xe2, 0x66, 0xea, 0x92, 0x8c, 0x74, 0xe7, 0x9c, 0x94, 0x97, 0xaa, 0xd4, + 0xb3, 0x79, 0x8d, 0xca, 0xad, 0xb1, 0x9b, 0xd3, 0x87, 0x75, 0xd6, 0x88, 0xca, 0x45, 0xf9, 0x12, 0x41, 0x90, 0xdf, + 0x38, 0xe1, 0xa9, 0xd6, 0x48, 0xcc, 0xb7, 0xae, 0xc0, 0xa8, 0xc0, 0xf2, 0xd5, 0x39, 0x7d, 0x44, 0x4a, 0xbd, 0xf1, + 0xe6, 0xc2, 0x0d, 0xa1, 0xc3, 0x75, 0x52, 0x44, 0x47, 0x98, 0x70, 0x50, 0x4b, 0x4c, 0xef, 0x54, 0xac, 0x4d, 0x9a, + 0x04, 0x16, 0x2d, 0x28, 0xb0, 0x41, 0x47, 0xb7, 0xad, 0xbf, 0xf6, 0x81, 0x7f, 0x7e, 0x0a, 0xec, 0xcd, 0xb9, 0xe3, + 0x2e, 0xdf, 0x3b, 0x25, 0xae, 0xa0, 0xf9, 0xbc, 0x5b, 0x0f, 0x65, 0x64, 0x9e, 0xc1, 0xc2, 0xe5, 0x8b, 0x89, 0xec, + 0x2e, 0xea, 0x4d, 0x07, 0xdb, 0x72, 0x1e, 0x60, 0x0e, 0x1f, 0xaa, 0xf7, 0x8d, 0x55, 0x50, 0x20, 0x9a, 0x65, 0xb9, + 0x45, 0x44, 0x15, 0xd8, 0x9f, 0xa5, 0x34, 0xdb, 0x92, 0x4c, 0x0d, 0xe1, 0x14, 0x8a, 0xbf, 0x01, 0xec, 0x65, 0x19, + 0x2f, 0x7d, 0x4a, 0x94, 0x14, 0x13, 0xd8, 0x38, 0x17, 0x3a, 0x12, 0x80, 0x5f, 0x8a, 0x30, 0x8a, 0x65, 0x97, 0x8e, + 0x76, 0x81, 0x2c, 0xac, 0x08, 0x34, 0xf7, 0xfa, 0x5a, 0xa2, 0x3a, 0x06, 0x69, 0x65, 0x07, 0xdb, 0x15, 0xdc, 0xb4, + 0x32, 0x3a, 0x93, 0x66, 0x70, 0xb6, 0x5a, 0xa1, 0xac, 0x03, 0xdc, 0xd2, 0xb5, 0x2d, 0x04, 0x30, 0x55, 0x00, 0x62, + 0xda, 0x80, 0xbc, 0x96, 0xe4, 0xc4, 0x41, 0xea, 0x09, 0xd0, 0x17, 0xb9, 0x58, 0x40, 0x6c, 0x2c, 0xb3, 0x84, 0x3b, + 0x3a, 0x45, 0x60, 0x09, 0xda, 0xb0, 0x0e, 0x2d, 0x2a, 0xd1, 0x45, 0x0f, 0xf5, 0xe0, 0x60, 0xa5, 0x3a, 0x3d, 0x76, + 0x51, 0xde, 0xd2, 0xe6, 0x48, 0x09, 0x93, 0x92, 0x84, 0x91, 0x9c, 0xc0, 0xa2, 0xb9, 0x08, 0x44, 0xc0, 0x68, 0x4f, + 0x42, 0xce, 0x07, 0x12, 0xe6, 0x20, 0xa3, 0x5e, 0x29, 0x6c, 0xa9, 0x6c, 0x2d, 0x45, 0x80, 0x6c, 0x84, 0x48, 0xa0, + 0x73, 0x5a, 0xe1, 0x00, 0x33, 0xcb, 0x4d, 0x3c, 0x0c, 0xa2, 0xf3, 0x92, 0xd8, 0xe8, 0x02, 0x5b, 0xf7, 0x00, 0xe8, + 0x9c, 0xc7, 0x30, 0x66, 0x76, 0x7d, 0xdd, 0x84, 0xa1, 0xe4, 0xa3, 0x75, 0x40, 0x7c, 0x43, 0xbe, 0x74, 0x94, 0xb6, + 0x18, 0x6f, 0x85, 0x62, 0xbe, 0xad, 0x4e, 0x34, 0xf3, 0xd5, 0x00, 0x00, 0x23, 0xa5, 0xb8, 0x50, 0xa3, 0x52, 0x8f, + 0x82, 0xd2, 0x68, 0xb0, 0x5c, 0x06, 0x6a, 0xee, 0x14, 0x4b, 0xc7, 0xd7, 0xd7, 0x2d, 0x78, 0x04, 0x96, 0x83, 0x4f, + 0x30, 0x33, 0x5d, 0xb8, 0x84, 0x47, 0x30, 0xa4, 0x80, 0x6c, 0xa1, 0x26, 0xbc, 0xa4, 0x05, 0xeb, 0x9a, 0xf0, 0x12, + 0x98, 0x95, 0xac, 0xf2, 0x4a, 0xfc, 0xe4, 0x48, 0x71, 0xd5, 0x8e, 0xc6, 0x6a, 0x47, 0x07, 0x6c, 0x26, 0xaf, 0x92, + 0x05, 0xce, 0x20, 0x88, 0x57, 0xef, 0xe8, 0x40, 0xef, 0xe8, 0x6c, 0xcd, 0x8e, 0xce, 0x6e, 0xd8, 0xd1, 0x50, 0xed, + 0x9e, 0x55, 0xe2, 0x0e, 0xe0, 0x04, 0x5e, 0x5a, 0x62, 0xef, 0x60, 0x1b, 0xf0, 0x8c, 0xbb, 0x81, 0xda, 0xa1, 0x88, + 0x26, 0x7c, 0x35, 0x51, 0xd6, 0x51, 0xcc, 0xbf, 0x08, 0x93, 0x15, 0x16, 0xb2, 0x3a, 0x16, 0x4c, 0xba, 0x2e, 0xa3, + 0x9e, 0x7f, 0x26, 0x65, 0x47, 0x88, 0x87, 0x1c, 0xf1, 0x30, 0xd6, 0x2f, 0x21, 0x75, 0x6c, 0xd0, 0x08, 0x6d, 0xcb, + 0xd6, 0x64, 0x0d, 0x2b, 0x47, 0x19, 0x41, 0xeb, 0xbb, 0x15, 0x2d, 0x62, 0x6b, 0x20, 0xc5, 0xb5, 0x34, 0x87, 0x09, + 0x0a, 0x17, 0x20, 0x39, 0x81, 0xea, 0xa8, 0xe9, 0x17, 0xa1, 0x0a, 0xc8, 0x4a, 0xa5, 0xbb, 0xad, 0xa5, 0xb5, 0xaa, + 0xde, 0xa4, 0xb8, 0xf6, 0xde, 0x9e, 0x6c, 0x31, 0x0d, 0x05, 0x48, 0xb9, 0x44, 0x51, 0xae, 0x6d, 0xff, 0x7f, 0x41, + 0x85, 0x2b, 0xf8, 0x4a, 0xa8, 0x37, 0x40, 0x13, 0xa0, 0xd2, 0xf3, 0x15, 0xcf, 0x97, 0xe2, 0x69, 0xa3, 0x52, 0x70, + 0xaf, 0x5c, 0xd3, 0xd6, 0x90, 0x45, 0x68, 0xfa, 0x80, 0xc1, 0x3c, 0xf8, 0x40, 0x0c, 0x1a, 0x54, 0x53, 0xa5, 0xb0, + 0x2e, 0x88, 0xbb, 0xaa, 0x03, 0xb3, 0x7f, 0x99, 0xb5, 0xef, 0xef, 0x1e, 0x02, 0x05, 0x10, 0x8f, 0x4f, 0x87, 0x43, + 0x20, 0x04, 0xeb, 0x76, 0xdd, 0x5a, 0xbb, 0xbf, 0xcc, 0x9e, 0x3e, 0x69, 0x3e, 0x2d, 0x3b, 0x27, 0x48, 0x44, 0x2a, + 0xc3, 0x42, 0x8b, 0x2a, 0x03, 0x5e, 0xbd, 0xa2, 0x61, 0x98, 0xac, 0x5f, 0xce, 0xb1, 0xb9, 0x9c, 0x7c, 0xca, 0xf9, + 0x00, 0x89, 0x93, 0x2d, 0x95, 0x7e, 0x88, 0xf9, 0x39, 0xd7, 0x2f, 0x7f, 0x5c, 0x31, 0xd9, 0x8a, 0x1e, 0x7d, 0xd0, + 0xc6, 0x84, 0x4a, 0x35, 0x51, 0xac, 0xd6, 0x58, 0xd2, 0x29, 0xad, 0xc1, 0x34, 0x21, 0xae, 0xa4, 0x9c, 0xab, 0x4b, + 0xaf, 0xe2, 0x94, 0xd9, 0x06, 0x00, 0x6b, 0x21, 0xeb, 0xad, 0x29, 0xf7, 0x9b, 0xac, 0xb9, 0x0e, 0x36, 0xd6, 0x72, + 0xc1, 0x0c, 0x39, 0xd1, 0x78, 0x22, 0x6f, 0x71, 0xed, 0x8d, 0x1d, 0x6b, 0xf1, 0xf5, 0x59, 0x0c, 0x9c, 0x65, 0x38, + 0x58, 0xc2, 0xf3, 0x7c, 0x2d, 0x02, 0xca, 0x4d, 0x64, 0x76, 0xd5, 0xda, 0x5e, 0x33, 0x0a, 0x2c, 0x02, 0x4f, 0x18, + 0x01, 0x5c, 0xc6, 0xac, 0x55, 0x2b, 0x3e, 0x1c, 0x82, 0x40, 0xd3, 0xce, 0x76, 0x8c, 0x3e, 0x0e, 0xa3, 0x58, 0x60, + 0x10, 0x8e, 0xa2, 0x63, 0xf6, 0x2b, 0x20, 0x78, 0xdb, 0xd5, 0xf9, 0xb4, 0x0a, 0x7e, 0x25, 0xff, 0x57, 0xc3, 0x23, + 0x47, 0xac, 0xc3, 0xa2, 0x66, 0xb9, 0xbe, 0xd6, 0xbe, 0xa0, 0x5a, 0x79, 0x1d, 0x91, 0x29, 0x39, 0x7b, 0xd6, 0x1d, + 0xa0, 0xdb, 0x1d, 0x93, 0x79, 0xeb, 0xe9, 0x5e, 0xab, 0x59, 0x00, 0x34, 0x38, 0xdc, 0x6d, 0x4f, 0x09, 0xf5, 0xda, + 0xc1, 0x5e, 0xb3, 0xe4, 0x4b, 0xfa, 0xb5, 0x5b, 0x0f, 0x5a, 0xd0, 0x89, 0x5e, 0xe4, 0x00, 0x34, 0xa7, 0x57, 0xd2, + 0x47, 0xf7, 0xf3, 0x1f, 0x5e, 0x4a, 0x7d, 0xf0, 0xdb, 0xc1, 0x73, 0xaf, 0xd5, 0x84, 0x2e, 0xb9, 0x48, 0xa7, 0x5f, + 0xb0, 0x84, 0x1d, 0xe8, 0xd2, 0x8f, 0xd3, 0x9c, 0x9b, 0x6b, 0x90, 0xea, 0xec, 0x1f, 0x5f, 0x84, 0x84, 0x68, 0x0a, + 0x4c, 0x36, 0xb7, 0xcc, 0xf1, 0x15, 0x29, 0x7d, 0x86, 0x61, 0xae, 0xa4, 0xb8, 0x9c, 0x0b, 0xc2, 0x8b, 0x7c, 0xc7, + 0x82, 0x49, 0x55, 0xb2, 0x6c, 0x89, 0xd8, 0x48, 0x04, 0x94, 0x8c, 0x4d, 0x6a, 0x57, 0x9f, 0x9d, 0x79, 0xc5, 0xd1, + 0x93, 0x13, 0xcb, 0xa8, 0xfc, 0xf2, 0x04, 0xb5, 0x12, 0x10, 0x7e, 0x1f, 0x56, 0x94, 0x86, 0x97, 0x2b, 0x4a, 0x51, + 0x65, 0x2b, 0xa1, 0x53, 0xef, 0xff, 0xf9, 0x3c, 0xd6, 0x2b, 0xc5, 0xc7, 0x04, 0x71, 0x40, 0xce, 0xcd, 0xcf, 0x40, + 0x6a, 0x6c, 0x03, 0x8d, 0xf0, 0xfb, 0xa7, 0xc3, 0x92, 0x2f, 0x99, 0xae, 0x1c, 0xe5, 0x8f, 0xad, 0x10, 0x4b, 0x1b, + 0x18, 0x41, 0x88, 0xbf, 0x68, 0xad, 0xa0, 0xd7, 0x7c, 0x76, 0xdb, 0x0d, 0xad, 0xea, 0x0f, 0x6c, 0xbd, 0x7a, 0x8f, + 0xc0, 0xe2, 0xde, 0xaf, 0x28, 0x56, 0x8a, 0x4f, 0xb9, 0xff, 0x60, 0x9a, 0x4e, 0x2a, 0x12, 0x58, 0x06, 0x93, 0x34, + 0x1e, 0x4c, 0x27, 0x33, 0x07, 0x91, 0xaa, 0xcf, 0x07, 0xbc, 0x24, 0x8b, 0xef, 0x21, 0x99, 0x65, 0x1c, 0xb8, 0xe9, + 0xc5, 0xe2, 0x8b, 0xd5, 0xd6, 0x37, 0x1e, 0x83, 0xc0, 0x30, 0x6e, 0xbe, 0xf1, 0xa0, 0xdc, 0x84, 0x1b, 0x27, 0x28, + 0xfe, 0xf5, 0x5f, 0x3c, 0xef, 0x5f, 0xff, 0xe5, 0xb3, 0x4d, 0x71, 0x78, 0x90, 0xc8, 0xa2, 0x1a, 0x76, 0xfd, 0xe9, + 0x5a, 0x3d, 0x53, 0x1d, 0xe7, 0xab, 0xdb, 0x2c, 0x6d, 0x02, 0xd6, 0x2f, 0x6d, 0xc1, 0x52, 0xa1, 0x3c, 0x7d, 0xd6, + 0xef, 0x01, 0x0c, 0xd7, 0xf5, 0x59, 0xc8, 0xb0, 0xd1, 0x1f, 0x02, 0xed, 0xd4, 0xf5, 0x6f, 0xb5, 0x23, 0xbf, 0x1f, + 0xc3, 0x9f, 0x5b, 0xc3, 0x1f, 0x04, 0x5f, 0xf9, 0x27, 0xfa, 0xa7, 0xa7, 0x65, 0x8a, 0xa3, 0xd9, 0x15, 0x5f, 0xa0, + 0xd0, 0x5b, 0x2a, 0x51, 0x8a, 0x87, 0xdf, 0x74, 0xbb, 0x74, 0x41, 0x13, 0xba, 0xbf, 0xc4, 0xb7, 0x26, 0x1d, 0x9c, + 0x65, 0xda, 0xc1, 0x7b, 0x83, 0x70, 0xc0, 0x21, 0xea, 0xab, 0xa2, 0x41, 0x97, 0x24, 0x03, 0x96, 0xa2, 0xb9, 0x81, + 0x60, 0x32, 0x30, 0x98, 0xa4, 0x71, 0x79, 0x28, 0xdd, 0x30, 0xfe, 0x22, 0x69, 0x2b, 0xf7, 0x4c, 0x0d, 0xe9, 0xcc, + 0x7a, 0x47, 0xf8, 0xa2, 0xc6, 0xbc, 0xb2, 0xee, 0xc9, 0xd5, 0x85, 0x76, 0x44, 0xc9, 0x7e, 0x80, 0x53, 0x9c, 0xdf, + 0x8e, 0xf1, 0xad, 0x17, 0xa8, 0xd7, 0xd6, 0xf5, 0x3f, 0x5a, 0x25, 0xb8, 0x6e, 0x5c, 0xd7, 0xf4, 0x01, 0x4a, 0xf3, + 0x88, 0xf8, 0x9a, 0x48, 0x74, 0xce, 0x3f, 0x1b, 0x89, 0x8e, 0x6f, 0x15, 0x89, 0xce, 0xf9, 0x9f, 0x1d, 0x89, 0x8e, + 0xb8, 0x11, 0x89, 0x46, 0x12, 0xfc, 0xf5, 0x56, 0x01, 0x4d, 0x1d, 0x7e, 0x4a, 0x2f, 0xf2, 0xa0, 0xa5, 0x8c, 0x80, + 0x38, 0x1d, 0x61, 0x34, 0xf3, 0x1f, 0x1f, 0x9c, 0x84, 0x89, 0xcc, 0xd0, 0x24, 0xbe, 0xf4, 0x17, 0x63, 0x91, 0x80, + 0x93, 0xb9, 0xfd, 0xcb, 0x65, 0xeb, 0xd1, 0x71, 0xab, 0xb3, 0xd3, 0x9a, 0x80, 0x8d, 0x8e, 0x52, 0x97, 0x0a, 0x9a, + 0x9d, 0x9d, 0x1d, 0x2c, 0xb8, 0x30, 0x0a, 0xda, 0x58, 0x10, 0x19, 0x05, 0x7b, 0x58, 0xd0, 0x37, 0x0a, 0xee, 0x61, + 0xc1, 0xc0, 0x28, 0xb8, 0x8f, 0x05, 0xe7, 0x76, 0x71, 0x1c, 0x95, 0xe1, 0xf6, 0xfb, 0x2e, 0xbd, 0x1f, 0x64, 0x23, + 0xab, 0xdf, 0x8d, 0x18, 0x07, 0xba, 0xc9, 0xfd, 0xf2, 0x5e, 0x55, 0x63, 0x57, 0xbf, 0x06, 0xe4, 0xf4, 0x2b, 0x38, + 0x48, 0x71, 0x80, 0xd7, 0x1c, 0x19, 0x1a, 0xe5, 0xb2, 0xe5, 0x8e, 0xae, 0x86, 0x49, 0xdc, 0x72, 0x82, 0xb6, 0x8e, + 0x4a, 0x43, 0x21, 0x9b, 0x95, 0x8d, 0xf7, 0xb6, 0x06, 0x6a, 0x58, 0x7c, 0xc3, 0x46, 0xf5, 0x7a, 0x9b, 0x1d, 0x97, + 0xc9, 0x37, 0x8a, 0x3f, 0x26, 0xf9, 0x08, 0x06, 0xdf, 0x3b, 0x50, 0x03, 0xf4, 0xef, 0xad, 0xe8, 0x09, 0x2c, 0x8a, + 0xdb, 0x77, 0xc6, 0xd5, 0x3b, 0xe1, 0x9e, 0xb2, 0x87, 0xd5, 0x1b, 0x95, 0xde, 0x89, 0x40, 0xbe, 0xa2, 0x02, 0xe8, + 0x92, 0x0c, 0xbd, 0x11, 0x13, 0xe1, 0xc8, 0xc7, 0xc0, 0x25, 0xfa, 0x4c, 0xfd, 0x87, 0x41, 0x10, 0x34, 0x7b, 0x33, + 0xff, 0x29, 0xbb, 0x18, 0xf3, 0xc4, 0x3f, 0x2f, 0x3a, 0x25, 0x01, 0xc8, 0xb8, 0xe9, 0x3b, 0x51, 0xbe, 0x88, 0x8f, + 0xa8, 0xa2, 0xaa, 0x96, 0x70, 0x36, 0x4a, 0xea, 0x59, 0x13, 0x6a, 0x33, 0x7c, 0x32, 0x43, 0x90, 0x5a, 0x8d, 0x4b, + 0xbb, 0xbb, 0x3a, 0xfc, 0x86, 0xab, 0x2b, 0xc3, 0x6f, 0x2f, 0x10, 0xd8, 0xf2, 0xe9, 0x5d, 0x38, 0x2a, 0xbf, 0xbf, + 0x04, 0xcd, 0x3a, 0x1c, 0xa9, 0x96, 0xeb, 0xc3, 0x6d, 0x04, 0xa2, 0x19, 0x6a, 0xd3, 0x40, 0x60, 0x4c, 0x0c, 0x31, + 0x82, 0x3e, 0x0d, 0x15, 0x22, 0xc3, 0xa5, 0xd7, 0xa3, 0x6b, 0x84, 0xab, 0x7a, 0x11, 0xa0, 0xad, 0x2a, 0x38, 0x00, + 0x05, 0x5f, 0xc5, 0xed, 0x10, 0x8d, 0x50, 0x81, 0x05, 0xb2, 0x7a, 0x4d, 0x14, 0x4d, 0x3b, 0x50, 0xd6, 0xc7, 0xd2, + 0x2c, 0x1d, 0x45, 0x33, 0x33, 0xbf, 0xca, 0xb4, 0xaf, 0xe5, 0xd8, 0xcd, 0xd7, 0xad, 0x3e, 0xfe, 0xa7, 0x22, 0x43, + 0x5f, 0x0f, 0x87, 0xc3, 0x1b, 0xa3, 0x6a, 0x5f, 0x0f, 0x86, 0xbc, 0xcd, 0xf7, 0x3a, 0x98, 0x15, 0xd4, 0x50, 0xb1, + 0x98, 0x56, 0x41, 0xb8, 0x9b, 0xdf, 0xae, 0x31, 0x86, 0x6d, 0xc4, 0x78, 0x7e, 0xfb, 0x08, 0x5b, 0x01, 0x58, 0x99, + 0x4f, 0x40, 0x5e, 0x44, 0x89, 0xdf, 0x2c, 0xbc, 0x73, 0x15, 0x92, 0xfa, 0x7a, 0x7f, 0x7f, 0xbf, 0xf0, 0x06, 0xfa, + 0xa9, 0x39, 0x18, 0x14, 0x5e, 0x7f, 0x5e, 0x2e, 0xa3, 0xd9, 0x1c, 0x0e, 0x0b, 0x2f, 0xd2, 0x05, 0x3b, 0xed, 0xfe, + 0x60, 0xa7, 0x5d, 0x78, 0x17, 0x46, 0x8b, 0xc2, 0xe3, 0xea, 0x29, 0xe3, 0x83, 0x5a, 0x6a, 0xd1, 0xfd, 0x26, 0x54, + 0x4a, 0x42, 0x9b, 0xa3, 0x59, 0x2a, 0xbf, 0xfa, 0xe1, 0x4c, 0xa4, 0xc8, 0xdc, 0x3b, 0xb1, 0x70, 0x8e, 0x3f, 0xa8, + 0xd7, 0xb6, 0xc8, 0x1f, 0x39, 0x29, 0xdc, 0x13, 0xf6, 0xab, 0x19, 0x3c, 0x42, 0x62, 0xa6, 0xa0, 0x51, 0xac, 0x63, + 0x4b, 0xb5, 0x6a, 0xa4, 0x2c, 0xaa, 0xfe, 0x35, 0x88, 0xab, 0x98, 0x12, 0x72, 0x32, 0x6c, 0x29, 0xdf, 0x2e, 0x98, + 0xac, 0x93, 0x1f, 0xd9, 0xe7, 0xe5, 0xc7, 0xd5, 0x6d, 0xc4, 0x47, 0xf6, 0xa7, 0x8b, 0x8f, 0xc4, 0x14, 0x1f, 0x92, + 0x79, 0x9c, 0x89, 0xc0, 0xee, 0x8f, 0x79, 0xff, 0xd3, 0x59, 0x7a, 0xd9, 0xc0, 0x23, 0x91, 0xd9, 0x24, 0x58, 0x36, + 0x7f, 0x6f, 0xa6, 0x8c, 0x1e, 0xcc, 0xf8, 0x89, 0x14, 0x52, 0x1f, 0x5e, 0x27, 0x81, 0xfd, 0x5a, 0xdb, 0xb6, 0xb2, + 0x64, 0x38, 0x84, 0xa2, 0xe1, 0xd0, 0xd6, 0x97, 0x4f, 0x81, 0x07, 0x52, 0xab, 0x57, 0xb5, 0x12, 0x6a, 0xf5, 0xf4, + 0xa9, 0x59, 0x66, 0x16, 0xa8, 0xd0, 0x93, 0x19, 0x66, 0x52, 0x35, 0x83, 0x28, 0xc7, 0xa3, 0x86, 0xbf, 0xdc, 0x52, + 0x7f, 0xf9, 0x65, 0x52, 0x7b, 0x4f, 0x79, 0x09, 0xf0, 0x8a, 0x97, 0xab, 0x2f, 0xbe, 0x79, 0x01, 0x36, 0x54, 0x65, + 0x74, 0x3e, 0xba, 0x7a, 0x3e, 0x70, 0xce, 0x80, 0x71, 0x46, 0xf9, 0xeb, 0x64, 0xe1, 0x56, 0x95, 0x84, 0x31, 0x08, + 0xcc, 0x65, 0x15, 0x22, 0x1d, 0x8d, 0x62, 0xfc, 0xf1, 0x9c, 0x79, 0xed, 0x85, 0xbc, 0xb2, 0x7b, 0xaf, 0xb6, 0x5e, + 0xdf, 0xec, 0xa8, 0x5e, 0x5f, 0x4b, 0xbf, 0xe5, 0x25, 0xb3, 0xf1, 0xe9, 0xde, 0x98, 0xce, 0xf9, 0x99, 0x2b, 0x26, + 0x3f, 0x97, 0x39, 0xdc, 0x82, 0x45, 0x03, 0xd9, 0x3d, 0x1a, 0x14, 0x85, 0xba, 0xfd, 0x02, 0x88, 0x98, 0xe2, 0x8b, + 0x62, 0x65, 0x4f, 0xfe, 0x39, 0x16, 0x9e, 0x5f, 0x18, 0xf1, 0x9d, 0xda, 0x76, 0x15, 0x3a, 0xc0, 0x23, 0x1d, 0xe6, + 0x67, 0xa2, 0xb0, 0x95, 0xdf, 0x5d, 0x23, 0xd1, 0xb6, 0x24, 0x3e, 0x65, 0xe4, 0xc9, 0x58, 0x21, 0x3a, 0xbf, 0xcb, + 0x0d, 0xd1, 0x55, 0xba, 0xa0, 0x30, 0xe3, 0x97, 0x54, 0x23, 0xb1, 0x45, 0xd1, 0x12, 0x80, 0x3d, 0x91, 0x6c, 0x14, + 0xa6, 0x21, 0x7e, 0xb0, 0x39, 0xaf, 0x76, 0x1e, 0xba, 0x2a, 0xb0, 0x25, 0xf1, 0x02, 0x0f, 0xc6, 0x0e, 0x5d, 0xab, + 0x06, 0x7a, 0xb2, 0x14, 0x64, 0xb9, 0x39, 0xdd, 0xe1, 0xf5, 0xa9, 0x97, 0x5f, 0x30, 0xf8, 0x67, 0xfd, 0x65, 0x0e, + 0x5c, 0xe7, 0xec, 0x53, 0x24, 0x1a, 0x22, 0x9c, 0x36, 0xd0, 0xf0, 0x21, 0xe7, 0xa8, 0x62, 0xcf, 0x94, 0x36, 0x29, + 0xdf, 0x1d, 0xd1, 0x99, 0xe5, 0x98, 0x15, 0x41, 0xea, 0xbb, 0x9f, 0xa4, 0x09, 0xef, 0xd4, 0xd3, 0x63, 0xcd, 0x20, + 0xbb, 0xc6, 0xd6, 0xc9, 0x3c, 0xc5, 0x2c, 0x0a, 0x71, 0xe5, 0x37, 0x15, 0x5b, 0x6f, 0xea, 0x08, 0x7a, 0x73, 0x65, + 0x7b, 0x5f, 0x21, 0x77, 0x8b, 0xa4, 0x57, 0xb6, 0x9c, 0x49, 0xb0, 0x2e, 0x13, 0xe0, 0x73, 0xc9, 0xa2, 0xe8, 0x52, + 0xd5, 0xff, 0x8c, 0x2c, 0xdb, 0xc5, 0x62, 0x4a, 0x16, 0xbd, 0x0d, 0x64, 0x7e, 0x38, 0x84, 0x35, 0xb3, 0xdb, 0xb4, + 0x3c, 0xa3, 0x7b, 0x5d, 0x73, 0x14, 0x33, 0x7e, 0x6b, 0x7f, 0x7a, 0x79, 0xfb, 0xe1, 0x6f, 0x5e, 0x7e, 0xa1, 0x70, + 0xa4, 0xdf, 0x73, 0x64, 0xdb, 0x1d, 0x3c, 0x08, 0x71, 0x78, 0xe5, 0x47, 0x09, 0xc9, 0xbc, 0x33, 0xf4, 0x8b, 0x76, + 0xa6, 0xa9, 0xca, 0x7a, 0xce, 0x78, 0x4c, 0x3f, 0x6b, 0xa8, 0xb6, 0x62, 0xe7, 0xde, 0xf4, 0x52, 0xef, 0x46, 0x6b, + 0x21, 0x9b, 0xf9, 0x4f, 0x4d, 0x5a, 0x5e, 0x9f, 0x25, 0x5d, 0x4f, 0xbc, 0xdd, 0x03, 0x18, 0xa4, 0xa0, 0x6d, 0x64, + 0x12, 0xaa, 0x26, 0x94, 0x18, 0x69, 0xdb, 0xd5, 0x40, 0x96, 0xb7, 0x03, 0xac, 0x3b, 0xcc, 0x79, 0x07, 0x5f, 0xe4, + 0x1e, 0xf5, 0xc3, 0x58, 0x09, 0xf3, 0x49, 0x34, 0x18, 0xc4, 0xbc, 0xa3, 0xe5, 0xb5, 0xd5, 0xba, 0x87, 0x59, 0xcf, + 0xe6, 0x96, 0xd5, 0x77, 0xc5, 0x40, 0x5e, 0x89, 0xa7, 0xf0, 0x0c, 0xf4, 0x07, 0xfc, 0x15, 0x95, 0x95, 0xe8, 0x54, + 0xe9, 0xc0, 0xcd, 0x0a, 0x79, 0xf4, 0xbd, 0xbe, 0x96, 0x3d, 0x50, 0x5e, 0x68, 0xc3, 0x9b, 0x1d, 0x30, 0xe2, 0xfc, + 0xc6, 0x4e, 0x7d, 0x21, 0x58, 0x55, 0x2e, 0x81, 0xad, 0x58, 0x16, 0x43, 0x69, 0x25, 0xf9, 0xb4, 0xe5, 0xb5, 0x54, + 0x19, 0x0d, 0x80, 0x69, 0x63, 0x65, 0x51, 0x51, 0x5f, 0xcc, 0x3f, 0xe6, 0xb4, 0x3c, 0x58, 0x7d, 0x5a, 0x1e, 0xe8, + 0xd3, 0x72, 0x33, 0xc5, 0x7e, 0x3d, 0x6c, 0xe1, 0x7f, 0x9d, 0x6a, 0x41, 0xb0, 0x2b, 0x80, 0x0e, 0x0b, 0xf5, 0xb4, + 0x46, 0x1b, 0xfe, 0xd0, 0xd0, 0x18, 0xbb, 0x69, 0x62, 0x1a, 0x37, 0x6b, 0x5a, 0x58, 0x88, 0xff, 0x9a, 0xb5, 0xaa, + 0xd6, 0x2e, 0xd6, 0x61, 0xaf, 0xbd, 0xe5, 0xba, 0xf6, 0xcd, 0x87, 0x16, 0xf8, 0x95, 0x70, 0x7c, 0xcd, 0x8d, 0xc1, + 0x0c, 0x09, 0xcf, 0xce, 0xa0, 0x74, 0x98, 0xf6, 0x67, 0xf9, 0x3f, 0x2b, 0xf8, 0x15, 0x12, 0x6f, 0x3c, 0xd2, 0x0b, + 0xe3, 0xe8, 0xae, 0x32, 0x85, 0x5e, 0x8f, 0x30, 0x2f, 0xf7, 0xc9, 0xcf, 0x81, 0x30, 0xb9, 0xd3, 0xf6, 0x76, 0x57, + 0x1c, 0x82, 0x7f, 0x97, 0xbd, 0x59, 0xb9, 0x98, 0x3f, 0x8a, 0x8c, 0x1b, 0x91, 0xf0, 0x45, 0x38, 0x30, 0xf7, 0xb0, + 0xb9, 0xbf, 0x1a, 0xdc, 0x63, 0x3d, 0xd3, 0x89, 0x16, 0x0a, 0x4a, 0xee, 0x80, 0x56, 0x1a, 0xce, 0x62, 0x71, 0xf3, + 0xa8, 0xeb, 0x28, 0x63, 0x69, 0xd4, 0x1b, 0x18, 0x7a, 0xd5, 0xf6, 0x96, 0x5c, 0xfa, 0xeb, 0x07, 0xbb, 0xf8, 0x9f, + 0xcc, 0xff, 0xba, 0xaa, 0x74, 0x75, 0x69, 0xf7, 0xa2, 0xae, 0xbe, 0x59, 0x53, 0xc6, 0xa5, 0x08, 0x27, 0x7d, 0xfc, + 0xb6, 0xad, 0x51, 0xab, 0xbc, 0x55, 0x73, 0xa5, 0x65, 0x7d, 0x51, 0xeb, 0x2f, 0x1b, 0xfc, 0x96, 0x6d, 0xfb, 0x52, + 0x73, 0xad, 0xb7, 0x55, 0xbf, 0xeb, 0xb8, 0xd4, 0x58, 0x63, 0x9c, 0xda, 0x6f, 0x06, 0x57, 0xa5, 0x89, 0x22, 0xa3, + 0xb1, 0x68, 0xa5, 0x6c, 0x4a, 0x2b, 0x25, 0xe5, 0xc1, 0xe9, 0x41, 0xef, 0x72, 0x12, 0x5b, 0xe7, 0xf2, 0xfe, 0x69, + 0x60, 0xb7, 0xbc, 0xa6, 0x6d, 0x51, 0x1e, 0x00, 0xbe, 0x06, 0xdf, 0xa6, 0xdf, 0x0b, 0xb6, 0x7b, 0xa8, 0x69, 0x9d, + 0x8f, 0x48, 0xb3, 0x7b, 0x11, 0x5e, 0xf1, 0xec, 0x43, 0xdb, 0xb6, 0xd0, 0x4f, 0xd3, 0x90, 0x29, 0x13, 0x54, 0x66, + 0x41, 0x19, 0x0c, 0x95, 0x80, 0xb6, 0x35, 0x16, 0x62, 0xea, 0xcb, 0x1f, 0x14, 0xbe, 0xd8, 0xf1, 0xd2, 0x6c, 0xb4, + 0xdd, 0x6e, 0x36, 0x9b, 0xf8, 0x46, 0x5d, 0xdb, 0x3a, 0x8f, 0xf8, 0xc5, 0x23, 0x50, 0xa8, 0xed, 0x26, 0xf0, 0xa1, + 0x56, 0x7b, 0x1f, 0xfe, 0xed, 0x7a, 0xf7, 0xf6, 0xed, 0xee, 0x57, 0x96, 0x75, 0x00, 0x64, 0x99, 0xe3, 0x17, 0xf8, + 0x4a, 0x8a, 0x97, 0xfc, 0x6e, 0x81, 0xde, 0x18, 0xe7, 0x8d, 0x96, 0x35, 0x57, 0x8f, 0x96, 0x85, 0xb7, 0x74, 0x7d, + 0xeb, 0xeb, 0x61, 0x7b, 0xb8, 0x3b, 0x7c, 0xd0, 0x51, 0xc5, 0xc5, 0x57, 0xb5, 0xe6, 0x4c, 0x7e, 0xb6, 0x8d, 0x6e, + 0x60, 0xa3, 0xa4, 0x9f, 0xb8, 0xca, 0x49, 0xb4, 0x50, 0xf4, 0xac, 0xec, 0xda, 0x5e, 0x9e, 0xa9, 0xb5, 0x7f, 0xd6, + 0x1f, 0xb6, 0xab, 0xe6, 0x04, 0xe3, 0x76, 0x09, 0x24, 0x68, 0x8e, 0x0a, 0xf4, 0x03, 0x13, 0x4d, 0xad, 0xc6, 0x2a, + 0x44, 0xb5, 0x6c, 0xb5, 0xc6, 0x91, 0x5e, 0xdf, 0x01, 0x5e, 0x0a, 0xd1, 0xba, 0x2a, 0x41, 0x00, 0xdd, 0x02, 0xfb, + 0x25, 0xe0, 0x87, 0xb5, 0x5a, 0xf7, 0x00, 0x3f, 0xfd, 0x26, 0xdb, 0xf5, 0x76, 0x1b, 0x3b, 0xde, 0x3d, 0xb6, 0xdf, + 0xd8, 0x67, 0xfb, 0xcf, 0xf6, 0xfb, 0x0d, 0x28, 0x60, 0xcd, 0xc6, 0x3e, 0x16, 0xc2, 0xdf, 0xfd, 0xf3, 0xc6, 0x2e, + 0x34, 0xa3, 0xd2, 0xb6, 0xb7, 0xb7, 0xd7, 0x68, 0x35, 0xe1, 0x2f, 0xdb, 0xf3, 0xee, 0xdd, 0x6b, 0xb4, 0xa0, 0xc9, + 0xbd, 0x17, 0x7b, 0xfb, 0xde, 0x0e, 0xd6, 0xed, 0xec, 0xf4, 0x77, 0xbc, 0x56, 0xab, 0x81, 0x7f, 0xd8, 0xbe, 0xd7, + 0x96, 0x5f, 0x5a, 0x2d, 0x6f, 0xa7, 0xc5, 0x9a, 0xf1, 0x5e, 0xdb, 0xbb, 0xf7, 0x80, 0xd1, 0x5f, 0x6a, 0xc6, 0xe8, + 0x0f, 0x0e, 0xc3, 0x1e, 0x78, 0xed, 0x7b, 0xf2, 0x1b, 0x0d, 0x78, 0xbe, 0xbb, 0xff, 0xb3, 0xbd, 0xbd, 0x76, 0x0d, + 0x2d, 0xb9, 0x86, 0xfd, 0x3d, 0x98, 0x90, 0xed, 0xb6, 0xbc, 0xfd, 0x9d, 0x71, 0x63, 0x17, 0x86, 0xbd, 0xdf, 0x6f, + 0xb4, 0xbc, 0xfb, 0xf7, 0x01, 0xf4, 0x1d, 0xaf, 0xcd, 0x5a, 0xde, 0xee, 0x0e, 0x7d, 0x81, 0x7f, 0xe7, 0xf7, 0x1f, + 0x78, 0xf7, 0xf6, 0xc6, 0xf7, 0xbc, 0xdd, 0x1f, 0x76, 0x01, 0xae, 0x9d, 0xf1, 0xce, 0x3d, 0xaf, 0x7d, 0xff, 0x1c, + 0x9e, 0xc7, 0x8d, 0xf6, 0xbd, 0x1b, 0x7b, 0xb6, 0xda, 0x1e, 0xe2, 0x88, 0xaa, 0xb1, 0x82, 0xa9, 0x0a, 0xfc, 0x37, + 0xa6, 0xbe, 0xff, 0x8e, 0xc3, 0xe4, 0xcb, 0x5d, 0x1f, 0x78, 0xfb, 0xf7, 0xfb, 0xb2, 0x39, 0x16, 0x34, 0x74, 0x0b, + 0xec, 0x72, 0xde, 0x90, 0xd3, 0xd2, 0x70, 0x0d, 0x3d, 0x90, 0xfe, 0xa7, 0x26, 0x3b, 0x6f, 0xe0, 0xc4, 0x72, 0xde, + 0xff, 0xd0, 0x71, 0xca, 0x2d, 0x3f, 0xd8, 0x1e, 0x49, 0xd2, 0x87, 0x0f, 0xf9, 0xba, 0xec, 0xaf, 0x4e, 0x59, 0xbc, + 0xce, 0xf1, 0x11, 0x7e, 0xde, 0xf1, 0x31, 0xe6, 0xb7, 0xf1, 0x7c, 0x84, 0x7f, 0xba, 0xe7, 0x23, 0x5e, 0x74, 0x9c, + 0x5f, 0x88, 0x25, 0x07, 0xc7, 0xa2, 0x55, 0xfc, 0x42, 0x38, 0xc7, 0x29, 0xfe, 0x30, 0x5b, 0xd1, 0x81, 0xd6, 0x63, + 0x6e, 0xfa, 0x81, 0x52, 0x64, 0xb1, 0x17, 0x42, 0xf2, 0xd8, 0xfe, 0x3a, 0x84, 0x0c, 0x3e, 0x8f, 0x90, 0x1f, 0x6e, + 0x83, 0x8f, 0xc1, 0x9f, 0x8e, 0x8f, 0xbe, 0x89, 0x8f, 0x9a, 0x2f, 0x9f, 0x3c, 0x0d, 0xe4, 0x29, 0x38, 0xa2, 0x67, + 0x07, 0x6f, 0xa5, 0x6d, 0xd9, 0xdb, 0x1c, 0x8b, 0x72, 0x5b, 0x46, 0xbe, 0xde, 0x7e, 0x49, 0xd8, 0x41, 0x5e, 0x41, + 0x0d, 0x6c, 0xe5, 0x96, 0x99, 0x92, 0xd4, 0x51, 0x0f, 0xa5, 0x50, 0x6a, 0x7b, 0x4d, 0x10, 0x4b, 0xda, 0xa5, 0x83, + 0xd7, 0x8e, 0x83, 0x79, 0x2a, 0x42, 0xfc, 0x09, 0x60, 0x40, 0x37, 0xfd, 0x58, 0x30, 0xfe, 0x3c, 0x03, 0x26, 0xfd, + 0xf4, 0xe5, 0x2f, 0x63, 0xe0, 0xbd, 0x09, 0xe5, 0xe8, 0x09, 0xb3, 0x4f, 0xdf, 0xe3, 0xd5, 0x5f, 0x1d, 0x95, 0x98, + 0xa0, 0xb7, 0xe3, 0x25, 0x1f, 0x44, 0xa1, 0x63, 0x3b, 0xd3, 0x8c, 0x0f, 0x61, 0x96, 0x46, 0xed, 0x3e, 0x2c, 0x5d, + 0x85, 0x75, 0x6d, 0xfd, 0x5b, 0xb3, 0x19, 0xbe, 0x6e, 0x3c, 0x38, 0x56, 0xfe, 0x46, 0x5b, 0x19, 0x60, 0x30, 0xbe, + 0x2e, 0xc9, 0x50, 0xd6, 0x56, 0x4a, 0x9b, 0x2d, 0xb5, 0xb6, 0x96, 0xd7, 0x06, 0x2b, 0x8e, 0x8a, 0xf1, 0x45, 0xce, + 0x3f, 0x39, 0x8d, 0x1d, 0xd0, 0x2a, 0x8d, 0x6e, 0xe5, 0x40, 0x27, 0xca, 0xdd, 0x96, 0x54, 0x3f, 0xd2, 0x5d, 0xbf, + 0xac, 0x6c, 0x4b, 0x8a, 0xf8, 0x5a, 0xae, 0x1d, 0xd0, 0x9c, 0xa8, 0x08, 0xb7, 0x7c, 0xe5, 0x06, 0x94, 0x39, 0xe6, + 0x4f, 0x30, 0xcb, 0x17, 0x45, 0xd3, 0x2f, 0xb7, 0xbb, 0x45, 0xd5, 0x24, 0x71, 0xe7, 0x14, 0x6f, 0x89, 0x12, 0x2b, + 0xb9, 0xbe, 0x86, 0x66, 0xf0, 0x10, 0x18, 0x38, 0xc5, 0x67, 0xb7, 0x86, 0xe4, 0x84, 0x95, 0x00, 0x11, 0x82, 0x81, + 0xbe, 0xe8, 0xb3, 0x2a, 0xd6, 0x5f, 0x94, 0xe3, 0xcb, 0x8b, 0x43, 0xd8, 0xbf, 0x84, 0x3e, 0x96, 0xdc, 0x6a, 0x32, + 0x64, 0xb4, 0x50, 0x5a, 0x0d, 0x55, 0xb9, 0xcf, 0xf2, 0x47, 0x57, 0xef, 0xd4, 0x1b, 0xe5, 0x6c, 0xf4, 0x4e, 0x53, + 0x84, 0xa3, 0x7a, 0xfb, 0xf5, 0x56, 0x70, 0xf7, 0x60, 0xc2, 0x45, 0x28, 0xf3, 0x35, 0x51, 0x9f, 0xc0, 0x6b, 0xc8, + 0x96, 0xb2, 0x46, 0x03, 0x9b, 0xa4, 0x7b, 0x20, 0xef, 0xd0, 0x48, 0x51, 0xcf, 0x2c, 0xf5, 0x2a, 0x86, 0x06, 0x6d, + 0x4d, 0xd0, 0x62, 0xd2, 0x1f, 0x03, 0x0f, 0xa8, 0x29, 0x05, 0x49, 0x6a, 0x77, 0xef, 0x96, 0x3f, 0x27, 0xbb, 0x6e, + 0x13, 0xc0, 0xac, 0xf8, 0x74, 0x9c, 0xf1, 0xf8, 0x9f, 0x83, 0xbb, 0x11, 0xb4, 0xbd, 0x7b, 0x02, 0x1b, 0x21, 0xbc, + 0x31, 0x50, 0x50, 0x70, 0x17, 0x65, 0xbc, 0x4f, 0xd6, 0x07, 0x32, 0xc2, 0x2d, 0xd0, 0x83, 0x18, 0x69, 0x4c, 0xb7, + 0x50, 0x88, 0x24, 0xb8, 0x76, 0x7b, 0xcf, 0xb6, 0xa4, 0x4d, 0x4c, 0xdf, 0xbb, 0x52, 0x9c, 0x92, 0x12, 0x00, 0x2a, + 0x92, 0xb7, 0x37, 0x6e, 0x7b, 0x0f, 0xce, 0xef, 0x7b, 0xfb, 0xe3, 0x16, 0xb0, 0x70, 0xfc, 0x84, 0xe7, 0xb8, 0x01, + 0x7f, 0xf0, 0xdf, 0x0f, 0xbb, 0xd0, 0x00, 0x38, 0xf5, 0xfe, 0xf9, 0x8e, 0xb7, 0xf3, 0x02, 0x9a, 0xef, 0x58, 0x2d, + 0x4b, 0xf6, 0x43, 0x76, 0x2d, 0xb9, 0xf3, 0xdd, 0x85, 0x03, 0xb1, 0x22, 0x1c, 0x27, 0x73, 0x4e, 0x6d, 0xe6, 0x94, + 0x3f, 0x5a, 0xa9, 0xce, 0xa7, 0x72, 0xd6, 0x3d, 0x86, 0xbe, 0x4e, 0x19, 0x0f, 0x5a, 0x55, 0xc7, 0x6a, 0xfc, 0x62, + 0xc5, 0x14, 0x78, 0xc2, 0x6d, 0x66, 0xbe, 0xcb, 0x00, 0x5f, 0x04, 0x40, 0x2f, 0x5a, 0xd7, 0xef, 0x9b, 0x5c, 0x4f, + 0xda, 0xb2, 0xa1, 0x7e, 0xa7, 0x25, 0x31, 0x8b, 0x88, 0x7e, 0xd2, 0x82, 0x26, 0x79, 0x3e, 0x28, 0x16, 0xe7, 0xc7, + 0xd4, 0xd7, 0x2c, 0x35, 0x5e, 0xe7, 0xc0, 0xab, 0x0b, 0x1b, 0x83, 0x08, 0x5f, 0xc0, 0x51, 0x14, 0x1a, 0xf4, 0x9a, + 0x9b, 0xb6, 0xc2, 0x12, 0xf1, 0x0b, 0x9e, 0xf7, 0x6c, 0x2c, 0xb2, 0x7d, 0x9b, 0x5c, 0x7c, 0x76, 0xf9, 0xeb, 0x49, + 0x25, 0x61, 0x57, 0x05, 0x8c, 0x2e, 0x5d, 0xe1, 0xa9, 0x45, 0xfc, 0xd8, 0xc0, 0x77, 0xd7, 0x9e, 0x17, 0x52, 0x20, + 0x71, 0xad, 0xd5, 0x8f, 0xae, 0x98, 0xac, 0xc8, 0x36, 0x11, 0x5d, 0x8e, 0x4b, 0x28, 0x74, 0x15, 0x9e, 0xce, 0x78, + 0x28, 0xbc, 0x30, 0x91, 0x49, 0x34, 0x06, 0xc3, 0x62, 0x2d, 0xbe, 0xe3, 0x16, 0xc0, 0x25, 0x8d, 0x1f, 0x56, 0x56, + 0xe7, 0x1c, 0x0a, 0xf5, 0xe5, 0x64, 0xe3, 0x3d, 0x4c, 0xe8, 0xe8, 0x1d, 0xb7, 0xbb, 0xaf, 0xdf, 0x3d, 0xb4, 0xe4, + 0xf1, 0x3c, 0xd8, 0x86, 0xc7, 0x03, 0xf2, 0x99, 0xc8, 0x8b, 0x7a, 0x81, 0xbc, 0xa8, 0x67, 0xa9, 0xbb, 0x99, 0x18, + 0x49, 0x2b, 0xb6, 0xe5, 0xb2, 0xc9, 0x66, 0x90, 0xde, 0xde, 0x09, 0xd8, 0x95, 0x11, 0xbe, 0x34, 0x7c, 0x9b, 0x6e, + 0xe9, 0xe1, 0x86, 0x95, 0x79, 0xd8, 0x4a, 0x3b, 0x3c, 0x13, 0x89, 0xf6, 0x0d, 0x83, 0x7a, 0xcd, 0x75, 0xe6, 0xb5, + 0x1a, 0xaa, 0xbc, 0x29, 0xb0, 0xdc, 0x3a, 0x9f, 0x9d, 0x4d, 0x80, 0x63, 0xea, 0xfb, 0x0c, 0xef, 0x55, 0x87, 0x03, + 0x9a, 0xaa, 0x7b, 0x5a, 0x28, 0xe7, 0xb5, 0xfe, 0x79, 0xa4, 0xfa, 0x96, 0xaa, 0xd5, 0x2b, 0x09, 0x81, 0x37, 0xe4, + 0xc6, 0x3b, 0xdd, 0xd2, 0x5d, 0x6c, 0xd6, 0x15, 0xb0, 0xf4, 0x9d, 0xee, 0xa9, 0x3f, 0x55, 0xe3, 0xbd, 0x48, 0x47, + 0xab, 0xc7, 0x02, 0x8e, 0xd9, 0xa3, 0xab, 0x20, 0xf2, 0xce, 0xb4, 0x56, 0x7e, 0xd3, 0x18, 0x60, 0x52, 0xca, 0x80, + 0x45, 0x81, 0x75, 0x7b, 0xaf, 0xa9, 0x6f, 0x97, 0x40, 0x19, 0x1e, 0x48, 0xd9, 0xc5, 0x98, 0xa4, 0xe6, 0x71, 0x1f, + 0xb7, 0xba, 0x07, 0xa1, 0x45, 0xbc, 0x85, 0x98, 0x47, 0x0e, 0xdc, 0x03, 0x3a, 0x8f, 0xd3, 0x09, 0xf7, 0xa2, 0x74, + 0xfb, 0x82, 0x9f, 0x35, 0xc2, 0x69, 0x54, 0xb9, 0xb7, 0x51, 0xe9, 0x28, 0xa7, 0x4c, 0xb5, 0x47, 0x5c, 0xdd, 0xbd, + 0x6a, 0x57, 0xee, 0xb6, 0x5d, 0xb4, 0x79, 0xb4, 0x6b, 0x8e, 0x7c, 0x72, 0x06, 0x58, 0x29, 0x7c, 0x0d, 0x17, 0x30, + 0x42, 0xfc, 0xbe, 0x50, 0x8e, 0x76, 0x34, 0x6c, 0x90, 0xde, 0x60, 0x3b, 0x48, 0x1c, 0x68, 0x87, 0xbc, 0x12, 0xd4, + 0x85, 0xdd, 0xfd, 0xb7, 0xff, 0xf1, 0xbf, 0x94, 0x8f, 0x1d, 0x50, 0xd8, 0xd2, 0x63, 0x2d, 0xec, 0x4a, 0x71, 0x80, + 0xf7, 0x43, 0xab, 0xa0, 0x30, 0xbf, 0x6c, 0x8c, 0xb2, 0x68, 0xd0, 0x18, 0x87, 0xf1, 0x10, 0xc0, 0x59, 0x8b, 0x4d, + 0x6e, 0x5c, 0xdb, 0x52, 0x50, 0xd7, 0x8b, 0x90, 0x5e, 0x7f, 0xd7, 0xc5, 0x23, 0x7d, 0x7f, 0x85, 0x8e, 0xb6, 0x79, + 0x0d, 0xa9, 0x3a, 0x7d, 0xb5, 0xab, 0x48, 0x89, 0xfa, 0xcd, 0x35, 0xc5, 0x01, 0x93, 0xda, 0x0d, 0x24, 0x68, 0x59, + 0x06, 0xb5, 0xfe, 0xef, 0xff, 0xfc, 0x2f, 0xff, 0x4d, 0x3f, 0x62, 0xac, 0xea, 0xdf, 0xfe, 0xfb, 0x7f, 0xfe, 0x3f, + 0xff, 0xfb, 0xbf, 0xe2, 0xad, 0x15, 0x15, 0xcf, 0x22, 0xa6, 0x62, 0x55, 0xc1, 0x2c, 0xc9, 0x5d, 0x2c, 0x4c, 0xec, + 0x9c, 0x00, 0xcf, 0x8c, 0xfa, 0xf5, 0x3b, 0x49, 0x47, 0x34, 0x21, 0x9d, 0x4c, 0x05, 0x1d, 0x9d, 0xf0, 0xa2, 0x22, + 0xa8, 0x1a, 0xca, 0x89, 0x70, 0xa1, 0x12, 0xf1, 0x7d, 0xbb, 0x6b, 0x9c, 0x5e, 0xb9, 0x1d, 0x73, 0x4d, 0x26, 0x58, + 0x52, 0x54, 0xe5, 0x16, 0xc6, 0x56, 0xe6, 0xf8, 0xe8, 0xb7, 0x8d, 0x62, 0xda, 0xbd, 0x5a, 0x9f, 0xce, 0xc7, 0x19, + 0x2c, 0x60, 0x88, 0x28, 0x97, 0x7e, 0x62, 0x0a, 0x63, 0x37, 0x50, 0x57, 0x8c, 0xaf, 0x0a, 0x1a, 0x45, 0x12, 0xe8, + 0xee, 0xfe, 0x3f, 0x15, 0x7f, 0x9d, 0xa0, 0x46, 0x66, 0x39, 0x93, 0xf0, 0x52, 0x99, 0xe7, 0xf7, 0x9a, 0x40, 0xab, + 0xee, 0xbc, 0x9a, 0x81, 0xad, 0x9b, 0x8c, 0xe8, 0xd8, 0x1c, 0x90, 0xe2, 0xdf, 0xa5, 0x1b, 0xbb, 0x69, 0xa1, 0x2f, + 0xdc, 0x6a, 0x16, 0xc5, 0x5f, 0xe6, 0xe4, 0x49, 0x8d, 0x7e, 0xc3, 0x38, 0xb5, 0x72, 0x3a, 0x43, 0x89, 0xb1, 0x8a, + 0xb9, 0xd1, 0xab, 0x2d, 0x7b, 0x8d, 0x5b, 0xcb, 0xb7, 0x13, 0xcd, 0x38, 0xbb, 0x19, 0x21, 0xdf, 0xc5, 0x98, 0xf7, + 0xb8, 0xc5, 0xc6, 0xed, 0x79, 0x39, 0xbc, 0x10, 0xe9, 0xc4, 0x0c, 0xac, 0xf3, 0x90, 0xf7, 0xf9, 0x50, 0x3b, 0xeb, + 0x55, 0xbd, 0x0c, 0x9a, 0x17, 0xe3, 0x9d, 0x15, 0x73, 0x29, 0x90, 0x28, 0xa0, 0x0e, 0xf0, 0x7c, 0x8d, 0x27, 0x10, + 0xf0, 0x9f, 0x86, 0xc2, 0x27, 0x82, 0xed, 0x98, 0xe1, 0xf9, 0x10, 0x79, 0x52, 0x3a, 0x37, 0xe0, 0xe9, 0xc8, 0xa6, + 0xe8, 0x36, 0xaf, 0xdf, 0x21, 0x2d, 0x3c, 0xea, 0x6e, 0x0e, 0x25, 0xbd, 0x6e, 0x3f, 0xa8, 0xa8, 0xf7, 0xdb, 0x9a, + 0xbb, 0x4a, 0x09, 0xa4, 0xb6, 0xbb, 0xba, 0x5e, 0xca, 0x75, 0x59, 0xfb, 0xbd, 0x70, 0x6c, 0x02, 0xd3, 0x5e, 0x6c, + 0x45, 0x85, 0xd8, 0xea, 0x6d, 0xf0, 0x43, 0x69, 0x32, 0x85, 0xd3, 0x29, 0x35, 0x74, 0x3b, 0x40, 0xc6, 0xa4, 0xe9, + 0x22, 0xf7, 0xa0, 0x94, 0x0e, 0x99, 0x41, 0xa1, 0x1a, 0xa9, 0xa3, 0x20, 0xbf, 0xa9, 0xdc, 0x0a, 0xfc, 0x2d, 0xbe, + 0xee, 0xff, 0x03, 0x85, 0xa3, 0x0b, 0x12, 0x20, 0x8b, 0x00, 0x00}; -} // namespace web_server -} // namespace esphome +#else // Brotli (default, smaller) +const uint8_t INDEX_BR[] PROGMEM = { + 0x1b, 0x1f, 0x8b, 0x11, 0x15, 0xb5, 0x07, 0x2e, 0x8a, 0x32, 0xd1, 0x4a, 0x00, 0x3d, 0x0c, 0xf0, 0x64, 0x64, 0xfe, + 0xca, 0x03, 0x1e, 0x15, 0x6c, 0xb2, 0x3d, 0x4d, 0x23, 0x3c, 0x36, 0x77, 0xaa, 0xd8, 0x7a, 0xb4, 0xd1, 0x42, 0x40, + 0x11, 0xba, 0x7d, 0xa8, 0xfe, 0xef, 0x5a, 0xfe, 0xb4, 0x58, 0x8c, 0x03, 0x96, 0xaa, 0x9a, 0x1e, 0xa8, 0x33, 0x7d, + 0x5d, 0x84, 0x65, 0xb5, 0x47, 0x68, 0xec, 0x93, 0x5c, 0xff, 0x2b, 0x9b, 0x76, 0xcf, 0xe5, 0xd4, 0x74, 0x28, 0xfc, + 0x7f, 0x78, 0x92, 0x3d, 0xcb, 0xf6, 0x9b, 0x2f, 0xcd, 0xba, 0xe3, 0x39, 0x31, 0x14, 0x22, 0xa6, 0x8d, 0x55, 0x43, + 0x59, 0x29, 0x8b, 0x2c, 0x54, 0xeb, 0xe5, 0x7e, 0x18, 0xa7, 0x9c, 0x80, 0x44, 0x51, 0xd2, 0x0a, 0x12, 0xb8, 0xda, + 0xff, 0xd4, 0x66, 0x5f, 0x6f, 0x9a, 0x0a, 0xde, 0x78, 0x8c, 0xb4, 0xd8, 0xc1, 0x22, 0x7b, 0x1a, 0x9e, 0xc9, 0x71, + 0xdc, 0x84, 0x1f, 0x2e, 0xe1, 0x68, 0x57, 0xc8, 0x1e, 0xe9, 0x11, 0x48, 0x6c, 0x17, 0x5d, 0xb3, 0x45, 0xf5, 0xfd, + 0xde, 0x57, 0xf7, 0x7d, 0xfd, 0x6e, 0xb4, 0x4d, 0xde, 0x92, 0xd8, 0xe1, 0xd5, 0x46, 0x69, 0x45, 0xc4, 0x13, 0x6b, + 0x40, 0xd1, 0xc0, 0xcc, 0xb5, 0x29, 0x3e, 0x58, 0xaa, 0xf9, 0x6f, 0x55, 0xdb, 0xb9, 0x34, 0xe0, 0xbc, 0xcd, 0x51, + 0x52, 0x9e, 0xe7, 0xdc, 0x45, 0x93, 0x52, 0x21, 0x60, 0x02, 0xa2, 0xbf, 0x95, 0x43, 0x0c, 0xe4, 0xc9, 0x6d, 0x9a, + 0xa6, 0xea, 0xb4, 0xba, 0x26, 0x39, 0xe4, 0xf0, 0x22, 0x5f, 0xea, 0x44, 0x64, 0xf7, 0x65, 0x2b, 0x7d, 0x2b, 0x6d, + 0x8b, 0xe9, 0x9b, 0xfe, 0x74, 0x5a, 0x9d, 0xa4, 0x65, 0xe9, 0x9d, 0x25, 0x63, 0x79, 0xc5, 0x19, 0xb7, 0x0f, 0x54, + 0x02, 0x27, 0xa5, 0x02, 0x7c, 0xd4, 0xb8, 0x51, 0xea, 0x44, 0xe9, 0xf6, 0x99, 0xe8, 0xff, 0x37, 0xd5, 0xb7, 0x76, + 0x00, 0x06, 0xa7, 0x58, 0x34, 0xf6, 0xc6, 0x36, 0xc4, 0x0e, 0xa2, 0xa9, 0x4d, 0xb9, 0x29, 0xb6, 0x28, 0x39, 0xf7, + 0xde, 0xf7, 0xae, 0x35, 0x09, 0x5f, 0x98, 0x01, 0x61, 0x23, 0xd1, 0x02, 0x09, 0x72, 0x99, 0x8f, 0x98, 0x14, 0xd2, + 0x7b, 0x33, 0x03, 0x70, 0x66, 0x40, 0xc9, 0x03, 0x52, 0x32, 0xc3, 0xa7, 0xcf, 0xa1, 0x82, 0x43, 0xca, 0xb2, 0x37, + 0xa4, 0x54, 0x35, 0x21, 0x77, 0x7b, 0xfa, 0x58, 0x95, 0xdf, 0x45, 0xb9, 0xdd, 0x16, 0xed, 0xea, 0x1c, 0xdb, 0x4c, + 0x25, 0x33, 0x2b, 0xe1, 0xfc, 0x2d, 0x43, 0x5b, 0xf3, 0xbc, 0x73, 0xff, 0x5b, 0xd8, 0x81, 0x02, 0x25, 0x64, 0x58, + 0x5d, 0xc6, 0xb4, 0x7e, 0xdc, 0xda, 0x45, 0xc4, 0x10, 0x92, 0x4d, 0xad, 0xed, 0x7f, 0xd8, 0x86, 0xbd, 0xba, 0x8a, + 0x6a, 0x12, 0x8a, 0x71, 0x80, 0x76, 0x0a, 0xca, 0xd0, 0x72, 0x9f, 0x76, 0xdc, 0xe2, 0xdc, 0xc7, 0xa8, 0x39, 0x08, + 0x26, 0x69, 0x0f, 0x5e, 0x4b, 0xc3, 0x81, 0x08, 0x34, 0x8a, 0x3c, 0x95, 0xfc, 0xf4, 0x81, 0xed, 0x9b, 0xe7, 0x33, + 0x36, 0xe8, 0xd5, 0xb0, 0xd6, 0x03, 0x6e, 0x01, 0xdb, 0x4f, 0x83, 0xb3, 0x11, 0x2c, 0xcd, 0x6a, 0x2e, 0x7c, 0xf6, + 0xb8, 0xf1, 0xf2, 0xcd, 0xcd, 0xda, 0x11, 0x1b, 0xfd, 0x23, 0xe5, 0xa7, 0xb4, 0x70, 0xea, 0xaa, 0x4b, 0xde, 0xba, + 0xba, 0xf0, 0xa1, 0xe6, 0x61, 0xe1, 0xe1, 0x12, 0xbc, 0x85, 0x31, 0xf6, 0xbb, 0x8c, 0x67, 0x86, 0x8c, 0x49, 0x17, + 0xad, 0x1e, 0xb8, 0x99, 0xf9, 0x79, 0xb3, 0xf6, 0xa5, 0x3a, 0x9c, 0x0b, 0xcc, 0xce, 0xb4, 0xe9, 0x7a, 0x29, 0x10, + 0xec, 0xa6, 0x2d, 0x45, 0x4a, 0xcf, 0xfe, 0x36, 0xca, 0x53, 0xc3, 0x3d, 0xf3, 0xe8, 0xb2, 0x1f, 0x7e, 0xc5, 0xc1, + 0x0a, 0x48, 0x64, 0x14, 0xbe, 0x73, 0xa4, 0x5c, 0xb9, 0xf4, 0x26, 0xcf, 0x91, 0xcb, 0xd2, 0xaf, 0x3c, 0xc7, 0x7a, + 0x44, 0x3e, 0x21, 0x3a, 0xeb, 0xf9, 0xf1, 0x34, 0x2c, 0x85, 0x31, 0xaf, 0x88, 0xdb, 0xf9, 0x69, 0x7d, 0xbb, 0x32, + 0xf0, 0x18, 0x72, 0x27, 0xb4, 0x1e, 0x88, 0x6e, 0xdf, 0x78, 0xe7, 0x72, 0xed, 0xea, 0xf5, 0x5e, 0x6d, 0x4d, 0xfa, + 0x57, 0xe9, 0x6a, 0xd9, 0x53, 0xb8, 0xba, 0x7b, 0x8e, 0x1d, 0xc1, 0xcd, 0x6c, 0xed, 0x78, 0x8a, 0xd0, 0xec, 0xb0, + 0x9e, 0xab, 0xe1, 0x44, 0xa3, 0x3b, 0x21, 0x39, 0x8e, 0x8d, 0x82, 0xe5, 0xed, 0x92, 0x14, 0x25, 0x2d, 0x1f, 0x72, + 0x13, 0xc8, 0x0c, 0xff, 0x80, 0xdc, 0xcb, 0x9d, 0x33, 0xdf, 0x85, 0xbd, 0xf0, 0x09, 0xd6, 0x01, 0x8a, 0xab, 0x79, + 0x76, 0x79, 0x7a, 0x6c, 0x7d, 0xf9, 0xb2, 0x83, 0xfc, 0x77, 0x73, 0x44, 0x61, 0xfc, 0xa9, 0xcf, 0xa0, 0x91, 0xeb, + 0xa6, 0x91, 0x10, 0x14, 0x90, 0xeb, 0x80, 0xf9, 0xc8, 0x1c, 0x01, 0x4f, 0x87, 0x73, 0x55, 0xeb, 0x12, 0x78, 0x97, + 0x9b, 0x8f, 0xc7, 0x3b, 0xc9, 0xc3, 0xf5, 0x0a, 0xfe, 0xd4, 0x1f, 0xb9, 0xb6, 0x50, 0xd7, 0x09, 0x28, 0x89, 0x9d, + 0xbc, 0x31, 0xd5, 0x32, 0xc9, 0x13, 0xcd, 0xae, 0xad, 0x1c, 0x90, 0x20, 0x6f, 0xd8, 0x20, 0x37, 0x4a, 0xb6, 0x5a, + 0x7d, 0xb4, 0xfb, 0x3b, 0x97, 0x85, 0xc0, 0xf1, 0x37, 0xc7, 0xd6, 0xe4, 0x9c, 0x00, 0x02, 0x48, 0x7f, 0x1f, 0x34, + 0x2b, 0x36, 0x91, 0x65, 0x0e, 0x81, 0x4f, 0x78, 0x52, 0x30, 0x93, 0x34, 0x7d, 0xbd, 0x65, 0x64, 0x96, 0x72, 0x65, + 0x88, 0xcb, 0xf0, 0xf8, 0x65, 0x99, 0x67, 0x1a, 0x15, 0x9e, 0xf4, 0x89, 0x89, 0x92, 0x21, 0xcf, 0x90, 0xa7, 0x0b, + 0x68, 0x2e, 0xe0, 0x74, 0xe5, 0x09, 0xb1, 0x2c, 0x60, 0x2a, 0x08, 0x1d, 0x7b, 0x1b, 0x57, 0x4d, 0x27, 0xd2, 0x10, + 0x07, 0x93, 0x3f, 0x3d, 0x79, 0xe6, 0xf8, 0x57, 0x5c, 0xca, 0x12, 0x90, 0x04, 0xdf, 0xfd, 0x72, 0x11, 0x54, 0x31, + 0x2c, 0xcd, 0xa2, 0x49, 0xac, 0x00, 0x29, 0x4b, 0x20, 0x81, 0x84, 0x3a, 0x31, 0xf7, 0xb3, 0x43, 0xec, 0xf8, 0x11, + 0x9f, 0xde, 0x03, 0xbb, 0x35, 0xc1, 0x2c, 0x5f, 0xcb, 0x29, 0x66, 0x35, 0x17, 0x52, 0x92, 0x73, 0xaa, 0xfb, 0x64, + 0xb3, 0xb1, 0xbb, 0xa2, 0x95, 0x99, 0xc2, 0xde, 0x37, 0xaa, 0x21, 0xa6, 0xdd, 0xce, 0x2f, 0x84, 0x75, 0x25, 0xd1, + 0xc4, 0x52, 0x55, 0xf8, 0xfe, 0xaf, 0x07, 0x85, 0xe9, 0x34, 0xf4, 0x46, 0xbf, 0x67, 0xe6, 0x2e, 0x77, 0x8c, 0xc2, + 0x32, 0xd4, 0xbc, 0x54, 0xcb, 0xd4, 0xda, 0x08, 0x30, 0x80, 0x43, 0x5a, 0x09, 0xf1, 0x37, 0xed, 0x12, 0xb1, 0xc3, + 0xc8, 0xf2, 0xeb, 0x7a, 0x06, 0x95, 0x88, 0xf6, 0x7e, 0x76, 0x94, 0x3c, 0xe6, 0x1f, 0x62, 0xf6, 0x68, 0x96, 0x41, + 0x09, 0x63, 0x7c, 0x67, 0x72, 0x80, 0xce, 0xb4, 0x5b, 0xb9, 0xc8, 0x81, 0x6e, 0x61, 0x3b, 0x38, 0xaf, 0x72, 0x4d, + 0x73, 0x42, 0xb5, 0x55, 0x89, 0xe4, 0xce, 0x06, 0x09, 0x8d, 0xc1, 0x52, 0x86, 0xe5, 0x59, 0x47, 0xb8, 0x87, 0x5c, + 0xe5, 0xfd, 0x92, 0x68, 0xc6, 0x64, 0x76, 0x37, 0xe8, 0xb0, 0x5b, 0x70, 0xc6, 0xe7, 0x8d, 0x58, 0xb2, 0x4f, 0xdc, + 0x4e, 0x7c, 0x2c, 0x19, 0xa4, 0x57, 0x96, 0xcc, 0x41, 0x50, 0xd7, 0x0e, 0x6c, 0x13, 0xd1, 0x02, 0x63, 0x9d, 0x3f, + 0x33, 0x7d, 0x51, 0x74, 0x42, 0xa5, 0x5d, 0x81, 0x23, 0x15, 0xe8, 0xe8, 0xc1, 0xf1, 0x74, 0x8a, 0x34, 0xef, 0x08, + 0x50, 0xec, 0xbd, 0x0d, 0x3f, 0xb2, 0x4c, 0xe7, 0x51, 0xc7, 0x97, 0xcc, 0xf5, 0xd0, 0xa6, 0xba, 0xed, 0xa4, 0x50, + 0xec, 0x54, 0x85, 0xb4, 0x86, 0x6d, 0xa7, 0x71, 0x56, 0x2b, 0x54, 0xae, 0x08, 0x1f, 0xea, 0xad, 0xc9, 0xbb, 0xd8, + 0x25, 0x46, 0x59, 0xef, 0x67, 0x17, 0xc9, 0xcc, 0x57, 0xcf, 0x03, 0x2a, 0x24, 0x6c, 0xf3, 0x84, 0xb9, 0xfb, 0x31, + 0x33, 0x18, 0x06, 0x9d, 0x78, 0x42, 0xbc, 0x23, 0x4d, 0x09, 0x15, 0x46, 0x65, 0xb5, 0x3f, 0x38, 0xb6, 0xe8, 0xad, + 0x90, 0x0c, 0x88, 0x37, 0xe1, 0xb8, 0x14, 0x12, 0xac, 0x68, 0xac, 0x00, 0x9c, 0x68, 0x81, 0xf8, 0x85, 0x1a, 0xf4, + 0x08, 0xf5, 0x3a, 0x91, 0xf8, 0x4e, 0xf3, 0xd6, 0x19, 0x18, 0x14, 0x56, 0x1d, 0xa4, 0x57, 0xd9, 0xe9, 0x1d, 0xc0, + 0x89, 0x8a, 0xd0, 0xa7, 0x4e, 0x3c, 0x88, 0xc8, 0x9b, 0x4e, 0x2c, 0x61, 0xca, 0x38, 0x9f, 0x66, 0xd7, 0xda, 0x11, + 0x8d, 0xdd, 0xed, 0x36, 0x10, 0x8c, 0xd5, 0xed, 0xa0, 0xef, 0x46, 0xfb, 0x4e, 0x04, 0xec, 0x60, 0x34, 0xac, 0xf6, + 0x9f, 0x79, 0x1a, 0x55, 0x82, 0x3b, 0x9f, 0x73, 0x03, 0x65, 0x2f, 0x06, 0x53, 0xd7, 0x12, 0x7e, 0x51, 0xae, 0x0a, + 0xec, 0x4b, 0x0a, 0xf8, 0x7b, 0x4a, 0x42, 0x36, 0xd7, 0x47, 0x79, 0x69, 0xc0, 0xcf, 0x1d, 0xf2, 0x39, 0x97, 0xba, + 0xbe, 0xe9, 0x75, 0xb1, 0xac, 0x1a, 0xeb, 0xbd, 0x33, 0x2d, 0xa6, 0x06, 0x6c, 0xdd, 0x79, 0x2e, 0x9a, 0x5b, 0xb0, + 0x82, 0xb1, 0x83, 0x23, 0xe1, 0x70, 0x98, 0x39, 0xdd, 0xb4, 0x02, 0xc3, 0xc6, 0x21, 0x7d, 0x21, 0x81, 0xdf, 0xca, + 0x0a, 0x78, 0x4f, 0xda, 0x88, 0x8b, 0x41, 0xcb, 0x2d, 0x7c, 0x99, 0xdf, 0x1c, 0xc9, 0x5c, 0x61, 0x4e, 0xfd, 0x73, + 0xb5, 0x8f, 0xca, 0xd8, 0x0c, 0xa8, 0x18, 0x69, 0xcd, 0x28, 0xb7, 0xab, 0xe0, 0x7e, 0x45, 0x79, 0x15, 0xbc, 0xe1, + 0xe2, 0xba, 0x7e, 0x68, 0xc2, 0x5b, 0x98, 0x2e, 0xca, 0x3c, 0x65, 0x96, 0x27, 0x67, 0x3c, 0x91, 0x05, 0xf2, 0xb4, + 0xa9, 0x8f, 0x1f, 0x32, 0x17, 0x26, 0x41, 0x6a, 0x78, 0x6c, 0xb9, 0xfd, 0xae, 0x36, 0xb6, 0x18, 0x19, 0x00, 0xb4, + 0x3b, 0x0d, 0x4a, 0x05, 0x40, 0x26, 0x79, 0x24, 0xe7, 0x46, 0x83, 0xbe, 0x7e, 0x91, 0x9e, 0x6d, 0x3a, 0x1e, 0x92, + 0x75, 0xdf, 0x67, 0x14, 0x94, 0xf1, 0x6e, 0xb1, 0x2e, 0x00, 0x84, 0xc5, 0x00, 0x1d, 0x37, 0xfe, 0x72, 0x90, 0x56, + 0x06, 0x32, 0x72, 0x37, 0x6f, 0x97, 0x07, 0xbb, 0xf0, 0xeb, 0xaf, 0x6c, 0x52, 0xc0, 0x38, 0x1e, 0xf9, 0x63, 0xf9, + 0x08, 0x45, 0x02, 0x6a, 0x56, 0x28, 0x87, 0x02, 0xe8, 0x30, 0x11, 0xbd, 0xd6, 0xfa, 0xb8, 0xc5, 0xdc, 0xeb, 0x2b, + 0x34, 0x89, 0xa8, 0x68, 0x37, 0x8d, 0x5a, 0x51, 0x2f, 0x21, 0x06, 0xff, 0x84, 0x63, 0x87, 0x0e, 0x12, 0x95, 0xac, + 0x52, 0x65, 0xc3, 0x3a, 0x58, 0x1f, 0x34, 0x00, 0x0d, 0x1c, 0xd4, 0xe8, 0xe6, 0xdb, 0x44, 0x56, 0xbb, 0x8f, 0x28, + 0x4d, 0x23, 0x1d, 0xbc, 0xbc, 0xa1, 0x42, 0xa0, 0x5f, 0xf6, 0xc8, 0x57, 0x2d, 0x6b, 0xa1, 0xa4, 0x3a, 0x81, 0x3b, + 0xec, 0x09, 0x0b, 0x94, 0x68, 0xf9, 0x53, 0x33, 0x50, 0xc9, 0x20, 0xf6, 0x3c, 0xef, 0x8f, 0x4f, 0xfc, 0xe8, 0xd2, + 0x65, 0x1e, 0x52, 0x49, 0x24, 0x7c, 0xc2, 0x93, 0x03, 0xba, 0xee, 0x81, 0xa4, 0x00, 0xde, 0x35, 0x60, 0x2a, 0x9f, + 0x1b, 0xe2, 0xe1, 0x64, 0x3b, 0x2d, 0x7c, 0x5c, 0xcc, 0x46, 0x0d, 0x07, 0x33, 0xd1, 0x85, 0x2b, 0xe0, 0xad, 0xd7, + 0x23, 0x3a, 0x16, 0x98, 0x25, 0x90, 0x08, 0x91, 0xce, 0x45, 0x73, 0xb6, 0x2a, 0x79, 0x9d, 0x64, 0x86, 0x22, 0x52, + 0xab, 0xe4, 0x26, 0x30, 0x37, 0x4e, 0x15, 0xb1, 0x90, 0x94, 0x94, 0x09, 0x07, 0x08, 0x96, 0x2b, 0xa2, 0xa3, 0xe4, + 0x3d, 0xaa, 0x2b, 0x09, 0xb7, 0xf3, 0x5f, 0xa6, 0x34, 0xc1, 0xec, 0xca, 0x62, 0x1f, 0x0c, 0x90, 0xd2, 0x2d, 0xb5, + 0x19, 0xbc, 0x15, 0x11, 0x4f, 0x45, 0x40, 0x25, 0x22, 0x51, 0x78, 0x4f, 0x6e, 0xb5, 0x67, 0xd1, 0x7c, 0xd6, 0x8d, + 0x85, 0x31, 0x9d, 0x61, 0xef, 0x69, 0xb1, 0xa2, 0x1c, 0x37, 0xda, 0x89, 0x6a, 0xda, 0xdf, 0xa8, 0x7c, 0x44, 0x6c, + 0x3b, 0xfd, 0x28, 0xc5, 0x20, 0x1a, 0x4d, 0xae, 0xa9, 0xf4, 0xb3, 0xd2, 0x3f, 0x1f, 0x0b, 0xd4, 0x1a, 0xf5, 0x9a, + 0x9f, 0x3f, 0xeb, 0xdc, 0x86, 0xa8, 0xb3, 0x76, 0x89, 0x03, 0x6e, 0xae, 0x9a, 0x6e, 0x95, 0x44, 0xf8, 0xaf, 0x65, + 0x72, 0x40, 0xa1, 0xdd, 0x99, 0xcd, 0xd1, 0xee, 0xd7, 0x21, 0xd9, 0x8f, 0x16, 0x2b, 0x10, 0x00, 0x4a, 0xc1, 0x2c, + 0x46, 0x91, 0x03, 0x55, 0x0c, 0x48, 0x29, 0xe7, 0x11, 0xb4, 0x28, 0xba, 0x0a, 0x60, 0x28, 0x54, 0x49, 0x23, 0xd7, + 0xb0, 0xd8, 0x6c, 0x0c, 0xc4, 0xa8, 0x55, 0xb7, 0x11, 0x08, 0x49, 0xb6, 0x5c, 0x3d, 0xb5, 0xa0, 0x14, 0x69, 0xf2, + 0xee, 0x82, 0xc2, 0x9a, 0x70, 0x5c, 0x19, 0xa0, 0xcc, 0x1f, 0x85, 0x89, 0xda, 0xaf, 0x9d, 0x17, 0xbb, 0xe2, 0x05, + 0x83, 0x0d, 0x17, 0x92, 0x5f, 0x89, 0x4c, 0x09, 0x0a, 0xa7, 0x2c, 0x69, 0xec, 0x65, 0x5b, 0xa7, 0xf2, 0xe5, 0x59, + 0x52, 0x29, 0x58, 0x19, 0xd5, 0x88, 0x83, 0x3b, 0x55, 0xd7, 0xb5, 0xea, 0x08, 0x2d, 0x7f, 0x74, 0x2a, 0xc9, 0x7b, + 0xd3, 0x4d, 0x46, 0x27, 0xb3, 0xce, 0xaa, 0x72, 0xbf, 0x1e, 0x9c, 0xe9, 0xe4, 0x49, 0x5d, 0x35, 0xc1, 0xb3, 0x82, + 0x22, 0x10, 0xf6, 0x78, 0xf3, 0x49, 0x75, 0xb4, 0x4f, 0x02, 0x96, 0x7c, 0x63, 0xf0, 0x26, 0xd5, 0x11, 0x13, 0x9a, + 0x96, 0xcf, 0x7d, 0x04, 0xce, 0x4a, 0x9d, 0x45, 0x07, 0xe0, 0xc3, 0x0b, 0x94, 0x1e, 0x94, 0x2a, 0x4b, 0xdd, 0xe4, + 0x36, 0xe4, 0x98, 0x80, 0x83, 0x1e, 0x05, 0x79, 0x3a, 0x3d, 0x71, 0x53, 0xa5, 0xd2, 0x93, 0x17, 0x4b, 0x36, 0x33, + 0xc9, 0xac, 0xae, 0x72, 0x8e, 0xfd, 0xa7, 0x00, 0x4d, 0xd3, 0xdc, 0xb0, 0x02, 0x91, 0x9e, 0x59, 0x90, 0x1b, 0x5b, + 0x71, 0xbe, 0x12, 0xa4, 0x48, 0x99, 0x90, 0x27, 0x68, 0xcb, 0x11, 0x55, 0x25, 0x4a, 0xe8, 0x40, 0x91, 0xc9, 0x90, + 0x65, 0x4d, 0x2a, 0xd9, 0xb7, 0x7c, 0x8d, 0x43, 0x26, 0x29, 0xc9, 0x69, 0x72, 0xdc, 0xcb, 0xe6, 0xb0, 0x2b, 0x43, + 0x54, 0x42, 0xa3, 0x14, 0xb6, 0x0b, 0x43, 0xf2, 0xd4, 0x33, 0xa5, 0x5b, 0x6a, 0x3b, 0x42, 0xba, 0x33, 0x23, 0x79, + 0x3b, 0xa0, 0xea, 0xeb, 0xe8, 0x82, 0x63, 0x41, 0x41, 0x23, 0x92, 0x24, 0xcf, 0x25, 0xc5, 0x20, 0x87, 0x91, 0x42, + 0x89, 0x67, 0xb2, 0xc5, 0x58, 0x16, 0xd1, 0xf7, 0x83, 0x14, 0xcc, 0xd2, 0x96, 0x41, 0x44, 0xfa, 0x34, 0x88, 0xbd, + 0x52, 0x3c, 0x42, 0xe5, 0x72, 0xef, 0xde, 0x46, 0x61, 0x49, 0xaa, 0x93, 0x32, 0x41, 0xd0, 0x9e, 0x45, 0xa3, 0x13, + 0x06, 0xcc, 0x47, 0x13, 0x28, 0x2f, 0x87, 0xcd, 0x61, 0x61, 0xf7, 0x21, 0xc5, 0xf3, 0x65, 0xf6, 0xf3, 0x99, 0xe5, + 0x1c, 0x59, 0xce, 0x0c, 0x9d, 0xb4, 0x29, 0xa4, 0xb0, 0x59, 0x3e, 0x11, 0x22, 0xd3, 0xbc, 0x73, 0xb1, 0x38, 0xd0, + 0x33, 0xfc, 0xae, 0x11, 0xdc, 0x00, 0x95, 0x30, 0x42, 0xae, 0xda, 0xac, 0x76, 0xd3, 0xea, 0x43, 0x0a, 0xab, 0x1d, + 0xdd, 0x70, 0x29, 0x76, 0xef, 0x14, 0x67, 0xde, 0x9b, 0x54, 0x98, 0xf7, 0x4a, 0x47, 0x26, 0x76, 0x79, 0x0b, 0x5f, + 0x2b, 0xc1, 0xe9, 0x39, 0xaf, 0xc2, 0x3b, 0x48, 0xa1, 0x22, 0x98, 0xac, 0x70, 0xc0, 0x50, 0x39, 0x9e, 0x8c, 0xf3, + 0x16, 0x51, 0xfc, 0x1c, 0x2c, 0x27, 0xa1, 0xa1, 0xac, 0x16, 0x28, 0xd8, 0x43, 0xf7, 0x79, 0x1f, 0xa5, 0x14, 0xd8, + 0xf7, 0x55, 0x12, 0x4f, 0x0e, 0x61, 0x66, 0x8c, 0x27, 0xb6, 0x16, 0x54, 0xc6, 0x70, 0xa1, 0xc9, 0x06, 0x7e, 0xb8, + 0xed, 0x33, 0x33, 0x44, 0xfb, 0x72, 0x90, 0x0b, 0xf3, 0x6a, 0x4d, 0x81, 0xa8, 0x80, 0x85, 0xd2, 0xf7, 0xb6, 0x23, + 0x5c, 0xec, 0xc0, 0x70, 0x5b, 0x78, 0x86, 0xc9, 0xb8, 0x6a, 0x85, 0x26, 0x56, 0xd5, 0xc2, 0x1e, 0xe8, 0x46, 0x6d, + 0x4b, 0x47, 0xfa, 0x39, 0x99, 0x8a, 0x5d, 0xc7, 0x4a, 0xaf, 0x05, 0x2d, 0xfe, 0xb6, 0xbe, 0x1c, 0x8b, 0x51, 0x09, + 0x47, 0x64, 0xa8, 0xf9, 0x69, 0x10, 0xf9, 0xb1, 0x96, 0x19, 0x78, 0x9a, 0x8e, 0xb0, 0x18, 0xfc, 0x27, 0xab, 0x57, + 0x49, 0x78, 0xd1, 0x56, 0x68, 0x4e, 0x6b, 0xdf, 0x70, 0xdb, 0x54, 0x6e, 0x91, 0x8a, 0xfd, 0xbb, 0x0e, 0x42, 0xcb, + 0x14, 0x2a, 0xd4, 0x44, 0x73, 0x94, 0x4b, 0x15, 0xc7, 0x41, 0xa7, 0xdb, 0xb0, 0xb1, 0x4a, 0x92, 0xe8, 0x2e, 0xef, + 0xa0, 0x4f, 0x5b, 0x20, 0x55, 0x75, 0x4d, 0x51, 0x2c, 0x9a, 0xdf, 0xc4, 0x50, 0x35, 0xf3, 0x65, 0x06, 0x0e, 0xdc, + 0xcb, 0xa9, 0x92, 0x74, 0xcf, 0xe5, 0x57, 0x51, 0xb1, 0xfc, 0x47, 0x0a, 0x65, 0x7e, 0x8c, 0x4a, 0x06, 0x96, 0x16, + 0x10, 0x75, 0x9d, 0xe3, 0x52, 0x42, 0xe6, 0x8c, 0x5b, 0xf2, 0xaa, 0x58, 0xbb, 0x5b, 0xcc, 0x0d, 0x77, 0xfd, 0x52, + 0xc3, 0x41, 0x2e, 0x48, 0x33, 0x1a, 0x57, 0x90, 0xe1, 0x77, 0x31, 0x2a, 0x2c, 0xf6, 0xab, 0x38, 0x69, 0x12, 0xd1, + 0x78, 0x0c, 0xf8, 0x33, 0xd9, 0x84, 0xee, 0x0d, 0x88, 0xbc, 0x85, 0x5c, 0x9e, 0x66, 0x99, 0x94, 0x8e, 0xc0, 0x22, + 0xd1, 0xed, 0xbb, 0x9a, 0xa2, 0x0a, 0x73, 0xbd, 0xf9, 0x29, 0xad, 0xd4, 0x29, 0x6b, 0x60, 0x0a, 0x0b, 0x4a, 0xea, + 0xcc, 0x39, 0xac, 0x23, 0x63, 0xaa, 0x4e, 0x8a, 0x9b, 0xb4, 0xeb, 0xc2, 0xac, 0x41, 0xa6, 0x08, 0x44, 0xa7, 0x43, + 0x65, 0x94, 0x7d, 0x10, 0x00, 0x77, 0x19, 0x20, 0x5a, 0x82, 0x44, 0x00, 0x2f, 0xe9, 0x9f, 0x06, 0x1a, 0xb3, 0x71, + 0x7d, 0x95, 0xca, 0x51, 0x93, 0xca, 0xc7, 0xc4, 0x76, 0x54, 0x0d, 0x13, 0x9d, 0xf2, 0x5a, 0x28, 0xa6, 0xd5, 0x1e, + 0x5a, 0x7f, 0xa5, 0x18, 0x7a, 0xd7, 0xb2, 0xc2, 0xd8, 0xd3, 0x24, 0xeb, 0x2c, 0xc0, 0x46, 0xa6, 0xee, 0x0f, 0xb2, + 0xb5, 0x27, 0x0a, 0xec, 0x12, 0x2a, 0x32, 0x5c, 0xd4, 0x37, 0x43, 0x8a, 0x7b, 0x38, 0xc6, 0xe3, 0xf6, 0x93, 0x4d, + 0x6a, 0xef, 0x53, 0x79, 0x4f, 0xaf, 0x63, 0xac, 0x8d, 0xf9, 0xc9, 0x53, 0xc9, 0x82, 0x2f, 0xa3, 0x11, 0xe6, 0x49, + 0xc4, 0x82, 0xb6, 0x00, 0xd8, 0xe1, 0x42, 0x87, 0x1d, 0x2f, 0x97, 0x21, 0xee, 0x29, 0xcc, 0x83, 0xd1, 0x7a, 0x6a, + 0x43, 0xe6, 0xf5, 0xc2, 0xc8, 0xda, 0x24, 0x65, 0xfd, 0x73, 0xe7, 0xb1, 0x3c, 0x72, 0x3d, 0xe6, 0x49, 0x9d, 0xa2, + 0x3c, 0x54, 0x5a, 0x98, 0x45, 0xfe, 0xba, 0x98, 0x85, 0xc7, 0xc0, 0x99, 0x90, 0x09, 0x0d, 0xcc, 0xc8, 0x85, 0xcd, + 0x0b, 0x51, 0xb2, 0xd8, 0x62, 0x79, 0xa7, 0x90, 0xfc, 0xf8, 0x4e, 0x0a, 0x34, 0xa2, 0x20, 0xa8, 0x56, 0x5e, 0x50, + 0x28, 0x0b, 0xab, 0xfc, 0xce, 0xd3, 0xbe, 0x4f, 0xce, 0xbb, 0xc4, 0x72, 0x39, 0x7b, 0x8c, 0x8f, 0xe9, 0x9f, 0x63, + 0xde, 0x7e, 0xac, 0x78, 0x71, 0x2d, 0x3c, 0x2d, 0x77, 0xb1, 0x36, 0xf3, 0x44, 0x6d, 0x02, 0x78, 0x37, 0x53, 0x4b, + 0xbf, 0x13, 0x66, 0xfd, 0x0c, 0x6c, 0xbe, 0x52, 0x78, 0x7f, 0x18, 0x80, 0x95, 0xbb, 0x27, 0x50, 0x0c, 0xdb, 0x92, + 0x87, 0xfb, 0x7b, 0xc8, 0xeb, 0x28, 0x7e, 0xc8, 0x56, 0x27, 0xc2, 0x8f, 0xb0, 0xd1, 0xcb, 0x92, 0xba, 0x2b, 0x5c, + 0xef, 0xdd, 0x84, 0x87, 0x4f, 0xd8, 0xd5, 0x9f, 0x3b, 0xa3, 0x6d, 0xdd, 0x5c, 0x6f, 0x6c, 0x50, 0xfa, 0xcf, 0xbb, + 0xb8, 0xe4, 0xf4, 0x46, 0xd5, 0xe5, 0x2e, 0x05, 0xe8, 0x17, 0x42, 0x64, 0x11, 0xc3, 0xda, 0xde, 0x70, 0xf8, 0x7e, + 0x39, 0x92, 0x1d, 0x3c, 0xbd, 0xd1, 0x1c, 0x82, 0x99, 0x86, 0x27, 0xf6, 0xf1, 0x8a, 0x2c, 0xe6, 0xec, 0x12, 0x80, + 0xdf, 0xfe, 0x54, 0x7f, 0xb1, 0xf7, 0xe0, 0x7c, 0x18, 0xcc, 0x93, 0x43, 0x0f, 0x9a, 0x73, 0xfe, 0xb0, 0x0f, 0xbb, + 0x7f, 0x14, 0xf3, 0xc4, 0xa6, 0x0b, 0x1e, 0x23, 0xf7, 0xb7, 0x60, 0x1f, 0x59, 0xe1, 0xc9, 0x3b, 0x88, 0x44, 0x16, + 0x7e, 0xa3, 0x38, 0xe0, 0xd8, 0x13, 0x04, 0xcc, 0x54, 0x32, 0xc5, 0x62, 0x9c, 0xe8, 0x18, 0xa7, 0xf3, 0x0b, 0xbf, + 0x9c, 0xed, 0x85, 0x87, 0xb1, 0x6b, 0xf7, 0xca, 0xeb, 0x58, 0x77, 0x6b, 0x6b, 0x6b, 0x4b, 0xc6, 0x53, 0x20, 0xb9, + 0x69, 0xc9, 0x9f, 0x41, 0x5e, 0xc2, 0x0c, 0xdf, 0xba, 0x66, 0x67, 0xbb, 0xa7, 0x63, 0x5b, 0xf5, 0x76, 0xff, 0xd2, + 0x56, 0x35, 0xec, 0xff, 0xf6, 0x65, 0xed, 0xe5, 0xfe, 0x67, 0x5b, 0xba, 0x60, 0x4b, 0xad, 0xae, 0xff, 0x1a, 0xfb, + 0x41, 0x18, 0x96, 0x2f, 0xad, 0xfe, 0xc5, 0xe8, 0xed, 0xf3, 0x9b, 0xf0, 0xb9, 0xe8, 0xc4, 0x90, 0x97, 0xf4, 0xfa, + 0x53, 0xa9, 0x95, 0xe4, 0x5f, 0x57, 0x5e, 0x3c, 0x7c, 0x69, 0x8f, 0xeb, 0x75, 0xca, 0x3b, 0x7b, 0x2b, 0x0c, 0x93, + 0xa8, 0xd0, 0xc0, 0x43, 0x70, 0xad, 0xff, 0x69, 0x8f, 0x1b, 0xaf, 0x92, 0xc9, 0x2b, 0x56, 0x47, 0xcb, 0x53, 0x62, + 0xe3, 0xe5, 0xfa, 0xf3, 0x55, 0xe2, 0xad, 0xc1, 0x92, 0xcb, 0x12, 0xff, 0x05, 0xfe, 0x64, 0x12, 0xa6, 0xaa, 0x80, + 0xe4, 0xaf, 0xe4, 0x7c, 0xf5, 0x32, 0xec, 0x7e, 0xf1, 0xc1, 0xc4, 0x5c, 0xd9, 0xe0, 0x13, 0xd7, 0xc9, 0xe3, 0x5d, + 0xe0, 0xd2, 0xb3, 0xa2, 0xb3, 0xb7, 0x1a, 0xad, 0x94, 0x53, 0xbf, 0x00, 0x85, 0x9f, 0x17, 0xfe, 0xd3, 0x53, 0xa0, + 0xcd, 0x9e, 0x62, 0x1c, 0x5e, 0x99, 0x24, 0xb6, 0xcb, 0x64, 0xed, 0x26, 0x3e, 0x6f, 0x29, 0xde, 0xa2, 0xaa, 0x22, + 0x88, 0xc4, 0x6c, 0xec, 0xe0, 0x89, 0xf4, 0x97, 0x03, 0x0e, 0x74, 0xd3, 0xf5, 0xe2, 0xcc, 0x7f, 0x1a, 0xbb, 0x00, + 0x4c, 0x08, 0xff, 0x72, 0x46, 0x43, 0x31, 0xd1, 0x5f, 0x4b, 0x05, 0xac, 0xf9, 0x84, 0x31, 0x76, 0x7f, 0x26, 0x32, + 0x81, 0x9b, 0x89, 0xb0, 0x02, 0xd3, 0x8f, 0x7e, 0x7b, 0x8a, 0x30, 0xf1, 0x9d, 0xc8, 0x59, 0x9b, 0x35, 0xfd, 0x47, + 0xda, 0xb3, 0x8d, 0x07, 0x91, 0x05, 0xec, 0x33, 0x04, 0x76, 0x9e, 0x71, 0x63, 0xb6, 0xf2, 0xc8, 0x2a, 0xd6, 0x8c, + 0x44, 0x2f, 0x0c, 0x04, 0x52, 0xce, 0x2a, 0xd8, 0xa4, 0x5c, 0x01, 0x68, 0x5a, 0x7d, 0xf8, 0x71, 0xd4, 0xf7, 0xaf, + 0x9b, 0xa8, 0xd5, 0xbb, 0xb1, 0x75, 0x19, 0x35, 0x3f, 0xea, 0xab, 0x3d, 0x05, 0x2f, 0x67, 0xa9, 0x89, 0x3e, 0xe3, + 0x59, 0xf2, 0xca, 0xd1, 0x6e, 0x48, 0xb6, 0x5e, 0xe7, 0x91, 0x85, 0x6b, 0x7e, 0xd9, 0x26, 0x04, 0xc3, 0x48, 0xcd, + 0x50, 0x52, 0x20, 0x32, 0xcf, 0xd7, 0xa9, 0x04, 0x39, 0x76, 0x84, 0x36, 0xe1, 0xef, 0x94, 0x2a, 0x12, 0x62, 0x7b, + 0x04, 0x5d, 0xfb, 0xeb, 0xd8, 0xc4, 0xd8, 0x41, 0x20, 0xba, 0x0c, 0x0e, 0x41, 0xa5, 0xaf, 0x1a, 0x15, 0x59, 0xbc, + 0xd0, 0x1c, 0x53, 0x93, 0xb7, 0x66, 0xa4, 0x60, 0x89, 0x4d, 0x7c, 0xc5, 0x80, 0x9a, 0xad, 0x42, 0xc1, 0x46, 0xd1, + 0x6a, 0xdd, 0x8f, 0xcd, 0x25, 0x6d, 0xea, 0x9d, 0x17, 0x3e, 0x93, 0x38, 0x4e, 0x9e, 0x5f, 0xd3, 0x53, 0xd6, 0xca, + 0x82, 0xfe, 0xc9, 0x13, 0x49, 0x40, 0x2d, 0x6d, 0xde, 0x59, 0x53, 0x71, 0x54, 0xd5, 0xdf, 0x3e, 0x44, 0x88, 0x97, + 0xd7, 0xe9, 0x52, 0xad, 0x1c, 0x11, 0xea, 0x61, 0x08, 0xbd, 0x4c, 0xb9, 0xad, 0x94, 0xbb, 0x6e, 0xce, 0x62, 0x5a, + 0x3a, 0x6e, 0xb2, 0x9f, 0x9e, 0xbb, 0x01, 0x8b, 0x20, 0x06, 0xe0, 0x19, 0x71, 0x2f, 0xb9, 0xee, 0x08, 0x9a, 0x4b, + 0x90, 0x40, 0x9f, 0x51, 0xfc, 0xe0, 0x05, 0x63, 0x94, 0xcc, 0x99, 0x0a, 0x5c, 0x00, 0x00, 0x14, 0xc6, 0x23, 0xe5, + 0x1b, 0x93, 0xd8, 0x5b, 0x81, 0x3b, 0xb1, 0x6a, 0xc3, 0xe5, 0xad, 0xee, 0xda, 0x00, 0xe7, 0x79, 0x6e, 0xe2, 0x0f, + 0xb5, 0x1b, 0xdf, 0x76, 0xc4, 0xaa, 0x36, 0x28, 0x30, 0x51, 0x47, 0xd0, 0x3a, 0x75, 0x88, 0xb6, 0xaa, 0xac, 0x5a, + 0x1d, 0xe7, 0xb2, 0x9b, 0x1b, 0xd0, 0xe8, 0xcd, 0x6c, 0x9d, 0x41, 0x58, 0x9a, 0x79, 0x2a, 0xb3, 0xaa, 0xcb, 0x6e, + 0x33, 0x8d, 0x8b, 0xc7, 0xf2, 0x62, 0x5a, 0xb9, 0xcc, 0xe0, 0x56, 0x69, 0x7f, 0x65, 0xc6, 0xc1, 0xef, 0x04, 0xff, + 0x4d, 0x9f, 0xfa, 0x66, 0x89, 0x49, 0xac, 0x6e, 0x07, 0x22, 0xb8, 0xa9, 0x64, 0xaf, 0x73, 0xad, 0x24, 0xf2, 0x16, + 0x6a, 0xc3, 0x3b, 0xd7, 0xd1, 0x84, 0x58, 0x7e, 0xaa, 0x4e, 0xdb, 0xb6, 0xf6, 0xdd, 0xa3, 0x11, 0x63, 0x31, 0xde, + 0x0d, 0x40, 0x12, 0x9e, 0xb7, 0x95, 0x6c, 0xb1, 0x94, 0x5e, 0x96, 0x99, 0xfd, 0xb2, 0x79, 0x7a, 0x87, 0x25, 0xf7, + 0x4a, 0xd6, 0x0a, 0x31, 0x6c, 0xaf, 0x2a, 0x55, 0xe0, 0x7c, 0x84, 0x75, 0x11, 0x4f, 0x7b, 0xc3, 0x12, 0xbf, 0xbe, + 0x26, 0x96, 0x17, 0x2a, 0x53, 0x09, 0xdd, 0xaf, 0x49, 0xe4, 0x4b, 0x46, 0x6c, 0xde, 0xe8, 0x7e, 0x1b, 0x57, 0x70, + 0x61, 0x46, 0x29, 0xbd, 0x91, 0x3d, 0x4a, 0xf4, 0x0c, 0xaf, 0xca, 0x81, 0x1a, 0x0d, 0x67, 0x86, 0x8c, 0x56, 0x9c, + 0x71, 0x22, 0x09, 0x7a, 0x87, 0x2a, 0xa2, 0x28, 0x80, 0x7d, 0x24, 0x8a, 0x88, 0x3e, 0x97, 0xb4, 0xca, 0x4d, 0x38, + 0x0d, 0xac, 0x8b, 0xef, 0x3a, 0x25, 0x5e, 0x0a, 0x8f, 0x3f, 0x8a, 0x7e, 0xf3, 0xf3, 0x2c, 0xee, 0xb6, 0x40, 0x06, + 0x1e, 0x6e, 0x60, 0x22, 0xd8, 0x2f, 0x90, 0x8b, 0xd5, 0xc5, 0x0d, 0x48, 0x54, 0x01, 0xf5, 0x4b, 0x72, 0x47, 0x76, + 0x5b, 0xa5, 0xd9, 0x68, 0x61, 0x43, 0x61, 0xd2, 0xb6, 0x9a, 0x1a, 0xe7, 0xb8, 0xcb, 0x0a, 0xb4, 0xd9, 0xdc, 0x65, + 0x19, 0x02, 0xc3, 0x61, 0x34, 0xc2, 0x46, 0x1a, 0x4e, 0xc9, 0x4b, 0x1d, 0x6f, 0x5a, 0x1e, 0xd4, 0x22, 0x2c, 0xc8, + 0xb1, 0x42, 0x3b, 0x4b, 0x16, 0x6b, 0xac, 0xe2, 0x2c, 0x72, 0x2c, 0x3b, 0x5c, 0x49, 0x40, 0xd1, 0x1c, 0x22, 0x8a, + 0x62, 0x90, 0x38, 0x5a, 0x9a, 0x4a, 0x81, 0x31, 0xd5, 0x8f, 0x60, 0x17, 0x9b, 0x94, 0x1d, 0x47, 0x23, 0xc5, 0xc2, + 0x37, 0x14, 0xda, 0xdf, 0xa5, 0x39, 0xce, 0x46, 0x24, 0x23, 0x8b, 0xfc, 0xb9, 0x54, 0x4a, 0x58, 0xe5, 0x59, 0xbb, + 0x9b, 0x3b, 0x4d, 0x17, 0x49, 0xcd, 0x50, 0xb4, 0xd3, 0x92, 0xc5, 0x42, 0x03, 0x74, 0xfc, 0x25, 0xeb, 0xee, 0xb3, + 0x80, 0x5b, 0x1b, 0x66, 0x5d, 0x48, 0x17, 0x68, 0xce, 0xd5, 0x39, 0x85, 0xbf, 0x9b, 0x19, 0xf0, 0x1d, 0x5b, 0xec, + 0x74, 0x78, 0xb2, 0x39, 0xd0, 0x96, 0x0d, 0x77, 0xf8, 0xb5, 0xf0, 0xe8, 0x76, 0x48, 0x69, 0x9e, 0x26, 0xa6, 0x31, + 0xfd, 0x8a, 0xd7, 0x07, 0xb8, 0xa2, 0xbc, 0x22, 0xc0, 0xd6, 0x77, 0x3e, 0x97, 0xb4, 0x53, 0x59, 0x20, 0x2d, 0x99, + 0x38, 0x49, 0x93, 0xf5, 0xf5, 0x79, 0xef, 0x88, 0x4a, 0x8c, 0x5e, 0xc9, 0xa7, 0xb1, 0x69, 0xdc, 0x57, 0x9f, 0x47, + 0xc0, 0x5b, 0xdf, 0xcc, 0xf8, 0x1b, 0x93, 0x11, 0x86, 0xbd, 0x03, 0x5a, 0x8d, 0x75, 0xac, 0x37, 0x60, 0x7f, 0x17, + 0x47, 0x4b, 0x16, 0xa8, 0x29, 0x5a, 0xd5, 0x51, 0x48, 0xb9, 0xec, 0x3e, 0x77, 0x1a, 0x89, 0x45, 0x52, 0x2c, 0xa0, + 0xf3, 0x5d, 0x9a, 0xf7, 0x1b, 0x94, 0xfa, 0x64, 0x35, 0x85, 0x64, 0xd3, 0x59, 0x52, 0x17, 0xba, 0xd7, 0x74, 0x97, + 0x66, 0xee, 0xbc, 0x91, 0xb8, 0xfe, 0x0e, 0xed, 0xd2, 0x15, 0xec, 0x33, 0xae, 0xa0, 0xa6, 0x34, 0xba, 0x38, 0x36, + 0xbe, 0xf8, 0x6f, 0xd3, 0x92, 0x69, 0xf5, 0xd1, 0x26, 0x20, 0xf1, 0x12, 0x4a, 0x6c, 0xfe, 0x2f, 0xdc, 0xc1, 0x94, + 0xa8, 0x8f, 0x18, 0x11, 0xf7, 0x48, 0x5b, 0x86, 0x07, 0x68, 0x02, 0xb9, 0x16, 0x04, 0x28, 0xe9, 0x89, 0xa6, 0x6f, + 0xb5, 0x3a, 0x07, 0x83, 0x97, 0x66, 0x6c, 0x93, 0x20, 0x74, 0xa8, 0x17, 0xd2, 0x5e, 0xc9, 0x5b, 0x73, 0xd6, 0x70, + 0x8e, 0xa9, 0x05, 0x7f, 0x4a, 0xcd, 0x9c, 0x99, 0xce, 0xbb, 0x21, 0x39, 0x88, 0xcc, 0xd5, 0xd4, 0x0c, 0xa9, 0x63, + 0x15, 0xad, 0x9a, 0xe4, 0x7d, 0xf0, 0x5e, 0xe1, 0x44, 0x1e, 0xd4, 0xed, 0xd6, 0xed, 0xdd, 0x36, 0x92, 0x0c, 0x39, + 0x54, 0xb7, 0x89, 0x4a, 0x61, 0x94, 0x1c, 0x6b, 0x42, 0xdc, 0x81, 0x1b, 0x82, 0x52, 0xe8, 0x68, 0x92, 0xd2, 0x4a, + 0x9f, 0x65, 0x93, 0x8c, 0x4b, 0x6f, 0xa7, 0xbb, 0x65, 0xe0, 0x54, 0xd8, 0x56, 0xd5, 0xfd, 0x4d, 0x76, 0xed, 0x08, + 0x7e, 0x3b, 0x11, 0xea, 0xf8, 0x08, 0x11, 0x48, 0x97, 0xf0, 0x37, 0xdf, 0xbf, 0x67, 0xcf, 0xf5, 0xcb, 0x48, 0x26, + 0x64, 0x2a, 0x05, 0x70, 0x00, 0x99, 0xd6, 0x28, 0xbe, 0xb3, 0xaf, 0xaa, 0x2a, 0x38, 0x69, 0x03, 0x2f, 0xdc, 0x60, + 0x33, 0x26, 0x0f, 0x11, 0xad, 0x9b, 0x1c, 0x02, 0xb4, 0x55, 0xad, 0x47, 0xdf, 0x27, 0x23, 0x69, 0x39, 0x48, 0x06, + 0x1a, 0x6b, 0xdc, 0x8a, 0x09, 0x2f, 0x0d, 0x29, 0xba, 0x7e, 0x93, 0x17, 0xb1, 0x28, 0x89, 0xfe, 0xb3, 0xf0, 0x4a, + 0x66, 0x2a, 0xdc, 0xcf, 0xb1, 0x02, 0xf8, 0x10, 0xab, 0x1b, 0x2e, 0xae, 0xb1, 0xf0, 0xae, 0xae, 0x81, 0xe4, 0x9a, + 0x59, 0x1a, 0x04, 0xdc, 0x5e, 0xe1, 0x26, 0x40, 0x18, 0x7f, 0x26, 0x13, 0xb7, 0x1c, 0x60, 0xa8, 0x8f, 0xec, 0x9b, + 0xb9, 0x69, 0x8a, 0x47, 0x6a, 0xdd, 0x7b, 0x4f, 0xb2, 0xc0, 0x3d, 0x6f, 0x9e, 0x0b, 0x67, 0x76, 0x9d, 0x4e, 0xbb, + 0x67, 0xf3, 0xc8, 0xaa, 0x77, 0x55, 0xe2, 0xab, 0x5e, 0x26, 0xd7, 0x2b, 0xd7, 0x35, 0x68, 0x05, 0x13, 0x1f, 0x78, + 0x57, 0x15, 0x96, 0xe5, 0xf8, 0xda, 0xcd, 0x47, 0x18, 0x57, 0x0b, 0xfd, 0x66, 0xbd, 0x7a, 0x98, 0x35, 0x1e, 0x50, + 0x49, 0x0b, 0x66, 0xc3, 0x41, 0x6a, 0x40, 0x95, 0x09, 0x0a, 0x19, 0x39, 0x0f, 0x47, 0x7c, 0x8a, 0x3b, 0x66, 0x8b, + 0x93, 0x1e, 0x0a, 0x1e, 0x71, 0x8b, 0x50, 0x21, 0xf8, 0x1f, 0x06, 0x73, 0x10, 0x21, 0x29, 0x30, 0xdb, 0x12, 0xea, + 0xb0, 0xb8, 0x4c, 0x78, 0x7a, 0x54, 0xc4, 0xa4, 0x88, 0x41, 0x6e, 0x29, 0x55, 0x44, 0xed, 0x63, 0x68, 0x9b, 0x14, + 0x4d, 0x37, 0x0a, 0x91, 0x63, 0x49, 0x05, 0x37, 0xea, 0x39, 0x84, 0x1f, 0x51, 0xab, 0xdb, 0x53, 0xd2, 0x58, 0x5e, + 0x62, 0x19, 0xa5, 0xf1, 0xcd, 0x6b, 0x35, 0x3e, 0xf5, 0xe6, 0x88, 0xdc, 0x05, 0x00, 0x56, 0x7d, 0x4c, 0xf8, 0xa1, + 0x1e, 0xab, 0x8e, 0x30, 0x84, 0xf0, 0x36, 0x5d, 0xf1, 0x11, 0xb1, 0xda, 0xdc, 0x8f, 0x2f, 0x9f, 0x3d, 0xce, 0x2f, + 0x90, 0x48, 0x9d, 0x9a, 0x62, 0x88, 0x49, 0x5e, 0xf8, 0x35, 0xfa, 0x70, 0xd9, 0xbf, 0x4d, 0x5d, 0xf8, 0x70, 0xee, + 0xc3, 0x02, 0x16, 0xe5, 0x6f, 0xb4, 0xf6, 0x42, 0x50, 0x50, 0x4b, 0xcd, 0x44, 0x6d, 0x08, 0xc6, 0xae, 0x58, 0x51, + 0xc7, 0x9b, 0x69, 0x02, 0x50, 0x66, 0x34, 0x6b, 0x8a, 0xc1, 0x18, 0xd2, 0x9e, 0x6e, 0x3d, 0xee, 0x90, 0xca, 0x16, + 0x16, 0xd7, 0x75, 0x8f, 0xa6, 0x2e, 0x2c, 0x80, 0x12, 0x7e, 0x48, 0x91, 0xd6, 0x6e, 0xc3, 0x7e, 0x43, 0x62, 0x0a, + 0x7e, 0x57, 0x9b, 0x15, 0x89, 0x67, 0x2e, 0xdd, 0xad, 0x92, 0x69, 0x88, 0x73, 0xe1, 0x07, 0x22, 0xac, 0xdc, 0x46, + 0xcc, 0x29, 0x0f, 0x3d, 0x27, 0xfb, 0xd2, 0x2d, 0x63, 0x5b, 0x65, 0x01, 0x47, 0x9d, 0x76, 0x25, 0xbc, 0xe0, 0xb9, + 0xc5, 0xf5, 0x9d, 0x3f, 0x3c, 0x8e, 0x16, 0x82, 0x55, 0x3d, 0xb3, 0x18, 0x47, 0xa8, 0x28, 0x5c, 0x42, 0x10, 0xb7, + 0x41, 0x12, 0x61, 0x98, 0x57, 0xe3, 0xcd, 0x47, 0x86, 0xf5, 0x14, 0x06, 0x80, 0x56, 0xe2, 0xd0, 0x3d, 0x43, 0x19, + 0xc1, 0xbd, 0x34, 0x03, 0x94, 0x7b, 0xae, 0x32, 0x2f, 0xa7, 0xf6, 0x18, 0x06, 0x4e, 0x65, 0x6b, 0x83, 0x19, 0xca, + 0x88, 0xac, 0x03, 0x01, 0x62, 0x5f, 0x68, 0xad, 0xa4, 0x6c, 0xba, 0xc1, 0x01, 0x48, 0xd0, 0x54, 0x8a, 0x21, 0x42, + 0xec, 0xad, 0x4a, 0xa7, 0xa0, 0xc7, 0xc3, 0x43, 0x75, 0x9b, 0xa4, 0x42, 0xe0, 0x55, 0xb4, 0xc8, 0x40, 0x22, 0x7b, + 0xa0, 0x1d, 0xd4, 0x0d, 0x80, 0x24, 0x3b, 0xc7, 0x95, 0x82, 0xb4, 0x05, 0x80, 0x1e, 0x1b, 0xff, 0x33, 0x33, 0xc4, + 0x3c, 0x00, 0x79, 0xec, 0x5a, 0x38, 0x69, 0xbc, 0x11, 0x06, 0x0e, 0x17, 0x52, 0x06, 0xd3, 0xdb, 0x59, 0x05, 0x9d, + 0xc8, 0x44, 0xd8, 0xdc, 0x0a, 0xb6, 0xf1, 0xc1, 0x69, 0xea, 0x48, 0x54, 0x84, 0xbd, 0x15, 0xff, 0x58, 0x59, 0xb7, + 0x00, 0x65, 0xc5, 0x3d, 0x6e, 0xfb, 0x31, 0xfc, 0x6f, 0x02, 0x4a, 0xe2, 0x9c, 0xd9, 0x4b, 0x25, 0xd3, 0x29, 0x79, + 0x71, 0xae, 0xf5, 0xd1, 0xa0, 0x3d, 0xb0, 0x7b, 0x3c, 0x05, 0x9b, 0x3b, 0x51, 0xb9, 0x5b, 0xd3, 0xc8, 0x13, 0xb7, + 0xa8, 0xaa, 0x69, 0x0b, 0x89, 0x26, 0xb8, 0xc8, 0xac, 0xa9, 0x52, 0xee, 0x16, 0x87, 0x01, 0xa4, 0xd0, 0x18, 0x06, + 0x36, 0xb9, 0xef, 0x38, 0x8a, 0x79, 0x11, 0x9e, 0x48, 0xb2, 0xeb, 0x82, 0x52, 0x7e, 0x9a, 0x9a, 0x65, 0x4a, 0xd8, + 0x94, 0x58, 0x86, 0xc3, 0xda, 0x41, 0x98, 0xed, 0x1d, 0x11, 0x95, 0x0a, 0xa3, 0x1d, 0x93, 0xca, 0x26, 0x19, 0xf9, + 0x1d, 0x5a, 0xc4, 0x49, 0xb0, 0x3a, 0xdb, 0x36, 0x55, 0x31, 0x37, 0x07, 0xf5, 0x08, 0xf7, 0x96, 0x19, 0x6c, 0x62, + 0x59, 0x24, 0x6b, 0xb7, 0x56, 0xcd, 0x62, 0xa5, 0x6e, 0xfd, 0x3e, 0xb1, 0xf1, 0x26, 0x52, 0x67, 0x28, 0x84, 0x8d, + 0x9b, 0x11, 0x05, 0xbd, 0xf1, 0x70, 0x16, 0xed, 0xb4, 0x79, 0x3f, 0xc9, 0xaa, 0x2e, 0x90, 0x97, 0x8a, 0xa8, 0x6a, + 0x66, 0x83, 0xfd, 0x94, 0xf2, 0x34, 0xf0, 0x28, 0x73, 0x27, 0x25, 0xa1, 0x32, 0x97, 0x44, 0x45, 0x81, 0x49, 0x3c, + 0xc7, 0x7c, 0x10, 0x28, 0xf6, 0xc6, 0x38, 0xd4, 0x69, 0xdc, 0x36, 0x99, 0xdf, 0xf5, 0xa8, 0xe6, 0xa6, 0xb2, 0x80, + 0x34, 0x3f, 0x93, 0x49, 0xb6, 0xf2, 0xbd, 0x7d, 0xa8, 0x0f, 0x8f, 0x33, 0x4c, 0xb8, 0x8f, 0xe8, 0x1a, 0x46, 0x21, + 0x4e, 0xff, 0x76, 0xdb, 0x49, 0x2f, 0x2e, 0x6b, 0x3a, 0x41, 0x66, 0x68, 0x5c, 0x87, 0x9e, 0x0e, 0x1b, 0x91, 0xba, + 0xb1, 0x26, 0x02, 0xb9, 0x90, 0xee, 0xb7, 0x5b, 0xc0, 0x52, 0xb3, 0x63, 0x97, 0xe6, 0x75, 0x52, 0x9e, 0x55, 0x9f, + 0xfa, 0x8a, 0xe8, 0x75, 0x3d, 0xda, 0x36, 0x60, 0x8d, 0x59, 0x77, 0xe0, 0x9f, 0x83, 0x49, 0xe4, 0xcb, 0x79, 0x53, + 0xec, 0x53, 0xcd, 0x73, 0xcd, 0xbd, 0x5f, 0xce, 0xf1, 0x40, 0xd8, 0x9f, 0x32, 0x10, 0x1c, 0x44, 0x24, 0x24, 0x88, + 0x05, 0xe6, 0xc0, 0x5c, 0x2a, 0xa6, 0x26, 0x6a, 0x1b, 0xcc, 0x25, 0xb8, 0xb3, 0x1f, 0x0c, 0x72, 0x83, 0x63, 0xcb, + 0xf0, 0x2b, 0x7c, 0xc1, 0x52, 0x56, 0x23, 0x6d, 0x45, 0xb5, 0x3c, 0x96, 0xe8, 0x09, 0xd4, 0x52, 0x59, 0x2a, 0xdb, + 0x80, 0x2a, 0xc6, 0xd7, 0xf9, 0x7c, 0x86, 0x8a, 0xa2, 0x14, 0xcf, 0x53, 0xc8, 0x40, 0xbb, 0xfc, 0xc4, 0xb3, 0x2f, + 0x7d, 0x9f, 0x09, 0x5c, 0xcb, 0x92, 0x47, 0xe4, 0x99, 0xe6, 0xc3, 0x72, 0xbd, 0x5a, 0xca, 0xd5, 0x0c, 0x11, 0xe0, + 0x64, 0xb1, 0xd2, 0xa5, 0x31, 0x05, 0x82, 0x6b, 0xc2, 0x6e, 0x8b, 0x85, 0x9b, 0xf2, 0x0f, 0xe7, 0x65, 0x21, 0x5d, + 0x13, 0xe5, 0x48, 0x22, 0x3f, 0xe3, 0x0a, 0xd6, 0x00, 0xa9, 0x35, 0xe1, 0x44, 0x0e, 0x66, 0x13, 0x00, 0x9d, 0xba, + 0x46, 0xaf, 0xd6, 0xa8, 0xae, 0x5b, 0x00, 0x5b, 0xfa, 0x0c, 0x46, 0x86, 0x42, 0xd8, 0x88, 0x7e, 0x5d, 0x64, 0xd4, + 0xc7, 0x95, 0x82, 0x2e, 0xba, 0xc4, 0x12, 0xa0, 0x39, 0xb7, 0x49, 0xde, 0x28, 0x8d, 0xe2, 0x53, 0xc6, 0x99, 0xe5, + 0xa4, 0x2e, 0x4e, 0xaa, 0x51, 0xde, 0x92, 0xcf, 0x40, 0xaa, 0x1b, 0x20, 0xbb, 0x94, 0x2b, 0x23, 0xf4, 0x8c, 0xa5, + 0x8b, 0xba, 0xc1, 0x6b, 0x29, 0x35, 0xf9, 0x7e, 0xcb, 0xbf, 0x42, 0x5c, 0x38, 0xe9, 0x02, 0x62, 0x02, 0x82, 0x94, + 0x56, 0x8e, 0x85, 0xf7, 0x1b, 0x37, 0xba, 0x28, 0xe4, 0x55, 0x32, 0xd0, 0x0a, 0x23, 0x63, 0xa4, 0x57, 0xa1, 0x55, + 0xb7, 0xe6, 0x57, 0x52, 0x9e, 0xa1, 0x0e, 0xb6, 0x31, 0x24, 0x64, 0x21, 0xc0, 0x67, 0x8d, 0x02, 0x52, 0x9f, 0x6a, + 0x69, 0x97, 0x94, 0xc4, 0x4a, 0xb1, 0xf6, 0x2d, 0x3e, 0x1a, 0xc3, 0xa7, 0x7e, 0xdd, 0xa9, 0xc9, 0xc2, 0x8d, 0xc5, + 0x1f, 0xfc, 0x82, 0x46, 0xb5, 0x11, 0x09, 0x0f, 0x08, 0x30, 0x55, 0x0d, 0x73, 0xab, 0xfb, 0x6c, 0xd6, 0xe8, 0xa9, + 0x1a, 0x00, 0xa0, 0x02, 0x31, 0xa9, 0xd7, 0x96, 0x69, 0xa2, 0xf5, 0xe0, 0xe7, 0xe9, 0xd5, 0xb5, 0x21, 0x8e, 0x75, + 0x3a, 0xa7, 0x60, 0x00, 0x67, 0x03, 0x54, 0x6d, 0xe3, 0xe5, 0xcd, 0xf6, 0xfc, 0x81, 0x77, 0x41, 0x6a, 0x02, 0x3e, + 0x47, 0xc9, 0xe0, 0xfb, 0x48, 0x03, 0x41, 0xf3, 0x03, 0xf2, 0x3c, 0xf6, 0x8d, 0x48, 0xe4, 0x81, 0xf3, 0x2b, 0x3e, + 0xde, 0x0e, 0xf7, 0x56, 0xc3, 0x2f, 0x63, 0x6b, 0x52, 0x07, 0x2c, 0x1f, 0x24, 0xb0, 0x5c, 0xa8, 0x7d, 0x64, 0x7c, + 0xe7, 0x13, 0x21, 0x4e, 0x51, 0xa1, 0x3e, 0x02, 0x62, 0xcc, 0x04, 0x8a, 0x45, 0x5a, 0xa2, 0xce, 0xaa, 0x7c, 0x87, + 0xb0, 0x80, 0xd0, 0x3a, 0x25, 0x86, 0xf1, 0x76, 0x24, 0xc0, 0xc0, 0x9d, 0x0c, 0x39, 0x71, 0xa3, 0xb9, 0x19, 0x75, + 0xcf, 0x99, 0xb0, 0x6d, 0xb0, 0x6a, 0xca, 0x7e, 0x77, 0x83, 0x0d, 0xf8, 0x14, 0x34, 0xe3, 0xf8, 0x20, 0xb6, 0xdb, + 0x81, 0xa8, 0x3a, 0xfb, 0xa6, 0x20, 0xdf, 0x64, 0x91, 0x14, 0x89, 0x02, 0x1d, 0x92, 0x0f, 0x92, 0x6e, 0x01, 0xf9, + 0x6c, 0x21, 0x8d, 0xb9, 0x7a, 0x94, 0x01, 0xe2, 0xf3, 0xf4, 0x61, 0x3d, 0xdc, 0x32, 0x30, 0x0e, 0x22, 0x3a, 0x44, + 0x7c, 0xd5, 0x96, 0x34, 0x8a, 0x21, 0x0f, 0xbb, 0xd6, 0x97, 0xd4, 0xb0, 0x0d, 0xb5, 0xf0, 0x1f, 0xc2, 0xb3, 0x18, + 0xa9, 0xb9, 0x8d, 0x3f, 0x72, 0x41, 0xa4, 0x77, 0x9c, 0x82, 0xd0, 0x72, 0x93, 0x07, 0x5a, 0xd5, 0x74, 0x9d, 0x56, + 0xae, 0x3b, 0x83, 0x17, 0x08, 0xdb, 0xa2, 0x3a, 0x08, 0xaa, 0x2b, 0x83, 0x7e, 0x74, 0x26, 0xdc, 0x63, 0x4c, 0x20, + 0xef, 0x89, 0x6a, 0x9c, 0xa7, 0x51, 0xaa, 0x98, 0x87, 0xdd, 0xd1, 0xb8, 0x5c, 0xfa, 0x13, 0x69, 0x23, 0x4e, 0xf4, + 0x61, 0x04, 0x32, 0xb5, 0x74, 0x79, 0x04, 0xf0, 0xb7, 0x79, 0xa5, 0x69, 0x83, 0x4b, 0x80, 0xf7, 0x2b, 0x5e, 0x22, + 0x50, 0xba, 0x25, 0xc7, 0xb5, 0xe3, 0xec, 0x36, 0x0a, 0x95, 0xfb, 0x9a, 0x76, 0xf8, 0x15, 0x22, 0x4a, 0x87, 0x71, + 0x48, 0x73, 0x60, 0x1e, 0x96, 0xcb, 0x25, 0xb0, 0x54, 0xed, 0x11, 0x8c, 0x25, 0x8f, 0x92, 0x5c, 0x5a, 0x64, 0x48, + 0xe3, 0xf8, 0x58, 0x45, 0x24, 0xfa, 0x19, 0xc7, 0x1e, 0x6b, 0x00, 0x73, 0x77, 0x6b, 0xbe, 0xa7, 0x65, 0x0b, 0x35, + 0xde, 0xdb, 0x25, 0x8a, 0x59, 0x34, 0x25, 0xce, 0x71, 0xd4, 0x40, 0xda, 0xe7, 0x34, 0x66, 0xe3, 0x37, 0xfd, 0x48, + 0x03, 0xc6, 0x6e, 0x3b, 0x10, 0x81, 0x48, 0x0c, 0xb3, 0x68, 0x85, 0x17, 0x64, 0xee, 0x5f, 0x26, 0x06, 0x1c, 0x19, + 0xc0, 0x19, 0xc6, 0x97, 0x81, 0xe2, 0x6e, 0x6d, 0x07, 0xc7, 0xcd, 0x62, 0x79, 0xfa, 0xf4, 0xfd, 0x32, 0x4f, 0x59, + 0x60, 0x0c, 0x3e, 0xd4, 0x65, 0xac, 0xa5, 0x1e, 0x6b, 0x52, 0x75, 0xb7, 0x67, 0x26, 0xde, 0xca, 0xd4, 0x2a, 0xe9, + 0xea, 0xb3, 0x47, 0x35, 0xa0, 0x76, 0x2c, 0x8f, 0xb4, 0x0d, 0x18, 0x14, 0x1e, 0xf7, 0x5e, 0x14, 0x92, 0xcf, 0xa3, + 0x13, 0x3e, 0x25, 0x03, 0x77, 0x1e, 0x15, 0x2e, 0xe3, 0xa8, 0x82, 0x17, 0x55, 0x50, 0x82, 0xf4, 0xb8, 0x4e, 0x21, + 0x45, 0x5a, 0x63, 0xa2, 0xa7, 0x45, 0x9f, 0x46, 0xa0, 0x20, 0x54, 0xc3, 0x40, 0x91, 0x43, 0x8e, 0x4c, 0x85, 0xd2, + 0x23, 0x1f, 0x2c, 0xb4, 0xf0, 0x79, 0x10, 0xf2, 0x1a, 0x77, 0xbd, 0x2c, 0x45, 0x10, 0xe1, 0x46, 0x5b, 0x6f, 0xf4, + 0xa3, 0xda, 0xed, 0xfa, 0x88, 0xf7, 0xb4, 0x83, 0x08, 0x2b, 0x53, 0x39, 0x3e, 0x72, 0xb6, 0xdb, 0x5f, 0x86, 0x10, + 0xa0, 0xe6, 0x96, 0x65, 0xe1, 0x67, 0xc5, 0x7b, 0x7a, 0x02, 0x5c, 0xbe, 0xe3, 0x4c, 0xf7, 0x01, 0x3a, 0x72, 0x24, + 0xa2, 0xdc, 0xa6, 0xdf, 0x16, 0xfd, 0x33, 0x8a, 0xc6, 0x50, 0x1c, 0x6c, 0xff, 0xf1, 0xe3, 0xf0, 0xb4, 0xa7, 0xdc, + 0xc2, 0x28, 0xe9, 0x28, 0xbd, 0x72, 0xae, 0xda, 0x6a, 0x25, 0x8c, 0x19, 0xf4, 0x6b, 0x97, 0xb6, 0x4d, 0x47, 0xb3, + 0x61, 0xcc, 0xa2, 0xe3, 0x09, 0x6d, 0xc6, 0x9e, 0x37, 0x33, 0xe6, 0xa1, 0xc1, 0x9d, 0xc2, 0xfb, 0xe3, 0x90, 0x22, + 0x5a, 0xb7, 0x92, 0xa7, 0xfb, 0x7d, 0xca, 0xfe, 0xf4, 0x96, 0xee, 0xe2, 0x46, 0xf8, 0xf2, 0xbd, 0xf5, 0x63, 0xe1, + 0x41, 0xfb, 0xac, 0xa4, 0xcf, 0xd2, 0xfb, 0x2a, 0xb9, 0x16, 0xc8, 0x11, 0xa2, 0x73, 0x11, 0xae, 0x3b, 0xd2, 0x1a, + 0xa1, 0x03, 0x73, 0xd8, 0x8a, 0x6f, 0xcf, 0x30, 0x6a, 0x2e, 0xab, 0x9c, 0x77, 0x8b, 0x96, 0x91, 0xfc, 0xcd, 0x9b, + 0x0e, 0x5f, 0x6f, 0x1c, 0x61, 0xef, 0x51, 0x2c, 0xde, 0x7b, 0x65, 0x45, 0x50, 0x22, 0xfc, 0x46, 0x01, 0xc9, 0x1c, + 0x4e, 0xc8, 0xfe, 0xac, 0xf8, 0x9c, 0x73, 0x44, 0x20, 0x91, 0x87, 0xa5, 0x29, 0xc9, 0xd0, 0x81, 0x0d, 0xa9, 0xb3, + 0x7c, 0xe6, 0x94, 0x5f, 0x39, 0xd6, 0xef, 0xc1, 0xf6, 0x83, 0x69, 0x5b, 0x0c, 0x81, 0xcf, 0xe8, 0x0d, 0xca, 0x24, + 0x62, 0x96, 0xa7, 0x21, 0x6e, 0xdb, 0x07, 0x32, 0x48, 0x4b, 0xb9, 0xed, 0xb4, 0x68, 0xb9, 0x80, 0x54, 0xd9, 0x68, + 0xc6, 0x91, 0xc4, 0x19, 0x0b, 0xf1, 0x83, 0xb8, 0xec, 0xef, 0xc7, 0x88, 0x88, 0xe6, 0xad, 0x7f, 0x01, 0x97, 0x81, + 0x0b, 0xbf, 0xc8, 0x28, 0x4c, 0x45, 0xce, 0x21, 0xd6, 0x64, 0x09, 0xfe, 0x64, 0x58, 0x69, 0x45, 0x21, 0x0e, 0x2a, + 0xec, 0x8d, 0xff, 0xe1, 0xad, 0xbb, 0x55, 0x0e, 0x11, 0xcd, 0xde, 0x97, 0xec, 0x0c, 0x61, 0xa5, 0x7b, 0x4b, 0x01, + 0x81, 0x12, 0xea, 0xd1, 0x22, 0x4f, 0xca, 0x6a, 0x8f, 0xf6, 0xa5, 0xe4, 0x3d, 0xcf, 0x91, 0x20, 0x92, 0xb9, 0x83, + 0x75, 0x1d, 0xb0, 0x6f, 0x27, 0x5b, 0x35, 0xd0, 0xef, 0xf3, 0xd6, 0x21, 0x1c, 0x80, 0xfd, 0xa6, 0x67, 0x9a, 0xf7, + 0x44, 0xfa, 0x25, 0x57, 0x8c, 0xae, 0xad, 0x92, 0xb3, 0x3e, 0x1b, 0x43, 0x96, 0x21, 0xb9, 0x8a, 0xa1, 0x9e, 0xd4, + 0x31, 0xc2, 0x46, 0x41, 0xcf, 0x39, 0x31, 0x8f, 0x68, 0x32, 0xa0, 0x1e, 0xa7, 0xa7, 0xb4, 0x09, 0x20, 0xd3, 0xa2, + 0x43, 0x0f, 0x2a, 0x60, 0x59, 0x8d, 0xb4, 0x42, 0x65, 0x1a, 0x3a, 0x2a, 0xf7, 0xb4, 0x26, 0xcd, 0x9e, 0xc2, 0xaa, + 0x2b, 0x2d, 0x5b, 0x4e, 0xe7, 0xa8, 0x3c, 0x48, 0xb3, 0x29, 0x7c, 0x5c, 0x0e, 0x22, 0xb3, 0xa6, 0xe9, 0x6e, 0xfb, + 0x1b, 0x44, 0x94, 0x3c, 0x45, 0x2a, 0xa8, 0x90, 0x91, 0x87, 0x94, 0x2c, 0x91, 0x07, 0x4b, 0xa0, 0xf3, 0x83, 0x01, + 0xfd, 0x4e, 0x4c, 0x0c, 0x45, 0x6e, 0x57, 0x7c, 0x33, 0x11, 0xdc, 0xa9, 0x45, 0xe7, 0x6c, 0x97, 0x89, 0x2c, 0x85, + 0xb3, 0xab, 0x24, 0x7d, 0x4e, 0x34, 0x8a, 0x6e, 0xa4, 0xfb, 0x63, 0x84, 0x9f, 0xec, 0x4d, 0x11, 0xb4, 0x61, 0xbd, + 0x4e, 0x0f, 0xcb, 0x2d, 0x91, 0xff, 0x46, 0x79, 0xad, 0xb8, 0x70, 0x5e, 0xf2, 0x71, 0x43, 0x89, 0xad, 0xd8, 0x6c, + 0x9c, 0x41, 0x4a, 0xc0, 0x50, 0x3a, 0x41, 0xdb, 0x31, 0x8e, 0xea, 0x64, 0x0c, 0xed, 0x31, 0x7b, 0x23, 0x0a, 0xca, + 0xba, 0x9c, 0x79, 0x8e, 0x2d, 0xcb, 0x79, 0x6e, 0x3c, 0xa4, 0x94, 0x99, 0x9c, 0x71, 0xc8, 0xca, 0xcc, 0x8c, 0x34, + 0x06, 0x14, 0xde, 0x1e, 0x35, 0xbb, 0x13, 0xdb, 0xc9, 0x6a, 0x49, 0x36, 0x92, 0x55, 0x15, 0x71, 0x31, 0x09, 0xb3, + 0xc1, 0x15, 0x65, 0x12, 0x57, 0x17, 0x3b, 0xde, 0x2f, 0xfe, 0x74, 0xa8, 0x80, 0x8f, 0x6d, 0xaf, 0x4f, 0x43, 0x43, + 0xae, 0xa4, 0x61, 0xe2, 0x83, 0xe4, 0x22, 0xdd, 0xa9, 0xe4, 0xfd, 0x22, 0xbc, 0xbe, 0x6a, 0xd4, 0x19, 0x8e, 0xdd, + 0x36, 0x64, 0xfb, 0xc5, 0x30, 0x1e, 0x75, 0xa5, 0x0a, 0xef, 0x8f, 0xcd, 0xed, 0x16, 0xce, 0xbb, 0x19, 0xce, 0x1d, + 0x3a, 0x71, 0x06, 0xf9, 0x9f, 0x5f, 0x2d, 0xb6, 0x60, 0xa9, 0xe9, 0x37, 0x99, 0xfa, 0xc9, 0x3e, 0x70, 0x5b, 0xb6, + 0x1f, 0xe9, 0xb0, 0x31, 0xb2, 0x4f, 0xc3, 0x32, 0x62, 0xa7, 0x8f, 0x07, 0x1a, 0x82, 0xcd, 0xd5, 0xe5, 0x98, 0x0d, + 0xf6, 0xc7, 0xaf, 0xcd, 0xf9, 0x35, 0x2b, 0x17, 0xa9, 0x5f, 0xb2, 0x53, 0xfd, 0xca, 0x76, 0x91, 0xcb, 0x08, 0x70, + 0x46, 0x6f, 0xa5, 0xff, 0x43, 0x9e, 0x26, 0x89, 0x4a, 0xff, 0xe4, 0x0f, 0x92, 0xee, 0x7e, 0x9f, 0x3f, 0xb6, 0xb3, + 0x13, 0xf2, 0xc9, 0xc3, 0x6f, 0xa7, 0x30, 0x97, 0xcb, 0x65, 0x94, 0xb2, 0xda, 0x61, 0x17, 0x6c, 0xa5, 0x7d, 0x65, + 0xbd, 0xf6, 0x23, 0xb8, 0xa2, 0x64, 0x15, 0xc6, 0x25, 0xa1, 0x82, 0x4d, 0x3e, 0xa5, 0xad, 0xd3, 0x7e, 0xe1, 0xec, + 0x15, 0x03, 0x14, 0x11, 0x1f, 0x2b, 0x63, 0xbc, 0x87, 0xc4, 0xe1, 0x54, 0xa8, 0x61, 0x5a, 0xa9, 0xd2, 0x00, 0xe0, + 0xd0, 0xe8, 0xd7, 0x29, 0x8f, 0x39, 0x85, 0x7e, 0x78, 0xb9, 0x67, 0x55, 0x2c, 0xf9, 0xff, 0x7b, 0x1e, 0x06, 0x84, + 0xc8, 0x0a, 0x58, 0xba, 0x0f, 0x6b, 0x8a, 0x49, 0x24, 0x2a, 0x69, 0x12, 0x56, 0x23, 0x7d, 0x7b, 0x89, 0x57, 0x4d, + 0x67, 0x7c, 0x7f, 0xc7, 0x1c, 0x3a, 0xb6, 0xcc, 0x94, 0x32, 0x2a, 0x4d, 0xde, 0x2c, 0x45, 0xaa, 0x27, 0x91, 0xc7, + 0x54, 0x85, 0xfc, 0x72, 0x35, 0xfd, 0xd3, 0xee, 0x8b, 0xc0, 0xaf, 0x5e, 0x89, 0x21, 0xd6, 0x43, 0xf2, 0x09, 0x0d, + 0x76, 0xe7, 0x75, 0x12, 0x7a, 0x5e, 0xf9, 0x11, 0xef, 0xfb, 0xa1, 0x94, 0xed, 0x5a, 0xf5, 0xcc, 0xf3, 0xc4, 0x42, + 0x09, 0xb6, 0x91, 0x67, 0x0e, 0x3d, 0x69, 0x9c, 0x8e, 0x8e, 0xbd, 0x16, 0xd1, 0xa1, 0x0b, 0x1c, 0x7d, 0xc4, 0xec, + 0x82, 0x63, 0x7b, 0x0b, 0xfa, 0x18, 0x2c, 0xda, 0x89, 0x5e, 0x75, 0xb2, 0xe8, 0x2a, 0xb4, 0xbf, 0x14, 0x1d, 0x90, + 0x64, 0xd3, 0xb3, 0x60, 0x52, 0xef, 0x84, 0x9c, 0xcb, 0x7c, 0x3d, 0xb0, 0xf4, 0x7e, 0xc7, 0x40, 0x5d, 0x6e, 0xf8, + 0x6b, 0xcb, 0xac, 0xef, 0x1e, 0x99, 0xbe, 0x85, 0x5c, 0x44, 0x66, 0xd1, 0x05, 0x7a, 0x1d, 0xc6, 0xd7, 0xcc, 0xd3, + 0xdf, 0x86, 0x06, 0x93, 0xc9, 0xa0, 0x53, 0x89, 0x0a, 0xb0, 0x99, 0x62, 0xa3, 0xfa, 0x64, 0x47, 0x79, 0x58, 0x6b, + 0xee, 0x09, 0x7b, 0x97, 0xfd, 0xb2, 0xd8, 0x78, 0x03, 0x93, 0x20, 0x98, 0xa9, 0xe0, 0x2e, 0x48, 0x59, 0xc6, 0x74, + 0x85, 0x6b, 0x01, 0x6f, 0xcd, 0x1a, 0x37, 0x58, 0xcb, 0x57, 0xc9, 0x23, 0xa4, 0xfa, 0x93, 0x3d, 0x54, 0x25, 0x8e, + 0xfc, 0x3e, 0xf5, 0xd6, 0x5e, 0xfe, 0xe1, 0x44, 0x30, 0x14, 0xa5, 0x4b, 0x4f, 0x1e, 0xb9, 0x24, 0x6f, 0x41, 0x6b, + 0x56, 0xf4, 0xa0, 0x63, 0xe0, 0xa2, 0xc2, 0x66, 0x23, 0xa1, 0xe2, 0xbf, 0x86, 0xb6, 0x60, 0x14, 0xde, 0xe9, 0x34, + 0x46, 0x1e, 0x7d, 0x55, 0x2b, 0xed, 0x6e, 0x14, 0xb7, 0x22, 0x27, 0xcf, 0x79, 0xcd, 0xc1, 0x64, 0x4e, 0x98, 0xe4, + 0xe3, 0x7b, 0x43, 0xaf, 0x70, 0x4c, 0x4e, 0xb6, 0x73, 0x5e, 0x0f, 0x63, 0x07, 0x10, 0x31, 0xf9, 0x5b, 0x9f, 0xcc, + 0x6b, 0xaf, 0xc8, 0x67, 0x3e, 0x12, 0xb6, 0x97, 0x6c, 0xcc, 0x0b, 0xbe, 0xcd, 0x63, 0x74, 0xd5, 0xc1, 0x9c, 0x9a, + 0x2a, 0x35, 0x1b, 0x02, 0x7e, 0x95, 0x0a, 0x3f, 0x90, 0x09, 0x1a, 0xc7, 0x0e, 0xdd, 0xa5, 0x90, 0x11, 0xd0, 0xfe, + 0x32, 0x1e, 0x34, 0xf3, 0xf3, 0xf7, 0xcb, 0xd4, 0x0c, 0x9b, 0x3d, 0xf5, 0x4b, 0xe0, 0xe5, 0x51, 0xa5, 0xb7, 0xe3, + 0x4f, 0xa5, 0x50, 0x5e, 0x10, 0x47, 0x27, 0x3a, 0x0a, 0xf6, 0xb3, 0x3d, 0xe0, 0xdf, 0x23, 0x11, 0x4b, 0xee, 0x39, + 0x1f, 0x00, 0x72, 0xcd, 0x22, 0xb6, 0x51, 0x1e, 0xff, 0x1a, 0x60, 0x66, 0xc6, 0x6c, 0xa7, 0x59, 0x56, 0x1e, 0x58, + 0x68, 0x7b, 0x8c, 0xc8, 0x7c, 0x3b, 0x6c, 0xc2, 0x29, 0x3a, 0x7c, 0xbd, 0x6d, 0x5a, 0xd9, 0x82, 0x1f, 0x50, 0x05, + 0x7f, 0x9f, 0x05, 0x67, 0x54, 0xc9, 0x13, 0xdc, 0x37, 0x3b, 0xa2, 0x0a, 0x5e, 0x91, 0x79, 0x37, 0x98, 0x10, 0x43, + 0xf1, 0xe5, 0x1b, 0xf2, 0x28, 0xc9, 0x55, 0xb0, 0x5e, 0x99, 0xca, 0x47, 0x8b, 0x7b, 0x9a, 0x58, 0x81, 0x71, 0x58, + 0x30, 0xd1, 0xc1, 0x8c, 0x29, 0x83, 0xb5, 0xec, 0xb9, 0xc1, 0x24, 0x20, 0x24, 0x80, 0x79, 0x0e, 0x52, 0xfa, 0x6b, + 0xd8, 0x3d, 0x26, 0x80, 0x13, 0x1a, 0x14, 0x84, 0xc2, 0x3c, 0x2b, 0x2a, 0x1a, 0x3a, 0xa6, 0xca, 0x12, 0x1b, 0x38, + 0xbd, 0xb2, 0x37, 0xf8, 0xc8, 0x04, 0x4f, 0x1e, 0x6a, 0xae, 0xdf, 0x4d, 0xdf, 0xbe, 0xe7, 0x5e, 0x28, 0x9a, 0x8a, + 0xb6, 0x5a, 0xac, 0xbf, 0x13, 0xf4, 0xb9, 0x90, 0x64, 0x17, 0xdb, 0x32, 0xc3, 0x8a, 0x19, 0x67, 0xcd, 0x45, 0xeb, + 0x45, 0x3d, 0x7f, 0x5a, 0x12, 0x82, 0xb8, 0xb7, 0x38, 0xd1, 0xe5, 0x94, 0x79, 0xe7, 0x3d, 0xd9, 0xe9, 0x46, 0x3c, + 0xc9, 0x5d, 0x55, 0x7e, 0x6c, 0xa7, 0xf6, 0x50, 0xc9, 0x5c, 0x42, 0x45, 0x09, 0x20, 0xda, 0x29, 0xa5, 0xe7, 0xf1, + 0x17, 0x37, 0x74, 0x41, 0x46, 0x4e, 0x1e, 0x6f, 0x45, 0x3b, 0xa3, 0x95, 0x8f, 0x91, 0x89, 0x83, 0x59, 0x80, 0xc0, + 0x9d, 0xb3, 0x41, 0x0d, 0x8a, 0x3f, 0x6d, 0xcc, 0x69, 0xa8, 0x79, 0x09, 0xd0, 0x0f, 0xe5, 0x7d, 0x73, 0xe7, 0xaa, + 0x9f, 0xb8, 0xd3, 0x75, 0x47, 0xeb, 0xdd, 0x82, 0x42, 0xbb, 0x3a, 0xad, 0x37, 0x29, 0xc7, 0x16, 0x6d, 0xc3, 0xea, + 0x6d, 0xf9, 0xf7, 0xdb, 0x8a, 0x7c, 0xac, 0x5b, 0x2d, 0x8e, 0xd3, 0x0f, 0xa4, 0x5a, 0x27, 0xf5, 0xdc, 0xcf, 0xca, + 0x71, 0xf2, 0x3f, 0xc4, 0xa0, 0xf3, 0x7a, 0xea, 0xa9, 0x10, 0x38, 0x17, 0x51, 0x9d, 0xdc, 0x54, 0x68, 0xae, 0x27, + 0x2b, 0xc4, 0x2b, 0x65, 0xc4, 0xd7, 0x1f, 0xcd, 0xef, 0xf5, 0xa0, 0x31, 0xa2, 0x87, 0x01, 0x0a, 0x64, 0xc4, 0xcb, + 0xfe, 0xf3, 0xa2, 0xa2, 0xd5, 0xdb, 0xc9, 0xcd, 0x1d, 0x65, 0xcf, 0x1e, 0x3f, 0x36, 0x75, 0xf1, 0xe4, 0x36, 0xac, + 0x18, 0xf3, 0x81, 0x19, 0x85, 0x0d, 0x0c, 0xdd, 0x44, 0x18, 0x5c, 0xee, 0xc8, 0xd5, 0xc9, 0xc8, 0x10, 0x0c, 0xc4, + 0x7d, 0x61, 0x25, 0x29, 0xed, 0x65, 0xa2, 0x6f, 0x50, 0xaf, 0xb6, 0xa1, 0x75, 0xb4, 0x2a, 0x53, 0xa8, 0x9b, 0x10, + 0x41, 0x79, 0xf5, 0x84, 0xbb, 0x36, 0x19, 0xc7, 0x49, 0x24, 0x39, 0x56, 0x77, 0x19, 0xbb, 0x7d, 0x56, 0xaa, 0x86, + 0x4c, 0x75, 0x6a, 0xcd, 0x40, 0xbb, 0xe3, 0xe4, 0x62, 0x28, 0xf9, 0xfd, 0xa5, 0x3a, 0x0e, 0x33, 0xc4, 0xfb, 0x81, + 0x01, 0x7a, 0xe3, 0x26, 0x1b, 0x5b, 0xc6, 0xe6, 0xd0, 0xb9, 0x1c, 0x12, 0x88, 0xdf, 0x30, 0xf6, 0xbe, 0xa5, 0x31, + 0xb4, 0x93, 0x1b, 0x2a, 0x0f, 0xbe, 0x88, 0xe1, 0x98, 0x58, 0xe4, 0x84, 0x92, 0x33, 0x23, 0xc6, 0x8a, 0xff, 0x96, + 0xae, 0x5a, 0xf9, 0x3f, 0x9c, 0xbc, 0x8d, 0x3f, 0x28, 0xcf, 0x8f, 0x32, 0xb6, 0x28, 0xbc, 0x09, 0xe5, 0xa9, 0x5a, + 0xe1, 0x99, 0x54, 0x90, 0x35, 0x2c, 0xdc, 0xa8, 0xf9, 0x33, 0xcf, 0xc3, 0xf0, 0xbb, 0x1e, 0x22, 0x37, 0xc5, 0xcb, + 0x96, 0xfd, 0x50, 0x9d, 0x3d, 0xb4, 0x77, 0x1d, 0x03, 0x78, 0x98, 0x0d, 0x28, 0xdc, 0xb9, 0x3f, 0x15, 0xcf, 0x47, + 0xc0, 0x03, 0x38, 0x5e, 0x4f, 0xbe, 0x6a, 0xc9, 0x45, 0xc4, 0x78, 0xab, 0xe9, 0x40, 0xf6, 0x84, 0x7b, 0x2b, 0x43, + 0x63, 0xdd, 0x44, 0x03, 0x61, 0xf8, 0xf0, 0xda, 0xe1, 0xf4, 0xbe, 0x53, 0x7c, 0xf5, 0x0c, 0x50, 0x7d, 0xe0, 0x9f, + 0xc2, 0x83, 0xaf, 0xe4, 0x51, 0x68, 0x6f, 0x50, 0x0b, 0xa8, 0xe8, 0x70, 0x12, 0xa6, 0x00, 0x87, 0x60, 0x5b, 0x12, + 0xb4, 0x34, 0xa9, 0xfa, 0x4e, 0x52, 0xf5, 0xf4, 0x10, 0x86, 0x5f, 0xcf, 0x3c, 0xd7, 0x34, 0xdb, 0x4c, 0x65, 0x9f, + 0x7c, 0x3d, 0x38, 0xac, 0x0c, 0x93, 0x89, 0xcf, 0x3a, 0xc4, 0x4a, 0x30, 0x0c, 0x26, 0x0a, 0x9e, 0xff, 0xda, 0x6e, + 0x40, 0x26, 0xb5, 0x9c, 0xae, 0x4f, 0x7c, 0x8b, 0x65, 0x1e, 0x39, 0x9e, 0xdb, 0x9b, 0x9e, 0x24, 0x62, 0x0c, 0x67, + 0xea, 0xfe, 0x40, 0x4e, 0x2b, 0xcb, 0x78, 0xdd, 0xfa, 0x63, 0x02, 0xf9, 0x7c, 0x55, 0x35, 0x7b, 0x76, 0x55, 0x6d, + 0x2d, 0xf0, 0x5e, 0xe5, 0xa9, 0xfb, 0xf7, 0x73, 0xd1, 0xa2, 0x09, 0x42, 0x19, 0x41, 0x3b, 0xcc, 0x84, 0x55, 0xc2, + 0x4c, 0x23, 0xa5, 0x4a, 0x6b, 0x93, 0xb3, 0xcf, 0xd5, 0x56, 0xf3, 0x08, 0x03, 0x0c, 0x66, 0x24, 0x58, 0xaf, 0xfa, + 0x14, 0xa9, 0x37, 0x1c, 0x95, 0x38, 0xfc, 0x56, 0x86, 0xdc, 0x92, 0x82, 0xfb, 0x2a, 0xb1, 0xbf, 0x94, 0x88, 0x1e, + 0x4c, 0x8c, 0x03, 0xf7, 0xa2, 0x9b, 0x7e, 0x84, 0x7a, 0x95, 0x72, 0x71, 0x7a, 0x22, 0x35, 0x6f, 0x74, 0x58, 0x20, + 0x14, 0xf0, 0xd8, 0x64, 0xf7, 0x43, 0x0c, 0x9b, 0xe7, 0xc3, 0xfa, 0x31, 0x5e, 0xe1, 0x0b, 0xda, 0x53, 0x48, 0x03, + 0xb7, 0xf5, 0x8e, 0x3e, 0x28, 0x87, 0xce, 0x6a, 0x33, 0x4e, 0xcc, 0x89, 0x2a, 0x31, 0x19, 0x3b, 0x31, 0x9b, 0xd1, + 0x23, 0x5d, 0xb5, 0xe3, 0x39, 0xa6, 0xa4, 0x04, 0x40, 0x4d, 0x76, 0xf8, 0xfb, 0x6f, 0xe9, 0xad, 0xb6, 0x05, 0xb1, + 0xd6, 0xb0, 0x41, 0x5a, 0x5d, 0xb4, 0x71, 0x53, 0xf8, 0xf3, 0x36, 0x3d, 0x9a, 0x57, 0x42, 0x48, 0xd4, 0xd9, 0x21, + 0x3e, 0x98, 0x4c, 0xa0, 0x53, 0x72, 0x4a, 0xde, 0x4e, 0xea, 0x78, 0xcb, 0x15, 0xcf, 0x81, 0x84, 0xe4, 0x27, 0x83, + 0xa1, 0x88, 0xb9, 0xcb, 0xad, 0x46, 0x69, 0xc7, 0x7b, 0xdc, 0x2f, 0x15, 0x7c, 0xac, 0x96, 0x4b, 0xb2, 0x0f, 0xc2, + 0x37, 0x8e, 0x9d, 0x90, 0xc8, 0x31, 0x69, 0x24, 0x86, 0xeb, 0x96, 0x28, 0x96, 0x94, 0x9d, 0xda, 0xd3, 0x30, 0xe0, + 0x3a, 0x69, 0xa5, 0xf0, 0x69, 0x3a, 0x3e, 0xa4, 0x78, 0x82, 0x91, 0x75, 0x05, 0x78, 0xab, 0x1d, 0x0b, 0x0f, 0xf6, + 0x21, 0x75, 0x37, 0x82, 0x5d, 0x01, 0x51, 0x2f, 0x53, 0x94, 0x30, 0x39, 0x5a, 0x94, 0xcc, 0xf9, 0x11, 0x6b, 0x3d, + 0x9e, 0xa4, 0x94, 0x6e, 0x1f, 0xad, 0xcf, 0x9a, 0x0c, 0x3e, 0xa6, 0xd8, 0x0d, 0x90, 0x43, 0x00, 0x04, 0xee, 0xab, + 0xfc, 0x8a, 0xab, 0xcb, 0x55, 0x58, 0x68, 0xcc, 0x4a, 0x51, 0x11, 0x52, 0xed, 0x04, 0xa6, 0xdd, 0xd6, 0xcc, 0x0b, + 0x7d, 0x95, 0x91, 0xf3, 0x70, 0x8d, 0x94, 0x49, 0x49, 0xc5, 0x0c, 0x94, 0xa1, 0xf3, 0x88, 0x62, 0xb4, 0xd8, 0xcc, + 0xb5, 0x40, 0x3c, 0xb2, 0x7f, 0x05, 0x87, 0x3c, 0x96, 0x32, 0x33, 0x07, 0xa8, 0xc3, 0x73, 0xc7, 0x39, 0xe7, 0xf7, + 0x97, 0xe2, 0xab, 0x14, 0x50, 0x7d, 0xbe, 0x99, 0x27, 0x43, 0x91, 0xe8, 0xd2, 0x2c, 0x4b, 0x52, 0xd2, 0x60, 0xfb, + 0xc2, 0xba, 0x1a, 0x97, 0x6e, 0x5d, 0x49, 0x75, 0x29, 0xaf, 0xc3, 0xc8, 0x30, 0xad, 0x54, 0xc7, 0xd2, 0xab, 0x6a, + 0xb6, 0x46, 0xf8, 0x59, 0xd4, 0xd2, 0xe3, 0xf5, 0x64, 0xda, 0xc9, 0x2e, 0xdc, 0x50, 0x82, 0xe5, 0x00, 0x3f, 0x43, + 0x6a, 0x42, 0xae, 0xca, 0x69, 0x10, 0x80, 0x12, 0x81, 0x11, 0xe2, 0xe3, 0xa9, 0x9f, 0xe7, 0x8a, 0x19, 0x06, 0xe6, + 0x7b, 0x44, 0xd0, 0xd4, 0x21, 0x61, 0x68, 0xac, 0xba, 0x0d, 0x2d, 0xde, 0x73, 0xeb, 0xa3, 0xc8, 0x45, 0x2b, 0x47, + 0x3d, 0x20, 0xb7, 0xdd, 0x9e, 0xe9, 0x6a, 0x70, 0x83, 0xdc, 0x43, 0x3f, 0x81, 0x79, 0xec, 0xbd, 0x3e, 0x12, 0xab, + 0xe2, 0x98, 0xf5, 0x4e, 0xd1, 0xd9, 0xc3, 0x31, 0xe7, 0x7d, 0x7a, 0xa3, 0x9a, 0x46, 0xf3, 0x07, 0x31, 0xeb, 0x1b, + 0xbb, 0xd1, 0x6b, 0x5d, 0x73, 0x9c, 0xe7, 0x17, 0xc1, 0x74, 0x58, 0xd4, 0xde, 0xff, 0xed, 0x00, 0x35, 0x31, 0xba, + 0x6d, 0x19, 0x0b, 0x5c, 0x09, 0x69, 0x40, 0x2d, 0xdb, 0x7d, 0xea, 0xa2, 0x12, 0xf5, 0x41, 0x6e, 0xf5, 0xa2, 0x25, + 0xa2, 0x1a, 0x8b, 0x13, 0x5f, 0x6b, 0xef, 0x1a, 0xe9, 0x56, 0x6f, 0x72, 0x1b, 0xb4, 0x86, 0x74, 0x79, 0xaa, 0xa7, + 0xa7, 0xc0, 0xbd, 0x2c, 0xbe, 0x2a, 0xb3, 0x59, 0x64, 0x3b, 0xff, 0xf1, 0x90, 0xdd, 0xef, 0xa3, 0x32, 0x78, 0x7d, + 0x46, 0x33, 0x6f, 0xe1, 0xc7, 0xbd, 0x9b, 0x65, 0x00, 0xd6, 0x5e, 0x91, 0x9c, 0xf8, 0x5d, 0x24, 0x5d, 0x4b, 0xb3, + 0xcc, 0xd5, 0x29, 0x67, 0xd5, 0xdc, 0xce, 0xd9, 0x20, 0x9f, 0x67, 0xa8, 0xa0, 0xd9, 0xb4, 0xb1, 0x77, 0xbf, 0xc0, + 0x89, 0x78, 0x11, 0x46, 0xf8, 0x22, 0x76, 0xde, 0xa3, 0x2d, 0x35, 0xb7, 0x5a, 0xb6, 0xec, 0xab, 0x7c, 0x60, 0xee, + 0xd9, 0x2f, 0x82, 0x32, 0xa4, 0x07, 0x3b, 0xcb, 0x2e, 0x70, 0x89, 0x88, 0x97, 0xba, 0xbd, 0xb4, 0x76, 0x4f, 0x64, + 0x25, 0xcb, 0x8f, 0x9d, 0xa8, 0x60, 0x0e, 0x06, 0xb0, 0xc2, 0x19, 0x63, 0xc6, 0x1c, 0xc7, 0x83, 0x59, 0xef, 0x50, + 0xa8, 0x5c, 0x1f, 0x01, 0x3e, 0xd9, 0x63, 0x76, 0xe3, 0x2b, 0x17, 0x64, 0x0e, 0xce, 0xc7, 0x5e, 0x62, 0x48, 0x75, + 0x94, 0xdc, 0xcb, 0x30, 0xd1, 0x02, 0x6f, 0xcd, 0x2e, 0x05, 0xab, 0x70, 0x4f, 0x31, 0x3e, 0x0e, 0xfd, 0xa1, 0xcd, + 0xd9, 0x84, 0x21, 0xb3, 0xe0, 0x84, 0x25, 0xbb, 0x3a, 0x2f, 0x28, 0x92, 0x44, 0x1d, 0x61, 0xac, 0x37, 0x0a, 0xf5, + 0xa0, 0x88, 0x98, 0x50, 0xf5, 0x9a, 0x28, 0x3b, 0x1d, 0x98, 0xc0, 0xe7, 0x3c, 0xee, 0x4e, 0xd4, 0x87, 0x5d, 0xe5, + 0xf2, 0xff, 0xab, 0xe5, 0x27, 0x2a, 0x3e, 0x20, 0xc8, 0x9e, 0xf7, 0x14, 0xf6, 0x59, 0x4c, 0xdf, 0x62, 0x8b, 0xfd, + 0xba, 0xe5, 0x2b, 0xad, 0xb5, 0x47, 0x66, 0x0e, 0x35, 0x65, 0x83, 0x80, 0x8c, 0xd6, 0x77, 0x33, 0x3b, 0x7a, 0x04, + 0xfd, 0x49, 0xd3, 0x2b, 0x0a, 0x15, 0x60, 0xff, 0xde, 0xaf, 0x6c, 0x14, 0x52, 0xe4, 0xae, 0xae, 0x5d, 0x68, 0x48, + 0xbb, 0xcb, 0x6f, 0x94, 0x2a, 0x94, 0x03, 0xa1, 0xc5, 0x41, 0xd5, 0x29, 0xee, 0x7d, 0xcc, 0x5a, 0xd7, 0x70, 0x8d, + 0x60, 0x03, 0x31, 0x87, 0xd4, 0x38, 0x32, 0x0f, 0x7d, 0xa5, 0x6e, 0x80, 0x1b, 0x37, 0x1a, 0x61, 0xc5, 0x8f, 0x9d, + 0x77, 0xbf, 0xc8, 0x57, 0xc2, 0xcc, 0x47, 0x44, 0xa2, 0x9b, 0x3e, 0x6e, 0xb6, 0x99, 0x9d, 0xcd, 0x8f, 0x54, 0x57, + 0x30, 0x6c, 0x93, 0x29, 0xc4, 0x31, 0x4d, 0xef, 0x90, 0xe7, 0xc1, 0x8f, 0x9e, 0x4c, 0xb0, 0xb9, 0x2b, 0x48, 0xad, + 0x5a, 0x14, 0x18, 0xca, 0x2e, 0x45, 0x09, 0x9f, 0xfd, 0xba, 0xa7, 0x90, 0x76, 0x31, 0x5a, 0xf9, 0x69, 0x87, 0x22, + 0x03, 0xfa, 0x97, 0xdf, 0x07, 0x6c, 0xab, 0x4a, 0xc7, 0x23, 0xab, 0xdd, 0x2b, 0x7e, 0x6a, 0x39, 0x43, 0xdf, 0x22, + 0xad, 0xc4, 0xe0, 0x87, 0xeb, 0x80, 0x90, 0x0a, 0x21, 0x5e, 0xf4, 0x27, 0x3e, 0xec, 0xc5, 0x25, 0x65, 0xaa, 0xb5, + 0x18, 0xfe, 0x24, 0x77, 0xe2, 0x12, 0xce, 0xbe, 0xea, 0xbe, 0x44, 0xc4, 0xf7, 0xe2, 0x01, 0x84, 0x30, 0xa8, 0xd4, + 0x6f, 0x35, 0x52, 0x41, 0xc4, 0x8f, 0xe4, 0xe7, 0xf9, 0x7f, 0xeb, 0xd8, 0x1f, 0x74, 0x5e, 0xde, 0xbb, 0xec, 0x02, + 0x04, 0x1d, 0x7f, 0x0f, 0x8b, 0x11, 0xdb, 0x03, 0x46, 0xa4, 0x28, 0xb4, 0x20, 0xd0, 0x4d, 0xf8, 0x7b, 0xb8, 0x05, + 0xed, 0x81, 0xf7, 0x38, 0x24, 0x8e, 0xf2, 0x16, 0xfb, 0x8d, 0x1d, 0xee, 0xde, 0xdb, 0x5b, 0xdf, 0x71, 0x2b, 0x95, + 0x5d, 0x82, 0x72, 0x4f, 0xf9, 0xd8, 0xcd, 0x2c, 0xd0, 0x1c, 0x85, 0xf9, 0x7a, 0xc3, 0x89, 0x16, 0xdd, 0x3b, 0x30, + 0x51, 0xc8, 0x6e, 0x4d, 0xc5, 0xe0, 0xa3, 0x63, 0x25, 0x9a, 0xc5, 0x0e, 0x10, 0x04, 0xe8, 0x2e, 0x62, 0xc5, 0x42, + 0x5d, 0x1e, 0x68, 0x5e, 0x74, 0x10, 0x10, 0x8c, 0xfe, 0x4e, 0x01, 0x6b, 0xaf, 0x6c, 0x03, 0x87, 0x1c, 0x90, 0xd4, + 0x16, 0x26, 0xb7, 0x23, 0x4e, 0xf1, 0x8b, 0xd4, 0x0f, 0xad, 0xde, 0xce, 0xc4, 0x4e, 0x07, 0xbc, 0x83, 0x53, 0x95, + 0x8a, 0x1e, 0x12, 0x74, 0x84, 0x6f, 0xaa, 0xa1, 0x62, 0x25, 0xb8, 0x09, 0xfa, 0x40, 0xbe, 0x57, 0xfd, 0x26, 0x77, + 0xaa, 0xff, 0xa7, 0xcb, 0x5a, 0x39, 0x2c, 0x38, 0x5e, 0x86, 0xe5, 0x0d, 0x97, 0x7a, 0xdc, 0x0f, 0x67, 0x6c, 0xf4, + 0x8f, 0xcd, 0xda, 0xf9, 0xa7, 0x9f, 0x68, 0xd7, 0xbe, 0xef, 0x0f, 0x52, 0x0d, 0xab, 0xe1, 0xdd, 0xdf, 0x23, 0x35, + 0x25, 0x0a, 0x6a, 0x4b, 0x5f, 0x8e, 0x2e, 0xe4, 0xad, 0x9e, 0x7a, 0x8a, 0xd1, 0x7c, 0x8e, 0x23, 0x28, 0x8f, 0xe9, + 0x9e, 0x3e, 0x1b, 0x8c, 0xf8, 0xc8, 0xdf, 0x5d, 0xa0, 0x2a, 0xdc, 0xcb, 0xea, 0xcb, 0xe9, 0x46, 0xd8, 0x9c, 0x5e, + 0xa2, 0x39, 0x79, 0x86, 0xa1, 0x11, 0x15, 0xd3, 0xd2, 0x65, 0x31, 0x96, 0xaa, 0x62, 0x5e, 0x3a, 0x29, 0x16, 0xa5, + 0xd3, 0x62, 0xb9, 0xfe, 0x7e, 0xe6, 0x86, 0x51, 0xee, 0x8d, 0xb7, 0xd8, 0x1e, 0xd2, 0x64, 0x58, 0xfa, 0x88, 0xe9, + 0x28, 0x67, 0xed, 0xb7, 0xe4, 0xc2, 0xf2, 0xbe, 0xb1, 0x02, 0xd9, 0x78, 0xb5, 0xd6, 0x29, 0x92, 0x48, 0x1d, 0xbb, + 0xa8, 0xa5, 0x5d, 0x4f, 0xcf, 0x01, 0x2f, 0xfd, 0xbc, 0xb8, 0x5d, 0x72, 0x7c, 0x35, 0xe2, 0x1a, 0x5a, 0x0f, 0xad, + 0xd7, 0xbf, 0xd3, 0xa0, 0xd7, 0x15, 0x8d, 0xf4, 0xc7, 0x7b, 0x9c, 0x03, 0x3a, 0xa9, 0x01, 0x52, 0xf9, 0xec, 0xe2, + 0x89, 0x84, 0xcc, 0x15, 0x8e, 0x04, 0x15, 0x5f, 0xc8, 0xa5, 0xc8, 0x17, 0x6e, 0x61, 0x81, 0xdf, 0x39, 0x54, 0x9b, + 0x7b, 0xe4, 0x6c, 0x2a, 0x02, 0xe5, 0xc1, 0xde, 0x4d, 0x0d, 0x51, 0x53, 0xab, 0x39, 0x6f, 0xfa, 0x07, 0xb1, 0xef, + 0x88, 0x18, 0x8d, 0x16, 0x79, 0x81, 0x0f, 0x8f, 0x6c, 0xac, 0x1b, 0xa9, 0x12, 0x10, 0xee, 0xf4, 0x9d, 0xf7, 0xac, + 0xd4, 0x9a, 0xc1, 0x5b, 0x22, 0xad, 0xe5, 0xff, 0x6b, 0x90, 0x8e, 0x6e, 0x8b, 0x9e, 0x97, 0x01, 0x69, 0x4f, 0x17, + 0xab, 0x95, 0x45, 0xa3, 0xe0, 0x7e, 0xf0, 0xed, 0x86, 0x8a, 0xd0, 0xb0, 0xc9, 0xcc, 0x6d, 0x4d, 0x9f, 0x85, 0xcc, + 0x28, 0xfa, 0x88, 0x46, 0xb9, 0x71, 0x32, 0x75, 0x74, 0x9b, 0x4c, 0x08, 0x7c, 0x01, 0x54, 0x6a, 0x55, 0xc4, 0xa7, + 0x41, 0xf6, 0x43, 0x30, 0x6c, 0x08, 0xaf, 0x10, 0xe3, 0x23, 0xa8, 0xe9, 0xf3, 0xec, 0xea, 0xee, 0xc8, 0x8f, 0x08, + 0x4a, 0x70, 0x0d, 0x8f, 0xe4, 0x24, 0x06, 0x26, 0x8d, 0x26, 0x51, 0xe2, 0xe3, 0xd3, 0xbc, 0xf0, 0xa0, 0xad, 0xfe, + 0xe2, 0x5b, 0x04, 0xfe, 0xa6, 0xfe, 0xbb, 0x38, 0xfc, 0x1b, 0xa2, 0x6a, 0x59, 0x7c, 0xcc, 0xda, 0xa7, 0xf8, 0xfd, + 0x70, 0xcf, 0xed, 0xd3, 0x77, 0x13, 0x29, 0xe5, 0x57, 0x0e, 0xbf, 0x7b, 0x50, 0x3c, 0x00, 0xea, 0xe6, 0xed, 0x73, + 0x7e, 0x14, 0x60, 0xd9, 0xd6, 0xf8, 0xea, 0xc8, 0x85, 0x58, 0xb2, 0xae, 0xd7, 0xe5, 0xff, 0xe4, 0xbe, 0xfe, 0xd7, + 0x67, 0x0a, 0x19, 0x6f, 0x5f, 0x1e, 0x1d, 0x0f, 0x46, 0x23, 0xb3, 0xf2, 0xa6, 0x1b, 0x39, 0xdf, 0xbf, 0xe3, 0xd3, + 0xb7, 0xda, 0x88, 0x87, 0xc3, 0x47, 0x31, 0xb9, 0x74, 0x35, 0x75, 0x8c, 0x75, 0xc7, 0xb5, 0x4d, 0x97, 0x17, 0x19, + 0x6f, 0x03, 0xaf, 0x2b, 0x9b, 0xf5, 0xff, 0xa1, 0x29, 0x6d, 0x2e, 0xe5, 0xf0, 0x27, 0x0d, 0x0d, 0xfc, 0xb6, 0x94, + 0x2c, 0xa7, 0x97, 0xac, 0x5b, 0x48, 0xd0, 0xc9, 0xbb, 0x50, 0xb7, 0xb8, 0x47, 0x20, 0x41, 0x85, 0x46, 0xc4, 0x91, + 0x97, 0xf0, 0x4b, 0xba, 0x93, 0x57, 0xeb, 0x9e, 0x1e, 0xb9, 0x97, 0xc6, 0x2b, 0x41, 0x6b, 0x06, 0xae, 0x1d, 0x2c, + 0xe5, 0x8b, 0xd5, 0x87, 0x9c, 0xa7, 0xde, 0xf5, 0xd3, 0xd3, 0x0f, 0xb8, 0xfd, 0xbe, 0xa5, 0x46, 0x34, 0x7f, 0xf3, + 0xd7, 0x76, 0x10, 0x7b, 0x57, 0xa9, 0xb4, 0x12, 0xe5, 0xd8, 0xb9, 0xbc, 0x59, 0xfe, 0x58, 0x2d, 0x23, 0x03, 0x54, + 0xc5, 0xa4, 0xe0, 0x10, 0xe2, 0x43, 0xfd, 0x61, 0x1c, 0x2e, 0x4c, 0x34, 0xbd, 0x34, 0xbb, 0x77, 0x70, 0xe8, 0x83, + 0xa6, 0xbd, 0xd0, 0x65, 0xea, 0xdc, 0x94, 0xee, 0x7f, 0xc8, 0xfe, 0xf7, 0xda, 0xab, 0x40, 0x3f, 0x69, 0xa8, 0x26, + 0x61, 0x2b, 0xc5, 0xaf, 0x9d, 0xe0, 0x75, 0xc1, 0x00, 0xd3, 0xcb, 0xb3, 0x5b, 0x7a, 0xa2, 0x23, 0x19, 0xac, 0x87, + 0x71, 0x5f, 0x88, 0x12, 0xb2, 0xfc, 0x49, 0xae, 0xad, 0xec, 0x9f, 0x7f, 0x98, 0x5d, 0x5c, 0x4d, 0xe7, 0x10, 0x62, + 0xd6, 0x1c, 0x4a, 0x96, 0x83, 0xf8, 0x75, 0xa6, 0xbc, 0x14, 0x44, 0xba, 0xcd, 0x73, 0xca, 0x3c, 0x63, 0x89, 0x10, + 0xd5, 0x39, 0xc9, 0x13, 0xc4, 0x2e, 0x96, 0xd7, 0xfd, 0xb8, 0x7d, 0x43, 0x6f, 0xf7, 0x66, 0x2b, 0x31, 0xc8, 0x6b, + 0x10, 0x3b, 0x78, 0x25, 0x35, 0x01, 0x03, 0x45, 0x1e, 0x92, 0xef, 0xbb, 0xa2, 0x66, 0x3c, 0xef, 0xfe, 0xc6, 0xf1, + 0xe8, 0x45, 0xe6, 0xf1, 0x8d, 0x26, 0xcd, 0xbb, 0xd7, 0x66, 0x9c, 0x7b, 0x57, 0xf4, 0xdb, 0xc7, 0x4d, 0x3b, 0x3b, + 0x52, 0x73, 0x69, 0xb2, 0x65, 0xa1, 0xb0, 0x35, 0x1b, 0x7a, 0xc4, 0x61, 0xb2, 0x2d, 0x42, 0x0c, 0xda, 0x70, 0x5d, + 0x4a, 0x37, 0xb5, 0x09, 0xc2, 0x1d, 0x1a, 0x4c, 0x33, 0x26, 0x9d, 0x2a, 0xb0, 0xd7, 0xc8, 0x73, 0xd4, 0x93, 0x9f, + 0x69, 0x10, 0x2d, 0xf1, 0x97, 0x3c, 0xc0, 0xed, 0x92, 0x18, 0xe1, 0xb5, 0xc4, 0xde, 0x0a, 0x5f, 0x0b, 0x01, 0x8a, + 0xeb, 0xd5, 0x67, 0x95, 0x57, 0x02, 0xfb, 0x44, 0x13, 0xad, 0x8a, 0xef, 0xdb, 0x92, 0xbe, 0x5c, 0xb6, 0xd5, 0x10, + 0x5e, 0x3d, 0x4b, 0xdc, 0x21, 0x4b, 0x64, 0x35, 0x44, 0xbb, 0x84, 0x58, 0x73, 0x5b, 0xb2, 0xbf, 0xdc, 0x04, 0xd7, + 0x36, 0xed, 0x9e, 0xc5, 0xa2, 0x0f, 0xb6, 0x7e, 0x5c, 0x03, 0xb0, 0xbf, 0x44, 0x8e, 0x23, 0x56, 0x13, 0x68, 0xbb, + 0x42, 0xbf, 0x56, 0xda, 0x22, 0xe2, 0x42, 0xfe, 0x4f, 0x0f, 0x91, 0x56, 0x74, 0xdf, 0x73, 0xea, 0x6c, 0xfb, 0xdd, + 0x08, 0xf5, 0xec, 0x9a, 0xc4, 0x33, 0xda, 0x85, 0xf0, 0x47, 0x10, 0x2d, 0xd9, 0x97, 0x28, 0x7a, 0x02, 0x81, 0xc3, + 0x37, 0xe6, 0x75, 0xf4, 0xec, 0x73, 0xe8, 0xc8, 0x5a, 0x8c, 0x10, 0xc0, 0x48, 0xc5, 0x91, 0x73, 0x51, 0xd2, 0x24, + 0x0a, 0x16, 0x7d, 0x2e, 0x2e, 0x5c, 0x8e, 0xbb, 0x52, 0x5a, 0x5f, 0xf9, 0x81, 0xc2, 0x37, 0x75, 0x01, 0x92, 0x28, + 0x53, 0x53, 0x5d, 0xba, 0xad, 0x04, 0xd6, 0x09, 0xeb, 0xe9, 0x87, 0xe6, 0x2f, 0x59, 0x1d, 0x11, 0x93, 0x24, 0x15, + 0xd3, 0xeb, 0x88, 0x11, 0x7e, 0x57, 0x2d, 0x5e, 0x57, 0x2c, 0x36, 0xbd, 0x64, 0x34, 0x00, 0x46, 0x88, 0xa9, 0x8c, + 0x06, 0x29, 0xdd, 0xbf, 0x4c, 0x2d, 0x6d, 0x87, 0xe4, 0x67, 0x36, 0xd8, 0x75, 0x2b, 0xf4, 0x4f, 0xc3, 0x1e, 0x7c, + 0xbe, 0xe4, 0xc6, 0x1b, 0x9f, 0x60, 0xcd, 0xd9, 0x18, 0x14, 0x64, 0xcd, 0x0a, 0x7a, 0x2c, 0x6a, 0xab, 0xa7, 0xf0, + 0x31, 0x65, 0x21, 0x9c, 0xfc, 0x94, 0xae, 0x32, 0xf4, 0x5c, 0xdc, 0x63, 0x26, 0x51, 0x8f, 0xd7, 0x3d, 0xd4, 0xda, + 0xfa, 0x62, 0x26, 0xff, 0x26, 0x0e, 0x32, 0xb5, 0xd1, 0xac, 0x49, 0xef, 0xab, 0x30, 0x56, 0xbb, 0x18, 0xb6, 0x1d, + 0x0c, 0xbe, 0x12, 0xd1, 0xe7, 0x06, 0xc4, 0x6d, 0x44, 0xc6, 0x02, 0x3e, 0xd2, 0x91, 0x75, 0x45, 0xfd, 0xa5, 0xa0, + 0xaf, 0xe3, 0xfb, 0x5d, 0xe8, 0x8e, 0xbb, 0xc3, 0x9e, 0x5f, 0x48, 0xe2, 0x16, 0xf9, 0x0d, 0x5a, 0xbf, 0x7d, 0xe3, + 0xa5, 0x6d, 0x72, 0x4c, 0xdd, 0xf7, 0x79, 0x10, 0x80, 0xbc, 0x57, 0x41, 0x58, 0xd2, 0x3b, 0x2d, 0xa3, 0x97, 0xa1, + 0x94, 0x8b, 0xb2, 0xb2, 0xed, 0x74, 0xce, 0xfe, 0x3f, 0x6c, 0x43, 0xdb, 0xa9, 0x7c, 0x22, 0x33, 0xb4, 0xab, 0x00, + 0x89, 0x0f, 0x88, 0x3e, 0x3e, 0x69, 0xe5, 0xf8, 0xa3, 0x90, 0xec, 0x6d, 0x62, 0xf3, 0xb6, 0x1d, 0x83, 0xe8, 0x7b, + 0x84, 0x60, 0xd9, 0x1e, 0x42, 0xfe, 0xb1, 0xab, 0x72, 0x0b, 0x11, 0xdf, 0x7d, 0x5f, 0x5a, 0xea, 0xfa, 0xe2, 0xb7, + 0xff, 0x97, 0xd6, 0x80, 0x25, 0x3e, 0xc6, 0xeb, 0x74, 0xec, 0x0b, 0xf7, 0xe7, 0x78, 0xa2, 0x1b, 0xc7, 0xaf, 0xbe, + 0xf8, 0x78, 0x4b, 0x9c, 0xfb, 0x8b, 0x18, 0x69, 0xf5, 0xed, 0xa1, 0x9f, 0x11, 0xf4, 0xe3, 0x19, 0xc1, 0x13, 0xfa, + 0x29, 0x64, 0x3b, 0x86, 0xbf, 0x49, 0xa8, 0xe3, 0xd7, 0x8c, 0x87, 0x1f, 0x87, 0x29, 0x84, 0x91, 0x85, 0xaf, 0x51, + 0xcf, 0xc8, 0xaa, 0xc3, 0xae, 0x80, 0x6c, 0x21, 0x05, 0x1c, 0xe4, 0xa8, 0x47, 0xe9, 0x24, 0x46, 0xd2, 0xa1, 0xad, + 0xd1, 0x68, 0xcb, 0x2f, 0xc7, 0xd1, 0x1d, 0xaa, 0x47, 0x57, 0xdd, 0xa9, 0x8c, 0x66, 0x5c, 0x36, 0x41, 0xe6, 0x46, + 0x50, 0xcf, 0x64, 0x7b, 0xd5, 0x41, 0x7a, 0x3c, 0x08, 0x15, 0xf6, 0x97, 0x4e, 0x04, 0x34, 0x48, 0x01, 0x07, 0xae, + 0xba, 0x56, 0x54, 0x78, 0xe8, 0x8f, 0xf7, 0x75, 0x5f, 0x39, 0x44, 0x46, 0x3a, 0x1a, 0xb3, 0xd1, 0xe8, 0x91, 0x3c, + 0x6f, 0xd7, 0x42, 0x58, 0x1d, 0xbb, 0xd9, 0xb8, 0x61, 0x1a, 0x0e, 0xbe, 0xe0, 0x0d, 0xae, 0xd7, 0xd5, 0x87, 0xc7, + 0xb1, 0x12, 0xf3, 0x7f, 0xc1, 0x2b, 0x9c, 0x6e, 0xc6, 0x5b, 0xcd, 0x9e, 0x69, 0xef, 0xfb, 0xbd, 0x03, 0xab, 0x61, + 0xae, 0x79, 0x71, 0xe8, 0xfb, 0xa4, 0x7a, 0xdd, 0x36, 0xfa, 0x69, 0xf8, 0x9b, 0xb5, 0x7d, 0x80, 0xee, 0x27, 0xda, + 0xfb, 0x00, 0x8d, 0xed, 0xe7, 0x10, 0xb9, 0x6e, 0xee, 0x3f, 0xc4, 0x74, 0xba, 0xc9, 0x05, 0x36, 0x65, 0xf2, 0x53, + 0x2b, 0x50, 0x1d, 0x28, 0x7c, 0xb5, 0x45, 0x86, 0xc1, 0x39, 0xbc, 0xe2, 0x15, 0x08, 0x43, 0xe4, 0x78, 0x15, 0x1e, + 0xc1, 0xeb, 0x1f, 0x5d, 0x70, 0x26, 0x34, 0x93, 0x48, 0x44, 0x72, 0x63, 0xc9, 0x43, 0x9c, 0x22, 0x94, 0xb9, 0xa2, + 0x7c, 0xda, 0x86, 0xc1, 0x0e, 0x59, 0xae, 0x2c, 0x0a, 0xfc, 0xf2, 0xc2, 0xde, 0xb6, 0x97, 0xcb, 0x15, 0x0e, 0xac, + 0xd6, 0xfb, 0x36, 0x14, 0x90, 0xc8, 0xea, 0x96, 0x2e, 0x9d, 0x41, 0xfb, 0xeb, 0xe8, 0x1a, 0x87, 0x11, 0x7e, 0x9d, + 0xa3, 0x0c, 0x6c, 0xfc, 0x0a, 0x22, 0xa0, 0x90, 0x7d, 0x0c, 0x8a, 0xec, 0xd1, 0x23, 0x3a, 0x2d, 0x12, 0x06, 0xb5, + 0x42, 0x7b, 0x40, 0xdc, 0xc2, 0x40, 0x3f, 0x6a, 0xc5, 0xe7, 0xbb, 0x95, 0xe4, 0x63, 0x54, 0xfe, 0x26, 0x7e, 0x05, + 0xd0, 0x91, 0x17, 0xd4, 0x8d, 0xec, 0x15, 0x49, 0x0d, 0x0b, 0x6e, 0x22, 0xcb, 0x29, 0x40, 0x02, 0xb9, 0x0d, 0x73, + 0xff, 0x0f, 0xbe, 0xa4, 0x3d, 0x24, 0x8c, 0x11, 0xba, 0x52, 0x06, 0xdc, 0xe8, 0x22, 0x90, 0x4a, 0x84, 0x46, 0xd6, + 0xe8, 0xbb, 0x2e, 0x0a, 0x38, 0x81, 0x13, 0xb4, 0x2c, 0x92, 0x78, 0xb7, 0x47, 0xff, 0x5f, 0x2f, 0x41, 0x0d, 0xf8, + 0x78, 0x7d, 0xc3, 0x1e, 0xaa, 0xa1, 0xa7, 0x02, 0x15, 0x19, 0x37, 0x20, 0x66, 0x87, 0x2f, 0x45, 0xf6, 0x38, 0x30, + 0xf9, 0xbf, 0x88, 0xb0, 0x52, 0x38, 0x72, 0x7a, 0xfa, 0xfa, 0x0d, 0x8b, 0x8b, 0xdd, 0x5f, 0x25, 0x75, 0x18, 0xbf, + 0x25, 0x96, 0xd9, 0x4f, 0xf4, 0xac, 0x0b, 0xf4, 0xe4, 0xc7, 0x79, 0x56, 0x0f, 0x3c, 0x76, 0x97, 0x1f, 0xf0, 0xea, + 0xfb, 0x66, 0xea, 0xc9, 0xf3, 0xfd, 0x97, 0xbe, 0x84, 0x1e, 0x19, 0x6f, 0x3c, 0xf5, 0x56, 0x3f, 0x93, 0x03, 0x12, + 0x5e, 0x53, 0x31, 0xf4, 0xd6, 0xcd, 0x05, 0x59, 0xc3, 0x25, 0x58, 0x7e, 0x5e, 0x26, 0x35, 0x39, 0xcf, 0xf6, 0xbc, + 0xaf, 0x7e, 0xc0, 0xdb, 0xef, 0x7b, 0xce, 0x44, 0x9b, 0xcf, 0xa7, 0xd9, 0x06, 0x9c, 0x2d, 0xa0, 0xbf, 0xd8, 0xa3, + 0xad, 0x19, 0x09, 0x6f, 0x42, 0xeb, 0x4c, 0xb5, 0x10, 0xf6, 0xeb, 0x9e, 0x5d, 0xa3, 0x1e, 0xe8, 0xaf, 0x06, 0xd6, + 0x81, 0xeb, 0x42, 0x3e, 0x99, 0xa3, 0x01, 0x77, 0x4c, 0x46, 0xdb, 0x34, 0xb2, 0x20, 0xd2, 0xe3, 0x25, 0x2f, 0x32, + 0xda, 0x8b, 0x01, 0x6e, 0x3c, 0xbe, 0xa4, 0x02, 0x83, 0x6f, 0x99, 0xe8, 0x94, 0xe3, 0x39, 0x04, 0xe2, 0xfe, 0xcd, + 0xd0, 0xa9, 0xfc, 0x00, 0x69, 0x2b, 0x6a, 0x19, 0x29, 0x31, 0x13, 0x28, 0xf2, 0x1f, 0x7f, 0xb5, 0x6e, 0x35, 0x60, + 0x3f, 0xcb, 0xdb, 0xf0, 0x58, 0xd1, 0xc4, 0x5a, 0xc6, 0x5f, 0x55, 0x63, 0x2a, 0x51, 0xa1, 0x80, 0x96, 0xf3, 0xfe, + 0x9c, 0xa3, 0x3b, 0x3f, 0x08, 0x6b, 0x47, 0x06, 0xa6, 0x96, 0xed, 0x6f, 0x95, 0x81, 0x9b, 0x33, 0xbf, 0x7c, 0x10, + 0x01, 0x84, 0xed, 0xa0, 0xe4, 0xef, 0x07, 0x7f, 0x9a, 0xc2, 0x0d, 0xbb, 0x0e, 0xaf, 0x1c, 0x98, 0x3b, 0x98, 0x72, + 0x2b, 0x1b, 0x91, 0x1f, 0x61, 0x04, 0xcb, 0x0e, 0xd4, 0x0a, 0xe7, 0x79, 0xfe, 0xe7, 0xa2, 0x76, 0x0c, 0x96, 0x1b, + 0xd9, 0x6e, 0xa0, 0x79, 0x4a, 0xc4, 0x30, 0x13, 0xb3, 0x68, 0x94, 0xde, 0x9e, 0xd3, 0xca, 0x59, 0xc3, 0x6c, 0x17, + 0x9c, 0xcb, 0x65, 0xbd, 0x09, 0xe6, 0xf5, 0x47, 0x35, 0x81, 0xa4, 0xc3, 0x15, 0xbe, 0xd2, 0x0a, 0xe8, 0x9a, 0xa1, + 0x44, 0x21, 0x90, 0x03, 0x34, 0xdf, 0x2b, 0x56, 0xe2, 0x4d, 0x4e, 0x7e, 0xc1, 0x79, 0x1b, 0xf0, 0xdd, 0xbc, 0x87, + 0x4f, 0xad, 0x11, 0xda, 0x28, 0xcc, 0x86, 0x83, 0x8b, 0xa1, 0xaa, 0x05, 0xcc, 0x50, 0x23, 0x96, 0x43, 0xe1, 0x80, + 0x7d, 0xec, 0xfc, 0x35, 0x74, 0x36, 0x96, 0x6a, 0x6f, 0xb0, 0x1e, 0x11, 0xd8, 0xc3, 0xa7, 0x5c, 0x87, 0x72, 0xc1, + 0x8e, 0x81, 0x34, 0x55, 0xa8, 0xa3, 0xa0, 0x39, 0x19, 0x34, 0xa8, 0xec, 0xc7, 0x59, 0xeb, 0xbe, 0x40, 0xd3, 0x28, + 0x88, 0xc9, 0x11, 0x68, 0x5b, 0x50, 0xd8, 0xd4, 0xdf, 0x1f, 0x35, 0xae, 0x9b, 0xaf, 0xb6, 0xc0, 0x3b, 0x04, 0xad, + 0x0b, 0x12, 0x8d, 0x0b, 0x34, 0x86, 0xcb, 0xcb, 0xd5, 0xd3, 0x09, 0xa3, 0xa6, 0x1e, 0x07, 0x45, 0x52, 0xd1, 0xb8, + 0x44, 0x50, 0x37, 0x19, 0xb6, 0x9d, 0xb6, 0x4b, 0xc5, 0x81, 0xc0, 0xeb, 0x8d, 0xfd, 0x72, 0xf0, 0xa8, 0x78, 0x25, + 0xa1, 0xa3, 0xa4, 0xc2, 0xdf, 0x9b, 0xd8, 0xc5, 0x47, 0x7e, 0xe0, 0xe9, 0xe2, 0x95, 0x48, 0xbf, 0x04, 0x0b, 0x5a, + 0x1c, 0x78, 0x39, 0x0a, 0x1a, 0x25, 0x5e, 0xa1, 0xc9, 0xa6, 0x16, 0x3a, 0xeb, 0x6a, 0xa7, 0x88, 0x42, 0xc9, 0xd1, + 0xd9, 0xa7, 0xb1, 0x41, 0x2a, 0xa0, 0xee, 0xa3, 0x72, 0x4a, 0x12, 0x7e, 0x7d, 0x76, 0x8c, 0x13, 0x8a, 0xb4, 0x4f, + 0x07, 0x6d, 0x46, 0x54, 0x34, 0xb0, 0x97, 0x0f, 0x45, 0xf0, 0x33, 0x68, 0xc8, 0xce, 0x84, 0xf8, 0x13, 0xed, 0x57, + 0xec, 0xea, 0x4d, 0x8e, 0xf3, 0x5c, 0x1b, 0x0c, 0x5b, 0x89, 0xb5, 0xc0, 0x88, 0x4e, 0xfc, 0xe9, 0xf8, 0x9f, 0x06, + 0xa1, 0x20, 0x87, 0xc2, 0x68, 0x20, 0x21, 0x89, 0x36, 0x6e, 0x99, 0x4e, 0x56, 0x24, 0x67, 0x95, 0x7c, 0x6e, 0xde, + 0xc3, 0x20, 0x25, 0x14, 0xd9, 0xa8, 0xb4, 0x73, 0xe3, 0x46, 0x34, 0xf8, 0x99, 0x7c, 0xf6, 0xe5, 0xa2, 0x72, 0x2b, + 0x86, 0x36, 0x08, 0xaf, 0x02, 0x01, 0x18, 0x3d, 0x19, 0xa8, 0xef, 0xef, 0x33, 0x38, 0x88, 0x84, 0xfc, 0x2a, 0x82, + 0x57, 0xb4, 0xe6, 0x28, 0x05, 0xc0, 0xe6, 0xb0, 0x11, 0x8c, 0xae, 0xc2, 0xaa, 0x3b, 0xee, 0x46, 0xf4, 0x27, 0xd2, + 0xa9, 0x36, 0x5a, 0x37, 0x43, 0xba, 0x8d, 0x6d, 0x66, 0xb4, 0xc7, 0xc2, 0x6b, 0xfb, 0xba, 0x99, 0x90, 0x04, 0xca, + 0xc3, 0x19, 0xd1, 0x8f, 0x6c, 0x7d, 0xd3, 0xe3, 0x3f, 0x6e, 0x7f, 0xd8, 0x45, 0x51, 0x09, 0x8c, 0x10, 0x85, 0x24, + 0x50, 0xa9, 0x14, 0xc2, 0xa7, 0x23, 0x36, 0x2d, 0xf8, 0xbf, 0xce, 0x3a, 0x1e, 0x37, 0xd0, 0x8c, 0x2f, 0xff, 0xdc, + 0x86, 0x9d, 0xbd, 0x3d, 0x5f, 0x7b, 0xb7, 0xaa, 0x42, 0x93, 0x6b, 0xb1, 0x55, 0xf5, 0x3d, 0xe4, 0xfc, 0x30, 0xa8, + 0x64, 0x21, 0x57, 0x5f, 0x8b, 0xe0, 0xee, 0xaf, 0xa9, 0xf9, 0x08, 0xd3, 0x90, 0xc2, 0xbd, 0x7b, 0x11, 0x1a, 0x61, + 0x54, 0xbb, 0x6a, 0x02, 0x5b, 0x8a, 0x48, 0xe2, 0x62, 0xd5, 0x20, 0x20, 0x7a, 0x09, 0xa0, 0x92, 0x7b, 0xc1, 0x9b, + 0x8b, 0xb2, 0xb7, 0xa9, 0xb4, 0x46, 0xab, 0x8b, 0xf3, 0x30, 0x4f, 0xfc, 0xb5, 0xa7, 0xe1, 0x71, 0xab, 0x09, 0x61, + 0x09, 0xa5, 0x30, 0x78, 0x00, 0xbc, 0xfa, 0xa2, 0x63, 0xd3, 0x1d, 0x10, 0x08, 0xc5, 0x56, 0x2a, 0xdf, 0x6d, 0x52, + 0x85, 0xb9, 0x19, 0x09, 0x6f, 0x6a, 0x44, 0x78, 0xe3, 0xc2, 0xd8, 0x5f, 0x8e, 0x15, 0x82, 0x81, 0x00, 0x50, 0xe3, + 0x8f, 0xaf, 0xa5, 0xb2, 0xd6, 0xb2, 0x1b, 0x57, 0x55, 0xde, 0x43, 0xac, 0x38, 0xf0, 0xe0, 0xb7, 0x8c, 0x41, 0x73, + 0x8d, 0x5c, 0xd0, 0x29, 0xb7, 0xa2, 0xd7, 0xf5, 0x3d, 0xe6, 0xa4, 0x5d, 0x18, 0x5a, 0xc6, 0x08, 0x2a, 0xd4, 0xe6, + 0x27, 0x2a, 0x1d, 0xf9, 0xa0, 0xce, 0xb1, 0xd6, 0x1a, 0xe8, 0x9e, 0x3b, 0xeb, 0x0f, 0x93, 0x31, 0xf9, 0xe7, 0xe9, + 0xad, 0x37, 0xfd, 0x26, 0xe1, 0x85, 0x95, 0x4c, 0xa5, 0xea, 0xd2, 0x88, 0x2a, 0x88, 0xe1, 0x7a, 0x5e, 0x55, 0xfa, + 0x88, 0x58, 0xd5, 0x8f, 0xdd, 0x16, 0x0f, 0xa2, 0x3a, 0xe9, 0x96, 0x78, 0xd5, 0x8d, 0xa1, 0x0a, 0xf7, 0x9f, 0xf8, + 0x48, 0x46, 0x87, 0xb3, 0xa7, 0x1a, 0x29, 0x92, 0xba, 0x07, 0xea, 0xe4, 0x48, 0x86, 0xd1, 0x5a, 0x41, 0x89, 0x0a, + 0x66, 0xc2, 0x25, 0x46, 0x6c, 0x75, 0xff, 0x0f, 0x68, 0x84, 0x9f, 0x77, 0xfc, 0x93, 0xbf, 0x10, 0x9e, 0xb1, 0x61, + 0x49, 0x84, 0x4f, 0x47, 0x27, 0x03, 0x16, 0x77, 0x7e, 0x18, 0xf3, 0xde, 0x9d, 0xfb, 0x09, 0x5d, 0x92, 0xbd, 0x8e, + 0x14, 0xf7, 0x4e, 0x91, 0x42, 0xdc, 0xcb, 0xcb, 0x55, 0xa5, 0x92, 0x56, 0x5c, 0x79, 0xd1, 0x01, 0xc0, 0xbc, 0x0f, + 0xa4, 0x9c, 0xe5, 0x50, 0x24, 0x5e, 0xa9, 0x2a, 0x60, 0x9a, 0xa0, 0x9d, 0x15, 0xf0, 0x2b, 0x56, 0xf9, 0xac, 0x9a, + 0x5e, 0xb9, 0x04, 0x75, 0x7f, 0x6e, 0x52, 0xf2, 0xc5, 0xab, 0xe6, 0x8a, 0x11, 0x95, 0x6d, 0x85, 0xe3, 0xc5, 0xc6, + 0xe9, 0xc2, 0x90, 0xa8, 0x92, 0x2e, 0xfb, 0xbc, 0xee, 0x9b, 0xca, 0xce, 0xac, 0xb0, 0x4b, 0xb5, 0xaa, 0x6c, 0xac, + 0xa8, 0xff, 0x4b, 0x4e, 0x4b, 0x2c, 0x03, 0x11, 0x21, 0xd7, 0x65, 0x19, 0x28, 0xe3, 0xc0, 0x79, 0x2a, 0xdb, 0x11, + 0x1d, 0xd9, 0x67, 0x9d, 0x5f, 0x8b, 0xd9, 0xd4, 0xca, 0xf4, 0x89, 0xab, 0x0e, 0x39, 0xcf, 0x0e, 0x5a, 0x06, 0x93, + 0xc5, 0xe7, 0x68, 0x3c, 0xe0, 0x41, 0x28, 0x14, 0x55, 0x5c, 0xbb, 0x8e, 0x25, 0x2f, 0xfa, 0x0f, 0xe3, 0x41, 0x68, + 0xd4, 0x5f, 0x35, 0x0e, 0x35, 0xe4, 0xfa, 0x36, 0x39, 0x92, 0x93, 0x3c, 0x4b, 0xa2, 0x74, 0x43, 0x88, 0xf3, 0x67, + 0xe3, 0x83, 0x8b, 0x4f, 0x24, 0x0b, 0x91, 0xea, 0xe6, 0x63, 0x3d, 0xdf, 0x0b, 0x53, 0x45, 0xf7, 0x14, 0x0d, 0x45, + 0x5f, 0xd9, 0xaf, 0xef, 0xcc, 0xc9, 0xe2, 0x31, 0x99, 0xd4, 0x4d, 0xd1, 0xd1, 0x08, 0x3e, 0xeb, 0x20, 0xb4, 0xe0, + 0x68, 0xe6, 0xcd, 0xbd, 0xc7, 0x7c, 0x97, 0x5e, 0x8b, 0x0e, 0x0d, 0x3f, 0xa2, 0xdd, 0x50, 0xdf, 0x4e, 0xef, 0xb3, + 0x57, 0xb4, 0x05, 0x49, 0xcd, 0x8c, 0x2e, 0xc1, 0x02, 0xef, 0x67, 0xae, 0xf1, 0xfe, 0xb0, 0xa9, 0x51, 0xe5, 0xaf, + 0xbe, 0x34, 0x1d, 0x5a, 0x54, 0x15, 0x59, 0xaa, 0xcd, 0xca, 0xa1, 0x99, 0xe1, 0x2d, 0x5d, 0x71, 0x19}; + +// Backwards compatibility alias +#define INDEX_GZ INDEX_BR + +#endif // USE_WEBSERVER_GZIP + +} // namespace esphome::web_server #endif #endif diff --git a/esphome/components/web_server/server_index_v3.h b/esphome/components/web_server/server_index_v3.h index 725bdc34e3..3a8d466208 100644 --- a/esphome/components/web_server/server_index_v3.h +++ b/esphome/components/web_server/server_index_v3.h @@ -6,4058 +6,7586 @@ #include "esphome/core/hal.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { +#ifdef USE_WEBSERVER_GZIP const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcc, 0xbd, 0x6d, 0x7b, 0x1b, 0x37, 0xb2, 0x20, 0xfa, - 0xf9, 0xee, 0xaf, 0x90, 0xfa, 0x38, 0x4a, 0x43, 0x04, 0x5b, 0x24, 0x25, 0xca, 0x72, 0x53, 0x10, 0xd7, 0xaf, 0x63, - 0x27, 0x8e, 0xed, 0x58, 0xb6, 0x33, 0x0e, 0xc3, 0xe3, 0x80, 0x4d, 0x90, 0x84, 0xdd, 0x04, 0x98, 0x06, 0x68, 0x49, - 0x21, 0xf9, 0xdf, 0xef, 0x53, 0x78, 0xe9, 0x46, 0x93, 0xb4, 0x67, 0x66, 0xef, 0xee, 0x7d, 0xf6, 0xe4, 0x8c, 0xc5, - 0xc6, 0x3b, 0x0a, 0x85, 0x42, 0x55, 0xa1, 0xaa, 0x70, 0x79, 0x38, 0x96, 0x99, 0xbe, 0x5b, 0xb0, 0x83, 0x99, 0x9e, - 0xe7, 0x57, 0x97, 0xee, 0x5f, 0x46, 0xc7, 0x57, 0x97, 0x39, 0x17, 0x5f, 0x0e, 0x0a, 0x96, 0x13, 0x9e, 0x49, 0x71, - 0x30, 0x2b, 0xd8, 0x84, 0x8c, 0xa9, 0xa6, 0x29, 0x9f, 0xd3, 0x29, 0x3b, 0x38, 0xb9, 0xba, 0x9c, 0x33, 0x4d, 0x0f, - 0xb2, 0x19, 0x2d, 0x14, 0xd3, 0xe4, 0xfd, 0xbb, 0x67, 0xcd, 0x8b, 0xab, 0x4b, 0x95, 0x15, 0x7c, 0xa1, 0x0f, 0xa0, - 0x49, 0x32, 0x97, 0xe3, 0x65, 0xce, 0xae, 0x4e, 0x4e, 0x6e, 0x6e, 0x6e, 0x92, 0xcf, 0xea, 0x7f, 0x7c, 0xa5, 0xc5, - 0xc1, 0x3f, 0x0a, 0xf2, 0x7a, 0xf4, 0x99, 0x65, 0x3a, 0x19, 0xb3, 0x09, 0x17, 0xec, 0x4d, 0x21, 0x17, 0xac, 0xd0, - 0x77, 0x3d, 0xc8, 0xfc, 0xb5, 0x20, 0x31, 0xc7, 0x1a, 0x33, 0x44, 0xae, 0xf4, 0x01, 0x17, 0x07, 0xbc, 0xff, 0x8f, - 0xc2, 0xa4, 0xac, 0x98, 0x58, 0xce, 0x59, 0x41, 0x47, 0x39, 0x4b, 0x0f, 0x5b, 0x38, 0x93, 0x62, 0xc2, 0xa7, 0xcb, - 0xf2, 0xfb, 0xa6, 0xe0, 0xda, 0xff, 0xfe, 0x4a, 0xf3, 0x25, 0x4b, 0xd9, 0x06, 0xa5, 0x7c, 0xa0, 0x87, 0x84, 0x99, - 0x96, 0xbf, 0x54, 0x0d, 0xc7, 0xbf, 0x9a, 0x26, 0xef, 0x16, 0x4c, 0x4e, 0x0e, 0xf4, 0x21, 0x89, 0xd4, 0xdd, 0x7c, - 0x24, 0xf3, 0xa8, 0xaf, 0x1b, 0x51, 0x94, 0x42, 0x19, 0xcc, 0x50, 0x2f, 0x93, 0x42, 0xe9, 0x03, 0xc1, 0xc9, 0x0d, - 0x17, 0x63, 0x79, 0x83, 0x6f, 0x04, 0x11, 0x3c, 0xb9, 0x9e, 0xd1, 0xb1, 0xbc, 0x79, 0x2b, 0xa5, 0x3e, 0x3a, 0x8a, - 0xdd, 0xf7, 0xdd, 0xe3, 0xeb, 0x6b, 0x42, 0xc8, 0x57, 0xc9, 0xc7, 0x07, 0xad, 0xf5, 0x3a, 0x48, 0x4d, 0x04, 0xd5, - 0xfc, 0x2b, 0xb3, 0x95, 0xd0, 0xd1, 0x51, 0x44, 0xc7, 0x72, 0xa1, 0xd9, 0xf8, 0x5a, 0xdf, 0xe5, 0xec, 0x7a, 0xc6, - 0x98, 0x56, 0x11, 0x17, 0x07, 0x4f, 0x64, 0xb6, 0x9c, 0x33, 0xa1, 0x93, 0x45, 0x21, 0xb5, 0x84, 0x81, 0x1d, 0x1d, - 0x45, 0x05, 0x5b, 0xe4, 0x34, 0x63, 0x90, 0xff, 0xf8, 0xfa, 0xba, 0xaa, 0x51, 0x15, 0xc2, 0x5f, 0x04, 0xb9, 0x36, - 0x43, 0x8f, 0x11, 0xfe, 0x4d, 0x10, 0xc1, 0x6e, 0x0e, 0x7e, 0x63, 0xf4, 0xcb, 0x2f, 0x74, 0xd1, 0xcb, 0x72, 0xaa, - 0xd4, 0xc1, 0x2b, 0xb9, 0x32, 0xd3, 0x28, 0x96, 0x99, 0x96, 0x45, 0xac, 0x31, 0xc3, 0x02, 0xad, 0xf8, 0x24, 0xd6, - 0x33, 0xae, 0x92, 0x4f, 0xf7, 0x32, 0xa5, 0xde, 0x32, 0xb5, 0xcc, 0xf5, 0x3d, 0x72, 0xd8, 0xc2, 0xe2, 0x90, 0x90, - 0x2f, 0x02, 0xe9, 0x59, 0x21, 0x6f, 0x0e, 0x9e, 0x16, 0x85, 0x2c, 0xe2, 0xe8, 0xf1, 0xf5, 0xb5, 0x2d, 0x71, 0xc0, - 0xd5, 0x81, 0x90, 0xfa, 0xa0, 0x6c, 0x0f, 0xa0, 0x9d, 0x1c, 0xbc, 0x57, 0xec, 0xe0, 0xcf, 0xa5, 0x50, 0x74, 0xc2, - 0x1e, 0x5f, 0x5f, 0xff, 0x79, 0x20, 0x8b, 0x83, 0x3f, 0x33, 0xa5, 0xfe, 0x3c, 0xe0, 0x42, 0x69, 0x46, 0xc7, 0x49, - 0x84, 0x7a, 0xa6, 0xb3, 0x4c, 0xa9, 0x77, 0xec, 0x56, 0x13, 0x8d, 0xcd, 0xa7, 0x26, 0x6c, 0x33, 0x65, 0xfa, 0x40, - 0x95, 0xf3, 0x8a, 0xd1, 0x2a, 0x67, 0xfa, 0x40, 0x13, 0x93, 0x2f, 0x1d, 0xfc, 0x99, 0xfd, 0xd4, 0x3d, 0x3e, 0x89, - 0x6f, 0xc4, 0xd1, 0x91, 0x2e, 0x01, 0x8d, 0x56, 0x6e, 0x85, 0x08, 0x3b, 0xf4, 0x69, 0x47, 0x47, 0x2c, 0xc9, 0x99, - 0x98, 0xea, 0x19, 0x21, 0xa4, 0xdd, 0x13, 0x47, 0x47, 0xb1, 0x26, 0xbf, 0x89, 0x64, 0xca, 0x74, 0xcc, 0x10, 0xc2, - 0x55, 0xed, 0xa3, 0xa3, 0xd8, 0x02, 0x41, 0x12, 0x6d, 0x00, 0x57, 0x83, 0x31, 0x4a, 0x1c, 0xf4, 0xaf, 0xef, 0x44, - 0x16, 0x87, 0xe3, 0x47, 0x58, 0x1c, 0x1d, 0xfd, 0x26, 0x12, 0x05, 0x2d, 0x62, 0x8d, 0xd0, 0xa6, 0x60, 0x7a, 0x59, - 0x88, 0x03, 0xbd, 0xd1, 0xf2, 0x5a, 0x17, 0x5c, 0x4c, 0x63, 0xb4, 0xf2, 0x69, 0x41, 0xc5, 0xcd, 0xc6, 0x0e, 0xf7, - 0xf7, 0x82, 0x70, 0x72, 0x05, 0x3d, 0xbe, 0x92, 0xb1, 0xc3, 0x41, 0x4e, 0x48, 0xa4, 0x4c, 0xdd, 0xa8, 0xcf, 0x53, - 0xde, 0x88, 0x22, 0x6c, 0x47, 0x89, 0xbf, 0x08, 0x84, 0x85, 0x06, 0xd4, 0x4d, 0x92, 0x44, 0x23, 0x72, 0xb5, 0xf2, - 0x60, 0xe1, 0xc1, 0x44, 0xfb, 0x7c, 0xd0, 0x1a, 0xa6, 0x3a, 0x29, 0xd8, 0x78, 0x99, 0xb1, 0x38, 0x16, 0x58, 0x61, - 0x89, 0xc8, 0x95, 0x68, 0xc4, 0x05, 0xb9, 0x82, 0xf5, 0x2e, 0xea, 0x8b, 0x4d, 0xc8, 0x61, 0x0b, 0xb9, 0x41, 0x16, - 0x7e, 0x84, 0x00, 0x62, 0x37, 0xa0, 0x82, 0x90, 0x48, 0x2c, 0xe7, 0x23, 0x56, 0x44, 0x65, 0xb1, 0x5e, 0x0d, 0x2f, - 0x96, 0x8a, 0x1d, 0x64, 0x4a, 0x1d, 0x4c, 0x96, 0x22, 0xd3, 0x5c, 0x8a, 0x83, 0xa8, 0x51, 0x34, 0x22, 0x8b, 0x0f, - 0x25, 0x3a, 0x44, 0x68, 0x83, 0x62, 0x85, 0x1a, 0x7c, 0x20, 0x1b, 0xed, 0x21, 0x86, 0x51, 0xa2, 0x9e, 0x6b, 0xcf, - 0x41, 0x80, 0x61, 0x0e, 0x93, 0xdc, 0xe0, 0x9f, 0xec, 0xce, 0x87, 0x29, 0xde, 0x88, 0x3e, 0x4f, 0x76, 0x77, 0x0a, - 0xd1, 0xc9, 0x9c, 0x2e, 0x62, 0x46, 0xae, 0x98, 0xc1, 0x2e, 0x2a, 0x32, 0x18, 0x6b, 0x6d, 0xe1, 0xfa, 0x2c, 0x65, - 0x49, 0x85, 0x53, 0x28, 0xd5, 0xc9, 0x44, 0x16, 0x4f, 0x69, 0x36, 0x83, 0x7a, 0x25, 0xc6, 0x8c, 0xfd, 0x86, 0xcb, - 0x0a, 0x46, 0x35, 0x7b, 0x9a, 0x33, 0xf8, 0x8a, 0x23, 0x53, 0x33, 0x42, 0x58, 0xc1, 0x56, 0xcf, 0xb9, 0x7e, 0x25, - 0x45, 0xc6, 0x7a, 0x2a, 0xc0, 0x2f, 0xb3, 0xf2, 0x0f, 0xb5, 0x2e, 0xf8, 0x68, 0xa9, 0x59, 0x1c, 0x09, 0x28, 0x11, - 0x61, 0x85, 0xb0, 0x48, 0x34, 0xbb, 0xd5, 0x8f, 0xa5, 0xd0, 0x4c, 0x68, 0xc2, 0x3c, 0x54, 0x31, 0x4f, 0xe8, 0x62, - 0xc1, 0xc4, 0xf8, 0xf1, 0x8c, 0xe7, 0xe3, 0x58, 0xa0, 0x0d, 0xda, 0xe0, 0x8f, 0x82, 0xc0, 0x24, 0xc9, 0x15, 0x4f, - 0xe1, 0x9f, 0x6f, 0x4f, 0x27, 0xd6, 0xe4, 0xca, 0x6c, 0x0b, 0x46, 0xa2, 0xa8, 0x37, 0x91, 0x45, 0xec, 0xa6, 0x70, - 0x00, 0xa4, 0x0b, 0xfa, 0x78, 0xbb, 0xcc, 0x99, 0x42, 0xac, 0x41, 0x44, 0xb9, 0x8e, 0x0e, 0xc2, 0xbf, 0x17, 0x31, - 0x83, 0x05, 0xe0, 0x28, 0xe5, 0x86, 0x04, 0xbe, 0xe4, 0x6e, 0x53, 0x8d, 0x4b, 0xa2, 0xf6, 0x97, 0x20, 0x63, 0x9e, - 0xe8, 0x62, 0xa9, 0x34, 0x1b, 0xbf, 0xbb, 0x5b, 0x30, 0x85, 0x35, 0x25, 0x7f, 0x89, 0xfe, 0x5f, 0x22, 0x61, 0xf3, - 0x85, 0xbe, 0xbb, 0x36, 0xd4, 0x3c, 0x8d, 0x22, 0xfc, 0x4f, 0x53, 0xb4, 0x60, 0x34, 0x03, 0x92, 0xe6, 0x40, 0xf6, - 0x46, 0xe6, 0x77, 0x13, 0x9e, 0xe7, 0xd7, 0xcb, 0xc5, 0x42, 0x16, 0x1a, 0x6b, 0x41, 0x56, 0x5a, 0x56, 0xf0, 0x81, - 0x15, 0x5d, 0xa9, 0x1b, 0xae, 0xb3, 0x59, 0xac, 0xd1, 0x2a, 0xa3, 0x8a, 0x1d, 0x3c, 0x92, 0x32, 0x67, 0x54, 0xa4, - 0x9c, 0xf0, 0xbe, 0xa6, 0xa9, 0x58, 0xe6, 0x79, 0x6f, 0x54, 0x30, 0xfa, 0xa5, 0x67, 0xb2, 0xed, 0xe1, 0x90, 0x9a, - 0xdf, 0x0f, 0x8b, 0x82, 0xde, 0x41, 0x41, 0x42, 0xa0, 0x58, 0x9f, 0xa7, 0x3f, 0x5d, 0xbf, 0x7e, 0x95, 0xd8, 0xbd, - 0xc2, 0x27, 0x77, 0x31, 0x2f, 0xf7, 0x1f, 0xdf, 0xe0, 0x49, 0x21, 0xe7, 0x5b, 0x5d, 0x5b, 0xd0, 0xf1, 0xde, 0x37, - 0x86, 0xc0, 0x08, 0x3f, 0xb4, 0x4d, 0x87, 0x23, 0x78, 0x65, 0x30, 0x1f, 0x32, 0x89, 0xeb, 0x17, 0xfe, 0x49, 0x6d, - 0x72, 0xcc, 0xd1, 0xf7, 0x47, 0xab, 0x8b, 0xbb, 0x15, 0x23, 0x66, 0x9c, 0x0b, 0x38, 0x18, 0x61, 0x8c, 0x19, 0xd5, - 0xd9, 0x6c, 0xc5, 0x4c, 0x63, 0x1b, 0x3f, 0x62, 0xb6, 0xd9, 0xe0, 0xbf, 0xa5, 0xc7, 0x7a, 0x7d, 0x48, 0x08, 0x37, - 0xf4, 0x8a, 0xe8, 0xf5, 0x9a, 0x13, 0xc2, 0x11, 0x7e, 0xcb, 0xc9, 0x8a, 0xfa, 0x09, 0xc1, 0xc9, 0x06, 0xdb, 0x33, - 0xb5, 0x54, 0x06, 0x4e, 0xc0, 0xaf, 0xac, 0xd0, 0xac, 0x48, 0xb5, 0xc0, 0x05, 0x9b, 0xe4, 0x30, 0x8e, 0xc3, 0x36, - 0x9e, 0x51, 0xf5, 0x78, 0x46, 0xc5, 0x94, 0x8d, 0xd3, 0xbf, 0xe5, 0x06, 0x33, 0x41, 0xa2, 0x09, 0x17, 0x34, 0xe7, - 0x7f, 0xb3, 0x71, 0xe4, 0xce, 0x85, 0x0f, 0xfa, 0x80, 0xdd, 0x6a, 0x26, 0xc6, 0xea, 0xe0, 0xf9, 0xbb, 0x5f, 0x5e, - 0xba, 0xc5, 0xac, 0x9d, 0x15, 0x68, 0xa5, 0x96, 0x0b, 0x56, 0xc4, 0x08, 0xbb, 0xb3, 0xe2, 0x29, 0x37, 0x74, 0xf2, - 0x17, 0xba, 0xb0, 0x29, 0x5c, 0xbd, 0x5f, 0x8c, 0xa9, 0x66, 0x6f, 0x98, 0x18, 0x73, 0x31, 0x25, 0x87, 0x6d, 0x9b, - 0x3e, 0xa3, 0x2e, 0x63, 0x5c, 0x26, 0x7d, 0xba, 0xf7, 0x34, 0x37, 0x73, 0x2f, 0x3f, 0x97, 0x31, 0xda, 0x28, 0x4d, - 0x35, 0xcf, 0x0e, 0xe8, 0x78, 0xfc, 0x42, 0x70, 0xcd, 0xcd, 0x08, 0x0b, 0x58, 0x22, 0xc0, 0x55, 0x66, 0x4f, 0x0d, - 0x3f, 0xf2, 0x18, 0xe1, 0x38, 0x76, 0x67, 0xc1, 0x0c, 0xb9, 0x35, 0x3b, 0x3a, 0xaa, 0x28, 0x7f, 0x9f, 0xa5, 0x36, - 0x93, 0x0c, 0x86, 0x28, 0x59, 0x2c, 0x15, 0x2c, 0xb6, 0xef, 0x02, 0x0e, 0x1a, 0x39, 0x52, 0xac, 0xf8, 0xca, 0xc6, - 0x25, 0x82, 0xa8, 0x18, 0xad, 0xb6, 0xfa, 0x70, 0xdb, 0x43, 0x93, 0xc1, 0xb0, 0x17, 0x92, 0x70, 0xe6, 0x90, 0xdd, - 0x72, 0x2a, 0x9c, 0xa9, 0x92, 0xa8, 0xc4, 0x70, 0xa0, 0x96, 0x84, 0x45, 0x11, 0x3f, 0xbf, 0x45, 0x2c, 0x80, 0x87, - 0x08, 0x29, 0x87, 0x3f, 0x73, 0x9f, 0x7e, 0x35, 0x87, 0x87, 0xc2, 0x02, 0x61, 0x6d, 0x47, 0xaa, 0x10, 0xda, 0x20, - 0xac, 0xfd, 0x70, 0x2d, 0x51, 0xf2, 0x7c, 0x11, 0x9c, 0xda, 0xe4, 0x2d, 0x37, 0xc7, 0x36, 0xd0, 0x36, 0xaa, 0xd9, - 0xd1, 0x51, 0xcc, 0x92, 0x12, 0x31, 0xc8, 0x61, 0xdb, 0x2d, 0x52, 0x00, 0xad, 0x6f, 0x8c, 0x1b, 0x7a, 0x36, 0x0c, - 0xce, 0x21, 0x4b, 0x84, 0x7c, 0x98, 0x65, 0x4c, 0x29, 0x59, 0x1c, 0x1d, 0x1d, 0x9a, 0xf2, 0x25, 0x67, 0x01, 0x8b, - 0xf8, 0xfa, 0x46, 0x54, 0x43, 0x40, 0xd5, 0x69, 0xeb, 0xf9, 0x26, 0x52, 0xf1, 0x4d, 0x9e, 0x09, 0x49, 0xa3, 0x4f, - 0x9f, 0xa2, 0x86, 0xc6, 0x0e, 0x0e, 0x53, 0xe6, 0xbb, 0xbe, 0x7b, 0xc2, 0x2c, 0x5b, 0x68, 0x98, 0x90, 0x1d, 0xd0, - 0xec, 0xe5, 0x07, 0xe3, 0xfa, 0x90, 0xb0, 0xc6, 0x0a, 0x6d, 0x82, 0x15, 0xdd, 0xdb, 0xb4, 0xe1, 0x6f, 0xec, 0xd2, - 0xad, 0xa6, 0x86, 0xa7, 0x08, 0xd6, 0x71, 0xc0, 0x86, 0x1b, 0x6c, 0x60, 0xef, 0x67, 0x23, 0xcd, 0x40, 0x07, 0x7a, - 0xd8, 0x73, 0xf9, 0x44, 0x59, 0xc8, 0x15, 0xec, 0xaf, 0x25, 0x53, 0xda, 0x22, 0x72, 0xac, 0xb1, 0xc4, 0x70, 0x46, - 0x6d, 0x33, 0x9d, 0x35, 0x96, 0x74, 0xdf, 0xd8, 0x5e, 0x2f, 0xe0, 0x6c, 0x54, 0x80, 0xd4, 0xdf, 0xc7, 0x27, 0x18, - 0xab, 0x46, 0xeb, 0xf5, 0x5b, 0xee, 0x5b, 0xa9, 0xd6, 0xb2, 0xe4, 0xd7, 0xb6, 0x16, 0x85, 0x09, 0xe4, 0x0e, 0xe7, - 0xc3, 0xb6, 0x1b, 0xbf, 0x18, 0x92, 0xc3, 0x56, 0x89, 0xc5, 0x0e, 0xac, 0x76, 0x3c, 0x16, 0x8a, 0xaf, 0x6d, 0x53, - 0xc8, 0x9c, 0xf5, 0x35, 0x7c, 0x49, 0x66, 0x3b, 0xb8, 0x3a, 0x23, 0x03, 0xe0, 0x3a, 0x92, 0xd9, 0xf0, 0x5b, 0xf8, - 0xe4, 0x29, 0x42, 0xac, 0x77, 0xf3, 0x2a, 0xc2, 0xf1, 0xb5, 0x4e, 0x38, 0xb6, 0xa6, 0x11, 0x2d, 0xca, 0x2a, 0x51, - 0x89, 0x66, 0x6e, 0xab, 0x57, 0x59, 0x58, 0x98, 0xc1, 0x54, 0x53, 0x0a, 0x9a, 0x78, 0x45, 0xe7, 0x4c, 0xc5, 0x0c, - 0xe1, 0x6f, 0x15, 0xb0, 0xf8, 0x09, 0x45, 0x86, 0xc1, 0x19, 0xaa, 0xe0, 0x0c, 0x05, 0x76, 0x17, 0x98, 0xb4, 0xfa, - 0x96, 0x53, 0x98, 0x0d, 0xd4, 0xb0, 0xe2, 0xed, 0x82, 0xc9, 0x9b, 0xc3, 0xd9, 0x21, 0xb8, 0x87, 0x9f, 0x4d, 0xb3, - 0x40, 0x33, 0x2c, 0x84, 0x42, 0xf8, 0xb0, 0xb5, 0xbd, 0x92, 0xbe, 0x54, 0x35, 0xc7, 0xc1, 0x10, 0xd6, 0xc1, 0x1c, - 0x1b, 0x09, 0x57, 0xe6, 0x6f, 0x6d, 0xab, 0x01, 0xd8, 0xae, 0x01, 0x33, 0x92, 0x49, 0x4e, 0x75, 0xdc, 0x3e, 0x69, - 0x01, 0x63, 0xfa, 0x95, 0xc1, 0xa9, 0x82, 0xd0, 0xee, 0x54, 0x58, 0xb2, 0x14, 0x6a, 0xc6, 0x27, 0x3a, 0xfe, 0x28, - 0x0c, 0x51, 0x61, 0xb9, 0x62, 0x20, 0xe1, 0x04, 0xec, 0xb1, 0x21, 0x38, 0x1f, 0x05, 0xf4, 0xd3, 0x2b, 0x0f, 0x22, - 0x37, 0x52, 0x43, 0xb8, 0x80, 0x3c, 0x54, 0xac, 0x75, 0x45, 0x66, 0x4a, 0xc6, 0x0d, 0xb8, 0xc7, 0x76, 0xdf, 0xb6, - 0x98, 0x3a, 0x6a, 0x20, 0x02, 0x0e, 0x56, 0xa4, 0x21, 0x89, 0x70, 0x89, 0x3a, 0xd1, 0xf2, 0xa5, 0xbc, 0x61, 0xc5, - 0x63, 0x0a, 0x83, 0x4f, 0x6d, 0xf5, 0x8d, 0x3d, 0x0a, 0x0c, 0xc5, 0xd7, 0x3d, 0x8f, 0x2f, 0x9f, 0xcc, 0xc4, 0xdf, - 0x14, 0x72, 0xce, 0x15, 0x03, 0xbe, 0xcd, 0xc2, 0x5f, 0xc0, 0x46, 0x33, 0x3b, 0x12, 0x8e, 0x1b, 0x56, 0xe2, 0xd7, - 0xc3, 0x97, 0x75, 0xfc, 0xfa, 0x74, 0xef, 0xe9, 0xd4, 0x53, 0xc0, 0xfa, 0x3e, 0x46, 0x38, 0x76, 0xe2, 0x45, 0x70, - 0xd2, 0x25, 0x33, 0xe4, 0x8e, 0xf9, 0xf5, 0x5a, 0x07, 0x62, 0x5c, 0x8d, 0x73, 0x64, 0x76, 0xdb, 0xa0, 0x0d, 0x1d, - 0x8f, 0x81, 0xc5, 0x2b, 0x64, 0x9e, 0x07, 0x87, 0x15, 0x16, 0xbd, 0xf2, 0x78, 0xfa, 0x74, 0xef, 0xe9, 0xf5, 0xf7, - 0x4e, 0x28, 0xc8, 0x0f, 0x0f, 0x29, 0x3f, 0x50, 0x31, 0x66, 0x05, 0xc8, 0x95, 0xc1, 0x6a, 0xb9, 0x73, 0xf6, 0xb1, - 0x14, 0x82, 0x65, 0x9a, 0x8d, 0x41, 0x68, 0x11, 0x44, 0x27, 0x33, 0xa9, 0x74, 0x99, 0x58, 0x8d, 0x5e, 0x84, 0x42, - 0x68, 0x92, 0xd1, 0x3c, 0x8f, 0xad, 0x80, 0x32, 0x97, 0x5f, 0xd9, 0x9e, 0x51, 0xf7, 0x6a, 0x43, 0x2e, 0x9b, 0x61, - 0x41, 0x33, 0x2c, 0x51, 0x8b, 0x9c, 0x67, 0xac, 0x3c, 0xbc, 0xae, 0x13, 0x2e, 0xc6, 0xec, 0x16, 0xe8, 0x08, 0xba, - 0xba, 0xba, 0x6a, 0xe1, 0x36, 0xda, 0x58, 0x80, 0xaf, 0x76, 0x00, 0xfb, 0x9d, 0x63, 0xd3, 0x0a, 0xe2, 0xab, 0xbd, - 0x64, 0x0d, 0x05, 0x67, 0x25, 0xf7, 0x82, 0x96, 0x25, 0xcf, 0x08, 0x8f, 0x59, 0xce, 0x34, 0xf3, 0xe4, 0x1c, 0x98, - 0x69, 0xbb, 0x75, 0xdf, 0x96, 0xf0, 0x2b, 0xd1, 0xc9, 0xef, 0x32, 0xbf, 0xe6, 0xaa, 0x14, 0xdd, 0xab, 0xe5, 0xa9, - 0xa0, 0xdd, 0xd7, 0x76, 0x79, 0xa8, 0xd6, 0x34, 0x9b, 0x59, 0x89, 0x3d, 0xde, 0x99, 0x52, 0xd5, 0x86, 0x23, 0xed, - 0xe5, 0x26, 0xfa, 0xa9, 0x70, 0xc3, 0xdc, 0x07, 0x82, 0x6b, 0x47, 0x14, 0x18, 0x08, 0x81, 0x76, 0xd9, 0x1e, 0xd3, - 0x3c, 0x1f, 0xd1, 0xec, 0x4b, 0x1d, 0xfb, 0x2b, 0x34, 0x20, 0xdb, 0xd4, 0x38, 0xc8, 0x0a, 0x48, 0x56, 0x38, 0x6f, - 0x4f, 0xa5, 0x6b, 0x1b, 0x25, 0x3e, 0x6c, 0x55, 0x68, 0x5f, 0x5f, 0xe8, 0x6f, 0x62, 0xbb, 0x19, 0x91, 0x70, 0x33, - 0x8b, 0x81, 0x0a, 0xfc, 0x4b, 0x8c, 0xf3, 0xf4, 0xc0, 0xe1, 0x1d, 0x08, 0x1e, 0x9b, 0xad, 0x81, 0x68, 0xb4, 0xda, - 0x8c, 0xb9, 0xfa, 0x36, 0x04, 0xfe, 0xb7, 0x8c, 0xf2, 0x49, 0xd0, 0xc3, 0xbf, 0x3b, 0xd0, 0x92, 0xc6, 0x39, 0xc6, - 0xb9, 0x1c, 0x99, 0x63, 0x28, 0x3c, 0xa1, 0xf9, 0x19, 0x98, 0x17, 0x83, 0xef, 0xaf, 0x6d, 0x96, 0xe1, 0xcb, 0x60, - 0x18, 0xaa, 0x17, 0x32, 0x14, 0x35, 0x14, 0x70, 0x44, 0x55, 0x98, 0x33, 0x57, 0xd6, 0x44, 0x49, 0xc7, 0xb5, 0x5b, - 0x71, 0xdc, 0xd1, 0xdc, 0x82, 0xc4, 0x71, 0xac, 0x40, 0x9a, 0xf3, 0xfc, 0x7d, 0x35, 0x0b, 0xb5, 0x33, 0x0b, 0x95, - 0x04, 0xd2, 0x16, 0xaa, 0x90, 0x39, 0xa8, 0x9e, 0x6a, 0x81, 0xc2, 0x52, 0xc0, 0xb2, 0x26, 0x40, 0xa1, 0x51, 0x49, - 0x70, 0x73, 0xa2, 0x71, 0xe1, 0x44, 0x1d, 0x87, 0x6b, 0x40, 0x32, 0xaa, 0x2a, 0x12, 0xd9, 0xcd, 0x51, 0x93, 0x7d, - 0x25, 0x2e, 0xd0, 0x16, 0x7f, 0xbf, 0xd9, 0x38, 0x28, 0x31, 0xe4, 0x56, 0xa7, 0xc6, 0x18, 0x07, 0x60, 0xc1, 0x92, - 0x38, 0x66, 0xd8, 0xb2, 0x3e, 0xdb, 0xc0, 0x29, 0xdb, 0x3d, 0x24, 0x44, 0x56, 0xb0, 0xa9, 0x31, 0x95, 0x9e, 0xbb, - 0x92, 0x08, 0x53, 0xcf, 0x96, 0x16, 0xd5, 0xc4, 0x09, 0x89, 0xbc, 0x76, 0x22, 0xea, 0xaf, 0x6a, 0xc2, 0x61, 0x1a, - 0x14, 0xdb, 0xa4, 0x40, 0x54, 0x8b, 0x7d, 0xf0, 0xde, 0x87, 0x35, 0xb5, 0x76, 0x02, 0x88, 0x17, 0x35, 0x88, 0x07, - 0xa0, 0x95, 0x96, 0x78, 0xc9, 0x21, 0xa1, 0xf5, 0xca, 0x31, 0xc3, 0x85, 0x5d, 0x88, 0x1d, 0x28, 0x6e, 0xb3, 0x9f, - 0x06, 0x0b, 0x41, 0x96, 0x55, 0xc0, 0xdf, 0x85, 0x47, 0x44, 0x0c, 0x83, 0x17, 0xeb, 0xf5, 0x0e, 0xda, 0xed, 0xe5, - 0x42, 0x51, 0x52, 0x49, 0x87, 0xeb, 0xf5, 0xdf, 0x12, 0xc5, 0x8e, 0xff, 0xc5, 0x0c, 0xf5, 0x3d, 0xd1, 0x7d, 0xf8, - 0x12, 0x4a, 0x19, 0x76, 0xb4, 0x4a, 0x29, 0x05, 0x87, 0x3a, 0xd6, 0xd6, 0x17, 0x4a, 0x07, 0x94, 0xfb, 0xf1, 0x0e, - 0x01, 0x33, 0x89, 0xee, 0xa4, 0xae, 0xa6, 0xfc, 0xd8, 0x35, 0x2d, 0x10, 0x42, 0xa9, 0x32, 0xb2, 0xcc, 0xe1, 0x3e, - 0xf9, 0xf2, 0xe8, 0x48, 0x05, 0x0d, 0x7d, 0x2a, 0x29, 0xc5, 0xe7, 0x18, 0x4e, 0x65, 0x75, 0x27, 0x0c, 0xfb, 0xf2, - 0xd9, 0x9f, 0x43, 0x3b, 0xd2, 0x69, 0xab, 0x07, 0x82, 0x39, 0xbd, 0xa1, 0x5c, 0x1f, 0x94, 0xad, 0x58, 0xc1, 0x3c, - 0x66, 0x68, 0xe5, 0xb8, 0x8d, 0xa4, 0x60, 0xc0, 0x3f, 0x02, 0x59, 0xf0, 0x5c, 0xb4, 0x45, 0xfc, 0x6c, 0xc6, 0x40, - 0x95, 0xed, 0x19, 0x89, 0x52, 0x3c, 0x3c, 0x74, 0x07, 0x89, 0x6b, 0x78, 0xff, 0xd8, 0x37, 0xdb, 0xd5, 0x6b, 0xd2, - 0xc0, 0x82, 0x15, 0x13, 0x59, 0xcc, 0x7d, 0xde, 0x66, 0xeb, 0xdb, 0x11, 0x47, 0x3e, 0x89, 0xf7, 0xb6, 0xed, 0x44, - 0x80, 0xde, 0x96, 0xec, 0x5d, 0x49, 0xed, 0xb5, 0xd3, 0xb4, 0x3c, 0x80, 0xad, 0x82, 0xd0, 0x63, 0xa6, 0x0a, 0xa5, - 0x7c, 0xa7, 0x5e, 0xed, 0x59, 0xdd, 0xc9, 0x61, 0xbb, 0x57, 0x4a, 0x7e, 0x1e, 0x1b, 0x7a, 0x56, 0xc7, 0xe1, 0x4e, - 0x55, 0xb9, 0xcc, 0xc7, 0x6e, 0xb0, 0x02, 0x61, 0xe6, 0xf0, 0xe8, 0x86, 0xe7, 0x79, 0x95, 0xfa, 0x9f, 0x90, 0x76, - 0xe5, 0x48, 0xbb, 0xf4, 0xa4, 0x1d, 0x48, 0x05, 0x90, 0x76, 0xdb, 0x5c, 0x55, 0x5d, 0xee, 0x6c, 0x4f, 0x69, 0x89, - 0xba, 0x32, 0xe2, 0x34, 0xf4, 0xb7, 0xf4, 0x23, 0x40, 0x25, 0xf3, 0xf5, 0x25, 0x76, 0xfa, 0x18, 0x10, 0x03, 0xad, - 0x4e, 0x93, 0x85, 0x9a, 0x8a, 0x2f, 0x31, 0xc2, 0x6a, 0xc3, 0x4a, 0xcc, 0x7e, 0xf8, 0x14, 0x94, 0x76, 0xc1, 0x74, - 0xe0, 0x1c, 0x33, 0xc9, 0xff, 0x11, 0x1f, 0xe5, 0x67, 0x27, 0xdc, 0xec, 0x94, 0x9f, 0x1d, 0xd0, 0xfa, 0x6a, 0x76, - 0xe3, 0xef, 0x53, 0x7b, 0x33, 0x3d, 0x51, 0x4e, 0xaf, 0x5a, 0xef, 0xf5, 0x3a, 0xde, 0x4a, 0x01, 0x8d, 0xbe, 0x93, - 0x52, 0x8a, 0xb2, 0x75, 0xa0, 0x01, 0x21, 0x64, 0x20, 0x61, 0x63, 0x27, 0x5d, 0x9e, 0x72, 0x2f, 0xff, 0x95, 0x9e, - 0xc7, 0x28, 0xee, 0x6d, 0xfd, 0xc7, 0x72, 0xbe, 0x00, 0x86, 0x6c, 0x0b, 0xa5, 0xa7, 0xcc, 0x75, 0x58, 0xe5, 0x6f, - 0xf6, 0xa4, 0xd5, 0xea, 0x98, 0xfd, 0x58, 0xc3, 0xa6, 0x52, 0x6a, 0x3e, 0x6c, 0x6d, 0x96, 0x65, 0x52, 0x49, 0x38, - 0xf6, 0xe9, 0x56, 0x1e, 0x6f, 0x6b, 0x66, 0x7c, 0xc6, 0xeb, 0x58, 0x58, 0x3a, 0x2c, 0x80, 0xd6, 0x05, 0xe4, 0xc7, - 0xa3, 0x7b, 0xb8, 0xfe, 0x9b, 0x0a, 0x38, 0xab, 0xcd, 0x16, 0xf8, 0x56, 0x9b, 0xcd, 0x07, 0xed, 0x24, 0x6d, 0xfc, - 0x61, 0x8f, 0xdc, 0x5b, 0x42, 0xaf, 0xca, 0x74, 0x32, 0xe3, 0x60, 0x08, 0x69, 0x3b, 0x2c, 0x24, 0x59, 0xcd, 0xe5, - 0x98, 0xa5, 0x91, 0x5c, 0x30, 0x11, 0x6d, 0x40, 0xcf, 0xea, 0x10, 0xe0, 0x9f, 0x22, 0x5e, 0xbd, 0xad, 0xeb, 0x5b, - 0xd3, 0x0f, 0x7a, 0x03, 0xaa, 0xb0, 0x97, 0x7c, 0x8f, 0x32, 0xf6, 0x03, 0x2b, 0x94, 0xe1, 0x49, 0x4b, 0xf6, 0xf6, - 0x25, 0xaf, 0x0e, 0xa8, 0x97, 0x3c, 0xfd, 0x76, 0x95, 0x4a, 0x20, 0x89, 0xda, 0xc9, 0x79, 0x72, 0x1a, 0x21, 0xa3, - 0x31, 0x7e, 0xe6, 0x35, 0xc6, 0xcb, 0x52, 0x63, 0xfc, 0x5c, 0x93, 0xe5, 0x96, 0xc6, 0xf8, 0x67, 0x41, 0x9e, 0xeb, - 0xfe, 0x73, 0xaf, 0x4d, 0x7f, 0x23, 0x73, 0x9e, 0xdd, 0xc5, 0x51, 0xce, 0x75, 0x13, 0x6e, 0x13, 0x23, 0xbc, 0xb2, - 0x19, 0xa0, 0x6a, 0x34, 0xfa, 0xee, 0x8d, 0x97, 0xff, 0xb0, 0x10, 0x24, 0xba, 0x97, 0x73, 0x7d, 0x2f, 0xc2, 0x33, - 0x4d, 0xfe, 0x84, 0x5f, 0xf7, 0x56, 0xf1, 0x2f, 0x54, 0xcf, 0x92, 0x82, 0x8a, 0xb1, 0x9c, 0xc7, 0xa8, 0x11, 0x45, - 0x28, 0x51, 0x46, 0x08, 0x79, 0x80, 0x36, 0xf7, 0xfe, 0xc4, 0x9f, 0x25, 0x89, 0xfa, 0x51, 0x63, 0xa6, 0x31, 0xa3, - 0xe4, 0xcf, 0xcb, 0x7b, 0xab, 0xcf, 0x72, 0x73, 0xf5, 0x27, 0x7e, 0xaa, 0x4b, 0xb5, 0x3e, 0xbe, 0x65, 0x24, 0x46, - 0xe4, 0xea, 0xa9, 0x1f, 0xd2, 0x63, 0x39, 0xb7, 0x0a, 0xfe, 0x08, 0xe1, 0xaf, 0xa0, 0xd7, 0xbd, 0xe2, 0x15, 0x11, - 0x72, 0x77, 0x30, 0x87, 0x24, 0x92, 0x46, 0x79, 0x10, 0x1d, 0x1d, 0x05, 0x69, 0x25, 0x0b, 0x81, 0x1f, 0x49, 0x52, - 0x13, 0xd5, 0x31, 0xa7, 0xd0, 0xd2, 0x23, 0x19, 0x73, 0xe4, 0x9b, 0x89, 0xbd, 0xa6, 0xda, 0xed, 0x58, 0x3e, 0xb0, - 0xba, 0x87, 0x84, 0x6b, 0x56, 0x50, 0x2d, 0x8b, 0x21, 0x0a, 0xd9, 0x12, 0xfc, 0x8a, 0x93, 0x3f, 0x07, 0x07, 0xff, - 0xcf, 0xff, 0xf8, 0x63, 0xf2, 0x47, 0x31, 0xfc, 0x13, 0x0b, 0x46, 0x4e, 0x2e, 0xe3, 0x7e, 0x1a, 0x1f, 0x36, 0x9b, - 0xeb, 0x3f, 0x4e, 0x06, 0xff, 0x4d, 0x9b, 0x7f, 0x3f, 0x6c, 0xfe, 0x3e, 0x44, 0xeb, 0xf8, 0x8f, 0x93, 0xfe, 0xc0, - 0x7d, 0x0d, 0xfe, 0xfb, 0xea, 0x0f, 0x35, 0x3c, 0xb6, 0x89, 0xf7, 0x10, 0x3a, 0x99, 0xe2, 0x7f, 0x08, 0x72, 0xd2, - 0x6c, 0x5e, 0x9d, 0x4c, 0xf1, 0xaf, 0x82, 0x9c, 0xc0, 0xdf, 0x3b, 0x4d, 0xde, 0xb2, 0xe9, 0xd3, 0xdb, 0x45, 0xfc, - 0xe7, 0xd5, 0xfa, 0xde, 0xea, 0x15, 0xdf, 0x40, 0xbb, 0x83, 0xff, 0xfe, 0xe3, 0x0f, 0x15, 0xfd, 0x78, 0x45, 0x4e, - 0x86, 0x0d, 0x14, 0x9b, 0xe4, 0x63, 0x62, 0xff, 0xc4, 0xfd, 0x74, 0xf0, 0xdf, 0x6e, 0x28, 0xd1, 0x8f, 0x7f, 0xfc, - 0x79, 0x79, 0x45, 0x86, 0xeb, 0x38, 0x5a, 0xff, 0x88, 0xd6, 0x08, 0xad, 0xef, 0xa1, 0x3f, 0x71, 0x34, 0x8d, 0x10, - 0xfe, 0x5d, 0x90, 0x93, 0x1f, 0x4f, 0xa6, 0xf8, 0x27, 0x41, 0x4e, 0xa2, 0x93, 0x29, 0xfe, 0x20, 0xc9, 0xc9, 0x7f, - 0xc7, 0xfd, 0xd4, 0x2a, 0xe1, 0xd6, 0x46, 0xfd, 0xb1, 0x86, 0x9b, 0x10, 0x5a, 0x30, 0xba, 0xd6, 0x5c, 0xe7, 0x0c, - 0xdd, 0x3b, 0xe1, 0xf8, 0xb9, 0x04, 0x60, 0xc5, 0x1a, 0x94, 0x34, 0xe6, 0x12, 0x76, 0xf5, 0x09, 0x16, 0x1e, 0x30, - 0xe8, 0x5e, 0xca, 0xb1, 0xd5, 0x13, 0xa8, 0x54, 0xdb, 0xdb, 0x5b, 0x05, 0xd7, 0xb7, 0xf8, 0x31, 0x79, 0x2e, 0xe3, - 0x36, 0xc2, 0x82, 0xc2, 0x8f, 0x0e, 0xc2, 0xef, 0xb5, 0xbb, 0xf0, 0x84, 0x6d, 0x6e, 0x31, 0x4c, 0x48, 0xcb, 0xcf, - 0x44, 0x08, 0xbf, 0xdc, 0x93, 0xa9, 0x67, 0xa0, 0x7e, 0x40, 0x58, 0xab, 0xf0, 0x7a, 0x14, 0x3f, 0xd6, 0xa4, 0x44, - 0x8e, 0x77, 0x05, 0x63, 0xbf, 0xd1, 0xfc, 0x0b, 0x2b, 0xe2, 0xa7, 0x1a, 0xb7, 0x3b, 0x0f, 0xb0, 0x51, 0x55, 0x1f, - 0xb6, 0x51, 0xaf, 0xbc, 0xdd, 0x7a, 0x2f, 0xed, 0x7d, 0x02, 0x9c, 0xc2, 0x75, 0x7d, 0x0d, 0xac, 0xfd, 0x21, 0xdf, - 0x51, 0x6a, 0x15, 0xf4, 0x26, 0x42, 0xf5, 0xab, 0x54, 0x2e, 0xbe, 0xd2, 0x9c, 0x8f, 0x0f, 0x34, 0x9b, 0x2f, 0x72, - 0xaa, 0xd9, 0x81, 0x9b, 0xf3, 0x01, 0x85, 0x86, 0xa2, 0x92, 0xa7, 0xf8, 0x59, 0x54, 0x9b, 0xf6, 0x67, 0x91, 0x54, - 0x7b, 0x27, 0x86, 0xfb, 0x2c, 0xc7, 0x97, 0x28, 0x5a, 0x5e, 0x97, 0x6d, 0xdf, 0x08, 0x36, 0xdb, 0xa0, 0x2c, 0x1b, - 0x9a, 0xf3, 0x5b, 0x61, 0xb8, 0xdf, 0x24, 0xa4, 0xd3, 0x8f, 0x2e, 0xd5, 0xd7, 0xe9, 0x55, 0x04, 0x37, 0x39, 0x05, - 0x11, 0xcc, 0x28, 0x8f, 0xa0, 0x04, 0x25, 0xad, 0x1e, 0xbd, 0x64, 0x3d, 0xda, 0x68, 0x78, 0x36, 0x3b, 0x23, 0x7c, - 0x40, 0x6d, 0xfd, 0x1c, 0xcf, 0xf0, 0x98, 0x34, 0xdb, 0x78, 0x49, 0x5a, 0xa6, 0x4a, 0x6f, 0x79, 0x99, 0xb9, 0x7e, - 0x8e, 0x8e, 0xe2, 0x22, 0xc9, 0xa9, 0xd2, 0x2f, 0x40, 0x23, 0x40, 0x96, 0x78, 0x46, 0x8a, 0x84, 0xdd, 0xb2, 0x2c, - 0xce, 0x10, 0x9e, 0x39, 0x1a, 0x84, 0x7a, 0x68, 0x49, 0x82, 0x62, 0x20, 0x67, 0x10, 0xc1, 0xfa, 0xb3, 0x41, 0x7b, - 0x48, 0x08, 0x89, 0x0e, 0x9b, 0xcd, 0xa8, 0x5f, 0x90, 0x7f, 0x88, 0x14, 0x52, 0x02, 0x76, 0x9a, 0xfc, 0x0a, 0x49, - 0x9d, 0x20, 0x29, 0xfe, 0x20, 0x13, 0xcd, 0x94, 0x8e, 0x21, 0x19, 0x94, 0x04, 0xca, 0x63, 0x78, 0x74, 0x79, 0x12, - 0x35, 0x20, 0xd5, 0xa0, 0x28, 0xc2, 0x05, 0xb9, 0xd3, 0x28, 0x9d, 0x0d, 0x4e, 0x87, 0xe1, 0x19, 0x61, 0x53, 0xa1, - 0xff, 0x3b, 0xdd, 0x9f, 0x0d, 0x5a, 0xa6, 0xff, 0xab, 0xa8, 0x1f, 0x17, 0x44, 0x59, 0x36, 0xae, 0xaf, 0x52, 0xc1, - 0xcc, 0x7c, 0x51, 0xea, 0x06, 0xe8, 0xfa, 0x1e, 0x93, 0x66, 0x27, 0x8d, 0xc7, 0xe1, 0x4c, 0x9a, 0xd0, 0xa1, 0x03, - 0x05, 0xce, 0x09, 0x94, 0xc7, 0x05, 0x81, 0x4e, 0xab, 0x6a, 0x77, 0x3a, 0x75, 0x09, 0x3f, 0x46, 0x3f, 0xf6, 0x7f, - 0x12, 0xe9, 0xef, 0xc2, 0x8e, 0xe0, 0x27, 0xb1, 0x5e, 0xc3, 0xdf, 0xdf, 0x45, 0x1f, 0x86, 0x65, 0xd2, 0xfe, 0xe1, - 0xd2, 0x7e, 0x85, 0x34, 0xc1, 0x52, 0x33, 0x60, 0xac, 0x4a, 0x7e, 0xcc, 0x2e, 0xce, 0x84, 0xd8, 0x19, 0x1c, 0x1d, - 0xf1, 0x01, 0x6d, 0xb4, 0x87, 0x70, 0x23, 0x50, 0x68, 0xf5, 0x1b, 0xd7, 0xb3, 0x38, 0x3a, 0xb9, 0x8a, 0x50, 0x3f, - 0x3a, 0x80, 0x55, 0xee, 0xc9, 0x06, 0x71, 0xb0, 0xce, 0x1a, 0x8c, 0xa6, 0xe3, 0x2b, 0xd2, 0xea, 0xc7, 0xc2, 0x12, - 0xf9, 0x1c, 0xe1, 0xcc, 0xd1, 0xd4, 0x16, 0x1e, 0xa3, 0x86, 0x10, 0x0d, 0xff, 0x3d, 0x46, 0x8d, 0x99, 0x6e, 0x4c, - 0x50, 0x9a, 0xc1, 0xdf, 0x78, 0x4c, 0x08, 0x69, 0x76, 0xca, 0x8a, 0xfe, 0xb0, 0xa4, 0x28, 0x9d, 0x78, 0xf5, 0xe8, - 0xc0, 0x6c, 0x0e, 0xd9, 0x88, 0xf9, 0x80, 0x0d, 0xd7, 0xeb, 0xe8, 0xb2, 0x7f, 0x15, 0xa1, 0x46, 0xec, 0xd1, 0xee, - 0xc4, 0xe3, 0x1d, 0x42, 0x58, 0x0c, 0x37, 0xee, 0x06, 0xea, 0x86, 0xd5, 0x6e, 0x9b, 0x56, 0xd5, 0xfe, 0x0f, 0xc8, - 0x02, 0xdb, 0x94, 0x72, 0x8f, 0xe5, 0x6f, 0x17, 0x30, 0x55, 0x8f, 0xdb, 0x92, 0xb4, 0x70, 0x41, 0xbc, 0xba, 0x9b, - 0x12, 0x5d, 0xe1, 0x7f, 0x46, 0xaa, 0xe2, 0x78, 0x90, 0xe3, 0xd9, 0x90, 0x28, 0x6a, 0xe4, 0x97, 0x9e, 0x57, 0xa6, - 0xb3, 0x9c, 0xdc, 0xb0, 0xad, 0xfb, 0xdf, 0x1c, 0xee, 0x64, 0x1e, 0xeb, 0x24, 0x5b, 0x16, 0x05, 0x13, 0xfa, 0x95, - 0x1c, 0x3b, 0xc6, 0x8e, 0xe5, 0x20, 0x5b, 0xc1, 0xc5, 0x2e, 0x06, 0xae, 0xae, 0xe3, 0x77, 0xca, 0x78, 0x27, 0x7b, - 0x49, 0xc6, 0x96, 0xe1, 0x32, 0xd7, 0xbd, 0xbd, 0xa5, 0x13, 0xa5, 0x63, 0x84, 0xc7, 0xee, 0x1e, 0x38, 0x4e, 0x92, - 0x64, 0x99, 0x64, 0x90, 0x0d, 0x1d, 0x28, 0xb4, 0x31, 0xfb, 0x2a, 0x56, 0xe4, 0xb1, 0x4e, 0x04, 0xbb, 0x35, 0xdd, - 0xc6, 0xa8, 0x3a, 0xc4, 0xfd, 0x7e, 0xbb, 0xa4, 0x3d, 0x43, 0x80, 0x54, 0x22, 0xe4, 0x98, 0x01, 0x84, 0xe0, 0xee, - 0xdf, 0x25, 0xcd, 0xa8, 0x0a, 0x6f, 0xb6, 0xaa, 0x01, 0x0e, 0x42, 0x95, 0xf7, 0x12, 0xf4, 0xc4, 0x86, 0x3d, 0x2b, - 0x0b, 0x5b, 0xe5, 0x39, 0x42, 0x7c, 0x12, 0x2f, 0x13, 0xb8, 0x11, 0x34, 0x98, 0x24, 0x04, 0x5a, 0xaf, 0x97, 0x21, - 0x6e, 0xcd, 0x2a, 0xc5, 0xf4, 0x84, 0xcc, 0x06, 0x45, 0xa3, 0x61, 0x94, 0xd7, 0x63, 0x8b, 0x17, 0x4b, 0x84, 0x27, - 0xe5, 0x5e, 0xf3, 0xe5, 0x16, 0xa4, 0xde, 0x55, 0x3c, 0xa9, 0x2b, 0x81, 0x1b, 0x42, 0x20, 0xa3, 0x5f, 0xd4, 0xd0, - 0x3a, 0x9e, 0x92, 0x93, 0x78, 0x90, 0xf4, 0xff, 0xe7, 0x10, 0xf5, 0xe3, 0xe4, 0x18, 0x9d, 0x58, 0x5a, 0x32, 0x41, - 0xbd, 0xcc, 0xf6, 0xb1, 0x32, 0xb7, 0x9f, 0x6d, 0x6c, 0x14, 0x90, 0xa9, 0xc4, 0x82, 0xce, 0x59, 0x3a, 0x85, 0x5d, - 0xef, 0x91, 0x67, 0x81, 0x01, 0x99, 0xd2, 0xa9, 0xa3, 0x2d, 0x49, 0xd4, 0x2f, 0x68, 0xf9, 0xd5, 0x8f, 0xfa, 0x59, - 0xf5, 0xf5, 0x3f, 0xa3, 0x7e, 0x4e, 0xd3, 0xc7, 0x7c, 0xe3, 0x94, 0xe4, 0xb5, 0x3e, 0xce, 0x7d, 0x1f, 0x1b, 0xbb, - 0x38, 0x01, 0xf0, 0xc6, 0x68, 0x57, 0x3b, 0xb2, 0x44, 0x1b, 0x3e, 0x29, 0xa9, 0x93, 0x4a, 0x34, 0x9d, 0x02, 0x54, - 0x83, 0x45, 0x50, 0xa1, 0x6d, 0x40, 0x30, 0x65, 0xc0, 0x16, 0x8f, 0xb4, 0x00, 0xcd, 0xe5, 0x55, 0x0b, 0xad, 0x6a, - 0x85, 0x1d, 0x67, 0x55, 0xbf, 0x8b, 0x2f, 0x89, 0xf7, 0x04, 0xa8, 0xf2, 0xe5, 0xb2, 0x37, 0x69, 0x34, 0x90, 0xf2, - 0xf8, 0x35, 0x1e, 0x4c, 0x86, 0xf8, 0x16, 0x50, 0x08, 0xd7, 0x30, 0x0a, 0xd7, 0xe6, 0xd8, 0x71, 0x73, 0x6c, 0x34, - 0xe4, 0x06, 0xf5, 0x82, 0xca, 0x4b, 0x57, 0x79, 0xb3, 0xb1, 0x90, 0xd9, 0xc6, 0xb8, 0x0b, 0x64, 0x52, 0xc0, 0x10, - 0x8c, 0x10, 0xf2, 0x59, 0xa2, 0xbd, 0xcd, 0x42, 0xa3, 0x50, 0xdd, 0xec, 0x5e, 0xa0, 0xa8, 0xf6, 0xf4, 0x88, 0x01, - 0x16, 0x50, 0xb5, 0x54, 0x23, 0xcf, 0x34, 0x1e, 0x37, 0xda, 0x06, 0xdd, 0x9b, 0xed, 0x5e, 0xbd, 0xb1, 0xfb, 0x55, - 0x63, 0x78, 0xdc, 0x20, 0xb3, 0x6a, 0x87, 0x6f, 0x64, 0xa3, 0xb1, 0xa9, 0xdf, 0x97, 0xfa, 0x4d, 0x5c, 0xbb, 0xbf, - 0x78, 0xba, 0x63, 0xe2, 0xe1, 0x4f, 0xdf, 0xea, 0xbc, 0x15, 0x09, 0x17, 0x82, 0x15, 0x70, 0xc2, 0x12, 0x8d, 0xc5, - 0x66, 0x53, 0x9e, 0xfa, 0xbf, 0x69, 0x6b, 0x33, 0x46, 0x38, 0xd0, 0x21, 0x23, 0xb5, 0x61, 0x89, 0x0b, 0x4c, 0x0d, - 0x15, 0x21, 0x84, 0xbc, 0xd7, 0xde, 0x3c, 0x46, 0x1b, 0x92, 0x94, 0x91, 0xe0, 0xec, 0x8e, 0x15, 0x61, 0xc9, 0xa7, - 0x7b, 0x8f, 0xe5, 0x77, 0x45, 0xba, 0x81, 0x18, 0xa6, 0xa6, 0x58, 0xee, 0x08, 0x59, 0x4e, 0xbe, 0x82, 0x9c, 0x53, - 0x5e, 0xb0, 0x24, 0x86, 0x20, 0x3e, 0xe1, 0x05, 0x33, 0x8c, 0xfb, 0x3d, 0x2f, 0x37, 0x66, 0x75, 0x4e, 0x33, 0x0b, - 0xb5, 0x3f, 0x00, 0xcd, 0x1c, 0x94, 0x43, 0x92, 0xec, 0x14, 0xfb, 0x74, 0xef, 0xe1, 0xeb, 0x7d, 0x32, 0xf4, 0x7a, - 0xed, 0xa4, 0xe7, 0x0c, 0x58, 0x1f, 0x9c, 0x57, 0x43, 0xcd, 0xdc, 0x8f, 0x34, 0xce, 0x0c, 0x13, 0x95, 0xc7, 0x1c, - 0x90, 0xe9, 0xd3, 0xbd, 0x87, 0xef, 0x62, 0x6e, 0x74, 0x53, 0x08, 0x87, 0xf3, 0x8e, 0x0b, 0x12, 0x53, 0xc2, 0x90, - 0x9d, 0x7c, 0x49, 0xc7, 0x8a, 0xe0, 0x74, 0x4f, 0xa9, 0xc9, 0x04, 0xb1, 0x63, 0x20, 0x86, 0x24, 0x73, 0x20, 0x20, - 0x19, 0xc2, 0x59, 0x4d, 0xae, 0x23, 0x66, 0x0d, 0x4c, 0x67, 0xd7, 0xb0, 0x18, 0x89, 0x65, 0x0f, 0x11, 0xce, 0x4c, - 0xb7, 0x7a, 0x63, 0x8f, 0x13, 0x49, 0xb7, 0x0d, 0xdd, 0x2a, 0x79, 0xf6, 0x03, 0x08, 0x5e, 0xfe, 0xe3, 0x95, 0x6b, - 0xbb, 0x4c, 0x78, 0xe2, 0x2d, 0xd2, 0x3e, 0xdd, 0x7b, 0xf8, 0x8b, 0x33, 0x4a, 0x5b, 0x50, 0x4f, 0xfe, 0x77, 0x64, - 0xd4, 0x87, 0xbf, 0x24, 0x55, 0xae, 0x29, 0xfc, 0xe9, 0xde, 0xc3, 0xf7, 0xfb, 0x8a, 0x41, 0xfa, 0x66, 0x59, 0x29, - 0x09, 0xcc, 0xf8, 0x56, 0x2c, 0x4f, 0x57, 0xee, 0xac, 0x48, 0xc5, 0x06, 0x9b, 0x13, 0x2a, 0x55, 0x9b, 0x52, 0xb7, - 0xf2, 0x04, 0x4b, 0x62, 0xae, 0x92, 0xea, 0xcb, 0xe6, 0xd0, 0x98, 0x4b, 0x71, 0x9d, 0xc9, 0x05, 0xfb, 0xc6, 0xfd, - 0xd2, 0x53, 0x8d, 0x12, 0x3e, 0x07, 0x43, 0x1c, 0x33, 0x76, 0x81, 0x0f, 0x5b, 0xa8, 0xb7, 0x75, 0x9e, 0x49, 0x83, - 0xa8, 0x45, 0xfd, 0xb0, 0xc1, 0x94, 0xb4, 0x70, 0x46, 0x5a, 0x38, 0x27, 0x6a, 0xd0, 0xb2, 0x27, 0x46, 0x2f, 0x2f, - 0x9b, 0xb6, 0xe7, 0x0e, 0x6c, 0xf7, 0xdc, 0xee, 0x5b, 0x7b, 0x28, 0xcf, 0x7a, 0xb9, 0xd1, 0x5f, 0x9a, 0x83, 0x7e, - 0x66, 0x50, 0xe3, 0x05, 0x8b, 0x0b, 0x5c, 0x98, 0x96, 0xaf, 0xf9, 0x28, 0x07, 0x3b, 0x15, 0x98, 0x19, 0xd6, 0x28, - 0x2d, 0xcb, 0xb6, 0x5d, 0xd9, 0x3c, 0x31, 0x6b, 0x55, 0xe0, 0x3c, 0x01, 0x52, 0x8e, 0x73, 0x67, 0xd7, 0xa3, 0x76, - 0xab, 0x9c, 0x1f, 0x1d, 0xc5, 0xb6, 0xd2, 0x8c, 0xc6, 0x85, 0xcf, 0xaf, 0x6e, 0x00, 0x3f, 0x58, 0xaa, 0x31, 0x43, - 0x66, 0x02, 0x8d, 0x46, 0x36, 0xdc, 0xd0, 0x43, 0x42, 0xe2, 0xbc, 0x0e, 0x45, 0x3f, 0x7a, 0xc3, 0x0c, 0x6e, 0x01, - 0xa0, 0xd1, 0x28, 0xaf, 0x7b, 0xb7, 0x20, 0xf6, 0x54, 0x63, 0xb9, 0xf9, 0x1a, 0x97, 0xd6, 0x44, 0xad, 0x1d, 0x3b, - 0x2c, 0x3f, 0x0a, 0x24, 0x42, 0xdc, 0x15, 0x7e, 0x3e, 0xc1, 0xd6, 0x10, 0x50, 0xee, 0x85, 0xb3, 0x81, 0xc0, 0xc6, - 0x6a, 0xcb, 0x15, 0xf2, 0xa4, 0xad, 0x83, 0x52, 0x5f, 0x08, 0x2e, 0xb8, 0xa0, 0x50, 0x63, 0xe3, 0xb0, 0xfc, 0x05, - 0xdb, 0x35, 0xe7, 0xc4, 0x0a, 0x39, 0x6d, 0x99, 0x19, 0x86, 0x01, 0x58, 0xa7, 0x04, 0xcc, 0x73, 0xf2, 0xf2, 0xdb, - 0xa8, 0xff, 0x30, 0x40, 0xfd, 0x47, 0x84, 0x05, 0xdb, 0xc0, 0xea, 0x4a, 0x12, 0xe9, 0x14, 0x14, 0xca, 0x67, 0x3d, - 0x5e, 0x10, 0xd0, 0xc6, 0xd5, 0xa1, 0x5a, 0xbb, 0xa2, 0xfc, 0x06, 0x65, 0x09, 0x77, 0x8a, 0xd1, 0x67, 0x62, 0x7f, - 0x9f, 0x1c, 0x57, 0x17, 0x74, 0xd0, 0xf5, 0x3e, 0xe5, 0x60, 0x48, 0x0a, 0x1f, 0xbe, 0xff, 0xfe, 0xdd, 0xea, 0xe3, - 0xc5, 0xee, 0x0e, 0x0e, 0xcc, 0x4a, 0x61, 0xd6, 0xc1, 0x06, 0xae, 0x1b, 0x99, 0x42, 0xff, 0xe5, 0x9d, 0x78, 0x9d, - 0x0a, 0x6d, 0x6d, 0x46, 0x7f, 0x1c, 0xc2, 0x68, 0xdb, 0x6d, 0x53, 0x82, 0x05, 0xcd, 0x02, 0x5d, 0xb2, 0xc6, 0xad, - 0xb4, 0xf8, 0x06, 0x19, 0x79, 0x68, 0x0a, 0x30, 0x31, 0xde, 0x9f, 0xfd, 0x68, 0xe3, 0xf0, 0xc4, 0x0e, 0x0d, 0xad, - 0x0c, 0x21, 0xb4, 0x78, 0x0f, 0x98, 0x63, 0x8f, 0x08, 0x00, 0xd1, 0x4b, 0x03, 0xa9, 0x0a, 0x64, 0x51, 0x54, 0x29, - 0xf2, 0x9f, 0x1f, 0x12, 0xf2, 0xb2, 0x52, 0x64, 0xbe, 0xad, 0x8c, 0xb9, 0x00, 0x31, 0x50, 0x0a, 0x17, 0x09, 0x65, - 0x82, 0xbd, 0x0c, 0x7d, 0xaf, 0x7d, 0x79, 0x23, 0x6d, 0x26, 0x15, 0x37, 0x1e, 0xdc, 0x94, 0x1a, 0x15, 0x9f, 0xcd, - 0xf7, 0x90, 0xd8, 0xca, 0xbd, 0x07, 0xb9, 0x9c, 0x9a, 0x41, 0xc2, 0xf7, 0x3b, 0x53, 0xda, 0xb7, 0xbb, 0xf9, 0xb2, - 0x6d, 0x11, 0xb3, 0xb5, 0x2e, 0x09, 0x17, 0x8a, 0x15, 0xfa, 0x11, 0x9b, 0xc8, 0x02, 0xee, 0x3f, 0x4a, 0xb0, 0xa0, - 0xcd, 0xbd, 0x40, 0x07, 0x68, 0x26, 0x18, 0x5c, 0x3a, 0x6c, 0xcd, 0xd0, 0xfc, 0xfa, 0x62, 0xee, 0xc0, 0x3f, 0x6d, - 0xd7, 0x7a, 0x79, 0x74, 0xf4, 0x95, 0x55, 0x80, 0x72, 0xc3, 0x34, 0xc3, 0x08, 0x88, 0x97, 0xe5, 0x72, 0xdc, 0xcd, - 0xf0, 0xbd, 0xb8, 0x52, 0x19, 0x78, 0xc2, 0x11, 0x12, 0xa1, 0xe7, 0x44, 0x6f, 0xa6, 0xdb, 0xf4, 0xde, 0x69, 0x33, - 0x44, 0x28, 0xd6, 0x00, 0xb9, 0x07, 0xb9, 0xdc, 0x2a, 0x99, 0x54, 0x65, 0x6b, 0x5b, 0x0e, 0xe2, 0x31, 0x80, 0x2b, - 0x36, 0x42, 0x4a, 0x80, 0x86, 0xfb, 0x85, 0x96, 0xf7, 0x12, 0xd8, 0x7f, 0xac, 0x12, 0x10, 0x69, 0x51, 0x6d, 0xe3, - 0x22, 0x84, 0xad, 0xa9, 0x4f, 0x60, 0x9c, 0xf0, 0xf0, 0xf9, 0x3e, 0x0d, 0xb5, 0x47, 0x6d, 0x66, 0xce, 0x20, 0x28, - 0x21, 0x51, 0x59, 0x21, 0xf9, 0x1a, 0x0b, 0xc7, 0xcd, 0xf9, 0x7b, 0x38, 0x20, 0xc5, 0x92, 0xc6, 0xf6, 0x6e, 0x0b, - 0x8e, 0x8f, 0x22, 0x59, 0xc6, 0xb5, 0xae, 0x7b, 0x85, 0xa9, 0x86, 0x1d, 0xe8, 0x68, 0x08, 0xa7, 0xc2, 0xdc, 0x13, - 0x3e, 0xae, 0x48, 0xaa, 0x76, 0x16, 0x50, 0x9e, 0x18, 0x56, 0xa6, 0x29, 0xc1, 0xfc, 0xb5, 0x33, 0x5f, 0x2b, 0x8f, - 0x09, 0x66, 0x86, 0x71, 0x63, 0x57, 0x81, 0x6d, 0x00, 0xc7, 0x56, 0x8f, 0x64, 0xb0, 0xa8, 0x5e, 0x29, 0x6e, 0x3a, - 0x0d, 0x98, 0x80, 0xb7, 0x60, 0x3d, 0xb3, 0xbd, 0xf5, 0x9f, 0x9b, 0x83, 0x51, 0x60, 0x55, 0x23, 0xf0, 0xd2, 0x10, - 0x78, 0x04, 0x8c, 0x9b, 0x37, 0x2d, 0xef, 0x3b, 0x23, 0x1a, 0xe1, 0x4f, 0x3c, 0x87, 0x67, 0x96, 0xe5, 0xde, 0xf9, - 0xd8, 0x5a, 0x91, 0x54, 0x10, 0xb0, 0x2d, 0xc2, 0x8e, 0xc8, 0x4b, 0x84, 0x55, 0xa3, 0xd1, 0x53, 0x97, 0xac, 0xd2, - 0xaa, 0x54, 0xc3, 0x14, 0x70, 0x4b, 0x0c, 0x78, 0x5f, 0x3b, 0x51, 0xc1, 0x90, 0xc0, 0x5b, 0x7f, 0x2b, 0x50, 0xdf, - 0x3f, 0x7c, 0x1b, 0x87, 0xf4, 0x2d, 0x2c, 0x5b, 0x5e, 0xc4, 0xc2, 0x94, 0xe2, 0xea, 0x0e, 0xe7, 0xcd, 0xf7, 0xcd, - 0x46, 0x60, 0xdc, 0x87, 0x6d, 0x0c, 0x36, 0x6e, 0xa8, 0xa7, 0x2d, 0x69, 0x28, 0x37, 0x61, 0x0f, 0x55, 0xf6, 0x8e, - 0x61, 0x67, 0x3d, 0x5d, 0x49, 0xbb, 0x9a, 0xa8, 0xcd, 0x46, 0xb1, 0xca, 0x68, 0x60, 0xcb, 0xb0, 0xd3, 0x1c, 0x33, - 0xbb, 0x0a, 0xfc, 0xc7, 0x0b, 0xa2, 0x71, 0x80, 0xac, 0x6f, 0xbe, 0x75, 0x9d, 0x52, 0x0d, 0x13, 0xb6, 0xb7, 0x3b, - 0x1f, 0x1f, 0xf3, 0x7d, 0xe7, 0x23, 0x96, 0x6e, 0xeb, 0x9b, 0xb3, 0xb1, 0xfd, 0x6f, 0x9c, 0x8d, 0x4e, 0x6d, 0xef, - 0x8f, 0x47, 0xe0, 0x4e, 0x6a, 0xc7, 0x63, 0x7d, 0x4d, 0x89, 0xc4, 0xc2, 0x2d, 0xc7, 0x55, 0x67, 0xbd, 0x16, 0x83, - 0x16, 0xa8, 0x9d, 0xa2, 0x08, 0x7e, 0xb6, 0xed, 0xcf, 0x80, 0x24, 0x5b, 0x1d, 0x72, 0x2c, 0x4a, 0x51, 0x06, 0x25, - 0x60, 0x40, 0x1d, 0x1b, 0x5b, 0x2f, 0x83, 0xd8, 0x0e, 0x87, 0x1c, 0x96, 0x13, 0x51, 0x5e, 0x5d, 0xc1, 0x88, 0xcd, - 0xb1, 0xe1, 0x04, 0xcc, 0x78, 0xaf, 0x55, 0xa1, 0x17, 0x3f, 0xff, 0x35, 0x73, 0x5a, 0x3b, 0x62, 0x2c, 0x27, 0x51, - 0xb3, 0x62, 0x70, 0x23, 0x70, 0x0c, 0xe3, 0xa1, 0x91, 0x50, 0xab, 0x53, 0x1d, 0xd5, 0x8e, 0x24, 0xdc, 0x02, 0xb5, - 0xdb, 0xa1, 0x39, 0x97, 0xd6, 0xeb, 0xbd, 0x07, 0x0b, 0x2e, 0x02, 0xdc, 0x7e, 0x4e, 0x74, 0x8d, 0xa4, 0x50, 0xe2, - 0x24, 0x28, 0x9c, 0x1b, 0x54, 0xd5, 0x44, 0x0e, 0x5a, 0x43, 0xe0, 0x49, 0x7b, 0xd9, 0xa5, 0xac, 0x84, 0xe4, 0xac, - 0xd1, 0x40, 0x79, 0xd9, 0x31, 0x1d, 0x88, 0x46, 0x36, 0xc4, 0x0c, 0x67, 0x56, 0x60, 0x81, 0xd3, 0x2b, 0xce, 0xab, - 0xae, 0x07, 0xd9, 0x10, 0xe1, 0x62, 0xbd, 0x8e, 0xed, 0xd0, 0x72, 0xb4, 0x5e, 0xe7, 0xe1, 0xd0, 0x4c, 0x3e, 0x54, - 0x7c, 0xd9, 0xd7, 0xe4, 0xa5, 0x39, 0x0f, 0x5f, 0xc2, 0x20, 0x1b, 0x24, 0xce, 0x9d, 0x4a, 0x30, 0x07, 0xcd, 0x55, - 0x43, 0x0e, 0xb2, 0x46, 0x7b, 0x18, 0xd0, 0xb0, 0x41, 0x36, 0x24, 0xf9, 0x06, 0x2c, 0x67, 0x95, 0x3b, 0x30, 0x3f, - 0xc3, 0xc1, 0xf6, 0xd9, 0x9c, 0x33, 0xb6, 0xc1, 0x70, 0x4d, 0xb6, 0x55, 0x06, 0x25, 0x5e, 0xb9, 0xc5, 0xf5, 0xe5, - 0x6a, 0x06, 0x16, 0x65, 0x21, 0xec, 0xae, 0x99, 0xfb, 0x20, 0xfc, 0x97, 0xd8, 0x5e, 0xd0, 0xd2, 0x88, 0x7b, 0x0b, - 0xf1, 0xbd, 0xed, 0x76, 0x92, 0x24, 0xb4, 0x98, 0x9a, 0x2b, 0x11, 0x7f, 0xc3, 0x6b, 0xf6, 0xc0, 0xa9, 0x1b, 0x67, - 0xd0, 0xf3, 0xa0, 0xec, 0x6c, 0x48, 0xec, 0xf8, 0x3d, 0xb3, 0xe3, 0x1d, 0x57, 0x28, 0xdd, 0xaf, 0x8b, 0xb0, 0x83, - 0xc9, 0xfe, 0x97, 0x07, 0x73, 0xe6, 0x06, 0x63, 0xd1, 0x64, 0x0b, 0x6e, 0xdf, 0x80, 0x07, 0xa5, 0x5b, 0x70, 0xfb, - 0x36, 0x7c, 0x3d, 0xb4, 0xf2, 0x6f, 0x0e, 0x30, 0x20, 0x13, 0x76, 0xa4, 0x55, 0x42, 0x30, 0xcc, 0xee, 0x36, 0x47, - 0x66, 0xc9, 0x2a, 0x1c, 0xae, 0x9a, 0xc4, 0x62, 0x6b, 0x2f, 0x54, 0x4c, 0x6a, 0x20, 0x18, 0x8b, 0xf4, 0x25, 0x0a, - 0x95, 0x06, 0x75, 0xe3, 0x18, 0xc0, 0x2a, 0xa7, 0xad, 0x7f, 0x79, 0x74, 0x04, 0x42, 0x03, 0xb0, 0x76, 0x49, 0x46, - 0x17, 0x7a, 0x59, 0x00, 0x7f, 0xa5, 0xfc, 0x6f, 0x48, 0x06, 0xb7, 0x13, 0x93, 0x06, 0x3f, 0x20, 0x61, 0x41, 0x95, - 0xe2, 0x5f, 0x6d, 0x9a, 0xfb, 0x8d, 0x0b, 0xe2, 0x31, 0x5a, 0x59, 0x4e, 0x51, 0xa2, 0x9e, 0x74, 0xe8, 0x5a, 0x87, - 0xdc, 0xd3, 0xaf, 0x4c, 0xe8, 0x97, 0x5c, 0x69, 0x26, 0x00, 0x00, 0x15, 0xe2, 0xc1, 0x94, 0x14, 0x82, 0xad, 0x5b, - 0xab, 0x45, 0xc7, 0xe3, 0xef, 0x56, 0xd1, 0x75, 0xb6, 0x68, 0x46, 0xc5, 0x38, 0xb7, 0x9d, 0x84, 0x36, 0x93, 0xde, - 0x4e, 0xb4, 0x2c, 0x19, 0x5a, 0xec, 0x54, 0xec, 0x87, 0xa1, 0xf5, 0xb1, 0x20, 0xfe, 0x5c, 0xf0, 0x67, 0xe9, 0x77, - 0xf9, 0x18, 0xb8, 0x52, 0xff, 0xc6, 0x2a, 0x84, 0x33, 0xc1, 0x3a, 0x20, 0xaf, 0x49, 0x7d, 0x9c, 0x1e, 0x75, 0x66, - 0x3b, 0xca, 0x85, 0xd2, 0x28, 0x6c, 0xeb, 0xa4, 0x30, 0x98, 0x72, 0xfe, 0x6d, 0x89, 0xeb, 0x17, 0x7f, 0x8c, 0xf8, - 0xa3, 0x43, 0xfc, 0xbb, 0x54, 0x1a, 0xad, 0x4a, 0x04, 0x43, 0x7e, 0x47, 0x32, 0x05, 0x57, 0xb1, 0x39, 0xd7, 0xcf, - 0xf5, 0x3c, 0xdf, 0xf2, 0xc4, 0xe9, 0x31, 0x55, 0x42, 0x47, 0xc5, 0x37, 0x0c, 0xbf, 0x60, 0x70, 0x6f, 0xfc, 0x8c, - 0x07, 0x55, 0x76, 0xef, 0x8b, 0x9f, 0x05, 0xf7, 0xc5, 0xcf, 0x78, 0xba, 0x5b, 0x34, 0xb8, 0x27, 0xee, 0x24, 0x17, - 0x49, 0x2b, 0xf2, 0x7c, 0xd4, 0x98, 0x56, 0xfe, 0x95, 0x76, 0x6b, 0xe0, 0xca, 0x26, 0x0e, 0x8c, 0xf3, 0xea, 0x22, - 0x14, 0x73, 0xe6, 0x8c, 0x96, 0xc3, 0xff, 0xd6, 0x3a, 0xb9, 0x93, 0x47, 0x5a, 0x29, 0xe4, 0x0d, 0x2d, 0xf4, 0x3d, - 0xd8, 0x70, 0xc5, 0x8e, 0x0f, 0x20, 0x25, 0xa0, 0x6c, 0xfb, 0xf7, 0xba, 0x08, 0xc4, 0x71, 0x65, 0x9d, 0x8f, 0xc2, - 0xf6, 0x49, 0x51, 0x72, 0x75, 0x75, 0x21, 0xe4, 0xd6, 0x68, 0x09, 0x10, 0xa6, 0xde, 0x35, 0x8f, 0x39, 0x9a, 0xcc, - 0xd2, 0xd5, 0xa6, 0x54, 0x1d, 0x14, 0x96, 0xab, 0xe3, 0x08, 0x17, 0x1b, 0x73, 0x83, 0xfe, 0x37, 0xc7, 0x9f, 0xb9, - 0xa3, 0x91, 0x3f, 0x95, 0x14, 0xe8, 0xc3, 0x7e, 0x5f, 0x9b, 0x3d, 0x24, 0xd2, 0xce, 0xa1, 0xb4, 0x14, 0x00, 0xac, - 0x36, 0xf8, 0xba, 0xf1, 0x38, 0xf5, 0x44, 0xba, 0xd9, 0x7c, 0xd3, 0x10, 0x16, 0xb3, 0xd2, 0x82, 0xc7, 0x74, 0xb3, - 0xc7, 0x72, 0xd4, 0xcb, 0xe2, 0xba, 0xdc, 0x63, 0xb5, 0x7e, 0xd1, 0x37, 0x40, 0x59, 0x19, 0xa2, 0xad, 0xd7, 0x71, - 0x1d, 0xde, 0x44, 0x04, 0xd7, 0x20, 0x08, 0x8b, 0xc0, 0x80, 0xa3, 0xc6, 0x78, 0xdb, 0x3a, 0x31, 0xda, 0xb6, 0x5f, - 0xf2, 0xac, 0x7b, 0x6d, 0x1c, 0xa1, 0xa2, 0xc1, 0x56, 0x0f, 0x35, 0x0f, 0xd8, 0xce, 0xae, 0xec, 0x28, 0x80, 0xd0, - 0x98, 0x7a, 0xe3, 0xdc, 0xca, 0x8a, 0x76, 0x0f, 0x7c, 0xd1, 0x77, 0xcc, 0x73, 0x1d, 0xe8, 0x76, 0xf3, 0x03, 0xdb, - 0xa6, 0x27, 0xf2, 0x5b, 0xb6, 0x4d, 0x35, 0x4e, 0xf8, 0xb0, 0x85, 0xbe, 0x6f, 0x08, 0x6b, 0xfb, 0xda, 0x5f, 0xe4, - 0x7f, 0xa1, 0xbb, 0x36, 0xa0, 0xa7, 0x05, 0xb3, 0xa7, 0x31, 0xef, 0xf5, 0x66, 0xf3, 0x53, 0xe9, 0xbf, 0x60, 0x6c, - 0x85, 0x7e, 0xb2, 0xbb, 0xc0, 0x89, 0x95, 0xc6, 0x21, 0x38, 0xfe, 0x9b, 0x93, 0x69, 0x2e, 0x47, 0x34, 0x7f, 0x07, - 0x3d, 0x56, 0xb9, 0xcf, 0xef, 0xc6, 0x05, 0xd5, 0xcc, 0xd1, 0x9a, 0x6a, 0x14, 0x7f, 0xf3, 0x60, 0x18, 0x7f, 0x73, - 0x4b, 0xb9, 0xab, 0x16, 0xf0, 0xea, 0x65, 0xd9, 0x44, 0xfa, 0xd3, 0xc6, 0xd3, 0x0e, 0xae, 0xf6, 0xf7, 0xb2, 0x4d, - 0xd2, 0x78, 0x49, 0xd2, 0xb8, 0x8a, 0xb7, 0x9b, 0x8a, 0xe3, 0xcf, 0xdf, 0x18, 0xec, 0x2e, 0x99, 0xfb, 0x1c, 0x90, - 0xb9, 0xcf, 0x3c, 0xfd, 0x6e, 0xad, 0x80, 0xe2, 0x9d, 0x26, 0xa7, 0xc6, 0x32, 0xc6, 0x8e, 0xfa, 0xad, 0x06, 0x83, - 0x06, 0x4d, 0xae, 0x02, 0x6f, 0x87, 0xea, 0xf4, 0xf2, 0xf6, 0x47, 0x71, 0xb6, 0x54, 0x5a, 0xce, 0x5d, 0xa3, 0xca, - 0xf9, 0x38, 0x99, 0x4c, 0x50, 0x60, 0x9b, 0x3b, 0xfc, 0xb4, 0xee, 0x46, 0xb6, 0xfa, 0xc2, 0xc5, 0x38, 0x55, 0xd8, - 0x9d, 0x2d, 0x2a, 0x95, 0x1b, 0xe2, 0xcd, 0x9c, 0x77, 0xf3, 0xf0, 0x84, 0x0b, 0xae, 0x66, 0xac, 0x88, 0x0b, 0xb4, - 0xfa, 0x56, 0x67, 0x05, 0xdc, 0xe6, 0xd8, 0xce, 0xf0, 0xb2, 0xb4, 0x1c, 0xd0, 0x09, 0xb4, 0x06, 0x3a, 0xa3, 0x39, - 0xd3, 0x33, 0x39, 0x06, 0xc3, 0x97, 0x64, 0x5c, 0xba, 0x53, 0x1d, 0x1d, 0x1d, 0xc6, 0x91, 0xd1, 0x5f, 0x80, 0x0f, - 0x7a, 0x98, 0x83, 0xfa, 0x2b, 0x70, 0x0c, 0xaa, 0xba, 0x66, 0x68, 0xc5, 0xb6, 0x7d, 0x68, 0x74, 0xf2, 0x85, 0xdd, - 0x61, 0x8e, 0x36, 0x9b, 0xd4, 0x8e, 0x3a, 0x9a, 0x70, 0x96, 0x8f, 0x23, 0xfc, 0x85, 0xdd, 0xa5, 0xa5, 0xdb, 0xba, - 0xf1, 0xb2, 0x36, 0x8b, 0x18, 0xc9, 0x1b, 0x11, 0xe1, 0xaa, 0x93, 0x74, 0xb5, 0xc1, 0xb2, 0xe0, 0x53, 0xc0, 0xd1, - 0x9f, 0xd9, 0x5d, 0xea, 0xda, 0x0b, 0x5c, 0x05, 0xd1, 0xca, 0x83, 0x3e, 0x09, 0x92, 0xc3, 0x65, 0x70, 0x02, 0xc7, - 0xc0, 0xd4, 0x1d, 0x92, 0x5a, 0xb9, 0x4a, 0x84, 0x44, 0x68, 0xf3, 0xef, 0x4e, 0x05, 0x4f, 0xc2, 0x73, 0x4e, 0xd7, - 0x2c, 0x6e, 0xb7, 0x2a, 0x31, 0xa8, 0x50, 0x59, 0x90, 0x7c, 0x8c, 0xb9, 0xdf, 0x7d, 0xce, 0xfb, 0x21, 0xd0, 0x99, - 0x4d, 0xa8, 0x6b, 0x34, 0x5d, 0x9a, 0x5f, 0xa8, 0xba, 0x83, 0x9a, 0xeb, 0xaa, 0xe2, 0xc1, 0xc7, 0x18, 0x00, 0x0f, - 0xd6, 0x32, 0xd4, 0x38, 0x84, 0x6e, 0xbc, 0x99, 0xea, 0x82, 0x92, 0x78, 0xe5, 0xe7, 0x90, 0xf2, 0x10, 0x8c, 0x7a, - 0x03, 0x68, 0xe8, 0x10, 0xcc, 0x5a, 0x1e, 0xf2, 0x49, 0x2c, 0x76, 0xce, 0x50, 0x69, 0xce, 0xd0, 0x24, 0x00, 0xf9, - 0x37, 0xce, 0x4c, 0x66, 0xa0, 0x61, 0x78, 0x4b, 0x73, 0x00, 0xba, 0xd5, 0x75, 0x38, 0x14, 0xae, 0x68, 0xe9, 0xbc, - 0x67, 0x17, 0x5d, 0xd6, 0x86, 0x15, 0x9b, 0x76, 0xd0, 0x26, 0x85, 0x29, 0x31, 0x5b, 0x60, 0xe3, 0xf5, 0x3e, 0xdc, - 0xdb, 0xd5, 0xc6, 0x45, 0xe2, 0xa7, 0x45, 0x3c, 0x4c, 0x62, 0x8a, 0x56, 0x3c, 0xa6, 0x58, 0x82, 0x1d, 0x64, 0xb1, - 0x29, 0xc7, 0xcf, 0xc2, 0xe5, 0xa8, 0x59, 0x49, 0xef, 0x77, 0x30, 0x04, 0x2e, 0x5f, 0x83, 0x6d, 0x28, 0xe6, 0x25, - 0x61, 0x89, 0x8d, 0xa7, 0x5f, 0xb0, 0x6e, 0x53, 0xbb, 0x20, 0x7e, 0x05, 0x16, 0x34, 0x5e, 0x05, 0xb3, 0x08, 0x9d, - 0xca, 0x9d, 0xc3, 0xa1, 0xbb, 0x26, 0xac, 0x8c, 0x57, 0x63, 0x45, 0xb6, 0x8e, 0x9e, 0xef, 0xdb, 0x78, 0xfe, 0xb5, - 0x64, 0xc5, 0xdd, 0x35, 0x03, 0x1b, 0x6b, 0x09, 0xee, 0xc6, 0xd5, 0x32, 0x54, 0x06, 0xf2, 0x7d, 0x69, 0x58, 0x97, - 0x0d, 0xfe, 0x6e, 0x54, 0x8c, 0x8d, 0xb9, 0xa7, 0x0c, 0xb4, 0x35, 0x76, 0xbb, 0xb0, 0x6f, 0xba, 0x6e, 0xb2, 0x9e, - 0x89, 0x95, 0x50, 0x41, 0xda, 0xdd, 0x2d, 0xe0, 0x22, 0xf4, 0x87, 0x1d, 0xa8, 0xe1, 0xb6, 0xea, 0x06, 0x92, 0xe0, - 0xda, 0x4f, 0x7e, 0x7b, 0xaa, 0xfb, 0xac, 0x75, 0xbf, 0x3d, 0xd5, 0xda, 0x65, 0xa1, 0x31, 0x24, 0xc2, 0xae, 0x9f, - 0xd2, 0x7f, 0x5a, 0x6c, 0x36, 0x68, 0x03, 0xc3, 0x7b, 0xc4, 0x7b, 0x71, 0xfc, 0xc8, 0x5b, 0x28, 0x26, 0x70, 0x91, - 0x7b, 0x9d, 0x4b, 0x4f, 0xc8, 0xab, 0x11, 0x3c, 0xe2, 0x3b, 0x43, 0x78, 0xc4, 0x03, 0xa7, 0x57, 0x90, 0x9a, 0xa6, - 0x82, 0x8d, 0x3d, 0xfd, 0x44, 0x16, 0x09, 0x0d, 0x1f, 0xf7, 0x9a, 0x13, 0xa1, 0xff, 0x4c, 0x81, 0xff, 0xc2, 0xa3, - 0xa5, 0xd6, 0x52, 0x60, 0x2e, 0x16, 0x4b, 0x8d, 0x95, 0x19, 0xfd, 0x6a, 0x22, 0x85, 0x6e, 0x4e, 0xe8, 0x9c, 0xe7, - 0x77, 0xe9, 0x92, 0x37, 0xe7, 0x52, 0x48, 0xb5, 0xa0, 0x19, 0xc3, 0xea, 0x4e, 0x69, 0x36, 0x6f, 0x2e, 0x39, 0x7e, - 0xce, 0xf2, 0xaf, 0x4c, 0xf3, 0x8c, 0xe2, 0xb7, 0x72, 0x24, 0xb5, 0xc4, 0xaf, 0x6f, 0xef, 0xa6, 0x4c, 0xe0, 0xf7, - 0xa3, 0xa5, 0xd0, 0x4b, 0xac, 0xa8, 0x50, 0x4d, 0xc5, 0x0a, 0x3e, 0xe9, 0x35, 0x9b, 0x8b, 0x82, 0xcf, 0x69, 0x71, - 0xd7, 0xcc, 0x64, 0x2e, 0x8b, 0xf4, 0xbf, 0x5a, 0xa7, 0xf4, 0xc1, 0xe4, 0xac, 0xa7, 0x0b, 0x2a, 0x14, 0x87, 0x85, - 0x49, 0x69, 0x9e, 0x1f, 0x9c, 0x76, 0x5b, 0x73, 0x75, 0x68, 0x2f, 0xfc, 0xa8, 0xd0, 0x9b, 0x3f, 0xf1, 0x6f, 0x12, - 0x46, 0x99, 0x8c, 0xb4, 0x70, 0x83, 0x5c, 0x65, 0xcb, 0x42, 0xc9, 0x22, 0x5d, 0x48, 0x2e, 0x34, 0x2b, 0x7a, 0x23, - 0x59, 0x8c, 0x59, 0xd1, 0x2c, 0xe8, 0x98, 0x2f, 0x55, 0x7a, 0xb6, 0xb8, 0xed, 0xd5, 0x7b, 0xb0, 0xf9, 0xa9, 0x90, - 0x82, 0xf5, 0x80, 0xdf, 0x98, 0x16, 0x72, 0x29, 0xc6, 0x6e, 0x18, 0x4b, 0xa1, 0x98, 0xee, 0x2d, 0xe8, 0x18, 0xec, - 0x80, 0xd3, 0x8b, 0xc5, 0x6d, 0xcf, 0xcc, 0xfa, 0x86, 0xf1, 0xe9, 0x4c, 0xa7, 0xdd, 0x56, 0xcb, 0x7e, 0x2b, 0xfe, - 0x37, 0x4b, 0xdb, 0x9d, 0xa4, 0xd3, 0x5d, 0xdc, 0x02, 0x07, 0xaf, 0x59, 0xd1, 0x04, 0x58, 0x40, 0xa5, 0x76, 0xd2, - 0x7a, 0x70, 0x7a, 0x1f, 0x32, 0xc0, 0xc6, 0xa1, 0x69, 0x26, 0x04, 0xc6, 0xee, 0xe9, 0x72, 0xb1, 0x60, 0x05, 0x78, - 0xd1, 0xf7, 0xe6, 0xb4, 0x98, 0x72, 0xd1, 0x2c, 0x4c, 0xa3, 0xcd, 0x8b, 0xc5, 0xed, 0x06, 0xe6, 0x93, 0x5a, 0xb3, - 0x55, 0x37, 0x2d, 0xf7, 0xb5, 0x0a, 0x86, 0x68, 0x62, 0xd2, 0xa4, 0xc5, 0x74, 0x44, 0xe3, 0x76, 0xe7, 0x3e, 0xf6, - 0xff, 0x4b, 0x3a, 0x28, 0x00, 0x5b, 0x73, 0xbc, 0x2c, 0xcc, 0x2d, 0x6a, 0xda, 0x56, 0xb6, 0xd9, 0x99, 0xfc, 0xca, - 0x0a, 0xdf, 0xaa, 0xf9, 0x58, 0xed, 0xcc, 0xfb, 0x3f, 0x6a, 0x94, 0xda, 0xb6, 0x5e, 0xa8, 0x6b, 0xa0, 0xd1, 0xbb, - 0x8d, 0xfd, 0x57, 0xe7, 0x82, 0xde, 0x3f, 0xeb, 0x7a, 0xb8, 0x4f, 0x26, 0x93, 0x1a, 0xd0, 0x3d, 0x74, 0xdb, 0xad, - 0xc5, 0xed, 0x41, 0xa7, 0xe5, 0x61, 0x6c, 0x61, 0x7a, 0xbe, 0xb8, 0xdd, 0xb3, 0x82, 0x01, 0x56, 0x6c, 0xf7, 0x76, - 0x90, 0x9c, 0xaa, 0x03, 0x46, 0x15, 0xdb, 0xfc, 0x89, 0xe7, 0x14, 0x70, 0xc3, 0x20, 0xed, 0xc0, 0xc8, 0xa9, 0xb0, - 0x02, 0xc3, 0xd5, 0x0d, 0x1f, 0xeb, 0x59, 0xda, 0x6e, 0xb5, 0x7e, 0xa8, 0x30, 0xa9, 0x37, 0xb3, 0x4b, 0xda, 0x2e, - 0xd8, 0xbc, 0x86, 0x5f, 0x23, 0x5a, 0xee, 0x82, 0xd5, 0x42, 0xba, 0x4e, 0x0b, 0x96, 0x9b, 0x28, 0x37, 0x1b, 0xb7, - 0x15, 0x76, 0xa6, 0xcc, 0xc5, 0x8c, 0x15, 0x5c, 0xf7, 0xea, 0x5f, 0x55, 0xc7, 0xbb, 0x73, 0xda, 0x58, 0xf9, 0x78, - 0x65, 0x6b, 0xb8, 0xcb, 0xd8, 0xc7, 0xf0, 0xb1, 0x8b, 0x95, 0x5f, 0x69, 0x11, 0x6f, 0x6d, 0x18, 0x1c, 0xd6, 0x40, - 0x9b, 0x60, 0xce, 0x05, 0x98, 0x8a, 0x0e, 0xf1, 0x37, 0xa0, 0x90, 0xd1, 0x3c, 0x8b, 0x61, 0x44, 0x07, 0xcd, 0x83, - 0xd3, 0x82, 0xcd, 0x91, 0x07, 0x44, 0x72, 0xbf, 0x5b, 0xb0, 0xf9, 0x26, 0x31, 0xd5, 0x57, 0x06, 0x75, 0x69, 0xce, - 0xa7, 0x22, 0xcd, 0x18, 0x6c, 0xab, 0x4d, 0xc2, 0x84, 0xe6, 0xfa, 0xae, 0x59, 0xc8, 0x9b, 0xd5, 0x98, 0xab, 0x45, - 0x4e, 0xef, 0xd2, 0x49, 0xce, 0x6e, 0x7b, 0xa6, 0x54, 0x93, 0x6b, 0x36, 0x57, 0xae, 0x6c, 0x0f, 0xd2, 0x9b, 0x63, - 0x6b, 0xce, 0x01, 0xd0, 0x93, 0x37, 0xdb, 0xfb, 0xda, 0x2f, 0x5a, 0x53, 0x2e, 0xf5, 0x41, 0x4b, 0xf5, 0xe6, 0x5c, - 0x34, 0xdd, 0x40, 0xce, 0x00, 0x23, 0x76, 0x21, 0x1f, 0xf4, 0x9f, 0xb0, 0xdb, 0x05, 0x15, 0x63, 0x36, 0x5e, 0x05, - 0xd5, 0x3a, 0x50, 0x2f, 0x2c, 0x95, 0x0a, 0x3d, 0x6b, 0x1a, 0x1b, 0xb4, 0xb8, 0x23, 0xd0, 0x37, 0x50, 0xfe, 0x41, - 0x0b, 0xdb, 0xff, 0x4f, 0xda, 0x28, 0xac, 0x7c, 0x00, 0xe1, 0xa0, 0xf8, 0xe4, 0xae, 0x09, 0x7f, 0x57, 0xe0, 0xf3, - 0xc4, 0x33, 0x9a, 0x3b, 0x88, 0xcc, 0xf9, 0x78, 0x9c, 0xd7, 0x46, 0x74, 0x15, 0x74, 0xd6, 0x46, 0x2b, 0x98, 0x7f, - 0xda, 0x3a, 0x68, 0x1d, 0x98, 0xb9, 0xb8, 0x6d, 0x70, 0x76, 0x76, 0xff, 0xf4, 0x01, 0xeb, 0xe5, 0x5c, 0xb0, 0xda, - 0x54, 0xbf, 0x0b, 0xea, 0xb0, 0xe1, 0x8e, 0x6b, 0xb8, 0x7d, 0xd0, 0x3e, 0x38, 0x6b, 0xfd, 0xe0, 0xa9, 0x48, 0xce, - 0x26, 0xda, 0xee, 0x9b, 0x1a, 0x59, 0xb9, 0xf0, 0x4d, 0xdf, 0x14, 0x74, 0x91, 0x0a, 0x09, 0x7f, 0x7a, 0xb0, 0xf9, - 0x27, 0xb9, 0xbc, 0x49, 0x67, 0x7c, 0x3c, 0x66, 0xc2, 0x16, 0x28, 0x13, 0x59, 0x9e, 0xf3, 0x85, 0xe2, 0x76, 0x35, - 0x1c, 0xee, 0x76, 0xb7, 0xa0, 0x1a, 0x0e, 0xe8, 0x34, 0x18, 0x50, 0xb7, 0x1a, 0x50, 0xd5, 0x7f, 0x38, 0xc2, 0xce, - 0xd6, 0x5c, 0x4d, 0xa9, 0x5e, 0x0d, 0x93, 0x3e, 0x2f, 0x95, 0x06, 0x98, 0x7b, 0xe3, 0x11, 0x73, 0xba, 0x34, 0x47, - 0x4c, 0xdf, 0x30, 0x26, 0xbe, 0x3d, 0x88, 0xab, 0x54, 0x8a, 0xfc, 0xce, 0x7e, 0xae, 0xc2, 0x2e, 0xe9, 0x52, 0xcb, - 0x4d, 0x32, 0xe2, 0x82, 0x16, 0x77, 0x9f, 0x14, 0x13, 0x4a, 0x16, 0x9f, 0xe4, 0x64, 0xb2, 0xfa, 0x16, 0xc9, 0xbb, - 0x8f, 0x36, 0x89, 0xe2, 0x62, 0x9a, 0x33, 0x4b, 0xe0, 0x0c, 0x22, 0xb8, 0x43, 0xc6, 0xb6, 0x6b, 0x9a, 0xac, 0x0d, - 0x7a, 0x93, 0x64, 0x39, 0x9f, 0x53, 0xcd, 0x0c, 0x9c, 0x03, 0x52, 0xe3, 0x26, 0x6f, 0xa9, 0x5c, 0xeb, 0xc0, 0xfe, - 0xa9, 0x4a, 0xc3, 0x36, 0x0a, 0x0a, 0xfb, 0x26, 0xb9, 0x30, 0xf8, 0x61, 0xc0, 0x61, 0x76, 0x91, 0x59, 0x3d, 0xb3, - 0x76, 0x01, 0xec, 0x60, 0x76, 0xb5, 0xa6, 0xae, 0x1c, 0x5d, 0xb2, 0x2d, 0x76, 0x5b, 0x3f, 0xd4, 0x73, 0x73, 0x3a, - 0x62, 0xf9, 0xca, 0x6e, 0x54, 0x0f, 0x5c, 0xb7, 0x55, 0xc3, 0x65, 0x0e, 0x48, 0x86, 0x01, 0xd1, 0x30, 0x4d, 0x9b, - 0x37, 0x6c, 0xf4, 0x85, 0x6b, 0xbb, 0x65, 0x9a, 0xea, 0x06, 0x9c, 0x8a, 0xcc, 0x98, 0x16, 0xac, 0x58, 0x79, 0x42, - 0xde, 0xaa, 0x11, 0xd0, 0x6b, 0x61, 0x0e, 0x68, 0x4d, 0x47, 0x4d, 0x08, 0xb1, 0xc6, 0x8a, 0xd5, 0xbe, 0xc9, 0xcd, - 0xe9, 0xad, 0x43, 0xb1, 0x07, 0xad, 0x1f, 0x6a, 0x87, 0xec, 0x59, 0xab, 0xe5, 0x8f, 0x88, 0xa6, 0xad, 0x91, 0xb6, - 0x93, 0x2e, 0x9b, 0x97, 0x89, 0x5a, 0x2e, 0xd2, 0x5a, 0xc2, 0x48, 0x6a, 0x2d, 0xe7, 0x36, 0x6d, 0x0f, 0x35, 0xaa, - 0x93, 0xde, 0x76, 0x67, 0x71, 0x7b, 0x60, 0xfe, 0x69, 0x1d, 0xb4, 0x76, 0x49, 0xed, 0x2e, 0x56, 0x9c, 0x22, 0x8f, - 0xc7, 0xd0, 0x71, 0x9b, 0xcd, 0x7b, 0x4b, 0x05, 0xc7, 0xbd, 0x81, 0xb8, 0x39, 0xd1, 0x36, 0x66, 0xb2, 0x00, 0x58, - 0xca, 0x05, 0x9c, 0xae, 0xf6, 0xb0, 0x83, 0x3e, 0x94, 0x04, 0x73, 0xf8, 0xbd, 0x8d, 0xd6, 0x87, 0xd5, 0x3a, 0xa8, - 0x06, 0x06, 0xff, 0x6c, 0xfe, 0xac, 0xf8, 0xf3, 0x27, 0x2c, 0x90, 0x8f, 0x78, 0x23, 0xe9, 0xae, 0x5b, 0x4e, 0x26, - 0x1a, 0xeb, 0x4a, 0x54, 0x33, 0x1e, 0x25, 0x73, 0x7a, 0x6b, 0x5d, 0x4b, 0xe6, 0x5c, 0x80, 0xe1, 0x1a, 0xc2, 0x3a, - 0x30, 0xf1, 0x9f, 0x85, 0x0d, 0x8d, 0x75, 0x0c, 0x0d, 0x1f, 0x77, 0x92, 0x6e, 0x17, 0xe1, 0x16, 0xee, 0x74, 0xbb, - 0x81, 0x4c, 0x36, 0xd1, 0xfb, 0x8a, 0xee, 0x2b, 0x29, 0xf7, 0x94, 0x3c, 0x31, 0x8d, 0x9e, 0xb4, 0x5b, 0x2d, 0x6c, - 0xdc, 0xe7, 0xcb, 0xc2, 0x42, 0xed, 0x69, 0xb6, 0xdd, 0x6a, 0x41, 0xb3, 0xf0, 0xc7, 0xcd, 0xeb, 0x67, 0xb2, 0x6a, - 0xa5, 0x2d, 0xdc, 0x4e, 0xdb, 0xb8, 0x93, 0x76, 0xf0, 0x69, 0x7a, 0x8a, 0xcf, 0xd2, 0x33, 0xdc, 0x4d, 0xbb, 0xf8, - 0x3c, 0x3d, 0xc7, 0xf7, 0xd3, 0xfb, 0xf8, 0x22, 0xbd, 0xc0, 0x0f, 0xd2, 0x07, 0xf8, 0x61, 0xda, 0x6e, 0xe1, 0x47, - 0x69, 0xbb, 0x8d, 0x1f, 0xa7, 0xed, 0x0e, 0x7e, 0x92, 0xb6, 0x4f, 0xf1, 0xd3, 0xb4, 0x7d, 0x86, 0x9f, 0xa5, 0xed, - 0x2e, 0xa6, 0x90, 0x3b, 0x82, 0xdc, 0x0c, 0x72, 0xc7, 0x90, 0xcb, 0x20, 0x77, 0x92, 0xb6, 0xbb, 0x1b, 0xac, 0x6c, - 0xc8, 0x8d, 0xa8, 0xd5, 0xee, 0x9c, 0x9e, 0x75, 0xcf, 0xef, 0x5f, 0x3c, 0x78, 0xf8, 0xe8, 0xf1, 0x93, 0xa7, 0xcf, - 0xa2, 0x21, 0xfe, 0x64, 0x3c, 0x5f, 0x94, 0x18, 0xf0, 0xa3, 0x76, 0x77, 0x88, 0xef, 0xfc, 0x67, 0xcc, 0x8f, 0x3a, - 0x67, 0x2d, 0x74, 0x75, 0x75, 0x36, 0x6c, 0x94, 0xb9, 0x8f, 0x8c, 0xc3, 0x4d, 0x95, 0x45, 0x08, 0x89, 0x21, 0x07, - 0xe1, 0x5b, 0xeb, 0x40, 0xc3, 0x62, 0x9e, 0x14, 0xe8, 0xe8, 0xc8, 0xfc, 0x98, 0xfa, 0x1f, 0x23, 0xff, 0x83, 0x06, - 0x8b, 0xf4, 0x95, 0xc6, 0xce, 0xe3, 0x5a, 0x97, 0xfe, 0x0e, 0xa5, 0x29, 0xd1, 0x01, 0x77, 0x46, 0xfd, 0xff, 0x15, - 0x59, 0xa3, 0x1d, 0x72, 0x66, 0x15, 0x63, 0xdd, 0x3e, 0x23, 0xab, 0x22, 0xed, 0x74, 0xbb, 0x47, 0x3f, 0x0f, 0xf8, - 0xa0, 0x3d, 0x1c, 0x1e, 0xb7, 0xef, 0xe3, 0x69, 0x99, 0xd0, 0xb1, 0x09, 0xa3, 0x32, 0xe1, 0xd4, 0x26, 0xd0, 0xd4, - 0xd6, 0x86, 0xa4, 0x33, 0x93, 0x04, 0x25, 0x36, 0xa9, 0x69, 0xfb, 0xbe, 0x6d, 0xfb, 0x01, 0x58, 0x93, 0x99, 0xe6, - 0x5d, 0xd3, 0x97, 0x97, 0x67, 0x6b, 0xd7, 0x28, 0x9e, 0xa6, 0xae, 0x35, 0x9f, 0x78, 0x36, 0x1c, 0xe2, 0x91, 0x49, - 0xec, 0x56, 0x89, 0xe7, 0xc3, 0xa1, 0xeb, 0xea, 0x81, 0xe9, 0xea, 0x7e, 0x95, 0x75, 0x31, 0x1c, 0x9a, 0x2e, 0x91, - 0x8b, 0x1d, 0xa0, 0xf4, 0xc1, 0x4d, 0xa9, 0xbf, 0xe1, 0x97, 0x9d, 0x6e, 0xb7, 0x0f, 0x18, 0x66, 0x6c, 0x82, 0x3d, - 0x8c, 0xbe, 0x04, 0x30, 0xba, 0x85, 0xdf, 0xfd, 0x4f, 0x34, 0xbd, 0xa3, 0x25, 0x90, 0xfa, 0xd1, 0x7f, 0x45, 0x0d, - 0x6d, 0x60, 0x6e, 0xfe, 0x4c, 0xed, 0x9f, 0x11, 0x6a, 0xdc, 0x50, 0x00, 0x37, 0x68, 0xa4, 0xbc, 0x4a, 0xd9, 0xf4, - 0x78, 0x4d, 0xc1, 0xc5, 0x67, 0xa6, 0x72, 0xda, 0x5f, 0xcf, 0x6e, 0x46, 0xeb, 0x99, 0xfa, 0x8a, 0xfe, 0x88, 0xff, - 0x50, 0xc7, 0xf1, 0xa0, 0xd9, 0x48, 0xd8, 0x1f, 0x63, 0xf0, 0x25, 0xea, 0xa7, 0x63, 0x36, 0x45, 0xfd, 0xc1, 0x1f, - 0x0a, 0x0f, 0x1b, 0x41, 0xc6, 0x0f, 0xbb, 0x29, 0xe0, 0x69, 0xb4, 0x9d, 0x18, 0xff, 0x80, 0xfa, 0xa8, 0xff, 0x87, - 0x3a, 0xfe, 0x03, 0xdd, 0x3b, 0x09, 0xb4, 0x26, 0xd2, 0x6d, 0xe1, 0x2a, 0xfc, 0xd0, 0x71, 0xb9, 0x85, 0x19, 0x6e, - 0x37, 0x19, 0x04, 0x6b, 0x03, 0x57, 0x74, 0x12, 0xcb, 0x06, 0x3f, 0x39, 0x6d, 0xa1, 0x1f, 0xda, 0x1d, 0x50, 0xae, - 0x34, 0xc5, 0xf1, 0xee, 0xa6, 0x2f, 0x9a, 0xa7, 0xf8, 0x41, 0xb3, 0xc0, 0x6d, 0x84, 0x9b, 0x6d, 0xaf, 0xf5, 0x1e, - 0xa8, 0xb8, 0x85, 0xb0, 0x8a, 0x2f, 0xe0, 0x9f, 0x33, 0x34, 0xac, 0x36, 0xe4, 0x2f, 0x74, 0xbb, 0x77, 0xf0, 0x9b, - 0x25, 0xb1, 0x6a, 0xf0, 0x93, 0xf3, 0x16, 0xfa, 0xe1, 0xdc, 0x74, 0xc4, 0x8e, 0xf5, 0x9e, 0xae, 0x24, 0x3e, 0x6b, - 0x4a, 0xe8, 0xa8, 0x55, 0xf6, 0x23, 0xe2, 0x2e, 0xc2, 0x22, 0x3e, 0x85, 0x7f, 0xda, 0x61, 0x3f, 0x8f, 0x77, 0xfa, - 0x31, 0xf3, 0x6e, 0xe3, 0xa4, 0x6b, 0xdd, 0x70, 0x95, 0xbd, 0x13, 0x6f, 0xb0, 0xab, 0xb6, 0xb9, 0xcc, 0x6b, 0x9f, - 0xc0, 0x07, 0xc2, 0xfa, 0x98, 0x28, 0xcc, 0x8e, 0xc1, 0x7f, 0x17, 0xcc, 0x56, 0xd4, 0xe5, 0x69, 0x4f, 0x35, 0x1a, - 0x48, 0x0c, 0xd4, 0xf0, 0x98, 0xb4, 0x9b, 0xba, 0xc9, 0x30, 0xfc, 0x6e, 0x90, 0x32, 0x28, 0x9c, 0xa8, 0x7a, 0x7d, - 0xed, 0x7a, 0xb5, 0x37, 0xff, 0x1e, 0x3b, 0x08, 0x21, 0xaa, 0x1f, 0xeb, 0x26, 0x43, 0x27, 0xa2, 0x11, 0xeb, 0x4b, - 0xd6, 0x3f, 0x4f, 0x5b, 0xc8, 0x60, 0xa7, 0xea, 0xc7, 0xac, 0xc9, 0x21, 0xbd, 0x93, 0xc6, 0xbc, 0xa9, 0xe1, 0xd7, - 0x59, 0x00, 0x2d, 0x01, 0x78, 0x57, 0x79, 0x23, 0x15, 0x27, 0x9d, 0x6e, 0x17, 0x0b, 0xc2, 0x93, 0xa9, 0xf9, 0xa5, - 0x08, 0x4f, 0x46, 0xe6, 0x97, 0x24, 0x25, 0xbc, 0x6c, 0xef, 0xb8, 0x20, 0xc1, 0xaa, 0x9a, 0x14, 0x0a, 0x0b, 0x5a, - 0xa0, 0x93, 0x8e, 0x37, 0x0b, 0xc0, 0x33, 0x3f, 0x07, 0x50, 0x83, 0x14, 0xc6, 0x22, 0x54, 0x36, 0x0b, 0x9c, 0x13, - 0x7a, 0x95, 0x74, 0xfb, 0xb3, 0x93, 0xb8, 0xd3, 0x94, 0xcd, 0x02, 0xa5, 0xb3, 0x13, 0x53, 0x13, 0x67, 0xe4, 0x35, - 0xb5, 0xad, 0xe1, 0x19, 0xdc, 0xe5, 0x66, 0x24, 0x3b, 0x3e, 0x6f, 0x35, 0x92, 0x2e, 0xc2, 0x83, 0x6c, 0xdd, 0xc2, - 0xf9, 0x7a, 0xdd, 0xc2, 0x34, 0x5c, 0x06, 0xe1, 0x01, 0x52, 0x6a, 0xea, 0xb6, 0x63, 0xf3, 0xf4, 0x79, 0xac, 0xc1, - 0x2e, 0x41, 0x83, 0xb7, 0x8f, 0x06, 0x3f, 0xa4, 0x94, 0xbb, 0x0b, 0x41, 0x64, 0xa2, 0x13, 0x4e, 0x42, 0xdd, 0xdd, - 0x6b, 0xe1, 0xd7, 0xd5, 0x5b, 0x96, 0x8a, 0xf8, 0xa3, 0xc4, 0x36, 0xad, 0x2a, 0xf6, 0x86, 0xee, 0x16, 0x7b, 0x4c, - 0x77, 0x8a, 0xdd, 0xdb, 0x53, 0xec, 0x97, 0xdd, 0x62, 0x7f, 0xc9, 0x40, 0xd3, 0xc8, 0x7f, 0x38, 0x3d, 0x6f, 0x35, - 0x4e, 0x01, 0x59, 0x4f, 0xcf, 0x5b, 0x55, 0xa1, 0x87, 0xb4, 0x5a, 0x2b, 0x4d, 0xae, 0xa9, 0xf5, 0xb5, 0xe0, 0xde, - 0xe9, 0xdb, 0x2c, 0x9c, 0x75, 0x39, 0x2f, 0xfd, 0xcb, 0x07, 0x5d, 0xb0, 0x65, 0x11, 0x86, 0xda, 0xe9, 0xc1, 0xf9, - 0xb0, 0x3f, 0x63, 0x71, 0x03, 0x52, 0x51, 0x3a, 0xd1, 0xee, 0x17, 0x2a, 0xaf, 0xb4, 0xff, 0x92, 0x90, 0xd4, 0x19, - 0x22, 0x2c, 0x49, 0x43, 0x0f, 0x4e, 0x87, 0xe6, 0xbc, 0x2b, 0xe0, 0xf7, 0x99, 0xf9, 0x5d, 0x2a, 0x94, 0x9c, 0x43, - 0xc6, 0xec, 0x66, 0x14, 0xf5, 0x05, 0x79, 0x43, 0x63, 0x63, 0x63, 0x8f, 0xd2, 0x32, 0x43, 0x7d, 0x85, 0x8c, 0x7b, - 0x65, 0x86, 0x20, 0xaf, 0x85, 0xfb, 0x8d, 0x57, 0x45, 0x0a, 0xf6, 0x36, 0x78, 0x9a, 0x82, 0xad, 0x0d, 0x1e, 0xa5, - 0x02, 0xfc, 0x41, 0x68, 0xca, 0x02, 0x2b, 0xfe, 0xa7, 0x4e, 0x83, 0x67, 0x6e, 0x9d, 0x89, 0xc1, 0xd2, 0x1e, 0x83, - 0x93, 0xe2, 0x2f, 0x19, 0xc3, 0xdf, 0x86, 0x46, 0x98, 0x41, 0x9b, 0x0c, 0x61, 0x9e, 0x14, 0x04, 0xd2, 0x30, 0x4f, - 0xa6, 0x84, 0x41, 0x93, 0x3c, 0x19, 0x11, 0x36, 0xe8, 0x04, 0x68, 0xf2, 0xc2, 0xc0, 0x0e, 0x80, 0xc3, 0xeb, 0x17, - 0xf9, 0xda, 0x36, 0x0e, 0x16, 0x02, 0xd0, 0x84, 0x20, 0x10, 0x73, 0x61, 0x00, 0x66, 0x23, 0xca, 0xfe, 0xec, 0x54, - 0xe1, 0x2f, 0x79, 0x42, 0x0d, 0xf5, 0xfe, 0x13, 0xc8, 0x6a, 0x7c, 0x6f, 0xc5, 0x36, 0xf8, 0xe0, 0xde, 0x4a, 0x6c, - 0x7e, 0x80, 0x3f, 0xca, 0xfe, 0x01, 0xe6, 0x21, 0xa1, 0x68, 0x83, 0xfe, 0x4c, 0xa1, 0xd8, 0x9e, 0x52, 0xe8, 0x4f, - 0xef, 0x0e, 0xa8, 0xc8, 0xea, 0x36, 0x8d, 0xc6, 0xb4, 0xf8, 0x12, 0xe1, 0xdf, 0xd3, 0x28, 0x07, 0x6e, 0x31, 0xc2, - 0x1f, 0xd3, 0xa8, 0x60, 0x11, 0xfe, 0x67, 0x1a, 0x8d, 0xf2, 0x65, 0x84, 0x7f, 0x4b, 0xa3, 0x69, 0x11, 0xe1, 0x0f, - 0xa0, 0xac, 0x1d, 0xf3, 0xe5, 0x3c, 0xc2, 0xef, 0xd3, 0x48, 0x19, 0x6f, 0x08, 0xfc, 0x30, 0x8d, 0x18, 0x8b, 0xf0, - 0xbb, 0x34, 0x92, 0x79, 0x84, 0xaf, 0xd3, 0x48, 0x16, 0x11, 0x7e, 0x94, 0x46, 0x05, 0x8d, 0xf0, 0xe3, 0x34, 0x82, - 0x42, 0xd3, 0x08, 0x3f, 0x49, 0x23, 0x68, 0x59, 0x45, 0xf8, 0x6d, 0x1a, 0x71, 0x11, 0xe1, 0x5f, 0xd3, 0x48, 0x2f, - 0x8b, 0xbf, 0x96, 0x92, 0xab, 0x08, 0x3f, 0x4d, 0xa3, 0x19, 0x8f, 0xf0, 0x9b, 0x34, 0x2a, 0x64, 0x84, 0x5f, 0xa7, - 0x11, 0xcd, 0x23, 0xfc, 0x2a, 0x8d, 0x72, 0x16, 0xe1, 0x5f, 0xd2, 0x68, 0xcc, 0x22, 0xfc, 0x32, 0x8d, 0xee, 0x58, - 0x9e, 0xcb, 0x08, 0x3f, 0x4b, 0x23, 0x26, 0x22, 0xfc, 0x73, 0x1a, 0x65, 0xb3, 0x08, 0xff, 0x23, 0x8d, 0x68, 0xf1, - 0x45, 0x45, 0xf8, 0x79, 0x1a, 0x31, 0x1a, 0xe1, 0x17, 0xb6, 0xa3, 0x69, 0x84, 0x7f, 0x4a, 0xa3, 0x9b, 0x59, 0xb4, - 0xc1, 0x52, 0x91, 0xd5, 0x6b, 0x9e, 0xb1, 0x7f, 0xb2, 0x34, 0x9a, 0xb4, 0x26, 0x17, 0x93, 0x49, 0x84, 0xa9, 0xd0, - 0xfc, 0xaf, 0x25, 0xbb, 0x79, 0xaa, 0x21, 0x91, 0xb2, 0xd1, 0xf8, 0x7e, 0x84, 0xe9, 0x5f, 0x4b, 0x9a, 0x46, 0x93, - 0x89, 0x29, 0xf0, 0xd7, 0x92, 0xce, 0x69, 0xf1, 0x96, 0xa5, 0xd1, 0xfd, 0xc9, 0x64, 0x32, 0x3e, 0x8b, 0x30, 0xfd, - 0x7b, 0xf9, 0xd1, 0xb4, 0x60, 0x0a, 0x8c, 0x18, 0x9f, 0x42, 0xdd, 0xee, 0xa4, 0x3b, 0xce, 0x22, 0x3c, 0xe2, 0xea, - 0xaf, 0x25, 0x7c, 0x4f, 0xd8, 0x59, 0x76, 0x16, 0xe1, 0x51, 0x4e, 0xb3, 0x2f, 0x69, 0xd4, 0x32, 0xbf, 0xc4, 0xcf, - 0x6c, 0xfc, 0x7a, 0x2e, 0xcd, 0x55, 0xc6, 0x84, 0x8d, 0xb2, 0x71, 0x84, 0xcd, 0x60, 0x26, 0xf0, 0xf7, 0x2b, 0x7f, - 0xc7, 0x74, 0x1a, 0x5d, 0xd0, 0xce, 0x88, 0x75, 0x22, 0x3c, 0x7a, 0x73, 0x23, 0xd2, 0x88, 0x76, 0x3b, 0xb4, 0x43, - 0x23, 0x3c, 0x5a, 0x16, 0xf9, 0xdd, 0x8d, 0x94, 0x63, 0x00, 0xc2, 0xe8, 0xe2, 0xe2, 0x7e, 0x84, 0x33, 0xfa, 0x8b, - 0x86, 0xda, 0xdd, 0xc9, 0x03, 0x46, 0x5b, 0x11, 0xfe, 0x99, 0x16, 0xfa, 0xe3, 0x52, 0xb9, 0x81, 0xb6, 0x20, 0x45, - 0x66, 0xef, 0x40, 0xcd, 0x1f, 0x8d, 0x3b, 0xe7, 0x0f, 0xda, 0x2c, 0xc2, 0xd9, 0xf5, 0x6b, 0xe8, 0xed, 0xfe, 0xa4, - 0xdb, 0x82, 0x0f, 0x01, 0x72, 0x29, 0x2b, 0xa0, 0x91, 0xf3, 0xb3, 0x07, 0x5d, 0x36, 0x36, 0x89, 0x8a, 0xe7, 0x5f, - 0xcc, 0xec, 0x2f, 0x60, 0x3e, 0x59, 0xc1, 0xe7, 0x4a, 0x8a, 0x34, 0x1a, 0x67, 0xed, 0xb3, 0x53, 0x48, 0xb8, 0xa3, - 0xc2, 0x03, 0xe7, 0x16, 0xaa, 0x5e, 0x8c, 0x22, 0x7c, 0x6b, 0x53, 0x2f, 0x46, 0xe6, 0x63, 0xfa, 0xee, 0x17, 0xf1, - 0x66, 0x9c, 0x46, 0xa3, 0x8b, 0x8b, 0xf3, 0x16, 0x24, 0xfc, 0x46, 0xef, 0xd2, 0x88, 0x3e, 0x80, 0xff, 0x20, 0xfb, - 0xe3, 0x33, 0xe8, 0x10, 0x46, 0x78, 0x3b, 0xfd, 0x18, 0xe6, 0x7c, 0x99, 0xd1, 0x2f, 0x3c, 0x8d, 0x46, 0xe3, 0xd1, - 0xfd, 0x73, 0xa8, 0x37, 0xa7, 0xd3, 0x67, 0x9a, 0x42, 0xbb, 0xad, 0x96, 0x69, 0xf9, 0x1d, 0xff, 0xca, 0x4c, 0xf5, - 0x6e, 0xf7, 0x7c, 0xd4, 0x81, 0x11, 0x5c, 0x83, 0x42, 0x05, 0xc6, 0x73, 0x91, 0x99, 0x06, 0xaf, 0xb3, 0xa7, 0xe3, - 0x34, 0x7a, 0xf0, 0xe0, 0xb4, 0x93, 0x65, 0x11, 0xbe, 0xfd, 0x38, 0xb6, 0xb5, 0x4d, 0x9e, 0x02, 0xd8, 0xa7, 0x11, - 0x7b, 0xf0, 0xe0, 0xfc, 0x3e, 0x85, 0xef, 0xe7, 0xa6, 0xad, 0x8b, 0xc9, 0x28, 0xbb, 0x80, 0xb6, 0xde, 0xc3, 0x74, - 0xce, 0x2e, 0x4e, 0xc7, 0xa6, 0xaf, 0xf7, 0x66, 0xd4, 0x9d, 0xc9, 0xd9, 0xe4, 0xcc, 0x64, 0x9a, 0xa1, 0x96, 0x9f, - 0xbf, 0xb2, 0x34, 0xca, 0xd8, 0xb8, 0x1d, 0xe1, 0x5b, 0xb7, 0x70, 0x0f, 0xce, 0x5a, 0xad, 0xf1, 0x69, 0x84, 0xc7, - 0x0f, 0x17, 0x8b, 0xb7, 0x06, 0x82, 0xed, 0xb3, 0x07, 0xf6, 0x5b, 0x7d, 0xb9, 0x83, 0xa6, 0x47, 0x06, 0x68, 0x63, - 0x3e, 0x37, 0x2d, 0x9f, 0x3f, 0x80, 0xff, 0xcc, 0xb7, 0x69, 0xba, 0xfc, 0x96, 0xe3, 0xa9, 0x5d, 0x94, 0x36, 0x7b, - 0xd0, 0x82, 0x1a, 0x13, 0xfe, 0x71, 0x54, 0x70, 0x40, 0xa3, 0x51, 0x07, 0xfe, 0x2f, 0xc2, 0x93, 0xfc, 0xfa, 0xb5, - 0xc3, 0xd9, 0xc9, 0x84, 0x4e, 0x5a, 0x11, 0x9e, 0xc8, 0x8f, 0x4a, 0xff, 0xf6, 0x50, 0xa4, 0x51, 0xa7, 0x73, 0x31, - 0x32, 0x65, 0x96, 0x3f, 0x2b, 0x6e, 0xf0, 0xb8, 0x65, 0x5a, 0x99, 0xd2, 0xb7, 0x6a, 0x74, 0x2d, 0x61, 0x25, 0xe1, - 0xbf, 0x08, 0x4f, 0x41, 0x0b, 0xe7, 0x5a, 0xb9, 0xb0, 0xdb, 0x61, 0xfa, 0xce, 0xa0, 0xe6, 0xf8, 0x3e, 0xc0, 0xcb, - 0x2f, 0xe3, 0x98, 0xd2, 0x6e, 0xa7, 0x15, 0x61, 0x33, 0xea, 0x8b, 0x16, 0xfc, 0x17, 0x61, 0x0b, 0x39, 0x03, 0xd7, - 0xe9, 0xc7, 0x67, 0x2f, 0x6f, 0xd2, 0x88, 0x8e, 0x27, 0x13, 0x58, 0x12, 0x33, 0x19, 0x5f, 0x6c, 0x26, 0x05, 0xbb, - 0xfb, 0xe5, 0xc6, 0x6d, 0x17, 0x93, 0xa0, 0x1d, 0x74, 0xce, 0x1f, 0x8c, 0xce, 0x22, 0xfc, 0x76, 0xcc, 0xa9, 0x80, - 0x55, 0xca, 0xc6, 0xdd, 0xac, 0x9b, 0x99, 0x84, 0xa9, 0x4c, 0xa3, 0x33, 0x58, 0xf2, 0x4e, 0x84, 0xf9, 0xd7, 0xeb, - 0x3b, 0x8b, 0x6e, 0x50, 0xdb, 0x21, 0xc8, 0xa4, 0xc5, 0xce, 0x2f, 0xb2, 0x08, 0xe7, 0xf4, 0xeb, 0xb3, 0x5f, 0x8a, - 0x34, 0x62, 0xe7, 0xec, 0x7c, 0x42, 0xfd, 0xf7, 0x3f, 0xd5, 0xcc, 0xd4, 0x68, 0x4d, 0xba, 0x90, 0x74, 0x23, 0xcc, - 0x58, 0xef, 0x67, 0x13, 0x83, 0x21, 0xaf, 0xe6, 0x52, 0x64, 0x4f, 0x27, 0x13, 0x69, 0xb1, 0x98, 0xc2, 0x26, 0xfc, - 0x1d, 0xa0, 0x4d, 0xc7, 0xe3, 0x0b, 0x76, 0x1e, 0xe1, 0xdf, 0xed, 0x2e, 0x71, 0x13, 0xf8, 0xdd, 0x62, 0x36, 0x73, - 0xbb, 0xfd, 0x77, 0x0b, 0x14, 0x98, 0xef, 0x84, 0x4e, 0xe8, 0xb8, 0x13, 0xe1, 0xdf, 0x0d, 0x5c, 0xc6, 0xa7, 0xf0, - 0x1f, 0x14, 0x80, 0xce, 0x1e, 0xb4, 0x18, 0x7b, 0xd0, 0x32, 0x5f, 0x61, 0x9e, 0x9b, 0xf9, 0xe8, 0x3c, 0x6b, 0x47, - 0xf8, 0x77, 0x87, 0x8e, 0x93, 0x09, 0x6d, 0x01, 0x3a, 0xfe, 0xee, 0xd0, 0xb1, 0xd3, 0x1a, 0x75, 0xa8, 0xf9, 0xb6, - 0x58, 0x73, 0x71, 0x3f, 0x63, 0x30, 0xb9, 0xdf, 0x2d, 0x42, 0xde, 0xbf, 0x7f, 0x71, 0xf1, 0xe0, 0x01, 0x7c, 0x9a, - 0xb6, 0xcb, 0x4f, 0xa5, 0x1f, 0xe6, 0x06, 0xc9, 0x5a, 0xd9, 0x19, 0xd0, 0xc9, 0xdf, 0xcd, 0x18, 0x27, 0x93, 0x09, - 0x6b, 0x45, 0x38, 0xe7, 0x73, 0x66, 0x31, 0xc1, 0xfe, 0x36, 0x1d, 0x9d, 0x76, 0xb2, 0xf1, 0x69, 0x27, 0xc2, 0xf9, - 0xdb, 0x67, 0x66, 0x36, 0x2d, 0x98, 0xbd, 0xdf, 0x72, 0x1e, 0x6b, 0xe6, 0xf4, 0x0d, 0x0c, 0x12, 0x56, 0x1a, 0x2a, - 0x7f, 0x08, 0xe8, 0xe1, 0xf9, 0x79, 0x36, 0x86, 0x81, 0x7e, 0x80, 0x6e, 0x01, 0x8c, 0x1f, 0xec, 0xe6, 0x1b, 0xd1, - 0x6e, 0x17, 0xa6, 0xfb, 0x61, 0xb1, 0x2c, 0x16, 0xaf, 0xd2, 0xe8, 0xc1, 0xe9, 0xfd, 0xd6, 0x78, 0x14, 0xe1, 0x0f, - 0x6e, 0x82, 0xa7, 0xd9, 0xe8, 0xf4, 0x7e, 0x3b, 0xc2, 0x1f, 0xcc, 0x7e, 0xbb, 0x3f, 0x3a, 0xbf, 0x80, 0x73, 0xe3, - 0x83, 0x5a, 0x14, 0x6f, 0xa7, 0xa6, 0xc0, 0x84, 0x3e, 0x80, 0x66, 0x7f, 0x35, 0xbb, 0x71, 0xdc, 0x86, 0x8d, 0xfc, - 0xc1, 0x6c, 0x32, 0x83, 0x27, 0xf7, 0xdb, 0xdd, 0x8b, 0x6e, 0x84, 0xe7, 0x7c, 0x2c, 0x80, 0xc0, 0x9b, 0x8d, 0xf2, - 0xa0, 0xfd, 0xe0, 0x7e, 0x2b, 0xc2, 0xf3, 0xb7, 0x3a, 0xfb, 0x48, 0xe7, 0x86, 0x1a, 0x4f, 0x00, 0x66, 0x73, 0xae, - 0xf4, 0xdd, 0x1b, 0xe5, 0xe8, 0x31, 0x6b, 0x47, 0x78, 0x2e, 0xb3, 0x8c, 0xaa, 0xb7, 0x36, 0x61, 0xd4, 0x8d, 0xb0, - 0xa0, 0x5f, 0xe9, 0x67, 0xe9, 0x37, 0xd3, 0x98, 0xd1, 0xb1, 0x49, 0x33, 0x38, 0x1c, 0xe1, 0x77, 0x63, 0xb8, 0x8c, - 0x4c, 0xa3, 0xc9, 0x78, 0xd2, 0x05, 0xf0, 0x00, 0x01, 0xb2, 0xd8, 0x0d, 0xd0, 0x80, 0xaf, 0xf1, 0xa3, 0x51, 0x1a, - 0x9d, 0x8f, 0x2e, 0x58, 0xe7, 0x34, 0xc2, 0x25, 0x35, 0xa2, 0x5d, 0xc8, 0x37, 0x9f, 0x1f, 0xcd, 0x96, 0x3a, 0xb3, - 0x09, 0x06, 0x40, 0x63, 0x7a, 0xbf, 0x35, 0x3e, 0x8f, 0xf0, 0xe2, 0x35, 0xf3, 0x7b, 0x8c, 0x31, 0x76, 0x01, 0xb0, - 0x84, 0x24, 0x83, 0x40, 0x17, 0x93, 0xd1, 0x83, 0x0b, 0xf3, 0x0d, 0x60, 0xa0, 0x13, 0xc6, 0x00, 0x48, 0x8b, 0xd7, - 0xac, 0x04, 0xc4, 0x78, 0x74, 0xbf, 0x05, 0xf4, 0x65, 0x41, 0x17, 0xf4, 0x8e, 0xde, 0x3c, 0x5d, 0x98, 0x39, 0x4d, - 0xc6, 0xdd, 0x08, 0x2f, 0x9e, 0xff, 0xbc, 0x58, 0x4e, 0x26, 0x66, 0x42, 0x74, 0xf4, 0x20, 0xc2, 0x0b, 0x56, 0x2c, - 0x61, 0x8d, 0x2e, 0xba, 0xa7, 0x93, 0x08, 0x3b, 0x34, 0xcc, 0x5a, 0xd9, 0x08, 0x6e, 0x5b, 0x97, 0xf3, 0x34, 0x1a, - 0x8f, 0x69, 0x6b, 0x0c, 0x77, 0xaf, 0xf2, 0xe6, 0x97, 0xc2, 0xa2, 0x11, 0x33, 0xf8, 0xe0, 0xd6, 0x10, 0xe6, 0x0b, - 0xf0, 0xf8, 0x38, 0x62, 0x59, 0x46, 0x5d, 0xe2, 0xf9, 0xf9, 0xe9, 0x29, 0xe0, 0x9e, 0x9d, 0xa1, 0x45, 0x90, 0x37, - 0xea, 0x6e, 0x54, 0x48, 0x38, 0xba, 0x80, 0xa8, 0x02, 0x59, 0x7d, 0x73, 0xf7, 0xda, 0xd0, 0xd5, 0xf6, 0xf9, 0x03, - 0x58, 0x00, 0x45, 0xc7, 0xe3, 0x57, 0xf6, 0x70, 0xbb, 0x18, 0x9d, 0x75, 0xdb, 0xa7, 0x11, 0xf6, 0x1b, 0x81, 0x5e, - 0xb4, 0xee, 0x77, 0xa0, 0x84, 0x18, 0xdf, 0xd9, 0x12, 0x93, 0x33, 0x7a, 0x76, 0xde, 0x8a, 0xb0, 0xdf, 0x1a, 0xec, - 0x62, 0xd4, 0xbd, 0x0f, 0x9f, 0x6a, 0xc6, 0xf2, 0xdc, 0xe0, 0x77, 0x17, 0xe0, 0xa2, 0xf8, 0x33, 0x41, 0xd3, 0x88, - 0xb6, 0xba, 0x9d, 0xce, 0x18, 0x3e, 0xf3, 0xaf, 0xac, 0x48, 0xa3, 0xac, 0x05, 0xff, 0x45, 0x38, 0xd8, 0x49, 0x6c, - 0x14, 0x61, 0x83, 0x77, 0xe7, 0xb4, 0x6b, 0xf6, 0xbe, 0xdb, 0x55, 0xad, 0x8b, 0x16, 0x6c, 0x58, 0xb7, 0xa9, 0xdc, - 0x97, 0x12, 0xf2, 0xc6, 0x91, 0x58, 0x1a, 0xe1, 0x00, 0x41, 0x27, 0xf7, 0x27, 0x11, 0xf6, 0x3b, 0xee, 0xec, 0xfc, - 0xa2, 0x03, 0xa4, 0x4c, 0x03, 0xa1, 0x18, 0x77, 0x46, 0x67, 0x40, 0x9a, 0x34, 0x7b, 0x6d, 0xf1, 0x24, 0xc2, 0xfa, - 0xa9, 0xd2, 0xaf, 0xd2, 0x68, 0x7c, 0x31, 0x9a, 0x8c, 0x2f, 0x22, 0xac, 0xe5, 0x9c, 0x6a, 0x69, 0x28, 0xe0, 0xe9, - 0xd9, 0xfd, 0x08, 0x1b, 0x34, 0x6f, 0xb1, 0xd6, 0xb8, 0x15, 0x61, 0x77, 0x94, 0x30, 0x76, 0xd1, 0x81, 0x69, 0xfd, - 0xf4, 0x5c, 0x03, 0x2e, 0x8f, 0xd9, 0xe8, 0x34, 0xc2, 0x25, 0xbd, 0x37, 0x84, 0x08, 0xbe, 0xd4, 0x5c, 0x7e, 0x71, - 0xac, 0x07, 0x90, 0x3a, 0xbf, 0xe1, 0x61, 0x19, 0x5e, 0xde, 0x58, 0x34, 0xa2, 0x66, 0x8b, 0x07, 0xb7, 0xd1, 0x4f, - 0x68, 0xec, 0xd9, 0x76, 0x4e, 0x56, 0x1b, 0x5c, 0x06, 0x79, 0xfd, 0xc2, 0xee, 0x54, 0x2c, 0x95, 0xe1, 0x64, 0x83, - 0x14, 0xa5, 0x90, 0x77, 0x6b, 0x70, 0x9e, 0xab, 0x20, 0x48, 0x0a, 0xd2, 0xea, 0x89, 0x4b, 0xef, 0x4d, 0xdb, 0x13, - 0x10, 0xfa, 0x01, 0xd2, 0x0b, 0x42, 0x89, 0x86, 0x08, 0x39, 0x56, 0x98, 0xf4, 0x4e, 0x06, 0x46, 0xa6, 0x94, 0xd6, - 0x6d, 0x81, 0x12, 0xea, 0x63, 0xe3, 0xc7, 0x12, 0x2b, 0x88, 0x1e, 0x85, 0x7a, 0x92, 0x98, 0x48, 0xd7, 0x2f, 0x84, - 0x8e, 0xa5, 0x1a, 0x14, 0x43, 0xdc, 0x3e, 0x47, 0x18, 0x62, 0x48, 0x90, 0x81, 0xbc, 0xba, 0x6a, 0x9f, 0x1f, 0x19, - 0xa1, 0xef, 0xea, 0xea, 0xc2, 0xfe, 0x80, 0x7f, 0x87, 0x55, 0xdc, 0x6e, 0x18, 0xdf, 0x07, 0x56, 0xcd, 0xf1, 0x9d, - 0xe1, 0xaf, 0x3f, 0xb0, 0xf5, 0x3a, 0xfe, 0xc0, 0x08, 0xcc, 0x18, 0x7f, 0x60, 0x89, 0xb9, 0x23, 0xb1, 0x1e, 0x42, - 0x64, 0x00, 0x9a, 0xb3, 0x16, 0x86, 0x68, 0xf2, 0x9e, 0xf3, 0xfe, 0xc0, 0x06, 0xbc, 0xee, 0x5d, 0x5e, 0x85, 0x70, - 0x3e, 0x3a, 0x5a, 0x15, 0xa9, 0xb6, 0x62, 0x82, 0xb6, 0x62, 0x82, 0xb6, 0x62, 0x82, 0xae, 0x82, 0xe8, 0x9f, 0xf5, - 0x41, 0x4a, 0x31, 0xca, 0x16, 0xc7, 0x53, 0xbf, 0x04, 0xb5, 0x07, 0x68, 0x27, 0xfb, 0x95, 0xb2, 0xa3, 0xd4, 0x55, - 0xec, 0x55, 0x60, 0xec, 0x4d, 0x74, 0xda, 0x8e, 0x93, 0x7f, 0x47, 0xdd, 0xf1, 0xb6, 0x26, 0x96, 0xbd, 0xdc, 0x2b, - 0x96, 0xc1, 0x4a, 0x1a, 0xd1, 0xec, 0xd0, 0xc6, 0x23, 0xd1, 0x83, 0xfb, 0x46, 0x30, 0xab, 0x82, 0xe4, 0x35, 0x20, - 0xa9, 0x07, 0x52, 0xc8, 0x85, 0x91, 0xd2, 0x0a, 0x94, 0x8e, 0x75, 0x5c, 0x80, 0x86, 0xd2, 0x2b, 0x28, 0xcb, 0x58, - 0xae, 0x0d, 0x03, 0x10, 0x65, 0x65, 0x34, 0x2b, 0xab, 0x75, 0x41, 0x74, 0x01, 0x4d, 0x98, 0x91, 0x58, 0xa0, 0x01, - 0x61, 0x1a, 0x10, 0xae, 0x32, 0x88, 0x33, 0x2e, 0xfb, 0xcc, 0x64, 0x2b, 0x93, 0xad, 0xca, 0x6c, 0xe9, 0xb3, 0xad, - 0x90, 0x28, 0x4d, 0xb6, 0x2c, 0xb3, 0x41, 0x66, 0xc3, 0xd3, 0x54, 0xe1, 0x51, 0x2a, 0xad, 0xa8, 0x56, 0xc9, 0x56, - 0xcf, 0x68, 0xa8, 0xcd, 0x3d, 0x3a, 0x8a, 0x4b, 0x39, 0xc9, 0xa8, 0x89, 0xef, 0xad, 0x78, 0x52, 0x18, 0x19, 0x88, - 0x27, 0x53, 0xf7, 0x77, 0xb4, 0xd9, 0x96, 0x95, 0x8a, 0xe9, 0xe8, 0x1b, 0x25, 0xd1, 0x9f, 0x5e, 0x89, 0xfa, 0x81, - 0x9b, 0x28, 0x40, 0x97, 0x24, 0x69, 0xb5, 0x4e, 0xdb, 0xa7, 0xad, 0x8b, 0x3e, 0x3f, 0x6e, 0x77, 0x92, 0x07, 0x9d, - 0xd4, 0x28, 0x22, 0x16, 0xf2, 0x06, 0x14, 0x30, 0x27, 0x9d, 0xe4, 0x0c, 0x1d, 0xb7, 0x93, 0x56, 0xb7, 0xdb, 0x84, - 0x7f, 0xf0, 0x23, 0x5d, 0x56, 0x3b, 0x6b, 0x9d, 0x75, 0xfb, 0xfc, 0x64, 0xab, 0x52, 0xcc, 0x1b, 0x50, 0x10, 0x9d, - 0x98, 0x4a, 0x18, 0xea, 0x57, 0xcb, 0xfb, 0x6a, 0x47, 0xcf, 0xf3, 0x48, 0xc7, 0xd2, 0xaa, 0xe2, 0x00, 0xaa, 0xfe, - 0x6b, 0x6a, 0x80, 0xe8, 0xbf, 0x46, 0x65, 0xa4, 0xde, 0x55, 0x01, 0xa2, 0xf6, 0x07, 0x1e, 0x8b, 0x06, 0x3b, 0x8e, - 0x6d, 0xbe, 0x86, 0xba, 0x4d, 0x88, 0x9e, 0x87, 0xa7, 0x2e, 0x57, 0x85, 0xb9, 0x53, 0x84, 0x9a, 0x0a, 0x72, 0x47, - 0x2e, 0x57, 0x86, 0xb9, 0x23, 0x84, 0x9a, 0x12, 0x72, 0x69, 0xca, 0x13, 0x0a, 0x39, 0x3a, 0xa1, 0x4d, 0x03, 0xc9, - 0x6a, 0x51, 0x9e, 0x33, 0x3f, 0x6c, 0x3e, 0x81, 0xe5, 0x31, 0x04, 0xc5, 0x09, 0xd2, 0x02, 0x5e, 0x58, 0x29, 0xb5, - 0x39, 0x2d, 0x5c, 0xaa, 0x71, 0x20, 0xa3, 0x01, 0xff, 0x1c, 0x33, 0xf3, 0xec, 0x46, 0xab, 0x7f, 0x7a, 0xde, 0x4a, - 0xdb, 0xe0, 0x2a, 0x0e, 0xb2, 0xb6, 0xb0, 0xb2, 0xb6, 0xf0, 0xb2, 0xb6, 0xf0, 0xb2, 0x36, 0x08, 0xf0, 0x41, 0xdf, - 0xff, 0x94, 0x35, 0xf3, 0x1b, 0x5e, 0xda, 0xf2, 0x58, 0x63, 0x8d, 0x58, 0xaf, 0xd7, 0xab, 0x0d, 0x58, 0x5a, 0x95, - 0x35, 0x0a, 0x55, 0xa9, 0x3f, 0x57, 0x45, 0xda, 0xc2, 0xd3, 0x14, 0xb4, 0xdc, 0x2d, 0x4c, 0xcd, 0xe6, 0xf6, 0x54, - 0x61, 0x3b, 0x8a, 0x4f, 0xdf, 0xab, 0x93, 0xaf, 0xc8, 0xa9, 0xd1, 0x1e, 0xaf, 0x8a, 0x94, 0x5b, 0x9a, 0xc1, 0x2d, - 0xcd, 0xe0, 0x96, 0x66, 0x40, 0x23, 0xb8, 0x2c, 0x6c, 0xca, 0x26, 0x94, 0xc0, 0x95, 0xc0, 0xe0, 0x74, 0x08, 0x41, - 0x0c, 0x63, 0x4d, 0xcc, 0xa8, 0xb7, 0x3a, 0x6f, 0x43, 0xd0, 0x36, 0x5b, 0x52, 0x27, 0xd4, 0xf8, 0xae, 0x97, 0x63, - 0xfe, 0xbb, 0x86, 0xf6, 0x09, 0xbc, 0xa8, 0xf3, 0x50, 0xc7, 0x2d, 0x30, 0x5d, 0x89, 0x8a, 0xa8, 0x6f, 0xc8, 0x42, - 0x6a, 0x74, 0x36, 0xce, 0x24, 0xfd, 0xcb, 0x96, 0x27, 0xb0, 0xa5, 0x04, 0xe1, 0x3b, 0x12, 0x5f, 0x58, 0x15, 0x9a, - 0xa0, 0xb4, 0xb8, 0x75, 0xe6, 0x72, 0xf6, 0x48, 0xe8, 0x81, 0xd9, 0xbc, 0x8f, 0x79, 0xd5, 0x17, 0xa4, 0x80, 0x98, - 0x8f, 0xa9, 0x49, 0x74, 0x51, 0x9b, 0xc1, 0x89, 0x99, 0x7c, 0xa5, 0xc6, 0xa5, 0xe7, 0x9d, 0xfd, 0xf3, 0x37, 0x0d, - 0x7c, 0x1e, 0x8b, 0xe9, 0xc8, 0xbb, 0x0a, 0x7f, 0x32, 0xb1, 0x8d, 0xc8, 0xe1, 0xa1, 0xb5, 0x68, 0x37, 0x5f, 0xdb, - 0x26, 0xed, 0x26, 0xd1, 0x64, 0xc3, 0x0e, 0xf5, 0x6b, 0xf4, 0x4f, 0xef, 0xb1, 0x57, 0x4c, 0x47, 0x28, 0xa0, 0xd9, - 0x06, 0xac, 0xb2, 0x02, 0x96, 0x72, 0xf5, 0x4a, 0x47, 0x4e, 0xe8, 0xdd, 0x8c, 0x79, 0x53, 0x4c, 0x47, 0x7b, 0x9f, - 0x5e, 0xb1, 0x3d, 0xf6, 0x9f, 0xd1, 0xa0, 0x07, 0xaf, 0xda, 0x9e, 0xb1, 0xdb, 0xef, 0xd5, 0xf9, 0xb2, 0xb7, 0x8e, - 0xca, 0xbf, 0x57, 0xe7, 0xc5, 0xbe, 0x3a, 0x73, 0x7e, 0x1b, 0xfb, 0xbd, 0xa3, 0x03, 0x35, 0xb6, 0x31, 0x93, 0x9a, - 0x8e, 0x20, 0x56, 0x3e, 0xfc, 0xb5, 0x11, 0x6d, 0x7a, 0x9e, 0x84, 0xc3, 0x2a, 0xc8, 0x7e, 0xd2, 0x4d, 0x19, 0xa6, - 0xa4, 0x73, 0x5c, 0x98, 0x98, 0x36, 0x22, 0xa1, 0x4d, 0x95, 0x50, 0x9c, 0x93, 0x38, 0xa6, 0xc7, 0x19, 0x44, 0xe6, - 0x69, 0xf7, 0x69, 0x1a, 0xd3, 0x46, 0x86, 0x4e, 0xe2, 0x76, 0x83, 0x1e, 0x67, 0x08, 0x35, 0xda, 0xa0, 0x33, 0x95, - 0xa4, 0xdd, 0xcc, 0x21, 0x56, 0xa7, 0x21, 0xc5, 0xf9, 0xb1, 0x48, 0x8a, 0x86, 0x3c, 0x56, 0x49, 0xd1, 0x48, 0xba, - 0x58, 0x24, 0xd3, 0x32, 0x79, 0x6a, 0x92, 0xa7, 0x36, 0x79, 0x54, 0x26, 0x8f, 0x4c, 0xf2, 0xc8, 0x26, 0x53, 0x52, - 0x1c, 0x8b, 0x84, 0x36, 0xe2, 0x76, 0xb3, 0x40, 0xc7, 0x30, 0x02, 0x3f, 0x7a, 0x22, 0xc2, 0x10, 0xe9, 0x1b, 0x63, - 0x63, 0xb4, 0x90, 0xb9, 0x0b, 0x5a, 0x5a, 0x01, 0xa9, 0x74, 0xfc, 0x82, 0x3a, 0xaf, 0x02, 0x30, 0x61, 0x6d, 0xff, - 0xf8, 0x90, 0x7c, 0x9b, 0x2c, 0x97, 0x22, 0x70, 0x6c, 0x03, 0x5b, 0xfc, 0x2f, 0xce, 0x9d, 0x07, 0xa0, 0xba, 0xa1, - 0xf9, 0x62, 0x46, 0x77, 0xbc, 0x87, 0x8b, 0xe9, 0xc8, 0xed, 0xac, 0xb2, 0x19, 0x46, 0x0b, 0x1b, 0xea, 0xba, 0xee, - 0xe7, 0x09, 0xa0, 0xf6, 0xbe, 0xa5, 0x09, 0x35, 0x4a, 0x72, 0x5b, 0x63, 0x5a, 0xb0, 0x3b, 0x95, 0xd1, 0x9c, 0xc5, - 0xd5, 0x01, 0x5c, 0x0d, 0x93, 0x91, 0x27, 0xe0, 0x11, 0x50, 0x1c, 0x27, 0xa7, 0x0d, 0x9d, 0x4c, 0x8f, 0x93, 0xee, - 0x83, 0x86, 0x4e, 0x46, 0xc7, 0x49, 0xbb, 0x5d, 0xe1, 0x6c, 0x52, 0x10, 0x9d, 0x4c, 0x89, 0x06, 0x8d, 0xa1, 0x6d, - 0x54, 0x2e, 0x28, 0x98, 0xb8, 0xfd, 0x1b, 0xc3, 0x68, 0xb8, 0x61, 0x08, 0x36, 0xb5, 0x51, 0x3f, 0x77, 0xc6, 0x10, - 0x76, 0xd3, 0xe9, 0x76, 0x9b, 0x3a, 0x29, 0xb0, 0xb6, 0x2b, 0xd9, 0xd4, 0xc9, 0x14, 0x6b, 0xbb, 0x7c, 0x4d, 0x9d, - 0x8c, 0x6c, 0x53, 0x46, 0x07, 0xc8, 0x44, 0x00, 0xac, 0xe7, 0x2c, 0x80, 0x7c, 0xc7, 0x3b, 0xe9, 0x6c, 0x40, 0x6b, - 0xf8, 0xbd, 0x72, 0x4d, 0x5f, 0x50, 0x51, 0x0d, 0xa6, 0x4e, 0xec, 0x5b, 0x45, 0xdb, 0x55, 0x93, 0xec, 0x5f, 0x97, - 0x2d, 0x9b, 0x2d, 0xa4, 0xae, 0x17, 0x7c, 0x5a, 0xc3, 0x10, 0x57, 0xca, 0x1d, 0xdc, 0x9f, 0x29, 0x89, 0x21, 0xb6, - 0x9f, 0x39, 0x85, 0x38, 0xf1, 0x7a, 0x64, 0x48, 0xe2, 0x8d, 0xc6, 0x06, 0xc5, 0xc1, 0x79, 0xfb, 0x34, 0xa4, 0xaa, - 0x3b, 0x01, 0xff, 0x08, 0x89, 0x96, 0xc2, 0x9a, 0x84, 0x8e, 0xa3, 0x8a, 0x16, 0xbf, 0x75, 0xda, 0xdd, 0xda, 0x01, - 0x71, 0x74, 0xb4, 0x7d, 0x5e, 0xf8, 0xa7, 0x17, 0x76, 0x9e, 0x5b, 0xa8, 0xec, 0x09, 0xfd, 0x83, 0x50, 0xd6, 0xd2, - 0x98, 0x07, 0x88, 0xe2, 0x43, 0x6f, 0xdd, 0x37, 0x14, 0x7e, 0x50, 0xc5, 0x1d, 0x74, 0x39, 0xcd, 0x73, 0x93, 0x61, - 0xfa, 0x1a, 0x06, 0x63, 0x7b, 0x13, 0x4e, 0xa8, 0xb4, 0x95, 0xfc, 0x97, 0x1d, 0x07, 0x9d, 0xb8, 0x07, 0x6b, 0xc2, - 0x46, 0x3f, 0x87, 0x96, 0xc9, 0x15, 0x6c, 0x9c, 0x4f, 0xfa, 0x7a, 0x5d, 0x7b, 0x9e, 0xc8, 0x3e, 0x82, 0x83, 0x8e, - 0x8e, 0xb8, 0x7a, 0x06, 0xc6, 0xd4, 0x2c, 0x6e, 0x84, 0x87, 0xef, 0x5f, 0xb5, 0xd3, 0xfa, 0xb3, 0x39, 0x57, 0xd3, - 0xe0, 0xa0, 0x7b, 0x58, 0xcb, 0xdf, 0xbb, 0x12, 0x7d, 0x9d, 0x72, 0xb7, 0xd6, 0x8f, 0x2a, 0x53, 0xf5, 0x9d, 0x87, - 0xb2, 0x8e, 0x8e, 0x78, 0x15, 0xae, 0x2a, 0xfa, 0x21, 0x42, 0x7d, 0x23, 0x83, 0x3c, 0xcb, 0x25, 0x85, 0x1b, 0x51, - 0xb8, 0x62, 0x48, 0x1b, 0xfc, 0x44, 0xe3, 0x9f, 0xe5, 0xff, 0xa7, 0x46, 0x8e, 0x75, 0xda, 0xe0, 0x15, 0x4a, 0xbd, - 0x08, 0x59, 0xa1, 0x2a, 0x50, 0xa4, 0x81, 0x74, 0x68, 0x79, 0x8e, 0xca, 0xc3, 0x9c, 0x2e, 0x16, 0xf9, 0x9d, 0x79, - 0x2b, 0x2c, 0xe0, 0xa8, 0xaa, 0x8b, 0x26, 0x17, 0xa5, 0x0f, 0x17, 0xc0, 0xd3, 0x03, 0xee, 0x21, 0xe3, 0x65, 0x5b, - 0x5e, 0x6e, 0x0b, 0x04, 0x92, 0x99, 0x22, 0xb2, 0xd9, 0xee, 0xa9, 0x2b, 0x90, 0xcb, 0x9a, 0x4d, 0xa4, 0x5d, 0xf0, - 0x72, 0xcc, 0x41, 0x26, 0x53, 0xd6, 0x93, 0xf6, 0xc0, 0x16, 0x04, 0xc9, 0x4d, 0x1a, 0x91, 0x6d, 0x7f, 0x29, 0x3e, - 0x89, 0x01, 0x8d, 0x90, 0x15, 0xf8, 0x42, 0x61, 0x91, 0x03, 0xd7, 0x59, 0xf8, 0x8e, 0xbf, 0xd1, 0x52, 0x31, 0x50, - 0xc3, 0x21, 0x2e, 0xcc, 0xf3, 0x18, 0xe5, 0x7c, 0xa8, 0x0a, 0x9e, 0x5b, 0x0a, 0x44, 0x14, 0xbe, 0x5e, 0x1f, 0xc2, - 0x6b, 0x46, 0xae, 0x4d, 0x70, 0xbd, 0x75, 0x3f, 0xab, 0x97, 0x4b, 0x60, 0x1c, 0x8c, 0xb4, 0xcc, 0x45, 0xa1, 0x93, - 0x37, 0xd9, 0xa5, 0xe8, 0x35, 0x1a, 0xcc, 0x04, 0x9a, 0x22, 0x10, 0x55, 0x0e, 0xfc, 0x22, 0xe1, 0x8f, 0x8d, 0x1d, - 0xa5, 0x98, 0x8d, 0xc0, 0x07, 0xa1, 0xc1, 0x6b, 0x09, 0xeb, 0xb5, 0xb2, 0x11, 0x5e, 0x4c, 0x8e, 0x8d, 0xf5, 0x52, - 0xf6, 0x53, 0x86, 0x92, 0xad, 0xcc, 0x38, 0xb8, 0xdb, 0xea, 0x6f, 0xab, 0xfd, 0x7c, 0xc0, 0xed, 0x35, 0x1e, 0x37, - 0x71, 0x13, 0x0c, 0xa0, 0x56, 0x5b, 0x1b, 0xdc, 0xda, 0xf9, 0xc7, 0xd6, 0x28, 0x99, 0x6d, 0x43, 0x50, 0x94, 0x71, - 0x02, 0xec, 0xcd, 0xad, 0x8f, 0x9b, 0xa8, 0xcc, 0x9c, 0x14, 0xd2, 0x03, 0x90, 0xa3, 0x87, 0x04, 0x3a, 0xb7, 0x3f, - 0x2b, 0xba, 0x50, 0xc9, 0xc4, 0xe5, 0x18, 0xff, 0x11, 0xdc, 0xe6, 0x0d, 0xa2, 0x4f, 0x9f, 0xcc, 0x26, 0xff, 0xf4, - 0x29, 0xc2, 0xa1, 0x71, 0x7d, 0x14, 0xf0, 0x82, 0xd1, 0xb0, 0x0c, 0xad, 0x65, 0x36, 0x7e, 0xb3, 0x5d, 0x35, 0xf6, - 0x81, 0x56, 0x78, 0x07, 0xcb, 0x63, 0x1a, 0xdf, 0x71, 0x46, 0x1d, 0x70, 0x80, 0x37, 0x1b, 0xf0, 0x61, 0xef, 0x4d, - 0xac, 0xd0, 0xd1, 0xd1, 0x9b, 0x58, 0xa2, 0xfe, 0x35, 0x33, 0x77, 0x6e, 0xe0, 0x8d, 0x3e, 0xe0, 0x66, 0xf8, 0x32, - 0x40, 0x80, 0x6b, 0xb6, 0x2d, 0xd9, 0xbc, 0x35, 0xb1, 0x3f, 0x52, 0x88, 0x2d, 0x0e, 0x11, 0x8e, 0x1d, 0x48, 0xa0, - 0xd7, 0x37, 0x21, 0xb4, 0x7b, 0x8c, 0x30, 0x60, 0xe1, 0x4b, 0x5f, 0x41, 0x96, 0xcc, 0x59, 0x31, 0x65, 0xc5, 0x7a, - 0xfd, 0x81, 0x5a, 0xff, 0xbf, 0xad, 0x50, 0x95, 0xaa, 0xd7, 0x68, 0x50, 0x33, 0x7e, 0x10, 0x1f, 0xe8, 0x10, 0x1f, - 0xbe, 0x89, 0x0b, 0x84, 0xc0, 0xc2, 0x88, 0x8b, 0xa5, 0xf7, 0x75, 0xcb, 0x6a, 0xeb, 0x52, 0xa0, 0xb2, 0x91, 0x9c, - 0xb4, 0xf0, 0x8c, 0x64, 0xe5, 0x1a, 0x5d, 0xce, 0x7a, 0x8d, 0x46, 0x8e, 0x64, 0x9c, 0x0d, 0xf2, 0x21, 0xe6, 0xb8, - 0x80, 0xcb, 0xd4, 0xdd, 0x75, 0x58, 0xb0, 0x1a, 0xe5, 0x72, 0xf3, 0x5d, 0xd9, 0xb1, 0xa6, 0xcf, 0xe9, 0x26, 0xdc, - 0xdd, 0x34, 0x20, 0x12, 0xfb, 0x80, 0x2c, 0x2c, 0x90, 0x95, 0x07, 0xb2, 0x30, 0x40, 0x56, 0xa8, 0xbf, 0x80, 0xa0, - 0x4d, 0x0a, 0xa5, 0x3b, 0x14, 0xbd, 0x1e, 0x5e, 0xd4, 0xb9, 0xae, 0x60, 0x6e, 0x22, 0x5c, 0xb8, 0xe5, 0x00, 0x37, - 0x16, 0x37, 0x77, 0x45, 0x56, 0x51, 0x64, 0x22, 0xed, 0xe2, 0x5b, 0xf3, 0x27, 0xb9, 0xc5, 0x77, 0xf6, 0xc7, 0x5d, - 0xa0, 0x4c, 0x7a, 0x5f, 0xd3, 0x36, 0x70, 0x17, 0x97, 0x2e, 0x4a, 0x22, 0x40, 0x6b, 0x17, 0x64, 0x51, 0xd4, 0xdf, - 0x9d, 0x53, 0x36, 0x1c, 0x86, 0x68, 0x10, 0x85, 0x45, 0x40, 0x3a, 0xff, 0xf8, 0x23, 0x42, 0x7d, 0x01, 0xd1, 0x8c, - 0xdc, 0xc9, 0xd6, 0x6c, 0xa3, 0x46, 0x94, 0x44, 0x69, 0xec, 0x83, 0x65, 0xc0, 0xce, 0x88, 0xa2, 0xe0, 0xcd, 0x99, - 0x2a, 0xca, 0x5a, 0x6d, 0x18, 0x66, 0x50, 0x55, 0xf8, 0x8f, 0xab, 0xd5, 0x76, 0xb0, 0x25, 0x03, 0x55, 0x61, 0x22, - 0xdd, 0x20, 0xfb, 0x10, 0x1b, 0x23, 0xec, 0xe8, 0x88, 0x0d, 0xc4, 0x30, 0x78, 0x59, 0xad, 0xb2, 0x20, 0xd1, 0xe1, - 0xc2, 0xc5, 0x19, 0x44, 0xbb, 0x5f, 0xaf, 0xed, 0x5f, 0xf2, 0x9b, 0x91, 0x66, 0xe0, 0x89, 0xbc, 0xe0, 0x8c, 0x15, - 0xfb, 0x65, 0xb1, 0x44, 0xcb, 0xf7, 0x60, 0xd9, 0xe7, 0x62, 0x17, 0x72, 0x37, 0xd5, 0x76, 0xe9, 0x82, 0x63, 0x34, - 0x0a, 0x41, 0xe4, 0xe0, 0xea, 0x48, 0xc3, 0x0b, 0x1d, 0xe6, 0xd5, 0x22, 0x00, 0xe7, 0xaa, 0x0c, 0xe4, 0x0a, 0x47, - 0x4a, 0x02, 0x96, 0xde, 0x86, 0x4e, 0xc2, 0x8f, 0x3a, 0x95, 0x74, 0x2c, 0x24, 0x40, 0x81, 0x23, 0x73, 0x39, 0x6f, - 0x02, 0xf5, 0x33, 0xb4, 0x87, 0xc8, 0x05, 0x26, 0x34, 0x75, 0xd9, 0xd2, 0x45, 0xd4, 0x8a, 0xe6, 0x72, 0xa9, 0xd8, - 0x72, 0x01, 0xe7, 0x7b, 0x99, 0x96, 0xe5, 0x3c, 0xfb, 0x52, 0x4f, 0x01, 0x83, 0xc8, 0x5b, 0x3d, 0x67, 0x62, 0x19, - 0xb9, 0x79, 0xbe, 0xb2, 0xe2, 0xfe, 0x9b, 0x17, 0xf8, 0x03, 0xe9, 0x1c, 0xbf, 0xc2, 0x7f, 0x51, 0xf2, 0xa1, 0xf1, - 0x0a, 0x4f, 0x39, 0xb1, 0xbc, 0x41, 0xf2, 0xe6, 0xf5, 0xf5, 0x8b, 0x77, 0x2f, 0x3e, 0x3c, 0xfd, 0xf4, 0xe2, 0xd5, - 0xb3, 0x17, 0xaf, 0x5e, 0xbc, 0xfb, 0x88, 0xff, 0x49, 0xc9, 0xab, 0x93, 0xf6, 0x45, 0x0b, 0xbf, 0x27, 0xaf, 0x4e, - 0x3a, 0xf8, 0x56, 0x93, 0x57, 0x27, 0x67, 0x78, 0xa6, 0xc8, 0xab, 0xe3, 0xce, 0xc9, 0x29, 0x5e, 0x6a, 0xdb, 0x64, - 0x2e, 0xa7, 0xed, 0x16, 0xfe, 0xcb, 0x7d, 0x81, 0x78, 0x1f, 0xb8, 0xe1, 0xb0, 0x2d, 0xe3, 0x07, 0x53, 0x86, 0x8e, - 0x94, 0x31, 0x44, 0xb9, 0x0c, 0xd0, 0x69, 0xac, 0x42, 0x74, 0xb2, 0xa1, 0xa4, 0xc1, 0x86, 0x11, 0xd0, 0x8a, 0x13, - 0xd7, 0x0e, 0x3f, 0x69, 0xb3, 0x53, 0xa0, 0x4f, 0xbc, 0x14, 0x8e, 0x4b, 0x15, 0x4e, 0xdb, 0x69, 0x31, 0x26, 0xb9, - 0x94, 0x45, 0xbc, 0x04, 0x46, 0xc0, 0x68, 0x2d, 0xf8, 0x49, 0x19, 0xb3, 0x4a, 0x5c, 0x92, 0x76, 0xbf, 0x9d, 0x8a, - 0x4b, 0xd2, 0xe9, 0x77, 0xe0, 0x4f, 0xb7, 0xdf, 0x4d, 0xdb, 0x2d, 0x74, 0x1c, 0x8c, 0xe3, 0xe7, 0x1a, 0x5a, 0x0f, - 0x86, 0xd8, 0x75, 0xa1, 0xfe, 0x2a, 0xb4, 0x57, 0xe9, 0x09, 0xa7, 0x8e, 0x6d, 0xf7, 0xc4, 0x25, 0x33, 0x7a, 0x58, - 0xfe, 0x03, 0xa0, 0xb6, 0x71, 0xab, 0x29, 0x37, 0x8e, 0xfb, 0xc5, 0x4f, 0x04, 0xaa, 0x05, 0xc6, 0x89, 0xd9, 0xba, - 0x85, 0x80, 0x69, 0x34, 0xd9, 0x60, 0x0e, 0x94, 0x28, 0x59, 0x68, 0x1f, 0xdc, 0x5f, 0x35, 0x25, 0x4a, 0x16, 0x72, - 0x11, 0xd7, 0x54, 0x0d, 0xbf, 0x04, 0x66, 0x8e, 0x87, 0x5c, 0xbd, 0xa2, 0xaf, 0xe2, 0x1a, 0xcf, 0x13, 0xb2, 0x76, - 0xe1, 0xb6, 0xf8, 0x87, 0xb3, 0xa2, 0xa8, 0x81, 0xab, 0x04, 0xac, 0x1f, 0x55, 0x53, 0x5f, 0xc2, 0x2b, 0x86, 0xac, - 0xa1, 0xaf, 0x48, 0x40, 0x3d, 0x7f, 0x2d, 0xcd, 0xb8, 0x4a, 0x65, 0xb4, 0x57, 0x44, 0x1b, 0xb3, 0x20, 0xaf, 0x88, - 0xbe, 0x54, 0x06, 0x08, 0x92, 0xf0, 0x81, 0x18, 0xc2, 0x81, 0x6f, 0x07, 0x28, 0x0d, 0x9d, 0x03, 0xb5, 0x52, 0x65, - 0x26, 0x64, 0x3e, 0x4d, 0x88, 0x06, 0xd0, 0x3c, 0x55, 0x2a, 0x28, 0xf3, 0x89, 0x25, 0x0a, 0x86, 0xfe, 0x7b, 0xb8, - 0x01, 0x8e, 0x63, 0x83, 0x8a, 0xa1, 0x5d, 0x8d, 0xa8, 0xe7, 0xb7, 0x2f, 0x5a, 0x27, 0xaf, 0x82, 0xfc, 0xa5, 0xf2, - 0xf6, 0x1e, 0x9f, 0x03, 0x4a, 0x6e, 0x83, 0x8a, 0xb5, 0xb1, 0x8f, 0x07, 0xd7, 0x0b, 0x01, 0x72, 0xac, 0xd1, 0x89, - 0x79, 0xd0, 0xb1, 0x87, 0xf4, 0x31, 0x69, 0xb7, 0x20, 0x88, 0xdb, 0x1e, 0xca, 0xf7, 0xeb, 0x16, 0x4c, 0x75, 0x72, - 0xdb, 0x04, 0x5a, 0x0d, 0x6f, 0x3c, 0xdd, 0x35, 0x79, 0x72, 0x87, 0x55, 0x80, 0x33, 0xec, 0x98, 0x35, 0xc4, 0xb1, - 0x40, 0x2e, 0xf8, 0xad, 0xdd, 0x00, 0x9a, 0x8a, 0x8e, 0x7d, 0x6b, 0xd0, 0x1b, 0x47, 0x5d, 0x36, 0x93, 0xee, 0xf1, - 0xab, 0xa3, 0xa3, 0x58, 0x36, 0xc8, 0x07, 0x84, 0x57, 0x14, 0x6c, 0xb6, 0xc1, 0xf7, 0x8e, 0x5b, 0x26, 0x3e, 0x55, - 0x01, 0x75, 0x9c, 0xa8, 0xda, 0xb1, 0x56, 0x75, 0x56, 0xee, 0x06, 0x3f, 0xa6, 0x0e, 0x6a, 0x04, 0x69, 0x76, 0x74, - 0x9d, 0x1a, 0x94, 0x6b, 0x8e, 0x72, 0xb0, 0x2d, 0x1b, 0x7f, 0x51, 0xf4, 0xc3, 0x87, 0xe6, 0xab, 0x60, 0xc2, 0x35, - 0xd3, 0xa4, 0x0f, 0x8d, 0x0f, 0xe8, 0x87, 0x0f, 0x81, 0xab, 0x23, 0xaf, 0xd8, 0x13, 0xcf, 0x8d, 0xfc, 0x6a, 0xb9, - 0xd2, 0x5f, 0x41, 0xb2, 0x2f, 0xc8, 0xaf, 0x80, 0xe5, 0x94, 0xfc, 0x1a, 0xcb, 0x26, 0x84, 0x80, 0x24, 0xbf, 0xc6, - 0x05, 0xfc, 0xc8, 0xc9, 0xaf, 0x31, 0x60, 0x3b, 0x9e, 0x99, 0x1f, 0x45, 0x09, 0x0c, 0x70, 0xaf, 0x93, 0xd6, 0xcb, - 0xae, 0x58, 0xaf, 0xc5, 0xd1, 0x91, 0xb4, 0xbf, 0xe8, 0x55, 0x76, 0x74, 0x94, 0x5f, 0xce, 0xaa, 0xbe, 0xb9, 0xde, - 0x47, 0x5f, 0x0c, 0x42, 0xe1, 0xc0, 0x34, 0x8d, 0x87, 0x33, 0xfe, 0xa9, 0x46, 0x59, 0xa1, 0x81, 0xe6, 0x69, 0xe7, - 0xfe, 0xf9, 0x05, 0x86, 0x7f, 0xef, 0x07, 0x05, 0x75, 0xe6, 0x27, 0x46, 0xda, 0xac, 0x79, 0x5e, 0xd5, 0xb9, 0x0a, - 0xf0, 0x19, 0x33, 0xd4, 0x14, 0x47, 0x47, 0xfc, 0x32, 0xc0, 0x65, 0xcc, 0x50, 0x23, 0xb0, 0xd8, 0x7b, 0x58, 0xda, - 0x93, 0x19, 0xae, 0x09, 0x1e, 0xf7, 0xe5, 0x83, 0x62, 0x78, 0xa9, 0x1d, 0x35, 0x09, 0x43, 0x80, 0x2b, 0xd2, 0x72, - 0x9b, 0xac, 0x27, 0x9a, 0xea, 0xaa, 0xdd, 0x43, 0x92, 0xa8, 0x86, 0xb8, 0xba, 0x6a, 0x63, 0x50, 0xc9, 0xf7, 0x15, - 0x91, 0xa9, 0x20, 0xde, 0x4d, 0x71, 0x95, 0xcb, 0x54, 0xe1, 0x19, 0x4f, 0x85, 0x97, 0xb3, 0x5f, 0x7b, 0xeb, 0x69, - 0xe3, 0x38, 0x6a, 0x7a, 0x66, 0x58, 0xf4, 0x55, 0xe9, 0xf0, 0x08, 0x9b, 0x54, 0x0d, 0xe1, 0xed, 0xc4, 0x12, 0xf3, - 0x98, 0xf5, 0xf2, 0x63, 0x10, 0x9b, 0x5a, 0x35, 0xda, 0x90, 0x09, 0x9f, 0x9b, 0x54, 0xc1, 0x40, 0x4d, 0xe1, 0x4b, - 0x08, 0x7b, 0x98, 0x55, 0x86, 0xd9, 0xbe, 0x61, 0x28, 0x20, 0xa0, 0xc0, 0x15, 0x61, 0x81, 0x04, 0xcf, 0xb3, 0x1a, - 0xe1, 0xa8, 0x93, 0x0b, 0x3b, 0xb9, 0x4b, 0x05, 0xdd, 0x89, 0xe1, 0xa5, 0xee, 0x21, 0xd1, 0x68, 0x38, 0x6e, 0xfb, - 0x4a, 0x98, 0x41, 0x34, 0xdb, 0xc3, 0x2b, 0xd6, 0x43, 0xaa, 0xd9, 0x2c, 0x0d, 0x20, 0xaf, 0x5a, 0xeb, 0xb5, 0xba, - 0xf4, 0x8d, 0xf4, 0xfd, 0x39, 0x6e, 0xf8, 0x2e, 0x2f, 0x78, 0xfe, 0x2e, 0xc9, 0x20, 0x02, 0xaa, 0x0a, 0x7c, 0xb6, - 0x5c, 0x44, 0x38, 0x32, 0xcf, 0xea, 0xc1, 0x5f, 0xf3, 0x1c, 0x5a, 0x84, 0x23, 0xf7, 0xd2, 0x5e, 0x34, 0xac, 0x06, - 0x2b, 0xb2, 0x32, 0x48, 0x3c, 0x4f, 0x3e, 0x01, 0xe3, 0xa0, 0x3f, 0x2b, 0xb4, 0xaa, 0x7e, 0x27, 0xb9, 0x0b, 0x97, - 0xa2, 0xfc, 0xe3, 0x6f, 0x6e, 0x54, 0x9b, 0xfd, 0x0e, 0xaa, 0x1c, 0x47, 0xbe, 0x2a, 0x3c, 0xa2, 0xf0, 0x9d, 0xd7, - 0x27, 0xdb, 0xee, 0xd1, 0xf3, 0x55, 0xd9, 0x03, 0x70, 0xde, 0x9b, 0x0d, 0xc2, 0xbf, 0xcb, 0xbd, 0x2f, 0x20, 0x47, - 0x9f, 0xa4, 0x78, 0x42, 0x35, 0x8d, 0x1a, 0x6f, 0x8c, 0xe1, 0x9b, 0x95, 0xb3, 0x7a, 0xdf, 0x1a, 0x07, 0xfb, 0xb7, - 0xba, 0x87, 0x00, 0x16, 0xb5, 0xc7, 0x9a, 0xac, 0xec, 0x6b, 0xc2, 0x96, 0xc8, 0xc0, 0xf4, 0x6d, 0x0f, 0x3c, 0xfc, - 0x18, 0x29, 0xb8, 0x55, 0x5b, 0x3e, 0x89, 0x42, 0x64, 0xd8, 0x9a, 0x33, 0x37, 0xa4, 0xd8, 0x3e, 0x8c, 0xe3, 0xef, - 0x06, 0x85, 0x5c, 0xf7, 0x42, 0xd5, 0x89, 0x69, 0xd5, 0x8d, 0x91, 0x3a, 0xd8, 0x36, 0x0b, 0xce, 0xaa, 0xde, 0x8d, - 0x84, 0x52, 0xbd, 0x6b, 0x67, 0xde, 0x26, 0x6d, 0xb6, 0xcd, 0x63, 0xcf, 0xf6, 0xf5, 0x3b, 0x05, 0x86, 0xbc, 0x87, - 0x65, 0xd0, 0xae, 0x2b, 0x38, 0x76, 0xe3, 0x00, 0xb2, 0x92, 0x5c, 0xad, 0xdc, 0xcb, 0x74, 0x7c, 0x20, 0x87, 0x9b, - 0xf2, 0x9d, 0xba, 0x00, 0x0f, 0xaa, 0x91, 0xaa, 0x2c, 0xe4, 0x0c, 0xfc, 0x23, 0x8f, 0x35, 0xfd, 0x10, 0xff, 0x1b, - 0x0e, 0xf8, 0x0a, 0x49, 0x53, 0xab, 0x7e, 0x82, 0xf7, 0xa3, 0x40, 0xe1, 0x6d, 0xeb, 0xfe, 0x24, 0x43, 0x47, 0xdd, - 0xba, 0x4e, 0xc5, 0xfa, 0xc2, 0xd6, 0x15, 0x2b, 0x65, 0xe1, 0x80, 0x6a, 0xc5, 0x68, 0x93, 0x3a, 0xbf, 0x59, 0xf7, - 0xe8, 0xd4, 0x43, 0x01, 0xbe, 0x31, 0x5c, 0x8a, 0x67, 0x05, 0x44, 0x11, 0x0b, 0xf5, 0x69, 0xba, 0x08, 0x5f, 0x55, - 0x1e, 0xc0, 0x3d, 0x61, 0xc9, 0x73, 0x96, 0x2f, 0x81, 0xc3, 0x02, 0x29, 0xa0, 0x50, 0x0a, 0x8b, 0xf5, 0x3a, 0x16, - 0x26, 0xb6, 0x84, 0x0b, 0x2d, 0xec, 0xde, 0x10, 0x31, 0xfa, 0x3b, 0xa8, 0x8b, 0xbd, 0x7a, 0xc4, 0x98, 0xb0, 0xa2, - 0xf0, 0xd2, 0x49, 0x66, 0x41, 0x5f, 0xfb, 0xfa, 0x10, 0xd5, 0x94, 0xfb, 0xb1, 0xd1, 0xf7, 0xbe, 0xe3, 0x73, 0x26, - 0x97, 0xf0, 0x78, 0x13, 0x66, 0x44, 0x31, 0xed, 0xbf, 0x81, 0x82, 0xc0, 0x0b, 0x40, 0x3c, 0xc4, 0x47, 0xe0, 0xab, - 0x3c, 0xad, 0x2b, 0x32, 0xff, 0x24, 0x48, 0x64, 0x42, 0x76, 0x46, 0xfd, 0x08, 0xbc, 0x88, 0x40, 0x84, 0x22, 0x24, - 0x62, 0x62, 0x1c, 0xf5, 0x23, 0xe3, 0x92, 0x15, 0x81, 0xd5, 0x18, 0x28, 0xb9, 0x23, 0x3c, 0x55, 0x15, 0x11, 0x0b, - 0x6b, 0xea, 0xa0, 0x12, 0x4b, 0x8d, 0x99, 0xf6, 0x49, 0xa7, 0x02, 0x21, 0xcd, 0xb6, 0x05, 0x65, 0xbd, 0xa5, 0x2e, - 0xc0, 0x92, 0x18, 0xd3, 0x5b, 0x9e, 0x7c, 0x02, 0x6e, 0x8e, 0x8d, 0x5d, 0xd1, 0x15, 0xbf, 0x06, 0xf5, 0x74, 0x5a, - 0xe0, 0x4f, 0x86, 0x61, 0x1b, 0xa7, 0x74, 0x43, 0x38, 0xce, 0x48, 0x91, 0xd0, 0x5b, 0x88, 0xad, 0x31, 0xe7, 0x22, - 0xcd, 0xf1, 0x9c, 0xde, 0xa6, 0x33, 0x3c, 0xe7, 0xe2, 0x89, 0x5d, 0xf6, 0x74, 0x0c, 0x49, 0xfe, 0x63, 0xb9, 0x21, - 0xe6, 0x69, 0xb0, 0xf7, 0x8a, 0x15, 0x8f, 0x80, 0x57, 0x51, 0x31, 0xea, 0x8d, 0x8d, 0x4d, 0x39, 0xd7, 0x95, 0xf1, - 0xfa, 0x6b, 0x1d, 0x53, 0x9c, 0xe1, 0x1c, 0x25, 0xb9, 0xc4, 0xac, 0x2f, 0xd2, 0xd7, 0x10, 0x57, 0x3b, 0xc3, 0xf6, - 0x59, 0x31, 0x7e, 0xcb, 0xf2, 0x67, 0xb2, 0xf8, 0x60, 0xb6, 0x7c, 0x8e, 0xa0, 0x10, 0xb8, 0xa8, 0x88, 0x26, 0xdc, - 0xee, 0x2d, 0xfb, 0xb2, 0x6a, 0x8a, 0xde, 0xda, 0xa6, 0xdc, 0x10, 0x67, 0x10, 0x90, 0x38, 0x99, 0xf1, 0x46, 0x1b, - 0xb3, 0x7e, 0xeb, 0x3b, 0x8d, 0xce, 0x50, 0x59, 0x12, 0x61, 0x58, 0xab, 0xa6, 0x4a, 0x25, 0x11, 0x4d, 0xe5, 0x24, - 0xbc, 0x95, 0x01, 0x76, 0xaa, 0x70, 0x26, 0x97, 0x42, 0xa7, 0x32, 0xc0, 0x9b, 0xac, 0xda, 0x5c, 0xab, 0x5b, 0x0b, - 0x31, 0x8d, 0xef, 0xec, 0x0f, 0x86, 0x3f, 0x19, 0x15, 0xff, 0x5b, 0x30, 0xec, 0x51, 0xa9, 0x00, 0xf8, 0x81, 0xe1, - 0x2c, 0x40, 0xce, 0xf2, 0x93, 0xb7, 0x00, 0x3e, 0xcb, 0x42, 0xde, 0x41, 0x2a, 0x33, 0xa9, 0x77, 0x90, 0xca, 0x20, - 0xd5, 0x78, 0xd4, 0x1f, 0x8a, 0x4a, 0x59, 0x14, 0x36, 0x48, 0x14, 0x2e, 0xd5, 0xc1, 0x92, 0x88, 0x04, 0xda, 0x35, - 0xa2, 0xdc, 0x9c, 0x0b, 0x08, 0xad, 0x08, 0x8d, 0xdb, 0x6f, 0x7a, 0x0b, 0xdf, 0x77, 0x36, 0x9f, 0xf9, 0xfc, 0x3b, - 0x9b, 0x6f, 0x3a, 0xf2, 0x18, 0x5f, 0xbf, 0xed, 0x34, 0x96, 0xf1, 0xd2, 0x61, 0xed, 0xfb, 0xf2, 0x21, 0x9b, 0x96, - 0x79, 0x30, 0x9c, 0xb4, 0xf1, 0x3c, 0x40, 0xca, 0x66, 0xc5, 0xc3, 0x75, 0x70, 0xbb, 0x75, 0x1c, 0xf3, 0x26, 0x69, - 0x23, 0x74, 0xec, 0x84, 0x2b, 0x11, 0x1b, 0xc9, 0xe9, 0xf8, 0xc3, 0x09, 0xdc, 0xbd, 0x8c, 0xd4, 0x96, 0xaf, 0x94, - 0xad, 0xd6, 0x6c, 0xb7, 0x8e, 0xf9, 0xde, 0x2a, 0x8d, 0x36, 0x9e, 0x33, 0xb2, 0x02, 0x0f, 0x34, 0x5a, 0x58, 0x55, - 0x03, 0xb8, 0xac, 0xbe, 0x10, 0xbf, 0x2e, 0xe9, 0xd8, 0x7c, 0x1f, 0xdb, 0x94, 0xd7, 0x4b, 0xed, 0x93, 0x9a, 0x1c, - 0x06, 0xd1, 0x41, 0xae, 0x64, 0x90, 0x13, 0xf3, 0x13, 0x92, 0x74, 0xd1, 0x65, 0xbb, 0x9f, 0x74, 0x8f, 0xf9, 0x31, - 0x4f, 0x81, 0x87, 0x8d, 0x9b, 0xbe, 0x42, 0xb3, 0xed, 0xeb, 0x3c, 0x5e, 0x8e, 0x78, 0xe6, 0x9a, 0xaf, 0x3a, 0x28, - 0x53, 0xed, 0x1c, 0x21, 0x0b, 0x50, 0xcc, 0xf7, 0x12, 0x64, 0xd7, 0xbb, 0x39, 0xe6, 0x29, 0xf4, 0x03, 0xb5, 0x3a, - 0xb6, 0x56, 0x39, 0xb8, 0x5f, 0x97, 0x80, 0x60, 0xbe, 0xa3, 0xda, 0x5c, 0x6c, 0x7a, 0x33, 0xae, 0x3a, 0x3b, 0xe6, - 0xd5, 0x08, 0xc3, 0x32, 0xbb, 0xfd, 0xf9, 0xa9, 0x55, 0x5d, 0x1e, 0x07, 0x10, 0xf9, 0x75, 0xc9, 0x45, 0xd8, 0x69, - 0xd8, 0xad, 0xcb, 0x09, 0x3b, 0xad, 0xcf, 0x32, 0x28, 0xb2, 0xdb, 0xeb, 0xce, 0x4c, 0xeb, 0xb3, 0xbd, 0x06, 0x47, - 0x42, 0x98, 0x94, 0x59, 0xe9, 0x4c, 0xaa, 0x98, 0x1f, 0xbf, 0x47, 0xae, 0xf5, 0xd7, 0x4b, 0xed, 0xf3, 0x4b, 0x44, - 0x80, 0xec, 0xaa, 0xeb, 0xb2, 0x3a, 0xf4, 0x51, 0x36, 0xf1, 0xea, 0x98, 0x07, 0x2b, 0xf7, 0xf4, 0x76, 0x21, 0x53, - 0x8f, 0xaf, 0xfd, 0x56, 0xba, 0x83, 0x9c, 0x40, 0x3c, 0x5c, 0x77, 0x61, 0x59, 0x90, 0xb3, 0x9b, 0x3b, 0x28, 0x19, - 0x4e, 0xdc, 0x97, 0x7e, 0xcf, 0xec, 0x75, 0x03, 0xbf, 0x4c, 0xba, 0x30, 0xf5, 0xed, 0x1e, 0x8e, 0x3b, 0xd0, 0x87, - 0x81, 0xc3, 0x76, 0x83, 0x3e, 0xb3, 0x82, 0xc8, 0x63, 0x5e, 0x58, 0x3c, 0xbb, 0x22, 0xed, 0x3e, 0x4f, 0xdd, 0x66, - 0x32, 0xa2, 0x51, 0xbb, 0xc9, 0x83, 0x99, 0x01, 0x7e, 0xb9, 0xb2, 0x61, 0x11, 0xbf, 0x4e, 0x01, 0x94, 0x7c, 0xb1, - 0x6a, 0x7d, 0x2a, 0x78, 0xd5, 0x1b, 0x4e, 0xb7, 0xd3, 0xfd, 0xba, 0xc1, 0xed, 0xae, 0x87, 0x27, 0x3c, 0x44, 0x63, - 0xd1, 0xda, 0x4f, 0x7c, 0x0e, 0x1c, 0x50, 0xd2, 0xba, 0xdf, 0x05, 0x17, 0xca, 0x12, 0x96, 0xbb, 0xe5, 0x46, 0x3b, - 0xe5, 0x2c, 0x1c, 0x6d, 0xc9, 0x80, 0x3b, 0xd8, 0x86, 0x28, 0x74, 0x70, 0xdc, 0xc1, 0x49, 0xbb, 0xdd, 0xe9, 0xe2, - 0xe4, 0xac, 0x0b, 0x03, 0x6d, 0x24, 0xdd, 0xe3, 0x91, 0xb2, 0x00, 0x0c, 0x72, 0x36, 0xae, 0xdd, 0x47, 0x10, 0xb4, - 0x2a, 0x14, 0xaf, 0xf9, 0x71, 0x1c, 0xb7, 0x93, 0xfb, 0xad, 0x76, 0xf7, 0xa2, 0x01, 0x00, 0x6a, 0xba, 0x0f, 0x57, - 0xe3, 0xf5, 0x52, 0xd7, 0xab, 0x94, 0x08, 0x5f, 0xaf, 0xd6, 0xf0, 0xd5, 0x1a, 0xed, 0x4d, 0x35, 0x05, 0x5f, 0xd5, - 0x09, 0xe7, 0xb6, 0x88, 0x57, 0xda, 0x84, 0xdb, 0x22, 0xb6, 0x03, 0x89, 0x41, 0x3a, 0x4f, 0xba, 0x9d, 0x2e, 0xb2, - 0x63, 0xd1, 0x0e, 0x3f, 0xca, 0x7d, 0xb2, 0x53, 0xa4, 0xa1, 0x01, 0x49, 0xca, 0xd9, 0xc9, 0x25, 0x48, 0xd4, 0x9c, - 0x5c, 0xb5, 0x9b, 0x73, 0x96, 0xf8, 0x09, 0x98, 0x54, 0x58, 0xce, 0x72, 0x15, 0x5c, 0x52, 0x00, 0x88, 0x4b, 0x30, - 0x2e, 0xba, 0xdf, 0xed, 0xdf, 0x4f, 0xba, 0xe7, 0x1d, 0x4b, 0xf4, 0xf8, 0x65, 0xa7, 0x96, 0x66, 0xa6, 0x9e, 0x74, - 0x4d, 0x1a, 0x74, 0x9d, 0xdc, 0xef, 0x42, 0x19, 0x97, 0x12, 0x96, 0x82, 0x60, 0x1b, 0x55, 0x31, 0x88, 0xb0, 0x91, - 0xd6, 0x72, 0xcf, 0x6b, 0xd9, 0x17, 0x67, 0xa7, 0xf7, 0xbb, 0x21, 0xd4, 0xca, 0x59, 0x98, 0x85, 0x76, 0x13, 0xf1, - 0xb3, 0x83, 0xa5, 0x45, 0xc7, 0x49, 0x37, 0xdd, 0x99, 0xa0, 0xdd, 0x34, 0xc7, 0x06, 0x07, 0x02, 0x85, 0xe3, 0x53, - 0xe1, 0xf4, 0x25, 0xc1, 0xfd, 0x58, 0x65, 0x68, 0x12, 0x2a, 0x9c, 0xfd, 0x3d, 0x65, 0xf0, 0x9e, 0x66, 0x78, 0x55, - 0xf9, 0x98, 0x8a, 0xaf, 0x54, 0xbd, 0xa1, 0x10, 0x41, 0x44, 0x0c, 0x23, 0x17, 0xdf, 0xbc, 0x9e, 0xfb, 0x0f, 0x70, - 0x11, 0x66, 0x02, 0x2e, 0x34, 0xbd, 0x12, 0xb4, 0xe2, 0x05, 0x3e, 0x85, 0x0e, 0xb5, 0x66, 0x58, 0x7d, 0x9e, 0x3a, - 0x93, 0x82, 0x50, 0xb7, 0xf5, 0x9c, 0x7f, 0xaf, 0x5c, 0x52, 0x5e, 0x65, 0x27, 0x5d, 0x94, 0xb8, 0xcb, 0xf2, 0xa4, - 0x8d, 0x92, 0xc0, 0x84, 0xc4, 0x1d, 0xc9, 0x79, 0x46, 0x06, 0xd1, 0x6d, 0x84, 0xa3, 0xbb, 0x08, 0x47, 0xd6, 0x87, - 0xf9, 0x37, 0xf0, 0xe3, 0x8e, 0x70, 0x64, 0x5d, 0x99, 0x23, 0x1c, 0x69, 0x26, 0x20, 0xb0, 0x58, 0x34, 0xc4, 0x33, - 0x28, 0x6d, 0x3c, 0xab, 0xcb, 0xd2, 0x8f, 0xfd, 0x57, 0xe9, 0x7a, 0x6d, 0x53, 0x02, 0x29, 0x73, 0x6c, 0x76, 0xa8, - 0x7d, 0x18, 0x3b, 0xa2, 0x9e, 0x59, 0x8f, 0x30, 0x08, 0x20, 0xf4, 0xce, 0x3f, 0xac, 0x57, 0xc5, 0x24, 0x61, 0xa7, - 0xb0, 0xd2, 0xe0, 0x8a, 0x1e, 0x85, 0x67, 0x58, 0x84, 0x27, 0xc2, 0x17, 0x06, 0xb1, 0xc2, 0xff, 0xce, 0xa5, 0x5c, - 0xf8, 0xdf, 0x5a, 0x96, 0xbf, 0xe0, 0x39, 0x16, 0x67, 0xd1, 0x02, 0x96, 0x5b, 0x36, 0x04, 0xd2, 0x88, 0xd5, 0x47, - 0xf0, 0x69, 0xe2, 0xc2, 0xd4, 0x81, 0x44, 0xf8, 0xc9, 0x08, 0x54, 0x5e, 0x3e, 0xfc, 0x64, 0x43, 0x26, 0x99, 0x4f, - 0x88, 0x99, 0x06, 0x61, 0x91, 0x25, 0x5c, 0x68, 0x4c, 0x0b, 0xa6, 0x54, 0x64, 0x63, 0x09, 0x46, 0x52, 0xf8, 0xc7, - 0x21, 0x7d, 0xca, 0x44, 0x44, 0xa6, 0xc3, 0xfa, 0x6c, 0xad, 0x38, 0x9c, 0xcb, 0x42, 0xa5, 0xf6, 0xa5, 0x18, 0x0f, - 0xc6, 0x45, 0xf9, 0x0c, 0x63, 0x3a, 0xcb, 0x36, 0xd8, 0xde, 0x61, 0x97, 0x85, 0xdc, 0x95, 0x76, 0x58, 0x2a, 0xcf, - 0x36, 0xdf, 0x9a, 0x90, 0xaa, 0xcd, 0x28, 0x98, 0x68, 0x35, 0xa0, 0x2a, 0x70, 0x07, 0x14, 0xb6, 0x41, 0x69, 0xd2, - 0x55, 0x59, 0x32, 0x5d, 0x95, 0xcb, 0x70, 0xd6, 0x6a, 0x6d, 0x36, 0xb8, 0x60, 0x26, 0x90, 0xcb, 0xde, 0x12, 0x90, - 0xaf, 0x66, 0xf2, 0x26, 0xc8, 0x55, 0x69, 0x39, 0x4b, 0xb3, 0x44, 0x51, 0x60, 0x04, 0x1b, 0x6d, 0xf0, 0x57, 0xae, - 0x38, 0xc0, 0xd3, 0xcd, 0x6e, 0x24, 0x65, 0xce, 0x28, 0xc4, 0x50, 0x0b, 0x9a, 0xdc, 0xe0, 0x19, 0x1f, 0xb3, 0xfd, - 0x6d, 0x82, 0x19, 0xf3, 0xbf, 0xd7, 0xa2, 0x47, 0x20, 0xcb, 0xee, 0x19, 0xd4, 0x81, 0x45, 0x5c, 0x43, 0x07, 0xa1, - 0x0c, 0xbe, 0x0c, 0x71, 0x33, 0xa7, 0x77, 0x72, 0xa9, 0x01, 0x2e, 0x4b, 0x2d, 0xdf, 0xb8, 0x70, 0x08, 0x87, 0x2d, - 0xec, 0x23, 0x23, 0xac, 0x20, 0x64, 0x40, 0x0b, 0xdb, 0x88, 0x18, 0x2d, 0xec, 0x02, 0x15, 0xb4, 0xb0, 0x09, 0x4f, - 0xd1, 0xda, 0x94, 0xb1, 0xcd, 0xee, 0xca, 0x27, 0x35, 0xab, 0x4d, 0x30, 0x71, 0xd2, 0xa1, 0x26, 0x3a, 0xb8, 0x3d, - 0x64, 0x84, 0x37, 0x7e, 0xba, 0x7e, 0xfd, 0xca, 0x45, 0xae, 0xe6, 0x13, 0x70, 0xd9, 0x74, 0xaa, 0xb1, 0x3b, 0x1b, - 0x62, 0xbe, 0x52, 0x94, 0x5a, 0xe1, 0xd4, 0x04, 0xfb, 0x14, 0x3a, 0x4f, 0xec, 0xe5, 0xc5, 0x33, 0x59, 0xcc, 0xa9, - 0xbd, 0x31, 0xc2, 0x77, 0xca, 0x3d, 0x3e, 0x6f, 0xde, 0xb7, 0xa9, 0x26, 0xf9, 0x6e, 0xfb, 0x2a, 0x62, 0x92, 0x19, - 0xf9, 0x15, 0xb4, 0x01, 0xa6, 0xb2, 0x1f, 0x38, 0x2b, 0x88, 0x8b, 0xff, 0x1f, 0x90, 0x97, 0xb7, 0x96, 0xba, 0x44, - 0x51, 0x83, 0x1b, 0xfc, 0x64, 0x45, 0xa5, 0xe3, 0xe2, 0xe6, 0xfd, 0x48, 0xd2, 0x72, 0xe2, 0x45, 0xd4, 0x8a, 0xea, - 0x6f, 0xef, 0x1a, 0x55, 0x82, 0x8f, 0x1d, 0x9b, 0xe4, 0x12, 0x44, 0x8f, 0xf2, 0x99, 0x3f, 0x0e, 0xa2, 0x89, 0xbf, - 0x7b, 0xbe, 0x6a, 0x7b, 0x3a, 0x9b, 0x57, 0xea, 0xc4, 0xf2, 0xca, 0x04, 0x3c, 0x1c, 0xed, 0x43, 0x3a, 0x08, 0x07, - 0x89, 0xac, 0xd4, 0x1e, 0xfa, 0x5c, 0xd4, 0x8b, 0xf3, 0xcb, 0x36, 0x6b, 0x9e, 0xad, 0xd7, 0xf9, 0x55, 0x9b, 0xb5, - 0xbb, 0xf6, 0xd9, 0xbd, 0x48, 0x65, 0x40, 0x73, 0xf9, 0x84, 0x67, 0x11, 0x68, 0x67, 0x17, 0x99, 0x09, 0xa7, 0xe0, - 0xa5, 0x69, 0xb2, 0xd4, 0x55, 0x5f, 0x12, 0x8c, 0x4b, 0x89, 0xd5, 0xe3, 0x17, 0xa8, 0xdf, 0x4e, 0x77, 0x5d, 0xa5, - 0x9b, 0xed, 0xe3, 0xe0, 0xc2, 0xa5, 0x40, 0xb8, 0x03, 0x21, 0x0f, 0x40, 0xbf, 0xbb, 0x12, 0x60, 0x1a, 0x04, 0xa8, - 0xac, 0x40, 0xa4, 0xe5, 0xf3, 0xe5, 0xfc, 0x59, 0x41, 0xcd, 0x32, 0x3c, 0xe1, 0x53, 0xae, 0x55, 0x4a, 0x41, 0xba, - 0xdd, 0x97, 0xbe, 0xd9, 0x2f, 0x41, 0x65, 0xb5, 0xf8, 0xbb, 0x89, 0xe6, 0xd9, 0x17, 0xe5, 0x16, 0x0e, 0x61, 0xb3, - 0xb2, 0x02, 0x67, 0x68, 0x83, 0x73, 0x39, 0xa5, 0x05, 0xd7, 0xb3, 0xf9, 0xbf, 0xb5, 0x3a, 0x6c, 0xa0, 0x87, 0xe6, - 0xc2, 0x0a, 0x40, 0x42, 0xc5, 0x78, 0xbd, 0xe6, 0x27, 0xdf, 0xbf, 0x4f, 0xf2, 0x3e, 0xe1, 0x6d, 0xdc, 0xc1, 0xa7, - 0xb8, 0x8b, 0xdb, 0x2d, 0xdc, 0xee, 0xc2, 0xd5, 0x7d, 0x96, 0x2f, 0xc7, 0x4c, 0xc5, 0xf0, 0xfe, 0x9a, 0xbe, 0x4a, - 0x2e, 0x8e, 0xab, 0x57, 0x07, 0x8a, 0xc4, 0xa1, 0x4b, 0x10, 0xfc, 0xde, 0x45, 0x0d, 0x8c, 0xa2, 0x30, 0x64, 0xdd, - 0x22, 0x54, 0x9d, 0x94, 0xfa, 0x85, 0xab, 0xd3, 0x3e, 0xd8, 0x73, 0xdb, 0x95, 0x6d, 0x82, 0xd9, 0xb7, 0xfd, 0x99, - 0x56, 0x3f, 0x9b, 0xba, 0x44, 0x0c, 0x0f, 0xbd, 0x0a, 0x3d, 0xd0, 0x15, 0x69, 0x1f, 0x1d, 0x81, 0xd5, 0x51, 0x30, - 0x1b, 0x6e, 0xa3, 0x1f, 0xf0, 0x66, 0x2d, 0x0d, 0x82, 0x15, 0x80, 0x71, 0xe7, 0x1b, 0x4e, 0x56, 0x16, 0xb6, 0x1a, - 0xa8, 0x30, 0x2b, 0xc2, 0xb8, 0x7a, 0x21, 0xa9, 0x30, 0x42, 0x34, 0x1c, 0x61, 0x2e, 0x18, 0xca, 0x61, 0x0b, 0xcb, - 0xc9, 0x44, 0x31, 0x0d, 0x47, 0x47, 0xc1, 0xbe, 0xb2, 0x42, 0x99, 0x53, 0x64, 0xc4, 0xa6, 0x5c, 0x3c, 0xd4, 0xbf, - 0xb3, 0x42, 0x9a, 0x4f, 0xa3, 0xc1, 0x48, 0x23, 0xb3, 0x8a, 0x11, 0xce, 0x72, 0xbe, 0x80, 0xaa, 0xd3, 0x02, 0x9c, - 0x7e, 0xe0, 0x2f, 0x1f, 0xa7, 0x61, 0x9b, 0x40, 0xbe, 0x7e, 0xb3, 0x31, 0x5d, 0xf0, 0xb8, 0xa0, 0x37, 0xaf, 0xc5, - 0x63, 0xd8, 0x51, 0x0f, 0x0b, 0x46, 0x21, 0x1b, 0x92, 0xde, 0x41, 0x53, 0xf0, 0x01, 0x6d, 0xbe, 0x34, 0x80, 0x4b, - 0x2f, 0xcc, 0x87, 0xad, 0xe8, 0x63, 0x37, 0x26, 0x65, 0x5b, 0x26, 0xd3, 0x9c, 0xd2, 0x55, 0xa6, 0x8d, 0x42, 0x55, - 0x4e, 0x61, 0x83, 0x5d, 0xd4, 0x93, 0x70, 0x30, 0x63, 0xaa, 0x66, 0xe9, 0x60, 0x68, 0xfe, 0xbe, 0xb6, 0x25, 0x5b, - 0xd8, 0x45, 0x9c, 0xd9, 0x60, 0xf3, 0x70, 0x6a, 0x50, 0xbe, 0x8d, 0xe1, 0x1e, 0x16, 0x5e, 0xef, 0xac, 0x91, 0xcf, - 0x33, 0x4f, 0x36, 0xcf, 0x36, 0x1b, 0x33, 0x10, 0x95, 0x82, 0x1e, 0xe8, 0xad, 0xdf, 0x36, 0x2d, 0xd8, 0x1e, 0xe5, - 0x57, 0xb7, 0x85, 0xe7, 0x1c, 0x1e, 0x23, 0xf5, 0xed, 0x5d, 0xeb, 0x42, 0x7e, 0x71, 0x20, 0x69, 0x05, 0x29, 0x76, - 0x3a, 0x41, 0x67, 0xa7, 0x38, 0x18, 0x39, 0xd0, 0xf3, 0xeb, 0x2f, 0x16, 0xd6, 0xfe, 0xf7, 0x9b, 0xb2, 0xa0, 0x89, - 0xa7, 0x53, 0x4e, 0x28, 0xf3, 0xe7, 0xe7, 0x1b, 0x9e, 0x54, 0xa8, 0xe0, 0x5e, 0xf1, 0x82, 0x3d, 0x6d, 0x03, 0x7d, - 0xce, 0xe9, 0x67, 0xfb, 0xc3, 0xc6, 0xf0, 0x29, 0xb5, 0x6c, 0x59, 0x21, 0x95, 0x7a, 0x68, 0xd3, 0xec, 0xd1, 0x03, - 0x47, 0xe4, 0x4b, 0xe8, 0x02, 0x78, 0xfd, 0x71, 0x21, 0x17, 0x06, 0x11, 0xdc, 0x6f, 0x37, 0x6e, 0xe3, 0x2b, 0x00, - 0xde, 0x0e, 0x07, 0xd5, 0x3f, 0x2d, 0x60, 0x7f, 0xa3, 0xb2, 0xa4, 0x1f, 0x6f, 0xc7, 0x1e, 0xff, 0x85, 0x84, 0xa8, - 0xf1, 0x16, 0x0f, 0x13, 0x87, 0x4e, 0x25, 0x6b, 0x56, 0xfe, 0xdc, 0x29, 0x09, 0x18, 0x56, 0x2f, 0x18, 0xb2, 0x71, - 0x3b, 0xc5, 0x6d, 0xe6, 0x7f, 0x50, 0xc1, 0x60, 0xc1, 0xb7, 0x46, 0x52, 0xb1, 0x2c, 0x7e, 0xfb, 0xd4, 0xf9, 0xaf, - 0x3a, 0xc7, 0x75, 0xa8, 0x6b, 0x2f, 0x85, 0x8e, 0x4c, 0x94, 0xe6, 0x08, 0x1d, 0x1d, 0x6d, 0x65, 0xd0, 0x09, 0x00, - 0x1e, 0x39, 0xf6, 0xcb, 0x2f, 0x9f, 0x67, 0xc7, 0x8c, 0xe6, 0xb1, 0x88, 0x42, 0xe6, 0xce, 0x73, 0x73, 0x76, 0x22, - 0x4f, 0xa8, 0x9a, 0xf9, 0xc2, 0x00, 0xc7, 0x47, 0x3b, 0xa9, 0x80, 0xef, 0xd1, 0x66, 0xcf, 0x04, 0xb6, 0xf8, 0x2d, - 0x3b, 0xa9, 0x7d, 0x05, 0xfd, 0x02, 0xad, 0xf6, 0x31, 0x95, 0x5b, 0x0b, 0x1c, 0x6d, 0x4f, 0x64, 0xef, 0xd0, 0xb7, - 0xea, 0x94, 0xac, 0xc7, 0x8b, 0xfd, 0x46, 0x5f, 0x52, 0xec, 0x4b, 0xae, 0x68, 0xdb, 0x88, 0x55, 0xaf, 0x05, 0xeb, - 0xca, 0xd4, 0xa9, 0xba, 0xe6, 0xad, 0x2c, 0x6d, 0x4a, 0xbb, 0x24, 0x7b, 0xb7, 0xc5, 0xc2, 0xab, 0xf0, 0x46, 0xa3, - 0xbc, 0x08, 0x05, 0x7b, 0x2c, 0x31, 0xec, 0x71, 0x02, 0xd7, 0x0b, 0xeb, 0x75, 0x0c, 0x7f, 0xf6, 0x8d, 0x61, 0x9f, - 0xe9, 0xd2, 0x7b, 0xbe, 0xc5, 0xaf, 0x04, 0x01, 0x8b, 0x9d, 0x1d, 0x24, 0x58, 0x77, 0xb9, 0x41, 0xc3, 0x71, 0xe2, - 0xbf, 0xe0, 0xb9, 0x6c, 0xed, 0x5d, 0x0e, 0xe6, 0xd9, 0x37, 0x9e, 0xd8, 0x2b, 0x59, 0xcb, 0x5a, 0xb4, 0xfb, 0x2d, - 0x09, 0x86, 0xd8, 0x4d, 0xe9, 0x1c, 0xb7, 0x92, 0x36, 0x8a, 0x5c, 0xb1, 0x0a, 0xfd, 0xbf, 0x55, 0x24, 0xb3, 0x99, - 0xff, 0x75, 0x7e, 0x7e, 0xee, 0x52, 0x9c, 0xcd, 0x9f, 0x32, 0x1e, 0x70, 0x26, 0x81, 0x7d, 0xe5, 0x19, 0x33, 0x3a, - 0xe4, 0xb7, 0x30, 0x14, 0x22, 0xc8, 0x95, 0x70, 0xec, 0x12, 0xbc, 0xf6, 0x08, 0x94, 0x07, 0xd8, 0xbf, 0x27, 0x5b, - 0xe5, 0xfc, 0x73, 0x51, 0x3e, 0x9c, 0x72, 0xd9, 0x20, 0xfb, 0x6a, 0x3e, 0x07, 0xd6, 0x4c, 0x06, 0x5e, 0x48, 0x88, - 0xb0, 0xfd, 0x6d, 0x58, 0x5a, 0x67, 0x29, 0x83, 0x23, 0x2d, 0x97, 0xd9, 0xcc, 0x6a, 0xfe, 0xdd, 0x87, 0x29, 0xeb, - 0x9e, 0x1a, 0x82, 0xc8, 0x5d, 0x64, 0xe5, 0xa2, 0x82, 0x46, 0x3f, 0x96, 0x01, 0x40, 0x0f, 0x5e, 0xb1, 0x25, 0xfb, - 0x11, 0x1f, 0x54, 0x29, 0xf0, 0xf1, 0xb0, 0xe0, 0x34, 0xff, 0x11, 0x1f, 0x54, 0x81, 0x40, 0xc1, 0x15, 0xd2, 0xc4, - 0xd2, 0xc4, 0xe6, 0x59, 0xed, 0x34, 0x12, 0x40, 0x41, 0xf3, 0xc8, 0x1c, 0x64, 0xcf, 0x5d, 0x8c, 0xc6, 0xa4, 0x83, - 0x5d, 0x70, 0x30, 0x1b, 0x11, 0xd6, 0x06, 0x52, 0x87, 0xb8, 0x75, 0xe5, 0x6c, 0xcc, 0xd7, 0xa3, 0xad, 0x05, 0x31, - 0xca, 0x64, 0x72, 0xf5, 0x9c, 0xc7, 0x3b, 0x8b, 0x85, 0xc2, 0x6a, 0xc1, 0x02, 0xd5, 0xaa, 0x54, 0xe9, 0x61, 0xf1, - 0xdd, 0x82, 0x59, 0x50, 0xc4, 0x6c, 0xbd, 0x87, 0xb7, 0x5c, 0x11, 0x90, 0x92, 0x5d, 0x12, 0xbc, 0x8c, 0x6e, 0x30, - 0x95, 0xac, 0xe6, 0x72, 0xcc, 0x2c, 0xa1, 0x67, 0x4a, 0x47, 0xd8, 0xe4, 0x29, 0x88, 0x24, 0x76, 0xd8, 0xc2, 0x8e, - 0x35, 0x7a, 0x21, 0xbc, 0x90, 0x02, 0xe7, 0xaa, 0x69, 0x62, 0x4e, 0xb9, 0x89, 0x2e, 0xf6, 0x50, 0x2d, 0x58, 0xa6, - 0x2d, 0x02, 0x1c, 0x3a, 0x34, 0x94, 0xe2, 0xb9, 0x01, 0x85, 0x79, 0xd2, 0xdb, 0xa5, 0x3c, 0x86, 0xc5, 0x0b, 0x52, - 0x80, 0xa8, 0x71, 0x31, 0x2d, 0xeb, 0x2c, 0xf2, 0xe5, 0x94, 0x8b, 0x0a, 0x19, 0x0a, 0xa6, 0x16, 0x52, 0xc0, 0x8b, - 0x1a, 0x65, 0x11, 0x43, 0x87, 0x6a, 0xf8, 0x6e, 0x49, 0x58, 0x59, 0xc7, 0x1c, 0x53, 0x5c, 0x54, 0x35, 0x80, 0xb9, - 0x78, 0x68, 0x04, 0x44, 0x1f, 0x5e, 0xf6, 0xb5, 0x78, 0x27, 0x17, 0x55, 0xbe, 0xa7, 0x71, 0x3e, 0x70, 0xbd, 0xb3, - 0x1b, 0x46, 0x1b, 0xf3, 0xe8, 0x55, 0xb0, 0x7d, 0xdf, 0xf3, 0xea, 0x21, 0xb8, 0x8d, 0x79, 0x36, 0xab, 0xcc, 0x1a, - 0xb1, 0xf2, 0x8d, 0x88, 0xaa, 0xbd, 0x7a, 0x55, 0x29, 0x6c, 0x45, 0x80, 0x4a, 0xc1, 0xc7, 0x3b, 0xf9, 0x2f, 0xb4, - 0xcd, 0xb7, 0xe7, 0x50, 0x19, 0x1e, 0xc8, 0x93, 0xa1, 0xaa, 0x07, 0x5c, 0x94, 0x1f, 0x02, 0x58, 0xfc, 0xc8, 0xc4, - 0x0f, 0xde, 0x77, 0x81, 0xcc, 0x99, 0x8a, 0x25, 0x5e, 0x0d, 0xe8, 0x30, 0xb5, 0xf2, 0x50, 0x2a, 0xc1, 0xb6, 0xe7, - 0xa6, 0xe0, 0xda, 0x07, 0x2a, 0xc6, 0x03, 0x36, 0x4c, 0x57, 0xf5, 0x60, 0xc6, 0x36, 0x9c, 0xb2, 0x37, 0xe7, 0x34, - 0xd1, 0x7f, 0xe9, 0x10, 0xe7, 0x04, 0x6c, 0x8f, 0x3d, 0x7b, 0xfa, 0x26, 0xce, 0x50, 0xbf, 0xce, 0xe1, 0xaf, 0x36, - 0x38, 0xc7, 0x19, 0x4a, 0x1f, 0xc6, 0x70, 0x81, 0xb5, 0xc1, 0x00, 0xbe, 0xcc, 0x92, 0x2a, 0xf0, 0x48, 0xcd, 0x8c, - 0xc4, 0xea, 0x2e, 0x02, 0xd1, 0x4a, 0x87, 0xb7, 0xe3, 0xcc, 0x87, 0x03, 0x37, 0xdc, 0xeb, 0x33, 0x23, 0x1c, 0xce, - 0xb3, 0xb8, 0x76, 0xce, 0x70, 0x72, 0x75, 0xc8, 0x6b, 0x27, 0x26, 0x58, 0x7b, 0x87, 0xa7, 0x0a, 0xe8, 0xd1, 0xe0, - 0x54, 0xb1, 0x34, 0x04, 0x62, 0x26, 0x80, 0x37, 0x73, 0x78, 0xb4, 0x05, 0x38, 0x1f, 0x6d, 0x70, 0xf0, 0x95, 0xd6, - 0xba, 0xda, 0x56, 0xa2, 0x6c, 0x36, 0x78, 0x30, 0xce, 0xf0, 0x32, 0xc3, 0xd3, 0x6c, 0x18, 0x1e, 0x37, 0x59, 0x68, - 0xd2, 0xb5, 0x5e, 0x3f, 0x75, 0x66, 0x84, 0xc8, 0xfe, 0xb4, 0xf4, 0x07, 0xf5, 0x01, 0xe1, 0x53, 0xc8, 0x02, 0x5a, - 0xd2, 0x77, 0x7f, 0x1b, 0xf6, 0xb5, 0x70, 0xd4, 0x88, 0x79, 0x62, 0xc9, 0x48, 0xdf, 0xff, 0x28, 0xb3, 0x6c, 0x6b, - 0x8d, 0x68, 0x71, 0x7b, 0x10, 0x35, 0x7c, 0x7b, 0xd5, 0xf9, 0x32, 0x2a, 0xcd, 0x76, 0x00, 0x51, 0xac, 0x71, 0x92, - 0x0e, 0xd6, 0x48, 0xae, 0xd7, 0xb1, 0x4d, 0x21, 0x3c, 0x99, 0x33, 0xaa, 0x96, 0x85, 0x79, 0x40, 0x2f, 0x56, 0x28, - 0x31, 0xfc, 0x2e, 0x76, 0x36, 0xa2, 0xf0, 0x5e, 0x9d, 0x04, 0xc3, 0x8d, 0x58, 0x10, 0x59, 0x13, 0xb9, 0x3f, 0x65, - 0x95, 0x65, 0x90, 0x20, 0xc2, 0x88, 0xfc, 0xf6, 0xba, 0x54, 0xd8, 0x27, 0xfa, 0xec, 0x1f, 0xe3, 0x0b, 0x08, 0x37, - 0x6f, 0x53, 0x5a, 0x8c, 0xe8, 0x14, 0xd8, 0x58, 0x88, 0x43, 0xb8, 0x93, 0xb0, 0x5e, 0x0f, 0x86, 0x3d, 0x61, 0xc8, - 0xb3, 0x7b, 0x40, 0xb0, 0x6c, 0x68, 0x7f, 0x03, 0x70, 0xd5, 0x6d, 0xa9, 0xb9, 0x36, 0xba, 0x1f, 0x6a, 0xde, 0x38, - 0xe3, 0x2e, 0xc9, 0x3d, 0x53, 0x52, 0xbd, 0x44, 0x5e, 0xb3, 0x00, 0x37, 0xa1, 0xab, 0xf0, 0x18, 0x2f, 0xad, 0x0d, - 0xa7, 0x79, 0xd0, 0x8a, 0x9a, 0x77, 0xac, 0xe0, 0xf9, 0x6c, 0xc2, 0x06, 0xd9, 0x10, 0x8f, 0x7d, 0xb8, 0xf3, 0xc3, - 0xb7, 0xf1, 0x18, 0xa1, 0x82, 0x18, 0x98, 0x5a, 0x97, 0xed, 0x71, 0x65, 0xb7, 0x6f, 0x32, 0x0d, 0xc3, 0x60, 0x8c, - 0x98, 0xc7, 0xa1, 0x11, 0x73, 0xde, 0x68, 0xa0, 0x25, 0x19, 0x83, 0x11, 0xf3, 0x32, 0x68, 0x6d, 0x69, 0x1f, 0x3b, - 0x0d, 0xda, 0x5b, 0x22, 0xd4, 0xe3, 0x40, 0xd3, 0x34, 0x3c, 0x6b, 0x52, 0x3d, 0x2b, 0xef, 0x1f, 0xd9, 0x3a, 0xe9, - 0x80, 0x22, 0x61, 0x72, 0xe5, 0x27, 0x61, 0x5d, 0xc3, 0xed, 0xb8, 0x27, 0x66, 0xdc, 0xce, 0xb6, 0x41, 0x0d, 0xe4, - 0x20, 0x1b, 0x0e, 0x7b, 0xd2, 0x5b, 0x49, 0xb4, 0xf0, 0xa4, 0x7a, 0x08, 0xa5, 0x5a, 0xbc, 0xaf, 0x7a, 0xfb, 0xca, - 0x9b, 0xfb, 0xf7, 0x55, 0xb7, 0xcf, 0x63, 0xe0, 0x80, 0x0e, 0xe1, 0x7e, 0xa8, 0x8a, 0x0f, 0x76, 0xd2, 0x81, 0x28, - 0x68, 0x69, 0xab, 0x26, 0x90, 0x5a, 0x33, 0xbb, 0x58, 0x37, 0x15, 0x3a, 0x16, 0x10, 0x86, 0x4c, 0x55, 0xdd, 0xdd, - 0xaa, 0x40, 0x35, 0xc4, 0xe1, 0xd4, 0x7f, 0x6c, 0x8d, 0x58, 0xe3, 0xa8, 0x33, 0x8e, 0x8c, 0x91, 0xa4, 0x5d, 0x3e, - 0x78, 0xfb, 0x08, 0xac, 0x04, 0x7c, 0x0c, 0x6a, 0x93, 0x64, 0x0c, 0x09, 0xde, 0xb2, 0x4c, 0x1b, 0x3e, 0x84, 0x3b, - 0x04, 0xe5, 0x89, 0x0d, 0x4a, 0xeb, 0x2a, 0x59, 0xc8, 0x55, 0x5d, 0xde, 0x05, 0xe8, 0x79, 0x5b, 0xfe, 0xc6, 0x86, - 0x23, 0x0b, 0x06, 0x96, 0xed, 0xec, 0x13, 0xf0, 0xc8, 0xc7, 0x15, 0x82, 0xf8, 0xa5, 0xd0, 0x89, 0x89, 0xd7, 0x7d, - 0x0d, 0x1b, 0x14, 0x2f, 0xc0, 0x41, 0xd0, 0x49, 0x70, 0x18, 0xbc, 0xcb, 0xac, 0x26, 0xd9, 0xe0, 0xd6, 0x9c, 0xc4, - 0x8b, 0xf5, 0xba, 0x85, 0x8e, 0xff, 0x69, 0x9e, 0xa4, 0x9e, 0x94, 0x0a, 0xf7, 0x49, 0xa5, 0x70, 0x07, 0x4b, 0x40, - 0x32, 0x09, 0x74, 0xed, 0x58, 0x86, 0x6a, 0x74, 0x88, 0x96, 0xfe, 0x02, 0x62, 0x67, 0xbb, 0x63, 0x09, 0xf4, 0xec, - 0x3b, 0x05, 0xac, 0xae, 0xbd, 0x2c, 0x81, 0x8c, 0xe0, 0xee, 0x37, 0x81, 0x51, 0x21, 0x1a, 0x9f, 0x3f, 0xf3, 0xaa, - 0x05, 0x4f, 0x9c, 0x3f, 0xd7, 0xdc, 0xb0, 0xee, 0x05, 0xbd, 0x31, 0xcd, 0xc7, 0x13, 0xdc, 0x9c, 0x58, 0x70, 0x9e, - 0x74, 0xe0, 0xa7, 0x85, 0xe8, 0x49, 0x07, 0xbb, 0x54, 0x3c, 0x29, 0x81, 0x1c, 0xa2, 0xa7, 0x33, 0x90, 0x02, 0x56, - 0x3a, 0xb6, 0x5a, 0xa4, 0x29, 0x5a, 0xaf, 0xa7, 0x97, 0xa4, 0x85, 0xd0, 0x4a, 0xdd, 0x70, 0x9d, 0xcd, 0xc0, 0x47, - 0x1a, 0x14, 0x03, 0x6f, 0xa8, 0x9e, 0xc5, 0x08, 0x4f, 0xd0, 0x6a, 0xcc, 0x26, 0x74, 0x99, 0xeb, 0x54, 0xf5, 0x79, - 0x62, 0x03, 0xf7, 0x32, 0x1b, 0x09, 0xee, 0xa4, 0x83, 0xa7, 0x86, 0xbf, 0xfc, 0x60, 0xcc, 0x41, 0x8a, 0xcc, 0x24, - 0x4f, 0x4d, 0x02, 0xe6, 0x49, 0x96, 0x4b, 0xc5, 0x6c, 0x33, 0x3d, 0x6b, 0x5b, 0x0e, 0x21, 0xc9, 0x23, 0x5d, 0x70, - 0x63, 0x45, 0x19, 0xa5, 0x33, 0xa2, 0xfa, 0xea, 0xa4, 0x93, 0x4e, 0x31, 0x4f, 0x80, 0xd3, 0x7b, 0x27, 0x63, 0xd6, - 0x28, 0x6f, 0x45, 0xe7, 0xe8, 0x78, 0x86, 0x45, 0x75, 0x89, 0x3a, 0x47, 0xc7, 0x53, 0x84, 0xe7, 0x0d, 0x32, 0x53, - 0xe0, 0x31, 0xcc, 0xc5, 0xff, 0x91, 0xf2, 0xdf, 0x1c, 0x36, 0x84, 0x98, 0x7e, 0x0b, 0x3b, 0x85, 0x8d, 0xa3, 0x34, - 0x27, 0xe0, 0xb5, 0xd8, 0x3e, 0xc7, 0x19, 0x99, 0x36, 0x73, 0x1f, 0x70, 0xcf, 0xb4, 0xd2, 0xb8, 0xd5, 0xe8, 0x38, - 0xc3, 0xe3, 0xed, 0xa4, 0xd8, 0xcc, 0xb5, 0x99, 0xa7, 0x19, 0x9c, 0xef, 0xd5, 0x28, 0x5c, 0xf9, 0xe5, 0x76, 0x52, - 0x58, 0xde, 0x01, 0xb7, 0x39, 0xc6, 0xa2, 0x49, 0x71, 0x8e, 0xe7, 0xcd, 0x57, 0x78, 0xde, 0x7c, 0x5f, 0x66, 0x34, - 0x96, 0x58, 0x40, 0xf0, 0x3e, 0x48, 0xc4, 0xf3, 0x2a, 0x79, 0x8c, 0x45, 0xc3, 0x94, 0xc7, 0xf3, 0x46, 0x55, 0xba, - 0xb9, 0xc4, 0xa2, 0x61, 0x4a, 0x37, 0xde, 0xe3, 0x79, 0xe3, 0xd5, 0xbf, 0x98, 0x74, 0x94, 0x02, 0xba, 0x2c, 0xd0, - 0x2a, 0xb3, 0x43, 0xbc, 0xfe, 0xf5, 0xed, 0xbb, 0xf6, 0xa7, 0xce, 0xf1, 0x14, 0xfb, 0xf5, 0xcb, 0x0c, 0x8e, 0x65, - 0x3a, 0x66, 0x4d, 0x80, 0x68, 0x86, 0x3b, 0xc7, 0x33, 0xdc, 0x39, 0xce, 0x5c, 0x53, 0x9b, 0x79, 0x83, 0xdc, 0xea, - 0x10, 0x8a, 0x3a, 0x4a, 0x43, 0xf8, 0xf8, 0xc9, 0xa6, 0x53, 0x54, 0x03, 0x25, 0x3a, 0x9e, 0xd6, 0x40, 0x05, 0xdf, - 0xcb, 0xda, 0x77, 0x55, 0xaf, 0xc2, 0x20, 0x0b, 0x25, 0x14, 0xae, 0xb9, 0x01, 0x4f, 0x2d, 0xc5, 0x40, 0x26, 0x4c, - 0xb1, 0x40, 0xf9, 0x0e, 0x28, 0x8c, 0xf2, 0xc4, 0x0c, 0x3d, 0x98, 0x8e, 0x49, 0xfc, 0xff, 0x79, 0x32, 0xe5, 0xd0, - 0xcb, 0x2d, 0xb3, 0x33, 0x3d, 0x37, 0x99, 0x70, 0xf8, 0xc0, 0x63, 0xfd, 0x5f, 0x3b, 0x50, 0x6c, 0x40, 0x8a, 0xff, - 0x2f, 0x1d, 0x5d, 0x08, 0x46, 0xc8, 0x8a, 0xd2, 0xc2, 0x21, 0xfe, 0xf7, 0x87, 0x15, 0x74, 0x5f, 0xec, 0x74, 0x5f, - 0x98, 0xee, 0xc3, 0xa6, 0x8d, 0x2a, 0x27, 0xad, 0x2a, 0x59, 0xf2, 0x5f, 0xa7, 0x5b, 0x3b, 0xa0, 0x11, 0x35, 0x7a, - 0x36, 0x0d, 0x1b, 0x3c, 0x6c, 0xa7, 0x7b, 0x90, 0x79, 0xc3, 0xed, 0x0b, 0xa9, 0x70, 0xf8, 0x06, 0x77, 0xaa, 0x57, - 0x2d, 0xf0, 0xde, 0x54, 0x46, 0x5f, 0x19, 0x87, 0x96, 0x83, 0x74, 0xdb, 0x94, 0xdb, 0x18, 0x4b, 0x27, 0x5d, 0x6c, - 0x5c, 0x11, 0xa1, 0xd2, 0xed, 0x15, 0x28, 0xc5, 0x27, 0xba, 0xc9, 0xcc, 0xd7, 0xa5, 0x4e, 0xcc, 0x25, 0x54, 0xc3, - 0x7c, 0xde, 0x5d, 0xe9, 0x44, 0xcb, 0x85, 0xcd, 0xbb, 0xbb, 0x84, 0x3e, 0x41, 0xc3, 0xda, 0x08, 0xec, 0xf6, 0xb9, - 0xb3, 0x83, 0x0c, 0x0e, 0xc1, 0xf0, 0x00, 0x72, 0xa4, 0xc5, 0xf6, 0x81, 0x4d, 0x6b, 0xd8, 0x75, 0xd1, 0x2c, 0x13, - 0x6d, 0xab, 0x4d, 0x93, 0x6b, 0xf7, 0x30, 0x5f, 0x84, 0x3c, 0x85, 0x28, 0xac, 0x7e, 0x7c, 0x0f, 0xbb, 0xf1, 0xb5, - 0xc6, 0x48, 0xd4, 0x95, 0x4c, 0x25, 0xf4, 0x93, 0x5b, 0xcc, 0x92, 0x3b, 0xe3, 0xc5, 0xa8, 0x8c, 0xbf, 0x8f, 0x89, - 0xcb, 0x1f, 0x55, 0x92, 0x1c, 0x58, 0xf6, 0x37, 0x58, 0x72, 0x0b, 0xe6, 0x89, 0x65, 0x35, 0x89, 0x75, 0x72, 0x17, - 0x2c, 0xa2, 0x34, 0x8d, 0x6c, 0x0c, 0x03, 0x6a, 0x9a, 0xb1, 0xea, 0xc1, 0x43, 0x08, 0xf4, 0xd0, 0x2f, 0x4b, 0x69, - 0xd7, 0x59, 0x5a, 0xeb, 0x5e, 0x9b, 0xee, 0xb7, 0x07, 0x54, 0x4d, 0xe3, 0x26, 0xe0, 0x9a, 0xfe, 0xd5, 0x24, 0x92, - 0x11, 0xfb, 0x9b, 0xb3, 0xe2, 0xf1, 0xb2, 0x30, 0x98, 0x26, 0xfa, 0x3a, 0xc9, 0x16, 0x6d, 0x30, 0xd5, 0xcb, 0x16, - 0x9d, 0x5b, 0xec, 0xbe, 0xef, 0xec, 0xf7, 0x1d, 0x16, 0x7d, 0x66, 0x32, 0x52, 0x66, 0x8a, 0xf9, 0xef, 0x3b, 0xfb, - 0x7d, 0x87, 0x77, 0x07, 0xf3, 0xc5, 0x5f, 0x28, 0x96, 0xec, 0x0c, 0x97, 0x60, 0x42, 0x1e, 0x70, 0x37, 0xb5, 0x2c, - 0x13, 0x04, 0xb6, 0x96, 0x00, 0x71, 0x3e, 0x9f, 0xc6, 0x15, 0xaf, 0x86, 0x80, 0xfb, 0xf4, 0xae, 0xed, 0x55, 0x2a, - 0xf0, 0x98, 0xa0, 0x11, 0x31, 0xb1, 0x6d, 0xcc, 0xeb, 0x66, 0xc0, 0xe5, 0x11, 0x5d, 0xea, 0x49, 0x12, 0xe0, 0x55, - 0x8d, 0xca, 0xdb, 0x14, 0x29, 0xbf, 0x48, 0x90, 0xe3, 0x8b, 0x3d, 0xa2, 0x8a, 0x01, 0xac, 0xca, 0x92, 0x3e, 0x81, - 0xd4, 0xf3, 0x43, 0x4f, 0xcd, 0x6d, 0xe4, 0xb1, 0xef, 0xfc, 0x7e, 0x61, 0x7a, 0x56, 0xc8, 0xe5, 0x74, 0x06, 0x3e, - 0xb4, 0xc0, 0x32, 0x14, 0xa6, 0x5e, 0x65, 0xeb, 0x5f, 0x93, 0xdc, 0x04, 0x50, 0x38, 0xdd, 0x94, 0x09, 0xcd, 0xf4, - 0x92, 0xe6, 0xc6, 0x92, 0x94, 0x8b, 0xe9, 0x23, 0x79, 0xfb, 0x12, 0xb0, 0x9b, 0x12, 0xdd, 0xd8, 0x93, 0xf7, 0x16, - 0x76, 0x00, 0xce, 0x08, 0xdb, 0x57, 0xf1, 0xa1, 0x02, 0x9d, 0x3f, 0xce, 0x09, 0xdb, 0x57, 0xf5, 0x09, 0xb3, 0xd9, - 0x33, 0xb2, 0x35, 0xdc, 0x7e, 0x9c, 0x35, 0x72, 0x74, 0xd2, 0x49, 0xf3, 0x9e, 0x27, 0x06, 0x16, 0xa0, 0x01, 0x70, - 0x77, 0xb6, 0x67, 0x79, 0x77, 0x43, 0x40, 0xef, 0x92, 0x49, 0x7b, 0x5d, 0x6e, 0x52, 0xd6, 0xeb, 0x4e, 0x45, 0x05, - 0x0b, 0x3c, 0x0b, 0xf6, 0x02, 0xb5, 0x5f, 0x7b, 0x28, 0xce, 0x2f, 0xd9, 0xb6, 0xe9, 0x79, 0xd9, 0x77, 0x6f, 0xcf, - 0x22, 0x63, 0x9b, 0xf6, 0x76, 0x0f, 0x91, 0xb0, 0x9c, 0xb0, 0x0e, 0x38, 0xe1, 0xaa, 0x76, 0x40, 0x80, 0x3e, 0x05, - 0x22, 0x37, 0x96, 0x64, 0xb5, 0xa9, 0x8c, 0xee, 0x03, 0xbf, 0x5b, 0x4a, 0xa4, 0x1b, 0x6d, 0x49, 0x30, 0x7d, 0x82, - 0x51, 0xd3, 0x99, 0xa7, 0xa9, 0x6b, 0xaf, 0x2e, 0x6f, 0x8b, 0xb6, 0xfe, 0x0d, 0x68, 0x6c, 0xb6, 0x87, 0x89, 0xa1, - 0x0c, 0x62, 0xa0, 0xf7, 0x11, 0xef, 0x35, 0x1a, 0x19, 0x02, 0x85, 0x4c, 0x36, 0xc4, 0x32, 0xf1, 0x5a, 0xf4, 0xa3, - 0x23, 0x03, 0x8f, 0x2a, 0x01, 0x61, 0x0a, 0x42, 0x48, 0xd8, 0xb5, 0x41, 0xd8, 0x70, 0xb9, 0x6a, 0xb9, 0xb0, 0x91, - 0x6a, 0x43, 0x07, 0xff, 0xaf, 0x70, 0xd9, 0xea, 0x99, 0xe5, 0xa2, 0x18, 0xdc, 0xcc, 0x0d, 0x58, 0x24, 0x48, 0x8f, - 0x36, 0xdb, 0x43, 0x71, 0x7f, 0x2e, 0x36, 0x1b, 0x02, 0x12, 0x73, 0x98, 0xa0, 0x68, 0x38, 0x37, 0xc6, 0x58, 0x25, - 0x95, 0x96, 0xb5, 0x26, 0x31, 0x07, 0x01, 0xa3, 0xc3, 0x75, 0x5f, 0xdd, 0xa6, 0x0c, 0xdf, 0xa5, 0x02, 0xdf, 0x80, - 0x27, 0x4d, 0x2a, 0xb1, 0x7b, 0xbc, 0xa0, 0xd8, 0x10, 0xdd, 0xf3, 0xec, 0x6d, 0x01, 0xeb, 0x6c, 0xf6, 0x88, 0x08, - 0x7e, 0x57, 0xbf, 0xda, 0xe0, 0xbb, 0x85, 0x5f, 0x81, 0xf5, 0x73, 0x70, 0x92, 0x62, 0xd1, 0x90, 0xcd, 0xc2, 0x1d, - 0x19, 0x50, 0xae, 0xe2, 0x97, 0xc3, 0xd4, 0x9d, 0x62, 0xb8, 0xf6, 0xf1, 0x0a, 0xbf, 0xdf, 0x6a, 0xb7, 0xa1, 0xca, - 0xe2, 0x76, 0x6f, 0x8a, 0x86, 0xac, 0x9a, 0xde, 0x93, 0xb9, 0x95, 0x52, 0xff, 0x7a, 0x8f, 0x5b, 0x3b, 0xed, 0xfb, - 0x69, 0xbe, 0xf5, 0xe8, 0x5c, 0x35, 0xed, 0x53, 0x6b, 0x45, 0x70, 0xf0, 0xb3, 0x85, 0x9b, 0x3b, 0x03, 0x0e, 0xe0, - 0xe7, 0xef, 0x68, 0x5e, 0x67, 0x10, 0x9d, 0xde, 0x6a, 0xc6, 0xd7, 0xf1, 0x1f, 0xe3, 0x46, 0xdc, 0x4f, 0xff, 0x48, - 0xfe, 0x18, 0x37, 0x50, 0x1f, 0xc5, 0x8b, 0xdb, 0x35, 0x9b, 0xaf, 0x21, 0xd8, 0xda, 0xbd, 0x13, 0xfc, 0x26, 0x2c, - 0xc9, 0x35, 0xcd, 0x79, 0xb6, 0x76, 0x0f, 0x02, 0xae, 0xdd, 0xab, 0x44, 0x6b, 0xf3, 0xc6, 0xd5, 0x3a, 0x96, 0xa3, - 0x1c, 0x02, 0x0b, 0xc7, 0x07, 0xcd, 0xfe, 0xa0, 0xd5, 0x7c, 0x30, 0xb4, 0xff, 0x9a, 0x08, 0xf7, 0xa8, 0x16, 0xb1, - 0xed, 0xde, 0xd6, 0xd6, 0x8f, 0xc1, 0xb0, 0x03, 0x42, 0x81, 0x83, 0x5c, 0xfa, 0x3a, 0x43, 0xd6, 0xf7, 0x64, 0xbd, - 0x66, 0x2e, 0x9a, 0xb5, 0xd3, 0xe0, 0x97, 0xb1, 0x99, 0x8e, 0xdb, 0x49, 0xa7, 0xe7, 0xc5, 0x58, 0xd2, 0x80, 0x48, - 0xd3, 0x98, 0x41, 0x20, 0xa9, 0x95, 0xe1, 0xb0, 0x16, 0xb7, 0x51, 0x5a, 0xdd, 0x1f, 0x41, 0xca, 0x0f, 0x51, 0xca, - 0x4f, 0x08, 0x04, 0xd0, 0xb6, 0xcc, 0x51, 0xd9, 0x90, 0xf7, 0x5d, 0x7a, 0x68, 0x9c, 0x19, 0x1a, 0x7c, 0xbd, 0x6e, - 0x55, 0xc3, 0x54, 0x45, 0x7d, 0x98, 0xab, 0x0d, 0x16, 0xe4, 0x0d, 0xe8, 0x9a, 0x15, 0x11, 0xfd, 0xd0, 0x55, 0x1e, - 0xde, 0x43, 0xc6, 0x92, 0x80, 0x93, 0x7e, 0x5f, 0xf4, 0x0b, 0x72, 0xf5, 0x30, 0x06, 0x1f, 0x33, 0xcc, 0x07, 0x7a, - 0x50, 0x0c, 0x87, 0x28, 0x75, 0x4e, 0x67, 0xa9, 0x89, 0xb8, 0x12, 0xf8, 0x25, 0x17, 0xe0, 0x97, 0xac, 0x10, 0x1b, - 0x14, 0x43, 0xf2, 0x30, 0x8b, 0x25, 0x38, 0xe5, 0xef, 0xf1, 0x79, 0x7c, 0x1a, 0x1a, 0x98, 0x9a, 0x61, 0x99, 0x8b, - 0x6c, 0xb0, 0x98, 0xb3, 0x96, 0x40, 0x70, 0x33, 0xe0, 0x2e, 0xb5, 0x21, 0xd1, 0x58, 0x03, 0x45, 0xb7, 0x51, 0x68, - 0x66, 0xf4, 0x62, 0xa7, 0x8d, 0x41, 0xe4, 0xf0, 0xc2, 0x5c, 0xc3, 0x58, 0x04, 0x32, 0x97, 0xab, 0x1e, 0xfb, 0xcb, - 0x0f, 0x9b, 0x15, 0x06, 0xaf, 0xc8, 0x74, 0xe8, 0x8e, 0x63, 0xc6, 0x57, 0x79, 0xe2, 0x18, 0x82, 0x4c, 0x2c, 0x95, - 0x6e, 0x38, 0x26, 0xae, 0xa4, 0xcf, 0xc4, 0x90, 0xed, 0x86, 0x67, 0xe6, 0x42, 0x37, 0xdb, 0x7f, 0x3a, 0xb7, 0x73, - 0x4e, 0xb8, 0xd1, 0x4a, 0x1a, 0x6d, 0xd4, 0x33, 0x43, 0x55, 0x5d, 0x30, 0xbf, 0x87, 0x4e, 0x4b, 0x8b, 0x9d, 0xab, - 0x77, 0x2f, 0x7c, 0x9d, 0xaf, 0x8c, 0xbf, 0xc5, 0xaa, 0xd0, 0x8a, 0x0c, 0xb7, 0x5b, 0xc8, 0x9b, 0x33, 0x3d, 0xf4, - 0x8a, 0x5c, 0xa8, 0x0e, 0x7f, 0x51, 0x4f, 0x98, 0x07, 0x3b, 0xa3, 0x86, 0xf0, 0xe8, 0xf7, 0x26, 0x03, 0xe5, 0x1f, - 0x4c, 0x4c, 0xe6, 0x2c, 0xb9, 0xa1, 0x85, 0x88, 0x7f, 0x7c, 0x21, 0x4c, 0xac, 0xaa, 0x03, 0x18, 0xc8, 0x81, 0xa9, - 0x78, 0x00, 0xb7, 0x26, 0x7c, 0xc2, 0xd9, 0x38, 0x3d, 0x88, 0x7e, 0x6c, 0x88, 0xc6, 0x8f, 0xd1, 0x8f, 0xe0, 0xee, - 0xec, 0x5e, 0x87, 0x2c, 0xe3, 0x42, 0xf8, 0x7b, 0xac, 0x87, 0xa5, 0x4a, 0x19, 0x6b, 0xaf, 0x5b, 0x0e, 0x2f, 0xa4, - 0xee, 0x65, 0xf1, 0x43, 0x47, 0xac, 0x6d, 0x0a, 0xd6, 0x21, 0x25, 0x85, 0x67, 0x57, 0xcc, 0xad, 0x16, 0x73, 0x97, - 0x5a, 0xc2, 0x5f, 0x5f, 0x3d, 0x2c, 0x55, 0xd0, 0x70, 0x10, 0xba, 0xd2, 0x16, 0x12, 0x60, 0xe0, 0x52, 0xfa, 0x74, - 0xba, 0x33, 0x89, 0x8c, 0xb2, 0x18, 0xde, 0x3d, 0x08, 0x02, 0x09, 0xb0, 0xad, 0xb0, 0x2a, 0x70, 0xb9, 0x52, 0x45, - 0xbd, 0x94, 0x04, 0x02, 0xd0, 0x97, 0xde, 0x83, 0xf2, 0xb2, 0xe8, 0x35, 0x1a, 0x12, 0xb4, 0xb0, 0xd4, 0x5c, 0xab, - 0x62, 0x7a, 0x18, 0xbe, 0x6a, 0x18, 0x7c, 0x78, 0x87, 0xb4, 0xad, 0xa7, 0x45, 0x29, 0xa1, 0x76, 0x07, 0x1d, 0x82, - 0x55, 0x76, 0x50, 0xfe, 0x6d, 0x4c, 0x91, 0xcd, 0x1f, 0xb0, 0x1f, 0xa8, 0xeb, 0x70, 0xe8, 0x0a, 0x56, 0xbd, 0x94, - 0x51, 0x30, 0x60, 0xe5, 0x14, 0xa8, 0xbd, 0x93, 0x8c, 0x66, 0x33, 0x06, 0xea, 0x7e, 0x5b, 0xb4, 0x9a, 0xdb, 0x93, - 0xba, 0xdf, 0x90, 0x71, 0xf6, 0x11, 0xc6, 0xd9, 0x47, 0x81, 0x17, 0x8b, 0x24, 0x3f, 0xcb, 0x58, 0xe3, 0x58, 0x35, - 0x05, 0x3a, 0xe9, 0x00, 0x77, 0x06, 0x0e, 0x3c, 0x60, 0x8b, 0x72, 0x74, 0x44, 0x9d, 0xc5, 0x3d, 0x6d, 0x64, 0xde, - 0xdb, 0x13, 0x6a, 0x17, 0xb1, 0xc0, 0xcd, 0x9a, 0x99, 0x16, 0xb4, 0x56, 0x18, 0xe7, 0xf1, 0x30, 0x22, 0x63, 0x2d, - 0x7e, 0xc2, 0x96, 0x35, 0x55, 0xfd, 0x06, 0x9a, 0xa3, 0x5a, 0x90, 0x9b, 0x17, 0xc6, 0x5b, 0x95, 0x0c, 0xa2, 0x68, - 0x68, 0x39, 0x15, 0x62, 0x48, 0xc6, 0xa0, 0x35, 0x0c, 0x6e, 0xb5, 0xd7, 0x6b, 0xee, 0x11, 0x5f, 0xd4, 0xbc, 0xd5, - 0xcc, 0x2d, 0x40, 0x56, 0xc4, 0x51, 0x79, 0x6f, 0x12, 0x81, 0xf7, 0x6d, 0x19, 0x21, 0x6d, 0x35, 0xb0, 0x4f, 0x57, - 0x96, 0x8a, 0xcd, 0x77, 0x74, 0x3a, 0x4c, 0x23, 0x3b, 0xa2, 0x08, 0x7f, 0x2a, 0x21, 0x09, 0x57, 0x49, 0x9f, 0x54, - 0x26, 0x17, 0x4c, 0xa5, 0x1c, 0x7f, 0x2a, 0xa4, 0xd4, 0xd7, 0xf6, 0x4b, 0xe2, 0xea, 0x4e, 0x46, 0xe0, 0x4f, 0x53, - 0xa6, 0xdf, 0xd1, 0x62, 0xca, 0xc0, 0xaf, 0xc8, 0xdf, 0x8e, 0xa5, 0x94, 0x5c, 0xbd, 0x10, 0xf1, 0x80, 0x62, 0x78, - 0x77, 0x75, 0x88, 0xb5, 0x09, 0x81, 0x52, 0xe2, 0x22, 0x5c, 0x10, 0xbd, 0x29, 0xe4, 0xed, 0x5d, 0x5c, 0x60, 0xe7, - 0x00, 0x58, 0x3a, 0x4d, 0x02, 0xfc, 0xcb, 0xc7, 0x7c, 0xac, 0xc6, 0x9c, 0x1a, 0x5d, 0xbf, 0xfb, 0x9d, 0x7c, 0x02, - 0x7a, 0x5b, 0x3a, 0x0a, 0x0e, 0x5a, 0x43, 0xc8, 0x85, 0xbb, 0x30, 0xb8, 0xf8, 0x0a, 0x6b, 0x17, 0x85, 0xf1, 0xc6, - 0x02, 0xe8, 0x3d, 0xca, 0xc0, 0x82, 0x0d, 0x73, 0x4c, 0xe1, 0xd1, 0xda, 0x29, 0xd3, 0x41, 0x54, 0x90, 0x27, 0xe5, - 0xb3, 0xa4, 0xb5, 0xda, 0x6f, 0xd9, 0x04, 0xee, 0x30, 0x92, 0x6f, 0x17, 0x4e, 0x1c, 0x78, 0x40, 0xa6, 0xc9, 0x6c, - 0xb3, 0x6f, 0x7c, 0xe4, 0x91, 0xd7, 0x93, 0x78, 0x5f, 0x4b, 0x61, 0xbe, 0x59, 0xd1, 0x0d, 0x86, 0x50, 0x14, 0x61, - 0xbf, 0x37, 0x2a, 0xa6, 0xa8, 0x32, 0x68, 0x83, 0x86, 0xe5, 0x8d, 0xf8, 0x19, 0xce, 0x18, 0x5a, 0x2f, 0x64, 0xef, - 0xe8, 0xac, 0xc3, 0x99, 0xc3, 0x8c, 0x19, 0x81, 0x51, 0x69, 0x59, 0xd0, 0x29, 0x38, 0x3a, 0x57, 0x1f, 0x44, 0xc5, - 0xd5, 0xb1, 0x02, 0xf0, 0x24, 0x33, 0xf8, 0x27, 0xdf, 0x06, 0xeb, 0x61, 0xab, 0x66, 0x98, 0xfa, 0xb3, 0xde, 0x75, - 0x2d, 0x5f, 0x85, 0x38, 0xd2, 0xc6, 0x10, 0x5a, 0xe7, 0xf6, 0x0e, 0x50, 0xc4, 0x05, 0xbd, 0x48, 0x35, 0xfe, 0xa4, - 0x96, 0x23, 0xb3, 0xbe, 0xc6, 0x75, 0x4c, 0x1b, 0x44, 0xb1, 0xee, 0x9a, 0xf8, 0x53, 0xf5, 0x0a, 0xac, 0x4a, 0x81, - 0x75, 0x06, 0xe5, 0x87, 0x2a, 0x2f, 0x1b, 0x52, 0x49, 0xae, 0x4c, 0xa7, 0xd2, 0x74, 0x5a, 0x21, 0x94, 0x4b, 0x4f, - 0xca, 0xfb, 0x57, 0x08, 0x61, 0x60, 0xca, 0xec, 0xc1, 0x2a, 0xb5, 0x83, 0x55, 0xf0, 0xea, 0xc5, 0x16, 0x56, 0x49, - 0x38, 0x9e, 0x4b, 0x34, 0x2a, 0x2a, 0x1c, 0x32, 0xa4, 0x2f, 0xc4, 0x22, 0x48, 0x00, 0x2c, 0x7a, 0x99, 0xb9, 0xbc, - 0xef, 0xe1, 0x50, 0xd8, 0x93, 0x4c, 0xc2, 0xe9, 0x26, 0x34, 0x87, 0xe7, 0x81, 0x55, 0xdf, 0x23, 0xc4, 0xcc, 0xc4, - 0x7f, 0x82, 0x67, 0xa1, 0xbf, 0xff, 0x1c, 0xad, 0xb3, 0x20, 0x4f, 0xff, 0x25, 0x4a, 0x42, 0x63, 0xff, 0x39, 0x1e, - 0x3a, 0x24, 0x0c, 0x07, 0xbe, 0x3d, 0xc2, 0x0a, 0x07, 0x77, 0x8a, 0xf8, 0x0c, 0xee, 0xf0, 0xb1, 0x0e, 0x3d, 0x00, - 0x2c, 0xa1, 0x38, 0x04, 0xf9, 0x16, 0x8a, 0x99, 0x61, 0x6b, 0xb2, 0x0a, 0x2f, 0x70, 0xc1, 0x6a, 0xa1, 0xbc, 0xbf, - 0x6d, 0x79, 0x29, 0xad, 0x76, 0xc9, 0x6b, 0xcc, 0x81, 0xca, 0xcf, 0xf0, 0xc2, 0x57, 0x98, 0xf7, 0xaa, 0xdd, 0x17, - 0xfe, 0xe4, 0x80, 0x9e, 0x42, 0xc0, 0x48, 0xf7, 0x7b, 0x43, 0xb8, 0xa7, 0xe8, 0x65, 0x2e, 0x0e, 0xdb, 0x0e, 0xba, - 0x17, 0x98, 0xab, 0xeb, 0x2a, 0x6b, 0x01, 0xa6, 0xd0, 0xe0, 0xa0, 0x0a, 0x67, 0x04, 0xe6, 0xea, 0x45, 0x59, 0x70, - 0x01, 0xe2, 0x7d, 0x5f, 0x98, 0x9c, 0x32, 0x1a, 0xc0, 0xbb, 0xac, 0x7c, 0x74, 0xaa, 0xcf, 0xc1, 0x65, 0xdc, 0xb0, - 0x89, 0x4f, 0x84, 0x4f, 0x05, 0x56, 0xd2, 0x1a, 0x87, 0x46, 0x74, 0x4c, 0x17, 0x60, 0xb6, 0x01, 0x14, 0xdc, 0x9d, - 0x0f, 0x5b, 0x0b, 0x15, 0x3c, 0xc9, 0x5b, 0x7b, 0x41, 0x9b, 0x10, 0x67, 0xd2, 0x14, 0xdc, 0x6d, 0x17, 0x45, 0x60, - 0x7e, 0xfb, 0x6f, 0x85, 0x45, 0x82, 0x01, 0x95, 0x9a, 0x24, 0x08, 0x4f, 0x50, 0x1a, 0xe9, 0x56, 0x6e, 0x26, 0x90, - 0x4e, 0x44, 0x78, 0xc3, 0xfc, 0x72, 0xeb, 0x7c, 0x75, 0xd4, 0x40, 0x54, 0xd4, 0x40, 0x05, 0xd4, 0x40, 0xd6, 0xb7, - 0x7f, 0x01, 0x0b, 0x61, 0x23, 0x54, 0x89, 0x20, 0x20, 0xc2, 0x42, 0x1b, 0x3e, 0xa0, 0x48, 0x42, 0xc8, 0x1b, 0x40, - 0xc5, 0x94, 0xbc, 0x05, 0xa3, 0x71, 0x78, 0xbd, 0x07, 0xdc, 0x2f, 0x2d, 0xc3, 0xe0, 0x39, 0x05, 0x93, 0xff, 0xcc, - 0xe7, 0x43, 0xf5, 0x72, 0x75, 0x10, 0xc2, 0x4f, 0x20, 0x56, 0x84, 0xe3, 0x2f, 0x7e, 0x06, 0xb2, 0xa9, 0xb0, 0x3c, - 0x3a, 0x92, 0x20, 0xf0, 0x43, 0x14, 0xe1, 0x80, 0x67, 0x78, 0x9b, 0x6d, 0x11, 0x3d, 0x3f, 0x2b, 0x55, 0xcd, 0x4a, - 0x06, 0xb3, 0x2a, 0x3c, 0x8d, 0xa3, 0x1b, 0xc2, 0x40, 0x70, 0xa1, 0x76, 0xdf, 0x20, 0x04, 0xca, 0x96, 0x1b, 0x43, - 0x97, 0x9e, 0x82, 0xf9, 0x68, 0x1c, 0xbd, 0x65, 0xf0, 0xb0, 0xb0, 0x71, 0x47, 0x61, 0x9a, 0x65, 0xda, 0x30, 0x8f, - 0x8d, 0xc0, 0x49, 0x9d, 0xa2, 0xe4, 0xb3, 0xe4, 0x22, 0x8e, 0x9a, 0x57, 0x11, 0x6a, 0xc0, 0xbf, 0x0d, 0x8e, 0x7a, - 0x34, 0xa1, 0xe3, 0xb1, 0x0f, 0x7e, 0x93, 0x11, 0xb3, 0xc9, 0xd6, 0x6b, 0x51, 0x11, 0xf4, 0xc4, 0x6e, 0x30, 0x60, - 0x25, 0x9e, 0x00, 0xfb, 0x60, 0x39, 0x58, 0xf2, 0x4e, 0xc4, 0xca, 0x9f, 0x52, 0x18, 0xac, 0x9e, 0x33, 0x84, 0x70, - 0x16, 0x30, 0x29, 0xff, 0xf9, 0x4c, 0xc3, 0xf5, 0xf3, 0xf3, 0x75, 0x8c, 0x88, 0xf4, 0x41, 0xe4, 0x6a, 0xec, 0x88, - 0x08, 0xc2, 0x96, 0xe9, 0x81, 0x2b, 0xf3, 0x83, 0xb7, 0xae, 0x1e, 0xda, 0x70, 0x71, 0x60, 0x40, 0x8d, 0x02, 0xa3, - 0x15, 0x9c, 0x93, 0x72, 0xe0, 0xa0, 0x84, 0xd0, 0xac, 0x88, 0x67, 0xe4, 0x0a, 0x22, 0xe1, 0x65, 0xa8, 0x07, 0x86, - 0x05, 0x81, 0x04, 0x35, 0x03, 0x09, 0x2a, 0xf3, 0xb5, 0xc7, 0x30, 0xeb, 0xdc, 0xcc, 0x76, 0x86, 0x7a, 0x2e, 0xc8, - 0xcf, 0xcf, 0x3a, 0x1e, 0x03, 0x4b, 0x7b, 0x74, 0x54, 0x40, 0x04, 0x31, 0xa0, 0xe0, 0xa5, 0x04, 0x18, 0x68, 0xc0, - 0x8b, 0x2d, 0x0d, 0xf8, 0x42, 0x1b, 0xaf, 0x03, 0x63, 0xeb, 0x53, 0x06, 0xb9, 0x78, 0x55, 0xed, 0x69, 0x42, 0xc8, - 0x61, 0xab, 0xaf, 0xd3, 0xdd, 0x08, 0x89, 0xfd, 0x8f, 0xda, 0x04, 0x1a, 0x73, 0xa4, 0xbb, 0xda, 0x98, 0x7f, 0xd7, - 0xf4, 0x88, 0xd5, 0x24, 0xa4, 0x0b, 0xd2, 0xe5, 0xf9, 0xb4, 0x57, 0x70, 0xc5, 0x2a, 0x8d, 0x1c, 0x5c, 0x80, 0x3e, - 0x1b, 0x10, 0xa0, 0x40, 0xa5, 0xa9, 0x04, 0x2d, 0xe2, 0x22, 0x29, 0xd9, 0x30, 0xcc, 0x20, 0x4c, 0x61, 0xb5, 0x12, - 0x74, 0x6b, 0x0d, 0x80, 0x77, 0x66, 0xf6, 0x4f, 0xe9, 0x83, 0x4d, 0x37, 0xde, 0x3c, 0x02, 0x08, 0xc8, 0x61, 0xbb, - 0x64, 0xd7, 0xc5, 0x56, 0x65, 0x16, 0xd6, 0x32, 0xb6, 0x72, 0xbb, 0x1e, 0x63, 0xef, 0xc4, 0x2e, 0x9f, 0x00, 0x21, - 0x6a, 0x4b, 0xa6, 0x11, 0x4b, 0x18, 0xb2, 0xae, 0x0d, 0xd9, 0x68, 0x43, 0xe1, 0xa9, 0x44, 0x0e, 0x5c, 0xa2, 0x09, - 0x92, 0xef, 0xb8, 0x04, 0x87, 0xf0, 0xc2, 0x23, 0xfc, 0x57, 0x60, 0x91, 0x0a, 0xcc, 0xb0, 0x5c, 0xaf, 0xa1, 0x9e, - 0xc7, 0xfb, 0x6c, 0x3b, 0x38, 0xa9, 0xdc, 0x1a, 0xbb, 0xb4, 0x13, 0x8f, 0xcb, 0x26, 0x24, 0xce, 0xa0, 0x5f, 0x5f, - 0x11, 0xf5, 0x0f, 0xdb, 0xe9, 0x0b, 0xff, 0x5e, 0x99, 0xdb, 0x81, 0xd8, 0xb0, 0xde, 0x60, 0xf5, 0x01, 0xb4, 0xfc, - 0x73, 0xe6, 0x1f, 0x2a, 0x0b, 0x6e, 0x12, 0xd4, 0xf6, 0x22, 0xf6, 0x58, 0x0f, 0x31, 0x52, 0x5b, 0xdc, 0x3d, 0x42, - 0xfc, 0xe7, 0x9d, 0x28, 0x06, 0x3c, 0xa9, 0xf8, 0xe7, 0x18, 0xf5, 0x20, 0x14, 0xb5, 0xf5, 0xb0, 0x01, 0x4a, 0xbb, - 0xda, 0x54, 0x62, 0x64, 0x48, 0x20, 0xdf, 0xba, 0xf0, 0x82, 0xe6, 0x24, 0x52, 0x20, 0x27, 0x57, 0x5d, 0x3c, 0xca, - 0xb6, 0x84, 0xb9, 0xde, 0x0e, 0x8e, 0x99, 0xab, 0x8d, 0xac, 0x88, 0xdf, 0x01, 0x3b, 0xc3, 0x8d, 0x64, 0xe9, 0xc0, - 0xa7, 0x6a, 0xe0, 0xf3, 0x6b, 0x6e, 0x28, 0x8a, 0x42, 0xfd, 0x77, 0xf6, 0x91, 0x39, 0xf8, 0x9d, 0x06, 0xe2, 0x63, - 0xe6, 0x74, 0x24, 0x5b, 0xa1, 0xd6, 0x9c, 0x1d, 0x2f, 0xdb, 0x8e, 0x30, 0x28, 0x6c, 0xf4, 0xbe, 0x0a, 0x59, 0xc5, - 0xde, 0x4e, 0x45, 0x30, 0xa7, 0x1b, 0x55, 0x39, 0xa7, 0x72, 0xcb, 0xa8, 0x96, 0x9a, 0x06, 0x88, 0x70, 0xe5, 0x13, - 0xc9, 0x87, 0xcc, 0x84, 0x7f, 0x30, 0x18, 0x57, 0x8f, 0x14, 0xfe, 0x61, 0x5f, 0xec, 0x90, 0xdd, 0xe8, 0x70, 0x5b, - 0x41, 0xf3, 0x42, 0x05, 0x0f, 0x38, 0x2a, 0x59, 0x42, 0xa4, 0xc8, 0xd5, 0xa1, 0xaa, 0x99, 0xb2, 0x7d, 0x8a, 0x10, - 0x42, 0xda, 0xe3, 0xac, 0x1b, 0x5a, 0x3d, 0xf4, 0x48, 0xe5, 0x34, 0xb9, 0x43, 0x73, 0x5d, 0x80, 0x0a, 0x23, 0x90, - 0xae, 0xbe, 0xb0, 0xbb, 0x54, 0x42, 0xf4, 0xf2, 0x8d, 0x0b, 0x61, 0xec, 0xac, 0x2c, 0x71, 0x61, 0x46, 0x6d, 0xc3, - 0xe8, 0xba, 0x8d, 0xe1, 0x6c, 0x60, 0xcc, 0x34, 0x28, 0x69, 0x41, 0xa8, 0xeb, 0x1e, 0xbd, 0xcc, 0x4c, 0xa0, 0xc7, - 0x9c, 0xd0, 0x06, 0xc3, 0x33, 0xa2, 0xc1, 0xb2, 0xa9, 0x00, 0x0b, 0xbe, 0x55, 0x91, 0x5a, 0x9b, 0x4d, 0x16, 0x7f, - 0xd4, 0xb1, 0x79, 0xda, 0x2f, 0xaf, 0x98, 0xe7, 0xc2, 0x47, 0x47, 0xc8, 0x7c, 0x3c, 0xba, 0xa7, 0x6f, 0xae, 0x5f, - 0xbc, 0x7c, 0xfd, 0x6a, 0xbd, 0x6e, 0xb3, 0x66, 0xfb, 0x0c, 0xff, 0x43, 0x97, 0xf1, 0x60, 0xcb, 0x28, 0x40, 0x47, - 0x47, 0x87, 0xdc, 0xb8, 0xf0, 0x7c, 0xe1, 0x0b, 0x88, 0x1b, 0xa4, 0x87, 0x38, 0x2f, 0xca, 0x98, 0x20, 0xb7, 0x51, - 0x3f, 0xba, 0x8b, 0x40, 0x09, 0x55, 0x91, 0xbf, 0xdf, 0xb6, 0x67, 0x7f, 0x00, 0x81, 0x89, 0xa0, 0x3e, 0x44, 0x00, - 0x81, 0x78, 0xa5, 0xb8, 0x20, 0xcc, 0x27, 0x40, 0x14, 0xef, 0x09, 0x70, 0xa6, 0x26, 0x6a, 0xd5, 0x44, 0xc5, 0x05, - 0x90, 0x44, 0x1b, 0x8e, 0x92, 0x9e, 0x98, 0x00, 0xde, 0x10, 0x94, 0xd2, 0xfe, 0xea, 0xe5, 0xce, 0x5d, 0x2a, 0x47, - 0xfd, 0x56, 0x9a, 0xe3, 0x99, 0xfb, 0x9c, 0xc1, 0xe7, 0xac, 0xe7, 0x4f, 0x07, 0x71, 0x9c, 0xe3, 0x25, 0x11, 0xc7, - 0xfe, 0x59, 0xc4, 0xd5, 0xa2, 0x60, 0x5f, 0xb9, 0x5c, 0xaa, 0x74, 0x75, 0x9b, 0xca, 0xe4, 0xb6, 0x39, 0x3e, 0x8e, - 0x8b, 0xe4, 0xb6, 0xa9, 0x92, 0x5b, 0x84, 0xef, 0x52, 0x99, 0xdc, 0xd9, 0x94, 0xbb, 0xa6, 0x82, 0x9b, 0x2f, 0x2c, - 0xe0, 0x50, 0xb4, 0x45, 0x1b, 0xcb, 0xed, 0xa2, 0x36, 0xc5, 0x15, 0x0d, 0xa3, 0x29, 0xee, 0xd9, 0xf8, 0x61, 0xf8, - 0x12, 0x5c, 0x9a, 0x34, 0x91, 0x7f, 0x80, 0xf4, 0xd3, 0xaa, 0x0c, 0xdc, 0x67, 0xa4, 0xd5, 0x9b, 0x5d, 0x8a, 0x66, - 0xbb, 0xd7, 0x68, 0xcc, 0x60, 0xef, 0x66, 0x24, 0xf7, 0xc5, 0x66, 0x0d, 0x13, 0x5f, 0xe7, 0x30, 0x5b, 0xaf, 0x0f, - 0x73, 0x64, 0x36, 0xdc, 0x94, 0xc5, 0x7a, 0x30, 0x1b, 0xe2, 0x16, 0x7e, 0x9f, 0x21, 0xb4, 0x62, 0x83, 0xd9, 0x90, - 0xb0, 0xc1, 0xac, 0xd1, 0x1e, 0x5a, 0x43, 0x3b, 0xb3, 0x15, 0x37, 0x10, 0x42, 0x73, 0x36, 0x3c, 0x31, 0x25, 0xa5, - 0xcb, 0xb7, 0x5f, 0xb4, 0x0a, 0xe8, 0xa7, 0x6a, 0xc1, 0xcb, 0x24, 0xee, 0x40, 0x5f, 0xf4, 0xd2, 0x3e, 0xdd, 0x5a, - 0x90, 0xd3, 0x93, 0xca, 0xd5, 0x9e, 0x22, 0x6c, 0x7a, 0x52, 0xc7, 0xc5, 0xb1, 0x69, 0xc6, 0x75, 0x29, 0xdd, 0x77, - 0xa8, 0x19, 0xf9, 0xcb, 0xc1, 0x02, 0x10, 0xa4, 0x82, 0x47, 0x5e, 0xb8, 0x70, 0x4a, 0x21, 0x5c, 0x1c, 0x54, 0x76, - 0x60, 0x92, 0x93, 0x56, 0x2f, 0x37, 0x96, 0xfe, 0xb9, 0x8b, 0x68, 0x4a, 0x31, 0x25, 0x99, 0x2f, 0x99, 0x1b, 0xb0, - 0xd0, 0x6d, 0xca, 0x33, 0x03, 0xbd, 0xd2, 0x10, 0x8f, 0x09, 0xc4, 0x43, 0xea, 0x15, 0xc6, 0xc0, 0x2b, 0x9e, 0x35, - 0x8b, 0x01, 0x1b, 0xa2, 0x93, 0x53, 0x4c, 0x07, 0x7f, 0x66, 0x8b, 0x36, 0x3c, 0x16, 0xf8, 0xe7, 0x90, 0xcc, 0x9a, - 0xb2, 0x4c, 0x10, 0x90, 0x30, 0x6e, 0xca, 0x63, 0xd8, 0x4b, 0x08, 0x67, 0xb6, 0x62, 0x36, 0x60, 0xc3, 0xe6, 0xac, - 0xac, 0xd8, 0xf1, 0x15, 0x1b, 0xb2, 0x4c, 0xb0, 0x15, 0x1b, 0xae, 0x62, 0xf8, 0x3a, 0x83, 0x01, 0x41, 0x08, 0x00, - 0x06, 0x00, 0xd0, 0x28, 0x88, 0xe6, 0x8b, 0x15, 0xf1, 0x9b, 0xdd, 0xde, 0xe3, 0xb7, 0xc0, 0x02, 0xad, 0xb6, 0xff, - 0xf7, 0xa1, 0x0c, 0xd8, 0x53, 0x16, 0x26, 0x66, 0x6e, 0x61, 0x55, 0x74, 0x00, 0x95, 0x12, 0x61, 0x0a, 0x03, 0x99, - 0xc3, 0xcc, 0x40, 0x2d, 0xd0, 0x1a, 0xe4, 0x03, 0x3d, 0x6c, 0x66, 0x70, 0xc4, 0xc0, 0x3b, 0x34, 0x64, 0x66, 0x8c, - 0x09, 0xe3, 0x1c, 0xa6, 0x98, 0x19, 0xf0, 0xcc, 0xd2, 0xd6, 0x46, 0x1a, 0x59, 0xae, 0x9f, 0xf7, 0xff, 0xd2, 0xb1, - 0x1a, 0x14, 0xcd, 0xf6, 0x10, 0x1d, 0x12, 0x62, 0x3f, 0x86, 0xb0, 0xc9, 0x5c, 0x6a, 0xc3, 0x7c, 0x9f, 0x74, 0x52, - 0xfb, 0x09, 0x7f, 0x86, 0x1b, 0xb3, 0x03, 0x40, 0x47, 0x86, 0xcd, 0xfa, 0xcb, 0x9a, 0xca, 0xeb, 0xe3, 0xde, 0x28, - 0x95, 0xfb, 0xde, 0x9d, 0x0e, 0x54, 0x13, 0xa1, 0xb7, 0x1e, 0x2e, 0x1f, 0xea, 0x21, 0x60, 0xc6, 0x60, 0x6e, 0x99, - 0xd1, 0xf7, 0x42, 0x24, 0x17, 0x44, 0x02, 0x4b, 0x82, 0x29, 0x61, 0xb0, 0xb7, 0x8e, 0x8e, 0x4c, 0x35, 0xd6, 0x80, - 0xe7, 0x49, 0x11, 0x08, 0x06, 0x3e, 0x82, 0x32, 0xa0, 0x89, 0x32, 0xb7, 0xe1, 0xe4, 0x23, 0x73, 0xbf, 0x70, 0x79, - 0xfb, 0x58, 0x38, 0x6d, 0xab, 0xb9, 0x1e, 0x2f, 0x0b, 0xdc, 0x95, 0xf7, 0x92, 0x56, 0xc1, 0x8d, 0xec, 0x4d, 0x9e, - 0x32, 0x77, 0xeb, 0xbe, 0x54, 0x67, 0x7f, 0x33, 0x9d, 0xb2, 0x99, 0xce, 0x6e, 0x33, 0x61, 0x5c, 0xc9, 0x6f, 0x59, - 0x45, 0x9a, 0x93, 0x35, 0x51, 0x0b, 0x2a, 0xfe, 0x41, 0x17, 0xa0, 0x1d, 0xe5, 0xf6, 0x5e, 0x15, 0x4e, 0xae, 0x9c, - 0x5c, 0x1d, 0xe6, 0x86, 0xb8, 0x22, 0x73, 0xa1, 0x0e, 0x01, 0x5e, 0x5e, 0x94, 0x8f, 0x0f, 0x70, 0x29, 0x7e, 0x91, - 0x63, 0x17, 0xe5, 0x54, 0x48, 0x2d, 0x05, 0x8b, 0x90, 0x41, 0x55, 0x17, 0x03, 0x7b, 0x65, 0xf7, 0x9e, 0xe8, 0xf3, - 0x41, 0x15, 0x31, 0x6f, 0x68, 0x9e, 0xfb, 0xf8, 0x9e, 0xa6, 0xd8, 0xa9, 0x89, 0x33, 0xf2, 0x5b, 0x16, 0xe7, 0x20, - 0x9b, 0x0d, 0xaa, 0xd7, 0x7e, 0x1b, 0x6d, 0x5c, 0x34, 0x63, 0xd1, 0x37, 0x4f, 0x9c, 0xfc, 0x50, 0x18, 0xe3, 0x00, - 0xeb, 0xe8, 0x8f, 0x30, 0xb5, 0x60, 0xcf, 0x12, 0x4f, 0xa1, 0x93, 0x5b, 0x9b, 0x76, 0x17, 0xa6, 0xdd, 0x99, 0xb4, - 0x0e, 0x94, 0x03, 0xd2, 0xec, 0xca, 0x74, 0xee, 0xfc, 0xf7, 0x1d, 0xbc, 0x74, 0xbb, 0x81, 0x48, 0xdc, 0x8b, 0x47, - 0xc6, 0x18, 0xe2, 0x0d, 0xd8, 0x88, 0xaa, 0xa3, 0xa3, 0x9f, 0x9d, 0xf7, 0x6d, 0x25, 0xcb, 0x7e, 0x2b, 0x1c, 0xd8, - 0x16, 0x53, 0xe9, 0xf2, 0xc6, 0x32, 0x5b, 0x82, 0x5d, 0xe7, 0xe1, 0x37, 0xe2, 0xe1, 0x8b, 0x90, 0x69, 0xb1, 0xae, - 0xe2, 0xaf, 0xe4, 0xb8, 0xf4, 0x10, 0xd5, 0x10, 0x81, 0xb4, 0xb2, 0x2e, 0x0d, 0x4d, 0x47, 0xaf, 0x67, 0x74, 0x2c, - 0x6f, 0xde, 0x4a, 0xa9, 0x87, 0xf6, 0x45, 0x6e, 0x9d, 0xc0, 0xa3, 0x85, 0x35, 0x86, 0xe6, 0xae, 0xf4, 0x4e, 0xb2, - 0x01, 0x51, 0xeb, 0xe3, 0x0e, 0x25, 0x91, 0x58, 0x54, 0x77, 0x21, 0x1c, 0xee, 0x42, 0x30, 0x2f, 0x83, 0xb6, 0x41, - 0xec, 0x76, 0x17, 0xb4, 0x0d, 0x9c, 0xba, 0x6d, 0xe0, 0xf6, 0x60, 0xb0, 0xb0, 0xf7, 0xe1, 0xe5, 0x58, 0x8e, 0x85, - 0xe3, 0x0f, 0xee, 0xd9, 0x07, 0x80, 0x40, 0xed, 0xc3, 0x8a, 0x27, 0x0e, 0x04, 0x89, 0x33, 0x1c, 0xfd, 0xc0, 0xd9, - 0x8d, 0xb5, 0x1c, 0x9e, 0x2f, 0x96, 0x9a, 0x8d, 0xcd, 0x1d, 0x35, 0xa8, 0xf8, 0xea, 0x7e, 0x5e, 0xbf, 0x66, 0x35, - 0xdd, 0xf8, 0x3d, 0x08, 0x23, 0xe1, 0x94, 0x1d, 0x46, 0x21, 0x61, 0x83, 0x59, 0x95, 0xf1, 0xda, 0x7e, 0x87, 0x78, - 0x0f, 0xda, 0x84, 0x13, 0x2c, 0x6a, 0x17, 0x54, 0x11, 0xb6, 0xf1, 0xc6, 0x82, 0x28, 0x0f, 0x6f, 0x76, 0x8c, 0xa6, - 0x57, 0x1b, 0x08, 0x74, 0xdc, 0x8f, 0x9a, 0x51, 0x83, 0xa5, 0x2e, 0x28, 0xb3, 0x8f, 0x30, 0xae, 0x2e, 0xcf, 0x4c, - 0x9c, 0xf6, 0x52, 0xaf, 0xfe, 0x7b, 0x06, 0x06, 0xf8, 0x02, 0xbc, 0xc4, 0xc2, 0xe8, 0xae, 0x03, 0xdd, 0x80, 0xfa, - 0xb2, 0xc1, 0x86, 0x68, 0xbd, 0x6e, 0x95, 0xcf, 0x40, 0xb9, 0x6b, 0x2e, 0x61, 0xaf, 0xb9, 0x84, 0xbb, 0xe6, 0x12, - 0xfe, 0x9a, 0x4b, 0x98, 0x6b, 0x2e, 0xe1, 0xaf, 0xb9, 0x3c, 0x08, 0x7f, 0x0a, 0xe2, 0x38, 0xc6, 0x1c, 0xe2, 0x2a, - 0x6a, 0x1b, 0x19, 0x0f, 0x2e, 0x3c, 0x0f, 0x59, 0xa2, 0xca, 0xe5, 0x0f, 0x63, 0xc8, 0xe5, 0xdb, 0xb6, 0x12, 0xc6, - 0x6d, 0x8a, 0x29, 0x88, 0x9c, 0x7e, 0x74, 0x54, 0xb9, 0x3b, 0x0f, 0x5a, 0xc3, 0x94, 0xe3, 0x95, 0x75, 0xa2, 0xfd, - 0x27, 0xe8, 0xe4, 0xcd, 0xaf, 0x8f, 0xa9, 0xdc, 0x10, 0xe1, 0x4c, 0xee, 0x0f, 0xdb, 0x9e, 0x52, 0xfc, 0x94, 0x99, - 0xf0, 0xe4, 0x3c, 0xd1, 0x46, 0x04, 0x41, 0x88, 0x12, 0xf5, 0xff, 0xb2, 0xf7, 0xae, 0xcb, 0x6d, 0x23, 0x59, 0xba, - 0xe8, 0xab, 0x48, 0x0c, 0x9b, 0x05, 0x98, 0x49, 0x8a, 0xf2, 0xde, 0x33, 0x11, 0x07, 0x54, 0x9a, 0xe1, 0x4b, 0xb9, - 0xcb, 0x5d, 0xe5, 0x4b, 0x5b, 0xae, 0x6a, 0x57, 0x33, 0x78, 0x54, 0x10, 0x90, 0x24, 0xe0, 0x02, 0x01, 0x16, 0x00, - 0x4a, 0xa4, 0x49, 0xbc, 0xfb, 0x8e, 0xb5, 0x56, 0x5e, 0x41, 0x50, 0x76, 0xcf, 0xec, 0xf9, 0x75, 0xce, 0x1f, 0x5b, - 0x4c, 0x24, 0x12, 0x79, 0xcf, 0x95, 0xeb, 0xf2, 0x7d, 0x2c, 0xe2, 0x05, 0xad, 0x77, 0x15, 0x0a, 0x8f, 0xaa, 0x28, - 0xe5, 0x56, 0xf2, 0x32, 0x83, 0x20, 0x76, 0xf4, 0xc2, 0xf0, 0x27, 0x10, 0x42, 0x10, 0x61, 0xc2, 0xe7, 0x61, 0x46, - 0xdb, 0x59, 0xa4, 0x93, 0x7e, 0x1f, 0x66, 0xb8, 0x81, 0x95, 0xfc, 0x5c, 0xf5, 0xd9, 0x7e, 0x1b, 0x84, 0x6c, 0x17, - 0x44, 0xec, 0xb6, 0xd8, 0x06, 0xa5, 0x75, 0x24, 0x5e, 0x2b, 0xc3, 0xdf, 0xc2, 0xeb, 0xe5, 0x21, 0xc4, 0xfb, 0xf4, - 0xd2, 0xfc, 0x2c, 0x6d, 0x45, 0x01, 0xee, 0x23, 0xf4, 0xa8, 0x0e, 0x04, 0x3b, 0xe1, 0x09, 0x0f, 0xe0, 0x64, 0x35, - 0xab, 0xf8, 0xa3, 0x14, 0xc4, 0x89, 0x82, 0x43, 0xc0, 0xd5, 0xf6, 0x3a, 0xfd, 0x0a, 0x86, 0x2f, 0x1d, 0x6c, 0x39, - 0xbc, 0x2d, 0xb6, 0x3d, 0x56, 0xf2, 0x0f, 0xc0, 0xbe, 0xd5, 0x93, 0xb1, 0xba, 0x3d, 0x70, 0xd6, 0xa5, 0x14, 0x1d, - 0x6f, 0x8a, 0xc3, 0xdb, 0xf3, 0xd9, 0x7e, 0x1b, 0x44, 0x6c, 0x17, 0x64, 0x58, 0xeb, 0xa4, 0xe1, 0x38, 0x18, 0xc2, - 0x67, 0x31, 0xc2, 0xfe, 0x2f, 0xea, 0x81, 0x97, 0x90, 0x1a, 0x0a, 0x5c, 0x0c, 0x36, 0x1c, 0xad, 0xed, 0x32, 0x0d, - 0xdc, 0xd4, 0xa0, 0xd7, 0xf7, 0x14, 0xa2, 0xbc, 0x60, 0x34, 0x37, 0x82, 0x75, 0x63, 0xc8, 0xc5, 0xe1, 0xb8, 0x59, - 0x0c, 0x79, 0x49, 0xd3, 0x69, 0x10, 0x4a, 0x77, 0x96, 0x35, 0x24, 0x51, 0xf6, 0x41, 0xa8, 0x5d, 0x5b, 0xf6, 0xdb, - 0xc0, 0xf6, 0xe5, 0x8f, 0x86, 0xb1, 0x7f, 0xb1, 0x78, 0x22, 0xa4, 0x8b, 0x78, 0x0e, 0x82, 0xa8, 0xfd, 0x3c, 0x1b, - 0x6e, 0xfc, 0x8b, 0xf5, 0x13, 0xa1, 0xfc, 0xc6, 0x73, 0x5b, 0x0e, 0x11, 0x59, 0x0b, 0x5f, 0x18, 0x0f, 0x0f, 0xae, - 0x0c, 0x6d, 0x87, 0x83, 0xd0, 0x7f, 0x9b, 0x35, 0x82, 0x1b, 0x1b, 0xda, 0xe7, 0x0b, 0x1f, 0xb6, 0x36, 0x1a, 0x6b, - 0x8a, 0xe9, 0x16, 0xfa, 0x37, 0x99, 0x2d, 0xed, 0x69, 0x54, 0xf2, 0xe2, 0xd4, 0x34, 0x62, 0x21, 0x0c, 0x18, 0xfa, - 0xc9, 0x7c, 0x00, 0xd5, 0xdc, 0xf1, 0x08, 0x64, 0xf2, 0x81, 0x1e, 0xac, 0x49, 0xad, 0xfa, 0x6b, 0x98, 0xc9, 0xff, - 0x23, 0x15, 0x16, 0xa3, 0xbb, 0x6d, 0x98, 0xa9, 0x3f, 0x22, 0xf9, 0x07, 0xcb, 0xf9, 0x2e, 0xf5, 0x42, 0xed, 0xc7, - 0xc2, 0x0a, 0x0c, 0x4a, 0x54, 0x0d, 0xe8, 0x81, 0x08, 0xaa, 0x32, 0x48, 0x33, 0xac, 0xce, 0x41, 0xbf, 0x7b, 0x5a, - 0x75, 0x24, 0x87, 0xb4, 0x56, 0x43, 0x2a, 0x98, 0x2a, 0x35, 0xc8, 0x0f, 0x87, 0x65, 0xca, 0x74, 0x19, 0x70, 0x49, - 0x5f, 0xa6, 0x4a, 0x29, 0xfc, 0x17, 0x02, 0xd0, 0x39, 0xb8, 0xc7, 0x97, 0x63, 0x20, 0xcd, 0xb0, 0xf0, 0x5b, 0xb3, - 0xe3, 0x6b, 0x12, 0x6e, 0x93, 0xe0, 0x62, 0x80, 0x73, 0x74, 0x15, 0x96, 0xcb, 0x14, 0x22, 0xa8, 0x4a, 0xa8, 0x6f, - 0x65, 0x1a, 0x94, 0xb6, 0x1a, 0x84, 0x35, 0x09, 0x75, 0x26, 0xd9, 0xa8, 0xb4, 0xdd, 0x28, 0xcc, 0x16, 0x71, 0x3d, - 0x23, 0xac, 0x39, 0x9b, 0xa9, 0x06, 0x26, 0x0d, 0xc7, 0x4d, 0xa3, 0xb5, 0xa8, 0x50, 0x53, 0x98, 0xd7, 0xb8, 0xaa, - 0x54, 0x75, 0x37, 0xa7, 0x96, 0xd2, 0xa2, 0xbd, 0xea, 0x26, 0xd9, 0x90, 0xcb, 0x50, 0x86, 0xc1, 0x46, 0x8e, 0x60, - 0x02, 0x49, 0x72, 0xe6, 0x6f, 0xe4, 0x1f, 0x6a, 0xd3, 0xb5, 0x80, 0x39, 0xc6, 0x2c, 0x1b, 0x16, 0xf4, 0x0a, 0xdc, - 0x03, 0xad, 0xf4, 0x7c, 0x9a, 0x5d, 0xe4, 0x41, 0x32, 0x2c, 0xf4, 0xb2, 0xc9, 0xf8, 0x5f, 0xc2, 0x48, 0x93, 0x19, - 0x2b, 0x59, 0x64, 0xbb, 0x3a, 0x25, 0xce, 0xe3, 0x04, 0xb6, 0x47, 0xd3, 0x5b, 0xbe, 0xcf, 0x20, 0x2a, 0x08, 0x14, - 0xcc, 0x98, 0x2f, 0xbb, 0x78, 0xea, 0xfb, 0xcc, 0x32, 0x75, 0x1f, 0x0e, 0xc6, 0x8c, 0xed, 0xf7, 0xfb, 0x79, 0xbf, - 0xaf, 0xe6, 0x5b, 0xbf, 0x9f, 0x3c, 0x33, 0x7f, 0x7b, 0xc0, 0xa0, 0x20, 0x27, 0xa2, 0xa9, 0x10, 0xc1, 0x3f, 0x24, - 0x4f, 0x90, 0x8c, 0xee, 0xb8, 0xcf, 0x2d, 0x67, 0xcb, 0xea, 0x08, 0x04, 0xf3, 0x70, 0xb8, 0x54, 0x60, 0xd7, 0x12, - 0x45, 0x42, 0x96, 0xff, 0x04, 0x8c, 0x67, 0xee, 0x03, 0x2c, 0x19, 0x80, 0xb0, 0x55, 0x9e, 0xae, 0xf7, 0x7c, 0x15, - 0xbc, 0xd3, 0xf1, 0xae, 0xb1, 0x22, 0x03, 0x71, 0x0b, 0x6c, 0xc4, 0x5a, 0x7b, 0x40, 0xce, 0x14, 0xe0, 0x78, 0x71, - 0x38, 0x9c, 0xcb, 0x5f, 0xba, 0xd9, 0x3a, 0x81, 0x4a, 0x81, 0xdb, 0xa3, 0x93, 0x83, 0xff, 0x01, 0x34, 0x83, 0x72, - 0x98, 0xd7, 0xdb, 0x3f, 0x98, 0x93, 0x9f, 0x9e, 0xe2, 0x9f, 0xf0, 0x10, 0x9d, 0x7e, 0xbb, 0x37, 0x7f, 0x50, 0x54, - 0x1e, 0x0e, 0x6a, 0xf1, 0x9f, 0x73, 0x5e, 0xc1, 0x2f, 0x7c, 0x13, 0x98, 0x4d, 0xa6, 0xde, 0xc9, 0x37, 0x79, 0xce, - 0xd4, 0x6b, 0xbc, 0x62, 0xf2, 0x1d, 0x0e, 0xe7, 0x62, 0x54, 0x6f, 0x47, 0x4e, 0xb4, 0x53, 0x8e, 0x71, 0x30, 0xf8, - 0x2f, 0xa2, 0x6d, 0x42, 0x80, 0xa1, 0x1c, 0x8e, 0xcc, 0xc6, 0x95, 0x25, 0x9e, 0xa5, 0xf3, 0xcb, 0x49, 0x5d, 0xee, - 0xb4, 0xe2, 0x69, 0x0f, 0x2c, 0x6e, 0x6b, 0xf0, 0x02, 0xb8, 0xb3, 0xd8, 0xba, 0x52, 0x70, 0xb8, 0x80, 0x38, 0xc5, - 0x09, 0x88, 0xa0, 0xfd, 0xbe, 0xc4, 0x7b, 0x05, 0x7d, 0xd2, 0x8f, 0x10, 0x0c, 0xf9, 0x8b, 0x04, 0xdc, 0xf5, 0x7a, - 0x35, 0xc6, 0xf7, 0x52, 0x08, 0xae, 0xcf, 0x34, 0x00, 0x2d, 0xf8, 0x5d, 0x3e, 0x94, 0xd3, 0x6f, 0x22, 0xf0, 0x6c, - 0xd9, 0x9b, 0x28, 0x77, 0x1b, 0x9e, 0xf6, 0xba, 0x85, 0x00, 0x2c, 0xc5, 0x33, 0x25, 0x58, 0x90, 0x53, 0xcc, 0xc5, - 0xff, 0x0b, 0x3e, 0x62, 0xbe, 0x27, 0x5d, 0xc4, 0xd6, 0xdb, 0x47, 0x17, 0x06, 0x12, 0x68, 0x3a, 0x00, 0x3f, 0x5e, - 0x05, 0x74, 0x65, 0xfc, 0x3b, 0x2d, 0xeb, 0xb1, 0x3e, 0xfe, 0x53, 0x70, 0x9f, 0x7e, 0xa2, 0xf0, 0xd1, 0xe1, 0xb8, - 0x4a, 0x47, 0x3b, 0x4a, 0x41, 0x74, 0x74, 0xfb, 0x7c, 0xaa, 0xb2, 0xef, 0x2a, 0x20, 0xb7, 0x1c, 0xb5, 0xa7, 0x02, - 0xb0, 0xd8, 0xd2, 0x11, 0xf8, 0x34, 0xcb, 0x27, 0xe4, 0x7b, 0x3d, 0x15, 0x57, 0x97, 0x3a, 0x5d, 0x3c, 0x1b, 0x4f, - 0xe1, 0x7f, 0x20, 0xf6, 0xb0, 0x4c, 0x91, 0x1d, 0xbb, 0x2e, 0x7e, 0x10, 0x6f, 0x6b, 0x3b, 0xfa, 0x63, 0x07, 0x91, - 0x8e, 0x7b, 0x72, 0xa1, 0xbe, 0x84, 0x54, 0x72, 0xa1, 0x6e, 0x20, 0x76, 0xa1, 0xc6, 0x3b, 0x2e, 0x62, 0xad, 0xbf, - 0xad, 0x51, 0xb0, 0x12, 0x70, 0xa6, 0xbd, 0x05, 0x83, 0x0d, 0xac, 0x5b, 0x96, 0xc1, 0xdf, 0x70, 0x4d, 0x13, 0xb8, - 0x61, 0x91, 0xf5, 0xde, 0x60, 0x2b, 0xbd, 0x05, 0x47, 0xcb, 0xc4, 0xb9, 0x94, 0x24, 0x65, 0x8b, 0x8c, 0xab, 0x47, - 0x21, 0x55, 0xd3, 0xfd, 0xad, 0xa8, 0xef, 0x85, 0xc8, 0x83, 0x55, 0xca, 0xa2, 0x62, 0x05, 0x32, 0x7b, 0xf0, 0xaf, - 0x90, 0x91, 0xa3, 0x1c, 0x38, 0x0a, 0xfd, 0xa3, 0x09, 0x74, 0x9e, 0x3a, 0xd2, 0x79, 0x24, 0xd8, 0x4a, 0x3d, 0x14, - 0x56, 0x5e, 0x40, 0x74, 0xb0, 0x1d, 0x73, 0x2b, 0x4f, 0x42, 0xc5, 0xa6, 0x4c, 0xe4, 0x71, 0x50, 0x4b, 0xc0, 0x58, - 0x41, 0x30, 0x67, 0xb9, 0x74, 0x41, 0xaa, 0x1a, 0x3d, 0x2c, 0x32, 0xf7, 0x63, 0x41, 0xf9, 0x1f, 0xab, 0x9c, 0x70, - 0x7d, 0x19, 0x02, 0x1c, 0xed, 0x63, 0x10, 0x25, 0xc6, 0xfa, 0x45, 0x8b, 0x77, 0x32, 0x73, 0x36, 0xb5, 0xbd, 0x04, - 0x19, 0xdb, 0xe1, 0x57, 0x08, 0xad, 0x16, 0x8a, 0x2c, 0x1a, 0x2e, 0x98, 0x6e, 0x4f, 0x69, 0xd5, 0x3d, 0x6c, 0x78, - 0x52, 0x7a, 0xa8, 0xd4, 0xb7, 0x31, 0x81, 0x65, 0x95, 0x32, 0x7c, 0x3b, 0xa1, 0xea, 0xc4, 0xa0, 0x62, 0xdd, 0xb0, - 0x05, 0x1c, 0x62, 0x31, 0x69, 0xac, 0xb3, 0x01, 0x8f, 0x58, 0x02, 0xff, 0x6c, 0xf8, 0x98, 0x2d, 0x78, 0x34, 0xd9, - 0x5c, 0x2d, 0xfa, 0xfd, 0xd2, 0x0b, 0xbd, 0x7a, 0x96, 0x3d, 0x8e, 0xe6, 0xb3, 0x7c, 0xee, 0xa3, 0xe2, 0x62, 0x32, - 0x18, 0x6c, 0xfc, 0x6c, 0x38, 0x64, 0xc9, 0x70, 0x38, 0xc9, 0x1e, 0xc3, 0x6b, 0x8f, 0x79, 0xa4, 0x96, 0x54, 0x72, - 0x95, 0xc1, 0xfe, 0x3e, 0xe0, 0x91, 0xcf, 0x3a, 0x3f, 0x2d, 0x9b, 0x2e, 0xdd, 0xcf, 0xec, 0xb8, 0x0b, 0xdd, 0x01, - 0x36, 0xde, 0x36, 0xe8, 0xc8, 0xbf, 0xdd, 0x21, 0xa5, 0x6e, 0x32, 0x00, 0xbb, 0xd1, 0x00, 0x87, 0x4c, 0xf5, 0x52, - 0x64, 0xf5, 0x52, 0xa6, 0x7a, 0x49, 0x56, 0x2e, 0xc1, 0x42, 0x62, 0xaa, 0xdc, 0x46, 0x56, 0x6e, 0xd1, 0x70, 0x3d, - 0x1c, 0x6c, 0xad, 0xb8, 0x6c, 0x96, 0x70, 0x5f, 0x58, 0x51, 0xe0, 0xff, 0x2d, 0xbb, 0x61, 0x77, 0xf2, 0x18, 0x78, - 0x8b, 0x8e, 0x49, 0x70, 0x81, 0xb8, 0x63, 0xb7, 0x60, 0x87, 0x85, 0xbf, 0xe0, 0x3a, 0x39, 0x66, 0x3b, 0x7c, 0x14, - 0x7a, 0x05, 0xbb, 0xf5, 0x09, 0x68, 0x17, 0x6c, 0x0d, 0x90, 0x8d, 0x6d, 0xf1, 0xd1, 0xf2, 0x70, 0x78, 0xeb, 0xf9, - 0xec, 0x1e, 0x7f, 0x9c, 0x2f, 0x0f, 0x87, 0x9d, 0x67, 0xd4, 0x7b, 0xd7, 0x3c, 0x61, 0xef, 0x79, 0x32, 0xb9, 0xbe, - 0xe2, 0xf1, 0x64, 0x30, 0xb8, 0xf6, 0x6f, 0x78, 0x3d, 0xbb, 0x06, 0xed, 0xc0, 0xf9, 0x8d, 0xd4, 0x35, 0x7b, 0xb7, - 0x3c, 0xf3, 0x6e, 0x70, 0x6c, 0x6e, 0xe1, 0xe8, 0xed, 0xf7, 0xbd, 0x25, 0x8f, 0xbc, 0x5b, 0x52, 0x31, 0xad, 0xb8, - 0xe2, 0x78, 0xdb, 0xe2, 0x7e, 0xba, 0xe2, 0x21, 0x3c, 0xc2, 0xaa, 0x4c, 0xaf, 0x83, 0xf7, 0x3e, 0x5b, 0x69, 0x16, - 0xb8, 0x7b, 0xcc, 0xb1, 0x26, 0x3b, 0xa1, 0x99, 0xf8, 0x2b, 0xec, 0x9f, 0x6b, 0xd5, 0x3f, 0x34, 0xff, 0x4b, 0xdd, - 0x4f, 0xe0, 0xf6, 0x45, 0x16, 0x24, 0xf6, 0x9e, 0x5f, 0xb3, 0x3b, 0x6e, 0xd8, 0x66, 0xcf, 0x4c, 0xd9, 0x27, 0x4a, - 0x8d, 0x1f, 0x28, 0x75, 0x6d, 0x19, 0x56, 0x5a, 0x57, 0x3e, 0x04, 0x0e, 0x07, 0xe4, 0xa7, 0x25, 0xe2, 0x20, 0xb4, - 0x6e, 0xb2, 0x9a, 0x2b, 0xca, 0xb9, 0xd0, 0x86, 0x99, 0x97, 0x03, 0x8b, 0x59, 0x4a, 0xa1, 0xb1, 0x00, 0x40, 0x30, - 0x29, 0xb4, 0xf6, 0x5e, 0x06, 0x90, 0x13, 0x34, 0xfc, 0xb1, 0xb9, 0x2a, 0xcb, 0x5a, 0xb6, 0x24, 0x44, 0xd9, 0xae, - 0x87, 0x97, 0x08, 0x99, 0xd6, 0xef, 0x9f, 0x13, 0xc9, 0xda, 0xa4, 0xba, 0xaa, 0xd1, 0x12, 0x50, 0x91, 0x25, 0x60, - 0xe2, 0x57, 0x9a, 0x4f, 0x00, 0x9e, 0x74, 0x3c, 0xa8, 0x1e, 0xf3, 0x9a, 0x09, 0x22, 0xdb, 0xa8, 0xfc, 0x49, 0xf1, - 0x0c, 0xc9, 0x08, 0x8a, 0xc7, 0xb5, 0xca, 0x58, 0x18, 0xe6, 0x81, 0x02, 0xf2, 0xee, 0xdd, 0xa9, 0x6f, 0xed, 0x8f, - 0x1d, 0x7b, 0xb6, 0x56, 0xa1, 0x16, 0x6a, 0x0a, 0x97, 0x1c, 0xa2, 0x2b, 0xd0, 0x40, 0x11, 0xc9, 0x78, 0xf2, 0x7a, - 0x70, 0x39, 0x89, 0xae, 0xb8, 0x40, 0x67, 0x7c, 0x7d, 0xd3, 0x4d, 0x67, 0xd1, 0xe3, 0x6a, 0x3e, 0x21, 0x25, 0xd9, - 0xe1, 0x90, 0x8d, 0xaa, 0xba, 0x58, 0x4f, 0x43, 0xf9, 0xd3, 0x43, 0xf0, 0xf5, 0x82, 0x7a, 0x4d, 0x56, 0xa9, 0x7e, - 0x4c, 0x95, 0xf2, 0xa2, 0xe1, 0xa5, 0xff, 0xb8, 0x92, 0xfb, 0x1e, 0x90, 0xd6, 0xf2, 0x92, 0xcb, 0xf7, 0x23, 0xc4, - 0x18, 0xf1, 0x03, 0xaf, 0xe4, 0x11, 0x0b, 0xd5, 0x14, 0xae, 0x79, 0x84, 0x20, 0x6f, 0x99, 0x0e, 0xfe, 0xd6, 0x13, - 0xa7, 0xfb, 0x13, 0xa5, 0x5d, 0x7c, 0x61, 0x51, 0xf7, 0x1c, 0xe9, 0x06, 0xe4, 0x60, 0xc3, 0x74, 0x51, 0x90, 0x6d, - 0x4a, 0x23, 0x68, 0xa3, 0xe5, 0xc0, 0x86, 0x53, 0xa9, 0x0d, 0x67, 0xae, 0x21, 0xb8, 0xcf, 0xcf, 0xd3, 0xd1, 0x0d, - 0x7c, 0x48, 0x75, 0x7b, 0x89, 0x9f, 0x0f, 0x1b, 0x8e, 0x64, 0x76, 0xc4, 0x67, 0x36, 0x91, 0x74, 0x52, 0xe7, 0x0a, - 0xd8, 0xed, 0xec, 0x25, 0xc8, 0x11, 0x33, 0xf7, 0x15, 0xaa, 0x6f, 0xd1, 0x80, 0x2b, 0x63, 0xed, 0x6b, 0x92, 0xb1, - 0xf0, 0xaa, 0x9c, 0x86, 0x03, 0x80, 0xa1, 0xcb, 0xe8, 0x6b, 0x8b, 0x4d, 0x96, 0xfd, 0x52, 0x40, 0x10, 0x44, 0x49, - 0x3c, 0x3e, 0xe0, 0x7d, 0x59, 0x0d, 0x35, 0x4a, 0x3e, 0x96, 0x9d, 0xc0, 0xd7, 0x4b, 0xf4, 0x77, 0x63, 0x2e, 0x31, - 0xe0, 0xcb, 0xaa, 0x2d, 0x28, 0x9c, 0xe7, 0x87, 0xc3, 0x79, 0x3e, 0x32, 0x9e, 0x65, 0xa0, 0x5a, 0x99, 0xd6, 0xc1, - 0xc6, 0xcc, 0x17, 0x0b, 0x7f, 0xb1, 0x73, 0x12, 0x11, 0x05, 0x81, 0x1d, 0x09, 0x0f, 0x22, 0xf5, 0xfb, 0xca, 0xd3, - 0x9d, 0xea, 0xb3, 0xfd, 0x8d, 0x4d, 0xa4, 0x17, 0x94, 0x4c, 0x3e, 0x09, 0xf6, 0xaa, 0xbf, 0x83, 0xb0, 0x21, 0xbc, - 0x79, 0xd5, 0xeb, 0x2c, 0x53, 0xb3, 0x12, 0x24, 0xcc, 0x98, 0x23, 0x78, 0x1c, 0x76, 0x1a, 0xdb, 0xf0, 0xd8, 0xc2, - 0x6a, 0xf4, 0xd6, 0x6c, 0xc9, 0x56, 0xec, 0x56, 0xd5, 0xe9, 0x86, 0x87, 0xd3, 0xe1, 0x65, 0x80, 0xab, 0x6f, 0x7d, - 0xce, 0xf9, 0x92, 0x4e, 0xb0, 0xf5, 0x80, 0x47, 0x13, 0x31, 0x5b, 0x3f, 0x8e, 0xd4, 0xe2, 0x59, 0x0f, 0xf9, 0x0d, - 0xad, 0x3f, 0x31, 0x5b, 0x9a, 0xe4, 0xe5, 0x80, 0xdf, 0x4c, 0xd6, 0x8f, 0x23, 0x78, 0xf5, 0x31, 0x58, 0x31, 0x32, - 0x67, 0x96, 0xad, 0x1f, 0x47, 0x38, 0x66, 0xcb, 0xc7, 0x11, 0x8d, 0xda, 0x4a, 0xee, 0x4b, 0xb7, 0x0d, 0x08, 0x2b, - 0xb7, 0x2c, 0x86, 0xd7, 0x40, 0x3c, 0xd3, 0x46, 0xd2, 0xb5, 0x34, 0xf4, 0xc6, 0x3c, 0x9c, 0xc6, 0xc1, 0x9a, 0x5a, - 0x21, 0xcf, 0x0c, 0x31, 0x8b, 0x1f, 0x47, 0x73, 0xb6, 0xc2, 0x8a, 0x6c, 0x78, 0x3c, 0xb8, 0x9c, 0x6c, 0xae, 0xf8, - 0x1a, 0xc8, 0xcf, 0x26, 0x1b, 0xb3, 0x45, 0xdd, 0x72, 0x31, 0xdb, 0x3c, 0x8e, 0xe6, 0x93, 0x15, 0xf4, 0xac, 0x3d, - 0x60, 0xde, 0x6b, 0x10, 0xa1, 0x24, 0xa4, 0xa6, 0xdc, 0xf4, 0x7a, 0x6c, 0x3d, 0x0e, 0x96, 0x6c, 0x7d, 0x19, 0xdc, - 0xb2, 0xf5, 0x18, 0x88, 0x38, 0xa8, 0xdf, 0xbd, 0x0d, 0x2c, 0xbe, 0x88, 0xad, 0x2f, 0x4d, 0xda, 0xe6, 0x71, 0xc4, - 0xdc, 0xc1, 0x69, 0xe0, 0x82, 0xb5, 0xc8, 0xbc, 0x15, 0x83, 0x4b, 0xc8, 0xc2, 0x8b, 0xd9, 0x66, 0x78, 0xc9, 0xd6, - 0x23, 0x9c, 0xea, 0x89, 0xcf, 0x96, 0xfc, 0x96, 0x25, 0x7c, 0xd5, 0xc4, 0x57, 0x1b, 0xd0, 0x88, 0x1e, 0x65, 0xd0, - 0x57, 0x50, 0x33, 0x73, 0xde, 0x5b, 0x18, 0x95, 0xfb, 0x16, 0x1c, 0x50, 0x90, 0xb6, 0x01, 0x82, 0x24, 0x9e, 0xdd, - 0xcb, 0x70, 0x7d, 0x2d, 0x85, 0x01, 0x37, 0x81, 0x19, 0x30, 0x30, 0xfd, 0x0c, 0x7e, 0x58, 0xe9, 0x12, 0x21, 0xce, - 0x7e, 0x4a, 0x49, 0x32, 0xcf, 0xdf, 0x8b, 0x34, 0x77, 0x0b, 0xd7, 0x29, 0xcc, 0x8a, 0x02, 0xd5, 0x4f, 0x49, 0x69, - 0x60, 0xa1, 0x12, 0x99, 0x4a, 0xc1, 0x2f, 0x9b, 0xf3, 0x28, 0x3b, 0x46, 0xe7, 0x3a, 0xbf, 0x9c, 0x38, 0xa7, 0x93, - 0xbe, 0xff, 0xc0, 0x31, 0x6c, 0x21, 0x03, 0x17, 0xfe, 0xd4, 0x13, 0xc6, 0xa9, 0x15, 0x88, 0xa9, 0xe4, 0xd9, 0x53, - 0xf8, 0x4c, 0x68, 0x75, 0x74, 0xe1, 0xfb, 0x41, 0xa1, 0x4d, 0xd2, 0x2d, 0x48, 0x52, 0xf0, 0x14, 0x3d, 0xe7, 0xbc, - 0x0d, 0x54, 0x8a, 0x11, 0x2d, 0x88, 0xb4, 0xb5, 0xce, 0x1c, 0xa4, 0x2d, 0xcd, 0x77, 0x4d, 0xfc, 0x1c, 0x16, 0x70, - 0x11, 0x2d, 0x6c, 0x0d, 0x8f, 0xaa, 0x58, 0xb9, 0x37, 0x79, 0x8e, 0x70, 0x46, 0x97, 0x32, 0x01, 0x70, 0xbd, 0x5f, - 0x85, 0xb5, 0xc2, 0x2b, 0x6a, 0x6e, 0xf2, 0xa2, 0xa6, 0x4f, 0xb6, 0xc0, 0x7d, 0x2c, 0x4a, 0x14, 0x38, 0x6b, 0xc1, - 0x80, 0xad, 0xb0, 0x64, 0x27, 0x85, 0x4d, 0xd1, 0x12, 0x7a, 0x7b, 0xfc, 0x74, 0x50, 0x33, 0x19, 0x40, 0x13, 0x40, - 0xe3, 0xf1, 0x2f, 0x00, 0x35, 0xbd, 0xae, 0xc5, 0xba, 0x0a, 0x4a, 0xa5, 0xdc, 0x84, 0x9f, 0x81, 0x61, 0x86, 0x1f, - 0x0a, 0xb9, 0x4d, 0x94, 0xc8, 0xf9, 0x71, 0x53, 0x8a, 0x45, 0x29, 0xaa, 0xa4, 0xdd, 0x50, 0xf0, 0x88, 0x70, 0x1b, - 0x34, 0x66, 0x6e, 0x4f, 0x74, 0xd1, 0x8a, 0x50, 0x8e, 0xcd, 0x3a, 0x46, 0x1a, 0x65, 0x76, 0xb2, 0xeb, 0x64, 0xa1, - 0xfd, 0xbe, 0xca, 0x21, 0xeb, 0x80, 0x35, 0x92, 0xaf, 0xd7, 0x1c, 0xba, 0x6d, 0x94, 0x17, 0xf7, 0x9e, 0xaf, 0xe0, - 0x34, 0xc7, 0x13, 0xbb, 0xeb, 0x75, 0xa7, 0x48, 0xc4, 0x2b, 0x9c, 0x54, 0xf9, 0x48, 0x16, 0x8e, 0x3b, 0x77, 0x5a, - 0x8b, 0x55, 0xe5, 0xb2, 0x9e, 0x5a, 0x1c, 0x11, 0xf8, 0x54, 0x1e, 0xed, 0x85, 0xb6, 0x45, 0xb1, 0x10, 0x46, 0x8f, - 0x4e, 0xf8, 0x49, 0x09, 0xac, 0xaf, 0xc3, 0x61, 0xe9, 0x47, 0x1c, 0xfd, 0x4e, 0xa3, 0xd1, 0x0d, 0x21, 0x0d, 0x4f, - 0xbd, 0x68, 0x74, 0x53, 0x17, 0x75, 0x98, 0x3d, 0xcb, 0xf5, 0x40, 0x61, 0x18, 0x81, 0xfa, 0xc1, 0x55, 0x06, 0x9f, - 0x45, 0x88, 0x9a, 0x07, 0xa6, 0xd9, 0x10, 0x8e, 0xba, 0xc0, 0x43, 0x2b, 0x68, 0x31, 0x33, 0x1f, 0x85, 0x18, 0x3e, - 0xa4, 0x8b, 0xf3, 0x27, 0x64, 0xe5, 0x03, 0xec, 0x0e, 0xdd, 0x85, 0x72, 0xce, 0x54, 0x0c, 0xf0, 0xa3, 0x80, 0x7c, - 0x94, 0x80, 0x9b, 0x01, 0xb2, 0x47, 0x96, 0x00, 0x62, 0xc5, 0xe8, 0x68, 0xf2, 0xb9, 0xef, 0x45, 0x0a, 0xde, 0xd9, - 0x67, 0xb9, 0x9a, 0x30, 0x14, 0x3e, 0x31, 0xd0, 0xcd, 0x6f, 0xfc, 0xf6, 0xbc, 0x05, 0x23, 0xbb, 0x24, 0xc5, 0x6b, - 0xcd, 0x70, 0xbf, 0x01, 0xb7, 0x23, 0xa0, 0xac, 0xa9, 0x8e, 0x49, 0xb6, 0x69, 0x88, 0x64, 0xc0, 0x8c, 0x18, 0x11, - 0x54, 0x96, 0x0b, 0xff, 0xbb, 0x97, 0x45, 0x81, 0x03, 0xb8, 0x9a, 0xc9, 0xe0, 0xb5, 0x0b, 0xa3, 0x02, 0xe0, 0x9c, - 0x86, 0x4e, 0x69, 0xaf, 0xaa, 0x0e, 0xc9, 0xaa, 0xf9, 0xc1, 0x6c, 0xde, 0x34, 0x4c, 0x8c, 0x08, 0xa2, 0x8b, 0x70, - 0x82, 0xe9, 0x15, 0xe9, 0x6b, 0x25, 0xa7, 0xa3, 0x55, 0x47, 0x6b, 0x89, 0x89, 0xb9, 0xa2, 0xf8, 0x6b, 0xc0, 0xe3, - 0x06, 0xaf, 0x4e, 0xd2, 0x74, 0xa2, 0x7a, 0xf4, 0xf8, 0x75, 0x9a, 0x4e, 0x4a, 0xdc, 0x15, 0x7e, 0x03, 0x2e, 0x9a, - 0x6d, 0x3e, 0xf4, 0xe3, 0x17, 0x14, 0x71, 0x51, 0x83, 0x2b, 0xef, 0x54, 0x5f, 0xa9, 0x3e, 0x82, 0x5a, 0x78, 0x62, - 0x64, 0x2d, 0x3c, 0xb9, 0x64, 0xad, 0x05, 0xc1, 0xcc, 0xe6, 0xc0, 0x85, 0xfc, 0x4a, 0x29, 0xe2, 0x4d, 0x24, 0xd4, - 0x62, 0xd0, 0x7a, 0xcc, 0x9c, 0x55, 0xa3, 0x1b, 0x95, 0x19, 0xa1, 0x7d, 0x5b, 0x8b, 0xce, 0x6f, 0xe4, 0xa7, 0x3c, - 0xb5, 0x2f, 0xdb, 0xe3, 0x7c, 0xbc, 0x47, 0x77, 0xd5, 0x59, 0x66, 0x52, 0xc6, 0x27, 0xb3, 0x04, 0x85, 0xbb, 0x04, - 0x1b, 0x90, 0x64, 0xbf, 0xd5, 0x01, 0x32, 0x6a, 0xaf, 0xfd, 0xae, 0xb3, 0x7c, 0x75, 0xb3, 0x35, 0x14, 0x95, 0x5a, - 0x49, 0x8a, 0x83, 0x0c, 0xd7, 0x6d, 0xe5, 0xc3, 0xc5, 0x05, 0xf4, 0x8c, 0x91, 0xc8, 0x3c, 0x7f, 0x22, 0x5f, 0x82, - 0x73, 0xc6, 0x59, 0x21, 0x30, 0x61, 0xac, 0xde, 0xb5, 0x96, 0x4a, 0x43, 0x8a, 0xb1, 0xa3, 0x51, 0x96, 0x55, 0x96, - 0x2e, 0xb3, 0xb5, 0x84, 0x2d, 0xab, 0xc8, 0x2d, 0x6c, 0x9d, 0xc9, 0x6a, 0x7e, 0xa8, 0xb8, 0x83, 0xf2, 0xcd, 0x96, - 0x19, 0xdf, 0x4b, 0x64, 0xef, 0x36, 0x50, 0xc2, 0xb3, 0xd1, 0x7f, 0x20, 0xfd, 0x36, 0xc3, 0x38, 0xe5, 0xb6, 0x92, - 0x16, 0xe0, 0xf4, 0x0f, 0x87, 0x0f, 0x15, 0x06, 0x0d, 0x8e, 0x30, 0x8e, 0xac, 0xdf, 0xbf, 0xa9, 0xbc, 0x1a, 0x13, - 0x75, 0x7c, 0x56, 0xbf, 0x5f, 0xd1, 0xc3, 0x69, 0x35, 0x5a, 0xa5, 0x5b, 0x64, 0x27, 0xb4, 0xb1, 0xf2, 0x83, 0x5a, - 0x01, 0xb3, 0xb7, 0x3e, 0x9f, 0x0e, 0x40, 0xc7, 0x02, 0x24, 0x9a, 0xcd, 0x44, 0x62, 0x4e, 0xba, 0x27, 0xe1, 0xf1, - 0x81, 0x05, 0x0e, 0x30, 0x15, 0xff, 0xa7, 0xf0, 0x66, 0x60, 0x83, 0x46, 0x89, 0xbe, 0x46, 0x57, 0xb5, 0xb9, 0xd1, - 0xf1, 0xd2, 0x53, 0x48, 0x64, 0x05, 0xab, 0xe6, 0xbe, 0xdc, 0xc0, 0x69, 0x0f, 0x35, 0x87, 0xca, 0x02, 0xfc, 0xed, - 0x17, 0x60, 0xf0, 0xc8, 0xa0, 0xb0, 0xdd, 0x5a, 0x68, 0x6f, 0xcc, 0x52, 0x0d, 0x15, 0xe1, 0xa0, 0xf3, 0x95, 0x98, - 0xd5, 0x23, 0xfa, 0x7b, 0x7e, 0x38, 0xac, 0x08, 0x0c, 0x38, 0x2c, 0x65, 0x26, 0x5a, 0x28, 0x96, 0xd6, 0xd9, 0x8c, - 0xea, 0xc0, 0x03, 0x13, 0x73, 0x16, 0xee, 0x00, 0xb4, 0x49, 0xad, 0x02, 0xbd, 0x8a, 0xe8, 0x27, 0xee, 0xd7, 0xf6, - 0xeb, 0xf5, 0xc8, 0x2c, 0x1d, 0xb9, 0x31, 0x16, 0x00, 0x1c, 0x78, 0x5e, 0x93, 0x3c, 0x27, 0x5f, 0x43, 0xbb, 0x27, - 0x17, 0xf2, 0x27, 0x28, 0x5b, 0x78, 0xae, 0x9a, 0x56, 0x16, 0x2b, 0xae, 0xaa, 0x57, 0x17, 0xbc, 0x32, 0x99, 0x56, - 0x69, 0x25, 0x2a, 0x25, 0x18, 0x50, 0x97, 0x78, 0xad, 0x69, 0x46, 0xa9, 0x8d, 0x3a, 0x13, 0x35, 0x60, 0x83, 0xfd, - 0x54, 0x6d, 0x74, 0x72, 0x2e, 0x9f, 0x5f, 0x1a, 0x87, 0x4f, 0xbb, 0x7a, 0x33, 0x53, 0x39, 0xf0, 0xd7, 0xca, 0x87, - 0x56, 0x8f, 0x81, 0x0e, 0xc8, 0xe9, 0x8f, 0x61, 0x31, 0xb1, 0x3b, 0x34, 0x6f, 0x77, 0x97, 0xd5, 0x45, 0x7a, 0xa7, - 0x29, 0x99, 0xd5, 0x5b, 0x3e, 0xb3, 0x7a, 0x74, 0xc0, 0x8b, 0x87, 0x7a, 0xaf, 0x30, 0x93, 0x08, 0x2e, 0x86, 0x6a, - 0x12, 0xd9, 0x1d, 0x68, 0xcd, 0xa3, 0x8a, 0x09, 0xf0, 0x83, 0x52, 0x6b, 0x7a, 0x6f, 0x77, 0x85, 0x3a, 0xa5, 0xf0, - 0xb8, 0xb5, 0xe4, 0x07, 0xe6, 0x4e, 0xbb, 0xd6, 0xf9, 0x78, 0x7e, 0xe9, 0xfb, 0x8d, 0x3c, 0xa1, 0xcd, 0xce, 0xe4, - 0xf4, 0x4f, 0xde, 0xea, 0x1f, 0xa6, 0xfa, 0x16, 0xba, 0x13, 0xf4, 0x19, 0xba, 0xaa, 0xba, 0x2b, 0xb1, 0x85, 0xa1, - 0x9e, 0x58, 0xe4, 0x85, 0x3c, 0x69, 0x8d, 0x1d, 0x07, 0x7b, 0x03, 0x9c, 0xf8, 0xe5, 0xe1, 0x20, 0xae, 0x72, 0x9f, - 0x9d, 0x77, 0x8d, 0xac, 0x1c, 0xc0, 0x0a, 0xa2, 0x60, 0xdc, 0x9a, 0x8f, 0x6d, 0x90, 0x2e, 0x71, 0x35, 0x3e, 0x7e, - 0x43, 0xb1, 0x4c, 0x36, 0x11, 0x17, 0x17, 0xf9, 0xe3, 0xa7, 0x40, 0x5a, 0xd6, 0xef, 0x47, 0xcf, 0x2e, 0xa7, 0x4f, - 0x87, 0x51, 0x00, 0x8e, 0x5d, 0xf6, 0xf2, 0x32, 0xe6, 0xab, 0x4b, 0x66, 0x99, 0xc2, 0x22, 0xdf, 0x0c, 0xa8, 0x2e, - 0x59, 0x2d, 0x5d, 0xaf, 0x00, 0x4b, 0x97, 0xdf, 0xdc, 0x87, 0xa9, 0x01, 0x8d, 0xac, 0xb9, 0x3b, 0xcd, 0xb5, 0x40, - 0xa9, 0xe7, 0xfd, 0xcc, 0x90, 0xaf, 0xcb, 0xa0, 0x2b, 0x48, 0xf7, 0x3c, 0x22, 0xbd, 0xdc, 0x4b, 0xa7, 0xfb, 0x7d, - 0x29, 0xc0, 0x52, 0x5f, 0x8a, 0x2f, 0xa0, 0xb0, 0x68, 0x7c, 0x23, 0x40, 0x5b, 0x43, 0x35, 0xed, 0x95, 0xa2, 0xea, - 0x05, 0xbd, 0x52, 0x7c, 0xe9, 0xe9, 0xa1, 0x32, 0x5f, 0x96, 0x8e, 0xfe, 0x27, 0xd4, 0x5c, 0x70, 0x42, 0xcc, 0xc4, - 0x1c, 0x40, 0x25, 0x68, 0xe3, 0xbb, 0x3d, 0xda, 0xf8, 0x54, 0xaf, 0xe2, 0xa6, 0xcf, 0x6b, 0x6b, 0x99, 0x13, 0xc2, - 0xa6, 0x7b, 0x09, 0x50, 0x91, 0x57, 0xc2, 0x23, 0x58, 0x7e, 0xf9, 0x43, 0x9e, 0xae, 0x10, 0xad, 0xe3, 0x9e, 0x65, - 0x2e, 0x8d, 0xfd, 0x6b, 0x83, 0xe9, 0xeb, 0xdb, 0x6d, 0x91, 0x9f, 0x9a, 0x98, 0xb0, 0x1e, 0x2b, 0xfa, 0xe6, 0x5d, - 0xb8, 0x12, 0x28, 0x70, 0x28, 0x91, 0xd8, 0xa6, 0x0a, 0x45, 0x3c, 0x48, 0xfa, 0x74, 0xd1, 0xfa, 0x34, 0xc0, 0xd4, - 0x5a, 0x0e, 0xcc, 0x21, 0x5c, 0xc5, 0x85, 0x8f, 0x9e, 0xbe, 0xc5, 0x2c, 0x9c, 0x4f, 0xbc, 0x8f, 0x5e, 0x31, 0x32, - 0x1f, 0xf7, 0x51, 0xa9, 0xa4, 0x7f, 0x1e, 0x0e, 0xb3, 0x6a, 0xee, 0x3b, 0xf4, 0x91, 0x1e, 0xaa, 0x5c, 0x50, 0xf6, - 0xc6, 0x98, 0x44, 0xa0, 0x34, 0xc6, 0xfb, 0x38, 0x38, 0xce, 0xfb, 0x34, 0x80, 0xd4, 0x3e, 0xf1, 0x9e, 0x94, 0x1c, - 0x9e, 0x73, 0xcc, 0x09, 0xa5, 0x15, 0x01, 0x13, 0x7a, 0x86, 0x72, 0xdd, 0x29, 0x05, 0x93, 0x1c, 0x12, 0x0c, 0x7f, - 0xd5, 0xbc, 0x89, 0x15, 0x08, 0xbb, 0x66, 0x5e, 0x8d, 0x1e, 0x55, 0x49, 0x58, 0x0a, 0x38, 0x2a, 0x33, 0xcf, 0xb0, - 0x37, 0x3c, 0x32, 0x8c, 0x1c, 0x2c, 0xf7, 0x47, 0x75, 0x22, 0x72, 0x8f, 0x2e, 0x30, 0x2a, 0x0b, 0xcf, 0x1b, 0xba, - 0xd2, 0xa0, 0x92, 0xec, 0xf8, 0x2b, 0xae, 0x01, 0xb5, 0x35, 0x46, 0x0c, 0x05, 0x8c, 0x82, 0xd7, 0xf6, 0x87, 0x90, - 0x45, 0xd9, 0xfa, 0x0d, 0x8e, 0xf9, 0xac, 0xe4, 0xae, 0x77, 0x38, 0x0b, 0x2d, 0x21, 0x4f, 0xee, 0x18, 0xa4, 0x69, - 0x2c, 0x8d, 0x80, 0x13, 0x91, 0x6c, 0x63, 0x29, 0x1c, 0x01, 0x04, 0x04, 0xba, 0x29, 0x33, 0x8c, 0xe9, 0x60, 0xe4, - 0x79, 0xd4, 0x33, 0xde, 0xab, 0xf0, 0x14, 0xd2, 0x64, 0xfb, 0x7a, 0xfe, 0xde, 0x08, 0xb2, 0x72, 0xcb, 0x39, 0x1e, - 0x16, 0xdf, 0x38, 0xfb, 0x2a, 0x27, 0x4f, 0x31, 0xcb, 0x48, 0xef, 0x14, 0xf3, 0x02, 0xfe, 0x54, 0x96, 0xfa, 0x1c, - 0xa5, 0xb7, 0xcc, 0x27, 0xab, 0x48, 0xba, 0xf0, 0x36, 0xfd, 0x7e, 0x3c, 0x52, 0x87, 0x9a, 0xbf, 0x8f, 0x47, 0xf2, - 0x0c, 0xdb, 0xb0, 0x84, 0x85, 0x56, 0xc1, 0x18, 0x40, 0x12, 0x1b, 0x11, 0x0d, 0x46, 0x7b, 0x73, 0x38, 0x9c, 0x6f, - 0xcc, 0x59, 0xb2, 0x07, 0xd7, 0x57, 0x9e, 0x98, 0x77, 0xe0, 0xcb, 0x3c, 0x26, 0x88, 0xd8, 0xcc, 0xdb, 0xb0, 0x1a, - 0x3c, 0xd8, 0xc1, 0xf5, 0x11, 0x5b, 0x14, 0x6b, 0x1d, 0x4b, 0x65, 0x1d, 0x9c, 0xd6, 0xb1, 0x69, 0x46, 0x4a, 0x91, - 0x7d, 0x8e, 0xfd, 0xbd, 0x1b, 0x5c, 0x5d, 0x1b, 0x83, 0x5a, 0xe3, 0x0e, 0x73, 0xe7, 0x54, 0x40, 0x3d, 0xa6, 0x2b, - 0xa8, 0x9e, 0x55, 0xe4, 0xcb, 0x6f, 0xed, 0x1c, 0x10, 0x34, 0x02, 0x81, 0x8b, 0x06, 0x4a, 0xa6, 0x4b, 0x39, 0xef, - 0x02, 0x42, 0x7c, 0x97, 0x82, 0x3e, 0x9d, 0xc1, 0x26, 0x36, 0x9f, 0x40, 0x2c, 0x9a, 0xee, 0x73, 0xad, 0x99, 0x2f, - 0x46, 0xb4, 0x33, 0xeb, 0x6e, 0x91, 0x5b, 0x2d, 0x44, 0x32, 0x7a, 0xb6, 0x99, 0x70, 0xd7, 0xa1, 0x9c, 0x91, 0x80, - 0x09, 0x5a, 0x5b, 0x29, 0xf9, 0x5c, 0xf7, 0x3a, 0x41, 0x7b, 0x20, 0x69, 0xdd, 0xbf, 0x59, 0x74, 0x46, 0xc9, 0xc9, - 0xf5, 0x26, 0x67, 0x90, 0x82, 0x05, 0xdb, 0xcb, 0x9c, 0x70, 0x03, 0x7c, 0x64, 0xb3, 0xe4, 0x34, 0x0d, 0xf2, 0x58, - 0x18, 0xa4, 0x8f, 0x36, 0xbf, 0x2c, 0xa0, 0x43, 0xc9, 0xa2, 0x11, 0xe2, 0x01, 0x76, 0x0e, 0xc9, 0x55, 0x81, 0xba, - 0x69, 0xa0, 0x2b, 0x57, 0xce, 0x14, 0x53, 0xe0, 0x42, 0x28, 0x88, 0xda, 0xd1, 0x49, 0x54, 0xce, 0xfb, 0xa4, 0xba, - 0xcc, 0xa7, 0x85, 0x34, 0x0d, 0xe4, 0xd3, 0xca, 0x31, 0x0f, 0x6c, 0x6d, 0xe3, 0x9a, 0xc0, 0x40, 0xa7, 0xf6, 0xb5, - 0x28, 0xe7, 0x58, 0x45, 0xf4, 0x3e, 0x7f, 0x54, 0xd9, 0xd3, 0x07, 0x11, 0x36, 0x2a, 0xd0, 0x58, 0x4a, 0x8c, 0x8d, - 0x1c, 0xff, 0x96, 0x28, 0x1b, 0x32, 0x04, 0x84, 0x90, 0x36, 0x72, 0xfa, 0x61, 0x7d, 0xf9, 0x2e, 0xd3, 0xfe, 0x9f, - 0x24, 0x7e, 0x1b, 0xec, 0xe5, 0xd4, 0x9f, 0x7a, 0xc4, 0xe3, 0xb5, 0x46, 0x8f, 0x29, 0xe9, 0x36, 0xc8, 0x53, 0xe5, - 0x29, 0x48, 0x26, 0x8c, 0x05, 0x04, 0x8b, 0x72, 0xc1, 0x73, 0x5e, 0x71, 0x09, 0xf7, 0x51, 0xcb, 0x8a, 0x08, 0x55, - 0x89, 0x9c, 0x3e, 0x5f, 0x01, 0xcf, 0x04, 0x04, 0x3a, 0xc6, 0x48, 0xa3, 0x0a, 0xbe, 0x04, 0xc6, 0x3a, 0x50, 0x76, - 0x9a, 0x91, 0xe0, 0xb2, 0x7b, 0x8d, 0x44, 0xa9, 0xaf, 0x48, 0x49, 0xfa, 0x56, 0xd4, 0x78, 0x25, 0x56, 0x11, 0x09, - 0x64, 0xa8, 0x21, 0x62, 0x55, 0x3d, 0x75, 0xaf, 0x8a, 0xc9, 0x60, 0x50, 0xf9, 0x72, 0x7a, 0xe2, 0x0d, 0x0d, 0x95, - 0x77, 0x5d, 0xd1, 0x4e, 0xcf, 0xb5, 0x52, 0xde, 0x42, 0x5a, 0x82, 0xa6, 0x61, 0xa4, 0x39, 0x94, 0xba, 0x92, 0xee, - 0xc6, 0x20, 0xbe, 0x64, 0xa2, 0x67, 0x3b, 0xb5, 0xa3, 0xb4, 0x25, 0xed, 0x21, 0xa4, 0xe7, 0x2e, 0xf9, 0x98, 0x85, - 0x5c, 0xdd, 0x29, 0x27, 0xe5, 0x55, 0x88, 0x4e, 0xee, 0x7b, 0x0c, 0x89, 0x40, 0x9f, 0x73, 0x0c, 0xeb, 0xa2, 0xa1, - 0xce, 0x61, 0x85, 0x98, 0x2d, 0x94, 0x30, 0x5f, 0x32, 0x9e, 0x4a, 0x06, 0x0d, 0x80, 0x0c, 0xf8, 0xe2, 0x65, 0x60, - 0xf9, 0x2b, 0x88, 0x1f, 0x6d, 0x7c, 0x38, 0xfc, 0x55, 0x53, 0x88, 0xed, 0x5f, 0xb0, 0x19, 0xc2, 0xa3, 0x7a, 0xc0, - 0x33, 0xdf, 0xc4, 0x09, 0x5a, 0x01, 0x49, 0x99, 0x1d, 0x4d, 0x64, 0xaf, 0x7a, 0x08, 0xa7, 0xb2, 0x02, 0x75, 0x94, - 0x75, 0x56, 0xc2, 0x8f, 0x30, 0xd5, 0xad, 0xc4, 0x5a, 0xa0, 0xcd, 0xd5, 0x8a, 0xb5, 0x00, 0x0e, 0xfc, 0x1c, 0x82, - 0x27, 0xf2, 0x39, 0xb8, 0x18, 0x14, 0xe0, 0x73, 0x00, 0xbc, 0xc8, 0x5d, 0x78, 0x30, 0x0f, 0x2c, 0xab, 0x11, 0x86, - 0xa3, 0x8a, 0x58, 0xbf, 0x66, 0x3b, 0xf2, 0x81, 0xdb, 0x31, 0x3e, 0xd7, 0x1e, 0x4b, 0x96, 0x83, 0x51, 0xe6, 0x5e, - 0x2d, 0xd1, 0xf3, 0x26, 0x8d, 0x9b, 0xd1, 0xa3, 0x7d, 0x2d, 0xff, 0x17, 0xf4, 0x32, 0xe8, 0x6f, 0xe1, 0x96, 0xd7, - 0xfc, 0x61, 0xb9, 0x70, 0x9a, 0x5e, 0x41, 0xa4, 0x8c, 0x1a, 0x91, 0x31, 0x84, 0x4d, 0xaa, 0x9b, 0xdb, 0xa4, 0xba, - 0x10, 0xf0, 0x74, 0x44, 0xaa, 0x6b, 0x21, 0x6d, 0xe4, 0xd3, 0x3a, 0x90, 0xb1, 0x48, 0xef, 0x7e, 0xfc, 0xdb, 0xf3, - 0x4f, 0x6f, 0x7e, 0xfb, 0xf1, 0xe6, 0xcd, 0xbb, 0xd7, 0x6f, 0xde, 0xbd, 0xf9, 0xf4, 0x3b, 0x41, 0x78, 0x4c, 0x85, - 0xca, 0xf0, 0xe1, 0xfd, 0xf5, 0x1b, 0x27, 0x83, 0xed, 0xcd, 0x90, 0xb5, 0x6f, 0xe4, 0x60, 0x08, 0x44, 0x36, 0x08, - 0x19, 0x64, 0xa7, 0x64, 0x8e, 0x99, 0x98, 0x63, 0xec, 0x9d, 0xc0, 0x64, 0x0b, 0x92, 0xc3, 0x32, 0x2f, 0x19, 0x91, - 0xab, 0x42, 0xeb, 0x07, 0xb4, 0xe0, 0x2d, 0xb8, 0xc8, 0xa4, 0xf9, 0xf2, 0x37, 0x82, 0xd8, 0xa7, 0x95, 0x94, 0xfb, - 0x6a, 0x5b, 0xf3, 0x7c, 0x7b, 0xbf, 0x97, 0x70, 0xfe, 0x73, 0x69, 0x44, 0x2d, 0xc0, 0x01, 0xf8, 0x1c, 0xfe, 0xb8, - 0xd2, 0x96, 0x34, 0x99, 0x45, 0xfb, 0x19, 0x43, 0xd0, 0xa5, 0x81, 0x34, 0xb1, 0x47, 0x5e, 0xea, 0x93, 0x85, 0x04, - 0xee, 0x88, 0xe1, 0xd3, 0x8a, 0xa0, 0x57, 0x8c, 0x28, 0x2e, 0xb9, 0x42, 0xa5, 0x94, 0xfc, 0x1b, 0x65, 0x17, 0x15, - 0x72, 0x56, 0xb0, 0x3b, 0x45, 0x8e, 0x8c, 0x1f, 0x04, 0x13, 0x5f, 0x0e, 0xee, 0xbf, 0xc4, 0x3b, 0x9c, 0x29, 0x8e, - 0xe4, 0x84, 0xff, 0x99, 0x61, 0x60, 0x7f, 0x0e, 0x3e, 0xaf, 0x0e, 0xf3, 0xf2, 0x46, 0x9f, 0x72, 0x0b, 0x3e, 0x9e, - 0x2c, 0xae, 0xc0, 0x60, 0xbf, 0x50, 0xcd, 0x5d, 0xf3, 0x7a, 0xb6, 0x98, 0xb3, 0xfd, 0x2c, 0x9a, 0x07, 0x4b, 0x36, - 0xcb, 0xe6, 0xc1, 0xaa, 0xe1, 0x6b, 0x76, 0xcb, 0xd7, 0x56, 0xd5, 0xd6, 0x76, 0xd5, 0x26, 0x1b, 0x7e, 0x0b, 0x12, - 0xc2, 0xdb, 0xcc, 0x03, 0xde, 0xe3, 0xa5, 0xcf, 0x36, 0x20, 0xd1, 0xae, 0xd8, 0x06, 0x2e, 0x62, 0x6b, 0xfe, 0xa6, - 0xf2, 0x36, 0xac, 0x64, 0xe7, 0x63, 0x96, 0xe3, 0xfc, 0xf3, 0xe1, 0x01, 0xed, 0x85, 0xfa, 0xd9, 0xa5, 0x7a, 0x36, - 0x51, 0x76, 0xb3, 0xcd, 0xe8, 0xe6, 0x2e, 0xad, 0x36, 0x61, 0x86, 0x9e, 0xe5, 0xf0, 0xd1, 0x56, 0x0a, 0x7e, 0xfa, - 0x06, 0xbf, 0x64, 0x4d, 0x9c, 0x7f, 0xa6, 0x6d, 0xbb, 0x2a, 0xb1, 0x15, 0xb4, 0x28, 0xb2, 0x5a, 0xe1, 0x81, 0x39, - 0x7f, 0x06, 0x0b, 0x18, 0x7b, 0x8e, 0x73, 0x5e, 0xfb, 0x23, 0x64, 0xbc, 0x77, 0x00, 0xd0, 0x32, 0xc7, 0x01, 0x1e, - 0xb1, 0x62, 0x14, 0x0d, 0xde, 0xf9, 0xa5, 0xb2, 0x5a, 0x69, 0x4e, 0x42, 0xdb, 0x88, 0x55, 0xcb, 0x91, 0xaa, 0x19, - 0x91, 0x3e, 0x48, 0xcf, 0xfb, 0x1e, 0x51, 0x0d, 0xf6, 0x64, 0x5e, 0x07, 0xf6, 0xe9, 0x7d, 0x6b, 0x55, 0x77, 0x7e, - 0x4f, 0x95, 0x2e, 0x39, 0xb2, 0xe5, 0xa7, 0xcb, 0xf0, 0x5e, 0xfd, 0x29, 0xb9, 0x3e, 0x14, 0x38, 0xc2, 0x43, 0x15, - 0x70, 0xbe, 0x5e, 0x89, 0x76, 0x27, 0xc2, 0xae, 0x5c, 0x02, 0x42, 0x7c, 0x49, 0xd3, 0x1c, 0x8f, 0x23, 0x9a, 0x88, - 0xb0, 0x89, 0xd1, 0x5f, 0xd8, 0x7d, 0x28, 0xb1, 0x9c, 0xe7, 0x1a, 0x94, 0x5c, 0x32, 0x78, 0x4f, 0xda, 0x6b, 0xd0, - 0x2c, 0xaf, 0x4a, 0x4d, 0x26, 0x72, 0x50, 0x3e, 0x1c, 0x0a, 0xd8, 0x4b, 0x8d, 0x9f, 0x26, 0xfc, 0x84, 0xe5, 0xad, - 0xbd, 0x35, 0xa5, 0xa8, 0xa4, 0x01, 0x2a, 0xf0, 0x31, 0x83, 0xff, 0xdd, 0x19, 0x62, 0xc1, 0x14, 0x1d, 0x3f, 0x9c, - 0x89, 0xb9, 0xf5, 0xdc, 0x2a, 0xeb, 0x28, 0x5b, 0xa3, 0x9c, 0x80, 0x7f, 0x4f, 0x75, 0x9c, 0x24, 0xc2, 0xa9, 0xf7, - 0x88, 0x8b, 0xba, 0x97, 0x43, 0xd4, 0x0d, 0xfb, 0x54, 0xe9, 0x60, 0xcb, 0x69, 0x1a, 0x1c, 0x89, 0x5f, 0xa9, 0xcf, - 0x3e, 0x64, 0x16, 0x8f, 0x3a, 0xb2, 0x11, 0x25, 0x69, 0x1c, 0x8b, 0x1c, 0xb6, 0xf7, 0x1b, 0xb9, 0xff, 0xf7, 0xfb, - 0x10, 0x4e, 0x5a, 0x05, 0x71, 0xe9, 0x09, 0x44, 0x84, 0xa3, 0xc3, 0x8f, 0x08, 0x4f, 0xa4, 0xaa, 0xf0, 0x51, 0x7d, - 0xe2, 0xc6, 0xec, 0x5e, 0x98, 0xa3, 0x7a, 0x0b, 0x30, 0x8c, 0xf5, 0xd6, 0x22, 0x24, 0xd1, 0x4a, 0x33, 0xda, 0x7a, - 0x40, 0x8c, 0x78, 0xbf, 0xb6, 0xc8, 0x60, 0xac, 0x2d, 0x89, 0x04, 0xf0, 0x25, 0x09, 0x19, 0xda, 0x36, 0x02, 0x33, - 0x86, 0xb7, 0xb3, 0xe2, 0xd2, 0x75, 0xd8, 0xe6, 0x1c, 0xbe, 0x90, 0x1b, 0xcd, 0x3a, 0xa2, 0x34, 0x41, 0xc8, 0x3f, - 0xe0, 0x64, 0xa1, 0x30, 0x9a, 0x57, 0x47, 0xe9, 0x24, 0xb1, 0xbe, 0xef, 0x2a, 0x15, 0x6c, 0x36, 0xd7, 0xa8, 0x2f, - 0x3b, 0x4a, 0x7e, 0x09, 0x4e, 0x3a, 0x4e, 0xb2, 0xc8, 0x41, 0xd4, 0xa2, 0x72, 0xae, 0x93, 0xb0, 0xb4, 0xab, 0x53, - 0x6d, 0xd6, 0xeb, 0xa2, 0xac, 0xab, 0x57, 0x22, 0x52, 0xf4, 0x3e, 0xea, 0xd1, 0x23, 0x09, 0xa9, 0xd0, 0xaa, 0xd4, - 0x2e, 0x8f, 0xc0, 0x6d, 0x53, 0x2b, 0xb6, 0xe5, 0x12, 0x96, 0xa8, 0xf1, 0x9f, 0xa0, 0x8f, 0x72, 0x71, 0x2f, 0x03, - 0x34, 0x3a, 0x9e, 0x9a, 0xb7, 0x1e, 0x78, 0xe5, 0x28, 0xbf, 0xb4, 0xda, 0xa4, 0x5f, 0x01, 0x99, 0xd1, 0xfe, 0xd1, - 0x52, 0x02, 0x99, 0x81, 0x99, 0xb4, 0x34, 0x24, 0x72, 0x14, 0xb3, 0x34, 0xff, 0x13, 0x57, 0x6c, 0x85, 0x48, 0xc3, - 0x6a, 0xee, 0xf1, 0x1f, 0x2b, 0xaf, 0x96, 0x6b, 0x99, 0x69, 0x6e, 0x96, 0x38, 0x56, 0x2c, 0x2e, 0xea, 0x75, 0x25, - 0xb2, 0x40, 0x88, 0x23, 0x4c, 0x63, 0x3d, 0xf5, 0x46, 0x69, 0xf5, 0x01, 0x09, 0x65, 0x7e, 0xc4, 0xde, 0x8e, 0xbd, - 0x1e, 0x64, 0x21, 0x8e, 0x2d, 0x07, 0x9b, 0xad, 0xf7, 0xa9, 0x4c, 0x45, 0x7c, 0x56, 0x17, 0x67, 0x9b, 0x4a, 0x9c, - 0xd5, 0x89, 0x38, 0xfb, 0x01, 0x72, 0xfe, 0x70, 0x46, 0x45, 0x9f, 0xdd, 0xa7, 0x75, 0x52, 0x6c, 0x6a, 0x7a, 0xf2, - 0x1a, 0xcb, 0xf8, 0xe1, 0x8c, 0xb8, 0x6a, 0xce, 0x68, 0x24, 0xe3, 0xd1, 0xd9, 0x87, 0x0c, 0x48, 0x5e, 0xcf, 0xd2, - 0x15, 0x0c, 0xde, 0x59, 0x98, 0xc7, 0x67, 0xa5, 0x58, 0x82, 0xc5, 0xa9, 0xec, 0x7c, 0x0f, 0x32, 0xac, 0xc2, 0x3f, - 0xc5, 0x19, 0x40, 0xbb, 0x9e, 0xa5, 0xf5, 0x59, 0x5a, 0x9d, 0xe5, 0x45, 0x7d, 0xa6, 0xa4, 0x70, 0x08, 0xe3, 0x87, - 0xf7, 0xf4, 0x95, 0x5d, 0xde, 0x66, 0x71, 0x97, 0x45, 0xfe, 0x14, 0xbd, 0x8a, 0x88, 0x49, 0xa3, 0x12, 0x5e, 0xbb, - 0xbf, 0x6d, 0xee, 0x1f, 0x5e, 0x37, 0x76, 0x3f, 0xbb, 0x63, 0x44, 0x17, 0xd4, 0xe3, 0x95, 0xa4, 0x54, 0x50, 0x40, - 0xe0, 0x44, 0xb3, 0xc6, 0x83, 0x3b, 0x0e, 0x78, 0x35, 0xb0, 0x05, 0x5b, 0xfb, 0xfc, 0x59, 0x2c, 0xc3, 0xb4, 0x37, - 0x01, 0xfe, 0x55, 0xf6, 0xa6, 0xeb, 0x60, 0x81, 0xf7, 0x2d, 0x64, 0x1b, 0x7a, 0xf3, 0x8a, 0x3f, 0xf7, 0x72, 0xf5, - 0x37, 0xfb, 0x27, 0x00, 0x61, 0x40, 0xcc, 0xaa, 0x8f, 0x26, 0xee, 0x9d, 0x95, 0x65, 0xe7, 0x64, 0xd9, 0xf5, 0xd0, - 0xaf, 0x49, 0x8c, 0x4a, 0x2b, 0x4b, 0xe9, 0x64, 0x29, 0x21, 0x0b, 0xf8, 0xc4, 0x68, 0x6a, 0x23, 0x80, 0xb0, 0x1d, - 0xa5, 0xf2, 0x85, 0xca, 0x8b, 0x28, 0x9c, 0x13, 0x3c, 0x4f, 0xc4, 0xe8, 0xce, 0x4a, 0x06, 0x0c, 0x87, 0x10, 0xcc, - 0x41, 0x5b, 0xec, 0x0d, 0xdd, 0x44, 0xfc, 0xf5, 0xba, 0x28, 0xdf, 0xc4, 0xe4, 0x53, 0xb0, 0x3b, 0xf9, 0xb8, 0x84, - 0xc7, 0xe5, 0xc9, 0xc7, 0x21, 0x7a, 0x24, 0x9c, 0x7c, 0x0c, 0xbe, 0x47, 0x72, 0x5e, 0x77, 0x3d, 0x4e, 0x90, 0x5b, - 0x48, 0xf7, 0xb7, 0x63, 0x12, 0xa0, 0x79, 0x0d, 0xcb, 0x51, 0x53, 0x71, 0xcd, 0xcc, 0x18, 0xcf, 0x1b, 0xbd, 0x3f, - 0x76, 0xbc, 0x65, 0x0a, 0xc5, 0x2c, 0xe6, 0x35, 0xfc, 0x9e, 0x55, 0x81, 0xba, 0xeb, 0x6d, 0x92, 0x5b, 0x66, 0xf5, - 0x1c, 0xed, 0xbe, 0xef, 0xeb, 0x44, 0x50, 0xfb, 0x3b, 0xec, 0x79, 0x66, 0xbd, 0xab, 0x62, 0xe0, 0x52, 0x25, 0x3b, - 0x64, 0xaa, 0x9a, 0x1e, 0xa8, 0x94, 0x06, 0x4f, 0x2f, 0xad, 0xcb, 0x97, 0x4a, 0x1b, 0x79, 0xa6, 0xf9, 0x0d, 0xe0, - 0xc5, 0xd4, 0x65, 0xb1, 0xfb, 0xe6, 0xbe, 0x82, 0xdb, 0x78, 0xbf, 0xbf, 0xae, 0x3c, 0xf3, 0x13, 0x17, 0x80, 0xbd, - 0xa9, 0xd0, 0x3a, 0x81, 0x52, 0xc3, 0x3a, 0x7c, 0x99, 0x88, 0xe8, 0xcf, 0x76, 0xb9, 0xce, 0x5c, 0x07, 0x8c, 0x28, - 0xe2, 0xb7, 0xf1, 0xe8, 0x0f, 0x50, 0x5c, 0x1b, 0x7b, 0x40, 0x58, 0x87, 0x84, 0x3e, 0x23, 0x00, 0xa9, 0x47, 0x1f, - 0x25, 0xf7, 0xa0, 0x59, 0xd1, 0xdc, 0x31, 0xf9, 0xb9, 0xbe, 0x52, 0xfa, 0xfb, 0x75, 0xe5, 0x91, 0x39, 0xa5, 0x6d, - 0xa6, 0xb1, 0x5a, 0x53, 0x09, 0x84, 0x57, 0x54, 0xb2, 0x0a, 0x9f, 0xcd, 0x1b, 0xd1, 0xef, 0xcb, 0x23, 0x3c, 0xad, - 0x7e, 0xdc, 0x62, 0x7c, 0x2b, 0x20, 0x1a, 0x09, 0x50, 0xb0, 0x02, 0xcc, 0x8b, 0x6c, 0x66, 0xf7, 0x71, 0x40, 0x95, - 0x12, 0x4d, 0xe3, 0x6c, 0x9e, 0xdf, 0xd3, 0x9b, 0xb2, 0x83, 0x4e, 0x9d, 0x2a, 0x70, 0xc1, 0x55, 0xc9, 0x78, 0x65, - 0x3d, 0x91, 0xcf, 0x6f, 0x6e, 0x37, 0x69, 0x16, 0xbf, 0x2f, 0x7f, 0xc5, 0xb1, 0xd5, 0x75, 0x78, 0x60, 0xea, 0x74, - 0xed, 0x3c, 0xd2, 0xda, 0x0b, 0x01, 0x11, 0xed, 0x1a, 0x6a, 0xbd, 0xb0, 0xd0, 0x23, 0x3d, 0x11, 0xce, 0x49, 0xa2, - 0xa6, 0x1d, 0x68, 0x69, 0x84, 0xbe, 0xbe, 0xe6, 0xf4, 0x17, 0x06, 0x6b, 0x9f, 0x8f, 0x19, 0x90, 0x95, 0xe8, 0xc7, - 0xea, 0xa1, 0xb1, 0x99, 0x43, 0xcf, 0x5a, 0x95, 0x67, 0x5e, 0x75, 0x38, 0x20, 0x3e, 0x8c, 0xfe, 0x92, 0xdf, 0xef, - 0xbf, 0xa2, 0xf9, 0xc7, 0x84, 0x1a, 0x3f, 0xdb, 0x0c, 0xd0, 0xb5, 0xef, 0xca, 0x03, 0x51, 0xcf, 0xb5, 0x4a, 0x10, - 0xe2, 0x0d, 0x62, 0xa2, 0x19, 0x31, 0x07, 0xa7, 0x1d, 0x6a, 0xfe, 0x49, 0x6a, 0x40, 0x88, 0x12, 0xaf, 0x63, 0xca, - 0x82, 0x9c, 0x36, 0x71, 0xa4, 0x1f, 0x85, 0x13, 0xf9, 0x51, 0x54, 0x45, 0x76, 0x07, 0x17, 0x0c, 0xa6, 0xde, 0xd3, - 0x7e, 0x89, 0x7e, 0x4b, 0x38, 0x72, 0x8e, 0x56, 0x85, 0x20, 0x72, 0x42, 0x58, 0x6b, 0x08, 0x13, 0xc4, 0x06, 0xf1, - 0xb2, 0xef, 0x92, 0x0c, 0x47, 0x0a, 0x2e, 0xeb, 0xd8, 0x31, 0xe6, 0xea, 0xa8, 0x7a, 0x0d, 0x60, 0xbc, 0x72, 0x04, - 0xcd, 0x46, 0x91, 0x5d, 0x42, 0x54, 0x91, 0xe3, 0x09, 0xa8, 0x1d, 0x94, 0xc6, 0x66, 0x7a, 0x3e, 0x0e, 0xf2, 0xd1, - 0x4d, 0x85, 0x3a, 0x27, 0x96, 0xf1, 0x1a, 0x80, 0xb5, 0x73, 0xd5, 0xcf, 0xb3, 0x1a, 0x3c, 0x69, 0x88, 0xcf, 0xc7, - 0x68, 0x7b, 0x65, 0x73, 0x50, 0x6d, 0xa7, 0xb3, 0xf2, 0x8a, 0xe9, 0x72, 0x60, 0xdc, 0x37, 0xbc, 0xa2, 0x38, 0xc3, - 0x8f, 0x1e, 0x6c, 0x71, 0xfe, 0x74, 0x43, 0xed, 0xc7, 0xdc, 0xa8, 0x87, 0x81, 0xd6, 0x82, 0x37, 0x05, 0xb1, 0xfe, - 0x7e, 0xe8, 0xc8, 0xf6, 0x5e, 0x8b, 0x8c, 0x26, 0x9f, 0xfd, 0xfc, 0x43, 0x99, 0xae, 0x52, 0xb8, 0x2f, 0x39, 0x59, - 0x34, 0xf3, 0x10, 0xd8, 0x1b, 0x62, 0xb8, 0x3e, 0x2a, 0x3c, 0xa2, 0xac, 0xdf, 0x87, 0xdf, 0x57, 0x19, 0x98, 0x62, - 0xe0, 0xba, 0x42, 0x30, 0x1e, 0x02, 0x41, 0x3c, 0x4c, 0xa3, 0x93, 0x41, 0x0d, 0xda, 0xf0, 0x0d, 0x40, 0x66, 0x80, - 0x47, 0xe6, 0xc2, 0x23, 0xe0, 0x2e, 0x70, 0xed, 0xc9, 0x78, 0xec, 0x4f, 0x4c, 0x43, 0xa3, 0xa6, 0x34, 0xd3, 0x73, - 0xe3, 0x37, 0x1d, 0xd5, 0x72, 0xed, 0xfc, 0xc7, 0x97, 0xfc, 0x06, 0xbd, 0xa0, 0xe5, 0xe5, 0x3e, 0x52, 0x97, 0xfb, - 0x8c, 0xe2, 0x32, 0x91, 0x1c, 0x16, 0xc4, 0xb2, 0x84, 0x03, 0x8f, 0x51, 0xc9, 0x62, 0x4b, 0x8f, 0x55, 0xd1, 0xf2, - 0x45, 0xb9, 0x41, 0x3a, 0x74, 0x42, 0xb0, 0x44, 0x05, 0xc1, 0x12, 0x18, 0x17, 0xb1, 0xe6, 0x9b, 0x41, 0xce, 0xe2, - 0xd9, 0x66, 0xce, 0x91, 0xb0, 0x2e, 0x39, 0x1c, 0x0a, 0x09, 0x36, 0x93, 0xcd, 0xd6, 0x73, 0xb6, 0xf6, 0x19, 0x28, - 0x01, 0x4a, 0x99, 0x26, 0x28, 0x4d, 0x2b, 0xb6, 0xe2, 0xa6, 0x35, 0x58, 0xad, 0xa6, 0x6c, 0x55, 0x53, 0x76, 0x4e, - 0x53, 0x8e, 0x2a, 0x28, 0x39, 0xa1, 0x14, 0x65, 0x18, 0xc0, 0x88, 0x4d, 0xa2, 0xab, 0x0c, 0x7d, 0xbc, 0x13, 0x1e, - 0x41, 0x15, 0x11, 0xf9, 0x84, 0x21, 0x04, 0x26, 0xa2, 0xb8, 0x50, 0x85, 0x62, 0x80, 0x8c, 0x48, 0x20, 0x98, 0xa8, - 0xd4, 0x29, 0x30, 0x1f, 0x4d, 0x15, 0xc3, 0xa6, 0x3d, 0x51, 0xbe, 0xa7, 0x8e, 0x7b, 0x94, 0x6d, 0x7e, 0x16, 0xbb, - 0x20, 0x44, 0xee, 0xc6, 0x9d, 0xfa, 0x19, 0xf1, 0xde, 0xee, 0x08, 0xe3, 0x27, 0x3b, 0x6e, 0x11, 0xae, 0x08, 0xb6, - 0x50, 0x73, 0x88, 0xc5, 0xbc, 0x9a, 0x24, 0xa8, 0x65, 0x49, 0xfc, 0x0d, 0x4f, 0x06, 0x39, 0x5b, 0x80, 0x07, 0xed, - 0x9c, 0x65, 0x80, 0xbf, 0x62, 0xb5, 0xe8, 0xf7, 0xda, 0x5b, 0x80, 0xfc, 0xb4, 0xb1, 0x1b, 0x85, 0x89, 0x11, 0x24, - 0xea, 0x76, 0x65, 0x20, 0x3f, 0x7c, 0xc0, 0xe9, 0x78, 0xec, 0x29, 0x63, 0x6e, 0x65, 0x7a, 0x99, 0xce, 0x95, 0x7c, - 0x23, 0xf7, 0xd2, 0x87, 0x5e, 0x82, 0x9d, 0x03, 0xde, 0x40, 0xda, 0xc0, 0x6b, 0xd8, 0x2e, 0xbc, 0x36, 0x48, 0x98, - 0x11, 0x60, 0x8b, 0xe3, 0x63, 0xa4, 0x04, 0x86, 0x70, 0x9c, 0xa5, 0x00, 0x4c, 0xa3, 0x2f, 0xb3, 0x95, 0x7d, 0x99, - 0xd5, 0x9a, 0x2d, 0x95, 0xd3, 0xbd, 0x73, 0xeb, 0x76, 0x3e, 0x97, 0x00, 0x60, 0x52, 0xe7, 0x40, 0x9c, 0x99, 0x60, - 0x97, 0x26, 0x91, 0xe5, 0x63, 0x98, 0x2f, 0xc5, 0xeb, 0xb2, 0x58, 0xa9, 0xae, 0x68, 0xfb, 0xcc, 0xe4, 0x33, 0xd2, - 0x49, 0xa8, 0x80, 0x82, 0x42, 0xae, 0xf5, 0xe9, 0xbb, 0xf0, 0x5d, 0x50, 0x68, 0x60, 0xb6, 0x0a, 0xf7, 0x34, 0x59, - 0x23, 0xf5, 0x46, 0xd5, 0xef, 0x93, 0x6b, 0x20, 0xd5, 0x99, 0x43, 0xcb, 0x9e, 0x57, 0x18, 0x20, 0x76, 0xd4, 0x67, - 0x24, 0xd4, 0x81, 0xd4, 0x03, 0x86, 0x10, 0x6d, 0xd3, 0xc7, 0x9f, 0x0c, 0x89, 0x2e, 0xc0, 0x16, 0xa2, 0x0d, 0xfc, - 0xf8, 0x13, 0xec, 0xb3, 0x20, 0x3c, 0xa6, 0xf9, 0x5b, 0x48, 0x3a, 0x36, 0x70, 0x5a, 0x7d, 0x0a, 0x3e, 0x48, 0x72, - 0x30, 0x51, 0x07, 0x2f, 0xf7, 0x97, 0x7e, 0x1f, 0xb6, 0xec, 0x5c, 0x4a, 0x75, 0xac, 0xd4, 0xdb, 0xb6, 0xf6, 0x83, - 0x68, 0x0b, 0x8e, 0x10, 0xac, 0x9d, 0x21, 0x22, 0x98, 0x19, 0x44, 0xd8, 0xb5, 0x50, 0x77, 0x7b, 0x4a, 0x2d, 0x8b, - 0x7a, 0xdb, 0x53, 0x4a, 0xdd, 0x86, 0xe1, 0xbb, 0x09, 0x66, 0x8a, 0x1b, 0x7e, 0x9d, 0x79, 0xa1, 0xde, 0x78, 0x2c, - 0x9e, 0x76, 0xcf, 0xdf, 0x2f, 0x78, 0x35, 0xdb, 0x28, 0x13, 0xe6, 0x92, 0x2f, 0x66, 0xa1, 0xec, 0x6a, 0x69, 0xdc, - 0xf9, 0xe2, 0x2d, 0xd4, 0x7c, 0xf0, 0x0f, 0x87, 0x04, 0xe2, 0x8d, 0xe2, 0xab, 0x65, 0x23, 0xb7, 0xae, 0xc9, 0xe6, - 0xaa, 0x04, 0xd4, 0xef, 0xf3, 0x35, 0xee, 0xb7, 0x58, 0xff, 0xee, 0x69, 0x90, 0xb1, 0x9a, 0xe1, 0x8a, 0x29, 0x7c, - 0x0a, 0x00, 0x83, 0xc3, 0xa9, 0x20, 0x2d, 0xf0, 0x86, 0x97, 0xc3, 0xcb, 0xc9, 0x86, 0x4c, 0xba, 0x1b, 0x1f, 0xb9, - 0xb3, 0x40, 0xd5, 0xfb, 0x1d, 0xc5, 0x49, 0x83, 0x44, 0x63, 0xaf, 0xc1, 0xe7, 0x59, 0x46, 0xb9, 0x68, 0xe2, 0x3e, - 0x24, 0x5f, 0xe9, 0x01, 0xcc, 0x55, 0x28, 0x01, 0xa2, 0xdf, 0x58, 0x16, 0x1b, 0xd1, 0xb6, 0xd8, 0xc0, 0x52, 0xaa, - 0xe6, 0x7a, 0x35, 0x7d, 0xf1, 0x4a, 0x34, 0xef, 0xa3, 0x19, 0xa7, 0x34, 0x1a, 0x70, 0x9c, 0x46, 0xe1, 0xf6, 0xfd, - 0x9d, 0x28, 0x17, 0x19, 0x58, 0xb2, 0x55, 0x38, 0xc5, 0x65, 0xa3, 0xce, 0x88, 0xe7, 0x79, 0xac, 0x00, 0x3a, 0x1e, - 0x12, 0x00, 0xd5, 0x05, 0x01, 0x15, 0xd1, 0x52, 0x7a, 0x2b, 0xb4, 0x58, 0xa8, 0x37, 0x1c, 0xa5, 0xf0, 0x47, 0xfa, - 0xf3, 0x20, 0x9f, 0x02, 0x10, 0xbb, 0x3e, 0x8e, 0x5e, 0x17, 0x25, 0x7d, 0xaa, 0x98, 0xe5, 0x72, 0x30, 0x81, 0x5d, - 0x9d, 0xc8, 0x50, 0x2b, 0xc8, 0x5b, 0x75, 0xe5, 0xad, 0x4c, 0xde, 0xc6, 0x38, 0x25, 0x3f, 0x70, 0xd3, 0xb1, 0x46, - 0x0c, 0xbc, 0xf2, 0xb4, 0x4e, 0x13, 0xa4, 0xc9, 0x1b, 0x60, 0x18, 0xe2, 0x77, 0x99, 0xf7, 0xdc, 0x73, 0xa4, 0x2a, - 0x48, 0x66, 0xdb, 0xcc, 0x53, 0x17, 0x51, 0x7d, 0xe5, 0xd4, 0xd2, 0x99, 0xd3, 0x8f, 0x00, 0xde, 0x63, 0x6a, 0xd2, - 0x90, 0x8f, 0x70, 0x5b, 0x8a, 0xaf, 0xb7, 0xea, 0x1a, 0x2f, 0x8d, 0xce, 0xdd, 0xcb, 0x97, 0xee, 0x34, 0xe8, 0xa7, - 0x20, 0x28, 0xe7, 0xf3, 0x52, 0xc0, 0x9e, 0x32, 0x9b, 0xeb, 0xd5, 0xaa, 0x15, 0x5a, 0x87, 0xc3, 0x58, 0x3b, 0x0a, - 0x69, 0x75, 0x16, 0xb0, 0xd5, 0x48, 0xa7, 0x04, 0x08, 0xc1, 0x71, 0x1a, 0x76, 0x82, 0x71, 0x97, 0x4e, 0x23, 0xb2, - 0x5e, 0x29, 0x49, 0x17, 0x66, 0x90, 0xfc, 0x93, 0xbc, 0x9e, 0x01, 0x2d, 0x01, 0x1c, 0x8a, 0x58, 0xc2, 0xc3, 0x49, - 0x72, 0x05, 0xd0, 0xe9, 0x70, 0x50, 0x69, 0x68, 0xce, 0x6a, 0x96, 0xcc, 0x27, 0xb1, 0x54, 0x55, 0x1e, 0x0e, 0x9e, - 0x72, 0x33, 0xe8, 0xf7, 0xb3, 0x69, 0xa9, 0x5c, 0x00, 0x82, 0x58, 0x17, 0x06, 0x88, 0x47, 0x5a, 0x78, 0xb2, 0xe8, - 0x53, 0x12, 0xbf, 0x9c, 0x25, 0x73, 0x93, 0x0d, 0xef, 0xc0, 0x08, 0x36, 0xe3, 0xba, 0xa4, 0x4c, 0x7b, 0x54, 0x7e, - 0xcf, 0xe8, 0xa9, 0xed, 0x6b, 0xad, 0xb6, 0x88, 0x75, 0x1d, 0x5c, 0x95, 0xa8, 0xa7, 0xf8, 0xa0, 0x24, 0xc1, 0xfb, - 0x95, 0x73, 0x33, 0x52, 0xbe, 0x16, 0xb9, 0x1f, 0xb4, 0x33, 0xb5, 0x72, 0xe0, 0x08, 0xe4, 0x58, 0x45, 0x25, 0xaf, - 0x77, 0x1d, 0x82, 0x47, 0x77, 0xa5, 0x02, 0xe5, 0xe0, 0x67, 0x20, 0x46, 0xd7, 0x57, 0x9d, 0x35, 0xd4, 0x4c, 0xa3, - 0xca, 0x23, 0xe8, 0xd4, 0x01, 0x3c, 0x29, 0x78, 0xa9, 0xd5, 0x8f, 0x87, 0x83, 0x67, 0x7e, 0xf0, 0xf7, 0x99, 0xbe, - 0x85, 0x98, 0x28, 0xa7, 0x1a, 0x21, 0x71, 0xa5, 0x24, 0x11, 0x1f, 0x2f, 0x5a, 0x56, 0x8c, 0xca, 0xf0, 0x9e, 0x57, - 0xaa, 0x7c, 0x75, 0xaa, 0xf2, 0x62, 0xa4, 0x6d, 0x09, 0xbc, 0x26, 0xff, 0x10, 0xb9, 0xe6, 0xad, 0xaf, 0xbb, 0xca, - 0xd0, 0x97, 0xb2, 0x02, 0x1d, 0xc1, 0x56, 0x96, 0x92, 0x03, 0x3e, 0xa9, 0xee, 0xaa, 0x55, 0xeb, 0x73, 0xca, 0x36, - 0xc2, 0x4d, 0x7e, 0x1d, 0x3b, 0x38, 0x52, 0x7e, 0x83, 0xe7, 0x02, 0xd8, 0x6b, 0xc0, 0xde, 0x9c, 0xb3, 0xa2, 0x79, - 0x70, 0x48, 0xdb, 0x02, 0x8d, 0xcc, 0xdc, 0xce, 0xd5, 0x7d, 0x5b, 0x1e, 0xa5, 0x31, 0x44, 0xa6, 0x3d, 0x30, 0x1d, - 0x6c, 0x46, 0xf9, 0xef, 0x29, 0xbf, 0x55, 0x38, 0x06, 0xbe, 0x9d, 0x7a, 0x07, 0x50, 0xf5, 0xb4, 0x41, 0xc6, 0x9a, - 0x61, 0x68, 0x65, 0x97, 0x4b, 0xa1, 0x25, 0x68, 0xa9, 0x9b, 0x20, 0x38, 0x3f, 0x22, 0xca, 0x11, 0x80, 0x2e, 0x52, - 0xc0, 0x04, 0x3f, 0xa5, 0xed, 0xee, 0xf7, 0xd7, 0xa9, 0x47, 0xee, 0x5d, 0xa1, 0xb2, 0x59, 0x7e, 0x22, 0x18, 0xfb, - 0x89, 0xc6, 0x0c, 0x3a, 0xba, 0x22, 0x27, 0x3c, 0x6b, 0x75, 0x58, 0xd7, 0x4d, 0x19, 0x94, 0xc5, 0x31, 0xaf, 0xa6, - 0xb3, 0x3f, 0x1e, 0xed, 0xeb, 0x06, 0x59, 0xc8, 0xff, 0x60, 0x3d, 0x24, 0x83, 0xee, 0x41, 0x28, 0x44, 0x6f, 0x1e, - 0xcc, 0xf0, 0x3f, 0xb6, 0xe1, 0xd9, 0x77, 0xdc, 0xa8, 0x13, 0xc0, 0x1c, 0x71, 0xbd, 0xf4, 0x14, 0x6d, 0x3d, 0xdc, - 0x02, 0xd9, 0x1a, 0x2f, 0x6f, 0xed, 0x35, 0x90, 0x53, 0x1c, 0xff, 0x92, 0x67, 0x6a, 0x65, 0x83, 0x9f, 0x9e, 0xb2, - 0x1d, 0x78, 0x78, 0x11, 0x02, 0x8a, 0x61, 0xd9, 0xf8, 0xa5, 0xe5, 0x38, 0xa3, 0xff, 0xe6, 0x11, 0xc3, 0x60, 0x11, - 0xf9, 0xf1, 0x45, 0x29, 0xc4, 0x57, 0xe1, 0x7d, 0xaa, 0xbc, 0x25, 0x39, 0x65, 0x2e, 0xf5, 0x30, 0xba, 0x2e, 0x49, - 0xdf, 0x25, 0x1f, 0x5b, 0xc3, 0xf6, 0x87, 0x76, 0xbf, 0x19, 0x22, 0x08, 0xa1, 0x1c, 0x3f, 0x67, 0x74, 0x42, 0xe3, - 0xc3, 0x6a, 0x76, 0x7a, 0xfd, 0xde, 0x39, 0x5e, 0xb0, 0x35, 0x1a, 0xe0, 0xf1, 0xd0, 0xc5, 0x3c, 0x51, 0x43, 0xa7, - 0xeb, 0xda, 0x39, 0x78, 0x60, 0x90, 0xe5, 0xc9, 0x77, 0x0c, 0x4b, 0xec, 0x4f, 0x22, 0x9e, 0xb4, 0x55, 0x1b, 0x9b, - 0x23, 0xd5, 0x46, 0xcd, 0xc0, 0x0f, 0x5e, 0x41, 0x81, 0xd1, 0x05, 0xe9, 0x16, 0x8c, 0xc3, 0x11, 0x80, 0xac, 0x18, - 0xc7, 0x23, 0x83, 0x09, 0x0c, 0xe9, 0x86, 0xa2, 0x00, 0x3c, 0x3c, 0x8e, 0x07, 0x21, 0x03, 0x48, 0x17, 0x3c, 0x34, - 0x6c, 0x93, 0x90, 0xf2, 0xf3, 0x3c, 0xaf, 0xd5, 0x10, 0xfa, 0xce, 0x42, 0x75, 0xec, 0x47, 0xda, 0x2b, 0xd6, 0xb5, - 0x2a, 0x1d, 0xd9, 0xea, 0x00, 0x7d, 0x43, 0x06, 0xbe, 0x75, 0x6c, 0x01, 0x10, 0x2d, 0xf1, 0x7b, 0xea, 0xd5, 0xbe, - 0x8c, 0x59, 0xa1, 0x5e, 0xbf, 0x31, 0xed, 0x7a, 0x25, 0x2d, 0x0a, 0xa8, 0xb8, 0x6d, 0xd5, 0xf6, 0x48, 0xce, 0x7f, - 0x78, 0xd7, 0xd1, 0x8e, 0xcf, 0x4e, 0x8d, 0x2d, 0xa1, 0xcc, 0x2d, 0x9e, 0xc8, 0xea, 0x68, 0x4b, 0x75, 0xaa, 0x0f, - 0xb8, 0xd4, 0xa4, 0x3a, 0x33, 0x30, 0xbc, 0x46, 0x80, 0x72, 0x0b, 0x91, 0x34, 0x0e, 0x7b, 0xe7, 0x93, 0x41, 0xc1, - 0xdc, 0x22, 0x01, 0x09, 0x6c, 0x63, 0x6b, 0x17, 0xcd, 0xf5, 0xeb, 0xf7, 0xd4, 0xab, 0xda, 0x54, 0xf5, 0xe0, 0x8d, - 0x17, 0x38, 0x7b, 0xa7, 0xb5, 0x80, 0x00, 0x0a, 0x5b, 0xcb, 0x72, 0x70, 0xee, 0x76, 0x55, 0x4b, 0x45, 0x19, 0xf5, - 0xfb, 0xe7, 0xbf, 0xa7, 0xa8, 0x88, 0x3d, 0x55, 0x9c, 0xb2, 0x7e, 0xbb, 0x65, 0xde, 0x54, 0x96, 0xbc, 0x41, 0x15, - 0xad, 0xd5, 0x51, 0x53, 0xb9, 0x6e, 0xae, 0x5a, 0x32, 0x41, 0x8c, 0xee, 0xd3, 0xb5, 0xce, 0x9d, 0x7a, 0xef, 0x55, - 0x1c, 0x31, 0x10, 0xdc, 0x74, 0x8f, 0x0f, 0x0e, 0x42, 0xa3, 0xa2, 0x5c, 0x70, 0xa3, 0xb4, 0xaa, 0xa4, 0x14, 0xf2, - 0x56, 0x45, 0x73, 0xa6, 0x8f, 0x00, 0x88, 0x00, 0xab, 0x44, 0xfd, 0x6f, 0xbe, 0x34, 0xc6, 0x83, 0x07, 0xbe, 0x26, - 0xd7, 0xb1, 0xf5, 0xfe, 0x69, 0x8d, 0xb4, 0xda, 0x38, 0x26, 0xb5, 0xea, 0x65, 0xab, 0x78, 0xd9, 0xbd, 0x4e, 0xc5, - 0xe0, 0xf9, 0xff, 0xdc, 0x07, 0xa8, 0x11, 0x2d, 0x65, 0x70, 0xeb, 0x6a, 0x80, 0xc6, 0x87, 0x63, 0xe1, 0x1b, 0x3f, - 0x64, 0x9c, 0x0f, 0x66, 0xe8, 0xa8, 0x36, 0x07, 0x07, 0x04, 0x47, 0x75, 0x8f, 0xc6, 0x84, 0x59, 0x38, 0xf7, 0x20, - 0x50, 0x7d, 0xe2, 0x3e, 0xe3, 0xda, 0x0b, 0xda, 0x04, 0x3e, 0x59, 0xd7, 0x35, 0x45, 0x80, 0x8b, 0xd8, 0x98, 0x88, - 0x21, 0x2e, 0x9b, 0x44, 0xea, 0x9b, 0x31, 0x28, 0x00, 0x8a, 0x67, 0x15, 0xc9, 0xa5, 0x37, 0x69, 0x5e, 0x89, 0xb2, - 0xd6, 0xcd, 0xa8, 0x58, 0x31, 0x04, 0x80, 0x87, 0xa0, 0xb8, 0xaa, 0xcc, 0x84, 0x46, 0x6c, 0x20, 0x95, 0xa5, 0x60, - 0xd5, 0xb0, 0xf0, 0x9b, 0xf6, 0x9b, 0xe4, 0xa4, 0x77, 0x3e, 0x6e, 0x9d, 0x3b, 0xf6, 0xbd, 0xa3, 0x90, 0xd2, 0x1e, - 0x8a, 0x09, 0x82, 0xe0, 0xa7, 0x75, 0x38, 0x7f, 0xc6, 0x9f, 0x11, 0x98, 0x8a, 0x6c, 0xc6, 0x80, 0x83, 0x10, 0x91, - 0x19, 0xbf, 0xe7, 0xf0, 0x19, 0x2f, 0x27, 0xe1, 0x70, 0xe8, 0x83, 0x3e, 0x94, 0x67, 0xb3, 0x70, 0x28, 0xe6, 0xd2, - 0x7b, 0x1d, 0xac, 0x75, 0x21, 0xaf, 0x27, 0x21, 0xa2, 0x85, 0x86, 0x3e, 0x38, 0xaf, 0xbb, 0xe6, 0x08, 0x4b, 0x00, - 0x9a, 0x38, 0xfa, 0xb2, 0x7e, 0x3f, 0xf2, 0xb4, 0xa1, 0x45, 0x8a, 0x8b, 0x46, 0x99, 0xcd, 0x72, 0xd9, 0x09, 0x1b, - 0xd7, 0x6e, 0x81, 0x50, 0x3c, 0x4c, 0x5b, 0xa8, 0x5a, 0x4f, 0xf5, 0x7a, 0x6e, 0xda, 0x7d, 0xf7, 0xa0, 0x5a, 0xe5, - 0x48, 0x67, 0x6d, 0xba, 0x52, 0xab, 0x5b, 0x46, 0xd5, 0x3a, 0x4b, 0x23, 0xaa, 0xdc, 0x24, 0x77, 0x8d, 0x5a, 0xf0, - 0xc9, 0x86, 0x2e, 0x53, 0x76, 0xb6, 0x06, 0x27, 0x8e, 0x3c, 0x97, 0xdc, 0xf2, 0xdd, 0x79, 0x45, 0x77, 0xa7, 0xda, - 0xb7, 0x00, 0xf7, 0x66, 0xd8, 0x90, 0x39, 0xaf, 0xb1, 0xd3, 0x20, 0x4c, 0x02, 0x3f, 0x62, 0x1f, 0x33, 0x64, 0x83, - 0x01, 0x1d, 0x85, 0xf4, 0xbf, 0xb6, 0xcc, 0x91, 0x80, 0xc9, 0x5f, 0xcf, 0xfd, 0xe6, 0xa6, 0xc8, 0x61, 0x31, 0x7e, - 0xd8, 0x60, 0xa4, 0xb1, 0x5a, 0x83, 0x61, 0xb9, 0x44, 0xe4, 0x4f, 0xed, 0x8e, 0x69, 0xaa, 0xe3, 0xcd, 0x7a, 0xad, - 0xf9, 0xd5, 0xd3, 0xa7, 0xba, 0x3e, 0xff, 0xed, 0xfb, 0xcb, 0xb0, 0x66, 0xf6, 0x87, 0x20, 0x94, 0x76, 0xef, 0x16, - 0xe7, 0x8e, 0x44, 0xef, 0x58, 0x69, 0x66, 0x97, 0x76, 0xc9, 0x2e, 0x4d, 0x69, 0xd7, 0xe4, 0x7a, 0xf5, 0x8d, 0xf2, - 0xc6, 0xce, 0x2b, 0xa6, 0xfb, 0xf7, 0x42, 0xef, 0x28, 0xa7, 0x6a, 0x02, 0x11, 0x4d, 0xda, 0x91, 0xb8, 0xdd, 0x2b, - 0xc3, 0xa7, 0x93, 0xbc, 0x5d, 0xc2, 0x51, 0xd7, 0xb0, 0xdc, 0x7c, 0xfb, 0xd7, 0xbc, 0xea, 0xac, 0x70, 0xfb, 0xa5, - 0x31, 0x6b, 0x7f, 0x0a, 0xe2, 0xaa, 0xfe, 0xf4, 0x1e, 0xd5, 0x4c, 0xc9, 0xff, 0x55, 0x8f, 0x81, 0xab, 0x9f, 0x4c, - 0x3b, 0xba, 0xa7, 0x10, 0x36, 0x98, 0xfd, 0xfc, 0xf8, 0xa1, 0x45, 0xd7, 0xe8, 0x02, 0x45, 0x72, 0x00, 0x9d, 0xbb, - 0x64, 0x84, 0xf7, 0x3b, 0xc6, 0xb9, 0x7f, 0xf5, 0x9b, 0x9a, 0x1c, 0x21, 0xa2, 0x5d, 0x84, 0x03, 0x80, 0xb8, 0xd3, - 0x54, 0xd6, 0xa1, 0x06, 0xe8, 0x03, 0x02, 0xeb, 0xd0, 0xb7, 0x19, 0xc0, 0x41, 0x1f, 0x6d, 0x9e, 0x45, 0x20, 0xaf, - 0x7b, 0x77, 0xec, 0x2d, 0xdb, 0xf9, 0xfc, 0xd9, 0x2a, 0xf5, 0xee, 0xd0, 0x21, 0xf8, 0x7c, 0xec, 0x4f, 0x2f, 0x03, - 0x83, 0x0b, 0xcd, 0xde, 0x3e, 0x11, 0x6c, 0xc7, 0x76, 0x4f, 0x10, 0xa9, 0xa8, 0x3b, 0xff, 0xf0, 0xd2, 0x44, 0xcf, - 0x3b, 0x2f, 0x2c, 0xf9, 0x02, 0xc0, 0x03, 0x59, 0x0c, 0x28, 0x3e, 0x0b, 0xef, 0x57, 0x96, 0x80, 0x9a, 0xfc, 0x96, - 0xaf, 0xbd, 0x77, 0x94, 0x7a, 0x03, 0x7f, 0x0e, 0x28, 0x7d, 0x92, 0x73, 0x6f, 0x39, 0xbc, 0xf5, 0x2f, 0x9e, 0x82, - 0xf3, 0xc4, 0x6a, 0x78, 0x03, 0x7f, 0x15, 0x7c, 0xe8, 0x2d, 0x07, 0x98, 0x58, 0xf2, 0xa1, 0xb7, 0x1a, 0x40, 0xaa, - 0xc2, 0x85, 0xc4, 0xd8, 0x87, 0xcf, 0x41, 0xce, 0xf0, 0x8f, 0xdf, 0x35, 0x06, 0xeb, 0xe7, 0xa0, 0xd0, 0x68, 0xac, - 0xa5, 0x0a, 0x59, 0x8a, 0xc5, 0x99, 0x00, 0x9b, 0x70, 0xdc, 0xed, 0x8b, 0x55, 0x6d, 0xd6, 0x82, 0xfe, 0x7c, 0xc0, - 0xf7, 0x68, 0xac, 0xae, 0xca, 0xb9, 0x28, 0x3f, 0x22, 0x7d, 0xaa, 0xe3, 0x63, 0x54, 0x6c, 0xea, 0xee, 0x74, 0xaa, - 0x55, 0x47, 0xda, 0xef, 0xca, 0x35, 0xd8, 0xf1, 0x3a, 0x39, 0xb2, 0x14, 0x9e, 0x75, 0xd8, 0x79, 0xe9, 0x94, 0xe8, - 0x30, 0x8c, 0x77, 0x5b, 0xf5, 0x8c, 0xa1, 0x3c, 0x37, 0x18, 0xd3, 0x05, 0x8f, 0xf8, 0xb3, 0x41, 0x2e, 0x43, 0x63, - 0x3e, 0x20, 0x1b, 0x86, 0xf2, 0xa1, 0x45, 0x86, 0x84, 0x88, 0xf7, 0x50, 0x09, 0xd8, 0xb6, 0xa0, 0x4c, 0x0a, 0x38, - 0x8b, 0x06, 0xbf, 0xd7, 0x5e, 0x0e, 0xbc, 0x07, 0x91, 0xdf, 0x48, 0x97, 0x72, 0x89, 0x8d, 0x4e, 0x1c, 0xcb, 0x42, - 0x3b, 0x8f, 0xeb, 0xaf, 0x63, 0x50, 0xbf, 0x57, 0xfa, 0x0d, 0xca, 0xd9, 0x1f, 0x25, 0xeb, 0xb4, 0xf1, 0xc4, 0xf8, - 0x97, 0xab, 0xfc, 0x53, 0xb4, 0xd4, 0xc3, 0xff, 0x67, 0x4c, 0xa1, 0xf4, 0x2f, 0xd3, 0x32, 0xda, 0xac, 0x16, 0xa2, - 0x14, 0x79, 0x24, 0x4e, 0xbe, 0x16, 0xd9, 0xb9, 0x7c, 0xe7, 0x53, 0xe8, 0x17, 0x80, 0x96, 0x7d, 0x82, 0x8c, 0xfe, - 0x8d, 0x09, 0x3e, 0xfc, 0x4d, 0x3b, 0xd7, 0xe6, 0x7c, 0x3c, 0xc9, 0xaf, 0xac, 0xbd, 0xdb, 0xf1, 0x22, 0x31, 0x8a, - 0xb1, 0xdc, 0x57, 0xdd, 0xac, 0x9c, 0xa8, 0xe4, 0xc0, 0x48, 0xd7, 0x64, 0x2f, 0x57, 0xb2, 0x6e, 0xa7, 0x5b, 0x09, - 0x44, 0x54, 0x81, 0xf7, 0x18, 0x57, 0xb1, 0x8f, 0x60, 0xba, 0xee, 0xb8, 0x8c, 0x76, 0xbc, 0x67, 0xbc, 0x3a, 0x51, - 0x56, 0x70, 0xbb, 0x11, 0xed, 0x09, 0x1d, 0xfd, 0x34, 0xa9, 0x2d, 0x0b, 0x07, 0x20, 0x77, 0x09, 0x63, 0xd9, 0x10, - 0xac, 0x18, 0x94, 0xbe, 0x5e, 0x53, 0xb2, 0x2c, 0xc0, 0xa2, 0xb3, 0xcb, 0x08, 0xc4, 0xb0, 0x6e, 0x9a, 0x13, 0x3a, - 0x5e, 0xba, 0x38, 0xef, 0xb5, 0x8a, 0x14, 0x3c, 0xa3, 0x45, 0xc7, 0xdc, 0x74, 0xa4, 0x1b, 0xa3, 0xbd, 0x7d, 0x61, - 0x10, 0x52, 0x3c, 0x7f, 0x60, 0xab, 0x75, 0x71, 0x91, 0x78, 0x85, 0x4c, 0xb4, 0x20, 0x96, 0x22, 0x30, 0xe3, 0x85, - 0xa6, 0x11, 0x26, 0x28, 0x53, 0x82, 0x45, 0x6b, 0x74, 0x68, 0x7f, 0x58, 0xc2, 0xee, 0x31, 0x46, 0x80, 0x40, 0x95, - 0xe9, 0x45, 0xd8, 0x9a, 0x30, 0x9b, 0xba, 0xd8, 0x00, 0x6d, 0x15, 0x43, 0x83, 0xb0, 0x36, 0xc4, 0x7c, 0x4c, 0xf3, - 0xe5, 0x3f, 0xb1, 0x18, 0xdb, 0x13, 0x88, 0xed, 0xdd, 0xae, 0x49, 0x98, 0xee, 0xb5, 0xb8, 0xb1, 0x5e, 0x6e, 0x4f, - 0x39, 0xa6, 0x76, 0xac, 0x8d, 0xda, 0xb1, 0x16, 0x7a, 0xc7, 0x5a, 0xeb, 0x1d, 0x6b, 0xd9, 0xf0, 0x47, 0x99, 0x17, - 0xb3, 0x04, 0xf4, 0xbb, 0x2b, 0xae, 0x1a, 0x04, 0xcd, 0xd8, 0xb0, 0x5b, 0xf8, 0x2d, 0xb1, 0x76, 0x4b, 0xff, 0x62, - 0xc1, 0x6e, 0x4c, 0x1f, 0xe8, 0xd6, 0x01, 0x96, 0x11, 0x35, 0xf9, 0x0e, 0x79, 0x37, 0x9d, 0x15, 0x85, 0xdb, 0x13, - 0xbb, 0xf1, 0xd9, 0x5b, 0xf3, 0xe6, 0xdd, 0x93, 0x08, 0x72, 0xef, 0xb8, 0x77, 0x37, 0x7c, 0xeb, 0x5f, 0xe8, 0x16, - 0xc8, 0xc9, 0x2c, 0x67, 0x20, 0x75, 0xc4, 0x27, 0x88, 0x56, 0xf6, 0x94, 0xef, 0x84, 0xdc, 0xd9, 0xd6, 0x4f, 0xee, - 0xdc, 0x6d, 0x6d, 0xf9, 0xe4, 0x8e, 0x55, 0x23, 0x8a, 0x15, 0xa7, 0x29, 0x12, 0x66, 0xd1, 0x06, 0x78, 0xea, 0xe5, - 0xfb, 0x1d, 0x3b, 0xe6, 0x70, 0xf7, 0xa4, 0xa3, 0xe3, 0xe5, 0x1c, 0xb0, 0xbb, 0xff, 0x68, 0x13, 0x36, 0x56, 0xba, - 0x56, 0xa1, 0xc3, 0xdd, 0x93, 0x4c, 0xe3, 0x39, 0x1c, 0xc9, 0xa7, 0x63, 0x8d, 0x0d, 0x82, 0xba, 0x3e, 0x67, 0x50, - 0x3b, 0x76, 0x5f, 0x13, 0x76, 0xd9, 0x31, 0xaf, 0x75, 0xcd, 0xdb, 0x2b, 0x4f, 0xc5, 0x86, 0x80, 0x0e, 0x5f, 0xab, - 0x1b, 0xe4, 0x5f, 0x02, 0xa7, 0x08, 0x00, 0x39, 0x1c, 0x2f, 0x79, 0xec, 0xfb, 0x34, 0x4b, 0xeb, 0x1d, 0x6a, 0x2d, - 0x2a, 0xcb, 0x32, 0xac, 0xbd, 0x1f, 0xb4, 0x62, 0x58, 0x6a, 0xfa, 0xa7, 0xe3, 0xc0, 0xed, 0x6c, 0xb7, 0x32, 0x76, - 0x19, 0x4f, 0x8a, 0x8b, 0xdf, 0x4e, 0x0b, 0xe5, 0xda, 0xcd, 0xdb, 0xf8, 0x4d, 0xab, 0x25, 0x4b, 0x6b, 0x3d, 0xe4, - 0xa5, 0x65, 0x11, 0x81, 0x00, 0x86, 0x23, 0x65, 0x17, 0x4b, 0xb8, 0x47, 0x58, 0xdd, 0x83, 0x50, 0x32, 0x2f, 0x5c, - 0x3c, 0x65, 0x31, 0x24, 0x02, 0x6c, 0x77, 0xa8, 0xd8, 0x16, 0x2e, 0x9e, 0xb2, 0x0d, 0x2f, 0xfa, 0xfd, 0x4c, 0x75, - 0x0a, 0x59, 0x77, 0x16, 0x7c, 0xa3, 0x9a, 0x63, 0x0d, 0x35, 0x5b, 0x9b, 0x64, 0x6b, 0x9c, 0xdb, 0x8a, 0x8f, 0x65, - 0x5b, 0xf1, 0xb1, 0xb2, 0xd6, 0xa5, 0x7b, 0xbd, 0x47, 0x75, 0x01, 0x6c, 0xfd, 0xb7, 0xc7, 0x2b, 0xd7, 0xf3, 0x19, - 0x01, 0x7c, 0xdd, 0xf0, 0xf1, 0xe4, 0x06, 0xbd, 0x4a, 0x6e, 0xfc, 0xdb, 0x81, 0x1a, 0x7f, 0xa7, 0x73, 0x6f, 0x00, - 0xba, 0x92, 0xf2, 0x0a, 0xc8, 0x3b, 0xc8, 0x31, 0xb7, 0xec, 0xca, 0xbb, 0x93, 0xef, 0xb0, 0xb7, 0xbc, 0x9e, 0xdd, - 0xcc, 0xd9, 0x0e, 0x9c, 0x0a, 0x92, 0x81, 0xbd, 0xac, 0xd8, 0x2e, 0x88, 0xed, 0x84, 0xdf, 0x09, 0x98, 0xf2, 0x39, - 0x04, 0x71, 0x05, 0xb7, 0x10, 0x87, 0x27, 0xff, 0x1c, 0xdc, 0xb5, 0x36, 0xeb, 0x3b, 0x66, 0x75, 0x4e, 0xb0, 0x66, - 0x56, 0x0f, 0x06, 0x8b, 0x66, 0xb2, 0xea, 0xf7, 0xbd, 0x9d, 0x76, 0x7c, 0x5a, 0x4a, 0x9d, 0xd8, 0x69, 0xad, 0xd6, - 0x0d, 0x7b, 0x2b, 0xb5, 0x2e, 0xc6, 0xd0, 0x03, 0xc4, 0x4f, 0xb7, 0x03, 0x7e, 0xd7, 0xb1, 0xb6, 0xbc, 0xb7, 0xec, - 0x86, 0xed, 0xe0, 0x12, 0xd4, 0xb4, 0x97, 0xfd, 0x49, 0xe5, 0x82, 0x76, 0xec, 0x92, 0x78, 0x38, 0x63, 0x56, 0x29, - 0x33, 0xeb, 0xa4, 0xba, 0x12, 0x9d, 0x31, 0x9d, 0xb5, 0x9e, 0xcf, 0xd5, 0x7c, 0x52, 0x68, 0x50, 0xbf, 0x73, 0xe2, - 0x23, 0x2a, 0x3a, 0x4f, 0x60, 0x6b, 0x59, 0x41, 0xac, 0xf6, 0x39, 0x58, 0x6b, 0xb5, 0x4b, 0xbf, 0x97, 0x0f, 0xb8, - 0x4d, 0x39, 0xac, 0x03, 0x83, 0x9a, 0x13, 0x2b, 0xea, 0x21, 0xdb, 0x31, 0x6e, 0x7e, 0x7a, 0xf9, 0x83, 0x13, 0x96, - 0xac, 0x58, 0xed, 0x4f, 0x7f, 0x7b, 0xe2, 0xe9, 0xef, 0xd4, 0xfe, 0x85, 0xf0, 0x83, 0xf1, 0xbf, 0x6b, 0xf7, 0xb5, - 0x16, 0xa3, 0xb2, 0x55, 0x8e, 0xd0, 0xb8, 0x5b, 0x49, 0x93, 0xe5, 0x27, 0xe1, 0x09, 0x6b, 0xc1, 0xb3, 0x5c, 0x2f, - 0xd1, 0xac, 0x80, 0x15, 0xd6, 0x32, 0x09, 0x57, 0x18, 0xab, 0xa5, 0xad, 0xbe, 0x45, 0xd3, 0x1c, 0x1f, 0xce, 0xb5, - 0x41, 0x99, 0x72, 0x76, 0x46, 0xac, 0x86, 0xcb, 0xb0, 0x34, 0xa1, 0x08, 0xd9, 0xbd, 0x1d, 0xdc, 0xd8, 0x29, 0x4b, - 0x29, 0xc3, 0x39, 0x06, 0x13, 0x1e, 0x89, 0x51, 0x95, 0xef, 0xef, 0x4b, 0x8a, 0x9c, 0xb6, 0xe5, 0xa0, 0x0a, 0x61, - 0x1f, 0x49, 0x94, 0xc0, 0xad, 0x48, 0x0b, 0x45, 0xca, 0xe2, 0x6f, 0x07, 0xe8, 0x02, 0x2f, 0xa0, 0xae, 0x46, 0xdd, - 0xfe, 0x70, 0xc4, 0xc3, 0x07, 0xa6, 0x3e, 0x30, 0x62, 0x49, 0xa0, 0xb6, 0xe7, 0x59, 0xba, 0x04, 0x15, 0x7e, 0x0f, - 0x57, 0x13, 0xb1, 0x9f, 0x5b, 0x52, 0x54, 0x64, 0x23, 0xbd, 0xa1, 0x35, 0x78, 0x84, 0xd6, 0x94, 0x17, 0x4e, 0xaa, - 0x4d, 0x3a, 0xef, 0x08, 0x39, 0x56, 0xdf, 0x5a, 0xc2, 0x68, 0x57, 0xf4, 0xe2, 0xde, 0xd1, 0x7b, 0x9e, 0xae, 0x7a, - 0xee, 0x4f, 0x5c, 0x31, 0x4f, 0x6e, 0x23, 0x50, 0xb7, 0x82, 0xea, 0xf6, 0x5e, 0x25, 0x58, 0xb0, 0xa4, 0xdd, 0xc7, - 0x6f, 0x67, 0xed, 0x40, 0x54, 0xc6, 0x2a, 0x7d, 0x4b, 0x12, 0xf6, 0xc4, 0xa0, 0x53, 0xa8, 0xca, 0xed, 0xee, 0x68, - 0x0b, 0x5c, 0xc7, 0x2c, 0x45, 0xcf, 0x6d, 0x91, 0xbb, 0xe5, 0xdf, 0x3d, 0x57, 0xe4, 0xec, 0x97, 0x80, 0xe0, 0xd4, - 0x7c, 0x43, 0x7c, 0x39, 0xc2, 0xa3, 0xea, 0x16, 0x38, 0x4e, 0xdf, 0x01, 0xfc, 0xc3, 0xe1, 0x12, 0x34, 0x01, 0xb1, - 0x60, 0xbd, 0x34, 0xee, 0xb1, 0x5e, 0x5c, 0x6c, 0x96, 0x49, 0xbe, 0x01, 0x67, 0x06, 0x4a, 0xb5, 0xf4, 0x03, 0xc7, - 0x6a, 0x01, 0x15, 0x0e, 0x66, 0x27, 0xf5, 0xc2, 0x32, 0xea, 0x31, 0x7d, 0x7e, 0x06, 0x7b, 0x47, 0x48, 0x00, 0xdc, - 0x2f, 0xfb, 0x80, 0x04, 0x3c, 0x74, 0x66, 0x07, 0x84, 0x13, 0x66, 0x51, 0x15, 0x48, 0x24, 0x47, 0xfa, 0xd9, 0x63, - 0x26, 0x92, 0x3f, 0x98, 0xf5, 0x9c, 0x53, 0xa2, 0xc7, 0x7a, 0xea, 0x08, 0xe9, 0xb1, 0x9e, 0x75, 0x44, 0xf4, 0x58, - 0xcf, 0x3a, 0x3e, 0x7a, 0xac, 0x67, 0x8e, 0x9d, 0x1e, 0x04, 0x26, 0x40, 0xe4, 0x01, 0xeb, 0xd1, 0x64, 0xea, 0x29, - 0xee, 0x01, 0xa2, 0x41, 0x60, 0x3d, 0x29, 0x9c, 0xf7, 0x00, 0x79, 0x8c, 0xc4, 0xea, 0xa0, 0xf7, 0x1f, 0xe3, 0xc7, - 0x3d, 0x23, 0x23, 0x8f, 0x5b, 0x87, 0xd5, 0xff, 0xfa, 0x4f, 0x08, 0x80, 0xc3, 0xb3, 0xa9, 0x77, 0x39, 0x86, 0xac, - 0xb2, 0x8c, 0x40, 0xf2, 0x13, 0x83, 0x2f, 0x5f, 0x00, 0x54, 0x7d, 0xa6, 0x6b, 0x35, 0x39, 0x6a, 0x8f, 0x39, 0x74, - 0xc5, 0x00, 0xb0, 0x0d, 0x4b, 0x54, 0xd5, 0xc2, 0x26, 0x2c, 0x6e, 0x3f, 0xc3, 0x68, 0x2e, 0x9b, 0x5e, 0xd0, 0x40, - 0x3d, 0x42, 0xf0, 0x4b, 0xeb, 0xa1, 0xb5, 0x96, 0x29, 0x87, 0xae, 0x8d, 0xa2, 0xca, 0x86, 0xba, 0x84, 0xd5, 0x5a, - 0x44, 0x35, 0x51, 0xa4, 0x5c, 0x32, 0x8a, 0x62, 0xa9, 0x82, 0x7d, 0x26, 0x96, 0x10, 0x35, 0x4f, 0x5b, 0x6d, 0x15, - 0xec, 0x97, 0x80, 0xb0, 0x16, 0xd6, 0x42, 0x3a, 0x83, 0xda, 0x3b, 0xfd, 0x48, 0xf9, 0xcb, 0x0b, 0xb9, 0x9d, 0x5b, - 0x28, 0xc2, 0xed, 0x39, 0x28, 0x6f, 0xea, 0xaa, 0x54, 0x44, 0xa3, 0x25, 0x50, 0xca, 0x9c, 0x20, 0xb2, 0x00, 0x01, - 0x1c, 0x37, 0x10, 0xf8, 0xbc, 0xc6, 0x27, 0xd0, 0x28, 0x04, 0xf2, 0x03, 0xab, 0x70, 0xed, 0x21, 0x2d, 0xb5, 0x46, - 0x44, 0x89, 0xf8, 0xd1, 0xd5, 0x73, 0x6c, 0x5f, 0x3d, 0x8d, 0xb5, 0xa5, 0x34, 0x41, 0xfc, 0xc4, 0x62, 0x0b, 0x31, - 0x41, 0x54, 0x87, 0xe8, 0x08, 0x96, 0x13, 0x42, 0x14, 0xfe, 0x14, 0xfa, 0xa9, 0x81, 0xbf, 0x64, 0x8b, 0x22, 0xaf, - 0x09, 0x16, 0xb3, 0x62, 0x80, 0x56, 0x45, 0xe0, 0x99, 0xce, 0x96, 0xca, 0x9c, 0xe6, 0xd1, 0x91, 0x1d, 0x9c, 0x77, - 0x1d, 0xec, 0xa5, 0x2f, 0x63, 0x27, 0xcb, 0xa6, 0x51, 0x1b, 0x1b, 0x22, 0xe1, 0x15, 0xf9, 0xcb, 0x2c, 0x35, 0xce, - 0x91, 0xb9, 0x5c, 0xdf, 0x75, 0xb1, 0x5c, 0xd2, 0x36, 0x61, 0x15, 0x22, 0xd4, 0x6d, 0x43, 0xe5, 0x52, 0x98, 0x8d, - 0x4d, 0xd3, 0x00, 0x5f, 0x28, 0x2a, 0x95, 0xaa, 0xd4, 0x56, 0x2a, 0x39, 0xe1, 0x5d, 0xdf, 0xd4, 0x22, 0x75, 0x45, - 0xb0, 0x8d, 0x19, 0xea, 0xa1, 0xdc, 0xa8, 0xb1, 0x6f, 0x3b, 0x56, 0xe9, 0x1d, 0x26, 0xc8, 0x19, 0x79, 0x91, 0x83, - 0x8b, 0x92, 0x82, 0xcc, 0xd5, 0x10, 0xe6, 0x0f, 0x1a, 0x3e, 0x2d, 0x2c, 0xf7, 0x50, 0x02, 0x66, 0x47, 0x0d, 0x0f, - 0x23, 0x04, 0x22, 0x2e, 0x95, 0x7d, 0xc5, 0xc4, 0xef, 0x29, 0x98, 0x25, 0x13, 0xba, 0x17, 0xb1, 0x28, 0x42, 0x1b, - 0x9f, 0x24, 0xc9, 0xd4, 0xd3, 0x14, 0xdc, 0xc8, 0x65, 0x98, 0xa3, 0x11, 0x5a, 0xf2, 0x91, 0x03, 0xe9, 0x6b, 0x39, - 0x95, 0xe0, 0x23, 0xea, 0x14, 0x70, 0x3c, 0x3f, 0x2f, 0xac, 0x9f, 0x2c, 0x97, 0x98, 0xcb, 0xda, 0xfc, 0x97, 0x1d, - 0x1d, 0x83, 0x5d, 0x9e, 0x26, 0x8e, 0xab, 0xff, 0xa8, 0x4a, 0x8a, 0xfb, 0x5f, 0xd2, 0x1c, 0x50, 0x04, 0x33, 0x7b, - 0x8a, 0xf1, 0xb1, 0xcf, 0x32, 0x05, 0xfc, 0xed, 0x7a, 0x6b, 0xc9, 0xc4, 0x2e, 0x69, 0x37, 0x57, 0xc6, 0x2f, 0xb5, - 0x61, 0xc7, 0xc1, 0xb9, 0x01, 0x28, 0xce, 0x1a, 0x1d, 0x96, 0xd7, 0xba, 0x6d, 0x55, 0xa8, 0x40, 0xad, 0xff, 0xbd, - 0x5b, 0x98, 0xf2, 0x36, 0x2f, 0x95, 0xb7, 0x79, 0x68, 0x02, 0x04, 0x22, 0x33, 0xe4, 0x59, 0xd3, 0x31, 0x49, 0xdc, - 0x3b, 0x52, 0xd2, 0xbe, 0x23, 0xc5, 0x0f, 0xde, 0x91, 0x90, 0x6f, 0x09, 0x1d, 0xd9, 0x17, 0x9c, 0x9c, 0x40, 0x99, - 0xc1, 0x5e, 0x5e, 0x33, 0xd9, 0x3f, 0xa0, 0xbd, 0x70, 0x2e, 0xcb, 0x2b, 0xfe, 0x56, 0x78, 0x6b, 0x7f, 0xba, 0x3e, - 0xed, 0xaa, 0x7a, 0xfb, 0x8d, 0x99, 0x79, 0x38, 0x14, 0x87, 0x43, 0x65, 0x82, 0x76, 0x6f, 0xb8, 0x18, 0xe4, 0xec, - 0xce, 0x8d, 0x8f, 0x7f, 0xcb, 0x51, 0xc4, 0x56, 0xca, 0x23, 0xe9, 0x42, 0x25, 0x86, 0x97, 0x06, 0x1e, 0x66, 0xc7, - 0xc7, 0x93, 0xdd, 0xd5, 0xdd, 0x64, 0x30, 0xd8, 0xa9, 0xbe, 0xdd, 0xf2, 0x7a, 0xb6, 0x9b, 0xb3, 0x7b, 0x7e, 0x3b, - 0xdd, 0x06, 0xfb, 0x06, 0xb6, 0xdd, 0xdd, 0x95, 0x38, 0x1c, 0x76, 0xcf, 0xf8, 0x8d, 0xbf, 0xbf, 0x47, 0x40, 0x67, - 0x7e, 0x3e, 0x6e, 0x63, 0xfc, 0x5c, 0xb7, 0x5d, 0xb5, 0x76, 0x00, 0x4f, 0xff, 0xa3, 0x77, 0x3d, 0x5b, 0xcc, 0x7d, - 0xf6, 0x88, 0xdf, 0x83, 0x7f, 0x3e, 0x6e, 0x92, 0x48, 0x7d, 0xa2, 0x5d, 0x26, 0xaf, 0xc1, 0x81, 0x7c, 0xe7, 0xb3, - 0x57, 0xfc, 0x7e, 0xb6, 0x98, 0xf3, 0xe2, 0x70, 0x78, 0x3f, 0x0d, 0x91, 0xac, 0x29, 0xac, 0x88, 0x25, 0xc5, 0xf3, - 0x83, 0xf0, 0xf8, 0xbd, 0x88, 0x0c, 0x91, 0x96, 0x7b, 0x77, 0xc8, 0xae, 0x59, 0xe4, 0x07, 0xf0, 0x41, 0xb6, 0xf3, - 0x27, 0xb2, 0xa6, 0x74, 0xbf, 0x78, 0xe4, 0x1f, 0x0e, 0xf4, 0xd7, 0x2b, 0xff, 0x70, 0x78, 0xcf, 0xee, 0x11, 0x1c, - 0x9d, 0xef, 0xa0, 0x7f, 0xf4, 0xad, 0x03, 0xaa, 0x32, 0x7c, 0x3b, 0xdb, 0xcc, 0xfd, 0x67, 0x2b, 0xb6, 0x04, 0x2e, - 0x14, 0xe5, 0x85, 0x76, 0xcd, 0xee, 0xd1, 0xeb, 0x8c, 0x9c, 0x88, 0x66, 0xbb, 0xb9, 0xcf, 0x62, 0x7c, 0xae, 0xee, - 0x8b, 0xc9, 0x37, 0xef, 0x8b, 0x3b, 0xb6, 0xed, 0xbe, 0x2f, 0xca, 0x37, 0xdd, 0xf5, 0xb3, 0x65, 0x3b, 0x76, 0x0f, - 0x33, 0xec, 0x2d, 0xbf, 0x6e, 0x8e, 0x1d, 0x63, 0xbf, 0x79, 0x63, 0x04, 0x50, 0x66, 0x0b, 0x16, 0x0b, 0x0e, 0x4a, - 0xb5, 0x6a, 0x5b, 0x12, 0x79, 0xa5, 0x03, 0xd5, 0x66, 0x04, 0xf7, 0xd5, 0x42, 0xce, 0x3c, 0x33, 0xd0, 0xb7, 0x15, - 0xa2, 0x85, 0xc3, 0x06, 0xfc, 0x8d, 0xb6, 0x8e, 0x31, 0x4c, 0xb3, 0x9a, 0x69, 0x5b, 0xd4, 0xe5, 0xf7, 0xbd, 0x67, - 0xf2, 0x1b, 0x19, 0xd8, 0x42, 0x24, 0x85, 0xe3, 0xf8, 0xe2, 0xe9, 0x09, 0xff, 0x55, 0xcb, 0xa3, 0x56, 0xfb, 0x85, - 0x52, 0x9f, 0xbe, 0xa4, 0x23, 0x9a, 0xb8, 0x17, 0x6d, 0x19, 0xd6, 0x28, 0x6b, 0x6a, 0xe9, 0x30, 0x8c, 0x6b, 0xd8, - 0x97, 0x07, 0x0e, 0x7d, 0x07, 0x04, 0xda, 0x2a, 0x95, 0x02, 0x2d, 0x1c, 0xc3, 0x28, 0xcc, 0x42, 0xca, 0xc3, 0xc2, - 0x2c, 0xe5, 0x3d, 0x16, 0x68, 0x71, 0xab, 0xee, 0x31, 0xb5, 0xdd, 0x82, 0x08, 0xab, 0xb7, 0x8c, 0xf3, 0xcb, 0x46, - 0x15, 0x6e, 0x0b, 0x50, 0x14, 0x41, 0x19, 0xec, 0x49, 0x6e, 0xbb, 0x51, 0xd2, 0x6c, 0x14, 0xd6, 0x62, 0x59, 0x94, - 0xbb, 0x5e, 0xc3, 0x6e, 0xf0, 0x82, 0xaa, 0x9f, 0x10, 0xb6, 0x65, 0xcf, 0x3a, 0x94, 0x8b, 0xf4, 0xdf, 0xb2, 0xf4, - 0x7c, 0xbf, 0x35, 0xe7, 0x7f, 0xfa, 0x8a, 0x3e, 0x2a, 0xff, 0xfd, 0x4b, 0xfa, 0xc9, 0x60, 0x19, 0x39, 0xa5, 0x7e, - 0x8a, 0x46, 0xb7, 0x69, 0x4e, 0x18, 0x5b, 0xbe, 0x7e, 0xfa, 0x1d, 0x32, 0x05, 0xc9, 0xa1, 0x94, 0xaa, 0x9c, 0xec, - 0xa1, 0x2f, 0xbc, 0xee, 0xc3, 0x4c, 0x30, 0x00, 0xe1, 0x35, 0xda, 0x54, 0x13, 0x26, 0xf1, 0xe0, 0x0a, 0xfe, 0x6f, - 0x04, 0x31, 0x68, 0x9f, 0x28, 0xea, 0xd8, 0x36, 0xd2, 0x75, 0xdb, 0x39, 0x48, 0xee, 0xd4, 0x95, 0x3f, 0x2a, 0x27, - 0xff, 0x8e, 0x86, 0xc8, 0x2b, 0xae, 0x10, 0x2b, 0x0b, 0x2e, 0xb1, 0x18, 0x2a, 0x52, 0x80, 0x6b, 0x08, 0x22, 0x65, - 0x51, 0x52, 0xb8, 0xe5, 0xa0, 0x2a, 0x02, 0x30, 0xae, 0x56, 0x47, 0x9d, 0x08, 0x1f, 0xb7, 0xd6, 0x22, 0x04, 0x2b, - 0x1a, 0xb5, 0xb2, 0x56, 0xe0, 0x0b, 0xd2, 0x97, 0x0e, 0x05, 0x31, 0x3d, 0x0a, 0xa9, 0x2a, 0x1d, 0x0a, 0xa4, 0x39, - 0x54, 0x7c, 0x63, 0xb0, 0x51, 0x54, 0xa4, 0xe7, 0x2f, 0x4d, 0x4a, 0x2e, 0x8d, 0x19, 0x1f, 0x44, 0x19, 0x89, 0xbc, - 0x0e, 0x97, 0x62, 0x5a, 0x20, 0xdf, 0xe8, 0xf1, 0x83, 0xe0, 0x12, 0xde, 0x0d, 0xb9, 0x57, 0x80, 0x2d, 0x01, 0x3b, - 0xc0, 0xbd, 0x32, 0xa3, 0x5c, 0xa7, 0x75, 0xfd, 0xd6, 0x7a, 0x28, 0x86, 0xe1, 0x13, 0x4b, 0x60, 0x3b, 0x5a, 0x47, - 0x47, 0x7a, 0xf8, 0xf0, 0xbf, 0xae, 0x6a, 0x8e, 0x3a, 0x95, 0xcb, 0xd9, 0xf1, 0x84, 0xa5, 0x88, 0x19, 0x74, 0x7f, - 0xdd, 0xbe, 0x14, 0x40, 0xb7, 0xcb, 0x62, 0x9e, 0x8d, 0x76, 0xf2, 0x6f, 0xe9, 0xc6, 0x8a, 0xd2, 0x26, 0xde, 0x65, - 0xbd, 0xb1, 0x3f, 0x1c, 0xfd, 0xc7, 0x93, 0x77, 0x13, 0x42, 0xd5, 0xd9, 0xb0, 0xb5, 0x8e, 0x73, 0xf9, 0x5f, 0xff, - 0x39, 0x26, 0x2b, 0x08, 0x0a, 0xc2, 0xb2, 0x53, 0x4c, 0x54, 0x30, 0x8a, 0x14, 0x6b, 0x3e, 0x9e, 0xac, 0x51, 0x27, - 0xbc, 0xf6, 0x17, 0x5a, 0x27, 0x4c, 0x8c, 0xac, 0x54, 0xfe, 0x9a, 0x55, 0x6c, 0xa9, 0x32, 0x0b, 0xc8, 0x3c, 0xc8, - 0x27, 0x6b, 0xa3, 0xc1, 0x5c, 0xf1, 0x7a, 0xb6, 0x9e, 0x4b, 0xe5, 0x33, 0x98, 0x72, 0x16, 0x83, 0x93, 0xa5, 0xb0, - 0x3b, 0x12, 0x28, 0x5a, 0x33, 0x74, 0xed, 0x4f, 0xb1, 0x55, 0xaf, 0xd2, 0xaa, 0x06, 0x78, 0x40, 0x88, 0x81, 0xa1, - 0xf6, 0x6a, 0xe1, 0xa1, 0xb5, 0x00, 0xd6, 0xfe, 0xa8, 0xf4, 0x83, 0xf1, 0x64, 0xc1, 0x6f, 0x90, 0x7f, 0x39, 0x72, - 0xd4, 0xee, 0xfd, 0xbe, 0x77, 0x07, 0x52, 0x70, 0xe4, 0x5a, 0x28, 0x90, 0x08, 0xe8, 0x86, 0x6f, 0x7c, 0xe5, 0x83, - 0xf1, 0x16, 0xb5, 0xd5, 0xa0, 0xa0, 0x76, 0x74, 0xcb, 0x63, 0x47, 0xef, 0x7c, 0x77, 0x42, 0x5f, 0x7d, 0xa3, 0x85, - 0xe3, 0x6f, 0x9c, 0x91, 0x6b, 0xb6, 0xea, 0x90, 0x23, 0x9a, 0x49, 0x87, 0x10, 0xb1, 0x62, 0x6b, 0xf6, 0x96, 0x54, - 0xce, 0x9d, 0x43, 0x76, 0xfa, 0x08, 0x55, 0x7a, 0xad, 0x87, 0xb7, 0x13, 0xa5, 0xbb, 0x3d, 0xde, 0x4d, 0xbe, 0x67, - 0x13, 0x11, 0x83, 0x01, 0x6d, 0x10, 0xce, 0xc8, 0x3a, 0x44, 0x2a, 0x1d, 0x20, 0x04, 0x8e, 0x09, 0x68, 0xfa, 0xaf, - 0x6f, 0x49, 0x14, 0x70, 0xa4, 0x8d, 0x90, 0xb5, 0xec, 0x70, 0xc8, 0x41, 0xa3, 0xdc, 0xfc, 0xe9, 0x15, 0xea, 0x34, - 0x07, 0xe6, 0xe9, 0x12, 0xf6, 0x1c, 0x3c, 0xd2, 0x8b, 0xe3, 0x23, 0xfd, 0xbf, 0xa3, 0x89, 0x1a, 0xff, 0xfb, 0x9a, - 0x28, 0xa5, 0x45, 0x72, 0x54, 0x4b, 0xdf, 0xa5, 0x8e, 0x82, 0x8b, 0xbc, 0xa3, 0x16, 0xb2, 0x67, 0xd9, 0xb8, 0x51, - 0xcd, 0xfb, 0xff, 0xb5, 0x32, 0xff, 0x5f, 0xd3, 0xca, 0x30, 0x25, 0x3b, 0x96, 0x6a, 0xe6, 0x81, 0x56, 0x31, 0xcc, - 0x7e, 0x21, 0x09, 0x91, 0xe1, 0xd2, 0x80, 0x1f, 0x55, 0xb0, 0x8f, 0xd3, 0x6a, 0x9d, 0x85, 0x3b, 0x54, 0xa2, 0xde, - 0x8a, 0x65, 0x9a, 0x3f, 0xaf, 0xff, 0x25, 0xca, 0x02, 0xa6, 0xf6, 0xb2, 0x4c, 0xe3, 0x80, 0x2c, 0xfc, 0x59, 0x58, - 0xe2, 0xe4, 0xc6, 0x36, 0xfe, 0x22, 0xc7, 0xd3, 0x7e, 0xd5, 0x99, 0x79, 0x20, 0x81, 0x1a, 0xe8, 0x42, 0x72, 0x2e, - 0x2b, 0x8b, 0x7b, 0x84, 0x6e, 0xfe, 0xb1, 0x2c, 0x8b, 0xd2, 0xeb, 0x7d, 0x4a, 0xd2, 0xea, 0x6c, 0x25, 0xea, 0xa4, - 0x88, 0x15, 0x94, 0x4d, 0x0a, 0x30, 0xfa, 0xb0, 0xf2, 0x44, 0x1c, 0x9c, 0x21, 0x50, 0xc3, 0x59, 0x9d, 0x84, 0x00, - 0x34, 0xac, 0x10, 0xf6, 0xcf, 0xa0, 0x85, 0x67, 0x61, 0x1c, 0xae, 0x01, 0x26, 0x27, 0xad, 0xce, 0xd6, 0x65, 0x71, - 0x97, 0xc6, 0x22, 0x1e, 0xf5, 0x14, 0x25, 0xcb, 0xeb, 0xdc, 0x95, 0x73, 0xfd, 0xfd, 0x9f, 0x14, 0xc0, 0x6e, 0xc0, - 0x6c, 0x5b, 0x60, 0x07, 0x00, 0x09, 0x0a, 0x64, 0x0b, 0x75, 0x1a, 0x9d, 0xa9, 0xa5, 0x02, 0xef, 0xb9, 0x1e, 0xe0, - 0xaf, 0x73, 0xc0, 0x32, 0xae, 0x0b, 0x19, 0x30, 0x82, 0x00, 0x46, 0xe0, 0xa0, 0x04, 0x0c, 0x9d, 0x21, 0x6e, 0xab, - 0x72, 0xd6, 0x42, 0x73, 0xa5, 0xdb, 0x92, 0x9b, 0x46, 0x39, 0x5b, 0x89, 0x00, 0xfa, 0xea, 0xa6, 0xc4, 0xe9, 0x62, - 0xd1, 0x4a, 0xc2, 0xbe, 0x7d, 0xdf, 0x4e, 0x15, 0x79, 0x7c, 0x94, 0x86, 0xbc, 0x02, 0xcf, 0x33, 0x8e, 0x24, 0x51, - 0x22, 0x78, 0x9d, 0x37, 0x66, 0x1c, 0x7e, 0x6c, 0x53, 0x4e, 0xed, 0xcd, 0x7a, 0x01, 0x38, 0x4f, 0xd0, 0x96, 0x01, - 0xc6, 0x02, 0x06, 0xe7, 0x42, 0x2c, 0x79, 0x8a, 0xe0, 0x97, 0x4e, 0xa4, 0x30, 0xee, 0x72, 0x18, 0xe6, 0x41, 0xd1, - 0xbb, 0xa4, 0xfe, 0xe8, 0xf7, 0x51, 0x9b, 0x0c, 0x86, 0xa0, 0x12, 0x40, 0x65, 0xdd, 0x20, 0x31, 0xb0, 0x2a, 0xdd, - 0x48, 0x5c, 0x42, 0xbc, 0xcc, 0x57, 0x53, 0x11, 0x05, 0xef, 0xeb, 0x09, 0x21, 0x9c, 0x60, 0x7c, 0x88, 0x1b, 0x20, - 0x60, 0xb0, 0x8a, 0x0b, 0x0c, 0x92, 0xe7, 0x12, 0xdd, 0x1f, 0xcf, 0x77, 0x0c, 0x70, 0xe5, 0xbc, 0xa7, 0xda, 0xd5, - 0x03, 0x7b, 0xb9, 0x4a, 0x97, 0x8c, 0x10, 0x56, 0xfc, 0x5f, 0x44, 0xde, 0xb7, 0xc3, 0x04, 0xd4, 0x36, 0xf2, 0xc7, - 0x20, 0x31, 0x97, 0x89, 0x22, 0x88, 0x47, 0x59, 0xc1, 0x92, 0x34, 0xd8, 0x8c, 0x92, 0x14, 0x34, 0x9a, 0x18, 0x43, - 0xa6, 0x42, 0x3b, 0x24, 0x8d, 0x66, 0x63, 0xb2, 0x8f, 0x21, 0xaf, 0xe1, 0x62, 0xb1, 0xc0, 0xfb, 0x7e, 0x11, 0xaa, - 0x83, 0x6d, 0x69, 0x0e, 0x01, 0x27, 0x09, 0xf6, 0xd4, 0x15, 0x29, 0x09, 0xb3, 0xd1, 0xa7, 0x90, 0x73, 0x03, 0x3a, - 0x4e, 0x1a, 0x43, 0xf5, 0x81, 0x49, 0x78, 0x15, 0xa1, 0x93, 0xb2, 0x42, 0x58, 0xc0, 0x7d, 0x23, 0xa3, 0xd1, 0x4a, - 0x1a, 0x04, 0xde, 0x66, 0xd8, 0x0a, 0x6c, 0x42, 0xc3, 0x7f, 0xcc, 0x3c, 0x4c, 0xab, 0x59, 0x09, 0xe6, 0x7c, 0x03, - 0x95, 0x18, 0x4f, 0x16, 0x57, 0x7c, 0xe3, 0x62, 0x25, 0x26, 0xb3, 0xc5, 0x7c, 0xb2, 0x96, 0x54, 0x73, 0xb9, 0xb7, - 0x66, 0x19, 0x5b, 0xc0, 0xfe, 0x61, 0x60, 0x28, 0x1d, 0xd8, 0xd1, 0x54, 0xd3, 0x26, 0x01, 0x26, 0xd3, 0x39, 0xe7, - 0xc3, 0x4b, 0x44, 0x93, 0xd5, 0xa9, 0x3b, 0x99, 0xaa, 0x76, 0x70, 0x4d, 0xce, 0xe4, 0xf4, 0x48, 0x3d, 0xd5, 0xba, - 0x97, 0x7c, 0xb4, 0x1d, 0x56, 0xa3, 0xad, 0x1f, 0x80, 0x5b, 0xa7, 0xb0, 0xd3, 0x77, 0xc3, 0x6a, 0xb4, 0xf3, 0x35, - 0xec, 0x2e, 0x29, 0x04, 0xaa, 0xbf, 0xca, 0x9a, 0xcc, 0xc5, 0xeb, 0xe2, 0xde, 0x2b, 0xd8, 0x53, 0x7f, 0xa0, 0x7f, - 0x95, 0xec, 0xa9, 0x6f, 0x33, 0xb9, 0xfe, 0x95, 0x76, 0x8d, 0xc6, 0x4c, 0xc7, 0x6b, 0x57, 0x60, 0x85, 0x06, 0xc8, - 0x2f, 0xd8, 0xd1, 0xde, 0xe4, 0x20, 0x10, 0xa0, 0x7b, 0x09, 0x8e, 0xa2, 0x80, 0xa8, 0x69, 0x55, 0x79, 0x74, 0xba, - 0xf7, 0xf7, 0xf8, 0x46, 0x08, 0xd8, 0xe4, 0xa9, 0x75, 0x6f, 0x19, 0xfb, 0x87, 0x03, 0x84, 0xd0, 0xcb, 0xe9, 0x37, - 0xda, 0xb2, 0x7a, 0xb4, 0x63, 0xb9, 0x6f, 0x18, 0xf5, 0x14, 0x8c, 0x61, 0xe8, 0xc2, 0x2a, 0x46, 0xf2, 0x0c, 0xc8, - 0x1a, 0xbf, 0x41, 0x74, 0x01, 0x8b, 0x5e, 0xef, 0xd5, 0x11, 0x0d, 0x22, 0xa0, 0xd2, 0x6b, 0xd2, 0x58, 0xe4, 0x73, - 0x55, 0x88, 0xde, 0x7b, 0x6b, 0xe7, 0xcd, 0x8c, 0x64, 0x99, 0x34, 0x52, 0xed, 0x56, 0x16, 0xeb, 0xca, 0x9b, 0x9d, - 0x90, 0x2e, 0xe6, 0x18, 0x2a, 0x83, 0xc7, 0x01, 0x28, 0x3d, 0xff, 0x11, 0x7a, 0x25, 0x43, 0xa6, 0x59, 0xa2, 0x99, - 0xdd, 0x35, 0xfe, 0x64, 0x95, 0x7a, 0x31, 0x22, 0x66, 0x03, 0x5b, 0x88, 0xdb, 0xa2, 0xd2, 0x6d, 0x51, 0x28, 0x5b, - 0x14, 0xe9, 0x43, 0xed, 0x4c, 0x77, 0x66, 0xe1, 0xb3, 0xca, 0xb4, 0xef, 0x53, 0x66, 0xc6, 0x06, 0x68, 0xbb, 0x08, - 0xdf, 0x40, 0x07, 0x2a, 0x84, 0xfc, 0x0d, 0x22, 0x22, 0x11, 0xb0, 0xcb, 0xa9, 0x3b, 0xb1, 0xe9, 0x90, 0xcc, 0x43, - 0xcc, 0x0a, 0x35, 0xca, 0x0b, 0x9e, 0x1c, 0x0d, 0x48, 0x45, 0xa8, 0xdb, 0xfd, 0xfe, 0xf9, 0xc2, 0x05, 0xb5, 0x5f, - 0x53, 0xec, 0x18, 0xdd, 0x14, 0x70, 0x2e, 0x78, 0x94, 0xf7, 0xdc, 0x3b, 0x07, 0x34, 0xc7, 0xf6, 0x14, 0x59, 0x03, - 0x4e, 0x6f, 0xbb, 0x10, 0x60, 0xfb, 0xac, 0xd9, 0xda, 0x9f, 0xac, 0xae, 0xa2, 0xa9, 0x57, 0xf2, 0x99, 0xee, 0xa2, - 0xc4, 0xed, 0xa2, 0x58, 0x76, 0xd1, 0xa6, 0x81, 0x60, 0xc7, 0x95, 0x1f, 0x00, 0x6f, 0x68, 0xd4, 0xef, 0x97, 0xad, - 0x9e, 0x3d, 0xf9, 0xda, 0x71, 0xcf, 0x66, 0x3e, 0x2b, 0x4d, 0xcf, 0x7e, 0x4e, 0xdd, 0x9e, 0x95, 0x93, 0xbd, 0xe8, - 0x9c, 0xec, 0xd3, 0xd9, 0x3c, 0x10, 0x5c, 0xee, 0xdc, 0xe7, 0xf9, 0x54, 0x4f, 0xbb, 0xca, 0x0f, 0x5a, 0x43, 0x64, - 0xed, 0x72, 0x55, 0xf7, 0xba, 0x82, 0x05, 0x2c, 0xc1, 0xdd, 0x7a, 0x69, 0xfe, 0x19, 0xbb, 0xbf, 0x17, 0xf4, 0xd2, - 0xfc, 0x77, 0xfa, 0x93, 0x02, 0x38, 0x00, 0x8d, 0xa9, 0xdd, 0x02, 0x0f, 0x31, 0x54, 0x50, 0xb8, 0x9b, 0x95, 0x73, - 0xaf, 0x06, 0x38, 0x4c, 0xd2, 0x37, 0xb4, 0x7a, 0xa5, 0xc5, 0xae, 0x97, 0xc9, 0x5e, 0x01, 0x1e, 0xaa, 0x90, 0x87, - 0x87, 0x43, 0xd4, 0x31, 0xec, 0xa0, 0x8e, 0x80, 0x61, 0x0f, 0xa1, 0xb1, 0x05, 0x9e, 0x8f, 0xbf, 0x64, 0x7c, 0x2f, - 0x40, 0x6d, 0x84, 0xf0, 0x78, 0xb5, 0x28, 0x43, 0x6c, 0xd9, 0x1b, 0xa4, 0x92, 0xfa, 0x45, 0x20, 0xca, 0x68, 0x15, - 0xd0, 0x56, 0x7b, 0xcc, 0xd2, 0x78, 0x0d, 0xa1, 0x62, 0xa9, 0x8f, 0x21, 0x34, 0x70, 0xf8, 0x1d, 0x0e, 0x20, 0xc1, - 0x97, 0x5c, 0x93, 0xcd, 0xbd, 0xc9, 0xef, 0x68, 0x9f, 0x3f, 0x1c, 0xce, 0x2f, 0x11, 0x94, 0x2e, 0x85, 0x8f, 0x54, - 0x22, 0xaa, 0xa7, 0xb8, 0x29, 0x21, 0x9b, 0x25, 0x2b, 0xfd, 0xe0, 0xb3, 0xfa, 0x05, 0x00, 0xb2, 0x10, 0x68, 0x13, - 0x99, 0xfd, 0xe9, 0x4c, 0x45, 0x17, 0x00, 0x87, 0xf8, 0xc3, 0x27, 0x88, 0xbe, 0xa1, 0x65, 0x5a, 0x3e, 0x4e, 0x78, - 0x08, 0x5a, 0x5b, 0xd2, 0x49, 0xc4, 0x4a, 0x81, 0x0d, 0x91, 0xf0, 0xfd, 0xfe, 0x79, 0x2c, 0xe9, 0x40, 0xa3, 0x56, - 0xf7, 0xc6, 0xad, 0xee, 0x95, 0xaf, 0xeb, 0x4e, 0x6e, 0x7c, 0x50, 0xb4, 0xcf, 0xe6, 0x8d, 0xca, 0xf7, 0x7d, 0x9d, - 0xb3, 0x3b, 0xdd, 0x3b, 0x72, 0x4e, 0x7c, 0x7f, 0x0f, 0xa1, 0xe8, 0xa1, 0x29, 0xb2, 0x2c, 0x09, 0x03, 0x5a, 0x6b, - 0xd7, 0x9e, 0x65, 0x74, 0xf0, 0xda, 0x37, 0x84, 0x88, 0x3c, 0xc5, 0x27, 0x21, 0xb7, 0x38, 0x3e, 0x28, 0xd0, 0x3f, - 0x33, 0xfe, 0xcc, 0x89, 0x1f, 0xb6, 0xfa, 0x05, 0x70, 0x6e, 0xba, 0xf7, 0xee, 0xc4, 0xac, 0xc7, 0x50, 0xca, 0xc6, - 0xff, 0xfd, 0x3e, 0x91, 0x05, 0x3a, 0x1d, 0xd1, 0x30, 0x10, 0xdc, 0x45, 0xf5, 0x7f, 0xaf, 0x78, 0xdd, 0xb3, 0x56, - 0xe7, 0xcb, 0x4f, 0x9d, 0x9e, 0xf4, 0x7a, 0xe9, 0x56, 0xf8, 0x32, 0x4c, 0x7c, 0xe7, 0x75, 0xbf, 0x61, 0xbb, 0xef, - 0x7e, 0x79, 0x77, 0xf4, 0x32, 0xb0, 0x49, 0xe1, 0x3b, 0x9b, 0x92, 0xcf, 0x7a, 0xa0, 0xf0, 0xeb, 0xb1, 0x5e, 0x5d, - 0xac, 0x7b, 0xac, 0x87, 0x5a, 0x40, 0xf4, 0xb0, 0x00, 0xf5, 0x5f, 0xcf, 0x3e, 0x0d, 0x85, 0x83, 0x6c, 0x9c, 0x2a, - 0x50, 0x64, 0xc1, 0x9f, 0x89, 0xd1, 0xba, 0x20, 0x40, 0x64, 0xb3, 0x7d, 0x7d, 0xac, 0x4e, 0x66, 0xdf, 0x94, 0x5a, - 0x92, 0xc1, 0x37, 0x01, 0x99, 0x1d, 0x58, 0x39, 0x41, 0xe9, 0xb8, 0x35, 0xe0, 0xca, 0x16, 0x91, 0x78, 0xfb, 0xd3, - 0x20, 0x3b, 0x6b, 0x4e, 0x1a, 0xed, 0xc3, 0x3e, 0xcd, 0x03, 0x04, 0x22, 0x99, 0x8a, 0x20, 0xd7, 0xdc, 0x5b, 0xd2, - 0x47, 0x87, 0x73, 0x5e, 0xc8, 0x3f, 0xa7, 0x52, 0x87, 0x38, 0x94, 0x58, 0x03, 0x81, 0xca, 0x33, 0x54, 0x39, 0x6c, - 0x90, 0xe3, 0x8f, 0x8e, 0x64, 0x26, 0x31, 0x59, 0xe4, 0x6e, 0xcd, 0x54, 0xf8, 0x81, 0xe0, 0x63, 0x96, 0x73, 0xe0, - 0x02, 0x9b, 0xcd, 0x7d, 0x35, 0xc5, 0xc5, 0x15, 0xf8, 0x63, 0x0a, 0xbf, 0xe2, 0x29, 0xec, 0xb4, 0xfb, 0x75, 0x51, - 0xa5, 0xa8, 0xdb, 0x28, 0x2c, 0x2a, 0x59, 0x30, 0xad, 0x21, 0x4d, 0x74, 0x18, 0xfd, 0x49, 0xce, 0x40, 0x41, 0xc8, - 0x2f, 0x9b, 0x06, 0x18, 0xa9, 0xe4, 0xf2, 0xa0, 0x4a, 0x02, 0x2f, 0xc0, 0x36, 0xa8, 0xd8, 0xba, 0x80, 0x20, 0xdb, - 0xa4, 0x28, 0xd3, 0xaf, 0x45, 0x5e, 0x87, 0x59, 0x50, 0x8d, 0xd2, 0xea, 0x27, 0xfd, 0x13, 0x98, 0xb7, 0xa9, 0x18, - 0xd5, 0x2a, 0x26, 0xbf, 0xd1, 0xef, 0x17, 0x83, 0xd6, 0x87, 0x0c, 0x3e, 0x7a, 0x6d, 0x1a, 0xfc, 0xda, 0x69, 0xb0, - 0xc3, 0x44, 0x23, 0x00, 0x92, 0x39, 0xb5, 0xe4, 0xa1, 0xe8, 0xcf, 0x20, 0xc7, 0x1a, 0x55, 0x4e, 0xc1, 0x60, 0xfd, - 0xc7, 0xa3, 0x1d, 0x98, 0x7a, 0x71, 0xb4, 0x25, 0x3b, 0x68, 0xe5, 0x1b, 0xe0, 0x7e, 0x8d, 0x6c, 0x31, 0xcb, 0x01, - 0x9a, 0xbd, 0x46, 0x64, 0x7c, 0xf2, 0x02, 0x18, 0xb3, 0x75, 0x16, 0x46, 0x22, 0x0e, 0xc6, 0xaa, 0x31, 0x63, 0x06, - 0x06, 0x2e, 0xd0, 0xb5, 0x4c, 0x4a, 0xd2, 0x90, 0x0e, 0x06, 0xac, 0x94, 0x2d, 0x1c, 0xf0, 0xa2, 0x39, 0x6e, 0xc7, - 0xbb, 0x16, 0x8d, 0x07, 0xb6, 0x8b, 0xed, 0xef, 0x5e, 0x14, 0xdb, 0xb7, 0xe1, 0x96, 0xf4, 0x0a, 0x39, 0x4b, 0xe8, - 0xe7, 0x4f, 0xb2, 0xcf, 0x1a, 0x4e, 0x4e, 0x85, 0x66, 0x68, 0x29, 0x12, 0x4a, 0xf1, 0x4e, 0x4f, 0x0a, 0x8c, 0x65, - 0x2c, 0xfc, 0x3d, 0x70, 0x4e, 0x17, 0x8a, 0xc8, 0x1d, 0x38, 0x8e, 0xaf, 0xa1, 0x82, 0xe0, 0xbf, 0x00, 0xb3, 0x18, - 0x20, 0x4f, 0x67, 0x21, 0xe1, 0x14, 0xc2, 0xc5, 0x2a, 0xeb, 0xf7, 0xe5, 0x2f, 0xea, 0xa2, 0x8b, 0x4c, 0xd6, 0x7d, - 0x12, 0x8e, 0xcc, 0x58, 0x4e, 0xbd, 0x90, 0x3c, 0xef, 0x79, 0x32, 0x4d, 0x9e, 0xe4, 0x41, 0x04, 0x90, 0xcf, 0xe1, - 0x5d, 0x98, 0x66, 0x60, 0x95, 0x26, 0xe5, 0x47, 0x28, 0x7d, 0xf1, 0x79, 0xe5, 0x07, 0x3a, 0x7b, 0x6e, 0x92, 0xe1, - 0xcd, 0xaa, 0xf5, 0x26, 0xb5, 0xae, 0x8b, 0x07, 0xfc, 0xab, 0x33, 0xd8, 0x38, 0xd7, 0x99, 0xe0, 0xc0, 0x8b, 0xa4, - 0xd6, 0x6b, 0xc6, 0x9f, 0x65, 0xb8, 0x2e, 0x55, 0x1b, 0x7d, 0x14, 0xa2, 0x73, 0xc8, 0x54, 0x80, 0x42, 0x91, 0xf6, - 0x0f, 0x4a, 0xad, 0x4c, 0x2a, 0x6d, 0x24, 0x80, 0xee, 0x61, 0xd2, 0x60, 0x8b, 0xa1, 0x8c, 0xa5, 0x49, 0x94, 0x3b, - 0x0d, 0xe2, 0xca, 0x7e, 0xac, 0x24, 0x0e, 0x2d, 0x8b, 0xe4, 0xdf, 0xbb, 0x9e, 0xbe, 0x42, 0xea, 0x4e, 0x16, 0xc8, - 0x8c, 0xf1, 0x3c, 0x8f, 0x3f, 0x01, 0x61, 0x36, 0x68, 0xa3, 0xa2, 0x10, 0x42, 0x36, 0x88, 0x41, 0xe3, 0x79, 0x1e, - 0xbf, 0x50, 0x34, 0x1e, 0xf2, 0x51, 0xe4, 0xab, 0xbf, 0x4a, 0xfd, 0x57, 0xe8, 0x33, 0x13, 0x3c, 0x42, 0x35, 0xd1, - 0xbf, 0x7b, 0x3e, 0xbb, 0x03, 0xb5, 0x61, 0x14, 0x66, 0xa6, 0xfc, 0xca, 0x37, 0xc5, 0xd9, 0xeb, 0xaf, 0xe8, 0x2a, - 0xdb, 0xba, 0x1f, 0xbd, 0x3e, 0x22, 0xb0, 0x36, 0x46, 0x57, 0xdc, 0x18, 0x40, 0x0e, 0x93, 0xf7, 0x2b, 0x4a, 0xcb, - 0x21, 0x0d, 0x42, 0x07, 0x0d, 0x41, 0xaf, 0x24, 0xfa, 0x40, 0x62, 0x11, 0x63, 0x78, 0x21, 0x9e, 0x91, 0x9a, 0x4c, - 0x34, 0xc4, 0x2b, 0x62, 0x3f, 0x44, 0x4b, 0x4e, 0x4d, 0x74, 0x23, 0x4c, 0x31, 0x90, 0xd8, 0x19, 0x24, 0x27, 0x49, - 0xad, 0xfc, 0xe2, 0x99, 0x24, 0x2c, 0xb1, 0xf3, 0x10, 0x83, 0x49, 0x2d, 0xdd, 0xe9, 0x4d, 0x95, 0xbe, 0x1c, 0x69, - 0x39, 0x68, 0x1f, 0x80, 0x5d, 0x4a, 0x7a, 0xff, 0xa4, 0x50, 0xc4, 0x87, 0x30, 0x8e, 0x21, 0x7c, 0x8b, 0xa8, 0xae, - 0xc0, 0xb9, 0x56, 0xa0, 0xb1, 0x1a, 0x78, 0x68, 0x66, 0xd5, 0x7c, 0xc8, 0xe9, 0xa7, 0xd2, 0xf2, 0xc7, 0x88, 0xc6, - 0x46, 0xeb, 0xe6, 0x70, 0xd8, 0xd3, 0xaa, 0x97, 0xce, 0x41, 0x97, 0xcd, 0x24, 0x26, 0x6e, 0x20, 0x5d, 0x3f, 0xfa, - 0xcd, 0x84, 0xbd, 0x88, 0x0a, 0xb9, 0x14, 0x82, 0x82, 0x56, 0x07, 0x02, 0x87, 0xc2, 0x5b, 0x94, 0xf9, 0x22, 0xa6, - 0x0d, 0x84, 0xc1, 0xe7, 0x07, 0xf2, 0xf3, 0x4d, 0x41, 0x2a, 0x76, 0xac, 0x6b, 0xbf, 0xbf, 0x28, 0x3d, 0xc0, 0x93, - 0x33, 0x49, 0x9e, 0x36, 0x43, 0x58, 0x11, 0x40, 0x63, 0x56, 0x93, 0xc5, 0x09, 0x57, 0xe6, 0xf0, 0x75, 0xe5, 0x95, - 0x2c, 0x65, 0xea, 0x3c, 0xd5, 0x0b, 0x20, 0xea, 0x78, 0x83, 0x56, 0xa4, 0x7e, 0x85, 0xce, 0x5e, 0xb3, 0x12, 0x32, - 0x1e, 0x9e, 0x73, 0x9e, 0x8e, 0xee, 0x59, 0xc2, 0x23, 0xfc, 0x2b, 0x99, 0xe8, 0xc3, 0xef, 0x9e, 0xc3, 0xcd, 0x38, - 0xe1, 0x91, 0xdb, 0xec, 0x7d, 0x15, 0xae, 0xe0, 0x66, 0x5a, 0x00, 0x92, 0x5b, 0x90, 0x34, 0x01, 0x25, 0x24, 0x32, - 0x21, 0xb3, 0xa6, 0xe4, 0x8b, 0x96, 0xb6, 0xc1, 0x1a, 0x26, 0x9d, 0x07, 0xbc, 0x68, 0xf5, 0xd1, 0x6a, 0xa2, 0x5d, - 0x66, 0xf9, 0x7c, 0x88, 0x33, 0x54, 0x73, 0xdc, 0x9d, 0xc1, 0xcf, 0x01, 0xaf, 0x58, 0xd5, 0xa4, 0xa3, 0xdd, 0x80, - 0x0b, 0x4f, 0xae, 0xf3, 0x74, 0xb4, 0xc5, 0x5f, 0x72, 0x7f, 0x00, 0xe8, 0x60, 0xea, 0x12, 0xf8, 0x53, 0xb5, 0xd5, - 0x54, 0xea, 0xb7, 0xd6, 0x7e, 0x5d, 0x77, 0x56, 0x2b, 0xf7, 0xac, 0xcb, 0xd0, 0x1e, 0x19, 0x72, 0xc6, 0x0c, 0xf8, - 0x73, 0xc6, 0x92, 0x3f, 0x67, 0xac, 0xf8, 0x73, 0xc6, 0x8d, 0x91, 0x01, 0x94, 0xe0, 0x5e, 0xf2, 0x67, 0x7b, 0xc4, - 0x0c, 0xb1, 0x1a, 0x54, 0x02, 0x2b, 0x4b, 0x39, 0xf7, 0x91, 0x53, 0x4c, 0x39, 0x65, 0x78, 0xe9, 0x74, 0xe6, 0x0e, - 0xe4, 0x3c, 0x98, 0xb9, 0xc3, 0x64, 0xaf, 0xcf, 0x8d, 0x38, 0x96, 0xc6, 0xa4, 0xa8, 0x20, 0x9d, 0xd3, 0xe1, 0xe6, - 0xd5, 0x71, 0x9e, 0xb0, 0x8c, 0x8f, 0xdb, 0x67, 0x0a, 0x84, 0xd8, 0xe2, 0x19, 0x12, 0x29, 0x55, 0xb3, 0xdc, 0xe6, - 0x0f, 0x87, 0x7a, 0x74, 0xaf, 0x77, 0x7a, 0xf8, 0x95, 0xb0, 0xdf, 0x32, 0xcf, 0x3e, 0x41, 0x00, 0x93, 0x44, 0x9e, - 0x49, 0x38, 0xfa, 0xb1, 0x1c, 0xfd, 0x4d, 0xc3, 0xbf, 0x64, 0xa8, 0xee, 0x0e, 0x81, 0x89, 0x2d, 0x3b, 0x70, 0x08, - 0x4e, 0x57, 0x95, 0x48, 0xc0, 0xc1, 0x66, 0xc3, 0x22, 0xbd, 0xc7, 0x43, 0x9c, 0x0f, 0x0a, 0x1f, 0xa1, 0x61, 0x46, - 0xef, 0xf7, 0x37, 0xc2, 0xab, 0x64, 0x2b, 0x0f, 0x87, 0xc4, 0xba, 0x0b, 0x3b, 0xfa, 0x38, 0xda, 0xa3, 0x84, 0xda, - 0x8f, 0x6a, 0xbd, 0xa9, 0xd4, 0x83, 0xdc, 0xec, 0x42, 0x62, 0x50, 0xb1, 0x54, 0x9f, 0x5e, 0xa9, 0x3e, 0xd4, 0xac, - 0xf3, 0xbb, 0x3a, 0xee, 0x53, 0x31, 0x5a, 0xcb, 0x09, 0x01, 0xae, 0x83, 0x44, 0xa3, 0x03, 0x60, 0x9c, 0x6d, 0xb6, - 0xbc, 0xd4, 0xd6, 0x89, 0xd2, 0x71, 0x9c, 0xeb, 0xe3, 0xf8, 0x70, 0x90, 0x62, 0xc6, 0xe5, 0x91, 0x98, 0x71, 0xd9, - 0x00, 0xbc, 0x59, 0xe7, 0x41, 0x7d, 0x38, 0x5c, 0xd2, 0xa5, 0xc8, 0x74, 0xb6, 0x51, 0x7e, 0xd6, 0xa3, 0xfb, 0x27, - 0x09, 0x9a, 0x7b, 0x2b, 0xec, 0xbd, 0x48, 0xb6, 0x67, 0xb2, 0x4e, 0xbd, 0x8c, 0x7c, 0x7a, 0xe1, 0x9e, 0x5d, 0x72, - 0xf5, 0xc3, 0xea, 0xeb, 0xe9, 0x67, 0xe1, 0x45, 0xac, 0xa2, 0xdd, 0xba, 0x64, 0xc2, 0xde, 0x52, 0x2a, 0x69, 0x95, - 0x97, 0x4f, 0x37, 0x7e, 0x80, 0x99, 0x69, 0x4f, 0x1f, 0x64, 0x23, 0xaa, 0x3f, 0x2b, 0x51, 0x2b, 0xc3, 0x64, 0xe1, - 0xbc, 0x64, 0xea, 0xc9, 0x80, 0xc7, 0xac, 0xe4, 0x91, 0xec, 0xf4, 0xc6, 0x20, 0x08, 0x60, 0x9d, 0x93, 0x56, 0x9d, - 0x71, 0x34, 0x5a, 0x55, 0x2e, 0x4e, 0x57, 0xb9, 0xc0, 0x70, 0xbb, 0x35, 0xdb, 0xa8, 0x3a, 0xcb, 0x4d, 0xad, 0x52, - 0xbe, 0x03, 0xf8, 0x58, 0x56, 0xb9, 0xa0, 0x63, 0xca, 0xd4, 0x79, 0x03, 0xc1, 0xd8, 0xaa, 0xc6, 0x85, 0x53, 0xe3, - 0x82, 0x47, 0xd4, 0xee, 0xa6, 0xa9, 0x47, 0x5b, 0x60, 0x29, 0x1d, 0xed, 0x78, 0x89, 0x2a, 0x85, 0x9f, 0x05, 0xdf, - 0x87, 0x71, 0xfc, 0xa2, 0xd8, 0xaa, 0x03, 0xf1, 0xb6, 0xd8, 0x22, 0xed, 0x8b, 0xfc, 0x0b, 0x71, 0xc0, 0x6b, 0x5d, - 0x53, 0x5e, 0x5b, 0x73, 0x1a, 0xd8, 0x1a, 0x46, 0x4a, 0x0a, 0xe7, 0xe6, 0xcf, 0xc3, 0x81, 0x56, 0x76, 0xad, 0xee, - 0x0a, 0xb5, 0x1e, 0x73, 0xd8, 0xb0, 0x6f, 0xb2, 0x70, 0x27, 0x4a, 0x70, 0xe4, 0x92, 0x7f, 0x1d, 0x0e, 0x5a, 0x65, - 0xa9, 0x8e, 0xf4, 0xd9, 0xfe, 0x6b, 0x30, 0x66, 0xe8, 0xd2, 0x04, 0x2c, 0x1b, 0x23, 0xf9, 0x57, 0xd3, 0xcc, 0x1b, - 0x26, 0x6b, 0xa6, 0x70, 0x1c, 0x1a, 0x46, 0x48, 0x03, 0xba, 0x0d, 0x6a, 0xc3, 0x93, 0xf9, 0xa6, 0x2a, 0xbf, 0xba, - 0x23, 0xd5, 0x7e, 0x30, 0xbc, 0x9c, 0x88, 0x73, 0xba, 0x24, 0xa9, 0xa7, 0x12, 0x4a, 0x42, 0xb0, 0x4b, 0x1f, 0xc8, - 0x89, 0x15, 0x90, 0xb5, 0x8c, 0xe5, 0xb7, 0x7a, 0x40, 0xe8, 0x3f, 0xed, 0xd6, 0x0b, 0xfd, 0xa7, 0x69, 0xb6, 0x50, - 0xd7, 0x1f, 0x26, 0xf7, 0x1d, 0xbd, 0xfe, 0xe0, 0xf0, 0x4e, 0x5d, 0x55, 0x5c, 0xc5, 0xa3, 0xda, 0x30, 0xc9, 0x8d, - 0xb2, 0x70, 0x57, 0x6c, 0x6a, 0xb5, 0x3c, 0x1d, 0x87, 0x11, 0x98, 0x11, 0x14, 0x20, 0xeb, 0xba, 0x8d, 0x88, 0x61, - 0x25, 0x97, 0x09, 0xf9, 0x84, 0x80, 0x2c, 0x4a, 0x8d, 0xf3, 0x71, 0x0b, 0x54, 0x22, 0x18, 0x9c, 0x86, 0xd6, 0xaa, - 0x9b, 0xfc, 0xa4, 0xb2, 0xb1, 0x25, 0x90, 0x43, 0x92, 0xc9, 0x62, 0x39, 0xba, 0x15, 0x8b, 0xa2, 0x14, 0xbf, 0x60, - 0x3d, 0x5c, 0xb3, 0x85, 0xfb, 0x0c, 0x08, 0xed, 0x27, 0x4a, 0x7b, 0x13, 0x69, 0x82, 0xee, 0x25, 0x5b, 0x01, 0xc8, - 0x00, 0x8a, 0xba, 0xda, 0xad, 0xcf, 0xf9, 0x39, 0x92, 0x66, 0x38, 0x8c, 0x6e, 0x9f, 0x2e, 0x83, 0xe5, 0xe0, 0x12, - 0xb5, 0xd2, 0x97, 0x2c, 0x6e, 0x61, 0x50, 0xed, 0xcd, 0x12, 0x0e, 0x6a, 0x66, 0xad, 0x8d, 0x40, 0x30, 0xd9, 0x43, - 0x41, 0xc5, 0x5c, 0xc1, 0x3e, 0x28, 0x58, 0x4b, 0x5e, 0x07, 0x87, 0x5b, 0xfb, 0xb2, 0x52, 0x5c, 0x3c, 0xbd, 0x48, - 0x5a, 0x17, 0x96, 0xf2, 0xe2, 0x69, 0x03, 0x06, 0x97, 0x23, 0x6c, 0x2a, 0x30, 0x49, 0x00, 0xe8, 0x56, 0x44, 0x11, - 0x2f, 0x4a, 0x61, 0xdb, 0xca, 0x67, 0x4e, 0xd8, 0x60, 0xc3, 0xee, 0xe1, 0x5e, 0x19, 0x94, 0x0c, 0x2e, 0xc4, 0xb8, - 0xdd, 0xec, 0x02, 0x5c, 0xc1, 0x50, 0x18, 0x5b, 0xf3, 0x77, 0x99, 0x17, 0x29, 0x01, 0x37, 0x43, 0x94, 0xaf, 0x0d, - 0x9c, 0x4c, 0x7a, 0x72, 0x2d, 0x58, 0x0c, 0x58, 0xd0, 0xe0, 0x3b, 0x6a, 0xfd, 0x9d, 0xc9, 0xbf, 0xf1, 0xf4, 0xd0, - 0x0f, 0x5e, 0x64, 0xde, 0xc2, 0x67, 0xef, 0x2a, 0x19, 0xad, 0x49, 0xa2, 0xbc, 0x7a, 0xb8, 0x00, 0xb9, 0x61, 0x31, - 0xba, 0x67, 0x0b, 0x10, 0x27, 0x16, 0xa3, 0x84, 0x32, 0xba, 0xc2, 0xbd, 0xca, 0x6c, 0x99, 0x08, 0xa4, 0x38, 0xb0, - 0x90, 0x72, 0x6f, 0xb1, 0x0e, 0x16, 0xb8, 0x3f, 0x91, 0x5c, 0x40, 0xc9, 0x03, 0x28, 0x57, 0x0a, 0x08, 0xf8, 0x74, - 0x00, 0xe5, 0x4b, 0x79, 0x11, 0xfe, 0xc4, 0x89, 0x1a, 0x2c, 0x46, 0xf7, 0x0d, 0xfb, 0xc9, 0x0b, 0x2d, 0xfb, 0xc3, - 0x52, 0x6b, 0x1a, 0x56, 0x7c, 0x09, 0xd3, 0x62, 0xe2, 0xf6, 0xe5, 0xca, 0xae, 0x8a, 0xcf, 0x56, 0xea, 0xec, 0xa6, - 0x86, 0x24, 0xec, 0x1b, 0xb2, 0x0a, 0x70, 0xb0, 0x2a, 0xe2, 0x9e, 0x75, 0xb9, 0x0f, 0xa3, 0xbf, 0x36, 0x69, 0x29, - 0x2c, 0x54, 0x49, 0x7f, 0xdf, 0x94, 0x02, 0xa9, 0x4c, 0x74, 0xa2, 0x85, 0xe0, 0x0a, 0x0c, 0x02, 0x77, 0x22, 0xaf, - 0x01, 0x30, 0x06, 0x5c, 0x0a, 0x94, 0x65, 0x5b, 0x42, 0x48, 0x75, 0x3f, 0x03, 0xb5, 0x9d, 0xb8, 0x4b, 0x23, 0xb2, - 0x16, 0xa2, 0xaf, 0x82, 0x31, 0x73, 0x5e, 0x4a, 0xb7, 0xd8, 0x74, 0xb5, 0x59, 0x5d, 0xa3, 0x73, 0x69, 0xcb, 0xcd, - 0x4f, 0xd8, 0x62, 0xad, 0x40, 0xd9, 0x84, 0xa4, 0xed, 0x9c, 0xe7, 0x28, 0x9b, 0xd0, 0xd2, 0xde, 0x53, 0x8f, 0x0a, - 0xd5, 0xc9, 0xd6, 0x4b, 0xd5, 0xd4, 0x22, 0xac, 0x16, 0x17, 0x95, 0x1f, 0x80, 0x6e, 0x2a, 0xad, 0x9e, 0xd7, 0x35, - 0x9a, 0x42, 0xad, 0x16, 0x8e, 0x1b, 0xed, 0x6c, 0xba, 0x48, 0x97, 0x88, 0xb3, 0x2a, 0xed, 0xd0, 0x3f, 0x65, 0xda, - 0xf5, 0xb2, 0xa3, 0xdf, 0x8c, 0xab, 0x0b, 0x5c, 0x88, 0x0d, 0xf8, 0x9c, 0xfb, 0xcb, 0xeb, 0x3d, 0x8d, 0x7b, 0xfe, - 0xe1, 0x80, 0xec, 0x49, 0xed, 0x0f, 0xd5, 0xc7, 0xae, 0x60, 0xc8, 0xc2, 0x28, 0xf5, 0x17, 0x29, 0xef, 0x3d, 0xc2, - 0x71, 0xff, 0x52, 0xf5, 0xd8, 0xaf, 0x19, 0xdf, 0xd7, 0xc5, 0x26, 0x4a, 0x28, 0xaa, 0xa1, 0xb7, 0x2a, 0x36, 0x95, - 0x88, 0x8b, 0xfb, 0xbc, 0xc7, 0x30, 0x19, 0xc6, 0x42, 0xa6, 0xc2, 0x9f, 0x32, 0x15, 0x3c, 0x42, 0x28, 0x71, 0xb3, - 0xee, 0x91, 0x76, 0x13, 0xe2, 0x94, 0x6a, 0x51, 0xca, 0x64, 0xfc, 0x5b, 0x3f, 0x81, 0xf2, 0x9c, 0xa2, 0x65, 0xfa, - 0x51, 0xe1, 0x32, 0x7d, 0xb3, 0x3e, 0x2e, 0x3d, 0x13, 0xa1, 0xce, 0x5c, 0x6c, 0x6a, 0x9d, 0x8e, 0xb1, 0x53, 0x3a, - 0xb5, 0x61, 0x5f, 0x2b, 0xc5, 0x65, 0x45, 0xe1, 0xdf, 0x48, 0x64, 0xd5, 0x33, 0xe2, 0xf8, 0x3f, 0xb3, 0xf6, 0x19, - 0x56, 0x81, 0x5f, 0x06, 0xf2, 0x7e, 0x01, 0xf0, 0x71, 0x5d, 0x97, 0xe9, 0xed, 0x06, 0x68, 0x43, 0x68, 0xf8, 0x7b, - 0x3e, 0x32, 0x60, 0xba, 0x8f, 0x70, 0x86, 0xf4, 0x50, 0xe7, 0x9c, 0xce, 0xca, 0x74, 0xce, 0x55, 0x58, 0x4b, 0xb0, - 0x97, 0x93, 0x26, 0x97, 0xeb, 0x12, 0xd4, 0x4c, 0xe0, 0xf6, 0xa1, 0x3d, 0x22, 0x84, 0xda, 0x94, 0xd5, 0xf4, 0x12, - 0x6a, 0xde, 0xc9, 0x69, 0x47, 0x93, 0x12, 0x5c, 0x35, 0x74, 0x56, 0xae, 0xff, 0x3a, 0x1c, 0x7a, 0xb7, 0x59, 0x11, - 0xfd, 0xd9, 0x43, 0x7f, 0xc7, 0xed, 0x75, 0xfa, 0x15, 0xa2, 0x65, 0xac, 0xbf, 0x21, 0x03, 0x3a, 0x9e, 0x0c, 0x6f, - 0x8b, 0x6d, 0x8f, 0x7d, 0x45, 0x0d, 0x96, 0xbe, 0x7e, 0x5c, 0x83, 0x84, 0xaa, 0x6b, 0x5f, 0x58, 0x3c, 0x61, 0x9e, - 0x12, 0x6d, 0x0b, 0x1f, 0xc2, 0x42, 0xbf, 0x42, 0x64, 0x24, 0x84, 0x9b, 0xca, 0xee, 0x51, 0xd2, 0x2e, 0xf4, 0xa5, - 0xaf, 0x65, 0x5f, 0xf9, 0xce, 0x05, 0xc0, 0xca, 0x3e, 0xb5, 0xe1, 0x9e, 0xf4, 0xa7, 0x54, 0x1f, 0xb6, 0xbf, 0x25, - 0x0b, 0x28, 0xb4, 0xb0, 0x9e, 0xca, 0xd9, 0xb9, 0x2c, 0x79, 0x9e, 0x4d, 0xf7, 0x6b, 0xd8, 0xa3, 0xee, 0xd0, 0x6b, - 0x2a, 0x38, 0xbf, 0x34, 0xa3, 0xf7, 0xbb, 0xa1, 0x50, 0x1d, 0x75, 0xee, 0x20, 0xcb, 0xd2, 0xba, 0xe4, 0xfc, 0x65, - 0xe5, 0x8e, 0xc2, 0xfc, 0x2e, 0x04, 0xcf, 0xb0, 0xee, 0xdd, 0xc5, 0x79, 0xef, 0x73, 0x6b, 0x8e, 0xfc, 0x9a, 0xcd, - 0x52, 0xc4, 0x22, 0x99, 0x83, 0xd5, 0x0f, 0xfd, 0x3c, 0xf6, 0xdb, 0x20, 0x87, 0xe3, 0xa6, 0x01, 0x1d, 0x36, 0x64, - 0xd6, 0xbe, 0x44, 0xe0, 0x54, 0x23, 0x48, 0x53, 0x13, 0xd4, 0x2c, 0x0f, 0x91, 0xd8, 0x2e, 0x65, 0xdb, 0x20, 0xd7, - 0x5d, 0x30, 0xcd, 0x91, 0xf6, 0x0c, 0xde, 0x37, 0x69, 0x92, 0x0a, 0xcd, 0xa2, 0x8b, 0x95, 0x8c, 0x7f, 0x47, 0xda, - 0x4c, 0xc9, 0x1e, 0x5b, 0x03, 0xef, 0x25, 0x28, 0x27, 0xc3, 0x14, 0xc3, 0x77, 0x7c, 0xbd, 0xf3, 0xe8, 0x22, 0x7e, - 0x3e, 0x66, 0x9b, 0x94, 0x1d, 0xc1, 0x24, 0xd9, 0xf8, 0x86, 0xe2, 0x0d, 0xdf, 0xdf, 0x56, 0xa2, 0x04, 0xd0, 0xcb, - 0x82, 0x3f, 0x93, 0x36, 0x57, 0xe8, 0x76, 0xf7, 0x8e, 0x52, 0xf8, 0x25, 0x2f, 0x0f, 0x87, 0x6d, 0xea, 0x85, 0xd0, - 0xf9, 0x22, 0x7e, 0x07, 0xe6, 0x30, 0x86, 0xd8, 0x8c, 0x00, 0x61, 0x8e, 0x0f, 0xa8, 0x83, 0xf5, 0x23, 0x00, 0x8d, - 0x13, 0x28, 0xc0, 0xe8, 0xab, 0x6d, 0x41, 0xdf, 0xf2, 0xe2, 0x22, 0x42, 0xd4, 0x28, 0xc0, 0x44, 0x49, 0xb3, 0x18, - 0x86, 0x03, 0x9d, 0xdf, 0x37, 0xb7, 0x75, 0x29, 0x70, 0xe8, 0x1d, 0xcb, 0xf0, 0xdf, 0xfe, 0xc7, 0xda, 0xd2, 0xaa, - 0xb2, 0xdd, 0x1a, 0xa7, 0x99, 0xff, 0xed, 0xb6, 0xd0, 0xf7, 0x5f, 0x0a, 0xc5, 0xf3, 0x8e, 0xd7, 0xed, 0x2f, 0x10, - 0xbd, 0xaf, 0x5b, 0xb9, 0x2a, 0xb5, 0x1b, 0x66, 0xca, 0xef, 0xd3, 0x3c, 0x2e, 0xee, 0x47, 0x71, 0xeb, 0xc8, 0x9b, - 0xa4, 0xe7, 0x9c, 0x7f, 0xa9, 0xfa, 0x7d, 0xef, 0x0b, 0x90, 0xf1, 0xbe, 0x14, 0xc6, 0x11, 0x93, 0x38, 0xf8, 0xf6, - 0x62, 0x14, 0x6d, 0x4a, 0xd8, 0x90, 0xdb, 0xa7, 0x25, 0x68, 0x66, 0xfa, 0x7d, 0x94, 0x28, 0xad, 0xf9, 0xfe, 0x0f, - 0x39, 0xdf, 0x5f, 0x0a, 0x79, 0xb3, 0x92, 0x1f, 0x3e, 0x5a, 0x61, 0xe0, 0x7b, 0x9c, 0x7e, 0x15, 0x3d, 0xb6, 0x2a, - 0x7d, 0xf8, 0xae, 0xb4, 0xf4, 0x59, 0x45, 0xfd, 0x0b, 0x15, 0x35, 0x2f, 0xc5, 0x88, 0x88, 0x07, 0x41, 0x3b, 0xdb, - 0x2e, 0xb5, 0x6b, 0x09, 0xda, 0x05, 0x9b, 0xc2, 0xfe, 0xfe, 0xe0, 0x90, 0xf7, 0xfb, 0x1f, 0x73, 0xaf, 0xc5, 0xeb, - 0x6e, 0xe0, 0x2e, 0x4b, 0x0f, 0x21, 0x80, 0xb5, 0x0c, 0x94, 0x71, 0x84, 0x49, 0x17, 0x79, 0x8d, 0xb2, 0xe9, 0x44, - 0xe0, 0x63, 0x96, 0x5d, 0x39, 0xc9, 0x34, 0xc0, 0x8c, 0x6a, 0x0a, 0x33, 0x01, 0x46, 0xea, 0x23, 0xd6, 0x4d, 0x4f, - 0xab, 0xd0, 0xf2, 0x35, 0x04, 0xeb, 0x22, 0xcb, 0x38, 0x8a, 0x99, 0x00, 0x60, 0xf3, 0x11, 0xe4, 0x2b, 0xba, 0x3a, - 0x24, 0xad, 0x54, 0x79, 0xbf, 0xce, 0x88, 0x8c, 0x26, 0x21, 0x9a, 0xdf, 0xc2, 0x03, 0xfb, 0xb6, 0x99, 0x51, 0xa5, - 0x9e, 0x51, 0x95, 0xcf, 0x70, 0x58, 0x0a, 0xc7, 0x88, 0xff, 0x73, 0xaa, 0x7a, 0x44, 0xa0, 0x57, 0x65, 0x5a, 0x45, - 0x45, 0x9e, 0x8b, 0x08, 0x11, 0xaa, 0xa5, 0x73, 0x38, 0xf4, 0x63, 0xbf, 0x8f, 0x03, 0x61, 0x5e, 0xac, 0x93, 0x07, - 0xba, 0xb2, 0xa6, 0xb5, 0x92, 0x02, 0xa7, 0xa2, 0x46, 0x88, 0x10, 0xde, 0x67, 0xe0, 0x59, 0x4d, 0x7d, 0xbf, 0xb1, - 0x4c, 0x74, 0xbf, 0x67, 0x40, 0xf9, 0x03, 0xf2, 0x75, 0x25, 0xc5, 0x19, 0x91, 0x3c, 0x24, 0xce, 0x38, 0x00, 0x31, - 0xdf, 0x96, 0x68, 0x34, 0xf6, 0x3f, 0x20, 0xc1, 0x50, 0xfd, 0x60, 0xa7, 0x9b, 0x7a, 0xff, 0xcc, 0x24, 0x8e, 0xa2, - 0x4f, 0xdb, 0xe4, 0xb1, 0x64, 0x69, 0xb4, 0x70, 0xf4, 0x1e, 0x31, 0x8c, 0xc3, 0xe9, 0x7c, 0x4c, 0xb2, 0x8d, 0xc9, - 0x2a, 0x80, 0x74, 0x32, 0x53, 0xc7, 0x94, 0x3a, 0x1a, 0xe7, 0x7a, 0x41, 0x15, 0x7a, 0xac, 0x4b, 0x9e, 0x83, 0xf5, - 0xe4, 0x47, 0xaf, 0xf4, 0xa7, 0x42, 0xce, 0x61, 0x23, 0x11, 0x14, 0x7e, 0x80, 0xab, 0xc1, 0x4a, 0x01, 0x83, 0xa9, - 0x6f, 0xe1, 0x6b, 0xe2, 0x39, 0x0a, 0x1e, 0x85, 0x5d, 0x8c, 0xad, 0x95, 0xef, 0x7c, 0x52, 0x50, 0xee, 0x59, 0x31, - 0xe7, 0x15, 0x70, 0x2e, 0x83, 0x42, 0x98, 0x8e, 0x67, 0xf9, 0x3f, 0x93, 0xbc, 0x9e, 0xd8, 0x10, 0x20, 0x83, 0x3f, - 0x25, 0x4e, 0x4b, 0x77, 0xe8, 0xce, 0x43, 0xcf, 0x22, 0x0e, 0x1b, 0x3d, 0x5a, 0x97, 0xc5, 0x36, 0x45, 0xbd, 0x84, - 0xf9, 0x81, 0xfc, 0xbc, 0x25, 0xdf, 0x87, 0x28, 0xde, 0x06, 0x3f, 0x67, 0x2c, 0x16, 0xf8, 0xd7, 0xdf, 0x32, 0x46, - 0x13, 0x2d, 0xf8, 0x7b, 0xd6, 0x20, 0x51, 0x31, 0x60, 0x45, 0x00, 0x97, 0xa9, 0xfa, 0xf0, 0x29, 0x31, 0xde, 0x9a, - 0x0d, 0x0f, 0x7c, 0xb3, 0x02, 0x9d, 0xfa, 0xdc, 0x5d, 0xd9, 0x9e, 0xae, 0x46, 0xaa, 0xaa, 0xf1, 0x73, 0xaa, 0xaa, - 0xf1, 0x73, 0x4a, 0xd5, 0xf8, 0x2b, 0xa3, 0xf8, 0x9d, 0xca, 0x67, 0xc8, 0x9c, 0x6c, 0x62, 0x92, 0x4e, 0xdf, 0x1b, - 0x4e, 0xec, 0xb2, 0xdf, 0xba, 0x4d, 0xa4, 0x99, 0x89, 0x14, 0x72, 0x6f, 0x00, 0x6a, 0x26, 0x7e, 0xcc, 0x0d, 0xa7, - 0xc4, 0xf9, 0xb9, 0x87, 0x2b, 0x36, 0xad, 0x5e, 0xd2, 0x82, 0x05, 0x36, 0x2f, 0xb3, 0x3c, 0xd3, 0x04, 0xb6, 0x4d, - 0x99, 0xf5, 0x97, 0xdc, 0x03, 0x08, 0x66, 0x52, 0x13, 0x00, 0xd2, 0x42, 0x54, 0x0a, 0x91, 0xbf, 0xc4, 0x59, 0x7d, - 0xce, 0x7b, 0x9b, 0x3c, 0x26, 0xd2, 0xea, 0x5e, 0xbf, 0x9f, 0x9e, 0xa5, 0x39, 0x05, 0x35, 0x1c, 0x67, 0x9d, 0xfe, - 0x94, 0x05, 0x22, 0x91, 0xab, 0xf4, 0x1f, 0x6e, 0x90, 0x97, 0xf1, 0x7d, 0xdd, 0xf6, 0xfc, 0x89, 0xfa, 0x7b, 0x67, - 0xfd, 0x6d, 0x81, 0xe0, 0x4e, 0x8e, 0xfd, 0x64, 0x55, 0xca, 0x23, 0xe3, 0xd2, 0xde, 0xf3, 0x9b, 0xba, 0x28, 0xb2, - 0x3a, 0x5d, 0x7f, 0x90, 0x7a, 0x1a, 0xdd, 0x17, 0x7b, 0x30, 0x06, 0xef, 0x00, 0xf0, 0x4c, 0x87, 0x06, 0x48, 0xdf, - 0x33, 0xf2, 0x70, 0x9f, 0x5b, 0xf2, 0x93, 0xca, 0xda, 0x24, 0x61, 0x45, 0xb1, 0x19, 0xc6, 0x08, 0x25, 0xe3, 0x34, - 0xb6, 0x7e, 0xbf, 0xaf, 0xfe, 0xde, 0x61, 0x14, 0x15, 0x15, 0x77, 0x8c, 0x46, 0x65, 0x55, 0x8f, 0xb6, 0x83, 0xc3, - 0xe1, 0x3c, 0xb7, 0x71, 0xb4, 0xf5, 0x0a, 0xd8, 0x5b, 0xa1, 0x52, 0xf6, 0x4a, 0x84, 0xe5, 0x87, 0x2b, 0xbf, 0xdf, - 0x87, 0x7f, 0x65, 0xa4, 0x85, 0xe7, 0x4f, 0xf1, 0xd7, 0x4d, 0x5d, 0x60, 0x78, 0x06, 0xad, 0xd1, 0x0a, 0x82, 0x09, - 0xfe, 0xd1, 0x81, 0x7a, 0x69, 0xa5, 0x7d, 0x04, 0xdd, 0x0a, 0xf4, 0xa0, 0xb1, 0x0f, 0x24, 0xed, 0x0b, 0x89, 0xba, - 0xbd, 0xd5, 0x69, 0xf4, 0x67, 0xc5, 0x72, 0x5e, 0xc1, 0xe4, 0x70, 0x43, 0x9f, 0x56, 0xe1, 0xf6, 0x13, 0x3c, 0xfd, - 0x05, 0x28, 0xb7, 0x0e, 0x87, 0x1c, 0xc4, 0x16, 0x70, 0xf3, 0x58, 0x85, 0x5f, 0x8a, 0x52, 0x46, 0xd4, 0xc7, 0xd3, - 0x12, 0xb4, 0x77, 0x01, 0x3a, 0x60, 0x69, 0x10, 0xaf, 0x90, 0x3c, 0x67, 0x23, 0x80, 0x65, 0x07, 0x96, 0xb3, 0x8c, - 0x53, 0x98, 0x67, 0xf9, 0xac, 0xd2, 0xf8, 0xec, 0x89, 0x57, 0xb3, 0x0c, 0x9c, 0x05, 0x2e, 0x2a, 0x9f, 0x65, 0x5a, - 0xf5, 0x54, 0x24, 0xe8, 0xf3, 0x4a, 0x4e, 0x70, 0x25, 0x38, 0xd9, 0x80, 0xfc, 0x02, 0x24, 0x69, 0x4a, 0x59, 0x53, - 0x3e, 0xbb, 0xa4, 0x1b, 0x32, 0x7a, 0xce, 0x7b, 0x5e, 0x34, 0x0c, 0xfd, 0x0b, 0xaf, 0x84, 0xf0, 0x4d, 0xdc, 0xb6, - 0x51, 0x0a, 0xfb, 0x9b, 0xc0, 0xe2, 0x13, 0xf6, 0xa3, 0xb7, 0xf0, 0xa7, 0xe3, 0x20, 0x1c, 0x22, 0x37, 0x54, 0xcc, - 0x81, 0x3d, 0x0d, 0x58, 0x6c, 0xe2, 0xab, 0xcd, 0x24, 0x1e, 0x0c, 0x7c, 0x9d, 0xb1, 0x98, 0xc5, 0x40, 0x83, 0x1c, - 0x0f, 0x2e, 0xe7, 0xfa, 0x84, 0xd0, 0x0f, 0x23, 0x2a, 0x47, 0x05, 0x3a, 0x07, 0xd1, 0x60, 0x01, 0x78, 0xea, 0xad, - 0x6c, 0x90, 0x64, 0x68, 0xa0, 0x13, 0xd7, 0x9a, 0xa4, 0x3a, 0x9c, 0xd0, 0x3a, 0xd0, 0x71, 0xf5, 0x06, 0x3a, 0x1f, - 0xd7, 0xbd, 0x8f, 0x57, 0xc3, 0x1b, 0x2a, 0xfd, 0x42, 0x0c, 0xbc, 0x7a, 0x3a, 0x0e, 0x2e, 0xe9, 0x56, 0x78, 0xb3, - 0x0a, 0xb7, 0xbf, 0xc8, 0x07, 0x8e, 0x3b, 0x2a, 0x69, 0x08, 0x0c, 0xde, 0x1e, 0xba, 0x9b, 0x19, 0xc7, 0x94, 0xa3, - 0xc3, 0x38, 0x92, 0x43, 0xac, 0x5a, 0x71, 0x21, 0xbd, 0x11, 0x7c, 0xbb, 0x50, 0x8c, 0x65, 0x63, 0x97, 0x86, 0xa2, - 0xf0, 0x67, 0x00, 0x3b, 0xd4, 0xfe, 0x4a, 0x25, 0x1f, 0x23, 0xa3, 0x9a, 0x06, 0x3a, 0x06, 0x60, 0xc9, 0xd2, 0x44, - 0x52, 0x45, 0x1a, 0x89, 0x3f, 0x32, 0x63, 0x1d, 0x35, 0x5d, 0x5f, 0xb0, 0x1c, 0x59, 0x92, 0x6e, 0x67, 0x12, 0xcb, - 0x89, 0x24, 0xb5, 0xdd, 0x47, 0xc4, 0x60, 0xe0, 0x83, 0x8d, 0x98, 0x66, 0x22, 0x1c, 0xf1, 0xa8, 0x44, 0x16, 0x5d, - 0x7e, 0x1b, 0x61, 0xd2, 0xf6, 0x65, 0x45, 0xb6, 0x20, 0x98, 0x9e, 0x44, 0x1f, 0x24, 0x29, 0xa7, 0x22, 0x91, 0x66, - 0x84, 0x00, 0x3f, 0x9e, 0x94, 0x57, 0xfa, 0x73, 0xd0, 0xb4, 0x12, 0xbc, 0x64, 0x90, 0x3c, 0x12, 0x3f, 0x93, 0x82, - 0x59, 0x8c, 0x55, 0x83, 0x01, 0x96, 0x53, 0x3d, 0x71, 0x4c, 0xd2, 0x7f, 0xeb, 0x74, 0xc2, 0x7e, 0xee, 0xe5, 0xb6, - 0x96, 0x37, 0xcd, 0xbd, 0xe7, 0x5e, 0xc5, 0x52, 0x0d, 0xcb, 0xa0, 0xff, 0x9a, 0x68, 0x17, 0x6c, 0x6d, 0x19, 0x13, - 0x56, 0xfd, 0x00, 0xd2, 0x1e, 0xe9, 0xf2, 0xaa, 0x61, 0xce, 0x04, 0x8f, 0x2e, 0xac, 0x79, 0x10, 0x5d, 0x08, 0x1f, - 0xb9, 0xec, 0x26, 0xc9, 0xd5, 0x78, 0xe2, 0x87, 0x83, 0x81, 0x02, 0xa0, 0xa5, 0x75, 0x52, 0x0c, 0xc2, 0x27, 0x42, - 0x0e, 0xa4, 0xd1, 0x51, 0x15, 0x60, 0xb1, 0xcc, 0xae, 0xca, 0x49, 0x36, 0x18, 0xf8, 0x20, 0x36, 0x26, 0x76, 0x43, - 0xb3, 0xb9, 0xcf, 0x4e, 0x14, 0x64, 0xb5, 0x39, 0x6a, 0xcd, 0x74, 0x0b, 0x0c, 0x00, 0x06, 0x11, 0xc1, 0x72, 0x9f, - 0x1a, 0xf9, 0x88, 0x3a, 0x3d, 0x85, 0x11, 0x10, 0xfc, 0x72, 0x22, 0x10, 0xb9, 0x48, 0xa0, 0x1e, 0x60, 0x26, 0xc0, - 0x8c, 0x2a, 0x86, 0x97, 0xc0, 0x2e, 0x9e, 0x9b, 0x57, 0x0c, 0xfa, 0x17, 0x89, 0xd9, 0x89, 0xa6, 0x12, 0x47, 0x63, - 0xe4, 0x54, 0x1a, 0x23, 0x03, 0x62, 0x17, 0xc7, 0xbf, 0xa7, 0xf4, 0x28, 0x48, 0xd9, 0x8b, 0xca, 0x10, 0x87, 0xa3, - 0xf8, 0x0a, 0x56, 0x8d, 0xc3, 0xa1, 0x36, 0xaf, 0xa7, 0xb3, 0x7a, 0x3e, 0x10, 0x01, 0xfc, 0x37, 0x14, 0xec, 0x37, - 0x4d, 0x45, 0x6e, 0x90, 0x3a, 0x0f, 0x87, 0x14, 0xe4, 0x53, 0xdd, 0xe4, 0x9f, 0x2a, 0x77, 0x3f, 0x9d, 0xcd, 0xad, - 0x39, 0x7a, 0x51, 0xe3, 0xba, 0xb5, 0xba, 0xa1, 0x90, 0x68, 0x4d, 0x93, 0xe2, 0xaa, 0x9a, 0x14, 0x03, 0x9e, 0xfb, - 0x42, 0x75, 0xb1, 0x35, 0x82, 0x85, 0x3f, 0xb7, 0x40, 0x98, 0xf4, 0xb7, 0x92, 0x0e, 0xa9, 0x1a, 0x77, 0x6d, 0xb5, - 0xdb, 0x56, 0x36, 0xa4, 0x68, 0x3e, 0xbc, 0x84, 0x5d, 0x3a, 0x45, 0xb4, 0xed, 0x92, 0xe0, 0x0b, 0xd0, 0xb2, 0x7a, - 0x23, 0xf2, 0x98, 0x7e, 0x85, 0xfc, 0x52, 0x0c, 0xff, 0x53, 0xba, 0x37, 0xa7, 0x36, 0xc8, 0x01, 0x6c, 0xf7, 0x1e, - 0x6e, 0xc7, 0xe8, 0x81, 0x0c, 0xde, 0x08, 0x39, 0xe7, 0xfc, 0x72, 0x6a, 0xcd, 0x98, 0x68, 0x58, 0xb0, 0x72, 0x18, - 0xf9, 0x01, 0x32, 0x5e, 0x4e, 0x81, 0x95, 0xfd, 0xa8, 0x88, 0x4b, 0x7f, 0x18, 0xf9, 0x17, 0x4f, 0x83, 0x8c, 0x7b, - 0xd1, 0xb0, 0xe3, 0x0b, 0xb0, 0x57, 0x5f, 0x3c, 0x65, 0xd1, 0x80, 0x57, 0x57, 0xf5, 0x34, 0x0b, 0x86, 0x19, 0x8b, - 0xae, 0x8a, 0x21, 0xf8, 0xd0, 0x3e, 0x2b, 0x07, 0xa1, 0xef, 0x9b, 0x9d, 0x43, 0x77, 0x43, 0x2c, 0x8f, 0xb0, 0x9f, - 0xc0, 0x6d, 0x57, 0x4b, 0xcc, 0x60, 0xb2, 0x59, 0x46, 0xcc, 0x60, 0xcb, 0x5f, 0x3c, 0x35, 0x5c, 0x42, 0xd5, 0x33, - 0xa9, 0xd9, 0x28, 0xd0, 0x9c, 0x5c, 0xa1, 0x39, 0x59, 0x09, 0xb5, 0xe4, 0x93, 0x0a, 0x27, 0xec, 0x7c, 0x92, 0x2b, - 0xbb, 0xd1, 0x18, 0x03, 0x17, 0xad, 0xb9, 0x1d, 0x0a, 0x23, 0x33, 0x9d, 0xa5, 0x68, 0xc0, 0xc2, 0x33, 0x71, 0x4a, - 0x63, 0x40, 0xfb, 0x72, 0x60, 0x69, 0x43, 0x7e, 0x95, 0x33, 0x03, 0x6d, 0x43, 0x4a, 0xa3, 0x66, 0xe0, 0xcf, 0xd4, - 0x84, 0xf9, 0x0c, 0x56, 0x22, 0x88, 0xea, 0x02, 0x4c, 0x92, 0x9c, 0x8c, 0x46, 0xca, 0x4a, 0x24, 0xe7, 0x80, 0xf7, - 0x11, 0x3c, 0x59, 0xc4, 0xb6, 0xf6, 0xa7, 0xf4, 0xbf, 0x3a, 0x7c, 0x2e, 0xfd, 0x27, 0x02, 0x58, 0xc8, 0xa5, 0x41, - 0x64, 0xa0, 0x70, 0x48, 0x2d, 0xc3, 0x7b, 0xe2, 0x78, 0x06, 0xbe, 0x86, 0x0b, 0x34, 0x05, 0xf4, 0x07, 0x35, 0xa3, - 0x88, 0x2c, 0xfc, 0xd5, 0xb3, 0x9b, 0xba, 0xd0, 0xf3, 0xcc, 0x79, 0x0d, 0x9a, 0x19, 0x08, 0xe9, 0x71, 0xaa, 0xde, - 0x86, 0x44, 0xe7, 0xe5, 0xb5, 0x7e, 0x99, 0x10, 0xc9, 0xca, 0xc8, 0xd3, 0xf7, 0x39, 0x98, 0x47, 0x14, 0xa1, 0x83, - 0x2b, 0xf3, 0x70, 0x38, 0x17, 0x14, 0xbe, 0xa3, 0x3c, 0x1f, 0x70, 0x9a, 0x65, 0x09, 0x68, 0x03, 0x59, 0x6e, 0xca, - 0x5c, 0x26, 0x2d, 0x53, 0xf7, 0x1e, 0xac, 0x04, 0x15, 0xba, 0x39, 0x05, 0x85, 0x32, 0x12, 0x94, 0xd2, 0x6a, 0x10, - 0x4a, 0x75, 0x58, 0x04, 0x91, 0x43, 0x16, 0x02, 0x6e, 0xa6, 0xa2, 0xd1, 0x92, 0x86, 0x47, 0x38, 0x37, 0x50, 0x08, - 0x40, 0x62, 0x4f, 0x15, 0x65, 0x5c, 0x0e, 0x01, 0x1f, 0x25, 0x1c, 0xe2, 0xac, 0x49, 0x5b, 0x9e, 0x83, 0x38, 0x96, - 0x0b, 0xbe, 0xac, 0x10, 0x0c, 0x22, 0xf4, 0x19, 0xf2, 0x27, 0xcb, 0xf9, 0x77, 0xeb, 0x30, 0xed, 0x08, 0x1f, 0x76, - 0xb5, 0x1b, 0x2e, 0x66, 0xb7, 0xf3, 0x09, 0xc4, 0xb7, 0xdc, 0xce, 0x8f, 0x31, 0x44, 0x6e, 0xfc, 0xc1, 0x72, 0x28, - 0xb9, 0xa2, 0xd0, 0x65, 0x3d, 0x22, 0x45, 0xf6, 0x74, 0xcd, 0x11, 0x04, 0x07, 0x5a, 0x35, 0xc8, 0xd0, 0x48, 0x7c, - 0xf1, 0x14, 0xb2, 0x06, 0x6b, 0xfe, 0xa2, 0x22, 0x67, 0x75, 0x7f, 0xb2, 0x81, 0x6a, 0x92, 0xc9, 0x5a, 0x51, 0x39, - 0x7f, 0xbb, 0x2a, 0x8b, 0x93, 0x55, 0x19, 0xae, 0x06, 0x5d, 0x55, 0x59, 0x70, 0xa4, 0x36, 0x40, 0x6b, 0xba, 0x42, - 0x0c, 0x85, 0xac, 0xc1, 0xc2, 0xaa, 0xca, 0x9a, 0xfa, 0x04, 0x02, 0x7d, 0x80, 0x65, 0xd4, 0xec, 0xa7, 0xc3, 0x5f, - 0x83, 0x5f, 0x55, 0xc8, 0x52, 0x9d, 0xd6, 0x99, 0xf8, 0x1c, 0x2c, 0x18, 0xfe, 0xf1, 0x7b, 0xb0, 0x06, 0x2c, 0x01, - 0xb2, 0xdc, 0x6d, 0x6c, 0xb4, 0x5e, 0x79, 0x85, 0x78, 0x57, 0xeb, 0x8b, 0x7e, 0xeb, 0x36, 0x51, 0x2b, 0xc0, 0x08, - 0x85, 0x16, 0x01, 0xb6, 0x7a, 0xe0, 0x9e, 0x82, 0x1f, 0x88, 0xe1, 0x5c, 0x93, 0xd6, 0xd4, 0x09, 0xaf, 0xb3, 0x71, - 0x24, 0xa2, 0x7a, 0x0b, 0x17, 0xf7, 0x7a, 0x6b, 0xf1, 0x37, 0x2a, 0x10, 0x00, 0x59, 0x4c, 0xb1, 0x76, 0xde, 0x90, - 0x5e, 0x19, 0x76, 0x12, 0x7a, 0x6f, 0xd8, 0x09, 0xe4, 0xc5, 0x61, 0xa7, 0xd0, 0x25, 0xda, 0x4e, 0x91, 0x9a, 0x68, - 0x3b, 0xe9, 0x66, 0x15, 0x96, 0x10, 0xfc, 0xaa, 0xbd, 0x75, 0x94, 0xed, 0x8b, 0x2c, 0x61, 0xda, 0x02, 0x46, 0xb9, - 0x55, 0x9f, 0x39, 0x45, 0xac, 0x94, 0xbd, 0xd3, 0x49, 0x95, 0xbb, 0xc8, 0xa7, 0x56, 0x53, 0x64, 0xf2, 0x8b, 0xe3, - 0x16, 0xc9, 0x27, 0xbf, 0xb4, 0x1b, 0x26, 0xd3, 0x3f, 0x1e, 0x7d, 0x01, 0x5d, 0x91, 0x9d, 0x3e, 0x81, 0x80, 0x4c, - 0x05, 0xd5, 0xea, 0x56, 0x31, 0xcd, 0xdb, 0x55, 0x76, 0x7b, 0xa1, 0xc4, 0x70, 0x3a, 0x3b, 0x09, 0x8f, 0x36, 0x43, - 0x06, 0x0e, 0x41, 0xa0, 0x10, 0x2a, 0x8a, 0xe1, 0x11, 0xa8, 0x35, 0x92, 0x0f, 0xf0, 0xa3, 0xdd, 0xa9, 0x20, 0x52, - 0xbb, 0xa9, 0xb8, 0x71, 0x72, 0xd3, 0xf5, 0x52, 0xa0, 0xd6, 0x29, 0x59, 0x01, 0x94, 0x10, 0xf5, 0x27, 0xb1, 0xad, - 0x5f, 0xc2, 0x15, 0x9b, 0xef, 0x1b, 0x45, 0x4f, 0xae, 0x4f, 0x51, 0xb7, 0xe2, 0xea, 0x34, 0x6d, 0x35, 0xc7, 0x8e, - 0x33, 0xe4, 0xe0, 0x59, 0x41, 0xb0, 0x1d, 0x95, 0x28, 0xdf, 0xb6, 0x9b, 0x8e, 0x89, 0xad, 0xfe, 0xb9, 0xa9, 0x36, - 0x4b, 0xa8, 0x88, 0x88, 0x8f, 0xb2, 0x9b, 0x27, 0xed, 0x77, 0xb0, 0xc7, 0x5a, 0x0d, 0x22, 0xfb, 0x0c, 0xae, 0x72, - 0x9d, 0x16, 0xb9, 0x2d, 0x83, 0xf3, 0x0f, 0xaf, 0x76, 0x15, 0x36, 0x39, 0xd6, 0xd5, 0xd5, 0x4c, 0x75, 0x52, 0xb1, - 0x81, 0xb1, 0xa6, 0xb5, 0x54, 0xf3, 0x18, 0x92, 0xee, 0xca, 0xe2, 0xac, 0x4a, 0xba, 0xe9, 0xb9, 0x71, 0xa6, 0x10, - 0x03, 0x67, 0xab, 0xd1, 0x72, 0x86, 0x21, 0xba, 0x3e, 0xcc, 0x12, 0xbf, 0xd5, 0x53, 0xee, 0xf3, 0x70, 0xeb, 0x77, - 0xf5, 0x82, 0x93, 0xc9, 0x7e, 0x72, 0x9c, 0xbb, 0x5d, 0xa4, 0xfd, 0xc4, 0xb7, 0x61, 0xfe, 0xf5, 0x0d, 0x62, 0x29, - 0xea, 0x5f, 0x2b, 0x00, 0x1a, 0xdc, 0xe4, 0xb1, 0x44, 0xa9, 0xdf, 0xab, 0xea, 0x07, 0x35, 0x53, 0x35, 0x0d, 0x04, - 0x73, 0x2a, 0x05, 0xfc, 0xe1, 0x76, 0xe1, 0x8a, 0x47, 0xdc, 0xb0, 0x30, 0xfe, 0xe5, 0xd5, 0xec, 0x54, 0x50, 0x19, - 0xb8, 0x19, 0xff, 0xe5, 0x09, 0x76, 0x0a, 0x6b, 0x05, 0x64, 0x85, 0xbf, 0xbc, 0xfc, 0x81, 0xf7, 0x2b, 0xfe, 0x97, - 0x57, 0x3d, 0xf0, 0x3e, 0xe2, 0xbc, 0xfc, 0x85, 0xa4, 0x4e, 0x88, 0xea, 0xf2, 0x17, 0x61, 0x8a, 0xad, 0xd2, 0xfc, - 0x15, 0x29, 0x7c, 0x82, 0x2f, 0xc0, 0x77, 0xb8, 0x0a, 0xb7, 0xe6, 0x37, 0x78, 0xec, 0x58, 0x6c, 0xbb, 0xd4, 0x17, - 0x50, 0x8e, 0xc0, 0x22, 0x72, 0xfb, 0xed, 0xca, 0x7e, 0xb5, 0x30, 0xca, 0x18, 0xbb, 0x2f, 0x59, 0x89, 0xd2, 0x59, - 0xbf, 0x5f, 0x48, 0xc1, 0xc8, 0x2e, 0xac, 0xd1, 0x1e, 0xa5, 0xea, 0xd5, 0xb7, 0x61, 0x1d, 0x25, 0x69, 0xbe, 0x94, - 0xd1, 0x47, 0x32, 0xec, 0x48, 0x5f, 0x49, 0x89, 0xf6, 0x5a, 0x85, 0xe5, 0x68, 0xf6, 0xeb, 0x92, 0x03, 0xe5, 0x75, - 0x2b, 0x28, 0x5f, 0x35, 0x01, 0xf4, 0x4a, 0xb5, 0xcf, 0x40, 0x2b, 0x28, 0x2c, 0x95, 0x07, 0x2b, 0x71, 0x2e, 0xfa, - 0xac, 0x38, 0x1c, 0xd4, 0xc5, 0x90, 0x50, 0xa0, 0x4a, 0x9c, 0x84, 0x46, 0x3c, 0x87, 0x0b, 0xa1, 0x78, 0x96, 0x63, - 0x6c, 0x45, 0x0e, 0x1c, 0xc8, 0xf0, 0x03, 0x02, 0xef, 0x65, 0xff, 0x0a, 0x06, 0xc3, 0x04, 0x37, 0x32, 0xea, 0xe4, - 0x9c, 0xfd, 0x85, 0x81, 0x19, 0xd4, 0x93, 0xda, 0x7d, 0x76, 0xaf, 0x02, 0x7b, 0xe1, 0x0c, 0x68, 0xef, 0xc6, 0xe8, - 0x67, 0x55, 0xac, 0x9d, 0xf4, 0x4f, 0xc5, 0x1a, 0x92, 0xe9, 0xb0, 0x38, 0xda, 0xa6, 0xe1, 0x91, 0x3c, 0x39, 0x8e, - 0x37, 0xfd, 0xc3, 0x61, 0x8c, 0x1f, 0x47, 0xf9, 0xb5, 0x05, 0xbc, 0x8a, 0x5b, 0x48, 0x63, 0x91, 0xa2, 0x77, 0x20, - 0xe6, 0x50, 0xf4, 0x92, 0xfd, 0x96, 0xf1, 0x72, 0x22, 0x28, 0x25, 0x89, 0x0d, 0xef, 0x48, 0x4f, 0xd3, 0x7a, 0xb4, - 0x95, 0x01, 0xfb, 0xf5, 0x68, 0x47, 0x7f, 0x81, 0xe2, 0xd1, 0xc2, 0x5f, 0xd2, 0xdf, 0xc5, 0xdd, 0xdc, 0x73, 0xbe, - 0x69, 0x7c, 0x47, 0x5c, 0xa0, 0x58, 0xb3, 0xfb, 0x6b, 0x5a, 0x3a, 0xeb, 0x40, 0x70, 0xc0, 0x5b, 0xec, 0xa2, 0x7d, - 0xbf, 0x71, 0x9d, 0x9e, 0xf6, 0xdf, 0xbb, 0x35, 0xca, 0xf7, 0x7e, 0x95, 0x28, 0x07, 0xfb, 0x37, 0x2e, 0x9a, 0xbf, - 0xfd, 0x94, 0x21, 0xa9, 0xd0, 0xdc, 0x60, 0x3b, 0xd9, 0x22, 0xac, 0x8d, 0x71, 0x50, 0xb1, 0x65, 0x19, 0x46, 0xc0, - 0xa0, 0x8e, 0xfd, 0x8f, 0x3e, 0x9b, 0x36, 0x64, 0x1f, 0x00, 0x2a, 0x57, 0x21, 0x60, 0x0f, 0xc0, 0x89, 0x46, 0xb8, - 0x01, 0x6e, 0x35, 0x5a, 0xd2, 0x41, 0xdd, 0x16, 0x0c, 0x44, 0x4b, 0xd8, 0xc8, 0xdb, 0xae, 0x4e, 0xdf, 0x10, 0x3e, - 0xd4, 0x4e, 0x4a, 0x87, 0xf2, 0x37, 0xcf, 0xd9, 0x7f, 0xef, 0xb0, 0xa6, 0xa6, 0x5c, 0x03, 0x66, 0xce, 0x4a, 0xe4, - 0x15, 0x42, 0xa7, 0xc8, 0xef, 0x55, 0x5d, 0x89, 0xe1, 0xa2, 0x16, 0x65, 0x67, 0x76, 0xeb, 0x44, 0xef, 0x9c, 0x82, - 0x5a, 0x2a, 0x1b, 0xe4, 0x24, 0xd5, 0xe6, 0x23, 0x6b, 0x05, 0x25, 0xea, 0x1a, 0x05, 0x8e, 0x4f, 0xb9, 0x76, 0xff, - 0xef, 0x9c, 0x09, 0x6a, 0xb6, 0x51, 0xdd, 0x5f, 0xe9, 0xa7, 0xaa, 0x26, 0xb1, 0x00, 0x97, 0x93, 0x34, 0xef, 0x78, - 0x84, 0xd5, 0x3f, 0x4e, 0x96, 0x22, 0xd0, 0xab, 0x88, 0x76, 0x25, 0x20, 0x41, 0x3b, 0x39, 0x0b, 0x15, 0x81, 0x02, - 0x7d, 0xfd, 0xc5, 0x26, 0xcd, 0x62, 0xb9, 0x9a, 0xed, 0x61, 0xa2, 0x2c, 0xd6, 0x43, 0x04, 0x39, 0x33, 0x75, 0xb0, - 0xdf, 0xd3, 0x8c, 0x66, 0xe1, 0x95, 0x29, 0xc1, 0xa5, 0xb8, 0x8a, 0x8a, 0x1c, 0x7c, 0x0e, 0xf1, 0x85, 0x4f, 0x85, - 0xdc, 0x20, 0xa2, 0xe9, 0x4f, 0x12, 0xd5, 0x8e, 0x14, 0xc8, 0xa1, 0xe4, 0x27, 0xc4, 0x5f, 0xb2, 0x36, 0xc6, 0xfd, - 0xd2, 0xa9, 0xf6, 0x4b, 0x85, 0xe0, 0xfe, 0x8b, 0x2d, 0x36, 0xaa, 0x3c, 0xd1, 0x83, 0x4f, 0xb1, 0xfe, 0x27, 0x0b, - 0x28, 0xd5, 0x7d, 0x1b, 0x9c, 0x8a, 0x47, 0xe1, 0xa6, 0x2e, 0xae, 0x11, 0x5a, 0xa0, 0x1c, 0x55, 0xc5, 0xa6, 0x8c, - 0x88, 0x13, 0x76, 0x53, 0x17, 0x3d, 0xcd, 0x81, 0x2e, 0xe7, 0x75, 0x22, 0x4f, 0x84, 0x76, 0x0b, 0xba, 0xa7, 0x39, - 0x56, 0xe2, 0xb9, 0x2c, 0x1d, 0x64, 0x9d, 0x48, 0x13, 0x2a, 0x77, 0x75, 0xd5, 0x51, 0xa9, 0xd4, 0x0d, 0xaf, 0x53, - 0xcd, 0xf8, 0xbb, 0x30, 0x7f, 0x62, 0xd9, 0xaf, 0x5b, 0xbf, 0xd5, 0x6a, 0x6f, 0xac, 0x1e, 0x95, 0xac, 0x39, 0xce, - 0x26, 0x24, 0xa5, 0x4f, 0xd8, 0x6e, 0x26, 0x5d, 0xeb, 0xc0, 0x93, 0xe0, 0x72, 0xe8, 0x09, 0xa8, 0x18, 0x34, 0xf1, - 0x76, 0x17, 0xa8, 0x47, 0xe0, 0x19, 0x28, 0x9f, 0xa8, 0x75, 0xc0, 0xcf, 0x6b, 0x2d, 0x4f, 0x19, 0x61, 0x58, 0xed, - 0x2c, 0x5a, 0x0e, 0xce, 0x3b, 0x45, 0xe0, 0xda, 0x95, 0xc0, 0xf3, 0xa1, 0x7a, 0x2f, 0x04, 0x0c, 0xf7, 0x4f, 0x85, - 0xca, 0x66, 0x37, 0xc3, 0x79, 0xd4, 0x38, 0x3d, 0xd0, 0xde, 0x76, 0xad, 0x87, 0x7a, 0xd7, 0xed, 0xdc, 0x56, 0xba, - 0xf7, 0x6b, 0x27, 0x93, 0x2e, 0xa0, 0xb5, 0xf9, 0xec, 0x3b, 0xbb, 0xd2, 0xba, 0xe9, 0x39, 0x7b, 0xb0, 0x75, 0x4b, - 0x74, 0x2e, 0x88, 0x26, 0xbf, 0x1f, 0x78, 0xd6, 0xb6, 0xa3, 0xdf, 0xa6, 0x1d, 0xdb, 0xdc, 0x43, 0xdd, 0x2b, 0xa8, - 0xf5, 0x86, 0xe6, 0xfd, 0x33, 0xd7, 0xb6, 0xe3, 0xab, 0x5f, 0xd7, 0x1d, 0xae, 0xf3, 0x26, 0x38, 0x6e, 0xba, 0xb6, - 0xd5, 0xce, 0x7e, 0xee, 0xee, 0xad, 0x9b, 0x28, 0xcc, 0xb2, 0x9f, 0x8a, 0xe2, 0xcf, 0x4a, 0xdf, 0x11, 0xe8, 0xe8, - 0xce, 0x8b, 0x3a, 0x5d, 0xec, 0x3e, 0x10, 0xc6, 0x93, 0x57, 0x1f, 0x11, 0xdd, 0xfa, 0x3e, 0x73, 0xbf, 0x02, 0xdc, - 0x08, 0xee, 0x20, 0xda, 0xbb, 0xa5, 0x3e, 0xa9, 0xd5, 0xd7, 0x7a, 0xed, 0x3c, 0x3d, 0xbf, 0xe9, 0xdc, 0x7e, 0xf7, - 0xcd, 0xd1, 0xd6, 0x7b, 0x5c, 0x58, 0x2b, 0x4b, 0x4f, 0x55, 0xc1, 0xde, 0x2c, 0x4f, 0x55, 0xc1, 0xe4, 0x81, 0xd7, - 0xec, 0x17, 0x34, 0xb8, 0xd2, 0xd1, 0xc6, 0x7b, 0xa2, 0x06, 0x6e, 0x51, 0x58, 0x3a, 0xfc, 0x92, 0x9b, 0xc9, 0x4b, - 0xdc, 0x5f, 0x2a, 0x72, 0xb1, 0xef, 0x9c, 0xd1, 0x9d, 0x99, 0x75, 0xaf, 0x2a, 0x5c, 0x2d, 0xc8, 0xd5, 0x81, 0xad, - 0x65, 0x17, 0x87, 0x1b, 0x16, 0x51, 0x80, 0x40, 0x4c, 0xaf, 0xd4, 0xda, 0x1f, 0xd1, 0x20, 0xe4, 0x83, 0x81, 0x5f, - 0x60, 0xb0, 0x2a, 0x50, 0xf8, 0x40, 0x91, 0xfc, 0x8d, 0x27, 0x60, 0x17, 0xcf, 0x00, 0xdd, 0x8a, 0xcd, 0x8a, 0x11, - 0x22, 0x64, 0xb2, 0x9c, 0xd5, 0x74, 0x06, 0xf9, 0xd4, 0x17, 0xdf, 0xd9, 0xaa, 0xd3, 0x79, 0x5b, 0x53, 0xe5, 0xd4, - 0xa1, 0xd0, 0xdd, 0x4d, 0xdd, 0xb9, 0x75, 0x91, 0xa7, 0x0e, 0x21, 0x57, 0x2a, 0x56, 0x62, 0x1a, 0x6a, 0x9e, 0xa4, - 0x19, 0xf5, 0x37, 0x7b, 0xbf, 0xd7, 0x28, 0x9c, 0xf2, 0xa7, 0x63, 0x50, 0x85, 0xab, 0x1a, 0xe2, 0x58, 0xaa, 0xe2, - 0x91, 0x0d, 0x02, 0xcd, 0xab, 0x5b, 0x95, 0x34, 0x21, 0x93, 0x1b, 0xe1, 0x53, 0x93, 0x52, 0x9e, 0xa6, 0x4d, 0x5a, - 0x29, 0x52, 0x07, 0x1f, 0xd4, 0xa9, 0xc6, 0x73, 0xb3, 0x7a, 0x06, 0x60, 0xc6, 0xf9, 0x15, 0xbf, 0x54, 0x5c, 0x46, - 0x6d, 0x65, 0x26, 0xed, 0x4f, 0x8e, 0xc6, 0x46, 0x5d, 0x4e, 0x1b, 0x65, 0x84, 0x95, 0xd2, 0x9c, 0x14, 0xcb, 0xf1, - 0xfc, 0x03, 0x06, 0x6b, 0x9e, 0xc0, 0x0e, 0x26, 0x2a, 0xe5, 0x7d, 0x04, 0xc4, 0xd7, 0x49, 0xba, 0x4c, 0x20, 0x45, - 0xfa, 0x97, 0x2e, 0x78, 0xea, 0x30, 0x36, 0x10, 0x63, 0x56, 0xcc, 0x8c, 0xfe, 0x07, 0x77, 0x49, 0x7f, 0x12, 0x02, - 0xe0, 0x26, 0x9a, 0x42, 0xa7, 0xce, 0x93, 0x8b, 0x3c, 0x58, 0x5c, 0x78, 0x68, 0xc5, 0x88, 0x07, 0xff, 0xf9, 0x2c, - 0x44, 0x10, 0x73, 0x4c, 0xf1, 0xf4, 0x0b, 0xa3, 0xff, 0x08, 0x2e, 0x31, 0x82, 0xd0, 0xdd, 0x3b, 0x87, 0x21, 0xdc, - 0xec, 0x41, 0x06, 0xf5, 0x87, 0x3a, 0x24, 0x6a, 0xf8, 0x6b, 0xe5, 0x41, 0xff, 0xd7, 0x99, 0xb0, 0xd4, 0x7e, 0x7a, - 0x3a, 0x80, 0x0a, 0xde, 0x57, 0xbc, 0x8d, 0x88, 0xef, 0x13, 0x3f, 0x89, 0x07, 0x9b, 0x27, 0x1b, 0xb0, 0xd6, 0x3d, - 0xca, 0x8d, 0x75, 0x95, 0xb0, 0x81, 0x80, 0xaf, 0x31, 0xad, 0x3d, 0xaf, 0xdd, 0xee, 0xc1, 0x7f, 0xfa, 0x17, 0x21, - 0x03, 0x26, 0x4e, 0xdf, 0x67, 0x4e, 0xd6, 0xe8, 0x22, 0x93, 0xe9, 0x43, 0x27, 0x7d, 0xa3, 0xd3, 0x7d, 0x27, 0xfc, - 0xa3, 0x62, 0x16, 0x1f, 0x6e, 0xe9, 0x2b, 0x4d, 0x8a, 0x3b, 0x60, 0x65, 0xf3, 0xa0, 0x20, 0xd4, 0xb9, 0x88, 0xbe, - 0x31, 0xe5, 0x5b, 0x42, 0xcd, 0xbe, 0xb1, 0xa4, 0x94, 0xee, 0x35, 0xf4, 0x3a, 0xad, 0xf5, 0xdb, 0x28, 0xc1, 0x98, - 0xe8, 0x78, 0xf2, 0x32, 0x1e, 0x2b, 0xef, 0xe3, 0x71, 0x23, 0x15, 0xf2, 0x00, 0x44, 0xa0, 0x62, 0xfc, 0xe9, 0xca, - 0x93, 0x93, 0x5e, 0x18, 0xaf, 0x42, 0x29, 0x28, 0x0c, 0xe8, 0x0a, 0xa4, 0x80, 0x47, 0xed, 0x89, 0xce, 0xc2, 0x2e, - 0xe1, 0x1e, 0xdd, 0x04, 0x8c, 0xf5, 0xf9, 0x57, 0x40, 0x73, 0x17, 0xee, 0xf0, 0x62, 0x80, 0xda, 0xd4, 0xab, 0xbb, - 0x8f, 0x6b, 0x75, 0x0e, 0x87, 0xe0, 0x60, 0x35, 0x88, 0xe0, 0x74, 0x3e, 0x75, 0x34, 0xcb, 0x02, 0x54, 0x4e, 0x96, - 0x1b, 0x79, 0xf3, 0x68, 0xd1, 0xab, 0xfb, 0xde, 0x22, 0x2d, 0xab, 0x3a, 0xc8, 0x58, 0x16, 0x56, 0x80, 0xab, 0x43, - 0xeb, 0x07, 0xe1, 0xb2, 0x70, 0xfe, 0x40, 0x08, 0x62, 0xf7, 0x6a, 0x5b, 0xf0, 0x5c, 0xcd, 0xe1, 0x27, 0x4f, 0xd9, - 0x9a, 0x4b, 0xd4, 0x49, 0x67, 0x22, 0x00, 0xb1, 0xa7, 0x66, 0x15, 0x5d, 0x03, 0x49, 0x9d, 0x66, 0x15, 0x5d, 0x53, - 0xb3, 0x8d, 0x71, 0x20, 0x1f, 0xad, 0x52, 0xc0, 0xbe, 0x9b, 0x8e, 0x83, 0xd5, 0x93, 0x58, 0x5e, 0x87, 0x96, 0x4f, - 0x36, 0xca, 0x67, 0x50, 0xb7, 0xda, 0x18, 0x13, 0xdb, 0xcd, 0x97, 0x73, 0xfd, 0x76, 0xb0, 0xf0, 0xed, 0xa0, 0x39, - 0xa7, 0xec, 0xa5, 0x2e, 0x7b, 0x65, 0x97, 0x4d, 0x3d, 0x77, 0x54, 0xb4, 0x1a, 0x03, 0x7a, 0x03, 0x0b, 0xd6, 0xe7, - 0x22, 0xcd, 0x56, 0xa5, 0x2a, 0x01, 0x2f, 0x8c, 0x15, 0x5b, 0xfa, 0x8d, 0xcc, 0x90, 0x84, 0x79, 0x9c, 0x89, 0xb7, - 0x74, 0xaf, 0x85, 0xc9, 0x71, 0x2c, 0x92, 0x29, 0xa1, 0x53, 0xba, 0xb3, 0x0d, 0x9d, 0xab, 0x30, 0x8a, 0x68, 0xad, - 0xa4, 0xd2, 0x48, 0x60, 0x6a, 0x06, 0x28, 0x99, 0x2b, 0x70, 0x4a, 0x97, 0xfb, 0xdf, 0x91, 0x18, 0x67, 0xbe, 0x28, - 0x99, 0x01, 0xdd, 0xf2, 0xeb, 0x62, 0xdd, 0x4a, 0x91, 0x11, 0xe6, 0xcd, 0x71, 0x7b, 0x5d, 0x1f, 0x02, 0xb9, 0x5a, - 0xf6, 0x28, 0x1a, 0x07, 0x85, 0x0e, 0x97, 0x2a, 0x01, 0xf6, 0x45, 0xe2, 0x67, 0x84, 0x2d, 0xed, 0x81, 0xdc, 0x1e, - 0x9d, 0x09, 0x73, 0xce, 0x49, 0x59, 0x76, 0x2e, 0xcd, 0xe0, 0x72, 0xe2, 0x4a, 0x70, 0x91, 0xde, 0xb6, 0xa7, 0x49, - 0x4b, 0xdb, 0xc7, 0x86, 0x73, 0x34, 0xb4, 0x0d, 0xba, 0x63, 0x7f, 0x68, 0x2e, 0x16, 0xb1, 0x75, 0xb1, 0x18, 0x76, - 0x66, 0x3f, 0x5a, 0x2c, 0x40, 0x0e, 0x00, 0x47, 0xdd, 0x86, 0x8f, 0xd9, 0x02, 0x38, 0xad, 0xa6, 0xd9, 0xd4, 0xdb, - 0xf0, 0xea, 0x89, 0xea, 0xe9, 0x05, 0xcf, 0x9f, 0x08, 0x33, 0x16, 0x1b, 0x9e, 0x3f, 0xb1, 0x8e, 0x9c, 0xea, 0x89, - 0x50, 0xa2, 0x75, 0x01, 0xcd, 0xc0, 0x6b, 0x0a, 0x18, 0xb1, 0x64, 0x32, 0xa5, 0x8a, 0x3c, 0xee, 0x4d, 0x37, 0x6a, - 0xf0, 0x82, 0xc2, 0x21, 0x90, 0xd2, 0xe9, 0x17, 0x4f, 0x99, 0x7e, 0xef, 0xe2, 0x69, 0x87, 0xac, 0x6d, 0x98, 0x2e, - 0x37, 0xc3, 0x64, 0x50, 0xfa, 0x4f, 0xcc, 0xc4, 0xb8, 0xb0, 0x26, 0x09, 0x20, 0xfe, 0x8d, 0xfd, 0x0e, 0x29, 0xdc, - 0xbc, 0xbf, 0x18, 0xc6, 0x0f, 0xbc, 0x1f, 0x23, 0x7b, 0x92, 0x66, 0x88, 0x35, 0x93, 0x0a, 0xb9, 0xfb, 0x6a, 0xfd, - 0x63, 0x62, 0x37, 0xd9, 0x03, 0x0b, 0x40, 0x6c, 0x4d, 0x5b, 0xdd, 0xf2, 0x7e, 0xdf, 0x33, 0x45, 0x80, 0x1f, 0x94, - 0x7f, 0x74, 0x67, 0x48, 0x06, 0x65, 0xd7, 0x0d, 0x21, 0x1e, 0x94, 0x4d, 0xd3, 0x5e, 0x6f, 0x7b, 0x67, 0x1e, 0xab, - 0xeb, 0xb4, 0xb3, 0xb8, 0x5a, 0x64, 0x90, 0x56, 0x1f, 0xb2, 0xe3, 0xcc, 0x3e, 0x3b, 0x5a, 0x2a, 0xdd, 0xef, 0x43, - 0x44, 0xdc, 0x51, 0xd6, 0xf6, 0xdb, 0x2d, 0xb8, 0x86, 0xa3, 0x41, 0xe8, 0xca, 0xde, 0x2e, 0xa3, 0x8d, 0x0b, 0x71, - 0xdc, 0x33, 0x9d, 0x2f, 0xf8, 0xf2, 0x28, 0xed, 0x3c, 0x38, 0xd5, 0x13, 0x7d, 0x6e, 0xba, 0xab, 0x4c, 0xae, 0x75, - 0x58, 0x8d, 0x41, 0x6d, 0x16, 0xb6, 0x70, 0x17, 0xb6, 0xd1, 0x41, 0x6b, 0x5f, 0x16, 0xfc, 0x53, 0x06, 0xe0, 0x4b, - 0xcf, 0x96, 0x6d, 0xaf, 0x49, 0xab, 0xd7, 0x32, 0x0a, 0xb1, 0xa5, 0xed, 0xd5, 0xa7, 0xa3, 0x7c, 0xdc, 0x9c, 0x50, - 0x5c, 0xc8, 0x51, 0x7e, 0xf0, 0x1a, 0xa2, 0xae, 0x75, 0x1d, 0x17, 0x8b, 0x0e, 0x37, 0xae, 0xba, 0xed, 0xc6, 0xf5, - 0x23, 0xe2, 0xad, 0xd1, 0x26, 0x85, 0x5a, 0x19, 0x3b, 0x82, 0x97, 0xe5, 0xc3, 0x21, 0x13, 0xc3, 0xa1, 0x84, 0x4c, - 0x7d, 0xe8, 0xde, 0xd0, 0xb4, 0xcf, 0x4f, 0x5b, 0x3f, 0x62, 0xa9, 0x71, 0x14, 0x1b, 0xde, 0xe9, 0x3b, 0x8f, 0xad, - 0x71, 0x25, 0x5f, 0x06, 0xb3, 0x5d, 0x41, 0xb5, 0x35, 0xde, 0xb0, 0x97, 0xf3, 0x9f, 0x2a, 0xa9, 0xe4, 0x6f, 0x7f, - 0x86, 0x6b, 0x78, 0x6b, 0x4b, 0x07, 0x4d, 0x35, 0xcb, 0x59, 0xae, 0xef, 0x05, 0xc7, 0x1f, 0x77, 0xaf, 0x08, 0x06, - 0xbf, 0xa7, 0xa3, 0x20, 0x17, 0x4b, 0xb5, 0x06, 0x14, 0xa4, 0x23, 0x3b, 0xa6, 0xb2, 0xc0, 0x30, 0x80, 0x37, 0x64, - 0x80, 0x3c, 0xa6, 0x70, 0x37, 0x54, 0x78, 0xe1, 0x6f, 0x15, 0xd9, 0x25, 0xb0, 0xad, 0x19, 0x1f, 0x33, 0xdc, 0x41, - 0xc8, 0x3f, 0x82, 0x2d, 0xd9, 0x8a, 0xdd, 0xb2, 0x1b, 0x86, 0x64, 0xe3, 0x38, 0x8c, 0x31, 0x1f, 0x4f, 0xe2, 0x2b, - 0x31, 0x89, 0x07, 0x3c, 0x42, 0xc7, 0x88, 0x35, 0xaf, 0x67, 0xb1, 0x1c, 0x40, 0xb6, 0xe4, 0x4a, 0x07, 0x84, 0xd0, - 0xd8, 0xd0, 0x92, 0xd7, 0x85, 0xc1, 0xc5, 0x8e, 0x7d, 0x46, 0x22, 0x19, 0x87, 0x60, 0xd1, 0xaa, 0x06, 0x16, 0x26, - 0x76, 0xcb, 0x8b, 0xd9, 0x6a, 0x8e, 0xff, 0x1c, 0x0e, 0x08, 0x80, 0x1d, 0xec, 0x1b, 0xb6, 0x8c, 0x10, 0xe9, 0xed, - 0x86, 0x2f, 0x2d, 0x4f, 0x17, 0x76, 0xc7, 0xdf, 0xf2, 0x31, 0x3b, 0xff, 0xd1, 0x83, 0xc8, 0xd9, 0xf3, 0x8f, 0x80, - 0x86, 0x78, 0xc7, 0x6f, 0x53, 0xaf, 0x62, 0xb7, 0x44, 0x41, 0x78, 0x0b, 0xce, 0x40, 0x77, 0x10, 0x01, 0xfb, 0x96, - 0xdf, 0x60, 0xac, 0xd8, 0x59, 0xba, 0xf0, 0x30, 0x23, 0xd4, 0x9e, 0xce, 0x97, 0xb5, 0x9a, 0x84, 0x9b, 0xab, 0xc5, - 0x64, 0x30, 0xd8, 0xf8, 0x3b, 0xbe, 0x06, 0x3e, 0x98, 0xf3, 0x1f, 0xbd, 0x1d, 0x95, 0x0b, 0xff, 0x79, 0x9d, 0x25, - 0xef, 0x7c, 0xf6, 0x76, 0xc0, 0x6f, 0x00, 0x6f, 0x09, 0x1d, 0xb8, 0xee, 0x7c, 0x26, 0xf1, 0xda, 0xde, 0xea, 0x6b, - 0x04, 0x12, 0xf9, 0x02, 0x30, 0x62, 0x62, 0x7e, 0xbf, 0x85, 0x08, 0x8c, 0x18, 0x7c, 0x5b, 0xb5, 0x47, 0xfc, 0x96, - 0x1b, 0xc0, 0xaf, 0xcc, 0x67, 0xf7, 0x3c, 0xd4, 0x3f, 0x13, 0x9f, 0x5d, 0xf3, 0xf7, 0xfc, 0x99, 0x27, 0x25, 0xe9, - 0x72, 0xf6, 0x7e, 0x0e, 0xd7, 0x43, 0x29, 0x4f, 0x87, 0xf4, 0xb3, 0x31, 0x18, 0x40, 0x28, 0x64, 0x5e, 0x7b, 0xc0, - 0x9a, 0x14, 0xe2, 0x5f, 0xc0, 0xb7, 0xa3, 0x84, 0xcd, 0x6b, 0x6f, 0xeb, 0x6b, 0x79, 0xf3, 0xda, 0xbb, 0xf7, 0x29, - 0x0a, 0xb0, 0x0a, 0x4a, 0x59, 0x60, 0x15, 0x84, 0x8d, 0x36, 0xc2, 0x18, 0xb8, 0x7a, 0xd7, 0x18, 0xea, 0x7a, 0x8e, - 0xd8, 0xb6, 0xd2, 0x77, 0xe1, 0x3b, 0xc8, 0x80, 0x0f, 0x5e, 0x17, 0x25, 0xd1, 0xe7, 0xd4, 0x14, 0x49, 0xeb, 0x9e, - 0xfb, 0xad, 0x75, 0x47, 0x6b, 0x4a, 0x7d, 0xe4, 0x6a, 0x7c, 0x38, 0xd4, 0xcf, 0x84, 0x16, 0x09, 0xa6, 0xa0, 0x71, - 0x0d, 0xda, 0x02, 0x04, 0x7d, 0x1e, 0x20, 0x6b, 0x49, 0xb1, 0xe0, 0xdb, 0x5f, 0x21, 0x06, 0xaf, 0x4c, 0xef, 0x5c, - 0xae, 0x32, 0x12, 0xb6, 0x17, 0x7e, 0x39, 0xac, 0xfd, 0x89, 0x53, 0x0b, 0x4b, 0xab, 0x39, 0xa8, 0x9f, 0xd8, 0x72, - 0x9c, 0xaa, 0xda, 0xdf, 0x25, 0x49, 0xb5, 0xab, 0xb4, 0x9c, 0xde, 0xd9, 0x37, 0x5d, 0x26, 0xd8, 0xd8, 0x0f, 0xa8, - 0x3a, 0xb2, 0x1a, 0x76, 0x5f, 0xa8, 0x2f, 0x7a, 0x4a, 0x26, 0x34, 0x1f, 0x55, 0x34, 0xcf, 0xee, 0x37, 0x3b, 0xea, - 0x3f, 0xbd, 0x1c, 0x8a, 0x00, 0xc9, 0x2a, 0x2d, 0x96, 0x22, 0x67, 0x63, 0x3f, 0x1e, 0x26, 0x99, 0x0a, 0x2f, 0x48, - 0x47, 0x77, 0xbf, 0x71, 0x7f, 0xcb, 0x0d, 0x64, 0x85, 0x56, 0x6d, 0x30, 0x56, 0x8a, 0x96, 0xc1, 0xfa, 0x6a, 0xdc, - 0xef, 0x8b, 0xab, 0xf1, 0x54, 0x04, 0x35, 0x10, 0x17, 0x89, 0x67, 0xe3, 0x69, 0x4d, 0x2c, 0xa9, 0x5d, 0x81, 0x31, - 0x7a, 0x5c, 0x15, 0xb5, 0x4f, 0xfd, 0x0c, 0x42, 0x91, 0x6a, 0xcd, 0x1c, 0x6b, 0xdc, 0x18, 0x11, 0x77, 0x58, 0xb9, - 0x76, 0x6a, 0xaf, 0x03, 0xb0, 0xbc, 0x1a, 0x17, 0x84, 0x45, 0x72, 0xec, 0x5c, 0xc0, 0x6a, 0x34, 0xa4, 0xda, 0x0d, - 0xb7, 0x5e, 0x76, 0x7e, 0xf3, 0x4d, 0x62, 0x6b, 0x23, 0xdc, 0x52, 0x40, 0x19, 0xe5, 0x37, 0x96, 0x13, 0x76, 0xa7, - 0x7a, 0x47, 0xaa, 0x76, 0xc4, 0x89, 0x0b, 0x58, 0x6e, 0x78, 0x6a, 0xf5, 0x4d, 0x0c, 0x4e, 0x84, 0xaa, 0x95, 0x0e, - 0x77, 0x32, 0x81, 0xb8, 0x5f, 0xdd, 0xd7, 0xbd, 0x12, 0xfc, 0x24, 0xe4, 0xf5, 0x5b, 0xde, 0x01, 0x60, 0xc5, 0x87, - 0xbc, 0x98, 0x16, 0x8e, 0xd6, 0x65, 0x50, 0x06, 0x88, 0xd0, 0x0c, 0x80, 0x4e, 0xae, 0x0e, 0xa2, 0x34, 0x70, 0xc5, - 0x1d, 0x22, 0xfc, 0x34, 0x7a, 0x92, 0x3f, 0x0b, 0x9f, 0x54, 0xd3, 0xf0, 0x22, 0x0f, 0xa2, 0x8b, 0x2a, 0x88, 0x9e, - 0x54, 0x57, 0xe1, 0x93, 0x7c, 0x1a, 0x5d, 0xe4, 0x41, 0x78, 0x51, 0x35, 0xf6, 0x5d, 0xbb, 0xbb, 0x27, 0xe4, 0x6d, - 0x57, 0x7f, 0xe4, 0x5c, 0xd9, 0x53, 0xa6, 0xe7, 0xe7, 0xb5, 0x5e, 0xa9, 0xdd, 0xe6, 0x7a, 0x8d, 0x9a, 0xa9, 0x8f, - 0xb2, 0xbf, 0xd9, 0xc6, 0xc2, 0xa3, 0x39, 0x84, 0x3e, 0x23, 0x2d, 0xe6, 0x1e, 0xe7, 0x7a, 0xb3, 0x27, 0x85, 0x81, - 0x11, 0x93, 0x4a, 0x46, 0x4e, 0x2f, 0x70, 0x11, 0xaa, 0x10, 0xc3, 0x5a, 0xba, 0xda, 0x67, 0x5d, 0x7a, 0x03, 0x75, - 0x4d, 0xb1, 0xaf, 0x21, 0x03, 0x2f, 0x9a, 0x5e, 0x06, 0x63, 0x40, 0x8e, 0xc0, 0x3b, 0x3e, 0x5b, 0xc0, 0x81, 0xb9, - 0x06, 0xe8, 0x9b, 0x07, 0x7d, 0x5d, 0x96, 0x7c, 0xad, 0xfa, 0x66, 0xba, 0x1e, 0x29, 0xe5, 0xc7, 0x8a, 0x2f, 0x2f, - 0x9e, 0xb2, 0x5b, 0xae, 0x51, 0x51, 0x5e, 0xe8, 0xc5, 0x7a, 0x07, 0x5c, 0x75, 0x2f, 0xe0, 0x36, 0x8b, 0xc7, 0xae, - 0x3c, 0x60, 0xd9, 0x96, 0xdd, 0xb3, 0x6b, 0xf6, 0x9e, 0x3d, 0x62, 0xaf, 0xd8, 0x57, 0x56, 0x23, 0x44, 0x79, 0xa9, - 0xa4, 0x3c, 0xff, 0x86, 0xdf, 0x4a, 0xdb, 0xa3, 0x84, 0x25, 0xbb, 0xb7, 0xed, 0x34, 0xc3, 0x0d, 0x7b, 0xcf, 0x6f, - 0x86, 0x2b, 0xf6, 0x0a, 0xb2, 0xa1, 0x50, 0x3c, 0x58, 0xb1, 0x1a, 0xae, 0xb0, 0x94, 0x41, 0x9f, 0x86, 0xa5, 0x25, - 0x2c, 0x9a, 0x42, 0x51, 0x8a, 0x7e, 0xc5, 0x6b, 0xc2, 0x4e, 0xab, 0xb1, 0x10, 0xf9, 0xa1, 0xe1, 0x8a, 0xdd, 0xf3, - 0x9b, 0xc1, 0x8a, 0xbd, 0xd7, 0x36, 0xa2, 0xc1, 0xc6, 0x2d, 0x8e, 0xc0, 0xac, 0x74, 0x61, 0x52, 0xa0, 0xde, 0xda, - 0x37, 0xc1, 0x0d, 0xbb, 0xc6, 0xfa, 0x3d, 0xc2, 0xa2, 0x51, 0xe6, 0x1f, 0xac, 0xd8, 0x57, 0x2e, 0x31, 0xd4, 0xdc, - 0xf2, 0xa4, 0x63, 0xa8, 0x2e, 0x90, 0xae, 0x08, 0x8f, 0x38, 0xbd, 0xc8, 0xbe, 0x62, 0x19, 0xf4, 0x95, 0xe1, 0x8a, - 0x6d, 0xb1, 0x76, 0xd7, 0xc6, 0xb8, 0x65, 0x55, 0x4f, 0x82, 0x02, 0xa3, 0xac, 0x52, 0x5a, 0x2e, 0x8e, 0x58, 0x36, - 0x75, 0xd4, 0xa0, 0x36, 0x0c, 0xe8, 0x83, 0xd1, 0x7f, 0xf8, 0xfa, 0xdd, 0x0f, 0x5e, 0xa9, 0x6f, 0xbe, 0x2f, 0x1c, - 0xef, 0xca, 0x12, 0xbd, 0x2b, 0x3f, 0xf3, 0x72, 0xf6, 0x62, 0x3e, 0xd1, 0xb5, 0xa4, 0x4d, 0x86, 0xdc, 0x4d, 0x67, - 0x2f, 0x3a, 0xfc, 0x2d, 0x3f, 0xfb, 0x7e, 0x63, 0xf5, 0xb1, 0xfa, 0xae, 0xee, 0xde, 0xfb, 0xc1, 0xa6, 0x71, 0x2a, - 0xbe, 0x3b, 0x5d, 0x71, 0x6c, 0x67, 0xad, 0xbd, 0x33, 0xff, 0x87, 0x6b, 0xbd, 0xc5, 0xb1, 0xbb, 0xe6, 0xdb, 0xe1, - 0xc6, 0x1e, 0x06, 0xf9, 0x7d, 0xe5, 0x97, 0x5f, 0xf3, 0xe7, 0x5e, 0xa7, 0x24, 0x0b, 0xa8, 0x46, 0x9f, 0x8c, 0x34, - 0x74, 0xc9, 0x4c, 0x4c, 0x43, 0x7c, 0x91, 0x01, 0x3a, 0x17, 0x88, 0x67, 0x77, 0x7c, 0x3c, 0xb9, 0xbb, 0x8a, 0x27, - 0x77, 0x03, 0xfe, 0xc9, 0xb4, 0xa0, 0xbd, 0xe0, 0xee, 0x7c, 0xf6, 0x99, 0x17, 0xf6, 0x92, 0x7c, 0xe1, 0xb3, 0x77, - 0xc2, 0x5d, 0xa5, 0x2f, 0x7c, 0xf6, 0x55, 0xf0, 0xcf, 0x23, 0x4d, 0x96, 0xc1, 0xbe, 0xd6, 0xfc, 0xf3, 0x08, 0x59, - 0x3f, 0xd8, 0x17, 0xc1, 0xdf, 0x81, 0xff, 0x77, 0x95, 0xa0, 0x65, 0xfc, 0x4b, 0xad, 0x7e, 0xbe, 0x97, 0xb1, 0x39, - 0xf0, 0x26, 0xb4, 0x82, 0xde, 0xbc, 0xad, 0xe5, 0x4f, 0xe2, 0xe2, 0x48, 0xd5, 0x53, 0xc3, 0x41, 0x8b, 0xc5, 0xdc, - 0xd4, 0x47, 0xe9, 0x54, 0xde, 0xe4, 0x2d, 0x4f, 0xa4, 0x85, 0xf9, 0x0e, 0xc2, 0x81, 0xdf, 0xda, 0x30, 0x05, 0x3b, - 0x8e, 0x9b, 0xc1, 0x5b, 0x06, 0x10, 0x92, 0xd9, 0x74, 0xcb, 0xaf, 0xf9, 0x23, 0xfe, 0x95, 0xef, 0x82, 0x7b, 0xfe, - 0x9e, 0xbf, 0xe2, 0x75, 0xcd, 0x77, 0x6c, 0x21, 0x21, 0x4f, 0xeb, 0xed, 0x65, 0xb0, 0x65, 0xf5, 0xee, 0x32, 0xb8, - 0x67, 0xf5, 0xf6, 0x69, 0x70, 0xcd, 0xea, 0xdd, 0xd3, 0xe0, 0x3d, 0xdb, 0x5e, 0x06, 0x8f, 0xd8, 0xee, 0x32, 0x78, - 0xc5, 0xb6, 0x4f, 0x83, 0xaf, 0x6c, 0xf7, 0x34, 0xa8, 0x15, 0xd2, 0xc3, 0x57, 0x21, 0x99, 0x4e, 0xbe, 0xd6, 0xcc, - 0xb0, 0xea, 0x06, 0x5f, 0x84, 0xf5, 0x8b, 0x6a, 0x19, 0x7c, 0xa9, 0x99, 0x6e, 0x73, 0x20, 0x04, 0xd3, 0x2d, 0x0e, - 0x6e, 0xe9, 0x89, 0x69, 0x57, 0x90, 0x0a, 0xd6, 0xd5, 0xd2, 0xe0, 0xa6, 0x6e, 0x5a, 0x27, 0xb3, 0xe3, 0x9d, 0x18, - 0x77, 0x78, 0x27, 0xde, 0xb0, 0x45, 0xd3, 0xe9, 0xaa, 0x73, 0xfa, 0x3c, 0xd0, 0x47, 0x80, 0xde, 0xfb, 0x2b, 0xe9, - 0x41, 0x53, 0x34, 0x3c, 0x57, 0xba, 0xe3, 0xd6, 0x7e, 0x1f, 0x5a, 0xfb, 0x3d, 0x93, 0x8a, 0xb4, 0x88, 0x45, 0x65, - 0x51, 0x55, 0xc8, 0x27, 0x1e, 0x64, 0x5a, 0xab, 0x96, 0x30, 0x52, 0x67, 0x02, 0x26, 0x7d, 0x41, 0x87, 0x41, 0x4e, - 0x76, 0x05, 0xb6, 0xe0, 0x9b, 0x41, 0xc2, 0xd6, 0x3c, 0x9e, 0x0e, 0x93, 0x60, 0xc1, 0x96, 0x7c, 0xd8, 0x2d, 0x16, - 0xac, 0x54, 0x18, 0x93, 0xbe, 0x3e, 0x1d, 0xed, 0xee, 0xbc, 0xb7, 0x4a, 0xe3, 0x38, 0x13, 0xa8, 0x73, 0xab, 0xf4, - 0x36, 0xbf, 0x75, 0x76, 0xf5, 0xb5, 0xda, 0xe5, 0x41, 0x60, 0xf8, 0x0c, 0x44, 0x3b, 0xc4, 0x7b, 0x07, 0x35, 0x46, - 0xba, 0x25, 0xb3, 0xee, 0x2b, 0x7b, 0x5f, 0xdf, 0x9a, 0xad, 0xfa, 0xdf, 0x2d, 0x82, 0xf6, 0x72, 0xd9, 0xfb, 0x9f, - 0xcc, 0xab, 0xbf, 0x77, 0xbc, 0xba, 0xf1, 0x27, 0xf7, 0xfc, 0x13, 0x46, 0x27, 0x60, 0x22, 0xdb, 0xf1, 0x4f, 0xa3, - 0x6d, 0xe3, 0x94, 0x27, 0xf7, 0xf2, 0xff, 0x2b, 0x05, 0xda, 0xbb, 0x79, 0x65, 0x6f, 0x8a, 0x5b, 0xde, 0xb1, 0x97, - 0x2f, 0xac, 0x3d, 0xd1, 0x20, 0x94, 0x7c, 0xe2, 0x6e, 0x50, 0x34, 0xec, 0x89, 0x2f, 0x78, 0x35, 0xfb, 0x34, 0x9f, - 0x6c, 0xf9, 0xf1, 0x8e, 0xf8, 0xa9, 0x63, 0x47, 0x7c, 0xe1, 0x0f, 0x16, 0xcd, 0xb7, 0x7a, 0xb5, 0x73, 0x27, 0x77, - 0x2a, 0xbd, 0xe3, 0xc7, 0xfb, 0xf8, 0xf0, 0xdf, 0xae, 0xf4, 0xee, 0xbb, 0x2b, 0x6d, 0x57, 0xb9, 0xbb, 0xf3, 0x4d, - 0xc7, 0x37, 0xb2, 0xd6, 0x18, 0x6e, 0x66, 0x14, 0x8c, 0x30, 0x6d, 0x61, 0x9a, 0x06, 0x91, 0xa5, 0x58, 0x84, 0x44, - 0x8d, 0xd2, 0x39, 0xd1, 0x67, 0x41, 0xa7, 0xa0, 0x8b, 0x1b, 0xfd, 0x2d, 0x1f, 0xb3, 0x1b, 0xe3, 0xb2, 0x79, 0x7b, - 0x75, 0x33, 0x19, 0x0c, 0x6e, 0xfd, 0xfd, 0x1d, 0x0f, 0x67, 0xb7, 0x73, 0xf6, 0x96, 0xdf, 0xd1, 0x7a, 0x9a, 0xa8, - 0xc6, 0x17, 0x0f, 0x49, 0x60, 0xb7, 0xbe, 0x3f, 0xb1, 0x88, 0x60, 0xed, 0x1b, 0xe7, 0xad, 0x3f, 0x90, 0x66, 0x69, - 0xb9, 0xb5, 0xbf, 0x7f, 0x58, 0x43, 0x71, 0x0b, 0x42, 0xc6, 0x7b, 0x5b, 0xe5, 0xf0, 0x8a, 0x7f, 0xf4, 0xde, 0xfa, - 0xd3, 0xb7, 0x3a, 0xf8, 0x66, 0xa2, 0xce, 0xa5, 0x57, 0x17, 0x4f, 0xd9, 0x67, 0xfe, 0x49, 0x9e, 0x29, 0xef, 0x84, - 0x9c, 0xb6, 0xd7, 0x48, 0xe2, 0x44, 0x47, 0xc5, 0x57, 0x37, 0x91, 0x40, 0x21, 0x60, 0x57, 0xf8, 0x5a, 0xf3, 0xfb, - 0x49, 0x39, 0xf5, 0x76, 0x40, 0xf2, 0xca, 0x6d, 0x45, 0xf4, 0x2d, 0xe7, 0xfc, 0x66, 0x78, 0x39, 0xfd, 0xda, 0xed, - 0xdb, 0xa3, 0xc2, 0xda, 0x54, 0xc4, 0xdb, 0x2d, 0x06, 0x61, 0x9d, 0xcc, 0x2c, 0x73, 0xc9, 0x97, 0xbe, 0xd6, 0x66, - 0xee, 0x31, 0xbd, 0xe3, 0x4c, 0x33, 0x64, 0xf4, 0x05, 0x66, 0xa6, 0xc3, 0x61, 0x79, 0x8e, 0xe5, 0xf1, 0xe1, 0xab, - 0x27, 0x8f, 0x06, 0x8f, 0x30, 0x84, 0xcb, 0x0a, 0x0b, 0xf9, 0xca, 0x87, 0x59, 0xdd, 0xba, 0x76, 0x5c, 0x3c, 0x1d, - 0xbe, 0x80, 0xbc, 0x41, 0xd7, 0x43, 0x53, 0x44, 0xab, 0xfc, 0x8e, 0xa2, 0x4f, 0x94, 0x1c, 0x74, 0x3c, 0x81, 0xda, - 0x21, 0x17, 0xee, 0xd7, 0x27, 0x1c, 0x14, 0x1d, 0x58, 0x6a, 0xbf, 0x7f, 0xfe, 0x89, 0x08, 0xa5, 0x61, 0xbc, 0x5f, - 0x84, 0xd1, 0x9f, 0x71, 0x59, 0xac, 0xe1, 0x88, 0x1d, 0xc0, 0xe7, 0x9e, 0xe8, 0x6b, 0xd8, 0xd2, 0xf7, 0xfd, 0xc0, - 0xdb, 0xf2, 0x6b, 0xf6, 0x95, 0x7b, 0x97, 0xc3, 0x57, 0xfe, 0x93, 0x47, 0x20, 0x3f, 0x21, 0x4e, 0x0a, 0x86, 0xc4, - 0x76, 0x14, 0xa3, 0xd6, 0xe1, 0x97, 0x1a, 0x62, 0xb5, 0x3e, 0x21, 0x75, 0x17, 0xa4, 0x7f, 0x50, 0xc8, 0x7e, 0x42, - 0x60, 0x35, 0x49, 0x9f, 0x02, 0x93, 0xf8, 0xb6, 0x86, 0x04, 0xd2, 0xb4, 0x40, 0x0c, 0x0e, 0x14, 0x9f, 0x0a, 0xfe, - 0x75, 0xf8, 0x85, 0xe4, 0xbf, 0x9b, 0x9a, 0x8f, 0xe1, 0x6f, 0x18, 0x9a, 0x49, 0x75, 0x9f, 0xd6, 0x51, 0xe2, 0xd5, - 0x70, 0xea, 0x85, 0x95, 0x50, 0x27, 0x43, 0x90, 0x8a, 0x21, 0x17, 0xe2, 0xe2, 0xe9, 0xe4, 0xb6, 0x14, 0xe1, 0x9f, - 0x13, 0x7c, 0x26, 0x57, 0x9a, 0x7c, 0x46, 0x4f, 0x1a, 0x59, 0xc0, 0xbd, 0x7c, 0x5f, 0xf6, 0x6a, 0x70, 0x53, 0x0f, - 0xf9, 0x6d, 0xed, 0xbe, 0x2f, 0xe7, 0x04, 0x3d, 0xb2, 0x1f, 0xd0, 0x1c, 0x0c, 0xd4, 0x0c, 0xa4, 0x0c, 0xc1, 0x2d, - 0x5c, 0xfa, 0x3d, 0x55, 0x90, 0x2f, 0xbf, 0xf7, 0x45, 0xc8, 0xc0, 0x95, 0x1b, 0xc2, 0x94, 0x4b, 0x85, 0x14, 0x38, - 0x6e, 0xeb, 0xc1, 0x17, 0x8d, 0x4e, 0x22, 0xc1, 0xa7, 0x04, 0x24, 0x49, 0xcb, 0x03, 0x49, 0x23, 0xa6, 0x03, 0x71, - 0xa1, 0x34, 0xcd, 0x4a, 0x8a, 0x38, 0xc4, 0xae, 0xfa, 0x16, 0x09, 0xcf, 0x82, 0xf7, 0x0c, 0xd6, 0x8e, 0x14, 0x2d, - 0xbe, 0x1a, 0xd3, 0xb1, 0x0e, 0x1b, 0x5a, 0xca, 0xe2, 0x3e, 0x4b, 0xea, 0x34, 0x12, 0x57, 0xde, 0x09, 0xf9, 0xf3, - 0x9f, 0x4a, 0x04, 0xd2, 0xbb, 0x1a, 0x88, 0x41, 0xf0, 0x03, 0xf4, 0x1f, 0xb0, 0xc8, 0x41, 0x50, 0xaa, 0xcb, 0x30, - 0xaf, 0x32, 0x2a, 0x70, 0xb6, 0x63, 0xdb, 0x39, 0x53, 0x75, 0x0b, 0xbe, 0x08, 0xc3, 0x90, 0x76, 0xb6, 0x6a, 0x4e, - 0x6e, 0xf5, 0x06, 0xea, 0x99, 0xc4, 0x91, 0x5a, 0x8a, 0x23, 0x6d, 0xcd, 0x7d, 0xba, 0xf0, 0xba, 0xe5, 0x05, 0x0d, - 0x17, 0xa0, 0x17, 0xa5, 0xbb, 0xce, 0x27, 0x14, 0xba, 0xac, 0xc6, 0xd5, 0x50, 0xd4, 0xa1, 0x1c, 0x63, 0xed, 0xcf, - 0x95, 0x3c, 0xbf, 0x03, 0xeb, 0x11, 0x1a, 0xbe, 0x2a, 0x75, 0x10, 0xdb, 0x4f, 0xf4, 0xae, 0x53, 0xa9, 0xbf, 0x01, - 0x60, 0xe0, 0xd4, 0xf1, 0x50, 0x1f, 0xb5, 0x53, 0xc8, 0x76, 0xee, 0x2d, 0x31, 0x2a, 0x57, 0xc2, 0x53, 0xa5, 0xe5, - 0x29, 0x65, 0xd5, 0xd7, 0x82, 0x5b, 0xd9, 0x7d, 0x36, 0x80, 0x8c, 0x36, 0x28, 0x90, 0x67, 0xd4, 0xd6, 0x78, 0x90, - 0x6a, 0x9a, 0x25, 0x8e, 0xe1, 0x83, 0x22, 0xcd, 0x2a, 0xb0, 0x78, 0x99, 0x4b, 0xe6, 0xa0, 0x60, 0xb9, 0xde, 0x6c, - 0xa6, 0x99, 0xea, 0x8b, 0xdc, 0xde, 0x68, 0xbc, 0x4c, 0xff, 0xcd, 0x92, 0x01, 0x8f, 0x2e, 0x9e, 0xfa, 0x01, 0xa4, - 0x49, 0x8a, 0x07, 0x48, 0x82, 0xed, 0xc1, 0x2e, 0x76, 0x18, 0xb6, 0x8a, 0x95, 0x3d, 0x79, 0xba, 0xdc, 0xa1, 0x29, - 0x97, 0xe0, 0x92, 0x13, 0x73, 0x39, 0xf5, 0x7d, 0xc9, 0x7a, 0x43, 0x71, 0xca, 0xa6, 0x09, 0x28, 0x09, 0xb4, 0x5b, - 0xf0, 0x5f, 0xf8, 0xd4, 0xd0, 0x69, 0x01, 0x96, 0xda, 0x6e, 0xc0, 0x7f, 0xa1, 0x5f, 0x6c, 0x77, 0x51, 0x3f, 0x30, - 0x0f, 0xf6, 0x66, 0x71, 0x65, 0x0c, 0x38, 0x49, 0x5c, 0x69, 0x1e, 0xb9, 0x7e, 0x50, 0xf4, 0xe9, 0xb2, 0x76, 0xe0, - 0x4c, 0x71, 0x61, 0x95, 0xda, 0x24, 0xbd, 0xf6, 0x5b, 0x6a, 0xe2, 0x4d, 0x94, 0x54, 0x85, 0xed, 0x90, 0xf6, 0x2f, - 0x29, 0x67, 0xaa, 0xb8, 0x43, 0xf4, 0x64, 0x37, 0x71, 0x15, 0x78, 0x61, 0x55, 0xb1, 0x11, 0x6a, 0x33, 0xb2, 0x9c, - 0xc0, 0xe9, 0x1e, 0xab, 0x0b, 0x3e, 0xb6, 0xab, 0xd9, 0x05, 0x2b, 0xd9, 0x9a, 0x49, 0xf7, 0x79, 0x3b, 0xe6, 0x42, - 0x5e, 0xe9, 0x65, 0xd1, 0x0a, 0x68, 0x0f, 0x02, 0x87, 0x5f, 0x68, 0xba, 0x47, 0xcf, 0x36, 0xdb, 0xd4, 0x66, 0x63, - 0x6b, 0x11, 0x42, 0x06, 0xa2, 0xa1, 0x2f, 0xe4, 0x8c, 0x22, 0x5f, 0xa5, 0xe5, 0x5a, 0x6d, 0xac, 0x32, 0x5e, 0x60, - 0x22, 0xc8, 0x70, 0x16, 0xde, 0xa1, 0xa7, 0xf5, 0x48, 0x53, 0x4c, 0x82, 0x93, 0x2e, 0xfe, 0x02, 0x6c, 0x28, 0x4f, - 0x72, 0x73, 0x40, 0x0e, 0xa0, 0x72, 0x29, 0x4a, 0xa5, 0x0c, 0xfe, 0x45, 0xdd, 0x91, 0x6d, 0xd5, 0x7f, 0xa7, 0x81, - 0x0c, 0xee, 0x40, 0xdf, 0xf6, 0x42, 0x6b, 0x47, 0x3b, 0x57, 0xb6, 0xa6, 0x6d, 0x91, 0xe6, 0x31, 0xb2, 0xd8, 0x00, - 0xf2, 0x89, 0x74, 0x0e, 0x44, 0x5e, 0x13, 0x8d, 0x77, 0xf6, 0x8c, 0x8f, 0xa7, 0xe2, 0x21, 0x79, 0xaf, 0xf2, 0x7d, - 0x73, 0xaf, 0x0f, 0xc6, 0xd8, 0xb7, 0xa0, 0x4c, 0x7c, 0xb0, 0xda, 0x5a, 0x97, 0x58, 0x6f, 0x95, 0x26, 0xd1, 0x0d, - 0x57, 0xd0, 0x71, 0x24, 0x6e, 0x10, 0x83, 0x63, 0xc6, 0x6b, 0xab, 0x2c, 0x7d, 0x85, 0x65, 0xae, 0x63, 0x96, 0x0c, - 0x99, 0xd4, 0x79, 0xa2, 0xe0, 0xc9, 0xcf, 0x13, 0x92, 0x11, 0x51, 0xb3, 0x2d, 0x47, 0x29, 0x37, 0x2d, 0xe0, 0x32, - 0x23, 0x03, 0xf8, 0x26, 0x4d, 0x00, 0xca, 0xe5, 0x4b, 0x90, 0x4a, 0x43, 0x04, 0xd7, 0x6c, 0x2f, 0x19, 0xdd, 0x3a, - 0x5a, 0x07, 0x55, 0x92, 0xb9, 0x83, 0x73, 0x3b, 0x8b, 0x94, 0x7a, 0xf3, 0x11, 0x86, 0x9d, 0x7c, 0x08, 0xeb, 0x04, - 0xbf, 0x0d, 0xa8, 0x49, 0x9f, 0x0a, 0x2f, 0x1a, 0x01, 0x9a, 0xfa, 0x4e, 0x95, 0xf1, 0xa9, 0xf0, 0xb2, 0xd1, 0x96, - 0x65, 0x94, 0x42, 0x75, 0xc1, 0xec, 0xd6, 0x74, 0x21, 0xe6, 0x55, 0x35, 0xd0, 0x06, 0xb9, 0x5d, 0xc7, 0x0c, 0x68, - 0xd4, 0x76, 0xe5, 0x91, 0x05, 0xb8, 0x35, 0x13, 0x81, 0x91, 0xf3, 0xef, 0xf3, 0x97, 0x2a, 0x9c, 0xa7, 0xdf, 0x0f, - 0xbd, 0xfd, 0x36, 0x88, 0x46, 0xdb, 0x4b, 0xb6, 0x0b, 0xa2, 0xd1, 0xee, 0xb2, 0x61, 0xf4, 0xfb, 0x29, 0xfd, 0x7e, - 0xda, 0x80, 0xaa, 0x44, 0x98, 0x88, 0x7b, 0xfd, 0x46, 0x2d, 0x5f, 0xa9, 0xf5, 0x3b, 0xb5, 0x7c, 0xa9, 0x86, 0xb7, - 0xf6, 0x24, 0x12, 0x44, 0x96, 0xc6, 0xe6, 0x5e, 0xb2, 0xa5, 0x5a, 0x2a, 0x1d, 0xa3, 0xca, 0x88, 0x5a, 0x3a, 0x9b, - 0x63, 0xc5, 0x48, 0x3b, 0x07, 0x25, 0x03, 0x32, 0x2d, 0xae, 0x6a, 0x4c, 0x37, 0x2b, 0x5a, 0x62, 0x32, 0xc2, 0xca, - 0xb6, 0xbc, 0xdd, 0xa4, 0x6a, 0x3a, 0x27, 0x37, 0xb7, 0x4a, 0xb9, 0xb9, 0x15, 0x3c, 0xff, 0x86, 0x6e, 0xb9, 0xe4, - 0xda, 0xcb, 0x6c, 0x5a, 0x28, 0xdd, 0x32, 0xae, 0xc1, 0xd6, 0xbe, 0x09, 0x64, 0x99, 0x0f, 0x14, 0x35, 0xb6, 0x17, - 0x8d, 0xf2, 0x0d, 0xb2, 0x15, 0x31, 0xea, 0x94, 0x05, 0xe3, 0x6f, 0x77, 0xf4, 0x40, 0x06, 0xaa, 0xaa, 0xda, 0x38, - 0xb8, 0xb3, 0xd2, 0x1f, 0x96, 0x17, 0x4f, 0x59, 0x62, 0xa5, 0x93, 0x0b, 0x55, 0xe8, 0x0f, 0x42, 0x74, 0x53, 0xd9, - 0x70, 0x70, 0xa8, 0x8b, 0xad, 0x0c, 0x08, 0x3d, 0x4c, 0xef, 0x6d, 0xac, 0x64, 0xb9, 0x6b, 0xca, 0x17, 0x33, 0x9e, - 0x70, 0x1c, 0x7d, 0xb9, 0x5a, 0x84, 0xb5, 0x5a, 0x64, 0x27, 0xc0, 0x43, 0x6b, 0xb5, 0x14, 0x72, 0xb5, 0x08, 0x67, - 0xa6, 0x0b, 0x35, 0xd3, 0x33, 0x50, 0x40, 0x0a, 0x35, 0xcb, 0x13, 0x80, 0x85, 0x17, 0x66, 0x86, 0x0b, 0x33, 0xc3, - 0x71, 0x48, 0x8d, 0xff, 0x83, 0xde, 0xeb, 0xdc, 0x73, 0xcb, 0xdd, 0xe8, 0x34, 0xe2, 0xdb, 0xd1, 0x06, 0x73, 0x7c, - 0x10, 0x4e, 0xaa, 0x7e, 0x3f, 0x2d, 0x11, 0xab, 0xc7, 0xc0, 0x08, 0xca, 0xa1, 0x72, 0xb4, 0x5f, 0x16, 0x96, 0x64, - 0x49, 0x58, 0x92, 0x7b, 0x35, 0xce, 0xa5, 0xe5, 0xe2, 0x55, 0x12, 0x88, 0x44, 0xc6, 0x4b, 0x69, 0x82, 0x4f, 0x78, - 0x39, 0x32, 0x52, 0xf3, 0xe4, 0x26, 0xf5, 0x72, 0x96, 0xb1, 0x31, 0x62, 0x18, 0x85, 0x7e, 0x53, 0xf5, 0xfb, 0x79, - 0xe9, 0xe5, 0xd4, 0xce, 0x4f, 0xe0, 0x7a, 0x79, 0xea, 0x2c, 0x72, 0x84, 0xbc, 0x1a, 0x49, 0x85, 0xe5, 0xb5, 0x52, - 0x4f, 0x5f, 0x82, 0x0f, 0xea, 0xee, 0x8d, 0x02, 0x20, 0x2e, 0x72, 0xe9, 0x5f, 0x5b, 0xc2, 0xa5, 0x29, 0x37, 0x30, - 0xe8, 0x21, 0xcf, 0x49, 0x08, 0x95, 0x20, 0x24, 0x85, 0x75, 0xe3, 0xbe, 0x78, 0x3a, 0x71, 0xdd, 0x59, 0x6c, 0x60, - 0x82, 0xc3, 0x01, 0x10, 0x0f, 0xa6, 0x5e, 0x34, 0xe0, 0xa5, 0x9a, 0x33, 0x1f, 0xbd, 0x9c, 0x60, 0x32, 0x40, 0x55, - 0x31, 0x70, 0xca, 0x7a, 0x22, 0x1f, 0x19, 0x37, 0x33, 0xdf, 0x0f, 0xf0, 0xdd, 0xba, 0x90, 0xe8, 0x0f, 0x0a, 0xa0, - 0x20, 0x53, 0x00, 0x05, 0x89, 0x01, 0x28, 0x88, 0x0d, 0x40, 0xc1, 0xa6, 0xe1, 0x4b, 0xa9, 0xc3, 0x8d, 0x80, 0x2e, - 0xc2, 0x87, 0x9e, 0x85, 0x8d, 0x15, 0x8a, 0x67, 0x63, 0x36, 0x66, 0x85, 0xda, 0x79, 0x72, 0x39, 0x15, 0x3b, 0x8b, - 0xb1, 0xae, 0x22, 0xeb, 0xc4, 0x0b, 0x09, 0x45, 0xce, 0xb9, 0x91, 0xa8, 0xbb, 0x9f, 0x7b, 0x2f, 0xc9, 0x58, 0x32, - 0x6f, 0x68, 0xd4, 0x60, 0x5e, 0x76, 0x1d, 0xc0, 0xb4, 0xe4, 0xdb, 0x82, 0x06, 0xd3, 0xa9, 0xf2, 0x88, 0x34, 0x09, - 0x6a, 0xe7, 0x32, 0x29, 0x72, 0x42, 0x98, 0x04, 0xbd, 0x12, 0xfc, 0x46, 0xa2, 0xfc, 0x7f, 0xd3, 0x09, 0x1e, 0xe0, - 0x98, 0x68, 0x95, 0x7c, 0x05, 0x03, 0x66, 0xce, 0x9f, 0x4b, 0xa7, 0x6c, 0x84, 0x62, 0x2c, 0xd3, 0x78, 0xf4, 0x95, - 0x0d, 0x11, 0xda, 0xea, 0x39, 0x9a, 0x98, 0xa0, 0x0e, 0xf0, 0x88, 0xfe, 0x1a, 0x7d, 0x35, 0x14, 0x2a, 0x5d, 0x8d, - 0xd4, 0x35, 0x3b, 0xe7, 0xfc, 0x5d, 0x6d, 0x38, 0x91, 0x31, 0x6d, 0x0a, 0x7c, 0x03, 0x02, 0xf9, 0x06, 0x02, 0xc0, - 0x55, 0xd3, 0x99, 0xbd, 0x02, 0x38, 0x07, 0x02, 0x78, 0x9c, 0x77, 0x3c, 0x7e, 0xa0, 0xbf, 0x8a, 0xe3, 0xde, 0x69, - 0x1a, 0xb6, 0xff, 0x0a, 0x8c, 0xc5, 0x50, 0x8e, 0xe7, 0x3b, 0x05, 0xc9, 0x1e, 0xa5, 0x2c, 0x5d, 0x35, 0x91, 0x1d, - 0x8a, 0xf5, 0x69, 0x4e, 0x19, 0x4b, 0xdb, 0x72, 0x8c, 0x36, 0x5e, 0x3f, 0xc4, 0xe3, 0x9b, 0x1b, 0x3d, 0xf9, 0xa0, - 0x07, 0xb7, 0xb7, 0x37, 0xaf, 0x7a, 0xcc, 0xe6, 0x5b, 0xb1, 0x78, 0x56, 0xc4, 0x89, 0xd3, 0x3a, 0xe4, 0x00, 0x07, - 0x39, 0x09, 0x81, 0x74, 0x8c, 0x4b, 0x2d, 0x3a, 0xa8, 0x59, 0xce, 0x6b, 0x60, 0x99, 0x45, 0x90, 0x0d, 0x10, 0xd5, - 0x34, 0x15, 0xab, 0xe1, 0x41, 0xa9, 0x9a, 0x53, 0x2a, 0xb5, 0x6f, 0x38, 0x5b, 0x9d, 0x3e, 0xb1, 0x6a, 0x13, 0x6e, - 0xfd, 0xb9, 0xf6, 0x04, 0x6d, 0x25, 0x0d, 0x84, 0x7a, 0xbe, 0x4a, 0x97, 0x14, 0xc5, 0xe3, 0xcc, 0xc4, 0x53, 0x15, - 0x18, 0xfb, 0xd6, 0x8e, 0xa0, 0x20, 0x69, 0xba, 0x0e, 0x38, 0x4c, 0xa3, 0x13, 0x16, 0xff, 0x94, 0x3e, 0x94, 0x17, - 0xb5, 0x02, 0x27, 0xf9, 0x87, 0x70, 0x11, 0x49, 0x2c, 0xf4, 0x4b, 0x02, 0x20, 0x91, 0xc1, 0xab, 0x51, 0xb1, 0x16, - 0x2a, 0x40, 0x4e, 0x51, 0x7a, 0xab, 0xf8, 0xb8, 0x14, 0xa5, 0x4a, 0xa9, 0xcc, 0x8d, 0x4a, 0x01, 0x61, 0x6d, 0xe0, - 0xe8, 0x02, 0xbe, 0x80, 0xa0, 0xb5, 0xdc, 0xad, 0x6d, 0xcf, 0x1b, 0x99, 0xcf, 0x4c, 0xf3, 0xb4, 0xfa, 0xa0, 0xfe, - 0x7e, 0xbf, 0xc0, 0x30, 0x1b, 0x4f, 0x7f, 0xdf, 0x66, 0x08, 0x37, 0x7f, 0xc3, 0x10, 0x2d, 0x01, 0x1c, 0xb3, 0xb4, - 0x87, 0x42, 0x16, 0x4c, 0xb0, 0x86, 0xaa, 0x3c, 0xe5, 0xb3, 0x97, 0x4f, 0x6e, 0x00, 0x4d, 0x0d, 0x5d, 0xdc, 0xe8, - 0x54, 0x57, 0x25, 0x08, 0xdf, 0x77, 0x85, 0x7a, 0x6c, 0x0e, 0x38, 0x35, 0x00, 0x14, 0x8b, 0xbc, 0xd6, 0x63, 0xfb, - 0x07, 0xbd, 0x51, 0x6f, 0x80, 0x78, 0x3a, 0xe7, 0x85, 0x7f, 0x44, 0xbf, 0x4e, 0xfd, 0x19, 0x17, 0x82, 0xa8, 0xd7, - 0x93, 0xf0, 0x4e, 0x9c, 0xa5, 0x71, 0x70, 0xd6, 0x1b, 0x98, 0x8b, 0x40, 0x71, 0x96, 0xe6, 0x67, 0x20, 0x96, 0x23, - 0x3c, 0x62, 0xcd, 0x56, 0x80, 0x18, 0x58, 0xea, 0x90, 0x64, 0xd5, 0xb1, 0xfd, 0xfe, 0xeb, 0x91, 0xe1, 0x4d, 0x47, - 0x44, 0x18, 0xfd, 0xbb, 0x02, 0x01, 0x0a, 0x96, 0x99, 0xed, 0xcc, 0xa4, 0xab, 0x3d, 0xab, 0xe7, 0xcd, 0x26, 0xef, - 0xea, 0x1d, 0xab, 0x69, 0x39, 0x35, 0xad, 0xb2, 0x9a, 0x36, 0xc9, 0xa1, 0x66, 0xa2, 0xdf, 0xd7, 0xf8, 0xa8, 0xf9, - 0x1c, 0x70, 0xd9, 0x30, 0xf9, 0xf5, 0xac, 0x9a, 0xf7, 0xfb, 0x9e, 0x7c, 0x04, 0xbf, 0x90, 0xb8, 0xcc, 0xad, 0xb1, - 0x7c, 0xfa, 0x86, 0xf8, 0xcc, 0x0c, 0xe2, 0xd1, 0xea, 0x08, 0xea, 0xeb, 0x5a, 0x78, 0x1d, 0x73, 0x85, 0xcd, 0xc4, - 0xf4, 0x35, 0x0c, 0x9e, 0x27, 0x7c, 0xf0, 0x96, 0xa3, 0xbf, 0x91, 0xce, 0x4c, 0xc1, 0x42, 0xce, 0xfd, 0xc9, 0x6b, - 0x84, 0x4e, 0x46, 0xa4, 0x07, 0x9d, 0x4e, 0xd0, 0x90, 0xfd, 0xfe, 0x2d, 0x74, 0x66, 0x2b, 0x95, 0xb2, 0x55, 0x51, - 0x99, 0xae, 0xeb, 0xa2, 0xac, 0xa0, 0x63, 0xe9, 0xe7, 0xad, 0x90, 0x99, 0xf5, 0x33, 0x0b, 0xf9, 0xe9, 0x56, 0x62, - 0x4d, 0xd9, 0xf6, 0x89, 0xda, 0x20, 0xcd, 0xba, 0x50, 0x5d, 0xe0, 0xdc, 0x59, 0x7b, 0xbd, 0x11, 0xea, 0x9f, 0xf3, - 0xd1, 0xba, 0x58, 0x7b, 0xe0, 0x12, 0x33, 0x4b, 0xe7, 0x8a, 0x43, 0x23, 0xf7, 0x47, 0x5f, 0x8a, 0x34, 0xa7, 0x3c, - 0x40, 0x83, 0x28, 0xe6, 0xf6, 0x5b, 0x20, 0xfd, 0xd0, 0x5b, 0x20, 0xfb, 0xe8, 0x9c, 0x93, 0xd7, 0x00, 0x4e, 0x87, - 0x88, 0xb8, 0x15, 0x09, 0x3a, 0x56, 0x0d, 0x6f, 0x2c, 0xdc, 0xd3, 0x5e, 0x1a, 0xf7, 0xd2, 0xfc, 0x2c, 0xed, 0xf7, - 0x0d, 0x80, 0x66, 0x8a, 0xc8, 0xf0, 0x38, 0x23, 0x77, 0x49, 0x0b, 0xc1, 0x94, 0xf6, 0x5f, 0x8d, 0x21, 0x41, 0x20, - 0xe0, 0xff, 0x10, 0xde, 0x23, 0x40, 0xdb, 0xa4, 0x0d, 0xb8, 0xea, 0x31, 0x1d, 0x98, 0x2d, 0x39, 0x5b, 0x75, 0x36, - 0x00, 0xe5, 0x54, 0x69, 0x3d, 0xe5, 0x71, 0x4d, 0x11, 0x91, 0x2a, 0x0b, 0xf5, 0x1b, 0xeb, 0xc9, 0x64, 0x95, 0x8b, - 0x0c, 0x39, 0x2a, 0xd3, 0xbb, 0x9a, 0x11, 0x62, 0x97, 0x7e, 0x7e, 0x03, 0x4b, 0x36, 0xfe, 0x88, 0x93, 0xb7, 0x04, - 0x48, 0xdb, 0x59, 0xbb, 0xaa, 0x76, 0x39, 0x6e, 0xed, 0xe6, 0x80, 0xe4, 0xeb, 0x8d, 0x46, 0x23, 0xed, 0x27, 0x27, - 0x60, 0xa8, 0x7a, 0x6a, 0x29, 0xf4, 0x58, 0xad, 0xb0, 0x75, 0x3b, 0x72, 0x99, 0x25, 0x83, 0xf9, 0xc2, 0x38, 0x7e, - 0x69, 0x3e, 0xfa, 0x70, 0xa9, 0xac, 0x5d, 0x47, 0x7c, 0xfd, 0x47, 0x59, 0xad, 0xef, 0x79, 0x57, 0x35, 0x01, 0x5f, - 0x54, 0xb1, 0xa5, 0xdf, 0xf1, 0x9e, 0xec, 0x5d, 0x7c, 0xed, 0x1a, 0xbb, 0xe4, 0x7b, 0xde, 0xa2, 0xce, 0xf3, 0x95, - 0xaf, 0x1b, 0x55, 0xba, 0xbd, 0x97, 0xdc, 0xe0, 0xda, 0x3b, 0x6a, 0x1a, 0xeb, 0x99, 0x1f, 0x3d, 0x2c, 0x42, 0xb6, - 0xf3, 0xa1, 0xf7, 0x55, 0xf3, 0xf4, 0xac, 0xa1, 0x37, 0xa9, 0xa1, 0x0f, 0xbd, 0x28, 0xdb, 0xa7, 0xa6, 0x11, 0xbd, - 0x86, 0x0d, 0x7d, 0xe8, 0x2d, 0x39, 0x39, 0x24, 0x18, 0x9c, 0x1a, 0xf3, 0x87, 0x87, 0xd3, 0x19, 0xfe, 0x8e, 0x01, - 0x95, 0x98, 0xcc, 0xa7, 0xc7, 0xb4, 0xa3, 0x00, 0x33, 0xaa, 0xf4, 0xf6, 0xe9, 0x81, 0xed, 0x78, 0x59, 0x0f, 0x2d, - 0xbd, 0x7b, 0x72, 0x74, 0x3b, 0x5e, 0x55, 0xe3, 0x4b, 0x39, 0xe4, 0x79, 0x3e, 0x1b, 0x8d, 0x46, 0xc2, 0xa0, 0x73, - 0x57, 0x7a, 0x03, 0x2b, 0x90, 0xc1, 0x45, 0xf5, 0xa1, 0x5c, 0x7a, 0x3b, 0x75, 0x68, 0x57, 0xfe, 0x24, 0x3f, 0x1c, - 0x8a, 0x91, 0x39, 0xc6, 0x01, 0xe7, 0xa4, 0x50, 0x72, 0x94, 0xac, 0x25, 0x88, 0x4e, 0x69, 0x3c, 0x95, 0xf5, 0xda, - 0x8a, 0xc8, 0xab, 0x11, 0xf2, 0x21, 0xf8, 0xc9, 0x03, 0xb5, 0xf8, 0x33, 0x2d, 0x88, 0x3d, 0xf4, 0xa9, 0x52, 0x3a, - 0xc4, 0xab, 0x02, 0x42, 0x84, 0x01, 0x6f, 0xa0, 0x1d, 0x94, 0xe0, 0xb0, 0xc3, 0x7d, 0x40, 0x84, 0xe8, 0x37, 0x5e, - 0x3e, 0x93, 0xe1, 0xca, 0xbd, 0x41, 0x35, 0x67, 0x80, 0x58, 0xe9, 0x33, 0x70, 0xc1, 0x04, 0xd4, 0x53, 0x7c, 0x8a, - 0xfe, 0xf5, 0xe6, 0x61, 0xd3, 0xf5, 0x69, 0x09, 0xa8, 0x88, 0x9e, 0xfd, 0x7c, 0x0c, 0xe0, 0x9d, 0x5d, 0x9b, 0x91, - 0xf6, 0xf2, 0x37, 0xc0, 0xb0, 0x52, 0x92, 0x68, 0xe7, 0x94, 0x08, 0xdc, 0xf9, 0xc8, 0x96, 0x7e, 0x94, 0x02, 0x31, - 0x77, 0x3c, 0x49, 0x64, 0x0f, 0x36, 0x72, 0x02, 0xb7, 0x18, 0xf0, 0xe8, 0x00, 0x54, 0xae, 0x14, 0xe4, 0x5e, 0x73, - 0x24, 0x77, 0xfc, 0xd0, 0xfb, 0x61, 0x50, 0x0f, 0x7e, 0xe8, 0x9d, 0xa5, 0x24, 0x77, 0x84, 0x67, 0x6a, 0x4a, 0x88, - 0xf8, 0xec, 0x87, 0x41, 0x3e, 0xc0, 0xb3, 0x44, 0x8b, 0xb4, 0xc8, 0xad, 0x26, 0x6a, 0xdc, 0x84, 0x77, 0x89, 0xa4, - 0x21, 0xda, 0x76, 0x1e, 0x11, 0x37, 0x00, 0x92, 0xc5, 0x67, 0xf3, 0x86, 0xa2, 0xde, 0x4d, 0xf8, 0x16, 0xdd, 0x65, - 0xb1, 0xdf, 0xdf, 0xe4, 0x69, 0xdd, 0xd3, 0xa1, 0x32, 0xf8, 0x82, 0x54, 0x13, 0xe0, 0xd1, 0xfe, 0xca, 0x1c, 0xaf, - 0x5e, 0x6d, 0x8e, 0x94, 0x1b, 0x55, 0xa2, 0x7e, 0x8b, 0xd5, 0xac, 0x87, 0x88, 0xdc, 0x59, 0x66, 0xec, 0xed, 0x05, - 0xaf, 0xe4, 0xac, 0x8a, 0xed, 0x72, 0x7c, 0x45, 0x58, 0x5b, 0x49, 0x80, 0x8e, 0xd6, 0x63, 0x6d, 0x8a, 0x91, 0x5f, - 0x29, 0x24, 0xe0, 0xa2, 0x63, 0x6b, 0xa1, 0xd8, 0x78, 0x01, 0xfa, 0x92, 0x9d, 0x69, 0x80, 0xf5, 0x46, 0xaf, 0x22, - 0x6e, 0xcb, 0x07, 0x2a, 0xbc, 0xc9, 0x4d, 0x95, 0x59, 0xd9, 0xdc, 0xb4, 0xfb, 0xa9, 0xe2, 0x15, 0xe2, 0xd6, 0x1b, - 0xb5, 0x47, 0x01, 0x6a, 0x0f, 0x2d, 0x94, 0x01, 0xba, 0x34, 0xcd, 0x00, 0x90, 0x01, 0x40, 0xa6, 0x8a, 0xf8, 0x4c, - 0x80, 0x4a, 0x5b, 0xdd, 0x28, 0x70, 0x22, 0xbd, 0x01, 0xc6, 0x05, 0x56, 0xfa, 0xc8, 0x46, 0x06, 0x8b, 0x2d, 0x02, - 0xdc, 0x72, 0xa4, 0x0f, 0xd3, 0x70, 0xb2, 0x8d, 0xe6, 0x30, 0x49, 0xf3, 0xbb, 0x30, 0x4b, 0x25, 0xb4, 0xc4, 0x8f, - 0xb2, 0xc6, 0x88, 0x05, 0xa4, 0xef, 0xd3, 0x37, 0x45, 0x16, 0x13, 0x24, 0x9c, 0xf5, 0xd4, 0x01, 0x54, 0x93, 0x73, - 0xad, 0x69, 0xf5, 0xac, 0x36, 0x79, 0xc8, 0x02, 0x9d, 0x3d, 0x18, 0x93, 0x5a, 0x6e, 0xe8, 0x91, 0xfd, 0x95, 0xe3, - 0x19, 0xe1, 0xbb, 0x9e, 0xe1, 0xd4, 0x7f, 0xd7, 0x35, 0x90, 0x32, 0x25, 0x80, 0x20, 0x83, 0xa3, 0x09, 0xa1, 0x3c, - 0x1d, 0x93, 0xa9, 0xcd, 0x8f, 0x40, 0x38, 0x22, 0x78, 0x05, 0xcf, 0x0d, 0xad, 0x5b, 0x6e, 0xec, 0x2c, 0xf2, 0x34, - 0x01, 0x64, 0xf1, 0x82, 0xdf, 0x01, 0x32, 0xa7, 0x5e, 0x15, 0xb2, 0x67, 0xcf, 0xc5, 0x74, 0x36, 0x0f, 0xfe, 0x4c, - 0x68, 0xff, 0x62, 0xc2, 0x6f, 0xba, 0xab, 0xe4, 0xca, 0xd4, 0xba, 0x37, 0xd1, 0x63, 0x2e, 0x77, 0xfa, 0xb4, 0xe2, - 0x18, 0xf1, 0x0c, 0x56, 0x01, 0x39, 0x67, 0x43, 0xfe, 0xec, 0x1c, 0xb0, 0x5b, 0x56, 0xc2, 0x8b, 0xf8, 0xb3, 0x50, - 0x56, 0x0b, 0x90, 0x1f, 0x39, 0x8f, 0xcc, 0x2f, 0x5f, 0x6d, 0x87, 0x72, 0x4e, 0x51, 0x44, 0xcb, 0xa9, 0x69, 0x49, - 0x21, 0x3b, 0xf4, 0x14, 0x4c, 0xa6, 0xb6, 0xfc, 0x7d, 0x97, 0xb8, 0x24, 0xdf, 0x4c, 0x22, 0xfb, 0x3a, 0xc0, 0x9a, - 0xb5, 0xea, 0x1e, 0xba, 0x21, 0x18, 0x20, 0x32, 0x42, 0x99, 0xcd, 0xf5, 0xdd, 0x7a, 0x30, 0x50, 0x30, 0xbf, 0x82, - 0x6e, 0x5a, 0x74, 0x8a, 0x03, 0xe4, 0xac, 0x75, 0x8d, 0x4a, 0x55, 0x71, 0xe8, 0x30, 0xef, 0x96, 0x55, 0xd9, 0x65, - 0xe9, 0x85, 0x20, 0x35, 0xea, 0x2a, 0x58, 0xa4, 0x54, 0x44, 0xf1, 0x9e, 0xfc, 0x1a, 0x98, 0x78, 0x66, 0xe5, 0x28, - 0x8d, 0xe7, 0x80, 0x18, 0xa4, 0x80, 0x38, 0xe5, 0x57, 0x80, 0x26, 0xba, 0x88, 0xc2, 0xec, 0x4d, 0x5c, 0x05, 0xb5, - 0xd5, 0xf4, 0x7b, 0x07, 0x32, 0xf6, 0xbc, 0xee, 0xf7, 0x53, 0x62, 0xf4, 0xc3, 0x28, 0x0c, 0xfc, 0x7b, 0x3c, 0xdd, - 0x37, 0x41, 0x6a, 0x5e, 0xf9, 0x13, 0x5e, 0xd1, 0xe5, 0xd6, 0xa6, 0x5c, 0xd1, 0xb8, 0xf0, 0xd7, 0x08, 0x0e, 0x9f, - 0x3a, 0x8a, 0xed, 0x36, 0x55, 0x4e, 0x6d, 0x0c, 0x06, 0x21, 0xdc, 0xb7, 0x32, 0x7e, 0x9f, 0x78, 0xf9, 0x2c, 0x9a, - 0x83, 0xa2, 0x34, 0xd3, 0x7c, 0x21, 0x85, 0x74, 0x13, 0xa0, 0x8f, 0x06, 0xa1, 0x56, 0x57, 0x5e, 0x27, 0x5e, 0xaa, - 0xa6, 0xb5, 0x79, 0x8a, 0x35, 0x0a, 0xc4, 0x2c, 0x9a, 0x37, 0x2c, 0xa3, 0x43, 0x52, 0x5d, 0x2e, 0x4d, 0x33, 0xae, - 0xad, 0x66, 0xa8, 0x56, 0x1c, 0x35, 0x41, 0x8d, 0xd2, 0x35, 0x5c, 0x00, 0x7f, 0xa6, 0x3b, 0x8e, 0x6a, 0x14, 0x29, - 0x1a, 0xf0, 0x09, 0x62, 0xc4, 0x9a, 0xcd, 0x13, 0xd6, 0x9a, 0xba, 0x66, 0xf4, 0xfb, 0x32, 0x64, 0xc8, 0x24, 0x21, - 0x4f, 0x1f, 0x2e, 0xd7, 0x8f, 0xa4, 0xba, 0x00, 0x7e, 0xe5, 0x8a, 0xcd, 0x7a, 0xbd, 0x39, 0xc0, 0xf5, 0xc2, 0xfa, - 0x85, 0x8d, 0x2b, 0x38, 0xbf, 0x24, 0xf8, 0x5d, 0xf5, 0x23, 0xcc, 0x32, 0xa8, 0x02, 0x32, 0xfe, 0x58, 0x28, 0xea, - 0x79, 0x8b, 0xd9, 0x7d, 0xa4, 0x2e, 0x28, 0xb3, 0x74, 0x6e, 0x71, 0x82, 0x80, 0xf3, 0xb0, 0x7a, 0x02, 0xc9, 0xbe, - 0x7c, 0xec, 0xd3, 0x8c, 0x02, 0xd5, 0x11, 0xe0, 0xb3, 0x59, 0x3f, 0x84, 0xfd, 0x03, 0x22, 0x0b, 0xf5, 0x37, 0xdf, - 0xca, 0x59, 0x43, 0xf2, 0x40, 0xaa, 0xb9, 0x8f, 0xe1, 0xd4, 0xb8, 0xc1, 0x97, 0x6e, 0x7a, 0x53, 0xc1, 0x6b, 0x42, - 0xe6, 0xbe, 0x41, 0x6b, 0xdf, 0x0d, 0x1c, 0x21, 0x82, 0xcb, 0x28, 0xc5, 0x69, 0x6f, 0xd7, 0x0b, 0x90, 0xdb, 0xdc, - 0x82, 0xbc, 0x7e, 0xe9, 0xe2, 0x17, 0xa7, 0x48, 0xcf, 0xa2, 0x0b, 0x0c, 0x74, 0x41, 0xe6, 0x8d, 0x7f, 0x56, 0xb0, - 0x72, 0x01, 0xbd, 0x97, 0x8a, 0x95, 0x9c, 0x6c, 0x3b, 0xf5, 0x47, 0xa9, 0xec, 0xb7, 0x67, 0xd6, 0x04, 0x7e, 0x9f, - 0xd8, 0x2f, 0x91, 0xc9, 0x37, 0x3d, 0x36, 0xf9, 0xca, 0xb0, 0xe8, 0xd4, 0x32, 0x38, 0xa7, 0x47, 0x06, 0xe7, 0xde, - 0xce, 0xaa, 0x4d, 0x08, 0x43, 0x41, 0x12, 0x68, 0xba, 0xf0, 0xb0, 0x6e, 0xfa, 0xf3, 0x93, 0x16, 0xd5, 0x56, 0xed, - 0x5b, 0xf7, 0xe3, 0x10, 0xbb, 0xf8, 0x7d, 0xe2, 0x19, 0x22, 0x52, 0x1f, 0xe8, 0xc0, 0x64, 0xf0, 0xc4, 0x65, 0xbf, - 0x0f, 0x85, 0xcd, 0xc6, 0xf3, 0x51, 0x5d, 0xfc, 0x52, 0xdc, 0x03, 0xaa, 0x43, 0x05, 0x76, 0x39, 0x94, 0xa1, 0x8c, - 0xd8, 0xd4, 0x96, 0x7b, 0xfe, 0x78, 0x19, 0xe6, 0x20, 0xef, 0x68, 0x78, 0x9c, 0x33, 0x10, 0xc3, 0xe0, 0xeb, 0x3f, - 0x3c, 0xda, 0xa7, 0xcd, 0x0f, 0x67, 0xf0, 0xdd, 0xd1, 0xd9, 0x07, 0xa4, 0xbb, 0x39, 0x5b, 0x97, 0xc5, 0x5d, 0x1a, - 0x8b, 0xb3, 0x1f, 0x20, 0xf5, 0x87, 0xb3, 0xa2, 0x3c, 0xfb, 0x41, 0x55, 0xe6, 0x87, 0x33, 0x5a, 0x70, 0xa3, 0x3f, - 0xac, 0x89, 0xf7, 0x7b, 0xa5, 0x19, 0xd0, 0x16, 0x10, 0x99, 0xa5, 0xd5, 0x8f, 0xa0, 0x44, 0x54, 0xfc, 0xa8, 0x32, - 0xaa, 0xd5, 0xda, 0x71, 0x3e, 0x24, 0x1a, 0x29, 0x9b, 0x26, 0x24, 0xae, 0x96, 0xb0, 0x0e, 0xf5, 0xec, 0xb4, 0xf9, - 0x76, 0x9c, 0x07, 0xea, 0x80, 0xc8, 0xf9, 0xb3, 0x7c, 0xb4, 0xa5, 0xaf, 0xc1, 0xb7, 0x0e, 0x87, 0x7c, 0xb4, 0x33, - 0x3f, 0x7d, 0xb2, 0x56, 0xca, 0xb8, 0x23, 0x45, 0x2e, 0x84, 0x9c, 0x71, 0xdb, 0x1e, 0x03, 0x0e, 0x00, 0xff, 0x70, - 0xa0, 0xdf, 0x3b, 0xf9, 0x5b, 0xed, 0x96, 0x56, 0x3d, 0x1f, 0xb5, 0xb8, 0x33, 0xde, 0xd4, 0x86, 0xa8, 0x6d, 0x2f, - 0xb1, 0xa5, 0xf7, 0x4d, 0x83, 0x9a, 0x22, 0xfa, 0x09, 0xab, 0x89, 0x55, 0x1c, 0x16, 0xa4, 0x84, 0x24, 0x86, 0x63, - 0xb4, 0x43, 0x8f, 0xd3, 0xc5, 0xd2, 0x93, 0xfb, 0x0e, 0x2f, 0xb7, 0xbe, 0x0f, 0x48, 0x5a, 0x85, 0xf3, 0x0f, 0x5e, - 0x68, 0xe0, 0xd1, 0x8b, 0xbc, 0x2a, 0x32, 0x31, 0x12, 0x34, 0xca, 0x6f, 0x48, 0x9c, 0x39, 0xc3, 0x5a, 0x9c, 0x29, - 0xb0, 0xb0, 0x90, 0xd0, 0xbd, 0x8b, 0x92, 0xd2, 0x83, 0xb3, 0x47, 0xfb, 0xb2, 0xf9, 0x83, 0xe0, 0x21, 0x46, 0x37, - 0xc0, 0x88, 0xb3, 0x6b, 0x97, 0x77, 0x1f, 0x96, 0xb9, 0xf7, 0xc7, 0x9b, 0x65, 0x5e, 0x40, 0x88, 0xe6, 0x99, 0x54, - 0xac, 0x96, 0x67, 0xc0, 0x98, 0x27, 0xe2, 0xb3, 0xb0, 0x92, 0xd3, 0xa0, 0xea, 0x28, 0x56, 0x6f, 0xe3, 0xb9, 0x07, - 0x14, 0xdf, 0x1f, 0x12, 0xe0, 0x72, 0xf7, 0xd9, 0x6b, 0xe5, 0x9a, 0x4a, 0x7a, 0xe4, 0x39, 0x44, 0x4b, 0xbe, 0x4c, - 0x80, 0xe2, 0x19, 0xe2, 0x24, 0x85, 0xd5, 0x73, 0x13, 0xa4, 0x22, 0x5f, 0x9f, 0x50, 0x7c, 0xd1, 0x3c, 0x8a, 0x1a, - 0x16, 0xb2, 0x04, 0x8e, 0x87, 0x64, 0x96, 0xcd, 0x91, 0xa5, 0x3c, 0x6d, 0x4f, 0x91, 0x8e, 0x4e, 0x2c, 0xf1, 0xdb, - 0x9a, 0x5f, 0x2f, 0x52, 0x11, 0x98, 0xb4, 0xb3, 0x95, 0xb9, 0x17, 0xc2, 0x50, 0x25, 0xdc, 0x7b, 0x53, 0xcf, 0x42, - 0xb9, 0x29, 0x5a, 0x15, 0xb3, 0x87, 0x29, 0x31, 0xc3, 0x14, 0xeb, 0x2f, 0x6c, 0xf8, 0xdb, 0xc4, 0x8b, 0xc1, 0x70, - 0xbd, 0xe0, 0xe5, 0x6c, 0x63, 0x16, 0xc2, 0xe1, 0xb0, 0x99, 0x14, 0xb3, 0x05, 0x84, 0xb9, 0x2e, 0xe6, 0x87, 0x43, - 0x57, 0xcb, 0xd6, 0xc2, 0x83, 0x87, 0xaa, 0x85, 0x9b, 0x86, 0xe5, 0xf0, 0x33, 0x99, 0xc5, 0xd8, 0xbe, 0xc6, 0x67, - 0xf6, 0xe7, 0x8b, 0xee, 0x59, 0x82, 0xe4, 0x1b, 0x6b, 0xa0, 0x1d, 0x9b, 0xb5, 0x3b, 0x5c, 0x8d, 0x80, 0xa4, 0x74, - 0x37, 0xfa, 0xbb, 0xb2, 0x93, 0xa7, 0x04, 0xb9, 0xa3, 0x15, 0xd8, 0xef, 0xbe, 0xf1, 0x27, 0x5a, 0xec, 0x41, 0xbb, - 0x8d, 0x2d, 0x21, 0xaa, 0x69, 0xcf, 0xe5, 0x4a, 0xb1, 0x34, 0x6f, 0xa5, 0x8d, 0x9e, 0x0f, 0xeb, 0x73, 0xdf, 0xc8, - 0x81, 0x82, 0x31, 0xe2, 0xa9, 0x75, 0x10, 0xcd, 0xe6, 0x40, 0x83, 0x81, 0xe6, 0x11, 0x9e, 0x5a, 0xe8, 0xa0, 0xcc, - 0xda, 0xb0, 0x9f, 0x27, 0x27, 0xcb, 0xe3, 0xf0, 0x2d, 0xfc, 0xcb, 0x67, 0xd8, 0x24, 0xa6, 0xd8, 0x1e, 0xff, 0xaa, - 0x14, 0x15, 0x1e, 0xdb, 0x11, 0xd7, 0xda, 0xb5, 0xa8, 0x0d, 0x95, 0xc3, 0xbf, 0x84, 0x7d, 0x84, 0xfd, 0x85, 0x26, - 0x08, 0x83, 0x5d, 0x7f, 0x26, 0x10, 0x22, 0x16, 0xe2, 0x05, 0xff, 0xaa, 0x24, 0x15, 0x9d, 0xf0, 0xd9, 0xae, 0x04, - 0xde, 0x3a, 0x0c, 0xe8, 0x13, 0xf2, 0x33, 0x91, 0x30, 0x34, 0x13, 0x7a, 0x47, 0xff, 0x9d, 0xd8, 0xc9, 0x26, 0xb9, - 0x15, 0xf2, 0x81, 0xa4, 0x92, 0x60, 0x82, 0x95, 0x17, 0xca, 0x1f, 0xdd, 0x0b, 0xa5, 0xd6, 0x5a, 0xd0, 0xfa, 0xe5, - 0xcf, 0x13, 0xcf, 0xe0, 0xef, 0x81, 0x8c, 0x41, 0xb7, 0x11, 0xd5, 0x24, 0xc7, 0xf4, 0x51, 0x3a, 0xcf, 0x40, 0x05, - 0x74, 0xb6, 0xce, 0xc2, 0x7a, 0x51, 0x94, 0xab, 0x56, 0xa4, 0xa8, 0x2c, 0x7d, 0xa4, 0x1e, 0x63, 0x5e, 0x98, 0x27, - 0x27, 0xf2, 0xc1, 0x23, 0x00, 0xc6, 0xa3, 0x3c, 0xad, 0x3a, 0x4a, 0xeb, 0x07, 0x96, 0x01, 0x23, 0x70, 0xa2, 0x0c, - 0x78, 0x84, 0x65, 0x60, 0x9e, 0x76, 0x19, 0x6a, 0x10, 0x6b, 0x54, 0x5d, 0xa9, 0x0d, 0xe6, 0x44, 0x51, 0xf2, 0x29, - 0x96, 0x56, 0x18, 0x43, 0x53, 0x57, 0x1e, 0x59, 0x2f, 0x39, 0x61, 0x4f, 0x76, 0x03, 0xe9, 0x16, 0x36, 0x0a, 0x67, - 0xd0, 0xb5, 0x2c, 0x51, 0x2e, 0xba, 0x65, 0x44, 0x99, 0x08, 0xa9, 0x9f, 0x3d, 0x9c, 0x69, 0xb5, 0xdf, 0xd8, 0x49, - 0xfb, 0xf6, 0x48, 0xd1, 0x0b, 0x06, 0xed, 0xd3, 0x1e, 0x29, 0xf5, 0xac, 0x91, 0xcb, 0xc0, 0x96, 0x2e, 0x55, 0x3d, - 0xff, 0x05, 0xca, 0x77, 0x30, 0x33, 0xce, 0x66, 0x7f, 0xe8, 0xcd, 0xed, 0xd1, 0xbe, 0x6e, 0xfe, 0x60, 0xbd, 0x1e, - 0x6c, 0x0d, 0x32, 0xf1, 0xb9, 0x62, 0xa1, 0xb2, 0x0a, 0xb1, 0x82, 0xb4, 0xff, 0x25, 0xbc, 0x3f, 0xe0, 0xad, 0x11, - 0x9a, 0x95, 0xf1, 0x30, 0x1f, 0x3d, 0xda, 0x8b, 0xe6, 0x8f, 0xce, 0xb2, 0xad, 0x5c, 0x95, 0xcc, 0xf6, 0xc7, 0x51, - 0xd2, 0x9c, 0x3d, 0x5c, 0x23, 0xa9, 0x03, 0x7c, 0xb8, 0x3e, 0xc3, 0x07, 0x2a, 0xa1, 0xd4, 0x82, 0xaa, 0x06, 0xad, - 0x8f, 0xfd, 0xd1, 0x7a, 0x4e, 0x1f, 0x3f, 0x96, 0xd3, 0x2d, 0x29, 0xc2, 0xf8, 0x81, 0xc1, 0x94, 0x9d, 0x38, 0x75, - 0xc9, 0x9b, 0x21, 0xbd, 0xeb, 0x56, 0x49, 0x5d, 0xf6, 0x28, 0x11, 0x84, 0x3a, 0x58, 0xbf, 0xd8, 0x0f, 0x61, 0x66, - 0x8b, 0xfe, 0xb0, 0x59, 0xcd, 0x09, 0x10, 0x11, 0xd0, 0x5a, 0xe5, 0x7d, 0xe0, 0x98, 0x2f, 0xcc, 0x9a, 0x1b, 0xd2, - 0xad, 0x37, 0x57, 0xda, 0x2b, 0x29, 0xa0, 0x9f, 0x83, 0xcc, 0xed, 0xa3, 0x5b, 0xae, 0x5a, 0xe6, 0xb9, 0xb4, 0xe5, - 0x80, 0x45, 0x0b, 0x81, 0x9a, 0x9d, 0x4b, 0x87, 0x03, 0x05, 0xa1, 0xae, 0x44, 0x15, 0x71, 0x75, 0x14, 0x2d, 0x44, - 0xad, 0x56, 0xed, 0x72, 0xb2, 0xa9, 0x90, 0x2d, 0x89, 0x20, 0xa3, 0x14, 0x43, 0x97, 0x3e, 0xca, 0xd5, 0x9e, 0x69, - 0x38, 0x40, 0x13, 0xb0, 0x69, 0x83, 0xbf, 0x05, 0xee, 0x65, 0x70, 0x66, 0xda, 0xa7, 0x61, 0x04, 0x9c, 0xe6, 0x10, - 0xf3, 0xe7, 0x77, 0x3d, 0xa8, 0xe0, 0x41, 0x47, 0xfa, 0x9b, 0x7a, 0x56, 0xe0, 0x99, 0x7b, 0xe2, 0xf9, 0xeb, 0x13, - 0xe9, 0x45, 0x0e, 0x0f, 0x34, 0x0d, 0x62, 0xc6, 0x9f, 0x97, 0x65, 0xb8, 0x1b, 0x2d, 0xca, 0x62, 0xe5, 0x45, 0x7a, - 0x1f, 0xcf, 0xa4, 0x18, 0x48, 0xcc, 0x98, 0x19, 0x5d, 0xc5, 0x3a, 0xce, 0x61, 0xdc, 0xdb, 0x93, 0xb0, 0x42, 0xfb, - 0x67, 0x89, 0xbd, 0x2e, 0x00, 0xcb, 0x21, 0x6b, 0xd0, 0x0a, 0xef, 0x74, 0x7b, 0xbb, 0xc7, 0x25, 0x3b, 0x8a, 0x1b, - 0x40, 0x3f, 0xab, 0xa1, 0x65, 0x82, 0x5a, 0x66, 0xdd, 0xc9, 0x64, 0x8a, 0xe4, 0xf2, 0x6d, 0xd8, 0x6b, 0x56, 0xe4, - 0xf3, 0x46, 0x6e, 0x0f, 0xef, 0xc2, 0x95, 0x88, 0xb5, 0x05, 0x9d, 0x74, 0x64, 0x1c, 0xee, 0x85, 0xe6, 0x46, 0xba, - 0x7f, 0x54, 0x25, 0x61, 0x29, 0x62, 0xb8, 0x05, 0xb2, 0xbd, 0xda, 0x56, 0x82, 0x12, 0xf8, 0x60, 0x3f, 0x94, 0x62, - 0x91, 0x6e, 0x05, 0xe0, 0x3a, 0xf0, 0xcf, 0x12, 0x91, 0xd0, 0xdd, 0x79, 0x88, 0x62, 0x8d, 0xbc, 0x6f, 0x10, 0x8d, - 0xfd, 0x15, 0xc8, 0x69, 0x40, 0x26, 0x52, 0x8c, 0x64, 0xc1, 0xc0, 0x07, 0x90, 0xf3, 0x35, 0x98, 0xe4, 0xa6, 0xb9, - 0xe7, 0x07, 0xb9, 0xee, 0x60, 0xda, 0x07, 0xdd, 0x8b, 0x6b, 0xcd, 0x72, 0xf0, 0x8a, 0x89, 0xf8, 0xcf, 0xb5, 0x57, - 0xb2, 0x9c, 0x65, 0x7e, 0x63, 0x2e, 0x3a, 0x19, 0x5c, 0x35, 0x84, 0x5f, 0xcc, 0xb2, 0x39, 0x8f, 0x66, 0x99, 0x8e, - 0xfa, 0x2f, 0x9a, 0xa3, 0x52, 0x00, 0x4e, 0x1d, 0x2f, 0xc0, 0x1a, 0xfa, 0x4a, 0x37, 0xad, 0x78, 0xa0, 0x31, 0x46, - 0x41, 0x85, 0x0e, 0x42, 0x3f, 0xd7, 0x80, 0xb4, 0xc1, 0x24, 0x4d, 0x42, 0xe5, 0x83, 0x0b, 0xba, 0x61, 0x5e, 0xae, - 0x5c, 0xae, 0x9a, 0x54, 0x2d, 0xbf, 0x1c, 0x51, 0xdf, 0xd5, 0x92, 0x4b, 0xb5, 0xf9, 0xd4, 0x28, 0x6b, 0x04, 0x99, - 0x1c, 0xa5, 0xdf, 0xa7, 0x5c, 0xb8, 0x95, 0x31, 0x59, 0x1f, 0x0e, 0x5e, 0xc1, 0x4d, 0x8d, 0xdf, 0xe4, 0x44, 0x28, - 0x6a, 0x0f, 0x89, 0xb0, 0xb5, 0x5b, 0xa1, 0x7b, 0x8f, 0x1b, 0xa5, 0x79, 0x94, 0x6d, 0x62, 0x51, 0x79, 0xbd, 0x04, - 0xac, 0xc5, 0x3d, 0xe0, 0x45, 0xa5, 0xa5, 0x5f, 0xb1, 0x02, 0xd0, 0x03, 0xa4, 0xb0, 0xf1, 0x06, 0x19, 0xb0, 0x3e, - 0x78, 0xa9, 0xdf, 0xef, 0x1b, 0x53, 0xfe, 0xfb, 0xfb, 0x1c, 0x48, 0x0a, 0x45, 0x59, 0xef, 0x60, 0x02, 0xc1, 0xb5, - 0x93, 0xb4, 0x67, 0x35, 0x7f, 0xb6, 0xae, 0x3d, 0xe0, 0xb7, 0xf2, 0x2d, 0x12, 0xab, 0x57, 0xf6, 0xc5, 0x66, 0x9f, - 0x56, 0xd7, 0x46, 0xe3, 0x20, 0x58, 0x5a, 0xbd, 0xd1, 0x2a, 0x87, 0xbc, 0xe1, 0x05, 0x88, 0x54, 0xd6, 0xd5, 0xb5, - 0x72, 0xae, 0xae, 0x05, 0x47, 0x2e, 0xd9, 0x92, 0xe7, 0xf0, 0x5f, 0xc8, 0xbd, 0xf2, 0x70, 0x28, 0xfc, 0x7e, 0x3f, - 0x9d, 0x91, 0x56, 0x16, 0xd8, 0xd3, 0xd6, 0xb5, 0x17, 0xfa, 0x87, 0xc3, 0x1b, 0xf0, 0x1a, 0xf1, 0x0f, 0x87, 0xb2, - 0xdf, 0xff, 0x68, 0x6e, 0x32, 0xe7, 0x63, 0xa5, 0x94, 0xbd, 0x44, 0xa5, 0xfb, 0xa7, 0x84, 0xf7, 0xfe, 0xf7, 0xe8, - 0x7f, 0x8f, 0x2e, 0x7b, 0xb2, 0xeb, 0x7f, 0x49, 0xf8, 0x0c, 0x6f, 0xe8, 0x4c, 0x5d, 0xce, 0x99, 0x74, 0x77, 0x57, - 0x7e, 0xe8, 0x3d, 0x0d, 0x15, 0xdf, 0x9b, 0x9b, 0x36, 0xfe, 0x5c, 0x1d, 0x69, 0x12, 0x3a, 0x2e, 0xfa, 0x87, 0xc3, - 0x2f, 0x89, 0xd6, 0xa7, 0xa5, 0x4a, 0x9f, 0xa6, 0x70, 0x94, 0x0c, 0xb9, 0x9b, 0x5b, 0x98, 0x0e, 0xec, 0xc7, 0xcd, - 0x57, 0xc9, 0x8b, 0xb3, 0x14, 0xae, 0xbd, 0xf9, 0x2c, 0x9d, 0x4f, 0xc1, 0xba, 0x32, 0xcc, 0x67, 0xf5, 0x3c, 0x80, - 0xd4, 0x21, 0xa4, 0x59, 0xd3, 0xf0, 0x1f, 0x95, 0x2b, 0x78, 0x6b, 0x8f, 0x77, 0x03, 0x17, 0xa5, 0x8e, 0xf4, 0x49, - 0x1b, 0x4d, 0x97, 0x54, 0xf2, 0x1f, 0x45, 0x1e, 0x63, 0xcc, 0xc6, 0x1b, 0xe2, 0xfd, 0x2c, 0xf2, 0x97, 0x05, 0x60, - 0x17, 0x01, 0x18, 0x72, 0x3a, 0x77, 0x24, 0xf1, 0x8f, 0xc9, 0xf7, 0x7f, 0x4c, 0x97, 0xf6, 0xa1, 0x2c, 0x96, 0xa5, - 0xa8, 0xaa, 0xa3, 0xd2, 0xb6, 0xb6, 0x5c, 0x0f, 0x4c, 0xa2, 0xfd, 0xbe, 0x64, 0x12, 0x4d, 0x31, 0x14, 0x05, 0x6e, - 0x8d, 0xbd, 0x69, 0xca, 0x15, 0x63, 0xf5, 0xc8, 0x58, 0x3f, 0x5f, 0xec, 0xde, 0xc4, 0x5e, 0xea, 0x07, 0x29, 0x08, - 0xc2, 0x1a, 0x4a, 0x29, 0x45, 0x3e, 0x38, 0x9f, 0x61, 0x2a, 0x51, 0xeb, 0x52, 0xaa, 0xfc, 0x61, 0xa4, 0xf9, 0x30, - 0x05, 0xbd, 0xec, 0xbf, 0x2a, 0x98, 0xff, 0xba, 0x3d, 0x58, 0x9f, 0xd6, 0x65, 0x1a, 0x55, 0x44, 0x95, 0x17, 0xa6, - 0xda, 0x04, 0x22, 0xf8, 0x33, 0x61, 0xf1, 0xfd, 0xfa, 0xe4, 0x48, 0xd0, 0x98, 0xc9, 0xf2, 0xfa, 0xc8, 0xfd, 0xc2, - 0xbe, 0x72, 0x1d, 0xcf, 0xff, 0xdc, 0xcc, 0xff, 0x01, 0x3a, 0x43, 0x16, 0xcf, 0xb8, 0x65, 0xb0, 0xc0, 0xd9, 0x2f, - 0x5d, 0x3d, 0xe0, 0x6f, 0xe6, 0x89, 0x67, 0x40, 0xc7, 0xfc, 0x0c, 0x5d, 0x15, 0xd3, 0x59, 0x31, 0x00, 0x2e, 0x5b, - 0xbf, 0xb1, 0xe6, 0xc4, 0x3b, 0x8b, 0xf2, 0x4a, 0x2e, 0x08, 0x7d, 0x5d, 0x85, 0xd9, 0xb8, 0x2a, 0x36, 0x95, 0x28, - 0x36, 0x75, 0x8f, 0xd4, 0xb2, 0xf9, 0xb4, 0xb6, 0x15, 0xb2, 0x7f, 0x17, 0x2d, 0x06, 0x2f, 0xc3, 0x3a, 0x19, 0x65, - 0xe9, 0x7a, 0x0a, 0xfc, 0x7a, 0x01, 0x9c, 0x45, 0xe6, 0x95, 0xaf, 0xce, 0x1e, 0xb0, 0x45, 0xe3, 0x29, 0x90, 0xa3, - 0xd2, 0x1f, 0x79, 0x63, 0x74, 0x7a, 0xa2, 0xdf, 0xcf, 0xa7, 0x14, 0xf3, 0xf5, 0x77, 0x80, 0xe7, 0xaa, 0xe5, 0x02, - 0xf4, 0x65, 0xa8, 0x83, 0x4a, 0x94, 0x5a, 0x31, 0x8c, 0x58, 0xf8, 0xbb, 0x40, 0x22, 0x67, 0x0a, 0x6c, 0x56, 0x51, - 0x12, 0x2a, 0x51, 0x29, 0xd9, 0x9a, 0xa0, 0x96, 0xde, 0x17, 0x65, 0xbd, 0xaf, 0xc0, 0x51, 0x32, 0xd2, 0x66, 0x39, - 0x69, 0xc6, 0x15, 0x28, 0x73, 0xd1, 0x0f, 0xf6, 0xf7, 0xca, 0xf3, 0x1b, 0x99, 0xcf, 0x72, 0xdf, 0xd1, 0x39, 0x6d, - 0xc7, 0x05, 0xca, 0xdc, 0x72, 0xda, 0x6a, 0xc9, 0x63, 0xf2, 0x9e, 0x85, 0xda, 0xb2, 0x04, 0x29, 0x16, 0x61, 0x3e, - 0xa1, 0xca, 0xe6, 0x5f, 0x10, 0x6a, 0x8b, 0x03, 0x7b, 0xec, 0xc2, 0x44, 0xfc, 0xb7, 0x60, 0x49, 0x0c, 0xb3, 0x52, - 0x84, 0xf1, 0x0e, 0xbc, 0x7f, 0x36, 0x95, 0x18, 0x9d, 0xa1, 0x93, 0xfb, 0xd9, 0x7d, 0x5a, 0x27, 0x67, 0x6f, 0x5e, - 0x9d, 0xfd, 0xd0, 0x1b, 0x14, 0xa3, 0x34, 0x1e, 0xf4, 0x7e, 0x38, 0x5b, 0x6d, 0x00, 0x2d, 0x53, 0x9c, 0xc5, 0x64, - 0x4a, 0x13, 0xf1, 0x19, 0x19, 0x06, 0xcf, 0xea, 0x44, 0x9c, 0xd1, 0xc4, 0x74, 0x5f, 0xa3, 0x34, 0xf9, 0x76, 0x14, - 0xe6, 0xf0, 0x72, 0x29, 0x36, 0x95, 0x88, 0xc1, 0x4e, 0xa9, 0xe6, 0x59, 0xde, 0x3e, 0x8b, 0xf3, 0x51, 0x87, 0xac, - 0xd2, 0x81, 0xbf, 0x3d, 0x91, 0x76, 0x55, 0xba, 0x02, 0x42, 0x0f, 0x80, 0x93, 0xae, 0xfc, 0x79, 0x38, 0xa4, 0x09, - 0x84, 0x5a, 0x30, 0x27, 0xd3, 0x88, 0x6e, 0x48, 0x2f, 0xb1, 0xcf, 0xc0, 0x2c, 0xa4, 0x34, 0x0f, 0x6e, 0xae, 0x16, - 0x43, 0x77, 0xc5, 0xca, 0x51, 0x58, 0xad, 0x45, 0x54, 0x23, 0xeb, 0x31, 0x38, 0xef, 0x40, 0x04, 0x80, 0x22, 0x07, - 0xcf, 0x78, 0xd4, 0xef, 0x47, 0x2a, 0x28, 0x27, 0xa1, 0x5f, 0x14, 0xfa, 0xa5, 0xe1, 0x28, 0x63, 0xfe, 0x25, 0xd4, - 0x1c, 0x01, 0xf5, 0x96, 0x87, 0x8a, 0x2e, 0x00, 0x97, 0x73, 0xc4, 0x8c, 0xf3, 0xde, 0xff, 0xe1, 0xed, 0x4b, 0xb8, - 0xdb, 0xb6, 0xb5, 0x75, 0xff, 0x8a, 0xc5, 0x97, 0xaa, 0x44, 0x04, 0xc9, 0x92, 0x93, 0xf4, 0x9c, 0x52, 0x86, 0x75, - 0xdd, 0x0c, 0x6d, 0x7a, 0x9a, 0xa1, 0x71, 0xd2, 0x49, 0x4f, 0xd7, 0xa5, 0x49, 0xd8, 0x62, 0x43, 0x03, 0x2a, 0x49, - 0x79, 0x88, 0xc4, 0xff, 0xfe, 0xd6, 0xde, 0x18, 0x49, 0xd1, 0x4e, 0xce, 0x79, 0xf7, 0xbd, 0x95, 0xb5, 0x62, 0x11, - 0x04, 0x31, 0x63, 0x63, 0x63, 0x0f, 0xdf, 0x66, 0x4d, 0x60, 0x4e, 0x13, 0x82, 0xc2, 0x5c, 0x07, 0x0b, 0x03, 0x40, - 0xef, 0xda, 0xa3, 0x2d, 0x27, 0x5d, 0x82, 0xc5, 0x73, 0x03, 0x8b, 0x57, 0x17, 0x8b, 0xea, 0x92, 0x6b, 0xb9, 0x85, - 0x4d, 0x29, 0xab, 0x18, 0x02, 0x08, 0x34, 0x63, 0x86, 0xdd, 0x70, 0x97, 0x23, 0x59, 0x17, 0x05, 0x17, 0x3b, 0x81, - 0xa1, 0x9b, 0x71, 0xc9, 0xcc, 0xc1, 0xd5, 0x0c, 0xeb, 0xa4, 0xa2, 0x00, 0xbb, 0xba, 0x00, 0xd9, 0x0b, 0x43, 0x5d, - 0x37, 0xb3, 0xe5, 0x3a, 0xf0, 0x75, 0xe9, 0xc2, 0x97, 0x14, 0xbc, 0x5c, 0x49, 0x51, 0x66, 0x57, 0xfc, 0x27, 0xfb, - 0xb2, 0x19, 0x4b, 0x0a, 0xed, 0x48, 0x5f, 0xb5, 0xbb, 0xa3, 0xc5, 0x38, 0xb6, 0x1c, 0xdf, 0x52, 0xe9, 0x46, 0x8f, - 0xaa, 0x17, 0x42, 0x5b, 0xe7, 0x5a, 0x66, 0x69, 0xca, 0xc5, 0x4b, 0x91, 0x66, 0x89, 0x97, 0x1c, 0xeb, 0x58, 0xd5, - 0x2e, 0x08, 0x96, 0x0b, 0x93, 0xfc, 0x2c, 0x2b, 0x31, 0x76, 0x70, 0xa3, 0x51, 0xad, 0xa8, 0x53, 0x26, 0x06, 0x86, - 0x7c, 0x87, 0xc1, 0xb7, 0x99, 0x4c, 0x80, 0xe1, 0xc7, 0x44, 0x7d, 0x49, 0x4f, 0x21, 0xe0, 0x83, 0x0a, 0xcd, 0xfd, - 0x8c, 0x23, 0xf8, 0xb5, 0x55, 0x99, 0x03, 0x93, 0xad, 0x55, 0x90, 0x88, 0x7b, 0x97, 0xcd, 0xf5, 0x22, 0x5a, 0xa8, - 0xbb, 0x50, 0x2f, 0xde, 0x6e, 0x7b, 0x89, 0xa2, 0x03, 0x4e, 0x7e, 0x1a, 0xbc, 0x88, 0xb3, 0x9c, 0xa7, 0x7b, 0x95, - 0xdc, 0x53, 0x1b, 0x6a, 0x4f, 0x39, 0x73, 0xc0, 0xce, 0xfb, 0xba, 0xda, 0xd3, 0x6b, 0x7a, 0x4f, 0xb7, 0x73, 0x0f, - 0x2e, 0x18, 0xb8, 0x73, 0x2f, 0xb2, 0x2b, 0x2e, 0xf6, 0x40, 0x19, 0x68, 0x8d, 0x07, 0xea, 0xb2, 0x1a, 0xa9, 0x89, - 0xd1, 0x31, 0xac, 0x13, 0x7d, 0x30, 0x07, 0xf4, 0x67, 0x08, 0x6b, 0xdf, 0x7a, 0xbb, 0xd2, 0x07, 0x6d, 0x40, 0xdf, - 0x2d, 0x4d, 0x1f, 0x74, 0xe0, 0x78, 0x15, 0x1d, 0xb8, 0x31, 0xa4, 0x1a, 0xb4, 0xd5, 0xc8, 0x2a, 0x50, 0xbc, 0xe1, - 0x2d, 0xde, 0x9d, 0x6b, 0xc9, 0xc6, 0x7b, 0x89, 0x18, 0x5f, 0x99, 0xa8, 0xe2, 0x4c, 0x1c, 0x7b, 0xa9, 0xbc, 0xd6, - 0x4e, 0x32, 0xc2, 0xf8, 0x96, 0x95, 0xd4, 0xdf, 0x21, 0xe6, 0x16, 0x69, 0x0e, 0x83, 0xe7, 0x61, 0x45, 0x66, 0xbc, - 0xdf, 0x97, 0x33, 0x19, 0x95, 0x33, 0xb1, 0x5f, 0x46, 0x0a, 0xac, 0xed, 0x2e, 0x11, 0xd0, 0xbd, 0x12, 0x20, 0x5f, - 0x00, 0x54, 0xdd, 0x27, 0xfc, 0xb9, 0x4f, 0xea, 0xd3, 0x29, 0xf4, 0x29, 0xb4, 0xf5, 0x8a, 0x2b, 0x88, 0x57, 0x75, - 0x63, 0x64, 0x1b, 0x15, 0xb4, 0x78, 0x2c, 0xcf, 0x6a, 0xc3, 0xd8, 0x9c, 0x5a, 0xff, 0x7a, 0xb3, 0xc1, 0x94, 0xcd, - 0x85, 0x5a, 0x85, 0x21, 0x89, 0x3e, 0x96, 0x5e, 0x24, 0x11, 0x0b, 0x9b, 0xd5, 0xda, 0xfc, 0x26, 0x0c, 0x48, 0x26, - 0x52, 0xdc, 0xcf, 0x96, 0x38, 0x77, 0xf1, 0x78, 0x5e, 0xf5, 0xb5, 0x96, 0x16, 0x99, 0x36, 0xdf, 0xe8, 0xcb, 0x90, - 0xa6, 0xa2, 0x86, 0x34, 0xea, 0xcc, 0xa0, 0xfb, 0x76, 0x79, 0xcb, 0x6a, 0x84, 0x09, 0xf0, 0x4a, 0x67, 0xd0, 0x8d, - 0xc6, 0x03, 0xb1, 0xac, 0x46, 0xc5, 0x5a, 0x08, 0x04, 0x1e, 0x86, 0x1c, 0x33, 0x4b, 0x48, 0xb2, 0x4f, 0xfc, 0x3b, - 0x15, 0x67, 0xa1, 0x88, 0xaf, 0x0d, 0xb2, 0x77, 0x65, 0x5d, 0xbb, 0xeb, 0xc8, 0xcf, 0x89, 0x85, 0xd5, 0xfe, 0x43, - 0xf3, 0xa8, 0x35, 0xce, 0x02, 0xda, 0x9a, 0x56, 0x37, 0x1c, 0xee, 0x51, 0x1d, 0x8b, 0xd2, 0x60, 0x13, 0x7b, 0x64, - 0xb9, 0x68, 0x1d, 0x33, 0x68, 0x40, 0x7f, 0x93, 0x5d, 0xae, 0x2f, 0x11, 0xc0, 0xad, 0x44, 0xd6, 0x49, 0x2a, 0xff, - 0x92, 0xf6, 0xa8, 0x6b, 0x7b, 0x2a, 0xff, 0xdb, 0x36, 0x55, 0x0e, 0x2d, 0xa6, 0x3c, 0x76, 0x73, 0x16, 0xa8, 0x8e, - 0x04, 0x51, 0xa0, 0xb6, 0x5e, 0x30, 0xf5, 0x4e, 0x99, 0xa2, 0x03, 0x04, 0xba, 0x30, 0x67, 0xd8, 0x17, 0x1c, 0x31, - 0x66, 0xa9, 0xc4, 0x60, 0xea, 0x63, 0x8c, 0x6a, 0x5a, 0x2b, 0x40, 0xd7, 0x4f, 0x37, 0xf0, 0x27, 0x2a, 0x6a, 0x34, - 0xd4, 0x1a, 0x49, 0xa1, 0x68, 0xa2, 0x42, 0x91, 0xa5, 0x85, 0x8e, 0xab, 0xd0, 0x49, 0x24, 0x2c, 0x01, 0x0d, 0x13, - 0xa2, 0x93, 0x0a, 0xbc, 0x35, 0x80, 0x33, 0x1f, 0x17, 0xe5, 0xba, 0xd0, 0x06, 0x73, 0x3f, 0xc4, 0x57, 0xfc, 0xe5, - 0x33, 0x67, 0x54, 0xdf, 0xb2, 0xd6, 0xf7, 0xb4, 0x20, 0x3f, 0x84, 0x9c, 0xa2, 0x03, 0x13, 0x3b, 0xda, 0xa0, 0x31, - 0x46, 0x59, 0xeb, 0xa8, 0x17, 0x6f, 0x74, 0x28, 0x16, 0x6d, 0x82, 0x77, 0x8f, 0xa7, 0x88, 0x36, 0x3c, 0x14, 0xc6, - 0xaa, 0x1a, 0x9f, 0x4a, 0xd6, 0xd2, 0x83, 0x15, 0x3c, 0x5d, 0x27, 0x3c, 0x04, 0x3d, 0x12, 0x61, 0x47, 0x61, 0x31, - 0x8f, 0x17, 0x70, 0x9c, 0x14, 0x04, 0xd4, 0x0e, 0xfa, 0x0a, 0x3e, 0x5f, 0xa0, 0xfb, 0xab, 0x44, 0x0f, 0x30, 0xb4, - 0x20, 0x6e, 0x46, 0x41, 0x1d, 0x5d, 0xc6, 0xab, 0x86, 0x8a, 0x84, 0xcf, 0x0b, 0xb0, 0x1d, 0x52, 0xea, 0x29, 0xd0, - 0x42, 0x25, 0x4a, 0x3f, 0x0c, 0x7c, 0x87, 0xc6, 0xc0, 0xd6, 0x3a, 0x40, 0x43, 0x3f, 0x63, 0x9a, 0x5a, 0x67, 0xa8, - 0x7c, 0xe6, 0xdd, 0x33, 0xa3, 0xe5, 0xcc, 0xa2, 0x31, 0xe8, 0xdb, 0x68, 0x8a, 0xe2, 0x9c, 0x7c, 0x16, 0x14, 0x71, - 0x9a, 0xc5, 0x39, 0xf8, 0x6d, 0xc6, 0x05, 0x66, 0x4c, 0xe2, 0x8a, 0x5f, 0xc8, 0x02, 0xb4, 0xdd, 0xb9, 0x4a, 0xad, - 0x6b, 0x10, 0x90, 0xfd, 0x00, 0x56, 0x2f, 0x0d, 0x1d, 0x95, 0xf3, 0xee, 0xd2, 0xa6, 0x10, 0xb1, 0x08, 0xc1, 0xa6, - 0x99, 0x2e, 0xd9, 0x71, 0xa8, 0xb4, 0x39, 0x10, 0xea, 0x08, 0x8d, 0xfb, 0xa7, 0x61, 0x6c, 0x35, 0xc5, 0xd6, 0xee, - 0x6d, 0xbb, 0xfd, 0x57, 0xe9, 0xa5, 0xd3, 0x9c, 0xf4, 0x18, 0xfb, 0x57, 0x19, 0x16, 0x23, 0xdb, 0x11, 0x02, 0x4b, - 0xce, 0xfb, 0xd4, 0x7f, 0x45, 0xcb, 0x79, 0x02, 0xa6, 0x23, 0x3a, 0x58, 0x2e, 0x50, 0x76, 0x0c, 0xe8, 0x0e, 0x0c, - 0xae, 0xe8, 0xf7, 0xc1, 0x2a, 0xc3, 0x5c, 0x48, 0x96, 0x24, 0x65, 0xf0, 0x3c, 0xf5, 0xe0, 0xe0, 0xd7, 0x4c, 0x99, - 0xbb, 0x28, 0xeb, 0xd3, 0x25, 0x99, 0xa6, 0xc8, 0x40, 0xac, 0xc3, 0x4d, 0x96, 0x46, 0x89, 0x12, 0x91, 0x2d, 0xd1, - 0x3f, 0xd2, 0x50, 0x2c, 0x1d, 0xb9, 0x17, 0xa9, 0x12, 0xa1, 0x62, 0x9e, 0xe2, 0x49, 0x9d, 0xd6, 0xe9, 0x08, 0x43, - 0x4f, 0x82, 0x52, 0xae, 0x86, 0x81, 0x2a, 0xa9, 0x5e, 0x0a, 0x9b, 0x62, 0xbb, 0xd5, 0x17, 0x2b, 0x31, 0x8f, 0x17, - 0xf8, 0x52, 0xe0, 0x28, 0xfe, 0x8b, 0x7b, 0x61, 0xa7, 0xd4, 0xf6, 0xa0, 0x76, 0x44, 0x09, 0xfd, 0x17, 0x87, 0x8b, - 0xc4, 0x77, 0x52, 0x87, 0x00, 0x44, 0x8b, 0x90, 0x53, 0x75, 0x90, 0x1a, 0x6e, 0x68, 0x47, 0xf8, 0x6f, 0xb8, 0x3e, - 0xe3, 0x8c, 0xde, 0x54, 0x33, 0x6a, 0x28, 0x5f, 0x0f, 0xda, 0x18, 0xf5, 0xd9, 0xc0, 0x61, 0x85, 0x28, 0xb4, 0x61, - 0x47, 0xa5, 0x12, 0x2d, 0x0c, 0xa5, 0xfa, 0x4b, 0xa8, 0x38, 0xe2, 0xce, 0x8c, 0xb2, 0x64, 0x7c, 0x5a, 0x1e, 0x8a, - 0xe9, 0x60, 0x50, 0x92, 0xca, 0x58, 0xe8, 0xc1, 0xf5, 0xc0, 0xf3, 0xef, 0x81, 0x5b, 0x88, 0x87, 0x8c, 0x2c, 0x86, - 0xdc, 0xe0, 0xe4, 0xb7, 0x38, 0xb9, 0x6a, 0x54, 0xaa, 0x38, 0xd6, 0x44, 0xb5, 0xe0, 0xfb, 0x32, 0x0c, 0xd0, 0x27, - 0x29, 0x00, 0x93, 0xc1, 0x94, 0xdf, 0x80, 0x44, 0xe9, 0x54, 0xdd, 0x90, 0x3e, 0x88, 0x82, 0x9f, 0xf3, 0x82, 0x8b, - 0xc4, 0x15, 0x60, 0x79, 0x07, 0xdb, 0xeb, 0xa8, 0xa2, 0x0a, 0x93, 0xd7, 0xf4, 0x38, 0xe2, 0xc6, 0xfb, 0xcf, 0xf4, - 0xd8, 0x62, 0xb6, 0x5a, 0xc7, 0x06, 0x9f, 0x39, 0x06, 0x17, 0x74, 0x2d, 0xb1, 0x35, 0x54, 0xc3, 0x8a, 0xc0, 0xc0, - 0x05, 0x1c, 0x84, 0x25, 0x8a, 0x63, 0x2b, 0x79, 0x45, 0x1a, 0x52, 0xda, 0x7b, 0x86, 0xa3, 0x4d, 0x72, 0x7c, 0x9b, - 0x65, 0x37, 0x81, 0xf3, 0x45, 0xe7, 0xa4, 0x99, 0xb0, 0x36, 0x78, 0x9f, 0x37, 0xe7, 0xd7, 0xdd, 0x43, 0x42, 0x55, - 0xdc, 0x1b, 0xde, 0x8e, 0x7b, 0xe3, 0x84, 0x5f, 0x73, 0xb1, 0xd0, 0xa1, 0x5a, 0xcc, 0x25, 0xcb, 0x6f, 0xad, 0x77, - 0x4b, 0x92, 0x5a, 0x01, 0xed, 0xb3, 0x2c, 0xa8, 0x89, 0x00, 0x90, 0x3f, 0xfc, 0x05, 0x42, 0x67, 0xf8, 0xdb, 0x63, - 0x70, 0x45, 0x0a, 0xef, 0x1c, 0x02, 0x61, 0x4d, 0x37, 0x77, 0x6a, 0x03, 0xbe, 0x18, 0xf7, 0x67, 0x4c, 0x3d, 0xfd, - 0x36, 0x93, 0xbb, 0xba, 0x6e, 0x8f, 0x2c, 0xc3, 0x47, 0xb8, 0x52, 0x00, 0x37, 0x13, 0xfe, 0x62, 0x98, 0x49, 0xf5, - 0x09, 0x60, 0xaa, 0xe9, 0xe0, 0x3e, 0x41, 0x60, 0x00, 0x95, 0x68, 0x31, 0xba, 0x52, 0x8e, 0x68, 0x06, 0x6e, 0x4d, - 0xb7, 0xc2, 0x78, 0xeb, 0x41, 0x0b, 0x3d, 0xd3, 0x70, 0xe2, 0x3f, 0x68, 0xe6, 0x55, 0x01, 0x01, 0xb4, 0x32, 0x82, - 0xb7, 0xd6, 0x47, 0x73, 0x84, 0xf8, 0x84, 0x25, 0xd1, 0x84, 0xc5, 0x33, 0xc5, 0x8f, 0x09, 0xdd, 0x34, 0xb5, 0x4d, - 0xef, 0x91, 0xfe, 0xe2, 0x9a, 0xf5, 0x53, 0x96, 0xb5, 0x6f, 0x0f, 0x15, 0x2f, 0xa6, 0xcd, 0x38, 0x88, 0x89, 0x2a, - 0xc6, 0xff, 0x82, 0xfb, 0x52, 0x2b, 0x40, 0x64, 0xee, 0xaa, 0xa7, 0xdf, 0x6f, 0x66, 0xcb, 0x81, 0x50, 0xf9, 0x9d, - 0x41, 0xd2, 0xa7, 0x43, 0xfb, 0x81, 0x4d, 0xa2, 0xb6, 0xd0, 0xf3, 0xc7, 0xa5, 0x6e, 0xe2, 0xe5, 0xb5, 0xa9, 0x11, - 0xad, 0x90, 0xa1, 0xb2, 0x75, 0xc0, 0xfa, 0xfe, 0x21, 0xdc, 0x5d, 0xd4, 0x34, 0xd4, 0xba, 0xe7, 0xae, 0x45, 0xc1, - 0x89, 0x3f, 0xc0, 0x58, 0x5c, 0x48, 0x6a, 0x1d, 0x8f, 0x49, 0x3f, 0x5a, 0xc8, 0xe4, 0x46, 0x5d, 0x9d, 0x9c, 0x29, - 0xe6, 0x09, 0x5c, 0x80, 0xcb, 0xb6, 0xbf, 0xa2, 0x52, 0x97, 0x72, 0x7b, 0x45, 0x69, 0x7a, 0x48, 0xdb, 0xab, 0x38, - 0x6f, 0x0b, 0x2e, 0xf8, 0x17, 0x0a, 0x2e, 0xac, 0x83, 0x75, 0xc7, 0x9d, 0xb2, 0x27, 0x3c, 0x51, 0xa6, 0xb5, 0xc1, - 0x5d, 0x37, 0x18, 0x13, 0x63, 0xbf, 0xbb, 0xe4, 0xc9, 0x47, 0x64, 0xc1, 0xbf, 0xcb, 0x04, 0x78, 0x26, 0xbb, 0x57, - 0x2a, 0xff, 0x0f, 0xfe, 0xd5, 0xd6, 0xbe, 0xb3, 0xe6, 0x9f, 0x9e, 0xf5, 0x70, 0xe7, 0x30, 0xf9, 0xb1, 0x3a, 0x03, - 0xba, 0xb9, 0x94, 0x29, 0x07, 0x64, 0x00, 0x6b, 0x91, 0x8c, 0x06, 0x7c, 0x68, 0x65, 0xd9, 0xf6, 0x9d, 0x56, 0x17, - 0x84, 0x3b, 0x09, 0xdc, 0xf4, 0xee, 0xda, 0xcc, 0xcc, 0xe9, 0x5a, 0x89, 0xa6, 0x4b, 0x63, 0x6b, 0x59, 0xaa, 0x30, - 0xde, 0xef, 0x3c, 0xc9, 0xa6, 0xf9, 0xe1, 0x72, 0x9a, 0x5b, 0xea, 0xb6, 0x71, 0xcb, 0x06, 0xd0, 0x10, 0xbb, 0xd6, - 0x56, 0x0e, 0x78, 0xb9, 0x3d, 0x88, 0xe6, 0x6b, 0x45, 0xe8, 0xa9, 0x12, 0xa1, 0x4f, 0xd3, 0x66, 0x1f, 0xec, 0xaa, - 0x5a, 0x37, 0x42, 0x1e, 0x0d, 0x52, 0xcd, 0xc8, 0xbf, 0xb9, 0xe2, 0xc5, 0x79, 0x2e, 0xaf, 0x01, 0x0e, 0x99, 0xd4, - 0x46, 0x61, 0x79, 0x09, 0xee, 0xfc, 0xe8, 0x38, 0xce, 0xc4, 0x28, 0xc7, 0xb8, 0xad, 0x88, 0x94, 0xac, 0x13, 0x67, - 0x80, 0x87, 0xec, 0x4f, 0x9a, 0x0e, 0xed, 0x5a, 0x60, 0x78, 0x5f, 0xe0, 0xae, 0x72, 0x76, 0xb4, 0xc9, 0xed, 0xa2, - 0x6f, 0xce, 0xb0, 0xee, 0x48, 0x69, 0x6d, 0x2c, 0xba, 0xee, 0x60, 0xad, 0x19, 0xb4, 0x45, 0x28, 0xf9, 0x90, 0x3b, - 0x69, 0x3f, 0x05, 0x34, 0x38, 0xcd, 0xd2, 0x1b, 0x6b, 0x95, 0xbf, 0xd1, 0x42, 0x9c, 0x28, 0xa6, 0x4e, 0x7c, 0x13, - 0x25, 0xfa, 0xfc, 0x4c, 0x8c, 0x1b, 0x08, 0xa4, 0xfe, 0x80, 0xf1, 0x35, 0x8a, 0x30, 0x81, 0xeb, 0x40, 0x14, 0xdb, - 0x13, 0xb5, 0xb1, 0x1c, 0x41, 0x27, 0x84, 0x78, 0x07, 0x65, 0x18, 0xab, 0x8b, 0x03, 0x6d, 0xb0, 0xf4, 0x75, 0x6b, - 0x9d, 0x1b, 0x42, 0x61, 0x9c, 0xc0, 0x14, 0x83, 0xa4, 0xce, 0x3a, 0xcb, 0x04, 0x55, 0x76, 0x4c, 0x3a, 0xef, 0x03, - 0x74, 0x77, 0x2d, 0x9a, 0xe2, 0xeb, 0xce, 0x1d, 0x74, 0x17, 0xd7, 0xaf, 0xb5, 0xc8, 0x0d, 0xfe, 0xbc, 0x25, 0xc2, - 0x22, 0x70, 0xd6, 0x9a, 0x7c, 0xd5, 0x08, 0x07, 0xa6, 0x24, 0xd3, 0xb0, 0x97, 0x2b, 0x9b, 0xee, 0xed, 0xb6, 0xd7, - 0xbb, 0x53, 0xc4, 0xd5, 0x63, 0xac, 0xf2, 0x6e, 0xe6, 0xf6, 0x4e, 0xb5, 0x16, 0xbb, 0x37, 0x6d, 0x3f, 0xc5, 0x8e, - 0x5a, 0x6b, 0xb7, 0x1b, 0x4e, 0xa8, 0x21, 0xdf, 0x8a, 0x2a, 0xad, 0x4e, 0x37, 0x06, 0xed, 0x10, 0xda, 0x5a, 0x64, - 0x70, 0xa3, 0x7c, 0xe6, 0x84, 0x4e, 0x2a, 0xe4, 0xaa, 0x53, 0x17, 0x6c, 0x2e, 0x79, 0xb5, 0x94, 0x69, 0x24, 0x28, - 0xda, 0x9c, 0x47, 0x25, 0x4d, 0xe4, 0x5a, 0x54, 0x91, 0xac, 0x51, 0x2f, 0x6a, 0x35, 0x06, 0x08, 0xc8, 0x74, 0xda, - 0xf4, 0xa0, 0x0a, 0x66, 0x43, 0x19, 0xc9, 0xe9, 0x0b, 0xb0, 0xb4, 0x47, 0x8e, 0xb5, 0xbe, 0xab, 0xce, 0x16, 0xdf, - 0xea, 0x09, 0xc1, 0x14, 0x66, 0x0f, 0x44, 0x84, 0x6b, 0x1a, 0x43, 0x4e, 0xbb, 0xc4, 0x65, 0x4d, 0xb7, 0x84, 0x3b, - 0xb8, 0x5d, 0xc9, 0x8e, 0xdc, 0x3c, 0x69, 0x6e, 0xae, 0x60, 0x47, 0xc5, 0x7c, 0x0c, 0xda, 0x2f, 0xa9, 0xae, 0x5d, - 0x9a, 0x5b, 0x8f, 0x07, 0x01, 0x0d, 0x06, 0x85, 0xe1, 0x5f, 0x27, 0xc6, 0xc3, 0x93, 0x06, 0x04, 0x49, 0xb9, 0x08, - 0xc7, 0xbe, 0x11, 0xfd, 0x64, 0x2a, 0x0f, 0x39, 0x5a, 0xbc, 0x43, 0xab, 0x73, 0x08, 0xe8, 0x25, 0x42, 0x49, 0x8c, - 0xaa, 0xd0, 0x88, 0xa0, 0x3c, 0x2d, 0x7f, 0xa9, 0xaa, 0x43, 0x40, 0x21, 0xed, 0x2b, 0x0a, 0x65, 0x9b, 0xc4, 0xd0, - 0x0c, 0xbf, 0x9c, 0x4f, 0x16, 0x7a, 0x06, 0x06, 0x72, 0x7e, 0xb0, 0xd0, 0xb3, 0x30, 0x90, 0xf3, 0x47, 0x8b, 0xda, - 0xad, 0x03, 0x4d, 0x40, 0x3c, 0x17, 0x8e, 0x4e, 0x4a, 0xab, 0xb2, 0x05, 0x74, 0x73, 0x1f, 0x41, 0xff, 0x97, 0x3d, - 0x04, 0x9d, 0x5c, 0x68, 0x47, 0x6e, 0x40, 0xdb, 0x21, 0x09, 0xec, 0x15, 0x93, 0x0a, 0x13, 0x8b, 0xe8, 0x90, 0x8d, - 0xc1, 0x10, 0x5b, 0x7d, 0x70, 0xc8, 0xc6, 0x53, 0x9f, 0x04, 0x01, 0xa3, 0xfb, 0x83, 0x01, 0x07, 0xbf, 0xc1, 0xab, - 0xf4, 0xd1, 0x46, 0xa0, 0x9b, 0xbe, 0xbb, 0x1b, 0x7a, 0x17, 0x57, 0x70, 0xaa, 0x76, 0xf7, 0x24, 0x74, 0x93, 0x69, - 0xc7, 0xea, 0x35, 0xc4, 0x0d, 0xf9, 0x95, 0xd1, 0x68, 0x64, 0x53, 0x42, 0x42, 0x0c, 0xe7, 0xd0, 0xcc, 0x69, 0xb9, - 0x7c, 0x75, 0xeb, 0xd9, 0x80, 0x0c, 0x33, 0xbd, 0x61, 0xb2, 0xbe, 0x87, 0xb2, 0xea, 0x31, 0xb4, 0x43, 0xef, 0x91, - 0xe3, 0xfb, 0x07, 0xdf, 0x64, 0xfc, 0xcc, 0xe1, 0xda, 0xc3, 0xb9, 0xf0, 0x5d, 0xd6, 0x8c, 0xcc, 0xa1, 0xf3, 0xec, - 0xe3, 0x78, 0x0f, 0xe3, 0xe4, 0xf3, 0x2c, 0x94, 0x37, 0x5e, 0xd3, 0xff, 0xa8, 0xf4, 0x66, 0x87, 0x43, 0x4e, 0x57, - 0xb0, 0xe2, 0x66, 0x55, 0x68, 0xf8, 0x59, 0xe4, 0x8d, 0x23, 0x5e, 0x93, 0xa8, 0xea, 0x3e, 0xef, 0x6d, 0xc4, 0xd2, - 0x8e, 0x71, 0x00, 0x70, 0xa2, 0x56, 0x0d, 0xbb, 0xd2, 0xb8, 0x56, 0x07, 0x31, 0x22, 0x25, 0x6c, 0x95, 0x38, 0x12, - 0xca, 0xdf, 0x00, 0x84, 0xc5, 0x50, 0x1c, 0x6f, 0x0d, 0xeb, 0x3d, 0xec, 0x87, 0x2e, 0xd0, 0x34, 0xa7, 0x54, 0x33, - 0x00, 0x48, 0x02, 0xfe, 0xe8, 0xe9, 0xa6, 0xa1, 0xb2, 0xcd, 0xf3, 0xd0, 0xb2, 0xba, 0x82, 0x7b, 0x7a, 0xea, 0x4a, - 0x06, 0xc6, 0x55, 0x1d, 0x7b, 0x9b, 0xbb, 0xdb, 0xa3, 0x55, 0xe4, 0x3b, 0x9b, 0xd4, 0x34, 0x0b, 0x20, 0x45, 0xe3, - 0xd2, 0x17, 0x7a, 0x3a, 0x01, 0x5a, 0xaf, 0x2d, 0x15, 0xed, 0xf7, 0x51, 0x8c, 0x1a, 0x17, 0x0a, 0xac, 0xc2, 0x04, - 0x85, 0x43, 0x84, 0x11, 0x42, 0x7f, 0x2e, 0xc3, 0x8d, 0x2f, 0xc8, 0x20, 0x1a, 0xae, 0x45, 0x87, 0x22, 0x72, 0xbc, - 0x68, 0x5b, 0xaa, 0x6a, 0x4e, 0x9a, 0xb6, 0x04, 0xde, 0x44, 0x06, 0x6c, 0xe7, 0x9f, 0x36, 0x44, 0xae, 0xc2, 0x05, - 0x0c, 0xdf, 0x11, 0xd7, 0x82, 0xe8, 0xa6, 0x36, 0xf5, 0x36, 0xec, 0x10, 0x1d, 0x4d, 0xf1, 0xe8, 0x90, 0x7b, 0xee, - 0x9e, 0xdb, 0x22, 0xbe, 0xfe, 0x0c, 0xb9, 0x6b, 0x3a, 0x7b, 0x29, 0xc2, 0xa0, 0x6e, 0xd9, 0x40, 0xb1, 0x0e, 0x9d, - 0xa0, 0x00, 0x03, 0xb8, 0x7c, 0x02, 0x3a, 0x36, 0x18, 0x54, 0x04, 0x9f, 0x14, 0xb6, 0x4d, 0x83, 0xfc, 0x11, 0xef, - 0x86, 0x0e, 0xaf, 0x2d, 0x79, 0x20, 0x5e, 0x61, 0x9f, 0x29, 0xe1, 0xee, 0x05, 0x05, 0xdd, 0x51, 0x5e, 0xae, 0x0a, - 0x57, 0xa5, 0x01, 0xa8, 0xb2, 0xe3, 0xb9, 0xd6, 0x94, 0xb4, 0x80, 0x95, 0x92, 0xba, 0xf3, 0x9b, 0xe0, 0xb8, 0x25, - 0x53, 0xe1, 0x5b, 0x75, 0xa3, 0xca, 0x43, 0x89, 0x22, 0x1d, 0x7b, 0xb6, 0x73, 0xb0, 0x06, 0xc0, 0x53, 0xd8, 0x5e, - 0x9c, 0x09, 0xf8, 0xdc, 0x69, 0x97, 0x2d, 0x73, 0x09, 0x14, 0xf5, 0xfd, 0x38, 0x2f, 0x3b, 0xbe, 0xdc, 0x1d, 0x6d, - 0xef, 0xa1, 0x37, 0x62, 0x63, 0xbc, 0xbe, 0x8c, 0x9a, 0x7e, 0xf1, 0x0c, 0x57, 0x96, 0x82, 0xdc, 0xd3, 0x54, 0x8f, - 0x30, 0x3a, 0x04, 0xa6, 0x29, 0x3f, 0x62, 0xe3, 0xe9, 0x70, 0x68, 0xc8, 0xa0, 0xd7, 0x4c, 0x0c, 0x05, 0xf6, 0x05, - 0xb4, 0xce, 0x4c, 0x5c, 0xe3, 0xd3, 0xf6, 0x15, 0xb4, 0xba, 0x41, 0x99, 0xdc, 0x29, 0x18, 0x3e, 0xd0, 0x92, 0x29, - 0x98, 0x2a, 0xbc, 0x21, 0x52, 0xc9, 0x3e, 0x2d, 0xad, 0xc3, 0xbe, 0x5d, 0x28, 0xb4, 0xd0, 0xc4, 0xaf, 0x32, 0xc4, - 0x4f, 0x5d, 0x67, 0xfe, 0x6d, 0xda, 0xa7, 0x06, 0xb1, 0x70, 0x24, 0x06, 0x11, 0xbf, 0x38, 0x55, 0xb6, 0x13, 0x42, - 0xc5, 0xc6, 0x43, 0xd7, 0xba, 0x71, 0x24, 0x55, 0x18, 0x4a, 0xa1, 0xf1, 0xd4, 0x70, 0xdf, 0x0b, 0x1d, 0xbe, 0x0e, - 0xb3, 0xb8, 0xcd, 0x1a, 0x49, 0x8d, 0x71, 0x2a, 0x4c, 0x9c, 0x4a, 0xb9, 0x8a, 0x04, 0x06, 0xca, 0xb3, 0x85, 0x41, - 0x80, 0x49, 0x4c, 0x32, 0xb6, 0x16, 0xc2, 0x84, 0xb1, 0x73, 0x85, 0x69, 0xea, 0x22, 0xf5, 0x9b, 0x81, 0xc9, 0x82, - 0x86, 0xfc, 0x1e, 0x8d, 0xd6, 0x54, 0x4d, 0x01, 0x86, 0x71, 0x94, 0x6a, 0xfc, 0x5b, 0x84, 0xda, 0x0c, 0x03, 0x00, - 0xdb, 0xbc, 0x95, 0x99, 0xa8, 0x5e, 0x0a, 0x84, 0x40, 0x73, 0xf6, 0x53, 0x71, 0xb5, 0x33, 0x0b, 0x46, 0xd1, 0x6e, - 0xaf, 0x7c, 0x3e, 0x70, 0x42, 0x79, 0xac, 0x2e, 0x50, 0x2f, 0x64, 0xf1, 0x4a, 0xa6, 0xbc, 0x15, 0x22, 0x73, 0x4f, - 0xb2, 0x9f, 0xf2, 0x11, 0x9c, 0x57, 0xe8, 0x54, 0x6e, 0xb6, 0x89, 0x32, 0x4b, 0x92, 0x8c, 0x05, 0xc6, 0xe6, 0x25, - 0x98, 0x49, 0xcd, 0x8c, 0xe1, 0xd7, 0x10, 0x67, 0x6c, 0xe7, 0x24, 0xdc, 0xdc, 0xcd, 0x03, 0x43, 0x94, 0x72, 0xd1, - 0x12, 0x0d, 0x5b, 0x3b, 0x5e, 0x4f, 0xae, 0x09, 0xf7, 0x61, 0x23, 0xd6, 0x64, 0x8c, 0x71, 0x6d, 0x6e, 0x64, 0xfd, - 0x68, 0x81, 0x07, 0x63, 0xca, 0xfa, 0x13, 0xc8, 0xb4, 0x92, 0xb2, 0xce, 0x17, 0x46, 0xcc, 0xa4, 0x12, 0xbd, 0xdb, - 0x37, 0x3e, 0xab, 0xbb, 0x88, 0xfa, 0xad, 0xfd, 0x9e, 0xd4, 0xc3, 0xad, 0xff, 0xa0, 0xb0, 0x06, 0x95, 0x11, 0x97, - 0x11, 0xe5, 0x99, 0x03, 0xdd, 0x34, 0x29, 0xe2, 0xf4, 0x74, 0x15, 0x17, 0x25, 0x4f, 0xa1, 0x52, 0x4d, 0xdd, 0xa2, - 0xde, 0x04, 0xec, 0x0d, 0x91, 0x24, 0x59, 0x4b, 0x63, 0x2b, 0x76, 0x69, 0x90, 0x9e, 0x3b, 0x23, 0x2e, 0xbd, 0xa8, - 0xd0, 0x90, 0x96, 0x7a, 0x67, 0xa1, 0x92, 0xf9, 0x2b, 0xfe, 0x33, 0xa8, 0x15, 0xe8, 0x68, 0x93, 0x62, 0x3c, 0x05, - 0x46, 0x7c, 0x37, 0x98, 0xd5, 0x3d, 0xc4, 0x45, 0x13, 0x94, 0x7a, 0x47, 0xec, 0xf8, 0xb9, 0xc9, 0xc3, 0xbb, 0x90, - 0x73, 0x06, 0x9f, 0xde, 0xcf, 0x12, 0xb5, 0xd6, 0x91, 0x18, 0xa9, 0x19, 0x40, 0xd3, 0x41, 0x99, 0xf3, 0x58, 0x04, - 0xb3, 0x9e, 0x49, 0x8c, 0x7a, 0x5c, 0xff, 0x02, 0x0d, 0xb5, 0xdf, 0xac, 0x2c, 0xcf, 0xaa, 0xdb, 0x2f, 0xe1, 0xc0, - 0xa6, 0xb6, 0x82, 0x1e, 0xaf, 0x2b, 0x79, 0x71, 0xa1, 0xba, 0xed, 0x17, 0x62, 0xe4, 0x74, 0x8d, 0x6b, 0xe9, 0xbc, - 0x5a, 0xb0, 0x5e, 0x77, 0xba, 0x59, 0xdc, 0xcd, 0x32, 0x1a, 0x08, 0x6b, 0x3b, 0x9f, 0x68, 0xfe, 0xac, 0xd9, 0x76, - 0x1f, 0x6f, 0x41, 0xcc, 0x02, 0x80, 0x48, 0x0f, 0xa2, 0x60, 0x99, 0xa5, 0x3c, 0xa0, 0xf2, 0x2e, 0x8e, 0xb2, 0x50, - 0x7a, 0x39, 0xcb, 0xf8, 0x69, 0xd3, 0x58, 0xeb, 0xac, 0x50, 0x86, 0xd6, 0x46, 0x77, 0xba, 0xca, 0x10, 0xdb, 0x4f, - 0xe2, 0x6c, 0x01, 0xee, 0x8f, 0x19, 0x0a, 0x0d, 0x9d, 0x65, 0xa4, 0x89, 0x86, 0xef, 0xba, 0x63, 0x90, 0x51, 0x9c, - 0xac, 0xf3, 0x4a, 0xba, 0xd1, 0x67, 0x6d, 0x24, 0xcc, 0x3d, 0x44, 0xbf, 0x8a, 0xc1, 0xa3, 0xdc, 0xe7, 0xb5, 0xd1, - 0xc9, 0xb4, 0x8c, 0xb4, 0x3b, 0x3f, 0xa9, 0x97, 0x59, 0xaa, 0x75, 0xd8, 0x3e, 0xc3, 0xde, 0x1a, 0x93, 0xde, 0x84, - 0xd4, 0x30, 0x12, 0x9f, 0xcf, 0xa8, 0x11, 0x02, 0xda, 0x72, 0xfc, 0x1d, 0x3e, 0xc3, 0xd0, 0x14, 0x58, 0xaa, 0xb8, - 0x85, 0xdd, 0xf0, 0x35, 0x9f, 0xac, 0x5a, 0x00, 0x82, 0x59, 0xf9, 0x7a, 0x17, 0xaf, 0x84, 0xfa, 0x54, 0x9b, 0x01, - 0x20, 0x0b, 0x4a, 0xb9, 0xe3, 0xa7, 0x54, 0x3a, 0x58, 0xa2, 0x68, 0x7b, 0x39, 0x7d, 0xa3, 0x63, 0xe3, 0xfb, 0xf4, - 0x5c, 0xc0, 0x76, 0x21, 0xbf, 0x75, 0xa7, 0x5e, 0xa2, 0x22, 0xb5, 0x6d, 0xd6, 0x3d, 0x7c, 0xb9, 0x41, 0x93, 0x30, - 0x82, 0x32, 0x65, 0x0a, 0x60, 0x70, 0x53, 0x8d, 0x82, 0x49, 0xab, 0x91, 0xb0, 0xa5, 0x9e, 0x64, 0xb9, 0xe9, 0x83, - 0x53, 0xdd, 0x21, 0xe8, 0xb9, 0x51, 0xce, 0x17, 0x2d, 0xfb, 0xb5, 0x82, 0xa3, 0x93, 0xab, 0x21, 0x6a, 0xe6, 0xbd, - 0xb6, 0x23, 0x43, 0xca, 0x65, 0x18, 0x08, 0xa6, 0x1c, 0xf3, 0xf4, 0xd8, 0x7a, 0x46, 0x44, 0xf7, 0x9c, 0x7d, 0xa6, - 0x5b, 0x75, 0x25, 0x01, 0xd1, 0xf1, 0x9b, 0xc7, 0x2f, 0x2f, 0xe3, 0x0b, 0x83, 0xa2, 0xd4, 0xb0, 0x88, 0x51, 0xa6, - 0x7d, 0x95, 0x84, 0xc1, 0xfb, 0xf0, 0xee, 0x27, 0x95, 0xa5, 0xf6, 0x7b, 0xb0, 0xb1, 0xa2, 0xaa, 0x0f, 0x25, 0x2f, - 0x9a, 0x02, 0xac, 0xbb, 0x2c, 0x51, 0x20, 0xf7, 0x3b, 0x9b, 0x66, 0xbe, 0x89, 0x1a, 0x37, 0x1b, 0xd6, 0x1b, 0xd7, - 0xed, 0x52, 0x5b, 0xb2, 0x23, 0x2b, 0x91, 0x33, 0x8b, 0xc1, 0x8c, 0x1f, 0x15, 0x06, 0xa5, 0x61, 0x83, 0xaa, 0x54, - 0xfc, 0xde, 0x88, 0xe0, 0xd4, 0xb1, 0xaa, 0x30, 0xa6, 0x01, 0xb3, 0xad, 0xa8, 0x35, 0xa8, 0x83, 0x52, 0xda, 0x9a, - 0x80, 0x6c, 0xbf, 0xb1, 0x82, 0x9a, 0xdf, 0xbf, 0x1b, 0x43, 0xbe, 0xa6, 0x14, 0x54, 0x12, 0xb0, 0x33, 0x68, 0xf4, - 0x54, 0x09, 0x03, 0x29, 0x08, 0x9e, 0x00, 0xe5, 0x8b, 0xa8, 0xb1, 0xda, 0xed, 0xab, 0x53, 0x63, 0xb4, 0x05, 0x84, - 0x16, 0xd2, 0xa3, 0xcb, 0x3e, 0x6e, 0x63, 0x1d, 0x48, 0x3c, 0x38, 0xc1, 0x76, 0xae, 0xae, 0xd1, 0x48, 0x68, 0x7e, - 0xdf, 0x68, 0xc0, 0x6b, 0x5a, 0x81, 0x42, 0x3d, 0xc7, 0xd1, 0xd0, 0xd9, 0x21, 0x05, 0x11, 0x1b, 0xb4, 0xb0, 0xef, - 0x8e, 0x0f, 0xcd, 0xbe, 0x9e, 0x27, 0x0b, 0x52, 0x53, 0xe9, 0x3e, 0x77, 0x4b, 0xc8, 0x5a, 0x75, 0x28, 0x2b, 0x0f, - 0x70, 0xbc, 0x50, 0x32, 0x7f, 0x87, 0x49, 0x8d, 0xd2, 0x98, 0xd0, 0x18, 0xb1, 0x80, 0x25, 0x41, 0x7b, 0x3d, 0x50, - 0xbf, 0x0c, 0x42, 0x85, 0x33, 0x3d, 0x91, 0xf8, 0x94, 0x72, 0xf5, 0x69, 0x41, 0xea, 0x69, 0xc1, 0x1c, 0xe8, 0xa5, - 0x6f, 0xe5, 0x57, 0x36, 0x3e, 0xda, 0xdd, 0xbb, 0xe6, 0xc2, 0x3a, 0x86, 0xb8, 0xd8, 0xc2, 0x6f, 0x4e, 0x4d, 0x01, - 0xd8, 0xf0, 0x58, 0x97, 0xe5, 0x1b, 0x35, 0x91, 0x59, 0x1c, 0x92, 0x08, 0x24, 0xdb, 0xcd, 0xcd, 0x6d, 0x04, 0xdb, - 0xde, 0x42, 0x6d, 0xa8, 0xbf, 0xbc, 0xed, 0x7e, 0xc7, 0xf0, 0x72, 0x4f, 0xee, 0xdd, 0xb4, 0xa1, 0xfc, 0xe1, 0xee, - 0x55, 0xf2, 0x7f, 0x55, 0xc9, 0xdd, 0x56, 0x99, 0x75, 0x5b, 0xbc, 0xdf, 0x75, 0xdc, 0x72, 0x8c, 0x06, 0x81, 0x35, - 0x05, 0x06, 0xd2, 0x93, 0xc6, 0x34, 0xd1, 0xd1, 0x95, 0x19, 0x33, 0x78, 0x74, 0x01, 0x9a, 0xc3, 0x74, 0x9e, 0xc7, - 0x00, 0x1c, 0xe0, 0x1f, 0x79, 0x84, 0xfa, 0xa7, 0xf3, 0x3c, 0x38, 0x0d, 0x06, 0xe5, 0x20, 0xd0, 0x9f, 0xb8, 0xe6, - 0x04, 0x0b, 0xd0, 0xb9, 0xc5, 0x0c, 0xe2, 0x4e, 0x5a, 0x33, 0x87, 0xf8, 0x30, 0x99, 0x0e, 0x06, 0x31, 0xd9, 0x00, - 0x48, 0x5f, 0xbc, 0xb0, 0xce, 0x41, 0x85, 0x5e, 0x90, 0xad, 0xba, 0x8b, 0x66, 0xc5, 0x5e, 0xb5, 0xd3, 0xbc, 0xdf, - 0xcf, 0xe7, 0xe5, 0x20, 0x68, 0x54, 0x58, 0x18, 0xef, 0x3f, 0xda, 0xfc, 0xd2, 0xe8, 0xa4, 0x09, 0x46, 0xac, 0x3d, - 0x46, 0xf5, 0x8a, 0xa7, 0x19, 0x6d, 0xdc, 0x8e, 0x95, 0xf2, 0x05, 0x44, 0xf1, 0xc0, 0x90, 0xb5, 0xf2, 0xee, 0x1c, - 0xbc, 0x2e, 0x37, 0xde, 0x1c, 0x51, 0x80, 0xdd, 0x14, 0xc6, 0x49, 0xcd, 0x45, 0x17, 0x35, 0xf1, 0x0c, 0x76, 0xba, - 0x7a, 0x2b, 0xd1, 0x6a, 0xbc, 0x17, 0xef, 0x9a, 0x8d, 0xbf, 0x96, 0x7b, 0xba, 0xcc, 0xbd, 0x73, 0x40, 0x9c, 0xdd, - 0x8b, 0xab, 0x3d, 0x2c, 0x75, 0x2f, 0x18, 0x58, 0xe4, 0x90, 0x76, 0xb5, 0x7a, 0x28, 0x22, 0x75, 0x1e, 0x83, 0x01, - 0x93, 0x69, 0x48, 0x4d, 0xa6, 0xbd, 0x58, 0x41, 0xda, 0x58, 0x6b, 0x01, 0x6d, 0x38, 0x2c, 0x76, 0xec, 0x86, 0xdd, - 0xe9, 0xd6, 0xa1, 0x50, 0xc2, 0x40, 0xd6, 0x75, 0xf3, 0x50, 0x6b, 0x78, 0x22, 0xe8, 0x41, 0x35, 0xda, 0x4f, 0x0f, - 0xe5, 0x49, 0x7b, 0x2c, 0xc0, 0x45, 0x0f, 0x5f, 0x3e, 0x17, 0x78, 0xd1, 0xde, 0x41, 0x9e, 0x33, 0x9f, 0x2a, 0x1f, - 0xc4, 0x86, 0x5b, 0x86, 0x0f, 0xed, 0xe3, 0x5b, 0x81, 0x4c, 0xea, 0x8e, 0xa6, 0xb6, 0x76, 0x47, 0xe3, 0x98, 0x40, - 0xbf, 0x29, 0x47, 0x29, 0x13, 0x53, 0xcb, 0x92, 0x1d, 0xf5, 0x72, 0xe5, 0x0d, 0x95, 0xb2, 0xa3, 0x65, 0x9b, 0xf3, - 0x4b, 0x1b, 0x09, 0xfd, 0xbe, 0x76, 0x07, 0xc2, 0x37, 0x6a, 0xbd, 0x21, 0x2f, 0x1b, 0x22, 0x96, 0x43, 0xcc, 0xc0, - 0xf1, 0x42, 0x2a, 0xd7, 0xee, 0xa2, 0xa9, 0xaa, 0xdb, 0xd9, 0xca, 0x05, 0x2d, 0xf1, 0x56, 0x0a, 0xac, 0x22, 0x75, - 0x7a, 0x3d, 0x95, 0x78, 0xd7, 0x47, 0xb1, 0xfd, 0x08, 0xd8, 0xc6, 0xc6, 0xd1, 0xd8, 0xb8, 0x45, 0x6c, 0xf0, 0x55, - 0x54, 0xd1, 0x82, 0x03, 0x04, 0x77, 0x5b, 0x52, 0x4b, 0x33, 0x87, 0xb8, 0xaf, 0x78, 0x80, 0xf6, 0x5d, 0x1c, 0x71, - 0x2a, 0xc0, 0xb6, 0xae, 0x75, 0xce, 0x6a, 0x39, 0x60, 0x33, 0xd1, 0xf3, 0x4f, 0xab, 0x46, 0x22, 0x86, 0x55, 0x36, - 0x52, 0x56, 0x68, 0xf7, 0x4a, 0x97, 0x70, 0xf1, 0x05, 0x78, 0xd9, 0xbe, 0x5b, 0xd9, 0x7d, 0xba, 0xc4, 0xfe, 0x61, - 0x5e, 0x35, 0xc1, 0x23, 0xaf, 0xf1, 0xf6, 0x1e, 0x26, 0xbe, 0x54, 0x0a, 0xe1, 0x55, 0x4a, 0x43, 0x09, 0xc0, 0x20, - 0x09, 0x6a, 0xb8, 0xd2, 0xb6, 0x19, 0xa4, 0x32, 0x86, 0xdd, 0xad, 0xde, 0xea, 0xff, 0xb4, 0x0a, 0x17, 0x95, 0x2c, - 0xc6, 0x24, 0xd0, 0x39, 0xd5, 0x72, 0x13, 0x58, 0xf0, 0x74, 0x97, 0x1c, 0x81, 0xc2, 0x4e, 0x00, 0x37, 0x94, 0xb0, - 0xdf, 0xf1, 0x36, 0x94, 0xb3, 0xd7, 0x56, 0xf2, 0xe4, 0xf6, 0x25, 0x15, 0x34, 0x21, 0x53, 0x61, 0xf7, 0x6f, 0x6b, - 0xc3, 0xbe, 0x0c, 0xe5, 0x48, 0x0a, 0x5c, 0x1c, 0x74, 0x0e, 0x60, 0x7f, 0x90, 0xcb, 0xd8, 0x7c, 0x26, 0xfd, 0xbe, - 0x7a, 0xff, 0x34, 0xcf, 0x92, 0x8f, 0x3b, 0xef, 0x0d, 0x4f, 0xb3, 0x64, 0x40, 0x25, 0x62, 0x6a, 0x5d, 0x15, 0xc3, - 0xa5, 0x76, 0x31, 0x6e, 0x90, 0x8c, 0xf8, 0x4e, 0xea, 0x10, 0x23, 0xc6, 0x17, 0xd9, 0x21, 0x29, 0x39, 0x5d, 0xd6, - 0x9d, 0x3d, 0xd7, 0xa2, 0x19, 0x34, 0x86, 0xdb, 0xf1, 0x5e, 0xd2, 0x2b, 0x40, 0x05, 0x88, 0xee, 0x59, 0xe0, 0x1a, - 0xde, 0x5c, 0x12, 0x8d, 0x2d, 0x3d, 0x6d, 0x89, 0x06, 0xee, 0x94, 0x09, 0x49, 0xb5, 0x71, 0x80, 0x45, 0xac, 0xeb, - 0x8f, 0x61, 0x01, 0x40, 0xad, 0x06, 0xe9, 0x95, 0xbe, 0x20, 0x54, 0x25, 0x21, 0x18, 0x9d, 0x48, 0x78, 0x19, 0xd0, - 0x38, 0x33, 0x89, 0x16, 0x36, 0x38, 0xa0, 0x2f, 0x2b, 0x93, 0x68, 0x6c, 0xc8, 0x03, 0xca, 0x6d, 0x1a, 0xc0, 0xe0, - 0x83, 0x24, 0x89, 0xbe, 0x5f, 0x9a, 0x24, 0x10, 0x94, 0xa0, 0x7c, 0x83, 0xfe, 0x51, 0x7a, 0x3e, 0x96, 0x3f, 0x7a, - 0x87, 0xd2, 0x0f, 0x61, 0x01, 0x32, 0x45, 0x5d, 0x31, 0xcd, 0xd8, 0x51, 0xd6, 0x6d, 0x4c, 0xe2, 0x79, 0xda, 0x5d, - 0x15, 0xca, 0xa5, 0x0b, 0xfc, 0xca, 0x32, 0xc4, 0xb1, 0x7e, 0x1a, 0xaf, 0xd8, 0x71, 0xc8, 0x35, 0x5e, 0xfa, 0xd3, - 0x78, 0x85, 0x33, 0x44, 0xab, 0x56, 0x02, 0x51, 0xfe, 0xab, 0x36, 0x70, 0x88, 0xfb, 0x04, 0x83, 0x5c, 0x54, 0xde, - 0x03, 0x81, 0xbc, 0xad, 0x20, 0x22, 0xcd, 0xec, 0x3a, 0x8c, 0x48, 0xb5, 0x93, 0x64, 0xbe, 0xfc, 0x51, 0x66, 0xc2, - 0xfb, 0x06, 0x1e, 0x9b, 0xcd, 0xb2, 0x29, 0xe6, 0x0b, 0x15, 0xcc, 0xc1, 0x7d, 0xa2, 0xe2, 0x52, 0x54, 0xfe, 0x13, - 0x76, 0xc1, 0x8b, 0xf1, 0xe0, 0xf5, 0x1a, 0x01, 0xf6, 0x2b, 0xff, 0xc9, 0x1b, 0xb3, 0xbf, 0xac, 0x1b, 0x5f, 0x66, - 0x22, 0x3e, 0xf0, 0xd1, 0x0d, 0xe5, 0xa3, 0x5b, 0x2f, 0xd3, 0x77, 0x0d, 0x28, 0x91, 0x51, 0x59, 0xf1, 0xd5, 0x8a, - 0xa7, 0xb3, 0xab, 0x24, 0xca, 0x46, 0x15, 0x17, 0x30, 0xbd, 0xe0, 0x78, 0x97, 0xac, 0xcf, 0xb2, 0xe4, 0x25, 0xc4, - 0x1e, 0x58, 0x49, 0x85, 0xc5, 0x0f, 0xcb, 0x4c, 0x2d, 0x66, 0x21, 0x2b, 0x29, 0x78, 0x30, 0xbb, 0x4e, 0xa2, 0xbf, - 0x96, 0x1e, 0x92, 0x9a, 0x99, 0xb2, 0x4d, 0xed, 0x08, 0xb5, 0xf1, 0x75, 0xa4, 0x1b, 0x6d, 0x01, 0x00, 0xf7, 0x6c, - 0x91, 0x46, 0x92, 0x89, 0xe1, 0xa4, 0x66, 0xdc, 0xa4, 0x17, 0x98, 0x1a, 0xd7, 0xac, 0xa2, 0x89, 0xb3, 0x90, 0x01, - 0xbd, 0x3f, 0xcd, 0xf5, 0x73, 0x06, 0xf7, 0x1f, 0xb4, 0x06, 0x2e, 0x0f, 0x8b, 0x7e, 0x5f, 0x1e, 0x16, 0xdb, 0x6d, - 0x79, 0x14, 0xf7, 0xfb, 0xf2, 0x28, 0x36, 0xfc, 0x83, 0x52, 0x6c, 0x1b, 0x73, 0x83, 0x84, 0xe6, 0x12, 0xa2, 0x16, - 0x8d, 0xe0, 0x0f, 0xcd, 0x72, 0x2e, 0xa2, 0xfc, 0x30, 0xe9, 0xf7, 0x7b, 0xcb, 0x99, 0x18, 0xe4, 0xc3, 0x24, 0xca, - 0x87, 0x89, 0xe7, 0x84, 0xf8, 0x8b, 0xe7, 0x84, 0xa8, 0x68, 0xe0, 0x0a, 0xce, 0x0c, 0x40, 0x14, 0xf0, 0xe9, 0x1f, - 0xd5, 0xb5, 0x14, 0xba, 0x96, 0x58, 0xd5, 0x92, 0xe8, 0x0a, 0x6a, 0x76, 0x5d, 0x84, 0x25, 0x96, 0x42, 0x97, 0xec, - 0xbb, 0x25, 0xf0, 0x44, 0x39, 0xaf, 0x36, 0xc0, 0xc0, 0x46, 0x78, 0xe7, 0x30, 0xe1, 0x24, 0xd6, 0x35, 0xa0, 0x9d, - 0x6e, 0x6a, 0x7a, 0x4e, 0x57, 0xf4, 0x02, 0xf9, 0xd9, 0x73, 0x30, 0x58, 0x3a, 0x64, 0xf9, 0x74, 0x30, 0x38, 0x27, - 0x2b, 0x56, 0xce, 0xc3, 0x78, 0x10, 0xae, 0x67, 0xf9, 0xf0, 0x3c, 0x3a, 0x27, 0xe4, 0xab, 0x62, 0x41, 0x7b, 0xab, - 0x51, 0xf9, 0x31, 0x83, 0xf0, 0x7e, 0xe9, 0x2c, 0xcc, 0x4c, 0x9c, 0x8f, 0xd5, 0xe8, 0x86, 0xae, 0x20, 0x7e, 0x0d, - 0xdc, 0x48, 0x48, 0x04, 0x1d, 0xb9, 0xa0, 0x2b, 0xba, 0xa6, 0xd2, 0xcc, 0x30, 0x46, 0xeb, 0xb6, 0xc7, 0x49, 0x02, - 0x8e, 0xc9, 0xae, 0xf8, 0x68, 0xac, 0x0a, 0xef, 0xfa, 0x8e, 0xd0, 0x5e, 0x2f, 0x71, 0x83, 0xf4, 0x43, 0x7b, 0x90, - 0x80, 0x11, 0x19, 0xa9, 0x81, 0x32, 0x23, 0x23, 0xa9, 0x99, 0x54, 0x1c, 0x92, 0xd8, 0x1f, 0x12, 0x35, 0x0e, 0x89, - 0x3f, 0x0e, 0xb9, 0x1e, 0x07, 0xe4, 0xee, 0x97, 0x6c, 0x4c, 0x53, 0x36, 0xa6, 0x6b, 0x35, 0x2a, 0xf4, 0x92, 0x9e, - 0x69, 0xea, 0x78, 0xca, 0x5e, 0xc1, 0x81, 0x3d, 0x08, 0xf3, 0x59, 0x3c, 0x7c, 0x15, 0xbd, 0x22, 0xe4, 0x2b, 0x49, - 0xaf, 0xd4, 0xa5, 0x0c, 0x02, 0x21, 0x5e, 0x82, 0x73, 0xa9, 0x0b, 0x75, 0x72, 0x69, 0x76, 0x1c, 0x3e, 0x5d, 0x34, - 0x9e, 0xce, 0x20, 0xa2, 0x0f, 0x5a, 0xa9, 0xf4, 0xfb, 0xe1, 0x39, 0x2b, 0xe7, 0xa7, 0xe1, 0x98, 0x00, 0x0e, 0x8f, - 0x1e, 0xce, 0xf3, 0xd1, 0x0d, 0x3d, 0x1f, 0xdd, 0x12, 0xb0, 0xf0, 0x1a, 0x4f, 0xd7, 0x87, 0x2c, 0x9e, 0x0e, 0x06, - 0x6b, 0xa4, 0xea, 0x2a, 0xf7, 0x9a, 0x2c, 0xe8, 0x39, 0x4e, 0x04, 0x01, 0x86, 0x3e, 0x13, 0x6b, 0x43, 0xc3, 0x5f, - 0x31, 0xf8, 0xf8, 0x96, 0x9d, 0x8f, 0x6e, 0xe9, 0x0d, 0x7b, 0xb5, 0x1d, 0x4f, 0x81, 0x99, 0x5a, 0xcd, 0xc2, 0xdb, - 0xc3, 0x8b, 0xd9, 0x05, 0xbb, 0x8d, 0x6e, 0x8f, 0xa0, 0xa1, 0x97, 0xec, 0x16, 0x01, 0x97, 0xd2, 0x87, 0xcb, 0xc1, - 0x2b, 0xb2, 0x3f, 0x18, 0xa4, 0x24, 0x0a, 0xaf, 0x42, 0xaf, 0x95, 0xaf, 0xe8, 0x2d, 0xa1, 0x2b, 0x76, 0x83, 0xa3, - 0x71, 0xc1, 0xf0, 0x83, 0x33, 0x76, 0x5b, 0x5f, 0x85, 0xde, 0x6e, 0x4e, 0x44, 0x27, 0x88, 0x11, 0xfa, 0x1a, 0x38, - 0x9a, 0xe5, 0xc2, 0x4c, 0xc0, 0x93, 0xb9, 0xc8, 0x68, 0x51, 0x68, 0x06, 0xe2, 0xac, 0x04, 0xc4, 0x92, 0xa8, 0xfb, - 0xcd, 0x46, 0xa7, 0xb0, 0x9c, 0xfb, 0xfd, 0x5e, 0x65, 0xe8, 0x01, 0x22, 0x67, 0x76, 0xd2, 0x83, 0x9e, 0x4f, 0x0f, - 0xf0, 0x13, 0xbd, 0x6a, 0x10, 0x27, 0xf3, 0x87, 0x65, 0xf4, 0x8b, 0x47, 0x1f, 0x3e, 0x74, 0x53, 0x9e, 0x32, 0xff, - 0xf7, 0x29, 0x8f, 0xcc, 0xa3, 0x57, 0x95, 0x07, 0x82, 0xe7, 0xad, 0x49, 0xa5, 0x91, 0xa8, 0x46, 0xa7, 0xab, 0x18, - 0xb4, 0x91, 0xa8, 0x6d, 0xd0, 0x4f, 0x68, 0x61, 0x05, 0x11, 0x72, 0x0e, 0x9e, 0x81, 0x41, 0x2a, 0x84, 0xca, 0x51, - 0x8b, 0x12, 0x0d, 0x41, 0x72, 0x59, 0x72, 0x15, 0x3e, 0x87, 0x50, 0x75, 0xfa, 0x38, 0x13, 0x61, 0x43, 0x8f, 0x43, - 0x1f, 0x00, 0xfe, 0xf7, 0x1d, 0x72, 0x51, 0xf2, 0x0b, 0x3c, 0x9b, 0xdb, 0x04, 0xa3, 0x60, 0x89, 0x68, 0x86, 0xb6, - 0x41, 0xec, 0xc7, 0x92, 0x60, 0x3d, 0x92, 0xc6, 0xa3, 0xd2, 0x1c, 0x11, 0x7e, 0x14, 0x1f, 0x45, 0x4f, 0x63, 0x43, - 0x22, 0x39, 0x92, 0x48, 0x3e, 0x00, 0xc2, 0x49, 0xd0, 0x5f, 0xdc, 0x35, 0xd9, 0xb5, 0x90, 0x18, 0xf4, 0xa7, 0x25, - 0xd3, 0xb2, 0x7b, 0xd5, 0x63, 0x5f, 0x11, 0xe4, 0x8e, 0xe9, 0xdf, 0xbc, 0x3e, 0xfc, 0xbd, 0xc4, 0x19, 0xb4, 0x9e, - 0x2f, 0xaa, 0x33, 0x33, 0x6f, 0x70, 0x23, 0xaf, 0xcb, 0xda, 0x75, 0xf9, 0x9c, 0xef, 0xf1, 0x9b, 0x8a, 0x8b, 0xb4, - 0xdc, 0xfb, 0xb9, 0x6a, 0xe3, 0x39, 0x95, 0xeb, 0x95, 0x8b, 0xb3, 0xa2, 0x8c, 0x53, 0x3d, 0xa9, 0x8b, 0xb1, 0x86, - 0x6d, 0xf8, 0x3d, 0xa2, 0xae, 0xa4, 0xe5, 0xe8, 0x29, 0xe5, 0xaa, 0x99, 0x72, 0xbe, 0xce, 0xf3, 0x9f, 0x76, 0x52, - 0x71, 0x8a, 0x9b, 0x29, 0x48, 0x95, 0x5a, 0x2e, 0xa0, 0x7a, 0x8e, 0x5a, 0xee, 0x96, 0x66, 0x07, 0x38, 0xb7, 0x4d, - 0xf5, 0xb1, 0x32, 0xbb, 0xf0, 0x92, 0x1b, 0xf7, 0x27, 0x53, 0x86, 0x05, 0xa3, 0xd0, 0x66, 0xd5, 0x95, 0xb6, 0x2f, - 0xb4, 0x4e, 0xc3, 0x70, 0xe5, 0xc7, 0x0b, 0x48, 0x17, 0x30, 0x8e, 0x17, 0x25, 0x13, 0xe3, 0xf6, 0xe8, 0xad, 0x20, - 0xbe, 0x64, 0x2b, 0x90, 0x7e, 0xbf, 0x27, 0xbc, 0x5d, 0xd7, 0xd1, 0x76, 0x4f, 0x9c, 0x32, 0x2a, 0x57, 0xb1, 0xf8, - 0x3e, 0x5e, 0x19, 0xc8, 0x64, 0x75, 0x3c, 0x36, 0xc6, 0x74, 0xfa, 0x7d, 0x12, 0xfa, 0x85, 0x50, 0xf0, 0x59, 0x2f, - 0xad, 0x3c, 0xb9, 0x3d, 0x2c, 0xe3, 0x1a, 0xbd, 0x12, 0x57, 0xba, 0x6f, 0x46, 0x0a, 0xa9, 0x47, 0xbe, 0x6a, 0x0a, - 0xe8, 0xcd, 0xd8, 0x37, 0x53, 0x61, 0xde, 0xee, 0x18, 0x73, 0x85, 0x60, 0xa5, 0xca, 0x6e, 0xdf, 0xa9, 0x31, 0x15, - 0x33, 0x98, 0x62, 0xdb, 0x59, 0x4c, 0xba, 0x95, 0x7f, 0xda, 0xb9, 0x4f, 0xf3, 0x0e, 0x77, 0x45, 0xfd, 0x16, 0xb8, - 0xd0, 0xac, 0x28, 0xab, 0xb6, 0x6c, 0xd8, 0x36, 0xde, 0xc8, 0x42, 0xb1, 0x01, 0x96, 0x3d, 0xf7, 0x2d, 0x3c, 0x40, - 0xdc, 0x84, 0x7b, 0x76, 0x51, 0xc3, 0x8d, 0xe1, 0xcb, 0x4a, 0xf2, 0x5d, 0x69, 0xcc, 0xa5, 0x4f, 0x95, 0x26, 0x86, - 0x93, 0xc5, 0x88, 0x8b, 0x74, 0x51, 0x67, 0x76, 0x2d, 0x7c, 0xc6, 0xcb, 0x70, 0xce, 0x17, 0x46, 0x37, 0xa5, 0x4b, - 0x2f, 0x58, 0xa2, 0x3b, 0xbd, 0x59, 0x69, 0xac, 0x94, 0x88, 0x5b, 0xb3, 0x4c, 0xa0, 0x2c, 0x65, 0xad, 0x84, 0x37, - 0x45, 0xcb, 0x56, 0xd2, 0xc8, 0x7b, 0xe6, 0xe0, 0x3e, 0xf6, 0x01, 0x31, 0x91, 0x4d, 0x60, 0x52, 0x34, 0x74, 0x40, - 0xbb, 0xea, 0xc2, 0x37, 0xa3, 0x1e, 0x0c, 0x72, 0x4b, 0x12, 0xb1, 0x82, 0x14, 0x2b, 0x58, 0xd7, 0xac, 0x98, 0xe7, - 0x0b, 0x7a, 0xce, 0xe4, 0x3c, 0x5d, 0xd0, 0x15, 0x93, 0xf3, 0x35, 0xde, 0x84, 0xce, 0xe1, 0x84, 0x24, 0x9b, 0x58, - 0x29, 0x60, 0xcf, 0xf1, 0xf2, 0x86, 0x67, 0xaa, 0xa6, 0x65, 0x17, 0x8a, 0x03, 0x8c, 0xcf, 0xca, 0x30, 0x2c, 0x87, - 0xe7, 0x60, 0x2d, 0xb1, 0x1f, 0xae, 0xe6, 0x7c, 0xa1, 0x7e, 0x43, 0xd4, 0xf9, 0x24, 0x54, 0xec, 0x82, 0xdd, 0x0b, - 0x64, 0x7a, 0x39, 0xe7, 0x0b, 0x35, 0x12, 0xba, 0xe0, 0x4b, 0x6b, 0x6c, 0x12, 0x7b, 0x82, 0x96, 0x59, 0x3c, 0x1f, - 0x2f, 0xa2, 0xb8, 0x86, 0x65, 0x78, 0xa2, 0x66, 0xa6, 0x25, 0xff, 0x49, 0xd4, 0x86, 0x26, 0xfa, 0x06, 0xab, 0xc8, - 0x1f, 0x1e, 0x1f, 0x5d, 0x02, 0x19, 0x3b, 0xbb, 0x92, 0x99, 0x0f, 0x7d, 0x1f, 0x19, 0xdc, 0x73, 0x53, 0xce, 0xb8, - 0x0a, 0x12, 0x65, 0xe0, 0xee, 0xd5, 0x2c, 0x19, 0x6b, 0x11, 0xbe, 0x7b, 0x54, 0x14, 0x7d, 0x26, 0x4d, 0x03, 0xba, - 0x8f, 0x04, 0x73, 0xa0, 0xf7, 0x0a, 0x1d, 0x2e, 0xab, 0x6d, 0x26, 0xe0, 0x2f, 0x12, 0xe4, 0xb7, 0x42, 0xaf, 0x6a, - 0x0c, 0xaa, 0x68, 0x17, 0xb1, 0xf4, 0xef, 0x23, 0x7e, 0x94, 0xcd, 0xdf, 0xcc, 0x3d, 0x5e, 0x49, 0x18, 0xfc, 0x90, - 0x9a, 0x4d, 0x32, 0x6f, 0xaf, 0xd8, 0x77, 0xd0, 0x51, 0x8f, 0x5a, 0xe3, 0x7d, 0xf5, 0x9c, 0x53, 0x88, 0x51, 0x42, - 0xd1, 0x49, 0x30, 0x80, 0xdb, 0x25, 0xa4, 0xb8, 0x1b, 0xec, 0xa6, 0x79, 0xcd, 0x8b, 0x82, 0xb3, 0x75, 0x55, 0x05, - 0x7e, 0x40, 0xc3, 0xf9, 0x62, 0x37, 0x84, 0xe1, 0x98, 0xb6, 0xae, 0x61, 0x10, 0x66, 0x0c, 0x23, 0x21, 0x78, 0xfd, - 0x8b, 0x1e, 0xd1, 0x24, 0x5e, 0x7d, 0xc7, 0x3f, 0x65, 0xbc, 0x50, 0x44, 0x1a, 0x44, 0x48, 0xdd, 0xc4, 0x37, 0x32, - 0x4d, 0x0a, 0x28, 0x04, 0x18, 0x05, 0x54, 0x62, 0x43, 0x53, 0xf1, 0xb7, 0x5a, 0x7c, 0xf0, 0x53, 0xd3, 0xf1, 0x68, - 0x5c, 0xb7, 0x3a, 0xa3, 0x82, 0xce, 0x40, 0x8f, 0x5a, 0x51, 0x4f, 0x83, 0x56, 0x82, 0x69, 0xa4, 0x79, 0xeb, 0x1e, - 0x02, 0xaf, 0x4c, 0x8b, 0x77, 0x1e, 0xd0, 0xcd, 0xa9, 0x0f, 0x9e, 0x3c, 0xa6, 0xa7, 0x0e, 0x3d, 0xb9, 0x62, 0x47, - 0x55, 0x0f, 0xb5, 0xf7, 0x66, 0x84, 0x82, 0x7e, 0x1f, 0x53, 0xa0, 0x1b, 0x41, 0xed, 0x5d, 0xdd, 0x2b, 0xb9, 0xcb, - 0xe1, 0x3b, 0xce, 0x72, 0x03, 0x58, 0x2a, 0xb2, 0x56, 0xe0, 0x51, 0x80, 0xba, 0x54, 0x86, 0xb0, 0xc5, 0x1c, 0x0e, - 0x95, 0xdd, 0xaa, 0xd5, 0x50, 0x92, 0xc3, 0x72, 0x04, 0x0e, 0xa1, 0xeb, 0x72, 0x50, 0x8e, 0x96, 0x59, 0xf5, 0x0e, - 0x7f, 0x6b, 0xd6, 0x21, 0xc9, 0xee, 0x62, 0x1d, 0xb8, 0x65, 0x1d, 0xa6, 0x1f, 0x0d, 0x52, 0x00, 0x9a, 0x6c, 0x04, - 0x2e, 0x01, 0x78, 0x6f, 0xff, 0x11, 0xa1, 0x56, 0xa6, 0x77, 0x32, 0x16, 0xea, 0xfb, 0x46, 0x12, 0x94, 0xd0, 0x4c, - 0xa8, 0x1c, 0x4b, 0xc1, 0x3b, 0x8f, 0x74, 0x4e, 0xea, 0x4c, 0xbc, 0x03, 0x71, 0x5a, 0x78, 0xcf, 0xde, 0x82, 0xe0, - 0x9c, 0x05, 0xbd, 0xc5, 0xdb, 0xac, 0x96, 0xda, 0xe8, 0x81, 0x02, 0xf8, 0xdd, 0xe0, 0x16, 0x41, 0xbe, 0x1a, 0xc3, - 0xb5, 0x92, 0xd7, 0x21, 0x1f, 0x16, 0xf4, 0x80, 0x0c, 0xec, 0xb3, 0x18, 0xc6, 0xf4, 0x80, 0x1c, 0xda, 0x67, 0xe9, - 0x06, 0x70, 0x20, 0xf5, 0xa8, 0xd2, 0x03, 0x68, 0xd0, 0x6f, 0xb6, 0x45, 0xee, 0x00, 0x94, 0x46, 0x11, 0x03, 0x55, - 0x82, 0x88, 0x5a, 0xfc, 0x7e, 0x6f, 0xae, 0x5b, 0xcc, 0x05, 0xc2, 0x1c, 0x0c, 0x38, 0x88, 0xdb, 0x20, 0x34, 0x07, - 0xcc, 0xe6, 0x26, 0x12, 0xf4, 0xd6, 0x1a, 0x66, 0x76, 0xf4, 0x87, 0x5b, 0x09, 0xbe, 0xc9, 0x5a, 0xa3, 0xce, 0x8b, - 0x43, 0x20, 0x08, 0xde, 0x14, 0xaa, 0xda, 0xab, 0x1e, 0xd8, 0x78, 0xab, 0x7e, 0x6c, 0xb7, 0xe3, 0xa9, 0x70, 0xd7, - 0x7e, 0x41, 0xe1, 0xe4, 0x53, 0xf2, 0xaf, 0x77, 0x26, 0x83, 0x03, 0x23, 0xc3, 0x97, 0xde, 0xfe, 0x85, 0xaf, 0xb5, - 0x74, 0x4f, 0x0c, 0x4a, 0xf2, 0xf0, 0x40, 0xd1, 0xbf, 0x3b, 0x65, 0xe5, 0x53, 0x3b, 0xfd, 0xdb, 0xad, 0x59, 0x9f, - 0x87, 0xa3, 0xc9, 0x76, 0xdb, 0x8b, 0x2b, 0xed, 0xb1, 0xa6, 0x17, 0x04, 0x3a, 0xd7, 0x93, 0xfd, 0x03, 0x88, 0x8a, - 0xd0, 0x8c, 0xbb, 0x59, 0x36, 0x24, 0x32, 0x7e, 0x9c, 0xce, 0xb2, 0x21, 0xd8, 0xe1, 0x5e, 0x54, 0xe2, 0x72, 0xd4, - 0xda, 0xe0, 0xf4, 0x36, 0x09, 0x21, 0x94, 0x03, 0x56, 0x76, 0xa3, 0xfe, 0xdc, 0x2a, 0x33, 0x21, 0x35, 0x59, 0xdd, - 0x4e, 0xe9, 0x1e, 0xa6, 0xf9, 0x9e, 0x19, 0xc1, 0x01, 0xf7, 0xf6, 0x57, 0xfd, 0x31, 0x4c, 0x32, 0x4d, 0x4e, 0x91, - 0xfc, 0x22, 0x3d, 0x85, 0xa4, 0x1d, 0x7a, 0xaa, 0x08, 0xe0, 0x84, 0xda, 0x8f, 0xe1, 0x37, 0x8c, 0xfb, 0x77, 0xcd, - 0xd7, 0x6e, 0x2a, 0xa2, 0xc7, 0x14, 0xcb, 0xd4, 0xe4, 0x34, 0xc9, 0x8a, 0x04, 0xa2, 0x36, 0xaa, 0x66, 0x44, 0x8f, - 0x5c, 0xcc, 0x47, 0x45, 0xf8, 0xbc, 0x5a, 0xff, 0x67, 0x08, 0x9f, 0x51, 0xb8, 0x01, 0x5c, 0x5e, 0x71, 0x71, 0x16, - 0x3e, 0x79, 0x4c, 0xf7, 0x26, 0xdf, 0x1c, 0xd0, 0xbd, 0x83, 0x47, 0x4f, 0x08, 0xc0, 0xa2, 0x5d, 0x9c, 0x85, 0x07, - 0x4f, 0x9e, 0xd0, 0xbd, 0x6f, 0xbf, 0xa5, 0x7b, 0x93, 0x47, 0x07, 0x8d, 0xb4, 0xc9, 0x93, 0x6f, 0xe9, 0xde, 0x37, - 0x8f, 0x1b, 0x69, 0x07, 0xe3, 0x27, 0x74, 0xef, 0x9f, 0xdf, 0x98, 0xb4, 0x7f, 0x40, 0xb6, 0x6f, 0x0f, 0xf0, 0x3f, - 0x93, 0x36, 0x79, 0xf2, 0x88, 0xee, 0x4d, 0xc6, 0x50, 0xc9, 0x13, 0x57, 0xc9, 0x78, 0x02, 0x1f, 0x3f, 0x82, 0xff, - 0xfe, 0x41, 0x60, 0x13, 0x48, 0x96, 0x0b, 0xd4, 0x9f, 0xa1, 0x88, 0x13, 0x55, 0x13, 0x09, 0x0f, 0x31, 0xb3, 0xfa, - 0x26, 0x0e, 0x03, 0xe2, 0xd2, 0xa1, 0x20, 0xba, 0x37, 0x1e, 0x3d, 0x21, 0x81, 0x0f, 0x4f, 0xf7, 0xd1, 0x07, 0x19, - 0xcb, 0xc5, 0x3c, 0xfb, 0x2a, 0x37, 0xb1, 0x15, 0x3c, 0x00, 0xab, 0x13, 0x3f, 0x17, 0x97, 0xf3, 0xec, 0x2b, 0x2e, - 0x77, 0x73, 0xfd, 0xab, 0x05, 0x28, 0xef, 0xaf, 0x5a, 0xf6, 0xb1, 0x50, 0xa1, 0xd3, 0x5a, 0xa3, 0xcf, 0x4e, 0x30, - 0x7d, 0x30, 0xf0, 0x6e, 0xd8, 0xdf, 0xef, 0x94, 0xd3, 0xfa, 0x46, 0xa3, 0x50, 0xa3, 0xf2, 0x90, 0xb0, 0x23, 0x28, - 0x7a, 0x30, 0x00, 0x9e, 0xc0, 0xc3, 0x7d, 0xfb, 0x37, 0xcb, 0x38, 0xe9, 0x28, 0xe3, 0x0f, 0x94, 0x21, 0xa0, 0x51, - 0x0f, 0xb3, 0x9b, 0x1e, 0x36, 0xba, 0xd5, 0x4b, 0x96, 0xea, 0x64, 0x6a, 0x7a, 0x06, 0xfb, 0x5a, 0xd7, 0x72, 0xcf, - 0x88, 0xa2, 0xe5, 0xf9, 0x5e, 0xca, 0x67, 0x15, 0xfb, 0x7e, 0x89, 0xea, 0xad, 0xa8, 0xf1, 0x46, 0x66, 0xb3, 0x8a, - 0xfd, 0x6c, 0xde, 0x00, 0x37, 0xc3, 0xfe, 0xa5, 0x9e, 0xfc, 0xc0, 0x19, 0x99, 0xb4, 0xed, 0x51, 0x26, 0x46, 0x80, - 0x15, 0x90, 0x81, 0x03, 0x0f, 0x80, 0x0e, 0xfa, 0xa3, 0xbd, 0xdd, 0xaa, 0x94, 0x66, 0x9f, 0x2d, 0x0c, 0xa0, 0x61, - 0xde, 0x26, 0x1e, 0xaa, 0x59, 0x43, 0x5e, 0x82, 0xc2, 0xad, 0x66, 0x79, 0x3b, 0x85, 0x21, 0x84, 0x60, 0x95, 0x32, - 0x00, 0x1c, 0x08, 0x30, 0x18, 0x6b, 0x19, 0x50, 0xb3, 0xe5, 0xa3, 0x0d, 0x57, 0xea, 0x49, 0xe0, 0x0c, 0xce, 0x65, - 0x91, 0xf0, 0x37, 0x5a, 0xec, 0x8f, 0xd6, 0x8f, 0xbe, 0x6f, 0x8f, 0x07, 0x6b, 0xdf, 0xe3, 0x23, 0xfd, 0x59, 0xe3, - 0x3a, 0xb0, 0x69, 0xf9, 0xc6, 0x8b, 0xda, 0x4a, 0x3c, 0x4a, 0xe0, 0x0d, 0x4c, 0x44, 0x0a, 0x83, 0x54, 0x0b, 0x1c, - 0x83, 0xf2, 0xc6, 0x42, 0x2c, 0x55, 0x57, 0x37, 0x74, 0x4b, 0x86, 0xe0, 0xe1, 0xf6, 0xe3, 0x52, 0x05, 0x8e, 0xea, - 0xf7, 0x33, 0xe9, 0xbb, 0x3d, 0x19, 0x3b, 0x72, 0x9c, 0xfa, 0xa9, 0x70, 0xf0, 0xdf, 0xa4, 0xae, 0x8d, 0xdd, 0x7d, - 0xca, 0x2c, 0xcb, 0xc2, 0x8e, 0x42, 0x2d, 0xf7, 0xa8, 0x3c, 0x48, 0xbe, 0x90, 0x43, 0x24, 0x0b, 0x8c, 0x42, 0x41, - 0x86, 0x13, 0x2a, 0x46, 0x6b, 0x51, 0x2e, 0xb3, 0xf3, 0x2a, 0xdc, 0x28, 0x85, 0x32, 0xa7, 0xe8, 0xdb, 0x0d, 0x0e, - 0x24, 0x24, 0xca, 0xca, 0xd7, 0xf1, 0xeb, 0x10, 0xc1, 0xea, 0xb8, 0xb6, 0x85, 0xe2, 0xde, 0xfe, 0xcc, 0xd2, 0x2e, - 0xfe, 0xc8, 0xb8, 0x80, 0xba, 0x58, 0x4c, 0xc3, 0x89, 0xd5, 0xef, 0xb8, 0x2f, 0xac, 0xa6, 0x07, 0xa0, 0xbe, 0x4b, - 0x25, 0x46, 0x50, 0x5f, 0x19, 0xfb, 0xd8, 0x1e, 0x63, 0x72, 0x06, 0xb1, 0x86, 0xf5, 0xdd, 0x4e, 0xf5, 0x8d, 0xb0, - 0x23, 0x00, 0x6e, 0x84, 0xd6, 0xe8, 0xc8, 0x24, 0x55, 0x88, 0xe7, 0xa5, 0x0a, 0xdf, 0x9a, 0x11, 0x3a, 0x06, 0x6f, - 0x2a, 0xdb, 0x48, 0x21, 0x7d, 0xc1, 0xa0, 0x39, 0xb6, 0x75, 0x14, 0x56, 0x5b, 0x59, 0x76, 0x04, 0x70, 0x03, 0xd9, - 0xa1, 0xb9, 0x78, 0xce, 0xaa, 0x79, 0xb6, 0x88, 0x4c, 0x50, 0xc0, 0xa5, 0xb0, 0x0c, 0xda, 0xeb, 0x3b, 0x64, 0x3b, - 0x0e, 0xa1, 0x1b, 0xee, 0x23, 0x18, 0x4f, 0xbb, 0x29, 0x58, 0x41, 0x34, 0x42, 0x3c, 0xcc, 0x98, 0xc5, 0xf7, 0x4a, - 0x53, 0x9e, 0xaa, 0x96, 0x40, 0xe0, 0x28, 0x84, 0xba, 0xd8, 0x35, 0x4a, 0x70, 0x99, 0x1a, 0xc1, 0x0c, 0x76, 0xec, - 0x48, 0x6d, 0x97, 0x9c, 0xd3, 0xa1, 0x9a, 0xd2, 0x52, 0x4f, 0xa9, 0xf6, 0x35, 0x14, 0xf3, 0x12, 0x3d, 0xf4, 0xc0, - 0xf5, 0x40, 0x3b, 0xe4, 0x95, 0x74, 0x62, 0x22, 0xe8, 0xb4, 0xda, 0x84, 0x9d, 0x1b, 0xe9, 0x96, 0xd5, 0xc8, 0x3b, - 0x86, 0x66, 0x47, 0x3c, 0xf7, 0x03, 0x75, 0x01, 0x44, 0xc8, 0x9d, 0x2d, 0x32, 0xb3, 0xcf, 0xb2, 0xf2, 0x05, 0x94, - 0xc5, 0x11, 0x5b, 0x57, 0xc0, 0xb5, 0x14, 0x4c, 0x2e, 0x79, 0x94, 0xa5, 0x88, 0x08, 0x78, 0xac, 0xb4, 0xeb, 0x3b, - 0x2d, 0x21, 0x54, 0xa4, 0x40, 0xdc, 0x5c, 0x14, 0xe7, 0xda, 0x06, 0xb2, 0x00, 0xfa, 0xf6, 0x53, 0x76, 0xe9, 0x85, - 0x83, 0xdd, 0x5c, 0x66, 0xe2, 0x19, 0x3f, 0xcf, 0x04, 0x4f, 0x11, 0xec, 0xea, 0xc6, 0x3c, 0x70, 0xc7, 0xb6, 0x81, - 0xe5, 0xdb, 0x77, 0xb0, 0x60, 0xca, 0x50, 0x2b, 0x25, 0x32, 0x11, 0x09, 0xc8, 0xec, 0x33, 0x77, 0xaf, 0x32, 0xf1, - 0x2a, 0xbe, 0x01, 0x6f, 0x8a, 0x06, 0x3f, 0x3d, 0x3a, 0xc3, 0x2f, 0x11, 0x49, 0x14, 0x62, 0xd8, 0x62, 0x44, 0x2c, - 0x44, 0x8e, 0x1d, 0x13, 0xca, 0x95, 0xa0, 0xb5, 0x35, 0x04, 0x5e, 0xfc, 0x69, 0xd5, 0xbd, 0xcb, 0x4c, 0x18, 0xfb, - 0x8c, 0xcb, 0xf8, 0x86, 0x95, 0x0a, 0xcc, 0x02, 0xe3, 0xdc, 0xb7, 0xa5, 0x24, 0x97, 0x99, 0x30, 0x02, 0x92, 0xcb, - 0xf8, 0x86, 0x36, 0x65, 0x1c, 0xda, 0x8a, 0xce, 0x8b, 0xf3, 0xbb, 0x3b, 0xfc, 0x12, 0x43, 0xad, 0x8c, 0xfb, 0x7d, - 0x90, 0x98, 0x49, 0xdb, 0x94, 0x99, 0x8c, 0xa4, 0x46, 0x0b, 0xa9, 0x28, 0x1f, 0x4c, 0xc8, 0xee, 0x4a, 0xb5, 0x8c, - 0xa8, 0xfd, 0x2a, 0x14, 0xb3, 0x71, 0x34, 0x21, 0x74, 0xd2, 0xb1, 0xde, 0x4d, 0x6b, 0x21, 0xd3, 0xe8, 0x49, 0xe4, - 0xf9, 0x74, 0x16, 0xac, 0x9a, 0x16, 0x87, 0x8c, 0x4f, 0x8b, 0xc1, 0x80, 0x68, 0x97, 0xc2, 0x0d, 0xd6, 0x03, 0xa6, - 0x34, 0x2e, 0xde, 0x9a, 0x69, 0xf5, 0x0b, 0xa9, 0x42, 0xd2, 0x7b, 0x06, 0x24, 0x42, 0xba, 0x60, 0xb7, 0x20, 0x51, - 0xf4, 0xfc, 0xef, 0xd4, 0x16, 0xdc, 0xf5, 0x60, 0x6c, 0x46, 0xf7, 0xf5, 0x8c, 0xff, 0x50, 0xdb, 0x82, 0xa8, 0x4f, - 0x25, 0xeb, 0x75, 0x24, 0xaa, 0x90, 0x8b, 0xf0, 0xb3, 0xa3, 0x21, 0x86, 0xa8, 0xf6, 0x58, 0x20, 0xd6, 0x97, 0x67, - 0xbc, 0xc0, 0xe9, 0x67, 0xee, 0x72, 0x05, 0xdb, 0x82, 0x56, 0x86, 0x46, 0xbd, 0x8e, 0x5f, 0x47, 0xf6, 0xb2, 0xa0, - 0x8b, 0x7c, 0x86, 0x42, 0xd6, 0x3c, 0x0c, 0xab, 0x61, 0x7b, 0x10, 0xc9, 0x7e, 0x7b, 0x12, 0x1a, 0x8d, 0x81, 0x05, - 0xb2, 0x43, 0x23, 0x70, 0x11, 0x5a, 0xf9, 0xdb, 0x21, 0xb8, 0x70, 0x59, 0x44, 0x96, 0xa1, 0x8e, 0xdf, 0xd4, 0x6e, - 0x82, 0xea, 0x15, 0x3a, 0x4d, 0x61, 0x55, 0xca, 0x24, 0x1f, 0x7e, 0xbd, 0x90, 0x05, 0x66, 0xf2, 0xba, 0xec, 0xd1, - 0xd7, 0x76, 0x7b, 0x07, 0xa6, 0x60, 0xdd, 0x27, 0xef, 0xeb, 0x87, 0x9d, 0x3d, 0x01, 0xa3, 0x58, 0x95, 0xa3, 0x29, - 0xa4, 0xd4, 0x3e, 0x28, 0xf5, 0xc7, 0x70, 0x29, 0x34, 0xc7, 0x6e, 0x01, 0x93, 0x80, 0x7d, 0x86, 0x54, 0x8f, 0x69, - 0xc7, 0x3e, 0x47, 0x1b, 0x58, 0x12, 0x70, 0xf8, 0x47, 0x42, 0xd6, 0xfe, 0xd5, 0xbd, 0x4c, 0x9b, 0x21, 0x5b, 0xe6, - 0x0b, 0xe0, 0xf3, 0x61, 0xd7, 0x46, 0x25, 0xca, 0x26, 0x22, 0x49, 0x61, 0xcb, 0x63, 0x90, 0xf6, 0x28, 0xa6, 0xab, - 0x82, 0x27, 0x19, 0x4a, 0x29, 0x12, 0xed, 0x13, 0x9c, 0xc3, 0x1b, 0xdc, 0x8f, 0x2a, 0x20, 0xbc, 0x0a, 0x39, 0x1d, - 0xa5, 0x54, 0x5b, 0xc0, 0x28, 0xea, 0x01, 0xa2, 0xbc, 0x0c, 0xe4, 0x78, 0xdb, 0xed, 0x84, 0xae, 0xd8, 0x72, 0x38, - 0xa1, 0x48, 0x4a, 0x2e, 0xb0, 0xdc, 0x4b, 0xd0, 0x79, 0x9c, 0xb1, 0xde, 0x73, 0xc0, 0x22, 0x38, 0x85, 0xbf, 0x31, - 0xa1, 0x57, 0xf0, 0x37, 0x27, 0xf4, 0x15, 0x0b, 0x2f, 0x87, 0x17, 0x64, 0x3f, 0x4c, 0x07, 0x13, 0x25, 0x18, 0xbb, - 0x65, 0x69, 0x19, 0xaa, 0xc4, 0xd5, 0xfe, 0x39, 0x79, 0x78, 0x4e, 0x6f, 0xe8, 0x35, 0x3d, 0xa1, 0x6f, 0x80, 0xf0, - 0xdf, 0x1e, 0x4e, 0xf8, 0x70, 0xf2, 0xb8, 0xdf, 0xef, 0x9d, 0xf5, 0xfb, 0xbd, 0x53, 0x63, 0x40, 0xa1, 0x77, 0xd1, - 0x45, 0x4d, 0xf5, 0xaf, 0xcb, 0x7a, 0x31, 0x7d, 0xa3, 0x36, 0x6e, 0xc2, 0xb3, 0x3c, 0xbc, 0xdc, 0xbf, 0x25, 0x43, - 0x7c, 0x3c, 0xcf, 0xa5, 0x2c, 0xc2, 0x8b, 0xfd, 0x5b, 0x42, 0xdf, 0x1c, 0x81, 0xde, 0x14, 0xeb, 0x7b, 0xf3, 0xf0, - 0x56, 0xd7, 0x46, 0xe8, 0xf3, 0x30, 0x81, 0x6d, 0x72, 0xc3, 0xec, 0x5d, 0x7b, 0x32, 0x86, 0x58, 0x26, 0xb7, 0x5e, - 0x79, 0xb7, 0x0f, 0x6f, 0xc8, 0xfe, 0x0d, 0x78, 0x8a, 0x5a, 0xf2, 0x37, 0x0b, 0xaf, 0x59, 0xab, 0x86, 0x87, 0xb7, - 0xf4, 0xa4, 0xd5, 0x88, 0x87, 0xb7, 0x24, 0x0a, 0xaf, 0xd9, 0x05, 0x3d, 0x61, 0x97, 0x84, 0x9e, 0xf5, 0xfb, 0xa7, - 0xfd, 0xbe, 0xec, 0xf7, 0xbf, 0x8f, 0xc3, 0x30, 0x1e, 0x16, 0x64, 0x5f, 0xd2, 0xdb, 0xfd, 0x09, 0x7f, 0x44, 0x66, - 0xa1, 0x6e, 0xbe, 0x5a, 0x70, 0x56, 0xe5, 0xad, 0x72, 0xdd, 0x52, 0xb0, 0x56, 0xb8, 0x65, 0xea, 0xe9, 0x0d, 0xbd, - 0x66, 0x05, 0x3d, 0x61, 0x31, 0x89, 0xae, 0xa0, 0x15, 0x67, 0xb3, 0x22, 0xba, 0xa6, 0x27, 0xec, 0x74, 0x16, 0x47, - 0x27, 0xf4, 0x0d, 0xcb, 0x87, 0x13, 0xc8, 0x7b, 0x32, 0xbc, 0x26, 0xfb, 0x6f, 0x48, 0x14, 0xbe, 0xd1, 0xbf, 0x6f, - 0xe9, 0x05, 0x0f, 0xdf, 0x50, 0xaf, 0x9a, 0x37, 0xc4, 0x54, 0xdf, 0xa8, 0xfd, 0x0d, 0x89, 0xfc, 0xc1, 0x7c, 0x63, - 0xed, 0x69, 0x1e, 0x38, 0xda, 0xb8, 0x2e, 0xc3, 0x5b, 0x42, 0xd7, 0x65, 0x78, 0x4d, 0xc8, 0xb4, 0x39, 0x76, 0x30, - 0xa0, 0xb3, 0x07, 0x51, 0x42, 0xe8, 0xb5, 0x5f, 0xea, 0x35, 0x8e, 0xa1, 0x19, 0x21, 0x95, 0x76, 0x82, 0x69, 0xb8, - 0x0e, 0x9e, 0x69, 0xb0, 0x8e, 0xb3, 0x7e, 0x3f, 0x5c, 0xf7, 0xfb, 0x10, 0xe9, 0xbe, 0x98, 0x99, 0xd8, 0x6e, 0x8e, - 0x6c, 0xd2, 0x6b, 0xd0, 0xfe, 0x3f, 0x1b, 0x0c, 0xa0, 0x33, 0x5e, 0x49, 0xe1, 0xf5, 0xe0, 0xd9, 0xc3, 0x5b, 0xa2, - 0xea, 0x28, 0x68, 0x29, 0xc3, 0x82, 0xbe, 0xa2, 0x19, 0x00, 0x7e, 0x3d, 0x1b, 0x0c, 0x48, 0x64, 0x3e, 0x23, 0xd3, - 0x67, 0x87, 0x6f, 0xa6, 0x83, 0xc1, 0x33, 0xb3, 0x4d, 0x3e, 0xb1, 0x3b, 0x4a, 0x81, 0xf5, 0x77, 0xda, 0xef, 0x7f, - 0x3a, 0x8a, 0xc9, 0x59, 0xc1, 0xe3, 0x8f, 0xd3, 0x66, 0x5b, 0x3e, 0xb9, 0xa8, 0x6a, 0xa7, 0xfd, 0xfe, 0xba, 0xdf, - 0x3f, 0x01, 0xec, 0xa2, 0x99, 0xf3, 0xf5, 0x04, 0x69, 0xcb, 0xdc, 0x51, 0x24, 0x4d, 0x72, 0x68, 0x0c, 0x6d, 0x8b, - 0x55, 0xdb, 0x66, 0x1d, 0x19, 0x58, 0x1c, 0x35, 0x2b, 0x8a, 0x6b, 0x12, 0x85, 0xbd, 0xd3, 0xed, 0xf6, 0x84, 0x31, - 0x16, 0x13, 0x90, 0x7e, 0xf8, 0xaf, 0x4f, 0xea, 0x46, 0x0c, 0xb1, 0x52, 0x89, 0xef, 0x36, 0x4b, 0x7b, 0x08, 0x44, - 0x1c, 0x36, 0xfd, 0x3b, 0x73, 0x2f, 0x17, 0xb5, 0xe3, 0x5b, 0xff, 0x00, 0x10, 0x22, 0xc9, 0x42, 0x3e, 0xc3, 0x31, - 0x28, 0x33, 0x00, 0x32, 0x8f, 0xd4, 0xcc, 0x4b, 0x00, 0x01, 0x26, 0xdb, 0xed, 0x68, 0x3c, 0x9e, 0xd0, 0x82, 0x8d, - 0xfe, 0xf1, 0xe4, 0x61, 0xf5, 0x30, 0x0c, 0x82, 0x41, 0x46, 0x5a, 0x7a, 0x0a, 0xbb, 0x58, 0xab, 0x7d, 0x30, 0x82, - 0xd7, 0xec, 0xe3, 0x55, 0xf6, 0xc5, 0xec, 0x23, 0x12, 0xd6, 0x06, 0xe3, 0xc8, 0x45, 0xda, 0xd2, 0xdb, 0xdd, 0xc1, - 0x60, 0x72, 0x91, 0x7e, 0x86, 0xed, 0xf4, 0xf9, 0x37, 0x0f, 0xc6, 0x13, 0x0e, 0x46, 0x77, 0x51, 0xd0, 0x67, 0xda, - 0x76, 0x5b, 0xf9, 0x97, 0xc0, 0xd7, 0x98, 0x0a, 0x3a, 0x36, 0xcb, 0xc2, 0x0d, 0x2a, 0xa2, 0x8e, 0x96, 0x41, 0x55, - 0x2b, 0xdb, 0x39, 0xa0, 0x96, 0x58, 0x95, 0x89, 0x5b, 0x60, 0x18, 0x32, 0xd4, 0xe5, 0x1e, 0x57, 0x7f, 0xf0, 0x42, - 0x1a, 0xf8, 0x0c, 0x27, 0x22, 0xf4, 0xb8, 0x35, 0xee, 0x73, 0x6b, 0xe2, 0x33, 0xdc, 0x5a, 0x89, 0x24, 0xd6, 0xc0, - 0x92, 0x9a, 0xcb, 0x51, 0xc2, 0x8e, 0x4a, 0xc6, 0x67, 0x65, 0x94, 0xd0, 0x18, 0x1e, 0x24, 0x13, 0x33, 0x19, 0x25, - 0x68, 0x9f, 0xe8, 0x22, 0x0c, 0xfe, 0x0d, 0x98, 0xfd, 0x34, 0x87, 0xbf, 0x92, 0x4c, 0x93, 0x43, 0x08, 0x08, 0x71, - 0x38, 0x9e, 0xc5, 0xe1, 0x98, 0x44, 0xc9, 0x11, 0x3c, 0xc1, 0x7f, 0x45, 0x38, 0x26, 0xb5, 0xbe, 0xc3, 0x48, 0x75, - 0xb9, 0x4d, 0x18, 0xc0, 0x95, 0x8d, 0x67, 0x93, 0xc8, 0x4a, 0x77, 0xe5, 0xc3, 0xd1, 0xf8, 0x09, 0x99, 0xc6, 0xa1, - 0x1c, 0x24, 0x84, 0x82, 0x77, 0x6f, 0x58, 0x0e, 0x13, 0x0d, 0xcf, 0x06, 0x6c, 0x5e, 0xe9, 0xd8, 0x3c, 0x09, 0x27, - 0x20, 0x0c, 0x13, 0x72, 0xac, 0x77, 0x20, 0xa5, 0xe8, 0xf3, 0x1c, 0xfb, 0xa9, 0x8f, 0x20, 0xcc, 0x8e, 0x5a, 0x2a, - 0xbe, 0x02, 0xa0, 0x4b, 0x1c, 0x1c, 0x6a, 0xcf, 0x7c, 0x31, 0x0b, 0x4b, 0x8f, 0x4a, 0x99, 0xea, 0xf6, 0x45, 0x83, - 0xf2, 0x9b, 0x06, 0xed, 0x0b, 0x32, 0x98, 0xd0, 0xf2, 0x68, 0xc2, 0x1f, 0x41, 0x00, 0x8f, 0x46, 0xc4, 0x2f, 0x85, - 0x13, 0x03, 0xe1, 0x55, 0x90, 0x81, 0x4a, 0x6b, 0xd5, 0x98, 0x91, 0xad, 0x78, 0x0f, 0xc2, 0xa4, 0xec, 0x5d, 0xcb, - 0x75, 0x9e, 0x42, 0x54, 0xb0, 0x75, 0x5e, 0xed, 0x5d, 0x80, 0x25, 0x7b, 0x5c, 0x41, 0x9c, 0xb0, 0xf5, 0x0a, 0xb0, - 0x73, 0x1f, 0x6c, 0xca, 0x7a, 0x4f, 0x7d, 0xb7, 0x87, 0x2d, 0x87, 0x57, 0x95, 0xdc, 0x9b, 0x8c, 0xc7, 0xe3, 0xd1, - 0x9f, 0x70, 0x74, 0x00, 0xa1, 0x25, 0x91, 0xe1, 0x93, 0x01, 0x1a, 0x77, 0x5d, 0x71, 0x6f, 0x5c, 0x28, 0xca, 0x4a, - 0x27, 0x13, 0x02, 0xe2, 0x67, 0xd3, 0x37, 0xd8, 0x57, 0x5c, 0xc7, 0x3f, 0xd9, 0xfd, 0xc4, 0xac, 0x68, 0xb5, 0x52, - 0x47, 0x6f, 0xdf, 0x9c, 0xbc, 0x7c, 0xff, 0xf2, 0x97, 0xe7, 0xa7, 0x2f, 0x5f, 0xbf, 0x78, 0xf9, 0xfa, 0xe5, 0xfb, - 0xdf, 0xef, 0x61, 0xb0, 0x7d, 0x5b, 0x11, 0x3b, 0xf6, 0xde, 0x3d, 0xc6, 0xab, 0xc5, 0x17, 0xce, 0x1e, 0xb8, 0x5b, - 0x2c, 0xc0, 0x26, 0x18, 0x6e, 0x41, 0x50, 0xcd, 0x68, 0x54, 0xfa, 0x9e, 0x80, 0x8c, 0x46, 0x85, 0x6c, 0x3c, 0xac, - 0xd8, 0x0a, 0xb9, 0x78, 0xc7, 0x70, 0xf0, 0x91, 0xfd, 0xad, 0x38, 0x13, 0x6e, 0x47, 0x5b, 0xb3, 0x22, 0xe0, 0xf3, - 0xb5, 0x16, 0x95, 0xc7, 0x85, 0xa8, 0xbd, 0x6d, 0x9f, 0x43, 0x42, 0x3d, 0x22, 0xd7, 0xc1, 0xfb, 0x36, 0xc8, 0x1e, - 0x1f, 0x79, 0x4f, 0xca, 0x33, 0xd4, 0xe7, 0x68, 0xf8, 0xa8, 0xf1, 0x8c, 0x4e, 0xcc, 0xb5, 0xd1, 0xa1, 0x9e, 0x16, - 0xb0, 0xbf, 0x95, 0x18, 0x9b, 0x16, 0xac, 0x4c, 0x11, 0xeb, 0xc3, 0xe9, 0x7e, 0x77, 0x6f, 0x46, 0x3f, 0xc3, 0xf1, - 0xa3, 0x54, 0x13, 0x48, 0x8b, 0x02, 0xa5, 0x2b, 0x43, 0x6e, 0x7b, 0x16, 0x16, 0xe6, 0x67, 0xd8, 0x20, 0x80, 0xf6, - 0xb2, 0x63, 0x49, 0xa0, 0x59, 0xbc, 0xd6, 0xf5, 0xcf, 0xcb, 0x97, 0x89, 0x76, 0xbe, 0xf8, 0x06, 0x42, 0x0c, 0xfb, - 0x57, 0x84, 0xc6, 0x84, 0xbb, 0x49, 0x76, 0x97, 0x16, 0x73, 0xaf, 0xba, 0x8c, 0xf1, 0xb8, 0xbb, 0xe3, 0x4a, 0xd1, - 0xbc, 0x75, 0x81, 0x3d, 0x50, 0xf3, 0x3a, 0x5e, 0xb2, 0x10, 0xb0, 0x19, 0xf7, 0xed, 0x22, 0x71, 0x7e, 0xef, 0x74, - 0x42, 0xf6, 0x0f, 0xa6, 0x7c, 0xc8, 0x4a, 0x2a, 0x06, 0xac, 0xac, 0x77, 0xa8, 0x39, 0x6f, 0x13, 0x72, 0xb1, 0x4b, - 0xc3, 0xc5, 0x90, 0xdf, 0x77, 0x49, 0x7a, 0xcf, 0x1b, 0x0e, 0xd5, 0xb6, 0xb9, 0x18, 0xd2, 0x94, 0xd3, 0x5d, 0x2a, - 0x03, 0x42, 0xa4, 0xcb, 0xb8, 0x22, 0xb5, 0x3e, 0xaa, 0x52, 0x27, 0xe9, 0xb8, 0xca, 0x36, 0x9f, 0xb9, 0x64, 0xab, - 0xdb, 0xb5, 0x7f, 0xad, 0x6e, 0x5f, 0x98, 0x81, 0xfc, 0xfd, 0x85, 0xa8, 0x26, 0x06, 0xa2, 0x0b, 0xa8, 0xe0, 0x5f, - 0xe0, 0xe5, 0xc9, 0x23, 0xad, 0x00, 0xbd, 0xeb, 0xec, 0xe8, 0xda, 0xe3, 0x8d, 0x59, 0x6c, 0x2d, 0x71, 0xce, 0x2a, - 0xdf, 0x59, 0x5e, 0x95, 0xad, 0xd0, 0x75, 0x04, 0xfb, 0x23, 0xec, 0xe8, 0xbb, 0xb7, 0x0d, 0x80, 0x28, 0x85, 0x95, - 0x3b, 0xfb, 0x85, 0x77, 0xf6, 0x0b, 0x7b, 0xf6, 0xdb, 0x4d, 0xa0, 0x7c, 0x58, 0xa1, 0x65, 0x2f, 0xa4, 0xa8, 0x4c, - 0x93, 0xc7, 0x4d, 0x5d, 0x16, 0xd2, 0x62, 0xbe, 0x6f, 0x69, 0xd7, 0xe3, 0x31, 0x95, 0xa8, 0x1e, 0xf9, 0x01, 0x5b, - 0xb5, 0x5f, 0x92, 0xfb, 0xef, 0x99, 0xff, 0xb3, 0x37, 0xc8, 0xbb, 0xee, 0x76, 0xff, 0x37, 0x17, 0x3a, 0xb8, 0xad, - 0xa5, 0xc2, 0x53, 0x57, 0xc7, 0x05, 0xde, 0xd5, 0xd2, 0xfb, 0xef, 0x6a, 0x6f, 0x33, 0xbd, 0xec, 0x2a, 0x40, 0x0d, - 0x12, 0xeb, 0x4b, 0x5e, 0x64, 0x49, 0x6d, 0x15, 0x1a, 0x6f, 0x38, 0x84, 0xf6, 0xf0, 0x0e, 0x2e, 0x90, 0xc3, 0x12, - 0x42, 0x3f, 0x56, 0x46, 0x00, 0xe8, 0xb3, 0xd8, 0x6f, 0x78, 0x98, 0x91, 0x81, 0x2f, 0xf1, 0x93, 0xd2, 0x17, 0x17, - 0xef, 0xef, 0x64, 0x26, 0xe8, 0x55, 0xe2, 0xa2, 0xe6, 0xca, 0x76, 0xcc, 0x0f, 0xff, 0x0b, 0x8c, 0x06, 0xe1, 0xb5, - 0x25, 0xdb, 0x17, 0x1d, 0xb3, 0x5c, 0xc1, 0x51, 0x5b, 0xba, 0x32, 0x65, 0xeb, 0xfa, 0x59, 0x0d, 0x33, 0x7d, 0xa6, - 0xbc, 0x01, 0xd9, 0x17, 0x72, 0xf7, 0x53, 0x5d, 0xb1, 0x20, 0x47, 0x93, 0xf1, 0x94, 0x88, 0xc1, 0xa0, 0x95, 0x7c, - 0x88, 0xc9, 0xc3, 0xe1, 0x0e, 0x73, 0x29, 0x74, 0x3f, 0xbc, 0x3e, 0x40, 0x7d, 0x8d, 0x2d, 0x49, 0x36, 0x15, 0xfb, - 0x1b, 0xcc, 0x62, 0x81, 0x38, 0x3a, 0xf8, 0xc5, 0xf9, 0x02, 0x40, 0x96, 0x61, 0x99, 0x69, 0x61, 0x91, 0x4c, 0x95, - 0x8f, 0x6c, 0xc1, 0xe4, 0xe1, 0x78, 0xe6, 0xf7, 0xdc, 0x31, 0x38, 0x84, 0x44, 0x13, 0x6b, 0xfc, 0xe2, 0x67, 0xc1, - 0x38, 0x0e, 0xe5, 0x91, 0x6c, 0x7c, 0x57, 0x92, 0x68, 0x6c, 0x4c, 0x95, 0xf5, 0x55, 0xa2, 0x1a, 0x26, 0xe4, 0x61, - 0x41, 0xf6, 0x0b, 0xba, 0xf4, 0xc7, 0x12, 0xd3, 0xf7, 0xe3, 0xfd, 0xc9, 0x98, 0x3c, 0x8c, 0x1f, 0x4e, 0x0c, 0xdc, - 0xb0, 0x9f, 0x23, 0x1f, 0x2e, 0xc9, 0x7e, 0xb3, 0x4a, 0x30, 0x45, 0x35, 0x3d, 0xf3, 0x2b, 0x49, 0x06, 0xcb, 0x41, - 0xfa, 0xb0, 0x95, 0x17, 0x6b, 0xd5, 0xe3, 0xbd, 0x3e, 0xe4, 0x53, 0x22, 0x1a, 0x37, 0x86, 0x35, 0xbd, 0x8c, 0xff, - 0x92, 0x45, 0x24, 0x25, 0x20, 0x12, 0x82, 0x7a, 0x3b, 0x3b, 0xcf, 0x92, 0x58, 0xa4, 0x51, 0x5a, 0x13, 0x9a, 0x1e, - 0xb1, 0xc9, 0x78, 0x96, 0xb2, 0xf4, 0x70, 0xf2, 0x64, 0x36, 0x79, 0x12, 0x1d, 0x8c, 0xa3, 0x74, 0x30, 0x80, 0xe4, - 0x83, 0x31, 0xb8, 0xd8, 0xc1, 0x6f, 0x76, 0x00, 0x43, 0x77, 0x84, 0x2c, 0x61, 0x01, 0x4d, 0xfb, 0xb2, 0x26, 0xe9, - 0xe1, 0x3c, 0x57, 0x3d, 0x89, 0x6f, 0xe8, 0xda, 0x73, 0x70, 0xf1, 0x5b, 0x78, 0xee, 0x5a, 0x78, 0xbe, 0xdb, 0x42, - 0xa1, 0xc9, 0x76, 0x2c, 0xff, 0x7f, 0xdc, 0x30, 0xee, 0xba, 0x4b, 0x98, 0xc5, 0x75, 0x95, 0x8d, 0x56, 0x85, 0xac, - 0x24, 0xdc, 0x26, 0x94, 0x28, 0x6c, 0x14, 0xaf, 0x56, 0xb9, 0x76, 0x11, 0x9b, 0x57, 0x14, 0xc0, 0x5d, 0x20, 0x4e, - 0x31, 0xb0, 0xd0, 0xc6, 0x40, 0xee, 0x13, 0x2f, 0x24, 0xb3, 0x6a, 0x1f, 0x73, 0x8f, 0xfc, 0x2b, 0x04, 0x63, 0x54, - 0x71, 0x34, 0x9e, 0x29, 0xac, 0x8b, 0xcf, 0xc9, 0x7b, 0xff, 0x8d, 0xa3, 0xc8, 0x1e, 0xcd, 0xa0, 0x27, 0x88, 0x9c, - 0x47, 0x9c, 0x3d, 0x99, 0xbc, 0x0c, 0xdc, 0xcf, 0x60, 0xa5, 0xbf, 0xee, 0x36, 0x63, 0x6d, 0x7b, 0x74, 0x2f, 0x8c, - 0x50, 0xf4, 0x13, 0xbe, 0x33, 0xf5, 0x02, 0x2e, 0xa1, 0x1a, 0xd8, 0xf5, 0xc5, 0x05, 0x2f, 0x01, 0x44, 0x28, 0x13, - 0xfd, 0x7e, 0xef, 0x2f, 0x03, 0x4d, 0x5a, 0xf2, 0xe2, 0x55, 0x26, 0xac, 0x33, 0x0e, 0x34, 0x15, 0xa8, 0xff, 0xc7, - 0xca, 0x3e, 0xd3, 0x31, 0x99, 0xf9, 0x8f, 0xc3, 0x09, 0x89, 0x9a, 0xaf, 0xc9, 0x67, 0x4e, 0xd3, 0xcf, 0x5c, 0xd1, - 0xfe, 0x03, 0x99, 0xb9, 0xe1, 0x90, 0xa1, 0xfe, 0xd2, 0x31, 0x4f, 0x46, 0xaf, 0x13, 0xb3, 0x23, 0xc1, 0xaa, 0x19, - 0x44, 0x61, 0x2f, 0xe0, 0x41, 0x5d, 0xcb, 0xe2, 0x29, 0xcc, 0x3e, 0xa8, 0x11, 0xc5, 0x21, 0x1b, 0xcf, 0x42, 0x19, - 0x4e, 0xc0, 0xbe, 0x77, 0x32, 0x86, 0xfb, 0x80, 0x0c, 0x3f, 0x56, 0x21, 0x76, 0x0e, 0xd2, 0x3e, 0x56, 0xa8, 0x98, - 0x00, 0x88, 0x40, 0xc8, 0xdb, 0xef, 0x4b, 0x95, 0x84, 0xaf, 0x4b, 0x4c, 0x29, 0xd4, 0x07, 0xff, 0x89, 0x54, 0xdd, - 0x31, 0xfd, 0x6a, 0xfd, 0xf8, 0x33, 0xa1, 0xf8, 0x74, 0x97, 0x12, 0xdf, 0x40, 0x70, 0xe7, 0x02, 0x74, 0x10, 0x15, - 0x9a, 0xb1, 0xdd, 0xcf, 0xef, 0x8a, 0xbb, 0xf9, 0x5d, 0xf1, 0xff, 0x8e, 0xdf, 0x15, 0xf7, 0x31, 0x86, 0x95, 0x85, - 0x86, 0x9f, 0x05, 0xe3, 0x20, 0xfa, 0xcf, 0xf9, 0xc4, 0x3b, 0x79, 0xea, 0xcb, 0x4c, 0x4c, 0xef, 0x60, 0x9a, 0x7d, - 0x82, 0x82, 0xb0, 0x8a, 0xbb, 0xf4, 0x64, 0x5d, 0xd9, 0x5b, 0x2b, 0x19, 0x62, 0x9e, 0x7b, 0x58, 0xa3, 0xb0, 0xf2, - 0x80, 0xee, 0x51, 0xb5, 0x41, 0x9c, 0x08, 0x1e, 0xc6, 0xcc, 0x4a, 0xdf, 0xb7, 0x5b, 0xa3, 0xc2, 0xbc, 0x97, 0x8b, - 0x82, 0xec, 0xe6, 0xe3, 0xd9, 0x38, 0x0a, 0xb1, 0x01, 0xff, 0x31, 0x63, 0xd5, 0x90, 0xcd, 0x77, 0x32, 0x52, 0x3b, - 0x26, 0x4f, 0x93, 0x5d, 0xd2, 0x3b, 0xe0, 0x1d, 0xf2, 0xf3, 0xfa, 0x63, 0x18, 0x4b, 0xc3, 0x6f, 0xc9, 0x8b, 0xb8, - 0xc8, 0xaa, 0xe5, 0x65, 0x96, 0x20, 0xd3, 0x05, 0x2f, 0xbe, 0x98, 0xe9, 0xf2, 0x3e, 0xd6, 0x07, 0x8c, 0xa7, 0x14, - 0xaf, 0x1b, 0xa2, 0xf4, 0x75, 0xcb, 0xb3, 0x42, 0x5d, 0x9e, 0x54, 0xcc, 0xf6, 0xac, 0x04, 0xa7, 0x53, 0x30, 0xc1, - 0xd7, 0x3f, 0x5d, 0xef, 0x13, 0xc0, 0x05, 0x85, 0x9a, 0xd3, 0x42, 0xae, 0x0c, 0x96, 0x93, 0x85, 0xee, 0x04, 0xcc, - 0x50, 0x29, 0xf0, 0x02, 0x05, 0x7f, 0xd1, 0xc0, 0x88, 0xbe, 0x70, 0xbf, 0xc9, 0xc0, 0x20, 0x5d, 0x9a, 0x13, 0x61, - 0xec, 0xb8, 0x9d, 0x38, 0x6d, 0x45, 0x39, 0xe3, 0xec, 0x9d, 0xba, 0x52, 0x80, 0x01, 0xde, 0xe6, 0x3a, 0x3a, 0x4d, - 0xd0, 0x6b, 0x41, 0xe9, 0xbc, 0x81, 0xbb, 0x59, 0x46, 0x46, 0xb8, 0xf8, 0xb0, 0xf2, 0x58, 0x70, 0xcf, 0x7e, 0x21, - 0xb1, 0xb6, 0x7e, 0x60, 0xcc, 0xe6, 0x05, 0x0b, 0x14, 0x2a, 0x50, 0x60, 0x39, 0xd3, 0x96, 0xa6, 0xd5, 0x90, 0xef, - 0x1f, 0xa0, 0xb5, 0x69, 0x35, 0xe0, 0xfb, 0x07, 0x75, 0x94, 0x1d, 0x42, 0x96, 0x23, 0x3f, 0x83, 0x7a, 0x5d, 0x47, - 0x26, 0xc5, 0x64, 0xf7, 0xeb, 0x4b, 0xfd, 0x51, 0xdd, 0x80, 0xeb, 0x07, 0x20, 0x80, 0x0d, 0xc0, 0x21, 0x50, 0x0d, - 0x96, 0x46, 0x04, 0x8b, 0x32, 0x85, 0xf6, 0x35, 0xf4, 0xde, 0x68, 0xf8, 0x2f, 0x70, 0x17, 0x91, 0x2b, 0xff, 0x13, - 0x04, 0xfe, 0x8a, 0x32, 0xad, 0x4c, 0xf1, 0x3f, 0xd1, 0xea, 0x15, 0xca, 0x59, 0xd3, 0x9a, 0x0f, 0xa2, 0x35, 0x11, - 0xaa, 0x19, 0x43, 0xf0, 0x6f, 0x65, 0x99, 0xb6, 0x54, 0x55, 0xea, 0x43, 0xe3, 0xb5, 0x56, 0x38, 0xcb, 0xc7, 0x91, - 0xf7, 0x1a, 0x43, 0xc7, 0x26, 0xce, 0x52, 0x4e, 0xa5, 0xce, 0x5e, 0xef, 0xcb, 0xc8, 0x01, 0x4e, 0x27, 0x6c, 0x3c, - 0x4d, 0x0e, 0xe5, 0x34, 0x71, 0x90, 0xf9, 0x39, 0xc3, 0xc8, 0xaa, 0x06, 0x84, 0x45, 0xd9, 0x50, 0xda, 0x02, 0x4c, - 0x72, 0x42, 0xc8, 0x14, 0x43, 0x51, 0xe4, 0x23, 0xdd, 0x0f, 0xeb, 0xcd, 0xea, 0xbe, 0x78, 0xab, 0x01, 0x4e, 0xc3, - 0x04, 0x02, 0x81, 0x17, 0xf1, 0x75, 0x26, 0x2e, 0xc0, 0x63, 0x78, 0x00, 0x5f, 0x82, 0x9b, 0x5c, 0xca, 0x7e, 0xab, - 0xc2, 0x1c, 0xd7, 0x16, 0x30, 0x68, 0xb0, 0x7a, 0x10, 0x1d, 0x2e, 0xa5, 0xcd, 0xae, 0x02, 0xc4, 0xc6, 0x14, 0x62, - 0x59, 0xb0, 0xb5, 0x65, 0xcf, 0x7e, 0x56, 0x4d, 0x43, 0xeb, 0x84, 0x63, 0x71, 0x91, 0x43, 0x14, 0x95, 0x41, 0x0c, - 0xee, 0x48, 0x1e, 0x9f, 0xf7, 0x40, 0x84, 0xe7, 0x04, 0xdc, 0xca, 0x12, 0x19, 0xae, 0xe8, 0x72, 0x74, 0x43, 0xd7, - 0xa3, 0x6b, 0x3a, 0xa6, 0x93, 0x7f, 0x8e, 0xd1, 0x22, 0x5b, 0xa5, 0xde, 0xd2, 0xf5, 0x68, 0x49, 0xbf, 0x1d, 0xd3, - 0x83, 0x7f, 0x8c, 0xc9, 0x34, 0xc7, 0xc3, 0x84, 0x9e, 0x83, 0x63, 0x17, 0xa9, 0xd1, 0x53, 0xd3, 0x37, 0x38, 0xac, - 0x46, 0xf9, 0x90, 0x8f, 0x72, 0xca, 0x47, 0xc5, 0xb0, 0x1a, 0x81, 0xa7, 0x63, 0x35, 0xe4, 0xa3, 0x8a, 0xf2, 0xd1, - 0xd9, 0xb0, 0x1a, 0x9d, 0x91, 0x66, 0xd3, 0x5f, 0x56, 0xfc, 0xb2, 0x64, 0x6b, 0xd8, 0x16, 0xb0, 0x7c, 0xdd, 0x2a, - 0xcb, 0x53, 0x7f, 0x55, 0x9b, 0x93, 0xd9, 0x72, 0xf6, 0xf6, 0xba, 0xcb, 0x89, 0xc5, 0xe3, 0xb6, 0xe9, 0x70, 0xf5, - 0xe5, 0x44, 0x9d, 0xf4, 0x0a, 0xf9, 0x61, 0x3c, 0x15, 0xea, 0x1c, 0x02, 0x33, 0x89, 0x59, 0x18, 0x33, 0x6c, 0xa6, - 0x4e, 0x03, 0x05, 0x4e, 0x36, 0xf2, 0x5c, 0x14, 0xb3, 0x51, 0x4e, 0xe1, 0x7d, 0x4c, 0x48, 0x24, 0xe0, 0xac, 0x3a, - 0xaa, 0x46, 0x05, 0xc4, 0x1c, 0x61, 0x21, 0x3e, 0x42, 0xbf, 0xd4, 0x47, 0x1e, 0x12, 0x78, 0x86, 0x7d, 0x2d, 0x06, - 0x31, 0x1c, 0xf1, 0xb6, 0xb2, 0x6a, 0x16, 0x26, 0x50, 0x59, 0x35, 0x2c, 0x4d, 0x65, 0x05, 0xcd, 0x46, 0x95, 0x5f, - 0x59, 0x85, 0x63, 0x94, 0x10, 0x12, 0x95, 0xba, 0x32, 0x50, 0x9f, 0x24, 0x2c, 0x2c, 0x75, 0x65, 0x67, 0xea, 0xa3, - 0x33, 0xbf, 0xb2, 0x33, 0x70, 0x21, 0x1d, 0x24, 0xfe, 0x55, 0x6a, 0x99, 0xb6, 0xaf, 0x83, 0x8d, 0x55, 0x45, 0x37, - 0xfc, 0xa6, 0x2a, 0xe2, 0xa8, 0xa4, 0x2e, 0x06, 0x34, 0x2e, 0x8c, 0x48, 0x52, 0xbd, 0x46, 0xc1, 0x1f, 0x12, 0x44, - 0xa5, 0x31, 0x78, 0x75, 0x26, 0x5d, 0x2b, 0xb5, 0xa2, 0x62, 0x50, 0x0e, 0x0a, 0xb8, 0x3f, 0xe5, 0xad, 0x85, 0xf4, - 0x33, 0x44, 0x54, 0x86, 0xf2, 0x06, 0x1f, 0x30, 0x78, 0x32, 0xbb, 0x48, 0xc3, 0x64, 0x74, 0x4b, 0xe3, 0xd1, 0x12, - 0xe1, 0x60, 0xd8, 0x79, 0xaa, 0xf0, 0xd6, 0x57, 0x90, 0x7e, 0x43, 0xe3, 0xd1, 0x35, 0x4d, 0xad, 0xcd, 0xa9, 0x81, - 0xba, 0xea, 0x8d, 0xe9, 0x4d, 0x04, 0xaf, 0x6f, 0xa3, 0x25, 0x85, 0xad, 0x74, 0x9c, 0x67, 0x17, 0x22, 0x4a, 0x29, - 0x22, 0x10, 0xae, 0x11, 0x39, 0x70, 0xa9, 0xd1, 0x06, 0xd7, 0x03, 0x28, 0x43, 0xc3, 0x05, 0x2e, 0x07, 0xf1, 0x68, - 0xe9, 0x91, 0xa9, 0x54, 0x5f, 0x64, 0x11, 0x3e, 0xda, 0xd9, 0x68, 0x29, 0x9e, 0x11, 0x0b, 0xe3, 0x0a, 0x86, 0x50, - 0x17, 0x56, 0x9a, 0x82, 0xa4, 0x0b, 0x1c, 0xd9, 0x0b, 0xe3, 0x2a, 0xdc, 0x80, 0x69, 0xd1, 0x2d, 0x98, 0x47, 0x81, - 0xc2, 0xc1, 0x25, 0x48, 0x3f, 0xa1, 0x6c, 0xe7, 0x28, 0x4d, 0x0e, 0x6f, 0x82, 0xd6, 0x3b, 0x13, 0x84, 0xb4, 0xab, - 0x9b, 0x6c, 0x49, 0xdf, 0x60, 0x7b, 0x87, 0x4e, 0x45, 0x05, 0xd5, 0xe7, 0x16, 0x4c, 0x96, 0x6c, 0x10, 0xb6, 0x84, - 0xe9, 0x99, 0x5e, 0x03, 0xf6, 0xf4, 0xfe, 0xc1, 0xce, 0x7c, 0x17, 0xb3, 0xd7, 0xfb, 0x65, 0x34, 0x56, 0x16, 0xbc, - 0xb9, 0x25, 0x76, 0x4b, 0x36, 0x9e, 0x2e, 0x0f, 0xcb, 0xe9, 0x12, 0x89, 0x9d, 0xa1, 0x5b, 0x8c, 0xcf, 0x97, 0x0b, - 0x9a, 0xe0, 0xd9, 0xc6, 0xaa, 0xf9, 0xd2, 0xa0, 0xa5, 0xa4, 0x0c, 0xd7, 0xdb, 0x12, 0xfd, 0xff, 0xd5, 0xc5, 0x2f, - 0x05, 0x78, 0x09, 0xc6, 0x02, 0x40, 0xb8, 0x07, 0xd3, 0x82, 0xd4, 0x46, 0xd9, 0x48, 0xd3, 0x30, 0xc5, 0x45, 0x60, - 0x52, 0xfa, 0xfd, 0x30, 0x67, 0x29, 0xf1, 0xa0, 0x43, 0xed, 0x28, 0x9d, 0xa7, 0xbe, 0x10, 0x04, 0x78, 0x24, 0x75, - 0x8e, 0x4d, 0xfe, 0x39, 0x9e, 0x05, 0x6a, 0x20, 0x82, 0x28, 0x3b, 0xc4, 0x47, 0x0c, 0x5c, 0x14, 0xe9, 0xb8, 0x9d, - 0xae, 0x88, 0xd5, 0xee, 0x31, 0x0b, 0x71, 0x92, 0x30, 0xd7, 0x2c, 0x1b, 0xb2, 0x2a, 0xc2, 0x04, 0x5d, 0x18, 0xd8, - 0xaf, 0x0d, 0x59, 0xb5, 0x7f, 0x00, 0x91, 0x5a, 0x6d, 0x19, 0x17, 0x5d, 0x65, 0x7c, 0x0b, 0x40, 0xd6, 0x8c, 0xb1, - 0x83, 0x7f, 0x8c, 0x67, 0xea, 0x9b, 0x28, 0xe4, 0x47, 0x07, 0xff, 0x80, 0xe4, 0xc3, 0x6f, 0x91, 0x99, 0x83, 0xe4, - 0x46, 0x41, 0x97, 0xcd, 0x59, 0xd7, 0x50, 0x9a, 0xb8, 0xf6, 0x4a, 0xbd, 0xf6, 0xa4, 0x59, 0x7b, 0x05, 0xba, 0x53, - 0x1b, 0xde, 0x43, 0xd9, 0xce, 0x82, 0x09, 0x3a, 0x9a, 0xdd, 0x81, 0x0e, 0xde, 0x29, 0x82, 0x5e, 0x26, 0xa1, 0xf1, - 0x08, 0x55, 0x46, 0xbd, 0x18, 0x0f, 0xaa, 0x93, 0x75, 0xc9, 0x3c, 0x03, 0xe6, 0xd8, 0x9e, 0x43, 0x62, 0x98, 0xab, - 0x83, 0x3a, 0x65, 0xe5, 0x30, 0xc7, 0x03, 0x78, 0xcd, 0xe4, 0x50, 0x0c, 0x72, 0x8d, 0xf2, 0x7d, 0xce, 0x8a, 0x61, - 0x39, 0xc8, 0x35, 0x37, 0x33, 0x6d, 0xc6, 0xa6, 0x4d, 0x74, 0x78, 0xe6, 0x15, 0x3b, 0x5a, 0xf5, 0x80, 0x8f, 0x05, - 0x4f, 0x66, 0xdf, 0xf3, 0xf1, 0x29, 0x70, 0x32, 0x9b, 0x9b, 0x68, 0x49, 0x6f, 0xa3, 0x94, 0x5e, 0x47, 0x6b, 0xba, - 0x8c, 0xce, 0x8d, 0x89, 0x71, 0x52, 0xc3, 0x39, 0x00, 0xad, 0x02, 0x48, 0x3c, 0xf5, 0xeb, 0x1d, 0x4f, 0xaa, 0x70, - 0x49, 0x53, 0x70, 0x1b, 0xf6, 0xed, 0x33, 0xcf, 0x7c, 0x89, 0xd4, 0x06, 0x31, 0xd6, 0xac, 0xa1, 0xe2, 0xc6, 0x5b, - 0xf7, 0x91, 0xa8, 0x61, 0xe7, 0xba, 0xd8, 0x44, 0xd5, 0x70, 0x32, 0x2d, 0x01, 0xb1, 0xb5, 0x1c, 0x0e, 0xdd, 0x11, - 0xb2, 0x7b, 0xfc, 0xe8, 0x40, 0xcf, 0x3d, 0x69, 0xb1, 0x6d, 0x5b, 0xfe, 0xc0, 0x10, 0xa6, 0xf4, 0xf3, 0x47, 0x3e, - 0x20, 0x56, 0x5c, 0xc2, 0xd9, 0x08, 0xd4, 0xd1, 0x0a, 0x9d, 0x7e, 0xab, 0xc2, 0x42, 0x1f, 0xe0, 0x9b, 0x9b, 0x28, - 0xa1, 0xb7, 0x51, 0xee, 0x91, 0xb5, 0x65, 0xcd, 0xe4, 0xf4, 0x34, 0x0b, 0x79, 0xfb, 0x40, 0x2f, 0x17, 0x00, 0xa2, - 0x35, 0x88, 0x7d, 0xa9, 0xeb, 0x01, 0x38, 0x0d, 0xa1, 0x49, 0x68, 0x04, 0x57, 0x15, 0x84, 0x11, 0x70, 0x25, 0xe1, - 0x6f, 0x30, 0x51, 0x81, 0x2f, 0xc0, 0x45, 0x26, 0x4d, 0x73, 0x1e, 0xd4, 0xfe, 0x48, 0xbe, 0x2a, 0xda, 0xde, 0xae, - 0x30, 0x9a, 0x60, 0xec, 0x89, 0xf6, 0x79, 0xa4, 0x1c, 0xc5, 0x45, 0x12, 0x66, 0xa3, 0x1b, 0x75, 0x9e, 0xd3, 0x6c, - 0x74, 0xab, 0x7f, 0x55, 0x74, 0x4c, 0x7f, 0xd1, 0x01, 0x6d, 0x94, 0xf4, 0xad, 0xe3, 0x6c, 0x40, 0xeb, 0xc5, 0xd2, - 0xf8, 0x5f, 0xcb, 0xd1, 0x0d, 0x95, 0xa3, 0x5b, 0xdf, 0x92, 0x6a, 0x32, 0x2d, 0x0e, 0x05, 0x1a, 0x52, 0x75, 0x7e, - 0x5f, 0x00, 0x3f, 0x57, 0x1a, 0xdf, 0x69, 0xf3, 0xbd, 0xd7, 0xfe, 0xd3, 0x4e, 0x9e, 0x40, 0xb1, 0x44, 0x05, 0xab, - 0x46, 0x60, 0xc7, 0xbe, 0xce, 0xe3, 0xc2, 0x8c, 0x52, 0x4c, 0xad, 0x49, 0x3f, 0x06, 0xae, 0x98, 0xf6, 0x0a, 0x70, - 0xb5, 0x04, 0x27, 0x01, 0x88, 0xa1, 0x09, 0x7b, 0x76, 0x0c, 0x51, 0xcf, 0x8d, 0x63, 0x94, 0x6c, 0xb8, 0x07, 0xc4, - 0x5a, 0xe6, 0xad, 0x5c, 0x02, 0x12, 0x78, 0xeb, 0x61, 0x52, 0x00, 0xc6, 0x60, 0xb9, 0x24, 0x3a, 0x8f, 0x87, 0x3e, - 0xa1, 0x5e, 0x68, 0xd4, 0x09, 0xd9, 0xd8, 0x12, 0x38, 0xfe, 0xb0, 0x3e, 0x04, 0x82, 0x57, 0x79, 0xae, 0xbf, 0xd2, - 0xba, 0xfe, 0x52, 0xe9, 0xb9, 0x63, 0xb9, 0xae, 0xdf, 0xb6, 0xa9, 0xd1, 0x0b, 0xb0, 0xf0, 0xdd, 0x28, 0xf3, 0x48, - 0x6e, 0x11, 0x52, 0x15, 0x58, 0xa9, 0x5b, 0x48, 0x30, 0xff, 0x4a, 0xce, 0x56, 0x65, 0xbe, 0x7a, 0xe4, 0x5e, 0x39, - 0x9b, 0x9e, 0xfe, 0x86, 0x04, 0xed, 0xb6, 0x23, 0xcd, 0xe3, 0x2d, 0x3a, 0x7c, 0x76, 0xad, 0x25, 0xe6, 0x4e, 0xa2, - 0xe2, 0xf9, 0x14, 0xb0, 0xd5, 0xb3, 0xec, 0x52, 0xf9, 0x58, 0xed, 0xe2, 0xf8, 0x99, 0xf3, 0x27, 0xa9, 0xc2, 0xb5, - 0x68, 0x28, 0x41, 0xc0, 0x9b, 0xc3, 0xd8, 0x15, 0xaa, 0x80, 0x86, 0xe6, 0x06, 0x8e, 0x73, 0x35, 0xac, 0x34, 0x01, - 0xd3, 0x52, 0x1e, 0x1d, 0xe0, 0xd0, 0xe4, 0x51, 0xbb, 0x69, 0x58, 0x19, 0xba, 0xd6, 0xe8, 0x73, 0x5b, 0xe9, 0x8c, - 0x37, 0x1b, 0xbe, 0x7f, 0x30, 0xa8, 0xf0, 0x27, 0x69, 0x8e, 0x46, 0x3b, 0x37, 0xdc, 0x69, 0x04, 0x66, 0xae, 0xe4, - 0x8a, 0xec, 0x8e, 0x92, 0x97, 0xdf, 0xd3, 0x0b, 0x0b, 0xe8, 0xcf, 0x7f, 0x2e, 0x26, 0x9c, 0xb4, 0xc4, 0x84, 0x68, - 0xe9, 0xa0, 0x45, 0x07, 0x3b, 0xca, 0x2b, 0xfb, 0x12, 0x2f, 0x9d, 0xe3, 0x7f, 0x5f, 0x8f, 0xb5, 0xab, 0x40, 0x68, - 0x75, 0x72, 0xbf, 0x3d, 0x59, 0x20, 0x6a, 0x40, 0x35, 0xbb, 0x2a, 0x47, 0x99, 0x76, 0x56, 0x64, 0xd3, 0x90, 0xb9, - 0xee, 0x66, 0x69, 0xd8, 0x4c, 0x76, 0x2c, 0x2c, 0x33, 0x0c, 0xd6, 0x4e, 0x15, 0x7d, 0x0e, 0x5a, 0x7e, 0x04, 0x2f, - 0x9b, 0xca, 0x33, 0x9f, 0xcd, 0x32, 0xe2, 0x05, 0x3a, 0xe7, 0x54, 0x2c, 0x9a, 0xd2, 0xb1, 0x72, 0xbb, 0x2d, 0xd1, - 0x58, 0xa2, 0x8c, 0x82, 0xa0, 0xb6, 0x41, 0xd8, 0x75, 0xe9, 0x9e, 0xf4, 0x69, 0x17, 0x9f, 0x56, 0xa0, 0xef, 0xf1, - 0x5d, 0x06, 0x12, 0x53, 0x4f, 0xf2, 0x50, 0x35, 0x9a, 0xa3, 0x93, 0x67, 0x49, 0xaa, 0xf1, 0xf9, 0x95, 0xec, 0xac, - 0x79, 0xb7, 0x1a, 0x53, 0xfc, 0x47, 0xea, 0xf6, 0x9d, 0xcb, 0xd0, 0x44, 0x7f, 0x2d, 0x0f, 0x5a, 0x0a, 0x0b, 0x8e, - 0xdb, 0xc6, 0x5f, 0xbf, 0xcd, 0x1c, 0x62, 0x58, 0xba, 0x1c, 0xde, 0x84, 0x0e, 0xdd, 0x5d, 0x65, 0x67, 0xae, 0x0f, - 0xa8, 0x53, 0x17, 0xeb, 0x36, 0xa0, 0x64, 0xc9, 0xbb, 0x75, 0x7a, 0x62, 0xa5, 0x5f, 0xf6, 0xc3, 0x9d, 0x79, 0xd4, - 0xec, 0xee, 0x76, 0x3b, 0x21, 0x6d, 0xfb, 0x60, 0xbc, 0x2f, 0x61, 0x21, 0xce, 0x3b, 0x6c, 0xef, 0xe7, 0xb0, 0x7a, - 0xc8, 0x07, 0x7f, 0xe0, 0x38, 0xc3, 0xe8, 0x67, 0xca, 0xd0, 0xe7, 0x45, 0x21, 0x2f, 0x55, 0xa7, 0x7c, 0xa1, 0x5b, - 0xcb, 0xd4, 0xfb, 0x75, 0xfc, 0xba, 0x15, 0x20, 0xc6, 0xeb, 0x8a, 0x95, 0xe2, 0x0d, 0xad, 0x30, 0xae, 0x81, 0xdb, - 0xe4, 0x50, 0x4b, 0xb5, 0x40, 0xd4, 0xe5, 0x27, 0x0f, 0x79, 0x64, 0xd4, 0x99, 0xf0, 0xdd, 0x43, 0xee, 0x4b, 0xd7, - 0x76, 0x9b, 0xf8, 0xb9, 0xa6, 0xed, 0xef, 0x0e, 0x74, 0x47, 0xeb, 0xee, 0x6f, 0x9e, 0xcd, 0xcf, 0x23, 0xf3, 0xc5, - 0x00, 0x9b, 0xb5, 0xcb, 0xb8, 0xec, 0x18, 0xee, 0x7b, 0xd3, 0x83, 0xb1, 0x80, 0x40, 0x62, 0x86, 0x5e, 0x06, 0x2e, - 0x70, 0x81, 0xbb, 0xc2, 0x80, 0x21, 0xae, 0x69, 0xc9, 0xad, 0xb6, 0xb2, 0xf5, 0x91, 0xb7, 0x51, 0x21, 0x58, 0xd7, - 0x1d, 0x37, 0x49, 0x0e, 0xc1, 0x09, 0x5b, 0xee, 0x7d, 0xed, 0xb5, 0x33, 0xfc, 0x30, 0x10, 0xce, 0x2d, 0xd1, 0x33, - 0x6a, 0x7b, 0xa8, 0xd5, 0xbd, 0x86, 0x57, 0xb9, 0x8d, 0x3c, 0xeb, 0x37, 0xf3, 0xd2, 0xb0, 0x2f, 0x78, 0x2d, 0x05, - 0x87, 0xc6, 0x76, 0x2b, 0xdc, 0x62, 0xf1, 0x8e, 0x56, 0x2b, 0x6b, 0x6d, 0xb5, 0xd7, 0x4a, 0x45, 0xef, 0x5e, 0x73, - 0x9c, 0x38, 0x4b, 0x61, 0xfb, 0xe1, 0xfd, 0x05, 0xbb, 0x26, 0x80, 0x41, 0x8b, 0xc9, 0x02, 0x25, 0xa8, 0x64, 0xad, - 0x6a, 0xb7, 0x53, 0xe2, 0x97, 0xfb, 0x45, 0x97, 0xd9, 0xce, 0xe3, 0xd7, 0x4d, 0xda, 0x67, 0x3e, 0x47, 0x3f, 0xcc, - 0xef, 0xac, 0x93, 0x92, 0x33, 0x8c, 0x6b, 0xf9, 0xff, 0x55, 0xf4, 0xa2, 0xc8, 0xd2, 0x68, 0x63, 0x78, 0x30, 0x1b, - 0x6a, 0xd3, 0x87, 0xc6, 0xa8, 0xdc, 0xb2, 0x51, 0x44, 0xb4, 0xba, 0x01, 0xc1, 0x8c, 0xe2, 0xbe, 0x44, 0x9b, 0x57, - 0xaa, 0x2c, 0xbc, 0xc3, 0x67, 0x36, 0x7a, 0xc3, 0xf6, 0x84, 0x50, 0xbe, 0x7b, 0x5a, 0x98, 0x55, 0x4b, 0x45, 0x83, - 0xed, 0x12, 0xde, 0xc5, 0xa8, 0xd2, 0x4f, 0x98, 0x6c, 0x59, 0x30, 0xd5, 0xff, 0xef, 0x8b, 0x2c, 0x6d, 0x53, 0x74, - 0x60, 0x3a, 0x9b, 0x3e, 0x9d, 0x74, 0x83, 0xeb, 0x0c, 0x58, 0x44, 0xb0, 0xa5, 0xc2, 0xf1, 0x28, 0xb5, 0x1b, 0x24, - 0x4c, 0x04, 0x37, 0x51, 0x2f, 0x3b, 0x5a, 0xa6, 0x64, 0x55, 0xc0, 0xf3, 0x2b, 0x57, 0x99, 0x8e, 0xa3, 0xa1, 0xdf, - 0x3f, 0x4b, 0x4d, 0xe8, 0x57, 0xea, 0xa5, 0x2a, 0xce, 0xc3, 0xa8, 0x3a, 0x54, 0x18, 0xa3, 0x25, 0x4d, 0xe1, 0x18, - 0xcc, 0xce, 0xc3, 0x14, 0x2f, 0x67, 0x9b, 0x84, 0x7d, 0xc1, 0x40, 0x2e, 0xb5, 0x41, 0xbd, 0xa6, 0x44, 0x6b, 0xd6, - 0xde, 0xcc, 0x29, 0xa1, 0xe7, 0xac, 0xf4, 0xef, 0x42, 0x6b, 0x10, 0x28, 0xca, 0x66, 0xca, 0xf4, 0x54, 0xb7, 0xf3, - 0x9c, 0x26, 0xb4, 0xa0, 0x2b, 0x52, 0x83, 0xbe, 0xd7, 0xc9, 0xd9, 0xd1, 0xc9, 0xce, 0xcc, 0x7a, 0xcc, 0x8a, 0xe1, - 0x64, 0x1a, 0xc3, 0x35, 0x2d, 0x76, 0xd7, 0xb4, 0x65, 0xf3, 0xc6, 0xd5, 0xd8, 0x38, 0x0d, 0xda, 0x05, 0xd2, 0x36, - 0xcd, 0xed, 0xa7, 0x1e, 0xb7, 0xbf, 0xae, 0xd9, 0x72, 0xda, 0x5b, 0x6f, 0xb7, 0xbd, 0x14, 0x6c, 0x44, 0x3d, 0x3e, - 0x7e, 0xad, 0xa4, 0xeb, 0x96, 0xcb, 0x4f, 0xe1, 0xd9, 0xe3, 0xeb, 0x97, 0x3e, 0xb8, 0x1c, 0xad, 0xda, 0xdc, 0xfd, - 0x72, 0x17, 0x59, 0xee, 0x8b, 0x86, 0x96, 0xeb, 0x19, 0x6a, 0x92, 0x67, 0xa3, 0xbd, 0x43, 0x2d, 0x58, 0xce, 0xba, - 0x09, 0x4f, 0x0c, 0x76, 0xec, 0x55, 0x63, 0x73, 0x54, 0xe6, 0x92, 0xd5, 0x20, 0x81, 0x3e, 0xc9, 0x33, 0x4d, 0x7f, - 0x2f, 0xc3, 0x7c, 0x74, 0x43, 0x73, 0xc0, 0x15, 0xab, 0xec, 0x25, 0x83, 0xd4, 0x55, 0x7b, 0x89, 0x2b, 0x5f, 0xe1, - 0x90, 0x6c, 0xf0, 0xc9, 0x30, 0x55, 0x9f, 0x5d, 0xf2, 0xe0, 0xff, 0x6d, 0xd5, 0x2a, 0x3d, 0x37, 0xc9, 0x0d, 0xc7, - 0xbf, 0x4e, 0xda, 0x3e, 0x26, 0x06, 0x09, 0x78, 0x6a, 0x17, 0x43, 0x35, 0xaa, 0x8a, 0x58, 0x94, 0xb9, 0x89, 0x39, - 0x76, 0x67, 0xd7, 0xd0, 0x41, 0x19, 0xfc, 0xba, 0xe1, 0x13, 0x73, 0x07, 0xb6, 0x02, 0x1d, 0x9d, 0x68, 0x2e, 0xc3, - 0xcc, 0x5c, 0x86, 0x69, 0xd7, 0x56, 0x81, 0xe1, 0x55, 0x5b, 0x25, 0x51, 0xae, 0x46, 0x3d, 0x6e, 0x66, 0xa9, 0xd9, - 0x8b, 0xbc, 0x7b, 0x4d, 0x7a, 0x12, 0x7f, 0xba, 0xf4, 0xe4, 0xf5, 0x30, 0x20, 0xf2, 0x4b, 0x96, 0x86, 0x6b, 0x14, - 0x04, 0xa7, 0x56, 0x3b, 0x90, 0xe6, 0x23, 0x40, 0xe6, 0xc7, 0x69, 0xf8, 0x4e, 0x8b, 0x73, 0xc8, 0x46, 0x69, 0x9c, - 0xd8, 0xd2, 0xa8, 0x87, 0xe0, 0xce, 0x7b, 0xc9, 0x63, 0x08, 0x7c, 0xf8, 0x1e, 0x37, 0x83, 0x8a, 0x6e, 0x4b, 0x4c, - 0x94, 0x36, 0x8f, 0xba, 0xe5, 0xa3, 0x86, 0x50, 0xc9, 0xca, 0xf0, 0x12, 0x68, 0xef, 0x8e, 0xc0, 0xa8, 0x72, 0x02, - 0x99, 0x61, 0xb1, 0x7f, 0x30, 0x4c, 0x95, 0xa0, 0x68, 0x28, 0x87, 0x4b, 0x94, 0x03, 0x62, 0x12, 0x08, 0x8c, 0x8a, - 0x41, 0xaa, 0x2b, 0x53, 0x2f, 0x06, 0xa9, 0xbe, 0x55, 0x91, 0xfa, 0x34, 0x0b, 0x2b, 0xaa, 0x5b, 0x44, 0xc7, 0x74, - 0x28, 0xe9, 0xd2, 0xec, 0xd4, 0x5c, 0x4b, 0x2f, 0xd4, 0x72, 0x7c, 0xaa, 0xd3, 0x60, 0x14, 0x4f, 0x5c, 0x8a, 0x7e, - 0xab, 0xf6, 0xb3, 0xff, 0x16, 0x53, 0x6a, 0xc4, 0xa6, 0xf6, 0x16, 0x31, 0xac, 0xda, 0xf7, 0x59, 0x95, 0x83, 0x76, - 0x17, 0x94, 0x8d, 0x95, 0x71, 0x9e, 0x6f, 0x04, 0x33, 0x07, 0x6d, 0x63, 0xd5, 0xf4, 0xa1, 0x37, 0x62, 0xd4, 0xde, - 0x98, 0x6a, 0xdc, 0x13, 0xf8, 0x69, 0x83, 0xa6, 0x7b, 0x91, 0xe7, 0xa8, 0x47, 0xde, 0xfd, 0xcf, 0x1c, 0xd9, 0x99, - 0x7c, 0x16, 0xcb, 0xa4, 0x6e, 0x1f, 0x93, 0x60, 0xa1, 0xea, 0x18, 0x5d, 0xb8, 0x91, 0x29, 0xed, 0xe7, 0xce, 0xf4, - 0x23, 0x9e, 0xc9, 0xfd, 0x76, 0x68, 0xd4, 0x97, 0x86, 0xb5, 0xa4, 0x88, 0xfa, 0x82, 0xde, 0x9a, 0xea, 0xe8, 0x80, - 0x7a, 0x1d, 0x81, 0xd5, 0x15, 0x6d, 0x50, 0x03, 0x30, 0x19, 0xd7, 0xb6, 0x36, 0x9f, 0x83, 0xa9, 0xad, 0xaa, 0xe0, - 0x09, 0xdd, 0x15, 0x4a, 0xf7, 0x26, 0x75, 0xdd, 0x1a, 0x62, 0x0b, 0x18, 0x10, 0xb8, 0xd1, 0x53, 0xd3, 0x1f, 0x34, - 0x51, 0x01, 0x68, 0xd0, 0xb8, 0x9d, 0xe9, 0x1c, 0x89, 0x7e, 0xa7, 0x36, 0x6d, 0x33, 0xd5, 0xab, 0xca, 0x07, 0x50, - 0xf1, 0x67, 0xe9, 0xf4, 0xdc, 0x8c, 0x58, 0x00, 0xe3, 0x1e, 0x38, 0x53, 0xbd, 0xe3, 0x0c, 0xac, 0x27, 0xf2, 0x3c, - 0x2b, 0x79, 0x22, 0x05, 0xcc, 0x88, 0xbc, 0xbc, 0x94, 0x02, 0x86, 0x41, 0x0d, 0x00, 0x5a, 0x34, 0x97, 0xd1, 0x84, - 0x3f, 0xaa, 0xe9, 0x5d, 0x79, 0xf8, 0x23, 0x9d, 0xeb, 0x9b, 0x71, 0x0d, 0x86, 0xca, 0xeb, 0x8a, 0xef, 0x64, 0xfa, - 0x86, 0x3f, 0xf6, 0x32, 0x2d, 0xe5, 0xba, 0xd8, 0xc9, 0xf2, 0xe8, 0x1b, 0xfe, 0x44, 0xe7, 0x39, 0x78, 0x5c, 0xd3, - 0x34, 0xbe, 0xdd, 0xc9, 0xf2, 0xcf, 0x6f, 0x1e, 0xdb, 0x3c, 0x8f, 0xc6, 0x35, 0xbd, 0xe6, 0xfc, 0xa3, 0xcb, 0x34, - 0xd1, 0x55, 0x8d, 0x1f, 0xff, 0xd3, 0xe6, 0x7a, 0x5c, 0xd3, 0x4b, 0x29, 0xaa, 0xe5, 0x4e, 0x51, 0x07, 0xdf, 0x1c, - 0xfc, 0x93, 0x7f, 0x63, 0xba, 0x77, 0x50, 0xd3, 0xbf, 0xd7, 0x71, 0x51, 0xf1, 0x62, 0xa7, 0xb8, 0x7f, 0xfc, 0xf3, - 0x9f, 0x8f, 0x6d, 0xc6, 0xc7, 0x35, 0xbd, 0xe5, 0x71, 0x47, 0xdb, 0x27, 0x4f, 0x1e, 0xf3, 0x7f, 0xd4, 0x35, 0xfd, - 0x95, 0xf9, 0xc1, 0x51, 0x8f, 0x33, 0x4f, 0x0f, 0x9f, 0xcb, 0x26, 0x6a, 0xc0, 0xd0, 0x43, 0x03, 0x58, 0x4a, 0xab, - 0xa6, 0xb9, 0xc3, 0x2b, 0x17, 0xdc, 0xbe, 0x4f, 0xe3, 0x34, 0x5e, 0xc1, 0x41, 0xb0, 0x41, 0xe3, 0xac, 0x02, 0x38, - 0x55, 0xe0, 0x3d, 0xa3, 0x92, 0x66, 0xa5, 0xfc, 0x95, 0xf3, 0x8f, 0x30, 0x68, 0x08, 0x69, 0xa3, 0x22, 0x03, 0xbd, - 0x59, 0xe9, 0xc8, 0x46, 0xe8, 0xbf, 0xd9, 0x8c, 0x83, 0xe3, 0xc3, 0xe8, 0xf5, 0xfb, 0x61, 0xc1, 0x44, 0x58, 0x10, - 0x42, 0xff, 0x0a, 0x0b, 0x70, 0x28, 0x29, 0x98, 0x97, 0xcf, 0xf8, 0x9e, 0x6b, 0xa3, 0xb0, 0x10, 0x44, 0x77, 0x91, - 0x7d, 0x40, 0xd5, 0xa3, 0xef, 0xd0, 0x0d, 0xf1, 0xb2, 0xc2, 0x82, 0xa1, 0x55, 0x0d, 0xcc, 0x10, 0x14, 0xff, 0x8a, - 0x87, 0x12, 0x7c, 0xe2, 0x01, 0x3e, 0x7a, 0x4c, 0x66, 0x5c, 0x5d, 0x6b, 0xdf, 0x9c, 0x87, 0x05, 0x0d, 0x74, 0xdb, - 0x21, 0xe8, 0x40, 0xe4, 0xbf, 0x00, 0x4f, 0x81, 0x81, 0x0f, 0x0b, 0xbb, 0xee, 0xc0, 0xf3, 0xf9, 0xd5, 0xb0, 0x8e, - 0x2e, 0xfc, 0xe8, 0xaf, 0xd6, 0x85, 0x3d, 0x23, 0x53, 0x79, 0x58, 0x0e, 0x27, 0xd3, 0xc1, 0x40, 0xba, 0x38, 0x6e, - 0xc7, 0xd9, 0xfc, 0xd7, 0xb9, 0x5c, 0x2c, 0x50, 0xf7, 0x8d, 0xf3, 0x3a, 0xd3, 0x7f, 0x23, 0xed, 0x7c, 0xf0, 0xea, - 0xf8, 0xb7, 0xd3, 0x93, 0xe3, 0x17, 0xe0, 0x7c, 0xf0, 0xfe, 0xf9, 0xf7, 0xcf, 0xdf, 0xa9, 0xe0, 0xee, 0x6a, 0xce, - 0xfb, 0x7d, 0x27, 0xf5, 0x09, 0xf9, 0xb0, 0x22, 0xfb, 0x61, 0xfc, 0xb0, 0x50, 0x46, 0x0f, 0xe4, 0x90, 0x59, 0x28, - 0x64, 0xa8, 0xa2, 0xb6, 0xbf, 0xcb, 0xe1, 0xc4, 0x03, 0xb3, 0xb8, 0x69, 0x88, 0x70, 0xfd, 0x96, 0xdb, 0x20, 0x6b, - 0xf2, 0xc8, 0xeb, 0x07, 0x27, 0x53, 0xe9, 0xd8, 0xc2, 0x82, 0x41, 0xd9, 0xd0, 0xa6, 0xe3, 0x6c, 0x5e, 0x2c, 0x6c, - 0xbb, 0xdc, 0x02, 0x19, 0xa5, 0xd9, 0xf9, 0x79, 0xa8, 0xa0, 0xab, 0x8f, 0x40, 0x03, 0x60, 0x1a, 0x55, 0xb8, 0x16, - 0xf1, 0x99, 0x5f, 0x7e, 0x34, 0xf6, 0x9a, 0x77, 0x85, 0xba, 0x27, 0xd3, 0xac, 0xaa, 0x31, 0xa0, 0x83, 0x09, 0xe5, - 0x6e, 0xd0, 0x4d, 0x30, 0x19, 0xd5, 0x96, 0x5f, 0xe7, 0xd5, 0xc2, 0x34, 0xc7, 0x0d, 0x43, 0xe5, 0x95, 0x7c, 0x2e, - 0x1b, 0x88, 0x0c, 0x24, 0xc3, 0xb0, 0x47, 0x63, 0x14, 0xa9, 0xef, 0xed, 0x7a, 0xc7, 0x6f, 0x72, 0x09, 0xd1, 0x14, - 0x33, 0x90, 0xce, 0x1f, 0x0b, 0xe5, 0x5c, 0x2e, 0x19, 0x9f, 0x8b, 0xc5, 0x11, 0xb8, 0x9d, 0xcf, 0xc5, 0x22, 0xc2, - 0xa0, 0x7c, 0x19, 0xc4, 0x2a, 0x01, 0xbb, 0x17, 0x07, 0xe1, 0xdb, 0x09, 0x6d, 0x60, 0x37, 0x90, 0x64, 0x83, 0xd2, - 0xae, 0x34, 0x44, 0xb9, 0x53, 0x1e, 0x6d, 0x10, 0x79, 0x88, 0x55, 0xf3, 0xaa, 0xed, 0xc9, 0x66, 0x2e, 0x26, 0xb8, - 0xca, 0x62, 0x26, 0xa7, 0xf1, 0x21, 0x2b, 0xa6, 0x31, 0x94, 0x12, 0xa7, 0x69, 0x18, 0xd3, 0x09, 0x15, 0x84, 0x24, - 0x8c, 0xcf, 0xe3, 0x05, 0x4d, 0x50, 0x4a, 0x10, 0x42, 0xc8, 0x8f, 0x11, 0xda, 0xe6, 0xc0, 0x92, 0xb7, 0xdb, 0xcf, - 0xd3, 0xcf, 0xed, 0x18, 0x2e, 0xa3, 0x22, 0x74, 0x83, 0xce, 0x1a, 0xfe, 0x8d, 0xa8, 0xa0, 0x31, 0x56, 0x0c, 0x41, - 0xc0, 0x0b, 0x8c, 0x4a, 0x58, 0x90, 0x98, 0x55, 0x10, 0x45, 0xa0, 0x9c, 0xc7, 0x0b, 0x56, 0xd0, 0xa6, 0xcd, 0x69, - 0xac, 0x4d, 0x82, 0x7a, 0x0e, 0x4b, 0x6d, 0x4f, 0x2a, 0x15, 0x62, 0x8f, 0xcf, 0x44, 0x74, 0xad, 0x0d, 0x0d, 0x00, - 0x05, 0x4a, 0xc9, 0xc5, 0xaf, 0xbf, 0xdc, 0xc3, 0x4d, 0x41, 0xff, 0xb3, 0x8d, 0x89, 0x76, 0x96, 0xab, 0x43, 0x6f, - 0xbe, 0xa0, 0x71, 0x9e, 0x43, 0x28, 0x36, 0x83, 0x40, 0x2e, 0xb2, 0x0a, 0x22, 0x5a, 0xdc, 0x06, 0x26, 0x24, 0x1c, - 0xb4, 0xe9, 0x03, 0xa4, 0x36, 0xc4, 0xe4, 0xca, 0x13, 0x03, 0xbb, 0xad, 0x12, 0x04, 0x1c, 0xe9, 0x79, 0xf6, 0xa9, - 0x89, 0xb1, 0xa6, 0xa9, 0x99, 0x89, 0xb7, 0xa1, 0x10, 0x0d, 0x5a, 0x10, 0xcd, 0xe0, 0xfd, 0x73, 0xc9, 0xf1, 0xaa, - 0x03, 0x3f, 0xe0, 0x9d, 0x8b, 0x33, 0xaf, 0x66, 0x1e, 0x91, 0x53, 0x8f, 0x73, 0x44, 0xbf, 0xe4, 0x61, 0x35, 0xd2, - 0xc9, 0x18, 0x2b, 0x89, 0x83, 0xde, 0x06, 0x0b, 0xe6, 0x84, 0xae, 0x78, 0x68, 0xf9, 0xf8, 0x17, 0xc8, 0x64, 0x94, - 0xd4, 0x58, 0xd1, 0x95, 0x16, 0x23, 0xce, 0x6b, 0x98, 0xa5, 0xc9, 0x8a, 0x2e, 0x16, 0x9a, 0x34, 0x0b, 0x65, 0x1a, - 0xe0, 0x13, 0x68, 0x31, 0x72, 0x0f, 0x35, 0x6d, 0x20, 0x34, 0xec, 0x0e, 0x01, 0x1f, 0xb9, 0x87, 0x0e, 0xff, 0x3f, - 0xcf, 0x2e, 0x10, 0x69, 0xef, 0xd2, 0x44, 0xc6, 0x23, 0x75, 0x03, 0x07, 0xc5, 0xf8, 0xd8, 0x37, 0x13, 0xbf, 0x70, - 0x46, 0xef, 0x93, 0xca, 0x77, 0xf8, 0x60, 0xf9, 0xe3, 0x4d, 0xcd, 0xac, 0x8c, 0x60, 0x3d, 0x6c, 0xb7, 0xb8, 0x20, - 0xda, 0x2e, 0x80, 0xd4, 0x33, 0x5e, 0x2d, 0x7c, 0xe3, 0xd5, 0xf8, 0x0e, 0xe3, 0x55, 0x67, 0x85, 0x15, 0xe6, 0x64, - 0x83, 0xfa, 0x2c, 0x25, 0xcf, 0xcf, 0x51, 0x26, 0xd8, 0x74, 0x39, 0x2b, 0xa9, 0x4a, 0x25, 0xb4, 0x17, 0xfb, 0x19, - 0xe3, 0x1b, 0x82, 0x71, 0x56, 0x1c, 0x46, 0x02, 0x55, 0xa9, 0xa4, 0x0e, 0x7b, 0x05, 0xa8, 0xc7, 0xe0, 0xbd, 0xc1, - 0x10, 0x35, 0x32, 0x76, 0xd3, 0x06, 0x42, 0x43, 0x63, 0x3d, 0xda, 0xb3, 0xd6, 0xa3, 0xdb, 0x6d, 0x65, 0xfc, 0xed, - 0xe4, 0xba, 0x48, 0x10, 0x55, 0x58, 0x8d, 0x26, 0xc0, 0x9b, 0x26, 0xf6, 0xb6, 0xe4, 0x94, 0x16, 0x18, 0x3e, 0xfb, - 0xaf, 0xb0, 0x74, 0x2a, 0x89, 0x92, 0xcc, 0xca, 0x68, 0xe0, 0xce, 0xc1, 0x67, 0x71, 0x05, 0x6b, 0x00, 0x22, 0x39, - 0xa2, 0x87, 0xeb, 0x5f, 0xa1, 0x74, 0x99, 0x25, 0x99, 0x49, 0xc8, 0xcc, 0x45, 0xda, 0xce, 0x3a, 0x98, 0x38, 0x93, - 0x5a, 0x6f, 0x2c, 0xe4, 0xd0, 0x20, 0x3f, 0x80, 0x32, 0xc4, 0xe1, 0x93, 0x0f, 0x26, 0x54, 0xaa, 0x50, 0xaa, 0x8d, - 0x6e, 0x76, 0x03, 0xaf, 0xbc, 0xcf, 0x2e, 0x79, 0x59, 0xc5, 0x97, 0x2b, 0x63, 0x49, 0xcc, 0xd9, 0x5d, 0x6e, 0x7b, - 0x54, 0x98, 0x57, 0xaf, 0x9f, 0x7f, 0x7f, 0xdc, 0x78, 0xb5, 0x8b, 0x38, 0x1a, 0x82, 0x6d, 0xc5, 0x18, 0xa3, 0xb7, - 0xf8, 0x34, 0x98, 0x28, 0xd7, 0x08, 0xf4, 0x2e, 0x05, 0xfd, 0xf6, 0x97, 0x7a, 0x02, 0x5e, 0x72, 0xbd, 0xfc, 0x92, - 0x8f, 0x80, 0x25, 0x2a, 0xf4, 0xac, 0x30, 0x37, 0x2b, 0xb3, 0x3b, 0xbb, 0x15, 0x99, 0x69, 0x57, 0x1a, 0x19, 0x88, - 0x57, 0xdb, 0x61, 0x2c, 0x5c, 0xba, 0xa6, 0xdb, 0xc1, 0xae, 0x96, 0x9e, 0x25, 0xf2, 0x76, 0x5b, 0x42, 0x87, 0xec, - 0x80, 0x7b, 0x2f, 0xe3, 0x1b, 0x78, 0x59, 0x7a, 0xdd, 0x6c, 0x06, 0x4f, 0x00, 0x33, 0xe1, 0xc2, 0x59, 0x16, 0xc7, - 0x2c, 0x4b, 0x42, 0x15, 0x9b, 0xab, 0x21, 0xf2, 0x56, 0x84, 0xd6, 0xec, 0xaf, 0x50, 0x8c, 0xc0, 0xee, 0xe4, 0xe4, - 0x63, 0xb6, 0x9a, 0xad, 0x01, 0x35, 0xff, 0x32, 0x13, 0x40, 0x73, 0xed, 0x5a, 0xb0, 0x4d, 0xa1, 0xcd, 0x75, 0xfd, - 0x34, 0x5e, 0xc5, 0x09, 0xa8, 0x6e, 0xc0, 0x5b, 0xe4, 0x46, 0x8b, 0xae, 0x0c, 0xba, 0x28, 0xbd, 0xa7, 0x1c, 0x4b, - 0x0a, 0x1d, 0x7d, 0xef, 0x09, 0x75, 0xee, 0x19, 0xc0, 0x25, 0x8d, 0x9a, 0xa7, 0x5a, 0xca, 0x58, 0x00, 0x2c, 0x74, - 0x30, 0x53, 0x64, 0x2b, 0xba, 0x32, 0x98, 0x14, 0xf0, 0xd6, 0x00, 0x7f, 0x88, 0xac, 0x52, 0x77, 0xc5, 0x32, 0x2c, - 0x3d, 0xfb, 0xeb, 0x7e, 0x3f, 0xf6, 0xec, 0xaf, 0x57, 0x9a, 0xd6, 0xc5, 0xed, 0x06, 0x90, 0x1a, 0x03, 0x88, 0x1c, - 0xeb, 0x81, 0x30, 0x11, 0xc5, 0x9a, 0xbe, 0x7f, 0xc7, 0x26, 0x8b, 0x02, 0xa1, 0xdf, 0xa9, 0xd7, 0x93, 0x92, 0x80, - 0x4e, 0xad, 0x62, 0x47, 0x03, 0x6d, 0xf6, 0x01, 0x01, 0x51, 0xfd, 0x8c, 0x6c, 0xbe, 0x50, 0xce, 0xc5, 0x2a, 0x7c, - 0xf8, 0x98, 0x42, 0x40, 0xe1, 0x8e, 0x1a, 0x9d, 0xb7, 0x21, 0x12, 0x28, 0x2b, 0x14, 0xb1, 0xe6, 0xc5, 0x5a, 0x12, - 0x32, 0x1f, 0x2f, 0x50, 0x70, 0xe5, 0x80, 0x5d, 0x39, 0x9b, 0x0c, 0xcb, 0x88, 0xb3, 0xf0, 0xee, 0x6f, 0x26, 0x0b, - 0x82, 0x9a, 0x2b, 0x3f, 0x90, 0xe3, 0x4e, 0xa6, 0xc6, 0x9e, 0x6a, 0xd4, 0x20, 0x98, 0x8c, 0x20, 0x30, 0xdc, 0xf0, - 0x0b, 0x3e, 0x3e, 0x58, 0x10, 0x50, 0x91, 0x59, 0xb3, 0x10, 0xf3, 0xe2, 0xf0, 0x11, 0xa0, 0xc6, 0x8c, 0x0e, 0x9e, - 0x4c, 0x39, 0x83, 0x43, 0x94, 0x8e, 0x41, 0x46, 0x2b, 0xe0, 0xb7, 0x50, 0xbf, 0x5b, 0x27, 0xbe, 0x0f, 0xfd, 0x2a, - 0xe8, 0x79, 0x0c, 0x0c, 0x47, 0x34, 0xd9, 0x0f, 0xf9, 0x60, 0x32, 0x00, 0x6d, 0x89, 0xb7, 0xfb, 0x5a, 0x5a, 0x71, - 0x73, 0xba, 0x74, 0xba, 0x7f, 0xd2, 0x26, 0x48, 0x22, 0x95, 0xac, 0x54, 0xc4, 0x00, 0x42, 0x59, 0xaa, 0x6d, 0xb2, - 0x06, 0xcb, 0x0a, 0xb3, 0xa4, 0xb9, 0x41, 0x49, 0xdc, 0xdd, 0x0c, 0x1c, 0xa3, 0x66, 0x1d, 0x87, 0x65, 0xcb, 0x8d, - 0x1a, 0xe0, 0x73, 0x12, 0x56, 0xd8, 0x1b, 0xce, 0x4c, 0x7a, 0x67, 0x3a, 0x5c, 0x1d, 0x73, 0xf6, 0x8a, 0x23, 0x18, - 0x47, 0x82, 0x37, 0x1e, 0xba, 0x64, 0x1a, 0x2a, 0x32, 0x65, 0x1c, 0x4c, 0x7b, 0x80, 0x7b, 0xcf, 0xc1, 0x38, 0x8c, - 0x0d, 0x2a, 0x4b, 0xea, 0x53, 0xef, 0x2e, 0x04, 0x82, 0xb4, 0xd6, 0xcb, 0x7c, 0x86, 0xa7, 0x67, 0x84, 0xb2, 0x3f, - 0xe4, 0xf0, 0x05, 0xd8, 0x51, 0x90, 0xa3, 0x09, 0x7f, 0xf2, 0x70, 0x37, 0x50, 0x15, 0x1f, 0x04, 0x7b, 0xb1, 0x48, - 0xf7, 0x82, 0x81, 0x80, 0x5f, 0x05, 0xdf, 0xab, 0xa4, 0xdc, 0x3b, 0x8f, 0x8b, 0xbd, 0x78, 0x15, 0x17, 0xd5, 0xde, - 0x75, 0x56, 0x2d, 0xf7, 0x4c, 0x87, 0x00, 0x9a, 0x37, 0x18, 0xc4, 0x83, 0x60, 0x2f, 0x18, 0x14, 0x66, 0x6a, 0x57, - 0xac, 0x6c, 0x1c, 0x67, 0x26, 0x44, 0x59, 0xd0, 0x0c, 0x10, 0xd6, 0x38, 0x0d, 0x80, 0x4f, 0x5d, 0xb3, 0x94, 0x9e, - 0x63, 0xb8, 0x01, 0x31, 0x5d, 0x43, 0x1f, 0x80, 0x47, 0x5e, 0xd3, 0x18, 0x96, 0xc0, 0xf9, 0x60, 0x40, 0xce, 0x21, - 0x72, 0xc1, 0x9a, 0xda, 0x20, 0x0e, 0xe1, 0x5a, 0xd9, 0x69, 0xef, 0x02, 0x33, 0x6d, 0xb7, 0x80, 0xa8, 0x3c, 0x21, - 0xfd, 0xbe, 0xfd, 0x86, 0xfa, 0x17, 0xec, 0x25, 0xd8, 0x5f, 0x15, 0x55, 0x98, 0x4b, 0xa5, 0xf9, 0xbe, 0x60, 0x47, - 0x03, 0x15, 0x71, 0x78, 0xc7, 0x91, 0xa2, 0x8d, 0xca, 0x65, 0xd9, 0x93, 0x65, 0xc3, 0x57, 0xe2, 0x92, 0x3b, 0x3f, - 0xae, 0x4a, 0xca, 0xbc, 0xca, 0x56, 0x8a, 0xfd, 0x9b, 0x71, 0xcd, 0xfd, 0x81, 0xf5, 0x67, 0xf3, 0x15, 0x5c, 0x5b, - 0xbd, 0x77, 0x4d, 0xae, 0x11, 0x39, 0x4b, 0x28, 0x97, 0xd4, 0x36, 0x0f, 0x6f, 0xe9, 0xfb, 0xfc, 0xea, 0xdb, 0x4c, - 0xa7, 0xf1, 0x59, 0x85, 0x85, 0x0b, 0xd1, 0x8a, 0xe0, 0xd0, 0x90, 0x8b, 0xe6, 0x11, 0x60, 0xae, 0x7d, 0xb6, 0x82, - 0x82, 0xd4, 0xa7, 0x15, 0x7a, 0xb7, 0x42, 0xc2, 0x0b, 0xcd, 0x2e, 0xdd, 0x0f, 0xa4, 0x8c, 0xdb, 0x43, 0x4b, 0x98, - 0xb4, 0xbc, 0x08, 0xef, 0xbd, 0xe6, 0x26, 0xf7, 0x32, 0xc4, 0xe8, 0x45, 0x9e, 0x9d, 0x80, 0xb1, 0xee, 0x92, 0x9d, - 0x0d, 0x4f, 0xfc, 0x86, 0xe7, 0xac, 0x45, 0xa3, 0xe9, 0x92, 0x25, 0xfd, 0x7e, 0x0c, 0x26, 0xde, 0x29, 0xcb, 0xe1, - 0x57, 0xbe, 0xa0, 0x6b, 0x06, 0x98, 0x62, 0xf4, 0x1c, 0x12, 0x52, 0x44, 0x22, 0x59, 0xab, 0x93, 0xe4, 0x33, 0xdd, - 0x05, 0x60, 0xf4, 0xf3, 0x59, 0x1a, 0x2d, 0xef, 0x34, 0xb3, 0x40, 0xf2, 0x0c, 0x7d, 0xd7, 0xc1, 0xf6, 0xc6, 0x3e, - 0x48, 0x39, 0x3f, 0x14, 0xd3, 0xc1, 0x80, 0x13, 0x0d, 0x37, 0x5e, 0x2a, 0x71, 0xad, 0x6e, 0x71, 0xc7, 0x30, 0x96, - 0xfa, 0xb6, 0x88, 0xc1, 0x01, 0xbb, 0x68, 0x65, 0xb7, 0x0f, 0xb0, 0xaf, 0x1c, 0xef, 0x52, 0x65, 0x77, 0x7a, 0xcc, - 0x34, 0x97, 0xad, 0x26, 0x9d, 0x54, 0xdc, 0x4d, 0xe4, 0x9b, 0xdc, 0x41, 0x97, 0xcb, 0xb1, 0xe6, 0x2d, 0x07, 0xa0, - 0xa2, 0x1f, 0x29, 0xaa, 0xfb, 0x05, 0x8e, 0x30, 0xf7, 0xd6, 0x6d, 0x3e, 0xd9, 0x37, 0x05, 0x0e, 0x91, 0x27, 0x6d, - 0x34, 0x05, 0x74, 0xef, 0xe2, 0x61, 0x57, 0xbf, 0x2d, 0xdd, 0x05, 0x4a, 0xb4, 0x53, 0x71, 0xc3, 0x8f, 0x89, 0x3a, - 0x9d, 0x69, 0x43, 0xe8, 0x5f, 0x19, 0x71, 0x7f, 0x69, 0x5c, 0xc5, 0x9b, 0xde, 0xe5, 0x33, 0x0e, 0x75, 0x76, 0x43, - 0x28, 0x00, 0x57, 0xed, 0xe9, 0xd4, 0x8d, 0x21, 0xbd, 0x52, 0xa2, 0xdb, 0xe0, 0x60, 0x77, 0xfa, 0x8c, 0xa3, 0xe8, - 0xc7, 0xa8, 0x91, 0xaf, 0x23, 0xf1, 0x50, 0x0e, 0xe2, 0x87, 0x05, 0x5d, 0x46, 0xe2, 0x61, 0x31, 0x88, 0x1f, 0xca, - 0xba, 0xde, 0x3d, 0x57, 0xee, 0xee, 0x23, 0xf2, 0xac, 0x3b, 0x7b, 0xa9, 0x84, 0x8d, 0x81, 0x67, 0xd7, 0x02, 0xc2, - 0x29, 0x78, 0x22, 0x5b, 0x4b, 0x1f, 0x3a, 0xb7, 0xfb, 0xd8, 0x32, 0x49, 0x10, 0xf4, 0xbc, 0xcd, 0x26, 0x51, 0xec, - 0x6c, 0xf3, 0xe8, 0xc3, 0x29, 0x90, 0xd0, 0xed, 0xb6, 0x59, 0x57, 0x6b, 0x40, 0x31, 0x0d, 0xc7, 0x7c, 0xbf, 0x18, - 0x5d, 0xfb, 0xee, 0xfa, 0xfb, 0xc5, 0x68, 0x49, 0x86, 0x13, 0x33, 0xf9, 0xf1, 0xd1, 0x78, 0x16, 0x47, 0x93, 0xba, - 0xe3, 0xb4, 0xd0, 0xf8, 0xa7, 0xde, 0x2d, 0x14, 0x81, 0x53, 0x31, 0x82, 0x23, 0xa7, 0x42, 0x39, 0x29, 0x35, 0x30, - 0xfc, 0xf7, 0xaa, 0x1d, 0x6d, 0xda, 0xab, 0xb8, 0x4a, 0x96, 0x99, 0xb8, 0xd0, 0xe1, 0xc3, 0x75, 0x74, 0x71, 0x1b, - 0xd0, 0xce, 0xbb, 0x4c, 0x3b, 0x7e, 0x9d, 0x34, 0xe8, 0x89, 0xab, 0x99, 0x01, 0xb7, 0xee, 0x47, 0x68, 0x86, 0xc0, - 0x68, 0x79, 0xfe, 0x16, 0x31, 0xb7, 0x7f, 0x51, 0x36, 0xbf, 0x8a, 0xf6, 0x39, 0x32, 0x52, 0xb6, 0xc9, 0x48, 0x05, - 0x46, 0x98, 0x52, 0x24, 0x71, 0x15, 0x42, 0x20, 0xfb, 0x2f, 0x29, 0xae, 0xc5, 0xd2, 0x7b, 0x0d, 0xc2, 0x04, 0xdb, - 0x05, 0xed, 0x57, 0xb7, 0x73, 0x5b, 0x69, 0xb1, 0x47, 0xea, 0xfb, 0xdc, 0xd9, 0xae, 0x68, 0xf2, 0xf7, 0x65, 0x03, - 0xda, 0x00, 0xa2, 0xbc, 0xab, 0x8f, 0x4a, 0xe0, 0x64, 0xc4, 0x0d, 0x25, 0x46, 0x2f, 0xe8, 0xea, 0x44, 0xee, 0xd9, - 0xa9, 0x79, 0x53, 0x31, 0x53, 0x71, 0xe5, 0x9b, 0x3d, 0xf3, 0x1f, 0x0c, 0x05, 0x2d, 0xc1, 0xc0, 0xdb, 0x9c, 0xf1, - 0xe8, 0x40, 0x77, 0x6d, 0x74, 0x5a, 0xb0, 0x59, 0x50, 0x97, 0x75, 0xdd, 0xc6, 0x83, 0x46, 0x1c, 0x14, 0xc5, 0xaa, - 0x50, 0x23, 0xe1, 0x89, 0x40, 0xc0, 0x94, 0x5d, 0xf2, 0xc8, 0x08, 0x6a, 0x7a, 0x13, 0x0a, 0x1b, 0x0a, 0xfe, 0x2a, - 0x51, 0x4d, 0x6f, 0x42, 0x9b, 0x4c, 0x9c, 0x66, 0x10, 0xc1, 0x8c, 0xd8, 0xee, 0xb7, 0x80, 0x36, 0xb7, 0x66, 0xb4, - 0xa9, 0x6b, 0xab, 0xad, 0x42, 0x2e, 0x29, 0x52, 0x96, 0xff, 0x4e, 0x4d, 0x05, 0x25, 0xb5, 0x5c, 0xf4, 0x26, 0x4d, - 0x17, 0x3d, 0x9e, 0x19, 0x49, 0xa0, 0x72, 0xcb, 0x1d, 0xa3, 0x3f, 0x84, 0x05, 0x1e, 0x31, 0x71, 0x62, 0xc1, 0xdc, - 0xea, 0x88, 0x65, 0x73, 0xb1, 0x18, 0xad, 0x24, 0x84, 0x0d, 0x3e, 0x64, 0xd9, 0xbc, 0xd4, 0x0f, 0xa1, 0x2f, 0x2c, - 0x7d, 0x03, 0x76, 0xb1, 0xc1, 0x4a, 0x96, 0x01, 0xf8, 0x5e, 0xd0, 0xcd, 0x4a, 0x96, 0x91, 0x54, 0xdd, 0x8f, 0x6b, - 0x2c, 0x41, 0xa5, 0x15, 0x2a, 0x2d, 0xa9, 0xb1, 0x20, 0xf0, 0x55, 0xd5, 0xe5, 0x43, 0xb2, 0xab, 0x40, 0x3d, 0x75, - 0xd4, 0x80, 0x53, 0xa0, 0xaa, 0xc0, 0x82, 0x24, 0xa8, 0x0c, 0x5d, 0x15, 0x98, 0x56, 0x60, 0x9a, 0xa9, 0xc2, 0x45, - 0x99, 0x1d, 0x4a, 0xb3, 0x5e, 0xf2, 0x59, 0x3c, 0x08, 0x93, 0x61, 0x4c, 0x1e, 0x22, 0xd4, 0xfe, 0x7e, 0x1e, 0xc5, - 0x5a, 0x2e, 0x79, 0xe1, 0xfc, 0xe2, 0xaf, 0x3f, 0x63, 0xaf, 0x7b, 0x8a, 0xc1, 0x02, 0x9c, 0xa5, 0xed, 0x65, 0x26, - 0xde, 0xca, 0x56, 0x70, 0x1c, 0xcc, 0xa2, 0x1c, 0x56, 0x3d, 0x39, 0xa2, 0xb9, 0xc8, 0xb5, 0x77, 0x11, 0x22, 0x07, - 0x99, 0x3d, 0x06, 0xd8, 0x8d, 0xf0, 0x75, 0x68, 0x6d, 0x6e, 0x75, 0x85, 0xf8, 0x1b, 0x25, 0x12, 0x3f, 0x49, 0xf9, - 0x71, 0xbd, 0x52, 0xb9, 0x2a, 0x83, 0xc7, 0xaa, 0x9b, 0xc1, 0x33, 0xed, 0x7b, 0xac, 0xfd, 0x5b, 0xdb, 0xcd, 0xf1, - 0xde, 0x83, 0x07, 0xad, 0xff, 0xad, 0x27, 0x21, 0xb4, 0x57, 0x4e, 0x52, 0x77, 0xd4, 0xe8, 0x99, 0xc9, 0x1a, 0x51, - 0x09, 0x53, 0xbb, 0x53, 0x39, 0x06, 0x6a, 0x3a, 0x80, 0x6b, 0x89, 0x9a, 0xa0, 0x27, 0x05, 0x1b, 0xc3, 0x11, 0x67, - 0x71, 0xd0, 0x0e, 0x63, 0x14, 0x2f, 0xe7, 0x4a, 0xbc, 0x9c, 0x1f, 0x31, 0x0e, 0xd0, 0x5a, 0x80, 0x54, 0xaf, 0x61, - 0x3f, 0x73, 0x05, 0x0b, 0x6c, 0xee, 0x7c, 0x07, 0x16, 0xc8, 0x10, 0x27, 0x9b, 0xe3, 0x64, 0x8f, 0x6b, 0x3d, 0xf7, - 0x02, 0x1f, 0x27, 0xf5, 0xc2, 0xab, 0xab, 0x6c, 0xd7, 0xb5, 0x64, 0xe5, 0xbc, 0x18, 0x4c, 0x20, 0x28, 0x4b, 0x39, - 0x2f, 0x86, 0x93, 0x05, 0xcd, 0xe1, 0xc7, 0xa2, 0x81, 0x0e, 0xb1, 0x1c, 0x24, 0x70, 0xe9, 0xec, 0x31, 0xe0, 0x0d, - 0xa5, 0x16, 0x77, 0x63, 0x1d, 0x39, 0xd6, 0x51, 0xec, 0x87, 0x31, 0xe0, 0xca, 0x3a, 0x81, 0xf7, 0xdd, 0xd7, 0xc7, - 0x26, 0x20, 0xab, 0x76, 0x85, 0x57, 0xa3, 0xdc, 0x75, 0xa5, 0xd1, 0x97, 0x94, 0x9e, 0xf0, 0x82, 0xa7, 0x92, 0xed, - 0xb6, 0x67, 0xe0, 0x6c, 0x89, 0x87, 0xc4, 0x3b, 0x46, 0xf4, 0x62, 0xda, 0xc8, 0xcc, 0x09, 0x9c, 0xd9, 0xee, 0xb2, - 0x8d, 0xf9, 0xb1, 0x03, 0x1c, 0x2c, 0x82, 0x90, 0xb8, 0x21, 0x0c, 0x13, 0x3b, 0x2a, 0x87, 0x5a, 0x08, 0xd7, 0xb5, - 0xf0, 0x3a, 0x4e, 0xcb, 0x18, 0x5c, 0xa4, 0xb5, 0x6d, 0xe2, 0x1d, 0x74, 0xdd, 0xf3, 0x63, 0x6e, 0x75, 0x8c, 0xb6, - 0x90, 0x7e, 0x3b, 0x3a, 0xbd, 0xe7, 0x30, 0x00, 0x4d, 0x0f, 0x66, 0x55, 0xfb, 0x4c, 0xe2, 0xe6, 0xb4, 0x13, 0x84, - 0x44, 0x20, 0x8a, 0xd2, 0x19, 0x61, 0xfa, 0x77, 0x9a, 0xcb, 0x2a, 0x5a, 0xdd, 0xcb, 0x33, 0x87, 0x3c, 0x0b, 0xbd, - 0xed, 0x41, 0xab, 0xe6, 0x6e, 0x30, 0x4e, 0xdc, 0x6e, 0xef, 0xfc, 0xbf, 0x65, 0x5d, 0x5b, 0xad, 0x11, 0x0f, 0xdb, - 0xd5, 0x0f, 0x1a, 0x7b, 0xb5, 0xa7, 0x62, 0xc0, 0x5c, 0x48, 0xef, 0x8c, 0x2a, 0x79, 0x91, 0xf1, 0x12, 0x4f, 0xaa, - 0x8b, 0x86, 0x8f, 0xf7, 0x75, 0x36, 0x32, 0x0f, 0x64, 0x0a, 0x88, 0xe7, 0x1f, 0x53, 0xa3, 0x3e, 0x4e, 0x51, 0x02, - 0xfe, 0x56, 0xc7, 0x37, 0xa2, 0x27, 0xf6, 0xc5, 0x05, 0xaf, 0xde, 0x5c, 0x0b, 0xf3, 0xe2, 0x99, 0xd5, 0xf9, 0xd3, - 0xa7, 0x85, 0x0f, 0x1d, 0x8e, 0xda, 0x3b, 0x28, 0xb2, 0x64, 0xe2, 0x68, 0x62, 0x64, 0x6d, 0x62, 0x76, 0xa2, 0xe0, - 0x62, 0xa2, 0x0a, 0x3d, 0xeb, 0xec, 0x09, 0x53, 0x80, 0xbe, 0x71, 0x8c, 0x4a, 0xc6, 0xb0, 0x60, 0xa0, 0x4e, 0x53, - 0x42, 0xf4, 0x50, 0xcc, 0x30, 0x5e, 0x31, 0x80, 0xc2, 0x14, 0x0a, 0x44, 0xd1, 0xd9, 0x87, 0x03, 0x4d, 0xe8, 0xf7, - 0x3f, 0xa6, 0x3a, 0x03, 0x2d, 0xeb, 0x69, 0x01, 0xa2, 0x3a, 0x88, 0xb6, 0x0a, 0x84, 0x39, 0xa5, 0x65, 0x46, 0x97, - 0x82, 0xa6, 0x82, 0x26, 0x19, 0x3d, 0xe7, 0x4a, 0x54, 0x7c, 0x2e, 0x98, 0xa2, 0xed, 0x86, 0xb0, 0xff, 0xd8, 0xa0, - 0xeb, 0xad, 0x58, 0x6b, 0x68, 0x77, 0x82, 0x8c, 0xd0, 0x7c, 0xa1, 0x83, 0x90, 0xa1, 0x72, 0x12, 0xf1, 0xe1, 0x35, - 0x5e, 0x81, 0x4b, 0xa6, 0xd9, 0x68, 0x19, 0x97, 0x61, 0x60, 0xbf, 0x0a, 0x2c, 0x26, 0x07, 0x26, 0x9d, 0xac, 0xcf, - 0x9e, 0xca, 0xcb, 0x95, 0x14, 0x5c, 0x54, 0x0a, 0xa2, 0xdf, 0xe0, 0xbe, 0x9b, 0xb8, 0xea, 0xac, 0x59, 0x2b, 0xbd, - 0xef, 0x5b, 0x9f, 0xb5, 0x71, 0x5f, 0x18, 0x1c, 0x83, 0x9d, 0x8f, 0x88, 0x81, 0x34, 0xa8, 0x74, 0x8b, 0x43, 0x13, - 0xa0, 0x4b, 0x87, 0x14, 0xb2, 0x64, 0x2a, 0x53, 0x25, 0xa8, 0xf8, 0xc6, 0xef, 0xa4, 0xac, 0x46, 0x7f, 0xaf, 0x79, - 0x71, 0x7b, 0xc2, 0x73, 0x8e, 0x63, 0x14, 0x24, 0xb1, 0xb8, 0x8a, 0xcb, 0x80, 0xf8, 0x96, 0x57, 0xc1, 0x41, 0x6a, - 0xc2, 0xc6, 0xec, 0x54, 0x8d, 0x5a, 0xaf, 0x02, 0x7d, 0x65, 0x94, 0x6f, 0x0c, 0x86, 0x26, 0xa2, 0x0a, 0xfa, 0x5e, - 0xab, 0x7b, 0x5a, 0xdd, 0xb0, 0x80, 0xf8, 0x73, 0xa5, 0x17, 0x6a, 0xbd, 0x6e, 0xc6, 0xdc, 0x30, 0x11, 0x82, 0x46, - 0x8f, 0xea, 0x85, 0xc3, 0xcf, 0xdf, 0x28, 0x4b, 0x22, 0x78, 0xb1, 0x49, 0xd7, 0x85, 0x89, 0xa5, 0x41, 0x75, 0xc0, - 0xdc, 0x68, 0x93, 0xf3, 0x0b, 0x10, 0xfd, 0x39, 0x2b, 0xa2, 0x49, 0x5d, 0x53, 0x85, 0x60, 0x18, 0x6d, 0x6e, 0x1a, - 0xe9, 0xf4, 0x16, 0xbc, 0xdc, 0x8c, 0x35, 0x92, 0xf6, 0x74, 0xac, 0x69, 0xc1, 0xcb, 0x95, 0x14, 0x25, 0x44, 0x77, - 0xee, 0x8d, 0xe9, 0x65, 0x9c, 0x89, 0x2a, 0xce, 0xc4, 0x71, 0xb9, 0xe2, 0x49, 0xf5, 0x0e, 0x2a, 0xd4, 0xc6, 0x38, - 0xd8, 0x7a, 0x35, 0xea, 0x2a, 0x1c, 0xf2, 0xcb, 0xf3, 0xe7, 0x37, 0xab, 0x58, 0xa4, 0x30, 0xea, 0xf5, 0x5d, 0x2f, - 0x9a, 0xd3, 0xb1, 0x8a, 0x0b, 0x2e, 0x4c, 0xd4, 0x62, 0x5a, 0xb1, 0x80, 0xeb, 0x8c, 0x01, 0xe5, 0x2a, 0x76, 0x67, - 0xa6, 0x62, 0x19, 0xc6, 0x65, 0xf9, 0x53, 0x56, 0xe2, 0x1d, 0x00, 0x5a, 0x03, 0xa7, 0xc5, 0xcc, 0x80, 0x80, 0xdc, - 0xe6, 0x06, 0x17, 0x81, 0x05, 0x07, 0x8f, 0xc7, 0xab, 0x9b, 0x80, 0x7a, 0x6f, 0xa4, 0xba, 0x1e, 0xb2, 0x60, 0x3c, - 0x7a, 0x12, 0x38, 0xe4, 0x10, 0xff, 0xa3, 0xc7, 0x07, 0x77, 0x7f, 0x33, 0x09, 0x48, 0x3d, 0x05, 0x55, 0x85, 0x51, - 0x88, 0xc2, 0xb4, 0xbf, 0x5a, 0xab, 0x5b, 0xee, 0x9b, 0xb3, 0x92, 0x17, 0x57, 0x10, 0xad, 0x9d, 0x4c, 0x33, 0x20, - 0xe7, 0x52, 0x25, 0xc0, 0xa2, 0x88, 0xab, 0xaa, 0xc8, 0xce, 0xc0, 0x44, 0x09, 0x0d, 0xc0, 0xcc, 0xd3, 0x0b, 0x74, - 0xf8, 0x88, 0xe6, 0x01, 0xf6, 0x29, 0x58, 0xd4, 0xa4, 0x2e, 0xa1, 0xb0, 0x64, 0x0f, 0x83, 0xd5, 0xa9, 0xb8, 0xd2, - 0x0e, 0xe0, 0xbb, 0xfa, 0x33, 0x5a, 0x4a, 0x8c, 0x35, 0xab, 0xe7, 0x29, 0x3e, 0x2b, 0x65, 0xbe, 0xae, 0x40, 0x7b, - 0x7e, 0x5e, 0x45, 0x07, 0x8f, 0x57, 0x37, 0x53, 0xd5, 0x8d, 0x08, 0x7a, 0x31, 0x55, 0x38, 0x6f, 0x49, 0x9c, 0x27, - 0xe1, 0x64, 0x3c, 0xfe, 0x6a, 0x6f, 0xb8, 0x07, 0xc9, 0x64, 0xfa, 0x69, 0xa8, 0x1c, 0xb9, 0x86, 0x93, 0xf1, 0xb8, - 0xfe, 0xb3, 0x36, 0x61, 0xbe, 0x4d, 0x3d, 0x4f, 0xff, 0x3c, 0x54, 0xeb, 0xff, 0xe8, 0x70, 0x5f, 0xff, 0xf8, 0xb3, - 0xae, 0xa7, 0x4f, 0x8b, 0x70, 0xfe, 0x7b, 0xa8, 0xd6, 0xf7, 0x71, 0x51, 0xc4, 0xb7, 0x35, 0x44, 0x36, 0x15, 0xce, - 0xbb, 0x86, 0x7a, 0x64, 0x81, 0x1e, 0x90, 0xe9, 0xb9, 0x60, 0xf0, 0xcd, 0xbb, 0x2a, 0x0c, 0x78, 0xb9, 0x1a, 0x72, - 0x51, 0x65, 0xd5, 0xed, 0x10, 0xf3, 0x04, 0xf8, 0xa9, 0xc5, 0x33, 0x2b, 0x0c, 0xf1, 0x3d, 0x2f, 0x38, 0xff, 0xc4, - 0x43, 0x65, 0x2c, 0x3e, 0x46, 0x63, 0xf1, 0x31, 0x55, 0xdd, 0x98, 0x7c, 0x43, 0x75, 0xdf, 0x26, 0xdf, 0x80, 0x49, - 0x56, 0xd6, 0xfe, 0x46, 0x19, 0x6b, 0x46, 0x63, 0x7a, 0xf5, 0x22, 0xcf, 0x56, 0x70, 0x29, 0x58, 0xea, 0x1f, 0x35, - 0xa1, 0xef, 0x78, 0x3b, 0xfb, 0x68, 0x34, 0x7a, 0x53, 0xd0, 0xd1, 0x68, 0xf4, 0x31, 0xab, 0x09, 0x5d, 0x89, 0x8e, - 0xf7, 0xef, 0x38, 0x3d, 0x93, 0xe9, 0x6d, 0x14, 0x04, 0x74, 0x99, 0xa5, 0x29, 0x17, 0xaa, 0xac, 0x57, 0x69, 0x3b, - 0xaf, 0x6a, 0x21, 0x02, 0x21, 0xe9, 0x36, 0x22, 0x24, 0x13, 0xa1, 0x6f, 0x77, 0x7a, 0x36, 0x1a, 0x8d, 0x5e, 0xa5, - 0xa6, 0x5a, 0x77, 0x41, 0x79, 0x8a, 0xe6, 0x14, 0xce, 0x4f, 0x01, 0xac, 0x91, 0x4c, 0xf4, 0x97, 0xfd, 0xff, 0x1e, - 0xce, 0xe6, 0xe3, 0xe1, 0xb7, 0xa3, 0xc5, 0xc3, 0x7d, 0x1a, 0x04, 0x7e, 0xe8, 0x86, 0x50, 0x5b, 0xb7, 0x4c, 0xcb, - 0xc3, 0xf1, 0x94, 0x94, 0x03, 0xf6, 0xd8, 0xfa, 0x16, 0x7d, 0xf5, 0x18, 0x90, 0x59, 0x51, 0xa4, 0x1c, 0x38, 0x69, - 0x28, 0x5e, 0xcd, 0x5e, 0x0a, 0xc0, 0x8b, 0xb3, 0x91, 0x1d, 0x8c, 0x56, 0x74, 0x1c, 0x41, 0x79, 0xb5, 0x35, 0x15, - 0xe9, 0x31, 0x96, 0x99, 0x28, 0xa9, 0xe3, 0x69, 0x79, 0x9d, 0x55, 0xc9, 0x12, 0x03, 0x3d, 0xc5, 0x25, 0x0f, 0xbe, - 0x0a, 0xa2, 0x92, 0x1d, 0x3c, 0x99, 0x2a, 0xb8, 0x63, 0x4c, 0x4a, 0xf9, 0x05, 0x24, 0x7e, 0x3b, 0x46, 0x48, 0x58, - 0xa2, 0x3d, 0x38, 0xb1, 0xc6, 0x17, 0xb9, 0x8c, 0xc1, 0xa3, 0xb5, 0xd4, 0x3c, 0x9c, 0x3d, 0x19, 0xad, 0x3d, 0x4a, - 0xab, 0x39, 0x12, 0x9a, 0x13, 0x4a, 0x26, 0xf7, 0x4b, 0x2a, 0xbf, 0x9a, 0xa0, 0x97, 0x14, 0xb8, 0x99, 0x47, 0x70, - 0xfc, 0x5b, 0x4b, 0x0f, 0xbd, 0x7c, 0x52, 0xb6, 0x3f, 0xff, 0xdf, 0x25, 0x5d, 0x0c, 0xf6, 0xdd, 0xd0, 0xbc, 0xd5, - 0xee, 0xbc, 0x15, 0x32, 0x8e, 0x55, 0xf8, 0x26, 0x25, 0xd6, 0x18, 0x97, 0xb3, 0xa3, 0x8d, 0xe9, 0xce, 0xa8, 0x2a, - 0xb2, 0xcb, 0x90, 0xe8, 0x5e, 0x39, 0x90, 0xd0, 0x20, 0xca, 0x46, 0xb8, 0x7e, 0xc0, 0x7a, 0xc6, 0xeb, 0xe4, 0x15, - 0x2f, 0xaa, 0x2c, 0x51, 0xef, 0xaf, 0x1a, 0xef, 0xeb, 0xda, 0x04, 0x54, 0x7d, 0x50, 0x30, 0x98, 0xe7, 0xb7, 0x05, - 0x80, 0x98, 0x22, 0x0d, 0xf0, 0x09, 0x66, 0x10, 0xd4, 0xae, 0x99, 0x97, 0x8d, 0xe0, 0x1b, 0xf0, 0xd5, 0x83, 0x02, - 0x30, 0x48, 0x42, 0x90, 0x22, 0x43, 0x68, 0x20, 0x10, 0x68, 0x18, 0x72, 0x81, 0xc1, 0x4f, 0xbc, 0x38, 0x92, 0xca, - 0x29, 0x91, 0x87, 0x01, 0xfe, 0x08, 0xa8, 0x0a, 0x40, 0x62, 0x3c, 0x0e, 0xe1, 0x85, 0xfa, 0xe5, 0xde, 0xa8, 0x3d, - 0xc2, 0x9e, 0xa6, 0x21, 0x04, 0x1b, 0xc2, 0x87, 0x00, 0x96, 0x14, 0xa1, 0x6f, 0x91, 0xcb, 0x08, 0x83, 0xf3, 0x3c, - 0x5b, 0xe9, 0xa4, 0x6a, 0xd4, 0xd1, 0x7c, 0x28, 0xb5, 0x23, 0x39, 0xa0, 0x5e, 0x7a, 0x8c, 0xe9, 0x85, 0x4a, 0x57, - 0x45, 0x39, 0xa3, 0x9c, 0x07, 0x7a, 0x62, 0x5c, 0xd8, 0x42, 0x0e, 0x91, 0x70, 0x1e, 0x14, 0x2a, 0x14, 0x0e, 0x5f, - 0x00, 0x18, 0x18, 0x48, 0x3b, 0x76, 0xe3, 0xdd, 0xa8, 0xec, 0xa7, 0x9c, 0xed, 0xff, 0xf7, 0x3c, 0x1e, 0x7e, 0x1a, - 0x0f, 0xbf, 0x5d, 0x0c, 0xc2, 0xa1, 0xfd, 0x49, 0x1e, 0x3e, 0xd8, 0xa7, 0x2f, 0xb8, 0xe5, 0xd2, 0x60, 0xe1, 0x37, - 0x82, 0xfd, 0xa8, 0x95, 0x10, 0x44, 0x01, 0xde, 0xb0, 0xdc, 0x6a, 0x9c, 0x00, 0xe0, 0x61, 0xf0, 0x5f, 0x01, 0x1a, - 0x4d, 0xb9, 0x8b, 0x17, 0xe8, 0x4b, 0xd4, 0xef, 0xa3, 0x47, 0x0d, 0x83, 0x41, 0x10, 0xd7, 0xa8, 0x98, 0x30, 0x44, - 0x97, 0x31, 0x51, 0x30, 0xc8, 0x36, 0xfb, 0x76, 0xdb, 0x6b, 0x4b, 0xc2, 0xf0, 0x4b, 0x3f, 0xd3, 0xc4, 0xcc, 0x3b, - 0xdc, 0xd8, 0x56, 0x72, 0x15, 0x22, 0x56, 0xa0, 0xfe, 0x95, 0x33, 0x88, 0xbd, 0x79, 0x95, 0x81, 0x4f, 0x87, 0xfd, - 0x62, 0x3c, 0x03, 0x36, 0x0a, 0xee, 0x7c, 0x05, 0x3f, 0xcf, 0xc0, 0xcd, 0x5b, 0xc4, 0x28, 0x70, 0xb0, 0x4b, 0xa2, - 0xdf, 0xef, 0xe5, 0x59, 0x98, 0x6b, 0xdc, 0xe9, 0xbc, 0x36, 0x6a, 0x08, 0xd4, 0x91, 0x83, 0xfa, 0x41, 0x0f, 0xc1, - 0x50, 0x0d, 0x41, 0xd1, 0xd1, 0x16, 0x57, 0xaf, 0xad, 0xa7, 0x30, 0xbd, 0x55, 0xf5, 0x15, 0xa3, 0xbf, 0x64, 0x26, - 0xb0, 0x90, 0x76, 0xcd, 0xb1, 0xae, 0x39, 0x46, 0xda, 0xd3, 0xef, 0x8b, 0x06, 0xf9, 0xe9, 0x2c, 0x3c, 0x08, 0x54, - 0xa9, 0x72, 0xa7, 0x2c, 0xca, 0x6d, 0x69, 0xde, 0x18, 0xd6, 0x34, 0xcf, 0x6c, 0x9c, 0x9b, 0x59, 0xaf, 0x17, 0x86, - 0xe8, 0xe0, 0x89, 0xa5, 0x62, 0x6d, 0x10, 0xee, 0xc8, 0x24, 0x8c, 0x2e, 0x41, 0x76, 0x19, 0x9e, 0x72, 0x82, 0x7c, - 0x2a, 0xb0, 0x0f, 0xaa, 0x5a, 0x2f, 0x27, 0x3c, 0x36, 0xf2, 0x65, 0x23, 0x68, 0x90, 0x97, 0x14, 0xf5, 0x26, 0x6e, - 0xc7, 0x1e, 0xb7, 0x90, 0x2b, 0x37, 0xf5, 0xb4, 0xa7, 0x49, 0x45, 0x8f, 0xf5, 0x2a, 0xf5, 0x0b, 0x2c, 0x2d, 0x2c, - 0xf9, 0x20, 0xb4, 0xa7, 0x69, 0x05, 0x66, 0xb8, 0xb2, 0x19, 0x0c, 0xfd, 0x70, 0xfc, 0x04, 0x74, 0x46, 0x6d, 0x4b, - 0x08, 0x63, 0x37, 0x08, 0x2b, 0xef, 0x89, 0x7c, 0xf5, 0xd8, 0xbb, 0x18, 0x84, 0xdc, 0x6c, 0x66, 0xd1, 0xc0, 0x74, - 0x3f, 0x93, 0xcd, 0xe6, 0xe9, 0xe6, 0x7a, 0x51, 0x42, 0x05, 0x6c, 0xb7, 0x95, 0x20, 0xf8, 0xf7, 0x63, 0x36, 0xc3, - 0xbf, 0x59, 0xbf, 0xdf, 0x0b, 0xf1, 0x17, 0xc7, 0x60, 0x46, 0x73, 0xb1, 0x60, 0x1f, 0x41, 0xc6, 0x44, 0x22, 0x4c, - 0x55, 0xc6, 0x80, 0xac, 0x02, 0x8b, 0x40, 0xf3, 0x81, 0xca, 0x85, 0x99, 0xec, 0x65, 0xce, 0x35, 0xe4, 0x79, 0x6b, - 0x9c, 0xb2, 0x51, 0x96, 0x28, 0x57, 0x8e, 0x6c, 0x14, 0xe7, 0x59, 0x5c, 0xf2, 0x72, 0xbb, 0xd5, 0x87, 0x63, 0x52, - 0x70, 0x60, 0xd7, 0x15, 0x95, 0x2a, 0x59, 0x47, 0xaa, 0x07, 0x5e, 0x1a, 0x16, 0xb8, 0x4f, 0xf9, 0xbc, 0x30, 0x34, - 0x62, 0x0f, 0x84, 0x19, 0x4c, 0xdd, 0xd2, 0x7b, 0x61, 0x01, 0xcd, 0x2b, 0x09, 0xd9, 0x60, 0xaa, 0x67, 0xe1, 0x1b, - 0x33, 0x31, 0x2f, 0x16, 0x10, 0x56, 0xa7, 0x58, 0x68, 0x66, 0x93, 0x26, 0x2c, 0x06, 0xd8, 0xbc, 0x98, 0x4c, 0x21, - 0xbe, 0xbb, 0x2a, 0x27, 0x5e, 0x98, 0xfb, 0x76, 0xe2, 0x90, 0x43, 0xe0, 0x55, 0x6d, 0xd0, 0xd5, 0x6c, 0xc3, 0x51, - 0x47, 0xca, 0x89, 0xc9, 0xef, 0xa7, 0x0a, 0x42, 0xdc, 0x89, 0x23, 0xe1, 0xf2, 0x66, 0xbb, 0xf0, 0xb2, 0x03, 0x41, - 0x47, 0x0d, 0x4e, 0xf9, 0x99, 0xc1, 0xd1, 0x98, 0xa4, 0x1b, 0xef, 0x04, 0x29, 0xc2, 0x98, 0x6c, 0x24, 0x3b, 0x93, - 0xa1, 0x98, 0xc7, 0x0b, 0x50, 0x5e, 0xc6, 0x0b, 0xb0, 0x34, 0x32, 0x06, 0xa9, 0x20, 0xbf, 0xe3, 0x5e, 0x28, 0x2c, - 0x8a, 0x2b, 0x44, 0x7a, 0x56, 0xbf, 0xc7, 0x45, 0x3b, 0x14, 0x08, 0x8a, 0x3b, 0x94, 0x79, 0x72, 0xd6, 0x63, 0x81, - 0xc4, 0x86, 0x80, 0xf1, 0x95, 0x4e, 0x53, 0xad, 0x75, 0x6f, 0x6c, 0xf4, 0xaa, 0x69, 0x36, 0x12, 0xb2, 0x3a, 0x3d, - 0x07, 0x91, 0x92, 0x8f, 0x8e, 0x8f, 0xfc, 0x22, 0xee, 0x2c, 0xf3, 0xd6, 0xb6, 0xa8, 0x64, 0x47, 0x1b, 0x00, 0x2d, - 0xd4, 0xd1, 0xb3, 0x94, 0xdc, 0xa6, 0x24, 0xb5, 0xdb, 0x14, 0xb0, 0x92, 0xfc, 0x05, 0x0c, 0xc1, 0xd7, 0xf6, 0x84, - 0xd3, 0xb1, 0x42, 0xbc, 0xa6, 0x29, 0x22, 0x4d, 0x86, 0x25, 0xc5, 0xb1, 0x2d, 0x11, 0x05, 0xd5, 0x96, 0x65, 0x07, - 0xc3, 0x44, 0x09, 0x7e, 0x96, 0x7a, 0x94, 0x28, 0x08, 0xa8, 0x1e, 0x72, 0x90, 0x60, 0xdb, 0x06, 0xc2, 0x03, 0xf2, - 0x88, 0xde, 0x58, 0x7f, 0x9f, 0x75, 0x9e, 0x5d, 0x68, 0x9e, 0xcb, 0xf5, 0xae, 0x30, 0x63, 0x84, 0x27, 0x99, 0x09, - 0x1b, 0xe0, 0x9d, 0x67, 0x46, 0x6d, 0xd3, 0xf3, 0xf0, 0xda, 0x9e, 0x63, 0x84, 0xbe, 0x3b, 0x06, 0xdd, 0x04, 0xf3, - 0xea, 0xb0, 0x59, 0xaf, 0x14, 0xa4, 0x86, 0xa9, 0x45, 0x13, 0xb3, 0x9e, 0x35, 0x28, 0xdf, 0x6e, 0x7b, 0x7a, 0xae, - 0xee, 0x9e, 0xbb, 0xed, 0xb6, 0x87, 0xdd, 0x7a, 0x96, 0x76, 0x5b, 0xc5, 0x57, 0xea, 0x83, 0xf6, 0xf8, 0x73, 0x37, - 0xfe, 0xdc, 0x20, 0x9b, 0x94, 0x8e, 0x66, 0xda, 0xfa, 0x20, 0x3c, 0x70, 0x7a, 0xdb, 0x68, 0xd2, 0xf7, 0x59, 0x28, - 0xe9, 0x4a, 0x34, 0xaa, 0x33, 0x21, 0xcc, 0x58, 0x75, 0xff, 0xfa, 0xbf, 0x7f, 0x15, 0xe0, 0x11, 0xa7, 0x76, 0xf6, - 0x9d, 0x0d, 0x2a, 0x1a, 0x6d, 0xe1, 0x48, 0x11, 0x7a, 0x40, 0x12, 0xee, 0x6a, 0x59, 0x8b, 0xdb, 0x3c, 0xc9, 0xee, - 0xa7, 0x4f, 0xef, 0x53, 0xdf, 0x0b, 0xc1, 0x2d, 0xb3, 0xcc, 0x1c, 0x78, 0x15, 0xc5, 0x01, 0x8d, 0xba, 0x68, 0xdf, - 0x65, 0x56, 0x96, 0xe0, 0xf5, 0x02, 0xf7, 0xca, 0x13, 0xee, 0xc3, 0xef, 0x5d, 0x54, 0xcd, 0x4d, 0x7a, 0x92, 0xcd, - 0xb3, 0xc5, 0x76, 0x1b, 0xe2, 0xdf, 0xae, 0x16, 0x39, 0x9a, 0x3c, 0x07, 0x9d, 0x26, 0x46, 0x32, 0x62, 0xba, 0x71, - 0xde, 0xe6, 0x7f, 0x2d, 0x1a, 0x4e, 0x13, 0xcf, 0x81, 0x5e, 0xcc, 0x8e, 0x41, 0x26, 0x65, 0x40, 0x0e, 0xc4, 0x4c, - 0xaf, 0x19, 0x88, 0x46, 0x26, 0x22, 0xc0, 0x15, 0xc6, 0x46, 0xa2, 0xd1, 0x09, 0x27, 0x35, 0x01, 0x0b, 0x56, 0x5b, - 0xde, 0x4f, 0x96, 0xb6, 0x55, 0xc5, 0xad, 0xb7, 0xa4, 0x39, 0xae, 0x03, 0xe7, 0xeb, 0x60, 0x86, 0xd8, 0x94, 0x5d, - 0x2d, 0x90, 0xfb, 0xe5, 0x35, 0xed, 0x8d, 0xeb, 0x04, 0x66, 0x6d, 0x53, 0x5b, 0xc6, 0xcf, 0x96, 0xfe, 0x4e, 0x0f, - 0xae, 0x32, 0x06, 0x9b, 0x1b, 0x2b, 0x0d, 0xbb, 0x6f, 0x3c, 0x5f, 0x0a, 0x08, 0x4f, 0xe7, 0xd3, 0xe3, 0x93, 0xcc, - 0xa3, 0xc7, 0x40, 0x74, 0xcc, 0x47, 0xa5, 0xfb, 0xc8, 0xee, 0x5e, 0x3f, 0x20, 0xe0, 0xbc, 0x6a, 0x17, 0x34, 0x2f, - 0x17, 0x10, 0x58, 0xd5, 0x2b, 0xaf, 0xb0, 0x7c, 0x66, 0xcc, 0x2e, 0x80, 0x0c, 0x15, 0x04, 0x02, 0x77, 0x77, 0x9d, - 0x0b, 0xb1, 0xea, 0xb0, 0x32, 0xa7, 0x49, 0xd8, 0x51, 0x88, 0xe6, 0xad, 0xc1, 0x2c, 0xf8, 0xaf, 0x60, 0x50, 0x0e, - 0x82, 0x28, 0x88, 0x82, 0x80, 0x0c, 0x0a, 0xf8, 0x85, 0xb8, 0x6b, 0x04, 0x63, 0xb6, 0x40, 0x87, 0xdf, 0x72, 0xe6, - 0x33, 0x22, 0x2f, 0xfd, 0xb0, 0x9e, 0xde, 0x00, 0x9c, 0x49, 0x99, 0xf3, 0x18, 0x7d, 0x4e, 0xde, 0x72, 0x96, 0x11, - 0xfa, 0xd6, 0x3b, 0x95, 0x1f, 0xf0, 0x46, 0xb0, 0xbf, 0xdd, 0x61, 0x7b, 0x01, 0xf2, 0x8a, 0xde, 0x98, 0xbe, 0xe5, - 0x24, 0xca, 0x1a, 0xce, 0xd4, 0x1c, 0x7a, 0x56, 0x59, 0xd6, 0x8a, 0x1a, 0x72, 0x83, 0x62, 0x6e, 0x64, 0x99, 0x9c, - 0x4c, 0x5b, 0xcd, 0xa9, 0xc0, 0x75, 0x67, 0xd7, 0x0b, 0x48, 0x0e, 0x85, 0x66, 0xe9, 0x6c, 0x38, 0x6f, 0xdb, 0xb2, - 0x67, 0xad, 0x53, 0xc8, 0x6b, 0x88, 0x8a, 0x06, 0xe9, 0x08, 0xa8, 0xa1, 0x15, 0x17, 0x15, 0xb8, 0x30, 0x9b, 0xf6, - 0x70, 0xd3, 0x1e, 0xd3, 0x8c, 0x9f, 0x20, 0x66, 0x1e, 0xc7, 0x96, 0x81, 0x1d, 0x89, 0xc3, 0xf7, 0x71, 0xbe, 0x40, - 0xbb, 0xf4, 0xd6, 0xd5, 0xe2, 0x11, 0xd6, 0x9e, 0xb7, 0x42, 0x42, 0x80, 0xf8, 0x34, 0x95, 0x6e, 0xb7, 0x41, 0x00, - 0x03, 0xdc, 0xef, 0xf7, 0x80, 0x6b, 0x35, 0xec, 0xa4, 0xb9, 0x35, 0x5b, 0x62, 0xaf, 0x28, 0x3c, 0x06, 0xe6, 0xd4, - 0xfc, 0x67, 0x10, 0x50, 0x3c, 0x77, 0x43, 0xb0, 0x37, 0x65, 0x47, 0x1b, 0x88, 0x38, 0x54, 0xe0, 0x03, 0xca, 0x85, - 0x41, 0xcc, 0xad, 0xe3, 0x78, 0x18, 0xf6, 0x49, 0x7d, 0x88, 0x63, 0x91, 0x67, 0xa1, 0x23, 0x2c, 0x95, 0x21, 0x2c, - 0x5c, 0x31, 0xd2, 0x41, 0x1c, 0xd4, 0xa4, 0x73, 0xb0, 0x2a, 0x17, 0x7c, 0xb9, 0xd7, 0x7b, 0x0d, 0x30, 0xe9, 0x99, - 0x37, 0x2c, 0x2f, 0x3c, 0x40, 0xb4, 0x5e, 0x0f, 0x17, 0x8a, 0x47, 0x26, 0x1a, 0x68, 0x9c, 0xf8, 0xd2, 0xb2, 0xeb, - 0x33, 0x2d, 0x2b, 0x19, 0x8d, 0x46, 0x55, 0xad, 0x24, 0x1f, 0xf6, 0xbb, 0x4f, 0x2d, 0x14, 0x4f, 0x19, 0xa7, 0x3c, - 0x05, 0xcb, 0x77, 0x43, 0xe9, 0xe6, 0x0b, 0xba, 0xe2, 0x22, 0x55, 0x3f, 0x3d, 0xf4, 0xcd, 0x06, 0x71, 0xcd, 0x9a, - 0x3a, 0x1c, 0x3b, 0xfc, 0x10, 0x00, 0xd3, 0x3e, 0xcc, 0x5c, 0xba, 0x86, 0xe9, 0x05, 0xf1, 0x6c, 0x5c, 0xf0, 0xd0, - 0xe5, 0x01, 0xec, 0x43, 0x73, 0x48, 0xe2, 0xa7, 0xf0, 0x73, 0x66, 0xd2, 0x3a, 0x3e, 0xc3, 0xd9, 0x8c, 0x4a, 0x75, - 0x23, 0x68, 0xbf, 0x86, 0x44, 0x62, 0x90, 0x9e, 0x1b, 0x0c, 0x45, 0xeb, 0x6e, 0x03, 0x57, 0x7e, 0x4b, 0xef, 0x7c, - 0x1a, 0x04, 0x58, 0xdf, 0x58, 0x0c, 0x00, 0xa8, 0xe2, 0x0f, 0x54, 0x5d, 0x99, 0x2b, 0x8a, 0x69, 0x98, 0x4a, 0xb4, - 0x77, 0x1c, 0xd7, 0x51, 0xe3, 0x3a, 0x2c, 0x58, 0x69, 0x6d, 0x9b, 0xdd, 0x5b, 0x88, 0x3f, 0xa2, 0x4b, 0x40, 0xb5, - 0x20, 0xee, 0x04, 0xf0, 0xa1, 0x91, 0xea, 0x40, 0x90, 0xdd, 0x07, 0x07, 0x00, 0xbc, 0xe1, 0x79, 0x18, 0xc2, 0x1f, - 0x58, 0x38, 0xb0, 0x2c, 0x55, 0x3f, 0x97, 0xd3, 0x18, 0xce, 0xdd, 0x5c, 0xed, 0xf0, 0xd9, 0x12, 0x14, 0x9b, 0x6a, - 0x4e, 0xcd, 0xe5, 0x2b, 0x6f, 0xec, 0xf7, 0x98, 0x60, 0x1e, 0x33, 0xdb, 0xf0, 0x5b, 0x4f, 0xb7, 0xf5, 0x0d, 0x76, - 0x03, 0x27, 0xed, 0x85, 0xd3, 0x5e, 0x6c, 0x97, 0x06, 0xf2, 0xaf, 0x6e, 0x08, 0x11, 0xde, 0x6b, 0x62, 0x91, 0x35, - 0x64, 0x3a, 0x16, 0x2b, 0x44, 0xb5, 0xa9, 0x78, 0xaa, 0x0d, 0x04, 0xca, 0xa9, 0xba, 0x30, 0xb5, 0x52, 0x99, 0x30, - 0x88, 0x3b, 0x25, 0x2c, 0xaa, 0x0c, 0x30, 0x0c, 0x2a, 0xa4, 0xb8, 0xb6, 0x9e, 0xbf, 0x70, 0xf9, 0x66, 0xa6, 0xcd, - 0xf6, 0xd3, 0x17, 0x79, 0x7c, 0xb1, 0xdd, 0x86, 0xdd, 0x2f, 0xc0, 0x1c, 0xb5, 0x54, 0x1a, 0x46, 0x70, 0x02, 0x51, - 0x92, 0xeb, 0x3b, 0x72, 0x4e, 0x1c, 0x27, 0xd7, 0x6e, 0xde, 0x6c, 0x27, 0xc5, 0x08, 0x2c, 0xe0, 0xc4, 0x45, 0x3a, - 0xd0, 0x52, 0x49, 0x6a, 0x4f, 0x01, 0x6f, 0xd3, 0x3b, 0x4a, 0x85, 0x57, 0x0b, 0x4d, 0x42, 0x2a, 0x77, 0x2f, 0xb1, - 0xa3, 0x06, 0x9c, 0x93, 0xba, 0x83, 0x80, 0xd3, 0x9e, 0x6e, 0xac, 0x55, 0x24, 0x9b, 0x04, 0xef, 0x95, 0x1e, 0xba, - 0x44, 0x3b, 0xb5, 0xbb, 0x6d, 0x55, 0xb6, 0x50, 0x30, 0xf7, 0x72, 0x96, 0xa8, 0xe3, 0x01, 0x85, 0x2e, 0xea, 0x68, - 0xc8, 0x17, 0xa4, 0xd0, 0x2b, 0x47, 0xab, 0x9a, 0x77, 0x25, 0x03, 0xa5, 0x5a, 0x05, 0x79, 0x4d, 0xac, 0xfb, 0x5a, - 0xd6, 0x58, 0x5c, 0x39, 0x21, 0x85, 0x4d, 0xf8, 0xd2, 0x52, 0x2c, 0xcc, 0x62, 0x6f, 0x4c, 0x7d, 0xe1, 0x12, 0xa1, - 0xed, 0x6e, 0x43, 0x8c, 0x36, 0x58, 0x37, 0xdb, 0xed, 0xfb, 0x22, 0x9c, 0x67, 0x0b, 0x2a, 0x47, 0x59, 0x8a, 0x90, - 0x6a, 0xc6, 0x63, 0xd9, 0x76, 0xc1, 0x4c, 0x0c, 0x75, 0xed, 0xf1, 0x92, 0x4c, 0xb1, 0x36, 0x49, 0x8e, 0xe2, 0x33, - 0x59, 0xa8, 0xb5, 0x46, 0x08, 0x1e, 0xee, 0xdf, 0xa5, 0x10, 0xd3, 0xce, 0xac, 0xbb, 0x5f, 0x76, 0x6e, 0x88, 0xdf, - 0x41, 0x60, 0x85, 0x92, 0xbd, 0x2f, 0x46, 0x67, 0x99, 0x48, 0x71, 0xa7, 0xaa, 0x28, 0xc1, 0x6a, 0x1d, 0x34, 0x5b, - 0x6e, 0xef, 0xc5, 0x96, 0x28, 0x40, 0x9c, 0x67, 0xa1, 0x19, 0xcf, 0xca, 0x59, 0xce, 0x64, 0x14, 0x1b, 0x12, 0x95, - 0x5e, 0x94, 0x78, 0x9f, 0xa7, 0x31, 0x3d, 0x74, 0x6b, 0x10, 0x5c, 0x57, 0x77, 0x36, 0xd2, 0x7c, 0x41, 0x88, 0x9a, - 0x00, 0x09, 0x1b, 0xd5, 0x9c, 0x5a, 0x17, 0xe2, 0x7e, 0x56, 0xf9, 0x56, 0x1f, 0xc4, 0x17, 0x02, 0x78, 0x58, 0x6f, - 0x7b, 0x5f, 0x0a, 0x8f, 0xb5, 0xc1, 0xb7, 0xdb, 0xed, 0x85, 0x98, 0x07, 0x81, 0xc7, 0x68, 0xfe, 0xa0, 0x24, 0xe6, - 0xbd, 0x31, 0x85, 0x15, 0xef, 0xbb, 0xf8, 0x75, 0x93, 0x5a, 0x6b, 0x91, 0xbb, 0xc3, 0xf5, 0x01, 0xcf, 0x53, 0xe2, - 0x68, 0x47, 0xe5, 0x54, 0x5a, 0xdb, 0x01, 0xec, 0x8a, 0xc0, 0x40, 0xd9, 0x1f, 0x52, 0xb6, 0x01, 0xf3, 0x44, 0xb0, - 0x3e, 0x42, 0xbf, 0x2d, 0xa5, 0x3f, 0x19, 0xa3, 0x71, 0x8f, 0x5c, 0x57, 0xd1, 0x01, 0xd7, 0xd1, 0xec, 0x79, 0xf4, - 0x8f, 0x27, 0x63, 0x5a, 0xc4, 0x22, 0x95, 0x97, 0xa0, 0x82, 0x00, 0x65, 0x08, 0x3a, 0x42, 0x68, 0x6a, 0x00, 0x1a, - 0x04, 0x37, 0x00, 0xbf, 0x76, 0x3a, 0x51, 0xda, 0x9a, 0x7c, 0x8c, 0x56, 0x55, 0xe4, 0xac, 0x0d, 0xed, 0xa6, 0x92, - 0x43, 0xf2, 0xb0, 0x04, 0x7c, 0x4b, 0x6c, 0x96, 0xb2, 0x41, 0x51, 0x9b, 0x4d, 0xbd, 0x56, 0xec, 0xc8, 0x4d, 0xa3, - 0x68, 0xb3, 0x16, 0xb5, 0xdd, 0xc8, 0x7c, 0x31, 0xbd, 0xb1, 0xc2, 0xc0, 0xa9, 0x69, 0xcd, 0xf5, 0x0e, 0x94, 0x9c, - 0xad, 0xcf, 0xe4, 0x26, 0x40, 0x1c, 0x60, 0xb8, 0x6e, 0xe6, 0xd7, 0x0b, 0x42, 0x6f, 0xd8, 0x8d, 0x15, 0xab, 0x5e, - 0x5b, 0xb9, 0x88, 0x49, 0xbb, 0x1e, 0x4c, 0xe0, 0x32, 0xce, 0x0a, 0xfb, 0x42, 0xab, 0x1b, 0x8a, 0x8e, 0xb6, 0x49, - 0xfb, 0x79, 0x47, 0xbb, 0xe1, 0x82, 0x6f, 0xc5, 0x3a, 0xce, 0x2d, 0x6b, 0xaa, 0xd0, 0xb4, 0x03, 0xbd, 0x1d, 0x02, - 0x9a, 0xb3, 0x31, 0x5d, 0xd2, 0x14, 0x2f, 0xd0, 0x74, 0x0d, 0x66, 0x3a, 0xe7, 0xd0, 0xd7, 0x6e, 0x1f, 0xed, 0x73, - 0xd5, 0x13, 0xe1, 0x2d, 0x51, 0xf0, 0x6d, 0x49, 0xc1, 0x4b, 0x2d, 0xe7, 0xb1, 0x99, 0x43, 0xc0, 0xa7, 0x51, 0x25, - 0x7a, 0x27, 0xc5, 0x05, 0x68, 0x33, 0xe1, 0x08, 0x34, 0x55, 0x23, 0xb6, 0x72, 0x80, 0xdb, 0x8b, 0xa7, 0x01, 0xa1, - 0x20, 0xd5, 0x5d, 0xdb, 0x15, 0x79, 0xc3, 0x8e, 0x36, 0x37, 0x60, 0x26, 0x5c, 0xad, 0xcb, 0xd6, 0x57, 0x36, 0xd9, - 0x7d, 0x5c, 0x13, 0x6c, 0xbb, 0xb7, 0x41, 0xc2, 0x1b, 0x7a, 0x4d, 0x36, 0xd7, 0xfd, 0x7e, 0x08, 0xfd, 0x21, 0x54, - 0x77, 0xe8, 0xa6, 0xb3, 0x43, 0x37, 0x5e, 0x3b, 0xcf, 0xac, 0x9e, 0x4f, 0x79, 0x87, 0xbc, 0x47, 0x93, 0x35, 0xba, - 0x8a, 0x6f, 0x61, 0x53, 0x47, 0x15, 0x55, 0x95, 0x47, 0x09, 0x05, 0x95, 0x78, 0xc6, 0xcb, 0x13, 0x8e, 0xb1, 0x5e, - 0xf5, 0xd3, 0x5b, 0xcd, 0xab, 0xad, 0xcd, 0xda, 0x2c, 0xd7, 0x67, 0x60, 0x21, 0x71, 0xc6, 0xa3, 0x4b, 0x4d, 0x4b, - 0x2e, 0x7c, 0x28, 0x4d, 0x1c, 0x95, 0xe0, 0x3c, 0xce, 0x72, 0x50, 0xe3, 0x9e, 0x37, 0xfb, 0x1f, 0x6a, 0xdb, 0xb1, - 0x65, 0xe3, 0xcc, 0xbd, 0x0a, 0xc9, 0xe6, 0x7f, 0x6c, 0xa0, 0x5e, 0x85, 0x18, 0x21, 0xd6, 0x2c, 0xe8, 0x37, 0x0c, - 0x62, 0x85, 0x06, 0xe5, 0x3a, 0x49, 0x78, 0x59, 0x06, 0x46, 0xa9, 0xb5, 0x66, 0x6b, 0x73, 0x9e, 0x3d, 0x60, 0x47, - 0x0f, 0x7a, 0x8c, 0xdd, 0x10, 0x9a, 0x68, 0x9d, 0x90, 0xa9, 0x31, 0xf2, 0xb4, 0x40, 0xba, 0x43, 0x51, 0x76, 0x1e, - 0xbe, 0x41, 0x21, 0x4b, 0x7b, 0x9f, 0x9b, 0x13, 0x59, 0x7d, 0xa3, 0x8d, 0x50, 0x22, 0x95, 0x08, 0xb2, 0xf1, 0x6b, - 0x04, 0x30, 0x86, 0x66, 0x07, 0x64, 0xb3, 0x64, 0x27, 0xf4, 0xd4, 0x9a, 0x04, 0xc1, 0xeb, 0x37, 0x2a, 0xd1, 0x8c, - 0xb2, 0x22, 0xba, 0xca, 0xe8, 0xe7, 0x36, 0x24, 0xd1, 0x69, 0x48, 0xfc, 0xdc, 0xb0, 0xb4, 0xae, 0x42, 0x14, 0x33, - 0x9b, 0x0d, 0xaf, 0x15, 0x51, 0x8d, 0x6d, 0x65, 0x7c, 0xcc, 0x6f, 0x6c, 0x1a, 0x99, 0x42, 0x5f, 0x87, 0x93, 0x7e, - 0x1f, 0xfe, 0x6a, 0xfa, 0x81, 0xb7, 0x14, 0xfc, 0xc5, 0x1e, 0x90, 0x3a, 0x61, 0x01, 0xc0, 0x33, 0xe6, 0xbc, 0x6a, - 0x4e, 0xe0, 0x03, 0x76, 0xb4, 0x79, 0x10, 0x9e, 0x34, 0x66, 0xee, 0x36, 0xc4, 0x4b, 0x55, 0xd2, 0xf3, 0xe6, 0xc9, - 0x0c, 0xc4, 0xca, 0x6a, 0xcd, 0x6f, 0x98, 0xd5, 0x27, 0x00, 0x91, 0xba, 0xb1, 0x0e, 0xb6, 0xf8, 0xb1, 0xe9, 0x32, - 0xd9, 0xa4, 0xac, 0xcd, 0x44, 0x29, 0x15, 0x49, 0x73, 0x11, 0x40, 0xbf, 0x61, 0x38, 0x6a, 0x80, 0x3b, 0xd7, 0x63, - 0x6f, 0x86, 0xc6, 0x1b, 0x53, 0x43, 0xcf, 0x36, 0x7a, 0x79, 0x3b, 0x0a, 0x61, 0xc6, 0x22, 0xba, 0x71, 0xc7, 0x62, - 0x78, 0x42, 0xdf, 0x40, 0x85, 0xaf, 0x42, 0x8c, 0x2e, 0x4c, 0xea, 0x7a, 0xba, 0x56, 0x5b, 0xe9, 0x9a, 0xd0, 0x1c, - 0xa3, 0x1a, 0x79, 0x6d, 0xbb, 0xa5, 0x46, 0x68, 0x4f, 0x28, 0x0f, 0x6f, 0x68, 0x45, 0xaf, 0x2d, 0x8b, 0xe0, 0xe4, - 0xc7, 0x5e, 0x7e, 0x42, 0xcf, 0x3c, 0x81, 0x49, 0xd1, 0xd6, 0x00, 0x7e, 0x40, 0xfd, 0x70, 0x56, 0x4f, 0xad, 0x94, - 0xc3, 0x53, 0xf8, 0x92, 0x0d, 0xc8, 0x15, 0xf4, 0x62, 0x8d, 0xd9, 0x51, 0x0c, 0x3a, 0xa8, 0x9d, 0xdd, 0xe1, 0x4d, - 0x4a, 0x19, 0xa2, 0x35, 0xa2, 0x83, 0xbc, 0xfa, 0x15, 0x34, 0x7d, 0x90, 0x16, 0xa6, 0x74, 0x8d, 0x02, 0x1e, 0xd0, - 0x37, 0xf5, 0xfb, 0x39, 0x3e, 0xd7, 0x9e, 0x65, 0x9a, 0xb2, 0x40, 0x26, 0x74, 0xe9, 0xc5, 0xed, 0x02, 0x69, 0xb3, - 0x63, 0x15, 0x80, 0x15, 0x49, 0xa0, 0x11, 0x09, 0x58, 0x2e, 0x79, 0xe2, 0xb2, 0x0d, 0x1a, 0xd4, 0x44, 0x25, 0x85, - 0x2c, 0x91, 0x04, 0x7e, 0x18, 0x41, 0x99, 0xa2, 0x18, 0xc4, 0xbd, 0x7a, 0x79, 0xc5, 0x35, 0x35, 0x60, 0x4d, 0x11, - 0x4c, 0xb0, 0x4e, 0xa7, 0x40, 0x6c, 0xc5, 0x7a, 0x05, 0x9e, 0xa8, 0xee, 0x22, 0x89, 0x2c, 0x01, 0x1a, 0xe8, 0xf9, - 0xd2, 0x69, 0xb7, 0xbc, 0x3d, 0xd1, 0x52, 0xc5, 0xe6, 0xde, 0x8b, 0x85, 0xe5, 0x1e, 0x2b, 0x7f, 0x3b, 0xd0, 0x5e, - 0x58, 0xed, 0x88, 0xa8, 0xc1, 0xea, 0xb0, 0x6d, 0xe7, 0x87, 0xd2, 0x50, 0xdd, 0x2b, 0xc7, 0x04, 0x54, 0x74, 0x15, - 0x57, 0xcb, 0x28, 0x1b, 0xc1, 0x9f, 0xed, 0x36, 0xd8, 0x0f, 0xc0, 0x22, 0xf4, 0xc3, 0xbb, 0x9f, 0x22, 0x0c, 0x57, - 0xf5, 0xe1, 0xdd, 0x4f, 0xdb, 0xed, 0x93, 0xf1, 0xd8, 0x70, 0x05, 0x4e, 0xad, 0x03, 0xfc, 0x81, 0x61, 0x1b, 0xec, - 0x92, 0xdd, 0x6e, 0x9f, 0x00, 0x07, 0xa1, 0xd8, 0x06, 0xb3, 0x8b, 0x95, 0x63, 0x9b, 0x62, 0x35, 0xf4, 0x8e, 0x04, - 0xec, 0xbe, 0x1d, 0x96, 0x62, 0x97, 0xfa, 0xa8, 0x90, 0x94, 0x7a, 0xd1, 0x3f, 0xef, 0x14, 0x58, 0x52, 0x30, 0xe5, - 0x0d, 0x96, 0x55, 0xb5, 0x2a, 0xa3, 0xfd, 0xfd, 0x78, 0x95, 0x8d, 0xca, 0x0c, 0xb6, 0x79, 0x79, 0x75, 0x01, 0x00, - 0x13, 0x01, 0x6d, 0xbc, 0x5b, 0x8b, 0xcc, 0xbc, 0x58, 0xd0, 0x65, 0x86, 0x6b, 0x12, 0xcc, 0x0e, 0x72, 0x6e, 0x75, - 0x93, 0x53, 0x62, 0x1f, 0xc0, 0x06, 0x73, 0xbb, 0x6d, 0xf0, 0x0b, 0x47, 0xa3, 0x27, 0xb3, 0x65, 0xa6, 0x0d, 0x5c, - 0xb9, 0xd9, 0xff, 0x24, 0xf2, 0xd2, 0x50, 0xf1, 0x49, 0xa6, 0xcf, 0x33, 0xe0, 0xf3, 0xd8, 0x27, 0x11, 0xfa, 0x2c, - 0x57, 0xa3, 0x35, 0xc0, 0xc6, 0x66, 0xe7, 0xb7, 0xa3, 0x94, 0x43, 0x84, 0x8e, 0xc0, 0xaa, 0x6b, 0x96, 0x19, 0xf1, - 0x6d, 0x2a, 0x6e, 0x5a, 0xaa, 0xb0, 0x4f, 0xc2, 0x73, 0xde, 0xe1, 0xc6, 0x71, 0xa8, 0x37, 0x89, 0xc2, 0xe7, 0x28, - 0x44, 0xe5, 0x68, 0x5c, 0xe8, 0xe4, 0x6b, 0x99, 0xc7, 0x84, 0x62, 0x0e, 0xf7, 0xee, 0xf7, 0xd4, 0x99, 0xcb, 0xf8, - 0xc2, 0xbd, 0xe7, 0xbe, 0xcc, 0xe4, 0x4a, 0x02, 0x48, 0x94, 0xaa, 0xfd, 0xe7, 0xcf, 0x48, 0x8d, 0xff, 0x4e, 0xb5, - 0x06, 0xa0, 0xf7, 0x33, 0xd4, 0xe4, 0x08, 0x02, 0xb6, 0x62, 0xea, 0x47, 0x17, 0xb0, 0x92, 0xf9, 0x9f, 0x50, 0xb7, - 0x23, 0xd8, 0x46, 0xc5, 0x13, 0x8a, 0x2a, 0x5a, 0xf0, 0x74, 0x2d, 0xd2, 0x58, 0x24, 0xb7, 0x11, 0xaf, 0xa7, 0x58, - 0x12, 0xb3, 0x11, 0xc3, 0x7e, 0x6e, 0x76, 0xe1, 0x5d, 0xd1, 0x30, 0x89, 0xa7, 0xa5, 0xbf, 0xad, 0xbc, 0xcd, 0x64, - 0x19, 0x67, 0x64, 0xca, 0x15, 0x82, 0xb9, 0xd5, 0xf7, 0x98, 0x13, 0xfc, 0xf1, 0xc1, 0x63, 0x42, 0xaf, 0xe4, 0xb4, - 0x44, 0x90, 0x3e, 0x91, 0x5a, 0xd7, 0x55, 0xec, 0xd7, 0x14, 0xa2, 0x5a, 0x08, 0x06, 0xa1, 0x4c, 0x4d, 0xfb, 0x14, - 0xdf, 0x67, 0xcb, 0xfe, 0x64, 0xca, 0x96, 0x64, 0x23, 0xa0, 0x63, 0xd2, 0x79, 0xbf, 0x7a, 0x7b, 0x76, 0xe6, 0xfd, - 0x06, 0x4d, 0x38, 0xa8, 0x6e, 0xa0, 0x5d, 0x05, 0x99, 0xc6, 0x28, 0x36, 0x8b, 0xb1, 0x76, 0x6b, 0x22, 0x82, 0x20, - 0xdc, 0xe5, 0x2c, 0x6c, 0xb7, 0x13, 0xe2, 0x6d, 0x20, 0x81, 0x02, 0xd7, 0x36, 0xca, 0x49, 0x48, 0xd4, 0x85, 0xcc, - 0x1c, 0x13, 0x92, 0x05, 0x7a, 0x8d, 0x1d, 0x04, 0xf4, 0x98, 0xdb, 0xa7, 0x80, 0xbe, 0x28, 0xd8, 0x31, 0x1f, 0x04, - 0x43, 0x8c, 0x37, 0x1b, 0xd0, 0x8f, 0x52, 0x3d, 0x82, 0xc7, 0x34, 0xb0, 0x5c, 0xf4, 0x75, 0xc1, 0x10, 0x66, 0xe9, - 0xb7, 0x94, 0x4d, 0xbe, 0xf9, 0xa7, 0x9b, 0xdf, 0x33, 0x2d, 0x66, 0x07, 0xa1, 0xb8, 0xbd, 0x9e, 0x00, 0xf1, 0xab, - 0xf8, 0x25, 0x58, 0x9b, 0x6b, 0x89, 0xb7, 0x27, 0x79, 0x10, 0xbe, 0x1c, 0xdd, 0x7e, 0x52, 0x9a, 0x4f, 0x20, 0x68, - 0x8f, 0x93, 0x94, 0xbb, 0xef, 0x4e, 0xa4, 0xab, 0x08, 0x46, 0x0b, 0x10, 0xfc, 0xee, 0xac, 0xe4, 0xb4, 0x29, 0xfc, - 0xc7, 0x3a, 0x5f, 0x60, 0x2c, 0x15, 0x79, 0x82, 0xd3, 0xdf, 0x04, 0x07, 0xf7, 0x6f, 0x65, 0xd6, 0x90, 0xe8, 0x4c, - 0x7d, 0x04, 0xf4, 0x7f, 0xac, 0xc7, 0xef, 0x18, 0x25, 0x7d, 0x49, 0x9c, 0x23, 0x7c, 0x13, 0x2f, 0xd1, 0x74, 0xb1, - 0x37, 0xae, 0xe9, 0xa7, 0xc2, 0xbc, 0xd0, 0x0a, 0x0e, 0xfb, 0xd6, 0x28, 0x3c, 0xf0, 0xcc, 0xfb, 0x4e, 0x34, 0x04, - 0xdd, 0xff, 0xc2, 0xbd, 0xf1, 0x9d, 0x60, 0x19, 0xde, 0x94, 0xb3, 0xcc, 0xdc, 0xe1, 0xae, 0x33, 0x91, 0xca, 0x6b, - 0xc6, 0x82, 0xb5, 0x50, 0xe6, 0xbc, 0x69, 0x30, 0xdb, 0xd4, 0x91, 0x4a, 0x76, 0xdf, 0xff, 0xd5, 0x38, 0x61, 0xb3, - 0x41, 0x70, 0x52, 0xc9, 0x22, 0xbe, 0xe0, 0xc1, 0x54, 0xab, 0x28, 0x32, 0xb0, 0x2b, 0x04, 0xa4, 0x1c, 0xa7, 0xbd, - 0x83, 0x27, 0x4b, 0xcd, 0x4c, 0xc8, 0x6f, 0xab, 0xb3, 0x80, 0xb7, 0x66, 0x34, 0x8f, 0x2b, 0xd8, 0x65, 0xbe, 0x92, - 0xe2, 0xbb, 0x96, 0x24, 0x1b, 0xeb, 0x6f, 0xc8, 0xb0, 0xad, 0x7c, 0xe6, 0x0c, 0x30, 0x77, 0x3e, 0x4a, 0x15, 0xf4, - 0xaf, 0xc7, 0xd8, 0xb5, 0x44, 0x22, 0x20, 0x9c, 0xc5, 0xc4, 0xad, 0x30, 0xe1, 0x30, 0x5d, 0xa0, 0xa0, 0x18, 0x03, - 0x05, 0x9d, 0xc8, 0x90, 0xd3, 0x63, 0x3e, 0x48, 0x1a, 0xb3, 0xf5, 0x97, 0x2a, 0x91, 0x5e, 0x4b, 0x42, 0x4f, 0xe1, - 0xf7, 0xb8, 0xc5, 0x03, 0x35, 0x82, 0x75, 0xba, 0x9b, 0xd3, 0xfe, 0xeb, 0x82, 0x0c, 0x7f, 0x03, 0x6f, 0xb7, 0xd8, - 0x5e, 0x96, 0x13, 0x58, 0xdc, 0xb1, 0x57, 0x3c, 0xcd, 0x55, 0x8b, 0x13, 0xe2, 0x11, 0x8b, 0xdc, 0x27, 0x16, 0x30, - 0xa2, 0x86, 0xd1, 0xf8, 0xf1, 0xe4, 0xcd, 0x6b, 0x8d, 0x61, 0x95, 0xfb, 0x1f, 0xc0, 0x88, 0x6a, 0x69, 0xbb, 0x1d, - 0xf0, 0xe5, 0x08, 0x0d, 0xd8, 0x53, 0x37, 0xd8, 0xfd, 0xbe, 0x49, 0x3b, 0x2a, 0xbd, 0x6c, 0x4e, 0x0c, 0xba, 0xa3, - 0xb4, 0x59, 0x2a, 0x03, 0xe3, 0xae, 0xc2, 0xd1, 0x9c, 0xd8, 0x88, 0x55, 0xbd, 0x0f, 0xc3, 0x25, 0x8d, 0xad, 0xac, - 0xdc, 0xee, 0x26, 0x1c, 0xd9, 0x04, 0xb8, 0x3e, 0x05, 0xed, 0xd5, 0x9c, 0x83, 0x16, 0x94, 0x28, 0x70, 0x44, 0xdb, - 0x6d, 0x08, 0x11, 0x49, 0x8a, 0xe1, 0x64, 0x16, 0x16, 0xc3, 0xa1, 0x1a, 0xf8, 0x82, 0x90, 0xe8, 0x53, 0x31, 0xcf, - 0x16, 0x0a, 0xc1, 0xc8, 0xdf, 0x49, 0xbf, 0x14, 0x8a, 0x53, 0xee, 0x7d, 0x27, 0xc8, 0xe6, 0x5f, 0x29, 0xc6, 0x60, - 0x74, 0x9a, 0xcd, 0x0c, 0x24, 0xac, 0xc7, 0x15, 0x51, 0xeb, 0xc8, 0xce, 0x06, 0xa8, 0x62, 0xd1, 0x34, 0x18, 0xd4, - 0x2d, 0x9e, 0x58, 0xcf, 0xe8, 0x3d, 0xa8, 0x04, 0x51, 0x2d, 0xd8, 0x8d, 0xe1, 0x5a, 0x7b, 0x2d, 0x42, 0x49, 0x39, - 0x69, 0x32, 0x33, 0x56, 0x34, 0x58, 0x80, 0x90, 0x34, 0x2e, 0xab, 0x57, 0x32, 0xcd, 0xce, 0x33, 0x40, 0x90, 0x70, - 0xfe, 0x84, 0xb2, 0xf1, 0xe6, 0xa9, 0x9a, 0x97, 0xae, 0xc4, 0x99, 0x85, 0x3d, 0xe9, 0x7a, 0x4b, 0x0b, 0x12, 0x15, - 0x40, 0xa3, 0x7c, 0x2d, 0xcf, 0xf7, 0x3b, 0x56, 0x21, 0xbb, 0x1f, 0x4e, 0x95, 0xed, 0x10, 0x3f, 0x62, 0x15, 0xf1, - 0x4e, 0xeb, 0x4a, 0x89, 0x34, 0x3a, 0xda, 0x06, 0xc4, 0xb0, 0x65, 0xdf, 0xa2, 0x86, 0x0f, 0xc2, 0x2e, 0x3a, 0xc9, - 0x0f, 0x7a, 0x8a, 0xc7, 0xd6, 0x40, 0xd2, 0xd7, 0x22, 0xf8, 0x1a, 0x1d, 0xe9, 0x44, 0x99, 0x46, 0x62, 0x0a, 0x89, - 0x7e, 0xbd, 0xd0, 0x1a, 0xcb, 0x28, 0xfb, 0x8a, 0xfc, 0x9f, 0x75, 0xf7, 0xbe, 0x13, 0xdb, 0x2d, 0x4c, 0xb2, 0xe7, - 0x81, 0x06, 0x9b, 0x1a, 0xb5, 0x42, 0x38, 0x3b, 0xc7, 0x15, 0x6a, 0xc7, 0x7a, 0x61, 0x09, 0xe4, 0x01, 0x6c, 0x45, - 0x1a, 0x94, 0x41, 0xb2, 0x4f, 0xc5, 0x5c, 0x2c, 0x9c, 0x28, 0x47, 0x2a, 0xfc, 0x33, 0x39, 0x4a, 0x39, 0x5c, 0xc5, - 0xc2, 0x82, 0x21, 0xbf, 0x3a, 0x3a, 0x2f, 0xe4, 0x25, 0x48, 0x4a, 0x0c, 0x43, 0x65, 0x79, 0x5d, 0x5c, 0xb5, 0x25, - 0xa1, 0xbd, 0x53, 0x00, 0xa5, 0x29, 0x40, 0xf0, 0xd2, 0xa8, 0x21, 0x66, 0x1b, 0xb5, 0xbb, 0xa2, 0x3b, 0xc9, 0x01, - 0x75, 0xba, 0x6b, 0xb7, 0xde, 0x94, 0xad, 0xba, 0x15, 0x17, 0xfe, 0x05, 0xa5, 0x1f, 0xf3, 0x41, 0xe1, 0x53, 0x09, - 0xdc, 0xf8, 0x6a, 0x93, 0x65, 0xe7, 0xb7, 0xb8, 0xf4, 0xab, 0xc6, 0xf8, 0xf5, 0xfb, 0x3d, 0xb5, 0x10, 0x1a, 0xa9, - 0xc0, 0x7c, 0xfb, 0xcc, 0x54, 0x65, 0x34, 0xa5, 0xf6, 0x12, 0x5c, 0x39, 0xfb, 0x11, 0x54, 0xc4, 0x75, 0x45, 0x6a, - 0x53, 0x03, 0xb4, 0xe7, 0x65, 0x85, 0x5b, 0x59, 0x80, 0xc7, 0x4e, 0x40, 0xb6, 0x5b, 0x1e, 0x06, 0xfa, 0xd0, 0x09, - 0xfc, 0x2d, 0xf9, 0x0a, 0x99, 0x35, 0xfb, 0xf8, 0x87, 0x16, 0xfc, 0x63, 0x0b, 0x7e, 0x42, 0x71, 0xa7, 0x95, 0xf9, - 0xb7, 0xd2, 0xba, 0xc5, 0xfd, 0x3b, 0x99, 0x26, 0x14, 0x95, 0x09, 0xb5, 0x5f, 0xe9, 0x8f, 0x26, 0x78, 0x94, 0xca, - 0xfe, 0x5e, 0xc2, 0x07, 0xb3, 0xc6, 0x13, 0x6b, 0x3c, 0x19, 0x4e, 0xb7, 0xd2, 0xb0, 0x0c, 0x28, 0xf4, 0xf3, 0x32, - 0x57, 0x54, 0x3f, 0xff, 0xbc, 0xe6, 0x6b, 0xde, 0x6c, 0xb1, 0x4d, 0xba, 0xa7, 0xc1, 0x5e, 0x1e, 0x4d, 0x29, 0x9c, - 0x44, 0x9d, 0x1b, 0x89, 0xba, 0xa8, 0x59, 0x86, 0xea, 0x04, 0xaf, 0xe6, 0xa9, 0x1e, 0xf6, 0x66, 0x22, 0x5a, 0x2b, - 0x29, 0x4b, 0x0c, 0x58, 0xeb, 0xc8, 0x43, 0x72, 0xb7, 0xd6, 0x71, 0xa7, 0xa1, 0x2e, 0x4d, 0xa1, 0x26, 0x58, 0xe1, - 0x02, 0x1c, 0x41, 0xef, 0x8a, 0x90, 0xc3, 0x35, 0x55, 0xe9, 0x17, 0x34, 0x25, 0x4f, 0x3c, 0x45, 0xad, 0x56, 0xa4, - 0xdb, 0x8f, 0x72, 0xec, 0x86, 0x6f, 0x9c, 0x90, 0x13, 0x23, 0xf4, 0x77, 0xc7, 0x52, 0xce, 0xd0, 0xe2, 0x41, 0x9d, - 0x60, 0xbd, 0xbc, 0xa5, 0x40, 0x31, 0x47, 0x97, 0x55, 0xd7, 0xbc, 0x44, 0xdb, 0x97, 0x65, 0xbf, 0x9f, 0xdb, 0x7a, - 0x52, 0x76, 0xb4, 0x59, 0x9a, 0x7d, 0x88, 0x8a, 0x29, 0xdc, 0xf5, 0x89, 0xe6, 0xaf, 0x42, 0x7d, 0xd5, 0x96, 0x39, - 0x1f, 0x71, 0xc4, 0x09, 0xc9, 0x49, 0xfd, 0x87, 0x9a, 0x7a, 0x25, 0xee, 0x57, 0x95, 0xfc, 0x22, 0x8c, 0x15, 0xa3, - 0x25, 0x86, 0x28, 0xd2, 0xee, 0x8d, 0xe9, 0xcb, 0x02, 0xe0, 0xaf, 0x04, 0xfb, 0x94, 0x86, 0x5a, 0xf9, 0x2d, 0xda, - 0x02, 0xfe, 0x8d, 0xe2, 0x06, 0xac, 0x02, 0x03, 0x8c, 0x26, 0xdb, 0x73, 0x9a, 0xc0, 0x01, 0x27, 0xb4, 0x8a, 0x82, - 0x0a, 0x33, 0x34, 0xd4, 0x16, 0x46, 0x5f, 0xa1, 0x8c, 0x5b, 0x65, 0xf6, 0x6e, 0x8c, 0x9d, 0x16, 0x78, 0x0d, 0xff, - 0x46, 0x2f, 0x14, 0xb3, 0x51, 0x07, 0xe9, 0xd1, 0x49, 0x4c, 0x7f, 0xdc, 0xc2, 0xc9, 0xcd, 0xc2, 0x59, 0xd6, 0x2c, - 0x81, 0xee, 0xc0, 0x05, 0x31, 0xee, 0xf7, 0x73, 0x38, 0x32, 0xcd, 0xc8, 0x17, 0x2c, 0xa7, 0x31, 0x5b, 0x52, 0xed, - 0x79, 0x78, 0x51, 0x85, 0x39, 0x5d, 0x5a, 0x19, 0x6f, 0xca, 0x40, 0x65, 0xb4, 0xdd, 0x86, 0xf0, 0xa7, 0xdb, 0xda, - 0x25, 0x9d, 0x2f, 0x21, 0x03, 0xfc, 0x01, 0x89, 0x28, 0x62, 0x81, 0xff, 0x5b, 0x8d, 0x53, 0x7a, 0xa2, 0xb4, 0x66, - 0x09, 0x5d, 0x33, 0x5d, 0x3f, 0x3d, 0x67, 0xeb, 0xc6, 0x52, 0xd8, 0x6e, 0xc3, 0x66, 0x02, 0xd3, 0x9c, 0x2b, 0x99, - 0x9e, 0xa3, 0x4e, 0x0a, 0xa8, 0x58, 0x78, 0x8e, 0xcb, 0x2f, 0x25, 0x14, 0x9a, 0x3b, 0x5f, 0x2e, 0x8c, 0x12, 0x13, - 0x5a, 0x25, 0xbf, 0x7c, 0xa8, 0xcc, 0xd7, 0xc6, 0x43, 0xf0, 0xc7, 0x34, 0x4c, 0x4c, 0x91, 0xa8, 0x10, 0x9d, 0xfd, - 0x02, 0xb2, 0x1c, 0x01, 0xb8, 0x9e, 0xaf, 0x20, 0x0a, 0xdc, 0x1a, 0xe2, 0xc2, 0x43, 0x83, 0xde, 0x16, 0xf2, 0x32, - 0x2b, 0x79, 0x88, 0xf7, 0x04, 0x4f, 0x33, 0x7a, 0xb7, 0xc1, 0x87, 0xb6, 0xf6, 0xe8, 0x09, 0xb2, 0xf1, 0x94, 0xfb, - 0xf5, 0x2f, 0x22, 0x9c, 0x43, 0xf4, 0xce, 0x05, 0xd5, 0xea, 0x6a, 0x07, 0xc8, 0xe5, 0xd9, 0x5e, 0x3d, 0x80, 0xd3, - 0x4d, 0x5f, 0xdf, 0xaa, 0xd0, 0x99, 0x03, 0x48, 0x7b, 0x48, 0xd6, 0x35, 0xd7, 0x3b, 0xc0, 0x3b, 0x12, 0xd7, 0x40, - 0x63, 0xdd, 0xd6, 0xec, 0xb4, 0x47, 0xf1, 0x98, 0xc8, 0xcc, 0x58, 0xa4, 0x18, 0x73, 0xb7, 0x4e, 0x8b, 0xa2, 0x0d, - 0x9a, 0x21, 0xec, 0xde, 0x75, 0xb2, 0x75, 0x2b, 0xe2, 0xfc, 0xdd, 0xb6, 0x2f, 0x30, 0x1a, 0xc6, 0x5c, 0xbb, 0xe7, - 0x1b, 0xba, 0xad, 0xdd, 0xc8, 0x68, 0x24, 0xc8, 0x4c, 0x1d, 0x88, 0xb2, 0xb6, 0x06, 0x6c, 0x0f, 0xb8, 0xde, 0xb4, - 0xc0, 0xcf, 0x9b, 0x18, 0xbc, 0x3d, 0x6b, 0x9c, 0xd2, 0xfa, 0x1a, 0xd7, 0x1c, 0x57, 0x85, 0x88, 0xda, 0x22, 0x05, - 0xc0, 0xb0, 0xf3, 0x05, 0xee, 0xcc, 0x0a, 0x83, 0x39, 0x61, 0xa9, 0x64, 0xa7, 0x72, 0xfd, 0x39, 0x6c, 0x71, 0x90, - 0xca, 0x97, 0x5e, 0x7f, 0xff, 0xf0, 0xc5, 0x17, 0xe8, 0xb6, 0xe7, 0xfc, 0x08, 0x82, 0x4c, 0xa0, 0x83, 0x9a, 0x52, - 0x3d, 0xfe, 0x50, 0x00, 0xb5, 0x87, 0x79, 0xf8, 0xa1, 0x60, 0x22, 0xbe, 0xca, 0x2e, 0xe2, 0x4a, 0x16, 0xa3, 0x2b, - 0x2e, 0x52, 0x59, 0x58, 0xa9, 0x71, 0x70, 0xbc, 0x5a, 0xe5, 0x3c, 0x00, 0x53, 0x79, 0xcb, 0x28, 0x3b, 0xb9, 0xa4, - 0x1e, 0x5c, 0x2d, 0x4f, 0xaf, 0xb4, 0xe8, 0xbc, 0xbc, 0xba, 0x08, 0x22, 0xfc, 0x75, 0x66, 0x7e, 0x5c, 0xc6, 0xe5, - 0xc7, 0x20, 0xb2, 0x36, 0x75, 0xe6, 0x07, 0x4a, 0xe5, 0xc1, 0xdf, 0x09, 0x64, 0xba, 0x3f, 0x14, 0x60, 0x99, 0x6d, - 0x2b, 0x3e, 0x8c, 0xb1, 0xd6, 0xe1, 0x84, 0xcc, 0x54, 0x89, 0xde, 0xbb, 0x64, 0x5d, 0x80, 0xb5, 0x9f, 0xc2, 0x76, - 0x56, 0xb9, 0x66, 0x58, 0x99, 0xaa, 0xc8, 0x10, 0xb4, 0x35, 0xdb, 0x0f, 0xad, 0x13, 0xcd, 0x1c, 0xbd, 0x05, 0xf4, - 0x03, 0xd9, 0xbf, 0xa0, 0x72, 0xcd, 0x3c, 0x1f, 0x9b, 0xc6, 0xeb, 0x07, 0xfb, 0x17, 0x9e, 0x40, 0xc9, 0xde, 0xc9, - 0x51, 0x98, 0x08, 0x9e, 0xc6, 0x66, 0x7c, 0x91, 0x67, 0x05, 0xec, 0xa0, 0xc9, 0x78, 0x4c, 0xbd, 0xa5, 0xd5, 0xba, - 0x39, 0x3a, 0x64, 0xdb, 0xec, 0x61, 0xf5, 0x90, 0x93, 0x7d, 0xde, 0x32, 0xb5, 0x6d, 0x5b, 0xc7, 0x79, 0x9a, 0x7c, - 0x65, 0xba, 0x5f, 0xae, 0x6d, 0x84, 0x78, 0xe5, 0xec, 0xe8, 0xbc, 0xa4, 0x5b, 0xdf, 0x94, 0x86, 0x5e, 0x4b, 0x00, - 0xe6, 0xd3, 0x06, 0xfc, 0x05, 0x93, 0xeb, 0x51, 0xc5, 0xcb, 0x0a, 0x24, 0x2c, 0x28, 0xc2, 0x9b, 0x62, 0x6f, 0x0a, - 0x77, 0xe3, 0xf4, 0x1c, 0x76, 0xe0, 0x62, 0x8a, 0xee, 0x38, 0x31, 0x99, 0x95, 0x46, 0x2b, 0x1a, 0xe9, 0x5f, 0xae, - 0x2f, 0xb1, 0xee, 0x8b, 0x56, 0xe6, 0xd9, 0x9c, 0x0a, 0x9b, 0xde, 0x55, 0x2e, 0x9d, 0xa8, 0xdf, 0x32, 0xe1, 0xca, - 0x95, 0x20, 0x20, 0xd3, 0x82, 0xf5, 0x0a, 0xb3, 0x8b, 0x0a, 0x24, 0x64, 0x60, 0xf8, 0x1a, 0xac, 0x45, 0xc9, 0x8d, - 0x15, 0xac, 0x77, 0xcf, 0xd7, 0x09, 0x42, 0x0a, 0x1e, 0xb8, 0x09, 0xfa, 0xd0, 0xba, 0x79, 0x3b, 0x4a, 0x94, 0x41, - 0x7c, 0x72, 0xed, 0x94, 0x83, 0x04, 0x02, 0x70, 0x60, 0x55, 0x48, 0x12, 0x05, 0x3a, 0x0f, 0xae, 0x66, 0x1c, 0xc1, - 0xe6, 0x95, 0x33, 0x17, 0x37, 0x80, 0xf3, 0xca, 0x9f, 0xcb, 0x06, 0x5b, 0xd6, 0x23, 0xaa, 0xcc, 0x19, 0xa7, 0x18, - 0xd4, 0xc9, 0x12, 0xf4, 0x95, 0xa5, 0xb4, 0x17, 0xa0, 0x69, 0xbc, 0x64, 0x2b, 0xe5, 0x03, 0x40, 0xcf, 0xd8, 0x4a, - 0x19, 0xfb, 0xe3, 0xd7, 0xa7, 0x6c, 0xa5, 0xa5, 0xc1, 0xd3, 0xcb, 0xd9, 0xd9, 0xec, 0x74, 0xc0, 0x0e, 0xa2, 0x50, - 0x1b, 0x30, 0x04, 0x2e, 0x32, 0x41, 0x30, 0x08, 0x35, 0xfe, 0xcb, 0x40, 0x05, 0x08, 0x23, 0x1e, 0x8f, 0x8d, 0x38, - 0x62, 0xe1, 0x78, 0x88, 0xc1, 0xc0, 0x9a, 0x2f, 0x48, 0x40, 0xa8, 0x29, 0x0d, 0x7d, 0x3d, 0xc3, 0xe1, 0x64, 0x6f, - 0x02, 0xa9, 0x98, 0x99, 0xa9, 0xc2, 0xd8, 0x98, 0x44, 0x10, 0xff, 0xb5, 0xb3, 0x5e, 0x28, 0xb7, 0xbb, 0x46, 0x03, - 0x41, 0x33, 0xf8, 0xa2, 0x8a, 0x27, 0x7b, 0xc3, 0xae, 0x8a, 0x71, 0x14, 0xae, 0x8c, 0xf2, 0xed, 0xf4, 0x10, 0xc0, - 0x7c, 0x4f, 0x87, 0xbe, 0x5c, 0xe2, 0x74, 0xff, 0x31, 0x79, 0xf8, 0x98, 0xd0, 0x53, 0x76, 0xfa, 0xd5, 0x63, 0x7a, - 0xaa, 0xc8, 0xc9, 0xde, 0x24, 0xba, 0x62, 0x16, 0x03, 0xe7, 0x40, 0x35, 0x81, 0x5e, 0x8c, 0xd6, 0x42, 0x2d, 0x30, - 0xed, 0xd0, 0x14, 0x7e, 0x3b, 0xde, 0x0b, 0x06, 0x57, 0xed, 0xa6, 0x5f, 0xb5, 0xdb, 0xea, 0x79, 0x75, 0xed, 0x1d, - 0x44, 0xbb, 0xc5, 0x4c, 0xfe, 0x39, 0xde, 0x73, 0x73, 0x80, 0xf5, 0xdd, 0x3f, 0x26, 0xa6, 0x49, 0x3b, 0xa3, 0xe2, - 0xd7, 0xf4, 0x08, 0xfb, 0xd0, 0x2c, 0xb2, 0xa3, 0x0f, 0xc3, 0x7f, 0xab, 0x13, 0xf5, 0xe9, 0x57, 0x07, 0x40, 0x8e, - 0x40, 0x06, 0x8a, 0x25, 0x82, 0x19, 0x0e, 0x34, 0x05, 0x14, 0x64, 0x7a, 0xdc, 0xa9, 0x1e, 0x7e, 0x35, 0x6a, 0x6a, - 0x46, 0xae, 0x60, 0x6a, 0xb0, 0x2d, 0xf8, 0x81, 0xea, 0x86, 0xfe, 0x46, 0xa3, 0x3d, 0x69, 0x27, 0x33, 0xf3, 0x92, - 0xda, 0x38, 0x77, 0x57, 0x10, 0xd0, 0xd9, 0xc1, 0x2d, 0x4a, 0xf6, 0xf5, 0xe1, 0xc5, 0x1e, 0xae, 0x22, 0x40, 0x0d, - 0x63, 0xc1, 0xd7, 0x83, 0x0b, 0xbd, 0xb9, 0xf7, 0x02, 0x32, 0xf8, 0x3a, 0x38, 0xfa, 0x7a, 0x20, 0x07, 0xc1, 0xe1, - 0xfe, 0xc5, 0x51, 0xe0, 0x8c, 0xfb, 0x21, 0xe4, 0xa5, 0xaa, 0x28, 0x66, 0xc2, 0x54, 0x91, 0xd8, 0xda, 0x73, 0x5b, - 0xaf, 0x32, 0x3e, 0xa3, 0xe9, 0xd4, 0x22, 0xa1, 0x87, 0x29, 0x8b, 0xcd, 0xef, 0x60, 0xc2, 0x2f, 0x83, 0xc8, 0x05, - 0x85, 0x9d, 0xe5, 0x51, 0x4c, 0x97, 0xec, 0x46, 0x84, 0x29, 0x4d, 0xf6, 0x73, 0x42, 0xa2, 0x70, 0xa9, 0xc0, 0x04, - 0xd5, 0xeb, 0x04, 0xe2, 0xda, 0xba, 0xcf, 0x6f, 0x44, 0xb8, 0xa4, 0xf9, 0x7e, 0x42, 0x5a, 0x45, 0xb8, 0x08, 0x35, - 0x9b, 0x9a, 0x9e, 0xb3, 0x70, 0x45, 0x2f, 0xd0, 0x54, 0x73, 0x1d, 0x5e, 0x00, 0x97, 0xb7, 0x9e, 0xaf, 0x16, 0xec, - 0xa2, 0x21, 0x7d, 0x33, 0x7c, 0xf1, 0xb9, 0xf5, 0xc9, 0x03, 0x1e, 0xd2, 0xf9, 0xe1, 0xa5, 0x60, 0x03, 0x70, 0x95, - 0xf1, 0xeb, 0xef, 0xe4, 0x8d, 0x9e, 0x97, 0xf6, 0x14, 0xe3, 0xcc, 0xb4, 0x13, 0x93, 0x76, 0x42, 0xee, 0xdf, 0xb7, - 0x7d, 0xf7, 0xe2, 0xb5, 0x72, 0x59, 0xb5, 0x0c, 0x49, 0xbc, 0x56, 0xae, 0xd3, 0x28, 0x39, 0xb5, 0x02, 0x4f, 0x76, - 0xce, 0xab, 0x64, 0xe9, 0x1f, 0x54, 0xd6, 0x6a, 0xc0, 0x1e, 0x23, 0x96, 0x85, 0xc2, 0xb1, 0x7f, 0x95, 0xb1, 0x78, - 0xdd, 0x40, 0x06, 0x46, 0xee, 0xed, 0x55, 0xc6, 0xbc, 0x18, 0xb4, 0xf9, 0xda, 0x0b, 0xdd, 0xe7, 0xa5, 0x2f, 0x5b, - 0xbc, 0x97, 0x53, 0x6a, 0x18, 0x89, 0xe8, 0xde, 0x58, 0x99, 0x51, 0xaa, 0x44, 0xad, 0x41, 0x23, 0x82, 0x8d, 0x5d, - 0x30, 0x50, 0x70, 0x42, 0xe5, 0x9e, 0x3a, 0xdb, 0xb7, 0x53, 0x2a, 0x3d, 0xa0, 0x5d, 0x6a, 0x54, 0xe5, 0x6e, 0x99, - 0x49, 0x56, 0x0d, 0x82, 0xd1, 0x5f, 0xa5, 0x14, 0x33, 0xbc, 0x33, 0xb2, 0x60, 0x0a, 0x56, 0x82, 0xaa, 0x96, 0x61, - 0x39, 0xe4, 0xa8, 0xc5, 0x33, 0x3e, 0xa9, 0x52, 0xff, 0xe8, 0x08, 0x1a, 0x9c, 0xae, 0x5b, 0x41, 0x83, 0x1f, 0x8f, - 0x1f, 0xeb, 0x81, 0x5e, 0xaf, 0xb5, 0xe3, 0xa1, 0xcf, 0x6f, 0x23, 0xde, 0xb8, 0xee, 0x3d, 0xd5, 0x5a, 0x85, 0x32, - 0xd0, 0x62, 0x45, 0xe5, 0x4a, 0x2d, 0xe9, 0xdd, 0x2e, 0x02, 0x60, 0x11, 0x1b, 0xb3, 0xf1, 0xae, 0x6d, 0x56, 0x08, - 0x1a, 0x5d, 0x76, 0xb4, 0x89, 0x07, 0x2c, 0xd1, 0xad, 0x1d, 0x4c, 0x68, 0x7c, 0xc4, 0xca, 0x7e, 0x3f, 0x3f, 0x02, - 0x7a, 0xaa, 0x8d, 0x98, 0x0a, 0x38, 0xf2, 0xbf, 0xb4, 0x22, 0x53, 0x14, 0xd8, 0xac, 0xa9, 0xbb, 0x35, 0x96, 0x91, - 0xe8, 0xcb, 0x94, 0x2e, 0x4f, 0x78, 0x06, 0x4c, 0xe7, 0xeb, 0x96, 0xe3, 0xca, 0xae, 0xe2, 0xc8, 0x53, 0x61, 0x59, - 0x71, 0x5e, 0x85, 0xe3, 0xad, 0xc7, 0x37, 0xd8, 0x37, 0x6c, 0xda, 0xca, 0x1f, 0x42, 0x58, 0x08, 0xaf, 0x32, 0xb8, - 0x8d, 0x68, 0x3b, 0x09, 0x54, 0xde, 0x98, 0xeb, 0x84, 0xb2, 0xb9, 0x3d, 0x5f, 0x7b, 0x06, 0xe9, 0xc4, 0x1c, 0x28, - 0xd5, 0x08, 0x5a, 0xa3, 0x59, 0x50, 0x35, 0xe2, 0x91, 0x33, 0xff, 0x72, 0x06, 0xb1, 0x5a, 0xbe, 0xa4, 0xa9, 0x14, - 0x0d, 0xc0, 0xb8, 0x00, 0x2e, 0x4f, 0x1f, 0xde, 0xfd, 0x74, 0xc2, 0xe3, 0x22, 0x59, 0xbe, 0x8d, 0x8b, 0xf8, 0xb2, - 0x0c, 0x37, 0x6a, 0x8c, 0xe2, 0x9a, 0x4c, 0xc5, 0x80, 0x49, 0xb3, 0x92, 0x9a, 0xbb, 0x52, 0x13, 0x62, 0xac, 0x33, - 0x59, 0x97, 0x95, 0xbc, 0x6c, 0x54, 0xba, 0x2e, 0x32, 0xfc, 0xb8, 0xe5, 0x73, 0xba, 0x0f, 0xc0, 0xa6, 0xc6, 0x85, - 0x34, 0x92, 0xba, 0x10, 0x63, 0x2e, 0xe2, 0x75, 0x7d, 0x3c, 0x6e, 0x74, 0xbd, 0x64, 0x4f, 0xc6, 0x8f, 0xa6, 0xaf, - 0xb2, 0x30, 0x1b, 0x08, 0x32, 0xaa, 0x96, 0x5c, 0xb4, 0x4c, 0x39, 0x95, 0x49, 0x00, 0xfa, 0x78, 0xf6, 0x18, 0x3b, - 0x18, 0x8f, 0xc9, 0xa6, 0x2d, 0x1e, 0xe0, 0x61, 0xba, 0x0e, 0x0b, 0x32, 0xd3, 0x75, 0x44, 0x81, 0xe0, 0x37, 0x55, - 0x00, 0xc8, 0x96, 0xb6, 0x2a, 0xc3, 0xa5, 0xb1, 0x27, 0xe3, 0x09, 0x95, 0xd8, 0xed, 0x90, 0xd4, 0x5e, 0x85, 0x6e, - 0xe6, 0xa5, 0xef, 0x51, 0x24, 0x8d, 0xcb, 0xd2, 0x4e, 0xa5, 0x52, 0xed, 0x99, 0x99, 0xeb, 0x1a, 0xc4, 0xa4, 0x08, - 0x75, 0xdd, 0xa5, 0x57, 0xf7, 0x6e, 0x73, 0xad, 0xd9, 0x0e, 0x78, 0xaf, 0x41, 0x33, 0x94, 0xbc, 0xc5, 0xbc, 0x75, - 0x45, 0xd4, 0xf4, 0x62, 0x0d, 0x66, 0xc5, 0x28, 0x5b, 0x8a, 0xd6, 0x6b, 0x0a, 0x4a, 0xc1, 0x68, 0xb5, 0xf6, 0x16, - 0xee, 0x53, 0xd9, 0xb8, 0xb0, 0x64, 0x7a, 0xb5, 0x28, 0x29, 0xa1, 0xba, 0xa9, 0x18, 0x29, 0x61, 0xa4, 0x34, 0x3c, - 0x95, 0xef, 0x05, 0x1e, 0xe7, 0x79, 0x10, 0xb5, 0xbc, 0xc0, 0x8e, 0x2b, 0x72, 0x0c, 0x8e, 0x5e, 0x26, 0xa7, 0xa1, - 0xc0, 0x3f, 0x66, 0x0a, 0xd4, 0x75, 0xa8, 0xee, 0x37, 0xb8, 0xf9, 0x7f, 0x2d, 0x58, 0xe0, 0xf1, 0xad, 0x97, 0xb8, - 0x8d, 0x7e, 0x2d, 0x7c, 0x5a, 0xfa, 0x46, 0xfa, 0xae, 0x2e, 0x9e, 0xb4, 0x37, 0x1b, 0x25, 0xcb, 0x2c, 0x4f, 0x5f, - 0xcb, 0x94, 0x83, 0xc8, 0x0c, 0xad, 0x41, 0xd9, 0x91, 0x68, 0xdc, 0xf0, 0xc0, 0x88, 0xb1, 0x71, 0xe3, 0xfb, 0x31, - 0x03, 0xd9, 0x30, 0x58, 0x7d, 0xb3, 0x54, 0x26, 0x6b, 0x40, 0xd8, 0xd0, 0xf2, 0x13, 0x8d, 0xb7, 0x11, 0xea, 0xeb, - 0x17, 0xb8, 0xcd, 0x95, 0xbe, 0xcf, 0xf9, 0x8f, 0x19, 0xfd, 0x11, 0x81, 0x5f, 0xe2, 0x15, 0xc8, 0x3d, 0x9e, 0x42, - 0xdd, 0x08, 0xdb, 0xcb, 0x31, 0x58, 0x12, 0xa2, 0xa3, 0x88, 0x8a, 0x05, 0x0a, 0x9a, 0xc2, 0x20, 0x8a, 0xa8, 0x0b, - 0xe6, 0xf0, 0x2c, 0x97, 0xc9, 0xc7, 0xa9, 0xf1, 0x99, 0x1f, 0xc6, 0x18, 0x43, 0x3a, 0x18, 0x84, 0xd5, 0x2c, 0x18, - 0x8e, 0x47, 0x93, 0x83, 0x27, 0x70, 0x6e, 0x07, 0xe3, 0x80, 0x0c, 0x82, 0xba, 0x5c, 0xc5, 0x82, 0x96, 0x57, 0x17, - 0xb6, 0x0c, 0xfc, 0xb8, 0x0e, 0x06, 0xbf, 0x16, 0x9e, 0xe2, 0x1d, 0x34, 0x27, 0xb7, 0x32, 0x0c, 0x02, 0x7a, 0xb1, - 0x26, 0x20, 0x29, 0xeb, 0x69, 0x7e, 0x52, 0x1f, 0x6e, 0x4c, 0x69, 0xff, 0xcc, 0xe1, 0x05, 0x87, 0x1d, 0x12, 0x28, - 0x90, 0xc6, 0xd3, 0x6c, 0xf4, 0x52, 0x29, 0x72, 0xdf, 0x16, 0x1c, 0xee, 0xcc, 0x3d, 0x67, 0x7a, 0xe4, 0x14, 0x12, - 0xcd, 0x2c, 0xe0, 0x46, 0xfe, 0x52, 0x5c, 0xc5, 0x79, 0x96, 0xee, 0x35, 0xdf, 0xec, 0x95, 0xb7, 0xa2, 0x8a, 0x6f, - 0x46, 0x81, 0xb1, 0x26, 0xe4, 0xbe, 0xea, 0x09, 0xd0, 0x13, 0x60, 0x0b, 0x80, 0x01, 0xf1, 0x8e, 0x99, 0xc9, 0x8c, - 0x47, 0xe0, 0x11, 0xd8, 0xf4, 0x81, 0x2c, 0x6e, 0x9d, 0x4b, 0x92, 0xbf, 0x99, 0x4a, 0x7b, 0xd5, 0x2b, 0x77, 0x0a, - 0xb2, 0x5e, 0x6d, 0xe5, 0xae, 0x5b, 0x9f, 0x7d, 0xd3, 0xe1, 0x15, 0x78, 0x2a, 0xc1, 0x2d, 0xb2, 0xdf, 0x6f, 0x0a, - 0x2a, 0x85, 0x51, 0x11, 0xef, 0x24, 0xd7, 0xe8, 0xdf, 0xee, 0x8d, 0x8d, 0x22, 0xb9, 0xe5, 0xfd, 0x03, 0xa8, 0x33, - 0x79, 0x57, 0xdc, 0xce, 0x21, 0x6a, 0xeb, 0x6e, 0x3c, 0xf0, 0xde, 0xa0, 0x5d, 0xd6, 0x1c, 0xc1, 0x96, 0x17, 0x7b, - 0x19, 0x8c, 0x05, 0xce, 0xca, 0x48, 0xa9, 0x71, 0xad, 0x8c, 0x06, 0xd4, 0x26, 0x77, 0x90, 0xa5, 0x9e, 0x04, 0x45, - 0x8e, 0x67, 0x31, 0x64, 0x1a, 0x6f, 0x03, 0xb1, 0xdf, 0xc8, 0x10, 0xa4, 0x69, 0xdb, 0x6d, 0x73, 0x04, 0xca, 0xee, - 0x81, 0x29, 0x49, 0x5d, 0x1b, 0x53, 0x03, 0x0d, 0x3d, 0x88, 0x1a, 0xa9, 0x88, 0xb3, 0xa3, 0xa7, 0xa0, 0x43, 0x04, - 0xdf, 0xef, 0x34, 0x2b, 0x3b, 0x5e, 0x4c, 0x08, 0x9e, 0xbc, 0xcf, 0x6f, 0xb2, 0xb2, 0x2a, 0xa3, 0x17, 0x29, 0x1a, - 0x42, 0x25, 0x52, 0x44, 0xaf, 0x21, 0xbe, 0x60, 0x89, 0xbf, 0xcb, 0xe8, 0x5d, 0x4a, 0xe3, 0x34, 0xc5, 0xf4, 0x67, - 0x05, 0xfc, 0x7c, 0x0a, 0x28, 0x97, 0xb8, 0x13, 0xa2, 0x53, 0x09, 0xf6, 0x6a, 0x10, 0xdd, 0xab, 0xe2, 0x80, 0x29, - 0x1a, 0xdd, 0x08, 0x8a, 0x98, 0x75, 0x98, 0xfd, 0x43, 0x81, 0x42, 0x21, 0x55, 0xcc, 0x2f, 0xc2, 0x3e, 0x44, 0xd5, - 0x1a, 0xca, 0x39, 0x7e, 0xfb, 0xd2, 0x0c, 0x69, 0x74, 0x23, 0xa9, 0xde, 0xda, 0x78, 0x6c, 0x21, 0x4a, 0x4f, 0x74, - 0xb9, 0xa6, 0xa7, 0xf1, 0x2a, 0x8b, 0x36, 0x80, 0x3f, 0xf1, 0xf6, 0xe5, 0x53, 0x65, 0x61, 0xf2, 0x32, 0x03, 0xc5, - 0xc1, 0xf1, 0xdb, 0x97, 0xaf, 0x64, 0xba, 0xce, 0x79, 0x74, 0x2b, 0x91, 0xb4, 0x1e, 0xbf, 0x7d, 0xf9, 0x33, 0x9a, - 0x7b, 0xbd, 0x2b, 0xe0, 0xfd, 0x0b, 0xe0, 0x2d, 0xa3, 0x64, 0x0d, 0x7d, 0x52, 0xbf, 0xf3, 0x35, 0x76, 0xca, 0xab, - 0xb5, 0x8c, 0x7e, 0x4f, 0x6b, 0x4f, 0x5a, 0xf5, 0x77, 0xe1, 0x53, 0x3b, 0x4f, 0xc0, 0x73, 0x93, 0x67, 0xe2, 0x63, - 0x64, 0x45, 0x3b, 0x41, 0xf4, 0xf5, 0xde, 0xcd, 0x65, 0x2e, 0xca, 0x08, 0x5f, 0x30, 0xb4, 0x0b, 0x8a, 0xf6, 0xf7, - 0xaf, 0xaf, 0xaf, 0x47, 0xd7, 0x8f, 0x46, 0xb2, 0xb8, 0xd8, 0x9f, 0x7c, 0xfb, 0xed, 0xb7, 0xfb, 0xf8, 0x36, 0xf8, - 0xba, 0xed, 0xf6, 0x5e, 0x11, 0x3e, 0x60, 0x01, 0x22, 0x76, 0x7f, 0x0d, 0x57, 0x14, 0xd0, 0xc2, 0x0d, 0xbe, 0x0e, - 0xbe, 0xd6, 0x87, 0xce, 0xd7, 0x87, 0xe5, 0xd5, 0x85, 0x2a, 0xbf, 0xab, 0xe4, 0x83, 0xf1, 0x78, 0xbc, 0x0f, 0x12, - 0xa8, 0xaf, 0x07, 0x7c, 0x10, 0x1c, 0x05, 0x83, 0x0c, 0x2e, 0x34, 0xe5, 0xd5, 0xc5, 0x51, 0xe0, 0x19, 0xd8, 0x36, - 0x58, 0x44, 0x07, 0xe2, 0x12, 0xec, 0x5f, 0xd0, 0xe0, 0xeb, 0x80, 0xb8, 0x94, 0xaf, 0x20, 0xe5, 0xab, 0x83, 0x27, - 0x7e, 0xda, 0xff, 0x52, 0x69, 0x8f, 0xfc, 0xb4, 0x43, 0x4c, 0x7b, 0xf4, 0xd4, 0x4f, 0x3b, 0x52, 0x69, 0xcf, 0xfd, - 0xb4, 0xff, 0x5d, 0x0e, 0x20, 0x75, 0xcf, 0xb7, 0xfe, 0x3b, 0xf5, 0x5a, 0x83, 0xa7, 0x50, 0x94, 0x5d, 0xc6, 0x17, - 0x1c, 0x1a, 0x3d, 0xb8, 0xb9, 0xcc, 0x69, 0x30, 0xc0, 0xf6, 0x7a, 0x46, 0x1e, 0xde, 0x07, 0x5f, 0xaf, 0x8b, 0x3c, - 0x0c, 0xbe, 0x1e, 0x60, 0x21, 0x83, 0xaf, 0x03, 0xf2, 0xb5, 0x3e, 0xd2, 0xae, 0x04, 0xdb, 0x04, 0x2e, 0x34, 0xeb, - 0xd0, 0x06, 0x4c, 0xf3, 0xa5, 0x71, 0x35, 0xfd, 0xad, 0xe8, 0xce, 0x86, 0xb7, 0x44, 0xe5, 0xa6, 0x1b, 0xd4, 0xf4, - 0x2d, 0x78, 0x27, 0x40, 0xa3, 0xa2, 0xe0, 0x2a, 0x2e, 0xc2, 0xe1, 0xb0, 0xbc, 0xba, 0x20, 0x60, 0x97, 0xb9, 0xe2, - 0x71, 0x15, 0x05, 0x42, 0x0e, 0xd5, 0xcf, 0x40, 0x45, 0x02, 0x0b, 0x10, 0xca, 0x08, 0xfe, 0x0b, 0x6a, 0xfa, 0x40, - 0xb2, 0x4d, 0x30, 0xbc, 0xe6, 0x67, 0x1f, 0xb3, 0x6a, 0xa8, 0x44, 0x8b, 0x57, 0x82, 0xc2, 0x0f, 0xf8, 0xeb, 0xaa, - 0x8e, 0x7e, 0x03, 0x37, 0xee, 0xa6, 0x86, 0xfd, 0x81, 0xf4, 0x1c, 0xda, 0xe4, 0x3c, 0x5b, 0x4c, 0x5b, 0x07, 0xfa, - 0x5b, 0x49, 0xaa, 0x79, 0x36, 0x08, 0x86, 0xc1, 0x80, 0x2f, 0xd8, 0x5b, 0x39, 0xe7, 0x9e, 0xf9, 0xd4, 0xb1, 0xf4, - 0xa7, 0x79, 0x96, 0x0d, 0xc0, 0x37, 0x05, 0xf9, 0x91, 0xfd, 0xff, 0x9e, 0x0f, 0x51, 0x78, 0x38, 0x78, 0xb0, 0x4f, - 0x66, 0xc1, 0xea, 0x06, 0x3d, 0x3a, 0xa3, 0x20, 0x13, 0x4b, 0x5e, 0x64, 0x95, 0xb7, 0x54, 0x6e, 0xd6, 0x6d, 0x2f, - 0x8f, 0x3b, 0xcf, 0xe6, 0x55, 0x2c, 0x02, 0x75, 0xce, 0x81, 0xe2, 0x0d, 0x65, 0x4f, 0x65, 0x53, 0x42, 0xaa, 0x0d, - 0x79, 0xc3, 0x72, 0xc0, 0x82, 0xc3, 0xde, 0x70, 0xb8, 0x17, 0x0c, 0x9c, 0x3a, 0x77, 0x10, 0xec, 0x0d, 0x87, 0x47, - 0x81, 0xbb, 0x0f, 0x65, 0x23, 0x77, 0x67, 0xa4, 0x05, 0xfb, 0xbb, 0x08, 0x4b, 0x0a, 0xe2, 0x31, 0xa9, 0xc5, 0x5f, - 0x1a, 0x5c, 0x66, 0x00, 0xd0, 0x47, 0x4a, 0x02, 0x66, 0x60, 0x65, 0x06, 0x10, 0xaa, 0x9c, 0xc6, 0xec, 0x16, 0x98, - 0x47, 0xe0, 0x98, 0x15, 0x4c, 0x16, 0x20, 0x96, 0x04, 0x38, 0x77, 0x41, 0x14, 0xeb, 0x42, 0x8e, 0x21, 0x08, 0x00, - 0xfe, 0x24, 0xa6, 0x14, 0x4c, 0xd2, 0xb1, 0x1b, 0x41, 0x10, 0xc7, 0x67, 0x57, 0xa2, 0x35, 0x39, 0x4b, 0x74, 0x30, - 0x23, 0x09, 0xb0, 0x21, 0x06, 0x86, 0x0f, 0xee, 0xe7, 0xa0, 0xf4, 0xb0, 0x7a, 0x27, 0xe4, 0x82, 0x6f, 0xb9, 0x63, - 0xa1, 0xae, 0xe0, 0xea, 0x09, 0x07, 0xc1, 0x2d, 0xd7, 0x2c, 0xc0, 0xa8, 0x2a, 0xd6, 0x65, 0xc5, 0xd3, 0xf7, 0xb7, - 0x2b, 0x88, 0x05, 0x88, 0x03, 0xfa, 0x56, 0xe6, 0x59, 0x72, 0x1b, 0x3a, 0x7b, 0xae, 0x8d, 0x4a, 0xff, 0xe1, 0xfd, - 0xab, 0x9f, 0x22, 0x10, 0x39, 0xd6, 0x86, 0xd2, 0xdf, 0x72, 0x3c, 0x9b, 0xfc, 0x88, 0x57, 0xfe, 0xc6, 0xbe, 0xe5, - 0xf6, 0xf4, 0xe8, 0xf7, 0xa1, 0x6e, 0x7a, 0xcb, 0x67, 0xb7, 0x7c, 0xe4, 0x8a, 0x43, 0x75, 0x85, 0xfb, 0xfa, 0xe3, - 0xda, 0x37, 0x42, 0xba, 0x7f, 0x9e, 0x29, 0x6f, 0xcc, 0x8f, 0x76, 0x30, 0x0c, 0x82, 0xa9, 0x16, 0x4a, 0x42, 0x14, - 0x12, 0xa6, 0x04, 0x0c, 0xd1, 0x9e, 0x5e, 0x56, 0x53, 0xe4, 0xdc, 0xd4, 0xc8, 0xc2, 0xfb, 0x01, 0xd3, 0x42, 0x87, - 0x46, 0x0e, 0xe5, 0x07, 0x87, 0x13, 0xc6, 0x2c, 0xfc, 0x56, 0x09, 0xd3, 0xaf, 0x16, 0x95, 0x73, 0x10, 0xdd, 0x03, - 0x63, 0x5c, 0xc1, 0x0b, 0xe8, 0x0a, 0xbb, 0x5e, 0xab, 0x28, 0x21, 0x08, 0xa6, 0x87, 0x1c, 0xa0, 0x87, 0x5d, 0xd0, - 0xb2, 0xb2, 0x54, 0xb7, 0x2a, 0x67, 0xa9, 0xa2, 0x2e, 0x43, 0x59, 0x19, 0x2b, 0x0c, 0xfc, 0x92, 0x7d, 0x28, 0xd0, - 0xb3, 0x7c, 0x2a, 0xba, 0xe0, 0x85, 0x50, 0x82, 0xe5, 0xba, 0xde, 0x89, 0x40, 0xd4, 0xf9, 0xa1, 0x77, 0xd5, 0xd7, - 0xb8, 0x7e, 0x3c, 0x7d, 0x25, 0x53, 0xae, 0x4d, 0x28, 0x34, 0x9f, 0x2f, 0x7d, 0xc5, 0x44, 0xc1, 0x3e, 0x42, 0xbf, - 0xda, 0x36, 0xfa, 0xec, 0x66, 0xad, 0x37, 0x83, 0x12, 0x1d, 0xf3, 0x1a, 0x05, 0xd7, 0x4a, 0xa1, 0x60, 0xb4, 0xb7, - 0xf1, 0x67, 0x38, 0x72, 0xab, 0xdb, 0x43, 0xef, 0xb7, 0x2a, 0xbe, 0x78, 0x8d, 0xbe, 0x9d, 0xf6, 0xe7, 0xa8, 0x92, - 0x1f, 0x56, 0x2b, 0xf0, 0xa1, 0x82, 0x48, 0x2b, 0x16, 0xa7, 0x17, 0xea, 0x39, 0x79, 0x7b, 0xfc, 0x1a, 0xfc, 0x28, - 0xf1, 0xf7, 0x2f, 0xdf, 0x07, 0x35, 0x99, 0xc6, 0xb3, 0xc2, 0x7c, 0x68, 0x73, 0x40, 0xa8, 0x16, 0x97, 0x66, 0xdf, - 0xcf, 0xe2, 0x26, 0xfb, 0xae, 0xd9, 0x7a, 0x5a, 0x34, 0x91, 0xa4, 0x0c, 0xb7, 0x0f, 0x06, 0x04, 0xfa, 0x00, 0x51, - 0x9c, 0x7d, 0x41, 0x63, 0x48, 0xf3, 0x99, 0x7d, 0x3f, 0x42, 0xe0, 0xcb, 0x9d, 0x90, 0x6a, 0x5c, 0x61, 0xd1, 0xe8, - 0x21, 0x9f, 0xf1, 0x48, 0x19, 0x16, 0xbd, 0xc3, 0x04, 0xe2, 0x0c, 0xa7, 0xd5, 0x7b, 0xc4, 0x80, 0xc6, 0xbb, 0x81, - 0x96, 0x3d, 0x44, 0x19, 0x75, 0xd9, 0x1b, 0x16, 0xdf, 0x27, 0xeb, 0x30, 0xb3, 0x96, 0x97, 0x43, 0xf8, 0x1b, 0x68, - 0x03, 0x70, 0xca, 0x91, 0xe5, 0xab, 0xcc, 0x46, 0x57, 0x4b, 0x4c, 0x6f, 0x22, 0x88, 0x4d, 0xa4, 0xd3, 0x61, 0xed, - 0xea, 0x54, 0xbd, 0xab, 0x9d, 0xcf, 0x44, 0xaf, 0x02, 0xad, 0x5c, 0xdb, 0x1e, 0x0f, 0xe1, 0x3f, 0xb5, 0xb4, 0xc2, - 0x46, 0xd8, 0x73, 0xf1, 0x85, 0xe7, 0xd8, 0x9c, 0x80, 0x06, 0x97, 0x32, 0x05, 0xe0, 0x2c, 0xad, 0x46, 0xa3, 0x46, - 0xd8, 0x67, 0xe5, 0x7c, 0x0e, 0x5b, 0x0b, 0xf1, 0xb4, 0x00, 0x1c, 0xb8, 0x89, 0xc9, 0xc9, 0xbb, 0x31, 0x39, 0xa7, - 0x1f, 0x15, 0xdc, 0x77, 0x70, 0x5a, 0x2e, 0xe3, 0x54, 0x5e, 0x03, 0x36, 0x65, 0xe0, 0xa7, 0x62, 0xa9, 0x5e, 0x42, - 0xb2, 0xe4, 0xc9, 0x47, 0xb4, 0xda, 0x48, 0x03, 0xe0, 0x2a, 0xa7, 0xc6, 0x72, 0x4f, 0x81, 0xa6, 0xba, 0x52, 0x54, - 0x42, 0x5c, 0x55, 0x71, 0xb2, 0x3c, 0xc1, 0xd4, 0x70, 0x03, 0xbd, 0x88, 0x02, 0xb9, 0xe2, 0x02, 0x48, 0x7a, 0xce, - 0xfe, 0xc8, 0x34, 0xf6, 0xfa, 0x1b, 0x89, 0x02, 0x26, 0x8d, 0xa2, 0x8c, 0x95, 0xb2, 0x97, 0xd2, 0x44, 0xbf, 0x0b, - 0x82, 0xda, 0xbd, 0xfc, 0x1b, 0xea, 0x7e, 0x0a, 0xad, 0x08, 0x1b, 0xe0, 0x85, 0x1a, 0xfc, 0x30, 0xb5, 0x4b, 0xce, - 0x03, 0x32, 0x74, 0xde, 0x67, 0xb5, 0xdd, 0xea, 0x4f, 0x97, 0x80, 0xf5, 0x9a, 0x1a, 0x9f, 0xc2, 0x30, 0x21, 0x26, - 0x56, 0xb2, 0x55, 0x56, 0xda, 0x0d, 0x65, 0xda, 0x49, 0x97, 0xcc, 0x6b, 0xe1, 0x34, 0xef, 0x31, 0xb6, 0x1c, 0xa9, - 0xdc, 0xfd, 0x7e, 0x68, 0x7e, 0xb2, 0x9c, 0xbe, 0xd1, 0x21, 0xac, 0xbd, 0xf1, 0xa0, 0x39, 0xd1, 0xea, 0xaa, 0x8e, - 0x7e, 0x40, 0x07, 0x60, 0xa6, 0x2d, 0x42, 0xa5, 0x0b, 0xbe, 0xed, 0x2b, 0x51, 0x71, 0x49, 0xc2, 0x52, 0x49, 0x60, - 0x67, 0x37, 0x25, 0x3b, 0x9b, 0x80, 0x78, 0x86, 0xbb, 0x9e, 0x16, 0x3b, 0x21, 0x4d, 0x78, 0x8b, 0xbd, 0x04, 0x44, - 0x1d, 0xaa, 0xba, 0x84, 0x6c, 0x8c, 0xa1, 0x8b, 0x7f, 0x51, 0x0a, 0x13, 0xd6, 0x32, 0xa9, 0x4a, 0x4c, 0x50, 0xa8, - 0x72, 0xb7, 0x45, 0x60, 0x89, 0x82, 0x1d, 0xc0, 0xde, 0xbb, 0x51, 0x37, 0xa3, 0xa6, 0xaa, 0x53, 0x2f, 0xc1, 0xc7, - 0x69, 0xd6, 0x55, 0x90, 0x59, 0xd8, 0x55, 0xb1, 0xe6, 0x81, 0x8e, 0xd5, 0xa5, 0x8c, 0x89, 0xbb, 0xb4, 0xc8, 0x10, - 0x1f, 0x19, 0x63, 0x0b, 0x6b, 0x38, 0xd2, 0xf6, 0xb8, 0xe9, 0x09, 0x42, 0x3f, 0x61, 0x43, 0x09, 0xdc, 0x74, 0xb6, - 0xa7, 0xa6, 0x99, 0x0f, 0x88, 0x38, 0x0c, 0x28, 0x90, 0x6c, 0x1c, 0xd2, 0x1c, 0xe9, 0x0b, 0x92, 0x26, 0x0c, 0x94, - 0xad, 0x78, 0x4e, 0x90, 0x15, 0x85, 0x9e, 0xad, 0xab, 0x1a, 0xe2, 0xe7, 0x32, 0xcc, 0xd1, 0x92, 0x53, 0xe1, 0x69, - 0x82, 0x4c, 0xec, 0x8e, 0xb6, 0x99, 0xc9, 0x70, 0x94, 0x2c, 0x30, 0xbf, 0x82, 0x28, 0x71, 0x67, 0x9a, 0x55, 0x39, - 0x18, 0x17, 0xb0, 0x40, 0x2b, 0xdf, 0x83, 0xba, 0xb1, 0x86, 0x36, 0x1a, 0x96, 0xd9, 0xed, 0x4f, 0xb0, 0x5f, 0x6b, - 0xa7, 0x75, 0x99, 0x62, 0x79, 0x99, 0x42, 0xb4, 0x17, 0x32, 0xbf, 0x51, 0x24, 0xba, 0x53, 0x84, 0x21, 0x61, 0x1d, - 0x65, 0x4f, 0xda, 0xd4, 0x00, 0x7a, 0xea, 0x05, 0x80, 0xef, 0x5c, 0xcb, 0xb0, 0x8b, 0x74, 0x7f, 0x55, 0x30, 0x2e, - 0xdd, 0x20, 0x48, 0xd1, 0x9b, 0x14, 0xcc, 0x79, 0x3d, 0x4a, 0xea, 0xcd, 0x69, 0xcb, 0x8c, 0xaa, 0xa3, 0x22, 0xa4, - 0x9c, 0xe0, 0x3f, 0x79, 0x29, 0x35, 0xb1, 0x09, 0x13, 0x3c, 0xf0, 0x61, 0x9e, 0x61, 0x03, 0x6f, 0xb7, 0x0f, 0xd2, - 0x30, 0x69, 0xb3, 0x0d, 0x29, 0x48, 0x2b, 0x4c, 0x9c, 0x10, 0xa8, 0xec, 0x25, 0xee, 0x17, 0x6c, 0x27, 0x4d, 0xc1, - 0x83, 0xb0, 0xd1, 0xc0, 0xc4, 0xad, 0xae, 0x6c, 0x1d, 0x26, 0x34, 0x5c, 0x52, 0xed, 0xec, 0xa4, 0x92, 0xcf, 0xdb, - 0xeb, 0xf2, 0xdc, 0xf6, 0x41, 0xc7, 0x52, 0xeb, 0x1a, 0x1e, 0x68, 0x5e, 0xb3, 0x8b, 0x2b, 0xa6, 0x69, 0xa2, 0xb1, - 0x1e, 0x52, 0x96, 0x1c, 0xeb, 0x7a, 0xba, 0xc2, 0xd5, 0x32, 0xd3, 0x40, 0xf7, 0x12, 0x2f, 0xf4, 0x80, 0x0f, 0x1e, - 0xae, 0x48, 0x74, 0x8e, 0xcd, 0x66, 0xab, 0x9a, 0x4c, 0xf3, 0xbb, 0xb2, 0xe5, 0x26, 0x40, 0x9e, 0xa5, 0xbe, 0xb9, - 0x4f, 0x8e, 0x35, 0x6d, 0xf3, 0x93, 0x00, 0xd7, 0xdc, 0x2b, 0x20, 0xe9, 0x58, 0x82, 0x2e, 0xde, 0xa7, 0x3f, 0x88, - 0xd4, 0x4c, 0x05, 0xbd, 0x73, 0xbe, 0x48, 0xdd, 0xfc, 0x02, 0x6c, 0xa3, 0x36, 0xc6, 0x34, 0x4b, 0xac, 0xc3, 0x44, - 0x59, 0x58, 0x23, 0x0b, 0xb9, 0x04, 0x1f, 0xcc, 0xdd, 0xa6, 0x4e, 0x9f, 0x77, 0x10, 0x61, 0xbf, 0x8b, 0x1e, 0x8f, - 0x30, 0x56, 0xac, 0x41, 0x62, 0x58, 0x85, 0x35, 0x6d, 0x2e, 0x87, 0x28, 0xa7, 0x66, 0xc9, 0x44, 0x4b, 0xea, 0x53, - 0x8a, 0x28, 0x05, 0x73, 0xe3, 0x69, 0xd9, 0x30, 0x25, 0x44, 0xc8, 0x0a, 0xe9, 0x80, 0x6a, 0x2d, 0xb4, 0x54, 0x13, - 0x04, 0x3c, 0xf4, 0xb2, 0xd0, 0x98, 0x82, 0xe8, 0x23, 0x32, 0xdc, 0x88, 0x23, 0xa3, 0xbb, 0x63, 0x14, 0x13, 0x08, - 0xdd, 0xed, 0xe5, 0x85, 0xd5, 0xa7, 0x65, 0x5b, 0x1d, 0xc4, 0x35, 0xa6, 0xc9, 0x1d, 0x04, 0x35, 0x46, 0x41, 0x9b, - 0xd3, 0x8d, 0xfe, 0x5e, 0x84, 0xbe, 0x5d, 0x38, 0x76, 0xa3, 0x20, 0x12, 0x22, 0xd2, 0x7a, 0x4d, 0xc5, 0x00, 0xb5, - 0xf3, 0xd8, 0x45, 0xac, 0xd2, 0xdd, 0x42, 0x94, 0x37, 0x2a, 0xeb, 0x93, 0x75, 0x48, 0xb6, 0x5b, 0x2c, 0x0b, 0x7c, - 0xd9, 0x5f, 0xad, 0xef, 0x80, 0x40, 0x7f, 0xba, 0xfe, 0x2c, 0x04, 0xfa, 0xb3, 0xec, 0x4b, 0x20, 0xd0, 0x9f, 0xae, - 0xff, 0xa7, 0x21, 0xd0, 0x5f, 0xad, 0x3d, 0x08, 0x74, 0x35, 0x18, 0xff, 0x2a, 0x58, 0xf0, 0xe6, 0x75, 0x40, 0x9f, - 0x49, 0x16, 0xbc, 0x79, 0xf1, 0xc2, 0x13, 0xa6, 0xff, 0x20, 0x34, 0x92, 0xbf, 0x91, 0x05, 0x23, 0x6e, 0x0b, 0xbc, - 0x42, 0xad, 0x93, 0x0f, 0x54, 0x94, 0x01, 0x10, 0x7d, 0xf9, 0x6b, 0x56, 0x2d, 0xc3, 0x60, 0x3f, 0x20, 0x33, 0x07, - 0x09, 0x3a, 0x9c, 0x34, 0x6e, 0x6f, 0x1f, 0x44, 0x43, 0xa8, 0x63, 0x23, 0x0f, 0xc0, 0x57, 0x9e, 0xc8, 0xde, 0xbf, - 0x21, 0xe2, 0x27, 0x33, 0x0b, 0x3a, 0xba, 0x1f, 0x10, 0xf0, 0x58, 0xca, 0x3c, 0x04, 0xce, 0xb9, 0x1f, 0x12, 0xfa, - 0xed, 0xda, 0xb3, 0x2d, 0xfa, 0x20, 0xc2, 0x0a, 0x7c, 0xee, 0xfe, 0x5e, 0xf3, 0xd3, 0x2c, 0x25, 0x4e, 0x1e, 0xca, - 0x45, 0x22, 0x53, 0xfe, 0xe1, 0xdd, 0x4b, 0x8b, 0x3c, 0x1e, 0x2a, 0xe8, 0x25, 0x82, 0x21, 0x8d, 0x53, 0x7e, 0x95, - 0x25, 0x7c, 0xf6, 0xe7, 0x83, 0x4d, 0x67, 0x46, 0xf5, 0x9a, 0xd4, 0xfb, 0x7f, 0x46, 0x41, 0xa0, 0xc7, 0xe0, 0xcf, - 0x07, 0x9b, 0xac, 0xde, 0x7f, 0xb0, 0xa9, 0x46, 0xa9, 0x04, 0x78, 0x6f, 0xf8, 0x2d, 0xeb, 0x07, 0x9b, 0x12, 0x7e, - 0xf0, 0xfa, 0x4f, 0x0f, 0x98, 0xcd, 0x36, 0xc8, 0xeb, 0x83, 0x55, 0x5e, 0x39, 0x4c, 0xd0, 0x7b, 0x0a, 0x16, 0xa6, - 0x50, 0x87, 0x47, 0xb5, 0xf6, 0xe4, 0x7e, 0x53, 0xdd, 0x75, 0x42, 0xe0, 0x1a, 0xe9, 0x06, 0x0e, 0xa1, 0xb2, 0x04, - 0x3b, 0xea, 0xe8, 0x94, 0x20, 0xa6, 0xe6, 0xfd, 0x40, 0xd9, 0xfa, 0x7a, 0xc1, 0x8a, 0x5d, 0x33, 0x31, 0xbe, 0xd3, - 0x18, 0xd8, 0x70, 0xd1, 0xd5, 0x62, 0xce, 0xfe, 0x34, 0x3d, 0xde, 0xad, 0x42, 0x12, 0xc4, 0xc8, 0xf6, 0xfb, 0xc4, - 0xeb, 0x59, 0xca, 0xab, 0x38, 0xcb, 0x59, 0x9c, 0xe7, 0x7f, 0xa2, 0x2c, 0xe2, 0xfb, 0x2f, 0x02, 0xdd, 0x1f, 0x8d, - 0x46, 0x71, 0x71, 0x81, 0x57, 0x7f, 0x43, 0x6e, 0x11, 0x16, 0x3b, 0xe3, 0xa5, 0x0d, 0xac, 0xb2, 0x8c, 0xcb, 0x53, - 0x1d, 0xd1, 0xa8, 0xb4, 0x04, 0xbb, 0x5c, 0xca, 0xeb, 0x53, 0x88, 0xee, 0x60, 0x29, 0x78, 0x8c, 0x03, 0xa8, 0xee, - 0x4d, 0x26, 0xec, 0xf2, 0x5a, 0xbf, 0x3b, 0x8b, 0x4b, 0xfe, 0x36, 0xae, 0x96, 0x0c, 0xf6, 0x82, 0xa6, 0xea, 0x85, - 0x5c, 0xaf, 0x5c, 0x25, 0xa7, 0x6b, 0xf1, 0x51, 0xc8, 0x6b, 0xa1, 0x68, 0xef, 0x29, 0xbf, 0x82, 0x16, 0xb1, 0x0d, - 0xea, 0xac, 0x04, 0x4f, 0x2a, 0x8f, 0x13, 0x57, 0xb1, 0x00, 0x32, 0x6a, 0xa2, 0x01, 0x74, 0xe4, 0xa0, 0xa1, 0xdd, - 0x6b, 0xda, 0xb1, 0xdc, 0xa8, 0x2c, 0x32, 0xb0, 0x84, 0x7d, 0x0e, 0xa5, 0x03, 0x62, 0x3b, 0x84, 0x0b, 0x81, 0xab, - 0x27, 0x5e, 0x8d, 0x1a, 0x88, 0x3d, 0xb4, 0xf4, 0xdd, 0x85, 0x14, 0xab, 0x65, 0xd0, 0x2e, 0x1b, 0xc3, 0x84, 0xd7, - 0x6b, 0x74, 0x19, 0x06, 0xc5, 0x7f, 0xe1, 0x16, 0x25, 0xe2, 0x22, 0x65, 0xa9, 0x32, 0x3a, 0xeb, 0xa1, 0x2c, 0x0c, - 0x9f, 0x3d, 0x1d, 0xa5, 0x0e, 0x2b, 0xe7, 0x99, 0xe5, 0x6d, 0x94, 0x26, 0x7e, 0x0e, 0x26, 0x61, 0x7e, 0x2d, 0x73, - 0xa9, 0xe3, 0x92, 0x9f, 0x8a, 0xf5, 0x25, 0x2f, 0xb2, 0xe4, 0x74, 0x99, 0x95, 0x95, 0x2c, 0x6e, 0x17, 0x06, 0xee, - 0x42, 0x97, 0xd5, 0x9a, 0xc4, 0x3b, 0xbf, 0x03, 0x9f, 0x77, 0x15, 0xc0, 0x64, 0xf8, 0x64, 0x4c, 0x6a, 0x6d, 0x2d, - 0x0f, 0x0d, 0xa4, 0xf6, 0xb7, 0xda, 0x27, 0xee, 0xd9, 0x76, 0x8d, 0x36, 0xfd, 0x1c, 0xda, 0x35, 0x52, 0xb3, 0x94, - 0x0a, 0xfe, 0xf7, 0x9a, 0x9b, 0x68, 0x07, 0xa1, 0x43, 0xf2, 0x0e, 0x4b, 0x7d, 0x18, 0x69, 0x12, 0xad, 0x90, 0xa0, - 0x14, 0xf5, 0x6d, 0xbd, 0x50, 0x6d, 0x20, 0x44, 0xdd, 0x16, 0xd3, 0xf4, 0x39, 0x82, 0xb6, 0x83, 0x94, 0x04, 0xf7, - 0x96, 0x8d, 0xf9, 0xd5, 0xb5, 0x7c, 0xe6, 0xd0, 0x9d, 0xc5, 0xec, 0x73, 0x19, 0x06, 0x83, 0xe8, 0x73, 0x59, 0xd8, - 0xe4, 0x9e, 0x55, 0xaa, 0xb2, 0x1c, 0x1a, 0xdb, 0xcb, 0x29, 0x9a, 0xb2, 0x84, 0x0f, 0xd6, 0x61, 0x73, 0xed, 0x53, - 0x9c, 0x7d, 0xba, 0xb9, 0xe4, 0xd5, 0x52, 0xa6, 0x51, 0xf0, 0xfd, 0xf3, 0xf7, 0x81, 0x51, 0x5d, 0x17, 0x1a, 0xb4, - 0x48, 0x6b, 0x73, 0x72, 0x79, 0x01, 0xb2, 0xcc, 0x5e, 0x31, 0x92, 0x1f, 0x77, 0xa2, 0x7c, 0xfe, 0xf9, 0xc3, 0xfb, - 0xf7, 0x6f, 0xf7, 0x50, 0xe1, 0xd3, 0xdb, 0x3b, 0x51, 0xe8, 0x01, 0x7b, 0x0f, 0x36, 0x85, 0x56, 0xb1, 0xd7, 0x7f, - 0xda, 0xb3, 0xaa, 0x68, 0x29, 0xc8, 0x0d, 0x28, 0xa0, 0x57, 0x45, 0x6b, 0x58, 0x0b, 0xa7, 0xc5, 0xf6, 0x33, 0x2b, - 0xed, 0x52, 0x80, 0xba, 0x13, 0x55, 0x73, 0xa4, 0xf4, 0xf2, 0x10, 0x69, 0x21, 0xac, 0xee, 0xd8, 0x6a, 0x55, 0xd7, - 0x56, 0x93, 0x45, 0x95, 0x89, 0x8b, 0x53, 0xdc, 0xfd, 0x5f, 0xb4, 0xe5, 0xcc, 0x0c, 0x2b, 0x7a, 0xd1, 0xde, 0x6d, - 0x0d, 0xa8, 0x32, 0x6d, 0x94, 0xab, 0xf7, 0x10, 0x08, 0xcc, 0xca, 0x7a, 0xea, 0x7f, 0x6c, 0x2c, 0x46, 0xfc, 0x34, - 0x05, 0xe4, 0x06, 0x3c, 0x10, 0x3b, 0x8a, 0x47, 0xa6, 0x7d, 0xd7, 0x28, 0x37, 0x39, 0x4c, 0x5a, 0x09, 0xb3, 0xe1, - 0x24, 0x9a, 0x10, 0x1b, 0x5f, 0x42, 0xd3, 0xb0, 0xef, 0x47, 0xcf, 0x5f, 0xbf, 0x7f, 0xf9, 0xfe, 0xf7, 0xd3, 0xa7, - 0xc7, 0xef, 0x9f, 0x7f, 0xff, 0xe6, 0xdd, 0xcb, 0xe7, 0x27, 0x78, 0x42, 0x68, 0xc0, 0xca, 0x70, 0xa3, 0xad, 0xa2, - 0x9b, 0x65, 0x45, 0xa2, 0x26, 0xcd, 0xa6, 0x28, 0xc4, 0x28, 0xcc, 0x6c, 0x8b, 0xfc, 0xf0, 0xfa, 0xd9, 0xf3, 0x17, - 0x2f, 0x5f, 0x3f, 0x7f, 0xd6, 0xfe, 0x7a, 0x38, 0xa9, 0x49, 0xed, 0x66, 0x4e, 0x47, 0x48, 0xe1, 0x76, 0xbc, 0x3a, - 0xe8, 0x13, 0x6a, 0xe5, 0x7d, 0xfa, 0x94, 0xc1, 0x8a, 0x64, 0x4a, 0x4e, 0x8f, 0xbf, 0x3d, 0xfc, 0x5f, 0xb5, 0xf1, - 0xb6, 0x5b, 0xe0, 0x21, 0x90, 0x8c, 0x29, 0x59, 0x3f, 0x8c, 0x6a, 0x46, 0xd5, 0xcb, 0x48, 0x50, 0x5b, 0x1a, 0xd8, - 0x40, 0xa7, 0x54, 0x85, 0x54, 0x38, 0x4d, 0xe2, 0x8a, 0x5f, 0xc8, 0xe2, 0x36, 0xca, 0x46, 0xad, 0x14, 0xda, 0x58, - 0x00, 0x51, 0x08, 0x82, 0xe5, 0x46, 0x12, 0xe9, 0x29, 0x02, 0xe0, 0x0d, 0x81, 0x1b, 0xd5, 0xb9, 0x8b, 0x16, 0xd0, - 0x2e, 0x98, 0x2c, 0xb6, 0xdb, 0x8e, 0x41, 0xeb, 0xa4, 0x7d, 0xd1, 0x3c, 0x53, 0x44, 0x71, 0x01, 0x8c, 0x39, 0x1c, - 0x6f, 0xea, 0xec, 0x62, 0xe6, 0xb8, 0x3b, 0xd6, 0x51, 0x3f, 0xc1, 0x1a, 0xd1, 0xbd, 0x36, 0x81, 0x65, 0x9a, 0xe7, - 0xe1, 0xb8, 0x45, 0x71, 0x0d, 0xc6, 0x6f, 0x2b, 0x55, 0x2d, 0x33, 0x8d, 0xad, 0x08, 0x33, 0x05, 0xe1, 0xb8, 0x8c, - 0xe8, 0x36, 0xcc, 0xc1, 0x42, 0xa6, 0x31, 0xbf, 0x66, 0x1c, 0xf2, 0x48, 0x1a, 0x98, 0x3c, 0x30, 0x19, 0xbc, 0x23, - 0xd7, 0x32, 0x2a, 0x1a, 0x80, 0x97, 0xb2, 0x39, 0xa8, 0x87, 0xff, 0xa7, 0xb9, 0xa7, 0xdd, 0x6e, 0xdb, 0x46, 0xf6, - 0x7f, 0x9f, 0x82, 0x61, 0xb2, 0x29, 0x99, 0x90, 0x34, 0x29, 0x59, 0xb6, 0x22, 0x59, 0x72, 0x9b, 0xaf, 0x6d, 0x5a, - 0xb7, 0xe9, 0x49, 0xdc, 0xec, 0xdd, 0xf5, 0xfa, 0x58, 0x94, 0x04, 0x49, 0xdc, 0x50, 0xa4, 0x0e, 0x49, 0xf9, 0xa3, - 0x0a, 0xf7, 0x59, 0xf6, 0x11, 0xee, 0x33, 0xf4, 0xc9, 0xee, 0x99, 0x19, 0x80, 0x04, 0xbf, 0x24, 0x79, 0x93, 0xb6, - 0xf7, 0xb4, 0x49, 0x44, 0x10, 0x00, 0x81, 0x01, 0x30, 0x33, 0x98, 0xcf, 0xa8, 0xf8, 0x0c, 0xdb, 0xb8, 0x54, 0x05, - 0x45, 0xb6, 0xc5, 0x4a, 0x20, 0x5a, 0x98, 0x9c, 0xd2, 0xe7, 0xad, 0x24, 0x3c, 0x0b, 0x6f, 0x84, 0x78, 0xf8, 0x24, - 0xaa, 0x29, 0xc4, 0xb3, 0xd1, 0x73, 0x4f, 0x26, 0xf4, 0xc3, 0x49, 0x1b, 0x88, 0x40, 0x9a, 0x03, 0x38, 0x63, 0x4e, - 0x47, 0x74, 0x65, 0xba, 0x7a, 0xb4, 0x11, 0x1b, 0x2f, 0x1d, 0x79, 0x59, 0xf2, 0xd7, 0x02, 0x63, 0x91, 0x72, 0xd0, - 0xcb, 0xb1, 0x46, 0x6b, 0xaa, 0xf1, 0xfd, 0x31, 0xf0, 0x6a, 0xb9, 0x13, 0x8b, 0x1e, 0x19, 0xe5, 0xc2, 0xac, 0xaf, - 0xc2, 0x6e, 0xd9, 0x44, 0xab, 0x1b, 0x18, 0x89, 0x97, 0xc4, 0x14, 0x30, 0xfc, 0x32, 0x62, 0xfc, 0x9f, 0x2b, 0x18, - 0x1f, 0xad, 0xec, 0x32, 0x84, 0xff, 0xf3, 0xdb, 0xf7, 0xe7, 0xa0, 0xbd, 0x72, 0x51, 0xdd, 0xbc, 0x51, 0xb9, 0xa5, - 0x8a, 0x09, 0xfa, 0x20, 0xb5, 0xa7, 0xba, 0x2b, 0xa0, 0xc7, 0x78, 0x2f, 0x38, 0xb8, 0x35, 0x6f, 0x6e, 0x6e, 0x4c, - 0xb0, 0x5b, 0x35, 0xd7, 0x91, 0x4f, 0x3c, 0xe0, 0x54, 0x4d, 0x05, 0x22, 0x67, 0x25, 0x44, 0x0e, 0x41, 0x6f, 0x79, - 0xd6, 0x94, 0xf7, 0x8b, 0xf0, 0xe6, 0x5b, 0xdf, 0x97, 0x85, 0x33, 0x82, 0x55, 0xe3, 0xf2, 0x8a, 0x02, 0x62, 0xd0, - 0x40, 0xc7, 0x64, 0x79, 0xf1, 0x15, 0xb7, 0x0a, 0x98, 0x5e, 0x8d, 0xef, 0xae, 0xb8, 0xe6, 0x21, 0x8b, 0x3a, 0xfc, - 0x62, 0x74, 0x32, 0xf5, 0xae, 0x15, 0xe4, 0x27, 0x07, 0x2a, 0xb8, 0x6c, 0xf9, 0x6c, 0xbc, 0x4e, 0x92, 0x30, 0x30, - 0xa3, 0xf0, 0x46, 0x1d, 0x9e, 0xd0, 0x83, 0xa8, 0xe0, 0xd2, 0xa3, 0xaa, 0x7c, 0x33, 0xf1, 0xbd, 0xc9, 0xc7, 0x81, - 0xfa, 0x68, 0xe3, 0x0d, 0x86, 0x25, 0xae, 0xd1, 0x4e, 0xd5, 0x21, 0x8c, 0x55, 0xf9, 0xd6, 0xf7, 0x4f, 0x0e, 0xa8, - 0xc5, 0xf0, 0xe4, 0x60, 0xea, 0x5d, 0x0f, 0xa5, 0x04, 0x30, 0x5c, 0x3b, 0x3a, 0xe0, 0x81, 0x36, 0x33, 0x7b, 0xb2, - 0x18, 0x23, 0x37, 0x4c, 0x98, 0x96, 0x5f, 0x71, 0x21, 0xa2, 0x0c, 0x8d, 0x57, 0x9b, 0xa0, 0xd0, 0xdc, 0x87, 0x0b, - 0xdd, 0xa7, 0x4f, 0x5a, 0x66, 0x6d, 0xba, 0x90, 0x42, 0xb1, 0xa1, 0x32, 0x0f, 0xab, 0x18, 0x18, 0x4f, 0x46, 0xd7, - 0x44, 0xc0, 0x38, 0x5f, 0x37, 0x26, 0xa9, 0x81, 0x79, 0x74, 0xdc, 0x15, 0xe8, 0x15, 0xf9, 0x4f, 0xe9, 0xde, 0x3b, - 0x81, 0xdc, 0xd9, 0x12, 0xe2, 0xd6, 0x25, 0xcd, 0x0a, 0x9d, 0x42, 0x1e, 0x0d, 0x10, 0x54, 0x22, 0xf8, 0x1d, 0xd2, - 0x76, 0x68, 0xbe, 0x0e, 0xb9, 0xdb, 0xb2, 0x10, 0x3c, 0x6e, 0x2a, 0xb2, 0xa5, 0x09, 0xb8, 0x9c, 0x16, 0x56, 0xa8, - 0x57, 0x5e, 0x2f, 0x11, 0x1b, 0xf2, 0x41, 0xdc, 0xb4, 0x64, 0xa0, 0xa9, 0xd3, 0x12, 0xa3, 0x44, 0x67, 0xc1, 0x77, - 0x4f, 0x52, 0x0f, 0x31, 0x43, 0xbb, 0x88, 0x8d, 0xf0, 0x32, 0xa7, 0x4d, 0x31, 0x21, 0xca, 0x5e, 0x98, 0xe6, 0x61, - 0x9a, 0x69, 0xd5, 0x87, 0x8f, 0x36, 0x01, 0x12, 0xb3, 0x78, 0x30, 0x2c, 0xee, 0x83, 0xc4, 0x1d, 0x9b, 0xb4, 0x99, - 0x55, 0xe5, 0x9b, 0xe9, 0xd8, 0xcf, 0x16, 0x9b, 0x0e, 0xc1, 0xc2, 0x0d, 0xa6, 0x3e, 0x3b, 0x77, 0xc7, 0xdf, 0x61, - 0x9d, 0x97, 0x63, 0xff, 0x05, 0x54, 0x48, 0xd5, 0xe1, 0xa3, 0x0d, 0x91, 0xeb, 0x3a, 0x84, 0x9d, 0xd2, 0x16, 0x28, - 0x7f, 0x87, 0x27, 0x56, 0x62, 0x11, 0xb5, 0xc6, 0xc1, 0x12, 0x89, 0x25, 0x8c, 0x5a, 0x1c, 0x19, 0x4f, 0xec, 0x03, - 0x7b, 0x53, 0xe1, 0xa7, 0x16, 0xc6, 0x15, 0x8a, 0x13, 0x2c, 0xef, 0x4c, 0x79, 0xb0, 0x44, 0x4a, 0xdf, 0x85, 0x37, - 0x62, 0xa4, 0x1c, 0x00, 0x14, 0x88, 0xf2, 0xf4, 0xc5, 0xe8, 0x44, 0x56, 0xfe, 0xa0, 0x84, 0x9c, 0xfa, 0x85, 0x5f, - 0xa9, 0xaa, 0xe4, 0x69, 0x9e, 0x56, 0xb7, 0xea, 0xf0, 0xe4, 0x40, 0xae, 0x3d, 0x1c, 0xf5, 0xce, 0xa4, 0xc9, 0x61, - 0xaf, 0xe2, 0x76, 0x7c, 0x91, 0x3f, 0xa4, 0x97, 0x0a, 0xdc, 0x85, 0x53, 0x28, 0x01, 0x18, 0x15, 0x9b, 0x54, 0xc8, - 0x0f, 0x24, 0x46, 0xcc, 0x09, 0x14, 0xed, 0x1e, 0x81, 0x1f, 0x43, 0xbd, 0x97, 0x2d, 0x21, 0xd9, 0x5f, 0x8a, 0xde, - 0x46, 0xfc, 0xdf, 0x1c, 0x24, 0x28, 0xcf, 0x66, 0x41, 0x1c, 0x46, 0x2a, 0x4c, 0xb3, 0x9c, 0x1d, 0x49, 0x91, 0xb2, - 0xb2, 0xe1, 0x84, 0x6b, 0xc9, 0x2a, 0x00, 0xec, 0xa0, 0xdc, 0x54, 0x9a, 0xf7, 0x48, 0xcf, 0x7f, 0x28, 0x7c, 0x32, - 0x25, 0xa4, 0x95, 0x0d, 0xb0, 0x39, 0xeb, 0xd4, 0xc5, 0x5b, 0xcf, 0xf8, 0x5b, 0x68, 0x2c, 0x5d, 0x63, 0xec, 0x1a, - 0xef, 0x83, 0xcb, 0xb4, 0x76, 0xf1, 0xb2, 0x8c, 0x71, 0x06, 0xeb, 0x6b, 0x10, 0x67, 0xa9, 0x78, 0xaf, 0xf0, 0x2c, - 0x6e, 0x19, 0x72, 0xee, 0x46, 0x73, 0x26, 0x12, 0xb5, 0x89, 0xb7, 0x42, 0x42, 0xa0, 0x4b, 0x60, 0x81, 0x20, 0x64, - 0x0f, 0xb8, 0x01, 0x9d, 0x67, 0x4d, 0x92, 0xc8, 0xff, 0x81, 0xdd, 0xc1, 0x75, 0x32, 0x4e, 0xc2, 0x15, 0x48, 0xa6, - 0xdc, 0x39, 0xd7, 0x34, 0x18, 0xc0, 0xd4, 0xec, 0xf3, 0xb9, 0x4f, 0x9f, 0x98, 0x94, 0x3b, 0x2c, 0x09, 0xe7, 0x73, - 0x9f, 0x69, 0x52, 0x8e, 0xb1, 0xec, 0x33, 0xa7, 0x0f, 0x6c, 0x11, 0x9f, 0x5a, 0x4f, 0x9b, 0x0e, 0x56, 0xce, 0x01, - 0x0a, 0x9d, 0x3e, 0x20, 0x2e, 0x32, 0xa1, 0x42, 0x26, 0x5c, 0x13, 0xe7, 0x22, 0x3f, 0xb8, 0xe6, 0x34, 0x5c, 0x8f, - 0x7d, 0x66, 0xe2, 0x69, 0x80, 0x4f, 0x6e, 0xc6, 0xeb, 0xf1, 0xd8, 0xa7, 0xa4, 0x60, 0x10, 0x65, 0x2d, 0x8c, 0x51, - 0xfa, 0x99, 0xea, 0x7d, 0xe4, 0xd4, 0x92, 0xf2, 0xf0, 0xc1, 0x32, 0x12, 0x6e, 0x0b, 0xf4, 0x81, 0x04, 0x24, 0x9d, - 0xd5, 0x33, 0x3d, 0x50, 0xe1, 0x96, 0xc2, 0x62, 0xb5, 0x5f, 0xc3, 0xd2, 0x0d, 0x2e, 0xd4, 0xf7, 0x08, 0x61, 0xc5, - 0x0d, 0xa6, 0xca, 0x0b, 0xda, 0xbb, 0xaa, 0xa1, 0x92, 0x81, 0x17, 0xcf, 0x21, 0xa7, 0x1a, 0xea, 0x4b, 0xcf, 0x9d, - 0x07, 0x61, 0x9c, 0x78, 0x13, 0xf5, 0xb2, 0xff, 0xd2, 0xd3, 0x2e, 0x96, 0x89, 0xa6, 0x5f, 0x1a, 0x7f, 0x95, 0xb3, - 0x7d, 0x09, 0x4c, 0x89, 0xc9, 0xbe, 0x1a, 0xea, 0xc8, 0xa7, 0x67, 0x5b, 0x3d, 0x81, 0x91, 0xb1, 0xce, 0x5f, 0x07, - 0x50, 0xab, 0x94, 0x37, 0x0c, 0x13, 0x42, 0x42, 0xde, 0xb0, 0xbf, 0xea, 0x7d, 0x12, 0xb5, 0x7c, 0xbb, 0xde, 0x20, - 0xd3, 0x90, 0xe4, 0xc4, 0x17, 0x43, 0xdd, 0x0b, 0xff, 0x50, 0x7a, 0x7e, 0x20, 0xfb, 0x36, 0x14, 0xc8, 0xf8, 0xe8, - 0xdb, 0x22, 0x07, 0xf2, 0x68, 0x93, 0xa4, 0x60, 0x58, 0x18, 0x84, 0x89, 0x02, 0xf1, 0xdb, 0xe0, 0x83, 0xa3, 0xb2, - 0x2d, 0x34, 0xef, 0x55, 0xd3, 0x53, 0x8e, 0x05, 0x9e, 0x23, 0x2d, 0x45, 0xf9, 0x24, 0x84, 0x9b, 0x80, 0x50, 0xa4, - 0x85, 0x68, 0x4d, 0xdc, 0x03, 0x0f, 0x96, 0xaf, 0xc0, 0xbf, 0x49, 0x78, 0xbf, 0x48, 0xcf, 0x1f, 0x6d, 0xe2, 0x53, - 0x41, 0xd4, 0xdf, 0xc4, 0xb8, 0x96, 0xc0, 0xae, 0x70, 0x2a, 0x9f, 0xaa, 0xca, 0xa9, 0xa0, 0x44, 0x58, 0xb7, 0x80, - 0x5e, 0x35, 0xc1, 0xee, 0x46, 0x22, 0x32, 0x3e, 0x4f, 0x3f, 0x2e, 0x18, 0xb0, 0xd2, 0xd1, 0x83, 0x90, 0x4c, 0x19, - 0x6f, 0x95, 0x80, 0x5d, 0x35, 0x12, 0x0c, 0xc0, 0x5c, 0x9c, 0x47, 0x18, 0xa5, 0x57, 0xc0, 0x48, 0x42, 0x9c, 0x32, - 0x31, 0x47, 0x23, 0x94, 0x53, 0xc5, 0x79, 0xc1, 0x6a, 0x9d, 0x60, 0xfc, 0x79, 0x18, 0x00, 0x4b, 0x55, 0x05, 0x2f, - 0x89, 0x80, 0xeb, 0xf3, 0xcb, 0x4f, 0xaa, 0x2a, 0xde, 0xb4, 0x5a, 0xc6, 0xe5, 0x31, 0x80, 0xe3, 0x70, 0x1a, 0xa8, - 0xbd, 0x81, 0xc7, 0x88, 0x4f, 0x63, 0x62, 0xe4, 0xc9, 0x5b, 0xb4, 0x09, 0x5a, 0x39, 0xd4, 0x20, 0x90, 0x09, 0xf5, - 0xd3, 0xd7, 0xfc, 0xda, 0xc9, 0x42, 0x4c, 0xea, 0xc2, 0x34, 0x47, 0x20, 0x89, 0x3c, 0x05, 0xd8, 0x0d, 0x1e, 0x6d, - 0xdc, 0xcc, 0x80, 0x4e, 0x3d, 0x57, 0xc9, 0x7a, 0x6e, 0x84, 0x60, 0x18, 0xa5, 0x57, 0xb9, 0x3b, 0x6b, 0x3e, 0x5f, - 0xd8, 0x92, 0x54, 0xae, 0xa0, 0x3d, 0xdb, 0x80, 0x5b, 0xad, 0xad, 0x22, 0x6f, 0xe9, 0x46, 0x77, 0x64, 0xe4, 0x66, - 0xc8, 0x96, 0x70, 0xba, 0xaa, 0x10, 0x3d, 0x20, 0x00, 0x10, 0x69, 0x50, 0x95, 0x6f, 0xb2, 0x32, 0xc6, 0x67, 0x9b, - 0x59, 0xfa, 0xc0, 0xb7, 0xae, 0xd4, 0xa7, 0xcc, 0x22, 0x29, 0x23, 0x35, 0xe9, 0x6b, 0x71, 0xc3, 0xf4, 0xe2, 0xe2, - 0xf4, 0x82, 0xe2, 0x46, 0xc3, 0xc9, 0x10, 0xa5, 0xa0, 0x71, 0xe3, 0xcc, 0x30, 0xd5, 0x65, 0xfd, 0x8a, 0xd2, 0xbb, - 0x3f, 0x74, 0x39, 0x18, 0x2c, 0x47, 0x00, 0xcb, 0x51, 0x23, 0x80, 0x75, 0xc5, 0x8a, 0x00, 0x2f, 0x02, 0x5c, 0x48, - 0x84, 0x1c, 0x08, 0x65, 0xc1, 0x54, 0xb2, 0x2d, 0x14, 0xc1, 0xd1, 0xa0, 0xb1, 0xd3, 0xd1, 0x88, 0x06, 0x83, 0x10, - 0x5b, 0x45, 0xe9, 0xc9, 0x01, 0xd5, 0x26, 0xa2, 0x48, 0x95, 0x00, 0x0c, 0x11, 0xcc, 0x30, 0x87, 0x02, 0xa4, 0x01, - 0x1f, 0x38, 0xf9, 0x45, 0xc7, 0x5a, 0xa2, 0xf2, 0xd9, 0x39, 0x2d, 0x32, 0x3c, 0xd8, 0x4a, 0x1d, 0x9e, 0x60, 0x62, - 0x4f, 0x20, 0xeb, 0x10, 0xfa, 0xea, 0xe4, 0x80, 0x1e, 0x95, 0xd2, 0x89, 0xc8, 0x3b, 0x11, 0x52, 0xc7, 0x1e, 0xef, - 0xe0, 0x5e, 0x47, 0x25, 0x4e, 0xd8, 0x0a, 0x4a, 0xdd, 0x54, 0x55, 0x96, 0x9c, 0xc1, 0xe2, 0x31, 0xf6, 0x20, 0x00, - 0x8f, 0x0d, 0x8e, 0x0f, 0xaa, 0xb2, 0x74, 0x6f, 0x71, 0xe6, 0xe2, 0x8d, 0x7b, 0xab, 0x39, 0xfc, 0x55, 0x7e, 0xd6, - 0xe2, 0xe2, 0x59, 0x9b, 0xf0, 0xc5, 0x05, 0xef, 0x3a, 0xc1, 0x58, 0x6b, 0x0b, 0xb4, 0x5a, 0xaa, 0x59, 0xdc, 0x85, - 0x58, 0xdc, 0x69, 0xc3, 0xe2, 0x4e, 0xb7, 0x2c, 0xae, 0xcf, 0x17, 0x52, 0xc9, 0x40, 0x17, 0xa1, 0xc7, 0x74, 0x06, - 0x3c, 0xce, 0x8f, 0xf4, 0xf8, 0x39, 0x43, 0x38, 0x99, 0xb1, 0x0f, 0x16, 0xc3, 0x0d, 0xb0, 0xaa, 0x83, 0x8b, 0x04, - 0x88, 0xea, 0xc4, 0xb3, 0x53, 0x37, 0x91, 0x24, 0x03, 0x9a, 0x5f, 0x9e, 0x2f, 0xec, 0x52, 0x6c, 0x68, 0x68, 0x8b, - 0x86, 0x99, 0x2e, 0xb6, 0xcc, 0x74, 0x52, 0x38, 0xba, 0x7c, 0xda, 0x74, 0x08, 0xe5, 0x49, 0xc1, 0x1e, 0x04, 0x2f, - 0x0a, 0xdc, 0x32, 0xc5, 0x7d, 0xd8, 0x8c, 0x63, 0xa5, 0x1d, 0xb5, 0x72, 0xe3, 0xf8, 0x26, 0x8c, 0xc0, 0x0c, 0x01, - 0xba, 0xb9, 0xdf, 0x96, 0x5a, 0x7a, 0x01, 0x8f, 0x70, 0xd6, 0xb8, 0x99, 0xf2, 0xf7, 0xf2, 0x96, 0x6a, 0x75, 0x3a, - 0x54, 0x63, 0xe5, 0x26, 0x09, 0x8b, 0x10, 0xe8, 0x2e, 0xa4, 0xc2, 0xf8, 0x7f, 0xb2, 0xcd, 0x6a, 0x70, 0x88, 0x2f, - 0x61, 0x75, 0xc4, 0xd0, 0x2b, 0x60, 0xc1, 0x48, 0xef, 0x18, 0xe8, 0x1b, 0x29, 0x5a, 0x6a, 0x94, 0x01, 0xfe, 0x27, - 0x3c, 0xae, 0x5a, 0x24, 0xf9, 0xf3, 0x3a, 0x47, 0xba, 0xb5, 0x72, 0xa7, 0xef, 0xc1, 0xda, 0x45, 0x6b, 0x19, 0xe0, - 0xb9, 0x22, 0xc7, 0x46, 0x8d, 0x88, 0x27, 0x9c, 0xe4, 0x48, 0x12, 0xb1, 0x24, 0xb7, 0x0b, 0x86, 0x90, 0x02, 0xae, - 0x39, 0xbb, 0xdc, 0xb4, 0xd2, 0x83, 0xb9, 0xa7, 0x57, 0xb0, 0x26, 0xa0, 0x36, 0x7f, 0x30, 0xcc, 0x84, 0x6e, 0xbe, - 0xe1, 0x1c, 0xe9, 0xa0, 0x0e, 0xbd, 0x80, 0xa4, 0xe7, 0xb6, 0xb8, 0x4c, 0x8f, 0x22, 0xa0, 0x5a, 0xa0, 0x3c, 0x7c, - 0x3c, 0xc7, 0x5f, 0xce, 0x65, 0xfa, 0x78, 0x8c, 0xbf, 0x5a, 0x97, 0x99, 0xaa, 0xaa, 0x24, 0x45, 0x90, 0xe6, 0xac, - 0x0e, 0x0b, 0xfb, 0x89, 0x8c, 0xb2, 0xef, 0xb1, 0x6d, 0xf8, 0x02, 0x3f, 0x7c, 0xb4, 0x89, 0x21, 0x0c, 0x81, 0x3c, - 0x87, 0xc0, 0x8a, 0xf4, 0xb4, 0xb6, 0x7c, 0xde, 0x50, 0x3e, 0xd6, 0xff, 0x60, 0xc2, 0x8f, 0xbb, 0x24, 0xcc, 0x69, - 0x4a, 0x51, 0x06, 0x72, 0x35, 0xf6, 0x02, 0x37, 0xba, 0xbb, 0xa2, 0x5b, 0x88, 0x26, 0x09, 0x79, 0x1f, 0xe4, 0xc2, - 0x81, 0xbb, 0xa2, 0x0d, 0x48, 0x22, 0x29, 0xa8, 0xee, 0x38, 0xa1, 0x1f, 0xfc, 0x10, 0x49, 0xfc, 0x5d, 0xe1, 0x1a, - 0xcb, 0x17, 0xa4, 0xf0, 0xa1, 0xab, 0x47, 0x1b, 0x8d, 0x55, 0xbb, 0x29, 0xcd, 0xb6, 0xc4, 0x40, 0xc2, 0xf2, 0xe0, - 0x95, 0x78, 0x39, 0xf5, 0x7a, 0x68, 0xe4, 0x31, 0x0e, 0x6f, 0xcd, 0x47, 0x9b, 0xe4, 0x54, 0x5d, 0xba, 0xd1, 0x47, - 0x36, 0x35, 0x27, 0x5e, 0x34, 0xf1, 0x81, 0x79, 0x1c, 0xfb, 0x6e, 0xf0, 0x91, 0x3f, 0x9a, 0xe1, 0x3a, 0x41, 0xb3, - 0xad, 0x9d, 0x37, 0x68, 0x01, 0x13, 0x12, 0x24, 0x22, 0x57, 0x5b, 0x03, 0x05, 0xe5, 0xc5, 0x48, 0x5c, 0xeb, 0x73, - 0x46, 0x31, 0xaf, 0x65, 0x80, 0xd7, 0x01, 0x58, 0x92, 0x41, 0x18, 0x07, 0x43, 0xc5, 0xf5, 0x52, 0x0d, 0x79, 0xaa, - 0xa4, 0x47, 0xcb, 0xf2, 0x10, 0x5f, 0x61, 0x0f, 0xff, 0xfd, 0xe7, 0xa0, 0xe4, 0x3e, 0x9f, 0xcb, 0x7a, 0xf9, 0xbc, - 0x19, 0x42, 0xa9, 0x49, 0xee, 0x83, 0xf7, 0xf8, 0x38, 0x67, 0x30, 0x9b, 0x3f, 0x2d, 0x37, 0x76, 0xe3, 0x78, 0xbd, - 0x64, 0x53, 0x52, 0x86, 0x9d, 0xe6, 0x83, 0x2a, 0xde, 0x43, 0xe4, 0x81, 0xfd, 0x73, 0xdd, 0x3a, 0x3e, 0x7c, 0x01, - 0x66, 0x7c, 0xc0, 0x50, 0x86, 0xb3, 0x99, 0x9a, 0x8b, 0x02, 0x76, 0x34, 0x73, 0x0e, 0xff, 0xb9, 0x7e, 0xfd, 0xca, - 0x7e, 0x9d, 0x35, 0x0e, 0x80, 0x31, 0x16, 0x36, 0x49, 0x9c, 0x2f, 0x96, 0xc6, 0x2b, 0x66, 0x34, 0x73, 0x83, 0xe6, - 0xe9, 0x5c, 0x14, 0xb6, 0xf8, 0x8a, 0xb1, 0x29, 0x30, 0xdc, 0x46, 0xa5, 0xf4, 0xca, 0x67, 0xd7, 0x2c, 0xb3, 0x77, - 0xaa, 0x7e, 0xac, 0xa6, 0x05, 0x06, 0x64, 0xe5, 0xba, 0x47, 0xce, 0xd5, 0x49, 0x53, 0x1a, 0xe1, 0x1c, 0xf8, 0xcc, - 0xe5, 0x23, 0x56, 0x3a, 0x52, 0x23, 0x43, 0x95, 0x06, 0xd0, 0x38, 0xb2, 0xd3, 0x86, 0xf2, 0x1e, 0x20, 0xea, 0x86, - 0xb1, 0x19, 0x8e, 0xde, 0x83, 0x04, 0x16, 0x1c, 0x4e, 0x3e, 0x9c, 0x3c, 0x2d, 0x97, 0x9a, 0x34, 0x41, 0xac, 0x4e, - 0xd4, 0xa6, 0x92, 0x90, 0x46, 0xb8, 0x00, 0xa0, 0x2f, 0x8c, 0x10, 0x57, 0xd5, 0xae, 0x8d, 0x52, 0x9c, 0xf9, 0x18, - 0xd3, 0xbb, 0x07, 0x2c, 0x8e, 0x1b, 0x01, 0x96, 0x2d, 0xba, 0xa1, 0xe6, 0xb5, 0x8b, 0xf0, 0xc8, 0xcb, 0x0d, 0xdb, - 0x00, 0x96, 0x00, 0x27, 0x58, 0xfe, 0x16, 0x92, 0x97, 0xab, 0x25, 0x37, 0xe2, 0x8c, 0xe6, 0x63, 0x95, 0x1b, 0xd8, - 0x35, 0xbd, 0xbf, 0x51, 0xf9, 0xa0, 0x0a, 0x64, 0xba, 0x76, 0x68, 0x5a, 0x01, 0xf5, 0x56, 0xa4, 0x4a, 0xd8, 0x81, - 0x18, 0x53, 0x09, 0xbf, 0xb2, 0xd9, 0x8c, 0x4d, 0x92, 0x58, 0x17, 0x32, 0xa6, 0x2c, 0xa4, 0x3a, 0x28, 0xed, 0x1e, - 0x0c, 0xd4, 0x9f, 0x20, 0xb0, 0x8c, 0x88, 0x3c, 0xc8, 0x07, 0x24, 0xee, 0x4c, 0xf5, 0x60, 0xa2, 0x1e, 0x8b, 0x20, - 0xe2, 0x5f, 0x01, 0x29, 0x74, 0x4d, 0x39, 0x0e, 0x8d, 0xd3, 0x9f, 0x7c, 0x5f, 0x84, 0x99, 0xa9, 0xe7, 0x76, 0x54, - 0xb4, 0xed, 0xf8, 0x6e, 0x9c, 0xd7, 0x1d, 0xc7, 0x4e, 0x55, 0x03, 0x1c, 0x9a, 0x3f, 0x96, 0xb6, 0x31, 0x11, 0xa8, - 0x81, 0x7a, 0xf6, 0xf6, 0xc5, 0x0f, 0xaf, 0x5e, 0xee, 0x8b, 0x11, 0xb0, 0xcb, 0x36, 0x74, 0xb9, 0x0e, 0xb6, 0x74, - 0xfa, 0xcb, 0x4f, 0xf7, 0xeb, 0xb6, 0xe5, 0x3c, 0x73, 0x54, 0x83, 0x6c, 0xd0, 0x25, 0xbc, 0x38, 0x09, 0xaf, 0x59, - 0xf4, 0xd9, 0x60, 0x90, 0x3b, 0xaf, 0x1f, 0xee, 0xdb, 0x9f, 0x5f, 0xfd, 0xb4, 0xf7, 0x50, 0x8f, 0x1c, 0x1b, 0x70, - 0x7b, 0x12, 0xae, 0xee, 0x31, 0xbb, 0xb6, 0x6a, 0xa8, 0x13, 0x3f, 0x8c, 0x59, 0xc3, 0x08, 0x5e, 0x9c, 0xbd, 0x7d, - 0x8f, 0xe0, 0xca, 0x59, 0x10, 0xea, 0xea, 0xf3, 0x26, 0xff, 0xf3, 0xbb, 0x57, 0xef, 0xdf, 0xab, 0x06, 0xa6, 0xe4, - 0x8e, 0xe5, 0xde, 0xf9, 0x26, 0xde, 0x41, 0x71, 0x6a, 0xf7, 0x3a, 0x51, 0x35, 0xba, 0x48, 0x17, 0x67, 0x43, 0x65, - 0x95, 0x6d, 0xce, 0xa9, 0x1d, 0xff, 0x32, 0xdd, 0x7e, 0xf7, 0x9a, 0x57, 0x0d, 0x3e, 0xda, 0x4e, 0x52, 0x0b, 0x25, - 0x4b, 0x2f, 0xb8, 0xaa, 0x29, 0x75, 0x6f, 0x6b, 0x4a, 0xe1, 0xfa, 0x58, 0xc1, 0x8f, 0xeb, 0x70, 0x29, 0xb1, 0x23, - 0xec, 0x76, 0x37, 0xb8, 0xa4, 0x3b, 0xdc, 0x67, 0x0c, 0x9a, 0xa7, 0x54, 0x29, 0x8f, 0xba, 0xa6, 0x98, 0x5f, 0xbc, - 0x32, 0xd8, 0x4e, 0x7c, 0xb0, 0xbc, 0x67, 0xb2, 0x1a, 0xb2, 0xc8, 0xaa, 0x72, 0xbf, 0x99, 0x41, 0xe9, 0x56, 0x40, - 0xcd, 0x48, 0x75, 0xc3, 0x69, 0xca, 0xca, 0x9d, 0x82, 0x39, 0xbb, 0x39, 0x0e, 0x93, 0x24, 0x5c, 0xf6, 0x1c, 0x7b, - 0x75, 0xab, 0x2a, 0x7d, 0x21, 0xec, 0xe0, 0xd6, 0xf6, 0xbd, 0xdf, 0xfe, 0x53, 0x42, 0xf3, 0x54, 0x7e, 0x95, 0xb0, - 0xe5, 0x8a, 0x45, 0x6e, 0xb2, 0x8e, 0x58, 0xaa, 0xfc, 0xf6, 0xbf, 0x2f, 0x4a, 0x17, 0xfb, 0xbe, 0xdc, 0x86, 0x58, - 0x7a, 0xb9, 0xc9, 0x95, 0x1f, 0xde, 0x3c, 0xc8, 0xfd, 0xea, 0x76, 0x54, 0x5e, 0x78, 0xf3, 0x45, 0x56, 0xfb, 0x34, - 0xd9, 0x32, 0x37, 0x31, 0x7a, 0xd2, 0x07, 0x28, 0x67, 0xe1, 0x4d, 0xef, 0xb7, 0xff, 0x64, 0x02, 0x9b, 0x9d, 0xbb, - 0xae, 0x7e, 0xa0, 0xc5, 0x15, 0xad, 0xaf, 0x53, 0x59, 0x62, 0x78, 0x5f, 0x59, 0xe0, 0x4a, 0x21, 0xed, 0xca, 0xaa, - 0x6e, 0x6e, 0xcb, 0x9c, 0xbe, 0xf3, 0xe6, 0x8b, 0xcf, 0x9d, 0x14, 0x00, 0x74, 0xe7, 0xac, 0xa0, 0xd2, 0x17, 0x98, - 0xd6, 0xa8, 0xb7, 0xff, 0x82, 0x7d, 0xe6, 0xbc, 0x76, 0x4d, 0xe9, 0x4b, 0xcc, 0x86, 0x4b, 0x6e, 0x5f, 0x8c, 0x46, - 0x59, 0x4a, 0x5a, 0xb9, 0x3d, 0x78, 0x06, 0x9e, 0x56, 0x4a, 0x38, 0x7b, 0xd1, 0xb3, 0x75, 0x0a, 0xd9, 0xb3, 0x07, - 0x40, 0xd0, 0xc6, 0xbd, 0x06, 0x1c, 0xcd, 0xf8, 0x9a, 0x5c, 0xd5, 0x2a, 0xdf, 0xae, 0x20, 0x6b, 0x28, 0xc5, 0x74, - 0xa6, 0x99, 0xd6, 0xd0, 0xa8, 0x1f, 0xce, 0x4d, 0xe4, 0xae, 0x48, 0x49, 0xa0, 0xa0, 0xc6, 0x04, 0x84, 0x2e, 0xa5, - 0x5b, 0xf4, 0xb5, 0xeb, 0x5f, 0xef, 0x77, 0xa1, 0x6a, 0xa6, 0x60, 0x48, 0x9a, 0xff, 0x3c, 0xe2, 0x8d, 0x74, 0x79, - 0x7f, 0xda, 0x8d, 0x69, 0xe2, 0x5e, 0x35, 0x99, 0xd6, 0xbf, 0xd9, 0x6d, 0x5a, 0x7f, 0xbe, 0x97, 0x69, 0xfd, 0x9b, - 0x2f, 0x6e, 0x5a, 0xff, 0x4a, 0x36, 0xad, 0x87, 0x4d, 0xfc, 0x8a, 0xed, 0x65, 0xc9, 0x2c, 0xac, 0x8d, 0xc2, 0x9b, - 0x78, 0xe0, 0xf0, 0x4b, 0x4f, 0x3c, 0x59, 0x30, 0x90, 0x22, 0x71, 0x70, 0xf9, 0xe1, 0x1c, 0x0c, 0x8e, 0x9b, 0x4d, - 0x8a, 0xbf, 0x94, 0x41, 0xb1, 0x1f, 0xce, 0x55, 0x29, 0x50, 0x7e, 0x20, 0x02, 0xe5, 0x43, 0x70, 0x80, 0x7f, 0xde, - 0x3a, 0xcf, 0x2f, 0x9c, 0x7e, 0xdb, 0x81, 0x40, 0x33, 0x20, 0x18, 0xc0, 0x02, 0xbb, 0xdf, 0x6e, 0x43, 0xc1, 0x8d, - 0x54, 0xd0, 0x82, 0x02, 0x4f, 0x2a, 0xe8, 0x40, 0xc1, 0x44, 0x2a, 0x38, 0x82, 0x82, 0xa9, 0x54, 0x70, 0x0c, 0x05, - 0xd7, 0x6a, 0x7a, 0x11, 0x64, 0x8e, 0x03, 0xc7, 0xfa, 0x65, 0x21, 0x47, 0x4a, 0x26, 0xc5, 0x12, 0x55, 0x8e, 0x0d, - 0x11, 0xb0, 0xd3, 0x3c, 0xd4, 0xb9, 0x89, 0xfa, 0xe8, 0xab, 0x11, 0xb8, 0xd2, 0x83, 0x50, 0xcf, 0x00, 0x91, 0x28, - 0xd5, 0x6c, 0x8b, 0xd7, 0x6a, 0x2f, 0x33, 0xb4, 0xb7, 0x8d, 0x96, 0x30, 0x5c, 0xef, 0xa1, 0x1b, 0x95, 0xa8, 0xdc, - 0x79, 0xba, 0xc8, 0xa2, 0x77, 0xad, 0x07, 0xb9, 0x37, 0x62, 0x1b, 0x62, 0x18, 0x83, 0x6a, 0xfa, 0x25, 0xf2, 0x07, - 0x56, 0x12, 0x82, 0xb3, 0x99, 0x88, 0x5a, 0x25, 0x3e, 0xa0, 0xa0, 0x37, 0x42, 0xdf, 0xcd, 0x03, 0x8c, 0xf1, 0x58, - 0x77, 0x34, 0xfa, 0x65, 0x16, 0x42, 0x8c, 0xae, 0xb9, 0x6b, 0x23, 0x71, 0xe7, 0xbd, 0x85, 0x41, 0x32, 0xee, 0xde, - 0x1c, 0x62, 0xc2, 0x9e, 0x4e, 0x7b, 0x2b, 0xe3, 0x66, 0xc1, 0x82, 0xde, 0x8c, 0x5b, 0x81, 0xc2, 0xfa, 0x93, 0x91, - 0xcf, 0x52, 0x17, 0xd6, 0x69, 0xb8, 0x27, 0xf2, 0xb7, 0x34, 0x4a, 0x33, 0xdb, 0x4a, 0xb9, 0x61, 0x95, 0x26, 0xcb, - 0xbf, 0xbf, 0x84, 0x19, 0xcc, 0x4b, 0x36, 0x5e, 0xcf, 0x95, 0xb3, 0x70, 0xbe, 0xd3, 0xe4, 0x45, 0x7e, 0x05, 0xa3, - 0x54, 0x49, 0xd1, 0x67, 0x8a, 0xed, 0xcd, 0xbf, 0x45, 0x8f, 0x69, 0xb1, 0x7e, 0x02, 0x63, 0x53, 0x12, 0x42, 0xd9, - 0xf0, 0x1d, 0x80, 0xb6, 0x64, 0x54, 0x72, 0x06, 0xf0, 0x93, 0x9e, 0xcf, 0x5d, 0x69, 0x3c, 0xc3, 0x1f, 0x59, 0x1c, - 0xbb, 0x73, 0x51, 0xbf, 0x3a, 0x4e, 0xf0, 0xaf, 0xca, 0x6e, 0xfa, 0x08, 0x40, 0x90, 0x19, 0x7b, 0x15, 0x53, 0x21, - 0xb0, 0x60, 0x06, 0x13, 0x3a, 0x58, 0xb4, 0xdc, 0xae, 0xc6, 0xb3, 0x60, 0x79, 0x8a, 0x26, 0x2e, 0x80, 0x44, 0xae, - 0x99, 0x5f, 0x2e, 0x4c, 0xdc, 0x79, 0xb9, 0x88, 0xd6, 0x3a, 0x95, 0xc7, 0x96, 0x59, 0x98, 0x14, 0x0a, 0x3f, 0xc7, - 0x64, 0xc2, 0x0f, 0xe7, 0xbf, 0xab, 0xbd, 0xc4, 0x16, 0x3b, 0x97, 0xf7, 0x81, 0x11, 0x24, 0x23, 0x0b, 0x61, 0xac, - 0x58, 0x00, 0xc2, 0x5e, 0x90, 0x2c, 0x4c, 0xf4, 0xec, 0xd7, 0x5a, 0x81, 0x6e, 0x58, 0xb8, 0xb6, 0x9b, 0x72, 0x3c, - 0x93, 0x5e, 0x34, 0x1f, 0xbb, 0x9a, 0xd3, 0x3a, 0x36, 0xc4, 0x1f, 0xcb, 0xee, 0xe8, 0x29, 0xf6, 0xa0, 0x4c, 0xbd, - 0xeb, 0xcd, 0x2c, 0x0c, 0x12, 0x73, 0xe6, 0x2e, 0x3d, 0xff, 0xae, 0xb7, 0x0c, 0x83, 0x30, 0x5e, 0xb9, 0x13, 0xd6, - 0xcf, 0x45, 0x37, 0x7d, 0x8c, 0x94, 0xc5, 0x83, 0x35, 0x38, 0x56, 0x2b, 0x62, 0x4b, 0x6a, 0x9d, 0x05, 0xc2, 0x9a, - 0xf9, 0xec, 0x36, 0xe5, 0x9f, 0x2f, 0x54, 0xa6, 0xaa, 0xb8, 0xe5, 0xa8, 0x05, 0xdc, 0x43, 0x78, 0x94, 0x2d, 0x88, - 0x2d, 0xd9, 0xe7, 0xcc, 0x7c, 0xcf, 0x6a, 0x75, 0x22, 0xb6, 0x54, 0xac, 0x4e, 0x63, 0xe7, 0x51, 0x78, 0x33, 0x84, - 0xd1, 0x62, 0x63, 0x33, 0x66, 0xfe, 0x0c, 0xdf, 0x98, 0xe8, 0xd8, 0x2b, 0xfa, 0x31, 0x51, 0xe4, 0x03, 0xbd, 0xb1, - 0x65, 0x1f, 0x5e, 0xf7, 0x5a, 0x8a, 0xdd, 0x5f, 0x7a, 0x81, 0x49, 0xd3, 0x39, 0xb6, 0x57, 0x52, 0x5f, 0x32, 0xfc, - 0xf4, 0x0d, 0x56, 0x77, 0x14, 0xbb, 0x0f, 0x57, 0xfb, 0x99, 0x1f, 0xde, 0xf4, 0x16, 0xde, 0x74, 0xca, 0x82, 0x3e, - 0x8e, 0x39, 0x2b, 0x64, 0xbe, 0xef, 0xad, 0x62, 0x2f, 0xee, 0x2f, 0xdd, 0x5b, 0xde, 0xeb, 0x61, 0x53, 0xaf, 0x6d, - 0xde, 0x6b, 0x7b, 0xef, 0x5e, 0xa5, 0x6e, 0xc0, 0x89, 0x98, 0xfa, 0xe1, 0x43, 0xeb, 0x28, 0x76, 0x69, 0x9e, 0x7b, - 0xf7, 0xba, 0x8a, 0xd8, 0x66, 0xe9, 0x46, 0x73, 0x2f, 0xe8, 0xd9, 0xa9, 0x75, 0xbd, 0xa1, 0x8d, 0xf1, 0xb0, 0xdb, - 0xed, 0xa6, 0xd6, 0x54, 0x3c, 0xd9, 0xd3, 0x69, 0x6a, 0x4d, 0xc4, 0xd3, 0x6c, 0x66, 0xdb, 0xb3, 0x59, 0x6a, 0x79, - 0xa2, 0xa0, 0xdd, 0x9a, 0x4c, 0xdb, 0xad, 0xd4, 0xba, 0x91, 0x6a, 0xa4, 0x16, 0xe3, 0x4f, 0x11, 0x9b, 0xf6, 0x71, - 0x23, 0x71, 0x73, 0xf4, 0x63, 0xdb, 0x4e, 0x11, 0x03, 0x5c, 0x14, 0x70, 0x13, 0x4a, 0x15, 0x2f, 0x37, 0x7b, 0xd7, - 0x54, 0xf2, 0xcf, 0x4d, 0x26, 0xb5, 0xf5, 0xa6, 0x6e, 0xf4, 0xf1, 0x52, 0x91, 0x66, 0xe1, 0xba, 0x54, 0x6d, 0x23, - 0xc0, 0x60, 0xde, 0xf6, 0x20, 0x62, 0x6a, 0x7f, 0x1c, 0x46, 0x70, 0x66, 0x23, 0x77, 0xea, 0xad, 0xe3, 0x9e, 0xd3, - 0x5a, 0xdd, 0x8a, 0x22, 0xbe, 0xd7, 0xf3, 0x02, 0x3c, 0x7b, 0xbd, 0x38, 0xf4, 0xbd, 0xa9, 0x28, 0x6a, 0x3a, 0x4b, - 0x4e, 0x4b, 0xef, 0x63, 0xbc, 0x20, 0x0f, 0xa3, 0x5e, 0xb9, 0xbe, 0xaf, 0x58, 0xed, 0x58, 0x61, 0x6e, 0x8c, 0x9a, - 0x0c, 0xc5, 0x8e, 0x09, 0x2e, 0x18, 0x1b, 0xc8, 0x39, 0x5c, 0xdd, 0x66, 0x7b, 0xde, 0x39, 0x5a, 0xdd, 0xa6, 0xdf, - 0x2c, 0xd9, 0xd4, 0x73, 0x15, 0x2d, 0xdf, 0x4d, 0x8e, 0x0d, 0xda, 0x0e, 0x7d, 0xd3, 0xb0, 0x4d, 0xc5, 0xb1, 0x80, - 0xc8, 0xd2, 0x0f, 0xbc, 0xe5, 0x2a, 0x8c, 0x12, 0x37, 0x48, 0xd2, 0x74, 0x74, 0x99, 0xa6, 0xfd, 0x73, 0x4f, 0xbb, - 0xf8, 0xbb, 0x46, 0xb4, 0x90, 0xb4, 0x83, 0xa9, 0x7e, 0x69, 0xbc, 0x62, 0xb2, 0x25, 0x13, 0x90, 0x31, 0xb4, 0x62, - 0x92, 0x2b, 0x13, 0xbd, 0xad, 0x56, 0x26, 0x20, 0x67, 0xd5, 0xc9, 0x30, 0xaa, 0x58, 0x05, 0x29, 0x10, 0x54, 0x78, - 0xc5, 0x06, 0xe7, 0x92, 0x59, 0x14, 0x30, 0x3d, 0x58, 0x99, 0xdc, 0x3a, 0x5f, 0x36, 0xf1, 0x9e, 0xe7, 0xbb, 0x79, - 0xcf, 0x7f, 0x24, 0xfb, 0xf0, 0x9e, 0xe7, 0x5f, 0x9c, 0xf7, 0x7c, 0x59, 0x75, 0xeb, 0x3c, 0x0f, 0x07, 0x6a, 0xa6, - 0xcb, 0x02, 0xd2, 0x14, 0x51, 0xc0, 0xc4, 0x97, 0xc9, 0x7f, 0xeb, 0x5f, 0x27, 0x7a, 0xa3, 0x14, 0xc0, 0x44, 0xb9, - 0x81, 0x81, 0x7f, 0x1b, 0x0c, 0x7e, 0x88, 0xe4, 0xe7, 0xd9, 0x6c, 0xf0, 0x32, 0x94, 0x0a, 0xb2, 0x27, 0x6e, 0xe6, - 0x53, 0x08, 0x6e, 0x45, 0x6f, 0x32, 0x43, 0x2c, 0x48, 0xff, 0x05, 0xb1, 0x71, 0xc8, 0xea, 0x7e, 0x9a, 0x99, 0x43, - 0xf6, 0x8b, 0x43, 0xd0, 0x32, 0xfb, 0x63, 0xe1, 0x01, 0x5d, 0x11, 0x5a, 0xcf, 0x59, 0xc2, 0x43, 0x96, 0x3c, 0xbf, - 0x7b, 0x33, 0xd5, 0xce, 0x43, 0x3d, 0xf5, 0xe2, 0xb7, 0x65, 0xff, 0x63, 0x71, 0x05, 0x91, 0xa7, 0x93, 0x72, 0x93, - 0x46, 0x29, 0xcc, 0x10, 0xbe, 0xa6, 0xe6, 0xa7, 0x85, 0x99, 0xf6, 0xe4, 0x86, 0x3c, 0xcf, 0x68, 0x85, 0x18, 0x73, - 0x3f, 0xbd, 0x0d, 0xe7, 0xf2, 0x30, 0x75, 0x2a, 0x86, 0x6d, 0x99, 0x52, 0x73, 0x6f, 0x9a, 0xa6, 0x7a, 0x5f, 0x00, - 0x42, 0x22, 0xb4, 0x6c, 0x17, 0x13, 0x17, 0xe7, 0x17, 0x5a, 0xae, 0x8b, 0x26, 0x45, 0xf3, 0x39, 0x98, 0x6e, 0x70, - 0xb5, 0x34, 0x87, 0x99, 0xaa, 0x10, 0xf8, 0xc8, 0xa4, 0x47, 0x9a, 0x10, 0xd8, 0x1a, 0xc8, 0x86, 0x70, 0x85, 0x05, - 0xa9, 0xda, 0x1c, 0x13, 0x70, 0xd0, 0xf6, 0x04, 0x82, 0x2c, 0x09, 0x69, 0x17, 0xa1, 0x1d, 0x5e, 0x07, 0x1f, 0x52, - 0x35, 0xe3, 0xfd, 0x70, 0xfb, 0x0d, 0x4f, 0x0e, 0xa0, 0xc1, 0xb0, 0x24, 0xc9, 0xda, 0x61, 0x32, 0x0b, 0xac, 0x44, - 0x7c, 0x63, 0x58, 0xf1, 0x8d, 0xf2, 0x64, 0x23, 0x02, 0x94, 0x25, 0xee, 0xca, 0x04, 0xf1, 0x09, 0xe2, 0x5e, 0x8e, - 0xf1, 0xa4, 0x58, 0x68, 0xfd, 0x75, 0x0c, 0xb8, 0x11, 0x6f, 0xf2, 0x88, 0x7f, 0xfa, 0x93, 0x75, 0x14, 0x87, 0x51, - 0x6f, 0x15, 0x7a, 0x41, 0xc2, 0xa2, 0x14, 0x41, 0x75, 0x81, 0xf0, 0x11, 0xe0, 0xb9, 0xdc, 0x84, 0x2b, 0x77, 0xe2, - 0x25, 0x77, 0x3d, 0x9b, 0xb3, 0x14, 0x76, 0x9f, 0x73, 0x07, 0x76, 0x6d, 0xfd, 0x1e, 0x87, 0xe6, 0x53, 0x64, 0xfc, - 0xa2, 0x2a, 0x3b, 0x23, 0x6f, 0xf3, 0xbe, 0xf4, 0x96, 0x42, 0xb4, 0x01, 0xfb, 0xe1, 0x46, 0xe6, 0x1c, 0xb0, 0x3c, - 0x2c, 0xb5, 0x3d, 0x65, 0x73, 0x03, 0xb1, 0x36, 0x68, 0x80, 0xc4, 0x1f, 0xab, 0xa3, 0x2b, 0x76, 0x7d, 0x31, 0x70, - 0x3c, 0xfa, 0x3e, 0x23, 0xeb, 0xb9, 0x90, 0xd0, 0xd4, 0xd8, 0xa7, 0xe6, 0x98, 0xcd, 0xc2, 0x88, 0x51, 0x38, 0x7f, - 0xa7, 0xbb, 0xba, 0xdd, 0xbf, 0xfb, 0xed, 0xd3, 0xaf, 0xef, 0x27, 0x08, 0x13, 0x4d, 0x74, 0xa6, 0xef, 0xe8, 0xad, - 0x4a, 0xcf, 0x80, 0x35, 0x24, 0xc8, 0x4f, 0xc8, 0x1f, 0x05, 0x5c, 0xb1, 0x6b, 0xa3, 0xa6, 0xae, 0x42, 0x4e, 0xf3, - 0x22, 0xe6, 0xbb, 0x89, 0x77, 0x2d, 0x78, 0xc6, 0xf6, 0xd1, 0xea, 0x56, 0xac, 0x31, 0x12, 0xbc, 0x7b, 0x2c, 0x52, - 0x69, 0x28, 0x62, 0x91, 0xca, 0xc5, 0xb8, 0x48, 0xfd, 0xca, 0x6c, 0x44, 0x20, 0xb1, 0x12, 0xa5, 0xef, 0xac, 0x6e, - 0x65, 0x12, 0x9d, 0x37, 0xcb, 0x28, 0x75, 0x39, 0x02, 0xec, 0xd2, 0x9b, 0x4e, 0x7d, 0x96, 0x16, 0x16, 0xba, 0xb8, - 0x96, 0x12, 0x70, 0x32, 0x38, 0xb8, 0xe3, 0x38, 0xf4, 0xd7, 0x09, 0xab, 0x07, 0x17, 0x01, 0xa7, 0x65, 0xe7, 0xc0, - 0xc1, 0xdf, 0xc5, 0xb1, 0x76, 0x80, 0xdd, 0x86, 0x6d, 0x62, 0xf7, 0x21, 0xe1, 0x83, 0xd9, 0x2e, 0x0e, 0x1d, 0x5e, - 0x65, 0x83, 0x36, 0x6a, 0x26, 0x62, 0x00, 0x59, 0x22, 0xec, 0xad, 0x58, 0x0e, 0x2f, 0xcb, 0x82, 0xde, 0x67, 0x45, - 0x69, 0x71, 0x32, 0xbf, 0xcf, 0x19, 0x7b, 0x56, 0x7f, 0xc6, 0x9e, 0x89, 0x33, 0xb6, 0x7d, 0x67, 0x3e, 0x9c, 0x39, - 0xf0, 0x5f, 0x3f, 0x9f, 0x50, 0xcf, 0x56, 0xda, 0xab, 0x5b, 0xc5, 0x59, 0xdd, 0x2a, 0x66, 0x6b, 0x75, 0xab, 0x60, - 0xd7, 0x68, 0x79, 0x64, 0x58, 0x2d, 0xdd, 0xb0, 0x15, 0x28, 0x84, 0x3f, 0x76, 0xe1, 0x95, 0x73, 0x08, 0xef, 0xa0, - 0x55, 0xa7, 0xfa, 0xae, 0xb5, 0xfd, 0xa8, 0xd3, 0x59, 0x12, 0x48, 0x5b, 0xb7, 0x12, 0x77, 0x3c, 0x66, 0xd3, 0xde, - 0x2c, 0x9c, 0xac, 0xe3, 0x7f, 0xf3, 0xf1, 0x73, 0x20, 0x6e, 0x45, 0x04, 0xa5, 0x7e, 0x44, 0x53, 0x90, 0xee, 0x5d, - 0x33, 0xd1, 0xc3, 0x26, 0x5b, 0xa7, 0x1e, 0x65, 0xa7, 0x68, 0x59, 0x87, 0x35, 0x9b, 0xbc, 0x1e, 0xd0, 0xbf, 0xdb, - 0x2a, 0x35, 0xa3, 0x98, 0xcf, 0x00, 0xcb, 0x56, 0x70, 0xdc, 0x1f, 0x1a, 0x7c, 0x35, 0xed, 0x6e, 0xfd, 0x70, 0x2f, - 0xc4, 0x97, 0x2e, 0x05, 0x51, 0xe1, 0x74, 0x8b, 0x7b, 0x49, 0x6d, 0xef, 0xb5, 0x69, 0x8f, 0x54, 0x7a, 0xdd, 0x42, - 0x10, 0xf2, 0xba, 0x7b, 0x62, 0xf9, 0x87, 0xcf, 0x0e, 0xe1, 0x3f, 0xe2, 0xea, 0xff, 0x91, 0xd4, 0x31, 0xea, 0x2f, - 0x93, 0x02, 0xa3, 0x4e, 0xac, 0x12, 0x32, 0xe2, 0xfb, 0xd7, 0x9f, 0xcd, 0xee, 0xd7, 0x60, 0xef, 0xda, 0x64, 0xb4, - 0x57, 0xae, 0xfd, 0x3c, 0x0c, 0x21, 0x73, 0x7a, 0xb5, 0xba, 0x00, 0x0f, 0x79, 0x60, 0x24, 0x03, 0x68, 0x24, 0xee, - 0x11, 0x64, 0x2f, 0xa2, 0x62, 0x1b, 0xba, 0x4a, 0x9c, 0x35, 0x5d, 0x25, 0xde, 0xed, 0xbe, 0x4a, 0x7c, 0xbf, 0xd7, - 0x55, 0xe2, 0xdd, 0x17, 0xbf, 0x4a, 0x9c, 0x55, 0xaf, 0x12, 0x67, 0xa1, 0xb0, 0xd4, 0x36, 0x5e, 0xaf, 0xf9, 0xcf, - 0x0f, 0xa4, 0x8a, 0x7d, 0x17, 0x0e, 0x3a, 0x36, 0x65, 0x9c, 0x38, 0xff, 0xaf, 0x2f, 0x16, 0xb8, 0x11, 0xdf, 0xa1, - 0xe1, 0x62, 0x7e, 0xb5, 0xe0, 0x98, 0x1d, 0xbf, 0x23, 0x15, 0xfb, 0x61, 0x30, 0xff, 0x19, 0x54, 0xf1, 0x20, 0x0e, - 0x8c, 0xa4, 0x17, 0x5e, 0xfc, 0x73, 0xb8, 0x5a, 0xaf, 0xde, 0x40, 0x5f, 0x1f, 0xbc, 0xd8, 0x1b, 0xfb, 0x2c, 0x0b, - 0xf1, 0x41, 0x86, 0x96, 0x5c, 0xb6, 0x0e, 0xb6, 0xcd, 0xe2, 0xa7, 0x7b, 0x2b, 0x7e, 0xa2, 0xf5, 0x33, 0xff, 0x4d, - 0x16, 0x9c, 0x6a, 0xfd, 0x45, 0x04, 0x42, 0x26, 0x96, 0x06, 0x7d, 0xff, 0xcb, 0xc8, 0x59, 0xa8, 0xd7, 0xcc, 0x52, - 0x58, 0xd6, 0x34, 0xf6, 0xc3, 0xca, 0xfd, 0xbc, 0x5e, 0xeb, 0x46, 0x16, 0x01, 0xb5, 0x2a, 0xce, 0x5f, 0x86, 0xeb, - 0x98, 0x4d, 0xc3, 0x9b, 0x40, 0x35, 0x02, 0x6e, 0x0e, 0x4a, 0x49, 0x24, 0xb3, 0x36, 0x98, 0xbb, 0xfb, 0x3d, 0x32, - 0xca, 0x10, 0x28, 0x01, 0x52, 0xc7, 0xaf, 0x57, 0x26, 0x19, 0x18, 0x98, 0x38, 0x45, 0x35, 0x4b, 0x32, 0xf9, 0x40, - 0xd3, 0xc2, 0xc1, 0xfd, 0x5a, 0x0a, 0xa3, 0xa0, 0xd0, 0xe2, 0x52, 0xe1, 0x58, 0x0b, 0x84, 0x70, 0x51, 0x84, 0x21, - 0xab, 0x59, 0x38, 0xfe, 0x86, 0xe2, 0x77, 0xe4, 0x6f, 0x21, 0x20, 0x44, 0xba, 0xe6, 0xeb, 0xc1, 0x83, 0x72, 0xd1, - 0xe3, 0x0b, 0x09, 0x8c, 0x6f, 0xaf, 0x59, 0xe4, 0xbb, 0x77, 0x9a, 0x9e, 0x86, 0xc1, 0x8f, 0x00, 0x80, 0x97, 0xe1, - 0x4d, 0x20, 0x57, 0xc0, 0x5c, 0x79, 0x35, 0x7b, 0xa9, 0x36, 0x7c, 0x1c, 0xb8, 0x53, 0x49, 0x23, 0xf0, 0xac, 0x95, - 0x3b, 0x67, 0xff, 0x63, 0xd0, 0xbf, 0x7f, 0xd7, 0x53, 0xe3, 0x5d, 0x98, 0x7d, 0xe8, 0x97, 0xd5, 0x1e, 0x9f, 0x79, - 0xfc, 0xf8, 0x41, 0xf3, 0xb4, 0xb5, 0x89, 0xcf, 0xdc, 0x48, 0x8c, 0xa2, 0xa6, 0xb5, 0xde, 0x78, 0x0a, 0x60, 0x14, - 0xe7, 0xe1, 0x7a, 0xb2, 0x40, 0x93, 0xea, 0x2f, 0x37, 0xdf, 0x04, 0xfa, 0xc4, 0x24, 0xf1, 0xd9, 0xd4, 0x4b, 0x45, - 0x39, 0x14, 0xf0, 0xfb, 0xaf, 0x20, 0xfe, 0xf9, 0x9f, 0x08, 0x86, 0xea, 0xae, 0xc9, 0xbc, 0xb1, 0xef, 0xb5, 0x79, - 0xfb, 0x90, 0xcb, 0x9c, 0x47, 0x16, 0x13, 0x4a, 0xba, 0x7a, 0x24, 0x93, 0x96, 0x81, 0x26, 0x47, 0xf1, 0x6d, 0x0a, - 0x50, 0x2c, 0xbe, 0xc2, 0x2c, 0xba, 0xa6, 0x73, 0x97, 0x16, 0x83, 0x71, 0x6c, 0x55, 0x42, 0x32, 0xdc, 0xd0, 0x85, - 0x21, 0xfa, 0x2a, 0xbf, 0x5b, 0x7a, 0x81, 0x81, 0x49, 0x78, 0xaa, 0x6f, 0xdc, 0x5b, 0x48, 0x43, 0x01, 0xc8, 0xad, - 0xfc, 0x0a, 0x0a, 0x0d, 0xd9, 0x91, 0x13, 0x32, 0x6d, 0xaa, 0xb5, 0x90, 0x10, 0xda, 0xc0, 0xd1, 0x57, 0x8a, 0xa2, - 0x28, 0xd9, 0x35, 0x42, 0xc9, 0xee, 0x11, 0x58, 0x8e, 0xd7, 0x01, 0xd0, 0x96, 0xa4, 0xab, 0x5b, 0x2a, 0x81, 0x9b, - 0x01, 0xaa, 0xb6, 0x45, 0x01, 0x8f, 0xb4, 0xdc, 0xb1, 0x45, 0x81, 0xb8, 0xd0, 0x43, 0x94, 0x5c, 0x37, 0x82, 0x84, - 0x0c, 0x3d, 0x05, 0x2f, 0xec, 0xf8, 0x96, 0x4b, 0x82, 0x15, 0x9b, 0x1e, 0x47, 0x7d, 0x56, 0x1f, 0x92, 0x37, 0x90, - 0xb0, 0x20, 0x68, 0x1d, 0x4a, 0x19, 0x36, 0x0c, 0x56, 0x83, 0x1b, 0xf1, 0x5e, 0x74, 0x9b, 0x2c, 0x59, 0xb0, 0x56, - 0x31, 0x25, 0x27, 0x86, 0x48, 0x86, 0x3a, 0x2f, 0x89, 0xd9, 0x02, 0x6c, 0x53, 0xdf, 0x72, 0x41, 0xb4, 0x30, 0xe6, - 0x28, 0xd5, 0x35, 0x26, 0xdc, 0x37, 0x31, 0xe6, 0xb8, 0xad, 0x4c, 0x21, 0xf8, 0x92, 0x86, 0x45, 0x6c, 0xce, 0xbd, - 0x91, 0x91, 0x53, 0xa0, 0xb0, 0x53, 0x5c, 0x5c, 0x24, 0xc0, 0xae, 0xb9, 0xe5, 0x45, 0xcb, 0x34, 0x32, 0x6e, 0x49, - 0x50, 0x14, 0xe9, 0xd5, 0x6e, 0xf8, 0x38, 0x21, 0x2e, 0x64, 0x63, 0x3f, 0x93, 0x4a, 0x3f, 0x0d, 0x93, 0xfe, 0xc8, - 0xee, 0x88, 0x90, 0x10, 0xa8, 0x3e, 0xb2, 0x3b, 0xd0, 0xdb, 0xbf, 0x02, 0x69, 0x8a, 0xba, 0x05, 0x5d, 0x1b, 0x90, - 0x69, 0x69, 0x02, 0xb1, 0x42, 0xb7, 0x1c, 0x20, 0x3b, 0xdd, 0x82, 0xc5, 0x11, 0xc4, 0x81, 0x11, 0xf7, 0xc5, 0x21, - 0xe6, 0xce, 0x24, 0x5a, 0x2d, 0x8c, 0xcd, 0x9a, 0xa3, 0xa1, 0x3f, 0x71, 0x6c, 0xfb, 0xa0, 0x52, 0x1f, 0x04, 0xd9, - 0x75, 0xb5, 0x75, 0x23, 0x19, 0x38, 0xb6, 0xe9, 0x3d, 0xb1, 0x5a, 0xfd, 0x0a, 0x8d, 0x96, 0x42, 0x79, 0x8f, 0x50, - 0xfc, 0x35, 0x7c, 0xb4, 0xd1, 0x2a, 0x07, 0x52, 0x2f, 0x3b, 0x67, 0xe0, 0xd8, 0x52, 0x2e, 0xff, 0x1a, 0x55, 0x49, - 0x3f, 0x05, 0x12, 0xa7, 0xb4, 0x72, 0x23, 0x48, 0x46, 0xa1, 0xc1, 0x31, 0xfa, 0x8b, 0xf2, 0x54, 0xd1, 0xe8, 0xf8, - 0xe8, 0xfa, 0xa8, 0x2f, 0x30, 0x8a, 0xf0, 0x5e, 0x94, 0x3b, 0x28, 0x7d, 0x31, 0x2e, 0x63, 0x38, 0x1e, 0xf6, 0x9e, - 0xe5, 0x1a, 0xbd, 0xad, 0xdc, 0x02, 0xf6, 0xdf, 0x40, 0x3e, 0xad, 0x31, 0x84, 0xdf, 0x80, 0x1a, 0x90, 0xba, 0x66, - 0x67, 0x87, 0x10, 0x2d, 0x49, 0xee, 0xae, 0x48, 0x24, 0xf7, 0xef, 0x0c, 0x89, 0x0e, 0xea, 0xd0, 0xb2, 0xfe, 0xea, - 0xc9, 0xdd, 0x3d, 0xbb, 0x64, 0xc1, 0xb4, 0xd8, 0x61, 0x89, 0x7e, 0xed, 0xdf, 0x5d, 0x01, 0xa3, 0x40, 0x4e, 0xa7, - 0xb0, 0x06, 0xa3, 0xa4, 0x61, 0x80, 0x9b, 0x9f, 0x8e, 0x9b, 0xb7, 0x17, 0x17, 0x83, 0x0d, 0x28, 0x20, 0x6b, 0xd6, - 0x4c, 0x12, 0x8a, 0x43, 0xe2, 0x10, 0x74, 0x6e, 0xd6, 0x04, 0x23, 0xda, 0xb8, 0x13, 0x13, 0x61, 0x49, 0x9a, 0xb7, - 0xf1, 0x78, 0x28, 0xf0, 0x7d, 0xa5, 0xd6, 0xde, 0x6e, 0xa9, 0x75, 0xb2, 0x4b, 0x6a, 0x4d, 0x8e, 0x7b, 0x64, 0xfe, - 0x94, 0x39, 0x30, 0x0a, 0xe6, 0x5c, 0x76, 0x01, 0x2d, 0x88, 0xba, 0xd1, 0xcf, 0x4f, 0xb4, 0xaa, 0xf4, 0x46, 0xb6, - 0xa1, 0x28, 0xfe, 0x96, 0x2e, 0x28, 0x42, 0xa1, 0x2e, 0xcb, 0xc6, 0xcf, 0x72, 0xd9, 0x38, 0xdd, 0x6a, 0x72, 0x97, - 0x2d, 0xc1, 0xfd, 0x4b, 0xee, 0x90, 0xd9, 0xed, 0x20, 0x77, 0x8b, 0xcc, 0x47, 0x2a, 0x39, 0xfa, 0xe5, 0x17, 0x0d, - 0xc9, 0x7d, 0x54, 0xdc, 0x32, 0x8a, 0x5e, 0xa4, 0xc5, 0xaa, 0xb9, 0x9f, 0x5f, 0x5e, 0x0e, 0x52, 0x77, 0x1c, 0x72, - 0x56, 0x2c, 0x6f, 0x9b, 0xa2, 0xa3, 0x97, 0xfc, 0x5a, 0xda, 0x24, 0x99, 0x47, 0x16, 0x01, 0x58, 0x88, 0xe9, 0x4b, - 0x7a, 0xed, 0xcc, 0x06, 0x02, 0x07, 0x59, 0xe3, 0x40, 0xba, 0x5b, 0x3a, 0x4f, 0xe9, 0xaa, 0x72, 0xd5, 0xb5, 0x83, - 0xd4, 0x9d, 0x34, 0xc1, 0xb2, 0x3c, 0x02, 0x61, 0x7d, 0x29, 0x49, 0x10, 0x7a, 0xb6, 0x62, 0xf7, 0x6b, 0x18, 0x00, - 0xa4, 0xff, 0xe5, 0x67, 0xce, 0x0a, 0x80, 0x24, 0x52, 0xb1, 0x65, 0x9d, 0x3f, 0x1e, 0x62, 0x93, 0xcc, 0xcf, 0xb0, - 0x6a, 0xf5, 0x9b, 0x24, 0xef, 0xd9, 0x70, 0x77, 0xad, 0xa2, 0x38, 0x9f, 0xd7, 0xe8, 0x89, 0x71, 0xf0, 0x5d, 0x16, - 0xad, 0x03, 0xcc, 0x42, 0x64, 0x26, 0x91, 0x3b, 0xf9, 0xb8, 0x91, 0xbe, 0xc7, 0x45, 0xa2, 0x20, 0x2e, 0x2e, 0x2a, - 0x15, 0xfa, 0x2e, 0x06, 0xed, 0x66, 0x3d, 0xab, 0x15, 0x4b, 0x82, 0x9a, 0xde, 0x43, 0xbb, 0xed, 0x3e, 0x9b, 0x1d, - 0x96, 0xe4, 0xa7, 0xad, 0x4e, 0x51, 0xba, 0x9e, 0x8d, 0x63, 0x19, 0xfe, 0xca, 0x1d, 0x5b, 0xff, 0xf8, 0x4f, 0xc7, - 0xfc, 0x9b, 0xa5, 0x35, 0xfa, 0x9c, 0x21, 0x40, 0xfb, 0x82, 0x62, 0x5a, 0x56, 0xd3, 0x54, 0x4a, 0x9a, 0x86, 0x35, - 0xf3, 0x7c, 0xdf, 0xf4, 0xc1, 0xbd, 0x68, 0xf3, 0x59, 0xd3, 0xc3, 0x7e, 0xd6, 0x90, 0x2e, 0xe2, 0x33, 0xfa, 0x29, - 0xee, 0x94, 0x64, 0xb1, 0x5e, 0x8e, 0x37, 0xb2, 0xa0, 0x5c, 0x92, 0x9f, 0x57, 0x65, 0xe6, 0xf2, 0x67, 0x67, 0xb3, - 0x59, 0x51, 0x6a, 0x6c, 0x2b, 0x87, 0x28, 0xf9, 0x7d, 0x68, 0xdb, 0x76, 0x19, 0xbe, 0x4d, 0x07, 0x85, 0x0e, 0x86, - 0x89, 0x42, 0xf8, 0xee, 0xee, 0x3d, 0xf5, 0x07, 0x8d, 0x96, 0xba, 0x6a, 0x3a, 0x8f, 0xb4, 0xd5, 0xfe, 0x5f, 0x0c, - 0x05, 0x51, 0xc3, 0xae, 0xe3, 0x5f, 0xdd, 0x2b, 0x5b, 0x7a, 0x2a, 0x1f, 0xe0, 0xfb, 0x35, 0xde, 0xb1, 0xd7, 0xf7, - 0x68, 0xda, 0xb4, 0xbd, 0x53, 0x2b, 0x27, 0xbb, 0x05, 0x9b, 0xa5, 0x3e, 0x59, 0x2a, 0x79, 0x09, 0x5b, 0xc6, 0xbd, - 0x09, 0x43, 0x05, 0xa9, 0x25, 0x51, 0x5b, 0xb4, 0xea, 0x31, 0xe7, 0x60, 0xc7, 0xe5, 0x08, 0x3c, 0x6c, 0x2b, 0xa8, - 0xac, 0xaa, 0x68, 0xd6, 0xc4, 0x47, 0x90, 0x8a, 0x6d, 0xaa, 0x0a, 0x27, 0xdc, 0xa6, 0x1d, 0xfb, 0x2f, 0x85, 0x7a, - 0x0a, 0x70, 0xa7, 0x1b, 0x61, 0x6d, 0x42, 0xca, 0x13, 0xfc, 0x3b, 0x53, 0xce, 0x3d, 0x5b, 0xdd, 0x16, 0x8d, 0xbb, - 0xba, 0xa0, 0x6e, 0xca, 0x49, 0x19, 0x8d, 0xba, 0x0e, 0xf5, 0x65, 0x26, 0x40, 0x33, 0xd9, 0xba, 0x05, 0x2c, 0x68, - 0x0a, 0xc9, 0x11, 0x6b, 0x74, 0x63, 0x78, 0x9d, 0x85, 0x9d, 0x97, 0xcb, 0xf7, 0xf3, 0xd4, 0xde, 0x30, 0x07, 0xe3, - 0x69, 0x17, 0x95, 0x7b, 0x85, 0xad, 0x8a, 0xa6, 0x32, 0xb8, 0x07, 0xc4, 0x8d, 0x54, 0x59, 0x47, 0xbe, 0x49, 0x99, - 0x03, 0x35, 0x7d, 0x53, 0x9d, 0x77, 0x73, 0xf7, 0x4e, 0x07, 0xf4, 0x1a, 0x55, 0x50, 0xed, 0xa5, 0xda, 0x2b, 0xeb, - 0xb0, 0xc5, 0x38, 0x61, 0x05, 0xc0, 0x15, 0x45, 0x41, 0xa3, 0x21, 0xa5, 0x84, 0xfb, 0x68, 0xd2, 0xd9, 0x5b, 0x19, - 0x59, 0x8b, 0x79, 0x62, 0x77, 0xf5, 0x55, 0xa8, 0x6f, 0xa1, 0x19, 0x04, 0xd8, 0x71, 0xec, 0x84, 0xcf, 0x26, 0xec, - 0x18, 0x19, 0x5d, 0x39, 0xb8, 0x83, 0xf0, 0x94, 0x9a, 0x14, 0x91, 0x96, 0x4e, 0x29, 0xea, 0x12, 0xbe, 0xaf, 0x15, - 0xde, 0x9f, 0x17, 0xa4, 0xf1, 0xdc, 0x1f, 0xa8, 0xa5, 0xef, 0x55, 0x7b, 0xe9, 0x05, 0xfb, 0xd7, 0x75, 0x6f, 0xf7, - 0xae, 0x0b, 0xcc, 0xe1, 0xde, 0x95, 0x81, 0xbb, 0x24, 0x2b, 0xa5, 0x64, 0xf0, 0xbd, 0xa4, 0x3c, 0x90, 0x63, 0x59, - 0xa8, 0xd8, 0x8a, 0x6e, 0xf4, 0x3f, 0xad, 0x07, 0xa3, 0x93, 0xd3, 0xdb, 0xa5, 0xaf, 0x5c, 0xb3, 0x08, 0xb2, 0xa8, - 0x0e, 0x54, 0xc7, 0xb2, 0x55, 0x05, 0x23, 0x33, 0x78, 0xc1, 0x7c, 0xa0, 0xfe, 0x72, 0xfe, 0xda, 0xec, 0xaa, 0xa7, - 0x60, 0x8e, 0x71, 0x3d, 0x47, 0x16, 0xf7, 0xcc, 0xbd, 0x63, 0xd1, 0x55, 0x4b, 0x55, 0x30, 0x59, 0x2a, 0x31, 0xb7, - 0x58, 0xa6, 0xb4, 0xd4, 0x3d, 0x72, 0xf2, 0x29, 0x22, 0xad, 0xb6, 0x0a, 0x88, 0xd5, 0x69, 0x75, 0x15, 0xa7, 0x75, - 0x68, 0x1d, 0x75, 0xd5, 0xe1, 0x57, 0x8a, 0x72, 0x32, 0x65, 0xb3, 0x78, 0x88, 0xe2, 0x98, 0x13, 0xe4, 0x07, 0xe9, - 0xb7, 0xa2, 0x58, 0x13, 0x3f, 0x36, 0x1d, 0x65, 0xc3, 0x1f, 0x15, 0x05, 0x90, 0x51, 0x4f, 0x79, 0x38, 0x6b, 0xcd, - 0x0e, 0x67, 0xcf, 0xfa, 0xbc, 0x38, 0xfd, 0xaa, 0x50, 0xdd, 0xa0, 0x7f, 0x5b, 0x52, 0xb3, 0x38, 0x89, 0xc2, 0x8f, - 0x8c, 0xf3, 0x92, 0x4a, 0x26, 0x28, 0x2a, 0x37, 0x6d, 0x55, 0xbf, 0xe4, 0x74, 0xc7, 0x93, 0x59, 0x2b, 0xaf, 0x8e, - 0x63, 0x3c, 0xc8, 0x06, 0x79, 0x72, 0x20, 0x86, 0x7e, 0x22, 0x83, 0xc9, 0x31, 0xeb, 0x00, 0xe5, 0xa8, 0x7c, 0x8e, - 0x73, 0x31, 0xbf, 0x13, 0x08, 0x79, 0x9f, 0x7b, 0x70, 0xc4, 0xd8, 0x6c, 0xa0, 0xfe, 0xe8, 0xb4, 0xba, 0x86, 0xe3, - 0x1c, 0x59, 0x47, 0xdd, 0x89, 0x6d, 0x1c, 0x5a, 0x87, 0x66, 0xdb, 0x3a, 0x32, 0xba, 0x66, 0xd7, 0xe8, 0x7e, 0xd7, - 0x9d, 0x98, 0x87, 0xd6, 0xa1, 0x61, 0x9b, 0x5d, 0x28, 0x34, 0xbb, 0x66, 0xf7, 0xda, 0x3c, 0xec, 0x4e, 0x6c, 0x2c, - 0x6d, 0x59, 0x9d, 0x8e, 0xe9, 0xd8, 0x56, 0xa7, 0x63, 0x74, 0xac, 0xa3, 0x23, 0xd3, 0x69, 0x5b, 0x47, 0x47, 0x67, - 0x9d, 0xae, 0xd5, 0x86, 0x77, 0xed, 0xf6, 0xa4, 0x6d, 0x39, 0x8e, 0x09, 0x7f, 0x19, 0x5d, 0xab, 0x45, 0x3f, 0x1c, - 0xc7, 0x6a, 0x3b, 0x86, 0xed, 0x77, 0x5a, 0xd6, 0xd1, 0x33, 0x03, 0xff, 0xc6, 0x6a, 0x06, 0xfe, 0x05, 0xdd, 0x18, - 0xcf, 0xac, 0xd6, 0x11, 0xfd, 0xc2, 0x0e, 0xaf, 0x0f, 0xbb, 0xff, 0x50, 0x0f, 0x1a, 0xe7, 0xe0, 0xd0, 0x1c, 0xba, - 0x1d, 0xab, 0xdd, 0x36, 0x0e, 0x1d, 0xab, 0xdb, 0x5e, 0x98, 0x87, 0x2d, 0xeb, 0xe8, 0x78, 0x62, 0x3a, 0xd6, 0xf1, - 0xb1, 0x61, 0x9b, 0x6d, 0xab, 0x65, 0x38, 0xd6, 0x61, 0x1b, 0x7f, 0xb4, 0xad, 0xd6, 0xf5, 0xf1, 0x33, 0xeb, 0xa8, - 0xb3, 0x38, 0xb2, 0x0e, 0x3f, 0x1c, 0x76, 0xad, 0x56, 0x7b, 0xd1, 0x3e, 0xb2, 0x5a, 0xc7, 0xd7, 0x47, 0xd6, 0xe1, - 0xc2, 0x6c, 0x1d, 0x6d, 0x6d, 0xe9, 0xb4, 0x2c, 0x80, 0x11, 0xbe, 0x86, 0x17, 0x06, 0x7f, 0x01, 0x7f, 0x16, 0xd8, - 0xf6, 0x0f, 0xec, 0x26, 0xae, 0x36, 0x7d, 0x66, 0x75, 0x8f, 0x27, 0x54, 0x1d, 0x0a, 0x4c, 0x51, 0x03, 0x9a, 0x5c, - 0x9b, 0xf4, 0x59, 0xec, 0xce, 0x14, 0x1d, 0x89, 0x3f, 0xfc, 0x63, 0xd7, 0x26, 0x7c, 0x98, 0xbe, 0xfb, 0xa7, 0xf6, - 0x93, 0x2d, 0xf9, 0xc9, 0xc1, 0x9c, 0xb6, 0xfe, 0x7c, 0xf8, 0x15, 0xe5, 0xd7, 0x1c, 0x19, 0xbf, 0x36, 0x29, 0x25, - 0xff, 0xb5, 0x5b, 0x29, 0xf9, 0x7c, 0xbd, 0x8f, 0x52, 0xf2, 0x5f, 0x5f, 0x5c, 0x29, 0xf9, 0x6b, 0xd9, 0xb7, 0xe6, - 0x75, 0x39, 0x0d, 0xd8, 0xf7, 0x9b, 0xb2, 0xc8, 0x21, 0x70, 0xb5, 0x8b, 0x9f, 0xd6, 0x97, 0x10, 0xda, 0xef, 0x75, - 0x38, 0x78, 0xbe, 0x2e, 0x18, 0x7c, 0x86, 0x80, 0x63, 0x5f, 0x87, 0x84, 0x63, 0x3f, 0xac, 0x07, 0x60, 0x65, 0xc6, - 0xd9, 0x1c, 0x6f, 0x6a, 0x2e, 0x5c, 0x7f, 0x96, 0xb1, 0x48, 0x50, 0xd2, 0xc7, 0x62, 0x70, 0x5c, 0x03, 0xf2, 0x0c, - 0x37, 0x99, 0xf5, 0x32, 0x88, 0xc1, 0x22, 0x18, 0x2c, 0x39, 0x66, 0x51, 0x5a, 0x6a, 0x6c, 0x89, 0x60, 0x88, 0x57, - 0xdc, 0x0b, 0xaa, 0xf1, 0x3d, 0x1a, 0x00, 0xd7, 0xf7, 0xee, 0x54, 0xfb, 0x55, 0xc0, 0xb2, 0x4e, 0x18, 0x48, 0x03, - 0xb7, 0x5f, 0xf7, 0xbe, 0x68, 0x86, 0x5b, 0x32, 0xbc, 0x6e, 0x1e, 0x29, 0x8c, 0xa4, 0xdc, 0xde, 0x29, 0x9a, 0xf1, - 0xee, 0x9a, 0x66, 0xcd, 0xe7, 0x0b, 0xcd, 0xb7, 0xd8, 0x10, 0x67, 0x1d, 0x97, 0x41, 0x55, 0x4a, 0x62, 0x5d, 0x0b, - 0x90, 0xfc, 0x82, 0x9a, 0x1b, 0x1a, 0xe7, 0x9c, 0xaa, 0xad, 0x20, 0xbf, 0x63, 0x4b, 0xef, 0x0a, 0x7d, 0xca, 0xc6, - 0xc9, 0x4f, 0x36, 0x78, 0xaf, 0xf0, 0x7e, 0x05, 0x4e, 0x94, 0x73, 0x3c, 0xe3, 0x50, 0x86, 0xf3, 0x46, 0xea, 0x97, - 0xa4, 0x11, 0xe9, 0xc2, 0xd9, 0x54, 0x79, 0xd1, 0x46, 0xb7, 0x04, 0x87, 0x2d, 0x05, 0x17, 0x84, 0x9f, 0x27, 0x27, - 0x80, 0x94, 0x1c, 0x35, 0xd0, 0xcf, 0x61, 0x5b, 0x67, 0xa2, 0xde, 0x43, 0xd8, 0xc4, 0x3c, 0x26, 0xb3, 0x22, 0x47, - 0x9b, 0xd9, 0xcc, 0xfc, 0xd0, 0x4d, 0x7a, 0xc8, 0xa6, 0x49, 0x2c, 0x6f, 0x0b, 0x3d, 0x16, 0xfa, 0x5b, 0x8c, 0xe9, - 0xe4, 0x8e, 0x79, 0x27, 0xe8, 0xf9, 0xb0, 0xcd, 0xfe, 0x2e, 0x73, 0x38, 0xdb, 0x14, 0xcc, 0x51, 0x9c, 0xce, 0xb1, - 0xe1, 0x1c, 0x19, 0xd6, 0x71, 0x47, 0x4f, 0xc5, 0x81, 0x93, 0xbb, 0x2c, 0x00, 0x04, 0x1c, 0x20, 0xb2, 0x61, 0x7a, - 0x81, 0x97, 0x78, 0xae, 0x9f, 0x02, 0x3f, 0x5c, 0xbc, 0xa4, 0xfc, 0x6b, 0x1d, 0x27, 0x30, 0x47, 0xc1, 0xf4, 0xa2, - 0xf3, 0x87, 0x39, 0x66, 0xc9, 0x0d, 0x63, 0x41, 0x83, 0x61, 0x4c, 0xd9, 0x97, 0xe4, 0xf7, 0xb3, 0xac, 0x4f, 0xc9, - 0x6a, 0x6d, 0x9c, 0x04, 0x7c, 0x7f, 0x08, 0xc7, 0x87, 0x74, 0x64, 0x7c, 0xd7, 0x84, 0x70, 0x7f, 0xd9, 0x8d, 0x70, - 0x13, 0xb6, 0x0f, 0xc2, 0xfd, 0xe5, 0x8b, 0x23, 0xdc, 0xef, 0x64, 0x84, 0x5b, 0xf0, 0x1f, 0xcc, 0x35, 0x4c, 0xef, - 0xf1, 0x59, 0x83, 0xcc, 0x28, 0x4f, 0xd5, 0x03, 0x62, 0xe0, 0x55, 0x3d, 0x4f, 0x1f, 0xf4, 0xb7, 0x42, 0xa2, 0x56, - 0x14, 0x80, 0x62, 0xd6, 0x0d, 0x4a, 0x0a, 0xe9, 0x81, 0xab, 0x5b, 0x96, 0x18, 0x92, 0xdd, 0x28, 0x6f, 0x82, 0xc4, - 0xb7, 0xde, 0xf1, 0x7b, 0x24, 0x28, 0x74, 0x5f, 0x87, 0xd1, 0xd2, 0xc5, 0xe8, 0xaf, 0x2a, 0x26, 0x78, 0x87, 0x07, - 0x1b, 0x9c, 0x71, 0x27, 0x61, 0x30, 0xcd, 0xb4, 0x92, 0x6c, 0x70, 0x41, 0x1c, 0xb7, 0x7a, 0xc7, 0xdc, 0x48, 0x35, - 0xe8, 0x35, 0x2c, 0xee, 0x93, 0xb6, 0xfd, 0xa4, 0x75, 0xf8, 0xe4, 0xc8, 0x86, 0xff, 0x1d, 0xd6, 0x4e, 0x0d, 0x5e, - 0x71, 0x19, 0x06, 0x90, 0x63, 0x52, 0xd4, 0x6c, 0xaa, 0x76, 0xc3, 0xd8, 0xc7, 0xbc, 0xd6, 0x71, 0x7d, 0xa5, 0xa9, - 0x7b, 0x97, 0xd7, 0xa9, 0xad, 0xb1, 0x08, 0xd7, 0xd2, 0xb0, 0x6a, 0x46, 0xe3, 0x05, 0x6b, 0x90, 0xb3, 0x4b, 0x35, - 0xe4, 0xd7, 0x7c, 0xba, 0xf9, 0xbc, 0x58, 0x3b, 0xbd, 0xcc, 0x13, 0xd9, 0x8a, 0x7c, 0x46, 0x3b, 0x21, 0xc8, 0x55, - 0x94, 0x36, 0x86, 0x03, 0xc7, 0x44, 0x13, 0x10, 0x0c, 0x3c, 0x4b, 0x3f, 0xea, 0xd2, 0x02, 0x25, 0xd1, 0x3a, 0x98, - 0x68, 0xf8, 0xd3, 0x1d, 0xc7, 0x9a, 0x77, 0x10, 0x59, 0xfc, 0xc3, 0x3a, 0xae, 0x9a, 0x3b, 0xb4, 0xf3, 0xac, 0x7f, - 0xb1, 0x58, 0x15, 0xf7, 0x49, 0x62, 0x44, 0xa8, 0xc7, 0xa6, 0xa5, 0x35, 0x07, 0xee, 0x93, 0xac, 0xe1, 0x93, 0xc4, - 0x08, 0x9e, 0x82, 0xee, 0x73, 0x60, 0x3f, 0x7e, 0x4c, 0xb5, 0x1e, 0x0c, 0xc4, 0xb4, 0x4e, 0x27, 0x79, 0xd0, 0x50, - 0xc5, 0x9d, 0x87, 0x14, 0x37, 0xb4, 0x37, 0x31, 0xc2, 0xa7, 0x4f, 0x87, 0x03, 0x47, 0xc7, 0x8c, 0xb2, 0x22, 0x33, - 0x3c, 0x4f, 0x56, 0x7c, 0xb6, 0x9f, 0xa1, 0x91, 0x5e, 0xeb, 0x4a, 0xbb, 0x82, 0x3b, 0x93, 0x2d, 0xdc, 0x11, 0x38, - 0xf6, 0x82, 0xe4, 0x81, 0x64, 0x50, 0xe0, 0x0a, 0x83, 0x1f, 0x51, 0x27, 0xbb, 0x75, 0xb5, 0x2d, 0xdb, 0xb2, 0xd5, - 0xac, 0xe1, 0xcc, 0x9b, 0x0f, 0x36, 0x61, 0xe2, 0x42, 0x1a, 0x56, 0x3f, 0x9c, 0x83, 0x1f, 0x5d, 0xe2, 0x25, 0x3e, - 0xe4, 0xf4, 0x04, 0x87, 0xba, 0x25, 0xdd, 0xcb, 0x53, 0xee, 0xdd, 0xe0, 0x46, 0x1f, 0x31, 0xaf, 0xbb, 0x70, 0xc5, - 0xc5, 0x38, 0x76, 0x3f, 0x02, 0x31, 0xd4, 0x54, 0x0d, 0x64, 0x03, 0x2c, 0x8a, 0x4d, 0xd9, 0x5b, 0xa8, 0xa7, 0x40, - 0x1b, 0x5d, 0xe5, 0x93, 0x98, 0x45, 0xee, 0x12, 0x12, 0x1b, 0x6d, 0x52, 0x83, 0x63, 0x5a, 0x95, 0xa3, 0x5a, 0xc5, - 0x79, 0x76, 0x64, 0x28, 0x2d, 0xc7, 0x50, 0x6c, 0x40, 0xb7, 0x6a, 0x6a, 0x6c, 0xd2, 0xcb, 0xfe, 0x2e, 0x83, 0x07, - 0xc2, 0x2f, 0x0f, 0x69, 0x1e, 0x64, 0xea, 0xc0, 0x55, 0x49, 0x09, 0xc5, 0x2f, 0xd6, 0xa4, 0x84, 0x26, 0x1e, 0x29, - 0x3d, 0xcf, 0xd9, 0x6d, 0xa2, 0x63, 0xce, 0x4b, 0x5e, 0xc5, 0xd3, 0x37, 0xe8, 0x30, 0xec, 0x05, 0x8a, 0xf7, 0xe9, - 0x93, 0xe6, 0x81, 0x33, 0xd3, 0x40, 0x82, 0x0f, 0x3c, 0xeb, 0x05, 0x80, 0x79, 0xb9, 0x9a, 0x1e, 0x81, 0x05, 0x9e, - 0x86, 0xf0, 0x6f, 0x5e, 0x2c, 0x7e, 0x70, 0x33, 0x09, 0xcb, 0x77, 0x83, 0x39, 0xa0, 0x34, 0x37, 0x98, 0x57, 0xcc, - 0xb1, 0xc8, 0xe7, 0xb9, 0x54, 0x9a, 0x77, 0x95, 0x9b, 0x4a, 0xc5, 0xcf, 0xef, 0xce, 0x29, 0xa7, 0xaf, 0xa6, 0x02, - 0x95, 0x43, 0x17, 0xdd, 0x5c, 0x93, 0xfb, 0x74, 0xf0, 0xf5, 0xc9, 0x92, 0x25, 0x2e, 0xa9, 0x81, 0xe0, 0xf2, 0x0b, - 0xec, 0x80, 0xc2, 0x09, 0x0d, 0x8f, 0x0d, 0x35, 0xa0, 0x30, 0xe7, 0x44, 0x27, 0x0c, 0x85, 0xd3, 0x29, 0x13, 0x2d, - 0x3e, 0x07, 0x8e, 0x41, 0x0e, 0x07, 0x13, 0x17, 0x43, 0x1d, 0x0f, 0x82, 0x50, 0x1d, 0x7e, 0x9d, 0xf9, 0x66, 0x36, - 0x2d, 0x82, 0xef, 0x05, 0x1f, 0x2f, 0x22, 0xe6, 0xff, 0x7b, 0xf0, 0x35, 0x10, 0xee, 0xaf, 0x2f, 0x55, 0xbd, 0x9f, - 0x58, 0x8b, 0x88, 0xcd, 0x06, 0x5f, 0xd7, 0x24, 0x98, 0xc7, 0xeb, 0x3d, 0x8d, 0x45, 0x6d, 0xb7, 0xf2, 0x90, 0x73, - 0xed, 0xbd, 0x2e, 0xf5, 0x43, 0x7e, 0x5b, 0x87, 0x1b, 0xe0, 0xa6, 0x70, 0xc7, 0x76, 0xfa, 0x78, 0x7f, 0x1e, 0xfb, - 0xee, 0xe4, 0x63, 0x9f, 0xde, 0x14, 0x1e, 0x4c, 0xa0, 0xd6, 0x13, 0x77, 0xd5, 0x43, 0xf2, 0x2a, 0x17, 0x82, 0xf7, - 0x34, 0x95, 0x66, 0x9c, 0x5d, 0xed, 0x5e, 0xc6, 0xad, 0xbc, 0xc1, 0x2f, 0xe3, 0xa7, 0x6e, 0x16, 0x5e, 0xc2, 0xc4, - 0xa7, 0xf0, 0x21, 0x4d, 0xc5, 0x45, 0x9d, 0xae, 0xa8, 0x78, 0xb1, 0xb6, 0xda, 0x8a, 0xd3, 0xfd, 0xae, 0x73, 0xed, - 0xd8, 0x8b, 0x96, 0x63, 0x75, 0x3f, 0x38, 0xdd, 0x45, 0xdb, 0x3a, 0xf6, 0xcd, 0xb6, 0x75, 0x0c, 0x7f, 0x3e, 0x1c, - 0x5b, 0xdd, 0x85, 0xd9, 0xb2, 0x0e, 0x3f, 0x38, 0x2d, 0xdf, 0xec, 0x5a, 0xc7, 0xf0, 0xe7, 0x8c, 0x5a, 0xc1, 0x05, - 0x88, 0xee, 0x3b, 0x5f, 0x17, 0xb0, 0x80, 0xf4, 0x3b, 0xd3, 0xc9, 0x1a, 0x05, 0xf2, 0x56, 0xa3, 0xd7, 0x05, 0x94, - 0x41, 0x19, 0x7f, 0xd0, 0x14, 0xa1, 0xaf, 0x05, 0x03, 0x46, 0x39, 0x7e, 0x84, 0x79, 0x9b, 0xf0, 0x43, 0x17, 0x89, - 0x56, 0x6a, 0x8f, 0x11, 0x6f, 0x53, 0x9f, 0x5c, 0x44, 0x24, 0x01, 0x26, 0x45, 0xf0, 0x2f, 0x2b, 0x0c, 0x8d, 0x27, - 0x72, 0x62, 0x49, 0x58, 0x29, 0x4f, 0x44, 0x9f, 0xee, 0x1e, 0x38, 0x7a, 0xf3, 0xb3, 0x2c, 0x11, 0xea, 0x17, 0xed, - 0x5b, 0x4a, 0x3d, 0xf6, 0x59, 0xfd, 0x60, 0x52, 0xa6, 0x3c, 0x9f, 0x12, 0x44, 0x14, 0x9f, 0x7a, 0x51, 0x36, 0x3c, - 0x09, 0x45, 0x3b, 0xf5, 0x59, 0x59, 0x74, 0xc8, 0x18, 0xf9, 0x06, 0xb8, 0xe4, 0x6b, 0xd7, 0x97, 0x0c, 0xd9, 0xa4, - 0x96, 0x0f, 0x32, 0xcc, 0xff, 0xf8, 0x71, 0x3e, 0x38, 0xb3, 0x34, 0xee, 0x13, 0xa7, 0x03, 0x64, 0xb7, 0xc3, 0xda, - 0x5b, 0x6d, 0x2a, 0x77, 0xc7, 0xa2, 0xcf, 0x83, 0x50, 0x0b, 0xbb, 0x29, 0x61, 0xb1, 0xd1, 0x68, 0xd8, 0x59, 0xb1, - 0xd7, 0x80, 0x28, 0xfe, 0xa5, 0xab, 0x8e, 0xaa, 0xf7, 0x03, 0x61, 0x7e, 0x10, 0x6c, 0x89, 0xbf, 0xcf, 0xef, 0x62, - 0x2a, 0x80, 0x66, 0xcb, 0x3c, 0x76, 0x38, 0x88, 0xff, 0xd9, 0x93, 0x40, 0x67, 0x4d, 0xb0, 0x97, 0x28, 0x9d, 0xd6, - 0x82, 0xf3, 0x5e, 0x46, 0x57, 0x89, 0xa0, 0xb2, 0xf8, 0x54, 0x85, 0x22, 0x48, 0x25, 0x8c, 0xd9, 0xc3, 0x33, 0x63, - 0xd1, 0x8c, 0x5a, 0xe4, 0x05, 0x86, 0x87, 0xb9, 0x4e, 0x84, 0xe3, 0xa8, 0xfe, 0xf8, 0x71, 0x23, 0x11, 0x22, 0xe3, - 0x9c, 0x98, 0x25, 0x59, 0x7e, 0x53, 0x55, 0xc6, 0x6f, 0xaa, 0x8c, 0x62, 0xb2, 0x7e, 0x11, 0x6b, 0x08, 0x1b, 0x57, - 0xda, 0x7b, 0xf8, 0x73, 0xcc, 0xdc, 0xc4, 0xe2, 0xca, 0x52, 0x4d, 0x22, 0xee, 0x86, 0xc3, 0xda, 0x60, 0xdd, 0xca, - 0x23, 0x68, 0x66, 0x69, 0x12, 0xff, 0xb6, 0xe6, 0x51, 0x1d, 0xa0, 0x8f, 0x4f, 0x76, 0x1e, 0x80, 0xec, 0x6d, 0xe2, - 0x52, 0x60, 0x18, 0x99, 0xe4, 0x86, 0x89, 0x2b, 0x52, 0x76, 0x02, 0x5f, 0xde, 0xaf, 0x35, 0xbf, 0x90, 0x22, 0x3f, - 0x0c, 0xdf, 0x9e, 0x7f, 0xab, 0xf0, 0xfd, 0x4f, 0xd6, 0x02, 0x78, 0x91, 0xa1, 0xcc, 0x3f, 0x03, 0xca, 0xfc, 0xa3, - 0xf0, 0x24, 0x53, 0x2a, 0xe6, 0x6c, 0x24, 0x08, 0xa2, 0x00, 0x9a, 0x6c, 0x28, 0x96, 0x6b, 0x3f, 0xf1, 0x56, 0x6e, - 0x94, 0x1c, 0x60, 0xda, 0x1f, 0x40, 0x72, 0x6a, 0x53, 0x3c, 0x08, 0x32, 0xc3, 0x10, 0x81, 0x5b, 0x93, 0x40, 0xd8, - 0x61, 0xcc, 0x3c, 0x3f, 0x33, 0xc3, 0x10, 0x1f, 0x70, 0x27, 0x13, 0xb6, 0x4a, 0x06, 0x85, 0xf4, 0x42, 0xe1, 0x24, - 0x61, 0x89, 0x19, 0x27, 0x11, 0x73, 0x97, 0x6a, 0x16, 0x20, 0xbc, 0xda, 0x5f, 0xbc, 0x1e, 0x2f, 0xbd, 0x24, 0x8b, - 0xb0, 0x4b, 0x13, 0x04, 0x83, 0x08, 0x18, 0xe2, 0x70, 0x94, 0x72, 0x10, 0x9e, 0x85, 0xf3, 0xd2, 0x8e, 0xca, 0x39, - 0x97, 0x53, 0x8c, 0xdf, 0x4e, 0x37, 0x19, 0x90, 0x16, 0x4f, 0x42, 0xff, 0x8a, 0xc7, 0xb0, 0xc8, 0x02, 0x01, 0xab, - 0xc3, 0x13, 0x7e, 0xbd, 0x55, 0x30, 0x7c, 0x8b, 0xda, 0xb1, 0x21, 0x42, 0x7d, 0x53, 0x74, 0x8b, 0x03, 0x5e, 0x19, - 0x48, 0x13, 0xf5, 0x8c, 0x49, 0x46, 0x68, 0x2c, 0xe7, 0xc0, 0x08, 0x15, 0x0c, 0x66, 0x16, 0xce, 0x30, 0x73, 0xa7, - 0xc4, 0x51, 0x21, 0xaf, 0xf4, 0xe9, 0xd3, 0x8b, 0xd1, 0x6f, 0xff, 0x81, 0x4c, 0x28, 0x0b, 0x47, 0xc4, 0x94, 0xb8, - 0x90, 0x6b, 0x71, 0xee, 0xd3, 0x18, 0xa1, 0xb1, 0x14, 0x9b, 0x8a, 0x10, 0x3d, 0x62, 0x6b, 0xa5, 0xa3, 0x4b, 0x11, - 0xa2, 0x11, 0x72, 0x28, 0xe9, 0x22, 0xf2, 0x05, 0xa6, 0xe4, 0x1c, 0x89, 0x98, 0x28, 0xca, 0x3f, 0x6f, 0x9f, 0x1f, - 0x2b, 0x79, 0x0c, 0xa3, 0x3a, 0x8b, 0x1e, 0xda, 0x43, 0xc3, 0x13, 0x57, 0x41, 0xa6, 0x05, 0xd9, 0x8f, 0xb8, 0x77, - 0x00, 0xd3, 0x5c, 0x84, 0x4b, 0x66, 0x79, 0xe1, 0xc1, 0x0d, 0x1b, 0x9b, 0xee, 0xca, 0x23, 0xbb, 0x1c, 0x94, 0xbb, - 0x29, 0xc4, 0xf9, 0x65, 0xe6, 0x2e, 0xc4, 0x5f, 0xa7, 0x39, 0x28, 0xc3, 0x62, 0x4c, 0xce, 0x4e, 0x2b, 0xd7, 0x03, - 0x42, 0xfc, 0x02, 0x09, 0x8e, 0xe1, 0xf0, 0xe4, 0xc0, 0x1d, 0x16, 0x83, 0x02, 0x5b, 0x22, 0xb9, 0x4d, 0x91, 0x08, - 0x9c, 0x52, 0x6c, 0x5f, 0x11, 0xc6, 0x37, 0x7f, 0x30, 0xc3, 0xd9, 0x4c, 0x0e, 0xe4, 0x6b, 0x15, 0x87, 0x97, 0x01, - 0x2d, 0xdf, 0xd2, 0xe1, 0x8a, 0xbe, 0x54, 0xfd, 0x44, 0xf6, 0x53, 0xed, 0x61, 0x04, 0x6f, 0x98, 0x33, 0x1c, 0xf7, - 0x4a, 0x40, 0xe0, 0x0c, 0x62, 0x0f, 0xa9, 0x12, 0xc7, 0x23, 0xe5, 0xf4, 0x13, 0x0d, 0x9c, 0xcb, 0x83, 0xc1, 0x80, - 0xd0, 0x5c, 0x19, 0xdb, 0x01, 0x10, 0x6b, 0x12, 0xfd, 0xc0, 0x64, 0x13, 0x68, 0x68, 0x92, 0xbb, 0x2c, 0x36, 0x2a, - 0x4f, 0xa7, 0x3a, 0xc6, 0x03, 0x57, 0x6c, 0xbf, 0xc2, 0x06, 0x85, 0x8d, 0xc7, 0xd7, 0x1d, 0xf0, 0xbb, 0xe8, 0xa7, - 0x84, 0xe6, 0x95, 0x6f, 0x08, 0xa3, 0x9b, 0xbe, 0x7b, 0x17, 0x4a, 0x66, 0x4c, 0x3c, 0xa2, 0xc9, 0x19, 0x96, 0x9e, - 0x0b, 0x4f, 0xe2, 0xca, 0x41, 0xcb, 0x12, 0xa2, 0x54, 0x0f, 0x9b, 0x9c, 0xc4, 0x64, 0xd7, 0x59, 0x93, 0xeb, 0x16, - 0x27, 0x83, 0xc8, 0x33, 0xcd, 0xcf, 0x61, 0xe1, 0x25, 0xa2, 0x85, 0xf4, 0xe4, 0x00, 0xe6, 0x07, 0x51, 0x58, 0x0a, - 0x8c, 0x93, 0xa7, 0x43, 0xa8, 0x17, 0x37, 0x26, 0x53, 0xac, 0x37, 0x53, 0xc1, 0xf3, 0xe1, 0xc5, 0x52, 0x4a, 0xf3, - 0x27, 0x55, 0xa9, 0xf2, 0x32, 0x76, 0x3d, 0x13, 0xb8, 0x3b, 0x7b, 0xd0, 0x87, 0x35, 0xa6, 0x0e, 0x4a, 0xfb, 0x09, - 0x13, 0x41, 0x0e, 0xce, 0x92, 0x86, 0x38, 0x08, 0x4d, 0x55, 0x88, 0x9f, 0xdd, 0x52, 0x21, 0xdf, 0xc7, 0xdb, 0x6a, - 0xe5, 0x9c, 0x53, 0x56, 0x6d, 0xee, 0x6a, 0xea, 0x43, 0xdc, 0xf1, 0x95, 0xda, 0x58, 0x0a, 0xf5, 0xce, 0x92, 0x01, - 0x54, 0x15, 0xb2, 0x78, 0x77, 0xb5, 0xa2, 0xca, 0x7a, 0xff, 0xe4, 0x80, 0xae, 0xa5, 0x43, 0xda, 0x61, 0xc3, 0x13, - 0x30, 0xe5, 0xa6, 0x45, 0x77, 0x57, 0x2b, 0xbe, 0xa4, 0xf4, 0x8b, 0xde, 0x1c, 0x2c, 0x92, 0xa5, 0x3f, 0xfc, 0x3f, - 0x04, 0xdf, 0xdf, 0xf4, 0x2c, 0x6c, 0x03, 0x00}; + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xcc, 0x7d, 0xf9, 0x7f, 0xdb, 0xb6, 0xf2, 0xe0, 0xcf, + 0xbb, 0x7f, 0x85, 0xcd, 0x6f, 0xe2, 0x92, 0x16, 0x44, 0x4b, 0xf2, 0x11, 0x87, 0x32, 0xad, 0xcd, 0xd9, 0xa4, 0xcd, + 0xd5, 0x38, 0x49, 0x0f, 0x57, 0xcf, 0xa1, 0x28, 0x48, 0x62, 0x42, 0x91, 0x2a, 0x49, 0xc5, 0x56, 0x65, 0xfd, 0xef, + 0x3b, 0x33, 0x38, 0x29, 0xc9, 0x79, 0x7d, 0x7b, 0x7d, 0xf6, 0xe5, 0xd5, 0x22, 0x01, 0x10, 0xc7, 0x60, 0x30, 0x17, + 0x06, 0x83, 0xb3, 0xdd, 0x61, 0x1e, 0x57, 0x8b, 0x19, 0xdf, 0x99, 0x54, 0xd3, 0xf4, 0xfc, 0x4c, 0xfe, 0xe5, 0xd1, + 0xf0, 0xfc, 0x2c, 0x4d, 0xb2, 0xaf, 0x3b, 0x05, 0x4f, 0xc3, 0x24, 0xce, 0xb3, 0x9d, 0x49, 0xc1, 0x47, 0xe1, 0x30, + 0xaa, 0xa2, 0x20, 0x99, 0x46, 0x63, 0xbe, 0x73, 0x70, 0x7e, 0x36, 0xe5, 0x55, 0xb4, 0x13, 0x4f, 0xa2, 0xa2, 0xe4, + 0x55, 0xf8, 0xf1, 0xc3, 0xf3, 0xe6, 0xe9, 0xf9, 0x59, 0x19, 0x17, 0xc9, 0xac, 0xda, 0xc1, 0x2a, 0xc3, 0x69, 0x3e, + 0x9c, 0xa7, 0xfc, 0xfc, 0xe0, 0xe0, 0xfa, 0xfa, 0xda, 0xff, 0x52, 0xfe, 0xf7, 0x6f, 0x51, 0xb1, 0xf3, 0x63, 0x11, + 0xbe, 0x1d, 0x7c, 0xe1, 0x71, 0xe5, 0x0f, 0xf9, 0x28, 0xc9, 0xf8, 0xbb, 0x22, 0x9f, 0xf1, 0xa2, 0x5a, 0x74, 0x31, + 0xf3, 0x97, 0x22, 0x74, 0x13, 0x56, 0x31, 0xee, 0x85, 0xe7, 0xd5, 0x4e, 0x92, 0xed, 0x24, 0xbd, 0x1f, 0x0b, 0x4a, + 0x59, 0xf2, 0x6c, 0x3e, 0xe5, 0x45, 0x34, 0x48, 0x79, 0xb0, 0xdb, 0x62, 0xd0, 0xa1, 0x51, 0x32, 0x9e, 0xeb, 0xf7, + 0xeb, 0x22, 0xa9, 0xd4, 0xf3, 0xb7, 0x28, 0x9d, 0xf3, 0x80, 0xaf, 0xbc, 0x20, 0xb9, 0xac, 0xfa, 0x21, 0xa7, 0x9a, + 0xbf, 0x9a, 0x8a, 0xdd, 0x5f, 0xa8, 0x4a, 0xe8, 0x60, 0x3e, 0xda, 0xa9, 0x76, 0x43, 0xa7, 0x5c, 0x4c, 0x07, 0x79, + 0xea, 0xf4, 0xaa, 0x86, 0xe3, 0x04, 0x58, 0x06, 0xfe, 0xdf, 0x85, 0x16, 0xca, 0x6a, 0x27, 0x4b, 0xc2, 0xeb, 0x24, + 0x1b, 0xe6, 0xd7, 0xec, 0x3a, 0x0b, 0xb3, 0xc4, 0xbf, 0x98, 0x44, 0xf0, 0xf2, 0x3e, 0xcf, 0xab, 0xbd, 0x3d, 0x57, + 0xbe, 0x2f, 0x9e, 0x5c, 0x5c, 0x84, 0x61, 0xf8, 0x2d, 0x4f, 0x86, 0x3b, 0xad, 0xdb, 0x5b, 0x2b, 0xd5, 0xcf, 0xa2, + 0x2a, 0xf9, 0xc6, 0xc5, 0x47, 0xde, 0xde, 0x9e, 0x03, 0xbf, 0xb3, 0x8a, 0x0f, 0x2f, 0xaa, 0x45, 0x0a, 0xa9, 0x9c, + 0x57, 0xa5, 0x03, 0x83, 0x7c, 0x9a, 0xc7, 0x30, 0xb6, 0xac, 0xf2, 0x67, 0x45, 0x5e, 0xe5, 0xd8, 0x31, 0x28, 0x5a, + 0xf0, 0x59, 0x1a, 0xc5, 0x1c, 0xf3, 0xa1, 0x26, 0xf3, 0x85, 0x29, 0xc4, 0xbe, 0x66, 0xe1, 0x05, 0x75, 0xdd, 0xf5, + 0xd8, 0xaf, 0xd0, 0x3d, 0x7e, 0xbd, 0xf3, 0x2b, 0x8f, 0xbe, 0xbe, 0x8e, 0x66, 0xdd, 0x38, 0x8d, 0xca, 0x72, 0xe7, + 0x4d, 0xbe, 0xa4, 0x61, 0x14, 0xf3, 0xb8, 0xca, 0x0b, 0x17, 0x86, 0xc6, 0x32, 0x6f, 0x99, 0x8c, 0xdc, 0x6a, 0x92, + 0x94, 0xfe, 0xd5, 0xbd, 0xb8, 0x2c, 0xdf, 0xf3, 0x72, 0x9e, 0x56, 0xf7, 0x42, 0x80, 0x5b, 0xb6, 0x1b, 0x86, 0x5f, + 0x33, 0xaf, 0x9a, 0x14, 0xf9, 0xf5, 0xce, 0xb3, 0xa2, 0x80, 0x2f, 0x1c, 0x68, 0x5a, 0x94, 0xd8, 0x49, 0xca, 0x9d, + 0x2c, 0xaf, 0x76, 0x74, 0x7d, 0x08, 0x6d, 0x7f, 0xe7, 0x63, 0xc9, 0x77, 0x3e, 0xcf, 0xb3, 0x32, 0x1a, 0x71, 0x28, + 0xfa, 0x79, 0x27, 0x2f, 0x76, 0x3e, 0x43, 0xad, 0x9f, 0x61, 0xee, 0xca, 0x0a, 0x90, 0xc8, 0x77, 0xbc, 0x2e, 0x35, + 0x06, 0x89, 0x1f, 0xf8, 0x4d, 0x15, 0x56, 0x8c, 0x5e, 0xab, 0x90, 0xaf, 0xc6, 0xbc, 0xda, 0x29, 0xf5, 0xb8, 0x5c, + 0x6f, 0x99, 0x42, 0x02, 0x94, 0xc0, 0xfc, 0x5c, 0xc2, 0x9f, 0x8b, 0xd7, 0xaa, 0x0b, 0x9d, 0xbe, 0xce, 0xf6, 0xf6, + 0x2a, 0x0d, 0x68, 0x6f, 0x29, 0x67, 0x28, 0xe4, 0xbb, 0x2a, 0x6d, 0x6f, 0x8f, 0xfb, 0x29, 0xcf, 0xc6, 0xd5, 0x04, + 0x8a, 0xb5, 0xbb, 0x50, 0xde, 0xad, 0xc2, 0x5f, 0x33, 0x1f, 0x5a, 0x72, 0xb9, 0xe7, 0x31, 0xf3, 0x35, 0xe4, 0x08, + 0x20, 0xe4, 0x61, 0x45, 0x80, 0xab, 0xc1, 0xd8, 0xf3, 0x25, 0xf4, 0x2f, 0x16, 0x59, 0xec, 0xda, 0xfd, 0xf7, 0x18, + 0x54, 0x0a, 0x35, 0x96, 0x58, 0x23, 0xab, 0x3c, 0x6f, 0x55, 0xf0, 0x6a, 0x5e, 0x64, 0x3b, 0xd5, 0xaa, 0xca, 0x2f, + 0xaa, 0x22, 0xc9, 0xc6, 0x30, 0x10, 0x95, 0x66, 0x7d, 0xb8, 0x5a, 0x89, 0xee, 0xfe, 0x51, 0x84, 0x49, 0x78, 0x8e, + 0x2d, 0xbe, 0xc9, 0x5d, 0x89, 0x83, 0x49, 0x08, 0x38, 0x48, 0xdf, 0x3a, 0xbd, 0x24, 0x48, 0x00, 0x0b, 0x99, 0xe8, + 0x25, 0xcc, 0x30, 0x34, 0x58, 0x21, 0xea, 0xfa, 0xbe, 0x5f, 0x01, 0xee, 0x2e, 0x15, 0x58, 0x12, 0x6b, 0xa0, 0xbd, + 0xe4, 0xb2, 0xd5, 0x0f, 0x2a, 0xe8, 0xf4, 0x70, 0x1e, 0x73, 0xd7, 0xcd, 0x58, 0xc9, 0x72, 0x28, 0x9c, 0x35, 0xdc, + 0x02, 0x3e, 0x01, 0xd0, 0x15, 0xf5, 0xc9, 0x0e, 0x61, 0xba, 0x3d, 0xd9, 0xc9, 0x42, 0xf5, 0x10, 0x41, 0x2c, 0x3b, + 0x54, 0x40, 0x87, 0x60, 0xb9, 0x0d, 0x78, 0xe1, 0xe8, 0x62, 0xdd, 0x1a, 0x5e, 0xcc, 0x61, 0xde, 0xe1, 0xbb, 0x9d, + 0xd1, 0x3c, 0x8b, 0xab, 0x04, 0xa8, 0x83, 0xd3, 0x28, 0x1a, 0x8e, 0xc0, 0x07, 0x8d, 0x0e, 0x8e, 0xb7, 0xf2, 0xdc, + 0xd2, 0x6b, 0x24, 0x97, 0x79, 0xa3, 0xdd, 0x67, 0xd8, 0x4b, 0xaf, 0x2b, 0xeb, 0x93, 0x10, 0xe0, 0x2c, 0xc1, 0x41, + 0xae, 0xd8, 0x4f, 0x62, 0xe5, 0xe3, 0x10, 0xaf, 0xb3, 0x5e, 0xe2, 0x6f, 0xae, 0x94, 0xb0, 0xf2, 0xa7, 0xd1, 0xcc, + 0xe5, 0xe1, 0x39, 0x27, 0xec, 0x8a, 0xb2, 0x18, 0xfb, 0x5a, 0x9b, 0xb8, 0x1e, 0xac, 0x7b, 0xdf, 0xe0, 0x94, 0x07, + 0x40, 0x19, 0xe5, 0xc5, 0xb3, 0x28, 0x9e, 0xe0, 0x77, 0x1a, 0x63, 0x86, 0x6a, 0xc1, 0xc5, 0x05, 0x8f, 0x2a, 0xfe, + 0x2c, 0xe5, 0xf8, 0xe6, 0x3a, 0xf4, 0xa5, 0xe3, 0xb1, 0x12, 0x97, 0x7a, 0x9a, 0x54, 0x6f, 0x72, 0x68, 0xa3, 0x5b, + 0x5a, 0xf8, 0x45, 0x33, 0xff, 0xa8, 0x82, 0xc9, 0x1a, 0xcc, 0x2b, 0xee, 0x3a, 0x19, 0x96, 0x70, 0x58, 0x09, 0xd3, + 0xe4, 0x57, 0x00, 0xc4, 0x27, 0x79, 0x56, 0x41, 0x55, 0x21, 0x57, 0x50, 0x65, 0x30, 0x94, 0xd9, 0x8c, 0x67, 0xc3, + 0x27, 0x93, 0x24, 0x1d, 0xba, 0x30, 0x54, 0x18, 0xec, 0xef, 0x59, 0x88, 0x83, 0x0c, 0xcf, 0x61, 0xb6, 0xe1, 0xcf, + 0xdd, 0xc3, 0x01, 0xf4, 0x3d, 0xa7, 0x65, 0xc1, 0x43, 0xc7, 0xe9, 0xc2, 0x50, 0x5c, 0x39, 0x84, 0x1d, 0x24, 0x5d, + 0xd8, 0xc6, 0x7b, 0x20, 0xb0, 0xa5, 0xc7, 0x1b, 0x61, 0xa6, 0xe7, 0x51, 0x42, 0xf8, 0x8f, 0x02, 0x70, 0x1e, 0x26, + 0x20, 0x01, 0x3a, 0x48, 0x24, 0xf0, 0x55, 0x22, 0x17, 0xd5, 0x50, 0x13, 0xb5, 0xbf, 0x00, 0x16, 0x89, 0x0f, 0xeb, + 0x19, 0x16, 0xeb, 0xf0, 0x03, 0x4c, 0x7e, 0xc9, 0xaa, 0x28, 0xfc, 0x2b, 0xeb, 0xfd, 0x95, 0xf9, 0x7c, 0x3a, 0xab, + 0x16, 0x17, 0x44, 0xcd, 0x03, 0xc0, 0xc8, 0xdf, 0xa8, 0x28, 0xc0, 0x2b, 0x46, 0x92, 0x26, 0x41, 0xf6, 0x2e, 0x4f, + 0x17, 0xa3, 0x24, 0x4d, 0x2f, 0xe6, 0xb3, 0x59, 0x5e, 0xc0, 0xda, 0xce, 0xc2, 0x65, 0x95, 0x1b, 0xf8, 0xe0, 0x8c, + 0x2e, 0xcb, 0xeb, 0xa4, 0x82, 0x09, 0x80, 0xa7, 0x38, 0x02, 0xf4, 0x78, 0x9c, 0xe7, 0x29, 0x8f, 0x32, 0x18, 0x79, + 0xd2, 0x03, 0x66, 0x92, 0xcd, 0xd3, 0xb4, 0x3b, 0x80, 0x7a, 0xbf, 0x76, 0x29, 0x5b, 0x30, 0x87, 0x80, 0x9e, 0x1f, + 0x15, 0x45, 0xb4, 0xc0, 0x82, 0x61, 0x88, 0xc5, 0x60, 0x71, 0xfc, 0x74, 0xf1, 0xf6, 0x8d, 0x2f, 0xd6, 0x4a, 0x32, + 0x5a, 0xc0, 0xd8, 0xd4, 0xfa, 0x4b, 0x56, 0x6c, 0x54, 0xe4, 0xd3, 0xb5, 0xa6, 0x05, 0xe8, 0x92, 0xee, 0x1d, 0x5d, + 0x80, 0xac, 0x5d, 0x51, 0xb5, 0xdd, 0x83, 0x37, 0x84, 0xf9, 0x98, 0x19, 0xca, 0x76, 0xf1, 0x4f, 0x20, 0x92, 0xa1, + 0xc9, 0xef, 0xf7, 0xb6, 0x2a, 0x16, 0x4b, 0x1e, 0x52, 0x3f, 0x67, 0xc8, 0x18, 0xb1, 0x8f, 0x71, 0x04, 0xad, 0x43, + 0x2a, 0xd6, 0xb3, 0x52, 0x3d, 0xe6, 0xab, 0x15, 0xfb, 0x3b, 0x57, 0x58, 0x0f, 0x7c, 0x28, 0x4c, 0x88, 0x5e, 0x85, + 0xd5, 0xed, 0x2d, 0xb4, 0x9c, 0x78, 0xec, 0x7d, 0x12, 0x2e, 0x23, 0x35, 0x20, 0xe4, 0x6c, 0xb8, 0x3c, 0x03, 0x41, + 0x65, 0x90, 0x03, 0x7e, 0x03, 0xbe, 0x09, 0x1d, 0xad, 0x32, 0x06, 0xac, 0x39, 0xc5, 0x7e, 0xec, 0xb6, 0xd9, 0x24, + 0x2a, 0x9f, 0x4c, 0xa2, 0x6c, 0xcc, 0x87, 0xc1, 0xdf, 0xf9, 0x8a, 0xf1, 0x2c, 0x74, 0x80, 0xcd, 0x46, 0x69, 0xf2, + 0x37, 0x1f, 0x3a, 0x92, 0x2f, 0x7c, 0x02, 0xa8, 0xdc, 0x00, 0x9e, 0x0e, 0xcb, 0x9d, 0x17, 0x1f, 0x5e, 0xbf, 0x92, + 0x93, 0x59, 0xe3, 0x15, 0x30, 0x6d, 0x73, 0xe0, 0xcb, 0xc0, 0x59, 0x24, 0xaf, 0x78, 0x96, 0x10, 0x9d, 0x04, 0xe6, + 0x22, 0x52, 0x92, 0xf2, 0xe3, 0x0c, 0xa4, 0x01, 0xfe, 0x0e, 0xaa, 0x81, 0xfe, 0x84, 0xd0, 0x34, 0xa5, 0x43, 0xfb, + 0x22, 0x63, 0xa8, 0x93, 0xe0, 0xe3, 0x94, 0xc6, 0xae, 0x5f, 0xe7, 0xae, 0xb7, 0x02, 0x94, 0xaf, 0x92, 0x78, 0x27, + 0x1a, 0x0e, 0x5f, 0x66, 0x49, 0x95, 0x50, 0x0f, 0x0b, 0x9c, 0x22, 0xc4, 0x55, 0x2e, 0xb8, 0x86, 0xea, 0x39, 0x74, + 0xc3, 0x75, 0x25, 0x2f, 0x98, 0x78, 0x72, 0xce, 0x80, 0xda, 0xeb, 0x95, 0x09, 0x0b, 0x5f, 0x64, 0x86, 0x97, 0x7d, + 0xcf, 0x9f, 0xcd, 0x4b, 0x9c, 0x6c, 0xd5, 0x04, 0x32, 0x9a, 0x7c, 0x50, 0xf2, 0xe2, 0x1b, 0x1f, 0x6a, 0x04, 0x29, + 0x61, 0x88, 0x6b, 0x6d, 0xc8, 0xe5, 0x51, 0x41, 0x1d, 0x5d, 0x9b, 0x84, 0x73, 0x89, 0xec, 0x42, 0x52, 0x49, 0x78, + 0xa9, 0x89, 0x8a, 0x8b, 0x0c, 0x55, 0x13, 0x96, 0x32, 0x54, 0xe3, 0x9b, 0x01, 0x0d, 0x06, 0x19, 0xc2, 0xa6, 0x1c, + 0x8a, 0xe7, 0x3e, 0xfb, 0x46, 0xcc, 0xa3, 0x84, 0x0f, 0x59, 0x25, 0x7a, 0x5a, 0x02, 0x1b, 0x81, 0x17, 0xd5, 0x5d, + 0x41, 0x94, 0x94, 0x5c, 0x84, 0x5c, 0x3b, 0x7c, 0x9f, 0x10, 0xdb, 0x46, 0xda, 0x06, 0x59, 0x50, 0x19, 0xf7, 0x35, + 0x62, 0x00, 0x98, 0xe5, 0x24, 0x59, 0xd0, 0xba, 0xa3, 0xdf, 0xd8, 0x32, 0x09, 0x38, 0xbb, 0xdc, 0xcf, 0xf2, 0x47, + 0x71, 0xcc, 0xcb, 0x32, 0x2f, 0xf6, 0xf6, 0x76, 0xa9, 0xbc, 0x96, 0x2c, 0x70, 0x12, 0xdf, 0x5e, 0x67, 0xa6, 0x0b, + 0x9e, 0xe1, 0xb6, 0x4a, 0x6e, 0x0a, 0x8d, 0xdc, 0xa4, 0x84, 0x90, 0xc0, 0xb9, 0xba, 0x72, 0x1a, 0x15, 0x93, 0x70, + 0x00, 0xb0, 0xab, 0x1a, 0x9e, 0x72, 0x21, 0x16, 0x92, 0x10, 0xb2, 0x01, 0x9a, 0xad, 0xf2, 0xa0, 0x5b, 0xef, 0x12, + 0xc8, 0x6e, 0xa5, 0xb7, 0xb2, 0x66, 0x74, 0x6b, 0xd5, 0x24, 0xdf, 0x88, 0xa9, 0x5b, 0x8e, 0x49, 0xa6, 0xb0, 0xe6, + 0xf1, 0x92, 0xf7, 0x57, 0x8c, 0x60, 0xaf, 0x46, 0x93, 0x53, 0x47, 0x41, 0x48, 0xec, 0xca, 0xfc, 0xb0, 0x14, 0x90, + 0x2b, 0xf8, 0x5f, 0x73, 0x5e, 0x56, 0x02, 0x91, 0xa1, 0xde, 0x9c, 0x21, 0x8f, 0x5a, 0x17, 0x3a, 0x6b, 0x22, 0xe9, + 0xb6, 0xbe, 0xbd, 0x9d, 0x21, 0x6f, 0x2c, 0x11, 0xa9, 0xbf, 0x8f, 0x4f, 0xd8, 0xd7, 0xca, 0xbb, 0xbd, 0x7d, 0x9f, + 0xa8, 0x5a, 0xcc, 0x5c, 0x6a, 0x79, 0x6d, 0x6d, 0x52, 0x78, 0xe6, 0x49, 0xe6, 0xbc, 0xdb, 0x96, 0xfd, 0xcf, 0xfa, + 0xc0, 0xd8, 0x35, 0x16, 0x4b, 0xb0, 0x8a, 0xfe, 0x08, 0x28, 0xbe, 0x15, 0x55, 0x79, 0xc4, 0xeb, 0x6b, 0xf8, 0xe2, + 0x4f, 0x36, 0x70, 0x15, 0xd6, 0x12, 0x4a, 0x1d, 0xfe, 0xa4, 0x7f, 0x17, 0x3e, 0x29, 0x8a, 0x00, 0x75, 0x6d, 0xe4, + 0x19, 0xc2, 0xf1, 0xad, 0x4e, 0x38, 0xd6, 0x86, 0xe1, 0xcc, 0xf4, 0x27, 0x8e, 0x46, 0x33, 0xb9, 0xd4, 0x4d, 0x16, + 0xcb, 0xa8, 0x33, 0x66, 0x48, 0x56, 0x15, 0x6f, 0xa2, 0x29, 0xac, 0x66, 0x40, 0xea, 0xbb, 0x0a, 0x08, 0xfc, 0xc4, + 0x22, 0x7d, 0x8b, 0x87, 0x96, 0xc8, 0x43, 0x51, 0xdc, 0x45, 0x21, 0xad, 0xbe, 0xe4, 0x4a, 0xc6, 0x2f, 0xcb, 0xbe, + 0x91, 0xed, 0xac, 0xc1, 0x13, 0x73, 0x96, 0x08, 0xae, 0xe0, 0x27, 0xd2, 0x04, 0xd0, 0x48, 0x84, 0x80, 0xc1, 0x03, + 0x42, 0xac, 0xcd, 0xa4, 0x2a, 0x65, 0xc6, 0x08, 0x64, 0x06, 0xe6, 0x81, 0xd8, 0x06, 0x90, 0x53, 0xfa, 0xad, 0x2d, + 0x35, 0x04, 0xdb, 0x05, 0x62, 0x86, 0x3f, 0x4a, 0xa3, 0xca, 0x6d, 0x1f, 0xb4, 0x50, 0x30, 0x05, 0xaa, 0x0f, 0x5c, + 0xc5, 0xf3, 0x36, 0x87, 0xc2, 0x7d, 0x10, 0xbd, 0x26, 0xc9, 0xa8, 0x72, 0x7f, 0xcf, 0x88, 0xa8, 0xf0, 0x14, 0xd8, + 0x52, 0x55, 0x13, 0x8f, 0x89, 0xe0, 0x40, 0x36, 0xb4, 0xd3, 0xd5, 0x8c, 0x48, 0xf6, 0x94, 0x08, 0x17, 0x92, 0x07, + 0x23, 0x5a, 0x1b, 0x32, 0xa3, 0x05, 0x37, 0x94, 0x1e, 0xdb, 0x3d, 0x51, 0x63, 0x20, 0xa9, 0x41, 0x66, 0x49, 0xb0, + 0x59, 0x60, 0x93, 0x08, 0x99, 0x58, 0xf9, 0x55, 0xfe, 0x2a, 0xbf, 0xe6, 0xc5, 0x93, 0x08, 0x3b, 0x1f, 0x88, 0xcf, + 0x57, 0x82, 0x15, 0x10, 0xc5, 0xaf, 0xba, 0x0a, 0x5f, 0xae, 0x68, 0xe0, 0x30, 0x19, 0xd3, 0x04, 0xca, 0x82, 0xdc, + 0x26, 0xe0, 0x9f, 0xe1, 0x42, 0xa3, 0x15, 0x89, 0xec, 0x86, 0x6b, 0xfc, 0x7a, 0xf4, 0xaa, 0x8e, 0x5f, 0x50, 0xc3, + 0x58, 0x51, 0xc0, 0xfa, 0x3a, 0x06, 0x26, 0x22, 0xd5, 0x0b, 0x8b, 0xd3, 0x01, 0x3f, 0x91, 0x6c, 0xfe, 0xf6, 0xb6, + 0xb2, 0xd4, 0xb8, 0x9a, 0xe4, 0xc8, 0xc5, 0xb2, 0xf1, 0x56, 0xc0, 0xad, 0x50, 0xc4, 0x2b, 0xf2, 0x34, 0xb5, 0x98, + 0x15, 0xcb, 0xba, 0x9a, 0x3d, 0x41, 0xf3, 0x17, 0xdf, 0xe3, 0x50, 0x98, 0x6f, 0x33, 0x29, 0xd5, 0xd1, 0x6c, 0xc8, + 0x0b, 0xd4, 0x2b, 0xad, 0xd9, 0x92, 0x7c, 0x16, 0x1a, 0xcc, 0x00, 0xa9, 0xf9, 0x10, 0x95, 0x16, 0x20, 0xc0, 0xfe, + 0x24, 0x2f, 0x2b, 0x9d, 0x68, 0x7a, 0x9f, 0xd9, 0x4a, 0xa8, 0x1f, 0x47, 0x69, 0xea, 0x0a, 0x05, 0x65, 0x9a, 0x7f, + 0xe3, 0x5b, 0x7a, 0xdd, 0xad, 0x75, 0x59, 0x57, 0xc3, 0xad, 0x6a, 0x80, 0xe1, 0xcc, 0xd2, 0x24, 0xe6, 0x9a, 0x79, + 0x5d, 0xf8, 0x20, 0x38, 0xf2, 0x1b, 0xa4, 0x23, 0xde, 0xf9, 0xf9, 0x79, 0x8b, 0xb5, 0xbd, 0x95, 0x00, 0xf8, 0x72, + 0x03, 0xb0, 0xdf, 0x61, 0x9b, 0x42, 0x11, 0x5f, 0x6e, 0x25, 0x6b, 0x9e, 0xc5, 0x2b, 0x13, 0xa5, 0x68, 0x09, 0xf2, + 0xec, 0xb1, 0x21, 0x54, 0x5a, 0x71, 0x45, 0xce, 0x51, 0x98, 0x16, 0x4b, 0xf7, 0xbd, 0x86, 0x9f, 0x46, 0x27, 0xb5, + 0xca, 0xd4, 0x9c, 0x97, 0x5a, 0x75, 0x37, 0xd3, 0x63, 0xa0, 0xdd, 0xab, 0xc4, 0xf4, 0x00, 0xbe, 0x43, 0x0f, 0x85, + 0xc6, 0xee, 0x6e, 0x0c, 0xc9, 0xd4, 0x21, 0x49, 0xbb, 0x5e, 0x44, 0x3f, 0x15, 0xb2, 0x9b, 0xdb, 0x40, 0x70, 0x21, + 0x89, 0x02, 0x47, 0x25, 0x50, 0x4c, 0xdb, 0x13, 0x98, 0x9e, 0x41, 0x14, 0x7f, 0xad, 0x63, 0xbf, 0x41, 0x83, 0x70, + 0x9d, 0x1a, 0x5b, 0x59, 0x16, 0xc9, 0xb2, 0xc7, 0xad, 0xa8, 0x74, 0x6d, 0xa1, 0xb8, 0xa0, 0xe9, 0x69, 0xb4, 0xaf, + 0x4f, 0xf4, 0x9d, 0xd8, 0x4e, 0x3d, 0xca, 0xe4, 0xc8, 0x5c, 0xa4, 0x02, 0xff, 0x16, 0xe3, 0x14, 0x3d, 0x90, 0x78, + 0x87, 0x8a, 0xc7, 0x6a, 0xad, 0x23, 0x80, 0x76, 0xab, 0x61, 0x52, 0xde, 0x0d, 0x81, 0xff, 0x23, 0xbd, 0x7c, 0x6a, + 0xb5, 0xf0, 0x4f, 0x3b, 0xaa, 0x69, 0x9c, 0x14, 0x9c, 0x75, 0xcf, 0xa4, 0x40, 0xa1, 0x08, 0xcd, 0xcf, 0x28, 0xbc, + 0x10, 0xbe, 0xbf, 0x15, 0x59, 0x24, 0x97, 0x61, 0x37, 0xca, 0xae, 0x2d, 0x50, 0xd4, 0x50, 0x40, 0x12, 0xd5, 0x8c, + 0x78, 0x6e, 0x5e, 0x53, 0x25, 0xa5, 0xd4, 0x2e, 0xd4, 0x71, 0x49, 0x73, 0x41, 0x0d, 0x76, 0xdd, 0x12, 0xb5, 0x39, + 0x25, 0xdf, 0x9b, 0x51, 0x94, 0x1b, 0xa3, 0x28, 0x7d, 0x4b, 0xdb, 0xf2, 0x0c, 0x32, 0x5b, 0x9f, 0x83, 0x7a, 0xe0, + 0xd9, 0xa5, 0x50, 0x64, 0xf5, 0x91, 0x42, 0x7b, 0x9a, 0xe0, 0xa6, 0x61, 0xc5, 0x0a, 0xa9, 0xea, 0x48, 0x5c, 0x43, + 0x92, 0x61, 0x3e, 0xc9, 0x3d, 0xb1, 0x38, 0x6a, 0xba, 0x6f, 0xce, 0x0a, 0x6f, 0x4d, 0xbe, 0x5f, 0xad, 0x24, 0x94, + 0xb8, 0x27, 0x67, 0xa7, 0x26, 0x18, 0x5b, 0x60, 0x61, 0x79, 0x28, 0x85, 0x61, 0x21, 0xfa, 0xac, 0x03, 0x47, 0xd7, + 0x0b, 0x69, 0xb9, 0x81, 0x4d, 0x4d, 0xa8, 0x54, 0xd2, 0x55, 0xee, 0xb1, 0x48, 0x89, 0xa5, 0x85, 0x19, 0x38, 0x70, + 0x1f, 0x65, 0x9d, 0x70, 0x7a, 0xcb, 0x9a, 0x72, 0x18, 0x58, 0xc5, 0x56, 0x01, 0x12, 0xd5, 0x62, 0x1b, 0xbc, 0xb7, + 0x61, 0x4d, 0xad, 0x1e, 0x0b, 0xe2, 0x45, 0x0d, 0xe2, 0x16, 0x68, 0x73, 0x41, 0xbc, 0xf2, 0x7e, 0x18, 0xd5, 0x3f, + 0x86, 0x89, 0x28, 0xc4, 0x44, 0x6c, 0x40, 0x71, 0x5d, 0xfc, 0x24, 0x2c, 0x44, 0x5d, 0xb6, 0x44, 0xf9, 0xce, 0x66, + 0x11, 0x2e, 0x76, 0x3e, 0x83, 0x95, 0xb1, 0x8e, 0x76, 0x5b, 0xa5, 0x50, 0xcf, 0x37, 0xda, 0xe1, 0xed, 0xed, 0xdf, + 0xb9, 0xe7, 0x4a, 0xf9, 0x17, 0x26, 0xac, 0xa7, 0x88, 0xee, 0xa3, 0x57, 0x58, 0x8a, 0xc4, 0x51, 0x93, 0xa2, 0x15, + 0x87, 0x3a, 0xd6, 0xd6, 0x27, 0xaa, 0xb2, 0x28, 0xf7, 0x93, 0x0d, 0x02, 0x46, 0x89, 0x92, 0x53, 0x9b, 0x21, 0x3f, + 0x91, 0x55, 0x83, 0x30, 0xeb, 0x05, 0x25, 0xe9, 0x32, 0xbb, 0xdb, 0xf4, 0xcb, 0xbd, 0xbd, 0xd2, 0xaa, 0xe8, 0x4a, + 0x53, 0x8a, 0x2f, 0x2e, 0x72, 0xe5, 0x72, 0x91, 0x91, 0xf8, 0xf2, 0x45, 0xf1, 0xa1, 0x0d, 0xed, 0x14, 0xc0, 0x06, + 0x8a, 0x79, 0x74, 0x1d, 0x25, 0xd5, 0x8e, 0xae, 0x45, 0x28, 0xe6, 0x40, 0x04, 0x96, 0x52, 0xda, 0x80, 0xc1, 0xa1, + 0xfc, 0x88, 0x64, 0x41, 0x49, 0xd1, 0x02, 0xf1, 0xe3, 0x09, 0x47, 0x53, 0xb6, 0x12, 0x24, 0xb4, 0x7a, 0xb8, 0x2b, + 0x19, 0x89, 0xac, 0x78, 0x7b, 0xdf, 0x57, 0xeb, 0x9f, 0xd7, 0xb4, 0x01, 0x98, 0x23, 0xa0, 0x6a, 0x53, 0x95, 0xb7, + 0x5a, 0x7b, 0x97, 0xc4, 0x11, 0xd6, 0xc7, 0xd6, 0xba, 0xa5, 0x0a, 0xd0, 0x5d, 0xd3, 0xbd, 0x8d, 0xd6, 0x5e, 0xe3, + 0xa6, 0x9a, 0x01, 0x0b, 0x03, 0xa1, 0xc2, 0xcc, 0xd2, 0xd6, 0xf2, 0xa5, 0x79, 0xb5, 0x2b, 0x6c, 0x27, 0xa0, 0x5b, + 0x68, 0xcd, 0x4f, 0x61, 0x43, 0x57, 0xd8, 0x38, 0x24, 0x57, 0xcd, 0xe7, 0xe9, 0x50, 0x76, 0x16, 0x54, 0x5a, 0x2e, + 0xf1, 0xe8, 0x3a, 0x49, 0x53, 0x93, 0xfa, 0x9f, 0x90, 0xf6, 0x52, 0x92, 0xf6, 0x5c, 0x91, 0x76, 0x24, 0x15, 0x48, + 0xda, 0x45, 0x75, 0xe6, 0xf3, 0x7c, 0x63, 0x79, 0xe6, 0x82, 0xa8, 0x97, 0xa4, 0x4e, 0x63, 0x7b, 0x73, 0xd5, 0x03, + 0x4f, 0x0b, 0x5f, 0xc0, 0x6f, 0xe4, 0xb4, 0x97, 0x88, 0x2b, 0x68, 0xd3, 0xe4, 0xb6, 0xa5, 0x02, 0xf2, 0x59, 0xb9, + 0xe2, 0x1a, 0xb3, 0x1f, 0x3d, 0x43, 0xa3, 0x9d, 0x35, 0x1c, 0xe4, 0x63, 0x94, 0xfc, 0x1f, 0xc9, 0x51, 0x6a, 0x74, + 0x99, 0x1c, 0x5d, 0xa9, 0x46, 0x87, 0xb4, 0xde, 0x8c, 0x6e, 0xf8, 0x7d, 0x6a, 0x4f, 0xc3, 0xcb, 0xf4, 0xf0, 0xcc, + 0x7c, 0xdf, 0xde, 0xba, 0x6b, 0x29, 0x68, 0xd1, 0x97, 0x5a, 0x4a, 0xa1, 0x6b, 0x47, 0x1a, 0x60, 0x43, 0x06, 0x13, + 0x56, 0x62, 0xd0, 0x9a, 0xcb, 0xbd, 0xfa, 0x77, 0x76, 0x1e, 0x32, 0xdc, 0x8b, 0xef, 0x9f, 0xe4, 0xd3, 0x19, 0x0a, + 0x64, 0x6b, 0x28, 0x0d, 0x05, 0x3e, 0xae, 0xe5, 0xaf, 0xb6, 0xa4, 0xd5, 0xbe, 0xa1, 0xf5, 0x58, 0xc3, 0x26, 0xad, + 0x35, 0x83, 0x2e, 0x35, 0xd7, 0x49, 0x9a, 0x70, 0x6c, 0xb3, 0xad, 0x3c, 0x59, 0xb7, 0xcc, 0xa8, 0x8c, 0xb7, 0x6e, + 0x26, 0xe8, 0x70, 0x86, 0xb4, 0xce, 0x22, 0x3f, 0x0a, 0xdd, 0xed, 0xf9, 0x5f, 0x19, 0xe0, 0x2c, 0x57, 0x6b, 0xe0, + 0x5b, 0xae, 0x56, 0x9f, 0x2a, 0xa9, 0x69, 0xb3, 0x4f, 0x5b, 0xf4, 0x5e, 0x0d, 0x3d, 0x93, 0x29, 0x75, 0xc6, 0xcb, + 0x3e, 0xa6, 0x6d, 0x88, 0x90, 0xe1, 0x72, 0x9a, 0x0f, 0x79, 0xe0, 0x40, 0x05, 0x99, 0xb3, 0x42, 0x3b, 0xab, 0x44, + 0x80, 0xdf, 0x32, 0x77, 0xf9, 0xbe, 0x6e, 0x6f, 0x0d, 0x3e, 0x55, 0x2b, 0x34, 0x85, 0xbd, 0x4a, 0xb6, 0x18, 0x63, + 0x3f, 0x81, 0x62, 0x48, 0x32, 0xa9, 0x16, 0x6f, 0x5f, 0x25, 0x86, 0x41, 0xbd, 0x4a, 0x82, 0xbb, 0x3f, 0x31, 0x0a, + 0x89, 0xd3, 0xf6, 0x4f, 0xfc, 0x43, 0xc7, 0x23, 0x8b, 0xf1, 0x73, 0x65, 0x31, 0x9e, 0x6b, 0x8b, 0xf1, 0x8b, 0x2a, + 0x9c, 0xaf, 0x59, 0x8c, 0x7f, 0xce, 0xc2, 0x17, 0x55, 0xef, 0x85, 0xb2, 0xa6, 0xbf, 0xcb, 0x41, 0x63, 0x00, 0xbd, + 0x3e, 0x4d, 0xaa, 0x26, 0xee, 0x26, 0x3a, 0x6c, 0x29, 0x32, 0xd0, 0xd4, 0x48, 0xf6, 0xee, 0x95, 0xd2, 0xff, 0x58, + 0x96, 0x85, 0xce, 0x3d, 0x28, 0x78, 0xcf, 0x61, 0x93, 0x2a, 0xfc, 0x8c, 0x4f, 0xf7, 0x96, 0xee, 0xeb, 0xa8, 0x9a, + 0xf8, 0x45, 0x04, 0xed, 0x4d, 0x5d, 0xaf, 0xe1, 0x38, 0x9e, 0x5f, 0x92, 0x12, 0xf2, 0xd0, 0x5b, 0xdd, 0xfb, 0xcc, + 0xbe, 0xe4, 0xa1, 0xd3, 0x73, 0x1a, 0x13, 0x60, 0x47, 0x51, 0xf8, 0xf9, 0xec, 0xde, 0xf2, 0x4b, 0xbe, 0x3a, 0xff, + 0xcc, 0x9e, 0x55, 0xda, 0xac, 0xcf, 0x6e, 0x40, 0xea, 0x87, 0xc9, 0x7f, 0xa6, 0xba, 0x04, 0x28, 0x27, 0x0c, 0xfc, + 0x8e, 0xc7, 0xbe, 0xa1, 0x5d, 0xf7, 0x3c, 0x31, 0x44, 0x48, 0xee, 0xc1, 0xec, 0x86, 0x4e, 0x4e, 0xc6, 0x03, 0x07, + 0x96, 0xbe, 0x49, 0xd3, 0x22, 0x04, 0x7b, 0x9c, 0x87, 0x35, 0x55, 0x9d, 0x25, 0x11, 0xd6, 0xf4, 0x38, 0x77, 0x13, + 0x4f, 0x55, 0xe3, 0x2a, 0x4b, 0xb5, 0x5c, 0xb1, 0xc9, 0xa5, 0xb0, 0x3d, 0xf8, 0x09, 0xc8, 0x05, 0x11, 0xf0, 0xe5, + 0xbe, 0x67, 0x8b, 0x25, 0xec, 0x4d, 0x12, 0x7e, 0xbe, 0xdc, 0xf9, 0x6f, 0xff, 0xfd, 0xcf, 0xd1, 0x9f, 0x45, 0xff, + 0x33, 0xcb, 0x78, 0x78, 0x70, 0xe6, 0xf6, 0x02, 0x77, 0xb7, 0xd9, 0xbc, 0xfd, 0xf3, 0xe0, 0xf2, 0x5f, 0x51, 0xf3, + 0xef, 0x47, 0xcd, 0x3f, 0xfa, 0xde, 0xad, 0xfb, 0xe7, 0x41, 0xef, 0x52, 0xbe, 0x5d, 0xfe, 0xeb, 0xfc, 0xcf, 0xb2, + 0xbf, 0x2f, 0x12, 0xef, 0x79, 0xde, 0xc1, 0x98, 0xfd, 0x98, 0x85, 0x07, 0xcd, 0xe6, 0x39, 0x3c, 0xfd, 0x02, 0x4f, + 0xf8, 0xbb, 0xa8, 0xc2, 0xf7, 0x7c, 0xfc, 0xec, 0x66, 0xe6, 0x7e, 0x3e, 0xbf, 0xbd, 0xb7, 0x7c, 0x93, 0xac, 0xb0, + 0xde, 0xcb, 0x7f, 0xfd, 0xf9, 0x67, 0xe9, 0xfc, 0x70, 0x1e, 0x1e, 0xf4, 0x1b, 0x9e, 0x4b, 0xc9, 0xfb, 0xa1, 0xf8, + 0x81, 0xec, 0xcb, 0x7f, 0xc9, 0xae, 0x38, 0x3f, 0xfc, 0xf9, 0xf9, 0xec, 0x3c, 0xec, 0xdf, 0xba, 0xce, 0xed, 0x0f, + 0xde, 0xad, 0xe7, 0xdd, 0xde, 0xf3, 0x3e, 0x33, 0x67, 0x0c, 0xe0, 0xfb, 0x03, 0xea, 0xff, 0x01, 0xea, 0xff, 0x09, + 0x7e, 0x1d, 0xf8, 0xfd, 0x94, 0x87, 0x07, 0xff, 0x82, 0x6f, 0x85, 0x11, 0xee, 0x96, 0xcc, 0x1f, 0xb7, 0xb8, 0x13, + 0x12, 0x01, 0xe4, 0x6f, 0xab, 0xa4, 0x4a, 0xb9, 0x77, 0xef, 0x20, 0x61, 0x2f, 0x72, 0x04, 0x16, 0x30, 0x7a, 0xdf, + 0xf7, 0x69, 0x13, 0x76, 0x79, 0x85, 0x13, 0x8f, 0x18, 0x74, 0x2f, 0x48, 0x98, 0xb0, 0x13, 0x94, 0x41, 0x25, 0x76, + 0x6f, 0x4b, 0xdc, 0xbe, 0x65, 0x4f, 0xc2, 0x17, 0xb9, 0x0b, 0x02, 0x41, 0x16, 0xe1, 0x43, 0xc7, 0x63, 0x1f, 0x2b, + 0xb9, 0xe1, 0x89, 0xcb, 0x5c, 0x60, 0x58, 0x96, 0x0b, 0x79, 0x06, 0xba, 0xf6, 0x6a, 0x4b, 0x26, 0xac, 0xea, 0x0c, + 0xbb, 0x5d, 0x95, 0xf6, 0xf6, 0x28, 0x7b, 0x52, 0x85, 0x1a, 0x39, 0x3e, 0x14, 0x9c, 0xff, 0x1a, 0xa5, 0x5f, 0x41, + 0x33, 0x7e, 0x56, 0xb1, 0x76, 0xe7, 0x21, 0x23, 0x53, 0x35, 0x48, 0x22, 0x5d, 0xbd, 0xbb, 0xf5, 0x31, 0x17, 0xfb, + 0x09, 0xc8, 0x85, 0xeb, 0xf6, 0x1a, 0x9c, 0xfb, 0xdd, 0x64, 0xc3, 0xa8, 0x55, 0x44, 0xd7, 0x8e, 0x57, 0xdf, 0x4a, + 0x4d, 0x32, 0x18, 0x1a, 0x60, 0x45, 0xc5, 0x81, 0xfe, 0x41, 0xbb, 0x3b, 0x72, 0xcc, 0x3b, 0x11, 0x56, 0xe4, 0x68, + 0x99, 0xe2, 0xe7, 0xcc, 0x2c, 0xda, 0x9f, 0x33, 0xdf, 0xac, 0x1d, 0x17, 0xf7, 0xb3, 0xa4, 0x5c, 0x52, 0x46, 0x7a, + 0xbb, 0x6c, 0x7d, 0x47, 0xb0, 0xd9, 0x46, 0x63, 0x59, 0x9f, 0xf8, 0x37, 0xb0, 0xf9, 0x10, 0xb9, 0x6c, 0xa7, 0xe7, + 0x9c, 0x95, 0xdf, 0xc6, 0xe7, 0x0e, 0xee, 0xe4, 0x14, 0x00, 0x0a, 0x32, 0x1e, 0x61, 0x89, 0x28, 0x6c, 0x75, 0xa3, + 0x33, 0xde, 0x8d, 0x1a, 0x0d, 0x25, 0x66, 0xc7, 0x61, 0x72, 0x19, 0x89, 0xef, 0x53, 0x36, 0x61, 0xc3, 0x10, 0x6a, + 0x9c, 0x43, 0x31, 0xfc, 0xa4, 0x3b, 0x3f, 0x8b, 0x65, 0x3b, 0x40, 0x76, 0x0b, 0x3f, 0x8d, 0xca, 0xea, 0x25, 0x5a, + 0x04, 0xc2, 0x39, 0x9b, 0x80, 0x14, 0xcd, 0x6f, 0x78, 0xec, 0xc6, 0x1e, 0x9b, 0x48, 0x1a, 0xe4, 0x75, 0xbd, 0x79, + 0x68, 0x15, 0x43, 0x3d, 0x03, 0x9a, 0xef, 0x4d, 0x2e, 0xdb, 0x7d, 0x78, 0x72, 0x00, 0xd1, 0x9d, 0x5e, 0x11, 0xfe, + 0x98, 0x05, 0x98, 0x62, 0x89, 0xd3, 0xe1, 0x2f, 0x98, 0xd4, 0xb1, 0x92, 0xdc, 0x4f, 0xb9, 0x5f, 0x81, 0x54, 0xec, + 0x62, 0x32, 0x1a, 0x09, 0x4a, 0x85, 0xe1, 0xce, 0xd9, 0x01, 0xd0, 0x03, 0x48, 0x25, 0x14, 0xf5, 0xa0, 0x8d, 0x05, + 0x80, 0x6a, 0x72, 0x79, 0xd8, 0xb7, 0x79, 0x84, 0x48, 0xc5, 0xf6, 0x17, 0x15, 0xb4, 0xdf, 0xa2, 0xf6, 0xcf, 0x9d, + 0x1e, 0x64, 0x94, 0x42, 0x8c, 0xeb, 0x95, 0x41, 0xc6, 0x69, 0xbc, 0x5e, 0x20, 0x3b, 0x28, 0xdb, 0x86, 0xb4, 0x4e, + 0xe0, 0x0e, 0xed, 0x91, 0x34, 0xb1, 0x41, 0x09, 0x0a, 0x96, 0x86, 0x58, 0x1e, 0x1a, 0xc6, 0x46, 0xcd, 0x67, 0x8b, + 0x2a, 0x90, 0x09, 0x3f, 0x38, 0x3f, 0xf4, 0x7e, 0xca, 0x82, 0x3f, 0x32, 0xd1, 0x83, 0x9f, 0x40, 0x64, 0xc7, 0xdf, + 0x3f, 0xb2, 0x1e, 0x76, 0x8b, 0xd2, 0x7e, 0x94, 0x69, 0xbf, 0x60, 0x5a, 0xc6, 0x03, 0xea, 0x30, 0x2b, 0xb5, 0x3c, + 0x26, 0x26, 0x67, 0x14, 0x8a, 0x11, 0xec, 0xed, 0xc1, 0x24, 0x35, 0xda, 0x7d, 0xdc, 0x11, 0x28, 0xaa, 0xf2, 0xd7, + 0xa4, 0x02, 0xda, 0x7d, 0x70, 0xee, 0x78, 0x3d, 0x67, 0x07, 0x67, 0xb9, 0x9b, 0x37, 0x42, 0x09, 0xeb, 0xb8, 0xc1, + 0xa3, 0x60, 0x78, 0x1e, 0x02, 0x08, 0x33, 0x41, 0xe4, 0x53, 0x8f, 0xc5, 0x92, 0xa6, 0xb6, 0xd8, 0xd0, 0x6b, 0x64, + 0x59, 0x43, 0xbd, 0xc3, 0xdb, 0xa4, 0x6a, 0x8c, 0xbc, 0x20, 0xc6, 0x5f, 0x18, 0x72, 0x08, 0x43, 0xd7, 0x1f, 0x2a, + 0x66, 0x19, 0x79, 0xc1, 0x48, 0x99, 0x47, 0x2f, 0x69, 0x71, 0xe4, 0x0d, 0x37, 0xb9, 0xe4, 0xfd, 0xdb, 0x5b, 0xe7, + 0xac, 0x07, 0xbd, 0x68, 0xb8, 0x0a, 0xed, 0x0e, 0x14, 0xde, 0xc1, 0xc4, 0x64, 0xfd, 0x95, 0xdc, 0x81, 0xba, 0xe6, + 0xb5, 0xdd, 0xa6, 0xa5, 0x59, 0xff, 0x16, 0x59, 0xe0, 0x2b, 0xad, 0xf7, 0x08, 0xf9, 0x76, 0x86, 0x43, 0x55, 0xb8, + 0x9d, 0x87, 0x2d, 0x00, 0xb8, 0x32, 0x77, 0x83, 0x06, 0x68, 0xf0, 0x3f, 0x0e, 0x4d, 0x71, 0x76, 0x09, 0x48, 0x0c, + 0x22, 0x6e, 0x44, 0xfa, 0x4b, 0x57, 0x19, 0xd3, 0x79, 0x1a, 0x5e, 0xf3, 0xb5, 0xfd, 0xdf, 0x14, 0xf7, 0x64, 0x9e, + 0x00, 0x5d, 0x98, 0x17, 0x05, 0xbc, 0xbf, 0x01, 0xb6, 0x1c, 0xca, 0xc2, 0xa8, 0x5b, 0xe1, 0xc6, 0x2e, 0x43, 0xa9, + 0xae, 0xa3, 0x56, 0xca, 0x70, 0x23, 0x7b, 0x1e, 0x0e, 0x85, 0xc0, 0x45, 0xdb, 0xbd, 0xdd, 0xb9, 0x54, 0xa5, 0x41, + 0xa6, 0x1c, 0xca, 0x7d, 0x60, 0x17, 0x08, 0xe0, 0xdc, 0x8f, 0x31, 0x1b, 0x1b, 0x00, 0x59, 0x95, 0xd6, 0x15, 0x60, + 0x33, 0xb4, 0x9c, 0x01, 0xe1, 0xc4, 0x54, 0x50, 0x6a, 0x34, 0x13, 0x57, 0xeb, 0xed, 0x2c, 0xea, 0x12, 0x01, 0x2a, + 0xfd, 0x0c, 0x4a, 0x20, 0x84, 0x70, 0xef, 0x5f, 0x26, 0x01, 0xfd, 0xb1, 0x77, 0xb6, 0x4c, 0x07, 0x2f, 0x6d, 0x93, + 0xf7, 0x1c, 0xed, 0xc4, 0x24, 0x9e, 0xe9, 0xc2, 0xc2, 0x78, 0xee, 0x79, 0x50, 0xcb, 0xdc, 0xc7, 0x1d, 0x41, 0xc2, + 0xa4, 0x2c, 0x03, 0xb2, 0x36, 0xb7, 0x71, 0x6b, 0x62, 0x0c, 0xd3, 0x23, 0xc0, 0xf2, 0xa2, 0xd1, 0x20, 0xe3, 0xf5, + 0x50, 0xe0, 0xc5, 0xdc, 0x63, 0x23, 0xbd, 0xd6, 0x54, 0xb9, 0x59, 0x58, 0x6f, 0xca, 0x1d, 0xd5, 0x8d, 0xc0, 0x80, + 0x76, 0x1e, 0xd9, 0x17, 0x2b, 0xac, 0x9d, 0x8d, 0xc3, 0x03, 0xf7, 0xd2, 0xef, 0xfd, 0x8f, 0x3e, 0xa8, 0xa2, 0xfe, + 0xbe, 0x77, 0x20, 0x68, 0xc9, 0x08, 0x10, 0x5f, 0xb4, 0xb1, 0xa4, 0xdd, 0xcf, 0x36, 0x23, 0x03, 0x64, 0x90, 0x03, + 0x57, 0x98, 0xf2, 0x60, 0x8c, 0xab, 0x5e, 0x21, 0xcf, 0x8c, 0x21, 0x32, 0x41, 0x9a, 0xa0, 0x2d, 0x3e, 0x50, 0x96, + 0x48, 0xbf, 0xf5, 0x9c, 0x5e, 0x6c, 0xde, 0xfe, 0x87, 0xd3, 0x4b, 0xa3, 0xe0, 0x49, 0xb2, 0x92, 0x46, 0xf2, 0x5a, + 0x1b, 0x27, 0xaa, 0x8d, 0x95, 0x98, 0x1c, 0x0b, 0x78, 0x43, 0x6f, 0xd3, 0x3a, 0x32, 0xf7, 0x56, 0x00, 0x09, 0x45, + 0x9d, 0x4a, 0xbf, 0x8a, 0xc6, 0x08, 0x55, 0x6b, 0x12, 0x4a, 0xdb, 0x37, 0xc0, 0x1a, 0x32, 0x62, 0x8b, 0x42, 0x5a, + 0x84, 0xe6, 0xfc, 0x1c, 0x80, 0x57, 0x2b, 0x2c, 0x25, 0xab, 0xfa, 0x5e, 0xbc, 0x26, 0xde, 0x23, 0xa4, 0xca, 0x67, + 0xf3, 0xee, 0x08, 0x88, 0x77, 0xa9, 0xf0, 0x6b, 0x78, 0x39, 0xea, 0x83, 0x04, 0x84, 0xf6, 0xc0, 0x1a, 0x46, 0xb1, + 0xda, 0x18, 0x3b, 0x72, 0x8c, 0x8d, 0x06, 0x8c, 0xb2, 0x6b, 0x7d, 0x3c, 0x97, 0x1f, 0xaf, 0x56, 0x02, 0x32, 0xeb, + 0x18, 0x77, 0xea, 0x51, 0x0a, 0x3a, 0x82, 0xc1, 0xdb, 0x97, 0xdc, 0xdb, 0x5a, 0x2d, 0x56, 0x8a, 0x9f, 0xd3, 0xea, + 0x45, 0x8a, 0x2a, 0xb8, 0x87, 0x8b, 0xb0, 0xc0, 0x4f, 0xb5, 0x19, 0x19, 0xc4, 0xb8, 0x61, 0xa3, 0x4d, 0xe8, 0x0e, + 0x85, 0xea, 0x95, 0x3d, 0x30, 0x95, 0x41, 0xa1, 0x70, 0x62, 0x56, 0xf8, 0x2a, 0x6f, 0x34, 0x56, 0xf5, 0xfd, 0x52, + 0xb5, 0x88, 0x6b, 0xfb, 0x17, 0xcf, 0x36, 0x5c, 0x3c, 0x14, 0xf7, 0x35, 0xfc, 0x36, 0x83, 0xbe, 0x64, 0xbc, 0x40, + 0x0e, 0x1b, 0x56, 0x2c, 0x5b, 0xad, 0x34, 0xd7, 0xff, 0xb5, 0x12, 0x3e, 0x63, 0x61, 0x82, 0x74, 0x88, 0xb4, 0x36, + 0x96, 0xb3, 0x82, 0x45, 0x44, 0x45, 0x60, 0xf4, 0x1f, 0x2b, 0xe5, 0x1e, 0x53, 0x11, 0x49, 0x8a, 0x43, 0x8b, 0x77, + 0xc3, 0x8a, 0xe6, 0xa0, 0x50, 0x3c, 0xc9, 0xbf, 0xab, 0xd2, 0x81, 0x42, 0x12, 0x50, 0xb1, 0x54, 0x12, 0xb2, 0x34, + 0xfc, 0x86, 0x7a, 0x8e, 0xde, 0x60, 0xf1, 0x89, 0x20, 0x3e, 0x4d, 0x0a, 0x4e, 0x82, 0xfb, 0x3d, 0xa5, 0x37, 0xc6, + 0x75, 0x49, 0x33, 0xb6, 0xad, 0x3f, 0x08, 0xcd, 0x14, 0x8d, 0x43, 0x79, 0xb8, 0x51, 0x0c, 0x34, 0xbc, 0xb7, 0xdb, + 0x74, 0x68, 0x78, 0x16, 0xea, 0x65, 0x8c, 0xa2, 0x0f, 0x70, 0x34, 0xdd, 0xd5, 0x58, 0x3e, 0x04, 0xd0, 0x26, 0x0a, + 0x51, 0x29, 0x08, 0x3d, 0x8c, 0x2a, 0xfa, 0x00, 0xf0, 0x41, 0x3d, 0xcb, 0x63, 0xf6, 0xb8, 0x81, 0x71, 0xb9, 0x51, + 0xc8, 0x3d, 0x31, 0x78, 0x4d, 0xc7, 0x0a, 0x8b, 0xbb, 0x07, 0x11, 0x65, 0xa2, 0xda, 0x01, 0x00, 0x08, 0x63, 0x09, + 0x82, 0x10, 0x24, 0x87, 0xb8, 0xa6, 0xd7, 0x85, 0x34, 0x07, 0xd4, 0xd8, 0x05, 0x4e, 0x86, 0x2f, 0xc4, 0x43, 0x28, + 0x46, 0xcd, 0x82, 0x38, 0x44, 0xec, 0x24, 0x8f, 0xd6, 0x1d, 0xdd, 0x8c, 0x3e, 0xfb, 0x09, 0x15, 0x2f, 0xf5, 0xf2, + 0x46, 0xd6, 0xad, 0x13, 0x9e, 0x2a, 0x8f, 0x34, 0x78, 0x7e, 0x2d, 0x9d, 0xd2, 0x80, 0x6f, 0x48, 0xf2, 0xbf, 0xa1, + 0xa3, 0x3e, 0x7a, 0xed, 0x9b, 0x5c, 0x2a, 0x0c, 0x69, 0x1f, 0xb7, 0x15, 0xc3, 0xf4, 0xd5, 0xdc, 0x18, 0x09, 0xa8, + 0x7f, 0x4b, 0x9e, 0x06, 0x4b, 0xc9, 0x2b, 0x82, 0x6c, 0xc5, 0x88, 0x43, 0x05, 0xe5, 0x4a, 0xdb, 0x56, 0x9e, 0x82, + 0xc0, 0x46, 0x5b, 0x49, 0xf5, 0x69, 0x93, 0x68, 0x0c, 0x48, 0x79, 0x11, 0x83, 0x88, 0x79, 0xc7, 0xfe, 0xd2, 0xb3, + 0xca, 0xf3, 0x93, 0x29, 0x3a, 0xe2, 0x50, 0xdf, 0x33, 0xb6, 0x0b, 0x62, 0xc3, 0x1a, 0x3f, 0xcb, 0x09, 0x51, 0x8b, + 0x3a, 0xb3, 0x61, 0x20, 0x05, 0x02, 0xd3, 0x6c, 0xc1, 0xac, 0x97, 0x20, 0x18, 0x89, 0xb5, 0x9a, 0xea, 0xaa, 0x05, + 0xdf, 0xc1, 0xe5, 0x9e, 0x8a, 0x75, 0x2b, 0x98, 0xf2, 0xa4, 0x9b, 0x92, 0xfd, 0x92, 0x18, 0xfd, 0x84, 0x50, 0xe3, + 0x25, 0x77, 0x0b, 0x56, 0x50, 0xcd, 0x17, 0xc9, 0x20, 0x45, 0x3f, 0x15, 0x1c, 0x19, 0x08, 0xaa, 0x81, 0x2e, 0xdb, + 0x96, 0x65, 0x81, 0x69, 0xe2, 0x5c, 0x15, 0x2c, 0xf5, 0x91, 0x94, 0xc3, 0x8f, 0xa4, 0xe3, 0x9b, 0x9f, 0x9c, 0x00, + 0x2a, 0x88, 0x8f, 0x26, 0x11, 0x7c, 0x20, 0xf3, 0xcd, 0x0e, 0xe0, 0x27, 0x41, 0x35, 0x26, 0x1e, 0x0d, 0xa0, 0xd1, + 0x88, 0xfb, 0xab, 0x08, 0x7a, 0xef, 0xa6, 0x75, 0x28, 0xaa, 0xde, 0x93, 0x30, 0xb8, 0x06, 0x00, 0xa0, 0xa0, 0x6a, + 0xbb, 0x77, 0x0d, 0x62, 0xa0, 0x14, 0xe4, 0xab, 0x6f, 0xae, 0xf6, 0x26, 0x6a, 0x6d, 0xf8, 0x61, 0xa9, 0x5e, 0x78, + 0x99, 0x8d, 0xbb, 0x99, 0x1a, 0x8f, 0xb5, 0x34, 0x32, 0x2c, 0xf7, 0x52, 0xfa, 0x40, 0x30, 0xf2, 0xda, 0x92, 0x85, + 0x14, 0x69, 0xeb, 0x78, 0x81, 0x2a, 0x84, 0x1b, 0x5c, 0x58, 0x08, 0x28, 0x9d, 0xc0, 0xf2, 0x97, 0x7c, 0x1d, 0xcb, + 0xa1, 0x9e, 0xd2, 0x93, 0xd6, 0x32, 0xea, 0x06, 0x01, 0xac, 0xa3, 0x01, 0xf3, 0x22, 0x7c, 0x75, 0x37, 0xea, 0x3f, + 0xb2, 0x50, 0xff, 0x71, 0xc8, 0xad, 0x65, 0x20, 0x6c, 0x25, 0x7e, 0x2e, 0x0d, 0x14, 0xa5, 0xca, 0x7a, 0x32, 0x0b, + 0xd1, 0x1a, 0x57, 0x87, 0x6a, 0x6d, 0x8b, 0xf2, 0x0e, 0xca, 0x62, 0xaf, 0x14, 0xb2, 0x67, 0x32, 0xb5, 0x9f, 0xec, + 0x9a, 0x0d, 0x3a, 0x6c, 0x7a, 0x9b, 0x71, 0xd0, 0x26, 0x85, 0x8f, 0x3e, 0x7e, 0x7f, 0x6f, 0xf5, 0xc9, 0x6c, 0x73, + 0x05, 0x5b, 0x6e, 0xa5, 0x38, 0x6a, 0x6b, 0x01, 0xd7, 0x9d, 0x4c, 0xb1, 0x7d, 0xbd, 0x27, 0x5e, 0xa7, 0x42, 0x6b, + 0x8b, 0x51, 0xb1, 0x43, 0xec, 0x6d, 0xbb, 0x4d, 0x25, 0xb8, 0x55, 0x2d, 0xd2, 0x25, 0xe1, 0xdc, 0x1a, 0x15, 0x77, + 0x90, 0x91, 0x47, 0x54, 0x00, 0x38, 0xee, 0xf6, 0xec, 0xc7, 0x2b, 0x89, 0x27, 0xa2, 0x6b, 0x40, 0xcc, 0x90, 0x10, + 0x0a, 0xbc, 0x47, 0xcc, 0x11, 0x2c, 0x02, 0x41, 0xf4, 0x8a, 0x20, 0x65, 0x40, 0xe6, 0x38, 0xc6, 0x90, 0xff, 0x02, + 0x06, 0xf1, 0xca, 0x18, 0x32, 0xdf, 0x1b, 0x67, 0x2e, 0x44, 0x0c, 0x50, 0x26, 0xd1, 0x66, 0xaf, 0x12, 0xc4, 0x66, + 0xe8, 0xc7, 0x4a, 0x95, 0x27, 0x6d, 0xd3, 0x37, 0xd2, 0xb8, 0xb5, 0x53, 0x4a, 0x26, 0x3e, 0x91, 0xaf, 0x20, 0xb1, + 0x96, 0x7b, 0x0f, 0x73, 0x93, 0x88, 0x3a, 0x89, 0xef, 0x1f, 0xa8, 0xb4, 0xaa, 0x77, 0xf5, 0x75, 0xdd, 0x23, 0x66, + 0x6d, 0x5e, 0x60, 0x9d, 0x96, 0xa0, 0x47, 0x3f, 0xe6, 0xb0, 0xd2, 0x70, 0xff, 0x43, 0x83, 0xc5, 0x5b, 0xdd, 0xb3, + 0x6c, 0x80, 0x34, 0x40, 0x6b, 0xd3, 0x61, 0x6d, 0x84, 0xf4, 0xf4, 0x95, 0xf6, 0xc0, 0xaf, 0xd6, 0xbf, 0x02, 0xb0, + 0x7c, 0xe3, 0x06, 0x50, 0xb2, 0x9b, 0xd4, 0x0d, 0x8b, 0x78, 0x09, 0x29, 0x47, 0xee, 0x0c, 0xdf, 0x73, 0x8d, 0xc9, + 0x40, 0x11, 0x0e, 0x9b, 0x08, 0x41, 0x83, 0xab, 0xf1, 0x3a, 0xbd, 0x97, 0xd6, 0x8c, 0xcc, 0x56, 0x6b, 0x90, 0xdc, + 0xa3, 0x5e, 0x2e, 0x8c, 0x4c, 0xa5, 0xf1, 0xb5, 0xd5, 0x9d, 0x78, 0x82, 0xe0, 0x72, 0x49, 0x49, 0xb1, 0xd0, 0x70, + 0xbb, 0xd2, 0x02, 0xda, 0x17, 0x88, 0xff, 0x0c, 0xfe, 0x43, 0xf7, 0xda, 0xda, 0xc2, 0x85, 0xce, 0x09, 0x57, 0x1f, + 0xcb, 0x39, 0x01, 0xc6, 0xba, 0xc5, 0x42, 0xad, 0x50, 0x9b, 0x13, 0x0f, 0xc2, 0x12, 0xb9, 0xa7, 0x3f, 0xf0, 0xbf, + 0xb9, 0x99, 0x94, 0xe6, 0xd4, 0x3e, 0x1c, 0x92, 0xe2, 0x3c, 0x72, 0xc5, 0xde, 0x16, 0xb2, 0x8f, 0xc2, 0x9f, 0xbb, + 0xb5, 0xa6, 0xbb, 0x05, 0x7d, 0xc6, 0x24, 0xe8, 0x22, 0x1b, 0x4e, 0x05, 0xed, 0x13, 0x3e, 0x31, 0x24, 0xb5, 0x92, + 0x1e, 0x50, 0x8a, 0x18, 0x1a, 0xd7, 0x14, 0x6b, 0xfc, 0x95, 0x74, 0x5f, 0xd3, 0x6c, 0x82, 0x53, 0x37, 0xae, 0xc5, + 0x2c, 0xf0, 0x15, 0xe2, 0xd8, 0xf2, 0x71, 0x6e, 0x4d, 0xaa, 0x32, 0x8a, 0x53, 0xa3, 0x96, 0x10, 0xf0, 0x1e, 0xbd, + 0x67, 0xd6, 0x97, 0xfe, 0x0b, 0x62, 0x8c, 0x40, 0x4f, 0x6b, 0x04, 0x3e, 0x27, 0x02, 0xef, 0xa1, 0xe0, 0xa6, 0x5c, + 0xcb, 0x7b, 0xd2, 0x89, 0x26, 0x53, 0x1c, 0x4f, 0xe2, 0x99, 0x10, 0xb9, 0x37, 0x5e, 0xd6, 0x66, 0x24, 0xc8, 0x42, + 0xf4, 0x2d, 0x62, 0x92, 0xc8, 0xe7, 0x30, 0x45, 0x8d, 0x46, 0xb7, 0x3c, 0xe3, 0xc6, 0xaa, 0x62, 0xba, 0x99, 0xe1, + 0x2e, 0x31, 0xe2, 0x7d, 0x8d, 0xa3, 0xa2, 0x23, 0x81, 0xf2, 0xfe, 0x2e, 0xd1, 0x7c, 0x0f, 0x25, 0x6d, 0xfa, 0x66, + 0x97, 0xd5, 0x1b, 0xb1, 0x38, 0x24, 0xd7, 0xec, 0xe1, 0xbc, 0xfb, 0xbe, 0xdb, 0x08, 0xf6, 0x7b, 0xb7, 0xcd, 0xd0, + 0xc7, 0xcd, 0xeb, 0x56, 0x82, 0x34, 0xe8, 0x45, 0xd8, 0x55, 0xf2, 0x35, 0xba, 0x64, 0x5b, 0x8d, 0x75, 0x2b, 0xa3, + 0xed, 0x56, 0x61, 0x09, 0xf2, 0x39, 0x37, 0x4e, 0x03, 0x6b, 0x8e, 0x9d, 0xc4, 0x66, 0x36, 0x0d, 0xf8, 0xc0, 0x60, + 0x2a, 0x66, 0x21, 0xeb, 0xbb, 0xbb, 0xb6, 0x53, 0x4c, 0x37, 0x71, 0x79, 0x4b, 0xfe, 0xf8, 0x24, 0xd9, 0xc6, 0x1f, + 0x59, 0x2e, 0x97, 0x3e, 0xf1, 0xc6, 0xf6, 0x3f, 0xe0, 0x8d, 0xd2, 0x6c, 0xaf, 0xd8, 0x23, 0x4a, 0x27, 0x35, 0xf6, + 0x58, 0x9f, 0xd3, 0x10, 0x54, 0x51, 0x39, 0x1d, 0xe7, 0x1d, 0x00, 0x21, 0x2c, 0xc3, 0x5d, 0xa4, 0xc3, 0xf8, 0xd8, + 0x16, 0x8f, 0x16, 0x49, 0x16, 0x36, 0x64, 0x37, 0xd3, 0xaa, 0x8c, 0xe7, 0xa3, 0x03, 0xb5, 0x4b, 0xbe, 0x5e, 0x84, + 0xd8, 0x12, 0x87, 0x24, 0x96, 0x87, 0x99, 0xde, 0xba, 0xc2, 0x1e, 0x13, 0xdb, 0x90, 0x0a, 0xa6, 0xbb, 0xd5, 0xab, + 0x50, 0xa9, 0x9f, 0xff, 0x5e, 0x38, 0xad, 0xb1, 0x18, 0x21, 0x49, 0xd4, 0xbc, 0x18, 0x64, 0x0f, 0xa4, 0xc0, 0xb8, + 0x4b, 0x1a, 0xaa, 0xe1, 0xea, 0x5e, 0x8d, 0x25, 0xb1, 0x16, 0x9a, 0xdd, 0x76, 0x89, 0x2f, 0x01, 0x23, 0xda, 0xc6, + 0x58, 0x58, 0x61, 0xe1, 0x36, 0x10, 0xcb, 0x1a, 0x49, 0x01, 0x2a, 0x2b, 0x34, 0x28, 0x96, 0x12, 0xaa, 0x56, 0x61, + 0x0e, 0x70, 0x44, 0x99, 0xb4, 0x1b, 0x9f, 0xe5, 0x46, 0x49, 0x8e, 0x41, 0x4e, 0x4b, 0x75, 0xc3, 0xd1, 0x65, 0x06, + 0xb2, 0x1e, 0xb4, 0x1e, 0x0b, 0x85, 0x05, 0xb9, 0x17, 0x08, 0x7d, 0xba, 0x91, 0xcb, 0x18, 0x28, 0x62, 0x01, 0x64, + 0x40, 0x74, 0x2d, 0x85, 0xae, 0xa5, 0x76, 0xd7, 0x28, 0x1f, 0x3f, 0x7c, 0x05, 0xbc, 0xf4, 0x15, 0xf1, 0xc3, 0x57, + 0xd8, 0xc9, 0x06, 0x88, 0x8e, 0xd2, 0x24, 0x98, 0xa2, 0xe5, 0xaa, 0x91, 0x5f, 0xc6, 0x8d, 0x76, 0xdf, 0xa2, 0x61, + 0xf0, 0x65, 0x98, 0xae, 0xd0, 0x73, 0xb6, 0x94, 0x0c, 0xf3, 0x0b, 0x32, 0xb6, 0x2f, 0xc4, 0x67, 0x44, 0x85, 0xf6, + 0x9c, 0xac, 0x9b, 0x0c, 0x34, 0x5e, 0xc9, 0xc9, 0x55, 0xe5, 0x6a, 0x0e, 0x16, 0xba, 0x10, 0x93, 0xdb, 0xcc, 0x3d, + 0x54, 0xfe, 0x35, 0xb6, 0x17, 0x91, 0x76, 0xe2, 0x5e, 0x43, 0x7c, 0xe5, 0xbb, 0xed, 0xfb, 0x7e, 0x54, 0x8c, 0x69, + 0x4b, 0x44, 0xed, 0xf0, 0xd2, 0x1a, 0x38, 0x94, 0xfd, 0xb4, 0x5a, 0xbe, 0xd4, 0x8d, 0xf5, 0x43, 0xd1, 0x7f, 0x25, + 0xec, 0xa8, 0x83, 0x2b, 0x51, 0xb4, 0xdd, 0x16, 0x21, 0x3a, 0x13, 0xff, 0x2f, 0x77, 0xe6, 0x48, 0x76, 0x46, 0xa0, + 0xc9, 0x1a, 0xdc, 0xee, 0x80, 0x47, 0x14, 0xad, 0xc1, 0xed, 0x6e, 0xf8, 0x2a, 0x68, 0xa5, 0x77, 0x76, 0xd0, 0x22, + 0x13, 0xa2, 0xa7, 0x26, 0xc1, 0xea, 0xe6, 0xf1, 0xba, 0x44, 0x26, 0xc8, 0x2a, 0x32, 0xd7, 0x2a, 0x04, 0xc2, 0x5a, + 0x5f, 0x0b, 0x46, 0x48, 0xb5, 0x14, 0xe3, 0x2c, 0x78, 0xe5, 0xd9, 0x46, 0x83, 0xba, 0x73, 0x0c, 0x62, 0x95, 0xb4, + 0xd6, 0x03, 0x0e, 0xa2, 0xd2, 0x80, 0xa2, 0x1d, 0x10, 0xba, 0x19, 0x94, 0x45, 0xf9, 0xaa, 0x54, 0xcf, 0x98, 0x8c, + 0xc7, 0x4e, 0x28, 0x0d, 0x1f, 0x30, 0x61, 0x06, 0x83, 0x4c, 0xbe, 0x89, 0x34, 0xf9, 0x0c, 0x0b, 0x52, 0x61, 0x74, + 0x29, 0x24, 0xc5, 0xdc, 0xeb, 0xe6, 0x12, 0x5d, 0xeb, 0x90, 0x7b, 0xf6, 0x0d, 0x9e, 0x5f, 0x25, 0x25, 0x00, 0x08, + 0x01, 0x60, 0x10, 0x0f, 0x87, 0x04, 0xd3, 0x55, 0xac, 0x7d, 0x15, 0x0d, 0x87, 0xdf, 0xfd, 0xa4, 0xaa, 0x8b, 0x45, + 0x93, 0x28, 0x1b, 0xa6, 0xa2, 0x11, 0xdb, 0x67, 0x52, 0xf9, 0x89, 0xea, 0x92, 0xb6, 0xc7, 0x8e, 0x11, 0x3f, 0x88, + 0xd6, 0x03, 0x88, 0x15, 0x5f, 0x50, 0xbc, 0xf4, 0xbb, 0x72, 0x0c, 0x6e, 0xa9, 0xdf, 0x31, 0x0b, 0xf6, 0x48, 0x58, + 0x65, 0x91, 0x57, 0xbf, 0xde, 0x4f, 0x85, 0x3a, 0x93, 0x0d, 0xe3, 0x82, 0x76, 0x0a, 0x5b, 0xe3, 0x14, 0x84, 0x29, + 0x27, 0x77, 0x6b, 0x5c, 0xaf, 0x15, 0x1b, 0x51, 0xac, 0x23, 0xfb, 0xa7, 0x54, 0xda, 0x5b, 0x6a, 0x04, 0xf3, 0xd4, + 0x8a, 0xe4, 0x25, 0x6e, 0xc5, 0x82, 0x58, 0xf9, 0xa2, 0x9a, 0xa6, 0x6b, 0x27, 0x71, 0xba, 0xbc, 0xd4, 0xd0, 0x29, + 0xdd, 0x6b, 0xce, 0x5e, 0x72, 0xdc, 0x37, 0x7e, 0x9e, 0x58, 0x9f, 0x6c, 0xee, 0x17, 0x3f, 0xb7, 0xf6, 0x8b, 0x9f, + 0x27, 0xc1, 0x66, 0x51, 0x6b, 0x9f, 0xb8, 0xe3, 0x9f, 0xfa, 0x2d, 0x47, 0xc9, 0x51, 0xc3, 0xc8, 0x9c, 0xaf, 0x14, + 0x4b, 0x83, 0x19, 0x9f, 0x38, 0x74, 0xce, 0xab, 0xab, 0x50, 0x5c, 0xba, 0x33, 0x0a, 0x09, 0xff, 0xae, 0x79, 0x92, + 0x9c, 0x27, 0x17, 0x5a, 0xc8, 0x3b, 0x50, 0xa6, 0xee, 0xe1, 0x82, 0x2b, 0x36, 0xce, 0x00, 0x42, 0xe3, 0xe5, 0x3f, + 0x6d, 0xc2, 0x52, 0xc7, 0x4b, 0x71, 0xf8, 0xc8, 0xae, 0x3f, 0x2c, 0xb4, 0x54, 0x57, 0x57, 0x42, 0x6e, 0xc8, 0x4a, + 0x00, 0xff, 0x57, 0x47, 0xf3, 0xb8, 0xa4, 0xc9, 0x3c, 0x58, 0xae, 0xb4, 0xe9, 0xa0, 0x10, 0x52, 0x5d, 0x02, 0x2b, + 0x66, 0x45, 0x3b, 0xe8, 0x7f, 0x27, 0xec, 0x4b, 0x22, 0x69, 0xe4, 0x4f, 0x9a, 0x02, 0x7d, 0xda, 0x7e, 0xd6, 0x66, + 0x0b, 0x89, 0x14, 0x63, 0xd0, 0x9e, 0x02, 0x88, 0xd5, 0x84, 0xaf, 0x2b, 0x85, 0x53, 0x4f, 0x73, 0x39, 0x9a, 0x3b, + 0x1d, 0x61, 0x99, 0x52, 0x73, 0xb3, 0x90, 0x9a, 0xd9, 0xe2, 0x39, 0xaa, 0x74, 0xf1, 0x4a, 0xaf, 0xb1, 0x5a, 0xbb, + 0xde, 0x1d, 0xa0, 0x34, 0x8e, 0x68, 0xc0, 0x62, 0xeb, 0xf0, 0x0e, 0x33, 0x6b, 0x1b, 0xc4, 0x63, 0x99, 0xe5, 0xc0, + 0x51, 0x13, 0xbc, 0xc5, 0x37, 0xae, 0xb7, 0xee, 0xbf, 0xa4, 0x44, 0xf7, 0x5a, 0x3f, 0x6c, 0x43, 0x83, 0xf8, 0xdc, + 0xb6, 0x3c, 0x30, 0x31, 0x3a, 0xdd, 0x90, 0x05, 0xa1, 0x61, 0xa4, 0x9c, 0x73, 0x8d, 0x17, 0xed, 0x16, 0xf8, 0x7a, + 0xdf, 0x71, 0xcf, 0x95, 0xa0, 0xdb, 0xcc, 0xb7, 0x7c, 0x9b, 0x9e, 0xe6, 0x77, 0xf9, 0x36, 0xd5, 0x24, 0xe1, 0xdd, + 0x96, 0xf7, 0x7d, 0x47, 0x58, 0xd1, 0xd6, 0xf6, 0x22, 0xff, 0x0b, 0xcd, 0xb5, 0x11, 0x3d, 0x05, 0x98, 0x15, 0x8d, + 0xf9, 0x08, 0x6c, 0xfd, 0x27, 0x7d, 0x7e, 0x81, 0x7c, 0x85, 0x7e, 0x12, 0xab, 0x40, 0xaa, 0x95, 0x74, 0x20, 0xd8, + 0xfd, 0x3b, 0x09, 0xc7, 0x69, 0x3e, 0x88, 0xd2, 0x0f, 0xd8, 0xa2, 0xc9, 0x7d, 0xb1, 0x18, 0x16, 0x00, 0x64, 0x49, + 0x6b, 0x4c, 0x2f, 0xfe, 0x4e, 0xac, 0x6e, 0xfc, 0x9d, 0x08, 0xca, 0x6d, 0x6a, 0x60, 0xcb, 0x57, 0xba, 0x8a, 0xe0, + 0xa7, 0x95, 0xa2, 0x1d, 0x49, 0xb9, 0xbd, 0x95, 0x75, 0x92, 0x96, 0x68, 0x92, 0x96, 0x94, 0xee, 0x7a, 0x55, 0xae, + 0xfb, 0xe5, 0x8e, 0xce, 0x6e, 0x92, 0xb9, 0x2f, 0x16, 0x99, 0xfb, 0x92, 0x04, 0xdf, 0xfd, 0xca, 0xa2, 0x78, 0x87, + 0xfe, 0x21, 0x79, 0xc6, 0x88, 0x5e, 0xbf, 0xaf, 0xd0, 0xa1, 0xa1, 0x82, 0xff, 0x9b, 0xd3, 0x0e, 0x86, 0x7b, 0x29, + 0xff, 0x23, 0x37, 0x9e, 0x97, 0x55, 0x3e, 0x95, 0x95, 0x96, 0xf2, 0x8c, 0x13, 0x65, 0xa2, 0x01, 0x9b, 0xf6, 0xf0, + 0x83, 0xfa, 0x31, 0xb2, 0xe5, 0xd7, 0x24, 0x1b, 0x06, 0xa0, 0xde, 0xca, 0x6f, 0x82, 0x7c, 0x15, 0x2a, 0x37, 0xe7, + 0xcd, 0x3c, 0x06, 0xf5, 0x25, 0xe5, 0x04, 0x26, 0xb7, 0x80, 0xa5, 0x75, 0x47, 0x63, 0x05, 0xee, 0xe6, 0x88, 0xc6, + 0xd8, 0x5c, 0x7b, 0x0e, 0x54, 0x3e, 0xd6, 0x86, 0x36, 0xa3, 0x29, 0xaf, 0x26, 0xf9, 0x10, 0x1d, 0x5f, 0xe0, 0x1b, + 0x75, 0x9c, 0x0a, 0x64, 0x5b, 0xd7, 0x21, 0xfb, 0x05, 0x9e, 0x41, 0xb7, 0x73, 0xbc, 0xde, 0x12, 0x0f, 0x06, 0x99, + 0xa6, 0x41, 0xcb, 0xe4, 0xeb, 0x67, 0x68, 0xa0, 0x76, 0xbe, 0x60, 0x09, 0xb4, 0x1c, 0x88, 0x5e, 0x3b, 0xa3, 0x84, + 0xa7, 0x43, 0x87, 0x41, 0x72, 0xa0, 0x8f, 0xad, 0xd3, 0x29, 0x6b, 0x9a, 0x44, 0x27, 0xbf, 0xce, 0x1c, 0x66, 0x1a, + 0x01, 0xb2, 0xca, 0xf2, 0x22, 0x19, 0x23, 0x8e, 0xfe, 0x0c, 0x9f, 0xc8, 0xfa, 0xac, 0xa3, 0x82, 0xde, 0x52, 0x81, + 0xde, 0xb7, 0x92, 0xed, 0x69, 0x90, 0x0a, 0xc7, 0x25, 0x7d, 0x0b, 0x82, 0xad, 0x5d, 0xce, 0xa8, 0x90, 0xa0, 0x40, + 0xfe, 0xd3, 0xa1, 0xb0, 0x91, 0xcd, 0xe7, 0xaa, 0x9a, 0xc7, 0xed, 0xda, 0x47, 0x1c, 0x3f, 0x30, 0x1e, 0x24, 0xbf, + 0x03, 0x53, 0x58, 0x2a, 0x32, 0x4b, 0x9f, 0x5b, 0x36, 0xb3, 0x51, 0x24, 0x2b, 0x0d, 0xe6, 0xf4, 0xe4, 0x99, 0x3d, + 0xa8, 0x69, 0x65, 0x3e, 0x84, 0x4a, 0x10, 0xf0, 0xe8, 0x2d, 0x13, 0xd1, 0x81, 0xd0, 0x95, 0x72, 0x53, 0x9d, 0x41, + 0xb7, 0x96, 0x6a, 0x0c, 0x41, 0x62, 0x83, 0xb1, 0x5a, 0x21, 0x1a, 0x4a, 0x04, 0x13, 0x9e, 0x87, 0xc0, 0x43, 0xb3, + 0x0d, 0x1e, 0x9a, 0x13, 0x0f, 0xf5, 0x2d, 0x90, 0xdf, 0xc1, 0x33, 0x39, 0x41, 0x83, 0x64, 0x4b, 0x62, 0x80, 0x72, + 0x76, 0x25, 0x0e, 0xd9, 0x33, 0xaa, 0x0f, 0xef, 0x89, 0x49, 0xcf, 0x6b, 0xdd, 0x72, 0xa9, 0x1e, 0x0f, 0xb0, 0x03, + 0x3d, 0x82, 0x44, 0x81, 0x95, 0xb2, 0xfb, 0x24, 0xca, 0xaf, 0xd6, 0x2d, 0x7c, 0x35, 0xac, 0x50, 0xc1, 0xc4, 0x8d, + 0xbc, 0x65, 0xe2, 0x46, 0x20, 0x9f, 0xaf, 0x90, 0xcf, 0xea, 0xfe, 0x73, 0x7b, 0x3a, 0x6a, 0x5e, 0xd2, 0xdb, 0x0f, + 0x18, 0xa2, 0x94, 0x5f, 0xa1, 0x6f, 0x28, 0x4b, 0x34, 0x61, 0x71, 0xe9, 0xa4, 0x9f, 0x35, 0x6f, 0x63, 0x31, 0x21, + 0x6a, 0x06, 0x66, 0x91, 0xbb, 0xb4, 0x46, 0x61, 0x1f, 0x2a, 0x97, 0x07, 0x0e, 0xe5, 0x36, 0xa1, 0x71, 0x5e, 0x75, + 0xcb, 0x70, 0x8d, 0xf5, 0x7c, 0xdf, 0xc7, 0xf3, 0xaf, 0x39, 0x2f, 0x16, 0x17, 0x1c, 0x7d, 0xac, 0x73, 0x3c, 0x6e, + 0x6c, 0xa6, 0xc1, 0x38, 0xc8, 0xf7, 0x72, 0x12, 0x5d, 0x56, 0xec, 0xbb, 0x51, 0x31, 0x56, 0xb4, 0x4f, 0x69, 0x59, + 0x6b, 0xc4, 0x72, 0xe1, 0x77, 0x1e, 0xdd, 0xe4, 0x5d, 0x8a, 0x95, 0x60, 0x20, 0x2d, 0xf7, 0x16, 0x58, 0x61, 0x9f, + 0x87, 0xbd, 0x2c, 0xfb, 0xeb, 0xa6, 0x1b, 0x4c, 0xc2, 0x6d, 0xbf, 0xfc, 0xee, 0xa1, 0x6e, 0xf3, 0xd6, 0xbd, 0x7b, + 0xa8, 0xb5, 0xcd, 0x42, 0x72, 0x24, 0x62, 0xb2, 0x1d, 0x7d, 0x7e, 0x3a, 0x03, 0x92, 0xb6, 0xc2, 0xee, 0x3d, 0x4e, + 0x80, 0xfa, 0x3f, 0x56, 0x1e, 0x8a, 0x3e, 0x6e, 0xe4, 0x5e, 0xa4, 0xb9, 0x22, 0xe4, 0xa6, 0x07, 0x8f, 0x93, 0x8d, + 0x2e, 0x3c, 0x4e, 0xac, 0x43, 0xaf, 0xa8, 0x35, 0x8d, 0x33, 0x3e, 0x54, 0xf4, 0xd3, 0x13, 0x48, 0x48, 0x72, 0xdc, + 0xdb, 0x24, 0xcc, 0xaa, 0xcf, 0x01, 0xca, 0x5f, 0x0c, 0x34, 0xcc, 0x2a, 0xcf, 0x80, 0x14, 0xcd, 0xe6, 0x15, 0x2b, + 0xa9, 0xf7, 0xcb, 0x51, 0x9e, 0x55, 0xcd, 0x51, 0x34, 0x4d, 0xd2, 0x05, 0x88, 0xcd, 0xcd, 0x69, 0x9e, 0xe5, 0xe5, + 0x0c, 0x16, 0x02, 0x2b, 0x17, 0xa0, 0x21, 0x4d, 0x9b, 0xf3, 0x84, 0xbd, 0xe0, 0xe9, 0x37, 0x5e, 0x25, 0x71, 0xc4, + 0xde, 0xe7, 0x03, 0x68, 0x93, 0xbd, 0xbd, 0x59, 0x8c, 0x79, 0xc6, 0x3e, 0x0e, 0xe6, 0x59, 0x35, 0x67, 0x65, 0x94, + 0x95, 0x4d, 0x10, 0x38, 0x93, 0x51, 0xb7, 0xd9, 0x9c, 0x15, 0xc9, 0x34, 0x2a, 0x16, 0xcd, 0x38, 0x4f, 0x01, 0xcb, + 0xfe, 0xab, 0x75, 0x18, 0x3d, 0x1c, 0x1d, 0x75, 0xab, 0x02, 0xca, 0x24, 0x38, 0x31, 0x01, 0xd0, 0xae, 0x9d, 0xc3, + 0xe3, 0xd6, 0xb4, 0xdc, 0x15, 0x1b, 0x7e, 0x51, 0x56, 0xad, 0x3e, 0xb3, 0x5f, 0x73, 0xec, 0xa5, 0x3f, 0xa8, 0x32, + 0xd9, 0x49, 0xe0, 0x08, 0x45, 0x09, 0x35, 0xcc, 0xf2, 0x24, 0xab, 0x78, 0xd1, 0x1d, 0xe4, 0x05, 0x4c, 0x4c, 0xb3, + 0x88, 0x86, 0xc9, 0xbc, 0x0c, 0x8e, 0x66, 0x37, 0xdd, 0x7a, 0x0b, 0x22, 0x3f, 0xc8, 0xf2, 0x8c, 0x77, 0x51, 0xde, + 0x18, 0x17, 0xf9, 0x3c, 0x1b, 0xca, 0x6e, 0xcc, 0x41, 0x1e, 0xae, 0xba, 0x33, 0xd0, 0xfd, 0x92, 0x6c, 0x1c, 0x9c, + 0xc2, 0xc7, 0x34, 0xea, 0x6b, 0x9e, 0x8c, 0x27, 0x55, 0x70, 0xdc, 0x6a, 0x89, 0xf7, 0x12, 0xa8, 0x6b, 0xd0, 0xee, + 0xf8, 0x9d, 0x63, 0x28, 0x01, 0x12, 0x3c, 0xb4, 0xdb, 0x44, 0x58, 0xe0, 0x47, 0x6d, 0xbf, 0xf5, 0xf0, 0xf0, 0x01, + 0x66, 0xa0, 0x8f, 0x43, 0x93, 0x06, 0x84, 0xce, 0xee, 0x01, 0xb0, 0x78, 0x5e, 0xe0, 0x29, 0xfa, 0x2e, 0x8c, 0x1b, + 0x08, 0x50, 0xb3, 0xa0, 0x4a, 0x9b, 0xd0, 0xca, 0x0a, 0xc7, 0x13, 0x08, 0xb7, 0x55, 0x39, 0x2c, 0xf9, 0xb6, 0xb4, + 0xba, 0x48, 0x31, 0x69, 0x82, 0x62, 0x3c, 0x88, 0xdc, 0x76, 0xe7, 0x01, 0x53, 0xff, 0xf9, 0x1d, 0xcf, 0x02, 0x5b, + 0x73, 0x08, 0x6b, 0x83, 0xe0, 0xd7, 0x2e, 0x45, 0xb5, 0x13, 0x50, 0x7e, 0x0b, 0x55, 0x2b, 0xbd, 0x2c, 0x37, 0xc6, + 0xfd, 0x1f, 0x55, 0x1a, 0x89, 0xba, 0x5e, 0x96, 0x17, 0x48, 0xa3, 0x37, 0x2b, 0xfb, 0xaf, 0xce, 0x69, 0xf4, 0xe0, + 0xe8, 0x58, 0xc1, 0x7d, 0x34, 0x1a, 0xd5, 0x80, 0xae, 0xa0, 0xdb, 0x6e, 0xcd, 0x6e, 0x76, 0x3a, 0x2d, 0x05, 0x63, + 0x01, 0xd3, 0x13, 0x78, 0xdd, 0x9c, 0x41, 0x0b, 0x2b, 0xd6, 0x5b, 0xdb, 0xf1, 0x0f, 0xcb, 0x1d, 0x0e, 0x50, 0x05, + 0xdc, 0x98, 0x46, 0x88, 0x1b, 0x84, 0xb4, 0x97, 0xa4, 0xa7, 0xe2, 0x0c, 0xf4, 0x97, 0xd7, 0xc9, 0xb0, 0x9a, 0x40, + 0x73, 0xad, 0xfb, 0x06, 0x93, 0xba, 0x13, 0x31, 0xa5, 0xed, 0x82, 0x4f, 0x6b, 0xf8, 0x35, 0x88, 0xf4, 0x2a, 0x58, + 0xce, 0x72, 0xd9, 0x68, 0xc1, 0x53, 0x8a, 0x72, 0xb3, 0x92, 0x4b, 0x61, 0x63, 0xc8, 0x49, 0x06, 0xd4, 0x3b, 0xa9, + 0xba, 0xf5, 0x37, 0xd3, 0xf0, 0xe6, 0x98, 0x56, 0x42, 0x3f, 0x5e, 0x8a, 0x2f, 0xe4, 0x66, 0xec, 0x13, 0x7c, 0xd9, + 0xc4, 0x4a, 0x58, 0x9d, 0xee, 0xda, 0x82, 0x61, 0xf6, 0x17, 0xde, 0xca, 0x1a, 0x73, 0x81, 0xae, 0xa2, 0x7d, 0x76, + 0x07, 0x14, 0x40, 0x22, 0x88, 0x5d, 0xec, 0xd1, 0x4e, 0x73, 0xe7, 0x10, 0xc6, 0xee, 0x29, 0x40, 0xf8, 0x0f, 0x8e, + 0xe1, 0x75, 0xe5, 0xd3, 0xe7, 0x4b, 0x42, 0x5d, 0x10, 0x22, 0xc6, 0x59, 0x10, 0x73, 0x5c, 0x56, 0x2b, 0x1f, 0x7e, + 0x92, 0x6a, 0xd1, 0x2c, 0xf2, 0xeb, 0x25, 0x88, 0xed, 0xc0, 0x02, 0x17, 0xc1, 0x28, 0xe5, 0x37, 0x5d, 0x2a, 0xd5, + 0x4c, 0x80, 0x02, 0x94, 0xb2, 0x6c, 0x17, 0xd3, 0x9b, 0x43, 0xe1, 0xce, 0x81, 0xd0, 0xcb, 0xaf, 0xd7, 0xd7, 0xb5, + 0x9a, 0xb4, 0x66, 0x3e, 0xaf, 0x76, 0x5a, 0x65, 0x77, 0x0a, 0xcb, 0x41, 0x76, 0xe4, 0x08, 0x31, 0x62, 0x13, 0xf2, + 0x56, 0xfb, 0x3e, 0xbf, 0x99, 0x45, 0x40, 0x7d, 0x87, 0x4b, 0xeb, 0xb3, 0x0e, 0x7e, 0x67, 0x97, 0x0a, 0xb2, 0x6a, + 0xd2, 0x24, 0x1f, 0x34, 0xb7, 0x93, 0x79, 0x77, 0xa0, 0xfc, 0xc3, 0x16, 0x13, 0xff, 0xf7, 0x41, 0x83, 0xb0, 0x3e, + 0xde, 0xc1, 0x70, 0x50, 0xc9, 0x68, 0xd1, 0xc4, 0xdf, 0x25, 0x9e, 0x79, 0x02, 0xa2, 0x96, 0x4a, 0x88, 0x4c, 0x93, + 0xe1, 0x30, 0xad, 0xf5, 0xe8, 0xdc, 0x6a, 0xac, 0xed, 0x2d, 0x71, 0xfc, 0x41, 0x6b, 0xa7, 0xb5, 0x43, 0x63, 0x91, + 0xcb, 0xe0, 0xe8, 0xe8, 0xc1, 0xe1, 0x43, 0xde, 0x4d, 0x81, 0x3d, 0xd7, 0x86, 0xfa, 0x5d, 0x50, 0xdb, 0x15, 0x77, + 0x64, 0xc5, 0xed, 0x9d, 0x36, 0x54, 0x7c, 0x5f, 0x51, 0x91, 0x94, 0x8f, 0x2a, 0xb1, 0x6e, 0x6a, 0x64, 0xe5, 0x54, + 0x55, 0x7d, 0x5d, 0x44, 0x33, 0x58, 0x78, 0xf8, 0xd3, 0xc5, 0xc5, 0x3f, 0x4a, 0x01, 0x36, 0x13, 0x18, 0x02, 0xcf, + 0x44, 0x01, 0x9d, 0xc8, 0xd3, 0x34, 0x99, 0x95, 0x89, 0x98, 0x0d, 0x89, 0xbb, 0xc7, 0x6b, 0x50, 0xb5, 0x3b, 0x74, + 0x68, 0x75, 0xe8, 0xd8, 0x74, 0xc8, 0xb4, 0x6f, 0xf7, 0xb0, 0xb3, 0x36, 0x56, 0x2a, 0xd5, 0xad, 0x61, 0xd2, 0x17, + 0x10, 0xed, 0x11, 0xe6, 0xca, 0x79, 0x84, 0xb8, 0x4b, 0x73, 0xc0, 0xab, 0x6b, 0xce, 0xb3, 0xbb, 0x3b, 0x71, 0x1e, + 0xe4, 0x59, 0xba, 0x10, 0xaf, 0x4b, 0xbb, 0xc9, 0x68, 0x5e, 0xe5, 0x40, 0x02, 0x41, 0xd4, 0x2b, 0x16, 0x57, 0x25, + 0xcf, 0x80, 0x4b, 0x5c, 0xe5, 0xa3, 0xd1, 0xf2, 0x2e, 0x92, 0xf7, 0x00, 0x50, 0xa0, 0x04, 0xca, 0x94, 0x72, 0x41, + 0xe0, 0x08, 0x11, 0x24, 0x93, 0x11, 0xf5, 0x52, 0x95, 0xb5, 0x4e, 0xaf, 0xfc, 0x38, 0x85, 0x65, 0x59, 0x71, 0x82, + 0xb3, 0x45, 0x6a, 0xe4, 0xe0, 0x05, 0x95, 0x6b, 0xed, 0x88, 0x1f, 0x53, 0x1a, 0x97, 0x91, 0x55, 0x58, 0x55, 0x99, + 0x64, 0x84, 0x1f, 0x04, 0x0e, 0x5a, 0x45, 0x34, 0x7b, 0x34, 0x77, 0x16, 0xec, 0x70, 0x74, 0xb5, 0xaa, 0xce, 0x25, + 0x5d, 0x12, 0x35, 0xc2, 0x5c, 0xd4, 0x73, 0xd3, 0x68, 0xc0, 0xd3, 0xa5, 0x58, 0xa8, 0x0a, 0xb8, 0x72, 0xa9, 0xda, + 0xd3, 0x6c, 0x91, 0x0c, 0x02, 0x51, 0x3f, 0x08, 0x80, 0xf3, 0x0d, 0xbe, 0x26, 0x95, 0x58, 0x32, 0xcd, 0xf2, 0x1a, + 0x0f, 0x15, 0x51, 0x9f, 0x80, 0x95, 0x2d, 0x15, 0x21, 0x6f, 0xd5, 0x08, 0xe8, 0x45, 0x46, 0x0c, 0xba, 0x8a, 0x06, + 0x4d, 0x0c, 0xb1, 0x06, 0xe5, 0xb6, 0x0d, 0x6e, 0x1a, 0xdd, 0x48, 0x14, 0x7b, 0x08, 0xc3, 0xb7, 0x99, 0xec, 0x11, + 0x30, 0x59, 0x59, 0x73, 0x53, 0x7c, 0x01, 0x2c, 0xf5, 0x98, 0x4f, 0x75, 0x62, 0x95, 0xcf, 0x82, 0x5a, 0x02, 0x48, + 0x1a, 0xa0, 0x12, 0x8a, 0xb4, 0x2d, 0xd4, 0xa8, 0x4e, 0x7a, 0xdb, 0x1d, 0x98, 0x08, 0xfa, 0x03, 0x0b, 0x74, 0x93, + 0xd4, 0x6e, 0x62, 0xc5, 0xa1, 0xa7, 0xf0, 0x18, 0x1b, 0x6e, 0x43, 0x1b, 0xf3, 0x12, 0xd9, 0x3d, 0x41, 0x9c, 0x38, + 0xda, 0x8a, 0x06, 0x8b, 0x80, 0x8d, 0xa0, 0xb7, 0xc0, 0x5d, 0x05, 0xb3, 0xc3, 0x36, 0xca, 0x1c, 0xdd, 0xe1, 0xb7, + 0x56, 0x5a, 0xef, 0x56, 0x6b, 0xc7, 0x74, 0x0c, 0xff, 0xac, 0x3e, 0x1b, 0xf9, 0xfc, 0x29, 0xb7, 0xf4, 0xa3, 0xa4, + 0xe1, 0x1f, 0xdf, 0xb6, 0xa4, 0x4e, 0x34, 0xac, 0x8c, 0xaa, 0x46, 0x27, 0x4a, 0x00, 0xac, 0xe2, 0x68, 0x09, 0x2c, + 0x61, 0x74, 0x5c, 0x03, 0x91, 0xd2, 0x72, 0xf1, 0x9f, 0xd8, 0x15, 0x0d, 0x2b, 0x17, 0x2b, 0xde, 0xef, 0xf8, 0xc7, + 0xc7, 0x1e, 0x6b, 0xb1, 0x0e, 0xfc, 0x18, 0x9d, 0x6c, 0x54, 0x6d, 0x2b, 0xba, 0xad, 0x64, 0xbe, 0xa5, 0xe4, 0x01, + 0x55, 0x7a, 0x00, 0xa8, 0xcd, 0xe8, 0xf8, 0xbc, 0x2e, 0x9c, 0x95, 0x5b, 0xaa, 0x85, 0x62, 0x58, 0x2d, 0xfe, 0xc8, + 0x71, 0xfd, 0x1c, 0x2e, 0x5b, 0x01, 0xa4, 0x04, 0x6d, 0xd6, 0x09, 0x3a, 0xec, 0x30, 0x38, 0x64, 0x47, 0xc1, 0x11, + 0x3b, 0x0e, 0x8e, 0xd9, 0x49, 0x70, 0xc2, 0x1e, 0x04, 0x0f, 0xd8, 0x69, 0x70, 0xca, 0x1e, 0x06, 0x0f, 0xd9, 0x23, + 0x58, 0x40, 0xec, 0x71, 0xd0, 0x6e, 0xb3, 0x27, 0x30, 0xb7, 0xec, 0x69, 0xd0, 0x3e, 0x64, 0xcf, 0x82, 0xf6, 0x11, + 0x7b, 0x0e, 0x58, 0xcd, 0x22, 0xcc, 0x1d, 0x60, 0x6e, 0x8c, 0xb9, 0x43, 0xcc, 0xe5, 0x98, 0x3b, 0x82, 0xdc, 0x15, + 0x2b, 0x45, 0xc8, 0x0d, 0xa7, 0xd5, 0xee, 0x1c, 0x1e, 0x1d, 0x9f, 0x3c, 0x38, 0x7d, 0xf8, 0xe8, 0xf1, 0x93, 0xa7, + 0xcf, 0x9e, 0x3b, 0x7d, 0x76, 0x45, 0x27, 0x5f, 0xca, 0xec, 0x32, 0xd9, 0x6b, 0x1f, 0xf7, 0xd9, 0x42, 0xbd, 0xba, + 0xc9, 0x1e, 0xb0, 0x1a, 0xef, 0xfc, 0xfc, 0xa8, 0xdf, 0xd0, 0xb9, 0x8f, 0xe9, 0xc0, 0x8d, 0xc9, 0x02, 0x21, 0xdc, + 0xc5, 0x1c, 0x8f, 0xdd, 0x88, 0x03, 0x34, 0x30, 0x4c, 0xbf, 0xf0, 0xf6, 0xf6, 0xe8, 0x61, 0xac, 0x1e, 0x06, 0xea, + 0x21, 0xb2, 0x26, 0xe9, 0x5b, 0xe4, 0xca, 0x13, 0xd7, 0x95, 0x3e, 0xef, 0xa0, 0x5d, 0x89, 0x76, 0x12, 0xe9, 0xd4, + 0xff, 0x5f, 0x8e, 0x70, 0xda, 0x09, 0x8f, 0x84, 0x61, 0xec, 0xb8, 0xc7, 0xc3, 0x25, 0xe0, 0xdc, 0xf1, 0xf1, 0xde, + 0xcf, 0x97, 0xc9, 0x65, 0xbb, 0xdf, 0xdf, 0x6f, 0x3f, 0x60, 0x63, 0x9d, 0xd0, 0x11, 0x09, 0x03, 0x9d, 0x70, 0x28, + 0x12, 0xa2, 0x40, 0x7c, 0x8d, 0x49, 0x47, 0x94, 0x84, 0x25, 0x56, 0x01, 0xd5, 0xfd, 0x40, 0xd4, 0xfd, 0x10, 0xbd, + 0xc9, 0xa8, 0x7a, 0x59, 0xf5, 0xd9, 0xd9, 0xd1, 0xad, 0xac, 0x14, 0x9a, 0x90, 0xb5, 0xa9, 0x44, 0xa8, 0x05, 0x9a, + 0xc1, 0xa7, 0x63, 0x93, 0x78, 0x02, 0x89, 0xa2, 0xa9, 0x87, 0xd4, 0xd4, 0x03, 0x93, 0x75, 0xda, 0xef, 0x53, 0x93, + 0x9e, 0x8c, 0x1d, 0x00, 0xd3, 0x7f, 0xad, 0xed, 0x37, 0xc9, 0x19, 0x64, 0xf5, 0x10, 0xc3, 0xc8, 0x27, 0x58, 0xc1, + 0xe8, 0xab, 0x05, 0xa3, 0x1b, 0x7c, 0xee, 0x5d, 0x45, 0xc1, 0x22, 0xd2, 0x40, 0xea, 0x01, 0x7c, 0x1a, 0x15, 0xc1, + 0x9c, 0x7e, 0xc6, 0xe2, 0x67, 0xe0, 0x35, 0xae, 0x23, 0x04, 0x37, 0x5a, 0xa4, 0x94, 0x49, 0x99, 0x5a, 0xbc, 0x88, + 0xf0, 0x88, 0xcf, 0xa4, 0x4c, 0xa3, 0xde, 0xed, 0xe4, 0x7a, 0x70, 0x3b, 0x29, 0xbf, 0x79, 0x7f, 0xba, 0x7f, 0x96, + 0xfb, 0xee, 0x65, 0xb3, 0xe1, 0xf3, 0x3f, 0x87, 0x78, 0x96, 0xa8, 0x17, 0x0c, 0xf9, 0xd8, 0xeb, 0x5d, 0xfe, 0x59, + 0xb2, 0x7e, 0xc3, 0xca, 0xb8, 0xbf, 0x99, 0x82, 0x27, 0x8d, 0xd6, 0x13, 0xdd, 0xfb, 0x5e, 0xcf, 0xeb, 0x41, 0x9d, + 0x7f, 0x7a, 0xf7, 0x0e, 0x2c, 0xab, 0x49, 0x2e, 0x97, 0xb0, 0x09, 0x3f, 0xb4, 0xaf, 0x97, 0x30, 0x67, 0xed, 0x26, + 0xc7, 0x60, 0x6d, 0x78, 0x14, 0x1d, 0xfe, 0x34, 0x92, 0x83, 0xc3, 0x96, 0x77, 0xbf, 0xdd, 0x41, 0xe3, 0x4a, 0x33, + 0xdb, 0xdf, 0x5c, 0xf4, 0x45, 0xf3, 0x90, 0x3d, 0x6c, 0x16, 0xb0, 0xea, 0x58, 0xb3, 0xad, 0xac, 0xde, 0x97, 0xa5, + 0x0b, 0x4b, 0xac, 0x74, 0x4f, 0xf1, 0xcf, 0x91, 0xd7, 0x37, 0x0b, 0xf2, 0x75, 0xb4, 0xde, 0x3a, 0x9e, 0x9b, 0x85, + 0x3f, 0xd0, 0xd2, 0x09, 0xb4, 0x74, 0x42, 0x0d, 0xf1, 0xfd, 0x6a, 0x4b, 0x53, 0x39, 0x3b, 0x6a, 0xe6, 0xd8, 0x50, + 0x4b, 0xb7, 0x93, 0xb9, 0x80, 0xf3, 0x19, 0x30, 0x65, 0xf8, 0xd3, 0xb6, 0xdb, 0x79, 0xb2, 0xd1, 0x0e, 0x8d, 0xbb, + 0xcd, 0xfc, 0x63, 0x71, 0x0c, 0xb7, 0x14, 0x7b, 0xe2, 0x0d, 0x7e, 0xde, 0xa6, 0xcd, 0xbc, 0xf6, 0x01, 0xbe, 0x00, + 0xfd, 0xda, 0x0f, 0x4b, 0xc6, 0xf7, 0xf1, 0xfc, 0x2e, 0xba, 0xad, 0x94, 0x67, 0x87, 0xdd, 0xb2, 0xd1, 0xf0, 0x60, + 0x48, 0xfd, 0xfd, 0xb0, 0xdd, 0xac, 0x9a, 0x9c, 0xe1, 0x73, 0x23, 0xd4, 0x41, 0xe1, 0x32, 0xd3, 0xea, 0x5b, 0xd9, + 0xaa, 0xd8, 0xf9, 0x57, 0xd8, 0x01, 0x58, 0x58, 0xf6, 0x5c, 0xf8, 0xd2, 0x3b, 0xc8, 0x1a, 0x6e, 0x75, 0xc6, 0x7b, + 0x27, 0x41, 0xcb, 0x23, 0xec, 0x84, 0x74, 0xde, 0x4c, 0x30, 0xbd, 0x13, 0xb8, 0x49, 0xb3, 0xc2, 0xa7, 0x23, 0x0b, + 0x5a, 0x19, 0xe2, 0x9d, 0x39, 0x8d, 0x54, 0x1c, 0x00, 0x7a, 0xb2, 0x0c, 0x9e, 0xc6, 0xf4, 0x54, 0xc2, 0xd3, 0x80, + 0x9e, 0xf2, 0x50, 0xc3, 0x4b, 0xb4, 0x0e, 0xd3, 0x67, 0xcd, 0x2a, 0xa5, 0x44, 0x38, 0xa1, 0x85, 0x77, 0xd0, 0x51, + 0x6e, 0x01, 0x6c, 0xa2, 0xc6, 0x80, 0x66, 0x90, 0x82, 0x3c, 0x42, 0x73, 0x98, 0xcb, 0x34, 0x8c, 0xce, 0xfd, 0xe3, + 0xde, 0xe4, 0xc0, 0xed, 0x34, 0xe1, 0xdd, 0x0b, 0xe0, 0x09, 0xbf, 0x64, 0x71, 0xf8, 0x36, 0x12, 0xb5, 0xb1, 0x09, + 0xee, 0xe5, 0xc6, 0x61, 0xbc, 0x7f, 0xd2, 0x02, 0x0e, 0xe1, 0xb1, 0xcb, 0xf8, 0xb6, 0xc5, 0xd2, 0x5b, 0xf8, 0x13, + 0xd9, 0xd3, 0x90, 0x29, 0x80, 0x68, 0x4b, 0xdd, 0x7a, 0x6c, 0x9e, 0x5e, 0xe2, 0x56, 0xe8, 0x97, 0x50, 0xe1, 0x69, + 0x9f, 0x0a, 0xcf, 0x21, 0x05, 0x89, 0xdc, 0x10, 0xf4, 0x28, 0x3a, 0xe1, 0xc8, 0xb6, 0xdd, 0xbd, 0xcd, 0xd4, 0xbc, + 0x2a, 0xcf, 0xd2, 0xcc, 0xfd, 0x3d, 0x67, 0x22, 0xcd, 0x14, 0x7b, 0x17, 0x6d, 0x16, 0x7b, 0x12, 0x6d, 0x14, 0xbb, + 0xb7, 0xa5, 0xd8, 0xeb, 0xcd, 0x62, 0x7f, 0xe5, 0x96, 0xa5, 0x31, 0xb9, 0x7f, 0x08, 0x43, 0x3e, 0x44, 0x64, 0x85, + 0x3f, 0xa6, 0xd0, 0xa3, 0xc8, 0xcc, 0x55, 0x15, 0x5e, 0x44, 0xe2, 0xac, 0x45, 0xa2, 0x0e, 0x7d, 0xd3, 0xc4, 0x89, + 0x23, 0xe7, 0xfa, 0x7c, 0x39, 0x50, 0x2c, 0xb4, 0xce, 0x10, 0xb5, 0xab, 0x80, 0x66, 0xf5, 0x80, 0x61, 0x36, 0x30, + 0xd5, 0x0b, 0x80, 0x1f, 0x8a, 0x27, 0x4f, 0x6f, 0x69, 0x43, 0x2f, 0x1a, 0x04, 0x1f, 0x98, 0x6c, 0x78, 0x38, 0xec, + 0x13, 0xbf, 0x2b, 0xf0, 0xf9, 0x88, 0x9e, 0xb5, 0x41, 0x49, 0x1e, 0xc8, 0x00, 0xc2, 0xe2, 0xf4, 0xb2, 0x10, 0x60, + 0x41, 0x3e, 0xf6, 0x80, 0x71, 0x2a, 0xa3, 0xfc, 0x86, 0x19, 0xf7, 0x74, 0x46, 0x16, 0x02, 0x5c, 0xc5, 0x33, 0x03, + 0xb2, 0x8b, 0xfe, 0x36, 0x40, 0x68, 0xd1, 0xd7, 0x06, 0x48, 0x6b, 0x86, 0xe7, 0x41, 0xa2, 0x80, 0x5b, 0x5e, 0xfc, + 0xcf, 0xa4, 0x05, 0x8f, 0x76, 0x9d, 0x43, 0xc2, 0xd2, 0x2e, 0x47, 0x4e, 0x01, 0x7d, 0xc4, 0xdf, 0x46, 0x05, 0xc4, + 0x15, 0xeb, 0x84, 0x05, 0x05, 0x58, 0x1b, 0x62, 0x1a, 0x3c, 0x8c, 0xe1, 0x01, 0x83, 0x56, 0xfa, 0x03, 0x78, 0xe8, + 0x58, 0x68, 0xf2, 0x92, 0x60, 0x87, 0xc0, 0x49, 0xea, 0x1b, 0xf9, 0x95, 0xa8, 0x1c, 0x3d, 0x04, 0xb0, 0x0a, 0x10, + 0xf5, 0x4a, 0x17, 0x47, 0x41, 0xf1, 0x24, 0xf1, 0xb1, 0x63, 0xc2, 0x5f, 0x02, 0x9d, 0x25, 0xea, 0xfd, 0x19, 0xc9, + 0xaa, 0x7b, 0x6f, 0xc9, 0x57, 0x6c, 0xe7, 0xde, 0x32, 0x5b, 0xdd, 0xc7, 0x9f, 0x52, 0xfc, 0xa0, 0xf0, 0x00, 0xdc, + 0x6f, 0xe5, 0x7d, 0x0e, 0xb0, 0xd8, 0x96, 0x52, 0xde, 0x67, 0x75, 0x1c, 0xb0, 0x0c, 0x97, 0x37, 0x81, 0x33, 0x8c, + 0x8a, 0xaf, 0x0e, 0xfb, 0x23, 0x70, 0x52, 0x94, 0x16, 0x1d, 0xf6, 0x7b, 0xe0, 0x14, 0xdc, 0x61, 0xbf, 0x05, 0xce, + 0x20, 0x9d, 0x3b, 0xec, 0xd7, 0xc0, 0x19, 0x17, 0x0e, 0xfb, 0x84, 0xc6, 0x5a, 0x10, 0xac, 0xa6, 0x0e, 0xfb, 0x18, + 0x38, 0x25, 0x9d, 0x86, 0x00, 0x51, 0xc1, 0xe1, 0xf0, 0xf3, 0x21, 0x70, 0xf2, 0xd4, 0x61, 0x17, 0xf0, 0x03, 0x25, + 0x1f, 0xc3, 0xf7, 0x91, 0x03, 0xc2, 0x83, 0x83, 0x85, 0xc6, 0x0e, 0x48, 0x10, 0x0e, 0xd6, 0x5c, 0x3a, 0xec, 0x3d, + 0x3c, 0x65, 0x0e, 0xfb, 0x25, 0x70, 0x60, 0x3c, 0x7f, 0xcd, 0xf3, 0x04, 0xd2, 0x9e, 0x05, 0xce, 0x24, 0x71, 0xd8, + 0x3b, 0xf8, 0x2a, 0x77, 0xd8, 0xdb, 0xc0, 0x89, 0xa0, 0xaa, 0x37, 0xf0, 0x31, 0x54, 0xfc, 0x1a, 0x7a, 0x07, 0x3f, + 0xaf, 0x02, 0x67, 0x01, 0xaa, 0x14, 0x64, 0x3f, 0x87, 0x06, 0xa1, 0x82, 0x9f, 0x03, 0x27, 0x9e, 0x38, 0xec, 0x47, + 0x28, 0x5c, 0x7c, 0x85, 0x3a, 0x5e, 0x40, 0x32, 0x34, 0xf9, 0x52, 0x34, 0x04, 0x4d, 0xfe, 0x14, 0x38, 0xd7, 0x13, + 0x67, 0xc5, 0x72, 0x18, 0xe2, 0xdb, 0x24, 0xe6, 0xbf, 0xf1, 0xc0, 0x19, 0xb5, 0x46, 0xa7, 0xa3, 0x91, 0xc3, 0x40, + 0xa8, 0x4e, 0xfe, 0x9a, 0xf3, 0xeb, 0x67, 0x15, 0x26, 0x46, 0x7c, 0x30, 0x7c, 0x00, 0x89, 0x7f, 0xcd, 0x23, 0x78, + 0x1b, 0x51, 0x01, 0x78, 0x06, 0x01, 0xf5, 0x3d, 0x64, 0x3f, 0x80, 0x84, 0xe1, 0x11, 0x24, 0xfd, 0x3d, 0xff, 0x9d, + 0x6a, 0xa0, 0x02, 0x03, 0x90, 0xab, 0xf1, 0xdb, 0xe3, 0xd1, 0xf1, 0x30, 0x86, 0xd7, 0xa4, 0x84, 0xfa, 0xf0, 0x6b, + 0x7e, 0x14, 0x43, 0xe1, 0x41, 0x0a, 0x32, 0x70, 0xe0, 0xb4, 0xe8, 0x29, 0xfb, 0x99, 0x0f, 0xdf, 0x4e, 0x73, 0xda, + 0xca, 0x18, 0xf1, 0x41, 0x3c, 0x04, 0xc8, 0x52, 0x59, 0xfc, 0xfd, 0x96, 0x7c, 0xe0, 0x55, 0xe0, 0x9c, 0x46, 0x9d, + 0x01, 0xef, 0x40, 0xf1, 0x77, 0xd7, 0x19, 0x0c, 0xe9, 0xb8, 0x13, 0x75, 0x60, 0x34, 0x83, 0x79, 0x91, 0x2e, 0xae, + 0xf3, 0x7c, 0x88, 0x40, 0x18, 0x9c, 0x9e, 0x42, 0x2f, 0xe3, 0xe8, 0x75, 0x85, 0x5f, 0x1f, 0x8f, 0x1e, 0xf2, 0x08, + 0xea, 0xff, 0x39, 0x2a, 0xaa, 0xdf, 0x41, 0x78, 0x16, 0x1d, 0x6d, 0x61, 0x4a, 0x1e, 0x7f, 0x40, 0x33, 0xbf, 0x33, + 0xec, 0x9c, 0x3c, 0x6c, 0x03, 0xec, 0xe2, 0x8b, 0xb7, 0xd8, 0xda, 0x83, 0xd1, 0x71, 0x0b, 0x5f, 0x32, 0xd4, 0x4b, + 0x79, 0x81, 0x95, 0x9c, 0x1c, 0x3d, 0x3c, 0xe6, 0x43, 0x4a, 0x2c, 0x93, 0xf4, 0x2b, 0x8d, 0xfe, 0x14, 0xc7, 0x13, + 0x17, 0xc9, 0xb4, 0xcc, 0xa1, 0x27, 0xc3, 0xb8, 0x7d, 0x74, 0x88, 0x09, 0x8b, 0x28, 0x53, 0xc0, 0xb9, 0xc1, 0x4f, + 0x4f, 0x07, 0xf0, 0x20, 0x52, 0x4f, 0x07, 0xf4, 0x32, 0xfe, 0xf0, 0x3a, 0x7b, 0x07, 0x3d, 0x85, 0x7e, 0x9e, 0xb4, + 0x30, 0xe1, 0x57, 0x50, 0x4f, 0x9c, 0xe8, 0x21, 0xfe, 0xc3, 0xec, 0xdf, 0x9f, 0x63, 0x83, 0xd8, 0x43, 0x78, 0xb6, + 0x73, 0xbe, 0x4e, 0xa2, 0xaf, 0x09, 0x7c, 0x37, 0x1c, 0x3c, 0x38, 0xc1, 0xef, 0xa6, 0xd1, 0xf8, 0x79, 0x15, 0x61, + 0xbd, 0xad, 0x16, 0xd5, 0xfc, 0x21, 0xf9, 0xc6, 0xe9, 0xf3, 0xe3, 0xe3, 0x93, 0x41, 0x07, 0x7b, 0x70, 0x81, 0x06, + 0x15, 0xec, 0xcf, 0x69, 0x4c, 0x15, 0x5e, 0xc4, 0xcf, 0xa0, 0xe5, 0x87, 0x0f, 0x0f, 0x3b, 0x31, 0x74, 0xf6, 0xe6, + 0xf7, 0xa1, 0xf8, 0x9a, 0xf2, 0x4a, 0x84, 0x3d, 0x60, 0xc7, 0xc3, 0x87, 0x27, 0x0f, 0x22, 0x7c, 0x7f, 0x41, 0x75, + 0x9d, 0x8e, 0x06, 0xf1, 0x29, 0xd6, 0xf5, 0x11, 0x87, 0x73, 0x74, 0x7a, 0x38, 0xa4, 0xb6, 0x3e, 0x52, 0xaf, 0x3b, + 0xa3, 0x23, 0xf8, 0x87, 0xaf, 0xd4, 0x55, 0xfd, 0xfa, 0x0b, 0x14, 0x8d, 0xf9, 0xb0, 0x0d, 0x8f, 0x72, 0xe2, 0x1e, + 0xc2, 0x88, 0x86, 0x87, 0x0e, 0x1b, 0x3e, 0x9a, 0xcd, 0xde, 0x13, 0x04, 0xdb, 0x47, 0x0f, 0xc5, 0x7b, 0xf9, 0x75, + 0x81, 0x55, 0x0f, 0x08, 0x68, 0xc3, 0x64, 0x4a, 0x35, 0x9f, 0x3c, 0xc4, 0x7f, 0xf4, 0x4e, 0x55, 0xeb, 0xf7, 0x7c, + 0x38, 0x16, 0x93, 0xd2, 0xe6, 0x0f, 0x5b, 0xf8, 0xc5, 0x28, 0xf9, 0x7d, 0x50, 0x24, 0x88, 0x46, 0x83, 0x0e, 0xfe, + 0x0f, 0x52, 0xd2, 0x8b, 0xb7, 0x12, 0x67, 0x47, 0xa3, 0x68, 0x04, 0x83, 0x1b, 0xe5, 0xbf, 0x97, 0xd5, 0xaf, 0x8f, + 0x60, 0x78, 0x9d, 0xce, 0xe9, 0x80, 0xca, 0xcc, 0x7f, 0x2e, 0x13, 0xc2, 0xe3, 0x16, 0xd5, 0x32, 0x8e, 0xde, 0x97, + 0x83, 0x8b, 0x1c, 0x67, 0x12, 0xff, 0x41, 0x02, 0x5a, 0xe1, 0x64, 0x2d, 0xa7, 0x62, 0x39, 0x8c, 0x3f, 0x10, 0x6a, + 0x0e, 0x1f, 0x20, 0xbc, 0xd4, 0x34, 0x0e, 0x23, 0xc0, 0x42, 0x78, 0xa7, 0x5e, 0x9f, 0xb6, 0xf0, 0x1f, 0x64, 0x12, + 0xe4, 0x08, 0xae, 0xf0, 0xf8, 0xea, 0x1a, 0x66, 0x71, 0x38, 0x1a, 0xe1, 0x94, 0xd0, 0x60, 0x54, 0xb1, 0x09, 0x28, + 0x70, 0x8b, 0xd7, 0xd7, 0x72, 0xb9, 0x50, 0x42, 0x25, 0xa1, 0x73, 0xf2, 0x70, 0x00, 0xeb, 0xe3, 0xfd, 0x30, 0x89, + 0x32, 0x9c, 0xa5, 0x78, 0x78, 0x1c, 0x1f, 0xc7, 0x94, 0x30, 0x86, 0x4e, 0x1e, 0xe1, 0x94, 0xc3, 0x28, 0x92, 0x6f, + 0x17, 0x0b, 0x81, 0x6e, 0xf8, 0xb5, 0x44, 0x90, 0x51, 0x8b, 0x9f, 0x9c, 0x42, 0xd9, 0x34, 0xfa, 0xf6, 0xfc, 0x75, + 0x01, 0x33, 0x7a, 0xc2, 0x4f, 0x46, 0x91, 0x7a, 0xff, 0xad, 0x9c, 0xd0, 0x17, 0xad, 0xd1, 0x31, 0x26, 0x5d, 0x67, + 0xd4, 0xd7, 0x07, 0xf1, 0x88, 0x30, 0xe4, 0x0d, 0xe0, 0x40, 0xfc, 0x6c, 0x34, 0xca, 0x05, 0x16, 0x47, 0xb8, 0x08, + 0xff, 0x40, 0x68, 0x83, 0xba, 0x7b, 0xca, 0x4f, 0xe0, 0x45, 0xac, 0x12, 0x39, 0x80, 0x3f, 0x04, 0x66, 0x73, 0xb9, + 0xda, 0xff, 0x10, 0x40, 0xc1, 0xf1, 0x02, 0xdc, 0xa3, 0x21, 0xf4, 0xf0, 0x0f, 0x82, 0xcb, 0xf0, 0x10, 0xff, 0x61, + 0x01, 0x6c, 0xec, 0x61, 0x8b, 0xc3, 0xdc, 0xd1, 0x9b, 0x9d, 0x27, 0x47, 0x3e, 0x38, 0x89, 0x01, 0x6f, 0xfe, 0x90, + 0xe8, 0x08, 0x7d, 0x68, 0x21, 0x3a, 0xfe, 0x21, 0xd1, 0xb1, 0xd3, 0x1a, 0x74, 0x22, 0x7a, 0x17, 0x58, 0x73, 0xfa, + 0x20, 0xe6, 0x38, 0xb8, 0x3f, 0x04, 0x42, 0x3e, 0x78, 0x70, 0x7a, 0xfa, 0xf0, 0x21, 0xbe, 0x52, 0xdd, 0xfa, 0xb5, + 0xac, 0x1e, 0xa5, 0x84, 0x64, 0xad, 0xf8, 0x08, 0xe9, 0xe4, 0x1f, 0xd4, 0x47, 0xf8, 0x1f, 0x87, 0x7e, 0xa4, 0xc9, + 0x94, 0x0b, 0x4c, 0x10, 0xcf, 0xd4, 0x10, 0x2c, 0x91, 0xe1, 0x21, 0x0c, 0x20, 0x7d, 0xff, 0x9c, 0x46, 0xd3, 0xc2, + 0xd1, 0xab, 0x25, 0xa7, 0xb0, 0x66, 0x1a, 0xbd, 0xc3, 0x4e, 0xe2, 0x4c, 0xe3, 0xc7, 0x9f, 0x2c, 0x7a, 0x78, 0x72, + 0x12, 0x0f, 0xb1, 0xa3, 0x9f, 0xb0, 0x59, 0x04, 0xe3, 0x27, 0xb1, 0xf8, 0x06, 0xd1, 0xf1, 0x31, 0x0e, 0xf7, 0xd3, + 0x6c, 0x5e, 0xcc, 0x80, 0x78, 0x3f, 0x3c, 0x7c, 0xd0, 0x1a, 0xc2, 0x8a, 0xfa, 0x24, 0x07, 0x78, 0x18, 0x0f, 0x0e, + 0x1f, 0x00, 0x00, 0x3e, 0xd1, 0x7a, 0x7b, 0x30, 0x38, 0x39, 0x45, 0xbe, 0xf1, 0xa9, 0x9c, 0x15, 0xef, 0xc7, 0x54, + 0x60, 0x04, 0xe4, 0x00, 0x12, 0x7e, 0xa1, 0xd5, 0x38, 0x6c, 0xe3, 0x42, 0xfe, 0x44, 0x8b, 0x8c, 0xf0, 0xe4, 0x41, + 0xfb, 0xf8, 0x14, 0x26, 0x76, 0x9a, 0x0c, 0x33, 0x24, 0xf0, 0xb4, 0x50, 0x1e, 0xb6, 0x1f, 0x3e, 0x80, 0xde, 0x4d, + 0xdf, 0x57, 0xf1, 0xef, 0xd1, 0x94, 0xa8, 0xf1, 0x08, 0x61, 0x36, 0x4d, 0xca, 0x6a, 0xf1, 0xae, 0x94, 0xf4, 0x98, + 0x43, 0xa3, 0xd3, 0x3c, 0x8e, 0xa3, 0xf2, 0xbd, 0x48, 0x18, 0x40, 0x3d, 0x59, 0xf4, 0x2d, 0xfa, 0x92, 0xab, 0xc5, + 0x34, 0xe4, 0xd1, 0x90, 0xd2, 0x08, 0x87, 0x81, 0x9b, 0x0d, 0x71, 0x33, 0x12, 0x72, 0x86, 0xa3, 0x63, 0x04, 0x0f, + 0x12, 0x20, 0x81, 0xdd, 0x08, 0x0d, 0x7c, 0x1b, 0x3e, 0x1e, 0x00, 0x28, 0x06, 0xa7, 0xbc, 0x03, 0x43, 0xd6, 0xd4, + 0x28, 0x3a, 0xc6, 0x7c, 0x7a, 0xfd, 0x9d, 0x96, 0xd4, 0x91, 0x48, 0x20, 0x00, 0x0d, 0x23, 0x00, 0x08, 0x54, 0x36, + 0x7b, 0xcb, 0xd5, 0x1a, 0xe3, 0x9c, 0x9f, 0x22, 0x2c, 0x31, 0x89, 0x10, 0x08, 0x88, 0xd2, 0xc3, 0x53, 0x7a, 0x47, + 0x30, 0x44, 0x23, 0x28, 0xc0, 0xe9, 0x55, 0x03, 0x02, 0x88, 0x64, 0x0b, 0xe9, 0xcb, 0x2c, 0x9a, 0x45, 0x8b, 0xe8, + 0xfa, 0xd9, 0x8c, 0xc6, 0x34, 0x1a, 0xc2, 0x98, 0x66, 0x2f, 0x7e, 0x9e, 0xcd, 0x47, 0x23, 0x1a, 0x50, 0x34, 0x00, + 0xec, 0x98, 0xf1, 0x62, 0x8e, 0x73, 0x74, 0x7a, 0x7c, 0x08, 0x73, 0x2a, 0xd1, 0x30, 0x6e, 0xc5, 0x03, 0xdc, 0x6d, + 0x9d, 0x03, 0xc0, 0x86, 0xc3, 0xa8, 0x35, 0xc4, 0xbd, 0xd7, 0xfc, 0xfa, 0x75, 0x21, 0xd0, 0x88, 0x13, 0x3e, 0xc8, + 0x39, 0xc4, 0xf1, 0x22, 0x3c, 0x7e, 0x1f, 0x70, 0x80, 0x9f, 0x4c, 0x3c, 0x39, 0x39, 0x3c, 0x44, 0xdc, 0x13, 0x23, + 0x14, 0x08, 0xf2, 0xae, 0x5c, 0x0c, 0x8a, 0x1c, 0x59, 0x17, 0x12, 0x55, 0x24, 0xab, 0xef, 0x16, 0x6f, 0x89, 0xae, + 0xb6, 0x4f, 0x1e, 0xe2, 0x04, 0x94, 0xb0, 0xce, 0xde, 0x08, 0xe6, 0x76, 0x3a, 0x38, 0x3a, 0x6e, 0xc3, 0x08, 0xd4, + 0x42, 0x88, 0x4e, 0x5b, 0x0f, 0x3a, 0x58, 0x22, 0x1b, 0x2e, 0x44, 0x89, 0xd1, 0x51, 0x74, 0x74, 0x02, 0xb5, 0xaa, + 0xa5, 0xc1, 0x4f, 0x07, 0xc7, 0x0f, 0xf0, 0xb5, 0x9c, 0x80, 0x0c, 0x40, 0xf8, 0x7d, 0x8c, 0x70, 0x29, 0x93, 0xe7, + 0x19, 0x20, 0x6d, 0xd4, 0x3a, 0xee, 0x74, 0x86, 0xf8, 0x9a, 0x7e, 0xe3, 0x40, 0x17, 0x60, 0x84, 0xf0, 0x0f, 0xde, + 0xcd, 0x4a, 0xe2, 0x30, 0x64, 0xc2, 0xbb, 0x93, 0xe8, 0x98, 0xd6, 0xbe, 0x5c, 0x55, 0x30, 0x3a, 0x5c, 0xb0, 0x72, + 0x51, 0xc9, 0xb7, 0x32, 0xcb, 0xaf, 0x25, 0x89, 0x85, 0xb9, 0xb1, 0x10, 0x14, 0x58, 0x28, 0xbc, 0xcb, 0x15, 0x77, + 0x74, 0x72, 0xda, 0x41, 0x52, 0x56, 0x21, 0xa1, 0x18, 0xc2, 0x23, 0x92, 0xa6, 0x8a, 0xbf, 0x15, 0x78, 0x02, 0x8f, + 0xcf, 0xca, 0x0a, 0xa0, 0x05, 0x5c, 0x65, 0x34, 0x84, 0x29, 0xad, 0xf2, 0x69, 0x54, 0xe5, 0x44, 0x01, 0x0f, 0x8f, + 0x60, 0x30, 0x84, 0xe6, 0x00, 0xed, 0x21, 0x14, 0x95, 0xac, 0x04, 0x90, 0xa1, 0x83, 0xc3, 0xfa, 0xe9, 0x45, 0x85, + 0xb8, 0x0c, 0x1c, 0x1f, 0xa0, 0xa4, 0xe9, 0x3d, 0x11, 0x22, 0x7c, 0x2b, 0xa7, 0xf9, 0x57, 0x29, 0x7a, 0x20, 0xa9, + 0x53, 0x0b, 0x1e, 0xa7, 0xe1, 0xd5, 0xb5, 0x40, 0xa3, 0x88, 0x96, 0xb8, 0xb5, 0x1b, 0xfd, 0x34, 0x72, 0x95, 0xd8, + 0x9e, 0x84, 0xcb, 0x15, 0xd3, 0x41, 0x5e, 0xbf, 0xf2, 0x45, 0xe9, 0xe6, 0x25, 0x49, 0xb2, 0x56, 0x4a, 0x59, 0x7a, + 0xea, 0x58, 0x83, 0x3c, 0xb9, 0x8a, 0x8a, 0x64, 0x06, 0xba, 0x62, 0x76, 0xa6, 0x4e, 0xd3, 0x76, 0x33, 0x0c, 0xfd, + 0x80, 0xe9, 0x45, 0x18, 0x81, 0xe8, 0x9a, 0xf5, 0xa5, 0x32, 0xa9, 0x0e, 0x19, 0x90, 0x4e, 0x99, 0x8b, 0x63, 0x0b, + 0x51, 0x18, 0xa9, 0xd8, 0xf8, 0xa0, 0xe2, 0x96, 0x18, 0x3d, 0xca, 0xeb, 0xe6, 0x21, 0x45, 0xba, 0x7e, 0x99, 0x55, + 0xd0, 0x85, 0xcb, 0xa2, 0xcf, 0xda, 0x27, 0x20, 0x4a, 0x5f, 0x46, 0xfd, 0xf0, 0x32, 0x3f, 0x3f, 0x6f, 0x9f, 0xec, + 0x91, 0xd2, 0x77, 0x7e, 0x7e, 0x2a, 0x1e, 0xf0, 0x6f, 0xdf, 0xc4, 0xed, 0xc6, 0xfe, 0x7d, 0xe2, 0x66, 0x8c, 0x1f, + 0x48, 0xbe, 0xfe, 0xc4, 0x6f, 0x6f, 0xdd, 0x4f, 0x3c, 0xc4, 0x11, 0xb3, 0x4f, 0xdc, 0xa7, 0x3d, 0x12, 0x71, 0x42, + 0x28, 0xbc, 0x44, 0xcb, 0x19, 0xfc, 0xeb, 0x9b, 0x88, 0xcd, 0x9f, 0xf8, 0x65, 0x52, 0x3f, 0x5d, 0x6e, 0x42, 0x38, + 0xef, 0xed, 0x81, 0x9a, 0x50, 0x09, 0x35, 0xa1, 0x12, 0x6a, 0x42, 0x25, 0xd4, 0x84, 0xca, 0x04, 0xd1, 0x3f, 0xea, + 0xa1, 0x96, 0x42, 0xc6, 0x16, 0x29, 0x53, 0xbf, 0x42, 0xb3, 0x07, 0x5a, 0x27, 0x7b, 0xc6, 0xd8, 0xa1, 0x6d, 0x15, + 0x5b, 0x0d, 0x18, 0x5b, 0x13, 0xa5, 0xb5, 0xe3, 0xe0, 0x9f, 0x98, 0x3b, 0xde, 0xd7, 0xd4, 0xb2, 0x57, 0x5b, 0xd5, + 0x32, 0x9c, 0x49, 0x52, 0xcd, 0x76, 0x45, 0x3c, 0x92, 0xea, 0xf2, 0x01, 0x29, 0x66, 0x26, 0x48, 0x5e, 0x03, 0x93, + 0xba, 0xa8, 0x85, 0x9c, 0x92, 0x96, 0x06, 0x3a, 0xd3, 0xb0, 0x72, 0x0b, 0xb4, 0x50, 0x2a, 0x03, 0xa5, 0x8e, 0xe5, + 0xda, 0x20, 0x80, 0x94, 0x42, 0x47, 0x13, 0xba, 0xda, 0x31, 0xaa, 0x2e, 0x68, 0x09, 0x23, 0x8d, 0x05, 0x2b, 0xc8, + 0xa8, 0x82, 0x4c, 0x7e, 0x8c, 0xea, 0x8c, 0xcc, 0x3e, 0xa2, 0xec, 0x92, 0xb2, 0x4b, 0x9d, 0x9d, 0xab, 0x6c, 0xa1, + 0x24, 0xe6, 0x94, 0x9d, 0xeb, 0x6c, 0xd4, 0xd9, 0x60, 0x26, 0x4a, 0x98, 0x86, 0x5c, 0xa8, 0x6a, 0x46, 0xb7, 0x7a, + 0x1e, 0xd9, 0xd6, 0x5c, 0xd0, 0x35, 0xb5, 0x9e, 0x44, 0x66, 0xe2, 0x7b, 0x4b, 0x50, 0xd0, 0x48, 0x07, 0x02, 0xfd, + 0x4c, 0xfe, 0x0e, 0x56, 0xeb, 0xba, 0x12, 0x14, 0xbd, 0xa3, 0xa4, 0xf7, 0x59, 0x19, 0x51, 0x3f, 0x25, 0x14, 0x05, + 0xe8, 0x2c, 0xf4, 0x5b, 0xad, 0xc3, 0xf6, 0x61, 0xeb, 0xb4, 0x97, 0xec, 0xb7, 0x3b, 0xfe, 0xc3, 0x4e, 0x40, 0x86, + 0x08, 0x20, 0xa4, 0x68, 0x80, 0x39, 0xe8, 0xf8, 0x47, 0xde, 0x7e, 0xdb, 0x6f, 0x1d, 0x1f, 0x37, 0xf1, 0x0f, 0x7b, + 0x5c, 0xe9, 0xcf, 0x8e, 0x5a, 0x47, 0xc7, 0xbd, 0xe4, 0x60, 0xed, 0x23, 0x37, 0x69, 0x60, 0x41, 0xef, 0x80, 0x3e, + 0x62, 0xf8, 0xbd, 0x99, 0xde, 0x37, 0x1b, 0x76, 0x9e, 0xc7, 0x00, 0x18, 0x61, 0x8a, 0x43, 0xa8, 0xaa, 0xb7, 0x31, + 0x01, 0x51, 0xbd, 0x0d, 0x74, 0xa4, 0x5e, 0x80, 0x1c, 0xa8, 0xda, 0x9f, 0x12, 0x37, 0x6b, 0xf0, 0x7d, 0x57, 0xe4, + 0x57, 0xf8, 0x6d, 0x13, 0xa3, 0xe7, 0x01, 0x4c, 0x45, 0x6e, 0x69, 0xe7, 0x42, 0x5d, 0xcd, 0x12, 0x73, 0x07, 0x32, + 0x37, 0xb7, 0x73, 0xa1, 0xee, 0x66, 0x8e, 0xb9, 0x51, 0x00, 0xe0, 0xc3, 0x9c, 0xca, 0x8f, 0x9a, 0x04, 0x49, 0x33, + 0x29, 0x2f, 0xb8, 0xea, 0x36, 0xa0, 0x5b, 0x22, 0xce, 0x6c, 0x65, 0x52, 0x91, 0xce, 0xf0, 0x86, 0x15, 0x6d, 0xcd, + 0x69, 0x31, 0x6d, 0xc6, 0xc1, 0x8c, 0x06, 0xfe, 0xd9, 0xe7, 0x74, 0xed, 0x46, 0xab, 0x77, 0x78, 0xd2, 0x0a, 0xda, + 0x78, 0x54, 0x1c, 0x75, 0xed, 0x4c, 0xe8, 0xda, 0x99, 0xd2, 0xb5, 0x33, 0xa5, 0x6b, 0xa3, 0x02, 0x6f, 0xb5, 0xfd, + 0x5b, 0x5e, 0x73, 0xbf, 0x49, 0xb4, 0x2f, 0x8f, 0x70, 0xd6, 0x70, 0xab, 0xdb, 0x5b, 0x20, 0x83, 0x89, 0x65, 0xff, + 0x28, 0x4a, 0x63, 0xfe, 0x04, 0x80, 0xb5, 0x00, 0x2c, 0x68, 0xe5, 0x6e, 0xc1, 0x10, 0x71, 0x71, 0x2b, 0xaa, 0xb0, + 0x1e, 0xc5, 0xa7, 0xa7, 0xcc, 0xc9, 0xe7, 0xe1, 0x21, 0x59, 0x8f, 0xe1, 0xdb, 0x44, 0xd0, 0x8c, 0x44, 0xd0, 0x8c, + 0x44, 0xd0, 0x0c, 0xac, 0x84, 0xe9, 0xc2, 0x54, 0xd6, 0x8f, 0x42, 0xdc, 0x12, 0x80, 0x15, 0x84, 0x41, 0x0c, 0xe1, + 0x5b, 0xea, 0xf5, 0x5a, 0xe3, 0x6d, 0x0c, 0xda, 0x26, 0x4a, 0xc2, 0x0f, 0x9d, 0x5d, 0xd7, 0x7d, 0xfe, 0xbb, 0x86, + 0xf6, 0x3e, 0xde, 0xa8, 0xf3, 0xa8, 0x72, 0x5b, 0xe8, 0xba, 0xe2, 0x14, 0x4e, 0x8f, 0xc8, 0x42, 0x40, 0x36, 0x1b, + 0xe9, 0x92, 0xfe, 0x75, 0xed, 0x24, 0xb0, 0xa0, 0x04, 0xf6, 0x3d, 0x12, 0x5f, 0xb9, 0x09, 0x4d, 0xa0, 0x3d, 0x6e, + 0xa5, 0xbb, 0x9c, 0x60, 0x09, 0x5d, 0x74, 0x9b, 0x57, 0x31, 0xaf, 0x7a, 0x59, 0x58, 0x60, 0xcc, 0xc7, 0x80, 0x12, + 0x65, 0xd4, 0x66, 0x3c, 0xc4, 0x1c, 0x7e, 0x8b, 0xe8, 0x48, 0xcf, 0x07, 0xf1, 0xf3, 0x77, 0x64, 0x9d, 0x79, 0x84, + 0x85, 0xa6, 0x8e, 0x0a, 0x5f, 0x51, 0x6c, 0xa3, 0x70, 0x77, 0x57, 0x78, 0xb4, 0xd3, 0xdb, 0xba, 0x4b, 0x3b, 0x25, + 0x52, 0x36, 0xae, 0x50, 0x35, 0x47, 0xbf, 0xa9, 0x13, 0x7b, 0x90, 0xe8, 0x59, 0x34, 0x9b, 0xc0, 0x9a, 0x1b, 0x60, + 0x95, 0xf2, 0x3b, 0x7d, 0x90, 0x13, 0x5b, 0xa7, 0x3e, 0xaf, 0xe0, 0x69, 0xeb, 0xd5, 0x2b, 0xa2, 0xc5, 0x1e, 0x10, + 0x15, 0xd3, 0x82, 0x32, 0x6d, 0x4f, 0xf8, 0xcd, 0xf7, 0xbe, 0xf9, 0xba, 0xf5, 0x9b, 0x32, 0xfd, 0xde, 0x37, 0x2f, + 0xb7, 0x7d, 0x33, 0x4d, 0x6e, 0x5c, 0xb5, 0x76, 0x2a, 0xcb, 0x8c, 0x4d, 0x6e, 0x52, 0xe3, 0x01, 0xc6, 0xca, 0xc7, + 0x5f, 0x11, 0xd1, 0xa6, 0xab, 0x48, 0x38, 0xce, 0x42, 0xde, 0xf3, 0x8f, 0x03, 0x0e, 0x1c, 0xb7, 0xb3, 0x5f, 0x50, + 0x4c, 0x9b, 0x0c, 0x96, 0x66, 0xe9, 0x47, 0x2c, 0x0d, 0x5d, 0x37, 0xda, 0x8f, 0x31, 0x32, 0x4f, 0xbb, 0x17, 0x05, + 0x6e, 0xd4, 0x88, 0xbd, 0x03, 0xb7, 0xdd, 0x80, 0x34, 0xcf, 0x6b, 0xb4, 0xd1, 0x66, 0x9a, 0x87, 0xed, 0x66, 0x8a, + 0xb1, 0x3a, 0x89, 0x14, 0xa7, 0xfb, 0xf0, 0xd4, 0xc8, 0xf7, 0xa1, 0xc5, 0x86, 0x0f, 0x2c, 0x04, 0xd6, 0x9b, 0x4a, + 0x1e, 0x53, 0xf2, 0x58, 0x24, 0x0f, 0x74, 0xf2, 0x80, 0x92, 0x07, 0x22, 0x39, 0x0a, 0x0b, 0x48, 0x8a, 0x1a, 0x6e, + 0xbb, 0x59, 0x78, 0xfb, 0xd8, 0x03, 0xd5, 0xfb, 0x30, 0xb3, 0x43, 0xa4, 0xaf, 0xc8, 0xc7, 0x68, 0x96, 0xa7, 0x32, + 0x68, 0xa9, 0x01, 0x92, 0x3e, 0xf8, 0x85, 0xdf, 0xbc, 0xb1, 0xc0, 0x04, 0x2b, 0x82, 0x7e, 0x54, 0x48, 0x3e, 0x40, + 0x6f, 0x50, 0x39, 0x0d, 0x78, 0xd1, 0x17, 0xff, 0xab, 0x3c, 0xce, 0x83, 0x50, 0x5d, 0x45, 0xe9, 0x6c, 0x12, 0x6d, + 0x9c, 0x1e, 0x86, 0x2c, 0xb9, 0xb2, 0x74, 0x35, 0x1c, 0x44, 0x85, 0x62, 0xc3, 0xdd, 0x1c, 0x4b, 0xea, 0xb3, 0xa5, + 0x7e, 0x44, 0x46, 0x72, 0xf1, 0xc5, 0xb8, 0x00, 0x79, 0x29, 0x8e, 0x52, 0xee, 0x1a, 0x06, 0x6c, 0xba, 0x09, 0x72, + 0x08, 0x9e, 0x08, 0x28, 0xf6, 0xfd, 0xc3, 0x06, 0xd0, 0xd4, 0x7d, 0xff, 0xf8, 0x21, 0xfc, 0x0e, 0xf6, 0xfd, 0x76, + 0xdb, 0xe0, 0x2c, 0x40, 0x1b, 0xf2, 0xe0, 0xbf, 0x81, 0x3c, 0xe6, 0xb1, 0xca, 0x67, 0x11, 0xba, 0xb8, 0xfd, 0x83, + 0x6e, 0x34, 0x64, 0x37, 0x32, 0x3e, 0x16, 0x51, 0x3f, 0x37, 0xfa, 0x60, 0x37, 0x03, 0xd3, 0xd4, 0x84, 0x5f, 0x56, + 0x89, 0x99, 0x84, 0xe7, 0x31, 0xab, 0xc4, 0xf4, 0xc1, 0xf3, 0x40, 0x54, 0x45, 0x36, 0x40, 0x9e, 0x59, 0xc0, 0x7a, + 0xc1, 0x2d, 0xc8, 0x77, 0xd4, 0x21, 0x9d, 0x15, 0x5a, 0x0d, 0xbf, 0x57, 0xae, 0xa9, 0x0a, 0x96, 0x51, 0x85, 0xae, + 0x4e, 0xfc, 0xae, 0xa2, 0x6d, 0x53, 0x25, 0xff, 0xf7, 0x65, 0x75, 0xb5, 0x45, 0x5e, 0xd5, 0x0b, 0x3e, 0xab, 0x61, + 0x88, 0x2c, 0x25, 0x19, 0xf7, 0x97, 0x08, 0xb0, 0xdf, 0x93, 0xb7, 0xe2, 0x24, 0xa1, 0xb2, 0x23, 0x63, 0x52, 0xd2, + 0x68, 0xac, 0x3c, 0xd7, 0xe2, 0xb7, 0xcf, 0x6c, 0xaa, 0xba, 0x11, 0xf0, 0x0f, 0xe8, 0xdc, 0x3c, 0x13, 0x2e, 0xa1, + 0x43, 0xc7, 0xd0, 0xe2, 0xf7, 0xd2, 0xba, 0x5b, 0x63, 0x10, 0x7b, 0x7b, 0xeb, 0xfc, 0x42, 0x5d, 0xbd, 0xb0, 0x71, + 0xdd, 0x82, 0xf1, 0x27, 0x54, 0x17, 0x42, 0x09, 0x4f, 0xe3, 0xc4, 0x42, 0x14, 0x15, 0x7a, 0xeb, 0x01, 0x51, 0xf8, + 0x4b, 0x13, 0x77, 0x50, 0xe6, 0x34, 0x4f, 0x28, 0x83, 0xda, 0xea, 0x5b, 0x7d, 0x7b, 0x67, 0x0f, 0x48, 0xfb, 0x4a, + 0xfe, 0xdb, 0x86, 0xad, 0x46, 0xe4, 0x85, 0x35, 0x76, 0xa5, 0x5f, 0x6c, 0xcf, 0x64, 0x03, 0x1b, 0x79, 0x26, 0xfd, + 0xf6, 0xb6, 0x76, 0x3d, 0x91, 0xb8, 0x04, 0xc7, 0xdb, 0xdb, 0x4b, 0xca, 0xe7, 0xe8, 0x4c, 0xcd, 0xdd, 0x86, 0xcd, + 0x7c, 0xff, 0xaa, 0x71, 0xeb, 0x2f, 0xc4, 0x57, 0x03, 0x8b, 0xd1, 0x3d, 0xaa, 0xe5, 0x6f, 0x9d, 0x89, 0x5e, 0x15, + 0x24, 0x72, 0xae, 0x1f, 0x1b, 0x57, 0xf5, 0x8d, 0x8b, 0xb2, 0xa0, 0x07, 0x26, 0x5c, 0x95, 0x73, 0xdf, 0xf1, 0x7a, + 0xa4, 0x83, 0x3c, 0x4f, 0xf3, 0x08, 0x77, 0x44, 0x71, 0x8b, 0x21, 0x68, 0x24, 0x07, 0x15, 0xfb, 0x39, 0xff, 0xdf, + 0xaa, 0x64, 0xbf, 0x82, 0x6a, 0x0c, 0x4a, 0xbd, 0xb4, 0x45, 0x21, 0x13, 0x28, 0x92, 0x20, 0x6d, 0x7b, 0x9e, 0x7b, + 0x9a, 0x99, 0x47, 0xb3, 0x59, 0xba, 0xa0, 0xbb, 0xc2, 0x2c, 0x89, 0xca, 0x6c, 0x34, 0xc9, 0x28, 0x7d, 0xac, 0x40, + 0x99, 0x1e, 0x71, 0xcf, 0xa3, 0x53, 0xb6, 0x7a, 0x73, 0x3b, 0xf3, 0x50, 0x33, 0x2b, 0xc3, 0xbc, 0xd9, 0xee, 0x96, + 0xe7, 0xa8, 0x97, 0x35, 0x9b, 0x5e, 0x25, 0x83, 0x97, 0x83, 0x92, 0x05, 0x3a, 0x59, 0x29, 0x4e, 0xd2, 0xee, 0x88, + 0x82, 0xa8, 0xb9, 0xe5, 0xa4, 0xb2, 0x6d, 0x2f, 0x05, 0xd5, 0x23, 0x1a, 0x79, 0x42, 0xe1, 0xb3, 0x95, 0xc5, 0x04, + 0xa5, 0xce, 0x42, 0x35, 0x7c, 0x47, 0x4d, 0x05, 0xd4, 0xd5, 0x67, 0x05, 0x5d, 0x8f, 0xa1, 0xc7, 0x13, 0x95, 0xd6, + 0x75, 0x4b, 0x96, 0x8a, 0x92, 0xdc, 0xde, 0xee, 0xe2, 0x6d, 0x46, 0xb2, 0x4e, 0x3c, 0x7a, 0x2b, 0x1f, 0xcd, 0xcd, + 0x25, 0xd8, 0x0f, 0x1e, 0xb6, 0x68, 0xa3, 0x50, 0xea, 0x9b, 0xfc, 0x2c, 0xeb, 0x36, 0x1a, 0x9c, 0x02, 0x4d, 0x85, + 0x18, 0x55, 0x0e, 0xcf, 0x45, 0xe2, 0x8f, 0x88, 0x1d, 0x05, 0x92, 0x00, 0x45, 0xe0, 0xc3, 0xd0, 0xe0, 0xb5, 0x84, + 0xdb, 0xdb, 0x52, 0x44, 0x78, 0xa1, 0x1c, 0x11, 0xeb, 0x45, 0xb7, 0xa3, 0x43, 0xc9, 0x1a, 0x37, 0x8e, 0x44, 0x2e, + 0xf5, 0xf7, 0x66, 0x3d, 0xc3, 0x84, 0xd1, 0x36, 0x5e, 0x42, 0x71, 0x13, 0x08, 0x50, 0xcb, 0xb5, 0x05, 0x2e, 0xfc, + 0xfc, 0x5d, 0xe1, 0x94, 0xcc, 0xd7, 0x21, 0x98, 0xe9, 0x38, 0x01, 0x62, 0xe7, 0x56, 0xc5, 0x4d, 0x2c, 0x69, 0x4c, + 0xa5, 0x07, 0xe3, 0x40, 0x08, 0x86, 0xd8, 0xb8, 0x78, 0x34, 0x74, 0xc1, 0xe8, 0xc4, 0xba, 0x8f, 0x3f, 0x5a, 0xbb, + 0x79, 0x97, 0xce, 0xd5, 0x15, 0x2d, 0xf2, 0xab, 0x2b, 0x87, 0xd9, 0xce, 0xf5, 0x8e, 0x25, 0x0b, 0x3a, 0x7d, 0x1d, + 0x5a, 0x8b, 0x16, 0x7e, 0xb3, 0x6d, 0x2a, 0xfb, 0x14, 0x19, 0xbc, 0xc3, 0xe9, 0xa1, 0xca, 0x37, 0x0e, 0xa3, 0x5e, + 0x26, 0x08, 0x6f, 0xd0, 0xa7, 0xfb, 0xdd, 0x77, 0xa0, 0xdb, 0xed, 0xed, 0xbd, 0x03, 0x15, 0xae, 0x77, 0xc1, 0x69, + 0xcf, 0x0d, 0x4f, 0xa3, 0x43, 0x0e, 0x76, 0x3f, 0xb7, 0x10, 0xe0, 0x82, 0xaf, 0x6b, 0x36, 0xef, 0x29, 0xf6, 0x47, + 0x80, 0xb1, 0xc5, 0x31, 0xc2, 0xb1, 0x04, 0x09, 0xb6, 0xfa, 0xce, 0x86, 0x36, 0x08, 0xa1, 0x1c, 0x45, 0x78, 0x7d, + 0x56, 0x90, 0xfb, 0x53, 0x5e, 0x8c, 0x79, 0x71, 0x7b, 0xfb, 0x29, 0x12, 0xe7, 0xff, 0xd6, 0x42, 0x55, 0x96, 0x00, + 0xc6, 0x88, 0xfa, 0x8f, 0xea, 0x43, 0xd4, 0x67, 0x50, 0x21, 0xa8, 0x40, 0xe8, 0x61, 0x94, 0x64, 0x73, 0x75, 0xd6, + 0x2d, 0xae, 0xcd, 0x4b, 0xe1, 0xe9, 0x4a, 0x52, 0x40, 0xb5, 0x49, 0x18, 0xeb, 0x39, 0x3a, 0x9b, 0x40, 0x7d, 0xa9, + 0x97, 0xbb, 0xf1, 0x65, 0x0a, 0x1a, 0x08, 0x2b, 0x70, 0x33, 0x75, 0x73, 0x1e, 0x66, 0xbc, 0x46, 0xb9, 0xe4, 0x78, + 0x97, 0xa2, 0xaf, 0xc1, 0x8b, 0x68, 0x65, 0xaf, 0xee, 0xc8, 0x22, 0x12, 0xdb, 0x80, 0x9c, 0x09, 0x20, 0x97, 0x0a, + 0xc8, 0x19, 0x01, 0xb9, 0x04, 0xea, 0x83, 0x41, 0x9b, 0x40, 0x9d, 0xde, 0xa0, 0xe8, 0xf5, 0xf0, 0xa2, 0xf2, 0xe8, + 0x0a, 0x4b, 0x28, 0xc2, 0x85, 0x9c, 0x0e, 0x3c, 0xc6, 0x22, 0xc7, 0x5e, 0x86, 0x4b, 0xc7, 0xa1, 0x48, 0xbb, 0xec, + 0x86, 0x7e, 0xfc, 0x1b, 0xb6, 0x10, 0x0f, 0x0b, 0xcb, 0x98, 0xf4, 0xb1, 0x66, 0x6d, 0x48, 0x64, 0x5c, 0x3a, 0xc7, + 0x77, 0x10, 0xad, 0x65, 0x90, 0xc5, 0xac, 0x7e, 0xef, 0x5c, 0x29, 0xc2, 0x61, 0x64, 0x8d, 0xb0, 0x04, 0xc1, 0xd0, + 0x90, 0xce, 0x3f, 0xff, 0x04, 0xda, 0x99, 0x61, 0x34, 0x23, 0xc9, 0xd9, 0x9a, 0x6d, 0xaf, 0x01, 0x35, 0x05, 0xae, + 0x0a, 0x96, 0x81, 0x2b, 0xc3, 0x71, 0xac, 0x3b, 0x67, 0x4c, 0x94, 0xb5, 0x5a, 0x37, 0xa8, 0x53, 0x26, 0xfc, 0xc7, + 0xf9, 0x72, 0x3d, 0xd8, 0x12, 0x41, 0x35, 0xa3, 0x48, 0x37, 0x9e, 0xb8, 0x88, 0x0d, 0x50, 0x68, 0x6f, 0x8f, 0x5f, + 0x66, 0x7d, 0xeb, 0x66, 0x35, 0xe3, 0x41, 0x52, 0xd9, 0x13, 0xe7, 0xc6, 0x18, 0xed, 0x1e, 0xa0, 0x46, 0xbf, 0xe1, + 0xaf, 0xa4, 0xcd, 0xe0, 0x15, 0x79, 0x16, 0x8f, 0xcd, 0xb6, 0xeb, 0x62, 0xc0, 0x55, 0x3f, 0xa2, 0x67, 0x9f, 0x8c, + 0x5d, 0x98, 0xc8, 0xa1, 0xb6, 0xf5, 0x11, 0x1c, 0xb2, 0x28, 0x58, 0x91, 0x83, 0x0d, 0x4b, 0x63, 0xb3, 0xca, 0xce, + 0xab, 0x45, 0x00, 0x4e, 0x4b, 0x1d, 0xc8, 0x15, 0x59, 0x8a, 0x8f, 0x9e, 0xde, 0x44, 0x27, 0xf1, 0xa1, 0x4e, 0x25, + 0xa5, 0x08, 0x89, 0x50, 0x48, 0x3c, 0xda, 0x9c, 0xa7, 0x40, 0xfd, 0xdc, 0xdb, 0x42, 0xe4, 0x2c, 0x17, 0x9a, 0xba, + 0x6e, 0x29, 0x23, 0x6a, 0x39, 0xd3, 0x7c, 0x5e, 0xf2, 0xf9, 0x0c, 0xf9, 0xbb, 0x4e, 0x8b, 0x61, 0x44, 0x5f, 0xeb, + 0x29, 0xe8, 0x10, 0x79, 0x53, 0x4d, 0x79, 0x36, 0x77, 0xe4, 0x38, 0xdf, 0x08, 0x75, 0xff, 0xdd, 0x4b, 0xf6, 0x09, + 0x34, 0x93, 0x37, 0xec, 0xaf, 0x28, 0xfc, 0xd4, 0x78, 0xc3, 0xc6, 0x49, 0x28, 0x64, 0x03, 0xff, 0xdd, 0xdb, 0x8b, + 0x97, 0x1f, 0x5e, 0x7e, 0x7a, 0x76, 0xf5, 0xf2, 0xcd, 0xf3, 0x97, 0x6f, 0x5e, 0x7e, 0xf8, 0x9d, 0xfd, 0x16, 0x85, + 0x6f, 0x0e, 0xda, 0xa7, 0x2d, 0xf6, 0x11, 0x7e, 0x3b, 0xec, 0xa6, 0x82, 0x9f, 0x23, 0x36, 0x29, 0xc3, 0x37, 0xfb, + 0x9d, 0x83, 0x43, 0x36, 0xaf, 0x44, 0x95, 0x69, 0x3e, 0x6e, 0xb7, 0xd8, 0x5f, 0xf2, 0x0d, 0xd5, 0x7b, 0xeb, 0x18, + 0x0e, 0x5f, 0x73, 0x7e, 0xa0, 0x32, 0xd1, 0xa0, 0x24, 0x47, 0x94, 0x33, 0x0b, 0x9d, 0x86, 0xa5, 0x8d, 0x4e, 0x22, + 0x94, 0x34, 0xfa, 0x30, 0x22, 0x5a, 0x25, 0xa1, 0xac, 0x27, 0x39, 0x68, 0xf3, 0x43, 0xa4, 0x4f, 0x89, 0x56, 0x8e, + 0xb5, 0x09, 0xa7, 0x2d, 0xad, 0x18, 0xa3, 0x34, 0x07, 0xa0, 0xcf, 0x51, 0x10, 0x20, 0xab, 0x45, 0x72, 0xa0, 0x63, + 0x56, 0x65, 0x67, 0x61, 0xbb, 0xd7, 0x0e, 0xe0, 0xa7, 0xd3, 0xeb, 0xe0, 0xcf, 0x71, 0xef, 0x38, 0x68, 0xb7, 0xbc, + 0x7d, 0xab, 0x1f, 0x3f, 0xd7, 0xd0, 0xfa, 0xb2, 0xcf, 0x64, 0x13, 0xe5, 0x5f, 0x45, 0xa5, 0x4c, 0x7a, 0x99, 0x34, + 0xc7, 0xb6, 0xbb, 0xd9, 0x19, 0x27, 0x3b, 0x6c, 0x72, 0x1f, 0x51, 0x9b, 0x8e, 0xd5, 0xe8, 0x85, 0x23, 0x9f, 0x92, + 0x83, 0xcc, 0xab, 0x05, 0xc6, 0x71, 0xf9, 0x6d, 0xcb, 0x43, 0xa1, 0x91, 0xb2, 0xd1, 0x1d, 0xc8, 0x2f, 0x73, 0xa8, + 0x5c, 0x06, 0xf7, 0x2f, 0x9b, 0xb9, 0x07, 0x03, 0x9a, 0xb9, 0x35, 0x53, 0xc3, 0x6b, 0xcb, 0xcd, 0x71, 0x37, 0x29, + 0xdf, 0x44, 0x6f, 0xdc, 0x9a, 0xcc, 0x63, 0x8b, 0x76, 0xf6, 0xb2, 0xf8, 0x51, 0x7a, 0x51, 0xd4, 0xc0, 0xa5, 0x01, + 0xab, 0x7a, 0xd5, 0xac, 0xce, 0xf0, 0x16, 0x43, 0xde, 0xa8, 0xce, 0x43, 0x8b, 0x7a, 0xfe, 0xa2, 0xdd, 0xb8, 0xb4, + 0x31, 0x5a, 0x19, 0xa2, 0xc9, 0x2d, 0x48, 0x19, 0xa2, 0x81, 0xb8, 0x67, 0x64, 0x6b, 0x4e, 0x60, 0x35, 0x23, 0xc3, + 0x17, 0x1d, 0xcc, 0x89, 0xce, 0xa1, 0x59, 0xc9, 0xb8, 0x09, 0xd1, 0x2b, 0x85, 0x68, 0x40, 0xcb, 0x93, 0x31, 0x41, + 0xd1, 0x2b, 0xa4, 0x5b, 0x5d, 0xff, 0xc3, 0x5e, 0x00, 0xfb, 0x2e, 0xa1, 0xa2, 0xed, 0x57, 0x93, 0xd5, 0xf3, 0x21, + 0xf7, 0xe0, 0x8d, 0x95, 0x3f, 0x2f, 0x95, 0xbf, 0xc7, 0x17, 0x8b, 0x92, 0x8b, 0xa0, 0x62, 0x6d, 0xa6, 0xe2, 0xc1, + 0x75, 0x6d, 0x80, 0xec, 0x57, 0xde, 0x01, 0x5d, 0xe8, 0xd8, 0xf5, 0x2a, 0x50, 0xee, 0x5a, 0x18, 0xc4, 0x6d, 0x0b, + 0xe5, 0xfb, 0x65, 0x0d, 0xa6, 0x95, 0x7f, 0xd3, 0x44, 0x5a, 0x8d, 0x77, 0x3c, 0x2d, 0xe0, 0x69, 0x01, 0xc0, 0x31, + 0x38, 0xc3, 0xf7, 0x79, 0x23, 0xdb, 0xcf, 0x3c, 0x19, 0xfc, 0x56, 0x2c, 0x00, 0x90, 0xcb, 0x3b, 0xe2, 0xae, 0x41, + 0xe5, 0x1c, 0x75, 0xd6, 0xf4, 0x8f, 0xf7, 0xdf, 0x00, 0x02, 0xe5, 0x8d, 0xf0, 0x93, 0xc7, 0x96, 0x11, 0xfa, 0x6c, + 0xe3, 0xd9, 0xbb, 0x44, 0x08, 0xf1, 0x41, 0x69, 0x51, 0xc7, 0x51, 0x59, 0x63, 0x6b, 0xa6, 0x31, 0xbd, 0x1a, 0x54, + 0x9f, 0x3a, 0x5e, 0xc3, 0x4a, 0x13, 0xbd, 0xeb, 0xd4, 0xa0, 0x5c, 0x3b, 0x28, 0x87, 0xcb, 0xb2, 0xf1, 0x57, 0xe4, + 0xdd, 0xff, 0xd4, 0x7c, 0x63, 0x0d, 0xb8, 0xe6, 0x9a, 0xf4, 0xa9, 0xf1, 0x09, 0xf2, 0xad, 0xa3, 0x8e, 0x89, 0x11, + 0x4f, 0x94, 0x34, 0xf2, 0x8b, 0x90, 0x4a, 0x7f, 0x41, 0xcd, 0xbe, 0x80, 0x1f, 0x8e, 0x9e, 0x61, 0xbf, 0xb8, 0x79, + 0x13, 0x43, 0x40, 0xc2, 0x43, 0x81, 0x0f, 0x29, 0x3c, 0x20, 0xb6, 0x03, 0x63, 0xc7, 0x87, 0x42, 0x03, 0x03, 0x8f, + 0xd7, 0xe5, 0xe2, 0x94, 0x1d, 0x08, 0x14, 0xd9, 0xde, 0x5e, 0x2e, 0x9e, 0xa2, 0xf3, 0x78, 0x6f, 0x0f, 0x58, 0xbf, + 0x69, 0x3b, 0xa9, 0xb6, 0xd1, 0x17, 0x42, 0x28, 0x66, 0xb9, 0xa6, 0x25, 0xf6, 0x88, 0x7f, 0xaa, 0x51, 0x56, 0xac, + 0xa0, 0x79, 0xd8, 0x79, 0x70, 0x72, 0xca, 0xf0, 0xef, 0x03, 0xab, 0x60, 0x15, 0xab, 0x81, 0x85, 0x6d, 0x0e, 0xba, + 0x9d, 0xfe, 0xe6, 0xdc, 0xc2, 0x67, 0x68, 0xbb, 0x09, 0x3d, 0x4c, 0xce, 0x2c, 0x5c, 0x86, 0xb4, 0x86, 0xe5, 0xb1, + 0xf7, 0x48, 0xfb, 0x93, 0x91, 0xd4, 0x84, 0x97, 0xfb, 0x82, 0x40, 0xde, 0x3f, 0xab, 0x24, 0x35, 0xb1, 0x43, 0x80, + 0x83, 0xe0, 0x29, 0x17, 0x59, 0x37, 0x6b, 0x96, 0xe7, 0xed, 0x2e, 0xac, 0xaa, 0xb2, 0x91, 0x9d, 0x9f, 0x03, 0xca, + 0xa2, 0x3c, 0x07, 0x1a, 0x45, 0x90, 0x85, 0xea, 0x98, 0xe2, 0x32, 0xcd, 0x83, 0x92, 0x4d, 0x92, 0x20, 0x53, 0x7a, + 0xf6, 0x5b, 0xe5, 0x3d, 0x4d, 0x07, 0x47, 0xa9, 0x65, 0x78, 0xec, 0x95, 0xfa, 0xc0, 0x23, 0x2e, 0xd2, 0xb2, 0x8f, + 0x77, 0x27, 0x6a, 0xcc, 0xe3, 0xe2, 0x94, 0x1f, 0xc7, 0xd8, 0xd4, 0x65, 0xa3, 0x8d, 0x99, 0xf8, 0xba, 0x0a, 0x4a, + 0xec, 0x28, 0x15, 0x3e, 0xc3, 0xb0, 0x87, 0xb1, 0x71, 0xcc, 0x56, 0x15, 0x63, 0x81, 0x0c, 0x0b, 0x9c, 0x87, 0xdc, + 0xd2, 0xe0, 0x93, 0xb8, 0x46, 0x38, 0xea, 0xe4, 0x42, 0x0c, 0xee, 0xac, 0xc4, 0xe6, 0x32, 0x80, 0x42, 0x17, 0xe4, + 0x92, 0x86, 0x94, 0xb6, 0xcf, 0x33, 0xea, 0x44, 0xb3, 0xdd, 0x3f, 0xe7, 0x5d, 0x0f, 0x74, 0x26, 0xed, 0x00, 0x79, + 0xde, 0x02, 0x84, 0x38, 0x53, 0x95, 0xf4, 0x14, 0x1f, 0x27, 0xb9, 0x4b, 0x29, 0x9e, 0x7f, 0xe4, 0xe1, 0xa5, 0x83, + 0x54, 0x15, 0xe5, 0xec, 0x7c, 0x06, 0x7f, 0xe9, 0x5a, 0x3d, 0xfc, 0xa5, 0xeb, 0xd0, 0xe0, 0x41, 0xde, 0xb4, 0xe7, + 0xf4, 0x4d, 0x67, 0xb3, 0x58, 0x07, 0x89, 0x4f, 0xfc, 0x2b, 0x14, 0x1c, 0xaa, 0x2f, 0x25, 0xbc, 0xea, 0x67, 0x3f, + 0x95, 0xe1, 0x52, 0x4a, 0x75, 0xf9, 0x9b, 0xec, 0xd5, 0x6a, 0xfb, 0x01, 0xd5, 0x84, 0x39, 0xea, 0x53, 0xbc, 0x44, + 0xe1, 0x3b, 0xb7, 0x4f, 0xb6, 0xe5, 0xa5, 0xe7, 0x4b, 0xdd, 0x02, 0x4a, 0xde, 0xab, 0x95, 0xc7, 0xfe, 0xc8, 0xb7, + 0xde, 0x80, 0xec, 0x5c, 0xe5, 0xd9, 0x53, 0xa0, 0x1e, 0x4e, 0xe3, 0x1d, 0x39, 0xbe, 0x09, 0x3d, 0xab, 0x7b, 0x57, + 0x3f, 0xf8, 0x3f, 0x6a, 0x1e, 0x03, 0x58, 0xd4, 0x2e, 0x6b, 0x12, 0xba, 0x2f, 0x85, 0x2d, 0xc9, 0x2d, 0xd7, 0xb7, + 0x2d, 0xf0, 0x50, 0x7d, 0x8c, 0xf0, 0x58, 0xb5, 0x90, 0x93, 0x22, 0x8c, 0x0c, 0x5b, 0x3b, 0xcc, 0x8d, 0x29, 0xa2, + 0x0d, 0x3a, 0xf8, 0xbb, 0xf2, 0x6c, 0xa9, 0x7b, 0x56, 0xd6, 0x89, 0xa9, 0x69, 0x86, 0xb4, 0x0e, 0xbe, 0x2e, 0x82, + 0x73, 0xd3, 0x3a, 0x69, 0x28, 0xe6, 0x5e, 0x3b, 0xba, 0x9b, 0xb4, 0xd9, 0xa6, 0xcb, 0x9e, 0xc5, 0xed, 0x77, 0x25, + 0x3a, 0xf2, 0xee, 0xea, 0xa0, 0x5d, 0xe7, 0xc8, 0x76, 0x5d, 0x0b, 0xb2, 0x39, 0xf4, 0x5a, 0xde, 0x4c, 0x97, 0x5c, + 0xe6, 0xfd, 0x95, 0xbe, 0xa7, 0xce, 0xc2, 0x03, 0xd3, 0xd3, 0x32, 0xb6, 0x25, 0x03, 0x75, 0xc9, 0x63, 0xcd, 0x3e, + 0x04, 0xb2, 0x1f, 0xac, 0x1c, 0x83, 0xa4, 0x81, 0x30, 0x3f, 0xe1, 0xfd, 0x51, 0x68, 0xf0, 0x16, 0xdf, 0xfe, 0x94, + 0xdb, 0x07, 0x75, 0xeb, 0x36, 0x15, 0x71, 0x16, 0xb6, 0x6e, 0x58, 0xd1, 0x85, 0x2d, 0xaa, 0xe5, 0x7a, 0xab, 0x40, + 0x9e, 0x9b, 0x95, 0x97, 0x4e, 0x3d, 0xca, 0xf0, 0x6c, 0x0c, 0x14, 0x7b, 0x5e, 0x60, 0x14, 0x31, 0xdb, 0x9e, 0x56, + 0x15, 0xf6, 0xad, 0xca, 0x97, 0xb8, 0x4f, 0xa8, 0x65, 0x4e, 0x7d, 0x13, 0x38, 0x4e, 0x50, 0x89, 0x14, 0x0a, 0x34, + 0x04, 0xa0, 0x51, 0x19, 0xc5, 0x96, 0x90, 0xa1, 0x85, 0xe5, 0x1d, 0x22, 0x64, 0xbf, 0xc3, 0x6f, 0x99, 0x32, 0x8f, + 0x90, 0x0b, 0xab, 0x67, 0x6f, 0x3a, 0xe5, 0xb1, 0xd5, 0xd6, 0xb6, 0x36, 0x32, 0x33, 0xe4, 0x9e, 0x4b, 0xf6, 0xde, + 0x0f, 0xc9, 0x94, 0xe7, 0x73, 0xbc, 0xbc, 0x09, 0xb8, 0x72, 0xc9, 0x2b, 0xf5, 0x8e, 0x14, 0x04, 0x6f, 0x00, 0x4a, + 0x6c, 0x7c, 0x44, 0xb9, 0x4a, 0xd1, 0xba, 0x22, 0x56, 0x57, 0x82, 0x38, 0x14, 0xb2, 0xd3, 0xe9, 0x39, 0x78, 0x8a, + 0x08, 0x55, 0x28, 0x48, 0x02, 0x2d, 0x07, 0x12, 0xe8, 0x48, 0x96, 0x83, 0x5e, 0x63, 0x68, 0xe4, 0x76, 0xd8, 0xb8, + 0x34, 0x44, 0xcc, 0xfe, 0xb2, 0xb2, 0x3e, 0xe2, 0x01, 0xb9, 0x69, 0x1f, 0x74, 0x0c, 0x08, 0xa3, 0x78, 0x5d, 0x51, + 0xae, 0xd6, 0xcc, 0x05, 0xc0, 0xed, 0xc8, 0xf5, 0x16, 0x50, 0x07, 0xa5, 0x39, 0x3e, 0x94, 0x45, 0x97, 0xc9, 0x05, + 0x9a, 0xa7, 0x83, 0x82, 0x5d, 0x91, 0xc0, 0x36, 0x0c, 0xa2, 0x55, 0x98, 0x00, 0x13, 0x2c, 0xfc, 0xe8, 0x06, 0x63, + 0x6b, 0x00, 0x17, 0x09, 0x52, 0x06, 0x7c, 0x23, 0x98, 0x30, 0x78, 0x7e, 0x2a, 0xa6, 0x3d, 0x18, 0x62, 0x92, 0x7a, + 0x99, 0xaf, 0x42, 0xba, 0x1a, 0xec, 0x63, 0xc9, 0x8b, 0xc7, 0x28, 0xab, 0x94, 0x30, 0xbf, 0x43, 0xf2, 0x29, 0x4f, + 0x2a, 0xe3, 0xbc, 0xfe, 0xb6, 0x72, 0x23, 0x16, 0xb3, 0xd4, 0x03, 0x99, 0x9c, 0xf1, 0x5e, 0x16, 0xbc, 0xc5, 0xb8, + 0xda, 0x31, 0x13, 0xd7, 0x8a, 0x25, 0x37, 0x3c, 0x7d, 0x9e, 0x17, 0x9f, 0x68, 0xc9, 0xa7, 0x1e, 0x16, 0xc2, 0x23, + 0x2a, 0x59, 0x13, 0x77, 0xf7, 0xe6, 0xbd, 0xdc, 0x54, 0x05, 0x3c, 0x8c, 0xaa, 0x92, 0x5d, 0x9c, 0x60, 0x40, 0x62, + 0x7f, 0x92, 0x34, 0x80, 0x07, 0xf5, 0x5a, 0xdf, 0xa9, 0x74, 0xe2, 0xe9, 0x92, 0x40, 0x9a, 0x60, 0xae, 0x9a, 0x65, + 0x00, 0x60, 0x69, 0x96, 0x52, 0xc3, 0x5b, 0x12, 0xb0, 0x81, 0x63, 0xc5, 0x30, 0x86, 0x2a, 0xc8, 0x2d, 0xbc, 0x89, + 0xcd, 0xe2, 0x5a, 0xde, 0x08, 0x88, 0x55, 0x6c, 0x21, 0x1e, 0x38, 0xbb, 0x22, 0x13, 0xff, 0x7b, 0x74, 0xec, 0x29, + 0x81, 0xd7, 0x01, 0xfc, 0xd0, 0x71, 0x16, 0x21, 0x27, 0xe4, 0xc9, 0x1b, 0x04, 0x9f, 0x10, 0x21, 0x17, 0x98, 0xca, + 0x29, 0x75, 0x81, 0xa9, 0x1c, 0x53, 0xe9, 0x44, 0xfd, 0x6e, 0x66, 0x8c, 0x45, 0x76, 0x85, 0x80, 0xf1, 0xda, 0x1c, + 0x0c, 0xbd, 0xf5, 0xb1, 0x5e, 0x52, 0xe5, 0xe0, 0x17, 0x43, 0x2b, 0x62, 0xe5, 0xe2, 0x3d, 0xba, 0xc1, 0xf7, 0x85, + 0xc8, 0xe7, 0x2a, 0x7f, 0x21, 0xf2, 0xa9, 0x21, 0x85, 0xf1, 0xf5, 0xdd, 0x4e, 0xf2, 0x8c, 0xcf, 0x25, 0xd6, 0x7e, + 0xd4, 0x17, 0xd9, 0xb4, 0xe8, 0xc2, 0x70, 0x10, 0x46, 0xa7, 0x16, 0x52, 0x36, 0x8d, 0x0c, 0xd7, 0x61, 0xed, 0xd6, + 0x3e, 0x48, 0x64, 0x21, 0x4c, 0xd0, 0xbe, 0x54, 0xae, 0x32, 0x97, 0x34, 0xa7, 0xfd, 0x4f, 0x07, 0xb8, 0xf7, 0x32, + 0x28, 0xd7, 0xce, 0x4a, 0x89, 0xcf, 0x9a, 0xf0, 0x5d, 0xb2, 0xf5, 0x13, 0x98, 0xc2, 0x29, 0x0f, 0x97, 0x78, 0x02, + 0x2d, 0x2a, 0x84, 0xa9, 0x01, 0x8f, 0xac, 0xbe, 0xcc, 0x7e, 0x99, 0x47, 0x43, 0x7a, 0xdf, 0x17, 0x29, 0x6f, 0xe7, + 0x95, 0x4a, 0x6a, 0x26, 0xd8, 0x89, 0x8e, 0x27, 0x4b, 0x5a, 0x39, 0xa0, 0xdb, 0x84, 0xfe, 0xb1, 0x77, 0xd6, 0xee, + 0x81, 0xec, 0x0a, 0x5f, 0x06, 0x28, 0xc3, 0xba, 0x4d, 0xf5, 0x01, 0x20, 0x96, 0xfc, 0xe6, 0xc9, 0x7c, 0x90, 0xc4, + 0xb2, 0x7a, 0xd3, 0x80, 0x4e, 0x15, 0x63, 0xc4, 0x2c, 0x44, 0x31, 0xd5, 0x8a, 0x95, 0x5d, 0x6f, 0x06, 0x1a, 0xc2, + 0x76, 0xf0, 0xab, 0x8e, 0xf8, 0x4a, 0x77, 0x0e, 0x7a, 0x56, 0x54, 0xba, 0xa1, 0xda, 0x58, 0x44, 0x7a, 0xd3, 0x35, + 0x8d, 0xed, 0x27, 0xa6, 0x87, 0x76, 0x99, 0xcd, 0xf6, 0xd4, 0xd0, 0x4c, 0x93, 0xfb, 0x16, 0x44, 0x7e, 0x99, 0x27, + 0x99, 0xdd, 0xa8, 0xdd, 0xac, 0xcc, 0xb1, 0x1b, 0xad, 0x8f, 0xd2, 0x2a, 0xb2, 0xd9, 0xea, 0xc6, 0x48, 0xeb, 0xa3, + 0xbd, 0xc0, 0x83, 0x84, 0x38, 0x28, 0x9a, 0xe9, 0x38, 0x07, 0x56, 0xba, 0xff, 0xd1, 0x93, 0xb5, 0x43, 0xdd, 0x2a, + 0x5f, 0x23, 0x02, 0x66, 0x9b, 0xa6, 0xf5, 0xe7, 0xd8, 0x86, 0xae, 0xe2, 0x0d, 0xa0, 0x8e, 0x81, 0xcb, 0xb3, 0x9b, + 0x59, 0x1e, 0x28, 0x7c, 0x85, 0xc5, 0xbf, 0x81, 0x9c, 0x48, 0x3c, 0x64, 0x73, 0x76, 0x59, 0xd4, 0xb3, 0x9b, 0x1b, + 0x28, 0x69, 0x0f, 0x5c, 0x95, 0xfe, 0xc8, 0xc5, 0x76, 0x43, 0x72, 0xe6, 0x1f, 0xe3, 0xd0, 0xd7, 0x5b, 0xd8, 0xef, + 0x60, 0x1b, 0x04, 0x87, 0xf5, 0x0a, 0x55, 0xa6, 0x81, 0xc8, 0x93, 0xa4, 0x10, 0x78, 0x76, 0x0e, 0x3d, 0x80, 0x49, + 0x73, 0x8d, 0x6a, 0xd4, 0x06, 0xb4, 0x34, 0x23, 0x43, 0xfc, 0x92, 0x65, 0xed, 0x22, 0x6a, 0x9e, 0x2c, 0x28, 0xa9, + 0x62, 0x66, 0x7e, 0x0c, 0xbc, 0xea, 0x15, 0x07, 0xeb, 0xe9, 0x6a, 0xde, 0x70, 0x77, 0x57, 0xc1, 0x13, 0x2f, 0xa2, + 0x11, 0x68, 0xad, 0x06, 0x3e, 0x45, 0x09, 0xc8, 0x6f, 0x3d, 0x38, 0xc6, 0x23, 0x94, 0x1a, 0x96, 0x9b, 0xe5, 0x06, + 0x1b, 0xe5, 0x04, 0x1c, 0x45, 0x49, 0x4b, 0x3a, 0x58, 0x87, 0x28, 0x36, 0xb0, 0xdf, 0x61, 0x7e, 0xbb, 0xdd, 0x81, + 0x6f, 0x8f, 0x8e, 0xb1, 0xa3, 0x0d, 0x48, 0x1f, 0x94, 0x02, 0x80, 0x56, 0xce, 0x4a, 0xd6, 0xfb, 0x18, 0x83, 0x56, + 0xd9, 0xea, 0x35, 0x2c, 0x69, 0xb7, 0xed, 0x3f, 0x68, 0xb5, 0x8f, 0x4f, 0x1b, 0x08, 0xa0, 0xa6, 0x7c, 0x91, 0x5f, + 0x40, 0x3f, 0xea, 0x9f, 0x68, 0x84, 0xaf, 0x7f, 0xd6, 0x50, 0x9f, 0x35, 0xda, 0x2b, 0x33, 0x04, 0xf5, 0xa9, 0x54, + 0xce, 0x45, 0x11, 0x65, 0xb4, 0xb1, 0x97, 0x85, 0x2b, 0x3a, 0xe2, 0xa2, 0x76, 0xee, 0x1f, 0x77, 0x8e, 0x3d, 0xd1, + 0x97, 0x4a, 0xe2, 0x87, 0x5e, 0x27, 0x1b, 0x45, 0x1a, 0x15, 0x22, 0x89, 0x1e, 0x1d, 0xb0, 0x9f, 0x98, 0x50, 0xbf, + 0xdd, 0x9c, 0x72, 0x5f, 0x0d, 0x80, 0x52, 0x71, 0x3a, 0xf5, 0x2c, 0xc8, 0x24, 0x0b, 0x10, 0x67, 0xe8, 0x5c, 0xf4, + 0xe0, 0xb8, 0xf7, 0xc0, 0x3f, 0x3e, 0xe9, 0x08, 0xa2, 0x97, 0x9c, 0x75, 0x6a, 0x69, 0x34, 0x74, 0xff, 0x98, 0xd2, + 0xb0, 0x69, 0xf8, 0xc1, 0x32, 0x32, 0xc5, 0x2e, 0x85, 0xc1, 0x36, 0x4c, 0x31, 0x8c, 0xb0, 0x11, 0xd4, 0x72, 0x4f, + 0x6a, 0xd9, 0xa7, 0x47, 0x50, 0xc0, 0x86, 0x9a, 0x1e, 0x05, 0x4d, 0xb4, 0x1c, 0x88, 0x1a, 0x1d, 0x4e, 0xad, 0xb7, + 0xef, 0x1f, 0x07, 0x1b, 0x03, 0x14, 0x8b, 0x66, 0x9f, 0x70, 0xc0, 0x32, 0x38, 0x3e, 0xcb, 0xa4, 0xbd, 0xc4, 0xda, + 0x1f, 0x33, 0x8e, 0x26, 0xb6, 0xc1, 0x59, 0xed, 0x53, 0x5a, 0xf7, 0x69, 0xda, 0x5b, 0x95, 0x4f, 0xa2, 0xec, 0x5b, + 0x54, 0xbe, 0x8b, 0x30, 0x82, 0x48, 0xd6, 0x77, 0x64, 0x7c, 0xf3, 0x7a, 0xee, 0x8f, 0x78, 0x44, 0x18, 0x44, 0xb2, + 0xbe, 0xb3, 0x52, 0x46, 0x50, 0x23, 0x0b, 0x5c, 0xd9, 0x07, 0x6a, 0xa9, 0x5b, 0x80, 0xcc, 0xd2, 0xa5, 0xc0, 0xb6, + 0x6d, 0xbd, 0x48, 0xbe, 0x57, 0xce, 0xd7, 0x5b, 0xd9, 0x80, 0x3e, 0xbe, 0xdc, 0x2c, 0xf7, 0xdb, 0x20, 0x9e, 0x18, + 0x17, 0x12, 0xc9, 0x92, 0xd3, 0x18, 0xf4, 0xc6, 0x1b, 0xd0, 0x0e, 0x17, 0xf0, 0x9f, 0x38, 0xc3, 0xfc, 0x2b, 0x9e, + 0xe3, 0x86, 0x37, 0x71, 0x94, 0x19, 0x1e, 0x40, 0xe5, 0xc0, 0xc0, 0x62, 0x4e, 0x9f, 0x4d, 0xb0, 0x34, 0x9d, 0xac, + 0xd6, 0xa5, 0x9f, 0xa8, 0x37, 0x7d, 0xf4, 0x5a, 0xa4, 0x58, 0x5a, 0xe6, 0x90, 0x56, 0xa8, 0xb8, 0x18, 0xdb, 0x89, + 0x94, 0xb0, 0x0e, 0xfa, 0x21, 0xa8, 0x1c, 0xd1, 0x42, 0x5d, 0xac, 0x67, 0x62, 0x92, 0xf0, 0x43, 0x9c, 0x69, 0x3c, + 0x8a, 0xee, 0xd8, 0x3c, 0xcc, 0x61, 0xa3, 0x4c, 0x15, 0x46, 0xb5, 0x42, 0x3d, 0xa7, 0x79, 0x3e, 0x53, 0xcf, 0x55, + 0xae, 0x9f, 0xf0, 0x3a, 0x16, 0xe9, 0xd1, 0x82, 0x9e, 0x5b, 0x22, 0x04, 0xd2, 0x80, 0xd7, 0x7b, 0x70, 0x35, 0x92, + 0x61, 0xea, 0x50, 0x23, 0xbc, 0x22, 0x85, 0x4a, 0xe9, 0x87, 0x57, 0x22, 0x64, 0x12, 0xbd, 0x62, 0xcc, 0x34, 0x0c, + 0x8b, 0x9c, 0xe3, 0x86, 0xc6, 0xb8, 0xe0, 0x65, 0xe9, 0x88, 0x58, 0x82, 0x90, 0xa2, 0x2e, 0x87, 0x54, 0x29, 0xa3, + 0xcc, 0xa1, 0x06, 0xeb, 0xa3, 0x15, 0xea, 0x30, 0x00, 0xa6, 0x0c, 0xc4, 0x4d, 0x31, 0x0a, 0x8c, 0x33, 0x7d, 0x0d, + 0x63, 0x30, 0x89, 0x57, 0x4c, 0xec, 0x61, 0xeb, 0x42, 0x72, 0x4b, 0xdb, 0x2e, 0x95, 0xc6, 0xab, 0xbb, 0x06, 0x54, + 0xd6, 0x46, 0x64, 0x0d, 0xd4, 0x74, 0xc8, 0x04, 0xee, 0xc0, 0xc2, 0x22, 0x28, 0x4d, 0xb0, 0xd4, 0x25, 0x83, 0xa5, + 0x9e, 0x86, 0xa3, 0x56, 0x6b, 0xb5, 0x62, 0x30, 0x56, 0x0c, 0xe4, 0xb2, 0xb5, 0x04, 0xe6, 0x97, 0x93, 0xfc, 0xda, + 0xca, 0x2d, 0x03, 0x3d, 0x4a, 0x9a, 0x22, 0xc7, 0x72, 0x82, 0x75, 0x56, 0xec, 0x5b, 0x52, 0x26, 0x08, 0x4f, 0x39, + 0xba, 0x41, 0x9e, 0x83, 0x16, 0x84, 0x31, 0xd4, 0xac, 0x2a, 0x57, 0x6c, 0x92, 0x0c, 0xf9, 0xf6, 0x3a, 0xd1, 0x8d, + 0xf9, 0x9f, 0xd5, 0xa8, 0x10, 0x48, 0x88, 0x7b, 0x84, 0x3a, 0x38, 0x89, 0xb7, 0xd8, 0x80, 0xad, 0x83, 0xcf, 0x6d, + 0xdc, 0x04, 0x6c, 0x04, 0xed, 0x0b, 0xe1, 0x32, 0xaf, 0xf2, 0x77, 0x32, 0x1c, 0x02, 0x28, 0x83, 0x2a, 0x32, 0xc2, + 0x12, 0x43, 0x06, 0xb4, 0x98, 0x88, 0x88, 0xd1, 0x62, 0x32, 0x50, 0x01, 0x60, 0x20, 0x86, 0xa7, 0x68, 0xad, 0x74, + 0x6c, 0xb3, 0x85, 0xbe, 0x52, 0xd3, 0x2c, 0x82, 0x91, 0xd4, 0x0e, 0xab, 0xb0, 0xb2, 0x76, 0x0f, 0x41, 0x20, 0x6e, + 0xfc, 0x74, 0xf1, 0xf6, 0x8d, 0x8c, 0x5c, 0x9d, 0x8c, 0xf0, 0xc8, 0xa6, 0x34, 0x8d, 0x2d, 0x44, 0x88, 0x79, 0x63, + 0x28, 0x15, 0xca, 0x29, 0x05, 0xfb, 0xcc, 0xaa, 0xd4, 0x17, 0x9b, 0x17, 0xa0, 0x81, 0x4c, 0x23, 0xb1, 0x63, 0xc4, + 0x16, 0xa5, 0xbc, 0x7c, 0x9e, 0xee, 0xb7, 0x31, 0x83, 0xfc, 0xb0, 0xbe, 0x15, 0x01, 0x9d, 0xc1, 0x57, 0xb4, 0x06, + 0xd0, 0xc7, 0xaa, 0xe3, 0xbc, 0x08, 0x65, 0xfc, 0x7f, 0x8b, 0xbc, 0xbc, 0x17, 0xd4, 0xc5, 0x71, 0x1a, 0x09, 0xe1, + 0x27, 0x2f, 0x8c, 0x8d, 0x2b, 0xa1, 0xfb, 0x23, 0xc3, 0x96, 0x54, 0x2f, 0x9c, 0x96, 0x53, 0xbf, 0x7b, 0x97, 0x4c, + 0x09, 0x2a, 0x76, 0x2c, 0x68, 0x5d, 0xa8, 0x7a, 0xe8, 0x6b, 0xfe, 0x12, 0x54, 0x4d, 0xd4, 0xde, 0xf3, 0x79, 0x5b, + 0xd1, 0xd9, 0xd4, 0x98, 0x13, 0xf5, 0x96, 0x09, 0x9e, 0x70, 0x14, 0x17, 0xe9, 0x78, 0xcc, 0x4a, 0xe4, 0xda, 0x7a, + 0xa8, 0x72, 0xbd, 0xae, 0x9b, 0x9e, 0xb5, 0x79, 0xf3, 0xe8, 0xf6, 0x36, 0x3d, 0x6f, 0xf3, 0xf6, 0xb1, 0xb8, 0x76, + 0xcf, 0x29, 0x63, 0xa4, 0xb9, 0xc9, 0x28, 0x89, 0x1d, 0xb4, 0xce, 0xce, 0x62, 0x0a, 0xa7, 0xa0, 0xb4, 0xe9, 0x70, + 0x5e, 0x99, 0xb6, 0x72, 0x74, 0x2e, 0x0d, 0x85, 0x1d, 0xbf, 0xf0, 0x40, 0x98, 0xdb, 0x3c, 0x2a, 0xdd, 0x6c, 0xef, + 0x5b, 0x1b, 0x2e, 0x85, 0xc7, 0x3a, 0x18, 0xf2, 0x00, 0xed, 0xbb, 0xcb, 0x0c, 0x5d, 0x83, 0x10, 0x95, 0x4b, 0x54, + 0x69, 0x93, 0xe9, 0x7c, 0xfa, 0xbc, 0x88, 0x68, 0x1a, 0x9e, 0x26, 0xe3, 0xa4, 0x2a, 0x83, 0x08, 0xb5, 0xdb, 0x6d, + 0xe9, 0xab, 0xed, 0x1a, 0x54, 0x5c, 0x8b, 0xbf, 0xeb, 0x83, 0xbc, 0xf3, 0xb5, 0x94, 0x13, 0xe7, 0x31, 0x9a, 0xd9, + 0x8c, 0xc5, 0xc0, 0xdf, 0xd3, 0x7c, 0x1c, 0x15, 0x49, 0x35, 0x99, 0xfe, 0xa3, 0xd9, 0xe1, 0x97, 0x55, 0x9f, 0x36, + 0xac, 0x10, 0x24, 0x51, 0x36, 0x04, 0x7d, 0xec, 0xe0, 0xfb, 0xfb, 0x49, 0xea, 0x4c, 0x78, 0x9b, 0x75, 0xd8, 0x21, + 0x3b, 0x06, 0x09, 0x95, 0xb5, 0x8f, 0x71, 0xeb, 0x3e, 0x4e, 0xe7, 0x40, 0x8b, 0x5c, 0xbc, 0x7f, 0xad, 0x3a, 0xf7, + 0x4f, 0xf7, 0xcd, 0xad, 0x03, 0x85, 0x2f, 0xd1, 0xc5, 0x0a, 0x7e, 0x2f, 0xa3, 0x06, 0x3a, 0x8e, 0x1d, 0xb2, 0x6e, + 0x66, 0x9b, 0x4e, 0xb4, 0x7d, 0xe1, 0xfc, 0xb0, 0x87, 0xfe, 0xdc, 0x62, 0x66, 0x9b, 0xe8, 0xf6, 0x2d, 0x1e, 0x03, + 0xf3, 0xd8, 0xac, 0x34, 0x62, 0x28, 0xe8, 0x19, 0xf4, 0xf0, 0x40, 0x12, 0xde, 0xdb, 0x43, 0xaf, 0x23, 0x6b, 0x34, + 0x89, 0x88, 0x7e, 0x90, 0x34, 0x6b, 0x69, 0x18, 0xac, 0x00, 0x9d, 0x3b, 0xdf, 0x25, 0xe1, 0x52, 0xc0, 0xb6, 0x42, + 0x2a, 0xcc, 0x0b, 0x3b, 0xae, 0x9e, 0x4d, 0x2a, 0x48, 0x89, 0x46, 0x16, 0x26, 0x83, 0xa1, 0x00, 0x95, 0xc8, 0x47, + 0x23, 0xc8, 0x42, 0xd6, 0x51, 0xf0, 0x6f, 0xf0, 0x35, 0x71, 0x91, 0x01, 0x1f, 0x27, 0xd9, 0xa3, 0xea, 0x0f, 0x5e, + 0xe4, 0xf4, 0x4a, 0x16, 0x0c, 0x20, 0x62, 0x38, 0x8b, 0x0e, 0x8b, 0xd3, 0x64, 0x86, 0x9f, 0x8e, 0x0b, 0x3c, 0xf4, + 0x83, 0xbf, 0xc9, 0x30, 0xb0, 0xeb, 0x44, 0xf2, 0xf5, 0xab, 0x88, 0xe9, 0xc2, 0x86, 0x45, 0x74, 0xfd, 0x36, 0x7b, + 0x82, 0x2b, 0xea, 0x51, 0xc1, 0x23, 0xcc, 0xc6, 0xa4, 0x0f, 0x58, 0x15, 0xbe, 0x60, 0x9d, 0xaf, 0x08, 0x70, 0xc1, + 0x29, 0xbd, 0x88, 0x0f, 0x55, 0xec, 0x46, 0x5f, 0xd7, 0x45, 0x99, 0xc4, 0xa5, 0x4d, 0xa6, 0x88, 0x42, 0xa5, 0x87, + 0xb0, 0x62, 0x32, 0xea, 0x89, 0xdd, 0x99, 0x61, 0x54, 0x4e, 0x82, 0xcb, 0x3e, 0xfd, 0xbe, 0x15, 0x25, 0x5b, 0x4c, + 0x46, 0x9c, 0x59, 0x31, 0xba, 0x38, 0xd5, 0x2a, 0xdf, 0x66, 0xb8, 0x0f, 0x8b, 0xb7, 0x77, 0xd6, 0xc8, 0xe7, 0x91, + 0x22, 0x9b, 0x47, 0xab, 0x15, 0x75, 0x04, 0xc8, 0x3a, 0x2c, 0x94, 0xf7, 0x6a, 0xd9, 0xb4, 0x70, 0x79, 0xe8, 0xb7, + 0x63, 0x78, 0x4d, 0xf0, 0x32, 0x52, 0x55, 0x1f, 0x88, 0x2f, 0xf9, 0x57, 0x09, 0x92, 0x96, 0x95, 0x22, 0x86, 0x63, + 0x35, 0x76, 0xc8, 0xac, 0x9e, 0x23, 0x3d, 0xbf, 0xf8, 0x2a, 0x60, 0xad, 0x9e, 0xdf, 0xe9, 0x82, 0x14, 0x4f, 0x47, + 0x0f, 0x28, 0x56, 0xfc, 0xf3, 0x5d, 0xe2, 0x1b, 0x54, 0x90, 0xb7, 0x78, 0xe1, 0x9a, 0x16, 0x81, 0x3e, 0xa7, 0xd1, + 0x17, 0xf1, 0x20, 0x62, 0xf8, 0x68, 0x2b, 0x5b, 0x5c, 0xe4, 0x65, 0xf9, 0x48, 0xa4, 0x09, 0xd6, 0x83, 0x2c, 0xf2, + 0x15, 0x36, 0x81, 0xb2, 0xfe, 0x10, 0x18, 0x39, 0x21, 0x82, 0x7c, 0x96, 0xfd, 0xa6, 0xb3, 0x02, 0x78, 0xda, 0x61, + 0xc7, 0xfc, 0x69, 0xa1, 0xf8, 0xeb, 0xe8, 0x92, 0xaa, 0xbf, 0x1d, 0xc1, 0xfe, 0x41, 0xd4, 0x02, 0xf9, 0x4e, 0xe0, + 0xa1, 0x2f, 0xd1, 0x49, 0x8b, 0x66, 0xfa, 0x71, 0xa3, 0x24, 0x62, 0x58, 0xbd, 0xa0, 0x2d, 0xc6, 0x6d, 0x14, 0x17, + 0x99, 0xff, 0xc1, 0x07, 0x84, 0x05, 0x77, 0xf5, 0xc4, 0x88, 0x2c, 0x6a, 0xf9, 0xd4, 0xe5, 0xaf, 0xba, 0xc4, 0xb5, + 0x8b, 0xf7, 0xa6, 0x98, 0x9b, 0x42, 0x07, 0x14, 0xa5, 0xd9, 0x81, 0xe5, 0xbb, 0x96, 0x11, 0x8d, 0x10, 0xf0, 0x9e, + 0x14, 0xbf, 0xd4, 0xf4, 0x29, 0x71, 0x8c, 0x2c, 0x8f, 0xd0, 0x13, 0x4b, 0xb8, 0x53, 0xd2, 0x9c, 0x18, 0xc8, 0x53, + 0xc0, 0x66, 0x55, 0x18, 0xe1, 0xf8, 0x78, 0x23, 0x15, 0xf1, 0xdd, 0x59, 0x6d, 0x19, 0xc0, 0x9a, 0xbc, 0x25, 0x06, + 0xb5, 0xad, 0xa0, 0x9a, 0xa0, 0xe5, 0x36, 0xa1, 0x72, 0x6d, 0x82, 0x9d, 0xf5, 0x81, 0x6c, 0xed, 0xfa, 0xda, 0x37, + 0x5a, 0xf4, 0x78, 0xb9, 0xdd, 0xe9, 0x2b, 0xcf, 0xb6, 0x25, 0x1b, 0xda, 0x36, 0xe0, 0xe6, 0xb6, 0xe0, 0xca, 0xb8, + 0x3a, 0x99, 0x6d, 0x5e, 0xe3, 0x69, 0xa3, 0xfd, 0x92, 0xc4, 0xde, 0x16, 0xb7, 0xb7, 0xc2, 0x1b, 0x0d, 0xbd, 0x11, + 0x8a, 0xfe, 0x58, 0x59, 0xbf, 0x9b, 0x84, 0xb8, 0xbd, 0x00, 0x82, 0x0b, 0xfe, 0x6c, 0xeb, 0xc3, 0x36, 0xd7, 0xa5, + 0x8f, 0xc9, 0x9a, 0xbc, 0x62, 0x05, 0x2c, 0x96, 0x7e, 0x90, 0xe8, 0xdd, 0x25, 0x3b, 0x8d, 0xec, 0x44, 0xbd, 0xe1, + 0x75, 0xd9, 0x95, 0x3a, 0x72, 0x30, 0x8d, 0xef, 0xb8, 0x62, 0x4f, 0x8b, 0x96, 0xb5, 0x68, 0xf7, 0x6b, 0x1a, 0x4c, + 0x28, 0x16, 0xa5, 0x3c, 0xb8, 0x05, 0xda, 0x93, 0x23, 0x8b, 0x19, 0xf4, 0xbf, 0xab, 0x48, 0x2c, 0x32, 0xff, 0xeb, + 0xe4, 0xe4, 0x44, 0xa6, 0x48, 0x9f, 0xbf, 0x92, 0x4e, 0xc0, 0x51, 0x02, 0xff, 0x96, 0xc4, 0x9c, 0x6c, 0xc8, 0xef, + 0xb1, 0x2b, 0x61, 0x16, 0x9e, 0x67, 0x52, 0x5c, 0xc2, 0xdb, 0x1e, 0x91, 0xf2, 0xa0, 0xf8, 0xf7, 0x74, 0xad, 0x9c, + 0xba, 0x2e, 0x4a, 0x85, 0x53, 0xd6, 0x15, 0xf2, 0x6f, 0xf4, 0x7a, 0x29, 0xdc, 0x64, 0xf0, 0x86, 0x04, 0x58, 0x7a, + 0xf4, 0x4c, 0x22, 0xad, 0xf4, 0x94, 0x01, 0x65, 0x2e, 0x9f, 0xc7, 0x13, 0x61, 0xf9, 0x97, 0x2f, 0x54, 0x56, 0x5e, + 0x35, 0x84, 0x91, 0xbb, 0x80, 0x03, 0x8a, 0xa8, 0xa0, 0xce, 0x0f, 0x3a, 0x00, 0xe8, 0xce, 0x1b, 0x3e, 0xe7, 0x3f, + 0xb0, 0x1d, 0x93, 0x82, 0x2f, 0x8f, 0x8a, 0x24, 0x4a, 0xe1, 0xc1, 0x04, 0x02, 0xc5, 0xa3, 0x90, 0x14, 0x4b, 0x93, + 0xd1, 0xb5, 0xda, 0x40, 0x02, 0x91, 0x82, 0xa6, 0x0e, 0x31, 0xb2, 0x17, 0x32, 0x46, 0xa3, 0xdf, 0x61, 0x32, 0x38, + 0x98, 0x88, 0x08, 0x2b, 0x02, 0xa9, 0x63, 0xdc, 0x3a, 0x3d, 0x1a, 0x7a, 0x7b, 0xbc, 0x36, 0x21, 0x64, 0x4c, 0x0e, + 0xcf, 0x41, 0xf7, 0xdd, 0x98, 0x2c, 0xcf, 0xfe, 0xcc, 0x9a, 0xa0, 0xda, 0x27, 0x26, 0xdd, 0x2e, 0xbe, 0x59, 0x30, + 0xb6, 0x8a, 0xd0, 0xd2, 0x7b, 0x74, 0x93, 0x80, 0x08, 0x79, 0xe3, 0xa8, 0x24, 0xbc, 0x19, 0x9d, 0x30, 0x35, 0x5c, + 0x4e, 0xf3, 0x21, 0x17, 0x84, 0x9e, 0x97, 0x00, 0x51, 0xca, 0x2b, 0x31, 0x92, 0x18, 0x70, 0x1a, 0x29, 0x1a, 0xbd, + 0xcc, 0x94, 0x92, 0x82, 0x7c, 0x95, 0xaa, 0x98, 0x46, 0x09, 0x45, 0x17, 0x7b, 0x54, 0xce, 0xa0, 0xac, 0x40, 0x80, + 0x5d, 0x89, 0x86, 0x79, 0xf6, 0x82, 0x40, 0x41, 0x57, 0x7a, 0xcb, 0x94, 0x27, 0x38, 0x79, 0x56, 0x0a, 0x12, 0x35, + 0x58, 0x05, 0xfa, 0x9b, 0x59, 0x3a, 0x07, 0x31, 0xc3, 0x20, 0x03, 0x74, 0x66, 0x06, 0x98, 0x0f, 0xba, 0x9d, 0x2e, + 0x42, 0x74, 0xa8, 0x86, 0xef, 0x82, 0x84, 0xe9, 0x6f, 0x88, 0x4d, 0xc1, 0x2c, 0xe9, 0x2f, 0x50, 0xb8, 0x78, 0x44, + 0x0a, 0xa2, 0x0a, 0x2f, 0xfb, 0x36, 0xfb, 0x90, 0xcf, 0x4c, 0xbe, 0xa2, 0x71, 0x2a, 0x70, 0xbd, 0xf4, 0x1b, 0xf6, + 0x56, 0x74, 0xe9, 0x95, 0xb5, 0x7c, 0x61, 0x3d, 0xab, 0x9b, 0x46, 0xbc, 0x15, 0x5d, 0x9b, 0xa5, 0xb3, 0x06, 0x5c, + 0xdf, 0x11, 0x61, 0xea, 0xab, 0x7f, 0x9a, 0x67, 0xe2, 0x43, 0x84, 0x0a, 0x70, 0xaf, 0x8d, 0xfc, 0x97, 0x95, 0xc8, + 0x17, 0x7c, 0x48, 0x87, 0x07, 0x52, 0x64, 0xc8, 0xb4, 0xc0, 0x0a, 0xfd, 0x92, 0xa1, 0x88, 0xef, 0x50, 0xfc, 0xe0, + 0x6d, 0x1b, 0xc8, 0xa0, 0x67, 0xbb, 0x39, 0x5b, 0x5e, 0x46, 0xfd, 0x40, 0xe8, 0x43, 0x41, 0x8e, 0xbe, 0x3d, 0xd7, + 0x20, 0x5f, 0xab, 0x40, 0xc5, 0x0c, 0x12, 0x82, 0x65, 0x3d, 0x98, 0xb1, 0x08, 0xa7, 0xac, 0xdc, 0x39, 0x29, 0xfa, + 0x6f, 0xd4, 0x67, 0x69, 0x88, 0xbe, 0xc7, 0x4a, 0x3c, 0x7d, 0xe7, 0xc6, 0x5e, 0xaf, 0x2e, 0xe1, 0xc3, 0x0c, 0xa4, + 0x20, 0xc2, 0x07, 0x8f, 0x5c, 0xdc, 0xc0, 0x02, 0x9d, 0x1a, 0xaa, 0x89, 0x05, 0xa9, 0xc2, 0x13, 0xa9, 0x31, 0x69, + 0xac, 0x72, 0x23, 0x10, 0x92, 0xed, 0xdd, 0x71, 0xae, 0xc2, 0x81, 0x93, 0xf4, 0xfa, 0x9c, 0x94, 0xc3, 0x69, 0xec, + 0xd6, 0xf8, 0x4c, 0x02, 0xac, 0x34, 0xa9, 0x71, 0x4c, 0xf4, 0xf6, 0xb6, 0xb9, 0x0a, 0xda, 0xd1, 0x90, 0xab, 0x08, + 0x1a, 0x82, 0x31, 0x13, 0xf0, 0x34, 0xb3, 0xcd, 0xda, 0x2c, 0x9c, 0x07, 0xa5, 0xdb, 0x7a, 0x0b, 0x6a, 0x4d, 0xad, + 0x1b, 0x51, 0x40, 0xc4, 0xbb, 0x1c, 0xc6, 0x6c, 0x1e, 0xb3, 0x71, 0xdc, 0xb7, 0xd9, 0x8d, 0x65, 0xb3, 0x45, 0x97, + 0xeb, 0x67, 0xd2, 0x8d, 0xd0, 0x13, 0x8f, 0x82, 0xfe, 0x78, 0x3d, 0x44, 0xf8, 0x00, 0xb3, 0x90, 0x96, 0xf4, 0xe4, + 0x6f, 0x43, 0xdc, 0x16, 0xee, 0x35, 0x20, 0x43, 0x90, 0x91, 0x9e, 0x7a, 0xd0, 0x59, 0xa2, 0xb6, 0x86, 0x33, 0xbb, + 0xd9, 0x01, 0xad, 0x55, 0xd6, 0x67, 0xf8, 0xcb, 0x40, 0xbb, 0xed, 0x20, 0xa2, 0x08, 0xe7, 0xa4, 0xca, 0x9a, 0xa3, + 0x1c, 0xf8, 0x95, 0x48, 0x09, 0x13, 0x7f, 0xca, 0xa3, 0x72, 0x5e, 0xd0, 0x05, 0x7a, 0x6e, 0xe9, 0xf9, 0x24, 0xef, + 0x32, 0xe9, 0x23, 0x8a, 0xf7, 0xd5, 0xe5, 0xe8, 0xb8, 0x01, 0x7a, 0x79, 0x5e, 0x53, 0xb9, 0xaf, 0xb4, 0x03, 0x8d, + 0xb7, 0x04, 0x9d, 0x9d, 0x54, 0x7e, 0xb1, 0x5d, 0x9a, 0x89, 0x2b, 0xfa, 0xc4, 0x0f, 0x9d, 0x05, 0xc4, 0x9d, 0x37, + 0xd0, 0xdd, 0x06, 0xd1, 0x18, 0xc5, 0x58, 0x8c, 0x43, 0xb8, 0x91, 0x70, 0x7b, 0x7b, 0xd9, 0xef, 0x66, 0x44, 0x9e, + 0xe5, 0x05, 0x82, 0xba, 0xa2, 0xed, 0x15, 0xe0, 0x56, 0xb7, 0xa0, 0xe6, 0x15, 0xd9, 0x7e, 0x22, 0xba, 0xe3, 0x2c, + 0x91, 0x49, 0xf2, 0x9a, 0x12, 0x73, 0x13, 0x79, 0xcd, 0x03, 0x9c, 0x42, 0x57, 0xb1, 0x21, 0x9b, 0x0b, 0x1f, 0x4e, + 0xba, 0xd0, 0x2a, 0xa2, 0x7b, 0xac, 0xf0, 0xfa, 0x6c, 0xe0, 0xeb, 0x31, 0xe8, 0x00, 0x2a, 0xdc, 0xf9, 0xee, 0x7b, + 0x77, 0xe8, 0x79, 0xb0, 0xb2, 0x10, 0xa6, 0xe2, 0xc8, 0xf6, 0xd0, 0xf8, 0xed, 0x53, 0x26, 0x09, 0x0c, 0xe4, 0xc4, + 0x3c, 0xb4, 0x9d, 0x98, 0x53, 0xa8, 0x70, 0x1e, 0x0e, 0xd1, 0x89, 0x79, 0x6e, 0xd5, 0x36, 0x17, 0x97, 0x9d, 0x5a, + 0xf5, 0xcd, 0x41, 0xf9, 0x4c, 0x90, 0xa6, 0x55, 0x78, 0xad, 0x89, 0xb9, 0x56, 0x5e, 0x5d, 0xb2, 0x75, 0xd0, 0x41, + 0x43, 0xc2, 0xe8, 0x5c, 0x0d, 0x42, 0x1c, 0x0d, 0x17, 0xfd, 0x1e, 0x51, 0xbf, 0xa5, 0x6f, 0x43, 0x79, 0x99, 0x43, + 0xdf, 0xfb, 0xdd, 0x5c, 0x79, 0x49, 0xb4, 0xd8, 0xc8, 0x5c, 0x84, 0x62, 0x26, 0xef, 0x5b, 0xb5, 0xbe, 0xe5, 0x9d, + 0xa8, 0xfb, 0x55, 0xd7, 0xf9, 0x31, 0x4a, 0x40, 0xbb, 0xb8, 0x3f, 0x64, 0xe2, 0x83, 0x1d, 0x74, 0x30, 0x0a, 0x5a, + 0xd0, 0xaa, 0x29, 0xa4, 0xc2, 0xcd, 0xce, 0xad, 0x9a, 0xa5, 0xb7, 0x9f, 0x61, 0x18, 0xb2, 0xd2, 0x34, 0x77, 0x53, + 0x5a, 0xa6, 0xa1, 0x04, 0xb9, 0xfe, 0x13, 0xe1, 0xc4, 0xea, 0x3a, 0x9d, 0xa1, 0x43, 0x4e, 0x92, 0x62, 0xfa, 0xf0, + 0xee, 0x23, 0xf4, 0x12, 0x50, 0x31, 0xa8, 0x29, 0x89, 0x1c, 0x09, 0xde, 0xc3, 0x9c, 0x93, 0x1c, 0x92, 0x48, 0x04, + 0x4d, 0x7c, 0x11, 0x94, 0x56, 0x7e, 0x24, 0x20, 0x67, 0x9a, 0x5c, 0x58, 0xe8, 0x79, 0xa3, 0x9f, 0x19, 0x49, 0x64, + 0x56, 0xc7, 0xe2, 0x8d, 0x75, 0x82, 0x27, 0xf2, 0x99, 0x41, 0x10, 0x35, 0x15, 0x20, 0xb4, 0x60, 0xbc, 0xee, 0x0b, + 0x5c, 0xa0, 0x6c, 0x86, 0x07, 0x04, 0xa5, 0x06, 0xc7, 0xf0, 0x74, 0x99, 0xb0, 0x24, 0x13, 0x6e, 0x4d, 0x43, 0x77, + 0x76, 0x7b, 0xdb, 0xf2, 0xf6, 0x7f, 0xa3, 0x2b, 0xa9, 0x47, 0xda, 0xe0, 0x3e, 0x32, 0x06, 0x77, 0xf4, 0x04, 0x0c, + 0x47, 0x96, 0xad, 0x9d, 0xe5, 0xb6, 0x19, 0x1d, 0xa3, 0xa5, 0xbf, 0xc4, 0xd8, 0xd9, 0x92, 0x2d, 0xa1, 0x9d, 0x7d, + 0xa3, 0x80, 0xb0, 0xb5, 0xeb, 0x12, 0x1e, 0x29, 0xee, 0x6a, 0x11, 0x90, 0x09, 0x91, 0xce, 0xfc, 0xd1, 0xad, 0x16, + 0x89, 0x2f, 0xcf, 0x73, 0x4d, 0x49, 0x74, 0x07, 0xb6, 0x47, 0xd5, 0xbb, 0x23, 0xd6, 0x1c, 0x09, 0x70, 0xc2, 0x94, + 0xc2, 0xa3, 0x80, 0x28, 0x3c, 0xcb, 0x54, 0x36, 0xd2, 0x40, 0xb6, 0xd1, 0x53, 0x3a, 0x48, 0xa1, 0x28, 0xed, 0x0a, + 0x2b, 0xd2, 0x18, 0xe8, 0xda, 0xf8, 0x2c, 0x6c, 0x41, 0x2f, 0xca, 0xeb, 0xa4, 0x02, 0xd2, 0x9d, 0xf8, 0x64, 0x18, + 0x78, 0x07, 0xa8, 0x01, 0x3d, 0x1a, 0x79, 0x4b, 0x60, 0x3f, 0xd1, 0x3c, 0xad, 0x82, 0x12, 0x88, 0x99, 0x08, 0xdc, + 0xcb, 0x45, 0x24, 0x38, 0x68, 0x6e, 0x4c, 0xf2, 0xe5, 0x27, 0x72, 0x07, 0x29, 0x62, 0x4a, 0x1e, 0x53, 0x02, 0x34, + 0x1b, 0xa7, 0x79, 0xc9, 0x45, 0x35, 0x5d, 0xe1, 0x5b, 0x8e, 0x21, 0xc9, 0x1d, 0x00, 0x1c, 0x79, 0x51, 0x3a, 0xc1, + 0x24, 0x2c, 0x7b, 0x50, 0x49, 0x30, 0x86, 0xc2, 0x28, 0xe9, 0x7d, 0xc8, 0x5d, 0xde, 0xd0, 0xbb, 0xa2, 0x53, 0x6f, + 0x7f, 0xc2, 0x32, 0xb3, 0x89, 0x0a, 0xef, 0x63, 0x8f, 0x4d, 0x1b, 0xe1, 0xa4, 0xc4, 0x13, 0xc3, 0xc0, 0x11, 0xff, + 0x6f, 0x94, 0xbf, 0xb3, 0xdb, 0x18, 0x62, 0xfa, 0x3d, 0xae, 0x14, 0x3e, 0x74, 0x82, 0x34, 0xc4, 0x53, 0x8b, 0xed, + 0x13, 0x16, 0x87, 0xe3, 0x66, 0xaa, 0x02, 0xee, 0x51, 0x2d, 0x8d, 0x9b, 0xca, 0xdb, 0x8f, 0xd9, 0x70, 0x3d, 0xc9, + 0xa5, 0xb1, 0x36, 0xd3, 0x20, 0x46, 0xfe, 0x6e, 0x7a, 0x21, 0xcb, 0xcf, 0xd7, 0x93, 0xec, 0xf2, 0x12, 0xb8, 0xcd, + 0x21, 0xf4, 0x37, 0x02, 0x04, 0x9f, 0x36, 0xdf, 0xc0, 0x7f, 0x1f, 0x75, 0x46, 0x63, 0x0e, 0x19, 0x05, 0x65, 0x7c, + 0x64, 0x53, 0x93, 0x0c, 0xe5, 0x1b, 0x54, 0x1e, 0xc0, 0x60, 0x4a, 0x37, 0xa1, 0x74, 0x83, 0x4a, 0x37, 0xa0, 0x74, + 0xe3, 0xcd, 0xbf, 0x19, 0xb4, 0x13, 0x20, 0xba, 0xcc, 0x80, 0xe0, 0x88, 0x2e, 0x5e, 0xfc, 0xf2, 0xfe, 0x43, 0xfb, + 0xaa, 0xb3, 0x3f, 0x66, 0x6a, 0xfe, 0x62, 0xc2, 0x31, 0x58, 0xe5, 0xbc, 0x89, 0x10, 0x8d, 0x59, 0x07, 0x20, 0xdb, + 0xd9, 0x8f, 0x65, 0x55, 0x2b, 0x98, 0x83, 0x9b, 0xca, 0x86, 0x22, 0xd4, 0x69, 0xc3, 0x47, 0x0d, 0x36, 0x18, 0x7b, + 0x35, 0x50, 0xc2, 0x84, 0xd4, 0x40, 0x85, 0xef, 0xf3, 0xda, 0xbb, 0xf9, 0xce, 0x60, 0x90, 0x80, 0x92, 0x67, 0xcf, + 0x39, 0x81, 0xa7, 0x96, 0x42, 0x90, 0xb1, 0x53, 0x04, 0x50, 0xbe, 0x03, 0x0a, 0x32, 0x9e, 0x50, 0xd7, 0xad, 0xe1, + 0x50, 0xe2, 0xff, 0xe3, 0xc1, 0xe8, 0xae, 0xeb, 0x25, 0xb3, 0x31, 0x3c, 0x39, 0x18, 0xbb, 0xfb, 0x28, 0x63, 0xfd, + 0x7f, 0xdb, 0x51, 0x46, 0x20, 0x65, 0xff, 0x9f, 0xf6, 0xce, 0x06, 0x23, 0x66, 0x39, 0x41, 0x21, 0x11, 0xff, 0xfb, + 0xdd, 0xb2, 0x9a, 0x2f, 0x36, 0x9a, 0x2f, 0xa8, 0x79, 0xbb, 0x6a, 0x32, 0xe5, 0x04, 0xe6, 0x23, 0x41, 0xfe, 0xeb, + 0x74, 0x6b, 0x03, 0x34, 0x59, 0x8d, 0x9e, 0x8d, 0xed, 0x0a, 0x77, 0xdb, 0xc1, 0x16, 0x64, 0x5e, 0x25, 0xe2, 0x86, + 0x54, 0x64, 0xbe, 0xd6, 0x9e, 0xea, 0x79, 0x0b, 0x4f, 0x6f, 0x96, 0x64, 0xaf, 0x74, 0x6d, 0xcf, 0xc1, 0x68, 0xdd, + 0x95, 0x9b, 0x9c, 0xa5, 0xfd, 0x63, 0x46, 0x47, 0x11, 0xf1, 0xa3, 0x9b, 0x73, 0x34, 0x8a, 0x8f, 0xaa, 0x26, 0xa7, + 0xb7, 0x33, 0xe0, 0xa9, 0x24, 0xf0, 0xd2, 0xeb, 0x02, 0x32, 0xab, 0x7c, 0x26, 0xf2, 0x16, 0x67, 0xd8, 0x26, 0x5a, + 0x58, 0x1b, 0x96, 0xdf, 0x7e, 0x22, 0xfd, 0x20, 0x2d, 0x26, 0x68, 0x33, 0x20, 0x49, 0x5a, 0x44, 0x1b, 0x8c, 0x6a, + 0x63, 0xb2, 0x89, 0xa6, 0x4e, 0x14, 0xb5, 0x36, 0x29, 0x57, 0xac, 0xe1, 0x64, 0x66, 0xcb, 0x14, 0x59, 0x21, 0xec, + 0xe3, 0x5b, 0xc4, 0x8d, 0x6f, 0x35, 0x41, 0xa2, 0x6e, 0x64, 0xd2, 0xd0, 0xf7, 0x6f, 0x40, 0xac, 0x5e, 0xd0, 0x29, + 0xc6, 0x92, 0xce, 0xfb, 0x50, 0x5c, 0x7e, 0xc7, 0x68, 0x72, 0xe8, 0xd9, 0xdf, 0x80, 0x62, 0xe8, 0x9e, 0xa8, 0x3f, + 0xcb, 0xa1, 0x67, 0x0b, 0x6b, 0x12, 0x73, 0xaa, 0x64, 0x45, 0x02, 0x28, 0x55, 0x23, 0xcc, 0x83, 0xbb, 0x18, 0xe8, + 0xa1, 0xa7, 0x4b, 0x55, 0xb2, 0xb1, 0xa0, 0xd6, 0x7c, 0x45, 0xcd, 0xaf, 0x77, 0xc8, 0x0c, 0xe3, 0xda, 0x92, 0x9a, + 0xfe, 0xdd, 0x20, 0x00, 0xbe, 0x7f, 0x27, 0xbc, 0x78, 0x32, 0x2f, 0x08, 0xd3, 0xb2, 0x1e, 0x48, 0x6a, 0xb3, 0x36, + 0xba, 0xea, 0xc5, 0xb3, 0xce, 0x0d, 0x93, 0xef, 0x0b, 0xf1, 0xbe, 0x80, 0x77, 0x4e, 0x19, 0x01, 0xa7, 0x62, 0xea, + 0x7d, 0x21, 0xde, 0x17, 0x6c, 0xb3, 0x33, 0x5f, 0xd5, 0x86, 0xa2, 0x16, 0x67, 0x20, 0x15, 0x31, 0xc0, 0x48, 0x37, + 0xb5, 0x2c, 0x0a, 0x02, 0x5b, 0x4b, 0xc0, 0x38, 0x9f, 0xcf, 0x5c, 0x23, 0xab, 0x79, 0x28, 0x7d, 0xaa, 0xa3, 0xed, + 0x26, 0x15, 0x65, 0x4c, 0xb4, 0x88, 0x50, 0x6c, 0x1b, 0xba, 0xdd, 0x0c, 0xa5, 0xbc, 0xb0, 0xd2, 0x76, 0x12, 0x1f, + 0x65, 0x55, 0x32, 0x79, 0x53, 0x11, 0xfd, 0x16, 0x5a, 0x39, 0xaa, 0xd8, 0x63, 0x58, 0x34, 0x08, 0x2b, 0x5d, 0x52, + 0x25, 0x84, 0xf5, 0x7c, 0xfb, 0xa4, 0xe6, 0x3a, 0xf2, 0x88, 0x7b, 0x7e, 0xbf, 0xf2, 0x6a, 0x02, 0x52, 0xf5, 0x78, + 0x82, 0x67, 0x68, 0x51, 0x64, 0x28, 0xe8, 0x3b, 0xe3, 0xeb, 0x5f, 0xd3, 0xdc, 0x32, 0xa4, 0x70, 0x55, 0x33, 0xf7, + 0x41, 0x6d, 0x9d, 0x47, 0x29, 0x79, 0x92, 0x82, 0x6c, 0xf9, 0x38, 0xbf, 0x79, 0x85, 0xd8, 0x1d, 0x85, 0x55, 0x63, + 0x4b, 0xde, 0x7b, 0x5c, 0x01, 0x20, 0x7f, 0xf0, 0x6d, 0x1f, 0x3e, 0x2a, 0xd1, 0xe6, 0x0f, 0xea, 0x3d, 0xdf, 0xf6, + 0xe9, 0x53, 0x2e, 0xb2, 0x81, 0x7f, 0xd7, 0xbb, 0xdb, 0x73, 0xe3, 0x46, 0x0a, 0x28, 0x1c, 0xa4, 0x5d, 0x45, 0x0c, + 0x04, 0x40, 0x2d, 0xe0, 0x6e, 0x2c, 0x4f, 0xbd, 0x77, 0x13, 0xa2, 0xdd, 0x25, 0xce, 0xc5, 0x76, 0x39, 0xa5, 0xdc, + 0xde, 0x76, 0x0c, 0x15, 0x2c, 0xd8, 0xc4, 0x5a, 0x0b, 0x91, 0x78, 0xdb, 0x42, 0x71, 0x5e, 0xc7, 0xeb, 0xae, 0xe7, + 0xba, 0xed, 0xee, 0x96, 0x49, 0x66, 0x22, 0xed, 0xfd, 0x16, 0x22, 0x21, 0x24, 0xe1, 0xca, 0x92, 0x84, 0xcd, 0xd7, + 0x16, 0x01, 0xba, 0xb2, 0x54, 0x6e, 0x10, 0xe7, 0x97, 0x2b, 0xe3, 0x74, 0x6f, 0x9d, 0xbb, 0x8d, 0x40, 0xa7, 0x2b, + 0xcd, 0x0e, 0x0f, 0x12, 0x4c, 0x95, 0x40, 0x66, 0x3a, 0xba, 0x9a, 0xba, 0x76, 0xeb, 0xf2, 0xba, 0x6a, 0xab, 0xee, + 0x80, 0x66, 0xb4, 0x3c, 0x28, 0x86, 0x32, 0xaa, 0x81, 0xea, 0x8c, 0x78, 0xb7, 0xd1, 0x88, 0x3d, 0x34, 0xc8, 0x80, + 0x0a, 0x9b, 0xfb, 0xca, 0x8a, 0xbe, 0xb7, 0x47, 0xf0, 0x30, 0x09, 0x20, 0x3d, 0xa2, 0x12, 0x62, 0x37, 0x4d, 0x08, + 0x6b, 0x4f, 0x57, 0x2d, 0x17, 0x17, 0x52, 0xad, 0xeb, 0x78, 0xfe, 0xcb, 0x9e, 0xb6, 0x7a, 0xa6, 0x9e, 0x14, 0xc2, + 0xcd, 0x94, 0xc0, 0x92, 0xa3, 0xf6, 0x28, 0xb2, 0x15, 0x14, 0xb7, 0xe7, 0x32, 0x5a, 0x10, 0x98, 0x98, 0xe2, 0x00, + 0xb3, 0x86, 0x3c, 0xc6, 0xe8, 0x96, 0xbe, 0xb1, 0xb2, 0xd6, 0x34, 0x66, 0x2b, 0x60, 0xb4, 0x3d, 0xef, 0x4b, 0xa0, + 0x36, 0x6c, 0x11, 0x64, 0xec, 0x1a, 0x4f, 0xd2, 0x04, 0xa0, 0xdb, 0x89, 0xcb, 0x0b, 0x8a, 0x55, 0x58, 0x75, 0x95, + 0x78, 0x5b, 0xe0, 0x3c, 0xd3, 0x1a, 0xc9, 0xac, 0x67, 0xf3, 0xd4, 0xc6, 0xb3, 0x5b, 0xec, 0x0d, 0x7a, 0x3f, 0x5b, + 0x9c, 0x14, 0x0a, 0xe7, 0xcd, 0x42, 0xb2, 0x0c, 0x2c, 0x67, 0xe4, 0x65, 0x3b, 0x75, 0xa3, 0x18, 0xab, 0xbd, 0xbc, + 0x61, 0x1f, 0xd7, 0xea, 0x6d, 0x94, 0xba, 0xb8, 0x58, 0x9b, 0x50, 0x81, 0xa9, 0x7a, 0x4b, 0xe6, 0x5a, 0x4a, 0xfd, + 0xed, 0x23, 0x68, 0x51, 0xeb, 0xf5, 0xab, 0x61, 0xbe, 0x57, 0xe8, 0x6c, 0xaa, 0x56, 0xa9, 0xb5, 0x22, 0xcc, 0x7a, + 0x6c, 0xb1, 0xe6, 0x46, 0x87, 0x2d, 0xf8, 0xa9, 0x3d, 0x9a, 0xb7, 0x31, 0x46, 0xa7, 0x17, 0x96, 0xf1, 0x5b, 0xf7, + 0xcf, 0x61, 0xc3, 0xed, 0x05, 0x7f, 0xfa, 0xf0, 0xeb, 0xf5, 0x3c, 0x77, 0x76, 0x73, 0xcb, 0xa7, 0xb7, 0x18, 0x6c, + 0xed, 0xde, 0x01, 0x7b, 0x67, 0x97, 0x4c, 0xaa, 0x28, 0x4d, 0xe2, 0x5b, 0x79, 0x21, 0xe0, 0xad, 0xbc, 0x95, 0xe8, + 0x96, 0xee, 0xb8, 0xba, 0x75, 0xf3, 0x41, 0x8a, 0x81, 0x85, 0xdd, 0x9d, 0x66, 0xef, 0xb2, 0xd5, 0x7c, 0xd8, 0x17, + 0x7f, 0x29, 0xc2, 0xbd, 0x57, 0x8b, 0xd8, 0x76, 0x6f, 0x6d, 0xe9, 0xbb, 0xe8, 0xd8, 0x81, 0xa1, 0xc0, 0x51, 0x2f, + 0x7d, 0x1b, 0x7b, 0xe2, 0xec, 0xc9, 0xed, 0x2d, 0x97, 0xd1, 0xac, 0xa5, 0x05, 0x5f, 0xc7, 0x66, 0xda, 0x6f, 0xfb, + 0x9d, 0xae, 0x52, 0x63, 0xc3, 0x06, 0x46, 0x9a, 0x66, 0x1c, 0x03, 0x49, 0x2d, 0x49, 0xc2, 0x9a, 0xdd, 0x80, 0xe8, + 0xa6, 0xf7, 0x8f, 0x30, 0xe5, 0x3e, 0x08, 0x5c, 0x07, 0x21, 0x06, 0xd0, 0x16, 0xc2, 0x91, 0xae, 0x48, 0x9d, 0x5d, + 0x7a, 0x44, 0x87, 0x19, 0x1a, 0xc9, 0xed, 0x6d, 0xcb, 0x74, 0xb3, 0x2c, 0xea, 0xdd, 0x5c, 0xae, 0x58, 0x16, 0xbe, + 0x43, 0x5b, 0x73, 0x19, 0x66, 0x3d, 0xfb, 0xa8, 0x3c, 0xde, 0x87, 0x0c, 0x24, 0x05, 0x0f, 0xe9, 0xf7, 0xb2, 0x5e, + 0x11, 0x9e, 0x3f, 0x72, 0xf1, 0x8c, 0x19, 0x4b, 0x2e, 0x2b, 0xf8, 0xe9, 0x7b, 0x81, 0x3c, 0x74, 0x16, 0x50, 0xc4, + 0x15, 0xeb, 0x5c, 0x72, 0x81, 0xe7, 0x92, 0x4b, 0x8f, 0x43, 0x5e, 0xf8, 0x28, 0x76, 0x73, 0x3c, 0x94, 0xbf, 0xe5, + 0xcc, 0xe3, 0x33, 0xdb, 0xc1, 0x94, 0xba, 0x45, 0x1b, 0xd9, 0xe8, 0x31, 0x27, 0x3c, 0x81, 0x70, 0x67, 0x40, 0x6e, + 0x6a, 0x63, 0x22, 0x79, 0x03, 0x41, 0x9a, 0xed, 0x66, 0xf4, 0x72, 0xa3, 0x8e, 0x4b, 0x47, 0xe2, 0x05, 0x6d, 0xc3, + 0x08, 0x04, 0xa2, 0xcd, 0x55, 0x85, 0xfd, 0xfa, 0x45, 0x64, 0xd9, 0xc1, 0x2b, 0xe2, 0xca, 0x3e, 0x8e, 0x43, 0xfd, + 0x33, 0x27, 0x71, 0x88, 0x20, 0x87, 0x82, 0x4a, 0x37, 0xa4, 0x10, 0xa7, 0xe9, 0x73, 0x48, 0x64, 0xbb, 0xa1, 0x84, + 0x39, 0xfb, 0x98, 0xed, 0x6f, 0xf2, 0xd8, 0x79, 0x12, 0x26, 0x64, 0x95, 0x24, 0x6b, 0xd4, 0x73, 0xa2, 0xaa, 0x32, + 0x98, 0xdf, 0x23, 0x69, 0xa5, 0x65, 0xf2, 0xa8, 0x77, 0xd7, 0xbe, 0x9d, 0x4f, 0xc7, 0xdf, 0xe2, 0x26, 0xb4, 0x22, + 0x67, 0xed, 0x96, 0xa7, 0xdc, 0x99, 0x1e, 0x29, 0x43, 0x2e, 0x7e, 0x8e, 0xbf, 0x5e, 0x37, 0xa3, 0x0b, 0x3b, 0x9d, + 0x46, 0xa6, 0xd0, 0xef, 0x5d, 0x8c, 0xc6, 0x3f, 0x1c, 0x58, 0x9e, 0x72, 0xff, 0x3a, 0x2a, 0x32, 0xf7, 0x87, 0x97, + 0x19, 0xc5, 0xaa, 0xda, 0xc1, 0x8e, 0xec, 0xd0, 0x87, 0x3b, 0xb8, 0x6b, 0x92, 0x8c, 0x12, 0x3e, 0x0c, 0x76, 0x9c, + 0x1f, 0x1a, 0x59, 0xe3, 0x07, 0xe7, 0x07, 0x3c, 0xee, 0x2c, 0x6f, 0x87, 0xd4, 0x71, 0x21, 0xd4, 0x3e, 0xd6, 0x23, + 0x6d, 0x52, 0x86, 0xa6, 0xa5, 0x6d, 0xd9, 0xde, 0x90, 0x82, 0x15, 0xf1, 0x48, 0x12, 0x6b, 0x91, 0x02, 0xc5, 0x2c, + 0x4a, 0x8a, 0xd7, 0xae, 0xd0, 0xae, 0x16, 0x97, 0x9b, 0x5a, 0x99, 0xda, 0xbe, 0x7a, 0xa4, 0x4d, 0xd0, 0xc8, 0x08, + 0x65, 0x69, 0x01, 0x09, 0x74, 0x70, 0xd1, 0x67, 0x3a, 0x25, 0x4f, 0x0a, 0x07, 0xb1, 0x8b, 0xf7, 0x1e, 0x58, 0x81, + 0x04, 0xf8, 0x5a, 0x58, 0x15, 0xdc, 0x5c, 0x31, 0x51, 0x2f, 0xf3, 0x10, 0x03, 0xd0, 0xeb, 0xd3, 0x83, 0xf9, 0x59, + 0x01, 0xfc, 0x2b, 0x47, 0x2b, 0x6c, 0x44, 0xdb, 0xaa, 0x2c, 0xda, 0xb5, 0x6f, 0x35, 0xb4, 0x5e, 0xd4, 0x81, 0xb4, + 0xb5, 0xab, 0x45, 0xa3, 0x30, 0x12, 0x2b, 0x68, 0x17, 0xbd, 0xb2, 0xad, 0xf2, 0xef, 0xdd, 0xc8, 0x13, 0xf9, 0x97, + 0xfc, 0x7e, 0x24, 0x1b, 0xec, 0xcb, 0x82, 0xa6, 0x15, 0x1d, 0x05, 0x03, 0x67, 0xae, 0x44, 0xb3, 0xb7, 0x1f, 0x47, + 0xf1, 0x84, 0xa3, 0xb9, 0x5f, 0x14, 0x35, 0x63, 0x7b, 0x5a, 0x3f, 0x37, 0x44, 0x87, 0x7d, 0x32, 0x3a, 0xec, 0x53, + 0xe2, 0x29, 0x96, 0x3c, 0xfc, 0x19, 0xc4, 0x70, 0xe6, 0x96, 0xcd, 0x0c, 0x84, 0x21, 0x94, 0xce, 0xf0, 0x00, 0x0f, + 0xfa, 0xa2, 0xec, 0xed, 0x45, 0xd2, 0xe3, 0x3e, 0x6a, 0xc4, 0xea, 0xb4, 0x27, 0x7e, 0x5d, 0xb8, 0x19, 0x6b, 0xd6, + 0xdc, 0xb4, 0xb0, 0xb6, 0x82, 0x0e, 0x8f, 0xdb, 0x11, 0x19, 0x6b, 0xf1, 0x13, 0xd6, 0xbc, 0xa9, 0xea, 0x3b, 0xd0, + 0x89, 0x57, 0x0b, 0x72, 0xf3, 0x92, 0x4e, 0xab, 0x86, 0x97, 0x8e, 0xd3, 0x17, 0x92, 0x4a, 0x48, 0x24, 0x03, 0x24, + 0x67, 0x6b, 0x57, 0x1b, 0x84, 0x64, 0x85, 0xf8, 0x59, 0xed, 0xb4, 0x1a, 0xed, 0x02, 0xc4, 0x85, 0xeb, 0xe8, 0x7d, + 0x13, 0x07, 0x4f, 0xdf, 0xea, 0x08, 0x69, 0xcb, 0x4b, 0x71, 0x75, 0xa5, 0x36, 0x6c, 0x7e, 0x88, 0xc6, 0xfd, 0xc0, + 0x11, 0x3d, 0x72, 0xd8, 0x95, 0x86, 0x24, 0x6e, 0x25, 0x5d, 0x95, 0x71, 0x3e, 0xe3, 0x65, 0x90, 0xb0, 0xab, 0x22, + 0xcf, 0xab, 0x0b, 0xf1, 0x96, 0x33, 0xb3, 0x27, 0x93, 0xb1, 0xab, 0x31, 0xaf, 0x3e, 0x44, 0x05, 0xfc, 0x05, 0xfe, + 0xad, 0x76, 0xc7, 0x82, 0x28, 0x3c, 0x87, 0x71, 0x5c, 0x46, 0x0c, 0xef, 0x5d, 0x05, 0x91, 0x9f, 0x42, 0xa0, 0x68, + 0x5c, 0xc4, 0x0d, 0xa2, 0x77, 0x45, 0x7e, 0xb3, 0x00, 0x51, 0x51, 0x1e, 0x00, 0xd4, 0x87, 0x26, 0x11, 0xfe, 0xfa, + 0x32, 0x1f, 0x61, 0x31, 0x8f, 0xc8, 0xd6, 0x2f, 0x9f, 0xfd, 0x2b, 0xa4, 0xb7, 0xfa, 0xa0, 0x20, 0x80, 0x05, 0x73, + 0x71, 0x2f, 0x0c, 0x37, 0xbe, 0xec, 0xaf, 0x8b, 0x82, 0x4e, 0x63, 0x21, 0xf4, 0x1e, 0xc7, 0xe8, 0xc1, 0xc6, 0x12, + 0x16, 0xe1, 0xa5, 0xb5, 0x50, 0xd0, 0x8a, 0x0a, 0xf2, 0x54, 0x5f, 0x4b, 0x5a, 0xfb, 0xfa, 0x3d, 0x1f, 0xe1, 0x1e, + 0x86, 0x7f, 0x77, 0x61, 0x5f, 0x82, 0x07, 0x75, 0x9a, 0x58, 0x54, 0xfb, 0x4e, 0x45, 0x1e, 0x79, 0x3b, 0x72, 0xb7, + 0xd5, 0x64, 0xe7, 0xd3, 0x8c, 0xae, 0x18, 0x86, 0xa2, 0xb0, 0xdb, 0xbd, 0x86, 0x57, 0xcf, 0x38, 0xb4, 0x61, 0xc5, + 0xf9, 0x75, 0xf6, 0x33, 0xf2, 0x98, 0xa8, 0x5e, 0x48, 0xec, 0xd1, 0x89, 0x03, 0x67, 0x12, 0x33, 0x26, 0x21, 0xf6, + 0x0a, 0x7a, 0x17, 0x8d, 0xf1, 0xa0, 0xb3, 0x79, 0x09, 0x4b, 0xd7, 0xb0, 0x15, 0x84, 0x67, 0x38, 0xc1, 0x3f, 0xe9, + 0x3a, 0x58, 0x77, 0x5b, 0x35, 0xc7, 0xd4, 0x9f, 0xab, 0xcd, 0xa3, 0xe5, 0x4b, 0x1b, 0x47, 0xda, 0x0c, 0x43, 0xeb, + 0xdc, 0x2c, 0x10, 0x45, 0x64, 0xd0, 0x0b, 0xe0, 0x83, 0x57, 0xe5, 0x7c, 0x40, 0xf3, 0x4b, 0x47, 0xc7, 0x2a, 0x42, + 0x14, 0x71, 0x5c, 0x93, 0x5d, 0x99, 0x5b, 0x60, 0x01, 0x93, 0x90, 0x07, 0x81, 0x52, 0x54, 0xea, 0xcd, 0x86, 0x20, + 0x0f, 0xcf, 0xa9, 0xd1, 0x9c, 0x1a, 0x35, 0x08, 0x25, 0xd3, 0x7d, 0xbd, 0xff, 0x8a, 0x21, 0x0c, 0xa8, 0xcc, 0x16, + 0xac, 0x2a, 0x37, 0xb0, 0x0a, 0x6f, 0xbd, 0x58, 0xc3, 0xaa, 0x1c, 0xd9, 0xb3, 0x46, 0xa3, 0xc2, 0xe0, 0x10, 0x91, + 0x3e, 0x1b, 0x8b, 0x30, 0x01, 0xb1, 0xe8, 0x55, 0x2c, 0xf3, 0xbe, 0x87, 0x43, 0x76, 0x4b, 0xb9, 0x6f, 0x0f, 0xd7, + 0x87, 0x45, 0x83, 0xf3, 0xd8, 0x53, 0x08, 0x31, 0xa1, 0xf8, 0x4f, 0x78, 0x2d, 0xf4, 0xf7, 0xaf, 0xa3, 0x95, 0x1e, + 0xe4, 0xc1, 0xbf, 0x45, 0x49, 0xac, 0xec, 0x3f, 0xc7, 0x43, 0x89, 0x84, 0x76, 0xc7, 0xd7, 0x7b, 0x68, 0x70, 0x70, + 0xa3, 0x88, 0xca, 0x48, 0x24, 0x3e, 0xd6, 0xa1, 0x87, 0x80, 0x0d, 0x23, 0x66, 0x83, 0x7c, 0x0d, 0xc5, 0xa8, 0xdb, + 0x55, 0xb8, 0xb4, 0x37, 0x70, 0xd1, 0x6b, 0x41, 0xef, 0xdf, 0xb6, 0x94, 0x96, 0x56, 0xdb, 0xe4, 0x25, 0x77, 0x20, + 0xfd, 0x6a, 0x6f, 0xf8, 0x66, 0x74, 0x5f, 0xb5, 0x7c, 0x63, 0x57, 0x12, 0xe8, 0x01, 0x06, 0x8c, 0x94, 0xcf, 0x40, + 0xf9, 0x15, 0x45, 0xd7, 0xb9, 0xcc, 0xae, 0xdb, 0x6a, 0x3e, 0x63, 0x49, 0x79, 0x61, 0xb2, 0x66, 0xe8, 0x0a, 0x8d, + 0x07, 0x54, 0x91, 0x47, 0x40, 0xd6, 0x4b, 0x5d, 0x70, 0x86, 0xea, 0x7d, 0x2f, 0xa3, 0x1c, 0x1d, 0x0d, 0xe0, 0x43, + 0xac, 0x2f, 0x9d, 0xea, 0x25, 0x78, 0x64, 0x9c, 0xc4, 0xc4, 0xa7, 0x99, 0x4a, 0x45, 0x51, 0x52, 0x38, 0x87, 0x3a, + 0xd1, 0x30, 0x9a, 0xa1, 0xdb, 0x06, 0x52, 0x70, 0xc9, 0x1f, 0xd6, 0x26, 0xca, 0xba, 0x92, 0xb7, 0x76, 0x83, 0x36, + 0xa4, 0x8a, 0x0f, 0xac, 0xbd, 0xed, 0xa2, 0xb0, 0xdc, 0x6f, 0xff, 0x51, 0x58, 0x24, 0xec, 0x90, 0xb6, 0x24, 0x61, + 0x78, 0x02, 0xed, 0xa4, 0x6b, 0x8e, 0x99, 0x60, 0x7a, 0x98, 0xd9, 0x3b, 0xcc, 0xaf, 0xd6, 0xf8, 0xab, 0xa4, 0x06, + 0x99, 0xa1, 0x06, 0xa5, 0x45, 0x0d, 0xf2, 0xfa, 0xf2, 0x2f, 0x70, 0x22, 0x44, 0x84, 0xaa, 0xcc, 0x0a, 0x88, 0x00, + 0x90, 0x44, 0x39, 0xa0, 0xf0, 0x6d, 0xc8, 0x13, 0xa0, 0x40, 0x34, 0x78, 0x8f, 0x4e, 0xe3, 0x78, 0x7b, 0x0f, 0x1e, + 0xbf, 0x14, 0x02, 0x83, 0x92, 0x14, 0x28, 0xff, 0xb9, 0xca, 0xc7, 0xcf, 0xf5, 0xec, 0x40, 0xd9, 0xa7, 0x18, 0x2b, + 0x42, 0xca, 0x17, 0x3f, 0x23, 0xd9, 0x04, 0x6e, 0x05, 0x8a, 0x3d, 0x2a, 0xfc, 0x18, 0x45, 0xd8, 0x92, 0x19, 0xde, + 0xc7, 0x6b, 0x44, 0x4f, 0x8d, 0xaa, 0x34, 0xa3, 0xca, 0xad, 0x51, 0x15, 0x8a, 0xc6, 0x45, 0xab, 0x90, 0xa3, 0xe2, + 0x12, 0x89, 0x75, 0xe3, 0x79, 0x68, 0x6c, 0xb9, 0x26, 0xba, 0xf4, 0x0c, 0xdd, 0x47, 0x5d, 0xe7, 0x3d, 0xc7, 0x8b, + 0x85, 0xe9, 0x38, 0x0a, 0xac, 0x87, 0xb8, 0x22, 0xe1, 0xb1, 0x61, 0x1d, 0x52, 0x07, 0xd2, 0xff, 0x25, 0x4f, 0x32, + 0xd7, 0x69, 0x9e, 0x3b, 0x5e, 0x03, 0xff, 0x82, 0x5a, 0xd4, 0x8d, 0xfc, 0x68, 0x38, 0x54, 0xc1, 0x6f, 0xe2, 0x90, + 0x16, 0xd9, 0xed, 0x6d, 0x66, 0x08, 0xba, 0x2f, 0x16, 0x18, 0x8a, 0x12, 0x4f, 0x51, 0x7c, 0x10, 0x12, 0x6c, 0xf8, + 0x21, 0x03, 0x75, 0x5c, 0x72, 0x29, 0x86, 0x5e, 0xcf, 0x31, 0x8c, 0x34, 0xb6, 0x84, 0x94, 0xff, 0x7c, 0xa4, 0xf6, + 0xfc, 0xa9, 0xf1, 0x4a, 0x41, 0x24, 0x57, 0x41, 0xe4, 0x6a, 0xe2, 0x48, 0x66, 0x85, 0x2d, 0xab, 0x2e, 0x65, 0x99, + 0xfb, 0xca, 0xbb, 0xba, 0x2f, 0xc2, 0xc5, 0xa1, 0x03, 0xb5, 0x67, 0x39, 0xad, 0xb0, 0x34, 0xd4, 0x1d, 0x47, 0x23, + 0x04, 0x2c, 0x0c, 0x77, 0x12, 0x9e, 0x63, 0x24, 0x3c, 0xd0, 0x0d, 0xd1, 0xb1, 0xc0, 0xd2, 0xa0, 0x26, 0xa8, 0x41, + 0xc5, 0xea, 0xeb, 0x21, 0x8e, 0x3a, 0xa5, 0xd1, 0x4e, 0xa0, 0xa8, 0x70, 0x91, 0x80, 0x09, 0x1f, 0xa2, 0x48, 0x0b, + 0x58, 0x85, 0x11, 0xc4, 0x90, 0x82, 0x6b, 0x0d, 0xd0, 0xb2, 0x80, 0xeb, 0x45, 0x63, 0x30, 0x31, 0xa1, 0xbb, 0xab, + 0xd0, 0xbb, 0x4f, 0x29, 0x8a, 0x6f, 0xcc, 0x9a, 0x86, 0x95, 0xb7, 0xdb, 0xea, 0x01, 0xc7, 0xdb, 0x88, 0x90, 0xd8, + 0xfb, 0xbd, 0xa2, 0x40, 0x63, 0x92, 0x74, 0x9b, 0x85, 0xf9, 0x77, 0xcd, 0x8e, 0x68, 0x06, 0x91, 0xcb, 0x20, 0x5d, + 0x4a, 0x4e, 0x7b, 0x83, 0x5b, 0xac, 0x39, 0xe9, 0xc1, 0x05, 0xda, 0xb3, 0x11, 0x01, 0x0a, 0x4f, 0xbb, 0x4a, 0x40, + 0x57, 0x0b, 0x5f, 0x8b, 0x61, 0x50, 0x5d, 0xe9, 0x59, 0x33, 0x11, 0xad, 0xcd, 0x01, 0xca, 0xce, 0x5c, 0xfc, 0xe8, + 0x33, 0xd8, 0xd1, 0x4a, 0xb9, 0x47, 0x20, 0x01, 0xd9, 0x6d, 0x6b, 0x71, 0x3d, 0x5b, 0xfb, 0x98, 0xdb, 0x5f, 0x91, + 0xaf, 0xdc, 0xe6, 0x89, 0xb1, 0x0f, 0xd9, 0xa6, 0x9c, 0x80, 0x21, 0x6a, 0xb5, 0xd0, 0x08, 0x92, 0x36, 0x74, 0xb9, + 0xaa, 0x75, 0x99, 0xac, 0xa1, 0x78, 0x55, 0x62, 0x82, 0x52, 0x22, 0x05, 0xc9, 0x97, 0x52, 0x82, 0x44, 0xf8, 0x4c, + 0x21, 0xfc, 0x37, 0x14, 0x91, 0x0a, 0xf8, 0x24, 0xbf, 0xbd, 0xc5, 0xef, 0x14, 0xde, 0xc7, 0xeb, 0xc1, 0x49, 0xf3, + 0xb5, 0xbe, 0xe7, 0x62, 0xe0, 0xae, 0xae, 0x22, 0x07, 0x69, 0x29, 0x43, 0x7b, 0x9c, 0xf8, 0xd0, 0xeb, 0xed, 0xb6, + 0x83, 0x97, 0xea, 0xbe, 0x32, 0xb9, 0x02, 0x19, 0x89, 0xde, 0xe8, 0xf5, 0x81, 0xb4, 0xfc, 0x4b, 0xac, 0x2e, 0x2a, + 0xb3, 0x76, 0x12, 0xca, 0xf5, 0x49, 0xec, 0xf2, 0xae, 0xc7, 0xc3, 0xda, 0xe4, 0x6e, 0x51, 0xe2, 0xbf, 0x6c, 0x44, + 0x31, 0x48, 0x7c, 0x23, 0x3f, 0x03, 0x9d, 0xc5, 0x50, 0xd4, 0xe2, 0x84, 0x0d, 0x52, 0xda, 0xe5, 0xca, 0xa8, 0x91, + 0x36, 0x81, 0x7c, 0x2f, 0xc3, 0x0b, 0x12, 0x27, 0x2a, 0x51, 0x4f, 0x36, 0x4d, 0x3c, 0x8e, 0xd7, 0x94, 0xb9, 0xee, + 0x06, 0x8e, 0xd1, 0xd6, 0x06, 0xa8, 0x08, 0x1f, 0x50, 0x9c, 0x49, 0x48, 0xb3, 0x94, 0xe0, 0x2b, 0x6b, 0xe0, 0x53, + 0x73, 0x4e, 0x14, 0xa5, 0xf4, 0x7a, 0x1f, 0xc4, 0x25, 0x73, 0xf8, 0x1c, 0x58, 0xea, 0x63, 0x2c, 0x6d, 0x24, 0x6b, + 0xa1, 0xd6, 0xa4, 0x1f, 0x2f, 0x5f, 0x8f, 0x30, 0x98, 0x89, 0xe8, 0x7d, 0x06, 0x59, 0xb3, 0xad, 0x8d, 0x66, 0xd6, + 0x98, 0xae, 0x4b, 0x73, 0x38, 0x35, 0x11, 0x82, 0xaa, 0xb6, 0x34, 0x60, 0x84, 0x2b, 0x95, 0x18, 0x7e, 0x8a, 0x29, + 0xfc, 0x03, 0x61, 0x5c, 0x3d, 0x52, 0xf8, 0xa7, 0x6d, 0xb1, 0x43, 0x36, 0xa3, 0xc3, 0xad, 0x05, 0xcd, 0xb3, 0x0d, + 0x3c, 0x78, 0x50, 0x49, 0x10, 0xa2, 0x32, 0x3c, 0xdf, 0x2d, 0x6b, 0xae, 0x6c, 0x57, 0x8e, 0x07, 0xc4, 0x5e, 0xe1, + 0xac, 0xec, 0x5a, 0x3d, 0xf4, 0x88, 0x39, 0x34, 0xb9, 0x41, 0x73, 0x65, 0x80, 0x0a, 0x52, 0x48, 0x97, 0xd0, 0x16, + 0xc8, 0xba, 0x4e, 0xe1, 0xac, 0x64, 0x08, 0x63, 0xe9, 0x65, 0x09, 0x4b, 0x05, 0x7b, 0x2d, 0xc2, 0xe8, 0xca, 0x85, + 0x21, 0x7d, 0x60, 0x68, 0x18, 0x11, 0x68, 0xe9, 0x71, 0x98, 0x75, 0xa3, 0xb3, 0x98, 0x02, 0x3d, 0xa6, 0x61, 0xd4, + 0xe0, 0x6c, 0x12, 0x56, 0xe8, 0xd9, 0x54, 0xa0, 0x07, 0xdf, 0xb2, 0x08, 0x84, 0xcf, 0x26, 0x77, 0x81, 0x38, 0xd1, + 0xd5, 0x7e, 0xa9, 0x11, 0x9e, 0x0b, 0x15, 0x1d, 0x21, 0x56, 0xf1, 0xe8, 0x9e, 0xbd, 0xbb, 0x78, 0xf9, 0xea, 0xed, + 0x9b, 0xdb, 0xdb, 0x36, 0x6f, 0xb6, 0x8f, 0xd8, 0x8f, 0x95, 0x8e, 0x07, 0xab, 0xa3, 0x00, 0x81, 0xfe, 0x9d, 0xd0, + 0x11, 0x9e, 0xaf, 0xc9, 0x0c, 0xe3, 0x06, 0x01, 0x2f, 0x4d, 0x0b, 0x1d, 0x13, 0xe4, 0xc6, 0xe9, 0x39, 0x0b, 0x07, + 0x8d, 0x50, 0x86, 0xfc, 0xfd, 0xba, 0x3e, 0xfa, 0x1d, 0x0c, 0x4c, 0x84, 0xdf, 0x63, 0x04, 0x10, 0x8c, 0x57, 0x0a, + 0x03, 0xe5, 0x2a, 0x01, 0xa3, 0x78, 0x8f, 0x50, 0x32, 0xa5, 0xa8, 0x55, 0xf0, 0x54, 0x20, 0x49, 0x14, 0xe1, 0x28, + 0xa3, 0x03, 0x0a, 0xe0, 0x8d, 0x41, 0x29, 0xc5, 0x53, 0x37, 0x95, 0xc7, 0xa5, 0x60, 0x55, 0xb7, 0x02, 0x80, 0x8b, + 0x7c, 0x9d, 0xe0, 0xeb, 0xa4, 0xab, 0xb8, 0x43, 0xb6, 0x9f, 0xb2, 0x39, 0xfc, 0x55, 0xd7, 0x22, 0x2e, 0x67, 0x05, + 0xff, 0x96, 0xe4, 0xf3, 0x32, 0x58, 0xde, 0x04, 0xb9, 0x7f, 0xd3, 0x1c, 0xee, 0x03, 0x69, 0xbd, 0x69, 0x96, 0xfe, + 0x8d, 0xc7, 0x60, 0x2e, 0xfc, 0x85, 0x48, 0x59, 0x40, 0xca, 0x02, 0x64, 0xdc, 0x0c, 0x99, 0xa2, 0x28, 0xda, 0x98, + 0xaf, 0x17, 0x15, 0x29, 0xb2, 0xa8, 0x1d, 0x4d, 0x71, 0xcb, 0xc2, 0xb7, 0xc3, 0x97, 0x30, 0xed, 0xd2, 0x14, 0xfe, + 0x88, 0xda, 0x4f, 0xcb, 0x38, 0xb8, 0x4f, 0xc2, 0x56, 0x77, 0x72, 0x96, 0x35, 0xdb, 0x30, 0xad, 0x13, 0x5c, 0xbb, + 0x31, 0x68, 0x6d, 0xb2, 0xd8, 0xa4, 0x41, 0xf1, 0x75, 0x76, 0xe3, 0xdb, 0xdb, 0xdd, 0xd4, 0xa3, 0x05, 0x37, 0x06, + 0x49, 0xe9, 0x72, 0xd2, 0x67, 0x2d, 0xf6, 0x11, 0x98, 0xfd, 0x92, 0xc3, 0x33, 0x2c, 0x38, 0x28, 0xd8, 0x17, 0x8e, + 0x76, 0xb4, 0x14, 0x57, 0x18, 0x42, 0x73, 0xd2, 0x3f, 0xa0, 0x92, 0xb9, 0xcc, 0x17, 0x6f, 0x91, 0x09, 0xe8, 0x57, + 0xd6, 0x82, 0x97, 0xe5, 0xf0, 0x06, 0x6d, 0x45, 0x67, 0xe2, 0xea, 0xd6, 0x22, 0x3c, 0x3c, 0x30, 0x47, 0xed, 0x81, + 0x68, 0x52, 0x4b, 0xe5, 0x7e, 0xb1, 0x4f, 0xd5, 0xc8, 0x26, 0x73, 0xf9, 0x6e, 0x5b, 0x46, 0xfe, 0x92, 0xb0, 0x40, + 0x04, 0x31, 0xf0, 0x48, 0x0b, 0x19, 0x4e, 0xc9, 0x86, 0x8b, 0x84, 0xca, 0x06, 0x4c, 0x52, 0x18, 0x4b, 0x4a, 0x9e, + 0xfe, 0xa9, 0x8c, 0x68, 0x1a, 0x41, 0xc7, 0x63, 0x55, 0x32, 0x25, 0xb0, 0x44, 0xeb, 0x94, 0x67, 0x82, 0x76, 0x25, + 0x50, 0xf9, 0x42, 0x8c, 0x87, 0xd4, 0x2d, 0xc8, 0xc1, 0xcb, 0x9d, 0x34, 0x0b, 0x48, 0xf4, 0x0e, 0x0e, 0x59, 0x74, + 0xf9, 0x39, 0x9e, 0xb5, 0xf1, 0xb2, 0xc0, 0xcf, 0xa0, 0x1d, 0x37, 0x73, 0x9d, 0x90, 0x61, 0xc2, 0xb0, 0x99, 0xef, + 0xe3, 0x5a, 0x02, 0x44, 0x14, 0x1f, 0xc6, 0xf0, 0x59, 0x73, 0xa2, 0x3f, 0xec, 0xa8, 0x0f, 0x1b, 0xb9, 0x4e, 0x10, + 0x1f, 0x36, 0xe4, 0x87, 0xf6, 0xed, 0x0c, 0x04, 0x02, 0x1b, 0x00, 0x1c, 0x01, 0x50, 0x79, 0x56, 0x34, 0x5f, 0x80, + 0x85, 0x5a, 0xec, 0x62, 0x1f, 0xbf, 0x85, 0x1e, 0x68, 0xb5, 0xf5, 0xbf, 0x0d, 0x65, 0xd0, 0x9f, 0xb2, 0xa0, 0x98, + 0xb9, 0x85, 0x30, 0xd1, 0x21, 0x54, 0x34, 0xc2, 0x14, 0x04, 0x99, 0xdd, 0x98, 0xa0, 0x66, 0x59, 0x0d, 0x52, 0x58, + 0xba, 0xcd, 0x18, 0x59, 0x0c, 0xde, 0x43, 0x13, 0x4e, 0xc8, 0x99, 0xd0, 0x4d, 0x71, 0x88, 0x31, 0x81, 0x67, 0x12, + 0xb4, 0x56, 0x39, 0xe9, 0x72, 0xbd, 0xb4, 0xf7, 0x57, 0xe5, 0x42, 0xb1, 0x66, 0xbb, 0xef, 0x41, 0x39, 0xf1, 0xd2, + 0xc7, 0x45, 0x26, 0x53, 0x1b, 0xf4, 0x7e, 0xd0, 0x09, 0xc4, 0x2b, 0xfe, 0xf4, 0x57, 0xb4, 0x02, 0xd0, 0x46, 0xc6, + 0x68, 0xfe, 0xf3, 0x9a, 0xc9, 0xeb, 0xf7, 0xad, 0x51, 0x2a, 0xb7, 0xdd, 0x3b, 0x6d, 0x99, 0x26, 0xec, 0xd3, 0x7a, + 0x4c, 0x5f, 0xd4, 0x13, 0xa2, 0x1b, 0x03, 0xed, 0x32, 0x7b, 0xdf, 0x0b, 0x91, 0x5c, 0x84, 0x39, 0x8a, 0x24, 0x50, + 0x9e, 0xe3, 0xda, 0x02, 0xd9, 0x08, 0x3f, 0xe3, 0x0d, 0xbc, 0x9e, 0xd4, 0x43, 0xc5, 0x40, 0x45, 0x50, 0x46, 0x34, + 0x29, 0x69, 0x37, 0x3c, 0x84, 0x5e, 0x8a, 0x27, 0xa6, 0x77, 0x1f, 0x0b, 0x69, 0x6d, 0xa5, 0xed, 0x71, 0x5d, 0x60, + 0xa1, 0xf7, 0x25, 0x85, 0x81, 0xdb, 0x13, 0x3b, 0x79, 0x25, 0xed, 0xad, 0xab, 0x52, 0x9d, 0xed, 0xd5, 0x74, 0x74, + 0x35, 0x9d, 0xcd, 0x6a, 0xec, 0xb8, 0x92, 0x77, 0x79, 0x45, 0x12, 0x67, 0xf5, 0xcb, 0x59, 0x94, 0xfd, 0x18, 0xcd, + 0xd0, 0x3a, 0x9a, 0x88, 0x7d, 0x55, 0xe4, 0x5c, 0x29, 0x70, 0xae, 0x94, 0x88, 0xab, 0x47, 0x1b, 0xea, 0x18, 0xe0, + 0xe5, 0xa5, 0xbe, 0x7c, 0x00, 0xaa, 0x7d, 0x9d, 0x0f, 0x65, 0x94, 0xd3, 0x0c, 0x74, 0xc5, 0x8c, 0x3b, 0x1e, 0xa1, + 0xaa, 0x8c, 0x81, 0xbd, 0x14, 0x6b, 0x2f, 0xeb, 0x25, 0x97, 0x26, 0x62, 0x5e, 0x9f, 0xae, 0xfb, 0xf8, 0x9e, 0xa5, + 0x58, 0x9a, 0x89, 0xe3, 0x10, 0x88, 0x7f, 0x8a, 0xba, 0xd9, 0xa5, 0xb9, 0xed, 0xb7, 0xd1, 0x66, 0x45, 0xd3, 0xcd, + 0x7a, 0x74, 0xc5, 0xc9, 0xfd, 0x82, 0x9c, 0x03, 0xc4, 0x41, 0x7f, 0x80, 0x99, 0x00, 0x7b, 0xec, 0x2b, 0x0a, 0xed, + 0xdf, 0x88, 0xb4, 0x85, 0x9d, 0xb6, 0xa0, 0xb4, 0x0e, 0x96, 0x43, 0xd2, 0x2c, 0xcb, 0x74, 0x16, 0xea, 0x7d, 0x81, + 0x37, 0xdd, 0xae, 0x30, 0x12, 0xf7, 0xec, 0x31, 0x39, 0x43, 0xbc, 0x43, 0x1f, 0x51, 0x00, 0xcc, 0xcf, 0xf2, 0xf4, + 0xad, 0xd1, 0x65, 0xef, 0x0a, 0x07, 0xb6, 0x26, 0x54, 0xca, 0xbc, 0x61, 0x1e, 0xcf, 0xd1, 0xaf, 0x73, 0xf7, 0x8e, + 0x78, 0xf8, 0x99, 0x2d, 0xb4, 0x88, 0xa3, 0xe2, 0x6f, 0x00, 0xbc, 0xd6, 0x55, 0x1d, 0x95, 0xe5, 0x5d, 0x6a, 0xbb, + 0x8e, 0x5e, 0x4c, 0x22, 0x68, 0xf5, 0x3d, 0x08, 0xcf, 0x7d, 0x71, 0x23, 0x77, 0xe5, 0xe3, 0xa5, 0x85, 0x35, 0x81, + 0x66, 0xa1, 0x4f, 0x27, 0x89, 0x80, 0xa8, 0xf5, 0x7e, 0xdb, 0x9a, 0x88, 0x9b, 0x99, 0xbd, 0x90, 0x04, 0xf7, 0x42, + 0x58, 0xa2, 0x83, 0xb6, 0x61, 0xec, 0x76, 0x19, 0xb4, 0x0d, 0x0f, 0x75, 0x8b, 0xc0, 0xed, 0x56, 0x67, 0x71, 0xed, + 0xe3, 0xcd, 0xb1, 0x09, 0xe8, 0xfc, 0x82, 0x56, 0xdc, 0x13, 0x17, 0x00, 0xa1, 0xd9, 0x87, 0x17, 0x4f, 0x25, 0x08, + 0x7c, 0xe9, 0x38, 0xfa, 0x29, 0xe1, 0xd7, 0xc2, 0x73, 0x78, 0x3a, 0x9b, 0x83, 0x72, 0x4b, 0x7b, 0xd4, 0x68, 0xe2, + 0xab, 0x9f, 0xf3, 0xfa, 0x25, 0xae, 0xd9, 0xc6, 0xef, 0x61, 0x18, 0x09, 0x69, 0xec, 0x20, 0x83, 0x84, 0x08, 0x66, + 0xa5, 0xe3, 0xb5, 0xfd, 0x81, 0xf1, 0x1e, 0x2a, 0x0a, 0x27, 0x58, 0xd4, 0x36, 0xa8, 0xe0, 0x81, 0xe2, 0x8d, 0x59, + 0x51, 0x1e, 0xde, 0x6d, 0x38, 0x4d, 0x2f, 0x57, 0x18, 0xe8, 0xb8, 0xe7, 0x34, 0x9d, 0x06, 0x0f, 0x64, 0x50, 0x66, + 0x15, 0x61, 0xbc, 0x3c, 0x3b, 0xa2, 0x38, 0xed, 0xda, 0xae, 0xfe, 0x47, 0x8c, 0x0e, 0xf8, 0x19, 0x9e, 0x12, 0xb3, + 0xa3, 0xbb, 0x5e, 0x56, 0x0d, 0xfc, 0x3e, 0x6f, 0x00, 0x38, 0x6e, 0x6f, 0x5b, 0xfa, 0x1a, 0x28, 0xb9, 0xcd, 0x95, + 0x89, 0x6d, 0xae, 0x4c, 0x6e, 0x73, 0x65, 0x6a, 0x9b, 0x2b, 0xa3, 0x6d, 0xae, 0x4c, 0x6d, 0x73, 0x29, 0x10, 0xfe, + 0x64, 0xc5, 0x71, 0x74, 0x13, 0x8c, 0xab, 0x58, 0x89, 0xc8, 0x78, 0xb8, 0xe1, 0xb9, 0x0b, 0xc2, 0x8f, 0x9e, 0x7e, + 0x3b, 0x86, 0x5c, 0xba, 0xee, 0x2b, 0x41, 0xc7, 0xa6, 0x40, 0xb3, 0xca, 0x28, 0x8c, 0xb3, 0x3e, 0xee, 0x0c, 0x8b, + 0x11, 0x04, 0xa9, 0xa5, 0x38, 0x44, 0xfb, 0x1b, 0xda, 0xe4, 0xe9, 0xe9, 0xf7, 0x20, 0x5f, 0x85, 0x99, 0x74, 0xb9, + 0xdf, 0x6d, 0x2b, 0x4a, 0xf1, 0x53, 0x4c, 0xe1, 0xc9, 0xa1, 0x36, 0x52, 0x41, 0x3c, 0x58, 0xac, 0x25, 0xac, 0xd4, + 0x5c, 0xac, 0x77, 0x75, 0x14, 0x9e, 0x4c, 0x51, 0xca, 0xad, 0xe4, 0x49, 0x8a, 0x87, 0xd8, 0xc9, 0x0b, 0xc3, 0xeb, + 0xe2, 0x11, 0x82, 0x98, 0x12, 0x7e, 0x6b, 0xa6, 0x82, 0x9c, 0xc5, 0x3a, 0xe9, 0xf7, 0x66, 0x4a, 0x04, 0x0c, 0x1a, + 0x54, 0x30, 0x03, 0xc1, 0x29, 0x02, 0x51, 0x29, 0x66, 0x83, 0xfc, 0x26, 0x28, 0x2c, 0x96, 0x78, 0xa1, 0x36, 0xfe, + 0x00, 0x29, 0xb3, 0x08, 0xcf, 0xfb, 0x38, 0xa0, 0x0a, 0x25, 0x6b, 0xa7, 0x00, 0x97, 0x31, 0x79, 0x54, 0x83, 0x60, + 0x78, 0x87, 0x27, 0x3c, 0x06, 0x27, 0xab, 0x80, 0x75, 0x02, 0x4e, 0x71, 0xe4, 0x97, 0x78, 0xe0, 0xea, 0xe6, 0x22, + 0xf9, 0x1b, 0x37, 0xbe, 0xf4, 0x61, 0xcb, 0x26, 0xa4, 0x39, 0xd0, 0xab, 0x77, 0x78, 0xfb, 0x96, 0x23, 0xcf, 0xea, + 0x3a, 0xe8, 0xac, 0x2b, 0x52, 0xf4, 0x79, 0x53, 0x9a, 0x5e, 0xc8, 0x80, 0x5e, 0xc7, 0xd0, 0xeb, 0x94, 0x7a, 0x3d, + 0x81, 0x16, 0x52, 0xc1, 0x8f, 0x86, 0x14, 0xf6, 0x1f, 0xa6, 0xde, 0x9d, 0x08, 0x33, 0x14, 0xba, 0x18, 0xcc, 0x43, + 0xda, 0x6d, 0x97, 0x69, 0xe8, 0xa6, 0x86, 0x50, 0x5f, 0x8a, 0x23, 0xca, 0x23, 0x26, 0x70, 0x23, 0x98, 0xad, 0xcc, + 0xe5, 0xe2, 0xc8, 0x6e, 0x46, 0x4d, 0xf8, 0x8c, 0xca, 0x34, 0x22, 0xe9, 0xce, 0x32, 0xc3, 0x24, 0x51, 0x1c, 0xd2, + 0x94, 0x6b, 0x0b, 0xf4, 0xc5, 0xf6, 0xe5, 0x8f, 0x9b, 0x43, 0xef, 0x60, 0xb4, 0xcf, 0xa5, 0x8b, 0x78, 0x86, 0x82, + 0xa8, 0x9d, 0x9f, 0x36, 0xe7, 0xde, 0xc1, 0x0c, 0xf2, 0xa5, 0xdf, 0x78, 0x66, 0xcb, 0x21, 0x3c, 0x5d, 0x8b, 0x2f, + 0x4c, 0xcc, 0x23, 0x54, 0x1b, 0x6d, 0xa0, 0x6c, 0xeb, 0x67, 0xb3, 0x46, 0x88, 0xb0, 0xd1, 0xfe, 0x7c, 0xee, 0x21, + 0x69, 0x13, 0x73, 0x2d, 0xce, 0x74, 0x73, 0xfd, 0x2e, 0xb6, 0x2d, 0x6d, 0x34, 0x02, 0x96, 0x7b, 0x17, 0x1a, 0x01, + 0xe4, 0xef, 0x61, 0x60, 0x7c, 0xc0, 0x9d, 0x77, 0x68, 0x9a, 0xdb, 0x9c, 0x81, 0x54, 0x66, 0xe8, 0xc9, 0xea, 0x56, + 0x0a, 0x5e, 0x80, 0x64, 0xe2, 0x37, 0x56, 0xc7, 0x62, 0x34, 0xd8, 0x20, 0x4b, 0x3e, 0xc4, 0xf2, 0x01, 0x56, 0x0b, + 0x50, 0xce, 0x48, 0xfb, 0xb1, 0x00, 0xee, 0x3b, 0xd6, 0x00, 0x1c, 0x14, 0x41, 0x55, 0x01, 0xb9, 0x0d, 0xab, 0x4b, + 0x88, 0x77, 0x47, 0x9b, 0x8e, 0xe4, 0x94, 0x56, 0x6a, 0x4a, 0x39, 0x53, 0xb5, 0x06, 0xa0, 0xc2, 0x8f, 0x13, 0xa6, + 0xeb, 0x40, 0x25, 0x7d, 0x9c, 0x28, 0xa3, 0xf0, 0x5f, 0x14, 0x80, 0xae, 0x16, 0xf7, 0x18, 0xa8, 0x32, 0xd0, 0x5e, + 0x2b, 0x7e, 0x6b, 0xba, 0xa9, 0x26, 0x11, 0x99, 0x44, 0x17, 0x03, 0xc2, 0xd1, 0x29, 0xac, 0xd7, 0x04, 0x4f, 0x50, + 0x15, 0xd8, 0xdf, 0xd2, 0x0c, 0x28, 0x59, 0x1b, 0x10, 0xf5, 0x24, 0xd2, 0x85, 0xe4, 0xa0, 0x92, 0xf5, 0x41, 0x51, + 0xb1, 0x38, 0xd4, 0x18, 0x61, 0xe1, 0x6c, 0xaa, 0x06, 0x08, 0x98, 0x4f, 0x44, 0x63, 0x6d, 0x51, 0x91, 0xa5, 0x30, + 0xab, 0x68, 0x55, 0xa9, 0xee, 0xce, 0xef, 0x5a, 0x4a, 0xa3, 0xf5, 0x55, 0xd7, 0x4d, 0x9b, 0xa1, 0x3c, 0xca, 0xd0, + 0x98, 0xcb, 0x19, 0x9c, 0x60, 0x92, 0xc4, 0xfc, 0xb9, 0x7c, 0x50, 0x44, 0xd7, 0x0a, 0xcc, 0xd1, 0x62, 0x69, 0x33, + 0x17, 0x9f, 0xa0, 0x1e, 0x68, 0xa5, 0x67, 0xbd, 0xf4, 0x20, 0x0b, 0x40, 0x88, 0xd7, 0xcb, 0x26, 0x0d, 0xff, 0xe2, + 0x46, 0x9a, 0x4c, 0x41, 0x56, 0x8a, 0x6d, 0x57, 0xa7, 0x49, 0x2d, 0x7b, 0x82, 0xe4, 0xd1, 0x40, 0x0b, 0xf2, 0xf1, + 0x54, 0x10, 0x1a, 0x98, 0xa9, 0x5c, 0x7a, 0xd0, 0x81, 0x24, 0x6b, 0xab, 0x1b, 0x16, 0x8a, 0xd9, 0x9d, 0xde, 0xdb, + 0xcb, 0xf6, 0xf6, 0x14, 0xbe, 0xed, 0xed, 0x4d, 0xce, 0xcd, 0xb3, 0x8b, 0x37, 0x28, 0x48, 0x44, 0x34, 0x1d, 0x12, + 0xe1, 0x1f, 0x26, 0xfb, 0x74, 0x19, 0xdd, 0x26, 0xcc, 0x2d, 0x67, 0xcb, 0x72, 0x23, 0x08, 0x26, 0x68, 0xe7, 0x2a, + 0xd8, 0xb5, 0x8c, 0x22, 0x21, 0xeb, 0xdf, 0xc7, 0xcd, 0xb3, 0x7a, 0x06, 0xd5, 0x8c, 0x81, 0xb0, 0x55, 0x99, 0x6d, + 0xdf, 0x79, 0xea, 0xf0, 0xce, 0x96, 0x6f, 0xcd, 0x2e, 0x32, 0x5e, 0xdc, 0x82, 0x84, 0x58, 0x5b, 0x0f, 0x84, 0x33, + 0x05, 0x3a, 0x5e, 0x00, 0x0b, 0x93, 0x6f, 0x7a, 0xd8, 0x3a, 0x41, 0xd4, 0x82, 0xda, 0x63, 0xad, 0x44, 0xf8, 0x19, + 0xaf, 0x19, 0x94, 0xd3, 0x3c, 0xbb, 0xf9, 0xcc, 0x6a, 0xe5, 0x45, 0x2e, 0x3d, 0x62, 0x26, 0x39, 0xfd, 0x6e, 0x27, + 0xfe, 0x68, 0xa8, 0xbc, 0xbd, 0x55, 0x8b, 0x1f, 0xde, 0x4a, 0x7c, 0xa3, 0x2f, 0xf1, 0x66, 0x93, 0x9e, 0x7b, 0xe7, + 0x97, 0x61, 0xc6, 0xd4, 0x67, 0xc0, 0xff, 0xe4, 0x37, 0x21, 0xf2, 0xc5, 0xb8, 0xba, 0xf1, 0x6b, 0xa7, 0x9d, 0x32, + 0x3a, 0x07, 0x43, 0x7f, 0x29, 0xda, 0x26, 0x1e, 0x30, 0x94, 0xd3, 0x91, 0xda, 0x71, 0x65, 0xc5, 0x3d, 0x4b, 0xbb, + 0xed, 0x6e, 0x55, 0x2c, 0xb4, 0xe1, 0x69, 0x89, 0xb7, 0xb8, 0xcd, 0xd0, 0x0b, 0xe0, 0x9b, 0x75, 0x5b, 0x57, 0x82, + 0x0e, 0x17, 0x78, 0x4e, 0xb1, 0x8b, 0x22, 0x28, 0x80, 0x47, 0x46, 0x96, 0x85, 0x25, 0xf2, 0x0c, 0x0f, 0x43, 0xbe, + 0x92, 0x01, 0x77, 0x5d, 0xa7, 0xa2, 0xf3, 0xbd, 0xe2, 0x08, 0xae, 0xc7, 0x74, 0x00, 0x5a, 0xf4, 0xbb, 0xfc, 0x5e, + 0x49, 0x90, 0xa4, 0xd0, 0xb3, 0x65, 0x69, 0x4e, 0xb9, 0xdb, 0xe1, 0x69, 0x2f, 0xd6, 0x22, 0x00, 0x4b, 0xf1, 0x4c, + 0x09, 0x16, 0xc2, 0x29, 0xe6, 0xe0, 0x5f, 0xe8, 0x23, 0xe6, 0xb9, 0xd2, 0x45, 0x6c, 0x76, 0x73, 0xef, 0xc0, 0x84, + 0x04, 0xea, 0x35, 0xd0, 0x8f, 0x57, 0x05, 0xba, 0x32, 0xfe, 0x9d, 0xd6, 0xee, 0xb1, 0x66, 0xff, 0x09, 0xba, 0x4f, + 0xef, 0xab, 0xf8, 0xe8, 0xc8, 0xae, 0x12, 0x7f, 0x21, 0x52, 0x28, 0x3a, 0xba, 0xcd, 0x9f, 0xca, 0xf4, 0x1f, 0x55, + 0x90, 0x59, 0x8e, 0xda, 0x3d, 0x8e, 0xb1, 0xd8, 0xa0, 0x9e, 0x00, 0xea, 0x13, 0x39, 0xc2, 0xf7, 0x1a, 0x32, 0xda, + 0x3a, 0x9d, 0x9f, 0xb7, 0x7a, 0xf8, 0x8b, 0x17, 0x7b, 0x58, 0x5b, 0x91, 0x5b, 0xa8, 0x2e, 0x35, 0x48, 0xda, 0xda, + 0x42, 0x3c, 0x2c, 0xf0, 0xa4, 0xe3, 0x52, 0xb8, 0x50, 0xb7, 0x31, 0x55, 0xb8, 0x50, 0xaf, 0xf0, 0xec, 0x42, 0x45, + 0x3a, 0x2e, 0xc5, 0x5a, 0x7f, 0x5d, 0x91, 0x60, 0xc5, 0x91, 0xa7, 0xbd, 0xc6, 0x0d, 0x1b, 0x5c, 0xb7, 0xb0, 0xe8, + 0xe1, 0x19, 0xd5, 0x34, 0x4e, 0x04, 0x4b, 0xec, 0xde, 0x9b, 0xd8, 0x4a, 0xaf, 0xd1, 0xd1, 0x72, 0x52, 0x53, 0x4a, + 0x26, 0xc5, 0xda, 0x65, 0x5c, 0x8e, 0x38, 0x52, 0xd5, 0x5b, 0x0e, 0x78, 0x75, 0xcd, 0x79, 0x16, 0x4c, 0x81, 0x6e, + 0x83, 0xbc, 0x0d, 0x32, 0x7b, 0xf0, 0x47, 0xc4, 0x84, 0xa3, 0x1c, 0x3a, 0x0a, 0xfd, 0xb2, 0x0a, 0x74, 0x99, 0x2a, + 0xd6, 0x65, 0x64, 0xb0, 0x95, 0xaa, 0xc9, 0xad, 0xb2, 0x18, 0xd1, 0xc1, 0x76, 0xcc, 0x2d, 0x5d, 0x19, 0x2a, 0x16, + 0xc4, 0x9c, 0x6c, 0x08, 0x2c, 0x4e, 0x04, 0x8c, 0xe5, 0x22, 0xcc, 0x59, 0x26, 0x5d, 0x90, 0xca, 0x95, 0x9e, 0x16, + 0x59, 0xfa, 0x3e, 0x17, 0xe5, 0xef, 0xab, 0x92, 0xa8, 0xbe, 0x34, 0x31, 0x1c, 0xed, 0x7d, 0x14, 0x25, 0x5a, 0xfa, + 0x43, 0xeb, 0xde, 0xc9, 0xb4, 0x46, 0xd4, 0x96, 0x32, 0xc8, 0xd8, 0x82, 0x5a, 0x11, 0xd1, 0x6a, 0xb1, 0x4a, 0x90, + 0x5e, 0x39, 0xd3, 0xe3, 0x29, 0xac, 0xbe, 0x47, 0xab, 0x10, 0x80, 0x44, 0x46, 0x7d, 0x3b, 0x26, 0xb0, 0xec, 0x52, + 0x4a, 0x5f, 0x4f, 0x44, 0x77, 0x86, 0x68, 0x62, 0x9d, 0xb3, 0x11, 0x32, 0xb1, 0xa1, 0xb0, 0x58, 0xa7, 0x8d, 0x30, + 0x66, 0x13, 0xfc, 0x33, 0x87, 0xee, 0x8d, 0x80, 0xc1, 0xcd, 0xcf, 0x46, 0x7b, 0x7b, 0x85, 0x1b, 0xb9, 0xd5, 0x65, + 0x7a, 0x3f, 0xee, 0x5f, 0x66, 0x7d, 0x8f, 0x0c, 0x17, 0xa0, 0xcb, 0xce, 0xbd, 0xb4, 0xd9, 0x04, 0xee, 0xd4, 0xec, + 0xa6, 0xf7, 0xf1, 0x33, 0xf8, 0xa3, 0x96, 0xd4, 0xe4, 0x2c, 0x45, 0xfa, 0x0e, 0x15, 0x01, 0x0d, 0xdf, 0xd6, 0xb4, + 0x1c, 0xba, 0x74, 0x3f, 0xb3, 0xcf, 0x5d, 0x68, 0x00, 0xd8, 0xf1, 0xb6, 0xd1, 0x46, 0xfe, 0xef, 0x01, 0x52, 0xe8, + 0x21, 0x63, 0x60, 0x37, 0x31, 0xc1, 0x11, 0x53, 0x50, 0x8a, 0x2d, 0x28, 0xa5, 0x0a, 0x4a, 0xb2, 0x73, 0x13, 0xaa, + 0x64, 0x28, 0x3a, 0x37, 0x97, 0x9d, 0x1b, 0xad, 0x42, 0x3d, 0x1d, 0x6c, 0xa6, 0xee, 0xb2, 0x19, 0xa3, 0xbe, 0x30, + 0x15, 0x07, 0xff, 0x07, 0xec, 0x8a, 0x7d, 0x93, 0x6c, 0xe0, 0x35, 0x39, 0x26, 0xa1, 0x02, 0xf1, 0x8d, 0x0d, 0x70, + 0x1f, 0x16, 0x9f, 0x50, 0x9d, 0x6c, 0xb1, 0x05, 0x65, 0x45, 0x80, 0xf6, 0x03, 0x4f, 0x04, 0xda, 0xc5, 0xbd, 0x06, + 0x2c, 0xc6, 0x6e, 0x28, 0x6b, 0x7c, 0x7b, 0xfb, 0x1a, 0xe4, 0xbe, 0x6b, 0x7a, 0xd9, 0x85, 0xb7, 0x85, 0x6b, 0xcc, + 0x7b, 0x17, 0xe1, 0x84, 0xbd, 0x0d, 0x27, 0xdd, 0x8b, 0xb3, 0x70, 0x08, 0x50, 0xbf, 0xf0, 0xae, 0xc2, 0xea, 0xf2, + 0x02, 0xad, 0x03, 0xbb, 0x57, 0xd2, 0xd6, 0xec, 0x0e, 0xc2, 0xd4, 0xbd, 0xa2, 0xb9, 0x19, 0x20, 0xeb, 0x85, 0x94, + 0x71, 0x18, 0xbb, 0x03, 0x61, 0x62, 0x9a, 0x86, 0xea, 0x8e, 0xb7, 0x1b, 0xa2, 0xa7, 0xd3, 0x30, 0xc2, 0x2c, 0xea, + 0x4a, 0xef, 0x22, 0x78, 0x0b, 0x25, 0xf4, 0x2d, 0x70, 0xd7, 0x54, 0x62, 0x26, 0xf6, 0x09, 0x0d, 0xe2, 0x4f, 0x09, + 0x3e, 0x17, 0x0a, 0x3e, 0x02, 0xff, 0x0b, 0x0d, 0x27, 0x74, 0xfb, 0x12, 0x3b, 0x48, 0xd0, 0xd3, 0x0b, 0xf6, 0x2d, + 0x34, 0xb7, 0xcd, 0xee, 0x98, 0xba, 0xef, 0xa8, 0x75, 0xf8, 0x9d, 0x5a, 0x67, 0xd6, 0xc6, 0xca, 0x9a, 0xca, 0x47, + 0x81, 0xc3, 0x31, 0xf2, 0xd3, 0x98, 0xe2, 0x20, 0xac, 0x69, 0xb2, 0xfa, 0xae, 0xa8, 0x9a, 0x42, 0x0b, 0xc8, 0x95, + 0xe1, 0x2d, 0x66, 0x89, 0x38, 0x1a, 0x8b, 0x01, 0x08, 0xba, 0xb9, 0xb6, 0xde, 0xcb, 0x03, 0xe4, 0x22, 0x34, 0xfc, + 0xe6, 0x76, 0x55, 0x6a, 0xd1, 0x43, 0x13, 0x65, 0xbb, 0x6a, 0xb6, 0x29, 0x64, 0x1a, 0xf0, 0x75, 0x71, 0xc9, 0x1a, + 0x34, 0x5e, 0xd1, 0x4e, 0x40, 0x29, 0x76, 0x02, 0xba, 0x5e, 0xa9, 0xef, 0x13, 0xc0, 0x9c, 0x2d, 0x19, 0xe5, 0x7d, + 0xd0, 0xd2, 0xb8, 0xb8, 0x6c, 0xa3, 0x84, 0x1e, 0x9d, 0xd3, 0x65, 0x04, 0xf9, 0xfd, 0x4a, 0x15, 0xcc, 0xcd, 0xcd, + 0x03, 0x39, 0x96, 0x5d, 0xd6, 0x51, 0xdf, 0xa2, 0x8f, 0x5b, 0x68, 0xb6, 0x36, 0xa1, 0xe6, 0x0a, 0x85, 0x61, 0x9d, + 0x60, 0x34, 0x35, 0xdc, 0xa0, 0x88, 0xe5, 0x79, 0xf2, 0xaa, 0xd1, 0xee, 0xc6, 0x67, 0x21, 0x27, 0x67, 0x7c, 0xad, + 0xe9, 0x26, 0x97, 0xf1, 0xfd, 0x12, 0xbe, 0xa1, 0x6e, 0xdc, 0xde, 0xc2, 0x2f, 0x28, 0x70, 0xbd, 0x48, 0xbe, 0xba, + 0x14, 0x7c, 0x3d, 0x17, 0x50, 0x93, 0x5d, 0xaa, 0xee, 0x8b, 0x4e, 0x81, 0x26, 0x06, 0x54, 0xae, 0x94, 0x74, 0x0f, + 0x2f, 0xad, 0x0d, 0x8b, 0x50, 0x7e, 0x1f, 0x53, 0x8c, 0x11, 0x2f, 0x70, 0x0b, 0xa0, 0x1a, 0x91, 0x42, 0xe1, 0x2a, + 0x8c, 0x29, 0xc8, 0x5b, 0xaa, 0x0f, 0x7f, 0x6b, 0xc4, 0xd9, 0xde, 0x44, 0x61, 0x57, 0x9f, 0x5b, 0x57, 0xf7, 0x6c, + 0xd8, 0x06, 0xe4, 0x64, 0x23, 0xba, 0xa8, 0x90, 0x6d, 0xca, 0x22, 0x68, 0x47, 0xcb, 0x41, 0x82, 0x53, 0x2a, 0x82, + 0xa3, 0x3c, 0x3e, 0xf2, 0x70, 0x77, 0x37, 0xf1, 0xaf, 0xb0, 0x21, 0x05, 0xf6, 0x82, 0x9a, 0x07, 0x42, 0x4b, 0x97, + 0xd9, 0x89, 0xfb, 0xcc, 0xba, 0xf2, 0x3a, 0xa9, 0x5d, 0x15, 0xd8, 0x6d, 0xe7, 0x09, 0xca, 0x11, 0x97, 0xf5, 0x4f, + 0x44, 0x7f, 0xf3, 0x15, 0xba, 0x32, 0x56, 0x4a, 0x7e, 0x8c, 0xc3, 0xe8, 0xac, 0xe8, 0x45, 0x0d, 0x0c, 0x43, 0x97, + 0x8a, 0xd6, 0x46, 0x30, 0xec, 0x57, 0x39, 0x1e, 0x82, 0x28, 0xc4, 0x3d, 0x3e, 0xe8, 0x7d, 0x59, 0x36, 0x75, 0x94, + 0x7c, 0xaa, 0x7b, 0x82, 0xad, 0x17, 0xe4, 0xef, 0xc6, 0xea, 0x17, 0x03, 0x3e, 0x29, 0xd7, 0x05, 0x85, 0x5d, 0x90, + 0x04, 0x77, 0x33, 0xdf, 0x78, 0x96, 0xa1, 0x69, 0xa5, 0x57, 0x05, 0x73, 0x83, 0x2f, 0x56, 0xfc, 0xc5, 0xad, 0x48, + 0x24, 0xae, 0x20, 0xb0, 0x4f, 0xc2, 0xa3, 0x48, 0xfd, 0xb6, 0x74, 0x35, 0x50, 0x81, 0x9a, 0x5e, 0xd9, 0x17, 0xe9, + 0xc1, 0xa8, 0x65, 0x4e, 0xb0, 0x54, 0xf0, 0x06, 0xb0, 0x89, 0x78, 0xf3, 0x0a, 0xea, 0x30, 0x64, 0x89, 0x95, 0x28, + 0x61, 0x0e, 0x43, 0x0a, 0x1e, 0x47, 0x40, 0x03, 0x16, 0x34, 0xb4, 0x62, 0x35, 0xba, 0x33, 0x36, 0x66, 0x53, 0xa0, + 0x99, 0xb2, 0x4f, 0x57, 0x61, 0xd4, 0x6b, 0xb6, 0x03, 0x5a, 0x7d, 0x33, 0x40, 0x95, 0xb1, 0xe0, 0x60, 0x33, 0xe0, + 0x39, 0x5d, 0x7e, 0x39, 0x03, 0x5e, 0x25, 0x17, 0xcf, 0xac, 0x19, 0x5e, 0x89, 0xf5, 0xc7, 0x2f, 0xc7, 0x26, 0x79, + 0xdc, 0x80, 0x64, 0x28, 0x86, 0x9f, 0xde, 0xc7, 0x5d, 0x8c, 0xb4, 0x86, 0x65, 0x90, 0x43, 0x73, 0x06, 0x79, 0x62, + 0xd6, 0xa6, 0x92, 0x2e, 0x0d, 0x56, 0x28, 0xac, 0x0c, 0xa0, 0xab, 0x90, 0x85, 0xe2, 0x99, 0xde, 0x24, 0x9d, 0xc9, + 0x8d, 0xde, 0x21, 0xf4, 0x6c, 0x18, 0xcc, 0xc4, 0x28, 0x24, 0xcf, 0xe0, 0x97, 0x43, 0x68, 0x9a, 0x4d, 0xa9, 0x23, + 0x30, 0x30, 0x58, 0x6e, 0xf3, 0xb3, 0x70, 0x86, 0x97, 0x9f, 0x75, 0xe7, 0x86, 0x44, 0x0d, 0xa0, 0xe0, 0x1c, 0x0a, + 0x76, 0xa7, 0x08, 0x59, 0x7b, 0xc2, 0xdc, 0xe7, 0x28, 0x42, 0xc9, 0x90, 0x9a, 0x92, 0xe8, 0x39, 0x6c, 0xd6, 0x0a, + 0xc6, 0x6c, 0xd6, 0x0e, 0x06, 0xf0, 0x84, 0x17, 0x71, 0x08, 0xb8, 0xbb, 0x73, 0x5c, 0x7c, 0x31, 0x64, 0x98, 0x34, + 0xa8, 0x95, 0xd5, 0x27, 0x67, 0x85, 0x0a, 0xd6, 0x28, 0x75, 0xa7, 0x0c, 0x95, 0x10, 0x10, 0x01, 0x18, 0x7c, 0xc6, + 0x66, 0x3e, 0xa1, 0x3a, 0xa8, 0x90, 0x63, 0x18, 0xe4, 0x24, 0x9c, 0xae, 0x86, 0x67, 0x73, 0xb4, 0x88, 0x6e, 0x14, + 0xd0, 0x2a, 0xa8, 0xc1, 0x9c, 0xb7, 0x56, 0x8c, 0xca, 0xe5, 0x5a, 0x38, 0x20, 0xe0, 0xf5, 0x6b, 0x29, 0x32, 0x9e, + 0xdd, 0x93, 0x68, 0x76, 0x21, 0x85, 0x81, 0x7a, 0x02, 0x33, 0xc1, 0xc0, 0x74, 0x1e, 0xbe, 0x58, 0xe9, 0x32, 0x42, + 0x9c, 0x9d, 0x2b, 0x92, 0x64, 0x99, 0x9f, 0x60, 0xe5, 0xd7, 0x2b, 0xd7, 0x29, 0xcc, 0x3a, 0x05, 0xaa, 0x73, 0x85, + 0xd1, 0xc0, 0x8a, 0x4a, 0x64, 0x3a, 0x85, 0x6f, 0xf6, 0x9d, 0x47, 0xe9, 0x66, 0x74, 0x2e, 0x50, 0x6f, 0x6a, 0xdc, + 0x49, 0xeb, 0x3f, 0xc8, 0x86, 0xad, 0xc8, 0xc0, 0xb9, 0xd7, 0x73, 0xb9, 0x71, 0x6a, 0xc5, 0x8b, 0xa9, 0x24, 0xef, + 0x01, 0x6e, 0xcd, 0xb5, 0x39, 0x3a, 0xf7, 0x3c, 0xa0, 0x15, 0x6a, 0xcd, 0xaf, 0x85, 0x24, 0x45, 0x4f, 0x51, 0x40, + 0xdd, 0xf5, 0x40, 0xa5, 0x74, 0xa2, 0x85, 0x22, 0x6d, 0xcd, 0xd2, 0x5a, 0xa4, 0x2d, 0x7d, 0xdf, 0xb5, 0xb8, 0x9f, + 0xc3, 0x0a, 0x5c, 0x24, 0x16, 0xb6, 0x0e, 0x8f, 0xaa, 0x6e, 0xe5, 0x9e, 0x67, 0x19, 0x85, 0x33, 0x6a, 0xcb, 0x04, + 0x8c, 0xeb, 0x0d, 0xe8, 0xa4, 0xe2, 0x15, 0xad, 0xae, 0xb2, 0xbc, 0x12, 0x4d, 0xae, 0x05, 0xf7, 0xb1, 0xae, 0x44, + 0x41, 0x5e, 0x8b, 0x1b, 0xd8, 0x2a, 0x96, 0x6c, 0x37, 0xb7, 0xaf, 0x68, 0x89, 0xdc, 0x25, 0x35, 0x0d, 0x02, 0xb5, + 0x3c, 0x40, 0x13, 0xe0, 0xe0, 0xe9, 0x09, 0x83, 0x9a, 0x5e, 0x54, 0x1c, 0x08, 0x46, 0xa1, 0x8c, 0x9b, 0xf8, 0x1a, + 0x98, 0x9b, 0xe1, 0x9b, 0x5c, 0x92, 0x89, 0x82, 0xee, 0xfc, 0x80, 0x81, 0x8d, 0x0a, 0x0e, 0x20, 0x5c, 0x1b, 0x28, + 0x7a, 0x44, 0xd4, 0x07, 0xd4, 0x62, 0x75, 0x48, 0x6c, 0xbb, 0x56, 0x44, 0x94, 0x98, 0xcf, 0x86, 0x74, 0x8d, 0x32, + 0xbb, 0x13, 0x74, 0xb2, 0xd2, 0xbd, 0x3d, 0x55, 0x42, 0xf6, 0x81, 0x7a, 0x24, 0x3f, 0xaf, 0x42, 0x04, 0x9b, 0x9f, + 0xe5, 0x20, 0x5b, 0xa9, 0x70, 0x9a, 0xad, 0xae, 0x0d, 0x7a, 0x0d, 0x14, 0x19, 0xf1, 0x8a, 0x90, 0x2a, 0xf3, 0x65, + 0xe5, 0x44, 0xb9, 0x93, 0x8a, 0x4f, 0xcb, 0xfa, 0xad, 0xa7, 0xd6, 0x1d, 0x11, 0x94, 0x2b, 0x59, 0x7b, 0xae, 0xf7, + 0xa2, 0x80, 0x99, 0xc2, 0xec, 0x09, 0x0e, 0xdf, 0x2d, 0xf0, 0xd6, 0xd7, 0x66, 0xb3, 0xf0, 0xe2, 0x90, 0xfc, 0x4e, + 0x63, 0xff, 0x4a, 0x44, 0x1a, 0xee, 0xb9, 0xf0, 0x58, 0xe5, 0x55, 0x94, 0x9e, 0x67, 0x7a, 0xa2, 0xe8, 0x18, 0x81, + 0x7a, 0x09, 0x55, 0x01, 0x50, 0x2a, 0x28, 0x6a, 0x1e, 0x6e, 0xcd, 0x46, 0xc8, 0xea, 0x02, 0x97, 0x76, 0x41, 0xf3, + 0x4b, 0xd3, 0x28, 0x9e, 0xe1, 0xa3, 0xeb, 0xe2, 0xbc, 0xae, 0xd8, 0xe5, 0xc3, 0xd8, 0x1d, 0x1a, 0x84, 0x12, 0x67, + 0x80, 0x17, 0x03, 0x83, 0xc1, 0xcb, 0x47, 0x45, 0xe0, 0x66, 0x0c, 0xd9, 0x23, 0x6b, 0x40, 0xb1, 0xc2, 0xdf, 0x40, + 0xbe, 0xfa, 0x77, 0xb1, 0x0a, 0xef, 0x0c, 0x5a, 0xb9, 0x42, 0x18, 0x71, 0x7c, 0xa2, 0xa1, 0x87, 0xbf, 0xf2, 0xd6, + 0xf1, 0x16, 0x37, 0xd9, 0xe5, 0xa5, 0x78, 0x6b, 0x18, 0x0e, 0x73, 0x05, 0x6c, 0x0d, 0xaf, 0xac, 0x29, 0x37, 0x2f, + 0xd9, 0x16, 0x53, 0x24, 0x0f, 0xcc, 0x70, 0x5f, 0x84, 0xca, 0xaa, 0x87, 0xff, 0x5d, 0xca, 0xaa, 0xd0, 0x01, 0x5c, + 0x61, 0x32, 0x7a, 0xed, 0xe2, 0xac, 0x60, 0x70, 0x4e, 0x73, 0x9d, 0xd2, 0x52, 0x75, 0x1d, 0x93, 0xd5, 0xf0, 0xe1, + 0x79, 0xb5, 0x82, 0x75, 0x2f, 0x42, 0x74, 0x89, 0x38, 0xc1, 0xe2, 0x13, 0xe9, 0x6b, 0x25, 0xd1, 0xd1, 0xea, 0xa3, + 0xb5, 0xc4, 0x78, 0x5f, 0x5d, 0xf1, 0xb7, 0x42, 0x8f, 0x1b, 0x52, 0x9d, 0xe4, 0xd6, 0x89, 0x82, 0xe8, 0xe6, 0xe7, + 0x02, 0x9d, 0x94, 0xb8, 0x0b, 0x1a, 0x36, 0xba, 0x68, 0xae, 0xdf, 0x87, 0xbe, 0xf9, 0x81, 0xba, 0xb8, 0x68, 0x45, + 0x2b, 0xef, 0x2e, 0x58, 0x29, 0x18, 0x61, 0x2f, 0x80, 0xce, 0x59, 0x0b, 0x4f, 0x2e, 0x59, 0x6b, 0x41, 0x30, 0x43, + 0x1c, 0x00, 0xb8, 0xa2, 0x95, 0x82, 0x0f, 0xe7, 0x31, 0x57, 0x8b, 0x41, 0xdb, 0x31, 0xe1, 0xd5, 0xbf, 0x52, 0x85, + 0x29, 0xb4, 0xef, 0xda, 0xa2, 0x03, 0x8e, 0x24, 0x9a, 0x72, 0x15, 0x5d, 0xb6, 0xe7, 0x79, 0x93, 0x46, 0x6f, 0xeb, + 0xb3, 0x2c, 0xa4, 0x36, 0x9f, 0xcc, 0x12, 0xe4, 0xf5, 0x25, 0xb8, 0x42, 0x49, 0xf6, 0xdf, 0x01, 0x40, 0x9e, 0xda, + 0x5b, 0xff, 0xb6, 0xb6, 0x7c, 0xf5, 0xb0, 0x75, 0x28, 0x2a, 0xb5, 0x92, 0xd4, 0x1d, 0x64, 0xb4, 0x6e, 0x4b, 0x0f, + 0x15, 0x17, 0xb4, 0x33, 0xc6, 0x3c, 0x05, 0xe5, 0x50, 0x7e, 0x84, 0x7c, 0xa6, 0xb6, 0x42, 0x10, 0x61, 0x2c, 0xe8, + 0x5a, 0x4b, 0x65, 0x25, 0x0c, 0x63, 0x1b, 0xb3, 0x2c, 0xbb, 0x2c, 0x5d, 0x66, 0x2b, 0x19, 0xb6, 0xac, 0x14, 0x6e, + 0x61, 0xb3, 0x54, 0x76, 0xf3, 0x5d, 0x19, 0xd6, 0xa2, 0x7c, 0xb3, 0x71, 0x1a, 0x2e, 0x65, 0x64, 0xef, 0xf5, 0x40, + 0x09, 0xe7, 0xfe, 0x31, 0x5d, 0xbf, 0xcd, 0xe8, 0x9c, 0xf2, 0xba, 0x91, 0x16, 0xc3, 0xe9, 0xdf, 0xde, 0xbe, 0x2b, + 0xe9, 0xd0, 0xa0, 0x4f, 0xe7, 0xc8, 0xf6, 0xf6, 0x20, 0xb1, 0xa2, 0x44, 0x7d, 0x3e, 0x6b, 0x6f, 0xaf, 0x14, 0x99, + 0xbd, 0x12, 0xe8, 0xfd, 0x0d, 0xdd, 0x4e, 0x68, 0xc7, 0xca, 0x0f, 0x2a, 0x15, 0x98, 0x7d, 0xad, 0xf9, 0xa4, 0x81, + 0x36, 0x16, 0xbc, 0x44, 0x73, 0xd5, 0x95, 0x31, 0x27, 0xeb, 0x9c, 0x70, 0x93, 0x61, 0xa1, 0x03, 0x4c, 0x19, 0xfe, + 0xca, 0xdd, 0x4b, 0xdc, 0x83, 0x26, 0x89, 0xbe, 0x22, 0x57, 0xb5, 0xbe, 0xb1, 0xf1, 0x8a, 0x5c, 0x4c, 0x84, 0xdc, + 0x12, 0x32, 0x04, 0xf4, 0x04, 0x0d, 0x35, 0x4c, 0x65, 0x84, 0xfe, 0xf6, 0x23, 0xdc, 0xf0, 0x48, 0xb1, 0x32, 0x90, + 0xd6, 0xb4, 0x37, 0x66, 0xa1, 0xa6, 0x4a, 0xc4, 0x41, 0x0f, 0xa7, 0x1c, 0x4a, 0x88, 0xe7, 0xfe, 0xed, 0xed, 0x54, + 0x04, 0x03, 0x8e, 0x0a, 0x59, 0x48, 0x2c, 0x14, 0xcb, 0xea, 0x6c, 0x66, 0x15, 0x06, 0xe8, 0x53, 0x98, 0x7e, 0x0c, + 0xda, 0xa4, 0x56, 0x81, 0x5e, 0x45, 0xe2, 0x95, 0xe8, 0xb5, 0xfd, 0x79, 0xe5, 0x9b, 0xa5, 0x23, 0x09, 0x63, 0x8e, + 0x81, 0x03, 0x77, 0x2b, 0x21, 0xcf, 0xc9, 0xcf, 0x68, 0xdf, 0x33, 0xe4, 0xf2, 0x15, 0x8d, 0x2d, 0x61, 0xa6, 0x86, + 0x06, 0x63, 0x0f, 0x55, 0xf7, 0xaa, 0x3c, 0x2c, 0x4d, 0xa1, 0x69, 0x52, 0xf2, 0x52, 0x09, 0x06, 0x02, 0x24, 0xee, + 0x1a, 0x9a, 0x89, 0xd4, 0x95, 0xe2, 0x89, 0x3a, 0x60, 0x83, 0x9d, 0xab, 0x08, 0x9d, 0xc4, 0x65, 0x20, 0xcc, 0xe6, + 0x3e, 0x69, 0xab, 0x7b, 0x97, 0xa6, 0x73, 0xe8, 0xaf, 0x95, 0x35, 0x2d, 0x88, 0xa1, 0x0d, 0xa8, 0x06, 0x8f, 0x66, + 0xde, 0xb5, 0x01, 0x9a, 0xad, 0x83, 0xcb, 0x02, 0x91, 0xa6, 0x34, 0x30, 0x48, 0x03, 0x2d, 0x8f, 0x59, 0x10, 0x05, + 0xfe, 0xf2, 0x3d, 0xe8, 0xe5, 0x06, 0x89, 0x50, 0x31, 0x54, 0x48, 0x64, 0x03, 0xd0, 0xc2, 0x23, 0x50, 0x63, 0xd1, + 0x0f, 0x4a, 0xad, 0xe9, 0xa5, 0x0d, 0x0a, 0xc5, 0xa5, 0x88, 0xdd, 0x5a, 0xf2, 0x03, 0xab, 0xa3, 0xdd, 0x1a, 0x7f, + 0x04, 0x88, 0x79, 0x2b, 0xc9, 0xa1, 0x0d, 0x65, 0xaa, 0xc1, 0x27, 0x5b, 0x83, 0x0f, 0x53, 0xb0, 0x45, 0x70, 0xa2, + 0x3d, 0x43, 0x77, 0x55, 0x83, 0x92, 0x46, 0x18, 0x69, 0xc4, 0x12, 0x5e, 0xc8, 0xdd, 0xb5, 0xb9, 0x0b, 0x71, 0xbf, + 0x01, 0x39, 0x7e, 0x01, 0xc2, 0xec, 0x19, 0x20, 0xd9, 0xee, 0xb6, 0x99, 0x95, 0x13, 0x58, 0xe2, 0x29, 0x98, 0x7a, + 0xcf, 0x5b, 0x76, 0x90, 0x2e, 0x7e, 0xd6, 0xda, 0xfc, 0x42, 0xdd, 0x32, 0xb9, 0x02, 0xe5, 0xf1, 0x20, 0xbb, 0xdf, + 0xc1, 0x4b, 0xcb, 0xf6, 0xf6, 0xe2, 0xf3, 0x76, 0xaf, 0xd3, 0x8c, 0x03, 0x74, 0xec, 0xb2, 0x97, 0x97, 0xd9, 0xbe, + 0x6a, 0x33, 0x6b, 0x2b, 0x2c, 0xf6, 0xcc, 0x84, 0xea, 0x9a, 0xd5, 0xd2, 0x75, 0x73, 0xdc, 0xe9, 0xf2, 0x56, 0xd7, + 0x51, 0x62, 0x82, 0x46, 0x56, 0x61, 0x1d, 0xcd, 0xb5, 0x40, 0xa9, 0xf1, 0xfe, 0xd2, 0x5c, 0xbe, 0x2e, 0x0f, 0x5d, + 0x61, 0xba, 0xeb, 0x8a, 0x4b, 0x2f, 0x97, 0xd2, 0xe9, 0x1e, 0x96, 0x03, 0xee, 0xd4, 0x17, 0xfc, 0x0b, 0x1a, 0x2c, + 0xe0, 0x9f, 0x26, 0xd9, 0xd6, 0x54, 0xf5, 0x1c, 0x28, 0xe5, 0x04, 0xf0, 0xf7, 0x8b, 0xa3, 0xa7, 0xca, 0xb4, 0x2c, + 0x1d, 0xfd, 0xef, 0x30, 0x73, 0x21, 0x87, 0x00, 0x71, 0x00, 0x83, 0x4a, 0x08, 0xc2, 0x37, 0xd8, 0x20, 0x7c, 0x0a, + 0xaa, 0x44, 0xf4, 0x41, 0x22, 0x32, 0x53, 0x2f, 0x22, 0x6c, 0xd6, 0x95, 0x00, 0x75, 0xf2, 0x8a, 0xbb, 0x22, 0x2c, + 0xbf, 0x7c, 0x91, 0xdc, 0x15, 0x4f, 0xeb, 0xd4, 0x79, 0x59, 0xfd, 0x1a, 0xfb, 0xe7, 0x26, 0xa6, 0xaf, 0x67, 0x8f, + 0x45, 0x36, 0xf5, 0x3f, 0xd9, 0x7b, 0xf7, 0xe6, 0xb6, 0x8d, 0x64, 0x6f, 0xf8, 0xab, 0x48, 0x2c, 0x1f, 0x05, 0x30, + 0x41, 0x5a, 0xf2, 0x5e, 0x9e, 0xe7, 0x80, 0x82, 0x59, 0xbe, 0xc4, 0x6b, 0xef, 0xc6, 0xb1, 0xd7, 0x76, 0xb2, 0xc9, + 0xaa, 0x54, 0x0a, 0x04, 0x82, 0x22, 0x12, 0x08, 0x60, 0x00, 0x50, 0x16, 0x4d, 0xe1, 0xbb, 0x3f, 0x7d, 0x9b, 0x1b, + 0x00, 0xca, 0xce, 0xb9, 0xfc, 0xf5, 0xbe, 0xa9, 0x8a, 0x45, 0x0c, 0x66, 0x06, 0x73, 0xed, 0xe9, 0xee, 0xe9, 0xfe, + 0xf5, 0xcc, 0xb8, 0xf5, 0x58, 0xde, 0x37, 0xdf, 0xc7, 0xd7, 0x29, 0x31, 0x1c, 0x8a, 0x25, 0xb6, 0x43, 0x85, 0x12, + 0x1e, 0x24, 0x7f, 0xba, 0xec, 0x7c, 0x1a, 0x61, 0x6a, 0x2d, 0x03, 0xe6, 0x18, 0x45, 0xf1, 0xd4, 0x27, 0x4b, 0xdf, + 0x12, 0xfe, 0x99, 0x79, 0xef, 0xbd, 0x72, 0x6a, 0x3e, 0xee, 0x93, 0x52, 0x49, 0x3f, 0xc2, 0xc8, 0x02, 0x49, 0x77, + 0xc2, 0x47, 0x7a, 0xa4, 0x72, 0x21, 0xde, 0x9b, 0x7c, 0x12, 0x31, 0xa4, 0x31, 0xc9, 0xe3, 0x68, 0x38, 0xef, 0xf3, + 0x04, 0x72, 0xff, 0xd2, 0xb7, 0xac, 0xe4, 0xf0, 0x9c, 0x63, 0x2e, 0x55, 0x5a, 0x11, 0xbc, 0x42, 0xcf, 0x89, 0xaf, + 0xdb, 0xa7, 0x60, 0x92, 0x29, 0x21, 0xf7, 0x57, 0x1d, 0x37, 0xb1, 0x46, 0x66, 0xd7, 0xac, 0xab, 0xe9, 0x83, 0x1a, + 0xa6, 0x2c, 0xc5, 0xa3, 0x12, 0x2a, 0xd3, 0x6a, 0xac, 0x07, 0x26, 0x22, 0x07, 0xe4, 0x9e, 0x36, 0x2b, 0xe0, 0x19, + 0x59, 0x80, 0x51, 0x59, 0xa2, 0xa2, 0x65, 0x91, 0x86, 0x94, 0x64, 0xfd, 0xaf, 0xb8, 0x17, 0xa8, 0x9d, 0x39, 0x0a, + 0x88, 0xc1, 0x80, 0x16, 0xda, 0x1f, 0xa2, 0x28, 0xca, 0xd6, 0x33, 0x1a, 0xe6, 0x03, 0xad, 0x70, 0xad, 0xc3, 0x81, + 0x5e, 0x18, 0xaa, 0x25, 0x14, 0x83, 0x35, 0x8d, 0x95, 0x61, 0x70, 0x12, 0xe6, 0x6d, 0x2c, 0x85, 0x23, 0x82, 0x80, + 0xe0, 0x30, 0xe5, 0x26, 0x62, 0x3a, 0x5e, 0xf2, 0x3c, 0x18, 0x19, 0xeb, 0x55, 0x7c, 0x8b, 0x69, 0xd2, 0xbf, 0x91, + 0xbf, 0x33, 0x8c, 0xac, 0x90, 0x9c, 0xfe, 0xb4, 0xf8, 0xc6, 0xd8, 0x57, 0x19, 0x79, 0xa6, 0x67, 0x39, 0xeb, 0x9d, + 0x16, 0xb0, 0x42, 0x72, 0x35, 0x1b, 0x1b, 0x84, 0xed, 0x84, 0x49, 0xce, 0x7d, 0xbe, 0x15, 0x81, 0x7f, 0x36, 0x47, + 0x47, 0x8b, 0xa9, 0x3a, 0xd4, 0xfc, 0xdd, 0x62, 0x2a, 0x67, 0xd8, 0x26, 0x58, 0x05, 0xb1, 0x55, 0x31, 0x39, 0x90, + 0x2c, 0x0c, 0x8b, 0x86, 0xb3, 0xbd, 0x81, 0x05, 0xb4, 0x31, 0x67, 0xc9, 0x0e, 0x4d, 0x5f, 0xa3, 0x95, 0x29, 0x83, + 0x5f, 0x8e, 0x16, 0x0c, 0x11, 0x9b, 0x43, 0x8d, 0x0d, 0x5a, 0xb0, 0xa3, 0xe9, 0x23, 0xf5, 0x68, 0xa1, 0x75, 0x2c, + 0xb5, 0x75, 0x70, 0x5a, 0xc7, 0xa6, 0x99, 0x29, 0x15, 0xec, 0x13, 0xe8, 0xa6, 0xeb, 0x5c, 0xdd, 0x98, 0x0b, 0xb5, + 0xd6, 0x9d, 0xe6, 0xc1, 0xa5, 0x40, 0x7a, 0x4c, 0x97, 0x51, 0x05, 0x5e, 0x90, 0x6c, 0xf9, 0x2d, 0xca, 0x81, 0x4e, + 0x23, 0xe8, 0xb8, 0x68, 0xa0, 0x64, 0x86, 0x94, 0xf3, 0x2e, 0x20, 0xc4, 0x57, 0x29, 0xe8, 0xb3, 0x33, 0x24, 0x62, + 0xe7, 0x33, 0xf4, 0x45, 0xd3, 0x63, 0xae, 0x35, 0xf3, 0xe5, 0x94, 0x29, 0xb3, 0x1e, 0x16, 0x21, 0xb5, 0xe8, 0xc9, + 0xe8, 0xd9, 0xd7, 0x84, 0xdb, 0x01, 0xe5, 0x8c, 0x00, 0x26, 0x68, 0x6d, 0xa5, 0xc4, 0x73, 0xdd, 0xe9, 0x04, 0x6d, + 0x81, 0xa4, 0x75, 0xff, 0x66, 0xd3, 0x19, 0x25, 0x67, 0xa4, 0x89, 0x9c, 0x41, 0x0a, 0x4e, 0x83, 0x9d, 0xe4, 0x44, + 0x09, 0xf0, 0x81, 0x1d, 0x25, 0xa7, 0x6d, 0x29, 0x8e, 0x85, 0x41, 0xfa, 0xe8, 0xc6, 0x97, 0x45, 0x74, 0x28, 0xa9, + 0x9a, 0x20, 0x1e, 0x90, 0x72, 0x48, 0xac, 0x0a, 0xd2, 0x4d, 0x63, 0xb8, 0x72, 0x65, 0x4c, 0x31, 0xc7, 0x58, 0x08, + 0x25, 0x87, 0x76, 0x74, 0x12, 0x95, 0xf1, 0x3e, 0xab, 0x2e, 0x8b, 0x79, 0x29, 0x57, 0x03, 0xc5, 0xbc, 0x76, 0xae, + 0x07, 0x6e, 0xed, 0xcb, 0xb5, 0x94, 0x1c, 0x9d, 0xba, 0x62, 0x51, 0x11, 0x51, 0x13, 0xc9, 0xfa, 0xfc, 0x41, 0x6d, + 0x2f, 0x1f, 0x42, 0xd8, 0xa8, 0x51, 0x63, 0x29, 0x18, 0x1b, 0x05, 0xfd, 0x16, 0x94, 0x0d, 0x71, 0x01, 0x61, 0xa4, + 0x8d, 0x82, 0x1f, 0xac, 0x2f, 0xdf, 0xe4, 0xda, 0xfe, 0x93, 0xd9, 0x6f, 0x83, 0xbd, 0x9c, 0xf9, 0x73, 0x8f, 0xe3, + 0x78, 0xad, 0xc9, 0x62, 0x4a, 0xcc, 0x06, 0xa3, 0x4c, 0x59, 0x0a, 0xf2, 0x15, 0xc6, 0x12, 0x9d, 0x45, 0x61, 0xf4, + 0x8b, 0xa8, 0x8e, 0x04, 0xee, 0xa3, 0x91, 0x86, 0xa4, 0xaa, 0x11, 0x05, 0x7f, 0xbe, 0xc6, 0x38, 0x13, 0xe8, 0xe8, + 0xb8, 0xa0, 0x30, 0xaa, 0x68, 0x4b, 0x60, 0x6e, 0x07, 0xaa, 0xc1, 0x6b, 0x24, 0x14, 0x76, 0x3f, 0x50, 0xa0, 0xd4, + 0x17, 0xac, 0x24, 0x7d, 0x93, 0x36, 0x24, 0x12, 0x2b, 0x8f, 0x04, 0xbe, 0xa8, 0xe1, 0xc0, 0xaa, 0x7a, 0xe9, 0x9e, + 0x96, 0xb3, 0xf1, 0xb8, 0xf6, 0x65, 0x79, 0x92, 0x84, 0x46, 0xca, 0xbb, 0x21, 0x6f, 0xa7, 0xa7, 0x5a, 0x29, 0x6f, + 0x21, 0x2d, 0x61, 0xd7, 0xc8, 0xd3, 0x1c, 0x6b, 0xbd, 0x16, 0x73, 0x63, 0x64, 0x5f, 0xf2, 0x74, 0x64, 0x1b, 0xb5, + 0x13, 0xb7, 0x25, 0xf7, 0x21, 0xac, 0xe7, 0xae, 0xa0, 0x29, 0x71, 0xa4, 0x64, 0xca, 0x59, 0x75, 0x1a, 0x93, 0x91, + 0xfb, 0x8e, 0x5c, 0x22, 0xc8, 0xe6, 0x9c, 0xdc, 0xba, 0x78, 0xaa, 0x0b, 0xdc, 0x21, 0x86, 0x84, 0x32, 0xe6, 0x4b, + 0x0e, 0xdf, 0xe6, 0x08, 0x1a, 0x08, 0x19, 0xf0, 0x2b, 0x90, 0x3c, 0xbc, 0x81, 0xe2, 0xf8, 0x68, 0xc7, 0x77, 0x77, + 0xbf, 0x37, 0xec, 0x62, 0xfb, 0x3b, 0x12, 0x43, 0x7c, 0xd5, 0x8c, 0xa3, 0xdc, 0x37, 0x7e, 0x82, 0x96, 0x43, 0x52, + 0x6e, 0x7b, 0x13, 0xd9, 0xbb, 0x1e, 0xdd, 0xa9, 0x2c, 0x47, 0x1d, 0x75, 0x3b, 0x2b, 0xf0, 0x23, 0x81, 0x1a, 0x56, + 0x8e, 0x5a, 0xa0, 0xaf, 0xab, 0x55, 0xd4, 0x02, 0x3c, 0xf0, 0x0b, 0x74, 0x9e, 0x28, 0xce, 0xd1, 0xc4, 0xa0, 0x44, + 0x9b, 0x03, 0x8c, 0x8b, 0x3c, 0x84, 0x07, 0x73, 0xcf, 0xb6, 0x9a, 0x92, 0x3b, 0x6a, 0xba, 0xd0, 0xc5, 0x6c, 0x43, + 0x3e, 0x34, 0x3b, 0xa6, 0xf7, 0xda, 0x62, 0xc9, 0x32, 0x30, 0xca, 0x5d, 0xd1, 0x92, 0x2c, 0x6f, 0xb2, 0x45, 0x3b, + 0x7d, 0x00, 0xc7, 0x2b, 0xff, 0x4d, 0xb9, 0x30, 0xea, 0x6f, 0x51, 0xca, 0x6b, 0x7f, 0xb1, 0x4c, 0x38, 0xcd, 0xa8, + 0x10, 0x52, 0x46, 0x43, 0xc8, 0x18, 0xa9, 0x1d, 0x54, 0xb7, 0xb0, 0x83, 0xea, 0xa2, 0xc3, 0x53, 0x2f, 0xa8, 0xae, + 0x85, 0xb4, 0x51, 0xc0, 0x46, 0x17, 0x5f, 0xa4, 0xef, 0xbf, 0xfd, 0xdb, 0xd3, 0x8f, 0xaf, 0x7f, 0xfc, 0xf6, 0xe2, + 0xf5, 0xf7, 0x2f, 0x5f, 0x7f, 0xff, 0xfa, 0xe3, 0xcf, 0x0c, 0xe1, 0x31, 0x4f, 0x55, 0x86, 0x77, 0x6f, 0x3f, 0xbc, + 0x76, 0x32, 0xd8, 0xd6, 0x0c, 0x79, 0x57, 0x22, 0xc7, 0x8b, 0x40, 0x8a, 0x06, 0x21, 0x4e, 0x76, 0x8a, 0xe7, 0x00, + 0x5e, 0x92, 0x7c, 0xef, 0x52, 0x4a, 0xb6, 0x20, 0x39, 0xac, 0xeb, 0x25, 0xc3, 0x72, 0xd5, 0x74, 0xfb, 0x81, 0x3d, + 0x78, 0x83, 0x26, 0x32, 0xb0, 0x86, 0x7f, 0x64, 0x88, 0x7d, 0xde, 0x49, 0xc0, 0x9d, 0x08, 0x59, 0xf3, 0x7c, 0x9b, + 0xde, 0x0b, 0x9c, 0xff, 0xb9, 0x5c, 0xa2, 0x96, 0x68, 0x00, 0x7c, 0x88, 0x3f, 0x4e, 0xf5, 0x4d, 0x9a, 0x64, 0xd1, + 0x76, 0xc6, 0xe8, 0x74, 0x69, 0x20, 0x4d, 0xec, 0x99, 0x17, 0x7d, 0x72, 0x2a, 0xc0, 0x1d, 0x0b, 0xfc, 0xb4, 0x0a, + 0xd0, 0x9b, 0x4e, 0xd9, 0x2f, 0xb9, 0x26, 0xa5, 0x94, 0xfc, 0x26, 0xde, 0x45, 0xb9, 0x9c, 0x95, 0xc1, 0x8d, 0x0a, + 0x8e, 0x4c, 0x1f, 0xc4, 0x2b, 0xbe, 0x02, 0xcd, 0x7f, 0x39, 0xee, 0x70, 0xae, 0x62, 0x24, 0xaf, 0x22, 0x58, 0x19, + 0xe8, 0x5f, 0x50, 0xa0, 0xcd, 0xab, 0x13, 0x79, 0x79, 0xa3, 0x4f, 0xb9, 0x25, 0x9c, 0x72, 0xcb, 0x53, 0xbc, 0xb0, + 0x5f, 0xaa, 0xee, 0xae, 0x61, 0x3d, 0x2f, 0xcf, 0x83, 0x1d, 0x6c, 0xb7, 0xf0, 0x2a, 0x80, 0x93, 0x3f, 0xbc, 0x6e, + 0xa3, 0x75, 0x70, 0x19, 0xad, 0xad, 0xa6, 0xad, 0xed, 0xa6, 0xcd, 0x36, 0xd1, 0x25, 0x72, 0x08, 0x30, 0x67, 0x18, + 0xf7, 0xf8, 0xca, 0x0f, 0x36, 0xc8, 0xd1, 0x5e, 0x07, 0x1b, 0x14, 0xc4, 0xd6, 0x11, 0xcc, 0xc4, 0x06, 0xda, 0x71, + 0x78, 0x1c, 0x14, 0xb4, 0xfe, 0x7c, 0x7c, 0xc1, 0xb4, 0x50, 0xbf, 0x3b, 0x51, 0xef, 0x66, 0xea, 0xde, 0x0c, 0xf2, + 0xdc, 0x64, 0xf5, 0x26, 0xce, 0xc9, 0xb2, 0x1c, 0x3f, 0xda, 0x49, 0xa1, 0x4f, 0x5f, 0xd0, 0x97, 0xac, 0x85, 0xf3, + 0xaf, 0xac, 0x7b, 0xaf, 0xca, 0xd1, 0x0a, 0x3a, 0x21, 0xb2, 0x3a, 0xee, 0x81, 0x45, 0xf4, 0x04, 0x37, 0x30, 0x8d, + 0x1c, 0xec, 0x3a, 0xe0, 0xec, 0x29, 0xe2, 0xbd, 0x03, 0x80, 0x96, 0x3b, 0x06, 0xf0, 0x84, 0x15, 0xa3, 0xc2, 0xe0, + 0x41, 0xf3, 0xe5, 0xd6, 0x4a, 0xc7, 0x24, 0xb4, 0x2f, 0xb1, 0x1a, 0x99, 0x29, 0xd8, 0x5c, 0x14, 0xf4, 0x41, 0x2c, + 0xef, 0x47, 0x1c, 0x6a, 0x70, 0x24, 0x79, 0x1d, 0xd8, 0xa7, 0xb7, 0x9d, 0x5d, 0x3d, 0xf8, 0x3d, 0x55, 0xbb, 0xc4, + 0xc8, 0x96, 0x4f, 0x57, 0xf1, 0x27, 0xf5, 0x53, 0x62, 0x7d, 0x28, 0x70, 0x84, 0xfb, 0x1a, 0xe0, 0x7c, 0xbd, 0x4e, + 0xbb, 0x83, 0x88, 0x54, 0xb9, 0x42, 0x84, 0xf8, 0x8a, 0x97, 0x39, 0x1d, 0x47, 0xbc, 0x10, 0x91, 0x88, 0xf1, 0x2f, + 0x1a, 0x3e, 0xe2, 0x58, 0x0e, 0x0b, 0x0d, 0x4a, 0x2e, 0x11, 0xbc, 0x67, 0xdd, 0x3d, 0x68, 0xb6, 0x57, 0xad, 0x16, + 0x13, 0x1b, 0x28, 0xdf, 0xdd, 0x95, 0x48, 0x4b, 0x8d, 0x9d, 0x26, 0x3e, 0xe2, 0xf6, 0xd6, 0xd6, 0x9a, 0xc2, 0x2a, + 0x69, 0x80, 0x0a, 0x7a, 0x1d, 0xe0, 0x5f, 0x77, 0x85, 0x58, 0x30, 0x45, 0xfd, 0x97, 0x50, 0xc4, 0x7a, 0x6f, 0xd5, + 0xd5, 0xcb, 0xd6, 0x2a, 0x23, 0xe0, 0x9f, 0x33, 0xed, 0x27, 0x49, 0x70, 0xea, 0x23, 0x8e, 0x45, 0x3d, 0x2a, 0xd0, + 0xeb, 0x26, 0xf8, 0x58, 0x6b, 0x67, 0xcb, 0x79, 0x16, 0xf6, 0xd8, 0x2f, 0x38, 0x65, 0xde, 0xe5, 0x56, 0x1c, 0x75, + 0x8a, 0x46, 0xb4, 0xca, 0x16, 0x8b, 0xb4, 0x40, 0xf2, 0x7e, 0x21, 0xf4, 0xff, 0xe8, 0x08, 0xdd, 0x49, 0xeb, 0x10, + 0x38, 0x80, 0x94, 0x10, 0xe1, 0xf8, 0xf0, 0xe3, 0x80, 0x27, 0xa2, 0x2a, 0x7c, 0xd0, 0xec, 0x91, 0x98, 0x5d, 0x81, + 0x39, 0x69, 0x6e, 0x11, 0x86, 0xb1, 0xb9, 0xb5, 0x02, 0x92, 0x68, 0xa5, 0x19, 0x93, 0x1e, 0x64, 0x23, 0x40, 0x02, + 0x31, 0xb1, 0x3c, 0x2c, 0x92, 0xc4, 0x0c, 0xf8, 0x15, 0x33, 0x19, 0xfa, 0x6e, 0x04, 0x57, 0x4c, 0xd4, 0xcd, 0x4a, + 0x5b, 0xd7, 0x89, 0x36, 0xe7, 0xc4, 0x0b, 0xb9, 0xd0, 0x51, 0x47, 0x94, 0x26, 0x88, 0xe2, 0x0f, 0x38, 0x59, 0xd8, + 0x8d, 0xe6, 0x45, 0x2f, 0x9d, 0x39, 0xd6, 0xb7, 0x43, 0xb5, 0xe2, 0x9d, 0xcd, 0x07, 0xd2, 0x97, 0xf5, 0x92, 0x9f, + 0xa3, 0x91, 0x8e, 0x93, 0x9c, 0x16, 0xc8, 0x6a, 0x71, 0x3d, 0x1f, 0xa0, 0x4e, 0xbb, 0x39, 0xf5, 0x66, 0xbd, 0x06, + 0xae, 0xaa, 0x7e, 0x91, 0x26, 0x2a, 0xbc, 0x8f, 0x7a, 0xf5, 0x40, 0x20, 0x15, 0x3a, 0x8d, 0xda, 0x16, 0x09, 0x9a, + 0x6d, 0x6a, 0xc5, 0xb6, 0x6c, 0x61, 0x41, 0x8d, 0xff, 0x88, 0x63, 0x04, 0x0c, 0x85, 0x38, 0x68, 0x0c, 0xbc, 0x35, + 0xa5, 0xee, 0x29, 0xd2, 0xcb, 0x2f, 0xb7, 0x36, 0x20, 0x43, 0x01, 0x61, 0xb2, 0x1f, 0x3a, 0x4a, 0x20, 0x33, 0x31, + 0xb3, 0x8e, 0x86, 0x44, 0x66, 0x31, 0xcf, 0x8a, 0xdf, 0x68, 0xc7, 0xd6, 0x84, 0x34, 0xac, 0xd6, 0x5e, 0x04, 0x0c, + 0x4a, 0x23, 0x7b, 0x39, 0xd0, 0xb1, 0x59, 0x16, 0x0b, 0x15, 0xc5, 0x45, 0x15, 0x57, 0x2c, 0x0b, 0xba, 0x38, 0xe2, + 0x32, 0xd6, 0x4b, 0x6f, 0x9a, 0xd5, 0xef, 0x28, 0xa0, 0xcc, 0xb7, 0x34, 0xda, 0x0b, 0x6f, 0x84, 0x59, 0x38, 0xc6, + 0x16, 0x36, 0x51, 0x63, 0xb3, 0x8d, 0x3e, 0x56, 0x59, 0xba, 0x38, 0x68, 0xca, 0x83, 0x0d, 0x88, 0xa3, 0xcd, 0x2a, + 0x3d, 0xf8, 0x06, 0x73, 0x7e, 0x73, 0xc0, 0x55, 0x1f, 0x7c, 0xca, 0x9a, 0x55, 0xb9, 0x69, 0xf8, 0xcd, 0x4b, 0xaa, + 0xe3, 0x9b, 0x03, 0x8e, 0x55, 0x73, 0xc0, 0x33, 0xb9, 0x98, 0x1e, 0xbc, 0xcb, 0x31, 0xc8, 0xeb, 0x41, 0x76, 0x8d, + 0x93, 0x77, 0x10, 0x17, 0x8b, 0x83, 0x2a, 0xbd, 0xc2, 0x1b, 0xa7, 0x6a, 0xb0, 0x1c, 0x66, 0xb8, 0x8e, 0x7f, 0x4b, + 0x0f, 0x10, 0xda, 0xf5, 0x20, 0x6b, 0x0e, 0xb2, 0xfa, 0xa0, 0x28, 0x41, 0xb2, 0x16, 0x2e, 0x1c, 0xdd, 0xf8, 0xb1, + 0x9c, 0x16, 0xd9, 0x45, 0x9a, 0x25, 0x2a, 0x4b, 0xf1, 0x53, 0xf4, 0x2e, 0xe2, 0x48, 0x1a, 0x75, 0xea, 0x75, 0xc7, + 0xdb, 0xb7, 0xb7, 0x5a, 0xd3, 0xda, 0xe3, 0xec, 0xce, 0x11, 0x0b, 0xa8, 0xfd, 0x9d, 0xa4, 0x54, 0x50, 0x18, 0xc0, + 0x89, 0x57, 0x8d, 0x87, 0x32, 0x0e, 0x5a, 0x35, 0x04, 0xcb, 0x60, 0x0d, 0x84, 0x63, 0x21, 0x6e, 0xda, 0x9b, 0x90, + 0x7e, 0x55, 0xa3, 0xf9, 0x3a, 0x5c, 0x92, 0xbc, 0x45, 0xd1, 0x86, 0x5e, 0xbf, 0x88, 0x9e, 0x02, 0x27, 0x2d, 0xbf, + 0x83, 0x7f, 0x21, 0x10, 0x06, 0xfa, 0xac, 0xfa, 0x74, 0xc5, 0xbd, 0xb5, 0xb2, 0x6c, 0x9d, 0x2c, 0xdb, 0x11, 0xd9, + 0x35, 0x81, 0x58, 0x67, 0x65, 0xa9, 0x9c, 0x2c, 0x15, 0x66, 0x41, 0x9b, 0x18, 0x1d, 0xda, 0x08, 0x21, 0x6c, 0xa7, + 0x99, 0x14, 0xa8, 0xbd, 0x84, 0xdd, 0x39, 0xd1, 0xf2, 0x24, 0x9d, 0xde, 0x58, 0xc9, 0x88, 0xe1, 0x10, 0xe3, 0x75, + 0xd0, 0x2d, 0x8d, 0x86, 0xee, 0x22, 0x3d, 0xbd, 0x2c, 0xab, 0xd7, 0x0b, 0xb6, 0x29, 0xd8, 0xee, 0x7d, 0x5d, 0xe1, + 0xeb, 0x6a, 0xef, 0xeb, 0x98, 0x2c, 0x12, 0xf6, 0xbe, 0x46, 0xdb, 0x23, 0x59, 0xd7, 0x43, 0xaf, 0x57, 0x14, 0x5b, + 0x48, 0x8f, 0xb7, 0x73, 0x25, 0xc0, 0xeb, 0x1a, 0xb7, 0xa3, 0x0e, 0xc5, 0x75, 0x66, 0xe6, 0xf8, 0xbc, 0xd5, 0xf4, + 0x71, 0xa0, 0x94, 0xa9, 0x94, 0xb2, 0x98, 0x62, 0xf4, 0x3d, 0xab, 0x01, 0xcd, 0x50, 0x69, 0xe6, 0x5b, 0x80, 0xdd, + 0xa5, 0x7b, 0xdf, 0xb7, 0xb0, 0x34, 0xb9, 0xff, 0x03, 0xf7, 0x79, 0x66, 0xbf, 0xab, 0x6a, 0x50, 0xa8, 0x92, 0x01, + 0x99, 0xab, 0xae, 0x87, 0x2a, 0xa5, 0xa5, 0xd3, 0x4b, 0xeb, 0xf2, 0x45, 0x69, 0x23, 0x67, 0x9a, 0xdf, 0x22, 0x5e, + 0x0c, 0x1c, 0xf6, 0xdb, 0x2f, 0xd2, 0x15, 0x22, 0xe3, 0x47, 0x47, 0xeb, 0xda, 0x33, 0x8f, 0xb4, 0x01, 0x6c, 0xa2, + 0xc2, 0xfb, 0x04, 0x6b, 0x85, 0xb7, 0xcf, 0x57, 0x69, 0xf2, 0x5b, 0xb7, 0x5e, 0x67, 0xad, 0x23, 0x46, 0x14, 0xc7, + 0xb7, 0xf1, 0xf8, 0x07, 0x2a, 0xae, 0xcd, 0x7d, 0x00, 0x24, 0x30, 0xfa, 0x4c, 0x8a, 0x48, 0x3d, 0xfa, 0x28, 0xf9, + 0x84, 0x9a, 0x15, 0x1d, 0x3b, 0xa6, 0x38, 0xd4, 0x22, 0xa5, 0xbf, 0x83, 0xd6, 0xf1, 0x75, 0x4a, 0xf7, 0x9a, 0xc6, + 0xea, 0x0d, 0xb4, 0x10, 0x6f, 0xfa, 0x14, 0xaf, 0x02, 0x9f, 0x6c, 0x81, 0xad, 0x91, 0x23, 0x3c, 0xab, 0xbf, 0xbd, + 0x25, 0xff, 0x56, 0x44, 0x34, 0x4a, 0x51, 0xc1, 0x8a, 0x30, 0x2f, 0xd2, 0xcd, 0xe1, 0xe3, 0x80, 0x1b, 0x95, 0xb6, + 0xad, 0x43, 0x3c, 0xbf, 0x66, 0x34, 0x65, 0x80, 0xf6, 0x9d, 0x2a, 0x28, 0xe0, 0xaa, 0x64, 0x12, 0x59, 0xf7, 0xe4, + 0xf3, 0xdb, 0xcb, 0x4d, 0x96, 0x2f, 0xde, 0x56, 0x3f, 0xd0, 0xdc, 0xea, 0x36, 0xdc, 0xb3, 0x74, 0x86, 0x28, 0x8f, + 0xdc, 0xf6, 0xa2, 0x43, 0x44, 0xb7, 0x85, 0x5a, 0x2f, 0x9c, 0xea, 0x99, 0x9e, 0xa5, 0xce, 0x49, 0xa2, 0x96, 0x1d, + 0x6a, 0x69, 0x52, 0x2d, 0xbe, 0x16, 0xfc, 0x8b, 0x9c, 0xb5, 0x41, 0x22, 0xc0, 0x60, 0x25, 0xfa, 0xb5, 0x7a, 0x69, + 0xee, 0xcc, 0x71, 0x64, 0xad, 0xc6, 0x07, 0x1e, 0xc8, 0x01, 0x84, 0x0f, 0xa3, 0xbf, 0x04, 0xf3, 0xf1, 0x82, 0xd7, + 0x1f, 0xd4, 0x22, 0xf3, 0x67, 0x5f, 0x03, 0x0c, 0xd1, 0x5d, 0x39, 0x10, 0xf5, 0x5a, 0xab, 0x53, 0x46, 0xbc, 0x21, + 0x4c, 0x34, 0xc3, 0xe6, 0xd0, 0xb2, 0x23, 0xcd, 0x3f, 0x73, 0x0d, 0x04, 0x51, 0xe2, 0x0d, 0x2c, 0x59, 0xe4, 0xd3, + 0x66, 0x0e, 0xf7, 0xa3, 0x70, 0x22, 0xdf, 0xa7, 0x70, 0xe6, 0xdd, 0xa0, 0x80, 0x11, 0xa8, 0x72, 0xda, 0x2e, 0xd1, + 0xef, 0x30, 0x47, 0xce, 0xd1, 0xaa, 0x10, 0x44, 0xf6, 0x30, 0x6b, 0x2d, 0x63, 0x82, 0xd8, 0x20, 0x5e, 0xb6, 0x2c, + 0x19, 0xd0, 0x4c, 0xa1, 0xb0, 0x4e, 0x03, 0x63, 0x44, 0x47, 0x35, 0x6a, 0x08, 0xe3, 0x55, 0x10, 0x68, 0x36, 0xb1, + 0xec, 0x02, 0x51, 0xc5, 0x86, 0x27, 0xa8, 0x76, 0x50, 0x1a, 0x9b, 0xf9, 0xe1, 0x71, 0x58, 0xc0, 0x58, 0x93, 0xce, + 0x09, 0xa8, 0x7d, 0x83, 0xc0, 0xda, 0x85, 0x1a, 0xe7, 0xb3, 0x06, 0x2d, 0x69, 0x38, 0x9e, 0x8f, 0xd1, 0xf6, 0x4a, + 0x77, 0x48, 0x6d, 0xa7, 0xb3, 0x46, 0x75, 0xa0, 0xeb, 0xc1, 0x79, 0xdf, 0x44, 0x35, 0xfb, 0x19, 0xbe, 0xf7, 0x90, + 0xc4, 0xf9, 0xf3, 0x0d, 0xf7, 0x9f, 0x72, 0x93, 0x1e, 0x06, 0x7b, 0x8b, 0xd6, 0x14, 0x1c, 0xf5, 0xf7, 0xdd, 0x40, + 0xb6, 0xb7, 0x9a, 0x65, 0x34, 0xf9, 0xec, 0xf7, 0xef, 0xaa, 0xec, 0x3a, 0x43, 0x79, 0xc9, 0xc9, 0xa2, 0x23, 0x0f, + 0xe1, 0x7d, 0xc3, 0x02, 0xc5, 0x47, 0x85, 0x47, 0x04, 0xbc, 0x0c, 0x3e, 0x9f, 0xe6, 0x78, 0x15, 0x83, 0xe2, 0x0a, + 0xc3, 0x78, 0xa4, 0x04, 0xe2, 0x61, 0x3a, 0xbd, 0x1a, 0x37, 0xa8, 0x0d, 0xdf, 0x20, 0x64, 0x06, 0x5a, 0x64, 0x2e, + 0x3d, 0x06, 0xee, 0x42, 0xd3, 0x9e, 0x3c, 0x5a, 0xf8, 0x33, 0xd3, 0xd1, 0xa4, 0xad, 0xcc, 0xf2, 0xdc, 0xf8, 0xed, + 0x40, 0xb3, 0xdc, 0x7b, 0xfe, 0xbe, 0x90, 0xdf, 0x92, 0x15, 0xb4, 0x08, 0xf7, 0x89, 0x12, 0xee, 0x73, 0xf6, 0xcb, + 0xa4, 0xe0, 0xb0, 0xc8, 0x96, 0xad, 0x22, 0x8c, 0x63, 0x54, 0x05, 0x0b, 0x4b, 0x8f, 0x55, 0xf3, 0xf6, 0x25, 0xbe, + 0x41, 0x0c, 0x3a, 0xd1, 0x59, 0xa2, 0x46, 0x67, 0x09, 0xf2, 0x8b, 0x58, 0x47, 0x9b, 0x71, 0x11, 0x2c, 0xce, 0x36, + 0xe7, 0x11, 0x05, 0xac, 0x5b, 0xc1, 0xde, 0x12, 0xb0, 0x99, 0xfc, 0x6c, 0x7d, 0x0e, 0xdc, 0x46, 0x80, 0x4a, 0x80, + 0x4a, 0xd2, 0x52, 0x4e, 0xd3, 0x8a, 0xad, 0x45, 0xdb, 0x99, 0xac, 0x4e, 0x57, 0x6e, 0x55, 0x57, 0xb6, 0x4e, 0x57, + 0x7a, 0x0d, 0x94, 0x98, 0x50, 0x2a, 0x64, 0x18, 0xc2, 0x88, 0xcd, 0x92, 0xd3, 0x9c, 0x6c, 0xbc, 0x57, 0x51, 0x82, + 0x4d, 0x24, 0xe4, 0x93, 0x80, 0x20, 0x30, 0x09, 0xc5, 0x85, 0x1b, 0xb4, 0x40, 0xc8, 0x88, 0x15, 0x3a, 0x13, 0x55, + 0x3a, 0x05, 0xd7, 0xa3, 0x69, 0x62, 0xdc, 0x76, 0x17, 0xca, 0xd7, 0xb4, 0x71, 0x47, 0xbc, 0x0d, 0x10, 0x83, 0x30, + 0xa6, 0xd8, 0x8d, 0x5b, 0xf5, 0x98, 0x44, 0xc0, 0x26, 0x75, 0x31, 0x7e, 0xf2, 0x7e, 0x8f, 0x68, 0x47, 0x04, 0x4b, + 0xb5, 0x86, 0xa0, 0xfd, 0x35, 0xac, 0xa3, 0x05, 0xad, 0xa3, 0x4d, 0xb4, 0x82, 0x1e, 0x2d, 0xd1, 0x82, 0xf6, 0x3c, + 0xc8, 0x11, 0x7f, 0xc5, 0xea, 0xd1, 0xcf, 0x8d, 0xb7, 0x44, 0xfe, 0x69, 0x63, 0x77, 0x8a, 0x12, 0x13, 0x4c, 0xd4, + 0xfd, 0xca, 0x91, 0x7f, 0x78, 0x47, 0xcb, 0xb1, 0x6f, 0x29, 0x63, 0xa4, 0x32, 0xbd, 0x4d, 0xcf, 0x15, 0x7f, 0x23, + 0xb4, 0xf4, 0xbe, 0x42, 0x48, 0x39, 0xb0, 0x04, 0x85, 0x0d, 0xfc, 0x80, 0xe4, 0x42, 0xd9, 0x41, 0x38, 0xc7, 0x27, + 0x33, 0xb0, 0x65, 0xff, 0x18, 0xa9, 0x30, 0x42, 0x38, 0xad, 0x52, 0x04, 0xa6, 0xd1, 0xc2, 0x6c, 0x6d, 0x0b, 0xb3, + 0x5a, 0xb3, 0xa5, 0x72, 0xba, 0x32, 0xb7, 0xee, 0xe7, 0x53, 0x01, 0x00, 0x13, 0x9d, 0x03, 0xc7, 0xcc, 0xc4, 0x7b, + 0x69, 0x66, 0x59, 0xde, 0xc7, 0xc5, 0x55, 0xfa, 0xb2, 0x2a, 0xaf, 0xd5, 0x50, 0x74, 0x6d, 0x66, 0x8a, 0x33, 0xd6, + 0x49, 0x28, 0x87, 0x82, 0x52, 0xf6, 0xfa, 0xfc, 0xfb, 0xf8, 0xfb, 0xb0, 0xd4, 0xc0, 0x6c, 0x35, 0xd1, 0x34, 0x69, + 0x91, 0x2a, 0x01, 0x89, 0x6c, 0x1a, 0xc8, 0x6d, 0x8e, 0xb0, 0x67, 0x4f, 0x6b, 0x72, 0x10, 0xeb, 0x8d, 0x19, 0x33, + 0x75, 0xc8, 0xf5, 0xe0, 0x45, 0x88, 0xbe, 0xd3, 0xa7, 0xc7, 0x80, 0x02, 0x5d, 0xe0, 0x5d, 0x88, 0xbe, 0xe0, 0xa7, + 0x47, 0xbc, 0x9f, 0x45, 0xe6, 0x31, 0x2b, 0xde, 0x60, 0x52, 0xff, 0x82, 0xd3, 0x1a, 0x53, 0xb4, 0x41, 0x92, 0xc9, + 0x24, 0x1d, 0xbc, 0xd0, 0x97, 0xa3, 0x23, 0x24, 0xd9, 0x85, 0x70, 0x75, 0xd0, 0x3e, 0x45, 0xb6, 0xb5, 0x1d, 0x44, + 0x97, 0x71, 0x44, 0x67, 0xed, 0x9c, 0x10, 0xc1, 0xcc, 0x24, 0x22, 0xd5, 0x22, 0xdd, 0xed, 0x3e, 0xb5, 0x2c, 0xe9, + 0x6d, 0xf7, 0x29, 0x75, 0xdb, 0x80, 0xca, 0xae, 0x28, 0xd3, 0xa2, 0x8d, 0x3e, 0xe4, 0xc0, 0x8c, 0x2b, 0xc2, 0x63, + 0xc5, 0x69, 0x87, 0x83, 0x18, 0x68, 0x0f, 0x2c, 0x7a, 0x19, 0xf5, 0xab, 0x68, 0x79, 0x16, 0xcb, 0x50, 0xcb, 0xe5, + 0xce, 0xaf, 0xde, 0x52, 0xad, 0x07, 0xff, 0xee, 0x6e, 0x85, 0xfe, 0x46, 0x8b, 0xd3, 0xab, 0x56, 0x48, 0x17, 0x90, + 0xad, 0x0a, 0x51, 0xbf, 0x0f, 0xd7, 0x44, 0x6f, 0xa9, 0xfd, 0xc3, 0xcb, 0x20, 0x07, 0x3a, 0x4f, 0x3b, 0xa6, 0xf4, + 0xd9, 0x01, 0x0c, 0x0f, 0xa7, 0x92, 0xb5, 0xc0, 0x9b, 0xa8, 0x9a, 0x9c, 0xcc, 0x36, 0x7c, 0xa5, 0xbb, 0xf1, 0x29, + 0x76, 0x16, 0xaa, 0x7a, 0xbf, 0xa2, 0x3a, 0xb9, 0x90, 0x68, 0xed, 0x3d, 0xf8, 0x34, 0xcf, 0x39, 0x17, 0x2f, 0xdc, + 0xfb, 0xf8, 0x2b, 0x3d, 0x81, 0x85, 0x72, 0x25, 0x20, 0xf4, 0x1b, 0xeb, 0xc6, 0x26, 0xed, 0xde, 0xd8, 0xe0, 0x56, + 0xaa, 0xcf, 0xf5, 0x6e, 0xfa, 0x15, 0xa4, 0x20, 0x5c, 0xa9, 0x74, 0x8d, 0x53, 0x19, 0x0d, 0x38, 0x2d, 0xa3, 0xf8, + 0xf6, 0x2d, 0xf0, 0x19, 0xcb, 0x1c, 0x6f, 0xb2, 0x95, 0x3b, 0xc5, 0x49, 0xab, 0xce, 0x88, 0xa7, 0xc5, 0x42, 0x01, + 0x74, 0xdc, 0xc7, 0x00, 0x2a, 0x01, 0x81, 0x14, 0xd1, 0xc2, 0xbd, 0x95, 0x9a, 0x2d, 0xd4, 0x04, 0x47, 0x29, 0xfc, + 0x29, 0xfc, 0x79, 0x58, 0xcc, 0x11, 0x88, 0x5d, 0x1f, 0x47, 0x20, 0xd1, 0xf0, 0xa7, 0xca, 0xb3, 0x42, 0x26, 0x13, + 0xa3, 0xab, 0x73, 0x30, 0xd4, 0x1a, 0xf3, 0xd6, 0x43, 0x79, 0x6b, 0x93, 0xb7, 0x35, 0x46, 0xc9, 0xf7, 0x48, 0x3a, + 0xd6, 0x8c, 0xa1, 0x55, 0x9e, 0xd6, 0x69, 0x22, 0x37, 0x79, 0x81, 0x11, 0x86, 0xa2, 0x9b, 0xdc, 0x7b, 0xea, 0x39, + 0x5c, 0x15, 0x26, 0x07, 0xb7, 0xb9, 0xa7, 0x04, 0x51, 0x2d, 0x72, 0x6a, 0xee, 0xcc, 0x19, 0x47, 0x04, 0xef, 0x31, + 0x2d, 0x69, 0xd9, 0x46, 0xb8, 0xcb, 0xc5, 0x37, 0xb7, 0x4a, 0x8c, 0x97, 0x4b, 0xe7, 0xe1, 0xed, 0xcb, 0x32, 0x0d, + 0xd9, 0x29, 0xa4, 0x9c, 0xf3, 0x29, 0x2c, 0x27, 0x04, 0x53, 0x3c, 0xd7, 0xbb, 0x55, 0x2b, 0xb4, 0xee, 0xee, 0x8e, + 0xb5, 0xa1, 0x90, 0x56, 0x67, 0x61, 0xb4, 0x1a, 0x31, 0x4a, 0x40, 0x17, 0x1c, 0xa7, 0x63, 0x7b, 0x22, 0xee, 0xf2, + 0x69, 0xc4, 0xb7, 0x57, 0x8a, 0xd3, 0xc5, 0x15, 0x24, 0x3f, 0xd9, 0xea, 0x19, 0xd1, 0x12, 0xd0, 0xa0, 0x08, 0x98, + 0x88, 0x18, 0x8e, 0x29, 0x84, 0x4e, 0xc7, 0x83, 0x4a, 0x43, 0x73, 0xd6, 0x70, 0x48, 0xcd, 0x16, 0xa2, 0xaa, 0x04, + 0xb1, 0x4c, 0x99, 0x19, 0x1c, 0x1d, 0xe5, 0xf3, 0x4a, 0x99, 0x00, 0x84, 0x0b, 0x5d, 0x19, 0x22, 0x1e, 0x69, 0xe6, + 0xc9, 0x0a, 0x9f, 0xb2, 0xf2, 0x2b, 0xa8, 0xc9, 0x64, 0x23, 0x19, 0x98, 0xc0, 0x66, 0x5c, 0x93, 0x94, 0xf9, 0x88, + 0xeb, 0x1f, 0x19, 0x3d, 0xb5, 0x2d, 0xd6, 0xea, 0x1b, 0xb1, 0xa1, 0x83, 0x0b, 0x3a, 0x35, 0xa7, 0x17, 0x15, 0x33, + 0xde, 0x2f, 0x1c, 0xc9, 0x48, 0xd9, 0x5a, 0x14, 0x7e, 0xd8, 0xcd, 0xd4, 0xc9, 0x41, 0x33, 0x50, 0x50, 0x13, 0x15, + 0xbf, 0x3e, 0x74, 0x08, 0xf6, 0x64, 0xa5, 0x92, 0xf8, 0xe0, 0x27, 0xc8, 0x46, 0x37, 0xa7, 0x83, 0x2d, 0xd4, 0x91, + 0x46, 0x95, 0x45, 0xd0, 0xbe, 0x03, 0x78, 0x56, 0x02, 0xb3, 0xa7, 0xd4, 0x8f, 0x30, 0xec, 0xe6, 0x21, 0x7a, 0x9b, + 0x6b, 0x29, 0xc4, 0x78, 0x39, 0x35, 0x04, 0x89, 0x2b, 0x9c, 0xc4, 0xa2, 0xbf, 0x69, 0xe1, 0x15, 0x8c, 0x7c, 0x54, + 0xab, 0xfa, 0xd5, 0xa9, 0x0a, 0x9c, 0xa4, 0xbe, 0x4b, 0x88, 0x1a, 0xb6, 0x0f, 0x91, 0x3d, 0x6f, 0x7d, 0xdd, 0x55, + 0x86, 0x3e, 0x97, 0x06, 0x0c, 0x38, 0x5b, 0x59, 0x4a, 0x0e, 0xfc, 0xa4, 0x92, 0x55, 0xeb, 0xce, 0xe7, 0xd4, 0xdd, + 0x48, 0x64, 0xf2, 0x6b, 0xdf, 0xc1, 0xa9, 0xb2, 0x1b, 0x3c, 0x4c, 0x31, 0x7a, 0x0d, 0xde, 0x37, 0x17, 0x41, 0xd9, + 0xde, 0x3b, 0xa5, 0x5d, 0x86, 0x46, 0x32, 0x77, 0x73, 0x0d, 0x4b, 0xcb, 0xd3, 0x6c, 0x81, 0x9e, 0x69, 0xf7, 0x2c, + 0x07, 0x3b, 0xa2, 0xfc, 0xd7, 0xd4, 0xdf, 0xa9, 0x9c, 0x1c, 0xdf, 0xf6, 0x95, 0x41, 0x54, 0x3d, 0x7d, 0x21, 0x63, + 0xad, 0x30, 0xba, 0x65, 0x97, 0xad, 0xd0, 0x61, 0xb4, 0x94, 0x24, 0x88, 0xc6, 0x8f, 0x84, 0x72, 0x84, 0xa0, 0x8b, + 0xec, 0x30, 0x11, 0xed, 0xd3, 0x76, 0x1f, 0x1d, 0xad, 0x33, 0x8f, 0xcd, 0xbb, 0x62, 0x75, 0x67, 0xf9, 0x91, 0x61, + 0xec, 0x67, 0xca, 0xb0, 0xa9, 0x2f, 0x22, 0xaf, 0xa2, 0xbc, 0x33, 0x60, 0x43, 0x92, 0x32, 0x2a, 0x8b, 0x81, 0x50, + 0xcc, 0xcf, 0x7e, 0x79, 0xb0, 0x6b, 0x5a, 0x8a, 0x42, 0xfe, 0x4b, 0x30, 0xa2, 0x60, 0xd0, 0x23, 0x74, 0x85, 0x18, + 0x9d, 0x87, 0x67, 0xf4, 0x07, 0xe4, 0xbe, 0xfc, 0x2b, 0x24, 0xea, 0x15, 0x62, 0x8e, 0xb8, 0x56, 0x7a, 0x2a, 0x6c, + 0x3d, 0x4a, 0x81, 0xc1, 0x9a, 0x84, 0xb7, 0xee, 0x1e, 0x28, 0xd8, 0x8f, 0xff, 0x0a, 0x3e, 0x21, 0x43, 0x8d, 0x76, + 0x7a, 0xea, 0xee, 0xc0, 0x23, 0x41, 0x08, 0x43, 0x0c, 0x4b, 0xe7, 0xaf, 0x2c, 0xc3, 0x19, 0xfd, 0x3b, 0x4a, 0x02, + 0x72, 0x16, 0x91, 0x8f, 0x2f, 0xab, 0x34, 0xfd, 0x9c, 0x7a, 0x30, 0x4e, 0x57, 0x6c, 0x94, 0x79, 0xa5, 0xa7, 0xd1, + 0x35, 0x49, 0xfa, 0x2a, 0xfe, 0xd8, 0x9a, 0xb6, 0x5f, 0xb4, 0xf9, 0xcd, 0x84, 0x40, 0x08, 0x65, 0xfe, 0x9c, 0xd9, + 0x89, 0x8d, 0x0d, 0xab, 0xa1, 0xf4, 0xba, 0xdc, 0x21, 0x09, 0xd8, 0x1a, 0x0d, 0xb0, 0x3f, 0x75, 0x8b, 0x68, 0xa5, + 0xa6, 0x4e, 0xb7, 0x75, 0x70, 0xf2, 0xf0, 0x42, 0x16, 0xf2, 0x7e, 0x79, 0x5a, 0x60, 0xec, 0x12, 0xc8, 0xd8, 0x51, + 0x6d, 0x6c, 0x7a, 0xaa, 0x0d, 0x38, 0x04, 0xd1, 0x9a, 0xad, 0x55, 0xcb, 0x0a, 0x05, 0xa4, 0x4b, 0xbc, 0x1c, 0x4e, + 0x10, 0x64, 0xc5, 0x18, 0x1e, 0x19, 0x4c, 0x60, 0x4c, 0x37, 0x21, 0x0a, 0xd0, 0xc2, 0xa3, 0x3f, 0x09, 0x39, 0x42, + 0xba, 0xd0, 0xa1, 0x61, 0x5f, 0x09, 0x29, 0x3b, 0xcf, 0xc3, 0x46, 0x4d, 0xa1, 0xef, 0x6c, 0x54, 0xe7, 0xfe, 0x48, + 0x5b, 0xc5, 0xba, 0xb7, 0x4a, 0xbd, 0xbb, 0x3a, 0x44, 0xdf, 0x10, 0xc7, 0xb7, 0x01, 0x12, 0x80, 0xde, 0x12, 0x3f, + 0x67, 0xf0, 0x61, 0xf1, 0x59, 0xe1, 0x51, 0xbf, 0x30, 0xfd, 0x7a, 0x21, 0x37, 0x0a, 0xa4, 0xb8, 0xed, 0xb4, 0xb6, + 0xc7, 0xe7, 0xdf, 0x4f, 0x75, 0xb4, 0xe1, 0xb3, 0xd3, 0x62, 0x8b, 0x29, 0x73, 0xab, 0xe7, 0x60, 0x75, 0x4c, 0x52, + 0x9d, 0xe6, 0x23, 0x2e, 0x35, 0xab, 0xce, 0x0c, 0x0c, 0xaf, 0x61, 0xa0, 0xdc, 0x4a, 0x24, 0x8c, 0xc3, 0xce, 0xf9, + 0x24, 0xc8, 0xc8, 0x6e, 0x95, 0x88, 0x04, 0xb6, 0xb1, 0xb5, 0x8b, 0x46, 0xfc, 0x82, 0xc1, 0xa9, 0xbb, 0xa1, 0xea, + 0xd1, 0x1a, 0x2f, 0x74, 0x68, 0xa7, 0xb5, 0x81, 0x10, 0x0a, 0x5b, 0xf3, 0x72, 0x78, 0xee, 0x0e, 0x35, 0x4b, 0x79, + 0x19, 0x81, 0x10, 0xf0, 0x73, 0x46, 0x8a, 0xd8, 0x7d, 0xd5, 0xa9, 0xdb, 0x6f, 0xb7, 0xce, 0x8b, 0xda, 0xe2, 0x37, + 0xb8, 0xa1, 0x8d, 0x3a, 0x6a, 0x6a, 0xd7, 0xcc, 0x55, 0x73, 0x26, 0x84, 0xd1, 0xbd, 0xbf, 0xd5, 0x85, 0xd3, 0xee, + 0x9d, 0xf2, 0x23, 0xc6, 0x00, 0x37, 0xc3, 0xf3, 0x43, 0x93, 0xd0, 0x2a, 0x2f, 0x17, 0x22, 0x94, 0x56, 0x93, 0x94, + 0x42, 0xde, 0x6a, 0x68, 0x11, 0xe8, 0x23, 0x00, 0x3d, 0xc0, 0xe0, 0xcd, 0x1f, 0x2c, 0x74, 0x4c, 0x07, 0x0f, 0x7e, + 0x4d, 0xf6, 0xb1, 0x55, 0x7e, 0xbf, 0x46, 0x5a, 0x11, 0x8e, 0x59, 0xa3, 0x46, 0xd9, 0xaa, 0x5e, 0x86, 0xd7, 0x69, + 0x18, 0xbe, 0xff, 0xdf, 0xfb, 0x00, 0x77, 0xa2, 0xa3, 0x0c, 0xee, 0x88, 0x06, 0x74, 0xf9, 0xd0, 0x67, 0xbe, 0xe9, + 0x43, 0xc6, 0xf8, 0xe0, 0x8c, 0x0c, 0xd5, 0xce, 0xd1, 0x00, 0xc1, 0x51, 0xdd, 0xd3, 0x65, 0xc2, 0x59, 0x7c, 0xee, + 0xa1, 0xa3, 0xfa, 0xcc, 0x7d, 0x17, 0x69, 0x2b, 0x68, 0xe3, 0xf8, 0x64, 0x89, 0x6b, 0x2a, 0x00, 0x2e, 0x61, 0x63, + 0x12, 0x86, 0xb8, 0x74, 0x89, 0xd5, 0x37, 0xc7, 0xa8, 0x00, 0x28, 0x9f, 0xd4, 0xcc, 0x97, 0x5e, 0x64, 0x45, 0x9d, + 0x56, 0x8d, 0xee, 0x06, 0x6c, 0xe5, 0x09, 0x02, 0x3c, 0x84, 0xe5, 0x69, 0x6d, 0x16, 0x34, 0x61, 0x03, 0xa9, 0x2c, + 0x50, 0xe7, 0x04, 0xb8, 0xe5, 0x6e, 0x49, 0x36, 0xd2, 0x3b, 0x3c, 0xee, 0x9c, 0x3b, 0xb6, 0xdc, 0x51, 0x0a, 0xb7, + 0x47, 0x6c, 0x42, 0xca, 0xf0, 0xd3, 0xda, 0x9d, 0x3f, 0x8f, 0x9e, 0x30, 0x98, 0x8a, 0x74, 0x63, 0x1c, 0x21, 0x13, + 0x91, 0x1b, 0xbb, 0xe7, 0xf8, 0x49, 0x54, 0xcd, 0xe2, 0xc9, 0xc4, 0x47, 0x7d, 0x68, 0x04, 0xff, 0x4c, 0xd2, 0x73, + 0xb1, 0x5e, 0xc7, 0xdb, 0x3a, 0x10, 0x5a, 0x66, 0x31, 0xa1, 0x85, 0xc6, 0x3e, 0x1a, 0xaf, 0xbb, 0xd7, 0x11, 0x16, + 0x03, 0x34, 0x73, 0xf4, 0x65, 0x40, 0xe9, 0x3d, 0x7d, 0xd1, 0x22, 0xec, 0xa2, 0x51, 0x66, 0x07, 0x85, 0x0c, 0xc2, + 0xc6, 0xbd, 0xb7, 0x20, 0x28, 0x9e, 0x40, 0xdf, 0x50, 0x75, 0xde, 0xea, 0xfd, 0xdc, 0x76, 0xc7, 0xee, 0x5e, 0xb5, + 0x4a, 0x4f, 0x67, 0x6d, 0x86, 0x52, 0xab, 0x5b, 0xa6, 0xf5, 0x3a, 0xcf, 0x12, 0x6e, 0xdc, 0xac, 0x70, 0x2f, 0xb5, + 0xf0, 0x93, 0x2d, 0x0b, 0x53, 0x76, 0xb6, 0x96, 0x16, 0x8e, 0x9c, 0x4b, 0x6e, 0xfd, 0xee, 0xba, 0x62, 0xd9, 0xa9, + 0xf1, 0x2d, 0xc0, 0xbd, 0x33, 0xea, 0xc8, 0x39, 0x0c, 0x2d, 0xad, 0xc7, 0xf4, 0x9c, 0x3f, 0x62, 0x1f, 0x33, 0x7c, + 0x07, 0x83, 0x3a, 0x0a, 0xb1, 0xbf, 0xb6, 0xae, 0x23, 0x11, 0x93, 0x1f, 0xf8, 0xa3, 0xf6, 0xa2, 0x2c, 0x70, 0x33, + 0xbe, 0xdb, 0x90, 0xa7, 0xb1, 0xda, 0x83, 0x71, 0x75, 0x45, 0xc8, 0x9f, 0xda, 0x1c, 0xd3, 0x34, 0xc7, 0x3b, 0x1b, + 0x75, 0xd6, 0xd7, 0x48, 0x9f, 0xea, 0xfa, 0xfc, 0xb7, 0xe5, 0x97, 0x49, 0x13, 0xd8, 0x1f, 0x42, 0x57, 0xda, 0x9d, + 0x5b, 0x9d, 0x3b, 0x13, 0xa3, 0xbe, 0xd2, 0xcc, 0xae, 0xed, 0x24, 0x38, 0x31, 0xb5, 0x7d, 0x60, 0xd3, 0xab, 0x2f, + 0xd4, 0x77, 0xec, 0x14, 0x31, 0xc3, 0xbf, 0x4b, 0x35, 0x45, 0xd9, 0xd7, 0x12, 0xf4, 0x68, 0xd2, 0x86, 0xc4, 0xdd, + 0x51, 0x99, 0x3c, 0x9e, 0x15, 0xdd, 0x1a, 0x7a, 0x43, 0x13, 0x14, 0xe6, 0xdb, 0x3f, 0x14, 0xf5, 0x60, 0x83, 0xbb, + 0x85, 0x8e, 0x83, 0xee, 0xa7, 0xd0, 0xaf, 0xea, 0x37, 0xef, 0x01, 0x70, 0xc6, 0xc2, 0xff, 0x43, 0x2e, 0x34, 0xf5, + 0x93, 0xb4, 0x9e, 0x9c, 0xc2, 0xd8, 0x60, 0xf6, 0xfb, 0xfe, 0x4b, 0x2b, 0x5c, 0xa3, 0x0b, 0x14, 0x19, 0x21, 0xe8, + 0xdc, 0x49, 0xc0, 0x78, 0xbf, 0xc7, 0xb4, 0xf6, 0x4f, 0x7f, 0x54, 0x8b, 0x23, 0x26, 0xb4, 0x8b, 0x78, 0x8c, 0x10, + 0x77, 0x3a, 0x94, 0x75, 0xac, 0x01, 0xfa, 0x30, 0x80, 0x75, 0xec, 0xdb, 0x11, 0xc0, 0x51, 0x1f, 0x6d, 0xde, 0x25, + 0xc8, 0xaf, 0x7b, 0x37, 0xc1, 0x9b, 0x60, 0x0b, 0x7c, 0xf9, 0x75, 0x06, 0x3f, 0x91, 0xce, 0x02, 0x71, 0x9a, 0x9f, + 0x84, 0x06, 0x17, 0x3a, 0x78, 0xf3, 0x30, 0x0d, 0xb6, 0xc1, 0xf6, 0x21, 0x21, 0x15, 0x0d, 0xe7, 0x9f, 0x9c, 0x18, + 0xef, 0x79, 0xa7, 0xc0, 0x55, 0xb4, 0x44, 0xf0, 0x40, 0x60, 0x42, 0x83, 0x6b, 0xf8, 0xf9, 0x43, 0xb0, 0x42, 0x35, + 0xf9, 0x65, 0xb4, 0xf6, 0xbe, 0xe7, 0xd4, 0x0b, 0xfc, 0x39, 0xe6, 0xf4, 0x59, 0x11, 0x79, 0x57, 0x93, 0x4b, 0xff, + 0xd1, 0x63, 0x34, 0x9e, 0xb8, 0x9e, 0x5c, 0xe0, 0xaf, 0x32, 0x9a, 0x78, 0x57, 0x63, 0x4a, 0xac, 0xe0, 0xe7, 0xf5, + 0x18, 0x53, 0x15, 0x2e, 0x24, 0xf9, 0x3e, 0xfc, 0x14, 0x16, 0x01, 0xfd, 0xf8, 0x59, 0x63, 0xb0, 0xfe, 0x04, 0x8c, + 0x8f, 0x42, 0x63, 0xad, 0x94, 0xcb, 0xd2, 0x22, 0x3d, 0x48, 0xf1, 0x4e, 0x78, 0x31, 0x6c, 0x8b, 0x55, 0x6f, 0xd6, + 0x29, 0xff, 0xbc, 0xc7, 0xf6, 0xe8, 0x58, 0x89, 0xca, 0x45, 0x5a, 0xbd, 0xa7, 0xf0, 0xa9, 0x8e, 0x8d, 0x51, 0xb9, + 0x69, 0x86, 0xd3, 0xb9, 0x55, 0x03, 0x69, 0x3f, 0x2b, 0xd3, 0x60, 0xc7, 0xea, 0xa4, 0x77, 0x53, 0x78, 0x30, 0x70, + 0xcf, 0xcb, 0xa7, 0xc4, 0xc0, 0xc5, 0xf8, 0xf0, 0xad, 0x9e, 0xb9, 0x28, 0x2f, 0x0c, 0xc6, 0x74, 0x19, 0x25, 0xd1, + 0x93, 0x71, 0x21, 0xae, 0x31, 0xef, 0x28, 0x1a, 0x86, 0xb2, 0xa1, 0xa5, 0x08, 0x09, 0x49, 0x34, 0x22, 0x25, 0x60, + 0xf7, 0x06, 0x65, 0x56, 0xe2, 0x59, 0x34, 0xfe, 0x19, 0x04, 0x38, 0x8c, 0x7b, 0x90, 0xf8, 0xad, 0x98, 0x94, 0x0b, + 0x36, 0x3a, 0xc7, 0x58, 0x4e, 0xb5, 0xf1, 0xb8, 0xfe, 0x3a, 0x39, 0xf5, 0x7b, 0x15, 0x6c, 0x22, 0xe4, 0xb3, 0xdf, + 0x4b, 0xd4, 0x69, 0x63, 0x89, 0xf1, 0x6f, 0x57, 0xf9, 0xa7, 0xc2, 0x52, 0x4f, 0xfe, 0xf3, 0x98, 0x5d, 0xe9, 0x9f, + 0x67, 0x55, 0xb2, 0xb9, 0x5e, 0xa6, 0x55, 0x5a, 0x24, 0xe9, 0xde, 0x62, 0x89, 0x9d, 0xcb, 0x77, 0x3e, 0x45, 0x76, + 0x01, 0x74, 0xb3, 0xcf, 0x90, 0xd1, 0x3f, 0x82, 0x28, 0x3f, 0xf9, 0x51, 0x1b, 0xd7, 0x16, 0xb0, 0xcd, 0x8a, 0x53, + 0x8b, 0x76, 0x3b, 0x56, 0x24, 0x46, 0x31, 0x56, 0xf8, 0x6a, 0x98, 0x95, 0x11, 0x95, 0x4c, 0x8c, 0x98, 0x26, 0x03, + 0x57, 0x2f, 0x04, 0x69, 0xd0, 0xac, 0x04, 0x3d, 0xaa, 0xd0, 0x7a, 0x2c, 0x52, 0xbe, 0x8f, 0x78, 0x75, 0x3d, 0x20, + 0x8c, 0x0e, 0x94, 0x33, 0x56, 0x9d, 0xc4, 0x2b, 0xb8, 0xc3, 0x48, 0xf7, 0x09, 0x03, 0xe3, 0x34, 0x6b, 0xac, 0x1b, + 0x0e, 0x44, 0xee, 0x4a, 0xcd, 0xcd, 0x06, 0x88, 0x19, 0x30, 0x43, 0x7a, 0x4f, 0x49, 0x5d, 0x88, 0x45, 0x67, 0xd7, + 0x11, 0xa6, 0x93, 0xa6, 0x6d, 0xf7, 0xe8, 0x78, 0x59, 0x70, 0xde, 0x69, 0x15, 0x29, 0x5a, 0x46, 0xa7, 0x03, 0x6b, + 0xd3, 0xe1, 0x6e, 0x8c, 0xf6, 0xf6, 0x99, 0x41, 0x48, 0xf1, 0xfc, 0xb1, 0xad, 0xd6, 0xa5, 0x4d, 0x02, 0x9c, 0xcb, + 0xd8, 0x99, 0xde, 0x7a, 0x1d, 0x27, 0x78, 0x8d, 0x17, 0x9b, 0x4e, 0x18, 0xa7, 0x4c, 0x01, 0x8b, 0xd6, 0xe8, 0xd0, + 0xfe, 0xa4, 0x42, 0xea, 0x71, 0x4c, 0x00, 0x81, 0x2a, 0xd3, 0xb3, 0xb8, 0xb3, 0x60, 0x36, 0x0d, 0x6c, 0x5e, 0xbc, + 0xc6, 0xa3, 0x0b, 0x61, 0x7d, 0x11, 0xf3, 0x1e, 0x3e, 0xf3, 0x2f, 0xaa, 0xc6, 0xb6, 0x04, 0x82, 0x9e, 0x3a, 0x43, + 0x03, 0xec, 0xa4, 0x1a, 0xb5, 0x45, 0x6b, 0x15, 0xee, 0x2e, 0xb9, 0x40, 0x51, 0xac, 0x8d, 0xa2, 0x58, 0x4b, 0x4d, + 0xb1, 0xd6, 0x9a, 0x62, 0x5d, 0xb5, 0x11, 0x1c, 0x03, 0x0b, 0xa0, 0x89, 0x09, 0x92, 0x4d, 0xd5, 0x21, 0xec, 0xc6, + 0x06, 0x68, 0xa7, 0xa7, 0x3a, 0x86, 0x09, 0x4b, 0xa0, 0xa0, 0x7d, 0x0c, 0x7f, 0xc4, 0x32, 0xe2, 0x2e, 0xdf, 0x50, + 0xdc, 0x4d, 0x67, 0x47, 0x11, 0x79, 0x0a, 0x2e, 0xfc, 0xe0, 0x8d, 0x29, 0x79, 0xf3, 0x30, 0xc1, 0xdc, 0x5b, 0xa0, + 0xef, 0x93, 0x37, 0xfe, 0x23, 0xdd, 0x03, 0x59, 0xcc, 0xb2, 0x02, 0x79, 0x20, 0x3e, 0xa2, 0xb7, 0xb2, 0xa7, 0x6c, + 0x27, 0x84, 0xb2, 0xad, 0x1f, 0xde, 0xb8, 0x64, 0xed, 0x0a, 0x12, 0xea, 0x29, 0xfb, 0x8a, 0xf3, 0x12, 0x89, 0xf3, + 0x64, 0x83, 0x71, 0xea, 0xa5, 0xfc, 0x00, 0xc5, 0x9c, 0x6c, 0x1f, 0x0e, 0x0c, 0xbc, 0xac, 0x01, 0x7b, 0xf8, 0x7b, + 0x44, 0xd8, 0xdc, 0xd2, 0x75, 0x2a, 0x85, 0x2a, 0x73, 0x8d, 0xe7, 0xd0, 0xe3, 0x4f, 0x8f, 0x35, 0x36, 0x08, 0xe9, + 0xfa, 0x9c, 0x49, 0x1d, 0xa0, 0xbe, 0xc6, 0xed, 0x72, 0x60, 0x5d, 0xeb, 0x96, 0x77, 0x77, 0x9e, 0xf2, 0x0d, 0x41, + 0x1d, 0xbe, 0x56, 0x37, 0xc8, 0xaf, 0x94, 0x96, 0x08, 0x02, 0x39, 0xf4, 0xb7, 0x3c, 0x8d, 0x7d, 0x96, 0x67, 0xcd, + 0x96, 0xb4, 0x16, 0xb5, 0x75, 0x33, 0xac, 0xad, 0x1f, 0xb4, 0x62, 0x58, 0x34, 0xfd, 0xf3, 0xe3, 0xd0, 0x1d, 0x6c, + 0xb7, 0x31, 0x76, 0x1d, 0x0f, 0xcb, 0x47, 0x3f, 0xee, 0x67, 0xca, 0xb5, 0x99, 0xb7, 0xb1, 0x9b, 0x56, 0x5b, 0x96, + 0xf7, 0x7a, 0x1c, 0x55, 0xd6, 0x8d, 0x08, 0x3a, 0x30, 0xf4, 0x94, 0x5d, 0xc0, 0x88, 0x78, 0x8c, 0xd5, 0x3d, 0x8e, + 0x25, 0xf2, 0x02, 0x2c, 0xca, 0x05, 0x26, 0x22, 0x6c, 0x77, 0xac, 0xa2, 0x2d, 0x40, 0xe2, 0x26, 0x2a, 0x8f, 0x8e, + 0x72, 0x35, 0x28, 0x7c, 0xbb, 0xb3, 0x8c, 0x36, 0xaa, 0x3b, 0xd6, 0x54, 0x03, 0x0f, 0xa2, 0x93, 0xad, 0x79, 0xee, + 0x2a, 0x3e, 0xae, 0xba, 0x8a, 0x8f, 0x6b, 0x6b, 0x5f, 0xba, 0xe2, 0x3d, 0xa9, 0x0b, 0x90, 0xf4, 0x5f, 0xf6, 0x77, + 0x2e, 0x2c, 0x53, 0x06, 0xf8, 0xba, 0x80, 0x63, 0xe1, 0x82, 0xac, 0x4a, 0x2e, 0xfc, 0xcb, 0xb1, 0x9a, 0x7f, 0x67, + 0x70, 0x2f, 0x10, 0xba, 0x92, 0xf3, 0xa6, 0x98, 0x77, 0x5c, 0x50, 0x6e, 0x19, 0xca, 0x9b, 0xbd, 0x65, 0x60, 0x1f, + 0x36, 0x67, 0x17, 0xe7, 0xb0, 0xf9, 0x76, 0xb7, 0xe1, 0x6a, 0x6c, 0x6f, 0xab, 0x60, 0x1b, 0x2e, 0xec, 0x84, 0x9f, + 0x19, 0x98, 0xf2, 0x29, 0x3a, 0x71, 0x85, 0x97, 0xe8, 0x87, 0x27, 0x3f, 0xc7, 0x37, 0x1d, 0x62, 0x7d, 0x13, 0x58, + 0x83, 0x03, 0xb4, 0xc5, 0x1a, 0xc1, 0x70, 0xd9, 0xce, 0xae, 0x8f, 0x8e, 0xbc, 0xad, 0x36, 0x7c, 0xba, 0x12, 0x9d, + 0xd8, 0x7e, 0xad, 0xd6, 0x45, 0xf0, 0x46, 0xb4, 0x2e, 0xe6, 0xa2, 0x07, 0x03, 0x3f, 0xc1, 0x50, 0xdc, 0x0c, 0xec, + 0x2d, 0x60, 0x02, 0x2f, 0x80, 0x05, 0xac, 0x09, 0x82, 0xc0, 0xdd, 0xf6, 0x7b, 0x95, 0x0b, 0xda, 0xb0, 0x4b, 0xf0, + 0x70, 0x8e, 0x83, 0x5a, 0x5d, 0xb3, 0xce, 0xea, 0xd3, 0x74, 0xd0, 0xa7, 0xb3, 0xd1, 0xeb, 0xb9, 0x3e, 0x9f, 0x95, + 0x1a, 0xd4, 0xef, 0x90, 0xe3, 0x11, 0x95, 0x83, 0x27, 0xb0, 0xb5, 0xad, 0xd0, 0x57, 0xfb, 0x10, 0x6f, 0x6b, 0xb5, + 0x49, 0xbf, 0x57, 0x8c, 0x23, 0x3b, 0xe4, 0xb0, 0x76, 0x0c, 0x6a, 0xf7, 0xec, 0xa8, 0xfb, 0xee, 0x8e, 0x89, 0xf8, + 0xe9, 0xed, 0x8f, 0x46, 0x58, 0xd2, 0xb0, 0xc6, 0x9f, 0xff, 0xf8, 0xd0, 0xd3, 0xdf, 0x81, 0x6d, 0x91, 0xfa, 0xe1, + 0xf1, 0x1f, 0xbd, 0xf7, 0xb5, 0x36, 0xa3, 0xba, 0xab, 0x9c, 0xd2, 0xe5, 0x6e, 0x2d, 0x57, 0x96, 0x1f, 0x53, 0x2f, + 0xb5, 0x36, 0x3c, 0x1c, 0x88, 0x6a, 0x8b, 0xe6, 0x25, 0xee, 0xb0, 0xce, 0x95, 0x70, 0x4d, 0xbe, 0x5a, 0xfa, 0xd6, + 0xb7, 0x6c, 0xdb, 0xfe, 0xe1, 0xdc, 0x18, 0x94, 0x29, 0x87, 0x32, 0x52, 0x33, 0xdc, 0x08, 0x4b, 0x33, 0xf6, 0x90, + 0xdd, 0xd9, 0xce, 0x8d, 0x83, 0xbc, 0x94, 0xba, 0x38, 0x27, 0x67, 0xc2, 0x1e, 0x1b, 0x05, 0xab, 0x6c, 0x57, 0xb1, + 0xe7, 0xb4, 0xcd, 0x07, 0xd5, 0x04, 0xfb, 0xc8, 0xac, 0x04, 0x91, 0x22, 0xcd, 0x14, 0xa9, 0x1b, 0x7f, 0xdb, 0x41, + 0x17, 0xe3, 0x02, 0xea, 0x66, 0x34, 0xdd, 0x0f, 0x83, 0x0c, 0x75, 0xcf, 0xd2, 0xc7, 0x88, 0x58, 0x02, 0xd4, 0xf6, + 0x34, 0xcf, 0xae, 0x50, 0x85, 0x3f, 0xa2, 0xdd, 0xc4, 0xd1, 0xcf, 0x2d, 0x2e, 0x2a, 0xb1, 0x91, 0xde, 0xe8, 0x36, + 0x78, 0x4a, 0xb7, 0x29, 0xcf, 0x9c, 0x54, 0x3b, 0xe8, 0xbc, 0xc3, 0xe4, 0x58, 0x63, 0x6b, 0x31, 0xa3, 0x43, 0xde, + 0x8b, 0x3b, 0x47, 0xef, 0xb9, 0xbf, 0xe9, 0x85, 0x3f, 0x73, 0xd9, 0x3c, 0x21, 0x23, 0xd8, 0xb6, 0x92, 0xdb, 0xf6, + 0x56, 0x25, 0x58, 0xb0, 0xa4, 0xc3, 0xc7, 0xef, 0x60, 0xeb, 0x90, 0x55, 0xa6, 0x26, 0x7d, 0x89, 0x13, 0xf6, 0xd2, + 0xf1, 0x20, 0x53, 0x55, 0xd8, 0xc3, 0xd1, 0x65, 0xb8, 0xfa, 0x51, 0x8a, 0x9e, 0xda, 0x2c, 0x77, 0xc7, 0xbe, 0xfb, + 0x5c, 0x05, 0x67, 0x3f, 0x41, 0x04, 0xa7, 0xf6, 0x0b, 0xec, 0x4b, 0x0f, 0x8f, 0x6a, 0x98, 0xe1, 0xd8, 0x2f, 0x03, + 0xc0, 0x91, 0x7c, 0x82, 0x9a, 0x80, 0x45, 0x1a, 0x8c, 0xb2, 0xc5, 0x08, 0x44, 0xfb, 0x72, 0x73, 0xb5, 0x2a, 0x36, + 0x68, 0xcc, 0xc0, 0xa9, 0x96, 0x7e, 0xa0, 0xaf, 0x16, 0x50, 0xee, 0x60, 0x76, 0xd2, 0x28, 0xae, 0x92, 0x51, 0xa0, + 0xcf, 0xcf, 0x70, 0xe7, 0x30, 0x09, 0x88, 0xfb, 0x65, 0x1f, 0x90, 0x88, 0x87, 0x1e, 0xd8, 0x0e, 0xe1, 0x8c, 0x59, + 0x04, 0x3f, 0xd8, 0x41, 0x8c, 0x1f, 0x47, 0x81, 0xf1, 0xe4, 0x0f, 0xcf, 0x46, 0xce, 0x29, 0x01, 0x8d, 0x56, 0x47, + 0x08, 0xfc, 0xb4, 0x8e, 0x08, 0x78, 0xb2, 0x8e, 0x0f, 0x78, 0x32, 0xc7, 0xce, 0x08, 0x1d, 0x13, 0xd0, 0xf3, 0x00, + 0xb2, 0xd0, 0x38, 0x8d, 0x54, 0xec, 0x01, 0x0e, 0x83, 0x00, 0x79, 0x99, 0x39, 0x1f, 0x21, 0xf2, 0x18, 0xb3, 0xd5, + 0xe1, 0xe8, 0x2f, 0xc7, 0xff, 0x31, 0x32, 0x3c, 0xf2, 0x71, 0xe7, 0xb0, 0xfa, 0xd3, 0x5f, 0xd1, 0x01, 0x8e, 0xce, + 0xa6, 0xd1, 0xc9, 0x31, 0x66, 0x95, 0x3a, 0x42, 0x89, 0x4f, 0x8c, 0xb6, 0x7c, 0x21, 0x86, 0xea, 0x33, 0x43, 0xab, + 0x83, 0xa3, 0xc2, 0xe8, 0xda, 0xe1, 0x8a, 0x11, 0x60, 0x1b, 0xb7, 0xa8, 0x6a, 0x85, 0x1d, 0xb0, 0xb8, 0xfb, 0x8e, + 0xbc, 0xb9, 0xec, 0xf0, 0x82, 0x06, 0xea, 0x11, 0x9d, 0x5f, 0x3a, 0x2f, 0xad, 0xbd, 0xcc, 0x39, 0x74, 0x6b, 0x54, + 0xa8, 0x6c, 0x6c, 0x4b, 0x5c, 0xaf, 0xd3, 0xa4, 0xe1, 0x10, 0x29, 0x27, 0x01, 0x7b, 0xb1, 0xc0, 0x94, 0xe4, 0xe9, + 0x15, 0x7a, 0xcd, 0x33, 0xa9, 0x85, 0xe7, 0x2b, 0x44, 0x58, 0x83, 0x99, 0x14, 0x63, 0x50, 0x9b, 0xd2, 0x4f, 0x95, + 0xbd, 0x7c, 0x2a, 0xe4, 0xdc, 0x42, 0x11, 0xee, 0xae, 0x41, 0x91, 0xd4, 0x55, 0xad, 0x84, 0x46, 0xcb, 0xa0, 0x94, + 0x05, 0x43, 0x64, 0x21, 0x02, 0x38, 0x11, 0x10, 0xfc, 0xbc, 0xc6, 0x27, 0xd0, 0x28, 0x04, 0xf2, 0x81, 0xeb, 0x78, + 0xed, 0x51, 0x58, 0x6a, 0x8d, 0x88, 0x92, 0x44, 0x3d, 0xd1, 0xf3, 0xd8, 0x16, 0x3d, 0xcd, 0x6d, 0x4b, 0x65, 0x9c, + 0xf8, 0x39, 0x8a, 0x2d, 0xfa, 0x04, 0x71, 0x1b, 0x92, 0x1e, 0x2c, 0x27, 0xba, 0x28, 0xfc, 0x96, 0xea, 0xb7, 0x06, + 0xfe, 0x32, 0x58, 0x42, 0xd5, 0x0c, 0x8b, 0x59, 0x07, 0x88, 0x56, 0xc5, 0xe0, 0x99, 0x0e, 0x49, 0x0d, 0x9c, 0xee, + 0xf1, 0x91, 0x1d, 0x1e, 0x0e, 0x1d, 0xec, 0x95, 0x2f, 0xbe, 0x93, 0x55, 0xdb, 0x2a, 0xc2, 0x46, 0x48, 0x78, 0x65, + 0xf1, 0x3c, 0xcf, 0x8c, 0x71, 0x64, 0x21, 0xfb, 0xbb, 0x29, 0xaf, 0xae, 0x98, 0x4c, 0x58, 0x95, 0xa4, 0x4a, 0xda, + 0x50, 0xb9, 0x14, 0x66, 0x63, 0x0b, 0xff, 0xf9, 0xe2, 0xd5, 0x57, 0x67, 0xb6, 0x52, 0xc9, 0x71, 0xef, 0xfa, 0xa2, + 0x16, 0x69, 0xc8, 0x83, 0x0d, 0xe8, 0x3d, 0xea, 0xa1, 0x5c, 0xaf, 0xb1, 0x2f, 0x1b, 0x56, 0x69, 0x0a, 0x03, 0x03, + 0xc3, 0x56, 0xe4, 0x68, 0xa2, 0xa4, 0x20, 0x73, 0x35, 0x84, 0xf9, 0xbd, 0x17, 0x9f, 0x16, 0x96, 0x7b, 0x2c, 0x80, + 0xd9, 0x49, 0x1b, 0xc5, 0x09, 0x01, 0x11, 0x57, 0xea, 0x7e, 0xc5, 0xf8, 0xef, 0x29, 0x98, 0x25, 0xe3, 0xba, 0x97, + 0x04, 0x49, 0x42, 0x77, 0x7c, 0x12, 0x24, 0x53, 0x2f, 0x53, 0x34, 0x23, 0x17, 0x37, 0x47, 0xc3, 0xb4, 0x14, 0x53, + 0x07, 0xd2, 0xd7, 0x32, 0x2a, 0xa1, 0x57, 0x3c, 0x28, 0x68, 0x78, 0x7e, 0x58, 0x5a, 0x8f, 0xf0, 0x8e, 0x31, 0x97, + 0xf5, 0xf5, 0x5f, 0xde, 0x3b, 0x06, 0x87, 0x2c, 0x4d, 0x1c, 0x53, 0xff, 0x69, 0xbd, 0x2a, 0x3f, 0x7d, 0x07, 0xab, + 0xec, 0xee, 0xce, 0xcb, 0xed, 0x25, 0x16, 0x81, 0xa8, 0x98, 0x2b, 0xe0, 0x6f, 0xd7, 0x5a, 0x4b, 0x12, 0x87, 0xb8, + 0xdd, 0x42, 0x5d, 0x7e, 0x29, 0x82, 0xbd, 0x08, 0x0f, 0x0d, 0x40, 0x71, 0xde, 0x6a, 0xb7, 0xbc, 0x8e, 0xb4, 0x55, + 0x93, 0x02, 0xb5, 0xf9, 0x63, 0x52, 0x98, 0xb2, 0x36, 0xaf, 0x94, 0xb5, 0x79, 0x6c, 0x1c, 0x04, 0x12, 0x33, 0xe5, + 0x79, 0x3b, 0xb0, 0x48, 0x5c, 0x19, 0x69, 0xd5, 0x95, 0x91, 0x16, 0xf7, 0xca, 0x48, 0x14, 0x6f, 0x89, 0x0c, 0xd9, + 0x97, 0x11, 0x1b, 0x81, 0x06, 0x06, 0x7b, 0x79, 0x1d, 0xc8, 0xf8, 0xa0, 0xf6, 0xc2, 0x11, 0x96, 0xaf, 0xa3, 0x37, + 0xa9, 0xb7, 0xf6, 0xe7, 0xeb, 0xfd, 0xa6, 0xaa, 0x97, 0x5f, 0x58, 0x99, 0x77, 0x77, 0x25, 0x88, 0xba, 0xc6, 0x69, + 0x17, 0x04, 0xac, 0x31, 0x8c, 0xb9, 0xeb, 0x1f, 0xff, 0x26, 0x22, 0x16, 0x5b, 0x29, 0x8f, 0xc4, 0x84, 0x2a, 0x9d, + 0x9c, 0x18, 0x78, 0x98, 0x2d, 0x30, 0x2c, 0xdb, 0xd3, 0x1b, 0x60, 0x58, 0xb6, 0x6a, 0x6c, 0x61, 0xd9, 0x9d, 0x6d, + 0xcf, 0x83, 0x4f, 0xd1, 0xe5, 0xfc, 0x36, 0xdc, 0xb5, 0x48, 0x76, 0xb7, 0xa7, 0xb0, 0x2c, 0xb6, 0x4f, 0x22, 0x10, + 0xdd, 0x3e, 0x11, 0xa0, 0x33, 0xec, 0xca, 0x2e, 0xc6, 0xcf, 0x87, 0xae, 0xa9, 0xd6, 0x16, 0xe1, 0xe9, 0xbf, 0xf5, + 0x3e, 0x9c, 0x2d, 0xcf, 0xfd, 0xe0, 0x41, 0xf4, 0x09, 0xed, 0xf3, 0x89, 0x48, 0x52, 0xe8, 0x13, 0x6d, 0x32, 0xf9, + 0x01, 0x0d, 0xc8, 0x21, 0xef, 0x0b, 0xc8, 0xb1, 0x3c, 0x8f, 0xa0, 0x5b, 0x6f, 0xe7, 0x31, 0x05, 0x6b, 0x82, 0x49, + 0xa3, 0xac, 0x9e, 0x1f, 0xc6, 0xfd, 0x72, 0x09, 0x5f, 0x44, 0x5a, 0xe6, 0xdd, 0x71, 0xf0, 0x21, 0x48, 0xfc, 0x10, + 0x3f, 0x08, 0x15, 0xce, 0xa4, 0xa5, 0x2c, 0x5f, 0x3c, 0x00, 0xde, 0x84, 0x7f, 0xbd, 0x80, 0x5f, 0x6f, 0x03, 0x78, + 0x89, 0x2e, 0xfd, 0x5b, 0x1c, 0x1f, 0x2d, 0x75, 0x60, 0x53, 0x26, 0x6f, 0xe0, 0x1f, 0xff, 0xc9, 0x75, 0x70, 0x85, + 0xb1, 0x50, 0x94, 0x15, 0xda, 0x07, 0x28, 0x80, 0x56, 0x67, 0x6c, 0x44, 0x04, 0xc3, 0xe3, 0x07, 0x0b, 0x7a, 0xaf, + 0xe4, 0xc5, 0xd5, 0x17, 0xe5, 0xc5, 0x6d, 0x70, 0x3b, 0x2c, 0x2f, 0x4a, 0x49, 0x77, 0xff, 0xdc, 0x82, 0xac, 0xf8, + 0x09, 0x57, 0xd8, 0x9b, 0xe8, 0x43, 0xdb, 0x37, 0x8c, 0xfd, 0xa2, 0xc4, 0x88, 0xa0, 0xcc, 0x16, 0x2c, 0x16, 0x1e, + 0x94, 0x6a, 0xd7, 0x76, 0x38, 0xf2, 0x5a, 0x3b, 0xaa, 0x9d, 0x31, 0xdc, 0x57, 0x07, 0x39, 0xf3, 0xc0, 0x40, 0xdf, + 0xd6, 0x84, 0x16, 0x8e, 0x04, 0xf8, 0x0b, 0x7d, 0x3d, 0x26, 0x37, 0xcd, 0xfa, 0x4c, 0xdf, 0x45, 0x9d, 0x7c, 0x5d, + 0x39, 0x93, 0xdf, 0xf0, 0xc0, 0x16, 0x22, 0x29, 0x1e, 0xc7, 0x8f, 0x1e, 0xef, 0xb1, 0x5f, 0xb5, 0x2c, 0x6a, 0xb5, + 0x5d, 0x28, 0x8f, 0xe9, 0x73, 0x3e, 0xa2, 0x39, 0xf6, 0xa2, 0xcd, 0xc3, 0x1a, 0x65, 0x4d, 0x23, 0x06, 0xc3, 0xb4, + 0x87, 0x7d, 0x39, 0x70, 0xf8, 0x3b, 0xc8, 0xd0, 0xd6, 0x99, 0x30, 0xb4, 0x78, 0x0c, 0x13, 0x33, 0x8b, 0x29, 0xf7, + 0x33, 0xb3, 0x9c, 0xb7, 0xcf, 0xd0, 0x12, 0xa9, 0x06, 0x76, 0x4e, 0xc8, 0x2d, 0xb2, 0xb0, 0x9a, 0x64, 0x00, 0xfb, + 0xaa, 0x2a, 0xb7, 0x19, 0x28, 0xf6, 0xa0, 0x0c, 0x77, 0xcc, 0xb7, 0x5d, 0x28, 0x6e, 0x36, 0x81, 0xbe, 0x5d, 0x95, + 0xd5, 0x76, 0xd4, 0x06, 0x17, 0x24, 0xa0, 0xea, 0x37, 0x8c, 0x6d, 0x39, 0xb2, 0x0e, 0xe5, 0x32, 0xfb, 0x43, 0x37, + 0x3d, 0x5f, 0x7f, 0x9b, 0xf3, 0xbf, 0x2d, 0xa2, 0x4f, 0xab, 0x3f, 0x2e, 0xa4, 0xef, 0x75, 0x96, 0x91, 0x25, 0xf5, + 0x0a, 0x78, 0x28, 0x18, 0x4a, 0x5a, 0x0a, 0xbe, 0x7e, 0xfb, 0x15, 0x3c, 0x05, 0xf3, 0xa1, 0x9c, 0xaa, 0x8c, 0xec, + 0x71, 0x2c, 0xbc, 0xe1, 0xc3, 0x2c, 0x0d, 0x10, 0x84, 0xd7, 0x68, 0x53, 0x8d, 0x9b, 0xc4, 0xbd, 0x3b, 0xf8, 0xbf, + 0xe1, 0xc4, 0xa0, 0x6d, 0xa2, 0x78, 0x60, 0xbb, 0x48, 0xd7, 0x5d, 0xe3, 0x20, 0xa1, 0xd4, 0xb5, 0x3f, 0xad, 0x66, + 0x7f, 0x44, 0x43, 0xe4, 0x95, 0xa7, 0x84, 0x95, 0x85, 0x42, 0x2c, 0xb9, 0x8a, 0x94, 0x68, 0x1a, 0x42, 0x48, 0x59, + 0x9c, 0x14, 0xdf, 0x46, 0xa8, 0x2a, 0x42, 0x30, 0xae, 0xce, 0x40, 0xed, 0x71, 0x1f, 0xb7, 0xf6, 0x22, 0x3a, 0x2b, + 0x1a, 0xb5, 0xb2, 0x56, 0xe0, 0xa7, 0xac, 0x2f, 0x9d, 0xa4, 0x1c, 0xe9, 0x31, 0x15, 0x55, 0x29, 0x3c, 0x63, 0x98, + 0x43, 0x15, 0x6f, 0x0c, 0x09, 0x45, 0xcd, 0x7a, 0xfe, 0xca, 0xa4, 0x14, 0x72, 0x99, 0xf1, 0x2e, 0xad, 0x12, 0x98, + 0x99, 0xf8, 0x2a, 0x9d, 0x97, 0x14, 0x6f, 0xb4, 0xff, 0x02, 0x24, 0x94, 0x63, 0x34, 0x0a, 0xf1, 0x4a, 0xbc, 0x4b, + 0xa0, 0x01, 0x70, 0x45, 0x66, 0xe2, 0xeb, 0xb4, 0xae, 0xdf, 0xda, 0x0f, 0xe5, 0x24, 0x7e, 0x68, 0x31, 0x6c, 0xbd, + 0x7d, 0xd4, 0xd3, 0xc3, 0xc7, 0xff, 0x75, 0x55, 0x73, 0x32, 0xa8, 0x5c, 0xce, 0xfb, 0x0b, 0x96, 0x3d, 0x66, 0xc8, + 0xfc, 0xf5, 0xf6, 0x79, 0x8a, 0xe1, 0x76, 0x83, 0x05, 0xfc, 0xde, 0xca, 0x6f, 0x31, 0x63, 0x25, 0x6e, 0x93, 0x64, + 0x59, 0x20, 0xdd, 0x93, 0xe9, 0x5f, 0x1e, 0x7e, 0x3f, 0x63, 0x54, 0x9d, 0x4d, 0xb0, 0xd6, 0x7e, 0x2e, 0x20, 0x92, + 0xf2, 0x2d, 0x08, 0x31, 0xc2, 0x32, 0x28, 0xc6, 0x2b, 0x98, 0x58, 0x8a, 0x35, 0xb0, 0x13, 0x6b, 0xd2, 0x09, 0xaf, + 0xfd, 0xa5, 0xd6, 0x09, 0x73, 0x44, 0x56, 0xae, 0x1f, 0xb8, 0xa2, 0xe0, 0x4a, 0x65, 0x4e, 0x31, 0xf3, 0xb8, 0x98, + 0xad, 0x8d, 0x06, 0xf3, 0x1a, 0xb8, 0x8f, 0xf5, 0xb9, 0x28, 0x9f, 0xf1, 0x2a, 0x67, 0x39, 0xde, 0x5b, 0x0b, 0xf0, + 0x3b, 0xd5, 0xc0, 0x0a, 0x05, 0xce, 0x8a, 0x7a, 0x05, 0xbc, 0x52, 0x83, 0xf0, 0x80, 0xe8, 0x03, 0xc3, 0xfd, 0xd5, + 0xcc, 0x43, 0x67, 0x03, 0xac, 0x61, 0x03, 0xf8, 0xe1, 0xf1, 0x6c, 0x19, 0x5d, 0x50, 0xfc, 0xe5, 0xc4, 0x51, 0xbb, + 0x43, 0xc2, 0x0d, 0x72, 0xc1, 0x89, 0x7b, 0x43, 0x41, 0x81, 0x80, 0x2e, 0xa2, 0x8d, 0xaf, 0x6c, 0x30, 0xde, 0x90, + 0xb6, 0x1a, 0x15, 0xd4, 0x8e, 0x6e, 0xf9, 0xd8, 0xd1, 0x3b, 0xdf, 0xec, 0xd1, 0x57, 0x5f, 0x68, 0xe6, 0xf8, 0x0b, + 0x67, 0xe4, 0x3a, 0xb8, 0x1e, 0xe0, 0x23, 0xda, 0xd9, 0x00, 0x13, 0x71, 0x1d, 0xac, 0x83, 0x37, 0xac, 0x72, 0x1e, + 0x9c, 0xb2, 0xfd, 0x47, 0xa8, 0xd2, 0x6b, 0xdd, 0x4f, 0x4e, 0x94, 0xee, 0xb6, 0x4f, 0x4d, 0xbe, 0x86, 0x88, 0xa4, + 0xe3, 0x31, 0x13, 0x08, 0x67, 0x66, 0x9d, 0x40, 0x2a, 0x03, 0x20, 0x04, 0xce, 0x15, 0xd0, 0xfc, 0xdf, 0x5f, 0xe2, + 0x28, 0xf0, 0x48, 0x9b, 0x52, 0xd4, 0xb2, 0xbb, 0xbb, 0x02, 0x35, 0xca, 0x70, 0x9a, 0x97, 0xea, 0x34, 0xc7, 0xc8, + 0xd3, 0x15, 0xd2, 0x1c, 0x3a, 0xd2, 0xcb, 0xfe, 0x91, 0xfe, 0xdf, 0xd1, 0x44, 0x1d, 0xff, 0x71, 0x4d, 0x94, 0xd2, + 0x22, 0x39, 0xaa, 0xa5, 0xaf, 0x52, 0x47, 0xa1, 0x20, 0xef, 0xa8, 0x85, 0xec, 0x55, 0x76, 0xdc, 0xaa, 0xee, 0xfd, + 0xff, 0x5a, 0x99, 0xff, 0xaf, 0x69, 0x65, 0x02, 0xc5, 0x3b, 0x56, 0x6a, 0xe5, 0xa1, 0x56, 0x31, 0xce, 0xbf, 0x63, + 0x0e, 0x31, 0xa0, 0xad, 0x81, 0x0f, 0x90, 0x65, 0x91, 0xd5, 0xeb, 0x3c, 0xde, 0x92, 0x12, 0xf5, 0x32, 0x85, 0xe5, + 0xf0, 0xb4, 0xf9, 0x77, 0x5a, 0x95, 0xb8, 0xb4, 0xaf, 0x60, 0xd5, 0x84, 0x7c, 0xc3, 0x0f, 0x5b, 0x86, 0x16, 0x37, + 0xf5, 0xf1, 0x3b, 0x99, 0x4f, 0xbb, 0xa8, 0xb3, 0xf2, 0x90, 0x03, 0x35, 0xd0, 0x85, 0x6c, 0x5c, 0x56, 0x95, 0x9f, + 0x08, 0xba, 0xf9, 0xdb, 0xaa, 0x82, 0x53, 0x60, 0xf4, 0x11, 0x76, 0xf0, 0xc1, 0x75, 0xda, 0xac, 0xca, 0x85, 0x82, + 0xb2, 0xc9, 0x10, 0x46, 0x1f, 0x77, 0x1e, 0x08, 0xf0, 0x07, 0x04, 0xd4, 0x00, 0x94, 0x20, 0x46, 0xa0, 0x61, 0x85, + 0xb0, 0x7f, 0x80, 0x3d, 0x3c, 0x88, 0x17, 0xf1, 0x1a, 0x61, 0x72, 0xa0, 0x18, 0x6c, 0xa4, 0x1b, 0x58, 0xd9, 0x8b, + 0xe9, 0x48, 0x85, 0x64, 0x79, 0x59, 0xb8, 0x7c, 0xae, 0xbf, 0xfb, 0x8d, 0x1d, 0xd8, 0x0d, 0x98, 0x6d, 0x07, 0xec, + 0x00, 0x21, 0x41, 0x31, 0xd8, 0x42, 0x93, 0x25, 0x07, 0x6a, 0xab, 0x60, 0x39, 0xd7, 0x02, 0xfc, 0x65, 0x81, 0x58, + 0xc6, 0x4d, 0x29, 0x0e, 0x23, 0x04, 0x60, 0x84, 0x06, 0x4a, 0x18, 0xa1, 0x33, 0x26, 0xb2, 0x2a, 0xab, 0x16, 0xbb, + 0x2b, 0x66, 0x4b, 0x6e, 0x1a, 0xe7, 0xec, 0x24, 0x22, 0xe8, 0xab, 0x9b, 0xb2, 0xc8, 0x96, 0xcb, 0x4e, 0x12, 0x8d, + 0xed, 0xdb, 0x6e, 0x2a, 0xec, 0x98, 0x5e, 0x1a, 0xc5, 0x15, 0x78, 0x9a, 0x47, 0x14, 0x24, 0x2a, 0x0d, 0x5f, 0x16, + 0xad, 0x99, 0x87, 0x6f, 0xbb, 0x21, 0xa7, 0x76, 0x66, 0xbf, 0x20, 0x9c, 0x27, 0x6a, 0xcb, 0x10, 0x63, 0x81, 0x9c, + 0x73, 0xd1, 0x97, 0x3c, 0x23, 0xf0, 0x4b, 0xc7, 0x53, 0x98, 0xa8, 0x1c, 0xb9, 0x79, 0xb0, 0xf7, 0x2e, 0xab, 0x3f, + 0xe0, 0xf7, 0x21, 0x81, 0xf9, 0x1c, 0x1d, 0x55, 0x08, 0x2a, 0xeb, 0x3a, 0x89, 0xe1, 0xad, 0xd2, 0x85, 0xe0, 0x12, + 0x92, 0x30, 0x5f, 0xcf, 0xd3, 0x24, 0x7c, 0xdb, 0xcc, 0x18, 0xe1, 0x84, 0xfc, 0x43, 0x5c, 0x07, 0x01, 0x83, 0x55, + 0x5c, 0x92, 0x93, 0x7c, 0x24, 0xe8, 0xfe, 0x74, 0xbe, 0x93, 0x83, 0x2b, 0x7c, 0x4d, 0xf5, 0x6b, 0x84, 0xf7, 0xe5, + 0x2a, 0x5d, 0x22, 0x42, 0x58, 0xfe, 0x7f, 0x09, 0x5b, 0xdf, 0x4e, 0x56, 0xa8, 0xb6, 0x91, 0x87, 0xf1, 0xca, 0x08, + 0x13, 0x65, 0xb8, 0x00, 0x01, 0x03, 0xb6, 0x6b, 0xb8, 0x99, 0xae, 0x32, 0xd4, 0x68, 0x92, 0x0f, 0x99, 0x72, 0xed, + 0x90, 0x30, 0x9a, 0xad, 0xc9, 0x7e, 0x8c, 0x79, 0x4d, 0x2c, 0x16, 0x0b, 0xbc, 0xef, 0xbb, 0x54, 0x0d, 0xb0, 0xcd, + 0xcd, 0x11, 0xe0, 0x24, 0xc3, 0x9e, 0xba, 0x2c, 0x25, 0x63, 0x36, 0xfa, 0xec, 0x72, 0x6e, 0x40, 0xc7, 0x59, 0x63, + 0xa8, 0x3e, 0x30, 0x8b, 0x4f, 0x13, 0x32, 0x52, 0x56, 0x08, 0x0b, 0x44, 0x37, 0x72, 0x9e, 0xad, 0x55, 0x4b, 0xc0, + 0xdb, 0x01, 0xf5, 0x82, 0xba, 0xd0, 0x46, 0x30, 0xcb, 0x94, 0xd6, 0x04, 0x15, 0x5e, 0xe7, 0x1b, 0xa8, 0xc4, 0xc5, + 0x6c, 0x79, 0x1a, 0x6d, 0x5c, 0xac, 0xc4, 0xd5, 0xd9, 0xf2, 0x7c, 0xb6, 0x96, 0x50, 0x73, 0x05, 0x30, 0x19, 0x79, + 0xb0, 0x44, 0xfa, 0x61, 0x60, 0x28, 0x1d, 0xd8, 0xd1, 0x4c, 0x87, 0x4d, 0x42, 0x4c, 0x26, 0x98, 0xf2, 0xc9, 0x09, + 0xa1, 0xc9, 0xea, 0xd4, 0xad, 0xa4, 0x2a, 0x0a, 0xae, 0x83, 0x33, 0x39, 0x23, 0xd2, 0xcc, 0xb5, 0xee, 0xa5, 0x98, + 0xde, 0x4e, 0xea, 0xe9, 0x2d, 0x1c, 0xd1, 0x38, 0x0c, 0x76, 0xfa, 0x16, 0xd2, 0xb7, 0xbe, 0x86, 0xdd, 0x65, 0x85, + 0x40, 0xfd, 0x7b, 0xd5, 0xf0, 0x75, 0xf1, 0xba, 0xfc, 0x04, 0x53, 0xf3, 0xd8, 0x1f, 0xeb, 0xa7, 0x0a, 0x9e, 0xec, + 0x48, 0xae, 0xbf, 0x67, 0x43, 0xb3, 0x71, 0xa6, 0xfd, 0xb5, 0x6b, 0xbc, 0x85, 0x46, 0xc8, 0x2f, 0xa4, 0x68, 0xaf, + 0x0b, 0x64, 0x08, 0xc8, 0xbc, 0x84, 0x66, 0x31, 0x45, 0xaf, 0x69, 0xd5, 0x78, 0x32, 0xba, 0xf7, 0x77, 0x54, 0x22, + 0x46, 0x6c, 0xf2, 0xcc, 0x92, 0x5b, 0x8e, 0xa1, 0x08, 0xba, 0xd0, 0xcb, 0xf2, 0x9b, 0x82, 0x08, 0x30, 0xdd, 0x06, + 0x85, 0x6f, 0x22, 0xea, 0x29, 0x18, 0xc3, 0xd8, 0x85, 0x55, 0x4c, 0xe4, 0x0c, 0xc8, 0xe1, 0x08, 0x20, 0x74, 0x01, + 0x2b, 0xbc, 0xde, 0x8b, 0x5e, 0x18, 0x44, 0x44, 0xa5, 0xd7, 0x41, 0x63, 0x29, 0x9e, 0xab, 0x42, 0xf4, 0xde, 0x59, + 0x94, 0x37, 0x37, 0x9c, 0x25, 0xac, 0x0d, 0x56, 0xbb, 0x01, 0xab, 0x51, 0x7b, 0x67, 0x7b, 0xb8, 0x8b, 0x73, 0x72, + 0x95, 0xa1, 0xe3, 0x00, 0x95, 0x9e, 0xff, 0x8c, 0xa1, 0x6a, 0x8a, 0x34, 0xcb, 0x61, 0x66, 0xb7, 0x40, 0xc7, 0xaf, + 0x33, 0x6f, 0x41, 0x88, 0xd9, 0x18, 0x2d, 0xc4, 0xed, 0x51, 0xe5, 0xf6, 0x28, 0x96, 0x1e, 0x25, 0xfa, 0x50, 0x3b, + 0xd0, 0x83, 0x09, 0xa2, 0x62, 0x6d, 0xfa, 0xf7, 0x31, 0x37, 0x73, 0x83, 0x61, 0xbb, 0x18, 0xdf, 0x40, 0x3b, 0x2a, + 0xc4, 0xd1, 0x6b, 0x42, 0x44, 0x62, 0x60, 0x97, 0x7d, 0x32, 0xb1, 0x19, 0x90, 0xdc, 0x23, 0xcc, 0x0a, 0x35, 0xcb, + 0xcb, 0x68, 0xd5, 0x9b, 0x90, 0x9a, 0x51, 0xb7, 0x61, 0x06, 0x97, 0x2e, 0xa8, 0xfd, 0x9a, 0x7d, 0xc7, 0x58, 0x52, + 0xa0, 0xb5, 0xe0, 0x71, 0xde, 0x43, 0xef, 0x10, 0xd1, 0x1c, 0xbb, 0x4b, 0x64, 0x8d, 0x38, 0xbd, 0xdd, 0x4a, 0x30, + 0xda, 0x67, 0x13, 0xac, 0x61, 0xb0, 0x4e, 0x93, 0xb9, 0x07, 0x3d, 0xd1, 0x43, 0xb4, 0x72, 0x87, 0x68, 0x21, 0x43, + 0xb4, 0x69, 0xd1, 0xd9, 0xf1, 0xda, 0x0f, 0x31, 0x6e, 0x68, 0x02, 0x64, 0xb3, 0x33, 0xb2, 0x7b, 0x8b, 0xf5, 0x47, + 0x36, 0x07, 0x0a, 0x62, 0x46, 0xf6, 0xa7, 0xcc, 0x1d, 0x59, 0x59, 0xec, 0xe5, 0xe0, 0x62, 0x9f, 0x9f, 0x9d, 0x87, + 0x69, 0x24, 0x94, 0xfb, 0xb0, 0x98, 0xeb, 0x65, 0x57, 0xfb, 0x61, 0x67, 0x8a, 0x2c, 0x2a, 0x57, 0x0f, 0xef, 0x2b, + 0xdc, 0xc0, 0x02, 0xee, 0x06, 0x2c, 0xeb, 0x4f, 0x34, 0xfc, 0xa3, 0x10, 0x7e, 0xfe, 0xcc, 0x3f, 0xd9, 0x81, 0x03, + 0xd1, 0x98, 0xba, 0x3d, 0xf0, 0x08, 0x43, 0x85, 0x98, 0xbb, 0xb3, 0xea, 0xdc, 0x6b, 0x10, 0x0e, 0x93, 0xf5, 0x0d, + 0x9d, 0x51, 0xe9, 0x44, 0xd7, 0xcb, 0x65, 0x54, 0x30, 0x0e, 0x55, 0x1c, 0xc5, 0x77, 0x77, 0xc9, 0xc0, 0xb4, 0xa3, + 0x3a, 0x02, 0xa7, 0x3d, 0xc6, 0xce, 0x96, 0x74, 0x3e, 0x7e, 0x07, 0xe7, 0x63, 0x8a, 0x6a, 0x23, 0x82, 0xc7, 0x83, + 0x69, 0x8f, 0xa9, 0x67, 0xaf, 0x29, 0x94, 0xd4, 0x77, 0x29, 0xa1, 0x8c, 0x02, 0x77, 0x43, 0x95, 0xf7, 0xa3, 0x34, + 0x7e, 0x40, 0x57, 0xb1, 0xcc, 0x27, 0x17, 0x1a, 0x3c, 0xfc, 0xee, 0xee, 0x90, 0x83, 0xaf, 0x22, 0x1d, 0x6c, 0xee, + 0x75, 0x71, 0xc3, 0x74, 0xfe, 0xee, 0xee, 0xf0, 0x84, 0x40, 0xe9, 0x32, 0xfc, 0x48, 0x0d, 0xcc, 0xc4, 0x9c, 0x88, + 0x12, 0x45, 0xb3, 0x04, 0x6e, 0x36, 0xfc, 0x49, 0x3d, 0x21, 0x80, 0x2c, 0x3a, 0xda, 0x24, 0x86, 0x3e, 0x1d, 0x28, + 0xef, 0x02, 0x8c, 0x21, 0x7e, 0xff, 0x09, 0xa2, 0x25, 0xb4, 0x5c, 0xf3, 0xc7, 0xab, 0x28, 0x46, 0xad, 0x2d, 0xeb, + 0x24, 0x16, 0x4a, 0x81, 0x8d, 0x9e, 0xf0, 0x30, 0x14, 0x0b, 0x09, 0x07, 0x9a, 0x74, 0x86, 0x77, 0xd1, 0x19, 0x5e, + 0x29, 0xae, 0x07, 0x19, 0x46, 0x32, 0xf1, 0x31, 0x90, 0x96, 0xca, 0xf7, 0x75, 0x83, 0xb3, 0xdd, 0x3f, 0x3a, 0xb2, + 0x26, 0xbe, 0x7e, 0x84, 0x88, 0xf5, 0xd0, 0x21, 0xb2, 0x2c, 0x0e, 0x03, 0x7b, 0x6b, 0xb7, 0x3e, 0xc8, 0xf9, 0xe0, + 0xb5, 0x25, 0x84, 0x84, 0x2d, 0xc5, 0x67, 0x71, 0x64, 0xc5, 0xf8, 0x60, 0x47, 0xff, 0xdc, 0xd8, 0x33, 0xaf, 0xfc, + 0xb8, 0x33, 0x2e, 0x88, 0x73, 0x33, 0x4c, 0xbb, 0x57, 0x66, 0x3f, 0xc6, 0xc2, 0x1b, 0xff, 0xf7, 0xc7, 0x44, 0x2a, + 0x74, 0x06, 0xa2, 0x0d, 0x90, 0x71, 0x4f, 0xeb, 0xff, 0xb9, 0xea, 0xf5, 0xc8, 0x5a, 0x83, 0x2f, 0x9f, 0xda, 0xbf, + 0xe8, 0xf5, 0xd6, 0xad, 0xa9, 0x30, 0x2e, 0x7c, 0xa7, 0x38, 0x14, 0xde, 0x7e, 0x75, 0xe1, 0x6d, 0xaf, 0x30, 0x46, + 0x93, 0xa2, 0x32, 0x1b, 0x20, 0xa1, 0x23, 0x54, 0xf8, 0xc1, 0x59, 0xd5, 0x94, 0x6b, 0xf8, 0x97, 0xb4, 0x80, 0x64, + 0x61, 0x81, 0xea, 0xbf, 0x91, 0x7d, 0x1a, 0xa6, 0x0e, 0xb2, 0x71, 0xa6, 0x40, 0x91, 0xd3, 0xe8, 0x49, 0x0a, 0x8c, + 0x01, 0x03, 0x22, 0x1b, 0xf2, 0xf5, 0xbe, 0xde, 0x9b, 0x7d, 0x53, 0x69, 0x4e, 0x86, 0x4a, 0x22, 0x32, 0x3b, 0x46, + 0xe5, 0x44, 0xa5, 0xe3, 0xad, 0x01, 0x57, 0xb6, 0x02, 0x89, 0x77, 0x3f, 0x8d, 0xbc, 0xb3, 0x8e, 0x49, 0xa3, 0x6d, + 0xd8, 0xe7, 0x45, 0x48, 0x40, 0x24, 0x73, 0x90, 0x0b, 0x75, 0xec, 0x2d, 0xb1, 0xd1, 0x81, 0x1a, 0x4b, 0xf9, 0x39, + 0x17, 0x1d, 0xe2, 0x44, 0xb0, 0x06, 0x42, 0x95, 0x67, 0xa2, 0x72, 0xd8, 0x20, 0xc7, 0xef, 0x1d, 0xce, 0x4c, 0x30, + 0x59, 0x84, 0x5a, 0x07, 0xca, 0xfd, 0x20, 0x05, 0x5e, 0xb2, 0x88, 0x30, 0x16, 0xd8, 0xd9, 0xb9, 0xaf, 0x96, 0x78, + 0x7a, 0x8a, 0xf6, 0x98, 0xa9, 0x5f, 0x47, 0x19, 0x52, 0x5a, 0x90, 0xca, 0xeb, 0x8c, 0x74, 0x1b, 0xa5, 0x15, 0x4a, + 0x16, 0xaf, 0xd6, 0x28, 0x4c, 0x34, 0xfc, 0x65, 0x63, 0xa0, 0x30, 0x8e, 0x80, 0xd9, 0xc5, 0x88, 0x54, 0xb2, 0x3d, + 0xb8, 0x91, 0x18, 0x17, 0x00, 0x9a, 0x0a, 0x8b, 0x1f, 0x9d, 0x6c, 0x57, 0x65, 0x95, 0x7d, 0x06, 0xa9, 0x22, 0xce, + 0xa1, 0xf5, 0x59, 0xfd, 0x4a, 0x3f, 0x62, 0xe4, 0x6d, 0xae, 0x46, 0xf5, 0x2a, 0x90, 0x6f, 0x00, 0xa3, 0x34, 0xee, + 0x7c, 0xc8, 0xe0, 0xa3, 0x37, 0xa6, 0xc3, 0x2f, 0x9d, 0x0e, 0x3b, 0x91, 0x68, 0x52, 0x84, 0x64, 0xce, 0x2c, 0x7e, + 0x08, 0xea, 0x2d, 0xa8, 0x45, 0xb5, 0x53, 0x31, 0xde, 0xfe, 0xd3, 0xd1, 0x8e, 0x91, 0x7a, 0x69, 0xb6, 0x25, 0x3a, + 0x28, 0x1c, 0x13, 0xea, 0x5e, 0x73, 0xa6, 0x91, 0x2d, 0xce, 0x0a, 0x84, 0x66, 0x6f, 0x08, 0x19, 0x9f, 0xad, 0x00, + 0x8e, 0x03, 0x10, 0x77, 0x13, 0x10, 0x8e, 0x8e, 0x55, 0x67, 0x8e, 0x03, 0xbc, 0xe0, 0x42, 0x5d, 0xcb, 0xac, 0x62, + 0x0d, 0xe9, 0x78, 0x1c, 0x54, 0xd2, 0xc3, 0x71, 0x54, 0xb6, 0xfd, 0x7e, 0x7c, 0xdf, 0x09, 0xe3, 0x41, 0xfd, 0x0a, + 0x76, 0x37, 0xcf, 0xca, 0xdb, 0x37, 0xf1, 0x2d, 0xeb, 0x15, 0x8a, 0x60, 0xc5, 0x8f, 0xaf, 0x64, 0xcc, 0xda, 0x88, + 0x8d, 0x0a, 0xcd, 0xd4, 0xb2, 0x27, 0x94, 0x8a, 0x3b, 0x3d, 0x2b, 0xc9, 0x97, 0x11, 0x0e, 0x7c, 0x8c, 0x39, 0x5d, + 0xaa, 0x40, 0xee, 0x18, 0xe3, 0xf8, 0x03, 0x36, 0x10, 0xed, 0x17, 0x70, 0x15, 0x23, 0xe4, 0xe9, 0x59, 0xcc, 0x38, + 0x85, 0x28, 0x58, 0xe5, 0x47, 0x47, 0xf2, 0xc4, 0x43, 0xf4, 0x28, 0x97, 0xb6, 0xcf, 0xe2, 0xa9, 0x99, 0xcb, 0x39, + 0xd0, 0x5c, 0xb2, 0xbc, 0x8f, 0x56, 0xf3, 0xd5, 0xc3, 0x22, 0x4c, 0x10, 0xf2, 0x39, 0xbe, 0x89, 0xb3, 0x1c, 0x6f, + 0xa5, 0x59, 0xf9, 0x11, 0x8b, 0x2d, 0x7e, 0x04, 0xbc, 0x83, 0xce, 0x5e, 0x98, 0x64, 0x2c, 0x59, 0x77, 0x4a, 0x72, + 0xef, 0x86, 0xe2, 0x80, 0x7f, 0x76, 0x26, 0x9b, 0xd6, 0x3a, 0x48, 0x1a, 0x18, 0x17, 0x49, 0xed, 0x57, 0x38, 0xeb, + 0x72, 0xda, 0x97, 0xaa, 0x8f, 0x3e, 0x31, 0xd1, 0x05, 0x66, 0x2a, 0x51, 0xa1, 0xc8, 0xf4, 0x83, 0x53, 0x6b, 0x93, + 0xca, 0x84, 0x04, 0xd1, 0x3d, 0x4c, 0x1a, 0x92, 0x18, 0xce, 0x58, 0x99, 0x44, 0xa1, 0x34, 0x84, 0x2b, 0xfb, 0xbe, + 0x16, 0x1c, 0x5a, 0x38, 0xa1, 0xf9, 0x37, 0x48, 0x3a, 0x4a, 0x84, 0xd4, 0x83, 0x9c, 0x52, 0x64, 0x8c, 0xa7, 0xc5, + 0xe2, 0x23, 0x06, 0xcc, 0x46, 0x6d, 0x54, 0x12, 0xa3, 0xcb, 0x06, 0x47, 0xd0, 0x80, 0xf4, 0x67, 0x2a, 0x8c, 0x87, + 0xbc, 0x4a, 0x7c, 0xf5, 0xab, 0xd2, 0xbf, 0x62, 0xf8, 0x86, 0x76, 0x1e, 0xe1, 0x96, 0xe8, 0x67, 0xf8, 0xfe, 0x0d, + 0xaa, 0x0d, 0x41, 0x08, 0x37, 0xf5, 0xd7, 0xbe, 0xa9, 0xce, 0xde, 0x7f, 0xe5, 0x50, 0xdd, 0x96, 0x7c, 0xf4, 0xb2, + 0x17, 0xc0, 0xda, 0x5c, 0xba, 0x12, 0x61, 0x40, 0x3e, 0x4c, 0xe4, 0x2b, 0x4e, 0x2b, 0x30, 0x0d, 0x5d, 0x07, 0x4d, + 0x80, 0x5e, 0x09, 0xf4, 0x41, 0x81, 0x45, 0xcc, 0xc5, 0x0b, 0xc7, 0x19, 0x69, 0xf8, 0x8a, 0x86, 0xe3, 0x8a, 0xd8, + 0x2f, 0xe9, 0x26, 0xa7, 0xe1, 0x70, 0x23, 0x81, 0x8a, 0x40, 0x62, 0x67, 0x90, 0x98, 0x24, 0x8d, 0xb2, 0x8b, 0x0f, + 0x24, 0x60, 0x89, 0x9d, 0x87, 0x23, 0x98, 0x34, 0x62, 0x4e, 0x6f, 0x9a, 0xf4, 0x6b, 0x4f, 0xcb, 0xc1, 0x74, 0x00, + 0xa9, 0x94, 0x58, 0xff, 0x64, 0x58, 0xc5, 0xbb, 0x78, 0xb1, 0x40, 0xf7, 0x2d, 0x0e, 0x75, 0x85, 0xc6, 0xb5, 0x29, + 0x5d, 0x56, 0x63, 0x1c, 0x9a, 0xb3, 0xfa, 0x7c, 0x12, 0xf1, 0xa3, 0xd2, 0xf2, 0x2f, 0x08, 0x8d, 0x8d, 0xf7, 0xcd, + 0xdd, 0xdd, 0x8e, 0x77, 0xbd, 0x18, 0x07, 0x9d, 0xb4, 0xb3, 0x05, 0xc7, 0x06, 0xd2, 0xed, 0xe3, 0x67, 0x38, 0xdf, + 0xac, 0x4d, 0x54, 0xca, 0x56, 0x80, 0x99, 0xa1, 0xdd, 0x41, 0xc0, 0xa1, 0x58, 0x8a, 0x33, 0x3f, 0x5a, 0x30, 0x01, + 0x09, 0xf0, 0xf3, 0x63, 0xf9, 0x7c, 0x5b, 0xb2, 0x8a, 0x9d, 0xda, 0x7a, 0x74, 0x04, 0xe3, 0x8d, 0x78, 0x72, 0x26, + 0xc9, 0xd3, 0xd7, 0x10, 0x96, 0x07, 0xd0, 0x31, 0x0c, 0x0b, 0xa9, 0x89, 0x69, 0x67, 0x4e, 0x60, 0xa2, 0xab, 0x20, + 0x0b, 0xd4, 0x79, 0xaa, 0x37, 0x40, 0x32, 0x50, 0x82, 0x77, 0xa4, 0x2e, 0xc2, 0x67, 0xaf, 0xd9, 0x09, 0x79, 0x14, + 0x83, 0x7c, 0x9f, 0x4d, 0x3f, 0x01, 0xe9, 0x48, 0xe8, 0xd7, 0x6a, 0xa6, 0x0f, 0xbf, 0x4f, 0x11, 0x4a, 0xc6, 0xf0, + 0xc2, 0xed, 0xf6, 0xae, 0x8e, 0xaf, 0x51, 0x32, 0x2d, 0x11, 0xc9, 0x0d, 0x38, 0xa6, 0x90, 0x13, 0x56, 0x92, 0x90, + 0x5b, 0x4b, 0xf2, 0x59, 0x47, 0xdb, 0x60, 0x4d, 0x93, 0xce, 0x83, 0x56, 0xb4, 0xfa, 0x68, 0x35, 0xde, 0x2e, 0xb0, + 0x2e, 0x27, 0xb4, 0x42, 0x75, 0x8c, 0xbb, 0x03, 0x7c, 0x1c, 0xc3, 0x79, 0x55, 0xb7, 0xd9, 0x74, 0x0b, 0x03, 0xea, + 0xc9, 0x3e, 0xcf, 0xa6, 0xb7, 0xf4, 0x24, 0xf4, 0x01, 0xa1, 0x83, 0x79, 0x48, 0xf0, 0xa7, 0xea, 0xab, 0x69, 0xd4, + 0x8f, 0x1d, 0x7a, 0xdd, 0x0c, 0x36, 0xab, 0xf0, 0x2c, 0x61, 0x68, 0x47, 0x11, 0x72, 0x60, 0x50, 0x81, 0x0e, 0x1c, + 0x4b, 0xfc, 0x9c, 0x63, 0x15, 0x3f, 0xe7, 0xb8, 0x35, 0x3c, 0x80, 0x62, 0xdc, 0x2b, 0x60, 0x17, 0x08, 0x33, 0xc4, + 0xea, 0x50, 0x85, 0x51, 0x59, 0xaa, 0x73, 0x9f, 0x62, 0x8a, 0x29, 0xa3, 0x0c, 0x2f, 0x9b, 0x9f, 0xb9, 0x13, 0x79, + 0x1e, 0x9e, 0xb9, 0xd3, 0x64, 0xef, 0xcf, 0x4d, 0xda, 0xe7, 0xc6, 0x84, 0x55, 0x10, 0xe3, 0x74, 0x94, 0xbc, 0x06, + 0xce, 0x13, 0x98, 0xe9, 0xe3, 0xee, 0x99, 0x82, 0x2e, 0xb6, 0x74, 0x86, 0x24, 0x4a, 0xd5, 0x2c, 0x64, 0xfe, 0xee, + 0xae, 0x81, 0x15, 0xa1, 0x28, 0x3d, 0x3e, 0xad, 0x02, 0x18, 0x35, 0xfb, 0x04, 0x41, 0x4c, 0x12, 0x39, 0x93, 0x68, + 0xf6, 0x17, 0x32, 0xfb, 0x9b, 0x36, 0xfa, 0x35, 0x27, 0x75, 0x77, 0x8c, 0x91, 0xd8, 0xf2, 0xbb, 0x08, 0x9d, 0xd3, + 0x55, 0x23, 0x56, 0x68, 0x60, 0xb3, 0x81, 0xef, 0x29, 0x12, 0x8b, 0x7e, 0x3e, 0xc4, 0x7c, 0xc4, 0x26, 0x32, 0xfa, + 0xd1, 0x11, 0xf4, 0xb2, 0x96, 0x5e, 0xde, 0xdd, 0xad, 0x2c, 0x59, 0xd8, 0xd1, 0xc7, 0x31, 0x8d, 0x4a, 0x15, 0x3d, + 0x6a, 0x34, 0x51, 0x69, 0xc6, 0x85, 0xa1, 0x42, 0xe9, 0xb8, 0x86, 0x07, 0x75, 0x7a, 0x65, 0xfa, 0x50, 0xb3, 0xce, + 0xef, 0xba, 0x3f, 0xa6, 0xc0, 0x2e, 0xca, 0x82, 0x40, 0xd3, 0x41, 0x0e, 0xa3, 0x83, 0x60, 0x9c, 0xdd, 0x68, 0x79, + 0x99, 0xad, 0x13, 0xe5, 0xe3, 0xb8, 0xd0, 0xc7, 0x31, 0x90, 0x15, 0xa1, 0x27, 0x3d, 0x36, 0xe3, 0xa4, 0x45, 0x78, + 0xb3, 0xc1, 0x83, 0xfa, 0xee, 0xee, 0x84, 0x85, 0x22, 0x33, 0xd8, 0x46, 0xf9, 0x09, 0xf3, 0xf2, 0x70, 0x45, 0xd7, + 0xbd, 0x35, 0x8d, 0x5e, 0x22, 0xfd, 0x99, 0xad, 0x33, 0x2f, 0x67, 0x9b, 0x5e, 0x94, 0xb3, 0xab, 0x48, 0x3d, 0x58, + 0x63, 0x3d, 0x87, 0xe1, 0x4b, 0x82, 0x9a, 0xa9, 0x75, 0x05, 0x7b, 0xda, 0x22, 0x29, 0xb5, 0xdc, 0xca, 0xcb, 0xdb, + 0x0d, 0x08, 0x6a, 0x98, 0x99, 0x69, 0xfa, 0x38, 0x9f, 0x72, 0xfb, 0xa1, 0x14, 0x6a, 0x65, 0x02, 0xa9, 0x3c, 0xaa, + 0x02, 0xf5, 0x66, 0x1c, 0xc1, 0xcb, 0x28, 0x91, 0x41, 0x6f, 0x0d, 0x82, 0x00, 0xb5, 0x79, 0xd5, 0x69, 0x33, 0xcd, + 0x46, 0xa7, 0xc9, 0xe5, 0xfe, 0x26, 0x97, 0xe4, 0x6e, 0xb7, 0x0e, 0x36, 0xaa, 0xcd, 0x42, 0xd4, 0x6a, 0x65, 0x3b, + 0x40, 0xaf, 0xa5, 0xc9, 0x25, 0x1f, 0x53, 0xa6, 0xcd, 0x1b, 0x74, 0xc6, 0x56, 0x2d, 0x2e, 0x9d, 0x16, 0x97, 0xd0, + 0x62, 0xea, 0x77, 0xdb, 0x36, 0xd3, 0x5b, 0x8c, 0x52, 0x3a, 0xdd, 0x46, 0x15, 0xa9, 0x14, 0xfe, 0x91, 0x46, 0x3b, + 0x58, 0x01, 0xc0, 0xb4, 0xa9, 0x03, 0x11, 0x66, 0x97, 0xc2, 0xbe, 0xc8, 0x2f, 0xc2, 0x01, 0x6f, 0x74, 0x4b, 0xa3, + 0xc6, 0x5a, 0xd3, 0x18, 0xad, 0x61, 0xaa, 0xb8, 0xf0, 0xc8, 0xfc, 0x04, 0x49, 0x8e, 0x76, 0x76, 0xa3, 0x64, 0x85, + 0x46, 0xcf, 0x39, 0x12, 0xec, 0x8b, 0x3c, 0xde, 0x82, 0xec, 0x14, 0xe9, 0x5f, 0x77, 0x77, 0x5a, 0x65, 0xa9, 0x8e, + 0xf4, 0xb3, 0xdd, 0x67, 0x58, 0x45, 0x64, 0xd2, 0x84, 0x51, 0x36, 0xa6, 0xf2, 0xab, 0x6d, 0x41, 0x88, 0x97, 0x96, + 0x29, 0x1c, 0x87, 0x36, 0x60, 0xa4, 0x01, 0xdd, 0x07, 0x45, 0xf0, 0x24, 0xdf, 0x5c, 0xe5, 0x57, 0x32, 0x52, 0xe3, + 0x87, 0x93, 0x93, 0x59, 0x7a, 0xc8, 0x42, 0x92, 0x7a, 0x2b, 0x50, 0x12, 0x69, 0x70, 0xe2, 0x63, 0x70, 0x62, 0x05, + 0x64, 0x2d, 0xbe, 0xfc, 0xd6, 0x08, 0xa4, 0xfa, 0xa7, 0xdd, 0xfb, 0x54, 0xff, 0x34, 0xdd, 0x4e, 0x95, 0xf8, 0x13, + 0x08, 0xdd, 0xd1, 0xfb, 0x0f, 0x0f, 0xef, 0xcc, 0x55, 0xc5, 0xd5, 0x51, 0xd2, 0x98, 0x48, 0x72, 0x53, 0x18, 0x19, + 0x58, 0x03, 0x6a, 0x7b, 0x3a, 0x06, 0x23, 0xb8, 0x22, 0xd8, 0x41, 0xd6, 0x35, 0x1b, 0x49, 0x21, 0x9d, 0xb7, 0x09, + 0xdb, 0x84, 0x20, 0x2f, 0xca, 0x9d, 0xf3, 0x89, 0x04, 0x2a, 0x16, 0x0c, 0x4f, 0x43, 0x6b, 0xd7, 0xcd, 0x5e, 0xa9, + 0x6c, 0xc1, 0x15, 0x06, 0x87, 0xe4, 0x2b, 0x8b, 0xab, 0xe9, 0x65, 0x0a, 0x44, 0x20, 0xfd, 0x8e, 0xda, 0xe1, 0x5e, + 0x5b, 0xb8, 0xef, 0x30, 0xa0, 0xfd, 0x4c, 0x69, 0x6f, 0x12, 0x1d, 0xa0, 0xfb, 0x2a, 0xb8, 0x46, 0x90, 0x01, 0x62, + 0x75, 0xb5, 0x59, 0x9f, 0xf3, 0x38, 0x95, 0x6b, 0x38, 0xf2, 0x6e, 0x9f, 0x5f, 0x85, 0x57, 0xe3, 0x13, 0xd2, 0x4a, + 0x9f, 0x04, 0x8b, 0x0e, 0x06, 0xd5, 0xce, 0x6c, 0xe1, 0xb0, 0x09, 0xac, 0xbd, 0x01, 0xac, 0xab, 0x8c, 0x10, 0x70, + 0x4a, 0x2e, 0x63, 0x0f, 0xb4, 0xac, 0xc3, 0xaf, 0xa3, 0xc1, 0xad, 0x2d, 0xac, 0x94, 0x8f, 0x1e, 0x3f, 0x5a, 0x75, + 0x04, 0x96, 0xea, 0xd1, 0xe3, 0x16, 0x2f, 0x5c, 0x7a, 0xd8, 0x54, 0x78, 0x25, 0x81, 0xa0, 0x5b, 0x09, 0x7b, 0xbc, + 0x28, 0x85, 0x6d, 0x27, 0x9f, 0x39, 0x61, 0xc3, 0x4d, 0xf0, 0x09, 0xe5, 0x4a, 0xf8, 0x28, 0x0a, 0xc4, 0x44, 0x6e, + 0xb6, 0x21, 0xed, 0x60, 0xac, 0x2c, 0x58, 0x47, 0x20, 0x4f, 0x25, 0x8a, 0xc1, 0xcd, 0x09, 0xe5, 0x6b, 0x83, 0x27, + 0x93, 0x5e, 0x5c, 0x4b, 0x20, 0x42, 0xc0, 0x86, 0xa2, 0xed, 0xa8, 0xf5, 0x3b, 0x97, 0xdf, 0x74, 0x7a, 0xe8, 0x17, + 0xc0, 0x80, 0x2c, 0xfd, 0x00, 0x28, 0x3c, 0x7b, 0x6b, 0x32, 0x2b, 0xaf, 0x5e, 0x2e, 0x91, 0x6f, 0x58, 0xc2, 0x49, + 0xb7, 0x44, 0x76, 0x62, 0x09, 0x87, 0x1c, 0x65, 0x74, 0x99, 0x7b, 0x95, 0xd9, 0xba, 0x22, 0x10, 0x76, 0x60, 0x29, + 0x7c, 0x2f, 0xf0, 0x04, 0x4b, 0xa2, 0x4f, 0xcc, 0x17, 0x70, 0xf2, 0x18, 0xeb, 0x15, 0x06, 0x81, 0xde, 0x8e, 0xb1, + 0x7e, 0xe1, 0x17, 0xf1, 0x27, 0x2d, 0x54, 0xf8, 0xf5, 0xa9, 0x0d, 0x5e, 0xc1, 0x47, 0xcd, 0xfd, 0xc3, 0x95, 0xd6, + 0x34, 0x5c, 0x47, 0x57, 0xb8, 0x2c, 0x66, 0xee, 0x58, 0x5e, 0xdb, 0x4d, 0xf1, 0x83, 0x6b, 0x75, 0x76, 0x73, 0x47, + 0x56, 0xc1, 0x17, 0x78, 0x15, 0x8c, 0xc1, 0xaa, 0x02, 0xf7, 0xac, 0xab, 0x5d, 0x9c, 0xfc, 0xbe, 0xc9, 0xaa, 0xd4, + 0x40, 0xa5, 0xc1, 0x9e, 0x86, 0x93, 0x98, 0x42, 0x99, 0xe8, 0x44, 0x0b, 0xc1, 0x15, 0x23, 0x08, 0xdc, 0xa4, 0x45, + 0x83, 0x80, 0x31, 0x68, 0x52, 0xa0, 0x6e, 0xb6, 0x05, 0x42, 0x6a, 0xf8, 0x1d, 0xaa, 0xed, 0xd2, 0x1b, 0xa0, 0x22, + 0x74, 0x5b, 0x48, 0xb6, 0x0a, 0xe6, 0x9a, 0xf3, 0x44, 0xcc, 0x62, 0xb3, 0xeb, 0xcd, 0xf5, 0x07, 0x32, 0x2e, 0xed, + 0x98, 0xf9, 0xa5, 0x36, 0x5b, 0x9b, 0x12, 0x6f, 0xc2, 0xdc, 0x76, 0x11, 0x15, 0xc4, 0x9b, 0xf0, 0xd6, 0xde, 0xf1, + 0x88, 0xa6, 0x6a, 0x90, 0xad, 0x42, 0xf5, 0xdc, 0x0a, 0x58, 0x9d, 0x3e, 0x02, 0x81, 0x16, 0x75, 0x53, 0x59, 0xfd, + 0xb4, 0x69, 0xe8, 0x2a, 0xd4, 0xea, 0xe1, 0x71, 0xab, 0x8d, 0x4d, 0x81, 0xd0, 0x11, 0xce, 0xaa, 0xdc, 0x43, 0xbf, + 0xca, 0xb5, 0xe9, 0xe5, 0xc0, 0xb8, 0x19, 0x53, 0x17, 0x14, 0x88, 0x0d, 0xf8, 0x9c, 0xfb, 0xe4, 0x8d, 0x1e, 0x2f, + 0x46, 0xb0, 0x93, 0x29, 0x7a, 0x52, 0xf7, 0x43, 0x4d, 0xdf, 0x14, 0x8c, 0xa2, 0x30, 0x8a, 0xfe, 0x22, 0x8b, 0x46, + 0x0f, 0x68, 0xde, 0x7f, 0xad, 0x47, 0xc1, 0x0f, 0x79, 0xb4, 0x6b, 0xca, 0x4d, 0xb2, 0x62, 0xaf, 0x86, 0xd1, 0x75, + 0xb9, 0xa9, 0xd3, 0x45, 0xf9, 0xa9, 0x80, 0xc3, 0x05, 0x93, 0x71, 0x2e, 0x24, 0x15, 0x7f, 0x4a, 0x2a, 0x5a, 0x84, + 0x70, 0xe2, 0x06, 0x4e, 0x21, 0xd2, 0x6e, 0xa2, 0x9f, 0x12, 0xfc, 0x23, 0xc9, 0xf4, 0x5b, 0xbf, 0xc1, 0xfa, 0x9c, + 0xaa, 0x25, 0xbd, 0x57, 0xb9, 0xa4, 0x6f, 0xd6, 0xfd, 0xda, 0x61, 0x21, 0xe9, 0xcc, 0x40, 0x9f, 0x74, 0x3a, 0xf9, + 0x4e, 0xe9, 0xd4, 0x36, 0xf8, 0x5c, 0xab, 0x58, 0x56, 0xec, 0xfe, 0x4d, 0x81, 0xac, 0x46, 0x86, 0x1d, 0xff, 0x57, + 0xde, 0x3d, 0xc3, 0x6a, 0xb4, 0xcb, 0xa0, 0xb8, 0x5f, 0x08, 0x7c, 0xdc, 0x34, 0x55, 0x76, 0xb9, 0xc1, 0xb0, 0x21, + 0x3c, 0xfd, 0x23, 0x9f, 0x22, 0x60, 0xba, 0xaf, 0x68, 0x85, 0x8c, 0x48, 0xe7, 0x9c, 0x9d, 0x55, 0xd9, 0x79, 0xa4, + 0xdc, 0x5a, 0xc2, 0x9d, 0x2c, 0x9a, 0x42, 0xf6, 0x25, 0xaa, 0x99, 0xd0, 0xec, 0x43, 0x5b, 0x44, 0xa4, 0x8a, 0x28, + 0xab, 0xe5, 0x95, 0xaa, 0x75, 0x27, 0xcb, 0x8e, 0x17, 0x25, 0x9a, 0x6a, 0xe8, 0xac, 0x91, 0xfe, 0x05, 0x07, 0xff, + 0x65, 0x5e, 0x26, 0xbf, 0x8d, 0xc8, 0xde, 0xf1, 0x16, 0x96, 0x39, 0x7a, 0xcb, 0x58, 0xbf, 0x31, 0x03, 0x19, 0x9e, + 0x4c, 0x20, 0x69, 0x04, 0xa3, 0x41, 0x02, 0xac, 0x12, 0x3f, 0x3e, 0x20, 0x87, 0xaa, 0x5b, 0x5f, 0x5a, 0x71, 0xc2, + 0x3c, 0xc5, 0xda, 0x96, 0x3e, 0xba, 0x85, 0x7e, 0x46, 0xcf, 0x48, 0x74, 0x37, 0x95, 0xe1, 0x51, 0xdc, 0x2e, 0x8e, + 0xa5, 0xaf, 0x79, 0x5f, 0x29, 0xf3, 0x08, 0x61, 0x65, 0x1f, 0xdb, 0x70, 0x4f, 0xfa, 0x53, 0x6a, 0x0c, 0xbb, 0xdf, + 0x92, 0x0a, 0x4a, 0xcd, 0xac, 0x67, 0xb2, 0x3a, 0xaf, 0xaa, 0xa8, 0x00, 0xc9, 0x70, 0x8d, 0x34, 0xea, 0x86, 0xac, + 0xa6, 0xc2, 0xc3, 0x13, 0x33, 0x7b, 0x3f, 0x9b, 0x10, 0xaa, 0xd3, 0x41, 0x0a, 0x72, 0x55, 0x59, 0x42, 0xce, 0xef, + 0x56, 0xee, 0x24, 0x2e, 0x6e, 0x62, 0xb4, 0x0c, 0x1b, 0xa6, 0x2e, 0x4e, 0xb9, 0x9f, 0x3a, 0x6b, 0xe4, 0x87, 0xfc, + 0x2c, 0x23, 0x2c, 0x92, 0x73, 0xbc, 0xf5, 0x23, 0x3b, 0x0f, 0x60, 0xe5, 0x0b, 0x3c, 0x6e, 0x5a, 0xd4, 0x61, 0x63, + 0x66, 0x6d, 0x4b, 0x84, 0x46, 0x35, 0x29, 0x6b, 0x6a, 0xe0, 0x28, 0x2d, 0x62, 0x0a, 0x6c, 0x97, 0xc1, 0x19, 0x55, + 0xe8, 0x21, 0x98, 0x17, 0x14, 0xf6, 0x0c, 0xcb, 0x9b, 0x34, 0x09, 0x85, 0x66, 0x85, 0x8b, 0x95, 0x88, 0x7f, 0x3d, + 0x6d, 0xa6, 0x44, 0x8f, 0x6d, 0x30, 0xee, 0x25, 0x2a, 0x27, 0xe3, 0x8c, 0xdc, 0x77, 0x7c, 0x4d, 0x79, 0x74, 0x15, + 0xff, 0xe8, 0x47, 0x9b, 0x94, 0x81, 0x08, 0x24, 0xd8, 0xf8, 0x86, 0xfd, 0x0d, 0xdf, 0x5e, 0xd6, 0x69, 0x85, 0xa0, + 0x97, 0x25, 0x1c, 0x1a, 0x7c, 0xe7, 0x8a, 0xc3, 0xee, 0xca, 0x28, 0xa5, 0x5f, 0x45, 0xd5, 0xdd, 0x1d, 0xb4, 0x2b, + 0xc6, 0xc1, 0x4f, 0x17, 0xdf, 0xe3, 0x75, 0x58, 0x40, 0xd8, 0x8c, 0x08, 0x61, 0x4e, 0x2f, 0x78, 0x80, 0xf5, 0x2b, + 0x04, 0x8d, 0x4b, 0x89, 0x81, 0xd1, 0xa2, 0x6d, 0xc9, 0xdf, 0xf2, 0x16, 0x65, 0x42, 0xa8, 0x51, 0x88, 0x89, 0x92, + 0xe5, 0x0b, 0x9c, 0x0e, 0x32, 0x7e, 0xdf, 0x5c, 0x36, 0xc0, 0x94, 0xe0, 0xd4, 0x3b, 0x37, 0xc3, 0x7f, 0xfb, 0x5f, + 0xeb, 0x4b, 0xa7, 0xc9, 0x76, 0x6f, 0x9c, 0x6e, 0xfe, 0xb7, 0xfb, 0xc2, 0xdf, 0x7f, 0x9e, 0xaa, 0x38, 0xef, 0x24, + 0x6e, 0xff, 0x8a, 0xde, 0xfb, 0xba, 0x97, 0xd7, 0x95, 0x36, 0xc3, 0xcc, 0xa2, 0x4f, 0xc0, 0x50, 0x97, 0x9f, 0xa6, + 0x8b, 0xce, 0x91, 0x37, 0xcb, 0x60, 0xd5, 0xfc, 0x0a, 0xcc, 0x9e, 0xf7, 0x2b, 0x06, 0xe3, 0x7d, 0x9e, 0x1a, 0x43, + 0x4c, 0x8e, 0xc1, 0xb7, 0x83, 0x75, 0xb1, 0xa9, 0x90, 0x20, 0x77, 0x4f, 0x4b, 0xd4, 0xcc, 0xc0, 0x49, 0x82, 0x9d, + 0xb0, 0xd6, 0xfb, 0x3f, 0x65, 0xbd, 0x3f, 0x4f, 0x45, 0xb2, 0x92, 0x0f, 0xf7, 0x76, 0x18, 0xda, 0x1e, 0x43, 0x86, + 0x51, 0x70, 0x5d, 0xf9, 0xf8, 0x5d, 0xb9, 0xe9, 0xb3, 0xaa, 0xfa, 0x37, 0x29, 0x6a, 0xe0, 0x15, 0x07, 0x1e, 0x44, + 0xed, 0x6c, 0xb7, 0xd6, 0xa1, 0x2d, 0x68, 0x57, 0x6c, 0x2a, 0xfb, 0xfb, 0xbd, 0x53, 0x7e, 0x74, 0xf4, 0xbe, 0xf0, + 0x3a, 0x71, 0xdd, 0x0d, 0xdc, 0x65, 0xe5, 0x11, 0x04, 0xb0, 0xe6, 0x81, 0xf2, 0x88, 0x60, 0xd2, 0xe1, 0xa3, 0xc4, + 0x9b, 0xce, 0x52, 0x7a, 0x1d, 0xe4, 0xa7, 0x4e, 0x32, 0x4f, 0x70, 0xc0, 0x2d, 0xc5, 0x95, 0x80, 0x33, 0xf5, 0x9e, + 0xda, 0xa6, 0x97, 0x55, 0x6c, 0xd9, 0x1a, 0xe2, 0xed, 0x22, 0x70, 0xfb, 0xc4, 0x66, 0x22, 0x80, 0xcd, 0x7b, 0xe4, + 0xaf, 0x58, 0x74, 0x58, 0x75, 0x52, 0x45, 0xbe, 0xce, 0x39, 0x18, 0xcd, 0x8a, 0xc3, 0xfc, 0x96, 0x1e, 0xde, 0x6f, + 0x9b, 0x15, 0x55, 0xe9, 0x15, 0x05, 0x1c, 0x2c, 0x4d, 0x4b, 0xe9, 0x5c, 0xe2, 0xff, 0x23, 0x53, 0x23, 0x92, 0x92, + 0x55, 0x65, 0x56, 0xc3, 0x27, 0x0a, 0xa8, 0x1e, 0x3d, 0x0e, 0xc4, 0x38, 0x1c, 0xc7, 0xf1, 0xe8, 0x88, 0x26, 0xc2, + 0x14, 0x6c, 0x56, 0xf7, 0x0c, 0x65, 0xc3, 0x7b, 0x25, 0xc3, 0x98, 0x8a, 0x1a, 0x21, 0x22, 0xf5, 0x7e, 0xc2, 0x38, + 0xab, 0x19, 0x2c, 0x14, 0xeb, 0x8a, 0x0e, 0x08, 0x30, 0xc8, 0x5f, 0xc8, 0x5f, 0xd7, 0xc2, 0xce, 0xa4, 0xab, 0xfb, + 0xd8, 0x19, 0x07, 0x20, 0xe6, 0xcb, 0x1c, 0x8d, 0xc6, 0xfe, 0x47, 0x24, 0x18, 0x6e, 0x1f, 0x52, 0xba, 0xb9, 0xf7, + 0xaf, 0x5c, 0x70, 0x14, 0x7d, 0x26, 0x93, 0x7d, 0xce, 0xd2, 0x68, 0xe1, 0xb8, 0x1c, 0x47, 0x18, 0xc7, 0xd3, 0xb9, + 0x1f, 0x64, 0x9b, 0x92, 0x95, 0x03, 0xe9, 0xec, 0x4c, 0x1d, 0x53, 0xea, 0x68, 0x3c, 0xd7, 0x1b, 0xaa, 0xd4, 0x73, + 0x0d, 0x4b, 0x01, 0x6f, 0x4f, 0xbe, 0xf5, 0x2a, 0x7f, 0x9e, 0xca, 0x1a, 0x36, 0x1c, 0x41, 0xe9, 0x87, 0xb4, 0x1b, + 0xac, 0x14, 0xbc, 0x30, 0x35, 0xa1, 0xb9, 0x0a, 0x3e, 0x47, 0xd1, 0xa2, 0x70, 0x28, 0x62, 0x6b, 0xed, 0x3b, 0x9f, + 0x4c, 0x39, 0x37, 0x7c, 0x30, 0xaa, 0x31, 0xe6, 0x32, 0x2a, 0x84, 0xf9, 0x78, 0x96, 0xbf, 0x81, 0xc4, 0xf5, 0xa4, + 0x8e, 0x60, 0x30, 0xf8, 0x7d, 0xec, 0xb4, 0x98, 0x43, 0x0f, 0x1e, 0x7a, 0x56, 0xe0, 0xb0, 0xe9, 0x83, 0x75, 0x55, + 0xde, 0x66, 0xa4, 0x97, 0x30, 0x0f, 0x14, 0x9f, 0xb7, 0x8a, 0x76, 0x31, 0xb1, 0xb7, 0xe1, 0x3f, 0x72, 0xf8, 0x2c, + 0xfd, 0xfa, 0x5b, 0x1e, 0xf0, 0x42, 0x0b, 0xff, 0x9e, 0xb7, 0x14, 0xa8, 0x18, 0xb1, 0x22, 0x30, 0x96, 0xa9, 0xfa, + 0xf0, 0x3e, 0x36, 0xde, 0x5a, 0x0d, 0xf7, 0x7c, 0xb3, 0x46, 0x9d, 0xfa, 0xb9, 0xbb, 0xb3, 0x3d, 0xdd, 0x8c, 0x4c, + 0x35, 0x03, 0x7e, 0x49, 0x33, 0xfe, 0x91, 0x71, 0x33, 0x7e, 0xcf, 0xd9, 0x7f, 0x07, 0xd6, 0x27, 0x45, 0x4e, 0x36, + 0x3e, 0x49, 0xfb, 0xe5, 0x86, 0x3d, 0x54, 0xf6, 0x4b, 0xd2, 0x44, 0x96, 0x1b, 0x4f, 0x21, 0x57, 0x02, 0x50, 0x2b, + 0x11, 0xc8, 0x93, 0xe6, 0x0b, 0x0e, 0x0f, 0x3d, 0xda, 0xb1, 0x59, 0xfd, 0x9c, 0x37, 0x2c, 0x46, 0xf3, 0x32, 0xdb, + 0x33, 0x5b, 0x21, 0xd9, 0x94, 0xac, 0xdf, 0x15, 0x1e, 0x42, 0x30, 0xb3, 0x9a, 0x00, 0x91, 0x16, 0x12, 0x38, 0x43, + 0x8a, 0xe7, 0xb4, 0xaa, 0x0f, 0xa3, 0xd1, 0xa6, 0x58, 0x70, 0xd0, 0x6a, 0xd8, 0xe5, 0xd9, 0x01, 0x9c, 0xfd, 0xe4, + 0xd4, 0xd0, 0xcf, 0x3a, 0x7f, 0x95, 0x87, 0xe9, 0x4a, 0x76, 0xe9, 0x3f, 0x5d, 0x27, 0x2f, 0x63, 0xfb, 0x7a, 0x0b, + 0x9b, 0x4e, 0xfd, 0xde, 0x5a, 0xbf, 0x2d, 0x10, 0xdc, 0x59, 0xdf, 0x4e, 0x56, 0xa5, 0x3c, 0x30, 0x26, 0xed, 0x23, + 0xbf, 0x6d, 0xca, 0x32, 0x6f, 0xb2, 0xf5, 0x3b, 0xd1, 0xd3, 0xe8, 0xb1, 0xd8, 0xe1, 0x65, 0xf0, 0x16, 0x01, 0xcf, + 0xb4, 0x6b, 0x80, 0xd8, 0x9e, 0xb1, 0x85, 0xfb, 0xb9, 0xc5, 0x3f, 0xa9, 0xac, 0xed, 0x2a, 0xae, 0xd9, 0x37, 0xc3, + 0x5c, 0x42, 0x89, 0x9f, 0xc6, 0x2d, 0xc8, 0xe6, 0xea, 0xf7, 0x96, 0xbc, 0xa8, 0xb8, 0xba, 0x3e, 0x1a, 0x95, 0xd5, + 0x3c, 0x26, 0x07, 0x77, 0x77, 0x87, 0x85, 0x8d, 0xa3, 0xad, 0x77, 0xc0, 0xce, 0x72, 0x95, 0xb2, 0x77, 0x22, 0x6e, + 0x3f, 0xda, 0xf9, 0x40, 0x90, 0xe0, 0x5f, 0xf1, 0xb4, 0xf0, 0xfc, 0x39, 0x3d, 0x5d, 0x34, 0x25, 0xb9, 0x67, 0xf0, + 0x1e, 0xad, 0xd1, 0x99, 0xe0, 0x9f, 0x03, 0xa8, 0x97, 0x56, 0xda, 0x7b, 0xd4, 0xad, 0xe0, 0x08, 0x9a, 0xfb, 0x81, + 0x55, 0x57, 0x20, 0x51, 0xd2, 0x5b, 0x93, 0x25, 0xbf, 0x01, 0xdf, 0x11, 0xd5, 0xb8, 0x38, 0x5c, 0xd7, 0x27, 0x10, + 0x47, 0x3f, 0xe2, 0xdb, 0xef, 0x30, 0xe4, 0x16, 0x88, 0x81, 0xc8, 0xb6, 0xa0, 0x99, 0xc7, 0x75, 0xfc, 0x6b, 0x59, + 0x89, 0x47, 0xfd, 0x62, 0x5e, 0xa1, 0xf6, 0x2e, 0x24, 0x03, 0x2c, 0x0d, 0xe2, 0x15, 0xb3, 0xe5, 0x6c, 0x82, 0xb0, + 0xec, 0x18, 0xe5, 0x2c, 0x8f, 0xd8, 0xcd, 0xb3, 0x7a, 0x52, 0x6b, 0x7c, 0x76, 0x28, 0x16, 0xe4, 0x68, 0x2c, 0x00, + 0x12, 0x6e, 0x90, 0x6b, 0xd5, 0x53, 0xb9, 0x22, 0x9b, 0x57, 0x36, 0x82, 0xab, 0xd0, 0xc8, 0x06, 0xf9, 0x17, 0x0c, + 0x92, 0xa6, 0x94, 0x35, 0xd5, 0x93, 0x13, 0x96, 0x90, 0xc9, 0x72, 0xde, 0xf3, 0x92, 0x49, 0xec, 0x3f, 0xf2, 0x2a, + 0x74, 0xdf, 0x24, 0xb2, 0x4d, 0x5c, 0xd8, 0xdf, 0x52, 0xaa, 0x7e, 0x15, 0x7c, 0xeb, 0x2d, 0xfd, 0xf9, 0x71, 0x18, + 0x4f, 0x28, 0x36, 0xd4, 0x22, 0xc2, 0xe8, 0x69, 0x18, 0xc5, 0x66, 0x71, 0xba, 0x99, 0x2d, 0xc6, 0x63, 0x5f, 0x67, + 0x2c, 0xcf, 0x16, 0x18, 0x06, 0x79, 0x31, 0x3e, 0x39, 0xd7, 0x27, 0x84, 0x7e, 0x99, 0x70, 0x3d, 0xca, 0xd1, 0x39, + 0x4c, 0xc6, 0x4b, 0xc4, 0x53, 0xef, 0x64, 0xc3, 0x24, 0x13, 0x06, 0x7a, 0xe5, 0xde, 0x26, 0xa9, 0x01, 0x67, 0xb4, + 0x0e, 0x32, 0x5c, 0xbd, 0xc0, 0xc1, 0xa7, 0x7d, 0xef, 0x93, 0x68, 0x78, 0xc1, 0xb5, 0x3f, 0x4a, 0xc7, 0x5e, 0x03, + 0x6d, 0x3e, 0x61, 0xa9, 0xf0, 0x02, 0xe6, 0xe1, 0x3b, 0x79, 0xe1, 0x98, 0xa3, 0xb2, 0x86, 0xc0, 0xe0, 0xed, 0x91, + 0xb9, 0x99, 0x31, 0x4c, 0xe9, 0x1d, 0xc6, 0x89, 0x4c, 0xb1, 0xea, 0xc5, 0x23, 0xb1, 0x46, 0xf0, 0xed, 0x4a, 0xc9, + 0x97, 0x2d, 0x38, 0x31, 0x21, 0x0a, 0xff, 0x81, 0x60, 0x87, 0xda, 0x5e, 0xa9, 0x82, 0x01, 0x8c, 0x23, 0x63, 0x7f, + 0x4c, 0xc0, 0x92, 0x95, 0xf1, 0xa4, 0x4a, 0x34, 0x12, 0x7f, 0x62, 0xe6, 0x3a, 0x69, 0x87, 0xbe, 0x60, 0x19, 0xb2, + 0xac, 0x86, 0x8d, 0x49, 0x2c, 0x23, 0x92, 0xcc, 0x36, 0x1f, 0x49, 0xe1, 0x7b, 0x78, 0x47, 0xcc, 0x2b, 0x11, 0x8f, + 0x78, 0x52, 0x22, 0xa7, 0x43, 0x76, 0x1b, 0xf1, 0xaa, 0x6b, 0xcb, 0x4a, 0xd1, 0x82, 0x70, 0x79, 0x72, 0xf8, 0x20, + 0x09, 0x39, 0x95, 0xa4, 0x40, 0x6b, 0x89, 0x2f, 0x3f, 0x86, 0x3e, 0xe9, 0xcf, 0x61, 0xd7, 0x2a, 0xb4, 0x92, 0xa1, + 0xe0, 0x91, 0xf4, 0x99, 0x0c, 0xaf, 0xc5, 0x82, 0x7a, 0x3c, 0xa6, 0x7a, 0xea, 0x87, 0xce, 0x95, 0xf4, 0xdf, 0x06, + 0x8d, 0xb0, 0x9f, 0xc2, 0xe4, 0x58, 0x5a, 0x5e, 0x98, 0xac, 0xa7, 0x5e, 0x1d, 0xa8, 0x8f, 0xf8, 0xe6, 0xd7, 0x4c, + 0x9b, 0x60, 0xeb, 0x9b, 0xb1, 0xd4, 0x6a, 0x1f, 0x42, 0xda, 0x53, 0xb8, 0xbc, 0x7a, 0x52, 0xc0, 0x0a, 0x4a, 0x1e, + 0x59, 0xeb, 0x20, 0x79, 0x94, 0xfa, 0x14, 0xcb, 0x6e, 0xb6, 0x3a, 0x3d, 0x9e, 0xf9, 0x31, 0xb4, 0x4f, 0x00, 0x68, + 0x79, 0x9f, 0x94, 0xe3, 0xf8, 0x61, 0x2a, 0x13, 0x69, 0x74, 0x54, 0x25, 0xde, 0x58, 0xe6, 0xa7, 0xd5, 0x2c, 0x87, + 0x8e, 0x22, 0xdb, 0xb8, 0xb2, 0x3b, 0x9a, 0x43, 0x47, 0xf7, 0x54, 0x64, 0xf5, 0x39, 0xe9, 0xac, 0x74, 0x0b, 0x0c, + 0x00, 0x27, 0x91, 0xc0, 0x72, 0x1f, 0x1b, 0xfe, 0x88, 0x07, 0x3d, 0xc3, 0x19, 0x48, 0xa3, 0x13, 0x98, 0xd0, 0x86, + 0xec, 0x81, 0x48, 0xcd, 0x91, 0xe2, 0x35, 0x6a, 0x0a, 0x24, 0x03, 0x39, 0x44, 0x53, 0xc4, 0xa0, 0x7f, 0x31, 0x9b, + 0xbd, 0xd2, 0xa1, 0xc4, 0xe9, 0x32, 0x72, 0x2e, 0x97, 0x91, 0x21, 0x47, 0x17, 0xa7, 0xdf, 0x73, 0x7e, 0x05, 0x42, + 0xf1, 0xb3, 0xda, 0x04, 0x0e, 0x27, 0xf6, 0x15, 0x6f, 0x35, 0xe0, 0xe8, 0x33, 0xc5, 0xb3, 0xb3, 0xe6, 0x7c, 0x0c, + 0xf2, 0x33, 0xfc, 0x99, 0xa4, 0xc1, 0x8f, 0x3a, 0x14, 0xb9, 0x41, 0xea, 0x04, 0x91, 0x1c, 0xf9, 0x53, 0xdd, 0xe5, + 0x57, 0xb5, 0x4b, 0x4f, 0x81, 0xfc, 0x99, 0x35, 0xfa, 0xa8, 0xa1, 0x7d, 0x6b, 0x0d, 0x43, 0x29, 0x68, 0x4d, 0xb3, + 0xf2, 0xb4, 0x9e, 0x95, 0x63, 0xe8, 0x5a, 0xaa, 0x86, 0xd8, 0x9a, 0xc1, 0xd2, 0x3f, 0xb7, 0x40, 0x98, 0xf4, 0xb7, + 0x56, 0x03, 0x5c, 0x35, 0x51, 0x6d, 0x45, 0x6d, 0x6b, 0x1b, 0x52, 0xb4, 0x00, 0x3a, 0x18, 0xa0, 0xd9, 0xff, 0x05, + 0x29, 0xdb, 0x88, 0xd5, 0xa0, 0x9b, 0xd5, 0x0b, 0xe0, 0x9e, 0xf9, 0x29, 0x8e, 0x4e, 0xd2, 0xc9, 0x5f, 0xc5, 0xbc, + 0x39, 0xb3, 0x41, 0x0e, 0x90, 0xdc, 0x7b, 0x44, 0x8e, 0xc9, 0x02, 0x19, 0xad, 0x11, 0x0a, 0x18, 0xa6, 0x93, 0xb9, + 0xb5, 0x62, 0x92, 0x09, 0xd0, 0xec, 0x49, 0xe2, 0x87, 0x14, 0xf1, 0x72, 0x8e, 0x51, 0xd9, 0x7b, 0x55, 0x9c, 0xf8, + 0x90, 0xe1, 0xd1, 0xe3, 0x10, 0x5e, 0x26, 0x93, 0x81, 0x2f, 0x20, 0xad, 0x7e, 0xf4, 0x38, 0x48, 0xc6, 0x51, 0x7d, + 0xda, 0xcc, 0xf3, 0x70, 0x92, 0x07, 0xc9, 0x69, 0x39, 0x41, 0x1b, 0xda, 0x27, 0xd5, 0x38, 0xf6, 0x7d, 0x43, 0x39, + 0xf4, 0x30, 0x2c, 0xe4, 0x08, 0x7b, 0x85, 0x66, 0xbb, 0x9a, 0x63, 0xc6, 0x2b, 0x9b, 0xab, 0x24, 0x30, 0xd8, 0xf2, + 0x8f, 0x1e, 0x9b, 0x58, 0x42, 0xf5, 0x13, 0xd1, 0x6c, 0x94, 0x74, 0x9d, 0x5c, 0xd3, 0x75, 0xb2, 0x62, 0x6a, 0xd9, + 0x26, 0x15, 0x4f, 0xd8, 0xf3, 0x59, 0xa1, 0xee, 0x8d, 0x8e, 0xc9, 0x71, 0xd1, 0x5a, 0xdb, 0x71, 0x6a, 0x78, 0xa6, + 0x83, 0x8c, 0x2e, 0xb0, 0xe8, 0x4c, 0x9c, 0xf3, 0x1c, 0x30, 0x5d, 0x0e, 0x2d, 0x6d, 0xc8, 0x0f, 0xb2, 0x32, 0xe8, + 0x6e, 0x48, 0x69, 0xd4, 0x0c, 0xfc, 0x99, 0x5a, 0x30, 0x3f, 0xe1, 0x2d, 0x11, 0x7a, 0x75, 0x21, 0x26, 0x49, 0xc1, + 0x97, 0x46, 0xea, 0x96, 0x48, 0xd6, 0x80, 0xf7, 0x1e, 0x2d, 0x59, 0x40, 0xf0, 0xf0, 0xe7, 0xfc, 0x57, 0x1d, 0x3e, + 0x27, 0xfe, 0xc3, 0x14, 0xa3, 0x90, 0xcb, 0x85, 0xc8, 0x58, 0xe1, 0x90, 0x5a, 0x17, 0xef, 0x2b, 0xc7, 0x32, 0xf0, + 0x25, 0x0a, 0xd0, 0xec, 0xd0, 0x1f, 0xc2, 0x07, 0xc9, 0x23, 0x8b, 0x9e, 0x46, 0x76, 0x57, 0x97, 0x7a, 0x9d, 0x39, + 0xc5, 0xb0, 0x9b, 0xc0, 0x2e, 0xb1, 0xb9, 0x9d, 0x2a, 0x8d, 0x89, 0x4e, 0xe1, 0xb5, 0x2e, 0xcc, 0x88, 0x64, 0x55, + 0xe2, 0x69, 0x79, 0x0e, 0xd7, 0x11, 0x7b, 0xe8, 0xd0, 0xce, 0x04, 0xf6, 0x28, 0x65, 0xf7, 0x1d, 0x65, 0xf9, 0x40, + 0xcb, 0x2c, 0x5f, 0xa1, 0x36, 0x30, 0x28, 0x4c, 0x9d, 0x57, 0x16, 0xe9, 0x66, 0x16, 0x0f, 0x6f, 0x09, 0x6a, 0x32, + 0x73, 0x0a, 0x4b, 0x75, 0x49, 0x50, 0xc9, 0xad, 0x41, 0x2c, 0xea, 0xb0, 0x04, 0x3d, 0x87, 0x2c, 0x04, 0xdc, 0x5c, + 0x79, 0xa3, 0xad, 0xda, 0x28, 0xa1, 0xb5, 0x41, 0x4c, 0x00, 0x05, 0xf6, 0x14, 0x75, 0x20, 0x30, 0x04, 0x88, 0x8f, + 0x12, 0x4f, 0x68, 0xd5, 0x64, 0x1d, 0xcb, 0x41, 0x9a, 0xcb, 0x65, 0x74, 0x55, 0x13, 0x18, 0x04, 0x30, 0x11, 0x14, + 0x3f, 0x59, 0xd6, 0xdf, 0xa5, 0x13, 0x69, 0x27, 0xf5, 0x91, 0xaa, 0x5d, 0xc0, 0xb1, 0x70, 0x79, 0x3e, 0x43, 0xff, + 0x96, 0xcb, 0xf3, 0x3e, 0x86, 0xc8, 0x85, 0x3f, 0xbe, 0x9a, 0x48, 0xac, 0x28, 0x32, 0x59, 0x4f, 0x58, 0x91, 0x3d, + 0x5f, 0x47, 0x04, 0x82, 0x83, 0xbd, 0x1a, 0xe7, 0x74, 0x49, 0xfc, 0xe8, 0x31, 0x66, 0x0d, 0xd7, 0xd1, 0xb3, 0x9a, + 0x8d, 0xd5, 0xfd, 0xd9, 0x06, 0x9b, 0xc9, 0x57, 0xd6, 0x2a, 0x94, 0xf3, 0x97, 0x9b, 0xb2, 0xdc, 0xdb, 0x94, 0xc9, + 0xf5, 0x78, 0xa8, 0x29, 0xcb, 0x88, 0x42, 0x1b, 0xd0, 0x6d, 0xba, 0x42, 0x0c, 0xc5, 0xac, 0xe1, 0xd2, 0x6a, 0xca, + 0x9a, 0xc7, 0x04, 0x1d, 0x7d, 0x30, 0xca, 0xa8, 0xa1, 0xa7, 0x93, 0x1f, 0xc2, 0x1f, 0x94, 0xcb, 0x52, 0x93, 0x35, + 0x79, 0xfa, 0x53, 0xb8, 0x0c, 0xe8, 0xc7, 0xcf, 0xe1, 0x1a, 0xb1, 0x04, 0xf8, 0xe6, 0x6e, 0x63, 0xa3, 0xf5, 0x8a, + 0x08, 0xf1, 0x7d, 0xa3, 0x05, 0xfd, 0x8e, 0x34, 0xd1, 0x28, 0xc0, 0x08, 0x85, 0x16, 0x81, 0x77, 0xf5, 0x18, 0x7b, + 0x0a, 0x1f, 0x08, 0xc3, 0xb9, 0x61, 0xad, 0xa9, 0xe3, 0x5e, 0x67, 0xe3, 0x48, 0x24, 0xcd, 0x2d, 0x0a, 0xee, 0xcd, + 0xad, 0x15, 0xbf, 0x51, 0x81, 0x00, 0x48, 0x35, 0xe5, 0xda, 0x29, 0x21, 0x56, 0x19, 0x76, 0x12, 0x59, 0x6f, 0xd8, + 0x09, 0x6c, 0xc5, 0x61, 0xa7, 0xb0, 0x10, 0x6d, 0xa7, 0x88, 0x26, 0xda, 0x4e, 0x02, 0xae, 0xae, 0x42, 0xe7, 0x57, + 0x6d, 0xad, 0xa3, 0xee, 0xbe, 0xf8, 0x26, 0x4c, 0xdf, 0x80, 0x71, 0x6e, 0x35, 0x66, 0x4e, 0x15, 0xd7, 0xea, 0xbe, + 0xd3, 0x49, 0x15, 0x2a, 0xf2, 0xb1, 0xd3, 0x15, 0x49, 0x7e, 0xd6, 0xef, 0x91, 0xbc, 0xf9, 0xae, 0xdb, 0x31, 0x49, + 0x7f, 0xdf, 0xfb, 0x02, 0x99, 0x22, 0x3b, 0x63, 0x82, 0x0e, 0x99, 0x0a, 0xaa, 0xd5, 0x6d, 0x62, 0x56, 0x74, 0x9b, + 0xec, 0x8e, 0x42, 0x45, 0xee, 0x74, 0x76, 0x12, 0x1d, 0x6d, 0x26, 0x18, 0x38, 0x3a, 0x81, 0xa2, 0xab, 0x28, 0xb9, + 0x47, 0x90, 0xd6, 0x48, 0x5e, 0xd0, 0x47, 0x87, 0x53, 0x91, 0xa5, 0x76, 0x53, 0x89, 0x70, 0x46, 0x66, 0xe8, 0x85, + 0xa1, 0xd6, 0x29, 0x79, 0x89, 0x21, 0x21, 0x9a, 0x8f, 0xb0, 0xf6, 0x9e, 0xa3, 0x88, 0x1d, 0xed, 0x5a, 0x15, 0x9e, + 0x5c, 0x9f, 0xa2, 0x6e, 0xc3, 0xd5, 0x69, 0xda, 0xe9, 0x8e, 0xed, 0x67, 0x18, 0xa1, 0x65, 0x05, 0xc3, 0x76, 0xd4, + 0x69, 0xf5, 0xa6, 0xdb, 0x75, 0x4a, 0xec, 0x8c, 0xcf, 0x45, 0xbd, 0xb9, 0xc2, 0x86, 0xa4, 0x8b, 0x5e, 0x76, 0xf3, + 0xa6, 0x5b, 0x86, 0x46, 0xac, 0xd3, 0x21, 0xbe, 0x9f, 0xa1, 0x5d, 0xae, 0xd3, 0x12, 0xb7, 0x67, 0x78, 0xfe, 0x91, + 0x68, 0x57, 0x53, 0x97, 0x17, 0xba, 0xb9, 0x3a, 0x52, 0x9d, 0x28, 0x36, 0xc8, 0xd7, 0xb4, 0x11, 0x35, 0x8f, 0x09, + 0xd2, 0x5d, 0x5b, 0x31, 0xab, 0x44, 0xe2, 0x56, 0xcf, 0x2a, 0x3c, 0x37, 0xad, 0x14, 0x8e, 0xc0, 0xd9, 0xe9, 0xb4, + 0xac, 0x30, 0x42, 0xd7, 0xc7, 0x55, 0xe2, 0x77, 0x46, 0xca, 0x7d, 0x1f, 0x2b, 0xac, 0x69, 0x77, 0x14, 0x9c, 0x4c, + 0xf6, 0x9b, 0x7e, 0xee, 0x6e, 0x95, 0xf6, 0x1b, 0xdf, 0x86, 0xf9, 0xd7, 0x12, 0x04, 0x74, 0xe7, 0x87, 0x1a, 0x81, + 0x06, 0x81, 0xe7, 0x15, 0x94, 0xfa, 0x9d, 0x6a, 0x3e, 0x9c, 0x83, 0xaa, 0xa5, 0x70, 0x34, 0x3a, 0x8d, 0xc2, 0xf8, + 0xe1, 0x76, 0xe5, 0x2a, 0x8e, 0xb8, 0x89, 0xc2, 0xf8, 0x3b, 0x7c, 0x68, 0x9f, 0x53, 0x19, 0x9a, 0x19, 0xff, 0xee, + 0xa5, 0xc1, 0x3e, 0xac, 0x15, 0xe4, 0x15, 0x7e, 0x07, 0x9a, 0xbb, 0xbf, 0x7c, 0x0d, 0xef, 0xeb, 0x7b, 0xca, 0x13, + 0xce, 0xcb, 0xef, 0x14, 0xd4, 0x89, 0x50, 0x5d, 0x7e, 0x67, 0x4c, 0x31, 0x48, 0x7d, 0xc1, 0x0a, 0x9f, 0xf0, 0x57, + 0x8c, 0x77, 0x08, 0xaf, 0xcc, 0x33, 0x5a, 0xec, 0x58, 0xd1, 0x76, 0x79, 0x2c, 0xb0, 0x9e, 0x94, 0xaa, 0x28, 0xec, + 0xd2, 0xb5, 0x5d, 0xb4, 0x34, 0xca, 0x18, 0x7b, 0x2c, 0x41, 0xf2, 0x42, 0xee, 0xec, 0xe8, 0xa8, 0x14, 0xc6, 0xc8, + 0xae, 0x4c, 0x39, 0x61, 0xc4, 0x16, 0x30, 0x7d, 0x93, 0xac, 0x80, 0x12, 0x89, 0xf7, 0x91, 0xb8, 0x1d, 0x69, 0x91, + 0x94, 0xc3, 0x5e, 0x2b, 0xb7, 0x1c, 0x1d, 0xfd, 0xba, 0x8a, 0x30, 0xe4, 0x75, 0xc7, 0x29, 0x5f, 0x75, 0x01, 0xf5, + 0x4a, 0xd0, 0x4b, 0xd4, 0x0a, 0xa6, 0x96, 0xca, 0x23, 0xa8, 0x68, 0x2d, 0x02, 0x03, 0x0d, 0x2f, 0x0a, 0x0b, 0xca, + 0x05, 0x5f, 0xc0, 0x42, 0x31, 0xec, 0x39, 0x0a, 0x84, 0xe9, 0x93, 0x82, 0x7c, 0x2b, 0x0a, 0x8c, 0x81, 0x8c, 0x0f, + 0xe8, 0x78, 0x2f, 0xe3, 0x9b, 0x06, 0x38, 0x4d, 0x28, 0x91, 0xf1, 0x20, 0x17, 0xc1, 0xef, 0xe4, 0x98, 0xc1, 0x23, + 0xa9, 0xcd, 0x67, 0x77, 0xca, 0xb1, 0x17, 0xcf, 0x80, 0x2e, 0x35, 0x26, 0x3b, 0x2b, 0x38, 0x0d, 0x3a, 0x64, 0x1d, + 0x93, 0xf9, 0xb0, 0xe8, 0x91, 0x69, 0x7c, 0x25, 0x27, 0x47, 0x9f, 0xe8, 0xc3, 0x4b, 0xfa, 0x38, 0xf1, 0xaf, 0x1d, + 0xe0, 0x55, 0x22, 0x21, 0x1a, 0x55, 0x69, 0x18, 0x31, 0x87, 0xbd, 0x97, 0xec, 0x52, 0xc6, 0xca, 0x89, 0xa1, 0x94, + 0x04, 0x1b, 0xde, 0xe1, 0x9e, 0xe6, 0xcd, 0xf4, 0x56, 0x1c, 0xf6, 0x9b, 0xe9, 0x96, 0x7f, 0xa1, 0xe2, 0xd1, 0xc2, + 0x5f, 0xd2, 0xdf, 0x25, 0x6a, 0xee, 0x39, 0xdf, 0x34, 0xb6, 0x23, 0x2e, 0x50, 0xac, 0xa1, 0xfe, 0x3a, 0x2c, 0x9d, + 0x75, 0x20, 0x38, 0xe0, 0x2d, 0x76, 0xd5, 0x30, 0xfe, 0xae, 0xd1, 0xd3, 0xee, 0x6b, 0x49, 0xa3, 0x94, 0xfb, 0x41, + 0x50, 0x0e, 0x76, 0xaf, 0x5d, 0x34, 0x7f, 0xfb, 0x6d, 0x40, 0x41, 0x85, 0xce, 0x0d, 0xb6, 0x93, 0xcd, 0xc2, 0xda, + 0x18, 0x07, 0x75, 0x70, 0x55, 0xc5, 0x09, 0x46, 0x50, 0xa7, 0xf1, 0x27, 0x9b, 0x4d, 0xab, 0x52, 0x02, 0x54, 0xae, + 0x63, 0xc4, 0x1e, 0xc0, 0x13, 0x8d, 0x71, 0x03, 0xdc, 0x66, 0x74, 0xb8, 0x83, 0xa6, 0xcb, 0x18, 0xa4, 0x1d, 0x66, + 0xa3, 0xe8, 0x9a, 0x3a, 0x7d, 0x81, 0xf9, 0x50, 0x94, 0x94, 0x0f, 0xe5, 0x2f, 0x9e, 0xb3, 0x7f, 0xec, 0xb0, 0xe6, + 0xae, 0x7c, 0x40, 0xcc, 0x9c, 0xeb, 0xb4, 0xa8, 0x09, 0x3a, 0x45, 0xbe, 0x57, 0x0f, 0x25, 0xc6, 0x4b, 0xe0, 0x4d, + 0x07, 0xb3, 0x5b, 0x27, 0xfa, 0xe0, 0x12, 0xd4, 0x5c, 0xd9, 0xb8, 0x60, 0xae, 0xb6, 0x98, 0x5a, 0x3b, 0x68, 0xa5, + 0xc4, 0x28, 0x34, 0x7c, 0x2a, 0xb4, 0xf9, 0xff, 0xe0, 0x4a, 0x50, 0xab, 0x8d, 0xdb, 0xfe, 0x42, 0xbf, 0x55, 0x2d, + 0x59, 0xa4, 0x68, 0x72, 0x02, 0x43, 0xd0, 0x7f, 0x45, 0xcd, 0xef, 0x27, 0x0b, 0x0b, 0xf4, 0x22, 0x61, 0xaa, 0x84, + 0x41, 0xd0, 0xf6, 0xae, 0x42, 0x15, 0x40, 0x81, 0xbf, 0xfe, 0x6c, 0x93, 0xe5, 0x0b, 0xd9, 0xcd, 0xf6, 0x34, 0x71, + 0x16, 0xeb, 0x25, 0x81, 0x9c, 0x99, 0x36, 0xd8, 0xe5, 0x66, 0xfa, 0x92, 0xf1, 0xd4, 0xd4, 0xe0, 0x86, 0xb8, 0x82, + 0x1c, 0x68, 0x73, 0x48, 0x05, 0x3e, 0x96, 0x42, 0x20, 0x92, 0xf9, 0x2b, 0x41, 0xb5, 0x63, 0x05, 0x72, 0x2c, 0xf1, + 0x09, 0xe9, 0x49, 0x5a, 0x63, 0xcc, 0x2f, 0x9d, 0x66, 0x3f, 0x57, 0x08, 0xee, 0xdf, 0xd9, 0x6c, 0xa3, 0xca, 0x93, + 0xdc, 0xfb, 0x96, 0xda, 0xbf, 0xb7, 0x82, 0x4a, 0xc9, 0xdb, 0x68, 0x54, 0x3c, 0x8d, 0x37, 0x4d, 0xf9, 0x81, 0xa0, + 0x05, 0x60, 0x17, 0x95, 0x9b, 0x2a, 0xe1, 0x98, 0xb0, 0x90, 0x3a, 0xd2, 0x31, 0xd0, 0x65, 0x5d, 0xaf, 0xe4, 0x44, + 0xe8, 0xf6, 0x60, 0x78, 0x99, 0x53, 0x23, 0x9e, 0x4a, 0xed, 0xc8, 0xeb, 0x24, 0x3a, 0xa0, 0xf2, 0xd0, 0x50, 0xf5, + 0x6a, 0xe5, 0x61, 0x78, 0x99, 0xe9, 0x88, 0xbf, 0x4b, 0xf3, 0x93, 0xea, 0x7e, 0xd9, 0x79, 0x56, 0xbb, 0xbd, 0xb5, + 0x46, 0x54, 0xa2, 0xe6, 0x38, 0x44, 0x48, 0xb8, 0x4f, 0x24, 0x37, 0xb3, 0xa1, 0x7d, 0xe0, 0x09, 0xb8, 0x1c, 0x59, + 0x02, 0xaa, 0x08, 0x9a, 0x24, 0xdd, 0x85, 0xea, 0x15, 0x5a, 0x06, 0xca, 0x1b, 0xb5, 0x0f, 0xa2, 0xc3, 0x46, 0xf3, + 0x53, 0x86, 0x19, 0x56, 0x94, 0x45, 0xf3, 0xc1, 0xc5, 0x20, 0x0b, 0xdc, 0xb8, 0x1c, 0x78, 0x31, 0x51, 0xe5, 0x62, + 0xc4, 0x70, 0xff, 0x58, 0xaa, 0x6c, 0x76, 0x37, 0x9c, 0x57, 0xad, 0x33, 0x02, 0x5d, 0xb2, 0x6b, 0xbd, 0xd4, 0x54, + 0x77, 0x90, 0xac, 0x0c, 0xd3, 0x6b, 0x27, 0x93, 0xae, 0xa0, 0x43, 0x7c, 0x76, 0x83, 0x43, 0x69, 0x49, 0x7a, 0x0e, + 0x0d, 0xb6, 0xa4, 0x44, 0x47, 0x40, 0x34, 0xf9, 0x61, 0xb0, 0x2d, 0xb2, 0xa3, 0x4b, 0x33, 0xc5, 0x36, 0x72, 0xa8, + 0x2b, 0x82, 0x5a, 0x25, 0x74, 0xdc, 0x3f, 0x23, 0xb6, 0xf5, 0x45, 0xbf, 0x21, 0x19, 0x6e, 0x50, 0x12, 0x3c, 0x6e, + 0x87, 0xc8, 0xea, 0xe0, 0x38, 0x0f, 0x8f, 0x16, 0x9c, 0x9d, 0x79, 0xfe, 0xaa, 0x2c, 0x7f, 0xab, 0xb5, 0x8c, 0xc0, + 0x47, 0x77, 0x51, 0x36, 0xd9, 0x72, 0xfb, 0x8e, 0x31, 0x9e, 0xbc, 0xa6, 0x17, 0xe8, 0x16, 0x36, 0x8e, 0xfb, 0x15, + 0x8c, 0x8d, 0xe0, 0x4e, 0xa2, 0x4d, 0x2d, 0xf5, 0x49, 0xad, 0xbe, 0x36, 0xea, 0xe6, 0x19, 0xf9, 0xed, 0x20, 0xf9, + 0xdd, 0xb5, 0x3d, 0xd2, 0xdb, 0xaf, 0xac, 0x93, 0x65, 0xa4, 0x9a, 0x60, 0x13, 0xcb, 0x7d, 0x4d, 0x30, 0x79, 0xb0, + 0x98, 0x5d, 0x40, 0x83, 0x2b, 0xf5, 0x08, 0xef, 0x9e, 0x16, 0xb8, 0x55, 0x51, 0xed, 0xf8, 0x24, 0xc4, 0xe4, 0x39, + 0xd1, 0x97, 0x9a, 0x4d, 0xec, 0x07, 0x57, 0xf4, 0x60, 0x66, 0x3d, 0xaa, 0x0a, 0x57, 0x0b, 0x73, 0x0d, 0x60, 0x6b, + 0xd9, 0xd5, 0x11, 0xc1, 0xe2, 0x10, 0x20, 0xe8, 0xd3, 0x2b, 0x5a, 0xfb, 0x5e, 0x18, 0x84, 0x62, 0x3c, 0xf6, 0x4b, + 0x72, 0x56, 0xc5, 0x10, 0x3e, 0x58, 0x65, 0xf4, 0xda, 0x4b, 0x91, 0x8a, 0xe7, 0x88, 0x6e, 0x15, 0x9c, 0x95, 0x53, + 0x42, 0xc8, 0x0c, 0x80, 0x3c, 0xf0, 0x19, 0xe4, 0xf3, 0x58, 0x7c, 0x65, 0xaf, 0xf6, 0xe7, 0xed, 0x2c, 0x95, 0x7d, + 0x87, 0xc2, 0xf0, 0x30, 0x0d, 0xe7, 0xd6, 0x55, 0xee, 0x3b, 0x84, 0x5c, 0xae, 0x58, 0xb1, 0x69, 0xa4, 0x79, 0x92, + 0x6b, 0xd4, 0x1f, 0x6d, 0x7a, 0xaf, 0x51, 0x38, 0xe5, 0xd1, 0xb9, 0x50, 0x45, 0x51, 0x8d, 0x70, 0x2c, 0x55, 0xf5, + 0x14, 0x0d, 0x82, 0xae, 0x57, 0x6f, 0x55, 0xd2, 0x8c, 0xaf, 0xdc, 0x18, 0x9f, 0x9a, 0x95, 0xf2, 0xbc, 0x6c, 0xb2, + 0x5a, 0x05, 0x75, 0xf0, 0x51, 0x9d, 0x6a, 0x2c, 0x37, 0xeb, 0x27, 0x08, 0x66, 0x5c, 0x9c, 0x46, 0x27, 0x2a, 0x96, + 0x51, 0x57, 0x99, 0xc9, 0xf4, 0xc9, 0xd1, 0xd8, 0x28, 0xe1, 0xb4, 0x55, 0x97, 0xb0, 0xc2, 0xcd, 0x09, 0x5b, 0x4e, + 0xe7, 0x1f, 0x46, 0xb0, 0x8e, 0x56, 0x48, 0xc1, 0x40, 0xb2, 0x15, 0xfb, 0x10, 0x0c, 0x7c, 0xbd, 0x02, 0xd2, 0x82, + 0x29, 0x62, 0x5f, 0xba, 0x8c, 0x32, 0x27, 0x62, 0x03, 0x47, 0xcc, 0x5a, 0x04, 0x46, 0xff, 0x43, 0x54, 0xd2, 0x9f, + 0xc5, 0x08, 0xb8, 0x49, 0x57, 0xa1, 0x73, 0xe7, 0xcd, 0xa3, 0x22, 0x5c, 0x3e, 0xf2, 0xe8, 0x16, 0x63, 0x31, 0xfe, + 0xeb, 0x93, 0x98, 0x40, 0xcc, 0x29, 0xc5, 0xd3, 0x05, 0xa6, 0x7f, 0x09, 0x4f, 0xc8, 0x83, 0xd0, 0xa5, 0x9d, 0x93, + 0x18, 0x25, 0x7b, 0xe4, 0x41, 0xfd, 0x89, 0x76, 0x89, 0x9a, 0xfc, 0x00, 0x33, 0x32, 0x25, 0xe5, 0xa3, 0xa5, 0xf6, + 0xd3, 0xcb, 0x01, 0x55, 0xf0, 0xbe, 0x8a, 0xdb, 0x48, 0xf8, 0x3e, 0x8b, 0x87, 0x8b, 0xf1, 0xe6, 0xe1, 0x06, 0x6f, + 0xeb, 0x1e, 0x14, 0xe6, 0x76, 0x95, 0xb1, 0x81, 0x30, 0x5e, 0x23, 0x74, 0xd0, 0xeb, 0xf6, 0x7b, 0xfc, 0x57, 0xff, + 0x51, 0x1c, 0x60, 0x24, 0x4e, 0x68, 0x97, 0x93, 0x35, 0x79, 0x94, 0x4b, 0xfa, 0xc4, 0x49, 0xdf, 0xe8, 0x74, 0xdf, + 0x71, 0xff, 0xa8, 0x03, 0x2b, 0x1e, 0x6e, 0xe5, 0x2b, 0x4d, 0x8a, 0x3b, 0x61, 0x55, 0x7b, 0x2f, 0x23, 0x34, 0xb8, + 0x89, 0xbe, 0xb0, 0xe4, 0x3b, 0x4c, 0xcd, 0xae, 0xb5, 0xb8, 0x94, 0xe1, 0x3d, 0x04, 0xaf, 0x74, 0x69, 0xe2, 0x60, + 0x8c, 0x77, 0x3c, 0x5b, 0x19, 0x1f, 0x2b, 0xeb, 0x63, 0x10, 0x24, 0x58, 0x21, 0x8f, 0x40, 0x04, 0xca, 0xc7, 0x9f, + 0x45, 0x9e, 0x82, 0xf5, 0xc2, 0x24, 0x0a, 0x65, 0xa8, 0x30, 0x60, 0x11, 0x48, 0x01, 0x8f, 0xda, 0x0b, 0x3d, 0x88, + 0x87, 0x98, 0x7b, 0x32, 0x13, 0x30, 0xb7, 0xcf, 0x3f, 0x20, 0x9a, 0x7b, 0xea, 0x4e, 0x2f, 0x39, 0xa8, 0xc1, 0x89, + 0x3d, 0x7c, 0x5c, 0xab, 0x73, 0x38, 0x46, 0x03, 0xab, 0x71, 0x82, 0xa7, 0xf3, 0xbe, 0xa3, 0x59, 0x2a, 0x50, 0x39, + 0x83, 0xc2, 0xf0, 0x9b, 0xbd, 0x4d, 0xaf, 0xe4, 0xbd, 0x65, 0x56, 0xd5, 0x4d, 0x98, 0x07, 0x79, 0x5c, 0x23, 0xae, + 0x0e, 0xef, 0x1f, 0x82, 0xcb, 0xa2, 0xf5, 0x83, 0x2e, 0x88, 0xc3, 0xbb, 0x6d, 0x19, 0x15, 0x6a, 0x0d, 0x3f, 0x7c, + 0x1c, 0xac, 0x23, 0x41, 0x9d, 0x74, 0x16, 0x02, 0x06, 0xf6, 0xd4, 0x51, 0x45, 0xd7, 0x18, 0xa4, 0x4e, 0x47, 0x15, + 0x5d, 0x73, 0xb7, 0xcd, 0xe5, 0x40, 0x01, 0x6b, 0x0a, 0xb1, 0xef, 0xe6, 0xc7, 0xe1, 0xf5, 0xc3, 0x85, 0x88, 0x43, + 0x57, 0x0f, 0x37, 0xca, 0x66, 0x50, 0xf7, 0xda, 0x5c, 0x26, 0x76, 0xbb, 0x2f, 0x6b, 0xfd, 0x72, 0xbc, 0xf4, 0x6d, + 0xa7, 0x39, 0xa7, 0xee, 0x2b, 0x5d, 0xf7, 0xb5, 0x5d, 0x37, 0x8f, 0x5c, 0xaf, 0x6a, 0x35, 0x07, 0x5c, 0x82, 0x2a, + 0xd6, 0xe7, 0x22, 0xaf, 0x56, 0xa5, 0x2a, 0x41, 0x2b, 0x8c, 0xeb, 0xe0, 0xca, 0x6f, 0x25, 0xc3, 0x2a, 0x2e, 0x16, + 0x79, 0xfa, 0x86, 0xe5, 0x5a, 0x5c, 0x1c, 0x7d, 0x96, 0x4c, 0x31, 0x9d, 0x62, 0xce, 0x36, 0x71, 0x44, 0x61, 0x62, + 0xd1, 0x3a, 0x49, 0x95, 0xe1, 0xc0, 0xd4, 0x0a, 0x50, 0x3c, 0x57, 0xe8, 0xd4, 0x2e, 0xf4, 0xaf, 0xc7, 0xc6, 0x99, + 0x2f, 0x4a, 0x64, 0x40, 0xb7, 0x7e, 0x60, 0xeb, 0x3a, 0x29, 0xe2, 0x61, 0xde, 0xf6, 0xfb, 0xeb, 0xda, 0x10, 0xc8, + 0x6e, 0xd9, 0x11, 0x6b, 0x1c, 0x96, 0xda, 0x5d, 0xaa, 0x42, 0xd8, 0x17, 0xc1, 0xcf, 0x88, 0x3b, 0xda, 0x03, 0x21, + 0x8f, 0xce, 0x82, 0x39, 0x8c, 0x58, 0x59, 0x76, 0x28, 0xd7, 0xe0, 0xb2, 0x70, 0x05, 0x5c, 0x64, 0x74, 0x3b, 0xd2, + 0x41, 0x4b, 0xbb, 0xc7, 0x86, 0x73, 0x34, 0x74, 0x2f, 0x74, 0x8f, 0xfd, 0x89, 0x11, 0x2c, 0x16, 0x96, 0x60, 0x31, + 0x19, 0xcc, 0xde, 0xdb, 0x2c, 0x18, 0x1c, 0x00, 0x8f, 0xba, 0x0d, 0xb4, 0x6e, 0x89, 0x31, 0xad, 0xe6, 0xf9, 0xdc, + 0xdb, 0x44, 0xf5, 0x43, 0x35, 0xd2, 0xb0, 0x19, 0x1e, 0xa6, 0x66, 0x2e, 0x36, 0xf0, 0x68, 0x1d, 0x39, 0xf5, 0xc3, + 0x54, 0xb1, 0xd6, 0x25, 0x76, 0x83, 0xc4, 0x14, 0xbc, 0xc4, 0x92, 0x64, 0x4e, 0x05, 0x49, 0x65, 0x34, 0xdf, 0xa8, + 0xc9, 0x0b, 0x4b, 0x27, 0x80, 0x94, 0x4e, 0x7f, 0xf4, 0x38, 0xd0, 0xe5, 0x1e, 0x3d, 0x1e, 0xe0, 0xb5, 0x4d, 0xa4, + 0xcb, 0xcd, 0x64, 0x35, 0xae, 0xfc, 0x87, 0x66, 0x61, 0x3c, 0xb2, 0x16, 0x09, 0x22, 0xfe, 0x1d, 0xfb, 0x03, 0x5c, + 0xb8, 0x29, 0xbf, 0x9c, 0x2c, 0xee, 0x29, 0xbf, 0xa0, 0xe8, 0x49, 0x3a, 0x42, 0xac, 0x59, 0x54, 0x14, 0xbb, 0xaf, + 0xd1, 0x0f, 0x33, 0xbb, 0xcb, 0x1e, 0xde, 0x00, 0x2c, 0xac, 0x65, 0xab, 0x7b, 0x0e, 0x7d, 0x34, 0x55, 0xa0, 0x1d, + 0x94, 0xdf, 0x93, 0x19, 0xa0, 0x37, 0x43, 0x12, 0x02, 0x34, 0xb2, 0x6d, 0xbb, 0xfb, 0x6d, 0xe7, 0xac, 0x63, 0x25, + 0x4e, 0x3b, 0x9b, 0xab, 0x13, 0x0c, 0xd2, 0x1a, 0xc3, 0xa0, 0x9f, 0xd9, 0x0f, 0x7a, 0x5b, 0x65, 0xb8, 0x3c, 0x7a, + 0xc4, 0xf5, 0xb2, 0x76, 0x4b, 0x77, 0xe0, 0x1a, 0x7a, 0x93, 0x30, 0x94, 0xbd, 0x5b, 0x47, 0x17, 0x17, 0xa2, 0x3f, + 0x32, 0x83, 0x05, 0x7c, 0x39, 0x4a, 0x07, 0x0f, 0x4e, 0xf5, 0x46, 0x9f, 0x9b, 0xee, 0x2e, 0x93, 0xbd, 0x8e, 0xbb, + 0x31, 0x6c, 0xcc, 0xc6, 0x4e, 0xdd, 0x8d, 0x6d, 0x74, 0xd0, 0xda, 0x96, 0x85, 0x7e, 0x8a, 0x03, 0xbe, 0x58, 0xb6, + 0xdc, 0x8e, 0xa0, 0xf2, 0x97, 0xe2, 0x85, 0xd8, 0xd1, 0xf6, 0xea, 0xd3, 0x51, 0x5e, 0xb7, 0x7b, 0x14, 0x17, 0x32, + 0xcb, 0xf7, 0x8a, 0x21, 0x4a, 0xac, 0x1b, 0x10, 0x2c, 0x06, 0xcc, 0xb8, 0x9a, 0xae, 0x19, 0xd7, 0xb7, 0x84, 0xb7, + 0xc6, 0x44, 0x8a, 0xb4, 0x32, 0xb6, 0x07, 0x6f, 0x50, 0x4c, 0x26, 0x41, 0x3a, 0x99, 0x08, 0x64, 0xea, 0x7d, 0x72, + 0x43, 0xdb, 0x3d, 0x3f, 0x6d, 0xfd, 0x88, 0xa5, 0xc6, 0x51, 0xd1, 0xf0, 0xf6, 0xcb, 0x3c, 0xb6, 0xc6, 0x95, 0x6d, + 0x19, 0x0c, 0xb9, 0xc2, 0x66, 0x6b, 0xbc, 0x61, 0x10, 0x87, 0x5e, 0xd5, 0xa2, 0xe4, 0xef, 0x7e, 0x26, 0xd2, 0xf0, + 0xd6, 0x96, 0x0e, 0x9a, 0x5b, 0x56, 0x04, 0x85, 0x96, 0x0b, 0xfa, 0x1f, 0x77, 0x45, 0x04, 0x83, 0xdf, 0x33, 0x50, + 0x91, 0x8b, 0xa5, 0xda, 0x20, 0x0a, 0x52, 0xef, 0x1e, 0x53, 0xdd, 0xc0, 0x04, 0x08, 0x6f, 0x18, 0x20, 0xf2, 0x98, + 0xc2, 0xdd, 0x50, 0xee, 0x85, 0x3f, 0xd6, 0x7c, 0x2f, 0x41, 0x7d, 0xcd, 0x61, 0x92, 0x88, 0x82, 0xb0, 0x7d, 0x44, + 0x70, 0x05, 0x47, 0xee, 0x65, 0x70, 0x11, 0x50, 0xb0, 0x71, 0x9a, 0x46, 0x20, 0x1c, 0xb3, 0xc5, 0x69, 0x3a, 0x5b, + 0x8c, 0xa3, 0x84, 0x0c, 0x23, 0xd6, 0x20, 0xfc, 0x2d, 0x64, 0x02, 0x81, 0x1b, 0x51, 0x3a, 0x20, 0x82, 0xc6, 0xc6, + 0x9e, 0xbc, 0x2c, 0x0d, 0x2e, 0x36, 0x90, 0x34, 0x66, 0xc9, 0x22, 0x74, 0x16, 0xad, 0x1b, 0x8c, 0xc2, 0x14, 0x5c, + 0x46, 0xe5, 0xd9, 0xf5, 0x39, 0xfd, 0x73, 0x77, 0x47, 0x00, 0xd8, 0xe1, 0xae, 0x0d, 0xae, 0x12, 0x42, 0x7a, 0xbb, + 0x80, 0x7c, 0xc6, 0xd2, 0x25, 0xb8, 0x89, 0xde, 0x40, 0xeb, 0x0e, 0xbf, 0xf5, 0xd0, 0x73, 0xf6, 0xf0, 0x3d, 0xa2, + 0x21, 0xde, 0x44, 0x97, 0x19, 0xb0, 0x7c, 0x97, 0x1c, 0x82, 0xf0, 0x12, 0x8d, 0x81, 0x6e, 0xd0, 0x03, 0xf6, 0x4d, + 0x74, 0x41, 0xbe, 0x62, 0x07, 0xd0, 0x46, 0xca, 0x88, 0xad, 0xe7, 0xf3, 0x65, 0xad, 0x16, 0xe1, 0xe6, 0x74, 0x39, + 0x1b, 0x8f, 0x37, 0xfe, 0x36, 0x5a, 0x63, 0x3c, 0x18, 0xa8, 0x77, 0xcb, 0xf5, 0xe2, 0x1f, 0x6f, 0xb0, 0xe6, 0x2d, + 0xd4, 0x3c, 0x8e, 0x2e, 0x10, 0x6f, 0x89, 0x0c, 0xb8, 0x6e, 0x80, 0xf3, 0xe0, 0x5f, 0x6f, 0xb4, 0x18, 0x41, 0x81, + 0x7c, 0x11, 0x18, 0x71, 0x65, 0x9e, 0xdf, 0xa0, 0x07, 0xc6, 0x02, 0x6d, 0x5b, 0xb5, 0x45, 0xfc, 0x6d, 0x64, 0x00, + 0xbf, 0x20, 0xf3, 0xa7, 0x28, 0xd6, 0x8f, 0x70, 0x76, 0x7c, 0x88, 0xde, 0x46, 0x4f, 0x3c, 0xe1, 0xa4, 0xab, 0xb3, + 0xb7, 0xe7, 0x28, 0x1e, 0x0a, 0x3f, 0x1d, 0xf3, 0x63, 0x6b, 0x30, 0x80, 0x88, 0xc9, 0xfc, 0xe0, 0x61, 0xd4, 0xa4, + 0x98, 0x7e, 0x61, 0xbc, 0x1d, 0xc5, 0x6c, 0x7e, 0xf0, 0x6e, 0x7d, 0xcd, 0x6f, 0x7e, 0xf0, 0x3e, 0xf9, 0xec, 0x05, + 0x58, 0x87, 0x95, 0x54, 0x58, 0x03, 0xef, 0xa0, 0x2f, 0x61, 0x0c, 0x5c, 0xbd, 0x7b, 0x19, 0xea, 0x5a, 0x8e, 0xd8, + 0x77, 0xa5, 0xdf, 0xc7, 0xdf, 0x63, 0x06, 0x7a, 0x01, 0x19, 0x38, 0x7c, 0x4e, 0xc3, 0x9e, 0xb4, 0xee, 0xb9, 0xdf, + 0xd9, 0x77, 0xbc, 0xa7, 0xd4, 0x47, 0x4e, 0x8f, 0x81, 0x74, 0x3d, 0x49, 0x35, 0x4b, 0x30, 0x47, 0x8d, 0x6b, 0xd8, + 0x65, 0x20, 0xf8, 0xf3, 0x08, 0x59, 0xcb, 0x8a, 0x05, 0xdf, 0xfe, 0x0a, 0x47, 0xf0, 0xca, 0x35, 0xe5, 0x72, 0x95, + 0x91, 0x48, 0x5e, 0xa2, 0x93, 0x49, 0xe3, 0xcf, 0x9c, 0x56, 0x58, 0x5a, 0xcd, 0x71, 0xf3, 0xd0, 0xe6, 0xe3, 0x54, + 0xd3, 0xfe, 0x2e, 0x41, 0xaa, 0x5d, 0xa5, 0xe5, 0xfc, 0xc6, 0x96, 0x74, 0x61, 0x33, 0x1e, 0xfb, 0x21, 0x37, 0x47, + 0x9a, 0x61, 0x8f, 0x85, 0xfa, 0xa2, 0xa7, 0x78, 0x42, 0xf3, 0x51, 0x15, 0xe6, 0xd9, 0xfd, 0xe6, 0x40, 0xfb, 0xe7, + 0x27, 0x93, 0x34, 0xa4, 0x60, 0x95, 0x56, 0x94, 0x22, 0x87, 0xb0, 0xf7, 0xa7, 0x49, 0x52, 0xb1, 0x80, 0x18, 0xba, + 0xfb, 0xad, 0xfb, 0x2c, 0x04, 0xe4, 0x9a, 0x6e, 0xb5, 0xf1, 0xb2, 0x32, 0xed, 0x5c, 0x58, 0x9f, 0x1e, 0x1f, 0x1d, + 0xa5, 0xa7, 0xc7, 0xf3, 0x34, 0x6c, 0x30, 0x70, 0x51, 0xfa, 0xe4, 0x78, 0xde, 0x70, 0x94, 0xd4, 0x21, 0xc7, 0x18, + 0x3d, 0xaf, 0x2a, 0xb4, 0x4f, 0xf3, 0x04, 0x5d, 0x91, 0x1a, 0x1d, 0x39, 0xd6, 0x98, 0x31, 0x12, 0xee, 0xb0, 0x32, + 0xed, 0xd4, 0x56, 0x07, 0x78, 0xf3, 0x6a, 0x4c, 0x10, 0x96, 0xab, 0xbe, 0x71, 0x41, 0xd0, 0xd0, 0x45, 0xaa, 0xdd, + 0x71, 0xab, 0xb0, 0xf3, 0x1c, 0x6d, 0x56, 0xb6, 0x36, 0xc2, 0xad, 0x05, 0x95, 0x51, 0x70, 0x70, 0x18, 0x23, 0xec, + 0x41, 0xf5, 0x8e, 0xa8, 0x76, 0xd2, 0x3d, 0x02, 0x58, 0x61, 0xe2, 0xd4, 0x6a, 0x49, 0x0c, 0x4f, 0x84, 0xba, 0x93, + 0x8e, 0x32, 0x59, 0x4a, 0xb8, 0x5f, 0xc3, 0xe2, 0x5e, 0x85, 0x76, 0x12, 0x22, 0x7e, 0x8b, 0x0c, 0x80, 0x3b, 0x3e, + 0x8e, 0xca, 0x79, 0xe9, 0x68, 0x5d, 0xc6, 0x55, 0x48, 0x08, 0xcd, 0x08, 0xe8, 0xe4, 0xea, 0x20, 0x2a, 0x03, 0x57, + 0x3c, 0xc0, 0xc2, 0xcf, 0x93, 0x87, 0xc5, 0x93, 0xf8, 0x61, 0x3d, 0x8f, 0x1f, 0x15, 0x61, 0xf2, 0xa8, 0x0e, 0x93, + 0x87, 0xf5, 0x69, 0xfc, 0xb0, 0x98, 0x27, 0xf0, 0x1c, 0x3f, 0xaa, 0x5b, 0x5b, 0xd6, 0x1e, 0x1e, 0x09, 0x91, 0x76, + 0xf5, 0x47, 0x0e, 0xd5, 0x7d, 0xca, 0xfc, 0xf0, 0xb0, 0xd1, 0x3b, 0x75, 0xf8, 0xba, 0x5e, 0xa3, 0x66, 0xea, 0xa3, + 0xec, 0x6f, 0xf6, 0x65, 0x61, 0x6f, 0x0d, 0x91, 0xcd, 0x48, 0x27, 0x72, 0x8f, 0x23, 0xde, 0xec, 0x58, 0x61, 0x60, + 0xd8, 0xa4, 0x2a, 0x60, 0xa3, 0x17, 0x14, 0x84, 0x6a, 0xc2, 0xb0, 0x16, 0x53, 0xfb, 0x7c, 0x48, 0x6f, 0xa0, 0xc4, + 0x14, 0x5b, 0x0c, 0x19, 0x7b, 0xc9, 0xfc, 0x24, 0x3c, 0x46, 0xe4, 0x08, 0x92, 0xf1, 0xe1, 0xac, 0x80, 0x03, 0x73, + 0x8d, 0xd0, 0x37, 0xf7, 0xda, 0xba, 0x5c, 0xc1, 0x91, 0x22, 0x63, 0x33, 0x5f, 0x4f, 0x95, 0xf2, 0x03, 0xce, 0x38, + 0x60, 0xd5, 0x2f, 0x23, 0x8d, 0x8a, 0xf2, 0x4c, 0x6f, 0xd6, 0x1b, 0x8c, 0x55, 0xf7, 0x0c, 0xa5, 0x59, 0x3a, 0x76, + 0xe5, 0x80, 0x0d, 0x6e, 0x83, 0x4f, 0xc1, 0x87, 0xe0, 0x6d, 0xf0, 0x20, 0x78, 0x11, 0x7c, 0x0e, 0x1a, 0x82, 0x28, + 0xaf, 0x14, 0x97, 0xe7, 0x5f, 0x44, 0x97, 0x72, 0xf7, 0x28, 0xb0, 0x64, 0x9f, 0xec, 0x7b, 0x9a, 0xc9, 0x26, 0x78, + 0x1b, 0x5d, 0x4c, 0xae, 0x83, 0x17, 0x98, 0x8d, 0x98, 0xe2, 0x31, 0x70, 0xa8, 0x28, 0xc2, 0x72, 0x06, 0x7d, 0x1a, + 0x56, 0x16, 0xb3, 0x68, 0x2a, 0x25, 0x2e, 0xfa, 0x45, 0xd4, 0x30, 0x76, 0x5a, 0x43, 0x95, 0xc8, 0x87, 0xa0, 0xd2, + 0x4f, 0xd1, 0x05, 0xd4, 0xf6, 0x56, 0xdf, 0x11, 0x8d, 0x37, 0x6e, 0x75, 0x0c, 0x66, 0xa5, 0x2b, 0x13, 0x86, 0xfa, + 0xd6, 0x96, 0x04, 0x37, 0x70, 0xa4, 0x61, 0xfb, 0x1e, 0x50, 0xd5, 0xc4, 0xf3, 0x43, 0x95, 0x9f, 0x23, 0xc1, 0x50, + 0x73, 0xeb, 0x13, 0xc3, 0x50, 0x5d, 0x21, 0x8b, 0x08, 0x0f, 0x22, 0x2e, 0x08, 0xa5, 0xb0, 0x0e, 0xfe, 0x0a, 0x54, + 0x79, 0x4b, 0xad, 0xfb, 0x60, 0x2e, 0xb7, 0xac, 0xe6, 0x09, 0x28, 0x30, 0xf1, 0x2a, 0x95, 0x65, 0xe2, 0x48, 0x75, + 0xf3, 0x40, 0x8d, 0x75, 0x4f, 0x1f, 0x3d, 0x1e, 0x4f, 0xff, 0xe2, 0xeb, 0xb2, 0xef, 0xbc, 0x4a, 0x4b, 0xbe, 0xcf, + 0x1c, 0xeb, 0xca, 0x8a, 0xac, 0x2b, 0x7f, 0x8a, 0xaa, 0xb3, 0x67, 0xe7, 0x33, 0xdd, 0x4a, 0x26, 0x32, 0x6c, 0x6e, + 0x0a, 0x2f, 0xfa, 0xe4, 0xf8, 0x27, 0xa0, 0x37, 0xd6, 0x18, 0xab, 0xef, 0xea, 0xe1, 0xfd, 0x34, 0xde, 0xb4, 0x4e, + 0xc3, 0xb7, 0xfb, 0x1b, 0x4e, 0xfd, 0x6c, 0xb4, 0x75, 0xe6, 0xff, 0x72, 0xab, 0x6f, 0x69, 0xee, 0x3e, 0x44, 0xb7, + 0x30, 0x8f, 0xd6, 0x34, 0xc8, 0xf7, 0x95, 0x5d, 0x7e, 0x13, 0x3d, 0xf5, 0x06, 0x39, 0x59, 0x44, 0x35, 0xfa, 0x68, + 0xb8, 0xa1, 0x93, 0xc0, 0xf8, 0x34, 0x2c, 0x1e, 0xe5, 0x88, 0xce, 0x85, 0xec, 0xd9, 0x0d, 0x30, 0x97, 0x37, 0xa7, + 0x8b, 0xd9, 0xcd, 0x38, 0xfa, 0x68, 0x7a, 0xd0, 0xdd, 0x70, 0xc0, 0x72, 0xfd, 0x04, 0x9b, 0xdb, 0xda, 0x92, 0xcf, + 0xfc, 0xe0, 0xfb, 0xd4, 0xdd, 0xa5, 0x90, 0xf4, 0x39, 0x8d, 0x7e, 0x9a, 0xea, 0x60, 0x19, 0xc1, 0xe7, 0x06, 0x1e, + 0x29, 0xea, 0x47, 0xf0, 0x6b, 0x1a, 0x7d, 0x8f, 0xf6, 0xdf, 0xf5, 0x8a, 0x6e, 0xc6, 0x7f, 0x6d, 0xd4, 0xe3, 0x5b, + 0xf1, 0xcd, 0xc1, 0x92, 0xd8, 0x0b, 0x2e, 0x79, 0xd9, 0xc8, 0x23, 0xc7, 0xe2, 0xc8, 0xd4, 0x5b, 0x13, 0x83, 0x96, + 0xaa, 0xb9, 0x68, 0x7a, 0xe9, 0x5c, 0xdf, 0xec, 0x4d, 0xb4, 0x92, 0x1b, 0xe6, 0x1b, 0x74, 0x07, 0x7e, 0x63, 0xc3, + 0x14, 0x6c, 0x23, 0x22, 0x06, 0x6f, 0x02, 0x84, 0x90, 0xcc, 0xe7, 0xb7, 0xd1, 0x87, 0xe8, 0x41, 0xf4, 0x39, 0xda, + 0x86, 0x9f, 0x80, 0x01, 0x84, 0xb5, 0xd2, 0x44, 0xdb, 0x60, 0x29, 0x90, 0xa7, 0xcd, 0xed, 0x49, 0x78, 0x1b, 0x34, + 0xdb, 0x93, 0xf0, 0x53, 0xd0, 0xdc, 0x3e, 0x0e, 0x3f, 0xc0, 0xef, 0xc7, 0xe1, 0xdb, 0x00, 0x92, 0x1f, 0x04, 0x90, + 0xfa, 0x22, 0x80, 0xc4, 0xcf, 0x01, 0xa4, 0x35, 0x0a, 0xe9, 0xe1, 0x73, 0x2a, 0x91, 0x4e, 0x3e, 0x37, 0x81, 0x89, + 0xaa, 0x1b, 0xfe, 0x9a, 0x5a, 0x4f, 0xdc, 0xca, 0xf0, 0xd7, 0x26, 0xd0, 0x7d, 0x0e, 0xd3, 0x34, 0xd0, 0x3d, 0x0e, + 0x2f, 0xf9, 0x8d, 0xe9, 0x57, 0x98, 0xa5, 0xc1, 0x50, 0x4f, 0xc3, 0x8b, 0xa6, 0xed, 0x9c, 0xcc, 0x8e, 0x75, 0xe2, + 0x62, 0xc0, 0x3a, 0xf1, 0x22, 0x58, 0xb6, 0x83, 0xa6, 0x3a, 0xfb, 0xcf, 0x03, 0x7d, 0x04, 0x68, 0xda, 0x5f, 0x8b, + 0x05, 0x0d, 0x88, 0x3b, 0x85, 0xd2, 0x1d, 0x77, 0xe8, 0x7d, 0x6c, 0xd1, 0xfb, 0x40, 0x14, 0x69, 0x49, 0x90, 0x54, + 0x65, 0x5d, 0x53, 0x3c, 0xf1, 0x30, 0xd7, 0x5a, 0xb5, 0x55, 0xc0, 0xea, 0x4c, 0xc4, 0xa4, 0x2f, 0xf9, 0x30, 0x28, + 0xf8, 0x5e, 0x01, 0x4e, 0x84, 0xcd, 0x78, 0x05, 0x47, 0xc2, 0x62, 0x3e, 0x59, 0x85, 0x4b, 0xa0, 0xfc, 0x93, 0x61, + 0xb6, 0xe0, 0x5a, 0xb9, 0x31, 0x69, 0xf1, 0xa9, 0x47, 0xdd, 0xa3, 0xd1, 0x75, 0xb6, 0x58, 0xe4, 0x29, 0xe9, 0xdc, + 0x6a, 0x4d, 0xe6, 0x6f, 0x1d, 0xaa, 0xbe, 0x56, 0x54, 0x1e, 0x19, 0x86, 0x9f, 0x30, 0xd0, 0x0e, 0xc7, 0xbd, 0xc3, + 0x16, 0x53, 0xb8, 0x25, 0xb3, 0xef, 0x6b, 0x9b, 0xae, 0xdf, 0x1a, 0x52, 0xfd, 0x47, 0xab, 0x60, 0x5a, 0x2e, 0xa3, + 0xff, 0xd1, 0x14, 0xfd, 0x79, 0xa0, 0xe8, 0xc6, 0x9f, 0x7d, 0x8a, 0x3e, 0x92, 0x77, 0x02, 0x25, 0x06, 0x5b, 0x78, + 0xba, 0x6d, 0x9d, 0xfa, 0x84, 0x96, 0xff, 0x8f, 0x54, 0x68, 0x53, 0xf3, 0xda, 0x26, 0x8a, 0xb7, 0xd1, 0x00, 0x2d, + 0x5f, 0x5a, 0x34, 0xd1, 0x20, 0x94, 0x7c, 0x8c, 0x5c, 0xa7, 0x68, 0xa4, 0x89, 0xcf, 0xa2, 0xfa, 0xec, 0xe3, 0xf9, + 0xec, 0x36, 0xea, 0x53, 0xc4, 0x8f, 0x03, 0x14, 0xf1, 0x99, 0x3f, 0x5e, 0xb6, 0x5f, 0x1a, 0xd5, 0x41, 0x4a, 0xee, + 0x34, 0x7a, 0x1b, 0xf5, 0xe9, 0xf8, 0xe4, 0x0f, 0x37, 0x7a, 0xfb, 0xd5, 0x8d, 0xb6, 0x9b, 0x3c, 0x3c, 0xf8, 0x66, + 0xe0, 0x5b, 0x69, 0x35, 0xb9, 0x9b, 0x19, 0x05, 0x23, 0x2e, 0x5b, 0x5c, 0xa6, 0x61, 0x62, 0x29, 0x16, 0x31, 0x51, + 0xa3, 0x74, 0xce, 0xf4, 0x59, 0x30, 0xc8, 0xe8, 0x12, 0xa1, 0xbf, 0x04, 0x39, 0xfc, 0xc2, 0x98, 0x6c, 0x5e, 0x9e, + 0x5e, 0x80, 0x1c, 0x7e, 0xe9, 0xef, 0x6e, 0xa2, 0xf8, 0xec, 0xf2, 0x1c, 0x64, 0xf7, 0x1b, 0xde, 0x4f, 0x33, 0xd5, + 0xf9, 0xf2, 0x3e, 0x0e, 0xec, 0x12, 0x3e, 0x6a, 0x05, 0x82, 0xb5, 0x25, 0xce, 0x4b, 0x7f, 0x2c, 0xd7, 0xd2, 0x42, + 0xda, 0xdf, 0xde, 0xaf, 0xa1, 0xb8, 0x44, 0x26, 0xe3, 0xad, 0xad, 0x72, 0x78, 0x11, 0xbd, 0x07, 0xd1, 0x7e, 0xfe, + 0x46, 0x3b, 0xdf, 0xcc, 0xd4, 0xb9, 0xf4, 0x02, 0xb8, 0xbb, 0x9f, 0x60, 0x75, 0xf2, 0x99, 0x02, 0x07, 0x10, 0x2f, + 0xdb, 0x0f, 0x14, 0xc4, 0x89, 0x8f, 0x8a, 0xcf, 0x6e, 0x22, 0x83, 0x42, 0x20, 0x55, 0x80, 0xc3, 0xe8, 0xd3, 0xac, + 0x9a, 0x03, 0xf5, 0xff, 0x00, 0xbb, 0xd3, 0x56, 0x44, 0x5f, 0xc2, 0xd3, 0x05, 0x08, 0xbf, 0x9f, 0x87, 0x6d, 0x7b, + 0x94, 0x5b, 0x9b, 0xf2, 0x78, 0xbb, 0x24, 0x27, 0xac, 0xbd, 0x99, 0x25, 0x97, 0x14, 0x82, 0x6c, 0x7a, 0xed, 0x05, + 0x9a, 0xe2, 0xcc, 0x73, 0x8a, 0xe8, 0x8b, 0x91, 0x99, 0xee, 0xee, 0xae, 0x0e, 0xa9, 0xbe, 0x68, 0xf2, 0xe2, 0xe1, + 0x83, 0xf1, 0x03, 0x72, 0xe1, 0xb2, 0xdc, 0x42, 0x20, 0x3d, 0x6f, 0x3a, 0x62, 0x07, 0x2c, 0xd9, 0x67, 0x98, 0x37, + 0x1c, 0x7a, 0x69, 0xaa, 0xe8, 0xd4, 0x3f, 0x50, 0xf5, 0x9e, 0x9a, 0xc3, 0x81, 0x37, 0xd8, 0x3a, 0x8a, 0x85, 0xfb, + 0xf9, 0x61, 0x84, 0x8a, 0x0e, 0xaa, 0xf5, 0xe8, 0xe8, 0xf0, 0x23, 0x07, 0x94, 0xc6, 0xf9, 0x7e, 0x16, 0x27, 0xbf, + 0x2d, 0xaa, 0x72, 0x8d, 0x47, 0xec, 0x18, 0x3f, 0xf7, 0x50, 0x8b, 0x61, 0x57, 0xbe, 0xef, 0x87, 0x1e, 0x9c, 0xb4, + 0xc0, 0xc0, 0x78, 0x27, 0x93, 0x17, 0xfe, 0xc3, 0x07, 0xc8, 0x3f, 0x11, 0x4e, 0x0a, 0xb9, 0xc4, 0x0e, 0x54, 0xa3, + 0xf6, 0x21, 0xf0, 0x0a, 0x49, 0x03, 0x19, 0x2e, 0x25, 0xfd, 0x9d, 0x42, 0xf6, 0x03, 0x9e, 0x21, 0x57, 0xcd, 0xab, + 0x71, 0x11, 0x03, 0xd7, 0x90, 0x8b, 0xc8, 0x86, 0xcf, 0x54, 0x3d, 0xb0, 0x0e, 0x9f, 0x27, 0xbf, 0x32, 0xff, 0x07, + 0xec, 0xc2, 0x31, 0xfe, 0xc6, 0xa9, 0x99, 0xd5, 0x9f, 0x32, 0x10, 0x9a, 0x60, 0x23, 0xc1, 0x77, 0x40, 0x34, 0x57, + 0x27, 0x03, 0x9c, 0xb3, 0x93, 0x28, 0x4d, 0x1f, 0x3d, 0x9e, 0x5d, 0x56, 0x69, 0xfc, 0xdb, 0x8c, 0xde, 0xc9, 0x4e, + 0x93, 0x77, 0xfc, 0xa6, 0x95, 0x0a, 0x3e, 0x49, 0x79, 0x19, 0x55, 0x38, 0x8f, 0x27, 0xd1, 0x65, 0xe3, 0x96, 0x97, + 0x35, 0xc1, 0xaf, 0xec, 0x17, 0xbc, 0x06, 0x43, 0xb5, 0x02, 0x39, 0x43, 0x78, 0x89, 0x42, 0xbf, 0xa7, 0x2a, 0xf2, + 0xe5, 0x7b, 0xc0, 0x42, 0xb1, 0xed, 0xe8, 0x05, 0x63, 0xca, 0x01, 0x43, 0xc0, 0x0c, 0xc7, 0x65, 0x33, 0xfe, 0x55, + 0xa3, 0x93, 0x08, 0xf8, 0x54, 0x8a, 0x49, 0x72, 0xf3, 0xc0, 0xdc, 0x88, 0x19, 0x40, 0xda, 0x28, 0x6d, 0x7b, 0x2d, + 0x2c, 0x0e, 0x47, 0x57, 0x7d, 0x43, 0x01, 0xcf, 0x80, 0xb3, 0xc1, 0xbd, 0x23, 0xac, 0xc5, 0x67, 0x73, 0x75, 0xac, + 0xdd, 0x86, 0xae, 0xa4, 0xba, 0x9f, 0x24, 0x74, 0x1a, 0xb3, 0x2b, 0xdf, 0xa7, 0xf2, 0xf8, 0x2f, 0xc5, 0x02, 0x69, + 0xaa, 0x86, 0x6c, 0x10, 0x3e, 0xa0, 0xfe, 0x03, 0x37, 0x39, 0x32, 0x4a, 0x4d, 0x15, 0x17, 0x75, 0xce, 0x15, 0x9e, + 0xc1, 0x31, 0x0d, 0x53, 0x27, 0x6d, 0x03, 0x36, 0xc9, 0x44, 0x48, 0x3b, 0xb8, 0x6e, 0xf7, 0x92, 0x7a, 0x03, 0xf5, + 0xcc, 0xec, 0x48, 0x23, 0xec, 0x48, 0x57, 0x73, 0x0f, 0x6b, 0x6b, 0x98, 0x5f, 0xd0, 0x70, 0x01, 0x7a, 0x53, 0xba, + 0xfb, 0x7c, 0xc6, 0xae, 0xcb, 0x6a, 0x5e, 0x4d, 0x88, 0x3a, 0xe2, 0x63, 0x2c, 0xfa, 0x5c, 0xcb, 0xf9, 0x1d, 0x5a, + 0xaf, 0xe8, 0xe2, 0xab, 0x56, 0x07, 0xb1, 0xfd, 0x46, 0x53, 0x9d, 0x5a, 0xfd, 0x46, 0x80, 0x81, 0x7d, 0xc7, 0x43, + 0xd3, 0xeb, 0x67, 0x2a, 0xfd, 0xdc, 0x59, 0x6c, 0x54, 0xa1, 0x98, 0xa7, 0x5a, 0xf3, 0x53, 0xea, 0x56, 0x5f, 0x33, + 0x6e, 0xd5, 0xf0, 0xd9, 0x80, 0x3c, 0xda, 0xb8, 0xa4, 0x38, 0xa3, 0xb6, 0xc6, 0x83, 0x55, 0xd3, 0xc1, 0xca, 0xb9, + 0xf8, 0x60, 0x4f, 0xb3, 0x1a, 0x6f, 0xbc, 0x8c, 0x90, 0x09, 0x85, 0x0b, 0x4d, 0x6c, 0x80, 0xae, 0xc9, 0x58, 0x14, + 0x36, 0xa1, 0xf1, 0x72, 0xfd, 0x3b, 0x58, 0x8d, 0xa3, 0x04, 0xd6, 0x74, 0x88, 0x69, 0x12, 0xe2, 0x01, 0x93, 0x90, + 0x3c, 0xd8, 0xd5, 0x4e, 0xe2, 0x4e, 0xb5, 0x32, 0x92, 0xfb, 0xeb, 0x9d, 0x98, 0x7a, 0x19, 0x2e, 0x79, 0x65, 0x84, + 0x53, 0xa8, 0x3d, 0xb5, 0xfc, 0x94, 0x4d, 0x17, 0x88, 0x13, 0xe8, 0xf6, 0xe0, 0xbf, 0xf0, 0xa9, 0x89, 0xd3, 0x03, + 0xaa, 0xb5, 0xdb, 0x81, 0xff, 0xc2, 0xb8, 0xd8, 0xe6, 0xa2, 0x7e, 0x68, 0x5e, 0xec, 0xcc, 0xe6, 0xca, 0x03, 0x8c, + 0x49, 0xe2, 0x72, 0xf3, 0x14, 0xeb, 0x87, 0x58, 0x9f, 0xa1, 0xdb, 0x0e, 0x5a, 0x29, 0x2e, 0xac, 0x52, 0x37, 0x48, + 0xaf, 0x5d, 0x4a, 0x2d, 0xbc, 0x99, 0xe2, 0xaa, 0xa8, 0x1f, 0x72, 0xff, 0x25, 0x7c, 0xa6, 0xf2, 0x3b, 0x24, 0x4b, + 0x76, 0xe3, 0x57, 0x41, 0x02, 0xab, 0xf2, 0x8d, 0x50, 0xc4, 0xc8, 0x32, 0x02, 0x67, 0x39, 0x56, 0x57, 0xdc, 0xbf, + 0x57, 0xb3, 0x2b, 0x56, 0xbc, 0x75, 0x20, 0xe6, 0xf3, 0xb6, 0xcf, 0x85, 0x88, 0xf4, 0x52, 0xb5, 0x02, 0xda, 0x43, + 0xc7, 0xe1, 0x67, 0x3a, 0xdc, 0xa3, 0x67, 0x5f, 0xdb, 0x34, 0x86, 0xb0, 0x75, 0x02, 0x42, 0x02, 0xfd, 0xe0, 0x2f, + 0x14, 0x01, 0x7b, 0xbe, 0xca, 0xcd, 0xb5, 0x22, 0xac, 0xe2, 0x2f, 0x30, 0x4b, 0xf9, 0xe2, 0x2c, 0xbe, 0x21, 0x4b, + 0xeb, 0xa9, 0x0e, 0x31, 0x89, 0x46, 0xba, 0xf4, 0x84, 0xd8, 0x50, 0x9e, 0xc4, 0xe6, 0xc0, 0x1c, 0x18, 0xca, 0xa5, + 0xac, 0x94, 0x32, 0xf8, 0x3b, 0x25, 0x23, 0xdb, 0xaa, 0xff, 0xc1, 0x0b, 0x32, 0x94, 0x81, 0xbe, 0x6c, 0x85, 0xd6, + 0xf5, 0x76, 0xae, 0x6d, 0x4d, 0xdb, 0x32, 0x2b, 0x16, 0x14, 0xc5, 0x06, 0x91, 0x4f, 0xc4, 0x38, 0x90, 0xe2, 0x9a, + 0x68, 0xbc, 0xb3, 0x27, 0xc0, 0x20, 0xa4, 0xf7, 0xf1, 0x7b, 0xc0, 0x15, 0x1b, 0xb9, 0x3e, 0x3c, 0xa6, 0xb1, 0x45, + 0x65, 0xe2, 0xbd, 0xcd, 0xd6, 0xba, 0xc4, 0xe6, 0x56, 0x69, 0x12, 0x5d, 0x77, 0x05, 0xed, 0x47, 0xe2, 0x3a, 0x31, + 0x38, 0xd7, 0x78, 0x5d, 0x95, 0xa5, 0xaf, 0xb0, 0xcc, 0xb5, 0xcf, 0x92, 0x09, 0x26, 0x75, 0xb8, 0x52, 0xf0, 0xe4, + 0x87, 0x2b, 0xe6, 0x11, 0x49, 0xb3, 0x2d, 0xb3, 0x54, 0x98, 0x1e, 0x44, 0x92, 0x31, 0x40, 0xf8, 0x26, 0x1d, 0x00, + 0x34, 0x92, 0x42, 0x98, 0xca, 0x53, 0x84, 0x62, 0xb6, 0xb7, 0x9a, 0x5e, 0x3a, 0x5a, 0x07, 0x55, 0x93, 0x91, 0xc1, + 0x23, 0x3b, 0x8b, 0x70, 0xbd, 0xc5, 0x94, 0xdc, 0x4e, 0xde, 0x01, 0x07, 0x44, 0xdf, 0x46, 0xd4, 0xa4, 0x8f, 0xa5, + 0x97, 0x4c, 0x11, 0x4d, 0x7d, 0xab, 0xea, 0x80, 0x94, 0x1c, 0x52, 0x72, 0x4e, 0xe1, 0xb6, 0x50, 0x76, 0x6b, 0xb9, + 0x70, 0xe4, 0x55, 0x35, 0xd1, 0x06, 0xb9, 0x5d, 0xfb, 0x0c, 0x68, 0xd4, 0x76, 0x65, 0x91, 0x85, 0xb8, 0x35, 0xb3, + 0x94, 0x3c, 0xe7, 0xdf, 0x16, 0xcf, 0x95, 0x3b, 0xcf, 0xd1, 0x51, 0xec, 0xed, 0x6e, 0x43, 0x68, 0xc1, 0x49, 0xb0, + 0x85, 0x3f, 0xdb, 0x93, 0x36, 0xe0, 0xe7, 0xc7, 0xfc, 0xfc, 0xb8, 0x45, 0x55, 0x49, 0x6a, 0x3c, 0xee, 0x75, 0x89, + 0x46, 0x8a, 0x34, 0xba, 0x4c, 0x23, 0x85, 0x1a, 0x2c, 0xb5, 0x63, 0x96, 0x20, 0xb1, 0x34, 0x36, 0x9f, 0x24, 0x5a, + 0xaa, 0xa5, 0xd2, 0x31, 0xaa, 0x8c, 0xa4, 0xa3, 0xb3, 0xe9, 0x2b, 0x46, 0xba, 0x39, 0x38, 0x19, 0x91, 0x69, 0x69, + 0x57, 0x53, 0xba, 0xd9, 0xd1, 0x82, 0xc9, 0x88, 0x3b, 0xdb, 0xb2, 0x76, 0x13, 0xd5, 0x74, 0xc1, 0x66, 0x6e, 0xb5, + 0x32, 0x73, 0x2b, 0xa3, 0xe2, 0x0b, 0xba, 0xe5, 0x2a, 0xd2, 0x56, 0x66, 0xf3, 0x52, 0xe9, 0x96, 0x69, 0x0f, 0x76, + 0xe8, 0x26, 0x06, 0xcb, 0xbc, 0xa7, 0xaa, 0x63, 0x7b, 0xd3, 0x28, 0xdb, 0x20, 0x5b, 0x11, 0xa3, 0x4e, 0x59, 0xbc, + 0xfc, 0x1d, 0xf6, 0x1e, 0xc8, 0x51, 0x55, 0xd5, 0x18, 0x03, 0x77, 0xa0, 0x25, 0x93, 0x0a, 0x84, 0xa0, 0x95, 0x95, + 0xce, 0x26, 0x54, 0xb1, 0x3f, 0x8e, 0xc9, 0x4c, 0x65, 0x13, 0xa1, 0x41, 0xdd, 0xc2, 0xca, 0x40, 0xd0, 0xc3, 0x5c, + 0x6e, 0x63, 0x25, 0x0b, 0xd5, 0x94, 0x82, 0x79, 0xb4, 0x8a, 0x68, 0xf6, 0x65, 0xb7, 0xa4, 0xd6, 0x6e, 0x91, 0x41, + 0xc0, 0x97, 0xd6, 0x6e, 0x29, 0x65, 0xb7, 0xa4, 0xce, 0x4a, 0x4f, 0xd5, 0x4a, 0xcf, 0x51, 0x01, 0x99, 0xaa, 0x55, + 0xbe, 0x42, 0x58, 0xf8, 0xd4, 0xac, 0xf0, 0xd4, 0xac, 0x70, 0x9a, 0x52, 0x63, 0xff, 0xa0, 0x69, 0x9d, 0x7b, 0x6e, + 0xb9, 0x84, 0x4e, 0x23, 0xbe, 0xf5, 0x08, 0x4c, 0xff, 0x20, 0x9c, 0xc1, 0x3a, 0xce, 0x2a, 0xc2, 0xea, 0x31, 0x30, + 0x82, 0x32, 0x55, 0x8e, 0xf6, 0xcb, 0xc2, 0x92, 0xac, 0x18, 0x4b, 0x72, 0xa7, 0xe6, 0xb9, 0xb2, 0x4c, 0xbc, 0x2a, + 0x06, 0x91, 0xc8, 0xe1, 0x07, 0x5f, 0xc1, 0xaf, 0xe0, 0x97, 0xe1, 0x9a, 0x67, 0x17, 0x19, 0x7c, 0x2b, 0x0f, 0x8e, + 0x09, 0xc3, 0x28, 0xf6, 0x5b, 0xf8, 0x7c, 0x01, 0x9f, 0xe7, 0x7e, 0x7e, 0x44, 0xd3, 0xcb, 0x7d, 0x67, 0x91, 0xc3, + 0xe4, 0x35, 0x14, 0x54, 0x58, 0xc4, 0x4a, 0xbd, 0x7c, 0x19, 0x3e, 0x68, 0x78, 0x34, 0x4a, 0x84, 0xb8, 0x28, 0xc4, + 0xbe, 0xb6, 0x42, 0xa1, 0xa9, 0x30, 0x30, 0xe8, 0x31, 0x2c, 0x6a, 0x62, 0x42, 0x05, 0x84, 0xa4, 0xb4, 0x24, 0x6e, + 0x10, 0x56, 0x5c, 0x73, 0x16, 0x1b, 0x98, 0xe0, 0xee, 0x0e, 0x11, 0x0f, 0xe6, 0x5e, 0x32, 0x86, 0x6e, 0xca, 0x9a, + 0x79, 0x0f, 0x35, 0x13, 0x4c, 0x06, 0xaa, 0x2a, 0xc6, 0x4e, 0x5d, 0x0f, 0xe5, 0x95, 0x31, 0x33, 0x03, 0xd6, 0x85, + 0xca, 0xc2, 0x32, 0x9c, 0x29, 0xcb, 0x3a, 0x02, 0x28, 0xc8, 0x15, 0x40, 0xc1, 0xca, 0x00, 0x14, 0x2c, 0x0c, 0x40, + 0xc1, 0xa6, 0x8d, 0xae, 0x44, 0x87, 0x9b, 0x60, 0xb8, 0x08, 0x1f, 0x47, 0x16, 0x09, 0x2b, 0x56, 0x0f, 0xc3, 0x7b, + 0x0c, 0x47, 0xab, 0x50, 0x9e, 0x42, 0x96, 0xe2, 0x60, 0x35, 0x96, 0x28, 0xb2, 0x5e, 0x79, 0x31, 0xa3, 0xc8, 0x39, + 0x12, 0x89, 0x92, 0xfd, 0x5c, 0xb9, 0x04, 0x26, 0xf6, 0xbc, 0xe5, 0x59, 0xc3, 0x75, 0x39, 0x74, 0x00, 0xf3, 0x96, + 0xef, 0x32, 0x1a, 0x81, 0x4e, 0x95, 0x23, 0xd2, 0x24, 0x28, 0xca, 0x65, 0x52, 0x64, 0x41, 0x98, 0x04, 0xbd, 0x13, + 0xfc, 0x56, 0x50, 0xfe, 0xbf, 0x68, 0x04, 0x8f, 0x70, 0x4c, 0xbc, 0x4b, 0x3e, 0xe3, 0x05, 0x66, 0x11, 0x3d, 0x15, + 0xa3, 0x6c, 0x82, 0x62, 0x84, 0xbf, 0xd3, 0xcf, 0xc1, 0x84, 0xa0, 0xad, 0x9e, 0xd2, 0x15, 0x13, 0xb6, 0x01, 0x5f, + 0xf1, 0x2f, 0x78, 0xa9, 0x43, 0xa8, 0x0c, 0x75, 0x52, 0xb7, 0x0c, 0x24, 0xfe, 0xef, 0x1b, 0x13, 0x13, 0x99, 0xd2, + 0xe6, 0x18, 0x6f, 0x20, 0xa5, 0x78, 0x03, 0x21, 0xe2, 0xaa, 0xe9, 0xcc, 0x5e, 0x89, 0x31, 0x07, 0x42, 0x7c, 0x5d, + 0x0c, 0xbc, 0xbe, 0x67, 0xbc, 0xca, 0xfe, 0xe8, 0xb4, 0x70, 0xc6, 0x7c, 0xc6, 0x88, 0xc5, 0x58, 0x8f, 0xe7, 0x3b, + 0x15, 0xc9, 0x88, 0x72, 0x96, 0xa1, 0x96, 0xc8, 0x80, 0x52, 0x7b, 0xda, 0x7d, 0x97, 0xa5, 0x5d, 0x3e, 0x46, 0x5f, + 0x5e, 0xdf, 0x17, 0xc7, 0xb7, 0x30, 0x7a, 0xf2, 0xf1, 0x08, 0xa5, 0xb7, 0xd7, 0x2f, 0x46, 0x81, 0x1d, 0x6f, 0xc5, + 0x8a, 0xb3, 0x92, 0xee, 0x39, 0xad, 0xe3, 0x08, 0xe1, 0x20, 0x67, 0x31, 0x06, 0x1d, 0x8b, 0x44, 0x8b, 0x8e, 0x6a, + 0x96, 0xc3, 0x06, 0xa3, 0xcc, 0x12, 0xc8, 0x06, 0xb2, 0x6a, 0x3a, 0x14, 0xab, 0x89, 0x83, 0x02, 0x52, 0xe3, 0x1e, + 0x95, 0xda, 0x17, 0x8c, 0xad, 0xf6, 0x9f, 0x58, 0x8d, 0x71, 0xb7, 0x06, 0x52, 0x92, 0x32, 0x29, 0x69, 0xd1, 0xd5, + 0xf3, 0x45, 0x76, 0xc5, 0x5e, 0x3c, 0xce, 0x4a, 0xdc, 0xd7, 0x80, 0x63, 0xdf, 0xa2, 0x08, 0x0a, 0x92, 0x66, 0xe8, + 0x80, 0xa3, 0x34, 0x3e, 0x61, 0xe9, 0xa7, 0xd8, 0x50, 0x3e, 0x6a, 0x14, 0x38, 0xc9, 0x3f, 0x53, 0x17, 0x91, 0xc4, + 0x42, 0xbf, 0x64, 0x00, 0x12, 0x71, 0x5e, 0x4d, 0xca, 0x75, 0xaa, 0x1c, 0xe4, 0x54, 0x48, 0x6f, 0xe5, 0x1f, 0x97, + 0x11, 0x57, 0x29, 0xca, 0xdc, 0x04, 0xce, 0x84, 0x26, 0xf5, 0xd0, 0xd0, 0x05, 0x6d, 0x01, 0x51, 0x6b, 0x09, 0xf5, + 0x58, 0x96, 0x37, 0x92, 0xcf, 0x2c, 0xf3, 0xac, 0x7e, 0xa7, 0x7e, 0xbf, 0x5d, 0x92, 0x9b, 0x8d, 0xa7, 0xbf, 0x6f, + 0x47, 0x08, 0x37, 0xbf, 0x71, 0x8a, 0xae, 0x10, 0x1c, 0xb3, 0xb2, 0xa7, 0x42, 0x2a, 0x66, 0x58, 0x43, 0x55, 0x9f, + 0xb2, 0xd9, 0x2b, 0x66, 0x17, 0x88, 0xa6, 0x46, 0x26, 0x6e, 0x7c, 0xaa, 0xab, 0x1a, 0x52, 0xdf, 0x77, 0x99, 0x7a, + 0xea, 0x0e, 0x1a, 0x35, 0x20, 0x14, 0x8b, 0x88, 0xf5, 0xd4, 0xff, 0xf1, 0x68, 0x3a, 0x1a, 0x13, 0x9e, 0xce, 0x61, + 0xe9, 0xf7, 0xc2, 0xaf, 0xf3, 0x78, 0x2e, 0xca, 0x94, 0x43, 0xaf, 0xaf, 0xe0, 0x98, 0x3f, 0x00, 0xbe, 0xe8, 0x60, + 0x34, 0x36, 0x82, 0x40, 0x79, 0x90, 0xc1, 0xba, 0x02, 0xba, 0x46, 0xf0, 0x88, 0x4d, 0x70, 0x8d, 0x88, 0x81, 0x95, + 0x76, 0x49, 0x56, 0x03, 0x7b, 0x74, 0xf4, 0x72, 0x6a, 0xe2, 0xa6, 0x13, 0x22, 0x8c, 0x7e, 0xae, 0x91, 0x81, 0xc2, + 0x6d, 0x66, 0x1b, 0x33, 0xe9, 0x66, 0x9f, 0x35, 0xe7, 0xed, 0xa6, 0x18, 0x1a, 0x1d, 0xab, 0x6b, 0x05, 0x77, 0xad, + 0xb6, 0xba, 0x36, 0x2b, 0xb0, 0x65, 0xf0, 0x61, 0x8d, 0x8f, 0x5a, 0x9c, 0x23, 0x2e, 0x1b, 0x25, 0xbf, 0x3c, 0xab, + 0xcf, 0x61, 0xe0, 0xe4, 0x15, 0x3e, 0x51, 0xe0, 0x32, 0xb7, 0xc5, 0xf2, 0xf6, 0x35, 0xc7, 0x33, 0x33, 0x88, 0x47, + 0xd7, 0x3d, 0xa8, 0xaf, 0x0f, 0xa9, 0x37, 0xb0, 0x56, 0x82, 0xb3, 0x74, 0xfe, 0x12, 0x27, 0x0f, 0x26, 0x04, 0xad, + 0xe5, 0xf8, 0x37, 0x85, 0x33, 0x53, 0xb0, 0x90, 0xe7, 0xfe, 0xec, 0x25, 0x41, 0x27, 0x13, 0xd2, 0x83, 0x4e, 0x67, + 0x68, 0xc8, 0xa3, 0xa3, 0x4b, 0x1c, 0xcc, 0x4e, 0x2a, 0x67, 0xab, 0x93, 0x2a, 0x5b, 0xc3, 0xf2, 0xae, 0x71, 0x60, + 0xf9, 0xf1, 0x32, 0x95, 0xcc, 0xfa, 0x9d, 0x85, 0xfc, 0x74, 0x29, 0x58, 0x53, 0xf6, 0xfd, 0x44, 0x63, 0x90, 0x66, + 0x5d, 0xa8, 0x2e, 0x34, 0xee, 0x6c, 0x3c, 0x58, 0x1a, 0x04, 0xbf, 0x0a, 0xf2, 0xfc, 0xda, 0x43, 0x93, 0x98, 0xb3, + 0xec, 0x5c, 0xc5, 0xd0, 0x28, 0xfc, 0xe9, 0xaf, 0x65, 0x56, 0x70, 0x1e, 0x0c, 0x83, 0x98, 0x9e, 0xdb, 0xa5, 0x90, + 0xfb, 0xe1, 0x52, 0xc8, 0xfb, 0xe8, 0x9c, 0xd0, 0x57, 0x04, 0xe9, 0x47, 0x44, 0xdc, 0x9a, 0x19, 0x1d, 0xab, 0x85, + 0x17, 0x16, 0xee, 0xe9, 0x28, 0x5b, 0x8c, 0x60, 0x96, 0xb2, 0xa3, 0x23, 0x03, 0xa0, 0x99, 0x11, 0x32, 0x3c, 0xad, + 0xc8, 0xed, 0xaa, 0x83, 0x60, 0xca, 0xf4, 0x57, 0x63, 0x48, 0x30, 0x08, 0xf8, 0x3f, 0x53, 0xef, 0x01, 0xa2, 0x6d, + 0x32, 0x01, 0xae, 0x47, 0x81, 0x76, 0xcc, 0x96, 0x98, 0xad, 0x3a, 0x1b, 0x82, 0x72, 0xaa, 0xb4, 0x91, 0xb2, 0xb8, + 0x66, 0x8f, 0x48, 0x95, 0x85, 0xc7, 0x2d, 0x18, 0x49, 0xb2, 0xca, 0xc5, 0x17, 0x39, 0x2a, 0xd3, 0xf7, 0x90, 0x81, + 0x53, 0xd4, 0xfb, 0x0b, 0xdc, 0xb2, 0x8b, 0xf7, 0xb4, 0x78, 0x2b, 0x84, 0xb4, 0x3d, 0xeb, 0x36, 0xd5, 0xae, 0xc7, + 0x6d, 0xdd, 0x39, 0x22, 0xf9, 0x7a, 0xd3, 0xe9, 0x54, 0xdb, 0xc9, 0xa5, 0x38, 0x55, 0x23, 0xb5, 0x15, 0x46, 0x41, + 0xa3, 0xb0, 0x75, 0x07, 0x72, 0x99, 0x2d, 0x43, 0xf9, 0xa0, 0xaa, 0xe7, 0xe6, 0xa3, 0xf7, 0xd7, 0x1a, 0x74, 0xdb, + 0x48, 0xc5, 0xbf, 0x95, 0x66, 0x7d, 0x4d, 0x59, 0xd5, 0x05, 0x2a, 0xa8, 0x7c, 0x4b, 0xbf, 0xa2, 0x9c, 0x8c, 0x2e, + 0x15, 0xfb, 0x40, 0x43, 0xf2, 0x35, 0xa5, 0x78, 0xf0, 0x7c, 0x65, 0xeb, 0xc6, 0x8d, 0xee, 0xd2, 0x92, 0x0b, 0xda, + 0x7b, 0xbd, 0xae, 0x05, 0x23, 0xf3, 0x30, 0xa2, 0x2a, 0xa4, 0x9f, 0xf7, 0x95, 0x57, 0xdd, 0xd3, 0xab, 0x86, 0x4b, + 0x72, 0x47, 0xef, 0x2b, 0x28, 0xfd, 0x53, 0xcb, 0x88, 0x8b, 0x51, 0x47, 0xef, 0x2b, 0x25, 0x8b, 0x43, 0xc0, 0xe0, + 0xd4, 0x9c, 0xdf, 0x3f, 0x9d, 0xce, 0xf4, 0x0f, 0x4c, 0xa8, 0x60, 0x32, 0xef, 0x9f, 0xd3, 0x81, 0x0a, 0xcc, 0xac, + 0x72, 0xe9, 0xfd, 0x13, 0x3b, 0x50, 0x58, 0x4f, 0x2d, 0x97, 0xdd, 0x3b, 0xbb, 0x03, 0x45, 0xd5, 0xfc, 0x72, 0x0e, + 0x39, 0xcf, 0xcf, 0xa0, 0x68, 0x6a, 0xd0, 0xb9, 0x6b, 0x4d, 0xc0, 0x4a, 0x8a, 0xe0, 0xa2, 0xc6, 0x50, 0xb6, 0xde, + 0x56, 0x1d, 0xda, 0x20, 0xd0, 0xc1, 0xeb, 0x72, 0x6a, 0x8e, 0x71, 0xc4, 0x39, 0x29, 0x15, 0x1f, 0x25, 0xad, 0x44, + 0xd6, 0x29, 0x5b, 0xcc, 0xa5, 0x5d, 0xb7, 0x69, 0x02, 0x5f, 0x45, 0xc8, 0x87, 0xf0, 0x95, 0x87, 0x6a, 0xf1, 0x27, + 0x9a, 0x11, 0xbb, 0xef, 0x53, 0x95, 0x18, 0xc4, 0xab, 0x0a, 0x62, 0x82, 0x01, 0x6f, 0xb1, 0x1f, 0x9c, 0xe0, 0x44, + 0x87, 0x7b, 0x47, 0x08, 0xd1, 0xaf, 0xbd, 0xe2, 0x4c, 0xdc, 0x95, 0x47, 0xe3, 0xfa, 0x3c, 0x40, 0xc4, 0x4a, 0x90, + 0x7c, 0xe1, 0x0c, 0x44, 0xd4, 0x53, 0x7a, 0x4b, 0xf6, 0xf5, 0xe6, 0x65, 0x3b, 0xf4, 0x69, 0x01, 0x54, 0x24, 0xcb, + 0xfe, 0xe8, 0x18, 0xc1, 0x3b, 0x87, 0x88, 0x91, 0xb6, 0xf2, 0x37, 0xc0, 0xb0, 0xc2, 0x49, 0x74, 0x73, 0x0a, 0x02, + 0x77, 0x31, 0xb5, 0xb9, 0x1f, 0xa5, 0x40, 0x2c, 0x1c, 0x4b, 0x12, 0x19, 0xc1, 0x56, 0x16, 0x70, 0x27, 0x02, 0x1e, + 0x1f, 0x80, 0xca, 0x94, 0x82, 0xcd, 0x6b, 0x7a, 0x7c, 0xc7, 0x37, 0xa3, 0x6f, 0xc6, 0xcd, 0xf8, 0x9b, 0xd1, 0x41, + 0xc6, 0x7c, 0x47, 0x7c, 0xa0, 0x96, 0x44, 0xba, 0x38, 0xf8, 0x66, 0x5c, 0x8c, 0xe9, 0x2c, 0xd1, 0x2c, 0x2d, 0xc5, + 0x56, 0x4b, 0x1b, 0x22, 0xc2, 0xdb, 0x95, 0x84, 0x21, 0xba, 0x1d, 0x3c, 0x22, 0x2e, 0x10, 0x24, 0x0b, 0x98, 0xed, + 0x96, 0xbd, 0xde, 0x8d, 0xfb, 0x16, 0xcb, 0xb2, 0x34, 0xee, 0xaf, 0x21, 0xcb, 0x48, 0xbb, 0xca, 0x50, 0x01, 0x51, + 0x13, 0xd0, 0xd1, 0xfe, 0xc2, 0x1c, 0xaf, 0x50, 0x5c, 0x1f, 0x29, 0x17, 0xaa, 0x46, 0x5d, 0x0a, 0x56, 0xef, 0x88, + 0x10, 0xb9, 0xf3, 0xdc, 0xdc, 0xb7, 0x97, 0x51, 0x2d, 0xab, 0x6a, 0x61, 0xd7, 0xe3, 0xab, 0x80, 0xb5, 0xb5, 0x00, + 0x74, 0x74, 0x5e, 0xeb, 0xab, 0x18, 0xf9, 0x4a, 0x29, 0x80, 0x8b, 0xce, 0x5d, 0x0b, 0xfb, 0xc6, 0xa7, 0xa8, 0x2f, + 0xd9, 0x9a, 0x0e, 0x58, 0x25, 0x46, 0x35, 0xc7, 0xb6, 0xbc, 0xa7, 0xc1, 0x9b, 0xc2, 0x34, 0x19, 0x78, 0xb2, 0x8b, + 0xee, 0x38, 0xd5, 0x51, 0x4d, 0xb8, 0xf5, 0x46, 0xed, 0x51, 0xa2, 0xda, 0x43, 0x33, 0x65, 0x88, 0x2e, 0xcd, 0x2b, + 0x00, 0x79, 0x00, 0xe4, 0xa9, 0x92, 0xe8, 0x2c, 0x45, 0x95, 0xb6, 0x92, 0x28, 0x68, 0x21, 0xbd, 0xc6, 0x88, 0x0b, + 0xb0, 0x1d, 0x28, 0x1a, 0x19, 0x6e, 0xb6, 0x04, 0x71, 0xcb, 0x29, 0x7c, 0x98, 0x86, 0x93, 0x6d, 0x75, 0x0c, 0x93, + 0xac, 0xb8, 0x89, 0xf3, 0x4c, 0xa0, 0x25, 0xbe, 0x95, 0x16, 0x13, 0x16, 0x90, 0x96, 0xa7, 0x2f, 0xca, 0x7c, 0xc1, + 0x90, 0x70, 0xd6, 0x5b, 0x07, 0x50, 0x4d, 0xd6, 0x5a, 0xdb, 0x19, 0x59, 0x7d, 0xe5, 0x21, 0x15, 0x3a, 0x34, 0x98, + 0x92, 0x3a, 0x66, 0xe8, 0x89, 0xfd, 0x95, 0xfe, 0x8a, 0xf0, 0x5d, 0xcb, 0x70, 0x1e, 0xbf, 0x0f, 0x0d, 0x06, 0x65, + 0x5a, 0x21, 0x82, 0x0c, 0xcd, 0x26, 0xba, 0xf2, 0x0c, 0x2c, 0xa6, 0x6e, 0x7c, 0x04, 0xc6, 0x11, 0x21, 0x11, 0xbc, + 0x30, 0x61, 0xdd, 0x0a, 0x73, 0xcf, 0x22, 0xa7, 0x09, 0x22, 0x8b, 0x97, 0xd1, 0x0d, 0x22, 0x73, 0xea, 0x5d, 0x21, + 0x23, 0x7b, 0x98, 0xce, 0xcf, 0xce, 0xc3, 0xdf, 0x56, 0x4c, 0xbf, 0xe0, 0x0b, 0xed, 0x70, 0x93, 0x5c, 0x9e, 0x5a, + 0x8f, 0x26, 0x59, 0xcc, 0x15, 0xce, 0x98, 0xd6, 0x11, 0x79, 0x3c, 0xe3, 0xad, 0x80, 0xac, 0xd9, 0x38, 0x7a, 0x72, + 0x88, 0xd8, 0x2d, 0xd7, 0xa9, 0x97, 0x44, 0x4f, 0x62, 0x69, 0x16, 0x22, 0x3f, 0x46, 0x51, 0x62, 0x9e, 0x7c, 0x45, + 0x0e, 0x65, 0x4d, 0xb1, 0x47, 0xcb, 0xbe, 0x65, 0xc9, 0x2e, 0x3b, 0xfc, 0x16, 0xaf, 0x4c, 0x6d, 0xfe, 0xfb, 0x66, + 0xe5, 0x06, 0xf9, 0x0e, 0x04, 0xd9, 0xd7, 0x01, 0xd6, 0x6c, 0xd4, 0xf0, 0xb0, 0x84, 0x60, 0x80, 0xc8, 0x18, 0x65, + 0xb6, 0xd0, 0xb2, 0x35, 0x10, 0x3f, 0x81, 0xf9, 0x4d, 0x59, 0xd2, 0xe2, 0x53, 0x1c, 0x21, 0x67, 0x2d, 0x31, 0x2a, + 0x53, 0xd5, 0x91, 0xc1, 0xbc, 0x5b, 0x57, 0x6d, 0xd7, 0xa5, 0x37, 0x82, 0x68, 0xd4, 0x95, 0xb3, 0x48, 0xa5, 0x02, + 0xc5, 0x7b, 0xf2, 0x35, 0xbc, 0xe2, 0x39, 0xab, 0x60, 0x60, 0xce, 0x11, 0x31, 0x48, 0x01, 0x71, 0xca, 0x57, 0x30, + 0x4c, 0x74, 0x09, 0xc7, 0xde, 0xeb, 0x45, 0x1d, 0x36, 0x56, 0xd7, 0x3f, 0x39, 0x90, 0xb1, 0x87, 0xb0, 0x4c, 0x32, + 0x8e, 0xe8, 0x47, 0x5e, 0x18, 0xf4, 0xfb, 0x78, 0xbe, 0x6b, 0xc3, 0xcc, 0x14, 0xf9, 0x0d, 0x8b, 0xe8, 0x7a, 0x1b, + 0x53, 0x6f, 0xda, 0xba, 0xf0, 0xd7, 0x04, 0x0e, 0x9f, 0x39, 0x8a, 0xed, 0x6e, 0xa8, 0x9c, 0xc6, 0x5c, 0x18, 0xc4, + 0x28, 0x6f, 0xe5, 0x11, 0x34, 0xa8, 0x38, 0x4b, 0xce, 0x51, 0x51, 0x9a, 0xeb, 0x78, 0x21, 0xa5, 0x98, 0x09, 0xf0, + 0x47, 0xc3, 0x58, 0xab, 0x2b, 0x3f, 0x40, 0x5b, 0xd4, 0xb2, 0x36, 0x6f, 0xa9, 0x45, 0x61, 0x0a, 0xd5, 0xb4, 0x41, + 0xce, 0x87, 0xa4, 0x12, 0x2e, 0x4d, 0x37, 0x3e, 0x58, 0xdd, 0x50, 0xbd, 0xe8, 0x75, 0x41, 0xcd, 0xd2, 0x07, 0x14, + 0x00, 0xff, 0xc1, 0x32, 0x8e, 0xea, 0x14, 0x2b, 0x1a, 0xe8, 0x0d, 0x61, 0xc4, 0x1a, 0xe2, 0x89, 0x7b, 0x4d, 0x89, + 0x19, 0x47, 0x47, 0xe2, 0x32, 0x64, 0x92, 0x28, 0x4e, 0x1f, 0x6d, 0xd7, 0xf7, 0xac, 0xba, 0xc0, 0xf8, 0xca, 0x75, + 0x70, 0x36, 0x1a, 0x9d, 0x23, 0x5c, 0x2f, 0xee, 0x5f, 0x24, 0x5c, 0xe1, 0xe1, 0x09, 0xc3, 0xef, 0xaa, 0x07, 0xa0, + 0xa2, 0xd8, 0x04, 0x8a, 0xf8, 0x63, 0xa1, 0xa8, 0x17, 0x9d, 0xc8, 0xee, 0x53, 0x25, 0xa0, 0x80, 0x78, 0x65, 0xc5, + 0x04, 0x41, 0xe3, 0x61, 0xf5, 0x06, 0x93, 0x7d, 0x79, 0xed, 0xf3, 0x8a, 0x42, 0xd5, 0x11, 0xe2, 0xb3, 0x59, 0x0f, + 0xa9, 0xfd, 0x80, 0x9e, 0x85, 0xfa, 0x9b, 0x6f, 0x64, 0xd5, 0x30, 0x3f, 0x90, 0xe9, 0xd8, 0xc7, 0x78, 0x6a, 0x5c, + 0x50, 0xa1, 0x8b, 0xd1, 0x1c, 0xf6, 0x3e, 0x23, 0x73, 0x5f, 0xd0, 0x6d, 0xdf, 0x05, 0x1e, 0x21, 0x90, 0xc6, 0xa6, + 0x7c, 0xf3, 0xd1, 0x76, 0x14, 0x52, 0x6c, 0x73, 0x0b, 0xf2, 0xfa, 0xb9, 0x8b, 0x5f, 0x9c, 0x51, 0x78, 0x16, 0x5d, + 0x61, 0xa8, 0x2b, 0x32, 0x25, 0xfe, 0x55, 0xe3, 0xce, 0x45, 0xf4, 0x5e, 0xae, 0x56, 0x62, 0xb2, 0x6d, 0xd5, 0x8f, + 0x4a, 0xdd, 0xdf, 0x1e, 0x58, 0x0b, 0xf8, 0xed, 0xca, 0x2e, 0xc4, 0x57, 0xbe, 0x59, 0xff, 0xca, 0x57, 0xdc, 0xa2, + 0x33, 0xeb, 0xc2, 0x39, 0xeb, 0x5d, 0x38, 0xc3, 0xa7, 0x2c, 0xd4, 0x68, 0x9c, 0x0a, 0xe6, 0x40, 0xa1, 0x20, 0xb5, + 0x4d, 0x7f, 0xde, 0x5a, 0xf9, 0xa9, 0xb3, 0xf2, 0x51, 0x3e, 0x8e, 0x69, 0x88, 0xa1, 0x5d, 0x26, 0x10, 0xa9, 0x8f, + 0xe1, 0xc0, 0xc4, 0x79, 0x02, 0x36, 0x39, 0x56, 0x76, 0x76, 0x7c, 0x3e, 0x6d, 0xca, 0xef, 0xca, 0x4f, 0x88, 0xea, + 0x50, 0xe3, 0xbd, 0x1c, 0xf1, 0x50, 0x86, 0x6d, 0xea, 0xf2, 0x3d, 0xbf, 0x3c, 0x8f, 0x0b, 0xe4, 0x77, 0x34, 0x3c, + 0xce, 0x01, 0xb2, 0x61, 0xf8, 0xf5, 0x6f, 0x1e, 0xec, 0xb2, 0xf6, 0x9b, 0x03, 0xfc, 0xee, 0xf4, 0xe0, 0x1d, 0x85, + 0xbb, 0x39, 0x58, 0x57, 0xe5, 0x4d, 0xb6, 0x48, 0x0f, 0xbe, 0xc1, 0xd4, 0x6f, 0x0e, 0xca, 0xea, 0xe0, 0x1b, 0xd5, + 0x18, 0x78, 0xa2, 0xc5, 0x3e, 0xfd, 0xc5, 0x5a, 0x78, 0x3f, 0xd7, 0x3a, 0x02, 0xda, 0x12, 0x3d, 0xb3, 0xb4, 0xfa, + 0x11, 0x95, 0x88, 0x2a, 0x3e, 0xaa, 0x78, 0xb5, 0x5a, 0x14, 0xe7, 0xdd, 0x4a, 0x23, 0x65, 0xf3, 0x82, 0xa4, 0xdd, + 0x02, 0x7f, 0xf5, 0xea, 0xb4, 0xe3, 0xed, 0x38, 0x2f, 0xd4, 0x01, 0x51, 0x44, 0x4f, 0x8a, 0xe9, 0x2d, 0x7f, 0x0d, + 0xbf, 0x75, 0x77, 0x57, 0x4c, 0xb7, 0xe6, 0xd1, 0xe7, 0xdb, 0x4a, 0xf1, 0x3b, 0x52, 0xc1, 0x85, 0x28, 0x66, 0xdc, + 0xed, 0x28, 0xc0, 0x18, 0x00, 0x30, 0xb8, 0xfc, 0xbc, 0x95, 0x67, 0x45, 0x2d, 0xad, 0x76, 0x3e, 0xe8, 0xc4, 0xce, + 0x78, 0xdd, 0x98, 0x40, 0x6d, 0x3b, 0xc1, 0x96, 0x86, 0xfc, 0xa4, 0x29, 0xe2, 0x47, 0xdc, 0x4d, 0x70, 0x9c, 0xe1, + 0x86, 0x14, 0x48, 0x62, 0x3c, 0x46, 0x07, 0xf4, 0x38, 0x43, 0x51, 0x7a, 0x0a, 0xdf, 0x89, 0xcb, 0xad, 0xe5, 0x01, + 0x09, 0xab, 0x70, 0xf8, 0xce, 0x8b, 0x0d, 0x3c, 0x3a, 0xbc, 0x2c, 0xf3, 0x74, 0x9a, 0xf2, 0x2c, 0xbf, 0x66, 0x76, + 0xe6, 0x80, 0x5a, 0x71, 0x90, 0x08, 0x58, 0x58, 0xcc, 0xe8, 0xde, 0x30, 0x8d, 0x8c, 0x00, 0x7e, 0xf0, 0x60, 0x57, + 0xb5, 0xbf, 0x30, 0x3c, 0xc4, 0xf4, 0x02, 0x23, 0xe2, 0x6c, 0xbb, 0xf5, 0x7d, 0x8a, 0xa1, 0xed, 0xbf, 0xbc, 0xbe, + 0x2a, 0x4a, 0x74, 0xd1, 0x3c, 0x10, 0xc5, 0x6a, 0x75, 0x80, 0x11, 0xf3, 0x80, 0x53, 0x8e, 0x6b, 0x59, 0x06, 0xf5, + 0x40, 0xb5, 0x9a, 0x8c, 0x17, 0x1e, 0x86, 0xf8, 0x86, 0x59, 0xae, 0x82, 0xcc, 0x0f, 0x5e, 0x2a, 0xd3, 0x54, 0xd6, + 0x23, 0x9f, 0xa3, 0xb7, 0x24, 0x6c, 0xf3, 0x04, 0xef, 0x3f, 0xd0, 0x31, 0xdd, 0x8c, 0xdc, 0x8c, 0x42, 0x91, 0xaf, + 0xf7, 0x28, 0xbe, 0x78, 0x1d, 0x25, 0x2d, 0x54, 0xbd, 0xc2, 0xe3, 0x61, 0x75, 0x96, 0x9f, 0x53, 0x94, 0xf2, 0xac, + 0xbb, 0x44, 0x06, 0x06, 0xb1, 0xa2, 0x6f, 0xeb, 0xf8, 0x7a, 0x89, 0xf2, 0xc0, 0x64, 0xca, 0x06, 0x7d, 0x8e, 0x71, + 0xaa, 0x56, 0x91, 0x07, 0x73, 0x1c, 0x0b, 0x51, 0xb4, 0x1a, 0x66, 0x4f, 0xd3, 0xca, 0x4c, 0xd3, 0x42, 0x7f, 0x61, + 0x13, 0x01, 0x41, 0x5c, 0xe0, 0xc5, 0xf5, 0x12, 0x78, 0xd4, 0x8d, 0xd9, 0x08, 0x77, 0x77, 0x1b, 0xe8, 0xd6, 0x12, + 0xdd, 0x5c, 0x97, 0xf0, 0x30, 0xd4, 0x33, 0xe8, 0x30, 0xbe, 0x54, 0x3d, 0xdc, 0xc0, 0x82, 0xc2, 0xc7, 0xd5, 0xd9, + 0x82, 0xfa, 0x07, 0x3d, 0xb4, 0x3f, 0x5f, 0x0e, 0xaf, 0x12, 0x0a, 0xbe, 0xb1, 0xc6, 0xb0, 0x63, 0x67, 0xdd, 0x01, + 0x57, 0x33, 0x20, 0x21, 0xdd, 0x8d, 0xfe, 0xae, 0x1a, 0x8c, 0x53, 0x42, 0xb1, 0xa3, 0x15, 0xd8, 0x2f, 0x8c, 0xc3, + 0x4c, 0xb3, 0x3d, 0x74, 0x6f, 0x63, 0x73, 0x88, 0x6a, 0xd9, 0x47, 0xb2, 0x53, 0x2c, 0xcd, 0x5b, 0x65, 0xa3, 0xe7, + 0xe3, 0xfe, 0xdc, 0xb5, 0x32, 0x51, 0x38, 0x47, 0x51, 0x66, 0x1d, 0x44, 0xc0, 0x24, 0x64, 0x02, 0x0a, 0x1a, 0x65, + 0x16, 0x3a, 0x68, 0x60, 0x11, 0xec, 0xa7, 0xab, 0xbd, 0xf5, 0x45, 0xf8, 0x2d, 0xfa, 0xe5, 0x07, 0xd4, 0xa5, 0x40, + 0x45, 0x7b, 0xfc, 0xbd, 0x56, 0xa1, 0xf0, 0x82, 0x2d, 0xc7, 0x5a, 0xfb, 0x90, 0x36, 0x26, 0x94, 0xc3, 0xbf, 0x53, + 0xfb, 0x08, 0xfb, 0x9d, 0xae, 0x20, 0x0c, 0x76, 0xfd, 0x41, 0x4a, 0x10, 0xb1, 0xe8, 0x2f, 0xf8, 0x7b, 0x2d, 0xa1, + 0xe8, 0x80, 0x7b, 0xdc, 0x56, 0x18, 0xb7, 0x8e, 0x1c, 0xfa, 0x52, 0xf9, 0x4c, 0x92, 0x9a, 0x30, 0x13, 0x9a, 0xa2, + 0xff, 0xcc, 0xd1, 0xc9, 0x66, 0x85, 0xe5, 0xf2, 0x41, 0x41, 0x25, 0xf1, 0x0a, 0x56, 0x04, 0xca, 0x6f, 0x5d, 0x81, + 0x52, 0x6b, 0x2d, 0x78, 0xff, 0x46, 0x4f, 0x57, 0x9e, 0xc1, 0xdf, 0x43, 0x1e, 0x83, 0xa5, 0x11, 0xd5, 0x25, 0xe7, + 0xea, 0xa3, 0x72, 0xde, 0xa1, 0x0a, 0xe8, 0x60, 0x9d, 0xc7, 0x0d, 0xac, 0x94, 0xeb, 0x8e, 0xa7, 0xa8, 0xd4, 0x3e, + 0x55, 0xaf, 0x29, 0x2f, 0xae, 0x93, 0x3d, 0xf9, 0xf0, 0x15, 0x02, 0xe3, 0x71, 0x9e, 0x4e, 0x1b, 0xe5, 0xf6, 0x83, + 0xea, 0xc0, 0x19, 0xd8, 0x53, 0x07, 0xbe, 0xa2, 0x3a, 0x28, 0x4f, 0xb7, 0x0e, 0x35, 0x89, 0x0d, 0xa9, 0xae, 0x14, + 0x81, 0xd9, 0x53, 0x95, 0xbc, 0xa5, 0xda, 0x4a, 0x73, 0xd1, 0x34, 0x94, 0x47, 0xda, 0x25, 0x0b, 0x76, 0xef, 0x30, + 0xb0, 0x6e, 0x61, 0xa3, 0x70, 0x06, 0xdd, 0x9b, 0x25, 0xce, 0xc5, 0x52, 0x46, 0x02, 0x87, 0x24, 0x8f, 0xb3, 0x47, + 0x2b, 0x0d, 0xda, 0x6b, 0x27, 0xed, 0xba, 0x33, 0xc5, 0x05, 0x0c, 0xda, 0xa7, 0x3d, 0x53, 0xea, 0x5d, 0x2b, 0xdb, + 0xc0, 0xe6, 0x2e, 0x55, 0x3b, 0xff, 0x8d, 0xca, 0x77, 0xbc, 0x66, 0x3c, 0x3b, 0xfb, 0x45, 0x13, 0xb7, 0x07, 0xbb, + 0xa6, 0xfd, 0x25, 0x00, 0x3e, 0xf1, 0x5c, 0x97, 0x7d, 0xaa, 0xa2, 0x50, 0x59, 0x95, 0x58, 0x4e, 0xda, 0x50, 0xcd, + 0x2f, 0x58, 0x6a, 0x4a, 0xd7, 0xca, 0x74, 0x98, 0x43, 0x2d, 0x29, 0xd4, 0x32, 0x54, 0xb7, 0x95, 0xab, 0x96, 0x6c, + 0xbf, 0xf4, 0x92, 0x80, 0x56, 0xdd, 0xdb, 0x22, 0xd1, 0x01, 0xde, 0xdf, 0x9e, 0xc9, 0x3d, 0x8d, 0x50, 0x6a, 0x41, + 0xd5, 0x82, 0xce, 0xc7, 0x7e, 0xe9, 0xbc, 0xe7, 0x8f, 0xf7, 0xf9, 0x74, 0x8b, 0x8b, 0x30, 0x76, 0x60, 0xb8, 0x64, + 0x67, 0x4e, 0x5b, 0x8a, 0x76, 0xc2, 0x65, 0xdd, 0x26, 0x29, 0x61, 0x8f, 0x13, 0x91, 0xa9, 0xc3, 0xfd, 0x4b, 0xe3, + 0x10, 0xe7, 0x36, 0xeb, 0x8f, 0xc4, 0xea, 0x9c, 0x01, 0x11, 0x11, 0xad, 0x55, 0xe4, 0x81, 0x7e, 0xbc, 0x30, 0x6b, + 0x6d, 0x88, 0x59, 0x6f, 0xa1, 0xb4, 0x57, 0xc2, 0xa0, 0x1f, 0x22, 0xcf, 0xed, 0x93, 0x59, 0xae, 0xda, 0xe6, 0x85, + 0xdc, 0xe5, 0xe0, 0x8d, 0x16, 0x01, 0x35, 0x3b, 0x42, 0x87, 0x03, 0x05, 0xa1, 0x44, 0xa2, 0x9a, 0x63, 0x75, 0x94, + 0x1d, 0x44, 0xad, 0x4e, 0xeb, 0x0a, 0xbe, 0x53, 0xe1, 0xbb, 0x24, 0x86, 0x8c, 0x52, 0x11, 0xba, 0xf4, 0x51, 0xae, + 0x68, 0xa6, 0x89, 0x01, 0xba, 0xc2, 0x3b, 0x6d, 0xb4, 0xb7, 0x20, 0x5a, 0x86, 0x67, 0xa6, 0x7d, 0x1a, 0x26, 0x18, + 0xd3, 0x1c, 0x7d, 0xfe, 0xfc, 0xa1, 0x17, 0x35, 0xbe, 0x18, 0x48, 0x87, 0x33, 0xb7, 0xa4, 0x33, 0x77, 0xcf, 0xfb, + 0x97, 0x7b, 0xd2, 0xcb, 0x02, 0x5f, 0xe8, 0x30, 0x88, 0x79, 0xf4, 0xb4, 0xaa, 0xe2, 0xed, 0x74, 0x59, 0x95, 0xd7, + 0x5e, 0xa2, 0xe9, 0x78, 0x2e, 0x6c, 0x20, 0x47, 0xc6, 0xcc, 0x59, 0x14, 0x1b, 0x38, 0x87, 0x89, 0xb6, 0xaf, 0xe2, + 0x9a, 0xee, 0x3f, 0x2b, 0x1a, 0xf5, 0x14, 0xb1, 0x1c, 0xf2, 0x96, 0x6e, 0xe1, 0x9d, 0x61, 0xef, 0x8e, 0xb8, 0x44, + 0x47, 0x71, 0x1d, 0xe8, 0xcf, 0x1a, 0xec, 0x59, 0xca, 0x3d, 0xb3, 0x64, 0x32, 0x49, 0x91, 0x58, 0xbe, 0xf0, 0x0a, + 0x3a, 0x72, 0xde, 0x0a, 0x79, 0xf8, 0x3e, 0xbe, 0x4e, 0x17, 0xfa, 0x06, 0x9d, 0x75, 0x64, 0x11, 0xca, 0x85, 0x46, + 0x22, 0xdd, 0x3d, 0xa8, 0xa1, 0x41, 0xe9, 0x02, 0xa5, 0xc0, 0x60, 0xa7, 0xc8, 0x4a, 0x58, 0x61, 0x3c, 0xd8, 0x77, + 0x55, 0xba, 0xcc, 0x6e, 0x53, 0xc4, 0x75, 0x88, 0x7e, 0x12, 0x44, 0x42, 0x97, 0xf2, 0x70, 0x88, 0x35, 0xb6, 0xbe, + 0x21, 0x34, 0xf6, 0x17, 0xc8, 0xa7, 0x61, 0x30, 0x91, 0x72, 0x2a, 0x15, 0x63, 0x3c, 0x80, 0x22, 0x5a, 0xe3, 0x95, + 0xdc, 0xbc, 0xf0, 0xfc, 0xb0, 0xd0, 0x03, 0xcc, 0x74, 0xd0, 0x15, 0x5c, 0x61, 0x25, 0xa1, 0x55, 0x4c, 0x12, 0xfd, + 0xa3, 0x81, 0x8a, 0x0a, 0x18, 0xb2, 0xd6, 0x08, 0x3a, 0x39, 0x8a, 0x1a, 0xa9, 0x5f, 0x02, 0xaf, 0x16, 0x25, 0xf0, + 0x8f, 0xbe, 0xe2, 0x6d, 0x7b, 0xb5, 0x20, 0x9c, 0x3a, 0x09, 0xc0, 0x1a, 0xfa, 0x4a, 0x77, 0xad, 0xbc, 0xa7, 0x33, + 0x46, 0x41, 0x45, 0x06, 0x42, 0xd0, 0x88, 0x12, 0xaa, 0x92, 0x30, 0x09, 0xb5, 0x8f, 0x26, 0xe8, 0x26, 0xf2, 0x72, + 0xed, 0xc6, 0xaa, 0xc9, 0xd4, 0xf6, 0x2b, 0x08, 0xf5, 0x5d, 0x6d, 0xb9, 0x4c, 0x5f, 0x9f, 0x1a, 0x65, 0x4d, 0xca, + 0x57, 0x8e, 0x62, 0xf7, 0x29, 0x1b, 0xb7, 0x36, 0x57, 0xd6, 0x50, 0x01, 0xcc, 0x8c, 0x6e, 0xf1, 0xeb, 0x82, 0x03, + 0x8a, 0xda, 0x53, 0x92, 0xda, 0xda, 0xad, 0xd8, 0x95, 0xe3, 0x80, 0x1b, 0x4d, 0xf2, 0xcd, 0x02, 0x96, 0xd6, 0x68, + 0x85, 0xb7, 0xc5, 0x23, 0x8c, 0x8b, 0xca, 0x5b, 0xbf, 0x0e, 0x4a, 0x44, 0x0f, 0x10, 0x66, 0xe3, 0x35, 0x45, 0xc0, + 0x7a, 0x07, 0x7c, 0xca, 0xd1, 0x91, 0xb9, 0xca, 0x7f, 0xfb, 0xa9, 0xc0, 0x20, 0x85, 0x69, 0xd5, 0x6c, 0x71, 0x01, + 0xa1, 0xd8, 0xc9, 0xda, 0xb3, 0x26, 0x7a, 0x02, 0xf3, 0x88, 0xf1, 0xad, 0x7c, 0x2b, 0x88, 0xd5, 0x0b, 0x5b, 0xb0, + 0xd9, 0x65, 0xf5, 0x07, 0xa3, 0x71, 0x48, 0x03, 0xe0, 0x5b, 0xb5, 0xca, 0xa1, 0x68, 0xa3, 0x12, 0x59, 0x2a, 0x4b, + 0x74, 0xad, 0x1d, 0xd1, 0xb5, 0x8c, 0x28, 0x96, 0x2c, 0x70, 0x57, 0xf8, 0x27, 0x8e, 0xbc, 0xea, 0xee, 0xae, 0x84, + 0xa6, 0x65, 0x67, 0xac, 0x95, 0xc5, 0xe8, 0x69, 0xd0, 0x80, 0x18, 0xc4, 0xad, 0xd7, 0x68, 0x35, 0x02, 0x7f, 0xab, + 0xa3, 0xa3, 0xf7, 0x46, 0x92, 0x81, 0x35, 0xac, 0xb5, 0xb3, 0xa8, 0x74, 0xff, 0xb8, 0x8a, 0x46, 0x7f, 0x9e, 0xfe, + 0x79, 0x7a, 0x32, 0x92, 0xa1, 0xff, 0x6e, 0x05, 0xab, 0x02, 0x25, 0xf4, 0x40, 0x09, 0xe7, 0x81, 0x98, 0xbb, 0x2b, + 0x3b, 0xf4, 0x91, 0x86, 0x8a, 0x1f, 0x9d, 0x9b, 0x3e, 0xfe, 0xa3, 0xee, 0x69, 0x12, 0x06, 0x04, 0xfd, 0xbb, 0xbb, + 0xef, 0x56, 0x5a, 0x9f, 0x96, 0x29, 0x7d, 0x9a, 0xc2, 0x51, 0x32, 0xc1, 0xdd, 0xdc, 0xca, 0xb4, 0x63, 0x3f, 0x11, + 0x5f, 0xc5, 0x2f, 0x9e, 0x65, 0x28, 0xf6, 0x16, 0xf0, 0x67, 0x8e, 0xb7, 0x2b, 0x13, 0x20, 0x00, 0xe7, 0x21, 0xa6, + 0x4e, 0x30, 0xcd, 0x5a, 0x86, 0xff, 0xac, 0x5d, 0xc6, 0x5b, 0x5b, 0xbc, 0x1b, 0xb8, 0x28, 0x75, 0xa4, 0xcf, 0xba, + 0x68, 0xba, 0xac, 0x92, 0x7f, 0x9f, 0x42, 0x93, 0xd1, 0x67, 0xe3, 0x35, 0xc7, 0xfd, 0x2c, 0x8b, 0xe7, 0x25, 0x62, + 0x17, 0x21, 0x18, 0x72, 0x76, 0xee, 0x70, 0xe2, 0xef, 0x57, 0x5f, 0xff, 0x31, 0x5d, 0x1b, 0x2c, 0xa6, 0x2b, 0x58, + 0xcb, 0x75, 0xaf, 0xb6, 0x5b, 0x9b, 0xaf, 0xc7, 0x48, 0xa2, 0x30, 0x58, 0x1c, 0x49, 0x34, 0x23, 0x57, 0x14, 0x94, + 0x1a, 0x47, 0xf3, 0x2c, 0x52, 0x11, 0xab, 0xa7, 0xe6, 0xf6, 0xf3, 0xd9, 0xf6, 0xf5, 0x02, 0x0a, 0x87, 0x19, 0x32, + 0xc2, 0x1a, 0x4a, 0x29, 0xa3, 0x78, 0x70, 0xc0, 0xb3, 0x63, 0x2a, 0x87, 0xd6, 0xe5, 0x54, 0x79, 0x30, 0xdc, 0x7c, + 0x9c, 0xa1, 0x5e, 0xf6, 0xdf, 0x35, 0xae, 0x7f, 0xdd, 0x1f, 0x6a, 0x4f, 0x47, 0x98, 0x26, 0x15, 0x51, 0xed, 0xc5, + 0x99, 0xbe, 0x02, 0x49, 0xa3, 0x27, 0xa9, 0x15, 0xef, 0xd7, 0x67, 0x43, 0x82, 0xd6, 0x2c, 0x96, 0x97, 0x3d, 0xf3, + 0x0b, 0x5b, 0xe4, 0xea, 0xaf, 0xff, 0xc2, 0xac, 0xff, 0x31, 0x19, 0x43, 0x96, 0x4f, 0x22, 0xeb, 0xc2, 0x82, 0x56, + 0xbf, 0x98, 0x7a, 0xe0, 0xef, 0xc0, 0x4b, 0x9f, 0x60, 0x38, 0xe6, 0x27, 0x64, 0xaa, 0x98, 0x9d, 0x95, 0x63, 0x8c, + 0x65, 0xeb, 0xb7, 0xd6, 0x9a, 0xf8, 0xde, 0x0a, 0x79, 0x25, 0x1b, 0x42, 0x8b, 0xab, 0xb8, 0x1a, 0xaf, 0xcb, 0x4d, + 0x9d, 0x96, 0x9b, 0x66, 0xc4, 0x6a, 0xd9, 0x62, 0xde, 0xd8, 0x0a, 0xd9, 0xbf, 0xa7, 0x9d, 0x08, 0x5e, 0x26, 0xea, + 0x64, 0x92, 0x67, 0xeb, 0x39, 0xc6, 0xd7, 0x0b, 0xf1, 0x2c, 0x32, 0x45, 0x3e, 0x3b, 0x34, 0xe0, 0x96, 0x2e, 0x4f, + 0x31, 0x38, 0x2a, 0xff, 0x80, 0x8d, 0xaf, 0x75, 0x7a, 0xb0, 0x26, 0x8a, 0x39, 0xfb, 0x7c, 0xfd, 0x1d, 0xe1, 0xb9, + 0x1a, 0xd9, 0x80, 0xbe, 0xb8, 0x3a, 0xa8, 0x44, 0xd1, 0x8a, 0x91, 0xc7, 0x02, 0xa4, 0x61, 0x20, 0x67, 0x76, 0x6c, + 0x56, 0x5e, 0x12, 0x2a, 0x51, 0x29, 0xd9, 0xda, 0xb0, 0x11, 0xeb, 0x8b, 0xaa, 0xd9, 0xd5, 0x68, 0x28, 0x99, 0xe8, + 0x6b, 0x39, 0xb9, 0xc6, 0x4d, 0x89, 0xe7, 0xe2, 0x87, 0xe0, 0xef, 0x70, 0xf0, 0xb6, 0x92, 0xcf, 0x32, 0xdf, 0xd1, + 0x39, 0x6d, 0xc3, 0x05, 0xce, 0xdc, 0x31, 0xda, 0xea, 0xf0, 0x63, 0x22, 0x67, 0x91, 0xb6, 0x6c, 0x45, 0x21, 0x16, + 0x71, 0x3d, 0x91, 0xca, 0xe6, 0xdf, 0xe8, 0x6a, 0x4b, 0x13, 0xdb, 0x37, 0x61, 0xe2, 0xf8, 0xb7, 0x78, 0x93, 0x18, + 0xe7, 0x40, 0x74, 0x16, 0x5b, 0xb4, 0xfe, 0x81, 0xd9, 0x99, 0x1e, 0x90, 0x91, 0xfb, 0xc1, 0xa7, 0xac, 0x59, 0x1d, + 0xbc, 0x7e, 0x71, 0xf0, 0xcd, 0x68, 0x5c, 0x02, 0xdf, 0x39, 0x1e, 0x7d, 0x73, 0x70, 0xbd, 0x41, 0xb4, 0xcc, 0xf4, + 0x60, 0xc1, 0x57, 0x69, 0xe9, 0xe2, 0x80, 0x2f, 0x06, 0x41, 0x16, 0x49, 0x0f, 0x78, 0x61, 0xba, 0xc5, 0x38, 0x4d, + 0x4a, 0xc3, 0x03, 0x16, 0xae, 0x52, 0xf8, 0xc8, 0x02, 0xef, 0x29, 0xd5, 0x3a, 0x2b, 0xba, 0x67, 0x71, 0x31, 0x1d, + 0xe0, 0x55, 0x06, 0xf0, 0xb7, 0x67, 0x72, 0xaf, 0xca, 0x22, 0x20, 0x8e, 0x00, 0x1a, 0xe9, 0xca, 0x23, 0x2c, 0xbb, + 0x15, 0xba, 0x5a, 0x04, 0x4e, 0xa6, 0x29, 0x4b, 0x48, 0xcf, 0x69, 0xcc, 0xf0, 0x5a, 0x48, 0x69, 0x1e, 0xdc, 0x5c, + 0x9d, 0x08, 0xdd, 0xc0, 0x7d, 0x4e, 0xe3, 0x7a, 0x0d, 0x5b, 0x89, 0xa2, 0x1e, 0xa3, 0xf1, 0x0e, 0x7a, 0x00, 0xa8, + 0xe0, 0xe0, 0x79, 0x94, 0x1c, 0x1d, 0x25, 0xca, 0x29, 0x67, 0xc5, 0x4f, 0xec, 0xfa, 0xa5, 0xe1, 0x28, 0x17, 0xd1, + 0xaf, 0xb1, 0x8e, 0x11, 0xd0, 0xdc, 0x46, 0xb1, 0x0a, 0x17, 0x40, 0xdb, 0x39, 0x09, 0x8c, 0xf1, 0x5e, 0xe4, 0x02, + 0x73, 0xaa, 0x10, 0x14, 0x4a, 0x1c, 0xac, 0x14, 0x00, 0xbd, 0x69, 0x8f, 0x58, 0x4e, 0x9a, 0x04, 0x8d, 0xe7, 0x86, + 0x16, 0xaf, 0x26, 0x16, 0xd5, 0x75, 0x2a, 0x7a, 0x0b, 0x9d, 0x02, 0xcb, 0x10, 0x03, 0x08, 0xb8, 0x31, 0xc3, 0x6e, + 0x53, 0x93, 0x23, 0xd9, 0x54, 0x15, 0x50, 0xbd, 0x6e, 0x60, 0x68, 0x37, 0x2e, 0x99, 0x3a, 0xb8, 0xdc, 0xb0, 0x4e, + 0x1c, 0x05, 0xd8, 0x7c, 0x0b, 0x91, 0xbd, 0x28, 0xd4, 0xb5, 0x9b, 0x2d, 0x97, 0xc0, 0xd7, 0xb5, 0x09, 0x5f, 0x02, + 0x34, 0x7b, 0x0d, 0x5d, 0x85, 0xd2, 0xdf, 0xe9, 0x97, 0x6e, 0x2c, 0x29, 0xb2, 0x23, 0x7d, 0xd3, 0xed, 0x8e, 0xa8, + 0x71, 0x74, 0x3d, 0xb6, 0xa5, 0xd2, 0xad, 0x8c, 0xaa, 0x15, 0x42, 0x5b, 0x72, 0xad, 0xb2, 0xc5, 0x22, 0x2d, 0x80, + 0x5b, 0x80, 0x1e, 0x9a, 0xe4, 0x58, 0x62, 0x55, 0x9b, 0x20, 0x58, 0x26, 0x4c, 0xf2, 0x8b, 0xac, 0xa6, 0xd8, 0xc1, + 0x4e, 0xa3, 0x3a, 0x51, 0xa7, 0x54, 0x0c, 0x8c, 0xf2, 0x3d, 0x05, 0xdf, 0x8e, 0xca, 0x04, 0x19, 0x7e, 0x4a, 0x14, + 0x21, 0x7d, 0x81, 0x01, 0x1f, 0x38, 0x34, 0xf7, 0x8b, 0x94, 0xc0, 0xaf, 0xf5, 0x95, 0x39, 0x32, 0xd9, 0x72, 0x05, + 0x49, 0xb8, 0x77, 0xd9, 0x99, 0x2c, 0xa2, 0x73, 0x96, 0x85, 0x0e, 0xe3, 0xbb, 0xbb, 0xc3, 0x84, 0xe9, 0x80, 0xd1, + 0x9f, 0x8e, 0x5e, 0xc6, 0x19, 0xb4, 0xea, 0xa0, 0x29, 0x0f, 0x78, 0x43, 0x1d, 0xb0, 0x33, 0x07, 0xee, 0xbc, 0x6f, + 0x60, 0x8d, 0xf3, 0x9a, 0x3e, 0x90, 0x76, 0x1e, 0xa0, 0x80, 0x41, 0x3b, 0xf7, 0x0a, 0x06, 0x1a, 0x68, 0x6d, 0x93, + 0x5e, 0x6b, 0xe3, 0x01, 0xa0, 0x4f, 0x53, 0x9e, 0x18, 0x89, 0x61, 0x9d, 0xc8, 0xc1, 0x3c, 0x0a, 0xfe, 0x89, 0x61, + 0xed, 0x3b, 0x6f, 0xd7, 0x72, 0xd0, 0x8e, 0x82, 0xf7, 0x2b, 0xd5, 0x07, 0x09, 0x1c, 0xcf, 0xd1, 0x81, 0x9d, 0x21, + 0x15, 0xd0, 0x56, 0xa5, 0xab, 0x20, 0xf5, 0x86, 0xb5, 0x78, 0x7b, 0x62, 0xc9, 0xce, 0x7a, 0x49, 0x18, 0x5f, 0x59, + 0xd1, 0xc0, 0xff, 0x4f, 0xad, 0x54, 0x90, 0x3f, 0xd8, 0xf0, 0xb5, 0x50, 0xbe, 0x65, 0x75, 0x60, 0xef, 0x10, 0x25, + 0x45, 0xaa, 0xc3, 0xe0, 0x5b, 0xa0, 0x8f, 0x73, 0x38, 0x11, 0xca, 0x79, 0x19, 0xd6, 0xf3, 0xe2, 0x51, 0x1d, 0x32, + 0x58, 0xdb, 0x3e, 0x15, 0xd0, 0xbd, 0x1a, 0x20, 0x5b, 0x01, 0xd4, 0xdc, 0xa7, 0xfc, 0xb9, 0x4f, 0xeb, 0x33, 0xa8, + 0xf4, 0xa9, 0xc4, 0x7a, 0xc5, 0x54, 0x94, 0x36, 0xad, 0x33, 0xb2, 0xce, 0x07, 0x3a, 0x3c, 0x96, 0x65, 0xb5, 0xa1, + 0x6c, 0x4e, 0xb5, 0x7f, 0xbd, 0xda, 0x60, 0x6c, 0x73, 0xc1, 0xab, 0x10, 0x64, 0xa4, 0xdf, 0x6a, 0x2b, 0x92, 0x88, + 0x86, 0xcd, 0xea, 0x6c, 0x7e, 0x15, 0x06, 0x04, 0x18, 0x4e, 0xda, 0xcf, 0x9a, 0x38, 0x0f, 0xf1, 0x78, 0xd6, 0xe7, + 0x5b, 0xd1, 0x16, 0xa9, 0x36, 0xdf, 0x8a, 0x30, 0x24, 0x54, 0x54, 0x91, 0x46, 0xc9, 0x8c, 0x77, 0xdf, 0x26, 0x2f, + 0xac, 0x38, 0x4a, 0xc0, 0x57, 0x92, 0x41, 0x1a, 0x4d, 0x07, 0x22, 0xbc, 0xae, 0x36, 0x45, 0x41, 0xc0, 0xc3, 0x98, + 0x63, 0xae, 0x09, 0x09, 0x64, 0x79, 0xc6, 0x71, 0x16, 0xaa, 0xf8, 0x93, 0x42, 0xf6, 0x6e, 0xb4, 0x6b, 0x77, 0x1b, + 0xda, 0x39, 0xa9, 0xb2, 0xd6, 0x7e, 0x70, 0x8f, 0x5a, 0xe5, 0x2c, 0x20, 0xd6, 0xb4, 0xd2, 0x70, 0x94, 0xa3, 0x06, + 0x16, 0xa5, 0xc2, 0x26, 0xb6, 0xc8, 0x72, 0xd5, 0x39, 0x66, 0xc8, 0x80, 0xfe, 0x36, 0xbb, 0xde, 0x5c, 0x13, 0x80, + 0x5b, 0x4d, 0xac, 0x53, 0xc9, 0xfe, 0x25, 0xdd, 0x51, 0x17, 0x7b, 0x2a, 0xbb, 0x6c, 0x97, 0x2a, 0x7b, 0x1a, 0x53, + 0x9e, 0xba, 0x39, 0x1f, 0x71, 0x47, 0x46, 0xe1, 0x88, 0xb7, 0xde, 0x68, 0x66, 0x9d, 0x32, 0xd5, 0x00, 0x08, 0x74, + 0xa5, 0xce, 0xb0, 0xaf, 0x38, 0x62, 0xd4, 0x52, 0x89, 0xd1, 0xd4, 0x47, 0x19, 0xd5, 0x74, 0x56, 0x80, 0x7c, 0x3f, + 0xd8, 0xe1, 0x9f, 0xb0, 0x6a, 0xc9, 0x50, 0x0b, 0x18, 0x73, 0xa6, 0x89, 0x8c, 0x22, 0x1b, 0x54, 0x12, 0x57, 0x61, + 0x90, 0x48, 0x68, 0x02, 0xea, 0x25, 0xbe, 0x24, 0x55, 0x24, 0x35, 0xa0, 0x33, 0x5f, 0x5a, 0xd4, 0x9b, 0x4a, 0x0c, + 0xe6, 0x5e, 0xc5, 0x37, 0xe9, 0xeb, 0x17, 0xc6, 0xa8, 0xbe, 0x63, 0xad, 0x6f, 0xdd, 0x82, 0xbc, 0x02, 0x3e, 0x8f, + 0x1c, 0x98, 0x80, 0x01, 0x27, 0x63, 0x8c, 0xba, 0x95, 0xa8, 0x17, 0x6f, 0x25, 0x14, 0x8b, 0x98, 0xe0, 0xdd, 0xe3, + 0x29, 0x22, 0x86, 0x87, 0x85, 0xb2, 0xaa, 0xa6, 0xa7, 0x3a, 0xea, 0xdc, 0x83, 0x55, 0xe9, 0x62, 0x93, 0xa4, 0x1e, + 0xde, 0x23, 0xc1, 0xc7, 0xbc, 0xea, 0x2c, 0x3e, 0xc7, 0xe3, 0xa4, 0xf2, 0xf1, 0xda, 0x41, 0x44, 0xf0, 0xb3, 0x73, + 0x72, 0x7f, 0x2d, 0xc9, 0x03, 0x8c, 0x2c, 0x88, 0xdd, 0x28, 0xa8, 0xb0, 0xb2, 0xd6, 0xce, 0x15, 0x49, 0x7a, 0x56, + 0xa1, 0xed, 0x10, 0x5f, 0x4f, 0xe1, 0x2d, 0x54, 0xc2, 0xf7, 0xc3, 0xc8, 0x77, 0x08, 0x06, 0xb6, 0xdc, 0x01, 0x2a, + 0xfa, 0x19, 0x07, 0x0b, 0xed, 0x0c, 0x95, 0xcf, 0x2d, 0x39, 0x33, 0x84, 0x25, 0xa2, 0xd0, 0x18, 0x44, 0x1a, 0x5d, + 0x90, 0x3a, 0x07, 0x72, 0x55, 0xf1, 0x02, 0x68, 0x0c, 0xfa, 0x6d, 0xc6, 0x15, 0x65, 0x84, 0xa6, 0xa5, 0x57, 0x65, + 0x85, 0xb7, 0xdd, 0x39, 0xa7, 0xb6, 0x2d, 0x2a, 0xc8, 0x5e, 0xa1, 0xd5, 0x8b, 0x73, 0x47, 0x65, 0xbc, 0xbb, 0xc4, + 0x14, 0x02, 0xda, 0x8a, 0x36, 0xcd, 0xd0, 0xc2, 0xa7, 0x1e, 0xdf, 0xe6, 0x60, 0xa8, 0x23, 0x32, 0xee, 0x9f, 0x41, + 0x82, 0x6a, 0x9c, 0xb6, 0x7b, 0xbb, 0xbb, 0x03, 0xb1, 0xd7, 0xa4, 0x07, 0xb9, 0x7f, 0x18, 0x45, 0x90, 0x04, 0x85, + 0xf4, 0xad, 0x32, 0x2e, 0x39, 0xab, 0xa8, 0xfd, 0x2a, 0xa8, 0xcf, 0x12, 0x34, 0x1d, 0x91, 0x60, 0xb9, 0x48, 0xd9, + 0x29, 0xa0, 0x3b, 0x32, 0xb8, 0x05, 0x48, 0x01, 0x18, 0x56, 0x4f, 0x04, 0x92, 0x95, 0x0f, 0xef, 0xe1, 0x79, 0x66, + 0xc1, 0xc1, 0x6f, 0x22, 0x36, 0x77, 0x61, 0xeb, 0xd3, 0x95, 0x3f, 0x5b, 0x10, 0x03, 0xb1, 0xf1, 0x76, 0xd9, 0x22, + 0x4c, 0x58, 0x45, 0xb6, 0x22, 0xff, 0x48, 0x45, 0xb1, 0x24, 0x72, 0x2f, 0x51, 0x25, 0x3f, 0x28, 0xce, 0x16, 0x74, + 0x52, 0x2f, 0x5a, 0xf8, 0x8b, 0xa1, 0x27, 0xf1, 0x52, 0xae, 0xc5, 0x81, 0xaa, 0x03, 0x59, 0x0a, 0xbb, 0xea, 0xee, + 0x4e, 0x04, 0xab, 0x02, 0x16, 0x05, 0xbd, 0x2c, 0x68, 0x14, 0xff, 0x91, 0x5a, 0x61, 0xa7, 0x78, 0x7b, 0x04, 0x7a, + 0x44, 0xfd, 0x00, 0x5e, 0x83, 0x20, 0xf1, 0xac, 0x94, 0x10, 0x80, 0x64, 0x11, 0x72, 0xc1, 0x07, 0xa9, 0xe2, 0x86, + 0x7a, 0xca, 0x7f, 0xc5, 0xf5, 0x29, 0x67, 0x74, 0xf7, 0x9a, 0x51, 0xa0, 0x7c, 0x2d, 0x68, 0x63, 0xba, 0xcf, 0x46, + 0x0e, 0xcb, 0x23, 0xa5, 0x4d, 0xf4, 0xa4, 0x66, 0xd5, 0xc2, 0xa4, 0xe4, 0xbf, 0xd0, 0xc3, 0x27, 0xa9, 0x31, 0xa3, + 0xac, 0xa3, 0x74, 0x56, 0x9f, 0x16, 0xb3, 0xf1, 0xb8, 0xf6, 0x65, 0xcb, 0xb2, 0x78, 0x60, 0xf9, 0xf7, 0xa0, 0x14, + 0x62, 0x21, 0x23, 0x17, 0x93, 0x54, 0xe1, 0xe4, 0x77, 0x38, 0x39, 0xc8, 0xc4, 0x71, 0xac, 0x7d, 0x6e, 0xc1, 0xdf, + 0x80, 0x88, 0x90, 0x4f, 0xd2, 0x08, 0x4d, 0x06, 0xe1, 0xe3, 0xa8, 0x51, 0xba, 0x60, 0x09, 0xe9, 0x07, 0x90, 0x9d, + 0x96, 0x29, 0x50, 0x83, 0xc4, 0x54, 0xa0, 0x79, 0x07, 0xdd, 0x6b, 0xa0, 0xf5, 0x8c, 0xc9, 0xab, 0x7a, 0x0c, 0x34, + 0x5f, 0x78, 0x01, 0xd5, 0x63, 0x8d, 0xd9, 0xaa, 0x1d, 0x1b, 0x6c, 0xe6, 0x18, 0x5d, 0xd0, 0x45, 0x63, 0xab, 0xa8, + 0x86, 0x56, 0x81, 0xa1, 0x0b, 0x38, 0x2a, 0x4b, 0x98, 0x63, 0x83, 0xda, 0x7d, 0x47, 0x4b, 0x7b, 0xcf, 0x70, 0x74, + 0x49, 0x8e, 0x6d, 0xb3, 0x6c, 0x26, 0xf0, 0xec, 0x7c, 0x70, 0xd2, 0x54, 0x58, 0x1b, 0x92, 0xe7, 0xd5, 0xf9, 0xb5, + 0x7f, 0x48, 0x02, 0x8e, 0x7b, 0xa3, 0x9d, 0xa6, 0x54, 0xdc, 0x1b, 0xa3, 0xfc, 0x3a, 0x2b, 0xce, 0x25, 0x54, 0x8b, + 0x12, 0xb2, 0xec, 0xd6, 0x5a, 0x52, 0x52, 0x29, 0x17, 0xd0, 0x36, 0xcb, 0x42, 0x37, 0x11, 0x08, 0xf2, 0x47, 0xbf, + 0x50, 0xe9, 0x8c, 0x7f, 0x61, 0xc7, 0xc6, 0xda, 0xd4, 0x72, 0x60, 0x08, 0x0a, 0x6d, 0xba, 0xd9, 0xfb, 0x1a, 0xf2, + 0xc5, 0xb4, 0x3f, 0xe3, 0xc0, 0xba, 0xdf, 0x8e, 0xca, 0xfe, 0x5d, 0xb7, 0x45, 0x96, 0xb1, 0x10, 0xad, 0x14, 0xc4, + 0xcd, 0xc4, 0xbf, 0x14, 0x66, 0x92, 0x8b, 0x20, 0xa6, 0x9a, 0x04, 0xf7, 0x19, 0x8d, 0x14, 0xa0, 0x12, 0x24, 0xdd, + 0xb0, 0x23, 0x9a, 0x82, 0x5b, 0x93, 0x56, 0x28, 0x6f, 0x3d, 0x6c, 0xa1, 0x65, 0x1a, 0xee, 0xdb, 0x0f, 0xc2, 0xbc, + 0x32, 0x10, 0x40, 0x27, 0x23, 0x7a, 0x6b, 0xfd, 0xa6, 0x8e, 0x10, 0x9b, 0xb0, 0x24, 0x42, 0x58, 0x2c, 0x53, 0x7c, + 0x20, 0x8a, 0x3b, 0xf7, 0xb6, 0xe9, 0x23, 0xd1, 0x5f, 0x5a, 0xb3, 0x76, 0xca, 0xaa, 0xb5, 0xed, 0xa1, 0xe2, 0xf3, + 0x99, 0x1b, 0x07, 0x31, 0xe1, 0x6a, 0xec, 0x12, 0xa9, 0xad, 0xb5, 0x42, 0x44, 0xe6, 0xa1, 0xef, 0x1c, 0x1d, 0xb9, + 0xd9, 0x72, 0x24, 0x54, 0x76, 0x67, 0x88, 0xf4, 0x49, 0x68, 0x3f, 0xb4, 0x49, 0x14, 0x0b, 0x3d, 0x7b, 0x5c, 0x5a, + 0x17, 0x2f, 0xaf, 0x4b, 0x8d, 0x82, 0x86, 0x18, 0x2a, 0xfd, 0x0d, 0x5c, 0xdf, 0xaf, 0xbc, 0xfe, 0xa2, 0x0e, 0x3c, + 0xb9, 0x7b, 0x1e, 0x5a, 0x14, 0x70, 0x0e, 0x5a, 0x03, 0x4c, 0xd5, 0x81, 0xe0, 0x20, 0xf1, 0x98, 0xe4, 0x51, 0x43, + 0x26, 0x3b, 0xdf, 0x1a, 0xe4, 0x4c, 0x29, 0xcf, 0xc8, 0x04, 0xb8, 0xec, 0xfa, 0x2b, 0xf2, 0x75, 0x69, 0xaa, 0x45, + 0x14, 0xd7, 0x43, 0x5a, 0x8b, 0xe2, 0x69, 0x57, 0x71, 0x91, 0x7e, 0xa5, 0xe2, 0x42, 0x3b, 0x58, 0x0f, 0xc8, 0x94, + 0x87, 0x85, 0xa5, 0xca, 0xd4, 0x36, 0xb8, 0x1b, 0x87, 0x31, 0x51, 0xf6, 0xbb, 0xab, 0x34, 0xf9, 0x8d, 0x58, 0xf0, + 0x67, 0xb0, 0xce, 0x81, 0xf9, 0x35, 0xaf, 0x38, 0xff, 0x2b, 0x5b, 0xb4, 0xd5, 0xef, 0xb4, 0xf9, 0xa7, 0x65, 0x3d, + 0x3c, 0x38, 0x4c, 0x76, 0xac, 0x4e, 0x60, 0xe2, 0xae, 0xcb, 0x45, 0x8a, 0xc8, 0x00, 0xda, 0x22, 0x99, 0x0c, 0xf8, + 0xc8, 0xca, 0xb2, 0xeb, 0x3b, 0xcd, 0x02, 0xc2, 0x5e, 0x02, 0x37, 0xdb, 0xff, 0x35, 0x35, 0x73, 0xf2, 0x55, 0x5f, + 0xe8, 0xd2, 0xb1, 0xb6, 0x2c, 0x65, 0x8c, 0xf7, 0xbd, 0x27, 0xd9, 0x2c, 0x3f, 0x85, 0xff, 0x35, 0x75, 0xdb, 0x99, + 0x65, 0x83, 0x68, 0x88, 0x43, 0x6b, 0x2b, 0x47, 0xbc, 0xdc, 0x43, 0x8c, 0xe6, 0xab, 0x55, 0xe8, 0x0b, 0x56, 0xa1, + 0xcf, 0x16, 0x6e, 0x1f, 0xf4, 0xaa, 0xda, 0x38, 0x21, 0x8f, 0xc6, 0x0b, 0x61, 0xe4, 0xdf, 0xc2, 0x1a, 0x58, 0xe6, + 0xe5, 0x27, 0x84, 0x43, 0x86, 0x65, 0xa9, 0xce, 0x5f, 0x74, 0xe7, 0x27, 0xc7, 0x71, 0x38, 0x28, 0x72, 0x8a, 0xdb, + 0x4a, 0x48, 0xc9, 0x92, 0x38, 0x47, 0x3c, 0x64, 0x7b, 0xd2, 0x24, 0xb4, 0x6b, 0x45, 0xe1, 0x7d, 0x91, 0xbb, 0xca, + 0x61, 0x53, 0xe4, 0x7a, 0xd1, 0xbb, 0x33, 0x2c, 0x1d, 0xa9, 0xb5, 0x8d, 0xc5, 0x90, 0x0c, 0xd6, 0x99, 0x41, 0x5d, + 0x05, 0xeb, 0x87, 0xcc, 0x49, 0xfb, 0x19, 0x4e, 0xd9, 0x8b, 0x6c, 0x71, 0xab, 0xad, 0xf2, 0x77, 0xa2, 0xc4, 0x01, + 0x16, 0xd2, 0xa8, 0x6f, 0xc2, 0x44, 0xce, 0xcf, 0x44, 0xb9, 0x81, 0x60, 0xea, 0x2b, 0x8a, 0xaf, 0x51, 0x01, 0x25, + 0x02, 0x71, 0x20, 0x8c, 0xf5, 0x89, 0xea, 0x2c, 0x47, 0xbc, 0x13, 0x22, 0xbc, 0x03, 0x60, 0xef, 0x58, 0x70, 0x08, + 0x1c, 0x96, 0xbe, 0xed, 0xac, 0x73, 0x45, 0x28, 0x94, 0x13, 0x18, 0x33, 0x48, 0x7c, 0xd6, 0x69, 0x26, 0xa8, 0xd1, + 0x63, 0x32, 0x28, 0x0f, 0x04, 0xfd, 0xb5, 0xa8, 0xaa, 0x6f, 0x07, 0x77, 0xd0, 0x3e, 0xae, 0x5f, 0x6e, 0x91, 0x1d, + 0xfe, 0xbc, 0xa3, 0xc2, 0xf2, 0xf1, 0xac, 0x55, 0xf9, 0x9a, 0x29, 0x0d, 0x0c, 0xb0, 0x3e, 0xde, 0x61, 0xce, 0x36, + 0xdd, 0x77, 0x77, 0x87, 0x87, 0x7b, 0x55, 0x5c, 0x70, 0x62, 0x36, 0x96, 0x64, 0xae, 0x65, 0xaa, 0x4d, 0xd1, 0x97, + 0xb4, 0xed, 0x14, 0x3d, 0x6a, 0x9d, 0xdd, 0xae, 0x38, 0x21, 0x47, 0xbf, 0x05, 0xb3, 0xcf, 0x2a, 0x24, 0x65, 0xd0, + 0x8e, 0xa1, 0xad, 0x8b, 0x0c, 0x25, 0xca, 0x17, 0x46, 0xe9, 0xc4, 0x21, 0x57, 0xcd, 0x75, 0xc1, 0x0e, 0xb8, 0xa9, + 0x55, 0xb9, 0x08, 0x81, 0xe7, 0x40, 0x9b, 0xf3, 0x10, 0x78, 0xfb, 0x72, 0x03, 0x2b, 0xa1, 0x6c, 0xe9, 0x5e, 0x54, + 0xdf, 0x18, 0x10, 0x20, 0xd3, 0x85, 0xeb, 0x41, 0x35, 0x9a, 0x4f, 0xca, 0xb0, 0x9c, 0xbd, 0x44, 0x4b, 0x7b, 0xe2, + 0x58, 0xdb, 0x7d, 0xdf, 0xec, 0xf0, 0xad, 0x96, 0x12, 0x8c, 0x31, 0x7b, 0x30, 0x22, 0x9c, 0x6b, 0x0c, 0x39, 0x1b, + 0x52, 0x97, 0xb9, 0x6e, 0x09, 0x7b, 0xb8, 0x5d, 0xe0, 0xdc, 0xcc, 0x3c, 0x09, 0x37, 0x07, 0xfc, 0x77, 0x75, 0x76, + 0x8c, 0xb7, 0x5f, 0x25, 0x8b, 0x5d, 0xc2, 0xad, 0xc7, 0x63, 0xd8, 0x17, 0xe3, 0x4a, 0xf1, 0xaf, 0x27, 0xca, 0xc3, + 0x33, 0x18, 0xf9, 0x44, 0xca, 0x0b, 0x60, 0x57, 0x2d, 0xc3, 0xf7, 0x93, 0x59, 0x79, 0x9a, 0x92, 0xc5, 0x3b, 0xb6, + 0x3a, 0xc7, 0x80, 0x5e, 0x85, 0x57, 0xfa, 0xea, 0xaa, 0x50, 0xa9, 0xa0, 0xac, 0x5b, 0xfe, 0x9a, 0x3f, 0x47, 0x80, + 0x42, 0xe2, 0x2b, 0x8a, 0x75, 0xab, 0x44, 0x4f, 0x0d, 0x7f, 0x79, 0x76, 0x72, 0x2e, 0x33, 0x30, 0x2e, 0xcf, 0x1e, + 0x9f, 0xcb, 0x2c, 0xc0, 0xef, 0x3f, 0x9d, 0xb7, 0x66, 0x1d, 0x08, 0x01, 0xb1, 0x5c, 0x38, 0x06, 0x29, 0x2d, 0x67, + 0x03, 0xaa, 0x70, 0x1f, 0x41, 0xff, 0x87, 0x3e, 0x04, 0x8d, 0x5e, 0xa8, 0xa7, 0x37, 0x08, 0xba, 0x21, 0x09, 0xb4, + 0x88, 0x19, 0x14, 0x2a, 0x16, 0xd1, 0x69, 0x74, 0x8c, 0x86, 0xd8, 0x5c, 0x00, 0x1e, 0x66, 0x36, 0x09, 0x42, 0x46, + 0xf7, 0x95, 0x02, 0x07, 0xbf, 0x25, 0x51, 0x1a, 0x64, 0x73, 0x72, 0xd3, 0x37, 0xb2, 0xa1, 0x25, 0xb8, 0xa2, 0x53, + 0xb5, 0x91, 0x93, 0xc8, 0x4d, 0xa6, 0x1b, 0xab, 0x57, 0x11, 0x37, 0xe2, 0x57, 0xa6, 0xd3, 0xa9, 0x4e, 0x81, 0x0d, + 0xa3, 0x38, 0x07, 0x37, 0xa7, 0xe6, 0xf2, 0x59, 0xea, 0xd9, 0xa1, 0x0e, 0x73, 0x71, 0x1b, 0x95, 0xed, 0x3d, 0x94, + 0x55, 0xc6, 0x50, 0x0f, 0xbd, 0x45, 0x8e, 0xef, 0x1f, 0x7c, 0x95, 0xf1, 0x0b, 0x87, 0xeb, 0x21, 0xcd, 0x85, 0xed, + 0xb2, 0xa6, 0x74, 0x0e, 0x83, 0x67, 0x5f, 0x4a, 0x72, 0x58, 0xea, 0x7f, 0x99, 0x85, 0xb2, 0xc6, 0x6b, 0xf6, 0x5f, + 0xaa, 0xdd, 0xed, 0x30, 0x50, 0xb7, 0x35, 0xae, 0xb8, 0x79, 0xe3, 0x29, 0x7e, 0x96, 0x78, 0x63, 0x10, 0xb6, 0xfc, + 0xb0, 0x19, 0x3e, 0xef, 0x75, 0xc4, 0xd2, 0x81, 0x71, 0x40, 0x70, 0xa2, 0xce, 0x17, 0xfa, 0xda, 0xb8, 0x4e, 0x07, + 0x29, 0x22, 0x25, 0x6e, 0x95, 0x18, 0xe8, 0x14, 0x9d, 0xe4, 0xa8, 0x2c, 0xc6, 0xea, 0xd2, 0xce, 0xb0, 0xde, 0xc3, + 0x7e, 0x48, 0x85, 0xaa, 0x39, 0x35, 0xcf, 0x00, 0x22, 0x09, 0xd8, 0xa3, 0x27, 0x4d, 0xa3, 0xcb, 0x36, 0xcb, 0x43, + 0x4b, 0xdf, 0x15, 0xdc, 0xd3, 0x53, 0x53, 0x33, 0x32, 0xae, 0x7c, 0xec, 0xed, 0xf6, 0xb7, 0x47, 0xae, 0xc8, 0x7b, + 0x9b, 0x54, 0x35, 0x0b, 0x21, 0x45, 0xe3, 0xda, 0x56, 0x7a, 0x1a, 0x05, 0xda, 0x61, 0x57, 0x2b, 0x0a, 0x1b, 0x05, + 0xd5, 0xa8, 0xb0, 0x8b, 0xf8, 0x39, 0x34, 0x90, 0x2b, 0xb0, 0x6d, 0xfe, 0x59, 0x7b, 0x3b, 0x5b, 0x91, 0xe1, 0x0b, + 0x5c, 0x8b, 0x84, 0x22, 0x32, 0xbc, 0x68, 0x57, 0xab, 0xaa, 0x4e, 0x9a, 0xae, 0x06, 0x5e, 0x45, 0x06, 0xec, 0xe6, + 0x9f, 0x39, 0x2a, 0xd7, 0xc2, 0x04, 0x0c, 0xef, 0xa9, 0x6b, 0x51, 0x75, 0xd3, 0xaa, 0xef, 0x3a, 0x76, 0x88, 0x86, + 0xa6, 0x58, 0x74, 0xc8, 0x3c, 0x0f, 0xcf, 0x2d, 0x54, 0xf9, 0x05, 0x72, 0xe7, 0x3a, 0x7b, 0x31, 0x61, 0x60, 0x29, + 0x1b, 0x29, 0xd6, 0xa9, 0x51, 0x14, 0x50, 0x00, 0x97, 0xcf, 0x48, 0xc7, 0xc6, 0xe3, 0xc6, 0xa7, 0x27, 0xc6, 0xb6, + 0x71, 0xc8, 0x9f, 0x6f, 0x49, 0xe8, 0xf8, 0x5a, 0x93, 0x07, 0xdf, 0xaa, 0xec, 0x0b, 0x35, 0xec, 0x5f, 0x50, 0xd8, + 0x1d, 0xf6, 0x72, 0x65, 0x5c, 0x15, 0x07, 0x50, 0xa5, 0xe7, 0xb9, 0xe6, 0x6a, 0x5a, 0xd0, 0x4a, 0x89, 0x65, 0x7e, + 0x15, 0x1c, 0xb7, 0x8e, 0x38, 0x7c, 0xab, 0x34, 0xaa, 0x3e, 0x2d, 0x49, 0xa5, 0xa3, 0xcf, 0xf6, 0x14, 0xad, 0x01, + 0xe8, 0x14, 0xd6, 0x82, 0xb3, 0x8f, 0x3e, 0x77, 0xe2, 0xb2, 0xa5, 0x84, 0xc0, 0xa2, 0xbd, 0x1f, 0xe7, 0xa5, 0xe7, + 0xcb, 0x3d, 0xd0, 0xf6, 0x43, 0xf2, 0x46, 0x74, 0xc6, 0xeb, 0xeb, 0xa8, 0xe9, 0x57, 0xcf, 0x70, 0xa3, 0x29, 0xc8, + 0x3d, 0x4d, 0xb5, 0x08, 0xa3, 0x41, 0x60, 0x9a, 0xa5, 0x4f, 0x60, 0xd2, 0x27, 0x13, 0x45, 0x06, 0xad, 0x66, 0x52, + 0x28, 0xb0, 0xaf, 0xa0, 0x75, 0x6a, 0xe2, 0x9c, 0xa2, 0x5d, 0x11, 0xb4, 0xb9, 0x25, 0x9d, 0xdc, 0x05, 0x1a, 0x3e, + 0x00, 0x5d, 0x63, 0x98, 0x2a, 0x92, 0x10, 0x61, 0x96, 0x3e, 0xaf, 0xb4, 0xc3, 0xbe, 0x5e, 0x28, 0x20, 0x91, 0x30, + 0xf1, 0x6b, 0x14, 0xf1, 0x63, 0x71, 0xe6, 0x0f, 0xd3, 0x3e, 0x1e, 0xc4, 0xca, 0x90, 0x18, 0x42, 0xfc, 0x4a, 0x03, + 0xb6, 0x9d, 0x28, 0x38, 0x36, 0x1e, 0xb9, 0xd6, 0x1d, 0x87, 0x25, 0x87, 0xa1, 0x2c, 0x04, 0x4f, 0x8d, 0xf6, 0x7d, + 0x21, 0xe1, 0xeb, 0x28, 0x8b, 0xd9, 0xac, 0x90, 0x97, 0x31, 0x4e, 0x0b, 0x15, 0xa7, 0xb2, 0x5c, 0x43, 0x5e, 0x0c, + 0x94, 0xa7, 0x2b, 0xc3, 0x00, 0x93, 0x94, 0xa4, 0x6c, 0x2d, 0x0a, 0x15, 0xc6, 0xce, 0x54, 0x26, 0xd4, 0xa5, 0x94, + 0x37, 0x63, 0x95, 0x85, 0x0c, 0xf9, 0x2d, 0x1a, 0x2d, 0x54, 0x8d, 0x01, 0xc3, 0x52, 0xd2, 0x6a, 0xfc, 0x21, 0x42, + 0xad, 0x86, 0x01, 0x81, 0x6d, 0xde, 0x01, 0xbf, 0x07, 0x07, 0x1a, 0x41, 0xa0, 0x19, 0xfb, 0xa9, 0xb8, 0xe9, 0xcd, + 0x82, 0xba, 0x68, 0xd7, 0x22, 0x9f, 0x0d, 0x9c, 0x50, 0x3f, 0x65, 0x01, 0xea, 0x65, 0x59, 0xbd, 0x29, 0x17, 0x69, + 0x27, 0x44, 0x26, 0x70, 0x8e, 0xdf, 0xe5, 0x53, 0x3c, 0xaf, 0xc8, 0xa9, 0x5c, 0x6d, 0x13, 0x36, 0x4b, 0x2a, 0x81, + 0xfd, 0x51, 0x36, 0x2f, 0xa3, 0x79, 0x29, 0xcc, 0x18, 0x95, 0xc6, 0x38, 0x63, 0xbd, 0x93, 0x70, 0xb7, 0x9f, 0x07, + 0xc6, 0x28, 0xe5, 0x45, 0x47, 0x35, 0xac, 0xed, 0x78, 0x2d, 0xbd, 0x26, 0xca, 0xc3, 0x4a, 0xad, 0x09, 0xc3, 0x9f, + 0x8a, 0xb9, 0x91, 0xf6, 0xa3, 0x45, 0x1e, 0x2c, 0x62, 0xeb, 0x4f, 0x24, 0xd3, 0xac, 0x65, 0x05, 0x3e, 0x4e, 0x8a, + 0x70, 0xa2, 0x25, 0x7d, 0xd3, 0x33, 0xcb, 0x22, 0xfc, 0x5b, 0xfc, 0x9e, 0xf8, 0x61, 0x6b, 0x3f, 0x30, 0xd6, 0x20, + 0x1b, 0x71, 0x29, 0x55, 0x9e, 0x3a, 0xd0, 0x55, 0x93, 0xe0, 0x50, 0xbf, 0x58, 0xc7, 0x55, 0x9d, 0x2e, 0xf0, 0xa3, + 0x42, 0xdd, 0xc2, 0xc3, 0x13, 0xb4, 0x37, 0x24, 0x92, 0xa4, 0x2d, 0x8d, 0xb5, 0xda, 0xc5, 0x21, 0x3d, 0x7b, 0x23, + 0x2e, 0xbd, 0x6c, 0xc8, 0x90, 0x36, 0xb0, 0xce, 0x42, 0xd6, 0xf9, 0x33, 0xff, 0x39, 0x6a, 0x19, 0x74, 0xd4, 0xa5, + 0x18, 0xcf, 0x91, 0x11, 0xef, 0x07, 0xb3, 0xba, 0x87, 0xb8, 0x08, 0x41, 0x69, 0x7b, 0x6a, 0xc7, 0x2f, 0x4d, 0x1e, + 0xc9, 0x42, 0xc6, 0x19, 0x7c, 0x76, 0x3f, 0x4b, 0xd4, 0x59, 0x47, 0xc5, 0x94, 0x67, 0x80, 0x4c, 0x07, 0x4b, 0x38, + 0x50, 0x61, 0x35, 0x1d, 0xaa, 0xc4, 0xf0, 0x30, 0x95, 0x5f, 0x78, 0x43, 0x6d, 0x37, 0x2b, 0x03, 0x99, 0x64, 0xfb, + 0x35, 0x1c, 0xd8, 0x4c, 0x7f, 0xe0, 0x30, 0x6d, 0x9b, 0xf2, 0xea, 0x8a, 0xbb, 0x6d, 0x57, 0xa2, 0xf4, 0x74, 0x8e, + 0x58, 0x0a, 0xfd, 0x8a, 0x0e, 0x87, 0xd3, 0xd5, 0xe2, 0x76, 0xeb, 0x70, 0x10, 0xd6, 0x7a, 0x45, 0x84, 0x3f, 0x73, + 0xdb, 0x6e, 0xe3, 0x2d, 0x14, 0xf3, 0x11, 0x42, 0xa4, 0x8f, 0xc2, 0x11, 0x94, 0x05, 0x6e, 0xac, 0xdc, 0xc7, 0x51, + 0x56, 0x7c, 0x2f, 0xa7, 0x19, 0x3f, 0x31, 0x8d, 0xd5, 0xce, 0x0a, 0xb5, 0xa7, 0x6d, 0x74, 0x67, 0xeb, 0x8c, 0xb0, + 0xfd, 0x4a, 0x9a, 0x2d, 0xc4, 0xfd, 0x51, 0x43, 0x21, 0xd0, 0x59, 0x4a, 0x9b, 0xa8, 0xf8, 0xae, 0x3d, 0x83, 0x4c, + 0xea, 0x64, 0xc9, 0x5b, 0x06, 0x3b, 0x39, 0x6b, 0xc3, 0x42, 0xc9, 0x21, 0xf2, 0x2a, 0x46, 0x8f, 0x72, 0x9b, 0xd7, + 0x26, 0x27, 0xd3, 0x3a, 0x14, 0x77, 0x7e, 0xbf, 0x5d, 0x65, 0x0b, 0xb9, 0xc3, 0xb6, 0x19, 0xf6, 0xce, 0x98, 0xc0, + 0xc1, 0xd8, 0xe2, 0x48, 0x7c, 0x39, 0xa3, 0x20, 0x04, 0x74, 0xf5, 0xf8, 0x3d, 0x3e, 0x43, 0xd1, 0x14, 0x5c, 0xaa, + 0xb4, 0x85, 0xcd, 0xf0, 0xb9, 0x4f, 0xfa, 0x5a, 0x00, 0x83, 0x59, 0xd9, 0xf7, 0x2e, 0x56, 0x0d, 0xed, 0x85, 0x98, + 0x01, 0x10, 0x0b, 0x1a, 0xa4, 0x86, 0x9f, 0xe2, 0x74, 0xb4, 0x44, 0x11, 0x7b, 0x39, 0x91, 0xe8, 0x80, 0x8b, 0xb9, + 0x47, 0xf2, 0x47, 0xb6, 0x8b, 0xf8, 0xad, 0xbd, 0xf7, 0x12, 0x0d, 0x70, 0xbd, 0xaa, 0x59, 0xf7, 0xf0, 0xe5, 0x0a, + 0x4d, 0x42, 0x29, 0xca, 0xd8, 0x14, 0x40, 0xe1, 0xa6, 0xaa, 0x0b, 0x26, 0x66, 0xbc, 0xb8, 0xa5, 0x96, 0x66, 0xd9, + 0xf5, 0xc1, 0x69, 0xf6, 0x28, 0x7a, 0x6e, 0xd9, 0xf9, 0xa2, 0x63, 0xbf, 0x56, 0xa5, 0xe4, 0xe4, 0xaa, 0x88, 0x9a, + 0x7a, 0x2f, 0x76, 0x64, 0x44, 0xb9, 0x14, 0x03, 0x11, 0xb1, 0x63, 0x9e, 0x8c, 0xad, 0x65, 0x44, 0x74, 0xcf, 0xd9, + 0xa7, 0xba, 0x05, 0x9b, 0x17, 0x11, 0x1d, 0xff, 0xfa, 0xe7, 0xd7, 0xd7, 0xf1, 0x95, 0x42, 0x51, 0x72, 0x2c, 0x62, + 0xd8, 0xb4, 0xaf, 0x29, 0x71, 0xf0, 0x7e, 0x78, 0xff, 0x1d, 0x67, 0x69, 0xed, 0x1e, 0xec, 0xb4, 0xaa, 0xea, 0x87, + 0x3a, 0xad, 0x5c, 0x05, 0xd6, 0x3e, 0x4b, 0x14, 0xcc, 0xfd, 0x5e, 0xa7, 0xa9, 0x32, 0xa1, 0x23, 0xd9, 0x44, 0xc0, + 0xc6, 0x74, 0x6b, 0xed, 0xe8, 0x8e, 0xb4, 0x46, 0x4e, 0x2d, 0x06, 0x35, 0x7e, 0x70, 0xf4, 0x09, 0x4a, 0xc3, 0x8e, + 0xae, 0x52, 0xa9, 0xbc, 0x52, 0xc1, 0xf1, 0xb1, 0xca, 0x18, 0xd3, 0x88, 0xd9, 0x56, 0xb5, 0x02, 0xea, 0xc0, 0x97, + 0xb6, 0x2a, 0x20, 0xdb, 0x4f, 0x51, 0x15, 0xa8, 0xdf, 0x3f, 0x2b, 0x43, 0x3e, 0x57, 0x0b, 0x5a, 0xfa, 0x68, 0x67, + 0xe0, 0xf4, 0x94, 0x95, 0x81, 0x01, 0x2a, 0x9e, 0x10, 0xe5, 0xcb, 0xe7, 0xb1, 0xea, 0xf7, 0xd5, 0x5c, 0x63, 0x74, + 0x15, 0x84, 0x1a, 0xd2, 0x63, 0xc8, 0x3e, 0x6e, 0xa7, 0x1d, 0x48, 0x2c, 0x38, 0xc1, 0x6e, 0xae, 0xa1, 0xd1, 0x48, + 0x82, 0xfc, 0xbe, 0xd1, 0xc0, 0xd7, 0x30, 0x1a, 0xc9, 0x79, 0x94, 0xd3, 0x68, 0x48, 0x76, 0x4c, 0x21, 0xc4, 0x06, + 0x51, 0xf6, 0xed, 0x29, 0xa8, 0xf6, 0x35, 0xe4, 0xf6, 0x5b, 0xe8, 0xbb, 0x2e, 0x6e, 0x96, 0x90, 0xb6, 0xea, 0x60, + 0x2b, 0x0f, 0x74, 0xbc, 0x60, 0x9d, 0xbf, 0xc1, 0xa4, 0x26, 0x6d, 0x8c, 0xa7, 0x8c, 0x58, 0xd0, 0x92, 0xa0, 0xbb, + 0x1e, 0x02, 0xbb, 0x0e, 0x3f, 0x28, 0x8c, 0xe9, 0x49, 0x49, 0x4f, 0x8b, 0x94, 0x8b, 0x82, 0x9c, 0x32, 0xab, 0x22, + 0x03, 0x7a, 0x69, 0x5b, 0xf9, 0xd5, 0x4e, 0xa1, 0xfe, 0xde, 0x55, 0x02, 0xeb, 0x31, 0xc6, 0xc5, 0x2e, 0xec, 0xe6, + 0xb4, 0x01, 0x02, 0x1b, 0x3e, 0x95, 0xba, 0x6c, 0xa3, 0x26, 0x7f, 0x1e, 0xc3, 0xea, 0x45, 0xcd, 0xb6, 0xbb, 0xb9, + 0x95, 0x62, 0xdb, 0x5a, 0xa8, 0xce, 0xf5, 0x97, 0xb5, 0xdd, 0xf7, 0x0c, 0x6f, 0x6a, 0xe9, 0xbd, 0x5d, 0x1b, 0xca, + 0x57, 0xfb, 0x57, 0xc9, 0x7f, 0xeb, 0x23, 0xfb, 0xad, 0x32, 0xdb, 0xae, 0x7a, 0x7f, 0xe8, 0xb8, 0x4d, 0x29, 0x1a, + 0x04, 0x7d, 0x69, 0xa4, 0x20, 0x3d, 0x83, 0x38, 0x48, 0x24, 0xba, 0x32, 0x70, 0x24, 0x42, 0xab, 0x47, 0x64, 0x0e, + 0x33, 0x78, 0x1e, 0x23, 0x70, 0x80, 0x7d, 0xe4, 0xf9, 0x81, 0x7d, 0x3a, 0x9f, 0x8d, 0x2e, 0x46, 0xe3, 0x7a, 0x3c, + 0x92, 0x22, 0xa6, 0x39, 0xa3, 0x73, 0xbc, 0x73, 0x8b, 0x23, 0x8c, 0x3b, 0xa9, 0xcd, 0x1c, 0xe2, 0xd3, 0x04, 0x4e, + 0x82, 0x18, 0xa8, 0x5a, 0x84, 0xce, 0xd2, 0xda, 0x39, 0xa8, 0x92, 0x05, 0xd9, 0xf9, 0x76, 0xe5, 0x7e, 0xd8, 0xfa, + 0xec, 0x2c, 0x3f, 0x3a, 0xca, 0xcf, 0xe0, 0xbb, 0xce, 0x07, 0x2b, 0xe5, 0xfd, 0x17, 0xb8, 0x25, 0xd5, 0x9d, 0xb4, + 0x4f, 0x11, 0x6b, 0x9f, 0xd2, 0xf5, 0x8a, 0x75, 0x33, 0xea, 0x48, 0xc7, 0x7c, 0xf9, 0x82, 0xaa, 0x78, 0x64, 0xc8, + 0x3a, 0x79, 0x7b, 0x07, 0xaf, 0xc9, 0x4d, 0x92, 0x23, 0x29, 0xb0, 0x5d, 0x65, 0x5c, 0x29, 0x5c, 0x74, 0xd5, 0xfa, + 0x96, 0xc1, 0xce, 0x50, 0x6f, 0x4b, 0xb2, 0x1a, 0x3f, 0x8c, 0xfb, 0x66, 0xe3, 0xdf, 0x97, 0x07, 0x52, 0xe7, 0xc1, + 0x12, 0x11, 0x67, 0x41, 0x0a, 0x3a, 0xa0, 0x5a, 0x0f, 0x46, 0x63, 0x8d, 0x1c, 0xd2, 0xfd, 0xac, 0x0c, 0x45, 0xc8, + 0xe7, 0x31, 0x1a, 0x30, 0xa9, 0x86, 0x00, 0xd5, 0x3a, 0x8c, 0x19, 0xd2, 0x46, 0x5b, 0x0b, 0x88, 0xe1, 0x70, 0xd1, + 0xb3, 0x1b, 0x36, 0xa7, 0xdb, 0xc0, 0x85, 0x12, 0x05, 0xb2, 0x6e, 0xdd, 0x43, 0xcd, 0xf1, 0x44, 0x90, 0x41, 0x55, + 0xb7, 0x9f, 0x16, 0xca, 0x93, 0x78, 0x2c, 0xa0, 0xa0, 0x47, 0x2f, 0xbf, 0x2d, 0x48, 0xd0, 0xee, 0x21, 0xcf, 0xa9, + 0xa2, 0xec, 0x83, 0xe8, 0xb8, 0x65, 0xd8, 0xd0, 0x3e, 0xb6, 0x15, 0xc8, 0x49, 0x3b, 0xd0, 0xd4, 0xce, 0xee, 0x70, + 0x8e, 0x09, 0xf2, 0x9b, 0x32, 0x94, 0x32, 0x51, 0x5f, 0x59, 0x45, 0x4f, 0x0e, 0x73, 0xf6, 0x86, 0x5a, 0x44, 0x4f, + 0x56, 0x5d, 0xce, 0x6f, 0xe1, 0x24, 0x1c, 0x1d, 0x89, 0x3b, 0x10, 0xbd, 0xe1, 0xf5, 0x46, 0xbc, 0xac, 0x47, 0x58, + 0x0e, 0x71, 0x84, 0x8e, 0x17, 0x25, 0xbb, 0x76, 0x57, 0xee, 0x55, 0x5d, 0x6f, 0x2b, 0x57, 0x41, 0x4d, 0x52, 0x29, + 0xb2, 0x8a, 0x81, 0xb9, 0xd7, 0xe3, 0xc4, 0x7d, 0x85, 0x62, 0x5d, 0x08, 0xd9, 0x46, 0xe7, 0x68, 0x74, 0xa4, 0x88, + 0x1d, 0xbd, 0x02, 0xb6, 0xa9, 0x4a, 0x11, 0x82, 0xbb, 0xab, 0xa9, 0x85, 0x65, 0xa2, 0x11, 0xf7, 0x99, 0x07, 0xe8, + 0xca, 0xe2, 0x84, 0x53, 0x81, 0xb6, 0x75, 0x9d, 0x73, 0x56, 0xf4, 0x80, 0x6e, 0xa2, 0xe5, 0x9f, 0xd6, 0x4c, 0x8b, + 0x18, 0x57, 0xd9, 0x94, 0xad, 0xd0, 0xee, 0xd5, 0x2e, 0xd1, 0xe2, 0x1b, 0x91, 0xb0, 0xbd, 0xff, 0xb2, 0xfb, 0x62, + 0x45, 0xfd, 0xa3, 0xbc, 0x3c, 0xc1, 0x53, 0xab, 0xf1, 0x5a, 0x0e, 0x2b, 0xbe, 0x56, 0x0b, 0x61, 0x7d, 0x34, 0xf0, + 0x4a, 0x04, 0x06, 0x49, 0xe8, 0x86, 0x6b, 0xd1, 0x35, 0x83, 0x64, 0x63, 0xd8, 0xfe, 0xe7, 0xf5, 0xfd, 0x9f, 0x5c, + 0xe1, 0xd2, 0x25, 0x8b, 0x32, 0x09, 0x34, 0x4e, 0xb5, 0xa9, 0x0a, 0x2c, 0x78, 0xd1, 0x27, 0x47, 0x78, 0x61, 0x57, + 0x20, 0x37, 0x94, 0x44, 0x3f, 0x93, 0x34, 0x94, 0x47, 0xdf, 0x6b, 0xcd, 0x93, 0xd9, 0x97, 0x90, 0x27, 0x01, 0xc9, + 0x4f, 0xef, 0xdf, 0xce, 0x86, 0x7d, 0x0d, 0x12, 0x51, 0x59, 0xd0, 0xe2, 0x08, 0xce, 0x10, 0xec, 0x0f, 0x73, 0x29, + 0x9b, 0xcf, 0xe4, 0xe8, 0x88, 0xdf, 0x3f, 0xcf, 0xb3, 0xe4, 0xb7, 0xde, 0x7b, 0xc5, 0xd3, 0xac, 0x22, 0xa4, 0x12, + 0x71, 0xa0, 0x5d, 0x15, 0xbd, 0x95, 0xb8, 0x18, 0x3b, 0x24, 0x23, 0xde, 0x4b, 0x1d, 0x62, 0xc2, 0xf8, 0xf2, 0x7b, + 0x24, 0x25, 0x0f, 0x56, 0xed, 0x60, 0xcf, 0x45, 0x35, 0x43, 0xc6, 0x70, 0x3d, 0xef, 0x25, 0x59, 0x01, 0x1c, 0x20, + 0xfa, 0x50, 0x03, 0xd7, 0xa4, 0xee, 0x92, 0x70, 0xb6, 0xf4, 0xac, 0xa3, 0x1a, 0xd8, 0xab, 0x13, 0x2a, 0x79, 0xe3, + 0x20, 0x8b, 0xd8, 0xb6, 0xbf, 0x79, 0x15, 0x02, 0xb5, 0x2a, 0xa4, 0xd7, 0xe0, 0xa5, 0x1f, 0x70, 0x12, 0x81, 0xd1, + 0xc1, 0x42, 0x82, 0xb4, 0x38, 0x53, 0x89, 0x1a, 0x36, 0x78, 0x14, 0xbc, 0x6e, 0x54, 0xa2, 0xb2, 0x21, 0x1f, 0x05, + 0xa9, 0x4e, 0x43, 0x18, 0x7c, 0xd4, 0x24, 0x05, 0x1f, 0x57, 0x2a, 0x09, 0x15, 0x25, 0xa4, 0xdf, 0x08, 0xfe, 0x5d, + 0x5b, 0x3e, 0x96, 0x7f, 0xb7, 0x0e, 0xa5, 0x57, 0x90, 0x71, 0xaa, 0x3f, 0x1c, 0x64, 0xd1, 0x93, 0x6c, 0xd8, 0x98, + 0xc4, 0xf2, 0xb4, 0xbb, 0xa9, 0xd8, 0xa5, 0x0b, 0xfd, 0xca, 0x32, 0xc2, 0xb1, 0x7e, 0x1e, 0xaf, 0xa3, 0xa7, 0xc0, + 0x38, 0x32, 0x5e, 0x3a, 0x3c, 0xd1, 0x0c, 0x05, 0x4d, 0x27, 0xc1, 0x67, 0xff, 0x55, 0x1d, 0x38, 0xc4, 0x14, 0xa1, + 0x20, 0x17, 0x8d, 0xf5, 0xe0, 0x63, 0xde, 0x4e, 0x10, 0x11, 0x37, 0xbb, 0x84, 0x11, 0x69, 0x7a, 0x49, 0xaa, 0xe4, + 0xdf, 0x81, 0xa8, 0x58, 0x65, 0xf0, 0xd1, 0x6d, 0x96, 0x4e, 0x51, 0x25, 0x38, 0x98, 0x83, 0x29, 0xc2, 0x71, 0x29, + 0x1a, 0xfb, 0x89, 0xba, 0x60, 0xc5, 0x78, 0xb0, 0x7a, 0x4d, 0x00, 0xfb, 0x8d, 0xfd, 0x64, 0x8d, 0xd9, 0xaf, 0xda, + 0x8d, 0x2f, 0x53, 0x11, 0x1f, 0xd2, 0xe9, 0x2d, 0xf0, 0x98, 0x5b, 0x2b, 0xd3, 0x33, 0x07, 0x4a, 0x04, 0xbe, 0x93, + 0xae, 0xd7, 0xe9, 0x62, 0x7e, 0x93, 0x84, 0xd9, 0x14, 0x98, 0x33, 0x9c, 0x5e, 0x74, 0xbc, 0x4b, 0x36, 0x97, 0x59, + 0xf2, 0x1a, 0x63, 0x0f, 0xac, 0x4b, 0xc6, 0xe2, 0xc7, 0x65, 0xc6, 0x8b, 0x19, 0xc8, 0x4e, 0x59, 0xa4, 0xa3, 0xf9, + 0xa7, 0x24, 0xfc, 0x75, 0x65, 0x21, 0xa9, 0xa9, 0x29, 0x03, 0x91, 0x42, 0x13, 0x6a, 0xe5, 0xeb, 0x18, 0xec, 0xc4, + 0x02, 0x00, 0xe5, 0xec, 0x62, 0x11, 0x96, 0x51, 0x31, 0x39, 0x69, 0x81, 0x8a, 0x48, 0x7a, 0x45, 0xa9, 0x31, 0x90, + 0x17, 0x20, 0x1a, 0xda, 0x42, 0x06, 0xef, 0xfd, 0x81, 0x78, 0xf0, 0x73, 0x86, 0xf2, 0x0f, 0x59, 0x03, 0xd7, 0xa7, + 0xc0, 0x6c, 0x95, 0xa7, 0xd5, 0xdd, 0x5d, 0xfd, 0x24, 0x86, 0x5f, 0x4f, 0x62, 0xc5, 0x3f, 0xf0, 0xc5, 0xb6, 0x32, + 0x37, 0x80, 0xa3, 0xb0, 0xc4, 0xa8, 0x45, 0x53, 0xfc, 0x13, 0x64, 0xd0, 0x92, 0x30, 0x3f, 0x05, 0xca, 0x71, 0xb8, + 0x9a, 0x17, 0xe3, 0x7c, 0x92, 0x84, 0xf0, 0xbf, 0xe5, 0x84, 0xf8, 0xa3, 0xe5, 0x84, 0xc8, 0x34, 0x70, 0x8d, 0x67, + 0x06, 0x22, 0x0a, 0xd8, 0xf4, 0x2f, 0x90, 0xaf, 0x54, 0xf2, 0x95, 0x98, 0xbf, 0x92, 0xc8, 0x07, 0xda, 0x08, 0x06, + 0xa2, 0xa6, 0x5a, 0xa0, 0xa9, 0x30, 0xdc, 0x25, 0xd9, 0x22, 0xed, 0x90, 0x81, 0x0d, 0x49, 0xe6, 0x50, 0xe1, 0x24, + 0x36, 0x2d, 0xa2, 0x9d, 0x02, 0xe7, 0xbd, 0x0c, 0xd6, 0xc1, 0x15, 0xf1, 0xb3, 0x4b, 0x34, 0x58, 0x3a, 0x8d, 0x72, + 0xe0, 0x30, 0x97, 0xfe, 0x3a, 0xaa, 0xcf, 0xbc, 0x78, 0xec, 0x6d, 0xe6, 0xf9, 0x64, 0x19, 0x2e, 0x7d, 0xff, 0x3f, + 0x80, 0x01, 0x3a, 0x5c, 0x4f, 0xeb, 0xdf, 0x32, 0x0c, 0xef, 0xb7, 0x98, 0x7b, 0x99, 0x8a, 0xf3, 0xb1, 0x86, 0x79, + 0x5e, 0x63, 0xfc, 0x1a, 0x94, 0x48, 0xfc, 0x10, 0x3b, 0x72, 0x05, 0x95, 0x6e, 0x80, 0x2a, 0xc8, 0x0c, 0x53, 0xb4, + 0x6e, 0x7d, 0x9c, 0x24, 0xe8, 0x98, 0x6c, 0xaa, 0x0f, 0x8f, 0xb9, 0xf2, 0xa1, 0x72, 0x7e, 0x70, 0x78, 0x98, 0x98, + 0x41, 0x7a, 0xd5, 0x1d, 0x24, 0x64, 0x44, 0xa6, 0x3c, 0x50, 0x6a, 0x64, 0xca, 0x40, 0x4d, 0x2a, 0x0d, 0x49, 0x6c, + 0x0f, 0x09, 0x8f, 0x43, 0x62, 0x8f, 0x43, 0x2e, 0xe3, 0x40, 0xdc, 0xfd, 0x0a, 0x16, 0xc8, 0x02, 0xfe, 0xdf, 0xf0, + 0xa8, 0x04, 0xd7, 0xc1, 0xa5, 0x50, 0xc7, 0x8b, 0xe8, 0x0d, 0x1e, 0xd8, 0x63, 0x2f, 0x9f, 0xc7, 0x93, 0x37, 0xe1, + 0x1b, 0x68, 0x72, 0x19, 0xdc, 0xb0, 0x50, 0x86, 0x81, 0x10, 0xaf, 0xd1, 0xb9, 0xd4, 0x84, 0x3a, 0xb9, 0x56, 0x3b, + 0x8e, 0x9e, 0xae, 0x9c, 0xa7, 0x4b, 0x8c, 0xe8, 0x43, 0x56, 0x2a, 0x50, 0x66, 0x09, 0xe3, 0x70, 0xe1, 0x1d, 0xfb, + 0x88, 0xc3, 0x23, 0xc3, 0xb9, 0x84, 0xe1, 0x5c, 0xc2, 0x70, 0xa2, 0x85, 0xd7, 0xf1, 0x6c, 0x73, 0x1a, 0xc5, 0x30, + 0x21, 0x1b, 0xa2, 0xea, 0x9c, 0x7b, 0x03, 0xb9, 0x97, 0x34, 0x11, 0x3e, 0x32, 0xf4, 0x59, 0xb1, 0x51, 0x34, 0xfc, + 0x4d, 0x84, 0x85, 0xb7, 0xf0, 0xef, 0x36, 0xb8, 0x8d, 0xde, 0xdc, 0x1d, 0xcf, 0x90, 0x99, 0x5a, 0xcf, 0xbd, 0xed, + 0xe9, 0xd5, 0xfc, 0x2a, 0xda, 0x86, 0xdb, 0x27, 0xd8, 0xd0, 0xeb, 0x68, 0x4b, 0x80, 0x4b, 0x8b, 0x87, 0xab, 0xf1, + 0x1b, 0xff, 0xd1, 0x78, 0xbc, 0xf0, 0x43, 0xef, 0xc6, 0xb3, 0x5a, 0xf9, 0x26, 0x80, 0x1c, 0xeb, 0xe8, 0x96, 0x46, + 0xe3, 0x2a, 0xa2, 0x02, 0x97, 0xd1, 0xb6, 0x85, 0x4c, 0x66, 0x36, 0x92, 0x62, 0x10, 0xc4, 0x88, 0x7c, 0x0d, 0x0c, + 0xcd, 0x32, 0x61, 0x26, 0xf0, 0x49, 0x09, 0x32, 0xa2, 0x0a, 0xcd, 0x50, 0x9d, 0x95, 0xa0, 0x5a, 0x92, 0xee, 0x7e, + 0xe1, 0x11, 0x97, 0x33, 0xfc, 0x6a, 0x14, 0x3d, 0x20, 0xe4, 0xcc, 0x41, 0x7a, 0x70, 0x68, 0xd3, 0x03, 0x2a, 0x22, + 0xab, 0x86, 0x70, 0x32, 0x5f, 0xad, 0xc2, 0x1f, 0x2d, 0xfa, 0xf0, 0xc3, 0x30, 0xe5, 0xa9, 0xf3, 0x3f, 0x4e, 0x79, + 0xca, 0x3c, 0x7c, 0xd3, 0x58, 0x20, 0x78, 0xd6, 0x9a, 0xe4, 0x1b, 0x89, 0x06, 0x0e, 0x98, 0x18, 0x6f, 0x23, 0xe9, + 0xb6, 0x41, 0x9e, 0xc8, 0xc2, 0x0a, 0x23, 0xe4, 0x3c, 0x7e, 0x81, 0x06, 0xa9, 0x18, 0x2a, 0x87, 0x17, 0x25, 0x19, + 0x82, 0xe4, 0x65, 0x9d, 0x72, 0xf8, 0x1c, 0x3f, 0xe0, 0xd3, 0xc7, 0x98, 0x08, 0x2b, 0x7a, 0xec, 0xd9, 0x00, 0xf0, + 0x3f, 0xf7, 0xc8, 0x45, 0x9d, 0x5e, 0xd1, 0xd9, 0xdc, 0x25, 0x18, 0x55, 0x94, 0x14, 0x6e, 0x68, 0x1b, 0xc2, 0x7e, + 0xac, 0x7d, 0xfa, 0x0e, 0x10, 0x35, 0xa8, 0x5e, 0x8e, 0x08, 0x3b, 0x8a, 0x0f, 0xd3, 0xd3, 0x58, 0x91, 0xc8, 0x94, + 0x48, 0x64, 0x3a, 0x46, 0xc2, 0xe9, 0x93, 0xbf, 0xb8, 0x69, 0xb2, 0x69, 0xa1, 0xaf, 0xd0, 0x9f, 0x56, 0x91, 0xe8, + 0xee, 0xb9, 0xc7, 0xf6, 0x45, 0x90, 0x39, 0xa6, 0x7f, 0xb2, 0xfa, 0xf0, 0xfb, 0x8a, 0x66, 0x50, 0x7b, 0xbe, 0x70, + 0x67, 0xe6, 0xd6, 0xe0, 0x86, 0x56, 0x97, 0xc5, 0x75, 0x79, 0x99, 0x1e, 0xa4, 0xb7, 0x30, 0x7b, 0x8b, 0xfa, 0xe0, + 0x9f, 0x4d, 0x17, 0xcf, 0xa9, 0xde, 0xac, 0x4d, 0x9c, 0x15, 0x36, 0x4e, 0xb5, 0xb4, 0x2e, 0xca, 0x1a, 0xd6, 0xf1, + 0x7b, 0xa4, 0xbb, 0x92, 0x8e, 0xa3, 0x27, 0x2c, 0x47, 0x37, 0x65, 0x09, 0xec, 0xe1, 0x77, 0xbd, 0x54, 0x9a, 0x62, + 0x37, 0x85, 0xa8, 0x52, 0xc7, 0x05, 0x54, 0xe6, 0xa8, 0xe3, 0x6e, 0xa9, 0x76, 0x80, 0x71, 0xdb, 0xe4, 0xc2, 0x6c, + 0x76, 0x61, 0x25, 0x3b, 0xf2, 0x93, 0xaa, 0x43, 0x83, 0x51, 0x88, 0x59, 0x35, 0x0b, 0x87, 0xca, 0x4e, 0x58, 0x4c, + 0x58, 0x49, 0x00, 0x19, 0x02, 0xc6, 0xb1, 0xa2, 0x64, 0x52, 0xdc, 0x1e, 0xd9, 0x0a, 0xc5, 0xd7, 0x6c, 0x05, 0x58, + 0x08, 0x87, 0x85, 0xb5, 0xeb, 0x06, 0xda, 0x6e, 0xa9, 0x53, 0xa6, 0xf5, 0x3a, 0x2e, 0xfe, 0x16, 0xaf, 0x15, 0x64, + 0x32, 0x1f, 0x8f, 0xce, 0x98, 0xce, 0xfe, 0x96, 0x78, 0x76, 0x25, 0x01, 0xfa, 0xac, 0xd7, 0x5a, 0x9f, 0xdc, 0x1d, + 0x96, 0xe3, 0x96, 0xbc, 0x12, 0xd7, 0xd2, 0x37, 0xa5, 0x85, 0x94, 0x91, 0x6f, 0x5c, 0x05, 0xbd, 0x1a, 0x7b, 0x37, + 0x15, 0xe7, 0x6d, 0xcf, 0x98, 0x33, 0x82, 0x15, 0xd7, 0xdd, 0x95, 0xa9, 0x29, 0x95, 0x32, 0xa8, 0x6a, 0xbb, 0x59, + 0x54, 0xba, 0xd6, 0x7f, 0xea, 0xb9, 0x5f, 0xe4, 0x03, 0xee, 0x8a, 0xf2, 0x16, 0xb9, 0xd0, 0xac, 0xaa, 0x9b, 0xae, + 0x6e, 0x58, 0x37, 0x5e, 0xe9, 0x42, 0xa9, 0x01, 0x9a, 0x3d, 0xb7, 0x2d, 0x3c, 0x50, 0xdd, 0x44, 0x7b, 0xf6, 0xbc, + 0x45, 0x89, 0xe1, 0xeb, 0x6a, 0xb2, 0x5d, 0x69, 0x94, 0xd0, 0xc7, 0xb5, 0xc1, 0x86, 0x3f, 0x9f, 0xc2, 0x16, 0x3b, + 0x6f, 0x33, 0xbd, 0x16, 0xbe, 0xe0, 0x65, 0x78, 0x96, 0x9e, 0xab, 0xbb, 0x29, 0xa9, 0x1d, 0x68, 0x90, 0x74, 0x7a, + 0xb7, 0x16, 0xac, 0x94, 0x30, 0xd5, 0x66, 0x99, 0x48, 0x59, 0xea, 0x96, 0x95, 0x37, 0x55, 0xc7, 0x56, 0x52, 0xe9, + 0x7b, 0xce, 0xd0, 0x7d, 0xec, 0x07, 0xc2, 0x44, 0x56, 0x81, 0x49, 0xc9, 0xd0, 0x81, 0xec, 0xaa, 0x2b, 0xdb, 0x8c, + 0x7a, 0x3c, 0xce, 0x35, 0x49, 0xa4, 0x0f, 0x2c, 0xe8, 0x03, 0xc0, 0xef, 0x54, 0x67, 0x39, 0x1c, 0x9c, 0x51, 0x79, + 0xb6, 0x38, 0x87, 0xb3, 0xad, 0x3c, 0xdb, 0x90, 0x24, 0xb4, 0xc4, 0x13, 0xd2, 0xdf, 0xc5, 0x7c, 0x01, 0xbb, 0x24, + 0xe1, 0x8d, 0xce, 0x54, 0xa1, 0x65, 0x57, 0xcc, 0x01, 0xc6, 0x97, 0xb5, 0xe7, 0xd5, 0x93, 0x25, 0x5a, 0x4b, 0x3c, + 0xf2, 0xd6, 0xf0, 0x87, 0x7f, 0x63, 0xd4, 0xf9, 0xc4, 0x63, 0x76, 0x41, 0xef, 0x05, 0x7f, 0x76, 0x0d, 0xef, 0x78, + 0x24, 0xa4, 0xe2, 0x6b, 0x6d, 0x6c, 0x12, 0x5b, 0x8a, 0x96, 0x79, 0x0c, 0xd3, 0x05, 0x3c, 0x0a, 0x2e, 0xc3, 0x0f, + 0x3c, 0x33, 0x1d, 0xfd, 0x4f, 0xc2, 0x1b, 0xda, 0x17, 0x09, 0x96, 0xc9, 0x1f, 0x1d, 0x1f, 0x43, 0x0a, 0x19, 0x3d, + 0xbb, 0x65, 0xa4, 0x0a, 0xda, 0x3e, 0x32, 0xb4, 0xe7, 0x66, 0x69, 0x94, 0x72, 0x90, 0x28, 0x05, 0x77, 0xcf, 0xb3, + 0xa4, 0xac, 0x45, 0xd2, 0xfe, 0x51, 0x51, 0x1d, 0x45, 0xa5, 0x6a, 0xc0, 0xf0, 0x91, 0xa0, 0x0e, 0xf4, 0xc3, 0x4a, + 0xc2, 0x65, 0x75, 0xcd, 0x04, 0xec, 0x45, 0x42, 0xfc, 0x96, 0x67, 0x7d, 0x9a, 0x82, 0x2a, 0xea, 0x45, 0x5c, 0xda, + 0xf2, 0x88, 0x1d, 0x65, 0xf3, 0x27, 0x25, 0xc7, 0xb3, 0x86, 0xc1, 0x0e, 0xa9, 0xe9, 0x92, 0x79, 0x2d, 0x62, 0xef, + 0xa1, 0xa3, 0x16, 0xb5, 0x26, 0x79, 0x75, 0x99, 0x06, 0x18, 0xa3, 0x24, 0x20, 0x27, 0xc1, 0x11, 0x4a, 0x97, 0x98, + 0x62, 0x24, 0xd8, 0x9d, 0x2b, 0xe6, 0x85, 0xa3, 0xcb, 0x4d, 0x03, 0x42, 0xa7, 0x15, 0xd0, 0x10, 0xd6, 0x67, 0x2f, + 0x84, 0xe1, 0x71, 0xd0, 0x11, 0xc3, 0x30, 0xcc, 0x18, 0x45, 0x42, 0xb0, 0xfa, 0x17, 0xfe, 0x29, 0x48, 0xe2, 0xf5, + 0xb3, 0xf4, 0x73, 0x96, 0x56, 0x4c, 0xa4, 0x51, 0x85, 0x34, 0x4c, 0x7c, 0x43, 0xd5, 0xa4, 0x51, 0x80, 0x01, 0x46, + 0x11, 0x95, 0x58, 0xd1, 0x54, 0xfa, 0xcd, 0x8b, 0x0f, 0x7f, 0x0a, 0x1d, 0x0f, 0x8f, 0xdb, 0x4e, 0x67, 0x38, 0xe8, + 0x0c, 0xf6, 0xa8, 0x13, 0xf5, 0x74, 0xd4, 0x49, 0x50, 0x8d, 0x54, 0x6f, 0xcd, 0xc3, 0xc8, 0xaa, 0x53, 0xe3, 0x9d, + 0x43, 0x8d, 0x17, 0x36, 0x78, 0xf2, 0x71, 0x70, 0x61, 0xd0, 0x93, 0x9b, 0xe8, 0x49, 0x73, 0x48, 0xb7, 0xf7, 0x6a, + 0x84, 0x80, 0x5f, 0xa3, 0x14, 0xec, 0x06, 0xd4, 0x67, 0x78, 0x82, 0xa6, 0xec, 0x73, 0xf8, 0x86, 0xb3, 0xdc, 0x21, + 0x96, 0x0a, 0x70, 0x35, 0x99, 0xc4, 0x50, 0x5a, 0xd7, 0x1e, 0x6e, 0x31, 0x83, 0x43, 0xa5, 0xb7, 0x6a, 0x33, 0x29, + 0xfd, 0xd3, 0x7a, 0x8a, 0x0e, 0xa1, 0x9b, 0x7a, 0x5c, 0x4f, 0x57, 0x59, 0xf3, 0x9e, 0x7e, 0x0b, 0xeb, 0x90, 0x64, + 0xfb, 0x58, 0x87, 0x54, 0xb3, 0x0e, 0xb3, 0xdf, 0x14, 0x52, 0x00, 0x99, 0x6c, 0x8c, 0x4c, 0x02, 0xf2, 0xde, 0xf6, + 0x23, 0x41, 0xad, 0xcc, 0xf6, 0x32, 0x16, 0x5c, 0xde, 0x49, 0xc2, 0x1a, 0xdc, 0x84, 0xc6, 0xb0, 0x14, 0xe9, 0xe0, + 0x91, 0x9e, 0xfa, 0x40, 0x82, 0xdf, 0xa3, 0x3a, 0xcd, 0xbb, 0x67, 0x6f, 0x61, 0x70, 0xce, 0x2a, 0xd8, 0x92, 0x34, + 0x2b, 0x5a, 0x1b, 0x19, 0x28, 0x84, 0xdf, 0x1d, 0x6d, 0x09, 0xe4, 0xcb, 0x19, 0xae, 0x75, 0xf9, 0xc9, 0x4b, 0x27, + 0x55, 0xf0, 0xd8, 0x1f, 0xeb, 0xe7, 0x62, 0x12, 0xc3, 0xf3, 0xa9, 0x7e, 0x2e, 0xcd, 0x00, 0x8e, 0x4b, 0x19, 0x55, + 0xc8, 0x00, 0x0d, 0xfa, 0x49, 0xb7, 0xc8, 0x1c, 0x80, 0xa5, 0xba, 0x88, 0xc1, 0x4f, 0xa2, 0x8a, 0xba, 0xf8, 0xf9, + 0xde, 0x5c, 0x5b, 0xca, 0x85, 0xca, 0x1c, 0x0a, 0x38, 0x48, 0xdb, 0xc0, 0x53, 0x07, 0x0c, 0xf4, 0xa7, 0x80, 0xfe, + 0xd4, 0xfb, 0xfb, 0x93, 0x6a, 0x0d, 0xbe, 0xca, 0xda, 0xd2, 0x9d, 0x57, 0x8a, 0x81, 0x20, 0x52, 0x57, 0xa9, 0xaa, + 0x45, 0x3d, 0xb4, 0xf1, 0xe6, 0x7e, 0x00, 0x2d, 0x9c, 0x15, 0x46, 0xec, 0x2f, 0x02, 0x3c, 0xf9, 0x58, 0xff, 0xf5, + 0x5e, 0x65, 0x30, 0x60, 0x64, 0xf4, 0xd2, 0xda, 0xbf, 0x58, 0x5a, 0xb4, 0x7b, 0xc5, 0xb8, 0xf6, 0x1f, 0x3e, 0x66, + 0xfa, 0xb7, 0x57, 0x57, 0x3e, 0xd3, 0xd3, 0x7f, 0x77, 0xa7, 0xd6, 0xe7, 0xe9, 0xf4, 0xe4, 0xee, 0xee, 0x30, 0x6e, + 0xc4, 0x63, 0x4d, 0x16, 0x04, 0x39, 0xd7, 0xfb, 0x8f, 0x1e, 0x63, 0x54, 0x04, 0x37, 0xee, 0x66, 0xed, 0x68, 0x64, + 0xec, 0x38, 0x9d, 0xb5, 0xa3, 0xd8, 0x49, 0xad, 0xa8, 0xc4, 0xf5, 0xb4, 0xb3, 0xc1, 0x83, 0x6d, 0xe2, 0x61, 0x28, + 0x07, 0xfa, 0xd8, 0x2d, 0xff, 0xd9, 0xb2, 0x99, 0x10, 0x4f, 0xd6, 0xb0, 0x53, 0xba, 0x85, 0x69, 0x7e, 0xa0, 0x46, + 0x70, 0x9c, 0x5a, 0xfb, 0x0b, 0xc8, 0x69, 0x92, 0x09, 0x39, 0x25, 0xf2, 0x4b, 0xf4, 0x14, 0x93, 0x7a, 0xf4, 0x94, + 0x09, 0xe0, 0x49, 0xa0, 0x0b, 0xe3, 0x6f, 0x1c, 0xf7, 0x67, 0xee, 0x6b, 0x33, 0x15, 0xe1, 0x9f, 0x03, 0xaa, 0x53, + 0xc8, 0x69, 0x92, 0x55, 0x09, 0x46, 0x6d, 0xe4, 0x66, 0x00, 0x29, 0xd5, 0x31, 0x1f, 0x99, 0xf0, 0x59, 0x5f, 0xfd, + 0x9f, 0x21, 0x7c, 0xea, 0xc2, 0x0d, 0xe1, 0xf2, 0xaa, 0xab, 0x4b, 0xef, 0x2f, 0x7f, 0x0e, 0x0e, 0x4e, 0xfe, 0xfa, + 0x38, 0x38, 0x78, 0xfc, 0xa7, 0xbf, 0xf8, 0x08, 0x8b, 0x06, 0x69, 0x8f, 0xff, 0xf2, 0x97, 0xe0, 0xe0, 0x3f, 0xff, + 0x13, 0x5e, 0xfc, 0xe9, 0xb1, 0x93, 0x76, 0xf2, 0x17, 0x48, 0xfc, 0xeb, 0x9f, 0x9d, 0xb4, 0xc7, 0xc7, 0xf0, 0xcf, + 0xff, 0xfd, 0xab, 0x4a, 0xfb, 0x3f, 0x98, 0xed, 0x3f, 0x1f, 0xd3, 0x3f, 0x2a, 0xed, 0xe4, 0x2f, 0x7f, 0x82, 0xe7, + 0x63, 0xfc, 0xc8, 0x5f, 0xcc, 0x47, 0x8e, 0x4f, 0xb0, 0xf0, 0x9f, 0xf0, 0x9f, 0xff, 0xe3, 0xe3, 0x26, 0x28, 0xa3, + 0xbc, 0xa0, 0xfb, 0x33, 0x52, 0x71, 0xd2, 0xd5, 0x44, 0x92, 0x7a, 0x94, 0x99, 0xcb, 0xc4, 0xde, 0xc8, 0x37, 0xe9, + 0x58, 0x51, 0x70, 0x70, 0x3c, 0x85, 0x1a, 0x6d, 0x78, 0xba, 0xdf, 0x6c, 0x90, 0xb1, 0xbc, 0x38, 0xcb, 0xfe, 0x23, + 0x57, 0xb1, 0x15, 0x2c, 0x00, 0xab, 0xff, 0xd7, 0xdc, 0x97, 0x26, 0xb6, 0x6d, 0xa4, 0x89, 0xfe, 0x9f, 0x53, 0x50, + 0x88, 0xe3, 0x00, 0x26, 0x48, 0x91, 0x5a, 0x6c, 0x07, 0x14, 0xc4, 0x71, 0xbc, 0x24, 0x4e, 0x7b, 0x8b, 0xe5, 0x24, + 0xdd, 0x51, 0x6b, 0x24, 0x88, 0x00, 0x45, 0xc4, 0x14, 0xc0, 0x06, 0x40, 0x2d, 0xa1, 0x30, 0x67, 0x79, 0x47, 0x78, + 0x67, 0x98, 0x93, 0xbd, 0x6f, 0xa9, 0x0d, 0x0b, 0x25, 0xa5, 0x93, 0xee, 0x79, 0xd3, 0x13, 0x0b, 0x2c, 0x14, 0x6a, + 0xaf, 0x6f, 0x5f, 0x0e, 0xcc, 0x5a, 0x51, 0x0a, 0xb5, 0xa2, 0xb4, 0x59, 0xeb, 0x2f, 0xb5, 0x80, 0xf2, 0xe6, 0xa9, + 0xf5, 0x3f, 0x67, 0x9c, 0x3a, 0xad, 0xb6, 0xfa, 0xfe, 0x01, 0x95, 0x77, 0xbb, 0x06, 0x87, 0xfd, 0x6d, 0xa3, 0x9d, + 0xda, 0x37, 0x22, 0x0a, 0x35, 0x29, 0x0f, 0x1d, 0x7f, 0x1f, 0x9b, 0xee, 0x76, 0x91, 0x26, 0x30, 0xe2, 0xbe, 0xfd, + 0xce, 0x36, 0x0e, 0x5a, 0xda, 0xf8, 0x85, 0x64, 0x08, 0x64, 0xd4, 0xe3, 0xab, 0x4b, 0x8f, 0x17, 0x5d, 0xe9, 0x25, + 0x73, 0xc6, 0x4c, 0xa6, 0x52, 0x32, 0xa9, 0x68, 0x5d, 0xf3, 0x8e, 0x14, 0x45, 0x03, 0x5d, 0x15, 0x46, 0xe3, 0xc2, + 0x87, 0xc9, 0xa1, 0x7a, 0xcb, 0xab, 0xbc, 0x49, 0x63, 0x78, 0xf3, 0x83, 0x7c, 0x83, 0xd4, 0x8c, 0xff, 0x17, 0xfe, + 0x65, 0x26, 0xce, 0x88, 0x53, 0x35, 0x1e, 0x36, 0x31, 0xc2, 0x58, 0x01, 0x31, 0x3a, 0xf0, 0x60, 0xd0, 0x41, 0x73, + 0xb5, 0x6f, 0x6e, 0xb8, 0xa4, 0x3a, 0x67, 0x15, 0x06, 0x50, 0x12, 0x6f, 0x43, 0x23, 0xaa, 0x59, 0x45, 0x5e, 0x42, + 0xc2, 0xad, 0x6a, 0x7b, 0x8d, 0xc6, 0x28, 0x84, 0x60, 0x11, 0xfa, 0x18, 0x70, 0xc0, 0xa2, 0x64, 0xac, 0x70, 0xdb, + 0xe4, 0x95, 0xf7, 0x56, 0x11, 0xab, 0x27, 0x91, 0x32, 0x80, 0xb1, 0x4e, 0xa2, 0xf7, 0x42, 0xec, 0x4f, 0xd6, 0x8f, + 0xa6, 0x6f, 0x8f, 0x11, 0xd6, 0x7e, 0x23, 0xea, 0x8b, 0xcf, 0x2a, 0xec, 0x00, 0x29, 0x6a, 0x0d, 0xdf, 0xf8, 0xa4, + 0x54, 0x12, 0x8f, 0x1c, 0x69, 0x83, 0x89, 0xc8, 0x48, 0x21, 0x23, 0xd5, 0x22, 0xc5, 0xc0, 0xde, 0x58, 0x14, 0x4b, + 0x55, 0xf7, 0x8d, 0xd3, 0x4a, 0x6d, 0xf4, 0x70, 0xfb, 0x7e, 0xc6, 0x89, 0xa3, 0x1e, 0x3e, 0x84, 0x02, 0xc3, 0xed, + 0x49, 0xda, 0x91, 0xd3, 0xd6, 0x8f, 0x12, 0x1d, 0xfe, 0x1b, 0xf6, 0x41, 0xda, 0xdd, 0x87, 0xbe, 0x22, 0x59, 0xfc, + 0x7d, 0x5b, 0xc8, 0x3d, 0x0a, 0x23, 0x24, 0x9f, 0x1d, 0x61, 0x26, 0x0b, 0xca, 0x42, 0xe1, 0xf4, 0x86, 0xc0, 0x25, + 0x2c, 0x93, 0x7c, 0x16, 0x4f, 0x0b, 0x7b, 0xc5, 0x0a, 0xe5, 0xc8, 0x25, 0xdf, 0x6e, 0x74, 0x20, 0x71, 0xbc, 0x38, + 0x7f, 0x17, 0xbc, 0xb3, 0x29, 0x58, 0x5d, 0x24, 0x6c, 0xa1, 0x22, 0xe3, 0x7e, 0xc6, 0x61, 0x1b, 0x7d, 0x24, 0x5d, + 0x40, 0x75, 0x2e, 0xa6, 0xde, 0x50, 0xe9, 0x77, 0xf4, 0x17, 0x4a, 0xd3, 0x83, 0xa1, 0xbe, 0x73, 0x16, 0x23, 0xf0, + 0x57, 0xd2, 0x3e, 0x16, 0x68, 0xb2, 0x74, 0x8c, 0xb9, 0x86, 0x05, 0x6f, 0xc7, 0x73, 0x83, 0x79, 0x61, 0xe0, 0x46, + 0x1c, 0x8d, 0xc8, 0x4c, 0x52, 0xd8, 0x84, 0x2f, 0x39, 0x7d, 0x6b, 0xec, 0xb8, 0x03, 0xf4, 0xa6, 0x52, 0x83, 0x4c, + 0x52, 0x53, 0x30, 0x28, 0xd1, 0xb6, 0xc8, 0xc2, 0xaa, 0x3a, 0x8b, 0xf7, 0x31, 0xdc, 0x40, 0xbc, 0x27, 0x19, 0xcf, + 0x71, 0x71, 0x18, 0x1f, 0x79, 0x32, 0x29, 0xe0, 0x2c, 0x51, 0x04, 0xda, 0xbb, 0x35, 0xb2, 0x1d, 0x1d, 0xa1, 0x1b, + 0xf9, 0x11, 0xca, 0xa7, 0x5d, 0x15, 0xac, 0x50, 0x34, 0x42, 0x42, 0x66, 0xbe, 0x8a, 0xef, 0x15, 0x86, 0x51, 0xc8, + 0x23, 0xc1, 0xc4, 0x51, 0x14, 0xea, 0xa2, 0x69, 0x94, 0xa0, 0x2b, 0x55, 0x92, 0x19, 0x34, 0xec, 0x48, 0xd5, 0x94, + 0xb4, 0xd3, 0x21, 0x6f, 0x69, 0x2e, 0xb6, 0x54, 0xf8, 0x1a, 0x26, 0x87, 0x39, 0x79, 0xe8, 0xa1, 0xeb, 0x81, 0x70, + 0xc8, 0xcb, 0xdd, 0xa1, 0xcc, 0xa0, 0x53, 0x1b, 0x13, 0x4d, 0xae, 0x2f, 0x46, 0x56, 0x12, 0xed, 0x68, 0xcb, 0x1b, + 0xf1, 0xd2, 0x4c, 0xd4, 0x85, 0x21, 0x42, 0xd6, 0x8e, 0x48, 0xa5, 0x92, 0x8a, 0xf3, 0x57, 0xd8, 0x56, 0x44, 0xb1, + 0x75, 0x13, 0x64, 0x4b, 0xd1, 0xe4, 0x32, 0xf2, 0xe0, 0x24, 0xa1, 0x74, 0xe5, 0x19, 0x6b, 0xd7, 0x1b, 0x23, 0x71, + 0x5c, 0xd8, 0x59, 0x54, 0x08, 0xab, 0x2c, 0xce, 0xa5, 0x4a, 0x64, 0x81, 0xf0, 0xed, 0x4d, 0x7c, 0x6e, 0xa4, 0x83, + 0x5d, 0x41, 0xf1, 0x8b, 0x68, 0x0a, 0xef, 0x42, 0x0a, 0x76, 0x75, 0x25, 0x7f, 0x44, 0x9a, 0x6c, 0x43, 0xcb, 0xb7, + 0x6f, 0xf0, 0xc0, 0xe4, 0xb6, 0x50, 0x4a, 0xc4, 0x09, 0xd0, 0x6b, 0x50, 0xd9, 0x24, 0xee, 0xde, 0xc6, 0xc9, 0x5b, + 0xa0, 0xc2, 0x36, 0x06, 0x55, 0x7a, 0x1a, 0xa0, 0x0f, 0x7e, 0x49, 0x91, 0x44, 0x31, 0x87, 0x2d, 0x65, 0xc4, 0xa2, + 0xc8, 0xb1, 0x03, 0xb8, 0x20, 0x2c, 0x68, 0xad, 0x2d, 0x81, 0x91, 0x7f, 0x9a, 0xa7, 0x07, 0xfd, 0x49, 0xfb, 0x0c, + 0xe8, 0xd4, 0xcf, 0x39, 0x98, 0x05, 0xe5, 0xb9, 0xaf, 0x4b, 0x49, 0xa0, 0xaa, 0x14, 0x90, 0x40, 0x55, 0xb7, 0x2a, + 0xe3, 0x10, 0x56, 0x74, 0x46, 0x9e, 0xdf, 0xe6, 0xf2, 0xa7, 0x94, 0x6a, 0x05, 0xce, 0x37, 0x4a, 0xcc, 0x52, 0x35, + 0x94, 0x71, 0xea, 0xa5, 0x22, 0x5a, 0x08, 0x6c, 0x69, 0x77, 0xe8, 0x34, 0x4f, 0xaa, 0x22, 0x44, 0xd5, 0x57, 0x76, + 0x32, 0x1e, 0x78, 0x50, 0x75, 0xd8, 0x72, 0xde, 0xe5, 0x68, 0xb1, 0x52, 0x7f, 0xd7, 0x33, 0x7c, 0x3a, 0x81, 0xe5, + 0x1f, 0x65, 0x7b, 0x7e, 0x34, 0xca, 0x00, 0x8f, 0x09, 0x97, 0xc2, 0x15, 0xf5, 0x83, 0xa6, 0x34, 0x3a, 0xdf, 0x9a, + 0x1c, 0xf5, 0xab, 0x94, 0x53, 0xd2, 0x1b, 0x06, 0x24, 0x49, 0xaa, 0x93, 0xdd, 0xa2, 0x44, 0xd1, 0xf0, 0xbf, 0xe3, + 0x2b, 0xd8, 0xf4, 0x60, 0xac, 0x66, 0xf7, 0x35, 0x8c, 0xff, 0x48, 0xdb, 0x42, 0x51, 0x9f, 0x72, 0x7f, 0xa3, 0xa5, + 0x90, 0x53, 0x2e, 0xe2, 0x63, 0xcb, 0x40, 0x24, 0x50, 0xdd, 0xf0, 0xad, 0x64, 0x79, 0x7e, 0x0a, 0x2c, 0x34, 0xe1, + 0x44, 0xcd, 0x5c, 0xe1, 0xb5, 0x70, 0x0b, 0x09, 0xa3, 0x00, 0x82, 0x7a, 0x8a, 0x59, 0x10, 0x4d, 0xbe, 0x20, 0x21, + 0xeb, 0xdc, 0x06, 0xce, 0xb0, 0xbe, 0x88, 0xce, 0x66, 0x7d, 0x13, 0x2a, 0x83, 0xc1, 0x03, 0xd2, 0x80, 0x11, 0x74, + 0x08, 0x95, 0xfc, 0x6d, 0x0f, 0x5d, 0xb8, 0x54, 0x44, 0x96, 0x9e, 0xc8, 0xdf, 0x54, 0x1f, 0x02, 0xcf, 0x8a, 0x9c, + 0xa6, 0xa8, 0x2b, 0x36, 0xc9, 0xc7, 0x27, 0x78, 0x49, 0x95, 0x8c, 0x29, 0x1b, 0xf0, 0xb5, 0x3e, 0xde, 0xae, 0x6c, + 0x58, 0xcc, 0xc9, 0xf8, 0xfa, 0x51, 0xeb, 0x4c, 0xd0, 0x28, 0x96, 0x6b, 0x54, 0x85, 0x94, 0xc2, 0x07, 0x05, 0x28, + 0xf6, 0x59, 0x22, 0x28, 0x76, 0x15, 0x30, 0x09, 0xc9, 0x67, 0x2c, 0x35, 0x88, 0x76, 0x9a, 0xb3, 0xb7, 0xc2, 0x23, + 0x81, 0xc8, 0xdf, 0x4b, 0xd2, 0xd2, 0x64, 0xdd, 0xf3, 0xb0, 0x9a, 0xb2, 0xe5, 0xf0, 0x08, 0xe9, 0x7c, 0xbc, 0xb5, + 0x00, 0x0e, 0x51, 0x36, 0xe1, 0xa5, 0x2e, 0x5e, 0x79, 0x4a, 0xd2, 0xee, 0x05, 0xee, 0x22, 0x83, 0xe1, 0x93, 0x94, + 0x62, 0x22, 0x7c, 0x82, 0xe7, 0xf8, 0x86, 0xee, 0x23, 0x27, 0x84, 0xe7, 0x94, 0xd3, 0x5e, 0xe8, 0x0a, 0x0b, 0x18, + 0x86, 0x1e, 0x28, 0xca, 0x8b, 0x51, 0x8e, 0x77, 0x73, 0x33, 0x74, 0x17, 0x3e, 0x2c, 0xb7, 0x4b, 0xa0, 0xe4, 0x8c, + 0xda, 0x3d, 0x47, 0x9d, 0xc7, 0xa9, 0xbf, 0xf1, 0x12, 0x63, 0x11, 0x1c, 0xe3, 0xdf, 0xc0, 0x71, 0x2f, 0xf0, 0x2f, + 0x60, 0xd2, 0xb7, 0xbe, 0x7d, 0xde, 0x3b, 0x73, 0x36, 0xed, 0x10, 0xae, 0x1e, 0x5d, 0xdd, 0x6b, 0x1f, 0xc0, 0x11, + 0x17, 0x2e, 0x36, 0xa7, 0xce, 0xa3, 0xa9, 0x7b, 0xe5, 0x5e, 0xba, 0x07, 0xee, 0x7b, 0x04, 0xfc, 0xd7, 0x7b, 0xc3, + 0xa8, 0x37, 0xdc, 0x79, 0xf8, 0x70, 0xe3, 0x14, 0xfe, 0x3b, 0x96, 0x06, 0x14, 0xe2, 0x16, 0x9d, 0x95, 0xae, 0x78, + 0x3a, 0x2f, 0x8f, 0x46, 0xef, 0xf9, 0xe2, 0x4e, 0xa2, 0x78, 0x6e, 0x9f, 0x6f, 0x5e, 0x3b, 0x3d, 0xfa, 0x39, 0x9d, + 0xa7, 0x70, 0x1d, 0xcf, 0xe0, 0xb7, 0xfb, 0x7e, 0x1f, 0xf5, 0xa6, 0xd4, 0xdf, 0xfb, 0x47, 0xd7, 0xa2, 0x37, 0xc7, + 0x7d, 0x69, 0x4f, 0xf0, 0x9a, 0x5c, 0xf9, 0x8a, 0xd7, 0x1e, 0x0e, 0x30, 0x97, 0xc9, 0xb5, 0xd1, 0xde, 0xf5, 0xa3, + 0x2b, 0x67, 0xf3, 0x0a, 0x3d, 0x45, 0x15, 0xf8, 0x1b, 0xdb, 0x97, 0x7e, 0xad, 0x87, 0x47, 0xd7, 0xee, 0x41, 0x6d, + 0x10, 0x8f, 0xae, 0x1d, 0x0f, 0x2a, 0x9e, 0xc1, 0x8b, 0x73, 0xc7, 0x85, 0x49, 0x1c, 0x3f, 0x7c, 0x08, 0x48, 0xe8, + 0xdb, 0xc0, 0xb6, 0x83, 0x5e, 0xe6, 0x6c, 0xa6, 0xee, 0xf5, 0xe6, 0x30, 0xda, 0x76, 0xc6, 0xb6, 0x18, 0x3e, 0x1f, + 0x38, 0xa5, 0xf2, 0xe6, 0x5a, 0xd7, 0x2e, 0x5a, 0x2b, 0x5c, 0xfb, 0xfc, 0xeb, 0xbd, 0x7b, 0xe9, 0x67, 0xd0, 0x60, + 0xe0, 0x78, 0x17, 0x38, 0x8a, 0xd3, 0x71, 0xe6, 0xc1, 0x8a, 0xf9, 0xc7, 0xe3, 0xc0, 0x83, 0x75, 0xf3, 0xe7, 0xb0, + 0x1f, 0x50, 0xf7, 0xa0, 0x77, 0x09, 0x75, 0xa1, 0xfb, 0xf7, 0xe2, 0xf9, 0xda, 0x05, 0x9e, 0xf2, 0xbd, 0x6b, 0x74, + 0xf3, 0xde, 0x91, 0xdd, 0x57, 0x7a, 0x87, 0x8f, 0xcc, 0xc5, 0x7c, 0xaf, 0xec, 0x69, 0x1e, 0x68, 0xd8, 0xb8, 0xcc, + 0x6d, 0x58, 0x52, 0xf8, 0xf7, 0x12, 0xde, 0x56, 0xd7, 0x0e, 0x17, 0x74, 0xfc, 0xc0, 0x83, 0x25, 0xbc, 0x34, 0x5b, + 0xbd, 0xa4, 0x35, 0x94, 0x2b, 0xc4, 0x65, 0x07, 0x54, 0x46, 0xe7, 0xe0, 0x85, 0x08, 0xd6, 0x01, 0x6b, 0x64, 0x2f, + 0x1f, 0x3e, 0xc4, 0x4c, 0xf7, 0xd9, 0x58, 0xe6, 0x76, 0xd3, 0x60, 0xd3, 0xbd, 0x44, 0xed, 0xff, 0x8b, 0x6e, 0x17, + 0x27, 0x63, 0xb4, 0x64, 0x5f, 0x76, 0x5f, 0xc0, 0x62, 0x73, 0x1f, 0x99, 0x9b, 0xa7, 0x76, 0xe6, 0xbe, 0x75, 0x63, + 0x0c, 0xf8, 0x05, 0x95, 0x1d, 0x4f, 0x7e, 0xe6, 0x8c, 0x5e, 0xec, 0xbd, 0x1f, 0x75, 0xbb, 0x2f, 0xe4, 0x35, 0xf9, + 0xcd, 0x5f, 0xd3, 0x0a, 0x9e, 0x3f, 0xd8, 0xad, 0xdf, 0xf6, 0x03, 0xe7, 0x34, 0x8b, 0x82, 0xcf, 0xa3, 0xea, 0x58, + 0x7e, 0xd3, 0x59, 0xd5, 0xa0, 0x16, 0x8c, 0xf8, 0x00, 0x63, 0x17, 0x8d, 0xb5, 0xaf, 0x27, 0x4a, 0x5b, 0x0e, 0x35, + 0x44, 0x12, 0x20, 0xc7, 0x0d, 0x70, 0x6c, 0x01, 0x8f, 0x6d, 0xdc, 0x52, 0xc1, 0x0f, 0xbc, 0x6a, 0x47, 0x41, 0x09, + 0x7b, 0xb8, 0x71, 0x7c, 0x73, 0x73, 0x00, 0x87, 0x2f, 0x70, 0x50, 0xfa, 0x61, 0xbe, 0x3e, 0x28, 0x2b, 0x39, 0xc4, + 0x72, 0x16, 0xdf, 0xad, 0x66, 0x0a, 0x09, 0x00, 0x75, 0x0b, 0x27, 0xe9, 0xa3, 0xe4, 0xcb, 0x93, 0x52, 0xd3, 0xad, + 0xbf, 0x60, 0x10, 0xa2, 0xd4, 0xb7, 0xa3, 0x31, 0xad, 0x41, 0x1e, 0x63, 0x20, 0x73, 0x8f, 0x77, 0x3e, 0xc5, 0x20, + 0xc0, 0x70, 0x31, 0xfa, 0x03, 0x60, 0x74, 0x33, 0xbf, 0xff, 0x64, 0xf7, 0x51, 0xf1, 0xc8, 0xb6, 0xac, 0x6e, 0xec, + 0xd4, 0xf4, 0x14, 0xea, 0xb0, 0x16, 0x9b, 0x68, 0x04, 0x2f, 0xc8, 0xc7, 0x8b, 0xf8, 0xde, 0xe4, 0x23, 0x01, 0xd6, + 0x0a, 0xe1, 0x08, 0x9f, 0xd5, 0xf4, 0x76, 0x6b, 0x08, 0x4c, 0xa8, 0x78, 0x07, 0xd9, 0x69, 0xd2, 0x6f, 0x46, 0x18, + 0x4f, 0x44, 0x8c, 0x9a, 0x51, 0x10, 0x38, 0x0d, 0x70, 0x88, 0xc9, 0x04, 0xbe, 0xa3, 0x52, 0xd4, 0xb1, 0x29, 0x12, + 0xae, 0x5b, 0x38, 0x8c, 0x5a, 0x80, 0x3d, 0x66, 0xdb, 0x39, 0x84, 0x96, 0xd4, 0x95, 0xcc, 0x5b, 0x20, 0x09, 0x32, + 0xd2, 0xe5, 0x3e, 0x2b, 0x7e, 0x89, 0xb2, 0x54, 0x86, 0xcf, 0xd0, 0x22, 0x42, 0x83, 0x5a, 0x8b, 0x4c, 0x6a, 0x2d, + 0xb9, 0x83, 0x5a, 0xcb, 0x09, 0xc4, 0xca, 0xb0, 0xa4, 0x92, 0x39, 0x9a, 0xf8, 0xfb, 0xb9, 0x1f, 0x8d, 0x73, 0x80, + 0xe3, 0x01, 0xfe, 0x48, 0xfd, 0x04, 0xe8, 0x9c, 0x09, 0xd9, 0x27, 0xea, 0x0c, 0x83, 0xff, 0xc0, 0x98, 0xfd, 0xee, + 0x1c, 0xff, 0xa6, 0x70, 0xa3, 0xf7, 0x30, 0x21, 0xc4, 0xde, 0x60, 0x1c, 0xd8, 0x03, 0xc7, 0x9b, 0xec, 0xe3, 0x2f, + 0xfc, 0x27, 0x83, 0x9f, 0xa5, 0xe0, 0x61, 0x52, 0x66, 0x6e, 0x27, 0x3e, 0x86, 0x2b, 0x1b, 0x8c, 0x87, 0x9e, 0x92, + 0xee, 0xa6, 0x8f, 0xfa, 0x83, 0x5d, 0x67, 0x14, 0xd8, 0x69, 0x17, 0xee, 0x39, 0x7a, 0xf7, 0xda, 0x79, 0x6f, 0x22, + 0xc2, 0xb3, 0x21, 0x99, 0x97, 0x6b, 0x32, 0x2f, 0x45, 0x0c, 0x88, 0xcb, 0x44, 0x14, 0xeb, 0x9a, 0x48, 0x29, 0x02, + 0x9f, 0xd3, 0x3c, 0x05, 0x0a, 0xa2, 0xea, 0xa4, 0xa5, 0x8a, 0x16, 0x18, 0xe8, 0x92, 0x16, 0xc7, 0x55, 0x38, 0x3f, + 0x19, 0xc3, 0x18, 0x35, 0x94, 0x92, 0xdd, 0x6d, 0x26, 0x15, 0xc8, 0x2f, 0x07, 0x04, 0xc5, 0xdd, 0xa1, 0x9b, 0xef, + 0x03, 0xb4, 0xc3, 0x04, 0x1e, 0xa9, 0x99, 0xf1, 0x8b, 0xe3, 0xc4, 0x60, 0x7a, 0x15, 0x22, 0xa0, 0xc2, 0x92, 0x07, + 0xd3, 0x57, 0x1d, 0x77, 0x30, 0x4d, 0x4a, 0xe7, 0x32, 0x5d, 0xce, 0x43, 0xcc, 0x0a, 0x06, 0xd8, 0xb8, 0x73, 0x86, + 0x96, 0xec, 0x70, 0xa3, 0x92, 0xb3, 0xce, 0x72, 0x81, 0xb1, 0x73, 0x1f, 0xac, 0xf2, 0xb2, 0xc3, 0xdf, 0x75, 0x68, + 0xe4, 0xf8, 0x0a, 0xca, 0x87, 0x83, 0xc1, 0xa0, 0x7f, 0x82, 0xa8, 0x03, 0x01, 0x2d, 0x5c, 0x65, 0x19, 0x05, 0x34, + 0x3d, 0x5f, 0x2c, 0x8b, 0xc8, 0x58, 0x17, 0x97, 0x64, 0xa5, 0x43, 0x20, 0x32, 0x23, 0x4a, 0x82, 0xa2, 0xee, 0x55, + 0x24, 0xf2, 0x9f, 0x34, 0x3f, 0x91, 0x27, 0x9a, 0x4f, 0x6a, 0xff, 0xc3, 0xfb, 0x83, 0xd7, 0x9f, 0x5e, 0xff, 0xf4, + 0xf2, 0xf8, 0xf5, 0xbb, 0x57, 0xaf, 0xdf, 0xbd, 0xfe, 0xf4, 0xb7, 0x5b, 0x08, 0x6c, 0xd3, 0x56, 0x44, 0xad, 0xbd, + 0xc1, 0xc7, 0x18, 0xbd, 0x98, 0xc2, 0xd9, 0x2d, 0xcd, 0xc5, 0x62, 0xd8, 0x04, 0x49, 0x2d, 0x24, 0xae, 0x20, 0x34, + 0x0a, 0xc1, 0x27, 0x10, 0xa1, 0x51, 0x10, 0x19, 0x8f, 0x27, 0xb6, 0x20, 0x2a, 0x5e, 0x13, 0x1c, 0x00, 0xc3, 0xe4, + 0x33, 0x53, 0x26, 0x91, 0x5a, 0x6d, 0x41, 0x8a, 0xa0, 0xcf, 0x17, 0xfc, 0x35, 0xa8, 0x10, 0xbe, 0xdb, 0xea, 0x37, + 0x2c, 0x98, 0x01, 0xe4, 0x5a, 0x68, 0xdf, 0x0a, 0xd8, 0x8b, 0xfa, 0xc6, 0x2f, 0xf6, 0x0c, 0x35, 0x29, 0x9a, 0xa8, + 0x5f, 0xf9, 0x4d, 0x4e, 0xcc, 0xa5, 0xd4, 0xa1, 0x1e, 0x67, 0x78, 0xbf, 0x59, 0x8c, 0x0d, 0xa0, 0x10, 0xc8, 0xac, + 0xdc, 0x48, 0x77, 0x59, 0xb4, 0x70, 0x46, 0x3f, 0x20, 0xfa, 0x61, 0xd5, 0x04, 0xc1, 0x22, 0x8b, 0x75, 0x65, 0x44, + 0x6d, 0x8f, 0xed, 0x4c, 0x3e, 0xda, 0x15, 0x00, 0xa8, 0x98, 0x1d, 0x05, 0x02, 0xe5, 0xe1, 0x55, 0xae, 0x7f, 0x46, + 0xbd, 0x38, 0xa9, 0xd7, 0x0b, 0xae, 0x30, 0xc5, 0xb0, 0xc9, 0x22, 0x54, 0x36, 0x5c, 0x6f, 0xb2, 0x66, 0x5a, 0x24, + 0x5f, 0x05, 0xdf, 0x92, 0xdc, 0xa2, 0x9d, 0xa5, 0xa8, 0x72, 0x5d, 0x68, 0x0f, 0x54, 0x65, 0xc7, 0x73, 0xdf, 0xc6, + 0xd8, 0x8c, 0x9b, 0xea, 0x90, 0x68, 0xbf, 0x77, 0x60, 0x99, 0x36, 0xb7, 0x46, 0x51, 0x0f, 0xe0, 0x41, 0xd2, 0x05, + 0x86, 0xaf, 0x01, 0xcd, 0xa3, 0x3a, 0x20, 0x4f, 0x9a, 0x30, 0x1c, 0x1a, 0xbf, 0x8d, 0x49, 0xfa, 0x14, 0x55, 0x1c, + 0xaa, 0xd5, 0x70, 0x29, 0xa5, 0x69, 0xe4, 0x36, 0xa1, 0x0c, 0x0a, 0x91, 0xce, 0x03, 0x60, 0xa7, 0x04, 0xaa, 0x0a, + 0xb5, 0xa4, 0xe3, 0x22, 0x5e, 0xdd, 0xc1, 0x64, 0x33, 0x77, 0x6d, 0xb2, 0xd5, 0x75, 0x86, 0x19, 0xc1, 0xdf, 0xaf, + 0x14, 0xd5, 0x44, 0x86, 0xe8, 0x42, 0x28, 0xf8, 0x2b, 0x7a, 0x79, 0x46, 0x9e, 0x50, 0x80, 0xae, 0xc3, 0x1d, 0x6d, + 0x77, 0xbc, 0xb2, 0x8b, 0xb5, 0x23, 0x0e, 0x5b, 0x69, 0x3a, 0xcb, 0x73, 0xdb, 0x1c, 0x5d, 0x27, 0x01, 0xf4, 0xde, + 0x32, 0x77, 0xe3, 0x1a, 0x20, 0x50, 0xb2, 0x0b, 0x8d, 0xfb, 0x13, 0x03, 0xf7, 0x27, 0x0a, 0xf7, 0xab, 0x4b, 0xc0, + 0x3e, 0xac, 0x38, 0xb2, 0x57, 0xd0, 0xbd, 0x1c, 0xf2, 0xa0, 0xaa, 0xcb, 0x22, 0x58, 0x1c, 0x6d, 0x2a, 0xd8, 0xb5, + 0x33, 0x70, 0x53, 0x52, 0x8f, 0x7c, 0x47, 0xa3, 0xda, 0xcc, 0x9d, 0xdb, 0xf9, 0xcc, 0x3f, 0x97, 0x83, 0x5c, 0xc7, + 0xdb, 0xfd, 0x11, 0x86, 0x0e, 0xb9, 0xb5, 0x30, 0x31, 0xd4, 0xd5, 0x41, 0x46, 0xbc, 0x5a, 0x78, 0x3b, 0xaf, 0xf6, + 0x21, 0x16, 0xc7, 0xae, 0xc0, 0xa8, 0x41, 0x40, 0x70, 0x44, 0x59, 0x3c, 0x29, 0x95, 0x42, 0xe3, 0x7d, 0x84, 0xa9, + 0x3d, 0x0c, 0xc4, 0x85, 0x72, 0x58, 0x80, 0xfa, 0x9f, 0x0b, 0x29, 0x00, 0x34, 0x49, 0xec, 0xf7, 0x11, 0xbc, 0xec, + 0x9a, 0x12, 0xbf, 0x34, 0x35, 0xc5, 0xc5, 0x9b, 0x8d, 0xca, 0x0e, 0x79, 0x95, 0xe8, 0xac, 0xb9, 0x69, 0x3d, 0xe7, + 0x87, 0xf9, 0x05, 0x65, 0x83, 0x30, 0xc6, 0x12, 0x6f, 0x26, 0x2d, 0xbb, 0x5c, 0x20, 0xaa, 0xcd, 0x75, 0x9b, 0x69, + 0x8d, 0xfd, 0x2c, 0x7a, 0xb1, 0xc0, 0x29, 0xef, 0x51, 0xf6, 0x45, 0xd4, 0xfd, 0x48, 0x74, 0x9c, 0x38, 0xfb, 0xc3, + 0xc1, 0xc8, 0x49, 0xba, 0xdd, 0x5a, 0xf1, 0x1e, 0x15, 0xf7, 0x7a, 0x0d, 0xe2, 0x32, 0x11, 0xf3, 0x30, 0xe6, 0x80, + 0xfd, 0x55, 0xae, 0xa4, 0xb3, 0x2a, 0xfc, 0x7f, 0xa0, 0x59, 0x2c, 0x02, 0x47, 0x1d, 0x7e, 0x11, 0xf8, 0xe0, 0x1c, + 0xc7, 0x50, 0xc8, 0xe8, 0xc9, 0x30, 0x52, 0xf2, 0x91, 0xcd, 0xfc, 0x14, 0x08, 0x20, 0x73, 0xe6, 0x9a, 0xc0, 0x01, + 0x54, 0x2d, 0xbd, 0xe4, 0x83, 0xca, 0xe2, 0x50, 0x1e, 0x87, 0x7c, 0x3f, 0xad, 0x7c, 0x07, 0x64, 0xf3, 0x40, 0x9a, + 0x2a, 0x0b, 0x56, 0xa2, 0x00, 0x7a, 0xe8, 0x11, 0xb0, 0x6b, 0x99, 0x3b, 0x33, 0xd7, 0x92, 0xca, 0x37, 0x83, 0xcd, + 0xe1, 0xc0, 0x79, 0x14, 0x3c, 0x1a, 0xca, 0x70, 0xc3, 0x66, 0x8d, 0x79, 0x6f, 0xe6, 0x6c, 0x56, 0xbb, 0x44, 0x53, + 0x54, 0x39, 0x33, 0xb3, 0x93, 0x49, 0x77, 0xd6, 0x0d, 0x1f, 0xd5, 0xea, 0x52, 0xaf, 0x62, 0xbd, 0x97, 0x7b, 0x11, + 0xac, 0x67, 0x85, 0x63, 0x58, 0xc2, 0x6a, 0xfd, 0x9a, 0x66, 0x1e, 0x1c, 0x99, 0x25, 0x6c, 0x74, 0x7c, 0x96, 0xc4, + 0xd3, 0x78, 0x02, 0x00, 0xc9, 0x0b, 0x81, 0x97, 0x08, 0xf7, 0xfd, 0xe1, 0x60, 0x1c, 0xfa, 0xe1, 0xde, 0x70, 0x77, + 0x3c, 0xdc, 0xf5, 0xb6, 0x06, 0x5e, 0x08, 0xdc, 0x16, 0x14, 0x6f, 0x0d, 0xd0, 0xc5, 0x0e, 0x9f, 0xfd, 0x2d, 0x5c, + 0xba, 0x7d, 0x22, 0x09, 0x33, 0x1c, 0xda, 0xfd, 0x86, 0x24, 0x96, 0x73, 0xca, 0x33, 0x01, 0x44, 0xb7, 0x34, 0x1c, + 0x5c, 0xcc, 0x11, 0x4e, 0xf5, 0x08, 0xa7, 0xcd, 0x11, 0x26, 0x02, 0x6c, 0x07, 0xe9, 0xbf, 0x83, 0xc3, 0x58, 0xc7, + 0x4b, 0xc8, 0xc3, 0x75, 0x11, 0x03, 0x29, 0x93, 0x16, 0x29, 0x72, 0x13, 0x2c, 0x0a, 0xeb, 0x07, 0x8b, 0xc5, 0x5c, + 0xb8, 0x88, 0x1d, 0x42, 0xdd, 0x23, 0xce, 0x53, 0x8c, 0x24, 0xb4, 0x34, 0x90, 0xfb, 0x0d, 0x98, 0x02, 0x5f, 0xa9, + 0x7d, 0x24, 0x1f, 0xf9, 0xab, 0x8d, 0xc6, 0xa8, 0xc9, 0xfe, 0x60, 0xcc, 0xb1, 0x2e, 0xee, 0x92, 0xf7, 0xfe, 0x0e, + 0x54, 0xa4, 0x50, 0x33, 0xea, 0x09, 0x3c, 0xed, 0x11, 0xa7, 0x30, 0x93, 0x51, 0x21, 0x32, 0x2b, 0x28, 0xe9, 0xaf, + 0xe6, 0x66, 0x94, 0x6d, 0x8f, 0x98, 0x85, 0x14, 0x8a, 0xfe, 0x46, 0xef, 0x64, 0xbf, 0x18, 0x97, 0x90, 0x17, 0x76, + 0x79, 0x76, 0x16, 0xe5, 0x18, 0x44, 0x28, 0x4e, 0x80, 0x95, 0xfa, 0x55, 0x86, 0x26, 0x05, 0xf6, 0x06, 0x4a, 0x94, + 0x33, 0x0e, 0x0e, 0x15, 0xa1, 0xff, 0xe7, 0x42, 0xfd, 0x76, 0x07, 0xce, 0xd8, 0xfc, 0xd9, 0x1b, 0x3a, 0x5e, 0xf5, + 0xb5, 0x73, 0x07, 0x36, 0xbd, 0x83, 0x45, 0xfb, 0x27, 0x64, 0xe6, 0x92, 0x42, 0xc6, 0xfe, 0x73, 0x4d, 0x3c, 0x49, + 0xbd, 0x4e, 0xe0, 0xef, 0x43, 0x85, 0x31, 0x66, 0x61, 0xcf, 0xf0, 0x07, 0xb3, 0x65, 0xc1, 0x08, 0x77, 0x1f, 0xd5, + 0x88, 0xc9, 0x1e, 0x5c, 0x1a, 0x3b, 0xb5, 0x87, 0x68, 0xdf, 0x0b, 0x20, 0x00, 0x28, 0xbb, 0xd4, 0x86, 0x39, 0xd1, + 0xe4, 0xb0, 0xec, 0x73, 0x41, 0x8a, 0x09, 0x0c, 0x11, 0x88, 0x75, 0x1f, 0x3e, 0x4c, 0xb9, 0x88, 0x5e, 0xe7, 0x54, + 0x92, 0xf1, 0x07, 0xff, 0x8c, 0x54, 0x5d, 0x13, 0xfd, 0x7c, 0x7e, 0xcc, 0x9d, 0x60, 0x3a, 0x5d, 0x97, 0x04, 0x57, + 0x98, 0xdc, 0x39, 0x43, 0x1d, 0x04, 0x96, 0xde, 0x45, 0xef, 0x26, 0xeb, 0xe9, 0xdd, 0xe4, 0x5f, 0x47, 0xef, 0x26, + 0xb7, 0x11, 0x86, 0x85, 0x0a, 0x0d, 0x3f, 0xb6, 0x06, 0x96, 0xf7, 0xcf, 0xd3, 0x89, 0x6b, 0x69, 0x6a, 0x18, 0xd5, + 0x68, 0x0d, 0xd1, 0x6c, 0x02, 0x14, 0x0a, 0xab, 0xd8, 0x84, 0x27, 0xcb, 0x42, 0x71, 0xad, 0x4e, 0x8f, 0xea, 0xdc, + 0x42, 0x1a, 0xd9, 0x7a, 0x36, 0xc0, 0x89, 0x10, 0x30, 0xd1, 0x22, 0x78, 0x5c, 0x33, 0x25, 0x7d, 0xbf, 0xb9, 0x91, + 0x2a, 0xcc, 0x5b, 0xa9, 0x28, 0xac, 0x2e, 0x3f, 0x1e, 0x0f, 0x3c, 0x9b, 0x06, 0xf0, 0x4f, 0x13, 0x56, 0x15, 0xd9, + 0x7c, 0x2b, 0x21, 0xd5, 0x30, 0x79, 0x1a, 0x36, 0x41, 0x6f, 0x37, 0x6a, 0x91, 0x9f, 0x03, 0xbd, 0x15, 0xa4, 0x92, + 0xde, 0x4a, 0xcf, 0x82, 0x2c, 0x2e, 0x66, 0xe7, 0xf1, 0x84, 0x88, 0x2e, 0x7c, 0x71, 0x6f, 0xa2, 0xcb, 0xf8, 0x58, + 0x20, 0x18, 0x43, 0x29, 0x5e, 0x56, 0x44, 0xe9, 0xcb, 0x9a, 0x67, 0x05, 0x33, 0x4f, 0x9c, 0xb3, 0x3d, 0xce, 0xd1, + 0xe9, 0x14, 0x4d, 0xf0, 0xc5, 0xa3, 0x9e, 0xfd, 0x04, 0xe3, 0x82, 0x62, 0xcf, 0x61, 0x96, 0x2e, 0x64, 0x2c, 0x27, + 0x15, 0xba, 0x13, 0x63, 0x86, 0x02, 0xe5, 0x8c, 0x0c, 0x14, 0xfe, 0x25, 0x03, 0x23, 0xf7, 0x95, 0x7e, 0x76, 0xba, + 0x32, 0xd2, 0xa5, 0xc4, 0x08, 0x03, 0x4d, 0xed, 0x04, 0x61, 0x2d, 0xcb, 0x59, 0xe4, 0x7f, 0x64, 0x96, 0x02, 0x0d, + 0xf0, 0x56, 0x97, 0xde, 0xf1, 0x84, 0xbc, 0x16, 0x58, 0xe7, 0x8d, 0xd4, 0xcd, 0xcc, 0x93, 0xc2, 0xc5, 0x47, 0x85, + 0x41, 0x82, 0x1b, 0xf6, 0x0b, 0x13, 0x65, 0xeb, 0x87, 0xc6, 0x6c, 0x46, 0xb2, 0xc0, 0x84, 0x13, 0x05, 0xe6, 0x63, + 0x61, 0x69, 0x5a, 0xf4, 0xa2, 0xcd, 0x2d, 0xb2, 0x36, 0x2d, 0xba, 0xf0, 0x54, 0x7a, 0xf1, 0x1e, 0x56, 0xd9, 0x37, + 0x2b, 0xf0, 0xeb, 0xd2, 0x93, 0x25, 0xb2, 0xba, 0xd9, 0x5f, 0x68, 0xae, 0xea, 0x0a, 0x5d, 0x3f, 0x30, 0x02, 0x58, + 0x17, 0x1d, 0x02, 0x79, 0xb1, 0x44, 0x44, 0x30, 0x78, 0x41, 0xd1, 0xbe, 0x7a, 0xc6, 0x1b, 0x11, 0xfe, 0x0b, 0xdd, + 0x45, 0xd2, 0x85, 0xf9, 0x09, 0x05, 0xfe, 0xf2, 0x62, 0xa1, 0x4c, 0x31, 0x3f, 0x11, 0xea, 0x15, 0x00, 0x77, 0x55, + 0x6b, 0x3e, 0xcc, 0xd6, 0xe4, 0xb8, 0x82, 0x30, 0x44, 0xff, 0x56, 0x3f, 0x16, 0x96, 0xaa, 0xac, 0x3e, 0x94, 0x5e, + 0x6b, 0x99, 0xb6, 0x7c, 0xec, 0x1b, 0xaf, 0x29, 0x75, 0xec, 0x44, 0x5b, 0xca, 0x71, 0xe9, 0xf8, 0xdd, 0x66, 0xea, + 0xe9, 0x80, 0xd3, 0x13, 0x7f, 0x30, 0x9a, 0xec, 0xa5, 0xa3, 0x89, 0x0e, 0x99, 0x3f, 0xf7, 0x29, 0xb3, 0xaa, 0x0c, + 0xc2, 0xc2, 0x36, 0x94, 0xaa, 0x01, 0x59, 0x3c, 0x71, 0x9c, 0x11, 0xa5, 0xa2, 0x98, 0xf7, 0xc5, 0x3c, 0x94, 0x37, + 0xab, 0xfe, 0xe2, 0x83, 0x08, 0x70, 0x6a, 0x4f, 0x30, 0x11, 0x78, 0x16, 0x5c, 0x42, 0x35, 0xf4, 0x18, 0xee, 0xe2, + 0x97, 0xe8, 0x26, 0x17, 0xfa, 0x7f, 0x2d, 0xec, 0x39, 0x9d, 0x2d, 0x24, 0xd0, 0xf0, 0xf4, 0x50, 0x74, 0xb8, 0xd0, + 0xad, 0x4e, 0x15, 0x43, 0x6c, 0x8c, 0x30, 0x97, 0x85, 0xbf, 0x54, 0xe4, 0xd9, 0x0f, 0x3c, 0x34, 0xb2, 0x4e, 0x78, + 0x96, 0x9c, 0xcd, 0x31, 0x8b, 0x4a, 0x37, 0x40, 0x77, 0x24, 0x83, 0xce, 0x7b, 0x90, 0x00, 0x6d, 0x86, 0x6e, 0x65, + 0x70, 0x88, 0x16, 0xee, 0xac, 0x0f, 0xd4, 0x5c, 0xff, 0xd2, 0x1d, 0xb8, 0xc3, 0xa7, 0x03, 0xb2, 0xc8, 0xe6, 0xd2, + 0x6b, 0x28, 0x9d, 0xb9, 0x5f, 0x0f, 0xdc, 0xad, 0x27, 0x40, 0x93, 0xcc, 0x09, 0x99, 0xb8, 0x53, 0x74, 0xec, 0x72, + 0x4a, 0xf2, 0xd4, 0x34, 0x0d, 0x0e, 0xe1, 0x94, 0xf6, 0xe0, 0xc8, 0xba, 0x51, 0x3f, 0xeb, 0x01, 0xfa, 0x80, 0xc3, + 0x0c, 0xc7, 0xaa, 0x0f, 0x07, 0xa9, 0x7f, 0x0a, 0xbf, 0x4f, 0x9d, 0xea, 0xd0, 0x5f, 0x17, 0xd1, 0x79, 0xee, 0x2f, + 0xf1, 0x5a, 0xe0, 0xf1, 0xd5, 0xa7, 0x6c, 0x1e, 0x9a, 0xa7, 0x5a, 0x62, 0x66, 0x45, 0xd9, 0x2b, 0x76, 0x37, 0x72, + 0x54, 0x3c, 0x6e, 0x55, 0x8e, 0xac, 0x2f, 0x94, 0x13, 0xa6, 0xe7, 0xc8, 0x0f, 0x83, 0x51, 0xc2, 0x78, 0x08, 0xcd, + 0x24, 0xc6, 0x76, 0xe0, 0xd3, 0x30, 0x45, 0x19, 0x2a, 0x70, 0xe0, 0x0c, 0x6b, 0x51, 0x1d, 0xfc, 0x70, 0xf1, 0x7d, + 0x00, 0x98, 0x3d, 0x41, 0x5c, 0xb5, 0x0f, 0x13, 0xc1, 0x9c, 0x23, 0xbe, 0x4d, 0x3f, 0x71, 0x5e, 0xfc, 0x91, 0x11, + 0x09, 0x3c, 0xa6, 0xb9, 0x66, 0xb0, 0xc4, 0x18, 0x18, 0x54, 0x76, 0x56, 0x8c, 0xed, 0x09, 0x76, 0x56, 0xf4, 0x72, + 0xd9, 0x59, 0x06, 0xdf, 0x15, 0x66, 0x67, 0x05, 0xad, 0x11, 0x9c, 0x18, 0x2f, 0x17, 0x9d, 0xa1, 0xfa, 0x04, 0x3e, + 0xcb, 0x45, 0x67, 0xa7, 0xfc, 0xd1, 0xa9, 0xd9, 0xd9, 0x29, 0xba, 0x90, 0x76, 0x27, 0x26, 0x2b, 0x35, 0x0b, 0xeb, + 0xec, 0x60, 0xe5, 0x54, 0xb9, 0x2b, 0x38, 0x98, 0x59, 0xe0, 0xc1, 0xc9, 0x57, 0x39, 0xa0, 0xe9, 0x60, 0x78, 0xa9, + 0x2b, 0xce, 0x28, 0xfa, 0x43, 0xa2, 0xa8, 0x34, 0x40, 0xaf, 0xce, 0x49, 0xdb, 0x49, 0x05, 0xee, 0xae, 0x9b, 0x77, + 0x33, 0xe4, 0x9f, 0xe6, 0xb5, 0x83, 0xf4, 0x03, 0x66, 0x54, 0xc6, 0xf6, 0xba, 0x3f, 0x52, 0xf2, 0x64, 0xff, 0x2c, + 0x84, 0x92, 0x6b, 0x37, 0x80, 0xb3, 0x83, 0xe1, 0x60, 0xfc, 0x69, 0xc8, 0xf1, 0xd6, 0x17, 0x58, 0x7e, 0x05, 0xe5, + 0x97, 0x6e, 0xa8, 0x6c, 0x4e, 0x65, 0xa8, 0xab, 0x8d, 0x81, 0x7b, 0xe5, 0xe1, 0xeb, 0x6b, 0x6f, 0xe6, 0xe2, 0x55, + 0x7a, 0x36, 0x87, 0xcb, 0xee, 0x85, 0x2e, 0x45, 0x20, 0x5c, 0x52, 0xe4, 0xc0, 0x99, 0x88, 0x36, 0xb8, 0xec, 0x62, + 0x1b, 0x22, 0x5c, 0xe0, 0x0c, 0x7e, 0xcc, 0x0c, 0x30, 0x15, 0x0a, 0x46, 0x96, 0xc2, 0x47, 0x6b, 0x1b, 0x2d, 0xa6, + 0x19, 0xa9, 0xb1, 0x88, 0xc3, 0x10, 0x8a, 0xc6, 0x72, 0xd9, 0x50, 0xaa, 0x13, 0x47, 0x6e, 0xd8, 0x41, 0x61, 0xaf, + 0xd0, 0xb4, 0xe8, 0x1a, 0xcd, 0xa3, 0x50, 0xe1, 0xa0, 0x0b, 0x52, 0xb3, 0x20, 0xaf, 0xd7, 0xc8, 0x65, 0x0d, 0x63, + 0x83, 0x96, 0x8d, 0x0d, 0x22, 0xd8, 0xd5, 0x0e, 0xb6, 0x52, 0xd3, 0x60, 0xbb, 0x01, 0xa7, 0x60, 0xa7, 0x04, 0xde, + 0xc2, 0xcd, 0x4a, 0x2b, 0x80, 0x6d, 0xe2, 0x8b, 0x9d, 0x5e, 0x62, 0xec, 0x69, 0x00, 0xf9, 0xf5, 0xfd, 0xce, 0x00, + 0xca, 0xe5, 0xde, 0x80, 0x2d, 0x78, 0xe7, 0x0a, 0xd8, 0xcd, 0xe0, 0x9a, 0xcc, 0xf6, 0xf2, 0xd1, 0x8c, 0x80, 0x9d, + 0x84, 0x5b, 0x7e, 0x74, 0x38, 0x3b, 0x72, 0x27, 0x84, 0xdb, 0xfc, 0x02, 0x9e, 0x05, 0x84, 0x09, 0x7d, 0x3a, 0x6f, + 0x33, 0xf2, 0xff, 0x67, 0xc6, 0x2f, 0xc4, 0xf0, 0x12, 0x40, 0x50, 0x62, 0x84, 0x7b, 0x34, 0x2d, 0x08, 0x55, 0x96, + 0x0d, 0xd8, 0x8c, 0x90, 0x0e, 0x81, 0x2c, 0x81, 0xb7, 0x73, 0x3f, 0x74, 0x8c, 0xd0, 0xa1, 0x6a, 0x95, 0xa6, 0xa1, + 0x29, 0x04, 0x41, 0x1a, 0x89, 0xf1, 0x18, 0xc0, 0xa4, 0xb1, 0xc5, 0x0b, 0x61, 0x01, 0xea, 0xa2, 0x9f, 0x94, 0xb8, + 0xc8, 0x13, 0x79, 0x3b, 0x75, 0x13, 0x8b, 0x26, 0x9a, 0xc5, 0x3c, 0x49, 0x54, 0x6b, 0x1c, 0xf7, 0x7c, 0xd8, 0x7b, + 0x0a, 0x2a, 0xcd, 0x8d, 0xa1, 0xfd, 0x1a, 0x94, 0x6d, 0x6e, 0x61, 0xa6, 0x56, 0xd5, 0xc6, 0x59, 0x5b, 0x1b, 0x5f, + 0x63, 0x20, 0x6b, 0xf8, 0x0b, 0x80, 0x70, 0xcc, 0xdf, 0x78, 0x76, 0xb4, 0x0f, 0xbf, 0xa0, 0x78, 0xef, 0x6b, 0x22, + 0xe6, 0xb0, 0xb8, 0xd2, 0xd0, 0x79, 0x75, 0xd7, 0x45, 0x28, 0x4d, 0x3a, 0x7b, 0xb9, 0x38, 0x7b, 0xa9, 0x3c, 0x7b, + 0x19, 0xb9, 0x53, 0x4b, 0xda, 0x83, 0x6d, 0x67, 0xd1, 0x04, 0x9d, 0xcc, 0xee, 0x50, 0x07, 0xaf, 0x15, 0x41, 0xaf, + 0x27, 0xb6, 0xf4, 0x08, 0x65, 0xa3, 0x5e, 0xca, 0x07, 0xd5, 0x4a, 0xba, 0xc4, 0x86, 0x01, 0x73, 0xa0, 0xf0, 0x50, + 0xd2, 0x9b, 0x33, 0xa2, 0x0e, 0xfd, 0x1c, 0x1e, 0x11, 0x01, 0x2f, 0xfd, 0xb4, 0x97, 0x74, 0xe7, 0x22, 0xca, 0xf7, + 0xd4, 0xcf, 0x7a, 0x39, 0xfc, 0x62, 0x6a, 0x66, 0x54, 0xcd, 0x4d, 0x3b, 0x11, 0xe9, 0x99, 0x17, 0xfe, 0xfe, 0x62, + 0x03, 0xe9, 0x58, 0xf4, 0x64, 0x36, 0x3d, 0x1f, 0x9f, 0x23, 0x25, 0x03, 0x17, 0x61, 0x06, 0x17, 0x21, 0x74, 0x2f, + 0xe1, 0xea, 0xce, 0xbc, 0xa9, 0x34, 0x31, 0x9e, 0x94, 0x88, 0x07, 0x70, 0x54, 0x18, 0x12, 0x8f, 0x9f, 0x3e, 0x42, + 0xeb, 0xf6, 0x0c, 0x70, 0xdb, 0xd2, 0x9d, 0x9a, 0xf6, 0x99, 0xa7, 0xa6, 0x44, 0x6a, 0x45, 0x31, 0xd6, 0x94, 0xa1, + 0xe2, 0xca, 0x38, 0xf7, 0x70, 0xff, 0xf0, 0xe6, 0xea, 0xdc, 0x44, 0x45, 0x6f, 0x38, 0xca, 0x31, 0x62, 0x6b, 0xde, + 0xeb, 0x69, 0x14, 0xd2, 0x44, 0x3f, 0x22, 0xd1, 0xf3, 0x46, 0xaa, 0x62, 0xdb, 0xd6, 0xfc, 0x81, 0x31, 0x4d, 0xe9, + 0xdd, 0x28, 0x1f, 0x23, 0x56, 0x9c, 0x23, 0x6e, 0x44, 0xe8, 0xa8, 0x84, 0x4e, 0x80, 0xc0, 0x33, 0x81, 0xc0, 0x61, + 0x31, 0x26, 0xb0, 0x18, 0x73, 0x03, 0xac, 0xcd, 0xe0, 0xee, 0x8e, 0x8e, 0x63, 0xf8, 0xa8, 0x86, 0xd0, 0xf3, 0x23, + 0x0c, 0xa2, 0x05, 0x20, 0xcd, 0x90, 0xba, 0x6e, 0xa1, 0xd3, 0x10, 0x99, 0x84, 0x7a, 0xc8, 0xaa, 0x50, 0x18, 0x01, + 0xdd, 0x12, 0x3d, 0xa3, 0x89, 0x0a, 0x7e, 0x81, 0x2e, 0x32, 0x21, 0x30, 0xcd, 0x56, 0x69, 0xae, 0xe4, 0xdb, 0xac, + 0xee, 0xed, 0x8a, 0xab, 0x89, 0xc6, 0x9e, 0x64, 0x9f, 0xe7, 0xe4, 0xfd, 0x20, 0x83, 0x5d, 0xeb, 0x5f, 0x31, 0x3e, + 0x87, 0x31, 0x5d, 0x8b, 0xa7, 0x02, 0x68, 0x82, 0x9f, 0x44, 0x42, 0x1b, 0x96, 0xbe, 0xb5, 0xe0, 0x06, 0xb2, 0x5e, + 0xcc, 0xa5, 0xff, 0x75, 0x0a, 0x30, 0x3c, 0xed, 0x5f, 0x9b, 0x96, 0x54, 0xc3, 0x51, 0xb6, 0x97, 0x90, 0x21, 0x55, + 0xeb, 0xf7, 0x19, 0xd2, 0x73, 0xb9, 0xf4, 0x9d, 0x96, 0xdf, 0x1b, 0xe3, 0x3f, 0x6e, 0xa5, 0x09, 0x98, 0x24, 0xca, + 0xfc, 0xa2, 0x8f, 0x76, 0xec, 0xcb, 0x79, 0x90, 0xc9, 0x55, 0x0a, 0x5c, 0x65, 0xd2, 0x4f, 0x89, 0x2b, 0x46, 0x1b, + 0x19, 0xba, 0x5a, 0xa2, 0x93, 0x00, 0xe6, 0xd0, 0xc4, 0x3b, 0x3b, 0xc0, 0xac, 0xe7, 0xd2, 0x31, 0x2a, 0xad, 0xb8, + 0x07, 0x04, 0x42, 0xe6, 0xcd, 0x2e, 0x01, 0x13, 0x7c, 0x6b, 0xc4, 0xa4, 0xc0, 0x18, 0x83, 0xf9, 0xcc, 0x11, 0x75, + 0x8c, 0xe8, 0x13, 0xfc, 0x42, 0x44, 0x9d, 0x48, 0x2b, 0x57, 0x82, 0xd6, 0x1f, 0xcf, 0x47, 0x42, 0xc1, 0xab, 0x0c, + 0xd7, 0x5f, 0xd9, 0x33, 0x3d, 0x6a, 0x77, 0x2c, 0x3d, 0xf5, 0xeb, 0x3a, 0x34, 0x7a, 0x85, 0x16, 0xbe, 0x2b, 0x36, + 0x8f, 0x8c, 0x54, 0x84, 0x54, 0x0e, 0x56, 0xaa, 0x0f, 0x12, 0xee, 0x3f, 0xcb, 0xd9, 0x8a, 0xd8, 0x54, 0x8f, 0xdc, + 0x2a, 0x67, 0x13, 0xdb, 0x5f, 0x91, 0xa0, 0x5d, 0xb7, 0x94, 0x19, 0xb4, 0x45, 0x8b, 0xcf, 0xae, 0xb2, 0xc4, 0x6c, + 0x14, 0x32, 0xcd, 0xc7, 0x81, 0xad, 0x5e, 0xc4, 0xe7, 0xec, 0x63, 0xd5, 0x8c, 0xe3, 0x27, 0xf1, 0x0f, 0x40, 0x85, + 0x65, 0x52, 0x51, 0x82, 0xa0, 0x37, 0x87, 0xb4, 0x2b, 0xe4, 0x84, 0x86, 0x92, 0x03, 0xa7, 0xbd, 0x02, 0x8a, 0x89, + 0x01, 0x98, 0x90, 0xf2, 0x88, 0x04, 0x87, 0xb2, 0x0e, 0xdf, 0x26, 0xa8, 0x24, 0xe0, 0x5a, 0x65, 0xce, 0x75, 0xa5, + 0x33, 0x71, 0x36, 0xc0, 0x2c, 0x75, 0x0b, 0x7a, 0x74, 0xaa, 0xab, 0x51, 0xaf, 0x8d, 0x3c, 0x4d, 0x42, 0x95, 0xe1, + 0xc9, 0x69, 0xae, 0x92, 0x51, 0xdf, 0xd0, 0x0b, 0x27, 0x38, 0x9f, 0x7f, 0x5e, 0x4c, 0x38, 0xac, 0x89, 0x09, 0xc9, + 0xd2, 0x41, 0x88, 0x0e, 0x1a, 0xca, 0x2b, 0xf5, 0x92, 0x98, 0xce, 0xc1, 0xef, 0xd7, 0x63, 0x35, 0x15, 0x08, 0xb5, + 0x49, 0x6e, 0xd6, 0x37, 0x0b, 0x45, 0x0d, 0xa4, 0x66, 0xe7, 0x76, 0xd8, 0xb4, 0x13, 0x0e, 0x5d, 0x45, 0xe6, 0xda, + 0xac, 0x52, 0xb1, 0x99, 0x6c, 0x39, 0x58, 0x72, 0x19, 0x94, 0x9d, 0x2a, 0xf9, 0x1c, 0xd4, 0xfc, 0x08, 0x5e, 0x57, + 0x95, 0x67, 0x26, 0x99, 0x25, 0xc5, 0x0b, 0xee, 0x21, 0x7c, 0x73, 0x54, 0x95, 0x8e, 0xe5, 0x37, 0x37, 0x39, 0x19, + 0x4b, 0xe4, 0x9e, 0x05, 0x57, 0x48, 0xc6, 0xe1, 0x12, 0xad, 0x1b, 0xd2, 0xa7, 0x66, 0x7c, 0xda, 0x84, 0x7c, 0x8f, + 0xd7, 0x19, 0x48, 0x8c, 0x0c, 0xc9, 0x43, 0x51, 0x19, 0x8e, 0x28, 0x1e, 0x4f, 0x42, 0x11, 0x9f, 0x9f, 0x65, 0x67, + 0x55, 0xde, 0x6a, 0xe0, 0xd2, 0xff, 0x9c, 0xb2, 0xce, 0x73, 0x49, 0x98, 0x68, 0x9e, 0xe5, 0x6e, 0x4d, 0x61, 0x11, + 0xd1, 0xb5, 0x31, 0xcf, 0x6f, 0xb5, 0x46, 0xd2, 0xcb, 0x75, 0x0d, 0x63, 0x43, 0x7b, 0x9a, 0x57, 0x69, 0xec, 0xf5, + 0x96, 0xab, 0xd5, 0xc5, 0x62, 0x0c, 0x24, 0x59, 0x32, 0xb8, 0x4e, 0x43, 0xac, 0xf4, 0xd3, 0xa6, 0xdd, 0xd8, 0x47, + 0x41, 0xee, 0xde, 0xdc, 0x0c, 0x9d, 0xba, 0x7d, 0x30, 0xf1, 0x4b, 0xd4, 0x88, 0xf6, 0x0e, 0xeb, 0xfc, 0x60, 0x17, + 0x8f, 0xa2, 0xee, 0x2f, 0xb4, 0xce, 0xb8, 0xfa, 0x31, 0x1b, 0xfa, 0xbc, 0xca, 0xd2, 0x73, 0x9e, 0x94, 0x29, 0x74, + 0xab, 0x99, 0x7a, 0xc3, 0xb9, 0xaf, 0x46, 0xf9, 0x37, 0xa7, 0xa2, 0xa4, 0x78, 0x3d, 0x25, 0x8c, 0xab, 0xc4, 0x6d, + 0xd2, 0x51, 0x4b, 0x85, 0x40, 0x54, 0xd7, 0x77, 0x1e, 0x45, 0x9e, 0x54, 0x67, 0xe2, 0x77, 0x8f, 0x22, 0x53, 0xba, + 0xd6, 0x1c, 0xe2, 0x5d, 0x43, 0xdb, 0x6c, 0x2e, 0x74, 0xcb, 0xe8, 0x6e, 0x1f, 0x9e, 0xaa, 0x1f, 0x79, 0xf2, 0x8b, + 0x2e, 0x0d, 0xab, 0x49, 0xb8, 0x34, 0x0c, 0xf7, 0x8d, 0xed, 0xa1, 0x5c, 0x40, 0x28, 0x31, 0x23, 0x2f, 0x03, 0x9d, + 0xb8, 0x40, 0xb3, 0x30, 0x68, 0x88, 0x2b, 0x47, 0x72, 0x2d, 0xac, 0x6c, 0xcd, 0xc8, 0xdb, 0xa4, 0x10, 0x2c, 0xcb, + 0x16, 0x4e, 0x32, 0xc2, 0xe4, 0x84, 0x35, 0xf7, 0xbe, 0xfa, 0xd9, 0xe9, 0xfd, 0xd8, 0x95, 0x59, 0x73, 0x80, 0x7c, + 0x32, 0x8c, 0xda, 0x1e, 0x09, 0x75, 0xaf, 0xa4, 0x55, 0xae, 0x3d, 0xc3, 0xfa, 0x4d, 0xbe, 0x94, 0xe4, 0x0b, 0xb1, + 0xa5, 0xe8, 0xd0, 0x58, 0x1f, 0x85, 0x3e, 0x2c, 0x06, 0x6a, 0x55, 0xb2, 0xd6, 0xda, 0x78, 0x95, 0x54, 0x74, 0xfd, + 0x99, 0x8b, 0x1c, 0x6d, 0x29, 0xac, 0x3e, 0xbc, 0xbd, 0x61, 0x3d, 0x04, 0x34, 0x68, 0x91, 0x55, 0xb0, 0x05, 0x2e, + 0x16, 0xaa, 0x76, 0xb5, 0x25, 0x66, 0xbb, 0xf7, 0x62, 0x66, 0x5b, 0xd1, 0xaf, 0xde, 0xb4, 0x3b, 0x3e, 0x27, 0x3f, + 0xcc, 0x6f, 0x94, 0x93, 0x92, 0x36, 0x8c, 0xab, 0xf9, 0xff, 0x15, 0xee, 0x59, 0x16, 0x87, 0xde, 0x4a, 0xd2, 0x60, + 0x2a, 0xd5, 0xa6, 0x19, 0x1a, 0xa3, 0xd0, 0xc7, 0x86, 0x81, 0x68, 0x71, 0x85, 0x82, 0x19, 0xa6, 0xbe, 0x92, 0x3a, + 0xad, 0xc4, 0xb0, 0xff, 0xee, 0x45, 0xe7, 0x09, 0x4a, 0xdb, 0x13, 0xc7, 0x8d, 0x9a, 0xd8, 0x42, 0x9e, 0x5a, 0xe8, + 0xc4, 0x24, 0xbb, 0x12, 0x83, 0x31, 0x2a, 0xc4, 0x2f, 0x2a, 0x56, 0x24, 0x18, 0xcf, 0xff, 0x5b, 0x98, 0x5a, 0x1d, + 0xa2, 0x23, 0xd1, 0x59, 0xf5, 0xe9, 0x74, 0x57, 0x74, 0xce, 0x90, 0x44, 0x44, 0x5b, 0x2a, 0x5a, 0x8f, 0x5c, 0xb8, + 0x41, 0xe2, 0x46, 0x44, 0x32, 0xeb, 0x65, 0xcb, 0xc8, 0x58, 0x56, 0x85, 0x34, 0x3f, 0xbb, 0xca, 0xb4, 0xa0, 0x86, + 0x87, 0x0f, 0x4f, 0x43, 0x99, 0xfa, 0xd5, 0x35, 0x4a, 0x99, 0xf2, 0x90, 0xaa, 0x0e, 0x4e, 0x63, 0x04, 0x6c, 0x14, + 0xa2, 0x41, 0x68, 0x2a, 0x24, 0xe6, 0x6c, 0x35, 0xf1, 0xef, 0xb1, 0x90, 0x33, 0x61, 0x50, 0x2f, 0x20, 0xd1, 0xd2, + 0xaf, 0x5f, 0x66, 0x60, 0xf0, 0xa7, 0x7e, 0x6e, 0xf2, 0x42, 0x4b, 0x14, 0x28, 0xa6, 0xd5, 0x92, 0xd1, 0xb1, 0x18, + 0xe7, 0x14, 0xe6, 0x93, 0xb9, 0x0b, 0x58, 0x44, 0x5c, 0x53, 0x25, 0x67, 0x27, 0x27, 0x3b, 0xb9, 0xeb, 0x01, 0x30, + 0x99, 0xc3, 0x51, 0x80, 0x6c, 0x5a, 0xa0, 0xd9, 0xb4, 0x59, 0x95, 0xe3, 0xaa, 0x5c, 0x9c, 0x0a, 0xec, 0x42, 0x69, + 0x9b, 0xa0, 0xf6, 0x43, 0x83, 0xda, 0x5f, 0x96, 0xfe, 0x6c, 0xb4, 0xb1, 0x04, 0x2a, 0x3f, 0x44, 0x1b, 0x51, 0x83, + 0x8e, 0x5f, 0xb2, 0x74, 0x5d, 0x51, 0xf9, 0x21, 0xfe, 0x36, 0xe8, 0xfa, 0x99, 0x19, 0x5c, 0xce, 0x2d, 0xea, 0xd4, + 0xfd, 0xac, 0x19, 0x59, 0xee, 0x5e, 0x4b, 0x1b, 0x89, 0x1d, 0xaa, 0x82, 0x67, 0xa9, 0xbd, 0x23, 0x2d, 0xd8, 0xdc, + 0x6f, 0x07, 0x3c, 0x01, 0xda, 0xb1, 0x17, 0x95, 0xcb, 0x51, 0x48, 0x26, 0xab, 0x02, 0x02, 0x4d, 0x90, 0x27, 0x87, + 0x0e, 0x75, 0xe6, 0xc0, 0x48, 0xcd, 0x31, 0xae, 0x58, 0xa1, 0x98, 0x0c, 0xa7, 0x2c, 0xea, 0x47, 0x9c, 0x7d, 0x85, + 0xe1, 0x90, 0xd3, 0x2f, 0x49, 0x54, 0xdd, 0x79, 0xe4, 0xd1, 0xff, 0x5b, 0xa9, 0x55, 0x36, 0xf4, 0x26, 0x57, 0x1c, + 0xff, 0x5a, 0x61, 0xfb, 0xc0, 0x91, 0x91, 0x80, 0x47, 0xea, 0x30, 0x00, 0xdd, 0x9c, 0x05, 0x49, 0x3e, 0x97, 0x39, + 0xc7, 0xd6, 0x4e, 0x8d, 0x1c, 0x94, 0xd1, 0xaf, 0x1b, 0x3f, 0x91, 0x3c, 0xb0, 0x12, 0xe8, 0x88, 0x42, 0xc9, 0x0c, + 0xfb, 0x92, 0x19, 0x76, 0xdb, 0xae, 0x0a, 0x2e, 0x2f, 0x5f, 0x95, 0x09, 0xbb, 0x1a, 0x6d, 0x44, 0x72, 0x97, 0xaa, + 0xb3, 0x98, 0xb7, 0x9f, 0x49, 0x43, 0xe2, 0xef, 0xce, 0x0c, 0x79, 0x3d, 0x2e, 0x48, 0x7a, 0x9f, 0xa3, 0xa1, 0x07, + 0x85, 0xc9, 0xa9, 0xf9, 0x06, 0xc2, 0x86, 0x61, 0x64, 0x7e, 0xda, 0x86, 0x6f, 0x84, 0x38, 0x07, 0xa8, 0x3b, 0x6a, + 0x19, 0xce, 0xa0, 0x50, 0x0f, 0x21, 0xcf, 0x7b, 0x1e, 0x05, 0x98, 0xf8, 0xf0, 0x13, 0x5d, 0x06, 0xce, 0x6e, 0xeb, + 0xc8, 0x2c, 0x6d, 0x06, 0x74, 0x9b, 0xf7, 0x2b, 0x42, 0x25, 0x25, 0xc3, 0x9b, 0xe0, 0x78, 0x1b, 0x02, 0xa3, 0x42, + 0x0b, 0x64, 0x7a, 0xd9, 0xe6, 0x56, 0x2f, 0x64, 0x41, 0x51, 0x2f, 0xed, 0xcd, 0x48, 0x0e, 0x48, 0x45, 0x28, 0x30, + 0xca, 0xba, 0xa1, 0xe8, 0x8c, 0x5f, 0xc0, 0x4f, 0xe6, 0xaa, 0x9c, 0xf2, 0x38, 0x06, 0x94, 0x29, 0x46, 0x04, 0x44, + 0x6b, 0x2f, 0x75, 0x67, 0xf2, 0xa6, 0xce, 0x85, 0xf4, 0x82, 0x8f, 0xe3, 0x73, 0x51, 0x86, 0xab, 0x78, 0xa0, 0x4b, + 0xc4, 0x5b, 0xbe, 0xcf, 0xe6, 0x5b, 0x2a, 0x29, 0x29, 0x36, 0xb5, 0x71, 0x88, 0xf1, 0xd4, 0x7e, 0x8a, 0x8b, 0x39, + 0x6a, 0x77, 0x51, 0xd9, 0x58, 0x48, 0xe7, 0xf9, 0x4a, 0x32, 0x73, 0xd4, 0x36, 0x16, 0x55, 0x1f, 0x7a, 0x29, 0x46, + 0xdd, 0x18, 0xb8, 0x22, 0xee, 0x09, 0x3e, 0xaa, 0xa4, 0xe9, 0x46, 0xe6, 0x39, 0xd7, 0x00, 0xef, 0xe6, 0x67, 0x1a, + 0xec, 0x0c, 0xef, 0x8c, 0x65, 0x52, 0xd6, 0xd1, 0x24, 0x5a, 0xa8, 0x6a, 0x42, 0x17, 0x39, 0x32, 0xd6, 0x7e, 0x36, + 0xb6, 0x9f, 0xe2, 0x99, 0xdc, 0x6e, 0x87, 0xe6, 0x9a, 0xd2, 0xb0, 0x9a, 0x14, 0x51, 0x30, 0xe8, 0xb5, 0xad, 0xf6, + 0xb6, 0x5c, 0x63, 0x22, 0x78, 0xba, 0xa0, 0x67, 0xd4, 0x00, 0x0c, 0x61, 0xa4, 0xb2, 0x37, 0x93, 0x82, 0x29, 0x95, + 0xaa, 0x60, 0xd7, 0x6d, 0x0a, 0xa5, 0x61, 0x32, 0x65, 0x6d, 0x89, 0x55, 0xc0, 0x00, 0x4b, 0xaf, 0x1e, 0x6f, 0xbf, + 0x55, 0x8d, 0x0a, 0xe0, 0x5a, 0x15, 0xee, 0x4c, 0xd4, 0x98, 0x88, 0x77, 0x7c, 0x69, 0xab, 0xa5, 0x46, 0x57, 0x66, + 0x00, 0x15, 0x73, 0x97, 0x8e, 0xa7, 0x72, 0xc5, 0x2c, 0x5c, 0x77, 0x4b, 0x9b, 0xea, 0x3d, 0x8b, 0xd1, 0x7a, 0x62, + 0x3e, 0x8f, 0xf3, 0x08, 0x0a, 0x70, 0x47, 0xd2, 0xf3, 0x73, 0xd8, 0x6f, 0x58, 0x06, 0x5e, 0x00, 0xb2, 0x68, 0xce, + 0xbd, 0x61, 0xb4, 0x0d, 0x1b, 0xb4, 0xa6, 0x4e, 0xb4, 0x2d, 0x6a, 0x3d, 0x86, 0xe5, 0x02, 0x68, 0x0e, 0x53, 0x6d, + 0x54, 0x7a, 0x1c, 0xed, 0x18, 0x95, 0x66, 0xe9, 0x32, 0x6b, 0x54, 0xd9, 0x7e, 0x1c, 0xed, 0x8a, 0x3a, 0x5b, 0x3b, + 0xa5, 0x1b, 0xc2, 0x6e, 0xd4, 0xab, 0x3c, 0x7d, 0xbc, 0xa3, 0xea, 0x6c, 0x43, 0x3b, 0x97, 0x51, 0xf4, 0x59, 0x57, + 0x1a, 0x8a, 0xae, 0x06, 0x3b, 0x4f, 0x55, 0x2d, 0x68, 0x08, 0xde, 0xc1, 0xa1, 0xac, 0x37, 0xb5, 0xf5, 0x78, 0xeb, + 0x69, 0xf4, 0x58, 0x4e, 0x6f, 0xab, 0x74, 0xff, 0xb1, 0x84, 0xe3, 0x17, 0x65, 0x8d, 0xe6, 0x9e, 0x3c, 0x7d, 0xba, + 0xa3, 0x2a, 0x42, 0x73, 0xd7, 0x70, 0x83, 0x9a, 0x63, 0x1f, 0xee, 0xee, 0x44, 0x4f, 0xca, 0xd2, 0xfd, 0xd9, 0x37, + 0x93, 0xa3, 0x3e, 0x8b, 0x0d, 0x3d, 0xfc, 0x3c, 0xad, 0x46, 0x0d, 0xe8, 0x19, 0xd1, 0x00, 0x66, 0xa9, 0x52, 0xd3, + 0xac, 0xf1, 0xca, 0x45, 0xb7, 0xef, 0xe3, 0x20, 0x0c, 0x16, 0x88, 0x08, 0x56, 0x64, 0x9c, 0x95, 0x21, 0xa5, 0x8a, + 0xb4, 0x27, 0xd0, 0x57, 0x71, 0x9e, 0xfe, 0x0c, 0x8b, 0x81, 0x8b, 0x46, 0x21, 0x6d, 0x38, 0x33, 0xd0, 0xfb, 0x85, + 0xc8, 0x6c, 0x44, 0xfe, 0x9b, 0xd5, 0x3c, 0x38, 0x66, 0x18, 0xbd, 0x87, 0x0f, 0xed, 0xcc, 0x4f, 0xec, 0x0c, 0x80, + 0xf7, 0xaf, 0xf0, 0x2f, 0x10, 0x0b, 0x99, 0x6f, 0xd4, 0x93, 0xbe, 0xe7, 0xc2, 0x28, 0xcc, 0x46, 0xd1, 0x9d, 0xa7, + 0x7e, 0x90, 0xea, 0xd1, 0x74, 0xe8, 0xc6, 0x7c, 0x59, 0xd0, 0x00, 0x59, 0xd5, 0xe0, 0x0e, 0x61, 0xf3, 0x6f, 0x23, + 0x3b, 0x45, 0x9f, 0x78, 0x0c, 0x1f, 0x3d, 0x70, 0xc6, 0x11, 0xb3, 0xb5, 0xef, 0xa7, 0xd0, 0x96, 0x25, 0xc6, 0x8e, + 0x49, 0x07, 0x3c, 0xf3, 0x05, 0x7a, 0x0a, 0x74, 0xcd, 0xb0, 0xb0, 0xcb, 0x96, 0x78, 0x3e, 0x3f, 0x4b, 0xd2, 0x51, + 0xa7, 0x1f, 0xfd, 0x59, 0xb9, 0xb0, 0xc3, 0xf2, 0xa7, 0x7b, 0x39, 0x50, 0x56, 0xdd, 0x6e, 0xaa, 0xf3, 0xb8, 0x3d, + 0x8b, 0x0f, 0x7f, 0x3e, 0x4c, 0x8f, 0x8e, 0x48, 0xf7, 0x4d, 0xfb, 0x3a, 0x16, 0x7f, 0x3d, 0xe1, 0x7c, 0xf0, 0xf6, + 0xd9, 0x5f, 0x8f, 0x0f, 0x9e, 0xbd, 0x42, 0xe7, 0x83, 0x4f, 0x2f, 0xbf, 0x7d, 0xf9, 0x91, 0x93, 0xbb, 0xf3, 0x9e, + 0x3f, 0x7c, 0xa8, 0xa5, 0x3e, 0x76, 0x04, 0x6c, 0xef, 0xa6, 0x1d, 0x3c, 0xca, 0xd8, 0xe8, 0xc1, 0xd9, 0xf3, 0x55, + 0x28, 0x64, 0xec, 0xa2, 0x54, 0xcf, 0x30, 0x08, 0x23, 0x98, 0xc5, 0x55, 0x45, 0x84, 0x6b, 0x8e, 0x5c, 0x25, 0x59, + 0x4b, 0xf7, 0x8d, 0x79, 0x00, 0x31, 0x9a, 0x6a, 0xb2, 0x30, 0xf3, 0xb1, 0x6d, 0x1c, 0x13, 0xcc, 0x24, 0x3b, 0x52, + 0xe3, 0xd2, 0x07, 0x04, 0x08, 0x90, 0xe9, 0xd4, 0xe6, 0xd0, 0xd5, 0xfb, 0xa8, 0x01, 0x90, 0x83, 0xca, 0xf4, 0x88, + 0xa2, 0xb1, 0xd9, 0xbe, 0x37, 0x30, 0x86, 0x77, 0x41, 0xba, 0x27, 0x39, 0xac, 0xa2, 0xb2, 0xa0, 0xdd, 0x21, 0x10, + 0x3f, 0x6a, 0xd1, 0x65, 0x32, 0x19, 0x1e, 0xcb, 0xcf, 0xc0, 0x4f, 0xc9, 0xe1, 0xe8, 0x65, 0x28, 0x8c, 0x96, 0xa7, + 0xca, 0x56, 0x97, 0x73, 0x38, 0xc5, 0x94, 0xf6, 0x68, 0x40, 0x22, 0xf5, 0x4e, 0xd3, 0x3b, 0x7e, 0x35, 0x4f, 0x31, + 0x9b, 0x62, 0x8c, 0xd2, 0xf9, 0x67, 0x09, 0x3b, 0x97, 0xa7, 0xc0, 0x6b, 0x27, 0x47, 0xfb, 0xe8, 0x76, 0x0e, 0x7f, + 0x3d, 0x4a, 0xca, 0x17, 0x63, 0xae, 0x12, 0xb4, 0x7b, 0xd1, 0x21, 0x7c, 0x5b, 0x43, 0x1b, 0xa8, 0x0b, 0x94, 0xfa, + 0xdd, 0x5c, 0x9d, 0x34, 0x8a, 0x72, 0xc7, 0x1e, 0x6d, 0x98, 0x79, 0xc8, 0x2f, 0x0e, 0x8b, 0xba, 0x27, 0x9b, 0x64, + 0x4c, 0xe8, 0x94, 0x05, 0x7e, 0x3a, 0x0a, 0xf6, 0xfc, 0x6c, 0x14, 0x60, 0x2b, 0x80, 0x08, 0x80, 0x7a, 0x1a, 0xc2, + 0xa7, 0xce, 0x04, 0x86, 0x16, 0x1c, 0xb9, 0x13, 0x92, 0x12, 0xd8, 0x98, 0xf2, 0xa3, 0x4f, 0xb6, 0x39, 0x78, 0xe4, + 0xd5, 0xf5, 0x33, 0xf4, 0x73, 0x0d, 0xc3, 0x65, 0x52, 0x84, 0xae, 0xc8, 0x59, 0xc3, 0xe4, 0x88, 0x80, 0x75, 0xa7, + 0x8e, 0x31, 0x09, 0x78, 0x46, 0x59, 0x09, 0x33, 0x27, 0x80, 0x61, 0x66, 0x50, 0x1d, 0x3a, 0xf4, 0x33, 0xb7, 0x6a, + 0x73, 0x1a, 0x08, 0x93, 0xa0, 0x0d, 0x1d, 0x4b, 0xad, 0x93, 0xb2, 0x0a, 0x71, 0x23, 0x1a, 0x27, 0xde, 0xa5, 0x30, + 0x34, 0xc0, 0x28, 0x50, 0x2c, 0x17, 0xbf, 0xbc, 0xbf, 0x87, 0x1b, 0x87, 0xfe, 0xf7, 0x57, 0x32, 0xdb, 0xd9, 0x9c, + 0x91, 0x1e, 0x3c, 0x01, 0x92, 0xc1, 0x54, 0x6c, 0x32, 0x02, 0x79, 0x12, 0x17, 0x98, 0xd1, 0xe2, 0xda, 0x92, 0x29, + 0xe1, 0x70, 0x4c, 0x3f, 0x62, 0x69, 0x45, 0x4c, 0xce, 0x9e, 0x18, 0x34, 0x6d, 0x2e, 0x48, 0x10, 0xa5, 0xcf, 0xe1, + 0x3a, 0x55, 0x62, 0xac, 0x09, 0x68, 0x26, 0xf3, 0x6d, 0x70, 0x44, 0x83, 0x5a, 0x88, 0x66, 0xf4, 0xfe, 0x39, 0x8f, + 0x88, 0xd5, 0xc1, 0x07, 0x7c, 0xa7, 0xf3, 0xcc, 0xf3, 0xce, 0x53, 0xe4, 0xd4, 0x67, 0x73, 0x8a, 0x7e, 0x09, 0x44, + 0x67, 0x5f, 0x14, 0x53, 0xae, 0xa4, 0x08, 0xf5, 0x36, 0xd4, 0x30, 0x90, 0x9e, 0x8b, 0xc8, 0x56, 0x74, 0xfc, 0x2b, + 0x22, 0x32, 0x72, 0x57, 0x5a, 0xd1, 0xe5, 0x2a, 0x46, 0x9c, 0x31, 0x30, 0x05, 0x93, 0x19, 0x2e, 0x66, 0x02, 0x34, + 0x27, 0x6c, 0x1a, 0x60, 0x02, 0xe8, 0xa4, 0xaf, 0x7f, 0x00, 0x56, 0x35, 0x23, 0x34, 0x34, 0x97, 0x20, 0xea, 0xeb, + 0x1f, 0x2d, 0xfe, 0x7f, 0x86, 0x5d, 0x20, 0xc1, 0xde, 0x99, 0xcc, 0x8c, 0xe7, 0x94, 0x95, 0x38, 0x28, 0xd2, 0xc7, + 0xbe, 0x5a, 0x78, 0xcf, 0x1d, 0xbd, 0x4d, 0x2a, 0xdf, 0xe2, 0x83, 0x65, 0xae, 0xb7, 0x2b, 0x77, 0xa5, 0x8f, 0xe7, + 0xe1, 0xe6, 0x86, 0x0e, 0x44, 0xdd, 0x05, 0xd0, 0x35, 0x8c, 0x57, 0x33, 0xd3, 0x78, 0x35, 0x58, 0x63, 0xbc, 0xaa, + 0xad, 0xb0, 0xec, 0xb9, 0xb3, 0x22, 0x7d, 0x16, 0xcb, 0xf3, 0xe7, 0x24, 0x13, 0xac, 0xba, 0x9c, 0xe5, 0x2e, 0x97, + 0x3a, 0xee, 0x46, 0x60, 0x56, 0x04, 0x6e, 0x93, 0xf2, 0xac, 0xe8, 0x18, 0x09, 0x2e, 0x97, 0x3a, 0xa5, 0xbd, 0x91, + 0xa1, 0x7a, 0x0c, 0xdf, 0xcb, 0x18, 0xa2, 0x52, 0xc6, 0x2e, 0xc7, 0xe0, 0xb8, 0xb6, 0xb4, 0x1e, 0xdd, 0x50, 0xd6, + 0xa3, 0x37, 0x37, 0x85, 0xf4, 0xb7, 0x03, 0x0a, 0x67, 0x42, 0x51, 0x85, 0x79, 0x35, 0x31, 0xbc, 0xe9, 0x44, 0x71, + 0x4b, 0x5a, 0x69, 0x41, 0xe9, 0xb3, 0x7f, 0xb5, 0x73, 0xad, 0x92, 0xc8, 0x9d, 0x71, 0xee, 0x75, 0x35, 0x1e, 0x84, + 0x25, 0xc7, 0x33, 0x80, 0x99, 0x1c, 0xc9, 0xc3, 0xf5, 0x57, 0x40, 0xa4, 0xaa, 0x72, 0xea, 0x8c, 0x53, 0xac, 0x0c, + 0x37, 0xb7, 0x5e, 0xb5, 0x3b, 0xd4, 0x26, 0xb5, 0xc6, 0x5a, 0xa4, 0x3d, 0x19, 0xf9, 0x01, 0x95, 0x21, 0x3a, 0x3e, + 0x39, 0x54, 0x4f, 0x39, 0x95, 0x6a, 0x65, 0x9a, 0xed, 0x81, 0x57, 0x3e, 0xc1, 0x86, 0xc2, 0xf8, 0xce, 0x17, 0xd2, + 0x92, 0x38, 0xf2, 0xd7, 0xb9, 0xed, 0xc1, 0x01, 0x10, 0xaf, 0xde, 0xbd, 0xfc, 0xf6, 0x59, 0xe5, 0x55, 0x33, 0xe2, + 0xa8, 0x8d, 0xb6, 0x15, 0x03, 0xca, 0xde, 0x62, 0xc2, 0x60, 0x87, 0x5d, 0x23, 0xc8, 0xbb, 0x14, 0xf5, 0xdb, 0xf7, + 0xf5, 0x04, 0x3c, 0x8f, 0xc4, 0xf1, 0x83, 0x9a, 0x2e, 0x05, 0x8d, 0xa5, 0x5d, 0xf1, 0xf5, 0xae, 0x8c, 0xd7, 0x4e, + 0xcb, 0x93, 0xdb, 0xce, 0x1a, 0x19, 0xcc, 0x57, 0xdb, 0x62, 0x2c, 0x9c, 0xeb, 0xa1, 0xab, 0xc5, 0x16, 0xe0, 0x8f, + 0x2d, 0x91, 0x6f, 0x6e, 0x72, 0x9c, 0x90, 0x5a, 0x70, 0xe3, 0x65, 0x70, 0x85, 0x2f, 0x73, 0x63, 0x9a, 0xca, 0xf4, + 0x5a, 0xb6, 0x25, 0x45, 0x65, 0x68, 0x59, 0x1c, 0xf8, 0xf1, 0xc4, 0xe6, 0xdc, 0x5c, 0x15, 0x91, 0x37, 0x03, 0x5a, + 0x79, 0xbf, 0x00, 0x68, 0xa1, 0xdd, 0xc9, 0xc1, 0xe7, 0x78, 0x31, 0x5e, 0x62, 0xd4, 0x7c, 0x68, 0x05, 0x61, 0xae, + 0x3a, 0x0b, 0x6a, 0x28, 0x6e, 0xf5, 0x5c, 0x3f, 0x0f, 0x16, 0xc1, 0x04, 0x55, 0x37, 0xe8, 0x2d, 0x72, 0x25, 0x44, + 0x57, 0x32, 0xba, 0xa8, 0x7b, 0x4b, 0x3b, 0x0a, 0x14, 0x6a, 0xf8, 0xbe, 0x91, 0x30, 0xde, 0x93, 0x01, 0x97, 0x44, + 0xd4, 0x3c, 0x1e, 0x29, 0xac, 0x1e, 0x92, 0xd0, 0xd6, 0x98, 0xc1, 0x96, 0x77, 0x21, 0x63, 0x52, 0xe0, 0x5b, 0x19, + 0xf8, 0x03, 0x1e, 0x99, 0x57, 0xcc, 0xed, 0xdc, 0xb0, 0xbf, 0x7e, 0xf8, 0x30, 0x30, 0xec, 0xaf, 0x17, 0x02, 0xd6, + 0x05, 0xf5, 0x01, 0x38, 0x25, 0x25, 0x10, 0x79, 0x26, 0x16, 0x42, 0x66, 0x14, 0xab, 0xfa, 0xfe, 0x3d, 0x93, 0x55, + 0x38, 0x08, 0x7d, 0xa3, 0x5f, 0x43, 0x4a, 0x82, 0x3a, 0xb5, 0xc2, 0xdf, 0xef, 0x0a, 0xb3, 0x0f, 0x4c, 0x88, 0x6a, + 0x56, 0x04, 0xb4, 0xcd, 0xce, 0xc5, 0x9c, 0x3e, 0x1c, 0x58, 0x02, 0x37, 0x1d, 0xb5, 0xf4, 0xa8, 0xbd, 0x0d, 0x09, + 0x40, 0x29, 0xa1, 0x88, 0x32, 0x2f, 0x16, 0x92, 0x10, 0x38, 0x30, 0x24, 0xb8, 0xd2, 0x81, 0x5d, 0x23, 0x7f, 0xd8, + 0xcb, 0xbd, 0xc8, 0xb7, 0xd7, 0x7f, 0x03, 0xc7, 0x87, 0x34, 0x57, 0x66, 0x22, 0xc7, 0x46, 0xa5, 0xca, 0x9d, 0xaa, + 0xf4, 0x90, 0xf8, 0x40, 0x69, 0xf9, 0x76, 0xda, 0xbb, 0xc7, 0xc7, 0x5b, 0x47, 0x0e, 0xaa, 0xc8, 0x94, 0x59, 0x88, + 0x7c, 0xb1, 0xb7, 0x8d, 0x51, 0x63, 0xfa, 0x5b, 0xbb, 0x23, 0x60, 0x56, 0x30, 0x21, 0xfa, 0x00, 0x65, 0xb4, 0x09, + 0x3e, 0x27, 0xfc, 0x5c, 0xc3, 0xf8, 0x66, 0xe8, 0xd7, 0xc4, 0x9d, 0x06, 0x48, 0x70, 0x78, 0xc3, 0x4d, 0x3b, 0xea, + 0x0e, 0xbb, 0xa8, 0x2d, 0x31, 0x6e, 0x5f, 0x4d, 0x2b, 0x2e, 0xb1, 0x4b, 0xab, 0xfb, 0xa7, 0x5b, 0x0d, 0x92, 0x08, + 0x2b, 0x92, 0x33, 0x30, 0xc0, 0x54, 0x96, 0x7c, 0x4d, 0x96, 0x68, 0x59, 0x21, 0x8f, 0x74, 0x24, 0xa3, 0x24, 0x36, + 0x2f, 0x43, 0x44, 0x59, 0xb3, 0x9e, 0xd9, 0x79, 0xcd, 0x8d, 0x1a, 0xc3, 0xe7, 0x4c, 0xfc, 0x4c, 0x71, 0x38, 0xe3, + 0xd4, 0xc0, 0xe9, 0xc8, 0x3a, 0xce, 0xfd, 0xb7, 0x11, 0x05, 0xe3, 0x98, 0x10, 0xc7, 0xe3, 0xce, 0x7c, 0x11, 0x2a, + 0x12, 0x30, 0x3a, 0x9a, 0xf6, 0x20, 0xf5, 0x3e, 0x47, 0xe3, 0x30, 0xbf, 0x5b, 0x28, 0x50, 0x1f, 0x1a, 0xbc, 0x10, + 0x0a, 0xd2, 0x6a, 0x2f, 0xe7, 0x63, 0xc2, 0x9e, 0x1e, 0xc9, 0xfe, 0x88, 0xc2, 0x4f, 0xd0, 0x8e, 0xc2, 0xd9, 0x1f, + 0x46, 0xbb, 0x8f, 0x9a, 0x89, 0xaa, 0xa2, 0xae, 0xd5, 0x09, 0x92, 0xb0, 0x63, 0x75, 0x13, 0x7c, 0xca, 0xa2, 0x4e, + 0x91, 0xa6, 0x9d, 0x69, 0x90, 0x75, 0x60, 0x76, 0x59, 0x01, 0x34, 0x5f, 0x31, 0xeb, 0xc8, 0x09, 0x61, 0x68, 0x5e, + 0xab, 0x1b, 0x40, 0x45, 0x0b, 0x88, 0x4b, 0xb1, 0xb5, 0x0b, 0x3f, 0xaf, 0xa0, 0x33, 0x99, 0xa2, 0xcc, 0xaa, 0x26, + 0x08, 0xab, 0x60, 0x03, 0xa4, 0x53, 0x97, 0x7e, 0xe8, 0x4e, 0x29, 0xdd, 0x40, 0x32, 0x5a, 0xe2, 0x1c, 0x90, 0x46, + 0x5e, 0xc2, 0x3d, 0x85, 0x91, 0x4f, 0xbb, 0x5d, 0x67, 0x8a, 0x99, 0x0b, 0x96, 0xae, 0x4a, 0xe2, 0x00, 0x1f, 0x90, + 0x9d, 0x76, 0x33, 0x30, 0xd3, 0xcd, 0x0d, 0x46, 0x54, 0x1e, 0x02, 0xc2, 0x53, 0xdf, 0xb8, 0x26, 0x83, 0x3d, 0x43, + 0xfb, 0xab, 0xac, 0xb0, 0xe7, 0x29, 0x6b, 0xbe, 0xcf, 0xe0, 0x96, 0x72, 0xc6, 0xe1, 0x86, 0x23, 0x45, 0x3d, 0x2a, + 0x97, 0x22, 0x4f, 0xcc, 0x8b, 0x8a, 0xc7, 0x44, 0xfb, 0x71, 0xc1, 0x7a, 0xcd, 0x8b, 0x78, 0xc1, 0xe4, 0xdf, 0x38, + 0x12, 0xd4, 0x1f, 0x5a, 0x7f, 0x56, 0x5f, 0x21, 0xdb, 0x6a, 0xbc, 0xab, 0x52, 0x8d, 0x44, 0x59, 0x62, 0xbb, 0xc0, + 0xc8, 0xca, 0x3a, 0x51, 0x4d, 0xdf, 0x67, 0x76, 0x5f, 0x27, 0x3a, 0xa5, 0xcf, 0x2a, 0x1e, 0x5c, 0xcc, 0x56, 0x84, + 0x48, 0x23, 0x3d, 0xaa, 0xa2, 0x00, 0xc9, 0xf6, 0xa9, 0x0e, 0x80, 0x6b, 0x3e, 0x2e, 0xc8, 0xbb, 0x15, 0x0b, 0x5e, + 0x09, 0x72, 0xe9, 0xf6, 0x40, 0xca, 0x74, 0x3d, 0x84, 0x84, 0x49, 0xc8, 0x8b, 0x88, 0xef, 0x95, 0x9c, 0xdc, 0x6b, + 0x9b, 0xb2, 0x17, 0x19, 0x76, 0x02, 0xd2, 0xba, 0x2b, 0x6d, 0x1d, 0xf8, 0xc4, 0x1c, 0xf8, 0xdc, 0xaf, 0xc1, 0x68, + 0xb8, 0x11, 0x13, 0x00, 0xe7, 0x68, 0xe2, 0x1d, 0xfa, 0x73, 0x7c, 0x9a, 0x1f, 0xb9, 0x4b, 0x1f, 0x63, 0x8a, 0xc1, + 0xd9, 0x81, 0x82, 0x90, 0x22, 0x91, 0x2c, 0x19, 0x93, 0xdc, 0x31, 0x5d, 0x0c, 0x8c, 0x3e, 0x1d, 0x87, 0xde, 0x6c, + 0xad, 0x99, 0x05, 0x81, 0x67, 0x9c, 0xbb, 0x48, 0xb6, 0x37, 0x30, 0x83, 0x94, 0x47, 0x70, 0x4e, 0xbb, 0xdd, 0xc8, + 0x11, 0xe1, 0xc6, 0x73, 0x16, 0xd7, 0x8a, 0x11, 0xb7, 0x2c, 0x63, 0x2e, 0xb8, 0x45, 0x4a, 0x0e, 0xd8, 0x06, 0x2b, + 0xdb, 0x7d, 0x80, 0x4d, 0xe5, 0x78, 0x9b, 0x2a, 0xbb, 0xd5, 0x63, 0xa6, 0x7a, 0x6c, 0x05, 0xe8, 0x74, 0x93, 0xf5, + 0x40, 0xbe, 0x4a, 0x1d, 0xb4, 0xb9, 0x1c, 0x0b, 0xda, 0xb2, 0x8b, 0x2a, 0xfa, 0x3e, 0x43, 0xdd, 0x7b, 0x38, 0xc2, + 0xdc, 0xda, 0xb7, 0xfc, 0x64, 0x53, 0x36, 0xd8, 0x23, 0x9a, 0xb4, 0x32, 0x14, 0xd4, 0xbd, 0x27, 0x8f, 0xda, 0xe6, + 0xad, 0xe0, 0x2e, 0x42, 0xa2, 0x46, 0xc7, 0x15, 0x3f, 0x26, 0x57, 0xeb, 0x4c, 0x2b, 0x42, 0xff, 0x42, 0x8a, 0xfb, + 0x73, 0xe9, 0x2a, 0x5e, 0xf5, 0x2e, 0x87, 0xdb, 0x0b, 0x7d, 0xb6, 0x87, 0x50, 0x40, 0xaa, 0xda, 0xd0, 0xa9, 0x4b, + 0x43, 0x7a, 0x56, 0xa2, 0xab, 0xe4, 0x60, 0x6b, 0x7d, 0xc6, 0x49, 0xf4, 0x23, 0xd5, 0xc8, 0x97, 0x5e, 0xf2, 0x28, + 0xed, 0x06, 0x8f, 0x32, 0x77, 0x06, 0x4f, 0x19, 0x3c, 0xa5, 0x65, 0xd9, 0xc4, 0x2b, 0xeb, 0xe7, 0x48, 0x34, 0x6b, + 0xe3, 0x2e, 0xe5, 0x78, 0x31, 0x08, 0x77, 0x1d, 0x61, 0x3a, 0x05, 0x43, 0x64, 0xab, 0xe0, 0x43, 0xeb, 0x75, 0x1f, + 0x28, 0x22, 0x09, 0x93, 0x9e, 0xd7, 0xc9, 0x24, 0x97, 0x26, 0x5b, 0x45, 0x7d, 0xb4, 0x05, 0x29, 0x4e, 0xbb, 0x6e, + 0xd6, 0x55, 0x5b, 0x50, 0x2a, 0xa3, 0x35, 0xdf, 0xcc, 0xfa, 0x97, 0xa6, 0xbb, 0x3e, 0xfc, 0x9e, 0x39, 0xbd, 0xa1, + 0xdc, 0xfc, 0x60, 0x7f, 0x30, 0x0e, 0xbc, 0x61, 0xd9, 0x82, 0x2d, 0x44, 0xfc, 0x53, 0x83, 0x0b, 0xa5, 0xc0, 0xa9, + 0x94, 0xc1, 0x11, 0xd6, 0x81, 0x9d, 0x94, 0x2a, 0x31, 0xfc, 0x3b, 0x45, 0x43, 0x9b, 0x06, 0xe3, 0x9c, 0xcc, 0xe2, + 0xe4, 0x4c, 0xa4, 0x0f, 0x17, 0xd9, 0xc5, 0x55, 0x42, 0x3b, 0x83, 0x99, 0xd6, 0xf4, 0xba, 0x53, 0x81, 0x27, 0xba, + 0x67, 0x1f, 0xa9, 0x75, 0x33, 0x43, 0x33, 0x26, 0x46, 0x9b, 0xcf, 0x3f, 0x50, 0xcc, 0xed, 0x9f, 0xd8, 0xe6, 0x97, + 0x61, 0x9f, 0x06, 0x23, 0x79, 0x1d, 0x8c, 0x14, 0x68, 0x84, 0x99, 0x26, 0x13, 0x00, 0x4d, 0x98, 0xc8, 0xfe, 0x3e, + 0xcd, 0xd5, 0x48, 0x7a, 0x63, 0x40, 0x54, 0xa0, 0xa6, 0x20, 0xfc, 0xea, 0x1a, 0xdc, 0x4a, 0x8d, 0x3c, 0xe2, 0xef, + 0xe7, 0xda, 0x76, 0x45, 0x80, 0xbf, 0xfb, 0x2d, 0x68, 0x25, 0x10, 0xe5, 0xba, 0x39, 0xb2, 0xc0, 0x49, 0x8a, 0x1b, + 0x72, 0xca, 0x5e, 0xd0, 0x36, 0x89, 0xb9, 0x61, 0xa7, 0x66, 0x6c, 0xc5, 0x98, 0xf3, 0xca, 0x57, 0x67, 0x66, 0xfe, + 0x90, 0x10, 0x34, 0x47, 0x03, 0x6f, 0x89, 0xe3, 0xc9, 0x81, 0xee, 0x52, 0xea, 0xb4, 0xf0, 0xb2, 0x90, 0x2e, 0xeb, + 0xb2, 0x1e, 0x0f, 0x9a, 0xe2, 0xa0, 0x30, 0xa9, 0xe2, 0x4a, 0x09, 0x8f, 0x87, 0x02, 0x26, 0xf8, 0xc2, 0x93, 0x82, + 0x1a, 0xa0, 0xd2, 0xf0, 0x42, 0xe1, 0x5f, 0x16, 0xd5, 0xc0, 0x43, 0x95, 0x88, 0x13, 0x04, 0x22, 0x9a, 0x11, 0xab, + 0xfb, 0x66, 0xb9, 0xd5, 0xab, 0x09, 0xcd, 0x96, 0x4a, 0x5b, 0x45, 0x54, 0x92, 0xc7, 0x96, 0xff, 0x5a, 0x4d, 0x85, + 0x2d, 0xd5, 0x5c, 0xf4, 0x86, 0x55, 0x17, 0xbd, 0x28, 0x96, 0x92, 0x40, 0x76, 0xcb, 0x1d, 0x90, 0x3f, 0x84, 0x0a, + 0x3c, 0x22, 0xf3, 0xc4, 0xa2, 0xb9, 0xd5, 0xbe, 0x1f, 0x1f, 0x26, 0x47, 0xfd, 0x45, 0x8a, 0x69, 0x83, 0xf7, 0xe0, + 0x47, 0x2e, 0x7e, 0xd8, 0xa6, 0xb0, 0xf4, 0x3d, 0xda, 0xc5, 0x5a, 0x50, 0x6e, 0xa1, 0xef, 0x85, 0xbb, 0x82, 0x27, + 0x2f, 0xe5, 0xe9, 0x07, 0x25, 0xb5, 0xc0, 0x65, 0x19, 0x97, 0x4d, 0x4a, 0x6a, 0x08, 0x7d, 0x55, 0x45, 0xfb, 0x58, + 0xac, 0x3b, 0xe0, 0x5f, 0x2d, 0x3d, 0xd0, 0x16, 0x70, 0x17, 0xd4, 0x50, 0x8a, 0x2a, 0x43, 0xdd, 0x05, 0x95, 0x65, + 0x54, 0x26, 0xbb, 0x50, 0x6a, 0xe4, 0xac, 0x97, 0xca, 0xf3, 0x32, 0x1f, 0x07, 0x5d, 0x7b, 0xd2, 0x0b, 0x9c, 0x47, + 0x14, 0x6a, 0x7f, 0x73, 0x0e, 0x2d, 0xb0, 0x5c, 0xf2, 0x4c, 0xfb, 0xc5, 0x5f, 0xde, 0x61, 0xaf, 0x7b, 0x4c, 0xc9, + 0x02, 0xb4, 0xa5, 0x2d, 0x6c, 0xde, 0x87, 0xb4, 0x96, 0x1c, 0x87, 0xaa, 0xb0, 0xc3, 0xaa, 0x21, 0x47, 0x94, 0x8c, + 0x5c, 0xfd, 0x16, 0x51, 0xe4, 0x20, 0x79, 0xc7, 0x30, 0x76, 0x23, 0x7e, 0x6d, 0x2b, 0x9b, 0x5b, 0xd1, 0x21, 0x3d, + 0x93, 0x44, 0xe2, 0x4d, 0x9a, 0x7e, 0x5e, 0x2e, 0xb8, 0x56, 0x21, 0xe3, 0xb1, 0x8a, 0x61, 0x44, 0xb1, 0xf0, 0x3d, + 0x16, 0xfe, 0xad, 0xf5, 0xe1, 0x18, 0xef, 0xd1, 0x83, 0xd6, 0xfc, 0xd6, 0x90, 0x10, 0x2a, 0x96, 0xd3, 0x29, 0x5b, + 0x7a, 0x34, 0xcc, 0x64, 0xa5, 0xa8, 0xc4, 0xe7, 0xdb, 0xc9, 0x8e, 0x81, 0x02, 0x0e, 0xd0, 0x59, 0x72, 0x65, 0xd2, + 0x93, 0x0c, 0x0e, 0x5b, 0x60, 0xe4, 0x6b, 0xd9, 0x0b, 0x48, 0xbc, 0x3c, 0x67, 0xf1, 0xf2, 0x7c, 0xdf, 0x8f, 0x30, + 0xb4, 0x16, 0x46, 0xaa, 0x17, 0x61, 0x3f, 0xe7, 0x1c, 0x16, 0x58, 0xf2, 0x7c, 0x5b, 0x2a, 0x90, 0x21, 0x6d, 0x76, + 0x44, 0x9b, 0x3d, 0x28, 0xc5, 0xde, 0x27, 0xf4, 0x73, 0x58, 0x1e, 0x19, 0x7d, 0xe5, 0xf5, 0xbe, 0x66, 0x00, 0x75, + 0xb3, 0xee, 0x10, 0x93, 0xb2, 0xc0, 0x03, 0xf0, 0xa6, 0x40, 0x2d, 0xe6, 0xd8, 0xbb, 0x19, 0x8a, 0x61, 0xd6, 0x9d, + 0x20, 0xd3, 0xb9, 0xe1, 0x23, 0x6d, 0x98, 0x0a, 0x71, 0x37, 0xf5, 0x31, 0xa7, 0x3e, 0xb2, 0x4d, 0x3b, 0xc0, 0xb8, + 0xb2, 0x5a, 0xe0, 0xbd, 0x9e, 0x7d, 0xac, 0x06, 0x64, 0x15, 0xae, 0xf0, 0xbc, 0xca, 0x6d, 0x2c, 0x8d, 0x60, 0x52, + 0x36, 0x12, 0x23, 0x79, 0x2a, 0x70, 0x76, 0x1b, 0x32, 0x9c, 0xad, 0x63, 0x44, 0xe2, 0x1d, 0x50, 0xf4, 0x62, 0xb7, + 0x52, 0x39, 0x72, 0x10, 0x67, 0x6b, 0x66, 0x9b, 0xea, 0xd3, 0x04, 0x22, 0xb4, 0x08, 0x22, 0xe0, 0x46, 0x61, 0x98, + 0xfc, 0xfd, 0xbc, 0x27, 0x84, 0x70, 0x6d, 0x07, 0xaf, 0x05, 0x5b, 0x06, 0xe8, 0x22, 0x2d, 0x6c, 0x13, 0xd7, 0xc0, + 0x75, 0xc3, 0x8f, 0xb9, 0x36, 0x31, 0xb7, 0x16, 0xe9, 0xb7, 0x65, 0xd2, 0x1d, 0x1d, 0x03, 0x50, 0xce, 0x60, 0x5c, + 0xd4, 0x71, 0x52, 0x24, 0xb1, 0x5d, 0xe2, 0x38, 0x1e, 0x8a, 0xa2, 0x44, 0x45, 0xdc, 0xfe, 0xc6, 0x70, 0xfd, 0xc2, + 0x2d, 0x6e, 0xa5, 0x99, 0x6d, 0xb8, 0x0a, 0xc6, 0xf5, 0x70, 0x8b, 0xea, 0x6d, 0x90, 0x4e, 0xdc, 0xfa, 0xee, 0xfc, + 0x6b, 0x49, 0xd7, 0xda, 0x68, 0x92, 0x47, 0xf5, 0xee, 0xbb, 0x95, 0xbb, 0xba, 0xc1, 0x39, 0x60, 0xce, 0x52, 0x03, + 0x47, 0x01, 0xb2, 0x89, 0xa3, 0x9c, 0x30, 0xd5, 0x59, 0xc5, 0xc7, 0xfb, 0x32, 0xee, 0xcb, 0x1f, 0xce, 0x08, 0x23, + 0x9e, 0x7f, 0x0e, 0xa5, 0xfa, 0x38, 0x24, 0x09, 0xf8, 0x07, 0x91, 0xdf, 0xc8, 0x3d, 0x50, 0x2f, 0x60, 0xec, 0xef, + 0x2f, 0x13, 0xf9, 0xe2, 0x85, 0xd2, 0xf9, 0xbb, 0xcf, 0x33, 0x33, 0x74, 0x38, 0x69, 0xef, 0xb0, 0x49, 0xa0, 0x1c, + 0xf7, 0x87, 0x52, 0xd6, 0x96, 0x8c, 0x0f, 0x38, 0x5c, 0x8c, 0x57, 0x90, 0x67, 0x9d, 0xc2, 0x30, 0x19, 0xea, 0x1b, + 0x07, 0xa4, 0x64, 0x84, 0x5b, 0x8a, 0xea, 0x34, 0x16, 0xa2, 0xdb, 0xc9, 0x98, 0xf2, 0x15, 0x63, 0x50, 0x98, 0x8c, + 0x83, 0x28, 0x6a, 0xfb, 0x70, 0x84, 0x09, 0x0f, 0x1f, 0x7e, 0x0e, 0x45, 0x05, 0x37, 0x2f, 0x47, 0x19, 0x8a, 0xea, + 0x30, 0xdb, 0x2a, 0x02, 0xe6, 0xd0, 0xcd, 0x63, 0x77, 0x96, 0xb8, 0x61, 0xe2, 0x4e, 0x62, 0x77, 0x1a, 0xb1, 0xa8, + 0x78, 0x9a, 0xf8, 0x0c, 0xdb, 0x25, 0x60, 0xff, 0xbe, 0x02, 0xd7, 0x6b, 0xb9, 0xd6, 0xc8, 0xee, 0x84, 0x08, 0xa1, + 0xc3, 0x23, 0x91, 0x84, 0x8c, 0x94, 0x93, 0x14, 0x1f, 0x5e, 0xc4, 0x2b, 0xd0, 0xc5, 0x6e, 0xdc, 0x9f, 0x01, 0xf1, + 0x67, 0xa9, 0xaf, 0x2c, 0x15, 0x93, 0x83, 0x8a, 0x0e, 0x96, 0xa7, 0xcf, 0xd3, 0xf3, 0x45, 0x9a, 0x44, 0x49, 0xc1, + 0x21, 0xfa, 0x65, 0xdc, 0x77, 0x99, 0x57, 0xdd, 0xaf, 0xf6, 0xea, 0xde, 0xf6, 0xad, 0x49, 0xda, 0xe8, 0x2f, 0x64, + 0x1c, 0x83, 0xc6, 0x47, 0x8e, 0x0c, 0x69, 0x50, 0x88, 0x11, 0xdb, 0x32, 0x41, 0x97, 0x48, 0x29, 0xa4, 0xc0, 0x54, + 0xcc, 0x2d, 0x70, 0x7e, 0xe3, 0x8f, 0x69, 0x5a, 0xf4, 0xff, 0xb1, 0x8c, 0xb2, 0xeb, 0x83, 0x68, 0x1e, 0xd1, 0x1a, + 0x59, 0x93, 0x20, 0xb9, 0x08, 0xe0, 0x44, 0x99, 0x96, 0x57, 0xd6, 0x56, 0x28, 0xd3, 0xc6, 0x34, 0xba, 0x26, 0xad, + 0x57, 0x46, 0xbe, 0x32, 0xec, 0x1b, 0x43, 0xa9, 0x89, 0x5c, 0x0e, 0x7d, 0x2f, 0xd4, 0x3d, 0xb5, 0x69, 0xa8, 0x80, + 0xf8, 0x87, 0xac, 0x17, 0xaa, 0xbd, 0xae, 0xe6, 0xdc, 0x90, 0x19, 0x82, 0xfa, 0xdb, 0xe5, 0x91, 0x8e, 0x9f, 0xbf, + 0x62, 0x4b, 0x22, 0x7c, 0xb1, 0x0a, 0x97, 0x99, 0xcc, 0xa5, 0xe1, 0x8a, 0x84, 0xb9, 0xd0, 0x73, 0x74, 0x86, 0xa2, + 0x3f, 0x6d, 0x45, 0x04, 0x64, 0x91, 0xcb, 0x11, 0x0c, 0xbd, 0xd5, 0x55, 0xa5, 0xdc, 0xbd, 0x46, 0x2f, 0x37, 0x69, + 0x8d, 0x24, 0x3c, 0x1d, 0x4b, 0x17, 0x98, 0x32, 0x98, 0x61, 0x8e, 0xd9, 0x9d, 0x37, 0x06, 0x80, 0xf3, 0x62, 0x60, + 0x4e, 0xe2, 0xe4, 0x59, 0xbe, 0x80, 0x85, 0xfa, 0x88, 0x1d, 0x0a, 0x63, 0x1c, 0x1a, 0x3d, 0xaf, 0x3a, 0xa7, 0x43, + 0x7e, 0x3d, 0x7d, 0x79, 0xb5, 0x08, 0x60, 0x7d, 0x61, 0xd5, 0xcb, 0x75, 0x2f, 0xaa, 0xdb, 0x01, 0x64, 0x23, 0x2c, + 0xa5, 0xc8, 0x5a, 0x0c, 0x80, 0xcd, 0x8a, 0x44, 0x45, 0x0b, 0x90, 0x09, 0xe5, 0xee, 0x8c, 0x39, 0x97, 0x21, 0x1c, + 0xee, 0x37, 0x70, 0x05, 0x88, 0xee, 0x0f, 0x28, 0x09, 0xbb, 0x33, 0x96, 0x41, 0x40, 0xa0, 0x0b, 0xe9, 0x89, 0x63, + 0x6d, 0xed, 0x0c, 0x16, 0x57, 0x96, 0x6b, 0xbc, 0x49, 0x99, 0x3d, 0xf4, 0xad, 0x41, 0x7f, 0xd7, 0xd2, 0x91, 0x43, + 0xcc, 0x8f, 0x76, 0xb6, 0xd6, 0x7f, 0x33, 0xb4, 0x9c, 0x72, 0x84, 0xaa, 0x0a, 0xa9, 0x10, 0xc5, 0x6d, 0x7f, 0xbb, + 0x64, 0x2e, 0xf7, 0xfd, 0x29, 0xc0, 0xa1, 0x0b, 0xcc, 0xd6, 0x0e, 0x08, 0x1c, 0xc1, 0x79, 0xca, 0x05, 0x78, 0x28, + 0x82, 0xa2, 0xc8, 0xe2, 0x53, 0x34, 0x51, 0x22, 0x03, 0x30, 0xf9, 0xeb, 0x15, 0x39, 0x7c, 0x78, 0x87, 0x16, 0xcd, + 0xc9, 0x3a, 0x2a, 0x9d, 0x32, 0xc7, 0xc6, 0x26, 0x1d, 0x4a, 0x56, 0xc7, 0x79, 0xa5, 0x75, 0x80, 0xef, 0xe2, 0xc4, + 0x9b, 0xa5, 0x94, 0x6b, 0x56, 0xec, 0x53, 0x70, 0x0a, 0x2c, 0x33, 0xb4, 0x33, 0x22, 0xe3, 0xea, 0xad, 0x9d, 0xc5, + 0xd5, 0x88, 0xa7, 0xe1, 0xe1, 0x2c, 0x46, 0x1c, 0xe7, 0x0d, 0xb6, 0x7b, 0x62, 0x0f, 0x07, 0x83, 0x2f, 0x3b, 0xbd, + 0x0e, 0x16, 0x3b, 0xa3, 0xdf, 0x7a, 0xec, 0xc8, 0xd5, 0x83, 0xd2, 0xf2, 0xa4, 0x94, 0x69, 0xbe, 0x65, 0x3f, 0xcf, + 0x4f, 0xf6, 0xf8, 0xfc, 0xef, 0xef, 0x6d, 0x8a, 0x87, 0x93, 0xb2, 0x1c, 0x3d, 0xcf, 0xec, 0xc3, 0xbf, 0xd9, 0x7c, + 0xbe, 0x9f, 0x65, 0x59, 0x70, 0x5d, 0x62, 0x66, 0xd3, 0x44, 0x7b, 0xd7, 0xb8, 0x06, 0x58, 0x70, 0xb7, 0x80, 0xfa, + 0x4e, 0x7c, 0xfc, 0xe6, 0x23, 0x5c, 0x1d, 0x38, 0x45, 0x3d, 0xd8, 0x54, 0x58, 0xc6, 0x1e, 0xd5, 0xb1, 0xe8, 0x53, + 0x15, 0xcf, 0x2c, 0x93, 0xc0, 0x77, 0x9a, 0x45, 0x11, 0x20, 0x3c, 0x36, 0x16, 0x1f, 0x90, 0xb1, 0xf8, 0xc0, 0xe5, + 0x69, 0x0c, 0x1f, 0xbb, 0x62, 0x6e, 0xc3, 0xc7, 0x68, 0x92, 0x15, 0xd7, 0xbf, 0x61, 0x63, 0x4d, 0xa8, 0x7f, 0xf1, + 0x6a, 0x1e, 0x2f, 0x90, 0x29, 0x98, 0x89, 0x07, 0xa8, 0xfe, 0x31, 0xaa, 0x57, 0xef, 0xf7, 0xfb, 0xef, 0x33, 0x17, + 0xfe, 0xfd, 0x1c, 0xc3, 0xfb, 0x45, 0xd2, 0xf2, 0xfe, 0x63, 0x04, 0xd7, 0x30, 0xbc, 0xf6, 0x2c, 0x0b, 0x68, 0xf2, + 0x30, 0x8c, 0x12, 0x6e, 0xeb, 0x6d, 0x58, 0xaf, 0xcb, 0x23, 0xa4, 0x40, 0x48, 0x62, 0x8c, 0x14, 0x92, 0xc9, 0x71, + 0x3f, 0x34, 0x66, 0x06, 0xcd, 0xbe, 0x0d, 0x65, 0xb7, 0x9a, 0x41, 0x79, 0x4e, 0xe6, 0x14, 0xda, 0x4f, 0x01, 0xad, + 0x91, 0x64, 0xf6, 0x97, 0xcd, 0xff, 0xea, 0x8d, 0x0f, 0x07, 0xbd, 0xaf, 0xfb, 0x47, 0x8f, 0x36, 0x5d, 0xcb, 0x32, + 0x53, 0x37, 0xd8, 0xc2, 0xba, 0x65, 0x94, 0xef, 0x0d, 0x46, 0x4e, 0xde, 0xf5, 0x77, 0x94, 0x6f, 0xd1, 0x97, 0x3b, + 0x18, 0x99, 0x95, 0x44, 0xca, 0x96, 0x96, 0x86, 0x12, 0x6b, 0xf6, 0x3a, 0xc1, 0x78, 0x71, 0x2a, 0xb3, 0x83, 0xd4, + 0x8a, 0x02, 0xfa, 0xc2, 0xac, 0xa5, 0xca, 0x54, 0x04, 0x48, 0xc1, 0x58, 0x66, 0x49, 0x1d, 0x8c, 0xf2, 0xcb, 0xb8, + 0x98, 0xcc, 0x28, 0xd1, 0x13, 0xc0, 0x2d, 0xeb, 0x4b, 0xcb, 0xcb, 0xfd, 0xad, 0xdd, 0x11, 0x87, 0x3b, 0xa6, 0xa2, + 0x30, 0x3a, 0xc3, 0xc2, 0xaf, 0x07, 0x14, 0x12, 0xd6, 0x11, 0x1e, 0x9c, 0xd4, 0xe3, 0xab, 0x79, 0x1a, 0xa0, 0x47, + 0x6b, 0x2e, 0x68, 0x38, 0x85, 0x19, 0x95, 0x3d, 0x4a, 0x6d, 0x38, 0x29, 0x0e, 0xc7, 0x4e, 0xfd, 0x74, 0x13, 0xe8, + 0xb6, 0x2f, 0x87, 0xe4, 0x25, 0x85, 0x6e, 0xe6, 0x1e, 0xa2, 0x7f, 0x65, 0xe9, 0x21, 0x8e, 0x4f, 0xe8, 0x6f, 0x1e, + 0xfe, 0x3d, 0x77, 0x8f, 0xba, 0x9b, 0x7a, 0x69, 0x3e, 0x08, 0x77, 0xde, 0x82, 0x08, 0xc7, 0xc2, 0x7e, 0x1f, 0x3a, + 0xca, 0x18, 0x37, 0x02, 0x50, 0x22, 0xa7, 0xd3, 0x87, 0xab, 0x78, 0x6e, 0x3b, 0x62, 0x56, 0x3a, 0x48, 0xa8, 0xe5, + 0x01, 0xaa, 0xc3, 0xf3, 0x83, 0xd6, 0x33, 0xc6, 0x24, 0xe1, 0x82, 0xc3, 0xfd, 0xe4, 0xf7, 0x17, 0x95, 0xf7, 0x65, + 0x29, 0x13, 0xaa, 0x3e, 0xc8, 0x7c, 0xdc, 0xe7, 0x0f, 0x19, 0x06, 0x31, 0x25, 0x18, 0x60, 0x02, 0x4c, 0xcb, 0x2a, + 0xf5, 0x30, 0xcf, 0x2b, 0xc9, 0x37, 0xf0, 0xab, 0x07, 0x19, 0xc6, 0x20, 0xb1, 0x51, 0x8a, 0x8c, 0xa9, 0x81, 0x50, + 0xa0, 0x21, 0xc1, 0x05, 0x25, 0x3f, 0x31, 0xf2, 0x48, 0xb2, 0x53, 0x62, 0x64, 0x5b, 0xf4, 0x60, 0xb9, 0x9c, 0x80, + 0x44, 0x7a, 0x1c, 0xe2, 0x0b, 0x7e, 0xd2, 0x6f, 0xf8, 0x8e, 0xf8, 0x70, 0xda, 0x30, 0xd9, 0x10, 0xfd, 0xb0, 0xf0, + 0x48, 0xc1, 0x49, 0x25, 0x2a, 0xc3, 0xb6, 0xa6, 0x30, 0x25, 0x51, 0x54, 0xf4, 0x5b, 0x86, 0x8f, 0xad, 0xb6, 0x14, + 0x5b, 0xae, 0x51, 0x1e, 0x50, 0x79, 0xc6, 0xe5, 0xdc, 0x94, 0x36, 0xca, 0x79, 0x20, 0x36, 0x46, 0xa7, 0x2d, 0x8c, + 0x30, 0x13, 0xce, 0x83, 0x8c, 0x53, 0xe1, 0x44, 0x47, 0x18, 0x0c, 0x0c, 0xa5, 0x1d, 0xcd, 0x7c, 0x37, 0x5c, 0xfd, + 0x38, 0xf2, 0x37, 0xff, 0xeb, 0x30, 0xe8, 0xfd, 0x06, 0x57, 0xe2, 0xa8, 0x6b, 0xf7, 0xd4, 0xa3, 0xf3, 0xe8, 0xc1, + 0xa6, 0xfb, 0x2a, 0x52, 0x54, 0x1a, 0x1e, 0xfc, 0x4a, 0xb2, 0x1f, 0x3e, 0x09, 0x96, 0x67, 0x11, 0x87, 0xa5, 0x4f, + 0xe3, 0x10, 0x03, 0x1e, 0x5a, 0xff, 0x69, 0x91, 0xd1, 0x94, 0x66, 0xbc, 0x50, 0x5f, 0xc2, 0xcf, 0xfb, 0xdb, 0x15, + 0x83, 0x41, 0x14, 0xd7, 0x70, 0x4e, 0x18, 0x47, 0xb4, 0x31, 0xe4, 0x30, 0xc8, 0xaa, 0x3a, 0x30, 0x2f, 0x75, 0x49, + 0x18, 0x7d, 0x69, 0x56, 0x1a, 0xca, 0x7d, 0x47, 0x8e, 0x6d, 0x91, 0x2e, 0x6c, 0x8a, 0x15, 0x28, 0x9e, 0xe6, 0x3e, + 0xe6, 0xde, 0xbc, 0x88, 0xd1, 0xa7, 0x43, 0x7d, 0x31, 0x18, 0x23, 0x19, 0x85, 0x3c, 0x5f, 0x06, 0xd4, 0x2b, 0xba, + 0x79, 0x27, 0x01, 0x09, 0x1c, 0xd4, 0x91, 0x78, 0xf8, 0x70, 0x63, 0x1e, 0x03, 0x07, 0xc9, 0xb6, 0x2a, 0xf3, 0x52, + 0xaa, 0x21, 0x48, 0x47, 0x8e, 0xea, 0x07, 0xb1, 0x04, 0x3d, 0x5e, 0x82, 0xac, 0x65, 0x2c, 0xba, 0x5f, 0xd5, 0x4f, + 0x26, 0x67, 0xcb, 0xfd, 0x65, 0xfd, 0x5f, 0xd3, 0x38, 0xa1, 0x46, 0xea, 0x3d, 0x07, 0xa2, 0xe7, 0x80, 0x60, 0x0f, + 0x20, 0xc1, 0x0a, 0xf8, 0x69, 0x6d, 0x1c, 0x80, 0x2b, 0xb5, 0x9a, 0x36, 0xda, 0x02, 0x32, 0x5a, 0xb6, 0x66, 0xac, + 0x61, 0xe9, 0xce, 0x63, 0x95, 0xe7, 0x66, 0xbc, 0xb1, 0x61, 0xdb, 0xe4, 0xe0, 0x49, 0xad, 0x52, 0x6f, 0x98, 0xee, + 0x48, 0x16, 0x00, 0xfb, 0x89, 0xb7, 0xfc, 0x38, 0x72, 0x88, 0x4e, 0x45, 0xf2, 0x81, 0xbb, 0x35, 0x6a, 0xe2, 0xcf, + 0x4a, 0xbd, 0xb8, 0x8f, 0x03, 0x32, 0x8a, 0x00, 0xec, 0xeb, 0x1b, 0xfb, 0xac, 0x16, 0xb9, 0x72, 0x55, 0x8e, 0x36, + 0x04, 0xa8, 0xd8, 0xf0, 0x37, 0x0a, 0x7e, 0x42, 0x4b, 0x0b, 0x05, 0x3e, 0x1c, 0x77, 0x43, 0xc0, 0x0a, 0xaa, 0x70, + 0xa1, 0x2a, 0x48, 0xf8, 0xa1, 0xe9, 0x09, 0x9c, 0x0c, 0x5f, 0x4b, 0x4c, 0x63, 0xd7, 0xb5, 0x0b, 0xe3, 0x97, 0xf3, + 0xe5, 0x8e, 0xc1, 0x18, 0xc0, 0xe7, 0xe2, 0x32, 0x27, 0x95, 0x98, 0xee, 0xa7, 0x69, 0x75, 0x78, 0x62, 0xb8, 0x46, + 0x96, 0xd0, 0x04, 0xaf, 0xdb, 0x22, 0x71, 0xe8, 0xef, 0xe7, 0x78, 0x4c, 0x7f, 0x81, 0x60, 0xd9, 0xb0, 0xe9, 0x29, + 0xa2, 0x64, 0x46, 0x87, 0xc9, 0x91, 0xff, 0x19, 0x65, 0x4c, 0x8e, 0x47, 0xa5, 0x6c, 0x0c, 0x08, 0x17, 0x33, 0x39, + 0xf2, 0xe4, 0x07, 0x5c, 0x8b, 0x2a, 0x29, 0x66, 0x4e, 0x0f, 0xe4, 0x65, 0x6d, 0x9d, 0xe2, 0x7e, 0x3c, 0x61, 0x57, + 0x8e, 0x18, 0xd8, 0xd4, 0x18, 0x60, 0x69, 0x7e, 0x73, 0x23, 0x90, 0xe3, 0x04, 0xe0, 0x27, 0x82, 0x37, 0x82, 0x52, + 0xb9, 0xdf, 0x52, 0x6a, 0x04, 0x2f, 0xb5, 0x33, 0xba, 0xa7, 0xd1, 0x61, 0x26, 0x61, 0x44, 0x07, 0x85, 0x19, 0x3e, + 0x73, 0xe9, 0x1b, 0x76, 0x86, 0xc3, 0x03, 0x4e, 0x6a, 0x45, 0xa5, 0x86, 0x85, 0x6f, 0xe0, 0x27, 0x50, 0x82, 0x69, + 0x75, 0xb2, 0x23, 0x41, 0x6c, 0xc2, 0x8d, 0x0b, 0x30, 0x6c, 0x5e, 0x00, 0x5b, 0x80, 0xfc, 0x18, 0xb5, 0x13, 0x1c, + 0x49, 0x7e, 0x7b, 0xa2, 0x23, 0x87, 0xe0, 0xab, 0x52, 0x46, 0x57, 0x53, 0x03, 0x27, 0x1d, 0x69, 0xe4, 0xc8, 0xfa, + 0x66, 0x29, 0xf0, 0xea, 0x1a, 0xe3, 0xa4, 0xc8, 0xbc, 0xa9, 0x29, 0xbc, 0x6e, 0x89, 0xa0, 0xc3, 0x8b, 0x93, 0xdf, + 0xb1, 0x38, 0x22, 0x26, 0xe9, 0xca, 0xc0, 0x20, 0x19, 0x0c, 0x7e, 0x95, 0xfa, 0xb0, 0xef, 0x09, 0x8c, 0x1c, 0x95, + 0x97, 0xc1, 0x11, 0x5a, 0x1a, 0x49, 0x83, 0x54, 0x94, 0xdf, 0x45, 0x46, 0x2a, 0x2c, 0x97, 0x4e, 0x48, 0x6a, 0x58, + 0xfd, 0x3e, 0xcb, 0xea, 0xa9, 0x40, 0x48, 0xdc, 0xc1, 0xe6, 0xc9, 0xf1, 0x86, 0x6f, 0xa5, 0x34, 0x10, 0x34, 0xbe, + 0x12, 0x65, 0x3c, 0x5a, 0xfd, 0x46, 0x65, 0xaf, 0x1a, 0xc1, 0xdd, 0x49, 0x8b, 0xe3, 0x29, 0x8a, 0x94, 0xcc, 0xe8, + 0xf8, 0x44, 0x2f, 0xd2, 0xcd, 0x92, 0x6f, 0xd5, 0x88, 0x72, 0x00, 0xd1, 0x18, 0xb4, 0x50, 0x64, 0xcf, 0x62, 0xb9, + 0x4d, 0xee, 0x94, 0xfa, 0x52, 0xe0, 0x49, 0x32, 0x0f, 0x30, 0x26, 0x5f, 0xeb, 0x24, 0x5a, 0xc7, 0x8a, 0xf9, 0x9a, + 0x46, 0x14, 0x69, 0x12, 0x9a, 0xa1, 0xb5, 0xcd, 0x29, 0x0a, 0xaa, 0x6a, 0x4b, 0x2d, 0x86, 0xcc, 0x12, 0xfc, 0x22, + 0x34, 0x20, 0x11, 0x00, 0x20, 0xb1, 0xe4, 0x28, 0xc1, 0x56, 0x03, 0xc4, 0x1f, 0x44, 0x23, 0x1a, 0x6b, 0xfd, 0x6d, + 0xdc, 0x8a, 0xbb, 0xc8, 0x3c, 0x37, 0x12, 0xb7, 0x42, 0xae, 0x11, 0x61, 0x32, 0x99, 0x36, 0xc0, 0xc0, 0x67, 0x52, + 0x6d, 0xb3, 0x61, 0xc4, 0x6b, 0x7b, 0x49, 0x19, 0xfa, 0xd6, 0x2c, 0xba, 0x4c, 0xe6, 0xd5, 0x62, 0xb3, 0x5e, 0x70, + 0x48, 0x0d, 0xd9, 0x8b, 0x00, 0x66, 0x1b, 0xca, 0xa0, 0x1c, 0xd0, 0x90, 0xd8, 0xab, 0xf5, 0x7b, 0x07, 0x75, 0x68, + 0x5a, 0x2f, 0xc2, 0x76, 0xab, 0xf8, 0x82, 0x3f, 0xa8, 0xaf, 0x7f, 0xa4, 0xd7, 0x3f, 0x92, 0x91, 0x4d, 0x72, 0x0d, + 0x33, 0x55, 0x7f, 0x98, 0x1e, 0x38, 0xbc, 0xae, 0x0c, 0x09, 0xba, 0x4b, 0x81, 0xe0, 0xae, 0x74, 0x27, 0x53, 0x98, + 0x41, 0x77, 0xb7, 0x9e, 0xff, 0xdb, 0x4f, 0x01, 0xa1, 0x38, 0xbe, 0xd9, 0x6b, 0x07, 0x94, 0x55, 0xc6, 0x12, 0x11, + 0x44, 0xd8, 0x40, 0x90, 0xb0, 0x6e, 0x64, 0x35, 0x6a, 0xf3, 0x20, 0xbe, 0x1d, 0x3e, 0x7d, 0x0a, 0x4d, 0x2f, 0x04, + 0x7d, 0xcc, 0x62, 0x89, 0xf0, 0x0a, 0x97, 0x16, 0xd4, 0x6b, 0x83, 0x7d, 0xe7, 0x71, 0x9e, 0xa3, 0xd7, 0x0b, 0xf2, + 0x95, 0x07, 0x91, 0x19, 0x7e, 0xef, 0xac, 0xa8, 0x5e, 0xd2, 0x83, 0xf8, 0x30, 0x86, 0x21, 0xdb, 0xf4, 0xb7, 0x6d, + 0x44, 0x1a, 0x26, 0x1f, 0xa2, 0x4e, 0x93, 0x32, 0x19, 0xf9, 0x62, 0x70, 0xc6, 0xe5, 0x7f, 0x97, 0x54, 0x9c, 0x26, + 0x5e, 0x22, 0xbc, 0x18, 0x3f, 0x43, 0x99, 0x94, 0x0c, 0x72, 0x90, 0x8c, 0xc5, 0x99, 0xc1, 0x6c, 0x64, 0x89, 0x87, + 0x71, 0x85, 0x69, 0x90, 0x64, 0x74, 0x12, 0xc1, 0x45, 0x45, 0x0b, 0x56, 0xd5, 0xde, 0x1b, 0x05, 0xdb, 0x8a, 0xec, + 0xda, 0x38, 0xd2, 0x11, 0x9d, 0x03, 0xed, 0xeb, 0x20, 0x97, 0x58, 0xb6, 0x0d, 0x83, 0x43, 0xea, 0x37, 0x2a, 0x5d, + 0xb8, 0x18, 0x13, 0xdc, 0xb5, 0x55, 0xa9, 0x08, 0x3f, 0xd5, 0xfa, 0x47, 0xb1, 0xb8, 0x6c, 0x0c, 0x76, 0x28, 0xad, + 0x34, 0xd4, 0xbd, 0x31, 0x7c, 0x29, 0x30, 0x3d, 0x9d, 0x09, 0x8f, 0x0f, 0x62, 0x03, 0x1e, 0x23, 0xd0, 0x91, 0x1f, + 0xe5, 0xfa, 0x23, 0x75, 0x7b, 0xcd, 0x84, 0x80, 0x30, 0xb4, 0x5a, 0x43, 0x70, 0xd2, 0x30, 0xb1, 0xaa, 0xd1, 0x5e, + 0xa6, 0xe8, 0xcc, 0xc0, 0x3f, 0x43, 0x30, 0x94, 0x39, 0x98, 0xb8, 0xbb, 0x0d, 0x2f, 0x04, 0x3c, 0x61, 0x36, 0xa7, + 0x99, 0xf8, 0xfb, 0x36, 0x99, 0xb7, 0x5a, 0x63, 0xa0, 0x3f, 0xbb, 0x79, 0x17, 0x88, 0x53, 0x00, 0x48, 0x4e, 0x37, + 0xc3, 0x27, 0x8a, 0xbb, 0xe6, 0x50, 0xce, 0x16, 0x9c, 0xf0, 0x87, 0xc8, 0x37, 0x09, 0x91, 0xd7, 0x66, 0x5a, 0x4f, + 0x63, 0x01, 0x4e, 0xd3, 0x74, 0x1e, 0x05, 0xe4, 0x73, 0x02, 0x5f, 0xc4, 0x40, 0xda, 0x1b, 0x58, 0xf9, 0x41, 0x54, + 0x49, 0xf6, 0xd7, 0x5c, 0xb6, 0x57, 0x28, 0xaf, 0xd8, 0x18, 0xc0, 0x47, 0x8e, 0x17, 0x57, 0x9c, 0xa9, 0x23, 0x9c, + 0x59, 0xa1, 0x48, 0x2b, 0x57, 0x82, 0x1b, 0x12, 0x73, 0x13, 0xc9, 0xa4, 0x65, 0xda, 0xbc, 0xa7, 0x09, 0x9d, 0x3b, + 0x75, 0x5e, 0x50, 0x72, 0x98, 0x08, 0x92, 0x4e, 0xa5, 0xf3, 0x56, 0x23, 0x7b, 0x51, 0xc3, 0x42, 0xc6, 0x40, 0x38, + 0x1b, 0xa4, 0x06, 0xa0, 0x12, 0x56, 0xc0, 0x78, 0x22, 0x3d, 0x9e, 0x48, 0x8e, 0x47, 0x0e, 0xe3, 0x0d, 0xe6, 0xcc, + 0x8b, 0x68, 0x64, 0x68, 0x47, 0xa2, 0xe3, 0xfb, 0x68, 0x5f, 0xa0, 0x26, 0xbc, 0xd5, 0xbd, 0x18, 0x80, 0x75, 0xc3, + 0x38, 0x21, 0x36, 0x86, 0xf8, 0x94, 0x9d, 0xde, 0xdc, 0xc0, 0x66, 0xc1, 0x10, 0x01, 0x84, 0x20, 0xd5, 0x2a, 0xc9, + 0x49, 0xc9, 0x35, 0x2b, 0x60, 0xcf, 0x10, 0x9e, 0x12, 0x73, 0x0a, 0xfa, 0x13, 0xb0, 0x0e, 0xe1, 0x5d, 0x1b, 0xed, + 0x4d, 0xe1, 0xf4, 0x60, 0xc6, 0xa1, 0x8c, 0x7e, 0x90, 0x5c, 0x18, 0xc5, 0xdc, 0x22, 0x8f, 0x87, 0x24, 0x9f, 0xf8, + 0x43, 0x5a, 0x0b, 0xa0, 0x8e, 0x35, 0x60, 0x29, 0x24, 0x60, 0x89, 0x98, 0x90, 0xb6, 0x02, 0xab, 0x74, 0x5a, 0x17, + 0xab, 0xd0, 0xc9, 0x97, 0x37, 0x36, 0xde, 0x61, 0x98, 0xf4, 0xd8, 0x58, 0x96, 0x57, 0x46, 0x40, 0xb4, 0x8d, 0x0d, + 0x3a, 0x28, 0x06, 0x98, 0xa8, 0x44, 0xe3, 0xa4, 0x97, 0x8a, 0x5c, 0x1f, 0x0b, 0x59, 0x09, 0xfc, 0x5b, 0x94, 0x2c, + 0xf9, 0x50, 0xdf, 0xfd, 0x56, 0x8b, 0xe2, 0x99, 0x06, 0x61, 0x14, 0xa2, 0xe5, 0xbb, 0x84, 0x74, 0xf0, 0xb8, 0x88, + 0x92, 0x90, 0x1f, 0x8d, 0xe8, 0x9b, 0x15, 0xe0, 0x1a, 0x57, 0x75, 0x38, 0x6a, 0xf9, 0x31, 0x01, 0xa6, 0xfa, 0x31, + 0xd6, 0xe5, 0x22, 0x4c, 0x2f, 0x8a, 0x67, 0x01, 0x19, 0xd8, 0xba, 0x0e, 0xc6, 0x3e, 0x94, 0x48, 0x92, 0x3e, 0xc5, + 0xc7, 0xb1, 0x2c, 0x6b, 0xf9, 0x8c, 0x76, 0x13, 0x3e, 0x22, 0x8e, 0xa0, 0xfe, 0x1a, 0x0b, 0x1d, 0x19, 0xe9, 0xb9, + 0x42, 0x50, 0xd4, 0x78, 0x1b, 0x64, 0xf9, 0x15, 0xbc, 0x33, 0x61, 0x10, 0xc6, 0xfa, 0xa6, 0x66, 0x30, 0x80, 0x2a, + 0x3d, 0x90, 0xea, 0x4a, 0xb2, 0x28, 0x72, 0x60, 0x5c, 0xa8, 0x78, 0x1c, 0x3d, 0x51, 0xe9, 0x3a, 0x0c, 0x1c, 0xa9, + 0xb2, 0x6d, 0xd6, 0x6f, 0x31, 0xff, 0x88, 0x68, 0x81, 0xd4, 0x82, 0x74, 0x13, 0xd0, 0x87, 0x26, 0x65, 0x84, 0x90, + 0xb6, 0x23, 0x0e, 0x0c, 0xf0, 0x46, 0xf8, 0xd0, 0xc6, 0x3f, 0x78, 0x70, 0xf0, 0x58, 0xf2, 0x3c, 0x67, 0xa3, 0x00, + 0xf1, 0xee, 0x9c, 0x6f, 0xf8, 0x78, 0x86, 0x8a, 0x4d, 0xde, 0x53, 0xc9, 0x7c, 0xcd, 0x2b, 0xf7, 0x1d, 0x18, 0x42, + 0xac, 0x23, 0x77, 0x1b, 0x9f, 0xc5, 0x76, 0x2b, 0xdf, 0x60, 0xbd, 0x70, 0xa9, 0x62, 0x38, 0x15, 0x63, 0x3b, 0x93, + 0x21, 0xff, 0xca, 0x8a, 0x10, 0xe1, 0x93, 0x00, 0x16, 0x71, 0x45, 0xa6, 0x23, 0x8f, 0x7a, 0xc4, 0x63, 0xca, 0x9e, + 0x0b, 0x03, 0x81, 0x7c, 0xc4, 0x0c, 0x53, 0xad, 0xd4, 0x4f, 0x64, 0xc4, 0x9d, 0x1c, 0x0f, 0x55, 0x8c, 0x31, 0x0c, + 0x0a, 0x82, 0xb8, 0xaa, 0x9f, 0x5f, 0xe9, 0xf8, 0xc6, 0x72, 0xcc, 0xea, 0xd3, 0x57, 0xf3, 0xe0, 0x0c, 0xd6, 0xa7, + 0xfd, 0x05, 0x9a, 0xa3, 0xe6, 0xac, 0x61, 0x44, 0x27, 0x10, 0x96, 0x5c, 0xaf, 0xa9, 0x39, 0xd4, 0x94, 0x5c, 0x7d, + 0x78, 0xe3, 0x46, 0x89, 0x14, 0x58, 0x20, 0xc6, 0x25, 0x38, 0x50, 0x53, 0x49, 0x0a, 0x4f, 0x01, 0xe3, 0xd2, 0x6b, + 0x48, 0x45, 0xac, 0x85, 0x00, 0x21, 0x85, 0xe6, 0x4b, 0xd4, 0xaa, 0x21, 0xe5, 0xc4, 0x3c, 0x08, 0x3a, 0xed, 0x89, + 0xc1, 0x2a, 0x45, 0xb2, 0x2c, 0x30, 0x5e, 0x89, 0xa5, 0x9b, 0x08, 0xa7, 0x76, 0x7d, 0xad, 0xf2, 0x5a, 0x14, 0xcc, + 0x0e, 0x1c, 0x27, 0x46, 0x0f, 0x24, 0x74, 0x61, 0xd4, 0x30, 0x07, 0x7a, 0x58, 0x9c, 0x1c, 0xa1, 0x6a, 0x6e, 0x4a, + 0x06, 0x72, 0x3e, 0x05, 0xf3, 0xd2, 0x51, 0xee, 0x6b, 0x71, 0xe5, 0x70, 0xc1, 0x59, 0xcd, 0x54, 0xc1, 0x7d, 0x5b, + 0x51, 0x61, 0x16, 0x61, 0x97, 0x4c, 0xe1, 0x92, 0xe3, 0xd6, 0xa7, 0x8d, 0x39, 0xda, 0xf0, 0xdc, 0xdc, 0xdc, 0xc0, + 0x71, 0x03, 0x72, 0xc2, 0x85, 0x15, 0x0a, 0x29, 0xa4, 0x9a, 0xf4, 0x58, 0x56, 0x53, 0x90, 0x1b, 0xe3, 0xea, 0xf1, + 0x18, 0x45, 0xb2, 0x59, 0x55, 0x94, 0xf6, 0x83, 0x53, 0x00, 0x68, 0x8c, 0xdd, 0x1d, 0x42, 0xee, 0xdf, 0x84, 0x98, + 0xd3, 0x4e, 0x9e, 0xbb, 0x9f, 0x1a, 0x1c, 0xe2, 0x37, 0x98, 0x58, 0x21, 0xf7, 0x3f, 0x65, 0xfd, 0xd3, 0x38, 0x09, + 0xe9, 0xa6, 0x72, 0x96, 0x60, 0x3e, 0x07, 0xd5, 0x91, 0x2b, 0xbe, 0x58, 0x01, 0x05, 0xcc, 0xf3, 0x9c, 0x08, 0xc2, + 0xb3, 0xd0, 0x96, 0x33, 0xb1, 0x4b, 0x03, 0xf1, 0x72, 0x23, 0x4b, 0xbc, 0x49, 0xd3, 0xc8, 0x19, 0xea, 0x33, 0x88, + 0xae, 0xab, 0x8d, 0x8b, 0x74, 0x78, 0x04, 0xb4, 0x10, 0x6d, 0x40, 0x8a, 0x17, 0x55, 0x62, 0xad, 0xb3, 0xe4, 0x76, + 0x52, 0xf9, 0x5a, 0x20, 0xe2, 0xb3, 0x04, 0x69, 0x58, 0xe3, 0x7a, 0x9f, 0x27, 0x06, 0x69, 0x43, 0x6f, 0x6f, 0x6e, + 0xe0, 0x8f, 0x65, 0x19, 0x84, 0xe6, 0x77, 0x2c, 0x31, 0x87, 0x5d, 0xc4, 0x13, 0x6f, 0xba, 0xf8, 0xb5, 0x83, 0x5a, + 0x65, 0x91, 0xdb, 0xa0, 0xfa, 0x90, 0xe6, 0xc9, 0x69, 0xb5, 0xbd, 0x7c, 0x94, 0x2a, 0xdb, 0x01, 0x9a, 0x4a, 0x42, + 0x89, 0xb2, 0x7f, 0x04, 0x28, 0x85, 0xe6, 0x89, 0x68, 0x7d, 0x44, 0x7e, 0x5b, 0xac, 0x3f, 0x19, 0x90, 0x71, 0x0f, + 0xdc, 0x71, 0x6f, 0x2b, 0x12, 0xd9, 0xec, 0x23, 0xef, 0xc9, 0xee, 0xc0, 0xcd, 0x82, 0x24, 0x4c, 0xcf, 0x51, 0x05, + 0x81, 0xca, 0x10, 0x72, 0x84, 0x10, 0xd0, 0x00, 0x35, 0x08, 0x7a, 0x01, 0x7e, 0x6e, 0x75, 0xa2, 0x54, 0x3d, 0x99, + 0x31, 0x5a, 0xb9, 0xc9, 0x71, 0x3d, 0xb4, 0x1b, 0x17, 0xdb, 0xce, 0xa3, 0x1c, 0xe3, 0x5b, 0xd2, 0xb0, 0xd8, 0x06, + 0x85, 0x2f, 0x1b, 0xbf, 0x66, 0x72, 0xe4, 0xaa, 0xd2, 0xb4, 0x3c, 0x8b, 0xc2, 0x6e, 0x04, 0x56, 0xed, 0x4a, 0x09, + 0x03, 0x47, 0x72, 0x34, 0x97, 0x8d, 0x50, 0x72, 0xaa, 0x3f, 0x59, 0xdb, 0x41, 0xe0, 0x80, 0xcb, 0x75, 0x75, 0x78, + 0x79, 0xe4, 0xb8, 0x57, 0xfe, 0x95, 0x12, 0xab, 0x5e, 0x2a, 0xb9, 0x88, 0x2c, 0xbb, 0xec, 0x0e, 0x91, 0x19, 0xf7, + 0x33, 0xf5, 0x42, 0xa8, 0x1b, 0xb2, 0x96, 0xb1, 0xa5, 0xea, 0xf3, 0x96, 0x71, 0x23, 0x83, 0xaf, 0xc4, 0x3a, 0xda, + 0x2d, 0x6b, 0xc4, 0xd1, 0xb4, 0x2d, 0x71, 0x1d, 0x2c, 0x40, 0x65, 0x03, 0x77, 0xe6, 0x86, 0xc4, 0x40, 0xbb, 0x4b, + 0x34, 0xd3, 0x99, 0xe2, 0x5c, 0xdb, 0x7d, 0xb4, 0xa7, 0x3c, 0x93, 0xc4, 0x38, 0xa2, 0xe8, 0xdb, 0x12, 0xa2, 0x97, + 0x1a, 0x90, 0xd4, 0x72, 0x0f, 0x31, 0x3e, 0x0d, 0xb7, 0x68, 0x60, 0x8a, 0x33, 0xd4, 0x66, 0x22, 0x0a, 0x94, 0x5d, + 0x53, 0x6c, 0x65, 0x8b, 0xae, 0x57, 0x14, 0x02, 0x91, 0x88, 0x52, 0xdd, 0xa5, 0x3a, 0x91, 0x57, 0x70, 0x22, 0xaf, + 0xd0, 0x4c, 0xb8, 0x58, 0xe6, 0xb5, 0xaf, 0x54, 0xb1, 0xfe, 0xb8, 0x74, 0x68, 0xec, 0xc6, 0x05, 0xb1, 0xaf, 0x60, + 0x79, 0x57, 0x97, 0x50, 0x1d, 0xe7, 0xe3, 0xb8, 0x62, 0x42, 0x57, 0xad, 0x13, 0xba, 0x32, 0xc6, 0x79, 0xaa, 0xf4, + 0x7c, 0xec, 0x1d, 0xf2, 0x89, 0x4c, 0xd6, 0xdc, 0x45, 0x70, 0x8d, 0x97, 0x1a, 0x60, 0x03, 0x77, 0xee, 0x4d, 0x5c, + 0x54, 0x89, 0xc7, 0x51, 0x7e, 0x10, 0x51, 0xae, 0x57, 0xf1, 0xeb, 0x83, 0xa0, 0xd5, 0x96, 0xf2, 0x6c, 0xe6, 0xcb, + 0x53, 0xb4, 0x90, 0x38, 0x8d, 0xbc, 0x73, 0x01, 0x4b, 0xce, 0xcc, 0x50, 0x9a, 0xb4, 0x2a, 0xd6, 0x34, 0x88, 0xe7, + 0xa8, 0xc6, 0x9d, 0x56, 0xe7, 0x6f, 0x0b, 0xdb, 0xb1, 0x59, 0x05, 0xe7, 0x5e, 0xc0, 0x37, 0x7f, 0xda, 0x42, 0xbd, + 0xb5, 0x29, 0x43, 0xac, 0x3c, 0xd0, 0xef, 0x7d, 0xcc, 0x15, 0x6a, 0xe5, 0xcb, 0x09, 0x9c, 0xa5, 0xdc, 0x92, 0x4a, + 0xad, 0xa5, 0xbf, 0x94, 0xf8, 0xec, 0x81, 0xbf, 0xff, 0x00, 0xaa, 0x00, 0x57, 0x33, 0x11, 0x3a, 0x21, 0xd9, 0xa3, + 0x67, 0x68, 0x81, 0xc4, 0x84, 0x3c, 0xb8, 0x64, 0xef, 0x49, 0xc8, 0x52, 0xbf, 0xe7, 0x12, 0x23, 0xf3, 0x37, 0xc2, + 0x08, 0xc5, 0xe3, 0x42, 0x94, 0x8d, 0x5f, 0x52, 0x00, 0x63, 0x1c, 0xb6, 0xe5, 0xac, 0x66, 0xfe, 0x81, 0x7b, 0xac, + 0x4c, 0x82, 0xf0, 0xf5, 0x7b, 0x2e, 0x94, 0xab, 0xcc, 0x40, 0x97, 0x8d, 0x7e, 0xae, 0x6d, 0xc7, 0x83, 0xca, 0x66, + 0x6d, 0x3c, 0x5a, 0xb0, 0x6a, 0x28, 0x66, 0x96, 0x17, 0x5e, 0x28, 0xa2, 0x2a, 0xd7, 0x4a, 0xfa, 0x98, 0x5f, 0xa9, + 0x32, 0x67, 0x84, 0x73, 0xed, 0x0d, 0x1f, 0x3e, 0xc4, 0xbf, 0x02, 0x7e, 0x10, 0x97, 0x42, 0x4f, 0xfe, 0x03, 0xa7, + 0x84, 0xdd, 0xc3, 0xf0, 0x8c, 0x70, 0xaf, 0xaa, 0x1b, 0x08, 0xeb, 0xb4, 0x7a, 0x60, 0x1f, 0x54, 0x76, 0x0e, 0x86, + 0x46, 0xb4, 0xc0, 0x86, 0xb1, 0x4f, 0x72, 0x21, 0x16, 0x4a, 0x6b, 0x7e, 0xe5, 0x2b, 0x7d, 0x02, 0x02, 0xa9, 0x2b, + 0xe5, 0x60, 0x4b, 0x1f, 0xcb, 0x29, 0xc3, 0xb5, 0xf3, 0xeb, 0x44, 0x14, 0xab, 0x48, 0xaa, 0x87, 0x00, 0xe7, 0x8d, + 0xcb, 0x51, 0x62, 0xb8, 0x73, 0xb1, 0xf6, 0x72, 0x69, 0x8c, 0x35, 0x95, 0xf0, 0x6c, 0x25, 0x8e, 0xb7, 0x86, 0x10, + 0x72, 0x2d, 0xbc, 0x2b, 0x8d, 0x16, 0xed, 0x03, 0xf7, 0x3d, 0x76, 0xf8, 0xd6, 0xa6, 0xec, 0xc2, 0xc0, 0xa6, 0x8e, + 0x96, 0x7c, 0x95, 0x2e, 0x81, 0x3a, 0xa6, 0xac, 0x46, 0xc6, 0xd8, 0xae, 0x5d, 0x29, 0xb4, 0x07, 0x4e, 0x1d, 0xce, + 0x5b, 0xe1, 0x5e, 0x2a, 0x12, 0x41, 0xcb, 0x8f, 0x8d, 0xfa, 0x8e, 0x7b, 0x6a, 0x08, 0x4c, 0xb2, 0xba, 0x06, 0xf0, + 0x47, 0xd2, 0x0f, 0xc7, 0xe5, 0x48, 0x49, 0x39, 0x0c, 0x85, 0xaf, 0xb3, 0x42, 0xb9, 0x82, 0x38, 0xac, 0x81, 0xbf, + 0x1f, 0xa0, 0x0e, 0xaa, 0x71, 0x3b, 0x8c, 0x4d, 0xc9, 0x6d, 0xb2, 0x46, 0xd4, 0x21, 0xaf, 0x7e, 0x46, 0x4d, 0x1f, + 0x96, 0xd9, 0xa1, 0xbb, 0x24, 0x01, 0x0f, 0xea, 0x9b, 0x1e, 0x3e, 0x9c, 0xd3, 0xef, 0xd2, 0xb0, 0x4c, 0x63, 0x0b, + 0x64, 0xc7, 0x9d, 0x19, 0x79, 0xbb, 0x50, 0xda, 0xac, 0x49, 0x05, 0x24, 0x45, 0x26, 0x38, 0x88, 0x09, 0x5a, 0x2e, + 0x19, 0xe2, 0xb2, 0x15, 0x19, 0xd4, 0x00, 0xf1, 0x85, 0x55, 0x80, 0xb0, 0xcf, 0x45, 0x50, 0x26, 0x2f, 0x40, 0x71, + 0xaf, 0x38, 0x5e, 0x41, 0xe9, 0xca, 0x60, 0x4d, 0x1e, 0x6e, 0xb0, 0x28, 0x77, 0x11, 0xd8, 0x26, 0xcb, 0x05, 0x7a, + 0xa2, 0x6a, 0x46, 0x92, 0x48, 0x02, 0x32, 0xd0, 0x33, 0xa5, 0xd3, 0xfa, 0x78, 0x1b, 0xa2, 0xa5, 0xc2, 0x3f, 0x34, + 0x5e, 0x1c, 0x29, 0xea, 0xb1, 0x30, 0xaf, 0x83, 0xbb, 0x61, 0x17, 0x0d, 0x11, 0x35, 0x5a, 0x1d, 0xd6, 0xed, 0xfc, + 0x48, 0x1a, 0x2a, 0x66, 0xa5, 0x89, 0x00, 0xe0, 0xba, 0x01, 0x1f, 0x02, 0xce, 0xc5, 0x3f, 0x37, 0x37, 0xd6, 0xa6, + 0x85, 0x16, 0xa1, 0x3f, 0x7e, 0x7c, 0xe3, 0x51, 0xba, 0x2a, 0x78, 0xb8, 0xb9, 0xd9, 0x1d, 0x0c, 0x24, 0x55, 0xa0, + 0xd5, 0x3a, 0x48, 0x1f, 0x48, 0xb2, 0x41, 0x1d, 0x59, 0xa8, 0x8b, 0x14, 0x04, 0x93, 0x0d, 0xf2, 0x16, 0xb3, 0x63, + 0x1b, 0x93, 0x1a, 0xe2, 0x46, 0x62, 0xec, 0xbe, 0x06, 0x49, 0xd1, 0x84, 0x3e, 0x9c, 0x92, 0x52, 0x1c, 0xfa, 0x97, + 0xad, 0x02, 0x4b, 0x17, 0x4d, 0x79, 0xad, 0x59, 0x51, 0x2c, 0x72, 0x6f, 0x73, 0x33, 0x58, 0x00, 0x8b, 0x1d, 0xe3, + 0x35, 0xcf, 0x2f, 0xce, 0x30, 0xc0, 0x84, 0xe5, 0x56, 0xde, 0x2d, 0x93, 0x58, 0xbe, 0x38, 0x72, 0x67, 0x31, 0x9d, + 0x49, 0x34, 0x3b, 0x98, 0x47, 0x4a, 0x37, 0x39, 0x72, 0xd4, 0x0f, 0xb4, 0xc1, 0xbc, 0xb9, 0xa9, 0xd0, 0x0b, 0xfb, + 0xfd, 0xdd, 0xf1, 0x2c, 0x16, 0x06, 0xae, 0x91, 0xbc, 0xff, 0x8e, 0x67, 0x94, 0x91, 0xe2, 0xd3, 0x19, 0xbd, 0x8c, + 0x91, 0xce, 0xf3, 0x61, 0xbf, 0x4d, 0x92, 0xab, 0x32, 0x1a, 0x24, 0x63, 0xe3, 0xe9, 0x75, 0x3f, 0x8c, 0x30, 0x43, + 0x87, 0xa5, 0xd4, 0x35, 0xb3, 0xd8, 0x31, 0x6d, 0x2a, 0xae, 0x6a, 0xaa, 0xb0, 0xdf, 0x12, 0xc3, 0x79, 0x27, 0x92, + 0x8e, 0x43, 0x1b, 0x43, 0xcf, 0x7e, 0x49, 0x42, 0xd4, 0x88, 0x8c, 0x0b, 0xb5, 0x7c, 0x2d, 0x36, 0x88, 0x50, 0xaa, + 0xa1, 0xdf, 0xfd, 0x2d, 0xd4, 0xe6, 0x32, 0xa6, 0x70, 0xef, 0xa5, 0x29, 0x33, 0xb9, 0x48, 0x31, 0x48, 0x14, 0xf7, + 0xfe, 0xc3, 0x1d, 0x52, 0xe3, 0x7f, 0x84, 0x42, 0x03, 0xb0, 0xf1, 0x03, 0xf6, 0xa4, 0x01, 0x02, 0x8d, 0x62, 0x64, + 0x66, 0x17, 0x50, 0x92, 0xf9, 0x37, 0xa4, 0xdb, 0x49, 0x7c, 0xac, 0x3b, 0x8d, 0xcf, 0xe0, 0x48, 0x66, 0x51, 0xb8, + 0x4c, 0x42, 0x38, 0xd0, 0xd7, 0x5e, 0x54, 0x8e, 0xa8, 0x25, 0x5f, 0x65, 0x0c, 0xfb, 0xa1, 0x3a, 0x85, 0x8f, 0x59, + 0xc5, 0x24, 0xde, 0xcd, 0xcd, 0x6b, 0x65, 0x5c, 0x26, 0x45, 0x38, 0x13, 0x51, 0xce, 0x11, 0xcc, 0x95, 0xbe, 0x47, + 0x62, 0xf0, 0x9d, 0xad, 0x1d, 0x40, 0x41, 0xe9, 0x28, 0xa7, 0x20, 0x7d, 0x49, 0xa8, 0x5c, 0x57, 0x69, 0x5e, 0x23, + 0xcc, 0x6a, 0x91, 0xf8, 0x98, 0xca, 0x54, 0x8e, 0x8f, 0xe9, 0x3e, 0xd5, 0xf6, 0x6f, 0xb2, 0xed, 0xd4, 0x59, 0x25, + 0x38, 0xb1, 0x54, 0x7b, 0xbf, 0x1a, 0x77, 0x76, 0x6c, 0x3c, 0xa3, 0x26, 0x1c, 0x55, 0x37, 0x38, 0xae, 0xcc, 0x19, + 0x05, 0x24, 0x36, 0x0b, 0xa8, 0x77, 0x65, 0x22, 0x42, 0x41, 0xb8, 0xf3, 0xb1, 0x5d, 0x1f, 0x27, 0xe6, 0xdb, 0x20, + 0x00, 0x85, 0xae, 0x6d, 0xb0, 0x04, 0x00, 0x43, 0x09, 0x17, 0x4b, 0x34, 0x91, 0xfa, 0x96, 0x38, 0x63, 0x5b, 0x96, + 0xfb, 0x2c, 0x52, 0xbf, 0x2c, 0xf7, 0x55, 0xe6, 0x3f, 0x8b, 0xba, 0x56, 0x8f, 0xf2, 0xcd, 0x5a, 0xee, 0xe7, 0x94, + 0x7f, 0xa2, 0xc7, 0x34, 0x92, 0x5c, 0xee, 0xbb, 0xcc, 0xa7, 0x30, 0x4b, 0x7f, 0x0d, 0xfd, 0xe1, 0xe3, 0xa7, 0x7a, + 0x7f, 0x4f, 0x85, 0x98, 0x1d, 0x85, 0xe2, 0x8a, 0x3d, 0x41, 0xe0, 0x57, 0x44, 0xe7, 0x68, 0x6d, 0x2e, 0x24, 0xde, + 0x86, 0xe4, 0x21, 0x31, 0xe5, 0xe8, 0xea, 0x93, 0x5c, 0x7e, 0x82, 0x49, 0x7b, 0xb4, 0xa4, 0x5c, 0x7f, 0x77, 0x90, + 0xea, 0x8e, 0x70, 0xb5, 0x30, 0x82, 0xdf, 0xda, 0x4e, 0x8e, 0xab, 0xc2, 0x7f, 0xea, 0xf3, 0x15, 0xe5, 0x52, 0x49, + 0x0f, 0x68, 0xfb, 0xab, 0xc1, 0xc1, 0x4d, 0xae, 0x4c, 0x19, 0x12, 0x9d, 0xf2, 0x47, 0x08, 0xff, 0x07, 0x62, 0xfd, + 0x9e, 0x91, 0xa4, 0x0f, 0x50, 0x20, 0x85, 0x6f, 0x02, 0x42, 0x2b, 0xa6, 0x48, 0x4e, 0xa5, 0xfb, 0x5b, 0x26, 0x5f, + 0x08, 0x05, 0x87, 0x7a, 0x2b, 0x15, 0x1e, 0x84, 0xf3, 0xbe, 0x49, 0x2a, 0x82, 0xee, 0xbf, 0xd0, 0xdd, 0x80, 0xc2, + 0x98, 0x38, 0xe5, 0x38, 0x96, 0x3c, 0xdc, 0x25, 0xc0, 0xc4, 0x14, 0x28, 0x29, 0x0b, 0x0e, 0x15, 0x07, 0xb4, 0xb0, + 0xc6, 0xab, 0xd2, 0xe3, 0x62, 0xfd, 0xfd, 0xaf, 0x15, 0x0c, 0x1b, 0x77, 0xad, 0x83, 0x22, 0xcd, 0x82, 0xb3, 0xc8, + 0x1a, 0x09, 0x15, 0x45, 0x8c, 0x76, 0x85, 0x18, 0x29, 0x47, 0x6b, 0xef, 0xf0, 0x97, 0x82, 0x66, 0x32, 0xe5, 0xb7, + 0xd2, 0x59, 0xe0, 0x5b, 0xb9, 0x9a, 0xcf, 0x0a, 0xbc, 0x65, 0xa6, 0x92, 0xe2, 0x9b, 0x9a, 0x24, 0x9b, 0xfa, 0xaf, + 0xc8, 0xb0, 0x95, 0x7c, 0xe6, 0x14, 0x63, 0xee, 0x7c, 0x4e, 0x39, 0xe9, 0x1f, 0x40, 0xed, 0xcb, 0x94, 0x80, 0x40, + 0xa2, 0x2d, 0x26, 0xae, 0x13, 0x99, 0x0e, 0x53, 0x27, 0x0a, 0x0a, 0x28, 0x51, 0x10, 0xec, 0x74, 0x04, 0x87, 0xb3, + 0x3b, 0xa9, 0xec, 0xd6, 0xaf, 0xdc, 0xa2, 0x0b, 0x2d, 0xb9, 0xc7, 0xf8, 0x3c, 0xa8, 0xd1, 0x40, 0x95, 0x64, 0x9d, + 0x9a, 0x73, 0xda, 0x7c, 0x97, 0x39, 0xbd, 0xbf, 0xa2, 0xb7, 0x5b, 0xa0, 0x98, 0xe5, 0x09, 0x1e, 0xee, 0xc0, 0x68, + 0x1e, 0xd8, 0x29, 0x1a, 0xf1, 0xc4, 0x31, 0x80, 0xc5, 0xdc, 0x04, 0x16, 0xb8, 0xa2, 0x92, 0xd0, 0xf8, 0xfe, 0xe0, + 0xfd, 0x3b, 0x11, 0xc3, 0x6a, 0x6e, 0x7e, 0x80, 0x2b, 0x2a, 0xa4, 0xed, 0x6a, 0xc1, 0x67, 0x7d, 0x32, 0x60, 0x0f, + 0xf5, 0x62, 0x3f, 0x7c, 0x28, 0xcb, 0xf6, 0x73, 0xa3, 0x9a, 0x16, 0x83, 0x36, 0x94, 0x36, 0x33, 0x36, 0x30, 0x6e, + 0x6b, 0x9c, 0xcc, 0x89, 0xa5, 0x58, 0xd5, 0xf8, 0xd0, 0x9e, 0xb9, 0x81, 0x92, 0x95, 0xab, 0xdb, 0x44, 0x2b, 0x3b, + 0x41, 0xaa, 0x8f, 0x43, 0x7b, 0x55, 0xf7, 0xa0, 0x16, 0x4a, 0x14, 0x29, 0x22, 0xa0, 0xcf, 0x31, 0x23, 0x09, 0x94, + 0x8f, 0xed, 0xac, 0xd7, 0xe3, 0x85, 0x87, 0x2b, 0xe1, 0xfd, 0x96, 0xc1, 0xe1, 0xe0, 0x08, 0x46, 0xe6, 0x4d, 0xfa, + 0x29, 0x63, 0x4a, 0x79, 0xe3, 0x1b, 0xd8, 0x69, 0x38, 0xde, 0x1b, 0x03, 0x53, 0xb3, 0x19, 0xa3, 0x84, 0xf5, 0x59, + 0xe1, 0xf0, 0x39, 0x52, 0xbb, 0x81, 0xaa, 0x58, 0x32, 0x0d, 0x46, 0x75, 0x8b, 0x21, 0xd6, 0x93, 0x7a, 0x0f, 0xd8, + 0xba, 0xb3, 0x82, 0xec, 0xc6, 0xe8, 0xac, 0xbd, 0x4b, 0xec, 0x14, 0x80, 0x44, 0x95, 0x98, 0x51, 0xa2, 0xc1, 0x0c, + 0x85, 0xa4, 0x41, 0x5e, 0xbc, 0x4d, 0xc3, 0x78, 0x1a, 0x63, 0x04, 0x09, 0xed, 0x4f, 0x98, 0x56, 0xde, 0x3c, 0xe7, + 0x7d, 0x69, 0x2b, 0x1c, 0xab, 0xb0, 0x27, 0x6d, 0x6f, 0x61, 0x01, 0xbc, 0x0c, 0x61, 0x94, 0xa9, 0xe5, 0xf9, 0xb6, + 0x61, 0x15, 0xd2, 0xfc, 0x70, 0xc4, 0xb6, 0x43, 0xd1, 0xbe, 0x5f, 0x38, 0x06, 0xb6, 0x2e, 0x58, 0xa4, 0xd1, 0x32, + 0x36, 0x04, 0x86, 0x35, 0xfb, 0x16, 0x5e, 0x3e, 0x4c, 0xbb, 0xa8, 0x25, 0x3f, 0xe4, 0x29, 0x1e, 0x28, 0x03, 0x49, + 0x53, 0x8b, 0x60, 0x6a, 0x74, 0x52, 0x2d, 0xca, 0x94, 0x12, 0x53, 0x2c, 0x34, 0xfb, 0xc5, 0xd1, 0x28, 0x42, 0xd9, + 0x54, 0xe4, 0xff, 0x20, 0xa6, 0xf7, 0x0d, 0x20, 0x1e, 0xdc, 0x64, 0xc3, 0x03, 0x0d, 0x2f, 0x35, 0x69, 0x85, 0x68, + 0x77, 0x9e, 0x15, 0xa4, 0x1d, 0xdb, 0x00, 0x9c, 0x05, 0xe0, 0x01, 0x6d, 0x45, 0x2a, 0x90, 0x01, 0x30, 0x62, 0x06, + 0x15, 0xb4, 0x28, 0x27, 0xe5, 0xf8, 0x67, 0x29, 0xd0, 0x3c, 0xc8, 0x8a, 0xd9, 0x80, 0x86, 0x90, 0x5e, 0xed, 0x4f, + 0x33, 0xa0, 0xae, 0x52, 0x47, 0x11, 0x54, 0x8a, 0xd6, 0xa5, 0x53, 0x9b, 0x03, 0x8e, 0x38, 0xc6, 0xa0, 0x34, 0x19, + 0x0a, 0x5e, 0x2a, 0x3d, 0x04, 0x40, 0x36, 0xd0, 0xea, 0x79, 0x6b, 0xc1, 0x81, 0xab, 0x75, 0xd7, 0xfa, 0xbc, 0xb1, + 0xad, 0xba, 0x12, 0x17, 0xfe, 0x8a, 0xad, 0x03, 0x94, 0xc8, 0x4c, 0x28, 0x41, 0x17, 0x9f, 0x2f, 0x19, 0x20, 0x4d, + 0x3a, 0xfa, 0x45, 0x65, 0xfd, 0x1e, 0x3e, 0xdc, 0xe0, 0x83, 0x50, 0x29, 0x45, 0xe2, 0xdb, 0x24, 0xa6, 0x0a, 0xa9, + 0x29, 0x55, 0x4c, 0x70, 0xa1, 0xed, 0x47, 0x48, 0x11, 0xd7, 0x96, 0xa9, 0x8d, 0x17, 0xa8, 0x63, 0x54, 0x45, 0xae, + 0xcc, 0x22, 0xb4, 0x63, 0x41, 0x17, 0xf0, 0x2c, 0x90, 0x8e, 0x65, 0x5e, 0xc9, 0xb7, 0x44, 0xac, 0xa9, 0x9f, 0xbf, + 0x08, 0xc1, 0x3f, 0x8d, 0xe0, 0x0d, 0x89, 0x3b, 0x95, 0xcc, 0xbf, 0x56, 0xd6, 0x2e, 0xee, 0x6f, 0x54, 0x1a, 0xba, + 0xa4, 0x4c, 0x28, 0xcd, 0x4e, 0xbf, 0x97, 0xc9, 0xa3, 0xb8, 0xfa, 0xa7, 0x14, 0x3f, 0x18, 0x57, 0x7e, 0xf9, 0x95, + 0x5f, 0x92, 0xd2, 0x2d, 0x44, 0x58, 0x06, 0x12, 0xfa, 0x19, 0x95, 0x0b, 0x57, 0xfc, 0xfe, 0x61, 0x19, 0x2d, 0xa3, + 0xea, 0x88, 0x55, 0xd1, 0x2d, 0x03, 0x36, 0xea, 0x08, 0x48, 0xa1, 0x25, 0xea, 0x91, 0x94, 0xa8, 0x27, 0xa5, 0x1f, + 0x93, 0x3a, 0xc1, 0xe8, 0x79, 0x24, 0x96, 0xbd, 0x5a, 0x48, 0xd6, 0x4a, 0x6c, 0x89, 0x81, 0x67, 0x9d, 0x68, 0xc8, + 0x48, 0x9f, 0x75, 0xba, 0x69, 0xa4, 0x4b, 0xe3, 0xa8, 0x09, 0x4a, 0xb8, 0x80, 0x28, 0x08, 0xe8, 0xd3, 0x08, 0xd9, + 0x54, 0xd6, 0x2f, 0x08, 0x48, 0x3e, 0x31, 0x14, 0xb5, 0x42, 0x91, 0xae, 0x3e, 0x9a, 0xd3, 0x34, 0x4c, 0xe3, 0x84, + 0xb9, 0x23, 0x85, 0xfe, 0x1a, 0x2d, 0xcd, 0x7d, 0xb2, 0x78, 0x60, 0x0c, 0xb6, 0x31, 0xaf, 0x29, 0x50, 0x24, 0xea, + 0x52, 0xea, 0x9a, 0xd7, 0x64, 0xfb, 0x32, 0x03, 0xee, 0x58, 0xf5, 0x13, 0x42, 0x3f, 0x33, 0x79, 0x0f, 0x49, 0x31, + 0x45, 0xb7, 0x7e, 0x22, 0xe8, 0x2b, 0x5b, 0xb0, 0xda, 0x29, 0xb0, 0x34, 0x11, 0xc5, 0x09, 0x81, 0x61, 0xfc, 0xc2, + 0x5b, 0xcf, 0xe2, 0x7e, 0xee, 0xe4, 0xa7, 0x44, 0x5a, 0x31, 0x2a, 0x60, 0x48, 0x22, 0x6d, 0xd8, 0x9c, 0xd7, 0x19, + 0x86, 0xbf, 0x4a, 0xfc, 0xdf, 0x42, 0x5b, 0x28, 0xbf, 0x93, 0xba, 0x80, 0x7f, 0xc5, 0xd4, 0x80, 0x52, 0x60, 0xa0, + 0xd1, 0x64, 0x7d, 0x4f, 0x27, 0x88, 0xe0, 0x12, 0xa1, 0xa2, 0x70, 0x13, 0xb9, 0x34, 0xae, 0x6a, 0xcc, 0x7d, 0x4b, + 0x32, 0x6e, 0xae, 0x6c, 0x70, 0x8c, 0xad, 0x16, 0x78, 0x15, 0xff, 0x46, 0x23, 0x15, 0xb3, 0x54, 0x07, 0x89, 0xd5, + 0x99, 0xc8, 0xf9, 0xe8, 0x83, 0x33, 0x97, 0x07, 0x67, 0x56, 0xfa, 0x13, 0x9c, 0x0e, 0x32, 0x88, 0x40, 0xaf, 0xcf, + 0x11, 0x65, 0xca, 0x95, 0xcf, 0xfc, 0x39, 0xd0, 0xf2, 0x33, 0x57, 0x78, 0x1e, 0x02, 0x26, 0x9b, 0xbb, 0x33, 0x25, + 0xe3, 0x0d, 0x7d, 0x54, 0x19, 0xc1, 0x59, 0xc6, 0x3f, 0xed, 0xd6, 0x2e, 0xe1, 0xe1, 0x0c, 0x2b, 0xe0, 0x1f, 0x94, + 0x88, 0x52, 0x2c, 0xf0, 0xdf, 0x35, 0x38, 0xd6, 0x13, 0x85, 0x30, 0x46, 0x77, 0xe9, 0x8b, 0xfe, 0xdd, 0xa9, 0xbf, + 0xac, 0x1c, 0x05, 0xe8, 0xa1, 0x5a, 0xe0, 0x0b, 0xca, 0x15, 0x40, 0x3d, 0xe9, 0xa4, 0x10, 0x8a, 0xd9, 0x53, 0x3a, + 0x7e, 0x00, 0x78, 0x70, 0xb8, 0x30, 0x20, 0xa9, 0xc4, 0xc4, 0x51, 0xa5, 0xf7, 0x5f, 0x2a, 0xf9, 0xb5, 0xf4, 0x10, + 0x04, 0x70, 0x31, 0x91, 0x4d, 0x92, 0x42, 0x74, 0xfc, 0x13, 0xca, 0x72, 0x12, 0x8c, 0xeb, 0xf9, 0x16, 0xb3, 0xc0, + 0x2d, 0x31, 0x2f, 0x3c, 0x0e, 0xe8, 0x03, 0xa0, 0x85, 0x18, 0xe8, 0x2e, 0xe2, 0x13, 0x0c, 0xcd, 0xe8, 0x7a, 0x83, + 0x0f, 0x61, 0xed, 0xb1, 0x01, 0x58, 0xca, 0x50, 0xee, 0x97, 0x3f, 0x25, 0xf6, 0x21, 0x66, 0xef, 0x3c, 0x72, 0x85, + 0xba, 0x5a, 0x07, 0xe4, 0x32, 0x6c, 0xaf, 0x1e, 0x20, 0x76, 0x13, 0xec, 0x5b, 0x61, 0x6b, 0x73, 0x80, 0x54, 0x21, + 0xc9, 0xb2, 0x8c, 0xc4, 0x0d, 0x30, 0x50, 0xe2, 0x12, 0x61, 0xac, 0xbe, 0x9a, 0xad, 0xf6, 0x28, 0x06, 0x11, 0x19, + 0x4b, 0x8b, 0x14, 0x69, 0xee, 0xd6, 0x6a, 0x51, 0xb4, 0x22, 0x33, 0x84, 0x26, 0xaf, 0x13, 0x2f, 0x6b, 0x19, 0xe7, + 0xd7, 0xdb, 0xbe, 0xe0, 0x6a, 0x48, 0x73, 0xed, 0x0d, 0xd3, 0xd0, 0x6d, 0xa9, 0x57, 0x46, 0x44, 0x82, 0x8c, 0x19, + 0x21, 0xa6, 0xa5, 0x32, 0x60, 0x7b, 0x10, 0x89, 0x4b, 0x8b, 0xf4, 0xbc, 0xcc, 0xc1, 0xbb, 0xa1, 0x8c, 0x53, 0x6a, + 0x5f, 0xd3, 0x99, 0x8b, 0xb8, 0x91, 0xa4, 0x54, 0x91, 0x02, 0x70, 0xd9, 0xa3, 0x23, 0xba, 0x99, 0x05, 0x25, 0x73, + 0xa2, 0x56, 0x9d, 0x46, 0xe7, 0xe2, 0x73, 0xbc, 0xe2, 0x28, 0x95, 0xcf, 0x8d, 0xf9, 0xfe, 0x62, 0x8a, 0x2f, 0xc8, + 0x6d, 0x4f, 0xfb, 0x11, 0x58, 0x71, 0x42, 0x0e, 0x6a, 0xac, 0x7a, 0xfc, 0x2e, 0x43, 0x68, 0x8f, 0xfb, 0x00, 0x4f, + 0x49, 0x70, 0x11, 0x9f, 0x05, 0xc0, 0x20, 0xf5, 0x2f, 0xe0, 0x94, 0xa7, 0x99, 0x92, 0x1a, 0x5b, 0xcf, 0x16, 0x8b, + 0x39, 0x7c, 0x82, 0xae, 0x18, 0x72, 0x75, 0xb5, 0x5c, 0x52, 0x2c, 0xae, 0x90, 0xa7, 0x17, 0x42, 0x74, 0x9e, 0x5f, + 0x9c, 0x59, 0x1e, 0x3d, 0x9d, 0xca, 0x87, 0xf3, 0x20, 0xff, 0x6c, 0x79, 0xca, 0xa6, 0x4e, 0x3e, 0x90, 0x54, 0x1e, + 0xfd, 0x9d, 0x50, 0xa6, 0xfb, 0x5d, 0x86, 0x96, 0xd9, 0xaa, 0xe3, 0xbd, 0x80, 0x7a, 0xed, 0x0d, 0x9d, 0x31, 0xb7, + 0x68, 0xbc, 0x9b, 0x2c, 0x33, 0xb4, 0xf6, 0xe3, 0xd8, 0xce, 0x5c, 0x6b, 0x4c, 0x9d, 0x71, 0x47, 0x12, 0xa0, 0x2d, + 0xfd, 0x4d, 0x5b, 0x39, 0xd1, 0x1c, 0x92, 0xb7, 0x80, 0xf8, 0xe1, 0x6c, 0x9e, 0xb9, 0x29, 0xbc, 0xfe, 0xaf, 0xf6, + 0xd7, 0x0f, 0x36, 0xcf, 0x0c, 0x81, 0x92, 0xe2, 0xc9, 0x49, 0x98, 0x88, 0x9e, 0xc6, 0x72, 0x7d, 0x89, 0x66, 0xc5, + 0xd8, 0x41, 0xc3, 0xc1, 0xc0, 0x35, 0x8e, 0x56, 0x8d, 0x73, 0xd4, 0x91, 0x6d, 0xe3, 0x47, 0xc5, 0xa3, 0xc8, 0xd9, + 0x8c, 0x6a, 0xa6, 0xb6, 0x75, 0xeb, 0x38, 0x43, 0x93, 0xcf, 0xa6, 0xfb, 0xf9, 0x52, 0x65, 0x88, 0x67, 0x67, 0x47, + 0xed, 0x25, 0x5d, 0xfb, 0x26, 0x97, 0xf0, 0x3a, 0xc5, 0xc0, 0x7c, 0xc2, 0x80, 0x3f, 0xf3, 0xd3, 0x65, 0xbf, 0x88, + 0xf2, 0x02, 0x25, 0x2c, 0x24, 0xc2, 0x1b, 0xd1, 0x6c, 0x32, 0xcd, 0x71, 0x1a, 0x0e, 0x3b, 0xc8, 0x98, 0x92, 0x3b, + 0x4e, 0xe0, 0x8c, 0x73, 0xa9, 0x15, 0xf5, 0xc4, 0x93, 0x9e, 0x4b, 0x20, 0xe6, 0x22, 0x94, 0x79, 0xaa, 0x26, 0xc7, + 0xa6, 0xd7, 0x9d, 0xa7, 0x5a, 0xd4, 0xaf, 0x88, 0x70, 0x76, 0x25, 0xb0, 0xe0, 0x64, 0xfb, 0x1b, 0x99, 0xbc, 0x45, + 0x19, 0x01, 0x32, 0x34, 0x7c, 0x05, 0xd6, 0x1e, 0xd0, 0x9a, 0xb0, 0x82, 0x35, 0xf8, 0x7c, 0x51, 0x90, 0xa4, 0x70, + 0x8a, 0xf5, 0x06, 0xfd, 0x58, 0xe3, 0xbc, 0x35, 0x24, 0x8a, 0x31, 0x3f, 0xb9, 0x70, 0xca, 0x21, 0x00, 0x81, 0x71, + 0x60, 0x39, 0x25, 0x09, 0x07, 0x9d, 0x47, 0x57, 0xb3, 0x88, 0x82, 0xcd, 0xb3, 0x33, 0x57, 0x24, 0x03, 0xce, 0xb3, + 0x3f, 0x97, 0x4a, 0xb6, 0x2c, 0x56, 0x94, 0xcd, 0x19, 0x47, 0x94, 0xd4, 0x49, 0x01, 0xf4, 0x85, 0x82, 0xb4, 0x67, + 0xa8, 0x69, 0x3c, 0xf7, 0x17, 0xec, 0x03, 0xe0, 0x9e, 0xc2, 0x13, 0x19, 0xfb, 0xd3, 0xd7, 0xc7, 0xf0, 0x8b, 0xa5, + 0xc1, 0xa3, 0xf3, 0xf1, 0xe9, 0xf8, 0xb8, 0xeb, 0x6f, 0x79, 0xb6, 0x30, 0x60, 0xb0, 0x74, 0x66, 0x02, 0xab, 0x6b, + 0x8b, 0xf8, 0x2f, 0x5d, 0x4e, 0x10, 0xe6, 0x18, 0x34, 0x36, 0xc5, 0x11, 0xb3, 0x07, 0x3d, 0x4a, 0x06, 0x56, 0x7d, + 0xe1, 0x58, 0x8e, 0x2b, 0x5b, 0x23, 0x5f, 0x4f, 0xbb, 0x37, 0xec, 0x0c, 0xb1, 0x94, 0x2a, 0xfb, 0xdc, 0x98, 0x0f, + 0x30, 0x0e, 0xf3, 0xbf, 0xb6, 0xf6, 0x8b, 0xed, 0xb6, 0xf7, 0x28, 0x43, 0xd0, 0x74, 0xef, 0xd5, 0xf1, 0xb0, 0xd3, + 0x6b, 0xeb, 0x98, 0x56, 0xe1, 0x42, 0x2a, 0xdf, 0x8e, 0xf7, 0x30, 0x98, 0xef, 0x71, 0xcf, 0x94, 0x4b, 0x1c, 0x6f, + 0xee, 0x38, 0x8f, 0x76, 0x1c, 0xf7, 0xd8, 0x3f, 0xfe, 0x72, 0xc7, 0x3d, 0x66, 0x70, 0xd2, 0x19, 0x7a, 0x17, 0xbe, + 0x8a, 0x81, 0xb3, 0xc5, 0x43, 0x80, 0x1e, 0xe1, 0x88, 0xd0, 0x01, 0x13, 0x0e, 0x4d, 0xf6, 0xd7, 0x03, 0x18, 0xe9, + 0x45, 0x7d, 0xe8, 0x17, 0xf5, 0xb1, 0x1a, 0x5e, 0x5d, 0x9d, 0x2d, 0xaf, 0xd9, 0xcc, 0xf0, 0xe9, 0xa0, 0xa3, 0xf7, + 0x80, 0xfa, 0xbb, 0x7d, 0x4d, 0xe4, 0x90, 0x1a, 0xab, 0x62, 0xf6, 0xb4, 0x4d, 0x73, 0xa8, 0x36, 0xd9, 0x32, 0x87, + 0xde, 0xef, 0x9a, 0x44, 0x79, 0xfc, 0xe5, 0x16, 0x82, 0x23, 0x94, 0x81, 0x52, 0x8b, 0x68, 0x86, 0x83, 0x43, 0x41, + 0x05, 0x99, 0x58, 0x77, 0x57, 0x2c, 0x3f, 0xaf, 0x1a, 0xef, 0xc8, 0x05, 0x6e, 0x0d, 0x8d, 0x85, 0x3e, 0xe0, 0x69, + 0x88, 0x6f, 0x44, 0xb4, 0x27, 0xe1, 0x64, 0x26, 0x5f, 0xba, 0x2a, 0xcf, 0xdd, 0x05, 0x26, 0x74, 0xd6, 0xe1, 0x16, + 0x53, 0xff, 0xab, 0xbd, 0xb3, 0x0e, 0x9d, 0x22, 0x8c, 0x1a, 0xe6, 0x5b, 0x5f, 0x75, 0xcf, 0xc4, 0xe5, 0xee, 0x58, + 0x4e, 0xf7, 0x2b, 0x6b, 0xff, 0xab, 0x6e, 0xda, 0xb5, 0xf6, 0x36, 0xcf, 0xf6, 0x2d, 0x6d, 0xdc, 0x8f, 0x29, 0x2f, + 0xb9, 0xa3, 0x00, 0x9e, 0x44, 0x17, 0x13, 0xd5, 0xfb, 0x5c, 0xf5, 0xcb, 0xc6, 0x67, 0x6e, 0x38, 0x52, 0x91, 0xd0, + 0x81, 0x0e, 0x0f, 0xe4, 0xb3, 0x35, 0x8c, 0xce, 0x2d, 0x4f, 0x27, 0x85, 0x1d, 0xcf, 0xbd, 0xc0, 0x9d, 0xf9, 0x00, + 0xaf, 0x43, 0x77, 0xb2, 0x09, 0xd4, 0xa1, 0x07, 0x34, 0x67, 0xa6, 0x5f, 0x4f, 0x30, 0xaf, 0xad, 0xfe, 0x1c, 0xea, + 0x41, 0x5f, 0x9b, 0x13, 0xa7, 0xd6, 0x84, 0xce, 0x50, 0x03, 0xac, 0xe5, 0xd4, 0xb7, 0xe1, 0xb0, 0x91, 0xa9, 0xe6, + 0xd2, 0x3e, 0x43, 0x2a, 0x6f, 0x79, 0xb8, 0x38, 0xf2, 0xcf, 0x2a, 0xd2, 0x37, 0x49, 0x17, 0x4f, 0x95, 0x4f, 0x1e, + 0xd2, 0x90, 0xda, 0x0f, 0x2f, 0x44, 0x1b, 0x80, 0x8b, 0x38, 0xba, 0xfc, 0x26, 0xbd, 0x12, 0xfb, 0x52, 0xdf, 0x62, + 0xda, 0x99, 0x7a, 0xe1, 0xa4, 0x5e, 0x30, 0x37, 0xf9, 0x6d, 0xd3, 0xbd, 0x78, 0xc9, 0x2e, 0xab, 0x8a, 0x20, 0x09, + 0x96, 0xec, 0x3a, 0x4d, 0x92, 0x53, 0x25, 0xf0, 0xf4, 0xa7, 0x11, 0x5c, 0x3d, 0x13, 0x51, 0x29, 0xab, 0x01, 0x85, + 0x46, 0x14, 0x09, 0x45, 0x6b, 0xff, 0x36, 0xf6, 0x83, 0x65, 0x25, 0x32, 0x30, 0x51, 0x6f, 0x50, 0x6c, 0xe4, 0xa0, + 0x9d, 0x2f, 0x8d, 0xd4, 0x7d, 0x46, 0xf9, 0xac, 0x46, 0x7b, 0x69, 0xa5, 0x86, 0x94, 0x88, 0x76, 0x06, 0x6c, 0x46, + 0xc9, 0x85, 0x42, 0x83, 0xe6, 0x24, 0xfe, 0x40, 0x27, 0x03, 0x45, 0x27, 0xd4, 0xc8, 0x50, 0x67, 0x9b, 0x76, 0x4a, + 0xb9, 0x11, 0x68, 0xd7, 0x95, 0xaa, 0x72, 0x7d, 0xcc, 0x52, 0xbf, 0xe8, 0x5a, 0xfd, 0x5f, 0xf3, 0x34, 0x19, 0x13, + 0xcf, 0xe8, 0x5b, 0x23, 0xb4, 0x12, 0xe4, 0x5e, 0x7a, 0x79, 0x2f, 0x22, 0x2d, 0x9e, 0xf4, 0x49, 0x4d, 0xc5, 0x43, + 0x4b, 0xd2, 0xe0, 0x70, 0x59, 0x4b, 0x1a, 0xbc, 0x33, 0xd8, 0x11, 0x0b, 0xbd, 0x5c, 0x0a, 0xc7, 0x43, 0x93, 0xde, + 0xa6, 0x78, 0xe3, 0x62, 0xf6, 0xae, 0xd0, 0x2a, 0xe4, 0x96, 0x10, 0x2b, 0xb2, 0x2b, 0x75, 0xea, 0xae, 0x77, 0x11, + 0x40, 0x8b, 0xd8, 0xc0, 0x1f, 0x68, 0x8d, 0xac, 0x4a, 0x27, 0x83, 0x1a, 0x5d, 0xe8, 0x27, 0xe8, 0xfa, 0x13, 0x31, + 0xda, 0xee, 0xd0, 0x0d, 0xf6, 0xfd, 0x1c, 0xd8, 0xaa, 0x7d, 0x84, 0xa7, 0xc2, 0x88, 0x29, 0x43, 0x94, 0x7f, 0xdf, + 0x8e, 0x64, 0x53, 0x68, 0xb3, 0xc6, 0xbc, 0x35, 0xb5, 0x31, 0x11, 0xcc, 0x94, 0x68, 0x2f, 0x31, 0x0c, 0x98, 0xa6, + 0xcb, 0x9a, 0xe3, 0x4a, 0x53, 0x71, 0x64, 0xa8, 0xb0, 0x94, 0x38, 0xaf, 0xa0, 0xf5, 0x16, 0xeb, 0x6b, 0x6d, 0x4a, + 0x32, 0x6d, 0x61, 0x2e, 0x21, 0x1e, 0x84, 0xb7, 0x31, 0x72, 0x23, 0xc2, 0x4e, 0x82, 0x94, 0x37, 0x92, 0x9d, 0x60, + 0x9b, 0x5b, 0xe8, 0x5e, 0x8b, 0x42, 0x1d, 0x89, 0x50, 0xe0, 0x4a, 0xc1, 0x68, 0x04, 0x09, 0xca, 0x2b, 0xee, 0x69, + 0xf3, 0x2f, 0x6d, 0x10, 0x2b, 0xe4, 0x4b, 0x02, 0x4a, 0xb9, 0x16, 0x1a, 0x17, 0x20, 0xf3, 0x04, 0x67, 0xe2, 0x20, + 0x0a, 0xb2, 0xc9, 0xec, 0x43, 0x90, 0x05, 0xe7, 0xb9, 0xbd, 0xe2, 0x35, 0x0a, 0xe0, 0x38, 0x25, 0x5d, 0x3f, 0x95, + 0x27, 0xa9, 0x7a, 0x2b, 0x05, 0x20, 0xa6, 0x3e, 0x27, 0xcb, 0xbc, 0x48, 0xcf, 0x2b, 0x9d, 0x2e, 0xb3, 0x98, 0x3e, + 0xae, 0xf9, 0x9c, 0x6e, 0x62, 0x60, 0x53, 0xe9, 0x42, 0xea, 0xa5, 0xa2, 0x11, 0x69, 0x2e, 0x62, 0x4c, 0x7d, 0x30, + 0xa8, 0x4c, 0x3d, 0xf7, 0x77, 0x07, 0xdb, 0xa3, 0xb7, 0xb0, 0xb0, 0xdd, 0x04, 0xd0, 0xcd, 0x2c, 0x4a, 0x6a, 0xa6, + 0x9c, 0x6c, 0x12, 0x40, 0x3e, 0x9e, 0x00, 0xda, 0xb7, 0xe0, 0xf3, 0x55, 0x5d, 0x3c, 0x10, 0xd9, 0x70, 0x9a, 0x33, + 0xa0, 0xa9, 0xb9, 0x0f, 0xcf, 0x4a, 0xa2, 0x2b, 0xe8, 0x2a, 0xd3, 0x26, 0x00, 0xca, 0x76, 0x01, 0x7a, 0x1b, 0x02, + 0xe3, 0x8a, 0xd3, 0xb6, 0xe1, 0xb5, 0xee, 0x50, 0xef, 0x7c, 0x6a, 0x7a, 0x14, 0xa5, 0xd2, 0x65, 0xa9, 0xd1, 0x69, + 0xca, 0x77, 0x66, 0xac, 0xa7, 0x86, 0x39, 0x29, 0x6c, 0xd1, 0x77, 0x6e, 0xf4, 0xdd, 0x1c, 0xae, 0x32, 0xdb, 0x41, + 0xef, 0x35, 0x1c, 0x06, 0xcb, 0x5b, 0xe4, 0x5b, 0xdd, 0x44, 0xe9, 0x9e, 0x2d, 0xd1, 0xac, 0x98, 0x64, 0x4b, 0xde, + 0x72, 0xe9, 0xa2, 0x52, 0xd0, 0x5b, 0x2c, 0x8d, 0x83, 0xfb, 0x3c, 0xad, 0x30, 0x2c, 0xb1, 0x38, 0x2d, 0x2c, 0x25, + 0x64, 0x4e, 0x45, 0x4a, 0x09, 0x3d, 0xd6, 0xf0, 0x14, 0xa6, 0x17, 0x78, 0x30, 0x87, 0x5a, 0x35, 0x2f, 0xb0, 0x67, + 0x85, 0xf3, 0x0c, 0x1d, 0xbd, 0x64, 0x4d, 0x09, 0x81, 0xbf, 0x8f, 0x39, 0xa8, 0x6b, 0x8f, 0xf9, 0x1b, 0xba, 0xfc, + 0x3f, 0x67, 0xbe, 0x65, 0xd0, 0xad, 0xe7, 0x74, 0x8d, 0xa0, 0xd0, 0x80, 0x99, 0xef, 0x53, 0xd3, 0xd5, 0xc5, 0x90, + 0xf6, 0xc6, 0xfd, 0xc9, 0x2c, 0x9e, 0x87, 0xef, 0xd2, 0x30, 0x42, 0x91, 0x19, 0x59, 0x83, 0x02, 0xd7, 0x5f, 0xe1, + 0xf0, 0xd0, 0x88, 0xb1, 0xc2, 0xf1, 0x7d, 0x1f, 0xa3, 0x6c, 0x18, 0xad, 0xbe, 0xfd, 0x30, 0x9d, 0x2c, 0x31, 0xc2, + 0x86, 0x90, 0x9f, 0x88, 0x78, 0x1b, 0xb6, 0x60, 0xbf, 0xd0, 0x6d, 0x2e, 0x37, 0x7d, 0xce, 0xbf, 0x8f, 0xdd, 0xef, + 0x29, 0xf0, 0x4b, 0xb0, 0x40, 0xb9, 0xc7, 0x73, 0xec, 0x9b, 0xc2, 0xf6, 0x46, 0x94, 0x2c, 0xe9, 0x39, 0x46, 0x47, + 0x49, 0x0a, 0xdf, 0xe2, 0xd0, 0x14, 0x32, 0xa2, 0x08, 0x33, 0x98, 0xbd, 0x53, 0x58, 0xd0, 0xcf, 0x23, 0xe9, 0x33, + 0xdf, 0x0b, 0x28, 0x87, 0x34, 0x90, 0x4c, 0xc5, 0xd8, 0xea, 0x0d, 0xfa, 0xc3, 0xad, 0x5d, 0xc4, 0xdb, 0xd6, 0x00, + 0x08, 0x04, 0xab, 0xcc, 0x17, 0x41, 0xe2, 0x02, 0x7f, 0xa7, 0xda, 0xa0, 0x8f, 0x4b, 0xab, 0xfb, 0x73, 0x66, 0x28, + 0xde, 0x51, 0x73, 0x72, 0x9d, 0x02, 0xc7, 0x00, 0x7b, 0xec, 0xa0, 0xa4, 0x6c, 0x43, 0xd0, 0x93, 0x02, 0xb9, 0xf9, + 0xac, 0xfd, 0x93, 0xc8, 0x0b, 0x91, 0x1d, 0x01, 0x28, 0x94, 0xc6, 0xc3, 0x24, 0x5e, 0xb3, 0x22, 0xf7, 0x43, 0x16, + 0x21, 0xcf, 0xbc, 0xa1, 0x4d, 0x8f, 0xb4, 0x42, 0xa2, 0x5a, 0x05, 0xdd, 0xc8, 0x5f, 0x27, 0xc0, 0x6f, 0x43, 0xb5, + 0xea, 0x9b, 0x4e, 0x7e, 0x9d, 0x14, 0xc1, 0x55, 0xdf, 0x92, 0xd6, 0x84, 0x91, 0xa9, 0x7a, 0xc2, 0xe8, 0x09, 0x78, + 0x05, 0xd0, 0x80, 0xb8, 0x61, 0x66, 0x32, 0x8e, 0x3c, 0xf4, 0x08, 0xac, 0xfa, 0x40, 0xc2, 0xe8, 0x95, 0x4b, 0x92, + 0x79, 0x99, 0x72, 0xc5, 0xea, 0xe5, 0x8d, 0x86, 0x94, 0x57, 0x5b, 0xde, 0x74, 0xeb, 0x53, 0x6f, 0x5a, 0xbc, 0x02, + 0x8f, 0x53, 0x74, 0x8b, 0x7c, 0xf8, 0xb0, 0x2a, 0xa8, 0x4c, 0xa4, 0x8a, 0xb8, 0x51, 0x5c, 0x92, 0x7f, 0xbb, 0xb1, + 0x36, 0x0c, 0x72, 0xf3, 0xdb, 0x17, 0x50, 0x54, 0x32, 0x58, 0xdc, 0xd6, 0x25, 0xaa, 0xeb, 0x6e, 0x8c, 0xe0, 0xbd, + 0x56, 0xbd, 0xad, 0x43, 0x0a, 0xb6, 0x7c, 0xd4, 0x89, 0x71, 0x2d, 0x68, 0x57, 0xfa, 0xac, 0xc6, 0x55, 0x32, 0x1a, + 0x54, 0x9b, 0xac, 0x01, 0x4b, 0x1b, 0x29, 0x2a, 0x72, 0x0c, 0x8b, 0x21, 0x39, 0xf8, 0x89, 0x4c, 0xc4, 0x7e, 0x95, + 0xda, 0x28, 0x4d, 0xbb, 0xb9, 0xa9, 0xae, 0x40, 0xde, 0xbe, 0x30, 0x50, 0x5c, 0x4a, 0x53, 0x03, 0x11, 0x7a, 0x90, + 0x34, 0x52, 0x5e, 0xe4, 0xef, 0x3f, 0x47, 0x1d, 0x22, 0xfa, 0x7e, 0xc3, 0x69, 0x6e, 0x79, 0x31, 0x74, 0x08, 0xf3, + 0xbe, 0xbc, 0x8a, 0xf3, 0x22, 0xf7, 0x5e, 0x85, 0x64, 0x08, 0x05, 0x05, 0xde, 0x3b, 0xcc, 0x2f, 0x98, 0xd3, 0x73, + 0xee, 0x7d, 0x0c, 0xdd, 0x20, 0x0c, 0xa9, 0xfc, 0x45, 0x86, 0x8f, 0xcf, 0x31, 0xca, 0x25, 0xdd, 0x04, 0xef, 0x38, + 0x45, 0x7b, 0x35, 0xcc, 0xee, 0x55, 0x44, 0x18, 0x53, 0xd4, 0xbb, 0x4a, 0x5c, 0x8a, 0x59, 0x47, 0xd5, 0x7f, 0xcc, + 0x48, 0x28, 0xc4, 0xcd, 0xfc, 0x94, 0xa8, 0x1f, 0x5e, 0xb1, 0xc4, 0x76, 0x9e, 0x7d, 0x78, 0x2d, 0x97, 0xd4, 0xbb, + 0x4a, 0x5d, 0x71, 0xb5, 0x09, 0x6d, 0x51, 0x94, 0x1e, 0xef, 0x7c, 0xe9, 0x1e, 0x07, 0x8b, 0xd8, 0x5b, 0x61, 0xfc, + 0x89, 0x0f, 0xaf, 0x9f, 0xb3, 0x85, 0xc9, 0xeb, 0x18, 0x15, 0x07, 0xf0, 0xfb, 0x6d, 0x1a, 0x2e, 0xa1, 0xd6, 0x75, + 0x4a, 0xa0, 0x15, 0x0a, 0x7e, 0x20, 0x73, 0xaf, 0x8f, 0x19, 0xbe, 0x7f, 0x85, 0xb4, 0xa5, 0x37, 0x59, 0xe2, 0x9c, + 0xf8, 0x79, 0xbe, 0xa4, 0x49, 0x19, 0xbd, 0xe6, 0xde, 0xdf, 0xc2, 0xd2, 0x90, 0x56, 0xfd, 0x23, 0x33, 0xa1, 0x9d, + 0x21, 0xe0, 0xb9, 0x02, 0x38, 0xf2, 0xd9, 0x53, 0xa2, 0x1d, 0xcb, 0xfb, 0xaa, 0x73, 0x75, 0x3e, 0x87, 0x49, 0xd1, + 0x0b, 0x9f, 0xec, 0x82, 0xbc, 0xcd, 0xcd, 0xcb, 0xcb, 0xcb, 0xfe, 0xe5, 0x76, 0x3f, 0xcd, 0xce, 0x36, 0x87, 0x5f, + 0x7f, 0xfd, 0xf5, 0x26, 0xbd, 0xb5, 0xbe, 0xaa, 0xbb, 0xbd, 0x17, 0x4e, 0xd4, 0xf5, 0x2d, 0x8a, 0xd8, 0xfd, 0x15, + 0xb2, 0x28, 0xa8, 0x85, 0x03, 0xde, 0xe4, 0x2b, 0x81, 0x74, 0xbe, 0xda, 0x03, 0xf8, 0xc3, 0xed, 0xb7, 0xb5, 0x0c, + 0x68, 0x74, 0xb0, 0x89, 0x12, 0xa8, 0xaf, 0xba, 0x51, 0xd7, 0xda, 0xb7, 0xba, 0x31, 0x32, 0x34, 0x50, 0xb0, 0x6f, + 0x19, 0x06, 0xb6, 0x15, 0x12, 0x51, 0x07, 0x71, 0xb1, 0x36, 0xcf, 0x5c, 0xeb, 0x2b, 0xcb, 0xd1, 0x25, 0x5f, 0x62, + 0xc9, 0x97, 0x5b, 0xbb, 0x66, 0xd9, 0x17, 0x5c, 0xb6, 0x6d, 0x96, 0xed, 0x51, 0xd9, 0xf6, 0x73, 0xb3, 0x6c, 0x9f, + 0xcb, 0x5e, 0x9a, 0x65, 0x7f, 0xcf, 0xbb, 0x58, 0xda, 0x31, 0xad, 0xff, 0x8e, 0x8d, 0xd1, 0x10, 0x16, 0xf2, 0xe2, + 0xf3, 0xe0, 0x2c, 0xc2, 0x41, 0x77, 0x61, 0x9e, 0xae, 0xd5, 0xa5, 0xf1, 0x1a, 0x46, 0x1e, 0xc6, 0x07, 0x5f, 0x2d, + 0xb3, 0xb9, 0x0d, 0x93, 0xa5, 0x46, 0x60, 0x99, 0x9c, 0xaf, 0x04, 0x4a, 0xbb, 0x48, 0xfc, 0x95, 0xa5, 0x53, 0xb3, + 0xf6, 0x54, 0xc2, 0x34, 0x53, 0x1a, 0x57, 0xba, 0x7f, 0xcd, 0xda, 0xab, 0x11, 0x97, 0xc8, 0x6e, 0xba, 0x50, 0xeb, + 0x03, 0x7a, 0x27, 0xe0, 0xa0, 0x3c, 0xeb, 0x22, 0xc8, 0xec, 0x5e, 0x0f, 0xc6, 0xe6, 0xa0, 0x5d, 0xe6, 0x02, 0xd0, + 0x13, 0x50, 0x25, 0x69, 0x8f, 0x1f, 0x2d, 0xce, 0x04, 0x66, 0x51, 0x28, 0x23, 0xfc, 0x07, 0xbe, 0x7d, 0x00, 0xdf, + 0x5a, 0xbd, 0xcb, 0xe8, 0xf4, 0x73, 0x5c, 0xf4, 0x58, 0xb4, 0x78, 0x91, 0xb8, 0xf8, 0x80, 0x7f, 0x75, 0xd7, 0xde, + 0x5f, 0xd1, 0x8d, 0xbb, 0xaa, 0x61, 0x7f, 0x90, 0x6a, 0x12, 0xf5, 0x41, 0x0a, 0x28, 0x7e, 0x54, 0x43, 0xe8, 0x1f, + 0x52, 0x07, 0x30, 0x7f, 0xd7, 0xea, 0x59, 0x5d, 0xc0, 0xea, 0x1f, 0x52, 0x00, 0xd9, 0x86, 0xd3, 0x7c, 0x6a, 0x6e, + 0xf3, 0x38, 0xee, 0xa2, 0x6f, 0x0a, 0xd1, 0x23, 0x9b, 0xff, 0x75, 0xd8, 0x23, 0xe1, 0x61, 0xf7, 0xc1, 0x26, 0x50, + 0x57, 0x8b, 0x2b, 0xf2, 0xe8, 0xf4, 0xac, 0x38, 0x99, 0x45, 0x59, 0x5c, 0x18, 0x47, 0xe5, 0x6a, 0x59, 0xf7, 0xf2, + 0x58, 0x8b, 0x9b, 0x01, 0x37, 0x5a, 0x8c, 0xe7, 0x50, 0xf1, 0x46, 0xb2, 0xa7, 0xbc, 0x2a, 0x21, 0x15, 0x86, 0xbc, + 0x76, 0x0e, 0x07, 0x7c, 0x6f, 0xa3, 0xd7, 0x83, 0x43, 0xae, 0xd5, 0xb9, 0xc0, 0x33, 0xf6, 0x7a, 0xc0, 0x78, 0x2b, + 0x7e, 0x08, 0xd0, 0xb9, 0xe2, 0x19, 0x81, 0x43, 0x80, 0xeb, 0x97, 0xbb, 0x28, 0x1e, 0x4b, 0x85, 0xf8, 0x4b, 0x04, + 0x97, 0xe9, 0x62, 0xe8, 0x23, 0x96, 0x80, 0xc9, 0xb0, 0x32, 0x5d, 0x4c, 0x55, 0x0e, 0xd4, 0xf3, 0x35, 0x12, 0x8f, + 0x48, 0x31, 0xf7, 0x89, 0x70, 0xc0, 0x88, 0x25, 0x16, 0xed, 0x1d, 0x30, 0xe2, 0xa2, 0x91, 0x67, 0x98, 0x04, 0x80, + 0x1e, 0x1d, 0xd9, 0x0a, 0x15, 0x89, 0xdc, 0x8d, 0x28, 0x88, 0x8b, 0xc6, 0x17, 0x49, 0x6d, 0x73, 0x66, 0xe4, 0x60, + 0xe6, 0x4c, 0x90, 0x0c, 0x91, 0x61, 0xf8, 0x90, 0x3f, 0x47, 0xa5, 0x87, 0xd2, 0x3b, 0x11, 0x15, 0x7c, 0x1d, 0x69, + 0x12, 0xea, 0x02, 0x59, 0x4f, 0x44, 0x04, 0xd7, 0x91, 0x20, 0x01, 0xfa, 0x45, 0x06, 0x00, 0x2d, 0x0a, 0x3f, 0x01, + 0x26, 0xc8, 0xc5, 0x82, 0x7e, 0x48, 0x81, 0xd6, 0xbe, 0xb6, 0xb5, 0x3d, 0xd7, 0x8a, 0xcb, 0xbf, 0xfb, 0xf4, 0xf6, + 0x8d, 0x87, 0x22, 0xc7, 0x52, 0x42, 0x7a, 0x68, 0x86, 0xcc, 0x59, 0x8d, 0x8c, 0x57, 0xe6, 0xc5, 0xbe, 0x8e, 0x14, + 0xf6, 0x78, 0xf8, 0x10, 0xfb, 0x76, 0xaf, 0xa3, 0xf1, 0x75, 0xd4, 0xd7, 0xcd, 0x91, 0xba, 0x42, 0x7f, 0xfd, 0x79, + 0x69, 0x1a, 0x21, 0xdd, 0xbe, 0xcf, 0x6e, 0x54, 0xd9, 0x1f, 0xe1, 0x60, 0x08, 0x04, 0xa3, 0x10, 0x4a, 0x62, 0x16, + 0x12, 0x9f, 0x05, 0x0c, 0x5e, 0x47, 0x1c, 0xab, 0x11, 0x51, 0x6e, 0xbc, 0xb2, 0xf8, 0x1e, 0x4e, 0x81, 0x58, 0x72, + 0xb3, 0x06, 0xfb, 0xc1, 0xd1, 0x86, 0xf9, 0x2a, 0xfc, 0x56, 0x8e, 0xdb, 0xcf, 0x87, 0x4a, 0x3b, 0x88, 0x76, 0xd0, + 0x18, 0x37, 0x89, 0x32, 0x9c, 0x8a, 0x0f, 0x53, 0xa7, 0x2c, 0x21, 0x14, 0x4c, 0x8f, 0x28, 0x40, 0x23, 0x76, 0x41, + 0xcd, 0xca, 0x92, 0xb9, 0x2a, 0x6d, 0xa9, 0xc2, 0xcc, 0x10, 0x20, 0x41, 0x8e, 0x81, 0x9f, 0xfb, 0x3f, 0x66, 0xe4, + 0x59, 0x3e, 0x4a, 0xda, 0xc2, 0x0b, 0x91, 0x04, 0x4b, 0x4f, 0xbd, 0x35, 0x02, 0x51, 0xeb, 0x87, 0x06, 0xab, 0x2f, + 0xe2, 0xfa, 0x45, 0x21, 0x60, 0xa9, 0x48, 0x98, 0x50, 0x08, 0x3a, 0x3f, 0x35, 0x15, 0x13, 0x99, 0xff, 0x19, 0xe7, + 0x55, 0xb7, 0xd1, 0xf7, 0xe1, 0x5a, 0xf2, 0x65, 0x60, 0xd1, 0x71, 0x54, 0x92, 0xe0, 0x9a, 0x15, 0x0a, 0x52, 0x7b, + 0x1b, 0xdc, 0x41, 0x91, 0x2b, 0xdd, 0x1e, 0x79, 0xbf, 0x15, 0xc1, 0xd9, 0x3b, 0xf2, 0xed, 0x54, 0x8f, 0xc0, 0x39, + 0xfe, 0x08, 0x48, 0x37, 0x7b, 0x1e, 0x60, 0xa6, 0x15, 0x15, 0xa7, 0x17, 0xfb, 0x39, 0xf8, 0xf0, 0xec, 0x1d, 0xfa, + 0x51, 0xd2, 0xf3, 0x4f, 0xdf, 0xc2, 0x6d, 0x1b, 0x05, 0xe3, 0x4c, 0x7e, 0xa8, 0x6a, 0x60, 0xaa, 0x16, 0x5d, 0xa6, + 0xde, 0x8f, 0x83, 0x2a, 0xf9, 0x2e, 0xc8, 0x7a, 0x37, 0xab, 0x46, 0x92, 0x92, 0xd4, 0x3e, 0x1a, 0x10, 0x08, 0x04, + 0xc2, 0x94, 0x3d, 0x90, 0x19, 0x58, 0x66, 0x12, 0xfb, 0x66, 0x86, 0xc0, 0xd7, 0x8d, 0x94, 0x6a, 0x11, 0xc7, 0xa2, + 0x11, 0x4b, 0x0e, 0x94, 0x2d, 0x1b, 0x16, 0x7d, 0xa4, 0x02, 0xa5, 0xb0, 0x92, 0xef, 0x29, 0x06, 0x34, 0xf1, 0x06, + 0x42, 0xf6, 0xe0, 0xc5, 0xae, 0xae, 0x5e, 0xb1, 0xf8, 0x3e, 0x80, 0x1b, 0xa3, 0x2c, 0x2f, 0x7b, 0xf8, 0xd7, 0x12, + 0x06, 0xe0, 0x6e, 0x44, 0x24, 0x5f, 0x21, 0x2f, 0x3a, 0x1f, 0x31, 0x71, 0x89, 0x30, 0x37, 0x91, 0x28, 0xc7, 0xb3, + 0x2b, 0x4a, 0xc5, 0xad, 0xd6, 0x3e, 0x13, 0x1b, 0x05, 0x6a, 0xe5, 0xea, 0xf6, 0x78, 0x14, 0xfe, 0x53, 0x48, 0x2b, + 0x54, 0x86, 0x3d, 0x9d, 0x5f, 0xf8, 0x90, 0x86, 0x63, 0xb9, 0xd6, 0x39, 0x6c, 0x35, 0xfc, 0x11, 0x6a, 0x34, 0x57, + 0x0a, 0xfb, 0x94, 0x9c, 0x4f, 0xc7, 0xd6, 0xa2, 0x78, 0x5a, 0x18, 0x1c, 0xb8, 0x1a, 0x93, 0x33, 0x6a, 0x8f, 0xc9, + 0x39, 0xfa, 0xcc, 0xe1, 0xbe, 0xad, 0xe3, 0x7c, 0x16, 0xc0, 0x14, 0x30, 0x36, 0xa5, 0x65, 0x96, 0x52, 0xab, 0x46, + 0x01, 0x10, 0x95, 0x93, 0xcf, 0x64, 0xb5, 0x11, 0x5a, 0x48, 0x55, 0x8e, 0xa4, 0xe5, 0x1e, 0x07, 0x4d, 0xd5, 0xad, + 0x70, 0x01, 0xdc, 0x2c, 0xa0, 0x43, 0x0f, 0xa8, 0xd4, 0x5e, 0xe1, 0x2c, 0x3c, 0x0b, 0x20, 0x6c, 0x82, 0x20, 0x7d, + 0xee, 0xff, 0x12, 0x8b, 0xd8, 0xeb, 0xc0, 0x7b, 0xa2, 0x80, 0x49, 0x44, 0x51, 0xa6, 0x4e, 0x7d, 0xd8, 0x79, 0xa1, + 0x76, 0x04, 0x04, 0xa0, 0x5f, 0xfe, 0x03, 0xfb, 0x7e, 0x8e, 0xa3, 0xb0, 0x2b, 0xc1, 0x0b, 0x45, 0xf0, 0xc3, 0x50, + 0x1d, 0x39, 0x23, 0x90, 0xa1, 0xf6, 0x3e, 0x2b, 0xd5, 0x55, 0x7f, 0x3e, 0xc3, 0x58, 0xaf, 0xa1, 0xf4, 0x29, 0xb4, + 0x27, 0x8e, 0xcc, 0x95, 0xac, 0x94, 0x95, 0xea, 0x42, 0xc9, 0x71, 0xba, 0x33, 0xdf, 0x18, 0xe1, 0x68, 0x0e, 0x28, + 0x70, 0xd6, 0xe7, 0xda, 0x70, 0x28, 0xe5, 0xa3, 0x3f, 0x77, 0xdf, 0x8b, 0x14, 0xd6, 0xc6, 0x7a, 0xc0, 0x0c, 0x84, + 0xba, 0xaa, 0x65, 0x1e, 0x38, 0x01, 0xdc, 0x69, 0x15, 0xa1, 0x52, 0x27, 0xdf, 0x36, 0x95, 0xa8, 0x74, 0x24, 0xf1, + 0xa8, 0x4c, 0xf0, 0x66, 0x57, 0x25, 0x3b, 0x2b, 0xcb, 0x31, 0x0c, 0x77, 0x0d, 0x2d, 0xf6, 0xc4, 0xa9, 0x86, 0xb7, + 0xe8, 0x4c, 0x50, 0xd4, 0xc1, 0xdd, 0xc1, 0xa4, 0xa5, 0xa1, 0x8b, 0xc9, 0x28, 0xc1, 0x32, 0xd4, 0x4c, 0xaa, 0x26, + 0x32, 0x29, 0x54, 0xde, 0x1c, 0x11, 0x5a, 0xa2, 0xd0, 0x04, 0x68, 0xf6, 0x7a, 0xd5, 0xe5, 0xaa, 0x71, 0x77, 0xfc, + 0x12, 0x7d, 0x9c, 0xc6, 0x6d, 0x0d, 0xc9, 0x83, 0x0d, 0x27, 0x14, 0x56, 0xde, 0x13, 0xe1, 0x52, 0xd1, 0x98, 0xb8, + 0x4d, 0x8b, 0x8c, 0xf9, 0x91, 0x29, 0xb7, 0xb0, 0x08, 0x47, 0x5a, 0x5f, 0x37, 0xb1, 0x41, 0xe4, 0x27, 0x2c, 0x21, + 0x81, 0xde, 0xce, 0xfa, 0xd6, 0x54, 0xeb, 0x21, 0x10, 0xc7, 0x05, 0x45, 0x90, 0x4d, 0x4b, 0x3a, 0x27, 0xf8, 0x42, + 0xa0, 0x89, 0x12, 0x65, 0x33, 0xcd, 0x89, 0xb2, 0x22, 0xdb, 0xb0, 0x75, 0xe5, 0x25, 0x06, 0xe4, 0x34, 0x27, 0x4b, + 0x4e, 0x8e, 0xa7, 0x89, 0x32, 0xb1, 0x35, 0x63, 0x93, 0x9b, 0xa1, 0x21, 0x99, 0x25, 0x9f, 0x2c, 0x6f, 0xa2, 0x71, + 0x9a, 0x52, 0x39, 0x48, 0x17, 0x30, 0x4b, 0x28, 0xdf, 0xad, 0xb2, 0x72, 0x86, 0x56, 0x22, 0x2c, 0xb3, 0xbe, 0x9f, + 0x68, 0xbf, 0x56, 0x2f, 0x6b, 0x33, 0xc5, 0x32, 0x2a, 0xd9, 0x64, 0x2f, 0x24, 0x9f, 0x49, 0x24, 0xda, 0x68, 0x42, + 0x82, 0xb0, 0x96, 0xb6, 0x87, 0x75, 0x68, 0x80, 0x33, 0x35, 0x12, 0xc0, 0xb7, 0x9e, 0x65, 0xbc, 0x45, 0x62, 0xbe, + 0x9c, 0x8c, 0x4b, 0x0c, 0x08, 0x4b, 0xc4, 0x25, 0x45, 0x73, 0x5e, 0x03, 0x92, 0x1a, 0x7b, 0x5a, 0x33, 0xa3, 0x6a, + 0xe9, 0x88, 0x20, 0x27, 0xfa, 0x4f, 0x9e, 0xa7, 0x02, 0xd8, 0xc0, 0x5e, 0x23, 0x14, 0xc0, 0x7d, 0xc6, 0x0b, 0x7c, + 0x73, 0xf3, 0x00, 0xd0, 0x67, 0x9d, 0x6c, 0x08, 0x51, 0x5a, 0x21, 0xf3, 0x84, 0x60, 0x67, 0xaf, 0xe9, 0xbe, 0xd0, + 0x38, 0xdd, 0x10, 0x3d, 0x08, 0x2b, 0x03, 0x9c, 0xe8, 0xd3, 0x15, 0x2f, 0x01, 0x96, 0x01, 0xfd, 0x28, 0x9c, 0x9d, + 0xb8, 0x78, 0x5a, 0x3f, 0x97, 0x53, 0x35, 0x07, 0x91, 0x4b, 0xad, 0x6d, 0x79, 0x70, 0x78, 0xd5, 0x29, 0x2e, 0x7c, + 0x01, 0x13, 0xa5, 0xf5, 0x10, 0x5b, 0x72, 0x2c, 0xcb, 0xd1, 0x82, 0x4e, 0xcb, 0x58, 0x04, 0xba, 0x4f, 0x89, 0xa1, + 0xc7, 0xf8, 0xe0, 0xf6, 0xc2, 0xf1, 0xa6, 0x34, 0x6c, 0x7f, 0x01, 0x98, 0x7d, 0xbe, 0xae, 0xda, 0x5c, 0x26, 0xc8, + 0x53, 0xd0, 0x77, 0x6e, 0x82, 0x63, 0x01, 0xdb, 0xcc, 0x22, 0x8c, 0x6b, 0x6e, 0x34, 0x30, 0x69, 0x39, 0x82, 0x3a, + 0xdf, 0xa7, 0xb9, 0x88, 0xae, 0xdc, 0x0a, 0x77, 0xed, 0x7e, 0x41, 0xdb, 0x95, 0x2f, 0xd0, 0x36, 0x6a, 0x25, 0x4d, + 0xb3, 0x12, 0x58, 0x61, 0xb6, 0xb0, 0x26, 0x12, 0x72, 0x86, 0x3e, 0x98, 0xcd, 0xa1, 0x8e, 0x5e, 0xb6, 0x00, 0x61, + 0x73, 0x8a, 0x06, 0x8d, 0x30, 0x60, 0xd2, 0x60, 0x22, 0x49, 0x85, 0xa5, 0x5b, 0x3d, 0x0e, 0xde, 0xdc, 0x95, 0x47, + 0x06, 0x56, 0xde, 0x84, 0x14, 0x5e, 0x88, 0xe6, 0xc6, 0xa3, 0xbc, 0x62, 0x4a, 0x48, 0x21, 0x2b, 0x52, 0x1d, 0xa8, + 0x56, 0x85, 0x96, 0xaa, 0x06, 0x01, 0xb7, 0x8d, 0x2a, 0x6e, 0xe0, 0xa2, 0xe8, 0xc3, 0x93, 0xd4, 0x88, 0x06, 0xa3, + 0xcd, 0x35, 0x0a, 0x1c, 0x4c, 0xdd, 0x6d, 0xd4, 0xc5, 0xd3, 0x27, 0x64, 0x5b, 0x2d, 0xc0, 0x35, 0x00, 0x80, 0xd4, + 0x0e, 0x50, 0x03, 0x12, 0xb4, 0x69, 0xdd, 0xe8, 0xdf, 0x32, 0xdb, 0xb4, 0x0b, 0xa7, 0x69, 0x64, 0x4e, 0x8a, 0x19, + 0x69, 0x8d, 0xa1, 0x52, 0x82, 0x5a, 0xf8, 0x47, 0x13, 0xee, 0x3c, 0x2d, 0x8a, 0xf2, 0xe6, 0xa6, 0x25, 0xd0, 0x51, + 0xce, 0xcd, 0x0d, 0xb5, 0x85, 0xbe, 0xec, 0x6f, 0x97, 0x6b, 0x42, 0xa0, 0x3f, 0x5f, 0xde, 0x19, 0x02, 0xfd, 0x45, + 0x7c, 0x9f, 0x10, 0xe8, 0xcf, 0x97, 0x7f, 0x76, 0x08, 0xf4, 0xb7, 0x4b, 0x23, 0x04, 0x3a, 0x2f, 0xc6, 0x5f, 0x32, + 0xdf, 0x7a, 0xff, 0xce, 0x72, 0x5f, 0xa4, 0xf0, 0xf7, 0xd5, 0x2b, 0x43, 0x98, 0xfe, 0x5d, 0x22, 0x22, 0xf9, 0x4b, + 0x59, 0x30, 0xc5, 0x6d, 0xc1, 0x57, 0xa4, 0x75, 0x32, 0x03, 0x15, 0xc5, 0x18, 0x88, 0x3e, 0xff, 0x39, 0x2e, 0x66, + 0xb6, 0xb5, 0x69, 0x39, 0x63, 0x1d, 0x12, 0xb4, 0x37, 0xac, 0x70, 0x6f, 0x3f, 0x26, 0x15, 0xa1, 0x8e, 0xca, 0x3c, + 0x80, 0x5f, 0x19, 0x22, 0x7b, 0x93, 0x43, 0xa4, 0x4f, 0xc6, 0x2a, 0xe8, 0x28, 0x54, 0x44, 0x8f, 0xa5, 0xd8, 0x88, + 0xc0, 0x79, 0x68, 0xa6, 0x84, 0xfe, 0xb0, 0x34, 0x6c, 0x8b, 0xe0, 0xdb, 0x02, 0x7d, 0xee, 0x00, 0xa1, 0x1c, 0xc7, + 0xa1, 0xa3, 0xe5, 0xa1, 0x51, 0x32, 0x81, 0x43, 0xfe, 0xe3, 0xc7, 0xd7, 0x2a, 0xf2, 0xb8, 0xcd, 0xa1, 0x97, 0x1c, + 0x4a, 0x69, 0x1c, 0x46, 0x17, 0x30, 0xfc, 0xf1, 0xc9, 0x83, 0x55, 0x6b, 0x45, 0x7e, 0xed, 0x94, 0x9b, 0x27, 0x40, + 0xc3, 0x89, 0x35, 0x80, 0xba, 0x71, 0xb9, 0xf9, 0x60, 0x05, 0x6f, 0x53, 0x0c, 0xef, 0x8d, 0xcf, 0x69, 0xf9, 0x60, + 0x95, 0xe3, 0x43, 0x54, 0x9e, 0x18, 0x81, 0xd9, 0xd4, 0x80, 0x8c, 0x39, 0x28, 0xe5, 0x95, 0x8e, 0x09, 0x7a, 0x4b, + 0xc3, 0x89, 0x6c, 0x54, 0xc7, 0xa3, 0x5a, 0x1a, 0x72, 0xbf, 0x91, 0x98, 0xba, 0xe3, 0x20, 0x1b, 0xa9, 0x17, 0x8e, + 0x42, 0x65, 0x25, 0xfe, 0x7e, 0xcb, 0xa4, 0x12, 0x47, 0xf6, 0x0c, 0xf5, 0x46, 0x86, 0x4f, 0x59, 0xa2, 0x5b, 0xe8, + 0xa1, 0x05, 0x8a, 0x9f, 0x60, 0x08, 0x54, 0xb2, 0x46, 0x6a, 0x8e, 0x38, 0xf2, 0x4f, 0xe4, 0x8c, 0x9b, 0x5d, 0xa4, + 0x0e, 0xc5, 0xc8, 0x36, 0xe7, 0x14, 0x95, 0xe3, 0x30, 0x2a, 0x80, 0x00, 0xf0, 0x81, 0x5c, 0x3d, 0x21, 0x59, 0xc4, + 0xb7, 0xf7, 0x0a, 0xba, 0x0f, 0xec, 0x64, 0x90, 0x9d, 0x11, 0xeb, 0x2f, 0xc1, 0x2d, 0x85, 0xc5, 0x8e, 0xa3, 0x5c, + 0x25, 0x56, 0x99, 0x05, 0xf9, 0xf1, 0x84, 0x33, 0x1a, 0xe5, 0x0a, 0x60, 0xe7, 0xb3, 0xf4, 0xf2, 0x18, 0xb3, 0x3b, + 0x28, 0x08, 0x1e, 0xd0, 0x02, 0x32, 0xdf, 0x24, 0xd3, 0x2e, 0x2f, 0xc5, 0xbb, 0x53, 0xe0, 0x2a, 0x3f, 0xc0, 0x59, + 0xf7, 0xf1, 0x2e, 0x08, 0xa8, 0x9e, 0xa5, 0xcb, 0x85, 0xee, 0xe4, 0x78, 0x99, 0x7c, 0x4e, 0xd2, 0xcb, 0x84, 0x61, + 0xef, 0x71, 0x74, 0x81, 0x23, 0xf2, 0x57, 0xa4, 0xb3, 0x4a, 0x00, 0x06, 0x18, 0x94, 0x38, 0xe7, 0x02, 0x80, 0x5d, + 0x12, 0xd9, 0x00, 0x5a, 0x6a, 0xb8, 0xb6, 0xba, 0x6b, 0xc2, 0xb1, 0x5c, 0xaa, 0x2c, 0x62, 0xb4, 0x84, 0x7d, 0x89, + 0xad, 0x63, 0xc4, 0x76, 0x4c, 0x17, 0x82, 0xac, 0x27, 0xb1, 0x46, 0x95, 0x88, 0x3d, 0xb0, 0x41, 0x06, 0x8d, 0xcc, + 0xa4, 0x96, 0x8c, 0x76, 0x59, 0x59, 0x26, 0x62, 0xaf, 0xc9, 0x65, 0x18, 0x15, 0xff, 0x99, 0x3e, 0x94, 0x14, 0x17, + 0x09, 0x2e, 0x0b, 0x19, 0x9d, 0x6d, 0x90, 0x2c, 0x8c, 0x7e, 0x1b, 0x3a, 0x4a, 0x91, 0x56, 0xce, 0x30, 0xcb, 0x5b, + 0xb1, 0x26, 0xfe, 0x10, 0x4d, 0xc2, 0xcc, 0x5e, 0x00, 0x38, 0x71, 0xdd, 0x63, 0xa8, 0x19, 0x65, 0xf1, 0xe4, 0x18, + 0xde, 0xc2, 0x46, 0x5e, 0x1f, 0xc9, 0x70, 0x17, 0xa2, 0xad, 0xda, 0x26, 0xae, 0xfd, 0x0e, 0x7d, 0xde, 0x39, 0x81, + 0x49, 0x6f, 0x17, 0xd8, 0x1e, 0x61, 0x2d, 0x8f, 0x03, 0x74, 0xd5, 0x33, 0xdf, 0x13, 0xfd, 0x5b, 0x4d, 0xcd, 0xad, + 0xfa, 0x39, 0xd4, 0x7b, 0x74, 0xe5, 0x51, 0xca, 0x22, 0xa8, 0x2f, 0xb3, 0x1d, 0xd8, 0x3a, 0x92, 0x37, 0x00, 0x59, + 0x46, 0x46, 0x02, 0x44, 0x73, 0x24, 0x28, 0x86, 0xbe, 0xb5, 0x17, 0x3c, 0x06, 0xc7, 0x61, 0x6e, 0x11, 0xb6, 0x8e, + 0x82, 0xb6, 0xa3, 0x94, 0x84, 0xee, 0x96, 0xca, 0xf9, 0xd5, 0x76, 0x7c, 0x0e, 0x71, 0x3a, 0x47, 0xe3, 0xbb, 0x2a, + 0x74, 0xbb, 0xde, 0x5d, 0x55, 0xfc, 0xe1, 0x2d, 0xa7, 0x94, 0xab, 0xec, 0x49, 0xdb, 0xcb, 0x11, 0x99, 0xb2, 0xd8, + 0x00, 0x48, 0xaa, 0x67, 0xdf, 0xa5, 0xdd, 0x77, 0x57, 0xe7, 0x51, 0x31, 0x4b, 0x43, 0xcf, 0xfa, 0xf6, 0xe5, 0x27, + 0x4b, 0xaa, 0xae, 0x33, 0x11, 0xb4, 0x48, 0x68, 0x73, 0xe6, 0xe9, 0x19, 0xca, 0x32, 0x37, 0xb2, 0x7e, 0xfa, 0xb9, + 0x91, 0xe5, 0xf3, 0xe4, 0xbb, 0x4f, 0x9f, 0x3e, 0x74, 0x48, 0xe1, 0xb3, 0xd1, 0x39, 0xe0, 0xe8, 0x01, 0x9d, 0x07, + 0xab, 0x4c, 0xa8, 0xd8, 0xcb, 0x13, 0x85, 0xab, 0xb2, 0x9a, 0x82, 0x5c, 0x06, 0x05, 0x34, 0xba, 0xa8, 0x2d, 0x6b, + 0xa6, 0xb5, 0xd8, 0x66, 0x65, 0xd6, 0x2e, 0x59, 0xa4, 0x3b, 0xe1, 0x9e, 0x3d, 0xd6, 0xcb, 0x63, 0xa6, 0x05, 0xbb, + 0x58, 0x73, 0xd5, 0x8a, 0xb6, 0xab, 0x96, 0x66, 0x05, 0xf0, 0x26, 0xc7, 0x74, 0xfb, 0xef, 0x75, 0xe5, 0xe4, 0x0e, + 0x33, 0xbc, 0xa8, 0xdf, 0xb6, 0x4a, 0xa8, 0x32, 0x61, 0x94, 0x2b, 0xee, 0x10, 0x0a, 0xcc, 0x00, 0x3b, 0x9b, 0x1f, + 0x4b, 0x8b, 0x11, 0xb3, 0x8c, 0x03, 0xb9, 0x21, 0x0d, 0xe4, 0xef, 0x07, 0x7d, 0x39, 0xbe, 0x4b, 0x92, 0x9b, 0xec, + 0x4d, 0x6a, 0x05, 0xe3, 0xde, 0xd0, 0x1b, 0x3a, 0x2a, 0xbf, 0x84, 0x80, 0x61, 0xdf, 0xf6, 0x5f, 0xbe, 0xfb, 0xf4, + 0xfa, 0xd3, 0xdf, 0x8e, 0x9f, 0x3f, 0xfb, 0xf4, 0xf2, 0xdb, 0xf7, 0x1f, 0x5f, 0xbf, 0x3c, 0x20, 0x0c, 0x21, 0x02, + 0x56, 0xda, 0x2b, 0x61, 0x15, 0x5d, 0x6d, 0xcb, 0x4b, 0x4a, 0xa7, 0x3a, 0x14, 0x8e, 0x18, 0x45, 0x95, 0x55, 0x93, + 0x3f, 0xbe, 0x7b, 0xf1, 0xf2, 0xd5, 0xeb, 0x77, 0x2f, 0x5f, 0xd4, 0xbf, 0xee, 0x0d, 0x61, 0xf9, 0xf5, 0xce, 0x89, + 0x0c, 0x29, 0x91, 0x5a, 0xaf, 0x16, 0xf8, 0x44, 0x5a, 0x79, 0x13, 0x3e, 0xc5, 0x78, 0x22, 0x7d, 0x96, 0xd3, 0xd3, + 0xb3, 0x11, 0xff, 0x97, 0x2f, 0x1e, 0x50, 0xa6, 0x4b, 0x9b, 0x5e, 0x09, 0x59, 0x3f, 0xae, 0x6a, 0xec, 0xf2, 0x4b, + 0x2f, 0x71, 0x55, 0x6b, 0x68, 0x03, 0x1d, 0xba, 0x9c, 0x52, 0xe1, 0x18, 0x4e, 0x50, 0x74, 0x06, 0x40, 0xc6, 0x8b, + 0xfb, 0xb5, 0x12, 0xb7, 0x72, 0x00, 0x3c, 0x1b, 0x05, 0xcb, 0x95, 0x22, 0xe0, 0x69, 0x08, 0x00, 0x18, 0x4b, 0xa0, + 0x57, 0xf5, 0x50, 0x67, 0x0b, 0xa8, 0x37, 0xec, 0x1c, 0xdd, 0xdc, 0xb4, 0x2c, 0x5a, 0x2b, 0xec, 0xf3, 0x0e, 0x63, + 0x06, 0x8a, 0x47, 0x48, 0x98, 0x23, 0x7a, 0x63, 0xdc, 0xe5, 0x4b, 0x74, 0xf7, 0x4c, 0x64, 0xfd, 0x44, 0x6b, 0x44, + 0xfd, 0x5a, 0x26, 0x96, 0xa9, 0xe2, 0xc3, 0x41, 0x0d, 0xe2, 0xca, 0x18, 0xbf, 0xb5, 0x52, 0x3e, 0x66, 0x22, 0xb6, + 0x22, 0xee, 0x14, 0xa6, 0xe3, 0x92, 0xa2, 0x5b, 0x7b, 0x8e, 0x16, 0x32, 0x95, 0xfd, 0x95, 0xeb, 0x30, 0xf7, 0x52, + 0x19, 0x26, 0x0f, 0x4d, 0x06, 0xd7, 0xd4, 0x9a, 0x79, 0x59, 0x25, 0xe0, 0x65, 0x5a, 0x5d, 0xd4, 0xbd, 0xac, 0xfa, + 0x1b, 0x8f, 0x71, 0xad, 0x0a, 0x89, 0x6c, 0xab, 0x95, 0x50, 0xb4, 0x30, 0x19, 0x73, 0xf7, 0xfd, 0x22, 0x7d, 0x93, + 0x5e, 0x4a, 0xf1, 0xf0, 0x5e, 0xd6, 0x52, 0x48, 0x77, 0xc3, 0x0b, 0xf6, 0x26, 0xfc, 0x30, 0x2c, 0xd7, 0x20, 0x81, + 0x52, 0x2f, 0xb0, 0x22, 0x4e, 0x4f, 0x98, 0x65, 0x3a, 0x06, 0x72, 0x46, 0x52, 0x67, 0x27, 0xb1, 0x4a, 0xfe, 0x5a, + 0x21, 0x2c, 0x4a, 0xb1, 0xf4, 0x66, 0xac, 0xd1, 0x96, 0x6a, 0xe2, 0x7c, 0xf8, 0x71, 0x2b, 0x75, 0xd2, 0xe7, 0x9f, + 0x11, 0xe7, 0xc2, 0x6c, 0xaf, 0x12, 0x5d, 0x45, 0x13, 0xbb, 0x6d, 0x60, 0x2c, 0x5e, 0x92, 0x53, 0xa0, 0xf0, 0xcb, + 0x04, 0xf1, 0x3f, 0x34, 0x20, 0x3e, 0x59, 0xd9, 0x29, 0x80, 0xff, 0xe1, 0xfd, 0xc1, 0x27, 0xd4, 0x5e, 0x05, 0xa4, + 0x6e, 0x5e, 0x59, 0xc2, 0x52, 0xa5, 0x87, 0xfa, 0x20, 0xcb, 0xb3, 0x82, 0x05, 0xe2, 0x63, 0xe2, 0x0b, 0x36, 0xaf, + 0x7a, 0x97, 0x97, 0x97, 0x3d, 0xb4, 0x5b, 0xed, 0x2d, 0xb3, 0x39, 0xd3, 0x80, 0xa1, 0x55, 0x4a, 0x40, 0x1e, 0xd5, + 0x00, 0x39, 0x06, 0xbd, 0x15, 0x59, 0x53, 0x0e, 0x80, 0x2e, 0x7b, 0x36, 0x9f, 0x9b, 0xc2, 0x19, 0x49, 0xaa, 0x09, + 0x79, 0x45, 0x05, 0x30, 0xd8, 0xa8, 0x63, 0xea, 0xc7, 0xf9, 0xb1, 0xb0, 0x0a, 0x08, 0x8f, 0x4f, 0xaf, 0x8f, 0x85, + 0xe6, 0x41, 0x45, 0x1d, 0x7e, 0x7e, 0xb2, 0x17, 0xc6, 0x17, 0x1d, 0xa2, 0x27, 0x7d, 0x0b, 0x5d, 0xb6, 0xe6, 0x11, + 0xf0, 0x87, 0x45, 0x9a, 0xf4, 0x00, 0x35, 0x59, 0xfb, 0x7b, 0xfc, 0x43, 0x56, 0x08, 0xf8, 0xa7, 0xd5, 0xf9, 0xcf, + 0x09, 0x4c, 0xe8, 0xb3, 0x6f, 0xc1, 0xe2, 0xf9, 0xfb, 0x35, 0xaa, 0x71, 0x50, 0x5a, 0xfb, 0x38, 0xd6, 0x0e, 0x0c, + 0x76, 0x6f, 0x93, 0xbf, 0xd8, 0xdf, 0xdb, 0x84, 0x7e, 0xf6, 0x8d, 0x04, 0x30, 0x42, 0x3b, 0xea, 0x8b, 0x40, 0x9b, + 0xca, 0x9e, 0x2c, 0xa7, 0xc8, 0x0d, 0x40, 0xbc, 0x68, 0x16, 0x17, 0x23, 0xca, 0xf0, 0x78, 0x81, 0xf9, 0x47, 0xa1, + 0xf9, 0x1c, 0x19, 0xba, 0x9b, 0x1b, 0x5b, 0x59, 0x9b, 0xce, 0x8c, 0x50, 0x6c, 0xa4, 0xcc, 0xa3, 0x2a, 0x2e, 0xc5, + 0x93, 0x71, 0x6c, 0x19, 0x30, 0x6e, 0xee, 0xb8, 0x93, 0xd2, 0xa5, 0x3c, 0x3a, 0xc1, 0x02, 0xf5, 0x8a, 0xe2, 0xd1, + 0xe0, 0x7b, 0x27, 0x98, 0x3b, 0xdb, 0x00, 0xdc, 0x8e, 0xa1, 0x59, 0xe1, 0x5b, 0x28, 0xa2, 0x01, 0xa2, 0x4a, 0x84, + 0xfa, 0x61, 0x6d, 0x07, 0x94, 0x60, 0xee, 0x36, 0x15, 0x82, 0x27, 0x28, 0x65, 0xb6, 0x34, 0xb9, 0x2e, 0xe3, 0xca, + 0x0e, 0x79, 0xf5, 0xfd, 0x92, 0xb1, 0x21, 0x37, 0xf2, 0x75, 0x5b, 0x86, 0x9a, 0x3a, 0x60, 0x4d, 0x6b, 0x78, 0x16, + 0x7d, 0xf7, 0x0c, 0xf5, 0x50, 0xe4, 0xda, 0x87, 0xb0, 0xa0, 0x47, 0x1a, 0x37, 0xe5, 0x0c, 0x28, 0xbd, 0xb4, 0xd4, + 0x61, 0x9a, 0x79, 0xd7, 0xf7, 0x81, 0x49, 0x22, 0x64, 0x06, 0xdd, 0x56, 0xcf, 0x41, 0x11, 0x9c, 0xf6, 0xf8, 0x30, + 0xc3, 0x4e, 0x87, 0xa7, 0x73, 0xb5, 0xd9, 0x7c, 0x09, 0x66, 0x41, 0x12, 0xce, 0xa3, 0x4f, 0xc1, 0xe9, 0x77, 0x54, + 0xe7, 0xc5, 0xe9, 0xfc, 0x39, 0x56, 0x80, 0x6d, 0x07, 0xce, 0x86, 0x96, 0xa9, 0x0d, 0x60, 0x97, 0x7c, 0x04, 0xea, + 0xfd, 0x4c, 0x38, 0xb1, 0x12, 0x74, 0x45, 0x5f, 0xd3, 0x60, 0x19, 0xc5, 0x32, 0x44, 0xad, 0x8e, 0x4c, 0x24, 0xf6, + 0xc1, 0xb3, 0xd9, 0x11, 0xb7, 0x16, 0xc7, 0x95, 0xca, 0x1b, 0x6c, 0x9e, 0x4c, 0x73, 0xb0, 0x8c, 0x4a, 0x3f, 0xa6, + 0x97, 0x72, 0xa4, 0x62, 0x01, 0x38, 0x10, 0xe5, 0x18, 0x3a, 0x31, 0x95, 0x3f, 0x24, 0x21, 0xe7, 0x76, 0xf1, 0x09, + 0x5a, 0xd5, 0x69, 0x9e, 0x16, 0x57, 0xf0, 0xf1, 0xa6, 0x59, 0x7b, 0xff, 0xc4, 0x7b, 0x63, 0x4c, 0x8e, 0x5a, 0x95, + 0xdc, 0xf1, 0xa1, 0xfe, 0x51, 0x1e, 0x75, 0x90, 0x17, 0x2e, 0xb1, 0x04, 0xd7, 0xa8, 0xfa, 0x49, 0x03, 0xfd, 0x60, + 0x62, 0x44, 0x8d, 0xa0, 0xf8, 0xf4, 0x48, 0xf8, 0x98, 0x3a, 0x9e, 0xda, 0x42, 0xb6, 0xbf, 0x94, 0xad, 0x9d, 0x88, + 0xbf, 0x7a, 0x49, 0x48, 0x9e, 0x1d, 0x01, 0x40, 0xc9, 0x2c, 0x9c, 0x66, 0x3d, 0x3b, 0x52, 0xc7, 0xc8, 0xca, 0x46, + 0x13, 0x6e, 0x45, 0xab, 0xb8, 0x60, 0x9b, 0xf5, 0x4f, 0x8d, 0x79, 0x03, 0xe0, 0x54, 0x0f, 0x1d, 0x31, 0x99, 0x1a, + 0xd0, 0x52, 0x03, 0x5c, 0x9f, 0x75, 0xea, 0xf0, 0x7d, 0xec, 0xfe, 0x9c, 0xba, 0xe7, 0x81, 0x7b, 0x1a, 0xb8, 0x07, + 0xc9, 0x51, 0xd9, 0xba, 0x79, 0x2a, 0x63, 0x9c, 0x1b, 0x8d, 0x6c, 0x8c, 0xb3, 0x54, 0xe5, 0x2b, 0xe2, 0xbe, 0xb0, + 0x0c, 0xf9, 0x04, 0xdc, 0x6f, 0x24, 0x13, 0xb5, 0xc9, 0xb7, 0x52, 0x42, 0xe0, 0x18, 0xcb, 0x82, 0x41, 0xc8, 0x36, + 0x84, 0x01, 0x1d, 0x7c, 0x5d, 0x64, 0xf3, 0xbf, 0x44, 0xd7, 0xc8, 0x4e, 0xc2, 0xd4, 0x17, 0x28, 0x99, 0x0a, 0xce, + 0x84, 0xa6, 0xc1, 0x45, 0xa2, 0xe6, 0x3e, 0xdd, 0xdd, 0xdc, 0x44, 0x46, 0xee, 0xb0, 0x22, 0x3d, 0x03, 0xb0, 0x6a, + 0x1b, 0x39, 0xc6, 0x54, 0x37, 0xe3, 0x8d, 0x81, 0x8c, 0x4f, 0xed, 0x94, 0xeb, 0x2e, 0x96, 0xa6, 0x00, 0xa5, 0x4e, + 0x1f, 0x01, 0x17, 0x9b, 0x50, 0x11, 0x11, 0x6e, 0xcb, 0x7b, 0xa1, 0x2f, 0x6e, 0x2f, 0x4c, 0x97, 0x00, 0x41, 0x7a, + 0x74, 0x1b, 0xb0, 0xcb, 0xd5, 0xe9, 0xf2, 0xf4, 0x74, 0xce, 0x49, 0xc1, 0x30, 0xca, 0x5a, 0x9a, 0x93, 0xf4, 0xb3, + 0x74, 0x46, 0x44, 0xa9, 0x15, 0xf5, 0xe1, 0xa3, 0x65, 0x24, 0x72, 0x0b, 0xdc, 0x41, 0x81, 0x92, 0xce, 0xe6, 0x9d, + 0xf6, 0x2d, 0xe4, 0x52, 0xa2, 0xdc, 0x1a, 0xb5, 0x90, 0x74, 0xfe, 0xa1, 0x75, 0x40, 0x2b, 0xdc, 0x81, 0x69, 0x75, + 0x9e, 0xf3, 0xd9, 0xb5, 0x5c, 0x8b, 0x0d, 0xbc, 0x44, 0x0e, 0x39, 0xf8, 0xfd, 0x22, 0x0e, 0xce, 0x92, 0x34, 0x87, + 0x43, 0x61, 0x1d, 0x8d, 0x5e, 0xc4, 0xf6, 0xe1, 0x79, 0x61, 0x3b, 0x47, 0xee, 0xb7, 0x66, 0xb6, 0x2f, 0x09, 0x29, + 0x29, 0xd9, 0xd7, 0x9a, 0x3a, 0xe6, 0xed, 0xb9, 0xad, 0x9e, 0x84, 0xc8, 0x54, 0xe7, 0x5b, 0x1f, 0x6b, 0xd5, 0xf2, + 0x86, 0x51, 0x42, 0x48, 0xcc, 0x1b, 0xf6, 0xad, 0x33, 0x62, 0x51, 0xcb, 0xb3, 0xe5, 0x8a, 0x88, 0x86, 0x42, 0x23, + 0x5f, 0x0a, 0x75, 0x2f, 0xfd, 0x43, 0xf9, 0xf7, 0x86, 0xe9, 0xdb, 0x50, 0x41, 0xe3, 0x27, 0xcf, 0xaa, 0x14, 0x08, + 0xdc, 0x91, 0x12, 0x0d, 0x0b, 0x93, 0x14, 0x70, 0x0e, 0x8c, 0x05, 0x3b, 0x3c, 0xa9, 0xdb, 0x42, 0x8b, 0x56, 0xe1, + 0xee, 0x08, 0x28, 0xf0, 0x0d, 0xe1, 0x52, 0x92, 0x4f, 0x62, 0xb8, 0x09, 0x0c, 0x45, 0x5a, 0x89, 0xd6, 0x24, 0x3c, + 0xf0, 0x70, 0xfb, 0x2a, 0xf4, 0x9b, 0x01, 0xf7, 0xab, 0xf8, 0x1c, 0x20, 0xee, 0x58, 0x22, 0xf5, 0xd7, 0x39, 0xed, + 0x25, 0x92, 0x2b, 0x02, 0xcb, 0x03, 0xdc, 0x1b, 0x4b, 0x4c, 0x44, 0x75, 0x2b, 0xe0, 0xd5, 0x96, 0xe4, 0x6e, 0x26, + 0x23, 0xe3, 0x8b, 0xf4, 0xe3, 0x92, 0x00, 0xab, 0x5d, 0x3d, 0x0c, 0xc9, 0xa4, 0x68, 0xab, 0x02, 0xed, 0xaa, 0x09, + 0x61, 0x20, 0xe4, 0x12, 0x34, 0xc2, 0x49, 0x79, 0x8c, 0x84, 0x24, 0xc6, 0x29, 0x93, 0x73, 0x84, 0x6a, 0x46, 0xaa, + 0xb8, 0x38, 0x59, 0x2c, 0x0b, 0x8a, 0x3f, 0x8f, 0x03, 0x88, 0x60, 0x38, 0xc4, 0x24, 0x22, 0xac, 0xd7, 0xcc, 0x0f, + 0x94, 0xc6, 0x61, 0xb3, 0x4c, 0xc8, 0x63, 0x10, 0xc6, 0xd1, 0x34, 0x48, 0x7b, 0x83, 0x3f, 0x33, 0x31, 0x0d, 0x20, + 0x33, 0x34, 0xd5, 0x3e, 0x21, 0x2b, 0x87, 0x16, 0x00, 0x32, 0xe1, 0x76, 0x46, 0xf6, 0xbc, 0x75, 0xb2, 0x18, 0x93, + 0xba, 0x32, 0xcd, 0x13, 0x94, 0x44, 0x8e, 0x71, 0xed, 0xfc, 0x07, 0xab, 0x40, 0x19, 0xd0, 0x59, 0x40, 0x2e, 0x92, + 0xf5, 0xdc, 0x09, 0x2d, 0x03, 0xcc, 0x5c, 0xbb, 0xb3, 0xea, 0xf9, 0xe2, 0x91, 0xe4, 0xf2, 0x0e, 0xd9, 0xb3, 0xf9, + 0xc2, 0x6a, 0x6d, 0x91, 0xc5, 0xe7, 0x41, 0x76, 0xcd, 0x46, 0x6e, 0xae, 0x69, 0x09, 0xe7, 0xc0, 0x44, 0x59, 0xc5, + 0x41, 0x0b, 0xc0, 0xa8, 0x01, 0xa6, 0xab, 0xca, 0x22, 0x31, 0x5b, 0x65, 0xe9, 0x83, 0x7d, 0x1d, 0x5b, 0x5d, 0xb8, + 0xf0, 0x24, 0x65, 0xe4, 0x4f, 0x46, 0x76, 0xbe, 0x66, 0x7a, 0x79, 0x75, 0x7a, 0x49, 0xf5, 0xa0, 0xd1, 0x64, 0x18, + 0x53, 0xf0, 0xb8, 0x69, 0x66, 0x94, 0xea, 0xb2, 0x7d, 0x47, 0xf9, 0xdd, 0xbf, 0x75, 0x3b, 0x22, 0xdc, 0x8e, 0x04, + 0xb7, 0xa3, 0x45, 0x00, 0x1b, 0xc8, 0x1d, 0x41, 0x5a, 0x04, 0xa9, 0x90, 0x8c, 0x28, 0x10, 0xce, 0x82, 0xd9, 0x51, + 0x47, 0x28, 0xc3, 0xab, 0xc1, 0x63, 0xe7, 0xab, 0x91, 0xf9, 0x7e, 0x4a, 0x5f, 0x65, 0x70, 0x9c, 0xb9, 0x36, 0x23, + 0x45, 0xae, 0x84, 0xcb, 0x90, 0xe1, 0x0c, 0xf5, 0x2a, 0x60, 0x1a, 0x70, 0x7f, 0xa8, 0x19, 0x9d, 0xfe, 0x39, 0x29, + 0x9f, 0x87, 0xe3, 0x2a, 0xc1, 0x43, 0x5f, 0xc1, 0x9a, 0x52, 0x62, 0x4f, 0x44, 0xeb, 0x18, 0xfa, 0x6a, 0x6f, 0x93, + 0x7f, 0x76, 0x6a, 0x37, 0x42, 0x37, 0x22, 0xa5, 0x8e, 0x9e, 0x68, 0xe0, 0x77, 0x5d, 0x95, 0xbc, 0x88, 0x16, 0x58, + 0x1a, 0xc0, 0xf3, 0xb9, 0x20, 0xb0, 0x44, 0x8c, 0x3d, 0x0c, 0xc0, 0x33, 0x40, 0xc7, 0x07, 0x78, 0x13, 0x5c, 0xd1, + 0xcc, 0xe5, 0x9b, 0xe0, 0xca, 0x1e, 0x8a, 0x57, 0xfa, 0xae, 0xe5, 0xd5, 0xbb, 0x36, 0x11, 0x9b, 0x8b, 0xde, 0x75, + 0x92, 0xb0, 0x06, 0xde, 0x77, 0xd2, 0xbe, 0xb9, 0x33, 0xb9, 0xb9, 0xe1, 0x9a, 0xcd, 0x0d, 0x6f, 0xd9, 0xdc, 0xb9, + 0xd8, 0xc8, 0x8e, 0x5a, 0xba, 0x8c, 0x3c, 0xa6, 0xd5, 0xe2, 0x09, 0x7a, 0xc4, 0x13, 0xf7, 0x8c, 0xd6, 0xa9, 0x97, + 0xcf, 0xd1, 0x62, 0x78, 0xcd, 0x5a, 0xb5, 0xad, 0x8b, 0xb1, 0x10, 0xcd, 0x89, 0xab, 0x5b, 0x37, 0x31, 0x24, 0x03, + 0xf6, 0xbc, 0x3e, 0x5f, 0x3c, 0xa5, 0xf4, 0xa1, 0x6b, 0xcf, 0xd6, 0xcc, 0x74, 0x76, 0xcb, 0x4c, 0x27, 0x95, 0xab, + 0x2b, 0xa6, 0xcd, 0x97, 0xd0, 0x9c, 0x14, 0x9e, 0x41, 0xf4, 0xa2, 0xa0, 0x23, 0x53, 0x3d, 0x87, 0xeb, 0x61, 0xac, + 0x71, 0xa2, 0x16, 0x70, 0x1e, 0x2f, 0xd3, 0x0c, 0xcd, 0x10, 0xb0, 0x99, 0xdf, 0x77, 0xa4, 0x60, 0xb9, 0x44, 0x84, + 0xb3, 0xb5, 0x87, 0x49, 0xbf, 0x37, 0x8f, 0xd4, 0xd6, 0xee, 0x2e, 0xd7, 0x00, 0x62, 0x04, 0x58, 0x24, 0x5a, 0xf4, + 0x00, 0x53, 0x61, 0xfc, 0x7f, 0x72, 0xcc, 0x5a, 0x60, 0xc8, 0xdc, 0x80, 0xea, 0x04, 0xa1, 0x17, 0x48, 0x82, 0xb1, + 0xde, 0x31, 0x71, 0x56, 0x46, 0xb4, 0xd4, 0x4c, 0x2d, 0xfc, 0x3b, 0xba, 0xae, 0x50, 0xa0, 0xdd, 0xbc, 0x86, 0x8f, + 0x81, 0x6d, 0x0d, 0xc2, 0x03, 0xb4, 0x76, 0xb1, 0xb7, 0x5c, 0xf4, 0x5c, 0x31, 0x63, 0xa3, 0x66, 0x4c, 0x13, 0x4e, + 0x34, 0x90, 0x24, 0x28, 0x29, 0xec, 0x82, 0x31, 0xa4, 0x40, 0xd0, 0x9b, 0x1e, 0xad, 0xb6, 0xca, 0xcd, 0xb3, 0xd8, + 0x69, 0x40, 0x4d, 0x04, 0x6d, 0x73, 0x7f, 0x5f, 0x09, 0xdd, 0xe6, 0x2e, 0x74, 0x87, 0xea, 0xd0, 0x43, 0x4c, 0x7a, + 0x3e, 0x90, 0xcc, 0xf4, 0x49, 0x86, 0x58, 0x0b, 0x95, 0x87, 0x0f, 0xcf, 0xe8, 0x69, 0x08, 0x4f, 0xa7, 0xf4, 0xb4, + 0x75, 0xa4, 0x54, 0x55, 0x35, 0x29, 0x82, 0x31, 0x67, 0x38, 0x87, 0xe6, 0x79, 0x62, 0xa3, 0xec, 0xdf, 0x71, 0x6c, + 0xc4, 0x06, 0x7f, 0x01, 0x3b, 0x8c, 0x61, 0x08, 0xcc, 0x39, 0x24, 0xfd, 0xcc, 0x29, 0x5b, 0xcb, 0xcf, 0xd6, 0x94, + 0x9f, 0x3a, 0xff, 0x66, 0xc4, 0x4f, 0xa7, 0x24, 0xd5, 0x38, 0xa5, 0x2a, 0x03, 0x39, 0x3e, 0x8d, 0x13, 0x40, 0xe2, + 0xc7, 0xcc, 0x85, 0xd8, 0x86, 0x90, 0x77, 0x43, 0x0b, 0x07, 0xae, 0xab, 0x36, 0x20, 0x85, 0xa1, 0xa0, 0xba, 0x16, + 0x88, 0xde, 0xff, 0x4b, 0x66, 0xd0, 0x77, 0x15, 0x36, 0x56, 0x6c, 0x48, 0xa5, 0xa3, 0x63, 0xa0, 0xdc, 0xa2, 0x66, + 0x33, 0xb5, 0xd9, 0xd6, 0x08, 0x48, 0xdc, 0x1e, 0x62, 0x89, 0xcf, 0xc3, 0xd8, 0x23, 0x23, 0x8f, 0xd3, 0xf4, 0xaa, + 0x07, 0xbb, 0x35, 0xb6, 0x80, 0x10, 0x01, 0xfe, 0xa2, 0x37, 0x89, 0xb3, 0xc9, 0x1c, 0x89, 0xc7, 0xd3, 0x79, 0x90, + 0x7c, 0x16, 0x3f, 0x7b, 0xe9, 0xb2, 0x20, 0xb3, 0xad, 0x3b, 0x39, 0x68, 0xb9, 0x26, 0x2c, 0x48, 0x24, 0xaa, 0xb6, + 0x65, 0x15, 0x60, 0x82, 0x92, 0xad, 0xd7, 0x84, 0xa2, 0xae, 0xe5, 0xa2, 0xd7, 0x01, 0x5a, 0x92, 0x61, 0x18, 0x07, + 0xd7, 0xa2, 0xfd, 0xb2, 0x5c, 0x73, 0xaa, 0xac, 0x47, 0x53, 0x79, 0x88, 0x8f, 0xa9, 0x85, 0x7f, 0xbe, 0x3b, 0x2c, + 0xf9, 0x3d, 0xdd, 0xa9, 0x56, 0xfe, 0xd8, 0x0c, 0xb1, 0xb4, 0xc7, 0xee, 0x83, 0xbf, 0xa3, 0x73, 0x41, 0x60, 0xae, + 0xef, 0xda, 0xfc, 0x18, 0xce, 0xcd, 0xf2, 0x3c, 0x0a, 0x59, 0x19, 0x36, 0xd6, 0x83, 0xaa, 0xf2, 0x21, 0xe6, 0xc0, + 0xfe, 0xbe, 0xdc, 0x7a, 0xb2, 0xf3, 0x1c, 0xcd, 0xf8, 0x90, 0xa0, 0x4c, 0xa7, 0x53, 0x4b, 0x8b, 0x02, 0xee, 0xf8, + 0x6c, 0xb8, 0xf3, 0xf7, 0xe5, 0xab, 0x97, 0x83, 0x57, 0xea, 0xe3, 0x04, 0x09, 0x63, 0x69, 0x93, 0x24, 0xe8, 0x62, + 0x63, 0xbc, 0x72, 0x46, 0xd3, 0x20, 0x59, 0x3f, 0x9d, 0xc3, 0xca, 0x11, 0x5f, 0x44, 0x51, 0x88, 0x04, 0xb7, 0xdb, + 0x28, 0x3d, 0x9e, 0x47, 0x17, 0x91, 0xb2, 0x77, 0x6a, 0x76, 0xd6, 0xf2, 0x05, 0x05, 0x64, 0x15, 0xba, 0x47, 0x41, + 0xd5, 0x19, 0x53, 0x3a, 0xa1, 0x39, 0x88, 0x99, 0x9b, 0x57, 0xac, 0x76, 0xa5, 0x4e, 0x60, 0xef, 0xf4, 0x00, 0xd6, + 0x8e, 0x6c, 0xbc, 0xa6, 0xdc, 0x43, 0x40, 0xbd, 0x66, 0x6c, 0xee, 0xd0, 0xf1, 0x30, 0x81, 0x85, 0x58, 0xa7, 0x39, + 0xde, 0x3c, 0x5b, 0x4b, 0x4d, 0xd6, 0xad, 0x58, 0x9b, 0xa8, 0xcd, 0x62, 0x21, 0x8d, 0x74, 0x01, 0x20, 0x5f, 0x18, + 0x29, 0xae, 0x6a, 0xdd, 0x9b, 0x4e, 0x75, 0xe6, 0xa7, 0x94, 0xde, 0x3d, 0x89, 0xf2, 0x7c, 0xed, 0x82, 0xa9, 0x4d, + 0x77, 0x2d, 0x5d, 0xbb, 0xba, 0x1e, 0xba, 0x1c, 0x26, 0x8d, 0x24, 0x01, 0x4d, 0xb0, 0xde, 0x17, 0xa1, 0x97, 0xe3, + 0x73, 0x61, 0xc4, 0x99, 0x9d, 0x9d, 0x5a, 0xc2, 0xc0, 0x6e, 0xdd, 0xfb, 0x4b, 0x4b, 0x0c, 0xaa, 0x82, 0xa6, 0x5b, + 0x87, 0x66, 0x57, 0x40, 0x6f, 0x43, 0xaa, 0x44, 0x0d, 0xc8, 0x31, 0xd5, 0xe0, 0x6b, 0x34, 0x9d, 0x02, 0x0b, 0x90, + 0x3b, 0x52, 0xc6, 0xa4, 0x42, 0xaa, 0xa3, 0xd2, 0x6e, 0xc3, 0xb7, 0xde, 0x61, 0x60, 0x19, 0x19, 0x79, 0x50, 0x0c, + 0x48, 0xf2, 0x4c, 0xed, 0xcb, 0xc4, 0x2d, 0x56, 0x97, 0x48, 0xf4, 0x82, 0x52, 0xe8, 0x96, 0x72, 0x1a, 0x9a, 0xc0, + 0x3f, 0xfa, 0x5c, 0xa4, 0xca, 0xd4, 0xf3, 0x76, 0x50, 0x74, 0xdb, 0xf5, 0x5d, 0x0d, 0x5f, 0xed, 0x0e, 0x07, 0x25, + 0x8c, 0x0a, 0x9b, 0xab, 0x1d, 0x63, 0x46, 0x50, 0xbe, 0xf5, 0xe6, 0xfd, 0xf3, 0xbf, 0xbc, 0x7c, 0x71, 0x5f, 0x88, + 0x40, 0x4d, 0x6e, 0x63, 0x93, 0xcb, 0xe4, 0x96, 0x46, 0x7f, 0x7c, 0xf7, 0xfb, 0x9a, 0xdd, 0x1a, 0x7e, 0x3d, 0x84, + 0x36, 0xc9, 0x06, 0xdd, 0x80, 0x8b, 0x93, 0xf4, 0x22, 0xca, 0xfe, 0xf0, 0x32, 0x98, 0x8d, 0xb7, 0x0f, 0xf7, 0xfd, + 0x87, 0x97, 0xef, 0xee, 0x3d, 0xd4, 0xc7, 0xc3, 0x01, 0xc2, 0xf6, 0x22, 0x5d, 0xfc, 0x8e, 0xd9, 0x6d, 0xc3, 0x27, + 0x93, 0x79, 0x9a, 0x47, 0x6b, 0x46, 0xf0, 0xfc, 0xcd, 0xfb, 0x03, 0x5a, 0x2e, 0x4d, 0x82, 0x70, 0x53, 0x7f, 0x6c, + 0xf2, 0x1f, 0x3e, 0xbe, 0x3c, 0x38, 0x80, 0xae, 0xd1, 0x53, 0x26, 0x37, 0x5b, 0x17, 0x87, 0xf8, 0x0e, 0x8c, 0xd3, + 0x7a, 0xd6, 0x19, 0xab, 0x31, 0x23, 0x5d, 0x9d, 0x0d, 0x97, 0x35, 0x8e, 0xb9, 0xc0, 0x76, 0xa2, 0x67, 0xe6, 0x7e, + 0xef, 0x35, 0xaf, 0x16, 0x78, 0x74, 0x3b, 0x4a, 0xad, 0x94, 0x00, 0x0b, 0x73, 0xdc, 0x52, 0x1a, 0x5c, 0xb5, 0x94, + 0x22, 0xfb, 0xd8, 0x80, 0x8f, 0xcb, 0xf4, 0xdc, 0x20, 0x47, 0x80, 0xaf, 0xba, 0x73, 0xb9, 0x0c, 0x1e, 0xee, 0x0f, + 0x0c, 0x5a, 0xa4, 0x54, 0xa9, 0x8f, 0xba, 0xa5, 0x58, 0x30, 0x5e, 0x6a, 0x6d, 0x27, 0x73, 0xb4, 0xbc, 0x8f, 0x4c, + 0x35, 0x64, 0x95, 0x54, 0x15, 0x7e, 0x33, 0x7e, 0x8d, 0x2b, 0xe0, 0xcf, 0x58, 0x75, 0x23, 0x70, 0x0a, 0xb0, 0x37, + 0x68, 0xce, 0xde, 0x3b, 0x4d, 0xe1, 0x54, 0x9d, 0x03, 0x72, 0x01, 0xd2, 0xb0, 0x33, 0x92, 0xc2, 0x0e, 0x61, 0x6d, + 0xef, 0xfd, 0xcf, 0xff, 0xa9, 0x81, 0x79, 0x2e, 0x87, 0x85, 0x38, 0x5f, 0x44, 0x59, 0x00, 0x7d, 0x46, 0x65, 0xe7, + 0x7f, 0xfe, 0xef, 0xf3, 0x1a, 0x63, 0x3f, 0x32, 0xbf, 0x61, 0x92, 0xde, 0xfc, 0x04, 0xa0, 0xdf, 0xe5, 0x86, 0xf6, + 0xab, 0xbb, 0xa3, 0xf2, 0x0c, 0xf0, 0x8f, 0xaa, 0x3d, 0x2e, 0x6e, 0x99, 0x9b, 0x1c, 0x3d, 0xeb, 0x03, 0x3a, 0x80, + 0xf1, 0x61, 0x02, 0x4a, 0x60, 0x73, 0xe7, 0xa9, 0x6b, 0x1f, 0x68, 0x75, 0x47, 0xdb, 0xeb, 0x34, 0xb6, 0x18, 0xdf, + 0x37, 0x36, 0xb8, 0x51, 0xc8, 0xa7, 0xb2, 0xa9, 0x9b, 0xbb, 0x65, 0x4e, 0xdf, 0xc1, 0x62, 0xfc, 0xd1, 0x49, 0xe1, + 0x82, 0xde, 0x39, 0x2b, 0xac, 0xf4, 0x27, 0x4c, 0x0b, 0x48, 0xc9, 0x7b, 0x6f, 0xd8, 0x1f, 0x9c, 0xd7, 0x5d, 0x53, + 0xfa, 0x33, 0x66, 0x23, 0x24, 0xb7, 0xcf, 0x4f, 0x4e, 0x54, 0x4a, 0x5a, 0xf3, 0x7b, 0xf4, 0x0c, 0x1c, 0x37, 0x4a, + 0x04, 0x79, 0xe1, 0x0d, 0x1c, 0x0e, 0xd9, 0x73, 0x8f, 0x05, 0x21, 0x1b, 0xf7, 0x96, 0xe5, 0x58, 0x0f, 0xaf, 0xd9, + 0x55, 0xad, 0xd1, 0x77, 0x03, 0x58, 0x63, 0x29, 0xa5, 0x33, 0x55, 0x5a, 0x43, 0xb7, 0x7d, 0x38, 0x97, 0x59, 0xb0, + 0x60, 0x25, 0x41, 0x87, 0x34, 0x26, 0x28, 0x74, 0xa9, 0x71, 0xd1, 0x00, 0xde, 0x2e, 0xee, 0xc7, 0x50, 0xad, 0xc7, + 0x60, 0x84, 0x9a, 0xff, 0xf7, 0x90, 0x37, 0xe1, 0xe5, 0xfb, 0xe3, 0x6e, 0x4a, 0x13, 0xf7, 0x72, 0x9d, 0x69, 0xfd, + 0xeb, 0xbb, 0x4d, 0xeb, 0x3f, 0xdd, 0xcb, 0xb4, 0xfe, 0xf5, 0x9f, 0x6e, 0x5a, 0xff, 0xd2, 0x34, 0xad, 0xc7, 0x43, + 0xfc, 0x32, 0xba, 0x97, 0x25, 0xb3, 0xb4, 0x36, 0x4a, 0x2f, 0x73, 0x7f, 0x28, 0x98, 0x9e, 0x7c, 0x32, 0x8b, 0x50, + 0x8a, 0x24, 0x96, 0x6b, 0x9e, 0x9e, 0xa1, 0xc1, 0xf1, 0x7a, 0x93, 0xe2, 0x3f, 0xcb, 0xa0, 0x18, 0x3a, 0xb2, 0x8c, + 0x40, 0xf9, 0x89, 0x0c, 0x94, 0x8f, 0xc1, 0x01, 0xfe, 0x7e, 0x35, 0xfc, 0xe6, 0x70, 0x38, 0xda, 0x1e, 0x62, 0xa0, + 0x19, 0x14, 0x0c, 0x50, 0xc1, 0x60, 0xb4, 0xbd, 0x8d, 0x05, 0x97, 0x46, 0xc1, 0x16, 0x16, 0xc4, 0x46, 0xc1, 0x2e, + 0x16, 0x4c, 0x8c, 0x82, 0xc7, 0x58, 0x10, 0x1a, 0x05, 0x4f, 0xb0, 0xe0, 0xc2, 0x2a, 0x0f, 0x13, 0xe5, 0x38, 0xf0, + 0xc4, 0x39, 0xaa, 0xe4, 0x48, 0x51, 0x52, 0x2c, 0x59, 0xe5, 0x89, 0x2b, 0x03, 0x76, 0xf6, 0x76, 0x1c, 0x61, 0xa2, + 0x7e, 0xf2, 0x1f, 0x27, 0xe8, 0x4a, 0x8f, 0x42, 0x3d, 0x17, 0x45, 0xa2, 0x5c, 0x73, 0x5b, 0xbe, 0x86, 0x4e, 0x1c, + 0xd5, 0xc1, 0x96, 0x34, 0x5c, 0xf7, 0xc8, 0x8d, 0x4a, 0x56, 0xde, 0xed, 0xce, 0x54, 0xf4, 0xae, 0xa5, 0xaf, 0xbd, + 0x11, 0xb7, 0x31, 0x86, 0x31, 0xaa, 0xa6, 0x5f, 0x10, 0x7d, 0x00, 0xfc, 0x2e, 0x3a, 0x9b, 0xc9, 0xa8, 0x55, 0xb2, + 0x83, 0x0e, 0x79, 0x23, 0x8c, 0x02, 0x1d, 0x60, 0x4c, 0xc4, 0xba, 0xe3, 0xd1, 0x9f, 0xab, 0x10, 0x62, 0xcc, 0xe6, + 0x2e, 0xdd, 0x22, 0x38, 0xf3, 0x66, 0x2e, 0xcb, 0xb8, 0xbd, 0x33, 0x8c, 0x09, 0x3b, 0x0e, 0xbd, 0x85, 0x7b, 0x39, + 0x8b, 0x12, 0x6f, 0x2a, 0xac, 0x40, 0x71, 0xff, 0xd9, 0xc8, 0xe7, 0xdc, 0x91, 0xd6, 0x69, 0x74, 0x26, 0xf4, 0x5b, + 0x1e, 0x65, 0x4f, 0x1d, 0x25, 0x6d, 0x58, 0x65, 0x9b, 0xf2, 0xef, 0x3f, 0xc3, 0x0c, 0xe6, 0x45, 0x74, 0xba, 0x3c, + 0x03, 0xd4, 0x7f, 0x76, 0xa7, 0xc9, 0x8b, 0xf9, 0x0a, 0x47, 0x69, 0xb1, 0xa2, 0xaf, 0x27, 0x8f, 0xb7, 0xe8, 0x8b, + 0x7f, 0x96, 0xd5, 0xfa, 0x05, 0x8e, 0xad, 0x53, 0x30, 0xc8, 0xc6, 0x7e, 0x70, 0xb5, 0x0d, 0xa3, 0x92, 0x37, 0xb8, + 0x7e, 0xc6, 0xef, 0x4f, 0x81, 0x31, 0x9e, 0xfd, 0xb7, 0x40, 0xac, 0x07, 0x67, 0xb2, 0x7e, 0x73, 0x9c, 0xe8, 0x5f, + 0xa5, 0x38, 0x7d, 0x5a, 0x40, 0x94, 0x19, 0xc7, 0x0d, 0x53, 0x21, 0xb4, 0x60, 0x46, 0x13, 0x3a, 0xdc, 0x34, 0x6d, + 0x57, 0x13, 0xf7, 0x71, 0x7b, 0xaa, 0x26, 0x2e, 0x08, 0x44, 0x60, 0x44, 0xf5, 0x42, 0xd8, 0xde, 0x7a, 0x11, 0xef, + 0x75, 0x69, 0x8e, 0x4d, 0x59, 0x98, 0x54, 0x0a, 0xff, 0x88, 0xc9, 0x04, 0xcc, 0xe9, 0x5f, 0x6a, 0x2f, 0x71, 0x8b, + 0x9d, 0xcb, 0x41, 0xe2, 0x26, 0xc5, 0x49, 0x9f, 0xd6, 0xb8, 0xd3, 0xc7, 0x25, 0xf4, 0x12, 0xb8, 0xa3, 0xe4, 0xd9, + 0x6f, 0x6f, 0x25, 0x8e, 0xdb, 0xa7, 0xbd, 0x5d, 0xd5, 0xe3, 0x99, 0x78, 0xd9, 0xd9, 0x69, 0x60, 0x0f, 0xb7, 0x9e, + 0xb8, 0xf2, 0xbf, 0xfe, 0x60, 0xd7, 0x29, 0xa9, 0x85, 0x0e, 0x2c, 0x08, 0x80, 0xf2, 0xa4, 0xe8, 0x4d, 0x83, 0xf3, + 0x78, 0x7e, 0xed, 0x9d, 0xa7, 0x49, 0x0a, 0x43, 0x9a, 0x44, 0x23, 0x2d, 0xba, 0x19, 0x51, 0xa4, 0x2c, 0x11, 0xac, + 0x61, 0xd8, 0xdf, 0xca, 0xa2, 0x73, 0xfe, 0x5a, 0x05, 0xc2, 0x9a, 0xce, 0xa3, 0xab, 0x52, 0x74, 0x5f, 0xa9, 0xcc, + 0x55, 0xe9, 0xc8, 0xf1, 0x17, 0xc8, 0x87, 0x88, 0x28, 0x5b, 0x18, 0x5b, 0x72, 0x24, 0x88, 0x79, 0xaf, 0xbf, 0xb5, + 0x0b, 0x75, 0x3b, 0xfd, 0xdd, 0xb5, 0x8d, 0x43, 0xd1, 0x3e, 0x8e, 0x96, 0x3e, 0xee, 0x01, 0x39, 0x31, 0xa5, 0x37, + 0x3d, 0x72, 0xec, 0x95, 0xed, 0xf4, 0x48, 0xe4, 0x83, 0xad, 0x45, 0xe7, 0x23, 0x7c, 0xed, 0x6d, 0x75, 0x06, 0x23, + 0x20, 0x99, 0x7a, 0x3c, 0x9d, 0x27, 0xc0, 0x2c, 0xe8, 0xb6, 0xcc, 0xf5, 0x73, 0x56, 0x54, 0x7d, 0x08, 0xd5, 0x91, + 0xb5, 0x9f, 0x02, 0x6d, 0xec, 0xcd, 0xe2, 0x30, 0x8c, 0x92, 0x11, 0x8d, 0x59, 0x15, 0x46, 0xf3, 0x79, 0xbc, 0xc8, + 0xe3, 0x7c, 0x04, 0x34, 0x97, 0x68, 0x75, 0x67, 0x5d, 0xab, 0xdb, 0xa2, 0xd5, 0xed, 0x7b, 0xb7, 0x6a, 0x34, 0x83, + 0x4e, 0xc4, 0xdc, 0x8e, 0x18, 0xda, 0x2e, 0xb4, 0x52, 0x9d, 0xe7, 0xbd, 0x5b, 0x05, 0x2e, 0x7b, 0x75, 0x0e, 0x87, + 0x2f, 0x4e, 0xbc, 0x41, 0xd9, 0xbf, 0x58, 0xf1, 0xc1, 0xf8, 0xe2, 0xe9, 0xd3, 0xa7, 0x65, 0x3f, 0x94, 0xbf, 0x06, + 0x61, 0x58, 0xf6, 0x27, 0xf2, 0xd7, 0x74, 0x3a, 0x18, 0x4c, 0xa7, 0x65, 0x3f, 0x96, 0x05, 0xdb, 0x5b, 0x93, 0x70, + 0x7b, 0xab, 0xec, 0x5f, 0x1a, 0x35, 0xca, 0x7e, 0x24, 0x7e, 0x65, 0x51, 0x38, 0xa2, 0x83, 0x24, 0xcc, 0xd1, 0x9f, + 0x0c, 0xe0, 0x25, 0x42, 0x80, 0xc3, 0x0a, 0x6c, 0x22, 0xa9, 0xe2, 0xd1, 0xea, 0xde, 0x35, 0x3b, 0xba, 0xbb, 0xc9, + 0xa4, 0xb5, 0x5e, 0x18, 0x64, 0x9f, 0xa1, 0x9a, 0x9e, 0x45, 0x10, 0x70, 0xb5, 0x95, 0x5c, 0x86, 0xde, 0x95, 0x87, + 0x11, 0x53, 0x47, 0xa7, 0x69, 0x86, 0x77, 0x36, 0x0b, 0xc2, 0x78, 0x99, 0x7b, 0xc3, 0xad, 0xc5, 0x95, 0x2c, 0x12, + 0x67, 0x5d, 0x17, 0xd0, 0xdd, 0xf3, 0xf2, 0x74, 0x1e, 0x87, 0xb2, 0x68, 0xdd, 0x5d, 0x1a, 0x6e, 0x39, 0x23, 0x8a, + 0x17, 0x14, 0x53, 0xd4, 0x2b, 0xa0, 0x10, 0x3a, 0xfd, 0x6d, 0x20, 0x4e, 0x82, 0x9c, 0x34, 0x19, 0x9d, 0x41, 0xce, + 0xeb, 0x42, 0xb1, 0x81, 0x86, 0x3b, 0xd0, 0x87, 0x3c, 0xf3, 0xc3, 0xc7, 0x70, 0x6c, 0xfe, 0xf3, 0x3c, 0x0a, 0xe3, + 0xa0, 0x63, 0xeb, 0xd3, 0x34, 0x1c, 0xa0, 0xb6, 0xc3, 0x59, 0xad, 0x39, 0xa6, 0xf2, 0x5a, 0x60, 0x64, 0xe9, 0x8d, + 0x18, 0x40, 0x4c, 0x56, 0x04, 0x49, 0x51, 0x96, 0x27, 0x47, 0x65, 0x39, 0xfa, 0x14, 0xdb, 0x87, 0x7f, 0xb3, 0x19, + 0x17, 0xb2, 0x76, 0xb0, 0x74, 0x8e, 0xdc, 0x97, 0x91, 0x69, 0xc9, 0x84, 0x68, 0x8c, 0xac, 0x98, 0xcc, 0xca, 0x8c, + 0x6f, 0x9b, 0x95, 0x79, 0x91, 0x55, 0x75, 0x36, 0x8c, 0xaa, 0x56, 0x21, 0x0c, 0x84, 0x15, 0x80, 0x30, 0xfb, 0x64, + 0x98, 0x45, 0x21, 0xd1, 0x43, 0x95, 0xd9, 0xad, 0xf3, 0xc5, 0x3a, 0xda, 0xf3, 0xd3, 0xdd, 0xb4, 0xe7, 0x2f, 0xc5, + 0x7d, 0x68, 0xcf, 0x4f, 0x7f, 0x3a, 0xed, 0xf9, 0xa2, 0xe9, 0xd6, 0xf9, 0x29, 0x05, 0x46, 0x43, 0xea, 0xb2, 0x10, + 0x35, 0x65, 0x1c, 0x30, 0xf1, 0x45, 0xf1, 0xcf, 0xfa, 0xd7, 0xc9, 0xd6, 0x38, 0x05, 0x30, 0x63, 0x6e, 0x24, 0xe0, + 0xdf, 0x27, 0xfe, 0x5f, 0x32, 0xf3, 0xf7, 0x74, 0xea, 0xbf, 0x48, 0x8d, 0x02, 0xf5, 0x4b, 0x98, 0xf9, 0x54, 0x82, + 0x5b, 0xf1, 0x1b, 0x65, 0x88, 0x85, 0xe9, 0xbf, 0x30, 0x36, 0x0e, 0x5b, 0xdd, 0x87, 0xca, 0x1c, 0x72, 0x54, 0x1d, + 0x82, 0xad, 0xec, 0x8f, 0xa5, 0x07, 0x74, 0x43, 0x68, 0x0d, 0x9b, 0x24, 0x42, 0x96, 0x7c, 0x73, 0xfd, 0x3a, 0xb4, + 0x3f, 0xa5, 0x4e, 0x19, 0xe7, 0xef, 0xeb, 0xfe, 0xc7, 0x92, 0x05, 0x31, 0xa7, 0x53, 0x0a, 0x93, 0x46, 0x23, 0xcc, + 0x10, 0xbd, 0xe6, 0xcf, 0xc7, 0x95, 0x99, 0x7a, 0xe6, 0x87, 0x22, 0xcf, 0x68, 0x03, 0x19, 0x0b, 0x3f, 0xbd, 0x95, + 0xa0, 0xf2, 0x28, 0x75, 0x2a, 0x85, 0x6d, 0x09, 0xf9, 0xf3, 0x38, 0x2c, 0x01, 0xf3, 0xca, 0x85, 0x30, 0x10, 0x6d, + 0x74, 0x17, 0x11, 0x97, 0x6b, 0x86, 0x56, 0xe8, 0xa2, 0x59, 0xd1, 0xfc, 0x09, 0x4d, 0x37, 0x84, 0x5a, 0x5a, 0xac, + 0x99, 0xd5, 0xe1, 0xe5, 0x63, 0x93, 0x1e, 0x63, 0x42, 0x68, 0x6b, 0x60, 0x1a, 0xc2, 0x55, 0x36, 0xa4, 0x69, 0x73, + 0xcc, 0x8b, 0x43, 0xb6, 0x27, 0x18, 0x64, 0x49, 0x4a, 0xbb, 0x18, 0xec, 0x88, 0x3a, 0xf4, 0x03, 0x3e, 0x95, 0xb4, + 0x1f, 0x1d, 0x3f, 0x20, 0x6b, 0xf0, 0x83, 0xfd, 0x9a, 0x24, 0xeb, 0x0e, 0x93, 0x59, 0x24, 0x25, 0xf2, 0x4b, 0x17, + 0xfe, 0xeb, 0x3c, 0x5a, 0xc9, 0x00, 0x65, 0x45, 0xb0, 0xe8, 0xa1, 0xf8, 0x84, 0x60, 0xaf, 0x80, 0x78, 0x46, 0x2c, + 0xb4, 0xd1, 0x32, 0x47, 0xd8, 0x48, 0x9c, 0x3c, 0xc1, 0x9f, 0x11, 0x1c, 0xb9, 0x1c, 0xea, 0x2c, 0x80, 0xde, 0x2f, + 0x00, 0xd6, 0xd0, 0x52, 0x1d, 0xd2, 0xfa, 0xc8, 0xe5, 0x39, 0x5a, 0xa5, 0x40, 0x4e, 0x00, 0x57, 0x0a, 0xc8, 0x8a, + 0xe1, 0xdb, 0x60, 0x24, 0xa8, 0x83, 0x41, 0x6b, 0x7d, 0x4f, 0xac, 0x66, 0x97, 0x08, 0xbf, 0xac, 0x49, 0xce, 0x98, + 0xc7, 0x7c, 0x64, 0xbc, 0xe5, 0x10, 0x6d, 0x48, 0x7e, 0x04, 0x59, 0xef, 0x0c, 0xa1, 0x3c, 0x6e, 0xf5, 0x20, 0x8c, + 0xce, 0x5c, 0x82, 0xda, 0xa8, 0x01, 0x92, 0xff, 0xf5, 0x77, 0x9d, 0xce, 0xa0, 0xbd, 0x18, 0x29, 0x1e, 0xe7, 0x3e, + 0x23, 0xf3, 0x02, 0x4c, 0x68, 0xea, 0xde, 0xa7, 0xe6, 0x69, 0x04, 0x20, 0x2b, 0xe2, 0x70, 0xfe, 0xc3, 0xa7, 0x00, + 0xf5, 0xef, 0xdd, 0xfc, 0xed, 0xd3, 0x6f, 0x6f, 0x27, 0x49, 0x0b, 0x5b, 0x36, 0xe6, 0xdc, 0xd1, 0x5a, 0x13, 0x9f, + 0x21, 0x69, 0xc8, 0x2b, 0x3f, 0x61, 0x7f, 0x14, 0x74, 0xc5, 0x6e, 0x8d, 0x9a, 0x0a, 0xd4, 0x2d, 0xe3, 0xbc, 0x2c, + 0x9a, 0xc3, 0x51, 0xbb, 0x90, 0x34, 0xe3, 0x36, 0xe0, 0x35, 0xb9, 0xc7, 0x84, 0xf0, 0x7e, 0xc7, 0x26, 0xd5, 0x86, + 0x22, 0x37, 0xa9, 0x5e, 0x4c, 0x9b, 0x34, 0x6a, 0xcc, 0x46, 0x06, 0x12, 0xab, 0x61, 0xfa, 0x5d, 0x18, 0x83, 0x81, + 0xa2, 0xf5, 0x67, 0x0a, 0x53, 0xd7, 0x23, 0xc0, 0x9e, 0x03, 0x35, 0x05, 0x97, 0xb1, 0xb2, 0xd1, 0xd5, 0xbd, 0x34, + 0x16, 0x47, 0xad, 0x43, 0x70, 0x0a, 0x14, 0xc3, 0xb2, 0x88, 0xda, 0x97, 0x8b, 0x17, 0x67, 0x6b, 0xa0, 0x17, 0x87, + 0x9e, 0xab, 0x63, 0xdd, 0x45, 0x72, 0x1b, 0x8f, 0xc9, 0x60, 0x84, 0x09, 0x1f, 0x7a, 0xdb, 0xd5, 0xa1, 0xe3, 0x2b, + 0x35, 0x68, 0xb7, 0x65, 0x22, 0x2e, 0xa2, 0x25, 0x86, 0xde, 0x9d, 0xfe, 0x50, 0x94, 0xa9, 0xa0, 0xf7, 0xaa, 0xa8, + 0xac, 0x4e, 0xe6, 0x5f, 0x73, 0xc7, 0xbe, 0x6e, 0xbf, 0x63, 0x5f, 0xcb, 0x3b, 0x76, 0xfb, 0xc9, 0xfc, 0x62, 0x3a, + 0xc4, 0xff, 0x8d, 0xf4, 0x84, 0xbc, 0x41, 0x07, 0x96, 0xa3, 0x03, 0x64, 0x5a, 0xa7, 0x07, 0xc4, 0x5b, 0x87, 0x9a, + 0x26, 0xcb, 0x23, 0xb7, 0xbf, 0xe5, 0xb8, 0x83, 0x0e, 0x16, 0xe2, 0x7f, 0x83, 0xca, 0xab, 0xe1, 0x0e, 0xbe, 0xc3, + 0xaf, 0x76, 0x9b, 0xef, 0xb6, 0x6e, 0xbf, 0xea, 0x7c, 0x97, 0x24, 0xd0, 0x76, 0x80, 0x81, 0x3b, 0x3d, 0x85, 0xd2, + 0x69, 0x3a, 0x59, 0xe6, 0xff, 0x2d, 0xc6, 0x2f, 0x16, 0xf1, 0x56, 0x40, 0x50, 0x6b, 0x47, 0x7e, 0x8a, 0xd2, 0xbd, + 0x8b, 0x48, 0xb6, 0xb0, 0x52, 0xfb, 0xe4, 0x71, 0x76, 0x8a, 0xad, 0xfe, 0x4e, 0xcb, 0x21, 0x6f, 0x5f, 0xe8, 0x7f, + 0xd9, 0x2e, 0xad, 0x07, 0x31, 0x7f, 0x60, 0x59, 0x6e, 0x5d, 0x8e, 0xdf, 0xbf, 0x1a, 0x62, 0x37, 0x07, 0x4f, 0xdb, + 0x87, 0x7b, 0x28, 0x7b, 0x3a, 0x92, 0x48, 0x45, 0xe0, 0x2d, 0xe1, 0x25, 0x75, 0x7b, 0xab, 0xeb, 0xce, 0x48, 0xa3, + 0xd5, 0x5b, 0x10, 0x82, 0xae, 0x7b, 0x4f, 0x28, 0xff, 0xc5, 0xd7, 0x3b, 0xf8, 0x3f, 0xa6, 0xea, 0x7f, 0x29, 0xda, + 0x08, 0xf5, 0x17, 0x45, 0x85, 0x50, 0x67, 0x52, 0x89, 0x08, 0xf1, 0xfb, 0xd7, 0x9f, 0x4e, 0x7f, 0xdf, 0x07, 0xf7, + 0xae, 0xcd, 0x46, 0x7b, 0xf5, 0xda, 0xdf, 0xa4, 0x29, 0x66, 0x4e, 0x6f, 0x56, 0x97, 0xcb, 0xc3, 0x1e, 0x18, 0x85, + 0x8f, 0x1f, 0x49, 0x3e, 0x82, 0xed, 0x45, 0x2c, 0xfa, 0x86, 0x59, 0x89, 0x37, 0xeb, 0x58, 0x89, 0x8f, 0x77, 0xb3, + 0x12, 0xdf, 0xdf, 0x8b, 0x95, 0xf8, 0xf8, 0xa7, 0xb3, 0x12, 0x6f, 0x9a, 0xac, 0xc4, 0x9b, 0x54, 0x5a, 0x6a, 0xbb, + 0xaf, 0x96, 0xe2, 0xf1, 0x27, 0x56, 0xc5, 0x7e, 0x4c, 0xfd, 0xdd, 0x01, 0x67, 0x9c, 0xf8, 0xf4, 0x4f, 0x33, 0x16, + 0x74, 0x10, 0x3f, 0x92, 0xe1, 0xa2, 0x66, 0x2d, 0x04, 0x64, 0xa7, 0x7e, 0x8c, 0xe2, 0x79, 0x9a, 0x9c, 0x7d, 0x40, + 0x55, 0x3c, 0x8a, 0x03, 0x33, 0xe3, 0x45, 0x9c, 0x7f, 0x48, 0x17, 0xcb, 0xc5, 0x6b, 0x6c, 0xeb, 0xa7, 0x38, 0x8f, + 0x61, 0x97, 0x54, 0x88, 0x0f, 0x36, 0xb4, 0x14, 0xb2, 0x75, 0xb4, 0x6d, 0x96, 0x8f, 0xc1, 0x95, 0x7c, 0x24, 0xeb, + 0x67, 0xf1, 0xcc, 0x16, 0x9c, 0x56, 0x3b, 0x23, 0x82, 0x21, 0x13, 0x6b, 0x83, 0xfe, 0xfd, 0xcc, 0xc8, 0x9b, 0xd4, + 0x69, 0x99, 0xa5, 0xb4, 0xac, 0x59, 0xdb, 0x4e, 0x54, 0x6f, 0xe7, 0xd5, 0xd2, 0x71, 0x55, 0x04, 0xd4, 0xa6, 0x38, + 0xff, 0x3c, 0x05, 0x42, 0x18, 0x9a, 0x82, 0xdb, 0x96, 0x08, 0x73, 0x50, 0x4e, 0x22, 0xa9, 0xbe, 0xa1, 0xdc, 0xdd, + 0x07, 0x44, 0x28, 0x63, 0xa0, 0x04, 0x4c, 0x1d, 0xbf, 0x5c, 0xf4, 0xd8, 0xc0, 0xa0, 0x47, 0x53, 0xb4, 0x54, 0x92, + 0xc9, 0x0d, 0xdb, 0x4e, 0xfd, 0xdf, 0xf7, 0xa5, 0x34, 0x0a, 0x4a, 0xfb, 0x42, 0x2a, 0x9c, 0xdb, 0x89, 0x14, 0x2e, + 0xca, 0x30, 0x64, 0x2d, 0x1b, 0x27, 0xde, 0x70, 0xfc, 0x0e, 0xfd, 0x16, 0x03, 0x42, 0x94, 0x4b, 0xb1, 0x1f, 0x22, + 0x28, 0x17, 0xff, 0x7c, 0x6e, 0x2c, 0xe3, 0x7b, 0x80, 0x56, 0x40, 0xd3, 0x40, 0xe5, 0x34, 0x79, 0x8b, 0x0b, 0xf0, + 0x02, 0x16, 0xc0, 0xac, 0x40, 0xb9, 0xf2, 0x5a, 0xce, 0x52, 0x6b, 0xf8, 0x38, 0x74, 0xa7, 0x32, 0x46, 0x10, 0xf7, + 0x17, 0x80, 0xb2, 0xfe, 0xea, 0xf2, 0xdf, 0xbf, 0x39, 0x25, 0xdc, 0x00, 0xd5, 0xd1, 0x8f, 0x8b, 0x7b, 0x74, 0xf3, + 0xf0, 0xe1, 0xc6, 0xfa, 0x69, 0xdb, 0x13, 0x00, 0x3b, 0x99, 0x1c, 0x45, 0xcb, 0xd7, 0xce, 0xda, 0x5b, 0x80, 0xa3, + 0xf8, 0x94, 0x2e, 0x27, 0x33, 0x32, 0xa9, 0xfe, 0xf3, 0xe6, 0x5b, 0x60, 0x9b, 0x94, 0x24, 0x5e, 0x4d, 0xbd, 0x56, + 0xa4, 0x57, 0x81, 0xfa, 0x7f, 0x89, 0xf1, 0xcf, 0xff, 0x17, 0x97, 0xa1, 0x79, 0x6a, 0x94, 0x37, 0xf6, 0xef, 0x3a, + 0xbc, 0x23, 0xcc, 0x65, 0x2e, 0x22, 0x8b, 0x49, 0x25, 0x5d, 0x3b, 0x90, 0x29, 0xeb, 0x8b, 0x66, 0x46, 0xf1, 0x5d, + 0x17, 0xa0, 0x58, 0xf6, 0x12, 0xf5, 0x99, 0x4d, 0x17, 0x2e, 0x2d, 0x6e, 0x24, 0xa0, 0x55, 0x0d, 0xc8, 0x08, 0x43, + 0x97, 0x88, 0xc0, 0x57, 0xfd, 0x1d, 0x94, 0xb9, 0x94, 0x84, 0xa7, 0xf9, 0x26, 0xb8, 0xc2, 0x34, 0x14, 0x08, 0xdc, + 0xea, 0xaf, 0xb0, 0xd0, 0x35, 0x1d, 0x39, 0x31, 0xd3, 0xa6, 0xd5, 0xba, 0x12, 0x52, 0x1b, 0x78, 0xf2, 0x1f, 0x1d, + 0xf8, 0x3f, 0xc5, 0x46, 0x74, 0x14, 0x1f, 0x41, 0xe5, 0xc4, 0x0e, 0xa0, 0xb6, 0xa4, 0x04, 0x5e, 0x80, 0x4a, 0x90, + 0x33, 0x20, 0xd5, 0xb6, 0x2c, 0x10, 0x91, 0x96, 0x77, 0x07, 0xb2, 0x40, 0x32, 0xf4, 0x18, 0x25, 0x37, 0xc8, 0x30, + 0x21, 0x83, 0xd7, 0x21, 0x86, 0x9d, 0xde, 0x0a, 0x49, 0x30, 0x10, 0x8d, 0xf4, 0xf3, 0x64, 0x14, 0xb5, 0x87, 0xe4, + 0x4d, 0x0c, 0x28, 0x88, 0x5a, 0x87, 0x5a, 0x86, 0x0d, 0x98, 0x65, 0x13, 0x36, 0x12, 0x5f, 0x74, 0x55, 0xc0, 0x37, + 0x4b, 0x8b, 0x52, 0x72, 0x52, 0x88, 0x64, 0xac, 0xf3, 0x82, 0x89, 0x2d, 0x84, 0x36, 0xed, 0x5f, 0xce, 0x18, 0x17, + 0xe6, 0x02, 0xa4, 0x06, 0xee, 0x44, 0xf8, 0x26, 0xe6, 0x02, 0xb6, 0xd5, 0x31, 0x84, 0xd8, 0xd2, 0xb4, 0x0a, 0xcd, + 0x85, 0x37, 0x32, 0x51, 0x0a, 0x1c, 0x76, 0x4a, 0x88, 0x8b, 0xe4, 0xb2, 0xdb, 0x41, 0x7d, 0xd3, 0x94, 0x46, 0x26, + 0xa8, 0x09, 0x8a, 0x32, 0xa7, 0xd9, 0x8c, 0x18, 0x27, 0xc6, 0x85, 0x5c, 0xdb, 0xce, 0xa4, 0xd1, 0xce, 0x9a, 0x49, + 0x7f, 0x8e, 0xae, 0x19, 0x91, 0xf0, 0x52, 0xc1, 0x4f, 0xd4, 0xdb, 0xbf, 0x44, 0x69, 0x8a, 0x75, 0x0b, 0xb8, 0x76, + 0x31, 0xd3, 0xd2, 0x04, 0x63, 0x85, 0xde, 0x72, 0x81, 0x06, 0xe5, 0x2d, 0x50, 0x9c, 0x96, 0x38, 0x71, 0xf3, 0x91, + 0xbc, 0xc4, 0xc2, 0x99, 0xc4, 0x6e, 0x5d, 0xe3, 0x5e, 0xcb, 0xd5, 0x70, 0x1e, 0x01, 0x83, 0xb0, 0xd9, 0xa8, 0x8f, + 0x82, 0xec, 0xb6, 0xda, 0x30, 0x52, 0x7f, 0x38, 0xe8, 0xc5, 0x8f, 0xfa, 0x5b, 0xa3, 0x06, 0x8e, 0x36, 0x42, 0x79, + 0x9f, 0x90, 0xf8, 0x6b, 0xff, 0xc1, 0xca, 0x6e, 0x5c, 0x48, 0xa7, 0xee, 0x9c, 0x41, 0x63, 0x2b, 0x85, 0xfc, 0xeb, + 0xa4, 0x89, 0xfa, 0x39, 0x90, 0x38, 0xa7, 0x95, 0x3b, 0xc1, 0x64, 0x14, 0x36, 0x5e, 0xa3, 0x2f, 0x3b, 0xdd, 0x8e, + 0xcd, 0xd7, 0xc7, 0x71, 0x4e, 0x46, 0x12, 0xa2, 0x48, 0xef, 0x45, 0xb3, 0x81, 0x5a, 0x8f, 0x79, 0x1d, 0xc2, 0x89, + 0xb0, 0xf7, 0x91, 0xd6, 0xe8, 0xdd, 0x4a, 0x2d, 0x50, 0xfb, 0x6b, 0xd0, 0x67, 0xff, 0x14, 0xc3, 0x6f, 0x60, 0x0d, + 0x4c, 0x5d, 0x73, 0x67, 0x83, 0x18, 0x2d, 0xc9, 0x6c, 0xae, 0x8a, 0x24, 0xef, 0xdf, 0x18, 0x21, 0x1d, 0xd2, 0xa1, + 0xa9, 0xf6, 0xda, 0xd1, 0xdd, 0xef, 0x6c, 0x12, 0xe0, 0x44, 0xb5, 0xc1, 0x1a, 0xfe, 0xba, 0x7f, 0x73, 0x15, 0x88, + 0x82, 0x39, 0x9d, 0xd2, 0x16, 0x88, 0x02, 0x58, 0x92, 0x0e, 0x3f, 0x5f, 0xb7, 0xf8, 0x5e, 0x54, 0x0c, 0x7d, 0xc0, + 0x01, 0x59, 0xd5, 0x67, 0x86, 0x50, 0x1c, 0x13, 0x87, 0x90, 0x73, 0xb3, 0x2d, 0x09, 0xd1, 0xb5, 0x27, 0xb1, 0x90, + 0x96, 0xa4, 0xfa, 0x9b, 0x58, 0x84, 0x02, 0xbf, 0xaf, 0xd4, 0x3a, 0xbe, 0x5b, 0x6a, 0x5d, 0xdc, 0x25, 0xb5, 0x66, + 0xc7, 0x3d, 0x36, 0x7f, 0x52, 0x0e, 0x8c, 0x92, 0x38, 0x37, 0x5d, 0x40, 0x2b, 0xa2, 0x6e, 0xf2, 0xf3, 0x93, 0x5f, + 0x35, 0x5a, 0x63, 0xdb, 0x50, 0x12, 0x7f, 0x1b, 0x0c, 0x8a, 0x54, 0xa8, 0x9b, 0xb2, 0xf1, 0x37, 0x5a, 0x36, 0xce, + 0x5c, 0x8d, 0x76, 0xd9, 0x92, 0xd4, 0xbf, 0xe1, 0x0e, 0xa9, 0xb8, 0x03, 0xed, 0x16, 0xa9, 0x47, 0x6a, 0x38, 0xfa, + 0x69, 0x46, 0xc3, 0x70, 0x1f, 0x95, 0x5c, 0x46, 0xd5, 0x8b, 0xb4, 0x5a, 0x55, 0xfb, 0xf9, 0xe9, 0x72, 0x94, 0xba, + 0xd3, 0x90, 0x55, 0xb1, 0x79, 0x6c, 0xaa, 0x8e, 0x5e, 0xe6, 0x6b, 0xe3, 0x90, 0x28, 0x8f, 0x2c, 0x5e, 0x60, 0x29, + 0xa6, 0xaf, 0xe9, 0xb5, 0x95, 0x0d, 0x04, 0x0d, 0xb2, 0xc5, 0x81, 0xf4, 0x6e, 0xe9, 0x3c, 0xa7, 0xab, 0xd2, 0xaa, + 0xeb, 0x21, 0x61, 0x77, 0xd6, 0x04, 0x9b, 0xf2, 0x08, 0x5a, 0xeb, 0x23, 0x43, 0x82, 0xe0, 0x0d, 0x00, 0xb1, 0xb7, + 0x10, 0x00, 0x84, 0xff, 0xeb, 0xbf, 0x05, 0x29, 0x80, 0x92, 0xc8, 0xce, 0xc0, 0xd4, 0xf9, 0xd3, 0x25, 0xee, 0xb1, + 0xf9, 0x19, 0x55, 0x6d, 0xf6, 0xc9, 0xf2, 0x9e, 0x95, 0x70, 0xd7, 0xaa, 0x8a, 0xf3, 0x45, 0x0d, 0x4f, 0x8e, 0x43, + 0x9c, 0xb2, 0x6c, 0x99, 0x50, 0x16, 0xa2, 0x5e, 0x91, 0xc1, 0x78, 0x57, 0x46, 0x7f, 0x42, 0x24, 0x8a, 0xe2, 0xe2, + 0xaa, 0x52, 0x61, 0x14, 0x50, 0xd0, 0xee, 0xc8, 0xeb, 0x6f, 0xe5, 0x86, 0xa0, 0xc6, 0xfb, 0x62, 0xb0, 0x1d, 0x7c, + 0x3d, 0xdd, 0xa9, 0xc9, 0x4f, 0xb7, 0x76, 0xab, 0xd2, 0x75, 0x35, 0x8e, 0xf3, 0xf4, 0x37, 0xe1, 0xd8, 0xfa, 0xef, + 0xef, 0x3a, 0x17, 0x7d, 0xd6, 0xf6, 0xe8, 0x8f, 0x0c, 0x01, 0xbf, 0xaf, 0x28, 0xa6, 0x4d, 0x35, 0x4d, 0xa3, 0x64, + 0xdd, 0xb0, 0xa6, 0xf1, 0x7c, 0xde, 0x9b, 0xa3, 0x7b, 0xd1, 0xea, 0x0f, 0x4d, 0x8f, 0xda, 0x59, 0x62, 0xba, 0x88, + 0x3f, 0xd0, 0x4e, 0xf5, 0xa4, 0x14, 0x33, 0xa0, 0x47, 0x56, 0xa6, 0xa0, 0xdc, 0x90, 0x9f, 0x37, 0x65, 0xe6, 0x66, + 0xb7, 0xd3, 0xe9, 0xb4, 0x2a, 0x35, 0x1e, 0x74, 0x76, 0x48, 0xf2, 0xfb, 0xc5, 0x60, 0x30, 0xa8, 0xaf, 0xef, 0xba, + 0x8b, 0xc2, 0x17, 0xa3, 0x47, 0x42, 0xf8, 0xa7, 0x77, 0x9f, 0xa9, 0x7f, 0xd3, 0x68, 0xb9, 0xa9, 0x75, 0xf7, 0x91, + 0x8f, 0xda, 0xff, 0x17, 0x43, 0x21, 0xd0, 0x70, 0xd7, 0xf5, 0x6f, 0x9e, 0x95, 0x5b, 0x5a, 0xaa, 0x5f, 0xe0, 0xdf, + 0xf7, 0xf1, 0x1d, 0x67, 0xfd, 0x1e, 0x9f, 0xae, 0x3b, 0xde, 0x65, 0x5f, 0xa3, 0xdd, 0x8a, 0xcd, 0xd2, 0x88, 0x2d, + 0x95, 0xe2, 0x22, 0x3a, 0xcf, 0xbd, 0x49, 0x44, 0x0a, 0xd2, 0xbe, 0x81, 0x6d, 0xc9, 0xaa, 0xa7, 0x77, 0x86, 0x76, + 0x5c, 0x43, 0x09, 0x87, 0x07, 0x1d, 0x52, 0x56, 0x35, 0x34, 0x6b, 0xb2, 0x13, 0xc2, 0x62, 0xab, 0xa6, 0xc2, 0x89, + 0x8e, 0x29, 0x6c, 0x67, 0xa5, 0x5e, 0x07, 0xa9, 0xd3, 0x95, 0xb4, 0x36, 0x61, 0xe5, 0x09, 0xfd, 0xab, 0x94, 0x73, + 0x5f, 0xc3, 0x73, 0xc5, 0x5e, 0xeb, 0x29, 0xaa, 0x9b, 0x34, 0x2a, 0xe3, 0x51, 0xb7, 0x81, 0x3e, 0x65, 0x02, 0x34, + 0x35, 0xad, 0x5b, 0xd0, 0x82, 0xa6, 0x92, 0x1c, 0xb1, 0x45, 0x37, 0x46, 0xec, 0x2c, 0x9e, 0x3c, 0x2d, 0xdf, 0xd7, + 0xa9, 0xbd, 0x71, 0x0e, 0x6e, 0xf7, 0x29, 0x29, 0xf7, 0x2a, 0x47, 0x95, 0x4c, 0x65, 0xe8, 0x0c, 0x48, 0x8e, 0xb4, + 0xb3, 0xcc, 0xe6, 0x3d, 0xce, 0x1c, 0x08, 0xa8, 0xb3, 0x39, 0xef, 0xf5, 0xcd, 0x03, 0x26, 0xfd, 0xd2, 0x29, 0x9b, + 0x4b, 0x75, 0x2f, 0xd5, 0x5e, 0x5d, 0x87, 0x2d, 0xc7, 0x89, 0x3b, 0x80, 0xae, 0x28, 0x1d, 0x32, 0x1a, 0xea, 0xd4, + 0x60, 0x1f, 0x4f, 0x5a, 0xbd, 0x35, 0x81, 0xb5, 0x9c, 0x27, 0x35, 0xd7, 0x5e, 0x85, 0xdb, 0x96, 0x9a, 0x41, 0x5c, + 0x3b, 0x01, 0x9d, 0xe8, 0x77, 0x0f, 0x4f, 0x8c, 0x09, 0xae, 0x86, 0x74, 0x82, 0xe8, 0x96, 0xf6, 0x38, 0x22, 0x2d, + 0xdf, 0x52, 0xd2, 0x25, 0x7c, 0xdf, 0x2a, 0xbc, 0xff, 0x54, 0x91, 0xc6, 0x0b, 0x7f, 0xa0, 0x2d, 0xe7, 0x5e, 0xb5, + 0x81, 0x44, 0xb9, 0x7f, 0xdd, 0xe0, 0xea, 0xde, 0x75, 0x91, 0x38, 0xbc, 0x77, 0x65, 0xa4, 0x2e, 0xd9, 0x4a, 0xa9, + 0xf0, 0xbf, 0x37, 0x94, 0x07, 0x66, 0x2c, 0x0b, 0x8b, 0xbe, 0x62, 0x8e, 0xfe, 0xdd, 0x12, 0x18, 0xcd, 0xf1, 0xd5, + 0xf9, 0xbc, 0x03, 0x0c, 0x01, 0x66, 0x51, 0xf5, 0xad, 0x61, 0x7f, 0x60, 0x75, 0x28, 0x32, 0x03, 0xf4, 0xe0, 0x5b, + 0x3f, 0x7e, 0x7a, 0xd5, 0x7b, 0x6a, 0x8d, 0xd1, 0x1c, 0xe3, 0xe2, 0x8c, 0x48, 0xdc, 0x37, 0xc1, 0x75, 0x94, 0x1d, + 0x6f, 0x59, 0x1d, 0x4a, 0x96, 0xca, 0xc4, 0x2d, 0x95, 0x75, 0xa0, 0xec, 0xee, 0x9c, 0x7c, 0x1d, 0x99, 0x56, 0xdb, + 0x42, 0xc0, 0x3a, 0xdc, 0x7a, 0x0a, 0xff, 0xed, 0xf4, 0x1f, 0x3f, 0xb5, 0xf6, 0xff, 0xa3, 0xd3, 0xd9, 0x0b, 0xa3, + 0x69, 0xbe, 0x4f, 0xe2, 0x98, 0x3d, 0xa2, 0x07, 0xf9, 0xb9, 0xd3, 0xe9, 0x4f, 0xe6, 0x79, 0x6f, 0xd8, 0x59, 0x89, + 0x9f, 0x9d, 0x0e, 0x02, 0x23, 0xaf, 0xf3, 0xc5, 0x74, 0x6b, 0xba, 0x33, 0xfd, 0x7a, 0x24, 0x8a, 0xcb, 0xff, 0xa8, + 0x54, 0x77, 0xf9, 0xef, 0x96, 0xf1, 0x59, 0x5e, 0x64, 0xe9, 0xe7, 0x48, 0xd0, 0x92, 0x1d, 0x25, 0x28, 0xaa, 0x7f, + 0xba, 0xd5, 0xec, 0x69, 0xf8, 0xf4, 0x74, 0x32, 0xdd, 0xd2, 0xd5, 0x69, 0x8c, 0x9b, 0x6a, 0x90, 0x40, 0xd0, 0x8a, + 0xa1, 0xef, 0x99, 0xcb, 0x34, 0xec, 0xb5, 0x2d, 0xd4, 0xd0, 0x12, 0x73, 0x3c, 0x93, 0xf3, 0xdb, 0xc3, 0x90, 0xf7, + 0xda, 0x83, 0x23, 0xa7, 0xcf, 0x7c, 0xeb, 0x2d, 0xac, 0x8f, 0x3b, 0x1c, 0x3e, 0x86, 0xf5, 0x99, 0x0c, 0xdc, 0x9d, + 0xfe, 0x4e, 0x6f, 0xbb, 0xff, 0xd8, 0x7d, 0xda, 0x7b, 0xea, 0x3e, 0xfd, 0xee, 0xe9, 0xa4, 0x07, 0x05, 0xee, 0xa0, + 0xf7, 0x14, 0x0b, 0xe1, 0xdf, 0xa7, 0x17, 0xbd, 0x1d, 0xa8, 0x46, 0xa5, 0x5b, 0xfd, 0xdd, 0xdd, 0xde, 0x70, 0x00, + 0xff, 0xba, 0xbb, 0xfd, 0xc7, 0x8f, 0x7b, 0x43, 0xa8, 0xf2, 0xf8, 0xcd, 0xee, 0xd3, 0xfe, 0x36, 0xbe, 0xdb, 0xde, + 0x9e, 0x6c, 0xf7, 0x87, 0xc3, 0x1e, 0xfe, 0xe3, 0x3e, 0xed, 0x6f, 0xf1, 0xc3, 0x70, 0xd8, 0xdf, 0x1e, 0xba, 0x83, + 0xf9, 0xee, 0x56, 0xff, 0xf1, 0xd7, 0x2e, 0xfd, 0x4b, 0xd5, 0x5c, 0xfa, 0x07, 0x9b, 0x71, 0xbf, 0xee, 0x6f, 0x3d, + 0xe6, 0x27, 0x6a, 0xf0, 0x62, 0xe7, 0xe9, 0x2f, 0xd6, 0xe6, 0xda, 0x39, 0x0c, 0x79, 0x0e, 0x4f, 0x77, 0xa1, 0x43, + 0x77, 0x67, 0xd8, 0x7f, 0xba, 0x3d, 0xeb, 0xed, 0x40, 0xb3, 0x4f, 0x26, 0xbd, 0x61, 0xff, 0xc9, 0x13, 0x18, 0xfa, + 0x76, 0x7f, 0xcb, 0x1d, 0xf6, 0x77, 0xb6, 0xe9, 0x01, 0xfe, 0xbb, 0x78, 0xf2, 0x75, 0xff, 0xf1, 0xee, 0xec, 0x71, + 0x7f, 0xe7, 0xa7, 0x1d, 0x18, 0xd7, 0xf6, 0x6c, 0xfb, 0x71, 0x7f, 0xeb, 0xc9, 0x05, 0xfc, 0x9e, 0xf5, 0xb6, 0x1e, + 0xdf, 0xfa, 0xe5, 0x70, 0xab, 0x8f, 0x6b, 0x44, 0xaf, 0xf1, 0x85, 0x2b, 0x5e, 0xe0, 0x7f, 0x33, 0xfa, 0xf6, 0xdf, + 0xd8, 0x4c, 0xde, 0xfc, 0xf4, 0xeb, 0xfe, 0xd3, 0x27, 0x13, 0xae, 0x8e, 0x05, 0x3d, 0x59, 0x03, 0x3f, 0xb9, 0xe8, + 0x71, 0xb7, 0xd4, 0x5c, 0x4f, 0x36, 0x24, 0xff, 0x13, 0x9d, 0x5d, 0xf4, 0xb0, 0x63, 0xee, 0xf7, 0x7f, 0xb5, 0x1d, + 0xb5, 0xe5, 0x7b, 0x9b, 0x67, 0x7c, 0xf4, 0xe1, 0x0f, 0xe7, 0xd7, 0x3c, 0x71, 0x7f, 0x5b, 0xa7, 0x94, 0xfc, 0xf5, + 0x6e, 0xa5, 0xe4, 0x37, 0xcb, 0xfb, 0x28, 0x25, 0x7f, 0xfd, 0xd3, 0x95, 0x92, 0xbf, 0xd5, 0x7d, 0x6b, 0x5e, 0xd5, + 0xd3, 0x80, 0x7d, 0xbf, 0xaa, 0x8b, 0x1c, 0x92, 0xc0, 0x3e, 0x7c, 0xb7, 0x3c, 0xc2, 0xd0, 0x7e, 0x50, 0xfb, 0x9b, + 0x65, 0xc5, 0xe0, 0x33, 0x45, 0x18, 0xfb, 0x2a, 0x65, 0x18, 0xfb, 0xd3, 0xd2, 0x47, 0x2b, 0x33, 0x41, 0xe6, 0xc4, + 0x61, 0x6f, 0x16, 0xcc, 0xa7, 0x8a, 0x44, 0xc2, 0x92, 0x11, 0x15, 0xa3, 0xe3, 0x1a, 0xa2, 0x67, 0xe4, 0x64, 0x96, + 0xe7, 0x49, 0x8e, 0x16, 0xc1, 0x68, 0xc9, 0x31, 0x05, 0x7a, 0xa9, 0xfa, 0x71, 0x5f, 0x06, 0x43, 0x3c, 0x16, 0x5e, + 0x50, 0x6b, 0xdf, 0x93, 0x01, 0x70, 0x7b, 0xeb, 0xc3, 0x66, 0xbb, 0x1d, 0xb4, 0xac, 0x93, 0x06, 0xd2, 0x48, 0xed, + 0xb7, 0xbd, 0xaf, 0x9a, 0xe1, 0xd6, 0x0c, 0xaf, 0xd7, 0x8f, 0x14, 0x47, 0x52, 0xff, 0x7e, 0x58, 0x35, 0xe3, 0xbd, + 0x6b, 0x9a, 0x2d, 0xdd, 0x57, 0x3e, 0xbf, 0xc5, 0x86, 0x58, 0x35, 0x5c, 0x5f, 0xaa, 0x5a, 0x12, 0xeb, 0xd6, 0x05, + 0xd1, 0x0c, 0xaa, 0x36, 0x34, 0xd6, 0x94, 0x2a, 0xe0, 0x30, 0x92, 0x1a, 0x18, 0xef, 0x2a, 0x6d, 0x9a, 0xc6, 0xc9, + 0x8f, 0x56, 0xc4, 0x57, 0xc4, 0xbf, 0x21, 0x25, 0x2a, 0x28, 0x1e, 0x28, 0x31, 0xba, 0x5d, 0x19, 0xed, 0xb2, 0x34, + 0xa2, 0x9c, 0x0d, 0x57, 0x4d, 0x5a, 0x74, 0xad, 0x5b, 0xc2, 0x30, 0x3a, 0x97, 0x54, 0x10, 0x75, 0xcf, 0x4e, 0x00, + 0x25, 0x3b, 0x6a, 0x90, 0x9f, 0xc3, 0x6d, 0x8d, 0xc9, 0x7a, 0x5f, 0xe0, 0x21, 0x16, 0x31, 0x99, 0x3b, 0x66, 0xb4, + 0x19, 0xa0, 0xd6, 0xd3, 0xa0, 0xf0, 0x88, 0x4c, 0x33, 0x48, 0xde, 0x2d, 0xf2, 0x58, 0x18, 0xdd, 0x62, 0x4c, 0x67, + 0x36, 0x2c, 0x1a, 0x21, 0xcf, 0x87, 0xdb, 0xec, 0xef, 0x94, 0xc3, 0xd9, 0xaa, 0x62, 0x8e, 0x32, 0xdc, 0x85, 0x3a, + 0x8f, 0xdd, 0xfe, 0x13, 0xa8, 0x23, 0x2f, 0x9c, 0xd9, 0x64, 0x65, 0x41, 0xd0, 0x01, 0x42, 0x0d, 0x33, 0x4e, 0x80, + 0x8c, 0x0d, 0xe6, 0x25, 0xd2, 0xc3, 0x55, 0x26, 0xe5, 0xd7, 0x65, 0x5e, 0xe0, 0x1c, 0x25, 0xd1, 0x4b, 0xce, 0x1f, + 0xbd, 0xd3, 0xa8, 0xb8, 0x8c, 0xa2, 0x64, 0x8d, 0x61, 0x4c, 0xdd, 0x97, 0xe4, 0x5f, 0x67, 0x59, 0x5f, 0xb2, 0xd5, + 0xda, 0x69, 0x91, 0x88, 0xf3, 0x21, 0x1d, 0x1f, 0xca, 0x13, 0xf7, 0xbb, 0x75, 0x00, 0xf7, 0xc7, 0xbb, 0x01, 0x6e, + 0x11, 0xdd, 0x07, 0xe0, 0xfe, 0xf8, 0xa7, 0x03, 0xdc, 0xef, 0x4c, 0x80, 0x5b, 0xf1, 0x1f, 0xd4, 0x1a, 0xa6, 0x03, + 0xfa, 0x6d, 0x63, 0x66, 0x94, 0xae, 0xb5, 0xc9, 0x04, 0xbc, 0xe5, 0xe8, 0xf4, 0x41, 0x3f, 0x57, 0x12, 0xb5, 0x92, + 0x00, 0x94, 0xb2, 0x6e, 0x70, 0x52, 0xc8, 0x18, 0x5d, 0xdd, 0x54, 0x62, 0x48, 0x68, 0xf3, 0x75, 0x52, 0xcc, 0xfb, + 0x1f, 0x05, 0x1f, 0x89, 0x0a, 0xdd, 0x57, 0xb0, 0xa4, 0x01, 0x45, 0x7f, 0xb5, 0x28, 0xc1, 0x3b, 0xfe, 0x18, 0xa0, + 0x33, 0x2e, 0x34, 0x19, 0x2a, 0xad, 0x64, 0xe4, 0x1f, 0x32, 0xc5, 0x6d, 0x5d, 0x47, 0x41, 0x66, 0xb9, 0xfc, 0x1a, + 0x37, 0xf7, 0xd1, 0xf6, 0xe0, 0xd1, 0xd6, 0xce, 0xa3, 0xc7, 0x03, 0xfc, 0xff, 0x61, 0xb4, 0x5d, 0xba, 0xa2, 0xe2, + 0x39, 0x1c, 0xa1, 0x99, 0xae, 0xb9, 0xae, 0x1a, 0x1c, 0xac, 0xcf, 0xba, 0xd6, 0x93, 0xf6, 0x4a, 0x61, 0x70, 0xad, + 0xeb, 0xb4, 0xd6, 0x98, 0xc1, 0x32, 0xe9, 0x2a, 0x2d, 0xa3, 0x89, 0x93, 0x25, 0xca, 0xd9, 0x8d, 0x1a, 0xe6, 0x6b, + 0x31, 0x5d, 0x3d, 0x2f, 0x78, 0x77, 0xa4, 0x13, 0xd9, 0xca, 0x7c, 0x46, 0x77, 0xae, 0xa0, 0x50, 0x51, 0x0e, 0x28, + 0x1c, 0x38, 0x25, 0x9a, 0xc0, 0x60, 0xe0, 0x2a, 0xfd, 0x68, 0xc0, 0x1b, 0x54, 0x64, 0xb0, 0x7b, 0x36, 0x3d, 0x02, + 0x27, 0x69, 0xc7, 0x9b, 0x59, 0x5f, 0x74, 0xec, 0xd0, 0xae, 0x05, 0xfb, 0x03, 0x9d, 0xf5, 0x2f, 0x97, 0xbb, 0x12, + 0x3c, 0x2a, 0xdc, 0x8c, 0xf4, 0xd8, 0xbc, 0xb5, 0x3d, 0x3f, 0x78, 0xa4, 0x3e, 0x84, 0x77, 0x49, 0x17, 0x75, 0x9f, + 0xfe, 0xe0, 0xe1, 0x43, 0xae, 0xb5, 0xe1, 0xcb, 0x69, 0x8d, 0x27, 0x3a, 0x68, 0x68, 0x27, 0x00, 0xb4, 0x4c, 0x71, + 0x43, 0xbd, 0x89, 0x9b, 0x76, 0xbb, 0xfb, 0xfe, 0xd0, 0xa1, 0x8c, 0xb2, 0x32, 0x33, 0xbc, 0x48, 0x56, 0xfc, 0xe6, + 0x7e, 0x86, 0x46, 0x32, 0xaf, 0x63, 0xd5, 0x95, 0x76, 0x81, 0x3c, 0xd3, 0x40, 0xba, 0x23, 0x08, 0xe8, 0x85, 0xc9, + 0x03, 0xd9, 0xa0, 0x20, 0x90, 0x06, 0x3f, 0xb2, 0x8e, 0xe2, 0xba, 0xb6, 0xfb, 0x03, 0xe0, 0xbb, 0xd4, 0x87, 0xd3, + 0xf8, 0xcc, 0x5f, 0xa5, 0x45, 0x80, 0x69, 0x58, 0x01, 0xbc, 0xa1, 0x1f, 0x1d, 0x60, 0xc0, 0x39, 0xe6, 0xf4, 0x44, + 0x87, 0xba, 0x73, 0xe6, 0xcb, 0x4b, 0xe1, 0xdd, 0x10, 0x64, 0x9f, 0x29, 0xaf, 0xbb, 0x74, 0xc5, 0xa5, 0x38, 0x76, + 0x6f, 0x11, 0x19, 0xda, 0x96, 0x8d, 0xb2, 0x01, 0xe8, 0xa5, 0x67, 0x7a, 0x0b, 0x79, 0x1d, 0xfc, 0xc6, 0xb1, 0xc4, + 0x24, 0xa6, 0x19, 0xf0, 0x26, 0x39, 0x9c, 0x74, 0x38, 0x16, 0x0c, 0x85, 0x2c, 0x01, 0x6a, 0x3b, 0xc3, 0xaf, 0x1f, + 0xbb, 0x9d, 0x2d, 0xe0, 0xa4, 0x06, 0x08, 0x6e, 0xa1, 0xc7, 0x15, 0x1c, 0x8f, 0xbb, 0x0c, 0x1e, 0x18, 0xbe, 0x7c, + 0xc1, 0xf3, 0x60, 0x53, 0x07, 0xa1, 0x4a, 0x2a, 0x38, 0x7e, 0xb1, 0x6d, 0x24, 0x34, 0x89, 0x59, 0xe9, 0xf9, 0x09, + 0x96, 0xdb, 0xa1, 0x9c, 0x97, 0xa2, 0x0a, 0x5c, 0x6e, 0x72, 0x18, 0x8e, 0x93, 0x4e, 0x7c, 0x73, 0x03, 0xd5, 0xe0, + 0x87, 0x6f, 0xac, 0x0f, 0xfe, 0x76, 0x2a, 0x0b, 0x16, 0x6b, 0x35, 0x3d, 0x2d, 0x16, 0x7a, 0x1a, 0xe2, 0x5f, 0x5d, + 0x2c, 0x1f, 0x84, 0x99, 0x04, 0x6c, 0x08, 0x6c, 0x57, 0x4c, 0x7f, 0x1a, 0xe6, 0x58, 0xec, 0xf3, 0x5c, 0x2b, 0xd5, + 0x4d, 0x69, 0x53, 0xa9, 0xfc, 0x9b, 0xeb, 0x4f, 0x9c, 0xd3, 0xd7, 0xb6, 0x10, 0xcb, 0x91, 0x8b, 0xae, 0xd6, 0xe4, + 0x76, 0xfd, 0xaf, 0xf6, 0xce, 0xa3, 0x22, 0x60, 0x35, 0x10, 0x32, 0xbf, 0x48, 0x0e, 0x74, 0x04, 0xa2, 0x11, 0xb1, + 0xa1, 0x7c, 0x0e, 0x73, 0xce, 0x78, 0xc2, 0xed, 0x08, 0x3c, 0xd5, 0x23, 0x8b, 0x4f, 0x7f, 0xe8, 0xb2, 0xc3, 0x01, + 0xfc, 0x40, 0xa9, 0xa1, 0x9f, 0xa4, 0xd6, 0xfe, 0x57, 0xca, 0x37, 0x73, 0xdd, 0x26, 0x00, 0x16, 0xfc, 0x7c, 0x98, + 0x45, 0xf3, 0xff, 0xf6, 0xbf, 0x42, 0xc4, 0xfd, 0xd5, 0x11, 0x6c, 0x44, 0xd1, 0x9f, 0xc1, 0x69, 0xf0, 0xbf, 0x6a, + 0x49, 0x30, 0x4f, 0xec, 0x3d, 0x8f, 0xc5, 0xda, 0xde, 0xd2, 0x21, 0xe7, 0xb6, 0xef, 0xc5, 0xd4, 0xef, 0x0b, 0x6e, + 0x1d, 0x39, 0xc0, 0x55, 0x85, 0xc7, 0x1e, 0x8e, 0x88, 0x7f, 0x3e, 0x85, 0x4b, 0xf8, 0x79, 0xc4, 0x6f, 0x2a, 0x3f, + 0x7a, 0x88, 0xad, 0x27, 0xc1, 0xc2, 0x23, 0xf4, 0x6a, 0x16, 0xa2, 0xf7, 0x34, 0x97, 0x2a, 0xca, 0xae, 0xf5, 0x2c, + 0xd3, 0x51, 0x5e, 0x51, 0xcf, 0xd4, 0xd5, 0xe5, 0x2c, 0x2e, 0x22, 0xd9, 0x15, 0xfd, 0x28, 0x4b, 0xc9, 0xa8, 0x33, + 0x8b, 0x4a, 0x8c, 0x75, 0x7f, 0xbb, 0x33, 0x7c, 0xfa, 0xdd, 0xee, 0xc5, 0x70, 0x30, 0xdb, 0x02, 0xd6, 0xf4, 0xa7, + 0xe1, 0xd3, 0xd9, 0x76, 0xff, 0xc9, 0x1c, 0x18, 0x9c, 0x27, 0xf8, 0xdf, 0x4f, 0x4f, 0xfa, 0x4f, 0x81, 0x61, 0x02, + 0x46, 0x74, 0xb8, 0x35, 0xef, 0x3d, 0x85, 0x42, 0xf8, 0xef, 0x0d, 0x7f, 0x85, 0x0c, 0x10, 0xf3, 0x3b, 0x5f, 0x55, + 0xa0, 0x80, 0xf1, 0xac, 0x74, 0xb2, 0x6e, 0x05, 0xbd, 0xb5, 0xe8, 0x75, 0x11, 0x64, 0x70, 0xc6, 0x1f, 0x32, 0x45, + 0x18, 0xd9, 0x89, 0x1f, 0x71, 0x8e, 0x1f, 0x69, 0xde, 0x26, 0xfd, 0xd0, 0x65, 0xa2, 0x95, 0xd6, 0x6b, 0x24, 0xbe, + 0x69, 0x4f, 0x2e, 0x22, 0x93, 0x00, 0xb3, 0x22, 0xf8, 0xc7, 0x05, 0x85, 0xc6, 0x93, 0x39, 0xb1, 0x0c, 0xa8, 0xa4, + 0x13, 0xd1, 0x97, 0x77, 0x0f, 0x9c, 0xbc, 0xf9, 0x23, 0x95, 0x08, 0xf5, 0x4f, 0x6d, 0xdb, 0x48, 0x3d, 0xf6, 0x87, + 0xda, 0xa1, 0xa4, 0x4c, 0x3a, 0x9f, 0x12, 0x46, 0x14, 0x0f, 0xe3, 0x4c, 0x0d, 0xcf, 0x00, 0xd1, 0xc3, 0xf6, 0xac, + 0x2c, 0x0e, 0x66, 0x8c, 0x7c, 0x8d, 0x54, 0xf2, 0x45, 0x30, 0x37, 0x0c, 0xd9, 0x8c, 0x2f, 0x37, 0x14, 0xe4, 0x7f, + 0xf8, 0x50, 0x0f, 0xae, 0x57, 0x1b, 0xf7, 0xde, 0x70, 0x17, 0xd1, 0x2e, 0xfc, 0x73, 0xab, 0x4d, 0x65, 0x74, 0x67, + 0x2c, 0x7a, 0x1d, 0x84, 0x5a, 0xda, 0x4d, 0x49, 0x8b, 0x8d, 0xb5, 0x86, 0x9d, 0x0d, 0x7b, 0x0d, 0x8c, 0xe2, 0x5f, + 0x63, 0x75, 0x00, 0x3a, 0x24, 0xd2, 0xfc, 0x20, 0xb9, 0x25, 0xfe, 0xbe, 0xe0, 0xc5, 0x2c, 0x5c, 0x9a, 0x5b, 0xe6, + 0x71, 0x87, 0x83, 0xf8, 0xff, 0xf6, 0x24, 0xc8, 0x59, 0x13, 0xed, 0x25, 0x6a, 0xb7, 0xb5, 0xe2, 0xbc, 0xa7, 0xf0, + 0x2a, 0x23, 0xd4, 0x28, 0x1f, 0x5b, 0x58, 0x84, 0xa9, 0x84, 0x29, 0x7b, 0xb8, 0x32, 0x16, 0x55, 0xd8, 0x42, 0x17, + 0xb8, 0x31, 0xe5, 0x3a, 0x91, 0x8e, 0xa3, 0x40, 0x19, 0xaf, 0x45, 0x42, 0x6c, 0x9c, 0x03, 0xc7, 0x4c, 0xe5, 0x37, + 0xb5, 0x4c, 0xf8, 0x66, 0x99, 0x20, 0x46, 0xb5, 0x4b, 0x50, 0x43, 0xda, 0xb8, 0xf2, 0xd9, 0xa3, 0xc7, 0xd3, 0x28, + 0x80, 0xed, 0x60, 0x65, 0xa9, 0x6d, 0x20, 0x77, 0x17, 0x08, 0x3b, 0xb4, 0x6e, 0x15, 0x11, 0x34, 0x55, 0x9a, 0x44, + 0xa0, 0xa2, 0x7b, 0xaa, 0x8d, 0x9b, 0x81, 0x0e, 0x40, 0xf6, 0xbe, 0x08, 0x38, 0x30, 0x8c, 0x89, 0x72, 0x81, 0x22, + 0x91, 0x29, 0x3b, 0x91, 0x2e, 0x1f, 0xdd, 0x15, 0xf9, 0x61, 0xff, 0xfd, 0xa7, 0x67, 0x1d, 0x71, 0xfe, 0xd9, 0x5a, + 0x80, 0x18, 0x19, 0xce, 0xfc, 0xe3, 0x73, 0xe6, 0x9f, 0x8e, 0x48, 0x32, 0x65, 0x51, 0xce, 0x46, 0x5e, 0x41, 0x12, + 0x40, 0xb3, 0x0d, 0xc5, 0x39, 0xec, 0x4b, 0x0c, 0x20, 0xae, 0xd8, 0xa4, 0xb4, 0x3f, 0x08, 0xe4, 0xac, 0x75, 0xf1, + 0x20, 0xd8, 0x0c, 0x43, 0x06, 0x6e, 0x2d, 0x12, 0x69, 0x87, 0x01, 0x68, 0x41, 0x99, 0x61, 0xc8, 0x0e, 0x82, 0xc9, + 0x24, 0x5a, 0x00, 0x7e, 0x33, 0xd3, 0x0b, 0xa5, 0x70, 0xa1, 0x81, 0x53, 0x2c, 0x80, 0x2a, 0x3c, 0xb7, 0x54, 0x80, + 0xf0, 0x66, 0x7b, 0xf9, 0xf2, 0xf4, 0x3c, 0x2e, 0x54, 0x84, 0x5d, 0x9e, 0x20, 0x1a, 0x44, 0xe0, 0x10, 0xf7, 0x4f, + 0x4a, 0xb1, 0x84, 0x6f, 0xd2, 0xb3, 0xda, 0x89, 0xd2, 0x94, 0xcb, 0x98, 0xe2, 0xb7, 0x33, 0x27, 0x83, 0xd2, 0x62, + 0xd8, 0xf1, 0x63, 0x11, 0xc3, 0x42, 0x05, 0x02, 0x86, 0x16, 0x05, 0x7b, 0xdb, 0xa1, 0xf0, 0x2d, 0xd6, 0xee, 0x00, + 0x23, 0xd4, 0xaf, 0x8b, 0x6e, 0xb1, 0x29, 0x2a, 0x23, 0x6a, 0xe2, 0x96, 0x29, 0xc9, 0x08, 0x8f, 0xe5, 0x13, 0x12, + 0x42, 0x15, 0x83, 0x99, 0xd9, 0x70, 0x5f, 0xb9, 0x53, 0xd2, 0xa8, 0x88, 0x56, 0xba, 0xb9, 0x79, 0x7e, 0xf2, 0x3f, + 0xff, 0x07, 0x33, 0xa1, 0xc0, 0x7b, 0x11, 0x53, 0xe2, 0xd0, 0xac, 0x25, 0xa8, 0x4f, 0xf7, 0x84, 0x8c, 0xa5, 0xa2, + 0x50, 0x86, 0xe8, 0x91, 0x47, 0xab, 0x3c, 0x39, 0x92, 0x21, 0x1a, 0x31, 0x87, 0x92, 0x23, 0x23, 0x5f, 0x50, 0x4a, + 0xce, 0x13, 0x19, 0x13, 0xa5, 0xf3, 0xf7, 0xab, 0x6f, 0x9e, 0x74, 0x74, 0x0c, 0xa3, 0x36, 0x8b, 0x1e, 0x3e, 0x43, + 0xfb, 0x7b, 0x41, 0x87, 0x88, 0x16, 0x22, 0x3f, 0x72, 0xa0, 0x3f, 0x60, 0x9a, 0xb3, 0xf4, 0x3c, 0xea, 0xc7, 0xe9, + 0xe6, 0x65, 0x74, 0xda, 0x0b, 0x16, 0x31, 0xdb, 0xe5, 0x90, 0xdc, 0xad, 0xc3, 0x94, 0x9f, 0x32, 0x77, 0x61, 0xfa, + 0xba, 0xd4, 0x4b, 0x99, 0x56, 0x63, 0x72, 0xee, 0x6e, 0x69, 0x3d, 0x20, 0xc6, 0x2f, 0x30, 0xd6, 0x31, 0x85, 0xc7, + 0x60, 0xbf, 0x1a, 0x14, 0xb8, 0x2f, 0x93, 0xdb, 0x54, 0x91, 0xc0, 0x98, 0x63, 0xfb, 0xca, 0x30, 0xbe, 0xfa, 0x47, + 0x2f, 0x9d, 0x4e, 0xcd, 0x40, 0xbe, 0xfd, 0xea, 0xf0, 0xd4, 0xa2, 0xe9, 0x23, 0x9d, 0x2e, 0xb8, 0xa7, 0x66, 0x17, + 0xea, 0xd1, 0xf2, 0x28, 0x82, 0x37, 0xce, 0x19, 0xaf, 0x7b, 0x23, 0x20, 0xb0, 0x5a, 0xb1, 0x2f, 0xb8, 0x92, 0x80, + 0x23, 0xf5, 0xf4, 0x13, 0x6b, 0x28, 0x97, 0x0d, 0xdf, 0x67, 0x30, 0x57, 0x87, 0x76, 0xb8, 0x88, 0x2d, 0x89, 0x7e, + 0x70, 0xb2, 0x05, 0x7e, 0xd8, 0x63, 0x77, 0x59, 0xfa, 0xa8, 0x3e, 0x9d, 0xe6, 0x18, 0x61, 0x69, 0x2b, 0xb9, 0x6e, + 0xc4, 0x01, 0xc5, 0x83, 0x27, 0xf6, 0x1d, 0xe1, 0xbb, 0x6c, 0xa7, 0x06, 0xe6, 0x3b, 0xff, 0xc9, 0x10, 0xbd, 0x37, + 0x0f, 0xae, 0x53, 0xc3, 0x8c, 0x49, 0x44, 0x34, 0x79, 0x43, 0xa5, 0x9f, 0xa4, 0x27, 0x71, 0xe3, 0xa2, 0x45, 0x32, + 0x21, 0x4a, 0xf3, 0xb2, 0x69, 0xfc, 0x3b, 0x8f, 0xee, 0xba, 0x6b, 0x66, 0xdd, 0xea, 0x64, 0x08, 0x78, 0x96, 0xfa, + 0x1e, 0x56, 0x5e, 0x12, 0x58, 0x80, 0x97, 0x38, 0x3f, 0x8c, 0xc2, 0x52, 0x21, 0x9c, 0x00, 0x95, 0xc4, 0x44, 0x35, + 0x10, 0x3e, 0x7d, 0x1d, 0x4a, 0x9a, 0x8f, 0x18, 0x4b, 0x23, 0xcd, 0x9f, 0x51, 0xa5, 0x49, 0xcb, 0x0c, 0xda, 0x89, + 0xc0, 0xbb, 0xb3, 0x07, 0xfd, 0xb4, 0xa4, 0xd4, 0x41, 0xe5, 0x08, 0xea, 0x8b, 0x20, 0x07, 0x6f, 0x8a, 0x35, 0x71, + 0x10, 0xd6, 0x55, 0x61, 0x7a, 0xf6, 0x96, 0x0a, 0xfa, 0x1c, 0xdf, 0x56, 0x4b, 0x53, 0x4e, 0xaa, 0xda, 0x19, 0xb0, + 0xb3, 0x5f, 0xd0, 0x89, 0x6f, 0xd4, 0xa6, 0x52, 0xac, 0x07, 0xdc, 0x3b, 0x56, 0x95, 0xb2, 0x78, 0x80, 0xee, 0x5c, + 0xd9, 0x19, 0xc1, 0x6e, 0x10, 0x5b, 0xba, 0xcf, 0x27, 0x6c, 0x7f, 0x0f, 0x4d, 0xb9, 0x79, 0xd3, 0xa1, 0x96, 0xd8, + 0x52, 0x7e, 0xe2, 0x37, 0x9b, 0xb3, 0xe2, 0x7c, 0xbe, 0xff, 0xff, 0x00, 0x04, 0xdf, 0xdf, 0xf4, 0x2c, 0x6c, 0x03, + 0x00}; -} // namespace web_server -} // namespace esphome +#else // Brotli (default, smaller) +const uint8_t INDEX_BR[] PROGMEM = { + 0x5b, 0x2b, 0x6c, 0x53, 0x31, 0x36, 0x86, 0xd6, 0x0d, 0xad, 0x13, 0x60, 0xc6, 0xfb, 0x5a, 0xd7, 0x08, 0x1b, 0x45, + 0xb0, 0x71, 0x00, 0x41, 0x10, 0x63, 0x53, 0x09, 0xbb, 0x95, 0x19, 0x6a, 0x27, 0xa0, 0xa2, 0x2a, 0xf7, 0x9b, 0x74, + 0x02, 0xa8, 0x6a, 0xd6, 0x51, 0x19, 0x43, 0x13, 0x5d, 0x5a, 0x68, 0x01, 0x45, 0x70, 0xfb, 0xcf, 0xc8, 0xe0, 0xa5, + 0x52, 0x09, 0x12, 0x12, 0xc9, 0x8d, 0xc5, 0xd1, 0x07, 0x3a, 0x35, 0x6a, 0x31, 0x2e, 0x19, 0xec, 0x9e, 0x4f, 0xc0, + 0xac, 0x20, 0xb0, 0xb0, 0x9d, 0xf2, 0x9d, 0xd1, 0x1d, 0xaa, 0x76, 0x51, 0x95, 0x96, 0x2f, 0xc1, 0x38, 0x0d, 0x1b, + 0x75, 0xa3, 0xab, 0x38, 0x0f, 0xb4, 0x34, 0xb8, 0x3f, 0x94, 0x22, 0x9d, 0xf1, 0x6f, 0xec, 0xa0, 0x1d, 0x93, 0x72, + 0x4e, 0x24, 0x12, 0x9e, 0xe4, 0x8e, 0x1e, 0x9a, 0x78, 0x8b, 0x0a, 0x8d, 0xd0, 0x15, 0x7e, 0xbb, 0x75, 0xb4, 0xa8, + 0x6e, 0x1f, 0xe9, 0xbf, 0xc3, 0xbf, 0xe3, 0xf9, 0x7f, 0xaa, 0xf0, 0x1e, 0x15, 0xf5, 0xd2, 0x67, 0xac, 0x5f, 0x43, + 0x10, 0x36, 0x12, 0x0b, 0xc1, 0x9b, 0xc5, 0x6d, 0x91, 0x71, 0xe0, 0x3b, 0x38, 0x85, 0xbe, 0xcd, 0x60, 0x56, 0x0e, + 0x5f, 0x32, 0xe1, 0x5e, 0xc6, 0xbd, 0x18, 0x75, 0x38, 0xe9, 0x07, 0xa7, 0x15, 0xa6, 0xc3, 0x21, 0x3b, 0x6e, 0x86, + 0x4d, 0xad, 0x3f, 0x91, 0xcb, 0x00, 0xc7, 0x98, 0xb8, 0x1e, 0x16, 0xbd, 0x58, 0xe4, 0x90, 0x45, 0xfc, 0x71, 0xf4, + 0xab, 0xd7, 0xb4, 0xfa, 0xfa, 0x1d, 0xe0, 0x1c, 0x10, 0x94, 0x41, 0x6b, 0xf6, 0xce, 0x6b, 0xc6, 0xcb, 0x8f, 0xb3, + 0xbd, 0x0c, 0x2a, 0x55, 0xf5, 0xd0, 0x55, 0xa3, 0xe4, 0xcb, 0xbe, 0xcc, 0xc4, 0x92, 0x14, 0x82, 0xf7, 0xfe, 0xd2, + 0xea, 0xeb, 0x97, 0x54, 0x67, 0x9d, 0xd2, 0x52, 0x72, 0xba, 0x33, 0x77, 0xd1, 0x65, 0xfa, 0xba, 0xd3, 0xc7, 0x9d, + 0xf5, 0xf3, 0x60, 0x90, 0x09, 0x13, 0x22, 0x58, 0x49, 0x8e, 0x21, 0xa0, 0xb9, 0x6c, 0xed, 0xbf, 0xd3, 0x75, 0x8a, + 0xb1, 0xea, 0xf1, 0xd7, 0x75, 0x4e, 0xac, 0x8a, 0xec, 0x8a, 0xeb, 0x85, 0x89, 0xa1, 0x81, 0x68, 0xec, 0xf0, 0x93, + 0x06, 0x97, 0xbf, 0x98, 0xa6, 0xbe, 0xde, 0x52, 0x17, 0xb3, 0xc9, 0x8c, 0x0d, 0xd6, 0x63, 0x6c, 0x75, 0x31, 0x14, + 0xce, 0x82, 0xad, 0x00, 0x36, 0x80, 0xb3, 0x9b, 0xe8, 0xf2, 0x55, 0xed, 0xbf, 0x7e, 0x43, 0x3e, 0x0f, 0x66, 0xf9, + 0x28, 0x32, 0x58, 0xa8, 0xb8, 0x64, 0x98, 0x5a, 0x9c, 0x5a, 0x7c, 0x30, 0xb5, 0x14, 0x91, 0x83, 0x16, 0x0a, 0xb0, + 0x4c, 0xac, 0x08, 0x67, 0x69, 0x89, 0x7b, 0x7d, 0x89, 0x15, 0x26, 0x8b, 0x9c, 0xb6, 0xf7, 0xa3, 0xec, 0x97, 0x52, + 0xba, 0xf1, 0x6d, 0xb8, 0x43, 0x96, 0x2d, 0x6c, 0xd3, 0x07, 0x08, 0xd5, 0xb7, 0xfc, 0xaa, 0x75, 0xd4, 0x61, 0x7c, + 0xf3, 0x76, 0xe5, 0x90, 0x28, 0x60, 0x59, 0x55, 0x74, 0x8c, 0x3f, 0x05, 0xf1, 0x5a, 0xcb, 0x7a, 0xfd, 0x53, 0xf3, + 0x9e, 0x42, 0x22, 0x5e, 0xbf, 0x5c, 0x73, 0x1d, 0x26, 0x73, 0x98, 0x5f, 0x43, 0x37, 0x86, 0x8d, 0x80, 0x2b, 0xa8, + 0xeb, 0xc2, 0xff, 0xf7, 0x6f, 0xd3, 0xef, 0xeb, 0xd7, 0x50, 0x36, 0xd1, 0x46, 0x4b, 0xc8, 0x7e, 0x3d, 0x9f, 0xe3, + 0x6b, 0xf5, 0x30, 0xcc, 0x6e, 0x0a, 0xd1, 0x0e, 0x49, 0xc8, 0x00, 0x2e, 0x20, 0xd3, 0xbb, 0xda, 0xbe, 0xd9, 0xec, + 0xeb, 0x74, 0xa5, 0x98, 0x28, 0x78, 0x56, 0x4b, 0xb2, 0x65, 0x1b, 0x33, 0x53, 0x6c, 0x4a, 0xe7, 0xcd, 0xfc, 0xcc, + 0xdf, 0xde, 0x94, 0x26, 0xfb, 0x95, 0x93, 0xaa, 0xda, 0x58, 0xb5, 0x31, 0xf5, 0x1c, 0xec, 0x2b, 0x70, 0xc6, 0x01, + 0x74, 0xe1, 0xfd, 0xd7, 0x37, 0xb5, 0xff, 0xfa, 0x95, 0x88, 0x0e, 0x7b, 0x6e, 0x75, 0xa4, 0x20, 0xfe, 0xbc, 0xec, + 0x0d, 0xe1, 0x74, 0x5d, 0xec, 0x59, 0x05, 0x65, 0x41, 0xc4, 0xc9, 0xe8, 0xc1, 0xb0, 0x1f, 0x4a, 0x77, 0x09, 0x4c, + 0xc2, 0xe7, 0x7e, 0xa0, 0xce, 0xd6, 0x4c, 0x98, 0xc0, 0xec, 0x64, 0xc5, 0x36, 0xe6, 0x06, 0xfd, 0x90, 0x88, 0x33, + 0xff, 0xc1, 0x71, 0x11, 0xf5, 0x6b, 0xa6, 0xf5, 0x51, 0x7a, 0xd2, 0x39, 0x10, 0x2d, 0xb4, 0x26, 0x1b, 0xdd, 0xd3, + 0x7c, 0x9f, 0xbb, 0x6c, 0xbe, 0x82, 0xae, 0xa1, 0xb5, 0x43, 0x15, 0x4b, 0xab, 0x30, 0xe7, 0xb1, 0xd7, 0x46, 0xbd, + 0xff, 0xab, 0x6d, 0x66, 0xa4, 0x41, 0x2e, 0x4e, 0xf9, 0x90, 0xfb, 0x96, 0x86, 0xc2, 0xd2, 0xdb, 0x6c, 0xfd, 0x2c, + 0x85, 0x4c, 0x73, 0x33, 0xe8, 0x77, 0x20, 0xbf, 0xe1, 0x26, 0x89, 0x82, 0x0b, 0x14, 0xb6, 0x15, 0xb8, 0xf6, 0xfa, + 0x1a, 0x99, 0xae, 0x7a, 0xdf, 0x7e, 0x5e, 0xd0, 0x93, 0xa4, 0xc2, 0xc6, 0x92, 0x81, 0x99, 0xcc, 0xcc, 0x86, 0x10, + 0x4d, 0xd2, 0xc8, 0xc2, 0x68, 0x57, 0x48, 0x9c, 0x25, 0xd2, 0x0a, 0xfe, 0x9f, 0xd5, 0xb1, 0x61, 0xea, 0x86, 0x34, + 0x79, 0x42, 0x8b, 0xf2, 0x82, 0xe5, 0x6f, 0xaf, 0x96, 0x5f, 0xdf, 0xde, 0xf1, 0x87, 0x7c, 0x95, 0xf4, 0x2f, 0xa5, + 0x38, 0xbe, 0x74, 0x56, 0x93, 0xe0, 0xdd, 0xbb, 0x4e, 0xa0, 0xeb, 0x86, 0x36, 0xf0, 0x10, 0x20, 0xcf, 0x85, 0xc8, + 0xff, 0xdf, 0x5b, 0x69, 0xb9, 0xfd, 0x91, 0xae, 0x8a, 0x68, 0x07, 0x90, 0xe3, 0x0c, 0x47, 0xd6, 0xef, 0x3b, 0x2b, + 0x0b, 0xa0, 0x1a, 0x20, 0xd9, 0xe3, 0xcc, 0x4a, 0x5a, 0x6b, 0xb1, 0xa9, 0xb8, 0xf7, 0xbe, 0x77, 0x99, 0xdf, 0x45, + 0x67, 0xfc, 0x30, 0xac, 0x30, 0x99, 0x8d, 0x74, 0xd5, 0x2c, 0xdb, 0xc8, 0x72, 0x1a, 0x00, 0x04, 0xef, 0x7d, 0xff, + 0x47, 0xe1, 0xff, 0x1f, 0x59, 0xec, 0x1f, 0x91, 0x05, 0x9e, 0xc8, 0xac, 0xe2, 0x9c, 0xac, 0x02, 0x66, 0x54, 0x05, + 0x72, 0x46, 0x05, 0xb0, 0x8f, 0x0e, 0xc8, 0xb1, 0xf4, 0x9a, 0xa6, 0x3b, 0x1a, 0x52, 0xde, 0xef, 0xb4, 0x58, 0xa1, + 0x29, 0xef, 0x77, 0x3b, 0x50, 0xc6, 0xad, 0xa4, 0xa5, 0xb4, 0xd4, 0xbd, 0xba, 0xd7, 0xf5, 0xc2, 0x4e, 0x26, 0x2e, + 0x4d, 0x4e, 0x27, 0x65, 0x18, 0xb6, 0xfa, 0xee, 0x64, 0x90, 0x3e, 0xcb, 0x21, 0x39, 0x28, 0x17, 0xed, 0xa2, 0x5d, + 0xa6, 0x91, 0x78, 0x5b, 0x88, 0x87, 0x89, 0xc7, 0xbe, 0xda, 0x1a, 0xe3, 0xbf, 0xb1, 0x04, 0x4c, 0x2b, 0x5f, 0xb0, + 0x2c, 0xff, 0x6e, 0x7f, 0x54, 0xeb, 0x7b, 0x3d, 0x6a, 0xe9, 0xd7, 0x51, 0x77, 0xbb, 0xb8, 0x0a, 0xee, 0x10, 0x20, + 0x04, 0xba, 0xe7, 0x31, 0xd4, 0x9a, 0xfb, 0x6b, 0x1c, 0x88, 0xc8, 0x50, 0x4c, 0xda, 0xfe, 0xdd, 0x4b, 0xbf, 0xe5, + 0xb3, 0x07, 0x68, 0x01, 0xb5, 0x3d, 0xf2, 0xff, 0x6d, 0x92, 0x9d, 0xe1, 0x21, 0xd3, 0xea, 0xd0, 0xa6, 0xcb, 0xd7, + 0x37, 0x84, 0x10, 0x20, 0x90, 0x2e, 0xed, 0x6d, 0x93, 0xa1, 0x6b, 0x6c, 0xf5, 0x77, 0x81, 0x00, 0x29, 0x04, 0x78, + 0xe9, 0x31, 0xb4, 0xae, 0x92, 0x6a, 0x70, 0xdf, 0x19, 0x9f, 0xd4, 0x0d, 0x50, 0x6b, 0xac, 0xd3, 0x1a, 0x24, 0x25, + 0xd7, 0x75, 0x22, 0xb8, 0x7f, 0x36, 0x64, 0xe2, 0x9c, 0x71, 0x00, 0x7b, 0x3a, 0x8e, 0x68, 0x0b, 0x3a, 0x03, 0xe6, + 0x38, 0x77, 0xf0, 0x21, 0x1b, 0xc1, 0x74, 0xb4, 0x87, 0x92, 0xcb, 0x31, 0x0a, 0xb1, 0x07, 0x51, 0xe1, 0x85, 0xd5, + 0xa5, 0x0f, 0x4c, 0xfe, 0x4e, 0xaa, 0x18, 0xcc, 0xdd, 0xea, 0x13, 0x0e, 0x08, 0xb4, 0xde, 0x4b, 0x7e, 0x50, 0xe0, + 0x95, 0xdf, 0x47, 0xe8, 0x4c, 0x02, 0x25, 0x5f, 0x7e, 0x12, 0xb9, 0x11, 0xf3, 0x2d, 0x0f, 0xed, 0xec, 0xd8, 0x1d, + 0x3c, 0x38, 0xf0, 0x7e, 0x1e, 0xb4, 0xc7, 0x2f, 0xe4, 0x27, 0x5c, 0x96, 0x61, 0x4f, 0x63, 0x3d, 0xbf, 0x1a, 0xa3, + 0x29, 0x7a, 0xc9, 0x5a, 0x30, 0xf7, 0xdc, 0xe5, 0xa1, 0xd9, 0xfc, 0x60, 0xac, 0x91, 0x81, 0xdb, 0xb4, 0xb3, 0xb6, + 0xa5, 0x7e, 0x60, 0x97, 0x62, 0x01, 0x38, 0x09, 0x00, 0x64, 0x39, 0x6c, 0x3e, 0x36, 0x18, 0x05, 0xe1, 0xd7, 0xf2, + 0x66, 0x37, 0xde, 0x96, 0xfb, 0x61, 0x82, 0x8e, 0x64, 0x43, 0x0f, 0x08, 0xdb, 0xfb, 0xf0, 0xec, 0x67, 0x63, 0x00, + 0x65, 0xd4, 0xec, 0xc7, 0xcd, 0x2d, 0x74, 0x4d, 0x1b, 0x50, 0xc2, 0x1d, 0x59, 0xa8, 0xb2, 0xa7, 0x20, 0xb1, 0xa1, + 0xcb, 0xe0, 0xb9, 0xec, 0x3e, 0xef, 0xc7, 0xa4, 0xe6, 0x96, 0xac, 0xd3, 0x0d, 0x97, 0x06, 0x35, 0x1b, 0xde, 0x4f, + 0x9b, 0x54, 0x32, 0xe2, 0x36, 0xbd, 0xdf, 0xed, 0x8f, 0x4c, 0xee, 0x45, 0x33, 0xbf, 0xd1, 0x78, 0x3d, 0x4c, 0x81, + 0xbd, 0xf5, 0xe1, 0xf4, 0x41, 0xd0, 0x6c, 0x64, 0x36, 0xf1, 0x8d, 0xaf, 0x7a, 0x70, 0x48, 0x82, 0x44, 0xed, 0xdf, + 0xf7, 0xa0, 0x79, 0x11, 0x8e, 0xfa, 0x33, 0xef, 0x15, 0xea, 0x5b, 0x3f, 0xb2, 0x46, 0xcb, 0x37, 0x83, 0xdb, 0x5d, + 0x2e, 0x5f, 0xdf, 0x0e, 0x87, 0x83, 0x47, 0x5f, 0xc4, 0xf8, 0xf8, 0xa7, 0xcd, 0xcb, 0xcd, 0x8d, 0xa8, 0x0d, 0x6a, + 0x98, 0x86, 0x84, 0xa9, 0xe6, 0xf7, 0x7e, 0x89, 0x94, 0xfc, 0x3f, 0xdf, 0x93, 0x59, 0x9d, 0xfe, 0xf3, 0x37, 0x97, + 0xba, 0x02, 0xed, 0xd4, 0x33, 0xdf, 0xfc, 0x81, 0xf0, 0xf4, 0xaf, 0x54, 0x16, 0x58, 0x57, 0xe8, 0x7f, 0xd6, 0x37, + 0xe7, 0xdd, 0x4e, 0x08, 0xdb, 0x79, 0x98, 0x6f, 0x98, 0xba, 0x3d, 0x8f, 0xb9, 0xea, 0x93, 0x10, 0xe2, 0x44, 0x5b, + 0x1b, 0x84, 0x10, 0xd4, 0x8e, 0xf1, 0xa2, 0x19, 0x9c, 0xdb, 0x3d, 0x04, 0xd9, 0x6d, 0x6a, 0x2b, 0xad, 0x03, 0xdf, + 0xc2, 0x87, 0x03, 0x24, 0xd0, 0x5f, 0x60, 0x00, 0x69, 0xf1, 0x79, 0x77, 0xa9, 0x73, 0x0e, 0xbc, 0x06, 0x00, 0x22, + 0x87, 0x7a, 0x36, 0x9c, 0x0a, 0x4b, 0xe4, 0xc8, 0xd3, 0x78, 0x6e, 0xc8, 0x92, 0xa1, 0x47, 0x3e, 0xbd, 0xf7, 0x77, + 0xa8, 0xc3, 0x8b, 0x05, 0x76, 0xf8, 0xc8, 0xc1, 0x4d, 0xbd, 0xc2, 0x84, 0xbc, 0x38, 0x2b, 0x1e, 0x97, 0xa1, 0xd4, + 0x6a, 0x22, 0x2a, 0x71, 0xab, 0x1a, 0xe4, 0x3d, 0x47, 0x37, 0x52, 0xbe, 0x6a, 0x3b, 0x33, 0x73, 0x7d, 0x5b, 0x00, + 0xda, 0x73, 0x28, 0xf7, 0xb2, 0x74, 0xa9, 0x5b, 0x00, 0x82, 0xf4, 0xac, 0xef, 0xf7, 0xf9, 0x8d, 0x2f, 0x0b, 0x9d, + 0x8f, 0xda, 0x56, 0x50, 0x72, 0x45, 0x4d, 0x9e, 0x2a, 0xfb, 0x49, 0xb9, 0x97, 0xa5, 0x22, 0xfd, 0xa0, 0x23, 0x2f, + 0xc4, 0x89, 0x0c, 0x5a, 0xc1, 0x9e, 0x83, 0x89, 0x2b, 0x87, 0x79, 0x89, 0xbf, 0xac, 0x82, 0x45, 0x00, 0x1a, 0xe9, + 0x17, 0x44, 0xb3, 0x01, 0xaa, 0xa6, 0xe9, 0xcc, 0xc2, 0x71, 0xd4, 0xe2, 0x99, 0x36, 0xcc, 0xb9, 0xdf, 0xef, 0xf0, + 0xea, 0x94, 0x70, 0xd1, 0x8a, 0xd1, 0x02, 0x4d, 0xf0, 0xc3, 0xaf, 0xe4, 0xb0, 0x9a, 0x40, 0xeb, 0x8a, 0x99, 0x16, + 0x36, 0x65, 0x96, 0x69, 0xb0, 0xd0, 0x03, 0xbb, 0x43, 0x37, 0xcd, 0x2b, 0x7f, 0xc3, 0x97, 0xcf, 0x71, 0xb3, 0xc7, + 0xdd, 0x7b, 0xcb, 0x37, 0x32, 0xf7, 0x65, 0xe9, 0x01, 0xe5, 0xdd, 0x24, 0xf0, 0x94, 0x6e, 0xd7, 0x78, 0x37, 0x45, + 0x4f, 0x70, 0xff, 0xdc, 0x2b, 0x91, 0x3e, 0x1e, 0x6e, 0x43, 0xf4, 0x1a, 0x40, 0xe0, 0xbe, 0x50, 0xb6, 0x62, 0xea, + 0x4d, 0x80, 0x48, 0xee, 0xef, 0x72, 0xde, 0xc2, 0x47, 0xec, 0x5d, 0xca, 0x82, 0xc6, 0x4a, 0xd8, 0x93, 0xd6, 0xb7, + 0xaa, 0xf6, 0x80, 0x01, 0xc8, 0x5e, 0x85, 0x61, 0xd8, 0x1d, 0x43, 0xcc, 0x4e, 0xb2, 0x73, 0xdc, 0x17, 0x13, 0x96, + 0xf9, 0x83, 0xe6, 0xd0, 0x4d, 0xe5, 0x7d, 0xba, 0x93, 0xb1, 0x4a, 0x5b, 0x92, 0xca, 0x5d, 0xe3, 0xef, 0x08, 0x68, + 0x21, 0x5b, 0x0f, 0x87, 0x26, 0xbe, 0xc9, 0xbe, 0xe0, 0x3a, 0xe8, 0x09, 0x7a, 0x59, 0x76, 0x66, 0x5c, 0xaa, 0xc5, + 0xf2, 0xe6, 0xd1, 0x81, 0x40, 0xd8, 0xea, 0x88, 0xce, 0x06, 0xdb, 0x49, 0xef, 0x58, 0x8d, 0x86, 0xb1, 0xc5, 0xe0, + 0xff, 0xa1, 0x37, 0xf1, 0x52, 0xb6, 0xc8, 0x4d, 0xf5, 0x5c, 0xe6, 0x22, 0xf7, 0x7b, 0xfc, 0xbe, 0x9f, 0x01, 0x0e, + 0x16, 0x86, 0xa5, 0x8e, 0x40, 0xbf, 0x47, 0x4f, 0x6d, 0x15, 0x2c, 0x12, 0x6d, 0xee, 0x33, 0xed, 0x43, 0xf1, 0x2c, + 0x05, 0xf1, 0xa9, 0xcc, 0xdd, 0xa6, 0x3c, 0x70, 0x44, 0xad, 0xda, 0xa8, 0x42, 0x28, 0x41, 0x84, 0x60, 0xea, 0xa7, + 0x5c, 0xeb, 0xda, 0x54, 0x58, 0xed, 0xa2, 0x3c, 0x17, 0x86, 0x38, 0x74, 0x90, 0x83, 0x3a, 0x44, 0xe1, 0xff, 0xec, + 0x1e, 0xfa, 0x24, 0x67, 0x5c, 0x54, 0xdc, 0x13, 0x1c, 0x36, 0xee, 0x89, 0x32, 0x30, 0xf7, 0x97, 0x23, 0xcb, 0xee, + 0x01, 0xbd, 0x0f, 0x5a, 0x31, 0x28, 0xf9, 0xae, 0x56, 0x3c, 0xa3, 0xef, 0x41, 0x68, 0x04, 0x03, 0xf0, 0xcc, 0x37, + 0x83, 0x07, 0x16, 0xf0, 0xa8, 0x60, 0x50, 0x2a, 0xde, 0x69, 0xe8, 0xa6, 0x97, 0x40, 0x07, 0xc4, 0xb5, 0x7c, 0x3d, + 0x9c, 0x20, 0x58, 0x49, 0xc7, 0x42, 0x7b, 0x92, 0xe2, 0x64, 0x4e, 0xc8, 0xb0, 0x55, 0xa1, 0x3e, 0x09, 0x86, 0xf9, + 0x70, 0x50, 0x7e, 0x79, 0x79, 0x33, 0x20, 0x9e, 0xf5, 0x35, 0xf0, 0x9b, 0x4e, 0x40, 0xe0, 0xc7, 0x2e, 0x93, 0x54, + 0x8a, 0xa4, 0x1b, 0x2e, 0x53, 0x8a, 0x39, 0x4d, 0x9b, 0xeb, 0x4b, 0x0f, 0x39, 0xcd, 0x9d, 0x5a, 0xfa, 0x89, 0x3a, + 0xa0, 0x6d, 0xf1, 0x7b, 0x16, 0x3c, 0x57, 0x3f, 0x1c, 0x11, 0xec, 0xd6, 0x6a, 0x3b, 0xe6, 0xe6, 0xa5, 0xe5, 0x36, + 0x55, 0x4e, 0x36, 0xb4, 0xb7, 0x39, 0x5b, 0x08, 0x60, 0x8a, 0x69, 0x50, 0x2d, 0x2f, 0x92, 0xb9, 0x5e, 0x88, 0xd9, + 0x9f, 0x98, 0x49, 0x1e, 0x08, 0x5e, 0x39, 0x48, 0xa5, 0x0f, 0x6d, 0xe9, 0xb9, 0x2b, 0xc1, 0xf0, 0xbd, 0x96, 0x32, + 0xb0, 0x06, 0xb7, 0xb5, 0x65, 0x79, 0x0c, 0xab, 0x60, 0xca, 0x20, 0x90, 0xf5, 0xe7, 0xa0, 0xed, 0x11, 0x67, 0x8f, + 0x7a, 0xcc, 0xb0, 0x30, 0xfb, 0x7c, 0x0f, 0x1c, 0x52, 0x25, 0x0b, 0x41, 0x72, 0x58, 0xe8, 0xaf, 0xc5, 0x99, 0x69, + 0x82, 0x0a, 0x89, 0xd0, 0x6e, 0x15, 0x0f, 0xee, 0x3f, 0x48, 0x4f, 0x11, 0xb6, 0xd5, 0x5d, 0x4a, 0xb3, 0x6b, 0x64, + 0x31, 0x50, 0xd8, 0x58, 0x3d, 0x4c, 0x5c, 0x23, 0x84, 0xd3, 0xe6, 0xfa, 0x6e, 0xba, 0x48, 0x44, 0x97, 0x52, 0x44, + 0xb1, 0x71, 0xa3, 0x74, 0x09, 0x0f, 0x6b, 0x66, 0x2c, 0xc7, 0x26, 0x90, 0x9c, 0xb9, 0x63, 0x50, 0xc7, 0xb7, 0x1c, + 0x40, 0x02, 0xdc, 0x23, 0x86, 0xed, 0xc1, 0x24, 0x40, 0xae, 0x09, 0x12, 0x24, 0xb4, 0xcf, 0x07, 0x64, 0xc0, 0x82, + 0x8c, 0x8c, 0x89, 0x75, 0x23, 0x28, 0xb9, 0xfb, 0x3a, 0xea, 0x8f, 0x01, 0xce, 0xca, 0xc9, 0xda, 0x02, 0x11, 0x13, + 0xc7, 0x9b, 0x4a, 0x83, 0x80, 0xb1, 0x0c, 0xb0, 0x63, 0xe9, 0xa8, 0xe4, 0x5c, 0x1c, 0xc8, 0xd7, 0x43, 0x33, 0xbf, + 0x70, 0x70, 0x1e, 0x45, 0x1a, 0xd6, 0x2e, 0x20, 0x20, 0x67, 0x3e, 0xdd, 0xf6, 0x60, 0xe4, 0x20, 0x71, 0xad, 0xee, + 0xb4, 0x0c, 0x56, 0x11, 0xf9, 0xa4, 0x18, 0x37, 0x17, 0x65, 0x2a, 0xc4, 0xc5, 0x3a, 0xaf, 0x21, 0x17, 0x79, 0x70, + 0x1b, 0xbc, 0x73, 0xa3, 0x27, 0x5d, 0x27, 0x2c, 0x01, 0x58, 0x8f, 0xa5, 0x64, 0xc0, 0xad, 0x9a, 0x9e, 0xfd, 0x3a, + 0x48, 0x09, 0xbc, 0x7a, 0xe2, 0x39, 0xc4, 0xf1, 0x85, 0x11, 0xd5, 0xe0, 0xdf, 0xd6, 0x28, 0x51, 0xd8, 0x36, 0xcc, + 0x9a, 0x71, 0x5a, 0xc5, 0xb0, 0x67, 0x94, 0x67, 0x22, 0xe6, 0x2f, 0x19, 0x93, 0x46, 0xc2, 0x8a, 0xa7, 0x56, 0x4b, + 0x53, 0xe1, 0x33, 0x46, 0x28, 0x44, 0xec, 0x11, 0x68, 0x0a, 0xec, 0x7b, 0x43, 0x62, 0xca, 0x6f, 0x7a, 0x4e, 0xdc, + 0x9e, 0xac, 0xb3, 0x39, 0x1b, 0x66, 0x88, 0x5d, 0x04, 0x74, 0xbb, 0xd5, 0x3a, 0x5c, 0xd9, 0xa7, 0xd2, 0x68, 0x4a, + 0x36, 0xa8, 0x25, 0x98, 0x08, 0x93, 0xe1, 0x79, 0x76, 0x81, 0xc8, 0x7b, 0x4d, 0x33, 0x93, 0x2e, 0xf4, 0x6a, 0xfa, + 0x13, 0x40, 0x1e, 0xf7, 0xe4, 0x52, 0x69, 0xca, 0x72, 0x85, 0x03, 0x90, 0xdb, 0x05, 0xf3, 0x38, 0x31, 0xa5, 0x56, + 0x25, 0x37, 0xee, 0xb8, 0x98, 0x49, 0xcd, 0x74, 0x77, 0xb1, 0x39, 0x21, 0x01, 0x12, 0xd5, 0x92, 0xd9, 0xe0, 0x35, + 0xa0, 0x78, 0x4f, 0x00, 0x91, 0x88, 0x46, 0xe1, 0x3b, 0xbf, 0x96, 0xa3, 0x73, 0x26, 0x41, 0x5b, 0x98, 0xab, 0xdb, + 0x79, 0xf1, 0xb9, 0x4a, 0x54, 0xb9, 0xe1, 0x68, 0x07, 0xb0, 0x45, 0xfb, 0xa2, 0xf0, 0x79, 0x04, 0x7e, 0xfa, 0xb8, + 0x30, 0xe8, 0xd6, 0xd4, 0x31, 0x45, 0x3d, 0x9b, 0xdd, 0x65, 0x58, 0xe0, 0x35, 0x4e, 0x99, 0xe8, 0xf9, 0x6f, 0xd5, + 0xdd, 0x10, 0x75, 0xfe, 0x3a, 0x76, 0xc0, 0x56, 0xdb, 0xec, 0x1c, 0xce, 0xe7, 0xfc, 0x59, 0x9c, 0x1c, 0x30, 0xab, + 0x3a, 0x5b, 0x3b, 0x3f, 0xf6, 0xcb, 0xbf, 0x9c, 0xfc, 0x41, 0xc6, 0x02, 0xc8, 0xab, 0x02, 0xa9, 0xc8, 0xc8, 0x74, + 0x40, 0x89, 0x97, 0x96, 0xcf, 0xc5, 0x00, 0x2d, 0x32, 0xaf, 0x5a, 0x32, 0x14, 0x92, 0xd5, 0xb0, 0xf2, 0x06, 0x10, + 0x64, 0x0b, 0x33, 0xd7, 0x28, 0x38, 0x42, 0x1e, 0xb5, 0xd8, 0x98, 0x37, 0x2c, 0xa7, 0xa2, 0x58, 0xa6, 0xad, 0x39, + 0xb2, 0x08, 0x5a, 0x1c, 0x10, 0xd7, 0x5f, 0xa0, 0x3a, 0x1a, 0xd4, 0x95, 0x37, 0xbb, 0x11, 0x06, 0xd8, 0x0d, 0x37, + 0x92, 0x4d, 0x8c, 0xbd, 0x12, 0x8a, 0x9c, 0x06, 0xd2, 0xd8, 0x4f, 0xde, 0x49, 0xf8, 0xed, 0xae, 0x1d, 0xe6, 0xbe, + 0x3c, 0x9c, 0x99, 0x4c, 0x0c, 0x29, 0xed, 0x86, 0x83, 0x40, 0xd4, 0xcf, 0x16, 0x8a, 0x29, 0xa7, 0xb5, 0xc7, 0x70, + 0xd7, 0x75, 0x2e, 0xc9, 0xdb, 0xc7, 0x3c, 0x10, 0x90, 0xdf, 0x09, 0xac, 0x01, 0x71, 0x14, 0x51, 0x04, 0xc2, 0x7e, + 0xd6, 0xdf, 0x1c, 0x47, 0x7e, 0x1b, 0x60, 0xc9, 0xae, 0x87, 0xba, 0xa5, 0x6c, 0x31, 0x23, 0x6b, 0x79, 0xe5, 0x14, + 0x54, 0x5c, 0x7a, 0x94, 0x33, 0x0f, 0xc4, 0x1b, 0x44, 0x0d, 0x96, 0xb3, 0xcf, 0xdb, 0x85, 0x03, 0x95, 0x14, 0xb2, + 0xf7, 0xd3, 0x20, 0x2f, 0x9e, 0x5f, 0x38, 0xab, 0xe5, 0x14, 0xd3, 0x21, 0xd5, 0x36, 0xca, 0x84, 0x71, 0xc5, 0xde, + 0x06, 0x5e, 0x9c, 0x78, 0x78, 0x78, 0xb8, 0xf5, 0x40, 0x40, 0xd6, 0x3a, 0x27, 0x72, 0x33, 0x8b, 0xce, 0xf7, 0x27, + 0x91, 0xce, 0xda, 0xf2, 0x90, 0x97, 0xf3, 0xa2, 0x0d, 0xb2, 0xe8, 0x96, 0x98, 0x85, 0xb1, 0x61, 0x03, 0xcb, 0xdd, + 0xb7, 0xf1, 0x11, 0x87, 0x4c, 0x52, 0xb6, 0xa7, 0xd9, 0x76, 0x6f, 0xb8, 0xf9, 0x2e, 0x9a, 0x68, 0x99, 0x2c, 0x4c, + 0xd7, 0xee, 0x17, 0xc9, 0x6a, 0x2f, 0x45, 0xb5, 0x64, 0x3e, 0x86, 0xbd, 0x73, 0x47, 0x6a, 0x2f, 0x1d, 0x54, 0x9d, + 0x46, 0x33, 0x8e, 0x73, 0x02, 0xda, 0x28, 0x98, 0x5d, 0xe1, 0xc5, 0x20, 0xbb, 0x48, 0x61, 0x99, 0x95, 0xc2, 0x66, + 0x0c, 0x4b, 0xa9, 0xfb, 0x41, 0xa2, 0xb3, 0x0c, 0x31, 0x68, 0xd2, 0xe7, 0xc6, 0x1e, 0x8d, 0xe2, 0x11, 0xaa, 0x08, + 0x6b, 0xf7, 0xc7, 0x09, 0x0a, 0xa9, 0xce, 0xf2, 0x04, 0x43, 0x7b, 0x3e, 0x42, 0xc7, 0x05, 0xcc, 0x2f, 0x67, 0xa2, + 0xde, 0xbf, 0xdc, 0x3c, 0x16, 0x5e, 0x7d, 0x48, 0x71, 0x4d, 0xe9, 0xad, 0x7c, 0x65, 0x3e, 0x67, 0x94, 0x67, 0x86, + 0xce, 0x28, 0x15, 0x96, 0xd9, 0x5c, 0x88, 0x91, 0x24, 0xcd, 0x87, 0xf9, 0x66, 0x80, 0xcd, 0x10, 0x94, 0xc8, 0x50, + 0xdc, 0x18, 0xc5, 0x62, 0x0c, 0x63, 0x0d, 0x28, 0x77, 0x8b, 0x66, 0x4e, 0x1a, 0xcd, 0x9d, 0x4c, 0x72, 0xc3, 0x77, + 0x3f, 0x55, 0x23, 0x4c, 0x53, 0x88, 0x5d, 0xc7, 0x17, 0x35, 0x59, 0x39, 0x55, 0xc3, 0x2d, 0xae, 0x9c, 0x22, 0x68, + 0x99, 0xc5, 0x7d, 0x9a, 0xbb, 0xc4, 0x04, 0xb0, 0x15, 0xd8, 0x9d, 0xaa, 0xa8, 0x54, 0xd0, 0x87, 0x4e, 0x37, 0x77, + 0x30, 0x9c, 0xd8, 0x86, 0x48, 0xd6, 0x32, 0x74, 0x10, 0x6d, 0xff, 0x3e, 0xc8, 0xe8, 0xd0, 0xf1, 0xdb, 0xd5, 0x98, + 0x0a, 0x81, 0x9a, 0x45, 0x5c, 0xd9, 0x0c, 0x69, 0x52, 0x88, 0x6b, 0x5c, 0x9e, 0x08, 0xe2, 0xbc, 0x8f, 0xb5, 0x06, + 0xf2, 0x76, 0x90, 0xb9, 0x86, 0x75, 0xd9, 0x80, 0x68, 0xf4, 0x42, 0x54, 0x7e, 0xdd, 0xa0, 0x3d, 0x68, 0xc2, 0x64, + 0x1e, 0x0c, 0xc4, 0x3c, 0x47, 0x6d, 0xd6, 0xc8, 0x58, 0x65, 0xe0, 0x08, 0x94, 0x23, 0xf3, 0xd2, 0x58, 0x3f, 0xb5, + 0x4b, 0xf4, 0x86, 0xc2, 0xc0, 0x76, 0xad, 0x96, 0x92, 0x01, 0x37, 0xc6, 0x82, 0xbb, 0x31, 0xe9, 0x54, 0x41, 0x3d, + 0x90, 0x3d, 0xeb, 0x1b, 0x18, 0x4f, 0x89, 0x09, 0x1f, 0x1c, 0x70, 0x34, 0xdf, 0x24, 0xbd, 0x74, 0x4d, 0xb4, 0xa2, + 0x95, 0x85, 0xf8, 0x73, 0x85, 0xd2, 0x56, 0x8d, 0x0e, 0x7c, 0x05, 0x60, 0x00, 0x99, 0x0a, 0x2a, 0x51, 0x25, 0xe5, + 0x50, 0x61, 0x3c, 0x68, 0xca, 0x75, 0x35, 0x56, 0xde, 0xb9, 0x77, 0xc3, 0xc3, 0x9f, 0xde, 0x60, 0xa4, 0x75, 0x57, + 0xf8, 0x30, 0xd9, 0x5f, 0x45, 0x48, 0x11, 0xf6, 0xcc, 0xd8, 0xc1, 0xc6, 0x3c, 0x73, 0xe4, 0xd3, 0x6b, 0xa7, 0x36, + 0xdb, 0xbe, 0xba, 0x70, 0xc6, 0x2c, 0x57, 0xef, 0x11, 0xc9, 0xd8, 0x2a, 0x02, 0x1a, 0xdd, 0xe6, 0xeb, 0xe1, 0x24, + 0x0d, 0x7f, 0x2c, 0x52, 0xfc, 0xbf, 0xaa, 0x20, 0xaa, 0x40, 0x0b, 0x69, 0x0e, 0x7a, 0x0e, 0x59, 0x83, 0x4d, 0x49, + 0x23, 0x07, 0x5b, 0x7b, 0x60, 0x13, 0x13, 0x9c, 0x45, 0x8c, 0x76, 0x91, 0x89, 0x11, 0x3c, 0x83, 0xfa, 0x4b, 0xab, + 0x10, 0x3a, 0x47, 0x60, 0xaa, 0x5d, 0x2c, 0x7d, 0xd1, 0x64, 0x1c, 0x40, 0xde, 0xbd, 0x7f, 0x87, 0xf8, 0x91, 0xd4, + 0xb0, 0xb9, 0xf2, 0xdf, 0xfc, 0x55, 0x14, 0x31, 0x2a, 0x47, 0xc2, 0x54, 0x48, 0x68, 0x5d, 0x67, 0xc5, 0xc0, 0xc7, + 0x16, 0xb1, 0x86, 0x59, 0x7e, 0xb2, 0xde, 0xe0, 0x4a, 0x10, 0xb2, 0x8b, 0x40, 0xe3, 0x0c, 0x41, 0x46, 0x95, 0x27, + 0x46, 0xc1, 0x3b, 0x04, 0x70, 0x4b, 0xd0, 0x8e, 0xc1, 0x34, 0xb7, 0x2e, 0x12, 0x5a, 0x8c, 0x25, 0xf5, 0x01, 0x93, + 0x91, 0x90, 0xe2, 0x67, 0xe5, 0xb0, 0x60, 0x5e, 0x18, 0x4d, 0xc5, 0x87, 0x2a, 0x3a, 0x8a, 0xdb, 0xf0, 0x82, 0xd9, + 0xea, 0xa3, 0xa8, 0xd4, 0x46, 0x9d, 0xe6, 0x65, 0x73, 0x8e, 0x68, 0x3b, 0xd7, 0xdd, 0xbc, 0x13, 0x20, 0xee, 0x84, + 0x96, 0x30, 0x2e, 0x43, 0xaa, 0x5b, 0x96, 0x96, 0x62, 0x1d, 0x2e, 0x72, 0xc9, 0xfc, 0xae, 0x21, 0xde, 0x7f, 0x15, + 0xe2, 0xd3, 0x63, 0x81, 0xda, 0xce, 0x8f, 0x2e, 0x84, 0x12, 0x98, 0x05, 0x8d, 0xd5, 0x3c, 0x86, 0x62, 0x1d, 0x09, + 0xd3, 0x61, 0xc2, 0xd0, 0x1a, 0x6f, 0x54, 0x2b, 0x1e, 0xa9, 0xac, 0x07, 0x0b, 0xef, 0x49, 0x0c, 0x5b, 0x2d, 0xe9, + 0x72, 0xd6, 0x75, 0x52, 0xd9, 0xef, 0x97, 0xc8, 0xe4, 0xe9, 0xfc, 0xd8, 0x4f, 0x6a, 0x17, 0x15, 0x27, 0xa3, 0x73, + 0xed, 0x79, 0x7f, 0x24, 0xe6, 0xc0, 0x63, 0x6c, 0x1b, 0xc8, 0x38, 0x4b, 0x67, 0x2c, 0xe5, 0x20, 0x07, 0xc3, 0x62, + 0x9f, 0xf9, 0x9d, 0x70, 0xf2, 0x4b, 0x5e, 0x21, 0x26, 0x70, 0x23, 0xc2, 0x80, 0x3a, 0x75, 0x6f, 0x84, 0x4c, 0x38, + 0xf4, 0x3f, 0x93, 0xdb, 0xbe, 0x47, 0x7c, 0x4b, 0xbe, 0x98, 0x19, 0x70, 0xbe, 0xb1, 0x59, 0x0c, 0xc5, 0xdd, 0xc9, + 0xc8, 0xa6, 0xc4, 0x85, 0x84, 0xfa, 0x98, 0x99, 0x67, 0xb7, 0xd9, 0x5f, 0x47, 0xf0, 0x26, 0x3f, 0xae, 0x89, 0x9f, + 0xca, 0x5c, 0xff, 0x6d, 0xe2, 0x6f, 0x19, 0xc2, 0x26, 0x6f, 0x54, 0x20, 0x6b, 0x07, 0x59, 0x10, 0x49, 0xdc, 0xca, + 0x1b, 0x5d, 0xef, 0xf1, 0x8d, 0x26, 0x6e, 0x22, 0x53, 0x72, 0x2d, 0x38, 0x67, 0x8e, 0x63, 0x75, 0x6f, 0x1c, 0x96, + 0x1a, 0x9f, 0x18, 0xff, 0x48, 0x2f, 0x82, 0xe5, 0x8d, 0xaf, 0x2f, 0x5e, 0x64, 0x7e, 0x51, 0xa2, 0x4e, 0xb0, 0xc8, + 0x78, 0x1e, 0x26, 0x06, 0x3d, 0x8d, 0x54, 0xcb, 0xbf, 0x94, 0x80, 0x6e, 0xc4, 0x32, 0xef, 0x78, 0xe4, 0x01, 0x5f, + 0x00, 0x9b, 0x05, 0x87, 0xe4, 0xbd, 0xc7, 0x90, 0xf4, 0x4d, 0x26, 0x67, 0x53, 0x8e, 0x63, 0xad, 0xec, 0x56, 0x0b, + 0xd0, 0xfd, 0xb4, 0xfa, 0x67, 0x7b, 0x41, 0xbe, 0x9e, 0x24, 0x67, 0x15, 0xdc, 0xa2, 0x35, 0x6b, 0xa4, 0x58, 0x26, + 0xd7, 0x87, 0xf1, 0xb7, 0xc6, 0x87, 0x31, 0x09, 0x52, 0xb4, 0xff, 0x4a, 0xda, 0x88, 0x10, 0x5a, 0x56, 0xbf, 0x50, + 0x96, 0x39, 0xb3, 0xd0, 0x7b, 0x01, 0xca, 0x24, 0x62, 0x0f, 0xf6, 0x6a, 0xb6, 0x1e, 0xc6, 0xdb, 0xd1, 0xc4, 0xad, + 0xab, 0xe3, 0x98, 0x9c, 0x93, 0x11, 0xf7, 0xeb, 0x82, 0xec, 0x7a, 0x8f, 0x34, 0x9b, 0x76, 0xf9, 0xa7, 0x18, 0x6d, + 0xbf, 0x5a, 0xa8, 0x85, 0xb6, 0xbe, 0xb6, 0xa5, 0x17, 0x28, 0xb0, 0xfc, 0xd5, 0x68, 0x33, 0xae, 0x08, 0x9d, 0x5b, + 0xab, 0xfc, 0xfc, 0x73, 0x98, 0x09, 0x93, 0xa2, 0xf1, 0x9f, 0x76, 0xff, 0xbb, 0xeb, 0x98, 0xa0, 0x48, 0x76, 0x4d, + 0x6e, 0x2f, 0xed, 0x66, 0x9a, 0xee, 0x06, 0x59, 0x31, 0x12, 0xc7, 0xdb, 0x5f, 0xdf, 0xfd, 0xfa, 0xd7, 0x44, 0x90, + 0x9f, 0xb8, 0x2d, 0xce, 0xa4, 0x2e, 0xf6, 0xff, 0xe7, 0xaf, 0xcb, 0xdb, 0x97, 0x3f, 0xa4, 0xea, 0x2b, 0x7a, 0x7e, + 0x91, 0xa5, 0x69, 0x3f, 0xaa, 0x7d, 0x84, 0x63, 0xb0, 0x6d, 0x02, 0x41, 0x90, 0x2f, 0x9a, 0xf4, 0xa7, 0xc3, 0xf8, + 0x4e, 0xc3, 0xec, 0x33, 0xe5, 0xde, 0xe0, 0xa5, 0xfb, 0xab, 0x7e, 0x39, 0x1d, 0xb6, 0xb4, 0xa1, 0x6a, 0x2d, 0x6f, + 0xfe, 0x5a, 0xa7, 0x9c, 0xe9, 0x46, 0x39, 0xff, 0xbc, 0xec, 0xaf, 0x06, 0x93, 0xeb, 0x7e, 0x59, 0x4f, 0x04, 0xbb, + 0x13, 0x3a, 0x51, 0x4d, 0x87, 0x74, 0x4c, 0xac, 0x6a, 0x0f, 0xc7, 0x8b, 0xec, 0x24, 0xab, 0xe8, 0x95, 0x5f, 0xc3, + 0xc9, 0x6f, 0xac, 0x8e, 0xea, 0xa9, 0x20, 0x2e, 0x98, 0x8b, 0x40, 0xe2, 0xb9, 0xfe, 0x3b, 0x98, 0xeb, 0xa1, 0xf7, + 0xaa, 0xeb, 0x4c, 0xd3, 0x0b, 0x3c, 0xec, 0xaa, 0x26, 0x50, 0xfb, 0xf6, 0xb6, 0x22, 0xb6, 0x19, 0xc7, 0x81, 0x0f, + 0xb1, 0x68, 0xe7, 0x98, 0x85, 0x42, 0xc4, 0xb3, 0x7e, 0x17, 0x66, 0x0d, 0xae, 0xef, 0xdf, 0xbe, 0x12, 0x5f, 0x3e, + 0xdd, 0x30, 0x3c, 0x00, 0x97, 0x13, 0xef, 0x49, 0xfb, 0x17, 0xaa, 0xf4, 0x84, 0xa4, 0x4e, 0xae, 0xac, 0x55, 0x8f, + 0x99, 0x7d, 0x1f, 0x0d, 0x6f, 0x18, 0x98, 0xf9, 0x78, 0xdf, 0x07, 0x79, 0x56, 0x91, 0x9e, 0xe3, 0x97, 0x69, 0x0e, + 0x04, 0x28, 0xa2, 0x00, 0x69, 0x90, 0xaf, 0x92, 0x1c, 0xbe, 0x26, 0x1e, 0x1e, 0x3c, 0xb7, 0xb8, 0xfa, 0xdc, 0x80, + 0x64, 0x0d, 0x57, 0x6b, 0x63, 0x75, 0x0a, 0x50, 0x12, 0x6b, 0x87, 0x80, 0xf6, 0x3f, 0xbd, 0x70, 0x05, 0x5b, 0x6f, + 0xe2, 0x96, 0x83, 0xa3, 0x3a, 0x35, 0x69, 0xf6, 0x16, 0x46, 0xb6, 0x16, 0xde, 0x74, 0x74, 0xc2, 0x43, 0x8e, 0x71, + 0xba, 0xa2, 0x12, 0xd3, 0xe3, 0xce, 0xdc, 0xe3, 0xe0, 0x7f, 0x2e, 0x21, 0x11, 0x99, 0x36, 0xe9, 0x1b, 0x16, 0x55, + 0xa5, 0xd5, 0x0a, 0x6e, 0xf6, 0x2a, 0xd1, 0x4b, 0x98, 0x66, 0xfc, 0xbe, 0xc9, 0xe4, 0x02, 0x78, 0x9d, 0xe1, 0x7e, + 0xfc, 0x79, 0x7f, 0x31, 0xe7, 0x11, 0xc8, 0xc5, 0x6b, 0x01, 0x12, 0x68, 0xc0, 0xfa, 0xba, 0x0c, 0x71, 0xc9, 0x84, + 0x51, 0x2c, 0x56, 0xe4, 0x3c, 0xde, 0x2a, 0x42, 0xe3, 0xab, 0x4a, 0x6a, 0xac, 0x7a, 0x81, 0x6e, 0x8a, 0xd9, 0x76, + 0xfe, 0x77, 0x9b, 0x4a, 0x59, 0x0f, 0x7b, 0x80, 0xfb, 0xe7, 0x01, 0xf4, 0x1d, 0x6d, 0xce, 0x26, 0xf5, 0xe1, 0xef, + 0x54, 0xa2, 0x80, 0xd6, 0x0e, 0x3d, 0x2c, 0x39, 0x7f, 0x14, 0xea, 0x3f, 0x01, 0xba, 0x00, 0x78, 0xcd, 0xb7, 0x72, + 0xa8, 0x02, 0xd0, 0x1e, 0x35, 0x4d, 0x99, 0x47, 0xad, 0x53, 0xa1, 0x2c, 0x7c, 0x7d, 0xcd, 0x1d, 0x2e, 0x7f, 0x32, + 0x90, 0x5f, 0x6d, 0xe1, 0x14, 0x48, 0x82, 0x27, 0x0a, 0xe2, 0xb0, 0xf2, 0xf3, 0xe3, 0xfd, 0xc7, 0x65, 0x9a, 0x82, + 0x66, 0xfa, 0x42, 0xe1, 0xfa, 0x71, 0xc9, 0x5f, 0x58, 0xf6, 0x71, 0xbd, 0x84, 0xf6, 0xc0, 0xc3, 0xfb, 0x7e, 0x5c, + 0xf3, 0xcb, 0xd9, 0xd5, 0x3c, 0xdf, 0x2d, 0x7b, 0xf3, 0xdc, 0xad, 0x91, 0xb8, 0xca, 0x1c, 0xc0, 0xd2, 0x89, 0x52, + 0xc7, 0xef, 0x33, 0x31, 0x55, 0x69, 0xfc, 0xbe, 0x50, 0xa3, 0x8e, 0x1e, 0x55, 0x79, 0x7a, 0xb8, 0x0a, 0x16, 0x59, + 0x87, 0x7f, 0x2e, 0x05, 0xbe, 0xfc, 0x0f, 0x38, 0xc9, 0x9f, 0x52, 0xff, 0x24, 0x17, 0x67, 0xc2, 0x66, 0xe1, 0xb1, + 0x77, 0xc5, 0xc8, 0x1a, 0x76, 0xd1, 0x3a, 0x7c, 0xee, 0xb0, 0xcf, 0x2b, 0x33, 0xdd, 0x66, 0xd0, 0xb3, 0xe4, 0xaa, + 0xa0, 0x68, 0xb5, 0xd0, 0xc5, 0xcb, 0x3c, 0x5c, 0x11, 0xd3, 0xda, 0xf1, 0x22, 0xd8, 0x14, 0xda, 0x17, 0x8d, 0xa7, + 0x37, 0xe7, 0x6a, 0x28, 0x93, 0x90, 0xde, 0xc9, 0xcc, 0xcc, 0xbc, 0xbf, 0x66, 0x36, 0x5f, 0xb8, 0xce, 0x25, 0xa6, + 0x30, 0xa0, 0xdf, 0x51, 0x50, 0xc2, 0xd5, 0x57, 0x2c, 0x56, 0xb4, 0xbc, 0x77, 0x64, 0x75, 0xdd, 0x3b, 0x5e, 0xdd, + 0x05, 0xa2, 0xdc, 0xbf, 0x59, 0xaa, 0x00, 0x5e, 0x18, 0xce, 0x36, 0xc2, 0xdc, 0x15, 0xc4, 0x5a, 0x44, 0x1a, 0x8c, + 0x1e, 0xe4, 0xbb, 0x9b, 0x49, 0x2f, 0xa8, 0x53, 0x2e, 0xef, 0x7f, 0x7a, 0x73, 0xae, 0x5a, 0x70, 0x7a, 0xf1, 0xe3, + 0xeb, 0x75, 0x4a, 0xb0, 0xba, 0x16, 0x6a, 0xf2, 0x26, 0x35, 0x4e, 0xb9, 0x3d, 0x0c, 0x90, 0x41, 0x43, 0x17, 0x2d, + 0xef, 0x43, 0x69, 0x5c, 0x6c, 0x5f, 0x63, 0xdd, 0xf7, 0x0d, 0x0a, 0x51, 0x57, 0x91, 0xf7, 0x0c, 0x83, 0xfb, 0x81, + 0x4a, 0x11, 0x97, 0x97, 0xf2, 0xa8, 0x6c, 0xb5, 0xd8, 0x92, 0x87, 0x89, 0xb3, 0x40, 0xfd, 0xcd, 0x86, 0xa7, 0x3d, + 0xae, 0x75, 0xc0, 0xd7, 0x2b, 0x09, 0xbe, 0xcc, 0x0d, 0x35, 0x02, 0x3a, 0x48, 0x91, 0x70, 0x30, 0x1b, 0x66, 0xd4, + 0x24, 0x9c, 0x66, 0x7d, 0x4b, 0x79, 0xc3, 0x64, 0xb0, 0xa9, 0x4f, 0x74, 0x61, 0x95, 0x6f, 0x3b, 0x6c, 0xd9, 0xe9, + 0x1d, 0x9c, 0xa9, 0x67, 0xcd, 0x7b, 0xeb, 0x51, 0xdb, 0x4e, 0xd3, 0x86, 0x84, 0x8b, 0xf4, 0xbd, 0xd8, 0x6f, 0x9d, + 0x9f, 0x42, 0xcb, 0xc9, 0xc5, 0x23, 0xc8, 0x54, 0x70, 0x8e, 0xc8, 0xad, 0x9f, 0xeb, 0x3f, 0x95, 0x31, 0x10, 0xf2, + 0x83, 0x3f, 0x4b, 0xf0, 0xa5, 0x1d, 0x01, 0x48, 0xc4, 0x87, 0x64, 0xf1, 0xb5, 0x5b, 0x42, 0x91, 0x85, 0xae, 0xd8, + 0x7b, 0x6e, 0x2c, 0x98, 0x89, 0x43, 0x52, 0x01, 0xfa, 0x9e, 0x59, 0xfb, 0xef, 0xd8, 0x18, 0x8b, 0x44, 0xd7, 0xf4, + 0x16, 0xdc, 0x65, 0xca, 0x93, 0xfb, 0xea, 0xb6, 0x4b, 0xe0, 0xab, 0x5b, 0x0c, 0xc5, 0x2d, 0x16, 0x60, 0x87, 0xd5, + 0x6d, 0xbc, 0xe9, 0x06, 0xd6, 0xb7, 0xd6, 0x19, 0x53, 0x06, 0x48, 0x37, 0x89, 0x6b, 0x41, 0x30, 0x44, 0xbd, 0x48, + 0x5a, 0x51, 0xfe, 0x40, 0xed, 0x12, 0x4e, 0x7a, 0x05, 0xc7, 0x47, 0xb7, 0x6e, 0x7f, 0xd7, 0x00, 0x8f, 0xfe, 0x5f, + 0xf5, 0xe1, 0xc9, 0xce, 0x8a, 0x2c, 0x4d, 0x53, 0xb1, 0x06, 0x34, 0x39, 0xb8, 0x04, 0xe2, 0xa6, 0x71, 0xed, 0xc3, + 0xfe, 0x29, 0xe5, 0x5b, 0x07, 0xd4, 0x3c, 0x33, 0x42, 0xb5, 0xf5, 0x14, 0xbe, 0xac, 0x2e, 0xb5, 0xb1, 0x90, 0x78, + 0xf9, 0xb2, 0xde, 0x7b, 0xe0, 0xf9, 0xe1, 0x97, 0x3d, 0x16, 0x94, 0x6b, 0x79, 0x96, 0xfe, 0xa3, 0xce, 0x70, 0x19, + 0xe0, 0x01, 0x8d, 0x2d, 0x21, 0xb9, 0xb2, 0xc1, 0xfb, 0xef, 0x21, 0xd4, 0xf9, 0xf0, 0x58, 0x6a, 0x9f, 0x95, 0x16, + 0x51, 0xb0, 0xf6, 0x01, 0x42, 0xdb, 0x10, 0x63, 0xd8, 0xe1, 0x82, 0x8f, 0x3e, 0xba, 0xcd, 0xb7, 0x54, 0x0e, 0x94, + 0xba, 0x72, 0xd5, 0xc7, 0x73, 0x00, 0x5e, 0x6b, 0x7f, 0xc7, 0x3a, 0x6d, 0xe3, 0x1a, 0x9f, 0xdd, 0xd8, 0x9e, 0x5a, + 0xd6, 0xca, 0x6d, 0x34, 0x2a, 0xe2, 0xaa, 0x8a, 0xe8, 0x73, 0xf1, 0xc9, 0xda, 0x45, 0x6d, 0xf9, 0x14, 0x60, 0x71, + 0x6b, 0xd9, 0xa6, 0xe3, 0x74, 0x75, 0xc9, 0x47, 0x2e, 0xd0, 0x19, 0xec, 0x0b, 0x16, 0x80, 0x9b, 0x43, 0x09, 0x0d, + 0x07, 0x18, 0x81, 0x3e, 0x18, 0x72, 0x55, 0x8a, 0x41, 0x06, 0x53, 0x15, 0x77, 0x2e, 0x4a, 0xde, 0xd3, 0x2e, 0x56, + 0xd3, 0x28, 0x0f, 0x0a, 0x1d, 0x02, 0x2e, 0xfb, 0xcf, 0x7a, 0xab, 0xd8, 0xe0, 0xcb, 0xcf, 0xf5, 0xd1, 0x62, 0xa5, + 0x9b, 0xf6, 0xc8, 0x8e, 0x3a, 0x8c, 0xf2, 0xd5, 0x66, 0x1e, 0x36, 0xc2, 0x94, 0xc0, 0x42, 0xe6, 0xe6, 0x71, 0x07, + 0xc4, 0x37, 0x99, 0x93, 0xcd, 0x92, 0xff, 0x89, 0x5d, 0x5e, 0x47, 0x88, 0xc8, 0xb1, 0xbe, 0xab, 0xc8, 0xe8, 0x14, + 0x4e, 0x72, 0x83, 0x00, 0x4e, 0x49, 0xcc, 0x18, 0x64, 0x4b, 0x55, 0x0e, 0xf9, 0xfd, 0x67, 0x5e, 0x42, 0xe8, 0x70, + 0x74, 0xcf, 0xcc, 0xba, 0xad, 0xc2, 0x1a, 0x51, 0x23, 0x65, 0x66, 0x37, 0x4a, 0x3c, 0xac, 0x98, 0x88, 0x94, 0xa4, + 0x33, 0x45, 0x3a, 0xc2, 0xe1, 0xee, 0x72, 0x93, 0x8e, 0x6d, 0xc7, 0x92, 0xa2, 0x6d, 0x16, 0xca, 0x08, 0x10, 0xf8, + 0xa8, 0x3f, 0x22, 0x88, 0xa6, 0x9a, 0xa2, 0x42, 0x2d, 0x6f, 0x6c, 0x6e, 0x47, 0xb7, 0x37, 0x2d, 0x68, 0xbf, 0x80, + 0x3f, 0x15, 0x14, 0xdc, 0x76, 0xd6, 0x73, 0x32, 0x15, 0x45, 0xea, 0x82, 0x34, 0x1d, 0x64, 0xd6, 0xaf, 0xd0, 0xc7, + 0xa7, 0x26, 0x44, 0xdd, 0x5f, 0x36, 0xb9, 0x82, 0x08, 0x81, 0x00, 0xe6, 0x65, 0xf9, 0x28, 0xe1, 0x93, 0x24, 0x11, + 0xa0, 0xea, 0x71, 0xe9, 0x91, 0xb5, 0x54, 0x34, 0x3c, 0x6a, 0x15, 0x14, 0xdb, 0x2e, 0x50, 0x3b, 0x60, 0x81, 0xb5, + 0x75, 0x98, 0xd7, 0x84, 0x54, 0x9d, 0x8a, 0x45, 0xb7, 0xb2, 0x89, 0x94, 0x67, 0x2b, 0x9e, 0x49, 0xc2, 0xa6, 0xf5, + 0xb7, 0x95, 0x2f, 0x1b, 0x10, 0x65, 0xf9, 0x73, 0xe0, 0x86, 0x6f, 0x7e, 0x3a, 0x4a, 0x1d, 0xd3, 0x3a, 0x03, 0x8b, + 0x00, 0x61, 0xa7, 0x72, 0xbc, 0xc6, 0x10, 0x83, 0x12, 0xd4, 0xf1, 0xb1, 0x9b, 0xa8, 0x20, 0x57, 0xa9, 0x1f, 0x07, + 0x0a, 0x10, 0xc6, 0x4b, 0x27, 0x3c, 0x75, 0x97, 0x17, 0x5b, 0xca, 0xc6, 0x05, 0xc6, 0xde, 0x88, 0x56, 0x40, 0xea, + 0xd3, 0x34, 0x9d, 0xe4, 0x68, 0xba, 0x38, 0xcf, 0xf2, 0x2a, 0xee, 0x99, 0x2e, 0xcb, 0x1c, 0xd3, 0xe0, 0x6b, 0x92, + 0xeb, 0xc4, 0x0a, 0xe1, 0x7f, 0x5b, 0xe4, 0xa0, 0x49, 0x99, 0x16, 0x08, 0xb1, 0x96, 0x68, 0x81, 0xf3, 0xed, 0x10, + 0x45, 0x8b, 0xa2, 0x2a, 0x2b, 0x4b, 0xf5, 0xd7, 0x86, 0x66, 0x4f, 0xb2, 0x07, 0x92, 0x7c, 0x38, 0xf6, 0x5d, 0x12, + 0xcc, 0xfd, 0xfe, 0xac, 0x63, 0x98, 0x28, 0xec, 0x83, 0x8d, 0x3c, 0xae, 0x8a, 0x80, 0x4e, 0xcf, 0xfd, 0xea, 0x8d, + 0xb6, 0xbb, 0xc5, 0xde, 0x23, 0xf8, 0x1c, 0x13, 0x11, 0x7f, 0x31, 0x25, 0xd2, 0xb7, 0x57, 0xcf, 0xa9, 0x5c, 0xac, + 0xd6, 0x5d, 0xc3, 0xcb, 0x53, 0x92, 0x6d, 0xbf, 0x1f, 0x80, 0xe7, 0xdd, 0x9e, 0xd1, 0xe3, 0x74, 0x45, 0x33, 0x13, + 0xe7, 0x48, 0x1a, 0x2a, 0xf1, 0x82, 0x8b, 0xba, 0xaf, 0xde, 0x8e, 0x29, 0x41, 0x31, 0x69, 0x6f, 0x51, 0x21, 0x74, + 0x21, 0xa1, 0x6b, 0x71, 0xca, 0xd7, 0x85, 0xb5, 0x9b, 0x57, 0x89, 0xd6, 0x3e, 0x05, 0x8f, 0xa2, 0xd2, 0xe8, 0x8b, + 0xc5, 0x90, 0x4c, 0xc8, 0x81, 0x7e, 0x63, 0xd6, 0x49, 0x76, 0x0f, 0x9f, 0x6a, 0xa3, 0xc8, 0x0f, 0xe2, 0x1c, 0x30, + 0xfb, 0xb2, 0xec, 0x6d, 0x7d, 0x21, 0x6e, 0x7f, 0x53, 0x41, 0x55, 0x3b, 0xfe, 0x47, 0xe9, 0xcf, 0x19, 0xe1, 0x12, + 0xc9, 0xc0, 0x88, 0x19, 0x7e, 0x21, 0xd2, 0x1a, 0xaf, 0x91, 0x73, 0x8f, 0x0b, 0xdb, 0x21, 0xff, 0xe5, 0x8e, 0xe7, + 0xca, 0xae, 0x10, 0xf2, 0x69, 0xe3, 0x47, 0xdc, 0x31, 0xdb, 0xdc, 0xbb, 0x77, 0x36, 0x8c, 0x9c, 0x03, 0xb3, 0x7a, + 0x9a, 0x32, 0x0b, 0x77, 0xe1, 0xde, 0x62, 0xc7, 0x34, 0x3b, 0x04, 0x5e, 0x86, 0x93, 0x74, 0x5c, 0x7c, 0xea, 0x00, + 0xa1, 0xe8, 0x08, 0x60, 0x4a, 0x16, 0xfa, 0x67, 0x28, 0x5d, 0xbd, 0x98, 0x5d, 0x4b, 0xb9, 0xe6, 0xb2, 0x13, 0xf3, + 0x3c, 0x21, 0xc3, 0xc0, 0xf3, 0xe2, 0xaa, 0x35, 0x9d, 0x1d, 0x92, 0x45, 0xa2, 0xa7, 0xfa, 0x90, 0xee, 0xc7, 0xca, + 0x63, 0xd6, 0x43, 0x45, 0x3a, 0xb8, 0xde, 0xc9, 0xad, 0x63, 0x25, 0x81, 0x13, 0xe9, 0x1e, 0x20, 0xcf, 0x4f, 0x6d, + 0xb7, 0xd2, 0x0a, 0x81, 0x97, 0x48, 0x65, 0x86, 0x44, 0xfb, 0x10, 0x7b, 0x48, 0x4c, 0x80, 0x37, 0x05, 0x3b, 0xd8, + 0x12, 0x68, 0x3b, 0xd7, 0x42, 0x0e, 0x14, 0xb0, 0xda, 0x72, 0xc4, 0x60, 0xe6, 0xa1, 0x15, 0x66, 0xe2, 0x38, 0x3b, + 0x47, 0x1e, 0x1c, 0x48, 0x13, 0xa2, 0x9d, 0x53, 0x4a, 0x83, 0x16, 0x67, 0xce, 0x35, 0x72, 0x81, 0x70, 0x7c, 0x0a, + 0x38, 0x0e, 0x63, 0xc3, 0xf5, 0x31, 0x77, 0xb3, 0xd6, 0x17, 0x8f, 0xc2, 0xe6, 0x7e, 0x38, 0xef, 0xd7, 0xf1, 0xbb, + 0x90, 0x58, 0xc9, 0xb2, 0xf0, 0x4d, 0x52, 0xea, 0x99, 0xf2, 0xb9, 0x6b, 0x55, 0x49, 0xcf, 0xf7, 0x01, 0x8f, 0x78, + 0x0a, 0x2e, 0x37, 0x23, 0x6f, 0x52, 0x32, 0x6a, 0xc0, 0x1f, 0x6d, 0x68, 0x57, 0x06, 0x50, 0x14, 0x03, 0x23, 0x4d, + 0xa7, 0xed, 0xa9, 0x5e, 0x62, 0xd8, 0xc0, 0x32, 0x8f, 0xc9, 0x44, 0xa1, 0x53, 0xdb, 0x6b, 0x9e, 0x6f, 0x46, 0x34, + 0xf2, 0x34, 0x6d, 0x90, 0xe5, 0x77, 0x68, 0xb3, 0x56, 0x39, 0xe3, 0x6f, 0xcb, 0x80, 0xf8, 0x9e, 0x97, 0x05, 0x93, + 0xa1, 0x22, 0x49, 0x31, 0x83, 0xe1, 0xd2, 0xfc, 0x45, 0x55, 0x01, 0x7a, 0x0c, 0xaf, 0xd6, 0x16, 0xd5, 0x79, 0x07, + 0x22, 0xdc, 0x7d, 0x50, 0xaa, 0x70, 0x7d, 0xb9, 0x9b, 0xad, 0x4c, 0x73, 0xf0, 0x7d, 0x55, 0x5d, 0x41, 0xa2, 0x9d, + 0xe1, 0x45, 0xa0, 0x46, 0x67, 0xe6, 0xe2, 0x90, 0x1d, 0x7b, 0x6a, 0xde, 0xa0, 0x08, 0xb9, 0x5f, 0xfd, 0x08, 0xc5, + 0x27, 0xe9, 0x49, 0x23, 0xd0, 0x57, 0x10, 0x5c, 0xf5, 0xb5, 0xb9, 0xa2, 0xcc, 0x56, 0x23, 0xf7, 0x82, 0xb2, 0x0c, + 0x51, 0xb8, 0xa7, 0x48, 0x1c, 0x78, 0x52, 0xf0, 0x88, 0x09, 0x01, 0x4a, 0x78, 0x6b, 0x89, 0x1e, 0xda, 0xdf, 0xcd, + 0xac, 0x7e, 0x61, 0x0d, 0x5b, 0x99, 0x72, 0x00, 0x29, 0x02, 0x4c, 0x2a, 0xe5, 0xea, 0xfe, 0xc1, 0x42, 0x38, 0x1e, + 0x46, 0x0a, 0x26, 0x3f, 0xaf, 0x7c, 0x0b, 0xbc, 0x59, 0xf7, 0xf2, 0x28, 0x3a, 0xd2, 0xc4, 0x94, 0x7a, 0x86, 0xd6, + 0x48, 0xed, 0x76, 0x07, 0xb8, 0x5a, 0xa5, 0x17, 0x54, 0xff, 0xa2, 0x08, 0x46, 0xff, 0x4a, 0x03, 0x61, 0xf1, 0x46, + 0x4c, 0x25, 0x68, 0x8a, 0xde, 0x62, 0x07, 0xa6, 0x7d, 0x2d, 0x55, 0xd7, 0x03, 0xa4, 0xd8, 0x32, 0x83, 0xef, 0x0e, + 0x4e, 0x11, 0x31, 0x4f, 0xc6, 0x62, 0xb5, 0xba, 0x13, 0x63, 0xee, 0xbf, 0x70, 0x1d, 0x41, 0xd8, 0xbf, 0xc6, 0x80, + 0x0e, 0x5a, 0x20, 0x93, 0x3d, 0x70, 0x47, 0xac, 0x84, 0xd9, 0xe7, 0x51, 0x15, 0x58, 0x09, 0x69, 0xf1, 0x3e, 0x2e, + 0x65, 0xcd, 0xe7, 0xd5, 0x71, 0x40, 0x74, 0x0e, 0xe6, 0xb7, 0x8a, 0xbe, 0x85, 0x90, 0x79, 0x5d, 0xe7, 0x04, 0x50, + 0x97, 0xd3, 0x72, 0x5a, 0x4f, 0x48, 0x46, 0x7e, 0x58, 0xaf, 0x1f, 0xa3, 0xa1, 0xca, 0xc7, 0x67, 0x9a, 0xcc, 0xd0, + 0xbd, 0xf9, 0x46, 0x0d, 0x45, 0xdf, 0xae, 0x60, 0x43, 0x21, 0x9b, 0x64, 0xef, 0xc5, 0x37, 0x9b, 0x5c, 0x16, 0xd4, + 0x53, 0xb3, 0x85, 0x53, 0xef, 0xe0, 0x62, 0xcb, 0x34, 0x8e, 0xdc, 0x05, 0xcd, 0x19, 0x9f, 0xed, 0x0d, 0x44, 0x7f, + 0x2f, 0x24, 0x6e, 0x43, 0x47, 0x86, 0xd2, 0x8f, 0x1b, 0x23, 0xa0, 0x46, 0xd1, 0x61, 0x8c, 0x4c, 0x7d, 0xcb, 0x90, + 0x1b, 0x13, 0x74, 0x54, 0x05, 0x55, 0x8b, 0x99, 0x59, 0x92, 0xae, 0x91, 0x0a, 0x0a, 0xa3, 0x34, 0x06, 0x8d, 0x95, + 0x2a, 0x24, 0x7b, 0x11, 0xb1, 0xf4, 0x3c, 0x17, 0xe0, 0xa1, 0x6c, 0x3a, 0x78, 0xca, 0xc2, 0x25, 0xa1, 0xb7, 0x35, + 0x33, 0x4c, 0xcd, 0x56, 0xda, 0x0c, 0x56, 0x54, 0x42, 0x4a, 0xae, 0x2f, 0x4a, 0x49, 0x59, 0x8a, 0x82, 0xe3, 0xa9, + 0x4c, 0x66, 0x28, 0x5f, 0x29, 0x16, 0xab, 0xf7, 0x85, 0x7f, 0x1b, 0x29, 0xb4, 0x5c, 0x01, 0x0b, 0x50, 0x49, 0xb0, + 0x0a, 0xe8, 0xe7, 0xcb, 0xc7, 0x52, 0x85, 0xdf, 0x04, 0xf0, 0x6a, 0xea, 0x79, 0x1d, 0x87, 0x12, 0x5f, 0xbb, 0xf3, + 0xe9, 0xd4, 0xce, 0xff, 0xa4, 0x70, 0xb8, 0x0e, 0xd2, 0x2c, 0xe8, 0xb0, 0xa0, 0x5f, 0xb0, 0x57, 0x5f, 0xac, 0x0d, + 0xa5, 0x98, 0x96, 0x48, 0xdd, 0x2b, 0x63, 0xeb, 0x3d, 0x1b, 0x25, 0xa1, 0x59, 0xfb, 0xf3, 0x7d, 0x27, 0xa9, 0x99, + 0xa9, 0x6a, 0x17, 0xf7, 0xb0, 0x01, 0xd3, 0xd6, 0xa4, 0x8a, 0xb8, 0x77, 0xf3, 0x35, 0xbc, 0xb4, 0x29, 0x01, 0xaa, + 0x4d, 0xa6, 0xf8, 0xae, 0x66, 0x47, 0xee, 0x83, 0xbd, 0xee, 0xbf, 0xdd, 0x7d, 0xd4, 0x9e, 0x0d, 0xec, 0x8a, 0xd0, + 0x0f, 0xa2, 0x37, 0xb0, 0xca, 0xd2, 0x1b, 0x63, 0x81, 0xad, 0x75, 0xa9, 0x83, 0x91, 0x0a, 0xbc, 0x78, 0xe5, 0x0e, + 0x18, 0x8f, 0x47, 0x10, 0x40, 0x7f, 0xe7, 0xf0, 0xc5, 0xb7, 0x44, 0x2a, 0x8c, 0xbb, 0xec, 0x38, 0xa9, 0xca, 0x72, + 0x9b, 0x1d, 0xc7, 0x8c, 0xb1, 0x25, 0x99, 0x99, 0x55, 0x10, 0xb4, 0x1c, 0xa8, 0xbe, 0x4a, 0xde, 0x75, 0x19, 0xc3, + 0x5a, 0x51, 0xc0, 0x3e, 0x28, 0x15, 0xd8, 0x1f, 0x84, 0xa2, 0xba, 0x8c, 0xef, 0xab, 0xe9, 0xcd, 0x3c, 0x70, 0x60, + 0xc4, 0xd0, 0x32, 0x83, 0x23, 0xf6, 0x48, 0x87, 0xad, 0xf4, 0xa6, 0xde, 0x99, 0x6b, 0x15, 0xce, 0x3b, 0x8a, 0xed, + 0x34, 0x48, 0xf5, 0xb7, 0xfd, 0xee, 0x05, 0x4e, 0xf0, 0xcb, 0x2c, 0xaa, 0xc7, 0x3b, 0x73, 0x7a, 0x9d, 0x5c, 0x54, + 0x1d, 0x86, 0xb6, 0xf8, 0xf3, 0x01, 0x69, 0x48, 0x1a, 0xf6, 0x70, 0x7d, 0x25, 0x9d, 0xf9, 0x82, 0x6a, 0x72, 0xc8, + 0xc6, 0x6a, 0x3d, 0x28, 0x80, 0xa7, 0xbc, 0x77, 0x7c, 0xcc, 0x2f, 0x25, 0xa9, 0x1a, 0xd1, 0x42, 0x4d, 0x7c, 0x53, + 0x7e, 0x93, 0x46, 0x0b, 0xe2, 0xd8, 0xda, 0x6b, 0x87, 0xb7, 0x10, 0xbf, 0x7f, 0x8b, 0x89, 0x0a, 0x10, 0xef, 0xcd, + 0xae, 0xcb, 0xe0, 0xb4, 0x7a, 0x96, 0x96, 0x50, 0xb5, 0x81, 0xaa, 0xa6, 0x28, 0x4d, 0xc3, 0xca, 0xe4, 0x73, 0x60, + 0xcf, 0x2b, 0xe9, 0x9c, 0xd2, 0x82, 0xa9, 0x65, 0xdc, 0x7b, 0xc9, 0xc2, 0xe2, 0x63, 0x05, 0xfc, 0xb4, 0x30, 0xa7, + 0xee, 0xa2, 0x12, 0x59, 0x8c, 0x5e, 0xb9, 0x05, 0xd5, 0xe6, 0x4b, 0x95, 0x80, 0x58, 0xbf, 0x6b, 0x35, 0xce, 0x46, + 0xc0, 0x89, 0xa1, 0x77, 0x7f, 0xe2, 0x66, 0xa5, 0x97, 0xa0, 0x98, 0xa6, 0x67, 0x3d, 0x59, 0xca, 0x9c, 0xf1, 0xf2, + 0x87, 0xde, 0x4d, 0x15, 0xdd, 0x3d, 0x57, 0xfc, 0xc1, 0xb3, 0x53, 0x3d, 0x89, 0x5d, 0x56, 0x94, 0xe3, 0x35, 0x96, + 0xd4, 0x3d, 0x72, 0xe4, 0x17, 0xf7, 0x80, 0x40, 0xa1, 0x19, 0x15, 0xc1, 0xa5, 0x91, 0xb4, 0xfc, 0x4c, 0x6d, 0x65, + 0xa4, 0x4f, 0x28, 0x96, 0x08, 0x90, 0xc1, 0xf7, 0x1f, 0x25, 0xba, 0x72, 0x5f, 0x07, 0xf8, 0x27, 0x84, 0x21, 0x44, + 0x31, 0xcb, 0x52, 0x38, 0x16, 0x20, 0xa9, 0xaf, 0xae, 0xa9, 0x66, 0xa7, 0x0d, 0x11, 0x54, 0xea, 0xae, 0x43, 0x80, + 0xb0, 0x5d, 0x23, 0xf0, 0xe5, 0xdf, 0xe0, 0x69, 0xbf, 0xe7, 0x05, 0x75, 0xd8, 0x64, 0x17, 0x3a, 0xf0, 0x36, 0x8b, + 0x7e, 0xe9, 0x19, 0xe9, 0xaf, 0x8d, 0x4f, 0x36, 0x96, 0x0f, 0x3f, 0x2b, 0xdb, 0xa4, 0x7a, 0xb0, 0xb7, 0x00, 0xc4, + 0x6c, 0xa4, 0xd9, 0xb8, 0xd2, 0x55, 0xfb, 0xbe, 0xd5, 0x28, 0x9b, 0x33, 0x6c, 0x97, 0xe0, 0xc5, 0x83, 0x45, 0x8d, + 0x19, 0x22, 0x5b, 0x7b, 0xdc, 0x4b, 0x0f, 0xee, 0xb2, 0xf7, 0x23, 0xe8, 0x3c, 0x21, 0x47, 0x6c, 0x02, 0xce, 0xab, + 0xc2, 0x53, 0x21, 0xb3, 0x02, 0x9d, 0x38, 0x08, 0x20, 0x5d, 0xd6, 0x5d, 0x30, 0xa7, 0x4f, 0x8b, 0x1b, 0x16, 0x3d, + 0xd8, 0x40, 0x59, 0xc8, 0xc0, 0x96, 0x50, 0x43, 0x2e, 0x4c, 0x62, 0xe9, 0x81, 0xfd, 0x82, 0xf0, 0xb5, 0x1e, 0xc7, + 0xba, 0x66, 0x75, 0xd1, 0xa5, 0x02, 0xd2, 0xe2, 0x48, 0xff, 0xa6, 0x8f, 0xe8, 0xbe, 0x69, 0x6d, 0xf8, 0x5e, 0x07, + 0x8d, 0x33, 0x2c, 0x97, 0xc7, 0xd1, 0x5e, 0xd5, 0x6f, 0x0f, 0x0d, 0xaa, 0x99, 0xcf, 0x5c, 0xde, 0x64, 0xf5, 0x6f, + 0xd7, 0x30, 0x90, 0xbf, 0x50, 0xf8, 0xaa, 0x27, 0x90, 0x69, 0x49, 0x8b, 0x82, 0xb7, 0xa7, 0x0f, 0x9b, 0x2c, 0x18, + 0xf7, 0xb7, 0x9f, 0x22, 0xc6, 0xf5, 0xaf, 0xdf, 0xf3, 0xb7, 0xdb, 0x90, 0xf8, 0x8d, 0x80, 0xa4, 0xfb, 0x77, 0x7b, + 0x04, 0x01, 0xe2, 0xde, 0x22, 0x97, 0x0d, 0xee, 0x6f, 0x1e, 0xd7, 0xf5, 0xd5, 0x3d, 0xff, 0xb8, 0x03, 0x51, 0x4b, + 0x72, 0xb2, 0xb5, 0x13, 0xdd, 0x73, 0xce, 0xec, 0xee, 0x65, 0x9a, 0xfe, 0x02, 0x38, 0x85, 0xd5, 0x2d, 0xff, 0xe9, + 0x7d, 0xcb, 0x9e, 0x52, 0x72, 0xbd, 0xb5, 0x5f, 0x4e, 0xbf, 0x7c, 0xdb, 0x5c, 0x65, 0xa7, 0x86, 0xf5, 0x8b, 0xb2, + 0x24, 0xd3, 0x12, 0xcc, 0xfa, 0x20, 0x85, 0xcd, 0x5a, 0x7d, 0x93, 0x71, 0x94, 0xd7, 0x39, 0x61, 0x84, 0x2d, 0x08, + 0xcd, 0xe3, 0x97, 0xc4, 0x52, 0x32, 0xff, 0xb4, 0x5d, 0x19, 0xc3, 0x20, 0xd2, 0xed, 0xaa, 0xb5, 0xe2, 0xea, 0x9c, + 0xb2, 0x3d, 0xe6, 0xd1, 0x04, 0x3f, 0x2f, 0x07, 0x47, 0x2d, 0xf0, 0xaf, 0x70, 0xd8, 0xee, 0xb2, 0x79, 0x0f, 0x34, + 0x2f, 0xfe, 0x03, 0x78, 0x23, 0xba, 0x64, 0x61, 0xc7, 0x87, 0x12, 0x3f, 0xeb, 0x70, 0xb8, 0x32, 0x2c, 0x81, 0x71, + 0x0c, 0x03, 0xc6, 0xa1, 0xab, 0xad, 0x3d, 0x19, 0x0e, 0x83, 0x83, 0x44, 0xf1, 0x1e, 0x4a, 0xb1, 0x8e, 0xe6, 0x85, + 0xe9, 0xd3, 0x3a, 0xb2, 0xcf, 0x30, 0x20, 0x0f, 0xc6, 0x33, 0x1f, 0xb3, 0x6a, 0xaa, 0xa1, 0x4b, 0xd7, 0x71, 0xa5, + 0x35, 0x36, 0xe4, 0x63, 0xc6, 0xee, 0xaf, 0xbd, 0xb3, 0xa0, 0x5d, 0xdd, 0xef, 0xd9, 0x39, 0xc3, 0xea, 0xbb, 0x32, + 0x93, 0xa7, 0x5c, 0x2e, 0xd8, 0x99, 0x2b, 0xfa, 0xf3, 0x7e, 0x93, 0x6a, 0x0a, 0x63, 0x74, 0x04, 0x63, 0xfa, 0xc9, + 0xa9, 0xc4, 0x15, 0x66, 0x64, 0x5d, 0x6f, 0x49, 0x75, 0x26, 0x58, 0x65, 0x5d, 0x21, 0xe7, 0x5d, 0x9c, 0x09, 0x7d, + 0x68, 0x12, 0x5e, 0x24, 0xeb, 0x87, 0xa9, 0xf7, 0xc0, 0x10, 0x59, 0xc8, 0x71, 0xb3, 0xf6, 0xc5, 0x92, 0x71, 0x12, + 0xdb, 0xa9, 0x2e, 0x9c, 0xb6, 0x7b, 0x6d, 0x8f, 0x60, 0x10, 0x78, 0xfe, 0x55, 0x8e, 0x71, 0xdd, 0x86, 0x75, 0x67, + 0xd6, 0x55, 0xf5, 0xf2, 0xc0, 0x12, 0xf6, 0x7a, 0x4c, 0x68, 0x47, 0xe5, 0xa5, 0x6c, 0x2a, 0x99, 0xc8, 0x68, 0x1e, + 0x0b, 0xca, 0xd1, 0x95, 0x39, 0xaf, 0xf2, 0x8e, 0xf6, 0x2c, 0x72, 0x33, 0x00, 0x46, 0x3a, 0x2e, 0xc3, 0xc4, 0xb4, + 0xb0, 0xa1, 0x23, 0xd5, 0xaa, 0xbc, 0x80, 0x8f, 0x1a, 0xbe, 0x68, 0xa0, 0x05, 0x21, 0xaf, 0x9e, 0x38, 0x9c, 0x15, + 0x52, 0xa4, 0xb8, 0x8d, 0xfd, 0x84, 0x22, 0x7f, 0x74, 0x33, 0xb1, 0x55, 0x53, 0xe7, 0x79, 0xf7, 0x7b, 0x08, 0x6a, + 0x0f, 0x42, 0x83, 0x85, 0xb7, 0x3a, 0x14, 0xb0, 0x49, 0x5e, 0x5b, 0xc8, 0x45, 0x86, 0xdf, 0x0c, 0x24, 0x33, 0x10, + 0xa2, 0x33, 0x97, 0x20, 0xac, 0xf6, 0x93, 0x6d, 0x17, 0x74, 0x8d, 0xc2, 0xad, 0x70, 0x7c, 0x60, 0xdc, 0x6b, 0xa2, + 0x16, 0x96, 0xe3, 0xa2, 0x43, 0x9e, 0x2a, 0xb4, 0x62, 0x15, 0x51, 0xeb, 0x12, 0x3f, 0xd9, 0x27, 0x05, 0xf1, 0x62, + 0x4b, 0xd0, 0x4e, 0x4d, 0x8e, 0x1f, 0x1c, 0xba, 0x3d, 0xb1, 0x2c, 0x34, 0x5b, 0x12, 0xbe, 0xf3, 0xca, 0x9e, 0x31, + 0x82, 0x9e, 0xab, 0x94, 0x23, 0x36, 0xd4, 0x65, 0xf6, 0x07, 0xcb, 0x37, 0x13, 0x1a, 0xf7, 0x97, 0x50, 0x66, 0xfb, + 0x2a, 0xb9, 0x4f, 0x91, 0x8f, 0x6b, 0x29, 0x27, 0xa3, 0x0e, 0xdf, 0x98, 0xe0, 0xcf, 0x33, 0xb8, 0x82, 0x55, 0x77, + 0xe5, 0xf0, 0x74, 0x6d, 0x59, 0x9c, 0xef, 0x6d, 0xa1, 0x2c, 0x5e, 0x43, 0xed, 0x61, 0x53, 0xb6, 0xe2, 0x0d, 0x6e, + 0xcf, 0xa6, 0xe9, 0x0b, 0x2f, 0x45, 0x58, 0x77, 0xc5, 0x70, 0xb3, 0x3b, 0x30, 0x73, 0x09, 0xc5, 0xf4, 0xbb, 0xfd, + 0xf1, 0x0c, 0x2c, 0x17, 0xbe, 0xda, 0xfd, 0xe6, 0x98, 0xc4, 0x1f, 0xc8, 0x32, 0x43, 0x5d, 0x6e, 0x6f, 0xde, 0x85, + 0xaf, 0x05, 0xe8, 0xdf, 0x67, 0x30, 0x58, 0x48, 0x7f, 0x9b, 0xa5, 0xb8, 0x49, 0x13, 0xac, 0x1e, 0x24, 0x1f, 0x14, + 0x26, 0x30, 0x95, 0xab, 0xa7, 0x4d, 0x6f, 0x0f, 0xdb, 0x4d, 0xc6, 0x3a, 0x41, 0xad, 0x7c, 0x0b, 0x3d, 0x6d, 0xf4, + 0x58, 0xfc, 0x8d, 0x09, 0x4d, 0x59, 0x50, 0xdb, 0x56, 0xff, 0x81, 0x3b, 0x7a, 0x39, 0x8c, 0xb5, 0xb1, 0xf4, 0x7e, + 0xbb, 0xb7, 0x61, 0x4f, 0xf8, 0x2e, 0xed, 0xa6, 0x41, 0x7f, 0xc9, 0xe9, 0x9e, 0x3a, 0x3f, 0x26, 0x5e, 0x9f, 0xee, + 0x7e, 0xcd, 0xe6, 0x71, 0xc4, 0x0c, 0x95, 0xff, 0xe1, 0x2f, 0x2e, 0xbf, 0xfb, 0xf6, 0x68, 0xcf, 0xc7, 0x5f, 0x66, + 0xbf, 0xa9, 0x4b, 0x77, 0xda, 0x9b, 0xcb, 0x3e, 0x95, 0x4d, 0x18, 0x2f, 0xce, 0x36, 0x60, 0xf6, 0xc6, 0xa5, 0x6a, + 0xf6, 0x9e, 0x51, 0x72, 0x1c, 0xb6, 0x28, 0xb4, 0x9e, 0x14, 0x8c, 0xc8, 0x1b, 0xa6, 0xdd, 0x9b, 0x90, 0x41, 0xec, + 0x00, 0xc8, 0x8f, 0x42, 0x18, 0x3a, 0xb4, 0x14, 0xc1, 0xae, 0xf1, 0x6d, 0x50, 0x1f, 0x61, 0x06, 0x56, 0x13, 0xe9, + 0x8b, 0x05, 0x79, 0x0f, 0x32, 0x96, 0x58, 0x14, 0xd5, 0x54, 0x3a, 0xf0, 0xf4, 0x95, 0x20, 0x4c, 0x0e, 0x6c, 0x49, + 0xef, 0x00, 0xdb, 0x39, 0xdf, 0x9c, 0xfc, 0x35, 0x44, 0x3f, 0x85, 0x5c, 0x75, 0xd5, 0x78, 0xcd, 0x2c, 0xd9, 0xc7, + 0x04, 0x58, 0xfb, 0xca, 0xf8, 0xdb, 0xb6, 0x40, 0xb4, 0x82, 0x9f, 0xb3, 0xad, 0x11, 0xeb, 0x2e, 0xe8, 0xf5, 0x0f, + 0x57, 0xf0, 0x30, 0xc1, 0x7f, 0x49, 0x13, 0xca, 0x12, 0x3d, 0x99, 0x05, 0xa5, 0x93, 0xdf, 0x6f, 0x21, 0xe1, 0xf0, + 0x9a, 0x5e, 0xf2, 0xd2, 0x5b, 0xdd, 0x4a, 0x79, 0x7e, 0x3b, 0xff, 0x33, 0xf5, 0xfa, 0xf8, 0x57, 0x6f, 0xc3, 0x56, + 0x31, 0x9e, 0x39, 0x0c, 0x80, 0x68, 0x88, 0xb8, 0x24, 0xf9, 0x0a, 0xb0, 0x46, 0xee, 0xb5, 0xa8, 0x42, 0x86, 0xd3, + 0xc6, 0xcd, 0xe4, 0x3d, 0xff, 0xdb, 0x10, 0xc3, 0xf9, 0xf8, 0xcb, 0xec, 0x7a, 0x09, 0x1f, 0x18, 0x70, 0x83, 0x8b, + 0xb2, 0x2a, 0x74, 0x07, 0x34, 0xff, 0x6f, 0xa3, 0xd1, 0x22, 0x34, 0x59, 0xa8, 0xa4, 0xfb, 0xf1, 0x1f, 0x73, 0x1d, + 0xc6, 0xba, 0xef, 0x96, 0x98, 0x30, 0x66, 0xc2, 0x0f, 0x8d, 0x62, 0x74, 0x70, 0x7b, 0x9b, 0x7b, 0x42, 0x5d, 0x92, + 0x2d, 0x11, 0x96, 0xa7, 0xf8, 0x2b, 0x8c, 0x65, 0xe4, 0x70, 0x32, 0x65, 0xce, 0x90, 0xc5, 0xc6, 0xce, 0x52, 0x42, + 0xe6, 0x83, 0x7e, 0x2f, 0x0e, 0x23, 0x4d, 0x81, 0x1e, 0xab, 0xaa, 0xf9, 0x1b, 0xbb, 0x53, 0xba, 0x1f, 0x72, 0xe2, + 0x42, 0xbf, 0xb4, 0x25, 0xa2, 0x80, 0xdd, 0x18, 0x3e, 0x8a, 0xff, 0x98, 0x6b, 0xf5, 0x7e, 0xb6, 0x2d, 0x6e, 0x49, + 0x74, 0x12, 0x63, 0x26, 0xe8, 0xd4, 0x1f, 0x6e, 0xf4, 0x63, 0x0f, 0x4d, 0x18, 0x75, 0x3c, 0x0e, 0x53, 0x23, 0x38, + 0x76, 0x24, 0xcb, 0x41, 0xa6, 0x33, 0xd0, 0x90, 0x4e, 0x46, 0xbf, 0x9f, 0xa9, 0xe5, 0x34, 0xe0, 0x57, 0xbd, 0xcc, + 0x15, 0xb7, 0x79, 0xc5, 0x45, 0x7a, 0xf2, 0x0d, 0xae, 0xff, 0xf8, 0x6f, 0xb6, 0xdd, 0xac, 0x76, 0x8c, 0xbf, 0x08, + 0x15, 0x6e, 0xee, 0x2b, 0x37, 0xd0, 0x44, 0x50, 0xce, 0x9a, 0x6a, 0x32, 0xea, 0x9b, 0x67, 0xa5, 0xf3, 0x02, 0xf3, + 0x75, 0xa9, 0xe7, 0xae, 0xeb, 0x5e, 0xcc, 0x1f, 0x2a, 0xd8, 0x0f, 0x4e, 0x05, 0x3f, 0x0e, 0xb5, 0x4a, 0x04, 0xa0, + 0xd5, 0x8a, 0xcd, 0x66, 0x37, 0xea, 0xdf, 0xcb, 0x26, 0x2c, 0x29, 0x82, 0xbf, 0x52, 0x46, 0xf2, 0x2b, 0x0b, 0x7b, + 0x48, 0xff, 0xf2, 0x60, 0x93, 0xf5, 0x80, 0x06, 0x3c, 0x6c, 0x54, 0x78, 0xab, 0x45, 0x2a, 0x71, 0x39, 0x6e, 0x20, + 0x52, 0xa1, 0x96, 0xcc, 0xec, 0xd6, 0xc4, 0x17, 0xcd, 0x53, 0x38, 0xdc, 0x89, 0x47, 0x89, 0x7b, 0x85, 0x71, 0x47, + 0x8b, 0x96, 0xe8, 0x84, 0x3c, 0x8f, 0x10, 0x3a, 0x64, 0x88, 0x2f, 0x27, 0x6a, 0x16, 0x69, 0x62, 0x85, 0x6b, 0xe9, + 0xd9, 0x36, 0xbd, 0x6f, 0xd6, 0x88, 0x09, 0xbf, 0x00, 0x05, 0x97, 0xc9, 0x51, 0xcb, 0xe7, 0x9e, 0xba, 0xdd, 0xa0, + 0x5d, 0x59, 0x66, 0xfc, 0x8b, 0x3a, 0x8b, 0x59, 0x68, 0x32, 0xa8, 0x76, 0xb1, 0xae, 0x40, 0x82, 0x29, 0x4c, 0xa4, + 0xc1, 0xc8, 0x32, 0x3a, 0xa4, 0x39, 0x07, 0xda, 0x17, 0x2d, 0x2a, 0x4c, 0xd6, 0xfe, 0x37, 0x96, 0xfd, 0x11, 0xba, + 0xe8, 0x37, 0x9b, 0x2d, 0xdc, 0xb8, 0x49, 0xab, 0xf5, 0x9d, 0xdb, 0x84, 0xef, 0xbb, 0x49, 0xec, 0x0d, 0x0a, 0xb5, + 0x69, 0xd0, 0x24, 0x21, 0x87, 0x81, 0x81, 0xfe, 0x34, 0xb4, 0x91, 0xac, 0x76, 0x78, 0x14, 0xa7, 0x65, 0x13, 0x79, + 0x9d, 0x9f, 0x62, 0xbd, 0x31, 0x45, 0x08, 0xb3, 0xf4, 0x2b, 0x9b, 0x00, 0xf2, 0xea, 0x90, 0x8e, 0x54, 0x8d, 0x2c, + 0x81, 0x5f, 0x68, 0x0d, 0xe2, 0x8b, 0x13, 0xec, 0x52, 0x93, 0x8b, 0x56, 0xb2, 0x89, 0xa8, 0x7e, 0x6c, 0xd9, 0x1d, + 0x3a, 0x10, 0x54, 0x2d, 0x98, 0xcc, 0x33, 0x83, 0x4d, 0x74, 0x95, 0xab, 0x5a, 0x09, 0x12, 0x64, 0xd3, 0x70, 0x9d, + 0xfe, 0x07, 0xe1, 0x8e, 0xa2, 0x4d, 0xa2, 0x13, 0x20, 0x1b, 0xb7, 0x39, 0xcf, 0xb0, 0x7b, 0x45, 0x2d, 0x44, 0x9f, + 0xf8, 0x29, 0x9f, 0x6b, 0xff, 0x5b, 0xb6, 0x7d, 0x4e, 0x1f, 0x3b, 0x79, 0x70, 0xb8, 0x5d, 0x86, 0x60, 0x76, 0xf8, + 0x54, 0x1e, 0x25, 0xb4, 0xb5, 0x85, 0xa1, 0x1e, 0x6a, 0x53, 0x6f, 0xc5, 0xc8, 0x7a, 0x6c, 0x33, 0x28, 0x38, 0xbd, + 0x90, 0xf0, 0xed, 0xe8, 0x3d, 0x45, 0x22, 0xb9, 0xc4, 0x2f, 0xa0, 0x75, 0xe2, 0x2f, 0x1f, 0xfb, 0x38, 0x6e, 0x77, + 0x4d, 0x0c, 0xbd, 0xa8, 0xb0, 0x0b, 0x5f, 0x76, 0x6c, 0xb3, 0xe9, 0x34, 0x4e, 0x73, 0x1d, 0x53, 0xb5, 0x20, 0x7a, + 0x98, 0x51, 0xe7, 0xa2, 0x29, 0xc1, 0xcb, 0x99, 0x96, 0xf3, 0x53, 0x8b, 0x94, 0xb0, 0x8b, 0xb5, 0x50, 0x1f, 0x79, + 0xa6, 0x12, 0x7f, 0xb9, 0x8f, 0x7e, 0xd2, 0x07, 0x1f, 0xae, 0x17, 0xd6, 0xfa, 0x3b, 0xdd, 0x9f, 0x96, 0x5c, 0xd6, + 0x70, 0xad, 0x92, 0xed, 0x65, 0x19, 0x30, 0xb2, 0xb6, 0xd9, 0xec, 0x43, 0x04, 0x26, 0x88, 0x9d, 0xd2, 0xf7, 0x9a, + 0x86, 0xc4, 0xbf, 0x4c, 0x6f, 0x58, 0x9d, 0xb8, 0xc0, 0xb8, 0x8d, 0x86, 0xfe, 0x61, 0xdb, 0x96, 0xda, 0x9d, 0x4e, + 0x05, 0x03, 0xe1, 0x68, 0xf8, 0x96, 0x4c, 0x55, 0x81, 0xe1, 0xc3, 0x61, 0xad, 0x53, 0x61, 0x0b, 0xb3, 0x46, 0xb6, + 0x7f, 0x0b, 0xc5, 0x4f, 0x0f, 0x4b, 0xd3, 0xc0, 0x86, 0x30, 0xcd, 0x60, 0xde, 0xe6, 0x58, 0xb4, 0x05, 0x96, 0x6d, + 0x89, 0x55, 0x5b, 0xe1, 0xb8, 0x1d, 0xe3, 0xa4, 0x9d, 0xe0, 0xb4, 0x9d, 0xe2, 0x0e, 0xa3, 0xe0, 0xee, 0x84, 0x8a, + 0x7b, 0x13, 0x1a, 0xee, 0x4f, 0x18, 0xf1, 0x80, 0x31, 0xe1, 0xe1, 0x84, 0x0e, 0x8b, 0x31, 0x77, 0x39, 0xd7, 0xcf, + 0xad, 0xe7, 0xa8, 0x36, 0x0c, 0x3a, 0xe3, 0x6d, 0x37, 0xfe, 0x15, 0x19, 0xef, 0xaa, 0xc5, 0xe4, 0xb9, 0xd4, 0xe5, + 0x7d, 0xd7, 0xf3, 0x83, 0xd0, 0x2a, 0xae, 0xac, 0x48, 0xf9, 0xbc, 0x5f, 0xb8, 0x0e, 0x7d, 0xe7, 0x1d, 0xe8, 0x9b, + 0x42, 0xef, 0x4c, 0x12, 0x9a, 0x1f, 0xbe, 0xbb, 0xdb, 0xee, 0x89, 0x2f, 0x44, 0x1c, 0x37, 0x9f, 0x77, 0x10, 0xba, + 0x01, 0xf1, 0x76, 0x25, 0x1a, 0x4f, 0x2e, 0xb8, 0xa1, 0x53, 0x91, 0x25, 0x5e, 0xa2, 0x3c, 0x56, 0xd4, 0xc9, 0x89, + 0xcd, 0x33, 0x65, 0xbc, 0xce, 0x43, 0x97, 0xe8, 0x91, 0xa3, 0x59, 0xb0, 0xbf, 0x75, 0xa7, 0x5c, 0x70, 0xa7, 0x05, + 0x6f, 0x9f, 0x1c, 0xfb, 0x47, 0xfb, 0x6e, 0x23, 0x4d, 0x9e, 0x8c, 0xfb, 0xe3, 0x67, 0x4b, 0x2f, 0xb8, 0xd7, 0x25, + 0x16, 0x6c, 0x80, 0x0b, 0xe6, 0xc4, 0xe5, 0xc2, 0xbc, 0x65, 0x13, 0x05, 0x18, 0xe6, 0x17, 0x80, 0x95, 0xf4, 0x44, + 0x7f, 0xe1, 0x83, 0xff, 0x69, 0x89, 0x32, 0x1c, 0x0d, 0xf9, 0x9d, 0xfb, 0xff, 0xfb, 0xdb, 0x3b, 0xc2, 0x09, 0xa7, + 0x84, 0x65, 0xf3, 0xda, 0x62, 0xfe, 0xdd, 0xb2, 0xa5, 0xb7, 0x32, 0xc5, 0xcd, 0x7f, 0xca, 0x1d, 0x27, 0x7b, 0x27, + 0x58, 0xec, 0x0e, 0x04, 0x4c, 0xa0, 0x71, 0x7b, 0x0e, 0x67, 0x6d, 0x3f, 0x9d, 0xfe, 0x66, 0x65, 0x33, 0x69, 0x04, + 0x3d, 0x2d, 0xbf, 0xf8, 0xff, 0xc7, 0x6f, 0xca, 0x17, 0x36, 0xed, 0x97, 0xcb, 0x55, 0xb9, 0xbc, 0x53, 0xa6, 0xa2, + 0x77, 0x9c, 0x5d, 0x60, 0x8f, 0x9a, 0x7f, 0x16, 0x95, 0x77, 0x28, 0xab, 0x68, 0x99, 0x52, 0x9b, 0x6b, 0x59, 0xcd, + 0xe7, 0x9f, 0xab, 0x76, 0xb6, 0x45, 0x9e, 0xc7, 0xbf, 0xb6, 0xf3, 0x78, 0xde, 0xa8, 0xff, 0xc1, 0xdf, 0x53, 0x0f, + 0xc7, 0x87, 0x75, 0x7b, 0xfc, 0x6f, 0x5d, 0xad, 0x6a, 0x3e, 0x3d, 0xab, 0x6b, 0xad, 0xf2, 0xd7, 0xd4, 0x2c, 0xbf, + 0xb7, 0x61, 0xcc, 0x0d, 0xb4, 0xce, 0x77, 0x8f, 0xbf, 0xa8, 0xf9, 0x17, 0x1a, 0xaa, 0x41, 0x1e, 0xee, 0xbf, 0x55, + 0xd7, 0xd7, 0x9f, 0x39, 0x09, 0xcd, 0x8d, 0xe5, 0x5b, 0x10, 0x35, 0xfa, 0xed, 0xf3, 0x44, 0x0c, 0xf6, 0x5f, 0x9f, + 0x50, 0x30, 0x46, 0x05, 0x33, 0x37, 0xcc, 0x85, 0x3e, 0xd4, 0x48, 0x79, 0x17, 0xb9, 0x77, 0xfc, 0x6f, 0x1c, 0xe3, + 0x1c, 0xa7, 0x21, 0x12, 0x89, 0x0f, 0xf1, 0x99, 0xef, 0x17, 0x5f, 0xa4, 0xa0, 0x89, 0x11, 0x4e, 0x66, 0x66, 0x16, + 0x54, 0x9d, 0x72, 0x6a, 0x2a, 0x1c, 0x7c, 0xab, 0x10, 0x0c, 0x0a, 0x61, 0x6e, 0x58, 0x09, 0x7d, 0xe4, 0xc2, 0xee, + 0x46, 0xbd, 0x84, 0xf3, 0x81, 0x8b, 0x10, 0xb6, 0x8d, 0xa5, 0x7b, 0x92, 0xc3, 0xf2, 0x8c, 0x33, 0x37, 0x2f, 0xe0, + 0x76, 0x65, 0x0f, 0x65, 0xbf, 0x88, 0x68, 0x9d, 0x56, 0xe5, 0xa1, 0xd7, 0xc5, 0xb7, 0x8c, 0xe6, 0x6f, 0x2f, 0xe8, + 0x35, 0xd3, 0x09, 0xf0, 0x2f, 0x0c, 0x98, 0x7d, 0x67, 0xa4, 0xbe, 0x33, 0x30, 0x14, 0xd7, 0x34, 0xcd, 0xd7, 0x64, + 0x75, 0x75, 0x28, 0xa9, 0xf6, 0x5d, 0x0a, 0x12, 0x89, 0xdd, 0x86, 0x75, 0xbe, 0xc0, 0xb3, 0x28, 0x79, 0x2e, 0xe7, + 0xe5, 0x08, 0xad, 0x85, 0xfc, 0xc0, 0x02, 0xb0, 0x0c, 0x19, 0x09, 0x03, 0x1b, 0xe4, 0x44, 0x79, 0xd5, 0x34, 0xa5, + 0x4c, 0x7b, 0x3f, 0xa2, 0xdb, 0x63, 0x1a, 0xf4, 0x28, 0xb1, 0xfc, 0x42, 0x63, 0x1e, 0xf3, 0x58, 0x23, 0xe9, 0x85, + 0x36, 0x18, 0x77, 0xf0, 0x31, 0xcf, 0xb5, 0x34, 0x4f, 0xdd, 0x20, 0x9d, 0x11, 0x07, 0x6d, 0x3a, 0xf4, 0x27, 0x37, + 0xeb, 0x4b, 0xa5, 0xee, 0x46, 0x0c, 0x66, 0xd1, 0x80, 0x0e, 0x47, 0x39, 0xe0, 0xa9, 0x90, 0x40, 0x97, 0x0a, 0xfd, + 0x7e, 0x25, 0x46, 0x7a, 0x95, 0xd9, 0x7d, 0x1b, 0x07, 0x78, 0x12, 0x42, 0x8c, 0x48, 0x6d, 0xc8, 0x7d, 0x91, 0x45, + 0xfb, 0x05, 0xc5, 0x4d, 0x8e, 0x0c, 0xb0, 0xff, 0x52, 0x6a, 0xbf, 0x92, 0xb9, 0x75, 0xe2, 0xb1, 0xdc, 0x1a, 0xfe, + 0xce, 0x1a, 0xb6, 0x2a, 0x62, 0x2a, 0xc5, 0x1e, 0xdc, 0xef, 0xcf, 0xb3, 0x99, 0x5d, 0x90, 0x65, 0x13, 0xce, 0x38, + 0x73, 0x78, 0x8d, 0xd9, 0x11, 0x3d, 0x64, 0x0b, 0xf0, 0x0e, 0x85, 0xb3, 0xc3, 0xa5, 0xb4, 0xbc, 0x40, 0x43, 0x12, + 0x4b, 0xe9, 0x7f, 0xd6, 0xd9, 0x49, 0xa2, 0x7e, 0x98, 0x42, 0x9e, 0xfb, 0x1e, 0x9f, 0x6e, 0x76, 0x9a, 0x17, 0xd5, + 0x3f, 0xfa, 0x6c, 0xe3, 0x0b, 0xdc, 0xc7, 0x3a, 0xcb, 0xfc, 0xf5, 0x29, 0x22, 0x10, 0x2b, 0x8e, 0xd4, 0xd7, 0x36, + 0xe1, 0x98, 0xd7, 0xb7, 0x88, 0xdd, 0x8a, 0xdf, 0x58, 0x79, 0xb4, 0xda, 0x39, 0xaf, 0x63, 0xde, 0x2c, 0xcb, 0xe5, + 0x59, 0x02, 0x6e, 0xf1, 0x5e, 0xec, 0x65, 0xb5, 0xe7, 0x71, 0xc5, 0x3a, 0x65, 0x07, 0xb1, 0x5e, 0xb1, 0x86, 0x5e, + 0xd6, 0x6e, 0xde, 0x1f, 0xe0, 0x39, 0xc0, 0x04, 0x2d, 0x08, 0xcc, 0x7c, 0xbf, 0xcd, 0x53, 0x71, 0x1c, 0x58, 0x44, + 0x4f, 0x2a, 0xc9, 0x95, 0x32, 0xbe, 0x7f, 0xf9, 0xd7, 0x16, 0x92, 0x75, 0xc0, 0x90, 0x18, 0x18, 0x91, 0x12, 0xc7, + 0xce, 0x9b, 0x91, 0x5f, 0xf7, 0x50, 0xfb, 0x76, 0x5f, 0x70, 0xf7, 0xc4, 0x75, 0x62, 0x4f, 0x89, 0x43, 0xf2, 0xce, + 0x05, 0xf3, 0xa4, 0x24, 0x5a, 0x92, 0x7f, 0x2c, 0x10, 0xf9, 0xca, 0xb3, 0x96, 0xa5, 0x76, 0x47, 0x14, 0x22, 0x9f, + 0x38, 0x5f, 0x98, 0x80, 0xa0, 0x5b, 0x25, 0xb0, 0xbb, 0x09, 0x1e, 0x04, 0x7d, 0xdc, 0x8f, 0x7d, 0x74, 0xbb, 0x31, + 0x15, 0x1d, 0xe9, 0x0f, 0x15, 0x34, 0xec, 0xa4, 0x32, 0xb9, 0x80, 0x8c, 0x90, 0x9d, 0xf8, 0x14, 0x9f, 0x08, 0x98, + 0x12, 0x51, 0x30, 0x51, 0x4b, 0x6c, 0x1c, 0x5b, 0x70, 0xf5, 0x4f, 0xd3, 0x7a, 0x34, 0x1e, 0x1d, 0x30, 0xe3, 0x06, + 0xff, 0x78, 0xd6, 0x8e, 0x9b, 0x7e, 0xd0, 0x1b, 0xd7, 0x6e, 0x01, 0x9d, 0x35, 0x5e, 0xa2, 0x5e, 0xab, 0x5b, 0xa0, + 0x71, 0x95, 0xc0, 0x4a, 0x95, 0xb8, 0x99, 0xf0, 0x29, 0x48, 0xe6, 0x1e, 0x0b, 0x9f, 0xdc, 0xe3, 0x0f, 0x78, 0x28, + 0xd4, 0x02, 0xee, 0x0b, 0xf7, 0x69, 0x0f, 0xee, 0x0a, 0x55, 0x01, 0xb7, 0x85, 0xdf, 0x8a, 0xdf, 0x63, 0x37, 0x05, + 0x9f, 0x8a, 0x70, 0x83, 0x93, 0x28, 0x17, 0x2a, 0xcf, 0xa5, 0xbe, 0x35, 0x14, 0x70, 0xbd, 0x9a, 0xc1, 0xc3, 0x4b, + 0x5c, 0x0b, 0xf8, 0x48, 0x3d, 0xf6, 0x02, 0x2b, 0x4f, 0x67, 0x28, 0x8f, 0x0d, 0xdd, 0xfd, 0x7f, 0xdf, 0x35, 0x38, + 0x04, 0xde, 0x2c, 0x26, 0x45, 0x25, 0x28, 0xe4, 0xff, 0x53, 0x24, 0x30, 0x2b, 0x18, 0x01, 0xd3, 0x42, 0x29, 0x60, + 0x52, 0xd8, 0x4a, 0xdb, 0x76, 0x60, 0x58, 0x50, 0x19, 0x8c, 0x0b, 0x72, 0x03, 0xe7, 0x05, 0x51, 0xae, 0x3c, 0xb8, + 0x28, 0x28, 0x06, 0xa3, 0xc2, 0xa0, 0x86, 0xe5, 0xdf, 0x58, 0x6f, 0xd0, 0xa8, 0x2a, 0xf1, 0x01, 0x66, 0x91, 0xa2, + 0x5f, 0xaa, 0x96, 0x6d, 0xb1, 0x7a, 0x05, 0x2e, 0x9a, 0xbf, 0x23, 0xcd, 0x07, 0x48, 0x98, 0x05, 0x92, 0x98, 0xef, + 0x1a, 0x09, 0x20, 0xc1, 0x0f, 0x5f, 0x37, 0x2d, 0x0c, 0x35, 0x25, 0x79, 0x60, 0x17, 0xd3, 0x36, 0xf1, 0x7f, 0x6c, + 0x1a, 0x2c, 0x38, 0x49, 0xa6, 0xcd, 0xef, 0x8b, 0x15, 0xad, 0x96, 0xa4, 0x09, 0xd6, 0x4f, 0xcb, 0x94, 0x56, 0x01, + 0x7d, 0xa7, 0x94, 0x0c, 0x62, 0xe7, 0xba, 0x67, 0xc2, 0x77, 0xef, 0x1d, 0xe2, 0x8c, 0x4b, 0x85, 0xc4, 0x90, 0xd2, + 0xf3, 0xff, 0x64, 0x32, 0x27, 0x8a, 0x95, 0x7f, 0x9e, 0xc4, 0x8f, 0xfc, 0x98, 0x5d, 0xf5, 0x05, 0x7d, 0xfe, 0x57, + 0x72, 0x1c, 0xba, 0x65, 0xf6, 0x1e, 0xab, 0x26, 0x59, 0xf8, 0x3b, 0x0a, 0x5e, 0x23, 0x85, 0xaf, 0xe9, 0x96, 0x03, + 0xec, 0x3c, 0xc3, 0x10, 0x9b, 0x31, 0x30, 0xab, 0x18, 0xd4, 0x79, 0x75, 0x02, 0xe1, 0x84, 0xe6, 0x16, 0x77, 0xac, + 0x3a, 0xbe, 0x77, 0x2e, 0x5f, 0x01, 0x40, 0xd3, 0x7d, 0xd2, 0xf0, 0x48, 0xa8, 0x66, 0x12, 0x3a, 0x98, 0x90, 0x07, + 0xd3, 0x5d, 0x35, 0xa0, 0x0a, 0x99, 0xe8, 0x53, 0x69, 0x79, 0x45, 0xf0, 0xae, 0x6a, 0x95, 0x08, 0x03, 0xd7, 0x16, + 0x70, 0x0a, 0x52, 0xff, 0xb2, 0x77, 0x3d, 0x03, 0x4c, 0xc1, 0x81, 0xc2, 0x69, 0xed, 0xe9, 0x15, 0x3f, 0xb0, 0x12, + 0xeb, 0x86, 0x53, 0xf5, 0x07, 0x96, 0xea, 0x97, 0x53, 0x44, 0xe9, 0x35, 0x77, 0x85, 0x1a, 0x20, 0x7e, 0x45, 0xea, + 0x81, 0xeb, 0x05, 0xae, 0xa6, 0x76, 0x07, 0xca, 0xdc, 0x2a, 0x16, 0xda, 0xbb, 0x08, 0xb4, 0x0d, 0x43, 0xc8, 0x4f, + 0x66, 0x22, 0x12, 0x85, 0xaa, 0x25, 0xe7, 0x22, 0x3f, 0x82, 0x6b, 0xd5, 0x68, 0x8a, 0x22, 0xba, 0x71, 0x54, 0x07, + 0x29, 0x73, 0x22, 0x98, 0x4a, 0xa5, 0x0d, 0x52, 0xa0, 0x74, 0x15, 0xf4, 0xbf, 0x54, 0xcd, 0x63, 0xed, 0x7d, 0x98, + 0x7e, 0xa0, 0xb7, 0xd3, 0x57, 0xaa, 0x42, 0x7d, 0x89, 0x94, 0x8b, 0x73, 0x97, 0xa7, 0xbf, 0x38, 0x2f, 0x69, 0xd3, + 0x4d, 0x11, 0x88, 0xdd, 0x08, 0x94, 0x7c, 0xd2, 0x37, 0x8b, 0x30, 0x51, 0x59, 0x79, 0x42, 0x08, 0xd0, 0xeb, 0x12, + 0x37, 0xa9, 0xaf, 0xb5, 0xb8, 0x92, 0x48, 0xc0, 0x6c, 0xb8, 0x2e, 0xe4, 0x75, 0x2a, 0x2e, 0x43, 0x63, 0xcf, 0x3d, + 0x8b, 0x29, 0xc1, 0x88, 0x7c, 0xb4, 0x5d, 0xf1, 0xd8, 0x56, 0x8c, 0xbb, 0x53, 0x22, 0x57, 0x45, 0x9b, 0x46, 0x0e, + 0xc6, 0x94, 0x7a, 0xc9, 0x27, 0x01, 0xba, 0xda, 0x1d, 0xdf, 0xed, 0xd6, 0xd7, 0x1a, 0x34, 0xfb, 0x8c, 0xf6, 0x4d, + 0x88, 0x3f, 0x1b, 0x56, 0x44, 0x9e, 0x78, 0xc5, 0xf8, 0x1c, 0x0c, 0x1c, 0x34, 0xa3, 0x54, 0x6d, 0xa1, 0x0e, 0xf2, + 0xe8, 0x6b, 0x13, 0xa7, 0x01, 0xb8, 0xe7, 0xe2, 0x43, 0xdc, 0x37, 0x0a, 0x97, 0x30, 0xed, 0xc9, 0x3c, 0x7b, 0x98, + 0xeb, 0x2c, 0xac, 0x4a, 0xa1, 0x01, 0x04, 0x81, 0xba, 0x9e, 0x63, 0x9a, 0x1e, 0xed, 0xa5, 0x40, 0xb4, 0x9b, 0xdc, + 0xce, 0x02, 0xb5, 0xe6, 0x0b, 0xba, 0x1c, 0xf5, 0xc4, 0x8b, 0x77, 0xb4, 0xbc, 0xce, 0x2e, 0x43, 0xc0, 0x66, 0xd4, + 0xd0, 0xb6, 0x2c, 0x32, 0x2d, 0x6a, 0x34, 0xe0, 0x6a, 0xaa, 0x56, 0x28, 0x4a, 0xd4, 0xae, 0x68, 0x7d, 0x25, 0xd5, + 0x0e, 0xe4, 0xcc, 0x74, 0x5c, 0x72, 0x30, 0x05, 0x69, 0x24, 0xc2, 0xb2, 0xc9, 0xbc, 0xbe, 0xd1, 0xc1, 0x74, 0xf7, + 0xaa, 0xab, 0xc9, 0x26, 0x5b, 0x80, 0x65, 0xbf, 0xe1, 0x92, 0xcb, 0xb2, 0x15, 0xa3, 0x99, 0x1d, 0xbf, 0xd9, 0x2b, + 0x6d, 0x25, 0x1b, 0x6f, 0xd6, 0x3b, 0xdb, 0xad, 0xb5, 0x77, 0x0a, 0x44, 0x47, 0x54, 0xe7, 0xfe, 0x98, 0x8f, 0x58, + 0x4c, 0x06, 0xb0, 0xa7, 0x48, 0x21, 0x1a, 0x67, 0xeb, 0x6e, 0x8e, 0xc7, 0x37, 0xce, 0x03, 0xca, 0x2c, 0x2c, 0x68, + 0x40, 0xae, 0xcf, 0x43, 0xaf, 0xd7, 0x53, 0x44, 0xc2, 0x5d, 0x2d, 0x30, 0x54, 0x02, 0x72, 0x45, 0xba, 0x94, 0x7e, + 0x53, 0x66, 0x95, 0x34, 0x69, 0x92, 0x3b, 0x56, 0x03, 0x1b, 0x3a, 0x97, 0x84, 0xe4, 0x1f, 0xc6, 0x96, 0x53, 0xf8, + 0x2b, 0xd2, 0x5b, 0x36, 0x4a, 0xe1, 0x3d, 0xf7, 0x88, 0xc1, 0x73, 0x74, 0x76, 0x21, 0x6e, 0x5c, 0x96, 0x39, 0xc9, + 0x2a, 0x6d, 0x42, 0x3e, 0x91, 0xf1, 0x73, 0x88, 0x9d, 0xce, 0x58, 0xb2, 0x06, 0x19, 0xa7, 0xc2, 0x04, 0xa6, 0xc2, + 0x35, 0x75, 0xbb, 0x7a, 0xa1, 0x25, 0xa1, 0x06, 0xa4, 0xdb, 0x45, 0x96, 0x53, 0x5a, 0xf8, 0xf0, 0xae, 0xb6, 0x29, + 0xdc, 0xfe, 0xdd, 0x26, 0x3a, 0xb3, 0x42, 0x66, 0xd5, 0xf7, 0x11, 0x55, 0xd9, 0xf8, 0x76, 0x40, 0x97, 0x20, 0x54, + 0xe4, 0xaf, 0x7f, 0xf7, 0xe3, 0xd0, 0xbf, 0x55, 0xc1, 0x0f, 0x2d, 0x57, 0xdf, 0x42, 0x2e, 0x71, 0x49, 0x34, 0xb9, + 0x4a, 0x3e, 0x57, 0x79, 0x99, 0xf9, 0x10, 0x46, 0xde, 0x0f, 0x75, 0x7b, 0xb2, 0x0b, 0x25, 0x69, 0xfd, 0xe9, 0x91, + 0x9b, 0x9d, 0x94, 0x3a, 0xc9, 0xd1, 0xc8, 0x56, 0xfc, 0xfc, 0xaa, 0x6b, 0x66, 0x0d, 0xbe, 0x3d, 0x3d, 0x39, 0x8b, + 0x8a, 0x4f, 0xf3, 0xf6, 0x1c, 0x5b, 0x47, 0xd5, 0x80, 0x6d, 0x91, 0x95, 0xd6, 0xdb, 0x89, 0xd6, 0x84, 0xb8, 0x7c, + 0x84, 0xa1, 0x3b, 0x09, 0x7a, 0xe5, 0x97, 0x36, 0xd5, 0xa8, 0x3b, 0x2f, 0xc5, 0x49, 0x0e, 0x26, 0xf8, 0xe3, 0x1d, + 0x0a, 0xdf, 0xc2, 0x9f, 0x46, 0xb2, 0xe3, 0xea, 0x39, 0x61, 0x07, 0x2a, 0xd5, 0xa2, 0x16, 0x2a, 0xc2, 0x46, 0x88, + 0x2d, 0x1c, 0x40, 0x90, 0xf7, 0xe3, 0x56, 0x56, 0x96, 0xc5, 0x70, 0x3e, 0x2d, 0x92, 0x07, 0x42, 0x5e, 0x74, 0xe3, + 0x56, 0x3a, 0x1d, 0x58, 0x49, 0x42, 0x43, 0x8e, 0xce, 0xd6, 0xfa, 0x77, 0xe5, 0x67, 0xb5, 0x21, 0x2a, 0x3e, 0x80, + 0xd9, 0x88, 0x96, 0x7c, 0xe5, 0x97, 0x60, 0xa8, 0x8a, 0xee, 0xd7, 0x24, 0x4d, 0x07, 0x56, 0x06, 0x52, 0x02, 0x89, + 0x6d, 0xcc, 0xb9, 0x3d, 0xf4, 0xb0, 0xc0, 0x7c, 0x48, 0x72, 0x8d, 0x6a, 0xac, 0xf7, 0x0f, 0xcf, 0xc3, 0xa8, 0xf5, + 0x71, 0x1c, 0x64, 0xe9, 0x7a, 0x1a, 0xc6, 0x7a, 0x3a, 0xa1, 0x50, 0xde, 0x95, 0x29, 0x6e, 0x53, 0xac, 0xe4, 0x46, + 0xdf, 0x8d, 0xb6, 0x8d, 0x37, 0x02, 0xe0, 0xc1, 0xde, 0xcf, 0xaf, 0x83, 0x29, 0xe9, 0xb1, 0x09, 0x59, 0x8f, 0x28, + 0x33, 0xea, 0xe4, 0x84, 0x46, 0x92, 0x52, 0x90, 0xe0, 0xcc, 0x98, 0xed, 0x8f, 0xa5, 0x64, 0x23, 0xc9, 0x3e, 0xc2, + 0xf2, 0xed, 0xd1, 0x52, 0x0d, 0x2b, 0x86, 0xec, 0x63, 0x79, 0xef, 0xed, 0xb5, 0xa8, 0x84, 0x83, 0x01, 0x10, 0xdf, + 0x6e, 0xe0, 0x04, 0xcf, 0x54, 0x2f, 0x87, 0xce, 0xe0, 0x41, 0xa1, 0x08, 0x6b, 0xda, 0xe1, 0x22, 0x6c, 0xfa, 0x53, + 0x43, 0xe8, 0x1e, 0xc5, 0x14, 0x29, 0x1e, 0x48, 0x85, 0x8f, 0x15, 0x41, 0x36, 0x3e, 0x2e, 0x86, 0xbe, 0x14, 0xcc, + 0x9d, 0x83, 0xea, 0x33, 0x25, 0xea, 0xd5, 0xcf, 0x59, 0xde, 0x16, 0x9b, 0xde, 0x79, 0xbc, 0xc9, 0x23, 0x49, 0xb6, + 0x97, 0xd9, 0x17, 0xfe, 0x2f, 0x3d, 0xcb, 0xc2, 0xe1, 0xb4, 0x74, 0x8e, 0x20, 0xf8, 0x81, 0x36, 0x64, 0x33, 0x0b, + 0x10, 0x19, 0x29, 0xdd, 0x8c, 0x2e, 0xac, 0xbc, 0xce, 0x89, 0x39, 0x35, 0x67, 0xbd, 0x08, 0x37, 0x95, 0x41, 0x88, + 0xcd, 0xd3, 0x4a, 0xa4, 0x77, 0x5b, 0x09, 0x8e, 0xcd, 0x43, 0x89, 0x6f, 0xd9, 0xd9, 0x22, 0x35, 0x16, 0xa5, 0xb7, + 0x10, 0x7d, 0x2b, 0x38, 0xce, 0xf2, 0xc8, 0xd4, 0x7d, 0x0c, 0x6b, 0x26, 0x6f, 0x4e, 0xdc, 0x50, 0x41, 0xa2, 0x2d, + 0x3f, 0x3d, 0xd0, 0xbb, 0x5d, 0x34, 0xc9, 0xa8, 0xcd, 0xc7, 0x2b, 0x41, 0xf6, 0x04, 0xbb, 0xc1, 0xed, 0xae, 0xee, + 0x7e, 0x60, 0x73, 0xe7, 0xcd, 0x05, 0x00, 0xd6, 0xe9, 0xbd, 0xbb, 0xf5, 0x98, 0x4c, 0x29, 0xb8, 0x76, 0x73, 0xf9, + 0xef, 0x45, 0x8b, 0xb4, 0xe7, 0x5b, 0xd0, 0xfd, 0xb2, 0xd0, 0x74, 0x0f, 0xa1, 0x57, 0xd8, 0x61, 0xef, 0x47, 0xc3, + 0xa9, 0x08, 0x51, 0x2f, 0x99, 0xa1, 0x0d, 0x66, 0xf4, 0x2a, 0x56, 0x26, 0x6f, 0x66, 0xc5, 0x95, 0x27, 0xe1, 0x45, + 0xea, 0x3c, 0x5b, 0x5d, 0x55, 0xb9, 0x4f, 0x8a, 0x2a, 0xf2, 0xc2, 0xb5, 0x42, 0x19, 0x93, 0xa2, 0x5a, 0x0a, 0x8f, + 0x43, 0x17, 0x36, 0x44, 0x65, 0x3a, 0x8d, 0x5e, 0x60, 0xa2, 0x20, 0x75, 0x0e, 0x69, 0x9a, 0xcd, 0xe2, 0xfb, 0x0c, + 0xe7, 0x45, 0xac, 0x3c, 0x64, 0x62, 0x2f, 0xb6, 0xaa, 0xb3, 0x79, 0xbe, 0xcb, 0x8f, 0x2b, 0x7e, 0x67, 0xa2, 0xf0, + 0x87, 0xaf, 0xf0, 0x91, 0xdb, 0x69, 0x77, 0x83, 0x1d, 0x2b, 0x5b, 0xb8, 0x33, 0xa8, 0x60, 0xec, 0x17, 0xfd, 0x2a, + 0x39, 0xc3, 0xaf, 0xd2, 0xb9, 0x7a, 0x96, 0x5f, 0x65, 0xbd, 0x8a, 0x5b, 0xd2, 0x2d, 0xf8, 0xdf, 0xdc, 0x4e, 0xb5, + 0xf9, 0x01, 0x67, 0xf2, 0xd5, 0x8d, 0x30, 0x17, 0x64, 0x9c, 0xaf, 0xf9, 0xa0, 0x0e, 0x9e, 0xa4, 0xaa, 0xb0, 0x20, + 0x0b, 0x62, 0xbd, 0x01, 0xd0, 0x45, 0xfe, 0x56, 0x53, 0x41, 0x84, 0x8b, 0x81, 0xab, 0xfd, 0x04, 0xf5, 0x01, 0x18, + 0xca, 0x5c, 0x8e, 0xf0, 0x60, 0x7c, 0x85, 0x46, 0x62, 0x64, 0x97, 0x30, 0x18, 0x8f, 0xdb, 0xbe, 0xfe, 0x56, 0x5c, + 0x55, 0xcd, 0x4e, 0xbb, 0x83, 0xa1, 0x89, 0xd5, 0xb3, 0xb0, 0xa0, 0x49, 0x72, 0xe6, 0x9e, 0x3f, 0x16, 0x84, 0xf2, + 0xfc, 0x67, 0xc2, 0xfe, 0x58, 0x13, 0x09, 0x4e, 0x6a, 0x6a, 0x7b, 0x23, 0x52, 0xbd, 0x30, 0x62, 0x29, 0xed, 0x2b, + 0xd3, 0x73, 0x0d, 0x9e, 0x79, 0x4a, 0x18, 0xf5, 0x34, 0xaa, 0xaf, 0x6d, 0x2e, 0x77, 0xf2, 0x72, 0xfe, 0xe3, 0x5a, + 0x0b, 0x1a, 0x32, 0xcf, 0x11, 0x1b, 0xb0, 0x0e, 0x42, 0xc9, 0xfc, 0x62, 0x3e, 0x9d, 0x48, 0x4b, 0x36, 0xbf, 0x9a, + 0x0f, 0x8b, 0x81, 0xea, 0x6d, 0xf5, 0xb1, 0x9a, 0x46, 0x18, 0x3e, 0xc4, 0x9b, 0x17, 0x4a, 0x8c, 0xee, 0x46, 0xa7, + 0xa0, 0xae, 0xf4, 0xfe, 0xf8, 0xdb, 0xf6, 0x6c, 0x77, 0x33, 0xb8, 0xbf, 0xc8, 0x35, 0xd2, 0xdf, 0xa0, 0x3c, 0xcb, + 0x6b, 0x07, 0x23, 0x22, 0x35, 0xd9, 0x76, 0x4a, 0x40, 0xdf, 0xcf, 0xd4, 0xbd, 0x14, 0x89, 0x1a, 0xa5, 0x6e, 0xae, + 0x47, 0xe3, 0xb0, 0x86, 0x8c, 0xf8, 0x4b, 0xd9, 0x8f, 0x03, 0x3a, 0xe2, 0xc9, 0xb0, 0x50, 0x8f, 0x0c, 0xf7, 0x70, + 0x67, 0x03, 0x77, 0x9b, 0xd6, 0x9f, 0x4f, 0x92, 0x5c, 0x57, 0x1a, 0x7a, 0xd0, 0x4d, 0x71, 0x42, 0xc3, 0xcd, 0x87, + 0x19, 0x22, 0x88, 0xcb, 0x33, 0x5c, 0x4f, 0x7f, 0xa9, 0xa1, 0xed, 0x7d, 0xf3, 0x59, 0x16, 0xa9, 0xf1, 0x09, 0xf3, + 0xc8, 0xf6, 0x00, 0xf3, 0x63, 0x75, 0xfb, 0x65, 0x1f, 0x22, 0xb0, 0x51, 0x0f, 0x6a, 0x7c, 0xd1, 0x7f, 0x7d, 0xc7, + 0x80, 0x38, 0xfd, 0x3d, 0x25, 0xd5, 0x78, 0x2e, 0x2d, 0xbe, 0x3b, 0x25, 0xd4, 0x68, 0x2e, 0x9c, 0x97, 0xe4, 0x1c, + 0x65, 0xb7, 0xd2, 0x31, 0xfa, 0xa6, 0x08, 0x98, 0xce, 0x02, 0x56, 0x6f, 0xf6, 0xef, 0x41, 0x84, 0x99, 0x8a, 0xa9, + 0x4c, 0x3f, 0xc9, 0x22, 0x8c, 0xd1, 0xe9, 0x6a, 0xa0, 0x72, 0x40, 0xd2, 0xb7, 0x3b, 0xdf, 0xba, 0x27, 0x8a, 0x00, + 0xe2, 0x36, 0xcf, 0xd2, 0x90, 0x40, 0x1d, 0x3e, 0xe4, 0xfa, 0xf6, 0x12, 0x2f, 0xf1, 0x1c, 0x8b, 0xb7, 0xc9, 0xbd, + 0x75, 0xc1, 0x89, 0x45, 0x7f, 0x1e, 0xd3, 0x61, 0xa3, 0x8d, 0xb2, 0xe5, 0x46, 0x33, 0x74, 0xe8, 0xaa, 0xe8, 0x8a, + 0x02, 0x7d, 0xc1, 0xe4, 0x49, 0x63, 0xf3, 0xad, 0x5d, 0x06, 0x36, 0x81, 0xdb, 0x9c, 0xc1, 0x81, 0xdd, 0xa9, 0x39, + 0x3b, 0x8e, 0xe7, 0x82, 0xde, 0x3c, 0x56, 0xa0, 0x3f, 0x74, 0xf9, 0xc6, 0x82, 0xab, 0x39, 0x63, 0x50, 0x83, 0x1a, + 0xf0, 0x75, 0x8f, 0xa3, 0x66, 0x17, 0x22, 0x7b, 0xaf, 0x86, 0x14, 0x53, 0xca, 0xe6, 0x2e, 0x0b, 0x6c, 0x63, 0x07, + 0xaa, 0x41, 0xd3, 0x31, 0x80, 0xb4, 0x77, 0xf1, 0x19, 0xf5, 0xbf, 0xb5, 0xb7, 0xaf, 0xc2, 0x3d, 0x0c, 0xd4, 0x24, + 0x80, 0xd6, 0xbd, 0x67, 0xd6, 0x10, 0x24, 0xbc, 0x8d, 0xe9, 0x96, 0xfe, 0xec, 0x8b, 0x98, 0xdc, 0x40, 0xcd, 0x25, + 0x31, 0xbc, 0x88, 0x32, 0x93, 0xa9, 0xe1, 0x32, 0xf9, 0x76, 0x2a, 0x16, 0xce, 0x9d, 0x49, 0x40, 0xf7, 0xea, 0xdd, + 0x24, 0x4b, 0x4f, 0x14, 0x24, 0x88, 0x72, 0x33, 0x0c, 0x64, 0xee, 0xeb, 0x61, 0x19, 0xd6, 0x13, 0xbe, 0xbb, 0x5b, + 0xfb, 0xcb, 0xe3, 0x1e, 0x2f, 0xc5, 0xdf, 0xb6, 0xc2, 0xe7, 0x48, 0xdf, 0x68, 0x20, 0x4d, 0xd1, 0xab, 0x1d, 0x45, + 0xd4, 0x17, 0x2b, 0x43, 0x25, 0x90, 0x4a, 0x29, 0x9c, 0xd0, 0xbf, 0xd8, 0x44, 0x41, 0x5b, 0xd1, 0x78, 0x6d, 0x4e, + 0x3c, 0x2c, 0x3c, 0xf5, 0x82, 0x7b, 0x59, 0x0f, 0xd9, 0x7b, 0xb5, 0x45, 0x34, 0x7c, 0x2a, 0xfd, 0xaa, 0x33, 0xf9, + 0x43, 0xbe, 0x0a, 0xa3, 0xb7, 0xef, 0xf9, 0x6b, 0x1d, 0xf4, 0x9d, 0xfe, 0xdf, 0x8f, 0xf8, 0x6c, 0x4f, 0xfb, 0x64, + 0xf7, 0xf7, 0x6d, 0xc0, 0xf4, 0xe3, 0x89, 0xa6, 0xa7, 0x58, 0xe7, 0x6e, 0x52, 0x92, 0xdf, 0x27, 0xaa, 0x36, 0xf1, + 0x98, 0x8d, 0x7f, 0x4b, 0x14, 0x80, 0xe4, 0xd6, 0x1b, 0xd1, 0xea, 0xf5, 0x14, 0xbf, 0xff, 0x4d, 0x61, 0xc7, 0x98, + 0x1c, 0xe9, 0x15, 0x26, 0xe9, 0x7b, 0x01, 0xbc, 0xc0, 0xcd, 0x26, 0x10, 0xeb, 0x31, 0xc0, 0x61, 0xb6, 0x7d, 0x0f, + 0xb4, 0x48, 0x83, 0x59, 0x83, 0x52, 0xf0, 0x64, 0xb7, 0xec, 0x78, 0xcd, 0xf7, 0xd1, 0x1f, 0x6d, 0xa1, 0x4f, 0x6e, + 0xd6, 0x7a, 0xea, 0x04, 0xcf, 0xf3, 0x02, 0x9f, 0xa0, 0x79, 0x5a, 0xa0, 0xb3, 0x28, 0x85, 0x5f, 0x88, 0x06, 0x87, + 0x10, 0xd1, 0x3a, 0x48, 0x17, 0xcc, 0x0a, 0x37, 0xbf, 0x28, 0x8b, 0xfc, 0xaa, 0x5e, 0xec, 0xda, 0x22, 0x18, 0x77, + 0xab, 0x10, 0xe5, 0x70, 0xcb, 0xef, 0x79, 0x55, 0x42, 0x72, 0x97, 0xc9, 0xf2, 0x5d, 0x15, 0x3f, 0x9f, 0x9d, 0x35, + 0x5e, 0xcc, 0x33, 0xc5, 0xac, 0x5f, 0x04, 0xaf, 0x99, 0xa8, 0xb6, 0xd1, 0x9f, 0x22, 0x53, 0x60, 0xd3, 0x4c, 0xea, + 0xe2, 0x9f, 0x47, 0x4c, 0xe1, 0x8c, 0xeb, 0x72, 0xce, 0xaa, 0x79, 0x7c, 0x69, 0xe2, 0x88, 0x28, 0x27, 0x5d, 0x30, + 0x5b, 0x19, 0xee, 0xf2, 0xa7, 0x7d, 0x5b, 0x46, 0xe7, 0x89, 0xb4, 0x22, 0xd9, 0xed, 0x97, 0xeb, 0xdb, 0x38, 0x8b, + 0x73, 0xdf, 0xfa, 0xe2, 0x7b, 0x54, 0x51, 0x8d, 0xb7, 0x4f, 0x7a, 0xd3, 0x1a, 0x2b, 0x67, 0x86, 0x7a, 0x78, 0x11, + 0xb9, 0xe9, 0x1b, 0xd9, 0x2a, 0x8b, 0xb3, 0x5d, 0xea, 0x47, 0xfa, 0x07, 0x5f, 0x8a, 0x02, 0x76, 0x35, 0xf8, 0x3e, + 0x2f, 0x49, 0xe7, 0x35, 0x5e, 0xa4, 0x90, 0x8c, 0x33, 0x8b, 0x36, 0xca, 0x5b, 0x0f, 0xab, 0x2e, 0xe0, 0xfb, 0x1d, + 0x83, 0x1b, 0x60, 0x0e, 0x33, 0xab, 0x8d, 0x9b, 0x37, 0xbb, 0x8c, 0x93, 0x7b, 0x2a, 0x82, 0x79, 0xcf, 0xa1, 0xb1, + 0xb7, 0xb9, 0x7e, 0xb1, 0xd1, 0x47, 0xbc, 0x8e, 0x70, 0xfc, 0x8c, 0x40, 0xf8, 0xc5, 0x14, 0xde, 0xd7, 0x9d, 0x59, + 0xc2, 0x34, 0xcf, 0x8c, 0xd3, 0x11, 0xc2, 0x25, 0x83, 0x70, 0x7f, 0x6f, 0xf5, 0xb4, 0x78, 0x02, 0xfb, 0xab, 0x72, + 0x56, 0x37, 0xd1, 0x3f, 0x4f, 0x62, 0x5a, 0xbb, 0x5c, 0xc3, 0x61, 0xf0, 0xea, 0x24, 0xcd, 0x3f, 0x75, 0xa1, 0xca, + 0x0c, 0x93, 0xc3, 0xa6, 0x4c, 0xb9, 0x10, 0xef, 0x1f, 0x76, 0x4d, 0xbd, 0xd4, 0xdb, 0x38, 0xe7, 0x22, 0x0b, 0x93, + 0xdf, 0x0a, 0x8e, 0x73, 0xf9, 0xc6, 0x05, 0xed, 0x41, 0x2b, 0x9b, 0x34, 0xab, 0x57, 0xbc, 0x5e, 0x7e, 0x6d, 0xbf, + 0x42, 0xe1, 0x28, 0xee, 0x2f, 0x39, 0xda, 0xc5, 0xe5, 0x85, 0xc2, 0x44, 0x83, 0x32, 0x78, 0xd9, 0xb9, 0x81, 0xfe, + 0xc3, 0xe3, 0xd2, 0x27, 0x77, 0x9e, 0xa2, 0x88, 0x26, 0xc2, 0xf9, 0x0f, 0xa1, 0x3a, 0xe5, 0xce, 0x67, 0x9d, 0x9b, + 0x60, 0xb0, 0x76, 0x0d, 0x10, 0xb2, 0xc4, 0x26, 0xd3, 0x8f, 0x65, 0x97, 0x44, 0xcd, 0xa9, 0x4e, 0x7a, 0x68, 0xec, + 0xa4, 0x24, 0x17, 0xde, 0x0d, 0xb7, 0xb9, 0x93, 0x55, 0xcb, 0xf2, 0x29, 0xe7, 0x48, 0x56, 0x5d, 0x28, 0xff, 0x33, + 0xaa, 0xa1, 0x1e, 0x72, 0xf5, 0x33, 0xed, 0x8a, 0x80, 0xae, 0x89, 0xfd, 0xcf, 0xbb, 0x56, 0xbb, 0x41, 0xf8, 0x4c, + 0x0d, 0x5a, 0x9b, 0xc4, 0x44, 0xb4, 0x2a, 0x44, 0xe0, 0x90, 0x20, 0x16, 0x89, 0xbe, 0xad, 0x7a, 0x9f, 0xc7, 0x84, + 0x14, 0x6e, 0xfe, 0xfd, 0x71, 0xa2, 0x00, 0xaa, 0xa2, 0x57, 0xa6, 0x70, 0xb6, 0x49, 0x81, 0x11, 0xae, 0x87, 0xf8, + 0x57, 0x43, 0x2e, 0x34, 0x70, 0x37, 0x19, 0xb5, 0xb3, 0x17, 0x25, 0x49, 0xc4, 0x35, 0xcd, 0xc7, 0x76, 0x6c, 0x2a, + 0xb6, 0xba, 0x84, 0x6e, 0x0f, 0xec, 0xf5, 0xb2, 0x30, 0x46, 0xd3, 0x06, 0x89, 0x62, 0xac, 0xbd, 0x05, 0x56, 0x6d, + 0xcb, 0xa0, 0xb2, 0x27, 0x6e, 0x88, 0x4a, 0x68, 0x74, 0xd1, 0x2b, 0x8c, 0x37, 0x74, 0x60, 0xd2, 0x8b, 0x67, 0x31, + 0x6d, 0xd5, 0xad, 0xb9, 0xe6, 0x9e, 0x74, 0x53, 0x95, 0xd5, 0x29, 0x97, 0x64, 0x35, 0xe5, 0x46, 0x77, 0xb3, 0x90, + 0x9b, 0x0e, 0x82, 0xe1, 0xb7, 0xe6, 0x54, 0xcc, 0x5e, 0x02, 0x76, 0xb9, 0x02, 0x45, 0xe9, 0xee, 0x17, 0x05, 0x4c, + 0x1c, 0x2f, 0x6a, 0xf2, 0x0d, 0x2f, 0x9a, 0x05, 0x95, 0x13, 0xdf, 0xef, 0x12, 0xfd, 0xa8, 0x96, 0x4f, 0x54, 0xf8, + 0x80, 0x3c, 0xb8, 0x16, 0xbc, 0x5c, 0x98, 0x5c, 0x65, 0xe9, 0x4c, 0x5c, 0xcb, 0xa6, 0x59, 0xd9, 0x95, 0x63, 0x2e, + 0x1a, 0xaf, 0xe7, 0x40, 0xa3, 0xa6, 0xbc, 0xf5, 0x4d, 0xa5, 0x67, 0x95, 0xd4, 0x44, 0x4e, 0xcf, 0xb1, 0x44, 0xea, + 0x98, 0x5c, 0x0a, 0x5f, 0xfc, 0xb5, 0x14, 0x52, 0xeb, 0xad, 0xa5, 0x9d, 0x18, 0x84, 0xbb, 0xce, 0x27, 0x5c, 0x0a, + 0x5e, 0xfc, 0x62, 0x01, 0x3a, 0x15, 0x79, 0x03, 0xd7, 0x8b, 0x85, 0x3d, 0xf3, 0xf3, 0x16, 0xbd, 0xad, 0x71, 0xdc, + 0x5c, 0x24, 0xf4, 0xfb, 0x36, 0x9f, 0x4a, 0xea, 0x60, 0x9f, 0x1f, 0xe1, 0xc1, 0xae, 0x2b, 0xf2, 0x5a, 0x49, 0x07, + 0x98, 0x20, 0x0c, 0xac, 0x6c, 0x25, 0xee, 0x41, 0x10, 0xe1, 0x05, 0xdc, 0x03, 0x52, 0x9b, 0xa5, 0xfb, 0xf9, 0x2a, + 0xba, 0x7f, 0x89, 0xf9, 0xfd, 0x14, 0x58, 0xa8, 0x57, 0x93, 0x61, 0x78, 0x0b, 0x0c, 0x1a, 0x89, 0x50, 0x04, 0x4e, + 0xc8, 0x5b, 0x24, 0x98, 0xba, 0x46, 0xed, 0x26, 0x38, 0xa2, 0xff, 0xd1, 0x4b, 0xc7, 0xac, 0x61, 0x46, 0xfc, 0x82, + 0x11, 0xa3, 0xf0, 0xca, 0xc7, 0x77, 0xce, 0x10, 0xee, 0x9a, 0xa7, 0xe8, 0xc8, 0x2f, 0xb7, 0xf7, 0xf4, 0xa2, 0x2b, + 0xfc, 0x20, 0x63, 0x00, 0x5d, 0xe4, 0x2a, 0x2f, 0xc4, 0x78, 0x51, 0x5b, 0x37, 0x40, 0x15, 0xc2, 0x93, 0x1b, 0x86, + 0x57, 0x93, 0x43, 0x73, 0x73, 0x1a, 0xc3, 0x8a, 0x3c, 0x6c, 0xba, 0x0e, 0x83, 0x3c, 0x79, 0xce, 0xa6, 0x1a, 0x4e, + 0xff, 0xb5, 0x9a, 0xda, 0x78, 0x51, 0x7b, 0x1c, 0x43, 0x29, 0x5a, 0xe9, 0x74, 0x02, 0x61, 0x51, 0x1d, 0x24, 0xf7, + 0x62, 0x37, 0x87, 0xbb, 0x7d, 0xef, 0xd3, 0x17, 0x12, 0x53, 0xb8, 0xf1, 0xea, 0x6c, 0xc7, 0x02, 0x2e, 0xc5, 0x3c, + 0xbd, 0xf0, 0x76, 0x17, 0xc9, 0x40, 0xd8, 0x3f, 0xdb, 0x9c, 0x4c, 0x94, 0x22, 0xbd, 0x44, 0x2a, 0x17, 0x26, 0x48, + 0x76, 0x02, 0xb2, 0xfb, 0x2f, 0x54, 0x9b, 0x46, 0xa0, 0xb6, 0x9b, 0xf0, 0xf6, 0xee, 0xb6, 0x27, 0xef, 0xde, 0xab, + 0xd5, 0x08, 0x9b, 0x6a, 0x8c, 0x43, 0x4c, 0xab, 0xa7, 0xd8, 0x2d, 0x15, 0x20, 0xb4, 0x58, 0x82, 0x73, 0xcf, 0xcd, + 0x9b, 0xc7, 0xdb, 0x83, 0x99, 0x91, 0xd9, 0xe7, 0xda, 0xff, 0xbe, 0xc9, 0x06, 0x8b, 0x4b, 0xb7, 0x57, 0x3e, 0x44, + 0xda, 0x72, 0x59, 0xf2, 0x44, 0x30, 0xa3, 0x09, 0x8c, 0x93, 0xf3, 0xce, 0xb0, 0x55, 0x4e, 0xdd, 0xa1, 0xc5, 0x58, + 0xe5, 0x91, 0xc8, 0x95, 0xe0, 0x12, 0xfa, 0x6c, 0x8f, 0x02, 0x13, 0x69, 0x32, 0xb6, 0x6f, 0x02, 0x61, 0x07, 0x2a, + 0xac, 0xbe, 0x80, 0x78, 0x9b, 0x38, 0x64, 0xe0, 0x35, 0x51, 0x28, 0xde, 0xb7, 0xa6, 0x7c, 0x3f, 0x29, 0xe7, 0x35, + 0x9a, 0x43, 0x6a, 0x7a, 0x8b, 0xfc, 0x3e, 0x58, 0x1f, 0xeb, 0xaf, 0xe4, 0xe0, 0xe5, 0xcc, 0xd0, 0x2f, 0x8c, 0x9c, + 0x73, 0x1b, 0x87, 0x61, 0x38, 0x88, 0x55, 0x60, 0xea, 0xc1, 0xaa, 0x58, 0x3b, 0xe4, 0xf9, 0x92, 0xd7, 0x54, 0x80, + 0x3b, 0x7a, 0x16, 0x51, 0x58, 0xac, 0xc0, 0x9a, 0xae, 0x21, 0xaf, 0xc4, 0x13, 0xf9, 0x97, 0x7d, 0x62, 0x0e, 0x12, + 0x61, 0x1b, 0xe8, 0x91, 0x58, 0xff, 0x41, 0x40, 0x46, 0x27, 0xbb, 0xd1, 0xe8, 0xfa, 0xa5, 0xa5, 0xaa, 0x15, 0xb4, + 0x93, 0xfa, 0x36, 0xf7, 0x7d, 0x19, 0xcf, 0xe2, 0x38, 0xb5, 0xad, 0x3e, 0xcc, 0x2c, 0xb3, 0xb0, 0xfe, 0x04, 0xe3, + 0xd6, 0x30, 0xab, 0xf2, 0xff, 0x20, 0x1f, 0x2f, 0xe9, 0xd1, 0x3e, 0xc0, 0x4c, 0xe7, 0xf4, 0x6b, 0x91, 0x33, 0x77, + 0x4e, 0x9b, 0x4d, 0x36, 0xc6, 0xc5, 0xee, 0x80, 0xd3, 0x66, 0xfd, 0xe4, 0x5d, 0xb8, 0x79, 0x4f, 0x5f, 0xf6, 0xf0, + 0xf6, 0x13, 0x77, 0x7b, 0x27, 0xac, 0x82, 0xf7, 0xfe, 0xf3, 0x06, 0x57, 0x57, 0xff, 0x0d, 0x0b, 0x8c, 0xe7, 0xb5, + 0x07, 0x73, 0x9a, 0x63, 0xba, 0x8c, 0xfd, 0x85, 0xf9, 0xa8, 0xef, 0xdd, 0x1d, 0x46, 0x04, 0xaa, 0xf8, 0x72, 0x28, + 0x4e, 0x99, 0x74, 0x80, 0x40, 0x75, 0x7d, 0xfc, 0xd5, 0x77, 0x46, 0xa9, 0x4c, 0x38, 0x7e, 0x46, 0x53, 0x8d, 0x71, + 0xb8, 0x23, 0xb8, 0x90, 0xad, 0x47, 0xfa, 0x39, 0xef, 0x06, 0x3c, 0xe6, 0x79, 0x54, 0xc3, 0x55, 0x90, 0x19, 0x8d, + 0x33, 0x8b, 0x82, 0x39, 0x77, 0xfd, 0x09, 0x18, 0x40, 0xcc, 0x45, 0x97, 0x06, 0xba, 0x55, 0x2f, 0xe4, 0xa9, 0x4b, + 0x1b, 0x36, 0xf7, 0xfd, 0xaf, 0x37, 0xf3, 0x8e, 0xef, 0xb6, 0x65, 0x43, 0xa1, 0xde, 0x2e, 0xcf, 0x8a, 0x65, 0xd8, + 0x5d, 0x72, 0x99, 0x5d, 0xff, 0x7f, 0x17, 0xd0, 0x7a, 0x11, 0xa2, 0x64, 0x6b, 0x29, 0x20, 0x5c, 0x6c, 0x73, 0xff, + 0x11, 0xbd, 0x71, 0x68, 0x0d, 0x27, 0x1e, 0x73, 0x76, 0xbf, 0x1b, 0x18, 0xc4, 0x71, 0x7e, 0x21, 0xe0, 0xd6, 0xd4, + 0x8d, 0x12, 0x9f, 0x14, 0xef, 0xfa, 0xe1, 0x02, 0x81, 0xff, 0x2f, 0x7e, 0x8e, 0x01, 0xfe, 0xd7, 0xd1, 0x23, 0xba, + 0xe1, 0x67, 0xa4, 0x78, 0xeb, 0x23, 0x77, 0xbe, 0x93, 0xf3, 0x09, 0xee, 0x51, 0xe0, 0x9d, 0x7a, 0x6d, 0xcd, 0xe5, + 0xc6, 0x5e, 0xa6, 0x3c, 0x97, 0x6d, 0xd6, 0x8b, 0x32, 0xcd, 0x64, 0xf8, 0x30, 0x99, 0xcc, 0xca, 0x41, 0x1b, 0x65, + 0x57, 0x2e, 0x46, 0x48, 0x5b, 0x64, 0xf4, 0x6f, 0x47, 0x12, 0x25, 0x53, 0xba, 0x9d, 0x3d, 0x8a, 0x5e, 0x28, 0xf4, + 0xc9, 0x92, 0x81, 0xe5, 0x75, 0x80, 0x5a, 0xe2, 0xae, 0x42, 0x24, 0x84, 0x64, 0x1a, 0x00, 0xfb, 0x24, 0xd0, 0x50, + 0xf8, 0x52, 0xcf, 0x49, 0xeb, 0x17, 0x8e, 0x89, 0x20, 0xe9, 0x21, 0x3a, 0x4a, 0x25, 0x53, 0x66, 0x7c, 0xab, 0x6b, + 0xf5, 0x74, 0x7e, 0xeb, 0x8c, 0x67, 0x9f, 0x8f, 0xfc, 0xcf, 0xdc, 0x9c, 0x08, 0x8b, 0xad, 0x27, 0x50, 0x21, 0xaf, + 0x3c, 0x65, 0xab, 0x17, 0x0c, 0x93, 0x3a, 0x7e, 0xb3, 0xb1, 0xba, 0xf4, 0x5d, 0x12, 0x90, 0xd5, 0xfe, 0x24, 0xbd, + 0xc6, 0xe5, 0xaf, 0x9b, 0xee, 0x19, 0xf3, 0xeb, 0x55, 0x8e, 0x6a, 0x35, 0x5b, 0x4c, 0xf1, 0xe6, 0x5a, 0xea, 0xc9, + 0xb8, 0xe7, 0xd8, 0x13, 0x2a, 0xc9, 0x8b, 0x78, 0x15, 0xf6, 0x6c, 0xbe, 0x41, 0xdb, 0x41, 0xea, 0x05, 0x53, 0xd3, + 0xfa, 0x74, 0x6e, 0xdf, 0xcd, 0xac, 0x3b, 0xb3, 0xdc, 0x65, 0xaf, 0x5b, 0xc3, 0x81, 0xfb, 0xc5, 0x98, 0x4f, 0x39, + 0xe4, 0xb3, 0xda, 0x9a, 0x4b, 0x2e, 0xc8, 0x29, 0xc2, 0xda, 0x6b, 0xc3, 0x90, 0x89, 0x5b, 0x1b, 0x16, 0x8d, 0x4f, + 0x17, 0xc4, 0x38, 0x48, 0x9a, 0xef, 0x68, 0xbf, 0x16, 0xf5, 0xf1, 0xf5, 0xf1, 0xdc, 0xd1, 0xba, 0x7b, 0xd2, 0xd9, + 0x6f, 0xf7, 0x81, 0x23, 0x8e, 0xca, 0x35, 0x82, 0x28, 0x16, 0x22, 0x01, 0x5d, 0x6b, 0x34, 0xd7, 0xcb, 0x9a, 0x7b, + 0xa8, 0xc9, 0xc7, 0x2d, 0x26, 0xab, 0x95, 0x39, 0xaf, 0xe9, 0x93, 0x11, 0xc3, 0x7c, 0x6b, 0x44, 0x0f, 0xb9, 0x68, + 0x00, 0x20, 0x23, 0xed, 0xae, 0xd5, 0x3b, 0x73, 0xa7, 0xc2, 0xd9, 0x3b, 0xd8, 0xf9, 0xb3, 0x53, 0x7e, 0x1a, 0xaf, + 0x3f, 0x55, 0x5c, 0x79, 0x0c, 0x47, 0xaa, 0x3c, 0xbb, 0xa7, 0x15, 0x9c, 0xab, 0x6b, 0x4b, 0x73, 0x18, 0x43, 0xf6, + 0x36, 0x73, 0x2b, 0xb9, 0xe2, 0x5e, 0xbe, 0x28, 0xee, 0xd0, 0xea, 0xb8, 0x87, 0x94, 0x8e, 0x66, 0xa5, 0x2f, 0x37, + 0xc7, 0x69, 0xb8, 0xfe, 0xe3, 0x23, 0x77, 0xca, 0x4d, 0xdd, 0xeb, 0x71, 0x82, 0x71, 0x0d, 0x8a, 0xfb, 0x94, 0x21, + 0xe4, 0x04, 0x13, 0xfc, 0x5e, 0xb7, 0x2f, 0xb1, 0x4e, 0x99, 0xa3, 0x28, 0x32, 0xe0, 0xc3, 0xa4, 0x98, 0xd0, 0xfe, + 0x99, 0xdd, 0x60, 0x4c, 0xf3, 0xb7, 0xb5, 0x3a, 0xca, 0x8c, 0xe7, 0xf9, 0x0e, 0x14, 0x02, 0x72, 0x92, 0x24, 0x96, + 0x60, 0x97, 0x5f, 0xe9, 0x0f, 0x4e, 0x1c, 0x37, 0xe6, 0xba, 0xf6, 0x46, 0x9e, 0x12, 0x22, 0x4d, 0xe5, 0xab, 0xc3, + 0x69, 0x37, 0xa7, 0x41, 0x8f, 0x90, 0x50, 0xbd, 0x54, 0xc9, 0x1a, 0xfb, 0x42, 0x1a, 0xe9, 0xe7, 0x23, 0x7d, 0x6a, + 0x2f, 0x81, 0x02, 0x10, 0x5a, 0x6b, 0xa9, 0xd2, 0x68, 0xb8, 0x6e, 0x02, 0x88, 0x4f, 0xe3, 0xe5, 0x01, 0x8a, 0xe6, + 0x78, 0x76, 0x2e, 0x58, 0x8b, 0x9f, 0x14, 0x9f, 0x99, 0x87, 0xce, 0x32, 0x8c, 0x03, 0x37, 0xa7, 0xd4, 0xa9, 0xdb, + 0xb5, 0x53, 0x8e, 0xd5, 0xc1, 0x6a, 0x99, 0x7e, 0xcc, 0xbb, 0x45, 0xbc, 0x78, 0xe3, 0xbc, 0x1b, 0xab, 0x73, 0x6c, + 0x6c, 0xfa, 0x24, 0x5e, 0x7e, 0xee, 0xa4, 0xc9, 0xa0, 0xc8, 0xf2, 0x58, 0x96, 0x13, 0xec, 0x15, 0xf7, 0x89, 0x9a, + 0x37, 0x4d, 0xba, 0x31, 0x67, 0x05, 0x58, 0x7b, 0xed, 0xe8, 0x3d, 0x9c, 0x5a, 0x0f, 0xbe, 0xfd, 0x3f, 0x74, 0xd4, + 0x57, 0x6b, 0xbf, 0x0b, 0xe7, 0xca, 0xc2, 0xf3, 0x18, 0xa2, 0x2c, 0x0f, 0x60, 0xb6, 0xfc, 0xb5, 0xff, 0x65, 0x88, + 0x3f, 0x68, 0x77, 0xfb, 0xd7, 0x93, 0xcb, 0x13, 0xc0, 0x19, 0x9b, 0xed, 0xa3, 0x0e, 0x52, 0x5d, 0xef, 0x1c, 0xb5, + 0xdf, 0xb3, 0x73, 0x9f, 0x81, 0x32, 0xc2, 0x89, 0xcf, 0x1b, 0xfb, 0x55, 0x00, 0xfa, 0xfe, 0x82, 0x6d, 0xf1, 0xa7, + 0xcd, 0x82, 0xbc, 0xd9, 0x6e, 0xc2, 0xfe, 0x28, 0x30, 0x5c, 0x7b, 0xfd, 0x8a, 0x00, 0xa8, 0x6b, 0xed, 0x2b, 0xed, + 0x72, 0xfb, 0xb0, 0x02, 0x10, 0xb1, 0x50, 0x13, 0x36, 0x51, 0xfd, 0x97, 0x48, 0x97, 0x34, 0x2c, 0x61, 0xa6, 0xfa, + 0x09, 0xdb, 0xb3, 0x26, 0x06, 0x56, 0x7a, 0x00, 0x5b, 0x58, 0x07, 0x74, 0x83, 0x28, 0x5e, 0x87, 0x06, 0xff, 0x4e, + 0x58, 0x90, 0xf7, 0x94, 0xa1, 0x66, 0xa8, 0xe2, 0x52, 0x58, 0xf3, 0x28, 0x25, 0x30, 0x3c, 0x87, 0x36, 0xc0, 0x5a, + 0x8a, 0x98, 0xdb, 0x2e, 0xc9, 0x20, 0x48, 0xbc, 0x3e, 0xc0, 0x33, 0xe0, 0xdd, 0x1c, 0x3c, 0xc0, 0x00, 0xa4, 0xbe, + 0xa0, 0xe8, 0xcd, 0x83, 0xef, 0xd6, 0xcc, 0xcc, 0x4e, 0x77, 0xa3, 0x58, 0xac, 0x5a, 0x88, 0xb0, 0x45, 0xa4, 0x6d, + 0xab, 0x66, 0x85, 0x0e, 0x78, 0xa2, 0x47, 0x51, 0x92, 0x12, 0xfc, 0x37, 0x23, 0x00, 0x11, 0x35, 0x4c, 0x28, 0x28, + 0xd8, 0xdb, 0xe3, 0x76, 0xe2, 0xcb, 0x5b, 0x5a, 0x73, 0x4b, 0x81, 0x57, 0xa1, 0xf1, 0xb0, 0x0e, 0xe4, 0x0a, 0x6b, + 0x90, 0x39, 0xd7, 0xb6, 0x99, 0x1d, 0x9f, 0x76, 0xbe, 0xc4, 0xf3, 0x6b, 0x5c, 0xae, 0x6f, 0x95, 0x22, 0xff, 0x06, + 0x85, 0x88, 0x53, 0xab, 0x52, 0xa9, 0xea, 0x03, 0x58, 0xd3, 0xa5, 0xe0, 0xce, 0xb8, 0xf9, 0x80, 0x40, 0xf2, 0x53, + 0x2e, 0x75, 0xd6, 0x48, 0x0b, 0x48, 0x5f, 0xc2, 0x90, 0x17, 0xa6, 0xab, 0x05, 0xc4, 0xe1, 0x81, 0xfe, 0x58, 0x14, + 0xc9, 0x93, 0x49, 0x14, 0xd4, 0xf0, 0x54, 0x55, 0xeb, 0xf5, 0x52, 0xaf, 0x93, 0x63, 0x67, 0x4f, 0xcc, 0xb2, 0x4d, + 0xcd, 0x90, 0xcd, 0xe4, 0xc5, 0xfd, 0x70, 0x5d, 0xbd, 0xbc, 0x0f, 0x8a, 0x1e, 0x27, 0x37, 0x08, 0xc8, 0x5b, 0x75, + 0xf4, 0x3a, 0x2d, 0x48, 0xd0, 0x2e, 0x97, 0xa6, 0x99, 0xe5, 0xf2, 0x68, 0x82, 0x10, 0x3e, 0x6b, 0xf6, 0x59, 0x71, + 0xd1, 0x34, 0x9d, 0x18, 0x39, 0x5c, 0xde, 0xf0, 0x74, 0x66, 0x64, 0x5a, 0x77, 0x5e, 0x34, 0x26, 0x94, 0xba, 0x6a, + 0x57, 0x7a, 0xe3, 0x8d, 0xd3, 0xd9, 0xda, 0xa9, 0xdd, 0x85, 0x32, 0xb8, 0xa8, 0x89, 0xc7, 0x6c, 0x04, 0x20, 0xba, + 0x76, 0xaa, 0x94, 0x27, 0xa7, 0xce, 0x84, 0xe6, 0x16, 0x3b, 0xaf, 0x80, 0xb6, 0x7f, 0xda, 0x95, 0x4a, 0x2b, 0xc4, + 0x22, 0x75, 0xfc, 0x7b, 0xf1, 0xdc, 0xaf, 0x37, 0xab, 0xca, 0xeb, 0x65, 0xd0, 0xc6, 0xeb, 0xcd, 0xae, 0x66, 0xa5, + 0xa9, 0x55, 0xd8, 0x0a, 0x6c, 0x6d, 0xad, 0xe8, 0x0c, 0x3d, 0x7d, 0x93, 0x1c, 0x63, 0x9b, 0x17, 0x32, 0xe6, 0xac, + 0xfd, 0xca, 0x2b, 0x74, 0x75, 0x60, 0xff, 0x8b, 0x7f, 0x47, 0x10, 0x16, 0x2a, 0x56, 0xfe, 0x20, 0xe4, 0x9a, 0xc0, + 0x5a, 0xa2, 0x7e, 0x64, 0x1f, 0xb4, 0xff, 0x12, 0xb7, 0xbd, 0xcf, 0x4d, 0x85, 0xc2, 0x77, 0xa7, 0xc4, 0x8d, 0x0b, + 0x38, 0x22, 0x81, 0x2d, 0x07, 0xe3, 0x7e, 0x7c, 0x19, 0x8c, 0x35, 0xfd, 0x91, 0x28, 0xf1, 0x9f, 0x59, 0x3f, 0x6f, + 0x29, 0x82, 0xb2, 0x36, 0xb8, 0x3b, 0x42, 0x75, 0x93, 0x18, 0x5e, 0x9f, 0x1c, 0x53, 0x0a, 0xe2, 0x98, 0x5a, 0x91, + 0x85, 0x1e, 0x5e, 0xdf, 0xb8, 0x65, 0x9f, 0xf9, 0x10, 0x77, 0x6a, 0xdc, 0xfa, 0xda, 0xc2, 0xd5, 0x40, 0xb8, 0x3f, + 0xf9, 0x4d, 0xc4, 0x8b, 0x84, 0x4c, 0x75, 0xf6, 0x3c, 0xb3, 0x1f, 0x42, 0xe7, 0x59, 0xf0, 0x3d, 0x82, 0x29, 0xfd, + 0x2b, 0x2f, 0xc4, 0xef, 0x26, 0xed, 0x65, 0xe6, 0x69, 0x6e, 0x3f, 0x47, 0x41, 0xcc, 0x4f, 0x72, 0x8c, 0x30, 0x52, + 0x86, 0x6e, 0x88, 0x11, 0x25, 0xbc, 0xa9, 0xc4, 0x6e, 0x76, 0xff, 0xa8, 0x83, 0x77, 0x20, 0xaf, 0x08, 0xbf, 0x24, + 0xf6, 0xc4, 0x32, 0x84, 0xa5, 0x66, 0x09, 0x17, 0x6a, 0x0e, 0x6d, 0xac, 0xd7, 0x2d, 0x9e, 0x58, 0x50, 0xfd, 0x04, + 0xad, 0xb3, 0x41, 0xcc, 0xed, 0xc5, 0x77, 0xf9, 0xee, 0x6c, 0xc2, 0x36, 0x62, 0xe6, 0x3a, 0x38, 0xee, 0xeb, 0x1b, + 0xa0, 0x4c, 0xd0, 0x63, 0x97, 0x08, 0xcb, 0x0a, 0x56, 0x62, 0xb1, 0xf6, 0xf4, 0x8e, 0xfd, 0x87, 0x13, 0x01, 0xbc, + 0xd3, 0x2c, 0x8f, 0xd5, 0x46, 0xc6, 0x3b, 0x6d, 0xd7, 0x1b, 0x62, 0xbb, 0xca, 0xb4, 0x8a, 0x51, 0x42, 0x37, 0x34, + 0x69, 0xe9, 0x06, 0x02, 0xec, 0xa6, 0x22, 0x65, 0xb6, 0x61, 0x0f, 0x5d, 0x3f, 0x3f, 0xa4, 0x6f, 0x76, 0x8e, 0x91, + 0xb6, 0x21, 0x6c, 0x99, 0xfa, 0xe5, 0x9b, 0x3e, 0x74, 0x2f, 0x9d, 0xa6, 0xc3, 0xd6, 0xfa, 0x54, 0xed, 0xa7, 0xfb, + 0xb1, 0x1d, 0x78, 0x98, 0xa9, 0x13, 0xf1, 0x94, 0x7b, 0xfc, 0x0d, 0x7d, 0xa6, 0x22, 0x64, 0xe0, 0x14, 0x92, 0x13, + 0x92, 0x3b, 0x86, 0x25, 0x31, 0x8a, 0xf3, 0x82, 0x01, 0x3a, 0xc8, 0xb9, 0xf0, 0x9a, 0x19, 0xac, 0x8f, 0x8e, 0x0b, + 0x85, 0xc9, 0xc0, 0x69, 0x9d, 0x0e, 0x06, 0x08, 0x82, 0x4b, 0xc6, 0x4b, 0x2b, 0x93, 0xc5, 0xe3, 0x11, 0xf3, 0xcb, + 0xe1, 0x59, 0xce, 0x8a, 0xc0, 0xa9, 0x6a, 0xa7, 0x25, 0x65, 0x34, 0xf7, 0x36, 0x5d, 0x5a, 0x87, 0xc4, 0x5b, 0x9f, + 0x3a, 0x1b, 0xc1, 0xb0, 0xb6, 0xf7, 0xb6, 0xc3, 0xa3, 0xeb, 0xdf, 0xd7, 0xb3, 0x61, 0xc1, 0xe2, 0xb0, 0x31, 0xc0, + 0x99, 0x8b, 0xe7, 0x2d, 0x8a, 0xc7, 0x64, 0x66, 0xcf, 0x0f, 0xae, 0xf6, 0x2e, 0x3f, 0xcd, 0xbe, 0x63, 0x9b, 0x3d, + 0x7a, 0xd3, 0xcd, 0x40, 0xeb, 0xdd, 0x16, 0x29, 0xf1, 0xf1, 0xb1, 0x76, 0x79, 0x63, 0x96, 0x2c, 0xbc, 0xcc, 0xd1, + 0xfa, 0x87, 0x79, 0xc9, 0x61, 0xd3, 0x4d, 0xad, 0x8b, 0xef, 0x2c, 0xa5, 0xd9, 0x9b, 0x80, 0xba, 0xd0, 0x44, 0x25, + 0x82, 0xd0, 0xca, 0xe0, 0x71, 0x8f, 0xb6, 0xf6, 0x0e, 0xb3, 0xd2, 0xe6, 0x52, 0x03, 0x0f, 0x8f, 0xfa, 0x18, 0x9c, + 0x74, 0xcc, 0xb2, 0xc5, 0x57, 0x68, 0xb6, 0xae, 0x2c, 0x4d, 0x46, 0x55, 0x75, 0xc4, 0x3a, 0x73, 0x11, 0x2f, 0x4d, + 0x69, 0xb6, 0xee, 0x2a, 0x30, 0x9d, 0x9a, 0x6f, 0x76, 0x71, 0xa1, 0x14, 0xfe, 0x9b, 0x6e, 0x4f, 0x74, 0x6c, 0xa5, + 0x38, 0xda, 0xc8, 0x60, 0x1a, 0xee, 0xf2, 0x4b, 0x1e, 0x79, 0x90, 0xf7, 0x10, 0x9c, 0x5a, 0x85, 0x42, 0xbe, 0x62, + 0x6f, 0xd0, 0xaa, 0x9a, 0xa3, 0x4d, 0xce, 0xec, 0xea, 0x7c, 0xb5, 0x2e, 0x4f, 0x25, 0xca, 0xd4, 0xa8, 0xd1, 0x86, + 0xcf, 0x33, 0x56, 0x3f, 0xc4, 0xae, 0xdb, 0x8b, 0xa7, 0x03, 0x3f, 0x51, 0xaf, 0xb3, 0x92, 0xa2, 0x08, 0xe8, 0x50, + 0xe3, 0xda, 0x91, 0x8d, 0x94, 0xe9, 0xce, 0xba, 0x64, 0x1d, 0xc3, 0xe2, 0x64, 0x76, 0x7a, 0x80, 0x6e, 0xd0, 0x4c, + 0x73, 0xaa, 0x5d, 0x23, 0x04, 0x66, 0xfc, 0xe9, 0x11, 0xfa, 0x45, 0x56, 0x73, 0x1a, 0x67, 0x05, 0xf0, 0x95, 0x54, + 0x34, 0xcf, 0x2a, 0x66, 0x24, 0xdc, 0xb2, 0xc9, 0x96, 0xe6, 0x7f, 0xe9, 0x2e, 0x6c, 0xd7, 0xa5, 0x10, 0x94, 0x02, + 0x55, 0x3a, 0x09, 0xfe, 0x53, 0xca, 0xf0, 0xa7, 0x05, 0x8c, 0x5e, 0xf6, 0x7c, 0x3b, 0x66, 0xcd, 0xd6, 0xf0, 0x9d, + 0xc1, 0x4f, 0x35, 0x28, 0x7e, 0xc2, 0xba, 0x85, 0xe2, 0xeb, 0x2e, 0xe0, 0x11, 0x54, 0x0f, 0xb3, 0xaf, 0x99, 0x7a, + 0x78, 0x97, 0x8d, 0x62, 0xc3, 0x28, 0x5a, 0xbf, 0x56, 0x93, 0x29, 0xcd, 0xb1, 0x24, 0x58, 0xd9, 0x34, 0xb2, 0x84, + 0x50, 0xbd, 0x6f, 0x4e, 0x7d, 0xeb, 0x6d, 0x24, 0x4e, 0x42, 0x12, 0x4e, 0xd0, 0xc7, 0x03, 0x89, 0x74, 0x3c, 0x19, + 0xd0, 0xd9, 0x46, 0x26, 0x3a, 0x9b, 0x28, 0xa4, 0xb3, 0xe0, 0x9a, 0x63, 0x29, 0x55, 0x73, 0xe5, 0x75, 0x9e, 0xf0, + 0x4d, 0xe1, 0x97, 0xd0, 0xf4, 0x7a, 0xeb, 0xf7, 0x39, 0xab, 0xf0, 0x9f, 0x0f, 0x13, 0xbe, 0xc1, 0xd8, 0xb0, 0x7e, + 0x8f, 0x88, 0x50, 0x01, 0x0f, 0x74, 0xeb, 0xf0, 0x75, 0xb4, 0x1a, 0x46, 0x68, 0xd3, 0xad, 0xa0, 0xe0, 0x90, 0xf6, + 0xfb, 0xee, 0x6a, 0x1b, 0xde, 0x9c, 0xbd, 0xd3, 0x3a, 0x79, 0x83, 0xf9, 0xf1, 0x56, 0xc9, 0xbb, 0x91, 0x98, 0x62, + 0xf9, 0x6b, 0xcb, 0x55, 0xba, 0x71, 0x78, 0x77, 0x1c, 0xdf, 0x1d, 0xc7, 0x31, 0xe3, 0x23, 0x7b, 0xd2, 0x72, 0x1c, + 0xce, 0x5c, 0x0f, 0xcc, 0xde, 0x38, 0xf5, 0x8a, 0xa6, 0x03, 0x47, 0x57, 0xf3, 0x2d, 0x7e, 0x2c, 0xc2, 0xc3, 0xaf, + 0x49, 0x54, 0xd6, 0x34, 0x83, 0x26, 0x95, 0xd2, 0xe4, 0xd8, 0x9d, 0xb2, 0x00, 0xd8, 0xfb, 0xe0, 0xce, 0xb4, 0x8d, + 0x9d, 0x5c, 0x4f, 0xd0, 0x25, 0x69, 0x1e, 0xf3, 0xd0, 0x7e, 0xa4, 0xb1, 0x35, 0x97, 0xe5, 0xd8, 0x2e, 0xb8, 0xee, + 0x1a, 0xa0, 0x6a, 0x61, 0xcf, 0x7f, 0x9b, 0x3f, 0xba, 0xbe, 0x23, 0xc7, 0x01, 0x61, 0x69, 0xfe, 0x3c, 0xda, 0x41, + 0xf7, 0x7d, 0xac, 0x70, 0xd8, 0xa2, 0x7d, 0x35, 0x90, 0xaa, 0x83, 0x5a, 0x61, 0x70, 0x51, 0xa9, 0xb2, 0x75, 0xc4, + 0xf0, 0x82, 0x6c, 0x12, 0x0a, 0x85, 0xc2, 0xf1, 0xc4, 0x7a, 0xdb, 0xed, 0x8f, 0x58, 0x4a, 0x7b, 0xa8, 0x5f, 0x3b, + 0x41, 0xa4, 0xbd, 0xf5, 0x77, 0x3b, 0x5c, 0xd4, 0xe1, 0xe2, 0x55, 0x2f, 0xf4, 0x9c, 0x36, 0xa1, 0xd9, 0x62, 0xac, + 0x57, 0x8a, 0x4d, 0x90, 0xdd, 0x1e, 0x3f, 0x23, 0xdb, 0xb1, 0x59, 0x21, 0x09, 0xe9, 0x2f, 0xcf, 0x67, 0x72, 0x54, + 0xd1, 0xbf, 0xb1, 0x9c, 0xb2, 0x14, 0x13, 0xef, 0xc0, 0x2f, 0x7a, 0xca, 0x95, 0x34, 0x0e, 0xaa, 0x3b, 0x7c, 0x08, + 0x8a, 0xfa, 0xb9, 0x2d, 0xbd, 0xc1, 0x2c, 0x4f, 0x06, 0x55, 0x70, 0x00, 0x63, 0x36, 0xf2, 0xe8, 0x7a, 0x76, 0x29, + 0x06, 0x53, 0xbe, 0x5c, 0xb5, 0x59, 0xbb, 0x61, 0xea, 0xc6, 0xba, 0x30, 0xdc, 0x32, 0xb6, 0x31, 0x01, 0x1a, 0xdb, + 0x34, 0xdb, 0x4c, 0x9b, 0xfe, 0xf6, 0x99, 0xc8, 0xe3, 0xe0, 0x48, 0x44, 0x20, 0x1f, 0x44, 0x1f, 0x0c, 0x48, 0xff, + 0xbe, 0x48, 0xc1, 0xe8, 0x52, 0x15, 0x3f, 0x89, 0x15, 0x1d, 0xf0, 0x9b, 0xf5, 0x1f, 0xa1, 0x63, 0xcc, 0x79, 0xbb, + 0xd0, 0xd8, 0x9d, 0x82, 0x1d, 0xb6, 0x7b, 0xd6, 0xa0, 0x8a, 0xd1, 0xd6, 0x20, 0x56, 0xd0, 0xd4, 0xb8, 0x81, 0xf5, + 0x6d, 0xc2, 0x6a, 0x3c, 0xa6, 0xd5, 0xcc, 0x65, 0xb8, 0xcb, 0x85, 0x75, 0x36, 0xe5, 0x2d, 0x88, 0x0f, 0x02, 0x91, + 0x31, 0x09, 0xc2, 0xe8, 0xb1, 0xf0, 0x07, 0x99, 0x79, 0x81, 0x2c, 0x5d, 0xc7, 0x9f, 0x99, 0x8c, 0xfb, 0x80, 0xa1, + 0x7d, 0x50, 0x3f, 0x68, 0x21, 0x5d, 0x68, 0xfe, 0x2a, 0xab, 0x6a, 0x6e, 0xe6, 0xb8, 0x11, 0x0e, 0x2d, 0xa9, 0x9a, + 0x23, 0xd0, 0x42, 0x20, 0xd4, 0xf6, 0x7b, 0x03, 0xac, 0xe5, 0x87, 0xf0, 0x92, 0x39, 0x00, 0x64, 0x16, 0x13, 0xae, + 0x5e, 0xb7, 0x4a, 0x48, 0x5d, 0x1c, 0x3d, 0x6e, 0x5c, 0x53, 0x36, 0x19, 0x22, 0x88, 0x64, 0x3e, 0x8f, 0x80, 0x39, + 0xce, 0xbf, 0x8b, 0xe8, 0x04, 0x4b, 0x3f, 0x16, 0x77, 0xf9, 0xa3, 0x61, 0xa6, 0xfa, 0xf3, 0xa9, 0x7a, 0x12, 0x13, + 0x96, 0x22, 0xfe, 0x96, 0xd7, 0xe6, 0x24, 0x4a, 0x91, 0x9b, 0x4d, 0x60, 0x94, 0x23, 0x3d, 0xae, 0x24, 0x79, 0x4a, + 0x34, 0x08, 0x83, 0x68, 0x55, 0x7c, 0x7b, 0xe4, 0xa4, 0x13, 0x1e, 0xcd, 0xe2, 0x5d, 0xf6, 0xf3, 0xb7, 0x03, 0x7f, + 0x9b, 0x7a, 0x2f, 0xd7, 0xae, 0x53, 0xef, 0x3c, 0x98, 0xee, 0x5c, 0x11, 0x93, 0xd3, 0x8f, 0x69, 0xcc, 0xaf, 0xeb, + 0x5d, 0x13, 0x00, 0x44, 0xb8, 0xfc, 0xcf, 0xe1, 0xc8, 0xd8, 0x86, 0x4b, 0xcf, 0x36, 0x38, 0x35, 0xba, 0x7c, 0xdb, + 0x08, 0x3e, 0x11, 0xe2, 0x42, 0x2c, 0x9a, 0x78, 0x84, 0xe8, 0xdc, 0x4e, 0xe4, 0x05, 0xa9, 0xff, 0xcc, 0x8b, 0xa0, + 0x30, 0x7b, 0x2b, 0xdd, 0xf3, 0x6c, 0x6b, 0xb8, 0xa9, 0x72, 0xfb, 0x68, 0xb5, 0xa8, 0x62, 0x4f, 0x83, 0x17, 0x76, + 0xa6, 0x24, 0xf2, 0x81, 0x97, 0x28, 0x86, 0xee, 0x3b, 0x31, 0x7b, 0x34, 0xf4, 0xa8, 0x77, 0xc6, 0x6e, 0xf4, 0x10, + 0x7a, 0x14, 0x8b, 0x54, 0x5f, 0x42, 0xc0, 0xe7, 0x5f, 0xcd, 0xd9, 0x0d, 0x6d, 0x4a, 0x3c, 0x2e, 0xfc, 0xbb, 0x20, + 0x0c, 0xe6, 0x58, 0x02, 0x05, 0xe8, 0xfb, 0x23, 0xc1, 0x03, 0x73, 0xb0, 0x3d, 0x24, 0xb3, 0xd7, 0x69, 0xe6, 0x81, + 0xf6, 0x6b, 0x19, 0x65, 0x24, 0x01, 0x7c, 0xb2, 0xde, 0xa1, 0x34, 0x02, 0xd9, 0xba, 0xc2, 0x80, 0x3e, 0xe9, 0xca, + 0xbb, 0xab, 0xee, 0x6c, 0x52, 0x4c, 0xe8, 0xae, 0x6b, 0x29, 0x01, 0x1d, 0xf1, 0x29, 0xc9, 0x2c, 0x6a, 0xeb, 0x83, + 0x22, 0x8a, 0xbc, 0x4b, 0xcc, 0x25, 0x8d, 0xd4, 0xc7, 0xc7, 0x7a, 0x27, 0xe2, 0xa2, 0x50, 0x53, 0x40, 0x98, 0xd4, + 0x47, 0xf1, 0xbe, 0x00, 0xe2, 0x6c, 0xbd, 0x42, 0x68, 0xc2, 0x35, 0xcf, 0xee, 0x95, 0x09, 0x55, 0x68, 0xeb, 0xa9, + 0xec, 0x02, 0x97, 0xd7, 0x17, 0x4b, 0x10, 0x05, 0x72, 0x0a, 0x62, 0x72, 0x09, 0x8a, 0x0f, 0xc3, 0xf5, 0x04, 0x9c, + 0x22, 0xdf, 0x4b, 0xcd, 0x87, 0xc4, 0x01, 0x38, 0x0a, 0x21, 0x16, 0x23, 0x61, 0x84, 0x64, 0x13, 0xe4, 0xd2, 0x0a, + 0xb4, 0xfb, 0xd5, 0x5e, 0xfb, 0xc2, 0xfd, 0x43, 0x4d, 0xc5, 0xe6, 0x42, 0x16, 0x46, 0x2b, 0xa2, 0x7b, 0x09, 0x47, + 0x59, 0x8a, 0xc3, 0x39, 0xb2, 0x34, 0x0e, 0x0c, 0x73, 0x83, 0x12, 0x10, 0xf6, 0x3f, 0x1b, 0x07, 0x02, 0x60, 0x2e, + 0xad, 0xa4, 0x2d, 0xe1, 0xf3, 0x1b, 0x69, 0xb6, 0xf4, 0x1b, 0x1b, 0xde, 0x3c, 0x54, 0x80, 0x25, 0xb5, 0xb8, 0x61, + 0x2e, 0x2b, 0x9c, 0xd1, 0xea, 0x94, 0xf0, 0xda, 0x42, 0x57, 0x97, 0xec, 0x2f, 0xb9, 0x18, 0xde, 0x0f, 0xcf, 0x55, + 0xb7, 0x19, 0xa8, 0xce, 0x24, 0x9d, 0xc9, 0x53, 0xa5, 0x9e, 0x26, 0xc7, 0xee, 0xfd, 0x4e, 0x01, 0xb2, 0xd0, 0xb8, + 0x9b, 0x75, 0x36, 0xbb, 0x5d, 0x1f, 0xd8, 0x1f, 0x70, 0x41, 0xf1, 0xc7, 0xa4, 0xe3, 0xb3, 0x24, 0xc2, 0x22, 0xab, + 0x3d, 0xb9, 0x1c, 0x9d, 0xeb, 0x9c, 0x99, 0x97, 0x3e, 0x49, 0xe9, 0xae, 0xbc, 0x25, 0xcf, 0x44, 0xc9, 0x49, 0xf2, + 0x82, 0xd5, 0x0e, 0x6c, 0x0f, 0x79, 0x3a, 0x30, 0x81, 0xae, 0xce, 0x1b, 0xef, 0x15, 0x23, 0x4d, 0x1d, 0x76, 0x94, + 0xdd, 0xc8, 0xe0, 0x99, 0x0f, 0xf2, 0xd1, 0x81, 0xe2, 0x19, 0xa6, 0x6b, 0x22, 0xf6, 0x89, 0xcb, 0xae, 0xc0, 0x13, + 0xa2, 0xe3, 0xc3, 0x98, 0x36, 0xe4, 0xfd, 0x2a, 0x7c, 0x28, 0x8e, 0xc5, 0x0f, 0x16, 0x93, 0xc8, 0x07, 0x39, 0xc0, + 0xfb, 0xc0, 0x96, 0x15, 0x46, 0x06, 0x73, 0x6e, 0xec, 0x8e, 0xe3, 0x05, 0x60, 0xc4, 0x43, 0xae, 0x71, 0xc7, 0x67, + 0x20, 0x70, 0x3c, 0x57, 0xcd, 0x76, 0x0e, 0x73, 0x10, 0x00, 0x99, 0xc1, 0x32, 0x71, 0x31, 0x5a, 0x46, 0x29, 0x06, + 0xcf, 0xf8, 0xd4, 0xad, 0x1a, 0x62, 0x99, 0xfe, 0x64, 0x50, 0x2e, 0x5d, 0x0b, 0x29, 0xb8, 0x55, 0xdf, 0x18, 0x13, + 0xd2, 0x6d, 0x23, 0xc1, 0x16, 0xf3, 0xab, 0x20, 0x96, 0x34, 0xa8, 0x6b, 0x72, 0x16, 0x66, 0x0b, 0xa5, 0xbd, 0xe9, + 0x3a, 0x8a, 0xc5, 0xb1, 0x24, 0xae, 0x2d, 0x31, 0x02, 0x04, 0xb3, 0xeb, 0x50, 0x34, 0x62, 0x88, 0x7d, 0xac, 0x3a, + 0x00, 0xf0, 0x18, 0xa2, 0x23, 0xc7, 0xec, 0x3e, 0xb1, 0xf5, 0xf2, 0x06, 0x7e, 0x59, 0xfe, 0x48, 0xc6, 0x2f, 0xcf, + 0xc7, 0xb5, 0x23, 0x8a, 0xfe, 0x8d, 0xc4, 0x53, 0x15, 0x73, 0x20, 0x8d, 0xfd, 0x0b, 0x58, 0xba, 0x02, 0xb9, 0x0c, + 0x1c, 0xc3, 0xd6, 0xcf, 0xac, 0x8f, 0xc1, 0x8f, 0x7d, 0x44, 0x1d, 0xbf, 0x0e, 0x54, 0x59, 0x4a, 0xe4, 0x6f, 0x95, + 0x93, 0xcc, 0x18, 0x9c, 0x0b, 0xdd, 0x9d, 0xb8, 0xa9, 0x57, 0xf6, 0xb6, 0xa1, 0xbe, 0x49, 0xdc, 0xbe, 0xb5, 0xa4, + 0x02, 0xdb, 0xd7, 0x89, 0x1b, 0x43, 0xa1, 0x4f, 0x96, 0x67, 0x9b, 0x02, 0x4d, 0x0c, 0xbd, 0x69, 0x47, 0xee, 0xa6, + 0xda, 0x23, 0xb5, 0xfb, 0xd2, 0xb4, 0x8d, 0x60, 0x96, 0x34, 0x16, 0x4d, 0x86, 0x9f, 0xc7, 0x58, 0x1c, 0x8a, 0x14, + 0x2b, 0x72, 0x19, 0xa0, 0xd0, 0xb0, 0x83, 0x3b, 0xb9, 0xae, 0xd3, 0xec, 0x31, 0x78, 0xa9, 0x29, 0xde, 0xbb, 0x6a, + 0x31, 0xa1, 0x24, 0x4c, 0x33, 0x78, 0x6b, 0x87, 0x95, 0xe6, 0x4a, 0x39, 0x09, 0x64, 0x53, 0x16, 0x36, 0x05, 0x3f, + 0x77, 0x70, 0xfa, 0x83, 0xa2, 0xdc, 0x11, 0x70, 0xdb, 0x4b, 0xdc, 0x7f, 0x12, 0xff, 0x64, 0x88, 0x47, 0x46, 0x66, + 0xf8, 0x2f, 0x89, 0x45, 0xff, 0x00, 0x32, 0x31, 0x2f, 0x69, 0x0f, 0x62, 0x09, 0x4f, 0xd4, 0x3a, 0x98, 0xe5, 0x91, + 0x86, 0x26, 0xc3, 0x67, 0xa1, 0xb0, 0xe8, 0x2a, 0x03, 0x8b, 0xb3, 0x0c, 0xfd, 0xb9, 0xd4, 0xed, 0x03, 0xf8, 0x9b, + 0x68, 0xf1, 0x86, 0x86, 0x49, 0xc8, 0x0a, 0xa0, 0xfa, 0xb0, 0x4c, 0xba, 0x6e, 0x54, 0x1b, 0x24, 0x52, 0x06, 0xed, + 0xe2, 0x84, 0x08, 0xe8, 0xf9, 0x24, 0x9b, 0x6e, 0x78, 0x7a, 0xe3, 0x1f, 0x10, 0xf8, 0xc4, 0xda, 0xb1, 0x68, 0x03, + 0xb1, 0x46, 0xcf, 0x48, 0x7a, 0x9a, 0x01, 0xb6, 0x71, 0x39, 0xc4, 0x36, 0x8e, 0x61, 0x61, 0x66, 0x16, 0xc3, 0x78, + 0x91, 0xb3, 0x61, 0x7d, 0x0a, 0xb1, 0xc0, 0xb8, 0x01, 0xa9, 0x59, 0x4e, 0x48, 0x14, 0x29, 0x7b, 0x14, 0x28, 0x88, + 0x50, 0x7e, 0xf3, 0xb2, 0xfa, 0xb5, 0x4c, 0x80, 0x15, 0x94, 0x56, 0x19, 0x48, 0x5a, 0x1b, 0xcb, 0x49, 0x2d, 0xa9, + 0x8a, 0xf3, 0x4d, 0x19, 0x26, 0xbf, 0xeb, 0x1f, 0xde, 0xa3, 0x17, 0x6c, 0xc2, 0x53, 0x72, 0xcd, 0x46, 0x93, 0xe1, + 0x53, 0x66, 0x7f, 0xc1, 0xf8, 0x00, 0xca, 0x6f, 0x7a, 0x53, 0x86, 0xd2, 0xc6, 0x80, 0x47, 0x9d, 0x97, 0x58, 0x51, + 0x69, 0x37, 0xb9, 0x13, 0x2d, 0x63, 0x6f, 0xf9, 0x87, 0x3b, 0x81, 0x8f, 0x81, 0xcb, 0x0c, 0x10, 0xf4, 0xb7, 0xfc, + 0xc2, 0x3b, 0xc0, 0x7f, 0xac, 0x28, 0x72, 0xe0, 0xb6, 0x4c, 0x20, 0x63, 0xdb, 0x68, 0x6c, 0xad, 0xcf, 0x70, 0xad, + 0xc7, 0x5e, 0xe8, 0x64, 0x61, 0xbc, 0x7b, 0x63, 0x94, 0x61, 0x5e, 0xc4, 0x71, 0x9e, 0x45, 0x85, 0x3e, 0x2c, 0xaa, + 0x2a, 0x2e, 0xff, 0x05, 0x00, 0xa3, 0xf0, 0x70, 0xfa, 0xfd, 0x1b, 0xfc, 0x39, 0xfe, 0x17, 0xee, 0x6a, 0x38, 0x9f, + 0x63, 0xe2, 0xf5, 0xa6, 0xef, 0x70, 0xc7, 0x9a, 0xfd, 0x0a, 0xc6, 0xcc, 0xe5, 0x34, 0x19, 0x5c, 0xd3, 0xaf, 0x15, + 0xf0, 0xf1, 0x13, 0x27, 0xe9, 0x58, 0x9b, 0xe9, 0xd4, 0xde, 0x72, 0xf9, 0x00, 0x9f, 0x39, 0xc3, 0x59, 0xb9, 0xfa, + 0xf1, 0x97, 0x81, 0xb2, 0x08, 0x7f, 0x1c, 0x93, 0x03, 0x89, 0xab, 0x74, 0xd1, 0x02, 0x9e, 0xed, 0x33, 0x80, 0x86, + 0x70, 0x6a, 0x23, 0xf2, 0x07, 0x1b, 0xe3, 0xd1, 0x6d, 0x17, 0xe2, 0x1a, 0x75, 0xa2, 0x47, 0xd8, 0x2d, 0xea, 0xca, + 0xb6, 0xdb, 0x5a, 0x07, 0x2c, 0x0b, 0x36, 0xdf, 0xc0, 0x79, 0x62, 0x5a, 0x72, 0xf8, 0xa9, 0xef, 0x9b, 0x90, 0xa8, + 0x17, 0xdf, 0xef, 0xe0, 0x4d, 0x8a, 0x64, 0x65, 0x2a, 0xc8, 0x50, 0xfe, 0xb7, 0x39, 0xdc, 0xa3, 0xec, 0x91, 0x3e, + 0xc7, 0x42, 0x0f, 0xa6, 0x8f, 0xbc, 0xde, 0x98, 0x91, 0xa8, 0xd6, 0x5a, 0xc9, 0x83, 0xef, 0x41, 0xf0, 0x2f, 0xa7, + 0x37, 0x08, 0xc1, 0x06, 0x61, 0x05, 0xf1, 0xf0, 0x75, 0xc5, 0xb2, 0xc6, 0x73, 0x93, 0x33, 0x57, 0x6a, 0x42, 0x8f, + 0x6f, 0x55, 0xb2, 0x78, 0x6e, 0x6e, 0x4f, 0xf9, 0x48, 0x02, 0xa6, 0x8d, 0x02, 0x17, 0x6f, 0x32, 0xde, 0xcf, 0x42, + 0x23, 0x52, 0xe0, 0xcb, 0xb9, 0x11, 0x29, 0xbf, 0xba, 0xde, 0x38, 0xd6, 0x4f, 0x9a, 0x3e, 0x69, 0xf3, 0xb9, 0x2e, + 0x72, 0x0a, 0x6b, 0xb7, 0xae, 0xe1, 0x9d, 0x1f, 0x74, 0x25, 0x0c, 0xd5, 0x29, 0xe5, 0x41, 0xa1, 0x62, 0x64, 0x0b, + 0xa4, 0x21, 0x3f, 0x89, 0xf2, 0xfb, 0x61, 0x3a, 0x63, 0xf8, 0x9a, 0x6a, 0x93, 0x94, 0x95, 0xc4, 0xf9, 0x09, 0x4b, + 0x93, 0x89, 0x37, 0xa6, 0x65, 0xff, 0x77, 0x62, 0xd9, 0xbb, 0x28, 0x63, 0xe6, 0x4e, 0x1c, 0x59, 0xa8, 0x83, 0x52, + 0xde, 0xc3, 0x69, 0x5b, 0xdd, 0x19, 0x44, 0xdb, 0xa2, 0x87, 0xb7, 0x07, 0x1e, 0x8a, 0xd6, 0x24, 0x61, 0xdb, 0x7b, + 0xba, 0x44, 0xac, 0x50, 0x6d, 0x61, 0xf3, 0x3e, 0xf2, 0xac, 0x28, 0xd5, 0xee, 0x4a, 0x5d, 0x7a, 0x07, 0xf5, 0x29, + 0xae, 0xf6, 0x53, 0x93, 0x69, 0xc0, 0x05, 0x82, 0xb9, 0x0b, 0x72, 0x6b, 0x18, 0x70, 0x3d, 0x2f, 0x6e, 0x5b, 0xd4, + 0x66, 0xb5, 0xa1, 0x6e, 0x41, 0x64, 0x5e, 0x30, 0x60, 0x16, 0x6d, 0x26, 0x11, 0x64, 0xc3, 0x32, 0x18, 0x72, 0xa6, + 0x64, 0x81, 0xf3, 0xe2, 0x60, 0x7f, 0x74, 0xaf, 0xe0, 0x84, 0x84, 0xd1, 0x3a, 0x6a, 0x36, 0xf2, 0x45, 0x14, 0xe4, + 0xab, 0xb6, 0xb8, 0xe6, 0x54, 0xdc, 0xd3, 0xa4, 0x56, 0x81, 0x9d, 0xcb, 0x0a, 0xa6, 0xd0, 0xbe, 0x51, 0x58, 0xfa, + 0xca, 0x4a, 0xf2, 0xfc, 0x25, 0xc5, 0x1b, 0x53, 0x81, 0x93, 0xfc, 0x95, 0x86, 0x6d, 0xe7, 0xd6, 0x7d, 0xf7, 0x41, + 0x01, 0xde, 0xa8, 0x23, 0xf3, 0x29, 0xfd, 0x5f, 0xd7, 0x5b, 0x5d, 0x7d, 0x54, 0x9f, 0xdf, 0x4c, 0xf4, 0xb7, 0x4a, + 0x9a, 0xfe, 0xbe, 0x34, 0x01, 0xd9, 0x9f, 0x66, 0xb9, 0x62, 0x6a, 0xf9, 0x48, 0x5e, 0x9a, 0xa7, 0x06, 0x99, 0xd0, + 0x19, 0xe6, 0xf7, 0x8a, 0xfa, 0xd7, 0xee, 0xf3, 0x83, 0xe9, 0xdf, 0xf0, 0x4a, 0xb3, 0x84, 0xd2, 0xd7, 0x9a, 0xd6, + 0x53, 0x62, 0x43, 0x63, 0x80, 0x41, 0x96, 0xe9, 0xa9, 0x53, 0x6b, 0xf6, 0x8e, 0x68, 0x8e, 0x55, 0x77, 0x99, 0x9c, + 0x50, 0x62, 0xe8, 0xae, 0x12, 0x37, 0x1a, 0x61, 0x37, 0x0f, 0x06, 0xad, 0x53, 0xa2, 0x19, 0x25, 0xbb, 0xda, 0xed, + 0x85, 0x8c, 0x09, 0x3d, 0x93, 0x22, 0x5b, 0x96, 0xaa, 0xf7, 0x8a, 0x1e, 0xf2, 0x5d, 0xc9, 0x6f, 0xfe, 0xa9, 0x44, + 0xdc, 0x58, 0x9e, 0xdd, 0x01, 0x64, 0x10, 0x06, 0xb9, 0x43, 0x46, 0xbc, 0x84, 0x0a, 0x14, 0xc6, 0xce, 0x04, 0x5b, + 0x1f, 0xd1, 0x07, 0x58, 0x24, 0x72, 0x91, 0x34, 0x74, 0x8f, 0x2c, 0x11, 0xa8, 0x62, 0xdf, 0x13, 0x97, 0xcd, 0x3f, + 0xa2, 0x68, 0xe3, 0xf6, 0x3c, 0x20, 0x54, 0x26, 0x2d, 0x57, 0xbe, 0xa6, 0x95, 0x2b, 0x8c, 0xb4, 0x8c, 0x24, 0x1c, + 0x1c, 0x81, 0x6f, 0xd4, 0x10, 0x3e, 0x24, 0xa5, 0x97, 0x76, 0x72, 0xb7, 0x5a, 0x5c, 0x93, 0x36, 0x02, 0x88, 0x4d, + 0xc6, 0xf8, 0xf5, 0xd5, 0xae, 0x08, 0xe7, 0x3b, 0x6b, 0xea, 0x3f, 0x71, 0xc0, 0x31, 0xd2, 0x7d, 0x59, 0x0b, 0xbd, + 0x01, 0xab, 0x9a, 0xe1, 0xdb, 0x27, 0x30, 0x54, 0x3a, 0x50, 0x4c, 0x47, 0x0e, 0x15, 0xcd, 0x1b, 0xe0, 0xda, 0xdd, + 0x8a, 0x08, 0xdf, 0xce, 0x5f, 0xb3, 0x4a, 0x35, 0x21, 0x88, 0xb5, 0x26, 0xd1, 0xcc, 0x16, 0x61, 0xb7, 0x71, 0xcf, + 0xe0, 0xf6, 0x1c, 0x8a, 0x3e, 0xbc, 0xc4, 0x45, 0x1c, 0xd8, 0x0c, 0x5e, 0xda, 0x3c, 0x35, 0xa7, 0xf4, 0x5b, 0x5f, + 0xd1, 0xe0, 0xd1, 0x27, 0x86, 0x13, 0x9d, 0x3b, 0x49, 0xc1, 0x23, 0x06, 0x41, 0x98, 0xa4, 0x4f, 0x3d, 0xe1, 0xce, + 0x9b, 0xa9, 0x8b, 0xc4, 0x5d, 0x5c, 0xe0, 0xf6, 0xc3, 0x08, 0x1e, 0x22, 0xdb, 0x4b, 0x15, 0x2b, 0xfe, 0x7d, 0x6e, + 0xeb, 0x6c, 0xa7, 0xec, 0x9c, 0x78, 0x2f, 0x5a, 0xf0, 0x2a, 0xa6, 0xc1, 0x60, 0x03, 0x63, 0x06, 0x1f, 0xba, 0x84, + 0x24, 0xaf, 0x52, 0x2a, 0xd8, 0x08, 0x7a, 0x44, 0xbe, 0x18, 0xc9, 0x28, 0xc9, 0xe8, 0xdb, 0x5f, 0x0c, 0xfe, 0x78, + 0x35, 0x85, 0xad, 0x5b, 0x03, 0xd1, 0x64, 0xf9, 0xf6, 0x9a, 0xef, 0x76, 0xec, 0xe8, 0xca, 0x67, 0x03, 0xb2, 0x96, + 0xd1, 0x9b, 0xb6, 0xe8, 0x47, 0x72, 0x43, 0x2a, 0x88, 0x7d, 0x95, 0x0d, 0x78, 0x6d, 0xdd, 0x70, 0x92, 0xc3, 0x1a, + 0xbc, 0xc7, 0xf4, 0x64, 0xb5, 0xfa, 0xce, 0x6f, 0x6b, 0x19, 0x9e, 0xf1, 0xb7, 0xbd, 0x50, 0x8d, 0x5c, 0x7c, 0x37, + 0x9a, 0xd1, 0xeb, 0x3a, 0x7a, 0x46, 0x99, 0xf4, 0x25, 0xda, 0xd5, 0xa5, 0xfa, 0x9e, 0x6b, 0xe1, 0x00, 0xdc, 0xbb, + 0x19, 0x7c, 0xea, 0xcb, 0x78, 0x6b, 0x13, 0xd6, 0xd3, 0x98, 0x84, 0xd3, 0x6e, 0xc6, 0x83, 0xbf, 0x67, 0x5c, 0x93, + 0xa3, 0xa5, 0xe7, 0x36, 0x9e, 0xd3, 0x36, 0xbe, 0x97, 0x7c, 0x9f, 0x4d, 0xf3, 0xd1, 0x7b, 0x2f, 0x0e, 0x6f, 0x1d, + 0xd8, 0xcb, 0x0c, 0xb7, 0x76, 0xd4, 0xe2, 0xbc, 0xd5, 0xec, 0xb5, 0xd8, 0xb9, 0xc3, 0x9b, 0x10, 0x51, 0x66, 0x7b, + 0x87, 0x7f, 0x8b, 0x35, 0xbf, 0xc1, 0xf7, 0xdf, 0xab, 0xea, 0xd3, 0xa5, 0x99, 0x36, 0x0c, 0xc3, 0x5a, 0xff, 0xbc, + 0xad, 0x65, 0x98, 0xb5, 0xf1, 0x13, 0x3d, 0xcd, 0x34, 0x7e, 0xa3, 0xde, 0xb8, 0xb7, 0x88, 0xbf, 0x5b, 0xf7, 0xff, + 0xe5, 0x93, 0x87, 0x04, 0xc2, 0x8f, 0xec, 0xc7, 0xa3, 0x6d, 0x03, 0xba, 0x5c, 0x7e, 0xe3, 0x5d, 0x24, 0x26, 0x7f, + 0xb4, 0x5e, 0x8e, 0x45, 0xc6, 0xa7, 0x1a, 0x08, 0x77, 0xeb, 0x45, 0x52, 0xd4, 0xf8, 0x8e, 0xbc, 0x19, 0x8f, 0xc9, + 0xea, 0x1d, 0x88, 0x40, 0x41, 0xeb, 0x1b, 0x27, 0xb1, 0xc4, 0x8f, 0xd0, 0x5f, 0x7e, 0x61, 0x75, 0xd2, 0x89, 0xfa, + 0x45, 0x3b, 0xe1, 0xff, 0x9f, 0x7e, 0xbf, 0x0a, 0x67, 0xd9, 0x80, 0xde, 0xfa, 0x7f, 0x41, 0x64, 0x43, 0xed, 0x95, + 0x20, 0x3d, 0x84, 0xf0, 0xc8, 0x9f, 0xdd, 0x6a, 0xca, 0x0a, 0x2b, 0xc7, 0xb9, 0xa8, 0x8c, 0x27, 0x74, 0x4e, 0x2e, + 0x34, 0x4e, 0x92, 0x35, 0x65, 0xf0, 0x9e, 0x49, 0xda, 0x75, 0x95, 0xa1, 0x77, 0x3b, 0xc2, 0x16, 0xa1, 0x93, 0xb8, + 0xcd, 0x92, 0x8a, 0x45, 0xb4, 0x6c, 0x57, 0x6d, 0xae, 0x7e, 0x1e, 0xc1, 0x19, 0x38, 0xce, 0xb2, 0x80, 0xbd, 0x07, + 0x4b, 0x59, 0x77, 0x6e, 0xec, 0x30, 0xdd, 0x6f, 0x1d, 0xc3, 0x11, 0x41, 0x63, 0x2f, 0xe7, 0x6b, 0xfb, 0xa3, 0x12, + 0x4d, 0x22, 0xde, 0x14, 0x18, 0xd0, 0x8b, 0x5f, 0x27, 0x6b, 0x1e, 0xc4, 0xd1, 0xcb, 0xd7, 0x8a, 0xb0, 0xac, 0xd5, + 0x94, 0x3a, 0x8c, 0xcf, 0xbd, 0x54, 0x41, 0xa8, 0x0b, 0x21, 0x93, 0xbd, 0xd0, 0x75, 0x02, 0x32, 0x31, 0xa2, 0xcf, + 0x58, 0x4b, 0x18, 0x93, 0x03, 0xad, 0x82, 0xd2, 0xf1, 0xa1, 0xa6, 0x47, 0x82, 0xd0, 0x73, 0x65, 0x6c, 0x07, 0x39, + 0x4e, 0x4c, 0x53, 0x99, 0x10, 0x4d, 0x1c, 0x02, 0x57, 0x79, 0x92, 0xbc, 0x46, 0x32, 0x5e, 0x4e, 0x02, 0x68, 0xd7, + 0xdd, 0x26, 0x6a, 0x73, 0xd0, 0xbb, 0xff, 0xa4, 0x11, 0x74, 0x51, 0x64, 0xad, 0x67, 0x03, 0x45, 0xf3, 0x14, 0xf3, + 0xc8, 0xb0, 0x49, 0x81, 0x46, 0x11, 0x8a, 0xd0, 0xbf, 0x49, 0xec, 0xa1, 0x91, 0x55, 0x46, 0x72, 0xc1, 0x62, 0xb2, + 0xc2, 0x9c, 0x38, 0x2d, 0x25, 0x9d, 0x72, 0xe2, 0x36, 0xda, 0x01, 0x7e, 0x7c, 0xf5, 0x6d, 0x63, 0xdc, 0xe3, 0x2d, + 0xc6, 0xb6, 0x17, 0x73, 0x16, 0x6f, 0x7f, 0xdc, 0x1b, 0xf8, 0xee, 0xf0, 0x6c, 0x20, 0xb1, 0x85, 0xb1, 0x36, 0x06, + 0x24, 0x99, 0x30, 0x29, 0xb5, 0x7f, 0x16, 0xc6, 0x03, 0x46, 0xb2, 0x50, 0x7c, 0x69, 0x54, 0x94, 0xfb, 0x79, 0x35, + 0xd1, 0x4e, 0xe8, 0x01, 0xe1, 0x28, 0x69, 0xa4, 0x5d, 0xa2, 0x05, 0x91, 0xd6, 0x67, 0xe5, 0x6b, 0x1b, 0x5e, 0x5b, + 0x97, 0x29, 0x83, 0x43, 0x4a, 0xa6, 0x45, 0x62, 0x45, 0x06, 0x1e, 0x10, 0xb7, 0x11, 0xd2, 0x8b, 0xe4, 0x05, 0x94, + 0x00, 0xbc, 0x8a, 0x68, 0x6f, 0x54, 0xc6, 0x22, 0x79, 0x93, 0xb5, 0x4a, 0x61, 0x09, 0x40, 0xe0, 0x5f, 0xf6, 0x97, + 0x05, 0x4b, 0x44, 0x8d, 0x1a, 0xad, 0xe9, 0x8c, 0x40, 0xed, 0xf0, 0x1d, 0x10, 0x31, 0xbc, 0xc6, 0x6e, 0x14, 0xa6, + 0x0d, 0xcd, 0xe2, 0x18, 0xe3, 0xaa, 0xbd, 0xf9, 0xcc, 0xe5, 0x76, 0x3c, 0x36, 0xdf, 0x5a, 0x0f, 0xec, 0x4d, 0xba, + 0x67, 0xb1, 0xf7, 0x97, 0x0c, 0x4f, 0x2b, 0x9c, 0xd7, 0x13, 0xb9, 0xc9, 0xe3, 0xa3, 0xaf, 0x7b, 0xd7, 0x22, 0xd4, + 0xdc, 0x5c, 0x9b, 0x87, 0x9b, 0xf7, 0xae, 0x2a, 0x7c, 0x37, 0xff, 0x32, 0x8e, 0xa7, 0x35, 0x59, 0xf9, 0x1b, 0x45, + 0x29, 0x56, 0xd0, 0x9b, 0xd0, 0x31, 0x43, 0x20, 0x4f, 0x95, 0x1e, 0xec, 0x4e, 0xa2, 0xdc, 0x7e, 0x53, 0x31, 0xdb, + 0x76, 0x2e, 0x14, 0x9d, 0x29, 0x34, 0x39, 0xe1, 0x19, 0xcd, 0xb7, 0x23, 0x14, 0x91, 0xb4, 0x5c, 0x67, 0x6a, 0x5a, + 0xba, 0x9f, 0xf3, 0xf5, 0xc7, 0x65, 0x01, 0x1d, 0x52, 0x7c, 0xdc, 0xd7, 0x8c, 0x82, 0xa0, 0xe1, 0x13, 0xa9, 0x4c, + 0xc8, 0x63, 0x9d, 0x58, 0x56, 0xbb, 0x2f, 0xd3, 0x34, 0xdd, 0x5f, 0xbf, 0xa0, 0xc7, 0xde, 0x0e, 0x45, 0x62, 0x63, + 0xce, 0x34, 0x6b, 0x81, 0x1b, 0x79, 0x70, 0x7a, 0x6a, 0x3d, 0xf3, 0xb2, 0xb3, 0x7a, 0x6b, 0xe7, 0x81, 0x0e, 0x98, + 0x44, 0xda, 0x86, 0x39, 0x99, 0x17, 0xfd, 0xf9, 0x70, 0x51, 0xf7, 0xea, 0x6b, 0x35, 0xb6, 0x63, 0x8e, 0x49, 0x4f, + 0x3c, 0xfc, 0x44, 0x08, 0xbe, 0xa5, 0x20, 0x1d, 0xe0, 0xf2, 0xf5, 0xe9, 0x08, 0x32, 0xad, 0x02, 0x12, 0x93, 0xb6, + 0x7e, 0x7d, 0x17, 0x20, 0xe8, 0xb4, 0x02, 0x0e, 0x18, 0x8a, 0x9f, 0x81, 0x7a, 0x8d, 0x6f, 0x35, 0x48, 0x52, 0x62, + 0x7d, 0x32, 0xc3, 0xa7, 0x4f, 0xa2, 0xc0, 0xc3, 0x57, 0x47, 0xb6, 0x69, 0x43, 0x19, 0x67, 0x10, 0x8e, 0x69, 0x3d, + 0x58, 0x32, 0x33, 0x9e, 0xf7, 0xb8, 0xc7, 0xf1, 0x77, 0x4a, 0xe0, 0x5d, 0x22, 0x6f, 0x52, 0xb0, 0x25, 0xe3, 0x04, + 0x06, 0x97, 0xbd, 0x15, 0x1f, 0x41, 0x5c, 0xbf, 0x87, 0x35, 0xa6, 0x93, 0x3c, 0xcf, 0xc5, 0xe1, 0x1f, 0xc6, 0x6f, + 0x3d, 0xf2, 0xe0, 0xdb, 0x07, 0x83, 0x2e, 0x4d, 0xeb, 0x05, 0xc3, 0x16, 0x7b, 0xb1, 0x01, 0x6a, 0x05, 0xcc, 0xac, + 0x64, 0xdc, 0x4a, 0x8b, 0x14, 0x38, 0xea, 0xdb, 0x07, 0xbf, 0x98, 0xdd, 0xb5, 0x71, 0x40, 0xfa, 0x44, 0x43, 0xd8, + 0xf8, 0x54, 0x05, 0x2e, 0x51, 0xc7, 0xef, 0xd1, 0xbf, 0xbc, 0x5d, 0xae, 0x61, 0x9d, 0x8a, 0x9f, 0xda, 0x78, 0x96, + 0xd1, 0xca, 0xf2, 0xfc, 0x3e, 0x7a, 0xf1, 0xa1, 0xa5, 0xa5, 0x1f, 0xc6, 0x18, 0xac, 0xe4, 0x17, 0x0a, 0xf5, 0x84, + 0x1d, 0xff, 0x32, 0x45, 0xfe, 0xbe, 0x36, 0xf9, 0xf4, 0xec, 0xef, 0xad, 0xff, 0x5f, 0xc2, 0x95, 0xa1, 0x6a, 0xce, + 0x7f, 0x17, 0xff, 0xbd, 0x5c, 0x56, 0x43, 0x75, 0x7d, 0x7d, 0xf6, 0xbd, 0x6a, 0xbb, 0x31, 0x1f, 0xed, 0xb2, 0x4c, + 0x0a, 0x18, 0x95, 0x99, 0x89, 0x82, 0x32, 0xcf, 0xd5, 0xf9, 0x22, 0xf6, 0xeb, 0x8e, 0xb8, 0xf0, 0xcb, 0xf1, 0x23, + 0xfd, 0xe7, 0xd7, 0xe5, 0xb8, 0x5a, 0xf7, 0x90, 0xca, 0x7d, 0xb1, 0xa2, 0xda, 0x56, 0x36, 0x4b, 0xfe, 0x7e, 0xe8, + 0xdc, 0x11, 0x81, 0xf5, 0xe7, 0xc1, 0x77, 0x5c, 0xc6, 0x9f, 0x0c, 0x6f, 0x12, 0x45, 0x6b, 0x83, 0xc5, 0x67, 0xfa, + 0x4c, 0xab, 0x87, 0x3b, 0x5e, 0x55, 0xd9, 0xa6, 0x9e, 0x51, 0x9b, 0x6e, 0x02, 0x62, 0x52, 0x41, 0x85, 0xb2, 0xce, + 0xfd, 0xf2, 0xb7, 0x8b, 0x36, 0x24, 0x14, 0xfd, 0xe4, 0xbb, 0xc0, 0x7d, 0xb0, 0x34, 0x27, 0x80, 0xc4, 0x86, 0xd9, + 0xf1, 0x4b, 0x81, 0x33, 0xa0, 0xe1, 0xa1, 0xd6, 0xdd, 0xd4, 0x56, 0x7d, 0x0e, 0x53, 0xfe, 0x43, 0x4c, 0xf7, 0x50, + 0x07, 0x53, 0x82, 0x10, 0xe7, 0xe1, 0xf2, 0xff, 0xf4, 0x13, 0x91, 0x34, 0x1d, 0x4c, 0x1d, 0x03, 0xe7, 0xae, 0xfc, + 0x13, 0xbb, 0x17, 0xaf, 0xf4, 0x92, 0xc5, 0xeb, 0x5a, 0x31, 0xf0, 0x2c, 0x08, 0xaa, 0x2b, 0x72, 0x6c, 0xf2, 0x9c, + 0x3e, 0x6b, 0xec, 0x2e, 0xff, 0xa7, 0x59, 0x16, 0x39, 0x2c, 0x38, 0x46, 0x7c, 0xe7, 0x03, 0x84, 0x25, 0x59, 0x0d, + 0x1b, 0xb3, 0x87, 0xe3, 0xf8, 0xde, 0x1b, 0x68, 0x38, 0x0e, 0xef, 0xef, 0x20, 0x01, 0xdf, 0xf8, 0xa9, 0xba, 0x1a, + 0x95, 0x47, 0xe6, 0xb4, 0x8f, 0x63, 0xdc, 0x18, 0x3a, 0xc7, 0x48, 0x04, 0xa7, 0x9c, 0x02, 0xc4, 0x37, 0x49, 0x2b, + 0x02, 0x16, 0x13, 0xbc, 0x0d, 0xc9, 0x41, 0x3b, 0x2e, 0xa6, 0xc9, 0x94, 0x43, 0xcb, 0xa9, 0xa3, 0x0e, 0x90, 0xe3, + 0x3c, 0x3a, 0xb0, 0xac, 0xdb, 0xeb, 0xc8, 0xf8, 0x2a, 0x93, 0x76, 0xd9, 0x42, 0xf6, 0xbb, 0x08, 0x33, 0x89, 0x18, + 0xa9, 0xe0, 0x61, 0xce, 0x6a, 0x08, 0xfa, 0x45, 0x8b, 0x98, 0xda, 0x88, 0x86, 0x7b, 0xdb, 0x19, 0xb1, 0x8b, 0x40, + 0x83, 0x2b, 0x07, 0xf2, 0x8a, 0x57, 0x81, 0x87, 0x89, 0x4c, 0x58, 0x40, 0x2c, 0xe0, 0xc2, 0xbc, 0xef, 0x2b, 0x8e, + 0x87, 0xfd, 0x22, 0x0a, 0x2e, 0xb1, 0xf7, 0xfd, 0x70, 0xa1, 0x60, 0xe7, 0x55, 0xaf, 0xef, 0x8b, 0xdb, 0xe3, 0xdf, + 0x5e, 0x32, 0x22, 0x05, 0x96, 0x41, 0xde, 0xe0, 0xf9, 0x41, 0x18, 0xf8, 0xa1, 0x7d, 0x04, 0x47, 0x29, 0xdb, 0x11, + 0x9c, 0x28, 0xc8, 0x7a, 0x52, 0xc3, 0xb4, 0x65, 0xc1, 0xd5, 0xe9, 0xb4, 0xcd, 0x3c, 0x0a, 0xd2, 0x28, 0xd5, 0xfb, + 0xe7, 0x3e, 0x28, 0x13, 0xa2, 0x55, 0xa6, 0xc0, 0xb6, 0x00, 0xb4, 0x6c, 0x45, 0x78, 0x8d, 0xd9, 0x84, 0x05, 0x3a, + 0xe9, 0xb4, 0xcd, 0x3c, 0x5a, 0x82, 0x6f, 0x40, 0x27, 0x17, 0x1b, 0x2e, 0xb6, 0x5c, 0x3a, 0x69, 0xdf, 0x8e, 0x07, + 0x08, 0x62, 0x02, 0x1e, 0xca, 0xc8, 0x30, 0x9f, 0xa4, 0x7c, 0xe9, 0xd9, 0x6f, 0xd3, 0x45, 0x7b, 0x6d, 0xf2, 0xf1, + 0xa2, 0x0e, 0x98, 0xd8, 0x40, 0x9a, 0x56, 0x27, 0x11, 0xbe, 0x3f, 0xa4, 0x80, 0x1d, 0x94, 0xe6, 0xf8, 0xd8, 0x02, + 0x51, 0x49, 0x45, 0xc8, 0xb8, 0x5e, 0x1e, 0x7a, 0xb6, 0xdf, 0x4c, 0x02, 0x35, 0x3b, 0x05, 0x1f, 0x70, 0xd1, 0x68, + 0x52, 0x7c, 0x42, 0x98, 0x12, 0x46, 0x88, 0xac, 0x04, 0x24, 0xc0, 0x4d, 0x66, 0x03, 0x94, 0x3f, 0x29, 0x17, 0xee, + 0x2b, 0xf1, 0xc7, 0x0e, 0xe1, 0x80, 0x9f, 0x39, 0x02, 0x47, 0x19, 0x35, 0xfa, 0xa7, 0xb3, 0x6d, 0x98, 0x9c, 0x22, + 0x56, 0xf4, 0x06, 0x5c, 0x41, 0x58, 0x50, 0xf2, 0xae, 0x83, 0xed, 0x75, 0x3f, 0x14, 0xcb, 0xfe, 0x88, 0x26, 0x3c, + 0xe2, 0x81, 0x7c, 0xd8, 0x55, 0x0b, 0x4d, 0x0c, 0x7c, 0x00, 0xb9, 0xa2, 0xca, 0x68, 0x22, 0x73, 0x7a, 0x8f, 0x2a, + 0x47, 0x2b, 0xf4, 0x7d, 0x1d, 0x64, 0x4a, 0xf5, 0x78, 0x2d, 0x53, 0xdd, 0x08, 0x6b, 0x68, 0x25, 0x50, 0xb6, 0x31, + 0x5b, 0x96, 0x6a, 0xd9, 0x5e, 0xbf, 0xe9, 0x22, 0x7f, 0x11, 0x47, 0xac, 0x61, 0x4b, 0xc0, 0xb9, 0xfa, 0xc2, 0xd8, + 0x76, 0xb2, 0xeb, 0xec, 0x88, 0x92, 0x9e, 0x36, 0xdc, 0x95, 0x31, 0x72, 0xf3, 0x9a, 0xed, 0xa9, 0xa7, 0x18, 0x6d, + 0x35, 0x74, 0x5e, 0xf9, 0x26, 0x35, 0x27, 0x05, 0x57, 0xfc, 0x7c, 0x7f, 0x0d, 0xaf, 0x9d, 0x57, 0xf1, 0xe8, 0xc2, + 0x46, 0xc4, 0x33, 0x6b, 0xfe, 0xb5, 0x4a, 0x5e, 0x6d, 0xd6, 0xb0, 0x54, 0xfe, 0x75, 0x0d, 0xb6, 0x90, 0x7d, 0x47, + 0x16, 0xe5, 0xdf, 0x20, 0x8e, 0xc2, 0x47, 0x6d, 0xeb, 0x2a, 0xdc, 0xc0, 0xda, 0xf8, 0x68, 0x78, 0x15, 0xbd, 0x38, + 0x3c, 0xc0, 0x39, 0x08, 0x90, 0x07, 0x4e, 0x42, 0x18, 0xb0, 0xcf, 0x19, 0xac, 0xf9, 0x2a, 0xd3, 0x98, 0xf3, 0xa6, + 0x87, 0x3c, 0xd7, 0xfe, 0x82, 0xd7, 0x80, 0x0a, 0x68, 0x0f, 0x3b, 0x7c, 0x5a, 0x83, 0x09, 0x4d, 0x5d, 0x84, 0x7c, + 0xfa, 0xe0, 0x77, 0xf9, 0x69, 0x41, 0x61, 0xb2, 0x53, 0x20, 0xc3, 0xe9, 0xaa, 0x9b, 0x19, 0x5a, 0x69, 0x82, 0xf9, + 0xf7, 0xbb, 0x9b, 0x51, 0x43, 0xe4, 0x5e, 0x84, 0x4c, 0x37, 0x90, 0xd1, 0x8a, 0x69, 0xd8, 0x34, 0xd3, 0xb4, 0x1a, + 0x0c, 0xd5, 0x47, 0x4d, 0x5b, 0xfc, 0xb5, 0x57, 0x6f, 0x50, 0x0e, 0x9d, 0xda, 0xc0, 0xf0, 0x9e, 0xfd, 0xb5, 0x7f, + 0x28, 0xc8, 0x8b, 0xa2, 0xd0, 0xb4, 0xd3, 0x21, 0x32, 0xdc, 0xc6, 0x8e, 0xb5, 0x1e, 0xff, 0x33, 0xc3, 0x38, 0xf9, + 0xcc, 0x4c, 0x10, 0xd8, 0x10, 0x0b, 0x45, 0x0b, 0xfa, 0x69, 0xb3, 0x0c, 0x77, 0xca, 0x46, 0xaf, 0x05, 0xbf, 0x54, + 0xa5, 0xc6, 0x01, 0x71, 0x0e, 0x2f, 0x2f, 0x4c, 0xf1, 0xc4, 0x43, 0x7f, 0x17, 0x98, 0xf0, 0x69, 0x9e, 0x5a, 0x51, + 0x57, 0x27, 0xaf, 0xd2, 0x8f, 0xed, 0x67, 0x29, 0x0f, 0x49, 0xba, 0x22, 0x0b, 0x0d, 0xd9, 0xdc, 0xe0, 0xd5, 0xda, + 0x17, 0x9b, 0x59, 0xc1, 0xe7, 0x2b, 0x24, 0x88, 0x06, 0x87, 0x2d, 0x35, 0xe5, 0x0b, 0xcf, 0x1b, 0x47, 0x2a, 0x2b, + 0x17, 0xbb, 0xe0, 0x10, 0x0d, 0x96, 0x4b, 0x8b, 0x59, 0x2b, 0xc7, 0xef, 0xd1, 0xe8, 0x4f, 0xaa, 0xc5, 0x39, 0x2a, + 0xe5, 0xfc, 0x03, 0xb3, 0x3f, 0xf5, 0xfd, 0x01, 0xa3, 0x7b, 0xbf, 0x27, 0xf9, 0xac, 0x2f, 0xfa, 0x6a, 0x13, 0xca, + 0x3b, 0x79, 0x73, 0xc0, 0xe7, 0x2e, 0x2c, 0xe3, 0x18, 0x29, 0x20, 0xf8, 0x9b, 0x9d, 0x53, 0x43, 0x8f, 0x30, 0x25, + 0xad, 0xe9, 0x45, 0x7e, 0x5b, 0x11, 0x2d, 0xd1, 0xc0, 0x5b, 0x1c, 0x17, 0xe9, 0x43, 0x5b, 0xde, 0x65, 0x4b, 0x19, + 0x0f, 0xdd, 0xca, 0x8c, 0x24, 0x1a, 0x55, 0x1a, 0x3e, 0x88, 0x9e, 0x3b, 0x5b, 0x0a, 0xbd, 0xdd, 0x19, 0xcd, 0x9e, + 0x50, 0x65, 0x42, 0x53, 0x23, 0x6e, 0x55, 0xfb, 0x1a, 0xeb, 0x9a, 0x9a, 0x6e, 0xe8, 0x85, 0x9a, 0xa1, 0x10, 0x52, + 0x8d, 0xbe, 0x50, 0xeb, 0x47, 0x11, 0x92, 0x34, 0x1e, 0xbf, 0xa6, 0x71, 0x9b, 0xf3, 0xf5, 0x5a, 0x28, 0x57, 0x67, + 0x15, 0x05, 0x9a, 0x9f, 0xa7, 0x89, 0xc7, 0xd8, 0xcf, 0x8d, 0xf4, 0xd5, 0xe7, 0x0b, 0xc0, 0x40, 0x90, 0xdc, 0xea, + 0xae, 0x21, 0x0d, 0x2d, 0xfb, 0xe9, 0x71, 0x9e, 0x99, 0x22, 0x40, 0xb7, 0x2a, 0x13, 0x4f, 0x5d, 0xcc, 0xbb, 0x3d, + 0x26, 0xa8, 0xd8, 0xf9, 0x8a, 0x32, 0x00, 0x4e, 0x52, 0x07, 0xd1, 0xa8, 0xdd, 0xdb, 0x9d, 0xa6, 0xcb, 0xb9, 0x78, + 0x06, 0x2e, 0x84, 0xe5, 0xb4, 0xbc, 0x7a, 0x16, 0xed, 0x7a, 0x86, 0x34, 0xc9, 0xf6, 0xed, 0xaa, 0x2e, 0x5d, 0x70, + 0x47, 0x26, 0x8d, 0x84, 0x96, 0x6a, 0x28, 0xe4, 0x2a, 0x59, 0x3b, 0xea, 0xce, 0x86, 0x39, 0xe5, 0x26, 0x0a, 0xb7, + 0x32, 0x97, 0x25, 0x65, 0xac, 0xc9, 0x11, 0x96, 0x71, 0x79, 0x94, 0x58, 0x16, 0xe0, 0x7b, 0x60, 0x10, 0x95, 0xaa, + 0x3c, 0x13, 0x45, 0x48, 0x86, 0x06, 0x0b, 0x4c, 0x44, 0xf7, 0xfd, 0x16, 0x26, 0x78, 0xf0, 0xb5, 0x8f, 0x0a, 0x92, + 0xc2, 0x4e, 0x40, 0x00, 0x0d, 0x16, 0x5a, 0x40, 0x35, 0x4b, 0x6b, 0x55, 0xf7, 0x10, 0x3a, 0xaf, 0xc4, 0x57, 0x94, + 0x24, 0x83, 0xfe, 0xc3, 0x6f, 0x27, 0x84, 0x41, 0xeb, 0x10, 0x4a, 0x56, 0x1c, 0x70, 0xc3, 0xe2, 0x6a, 0x1a, 0xe9, + 0xb2, 0x65, 0xb1, 0xdc, 0x62, 0xcf, 0xe7, 0x36, 0xa5, 0x15, 0xac, 0xbc, 0x84, 0x92, 0x76, 0x2c, 0x2f, 0x7b, 0x5d, + 0x14, 0x7e, 0xf6, 0x9a, 0x1f, 0x31, 0xb9, 0x30, 0x0e, 0x49, 0xb9, 0x20, 0x21, 0x95, 0x47, 0x00, 0x32, 0x1f, 0x07, + 0xaa, 0x37, 0x05, 0x0f, 0x5b, 0x65, 0x73, 0xd4, 0x0c, 0xc1, 0xc1, 0xbd, 0xf3, 0xa9, 0x43, 0x0a, 0xf3, 0x18, 0x96, + 0x00, 0x4e, 0xc2, 0x91, 0xd0, 0x66, 0x6e, 0x72, 0xa6, 0x4e, 0x4f, 0xb2, 0xeb, 0xa0, 0xbb, 0xb5, 0xd5, 0x09, 0xc5, + 0x5e, 0xd6, 0x09, 0x11, 0x25, 0x55, 0x8f, 0x41, 0xb9, 0x19, 0x51, 0x5c, 0xfb, 0x21, 0x0e, 0x65, 0x18, 0xb2, 0x9b, + 0x0d, 0x30, 0x12, 0x2b, 0x42, 0x72, 0x1a, 0x24, 0xc9, 0x33, 0xe9, 0xb2, 0x36, 0x2d, 0xea, 0x3a, 0xbf, 0x45, 0x08, + 0x8f, 0x48, 0xc6, 0xf9, 0x59, 0x1e, 0xca, 0x8e, 0x2b, 0x1b, 0x54, 0x59, 0x9e, 0x9e, 0x7c, 0xd7, 0xbd, 0xa6, 0x4c, + 0x0d, 0xef, 0x01, 0x15, 0x99, 0x1c, 0xba, 0xcd, 0xf5, 0x31, 0x37, 0xc1, 0x6f, 0x5c, 0x1e, 0xa8, 0x8b, 0x87, 0x0d, + 0x49, 0xe8, 0xe7, 0x5b, 0xbc, 0x4e, 0x54, 0x1a, 0xbd, 0x43, 0x2c, 0xbd, 0xe6, 0xe2, 0x5c, 0x0b, 0x94, 0x58, 0xf0, + 0x95, 0x4a, 0x32, 0xb4, 0x91, 0x07, 0x7e, 0x10, 0x67, 0x42, 0x17, 0xb9, 0x2c, 0x5d, 0x23, 0x3f, 0xa7, 0x77, 0xcb, + 0x99, 0x78, 0x92, 0x5f, 0xfb, 0x9c, 0x95, 0x5e, 0xa7, 0x3f, 0x22, 0x71, 0x16, 0x9f, 0xb8, 0x44, 0x37, 0xd3, 0x3c, + 0x8c, 0x93, 0xba, 0x6a, 0xaf, 0x00, 0xb4, 0xba, 0xf0, 0x96, 0xa3, 0xf6, 0x48, 0xe0, 0xb9, 0xed, 0x2e, 0x71, 0xa5, + 0xbc, 0xe0, 0x0e, 0x8a, 0x3d, 0x4c, 0x30, 0x10, 0x1a, 0x65, 0x2c, 0x1c, 0x00, 0x7e, 0x0e, 0xf1, 0x6e, 0xcc, 0x5f, + 0x0d, 0xc6, 0xaa, 0x5a, 0xe0, 0xdc, 0x29, 0x33, 0xc8, 0x5e, 0x86, 0x06, 0x14, 0x48, 0x27, 0x64, 0x41, 0x69, 0xa3, + 0xc6, 0x0e, 0xa8, 0x08, 0x2b, 0x54, 0xdd, 0x01, 0xc7, 0x26, 0xcd, 0xda, 0xa7, 0x26, 0x62, 0x44, 0x83, 0x6e, 0x72, + 0xfe, 0xba, 0x83, 0x84, 0xf2, 0x0d, 0xa4, 0x60, 0x22, 0xfd, 0x12, 0xfd, 0x07, 0x30, 0xe9, 0x9d, 0x9c, 0xa0, 0xdd, + 0x24, 0xc6, 0xfd, 0x52, 0x4e, 0xa0, 0xb4, 0x4c, 0xd9, 0x3f, 0x0c, 0x8e, 0x6f, 0x80, 0x31, 0x3d, 0x63, 0xb9, 0xb6, + 0xd5, 0xfc, 0x45, 0x65, 0x28, 0x30, 0x5e, 0x22, 0x52, 0x61, 0x00, 0xf8, 0xbd, 0x19, 0x34, 0x1e, 0x83, 0xb7, 0x9f, + 0x70, 0x9c, 0x6a, 0x4d, 0x28, 0xad, 0x01, 0xf9, 0x16, 0xff, 0x8b, 0x71, 0xde, 0x29, 0x80, 0x53, 0x2b, 0xf2, 0xde, + 0xd9, 0xdd, 0xad, 0x43, 0xc0, 0xd0, 0xd7, 0x61, 0xce, 0xfc, 0x49, 0x8c, 0xb2, 0x81, 0xdc, 0xb6, 0x3b, 0xdd, 0x57, + 0x25, 0x75, 0x26, 0x19, 0xae, 0x48, 0x0c, 0x52, 0x69, 0xe4, 0x47, 0x5d, 0x59, 0x5c, 0x66, 0xd1, 0x15, 0x5c, 0x00, + 0xba, 0xe3, 0x19, 0x36, 0x6f, 0x6c, 0xd6, 0x17, 0x7e, 0x06, 0x69, 0xae, 0xeb, 0x00, 0xc0, 0x0b, 0x4f, 0x35, 0x36, + 0xdc, 0x31, 0x57, 0xd9, 0x0e, 0x0c, 0xef, 0x26, 0x9a, 0x83, 0xf3, 0x7c, 0x54, 0x21, 0x9f, 0xe0, 0x45, 0xc5, 0xe7, + 0x45, 0x40, 0x3c, 0x8e, 0x93, 0xca, 0x60, 0x88, 0x14, 0xfc, 0x44, 0x84, 0x1d, 0x8f, 0x17, 0xce, 0xa3, 0xbb, 0x2a, + 0xb7, 0x3a, 0x40, 0x35, 0x24, 0x60, 0x95, 0xb1, 0x0d, 0x1b, 0x3f, 0x09, 0x12, 0x97, 0xed, 0xac, 0xa0, 0x67, 0x1d, + 0x0e, 0x6a, 0xe1, 0x63, 0xb7, 0xf4, 0x43, 0x52, 0x28, 0xc4, 0xb9, 0x08, 0xe7, 0x29, 0x24, 0x4f, 0x07, 0xc8, 0x8c, + 0xbc, 0x98, 0x9c, 0xa5, 0xee, 0x6c, 0xd7, 0x6d, 0x4a, 0x25, 0x5d, 0xe2, 0xad, 0xb5, 0x9e, 0x8c, 0x57, 0x9a, 0x8e, + 0xa9, 0x08, 0x24, 0x4d, 0xa4, 0x82, 0x2a, 0xa5, 0xc1, 0xce, 0xd3, 0x01, 0x50, 0x30, 0xb7, 0xfc, 0xad, 0x99, 0x9e, + 0x96, 0x09, 0xdd, 0xe6, 0x68, 0xb0, 0x4d, 0x1c, 0xa6, 0x1f, 0x0c, 0x3a, 0x85, 0xb8, 0x51, 0xbb, 0xc0, 0xa3, 0x4c, + 0x85, 0xd8, 0xe3, 0x3f, 0xcc, 0xf7, 0x2a, 0xe6, 0x6c, 0xf2, 0xa2, 0x15, 0x2f, 0x52, 0xcb, 0xe4, 0x29, 0xd8, 0xab, + 0x7f, 0xa7, 0x3f, 0x12, 0x8d, 0x06, 0x14, 0x80, 0x8e, 0x35, 0x85, 0x84, 0xb8, 0xd0, 0xa4, 0x3b, 0x79, 0x5b, 0x7c, + 0xf3, 0xf7, 0x9f, 0x3e, 0x16, 0xe2, 0x7b, 0xad, 0x8f, 0x7c, 0x9e, 0x34, 0x61, 0xbb, 0x53, 0x2f, 0x75, 0xfd, 0x02, + 0x2c, 0x80, 0x9d, 0x6d, 0x3c, 0x13, 0xc3, 0xeb, 0x06, 0xfa, 0xa2, 0x0b, 0xf2, 0xc2, 0xe1, 0x3d, 0xcb, 0xf1, 0x3d, + 0x5e, 0x91, 0x05, 0x7a, 0x3b, 0x77, 0x66, 0x11, 0x90, 0x19, 0x40, 0x05, 0xe4, 0x15, 0x34, 0x39, 0x5d, 0x28, 0xf8, + 0x95, 0x41, 0xd1, 0xec, 0x84, 0x31, 0x40, 0x39, 0x1b, 0x38, 0xcd, 0xf9, 0x5b, 0x12, 0x65, 0x20, 0xe2, 0xbd, 0x4d, + 0x53, 0xfd, 0xd3, 0x4e, 0x1d, 0x32, 0xe5, 0xe9, 0x25, 0x1d, 0xca, 0x26, 0x34, 0xd7, 0x3a, 0x6d, 0x2c, 0x21, 0x3e, + 0xc1, 0x7d, 0xe1, 0xe6, 0xc5, 0x1b, 0x18, 0x68, 0x1f, 0x6c, 0x92, 0x2f, 0xb7, 0xdf, 0x26, 0xf3, 0x8c, 0x6b, 0x2f, + 0xbe, 0x5e, 0x30, 0x3c, 0xc1, 0x1d, 0xba, 0x50, 0xb5, 0xfa, 0xa4, 0x82, 0xe1, 0x3a, 0x1c, 0x95, 0x3f, 0xa7, 0xfd, + 0xa0, 0x9b, 0xb9, 0x29, 0x8a, 0x8a, 0x29, 0x89, 0x5f, 0x93, 0x30, 0x69, 0x50, 0xb7, 0xa6, 0xc7, 0x69, 0x3d, 0xd1, + 0xb9, 0x74, 0x72, 0xf7, 0xfa, 0xaf, 0xb0, 0xda, 0xc4, 0xa8, 0xeb, 0xa2, 0x1e, 0xd3, 0xef, 0x7a, 0xd1, 0xa8, 0x34, + 0x69, 0x46, 0x63, 0xe7, 0xea, 0xa1, 0xbf, 0x59, 0x78, 0x24, 0xba, 0xbf, 0xe8, 0x69, 0xc5, 0x2c, 0xad, 0xb2, 0xf8, + 0x0b, 0x17, 0x55, 0xc4, 0xf5, 0x56, 0x27, 0xef, 0x56, 0x89, 0x2b, 0x39, 0xf6, 0xe2, 0x59, 0x92, 0x41, 0xaf, 0x3a, + 0x34, 0x35, 0x46, 0x24, 0x27, 0x44, 0xfd, 0x8a, 0x41, 0xc0, 0xac, 0x67, 0x7c, 0x72, 0xbd, 0x96, 0x4c, 0xd3, 0xb7, + 0x1e, 0x73, 0xbd, 0x39, 0x0f, 0xb7, 0xef, 0x0b, 0xb8, 0xda, 0x96, 0xf5, 0xbd, 0x37, 0xca, 0x5c, 0x36, 0x12, 0x31, + 0x85, 0x1d, 0x89, 0xfe, 0xf8, 0x07, 0x44, 0x12, 0xe1, 0x27, 0xad, 0xf1, 0x45, 0x52, 0x49, 0xfb, 0x8d, 0x39, 0xdc, + 0x47, 0x4d, 0x06, 0xda, 0xdb, 0x3f, 0xf2, 0x54, 0x4d, 0x2f, 0x17, 0x8a, 0xaa, 0xea, 0x40, 0xbd, 0xa6, 0xd4, 0xe6, + 0x3e, 0xff, 0x6a, 0xf7, 0x28, 0x05, 0x95, 0xaa, 0x52, 0x58, 0xd3, 0x3b, 0x80, 0x29, 0xb4, 0xd8, 0x0b, 0xaa, 0xfa, + 0xb9, 0x52, 0xde, 0xf6, 0x7f, 0x9c, 0x1f, 0xff, 0x43, 0x1b, 0xbf, 0x97, 0x21, 0x36, 0xc1, 0x39, 0x7a, 0xbd, 0xdf, + 0xa7, 0xa7, 0x81, 0x28, 0x9a, 0x57, 0x8b, 0x76, 0xbb, 0xf1, 0xb6, 0xb4, 0x5a, 0xb4, 0xa5, 0xd5, 0xa2, 0x49, 0x6f, + 0x98, 0xdf, 0xab, 0x63, 0x0e, 0xbc, 0x3c, 0xc1, 0x75, 0x86, 0x12, 0x3c, 0xb5, 0xe5, 0x3f, 0xba, 0x80, 0x29, 0x0f, + 0x48, 0x8d, 0x36, 0xd0, 0xa7, 0x32, 0x70, 0xba, 0xb9, 0x61, 0x44, 0xd3, 0x55, 0x2b, 0x23, 0xcd, 0xbc, 0x27, 0x45, + 0xef, 0x73, 0x12, 0x73, 0xb0, 0x67, 0x2c, 0x59, 0xbb, 0xbe, 0x98, 0xf1, 0xa3, 0xd2, 0x16, 0x0c, 0x1d, 0x0a, 0x3f, + 0x26, 0x99, 0x1a, 0xd0, 0x8b, 0xce, 0x8f, 0x69, 0xe9, 0xa3, 0xf4, 0xb7, 0x2f, 0xb9, 0xe4, 0xef, 0x17, 0xa2, 0x78, + 0xd4, 0xd2, 0xac, 0x03, 0x01, 0xbf, 0x26, 0x34, 0x23, 0xe3, 0xb7, 0x62, 0xd0, 0xbe, 0x48, 0x9f, 0x73, 0x11, 0x38, + 0x15, 0x20, 0x7f, 0x48, 0x18, 0x07, 0x8a, 0x86, 0xf6, 0x1a, 0x7b, 0xa0, 0x54, 0x99, 0x3e, 0xf7, 0xb4, 0x44, 0x4f, + 0x94, 0x16, 0xfa, 0xc9, 0x18, 0x63, 0xd6, 0x54, 0x14, 0x5b, 0x9a, 0xf7, 0x0d, 0x32, 0xc9, 0x17, 0x4e, 0x12, 0x5a, + 0xcc, 0xa9, 0xa3, 0x40, 0xb7, 0x0a, 0xb5, 0xf6, 0x60, 0xfa, 0x0e, 0x95, 0x03, 0x4b, 0x45, 0xd9, 0xf7, 0x53, 0x6c, + 0x11, 0x1f, 0xda, 0x25, 0x3e, 0x43, 0xe2, 0xb6, 0x17, 0x99, 0x08, 0x52, 0x8d, 0x51, 0x51, 0x3c, 0xb6, 0x4d, 0x1a, + 0x24, 0x37, 0x54, 0x31, 0x8c, 0x51, 0x27, 0x56, 0x4f, 0x63, 0x75, 0x62, 0x75, 0x9a, 0x95, 0xa2, 0x2d, 0x99, 0xa9, + 0x67, 0x24, 0x96, 0xae, 0xb0, 0x8b, 0x97, 0xdf, 0xe0, 0xd5, 0xe9, 0xb3, 0x07, 0x74, 0x51, 0xe9, 0x55, 0xd3, 0xc0, + 0x67, 0xb0, 0x81, 0x34, 0xaa, 0x03, 0x91, 0x97, 0xf8, 0xcd, 0x04, 0x04, 0x60, 0xf4, 0xf0, 0x03, 0x15, 0xa5, 0xd3, + 0xa6, 0x3d, 0xec, 0xac, 0x4c, 0x40, 0x74, 0xcd, 0x07, 0x63, 0x92, 0x37, 0x48, 0x70, 0xde, 0x8b, 0xa7, 0x42, 0x18, + 0x83, 0x97, 0x67, 0xc6, 0x16, 0x9b, 0x8f, 0x6f, 0x56, 0x74, 0x5e, 0xa0, 0x14, 0xf6, 0x12, 0xb2, 0x2f, 0x35, 0x53, + 0x3d, 0x0a, 0xf3, 0x34, 0x27, 0x97, 0x6e, 0x5c, 0x25, 0x1f, 0x03, 0xcf, 0x81, 0x20, 0x5e, 0xdf, 0xa9, 0xbe, 0x1d, + 0x38, 0x61, 0x8a, 0x9f, 0x32, 0xce, 0x97, 0xbf, 0x52, 0x0b, 0xd9, 0xb3, 0x51, 0xe7, 0xad, 0x8c, 0xd8, 0x55, 0x45, + 0x55, 0x69, 0x4c, 0x41, 0x05, 0x97, 0x1d, 0x22, 0x68, 0xdb, 0x4f, 0x12, 0x8f, 0x48, 0x54, 0x05, 0xea, 0xb3, 0x99, + 0x48, 0x82, 0xb9, 0x00, 0x4b, 0x23, 0x6f, 0x39, 0xe7, 0xd5, 0xdf, 0xb2, 0x26, 0x99, 0x27, 0xe0, 0x22, 0xf9, 0xb4, + 0x93, 0xd3, 0x75, 0xe4, 0xa5, 0x87, 0xa9, 0x3a, 0xc4, 0x82, 0x59, 0xc2, 0x69, 0x56, 0x6e, 0x62, 0x5f, 0x5e, 0x03, + 0x35, 0x93, 0x8f, 0x48, 0xe5, 0x8f, 0x4d, 0x4e, 0xa9, 0x8d, 0x54, 0x80, 0xed, 0x21, 0x73, 0xbd, 0x43, 0x27, 0xdc, + 0x86, 0xf4, 0xdb, 0xcd, 0x6d, 0x1b, 0x28, 0xfe, 0x91, 0x16, 0x86, 0xf4, 0x7f, 0xbd, 0x82, 0x22, 0x12, 0x46, 0x08, + 0x67, 0x93, 0x00, 0xe1, 0x5e, 0x74, 0xf2, 0x22, 0x5c, 0x4c, 0xe7, 0x51, 0xd8, 0xef, 0x9d, 0x75, 0x5e, 0xc3, 0x82, + 0xf3, 0x83, 0xea, 0xe7, 0x28, 0xec, 0x1c, 0xeb, 0x35, 0x59, 0x16, 0xfc, 0xf8, 0x23, 0x33, 0x0a, 0x1d, 0xcc, 0x77, + 0x13, 0x41, 0xab, 0x4e, 0x82, 0xb2, 0x60, 0xad, 0x04, 0x81, 0xa4, 0x60, 0xd1, 0x35, 0x52, 0xd5, 0xdb, 0x94, 0x76, + 0xfd, 0x28, 0x2f, 0x3a, 0x4e, 0xff, 0xaa, 0xa6, 0xfb, 0x84, 0x30, 0x07, 0x5d, 0xa6, 0xc4, 0x07, 0x28, 0xd1, 0x6a, + 0x9f, 0xff, 0x1b, 0xe3, 0xd6, 0xd7, 0xae, 0xca, 0x6c, 0x26, 0x47, 0x8a, 0x29, 0x0a, 0xaf, 0xbd, 0x92, 0x66, 0xc6, + 0xa7, 0x04, 0xf3, 0xaa, 0x29, 0xeb, 0xa9, 0xa0, 0x4c, 0x53, 0xd7, 0x02, 0x2f, 0xd6, 0x25, 0xce, 0xf3, 0x62, 0x65, + 0x6f, 0xcc, 0x2f, 0x69, 0x39, 0x36, 0x9b, 0x4e, 0x5b, 0x31, 0x10, 0xea, 0x78, 0xd8, 0xe8, 0x39, 0x51, 0x10, 0xd3, + 0xf5, 0xc2, 0x71, 0x53, 0x29, 0xf5, 0x97, 0xc0, 0xa1, 0x53, 0x9b, 0xc9, 0x93, 0xb5, 0x1c, 0x31, 0xe4, 0x19, 0x96, + 0xb5, 0x32, 0xe8, 0xdc, 0x07, 0xd2, 0x6e, 0xff, 0x55, 0xc1, 0xb3, 0xe0, 0x6f, 0x96, 0x78, 0x98, 0x2f, 0x35, 0x9b, + 0x5b, 0x24, 0xaf, 0x0a, 0x89, 0xf2, 0xb0, 0x3f, 0xd8, 0x9d, 0x5f, 0x4c, 0xb4, 0x95, 0xb1, 0x48, 0xf8, 0x4c, 0xf3, + 0x24, 0x84, 0x90, 0xed, 0x5a, 0x42, 0x5b, 0xf6, 0x82, 0x3d, 0x31, 0xae, 0x9e, 0x53, 0xc4, 0xde, 0x04, 0x07, 0x7f, + 0x7d, 0x94, 0x2c, 0xad, 0x51, 0xbe, 0xbd, 0xbf, 0x31, 0x13, 0xdb, 0x75, 0x35, 0x9a, 0x0a, 0xd9, 0xbb, 0xbf, 0x4d, + 0xc2, 0xbb, 0x54, 0xf8, 0xee, 0x18, 0xd4, 0x8f, 0xee, 0x3a, 0xc7, 0xff, 0x85, 0xe3, 0x46, 0x4b, 0xaf, 0x7e, 0x16, + 0xd7, 0x48, 0xc0, 0xe4, 0x54, 0x7a, 0x55, 0xdf, 0x8b, 0x82, 0xbd, 0xe1, 0x81, 0x40, 0x59, 0xcd, 0xff, 0x51, 0xfd, + 0xea, 0x02, 0x80, 0xc9, 0x9c, 0x8e, 0x61, 0xa3, 0xdb, 0xc5, 0xde, 0xdb, 0x80, 0xcb, 0xd5, 0x1e, 0xff, 0x3a, 0x4e, + 0xd7, 0x41, 0x22, 0x38, 0x6e, 0xd7, 0xf5, 0x10, 0xe4, 0xe0, 0x31, 0xa7, 0x18, 0xc4, 0xf5, 0xe4, 0x4b, 0xf6, 0xdb, + 0x54, 0x78, 0x97, 0xd4, 0xe6, 0x17, 0x5b, 0x2b, 0xfe, 0x2c, 0x93, 0x06, 0x55, 0x1a, 0xfc, 0x17, 0xa4, 0x75, 0xb0, + 0xa7, 0x17, 0x84, 0x4d, 0xfa, 0x83, 0xe2, 0x70, 0x86, 0x05, 0xb6, 0x61, 0xa5, 0xa1, 0xb5, 0x32, 0x7e, 0xcc, 0xe8, + 0xb9, 0x4d, 0x30, 0x0e, 0x45, 0xce, 0xf6, 0x1a, 0x4d, 0xa3, 0x54, 0xab, 0x4b, 0xf7, 0x9b, 0x3c, 0x0b, 0x93, 0x96, + 0x76, 0x97, 0x4e, 0xd0, 0x3e, 0xaa, 0x3f, 0xff, 0x27, 0x95, 0xb8, 0xa2, 0x1f, 0x7a, 0xef, 0x26, 0x7e, 0x7f, 0x05, + 0x77, 0x6a, 0x9e, 0xd4, 0xf8, 0xc3, 0xdb, 0x99, 0xef, 0xfa, 0x92, 0x1e, 0x3f, 0x0d, 0x12, 0x8d, 0x61, 0x2c, 0x40, + 0x14, 0xd3, 0x78, 0x69, 0x2c, 0xef, 0x60, 0xe6, 0x86, 0x6d, 0xf4, 0xcd, 0x80, 0x6f, 0xf9, 0x9c, 0x21, 0x68, 0x40, + 0x8c, 0x9a, 0x2e, 0x55, 0x54, 0xfa, 0x7d, 0xd2, 0x90, 0x26, 0x12, 0x68, 0x9e, 0xf9, 0x25, 0x14, 0x4e, 0x27, 0x91, + 0x4a, 0x72, 0x80, 0x75, 0x32, 0xfd, 0xac, 0xdd, 0x97, 0xfb, 0x0b, 0xb9, 0x4b, 0x78, 0x5d, 0xa7, 0x2f, 0x5f, 0x8b, + 0xf9, 0x66, 0x52, 0x3f, 0x36, 0x94, 0xbe, 0x2c, 0x3b, 0x34, 0x8e, 0x8e, 0x30, 0x57, 0x6b, 0x84, 0x22, 0x72, 0x86, + 0xf8, 0x22, 0xb1, 0x86, 0x43, 0x1f, 0x99, 0x60, 0x5d, 0xca, 0x0e, 0x68, 0xf2, 0xcd, 0xca, 0x9b, 0x9b, 0x8d, 0xcc, + 0x69, 0x6a, 0x88, 0x59, 0xc4, 0x70, 0xf0, 0x87, 0x08, 0x9d, 0xa6, 0x7d, 0xdc, 0xd4, 0x39, 0x71, 0x86, 0xa4, 0xe1, + 0x3a, 0x26, 0x55, 0x95, 0x30, 0xab, 0x6c, 0x63, 0xf1, 0x94, 0x76, 0xd5, 0xba, 0x07, 0x6e, 0xe7, 0x5c, 0x38, 0x6a, + 0xa5, 0x8e, 0xb6, 0x89, 0x44, 0x21, 0x3b, 0x6c, 0xa5, 0x4e, 0xdf, 0x9d, 0xa2, 0x58, 0xa1, 0xe2, 0xed, 0x92, 0x7a, + 0x0d, 0x33, 0x0e, 0x57, 0xc2, 0x32, 0x1c, 0x60, 0xe0, 0x97, 0xb5, 0xf2, 0xbe, 0xc6, 0xe4, 0x54, 0xba, 0xa6, 0xbc, + 0x4b, 0xa9, 0xd1, 0x66, 0xb1, 0x7f, 0xf1, 0x87, 0xd7, 0x96, 0x66, 0x8b, 0x3f, 0x6c, 0xc6, 0x5b, 0x90, 0x53, 0xa7, + 0x0d, 0x34, 0x36, 0x6d, 0x87, 0x84, 0x26, 0xf7, 0xa9, 0xcc, 0x65, 0xa8, 0xa9, 0xc5, 0x96, 0x74, 0x8e, 0xa9, 0xee, + 0x57, 0x88, 0xf2, 0x11, 0xa5, 0xb3, 0x74, 0x77, 0x2a, 0x36, 0x25, 0x4f, 0x35, 0x0a, 0xfb, 0x58, 0x7d, 0xc1, 0xc8, + 0xa0, 0xd5, 0x71, 0xa0, 0xda, 0x77, 0xd6, 0x61, 0x41, 0x4c, 0x79, 0xc1, 0x7e, 0x4b, 0x2c, 0x27, 0xde, 0x2e, 0x2f, + 0x14, 0x77, 0x2a, 0xce, 0xed, 0x7a, 0x35, 0xce, 0xe7, 0x68, 0x84, 0xfd, 0xb2, 0x96, 0x62, 0x12, 0x10, 0x89, 0xed, + 0x27, 0xa4, 0x4b, 0xf1, 0x77, 0xd9, 0xe1, 0xb2, 0xbc, 0x12, 0xc2, 0x7c, 0xf4, 0xce, 0x68, 0xa1, 0x2e, 0x61, 0xae, + 0xf1, 0x26, 0x4f, 0x18, 0x44, 0x49, 0x34, 0xbd, 0x3e, 0xe3, 0x54, 0x97, 0xaa, 0xb7, 0x2d, 0x28, 0x4e, 0xd3, 0x2f, + 0x5a, 0x92, 0x5b, 0xab, 0x67, 0xc6, 0x8c, 0x41, 0xa0, 0x0e, 0x15, 0xbd, 0x85, 0x82, 0x62, 0xcc, 0x88, 0xb0, 0xd3, + 0xf9, 0x97, 0x4c, 0xaa, 0x33, 0x1d, 0xaa, 0x76, 0xed, 0xff, 0xa3, 0x83, 0x1d, 0x1e, 0xad, 0xcf, 0x7f, 0xdf, 0xcc, + 0xf4, 0xa0, 0x0a, 0x3a, 0x85, 0xcf, 0x3b, 0xab, 0x18, 0x0a, 0x05, 0xc8, 0xca, 0xce, 0x9d, 0x33, 0x80, 0x3a, 0x52, + 0x2b, 0xa4, 0xbb, 0x3e, 0xef, 0x2d, 0x66, 0xb8, 0xad, 0x4b, 0xe0, 0xc7, 0xd0, 0x0a, 0x3f, 0x9a, 0x16, 0x2f, 0x77, + 0xf8, 0xfa, 0x35, 0x8c, 0x78, 0x0a, 0xc2, 0x58, 0x18, 0xbc, 0x87, 0x14, 0x37, 0x61, 0x90, 0xa1, 0x09, 0x92, 0x7c, + 0xa2, 0x2d, 0x6b, 0xe6, 0x5a, 0x4a, 0x0e, 0xc8, 0xf8, 0x3d, 0x29, 0x0a, 0x80, 0xdf, 0x25, 0xb3, 0x8d, 0x1b, 0x0c, + 0x70, 0x82, 0xd2, 0xa6, 0x76, 0xac, 0xe2, 0x66, 0xfe, 0xb3, 0xc3, 0x8b, 0x08, 0xf4, 0x4c, 0xe1, 0x18, 0xcf, 0xbf, + 0xdb, 0x8f, 0x23, 0x04, 0xa9, 0xe0, 0xc7, 0x28, 0xd5, 0xec, 0xe8, 0xa5, 0xff, 0xda, 0xd5, 0xf4, 0xf0, 0x48, 0x77, + 0x4d, 0xa2, 0xb6, 0x8c, 0x50, 0x01, 0x36, 0x20, 0x9a, 0x01, 0x2e, 0x4c, 0xc7, 0x98, 0xe6, 0xcb, 0x1f, 0xfd, 0xc4, + 0xda, 0x55, 0xf3, 0x7a, 0x46, 0x2b, 0x8f, 0xae, 0x93, 0x05, 0x6a, 0xce, 0x75, 0x5f, 0x5e, 0x83, 0xef, 0x96, 0x7d, + 0x3c, 0x65, 0x0b, 0x32, 0x83, 0x00, 0x83, 0xd5, 0x73, 0xce, 0x44, 0x2f, 0x09, 0x77, 0x52, 0x5b, 0x3d, 0xaa, 0xc1, + 0xbc, 0xe3, 0xd7, 0xd7, 0x5e, 0x92, 0x21, 0xf9, 0xd0, 0x59, 0xbb, 0x99, 0x44, 0x8f, 0xad, 0xd9, 0x78, 0xbf, 0x66, + 0x92, 0xa5, 0xfe, 0xd7, 0xf0, 0xd9, 0xf8, 0xea, 0xf5, 0x2a, 0x35, 0xdf, 0x66, 0xf0, 0xcb, 0xcb, 0x4b, 0x8e, 0x8b, + 0x39, 0x2f, 0x7e, 0x63, 0x8d, 0x2d, 0x1c, 0x27, 0x9b, 0x0f, 0x95, 0x28, 0x53, 0x83, 0xb9, 0xf3, 0x1a, 0xd4, 0xce, + 0x93, 0x2c, 0x37, 0x7b, 0xa5, 0x07, 0xd8, 0xf9, 0xfb, 0x0b, 0xe3, 0x9c, 0x78, 0x7f, 0x24, 0xcc, 0xb9, 0xf7, 0x67, + 0xb2, 0x39, 0xfb, 0x24, 0xa3, 0x60, 0xcf, 0xac, 0x83, 0x87, 0x1d, 0x6d, 0xc5, 0x64, 0x9a, 0x2d, 0x13, 0x72, 0x85, + 0x77, 0xc3, 0xaa, 0xda, 0x79, 0x78, 0x42, 0xc5, 0x0d, 0xfd, 0x46, 0xcf, 0x07, 0xb9, 0xc1, 0xbb, 0x85, 0x3a, 0xcb, + 0x41, 0x67, 0xa1, 0x8d, 0x7f, 0x5b, 0xa8, 0xfb, 0x49, 0xf8, 0xb4, 0x04, 0x57, 0x66, 0x2f, 0x91, 0xd4, 0xaf, 0x02, + 0xa8, 0x3a, 0xe7, 0xb6, 0x0d, 0xe4, 0x62, 0xaf, 0x5f, 0xa9, 0xf6, 0x59, 0x81, 0x68, 0x6c, 0xa8, 0x48, 0x9d, 0x45, + 0xa1, 0x1b, 0x52, 0xa7, 0x34, 0xdb, 0x59, 0x1d, 0xad, 0x0f, 0x7c, 0x8f, 0x3f, 0x54, 0x43, 0x15, 0xe6, 0x9b, 0x45, + 0x67, 0xc8, 0x0d, 0x34, 0xcf, 0x48, 0xb3, 0xb8, 0xde, 0x94, 0x9c, 0x5a, 0x11, 0x0d, 0xa1, 0x31, 0xf8, 0x26, 0x83, + 0x83, 0x72, 0x5c, 0x89, 0xad, 0x28, 0xba, 0x29, 0x9f, 0xed, 0x7f, 0x15, 0x6b, 0xc5, 0xbe, 0x80, 0xd8, 0x57, 0x74, + 0x81, 0x8d, 0xb5, 0x02, 0x77, 0x84, 0xf6, 0x78, 0x89, 0x6c, 0x65, 0x65, 0xa5, 0xe6, 0xc2, 0x9a, 0xb0, 0xd6, 0x7b, + 0x9c, 0x75, 0x82, 0xf6, 0xce, 0xbf, 0xd0, 0x55, 0x48, 0x2d, 0x31, 0xd6, 0x49, 0x77, 0x2a, 0x06, 0x16, 0xa0, 0xb8, + 0x8f, 0x5a, 0x1f, 0x43, 0x15, 0x85, 0x11, 0xf9, 0xb4, 0x9e, 0xf0, 0x3e, 0x36, 0x33, 0x1b, 0x9b, 0x1b, 0xee, 0x43, + 0x6b, 0xbd, 0xd9, 0x64, 0xd5, 0xb4, 0x9f, 0x38, 0xef, 0x02, 0xc3, 0x0e, 0x06, 0x97, 0x37, 0xce, 0x37, 0x5d, 0xd0, + 0xa4, 0x27, 0x9c, 0x60, 0x8a, 0xe6, 0x36, 0xe0, 0xc9, 0x47, 0xf4, 0x94, 0x46, 0x76, 0x76, 0x87, 0xb7, 0x40, 0xee, + 0x30, 0xfa, 0xd4, 0x72, 0xb1, 0xe0, 0xd8, 0x82, 0xf3, 0x76, 0x41, 0x2e, 0xd6, 0x0d, 0xdd, 0x4a, 0x90, 0x74, 0x48, + 0xf3, 0xd9, 0xe0, 0x3a, 0x95, 0x42, 0x3f, 0xf8, 0xff, 0x12, 0x27, 0x9d, 0x6d, 0x45, 0x41, 0x80, 0x3b, 0x27, 0x41, + 0x25, 0x62, 0xef, 0x46, 0x8e, 0x85, 0xb2, 0x67, 0x50, 0xa5, 0xec, 0x23, 0x87, 0xf4, 0xd9, 0x6d, 0x88, 0x4a, 0xc4, + 0x76, 0xcf, 0x50, 0xe7, 0x63, 0x42, 0x7f, 0xf9, 0x85, 0xd7, 0x77, 0x7e, 0x11, 0x80, 0xf9, 0xb6, 0x3a, 0x13, 0x6f, + 0xcf, 0xd5, 0xb3, 0xbf, 0x44, 0x02, 0x96, 0x8b, 0xec, 0x48, 0xa6, 0x59, 0x8e, 0x4f, 0xeb, 0xb7, 0xd8, 0x74, 0x50, + 0x08, 0x86, 0xa0, 0x7d, 0xb8, 0xc0, 0x37, 0x5f, 0x05, 0x5c, 0x2e, 0xd0, 0xf0, 0xf1, 0x79, 0xf1, 0x5b, 0x47, 0x2f, + 0x1a, 0x89, 0xc0, 0xd1, 0x4c, 0x1d, 0xaf, 0x79, 0xde, 0x82, 0xab, 0x3c, 0xb5, 0xe7, 0xc5, 0xa4, 0xd1, 0x3a, 0xcf, + 0xe7, 0xa7, 0xb4, 0xb0, 0x96, 0xde, 0xe6, 0xff, 0x84, 0xa9, 0xad, 0x50, 0x44, 0xfe, 0x79, 0x1f, 0x11, 0x3f, 0x32, + 0x69, 0xa0, 0x91, 0xfd, 0xaa, 0x2d, 0xf9, 0xca, 0x3b, 0x09, 0x8b, 0x08, 0x37, 0xf1, 0x2e, 0x36, 0xc2, 0xfc, 0x2c, + 0x26, 0x3f, 0x5d, 0x99, 0x4f, 0x8d, 0x2b, 0xb2, 0xda, 0xc7, 0xc4, 0xc3, 0xa3, 0xf5, 0x61, 0xdc, 0x2d, 0xcb, 0xb5, + 0x59, 0x9e, 0x2f, 0x4a, 0x57, 0x6a, 0x9f, 0x67, 0x4f, 0x04, 0x6b, 0xb1, 0x3e, 0xd8, 0xb9, 0x97, 0x08, 0xc8, 0xa8, + 0x9a, 0x97, 0xb6, 0x43, 0xe4, 0xe1, 0xf9, 0x93, 0x6f, 0x79, 0x97, 0x27, 0x0a, 0x4a, 0xdb, 0x21, 0xf0, 0xdd, 0x7d, + 0x9d, 0xea, 0x54, 0xc1, 0x98, 0xa7, 0x2b, 0x80, 0xd6, 0x80, 0x85, 0xd2, 0x0c, 0x27, 0xf6, 0x5c, 0x85, 0x6c, 0xda, + 0xeb, 0x35, 0xe5, 0x03, 0x80, 0x6a, 0xc2, 0x8d, 0x3d, 0x05, 0x19, 0xe7, 0xb3, 0xd2, 0xf5, 0xf8, 0xe1, 0xef, 0xb9, + 0xe3, 0xec, 0x03, 0x7e, 0xfc, 0x2d, 0xb9, 0x45, 0x7f, 0x99, 0x47, 0xa6, 0xe5, 0xdb, 0x8c, 0x46, 0x8d, 0x63, 0x34, + 0xde, 0xcc, 0x00, 0xa2, 0xa2, 0x6a, 0x60, 0xc6, 0x94, 0x9f, 0x05, 0x43, 0x4b, 0x66, 0xd5, 0x21, 0x9f, 0x6b, 0xbb, + 0x9f, 0x58, 0x8b, 0xf8, 0xb0, 0x3a, 0xb4, 0xca, 0x5a, 0xc9, 0xfe, 0xa5, 0xeb, 0xd0, 0x47, 0x2b, 0x14, 0x00, 0x01, + 0xf6, 0x30, 0xfa, 0x9c, 0xb1, 0x56, 0x0b, 0x3e, 0xde, 0x7f, 0x61, 0x12, 0x11, 0x89, 0x4d, 0xde, 0x77, 0x7d, 0x13, + 0xa0, 0x2c, 0x33, 0xc2, 0x9e, 0x1d, 0xb3, 0x1b, 0x63, 0xca, 0x12, 0x64, 0xd1, 0xb8, 0x0f, 0x12, 0x84, 0xde, 0x1a, + 0x6e, 0x00, 0x38, 0x47, 0x9e, 0x0c, 0x97, 0x29, 0xe4, 0x4b, 0xe4, 0xe3, 0xf4, 0x3d, 0xae, 0xc8, 0xc2, 0x01, 0xbe, + 0x1e, 0xb4, 0x92, 0x6d, 0x63, 0xac, 0xa0, 0xa2, 0x98, 0x03, 0x99, 0xce, 0x52, 0xc2, 0x57, 0x8c, 0x38, 0xe7, 0x0f, + 0x85, 0x73, 0xb8, 0x98, 0xf5, 0xfa, 0xf9, 0xdc, 0x67, 0xad, 0x4c, 0x68, 0x47, 0x73, 0x9a, 0x81, 0x01, 0xc5, 0xb0, + 0x2a, 0x36, 0xff, 0x57, 0x58, 0xa2, 0xe4, 0xbd, 0x36, 0xb2, 0xf3, 0xa7, 0xc4, 0xb6, 0x46, 0x11, 0x30, 0xd1, 0xd8, + 0x5e, 0x36, 0xe5, 0x6c, 0x2d, 0xa9, 0xa1, 0x2b, 0xc2, 0xc2, 0xfb, 0x60, 0xc7, 0x16, 0xae, 0xf5, 0x2a, 0x6a, 0x9e, + 0x51, 0x0f, 0xcc, 0xfb, 0x4a, 0xc4, 0x65, 0xbe, 0x6f, 0x6d, 0x10, 0xe4, 0x3e, 0x6f, 0x45, 0x26, 0x07, 0x24, 0x25, + 0x1a, 0x58, 0x78, 0x5c, 0xcb, 0x77, 0x2e, 0xd8, 0x7b, 0x97, 0xd5, 0xa1, 0x2b, 0x94, 0x25, 0xd5, 0x38, 0xa3, 0x68, + 0x30, 0xa6, 0x44, 0x24, 0x9e, 0x0b, 0x41, 0x8d, 0x86, 0xbf, 0xf9, 0x44, 0xd4, 0x72, 0xc2, 0x63, 0xef, 0x13, 0x6e, + 0x74, 0x32, 0xbd, 0xa1, 0x66, 0xca, 0x76, 0xf4, 0xe6, 0xe7, 0x03, 0x65, 0x8d, 0xe6, 0x7c, 0xac, 0x62, 0xe6, 0x92, + 0x03, 0x28, 0x33, 0x91, 0x22, 0x20, 0x87, 0x1e, 0x77, 0x9c, 0x55, 0x92, 0x5e, 0xe3, 0x2b, 0x4c, 0xa2, 0xa1, 0x27, + 0xb7, 0xd7, 0xd5, 0x54, 0x2c, 0x37, 0x18, 0x29, 0xf0, 0x62, 0x7a, 0xeb, 0x66, 0x53, 0x8a, 0xd5, 0xd2, 0x0d, 0xa3, + 0x2a, 0xe9, 0xa3, 0x7e, 0x6d, 0x3d, 0xc7, 0x8e, 0xbe, 0xd5, 0x53, 0xcb, 0xcc, 0xfb, 0x5e, 0x5b, 0x9d, 0x0a, 0xda, + 0xfd, 0x8f, 0x96, 0x8c, 0xa2, 0x8b, 0x69, 0xf5, 0x2c, 0xde, 0xea, 0x0f, 0xdd, 0x22, 0x9f, 0x37, 0x79, 0x7c, 0x0f, + 0x56, 0x9c, 0x52, 0x6b, 0x15, 0x66, 0xef, 0x3d, 0x4a, 0x8d, 0xbd, 0xd7, 0x4a, 0xb1, 0xbc, 0x4e, 0xc9, 0x25, 0xa6, + 0xdc, 0x78, 0x89, 0x2a, 0xf2, 0xa3, 0x56, 0xbc, 0x65, 0xfe, 0x03, 0x55, 0x49, 0xc3, 0x55, 0xa7, 0x39, 0xf9, 0xb0, + 0xcf, 0xa1, 0x66, 0x53, 0xc0, 0x29, 0xca, 0x6a, 0x25, 0x37, 0xd3, 0xfb, 0xae, 0x0a, 0xb4, 0x26, 0x7e, 0x03, 0xa3, + 0x0c, 0x59, 0x4d, 0xb3, 0x79, 0xf5, 0xdf, 0xde, 0xf4, 0xb3, 0x1a, 0xa6, 0x6f, 0xca, 0x99, 0x7e, 0x7e, 0xbe, 0x84, + 0x5b, 0xbf, 0x6f, 0x4e, 0x35, 0x01, 0xae, 0x96, 0xa4, 0x10, 0x5f, 0xad, 0x98, 0x23, 0x80, 0xbe, 0xef, 0x26, 0x09, + 0xfd, 0x84, 0xa7, 0xc5, 0x42, 0x0d, 0x41, 0xb2, 0x46, 0x34, 0x79, 0x74, 0xff, 0x8f, 0xc5, 0x02, 0x23, 0x4e, 0x2b, + 0xdf, 0x5e, 0x4e, 0x35, 0xa2, 0x22, 0xce, 0x26, 0x97, 0x39, 0x88, 0x11, 0x0c, 0x08, 0xb9, 0x48, 0x02, 0x1d, 0xa5, + 0xab, 0x8b, 0x46, 0xa4, 0x00, 0x34, 0x2c, 0xfa, 0x0d, 0x40, 0xe0, 0x10, 0xcc, 0x11, 0x41, 0x30, 0x92, 0x37, 0x02, + 0x2c, 0xc7, 0x64, 0xef, 0x58, 0x05, 0x0b, 0x1b, 0x75, 0xb0, 0xeb, 0x0d, 0xe2, 0x02, 0x5a, 0x34, 0x4f, 0x23, 0x41, + 0x51, 0x05, 0x8b, 0x18, 0x59, 0xb6, 0xb9, 0xf8, 0xa3, 0xe6, 0x3d, 0x2a, 0x24, 0x0a, 0x5d, 0x3c, 0x7d, 0x9a, 0x2e, + 0xa1, 0xec, 0x2f, 0xc0, 0xbf, 0x8a, 0x3a, 0xb0, 0xa7, 0x0e, 0x5a, 0x17, 0xd0, 0xb1, 0x15, 0x27, 0xa7, 0x32, 0xe5, + 0xcf, 0x39, 0x03, 0x40, 0x49, 0xcf, 0x1a, 0xc4, 0xd0, 0xa0, 0x73, 0xdd, 0x72, 0x4d, 0x12, 0xc0, 0x70, 0xc9, 0x78, + 0x69, 0xa9, 0x6d, 0x3d, 0x7b, 0x48, 0xe6, 0x23, 0x82, 0x39, 0x3a, 0x24, 0xf1, 0x22, 0x4a, 0xdc, 0x65, 0x79, 0x09, + 0x54, 0x66, 0x9d, 0x8f, 0x62, 0x5d, 0x2b, 0xaf, 0xf4, 0x8b, 0x3f, 0x3e, 0x3f, 0x21, 0xc9, 0xc2, 0xab, 0x06, 0x7c, + 0x84, 0xcb, 0xba, 0x08, 0xfc, 0xc8, 0x69, 0x51, 0xa0, 0xbc, 0x5d, 0x75, 0xd0, 0xd2, 0x93, 0x9d, 0x08, 0x36, 0x4d, + 0x4b, 0x7f, 0x1b, 0xf6, 0xcb, 0x77, 0x34, 0x99, 0x72, 0x1c, 0x2a, 0xfc, 0x0c, 0x08, 0x9b, 0xe2, 0x6e, 0x50, 0x34, + 0x94, 0x17, 0x37, 0x10, 0xca, 0xe9, 0xec, 0xf0, 0xed, 0xe8, 0x59, 0x45, 0xe0, 0xf3, 0x7e, 0xf4, 0x37, 0x71, 0x5d, + 0x81, 0xa5, 0xd3, 0xb1, 0x47, 0x15, 0x82, 0xa3, 0x3c, 0xad, 0x7d, 0x23, 0x75, 0x6a, 0x11, 0x52, 0x15, 0x4f, 0x8b, + 0xbe, 0x4a, 0xf7, 0x3e, 0x69, 0xb0, 0xe1, 0x6d, 0x96, 0xed, 0x41, 0xb6, 0x32, 0x46, 0x34, 0x53, 0x5e, 0xf7, 0x98, + 0xe5, 0x7a, 0x1c, 0x30, 0x73, 0x80, 0xae, 0xbc, 0x9f, 0x3d, 0xc6, 0x50, 0xbd, 0xc2, 0x88, 0xd5, 0x66, 0xbf, 0x00, + 0xe6, 0xde, 0xb8, 0x9f, 0x6b, 0x7a, 0xe6, 0x53, 0x2e, 0xa4, 0x8c, 0x0a, 0xe6, 0x75, 0x95, 0x67, 0x70, 0xf2, 0x25, + 0x04, 0xc3, 0xf2, 0xe5, 0xfb, 0xcc, 0xaf, 0x57, 0x3d, 0x6c, 0x22, 0x9e, 0xd4, 0xf7, 0x74, 0x50, 0x1c, 0xa8, 0x0d, + 0xaf, 0xe5, 0x12, 0xe2, 0x8a, 0x30, 0xbb, 0x17, 0x87, 0xc0, 0x9a, 0xf1, 0x80, 0xef, 0xd8, 0x14, 0xf5, 0x84, 0xf2, + 0x28, 0x9c, 0x37, 0x13, 0xe6, 0x6f, 0x6a, 0x62, 0x32, 0x6f, 0xa6, 0x48, 0xb9, 0xf0, 0xa0, 0xb0, 0x29, 0xe3, 0x12, + 0x65, 0x4b, 0x1f, 0xd2, 0xef, 0x60, 0x6f, 0x34, 0xf1, 0x66, 0x35, 0x74, 0x3d, 0x39, 0x6e, 0x35, 0xd9, 0x3a, 0xc2, + 0x14, 0x00, 0x2d, 0x72, 0x1e, 0x01, 0xd3, 0xf5, 0xda, 0xad, 0x28, 0x5b, 0x07, 0x46, 0x1a, 0x1a, 0x42, 0xd1, 0x30, + 0x2f, 0x98, 0x5a, 0x15, 0x77, 0x87, 0x4e, 0x4c, 0x8c, 0xe7, 0x8c, 0xe5, 0x17, 0xf0, 0x66, 0xb3, 0x4e, 0x5b, 0xa3, + 0x17, 0x57, 0xa6, 0x82, 0xc9, 0x0c, 0x9a, 0x09, 0x90, 0x04, 0xf0, 0x4a, 0x19, 0x4d, 0xc6, 0x79, 0xca, 0xb1, 0xb9, + 0x3f, 0x68, 0x43, 0x06, 0x08, 0x74, 0x8a, 0x29, 0x95, 0xe2, 0xdd, 0xfa, 0x20, 0x65, 0xbc, 0x00, 0x94, 0x1d, 0xb3, + 0xc1, 0x32, 0x86, 0x06, 0xb0, 0x69, 0xd3, 0x9c, 0xe2, 0x2a, 0x7b, 0xca, 0x64, 0xd6, 0x46, 0x9d, 0xe6, 0x0f, 0x97, + 0x16, 0x46, 0xc4, 0xb8, 0xa8, 0xf9, 0x84, 0x7d, 0x35, 0xc5, 0x08, 0xb4, 0x1e, 0x83, 0xbc, 0x1e, 0x4d, 0x79, 0xb3, + 0xac, 0x31, 0x2e, 0x5d, 0x13, 0x4f, 0x5e, 0x14, 0x74, 0xea, 0xcb, 0xe4, 0x5f, 0xd6, 0x9f, 0xc0, 0x26, 0x1e, 0xc8, + 0xc4, 0x67, 0x92, 0xad, 0x4c, 0x14, 0x15, 0x10, 0xd5, 0x22, 0xbc, 0x92, 0x5c, 0x84, 0xa4, 0x64, 0xbc, 0x0c, 0x84, + 0x3a, 0x0e, 0x1a, 0x90, 0xbc, 0xaf, 0x2b, 0xe1, 0xb5, 0xe5, 0xcb, 0x45, 0xc8, 0x9b, 0x75, 0x58, 0xbb, 0xf3, 0xe9, + 0x74, 0x7b, 0x1b, 0x85, 0xa6, 0x01, 0x4a, 0x26, 0xc3, 0x15, 0xc8, 0x37, 0x34, 0x3b, 0x92, 0x27, 0x74, 0xfa, 0x77, + 0xb3, 0x32, 0x26, 0x61, 0x76, 0xba, 0x25, 0x47, 0xc5, 0x7f, 0x28, 0xed, 0x2e, 0x44, 0x2f, 0xe1, 0x50, 0x77, 0x50, + 0x0f, 0x44, 0xe5, 0x10, 0x73, 0x6a, 0x64, 0x9e, 0xc4, 0x55, 0x8e, 0x2c, 0x7d, 0x58, 0x26, 0x17, 0xb3, 0x8a, 0x33, + 0x19, 0xd2, 0xda, 0xda, 0xa6, 0xdb, 0x6c, 0x94, 0xa4, 0xcb, 0x12, 0xd3, 0xde, 0xda, 0x43, 0xb2, 0xea, 0xe1, 0x22, + 0x8c, 0xd2, 0xf7, 0x25, 0x4f, 0x4f, 0x9b, 0xa8, 0x7b, 0x3b, 0x89, 0x25, 0x7c, 0xec, 0x32, 0x70, 0x0a, 0xee, 0x80, + 0xb1, 0xca, 0x4e, 0x44, 0x2d, 0x90, 0xd4, 0x7f, 0xe9, 0xcd, 0x57, 0x21, 0x84, 0x78, 0xb7, 0xac, 0xd7, 0x3c, 0x12, + 0xf3, 0xc9, 0x63, 0xb4, 0x06, 0x1f, 0x90, 0x14, 0xf4, 0xe2, 0x70, 0x0e, 0x08, 0x3c, 0x6a, 0x7a, 0xf9, 0x9d, 0x48, + 0xe2, 0xec, 0x2e, 0x2b, 0x34, 0x85, 0xc3, 0xb3, 0xec, 0x62, 0x49, 0x6d, 0xa5, 0x20, 0xcf, 0xbe, 0x26, 0x2e, 0xb4, + 0x34, 0x61, 0xcc, 0x6f, 0x19, 0x3a, 0x07, 0x1e, 0xd3, 0xe3, 0xa0, 0xa0, 0x31, 0xbb, 0xab, 0x35, 0x26, 0x3c, 0x2f, + 0xaa, 0x20, 0x89, 0x7a, 0x1b, 0x46, 0x55, 0x26, 0x4e, 0xf9, 0xa8, 0x28, 0x58, 0xce, 0x0e, 0xbb, 0x31, 0xa1, 0xd1, + 0xa3, 0xff, 0x1c, 0x73, 0x12, 0x54, 0xee, 0x91, 0x19, 0xeb, 0xe0, 0x22, 0x5a, 0xe8, 0x67, 0xed, 0xfb, 0xca, 0xa2, + 0xf3, 0x6b, 0xa6, 0x29, 0x6f, 0xd7, 0x25, 0x6d, 0x74, 0xa3, 0x90, 0x12, 0x8b, 0xc4, 0x3c, 0x8b, 0x90, 0xed, 0xb7, + 0x90, 0x5b, 0xab, 0x0d, 0x84, 0x9b, 0x6c, 0x4a, 0xe0, 0x94, 0x84, 0x2f, 0x94, 0x67, 0x2b, 0x23, 0x1a, 0x99, 0xd6, + 0x48, 0x17, 0x55, 0x6b, 0xce, 0x5b, 0x53, 0x88, 0x9f, 0x18, 0x71, 0xb3, 0x46, 0xde, 0x08, 0x85, 0x00, 0xe1, 0xc2, + 0xfc, 0x39, 0x80, 0xfb, 0x3b, 0xa6, 0x8a, 0x87, 0x87, 0xd8, 0xa9, 0x59, 0xa9, 0x6d, 0xec, 0x81, 0x03, 0xf2, 0x16, + 0x27, 0x83, 0x0b, 0x24, 0xc3, 0x4c, 0xfc, 0x32, 0xd1, 0x06, 0xa5, 0x62, 0x52, 0xd5, 0xf8, 0x5c, 0x19, 0xb0, 0xd3, + 0xa7, 0xb2, 0x62, 0x6e, 0x3c, 0xa0, 0xcf, 0x56, 0x95, 0xa3, 0xaf, 0x1d, 0x41, 0x97, 0x9f, 0xcc, 0xf6, 0x6d, 0x54, + 0xf2, 0x7b, 0x30, 0xa6, 0xf2, 0x26, 0xce, 0x6d, 0xaa, 0x4e, 0x1f, 0xab, 0x0a, 0x5f, 0xbd, 0xe7, 0xce, 0xc4, 0x07, + 0x73, 0x47, 0x44, 0xfa, 0xf5, 0x60, 0xc8, 0x5f, 0xf5, 0xfb, 0x64, 0x10, 0x1d, 0x3b, 0x5d, 0x17, 0xaa, 0x77, 0x98, + 0x92, 0x8a, 0x8a, 0x5c, 0x09, 0x43, 0x14, 0x50, 0xc8, 0x65, 0xa4, 0xf4, 0xb5, 0x44, 0xd6, 0xa6, 0x72, 0x27, 0xed, + 0x47, 0x2b, 0x71, 0x86, 0x21, 0x2f, 0xad, 0x77, 0x61, 0x5d, 0xfe, 0x41, 0x57, 0x76, 0x44, 0xef, 0xd4, 0x13, 0xb9, + 0xec, 0x1c, 0x7e, 0xbe, 0xb4, 0x39, 0x40, 0xa9, 0xff, 0xa5, 0xfa, 0x4d, 0x9a, 0x08, 0x86, 0xd0, 0x95, 0x81, 0xf8, + 0xa0, 0xa8, 0xa5, 0xd8, 0xbe, 0xa0, 0x09, 0x75, 0xdd, 0x05, 0xd3, 0xa2, 0x2b, 0x16, 0x45, 0xff, 0xe5, 0x3d, 0x09, + 0x87, 0xfa, 0xc1, 0x2d, 0xd2, 0x0e, 0xfd, 0x7c, 0x97, 0xcb, 0x91, 0x53, 0x61, 0x51, 0x12, 0x13, 0x4d, 0x58, 0xd5, + 0x83, 0xcd, 0x61, 0x6b, 0x05, 0xcd, 0x69, 0xba, 0x4d, 0xbe, 0x3f, 0x54, 0x18, 0x85, 0x05, 0xfe, 0xbc, 0x34, 0x81, + 0x81, 0x06, 0x8e, 0xea, 0x0c, 0x54, 0x72, 0xdc, 0x2f, 0x74, 0x6b, 0x52, 0xe5, 0x65, 0x07, 0x32, 0x4b, 0xbe, 0x51, + 0xd6, 0x2d, 0xbf, 0x9b, 0xaf, 0x5e, 0x41, 0x5f, 0xff, 0x51, 0x36, 0x59, 0x40, 0xb4, 0x0e, 0xae, 0xb5, 0x1c, 0x98, + 0x4f, 0xb9, 0x18, 0x8f, 0x76, 0xf2, 0xea, 0x77, 0xfa, 0xf5, 0x12, 0xd5, 0x85, 0xa5, 0xc4, 0xb9, 0x47, 0xd4, 0x86, + 0xed, 0xec, 0xe7, 0x3c, 0x25, 0x3a, 0x28, 0xe3, 0xe7, 0xcf, 0x06, 0xcc, 0x66, 0xe1, 0xaa, 0x08, 0xd8, 0xd1, 0x57, + 0x57, 0x12, 0x02, 0x16, 0xb0, 0x25, 0x2c, 0x8c, 0xd8, 0x71, 0x94, 0x67, 0x8e, 0xa9, 0xec, 0x73, 0xcf, 0xe8, 0xfa, + 0xe6, 0xc4, 0x3d, 0x7c, 0xb9, 0xfd, 0x86, 0x95, 0x38, 0x4e, 0x26, 0xd6, 0xf2, 0x45, 0x57, 0x30, 0x34, 0x21, 0xe9, + 0xf2, 0x2b, 0x19, 0x90, 0xaa, 0x95, 0x9d, 0x98, 0xe7, 0x58, 0x02, 0xb6, 0xb7, 0xef, 0xaa, 0x38, 0x8f, 0xc9, 0xe9, + 0xc7, 0xc9, 0xdc, 0x02, 0x33, 0xb2, 0x50, 0x42, 0xe8, 0xbb, 0x95, 0xa8, 0xd7, 0xad, 0x49, 0x30, 0xa1, 0x5d, 0x10, + 0xfe, 0xc6, 0x71, 0x89, 0x6d, 0x68, 0xe9, 0xae, 0x17, 0x21, 0x74, 0x04, 0x22, 0xb9, 0x31, 0x4a, 0xbc, 0x3f, 0x3b, + 0xd7, 0xbd, 0x18, 0x6e, 0x52, 0xd0, 0x8c, 0x1e, 0x3c, 0x61, 0xbb, 0x4c, 0x48, 0x26, 0xf2, 0x1d, 0x1a, 0x02, 0xcb, + 0x73, 0x27, 0xfa, 0x19, 0xe0, 0x15, 0x71, 0x6b, 0xaf, 0xbf, 0x49, 0x5f, 0xb7, 0x2e, 0x15, 0x97, 0x9e, 0x67, 0x54, + 0x56, 0xe3, 0xc6, 0x9b, 0x41, 0x07, 0x3c, 0xba, 0xfc, 0x54, 0x89, 0x91, 0x0c, 0x82, 0x07, 0x88, 0x22, 0xa2, 0x4c, + 0x5f, 0xc9, 0x6d, 0x71, 0x77, 0x38, 0x05, 0x04, 0x32, 0x66, 0x4d, 0x7e, 0x31, 0x4c, 0x04, 0x4a, 0xcc, 0x37, 0xe3, + 0x8b, 0x1e, 0xfc, 0xd8, 0xee, 0x23, 0x72, 0x2e, 0xca, 0x35, 0x0c, 0xb6, 0x31, 0xaf, 0xac, 0xd8, 0x13, 0x7c, 0x23, + 0x91, 0x8e, 0x9e, 0x62, 0x28, 0x97, 0x28, 0x07, 0x2b, 0xdd, 0xe3, 0xb2, 0x07, 0x2b, 0xaa, 0x00, 0x71, 0xe3, 0xc6, + 0x19, 0x47, 0x05, 0x66, 0xc9, 0x0d, 0x69, 0x41, 0x93, 0x53, 0x87, 0x5f, 0xdb, 0xd1, 0x73, 0x80, 0x45, 0x71, 0x4f, + 0x5e, 0x03, 0xa7, 0xb6, 0xb5, 0x9e, 0xd6, 0xfa, 0x1b, 0x68, 0x88, 0x05, 0x75, 0x51, 0x3b, 0xbb, 0x1d, 0xd6, 0x5e, + 0xb0, 0xad, 0xab, 0x56, 0xfe, 0xb0, 0x1f, 0xda, 0xd8, 0x28, 0x86, 0xd3, 0x20, 0x92, 0xb8, 0x01, 0xd3, 0x28, 0xc4, + 0x1f, 0x9a, 0x6e, 0x16, 0x53, 0x79, 0xe2, 0xd7, 0xee, 0xaf, 0x95, 0x32, 0xe8, 0xf3, 0x8f, 0x62, 0x61, 0x4f, 0x26, + 0xf6, 0xeb, 0x25, 0x6c, 0x4c, 0x2a, 0x1b, 0x70, 0x55, 0xa3, 0xe2, 0x59, 0xf2, 0xa6, 0xf0, 0xe4, 0x43, 0x85, 0x96, + 0x9d, 0xf0, 0xf3, 0xa8, 0x48, 0x65, 0x6f, 0x46, 0x34, 0xaa, 0x15, 0x6b, 0x50, 0xa7, 0x47, 0x07, 0xc2, 0x65, 0x32, + 0xb0, 0x6a, 0x2c, 0x40, 0xfd, 0xf9, 0x65, 0xee, 0xd1, 0x27, 0x31, 0x78, 0x91, 0x8f, 0xb1, 0x35, 0x82, 0xde, 0x41, + 0x14, 0x62, 0x74, 0x24, 0x7d, 0x93, 0xca, 0xab, 0xbf, 0xe6, 0x31, 0xbe, 0x11, 0xfc, 0x9d, 0xb1, 0xf3, 0x2d, 0xf3, + 0xdc, 0x99, 0xbd, 0xc6, 0xae, 0xb9, 0x8e, 0xd6, 0x61, 0x28, 0xbb, 0x04, 0xa6, 0x21, 0x68, 0x0c, 0xd1, 0x04, 0xc6, + 0x58, 0x9a, 0x39, 0x5d, 0x1b, 0x55, 0x90, 0x7b, 0x21, 0x31, 0xfe, 0x5f, 0x24, 0xbc, 0x1c, 0xa8, 0x9c, 0x8e, 0xa2, + 0x16, 0x3c, 0x04, 0x77, 0xd5, 0x50, 0x0b, 0x94, 0xc9, 0xc3, 0x53, 0x68, 0xc9, 0x58, 0x86, 0xe7, 0x98, 0xd9, 0x06, + 0x75, 0x30, 0x1e, 0xc9, 0x3c, 0xac, 0x53, 0xb8, 0x42, 0xcf, 0x16, 0xc5, 0xcc, 0x8e, 0x79, 0x5b, 0x61, 0x64, 0x6f, + 0xd1, 0x24, 0x9e, 0xbd, 0x0c, 0xc5, 0xd5, 0xf6, 0x38, 0x64, 0x6e, 0xcd, 0x03, 0x87, 0x99, 0x47, 0x13, 0x41, 0xe1, + 0xde, 0xc0, 0x16, 0x60, 0xb0, 0x93, 0xb3, 0x6a, 0x94, 0x20, 0xac, 0xb9, 0x09, 0x10, 0x67, 0x32, 0x0a, 0x21, 0x95, + 0x0d, 0x3f, 0xb0, 0x96, 0xe6, 0x3b, 0xe0, 0xb9, 0xf9, 0x36, 0xc3, 0x40, 0x88, 0xda, 0x46, 0x48, 0x01, 0x79, 0xf5, + 0xda, 0x44, 0x08, 0x10, 0x03, 0x82, 0x0b, 0xfa, 0xcb, 0x5e, 0x0f, 0x61, 0x2e, 0xaf, 0xcb, 0x3d, 0x21, 0xea, 0x3a, + 0x58, 0x8f, 0xc8, 0x78, 0xd3, 0x15, 0xfe, 0xeb, 0xee, 0x8e, 0x12, 0x29, 0x14, 0xcb, 0x44, 0xf2, 0x23, 0xca, 0x23, + 0xc6, 0x11, 0xf2, 0xe8, 0x04, 0x1f, 0xbb, 0xc2, 0xa0, 0x3b, 0x54, 0x5a, 0x66, 0x7a, 0x53, 0x62, 0xab, 0x1d, 0x7b, + 0x14, 0x6c, 0x26, 0x4b, 0x0d, 0x9f, 0x23, 0x4a, 0xd7, 0x3e, 0x8c, 0x6b, 0x27, 0xff, 0xb1, 0xb3, 0xcd, 0x53, 0xb3, + 0x8f, 0x88, 0xbe, 0xc9, 0x64, 0x1c, 0x59, 0x98, 0x28, 0x0a, 0x7f, 0x08, 0x81, 0x97, 0x3a, 0xe2, 0xa9, 0xe1, 0x20, + 0xe6, 0x21, 0xd3, 0x64, 0xe4, 0x7a, 0x40, 0x5f, 0x68, 0x72, 0xb4, 0x74, 0x39, 0xa6, 0x07, 0x0a, 0x44, 0x75, 0x6c, + 0x47, 0x88, 0xcb, 0xb4, 0x89, 0x58, 0x4e, 0xab, 0x2e, 0x47, 0x45, 0x66, 0x9d, 0xa7, 0x7c, 0x92, 0x20, 0x06, 0x6e, + 0x52, 0xd4, 0xef, 0x1c, 0x87, 0x76, 0x51, 0x70, 0xfb, 0x4c, 0x25, 0x9c, 0x8d, 0x1a, 0xba, 0x2f, 0xc3, 0xf7, 0xa2, + 0x59, 0x34, 0x80, 0x6c, 0xc0, 0xd7, 0xfa, 0x2b, 0x28, 0x16, 0x0a, 0xec, 0xa6, 0x34, 0x53, 0xb6, 0x7e, 0x5d, 0xc3, + 0x6c, 0xeb, 0xfe, 0x40, 0xa1, 0x35, 0x2d, 0x34, 0xc6, 0xd4, 0xa7, 0xc2, 0xb7, 0x76, 0x63, 0x88, 0xc9, 0xc9, 0xcd, + 0x46, 0x1e, 0x83, 0x75, 0x98, 0x75, 0x8f, 0xb1, 0x39, 0x89, 0x7f, 0xa9, 0xcf, 0x5c, 0x10, 0x1e, 0x59, 0xc9, 0x82, + 0xbf, 0xd0, 0xcd, 0x60, 0xd3, 0x78, 0x97, 0xfe, 0x8e, 0x35, 0x4d, 0x98, 0xac, 0x49, 0x2b, 0x18, 0x86, 0xa4, 0x76, + 0x53, 0xa5, 0x75, 0xf2, 0x27, 0x17, 0x05, 0x42, 0x6a, 0xe2, 0x56, 0x54, 0x76, 0x32, 0x5a, 0x4a, 0xa4, 0x1b, 0x7b, + 0xdf, 0xfc, 0xbc, 0x3e, 0xea, 0xca, 0x5f, 0xcc, 0x10, 0x06, 0xf4, 0x0b, 0xa9, 0xbe, 0x57, 0x89, 0xe3, 0x57, 0x6d, + 0xad, 0x68, 0x6d, 0xcc, 0x67, 0x01, 0x13, 0xeb, 0x9e, 0x42, 0x2f, 0x56, 0x26, 0xf9, 0xbd, 0xc3, 0x3c, 0xd3, 0x78, + 0x24, 0xfb, 0x48, 0x4e, 0x31, 0x7f, 0x0c, 0x68, 0xfc, 0x1b, 0x8a, 0xec, 0x89, 0x81, 0x06, 0xd5, 0xa3, 0xa1, 0x5c, + 0x07, 0xe0, 0x10, 0x43, 0x13, 0x51, 0x1f, 0x68, 0xc7, 0x70, 0x47, 0x73, 0x81, 0xd4, 0x53, 0x2c, 0xd0, 0xc4, 0x73, + 0x64, 0x13, 0x93, 0x93, 0xb1, 0x0b, 0x70, 0x05, 0x6e, 0x59, 0xcf, 0x70, 0xb1, 0xf7, 0xdb, 0x45, 0x48, 0xa9, 0xa9, + 0xc0, 0x85, 0xc7, 0xaa, 0x09, 0xe0, 0x29, 0xd5, 0x44, 0x53, 0x43, 0xaa, 0x1f, 0x3a, 0x01, 0xfb, 0xc5, 0x49, 0x63, + 0x6a, 0x4d, 0x33, 0xca, 0xf2, 0x75, 0xe0, 0xa5, 0x24, 0x6b, 0xa2, 0x42, 0xdf, 0xe0, 0x94, 0x23, 0x10, 0xef, 0xf0, + 0xab, 0xcb, 0xdb, 0x49, 0x7a, 0x5b, 0xe8, 0x63, 0x93, 0x01, 0x86, 0xe1, 0x73, 0x84, 0x5f, 0xec, 0xb0, 0xb3, 0x75, + 0xe3, 0x3f, 0x26, 0x48, 0xc6, 0x8b, 0xc2, 0xdf, 0x1a, 0x2f, 0x48, 0x47, 0x35, 0x09, 0xf1, 0x0f, 0x45, 0xb7, 0x4f, + 0x18, 0x9d, 0x04, 0x75, 0xf6, 0xe5, 0xb2, 0x71, 0x61, 0x55, 0xd0, 0x68, 0x9f, 0x1b, 0x56, 0xb3, 0xe5, 0xfb, 0x9f, + 0xff, 0x22, 0xf7, 0x63, 0xa9, 0x71, 0xe2, 0x51, 0x6b, 0x5c, 0xb7, 0x72, 0xc7, 0x77, 0xba, 0xb3, 0xde, 0x29, 0xe7, + 0xcd, 0xdc, 0xf4, 0x0d, 0x4a, 0xf8, 0x67, 0xbe, 0xab, 0x08, 0x36, 0x9e, 0xc0, 0x56, 0xa4, 0x96, 0xef, 0xdc, 0x1c, + 0xe4, 0xe8, 0x6d, 0xd3, 0x60, 0xa3, 0x73, 0xd0, 0x0b, 0xf2, 0x04, 0x49, 0xca, 0x0f, 0xf9, 0x77, 0x09, 0xe3, 0x6c, + 0x3b, 0xab, 0xf3, 0x68, 0x15, 0xf1, 0xd8, 0xbb, 0x1c, 0x2e, 0xec, 0x10, 0xa5, 0xcb, 0x07, 0x57, 0x57, 0x53, 0x6b, + 0x99, 0x2a, 0xeb, 0xe9, 0xcc, 0xcc, 0x58, 0x60, 0xe2, 0x4c, 0x96, 0x21, 0x82, 0x1e, 0x21, 0x11, 0xa3, 0xef, 0x5d, + 0x30, 0x00, 0xc7, 0xd6, 0xea, 0x6b, 0x22, 0x68, 0x0b, 0x8a, 0x66, 0x11, 0xbd, 0x18, 0x9e, 0x0a, 0xaf, 0x33, 0x60, + 0xcb, 0x15, 0xcf, 0x4b, 0xa8, 0x1a, 0xb2, 0x86, 0xc9, 0x7e, 0x9f, 0x26, 0x7e, 0xb0, 0xcf, 0xe7, 0x16, 0x6a, 0x2b, + 0x33, 0xea, 0x27, 0x5c, 0xb3, 0xd4, 0xb9, 0xa0, 0xd9, 0xaf, 0x69, 0xaf, 0x60, 0xe6, 0xc9, 0x36, 0x97, 0x5f, 0xf7, + 0xe5, 0x70, 0x6a, 0x9e, 0x41, 0x95, 0xb7, 0x09, 0x4b, 0xe8, 0x8f, 0xa9, 0x46, 0x3c, 0xb2, 0xd1, 0x96, 0x55, 0x4b, + 0x51, 0x1d, 0x8b, 0x24, 0x8a, 0x8c, 0x9d, 0x05, 0xce, 0xd0, 0x0b, 0x89, 0x67, 0xb3, 0x06, 0x13, 0x26, 0x37, 0xb3, + 0x78, 0xa7, 0x30, 0x57, 0xc2, 0x49, 0x2c, 0xb2, 0x08, 0x45, 0xda, 0x37, 0xc3, 0xcd, 0x90, 0x9f, 0xda, 0xdc, 0x8e, + 0x84, 0x2a, 0x8f, 0xf8, 0xcf, 0x82, 0x4b, 0x4c, 0xa4, 0x02, 0x95, 0xf8, 0xdc, 0x77, 0xc4, 0x12, 0x49, 0xaa, 0x28, + 0x45, 0x41, 0xbd, 0x4c, 0xfe, 0xb2, 0x79, 0x69, 0x4a, 0x63, 0x77, 0x04, 0xee, 0x1e, 0xfa, 0x58, 0x49, 0xdc, 0x71, + 0xcc, 0x64, 0x67, 0x01, 0xd8, 0xa3, 0xcb, 0x0d, 0x7f, 0x8e, 0x01, 0x97, 0x47, 0xe7, 0x3d, 0x81, 0x60, 0x0f, 0x77, + 0xf0, 0xbd, 0xce, 0xa5, 0x8e, 0x33, 0x12, 0x11, 0x0b, 0xce, 0x90, 0xc5, 0x93, 0x37, 0x00, 0x24, 0xe7, 0x1f, 0xe2, + 0xe7, 0x05, 0xad, 0x2f, 0x80, 0x2a, 0x1c, 0x15, 0x80, 0xd8, 0x21, 0xc1, 0xa2, 0x0b, 0xef, 0x64, 0xe6, 0xb5, 0x66, + 0xc7, 0xeb, 0x0b, 0xfa, 0xeb, 0x97, 0xb9, 0x7b, 0x72, 0x52, 0x12, 0x46, 0x9c, 0x61, 0xf6, 0x83, 0xa0, 0x44, 0xf5, + 0x7c, 0x98, 0x10, 0x46, 0x66, 0x4b, 0xbc, 0xb8, 0x69, 0x10, 0xe0, 0xf6, 0x11, 0x62, 0x26, 0xdb, 0xa5, 0x1c, 0x93, + 0xaf, 0x9e, 0x71, 0x4e, 0xcd, 0x19, 0x42, 0xd1, 0x40, 0xb7, 0x96, 0x40, 0xac, 0x73, 0x28, 0xa3, 0xa1, 0x34, 0xe5, + 0xcf, 0xe5, 0x08, 0x6a, 0x1d, 0xfb, 0xc9, 0x64, 0x68, 0xb7, 0xc1, 0xdd, 0x0f, 0x48, 0x91, 0xc2, 0x3d, 0x1a, 0x38, + 0x61, 0xbc, 0x5a, 0x5c, 0x32, 0xcb, 0x6c, 0x79, 0x24, 0x5b, 0xc9, 0xee, 0x07, 0xc1, 0xf0, 0xf9, 0x94, 0x70, 0x31, + 0x4b, 0x47, 0x37, 0x64, 0x15, 0x5c, 0x16, 0xeb, 0xfd, 0xb3, 0xbf, 0xeb, 0xba, 0xc9, 0xc8, 0x6d, 0x93, 0x8c, 0x0d, + 0xe5, 0x78, 0x5c, 0x41, 0xda, 0x90, 0xd7, 0xc3, 0x20, 0x8d, 0x34, 0x15, 0x42, 0x67, 0xd6, 0x0f, 0xf7, 0x87, 0x78, + 0xbc, 0x98, 0xab, 0xb3, 0x05, 0x18, 0xb4, 0x71, 0x07, 0x4e, 0x59, 0x86, 0x25, 0x31, 0x21, 0x09, 0x1b, 0x1e, 0x80, + 0xa9, 0xd6, 0xf7, 0xa2, 0x1c, 0xff, 0x2e, 0xd9, 0xaa, 0x41, 0xa6, 0xe7, 0x06, 0xcd, 0xcf, 0xd2, 0x78, 0x30, 0x0a, + 0x3f, 0x89, 0x39, 0x9c, 0x71, 0x98, 0x23, 0x44, 0x65, 0x8e, 0x7e, 0x83, 0x41, 0xcf, 0xfd, 0xb4, 0x34, 0xff, 0x6c, + 0xe3, 0xfc, 0x51, 0x19, 0xcd, 0xb3, 0xa5, 0xe9, 0xb3, 0x05, 0xe3, 0xde, 0x96, 0xb4, 0x49, 0x77, 0x16, 0xc5, 0x7f, + 0x51, 0x1d, 0x3e, 0xde, 0x61, 0x12, 0xf5, 0xc0, 0x95, 0x04, 0x37, 0xcd, 0x09, 0x1f, 0xef, 0xd4, 0x89, 0x79, 0x48, + 0x88, 0xcc, 0x8e, 0x91, 0xd1, 0xd6, 0x98, 0xda, 0xad, 0x60, 0x71, 0xe9, 0x45, 0x45, 0xb0, 0x93, 0x0c, 0x1b, 0xa6, + 0xbb, 0x1d, 0xaf, 0xb4, 0x3b, 0x48, 0x08, 0x37, 0xae, 0xb6, 0x9b, 0x1a, 0x2d, 0xe6, 0xb4, 0x80, 0x52, 0x12, 0x29, + 0x89, 0x66, 0xd3, 0x38, 0x32, 0xad, 0xe8, 0xe7, 0x05, 0x95, 0xdc, 0x2a, 0xde, 0xfc, 0xda, 0x19, 0x4e, 0x54, 0x49, + 0x4d, 0x49, 0x4d, 0x5d, 0x62, 0xd2, 0x33, 0x60, 0xfe, 0x77, 0xc6, 0x4c, 0x28, 0x54, 0x6e, 0x5c, 0x79, 0x46, 0xc9, + 0x2f, 0x87, 0x6a, 0x27, 0x53, 0x3a, 0xf3, 0x5c, 0x7f, 0x97, 0xd6, 0x3a, 0x17, 0xce, 0x38, 0x74, 0xc3, 0x1b, 0xdf, + 0x16, 0x6d, 0x9e, 0xbf, 0x80, 0xbf, 0x75, 0x4c, 0x96, 0x24, 0xa8, 0x99, 0x3b, 0x5f, 0xca, 0x4e, 0xad, 0xb9, 0xb1, + 0x26, 0xcb, 0xed, 0x54, 0x9b, 0x73, 0xb5, 0xc2, 0xd3, 0x1b, 0x35, 0xa9, 0x22, 0x46, 0xd3, 0xc0, 0x42, 0x19, 0xd2, + 0x79, 0x16, 0xb4, 0xd4, 0xf2, 0xf3, 0x29, 0xa7, 0x0d, 0x12, 0xce, 0xa9, 0x00, 0x3b, 0x13, 0x45, 0x2e, 0x3e, 0xc3, + 0xbd, 0x69, 0xfa, 0x15, 0xec, 0xaf, 0x94, 0xcc, 0xf9, 0xd3, 0xb3, 0x39, 0x79, 0x7a, 0x3a, 0xa7, 0x4f, 0xef, 0x9c, + 0x6d, 0xb0, 0x9f, 0x73, 0xb1, 0xcb, 0x81, 0x45, 0x49, 0x92, 0xa7, 0xe3, 0xca, 0x8d, 0xe1, 0xdc, 0x99, 0x9c, 0x47, + 0xa6, 0xea, 0x64, 0x83, 0x49, 0x99, 0xd0, 0xea, 0x89, 0xb6, 0xc4, 0xd8, 0x34, 0x81, 0x60, 0x97, 0xfe, 0x98, 0xb5, + 0xed, 0x17, 0x77, 0x49, 0x0a, 0xb5, 0xa5, 0xb5, 0xe9, 0x71, 0x14, 0x52, 0xf3, 0x4b, 0x5b, 0x4f, 0x89, 0x73, 0xe7, + 0xc7, 0x2c, 0x3a, 0x88, 0xa7, 0xd5, 0xb1, 0xba, 0x13, 0x81, 0x29, 0xd7, 0x86, 0x78, 0xb3, 0x35, 0x10, 0xd9, 0xf3, + 0x16, 0xce, 0xca, 0x1f, 0x5a, 0x60, 0x77, 0x42, 0x08, 0x13, 0x81, 0xca, 0x58, 0x28, 0xad, 0x24, 0xb0, 0x0a, 0x7c, + 0x94, 0xaa, 0xd9, 0xec, 0xb4, 0xf8, 0x3e, 0x84, 0x7c, 0x8e, 0x9b, 0x10, 0x4e, 0x00, 0x79, 0x3d, 0x03, 0x75, 0x15, + 0xa2, 0x40, 0x33, 0x03, 0x48, 0xf8, 0x21, 0x79, 0x7c, 0x02, 0xf3, 0xc7, 0x74, 0xf9, 0x56, 0xad, 0xdc, 0x41, 0x3d, + 0x9f, 0x4b, 0x62, 0xe5, 0xa6, 0x9a, 0x14, 0x17, 0x25, 0xb1, 0x92, 0x58, 0xb4, 0xf0, 0xca, 0x15, 0xeb, 0x2e, 0xbd, + 0xe3, 0x37, 0x6c, 0x4b, 0xcf, 0x17, 0xf7, 0x31, 0xae, 0x40, 0xd5, 0xa8, 0x86, 0x6d, 0xfe, 0x04, 0x4c, 0x4d, 0x2f, + 0x12, 0xd8, 0x62, 0xb3, 0xd8, 0x9c, 0x81, 0x8e, 0xec, 0xa3, 0xec, 0x49, 0x99, 0x4a, 0x16, 0xa8, 0xe4, 0x5a, 0x29, + 0xac, 0xb6, 0x66, 0x51, 0x9b, 0x28, 0xef, 0xb9, 0x43, 0xeb, 0x93, 0x5e, 0x66, 0x0a, 0xdb, 0x43, 0x44, 0x9f, 0xd1, + 0x73, 0x1f, 0xd3, 0xe2, 0x7b, 0x40, 0x83, 0xb6, 0x14, 0x8c, 0x4c, 0x71, 0x68, 0x4f, 0x06, 0x94, 0x26, 0x4b, 0xf0, + 0xd0, 0x40, 0x15, 0x36, 0xe4, 0x33, 0x1c, 0xb0, 0xfd, 0x98, 0x46, 0x1b, 0x2b, 0x2a, 0xb1, 0x87, 0x68, 0x37, 0x07, + 0xdf, 0x95, 0x0e, 0x78, 0x47, 0xa6, 0xb8, 0x19, 0xde, 0xec, 0xc2, 0xdb, 0xb2, 0x50, 0xf7, 0xe3, 0x60, 0x87, 0x08, + 0x43, 0xd9, 0x14, 0x90, 0x9e, 0xf7, 0x9a, 0x42, 0x91, 0xe3, 0x5b, 0xcb, 0x27, 0xaa, 0x37, 0x48, 0x17, 0x4d, 0x80, + 0x3a, 0x18, 0xf5, 0xc0, 0x4f, 0x08, 0x72, 0x40, 0x65, 0xf4, 0xe1, 0x8a, 0xb6, 0xb8, 0xfe, 0x3c, 0x0d, 0x01, 0x59, + 0x5a, 0x91, 0x66, 0x07, 0x4c, 0x23, 0x17, 0x0d, 0x75, 0xdf, 0xc4, 0x32, 0x01, 0x48, 0xba, 0x78, 0x35, 0x12, 0x99, + 0x00, 0xb6, 0xc0, 0x8e, 0xcd, 0x63, 0x37, 0x7c, 0x5d, 0x9f, 0x0c, 0x18, 0x5a, 0x76, 0xbd, 0x7d, 0xb2, 0xfa, 0x68, + 0x7d, 0xae, 0xa9, 0x5e, 0x71, 0x5c, 0x14, 0x4b, 0xa6, 0x8a, 0x3a, 0x9f, 0x6c, 0x90, 0x00, 0x6b, 0x73, 0x39, 0x1e, + 0xe8, 0x20, 0x83, 0x1e, 0x9b, 0x54, 0x39, 0x71, 0xed, 0x98, 0xfd, 0xfa, 0xe2, 0x98, 0x51, 0x6b, 0x4e, 0x1f, 0x48, + 0x98, 0xda, 0x49, 0x84, 0xba, 0x63, 0xe5, 0x1b, 0x66, 0x34, 0x0b, 0xc2, 0x7e, 0xb0, 0xc9, 0xc6, 0x1a, 0x36, 0x7f, + 0x9c, 0xb2, 0xb9, 0x81, 0x86, 0xc9, 0x0f, 0x50, 0x07, 0xe2, 0x62, 0xc0, 0xc5, 0xdb, 0x48, 0xc6, 0xcc, 0xbf, 0xe3, + 0xbe, 0x57, 0x4a, 0x69, 0xd4, 0xf1, 0xa5, 0xd4, 0xf0, 0xf6, 0x7e, 0xe9, 0xff, 0x78, 0xf6, 0x21, 0x3f, 0x14, 0xa8, + 0x50, 0x85, 0xb4, 0x34, 0x89, 0x7a, 0xbe, 0x83, 0xd8, 0xf6, 0xbd, 0x16, 0x69, 0xc5, 0x22, 0x52, 0x1e, 0x01, 0x0e, + 0xe1, 0xf0, 0x5e, 0xb3, 0x89, 0x11, 0xdf, 0x9a, 0x97, 0x56, 0x69, 0x4b, 0xb4, 0xd6, 0x40, 0xbc, 0x8b, 0x56, 0x93, + 0x38, 0xd3, 0x23, 0x1d, 0x3b, 0x53, 0xdb, 0x29, 0x8a, 0xde, 0x65, 0x83, 0xad, 0xe6, 0x6a, 0xeb, 0x77, 0x56, 0xf4, + 0x21, 0xac, 0x4e, 0x78, 0x7d, 0x2d, 0x2a, 0x8d, 0xcd, 0xc4, 0x09, 0x62, 0xdf, 0x7d, 0xc6, 0x4c, 0x93, 0x08, 0x40, + 0x59, 0x71, 0x59, 0x86, 0x25, 0x52, 0x84, 0x1d, 0xd0, 0x49, 0x34, 0x60, 0x62, 0xce, 0x70, 0x6c, 0xc4, 0x9e, 0x67, + 0xcd, 0xad, 0x72, 0x25, 0x94, 0x41, 0x99, 0xb4, 0x6e, 0xbb, 0xdd, 0x0c, 0xbe, 0xf7, 0x1d, 0x50, 0xcb, 0xb9, 0x72, + 0x06, 0xc7, 0x52, 0xc3, 0xc5, 0x17, 0xc0, 0xc8, 0xad, 0x4a, 0x33, 0x02, 0x2c, 0xd5, 0x39, 0x4b, 0xb7, 0xa7, 0xde, + 0x69, 0x4b, 0xca, 0x89, 0xc3, 0xec, 0xf1, 0xbc, 0x2a, 0x7b, 0x02, 0xed, 0xe7, 0xbd, 0xe7, 0x8d, 0xaa, 0x55, 0xfe, + 0x7a, 0x6d, 0x20, 0xa5, 0xa0, 0xd9, 0x8c, 0xa5, 0x9e, 0xac, 0x65, 0xb9, 0xa9, 0x81, 0xbb, 0xe4, 0xa2, 0x73, 0x07, + 0xb6, 0x66, 0x8d, 0xe9, 0xc8, 0x32, 0xfa, 0x57, 0xb0, 0xc3, 0x37, 0xb0, 0x16, 0x97, 0x40, 0xe6, 0x6f, 0x8c, 0xef, + 0x84, 0xbc, 0x52, 0x3e, 0xd2, 0xf9, 0x19, 0xa3, 0xae, 0xc2, 0x54, 0x91, 0x70, 0xbd, 0xd5, 0x5a, 0xad, 0x06, 0x0d, + 0xd3, 0xf2, 0x89, 0x11, 0x1f, 0x35, 0xb4, 0xda, 0xea, 0x6d, 0xbf, 0x56, 0x4b, 0x63, 0x3a, 0x3f, 0x4f, 0xae, 0x94, + 0x78, 0xfb, 0x65, 0x89, 0xb9, 0xc2, 0xbf, 0x4d, 0x39, 0x46, 0xdf, 0xc3, 0xaf, 0x1b, 0x7e, 0x90, 0x79, 0x81, 0x48, + 0xaa, 0x71, 0x85, 0x71, 0x4c, 0x7e, 0x9a, 0x55, 0x23, 0x66, 0x8a, 0xf0, 0x83, 0x53, 0x07, 0x56, 0x37, 0x59, 0x1e, + 0x3a, 0x17, 0xa1, 0x02, 0x88, 0x3d, 0x8d, 0x9d, 0x77, 0x61, 0xc9, 0x98, 0x8a, 0x04, 0xc2, 0xb8, 0x42, 0x7b, 0x4a, + 0x30, 0x76, 0xcb, 0xa8, 0x76, 0xd5, 0xbb, 0x05, 0xf3, 0x9a, 0x86, 0x08, 0x98, 0xc1, 0x3b, 0xd0, 0xbc, 0x99, 0x6d, + 0x36, 0xe8, 0x9c, 0xd8, 0x51, 0x81, 0x3d, 0x20, 0x33, 0xde, 0xe1, 0xee, 0x37, 0x33, 0xe0, 0x82, 0x6b, 0xd8, 0x9e, + 0x87, 0x76, 0xa3, 0x1b, 0xae, 0x3c, 0x94, 0x77, 0x65, 0xc4, 0xd9, 0x02, 0xcb, 0xd7, 0xc8, 0x56, 0xac, 0xab, 0x9e, + 0xa0, 0xee, 0x81, 0x64, 0x6f, 0xdf, 0x5c, 0xf7, 0x76, 0x17, 0x0d, 0x82, 0x46, 0x77, 0x1b, 0xc0, 0xee, 0x60, 0xc1, + 0xbb, 0xd5, 0xd3, 0xf1, 0xc4, 0x01, 0x40, 0xf6, 0x64, 0x3f, 0x08, 0x2b, 0x74, 0xa7, 0xdb, 0x5f, 0xbb, 0xa1, 0x2c, + 0x68, 0x83, 0xa6, 0x3c, 0x86, 0xb6, 0x09, 0x23, 0x62, 0xc8, 0xae, 0x4b, 0x9e, 0x75, 0x73, 0x5f, 0x08, 0x17, 0xf0, + 0x88, 0x03, 0xb6, 0x43, 0x5d, 0x10, 0x0c, 0x07, 0x24, 0xe4, 0x5c, 0x88, 0xbf, 0x4d, 0x43, 0xcd, 0x32, 0xee, 0x36, + 0x1b, 0x62, 0x37, 0xa9, 0xe8, 0x0f, 0x9a, 0xc2, 0x9b, 0x6b, 0x2b, 0xc7, 0x0a, 0xc9, 0x7c, 0xe4, 0x5c, 0xed, 0x67, + 0xcd, 0x5c, 0x77, 0xa7, 0x6c, 0xcd, 0x98, 0x97, 0x3a, 0xcc, 0x8a, 0x78, 0xb7, 0x69, 0xe0, 0x59, 0xff, 0x16, 0xd6, + 0x38, 0xb1, 0xa9, 0x29, 0x8f, 0x63, 0x8e, 0x76, 0xd6, 0xd3, 0xa9, 0x65, 0x5f, 0xfd, 0xb5, 0x4d, 0x4b, 0xf5, 0xd9, + 0x18, 0x76, 0x61, 0xe5, 0xfc, 0x19, 0xa2, 0xc2, 0x96, 0x32, 0x99, 0x6a, 0x12, 0xc3, 0x20, 0x30, 0x5a, 0x74, 0x7b, + 0x0b, 0xd5, 0xb0, 0x8b, 0x1a, 0x75, 0x6d, 0xcd, 0xba, 0x5b, 0x13, 0xf6, 0xdf, 0xcd, 0x7c, 0xad, 0xaa, 0xaf, 0xa6, + 0x51, 0xa2, 0xe0, 0x74, 0x38, 0x58, 0xcb, 0xdd, 0x7f, 0x14, 0x79, 0x33, 0xc3, 0x58, 0x1c, 0x88, 0x6a, 0xd1, 0xc2, + 0x55, 0x7a, 0xab, 0xcd, 0x8a, 0x08, 0xc9, 0xa9, 0x1d, 0xf5, 0x3f, 0x68, 0x00, 0xa9, 0xb9, 0x59, 0xa1, 0x6e, 0xce, + 0xb1, 0xe0, 0x18, 0x95, 0x3a, 0x67, 0x78, 0xca, 0x29, 0xf1, 0x90, 0x8a, 0x8e, 0x76, 0x39, 0xd1, 0x5a, 0xa3, 0x2d, + 0x47, 0x00, 0x02, 0xb9, 0xda, 0xb0, 0x4e, 0xa1, 0x77, 0x9b, 0xcc, 0x55, 0xa6, 0xc3, 0xcc, 0x54, 0x81, 0xf1, 0x37, + 0x2b, 0x73, 0x80, 0x94, 0x8b, 0x24, 0x66, 0x6e, 0x67, 0x68, 0x85, 0x01, 0x3a, 0x8c, 0x11, 0xf8, 0x76, 0xf2, 0x43, + 0xfd, 0x69, 0xd3, 0xb2, 0x88, 0x63, 0xc7, 0xe4, 0xec, 0xb5, 0x1d, 0x14, 0xb4, 0x6a, 0xbb, 0x37, 0xf1, 0x9a, 0x67, + 0x01, 0xed, 0x8b, 0x3f, 0x97, 0xde, 0x1e, 0x9e, 0xf8, 0xdc, 0x86, 0xac, 0x40, 0xea, 0x24, 0x6f, 0x6d, 0x65, 0x94, + 0xab, 0x9d, 0x4b, 0xa4, 0xfd, 0xf2, 0x98, 0x24, 0xdb, 0xc6, 0xbf, 0x47, 0x2e, 0xa5, 0x40, 0xf2, 0xf7, 0x5d, 0xbf, + 0x88, 0x6c, 0x31, 0x4b, 0x12, 0xa6, 0x7a, 0x4d, 0xf2, 0xf5, 0x32, 0xac, 0xe3, 0x36, 0x1d, 0xff, 0x59, 0x72, 0xc1, + 0xd3, 0x44, 0x48, 0xad, 0xb7, 0x55, 0xa8, 0x2d, 0x3a, 0xba, 0x85, 0x8b, 0xe7, 0x3c, 0x94, 0xa8, 0x20, 0x63, 0xb6, + 0x59, 0x77, 0xa9, 0x44, 0xa2, 0x16, 0x2c, 0x0b, 0xed, 0x76, 0x3d, 0x4e, 0x51, 0xeb, 0x00, 0xc9, 0x4e, 0x45, 0x5f, + 0x85, 0xa6, 0x32, 0xb6, 0x49, 0x2c, 0x17, 0x56, 0x62, 0x51, 0xb7, 0x97, 0x4a, 0xab, 0x59, 0xd1, 0x95, 0x97, 0x0b, + 0xa1, 0x7a, 0x07, 0xb0, 0x75, 0x9f, 0x7b, 0x37, 0x80, 0x6e, 0x54, 0x03, 0x37, 0x20, 0x03, 0x50, 0x6a, 0x0a, 0x95, + 0x9b, 0x46, 0x9c, 0x4a, 0xab, 0x4a, 0x31, 0x07, 0x24, 0x82, 0x33, 0xfa, 0x73, 0x80, 0x39, 0x5c, 0x23, 0x07, 0xb8, + 0x6a, 0x59, 0xb5, 0x65, 0xac, 0xad, 0xd3, 0xa7, 0xad, 0xc3, 0xcc, 0xca, 0xfe, 0x09, 0xf8, 0x2e, 0x3e, 0xa9, 0x1d, + 0xd9, 0xfe, 0x16, 0x47, 0x12, 0xa1, 0xd0, 0xf5, 0x81, 0xa1, 0x30, 0x43, 0x18, 0x66, 0x77, 0x17, 0x84, 0xe9, 0xf5, + 0xa5, 0x40, 0xc1, 0xc2, 0xcd, 0xb9, 0xd8, 0x71, 0xfc, 0xf4, 0x6e, 0xfc, 0x44, 0x10, 0x0e, 0xcd, 0x54, 0x08, 0x9f, + 0x4b, 0x93, 0xa2, 0x20, 0x67, 0x0a, 0x41, 0xe0, 0xc1, 0xb6, 0x2f, 0x51, 0xa3, 0x48, 0x48, 0xb2, 0xb8, 0x06, 0x9a, + 0x28, 0xaf, 0x12, 0x2e, 0x48, 0x5f, 0xb6, 0xe5, 0x60, 0x76, 0x0d, 0x5b, 0xb2, 0x1a, 0xde, 0x22, 0xf1, 0xa3, 0x65, + 0xde, 0x47, 0xc4, 0xa4, 0x7b, 0x69, 0x03, 0x2d, 0xd6, 0xb6, 0xe5, 0x5f, 0xf7, 0x80, 0x2b, 0x85, 0x03, 0x43, 0x1b, + 0xd9, 0xfa, 0x6a, 0xc3, 0x2e, 0xc9, 0xe2, 0xe6, 0x97, 0xf5, 0xf0, 0xf8, 0xc1, 0xd8, 0xf7, 0x1d, 0xdc, 0x2e, 0x88, + 0xd6, 0xe6, 0xfc, 0x86, 0x99, 0xd6, 0x82, 0xd5, 0x0d, 0xd4, 0x18, 0x0d, 0x2e, 0x95, 0x59, 0x6a, 0xcc, 0xbf, 0xbc, + 0x4b, 0x7d, 0x30, 0xe1, 0x28, 0xf1, 0x19, 0x24, 0xae, 0xe1, 0xfa, 0xf0, 0xf1, 0x9e, 0x49, 0xdf, 0x06, 0xa1, 0xc8, + 0xae, 0x62, 0xd9, 0x2e, 0x0a, 0x20, 0x39, 0xdc, 0x55, 0x53, 0x6d, 0x70, 0x40, 0x17, 0xa5, 0xa3, 0x21, 0x51, 0x06, + 0x4d, 0xec, 0x4b, 0x18, 0xef, 0x8b, 0x1e, 0xbb, 0x0d, 0xbb, 0x78, 0x45, 0xf2, 0xda, 0x6a, 0x2a, 0x52, 0xf6, 0xbc, + 0x22, 0xdf, 0x94, 0x6a, 0xf2, 0x11, 0x4d, 0xa3, 0xa7, 0xa5, 0xd7, 0x96, 0x7b, 0x94, 0x00, 0x74, 0xaf, 0xce, 0xef, + 0xde, 0x1e, 0x1d, 0xb2, 0xcd, 0x44, 0xbe, 0x79, 0x73, 0x3c, 0x5e, 0xde, 0x7f, 0xa9, 0x33, 0x39, 0xc2, 0x3a, 0x8e, + 0xaf, 0x7f, 0xbc, 0x6c, 0x1a, 0x6b, 0xd5, 0x0f, 0x6a, 0x60, 0xed, 0xa9, 0x67, 0x9a, 0xb3, 0xb0, 0x26, 0x98, 0x70, + 0xae, 0xc0, 0x39, 0xd3, 0x41, 0xc8, 0xb1, 0xfc, 0xeb, 0x27, 0x8d, 0xa9, 0x1b, 0x37, 0x28, 0xcf, 0x7a, 0x36, 0x56, + 0x86, 0xba, 0xac, 0x65, 0x67, 0xbe, 0xeb, 0x3c, 0xc3, 0xe7, 0x3d, 0xab, 0xf4, 0x4b, 0x6b, 0x81, 0xaf, 0x35, 0xbe, + 0x92, 0xb9, 0x4d, 0xf8, 0x73, 0xe0, 0xd4, 0x31, 0x33, 0x3e, 0x05, 0xa5, 0xee, 0x96, 0x34, 0x3a, 0x8c, 0xac, 0x83, + 0xae, 0xf3, 0x9f, 0x35, 0x22, 0xe8, 0x09, 0x11, 0x9a, 0x30, 0xd4, 0x21, 0x94, 0x63, 0xfd, 0x58, 0x45, 0x6e, 0xcf, + 0x7a, 0x83, 0xb6, 0x92, 0xcd, 0x14, 0x01, 0x53, 0x82, 0xef, 0xd7, 0x55, 0x0d, 0x6c, 0xdf, 0xf4, 0x6f, 0x0f, 0x9f, + 0xe5, 0xa6, 0x21, 0x68, 0xc0, 0xff, 0x8e, 0x2c, 0x1a, 0xd0, 0x1b, 0x2b, 0x6f, 0xb4, 0xec, 0x5b, 0x4b, 0x0e, 0x8c, + 0x2a, 0x49, 0x9b, 0xa7, 0x84, 0xa8, 0x32, 0xe7, 0x7a, 0x57, 0x88, 0xbe, 0xf4, 0x28, 0xc7, 0xd3, 0x14, 0x00, 0xa6, + 0x2b, 0x2d, 0x20, 0x2e, 0x28, 0x84, 0x16, 0x1c, 0xaa, 0x59, 0xc8, 0xf6, 0xf5, 0xec, 0x14, 0x12, 0x8c, 0xd6, 0x0b, + 0xd3, 0xda, 0x90, 0x28, 0x33, 0x73, 0xca, 0xa4, 0x74, 0x97, 0xda, 0x49, 0xc8, 0x83, 0xdf, 0xd2, 0xb2, 0x01, 0x18, + 0x31, 0x91, 0x7c, 0xee, 0x6c, 0x22, 0x4b, 0x9f, 0xcf, 0x81, 0xdb, 0xec, 0x9e, 0xf4, 0x4d, 0xad, 0x1b, 0x1b, 0xa7, + 0xc1, 0xfa, 0x23, 0xe4, 0x3c, 0x77, 0x23, 0x64, 0x6b, 0x1b, 0xb7, 0xdc, 0x97, 0xe8, 0x10, 0x7d, 0xa2, 0x5d, 0x4e, + 0xd8, 0x06, 0x0d, 0xe8, 0x33, 0x6e, 0x0d, 0x97, 0x40, 0x94, 0xc3, 0x20, 0x33, 0x95, 0x43, 0x71, 0xbf, 0xec, 0x11, + 0x6a, 0x34, 0x16, 0xa8, 0x81, 0x15, 0x3e, 0x62, 0x18, 0x45, 0x15, 0xec, 0x81, 0x7f, 0x74, 0x8c, 0xe9, 0xea, 0x3b, + 0xc9, 0x92, 0x37, 0x2d, 0x11, 0xed, 0x80, 0x01, 0xeb, 0xa0, 0xe2, 0x31, 0xb6, 0x1a, 0xa7, 0x34, 0x18, 0xba, 0x9e, + 0x7c, 0xee, 0xc8, 0xd8, 0x4c, 0x46, 0xda, 0x16, 0x70, 0x87, 0xb9, 0x9d, 0x4d, 0x82, 0x39, 0xd8, 0xb2, 0x71, 0x16, + 0x37, 0xae, 0x7d, 0x8d, 0x60, 0xe8, 0x04, 0xe9, 0x74, 0x67, 0xb4, 0x79, 0x91, 0xef, 0xf1, 0x3e, 0x96, 0x98, 0xd7, + 0xca, 0xe9, 0x0e, 0x23, 0x0c, 0x88, 0xb8, 0x8d, 0x74, 0xc1, 0xcc, 0x52, 0x5a, 0xcb, 0x54, 0x45, 0x94, 0x65, 0xbb, + 0x64, 0x31, 0x00, 0xa3, 0xc0, 0xfe, 0x58, 0x9e, 0xd7, 0xa0, 0xd1, 0xd3, 0xe1, 0xe6, 0x8a, 0x26, 0x2d, 0x4b, 0x33, + 0x34, 0x3c, 0x9b, 0x0e, 0x50, 0xe1, 0x9a, 0x58, 0x9d, 0x97, 0x30, 0x5e, 0x2c, 0x2d, 0x47, 0x7f, 0xdf, 0xa3, 0x17, + 0xf1, 0x34, 0x23, 0x84, 0x53, 0xb1, 0xd9, 0xdc, 0x00, 0xb1, 0x0f, 0x5e, 0x4c, 0x74, 0x40, 0x08, 0xc6, 0xce, 0x6a, + 0x0f, 0xa8, 0xc7, 0xef, 0x0d, 0xfa, 0x3e, 0x12, 0xbe, 0x09, 0x90, 0x99, 0x82, 0xf2, 0x44, 0xed, 0x53, 0x14, 0x91, + 0x83, 0x9f, 0x64, 0x95, 0x4d, 0x2d, 0xea, 0x24, 0x30, 0x3a, 0xe2, 0xe4, 0x2c, 0x83, 0xc2, 0x79, 0xf9, 0xa2, 0x03, + 0x9b, 0xcf, 0xd9, 0x60, 0x5a, 0x6c, 0x23, 0x3b, 0x65, 0x2b, 0x68, 0x71, 0x0d, 0xd0, 0x1d, 0x9b, 0x22, 0x8e, 0x09, + 0x32, 0x56, 0xf6, 0x76, 0x8d, 0x11, 0x1b, 0xac, 0x9b, 0x3a, 0xea, 0xfd, 0x54, 0xa3, 0x53, 0xea, 0x1e, 0x1f, 0x30, + 0x19, 0x82, 0x0a, 0xa1, 0x93, 0xa0, 0xe6, 0x21, 0xfd, 0xd1, 0xc8, 0x73, 0x6a, 0xa4, 0x4e, 0x79, 0x5c, 0x67, 0x48, + 0xd1, 0xd8, 0xf7, 0xd9, 0x93, 0x3d, 0x3a, 0x4a, 0x3c, 0xa9, 0x70, 0xa2, 0xab, 0xc5, 0x19, 0x46, 0xaf, 0x3d, 0xd8, + 0xcd, 0x56, 0x54, 0x24, 0xa3, 0x50, 0x0c, 0xed, 0x59, 0x1c, 0x55, 0xe9, 0x26, 0x09, 0xf9, 0x51, 0xcc, 0x70, 0x66, + 0xe7, 0x3c, 0xfa, 0xd0, 0xd8, 0x62, 0x40, 0x46, 0x81, 0xd0, 0x6e, 0x43, 0x8f, 0xb0, 0xb9, 0x0b, 0x7f, 0xc2, 0x2f, + 0xb7, 0x52, 0xb9, 0x54, 0x70, 0x9a, 0x2e, 0xbd, 0xf7, 0xbf, 0xec, 0xa8, 0x15, 0x37, 0xde, 0xda, 0x2a, 0x97, 0x28, + 0x17, 0x3b, 0xe7, 0x3f, 0x62, 0x8f, 0x4b, 0xd8, 0xb0, 0x05, 0x97, 0x0d, 0x5d, 0xa1, 0x52, 0x4a, 0x03, 0x47, 0x1e, + 0x88, 0xa4, 0xee, 0x6b, 0x38, 0xe2, 0x16, 0xd5, 0x9f, 0xf4, 0xfa, 0x60, 0x83, 0xd1, 0x31, 0xeb, 0xb5, 0xf8, 0x46, + 0xad, 0xd0, 0x85, 0x54, 0x51, 0x03, 0x97, 0x34, 0x5b, 0x30, 0x4d, 0x86, 0xc8, 0x26, 0x49, 0xdc, 0x3d, 0x9d, 0x61, + 0x95, 0x99, 0x5e, 0xf4, 0xdf, 0x2b, 0x11, 0xe9, 0x90, 0xd7, 0x5c, 0x69, 0xc2, 0x59, 0x46, 0xf5, 0xa3, 0x70, 0x1c, + 0xe3, 0xd8, 0x74, 0x27, 0x0c, 0xa0, 0xea, 0x30, 0x87, 0xfa, 0xc9, 0x8b, 0xad, 0xd1, 0x77, 0x6d, 0x24, 0x87, 0x86, + 0xbc, 0xe9, 0x10, 0xd0, 0x9f, 0xbf, 0x8b, 0x7a, 0xc0, 0x6d, 0x6d, 0x1c, 0xed, 0xbd, 0xb8, 0x68, 0x13, 0x41, 0xf0, + 0xc7, 0x53, 0xd1, 0xc1, 0x36, 0x5d, 0x62, 0xd8, 0x5c, 0x39, 0xfa, 0xcc, 0xb0, 0x5f, 0x56, 0x44, 0xcc, 0xea, 0x3d, + 0x15, 0xf2, 0xfa, 0x77, 0xff, 0x82, 0xd9, 0xd2, 0xac, 0x91, 0x6f, 0xce, 0xcb, 0xc1, 0xd8, 0xaa, 0x4b, 0xef, 0xc2, + 0x3f, 0x0b, 0xeb, 0x00, 0xa0, 0x72, 0x67, 0xbf, 0x17, 0xe2, 0xee, 0xba, 0x0a, 0xd1, 0x07, 0x53, 0x6a, 0x52, 0x3e, + 0xe4, 0x94, 0x8d, 0x25, 0x23, 0x4f, 0x99, 0xb5, 0xd4, 0x4e, 0x42, 0xe0, 0x6e, 0x02, 0x60, 0xfc, 0xaf, 0x4c, 0x8f, + 0x0b, 0x0b, 0x7d, 0xa7, 0x95, 0x42, 0xcc, 0x84, 0x6e, 0x46, 0xef, 0x73, 0x0c, 0xd8, 0x03, 0x44, 0x72, 0x52, 0xde, + 0x13, 0xcb, 0xaa, 0x27, 0xfe, 0x2b, 0xa2, 0x35, 0x6d, 0xc4, 0x1c, 0x28, 0x3f, 0x7e, 0xd8, 0x70, 0x23, 0xa3, 0xe0, + 0x41, 0x33, 0xc2, 0xf4, 0xef, 0x1c, 0xe2, 0xde, 0x70, 0x70, 0xf4, 0x04, 0x92, 0x9e, 0xb8, 0x79, 0x7b, 0x8e, 0xcd, + 0xfe, 0x53, 0x31, 0xd2, 0x01, 0xe9, 0x28, 0x81, 0x97, 0x0e, 0x0f, 0xb4, 0xac, 0x80, 0x2b, 0x85, 0x7b, 0x19, 0x9f, + 0x9e, 0x86, 0xf6, 0xfd, 0x2b, 0x67, 0xd0, 0x64, 0xe2, 0x25, 0x7a, 0x63, 0xd1, 0xe4, 0xe3, 0xc5, 0xe7, 0xc7, 0xa3, + 0x9e, 0xbf, 0xf3, 0xf6, 0xf1, 0xac, 0xe7, 0x32, 0x67, 0x96, 0x36, 0x24, 0x8e, 0x7f, 0xd8, 0xfa, 0xce, 0x81, 0x27, + 0xbb, 0x3f, 0xb8, 0x90, 0x22, 0xeb, 0x89, 0x18, 0xe7, 0x61, 0x4b, 0x91, 0x7c, 0x00, 0xe2, 0x52, 0xfb, 0x63, 0xf1, + 0xf2, 0x6a, 0xf7, 0x8b, 0xfe, 0x47, 0xf3, 0x9a, 0xd6, 0x64, 0x1f, 0x7c, 0x9d, 0x42, 0x55, 0xf7, 0x33, 0xba, 0xfb, + 0xf6, 0x23, 0xc8, 0x19, 0x9b, 0xa5, 0x89, 0x2f, 0xfe, 0xed, 0xe8, 0x79, 0xc2, 0xad, 0x85, 0x2a, 0x33, 0x4c, 0x4f, + 0xdd, 0x63, 0xa8, 0x14, 0xc9, 0xd2, 0xb2, 0x77, 0xe2, 0x9c, 0x0b, 0x74, 0xa6, 0x3f, 0x39, 0x42, 0xe8, 0xc7, 0x96, + 0x8b, 0x98, 0x22, 0xe6, 0xd7, 0x87, 0x02, 0x38, 0x4b, 0x82, 0x27, 0x10, 0x11, 0xe8, 0x6c, 0x45, 0xf9, 0x58, 0xa8, + 0xae, 0xb9, 0xb5, 0xcf, 0x57, 0x59, 0xe0, 0x66, 0x66, 0x33, 0xdd, 0xca, 0x0d, 0x7d, 0x74, 0x9a, 0x67, 0xb9, 0x41, + 0x1d, 0xc8, 0xd9, 0x06, 0x38, 0xb0, 0x7f, 0x2b, 0x64, 0x18, 0xd6, 0xb6, 0xdc, 0x1f, 0x89, 0xde, 0x18, 0x25, 0xef, + 0x10, 0x80, 0x91, 0x29, 0xda, 0xec, 0x4f, 0x27, 0xba, 0x90, 0x51, 0xbd, 0x3f, 0x73, 0xdf, 0xaf, 0xff, 0x4d, 0x2e, + 0xd9, 0xbb, 0xa4, 0xb5, 0xd0, 0x54, 0x16, 0xd5, 0xe5, 0x68, 0xf3, 0xbc, 0x1b, 0x79, 0xe9, 0xe5, 0x37, 0x3d, 0x52, + 0xe9, 0xf9, 0x87, 0x0e, 0xb6, 0xb4, 0xfc, 0x88, 0x4c, 0x3d, 0x49, 0x04, 0x72, 0xac, 0xcd, 0xf0, 0x6a, 0xee, 0x48, + 0x65, 0x02, 0xc7, 0x74, 0x4f, 0x46, 0xbe, 0x99, 0x33, 0x76, 0x2d, 0xe9, 0x04, 0xb0, 0x31, 0x2c, 0x9b, 0xaf, 0xb9, + 0x34, 0xcb, 0xac, 0x57, 0xf6, 0xec, 0x44, 0x78, 0xc1, 0xe1, 0x95, 0xd8, 0xa6, 0x90, 0xe6, 0x57, 0x13, 0x09, 0xdc, + 0xbc, 0xde, 0x17, 0x81, 0x5a, 0xe6, 0xd2, 0xb9, 0x68, 0x11, 0xe9, 0xca, 0xbc, 0x1c, 0x56, 0xa0, 0x4f, 0x7e, 0x6e, + 0x56, 0x51, 0xda, 0x4a, 0x59, 0xb9, 0xf2, 0x1c, 0xdf, 0xd0, 0xa4, 0xd8, 0x3b, 0xda, 0x53, 0xd9, 0x21, 0x1c, 0x89, + 0xc1, 0xcd, 0x5d, 0x52, 0x49, 0x99, 0xc5, 0xa9, 0x95, 0xa4, 0x7f, 0x49, 0x98, 0x61, 0x94, 0xe0, 0x20, 0x36, 0xff, + 0x88, 0x1b, 0x73, 0xcc, 0x21, 0x8d, 0xc9, 0x89, 0x86, 0x60, 0x34, 0x53, 0x88, 0x6e, 0x4a, 0xb7, 0x52, 0x27, 0xe2, + 0xd9, 0x8b, 0x15, 0x4e, 0x3b, 0x2f, 0x91, 0xe6, 0xa5, 0x3f, 0xc2, 0x61, 0x1f, 0xc8, 0x40, 0xa3, 0x30, 0x37, 0xc6, + 0xc0, 0xce, 0x6e, 0xd2, 0x36, 0x86, 0xab, 0xde, 0x40, 0x53, 0xb8, 0x79, 0x4f, 0xd7, 0xd2, 0xe7, 0x22, 0x99, 0x58, + 0xd2, 0xa3, 0x5d, 0x4c, 0xae, 0xb5, 0x54, 0x4f, 0xe8, 0x13, 0xe9, 0xbb, 0x99, 0x56, 0x8e, 0x3e, 0x48, 0x5a, 0x0a, + 0xe0, 0xe5, 0x28, 0x70, 0xca, 0xe5, 0x89, 0x5c, 0x93, 0xe3, 0xe0, 0x75, 0x66, 0x20, 0xf5, 0x07, 0x7e, 0x1d, 0x7a, + 0x72, 0x67, 0x3f, 0x68, 0xd3, 0xdf, 0x47, 0xaa, 0xc2, 0x2c, 0xea, 0x21, 0xd2, 0x0a, 0xac, 0xbb, 0xb7, 0xf5, 0xbd, + 0x8e, 0x4f, 0x1a, 0x9a, 0xb6, 0xf8, 0x02, 0xbd, 0x2b, 0x38, 0x42, 0x9b, 0x58, 0x48, 0x9e, 0x81, 0x4f, 0xf1, 0xb0, + 0xc9, 0x53, 0xe6, 0x6e, 0x47, 0xa4, 0xfe, 0xf7, 0xc7, 0xaa, 0x01, 0x7b, 0xc5, 0x43, 0x3d, 0x79, 0x8a, 0xd8, 0xaf, + 0x5a, 0x63, 0x46, 0x5a, 0xae, 0x79, 0xb7, 0xb8, 0xef, 0x3d, 0x9f, 0xe2, 0x81, 0x0e, 0x02, 0x77, 0x46, 0xcc, 0x8e, + 0x71, 0x7e, 0x6f, 0xf6, 0xfb, 0xde, 0xbb, 0x2e, 0x03, 0x8c, 0xda, 0x40, 0xf4, 0x41, 0x10, 0xdf, 0xa7, 0x03, 0xd6, + 0x9d, 0x03, 0xb3, 0x37, 0xa6, 0xc7, 0x6d, 0x12, 0x4e, 0x4b, 0x7d, 0x3c, 0x3e, 0x64, 0xe3, 0x83, 0x62, 0x54, 0x8a, + 0x3d, 0x0b, 0x52, 0x34, 0x01, 0x62, 0x0e, 0x29, 0xc9, 0x5e, 0xec, 0x03, 0x28, 0x62, 0x2e, 0x44, 0x2e, 0x9a, 0x83, + 0x1e, 0x10, 0x8c, 0x1c, 0x36, 0xd8, 0xfe, 0x23, 0xa2, 0x0e, 0x0f, 0x77, 0x58, 0x48, 0x99, 0xf3, 0x06, 0x97, 0x61, + 0xf8, 0x00, 0x98, 0x73, 0x57, 0x6f, 0xd1, 0x77, 0x7a, 0xc2, 0x9c, 0xde, 0x67, 0x99, 0x72, 0xab, 0xc8, 0x5d, 0x9c, + 0x32, 0xc2, 0xeb, 0x22, 0xad, 0x26, 0x46, 0xba, 0x3b, 0xb4, 0xc3, 0xaf, 0x20, 0x62, 0xfa, 0x52, 0x02, 0x5e, 0x22, + 0x3b, 0x10, 0x0b, 0xfe, 0xdc, 0x50, 0x15, 0x3b, 0xe5, 0x3d, 0x86, 0x5a, 0x78, 0x86, 0x5a, 0xd5, 0x49, 0x2a, 0xb8, + 0x3b, 0x4c, 0x52, 0xfa, 0xc7, 0x89, 0xfc, 0x58, 0xd1, 0xc4, 0x3e, 0x6d, 0xba, 0xab, 0xf8, 0x6d, 0xc5, 0x5e, 0x00, + 0x31, 0x57, 0xa5, 0x33, 0x55, 0x22, 0xf2, 0x75, 0x81, 0x76, 0x68, 0xd1, 0x9e, 0x25, 0x3b, 0x17, 0xd5, 0xff, 0x4d, + 0xb1, 0x23, 0xce, 0xec, 0xd0, 0x59, 0xc4, 0xef, 0x9c, 0x4a, 0x8c, 0xdf, 0x8d, 0x77, 0xee, 0x6b, 0xd3, 0x07, 0x69, + 0xf1, 0x57, 0x05, 0x85, 0x7e, 0x67, 0xe8, 0xd6, 0x93, 0xb5, 0xa4, 0xf5, 0x77, 0xa3, 0x91, 0xa4, 0xf5, 0xa8, 0x5c, + 0xde, 0x8e, 0x5c, 0x33, 0x5f, 0xe2, 0x54, 0xfb, 0x7c, 0x3a, 0xcd, 0x7e, 0x67, 0xd8, 0xd5, 0x8d, 0x1e, 0x34, 0xab, + 0x89, 0xbe, 0xf8, 0xa9, 0xaa, 0x5b, 0xfa, 0x4e, 0xc1, 0xe8, 0xfc, 0xf4, 0xd9, 0x98, 0x3b, 0x63, 0xb6, 0x86, 0x74, + 0x86, 0x31, 0x2f, 0xa2, 0xb1, 0xe7, 0xfa, 0x02, 0xc1, 0xad, 0x4b, 0xfb, 0x0e, 0xcc, 0xd2, 0xef, 0xc0, 0xa4, 0xb3, + 0x31, 0xc5, 0xfe, 0xcc, 0x8a, 0x60, 0xe0, 0xce, 0x5d, 0x8d, 0x8c, 0xe3, 0x2c, 0xf4, 0xd1, 0xb4, 0xe5, 0xbe, 0x88, + 0x91, 0xdb, 0x1c, 0xa7, 0xcf, 0x95, 0x9a, 0x91, 0xb0, 0x5f, 0x5c, 0x70, 0x66, 0x7d, 0xe7, 0x0b, 0x95, 0xb4, 0xd6, + 0x0a, 0xf8, 0xab, 0x54, 0xcf, 0xdd, 0xfa, 0xef, 0x83, 0x60, 0xdd, 0xb4, 0xc3, 0x62, 0x39, 0xa7, 0xe9, 0xa9, 0x2a, + 0x7b, 0x83, 0x27, 0x65, 0x80, 0x9c, 0x05, 0x74, 0x2f, 0xb7, 0x96, 0xbb, 0x09, 0x30, 0x7c, 0xe8, 0xf1, 0x5a, 0xff, + 0x40, 0x05, 0xf4, 0x84, 0x40, 0x6d, 0xbf, 0xc2, 0x79, 0xdc, 0x5e, 0x89, 0x8f, 0xbf, 0x57, 0x94, 0x9b, 0x2d, 0x8f, + 0x5f, 0x57, 0x54, 0x89, 0x7e, 0x1a, 0x81, 0x3b, 0x3f, 0x57, 0xb3, 0x30, 0x31, 0x9f, 0xce, 0x2d, 0xbf, 0x47, 0xc7, + 0xe6, 0x02, 0x5a, 0xee, 0x33, 0x42, 0x1a, 0xf3, 0x7f, 0x8e, 0x59, 0x96, 0xcc, 0x0a, 0xcd, 0xf2, 0x75, 0x80, 0x63, + 0x3a, 0x3c, 0xc5, 0x8d, 0xe7, 0x38, 0xa0, 0xd0, 0x0d, 0x4a, 0xbd, 0xdd, 0x02, 0xd5, 0xac, 0x00, 0x0b, 0x05, 0x85, + 0xf4, 0x23, 0x9a, 0x47, 0xd9, 0x11, 0x03, 0x46, 0xb6, 0xd5, 0x5f, 0x73, 0x6d, 0x91, 0x47, 0xad, 0xf6, 0x2b, 0xc2, + 0xbd, 0x3e, 0x89, 0x45, 0xff, 0x9d, 0x02, 0x04, 0xb1, 0x36, 0x64, 0x6f, 0x02, 0xa6, 0x11, 0xc5, 0x14, 0x05, 0x3f, + 0x0a, 0x92, 0x42, 0xa5, 0xec, 0x5d, 0xd8, 0x22, 0xcc, 0x5c, 0x6a, 0x49, 0x19, 0x35, 0xf1, 0xbc, 0x02, 0x74, 0xa4, + 0xff, 0xba, 0xf8, 0x2e, 0x7b, 0x7a, 0x30, 0x4a, 0xca, 0x3d, 0x22, 0x20, 0x41, 0x3d, 0x93, 0x95, 0x80, 0xfd, 0x16, + 0x62, 0xfc, 0x49, 0x50, 0x92, 0x26, 0x75, 0x17, 0xc1, 0xe9, 0x36, 0x64, 0x70, 0x19, 0xad, 0x35, 0x12, 0x34, 0x7c, + 0x77, 0x50, 0x06, 0x58, 0x15, 0x82, 0x97, 0xb8, 0xe4, 0xc7, 0xc0, 0x4c, 0x45, 0x77, 0xf8, 0x4b, 0xe8, 0xe3, 0x1d, + 0xd5, 0x79, 0xd9, 0x69, 0x5d, 0x7b, 0xb7, 0x61, 0x10, 0x86, 0x8d, 0xcf, 0x0c, 0x74, 0x64, 0xaf, 0x07, 0x6c, 0xca, + 0x94, 0x11, 0x1b, 0x70, 0x52, 0xae, 0xc8, 0x68, 0x9d, 0x5f, 0xb1, 0x7c, 0xb1, 0x67, 0xff, 0x3d, 0xcc, 0x21, 0x00, + 0x19, 0x8d, 0x23, 0x70, 0xa3, 0x06, 0x78, 0x48, 0x98, 0x12, 0x7e, 0x2c, 0x44, 0x29, 0x41, 0xfe, 0x2b, 0x35, 0xa0, + 0x80, 0x1c, 0xed, 0x71, 0x21, 0x69, 0x78, 0x0c, 0x53, 0x83, 0xc2, 0x47, 0x64, 0x28, 0x73, 0xb2, 0x40, 0x49, 0xb1, + 0x0e, 0x5a, 0xc5, 0xc8, 0x2c, 0x68, 0xfd, 0xd4, 0x91, 0xdf, 0xd5, 0xf5, 0x20, 0x8a, 0x9e, 0x2e, 0xc8, 0x43, 0x28, + 0x45, 0x28, 0x37, 0x33, 0xa1, 0xe6, 0x11, 0x16, 0xdd, 0x2e, 0x1b, 0xd0, 0xfa, 0x31, 0x8c, 0x9e, 0x3c, 0xba, 0x05, + 0x27, 0x97, 0xb1, 0x02, 0xeb, 0x62, 0x27, 0x0b, 0xea, 0x8f, 0x1e, 0xb8, 0x28, 0x37, 0xe6, 0xd4, 0x27, 0x0f, 0x5d, + 0xf0, 0x79, 0xba, 0x3e, 0xf6, 0x5e, 0x9e, 0x20, 0x35, 0x0b, 0xab, 0x75, 0x6c, 0x13, 0x9e, 0xb4, 0x38, 0x4d, 0xde, + 0xcd, 0x5f, 0x9e, 0x64, 0x13, 0xaf, 0x1c, 0x05, 0x36, 0x3d, 0xb3, 0x2a, 0xb6, 0x91, 0x9d, 0x2e, 0x1b, 0xfe, 0xe6, + 0x04, 0x9f, 0x67, 0x43, 0xe6, 0x78, 0x7e, 0x41, 0x37, 0xe2, 0xc3, 0x2c, 0x12, 0x72, 0x76, 0x5b, 0xc7, 0xec, 0x4e, + 0xb5, 0xcd, 0xbf, 0xd1, 0xb9, 0x7d, 0xec, 0xfb, 0xcc, 0x47, 0x2a, 0x4b, 0x17, 0x94, 0x84, 0xdd, 0xf1, 0x90, 0x74, + 0xb2, 0xc9, 0x82, 0x33, 0xa7, 0x01, 0xf7, 0x1a, 0xb9, 0x2c, 0xce, 0x6b, 0xa2, 0xb9, 0xb8, 0x5b, 0xc1, 0x96, 0x01, + 0x9c, 0xea, 0xcc, 0x45, 0x70, 0x55, 0x11, 0x38, 0x35, 0x35, 0x53, 0x45, 0xf1, 0xb8, 0x33, 0xdb, 0x2d, 0x2c, 0xd5, + 0x4f, 0xd1, 0xe2, 0xd2, 0xf0, 0xa2, 0xc4, 0xcc, 0x64, 0xcb, 0x4c, 0x81, 0x4c, 0xe7, 0x42, 0x9a, 0x93, 0x58, 0xe1, + 0xa0, 0x9f, 0xc5, 0x42, 0xb2, 0x5b, 0xeb, 0x61, 0x8f, 0x67, 0x55, 0x0d, 0x56, 0x18, 0x75, 0xb6, 0x5a, 0xa4, 0x13, + 0xa9, 0xd8, 0x3e, 0x50, 0x87, 0xc2, 0x7d, 0xc7, 0x41, 0xa7, 0x46, 0xca, 0xcb, 0x5f, 0x47, 0x6a, 0x78, 0xc4, 0x5f, + 0x1b, 0x77, 0x90, 0xb4, 0x6c, 0xd8, 0xd1, 0xe6, 0xb6, 0xb9, 0x0c, 0x8a, 0x51, 0xe3, 0xb0, 0x2a, 0x2d, 0xdd, 0x46, + 0xe4, 0x2b, 0xd4, 0x8c, 0xec, 0x9b, 0x30, 0x11, 0xb1, 0xa4, 0x87, 0x78, 0x8d, 0x84, 0x49, 0x4a, 0x1f, 0xc5, 0x16, + 0xe9, 0x85, 0xa2, 0x2b, 0xd3, 0x6e, 0xd3, 0x9d, 0xab, 0x38, 0x97, 0xe9, 0x29, 0x79, 0x16, 0x30, 0x85, 0x95, 0xe8, + 0x4a, 0x82, 0xc9, 0x98, 0x67, 0x6f, 0xfc, 0x94, 0xf4, 0xdc, 0x23, 0x21, 0x9a, 0x7d, 0xa1, 0xb4, 0x52, 0x3e, 0x89, + 0x11, 0x1f, 0xe9, 0xc8, 0x31, 0x7c, 0xe5, 0x65, 0xf8, 0xde, 0x56, 0x65, 0xf9, 0x6c, 0xe7, 0x33, 0x13, 0x65, 0x72, + 0x54, 0xed, 0x42, 0xda, 0x40, 0x6c, 0x0d, 0x10, 0xcf, 0xd2, 0xb1, 0x04, 0xa5, 0xd1, 0x63, 0xb0, 0xf3, 0x79, 0xb5, + 0x08, 0x42, 0x6d, 0x92, 0xee, 0x32, 0x37, 0x01, 0x2e, 0xc8, 0x51, 0x7a, 0x9b, 0xe8, 0xee, 0xfe, 0xcc, 0x01, 0xdd, + 0x7d, 0x33, 0x41, 0xa3, 0xd9, 0x65, 0x8b, 0xa0, 0xc4, 0xbf, 0x8b, 0xa6, 0xcd, 0x48, 0xa4, 0x42, 0xbc, 0x31, 0x1b, + 0xc0, 0x6c, 0xa6, 0x3d, 0x53, 0xe9, 0x40, 0xa4, 0xf8, 0x0d, 0x68, 0x23, 0x2b, 0xa1, 0x41, 0x20, 0x51, 0x3f, 0x8d, + 0x59, 0x13, 0x33, 0x3d, 0xce, 0x7f, 0x92, 0x3d, 0x27, 0x83, 0x04, 0x72, 0x25, 0xd8, 0x3d, 0x75, 0x46, 0x14, 0x67, + 0x3d, 0x69, 0xc5, 0xf6, 0xf4, 0x6b, 0x87, 0xad, 0x33, 0xfb, 0xe0, 0x5c, 0x68, 0xb1, 0x13, 0x41, 0x7f, 0xb9, 0x9d, + 0xe8, 0x47, 0x80, 0xc1, 0xd0, 0x15, 0xe6, 0x3f, 0x93, 0x5e, 0x78, 0xc5, 0x84, 0x61, 0xd7, 0x5d, 0xf5, 0x76, 0x78, + 0xec, 0x4c, 0x4a, 0xe6, 0x93, 0x9f, 0x07, 0xee, 0xbe, 0x1d, 0xda, 0x29, 0x63, 0x6e, 0x63, 0xe4, 0x13, 0x0c, 0x60, + 0xb6, 0xab, 0x1b, 0xd5, 0x8c, 0x8a, 0x48, 0x22, 0xa6, 0x36, 0x6e, 0x1c, 0xd3, 0x36, 0x3e, 0xad, 0xf7, 0xfe, 0xfb, + 0x2d, 0x3a, 0xa0, 0x8c, 0x4a, 0x13, 0xcf, 0x57, 0xe9, 0xc6, 0x41, 0xf7, 0x57, 0x1d, 0x93, 0xba, 0xf3, 0x7e, 0x2e, + 0xb4, 0xaa, 0x8d, 0xa8, 0xf3, 0x91, 0x76, 0xd0, 0x74, 0x7e, 0xa6, 0x11, 0xb8, 0xd6, 0x7f, 0xad, 0x06, 0x58, 0xf4, + 0x2a, 0x90, 0xa2, 0xab, 0x38, 0x76, 0xad, 0xd4, 0x5a, 0x78, 0x07, 0x5f, 0x25, 0xb9, 0x55, 0xf8, 0xab, 0x17, 0x96, + 0xa7, 0xf7, 0xc4, 0x3b, 0xbf, 0xcc, 0x85, 0xd2, 0xad, 0xcb, 0xc2, 0x27, 0xe5, 0xaf, 0x83, 0x53, 0x30, 0xbd, 0xa1, + 0x68, 0x8c, 0x94, 0x5d, 0xbc, 0x5f, 0xc8, 0x48, 0x28, 0x10, 0xff, 0x28, 0x18, 0x1a, 0xa5, 0x6d, 0xa9, 0x8e, 0xb1, + 0x7d, 0xac, 0xa6, 0x7c, 0x56, 0xbb, 0xac, 0xbd, 0x24, 0xe1, 0xe4, 0x75, 0xf0, 0x87, 0x1f, 0x71, 0xcd, 0x03, 0xac, + 0xb3, 0xd6, 0xbd, 0x70, 0xce, 0xeb, 0xe1, 0x03, 0xcd, 0x7c, 0x28, 0xb6, 0x27, 0x5a, 0x9f, 0xb0, 0xf5, 0x3c, 0x5b, + 0x70, 0xe7, 0x47, 0x15, 0x52, 0x0d, 0xd7, 0x19, 0x9f, 0x99, 0xfd, 0x7d, 0x18, 0xec, 0x07, 0xdd, 0xf9, 0xf8, 0x1d, + 0x9d, 0x83, 0xed, 0xcd, 0xc0, 0x22, 0xad, 0x73, 0xec, 0xc8, 0xb5, 0xc6, 0xe3, 0x3e, 0x05, 0x92, 0xc6, 0xea, 0x7a, + 0x75, 0xea, 0x84, 0xca, 0x37, 0x8a, 0xa0, 0x63, 0x27, 0xd1, 0xcd, 0x72, 0x11, 0x25, 0x12, 0xe4, 0x6f, 0x83, 0x42, + 0x31, 0x1c, 0x32, 0xe1, 0x51, 0xdc, 0x9b, 0x20, 0x30, 0xaf, 0x95, 0x52, 0x88, 0xd5, 0x8e, 0xae, 0x57, 0xe8, 0x11, + 0x70, 0xb0, 0xa4, 0x4a, 0xda, 0x8c, 0x44, 0x5d, 0xca, 0x3e, 0xac, 0xe9, 0xfe, 0xd0, 0xc8, 0x0e, 0xe3, 0xaf, 0x66, + 0x2b, 0xb5, 0x68, 0xfe, 0x45, 0x09, 0xc7, 0x4a, 0x84, 0xcd, 0x0c, 0xb8, 0x8e, 0xfe, 0x8f, 0xa4, 0xd0, 0xa1, 0x6b, + 0x01, 0x50, 0xfb, 0x13, 0x65, 0x83, 0xa2, 0x18, 0x01, 0xda, 0x8f, 0xaa, 0x6c, 0xa4, 0x3e, 0xe5, 0x05, 0xb3, 0xeb, + 0xb6, 0x33, 0xcb, 0x45, 0x30, 0xd6, 0x66, 0x1b, 0x00, 0xc2, 0x9a, 0x0b, 0x68, 0x20, 0x8a, 0x46, 0x51, 0xb6, 0xf4, + 0x06, 0xbb, 0xc5, 0xd7, 0x10, 0xad, 0x7d, 0x4c, 0x28, 0xfa, 0x0d, 0xad, 0xa4, 0x1a, 0x59, 0xe6, 0xfb, 0x57, 0x16, + 0xcc, 0xb5, 0x38, 0x7a, 0x6b, 0xcf, 0xad, 0x6c, 0xd1, 0x79, 0x6f, 0x57, 0xd3, 0x3f, 0xb3, 0xab, 0xe1, 0x3d, 0xdb, + 0x80, 0x19, 0x5e, 0xd8, 0x92, 0x5f, 0x9f, 0xd6, 0x4d, 0x38, 0xfe, 0x91, 0x55, 0x8c, 0x0a, 0x57, 0x10, 0x2c, 0xaa, + 0x46, 0x9c, 0x92, 0x7f, 0xec, 0x03, 0x05, 0xda, 0xc3, 0x8a, 0x48, 0x85, 0x51, 0xe5, 0xa1, 0x12, 0xd9, 0x53, 0xf1, + 0xab, 0x36, 0x90, 0xc1, 0x26, 0x1c, 0x4a, 0x06, 0x6e, 0x6a, 0xd7, 0x26, 0x31, 0x3b, 0x73, 0xeb, 0x3f, 0x65, 0x05, + 0xab, 0x61, 0xc4, 0x12, 0xf5, 0x21, 0x4e, 0xf4, 0xb2, 0xea, 0x11, 0x1e, 0x4d, 0x8b, 0xdd, 0x43, 0x90, 0x5a, 0x16, + 0x09, 0xbf, 0x6f, 0x19, 0xa0, 0x46, 0x30, 0xa1, 0x9a, 0x67, 0x65, 0x1c, 0x42, 0x2e, 0xd3, 0xe3, 0x4c, 0x10, 0xa0, + 0x96, 0xe5, 0x3a, 0x63, 0x0d, 0x47, 0x5e, 0x0b, 0xb4, 0xa4, 0x16, 0x48, 0xae, 0x51, 0x36, 0x2c, 0xb8, 0xfe, 0x5c, + 0x16, 0x8d, 0x42, 0x83, 0x86, 0xd8, 0x91, 0xe7, 0x24, 0x0f, 0x73, 0x8e, 0xa6, 0x48, 0x6e, 0xf3, 0x57, 0xa2, 0xd5, + 0xcc, 0xd6, 0xe2, 0x74, 0xf1, 0x6a, 0x33, 0xc2, 0x76, 0x47, 0xe3, 0x94, 0x69, 0xe2, 0x78, 0x8c, 0xb6, 0x07, 0xda, + 0xdc, 0x69, 0xc9, 0x8d, 0x8b, 0xff, 0x25, 0xb2, 0xe8, 0xe6, 0xf1, 0x02, 0xc1, 0x5c, 0xee, 0x62, 0x94, 0x19, 0x6e, + 0x8e, 0x5d, 0x60, 0xc3, 0xfc, 0x27, 0x2e, 0xba, 0x2a, 0xe3, 0xc5, 0xaa, 0xd5, 0x88, 0x21, 0x4e, 0xac, 0xcf, 0xf6, + 0x11, 0xb1, 0x1e, 0x91, 0x70, 0xa4, 0xac, 0xb3, 0x51, 0x99, 0xc6, 0xba, 0x0c, 0xbe, 0x79, 0x32, 0x5b, 0x42, 0x10, + 0x18, 0x36, 0x5f, 0x6c, 0x7f, 0x02, 0x2b, 0x2e, 0x24, 0xa1, 0x95, 0xf0, 0xc2, 0xbf, 0x7e, 0x87, 0x57, 0x34, 0x2b, + 0x2b, 0x5d, 0xe3, 0x72, 0x62, 0xa1, 0xd1, 0x30, 0xa7, 0x8e, 0x19, 0x8f, 0x09, 0x8b, 0xe9, 0xf9, 0x61, 0x49, 0x15, + 0x01, 0x0d, 0xcd, 0x39, 0x77, 0xa4, 0xcd, 0x24, 0xb8, 0x8d, 0xc9, 0xb1, 0x14, 0xa0, 0x2b, 0x74, 0x99, 0xdd, 0x6d, + 0x67, 0x18, 0x07, 0x43, 0x6e, 0x66, 0x00, 0xc2, 0x95, 0x08, 0x6a, 0x09, 0x78, 0x56, 0xec, 0x6b, 0xd1, 0x39, 0x98, + 0x8b, 0x02, 0xf5, 0x5e, 0x07, 0xfd, 0x0d, 0x12, 0x2e, 0x11, 0xad, 0x14, 0x38, 0x19, 0xd0, 0x65, 0xda, 0x55, 0x68, + 0x5e, 0x22, 0xc4, 0x58, 0x03, 0x92, 0x5a, 0xe3, 0x97, 0x8b, 0x08, 0xf7, 0xbc, 0x9f, 0x0d, 0x67, 0xdd, 0xa0, 0x00, + 0xf2, 0x28, 0x7f, 0x7e, 0x6f, 0x89, 0xfe, 0x20, 0x27, 0x20, 0x71, 0xc1, 0x33, 0x11, 0xa3, 0x9d, 0x53, 0x83, 0x2d, + 0x73, 0x31, 0x6a, 0x70, 0x5b, 0xa3, 0x64, 0x29, 0x26, 0xdb, 0x48, 0xfb, 0x1c, 0x39, 0x23, 0x19, 0x90, 0xd5, 0x99, + 0x84, 0x0e, 0x5d, 0x3d, 0x99, 0xca, 0xed, 0x28, 0xcc, 0x4d, 0x33, 0xd0, 0x67, 0x3b, 0x7c, 0x99, 0xfa, 0x9f, 0x36, + 0x57, 0xd6, 0xf4, 0x3d, 0x33, 0x19, 0xb1, 0x94, 0xe8, 0x4b, 0x06, 0xb3, 0x4f, 0xfb, 0x2e, 0xdf, 0xc1, 0x62, 0x7d, + 0x05, 0x7b, 0x55, 0xb1, 0x51, 0x1f, 0x5a, 0xcd, 0x98, 0x24, 0x8e, 0x25, 0xb7, 0x06, 0x25, 0x05, 0x95, 0x79, 0x8d, + 0x1a, 0x52, 0x31, 0xad, 0xb5, 0xb4, 0x13, 0xff, 0x17, 0x57, 0xcc, 0x4c, 0x0c, 0xfc, 0x18, 0x7b, 0xec, 0xe3, 0x47, + 0x6c, 0xbc, 0xdd, 0xbe, 0xe7, 0x0c, 0x1d, 0x93, 0x07, 0x08, 0x14, 0x3c, 0xf3, 0xd2, 0x25, 0xcd, 0xb9, 0xb5, 0x61, + 0xcd, 0x9a, 0x5a, 0xf9, 0xcf, 0xac, 0x9a, 0x0b, 0x1b, 0xfb, 0x44, 0xf8, 0x3a, 0xad, 0x5d, 0xc7, 0x3e, 0x84, 0x42, + 0x05, 0xf9, 0x42, 0xea, 0x60, 0xe6, 0xe2, 0x4d, 0x65, 0xf0, 0xb9, 0x2d, 0x8f, 0x92, 0x80, 0x21, 0x67, 0x23, 0x9f, + 0xa8, 0x15, 0xc1, 0x47, 0x8f, 0x08, 0x5f, 0xcc, 0xb6, 0x45, 0x14, 0x5c, 0x19, 0x25, 0xbc, 0xcb, 0xc8, 0x27, 0x37, + 0x5a, 0x7c, 0x35, 0x2d, 0xcb, 0xb3, 0x43, 0x6e, 0x27, 0xea, 0xfb, 0x41, 0xec, 0x82, 0xa0, 0xad, 0xc6, 0x82, 0x20, + 0x6b, 0xea, 0xbc, 0x49, 0x45, 0x82, 0xdf, 0x5a, 0x27, 0x9d, 0xd7, 0x89, 0x67, 0xdc, 0xe5, 0x3e, 0x24, 0x62, 0x04, + 0x6e, 0x8b, 0xee, 0xb7, 0x41, 0x94, 0x71, 0xe9, 0xe8, 0x64, 0x82, 0x47, 0x5d, 0xe2, 0xa4, 0xda, 0xf5, 0x7a, 0xd4, + 0xf6, 0x31, 0x1f, 0xfa, 0x01, 0xa8, 0x74, 0x1d, 0xe8, 0xbf, 0xa5, 0x9f, 0x64, 0x8e, 0xdc, 0xed, 0xf3, 0x41, 0x73, + 0x0b, 0xf4, 0x1d, 0xb5, 0x89, 0xa2, 0xee, 0x4b, 0xfc, 0x8c, 0x1c, 0xff, 0x97, 0x2a, 0x2b, 0x18, 0x02, 0x93, 0x99, + 0x68, 0x54, 0x5b, 0x90, 0xce, 0xc2, 0xe0, 0x70, 0xc7, 0xd7, 0x1a, 0x39, 0x60, 0x8b, 0x79, 0xc5, 0xa1, 0x1e, 0x34, + 0x82, 0x97, 0x50, 0x20, 0x86, 0x7b, 0x67, 0x68, 0x0c, 0x7a, 0x50, 0xec, 0x10, 0x06, 0x8a, 0x41, 0xcb, 0x52, 0x08, + 0xb4, 0x09, 0xa9, 0x76, 0xbf, 0x37, 0x47, 0xff, 0xd4, 0xef, 0xc8, 0x28, 0xa2, 0x51, 0xef, 0x1c, 0x24, 0x20, 0xe8, + 0x15, 0x07, 0x32, 0x50, 0xde, 0x2e, 0x89, 0x11, 0xcb, 0x78, 0x1c, 0xe4, 0xea, 0xe0, 0xf1, 0x4a, 0xc8, 0x99, 0x59, + 0x21, 0xe4, 0x18, 0xc0, 0xb0, 0xf6, 0xc0, 0xbd, 0x1c, 0x37, 0xb0, 0x09, 0x78, 0x26, 0x57, 0xd4, 0xb3, 0x59, 0x7d, + 0x3e, 0xfe, 0xbb, 0xbc, 0xb9, 0x6c, 0x71, 0x3f, 0x95, 0x8c, 0xc7, 0x1a, 0x4d, 0x15, 0xf8, 0xf0, 0x97, 0x0f, 0x9e, + 0x8f, 0x45, 0x91, 0x3e, 0x7d, 0x22, 0x81, 0x13, 0xa2, 0xeb, 0x92, 0xdf, 0x4c, 0x71, 0xac, 0x29, 0xa0, 0x86, 0xf3, + 0xb0, 0x73, 0x41, 0x78, 0x9c, 0xb0, 0x86, 0x8b, 0xc2, 0x1c, 0x76, 0x98, 0x60, 0x23, 0x8c, 0x6e, 0xa8, 0x21, 0x96, + 0xb4, 0x46, 0x7c, 0x3b, 0xc0, 0x25, 0xf8, 0x71, 0xa1, 0x95, 0x55, 0x09, 0xf1, 0xc7, 0x26, 0x9d, 0x01, 0x72, 0x89, + 0xb5, 0x7e, 0xca, 0xd3, 0xf9, 0x5b, 0x6d, 0x68, 0xe7, 0x79, 0xba, 0xb9, 0xb7, 0xe6, 0x70, 0xa6, 0x72, 0xe5, 0x24, + 0xa3, 0x18, 0x91, 0x1e, 0x95, 0x33, 0xf9, 0x2f, 0x0e, 0x13, 0x42, 0x66, 0x71, 0x74, 0xaf, 0x04, 0x46, 0xfb, 0x4a, + 0xd7, 0x33, 0xfe, 0x25, 0x22, 0x3f, 0x1b, 0xcd, 0x58, 0xbc, 0x42, 0xc9, 0xd5, 0xaa, 0x56, 0xfb, 0x5c, 0x8b, 0x5e, + 0x2a, 0x1a, 0xc7, 0x18, 0x35, 0xd7, 0x8c, 0x5f, 0x8c, 0x8d, 0x21, 0xd2, 0xe8, 0xc6, 0x6c, 0xfb, 0xa3, 0x25, 0xba, + 0xef, 0x7a, 0x22, 0x09, 0x9a, 0x6f, 0x10, 0x28, 0xc0, 0x2e, 0x26, 0x18, 0x92, 0xab, 0x30, 0x6b, 0x9a, 0xe5, 0x79, + 0x0a, 0x75, 0xad, 0xd9, 0x0b, 0xca, 0xf7, 0xba, 0xcb, 0xea, 0x52, 0xb6, 0x63, 0x5d, 0xf7, 0x28, 0x48, 0x1c, 0x35, + 0xce, 0x90, 0x98, 0x55, 0xcf, 0x92, 0x32, 0x2c, 0x11, 0xd2, 0x8a, 0x8b, 0x65, 0x4b, 0x9b, 0x4c, 0x61, 0x20, 0x6f, + 0x44, 0x37, 0x9d, 0x53, 0x21, 0x82, 0xdd, 0x59, 0x45, 0xa2, 0x4c, 0xdb, 0xb2, 0x8d, 0x16, 0x32, 0xf7, 0x6d, 0x28, + 0x74, 0x09, 0xf1, 0x83, 0xb7, 0x17, 0xee, 0x5e, 0x32, 0x8e, 0x10, 0xc6, 0x46, 0xc1, 0xc5, 0x47, 0xbd, 0xa4, 0xb6, + 0x72, 0x28, 0xe8, 0x9c, 0x6d, 0x96, 0x3e, 0xfe, 0x23, 0xab, 0xa6, 0xbc, 0xde, 0xb0, 0x52, 0xd9, 0xb0, 0x50, 0xc9, + 0x55, 0x2b, 0xf8, 0xa2, 0xd4, 0x8a, 0x09, 0x39, 0xf6, 0xd0, 0xa2, 0xf1, 0x80, 0x36, 0x51, 0x29, 0x67, 0x84, 0xe4, + 0xce, 0x9a, 0x66, 0xba, 0x06, 0xbb, 0x9b, 0x12, 0x60, 0xc7, 0x26, 0x53, 0xbd, 0x58, 0x78, 0x4a, 0x22, 0x0d, 0xdd, + 0x0a, 0xb9, 0xf3, 0x55, 0xee, 0x40, 0x21, 0x86, 0x75, 0x80, 0x85, 0xb3, 0x92, 0x81, 0xb0, 0x7d, 0x38, 0x8c, 0x1f, + 0xa3, 0xda, 0x02, 0xc6, 0x87, 0x10, 0xea, 0x7b, 0x03, 0x0c, 0x14, 0x1d, 0x9d, 0xd1, 0xe4, 0x2e, 0x67, 0xc8, 0xa0, + 0xef, 0x7d, 0x45, 0x09, 0xc9, 0x33, 0xf2, 0x7a, 0x2a, 0x59, 0x5c, 0x9d, 0x37, 0xad, 0xd0, 0x16, 0xeb, 0x77, 0xdf, + 0xda, 0x9a, 0xa2, 0xef, 0x88, 0x6b, 0x01, 0xd4, 0x0e, 0xd1, 0x77, 0x47, 0xd1, 0x9a, 0x76, 0xf3, 0x74, 0x99, 0x3f, + 0xc7, 0xbe, 0x9b, 0x08, 0x7f, 0xfa, 0x76, 0xdd, 0xf0, 0xf5, 0x32, 0xa0, 0xdc, 0x7b, 0xa3, 0xe0, 0x1c, 0xf5, 0x8e, + 0x0a, 0x44, 0x30, 0xc9, 0x9c, 0xee, 0xb4, 0x11, 0x74, 0x13, 0x91, 0x59, 0x3e, 0x5c, 0xfa, 0xcd, 0xfe, 0x57, 0x10, + 0xac, 0x41, 0x84, 0x0b, 0x7f, 0xd3, 0x20, 0x0e, 0x5e, 0x8a, 0x90, 0x74, 0x45, 0x30, 0xd1, 0x51, 0x41, 0x6c, 0xc5, + 0x76, 0xf1, 0xda, 0xac, 0x21, 0x10, 0x71, 0x0e, 0x2e, 0x9f, 0x59, 0xa1, 0x2c, 0xaa, 0xd7, 0xbe, 0x30, 0x44, 0xe5, + 0x66, 0x1f, 0xf4, 0xbf, 0x49, 0x16, 0xbe, 0x75, 0x70, 0x60, 0x65, 0x64, 0x2d, 0xb1, 0x3b, 0x6d, 0x2a, 0xb7, 0x82, + 0x1d, 0xb7, 0x73, 0xbd, 0xaf, 0x6f, 0x40, 0x69, 0xb4, 0xe5, 0x35, 0xbb, 0x4d, 0x99, 0xa9, 0x31, 0x3c, 0x66, 0xb5, + 0x68, 0x80, 0x09, 0x77, 0xd8, 0x2f, 0x2c, 0xd9, 0x3b, 0x98, 0x8a, 0xce, 0xfb, 0xf6, 0xcf, 0xcd, 0x14, 0x24, 0x4c, + 0xc7, 0x1c, 0x72, 0x07, 0x31, 0x63, 0x7a, 0x2a, 0x68, 0xbb, 0x8e, 0xf8, 0x5d, 0x24, 0x19, 0xf0, 0xbf, 0x2a, 0x65, + 0x4f, 0x23, 0x63, 0x46, 0xc8, 0xe8, 0xb6, 0xa4, 0x14, 0xe1, 0x5a, 0x1a, 0x0c, 0x8c, 0x11, 0xcc, 0xa7, 0x44, 0x0b, + 0xb1, 0xec, 0x0e, 0x2b, 0x12, 0xfb, 0x6c, 0xab, 0xd9, 0xdb, 0xc5, 0x7a, 0x4a, 0xd0, 0xb2, 0xde, 0x8b, 0x57, 0xaa, + 0xf3, 0x2e, 0x3c, 0xba, 0x6e, 0x37, 0x14, 0xc4, 0xf1, 0xd2, 0x6a, 0x6f, 0x81, 0x07, 0xed, 0xf3, 0x9f, 0x37, 0x8a, + 0x17, 0xb7, 0xca, 0x97, 0x10, 0xfc, 0xe0, 0x69, 0xb2, 0x18, 0xca, 0x20, 0x17, 0x2b, 0x17, 0x3c, 0x90, 0x2a, 0x6a, + 0xbb, 0xf5, 0x0a, 0x62, 0xf3, 0x7c, 0xf1, 0x69, 0x07, 0xc3, 0x33, 0x05, 0x1d, 0xec, 0x5f, 0xb6, 0xf5, 0x1b, 0xa0, + 0x75, 0x93, 0x21, 0xff, 0xae, 0x35, 0x1b, 0x64, 0x04, 0x1f, 0xbf, 0xda, 0xfe, 0x62, 0x0c, 0xd5, 0x9e, 0xd1, 0x32, + 0xec, 0x96, 0xbf, 0x47, 0x2e, 0xc1, 0xbe, 0x2b, 0x03, 0xc0, 0x5c, 0x2e, 0x63, 0xc4, 0xe7, 0xe4, 0x1b, 0x84, 0x01, + 0x10, 0xf9, 0xf5, 0x77, 0xfd, 0xf8, 0xd0, 0xdc, 0xad, 0x3c, 0x76, 0xec, 0x59, 0x22, 0x06, 0xc5, 0x3a, 0x66, 0x3b, + 0xf6, 0x21, 0x2b, 0x1e, 0xaa, 0x44, 0x74, 0xac, 0xf9, 0x90, 0xb9, 0x4f, 0xf1, 0xb0, 0xbd, 0xf3, 0xfd, 0x20, 0x8a, + 0x2c, 0x19, 0x25, 0xd9, 0x5f, 0x68, 0xe9, 0x7e, 0x7c, 0x41, 0x33, 0xa8, 0xdb, 0x13, 0xe4, 0x55, 0x04, 0x10, 0x8f, + 0xc1, 0xe0, 0xbf, 0x2e, 0xf3, 0x9e, 0x4b, 0x0b, 0xc0, 0xcf, 0x29, 0xbb, 0x8d, 0x79, 0x3e, 0xd7, 0x04, 0x82, 0x3e, + 0xeb, 0x9e, 0xe8, 0x11, 0x91, 0xe9, 0xa7, 0xb3, 0x63, 0x00, 0x72, 0x1a, 0x45, 0x3c, 0x62, 0xab, 0x6f, 0x3f, 0xb1, + 0xaa, 0x8b, 0xfb, 0xa3, 0x50, 0xdd, 0xa2, 0xa0, 0x33, 0x88, 0xd4, 0x2e, 0xce, 0x64, 0x24, 0x3b, 0x32, 0xcd, 0xd0, + 0x11, 0xed, 0xa5, 0x62, 0x4a, 0xc6, 0xb0, 0x1c, 0x23, 0xb6, 0x88, 0x23, 0x67, 0xd3, 0xc9, 0x12, 0x87, 0x61, 0x89, + 0xf3, 0x3e, 0x75, 0x08, 0x7a, 0xa5, 0x8a, 0x52, 0xbb, 0x18, 0xe7, 0xdb, 0x21, 0x8b, 0x06, 0xe0, 0x52, 0xe3, 0x1d, + 0x82, 0xe7, 0xc3, 0xab, 0xf1, 0x99, 0x2b, 0x72, 0x2c, 0x5c, 0xcf, 0x27, 0xb2, 0x63, 0x3c, 0x33, 0x90, 0x18, 0xc7, + 0x61, 0xae, 0xf3, 0xa0, 0x0e, 0xc1, 0x2e, 0x26, 0x68, 0xa4, 0xe7, 0x89, 0x7b, 0x30, 0xed, 0x68, 0x3d, 0x1b, 0xcd, + 0x1c, 0x58, 0x05, 0xc6, 0xfe, 0x01, 0x6d, 0x55, 0x0c, 0xc3, 0x23, 0xe3, 0x20, 0x0c, 0x67, 0xeb, 0xad, 0x4c, 0xaf, + 0x19, 0x42, 0xcb, 0x63, 0x65, 0x92, 0x81, 0xa6, 0x8d, 0x0f, 0xa3, 0xae, 0xf1, 0x9b, 0x7a, 0x02, 0xb4, 0x16, 0x9f, + 0x6d, 0xf1, 0xf1, 0xe2, 0xa0, 0x70, 0xe1, 0x92, 0xd1, 0x5f, 0x6c, 0x4d, 0x58, 0xd2, 0x9d, 0x2e, 0xce, 0xdc, 0x95, + 0xaf, 0xd3, 0x99, 0x34, 0xb7, 0x94, 0x65, 0x53, 0x79, 0xa0, 0xdb, 0x6e, 0x66, 0xff, 0xc7, 0x7f, 0x19, 0x3e, 0x30, + 0x74, 0x91, 0xb0, 0x09, 0xf1, 0x2d, 0x6a, 0xfc, 0x8b, 0x8f, 0xc6, 0x27, 0x63, 0x58, 0x0f, 0x33, 0x99, 0x3b, 0xcc, + 0x73, 0x0c, 0x7b, 0x8f, 0x8f, 0x7d, 0x18, 0x20, 0x26, 0x5f, 0x4d, 0x15, 0x16, 0x71, 0x8f, 0x81, 0xca, 0xd5, 0x70, + 0xec, 0x20, 0x42, 0xde, 0xd4, 0x3e, 0x2b, 0xf6, 0x9f, 0x09, 0xe2, 0xe1, 0xd9, 0x8e, 0xc2, 0x24, 0xc8, 0x37, 0xb3, + 0x21, 0x6f, 0x7d, 0x4d, 0xa5, 0x83, 0x3c, 0x2e, 0xb1, 0x68, 0xed, 0x2e, 0x85, 0xa1, 0x85, 0x2e, 0x42, 0x4b, 0x83, + 0xa5, 0x50, 0xf7, 0x4a, 0x83, 0x5c, 0x98, 0x3a, 0x17, 0x78, 0xf1, 0x19, 0xe0, 0x18, 0xdf, 0x1f, 0x0f, 0xa4, 0x2b, + 0x26, 0x73, 0x39, 0xc1, 0xfb, 0xb4, 0x69, 0xdd, 0x19, 0x28, 0x40, 0xda, 0xa7, 0xc2, 0x55, 0xbb, 0x0c, 0xb3, 0x2e, + 0x24, 0x73, 0x22, 0x4c, 0x66, 0xb2, 0x8b, 0xb0, 0xbf, 0x94, 0xcd, 0x90, 0xe2, 0x13, 0x6e, 0x72, 0x1b, 0xf8, 0xda, + 0x11, 0x1d, 0x05, 0x2d, 0xda, 0x36, 0xe5, 0x5c, 0xec, 0x04, 0x9d, 0xf0, 0xe8, 0x26, 0x78, 0xd9, 0xab, 0xd7, 0x38, + 0x6f, 0xe9, 0x75, 0x6f, 0x17, 0xf7, 0xde, 0xdd, 0x06, 0x18, 0xf8, 0x02, 0x39, 0xf3, 0x98, 0x19, 0xc5, 0xf1, 0x5f, + 0xa7, 0xe0, 0xec, 0x84, 0xcf, 0x08, 0x18, 0x40, 0x42, 0xb6, 0x61, 0xb1, 0x75, 0x80, 0x7d, 0xfc, 0x10, 0xd7, 0xa8, + 0x04, 0x8c, 0x54, 0x69, 0x70, 0x9c, 0x99, 0xaf, 0x59, 0x86, 0x64, 0x58, 0x2e, 0xa5, 0x87, 0x21, 0x66, 0x0e, 0xb8, + 0xb3, 0x5d, 0xf9, 0x56, 0x7c, 0x8e, 0x1e, 0xe5, 0x24, 0xbd, 0x8b, 0x05, 0x5a, 0x64, 0xc4, 0x07, 0x0a, 0x35, 0xc8, + 0x34, 0x31, 0xf8, 0x8c, 0x50, 0x40, 0x62, 0x86, 0x1b, 0x21, 0x50, 0x42, 0x9e, 0x2d, 0xbf, 0x8d, 0xa8, 0xe0, 0x44, + 0x28, 0x62, 0xd2, 0x12, 0xa9, 0x8e, 0x04, 0x99, 0x99, 0x21, 0xc9, 0xf4, 0x98, 0x1b, 0xd3, 0x81, 0x79, 0x01, 0xe6, + 0x54, 0xa6, 0x07, 0x90, 0x4f, 0xc6, 0x98, 0x55, 0x44, 0x98, 0xe9, 0xae, 0xbc, 0x48, 0x2a, 0x1a, 0xac, 0x61, 0x2e, + 0x9a, 0x8b, 0x25, 0x6a, 0xd7, 0x09, 0xe5, 0x14, 0x27, 0x57, 0xed, 0x82, 0xb4, 0x33, 0x98, 0xf2, 0x21, 0xe5, 0x9b, + 0x0e, 0x42, 0x5b, 0x87, 0xce, 0xaa, 0x41, 0xd8, 0x91, 0xc4, 0x26, 0x08, 0x09, 0xc9, 0x72, 0x07, 0xd2, 0x50, 0x26, + 0x42, 0x42, 0xe8, 0x24, 0x0d, 0xad, 0xd3, 0x35, 0x13, 0xf1, 0x69, 0x85, 0xc5, 0x36, 0x88, 0xc4, 0x12, 0x8d, 0x7d, + 0xdf, 0xca, 0x63, 0x78, 0xc1, 0x29, 0x2c, 0xb6, 0xc8, 0xaa, 0xc0, 0x18, 0x12, 0x09, 0xb3, 0xd5, 0x09, 0x53, 0xe7, + 0xf5, 0xfd, 0xcd, 0x80, 0xb3, 0xa7, 0x09, 0x99, 0x08, 0xc6, 0x15, 0x5d, 0x94, 0x2a, 0xcd, 0x17, 0x25, 0x0e, 0x95, + 0x1a, 0xbe, 0xba, 0xaa, 0xbc, 0x19, 0x90, 0x77, 0x36, 0xd5, 0x33, 0xde, 0xee, 0x87, 0x7a, 0xe2, 0xac, 0x0a, 0xc1, + 0x29, 0xaa, 0x4e, 0xf7, 0xff, 0x3d, 0x5f, 0x55, 0x12, 0xe5, 0x13, 0x25, 0x65, 0xd0, 0x5a, 0xdc, 0x16, 0x5b, 0x41, + 0xb9, 0x49, 0xd3, 0x29, 0x5f, 0x07, 0x15, 0xab, 0xab, 0x82, 0x81, 0x2e, 0xd8, 0x11, 0x10, 0x37, 0x83, 0x2e, 0x96, + 0x36, 0x96, 0xf2, 0x03, 0x84, 0xbe, 0x7e, 0xdb, 0x19, 0xa3, 0xed, 0xbd, 0xcc, 0x30, 0xa4, 0xf5, 0x33, 0x73, 0x2e, + 0x6d, 0x1e, 0x91, 0x20, 0x78, 0x0b, 0x9d, 0xb2, 0x7b, 0x25, 0x2f, 0x67, 0xae, 0x3c, 0x7b, 0xbc, 0x5b, 0xad, 0x80, + 0xdb, 0x55, 0x1a, 0x4e, 0x8b, 0xe3, 0xd9, 0x87, 0x4b, 0xe7, 0x11, 0x3c, 0x92, 0x32, 0xf0, 0x5e, 0x73, 0xa4, 0x1b, + 0x22, 0x1d, 0xf5, 0xbc, 0x57, 0xe0, 0x9c, 0x93, 0x45, 0x51, 0xf2, 0x26, 0x68, 0x7a, 0x7e, 0x17, 0x98, 0x47, 0x54, + 0x1c, 0x83, 0xaa, 0x50, 0x33, 0xcd, 0xaf, 0x2c, 0x04, 0x2e, 0x68, 0xae, 0xe6, 0xd3, 0xca, 0x94, 0x07, 0x52, 0x15, + 0xcb, 0x95, 0x36, 0xa5, 0x1c, 0x89, 0x0b, 0xba, 0xd9, 0x64, 0xf5, 0xc0, 0x20, 0x62, 0xa3, 0xff, 0x82, 0xc8, 0x23, + 0xc2, 0x5f, 0x15, 0xa5, 0x48, 0x87, 0x19, 0x59, 0x29, 0x06, 0x2c, 0x54, 0x40, 0x5a, 0x79, 0x37, 0x60, 0xcb, 0x6f, + 0x28, 0x24, 0x52, 0x6f, 0xeb, 0x3b, 0x24, 0x8b, 0xf8, 0xad, 0x93, 0xcd, 0xc6, 0xc4, 0x92, 0xcf, 0xb7, 0x67, 0xa2, + 0x21, 0x72, 0x32, 0x3b, 0xfa, 0x75, 0x1f, 0x30, 0xad, 0x18, 0x2e, 0x9d, 0x9d, 0xf2, 0x58, 0x1f, 0x5e, 0x06, 0x9b, + 0x4f, 0xc5, 0x98, 0x27, 0x35, 0x3c, 0x63, 0x99, 0x71, 0xab, 0x39, 0xd1, 0x15, 0xb4, 0xc1, 0x5e, 0x82, 0x2d, 0xf5, + 0xbc, 0x95, 0xe1, 0x63, 0xc2, 0x5f, 0x23, 0x7c, 0x02, 0x8e, 0x81, 0xf7, 0x9a, 0xa9, 0x74, 0x1e, 0xe8, 0xad, 0xf8, + 0x8c, 0x79, 0x1c, 0xc3, 0x1b, 0xbc, 0x48, 0x3e, 0xc1, 0xed, 0x29, 0x83, 0x1a, 0x2b, 0x1f, 0xde, 0xfc, 0xd0, 0x34, + 0x15, 0xd6, 0xc6, 0xcb, 0x61, 0x86, 0x7e, 0xfd, 0x57, 0xee, 0x7b, 0xde, 0x96, 0xf7, 0x7e, 0x6b, 0xf2, 0x0d, 0xdc, + 0x07, 0xa3, 0xde, 0x4d, 0x17, 0xc3, 0x79, 0x43, 0xed, 0xd7, 0x4b, 0xdc, 0x95, 0x59, 0x01, 0xe9, 0xdf, 0xd7, 0xe6, + 0xaf, 0x81, 0x85, 0x91, 0x63, 0x1f, 0x72, 0x22, 0x5b, 0x89, 0x46, 0xcb, 0xfc, 0xc2, 0xe8, 0x6f, 0x08, 0x0d, 0xf3, + 0x39, 0xe8, 0x8a, 0xbb, 0x21, 0xa1, 0x63, 0x66, 0x22, 0x2e, 0xb0, 0x6e, 0x4a, 0xd9, 0xb5, 0xce, 0xae, 0xb6, 0xaa, + 0xc7, 0x23, 0xd6, 0x55, 0xda, 0x7b, 0x05, 0xa1, 0x10, 0x71, 0x7d, 0x3e, 0x4d, 0xeb, 0x1b, 0xa3, 0x0c, 0x3a, 0xbc, + 0x11, 0x19, 0x5b, 0x3c, 0xfa, 0x0f, 0xcc, 0x8f, 0x28, 0x34, 0xcc, 0xa5, 0xb0, 0xb7, 0xb7, 0x89, 0xe3, 0x54, 0xf1, + 0x8c, 0xdc, 0xbe, 0xa7, 0xfa, 0xe2, 0x29, 0xed, 0xfd, 0x92, 0xa2, 0x15, 0x4e, 0xc6, 0xb8, 0x4e, 0x1e, 0xf3, 0x25, + 0x43, 0xa1, 0x08, 0x21, 0x8c, 0x53, 0xbf, 0x98, 0x33, 0xa4, 0x05, 0xde, 0x48, 0xdb, 0x36, 0xe4, 0x94, 0x30, 0x05, + 0x71, 0x3e, 0xc2, 0xc3, 0x39, 0xbb, 0xb5, 0x0a, 0x53, 0x77, 0x9c, 0xf5, 0x85, 0x86, 0x87, 0x3b, 0x64, 0xda, 0x21, + 0xec, 0x1b, 0x72, 0xc3, 0xce, 0xa6, 0x17, 0x2e, 0x34, 0x71, 0x7b, 0xdc, 0x3f, 0xa4, 0x25, 0xf3, 0x34, 0x01, 0xa2, + 0x31, 0xe6, 0xe5, 0x92, 0xbd, 0x36, 0xb6, 0xe3, 0x6d, 0x98, 0x0d, 0xa1, 0x36, 0x60, 0xdd, 0xc1, 0x99, 0x9e, 0xf9, + 0x1b, 0xc5, 0xfb, 0x72, 0x09, 0x8e, 0x97, 0xe2, 0x21, 0x2e, 0xfc, 0x67, 0x54, 0x58, 0x72, 0x12, 0xb3, 0xe0, 0x61, + 0x2b, 0x5e, 0x99, 0xe1, 0x32, 0xe9, 0x0b, 0x27, 0x50, 0xf9, 0xc0, 0x09, 0x14, 0x4e, 0x5f, 0xc4, 0xa4, 0x92, 0xf7, + 0x70, 0xca, 0x4e, 0xdf, 0xde, 0x8b, 0x63, 0x06, 0xf5, 0x9a, 0x4d, 0x8e, 0x9e, 0x64, 0x63, 0x56, 0x0d, 0x07, 0x51, + 0xdd, 0x69, 0x64, 0x90, 0x71, 0x78, 0x2f, 0x57, 0xd7, 0x91, 0x21, 0xe2, 0x08, 0x2a, 0xd4, 0x2f, 0xa4, 0xf3, 0xf3, + 0x79, 0x21, 0x03, 0x89, 0x06, 0x6d, 0x36, 0xec, 0x10, 0x55, 0x21, 0xaf, 0x13, 0x5e, 0x08, 0xad, 0xf0, 0x84, 0x1e, + 0x65, 0xd8, 0x1d, 0x2d, 0x32, 0xda, 0xed, 0x2b, 0x47, 0xd3, 0x85, 0x83, 0xff, 0x1c, 0x66, 0x0b, 0x74, 0xb8, 0x46, + 0x5d, 0xa4, 0xfb, 0xa9, 0x13, 0xaf, 0xfd, 0x0f, 0xa6, 0x8b, 0xae, 0x7d, 0x9a, 0xc9, 0x9c, 0x1e, 0x32, 0xa2, 0xf8, + 0xea, 0x85, 0xaf, 0xe9, 0x64, 0x5a, 0x33, 0x7d, 0x14, 0x5a, 0xe7, 0xa1, 0xca, 0xed, 0xc1, 0x78, 0x56, 0x93, 0xe1, + 0xa3, 0xab, 0xc0, 0x21, 0x68, 0x4a, 0x30, 0x73, 0x5f, 0x51, 0x63, 0x34, 0x12, 0x26, 0x9d, 0x05, 0x96, 0x37, 0xb3, + 0x61, 0x39, 0x6f, 0x85, 0x96, 0x8f, 0x39, 0xfe, 0xe0, 0x16, 0xdb, 0xfc, 0x54, 0xd8, 0x2c, 0x71, 0xf1, 0x85, 0x8d, + 0x65, 0xd4, 0x8b, 0xa9, 0xb4, 0x75, 0x8a, 0x15, 0xa9, 0xec, 0xa6, 0x6e, 0xbc, 0xa9, 0x5b, 0x5e, 0x4a, 0xce, 0xdb, + 0x14, 0xe5, 0x24, 0x77, 0x29, 0x14, 0x83, 0x81, 0x37, 0x72, 0xd6, 0x9e, 0xa2, 0x64, 0x72, 0x03, 0x9d, 0xe9, 0xeb, + 0xd8, 0xb6, 0xa2, 0x78, 0xb0, 0x17, 0xae, 0xac, 0x3c, 0x83, 0x44, 0xbe, 0x3c, 0x5f, 0xd4, 0x89, 0x85, 0xe2, 0x7c, + 0x37, 0x5f, 0x28, 0x56, 0xf0, 0x6d, 0x40, 0x3c, 0x3b, 0x6f, 0x05, 0xf8, 0x2b, 0xec, 0x70, 0x76, 0xe6, 0x3d, 0x98, + 0xab, 0x50, 0x04, 0x3b, 0x32, 0xf8, 0x46, 0x82, 0x6a, 0x26, 0x30, 0x59, 0x82, 0x60, 0x55, 0x16, 0xf3, 0x05, 0xc1, + 0x1a, 0x47, 0xa1, 0x64, 0xc3, 0xe5, 0x77, 0x66, 0x53, 0x14, 0x45, 0x22, 0xfc, 0xee, 0x1a, 0x26, 0xcf, 0x08, 0xa7, + 0xf8, 0x58, 0x55, 0xe6, 0x43, 0x8d, 0x17, 0x93, 0x68, 0x80, 0xfe, 0xcd, 0x56, 0x44, 0xfb, 0xd7, 0x51, 0xa3, 0x24, + 0xb8, 0x67, 0xe1, 0xda, 0x38, 0xeb, 0x8a, 0xeb, 0xf4, 0x9b, 0x2c, 0xf3, 0x5a, 0x84, 0xf9, 0x95, 0x0d, 0x59, 0xeb, + 0x7c, 0x0f, 0x87, 0x72, 0xa2, 0xee, 0xb3, 0x9c, 0x59, 0x72, 0x88, 0x7a, 0x4f, 0x47, 0x67, 0xe6, 0x90, 0xb3, 0xb8, + 0xba, 0x61, 0x0a, 0x32, 0x81, 0xd7, 0x4c, 0x23, 0x95, 0x81, 0x3f, 0x15, 0x05, 0x1c, 0x2e, 0xa7, 0xf8, 0x9f, 0x94, + 0x98, 0x1d, 0x04, 0x83, 0x30, 0x76, 0x77, 0xf5, 0x2b, 0x40, 0xd6, 0x16, 0x66, 0x07, 0xb7, 0x39, 0x6e, 0xa2, 0xc0, + 0xa8, 0x09, 0xde, 0x16, 0xf2, 0xa5, 0x25, 0x48, 0x89, 0xd5, 0xc8, 0x8b, 0x56, 0x9f, 0xc7, 0x30, 0x10, 0xf8, 0xa9, + 0x0b, 0xfb, 0x73, 0x4d, 0xc7, 0xe6, 0xd0, 0x34, 0x92, 0x67, 0xd2, 0x96, 0x84, 0x81, 0xa4, 0x9d, 0x68, 0xe3, 0x27, + 0x17, 0x9a, 0xea, 0x76, 0x0a, 0xe9, 0x7a, 0x39, 0x0d, 0x25, 0xb3, 0x28, 0x5c, 0x38, 0x9a, 0x53, 0x1e, 0xd0, 0xe9, + 0xd6, 0x6c, 0x4c, 0x76, 0x07, 0xc3, 0x95, 0x18, 0x4d, 0x1d, 0xc6, 0xb9, 0x28, 0xed, 0x31, 0x56, 0xaa, 0xc7, 0x7e, + 0x91, 0x36, 0x21, 0xc1, 0x13, 0x2a, 0x8e, 0x3c, 0xf0, 0xa8, 0xa2, 0x35, 0x44, 0x87, 0xc7, 0xf0, 0xc0, 0xa9, 0xb9, + 0xc1, 0x58, 0x1d, 0xc6, 0x40, 0x39, 0x92, 0x38, 0x45, 0x7e, 0xba, 0x63, 0xb8, 0xf0, 0x39, 0xff, 0x80, 0x99, 0x52, + 0x79, 0xe6, 0x73, 0x3d, 0xf8, 0x76, 0xc0, 0xf7, 0xfe, 0x1b, 0x8f, 0x91, 0x0d, 0xe3, 0xf0, 0xc3, 0xbf, 0xea, 0xb1, + 0xfc, 0xfd, 0x80, 0xe6, 0xc3, 0x49, 0x1e, 0x4c, 0x38, 0x2b, 0xb4, 0x80, 0x3f, 0xba, 0x32, 0xee, 0x16, 0x0c, 0xeb, + 0x23, 0x28, 0x22, 0x76, 0xe2, 0x70, 0xbc, 0x5c, 0x0b, 0x00, 0xe5, 0xc1, 0x6d, 0x80, 0xc8, 0x42, 0x34, 0x3f, 0x2f, + 0x17, 0xdf, 0xd6, 0x65, 0x68, 0x4b, 0x5b, 0x77, 0x8f, 0x13, 0x31, 0x6c, 0x26, 0x41, 0x2d, 0x44, 0xaf, 0x88, 0x18, + 0x11, 0x33, 0x43, 0xeb, 0x65, 0xfd, 0x9e, 0xb9, 0x1b, 0x08, 0xa3, 0x36, 0x6c, 0x84, 0x37, 0xec, 0x59, 0xbf, 0xb0, + 0xdc, 0x23, 0xb6, 0xaa, 0x80, 0x7d, 0x4b, 0x3e, 0x40, 0x91, 0x84, 0x2d, 0xed, 0xf8, 0x87, 0x13, 0xb1, 0xe8, 0x1f, + 0x42, 0xd8, 0xc4, 0x36, 0x28, 0x18, 0xbc, 0xd4, 0x36, 0x7b, 0x1b, 0x08, 0x31, 0x14, 0xeb, 0xb5, 0x1e, 0xc1, 0x89, + 0x1a, 0x40, 0x2a, 0x74, 0xcf, 0x58, 0x3d, 0x22, 0x93, 0xe7, 0x9f, 0x91, 0x96, 0x2d, 0x98, 0x29, 0x9f, 0x58, 0x3b, + 0x12, 0xec, 0xfc, 0x66, 0x45, 0xf2, 0x82, 0xef, 0x12, 0x49, 0xbe, 0xbe, 0x2b, 0xc6, 0xcf, 0xf8, 0x0f, 0x34, 0x29, + 0x04, 0x3a, 0xb8, 0x3a, 0x20, 0x32, 0xd4, 0x62, 0x19, 0xd5, 0xf1, 0x1e, 0x8b, 0x4c, 0x9c, 0x5e, 0xd8, 0xcc, 0xb3, + 0x0c, 0x0b, 0x27, 0x16, 0xc3, 0x28, 0x95, 0x5d, 0x6e, 0xd9, 0xde, 0x9f, 0x0c, 0x4e, 0xe5, 0x88, 0x52, 0x75, 0xbc, + 0xc6, 0xad, 0x1a, 0x9a, 0x43, 0x57, 0xa9, 0xcd, 0x68, 0x78, 0xec, 0x5f, 0xd7, 0x99, 0xd4, 0xb1, 0xa6, 0x03, 0x82, + 0xd7, 0x41, 0xaf, 0xd3, 0x82, 0xfb, 0x3b, 0x6f, 0x13, 0x6d, 0xc4, 0x20, 0x82, 0x82, 0x19, 0x82, 0x7d, 0x7e, 0xa8, + 0x25, 0x63, 0xc3, 0x68, 0x92, 0x54, 0xd4, 0xb1, 0x71, 0x66, 0x94, 0xf5, 0x9b, 0x74, 0x9c, 0xf2, 0x36, 0x12, 0x7a, + 0xf0, 0xbd, 0x3c, 0x5e, 0x71, 0xd2, 0x3a, 0x46, 0xc4, 0x05, 0xc7, 0xc3, 0x6d, 0xcc, 0xa1, 0xd9, 0x2c, 0xb6, 0xc0, + 0x61, 0x42, 0x6b, 0x61, 0xb3, 0x73, 0x96, 0x53, 0xbe, 0x92, 0xb2, 0x27, 0xe9, 0xcd, 0xab, 0x0d, 0x25, 0x00, 0xc2, + 0x44, 0xff, 0x48, 0x02, 0x9f, 0x15, 0xc8, 0x98, 0x23, 0x27, 0x39, 0x32, 0x3f, 0x56, 0xf3, 0x08, 0x28, 0x13, 0xc3, + 0xc5, 0xdb, 0x70, 0xd3, 0x4f, 0xd0, 0x42, 0x3b, 0xd8, 0xa9, 0x1b, 0x04, 0x41, 0x82, 0x1d, 0xe0, 0x2f, 0xfc, 0xd8, + 0x1e, 0xbf, 0xa5, 0xcb, 0xaf, 0x5b, 0x87, 0xff, 0x8f, 0x39, 0xb7, 0x0f, 0x1b, 0x5b, 0x3f, 0xd8, 0xaa, 0xef, 0x91, + 0xff, 0xc8, 0x0c, 0x5d, 0x0f, 0xf8, 0xf0, 0x81, 0x0d, 0x67, 0xcf, 0xd5, 0x00, 0x6e, 0x5b, 0xbf, 0x16, 0x58, 0x92, + 0x09, 0x22, 0xe5, 0xc4, 0x8e, 0xea, 0xcb, 0x64, 0xfd, 0xcd, 0xe0, 0xe6, 0xf4, 0x80, 0xfa, 0x07, 0x13, 0xbf, 0x04, + 0x5d, 0x76, 0xf7, 0x0b, 0xc8, 0xc4, 0xfa, 0xd4, 0x21, 0x57, 0x29, 0xfd, 0xfc, 0x62, 0xd9, 0xfe, 0x12, 0xa5, 0xab, + 0x6c, 0x70, 0x7f, 0xd1, 0x49, 0x41, 0xd8, 0xf4, 0xfa, 0x25, 0xd7, 0x36, 0xad, 0x7d, 0x52, 0xb9, 0xda, 0x11, 0xdf, + 0x05, 0x20, 0xc0, 0x4e, 0x95, 0xc9, 0xc9, 0x33, 0xfe, 0x48, 0x04, 0x9d, 0xb3, 0x5f, 0xf6, 0x37, 0xeb, 0xf0, 0x86, + 0x58, 0xdb, 0x5d, 0xbc, 0xb7, 0x5f, 0x80, 0xa0, 0x9c, 0x7b, 0x0d, 0x15, 0x0c, 0x42, 0xbc, 0x24, 0x40, 0xc8, 0x61, + 0x74, 0xe2, 0xa2, 0x8b, 0x1c, 0x62, 0xda, 0x48, 0x60, 0x5d, 0xa5, 0xed, 0x91, 0x63, 0xaf, 0xe5, 0x89, 0x6e, 0x61, + 0xdc, 0x42, 0x55, 0xc3, 0xa2, 0x01, 0x23, 0xcf, 0xc2, 0x0e, 0xe7, 0xe2, 0xa1, 0x47, 0x2b, 0xe8, 0x82, 0x34, 0xe1, + 0xb8, 0xd4, 0xef, 0xc3, 0x21, 0x28, 0x9b, 0xaf, 0x3a, 0xa1, 0xfb, 0x01, 0x2e, 0x4d, 0xd1, 0x1a, 0xf2, 0xe7, 0x62, + 0x3b, 0x23, 0x5c, 0x9e, 0x48, 0x4c, 0x16, 0x82, 0x38, 0xda, 0xd6, 0x2d, 0x93, 0xa1, 0x2e, 0x86, 0xf8, 0xf9, 0x48, + 0x69, 0x0b, 0x3d, 0xbc, 0x20, 0x90, 0x69, 0x82, 0x3a, 0x87, 0xdf, 0x24, 0xfc, 0xae, 0x30, 0x76, 0xc8, 0xcd, 0x74, + 0x18, 0x09, 0xd7, 0xb8, 0x13, 0x20, 0xb2, 0x34, 0x1f, 0x29, 0x6c, 0x5a, 0xd7, 0x51, 0x7f, 0x72, 0x09, 0x51, 0x7b, + 0xc4, 0x66, 0x3c, 0x69, 0xb0, 0x93, 0xe5, 0x37, 0x9d, 0x4b, 0x31, 0x44, 0x54, 0x99, 0x92, 0xbb, 0xe4, 0x9a, 0x38, + 0x92, 0x6b, 0x56, 0x19, 0x87, 0x12, 0x3a, 0x65, 0xa1, 0x6d, 0xe4, 0xb2, 0x83, 0x48, 0x3b, 0xe1, 0x98, 0x54, 0xd9, + 0xe4, 0xb1, 0x58, 0x88, 0x21, 0x33, 0xcd, 0x04, 0xeb, 0xeb, 0x0b, 0xcd, 0xaf, 0x6d, 0xc7, 0x40, 0x04, 0xd6, 0x1d, + 0x83, 0x4f, 0x05, 0x36, 0x2f, 0xa5, 0xa5, 0xd0, 0xab, 0x30, 0x75, 0xd5, 0x56, 0x2f, 0x94, 0xb6, 0x21, 0x64, 0x20, + 0x69, 0x67, 0x09, 0x1f, 0x65, 0x0b, 0x83, 0x9c, 0xf9, 0xbb, 0x05, 0x64, 0xdb, 0x83, 0x60, 0x7b, 0xcb, 0x94, 0xa5, + 0xbe, 0xb7, 0xfc, 0x5a, 0x12, 0x3e, 0x36, 0x21, 0x70, 0x19, 0x70, 0xd5, 0xee, 0x75, 0x18, 0x16, 0xf3, 0xc9, 0x0c, + 0xe6, 0x4f, 0x38, 0xd1, 0x54, 0xf8, 0x2a, 0x7d, 0xad, 0xac, 0xeb, 0x33, 0xef, 0x39, 0x79, 0xb6, 0x2d, 0x7e, 0xa5, + 0x75, 0xb1, 0x62, 0xa0, 0x17, 0x01, 0xdf, 0x56, 0xf3, 0x7d, 0x59, 0xdf, 0x85, 0xfa, 0xf4, 0x11, 0xd8, 0x0c, 0xf6, + 0xa0, 0x43, 0x77, 0x63, 0x32, 0x4a, 0xad, 0x9b, 0x67, 0xf6, 0xf6, 0xe3, 0xaf, 0x7b, 0xff, 0xd7, 0x53, 0x0c, 0xd1, + 0x0b, 0x60, 0x8a, 0x49, 0x5f, 0x47, 0x28, 0x3b, 0x64, 0x89, 0x69, 0x47, 0x2a, 0x51, 0x74, 0x45, 0x62, 0x96, 0xa5, + 0x00, 0xe5, 0x1b, 0xab, 0x47, 0x84, 0x6b, 0x25, 0x39, 0x80, 0x50, 0xab, 0x08, 0xde, 0x26, 0xd3, 0xfb, 0xaa, 0xd8, + 0x50, 0xc0, 0x02, 0xe6, 0xeb, 0x18, 0x76, 0x91, 0x41, 0xbe, 0xfe, 0x4b, 0xf6, 0x73, 0xc2, 0xe1, 0xc8, 0x05, 0x02, + 0x28, 0xd3, 0x76, 0x21, 0x4d, 0xf8, 0x75, 0xf1, 0xb6, 0x2a, 0xbe, 0x42, 0x6a, 0x49, 0x39, 0xd0, 0x4b, 0xf4, 0x7f, + 0xdd, 0x1d, 0xde, 0xc9, 0xe7, 0x27, 0x46, 0x3d, 0x11, 0x6e, 0x79, 0xf6, 0x95, 0x65, 0x56, 0xb9, 0xe2, 0xfe, 0xd0, + 0x61, 0x42, 0xb9, 0xdb, 0x33, 0xc9, 0xea, 0x44, 0xad, 0xc2, 0x0c, 0x7d, 0xe5, 0xfe, 0x67, 0xb6, 0x97, 0x7b, 0xae, + 0xf2, 0x50, 0x2c, 0x1c, 0xf8, 0xa2, 0xa1, 0xf1, 0x19, 0xa2, 0x21, 0xca, 0x8c, 0xd5, 0x80, 0x0d, 0xa0, 0xb4, 0xcf, + 0x87, 0x05, 0x2b, 0x99, 0x3a, 0x31, 0xaa, 0x34, 0x22, 0x83, 0x04, 0x53, 0xe0, 0xb9, 0xb4, 0x39, 0x14, 0x88, 0xa0, + 0x59, 0xa4, 0xd0, 0xe8, 0x47, 0x3a, 0xac, 0x71, 0x73, 0xa1, 0xee, 0x31, 0x64, 0x04, 0xc1, 0xc0, 0xb2, 0x79, 0x21, + 0x07, 0x1b, 0x45, 0xe1, 0xd4, 0x8f, 0x01, 0x05, 0xe5, 0x3f, 0xcc, 0x7c, 0xa9, 0xd9, 0x71, 0xc7, 0x6a, 0xc0, 0x17, + 0xf9, 0xf2, 0x7b, 0xe8, 0x2a, 0x94, 0x68, 0xe4, 0x26, 0x37, 0x49, 0x59, 0x04, 0xd1, 0xc3, 0x9f, 0xa0, 0x4a, 0x8a, + 0x98, 0x2e, 0xe2, 0x86, 0xd6, 0x5c, 0x2c, 0x64, 0xb2, 0xab, 0x07, 0x6e, 0x7d, 0xa3, 0x59, 0x4d, 0xf4, 0xea, 0xd8, + 0xbf, 0x90, 0x88, 0x4e, 0x58, 0x3c, 0x91, 0x57, 0x2c, 0x84, 0x19, 0x0c, 0x04, 0x28, 0xda, 0x36, 0x4a, 0xc5, 0xe8, + 0x75, 0x9f, 0xcf, 0x8e, 0xf3, 0xbd, 0xd8, 0x86, 0xa4, 0xd2, 0xe8, 0xc3, 0x00, 0x5a, 0xfd, 0xb4, 0x25, 0x5f, 0xb0, + 0xf8, 0x9f, 0xe4, 0x84, 0xb7, 0x3d, 0xf4, 0x14, 0xe2, 0x53, 0x0f, 0xd0, 0xf9, 0x2e, 0x81, 0xc2, 0xf4, 0xd2, 0x45, + 0xf0, 0xa8, 0x44, 0xdd, 0x98, 0x13, 0x4b, 0x9e, 0x43, 0x8b, 0x1f, 0xa8, 0xf1, 0x6b, 0x7b, 0xc5, 0xa0, 0x43, 0xe2, + 0x05, 0x05, 0x54, 0x45, 0x57, 0xb2, 0xf9, 0x94, 0x3a, 0x6d, 0x63, 0xb5, 0x58, 0x09, 0xde, 0xb4, 0x52, 0xeb, 0x9d, + 0x8a, 0x29, 0xbc, 0x1f, 0x2c, 0x20, 0x2c, 0x6a, 0x45, 0x62, 0xc9, 0x2f, 0x49, 0x8b, 0x5d, 0x00, 0x6d, 0x42, 0x41, + 0x98, 0x9d, 0xbd, 0x66, 0xec, 0xfb, 0x72, 0xa2, 0x17, 0xe5, 0x35, 0xe2, 0x1e, 0x0e, 0x01, 0x80, 0x61, 0xbf, 0x37, + 0x43, 0x31, 0xd2, 0xe1, 0xc2, 0x99, 0x9b, 0x24, 0x90, 0xb0, 0x4d, 0x42, 0x66, 0x67, 0xbb, 0x0c, 0xef, 0xff, 0xb7, + 0x86, 0x73, 0x83, 0xb5, 0x12, 0x6e, 0x1d, 0x5d, 0x65, 0x82, 0xbc, 0xb2, 0x8a, 0xea, 0x65, 0xee, 0xb8, 0xeb, 0xe5, + 0x40, 0x54, 0x5a, 0x8c, 0x67, 0xab, 0xf0, 0xb0, 0x4c, 0xe1, 0xb1, 0x6d, 0x41, 0x76, 0xe6, 0x25, 0xb8, 0x04, 0x44, + 0x1f, 0x64, 0x7c, 0xe4, 0x9d, 0x2c, 0xb0, 0x3c, 0x1b, 0x7f, 0xae, 0x5c, 0xef, 0x83, 0xf8, 0xd0, 0x45, 0x1a, 0xd7, + 0xd3, 0xe9, 0x1d, 0x4a, 0x32, 0xc1, 0xb4, 0xbb, 0x09, 0x8b, 0x55, 0x3b, 0x31, 0xf9, 0x46, 0x41, 0xdc, 0x80, 0xd4, + 0xbe, 0x1b, 0x07, 0xb2, 0x0d, 0xac, 0x37, 0x1f, 0xa3, 0x68, 0xe0, 0x7a, 0x44, 0x9c, 0x9b, 0xf5, 0xda, 0xb5, 0x69, + 0x41, 0x00, 0x52, 0x30, 0x6b, 0x09, 0xce, 0xbb, 0x72, 0x12, 0x80, 0x10, 0x5b, 0x00, 0x31, 0xdd, 0x40, 0x82, 0x3c, + 0xa2, 0x5a, 0x93, 0xd1, 0x37, 0x4b, 0x8f, 0x9f, 0x77, 0xc4, 0x1e, 0x2c, 0xda, 0xac, 0xe9, 0xe5, 0x0b, 0xb6, 0xfb, + 0xb9, 0x1d, 0xd4, 0x28, 0x27, 0xcf, 0x1a, 0xd9, 0x0d, 0x9b, 0x5b, 0xc7, 0xf2, 0x30, 0xa3, 0x87, 0x20, 0x96, 0xcc, + 0x7b, 0x47, 0x8b, 0x95, 0x50, 0x1d, 0xfc, 0x42, 0x50, 0x70, 0xb4, 0xfe, 0x0f, 0xda, 0x24, 0x63, 0x9b, 0x96, 0x35, + 0x6f, 0x2d, 0xce, 0x9a, 0xc2, 0xca, 0x25, 0xb3, 0xd4, 0xfb, 0xc5, 0x14, 0xf5, 0x54, 0xbe, 0xb5, 0x5a, 0xf6, 0x75, + 0x72, 0xd4, 0xd0, 0x1e, 0x79, 0x2f, 0x80, 0xc1, 0xb2, 0x35, 0xcd, 0xa2, 0x98, 0xc2, 0x8d, 0x63, 0xf9, 0xc6, 0x57, + 0x8b, 0x2f, 0x24, 0x4e, 0x98, 0xdb, 0x78, 0x92, 0x47, 0x32, 0x0e, 0xef, 0x53, 0x28, 0xdc, 0x08, 0xa5, 0x85, 0x29, + 0xe9, 0x38, 0x01, 0xc7, 0x6a, 0xb7, 0x5b, 0x51, 0x89, 0xd5, 0xc2, 0xd2, 0x78, 0x06, 0x30, 0x93, 0xd5, 0x8e, 0xb7, + 0x2a, 0x18, 0x6a, 0x90, 0x5c, 0xcc, 0x20, 0x98, 0xe9, 0x39, 0x63, 0x67, 0x5e, 0xe5, 0x17, 0x68, 0x6b, 0x63, 0x16, + 0x16, 0x5a, 0x36, 0x66, 0x76, 0x16, 0x25, 0x80, 0x35, 0x82, 0x5e, 0x4b, 0xe9, 0xee, 0xb9, 0x8e, 0xf8, 0xeb, 0x72, + 0xe4, 0x7c, 0xc5, 0x60, 0x3e, 0xf6, 0x2a, 0x7b, 0x9c, 0x7a, 0xf0, 0x22, 0xc0, 0x8c, 0x30, 0x61, 0xab, 0x7c, 0x82, + 0xf6, 0x6c, 0xe9, 0x3f, 0xf0, 0x0d, 0x6c, 0x8a, 0xd1, 0xcc, 0x18, 0x3b, 0x67, 0x96, 0x57, 0xf1, 0xae, 0x86, 0x35, + 0x64, 0x11, 0x63, 0xfa, 0x9c, 0x35, 0xc5, 0xdc, 0x46, 0x17, 0xf5, 0x15, 0x24, 0x82, 0xe5, 0xab, 0x0c, 0x03, 0x10, + 0x0e, 0x66, 0x37, 0x9a, 0x74, 0xb1, 0x01, 0xed, 0xf3, 0x7b, 0x20, 0x04, 0x06, 0x30, 0x5d, 0x9c, 0x0b, 0x34, 0xd1, + 0x01, 0x64, 0xf9, 0x03, 0x04, 0x00, 0x92, 0xc0, 0x0c, 0x45, 0x02, 0x44, 0xaf, 0x5a, 0xfa, 0x9a, 0x97, 0x97, 0xd8, + 0xe8, 0x31, 0x23, 0x08, 0xb6, 0x72, 0x97, 0xa2, 0xa0, 0xcc, 0xd6, 0x81, 0xae, 0xc7, 0xb7, 0x67, 0x55, 0x25, 0x29, + 0x42, 0x7e, 0x63, 0x44, 0xe5, 0x9f, 0x8d, 0xc5, 0xcc, 0xa6, 0xcc, 0x25, 0x3d, 0xf4, 0x2c, 0x93, 0xc5, 0x74, 0xb3, + 0x9f, 0x4f, 0x96, 0xa0, 0xd8, 0x43, 0x42, 0x76, 0xb1, 0xb0, 0xc1, 0xb6, 0x68, 0x28, 0x24, 0xf5, 0x8f, 0xa3, 0xf3, + 0x27, 0xf5, 0x28, 0x2a, 0x67, 0xcb, 0x7a, 0x42, 0xed, 0x98, 0x6b, 0x37, 0x02, 0x6b, 0x52, 0x9e, 0x0f, 0x9b, 0xb1, + 0xa5, 0x7e, 0xa9, 0x50, 0xca, 0xce, 0xac, 0x93, 0xd8, 0xc9, 0x79, 0xa1, 0xfa, 0xba, 0x4d, 0x32, 0x21, 0x8c, 0x03, + 0x63, 0x7f, 0x3a, 0x1b, 0x77, 0xbf, 0xe7, 0x17, 0x44, 0x2a, 0xe7, 0xc2, 0xb4, 0x0f, 0xd9, 0x5a, 0x35, 0x31, 0x05, + 0x9a, 0xf5, 0x34, 0x83, 0x8b, 0x04, 0xfa, 0x3c, 0x04, 0x7b, 0xa6, 0x5f, 0x85, 0x4c, 0x3b, 0x88, 0xf5, 0x07, 0x6d, + 0xfc, 0x88, 0xaf, 0x51, 0x22, 0xc3, 0xdf, 0x39, 0x01, 0x3a, 0x7e, 0x60, 0x49, 0xd8, 0x92, 0xc4, 0xdd, 0x5c, 0xa4, + 0xb2, 0xf1, 0x7d, 0xdc, 0x06, 0x72, 0x41, 0x14, 0x78, 0xae, 0x03, 0xdc, 0xc7, 0x98, 0x73, 0xaa, 0x1e, 0x35, 0xec, + 0xd7, 0xb4, 0x54, 0xf8, 0xb5, 0x70, 0x10, 0xb0, 0x0a, 0x20, 0xa1, 0xbf, 0xbc, 0x41, 0xcf, 0x99, 0x9d, 0x57, 0x0c, + 0x59, 0x20, 0x2e, 0x75, 0xe5, 0x2d, 0x74, 0x12, 0x00, 0x10, 0x5d, 0x11, 0x63, 0x80, 0x57, 0x3b, 0x6a, 0xfc, 0x02, + 0x0e, 0xbd, 0xd3, 0xba, 0xad, 0xb9, 0x9b, 0x40, 0x14, 0x21, 0x20, 0x40, 0x62, 0x6b, 0x28, 0x88, 0xbc, 0xe5, 0x20, + 0x92, 0x2a, 0xb1, 0xfb, 0x4a, 0x2b, 0x34, 0x0b, 0x6e, 0xa4, 0x23, 0xd2, 0x08, 0xa0, 0x57, 0xb0, 0x21, 0x66, 0x04, + 0xaa, 0x5c, 0x47, 0x1a, 0xbf, 0x44, 0xe3, 0xe0, 0xb5, 0x4a, 0xf4, 0x39, 0x65, 0xbd, 0xf7, 0x20, 0x5e, 0xcf, 0xad, + 0x55, 0xd7, 0xe7, 0x84, 0x10, 0x7d, 0x01, 0x6b, 0x28, 0x9b, 0x3f, 0x65, 0x27, 0xc0, 0x68, 0x20, 0x67, 0xfb, 0x32, + 0x57, 0x1d, 0x96, 0x77, 0x35, 0xcf, 0x1a, 0xdc, 0x94, 0x79, 0x44, 0x2e, 0x5d, 0x55, 0xed, 0x26, 0xae, 0x17, 0xb5, + 0x0b, 0x00, 0xc0, 0x63, 0xf5, 0x52, 0xee, 0x14, 0xb4, 0xa1, 0xf8, 0xc7, 0xba, 0xd2, 0xec, 0x8d, 0xf2, 0x8b, 0xe9, + 0xac, 0xa5, 0xe8, 0xb0, 0xde, 0x43, 0x6e, 0x56, 0x45, 0x05, 0xc0, 0x6a, 0xb7, 0xc2, 0x38, 0x4f, 0x6f, 0xfd, 0xbd, + 0x76, 0x5b, 0xab, 0x0d, 0xa6, 0x45, 0x9d, 0x67, 0x0d, 0x05, 0xe5, 0xd5, 0xb4, 0xfe, 0x17, 0x9e, 0xdd, 0xe5, 0x49, + 0x67, 0xcc, 0x54, 0x61, 0x9c, 0xba, 0xdb, 0xbc, 0xff, 0xfd, 0xcb, 0xb0, 0x25, 0xc4, 0x4e, 0x75, 0xeb, 0x7b, 0xad, + 0x7c, 0x32, 0xf3, 0xa3, 0xce, 0x70, 0xe3, 0x16, 0xb6, 0x19, 0x7b, 0x4d, 0x31, 0xa2, 0x9b, 0x65, 0x61, 0xbf, 0xac, + 0x9c, 0x36, 0xc2, 0xbe, 0xdb, 0xcf, 0xc7, 0x3e, 0xcc, 0x5d, 0x1f, 0x34, 0x75, 0xbc, 0x1d, 0x87, 0xa6, 0x40, 0x83, + 0x75, 0xc0, 0x30, 0x1f, 0xd0, 0xdf, 0xcd, 0x60, 0x90, 0x9a, 0x3c, 0x7a, 0x53, 0xb4, 0x42, 0xf4, 0x62, 0xf0, 0xa5, + 0x3a, 0x11, 0x8b, 0xd4, 0x91, 0x0a, 0xd0, 0xfa, 0x7e, 0xba, 0xac, 0x1a, 0x43, 0xe8, 0xd3, 0x6e, 0x23, 0xd7, 0xd8, + 0x41, 0x5a, 0x7c, 0x6e, 0x18, 0x3f, 0x90, 0x09, 0xc3, 0x37, 0x4d, 0x99, 0xbb, 0x6c, 0xab, 0xbf, 0xac, 0x24, 0xb9, + 0xeb, 0x96, 0x5e, 0x89, 0xce, 0x2a, 0x4c, 0xfd, 0x5a, 0x7d, 0x3f, 0x53, 0x4f, 0xa5, 0x5f, 0x6d, 0xa1, 0xea, 0xc8, + 0x72, 0x21, 0xc1, 0x69, 0x58, 0xb8, 0x56, 0x87, 0x33, 0xa7, 0x6e, 0xd6, 0xd9, 0x0e, 0x4e, 0xe0, 0x64, 0xc9, 0xa9, + 0xc5, 0x94, 0xbc, 0x89, 0x06, 0xc1, 0x61, 0x43, 0x50, 0xd8, 0x5c, 0x14, 0x37, 0x91, 0x50, 0x7d, 0xb3, 0x43, 0x8b, + 0xd3, 0x55, 0xf0, 0xcc, 0x57, 0x7d, 0x4f, 0xc2, 0x33, 0x36, 0x8b, 0x39, 0x53, 0xcd, 0x4b, 0xcd, 0x65, 0xd4, 0x00, + 0xa3, 0x7f, 0xc2, 0xcb, 0xef, 0x89, 0xbb, 0xf5, 0x20, 0x53, 0x52, 0xe5, 0x52, 0x4e, 0x51, 0xb5, 0x8a, 0x6b, 0x5b, + 0x43, 0xd1, 0x6b, 0x87, 0x47, 0xa6, 0x33, 0xb9, 0x0f, 0xde, 0x67, 0x85, 0xe7, 0x90, 0xe5, 0x4b, 0x2b, 0x81, 0x8d, + 0xb8, 0x55, 0xdd, 0xe6, 0x90, 0x6b, 0x2e, 0x4f, 0xe1, 0x79, 0x34, 0x14, 0xd3, 0x79, 0x79, 0x49, 0xef, 0x3b, 0x48, + 0x03, 0xe9, 0xe2, 0xca, 0x44, 0x6f, 0xef, 0xab, 0xea, 0xba, 0x03, 0x6d, 0x6f, 0x7a, 0x44, 0xa2, 0x6e, 0xbb, 0xaa, + 0x3a, 0xaf, 0x98, 0xda, 0x44, 0x37, 0x87, 0x96, 0xc0, 0x7f, 0x75, 0x79, 0x5c, 0x35, 0x5f, 0x02, 0x21, 0xa5, 0x44, + 0xb8, 0xe5, 0xd2, 0x73, 0xd7, 0x65, 0x10, 0x4e, 0x68, 0x8d, 0x4a, 0xd5, 0x6d, 0xc2, 0x4d, 0x56, 0x3d, 0x14, 0x04, + 0xea, 0x9f, 0x4b, 0x11, 0x8e, 0xbe, 0xba, 0x60, 0xda, 0x16, 0x9f, 0xbc, 0xb0, 0xae, 0x49, 0xd7, 0x1e, 0x56, 0xd0, + 0x61, 0x87, 0x95, 0xb4, 0xda, 0xb6, 0x25, 0x2e, 0x5a, 0x62, 0x7c, 0x43, 0xf7, 0x21, 0xb9, 0x61, 0xd9, 0x59, 0x01, + 0x1c, 0x0f, 0x4e, 0xbc, 0x15, 0xbe, 0xaf, 0x71, 0xbf, 0xa4, 0x51, 0x8b, 0xa7, 0x1b, 0x88, 0xf5, 0x02, 0xce, 0x08, + 0x39, 0x96, 0x1f, 0xb0, 0x71, 0x22, 0xd0, 0x6d, 0x99, 0xfe, 0x5f, 0x21, 0x95, 0xc5, 0xb2, 0x43, 0x80, 0x5b, 0xdc, + 0x45, 0x7e, 0x20, 0xad, 0x00, 0x64, 0x0a, 0xa4, 0x50, 0x88, 0xb7, 0x86, 0xfd, 0x43, 0x46, 0xf3, 0x93, 0xe6, 0xb1, + 0x65, 0x27, 0x3a, 0x84, 0xcc, 0x91, 0x22, 0x18, 0xb7, 0x5a, 0xa5, 0x6c, 0x3c, 0xbd, 0xc3, 0x31, 0xa2, 0x58, 0x5a, + 0xe3, 0xbf, 0x56, 0x55, 0xb8, 0x05, 0xec, 0xe6, 0x49, 0xcd, 0x5e, 0x2b, 0xb8, 0x44, 0xf6, 0x06, 0x8f, 0x4e, 0x15, + 0x3b, 0x0b, 0xa3, 0x66, 0x95, 0x2c, 0xf0, 0xf0, 0xd8, 0x17, 0xd6, 0xf1, 0x3f, 0x89, 0x04, 0xe5, 0xfb, 0xef, 0x12, + 0x27, 0x15, 0x0a, 0x85, 0x7d, 0x9a, 0x0f, 0xdb, 0x57, 0x0c, 0x28, 0x5b, 0xb0, 0xee, 0x89, 0x3c, 0x6e, 0x82, 0xab, + 0xaa, 0x03, 0x30, 0xba, 0x49, 0x2e, 0x4f, 0x08, 0x6e, 0x72, 0x15, 0x5a, 0x1b, 0xe4, 0x28, 0x1d, 0x70, 0x4a, 0xe4, + 0x56, 0x61, 0x8a, 0xe0, 0x12, 0xed, 0xa0, 0x44, 0x09, 0x86, 0x5b, 0x54, 0x83, 0xb2, 0x49, 0x3d, 0x52, 0x04, 0x7f, + 0x9d, 0x80, 0x0e, 0xc0, 0x3c, 0x2c, 0x9f, 0x72, 0x8d, 0xe0, 0x65, 0x64, 0xa5, 0x74, 0xd4, 0x1c, 0x83, 0x89, 0xa9, + 0x4c, 0x72, 0x4d, 0x27, 0x3c, 0x98, 0x36, 0xe2, 0x5c, 0x39, 0xa8, 0xed, 0x41, 0x3c, 0x09, 0xc3, 0x46, 0x9d, 0xf4, + 0x17, 0xfc, 0xf3, 0x51, 0x62, 0x30, 0xa1, 0xdd, 0x2c, 0xd1, 0x8b, 0xb0, 0x97, 0xf3, 0x72, 0xd2, 0xcd, 0x3a, 0x05, + 0x60, 0xab, 0xe5, 0x62, 0x07, 0x0c, 0x8c, 0x22, 0x7f, 0x04, 0xe4, 0x8a, 0x4f, 0x9f, 0x96, 0x2a, 0x1e, 0x8d, 0x59, + 0x9e, 0x70, 0xf1, 0xc9, 0x97, 0x36, 0x1b, 0x94, 0x8e, 0x26, 0x1d, 0xdf, 0x1a, 0x13, 0xbe, 0x93, 0xce, 0xc6, 0x2a, + 0x9c, 0x83, 0x28, 0x46, 0xa1, 0x1d, 0xd9, 0xa8, 0xac, 0xcb, 0x1c, 0xb6, 0x6f, 0x20, 0xd2, 0xeb, 0xb4, 0x22, 0xa4, + 0x6a, 0x41, 0xa6, 0xee, 0x70, 0x8a, 0x2b, 0x87, 0x50, 0xea, 0x99, 0x83, 0x32, 0xcc, 0x70, 0x94, 0x71, 0x75, 0xa7, + 0x91, 0x32, 0x20, 0x15, 0x22, 0x61, 0xce, 0x9c, 0xd6, 0xde, 0xe2, 0x04, 0x9b, 0xbd, 0x33, 0xf3, 0x69, 0xc2, 0xf5, + 0x5a, 0xd8, 0xd8, 0x39, 0xd6, 0x15, 0xb0, 0x6b, 0xcb, 0xe9, 0x46, 0x64, 0xaa, 0x9e, 0x1e, 0x0c, 0x39, 0x67, 0x56, + 0xd2, 0xf5, 0x5e, 0x3e, 0xaf, 0x39, 0xb4, 0x92, 0xee, 0x2b, 0x4b, 0x68, 0xc0, 0x10, 0xc7, 0xee, 0x8f, 0x9f, 0xe8, + 0xe0, 0x4f, 0xbe, 0xdc, 0xf8, 0xe4, 0xa7, 0xf6, 0x82, 0x53, 0x89, 0xb3, 0x41, 0xad, 0x95, 0x22, 0x99, 0xde, 0x98, + 0x31, 0x10, 0x12, 0x27, 0x34, 0xdc, 0xc3, 0x4b, 0x06, 0x8c, 0x62, 0xb4, 0x5e, 0x55, 0xa4, 0x4d, 0x6a, 0x80, 0x88, + 0x26, 0xdd, 0x21, 0xcd, 0xaa, 0x4e, 0xca, 0x32, 0x15, 0x57, 0x33, 0x57, 0x66, 0xca, 0xda, 0xd1, 0x50, 0x7b, 0x8b, + 0x90, 0x78, 0xa8, 0xe4, 0xdb, 0x81, 0xe3, 0x31, 0x0a, 0x62, 0x49, 0xa9, 0xf4, 0x2f, 0x4e, 0x3b, 0x3d, 0x7e, 0x8c, + 0xe2, 0x5e, 0x78, 0x93, 0x75, 0x8c, 0x61, 0x30, 0x9b, 0x0e, 0xf6, 0x43, 0x36, 0x22, 0xb1, 0x97, 0xb9, 0xb1, 0xd3, + 0xdd, 0x76, 0xd4, 0xe1, 0xc3, 0xc2, 0x78, 0x35, 0x7b, 0xec, 0x01, 0xb4, 0x2a, 0x58, 0xea, 0x54, 0x61, 0x3b, 0xfc, + 0xe2, 0xbf, 0xf2, 0x45, 0x7a, 0x50, 0x97, 0x0e, 0x89, 0xd6, 0x36, 0x24, 0x98, 0x07, 0x49, 0x91, 0x95, 0x5d, 0x82, + 0xb2, 0x66, 0xc5, 0xfa, 0x33, 0x05, 0x9f, 0x85, 0xe3, 0x9f, 0x4c, 0xbf, 0xec, 0xe5, 0x32, 0x9e, 0xf5, 0x75, 0xe6, + 0x2a, 0xb7, 0x9c, 0xa3, 0x64, 0x06, 0x77, 0x2c, 0xec, 0x75, 0x41, 0x8e, 0x71, 0x61, 0x03, 0xa0, 0x72, 0x8c, 0x41, + 0xc6, 0xa3, 0x87, 0x8e, 0x84, 0xf2, 0x30, 0x8e, 0xfe, 0x89, 0x1f, 0x5e, 0x47, 0x8d, 0x20, 0x43, 0x5c, 0x8a, 0x79, + 0xe1, 0xf1, 0x9c, 0x0d, 0x2e, 0xf1, 0x5c, 0x78, 0x87, 0xa3, 0x59, 0x1c, 0xc3, 0x79, 0x67, 0x8b, 0x56, 0xe5, 0x15, + 0xad, 0x1c, 0x57, 0x2e, 0xa4, 0x8b, 0x5e, 0xd6, 0x20, 0x0f, 0xa9, 0xa2, 0xc5, 0x6d, 0x38, 0x4d, 0x23, 0x13, 0x29, + 0x3b, 0x1b, 0x4f, 0x67, 0xa7, 0x2c, 0xed, 0x69, 0x39, 0x2c, 0x4b, 0x8a, 0xba, 0x90, 0x28, 0xcc, 0x1c, 0x92, 0x98, + 0x0a, 0x86, 0x5a, 0x6a, 0xcb, 0xb2, 0xa8, 0xa3, 0x18, 0x2a, 0x4d, 0x8d, 0x91, 0xa2, 0x5b, 0x4a, 0xf1, 0x2f, 0xb5, + 0x1c, 0x97, 0x5e, 0x1a, 0x0c, 0x23, 0x93, 0x47, 0x61, 0x19, 0xeb, 0x4b, 0x39, 0xbc, 0x74, 0xd3, 0x2f, 0xed, 0xee, + 0x35, 0xdd, 0x15, 0x82, 0xb6, 0x4c, 0xbe, 0xa9, 0xff, 0x83, 0xe6, 0x96, 0x3d, 0x60, 0x6c, 0xf6, 0xe3, 0xc3, 0x61, + 0xd3, 0xc6, 0x98, 0x95, 0x72, 0x8a, 0x8c, 0x9d, 0xaf, 0xd2, 0xc6, 0xd2, 0x51, 0xdf, 0xdd, 0x1d, 0x16, 0xc1, 0x61, + 0xf5, 0xe6, 0x08, 0x04, 0x59, 0x1b, 0x1f, 0x5c, 0x82, 0x36, 0xdd, 0xa4, 0x4b, 0x1f, 0x1e, 0x95, 0x21, 0x86, 0xbd, + 0x46, 0x2b, 0x46, 0xa0, 0xe7, 0xd2, 0xc6, 0xae, 0x80, 0x07, 0x23, 0x50, 0xc4, 0x45, 0xc7, 0x96, 0xb2, 0x9a, 0xb2, + 0x1a, 0x60, 0xce, 0x1a, 0xc4, 0x73, 0xd4, 0x99, 0x86, 0x7a, 0xee, 0x9e, 0xf4, 0x80, 0x11, 0x3b, 0x72, 0xf6, 0xeb, + 0x68, 0x31, 0xb4, 0x6c, 0xdd, 0xae, 0x6d, 0xda, 0xb8, 0x56, 0x4a, 0x0c, 0x78, 0xb4, 0x6e, 0x40, 0x6c, 0x13, 0x99, + 0xe9, 0x50, 0x28, 0xfc, 0xb8, 0x15, 0x3e, 0x63, 0xfd, 0x19, 0x0f, 0xd9, 0x53, 0x16, 0x37, 0xd1, 0xcc, 0x26, 0x58, + 0x82, 0xdf, 0x99, 0x0a, 0x22, 0xe3, 0x5d, 0xa7, 0xd2, 0x39, 0x42, 0xde, 0x94, 0x7a, 0xf4, 0x59, 0xa0, 0x2c, 0x8d, + 0xa8, 0xc4, 0xd1, 0xa8, 0x1a, 0x0b, 0xff, 0x77, 0xf6, 0x12, 0xdd, 0xc2, 0xbf, 0x56, 0xd8, 0x70, 0x5f, 0x11, 0x1b, + 0x97, 0x70, 0xcc, 0xf4, 0x6a, 0xdb, 0x9d, 0x15, 0x22, 0x28, 0xe0, 0xf3, 0x45, 0xef, 0xcd, 0x66, 0x9a, 0x09, 0x1a, + 0xef, 0xf0, 0xf4, 0x34, 0xcd, 0x67, 0xe4, 0x46, 0x68, 0xa6, 0x61, 0x6d, 0x4a, 0x8c, 0x83, 0xc0, 0xc5, 0x9e, 0x40, + 0x73, 0x53, 0x06, 0x26, 0x98, 0xe7, 0xc5, 0x96, 0x4f, 0xda, 0x3a, 0xdb, 0x03, 0x69, 0xb8, 0x69, 0x78, 0x7a, 0x57, + 0xda, 0x3e, 0xa6, 0xb3, 0xb8, 0xaa, 0xb5, 0xce, 0xda, 0x60, 0x14, 0x3f, 0xa0, 0x5f, 0x13, 0x41, 0xab, 0x97, 0x24, + 0x99, 0xa0, 0x39, 0xb4, 0x18, 0x65, 0xaf, 0x3a, 0xe3, 0xa4, 0x07, 0x68, 0xc9, 0xb8, 0x20, 0x6d, 0x75, 0xd9, 0x12, + 0x32, 0x47, 0xd1, 0x06, 0x68, 0x4a, 0x5a, 0xb1, 0x47, 0x5c, 0x65, 0x22, 0xff, 0xbc, 0xd0, 0xdb, 0x15, 0x61, 0xb9, + 0xfd, 0x5d, 0xe9, 0x86, 0x45, 0x5d, 0xec, 0xdf, 0x9f, 0x80, 0x35, 0xf2, 0x8f, 0xa1, 0xfd, 0x51, 0x40, 0x34, 0xfc, + 0xac, 0xe0, 0x76, 0xd0, 0x66, 0xaf, 0xec, 0xd7, 0x36, 0xf5, 0x53, 0x8b, 0xdd, 0xf0, 0xc8, 0xe1, 0x3f, 0x73, 0xb2, + 0x86, 0xa7, 0x3c, 0x70, 0xbd, 0x30, 0xb9, 0xfd, 0xc5, 0xdd, 0x8a, 0xde, 0xf2, 0x39, 0x6a, 0x37, 0xad, 0x7c, 0x28, + 0x06, 0x2f, 0x77, 0x6a, 0x77, 0xa2, 0x16, 0x35, 0x04, 0xa4, 0x5a, 0x38, 0x27, 0xae, 0x9f, 0x15, 0xa4, 0x05, 0x5a, + 0x98, 0xd3, 0xd1, 0x2d, 0x89, 0xf7, 0xac, 0x21, 0xeb, 0xa7, 0x0b, 0xb0, 0xe9, 0xd6, 0x2f, 0x90, 0x4a, 0x8a, 0x99, + 0x2c, 0xcd, 0x26, 0x14, 0x5f, 0x71, 0xd4, 0x69, 0x3a, 0xb8, 0x2f, 0xa1, 0x0d, 0xd4, 0xb0, 0xee, 0xc6, 0xe3, 0xfc, + 0xa9, 0xda, 0x13, 0x06, 0xdc, 0x30, 0x56, 0x87, 0xfc, 0x05, 0x69, 0x40, 0x6f, 0x47, 0x33, 0x2b, 0xb2, 0x1f, 0x04, + 0x03, 0xf0, 0xa9, 0xab, 0x80, 0x68, 0x81, 0x7e, 0xcb, 0x87, 0x13, 0x85, 0x2a, 0x88, 0x51, 0xd9, 0x1b, 0x60, 0x19, + 0xa0, 0xc8, 0xb6, 0x65, 0xf1, 0x5e, 0xb6, 0x62, 0x44, 0xdb, 0x97, 0xd0, 0x99, 0x3e, 0xc8, 0x10, 0x56, 0x5d, 0x21, + 0xc0, 0x9c, 0xee, 0x84, 0x6f, 0xeb, 0x7c, 0xf8, 0xdc, 0x19, 0x7b, 0xde, 0xd1, 0x2d, 0x6e, 0xcb, 0x32, 0x0d, 0x0d, + 0x55, 0x32, 0x0e, 0xdd, 0x8b, 0xd6, 0x96, 0xe2, 0xd6, 0xb5, 0x42, 0x3c, 0xfe, 0x42, 0x9e, 0x18, 0x93, 0xde, 0x85, + 0xc8, 0x30, 0x23, 0x98, 0x99, 0x45, 0xbd, 0xb4, 0xcc, 0x87, 0xc9, 0xe9, 0xa2, 0x81, 0xa0, 0x15, 0x7c, 0x5b, 0x0f, + 0xae, 0x8b, 0x3a, 0x7c, 0xbf, 0xae, 0x12, 0xad, 0x6f, 0xfa, 0xa9, 0xdf, 0x20, 0xbb, 0x9c, 0xe3, 0xa0, 0xcb, 0x0b, + 0x38, 0xbe, 0x0f, 0x2e, 0x6d, 0x58, 0xed, 0x6d, 0xaa, 0xf9, 0x27, 0xcc, 0xf2, 0x2d, 0x09, 0xcb, 0x4f, 0x1d, 0x0e, + 0x46, 0x09, 0x19, 0x4e, 0x3e, 0xc2, 0xb9, 0xb0, 0x45, 0x97, 0x7c, 0xb2, 0xa3, 0xa5, 0xa7, 0x81, 0x15, 0xd1, 0x2e, + 0xfc, 0x86, 0x6f, 0xeb, 0xb6, 0x10, 0x41, 0xdc, 0x60, 0x19, 0x03, 0x9e, 0x91, 0xca, 0x89, 0x54, 0x75, 0x98, 0xc0, + 0x34, 0x97, 0x30, 0x0d, 0xec, 0xd6, 0x00, 0x9a, 0x39, 0x99, 0x94, 0xb8, 0x02, 0x7d, 0x2a, 0xd5, 0xce, 0xab, 0x92, + 0x8c, 0xfe, 0x42, 0xd0, 0xce, 0xf5, 0x62, 0x46, 0x99, 0x97, 0xdb, 0xcd, 0x2e, 0xd2, 0xbc, 0x26, 0xc5, 0xd0, 0x0e, + 0x64, 0x76, 0x35, 0x0e, 0x99, 0xba, 0x4b, 0x6e, 0x3c, 0x38, 0xa9, 0x2e, 0x0c, 0xc2, 0x01, 0x42, 0x91, 0xa6, 0xd5, + 0x76, 0x08, 0xb3, 0xe8, 0xad, 0x29, 0xbc, 0x3b, 0x14, 0xae, 0x96, 0x08, 0x28, 0x41, 0xc4, 0x49, 0x2f, 0x3a, 0x54, + 0xf1, 0xe0, 0x6e, 0xd4, 0x9d, 0x41, 0x74, 0x95, 0x8b, 0xe5, 0x72, 0x7c, 0xaf, 0xc5, 0x9d, 0x60, 0x5a, 0x3d, 0xf6, + 0x7e, 0x20, 0x46, 0x7b, 0xbe, 0x11, 0x5a, 0x22, 0xfb, 0x0c, 0x55, 0xa5, 0xf7, 0xed, 0x87, 0xb4, 0x42, 0x4f, 0xe1, + 0xb1, 0x5a, 0x21, 0x51, 0xec, 0xd4, 0xb9, 0x0b, 0x34, 0x2b, 0x11, 0x77, 0x59, 0x26, 0x1e, 0x42, 0xbf, 0xab, 0x71, + 0xb7, 0x65, 0xd6, 0x27, 0x81, 0xb8, 0x9e, 0x44, 0x8a, 0xe5, 0x60, 0xad, 0xe1, 0x1d, 0xa9, 0xf3, 0x34, 0x78, 0x9b, + 0x73, 0xeb, 0x97, 0x4c, 0x35, 0x8e, 0x16, 0xb1, 0xfc, 0x21, 0x3d, 0x32, 0xa2, 0x00, 0xd5, 0xbf, 0xe9, 0x08, 0x48, + 0x5c, 0x99, 0xf1, 0xfb, 0xaa, 0xe3, 0x7a, 0x6e, 0xdd, 0xf0, 0x2c, 0xb1, 0x22, 0x62, 0xe1, 0xfc, 0x7d, 0x0d, 0x10, + 0x28, 0x2c, 0x67, 0x1b, 0xae, 0x79, 0x38, 0xea, 0xb2, 0xeb, 0xc1, 0x9d, 0xc2, 0x98, 0xba, 0x4e, 0x47, 0xd4, 0x1b, + 0xee, 0x6a, 0x2d, 0xd6, 0x12, 0xde, 0x54, 0x5a, 0x6c, 0x4e, 0x73, 0xc5, 0x1a, 0x97, 0x4d, 0x95, 0x5a, 0x27, 0x30, + 0xe9, 0x5a, 0xa7, 0x3f, 0x8e, 0xa1, 0x86, 0x32, 0x11, 0xd7, 0x44, 0x1d, 0x5c, 0x96, 0x4d, 0x51, 0xee, 0x32, 0xc1, + 0x49, 0xb2, 0xc1, 0x1d, 0x10, 0xa9, 0x5a, 0x5c, 0xe6, 0xb8, 0x69, 0x43, 0xa4, 0xf8, 0x4e, 0xba, 0xa6, 0x48, 0x8a, + 0xd3, 0xf4, 0xc2, 0xd3, 0xca, 0xf2, 0xa7, 0x4e, 0x69, 0x36, 0xbd, 0x43, 0x8a, 0xac, 0x28, 0x24, 0xee, 0x15, 0x14, + 0xe3, 0xa1, 0x45, 0x7f, 0x96, 0x39, 0x22, 0x3b, 0xd8, 0xde, 0x2d, 0xd7, 0x0d, 0xef, 0xbd, 0x3f, 0x5f, 0x8e, 0x44, + 0x74, 0x61, 0x7c, 0xb9, 0x84, 0x34, 0x4a, 0xf6, 0x13, 0x91, 0x17, 0x26, 0xa4, 0xb3, 0x57, 0x45, 0x02, 0x8e, 0xf4, + 0xca, 0x45, 0x5a, 0x57, 0xae, 0x15, 0xa1, 0xf7, 0x8b, 0x00, 0xef, 0x98, 0x51, 0x21, 0x66, 0x46, 0x8d, 0xe2, 0xfd, + 0x24, 0x42, 0x6c, 0x66, 0x2a, 0x36, 0x64, 0x53, 0x33, 0xd7, 0x1d, 0xca, 0x5c, 0x86, 0x4a, 0x1c, 0x89, 0x46, 0xe5, + 0x10, 0x1c, 0x9d, 0xb9, 0xde, 0xa3, 0xb0, 0xae, 0x60, 0xce, 0x5c, 0x30, 0xf2, 0x60, 0x75, 0xb1, 0xfe, 0xc2, 0x9d, + 0xa0, 0x3f, 0x4e, 0x44, 0xbf, 0xf3, 0x52, 0x53, 0x0d, 0xf0, 0xd0, 0x6c, 0xbb, 0x4e, 0xa0, 0x31, 0x05, 0x5a, 0x20, + 0xbd, 0x9a, 0x20, 0x68, 0xf8, 0xa4, 0x19, 0xe6, 0xa0, 0xa7, 0xfa, 0xe6, 0xd7, 0x2f, 0x2d, 0x6c, 0xcb, 0xb4, 0xad, + 0x30, 0x86, 0x5d, 0x1a, 0xb8, 0x33, 0x29, 0x34, 0xc4, 0xb0, 0xf5, 0xe1, 0x6c, 0x9b, 0xc8, 0xe5, 0xbe, 0x67, 0xb5, + 0x90, 0x4c, 0xd3, 0x19, 0x8e, 0xfc, 0xfb, 0xa4, 0xb3, 0x09, 0xc7, 0x31, 0x28, 0x3d, 0xf9, 0xb2, 0x25, 0x27, 0x35, + 0x4a, 0xeb, 0xd6, 0xd5, 0x05, 0x5c, 0x4f, 0x46, 0x32, 0x10, 0x0f, 0xd3, 0x88, 0xe5, 0x07, 0x70, 0x4d, 0xe5, 0x65, + 0x47, 0x1b, 0x7b, 0x20, 0x01, 0x58, 0xb6, 0x6b, 0x5e, 0x66, 0xd5, 0xc8, 0x57, 0x71, 0xc5, 0xe2, 0xeb, 0x9d, 0x64, + 0xca, 0x44, 0xb0, 0xaf, 0xe2, 0xdb, 0x17, 0xcc, 0x27, 0xb5, 0xb7, 0x43, 0xb4, 0x33, 0x8b, 0x84, 0xfd, 0x6a, 0x9b, + 0xb0, 0x90, 0x87, 0x07, 0x21, 0x61, 0xe3, 0x02, 0x36, 0x13, 0xf4, 0xe9, 0xd8, 0x2c, 0xe4, 0xae, 0xbb, 0xee, 0xbe, + 0x5b, 0xfe, 0xe4, 0xc6, 0x04, 0xb0, 0xe8, 0x96, 0x08, 0x2c, 0x24, 0x2a, 0x63, 0xa2, 0x74, 0x63, 0x7e, 0xae, 0xf6, + 0x7c, 0x55, 0x2a, 0xb5, 0x9a, 0x3d, 0x3f, 0xf0, 0xee, 0x78, 0xf8, 0xae, 0x8d, 0xeb, 0x4c, 0x68, 0x95, 0x79, 0xaf, + 0xfb, 0x07, 0x80, 0x9d, 0x79, 0x38, 0xef, 0x26, 0xf3, 0x9d, 0xa4, 0x5b, 0xa5, 0xcd, 0x3b, 0x61, 0x5a, 0xf8, 0xe5, + 0x17, 0x62, 0xaf, 0xf8, 0x4a, 0x07, 0xd1, 0xaf, 0x7a, 0xc1, 0x46, 0x13, 0x13, 0xf2, 0xec, 0x75, 0x44, 0x8b, 0x7e, + 0x6c, 0xa0, 0xe9, 0x9b, 0x72, 0x59, 0xea, 0x94, 0x61, 0xea, 0x90, 0xfd, 0xe1, 0x51, 0x1d, 0x06, 0xd0, 0xdc, 0x26, + 0x01, 0xf1, 0x5f, 0x40, 0xe6, 0xdc, 0x41, 0x80, 0x13, 0x45, 0x92, 0x8e, 0x5f, 0xfa, 0xf4, 0x1a, 0x9a, 0x3c, 0xcc, + 0x9a, 0x0d, 0xc5, 0x65, 0x1b, 0xb8, 0x59, 0x0b, 0xca, 0x43, 0x83, 0xa8, 0xb3, 0xf7, 0x88, 0x6e, 0x2f, 0xed, 0xdd, + 0x97, 0x77, 0xef, 0x28, 0x59, 0x27, 0xa1, 0x62, 0x30, 0xa1, 0xfc, 0x4b, 0xd9, 0xcf, 0x69, 0x4f, 0xa5, 0x2b, 0x7b, + 0xa1, 0xe0, 0xd3, 0xda, 0xa0, 0x1a, 0x3b, 0xb0, 0x80, 0xf6, 0x22, 0x01, 0x15, 0xbb, 0xad, 0xb0, 0xae, 0x0a, 0x6c, + 0x3f, 0x89, 0xba, 0x92, 0x79, 0x28, 0xc0, 0xe1, 0xbf, 0x34, 0x84, 0x24, 0x64, 0x31, 0xf7, 0xeb, 0xab, 0x23, 0x6c, + 0xeb, 0xb8, 0xc6, 0x6c, 0x1e, 0xea, 0xa4, 0x87, 0xe5, 0x98, 0x37, 0xff, 0x83, 0x0a, 0x22, 0x2e, 0xab, 0xf1, 0x77, + 0x6d, 0x97, 0x76, 0xf4, 0x3a, 0x0c, 0x2b, 0x19, 0xab, 0x28, 0xff, 0xb0, 0x57, 0x3d, 0x72, 0x82, 0xf8, 0xdf, 0x81, + 0xd2, 0xbf, 0xa6, 0xb9, 0xe1, 0xed, 0x44, 0x3c, 0x4c, 0x52, 0x6d, 0xa6, 0x22, 0x0e, 0xa7, 0x74, 0xbf, 0x22, 0x19, + 0xd2, 0x44, 0xa2, 0x87, 0x4f, 0xf2, 0x91, 0xc6, 0xc3, 0x2a, 0x65, 0x1b, 0x32, 0x6c, 0xb6, 0xc6, 0x82, 0xa6, 0xed, + 0x73, 0xf7, 0xe7, 0x53, 0x6f, 0x86, 0x6a, 0x9d, 0xc0, 0xe6, 0xa5, 0x87, 0x2d, 0xb6, 0xee, 0x29, 0xa6, 0x7e, 0x0a, + 0xaa, 0x72, 0xc4, 0x1b, 0x62, 0x62, 0xaf, 0x54, 0x08, 0x3c, 0xf3, 0xe4, 0xc2, 0xc7, 0xc1, 0xfa, 0x8b, 0x63, 0x1f, + 0x2f, 0x70, 0xfe, 0x98, 0x9d, 0xe1, 0x9e, 0x8f, 0xbc, 0x7e, 0xcd, 0x8a, 0xdc, 0x11, 0x3b, 0x1d, 0xc5, 0x43, 0x2e, + 0xba, 0x13, 0xaa, 0x4a, 0x58, 0x2e, 0xcc, 0x55, 0xcb, 0xb5, 0x31, 0x28, 0x15, 0xca, 0x8a, 0xdc, 0x07, 0x7c, 0xb3, + 0xfb, 0x89, 0x55, 0xbe, 0x07, 0x65, 0x81, 0xef, 0x93, 0x08, 0xa4, 0xfa, 0x47, 0x99, 0x62, 0x0e, 0x98, 0x97, 0x66, + 0x17, 0xa9, 0x3f, 0xa1, 0x94, 0x03, 0x0f, 0x01, 0xdf, 0x1c, 0x70, 0x69, 0x68, 0xeb, 0x7f, 0xd0, 0xbe, 0xe7, 0x79, + 0xdb, 0xa7, 0x6c, 0x07, 0x4e, 0x62, 0x48, 0x09, 0x3b, 0x99, 0xa6, 0xa7, 0x38, 0xf7, 0x1e, 0x2b, 0xb4, 0xfa, 0x76, + 0xba, 0x6f, 0xd8, 0x1a, 0x2f, 0x5f, 0x28, 0xde, 0xb5, 0xa9, 0xfc, 0x49, 0x64, 0x51, 0x48, 0x6a, 0xb2, 0x67, 0xca, + 0x99, 0x5c, 0x9c, 0x15, 0x9e, 0x37, 0x18, 0xaa, 0x78, 0xc4, 0xd5, 0x12, 0xe7, 0xec, 0x3d, 0xba, 0x8a, 0x13, 0xde, + 0x90, 0x06, 0x02, 0x54, 0xb2, 0x0b, 0x8e, 0x18, 0x28, 0x6b, 0xcb, 0xfa, 0xb2, 0xdd, 0xed, 0xc7, 0x3a, 0xdc, 0xc1, + 0xd1, 0x48, 0x55, 0x45, 0xbe, 0x46, 0x07, 0x22, 0x63, 0x20, 0xa8, 0xf5, 0xfc, 0x26, 0xba, 0xd0, 0xfc, 0x6c, 0x92, + 0x58, 0x7f, 0x97, 0x75, 0xa3, 0xe4, 0x3f, 0xeb, 0x71, 0xe7, 0x08, 0x90, 0xc7, 0x79, 0x99, 0xc1, 0xc8, 0x97, 0x09, + 0xd1, 0x38, 0x1f, 0x7b, 0xb6, 0x86, 0xc0, 0x73, 0x8d, 0x16, 0x8b, 0x5e, 0x53, 0xc8, 0x7e, 0x6b, 0x76, 0x36, 0x12, + 0x2e, 0x3c, 0xce, 0x2e, 0xdc, 0x09, 0x17, 0x7e, 0xc3, 0x42, 0x7a, 0x0f, 0xb3, 0xe9, 0x78, 0x68, 0x16, 0x80, 0x52, + 0x43, 0xe2, 0xbf, 0xe8, 0xe3, 0xfd, 0xc4, 0x79, 0x9e, 0x36, 0x5b, 0x5e, 0xb4, 0x62, 0xc6, 0xf8, 0xfa, 0x76, 0x4b, + 0xb8, 0x0c, 0x34, 0x71, 0x8a, 0x98, 0xcf, 0x19, 0x46, 0x26, 0x8c, 0x86, 0xea, 0xf5, 0xf8, 0x02, 0x6f, 0x40, 0xc8, + 0x58, 0xb6, 0xd6, 0x6d, 0xed, 0x06, 0x5a, 0x6f, 0xb9, 0x48, 0xfb, 0x94, 0x6e, 0x22, 0x09, 0xd9, 0x78, 0x3e, 0x71, + 0xa6, 0xba, 0xd0, 0x4b, 0x83, 0xfd, 0x89, 0xc4, 0x8d, 0xf3, 0x47, 0x30, 0x04, 0xc7, 0xc1, 0x2e, 0xf8, 0x74, 0x9c, + 0x70, 0xb1, 0x38, 0x90, 0x91, 0x7a, 0x39, 0xbd, 0xd4, 0x47, 0x9b, 0x87, 0x57, 0x70, 0xe1, 0x7c, 0xf7, 0x88, 0xd5, + 0x2e, 0x8f, 0x06, 0x18, 0xd8, 0x8c, 0x9c, 0x90, 0xab, 0x39, 0x12, 0xfd, 0x92, 0x69, 0x6c, 0xd6, 0x50, 0xe7, 0x72, + 0x62, 0x29, 0xa6, 0x40, 0xae, 0x91, 0x4b, 0xbf, 0xc5, 0xf3, 0x57, 0x90, 0x6b, 0x58, 0x7e, 0xf0, 0x8d, 0x72, 0x39, + 0xb3, 0x4b, 0x75, 0xf7, 0x23, 0xc7, 0x9d, 0xeb, 0x5c, 0x1e, 0x7f, 0x03, 0x99, 0xcf, 0xf6, 0x85, 0xdf, 0xee, 0x6f, + 0x36, 0x35, 0xc7, 0x3f, 0xbd, 0x51, 0x0f, 0x36, 0xbb, 0xde, 0xde, 0xcb, 0x6f, 0xc1, 0x97, 0xf5, 0xf7, 0x08, 0x51, + 0x8f, 0xd3, 0xaa, 0x30, 0x2f, 0xb6, 0x6e, 0xfe, 0xeb, 0x37, 0xfb, 0x25, 0xe4, 0x66, 0x1f, 0xa2, 0x04, 0xd7, 0x1c, + 0x51, 0xe5, 0x2b, 0xb7, 0x6b, 0xd8, 0x91, 0x83, 0xbf, 0x2e, 0xa7, 0x9c, 0x77, 0xb5, 0x09, 0xad, 0x6d, 0x92, 0xe3, + 0x66, 0xdd, 0x55, 0x21, 0xb8, 0xc2, 0x99, 0xe8, 0xd6, 0x36, 0xac, 0x42, 0x4c, 0x96, 0x6f, 0xef, 0x0c, 0xee, 0x57, + 0x61, 0xc2, 0xde, 0x9e, 0x7b, 0xb3, 0x1c, 0xf8, 0x33, 0x90, 0x77, 0x99, 0xa5, 0xd5, 0x6f, 0x7e, 0x5e, 0xe5, 0xbe, + 0x0f, 0x78, 0xd3, 0xff, 0x57, 0x50, 0x9d, 0x39, 0x17, 0x2e, 0x5e, 0x5c, 0x88, 0xaf, 0x74, 0x83, 0x4b, 0xe8, 0x36, + 0xc7, 0x79, 0x03, 0x5f, 0x3a, 0x5c, 0x55, 0xf0, 0x3c, 0xb4, 0x64, 0x94, 0x47, 0xb5, 0x1c, 0x9b, 0xb9, 0x13, 0xb0, + 0x6c, 0x2b, 0xf3, 0x5b, 0x6c, 0x14, 0x00, 0x93, 0x2a, 0x48, 0x86, 0x32, 0x84, 0x7f, 0x86, 0x98, 0xfb, 0xaa, 0x70, + 0xf9, 0x4a, 0x44, 0xc7, 0x6e, 0xd7, 0x48, 0x4d, 0x8b, 0x2e, 0x82, 0xcb, 0x06, 0xf5, 0xe3, 0x9d, 0xf0, 0x0f, 0xf5, + 0xef, 0x73, 0xed, 0x84, 0x7f, 0xce, 0xb5, 0x11, 0xfe, 0x5e, 0x29, 0x21, 0xfc, 0xb3, 0xd2, 0x85, 0xa9, 0xb5, 0xe3, + 0xac, 0xaf, 0x5d, 0xd8, 0xd7, 0x76, 0xf6, 0x18, 0xa8, 0x3d, 0xb4, 0xf7, 0x75, 0x0e, 0xda, 0x89, 0xfd, 0x5a, 0x6f, + 0xc9, 0x01, 0x9f, 0xd7, 0x55, 0x96, 0x6c, 0x7c, 0xbe, 0xe8, 0xee, 0x73, 0xaa, 0x56, 0x3a, 0x1e, 0xa0, 0xfc, 0xe2, + 0x71, 0x58, 0xd7, 0x55, 0x94, 0xcd, 0x99, 0xa5, 0xd2, 0x4a, 0x67, 0x77, 0x0f, 0xc5, 0xd3, 0xc9, 0x23, 0xe8, 0x4c, + 0xd7, 0x70, 0x46, 0x2a, 0x13, 0xf8, 0x45, 0xd2, 0x8f, 0x8d, 0x4a, 0x2f, 0x1a, 0x2f, 0xde, 0x3d, 0x30, 0xe7, 0xf9, + 0xcb, 0x59, 0x44, 0x64, 0x5a, 0x41, 0xec, 0x1d, 0x4c, 0xc3, 0x61, 0xd6, 0x62, 0xbb, 0x03, 0x32, 0xdb, 0xb2, 0x95, + 0x58, 0x20, 0x84, 0xa1, 0x6d, 0xe1, 0xbf, 0x09, 0xd0, 0xaa, 0x36, 0x32, 0x49, 0xe6, 0x66, 0x4c, 0x9a, 0x46, 0xcf, + 0x41, 0x83, 0xd8, 0x0f, 0x65, 0xba, 0x23, 0x4e, 0x39, 0x3c, 0xaf, 0xe2, 0x0a, 0xb2, 0xfa, 0x13, 0xa5, 0xf8, 0x25, + 0x67, 0x0f, 0x77, 0x9f, 0x04, 0x34, 0xf8, 0x7f, 0x4d, 0xb6, 0x83, 0xfe, 0x84, 0xb6, 0xc6, 0x29, 0x97, 0x44, 0xda, + 0x2f, 0x94, 0xbc, 0x3d, 0xf7, 0x25, 0xbb, 0xbe, 0x75, 0xc6, 0x70, 0x7e, 0x6e, 0x42, 0x20, 0x77, 0xd5, 0x7e, 0xba, + 0xbf, 0x1f, 0x53, 0x91, 0x4d, 0xd7, 0x3d, 0x27, 0x58, 0xe3, 0x40, 0x2a, 0xd9, 0xcc, 0xe4, 0xfc, 0xfc, 0xd5, 0xff, + 0xd2, 0xdf, 0xa7, 0x94, 0x83, 0xbe, 0xe8, 0x4f, 0x7e, 0x77, 0x9c, 0x28, 0xa7, 0x5e, 0xe7, 0xcb, 0x07, 0xcf, 0x2a, + 0xe5, 0x7a, 0x40, 0xd7, 0x71, 0xba, 0xeb, 0x9f, 0xd4, 0x19, 0xed, 0xd0, 0x7c, 0x36, 0xfd, 0xcf, 0x29, 0xe5, 0x80, + 0xaf, 0xfa, 0xf3, 0xe9, 0x3d, 0x2c, 0xfe, 0xa1, 0x19, 0x7c, 0x64, 0x0b, 0x00, 0xe1, 0xf9, 0x51, 0xb5, 0x39, 0x0e, + 0x39, 0x93, 0x3b, 0xd7, 0x15, 0x5e, 0x51, 0xb5, 0x1a, 0x02, 0xb9, 0x58, 0x81, 0x2b, 0xf2, 0x31, 0x4f, 0x64, 0xe3, + 0x1b, 0xb0, 0x4b, 0x99, 0xbd, 0xc7, 0x2b, 0xd2, 0x6e, 0x9b, 0x4f, 0xf1, 0x89, 0x3e, 0x7f, 0x89, 0xba, 0xc9, 0x35, + 0xbc, 0x82, 0xb5, 0xa5, 0xa9, 0x38, 0x78, 0xc0, 0xbe, 0x23, 0x14, 0x06, 0xab, 0x91, 0xda, 0x07, 0x41, 0x4e, 0x6f, + 0x73, 0xb4, 0xc1, 0x38, 0x93, 0xbd, 0xdf, 0xc6, 0x82, 0xa5, 0xf0, 0xf8, 0xa1, 0x63, 0xb7, 0xa0, 0x1d, 0x3e, 0xe5, + 0xa2, 0xff, 0x02, 0xc6, 0xf0, 0x62, 0x00, 0x4e, 0xbc, 0xca, 0xa6, 0xeb, 0x2f, 0xc3, 0x99, 0x6f, 0x74, 0x2d, 0x57, + 0xd6, 0xee, 0xd7, 0xd0, 0xb5, 0x39, 0x3a, 0x93, 0xdc, 0x25, 0xa8, 0x30, 0xc2, 0x9c, 0xe1, 0xc5, 0x7b, 0xb3, 0x36, + 0xe6, 0x0c, 0x63, 0xa2, 0xd7, 0x82, 0x90, 0xd1, 0x7f, 0x26, 0xb8, 0xe6, 0x99, 0x36, 0x98, 0xb7, 0xfd, 0x7b, 0xec, + 0xd0, 0x36, 0x3b, 0xea, 0xad, 0xc6, 0xf7, 0x89, 0x34, 0x18, 0x95, 0x46, 0x63, 0x7d, 0x71, 0xfc, 0x6b, 0x90, 0xeb, + 0x34, 0xd3, 0x9f, 0xe1, 0xec, 0x4c, 0x5b, 0x3b, 0x6f, 0xde, 0xcc, 0xdc, 0xab, 0xe7, 0xf7, 0xfb, 0xe3, 0xfa, 0x68, + 0x18, 0x97, 0x8c, 0xb4, 0x20, 0x37, 0xcd, 0x5f, 0x55, 0x8f, 0xcd, 0x33, 0x73, 0x3c, 0x52, 0x3f, 0xf6, 0x4c, 0x01, + 0x1b, 0xe3, 0xbc, 0x46, 0x9a, 0xf1, 0x59, 0x5e, 0xc7, 0x9d, 0xc9, 0x82, 0x0d, 0x71, 0x3e, 0xdc, 0xde, 0xde, 0x77, + 0xc8, 0x0e, 0x34, 0xf9, 0xcd, 0xc7, 0xa2, 0x78, 0xe7, 0x3b, 0xbf, 0x03, 0xc5, 0xac, 0x6f, 0x97, 0x14, 0x1b, 0xd4, + 0x15, 0x98, 0x0d, 0xb8, 0xfc, 0xb2, 0x5d, 0x84, 0xbb, 0xa6, 0xaf, 0x8e, 0x27, 0x58, 0x52, 0x42, 0x68, 0xa5, 0xe0, + 0xb0, 0xd8, 0x34, 0xa3, 0x28, 0x2d, 0xd6, 0x49, 0xbf, 0xb1, 0x99, 0xb8, 0x5c, 0xbb, 0x81, 0x73, 0x83, 0xa0, 0x56, + 0x71, 0x56, 0xf4, 0x73, 0x44, 0xde, 0xe1, 0x97, 0xb9, 0x6a, 0x1b, 0x88, 0xe1, 0x25, 0xae, 0x59, 0x45, 0xf6, 0xf8, + 0x13, 0x06, 0xf4, 0xa8, 0x8f, 0xce, 0x21, 0x78, 0xf8, 0xa1, 0xc7, 0x32, 0x6f, 0xb6, 0x74, 0x78, 0xe7, 0x58, 0x57, + 0xdd, 0xa9, 0xf5, 0xd4, 0xfc, 0x35, 0xa9, 0xcd, 0xca, 0xd3, 0x11, 0x55, 0x43, 0xa2, 0xf6, 0x6a, 0x7c, 0xf6, 0xe3, + 0x41, 0xa5, 0x3d, 0x9b, 0x5f, 0x26, 0x5e, 0x18, 0x69, 0x79, 0x98, 0x78, 0x18, 0xc7, 0x41, 0x8a, 0x9a, 0xca, 0x2b, + 0xfe, 0x31, 0x18, 0x16, 0x71, 0xcd, 0xf6, 0xdb, 0x75, 0xf0, 0x4a, 0x48, 0xe9, 0xca, 0xb5, 0xd7, 0x15, 0xe8, 0xd8, + 0xe1, 0x57, 0xf7, 0xd3, 0xe9, 0x4c, 0x80, 0xca, 0x5f, 0x85, 0x49, 0xc0, 0x40, 0x24, 0xc2, 0x13, 0xcd, 0xc2, 0x8b, + 0xc2, 0x0f, 0x60, 0x8e, 0x99, 0x61, 0x35, 0xc0, 0x53, 0xd1, 0x5f, 0xaf, 0x0b, 0x61, 0x1d, 0x8e, 0xe0, 0x45, 0x6e, + 0x6f, 0x3e, 0xe6, 0xa1, 0x43, 0x91, 0x31, 0x72, 0xe6, 0x0d, 0x4b, 0xca, 0x1b, 0xea, 0xfd, 0x2a, 0x14, 0x7f, 0x03, + 0x83, 0x38, 0x67, 0x03, 0x50, 0x48, 0xed, 0x79, 0x04, 0x00, 0x4b, 0xf2, 0x89, 0x19, 0x78, 0xc3, 0xfb, 0xc1, 0x5e, + 0xb1, 0x5f, 0xfd, 0xce, 0xc5, 0x69, 0x16, 0xee, 0xcc, 0xfb, 0xc4, 0x18, 0xd9, 0x3d, 0x45, 0x10, 0x01, 0x92, 0x2c, + 0xa4, 0x13, 0x7b, 0x0f, 0xf1, 0x2b, 0x03, 0xcc, 0xc0, 0x24, 0x03, 0x38, 0x61, 0x3a, 0xd2, 0xba, 0xe1, 0x27, 0xd7, + 0x86, 0xad, 0xdc, 0x16, 0x4a, 0xb0, 0x88, 0xcc, 0xa3, 0xdb, 0x49, 0x9a, 0xa5, 0x74, 0xdf, 0xec, 0xf3, 0x41, 0xc6, + 0xf1, 0x97, 0xca, 0x79, 0xe2, 0x38, 0x7f, 0x43, 0x10, 0xad, 0x88, 0x28, 0x4f, 0xb7, 0x2e, 0x22, 0xbf, 0x83, 0x52, + 0x31, 0x01, 0xa8, 0x46, 0xc0, 0x54, 0x53, 0xac, 0xf9, 0xc5, 0x1d, 0xd0, 0xcb, 0x07, 0xda, 0x13, 0x8a, 0xfb, 0x59, + 0xa4, 0x58, 0x0f, 0xfb, 0x20, 0xb3, 0xb0, 0xcb, 0xc5, 0x68, 0xb3, 0x63, 0x29, 0x4b, 0xab, 0x68, 0x82, 0x01, 0x0d, + 0x66, 0x38, 0x9d, 0xf4, 0xfe, 0x51, 0x60, 0x66, 0xd3, 0x05, 0x3d, 0xc7, 0xa5, 0xd0, 0x6d, 0x0e, 0x0b, 0x21, 0x8e, + 0x86, 0xd0, 0xe7, 0x61, 0x28, 0x8c, 0x7e, 0x56, 0x9f, 0x58, 0x7d, 0xd6, 0xaf, 0xc5, 0x68, 0xad, 0x31, 0x63, 0x53, + 0x5d, 0x76, 0x5d, 0x01, 0xd8, 0xc8, 0x38, 0x83, 0x15, 0xf0, 0xe7, 0xab, 0x76, 0xb9, 0x46, 0xbc, 0xb1, 0xc9, 0xbf, + 0xb4, 0xc9, 0x6f, 0xa4, 0x9f, 0xc0, 0x8b, 0x90, 0xcc, 0xed, 0x09, 0xac, 0xa9, 0x9c, 0x97, 0xc4, 0x01, 0xd1, 0xe3, + 0x7c, 0x3f, 0x08, 0xfe, 0xa4, 0xb5, 0x78, 0x50, 0x6c, 0x61, 0xd2, 0x4a, 0xa9, 0x13, 0xf5, 0x6a, 0x9d, 0x36, 0xf2, + 0x41, 0x62, 0x12, 0x31, 0xa1, 0xba, 0xb3, 0x9f, 0xe6, 0x59, 0xcd, 0x82, 0xd9, 0x26, 0x2a, 0xf6, 0x0a, 0xdb, 0xb9, + 0x9d, 0x33, 0x24, 0xd9, 0x11, 0x9c, 0xea, 0xb2, 0x6c, 0xb8, 0xbb, 0x6d, 0x4d, 0xdf, 0x2c, 0x7c, 0x4d, 0xd7, 0x70, + 0x8c, 0xbb, 0xa0, 0x73, 0x6b, 0xbc, 0x25, 0xb6, 0x07, 0x83, 0x87, 0xc5, 0x93, 0xb3, 0x53, 0x35, 0xdd, 0x34, 0x32, + 0x73, 0x73, 0xaf, 0xa9, 0xab, 0x85, 0x76, 0x95, 0x80, 0xf9, 0x6c, 0x14, 0xaf, 0xb1, 0x65, 0xae, 0x91, 0x63, 0x6b, + 0x89, 0xbb, 0x65, 0xde, 0xb1, 0x18, 0xb9, 0x1b, 0x18, 0x15, 0xe6, 0x2e, 0x62, 0x98, 0xf9, 0x39, 0xf4, 0xf6, 0xc4, + 0x04, 0x42, 0xfd, 0xdb, 0x7a, 0x32, 0x83, 0x8b, 0x59, 0x1a, 0xc9, 0xb0, 0x1e, 0x94, 0xbd, 0x27, 0x5a, 0xfa, 0x88, + 0xe7, 0x82, 0xc1, 0xb6, 0x6d, 0x8f, 0x9b, 0x9d, 0x19, 0x03, 0x1f, 0x9a, 0x72, 0x07, 0xc1, 0x55, 0x79, 0x05, 0xcd, + 0x33, 0x78, 0x0c, 0x62, 0xf6, 0xed, 0x30, 0x9f, 0x17, 0xa2, 0x6e, 0x9f, 0xe8, 0xe2, 0xbf, 0x80, 0x50, 0xcc, 0x6e, + 0x75, 0xfe, 0xec, 0x5c, 0xbf, 0x0e, 0x1e, 0xb2, 0xc0, 0x63, 0x49, 0x52, 0x64, 0xf8, 0x37, 0x1d, 0x6d, 0x19, 0x8b, + 0x9e, 0x39, 0x8f, 0x5b, 0x12, 0x13, 0x4a, 0x75, 0x86, 0x91, 0x44, 0x79, 0x3d, 0xc2, 0xa2, 0x0a, 0xb1, 0xdb, 0x26, + 0xa4, 0x72, 0x74, 0x45, 0x64, 0x8a, 0x27, 0xc9, 0xcd, 0xce, 0x30, 0x1a, 0x41, 0x86, 0x82, 0x09, 0xaa, 0xda, 0xf7, + 0xa3, 0x39, 0x61, 0x1e, 0xac, 0x69, 0xa2, 0x1e, 0xde, 0x30, 0x65, 0x2c, 0x3c, 0x49, 0xee, 0x6d, 0x47, 0xcc, 0xad, + 0xeb, 0x38, 0x5f, 0x3c, 0x59, 0xb6, 0x72, 0x64, 0x89, 0xab, 0x8a, 0xd6, 0x94, 0xed, 0x83, 0x5a, 0x44, 0x94, 0xa1, + 0x44, 0xc2, 0x81, 0x2d, 0xa8, 0xb7, 0x97, 0xda, 0x6c, 0x20, 0xdc, 0x2b, 0xeb, 0x83, 0x56, 0xac, 0xa6, 0x6b, 0x2b, + 0xa5, 0x60, 0x01, 0x85, 0xb0, 0xd0, 0xd8, 0xb3, 0x9e, 0xdf, 0xae, 0x6b, 0x8a, 0xd7, 0xb7, 0x88, 0xd8, 0xc2, 0x7f, + 0xbc, 0xff, 0x1c, 0x2b, 0x00, 0xa3, 0xee, 0x59, 0x57, 0x14, 0x6f, 0x8d, 0x78, 0x8b, 0xe2, 0xcd, 0x8f, 0x37, 0xbb, + 0x1f, 0xe8, 0x12, 0x6b, 0x63, 0x36, 0xee, 0x5c, 0xa1, 0x75, 0xd8, 0x97, 0x8c, 0xd4, 0x7e, 0x2f, 0x97, 0x9f, 0xc6, + 0xaa, 0xf4, 0x9f, 0x56, 0x55, 0xca, 0xa6, 0x2f, 0x4e, 0xd5, 0x96, 0x2e, 0x23, 0xa4, 0xee, 0xe5, 0x30, 0xdb, 0xb7, + 0x4e, 0x5d, 0xdd, 0x96, 0xe2, 0xb3, 0x31, 0x31, 0x7e, 0xf9, 0xd7, 0xbb, 0x78, 0xa9, 0x61, 0x3a, 0x74, 0xe5, 0x9d, + 0xf7, 0xcc, 0x60, 0xc6, 0x15, 0x96, 0x84, 0x73, 0x34, 0x8b, 0x90, 0x31, 0xe2, 0xa1, 0xdc, 0xb9, 0x03, 0x6e, 0x23, + 0x08, 0x7c, 0x45, 0x57, 0x55, 0x92, 0x59, 0xea, 0x3b, 0x3a, 0xba, 0x38, 0x2c, 0x36, 0x39, 0xfc, 0xbd, 0x40, 0x41, + 0x6b, 0xbb, 0xaa, 0x5c, 0x3b, 0x57, 0x45, 0x4c, 0x55, 0x11, 0xe7, 0x9c, 0x46, 0xef, 0x89, 0x8d, 0x6f, 0xe6, 0xeb, + 0x29, 0xd5, 0xd0, 0x01, 0x29, 0x66, 0x39, 0x16, 0xe5, 0x54, 0x2c, 0x4a, 0x11, 0xb1, 0x7d, 0x09, 0x43, 0x65, 0x35, + 0x09, 0x44, 0xde, 0xdf, 0xb8, 0x71, 0x2c, 0x5f, 0x06, 0x0c, 0x56, 0xd1, 0x07, 0x82, 0xf3, 0x3b, 0x5d, 0x76, 0xf1, + 0x8f, 0xfe, 0x4a, 0xc9, 0x64, 0xd2, 0x0a, 0x43, 0xe0, 0x8e, 0xf8, 0xed, 0xbb, 0x17, 0xc8, 0x00, 0xe7, 0x8c, 0x1a, + 0x03, 0xe6, 0xdc, 0x34, 0x0d, 0x4e, 0x55, 0xb3, 0x0c, 0xda, 0xcd, 0x2b, 0x54, 0x42, 0x12, 0x43, 0x95, 0x36, 0xc3, + 0xaf, 0xb6, 0x48, 0x40, 0xce, 0x3f, 0x70, 0xb8, 0x72, 0x81, 0x54, 0x2e, 0x36, 0x16, 0x0a, 0x7c, 0xee, 0xc0, 0x2f, + 0xd0, 0xad, 0xf8, 0xe0, 0x1f, 0x67, 0x29, 0x8f, 0x34, 0xa0, 0x07, 0x6a, 0x87, 0x4c, 0x56, 0x2d, 0x39, 0x0a, 0x13, + 0x09, 0xa1, 0x0c, 0x3e, 0xe2, 0x2b, 0x32, 0x17, 0x73, 0xac, 0xeb, 0x9c, 0x9f, 0xe0, 0x36, 0x22, 0x06, 0x44, 0x0d, + 0x21, 0x92, 0x9c, 0xd4, 0xba, 0xa1, 0xc2, 0xe2, 0xe8, 0xd2, 0xa2, 0x88, 0x13, 0x24, 0x3b, 0x2f, 0x04, 0xff, 0x32, + 0x14, 0x33, 0x8d, 0x37, 0xfd, 0x1f, 0x27, 0xba, 0x1a, 0x99, 0xc9, 0x2e, 0x9a, 0x79, 0xd1, 0x13, 0x69, 0xc9, 0xe5, + 0x43, 0xa2, 0xd0, 0x3f, 0x88, 0xa3, 0xb7, 0xac, 0x25, 0x52, 0xb0, 0xa4, 0xcb, 0xda, 0xb2, 0xba, 0xb3, 0xb5, 0x72, + 0x8d, 0xc7, 0xdd, 0x53, 0x08, 0x0a, 0x7c, 0xb7, 0x95, 0xbc, 0x04, 0x2e, 0x92, 0x35, 0xb6, 0xdc, 0x27, 0x62, 0x74, + 0x4c, 0x37, 0x4a, 0x56, 0x47, 0xb6, 0xcf, 0x5f, 0x10, 0x4a, 0x72, 0xba, 0x56, 0x62, 0xeb, 0x7f, 0x8e, 0xba, 0xc9, + 0x45, 0x65, 0xab, 0x0e, 0x39, 0x88, 0x9b, 0xa9, 0x85, 0x30, 0x25, 0x7b, 0x27, 0xb0, 0x11, 0x22, 0xc3, 0xc5, 0x24, + 0x0b, 0x72, 0xee, 0xc5, 0x5f, 0x1c, 0x29, 0xf8, 0x45, 0xa4, 0x86, 0xb6, 0x4c, 0xe9, 0x7f, 0xb4, 0x8e, 0xf0, 0x6d, + 0x8f, 0x93, 0x64, 0xf8, 0xcf, 0x0b, 0x6e, 0x5b, 0x8b, 0x1d, 0xb3, 0x41, 0x12, 0xee, 0x9f, 0x99, 0x3e, 0xeb, 0xed, + 0x41, 0xbc, 0x50, 0x26, 0x04, 0x5e, 0xbd, 0x7c, 0xd3, 0xb3, 0xfa, 0xc3, 0xcb, 0xa8, 0xa4, 0x5a, 0x94, 0xef, 0xc7, + 0x5b, 0x83, 0x3d, 0x2a, 0x2f, 0xe0, 0x6f, 0x3f, 0xce, 0x39, 0x30, 0x0f, 0x5f, 0x69, 0xab, 0xb1, 0x84, 0xbd, 0x30, + 0xd8, 0xeb, 0x50, 0xb2, 0x8c, 0x23, 0xbb, 0xd9, 0x18, 0x73, 0xa1, 0x6b, 0x6d, 0xf6, 0x0f, 0x29, 0xd4, 0xea, 0x4e, + 0xf3, 0x1b, 0xbb, 0xaa, 0x15, 0xe6, 0x36, 0x0d, 0x3b, 0x2a, 0x99, 0x69, 0x5f, 0x6e, 0x30, 0x4d, 0x87, 0xec, 0x6d, + 0xa4, 0xb5, 0x7c, 0x73, 0xac, 0x2b, 0x6f, 0x75, 0x01, 0x85, 0x80, 0x09, 0xe7, 0x9a, 0x2b, 0x72, 0xad, 0x95, 0xfd, + 0x60, 0x8a, 0xfd, 0x7e, 0x06, 0x24, 0xa2, 0x2a, 0x92, 0x9a, 0x4d, 0x7d, 0xac, 0xd5, 0xda, 0x93, 0x86, 0x05, 0x96, + 0x4e, 0x2c, 0xc7, 0x9a, 0x17, 0x0c, 0x86, 0x32, 0x55, 0xab, 0x65, 0xee, 0x70, 0x95, 0x3d, 0xd5, 0xf2, 0x92, 0x57, + 0x0c, 0x1c, 0x24, 0x10, 0x9d, 0xf8, 0x1e, 0xee, 0x49, 0xe4, 0x3b, 0x73, 0xfa, 0xc6, 0x4c, 0x86, 0xa8, 0x2e, 0xc4, + 0x0a, 0x46, 0xd5, 0xdb, 0xc0, 0x50, 0x51, 0x35, 0x36, 0x74, 0x57, 0x7a, 0x69, 0x73, 0x1c, 0xee, 0x0b, 0x7b, 0x7c, + 0x41, 0xee, 0x63, 0xfe, 0x9c, 0xf5, 0xcf, 0x83, 0x2c, 0xa0, 0x5d, 0xd6, 0xdb, 0x8c, 0x91, 0x23, 0xbc, 0xde, 0x52, + 0xa9, 0xb2, 0x05, 0x5d, 0xf6, 0x9a, 0xb8, 0xa7, 0x83, 0xea, 0x89, 0x74, 0x13, 0x33, 0xa2, 0xaa, 0x27, 0x91, 0x24, + 0xe8, 0x8b, 0xcd, 0x20, 0x84, 0xe6, 0x49, 0x5a, 0x47, 0x19, 0xb9, 0x07, 0x75, 0x39, 0x32, 0x11, 0x5d, 0xc2, 0x50, + 0x9c, 0xb3, 0x75, 0x35, 0xc2, 0x90, 0x63, 0x3c, 0x16, 0xab, 0x92, 0x07, 0xf5, 0xbe, 0x85, 0x95, 0x5a, 0xb4, 0xe9, + 0x58, 0x2a, 0x2e, 0x37, 0x80, 0x8d, 0x1d, 0xed, 0xa4, 0x01, 0x73, 0x6a, 0xe7, 0x12, 0xec, 0xe4, 0xa6, 0x7a, 0x87, + 0x24, 0x03, 0x41, 0x1e, 0x08, 0x51, 0x18, 0xf0, 0xd5, 0xba, 0x22, 0x00, 0x34, 0xc7, 0x29, 0x12, 0x7f, 0x18, 0xce, + 0xeb, 0x2f, 0x24, 0x9d, 0x84, 0xe3, 0x6e, 0x2c, 0xf0, 0xf0, 0x79, 0x80, 0x46, 0x69, 0x24, 0x9b, 0xef, 0x81, 0x28, + 0x17, 0xf9, 0xab, 0xd8, 0xe8, 0x88, 0x21, 0xc2, 0x81, 0x1f, 0x77, 0x17, 0x92, 0xd6, 0x5b, 0xe0, 0xf2, 0x68, 0x66, + 0xb4, 0x5d, 0x03, 0x70, 0x1f, 0xa9, 0x01, 0xd0, 0x66, 0x43, 0xae, 0x9f, 0x0f, 0x8f, 0x51, 0xaf, 0x10, 0xde, 0x90, + 0x05, 0x7e, 0x5d, 0xa6, 0x36, 0xb7, 0x30, 0x5a, 0xd3, 0x4c, 0xbc, 0x95, 0x5f, 0xa7, 0xfb, 0xba, 0xdb, 0x31, 0xd0, + 0xbf, 0x5c, 0xe2, 0x88, 0x7c, 0x93, 0x54, 0x71, 0xd0, 0x63, 0x8e, 0x9e, 0xe3, 0x92, 0x66, 0x76, 0x6a, 0xc8, 0x73, + 0x93, 0xf2, 0x19, 0xca, 0x81, 0x86, 0x8f, 0xa7, 0x87, 0xec, 0x79, 0x5c, 0x7a, 0xa8, 0x8b, 0x87, 0x84, 0x32, 0x50, + 0x4f, 0xe0, 0xb9, 0x92, 0x40, 0x58, 0x9a, 0x3f, 0x27, 0xe6, 0x9e, 0x3f, 0x4d, 0x41, 0x45, 0x07, 0x2a, 0x9d, 0x9e, + 0x94, 0x05, 0xa4, 0x1e, 0xea, 0x30, 0xc4, 0x84, 0x07, 0xbd, 0x6c, 0xea, 0x7a, 0x5d, 0x47, 0x23, 0x24, 0x4d, 0x28, + 0x48, 0x5c, 0xe0, 0x14, 0x7d, 0x55, 0x32, 0x7f, 0xad, 0x48, 0x37, 0x8a, 0x54, 0x34, 0xb8, 0x97, 0x68, 0x91, 0x35, + 0x30, 0x46, 0x66, 0x47, 0x9a, 0xb2, 0x56, 0x5b, 0x2f, 0x00, 0x7c, 0x38, 0x04, 0x9f, 0x49, 0x44, 0xcc, 0x93, 0x68, + 0x22, 0x9b, 0x09, 0xe5, 0xcf, 0x7e, 0x6c, 0x14, 0xc0, 0xe5, 0x3c, 0x12, 0x34, 0x11, 0xf8, 0x44, 0x09, 0x38, 0x33, + 0x83, 0x0c, 0x67, 0xab, 0xa6, 0x11, 0x18, 0x0b, 0xad, 0xfd, 0x60, 0xd9, 0x27, 0x1c, 0x94, 0xe3, 0x62, 0x61, 0xe4, + 0x76, 0x2d, 0x8e, 0x5c, 0x2c, 0x12, 0x1b, 0x73, 0xf8, 0x6b, 0xfd, 0xdb, 0x24, 0xfd, 0xcb, 0x14, 0x3e, 0x29, 0x51, + 0x75, 0x1c, 0x29, 0xde, 0x74, 0x31, 0xde, 0x3a, 0xe3, 0x2a, 0xb5, 0x1c, 0x26, 0x0c, 0xa6, 0xb5, 0xff, 0x37, 0x1e, + 0xb5, 0x6d, 0x5f, 0x39, 0x9f, 0x82, 0x2b, 0x1e, 0x83, 0xc3, 0xba, 0x33, 0xe7, 0xd7, 0x4d, 0x13, 0x39, 0xb9, 0xdf, + 0x3f, 0xf4, 0x66, 0xd7, 0xdd, 0x46, 0x56, 0xb9, 0x94, 0xe6, 0xcc, 0x0b, 0xa2, 0x0f, 0x23, 0xcb, 0x67, 0x6b, 0xcc, + 0x09, 0x8d, 0x2f, 0x1d, 0x51, 0x2e, 0x3b, 0x3c, 0x7d, 0x11, 0x88, 0x97, 0xa3, 0x7d, 0xb1, 0x03, 0x62, 0x45, 0x89, + 0xb0, 0x67, 0x2a, 0x22, 0x8d, 0x63, 0x60, 0xbd, 0x0a, 0x47, 0x86, 0xc0, 0x29, 0x63, 0xec, 0x9a, 0xf5, 0x0f, 0x5b, + 0x91, 0x7d, 0x0e, 0xc9, 0x26, 0xcb, 0x66, 0xfc, 0x62, 0x3b, 0x5a, 0xaf, 0x44, 0x52, 0xd1, 0xb2, 0xe9, 0x5f, 0xda, + 0x8e, 0xc6, 0x7b, 0x31, 0x0e, 0x89, 0x23, 0x5c, 0xd2, 0x3b, 0xdf, 0xef, 0x1f, 0x46, 0x1d, 0xfe, 0x19, 0xf5, 0x0f, + 0x3f, 0xf8, 0x0f, 0xff, 0x8c, 0xf6, 0xc5, 0x0f, 0xfe, 0xe2, 0x9f, 0x11, 0xbf, 0xf8, 0x41, 0xa2, 0x4c, 0x9a, 0xbe, + 0x7a, 0x3c, 0x0f, 0xa6, 0x8a, 0xa1, 0x5c, 0x9e, 0x91, 0xad, 0x34, 0xc1, 0x2f, 0x7e, 0x48, 0xb8, 0xcf, 0x06, 0x92, + 0x72, 0x32, 0xb8, 0x60, 0x25, 0x2a, 0x59, 0x29, 0x93, 0x02, 0xf8, 0x34, 0xd4, 0x47, 0x34, 0xdb, 0xf7, 0xfc, 0x3b, + 0x95, 0x48, 0x1a, 0x03, 0xf1, 0x62, 0x0a, 0xba, 0x76, 0x4f, 0x9f, 0x79, 0xae, 0x15, 0x61, 0x94, 0xe5, 0x2c, 0xa3, + 0x5e, 0x21, 0x0e, 0x09, 0x31, 0x72, 0x39, 0x5f, 0xda, 0x2c, 0x04, 0x7d, 0xfb, 0x3e, 0xae, 0x53, 0xe5, 0x78, 0x09, + 0x01, 0x4a, 0xd6, 0x86, 0x40, 0x04, 0xf6, 0xb1, 0x17, 0x5f, 0x63, 0xad, 0xbd, 0x99, 0x54, 0xd1, 0x82, 0x6b, 0x72, + 0x30, 0xa6, 0x08, 0x89, 0x7b, 0xfa, 0x57, 0x2c, 0x26, 0x67, 0xe9, 0xa2, 0xcd, 0x2c, 0xdc, 0x13, 0xf4, 0x1c, 0xd0, + 0xa2, 0x18, 0x55, 0x33, 0xe5, 0x8a, 0x68, 0x34, 0xa7, 0x3d, 0xf3, 0x88, 0x93, 0xa5, 0xd8, 0x2e, 0x0b, 0x77, 0xde, + 0xe3, 0x17, 0xfd, 0x1b, 0x9c, 0xa4, 0x62, 0x9e, 0x05, 0xfb, 0x22, 0xc7, 0xf6, 0xde, 0x15, 0xb6, 0xb5, 0x06, 0xd3, + 0x13, 0xce, 0xd6, 0xe2, 0xfa, 0x6a, 0x06, 0x5f, 0x90, 0xf6, 0x63, 0x5b, 0x8a, 0x68, 0xfc, 0x3d, 0x99, 0xd8, 0x04, + 0xb7, 0x2f, 0xe4, 0x6b, 0x4b, 0x8d, 0x36, 0x2b, 0x96, 0x60, 0xc9, 0xed, 0x57, 0x5f, 0xd2, 0xb0, 0xc9, 0x9c, 0x25, + 0x69, 0x54, 0xdd, 0x04, 0x69, 0x53, 0xe0, 0x8b, 0x93, 0x15, 0xc6, 0x23, 0x90, 0x65, 0xee, 0x30, 0x39, 0x1e, 0x57, + 0xb5, 0x1c, 0x55, 0x09, 0x91, 0xf9, 0xac, 0xc2, 0x5a, 0x2d, 0x41, 0xc7, 0x8b, 0x03, 0x11, 0x42, 0x0e, 0xe3, 0xd2, + 0xa9, 0x65, 0x74, 0x5d, 0xd5, 0xde, 0x42, 0x9e, 0xc3, 0x1c, 0x79, 0x0d, 0xb6, 0x84, 0x92, 0xd5, 0x1c, 0x3d, 0xce, + 0x3c, 0xbb, 0xa1, 0x2b, 0xfb, 0xfd, 0xe3, 0x00, 0x1c, 0xbd, 0xd8, 0x7e, 0xc8, 0xe6, 0xae, 0xcf, 0x48, 0x24, 0x90, + 0x48, 0x7c, 0x01, 0xc0, 0x01, 0x00, 0x57, 0xbd, 0xa2, 0xa8, 0x03, 0x83, 0x56, 0xaa, 0x40, 0xcf, 0x14, 0xbc, 0x04, + 0x99, 0xa1, 0xed, 0xa0, 0xf2, 0x47, 0x94, 0xf0, 0xb5, 0x43, 0xb2, 0x98, 0xf0, 0xd2, 0x50, 0xbc, 0x8e, 0x09, 0xed, + 0xb6, 0x98, 0x99, 0x5e, 0xa2, 0xf0, 0x35, 0x52, 0x3a, 0x62, 0x5b, 0x80, 0x06, 0xe1, 0x39, 0x4f, 0x0b, 0x27, 0x45, + 0xe4, 0xe9, 0xb9, 0xf5, 0x6f, 0x79, 0xbb, 0xae, 0xa9, 0x3f, 0xd2, 0x8a, 0xa6, 0xa0, 0x0d, 0x61, 0xae, 0x9e, 0x56, + 0x3d, 0xa3, 0xaf, 0xe0, 0xcb, 0xa2, 0x1c, 0x7a, 0x7d, 0x93, 0xdb, 0x0d, 0xe9, 0xc3, 0x2b, 0x7a, 0x20, 0x9a, 0x74, + 0xf7, 0x1a, 0x09, 0x34, 0x97, 0x08, 0x16, 0xc3, 0x73, 0x5c, 0xda, 0x8d, 0x9f, 0x72, 0x8a, 0x82, 0x58, 0x05, 0x3e, + 0xa4, 0x87, 0x2f, 0x30, 0x64, 0x18, 0xd7, 0x6d, 0x16, 0xc4, 0xd5, 0x40, 0xeb, 0xfd, 0x57, 0x1c, 0xf6, 0xfa, 0x04, + 0x6c, 0x2c, 0x99, 0xaf, 0xd6, 0x2e, 0x8a, 0xbd, 0xe6, 0x8a, 0x74, 0xd7, 0x76, 0x08, 0xfc, 0xb9, 0xf8, 0xe8, 0x6f, + 0xcf, 0x8b, 0xf2, 0x2c, 0x3e, 0x11, 0xbe, 0x77, 0x8a, 0xf2, 0xd6, 0x68, 0x40, 0xfd, 0x71, 0x06, 0xa0, 0xb2, 0x1c, + 0x16, 0xa5, 0xe7, 0xa3, 0x51, 0x2d, 0x6e, 0xaf, 0xc9, 0x22, 0xde, 0x4f, 0xc2, 0x34, 0x9b, 0xaa, 0x3c, 0xb8, 0x37, + 0xe9, 0x85, 0xbe, 0xc7, 0x81, 0xea, 0x5e, 0x1b, 0x97, 0xee, 0xa6, 0x94, 0x20, 0x26, 0x23, 0xa3, 0x99, 0x66, 0x63, + 0xde, 0x86, 0x66, 0x71, 0xaa, 0x5f, 0xd0, 0x27, 0x52, 0x72, 0x20, 0x3b, 0x2b, 0x8b, 0x52, 0x31, 0x29, 0x09, 0xde, + 0xe2, 0xfa, 0xb3, 0x3c, 0x38, 0x30, 0x98, 0x5a, 0x75, 0x1e, 0x30, 0x12, 0xfb, 0x62, 0xf1, 0x11, 0xe8, 0xf8, 0xcd, + 0x1d, 0x64, 0x71, 0xd7, 0xc7, 0x54, 0x1a, 0x0d, 0x3f, 0x88, 0xd1, 0xa9, 0xaf, 0x20, 0x50, 0x53, 0xf7, 0xbf, 0x69, + 0xb2, 0x62, 0xf9, 0xa6, 0xa7, 0x8d, 0xbd, 0xd0, 0xce, 0x0e, 0xee, 0x6a, 0xd3, 0x48, 0x0c, 0x43, 0xfc, 0x93, 0x63, + 0xff, 0xee, 0xa3, 0x4b, 0x9b, 0xbb, 0xb0, 0xda, 0xcb, 0xf2, 0x20, 0x34, 0x08, 0x1d, 0x8a, 0x54, 0xb9, 0x2d, 0xc3, + 0xfa, 0x12, 0xe3, 0xe5, 0x49, 0xff, 0xa3, 0xd7, 0x07, 0x55, 0x8f, 0x5f, 0x60, 0x29, 0x95, 0x40, 0x2a, 0xaa, 0xd8, + 0xa4, 0x71, 0x57, 0xa5, 0xca, 0x72, 0xf7, 0xa7, 0x86, 0xee, 0xb5, 0x59, 0x04, 0x99, 0xea, 0x83, 0x8a, 0x52, 0x6a, + 0xa8, 0x09, 0xa5, 0x2d, 0x60, 0x0a, 0xab, 0x2c, 0x5f, 0xdb, 0xbb, 0xf3, 0x93, 0xaf, 0x94, 0x84, 0x03, 0x3e, 0x86, + 0xc5, 0x34, 0xf0, 0xef, 0x87, 0x48, 0x03, 0x37, 0xb5, 0x21, 0x85, 0x32, 0x86, 0xb4, 0x42, 0x30, 0x1f, 0x29, 0x74, + 0x98, 0xe0, 0x07, 0xce, 0xa0, 0xc8, 0x49, 0xc9, 0x4e, 0xe3, 0x37, 0x75, 0x8f, 0xa5, 0xe3, 0xcc, 0xc4, 0xa0, 0x8b, + 0x3a, 0xd3, 0x74, 0x5e, 0xdd, 0x5d, 0xc0, 0xe5, 0x58, 0xed, 0x75, 0x5d, 0x90, 0x0d, 0x4c, 0xc9, 0x0b, 0x1f, 0x91, + 0x84, 0xe4, 0x84, 0x9e, 0xd2, 0xa1, 0x42, 0x61, 0x38, 0x85, 0x66, 0xcb, 0xe1, 0x1d, 0xdb, 0xb8, 0x92, 0xb6, 0x85, + 0x9e, 0x0a, 0x75, 0x7b, 0x03, 0x3c, 0xd8, 0x55, 0x21, 0x27, 0x79, 0x62, 0x35, 0x40, 0x83, 0x11, 0xd0, 0x96, 0x25, + 0x4e, 0x35, 0x11, 0x8d, 0x46, 0x61, 0xe4, 0x29, 0x2d, 0x95, 0xec, 0x7a, 0xfe, 0x68, 0xb0, 0x62, 0x12, 0x7b, 0xb5, + 0x38, 0xe8, 0x89, 0x49, 0x4a, 0x9b, 0x75, 0xd9, 0xe2, 0xf1, 0x89, 0xd8, 0xa3, 0x52, 0xe9, 0x89, 0xbd, 0xdc, 0x4a, + 0xe4, 0x66, 0xdf, 0xd3, 0x38, 0x33, 0x34, 0x3a, 0xdf, 0x1b, 0x9d, 0x57, 0x76, 0x55, 0xf7, 0xaf, 0x89, 0xda, 0x2c, + 0x6f, 0xc6, 0x29, 0xea, 0x80, 0xe6, 0x63, 0x23, 0x48, 0xdf, 0x7f, 0x2a, 0x34, 0x10, 0x8a, 0x86, 0x99, 0x97, 0x3d, + 0x16, 0x23, 0xdd, 0x90, 0x29, 0x13, 0x12, 0xdc, 0xbb, 0x03, 0x03, 0x8f, 0x28, 0x4b, 0x91, 0x31, 0x9d, 0x20, 0x0c, + 0x11, 0x59, 0x27, 0x67, 0xde, 0xe7, 0xe6, 0xb7, 0xf7, 0xc4, 0xee, 0x0f, 0x61, 0x53, 0xcb, 0xcd, 0x1e, 0x4e, 0xef, + 0x7d, 0x39, 0x3c, 0xf4, 0xf6, 0x22, 0xb9, 0x36, 0xe3, 0x85, 0x65, 0xf6, 0x75, 0xd8, 0x7f, 0x91, 0xf9, 0xd8, 0x63, + 0xa6, 0x57, 0xac, 0x81, 0x2c, 0x9e, 0x59, 0x13, 0xc3, 0x2f, 0x41, 0x3b, 0x0a, 0x81, 0x76, 0x62, 0xb7, 0x64, 0x15, + 0x24, 0x20, 0x12, 0x63, 0x6a, 0x3b, 0x07, 0x03, 0x75, 0xac, 0xb3, 0x58, 0xb4, 0xfb, 0x77, 0x4f, 0x39, 0x6d, 0x01, + 0x28, 0x2f, 0x85, 0x7f, 0x76, 0x71, 0x4a, 0xec, 0xe3, 0x18, 0x63, 0x2b, 0xb8, 0x1e, 0x12, 0x48, 0x55, 0x30, 0xa1, + 0xd3, 0x14, 0x01, 0x5d, 0x9c, 0x31, 0xf1, 0x27, 0xc6, 0xe1, 0x5d, 0xcc, 0x81, 0xa6, 0xcc, 0xb6, 0x30, 0x72, 0x8c, + 0x29, 0xcd, 0xba, 0x30, 0x63, 0xf3, 0x54, 0x36, 0x69, 0xb1, 0x36, 0x86, 0x94, 0x2d, 0xb9, 0x77, 0x6d, 0x11, 0x32, + 0x61, 0xc8, 0xba, 0x86, 0x90, 0xee, 0x10, 0xfc, 0x39, 0x29, 0x81, 0xd5, 0xfb, 0xa5, 0xae, 0xd4, 0xb3, 0x8c, 0xba, + 0x4c, 0xd0, 0x29, 0x8e, 0x1c, 0xe9, 0x92, 0x98, 0x7f, 0x2b, 0x13, 0xc2, 0xe1, 0x4a, 0x5b, 0xba, 0x2d, 0xa1, 0x49, + 0x8a, 0xc9, 0xc9, 0x5d, 0x40, 0xbe, 0xeb, 0xf9, 0xa3, 0xe3, 0xc9, 0xdb, 0x08, 0xcf, 0x06, 0x91, 0xc0, 0x86, 0xd5, + 0x94, 0xa8, 0x86, 0xd5, 0x67, 0xaf, 0xdb, 0x1f, 0x1e, 0x51, 0xdf, 0x48, 0xc7, 0x50, 0x61, 0xf3, 0xda, 0x53, 0x32, + 0x71, 0x6f, 0x9a, 0xd2, 0x1c, 0x95, 0xff, 0x35, 0x77, 0x38, 0x85, 0xdf, 0xdf, 0xe0, 0xec, 0xe0, 0x59, 0xf7, 0xd4, + 0x50, 0xdc, 0xef, 0x3f, 0xa8, 0x50, 0x2d, 0x37, 0x14, 0x06, 0x68, 0xc2, 0xf7, 0x20, 0x97, 0x23, 0xdf, 0xcf, 0x5d, + 0xe5, 0x17, 0xf9, 0xa5, 0x6f, 0x5d, 0x1a, 0xc2, 0x56, 0x4a, 0xa5, 0x15, 0xad, 0x6b, 0xf9, 0xa1, 0xbc, 0x49, 0xba, + 0x43, 0x06, 0xaa, 0x3f, 0xaf, 0xb1, 0x77, 0xa8, 0x48, 0x63, 0xb7, 0x3d, 0x71, 0x87, 0x30, 0xad, 0x74, 0x29, 0xbc, + 0xa4, 0x51, 0x22, 0x46, 0x69, 0x38, 0x75, 0xa9, 0x96, 0xb5, 0x13, 0xf5, 0xec, 0x88, 0x5d, 0x28, 0x20, 0x54, 0xdf, + 0x33, 0x5a, 0xb6, 0xc0, 0xb0, 0x77, 0xca, 0xd3, 0x68, 0x65, 0x1d, 0xea, 0x5a, 0x5b, 0x53, 0x6d, 0xd7, 0x3b, 0xaf, + 0xa8, 0xc0, 0xda, 0xd2, 0xdb, 0x7a, 0x2c, 0xeb, 0x31, 0x57, 0xe1, 0xa6, 0x8c, 0xe0, 0x59, 0xea, 0x17, 0x78, 0x5f, + 0x2d, 0x8f, 0x14, 0x1e, 0x1d, 0xdd, 0x5e, 0x05, 0x74, 0x30, 0x99, 0x04, 0x1e, 0x7c, 0x2a, 0x6b, 0x95, 0xac, 0x17, + 0xc2, 0x73, 0x42, 0x18, 0x90, 0xb3, 0x3e, 0xd8, 0x76, 0xa3, 0x72, 0x89, 0xd6, 0xeb, 0x47, 0x16, 0x5a, 0x64, 0xed, + 0xdb, 0x32, 0x8d, 0x1b, 0x2a, 0x47, 0x35, 0xfa, 0x75, 0xca, 0xd9, 0x53, 0xcc, 0x13, 0x46, 0x7d, 0x62, 0xd0, 0xc8, + 0x9e, 0x18, 0x1e, 0xa1, 0xe6, 0x47, 0xe6, 0x34, 0x5f, 0xae, 0x87, 0x5f, 0x89, 0xc2, 0x5a, 0x1d, 0x3a, 0x05, 0x2a, + 0x04, 0xda, 0xb3, 0x95, 0xbb, 0x17, 0x35, 0x64, 0x78, 0x41, 0xb9, 0x02, 0x92, 0xb1, 0xe0, 0xbe, 0x19, 0x42, 0x51, + 0x2b, 0x19, 0xa7, 0x89, 0x7d, 0xde, 0xc3, 0x4c, 0x7a, 0xce, 0xaf, 0x22, 0xc0, 0xdd, 0x0b, 0x67, 0xd2, 0x3c, 0xb6, + 0xdc, 0xa2, 0xc2, 0x65, 0xa9, 0x09, 0xb1, 0x45, 0x13, 0x51, 0x02, 0x40, 0x2f, 0x87, 0x7d, 0x44, 0x8e, 0x74, 0x32, + 0x76, 0x6d, 0xdb, 0x92, 0xa0, 0x50, 0x19, 0xe7, 0x1b, 0x85, 0xe1, 0xae, 0x2d, 0xee, 0xff, 0xa6, 0x21, 0xf6, 0x0c, + 0xe6, 0xa1, 0xd9, 0x5a, 0xba, 0xf9, 0xa3, 0xe8, 0xf8, 0x01, 0x0d, 0x64, 0x5f, 0xd4, 0x41, 0xfc, 0xbf, 0xd2, 0x21, + 0x1e, 0x84, 0xa0, 0x78, 0x88, 0x2b, 0x59, 0xc4, 0x82, 0x74, 0x27, 0x9e, 0xc5, 0xb9, 0xcc, 0x69, 0x5a, 0x40, 0x50, + 0x1d, 0x08, 0x85, 0x2c, 0x37, 0x90, 0xc6, 0x1b, 0x9c, 0x38, 0x6f, 0x7c, 0x21, 0x09, 0x6c, 0x3d, 0x1b, 0xc9, 0x64, + 0x51, 0xce, 0x88, 0xc0, 0x1f, 0xf3, 0xdc, 0xfd, 0x63, 0xf8, 0x8c, 0xd9, 0x8c, 0xa7, 0xcd, 0xf2, 0x63, 0x44, 0xd8, + 0x59, 0x5b, 0x45, 0x98, 0x50, 0x22, 0x0d, 0x4e, 0x78, 0xfd, 0x67, 0x2a, 0xdc, 0x1a, 0xbe, 0x82, 0x77, 0x66, 0x56, + 0xcc, 0xa5, 0xf5, 0x6a, 0x91, 0xa4, 0x83, 0x30, 0xdf, 0x98, 0x7f, 0x8c, 0x91, 0xe9, 0x32, 0x66, 0x45, 0x3f, 0x1a, + 0x24, 0x8a, 0xb7, 0x1b, 0x8f, 0xc7, 0xb7, 0x11, 0x1c, 0xac, 0x16, 0x64, 0x73, 0x9c, 0x8d, 0x90, 0x3d, 0x64, 0x45, + 0x55, 0x63, 0x82, 0x50, 0x0a, 0x71, 0x0c, 0x11, 0x47, 0xfc, 0xab, 0x3e, 0x3d, 0xa4, 0xab, 0x2f, 0x45, 0x46, 0xf9, + 0xad, 0xfc, 0x2d, 0xf3, 0x1d, 0x7f, 0xc7, 0x54, 0x5c, 0xe7, 0x39, 0xc2, 0xef, 0xfd, 0x76, 0x06, 0x09, 0x49, 0x11, + 0xfe, 0xa3, 0x44, 0x80, 0x98, 0x7a, 0xb0, 0x01, 0xdc, 0x79, 0x75, 0x95, 0x93, 0xe0, 0xbe, 0x60, 0xe8, 0x6d, 0x8b, + 0x99, 0x79, 0x3c, 0x92, 0x7c, 0x87, 0xb1, 0x88, 0xdd, 0xe8, 0x83, 0x19, 0x3b, 0x71, 0xce, 0xc4, 0x64, 0xf6, 0x1f, + 0x23, 0x2c, 0xb0, 0x84, 0x81, 0x5a, 0x0b, 0xbf, 0x5d, 0x05, 0x70, 0xa7, 0xff, 0x60, 0xa4, 0xc0, 0x35, 0x7a, 0xe2, + 0x67, 0xba, 0x97, 0xb0, 0x09, 0x4e, 0xc4, 0x5e, 0x11, 0xdb, 0x73, 0xa0, 0x55, 0x6b, 0x2e, 0x84, 0xee, 0xce, 0xe9, + 0x20, 0x6c, 0xb1, 0x28, 0x8c, 0xf5, 0x3a, 0x4a, 0x6c, 0x56, 0x2d, 0xa7, 0x09, 0x43, 0xb6, 0xab, 0x50, 0x7b, 0x92, + 0x0b, 0x8b, 0x12, 0x13, 0xb9, 0x71, 0xbc, 0x29, 0xd6, 0x01, 0xf5, 0x5b, 0xbb, 0x36, 0xc1, 0xad, 0x17, 0x3c, 0x3a, + 0x16, 0x14, 0x5a, 0x8a, 0x18, 0x3c, 0x41, 0x64, 0xf0, 0xba, 0xac, 0x10, 0xa7, 0x17, 0xe9, 0xf7, 0xad, 0x97, 0x34, + 0x5a, 0xba, 0x9b, 0x45, 0xcc, 0x7e, 0x9e, 0xfd, 0x6a, 0xda, 0xfe, 0x96, 0x33, 0x32, 0x2e, 0x92, 0x16, 0x3d, 0x8d, + 0x12, 0x97, 0x5b, 0x30, 0x7b, 0x68, 0x75, 0xcc, 0xd0, 0x7f, 0xa7, 0xa5, 0xc5, 0x18, 0xbf, 0x13, 0xc5, 0xb4, 0x87, + 0x0b, 0x15, 0x89, 0x7b, 0x7a, 0x41, 0x81, 0xb6, 0x96, 0x78, 0xed, 0xf4, 0x4e, 0x57, 0x9f, 0x4f, 0xd7, 0x20, 0xfa, + 0xe6, 0x98, 0xae, 0x5b, 0x80, 0x2c, 0x97, 0x19, 0xd6, 0x68, 0x97, 0xf6, 0x85, 0xf2, 0xec, 0x81, 0xed, 0x93, 0xf1, + 0x6f, 0x31, 0xf5, 0x64, 0x48, 0x24, 0x2d, 0x51, 0x2a, 0x15, 0x38, 0xe9, 0x02, 0x89, 0x35, 0x1b, 0xb5, 0x5c, 0xa3, + 0xce, 0xf8, 0xb1, 0x3d, 0xae, 0x2c, 0x7f, 0x7a, 0xc9, 0xab, 0xb4, 0x72, 0x11, 0x88, 0x7d, 0x76, 0x29, 0x19, 0x50, + 0x4e, 0xe1, 0x8c, 0xec, 0xfe, 0x57, 0xb0, 0xda, 0x15, 0x40, 0xd5, 0x30, 0x7a, 0xb9, 0xd4, 0xa0, 0x14, 0xa5, 0x9f, + 0xee, 0x0f, 0x73, 0x10, 0xd6, 0x57, 0x67, 0xe1, 0xb5, 0x9f, 0x24, 0xba, 0xc4, 0x5f, 0x4c, 0xdb, 0x09, 0x27, 0xa9, + 0xbb, 0xe2, 0x37, 0x27, 0x13, 0xb0, 0xe8, 0x10, 0xaf, 0x56, 0x87, 0x9b, 0x79, 0x4b, 0x86, 0x4a, 0x64, 0x5d, 0x7c, + 0xe7, 0x02, 0xb8, 0xb0, 0xde, 0x3e, 0xcd, 0x42, 0xb2, 0xd6, 0x12, 0x3b, 0x09, 0xdd, 0xf0, 0x2c, 0x61, 0x04, 0x48, + 0xb0, 0x93, 0x01, 0x34, 0x79, 0xa7, 0xec, 0x63, 0xbd, 0x9a, 0x98, 0x82, 0x20, 0xa2, 0x7b, 0xcf, 0xc1, 0x6e, 0xb1, + 0xe7, 0x59, 0x61, 0x13, 0x62, 0xb3, 0xa3, 0xe9, 0xfb, 0x69, 0x02, 0xaf, 0x17, 0x9a, 0x8a, 0x8d, 0xc2, 0xd4, 0xc9, + 0x4b, 0x8c, 0x03, 0xf4, 0x65, 0x29, 0xa0, 0x86, 0xab, 0x68, 0xcb, 0x72, 0x92, 0x12, 0x5a, 0x06, 0x07, 0x9c, 0x21, + 0x72, 0xf0, 0x3f, 0x16, 0x34, 0x90, 0x75, 0xf8, 0x89, 0xa8, 0x05, 0x7f, 0x26, 0xad, 0x69, 0x56, 0x44, 0xab, 0xbd, + 0x8e, 0x35, 0x68, 0x5e, 0x26, 0xcf, 0x17, 0x06, 0xb0, 0x79, 0x2d, 0x64, 0xf5, 0x33, 0xfd, 0x56, 0x3c, 0x51, 0x7e, + 0xca, 0x41, 0xed, 0xa9, 0x3e, 0x5b, 0x21, 0xd9, 0x69, 0x56, 0x54, 0x44, 0x71, 0x3d, 0xd9, 0x9e, 0x8b, 0xef, 0xbe, + 0x4c, 0x14, 0xfc, 0x66, 0x00, 0x31, 0x24, 0x20, 0x82, 0xfd, 0x40, 0xd6, 0x01, 0xce, 0x17, 0x04, 0x53, 0x7e, 0xd2, + 0x43, 0xa0, 0x29, 0x47, 0x0a, 0x88, 0xd9, 0x8a, 0xd9, 0xa5, 0xf3, 0x40, 0x2b, 0xde, 0xbf, 0x4e, 0xd5, 0xac, 0xfd, + 0xf5, 0x94, 0x4e, 0x7a, 0xcb, 0xf9, 0x8f, 0x4a, 0x9a, 0x01, 0x1b, 0xe2, 0x82, 0x2a, 0x45, 0xc2, 0x2a, 0xa3, 0x40, + 0x34, 0x7a, 0x76, 0x1c, 0x59, 0x0a, 0xfb, 0xe7, 0x76, 0x7e, 0xde, 0xea, 0xd4, 0x96, 0xba, 0x9d, 0x4b, 0x89, 0x15, + 0x5c, 0x61, 0x6e, 0xf9, 0x0a, 0x00, 0x64, 0xa6, 0x0f, 0x4b, 0x07, 0x0d, 0xbe, 0xee, 0xbe, 0x70, 0xa0, 0x92, 0xb5, + 0x63, 0xf5, 0x43, 0xc6, 0xef, 0x0e, 0xe9, 0x15, 0xbd, 0x52, 0x65, 0xbe, 0xb9, 0xd7, 0x7b, 0xda, 0xea, 0xfa, 0xa5, + 0x61, 0x46, 0x5d, 0xaa, 0x96, 0xa7, 0xd7, 0xf8, 0xfd, 0x00, 0x5e, 0xae, 0xfd, 0x29, 0x28, 0x63, 0xfb, 0x28, 0x1d, + 0x7b, 0x05, 0xf6, 0xa4, 0x4f, 0x53, 0xd3, 0xd0, 0xaa, 0xc8, 0x94, 0xb7, 0x75, 0xdf, 0x0a, 0xf7, 0xbc, 0x3d, 0x91, + 0x71, 0x24, 0x75, 0x97, 0x92, 0xf7, 0xa5, 0xad, 0x83, 0xee, 0x77, 0x04, 0x0a, 0xbf, 0x3c, 0x9d, 0x52, 0xd0, 0xd7, + 0x84, 0x4b, 0x04, 0x0f, 0x2d, 0xaf, 0x4b, 0x37, 0xc3, 0x20, 0x72, 0xf4, 0x01, 0xdb, 0xd2, 0x86, 0xe0, 0xcf, 0x45, + 0xf8, 0xd9, 0x4e, 0x68, 0x26, 0x57, 0x81, 0xda, 0x10, 0x55, 0xf6, 0x90, 0x6c, 0x0a, 0x2c, 0x27, 0x52, 0x93, 0x7e, + 0xa8, 0x33, 0x81, 0x04, 0x53, 0xaf, 0x3c, 0xec, 0x82, 0x21, 0x8b, 0x5d, 0x69, 0x61, 0x60, 0x19, 0x26, 0xcf, 0x92, + 0x5f, 0xaf, 0xce, 0xa5, 0xd1, 0x02, 0x43, 0x00, 0xd3, 0xdc, 0xcb, 0x8b, 0xe6, 0x84, 0xe5, 0xef, 0x6e, 0x86, 0xf9, + 0x02, 0x07, 0xbe, 0x75, 0x31, 0xf3, 0x5a, 0x14, 0xe8, 0xb9, 0xe9, 0x59, 0x23, 0xed, 0x44, 0xc1, 0x49, 0x6d, 0xce, + 0x70, 0x08, 0x50, 0xd5, 0xec, 0xc3, 0xcc, 0x33, 0xf5, 0xe5, 0xac, 0x17, 0xc1, 0x5e, 0x5e, 0x23, 0xf0, 0xe5, 0xc7, + 0x39, 0xf6, 0x4e, 0x46, 0xe6, 0xaa, 0xb4, 0x27, 0x9e, 0x9b, 0x0b, 0xab, 0xbb, 0x25, 0x78, 0x2c, 0x10, 0xec, 0xfc, + 0x61, 0x1e, 0xf7, 0x75, 0xdd, 0x2b, 0x20, 0x98, 0x81, 0xf0, 0x09, 0x60, 0x0e, 0x86, 0x68, 0xa6, 0x97, 0x47, 0xff, + 0x39, 0x47, 0xaa, 0x8a, 0xa7, 0x00, 0xc7, 0x1c, 0x0c, 0xef, 0x4c, 0x27, 0x72, 0xb3, 0xd2, 0x5a, 0xed, 0x04, 0x0c, + 0x21, 0x37, 0xcd, 0x99, 0xa6, 0xdc, 0x00, 0xf9, 0x2e, 0x62, 0x98, 0xe1, 0x55, 0xec, 0x0b, 0xf1, 0x01, 0x63, 0x16, + 0xcd, 0x9d, 0x17, 0xf8, 0x47, 0x17, 0xb1, 0xdc, 0x79, 0x32, 0x53, 0xd6, 0xc2, 0x1a, 0x33, 0xa4, 0x90, 0xb9, 0x1f, + 0x71, 0x0a, 0xab, 0x6d, 0xda, 0x57, 0x01, 0x91, 0x5b, 0x5a, 0x37, 0x26, 0x5a, 0x03, 0x17, 0x5a, 0x13, 0xcd, 0xa1, + 0x5d, 0xdb, 0x1a, 0x6b, 0x5a, 0x9e, 0x7e, 0x32, 0x78, 0xf0, 0xcd, 0xbf, 0x7e, 0xf4, 0x55, 0xf3, 0x12, 0xd2, 0xe0, + 0xfa, 0xe8, 0xc2, 0xc9, 0x7e, 0xde, 0xf3, 0x76, 0x81, 0xcb, 0x9d, 0xbc, 0x26, 0xcf, 0xe9, 0x90, 0x4a, 0xec, 0xa4, + 0x02, 0x28, 0x82, 0xcf, 0x2d, 0x2d, 0x7b, 0x0f, 0x75, 0x22, 0x83, 0x0b, 0x55, 0xce, 0x38, 0x33, 0x8e, 0xf3, 0xfc, + 0x4a, 0xda, 0x1c, 0xdc, 0x7e, 0x1e, 0x5c, 0x0c, 0x04, 0x5f, 0xe8, 0x82, 0x4c, 0x83, 0x42, 0x47, 0x6d, 0x4d, 0x2d, + 0xb0, 0x38, 0xc0, 0x02, 0x91, 0x23, 0x08, 0x40, 0x0e, 0x91, 0xae, 0x55, 0xba, 0x8f, 0x07, 0xdd, 0x81, 0xf2, 0x46, + 0x60, 0x46, 0x86, 0x1d, 0xac, 0x1d, 0xeb, 0x2b, 0x57, 0x89, 0x08, 0x93, 0xb0, 0xb1, 0x98, 0xe1, 0x5f, 0x3c, 0x25, + 0xe5, 0x63, 0x1e, 0x76, 0xb3, 0xe0, 0xbc, 0x98, 0x57, 0x58, 0x9e, 0x9a, 0x20, 0x5d, 0xa8, 0xab, 0x6f, 0x39, 0x26, + 0xe7, 0xb4, 0xcb, 0x50, 0xd8, 0x22, 0x61, 0xa4, 0x6c, 0xd2, 0x4c, 0x64, 0x01, 0xc9, 0x38, 0x47, 0x0c, 0xe5, 0x0a, + 0xaf, 0x47, 0x95, 0x6c, 0xd7, 0xfc, 0x1b, 0xb3, 0x32, 0x2e, 0xc7, 0x8e, 0x75, 0xc3, 0xba, 0x43, 0x12, 0x95, 0x69, + 0x99, 0x7c, 0x2d, 0x8b, 0x13, 0x2f, 0xe6, 0x51, 0x88, 0xf7, 0xb3, 0x7e, 0x5b, 0x7e, 0xf1, 0xc7, 0xe5, 0xae, 0x1d, + 0x42, 0x99, 0x54, 0x0c, 0x62, 0x09, 0x13, 0x41, 0x8b, 0xd2, 0xf8, 0x8d, 0x00, 0x2d, 0xcf, 0x00, 0xda, 0x58, 0xfa, + 0xc1, 0x4a, 0xa6, 0x2a, 0x87, 0xd5, 0x52, 0xbd, 0x95, 0xa2, 0xbb, 0xcb, 0xf2, 0x32, 0xda, 0x2c, 0x92, 0x80, 0x00, + 0x37, 0xa7, 0x73, 0x35, 0x5f, 0x72, 0x1d, 0xc6, 0xeb, 0xb8, 0xb2, 0x54, 0x2a, 0x4c, 0xd1, 0x6c, 0xb0, 0x8c, 0x08, + 0xe3, 0xb6, 0xd6, 0xc7, 0xe2, 0xf8, 0x3d, 0x91, 0x4f, 0xa0, 0x5f, 0xe1, 0x2e, 0x77, 0x55, 0x05, 0xee, 0x26, 0x22, + 0x7a, 0x42, 0x2e, 0x03, 0xbe, 0x33, 0xaa, 0x37, 0x68, 0xc1, 0xb6, 0x6c, 0xb7, 0xd6, 0x63, 0x2a, 0x0f, 0x7d, 0x09, + 0x83, 0xbd, 0x59, 0xf4, 0xa8, 0x75, 0xa8, 0xe1, 0x78, 0xda, 0x70, 0x78, 0x37, 0xe8, 0x69, 0x40, 0x07, 0xa4, 0xbe, + 0xf6, 0xe3, 0x3f, 0x4e, 0x16, 0x80, 0x79, 0x41, 0x5d, 0x24, 0xb4, 0x1d, 0xe3, 0x1b, 0x68, 0x10, 0xe4, 0x0e, 0x04, + 0xdb, 0xf5, 0x7c, 0x0d, 0x17, 0x07, 0xbd, 0xb0, 0x24, 0xd4, 0xc2, 0x9b, 0xfa, 0x43, 0x08, 0x22, 0x98, 0x52, 0x12, + 0xeb, 0xfe, 0xd8, 0xec, 0xa1, 0xa0, 0x0f, 0x77, 0x38, 0xab, 0xc6, 0xf4, 0x27, 0xc4, 0x2a, 0x13, 0xe9, 0x81, 0xdd, + 0x45, 0x13, 0x0d, 0x0f, 0xfb, 0x41, 0x49, 0x4a, 0xa8, 0x0e, 0x19, 0x6d, 0xa0, 0x4c, 0xcc, 0xf1, 0x65, 0x87, 0x82, + 0x57, 0x52, 0x4b, 0x6c, 0x62, 0xb0, 0x6f, 0x1b, 0x4c, 0x89, 0x81, 0x3a, 0x84, 0xdc, 0x52, 0xe4, 0x3a, 0xc0, 0x16, + 0xb1, 0x3f, 0xad, 0x06, 0xca, 0x7e, 0x57, 0xf7, 0x7d, 0x7b, 0x05, 0x50, 0xe6, 0x9a, 0x9f, 0xf4, 0x7b, 0xa4, 0x7b, + 0xb0, 0x88, 0x5f, 0x87, 0xa0, 0x55, 0xd7, 0xd5, 0x9a, 0x38, 0xf3, 0x59, 0xb2, 0xe7, 0x86, 0x0b, 0x7f, 0x5f, 0x11, + 0xc8, 0x18, 0x69, 0x3a, 0x54, 0xb1, 0x15, 0xef, 0xcb, 0x28, 0x5a, 0x86, 0x7b, 0xe1, 0x77, 0x52, 0x10, 0x21, 0x42, + 0xc6, 0x30, 0xcd, 0x11, 0x74, 0x6a, 0x3e, 0x4f, 0x1a, 0x81, 0xea, 0x9a, 0x84, 0xb9, 0x67, 0xef, 0x15, 0xf1, 0x20, + 0x47, 0x8f, 0x46, 0x02, 0xb4, 0x7f, 0x8b, 0xaf, 0xbe, 0x69, 0x03, 0x23, 0x48, 0x1b, 0x41, 0x64, 0x70, 0x03, 0x8e, + 0x73, 0x7c, 0xd1, 0x42, 0x82, 0x44, 0x99, 0xee, 0x24, 0xf4, 0x4d, 0x9b, 0xb5, 0x06, 0x4f, 0xca, 0x8b, 0x72, 0xa3, + 0x02, 0x50, 0xa7, 0x85, 0x68, 0x56, 0x30, 0x67, 0x86, 0xec, 0xc8, 0x7b, 0xb0, 0xe1, 0xa1, 0x2e, 0x95, 0xe6, 0x31, + 0xa7, 0x2f, 0x10, 0x35, 0x97, 0x79, 0x52, 0xa3, 0x57, 0xd0, 0xb7, 0xa0, 0x38, 0x85, 0x36, 0xc6, 0xc4, 0xe9, 0xf2, + 0xd4, 0xa7, 0x6a, 0x24, 0x4a, 0xcf, 0xe6, 0xcb, 0x62, 0x1d, 0x71, 0x04, 0x76, 0xa1, 0x15, 0x63, 0xf0, 0xab, 0xb8, + 0x25, 0xe0, 0x20, 0xd3, 0x89, 0xa0, 0x23, 0x7d, 0x32, 0x72, 0x32, 0x63, 0xbd, 0x4b, 0x5f, 0x38, 0xd0, 0x43, 0x29, + 0xd5, 0x17, 0x18, 0x33, 0x84, 0x02, 0xfd, 0xd5, 0xf4, 0x06, 0xed, 0xaf, 0x72, 0x50, 0xbc, 0x98, 0xd0, 0x51, 0x11, + 0x43, 0x78, 0x88, 0xd4, 0x88, 0x42, 0x46, 0xa2, 0x03, 0x2c, 0x60, 0xe6, 0xdd, 0x74, 0x6b, 0xc9, 0x7b, 0xb1, 0x4e, + 0x9d, 0xe6, 0xe0, 0x29, 0x83, 0xf1, 0x46, 0x5e, 0xfa, 0x19, 0x3d, 0xf6, 0x65, 0x4b, 0xc8, 0xee, 0x8b, 0x09, 0x04, + 0xc8, 0x17, 0x3b, 0x64, 0x4c, 0xb1, 0x86, 0x35, 0x2d, 0xa9, 0x9a, 0x7d, 0xb4, 0x08, 0xfd, 0x31, 0xf5, 0x71, 0x96, + 0x65, 0x4a, 0xa8, 0x2d, 0x8c, 0x01, 0x11, 0x7a, 0xca, 0x29, 0x41, 0x43, 0xee, 0x83, 0x57, 0x34, 0xa8, 0x83, 0xfd, + 0xbe, 0x18, 0x9e, 0x74, 0x97, 0x43, 0x60, 0xbb, 0x26, 0xa0, 0xd3, 0x94, 0x42, 0x21, 0x36, 0xdc, 0x47, 0x35, 0x93, + 0xd4, 0x30, 0xa6, 0x89, 0xca, 0x07, 0xfc, 0x41, 0x6d, 0xc4, 0x2d, 0xdb, 0xb3, 0x78, 0x18, 0x61, 0xcf, 0x71, 0xe8, + 0x86, 0xb0, 0x0e, 0x88, 0xaa, 0x6a, 0x2e, 0x6b, 0x6e, 0x44, 0xcb, 0x33, 0x32, 0x98, 0x12, 0xa9, 0x5f, 0xa1, 0x75, + 0x50, 0xa9, 0xa0, 0x9e, 0xc5, 0x1f, 0x06, 0x9e, 0x5b, 0x42, 0xcb, 0xfd, 0x29, 0x92, 0x78, 0x31, 0x19, 0xcd, 0xa8, + 0x47, 0x78, 0xd9, 0xee, 0x10, 0x60, 0xdf, 0xb9, 0xb2, 0xd3, 0xb4, 0x68, 0xa3, 0x8c, 0x9d, 0xb8, 0xeb, 0xb6, 0xa6, + 0x12, 0xb4, 0x06, 0x94, 0x98, 0x7f, 0x57, 0x9f, 0x05, 0xdf, 0x0b, 0xa8, 0x98, 0x75, 0x00, 0xd7, 0x0b, 0xed, 0xb7, + 0x2f, 0xd1, 0x4e, 0x4a, 0x57, 0xdc, 0x9b, 0x44, 0x29, 0xdd, 0x47, 0xcc, 0x82, 0xb9, 0x90, 0xbb, 0xe3, 0xa2, 0xee, + 0xad, 0x9f, 0x04, 0xdc, 0x14, 0x75, 0x86, 0x2d, 0x94, 0x6c, 0x0e, 0x78, 0x29, 0x31, 0x05, 0x9a, 0xcb, 0x95, 0x11, + 0x78, 0x0d, 0x3d, 0x50, 0x07, 0x0d, 0x89, 0x5b, 0xeb, 0xb5, 0xa3, 0xd9, 0xfb, 0x43, 0x4b, 0x35, 0x59, 0xd0, 0xc6, + 0x48, 0xf2, 0x98, 0x39, 0x74, 0x56, 0x64, 0xba, 0xaa, 0x0a, 0x96, 0x9a, 0x5a, 0xaf, 0xf5, 0xcd, 0x7f, 0xf1, 0xbe, + 0x04, 0x08, 0x13, 0xf6, 0xac, 0x76, 0xd0, 0x3b, 0xec, 0x54, 0xbf, 0x59, 0xb8, 0xda, 0xc2, 0x45, 0xaa, 0x80, 0x80, + 0x2e, 0x59, 0x7d, 0x8d, 0xa5, 0xa7, 0x28, 0x88, 0x34, 0xe8, 0xaa, 0x6b, 0x02, 0x85, 0x60, 0xa5, 0x32, 0x7f, 0xba, + 0x30, 0x21, 0x47, 0x4c, 0x8e, 0xc8, 0xed, 0x75, 0x39, 0xe7, 0x67, 0x06, 0xa4, 0xa7, 0x23, 0x22, 0x21, 0xa7, 0x37, + 0x06, 0xef, 0x72, 0xd0, 0xd8, 0xdf, 0x05, 0x5c, 0xe1, 0x43, 0x02, 0xe7, 0x5d, 0x57, 0xca, 0x0d, 0x03, 0xdc, 0xf7, + 0x05, 0xd2, 0xd4, 0x14, 0x11, 0x1c, 0xa8, 0x76, 0xe4, 0x73, 0x76, 0xe4, 0xcf, 0xcd, 0xe8, 0x70, 0x6e, 0x8e, 0x77, + 0x8d, 0x22, 0xc4, 0x14, 0xbb, 0xcf, 0x03, 0x23, 0x51, 0x92, 0xf0, 0xa7, 0xeb, 0x40, 0x6b, 0xad, 0xfb, 0x05, 0xf7, + 0x20, 0x9e, 0x15, 0xe1, 0xfc, 0x03, 0xdb, 0x7c, 0xa0, 0xc9, 0x79, 0x79, 0xad, 0xad, 0x3b, 0x8a, 0x11, 0x80, 0xf6, + 0xc6, 0xf3, 0xb6, 0xf2, 0xe0, 0x06, 0x95, 0x41, 0x9e, 0xce, 0x04, 0xe3, 0x99, 0xab, 0xc1, 0x3c, 0x3b, 0x76, 0x14, + 0x00, 0x16, 0x02, 0x45, 0xa9, 0xa9, 0xcd, 0xea, 0x24, 0xae, 0x68, 0x47, 0xf7, 0x5b, 0x56, 0xe0, 0x04, 0xa4, 0x5e, + 0x38, 0x5e, 0xda, 0x06, 0xdf, 0x51, 0x48, 0x76, 0x67, 0x19, 0x07, 0xd9, 0x85, 0x7f, 0x0f, 0xb4, 0x88, 0xea, 0x0a, + 0x84, 0x8a, 0xa4, 0x89, 0x55, 0x49, 0x29, 0x92, 0x46, 0x68, 0x99, 0x6d, 0x41, 0x56, 0x9c, 0xed, 0x11, 0x9f, 0xb5, + 0x16, 0x78, 0x37, 0xe4, 0xb6, 0xc8, 0xe6, 0x0c, 0xf7, 0x44, 0xa0, 0x63, 0x4b, 0x28, 0x33, 0x2b, 0x85, 0x6d, 0xdc, + 0xd3, 0x4d, 0xda, 0xbb, 0x55, 0xd4, 0x0c, 0x1a, 0xd1, 0xb7, 0xb4, 0x4c, 0xfe, 0xbe, 0x5e, 0xa8, 0x95, 0x18, 0x1a, + 0x73, 0x88, 0xe9, 0xba, 0xb5, 0x30, 0xa9, 0x52, 0x9b, 0x7e, 0x56, 0xf4, 0xe9, 0x23, 0x0d, 0xe5, 0x90, 0x02, 0x38, + 0xa1, 0x14, 0x84, 0x21, 0x3f, 0x60, 0x08, 0xee, 0x14, 0xac, 0x91, 0x2c, 0x57, 0x91, 0xcb, 0x22, 0x97, 0xdd, 0xf1, + 0x0f, 0x16, 0x80, 0x42, 0x5f, 0xae, 0x50, 0xd0, 0x4f, 0xb4, 0xd6, 0x27, 0xea, 0x48, 0xa9, 0x49, 0xf1, 0xe9, 0xd2, + 0x5d, 0x56, 0x01, 0x35, 0x57, 0xaf, 0x8b, 0x06, 0xf4, 0x9a, 0xd2, 0x41, 0xe8, 0x11, 0x0a, 0x5b, 0x08, 0xa3, 0x3f, + 0x5a, 0xde, 0x4f, 0xee, 0xe5, 0xb8, 0x76, 0x9b, 0xa2, 0x7b, 0x3a, 0xbb, 0x63, 0xa4, 0x26, 0x99, 0x68, 0x59, 0x73, + 0x0c, 0xa7, 0x07, 0xbc, 0x98, 0x3c, 0x76, 0x4c, 0xd8, 0x6c, 0x52, 0x3d, 0xc6, 0x04, 0xe0, 0xc8, 0x06, 0x8b, 0x6d, + 0x2a, 0xad, 0x95, 0x09, 0x52, 0xdb, 0xac, 0x5f, 0xd6, 0xdc, 0x29, 0x8a, 0xdb, 0x9f, 0x03, 0x60, 0xde, 0x34, 0x99, + 0x36, 0x50, 0x4c, 0x11, 0xa3, 0xa4, 0x75, 0x71, 0xbc, 0x14, 0x2b, 0x2f, 0x3e, 0x14, 0xb8, 0x37, 0x42, 0xe5, 0xca, + 0x6a, 0xc3, 0xd5, 0x99, 0xdc, 0x0f, 0xb7, 0x38, 0x65, 0x4e, 0x22, 0x1e, 0xc0, 0xe8, 0x33, 0x66, 0xc3, 0xcd, 0x61, + 0x3f, 0x32, 0xdc, 0x2c, 0xa7, 0xf0, 0xa2, 0x78, 0xcb, 0xfc, 0xdc, 0x05, 0x54, 0xf6, 0x20, 0x4e, 0x77, 0x2a, 0xb5, + 0x5e, 0x67, 0x04, 0x48, 0xf8, 0x56, 0x92, 0xbd, 0x92, 0xb1, 0x13, 0x9f, 0x22, 0xd3, 0x83, 0x63, 0x58, 0x78, 0xc6, + 0x48, 0x6e, 0x9f, 0xa9, 0xa3, 0xbd, 0x7a, 0x46, 0xe7, 0x06, 0x74, 0xe7, 0xd5, 0x7b, 0x5b, 0x91, 0x9e, 0x9a, 0x69, + 0x3a, 0xf1, 0xa6, 0x01, 0xea, 0x7c, 0x70, 0x6d, 0x91, 0xce, 0x79, 0x05, 0x9b, 0x38, 0x14, 0x6e, 0x84, 0x6a, 0xf4, + 0x85, 0x7a, 0x0f, 0xab, 0x3a, 0x36, 0xdd, 0xc5, 0x6d, 0xd9, 0xa3, 0xe9, 0x17, 0x6b, 0x04, 0x62, 0x4f, 0xe2, 0xf1, + 0x29, 0x0d, 0x6e, 0x6f, 0x76, 0xa6, 0xad, 0x8e, 0x31, 0x50, 0x6d, 0x92, 0x5a, 0xe0, 0xf7, 0xc7, 0x9e, 0x96, 0x0b, + 0x67, 0x96, 0x77, 0x0d, 0x7c, 0x89, 0x5f, 0x80, 0xb0, 0x6a, 0xda, 0x50, 0x8f, 0xef, 0xb8, 0xca, 0xc6, 0x32, 0xf7, + 0x9a, 0x91, 0xe5, 0x30, 0xd7, 0x55, 0x9c, 0x6c, 0xaa, 0x23, 0x12, 0xa9, 0xed, 0xa7, 0xde, 0xbe, 0x3b, 0x6e, 0xdc, + 0xe3, 0x97, 0x83, 0x38, 0xd0, 0x54, 0xe4, 0xcc, 0xb0, 0xb0, 0x0a, 0xa7, 0xad, 0x25, 0x0d, 0x8d, 0xcb, 0x50, 0x10, + 0x90, 0x31, 0xff, 0xea, 0xe1, 0x20, 0x32, 0x6f, 0xdd, 0x90, 0x54, 0x55, 0x1a, 0x58, 0xa2, 0xbd, 0x38, 0xf0, 0x90, + 0xc2, 0x43, 0x91, 0x6c, 0x7d, 0xd1, 0x7e, 0x9d, 0x23, 0x0b, 0x1e, 0x08, 0x46, 0x99, 0x24, 0x06, 0xb6, 0x8e, 0x6e, + 0x8d, 0x74, 0x97, 0xf4, 0x32, 0x01, 0xfd, 0xa4, 0xe7, 0xf1, 0xc7, 0x38, 0x14, 0x65, 0xcd, 0xf9, 0x9b, 0x96, 0x64, + 0x9d, 0x47, 0x77, 0x55, 0x63, 0x1d, 0x12, 0xb1, 0xa1, 0xe5, 0xe8, 0x38, 0x2f, 0xcb, 0x92, 0xd3, 0xe0, 0x4f, 0xc0, + 0x58, 0x78, 0x67, 0x45, 0x51, 0xcd, 0x85, 0x54, 0xd3, 0x17, 0x7c, 0x02, 0xd7, 0xe1, 0x31, 0x7b, 0x69, 0x5b, 0xb1, + 0x1e, 0x2c, 0x87, 0x78, 0xce, 0x0d, 0x15, 0x46, 0xae, 0xb6, 0x66, 0x93, 0x7a, 0x0e, 0xd3, 0x58, 0x99, 0x43, 0xa1, + 0x94, 0x7b, 0x5e, 0xf1, 0xd4, 0xc5, 0xdc, 0xa1, 0x9b, 0x14, 0xa2, 0x57, 0x64, 0xe6, 0x54, 0x92, 0x26, 0xfd, 0xd8, + 0x36, 0x2a, 0x20, 0x00, 0x3a, 0x5a, 0x00, 0x68, 0xf7, 0x7d, 0xb5, 0x6c, 0x42, 0xb2, 0xf1, 0x59, 0x43, 0x32, 0xd7, + 0x3a, 0xde, 0xfa, 0x2a, 0xc7, 0x77, 0x57, 0x84, 0xd1, 0xbc, 0x3d, 0x30, 0x2b, 0x9c, 0x8b, 0x48, 0x31, 0x6e, 0xd1, + 0x02, 0x12, 0xe6, 0x11, 0x72, 0xbc, 0x1b, 0xa2, 0x7c, 0xad, 0x6c, 0x8e, 0xce, 0xf3, 0xf0, 0xb4, 0xb9, 0x62, 0xa1, + 0x14, 0xbd, 0x4c, 0x8e, 0xfd, 0xc6, 0xad, 0x05, 0x1a, 0xd3, 0x98, 0x87, 0x8d, 0x0f, 0xa3, 0x3c, 0x3d, 0xc2, 0xe8, + 0xfc, 0x10, 0x87, 0xa5, 0x23, 0x7d, 0x2d, 0x05, 0xe8, 0xf5, 0x9e, 0xec, 0xbf, 0x5f, 0x7d, 0x2f, 0xba, 0xc1, 0xe0, + 0xc2, 0x48, 0xa3, 0x38, 0x5f, 0x10, 0x5a, 0x62, 0x8e, 0xd2, 0x8e, 0x17, 0xf5, 0x88, 0x39, 0xe2, 0xe6, 0x12, 0xec, + 0x58, 0x39, 0xd4, 0xff, 0x17, 0x2e, 0x42, 0x79, 0x52, 0x57, 0x02, 0x9f, 0x0f, 0x3f, 0x2e, 0xfb, 0x05, 0x8e, 0x44, + 0xe4, 0x30, 0x8e, 0xf4, 0xd8, 0xd6, 0x08, 0xbd, 0x67, 0xe7, 0x1e, 0xa9, 0x37, 0x5e, 0x27, 0x84, 0x5c, 0x37, 0x94, + 0x52, 0x77, 0x50, 0xc6, 0x65, 0x09, 0x34, 0x6e, 0x0a, 0x21, 0xca, 0xf1, 0x9f, 0x72, 0xf3, 0x14, 0xd1, 0x77, 0x1d, + 0x28, 0x5d, 0xd5, 0xd4, 0x14, 0xdc, 0x0d, 0x18, 0x80, 0xb5, 0xb8, 0x8f, 0x1d, 0x52, 0xf9, 0x50, 0x16, 0x5e, 0x61, + 0x60, 0xb5, 0x28, 0x2b, 0x81, 0x5a, 0x66, 0x28, 0x62, 0x04, 0x27, 0x68, 0x2f, 0xc2, 0xac, 0xeb, 0x98, 0x95, 0x7b, + 0xd0, 0xa2, 0x9d, 0xdb, 0x86, 0xdd, 0xab, 0xe1, 0xf9, 0xe1, 0xbd, 0x9a, 0xb4, 0xd8, 0xd5, 0xb8, 0xe3, 0x01, 0x38, + 0x4a, 0xce, 0x7e, 0x01, 0x58, 0xf0, 0x28, 0x09, 0xec, 0xdc, 0x4c, 0x0f, 0x5b, 0xbb, 0x43, 0xe9, 0xb7, 0x19, 0x3e, + 0xdd, 0x11, 0xb3, 0x51, 0xd2, 0xcd, 0x3e, 0xfd, 0xa9, 0x83, 0xc3, 0xd2, 0x9b, 0x00, 0xcf, 0xb1, 0xee, 0xfe, 0x41, + 0xc2, 0x0e, 0xee, 0xf1, 0x2f, 0x1f, 0x9a, 0x22, 0x91, 0x8e, 0x98, 0xc5, 0x2d, 0x8e, 0x6a, 0xb2, 0xb3, 0xba, 0x6b, + 0xe4, 0xdc, 0x96, 0xc4, 0x55, 0x29, 0x21, 0xb9, 0x1c, 0x71, 0xcb, 0x24, 0x7b, 0x44, 0x19, 0x9c, 0xf6, 0xf6, 0xf2, + 0xda, 0xd8, 0x7b, 0x18, 0x57, 0x01, 0xa8, 0x09, 0x28, 0x17, 0x34, 0xc6, 0xbb, 0x0f, 0x01, 0x46, 0x69, 0xd5, 0xd9, + 0x19, 0xbd, 0x8b, 0x5b, 0x75, 0xb9, 0x61, 0x91, 0x19, 0xcd, 0x44, 0xcd, 0xe4, 0xee, 0x80, 0xca, 0x46, 0x0b, 0x83, + 0xec, 0x97, 0x5c, 0xf1, 0xa9, 0x2a, 0xd1, 0x4a, 0x8b, 0x9a, 0xd1, 0xe1, 0x42, 0x25, 0x74, 0x94, 0x88, 0x2d, 0x17, + 0xd3, 0xae, 0xbc, 0x15, 0x66, 0x7e, 0x08, 0xec, 0x95, 0x19, 0x91, 0xa6, 0x6c, 0x31, 0xcb, 0x11, 0x71, 0x7e, 0xd4, + 0x5d, 0xb3, 0x6a, 0x53, 0x1b, 0x67, 0x2d, 0x3c, 0xdd, 0x52, 0xe4, 0x14, 0x02, 0x17, 0x6d, 0xf7, 0x41, 0x06, 0xc1, + 0xb4, 0x51, 0xe4, 0x46, 0x6f, 0xdd, 0x17, 0x51, 0xc6, 0x2b, 0x32, 0x2e, 0x62, 0x36, 0xb7, 0x7b, 0xb2, 0xb2, 0xa3, + 0x44, 0x79, 0x1c, 0x93, 0xc9, 0xc8, 0x81, 0x4a, 0xda, 0x90, 0x6b, 0xc9, 0xfd, 0x6b, 0xc5, 0x45, 0x5c, 0xfc, 0xbf, + 0x79, 0xd9, 0xd6, 0x45, 0x2d, 0x60, 0x41, 0x07, 0x73, 0x44, 0x81, 0x79, 0x55, 0x4a, 0x07, 0x25, 0x1c, 0x45, 0xe4, + 0x67, 0x05, 0xb3, 0xab, 0x92, 0x35, 0xf8, 0xa0, 0x95, 0xee, 0x0c, 0x93, 0x86, 0x84, 0xcb, 0x35, 0xa9, 0xb5, 0x2d, + 0x1a, 0x19, 0xf1, 0xcc, 0xef, 0x46, 0x25, 0xc2, 0x48, 0x3c, 0xc8, 0x94, 0x98, 0x0b, 0xcf, 0x0a, 0x29, 0xf1, 0x65, + 0xce, 0x3e, 0xd7, 0x8b, 0x6e, 0xb4, 0xc8, 0x62, 0x7e, 0x18, 0xf9, 0xe5, 0x70, 0xb3, 0x5b, 0x11, 0xa3, 0xde, 0x9a, + 0xab, 0xbc, 0x40, 0x99, 0x8d, 0xab, 0x23, 0xc7, 0x9c, 0x03, 0x8d, 0x14, 0x02, 0x85, 0xf4, 0xe9, 0x84, 0x4b, 0x8b, + 0xcb, 0x81, 0x8d, 0x1a, 0xdf, 0x99, 0x8b, 0xa7, 0xa5, 0xbb, 0x8b, 0xa1, 0xe1, 0xb5, 0x43, 0x22, 0x88, 0xd0, 0x78, + 0x93, 0x51, 0x33, 0xb4, 0x2f, 0x76, 0x9d, 0xb7, 0x67, 0xfa, 0xf9, 0x85, 0xa4, 0x67, 0x69, 0xe3, 0x11, 0xe0, 0x52, + 0x52, 0x91, 0xe6, 0xd6, 0x7e, 0x91, 0x43, 0x36, 0xfc, 0x37, 0xcd, 0xfa, 0x15, 0x01, 0xdc, 0x49, 0x02, 0x42, 0x80, + 0x86, 0xd7, 0xf5, 0xcf, 0xc3, 0x58, 0xc2, 0x9e, 0x83, 0xe9, 0xae, 0x82, 0x7f, 0x27, 0x61, 0x72, 0x5e, 0x9a, 0xd0, + 0xf8, 0xa2, 0x4e, 0x0c, 0x76, 0xb2, 0x40, 0xb8, 0x05, 0x58, 0x41, 0x10, 0x08, 0x7a, 0x66, 0xa6, 0x98, 0x4a, 0xe8, + 0x4f, 0xa4, 0x82, 0x0c, 0x09, 0x30, 0xf1, 0xc6, 0x9d, 0x30, 0x28, 0xaa, 0x55, 0x3c, 0x6d, 0x62, 0x36, 0x9c, 0x34, + 0x0c, 0x32, 0xeb, 0x0f, 0xa1, 0xb3, 0x53, 0x4c, 0x93, 0x61, 0x7f, 0x67, 0x6f, 0x3c, 0x9d, 0xb5, 0x2b, 0x51, 0x4a, + 0xc5, 0x3e, 0x35, 0x79, 0x42, 0x2b, 0xaf, 0x0b, 0x51, 0x5f, 0xd0, 0x4b, 0xdc, 0x07, 0x2a, 0xba, 0x83, 0x27, 0x3b, + 0x8a, 0xf4, 0x36, 0x0e, 0x2c, 0x77, 0x90, 0x08, 0xb0, 0xa8, 0xf6, 0x5d, 0x33, 0x0a, 0x19, 0x32, 0x54, 0x01, 0xe6, + 0x1a, 0x73, 0xa7, 0x86, 0xda, 0x43, 0xb9, 0x91, 0x5c, 0x9b, 0x06, 0xab, 0x87, 0xb9, 0x2f, 0xaf, 0xac, 0xdb, 0xdc, + 0xe8, 0x10, 0xee, 0x3b, 0x06, 0x80, 0x5d, 0x8e, 0xc8, 0x70, 0x66, 0x72, 0x9b, 0x50, 0x3d, 0x40, 0x21, 0xab, 0xa6, + 0x9a, 0xd6, 0xe1, 0xf1, 0x92, 0x0f, 0xe2, 0x10, 0xd7, 0x84, 0xd8, 0xa8, 0x3c, 0x42, 0xcf, 0xcd, 0xd8, 0x27, 0xfa, + 0x3e, 0xe5, 0x8e, 0xe6, 0x1b, 0xa4, 0x8e, 0x5c, 0xa6, 0x3a, 0x8f, 0x13, 0x75, 0x6b, 0xac, 0x8e, 0x62, 0x8b, 0x30, + 0xe0, 0xc2, 0x26, 0x86, 0x43, 0x74, 0x2a, 0x4c, 0xb4, 0xc4, 0xbd, 0x8d, 0xb9, 0xea, 0xe5, 0xf6, 0xeb, 0xaa, 0x4b, + 0xfd, 0xb5, 0x84, 0x41, 0x64, 0x7a, 0x68, 0x00, 0x07, 0x42, 0xc6, 0xfa, 0x3a, 0x58, 0xc6, 0x71, 0x46, 0xea, 0x32, + 0x6a, 0x04, 0x63, 0xe9, 0x00, 0xe5, 0xac, 0x7a, 0x1c, 0xed, 0x02, 0x62, 0x79, 0x48, 0x6e, 0x9a, 0xcc, 0x08, 0xd9, + 0x22, 0x97, 0x5f, 0x6a, 0xa1, 0xbf, 0x0a, 0x5d, 0x83, 0x7d, 0x0f, 0xe8, 0x64, 0xc1, 0x31, 0xd4, 0x81, 0x9e, 0x75, + 0xeb, 0x59, 0x2d, 0x81, 0x36, 0xc7, 0xc8, 0xb9, 0xb0, 0x38, 0xd5, 0xf3, 0x6c, 0x6c, 0xc3, 0xbd, 0x03, 0x9c, 0x98, + 0xd2, 0xf5, 0x02, 0xdc, 0x76, 0x70, 0x69, 0xf8, 0xd8, 0x83, 0x6b, 0x75, 0x8c, 0x63, 0x67, 0x92, 0x66, 0xf2, 0x68, + 0xe2, 0xe6, 0x9c, 0x73, 0xa1, 0xc7, 0x39, 0x3c, 0x37, 0xb8, 0xfe, 0x4c, 0xc5, 0x78, 0xb9, 0x55, 0x09, 0xb4, 0x5c, + 0xe5, 0xac, 0x4b, 0x22, 0x96, 0xac, 0x38, 0x47, 0x5f, 0x08, 0xe4, 0x79, 0x9b, 0xdf, 0x2f, 0x34, 0xe8, 0x9c, 0xba, + 0xa8, 0xce, 0x31, 0xc9, 0xec, 0xf4, 0x60, 0xf3, 0x5e, 0x59, 0x1c, 0x1a, 0xab, 0x94, 0x8d, 0xf8, 0xa1, 0x71, 0x4f, + 0x6f, 0xef, 0x67, 0x36, 0x29, 0xe5, 0xcb, 0xa8, 0x72, 0x95, 0xdf, 0xc6, 0x81, 0xa6, 0x9d, 0x91, 0xc8, 0x6e, 0xeb, + 0xdb, 0x75, 0x92, 0x44, 0x24, 0xe9, 0xee, 0x82, 0x28, 0x3c, 0x83, 0x6f, 0x8c, 0x28, 0xd8, 0xd3, 0xa9, 0x75, 0xf9, + 0xd2, 0xcb, 0x72, 0xbc, 0xc2, 0xde, 0x15, 0x83, 0xb1, 0x70, 0x42, 0x07, 0x0b, 0x37, 0x0d, 0x34, 0xb0, 0x93, 0x24, + 0x6e, 0x55, 0x12, 0x6f, 0x5a, 0xfe, 0x99, 0x34, 0x37, 0x22, 0x4f, 0x45, 0x7c, 0x5d, 0xa2, 0xcf, 0x9c, 0x29, 0xd5, + 0xbd, 0x51, 0x45, 0x51, 0x8e, 0x79, 0x2a, 0x27, 0x2c, 0x4a, 0xba, 0x9d, 0x22, 0xf3, 0x64, 0xcf, 0x9b, 0xdb, 0x19, + 0x25, 0x4a, 0x20, 0x75, 0xa1, 0x97, 0xb9, 0x6b, 0x15, 0x3a, 0xd2, 0xc8, 0x98, 0x56, 0x35, 0x9b, 0xe8, 0x71, 0x38, + 0xfb, 0xc9, 0xca, 0x9e, 0xe0, 0xb6, 0xf7, 0xbc, 0xb3, 0x0f, 0x9b, 0x0b, 0xae, 0x43, 0x2b, 0x86, 0xcc, 0x80, 0x99, + 0x66, 0xee, 0x4c, 0x81, 0x2c, 0xec, 0xbe, 0xb2, 0x23, 0x51, 0xc6, 0xf4, 0x8f, 0xad, 0xd6, 0xf3, 0xe5, 0xa0, 0xea, + 0x98, 0xfc, 0xfe, 0x2b, 0x5d, 0xc3, 0x4d, 0x07, 0x45, 0x8e, 0xe1, 0x58, 0xd3, 0xde, 0x10, 0x87, 0x07, 0xc2, 0xf3, + 0x84, 0x18, 0x85, 0x15, 0x6c, 0x8b, 0x46, 0xad, 0xae, 0x02, 0x06, 0x6a, 0x0c, 0x4f, 0xc6, 0xde, 0x28, 0x2c, 0xa3, + 0xf1, 0x95, 0xdd, 0xca, 0x2b, 0x8b, 0x6e, 0x59, 0x93, 0xba, 0x55, 0x4e, 0xa9, 0x3f, 0xe2, 0x5a, 0xb9, 0xc3, 0x84, + 0xdc, 0x70, 0x9d, 0x14, 0x92, 0x7a, 0xe3, 0xb1, 0xc1, 0xb7, 0x76, 0x9e, 0x69, 0xa0, 0xdb, 0xd6, 0xd2, 0x36, 0xb1, + 0x83, 0x69, 0xb8, 0x6d, 0x18, 0xa6, 0xaa, 0x1d, 0xe5, 0xf3, 0xd7, 0xfb, 0xfb, 0xb3, 0xb0, 0x26, 0xb4, 0x69, 0xe8, + 0x6b, 0xa0, 0x6d, 0x8b, 0x62, 0x2e, 0x66, 0x9a, 0x32, 0xb6, 0xd8, 0x77, 0x20, 0xdb, 0x77, 0x7f, 0xb5, 0x92, 0xd0, + 0x24, 0x57, 0xe9, 0xfc, 0x92, 0xfc, 0xa4, 0xd3, 0x09, 0x22, 0x33, 0xbb, 0xc8, 0xef, 0x73, 0xa1, 0xbe, 0xbf, 0x50, + 0xbd, 0x52, 0x1b, 0x59, 0x0a, 0x88, 0xc2, 0x50, 0x78, 0x13, 0x02, 0xd3, 0x25, 0x0b, 0xff, 0x07, 0x3a, 0xce, 0xc9, + 0x98, 0x12, 0xd2, 0x1b, 0x55, 0x3d, 0xba, 0x21, 0xa1, 0x18, 0x5a, 0x58, 0x4e, 0xce, 0x4b, 0x0d, 0xba, 0x3a, 0x11, + 0x3a, 0xb2, 0x3c, 0x50, 0x01, 0x7e, 0xa2, 0xa0, 0x3a, 0x33, 0x33, 0xec, 0x7e, 0x12, 0x18, 0x4b, 0xab, 0x5a, 0xa5, + 0x5f, 0x74, 0x38, 0x5d, 0x51, 0x77, 0x4f, 0xbf, 0x62, 0x48, 0x74, 0x87, 0x5f, 0x2e, 0xb4, 0x3e, 0xb9, 0xb8, 0x14, + 0x68, 0xa4, 0xe3, 0x06, 0x0e, 0xe1, 0xa2, 0xb4, 0xe0, 0xbb, 0x94, 0xc1, 0x86, 0x39, 0x3c, 0xcc, 0x0e, 0xe0, 0x5c, + 0xb9, 0xad, 0x3c, 0x2b, 0x37, 0x81, 0xe9, 0x8b, 0x94, 0xd1, 0x89, 0x63, 0xd4, 0x7d, 0xbb, 0xfb, 0x51, 0x92, 0xf2, + 0xfa, 0xf1, 0x2a, 0x6b, 0x94, 0xbd, 0x67, 0x66, 0x69, 0x57, 0xf1, 0xa7, 0x26, 0xb9, 0x6b, 0x6b, 0xe8, 0xa4, 0x5b, + 0x01, 0xc0, 0x28, 0x37, 0x2b, 0x2c, 0x77, 0x01, 0xc8, 0x61, 0x73, 0x2f, 0x43, 0x41, 0x9a, 0xec, 0x44, 0x55, 0x62, + 0x42, 0x48, 0xa1, 0xfd, 0x71, 0x77, 0xee, 0x8f, 0x4e, 0x16, 0xd0, 0x51, 0xdf, 0x85, 0x6a, 0xe6, 0xed, 0xdf, 0x2e, + 0x77, 0xec, 0x60, 0x9a, 0xae, 0x01, 0xd3, 0x3c, 0x40, 0xe8, 0xae, 0xda, 0x41, 0xff, 0x12, 0xff, 0x94, 0xef, 0x2f, + 0x08, 0xda, 0xc1, 0x17, 0xd1, 0x9a, 0xf2, 0xb1, 0x38, 0x28, 0xa1, 0x6c, 0xdb, 0x2e, 0x4d, 0xc0, 0x14, 0x39, 0x48, + 0xb7, 0x90, 0x81, 0x28, 0x59, 0x08, 0x66, 0x50, 0xf9, 0x59, 0xbc, 0x4c, 0x7c, 0x9d, 0x8f, 0x36, 0x3c, 0x46, 0x14, + 0xae, 0x0a, 0xb9, 0x86, 0x61, 0xb4, 0x98, 0x57, 0xa7, 0x9d, 0x54, 0x1b, 0x87, 0x06, 0x35, 0xea, 0x18, 0xdb, 0x75, + 0x7c, 0xf8, 0xce, 0x12, 0x75, 0x83, 0xe6, 0xd5, 0x9a, 0x80, 0x1f, 0x1b, 0x8d, 0x15, 0x67, 0x0f, 0x71, 0x6a, 0xcd, + 0x78, 0xbc, 0x50, 0xf6, 0xa8, 0xea, 0x15, 0xb5, 0xc6, 0x41, 0x0e, 0x76, 0x5a, 0x1b, 0x93, 0x14, 0x72, 0xe7, 0xe1, + 0x52, 0xbe, 0x73, 0x0a, 0x97, 0xde, 0xa8, 0x44, 0x94, 0x07, 0xd0, 0x09, 0x5b, 0x2a, 0x6e, 0x54, 0xdc, 0x02, 0x2a, + 0x67, 0x7a, 0xb2, 0x24, 0xa6, 0x73, 0x12, 0x31, 0x66, 0x7c, 0x4a, 0xb7, 0xe3, 0x10, 0xd5, 0xd0, 0x0c, 0x3d, 0xbd, + 0x8f, 0xea, 0x09, 0x72, 0x40, 0x6a, 0xe3, 0x3a, 0x43, 0x88, 0x4e, 0x2a, 0x7c, 0x51, 0x2b, 0x62, 0xcb, 0xfc, 0x13, + 0x41, 0x6d, 0x5b, 0x33, 0x3f, 0x22, 0xca, 0xab, 0xa5, 0x6f, 0x72, 0x7f, 0xc5, 0x9e, 0xf1, 0x8a, 0xc7, 0xe8, 0xca, + 0x4b, 0x7e, 0x0e, 0xf3, 0xb3, 0x5f, 0x3f, 0x80, 0x09, 0x44, 0x9c, 0x67, 0x32, 0xea, 0x29, 0x39, 0x9a, 0xeb, 0x9c, + 0xf7, 0x2d, 0x74, 0x46, 0xc9, 0x34, 0x20, 0x62, 0x2d, 0xb3, 0x04, 0xe2, 0xc8, 0x24, 0x71, 0x51, 0x28, 0xeb, 0xa8, + 0x93, 0x47, 0x07, 0xbd, 0x37, 0x91, 0xfb, 0x82, 0x26, 0x7d, 0x05, 0xfe, 0xd8, 0x55, 0x5b, 0x5b, 0xb5, 0x6f, 0x5e, + 0x04, 0xea, 0xe6, 0x2c, 0x8f, 0xe3, 0x5c, 0x15, 0xd0, 0xbf, 0xce, 0x95, 0xd6, 0x52, 0x5d, 0xa0, 0x8b, 0x43, 0x8e, + 0x51, 0x8b, 0xf2, 0xd4, 0x6a, 0x18, 0xda, 0x03, 0x65, 0xaf, 0x61, 0x42, 0x0f, 0xf8, 0xf9, 0x3a, 0xc9, 0x68, 0xf0, + 0xce, 0xc3, 0xb6, 0x1a, 0xd3, 0x45, 0xce, 0xbb, 0x05, 0x04, 0xdc, 0xae, 0xb7, 0xa4, 0x26, 0x42, 0x6a, 0xdc, 0x64, + 0x62, 0x8b, 0xc4, 0xbc, 0x8a, 0x3a, 0x9c, 0x5d, 0x21, 0xa9, 0x43, 0x6c, 0x8a, 0x11, 0xa6, 0xa0, 0x36, 0xe3, 0x62, + 0x43, 0x8b, 0x71, 0x13, 0x39, 0xb0, 0x36, 0x8f, 0x24, 0xe3, 0x70, 0x47, 0x33, 0x1d, 0x06, 0x72, 0x2d, 0xc1, 0x65, + 0x89, 0xe8, 0x6d, 0xaa, 0xf6, 0x1a, 0x32, 0x6c, 0x6e, 0x91, 0x2c, 0x94, 0x99, 0x1e, 0x0f, 0x81, 0x76, 0xfd, 0xa5, + 0x6d, 0x3b, 0x54, 0xf0, 0x97, 0xf3, 0x98, 0x14, 0xdb, 0xef, 0xb3, 0x5f, 0x3a, 0x88, 0x21, 0x9c, 0x3a, 0xdd, 0x37, + 0x01, 0xd6, 0x39, 0xe4, 0x14, 0x1b, 0xc2, 0x18, 0x67, 0x1c, 0xb5, 0xbb, 0x8e, 0x36, 0xf6, 0x53, 0x62, 0x08, 0x94, + 0x0e, 0xdf, 0xee, 0x68, 0xe5, 0x75, 0x47, 0xb6, 0x66, 0x7b, 0x49, 0x3b, 0xf2, 0x31, 0x39, 0x82, 0x49, 0x10, 0x49, + 0x4b, 0x03, 0xa1, 0x19, 0x83, 0xb7, 0x48, 0x0b, 0xd6, 0x7a, 0x06, 0xb4, 0xcc, 0xf5, 0x42, 0xa1, 0x05, 0x9e, 0xbe, + 0x31, 0x30, 0x29, 0x2c, 0x3a, 0xb8, 0xa4, 0x83, 0x67, 0x33, 0xcc, 0x16, 0xaa, 0xd5, 0xfa, 0xea, 0xc8, 0xf6, 0x26, + 0x51, 0x20, 0xec, 0xbc, 0xa4, 0x9b, 0xed, 0x47, 0x7e, 0x66, 0x9e, 0x80, 0x39, 0x39, 0xb7, 0xeb, 0xfe, 0xdc, 0xee, + 0xfd, 0x7a, 0x0d, 0x3e, 0xf6, 0x37, 0x38, 0xff, 0x0c, 0x31, 0x2c, 0x4b, 0x26, 0x4c, 0x57, 0x16, 0x93, 0x5f, 0x83, + 0xe6, 0x3e, 0xd9, 0x98, 0x4c, 0xef, 0x50, 0xa2, 0x89, 0xaf, 0xbb, 0x50, 0x60, 0x6c, 0x19, 0xd1, 0xcb, 0xfa, 0xed, + 0x3e, 0x3e, 0xbd, 0x2d, 0xda, 0x20, 0xa6, 0x5e, 0x59, 0x32, 0x1e, 0x56, 0xf4, 0xc5, 0x43, 0x50, 0x1a, 0x3f, 0xaa, + 0x4c, 0x06, 0x5f, 0xd4, 0xbb, 0x16, 0x12, 0xe1, 0x1f, 0xf5, 0x28, 0x4f, 0x66, 0x2b, 0x1b, 0xb6, 0x5d, 0x4f, 0xca, + 0x03, 0x21, 0x61, 0x34, 0xfd, 0xa3, 0x49, 0x6b, 0x2e, 0xa5, 0xe1, 0x6f, 0x96, 0xe2, 0xa2, 0xd9, 0x38, 0xca, 0xb6, + 0x12, 0x18, 0x5d, 0xd5, 0x42, 0xb2, 0x28, 0x3c, 0x2c, 0x78, 0x28, 0x5f, 0x56, 0xc2, 0xb2, 0x17, 0xdd, 0xe9, 0x44, + 0xbe, 0xb1, 0xf7, 0x2b, 0xf5, 0xff, 0x4f, 0x55, 0xfc, 0xf9, 0xa9, 0x9d, 0x28, 0xab, 0x9f, 0x75, 0xa1, 0xa3, 0x1a, + 0xeb, 0x63, 0xf3, 0xcf, 0xa7, 0xfe, 0xf5, 0x35, 0x9b, 0x9a, 0xbd, 0x6c, 0x07, 0x4c, 0xf7, 0x14, 0xb0, 0x65, 0xda, + 0x1e, 0xb6, 0x14, 0x3f, 0xde, 0xbe, 0x38, 0x6a, 0x17, 0x24, 0x3e, 0x61, 0xc1, 0x91, 0xa4, 0x78, 0x6c, 0xd0, 0x81, + 0x52, 0xcb, 0x80, 0x7a, 0x04, 0xfb, 0xb6, 0xb1, 0x23, 0xdf, 0x38, 0x4c, 0x7e, 0x33, 0xcd, 0xdb, 0x0e, 0x61, 0xde, + 0xe6, 0x21, 0xb0, 0x71, 0x9b, 0xbb, 0x1c, 0x18, 0x12, 0x07, 0x1a, 0x62, 0xa6, 0xfd, 0x17, 0xdd, 0x9f, 0xc9, 0xaa, + 0x4e, 0xa5, 0x43, 0x31, 0xe6, 0x6a, 0x25, 0xe6, 0xa4, 0x31, 0x37, 0xb4, 0x23, 0xdc, 0xb0, 0x37, 0xd2, 0x0b, 0x47, + 0xb7, 0x18, 0x71, 0x60, 0x44, 0x9d, 0x16, 0x7d, 0x41, 0xa2, 0xa2, 0xc9, 0x9e, 0x56, 0x1e, 0x5f, 0x08, 0x93, 0xc0, + 0xba, 0xdb, 0xec, 0x9c, 0xbf, 0x10, 0xc9, 0xec, 0x89, 0x30, 0x99, 0x43, 0xbd, 0x83, 0x97, 0x94, 0x68, 0xd5, 0xb6, + 0x5b, 0x07, 0x04, 0x76, 0x02, 0xe6, 0x69, 0x89, 0xbc, 0x4e, 0xc9, 0xec, 0xde, 0xbe, 0xf5, 0xb3, 0x4a, 0x99, 0x98, + 0x41, 0xcd, 0x3e, 0x0e, 0x49, 0x43, 0xc4, 0x71, 0x5c, 0xda, 0x6d, 0xc8, 0x0c, 0xc4, 0x58, 0x7f, 0x71, 0xf2, 0x64, + 0xf2, 0x02, 0xb5, 0x92, 0x26, 0xe1, 0x98, 0x1b, 0x0d, 0x13, 0x2b, 0xe3, 0x55, 0xc6, 0x15, 0xbb, 0x15, 0xfd, 0x24, + 0xf2, 0xa0, 0x45, 0x2b, 0xa1, 0xf7, 0x54, 0xb9, 0x48, 0x4a, 0x5a, 0xe1, 0x50, 0xa7, 0xfc, 0xc1, 0x50, 0x0e, 0x93, + 0x63, 0x59, 0xd7, 0x59, 0x8c, 0x61, 0x76, 0x96, 0x53, 0x18, 0xb9, 0xc5, 0x29, 0x62, 0xd1, 0x19, 0x72, 0x19, 0xd6, + 0x50, 0x01, 0x01, 0xe3, 0xc0, 0x14, 0x7e, 0xf2, 0x71, 0xa1, 0xfd, 0xed, 0x0c, 0x5c, 0xb6, 0xf1, 0x45, 0xc2, 0xd0, + 0x8d, 0x71, 0x2b, 0x98, 0xc1, 0x64, 0x88, 0x9e, 0xaf, 0xc0, 0xba, 0xdc, 0x89, 0xe7, 0x25, 0xcb, 0xa9, 0x66, 0xc8, + 0xa1, 0x59, 0x37, 0xeb, 0x75, 0xdf, 0x44, 0xab, 0x14, 0x0a, 0x88, 0x35, 0x4e, 0x5a, 0xd3, 0x35, 0xdd, 0x12, 0x74, + 0x44, 0x49, 0x66, 0x06, 0x33, 0x03, 0xe4, 0xec, 0x24, 0xa3, 0x56, 0x03, 0x7d, 0x31, 0x9c, 0x58, 0xcc, 0x34, 0x31, + 0x18, 0xb0, 0x36, 0x64, 0x1f, 0x64, 0x17, 0xbe, 0x01, 0xa0, 0xfa, 0xca, 0x29, 0xc3, 0x98, 0x4c, 0xde, 0x68, 0xa4, + 0x31, 0x5b, 0xf6, 0xbf, 0x68, 0xaf, 0x01, 0xac, 0x26, 0xd9, 0x8f, 0xf6, 0x02, 0x7d, 0x5d, 0xaf, 0x11, 0xd3, 0x16, + 0x3e, 0x61, 0xab, 0x03, 0xb6, 0x48, 0xea, 0x2e, 0x92, 0x18, 0x49, 0x7a, 0x9e, 0x6b, 0x34, 0xa2, 0x04, 0x43, 0xf5, + 0xd4, 0x2c, 0x7b, 0x1e, 0xa9, 0x78, 0x6b, 0xc2, 0x2b, 0x96, 0xe2, 0x00, 0x74, 0x41, 0xb5, 0x75, 0x08, 0xce, 0x76, + 0xbc, 0x22, 0x0a, 0x63, 0x3d, 0x8a, 0x25, 0x4e, 0xf8, 0x7d, 0xf1, 0x4a, 0x4e, 0x39, 0x42, 0x69, 0xc6, 0xe7, 0xe0, + 0x5c, 0x99, 0xc7, 0x1f, 0x7f, 0xe9, 0x63, 0x1c, 0xb1, 0x8f, 0x87, 0xd8, 0xe7, 0x67, 0x39, 0x3d, 0xff, 0x2e, 0xe1, + 0xa9, 0xf8, 0x2a, 0x7d, 0x0d, 0xef, 0x2c, 0x21, 0xe7, 0x3d, 0x08, 0xff, 0x85, 0x16, 0x07, 0xc3, 0x42, 0x27, 0xe7, + 0xa2, 0x1e, 0x9c, 0x5f, 0x3d, 0x27, 0xb8, 0xa8, 0xf1, 0x98, 0x00, 0x99, 0x4a, 0xca, 0xea, 0xb7, 0x44, 0x0a, 0x64, + 0x5d, 0x54, 0xc4, 0xc9, 0x13, 0x58, 0xa6, 0x80, 0x73, 0x47, 0x19, 0x06, 0x0d, 0x59, 0xf7, 0xe3, 0x6d, 0x67, 0x89, + 0x1d, 0x30, 0xff, 0x63, 0x52, 0xac, 0x5e, 0x83, 0x48, 0x06, 0x95, 0xb4, 0x93, 0x54, 0xfa, 0xa0, 0xc0, 0x03, 0x1b, + 0x4d, 0xce, 0xab, 0xa6, 0xb2, 0xcc, 0x13, 0xe8, 0x0c, 0x48, 0x63, 0x4b, 0x53, 0xc6, 0xa7, 0x19, 0xa0, 0x18, 0x85, + 0x37, 0xb2, 0xb5, 0x6a, 0x0c, 0x12, 0x49, 0x75, 0xfe, 0x8d, 0x3d, 0xce, 0x54, 0xe1, 0x55, 0x74, 0xe1, 0x9c, 0x9b, + 0x8f, 0x0e, 0x9c, 0x0f, 0x6b, 0x9b, 0xe1, 0xcb, 0x9f, 0x11, 0x2d, 0xb0, 0x6b, 0x52, 0x07, 0x55, 0x59, 0xf3, 0x92, + 0x4e, 0xf8, 0x27, 0x6c, 0x2f, 0x51, 0xcc, 0xe4, 0x25, 0xf5, 0x17, 0xd3, 0x11, 0x52, 0xf3, 0xd0, 0x65, 0xd6, 0xff, + 0x51, 0xcc, 0xe4, 0x78, 0x18, 0xab, 0xe9, 0x87, 0xc6, 0xf8, 0xb7, 0x88, 0x0a, 0xb8, 0x4f, 0xeb, 0x87, 0x2a, 0x32, + 0xb1, 0x3a, 0xae, 0x8d, 0x17, 0xe4, 0x3e, 0x86, 0xe9, 0x68, 0xb1, 0xca, 0xb2, 0x8c, 0x7b, 0xa5, 0xcc, 0xca, 0x14, + 0x82, 0xc3, 0x33, 0x65, 0x44, 0x15, 0x3a, 0xcf, 0x21, 0x2f, 0xcd, 0xfc, 0xb2, 0x4a, 0x85, 0xe9, 0x43, 0x99, 0x8b, + 0xce, 0x5b, 0xa2, 0xee, 0x7a, 0x5f, 0xf8, 0xfd, 0x13, 0x0a, 0x6d, 0x31, 0x10, 0xf2, 0xc1, 0x19, 0x9c, 0x26, 0xd1, + 0x3d, 0x32, 0x11, 0x79, 0xf8, 0x8b, 0x5d, 0xbf, 0x08, 0x95, 0xf4, 0xfc, 0x60, 0x1e, 0xbe, 0x76, 0x59, 0xdc, 0xbd, + 0xd2, 0xaf, 0xd5, 0xe7, 0xe8, 0x43, 0x09, 0x47, 0x41, 0x46, 0x69, 0xf7, 0xc3, 0xf2, 0x71, 0x1d, 0x28, 0x5f, 0xff, + 0x83, 0xe4, 0x73, 0xc8, 0xa2, 0x11, 0xd1, 0x12, 0x9d, 0x6a, 0x07, 0x47, 0xfd, 0x30, 0x0b, 0xe1, 0xdf, 0x64, 0x08, + 0xce, 0xa2, 0xab, 0x5e, 0x1e, 0x4c, 0xeb, 0xc9, 0x3f, 0x21, 0x93, 0xdf, 0x2f, 0xaa, 0xc9, 0xe1, 0x34, 0x5c, 0xf0, + 0x23, 0x15, 0xfd, 0xb0, 0xaa, 0xdb, 0xfb, 0x69, 0x9f, 0xa7, 0x3d, 0x04, 0xcc, 0x3e, 0xd2, 0x10, 0xc9, 0x9b, 0xa5, + 0x75, 0x18, 0x66, 0x09, 0x8b, 0x0b, 0x5a, 0x83, 0x9e, 0xaa, 0x5a, 0xa5, 0x55, 0xe0, 0x8c, 0x8f, 0x04, 0x1d, 0x34, + 0x70, 0xb4, 0x5c, 0x79, 0xf8, 0x42, 0xc0, 0xe2, 0xa4, 0x62, 0xbb, 0x2d, 0xca, 0xf2, 0x9e, 0xc1, 0x71, 0xb4, 0x88, + 0x9a, 0xcc, 0xcb, 0xaf, 0x52, 0x5b, 0xc9, 0xc5, 0x55, 0xb4, 0xe0, 0x8e, 0xbe, 0x90, 0x14, 0x4b, 0x2d, 0x0d, 0xf9, + 0xba, 0x94, 0x6c, 0xa3, 0x02, 0x95, 0x52, 0xae, 0xa9, 0x1a, 0x97, 0x33, 0x70, 0x65, 0x6c, 0x4d, 0x25, 0x0e, 0xb4, + 0x2a, 0x5e, 0x83, 0xe4, 0xd2, 0xf3, 0x6b, 0x84, 0x97, 0x08, 0x59, 0x06, 0x0e, 0x0f, 0x67, 0x25, 0x7d, 0x9e, 0x5c, + 0xc3, 0x28, 0x9e, 0xf3, 0x98, 0x83, 0xa9, 0xf3, 0x81, 0x32, 0x57, 0x34, 0x59, 0x8b, 0x8a, 0xe6, 0xe4, 0xd4, 0xb9, + 0xea, 0x80, 0xba, 0x22, 0x23, 0x30, 0x0b, 0xf7, 0x91, 0x1f, 0xa5, 0xa4, 0x57, 0xca, 0xf0, 0x15, 0xee, 0x44, 0x49, + 0xb6, 0xe3, 0x64, 0x38, 0xdc, 0xd1, 0xc6, 0xce, 0x85, 0x1a, 0x3f, 0x0a, 0x79, 0xa5, 0x6c, 0x8f, 0x91, 0x31, 0x94, + 0x5c, 0xec, 0x9e, 0xba, 0x2e, 0x1a, 0x22, 0x3d, 0x59, 0x10, 0x00, 0x91, 0x87, 0xb1, 0x0f, 0x16, 0x97, 0x47, 0x53, + 0x0b, 0x98, 0x98, 0xe7, 0xca, 0x4f, 0xda, 0x2b, 0x7c, 0xb5, 0x0e, 0x79, 0x15, 0xca, 0x24, 0x6a, 0xa5, 0xcd, 0xf8, + 0x4d, 0x59, 0x1e, 0x95, 0x57, 0xcb, 0x6a, 0xba, 0xa2, 0x7a, 0xd0, 0x98, 0x1e, 0xae, 0x53, 0x62, 0x6c, 0x2c, 0xb3, + 0x4e, 0xd5, 0x2b, 0xe6, 0xbf, 0xcf, 0xd6, 0x2b, 0x65, 0x75, 0x51, 0xd5, 0xf2, 0x2d, 0xca, 0xed, 0x8f, 0x10, 0x60, + 0x51, 0xc3, 0x03, 0xbe, 0x5e, 0x2e, 0x63, 0x58, 0xae, 0x09, 0x66, 0xd9, 0x2f, 0xaf, 0xb7, 0xd5, 0x10, 0xa4, 0x1d, + 0x8f, 0xe2, 0x39, 0xe8, 0x46, 0x0c, 0x68, 0xa4, 0xd4, 0x08, 0x58, 0x93, 0x42, 0x0c, 0x9e, 0x75, 0xfb, 0x13, 0x05, + 0x22, 0x15, 0x1c, 0xc1, 0x03, 0xc2, 0xa9, 0x89, 0xcb, 0x0f, 0xb3, 0xee, 0x16, 0x62, 0x48, 0xc5, 0xcb, 0x5d, 0xf9, + 0xb4, 0xa4, 0x64, 0x64, 0x83, 0x1a, 0x11, 0x92, 0x21, 0x04, 0x75, 0x33, 0x23, 0xa3, 0x0e, 0xce, 0x18, 0x7d, 0x23, + 0x62, 0xc0, 0x29, 0x05, 0x40, 0x1e, 0x73, 0x3a, 0x69, 0x2e, 0x5f, 0xe6, 0x2e, 0xfd, 0x03, 0x3d, 0x95, 0xe3, 0xd6, + 0x44, 0xd0, 0xa6, 0x57, 0x51, 0xe7, 0xa2, 0xe2, 0xe0, 0x7a, 0x59, 0x62, 0xc0, 0x44, 0xf3, 0x85, 0xab, 0xd3, 0xe4, + 0xb2, 0x85, 0x8f, 0xcf, 0x4d, 0xf6, 0xe1, 0x23, 0xe2, 0xf1, 0x04, 0x31, 0x3c, 0xec, 0xd4, 0x6d, 0x23, 0x07, 0x8c, + 0x00, 0x7f, 0xab, 0xdf, 0x4f, 0x5c, 0x5d, 0xb6, 0xcc, 0x05, 0xa9, 0xa1, 0x6f, 0xf0, 0xed, 0x60, 0xfc, 0xa9, 0xab, + 0x1b, 0x4f, 0xc9, 0xba, 0x47, 0x16, 0xdd, 0xbd, 0xac, 0xe1, 0x0b, 0x26, 0xe8, 0xfc, 0x3c, 0x23, 0xf7, 0xc9, 0x82, + 0xe5, 0xbe, 0x14, 0x8d, 0x04, 0x99, 0xc6, 0xc3, 0x14, 0x23, 0x72, 0x8d, 0x67, 0x39, 0x5a, 0x6b, 0x4c, 0x6c, 0xb9, + 0xe0, 0x88, 0xed, 0x34, 0xb8, 0x6c, 0x93, 0x8c, 0xa5, 0x1a, 0xdf, 0xdf, 0x44, 0x22, 0x18, 0xa9, 0xa1, 0x7d, 0x5e, + 0xd6, 0xa7, 0xca, 0xa7, 0xff, 0x63, 0x7e, 0x74, 0x61, 0x65, 0x98, 0x23, 0x30, 0xe3, 0x6b, 0xfc, 0x34, 0xd3, 0x12, + 0x58, 0x78, 0xd0, 0xda, 0xbd, 0xec, 0x6e, 0xac, 0x39, 0xa3, 0x1f, 0xa6, 0x3b, 0x25, 0xd9, 0x05, 0xb6, 0xe3, 0x5f, + 0x83, 0x9e, 0x0a, 0xa5, 0x1f, 0xd5, 0x81, 0xc5, 0x1f, 0x05, 0x89, 0x09, 0xc9, 0x50, 0x80, 0x03, 0xb9, 0x6a, 0xde, + 0x7b, 0xba, 0x1d, 0x4b, 0xe8, 0x11, 0x97, 0xce, 0x56, 0x5f, 0xde, 0xc8, 0xbc, 0x40, 0x67, 0xce, 0x7e, 0xb4, 0x79, + 0xbf, 0x65, 0x1e, 0x6f, 0x68, 0x2f, 0x8c, 0xdf, 0x50, 0x18, 0x5a, 0xe3, 0x4b, 0x48, 0x18, 0x0c, 0xdd, 0x19, 0xd6, + 0xde, 0xd4, 0x7b, 0xfc, 0xa2, 0xd7, 0xea, 0x2c, 0x98, 0xf1, 0xaa, 0x44, 0x2b, 0x13, 0x97, 0x1b, 0x4a, 0xf2, 0xe1, + 0x1e, 0xc1, 0x75, 0xfc, 0x6c, 0x27, 0xe1, 0xae, 0xc7, 0x37, 0xba, 0x60, 0x19, 0x06, 0xa6, 0x2b, 0x5c, 0x07, 0x62, + 0x08, 0x63, 0xcb, 0x08, 0xb2, 0xd4, 0x9f, 0xe8, 0x37, 0x8c, 0x82, 0xf1, 0xfb, 0xc3, 0xf5, 0x9b, 0xdd, 0xf1, 0x1f, + 0x56, 0x00, 0xce, 0x5d, 0x84, 0x23, 0xb0, 0xcc, 0x46, 0x50, 0x61, 0x81, 0x0b, 0x7d, 0x54, 0x9e, 0xa6, 0xca, 0x01, + 0xe8, 0x02, 0xf2, 0x73, 0x4c, 0xfb, 0xc3, 0xae, 0xf3, 0x7b, 0x29, 0xf2, 0xb3, 0x8d, 0x6e, 0xdf, 0x6f, 0x96, 0x6e, + 0x74, 0xbf, 0x6d, 0x71, 0x99, 0x70, 0x4d, 0xb3, 0xe1, 0x9a, 0x45, 0xa7, 0x77, 0x62, 0xc7, 0x4d, 0xed, 0x59, 0x5b, + 0x96, 0xe4, 0x64, 0xb4, 0x5f, 0x6b, 0xea, 0x32, 0x99, 0xd3, 0x6d, 0xcd, 0xe4, 0xec, 0xd4, 0x7c, 0xf3, 0x8f, 0x95, + 0x26, 0x8b, 0xfd, 0xc5, 0xfe, 0x3a, 0x8b, 0x1e, 0xc5, 0x97, 0x0b, 0x2d, 0xf3, 0xa6, 0xa8, 0x01, 0x7e, 0xbc, 0xd3, + 0x2c, 0x94, 0x10, 0xb6, 0x0e, 0x19, 0xf7, 0x36, 0x34, 0x40, 0x73, 0xf2, 0x4e, 0x90, 0xa2, 0x04, 0x92, 0x77, 0xac, + 0xc0, 0x52, 0xf1, 0x81, 0x61, 0xf0, 0xd8, 0xfb, 0xd4, 0xbb, 0x9a, 0xa2, 0xae, 0x70, 0x99, 0x68, 0xec, 0x46, 0x99, + 0x2f, 0xdc, 0x47, 0xb7, 0xd3, 0x7f, 0x44, 0xce, 0xe0, 0xaf, 0x44, 0xbe, 0x0f, 0x8e, 0x0f, 0x43, 0x47, 0x59, 0x3c, + 0xa9, 0xbc, 0xf2, 0xb9, 0x03, 0xb9, 0x7d, 0x2d, 0x9f, 0xe6, 0xba, 0x02, 0x89, 0x1c, 0xf3, 0xcd, 0x5c, 0xd4, 0xa2, + 0xd6, 0x4a, 0x73, 0xb7, 0x3c, 0x9a, 0x86, 0x18, 0x1d, 0x00, 0x90, 0x32, 0x60, 0xfc, 0x14, 0xab, 0xa9, 0x33, 0xfe, + 0xe9, 0x2c, 0xd5, 0x39, 0xdd, 0xdf, 0xbc, 0x23, 0xd3, 0x5b, 0x9a, 0x03, 0xbe, 0x0b, 0xf9, 0xbf, 0xfd, 0x13, 0xdd, + 0x3a, 0x26, 0xb2, 0x84, 0xd9, 0xc1, 0xd5, 0x8d, 0xe4, 0xa2, 0xf5, 0xad, 0x89, 0xab, 0x98, 0x78, 0xdf, 0xa7, 0xb6, + 0xc5, 0x43, 0x02, 0xa3, 0x29, 0xdc, 0x66, 0xd1, 0xe8, 0x25, 0x34, 0x0e, 0x33, 0x61, 0xb6, 0x8d, 0x8e, 0x65, 0x0d, + 0xc1, 0xf5, 0x3e, 0x21, 0xd5, 0xc5, 0x93, 0x39, 0xb3, 0xba, 0x7e, 0xf3, 0x54, 0x80, 0x27, 0x56, 0x51, 0x8d, 0x5e, + 0xda, 0xb6, 0x7b, 0xd4, 0xa5, 0xa6, 0xfb, 0x13, 0xa7, 0x11, 0xec, 0x51, 0x10, 0xa2, 0x03, 0x61, 0x79, 0xa4, 0xa1, + 0x63, 0x83, 0x7d, 0x9b, 0x0f, 0xd8, 0x0b, 0x00, 0x74, 0x80, 0x87, 0xe3, 0x26, 0x3a, 0xaf, 0xfa, 0xc7, 0x1e, 0x90, + 0x6a, 0x7c, 0x8f, 0x26, 0x55, 0x6e, 0xe4, 0x52, 0x75, 0x91, 0xa0, 0x6c, 0x1a, 0x1f, 0xdf, 0xe5, 0x56, 0xeb, 0xe1, + 0x25, 0xb1, 0x2a, 0x85, 0xda, 0xa6, 0xf7, 0x96, 0x8d, 0x6a, 0x6a, 0xfd, 0xc1, 0x69, 0xc1, 0xb1, 0xa1, 0x4b, 0xad, + 0xa7, 0xdb, 0xe2, 0x51, 0x6b, 0x9d, 0xde, 0x01, 0x9c, 0x6e, 0x20, 0x84, 0x23, 0x5e, 0xfe, 0x4a, 0x72, 0x0a, 0xf0, + 0x06, 0x70, 0x57, 0x9c, 0x80, 0xb0, 0x1d, 0x77, 0xe3, 0x9a, 0x2d, 0xfc, 0xd9, 0x05, 0x88, 0xb0, 0xae, 0x3a, 0xd7, + 0xa1, 0x3c, 0x0b, 0x2a, 0x8c, 0x52, 0x21, 0x01, 0xe1, 0x70, 0x39, 0x9b, 0x14, 0x84, 0x92, 0x80, 0xbe, 0x2a, 0xa6, + 0x3f, 0x94, 0xde, 0x76, 0xbb, 0x71, 0x21, 0x8f, 0xc4, 0xc3, 0x40, 0xc5, 0x7a, 0x4c, 0xdd, 0x17, 0xd8, 0xc6, 0x23, + 0x64, 0x2d, 0xa4, 0xa7, 0xe4, 0x0b, 0x04, 0x49, 0x2e, 0x05, 0x8f, 0x44, 0x2c, 0xee, 0x78, 0x42, 0x6a, 0xa5, 0x8b, + 0x81, 0x10, 0xf1, 0xfa, 0xc0, 0xc5, 0x1c, 0xee, 0x17, 0x76, 0x19, 0xf0, 0x38, 0x8b, 0x69, 0xd0, 0x93, 0xa8, 0xae, + 0xa2, 0xa7, 0x80, 0x74, 0x98, 0x35, 0x0c, 0x04, 0x9c, 0x52, 0xbf, 0xd6, 0x4d, 0x79, 0x43, 0x9c, 0xa5, 0xd7, 0x58, + 0x18, 0xb0, 0xdb, 0xe7, 0x4c, 0xda, 0x0a, 0xd9, 0x52, 0x6a, 0x28, 0x59, 0x09, 0x1a, 0x2e, 0x9d, 0xad, 0xa2, 0x45, + 0x2b, 0x08, 0x7f, 0x5c, 0xcb, 0x8c, 0xa8, 0xbf, 0x90, 0x69, 0xd6, 0xad, 0xbb, 0x84, 0xb4, 0x9a, 0x53, 0x3b, 0xb7, + 0x40, 0xbd, 0xa0, 0x81, 0x79, 0x8c, 0x41, 0x6b, 0x71, 0xa9, 0xc9, 0x5c, 0xd6, 0xc5, 0x30, 0xe3, 0x0d, 0xc3, 0xc3, + 0xc6, 0xac, 0x97, 0xc9, 0xc6, 0xd5, 0x8d, 0x4f, 0x73, 0x09, 0x3a, 0x18, 0x74, 0x91, 0x90, 0x52, 0x9b, 0x50, 0x91, + 0xff, 0x7a, 0xb1, 0x2e, 0x9c, 0x26, 0x24, 0x9b, 0xa9, 0xac, 0x0f, 0x68, 0x60, 0xcf, 0x08, 0x71, 0xf4, 0x03, 0x51, + 0x26, 0xef, 0x3f, 0x1e, 0xcf, 0x9e, 0xfa, 0x03, 0x64, 0xf2, 0x73, 0x1b, 0x4c, 0xb8, 0xd0, 0xe3, 0x04, 0x3e, 0x00, + 0x92, 0x4e, 0x34, 0x13, 0xac, 0xa7, 0x64, 0x75, 0x09, 0x90, 0xf4, 0x29, 0x79, 0x7a, 0x60, 0xc1, 0x54, 0xef, 0x94, + 0x82, 0x11, 0xc9, 0x00, 0x43, 0x3b, 0x69, 0x54, 0x96, 0x0c, 0x24, 0x7b, 0x68, 0x00, 0x7b, 0x0a, 0x05, 0x19, 0x23, + 0xce, 0x1e, 0xfb, 0x7c, 0x04, 0x04, 0xe5, 0xe1, 0x0c, 0xc2, 0xf4, 0x39, 0xc1, 0x46, 0x9e, 0x63, 0xb0, 0xc8, 0x8b, + 0x06, 0x39, 0x2a, 0x7b, 0x59, 0x2b, 0xfc, 0xd7, 0x11, 0x72, 0x81, 0xc1, 0x63, 0x7a, 0xd2, 0xc9, 0xb5, 0x7e, 0x53, + 0x2e, 0x41, 0xc1, 0xe8, 0x53, 0xd6, 0x10, 0xe3, 0xdc, 0x90, 0x2d, 0x99, 0x3b, 0xf5, 0x67, 0xee, 0x62, 0x8a, 0x6f, + 0xe5, 0xcc, 0x28, 0xcb, 0x34, 0xc4, 0x0b, 0x3f, 0x80, 0x83, 0x9f, 0x67, 0x13, 0x83, 0xc2, 0xfb, 0x4e, 0x97, 0xb1, + 0x20, 0x0e, 0x71, 0x95, 0x8f, 0x3e, 0x3d, 0x45, 0x8c, 0x1a, 0x2c, 0x26, 0xb7, 0xa8, 0xd7, 0x0c, 0x7b, 0x0b, 0xf5, + 0x99, 0xc1, 0x50, 0x9b, 0x44, 0x3d, 0xcd, 0x09, 0x1a, 0x1a, 0x5e, 0xf2, 0xa9, 0x92, 0x75, 0x1c, 0x2b, 0xfc, 0x72, + 0x05, 0x98, 0x0b, 0x4c, 0xf3, 0x34, 0x0e, 0x06, 0x2b, 0x2d, 0xd5, 0x30, 0xf2, 0x44, 0xf0, 0x10, 0x4a, 0xdd, 0xe8, + 0x05, 0x34, 0x80, 0xe1, 0x10, 0xd0, 0x8a, 0x5e, 0x5e, 0xf8, 0x48, 0x83, 0x54, 0xed, 0xc8, 0x29, 0xa3, 0x43, 0x4e, + 0x55, 0xfd, 0x85, 0xf8, 0x3f, 0x89, 0x82, 0xa4, 0xcd, 0x0d, 0xc4, 0x5b, 0xca, 0x6e, 0xea, 0x38, 0xcd, 0x1c, 0xa4, + 0xf7, 0x74, 0xb0, 0xd7, 0xca, 0x97, 0xb6, 0xc9, 0x0c, 0xbd, 0x1a, 0x8d, 0x43, 0x01, 0xa8, 0xfd, 0xc7, 0xdb, 0xce, + 0x93, 0x26, 0xe1, 0xcf, 0xb3, 0xdb, 0x6e, 0x41, 0x0f, 0xe1, 0x9d, 0x79, 0x28, 0x1e, 0x78, 0x5f, 0x4f, 0x37, 0x01, + 0x45, 0x87, 0x70, 0x0f, 0xb9, 0x89, 0x46, 0xfd, 0x44, 0x4f, 0x8d, 0xc9, 0x7e, 0xde, 0xbb, 0xc2, 0xe3, 0xcc, 0x09, + 0x00, 0x7b, 0x54, 0xc6, 0x05, 0x77, 0x94, 0x30, 0x4c, 0x6d, 0x68, 0xe3, 0xb2, 0xd3, 0x1d, 0xad, 0xe4, 0x5a, 0xa0, + 0x18, 0x04, 0xd0, 0x79, 0x3e, 0x7d, 0x3c, 0x75, 0x13, 0x61, 0x3b, 0x76, 0x30, 0x3a, 0x99, 0x3f, 0x5d, 0xa6, 0xe6, + 0x37, 0xc9, 0x75, 0x58, 0xd2, 0x4d, 0x13, 0x7a, 0xdf, 0x63, 0x73, 0x9b, 0xe7, 0x7c, 0xbc, 0xbb, 0x36, 0xde, 0x66, + 0x08, 0xd2, 0x00, 0xa0, 0xbc, 0x38, 0x20, 0x4f, 0x70, 0xdc, 0xb0, 0x1b, 0x06, 0x2c, 0x07, 0xa8, 0x91, 0x88, 0x20, + 0x0a, 0x48, 0x98, 0xfa, 0xe7, 0xc7, 0xa2, 0xb8, 0xfc, 0xc6, 0x17, 0x9d, 0x28, 0x4c, 0x48, 0x1a, 0x9a, 0xc6, 0xb3, + 0x87, 0x7d, 0x5b, 0xe7, 0x82, 0x35, 0xc6, 0x7d, 0x7e, 0xd8, 0x63, 0xdf, 0xb5, 0x56, 0x0d, 0xd6, 0x63, 0x99, 0xdb, + 0x4e, 0x51, 0xcf, 0xe1, 0xd2, 0x8f, 0xef, 0xe7, 0xc1, 0xab, 0x20, 0x92, 0x4a, 0xb1, 0xef, 0x72, 0xe4, 0x71, 0x01, + 0x86, 0x9a, 0xef, 0x38, 0xfa, 0x2b, 0xf4, 0x57, 0x20, 0x0a, 0x7c, 0x50, 0x42, 0x93, 0x2e, 0xf7, 0x2d, 0xf7, 0x24, + 0x06, 0x41, 0xe6, 0x35, 0x04, 0x79, 0xd5, 0x24, 0x7f, 0x60, 0x0f, 0x78, 0xa7, 0x2e, 0x94, 0x0a, 0x9d, 0x7a, 0xb4, + 0xe0, 0xc4, 0x62, 0x44, 0x1f, 0x38, 0xb6, 0x5b, 0x72, 0x16, 0x16, 0x79, 0x3a, 0x6e, 0xf6, 0x26, 0x91, 0x9b, 0x7b, + 0x5a, 0x58, 0x8a, 0x58, 0xbe, 0xe2, 0x4b, 0x36, 0x65, 0x12, 0x31, 0xc4, 0x66, 0x93, 0x4f, 0xf0, 0x1c, 0x53, 0x7b, + 0xbe, 0x12, 0xbd, 0x65, 0x13, 0xe1, 0x22, 0x17, 0x51, 0xfd, 0x53, 0x24, 0x1e, 0x76, 0x10, 0xd3, 0xf2, 0xfc, 0xe7, + 0xf7, 0x34, 0xab, 0xe4, 0xa9, 0x55, 0x67, 0x81, 0x11, 0xf4, 0xbd, 0x5e, 0xa8, 0x1c, 0xb4, 0xb1, 0xf4, 0xa9, 0xc9, + 0x1d, 0xe3, 0xc7, 0x17, 0xd4, 0x13, 0x03, 0x78, 0xf7, 0x37, 0x28, 0xdd, 0x0f, 0x1d, 0xf5, 0xad, 0x86, 0xa3, 0x33, + 0x01, 0x9a, 0xca, 0x41, 0x73, 0xc7, 0x09, 0x12, 0xf6, 0x79, 0x82, 0x7e, 0x21, 0xba, 0x4c, 0xd8, 0xe1, 0x73, 0xd6, + 0x34, 0x7f, 0x88, 0x9d, 0xfa, 0xf3, 0xd6, 0xcd, 0x39, 0x83, 0x8a, 0x3a, 0x9b, 0xb4, 0x1e, 0x00, 0xbd, 0xfb, 0x96, + 0x3a, 0x48, 0xbe, 0x5f, 0x3b, 0x35, 0xbb, 0xdf, 0x8f, 0xd0, 0xe7, 0xab, 0x66, 0x96, 0x03, 0x33, 0x31, 0x57, 0x48, + 0x42, 0xa7, 0x0a, 0xa9, 0xbf, 0xb8, 0xca, 0xcb, 0xb4, 0x8c, 0x4f, 0x9b, 0x0d, 0x2a, 0x77, 0xb2, 0x43, 0x25, 0x88, + 0x83, 0x37, 0x50, 0xa5, 0xfe, 0xce, 0xdb, 0xbd, 0xd4, 0xd4, 0xe6, 0xc9, 0xed, 0x2b, 0xab, 0xd5, 0x99, 0xef, 0x34, + 0x75, 0xea, 0xed, 0x9b, 0xcc, 0x2c, 0x8f, 0xf7, 0x3c, 0x70, 0xb3, 0xee, 0x1b, 0x59, 0x93, 0x98, 0xfd, 0x16, 0xd8, + 0xb8, 0x41, 0xd0, 0x11, 0xc2, 0x92, 0x66, 0x1e, 0xfb, 0x8f, 0x20, 0xa6, 0xc3, 0x45, 0xce, 0x6f, 0x35, 0xcf, 0x0c, + 0x0b, 0xf5, 0xdb, 0x23, 0x6a, 0x1d, 0xb1, 0x27, 0x10, 0xde, 0x68, 0x95, 0x3b, 0x5e, 0xa2, 0x13, 0xe0, 0xb6, 0x77, + 0xe5, 0x49, 0xa6, 0xcb, 0xe7, 0xaf, 0x26, 0x42, 0x37, 0x02, 0x32, 0xa4, 0x40, 0x95, 0xc0, 0x3e, 0xd8, 0xfc, 0x00, + 0x01, 0x4d, 0xb9, 0xd5, 0xf3, 0xbe, 0xae, 0x97, 0xc7, 0x8f, 0xef, 0xcd, 0x7e, 0x0b, 0x47, 0x5c, 0x6d, 0x15, 0xec, + 0x61, 0x2f, 0xe7, 0xd0, 0xe8, 0xcd, 0x83, 0x9f, 0x99, 0x29, 0x66, 0x21, 0xf1, 0xee, 0xa2, 0xbe, 0x61, 0x63, 0x66, + 0xcb, 0x9e, 0xc9, 0xac, 0x93, 0x47, 0xc9, 0x7a, 0xaa, 0x3c, 0x9c, 0x9c, 0xca, 0x73, 0x62, 0xca, 0x85, 0x05, 0xde, + 0xd0, 0xd5, 0xd3, 0x4b, 0xae, 0x78, 0x6b, 0x09, 0x92, 0x97, 0xf8, 0xe2, 0xe4, 0x2e, 0x40, 0x8e, 0x89, 0xda, 0xd9, + 0xb5, 0x0b, 0x52, 0xc1, 0x5e, 0x2f, 0x4b, 0x68, 0x70, 0x54, 0x8e, 0xed, 0x14, 0x3c, 0x13, 0xce, 0xc7, 0x82, 0x71, + 0x01, 0xe9, 0x2b, 0x56, 0xe5, 0xb3, 0x30, 0x8f, 0x54, 0x28, 0x02, 0x1f, 0x3c, 0x8a, 0x73, 0xd3, 0x47, 0x1c, 0x20, + 0xe0, 0x54, 0xbc, 0xef, 0x01, 0xbd, 0xbf, 0x69, 0xc8, 0xc2, 0xe4, 0x60, 0xcd, 0xef, 0x49, 0x61, 0x21, 0xca, 0x7f, + 0xbe, 0xbf, 0xb3, 0x1a, 0xa1, 0xc8, 0xe5, 0x18, 0x83, 0x28, 0x76, 0x9f, 0x43, 0x60, 0x6e, 0xaa, 0x68, 0x10, 0xa8, + 0xe5, 0x1f, 0x6d, 0x7f, 0xaa, 0x1f, 0x93, 0x12, 0xcc, 0x32, 0xc8, 0x9b, 0x31, 0x44, 0xcc, 0x70, 0x56, 0x6f, 0x99, + 0xc5, 0x02, 0x82, 0x3d, 0xe6, 0x5a, 0xb3, 0xc3, 0x1c, 0x48, 0x76, 0x55, 0x60, 0xb4, 0x25, 0x8a, 0xd4, 0x0b, 0x88, + 0x5d, 0x1a, 0xba, 0xaf, 0x0b, 0x8a, 0x74, 0x57, 0x25, 0x62, 0x2a, 0xab, 0xe5, 0xe4, 0x59, 0x8d, 0xfd, 0xad, 0xa1, + 0xea, 0xa9, 0x2f, 0xb1, 0x45, 0x63, 0xbe, 0x7b, 0xff, 0xcb, 0xc6, 0x8f, 0x84, 0x6f, 0xe7, 0x8a, 0x19, 0x0e, 0x37, + 0x87, 0xd1, 0x2e, 0x61, 0x35, 0x50, 0x63, 0xd0, 0x1c, 0x37, 0x96, 0x1b, 0x8f, 0xf8, 0x17, 0x9d, 0x12, 0x2b, 0xfb, + 0x5a, 0x5f, 0xe0, 0x73, 0xf0, 0x5e, 0xa7, 0x79, 0x41, 0x72, 0xfd, 0x0b, 0xdb, 0x23, 0x77, 0x42, 0x7a, 0xde, 0x30, + 0xcf, 0x5f, 0x55, 0x84, 0x32, 0xdb, 0x8e, 0x22, 0xaf, 0x6c, 0x00, 0xe5, 0x90, 0x6e, 0x13, 0x50, 0x5f, 0xba, 0x89, + 0xf7, 0xa1, 0x6e, 0xac, 0xf0, 0x12, 0x32, 0x6b, 0xa0, 0xb8, 0xa5, 0xb1, 0xaf, 0xce, 0x46, 0x21, 0x4d, 0xce, 0x64, + 0x8f, 0x08, 0xc5, 0x84, 0x02, 0xfd, 0xd3, 0x71, 0xd1, 0x54, 0x05, 0xad, 0xf7, 0x44, 0x54, 0xc7, 0xf2, 0xe5, 0x1a, + 0x2a, 0x99, 0xd9, 0x68, 0xa5, 0x3f, 0xc9, 0x71, 0xe3, 0x10, 0xf9, 0x2e, 0x33, 0x1d, 0x5d, 0xf8, 0x73, 0xca, 0x9d, + 0x34, 0xe0, 0xe2, 0x26, 0x3b, 0x92, 0xc0, 0xff, 0x2e, 0x73, 0x22, 0x14, 0xbe, 0x99, 0xad, 0x08, 0xe4, 0x6b, 0xd3, + 0xf2, 0xbf, 0x3a, 0xea, 0x73, 0xc2, 0x1d, 0xed, 0xca, 0xd5, 0x4c, 0xc2, 0xd9, 0x70, 0x20, 0xf3, 0xf1, 0xc6, 0x04, + 0xaf, 0x3c, 0x55, 0x65, 0xbf, 0x05, 0x5d, 0xf6, 0xc0, 0x9e, 0x4d, 0x8e, 0xa3, 0xd2, 0x51, 0xe7, 0xb5, 0x1d, 0xc5, + 0x0e, 0x43, 0xc3, 0xa2, 0x0d, 0x48, 0x10, 0xb5, 0x3a, 0xe2, 0x4f, 0x8b, 0x18, 0x09, 0x5a, 0x5f, 0x2c, 0x1e, 0xd6, + 0x03, 0x1f, 0x2d, 0x31, 0x89, 0x7d, 0xd6, 0x66, 0x49, 0xf6, 0xbd, 0x09, 0x4e, 0xca, 0x40, 0x79, 0xa1, 0x73, 0xa0, + 0x4c, 0x4c, 0xf9, 0x98, 0xe4, 0xa5, 0x16, 0x2b, 0xdc, 0x25, 0xaf, 0xa3, 0x3c, 0xa4, 0x48, 0xfe, 0x29, 0x06, 0x81, + 0xd1, 0xd1, 0x7b, 0x14, 0x79, 0xdc, 0x4c, 0x70, 0xcb, 0x7d, 0x7f, 0xc8, 0x88, 0x66, 0xa4, 0x99, 0xb5, 0x18, 0xbd, + 0x14, 0x23, 0xf3, 0x31, 0x1c, 0x8f, 0xdf, 0x33, 0x47, 0x2f, 0x28, 0x7d, 0x7e, 0x15, 0x30, 0xb9, 0x09, 0x74, 0x59, + 0x37, 0x35, 0x8e, 0xc9, 0xb3, 0x74, 0x22, 0xdf, 0xf3, 0x14, 0xa7, 0x82, 0xb6, 0x04, 0x19, 0x33, 0xbd, 0xd0, 0xcc, + 0x51, 0x60, 0x78, 0xb3, 0xd5, 0x38, 0x02, 0x06, 0xec, 0x3a, 0xea, 0xe1, 0xd7, 0x50, 0x3a, 0x64, 0x4a, 0x3f, 0xd2, + 0xc2, 0x39, 0x5f, 0xc9, 0x54, 0x6f, 0x7e, 0x2f, 0x90, 0x03, 0x71, 0xf1, 0xb9, 0x48, 0x5d, 0x4d, 0xf6, 0x97, 0x41, + 0x01, 0x32, 0xa6, 0x4a, 0xc8, 0xf4, 0x7f, 0x39, 0x4f, 0xf0, 0x76, 0x0c, 0xd6, 0x0c, 0x0e, 0x0c, 0x22, 0x3e, 0xee, + 0x62, 0x88, 0xbf, 0x0e, 0xff, 0x6d, 0x0a, 0xea, 0xca, 0x9d, 0x0f, 0x94, 0x35, 0xdf, 0x23, 0xa5, 0xc8, 0xec, 0xe5, + 0xbb, 0x57, 0xad, 0x50, 0x07, 0x35, 0xb6, 0xc5, 0x6a, 0xe6, 0xb5, 0xc5, 0xdf, 0xcf, 0xa0, 0xea, 0x4d, 0x7e, 0xb3, + 0xdb, 0x55, 0xd7, 0xa8, 0x8d, 0x1a, 0x8d, 0x9c, 0x60, 0xf4, 0x7a, 0x33, 0xec, 0xd6, 0x79, 0x7f, 0x55, 0x02, 0xaa, + 0x6c, 0xf6, 0xda, 0x77, 0x40, 0x28, 0xb4, 0xb5, 0x7e, 0x9e, 0x92, 0x55, 0xc6, 0xe5, 0x27, 0x09, 0x81, 0x46, 0x96, + 0xf0, 0xa3, 0x5c, 0x6f, 0x1e, 0xbd, 0xfa, 0xc1, 0x3f, 0xd2, 0x3c, 0x77, 0xeb, 0x43, 0xbd, 0xd2, 0x7e, 0x16, 0xb7, + 0xb9, 0xa4, 0x6e, 0x99, 0x2a, 0x82, 0x0e, 0x3d, 0xee, 0xe9, 0xca, 0x70, 0x1f, 0x10, 0x0f, 0xa7, 0xe8, 0x7f, 0x35, + 0x7f, 0x98, 0x78, 0xb8, 0x9b, 0x9b, 0x12, 0xca, 0x5c, 0xf3, 0xf7, 0xe2, 0x8b, 0x3d, 0x53, 0x44, 0xbb, 0x08, 0x3d, + 0xe7, 0xb4, 0x0e, 0x96, 0x4d, 0xd5, 0xb5, 0x86, 0x84, 0x3d, 0x82, 0x14, 0x53, 0x30, 0x6e, 0x76, 0x97, 0x84, 0x3d, + 0xe2, 0x9c, 0x41, 0x39, 0x14, 0x8a, 0x32, 0xbf, 0x15, 0x2e, 0x99, 0x96, 0xb4, 0xbd, 0xa2, 0xc0, 0x0f, 0x03, 0x3e, + 0x57, 0x64, 0x0e, 0xe4, 0xd9, 0xa3, 0x45, 0x36, 0x1f, 0xe5, 0xbe, 0x9d, 0x40, 0x2c, 0xaa, 0x76, 0x60, 0xcb, 0xcb, + 0x5e, 0x9c, 0x8d, 0x6c, 0xee, 0x49, 0x7c, 0xbd, 0xde, 0x0e, 0x0f, 0x89, 0xeb, 0xd0, 0xf6, 0x3a, 0xe5, 0xb0, 0x16, + 0xf5, 0xa4, 0xc6, 0xcd, 0xa1, 0xed, 0x86, 0xd8, 0x6c, 0xa7, 0x34, 0x7a, 0x1b, 0xc5, 0x36, 0x5b, 0x7b, 0xf1, 0xa4, + 0x73, 0x8c, 0x33, 0xe9, 0x72, 0x3f, 0xc6, 0xf9, 0x50, 0x6a, 0x88, 0xb6, 0x2d, 0xa5, 0x7b, 0xac, 0x1d, 0xb8, 0x81, + 0xad, 0xd9, 0xfb, 0x27, 0xa2, 0xb5, 0x89, 0x3a, 0x97, 0x34, 0x73, 0x55, 0x3f, 0x1d, 0x1e, 0xa7, 0x3e, 0x8e, 0x27, + 0x33, 0xaa, 0x45, 0xa9, 0x33, 0xd1, 0xe6, 0x00, 0xd8, 0x7b, 0x53, 0xaf, 0xbd, 0x2f, 0x98, 0xb3, 0x85, 0xbe, 0x5b, + 0xef, 0x16, 0x18, 0x8b, 0xf1, 0x19, 0x43, 0xdb, 0x9c, 0x5b, 0x06, 0x6e, 0x3e, 0xcb, 0x61, 0xca, 0x65, 0xeb, 0x69, + 0xd6, 0x88, 0x51, 0xf7, 0xff, 0xed, 0x99, 0x96, 0x32, 0xd2, 0xcb, 0x13, 0x32, 0xec, 0x14, 0x1e, 0x8e, 0xc9, 0x02, + 0x8a, 0x51, 0x5b, 0xe9, 0x79, 0xe5, 0x98, 0x44, 0xa5, 0xa3, 0x38, 0xd3, 0x7f, 0xf8, 0xca, 0xdf, 0xd5, 0x1d, 0xa4, + 0xe9, 0xc7, 0x96, 0x25, 0x7f, 0xc3, 0xd9, 0x10, 0x1d, 0x9e, 0x11, 0x99, 0xfc, 0x3f, 0x48, 0x4d, 0x8e, 0x14, 0xe2, + 0xd1, 0x01, 0x34, 0xd0, 0xa9, 0x93, 0x16, 0xb4, 0x1c, 0x9c, 0x80, 0xc8, 0x12, 0xcd, 0xe1, 0x05, 0xc0, 0x26, 0x6d, + 0x60, 0x42, 0x7e, 0xab, 0xf7, 0x5d, 0x5a, 0x03, 0xfc, 0x65, 0x1e, 0xf5, 0x28, 0x7e, 0x06, 0xb4, 0x68, 0x02, 0x51, + 0x91, 0xa4, 0x61, 0xad, 0x6d, 0xf7, 0xf6, 0x62, 0xbb, 0x8f, 0x0b, 0xc3, 0xfd, 0x01, 0x0f, 0x91, 0x10, 0x72, 0x3b, + 0x4a, 0x51, 0xd3, 0x39, 0x5f, 0xb5, 0x7a, 0x73, 0x08, 0x75, 0x36, 0x43, 0x2e, 0x32, 0x50, 0x4c, 0x8b, 0xf5, 0xbe, + 0xf2, 0x01, 0xff, 0xf8, 0xcb, 0x42, 0x57, 0x29, 0x9a, 0x35, 0x65, 0x8c, 0x01, 0xb8, 0x8d, 0x39, 0xdf, 0xef, 0xf4, + 0x34, 0xa6, 0x58, 0x38, 0x80, 0x87, 0xc3, 0x44, 0x31, 0x59, 0x78, 0x7f, 0xc1, 0x82, 0x20, 0x5e, 0xdf, 0x51, 0x80, + 0x48, 0x0f, 0xea, 0x93, 0x01, 0x21, 0x0b, 0x11, 0x04, 0x6b, 0x18, 0x25, 0xdd, 0xea, 0x9a, 0x75, 0x99, 0xf1, 0x27, + 0x3f, 0xa4, 0xae, 0x06, 0xfa, 0x27, 0x8d, 0x92, 0xce, 0x75, 0x15, 0x4a, 0x1f, 0xc4, 0xcb, 0x1c, 0x29, 0xcf, 0xfb, + 0x34, 0x79, 0x7a, 0xfe, 0xf4, 0xef, 0x9b, 0x99, 0x43, 0x79, 0x4c, 0x4e, 0xfe, 0xbe, 0x09, 0x77, 0x6f, 0x45, 0x5e, + 0x31, 0x35, 0x5f, 0xcd, 0x26, 0x2b, 0x0f, 0x19, 0xe7, 0x94, 0x48, 0x05, 0xa7, 0x56, 0x74, 0x7e, 0x21, 0x17, 0xdb, + 0xc6, 0xef, 0x04, 0x32, 0x67, 0x8f, 0xf4, 0x9e, 0x1d, 0xd4, 0x84, 0x96, 0x50, 0x60, 0xb3, 0x88, 0x1a, 0xf8, 0x4e, + 0x61, 0x33, 0x66, 0xf6, 0x9c, 0x14, 0x68, 0xb1, 0x0a, 0x6c, 0xc5, 0xf3, 0x98, 0x0c, 0xf1, 0xaa, 0x64, 0xae, 0x38, + 0xe1, 0xcf, 0x96, 0x99, 0x62, 0x3f, 0xa4, 0x52, 0x07, 0x77, 0x5e, 0xac, 0xdc, 0xb3, 0x5c, 0xbe, 0x7a, 0xfb, 0x49, + 0x39, 0xc4, 0xde, 0x23, 0x62, 0xc6, 0xeb, 0x67, 0x4b, 0x0e, 0xa9, 0x00, 0x25, 0x32, 0xb2, 0x61, 0xdc, 0x45, 0x42, + 0x8d, 0xb2, 0x71, 0x74, 0x05, 0x2a, 0x8e, 0x75, 0x2a, 0x02, 0x00, 0xfe, 0xd8, 0x3d, 0x15, 0x2e, 0xf0, 0xe0, 0x7a, + 0x21, 0x01, 0x65, 0xe4, 0xe9, 0x3b, 0x93, 0x29, 0x21, 0x3a, 0x6a, 0x79, 0xfc, 0x9d, 0x30, 0x56, 0xcf, 0x3d, 0x7b, + 0x7e, 0x94, 0x6a, 0x69, 0x23, 0x0c, 0x24, 0x96, 0x65, 0xd3, 0x59, 0xd8, 0xba, 0xad, 0xf0, 0xa8, 0x58, 0x81, 0x34, + 0x05, 0x68, 0xdd, 0xa4, 0x8d, 0x80, 0xb3, 0x30, 0x66, 0x5f, 0x06, 0x68, 0xa9, 0x82, 0xb1, 0xfa, 0x64, 0x66, 0xc3, + 0xd5, 0x86, 0xa0, 0xee, 0x47, 0x8f, 0xb9, 0x40, 0xc8, 0x8b, 0x05, 0x76, 0x2c, 0x53, 0x27, 0x7e, 0x2b, 0xfa, 0xb0, + 0x7c, 0xac, 0xa5, 0x45, 0x9b, 0x1d, 0x7d, 0x52, 0x56, 0x98, 0xdf, 0xe9, 0xbd, 0xd3, 0x82, 0xd5, 0x4a, 0x51, 0x7a, + 0x16, 0x82, 0xcb, 0x6e, 0x4f, 0x9a, 0x3a, 0xac, 0x7e, 0x30, 0x28, 0x90, 0x1c, 0x0c, 0xd9, 0x76, 0xe8, 0xa9, 0x83, + 0x21, 0x28, 0x79, 0xcd, 0x14, 0x60, 0xcd, 0xd4, 0x0f, 0x29, 0x8d, 0x26, 0xff, 0xaa, 0x4f, 0xfa, 0x4f, 0xfe, 0x67, + 0x58, 0xef, 0x5a, 0x01, 0xc9, 0xf6, 0x70, 0x3e, 0x3b, 0x4b, 0x0b, 0x66, 0xd0, 0x28, 0x08, 0xed, 0xc1, 0x94, 0x9a, + 0x93, 0x48, 0x0c, 0x4a, 0x21, 0x44, 0xf6, 0x27, 0xd3, 0x5b, 0x8e, 0x79, 0x1e, 0x8a, 0x0f, 0x1f, 0xb7, 0x34, 0xe9, + 0xb4, 0x39, 0x9e, 0x23, 0xb8, 0x2b, 0x70, 0x82, 0x12, 0xcc, 0x0a, 0xfa, 0x27, 0xbf, 0x7f, 0x08, 0x45, 0x2c, 0xd7, + 0x85, 0x02, 0x65, 0xcc, 0x9e, 0x11, 0xb9, 0x5d, 0x78, 0x44, 0xab, 0x90, 0xc5, 0xb8, 0x40, 0x0e, 0x0b, 0xfb, 0xeb, + 0x31, 0x0b, 0x46, 0x0d, 0xfb, 0xa9, 0x6e, 0x44, 0xfb, 0x10, 0x98, 0x11, 0x5b, 0xd4, 0xe6, 0xa6, 0x18, 0x84, 0x33, + 0xc4, 0x4d, 0x56, 0x5a, 0x17, 0xb4, 0xf4, 0x94, 0xbc, 0xa4, 0x40, 0x37, 0xf1, 0xa8, 0xc3, 0xa9, 0x53, 0xab, 0xe5, + 0xad, 0x0a, 0x5a, 0x0b, 0xce, 0x79, 0xbd, 0x7f, 0x86, 0xff, 0xcc, 0x61, 0x85, 0x38, 0xa9, 0x1e, 0x78, 0x66, 0xf3, + 0xe4, 0xe6, 0x9d, 0x8b, 0x87, 0x67, 0x13, 0xae, 0x79, 0x6a, 0xf5, 0xd5, 0x84, 0xff, 0x3d, 0x7d, 0xf2, 0x74, 0x92, + 0x9a, 0xa0, 0xfc, 0x30, 0xb8, 0xf2, 0xc4, 0x6b, 0x78, 0xef, 0xde, 0xe9, 0xf0, 0xeb, 0xa5, 0xcf, 0xd1, 0x7d, 0x26, + 0xdc, 0xb6, 0x70, 0x7b, 0x35, 0xb0, 0x5a, 0xd4, 0xa8, 0x4a, 0x30, 0x3e, 0xe3, 0xd6, 0xe3, 0xcf, 0x93, 0xc6, 0x04, + 0x39, 0x3a, 0xe7, 0x20, 0x19, 0x10, 0x12, 0xcc, 0x02, 0x9b, 0xf4, 0x78, 0x87, 0x65, 0x5a, 0x20, 0x25, 0x08, 0x21, + 0x69, 0xe8, 0x7e, 0x0c, 0x5d, 0x25, 0xb6, 0xb2, 0x88, 0x8c, 0x6a, 0xd8, 0xa1, 0x11, 0xa7, 0xda, 0x7d, 0x98, 0x62, + 0x1d, 0xf0, 0xa9, 0x66, 0x90, 0x20, 0x4a, 0x22, 0xef, 0xb9, 0xb8, 0xb8, 0x75, 0xd0, 0x8a, 0x4c, 0xb5, 0x9e, 0x7e, + 0x17, 0x0d, 0xb7, 0xfe, 0x7a, 0x7b, 0x9f, 0x59, 0x97, 0xd0, 0xd0, 0x8b, 0xb9, 0xf2, 0xeb, 0x22, 0x8e, 0x5a, 0x3c, + 0x25, 0x6a, 0xf1, 0x54, 0xc4, 0xbc, 0x18, 0x35, 0xf2, 0xca, 0x62, 0x84, 0x74, 0x2b, 0xd0, 0xad, 0x46, 0x4f, 0x92, + 0x65, 0x89, 0x70, 0xdf, 0x8a, 0x41, 0xfb, 0xac, 0x84, 0xf6, 0xd7, 0xea, 0x49, 0x25, 0xb8, 0x0e, 0x34, 0x7a, 0x0e, + 0x85, 0x97, 0x41, 0x9b, 0xb1, 0x53, 0x03, 0x77, 0x45, 0x16, 0xdf, 0xfa, 0x43, 0x4b, 0xd2, 0x86, 0x75, 0x1a, 0x50, + 0xcf, 0x34, 0xf8, 0x61, 0x2a, 0xc4, 0x63, 0xf9, 0x27, 0x9e, 0xbe, 0x78, 0x25, 0xd1, 0x74, 0xfd, 0x40, 0xf1, 0xa2, + 0xb8, 0x50, 0x41, 0xd5, 0xec, 0x01, 0x05, 0x9b, 0xee, 0xe4, 0x6a, 0x99, 0xeb, 0x84, 0x39, 0xe4, 0xe9, 0xae, 0xfa, + 0x83, 0x16, 0x9c, 0xbe, 0x7a, 0xcf, 0x37, 0xa0, 0xd3, 0xc2, 0x0a, 0x50, 0xe2, 0xfc, 0x16, 0xb5, 0x19, 0x07, 0x67, + 0x36, 0x4b, 0xf8, 0x5f, 0xaf, 0x36, 0x40, 0xed, 0xc1, 0xc3, 0x41, 0x4d, 0xa0, 0x82, 0xfc, 0xc2, 0x39, 0x4d, 0x69, + 0x18, 0x40, 0xc1, 0x45, 0xc6, 0xa1, 0x58, 0xce, 0x16, 0x8f, 0xbc, 0xd2, 0x8a, 0x5c, 0xc0, 0xba, 0xd3, 0xa7, 0xd1, + 0x41, 0x30, 0x9e, 0x62, 0xa5, 0x21, 0x6a, 0x31, 0xd4, 0x45, 0x5a, 0x69, 0xa4, 0xf8, 0x2b, 0x4b, 0xd4, 0xb5, 0xcc, + 0xa6, 0x73, 0x8f, 0xab, 0x86, 0xea, 0x4c, 0xbb, 0x20, 0xc0, 0x8a, 0x12, 0xb9, 0xb5, 0x8b, 0xae, 0xf6, 0xdb, 0xdf, + 0x76, 0x18, 0x7a, 0x73, 0xcd, 0x5f, 0xe6, 0xf9, 0xe1, 0xe3, 0x91, 0x96, 0x1e, 0x07, 0xe2, 0xa2, 0xe9, 0x44, 0x3f, + 0xc6, 0x16, 0xe1, 0xd9, 0xb8, 0x8b, 0xc8, 0x30, 0x1a, 0x94, 0xf4, 0x6d, 0x4d, 0x3c, 0x66, 0xd2, 0x0e, 0xa0, 0x9f, + 0x0b, 0xda, 0x1c, 0x00, 0x96, 0x22, 0x94, 0x5d, 0x04, 0x57, 0xa1, 0x7a, 0x6f, 0xd4, 0x95, 0x36, 0x36, 0xfb, 0x07, + 0x6a, 0x44, 0x60, 0x56, 0x3c, 0xc6, 0x28, 0x85, 0x9e, 0x40, 0x5e, 0xec, 0x52, 0xb5, 0x56, 0xa7, 0x65, 0x0b, 0xf1, + 0x5c, 0x7a, 0x36, 0x13, 0x40, 0xd4, 0xa4, 0x4f, 0x4c, 0x1a, 0xd8, 0x50, 0xa1, 0x4c, 0xf9, 0x90, 0xd4, 0x4a, 0xc0, + 0x35, 0x1f, 0x76, 0xd1, 0x3c, 0x01, 0x67, 0x07, 0xb6, 0x05, 0x71, 0x58, 0x35, 0x43, 0xae, 0xab, 0x73, 0xda, 0x2e, + 0xfa, 0xb4, 0xd5, 0x1a, 0xdb, 0xaf, 0x9f, 0xce, 0x94, 0xf7, 0xf3, 0x31, 0x61, 0x46, 0x40, 0x6a, 0x67, 0xfa, 0xef, + 0x89, 0x72, 0xfa, 0xfd, 0xc4, 0x55, 0xfa, 0xbf, 0x79, 0x2e, 0xc6, 0x92, 0x79, 0x7e, 0x18, 0xb9, 0xc3, 0x98, 0x0a, + 0x61, 0x8c, 0x93, 0xf0, 0x62, 0x3b, 0xbc, 0x68, 0x0c, 0xea, 0x6c, 0x72, 0x30, 0xe4, 0x4c, 0xc7, 0xde, 0x7b, 0x10, + 0x34, 0xfb, 0xa2, 0xb7, 0x68, 0xac, 0x51, 0xd2, 0xa2, 0x98, 0xf7, 0x01, 0x64, 0x58, 0x65, 0xfb, 0xff, 0x71, 0xf3, + 0x26, 0x89, 0xd5, 0x0a, 0xf2, 0x12, 0x97, 0x34, 0x0e, 0x2b, 0x1f, 0x25, 0x61, 0x7d, 0xda, 0xa8, 0x2c, 0xd1, 0x5c, + 0x3b, 0xfb, 0x6f, 0xb0, 0x6c, 0xd9, 0xb0, 0x4b, 0x79, 0xb8, 0x77, 0x60, 0x4c, 0xe3, 0x9b, 0x1b, 0x6f, 0xd2, 0xc6, + 0x96, 0xd6, 0x6e, 0xc6, 0xdb, 0xa5, 0x09, 0x93, 0x1d, 0xc7, 0xac, 0xd8, 0x2e, 0x32, 0x43, 0x45, 0x53, 0xd5, 0x47, + 0x33, 0x38, 0xb9, 0xa1, 0x82, 0xf6, 0x6f, 0x39, 0x64, 0xb0, 0x78, 0x98, 0xcd, 0x85, 0x68, 0x79, 0x5d, 0xf0, 0x1d, + 0x05, 0xe7, 0x64, 0x24, 0x25, 0x09, 0xb2, 0xa4, 0xfb, 0x8e, 0x83, 0x07, 0x4d, 0xa1, 0x6a, 0xc4, 0xed, 0x72, 0xb2, + 0x5f, 0x0b, 0xff, 0xba, 0x7c, 0xdc, 0x70, 0xda, 0xca, 0x39, 0x50, 0x88, 0x2f, 0x38, 0x6f, 0x4e, 0x88, 0x8c, 0xda, + 0x36, 0x5b, 0x2b, 0xc2, 0x91, 0x5f, 0x29, 0x12, 0xf5, 0x2f, 0x1a, 0x95, 0x42, 0xb5, 0x04, 0x60, 0x60, 0x47, 0x81, + 0xd5, 0x6f, 0xd1, 0xa5, 0x6a, 0x19, 0xa0, 0xf1, 0x2b, 0xf6, 0x36, 0x9e, 0xd5, 0x3c, 0x05, 0xfd, 0x82, 0xa8, 0x6d, + 0xad, 0x68, 0x82, 0xf3, 0xee, 0x85, 0x35, 0x3a, 0xf1, 0x7b, 0x1a, 0xfd, 0x3d, 0xc8, 0x0d, 0xe4, 0x93, 0x74, 0xbf, + 0x4b, 0x4d, 0x1f, 0xb0, 0x07, 0x63, 0x1c, 0x63, 0xb0, 0x6b, 0xe6, 0x99, 0xea, 0x4d, 0x55, 0x4b, 0x01, 0xef, 0xe9, + 0x6a, 0xd4, 0xdc, 0xe3, 0x77, 0x9d, 0x37, 0xab, 0xec, 0x30, 0xbe, 0xce, 0x17, 0x50, 0xb6, 0x68, 0xd7, 0xe5, 0x4e, + 0x72, 0x19, 0xeb, 0x52, 0x05, 0xa1, 0x04, 0x16, 0xa4, 0xa4, 0xe6, 0x10, 0x87, 0x5b, 0xb6, 0x6e, 0xae, 0xa3, 0x09, + 0xe9, 0xd6, 0x5f, 0x66, 0x3e, 0xb7, 0x83, 0xa3, 0x8a, 0x36, 0x44, 0x60, 0xb6, 0xd0, 0x86, 0x05, 0x1c, 0xae, 0xb4, + 0x79, 0x29, 0x82, 0x00, 0xbc, 0x1b, 0xf4, 0xb9, 0x66, 0xa0, 0x28, 0x18, 0x44, 0xde, 0x45, 0x0b, 0x16, 0x78, 0x0d, + 0x9e, 0x02, 0x7d, 0x12, 0x1b, 0xfe, 0x7b, 0xc2, 0xaa, 0xd8, 0x90, 0x2c, 0x61, 0x7d, 0xef, 0x91, 0x8a, 0xe4, 0x24, + 0x75, 0x91, 0x74, 0x7e, 0x0b, 0xcf, 0xd4, 0x71, 0xdd, 0x9a, 0xbf, 0x8c, 0x3e, 0xf2, 0x29, 0x5a, 0x2f, 0xa0, 0x1f, + 0xe3, 0x8a, 0x5d, 0xe6, 0x2f, 0x34, 0x9f, 0xf4, 0xcc, 0xbc, 0x46, 0xab, 0x33, 0xe0, 0x81, 0xa4, 0x13, 0x61, 0x29, + 0x5d, 0x32, 0xe7, 0x32, 0x00, 0xe4, 0x6b, 0x93, 0xdb, 0xde, 0x10, 0xe2, 0x4b, 0x76, 0x7d, 0x47, 0x10, 0x2a, 0x53, + 0xad, 0x7e, 0xf2, 0xdc, 0x23, 0x29, 0x84, 0xa5, 0x3a, 0xfd, 0x8c, 0xdb, 0xb4, 0xbd, 0x5d, 0x0d, 0xcf, 0x90, 0x49, + 0x62, 0x81, 0x29, 0xba, 0x06, 0xfd, 0x9d, 0x5d, 0x24, 0x55, 0x06, 0x21, 0x62, 0x06, 0x9f, 0x70, 0x36, 0x46, 0x5c, + 0x2a, 0x15, 0xfd, 0xd9, 0x1e, 0x48, 0x37, 0xbd, 0x4a, 0x55, 0x85, 0x2b, 0x21, 0x93, 0x89, 0x2d, 0xb5, 0x01, 0x8b, + 0x05, 0x78, 0xf0, 0xe8, 0x16, 0xb7, 0x65, 0xb9, 0x1b, 0x11, 0x9c, 0x16, 0x2d, 0x3d, 0xbd, 0x60, 0x99, 0xd0, 0x77, + 0xd2, 0xf5, 0xae, 0x29, 0xc2, 0x74, 0xbf, 0x49, 0xb7, 0x3f, 0x4a, 0xe9, 0xab, 0x4a, 0xe3, 0x0e, 0x5c, 0x63, 0x09, + 0x5c, 0x78, 0x8c, 0x48, 0x35, 0x24, 0xaa, 0x4f, 0x03, 0x90, 0x1e, 0x55, 0x4d, 0x72, 0x1c, 0xa4, 0x0e, 0x13, 0x57, + 0x46, 0x1a, 0x21, 0x2e, 0xc4, 0x6e, 0x74, 0xc8, 0x4e, 0x67, 0x21, 0x9f, 0x29, 0x37, 0x5b, 0x27, 0x89, 0x7c, 0xa8, + 0x7d, 0x28, 0x8a, 0x91, 0x0c, 0xd7, 0x30, 0xe6, 0xc7, 0x1c, 0xfd, 0x78, 0x77, 0x95, 0xaf, 0xcc, 0xd6, 0x71, 0x4e, + 0xc3, 0x7c, 0x1c, 0x2f, 0x2a, 0xff, 0x5c, 0xd6, 0x35, 0x5a, 0x78, 0x1e, 0x1b, 0x3f, 0x9c, 0x2a, 0xea, 0xd7, 0x33, + 0x0e, 0x75, 0x51, 0x1b, 0xba, 0xbc, 0x92, 0x72, 0xbd, 0x3b, 0x8b, 0xc5, 0x12, 0x36, 0xc7, 0xa4, 0x5c, 0xf2, 0x54, + 0x55, 0x4b, 0x47, 0x9f, 0x3d, 0x96, 0x6b, 0xc9, 0x3b, 0x01, 0x30, 0x15, 0xe9, 0x23, 0x2c, 0x68, 0x2f, 0x23, 0x46, + 0x88, 0xbd, 0xa4, 0x29, 0xa7, 0xec, 0xdd, 0xde, 0xba, 0xf5, 0x28, 0x64, 0x4b, 0x92, 0xdd, 0xbd, 0x19, 0xe1, 0x0b, + 0xf3, 0xe5, 0x81, 0xd3, 0x3a, 0x5c, 0x93, 0x17, 0xf7, 0x21, 0x8a, 0xbd, 0x44, 0x3a, 0x8c, 0xda, 0x52, 0xce, 0x4d, + 0x58, 0x52, 0x9a, 0x52, 0x6e, 0x1d, 0x52, 0x35, 0x6c, 0x29, 0xc6, 0x1c, 0xc9, 0x78, 0x64, 0x9e, 0x91, 0x7e, 0x46, + 0x78, 0xe3, 0x5b, 0xc7, 0x93, 0xac, 0xbb, 0x77, 0xde, 0x32, 0x2f, 0xf2, 0x2a, 0x39, 0x3f, 0x6f, 0x25, 0x36, 0x14, + 0x77, 0xf2, 0x08, 0x58, 0x4f, 0x1c, 0x64, 0x97, 0x26, 0x1f, 0x08, 0x52, 0x94, 0xac, 0x74, 0xfa, 0x9f, 0x73, 0xdd, + 0xfb, 0x37, 0x75, 0x68, 0xa2, 0xea, 0xd8, 0x76, 0x68, 0x49, 0xe6, 0xe1, 0xd7, 0x79, 0x97, 0xaa, 0x05, 0xa4, 0x80, + 0xc5, 0x22, 0x4a, 0xdd, 0x8c, 0x09, 0x93, 0xa4, 0xe3, 0x05, 0x62, 0x7e, 0xdc, 0x66, 0x26, 0xbe, 0xec, 0xfe, 0x2e, + 0x07, 0x34, 0x35, 0x84, 0xe4, 0x11, 0x14, 0xe7, 0x1f, 0xc2, 0x9b, 0x31, 0x15, 0xf1, 0x0d, 0x7d, 0xe5, 0x94, 0xda, + 0xa3, 0x17, 0x68, 0xd3, 0x93, 0x60, 0x71, 0xe1, 0x46, 0x50, 0x22, 0x13, 0x04, 0xc8, 0xee, 0x31, 0x80, 0xa5, 0x49, + 0xf6, 0xa2, 0xe9, 0x68, 0x40, 0x64, 0xb3, 0xb1, 0x25, 0xcc, 0xb1, 0xb9, 0x00, 0x5a, 0xb0, 0x33, 0xbf, 0x04, 0xca, + 0x46, 0x76, 0x78, 0x47, 0xff, 0x93, 0x37, 0xa4, 0x00, 0x63, 0x9a, 0xfa, 0xd0, 0x59, 0xaf, 0x02, 0xf7, 0xae, 0xcf, + 0xb0, 0x38, 0x20, 0x07, 0x6e, 0x18, 0x4a, 0x63, 0x67, 0xac, 0x2e, 0x69, 0x40, 0xcb, 0x45, 0x75, 0x41, 0x20, 0x24, + 0x86, 0x98, 0xd7, 0x0c, 0x85, 0x94, 0x24, 0x54, 0x4b, 0x37, 0x9d, 0xd8, 0x26, 0x28, 0xcc, 0x8e, 0xa7, 0x26, 0x0f, + 0x03, 0x8f, 0xcb, 0xb7, 0xcf, 0x4d, 0x07, 0x81, 0xc2, 0x15, 0x2f, 0x65, 0x34, 0x6c, 0xac, 0x9b, 0xf5, 0xd0, 0xaf, + 0x6f, 0x16, 0xd0, 0x6e, 0x57, 0x8e, 0x19, 0xb5, 0x4e, 0xf5, 0x4c, 0x70, 0x7a, 0x67, 0x80, 0x46, 0x44, 0x02, 0x34, + 0xf0, 0xa3, 0xfe, 0x86, 0x54, 0x2c, 0x11, 0xd6, 0x76, 0x5e, 0x99, 0xf5, 0x5d, 0xae, 0x2c, 0x74, 0x9e, 0x61, 0xe3, + 0x9c, 0x45, 0xab, 0x1a, 0xf1, 0x84, 0x04, 0x03, 0x91, 0x92, 0x9d, 0x73, 0x2e, 0x32, 0xbe, 0x4e, 0x69, 0xf0, 0x05, + 0xa7, 0xc7, 0x5f, 0xeb, 0x02, 0xe5, 0xf8, 0x97, 0x57, 0x6f, 0xf3, 0x06, 0xd8, 0xe2, 0x7a, 0xc4, 0x7c, 0x51, 0xe6, + 0xe5, 0x0f, 0xcf, 0x99, 0x39, 0xfd, 0x7b, 0xeb, 0x99, 0x80, 0x2a, 0x7f, 0xb6, 0x42, 0x02, 0xa9, 0x3c, 0xba, 0xf3, + 0x46, 0xb8, 0x4a, 0x3a, 0x8a, 0xc6, 0xac, 0x1d, 0xb7, 0x84, 0x1d, 0xac, 0x8a, 0x23, 0x08, 0x15, 0xff, 0x62, 0x06, + 0x90, 0x38, 0x0b, 0x5a, 0x3a, 0x1a, 0x54, 0xd1, 0x1e, 0xa8, 0x73, 0xc2, 0xc6, 0x7c, 0x22, 0x37, 0xe4, 0xcb, 0x9b, + 0x13, 0x1c, 0x64, 0x09, 0x49, 0xf0, 0xa8, 0xde, 0xbe, 0x45, 0xd9, 0x2e, 0x3b, 0x4c, 0xbd, 0xe9, 0xf1, 0x7b, 0x6e, + 0x01, 0x42, 0x9a, 0x3d, 0x44, 0x3e, 0x77, 0x23, 0x31, 0xbb, 0xf5, 0xcc, 0xb6, 0x23, 0x16, 0x63, 0x3b, 0x11, 0xb9, + 0x52, 0xc7, 0xb5, 0x79, 0x88, 0x8c, 0xb0, 0xc2, 0x58, 0x83, 0xcb, 0xaf, 0x15, 0x96, 0x56, 0x11, 0x34, 0xf6, 0x31, + 0x5d, 0x29, 0x87, 0x4d, 0xf6, 0x21, 0xfd, 0xa5, 0xac, 0xf5, 0xaf, 0xd0, 0xea, 0xec, 0x09, 0xfc, 0x8a, 0x91, 0xbd, + 0x87, 0x30, 0x58, 0x77, 0x2c, 0xbb, 0x16, 0x3c, 0x56, 0x41, 0xb9, 0x0f, 0x13, 0x09, 0xa1, 0x78, 0x78, 0x3f, 0xec, + 0xf2, 0x5d, 0x4b, 0x34, 0xc4, 0x21, 0x8b, 0x65, 0xa5, 0x36, 0x19, 0xc3, 0x95, 0x8c, 0x80, 0xcb, 0x73, 0x3d, 0x9e, + 0x5f, 0xed, 0xec, 0xae, 0x34, 0x92, 0xd0, 0x77, 0x03, 0xc7, 0xcb, 0xad, 0x63, 0xad, 0x2c, 0xda, 0x7a, 0x18, 0xe2, + 0x56, 0x17, 0x88, 0xcc, 0x08, 0x11, 0x73, 0xdb, 0x56, 0x0d, 0x89, 0xb3, 0x93, 0xae, 0xd0, 0xc7, 0x06, 0x86, 0x33, + 0xd8, 0x98, 0xaa, 0x3e, 0x77, 0x1f, 0x06, 0xb6, 0xff, 0x17, 0x54, 0x03, 0x3f, 0x5f, 0xae, 0x48, 0x08, 0x48, 0x58, + 0xe8, 0x59, 0x04, 0xb3, 0x1e, 0xae, 0xf2, 0xec, 0x25, 0x1a, 0x2e, 0x64, 0x68, 0x70, 0xfc, 0xf1, 0xdc, 0xf4, 0x82, + 0xe6, 0xf8, 0xfd, 0xec, 0xdc, 0x8c, 0xaf, 0x95, 0x34, 0xc9, 0x82, 0x53, 0x5e, 0x38, 0x5d, 0xbe, 0xe7, 0x8c, 0xe2, + 0x33, 0xed, 0xba, 0xef, 0x68, 0xf3, 0x99, 0x94, 0x75, 0x52, 0xe9, 0x26, 0x02, 0x95, 0x85, 0x4c, 0xde, 0xed, 0x05, + 0xe0, 0x6a, 0x23, 0xf4, 0x45, 0x73, 0x91, 0x99, 0x4a, 0x5f, 0x74, 0xb3, 0x3c, 0x44, 0xca, 0xf6, 0xf0, 0xe6, 0xb4, + 0x0c, 0x01, 0xaf, 0x4f, 0x6b, 0xf6, 0x6f, 0xec, 0x2c, 0x94, 0xae, 0xa3, 0xc6, 0xa8, 0x88, 0x9b, 0x0b, 0xa6, 0x2f, + 0x51, 0x31, 0x0d, 0x0e, 0xc2, 0x49, 0x03, 0x4e, 0x27, 0xb9, 0xd1, 0x05, 0xc9, 0x0b, 0x4c, 0x83, 0xd8, 0x13, 0x68, + 0x69, 0x03, 0x16, 0x15, 0x95, 0xfc, 0xe8, 0x32, 0x2e, 0x1e, 0xa1, 0x9d, 0x9e, 0x44, 0x45, 0xfc, 0xd1, 0x7b, 0xd2, + 0x0a, 0x7e, 0x9f, 0x83, 0xee, 0x7a, 0x4d, 0xa7, 0xf7, 0xa0, 0x18, 0xb4, 0x6d, 0xf3, 0xcf, 0x94, 0x41, 0x78, 0x38, + 0x2a, 0x74, 0xe0, 0x15, 0xe4, 0x08, 0x01, 0x5e, 0xed, 0x41, 0x9f, 0x07, 0x1e, 0xf3, 0x48, 0x71, 0x20, 0x6f, 0x1e, + 0xdb, 0x2b, 0xe9, 0x3c, 0xf1, 0x75, 0xee, 0xfb, 0xef, 0xba, 0x3a, 0x16, 0x0f, 0x6e, 0xc7, 0xde, 0xbd, 0x7f, 0x6c, + 0x2e, 0x8b, 0xcb, 0x93, 0x26, 0x1d, 0x5d, 0x97, 0xf1, 0xb6, 0x71, 0x92, 0xd3, 0xed, 0xe4, 0x5c, 0xe0, 0x1f, 0x5b, + 0x10, 0xbf, 0x2d, 0xe0, 0xb9, 0x95, 0xa4, 0xfa, 0x76, 0x76, 0xd1, 0x99, 0x9f, 0x1e, 0x2e, 0x95, 0xfb, 0xb8, 0xc9, + 0x76, 0x2c, 0xae, 0x76, 0xd0, 0x3a, 0x4f, 0xba, 0x9d, 0x5b, 0xc9, 0xad, 0xe3, 0x5a, 0xd3, 0x8f, 0xb9, 0xdd, 0xed, + 0x6e, 0x2d, 0xcc, 0xff, 0xc5, 0xd5, 0xd3, 0x71, 0x1c, 0x1b, 0xbf, 0x41, 0x41, 0x50, 0xb8, 0x83, 0x75, 0x7a, 0x99, + 0x3c, 0xe3, 0x0e, 0xd5, 0x28, 0x0d, 0x57, 0x73, 0x9a, 0x79, 0x64, 0x2e, 0x62, 0x9c, 0x6f, 0x88, 0x97, 0xd5, 0xa2, + 0xc3, 0x06, 0xfd, 0xf6, 0x99, 0x99, 0xff, 0xfc, 0x0a, 0x9c, 0xe8, 0xce, 0xee, 0x1b, 0xe8, 0xca, 0xe3, 0x9a, 0xa1, + 0x29, 0x47, 0x51, 0xb0, 0xe3, 0xba, 0x76, 0xda, 0x26, 0xe7, 0x5a, 0x38, 0xae, 0x5d, 0x0e, 0xbc, 0xda, 0xc5, 0x21, + 0x02, 0x94, 0x56, 0xc6, 0x3d, 0xa7, 0x4f, 0x3b, 0xf2, 0x67, 0xa6, 0x63, 0xd8, 0x75, 0x18, 0xc9, 0x48, 0x0c, 0x28, + 0xb1, 0xe8, 0x83, 0xba, 0x13, 0x08, 0x35, 0xb1, 0x67, 0x0d, 0x84, 0x12, 0x5c, 0xa2, 0xf5, 0x8d, 0x10, 0x80, 0x96, + 0x76, 0xe0, 0x65, 0x3d, 0x93, 0x93, 0x25, 0x6b, 0x18, 0x59, 0x9a, 0xff, 0x11, 0xd0, 0x90, 0x38, 0xdf, 0x22, 0x01, + 0x5c, 0xc4, 0xd6, 0xa3, 0xb4, 0x39, 0x7d, 0xa2, 0xf3, 0xec, 0xa3, 0x5c, 0x82, 0x34, 0x7f, 0x0e, 0x0c, 0x10, 0xb0, + 0xe1, 0x38, 0x11, 0x11, 0x4a, 0xe6, 0x17, 0x68, 0x2c, 0x36, 0x5f, 0x3f, 0x5e, 0x41, 0x6d, 0xff, 0xc6, 0x6b, 0x1b, + 0xe5, 0x7f, 0xb7, 0x54, 0x72, 0xfb, 0x6b, 0xbe, 0xfe, 0xba, 0xaf, 0xdf, 0xfe, 0x1a, 0x9a, 0x97, 0x7c, 0x37, 0x2d, + 0xf0, 0x4e, 0xd7, 0xbd, 0x82, 0x2d, 0x1e, 0x35, 0x3f, 0x5f, 0x63, 0x42, 0x9c, 0xe8, 0x41, 0xfa, 0xde, 0xf1, 0xa1, + 0xa2, 0xfb, 0xad, 0x67, 0x83, 0xcb, 0xc4, 0x7e, 0x8e, 0x5b, 0x54, 0x2f, 0x21, 0xf0, 0xd1, 0xd5, 0xb8, 0xca, 0xf4, + 0xbc, 0xd0, 0x9a, 0x67, 0xc2, 0x2e, 0x35, 0x54, 0xe2, 0x89, 0x2d, 0xe0, 0x03, 0xec, 0x75, 0xe5, 0x73, 0x71, 0xe1, + 0xd5, 0x6c, 0xc2, 0x93, 0x04, 0x03, 0x1d, 0xb8, 0x68, 0xfa, 0xea, 0x99, 0x87, 0xe2, 0x63, 0xf8, 0x73, 0xda, 0x54, + 0x13, 0xc8, 0x7a, 0x54, 0x63, 0x31, 0x62, 0x6d, 0x12, 0x46, 0xcb, 0x93, 0x61, 0x68, 0x4b, 0xc6, 0xd9, 0xac, 0xbc, + 0xa0, 0x0c, 0xd8, 0x07, 0x7c, 0xd6, 0x4b, 0xfa, 0x91, 0x4e, 0x91, 0x4f, 0xd5, 0xa7, 0xb4, 0xba, 0x79, 0x3c, 0xc1, + 0x7e, 0xe9, 0xa3, 0x86, 0x46, 0x9a, 0xc4, 0x55, 0xb8, 0x86, 0xbd, 0xc9, 0x9a, 0xa3, 0x17, 0xad, 0x45, 0x1c, 0x60, + 0x4e, 0x0b, 0xf6, 0x6f, 0x3e, 0x14, 0xcf, 0xf7, 0x93, 0x40, 0xdb, 0x45, 0xb3, 0x98, 0x51, 0x0a, 0x20, 0xca, 0xf4, + 0xe9, 0x0d, 0x38, 0x10, 0x9d, 0x33, 0x3d, 0x4b, 0xbe, 0x4d, 0x6c, 0x87, 0x73, 0x8b, 0x08, 0xb5, 0x70, 0x69, 0x8e, + 0x66, 0xb3, 0x85, 0x13, 0xb3, 0x77, 0x69, 0x2f, 0x92, 0x51, 0xa6, 0xa7, 0x3a, 0x8a, 0x49, 0xdf, 0x0f, 0x3e, 0x66, + 0x08, 0x0f, 0x12, 0x7a, 0x12, 0x16, 0xa9, 0x94, 0x9a, 0x82, 0x1d, 0xc8, 0x52, 0x68, 0x1c, 0x00, 0xd1, 0x3e, 0x8d, + 0xb8, 0x01, 0x07, 0x0f, 0xda, 0x18, 0x3a, 0x31, 0xdd, 0x93, 0x57, 0x92, 0x09, 0x82, 0xca, 0x9b, 0x25, 0x36, 0x6f, + 0xc9, 0x56, 0x54, 0xbe, 0xc1, 0xcd, 0xce, 0x9d, 0xa2, 0xec, 0x77, 0x3a, 0x2f, 0x98, 0xb2, 0xb2, 0xde, 0xa1, 0x6a, + 0x46, 0xbc, 0xae, 0xa0, 0x5c, 0xd3, 0x4d, 0x67, 0x83, 0x0e, 0x51, 0x37, 0x7e, 0xfb, 0xb7, 0x51, 0x6e, 0x1a, 0xdb, + 0x62, 0xbb, 0xe2, 0x19, 0xc1, 0x7a, 0x07, 0x67, 0x67, 0xe5, 0xb9, 0x1b, 0x91, 0x85, 0xc2, 0x3f, 0xc0, 0xc9, 0x9d, + 0x6a, 0x19, 0x1d, 0x23, 0x9a, 0xcb, 0xff, 0x5d, 0x46, 0x57, 0x95, 0xd3, 0x68, 0x0c, 0x09, 0x91, 0x0c, 0x6f, 0x02, + 0x10, 0xcf, 0xb3, 0x26, 0x63, 0x34, 0x8d, 0xd5, 0xb6, 0x73, 0x9a, 0x66, 0xdf, 0x9f, 0xe6, 0xfa, 0xfd, 0x99, 0xf0, + 0x04, 0xf9, 0x4d, 0xe7, 0x46, 0xbe, 0xfa, 0x44, 0xb1, 0xee, 0x81, 0x0e, 0xb0, 0xaa, 0x6f, 0xe4, 0x5c, 0xcd, 0x3c, + 0x88, 0x41, 0xdb, 0x0d, 0x2a, 0x1e, 0x00, 0xa0, 0x3f, 0xce, 0xcb, 0x9b, 0x7f, 0xa9, 0x9a, 0xbb, 0xe1, 0x04, 0x1b, + 0x2b, 0x97, 0xe1, 0x38, 0x5e, 0x0e, 0xfd, 0x89, 0x89, 0x9e, 0x13, 0xfa, 0x97, 0x8a, 0xa4, 0x2b, 0x74, 0x86, 0xc9, + 0xca, 0x2c, 0x0d, 0x29, 0xce, 0x50, 0x41, 0xff, 0xa5, 0xfa, 0xed, 0xba, 0xfb, 0x06, 0x52, 0xfc, 0x1b, 0xb7, 0xd5, + 0xf1, 0xdc, 0xa8, 0x32, 0x93, 0x5e, 0x9a, 0xe3, 0x96, 0x5c, 0xd5, 0xb4, 0x9a, 0xf9, 0xac, 0x5d, 0x32, 0xb5, 0x9b, + 0xc7, 0xbc, 0x32, 0xfe, 0x32, 0x4e, 0x24, 0x85, 0x6f, 0xce, 0x61, 0x80, 0x0a, 0x03, 0xed, 0x53, 0xfc, 0xf4, 0x22, + 0xd3, 0xd5, 0x9b, 0xb9, 0x51, 0xd4, 0x21, 0xd6, 0xe9, 0x07, 0x5a, 0x2f, 0xe8, 0x18, 0x76, 0xd6, 0x0a, 0xf2, 0xdc, + 0x27, 0x06, 0x2b, 0xf8, 0x89, 0x65, 0x28, 0x18, 0x35, 0x0d, 0xe8, 0xd7, 0x79, 0xb9, 0x19, 0x62, 0x13, 0x19, 0xa8, + 0x99, 0x52, 0x02, 0xb2, 0x1b, 0xd6, 0x1c, 0xeb, 0x62, 0xea, 0x41, 0x7a, 0xa2, 0x6d, 0xef, 0x3c, 0x08, 0xcd, 0xa0, + 0x2c, 0x5c, 0x3b, 0x22, 0x17, 0x85, 0xef, 0xe9, 0x7d, 0x68, 0xb8, 0xa0, 0x25, 0xb4, 0xcf, 0xdd, 0x84, 0x80, 0xe9, + 0x1e, 0x13, 0x18, 0x52, 0xba, 0x48, 0x0b, 0xe5, 0x66, 0xa1, 0x3c, 0x4f, 0x60, 0x05, 0xcb, 0xcc, 0xb3, 0xb9, 0x70, + 0xe4, 0xb0, 0xcf, 0xef, 0xdd, 0x16, 0xbb, 0xd5, 0x2a, 0xf7, 0x98, 0xb1, 0xa8, 0x78, 0x47, 0x0b, 0xfb, 0xaf, 0x46, + 0xc9, 0x91, 0x06, 0x5b, 0x35, 0xcb, 0xe4, 0x2b, 0x7c, 0xe6, 0x48, 0x6d, 0x6f, 0xe2, 0x7d, 0xc2, 0xa9, 0x40, 0xb8, + 0x23, 0x0a, 0x9d, 0xf1, 0x94, 0x59, 0x47, 0x1b, 0x70, 0xa6, 0x4e, 0x74, 0x3c, 0x3c, 0x29, 0x50, 0x0c, 0xbf, 0x35, + 0xa3, 0x01, 0xcf, 0x5d, 0x27, 0x22, 0x76, 0xaf, 0xc3, 0x10, 0x5f, 0x99, 0x65, 0xb2, 0xbf, 0xb4, 0x9a, 0xe9, 0x42, + 0x6c, 0x5b, 0x65, 0xee, 0x8a, 0x73, 0x76, 0x18, 0xce, 0x25, 0xe3, 0xb3, 0x83, 0xeb, 0xde, 0xc8, 0xbd, 0xc2, 0xe4, + 0x28, 0x76, 0x74, 0x29, 0xe0, 0x89, 0x33, 0xe4, 0xc3, 0xaa, 0xdc, 0xfd, 0xda, 0xaa, 0x57, 0xbf, 0xad, 0xec, 0x4b, + 0x14, 0xd1, 0xe7, 0x79, 0x48, 0x61, 0xc7, 0x13, 0x91, 0xad, 0x0d, 0x63, 0x5d, 0x20, 0x1e, 0x6a, 0x83, 0xf8, 0xe3, + 0x20, 0xb5, 0x1f, 0xe4, 0xbb, 0x0d, 0x2a, 0xcb, 0xef, 0x0e, 0xbf, 0x6d, 0x5f, 0x92, 0xb8, 0x97, 0x0c, 0x8a, 0xdb, + 0xea, 0xab, 0x48, 0x0c, 0xb8, 0x5f, 0xaa, 0x35, 0x5d, 0x14, 0xc7, 0x4f, 0x69, 0x98, 0xca, 0x14, 0xd5, 0x42, 0xd3, + 0xf6, 0x00, 0x5a, 0x30, 0x3a, 0x76, 0x67, 0x73, 0x67, 0xe6, 0x1f, 0x0f, 0x88, 0xc8, 0x09, 0xb5, 0xff, 0xa9, 0xbc, + 0x38, 0x9b, 0x11, 0x7d, 0xb0, 0x7f, 0xd7, 0x05, 0x6c, 0xfb, 0x7c, 0x98, 0xd4, 0x69, 0x88, 0x14, 0x7c, 0x58, 0xd3, + 0xc6, 0xff, 0xc5, 0x6b, 0x61, 0x34, 0x11, 0xbd, 0x53, 0x6f, 0x59, 0x29, 0x81, 0xed, 0x6a, 0x97, 0x52, 0xb9, 0x95, + 0xba, 0x89, 0x49, 0x59, 0xf0, 0x86, 0xba, 0x4b, 0xb2, 0x7e, 0x12, 0xb4, 0x21, 0xf7, 0x4e, 0x1b, 0x47, 0x1c, 0x62, + 0x24, 0xe5, 0xec, 0x62, 0xcc, 0xb5, 0x21, 0x74, 0xc0, 0xa3, 0xd4, 0xe4, 0xae, 0x7f, 0x26, 0x5d, 0xe9, 0x18, 0x09, + 0x0a, 0xf2, 0xbc, 0x03, 0xb6, 0x5e, 0xdd, 0xa9, 0xa6, 0xb8, 0x0e, 0x57, 0x5a, 0x97, 0xa0, 0x9e, 0x13, 0x83, 0xcf, + 0x36, 0x58, 0x54, 0xc0, 0x5b, 0xa0, 0x99, 0x8c, 0xbb, 0x86, 0x58, 0xce, 0xe6, 0xd3, 0x8f, 0x29, 0xc7, 0xfc, 0xca, + 0x8f, 0xcc, 0xe3, 0x05, 0xd1, 0x05, 0x70, 0xd1, 0x01, 0xcf, 0xe7, 0xe9, 0x4a, 0xf5, 0xf5, 0xf4, 0xaf, 0x2d, 0xf2, + 0x5f, 0xe4, 0x86, 0x6f, 0x76, 0x38, 0x0b, 0x4c, 0xaf, 0x2b, 0xec, 0x10, 0xe8, 0x0c, 0xea, 0x9c, 0xc2, 0xa4, 0x0f, + 0x01, 0x75, 0xe2, 0x1a, 0x37, 0x30, 0x59, 0x84, 0x30, 0x74, 0x49, 0x50, 0x39, 0x37, 0x1d, 0x68, 0xde, 0xfa, 0x9e, + 0x40, 0x06, 0x08, 0x0f, 0x55, 0x04, 0x2d, 0x32, 0x1e, 0xdc, 0x1b, 0x1c, 0x40, 0x58, 0xe7, 0x52, 0x4e, 0x35, 0x43, + 0xba, 0x0e, 0xd5, 0xc7, 0x2d, 0xd5, 0x2e, 0x26, 0xe4, 0x08, 0xfa, 0x52, 0xcf, 0xd0, 0xa6, 0xe9, 0x22, 0xbd, 0xc6, + 0x6e, 0xe9, 0x94, 0xaf, 0x1d, 0xc4, 0x36, 0xf4, 0x4c, 0xa2, 0xfb, 0x7c, 0x2d, 0x0f, 0x01, 0x32, 0xcd, 0x25, 0x20, + 0xd1, 0x9c, 0x82, 0xfa, 0xe1, 0x99, 0x94, 0xcb, 0x7f, 0x3f, 0x89, 0xd7, 0xb9, 0x7b, 0xff, 0xdb, 0x7f, 0x17, 0xab, + 0xea, 0xc3, 0xc2, 0xc6, 0x0f, 0xf4, 0xac, 0xc8, 0x67, 0xf5, 0xf6, 0x62, 0xd2, 0x2e, 0x69, 0xd9, 0x35, 0xec, 0x9f, + 0x19, 0x84, 0xb5, 0xd4, 0xd9, 0x44, 0xdd, 0xc8, 0x35, 0xbb, 0x0e, 0x31, 0x5e, 0xde, 0x43, 0x03, 0x2c, 0x49, 0xe3, + 0x65, 0xca, 0xea, 0x43, 0x7d, 0xea, 0x14, 0x81, 0x10, 0x75, 0xf4, 0x7a, 0x5c, 0x72, 0xe0, 0x22, 0xc7, 0x4d, 0xcf, + 0xc0, 0x3f, 0xfb, 0x47, 0xb1, 0x51, 0xe8, 0x60, 0xb5, 0x58, 0xbd, 0x3a, 0xb0, 0x71, 0xe3, 0x42, 0x0e, 0x2d, 0x6c, + 0x7d, 0xb5, 0x53, 0x26, 0x67, 0xea, 0xed, 0x3c, 0x44, 0xd1, 0x0d, 0x16, 0x4a, 0x5e, 0x19, 0x70, 0x9c, 0x7a, 0x69, + 0x78, 0x84, 0xc2, 0xac, 0xf0, 0x05, 0x9f, 0x94, 0xdb, 0x0d, 0x06, 0x53, 0x90, 0xb5, 0xdc, 0x59, 0x14, 0xe5, 0x5d, + 0xc8, 0xab, 0x88, 0xb1, 0x5c, 0x86, 0x58, 0x21, 0x94, 0x05, 0x6c, 0x47, 0xbf, 0x1f, 0x85, 0x24, 0xf7, 0xa4, 0xc4, + 0x9b, 0xce, 0x39, 0x52, 0xee, 0x12, 0xcc, 0xee, 0x2a, 0x8b, 0x93, 0x44, 0xea, 0x75, 0x1b, 0xc1, 0xa5, 0xc4, 0x0c, + 0x4d, 0x51, 0xe4, 0x26, 0x5d, 0x58, 0x70, 0x34, 0xac, 0xbd, 0x51, 0xdb, 0x48, 0x18, 0x24, 0x72, 0xcc, 0x08, 0xe9, + 0xcb, 0xbe, 0xa0, 0x7c, 0xb3, 0x4f, 0xa6, 0x8c, 0x41, 0x04, 0x34, 0x8a, 0x9e, 0x01, 0x84, 0x9e, 0xaf, 0xd1, 0x2e, + 0x89, 0xa6, 0x15, 0x8c, 0xb8, 0xaf, 0x80, 0x84, 0x8b, 0x26, 0xdd, 0xf0, 0xa7, 0x94, 0x69, 0xaa, 0x78, 0x9a, 0x00, + 0x45, 0xa3, 0xad, 0xf2, 0x6c, 0x6a, 0x3c, 0xf3, 0x34, 0xd8, 0x88, 0x7a, 0xd2, 0x64, 0x15, 0x0c, 0xd6, 0xb3, 0x91, + 0x84, 0x53, 0x6a, 0x32, 0x8a, 0x95, 0x81, 0x38, 0xfa, 0xe7, 0xb7, 0xe6, 0x35, 0xb5, 0xae, 0x16, 0x06, 0x99, 0xd1, + 0x83, 0x19, 0x1f, 0x4c, 0xd4, 0xb0, 0x39, 0x0f, 0xe3, 0x41, 0xa6, 0x4e, 0x75, 0xca, 0x28, 0x4a, 0x8c, 0xb3, 0x60, + 0x62, 0x0c, 0x39, 0xd2, 0x38, 0x60, 0xdd, 0x40, 0x9e, 0xd6, 0x9c, 0xcd, 0xa3, 0x66, 0xd2, 0xbd, 0xae, 0x8e, 0x3e, + 0xed, 0x2d, 0x5d, 0xff, 0xc5, 0xcc, 0x36, 0xec, 0xd8, 0xe4, 0x2f, 0xfd, 0xae, 0x99, 0x3e, 0xf4, 0x98, 0x37, 0xe3, + 0x60, 0x98, 0xd1, 0xf5, 0x67, 0x69, 0x71, 0xaf, 0x68, 0xd0, 0xe7, 0x4b, 0xad, 0x71, 0xb8, 0xfd, 0xfd, 0xc0, 0xda, + 0xdb, 0x5d, 0x63, 0x92, 0x34, 0x02, 0xca, 0x11, 0x12, 0x11, 0x1c, 0x5d, 0xf1, 0x1f, 0x67, 0x95, 0xff, 0xdd, 0x43, + 0x57, 0xe8, 0x41, 0xf8, 0x74, 0xdd, 0xf4, 0x69, 0x14, 0x30, 0x67, 0x2d, 0xdb, 0xd5, 0x67, 0x71, 0x35, 0xa4, 0xbf, + 0x26, 0x64, 0xdc, 0x38, 0x56, 0xff, 0xd8, 0xa6, 0xe4, 0x2f, 0x77, 0x93, 0xd8, 0x37, 0x4b, 0x7d, 0x67, 0x8b, 0x6a, + 0xfd, 0xe8, 0xd6, 0x9c, 0xb6, 0x64, 0xb4, 0x27, 0xe5, 0x5b, 0xdd, 0xe1, 0x69, 0x3b, 0xc4, 0x25, 0x9b, 0xf7, 0xe4, + 0x34, 0x65, 0x59, 0x6d, 0xcb, 0x71, 0x44, 0x7a, 0x90, 0x6f, 0x2c, 0x19, 0xa5, 0xa3, 0x8f, 0x59, 0x3f, 0xee, 0x4e, + 0x00, 0x99, 0xa7, 0x27, 0xd0, 0x54, 0xd7, 0xae, 0xec, 0xf8, 0x56, 0xdc, 0x77, 0xd6, 0xbd, 0x0f, 0xd9, 0xaf, 0x63, + 0x15, 0x01, 0x1d, 0xf7, 0xbe, 0x64, 0x76, 0xb9, 0xcd, 0x94, 0x0e, 0x9d, 0x62, 0x30, 0x9a, 0xaf, 0xb2, 0x24, 0x2a, + 0x88, 0x05, 0x6f, 0x89, 0x0f, 0xe2, 0x02, 0x00, 0x39, 0x47, 0x2d, 0x6a, 0xd9, 0x31, 0x96, 0x44, 0xf9, 0xae, 0x02, + 0x35, 0xe7, 0xd9, 0x59, 0x45, 0xa7, 0xee, 0x44, 0xaf, 0x4e, 0xb9, 0x4a, 0x73, 0x1a, 0xa1, 0xeb, 0xe1, 0x2b, 0x2f, + 0x51, 0xc9, 0x8a, 0xe6, 0x5d, 0x98, 0xbe, 0x62, 0xaf, 0xbd, 0x40, 0xc9, 0x3b, 0x52, 0x1a, 0x0a, 0x19, 0xd9, 0x1a, + 0x34, 0xb6, 0xce, 0x5d, 0x62, 0x49, 0x27, 0xcb, 0xa3, 0x84, 0xc2, 0x17, 0x73, 0x1f, 0xb7, 0xc6, 0x51, 0x4d, 0xcc, + 0x39, 0x82, 0x3d, 0xa9, 0xd2, 0xc9, 0x56, 0x39, 0x80, 0x5b, 0xd3, 0x2c, 0xc2, 0x06, 0x29, 0xb5, 0xcb, 0x71, 0x97, + 0x38, 0x93, 0xed, 0x14, 0x03, 0x83, 0xbe, 0xb6, 0xb1, 0x22, 0xd3, 0x79, 0x1c, 0x44, 0xf7, 0x13, 0x37, 0x1b, 0x24, + 0x18, 0x6c, 0xcf, 0x3a, 0xe5, 0x86, 0xe7, 0x8a, 0xa3, 0xec, 0x4a, 0x6c, 0x2d, 0xcf, 0xa6, 0xee, 0xf7, 0xe8, 0x8a, + 0xb9, 0xb2, 0xa6, 0x37, 0xa0, 0x89, 0x6a, 0xa0, 0x03, 0x3e, 0xe0, 0xc4, 0x91, 0x91, 0x33, 0x99, 0xc4, 0xaa, 0x8e, + 0x61, 0x6e, 0xd2, 0x19, 0x3c, 0xc1, 0x68, 0xde, 0x92, 0x79, 0xca, 0x29, 0x85, 0xdc, 0xfb, 0x9f, 0x1b, 0x8f, 0x50, + 0x35, 0xd7, 0x30, 0xbd, 0x15, 0xf0, 0x0e, 0x21, 0x9e, 0x7f, 0x88, 0x6e, 0xaf, 0xb4, 0xe0, 0xfd, 0x87, 0x89, 0x2c, + 0x39, 0x17, 0x2a, 0xac, 0xab, 0xdd, 0xe1, 0x3d, 0x64, 0x7a, 0x4a, 0x25, 0x18, 0x0a, 0x50, 0x61, 0xfa, 0x82, 0x7d, + 0x42, 0xe7, 0x98, 0xbb, 0xd6, 0x99, 0x10, 0x3b, 0x81, 0xdd, 0xd0, 0x09, 0x12, 0x69, 0xaa, 0x10, 0xfb, 0x82, 0xcf, + 0x95, 0x73, 0x51, 0x15, 0x7a, 0xc0, 0x2f, 0x7f, 0x13, 0x4b, 0xea, 0x37, 0x48, 0x9a, 0x6f, 0x38, 0x25, 0x84, 0x3e, + 0xf9, 0x17, 0xb1, 0xcf, 0x39, 0xdf, 0xc4, 0x4a, 0xb3, 0x9d, 0xd8, 0xfc, 0xcc, 0x3f, 0x1c, 0x39, 0x27, 0x93, 0x12, + 0x13, 0x96, 0x90, 0xb1, 0xa1, 0xf7, 0x49, 0xea, 0x26, 0x7f, 0xaa, 0xd3, 0x8f, 0xe6, 0x16, 0x7d, 0xf1, 0xdf, 0x59, + 0xe9, 0x2e, 0xa1, 0x50, 0x88, 0xa9, 0x33, 0x54, 0xc2, 0xb0, 0x87, 0xa7, 0xe1, 0xd3, 0x85, 0x39, 0x0e, 0x49, 0x22, + 0x5a, 0xe4, 0x70, 0x86, 0xf8, 0x0d, 0x80, 0x09, 0x34, 0x59, 0x89, 0x50, 0x51, 0x02, 0x7b, 0x04, 0x4f, 0xf2, 0xd9, + 0xd6, 0xf7, 0x5e, 0xe4, 0xe1, 0x44, 0x1a, 0xe5, 0x0a, 0x6e, 0x08, 0xa6, 0x7a, 0x6e, 0x23, 0x19, 0x31, 0x3c, 0x8a, + 0x56, 0xc9, 0xe7, 0x5a, 0x42, 0x59, 0xec, 0x3c, 0x08, 0xd6, 0x55, 0x76, 0x95, 0x9d, 0xc7, 0x02, 0xed, 0xe0, 0x40, + 0x13, 0x17, 0x48, 0xd2, 0x8d, 0xf1, 0x36, 0xc5, 0xac, 0xe8, 0xe1, 0xfb, 0x2a, 0xe6, 0x4d, 0x9d, 0x0b, 0x92, 0xd7, + 0xea, 0x3e, 0x65, 0xac, 0x61, 0x42, 0x9d, 0x1a, 0xd8, 0x48, 0x62, 0xd2, 0xb0, 0x84, 0xe3, 0x05, 0x9f, 0xc7, 0x60, + 0x69, 0x98, 0xe6, 0x39, 0x7b, 0xe8, 0xb5, 0x7e, 0x2b, 0xda, 0x93, 0x6f, 0x64, 0xe7, 0x4d, 0x8b, 0x72, 0x52, 0x8b, + 0x73, 0x5a, 0xac, 0x33, 0x44, 0xf4, 0x9a, 0xe5, 0x8a, 0xe7, 0xcc, 0xac, 0x03, 0x40, 0xe2, 0x49, 0x9f, 0x79, 0x7d, + 0xbc, 0x0f, 0x22, 0x51, 0xa9, 0xf4, 0x96, 0x45, 0xc8, 0xec, 0x93, 0xb2, 0x9a, 0xe1, 0xf0, 0xe4, 0xd2, 0x69, 0x09, + 0x15, 0xc3, 0xf5, 0x9b, 0xe7, 0x05, 0x54, 0x81, 0x99, 0xa1, 0x98, 0x63, 0x53, 0x39, 0x1b, 0x6f, 0xb0, 0xcc, 0x60, + 0x5c, 0x44, 0x42, 0x0d, 0xf6, 0x6e, 0x4a, 0x34, 0x0d, 0x3e, 0xcc, 0x19, 0x2b, 0x73, 0x1f, 0xf6, 0x7c, 0xe9, 0x29, + 0x66, 0x0f, 0x9f, 0xbb, 0xd7, 0xcc, 0x71, 0xfb, 0x3c, 0xa4, 0x5a, 0xee, 0x4e, 0x61, 0xcd, 0x9e, 0x51, 0x49, 0xcc, + 0x03, 0xd8, 0xe0, 0xd9, 0x95, 0x9d, 0xe9, 0x52, 0x0e, 0xd8, 0x9f, 0xe0, 0x0e, 0xe0, 0x58, 0xc1, 0x10, 0x05, 0xdc, + 0x46, 0x7e, 0xe3, 0xe6, 0xcc, 0x7c, 0xf3, 0x71, 0x60, 0xc3, 0x20, 0x32, 0x85, 0x32, 0x44, 0x4c, 0x95, 0x3e, 0xfc, + 0xbc, 0x87, 0xb3, 0xaf, 0x2e, 0x13, 0x4d, 0x65, 0x6f, 0x84, 0x62, 0x1a, 0x5e, 0xc3, 0x61, 0x1d, 0x98, 0xea, 0xe9, + 0x48, 0xa7, 0xe8, 0xd4, 0x4e, 0x62, 0x6a, 0xf5, 0xac, 0xd3, 0x51, 0xb9, 0xd9, 0x80, 0xc9, 0xa7, 0xd3, 0x2b, 0xb9, + 0x8f, 0x70, 0x68, 0x26, 0x13, 0xfe, 0xc4, 0x99, 0x79, 0x33, 0x65, 0x0f, 0xf1, 0x4b, 0x9f, 0x3e, 0x3c, 0xae, 0x07, + 0x8c, 0xf2, 0x9c, 0x01, 0xcf, 0xc7, 0xb8, 0x70, 0xc0, 0xfc, 0x6c, 0xca, 0x98, 0xfb, 0x22, 0x69, 0xa9, 0x4c, 0xcf, + 0x47, 0xeb, 0x7f, 0x56, 0xb3, 0xa4, 0x71, 0x82, 0xad, 0x51, 0x45, 0xfa, 0xa5, 0xc0, 0xdc, 0x03, 0x51, 0x0f, 0x43, + 0x9f, 0x48, 0xb1, 0x90, 0x28, 0x70, 0x74, 0x29, 0x75, 0xf8, 0xf7, 0x21, 0xe8, 0x31, 0x44, 0x4b, 0xbf, 0xb0, 0xcd, + 0xf9, 0x8e, 0xf9, 0x60, 0x6c, 0xd4, 0x76, 0xd6, 0x9d, 0x65, 0xa3, 0x95, 0x26, 0x8b, 0xfd, 0x36, 0x5b, 0xfc, 0xce, + 0x9f, 0xfb, 0x5a, 0x1e, 0x48, 0x46, 0xee, 0x64, 0x6e, 0xf6, 0xc2, 0x83, 0xcf, 0x6b, 0x5e, 0x49, 0x3f, 0x41, 0x57, + 0x82, 0x2b, 0xa1, 0xc3, 0x83, 0xd3, 0x18, 0x65, 0x0a, 0x5a, 0x41, 0x20, 0xf3, 0xc6, 0x66, 0xb7, 0x2f, 0x02, 0x69, + 0x0d, 0x21, 0x83, 0x5d, 0x5b, 0xbd, 0x44, 0x74, 0xde, 0x37, 0xc9, 0xe7, 0xec, 0x8d, 0x96, 0x75, 0x0f, 0x53, 0xea, + 0xff, 0x9c, 0xec, 0x86, 0x2c, 0xc3, 0x42, 0xa1, 0x8f, 0x9d, 0x1e, 0x47, 0x55, 0xa5, 0x51, 0x2a, 0x8e, 0x57, 0x94, + 0xeb, 0x04, 0x45, 0x85, 0x58, 0x5e, 0xff, 0x00, 0x80, 0x38, 0x4a, 0x19, 0xb4, 0xc7, 0x96, 0xaf, 0x50, 0x13, 0x04, + 0xc6, 0x45, 0x68, 0x58, 0x26, 0xa6, 0xb0, 0xcb, 0x2a, 0xd6, 0x71, 0x7a, 0x5c, 0xc4, 0x57, 0xa7, 0x10, 0xbd, 0xee, + 0x06, 0xaf, 0x12, 0x74, 0xae, 0xbd, 0xee, 0xeb, 0x39, 0xf4, 0x33, 0x9d, 0x7f, 0x04, 0x37, 0x39, 0x87, 0x25, 0x29, + 0x38, 0x25, 0xf5, 0x39, 0x8b, 0x1b, 0xbe, 0x0a, 0x0f, 0x9c, 0xb6, 0x78, 0xb4, 0x63, 0xd7, 0xac, 0xca, 0x8f, 0x5d, + 0x96, 0xd2, 0xc0, 0x90, 0x44, 0x75, 0x35, 0xe8, 0x18, 0xb4, 0x24, 0x72, 0xeb, 0x65, 0x7b, 0x8c, 0xbe, 0xc1, 0xe7, + 0xc7, 0x9b, 0xd3, 0x7d, 0x49, 0x68, 0x63, 0xb5, 0xcc, 0xca, 0xc5, 0x97, 0x28, 0x00, 0x4a, 0xf0, 0x00, 0x84, 0xf5, + 0x3b, 0x11, 0xfe, 0x5d, 0xf4, 0xe0, 0xc0, 0x23, 0x80, 0x22, 0xbc, 0x95, 0xe9, 0x2b, 0xaf, 0x09, 0xa5, 0x15, 0xe0, + 0xb8, 0x36, 0x55, 0x41, 0xde, 0x70, 0xfb, 0x47, 0x96, 0xec, 0x0b, 0x64, 0xec, 0x23, 0x21, 0xf1, 0x0e, 0xbb, 0x76, + 0xf7, 0x74, 0xc2, 0x09, 0xb0, 0x5b, 0x3a, 0xcd, 0xab, 0xb6, 0x94, 0x4d, 0x81, 0x2e, 0x06, 0x31, 0xce, 0xa0, 0xa6, + 0xd3, 0x07, 0xe9, 0xfb, 0x37, 0x66, 0xa1, 0x33, 0x04, 0xde, 0xe7, 0x02, 0xf6, 0x3a, 0x0c, 0xe5, 0xa3, 0xf6, 0x88, + 0x46, 0x90, 0xcb, 0xd5, 0xf1, 0x91, 0x81, 0x4a, 0x39, 0x6c, 0x98, 0x74, 0xfa, 0x99, 0x6a, 0xc9, 0x9e, 0xfa, 0xa6, + 0x9a, 0xa5, 0x9c, 0xc6, 0x27, 0x90, 0xea, 0xa8, 0x3c, 0xc1, 0x98, 0xab, 0x69, 0x9f, 0xde, 0xc7, 0x43, 0xad, 0x35, + 0xf0, 0x5e, 0xd7, 0x61, 0x60, 0xb8, 0xba, 0xcf, 0x9e, 0x7c, 0x8d, 0x71, 0xef, 0xf4, 0x6c, 0x8a, 0x9f, 0x0f, 0x3f, + 0x8e, 0xd0, 0x65, 0xf9, 0x71, 0x76, 0x53, 0x45, 0x78, 0x4f, 0x19, 0x35, 0xec, 0xf5, 0x33, 0x65, 0x9b, 0x45, 0xdf, + 0x27, 0x7d, 0x8a, 0x81, 0xaa, 0xbd, 0x3e, 0x5a, 0x4d, 0x28, 0x2e, 0x00, 0xe9, 0x3c, 0xa7, 0x59, 0xa9, 0x49, 0xa1, + 0xa6, 0xc9, 0x5e, 0x46, 0x56, 0x19, 0xf0, 0x0c, 0xfb, 0xd5, 0x23, 0xe8, 0x96, 0x55, 0x1a, 0xe5, 0x51, 0x87, 0xcd, + 0xfa, 0x93, 0xac, 0xfe, 0x3a, 0xc0, 0x06, 0x43, 0x8e, 0xd9, 0x93, 0x7a, 0xc9, 0x73, 0xae, 0x06, 0x9e, 0xe4, 0x8c, + 0x6b, 0x26, 0x6f, 0xed, 0x1a, 0x39, 0x1f, 0x6e, 0xc8, 0xbd, 0x7a, 0xe8, 0x9f, 0x7a, 0x59, 0x1b, 0x80, 0x2c, 0x86, + 0xd4, 0x7b, 0xce, 0xb4, 0xc9, 0x94, 0x90, 0x01, 0x99, 0x07, 0x3e, 0xaf, 0x3f, 0x07, 0x15, 0x56, 0xba, 0xf3, 0xda, + 0x5a, 0x8b, 0x42, 0xca, 0x13, 0x5e, 0x57, 0xc0, 0x12, 0x57, 0xb1, 0x32, 0x4c, 0x2b, 0x1b, 0xed, 0x9e, 0xf4, 0x97, + 0xc4, 0xfb, 0x32, 0x4b, 0x2c, 0x50, 0xba, 0xe6, 0x99, 0x8d, 0xd5, 0xbb, 0x99, 0xad, 0xf4, 0xec, 0xe4, 0xd7, 0xf1, + 0x45, 0xeb, 0xef, 0x53, 0xc7, 0xd6, 0x30, 0x86, 0xca, 0x79, 0xb3, 0xf3, 0x9b, 0x9c, 0xd2, 0x70, 0xcb, 0x68, 0x93, + 0x35, 0x33, 0x05, 0x5e, 0xae, 0xde, 0xae, 0xd1, 0x50, 0x85, 0xc8, 0xe2, 0xe0, 0x19, 0xa9, 0xfb, 0x5b, 0xdf, 0x1d, + 0x2f, 0x0d, 0x72, 0xdf, 0xc6, 0xf2, 0xa6, 0xd4, 0x12, 0xcd, 0x0b, 0xb9, 0x38, 0x6f, 0x61, 0xbe, 0xda, 0x37, 0x4d, + 0x3e, 0xd6, 0x26, 0x9c, 0x3a, 0x72, 0x9f, 0x14, 0x97, 0x91, 0xe6, 0xc2, 0x57, 0x21, 0x0e, 0xee, 0xe4, 0xc9, 0xbd, + 0xd7, 0x91, 0x52, 0x6f, 0x61, 0x8d, 0xb3, 0xf1, 0x71, 0xa7, 0x6e, 0x8d, 0x17, 0x1a, 0x34, 0xb2, 0x23, 0x83, 0x93, + 0xcf, 0x36, 0x7e, 0xd3, 0x0c, 0xe9, 0xb5, 0x87, 0x84, 0x60, 0xca, 0xfb, 0x46, 0xba, 0xc9, 0x5a, 0x09, 0xfe, 0xe9, + 0xaa, 0x46, 0xd4, 0x66, 0xe2, 0x5c, 0x9f, 0xf7, 0x43, 0x78, 0x3f, 0x74, 0x48, 0x9a, 0xcc, 0xa2, 0x44, 0x5f, 0x38, + 0x12, 0x73, 0xea, 0x29, 0x4e, 0x5a, 0x43, 0x6f, 0xe6, 0x10, 0xd6, 0x3b, 0x1f, 0xa6, 0xc5, 0xd1, 0xe1, 0x54, 0x91, + 0x66, 0x01, 0x07, 0x51, 0xa7, 0x70, 0x5d, 0x80, 0x03, 0x65, 0x5e, 0xbe, 0xa9, 0x07, 0x70, 0xf2, 0x03, 0x5c, 0x44, + 0x2f, 0xf3, 0x2d, 0x88, 0xe0, 0xdc, 0x54, 0x69, 0xa6, 0x85, 0xd9, 0x63, 0xc4, 0xde, 0xb6, 0xea, 0x9b, 0xe9, 0x67, + 0x26, 0x78, 0x29, 0x9c, 0x3c, 0x2f, 0x8f, 0xdb, 0x6c, 0x62, 0xf0, 0x21, 0x5a, 0xf5, 0x45, 0x91, 0xd3, 0xb8, 0xce, + 0x60, 0x9a, 0x9a, 0x9e, 0xf9, 0x20, 0xf2, 0x56, 0x00, 0x93, 0xd3, 0xb8, 0xaa, 0xe0, 0xdb, 0x7c, 0x31, 0xe7, 0xac, + 0x72, 0x12, 0x5f, 0x88, 0x31, 0x15, 0x49, 0xc5, 0x56, 0x45, 0x2a, 0x76, 0x24, 0x2e, 0x4a, 0x66, 0x37, 0x35, 0xc5, + 0x69, 0x6b, 0xde, 0x90, 0x94, 0xef, 0x59, 0x1b, 0x64, 0xa3, 0x42, 0x1c, 0x6f, 0x1f, 0xc2, 0xc5, 0xde, 0x92, 0xf4, + 0x29, 0x2c, 0x54, 0x75, 0x23, 0x09, 0x27, 0xa7, 0x06, 0x91, 0x5f, 0x2d, 0xf8, 0x63, 0x5f, 0xd2, 0x6a, 0x86, 0x6a, + 0x68, 0xec, 0x23, 0x5b, 0x9c, 0x8e, 0x32, 0xde, 0x24, 0xad, 0x85, 0x2f, 0xb1, 0x29, 0x16, 0x93, 0xab, 0xef, 0x74, + 0x1a, 0x5c, 0xb7, 0xf0, 0x7b, 0x38, 0x24, 0x4e, 0x2f, 0x8a, 0x94, 0x7e, 0x72, 0xf5, 0x9d, 0xb8, 0xf9, 0xa6, 0xdf, + 0x95, 0xc0, 0xdc, 0x1d, 0x5a, 0x69, 0xb1, 0x47, 0xda, 0x8a, 0x9f, 0xad, 0x4b, 0xc4, 0x57, 0xee, 0x1a, 0xcb, 0x6a, + 0xde, 0x7a, 0xfb, 0x04, 0xa5, 0x06, 0x41, 0xfe, 0x5b, 0xbb, 0xb1, 0x55, 0xc8, 0xa5, 0xd2, 0x41, 0xee, 0x21, 0xe9, + 0xa5, 0xc2, 0x6f, 0xa8, 0x73, 0x4f, 0x4f, 0x68, 0xa3, 0x12, 0xf1, 0x2e, 0x2e, 0x71, 0x86, 0x05, 0x3d, 0xce, 0xcd, + 0xc3, 0x7a, 0x0b, 0xbd, 0x62, 0xb7, 0x31, 0xe9, 0x69, 0x11, 0xcb, 0xf2, 0xb4, 0x53, 0xf7, 0x45, 0x09, 0x24, 0xfe, + 0x39, 0xdc, 0x81, 0xff, 0x4f, 0xd7, 0xa0, 0x35, 0x82, 0xca, 0xe5, 0xa6, 0x5e, 0x97, 0xf8, 0x90, 0xee, 0x78, 0xac, + 0xa2, 0xdb, 0x26, 0x1a, 0xdf, 0x70, 0x50, 0xc0, 0xd4, 0x5d, 0x65, 0xd4, 0xb1, 0x32, 0x67, 0x46, 0xb3, 0x59, 0x91, + 0xfd, 0x2b, 0x42, 0x5e, 0x15, 0x40, 0x08, 0xd2, 0x9a, 0x45, 0x13, 0x93, 0xfe, 0xdd, 0x74, 0x6e, 0x29, 0xc9, 0xac, + 0xc9, 0x9f, 0x36, 0xd5, 0xa9, 0x5b, 0xc0, 0xdd, 0xce, 0x21, 0xba, 0xd8, 0x6e, 0xad, 0x91, 0x67, 0xd0, 0xdb, 0x82, + 0xa3, 0x90, 0x35, 0x20, 0x9d, 0x38, 0x97, 0x4e, 0xfe, 0x16, 0x79, 0x0a, 0x70, 0x10, 0x05, 0x4d, 0x98, 0xca, 0x6e, + 0xb6, 0x85, 0x64, 0xe9, 0xea, 0x26, 0x82, 0x0f, 0x50, 0x09, 0x13, 0xa0, 0x8b, 0x3c, 0xaf, 0xbb, 0x77, 0xd2, 0x46, + 0x34, 0x46, 0x56, 0x67, 0xad, 0x0e, 0x92, 0x5c, 0x7f, 0xc8, 0xf2, 0xf7, 0x0f, 0x5c, 0x62, 0x7e, 0x41, 0x7c, 0x40, + 0x01, 0x1f, 0x3e, 0xf8, 0x16, 0xee, 0x38, 0xd4, 0xda, 0xed, 0xe6, 0x25, 0x0a, 0x9e, 0x84, 0xe6, 0xcf, 0x55, 0xef, + 0x74, 0x8f, 0x85, 0x48, 0xbc, 0xa7, 0x6e, 0x48, 0xd4, 0x4a, 0xb5, 0xe9, 0x7a, 0xba, 0x46, 0xf6, 0x5f, 0x0e, 0x7c, + 0x2e, 0x85, 0x4f, 0x1e, 0xd3, 0x8b, 0xdb, 0x88, 0x43, 0xa1, 0xd0, 0x57, 0xc6, 0x3f, 0x81, 0x51, 0x59, 0xfb, 0xc0, + 0x4d, 0x8a, 0x35, 0xe0, 0x32, 0x34, 0xc1, 0xad, 0xb8, 0x65, 0xac, 0x2a, 0x0f, 0x06, 0x3d, 0x85, 0x25, 0x48, 0x73, + 0xa1, 0xee, 0x99, 0x0c, 0x27, 0x41, 0x5a, 0x7d, 0x3e, 0x04, 0x41, 0xce, 0xf1, 0x4e, 0x6a, 0x55, 0xa4, 0xd2, 0xec, + 0xb1, 0x2a, 0x6b, 0x63, 0x9e, 0xc3, 0x30, 0x3a, 0x07, 0x14, 0xb5, 0xa9, 0xb0, 0xd5, 0xce, 0x10, 0x4a, 0x75, 0x1c, + 0x04, 0x36, 0x2d, 0x8d, 0x26, 0x69, 0x61, 0x07, 0x7a, 0x26, 0x61, 0xe6, 0x25, 0xcd, 0x4f, 0xa9, 0xc8, 0x57, 0xd3, + 0x46, 0x50, 0x4d, 0xdf, 0x0e, 0x24, 0x52, 0x1e, 0xab, 0xc9, 0xeb, 0x62, 0x66, 0xc7, 0x3a, 0xbc, 0x06, 0x0c, 0xe3, + 0x39, 0xc4, 0x12, 0x50, 0x24, 0x7c, 0xf5, 0xa9, 0x6e, 0x1c, 0x33, 0xe5, 0x3c, 0x17, 0xfe, 0xe4, 0xce, 0x64, 0x62, + 0xc1, 0x2b, 0x20, 0x94, 0xb8, 0xaa, 0xfb, 0x7a, 0x84, 0x6b, 0xd0, 0x4d, 0xd4, 0x0c, 0xf9, 0xe6, 0xf0, 0xd7, 0x48, + 0x11, 0x1a, 0x81, 0xac, 0x61, 0x86, 0x48, 0xa6, 0xf8, 0x08, 0x13, 0xe4, 0x1c, 0x30, 0xa6, 0xae, 0x36, 0xc0, 0xa5, + 0x3f, 0x4b, 0x64, 0x98, 0xb3, 0x69, 0x35, 0xd5, 0x07, 0xa3, 0xd0, 0x7d, 0x8f, 0x07, 0x0d, 0xea, 0xb1, 0x83, 0xd8, + 0x95, 0x82, 0xab, 0x7e, 0x8d, 0x8d, 0x06, 0x23, 0x90, 0xb3, 0x63, 0x2d, 0x36, 0x1b, 0x12, 0x04, 0x30, 0x9f, 0x28, + 0xdd, 0xfb, 0xe0, 0x79, 0x4f, 0xbf, 0x7c, 0x7a, 0x8d, 0x10, 0x32, 0x8c, 0x3b, 0x51, 0x6a, 0x66, 0x18, 0x6a, 0x94, + 0xf9, 0x04, 0xcd, 0xfc, 0x78, 0xe0, 0xe9, 0x0e, 0x6f, 0x17, 0xf4, 0x4a, 0xeb, 0xb7, 0xee, 0x91, 0xbc, 0x5c, 0x11, + 0x45, 0x68, 0x8d, 0xb6, 0x53, 0xf9, 0x70, 0xe7, 0xe2, 0x7a, 0x65, 0xbb, 0x7e, 0xa0, 0x0e, 0xad, 0x76, 0x07, 0x5f, + 0xd5, 0x9e, 0xf1, 0xa1, 0x4b, 0x72, 0x5e, 0x83, 0x65, 0xe0, 0xa1, 0xa5, 0x52, 0x34, 0xc7, 0x9b, 0xac, 0x67, 0x53, + 0x79, 0x30, 0x9a, 0x8d, 0x65, 0xb4, 0xfc, 0xc3, 0x8d, 0xe7, 0x7d, 0xa1, 0xd6, 0x30, 0x7b, 0x18, 0xc8, 0xee, 0x13, + 0x28, 0x99, 0xa3, 0xd0, 0x9d, 0xcd, 0x50, 0x45, 0x6d, 0x9e, 0x80, 0x62, 0x05, 0xbf, 0x78, 0xcb, 0x62, 0x3a, 0x67, + 0x8e, 0xf3, 0x35, 0xac, 0x7b, 0x1c, 0x35, 0x51, 0xd5, 0x91, 0x08, 0xc2, 0xab, 0xc5, 0x3d, 0xd1, 0x4b, 0x92, 0x8e, + 0x3a, 0x56, 0xe3, 0x3f, 0x73, 0x58, 0x4f, 0xed, 0xee, 0x68, 0xcb, 0x3e, 0xd7, 0x53, 0xba, 0x16, 0x50, 0x93, 0xe3, + 0x8e, 0x97, 0x50, 0x00, 0x7f, 0x4f, 0x35, 0x95, 0x88, 0x97, 0x69, 0x73, 0xb3, 0xe6, 0x60, 0x0c, 0x28, 0x23, 0x80, + 0x6a, 0x46, 0x23, 0xa3, 0x6f, 0xfc, 0x60, 0x62, 0xb8, 0xa3, 0x63, 0x4e, 0x74, 0xd0, 0x66, 0xf6, 0x37, 0x30, 0x23, + 0xd9, 0x0e, 0xa7, 0xee, 0x06, 0x35, 0x28, 0x9e, 0x04, 0x06, 0x11, 0x8d, 0xd5, 0x6c, 0x24, 0xf1, 0xdb, 0x6c, 0xbe, + 0x1e, 0xbc, 0x75, 0x00, 0x7e, 0xe1, 0x84, 0x59, 0x67, 0xb6, 0xea, 0x7b, 0x09, 0x10, 0xf0, 0xa3, 0xd4, 0x18, 0xf4, + 0xee, 0x84, 0xea, 0xc5, 0x0e, 0xa7, 0xac, 0xea, 0xc9, 0x39, 0x31, 0xf6, 0x4c, 0x3d, 0x00, 0xa4, 0x30, 0x45, 0x9d, + 0x8a, 0x93, 0x4f, 0xfe, 0xd4, 0x75, 0xc4, 0x63, 0x25, 0x2e, 0x33, 0x1c, 0x82, 0x0b, 0xa4, 0xd8, 0x8a, 0xd9, 0xba, + 0x08, 0xe4, 0x67, 0x32, 0x20, 0x19, 0x13, 0x5c, 0x03, 0xf4, 0xfe, 0x39, 0x91, 0x10, 0xa6, 0x0d, 0xa1, 0x58, 0x9a, + 0xba, 0xd4, 0x78, 0xa5, 0xd5, 0xc4, 0xd1, 0xa6, 0x4b, 0xca, 0x07, 0xd0, 0x05, 0x85, 0x7c, 0x56, 0x14, 0xfc, 0x78, + 0x86, 0x73, 0xaf, 0x11, 0xc5, 0x9e, 0xc1, 0xd1, 0x87, 0x0a, 0x48, 0x66, 0xa4, 0x10, 0x2d, 0xed, 0xc9, 0x8e, 0x20, + 0x20, 0x2f, 0x3b, 0x3b, 0x14, 0x54, 0x3d, 0x4c, 0xe0, 0x7b, 0xe4, 0xc0, 0x0b, 0x58, 0xe6, 0x48, 0x73, 0xba, 0x17, + 0x3e, 0x04, 0x76, 0x0e, 0xba, 0xf0, 0xb0, 0x8a, 0x3e, 0xa0, 0xda, 0x07, 0xd7, 0x35, 0x32, 0x7b, 0x15, 0x33, 0xca, + 0xc1, 0xaa, 0x6e, 0xcc, 0x36, 0xb1, 0xaf, 0x33, 0xd7, 0x35, 0xa0, 0xdf, 0x63, 0x3f, 0x03, 0xd8, 0xc3, 0x88, 0xd9, + 0x3a, 0x5e, 0xd2, 0x5b, 0xa0, 0xdb, 0xe0, 0x4a, 0x89, 0xa4, 0x61, 0xb2, 0xc0, 0xa4, 0x06, 0x76, 0x5a, 0x86, 0x45, + 0x98, 0x6f, 0xf4, 0x8e, 0x7d, 0x12, 0xa6, 0x73, 0xa0, 0x5d, 0x63, 0xb2, 0x49, 0xe9, 0x71, 0xae, 0xd0, 0x49, 0x08, + 0xa9, 0x11, 0xfb, 0x92, 0x19, 0x48, 0xe5, 0x31, 0x61, 0x8f, 0xab, 0xc1, 0x93, 0x81, 0xa5, 0x82, 0xfa, 0xd7, 0x66, + 0x29, 0x6d, 0xc9, 0x99, 0x23, 0x7c, 0x8a, 0xe5, 0xcf, 0xb0, 0x96, 0x60, 0xcb, 0x5a, 0xa0, 0xb4, 0xd7, 0x82, 0x8b, + 0x05, 0xb3, 0x0e, 0x05, 0xce, 0x60, 0xb6, 0xaf, 0x28, 0xc1, 0x6b, 0x79, 0x58, 0x27, 0xb1, 0x92, 0xf1, 0xcf, 0x8c, + 0xdf, 0x30, 0x72, 0x59, 0xca, 0x10, 0x99, 0xe9, 0xcf, 0x3d, 0x16, 0xef, 0x2d, 0x07, 0x9b, 0x49, 0x62, 0xf9, 0xf7, + 0xfb, 0xcf, 0x09, 0x1e, 0xf8, 0xa1, 0x27, 0xc2, 0x6a, 0x85, 0xa0, 0x67, 0x91, 0x51, 0x85, 0x9e, 0x91, 0x13, 0x05, + 0x70, 0x96, 0x3d, 0x9a, 0xeb, 0x4b, 0xea, 0xde, 0x11, 0xd2, 0x62, 0x73, 0xc2, 0xca, 0x9e, 0x33, 0x79, 0x0e, 0x8e, + 0xa2, 0x02, 0xa9, 0xce, 0x78, 0x0c, 0x38, 0xfd, 0x2d, 0xe7, 0xd5, 0xce, 0x77, 0x37, 0x88, 0x60, 0x4b, 0x7a, 0x3b, + 0x0e, 0x5f, 0x02, 0x98, 0x04, 0x93, 0x19, 0x72, 0x91, 0xcf, 0x4c, 0xd5, 0x6b, 0xfe, 0x49, 0x74, 0x14, 0xe2, 0x36, + 0x24, 0x22, 0xa1, 0x6c, 0x83, 0x3c, 0x8d, 0x23, 0xbb, 0x5b, 0x2a, 0xfd, 0xc0, 0x5f, 0xf0, 0x5d, 0xa7, 0x10, 0xe4, + 0xe0, 0x9d, 0x84, 0x57, 0x99, 0x0a, 0x67, 0xf9, 0xa6, 0x3b, 0xc2, 0x5b, 0x51, 0xc4, 0x43, 0x88, 0xdb, 0x1d, 0x28, + 0x1b, 0x08, 0x80, 0x46, 0x33, 0x0b, 0x70, 0x31, 0xc5, 0x1a, 0x8e, 0x38, 0xb8, 0x9c, 0xc3, 0x96, 0x65, 0x1c, 0x77, + 0x73, 0xdc, 0x65, 0x10, 0x29, 0xa2, 0x4b, 0x57, 0xa5, 0x4d, 0xd1, 0x63, 0x9b, 0x8a, 0xf0, 0xec, 0x53, 0x3c, 0x2d, + 0x6c, 0xa1, 0xaa, 0x69, 0xa2, 0x0f, 0x77, 0x21, 0xf2, 0x0c, 0x8d, 0x6e, 0xd7, 0x5e, 0x7f, 0x96, 0xb2, 0x37, 0x94, + 0xa1, 0xfc, 0x52, 0xdc, 0xe4, 0x7e, 0x5d, 0x6d, 0x01, 0xac, 0xe8, 0xc3, 0xbc, 0x27, 0xa0, 0xce, 0x5e, 0x71, 0x38, + 0x37, 0xdf, 0xc6, 0xcc, 0x2a, 0x37, 0xd6, 0xb0, 0x42, 0x27, 0x17, 0x6e, 0x1b, 0x6d, 0x1f, 0xe8, 0x74, 0x45, 0xc2, + 0x8f, 0x57, 0xbe, 0x8f, 0x8e, 0x48, 0x25, 0x99, 0x2b, 0x76, 0xa7, 0xfd, 0x2d, 0xc8, 0xd0, 0x03, 0x39, 0x79, 0x24, + 0x40, 0x82, 0x5e, 0xc0, 0x7c, 0x3e, 0x8f, 0xb6, 0x06, 0x0e, 0xdc, 0x28, 0x9f, 0x38, 0x87, 0xb8, 0x9b, 0x9b, 0xa5, + 0xc3, 0xd3, 0x52, 0xfa, 0x1e, 0x0a, 0x81, 0x13, 0xba, 0xa4, 0xca, 0x17, 0x8c, 0x24, 0x24, 0xf1, 0x4c, 0xa2, 0x73, + 0x98, 0x9b, 0x73, 0x1a, 0xb1, 0x3c, 0xb2, 0x32, 0xf4, 0xda, 0x51, 0x94, 0x77, 0x5c, 0xcd, 0x92, 0xee, 0xa3, 0x66, + 0x29, 0x63, 0xab, 0x0a, 0x23, 0x1a, 0x19, 0x9f, 0xb4, 0xd5, 0x8b, 0xa4, 0x1c, 0xb2, 0x2e, 0x52, 0x03, 0xdb, 0xc6, + 0x64, 0x66, 0x94, 0xdc, 0xc6, 0xf9, 0xa2, 0x32, 0xb0, 0x5d, 0x04, 0xad, 0x52, 0x3b, 0xd2, 0x41, 0x7d, 0x89, 0xea, + 0xfd, 0xc0, 0xda, 0x36, 0xc9, 0xdf, 0xd1, 0xaa, 0x93, 0xef, 0xa1, 0xab, 0x9c, 0x56, 0x3b, 0x10, 0xc6, 0xbd, 0xa2, + 0x8d, 0x02, 0x03, 0xc5, 0xee, 0xc1, 0x76, 0x15, 0x3a, 0xbd, 0xdf, 0x1a, 0x95, 0x6c, 0x3a, 0x05, 0x21, 0xa5, 0x2a, + 0x98, 0xa6, 0x0a, 0xab, 0x1b, 0x18, 0xb3, 0xbf, 0x3b, 0x6e, 0xbe, 0x73, 0x1c, 0x7b, 0xc5, 0x5c, 0x1f, 0x82, 0xbd, + 0xa0, 0x78, 0xad, 0x35, 0xf8, 0xe2, 0x34, 0x30, 0x13, 0x33, 0xb3, 0xd2, 0xf2, 0x76, 0xad, 0x39, 0xfe, 0x7a, 0x70, + 0xc7, 0x14, 0x3e, 0xd0, 0xfd, 0x98, 0x5b, 0x2d, 0xde, 0xcb, 0xb7, 0xa5, 0xc4, 0x97, 0x2e, 0xd5, 0x2e, 0xce, 0x42, + 0x27, 0x0e, 0xc3, 0x1c, 0x86, 0x56, 0x6c, 0x8d, 0x34, 0x7e, 0x81, 0x4d, 0xaf, 0xeb, 0x9d, 0x38, 0x62, 0xc0, 0xb5, + 0x9e, 0x23, 0x66, 0x3e, 0x73, 0xd9, 0x7b, 0x26, 0xe9, 0x34, 0x4c, 0x57, 0x23, 0x8e, 0x3a, 0x01, 0x35, 0x9f, 0x3a, + 0xb9, 0x3f, 0x3c, 0xa0, 0x90, 0x1d, 0xfa, 0xe8, 0xc7, 0xda, 0xbe, 0x4d, 0x49, 0x65, 0xc3, 0xd7, 0x70, 0x4e, 0x4c, + 0xce, 0x2e, 0xff, 0xf8, 0xd0, 0xa6, 0xd9, 0xe1, 0x58, 0x19, 0xc4, 0xab, 0x3e, 0x67, 0x6f, 0x50, 0xc4, 0xda, 0x94, + 0xdd, 0xea, 0x1d, 0x64, 0x36, 0xe7, 0xff, 0x80, 0x90, 0x84, 0x58, 0xaf, 0x86, 0xf9, 0x65, 0x88, 0x6e, 0xad, 0x6b, + 0x70, 0xce, 0x4f, 0xe7, 0x7b, 0xcc, 0x16, 0x3f, 0xf9, 0xa5, 0x2e, 0x99, 0x67, 0xeb, 0x04, 0x61, 0xb1, 0xec, 0xda, + 0x03, 0xbf, 0xc2, 0xfb, 0xe2, 0xf7, 0xf0, 0x85, 0x0d, 0x90, 0x10, 0xce, 0x2f, 0xe9, 0xd7, 0xef, 0x7f, 0x61, 0x1b, + 0xc4, 0xf6, 0x0f, 0x8f, 0xa7, 0x67, 0xae, 0xd2, 0xd2, 0xce, 0x1f, 0x07, 0x43, 0x98, 0x18, 0x01, 0x5a, 0x11, 0x24, + 0x21, 0x24, 0xd3, 0x07, 0x52, 0xb2, 0x6e, 0x6a, 0x9e, 0x56, 0x52, 0x56, 0xd1, 0xbe, 0x52, 0x9f, 0xee, 0xca, 0x9d, + 0x65, 0x42, 0xac, 0x2e, 0x4d, 0xfd, 0x50, 0xe0, 0xcb, 0xca, 0xa4, 0x2c, 0x49, 0xa1, 0x2b, 0xcf, 0xfe, 0x91, 0xfa, + 0x8e, 0x80, 0xcf, 0x35, 0x7a, 0x85, 0x60, 0xbc, 0xde, 0xee, 0xac, 0x41, 0x4f, 0x2b, 0x83, 0x49, 0x3d, 0xf2, 0xca, + 0x5e, 0x5a, 0x15, 0x5d, 0x5d, 0xf9, 0xae, 0xad, 0x6e, 0xc4, 0xb2, 0xf7, 0x9a, 0x61, 0x85, 0xbc, 0xab, 0xcc, 0x69, + 0x9a, 0x1c, 0xc6, 0x65, 0xf3, 0x35, 0xa2, 0x12, 0x69, 0x66, 0xef, 0x1a, 0x17, 0x9b, 0x4c, 0xd9, 0x67, 0xc1, 0x36, + 0xee, 0x82, 0x44, 0xe2, 0x04, 0x3b, 0x24, 0x24, 0x94, 0xf6, 0x4d, 0xd6, 0xae, 0x3a, 0x73, 0x3a, 0xc3, 0x38, 0xed, + 0x78, 0xd8, 0x47, 0x4c, 0xc7, 0xde, 0x22, 0x1d, 0x21, 0x3b, 0x75, 0x30, 0x66, 0x3c, 0x49, 0xaa, 0xe6, 0xa5, 0x8c, + 0x56, 0x27, 0xcf, 0x08, 0xcd, 0xda, 0x83, 0xc6, 0x44, 0x77, 0x41, 0xec, 0x9b, 0xa4, 0xca, 0x41, 0x0a, 0x27, 0xea, + 0x7b, 0xad, 0x87, 0xfd, 0x75, 0x43, 0xeb, 0x92, 0xc9, 0x5c, 0x42, 0x81, 0xc4, 0x32, 0xbf, 0x0c, 0x24, 0x2f, 0x34, + 0x9d, 0x0d, 0xb5, 0x95, 0x80, 0x9b, 0xda, 0xe2, 0xed, 0xf4, 0x3d, 0xdf, 0xcd, 0xea, 0x71, 0xbd, 0x47, 0xb7, 0xe0, + 0xae, 0x3d, 0xcc, 0x98, 0x96, 0x31, 0x61, 0x0f, 0x14, 0x68, 0x80, 0x9e, 0x0a, 0x1e, 0xea, 0x66, 0xf1, 0x57, 0x2e, + 0xc6, 0x6f, 0x8c, 0x3d, 0xaa, 0x23, 0x89, 0x3e, 0xa7, 0xd7, 0xe2, 0xea, 0x3e, 0x19, 0x0b, 0x02, 0x23, 0x1f, 0x9e, + 0x14, 0xcc, 0x70, 0x9a, 0x68, 0x32, 0xe1, 0x1d, 0x8e, 0x3a, 0x4d, 0x51, 0x9b, 0x81, 0x9d, 0xdc, 0x64, 0xd1, 0xc6, + 0xbb, 0xab, 0x5b, 0xdd, 0x7b, 0xc3, 0xb0, 0xe6, 0x70, 0x87, 0xc4, 0x51, 0xf7, 0x16, 0xb5, 0x1a, 0x97, 0x41, 0xdb, + 0x26, 0x5b, 0xb1, 0xb5, 0x5e, 0x55, 0xe3, 0x34, 0xef, 0xb8, 0x7a, 0x4d, 0xdb, 0xee, 0x40, 0xd4, 0x0b, 0xb1, 0xac, + 0xf9, 0xa8, 0x09, 0x32, 0x9a, 0xf2, 0xe3, 0xc1, 0x94, 0xc7, 0x82, 0x16, 0xa7, 0x5f, 0xb7, 0xb1, 0x32, 0xf1, 0xb3, + 0x70, 0x34, 0x21, 0xce, 0xef, 0xe3, 0xb0, 0x69, 0x56, 0xfa, 0x17, 0x75, 0x54, 0x2b, 0x42, 0x0a, 0xa3, 0x13, 0xe1, + 0x42, 0x61, 0x4d, 0x1a, 0x03, 0x5e, 0x7a, 0xf9, 0x89, 0xb8, 0x0f, 0xa7, 0x28, 0x50, 0x57, 0x88, 0x5e, 0x0f, 0x6d, + 0xe1, 0xc4, 0xc1, 0xd3, 0x9b, 0xca, 0x28, 0xf5, 0xcd, 0xfd, 0xcc, 0x1b, 0x9d, 0x7c, 0xa8, 0xf2, 0xb8, 0x41, 0xa8, + 0x70, 0xdf, 0x84, 0x21, 0x43, 0xaf, 0xe0, 0xfb, 0x26, 0x3b, 0x56, 0x7e, 0xd2, 0xe2, 0x3c, 0xb4, 0x25, 0xd6, 0x8e, + 0x87, 0x8f, 0x40, 0x6c, 0xcf, 0x1b, 0x98, 0x1e, 0xc8, 0xbf, 0xd9, 0x95, 0x35, 0x8c, 0x4a, 0x0a, 0x1b, 0xbb, 0xc2, + 0x75, 0x0a, 0x90, 0x51, 0x89, 0xf3, 0x38, 0x88, 0x02, 0x58, 0x6d, 0x3f, 0x50, 0xaa, 0x54, 0x42, 0x0a, 0xdc, 0xea, + 0xf4, 0x67, 0xe0, 0x76, 0xf0, 0x92, 0x1d, 0xb3, 0x7a, 0x88, 0xcc, 0xc6, 0x23, 0xcd, 0xd5, 0x86, 0x69, 0xd1, 0xcd, + 0x89, 0xb3, 0x30, 0xb1, 0xe6, 0x51, 0xe1, 0x1e, 0x35, 0x6e, 0x4e, 0x84, 0xcb, 0x45, 0x37, 0x71, 0x68, 0x3c, 0xdd, + 0xb6, 0xb3, 0x7a, 0xa7, 0xb7, 0x6e, 0xc0, 0x59, 0x3d, 0x53, 0x5f, 0x67, 0xb0, 0xe0, 0xfe, 0x53, 0x15, 0x2a, 0x74, + 0x02, 0x5b, 0x32, 0x81, 0x8b, 0x1f, 0x15, 0x9f, 0x59, 0xc0, 0x0b, 0x4d, 0x4a, 0xb5, 0x16, 0x30, 0xf8, 0xaa, 0x23, + 0x5e, 0x37, 0x2d, 0x4b, 0x3a, 0x54, 0xa5, 0xef, 0x7d, 0xa7, 0xaa, 0xb4, 0x46, 0xa6, 0xbb, 0x6d, 0xc3, 0xc9, 0x87, + 0x48, 0x8b, 0x29, 0x2d, 0x10, 0x01, 0x70, 0xfe, 0xf5, 0x03, 0x40, 0xf1, 0xad, 0x49, 0xb2, 0x16, 0xf2, 0x5c, 0x1d, + 0x4e, 0xb8, 0x4e, 0x79, 0xb2, 0x9c, 0xcb, 0x8e, 0x18, 0x18, 0x1c, 0x73, 0x56, 0x44, 0x2f, 0x72, 0xe4, 0x0c, 0x56, + 0xd9, 0xb4, 0x33, 0xc4, 0xd1, 0x11, 0xd8, 0x09, 0x51, 0x13, 0xc0, 0x20, 0xc6, 0x18, 0x36, 0x0b, 0x95, 0xcd, 0x68, + 0xe4, 0x36, 0x54, 0x03, 0xca, 0xd7, 0x38, 0xb1, 0x5d, 0xd6, 0x30, 0x74, 0x10, 0xc8, 0xe6, 0xc3, 0x67, 0x86, 0xfb, + 0x58, 0xfb, 0xbd, 0xd7, 0xd0, 0x32, 0x93, 0xe2, 0xf4, 0x69, 0xcb, 0xdc, 0xb9, 0x74, 0xe2, 0xb2, 0x78, 0x69, 0xf6, + 0xa2, 0xf7, 0x94, 0x8d, 0x29, 0x05, 0x59, 0x05, 0x3d, 0x1f, 0xcc, 0x03, 0x40, 0x31, 0x75, 0x99, 0x4c, 0x02, 0x9c, + 0x4b, 0x44, 0xe9, 0xb5, 0x4c, 0x08, 0x6c, 0x01, 0xd3, 0x72, 0xda, 0x5c, 0xe1, 0xee, 0xf9, 0x92, 0xcb, 0xd3, 0xb2, + 0x57, 0xe8, 0x35, 0xbf, 0xcf, 0xde, 0xd5, 0x57, 0x4d, 0xfb, 0xef, 0xcb, 0xef, 0x76, 0x3d, 0x91, 0x94, 0x81, 0xa8, + 0xda, 0xa9, 0x9c, 0x79, 0xd2, 0x81, 0x9d, 0x6d, 0x9d, 0xdf, 0x39, 0x9e, 0xff, 0xb1, 0x3e, 0x99, 0x3a, 0xb7, 0xa1, + 0xb1, 0xd8, 0xf8, 0xfd, 0x2e, 0xf3, 0xcc, 0x92, 0x15, 0x29, 0x21, 0x50, 0xf5, 0x48, 0x36, 0x9b, 0xd4, 0x0b, 0x52, + 0x8b, 0x1b, 0x1f, 0x91, 0x22, 0x05, 0x90, 0x86, 0x6c, 0x20, 0xee, 0x0d, 0x76, 0xec, 0xf6, 0xfd, 0x4d, 0x8d, 0x7d, + 0xed, 0x9a, 0x7e, 0xfd, 0xc8, 0xcd, 0xd7, 0x6e, 0xd1, 0xc7, 0x2d, 0x7a, 0x5e, 0xbf, 0x92, 0x92, 0x70, 0xf0, 0x6e, + 0x89, 0xc0, 0xb4, 0xc3, 0x9c, 0x7c, 0xb7, 0x4b, 0x33, 0x20, 0x68, 0x99, 0x45, 0xb1, 0x91, 0x6b, 0x2c, 0x61, 0x40, + 0x55, 0x41, 0x6e, 0xce, 0x99, 0x6a, 0x33, 0x10, 0xac, 0x77, 0xbc, 0xdc, 0x66, 0x34, 0x71, 0x87, 0x6a, 0x0d, 0xdc, + 0xc4, 0x85, 0x4b, 0x84, 0x9d, 0xe2, 0x2a, 0x9b, 0x93, 0xb3, 0x28, 0xe3, 0xef, 0x92, 0x7b, 0x75, 0x18, 0x18, 0x9b, + 0x0b, 0x2e, 0xdc, 0x76, 0x1e, 0xba, 0x70, 0xc4, 0x1b, 0x79, 0x5a, 0x90, 0x4a, 0xd3, 0x09, 0x14, 0x65, 0x6c, 0xcb, + 0x85, 0xac, 0x04, 0xfd, 0xc6, 0xa1, 0xcd, 0x45, 0x15, 0xf0, 0x4d, 0x3c, 0x55, 0x64, 0xb6, 0xaa, 0x62, 0x2c, 0xe2, + 0xb4, 0xa5, 0x54, 0x6f, 0xe7, 0x4f, 0x00, 0x8e, 0x21, 0xff, 0xe3, 0xd5, 0x44, 0xa0, 0x80, 0x2a, 0x71, 0x80, 0x90, + 0x99, 0xd5, 0x53, 0xf4, 0xe6, 0x0c, 0xa1, 0x1b, 0x90, 0xf2, 0xc1, 0x69, 0x0e, 0x7e, 0xd5, 0x6a, 0x6f, 0xab, 0xc2, + 0x96, 0x23, 0xab, 0x91, 0xa2, 0x09, 0xd0, 0x92, 0x42, 0x22, 0x59, 0x9c, 0x3a, 0xd6, 0xe7, 0xfa, 0xda, 0xfd, 0x23, + 0x0a, 0xd9, 0xf3, 0xf3, 0xf3, 0x7c, 0x6d, 0x0d, 0xd2, 0xc6, 0xe2, 0x2f, 0xed, 0x5a, 0x22, 0x26, 0xcd, 0xfc, 0x40, + 0x7d, 0x5d, 0x5b, 0x75, 0x5f, 0x45, 0xbd, 0xe1, 0xb9, 0x55, 0x3f, 0x66, 0x51, 0x7b, 0x35, 0x52, 0xda, 0x8f, 0xc5, + 0x5f, 0xfa, 0xbb, 0xe9, 0x6c, 0xb5, 0x8e, 0xe3, 0xe1, 0xcd, 0x75, 0xea, 0x30, 0x97, 0xbf, 0xc6, 0xda, 0x70, 0x68, + 0xbe, 0x01, 0xbb, 0x45, 0xf5, 0xa9, 0x76, 0x4d, 0x20, 0x54, 0xc0, 0xcd, 0x44, 0x70, 0x20, 0xee, 0x23, 0xfd, 0x8c, + 0x6a, 0xed, 0xab, 0x40, 0x8c, 0x56, 0xa6, 0x49, 0x4b, 0xb5, 0x1a, 0x9a, 0x29, 0xb3, 0x51, 0xc5, 0x50, 0x05, 0xee, + 0xec, 0xca, 0xd9, 0xa8, 0x33, 0x24, 0x2f, 0x07, 0x43, 0xe3, 0xe1, 0x38, 0x40, 0x1f, 0xac, 0x81, 0x17, 0xbd, 0xf2, + 0x8c, 0xf7, 0xfa, 0x99, 0x31, 0xd4, 0xd2, 0x63, 0x7e, 0x34, 0x78, 0xae, 0x76, 0x1c, 0x9c, 0x8a, 0xda, 0xa9, 0x60, + 0x9c, 0xb3, 0x28, 0x90, 0x01, 0x5e, 0x21, 0xb9, 0xc6, 0x70, 0x3e, 0x9e, 0x85, 0x15, 0xfd, 0xe4, 0x39, 0x6b, 0x6e, + 0x4b, 0x1e, 0xd0, 0x96, 0x2a, 0xa7, 0xf0, 0xad, 0xac, 0x91, 0x2c, 0xd2, 0x53, 0xef, 0xcc, 0x26, 0x51, 0xb1, 0xae, + 0x00, 0x20, 0x79, 0x0f, 0x3d, 0x33, 0x9d, 0x87, 0x72, 0x15, 0xc5, 0xab, 0x9c, 0x19, 0x37, 0x26, 0x0a, 0xef, 0x39, + 0x42, 0x19, 0x10, 0x31, 0x33, 0x0c, 0x9c, 0xd2, 0xbe, 0xb0, 0x3c, 0x07, 0xb0, 0x06, 0x10, 0x7f, 0x71, 0x1d, 0x6e, + 0x9d, 0x67, 0xa1, 0x34, 0x95, 0x1c, 0xcc, 0xc2, 0x90, 0x51, 0xbc, 0x37, 0xe9, 0xde, 0xc2, 0xee, 0x27, 0xe2, 0xae, + 0x27, 0x02, 0x9d, 0x5b, 0xbe, 0x57, 0x11, 0x05, 0x38, 0x7f, 0x06, 0x71, 0x1f, 0x54, 0xb9, 0xcc, 0x38, 0x75, 0x2c, + 0x3b, 0xd4, 0x0e, 0xde, 0x45, 0xd0, 0xa2, 0x55, 0x4d, 0xe1, 0xeb, 0xbe, 0x85, 0xe7, 0xe2, 0x45, 0xb8, 0xd6, 0x01, + 0x20, 0x09, 0x87, 0x68, 0x2e, 0x18, 0xde, 0x25, 0x16, 0xd6, 0x44, 0x9b, 0x3f, 0x49, 0x4a, 0x53, 0x15, 0x15, 0x65, + 0x6e, 0x69, 0x36, 0xaa, 0x21, 0x03, 0xf0, 0x04, 0xbd, 0xfc, 0x3e, 0xa3, 0xa2, 0x3f, 0xaa, 0xf2, 0xcc, 0xd2, 0xad, + 0xcb, 0x41, 0xaf, 0xc4, 0xea, 0x29, 0x86, 0x6d, 0x99, 0x3d, 0x58, 0xd0, 0xbc, 0xf8, 0xe2, 0x7f, 0xc3, 0x28, 0x7a, + 0x38, 0xae, 0xb4, 0xb1, 0xf0, 0xad, 0xa8, 0x7b, 0x68, 0xad, 0x8a, 0xfa, 0xbc, 0x8c, 0xa5, 0x83, 0xf6, 0x51, 0x5b, + 0xe6, 0x94, 0xaa, 0xbe, 0xcb, 0xfe, 0xdc, 0xa5, 0xfb, 0x54, 0x69, 0xda, 0x5b, 0x51, 0x88, 0xe0, 0xf9, 0x24, 0x45, + 0x0b, 0x55, 0x75, 0xb5, 0x20, 0xc0, 0x38, 0x45, 0x8a, 0x93, 0x5b, 0x5d, 0xe8, 0x61, 0x37, 0xb7, 0xa0, 0x9c, 0xbc, + 0xde, 0x0d, 0x22, 0xa4, 0x83, 0xde, 0xb7, 0x99, 0xb2, 0xca, 0x79, 0x43, 0x58, 0xf2, 0xca, 0xc5, 0x99, 0x29, 0x97, + 0x69, 0xa5, 0x55, 0xdc, 0xd0, 0xac, 0x35, 0x9d, 0x12, 0x30, 0x25, 0x17, 0xad, 0x4a, 0xce, 0xb3, 0xa1, 0x71, 0x91, + 0x6d, 0xd6, 0x5a, 0xd1, 0x74, 0x87, 0x05, 0x9e, 0x29, 0xac, 0xcb, 0x1e, 0x5c, 0xc3, 0x89, 0x06, 0xc7, 0x75, 0xbb, + 0x6d, 0x82, 0x08, 0xe4, 0x2e, 0xa6, 0x78, 0x3c, 0x31, 0xfc, 0x97, 0x3b, 0x14, 0x1f, 0x5e, 0x63, 0xaf, 0xa3, 0x18, + 0x29, 0xdf, 0x34, 0xd8, 0x3a, 0x02, 0xfc, 0x0b, 0x77, 0x29, 0xf5, 0x35, 0x24, 0x13, 0xca, 0xda, 0xcd, 0x39, 0x31, + 0x70, 0x52, 0xdc, 0xd2, 0x34, 0xe5, 0x65, 0x9e, 0xde, 0x47, 0x94, 0x17, 0xed, 0x2d, 0x62, 0x93, 0x86, 0xae, 0x8d, + 0xb8, 0x37, 0x98, 0x80, 0xe3, 0x7f, 0xb0, 0x11, 0x80, 0x13, 0x29, 0x9e, 0x70, 0x39, 0xce, 0x2c, 0xc5, 0x42, 0xbf, + 0x79, 0xa5, 0xb0, 0x4f, 0x61, 0x51, 0xd1, 0x9a, 0xcd, 0x75, 0x4b, 0xa5, 0x68, 0xc4, 0xa1, 0x4f, 0x52, 0x59, 0x9c, + 0xa7, 0x23, 0x3c, 0x9f, 0x52, 0x35, 0x3d, 0xcf, 0x31, 0xaf, 0xe3, 0x88, 0xb3, 0x39, 0x11, 0x92, 0x0f, 0xd3, 0x5c, + 0x1c, 0x31, 0xb6, 0xef, 0x72, 0xa9, 0x7c, 0x7c, 0xcb, 0x10, 0x85, 0x11, 0x2e, 0xc3, 0x09, 0xb2, 0xf4, 0xa7, 0x68, + 0x5b, 0x74, 0xa7, 0x6a, 0x12, 0x74, 0xad, 0x4e, 0x2e, 0x14, 0x67, 0xda, 0x27, 0x48, 0x28, 0xb9, 0xc1, 0x46, 0x07, + 0x35, 0x55, 0xe6, 0x47, 0xa5, 0x16, 0x37, 0xbf, 0x60, 0x61, 0x40, 0xeb, 0x9d, 0x71, 0xa6, 0xd8, 0x16, 0x6f, 0xad, + 0xe1, 0xce, 0x06, 0x66, 0xf7, 0x0a, 0x88, 0x2c, 0x22, 0xc7, 0x79, 0xcc, 0x70, 0xac, 0xb8, 0xfa, 0x22, 0x89, 0xe2, + 0x9c, 0xd0, 0xc6, 0x40, 0x8b, 0x8f, 0x7b, 0xba, 0x5a, 0xf0, 0x65, 0x1c, 0x76, 0x0d, 0x89, 0x2a, 0x4e, 0xef, 0xa2, + 0xcb, 0xfd, 0x44, 0x80, 0xe1, 0xc2, 0xc5, 0xf3, 0x7c, 0xef, 0x9c, 0xa8, 0x59, 0x38, 0xf9, 0xb0, 0xfd, 0x69, 0x90, + 0x90, 0xa3, 0xfe, 0x5a, 0xb8, 0x39, 0x6a, 0xaf, 0x5e, 0x69, 0x19, 0x6d, 0xf8, 0x91, 0x11, 0x2d, 0xef, 0x25, 0x9c, + 0x92, 0x61, 0x74, 0xfe, 0xc5, 0x8b, 0x19, 0x0e, 0x3c, 0x0a, 0xc7, 0xa0, 0x69, 0x00, 0x00, 0xd7, 0xab, 0x9f, 0xb0, + 0xb5, 0xa5, 0x9c, 0x11, 0xce, 0x65, 0x1b, 0x12, 0xac, 0x8c, 0x4b, 0x39, 0x5a, 0x1b, 0xcf, 0x09, 0x58, 0x32, 0xc7, + 0xad, 0x83, 0xc6, 0xa8, 0xe7, 0x79, 0xc7, 0x94, 0x63, 0xf3, 0x2c, 0x38, 0x6f, 0xf9, 0xbe, 0xd4, 0xe4, 0x0c, 0x82, + 0xe6, 0xaa, 0x29, 0xb1, 0x26, 0x2c, 0x7a, 0xe0, 0x72, 0x7f, 0x49, 0x9e, 0xb1, 0xc8, 0xdf, 0xcd, 0x30, 0x41, 0x2b, + 0x14, 0x8a, 0x86, 0x7c, 0x25, 0x34, 0x08, 0x96, 0xab, 0xb9, 0xe5, 0xbd, 0x18, 0x49, 0x4e, 0x35, 0x9b, 0xda, 0x8d, + 0x72, 0x68, 0x86, 0x29, 0x73, 0x70, 0x68, 0x3b, 0x3f, 0x11, 0x7c, 0xd9, 0x59, 0xd5, 0x82, 0x38, 0xfb, 0x5d, 0xb3, + 0x33, 0xa1, 0x85, 0x28, 0x55, 0x5f, 0x84, 0x3d, 0x9d, 0x98, 0x5a, 0x76, 0x5e, 0xc8, 0x9d, 0xa6, 0xe0, 0xad, 0xa6, + 0x6c, 0x0e, 0x29, 0xbe, 0x63, 0x3a, 0x32, 0x87, 0x8f, 0xc8, 0xb0, 0x1d, 0xd9, 0x4c, 0x93, 0x4b, 0x9f, 0x7e, 0xd6, + 0xeb, 0x7f, 0x74, 0x80, 0xc2, 0x3a, 0x06, 0x13, 0x9a, 0xe7, 0x79, 0x65, 0xad, 0xa9, 0x4c, 0x67, 0x8e, 0x35, 0x75, + 0x34, 0x50, 0xf0, 0xd4, 0x78, 0x0a, 0x11, 0x35, 0x25, 0x12, 0xad, 0x1e, 0xc2, 0x12, 0x50, 0x8e, 0xf7, 0x93, 0x98, + 0xeb, 0xf6, 0xa8, 0xb2, 0xf1, 0xdf, 0x88, 0x3e, 0x1a, 0x5a, 0x07, 0xbf, 0x9e, 0xd9, 0xa8, 0xf9, 0x45, 0xaa, 0x9c, + 0x42, 0x9f, 0x1f, 0x95, 0x34, 0xd6, 0x34, 0xe8, 0xc2, 0xc9, 0xc0, 0x2a, 0xe8, 0xa4, 0x02, 0xff, 0x97, 0x21, 0x67, + 0xac, 0x9c, 0x90, 0x87, 0x8a, 0xc5, 0xda, 0x75, 0xfd, 0x9a, 0x2f, 0x35, 0x66, 0x41, 0xfd, 0x7c, 0x00, 0x6a, 0x2d, + 0xe5, 0x42, 0x7e, 0x33, 0xdf, 0x86, 0x6a, 0xa3, 0x1b, 0x4e, 0x3a, 0xed, 0xd5, 0x2f, 0x85, 0x5c, 0xc4, 0x6a, 0x8a, + 0x0e, 0x69, 0x3c, 0x0d, 0x0a, 0x98, 0xf0, 0x9b, 0x72, 0x6e, 0xe2, 0x20, 0x41, 0x5e, 0x6a, 0x1c, 0x6b, 0xee, 0x7a, + 0x8c, 0xa3, 0xd5, 0x99, 0x95, 0x63, 0x17, 0xaa, 0x15, 0x97, 0xe7, 0x59, 0x54, 0xde, 0x2f, 0xf6, 0xa4, 0xa9, 0x75, + 0x65, 0xd5, 0xbf, 0xdc, 0x8a, 0x79, 0xa3, 0x6b, 0xa4, 0x68, 0xd8, 0x4c, 0xeb, 0xe4, 0x74, 0x51, 0x65, 0x56, 0x10, + 0x78, 0x1b, 0x9a, 0x8d, 0x90, 0xd8, 0x05, 0xfb, 0xd1, 0x1e, 0x2f, 0xbd, 0x5a, 0x9a, 0x22, 0xb8, 0x0b, 0x58, 0xd1, + 0x3f, 0x96, 0x16, 0x06, 0xa4, 0xb6, 0xa2, 0xa4, 0xb6, 0xad, 0xff, 0x36, 0x66, 0xb8, 0x9f, 0x55, 0x73, 0xef, 0x50, + 0xdf, 0x0e, 0x46, 0x8b, 0x8f, 0x49, 0x5f, 0x72, 0x6d, 0x97, 0x82, 0x9c, 0x2c, 0x81, 0xd5, 0x0b, 0xf6, 0x2a, 0xb0, + 0xf2, 0xfa, 0x63, 0xab, 0x0d, 0x99, 0xf0, 0xf6, 0x69, 0x32, 0xe7, 0x32, 0x2a, 0xf0, 0x28, 0xc1, 0xdd, 0xa7, 0x15, + 0x45, 0x59, 0x30, 0xee, 0xbf, 0xe4, 0xcc, 0x2f, 0x2b, 0x92, 0xa4, 0x21, 0xbe, 0xb1, 0x48, 0xfe, 0x10, 0x52, 0xde, + 0x81, 0x5b, 0x07, 0xc2, 0xb5, 0x59, 0x3b, 0x7d, 0x98, 0xcf, 0xe3, 0x03, 0xa3, 0x36, 0xf0, 0x9f, 0xaf, 0xff, 0x24, + 0x8e, 0x33, 0x78, 0xb6, 0x2b, 0x4d, 0xd6, 0x8d, 0x30, 0x91, 0xec, 0x7f, 0xb5, 0xfb, 0x2b, 0x05, 0x16, 0xe1, 0x2a, + 0x19, 0xba, 0x04, 0x74, 0x43, 0x53, 0x2c, 0x70, 0x9f, 0x02, 0x5d, 0x46, 0x3f, 0x09, 0x5d, 0xe5, 0x1a, 0xd3, 0xfb, + 0xa4, 0xe8, 0x03, 0x40, 0x5c, 0x12, 0x5d, 0xde, 0xd2, 0x7b, 0x15, 0x81, 0x0d, 0x0e, 0x90, 0x56, 0xde, 0x99, 0x14, + 0x3d, 0xf7, 0x91, 0xd5, 0x39, 0xf0, 0x7f, 0xdb, 0x65, 0xbb, 0x30, 0x74, 0x76, 0xcb, 0xfd, 0x70, 0x1d, 0x33, 0x1b, + 0x64, 0xd8, 0x13, 0xc5, 0xd8, 0x82, 0x8f, 0x8f, 0xfe, 0x31, 0x8f, 0x3c, 0xb7, 0x03, 0xbe, 0x51, 0x74, 0x50, 0x71, + 0xa8, 0x40, 0xfe, 0x2e, 0x84, 0x42, 0x5d, 0xb6, 0xd9, 0x02, 0xc0, 0xeb, 0x13, 0xd4, 0xd0, 0x89, 0x28, 0xf5, 0x06, + 0x54, 0xf9, 0x1e, 0xa4, 0x54, 0xc2, 0xfc, 0x43, 0x26, 0x53, 0x8d, 0x96, 0x8a, 0xa9, 0x2a, 0x8c, 0xa2, 0x38, 0xd8, + 0xa8, 0x2d, 0x0c, 0x8d, 0xf1, 0x8a, 0x31, 0xa3, 0xe6, 0xc7, 0xce, 0xc2, 0xdb, 0xd2, 0x5e, 0x8f, 0xf8, 0x24, 0x3b, + 0xe3, 0x6b, 0x0f, 0x4a, 0xd0, 0xe0, 0x2a, 0xc0, 0xeb, 0xb4, 0xfa, 0x1c, 0x45, 0x24, 0xb3, 0xcc, 0x1a, 0x1e, 0x88, + 0xfd, 0x88, 0x1a, 0x5f, 0x05, 0xbf, 0x7f, 0xae, 0x21, 0x9e, 0xc5, 0x6f, 0xee, 0x37, 0x7d, 0x67, 0x7f, 0x05, 0xd1, + 0xaf, 0x4e, 0xd1, 0xca, 0x09, 0xc7, 0x3c, 0x01, 0x5e, 0x37, 0x78, 0x01, 0xb6, 0x16, 0x6b, 0x9e, 0x5c, 0x8b, 0xcf, + 0x6d, 0xcd, 0x69, 0x50, 0x55, 0xf2, 0xab, 0x13, 0x4a, 0x68, 0x44, 0x06, 0x9b, 0x86, 0xa4, 0x99, 0x8d, 0xd4, 0xce, + 0x75, 0xc9, 0xc8, 0x40, 0x1a, 0xb8, 0xdb, 0xde, 0x5b, 0x86, 0x4f, 0xd0, 0xbe, 0x22, 0x47, 0x79, 0x1a, 0x23, 0xef, + 0x50, 0x56, 0x2a, 0x71, 0xb9, 0xdc, 0x6c, 0x8b, 0x3e, 0x4f, 0xfd, 0x8a, 0x7e, 0x93, 0xc0, 0xf2, 0xb8, 0x8b, 0x08, + 0x4e, 0xff, 0x57, 0x39, 0x6b, 0x1a, 0x7d, 0x51, 0x7d, 0x08, 0x07, 0x63, 0x7b, 0xd5, 0xdd, 0x36, 0xa5, 0x87, 0x9c, + 0x2d, 0x2d, 0xf2, 0x08, 0x86, 0x96, 0xef, 0x3f, 0xa6, 0x0a, 0x4b, 0x8e, 0x94, 0x76, 0xca, 0x93, 0x40, 0x2f, 0x35, + 0x6c, 0x27, 0xfe, 0x6d, 0x0d, 0xd7, 0xcf, 0x57, 0x6a, 0xe4, 0x16, 0xfe, 0x0f, 0xfe, 0xb3, 0x51, 0xae, 0x2e, 0x75, + 0xf2, 0xa0, 0x1d, 0x14, 0x47, 0x43, 0x72, 0x3c, 0x1b, 0xfb, 0x96, 0xd1, 0x28, 0x9e, 0x09, 0xfb, 0x2c, 0x0a, 0xd3, + 0xa0, 0xc7, 0xa2, 0xc1, 0xb2, 0xca, 0x00, 0xf2, 0xd0, 0x6b, 0x72, 0x46, 0x9c, 0x86, 0x13, 0xdc, 0x08, 0x9a, 0x1a, + 0xcd, 0x3a, 0x24, 0x75, 0x75, 0xa7, 0x60, 0x6a, 0x75, 0x99, 0x37, 0x43, 0x94, 0xcb, 0xfa, 0x7a, 0x77, 0x44, 0xc5, + 0xa3, 0x6c, 0x53, 0xfa, 0x16, 0xb1, 0xd1, 0x8e, 0x19, 0xe0, 0xc0, 0x1c, 0x10, 0xf2, 0xa2, 0x25, 0x2d, 0x67, 0x4e, + 0x51, 0x7e, 0x7a, 0x5e, 0x69, 0x1a, 0x30, 0x3b, 0x29, 0xc4, 0x1c, 0x45, 0x57, 0xa5, 0x2e, 0x8d, 0x41, 0xb6, 0xb0, + 0x13, 0x47, 0xea, 0x4b, 0x8b, 0x5e, 0xd2, 0xa1, 0x9b, 0x53, 0x0b, 0x8b, 0x71, 0x00, 0xdb, 0x74, 0x8f, 0xc5, 0x27, + 0xaa, 0x30, 0xac, 0xa5, 0xaf, 0x11, 0xfe, 0x7a, 0xd9, 0x7c, 0xde, 0xce, 0x9b, 0xd6, 0xcd, 0x59, 0x23, 0x9d, 0x18, + 0x72, 0x58, 0x9d, 0x60, 0x1d, 0x2d, 0x42, 0x96, 0xc5, 0x79, 0x9f, 0x57, 0xf9, 0x6c, 0x9c, 0x4b, 0xac, 0x4d, 0x18, + 0xb5, 0xbc, 0x35, 0xe7, 0x2e, 0x9d, 0x53, 0x54, 0xa4, 0x93, 0x58, 0xb6, 0x99, 0xf2, 0x41, 0x65, 0x50, 0x42, 0x2c, + 0xe4, 0x17, 0xfc, 0x5c, 0x14, 0xa7, 0x0f, 0x0b, 0xbd, 0x49, 0xed, 0x2c, 0xba, 0x32, 0x39, 0x18, 0x47, 0xd7, 0xfc, + 0xf7, 0xca, 0x87, 0x80, 0x2e, 0x11, 0x4f, 0x0d, 0x70, 0xc9, 0xf8, 0x9a, 0x82, 0x3a, 0x85, 0xb4, 0x6e, 0xe2, 0x3b, + 0x8d, 0xb3, 0x2e, 0x2d, 0xbb, 0x44, 0x7d, 0xb3, 0x39, 0x4a, 0x18, 0x1f, 0x37, 0xe5, 0xdd, 0x4f, 0x6d, 0x05, 0x43, + 0xb4, 0x4e, 0xcf, 0x3e, 0xba, 0x54, 0x01, 0x69, 0x66, 0x06, 0x35, 0xbd, 0x35, 0x5e, 0xc8, 0x30, 0x76, 0x2b, 0xe3, + 0xb9, 0x19, 0x27, 0xe9, 0xfc, 0xf8, 0x29, 0x1f, 0xf0, 0xb9, 0x29, 0xd5, 0xe4, 0xf5, 0xcd, 0x5e, 0x07, 0xd4, 0x33, + 0xc3, 0xc3, 0x69, 0xac, 0x7d, 0x2b, 0xdd, 0x82, 0x2f, 0xf6, 0x70, 0x28, 0x9b, 0x76, 0xfb, 0xf4, 0xa5, 0x6f, 0x9b, + 0x71, 0x73, 0x64, 0x59, 0xd2, 0xe0, 0xf1, 0x31, 0x51, 0xb5, 0x7f, 0x24, 0x62, 0x1c, 0xa4, 0xd3, 0x98, 0xd1, 0x88, + 0xfe, 0x4a, 0x7c, 0xe6, 0xaa, 0x0c, 0x57, 0x16, 0xf3, 0xa0, 0x24, 0xde, 0x96, 0x7e, 0x36, 0xa6, 0x15, 0xc2, 0xef, + 0x31, 0x3d, 0xa9, 0xd7, 0x43, 0x88, 0xf3, 0x94, 0x8b, 0x57, 0x1c, 0x4e, 0xae, 0xab, 0x7f, 0x42, 0x17, 0x86, 0x27, + 0xad, 0x2a, 0x80, 0xff, 0xe2, 0x06, 0xa4, 0x3d, 0x41, 0x1f, 0x7c, 0x97, 0xce, 0xc1, 0x75, 0x56, 0x5d, 0xe6, 0xd4, + 0xd8, 0x79, 0x1a, 0xcd, 0xd6, 0x05, 0xac, 0x36, 0x0d, 0x69, 0x82, 0x86, 0xd1, 0xdb, 0xee, 0x7d, 0x63, 0xe3, 0x3c, + 0x1b, 0xfa, 0x2e, 0x96, 0x10, 0xcf, 0x1f, 0x03, 0xfc, 0x78, 0x85, 0x58, 0x18, 0xf6, 0xdb, 0xa5, 0x99, 0x04, 0xcd, + 0xb0, 0xc9, 0x58, 0x92, 0x3c, 0xb5, 0xe4, 0xbe, 0xc9, 0xd5, 0x42, 0x8e, 0xcb, 0xa9, 0x26, 0x25, 0xea, 0xdc, 0xb7, + 0x35, 0x23, 0x73, 0x2a, 0xad, 0x32, 0x62, 0x95, 0x1a, 0xe0, 0xbe, 0x78, 0x0d, 0x44, 0xb0, 0xbb, 0xe6, 0x66, 0xc7, + 0xca, 0x02, 0x83, 0x8f, 0x2e, 0x8b, 0x6b, 0xa9, 0x6b, 0xe0, 0x78, 0x6b, 0x1d, 0x78, 0x5c, 0x1e, 0xc0, 0x91, 0x62, + 0x2c, 0xde, 0x66, 0x63, 0x19, 0xff, 0x2d, 0x51, 0x0d, 0x1f, 0x85, 0xaf, 0xd7, 0xc6, 0x79, 0x99, 0xf1, 0xe1, 0xe4, + 0x28, 0x95, 0x8c, 0xcb, 0x8d, 0x1e, 0x62, 0x1c, 0x2e, 0x87, 0xad, 0x72, 0xa6, 0x64, 0x86, 0x75, 0x39, 0xe6, 0x67, + 0x5c, 0xe9, 0x79, 0x23, 0xa6, 0x12, 0xca, 0x4b, 0xfa, 0x4c, 0x3f, 0x71, 0x65, 0x9c, 0x8d, 0xa2, 0x7d, 0xab, 0xb9, + 0x5f, 0x10, 0x7d, 0x76, 0x60, 0xcc, 0xc1, 0xd5, 0x53, 0x72, 0x1d, 0xda, 0xaa, 0xec, 0xcb, 0xf7, 0x49, 0xbe, 0x92, + 0x3e, 0xff, 0x25, 0x1b, 0x36, 0x16, 0x55, 0x92, 0x7c, 0xd4, 0x29, 0x44, 0x0e, 0xd3, 0x8a, 0x76, 0x8c, 0x0e, 0x60, + 0x0b, 0x82, 0xfa, 0x46, 0x40, 0xb4, 0x01, 0xe8, 0xbe, 0x4a, 0x45, 0x0f, 0x0c, 0x34, 0x6f, 0x51, 0xb6, 0x9c, 0xf4, + 0xb5, 0xe9, 0xe0, 0xdf, 0xfd, 0x55, 0x33, 0x6f, 0x44, 0x2f, 0x0b, 0xae, 0x10, 0x3a, 0x6d, 0x05, 0x7f, 0x34, 0x2d, + 0x79, 0x76, 0x5c, 0xea, 0x62, 0x97, 0xb6, 0x70, 0xf0, 0x73, 0x35, 0x7d, 0xf8, 0xab, 0x24, 0xc7, 0xae, 0x8d, 0x1a, + 0xc1, 0x71, 0x9e, 0x73, 0xdc, 0xb8, 0x40, 0x44, 0xcb, 0x42, 0xdb, 0x43, 0xd5, 0x22, 0x0d, 0xa7, 0xbe, 0x33, 0x47, + 0xc3, 0x04, 0xb0, 0x57, 0xec, 0x5d, 0x18, 0x58, 0xff, 0x2c, 0x8b, 0xe8, 0xc0, 0x8e, 0x70, 0x52, 0x9a, 0xf8, 0xfa, + 0xf4, 0x1e, 0xc4, 0xa5, 0x78, 0xe8, 0x8c, 0x1a, 0x04, 0x73, 0x9a, 0x4f, 0x0f, 0x9a, 0xb1, 0x72, 0x5b, 0x0a, 0xed, + 0xf9, 0x48, 0x9d, 0x40, 0x16, 0xa8, 0x58, 0x4d, 0xb5, 0x6b, 0x19, 0x41, 0xeb, 0x26, 0xd9, 0x1b, 0x8f, 0x64, 0x33, + 0x56, 0x1c, 0xbe, 0x02, 0x72, 0x73, 0xe3, 0xc6, 0x1d, 0x38, 0x67, 0xd5, 0x0a, 0x83, 0x33, 0x42, 0x64, 0x68, 0xbe, + 0x55, 0x8d, 0xc4, 0xf4, 0x95, 0x80, 0x06, 0x1c, 0x2e, 0x52, 0x50, 0xbf, 0xdb, 0xea, 0xb4, 0x77, 0x4a, 0x8f, 0x54, + 0xa2, 0x62, 0x20, 0x2a, 0xa7, 0x74, 0xc3, 0xb8, 0x7a, 0x2e, 0xba, 0x80, 0x89, 0x4e, 0xb1, 0x51, 0xb0, 0x28, 0xa3, + 0x5b, 0x15, 0x82, 0xcb, 0xf5, 0x60, 0xf3, 0xfd, 0xd5, 0xe9, 0xc6, 0xa6, 0x7f, 0x7d, 0x49, 0x83, 0x28, 0x14, 0x37, + 0x3e, 0x60, 0x17, 0x9d, 0x74, 0xbe, 0x49, 0x87, 0x4c, 0x1b, 0x19, 0xa3, 0xbe, 0x74, 0x7c, 0x30, 0xf5, 0x77, 0xff, + 0x28, 0x8d, 0x02, 0xae, 0x71, 0xa7, 0xeb, 0x19, 0x6e, 0x99, 0x77, 0x38, 0x85, 0x52, 0xc8, 0x76, 0x19, 0x91, 0xd6, + 0x7f, 0xb6, 0xc8, 0x37, 0x32, 0xe8, 0x49, 0x5c, 0xdf, 0xca, 0x68, 0xb7, 0xe9, 0xcd, 0xba, 0x3b, 0xa7, 0xe9, 0x16, + 0x00, 0x1e, 0xff, 0xa5, 0xd6, 0x3b, 0x91, 0x33, 0x11, 0xaa, 0x55, 0x04, 0x9f, 0xa4, 0x4c, 0xd9, 0xb1, 0xb6, 0x83, + 0x3e, 0xcd, 0xb0, 0xfe, 0xdd, 0x93, 0x10, 0xc9, 0x9c, 0x35, 0xbc, 0x21, 0xa8, 0x63, 0x5f, 0xe1, 0xac, 0x3e, 0x8b, + 0xd4, 0x3f, 0xe2, 0xea, 0x17, 0xd4, 0xa8, 0xa2, 0xc8, 0xc5, 0xae, 0x29, 0x1a, 0x87, 0x2e, 0xdc, 0xe4, 0x36, 0xeb, + 0x5d, 0x52, 0x36, 0xc0, 0xe8, 0xbf, 0xdc, 0xa4, 0x18, 0x99, 0xbb, 0xee, 0x6c, 0xaa, 0xac, 0xad, 0x82, 0x91, 0x14, + 0x8b, 0x10, 0xca, 0x5c, 0x67, 0xba, 0x2e, 0xb1, 0x7e, 0x69, 0x93, 0x97, 0x9d, 0x1a, 0x06, 0x67, 0x79, 0xd1, 0x3d, + 0xef, 0xa4, 0x47, 0xa1, 0x65, 0x41, 0x53, 0x4d, 0x21, 0x3d, 0xf8, 0x16, 0x46, 0x2b, 0x8f, 0xa6, 0x05, 0xd3, 0xda, + 0xeb, 0xc4, 0x14, 0xd5, 0xce, 0x8b, 0x50, 0x67, 0x9f, 0x50, 0x36, 0x16, 0x6e, 0x2d, 0x35, 0xb1, 0x2b, 0x1d, 0x82, + 0x6d, 0x8a, 0x00, 0x26, 0x77, 0x26, 0x88, 0x78, 0x70, 0x0d, 0x4a, 0xf5, 0x84, 0xa2, 0xbe, 0x41, 0x9e, 0xe1, 0x2e, + 0xaa, 0xc6, 0xb7, 0xa0, 0x0f, 0xbd, 0x2d, 0x75, 0xc3, 0xf0, 0x6c, 0xf1, 0x50, 0xca, 0x9b, 0xb2, 0x58, 0xb0, 0x5d, + 0x85, 0xaf, 0x64, 0x95, 0xc8, 0xda, 0x8f, 0x76, 0x68, 0xab, 0x99, 0x88, 0xd2, 0x56, 0xf6, 0x2a, 0x5d, 0xfa, 0xf8, + 0x55, 0x1b, 0xcc, 0xee, 0xf5, 0xf9, 0xaa, 0x0d, 0x31, 0x3d, 0x45, 0xfa, 0x49, 0xd8, 0x76, 0x1d, 0x11, 0xf7, 0x7f, + 0x96, 0x2d, 0x12, 0x66, 0x48, 0x19, 0xab, 0xf9, 0xf1, 0x0a, 0x61, 0xb1, 0x91, 0x16, 0xfb, 0xf3, 0xbd, 0xb0, 0xe4, + 0x3f, 0xd7, 0xea, 0xa0, 0x02, 0x0f, 0xc7, 0x79, 0xeb, 0xbb, 0x1b, 0xdf, 0x6b, 0xfe, 0xaa, 0x5b, 0x45, 0x20, 0xe9, + 0xba, 0x22, 0x72, 0xfb, 0x89, 0x4c, 0xe0, 0x0c, 0xc1, 0xb6, 0x6d, 0x02, 0xd6, 0xde, 0xdf, 0x22, 0x29, 0xc0, 0xc8, + 0x6f, 0x23, 0xfc, 0x63, 0x63, 0x6e, 0xf8, 0xfe, 0x14, 0x73, 0x73, 0x29, 0x5d, 0x24, 0x4f, 0x4e, 0x61, 0xbb, 0x64, + 0x01, 0xc2, 0x11, 0x38, 0x78, 0x3f, 0x35, 0x31, 0xdf, 0x6c, 0x28, 0x86, 0xe5, 0xd5, 0x89, 0x43, 0x51, 0xcd, 0x4f, + 0x0d, 0x45, 0x94, 0x87, 0xe9, 0x44, 0x75, 0x35, 0x21, 0x60, 0x2c, 0x1c, 0x43, 0x92, 0xc0, 0x05, 0x36, 0x40, 0x9d, + 0x62, 0x16, 0x7d, 0x3d, 0x72, 0x2b, 0xc6, 0x23, 0xdb, 0xca, 0xcd, 0xd0, 0x3e, 0xe6, 0x4d, 0x1d, 0x19, 0xb8, 0x39, + 0x76, 0xc6, 0x0f, 0x77, 0x76, 0x54, 0x56, 0x7a, 0x96, 0x9c, 0xce, 0xcd, 0x95, 0x58, 0xae, 0xc9, 0xee, 0x63, 0xa2, + 0xdd, 0x7d, 0x7b, 0x47, 0x64, 0x81, 0x18, 0xfd, 0x67, 0x75, 0x26, 0x67, 0xfb, 0x8d, 0x9c, 0xc0, 0xb7, 0x14, 0xea, + 0x05, 0x17, 0x13, 0x2e, 0xf7, 0x86, 0x57, 0xda, 0x3f, 0xf0, 0xca, 0x84, 0xcb, 0xa6, 0x5c, 0x8d, 0x6c, 0x64, 0x96, + 0xa8, 0x09, 0xf7, 0xff, 0x57, 0xf6, 0x1a, 0x62, 0x0b, 0xf0, 0x62, 0xec, 0x5b, 0x1b, 0x45, 0xbb, 0x9b, 0x85, 0x0e, + 0x57, 0xd8, 0x87, 0x77, 0x9c, 0x9a, 0xb7, 0xdd, 0x0d, 0xcc, 0xf4, 0x83, 0x44, 0x56, 0xbe, 0x4b, 0x0a, 0xfd, 0x9f, + 0xb1, 0xc7, 0xbe, 0x77, 0x6b, 0xa2, 0xe7, 0xf4, 0x38, 0xf8, 0xe7, 0xb6, 0x34, 0xc2, 0x6f, 0x93, 0x3e, 0x3f, 0xeb, + 0xc6, 0x50, 0x75, 0xf0, 0x6a, 0x7e, 0xd7, 0x64, 0xa2, 0x7f, 0x7d, 0x26, 0xe7, 0x7d, 0xfa, 0x80, 0x08, 0x55, 0xf3, + 0x89, 0xef, 0x02, 0xab, 0x58, 0x37, 0xc6, 0x8e, 0x76, 0x45, 0x11, 0x27, 0x1f, 0xd6, 0xdb, 0x64, 0x1a, 0xb7, 0x8e, + 0xb8, 0x78, 0x5f, 0xc1, 0x9d, 0x92, 0x50, 0xdd, 0x6f, 0x97, 0xa0, 0xc7, 0xa1, 0x3f, 0xd6, 0x1f, 0x16, 0x9f, 0x1b, + 0xeb, 0x0e, 0x6f, 0x94, 0xbc, 0x37, 0x46, 0x5d, 0x34, 0x61, 0xd7, 0x2f, 0x3b, 0xef, 0x34, 0x16, 0xf7, 0x86, 0x63, + 0x25, 0xa1, 0x80, 0x16, 0xb4, 0x7c, 0x81, 0x5f, 0x8a, 0x3c, 0x19, 0x61, 0xc2, 0xc9, 0x53, 0xbc, 0x55, 0xdf, 0x8a, + 0x18, 0xbc, 0x6e, 0xf2, 0xd5, 0xfc, 0x64, 0x36, 0xfe, 0xb3, 0xf4, 0xe5, 0x65, 0x96, 0x9d, 0xaa, 0xd7, 0x9e, 0x01, + 0x8d, 0x49, 0x99, 0x1f, 0xf1, 0xfc, 0xb3, 0xc7, 0xf9, 0x20, 0xc9, 0x97, 0x10, 0x4f, 0x9f, 0xca, 0x12, 0x40, 0x01, + 0x56, 0x2e, 0xde, 0x99, 0xc5, 0xe5, 0x0b, 0x1e, 0x26, 0x22, 0x65, 0x20, 0x6d, 0x83, 0x42, 0x06, 0x8c, 0xef, 0x34, + 0x20, 0xbc, 0x47, 0x16, 0x53, 0xe1, 0xaa, 0xeb, 0xfb, 0x55, 0x64, 0x77, 0x4e, 0x60, 0xd4, 0x6e, 0xff, 0x24, 0x9b, + 0x02, 0x35, 0x71, 0x00, 0xa8, 0x2b, 0x26, 0xe2, 0xf1, 0x4f, 0x80, 0xfb, 0x97, 0xe4, 0x48, 0x15, 0x1d, 0x60, 0x64, + 0x7c, 0xf5, 0xc4, 0xaa, 0x17, 0xbf, 0x42, 0x39, 0xe8, 0xcf, 0x5b, 0xda, 0xc1, 0x65, 0xaf, 0x6c, 0x64, 0xab, 0x93, + 0x89, 0x02, 0x3b, 0x59, 0x1e, 0x97, 0x55, 0x84, 0x13, 0x50, 0xd2, 0xb9, 0xa3, 0x43, 0x47, 0xe6, 0x9f, 0x0b, 0x07, + 0xa7, 0xc4, 0xf9, 0xeb, 0x67, 0xc5, 0x0f, 0x17, 0xfa, 0xa7, 0x7c, 0xdc, 0x1b, 0x78, 0xf7, 0x39, 0xd1, 0x40, 0x7f, + 0x37, 0xa9, 0xa2, 0x1c, 0x00, 0x1d, 0x38, 0x28, 0xfc, 0xea, 0x74, 0x78, 0x6d, 0x09, 0x2d, 0x35, 0x0b, 0x4e, 0xcb, + 0x1f, 0xfe, 0x09, 0x72, 0xb6, 0x34, 0x4f, 0x5a, 0x02, 0x41, 0x36, 0xae, 0x4d, 0x5f, 0x44, 0x6b, 0x6f, 0x3a, 0x65, + 0x89, 0x00, 0x6b, 0xc9, 0x29, 0x10, 0x25, 0x72, 0xaf, 0xdb, 0xec, 0xcd, 0xd4, 0x09, 0x2c, 0x53, 0x5b, 0x05, 0x88, + 0x0a, 0x6b, 0x31, 0x60, 0x10, 0xf3, 0x1a, 0xff, 0xb9, 0x56, 0xf3, 0xe4, 0xed, 0x9b, 0x27, 0x07, 0x84, 0x91, 0x30, + 0x50, 0x7c, 0x14, 0x60, 0x38, 0x23, 0xf8, 0x4b, 0xbd, 0xbe, 0x36, 0x8b, 0x01, 0x3f, 0x94, 0x54, 0xbc, 0xd8, 0x9b, + 0x32, 0xbd, 0x80, 0x8b, 0x90, 0xfe, 0x81, 0xfc, 0xc3, 0xa3, 0x96, 0xbd, 0x3a, 0xeb, 0x3a, 0xb5, 0x1d, 0x77, 0x8b, + 0xbe, 0x32, 0x9a, 0x03, 0xaf, 0x18, 0x5c, 0x16, 0x7d, 0x0e, 0x2c, 0xef, 0xbf, 0x96, 0xf7, 0xac, 0x0c, 0x7c, 0xc2, + 0xc1, 0xaa, 0xaf, 0xcc, 0x8d, 0x43, 0x0d, 0x19, 0x0b, 0x49, 0x91, 0x16, 0x20, 0x7a, 0xed, 0xc7, 0xe9, 0x80, 0x86, + 0xbd, 0x3a, 0xdb, 0x61, 0x81, 0x46, 0x8c, 0x74, 0x2e, 0xb5, 0xbb, 0xee, 0xe9, 0x91, 0x31, 0x7e, 0xde, 0xbd, 0x17, + 0x16, 0x4e, 0x99, 0x6d, 0xa8, 0xcb, 0x9f, 0x8a, 0xbe, 0x94, 0x94, 0xc1, 0xb6, 0x62, 0xcb, 0x3b, 0x01, 0xcc, 0x83, + 0xc9, 0x77, 0x27, 0xcc, 0xdd, 0x07, 0x9a, 0xc3, 0xa8, 0x79, 0xa5, 0x8a, 0x7c, 0xe3, 0x8b, 0xa8, 0xc6, 0xff, 0x05, + 0x3d, 0x15, 0x91, 0x45, 0xbe, 0x08, 0x6c, 0x21, 0xac, 0xa7, 0x80, 0x7f, 0x17, 0x43, 0x1d, 0x7e, 0xe9, 0x5e, 0x30, + 0x6c, 0x3b, 0xd6, 0xa9, 0x66, 0xb3, 0x79, 0xf3, 0xbe, 0xb1, 0x8d, 0x20, 0x2d, 0xa3, 0x8d, 0x98, 0x40, 0xdb, 0x0f, + 0x31, 0xee, 0xdf, 0x48, 0xdd, 0x88, 0x98, 0x7e, 0x11, 0xce, 0xac, 0x1f, 0x44, 0xf3, 0x4d, 0xb6, 0xa0, 0xb9, 0xfe, + 0x21, 0x69, 0x9f, 0xbc, 0x43, 0x80, 0x05, 0xe4, 0xd1, 0x2b, 0xcb, 0x6c, 0xac, 0x09, 0x4a, 0x09, 0x57, 0x1e, 0x92, + 0x8c, 0xf2, 0x26, 0xaa, 0x4f, 0x68, 0x6f, 0x25, 0x19, 0xa0, 0x81, 0xeb, 0xd8, 0x05, 0x53, 0xc7, 0xdc, 0xa6, 0xcb, + 0x7d, 0x37, 0xa1, 0x3d, 0x08, 0xe5, 0xda, 0x0b, 0xf8, 0xc2, 0xcd, 0x73, 0x17, 0x1e, 0x27, 0xd0, 0x1a, 0xe4, 0x7f, + 0x1c, 0x0f, 0xc9, 0x47, 0x59, 0x0a, 0x89, 0x55, 0x16, 0x42, 0x84, 0xda, 0x85, 0xdd, 0xdc, 0x28, 0x76, 0x3d, 0x09, + 0xd8, 0x91, 0x00, 0x20, 0x38, 0x53, 0x30, 0x89, 0xb3, 0x29, 0x0d, 0x61, 0xce, 0x51, 0xc3, 0x69, 0xf9, 0x19, 0x57, + 0x63, 0xba, 0xc0, 0x66, 0xe0, 0x04, 0x08, 0xf4, 0x37, 0xf6, 0x85, 0x23, 0x4b, 0xcb, 0xc9, 0xfc, 0xec, 0xc0, 0x09, + 0x90, 0xc3, 0xca, 0xd3, 0x26, 0x61, 0x4b, 0x70, 0xee, 0x4d, 0x7a, 0x3f, 0x52, 0x88, 0x04, 0x45, 0x54, 0x39, 0x7e, + 0x81, 0xab, 0xa5, 0x25, 0xe5, 0xac, 0x44, 0x6b, 0x15, 0xca, 0x10, 0xad, 0x37, 0xbe, 0x98, 0x75, 0x7a, 0xff, 0xab, + 0x4d, 0xe7, 0xa5, 0xb1, 0x38, 0xc4, 0x10, 0x38, 0x62, 0xa9, 0xfd, 0x54, 0x66, 0xe3, 0x85, 0xb3, 0xcc, 0xee, 0x9b, + 0xb1, 0xfd, 0x9a, 0xa6, 0x9a, 0x89, 0x37, 0xe5, 0xb7, 0x6d, 0xf6, 0xb0, 0xe0, 0x2a, 0x27, 0x7a, 0x1a, 0x7f, 0xf4, + 0xe6, 0x6c, 0xa7, 0xbf, 0x86, 0x4d, 0x26, 0x47, 0xc5, 0xc7, 0x1e, 0x41, 0xce, 0x85, 0x2a, 0x15, 0x22, 0xae, 0xb7, + 0xc3, 0x52, 0xe5, 0x9e, 0xe4, 0x4a, 0xe7, 0x58, 0x82, 0x92, 0x6c, 0x77, 0xb1, 0x78, 0xa9, 0xa1, 0x20, 0x8e, 0xcd, + 0xdd, 0x97, 0x72, 0xc1, 0xbb, 0x6d, 0x48, 0x6b, 0xef, 0x5e, 0xbf, 0xf2, 0xef, 0xd3, 0xb1, 0x95, 0x6c, 0x76, 0x9c, + 0x99, 0xd5, 0xec, 0x38, 0x16, 0xe4, 0xfc, 0x9c, 0xcc, 0x8b, 0x24, 0xbc, 0x4d, 0xda, 0xbf, 0x9e, 0xda, 0x33, 0xd5, + 0x06, 0x8a, 0x68, 0x8a, 0x52, 0xf7, 0x6f, 0x52, 0x93, 0xb0, 0x91, 0xe0, 0x2f, 0xd4, 0x0d, 0x63, 0xdd, 0x87, 0x55, + 0xf3, 0x95, 0xb3, 0x62, 0x63, 0x1f, 0xe4, 0x66, 0x6a, 0x5c, 0x14, 0x7c, 0x24, 0xe4, 0x4a, 0x2b, 0xc3, 0x06, 0xc7, + 0x81, 0xa6, 0xfc, 0x81, 0x29, 0x7f, 0x98, 0x61, 0x4d, 0x5c, 0x74, 0xab, 0xef, 0x93, 0xeb, 0x9d, 0x00, 0x60, 0xa4, + 0x33, 0x07, 0x0b, 0xb5, 0x02, 0xdf, 0x1e, 0x87, 0x8b, 0x03, 0x5c, 0x4f, 0x3b, 0xa4, 0xe8, 0x24, 0x4c, 0x7e, 0x1b, + 0x9f, 0x6d, 0xf2, 0x97, 0x76, 0xbd, 0x97, 0xee, 0x36, 0x79, 0x71, 0xd8, 0xde, 0x0a, 0xbd, 0x61, 0x20, 0xa2, 0x52, + 0x05, 0x95, 0x84, 0x20, 0xec, 0xb4, 0x5b, 0x19, 0xb0, 0x2a, 0x2d, 0xb6, 0xc0, 0x8f, 0xcb, 0xfa, 0x78, 0x7c, 0x2d, + 0x1a, 0x53, 0xeb, 0xa8, 0x42, 0x8d, 0xcb, 0xf9, 0x34, 0x80, 0x27, 0x2a, 0x9e, 0x1b, 0x03, 0x7d, 0x46, 0x01, 0x68, + 0x50, 0x64, 0x41, 0x48, 0x2b, 0x0c, 0xc5, 0x96, 0x1b, 0x33, 0x15, 0x78, 0xba, 0xf0, 0x17, 0xb8, 0x14, 0x69, 0x8c, + 0x40, 0xe5, 0xb5, 0x86, 0x97, 0xe6, 0xeb, 0x84, 0xc4, 0x2f, 0x8d, 0xbf, 0x9e, 0xe4, 0x14, 0x32, 0x05, 0xf0, 0x23, + 0x89, 0xd7, 0xe5, 0x25, 0x47, 0xc0, 0x9b, 0x01, 0xf6, 0x0c, 0x10, 0x4e, 0xd6, 0xe9, 0xbc, 0x0d, 0x8f, 0x08, 0x27, + 0xe0, 0xc6, 0x50, 0xb9, 0x27, 0x57, 0x56, 0x90, 0xb1, 0xee, 0x48, 0x7b, 0xbc, 0x64, 0x6b, 0xd2, 0xd6, 0xd9, 0x09, + 0xb5, 0xce, 0xd2, 0x43, 0x95, 0xb8, 0x0f, 0x2a, 0xc9, 0xa5, 0xe7, 0x01, 0x0e, 0xd3, 0xbe, 0x7c, 0x9b, 0x62, 0xe8, + 0x3d, 0xc6, 0x4c, 0x86, 0x64, 0x6c, 0x95, 0x55, 0x9a, 0x5e, 0x40, 0x13, 0x60, 0xdb, 0x2e, 0xb8, 0x69, 0x72, 0x78, + 0x23, 0x72, 0x0f, 0xe8, 0x5c, 0x70, 0x45, 0xf6, 0x8e, 0xd2, 0x9d, 0xd9, 0x08, 0xda, 0x78, 0x57, 0xd6, 0x64, 0x97, + 0x4a, 0xe0, 0x9b, 0x18, 0xf2, 0x71, 0x96, 0x12, 0x34, 0x6e, 0x1b, 0xbb, 0x2c, 0x81, 0x3a, 0x93, 0xb9, 0x9a, 0x55, + 0x79, 0xd2, 0x0c, 0x53, 0x99, 0xa2, 0xaa, 0x96, 0x5c, 0x13, 0x64, 0x02, 0xc2, 0xe4, 0xad, 0xc9, 0x75, 0x7d, 0x66, + 0x00, 0x92, 0x1e, 0x24, 0x67, 0xc1, 0xd8, 0x71, 0x23, 0xbc, 0xb0, 0x57, 0x89, 0x2d, 0x8c, 0x4f, 0xad, 0x49, 0x4e, + 0x4e, 0xd2, 0x9f, 0x8c, 0x97, 0xad, 0xa6, 0xcd, 0x8e, 0x2d, 0x09, 0x4d, 0x8b, 0x43, 0x0b, 0xb6, 0x52, 0xb7, 0x73, + 0x75, 0xb0, 0x35, 0x6b, 0x59, 0xc0, 0x60, 0x1b, 0x8d, 0x75, 0xc7, 0xf8, 0x99, 0x63, 0x22, 0x3c, 0x58, 0x36, 0xa6, + 0x3b, 0xb1, 0xbc, 0x48, 0xcc, 0x31, 0xd0, 0x1a, 0xf3, 0xe6, 0x2f, 0x30, 0xc6, 0x4c, 0xf0, 0x4a, 0xe5, 0x62, 0x59, + 0xd4, 0x67, 0x2d, 0x44, 0x84, 0x66, 0x31, 0xbc, 0xda, 0x80, 0xe0, 0xf1, 0x1a, 0xd7, 0x1b, 0x70, 0x37, 0xb0, 0xc8, + 0xc2, 0x22, 0xc2, 0x62, 0xbb, 0x23, 0x3a, 0x2a, 0x96, 0xf2, 0xa3, 0x47, 0xcf, 0x33, 0x21, 0x4f, 0x6d, 0x8e, 0x25, + 0x19, 0xd2, 0x1e, 0xf1, 0xdb, 0xd7, 0x58, 0x2c, 0xea, 0x16, 0xc6, 0x06, 0x63, 0x69, 0xe2, 0x1f, 0xc4, 0x57, 0xdd, + 0xbe, 0x06, 0x32, 0xee, 0x19, 0x7c, 0x92, 0xd1, 0x33, 0x7a, 0x79, 0xab, 0x1b, 0x20, 0x35, 0x1e, 0x75, 0x12, 0x0d, + 0x3c, 0x29, 0x09, 0x73, 0x73, 0x35, 0xa5, 0x4a, 0x93, 0x8c, 0x42, 0xcf, 0xeb, 0xa0, 0xc1, 0x5c, 0x03, 0x9c, 0xc4, + 0x96, 0x91, 0xbe, 0x59, 0x28, 0xc8, 0xdb, 0xbd, 0xfc, 0x39, 0x1f, 0x96, 0x46, 0x26, 0x1d, 0xba, 0xa5, 0x3d, 0x27, + 0xa3, 0x24, 0x21, 0x64, 0x68, 0x43, 0x54, 0x6b, 0x93, 0xca, 0x5e, 0x84, 0x5a, 0x91, 0x0a, 0x82, 0x1f, 0x76, 0x3a, + 0x4a, 0x3a, 0x05, 0x3d, 0x9d, 0xb2, 0xfa, 0x74, 0xba, 0xcd, 0x13, 0xd3, 0x3b, 0x9b, 0x67, 0x23, 0x02, 0x25, 0x3a, + 0xcf, 0x4f, 0x0f, 0x85, 0x8b, 0xdc, 0x82, 0x88, 0x5a, 0x73, 0x78, 0xcb, 0x60, 0x70, 0x31, 0x61, 0x2e, 0x55, 0xaf, + 0xd2, 0xf0, 0x07, 0x4f, 0x7c, 0xd7, 0xd6, 0xc2, 0x46, 0xab, 0xf4, 0xa6, 0x2c, 0x56, 0x69, 0x9e, 0x89, 0xa9, 0x67, + 0x5c, 0x21, 0x70, 0xd5, 0x10, 0xa1, 0x09, 0xdf, 0xd4, 0xf7, 0x84, 0xb2, 0x20, 0x60, 0x0c, 0xa9, 0xce, 0x62, 0x0c, + 0x0c, 0x32, 0x26, 0xeb, 0xd8, 0x1f, 0x76, 0x36, 0x45, 0xe6, 0xb5, 0x6e, 0x68, 0x66, 0xca, 0x4f, 0x35, 0xb0, 0xa8, + 0x15, 0x4a, 0x46, 0xe2, 0xb9, 0x3c, 0x4a, 0x51, 0xee, 0x41, 0xd9, 0xd3, 0x80, 0x58, 0x69, 0x53, 0xa6, 0x13, 0x42, + 0x96, 0x3c, 0x88, 0x65, 0x72, 0x61, 0xca, 0x92, 0x9c, 0xc4, 0x90, 0x0d, 0x39, 0x1b, 0x30, 0x7d, 0x15, 0x5a, 0x24, + 0x5c, 0x9e, 0x12, 0x89, 0xa2, 0x69, 0x4e, 0x88, 0xc3, 0x56, 0xe4, 0xd4, 0x60, 0xb5, 0x77, 0x30, 0xcf, 0xf0, 0x19, + 0x27, 0x99, 0xf5, 0x48, 0x50, 0xf1, 0xc7, 0x38, 0x06, 0x0c, 0x38, 0xb7, 0x39, 0x19, 0xc8, 0xbb, 0x61, 0xf6, 0xe8, + 0x02, 0x4d, 0xf9, 0x56, 0x5a, 0x00, 0x7b, 0xb4, 0x20, 0x19, 0xa8, 0xb2, 0x60, 0x05, 0xd7, 0x8f, 0x42, 0x81, 0xa7, + 0xcc, 0x1c, 0xcd, 0xb1, 0x50, 0x9f, 0x10, 0x51, 0xa9, 0x5c, 0x20, 0x5a, 0x75, 0x8e, 0x0d, 0xc6, 0xac, 0xd6, 0xe8, + 0xc4, 0x05, 0xbc, 0x48, 0x8d, 0xbf, 0xad, 0xa1, 0xd3, 0x4e, 0x3c, 0x18, 0x06, 0xfd, 0xf7, 0x25, 0x49, 0xf7, 0xa3, + 0x7b, 0xbc, 0x1f, 0xf5, 0x13, 0x9d, 0x79, 0xdf, 0x53, 0x1f, 0x3f, 0xf3, 0xed, 0x92, 0x81, 0xfc, 0x07, 0x8e, 0x0d, + 0xbc, 0x7f, 0x8e, 0xfa, 0x00, 0xef, 0xbf, 0x3e, 0x19, 0x37, 0x9f, 0x5d, 0x40, 0xdb, 0x86, 0x0b, 0x34, 0xe3, 0xb1, + 0xe6, 0x49, 0x0d, 0x56, 0xa4, 0xa0, 0x56, 0xb0, 0x96, 0xe5, 0x35, 0xdc, 0xf5, 0xe9, 0x1c, 0xdc, 0x13, 0x9b, 0xcb, + 0x3c, 0xf9, 0xe8, 0x03, 0xf0, 0x21, 0x58, 0xfe, 0x54, 0xbe, 0xe6, 0x27, 0x1a, 0x9a, 0xee, 0x20, 0x63, 0xe8, 0xb5, + 0xc4, 0x64, 0xf3, 0xa9, 0xe0, 0x5f, 0x2b, 0xb8, 0x7d, 0x51, 0xbd, 0x85, 0xc2, 0x99, 0x08, 0x29, 0x7f, 0x6c, 0xb3, + 0x3f, 0xa4, 0x4c, 0x34, 0x0d, 0xee, 0xe4, 0x69, 0xf8, 0xa5, 0x4b, 0x0a, 0x93, 0x2f, 0xea, 0xff, 0xf5, 0xf3, 0xcf, + 0x47, 0x07, 0xc6, 0xc4, 0xe4, 0x1d, 0x94, 0xfb, 0x7f, 0xce, 0x46, 0xc8, 0x4e, 0xe9, 0x7c, 0xb3, 0xf5, 0x5f, 0x04, + 0xc3, 0xf2, 0xf0, 0xfa, 0xbc, 0xb5, 0xeb, 0x43, 0x7c, 0x6d, 0xa0, 0x8d, 0x6d, 0xdb, 0xa4, 0xa2, 0xa4, 0x3e, 0xb8, + 0x02, 0x4b, 0x8c, 0x0b, 0x9a, 0x55, 0xf0, 0x98, 0x02, 0xb3, 0xd6, 0xbd, 0x99, 0x04, 0x4f, 0x39, 0xa3, 0xfd, 0x87, + 0xa0, 0x9d, 0xdc, 0xf0, 0x68, 0xb8, 0x2c, 0x8f, 0x69, 0x1a, 0xeb, 0x10, 0xb2, 0x7b, 0xa7, 0x54, 0x5f, 0xcc, 0x8a, + 0x96, 0x4a, 0x6d, 0x11, 0x20, 0xd2, 0x78, 0x57, 0x13, 0xab, 0x7a, 0x08, 0xfd, 0xa7, 0xeb, 0x34, 0x69, 0x12, 0xc3, + 0x38, 0xd9, 0x4a, 0x5f, 0x03, 0xb4, 0x01, 0xbe, 0xe8, 0x2f, 0x58, 0x79, 0xee, 0x6e, 0xca, 0x23, 0xc6, 0xcd, 0x07, + 0xde, 0x1f, 0x1d, 0x32, 0x71, 0x71, 0x68, 0xec, 0xfc, 0xb2, 0x27, 0x0e, 0xbb, 0xb2, 0xf8, 0x6b, 0xa8, 0xa9, 0xe7, + 0x40, 0x1b, 0xe6, 0x6a, 0x70, 0xec, 0x3a, 0x3d, 0x77, 0xa1, 0x78, 0x60, 0x0d, 0x91, 0xf2, 0x4e, 0xf6, 0xd0, 0xcb, + 0xd1, 0x46, 0xdf, 0xba, 0x5c, 0xe6, 0x1f, 0xe2, 0x9b, 0x7f, 0x20, 0x72, 0x82, 0x76, 0x7a, 0x96, 0x28, 0x02, 0x40, + 0xa6, 0xa7, 0xf0, 0x17, 0x97, 0x50, 0x86, 0x87, 0x89, 0xce, 0x06, 0xb9, 0xb7, 0x77, 0x2e, 0xe8, 0x93, 0xfd, 0x9b, + 0xe9, 0x5c, 0xce, 0x33, 0x0c, 0x4c, 0x3b, 0x81, 0x0d, 0x94, 0x91, 0x71, 0xec, 0x52, 0xfc, 0x04, 0xa3, 0xcb, 0x10, + 0x25, 0xb7, 0xec, 0x88, 0x97, 0xda, 0xa8, 0xe4, 0x0e, 0xd9, 0x79, 0x9e, 0x3b, 0x39, 0x4c, 0x1c, 0x1b, 0xa9, 0xad, + 0x0f, 0x08, 0x08, 0xc7, 0xf2, 0x30, 0x24, 0x93, 0xf5, 0x94, 0xe6, 0xd0, 0x72, 0xa2, 0x69, 0x1e, 0xb9, 0x3d, 0x02, + 0x3a, 0x1a, 0x2d, 0xd3, 0x60, 0xb4, 0xca, 0x3e, 0xda, 0x4c, 0x7c, 0x21, 0xba, 0xc6, 0x02, 0x5b, 0x3b, 0xe4, 0x85, + 0x11, 0xef, 0x33, 0xa0, 0xe0, 0xdb, 0xb0, 0xf5, 0x3d, 0x5f, 0x7a, 0xb8, 0x9c, 0xb8, 0xc5, 0x3c, 0x28, 0x32, 0xe6, + 0x6b, 0xe3, 0x57, 0x98, 0x4c, 0x68, 0xaf, 0x84, 0x7d, 0x1d, 0x9c, 0xb2, 0x1e, 0x4c, 0x40, 0xe9, 0xb9, 0x18, 0xcd, + 0xbb, 0x74, 0x82, 0x34, 0x65, 0x0a, 0x36, 0xa6, 0x2f, 0x14, 0xa5, 0x51, 0x47, 0x57, 0x74, 0x7a, 0xd6, 0x41, 0x2a, + 0x3c, 0xf9, 0xd4, 0x76, 0x4f, 0xdd, 0x33, 0x3a, 0x21, 0x7f, 0xd4, 0xd2, 0x6b, 0x9a, 0x06, 0x46, 0x9f, 0x55, 0x5b, + 0x9f, 0x7d, 0xfe, 0x7e, 0x95, 0x64, 0xe3, 0x7e, 0x9d, 0x6c, 0xd8, 0x9c, 0xe5, 0x01, 0xf8, 0xe7, 0x29, 0xb8, 0xff, + 0x80, 0x2e, 0x20, 0x66, 0x71, 0xd3, 0xfe, 0x08, 0x43, 0xf9, 0xb6, 0xfa, 0x7a, 0xc1, 0x6d, 0x8d, 0xce, 0x0f, 0x7f, + 0x3e, 0x6a, 0x38, 0x18, 0xa2, 0x6f, 0xff, 0x27, 0x9b, 0x03, 0x04, 0x00, 0xe7, 0xef, 0xc3, 0xeb, 0xb0, 0x45, 0xf4, + 0x73, 0xdc, 0xcc, 0x1d, 0x53, 0xcd, 0xde, 0xad, 0x63, 0x7c, 0x55, 0xa6, 0x2b, 0xc9, 0x6b, 0xee, 0x92, 0xc6, 0x65, + 0xb4, 0x0d, 0xaf, 0xc9, 0x66, 0x0e, 0x6a, 0xbf, 0x25, 0x40, 0x73, 0x24, 0x7b, 0xa8, 0x75, 0xf3, 0xbf, 0x2c, 0xb6, + 0x98, 0xd2, 0xbb, 0x9d, 0x56, 0xae, 0x3e, 0xdc, 0x71, 0xb1, 0xf7, 0xd7, 0x21, 0xc0, 0xe8, 0xde, 0xce, 0xda, 0x32, + 0xf2, 0x72, 0xfb, 0x38, 0x3d, 0x5c, 0x19, 0xd5, 0xcb, 0x58, 0x7f, 0x54, 0x9b, 0x66, 0x53, 0x4b, 0x01, 0xb8, 0xd5, + 0x88, 0x22, 0x07, 0x55, 0xd9, 0x19, 0x91, 0x74, 0xb6, 0xae, 0xc6, 0xf0, 0xd7, 0x02, 0x8b, 0xcb, 0x0a, 0x21, 0x82, + 0x37, 0x06, 0xea, 0x5c, 0x85, 0x63, 0xef, 0xb4, 0xfe, 0xe8, 0xe3, 0x6c, 0xfb, 0x66, 0xfb, 0xf7, 0x35, 0xd2, 0xa5, + 0xaa, 0x99, 0x39, 0xb5, 0x9b, 0xbd, 0x65, 0xb4, 0x43, 0xbc, 0xb7, 0xad, 0x6c, 0x12, 0x4a, 0x24, 0x0e, 0x2c, 0x2c, + 0xa3, 0xac, 0xaa, 0x53, 0x04, 0x58, 0x3a, 0x4d, 0xc3, 0xb6, 0x67, 0x8e, 0x92, 0x42, 0xd1, 0x56, 0xa6, 0x30, 0xca, + 0xe3, 0xa9, 0x87, 0x1a, 0xd9, 0xdd, 0x33, 0xc1, 0xd7, 0x6c, 0x21, 0x11, 0x79, 0xbe, 0x66, 0x61, 0x3a, 0x02, 0xa8, + 0x4d, 0x79, 0x3f, 0xc1, 0x29, 0x2e, 0x31, 0xd8, 0x50, 0x52, 0x44, 0x5f, 0x6f, 0x09, 0xa1, 0xa0, 0xf8, 0x7a, 0x2a, + 0xf0, 0xf5, 0x84, 0xeb, 0xab, 0x48, 0x47, 0x70, 0x52, 0xa7, 0x49, 0xf2, 0x47, 0x43, 0xa0, 0xef, 0x37, 0xcd, 0x64, + 0x1b, 0x63, 0x47, 0x5f, 0xb7, 0xe0, 0xaf, 0x27, 0xeb, 0x29, 0x1b, 0x5c, 0x7a, 0xf8, 0x37, 0xd0, 0x63, 0x0f, 0x18, + 0x07, 0x6b, 0x67, 0x14, 0x47, 0xf1, 0x57, 0x6c, 0x51, 0x5e, 0xb4, 0x3e, 0xf2, 0xe7, 0x04, 0x60, 0x72, 0x37, 0x0f, + 0x88, 0x13, 0xcb, 0x74, 0xa8, 0x6b, 0x42, 0x64, 0x27, 0x0d, 0x39, 0x35, 0x1a, 0x5f, 0x11, 0x6d, 0x6b, 0x46, 0x52, + 0x5b, 0xf1, 0xe5, 0x51, 0x2a, 0x71, 0x6d, 0xd6, 0x2c, 0xd6, 0x4b, 0xb2, 0xa1, 0xf2, 0xa0, 0xd9, 0x60, 0x16, 0x96, + 0x1f, 0xd8, 0x92, 0x73, 0xaf, 0x49, 0x87, 0xc1, 0x4e, 0x79, 0xea, 0x64, 0xe0, 0x94, 0x5d, 0xcc, 0x8e, 0x34, 0x80, + 0xcf, 0x6b, 0x92, 0x00, 0x6a, 0x56, 0x82, 0x63, 0xdf, 0x5b, 0x0e, 0xba, 0x77, 0x0a, 0xf4, 0x1f, 0xdb, 0x21, 0x53, + 0x79, 0x71, 0x27, 0x87, 0x8e, 0x08, 0x72, 0x36, 0x64, 0x4e, 0xa0, 0x86, 0x37, 0xd9, 0xa6, 0xf5, 0xeb, 0xc3, 0x83, + 0xe3, 0xfb, 0x9c, 0x75, 0x74, 0x7b, 0x93, 0xb8, 0x88, 0xaa, 0xf1, 0x0b, 0x01, 0xd2, 0x99, 0x1a, 0x37, 0x72, 0x77, + 0x23, 0xb2, 0x20, 0xa2, 0x24, 0x39, 0x6b, 0x4f, 0x67, 0x46, 0x20, 0x9a, 0x99, 0xaa, 0x38, 0x2a, 0x62, 0x22, 0x6d, + 0x50, 0x82, 0x91, 0x42, 0xfd, 0x95, 0xbf, 0x07, 0x06, 0xe2, 0x8e, 0xef, 0xac, 0x17, 0x04, 0x1e, 0xb0, 0x3b, 0x59, + 0xf7, 0xb1, 0xb5, 0x4a, 0xd0, 0x23, 0x21, 0x73, 0x61, 0x04, 0xe2, 0xfe, 0x3d, 0xbf, 0xe3, 0xaf, 0xbf, 0x7e, 0xf6, + 0x6d, 0x57, 0x77, 0xfa, 0x1f, 0xaf, 0x1d, 0xfa, 0x6c, 0xc6, 0x1d, 0x98, 0xaf, 0x04, 0x1f, 0xc1, 0x31, 0xa9, 0x16, + 0xd0, 0x3f, 0x64, 0x76, 0x2d, 0x77, 0x05, 0x34, 0x9c, 0x5c, 0x3b, 0xc0, 0x23, 0x70, 0xa0, 0x25, 0x9f, 0xac, 0xeb, + 0x79, 0xbd, 0xda, 0xe6, 0x9d, 0xeb, 0x2f, 0xff, 0x5c, 0xd7, 0x3e, 0x74, 0x45, 0xc7, 0x60, 0x51, 0x90, 0xe5, 0xef, + 0x04, 0x16, 0xd9, 0x81, 0xbb, 0xdc, 0x7f, 0x5a, 0x46, 0x71, 0x01, 0x2d, 0xff, 0xc1, 0x83, 0xb7, 0xd3, 0x85, 0x37, + 0x3b, 0x3e, 0x74, 0xe5, 0x94, 0xe3, 0xa6, 0xee, 0xf9, 0x4a, 0xbd, 0xab, 0x8d, 0x30, 0xea, 0xc1, 0x08, 0x9c, 0xed, + 0xe2, 0x83, 0xc0, 0x88, 0x43, 0xf2, 0x98, 0xc1, 0x36, 0xb9, 0x4d, 0x19, 0x43, 0xa4, 0x85, 0xc8, 0xe6, 0x56, 0xa8, + 0xb0, 0xa0, 0x48, 0xf0, 0xc2, 0x00, 0x99, 0xba, 0x8d, 0x29, 0x43, 0x8b, 0xd3, 0x7c, 0x3f, 0x1c, 0xdb, 0x19, 0xda, + 0xa2, 0x07, 0x8c, 0x29, 0x73, 0x4c, 0xcd, 0x2c, 0x10, 0xea, 0xee, 0x36, 0xc0, 0x19, 0xbd, 0x2d, 0x7a, 0x29, 0xf3, + 0xbe, 0x3e, 0x3f, 0x7e, 0xb6, 0x0c, 0xbc, 0xa7, 0x49, 0x1d, 0x5b, 0x90, 0x76, 0x24, 0xa7, 0xa6, 0xf3, 0x76, 0xa0, + 0x74, 0x65, 0xe1, 0x3a, 0xf3, 0xd5, 0x49, 0xc0, 0x9a, 0x4c, 0x99, 0x4d, 0x31, 0xda, 0x52, 0x51, 0x49, 0x34, 0x74, + 0xef, 0x64, 0xb2, 0xb6, 0xb7, 0x07, 0x44, 0x21, 0x19, 0xd7, 0x1e, 0x02, 0x56, 0x11, 0xbd, 0x03, 0x38, 0x1d, 0x83, + 0x56, 0xe3, 0xa7, 0x46, 0x9c, 0x17, 0xc1, 0xc3, 0x52, 0xda, 0xfc, 0xe0, 0xe2, 0xfa, 0xb0, 0x36, 0x18, 0x2f, 0x1c, + 0x6e, 0x41, 0xc0, 0xba, 0xec, 0x4d, 0xee, 0xc6, 0x83, 0xfe, 0xd7, 0x9d, 0x85, 0xab, 0xf5, 0x41, 0x6e, 0x7d, 0x3e, + 0xdb, 0xd7, 0xec, 0x3e, 0xf6, 0x7c, 0xcc, 0x3d, 0xfb, 0x78, 0x9d, 0xaf, 0x84, 0xa2, 0xec, 0xd4, 0x40, 0x7b, 0x4c, + 0xc5, 0x62, 0x00, 0x0d, 0x66, 0xb2, 0x24, 0x02, 0x2a, 0x58, 0x9c, 0x7d, 0x4b, 0xa7, 0xea, 0x04, 0xcc, 0xd4, 0x9e, + 0x69, 0xc6, 0xe7, 0xc2, 0x33, 0x36, 0x5f, 0x2c, 0x5d, 0xa7, 0x56, 0x30, 0x45, 0xa7, 0xeb, 0x5f, 0x39, 0xfc, 0x18, + 0x43, 0xa0, 0xc1, 0x28, 0x57, 0xe3, 0xad, 0x2a, 0x80, 0xe0, 0xfd, 0x5d, 0xc4, 0x30, 0x01, 0x91, 0xc5, 0xa1, 0x9e, + 0xea, 0xfd, 0xd0, 0xe5, 0xfe, 0x31, 0x8f, 0xed, 0x8b, 0xf1, 0xec, 0x8d, 0x06, 0x59, 0x64, 0xce, 0x21, 0xe7, 0xbe, + 0x12, 0xf4, 0xe2, 0xfa, 0x2a, 0x3f, 0xec, 0xc7, 0x3f, 0x60, 0x4e, 0x0e, 0x6e, 0xee, 0x28, 0x68, 0xd6, 0x23, 0x44, + 0x52, 0xe4, 0x82, 0x0c, 0xfd, 0x39, 0x18, 0xc2, 0x1a, 0xa9, 0x0d, 0xa6, 0x15, 0x31, 0x1a, 0xff, 0x2e, 0x8c, 0x05, + 0xcb, 0xb4, 0xf7, 0xac, 0x36, 0x1f, 0x9c, 0xd3, 0xaa, 0xf3, 0x08, 0x49, 0xb9, 0xe6, 0x58, 0x60, 0xa4, 0xe9, 0xf4, + 0xa8, 0xd5, 0xaf, 0xbf, 0x4e, 0xee, 0x52, 0xef, 0x95, 0xb1, 0x7a, 0x11, 0x5d, 0x69, 0xc8, 0x11, 0x70, 0x31, 0xa3, + 0xac, 0x47, 0x9e, 0xfa, 0x8f, 0x7f, 0xfc, 0x4e, 0xd0, 0x6f, 0x46, 0xd7, 0x0b, 0xbd, 0x5b, 0xe8, 0x27, 0x4e, 0xb6, + 0x40, 0x3e, 0x6a, 0xdc, 0xdb, 0x25, 0x8c, 0xd3, 0x94, 0x02, 0xdf, 0x72, 0xb3, 0xd1, 0x2b, 0x06, 0xb0, 0x81, 0x4a, + 0x84, 0x45, 0x9c, 0x39, 0x0f, 0x69, 0x34, 0x1b, 0x43, 0x01, 0xec, 0x02, 0xd7, 0xff, 0x42, 0xf3, 0xfa, 0xb6, 0xa5, + 0x5b, 0x47, 0x15, 0x3a, 0x84, 0x37, 0xfa, 0x13, 0x30, 0x33, 0x41, 0xca, 0xd9, 0x42, 0x3a, 0x7e, 0x18, 0xad, 0x1e, + 0x3a, 0x8f, 0xc7, 0x19, 0x63, 0xa9, 0x66, 0xe2, 0x81, 0x5e, 0xe6, 0x72, 0x26, 0xcf, 0x25, 0x52, 0x81, 0x0a, 0x88, + 0x80, 0xa8, 0x19, 0x15, 0x2d, 0x93, 0xd3, 0xfa, 0x0d, 0xe1, 0x79, 0xc3, 0x1e, 0xb9, 0x08, 0x02, 0xfd, 0x09, 0xd2, + 0xfe, 0xdc, 0xfe, 0x0e, 0xab, 0x0b, 0x1e, 0x18, 0xcc, 0x62, 0x4f, 0x34, 0x8c, 0x9a, 0x10, 0x64, 0x6a, 0xad, 0x34, + 0xb4, 0x00, 0xad, 0x23, 0x21, 0xc7, 0x20, 0x20, 0x6b, 0x79, 0x4c, 0xfa, 0xeb, 0x96, 0xb5, 0xef, 0x8d, 0x8a, 0xe3, + 0x56, 0xb2, 0x6e, 0xcb, 0xba, 0xf1, 0x93, 0x29, 0xe3, 0xec, 0x05, 0x0c, 0x1e, 0x76, 0x4e, 0x3a, 0xe2, 0x30, 0xe2, + 0xff, 0x08, 0x8e, 0x4f, 0x7a, 0x51, 0xef, 0x0f, 0xfe, 0xb8, 0x94, 0xae, 0xf2, 0xab, 0x8e, 0xc7, 0xeb, 0x0b, 0xf3, + 0x9a, 0xe7, 0x47, 0xfc, 0xb2, 0xa1, 0x25, 0xde, 0xb3, 0xb0, 0x93, 0x3e, 0x28, 0x3b, 0x5b, 0x1b, 0x2a, 0xe3, 0x9f, + 0xe6, 0xf3, 0xa7, 0x9f, 0xae, 0x63, 0x87, 0x21, 0xce, 0x45, 0x01, 0x5f, 0x7b, 0xfe, 0x1c, 0x9f, 0x74, 0xb6, 0x69, + 0x22, 0x95, 0x1b, 0xd1, 0xb8, 0x31, 0x66, 0x6d, 0xfc, 0x68, 0xc8, 0x0a, 0xb3, 0xfe, 0x0d, 0x3a, 0x2a, 0x9f, 0x7c, + 0x7b, 0x27, 0x15, 0x82, 0xdb, 0xa6, 0xc4, 0xe8, 0x39, 0xd0, 0xe3, 0x76, 0x27, 0x69, 0xe9, 0xdf, 0xed, 0xa1, 0x8d, + 0xc8, 0xc3, 0x66, 0x76, 0x48, 0x40, 0x48, 0x70, 0xc0, 0xbb, 0x50, 0x34, 0xbd, 0x41, 0xa0, 0x8b, 0x30, 0x5b, 0x23, + 0xe7, 0x48, 0xc3, 0xc9, 0xb0, 0xdf, 0x15, 0xa7, 0xbf, 0xc9, 0xf1, 0x21, 0x72, 0x00, 0x7f, 0xdf, 0x2e, 0x44, 0x4c, + 0xbf, 0xdb, 0xf3, 0x24, 0x8f, 0x8d, 0x90, 0xbd, 0x0e, 0x62, 0xe3, 0x29, 0xc9, 0x1b, 0x69, 0x29, 0x42, 0x24, 0xbb, + 0x38, 0x11, 0xe6, 0xf2, 0x7e, 0x15, 0xf7, 0xf4, 0x15, 0x9d, 0x38, 0x73, 0x8c, 0x34, 0xba, 0x68, 0xe9, 0x84, 0xf5, + 0xf1, 0xbf, 0xcd, 0x1c, 0x0b, 0x14, 0x84, 0x3f, 0xb3, 0x06, 0xd2, 0x71, 0xb7, 0x02, 0x78, 0x5b, 0x9c, 0x20, 0xc3, + 0xd7, 0xb2, 0xd4, 0xb0, 0x9e, 0x39, 0xa0, 0x09, 0x37, 0x0a, 0xdc, 0x30, 0x9c, 0x43, 0xad, 0x60, 0x0d, 0x0e, 0x3c, + 0x22, 0xff, 0x67, 0x96, 0x93, 0xee, 0x1d, 0x26, 0xb6, 0x74, 0x48, 0x0b, 0x44, 0xc2, 0x22, 0xd6, 0xdd, 0xb3, 0x49, + 0x35, 0x7c, 0x44, 0xa1, 0xd7, 0x2a, 0xee, 0x66, 0x93, 0x3f, 0x98, 0xdf, 0xc7, 0xca, 0x9d, 0x93, 0x95, 0x36, 0x79, + 0x9f, 0xb1, 0x05, 0x9d, 0x3f, 0x1a, 0x05, 0xe8, 0x9e, 0xbb, 0xb4, 0xb2, 0x9e, 0x29, 0xc0, 0x26, 0x3a, 0x7c, 0xc7, + 0x30, 0x19, 0x0d, 0x72, 0x7e, 0x93, 0x79, 0x12, 0x27, 0x90, 0xc1, 0xd0, 0x4b, 0xd0, 0x32, 0x93, 0x47, 0xb8, 0x73, + 0x6b, 0x1f, 0x90, 0xef, 0xe2, 0x75, 0x48, 0xc9, 0x4b, 0x1a, 0x89, 0xdb, 0xbd, 0x64, 0x53, 0x68, 0x71, 0x85, 0x54, + 0x42, 0xc7, 0xd6, 0x92, 0x63, 0x0b, 0x56, 0x3b, 0xea, 0x0e, 0x0f, 0x77, 0x99, 0xc1, 0x56, 0x89, 0xfe, 0x47, 0xc7, + 0x4a, 0xe6, 0xe0, 0x31, 0xbd, 0x10, 0xb6, 0x26, 0xc6, 0x71, 0xd2, 0x74, 0x41, 0x02, 0x4f, 0x85, 0x18, 0x70, 0x3e, + 0x60, 0x67, 0x54, 0xf3, 0x38, 0xc7, 0xe3, 0x04, 0xfa, 0x4c, 0x40, 0x78, 0x76, 0xbd, 0xe1, 0x7e, 0x0e, 0x45, 0x8c, + 0xc4, 0x10, 0x8c, 0xc2, 0x8f, 0xba, 0xa4, 0x3b, 0x03, 0x0c, 0x50, 0x2e, 0x65, 0x76, 0x0c, 0x40, 0x4a, 0x4c, 0x49, + 0x99, 0xe8, 0x41, 0x44, 0xf2, 0x52, 0x08, 0xa0, 0x0e, 0x51, 0x3b, 0x0c, 0x7f, 0x65, 0x71, 0xe0, 0xc1, 0xe5, 0x2b, + 0x84, 0x40, 0xb6, 0xe4, 0x31, 0x0a, 0x41, 0x6e, 0x9d, 0x77, 0xd9, 0xf4, 0x2d, 0xf5, 0xb7, 0x9a, 0x44, 0xd7, 0x89, + 0xac, 0xee, 0xcd, 0x41, 0x78, 0x36, 0x17, 0x03, 0xb4, 0x4f, 0xf0, 0x50, 0x73, 0xde, 0x4c, 0x60, 0x7a, 0x48, 0xff, + 0xb6, 0x42, 0x2c, 0x96, 0x5f, 0xdb, 0x53, 0xf5, 0xe4, 0xef, 0x43, 0x53, 0x5d, 0x6a, 0x4f, 0xa7, 0xee, 0x77, 0x6d, + 0xc1, 0x91, 0x5a, 0x66, 0xe3, 0xca, 0xf8, 0x3c, 0xef, 0xe2, 0xfc, 0xb4, 0xb9, 0x92, 0xe8, 0xcc, 0xaf, 0xa1, 0x59, + 0x85, 0x1a, 0xf3, 0x19, 0xd8, 0xbc, 0x46, 0x34, 0x1b, 0x69, 0x18, 0x29, 0x9a, 0x67, 0x9f, 0x97, 0x2f, 0xa5, 0x03, + 0x95, 0x49, 0xf5, 0xcc, 0x2e, 0x95, 0x38, 0xdf, 0x0b, 0x4d, 0x4d, 0xb7, 0x4f, 0x77, 0x2c, 0x16, 0xef, 0x4b, 0x5a, + 0x27, 0x8f, 0xa8, 0x35, 0x06, 0xee, 0x10, 0x7a, 0x52, 0xa0, 0xb3, 0x71, 0xb8, 0x45, 0xe9, 0x2b, 0x33, 0xba, 0x2a, + 0x8b, 0xbb, 0x0c, 0x75, 0x03, 0xa1, 0x6e, 0xd5, 0x9d, 0x8c, 0x8c, 0x2a, 0xe8, 0x32, 0x3a, 0x28, 0x66, 0xa3, 0x9c, + 0x15, 0xae, 0x87, 0x0c, 0x1f, 0x06, 0x25, 0x1b, 0xe0, 0x8f, 0x27, 0xff, 0xcf, 0x2f, 0xc1, 0xa9, 0xa5, 0x87, 0x23, + 0x79, 0x0d, 0xd9, 0x31, 0x94, 0x36, 0x18, 0xa4, 0xce, 0x9b, 0x14, 0x29, 0x47, 0x4c, 0x2d, 0xf3, 0xc5, 0x72, 0x54, + 0xe1, 0x7d, 0x7a, 0x7b, 0x01, 0x0f, 0xcf, 0x34, 0x69, 0x61, 0xc4, 0x89, 0xf9, 0xb7, 0xce, 0x01, 0x36, 0xe2, 0xa3, + 0x93, 0x51, 0x50, 0x29, 0x6e, 0x78, 0x35, 0x6d, 0x83, 0x57, 0x0b, 0xfd, 0x61, 0x5e, 0x1f, 0x77, 0xd7, 0x9a, 0xf7, + 0xbe, 0x8a, 0x2a, 0xe4, 0xc4, 0xe0, 0xcd, 0x33, 0x5c, 0xf0, 0xb6, 0xc7, 0x10, 0xcf, 0xa4, 0x7c, 0x2c, 0x41, 0xe9, + 0xaa, 0xad, 0x5c, 0xf3, 0x5d, 0xcc, 0x05, 0x15, 0x88, 0xc9, 0xa2, 0x7a, 0x55, 0xe3, 0xf3, 0xf7, 0x5e, 0x7e, 0xe6, + 0x48, 0x2f, 0x49, 0x90, 0xfa, 0x76, 0xa1, 0xb5, 0x06, 0xd1, 0xde, 0x32, 0xa7, 0x63, 0x2c, 0x79, 0xf5, 0x40, 0xa6, + 0x18, 0xab, 0x8f, 0xcf, 0x6d, 0xbd, 0x59, 0xd3, 0x3d, 0x1d, 0xf6, 0xe8, 0xff, 0x64, 0xf3, 0xd8, 0xa6, 0xe1, 0xe2, + 0x15, 0x57, 0x39, 0x48, 0x71, 0x03, 0xc8, 0xc3, 0x79, 0x2c, 0xe8, 0xcd, 0x35, 0xaf, 0x63, 0xc6, 0xcd, 0x5e, 0x60, + 0x6a, 0x13, 0xd9, 0x77, 0x6f, 0x5e, 0xcf, 0xaa, 0xc4, 0x99, 0x95, 0x35, 0x96, 0xa2, 0x24, 0x83, 0x32, 0xaf, 0x03, + 0x88, 0x12, 0xca, 0xa5, 0x63, 0x57, 0x22, 0xb2, 0xed, 0x79, 0xb6, 0x78, 0x77, 0x1e, 0x1a, 0xc6, 0x7a, 0x89, 0x8f, + 0xe9, 0xd7, 0x0b, 0x8c, 0x5b, 0x6e, 0x81, 0x5e, 0x74, 0x1d, 0xdc, 0x11, 0x27, 0x25, 0x0c, 0x51, 0xa4, 0xf1, 0x2f, + 0x75, 0xa4, 0x5c, 0x34, 0x9b, 0x0b, 0x16, 0x26, 0x52, 0xca, 0x56, 0x9b, 0x04, 0x6b, 0xd2, 0x66, 0xed, 0x39, 0xb1, + 0x28, 0x59, 0x0e, 0x08, 0x65, 0x63, 0xc8, 0x5d, 0x2f, 0x01, 0x66, 0x7d, 0x9f, 0x97, 0x91, 0x5d, 0x61, 0x13, 0x4b, + 0xf9, 0x07, 0x2d, 0xc4, 0x6b, 0x20, 0x1d, 0xfd, 0x43, 0xea, 0xf0, 0xd5, 0x94, 0xd6, 0xd2, 0x84, 0xba, 0x0b, 0xe1, + 0xad, 0x52, 0x00, 0x25, 0xca, 0xb2, 0x3e, 0x95, 0x00, 0x06, 0x31, 0x29, 0xa8, 0x42, 0x2c, 0x97, 0x2b, 0x4c, 0xe7, + 0x45, 0xb4, 0xe2, 0x5e, 0x36, 0x58, 0x3e, 0x95, 0xf4, 0xe1, 0x68, 0xa2, 0x72, 0xab, 0xbb, 0xed, 0xa0, 0x38, 0x60, + 0x64, 0xae, 0x3f, 0xa7, 0xc9, 0x3b, 0xf7, 0xd7, 0x03, 0xfd, 0x89, 0x00, 0x7e, 0x4e, 0x1c, 0x07, 0x97, 0x4f, 0xbe, + 0x4b, 0x35, 0x5b, 0x5d, 0xdd, 0xe3, 0x8e, 0xed, 0xd7, 0xf2, 0x0b, 0xc4, 0xc2, 0x49, 0x48, 0x74, 0x97, 0x93, 0x26, + 0xd8, 0xbc, 0xbf, 0x86, 0x64, 0x93, 0x30, 0x8d, 0x17, 0x0e, 0xca, 0x9a, 0xcf, 0x97, 0xe1, 0xa2, 0xb5, 0x15, 0x4c, + 0xf4, 0x41, 0xba, 0x57, 0xde, 0xcc, 0xe9, 0xad, 0x66, 0xc7, 0xc4, 0x52, 0x44, 0x36, 0xd5, 0xed, 0x27, 0xa7, 0x7b, + 0x43, 0xcc, 0xc0, 0xbe, 0x6f, 0x24, 0x4e, 0x61, 0x61, 0x68, 0x56, 0x88, 0x32, 0xf8, 0xa2, 0xe4, 0x24, 0x93, 0xa9, + 0xa5, 0x16, 0xb7, 0x95, 0x02, 0xca, 0x5e, 0xf8, 0x8f, 0x92, 0xba, 0x19, 0xed, 0xa7, 0xe4, 0x4a, 0xf7, 0x4d, 0x2a, + 0x0a, 0x78, 0x63, 0xd6, 0x78, 0x6f, 0x68, 0x6c, 0xf1, 0x15, 0x2d, 0x1e, 0x07, 0x83, 0x22, 0x19, 0x88, 0x85, 0x25, + 0x4d, 0x3a, 0xe5, 0xc3, 0x28, 0x26, 0xdc, 0x24, 0x5a, 0x10, 0x32, 0xf9, 0xee, 0x1d, 0xf6, 0x8e, 0x4d, 0xfb, 0x00, + 0x0a, 0xd3, 0x51, 0x75, 0xba, 0xf2, 0x8a, 0x76, 0xf0, 0xeb, 0x2c, 0x59, 0x22, 0xe1, 0x49, 0x5d, 0xee, 0x30, 0xd6, + 0x20, 0xa4, 0x45, 0xac, 0xca, 0x99, 0x0a, 0x6a, 0xcf, 0xf4, 0xb6, 0x76, 0x02, 0xef, 0x56, 0x4d, 0xcb, 0xb3, 0x72, + 0x80, 0x1b, 0xe7, 0x9b, 0x32, 0x18, 0x15, 0x89, 0x7a, 0x10, 0x28, 0x13, 0x94, 0x44, 0xa7, 0xd1, 0xa1, 0xf2, 0x4c, + 0x1c, 0xc4, 0x6e, 0x03, 0xfa, 0xbd, 0x66, 0x0e, 0x8e, 0x46, 0xf7, 0xf9, 0x2f, 0xfc, 0xc1, 0x3f, 0x71, 0x95, 0x7e, + 0xd1, 0x89, 0xca, 0x4a, 0x8b, 0xa4, 0x9a, 0x02, 0xf8, 0x7b, 0xdc, 0xf9, 0xf8, 0x7e, 0xa8, 0x30, 0xba, 0xb0, 0x73, + 0xf1, 0x4e, 0x97, 0xf9, 0xf3, 0x33, 0xf8, 0xf5, 0xf9, 0x79, 0xcd, 0x93, 0x2e, 0x3a, 0xda, 0x0e, 0x5d, 0x86, 0x8d, + 0x88, 0x12, 0x18, 0xaf, 0xae, 0x51, 0x48, 0x09, 0xb2, 0xfc, 0x00, 0x06, 0x1f, 0xdf, 0x1a, 0xe5, 0x3e, 0x95, 0x55, + 0xa0, 0x10, 0xf2, 0x56, 0xe9, 0xb0, 0x16, 0x71, 0x3e, 0xb0, 0x59, 0x54, 0x41, 0xef, 0x77, 0xe8, 0x52, 0xff, 0x54, + 0xf8, 0xf2, 0x6a, 0x3e, 0xf0, 0x0d, 0x4e, 0x27, 0xf0, 0xb0, 0xdc, 0x79, 0x2f, 0x83, 0x71, 0xc8, 0x59, 0x3f, 0x32, + 0xba, 0x77, 0x8a, 0xd9, 0x28, 0x33, 0xed, 0x78, 0x10, 0xb9, 0x74, 0xea, 0x3a, 0xc6, 0x33, 0x37, 0xca, 0x01, 0x8f, + 0xae, 0x37, 0x77, 0x98, 0x34, 0x52, 0xb4, 0xaa, 0x49, 0x5f, 0x6f, 0x89, 0x76, 0x43, 0xf3, 0xe1, 0x78, 0x80, 0x2c, + 0xa4, 0x3b, 0x8c, 0x9c, 0x39, 0xf4, 0xda, 0xd0, 0x3e, 0xcb, 0x20, 0xc3, 0x8d, 0xf3, 0x71, 0xaf, 0x9e, 0xc2, 0x29, + 0xc4, 0x71, 0x3e, 0x52, 0xa9, 0x4c, 0xe6, 0x8b, 0xcd, 0xd7, 0x00, 0x62, 0x33, 0x3d, 0x52, 0x93, 0xab, 0xa6, 0xef, + 0x4c, 0x09, 0xa2, 0x14, 0x09, 0xbf, 0xcd, 0x1c, 0xaf, 0xb8, 0x00, 0x9e, 0x19, 0xb0, 0x09, 0xad, 0x73, 0xe3, 0xaa, + 0xc0, 0xc3, 0xb4, 0xb8, 0x3f, 0x8a, 0xff, 0x09, 0xea, 0x89, 0x2e, 0xbf, 0xc1, 0x8c, 0x8d, 0x37, 0x8b, 0x57, 0xff, + 0x7a, 0x99, 0xfb, 0xfc, 0x15, 0x3f, 0x4a, 0xd6, 0xeb, 0xfb, 0xf1, 0x4d, 0xab, 0x87, 0xad, 0xe0, 0x1b, 0x85, 0x03, + 0x6c, 0x7a, 0x34, 0x3f, 0xee, 0x74, 0x0b, 0xfa, 0xb3, 0x3a, 0x20, 0xf4, 0xdd, 0x39, 0x9b, 0xf0, 0xe5, 0xe2, 0x42, + 0x9b, 0xd1, 0x5c, 0x5e, 0x06, 0xc5, 0xbe, 0x46, 0x50, 0x43, 0x4e, 0x6b, 0x46, 0x36, 0xce, 0x27, 0x53, 0xb8, 0x5c, + 0x26, 0x99, 0xdb, 0x2d, 0xd0, 0x17, 0xa0, 0x2b, 0xe5, 0xab, 0x75, 0x65, 0x43, 0xcd, 0xfc, 0x60, 0x86, 0xaf, 0xf6, + 0xe3, 0x33, 0x34, 0x5e, 0x5a, 0xda, 0xab, 0xf9, 0x18, 0x49, 0x5b, 0xaf, 0xba, 0x8e, 0xd0, 0x93, 0xad, 0xa2, 0xc6, + 0x93, 0xc2, 0x77, 0x2d, 0x06, 0xf4, 0x0e, 0x2c, 0xcf, 0x49, 0xed, 0xa6, 0x69, 0xb7, 0x18, 0x0d, 0xdb, 0x33, 0xaa, + 0xa1, 0x60, 0x3b, 0x77, 0xfd, 0xcc, 0x1e, 0x66, 0x73, 0x77, 0x35, 0xb7, 0xfc, 0x2a, 0xc0, 0x66, 0x1a, 0x28, 0xc7, + 0x61, 0x6d, 0x4d, 0xa7, 0x28, 0x89, 0x61, 0x50, 0x9a, 0x2a, 0x0a, 0x40, 0x05, 0xf0, 0x97, 0x69, 0x76, 0xe4, 0xa4, + 0xff, 0x7e, 0xbc, 0xe1, 0xf6, 0xc6, 0xc4, 0x62, 0x14, 0x3d, 0xff, 0x68, 0x46, 0xe0, 0xf4, 0x73, 0x8b, 0xee, 0x78, + 0xa8, 0xb8, 0x7d, 0x05, 0xcd, 0xeb, 0x9f, 0xe1, 0xcb, 0x67, 0xfe, 0xe5, 0x94, 0x97, 0x3f, 0xfd, 0xa7, 0x3c, 0x85, + 0x8f, 0xcf, 0x58, 0x30, 0x53, 0x88, 0xb9, 0x6c, 0x7a, 0xf0, 0xb2, 0x40, 0x17, 0x7a, 0x6b, 0x49, 0x35, 0x46, 0xd6, + 0x9b, 0xb5, 0x6e, 0x74, 0x9c, 0xb1, 0x59, 0xf1, 0x7b, 0xe2, 0xf6, 0x71, 0xa3, 0xb5, 0x2e, 0x27, 0x33, 0x58, 0xfe, + 0x0f, 0xe4, 0x10, 0x31, 0xd8, 0x3c, 0x7e, 0xf0, 0x03, 0xae, 0x1d, 0xfd, 0x7d, 0x1f, 0x96, 0xda, 0x4b, 0xe8, 0xe9, + 0xb9, 0xb8, 0x7c, 0x3d, 0x67, 0xcb, 0xf5, 0x9c, 0x2f, 0x97, 0x73, 0x79, 0x79, 0x39, 0x57, 0x1e, 0x5e, 0xed, 0xfb, + 0xd1, 0x82, 0x22, 0xad, 0x59, 0xe9, 0x81, 0x73, 0xc8, 0x75, 0xcb, 0x2c, 0x77, 0xb6, 0x52, 0xcf, 0xf8, 0x57, 0xb0, + 0xed, 0xbf, 0x34, 0x79, 0x70, 0xa3, 0x38, 0x93, 0x65, 0x5e, 0x6f, 0x58, 0x57, 0x3b, 0x34, 0x6b, 0x09, 0x96, 0x24, + 0xce, 0x1b, 0xaf, 0x7b, 0x97, 0x5d, 0x1a, 0x0e, 0x1b, 0xbd, 0x79, 0x36, 0x82, 0xd7, 0xc6, 0x08, 0xf9, 0xfd, 0x39, + 0x3b, 0x44, 0x0b, 0x24, 0x15, 0x6a, 0x46, 0xed, 0x70, 0x66, 0x8a, 0x34, 0x59, 0x12, 0x44, 0xcb, 0x9e, 0x7c, 0x9e, + 0x4e, 0x5e, 0x33, 0xde, 0x1d, 0xe9, 0xa0, 0x71, 0x1d, 0x46, 0x8c, 0xb2, 0xba, 0x9b, 0x4b, 0x0a, 0xbc, 0x0f, 0xec, + 0x54, 0xce, 0x9d, 0x70, 0x35, 0x45, 0x8f, 0x6f, 0x5a, 0x4e, 0x0b, 0x5a, 0x6f, 0xc6, 0xf2, 0x74, 0xa4, 0x1b, 0xe0, + 0x25, 0x75, 0xe7, 0xe5, 0x2e, 0xf8, 0x95, 0x51, 0x69, 0x23, 0x75, 0x4e, 0x75, 0x6e, 0xda, 0x94, 0x54, 0x5e, 0xfd, + 0xa6, 0x30, 0x88, 0xe1, 0x35, 0x9f, 0x55, 0xc2, 0x4b, 0xb3, 0x17, 0x4d, 0x0d, 0x2a, 0x0d, 0xa1, 0xb2, 0x2f, 0xd4, + 0x4e, 0x9c, 0xe5, 0x23, 0xa2, 0x92, 0xc8, 0xc7, 0x0f, 0xea, 0x6b, 0x25, 0x14, 0x6c, 0x83, 0x5c, 0x60, 0xe2, 0xb2, + 0x61, 0x89, 0xff, 0x7b, 0x33, 0x94, 0x64, 0xb3, 0x7f, 0x3f, 0x2f, 0x37, 0x83, 0x67, 0x25, 0xf4, 0x9d, 0xa6, 0x56, + 0xe8, 0x63, 0x05, 0xc1, 0x2f, 0x1b, 0xa2, 0x5d, 0x46, 0xb0, 0xc3, 0x9c, 0x40, 0x09, 0x08, 0xa9, 0xbf, 0x60, 0x71, + 0xb6, 0x6d, 0x0d, 0x90, 0x81, 0x15, 0x99, 0xf0, 0x9a, 0xc9, 0xd1, 0x56, 0x9d, 0x8f, 0x23, 0x83, 0xeb, 0x1b, 0xd9, + 0xb7, 0xf9, 0x75, 0xb5, 0x32, 0xd1, 0xf7, 0x57, 0xab, 0xd9, 0x04, 0x18, 0xf9, 0x7b, 0xf6, 0x27, 0x73, 0x5b, 0x97, + 0xfe, 0xe7, 0x4b, 0x20, 0x85, 0x8d, 0x68, 0x7a, 0x57, 0x1e, 0x81, 0x7b, 0x3b, 0xe1, 0xbd, 0xfd, 0x8e, 0x0c, 0xd6, + 0xde, 0x5b, 0xcc, 0xc9, 0xa2, 0xdc, 0x9f, 0x34, 0xf1, 0x06, 0xa4, 0xc1, 0xf9, 0x4b, 0x82, 0x6d, 0xb7, 0x86, 0xd6, + 0xed, 0x6b, 0x38, 0xdb, 0x3f, 0x72, 0x2d, 0x82, 0x06, 0x81, 0xee, 0x28, 0x2a, 0x41, 0x73, 0xc0, 0x1b, 0x39, 0x3b, + 0xe2, 0xe5, 0x44, 0x54, 0x13, 0x93, 0xa0, 0x87, 0x79, 0x43, 0xd6, 0xf9, 0xf7, 0x90, 0xbc, 0x17, 0x12, 0x10, 0xb1, + 0xbf, 0x52, 0x31, 0x0a, 0x70, 0x41, 0x7b, 0x69, 0x77, 0xb4, 0xa1, 0x06, 0xe1, 0x22, 0x07, 0xfe, 0x3d, 0xc1, 0xd8, + 0x5d, 0x7e, 0xc6, 0xe4, 0x38, 0xe7, 0x83, 0x40, 0xf2, 0xb6, 0x7f, 0x76, 0x61, 0xf1, 0xe4, 0xcf, 0x89, 0xfc, 0xd3, + 0x23, 0xf0, 0xc1, 0xfd, 0x13, 0x3c, 0xec, 0xab, 0x2b, 0xf2, 0x62, 0xe7, 0x43, 0xd3, 0x91, 0x42, 0x6d, 0x1b, 0xf6, + 0xe1, 0x6a, 0x94, 0xb7, 0xea, 0xe2, 0xd0, 0xeb, 0xad, 0x11, 0x79, 0xfd, 0xb9, 0x18, 0xe0, 0xdd, 0xbe, 0xc4, 0xfa, + 0x6a, 0xfa, 0x01, 0x8d, 0xdd, 0x37, 0x9c, 0xd7, 0xda, 0x57, 0x48, 0xf2, 0x24, 0xbb, 0x0b, 0x4b, 0x8a, 0x20, 0x4b, + 0xf9, 0xcc, 0x5e, 0x32, 0x04, 0x87, 0xca, 0xb3, 0x7d, 0x35, 0xb3, 0x3a, 0x33, 0x16, 0xe9, 0x70, 0x2d, 0x15, 0x00, + 0x6b, 0x7b, 0x67, 0x41, 0x0f, 0x05, 0x8a, 0x17, 0xe1, 0x13, 0x96, 0x2c, 0x7e, 0x66, 0xd6, 0x95, 0x75, 0xee, 0x4a, + 0xca, 0x83, 0xe5, 0xfa, 0x04, 0x4c, 0x4c, 0xcc, 0x5f, 0xe9, 0xa4, 0x75, 0xcd, 0xf7, 0x78, 0x32, 0x8a, 0x44, 0x0f, + 0x10, 0xcb, 0x9d, 0x06, 0x02, 0xdb, 0x96, 0xd8, 0xb8, 0x77, 0xf1, 0x34, 0x50, 0xff, 0xb4, 0xa3, 0x6d, 0x53, 0xfd, + 0xc9, 0xe6, 0x9f, 0x3e, 0xab, 0xc4, 0x08, 0xae, 0xbf, 0x25, 0x64, 0x87, 0xd7, 0x12, 0x83, 0x7e, 0xb8, 0x97, 0x93, + 0xf8, 0x63, 0xe4, 0x6c, 0xc9, 0xd9, 0x2e, 0xaa, 0xc9, 0xa5, 0xc6, 0xe3, 0x4e, 0x54, 0x75, 0xaa, 0x0a, 0x87, 0x56, + 0x24, 0xc8, 0x71, 0x9f, 0x91, 0xc7, 0x0c, 0xed, 0xac, 0x34, 0x0d, 0x22, 0xaa, 0x21, 0x84, 0xc7, 0xb3, 0x82, 0x81, + 0xa8, 0x0c, 0x5b, 0x32, 0x29, 0x0e, 0x14, 0xe7, 0xa6, 0x7c, 0x90, 0x41, 0xf3, 0xf7, 0xd1, 0xf3, 0x86, 0xcc, 0xc0, + 0xbc, 0xf1, 0x79, 0x3c, 0x18, 0x7c, 0x69, 0x1a, 0x05, 0x09, 0x1f, 0xe4, 0x40, 0x5d, 0x3b, 0x9f, 0x96, 0xff, 0x0c, + 0xbf, 0x7c, 0xd4, 0xbf, 0x72, 0xca, 0xe7, 0x9f, 0xfe, 0xd3, 0x3c, 0x39, 0x59, 0xc1, 0xf5, 0x59, 0x10, 0x3d, 0xb5, + 0x1c, 0xac, 0xfa, 0xf4, 0x97, 0x24, 0x7d, 0xec, 0xac, 0x88, 0x62, 0x7b, 0x47, 0x28, 0x11, 0xff, 0xc8, 0x39, 0xe2, + 0xb5, 0xc9, 0x1e, 0x6e, 0x6f, 0xf2, 0x84, 0x2b, 0x7f, 0x99, 0xa6, 0x3e, 0x7a, 0x80, 0x2b, 0xa2, 0xe5, 0xc6, 0x58, + 0x95, 0x26, 0x9f, 0x6c, 0x96, 0x9e, 0x44, 0x97, 0x56, 0x46, 0x1a, 0x0a, 0x6d, 0xb8, 0x0a, 0xae, 0xdd, 0x5b, 0x81, + 0xf8, 0xf6, 0xbe, 0x60, 0xbd, 0xaf, 0xe7, 0x7d, 0x90, 0x39, 0x1b, 0xa2, 0xb4, 0x64, 0x36, 0x44, 0xc7, 0xbd, 0xa0, + 0x67, 0x12, 0xa2, 0x38, 0xe8, 0x36, 0xcf, 0x24, 0xe6, 0xd0, 0xf1, 0xce, 0x08, 0x99, 0xfb, 0xea, 0x45, 0x7f, 0x53, + 0x7a, 0x41, 0x24, 0xbb, 0x81, 0x03, 0xe9, 0xc0, 0x0b, 0xe0, 0x4c, 0x2c, 0x5d, 0xba, 0x22, 0xdb, 0xdb, 0x52, 0x09, + 0xb9, 0x9c, 0xcf, 0xd4, 0x4a, 0x24, 0xcd, 0x84, 0xfb, 0xce, 0x17, 0xc6, 0xbc, 0x6a, 0x5a, 0x20, 0x5b, 0x1f, 0xbe, + 0x0c, 0x0c, 0x23, 0xa9, 0xa9, 0x3a, 0x9b, 0xed, 0x80, 0xa9, 0x2d, 0x61, 0x4a, 0xbb, 0x63, 0x34, 0x20, 0x48, 0x10, + 0x69, 0x94, 0xd1, 0xa1, 0xa0, 0xc7, 0xb0, 0x43, 0x82, 0x4e, 0x9c, 0xab, 0x14, 0x9d, 0xa0, 0xc4, 0x95, 0x17, 0x52, + 0xf4, 0xe8, 0xcd, 0x66, 0x31, 0x39, 0x71, 0x36, 0x7c, 0xa1, 0xef, 0x7a, 0xf2, 0x7a, 0x1a, 0xa4, 0xec, 0x5c, 0xe0, + 0x5e, 0xa3, 0x8b, 0x8b, 0x09, 0xf5, 0xe4, 0x78, 0x68, 0x6e, 0x98, 0xff, 0x2e, 0xc9, 0xa0, 0x0e, 0x5d, 0xc3, 0x4e, + 0xb8, 0x31, 0xa2, 0xf6, 0xba, 0x4e, 0x51, 0x6b, 0x9d, 0xd9, 0xba, 0xfb, 0xe1, 0x7c, 0x5a, 0x61, 0x52, 0xf9, 0x44, + 0x61, 0xa2, 0x49, 0xba, 0xd4, 0xa2, 0x4e, 0xd2, 0x64, 0x87, 0x12, 0x73, 0x1a, 0xa6, 0x8d, 0xd6, 0xb1, 0xd8, 0xd6, + 0x35, 0xb8, 0x90, 0xde, 0x84, 0xec, 0x4e, 0x6a, 0x82, 0x13, 0x4d, 0xfb, 0xff, 0x8f, 0xb7, 0x05, 0xad, 0x0c, 0x54, + 0x90, 0xea, 0xb9, 0x6d, 0x54, 0x17, 0xf0, 0x34, 0x73, 0x6f, 0x9b, 0x58, 0xd3, 0xbc, 0x9b, 0xc3, 0x09, 0x48, 0xc9, + 0xbd, 0x7a, 0x4b, 0xa4, 0x18, 0xc9, 0x08, 0x32, 0x88, 0xb5, 0x8b, 0x1a, 0x50, 0xc0, 0xa2, 0xba, 0x8e, 0x9c, 0x3e, + 0x84, 0x0b, 0x72, 0x63, 0xf4, 0x35, 0xb8, 0x3b, 0x70, 0x35, 0x72, 0xaa, 0x1b, 0x3c, 0xcd, 0x8d, 0x25, 0x77, 0x3b, + 0x79, 0xaa, 0x5f, 0x14, 0xe9, 0xdd, 0x2a, 0x8b, 0xd2, 0xca, 0xe1, 0x0f, 0x4d, 0xc5, 0x1f, 0xfb, 0x4a, 0x00, 0x44, + 0x77, 0xc6, 0xf6, 0xfb, 0x72, 0xe0, 0x49, 0xe7, 0xd8, 0x7a, 0x1f, 0x19, 0x70, 0x9b, 0x6b, 0xcb, 0xd6, 0xf6, 0xa9, + 0x0c, 0xc8, 0x3d, 0xe3, 0xb1, 0x74, 0x23, 0x33, 0xff, 0xfc, 0x54, 0x2e, 0xf4, 0xda, 0xa1, 0x5e, 0x80, 0x20, 0x73, + 0x9a, 0x5f, 0xd8, 0xdc, 0x3a, 0x25, 0xed, 0x28, 0x1f, 0xa8, 0x59, 0xe1, 0xe3, 0x48, 0x33, 0x3e, 0x45, 0x4d, 0x14, + 0x82, 0x9f, 0xc1, 0x17, 0x13, 0x1e, 0xf8, 0x9e, 0xef, 0xc5, 0xa8, 0xa1, 0xf7, 0xa2, 0x3f, 0x7b, 0x1a, 0xe5, 0x52, + 0x99, 0xd1, 0x75, 0x52, 0x12, 0xd3, 0x7f, 0xfc, 0x6f, 0x6e, 0xd5, 0xd4, 0x1f, 0xc5, 0x6c, 0x88, 0x7f, 0x7d, 0x4d, + 0x3e, 0x3d, 0x6b, 0xef, 0xce, 0x44, 0x42, 0x91, 0xe6, 0x9e, 0x36, 0x27, 0x3e, 0x0e, 0x14, 0xf0, 0xa7, 0xf8, 0x91, + 0xba, 0x99, 0xa4, 0x6b, 0x4d, 0x4f, 0xf3, 0xed, 0x36, 0xcc, 0xbb, 0x93, 0x7f, 0xb2, 0x01, 0xf7, 0x0b, 0xd4, 0x2a, + 0xf2, 0x76, 0x9b, 0x41, 0xdc, 0x9b, 0x89, 0xfe, 0xd5, 0x83, 0x31, 0xc8, 0x4f, 0x17, 0x89, 0x65, 0xb9, 0x9b, 0xad, + 0x69, 0xb1, 0x56, 0xc1, 0x76, 0x15, 0xf2, 0x11, 0x2a, 0xee, 0x96, 0x73, 0x72, 0xe8, 0x19, 0x0c, 0xfb, 0xbd, 0x69, + 0x53, 0xa9, 0x5a, 0x7b, 0x55, 0x8d, 0x26, 0xbb, 0x91, 0xdc, 0xda, 0x0b, 0xdb, 0xe8, 0x47, 0xc0, 0x6a, 0xfc, 0x28, + 0x5b, 0xbc, 0x8e, 0x58, 0x4c, 0xdc, 0x65, 0x7a, 0x4a, 0x8d, 0x20, 0xf2, 0x47, 0x9f, 0x18, 0x87, 0x15, 0xd9, 0xd9, + 0xdf, 0x92, 0x90, 0xbe, 0x77, 0x70, 0x91, 0xfe, 0x4b, 0x29, 0xfb, 0x03, 0x8f, 0x49, 0xac, 0x3f, 0xdb, 0x0f, 0x25, + 0x97, 0xf0, 0x71, 0x8b, 0xf8, 0x7c, 0x06, 0xdb, 0xdb, 0xf5, 0xd6, 0x7c, 0xa1, 0x4e, 0xdc, 0x1f, 0x06, 0x1d, 0x5f, + 0x52, 0x1e, 0xd6, 0x88, 0xf0, 0xb7, 0xe7, 0x07, 0xff, 0xda, 0xbc, 0x04, 0xa1, 0xb5, 0x33, 0xde, 0xe9, 0xf7, 0x69, + 0x25, 0xcf, 0xf0, 0xd3, 0x33, 0x7f, 0x75, 0xca, 0xc7, 0x9f, 0xfe, 0xd3, 0xbc, 0xf1, 0x12, 0xd3, 0xd8, 0x7a, 0x68, + 0xb8, 0x2f, 0xc3, 0xaf, 0xd5, 0xb1, 0x17, 0x57, 0xa0, 0x27, 0x47, 0x9d, 0xf8, 0xa5, 0xa9, 0x32, 0x17, 0xe8, 0x18, + 0xcd, 0xc2, 0x73, 0x39, 0x7b, 0x61, 0x42, 0x77, 0x94, 0x4b, 0x52, 0x6d, 0xcd, 0x6a, 0x6b, 0x9c, 0xe3, 0x82, 0x22, + 0xc1, 0xbc, 0xfb, 0x4e, 0xb4, 0xe2, 0x33, 0x97, 0xff, 0x52, 0x3c, 0x33, 0xc7, 0x5a, 0x1e, 0xe6, 0x83, 0x1a, 0x5b, + 0x8a, 0x53, 0xc5, 0xc6, 0x2f, 0x7f, 0x82, 0x07, 0xac, 0x87, 0xb2, 0x0f, 0xc2, 0xd2, 0xe8, 0x0c, 0x99, 0x91, 0xa3, + 0x48, 0x9d, 0x15, 0x7e, 0xab, 0x33, 0x24, 0xde, 0x9b, 0x34, 0x9d, 0xbe, 0x16, 0xa2, 0xc4, 0x5a, 0x51, 0x9d, 0x7c, + 0xb6, 0x3c, 0x0a, 0xaa, 0xbb, 0x48, 0x5f, 0x9d, 0xa4, 0x50, 0x83, 0xe5, 0x1f, 0xe6, 0x40, 0x92, 0xcc, 0xcf, 0x35, + 0x63, 0x47, 0x44, 0x09, 0xdd, 0x50, 0x37, 0x32, 0x08, 0xb4, 0x36, 0x1b, 0x70, 0xa0, 0xc9, 0x3f, 0x75, 0x4d, 0x42, + 0x1c, 0x78, 0x73, 0xc3, 0xa0, 0x9c, 0x9b, 0x65, 0x62, 0x6c, 0x0b, 0x18, 0xef, 0x49, 0x8b, 0x63, 0xcb, 0x33, 0xb4, + 0x34, 0x86, 0x87, 0x60, 0x31, 0x83, 0xd4, 0x98, 0xf0, 0xa1, 0xa1, 0x41, 0x25, 0xff, 0xd8, 0x28, 0xcd, 0x3b, 0x5a, + 0x75, 0x95, 0x1a, 0xd1, 0xc9, 0x01, 0xa5, 0xbc, 0x14, 0x23, 0xb0, 0xce, 0x54, 0xf7, 0xe4, 0x1c, 0x42, 0x8e, 0x3b, + 0xb9, 0x4a, 0x55, 0xf8, 0x1a, 0x83, 0x75, 0x5b, 0xdf, 0x67, 0x7f, 0xbf, 0xe9, 0x47, 0x65, 0x56, 0x48, 0xf6, 0x5a, + 0xbf, 0x0c, 0xb7, 0x7b, 0x5a, 0x2c, 0x9a, 0x5a, 0x77, 0xce, 0x90, 0x23, 0x4d, 0x84, 0xfd, 0x29, 0x9f, 0x00, 0x2a, + 0x41, 0xa4, 0x00, 0xc3, 0x66, 0xd5, 0xef, 0x11, 0x3d, 0xfb, 0x21, 0x2e, 0x1e, 0x28, 0x9e, 0xb5, 0xaf, 0x88, 0xd9, + 0x24, 0x58, 0x0d, 0xff, 0xef, 0x7d, 0x8a, 0xe1, 0xe1, 0x76, 0x39, 0xdc, 0x23, 0xfb, 0xd6, 0x39, 0x60, 0x9c, 0x7e, + 0x64, 0x43, 0xe3, 0x8f, 0x42, 0xe6, 0x80, 0xa6, 0xb4, 0x1b, 0x9c, 0x1c, 0xc9, 0x37, 0xdb, 0x20, 0xc5, 0x56, 0x26, + 0xda, 0xd9, 0x20, 0x1e, 0x4c, 0xa7, 0x2d, 0x84, 0x58, 0xae, 0x80, 0x86, 0x68, 0x68, 0x86, 0xf3, 0x2e, 0x7a, 0x24, + 0x87, 0xe7, 0x73, 0xf1, 0x80, 0x1f, 0x09, 0xa9, 0x00, 0xce, 0x45, 0x4d, 0x14, 0x79, 0xea, 0xcc, 0x89, 0x47, 0xdb, + 0x1c, 0x0f, 0xd3, 0xb0, 0x35, 0x82, 0x67, 0x27, 0x0f, 0x9a, 0x9f, 0x34, 0xb4, 0xed, 0x41, 0x3a, 0xcc, 0x12, 0x40, + 0x8d, 0xc9, 0xea, 0x78, 0x91, 0xb4, 0x17, 0xed, 0x2f, 0x27, 0x82, 0x19, 0x6d, 0xb9, 0xd3, 0x93, 0x0f, 0x9b, 0xa9, + 0x17, 0xef, 0x07, 0x05, 0x3b, 0x0d, 0xab, 0x56, 0x54, 0xc6, 0x6e, 0x1f, 0xfa, 0xba, 0x87, 0x57, 0x06, 0xc3, 0xf2, + 0x1e, 0x81, 0x16, 0x24, 0xf3, 0xd1, 0x47, 0xd9, 0x80, 0x8b, 0x3b, 0x7c, 0xd4, 0x80, 0x84, 0x8c, 0x87, 0xe5, 0x2c, + 0xf5, 0xc8, 0xea, 0x73, 0x07, 0xd4, 0xbc, 0xa9, 0x9d, 0x3d, 0x72, 0xc4, 0xe7, 0x90, 0x91, 0xec, 0x9f, 0x18, 0xf9, + 0xf0, 0x6d, 0x7c, 0x66, 0x81, 0x53, 0xc4, 0x85, 0xe8, 0xa1, 0xa3, 0xc2, 0xd4, 0x02, 0x0d, 0x3d, 0x85, 0x92, 0x09, + 0xe7, 0x31, 0xd5, 0x16, 0x16, 0xc7, 0xb5, 0x32, 0x2b, 0x89, 0xe9, 0xb5, 0x15, 0xca, 0xa8, 0x15, 0x1e, 0x4f, 0x4f, + 0xae, 0x28, 0x33, 0x69, 0x7c, 0xf0, 0x47, 0xc6, 0xc8, 0x84, 0x8c, 0xab, 0x0e, 0x6d, 0xc9, 0x8d, 0xc0, 0x5b, 0x10, + 0x49, 0xa6, 0x20, 0x9b, 0xc4, 0xc3, 0x04, 0x77, 0x76, 0x4f, 0x74, 0xa7, 0xac, 0x87, 0xe4, 0x2e, 0xdc, 0x2d, 0xb0, + 0x40, 0x9d, 0x7c, 0x30, 0x12, 0x73, 0x1e, 0xa7, 0xbd, 0xc7, 0xdf, 0x33, 0xb5, 0xe4, 0x22, 0x27, 0x92, 0xce, 0xe3, + 0x87, 0x2e, 0x08, 0x5c, 0x82, 0x3e, 0x2c, 0xf9, 0x1a, 0x4f, 0xcb, 0xe1, 0xbc, 0x8b, 0xbf, 0xb8, 0x1a, 0xfa, 0x46, + 0xae, 0x23, 0xb1, 0x46, 0xcc, 0x49, 0x35, 0x52, 0x5f, 0x14, 0xcb, 0xc0, 0x0e, 0x18, 0x13, 0xa1, 0xd1, 0x31, 0xd3, + 0xa1, 0xe5, 0x82, 0xc9, 0x54, 0x43, 0xc1, 0x2b, 0x9d, 0x59, 0x83, 0x56, 0x42, 0xbc, 0xd0, 0x8c, 0xd2, 0x88, 0x3e, + 0x30, 0x9e, 0x77, 0x0a, 0xc2, 0xb2, 0xa6, 0xc3, 0x6c, 0x3f, 0x0f, 0x7d, 0xdf, 0x80, 0x5a, 0xbd, 0xec, 0x29, 0x06, + 0x52, 0x64, 0x96, 0x28, 0x54, 0x42, 0x97, 0xc0, 0x6b, 0x14, 0x82, 0x76, 0xf6, 0xfd, 0x8d, 0x23, 0xca, 0x5b, 0xff, + 0x14, 0xe7, 0x75, 0x96, 0x7f, 0x45, 0x94, 0xf7, 0x6a, 0xfd, 0x6e, 0x18, 0x5b, 0xdd, 0x38, 0x0e, 0xbb, 0xf5, 0x4f, + 0x1d, 0xaa, 0xd4, 0xd4, 0xe9, 0x5c, 0xdf, 0xc7, 0xec, 0x01, 0xc8, 0xcb, 0xdd, 0x9e, 0x4e, 0xc9, 0x30, 0x27, 0x1a, + 0x94, 0x95, 0xb9, 0xca, 0x82, 0xf1, 0x2d, 0x17, 0x5b, 0x75, 0x70, 0x0e, 0xf1, 0x91, 0xd5, 0x35, 0x2e, 0x84, 0xae, + 0x32, 0x3f, 0x9e, 0x0f, 0xca, 0x2d, 0x29, 0xb9, 0x7e, 0x86, 0xfa, 0x99, 0x04, 0xca, 0x45, 0x98, 0x26, 0x50, 0xdc, + 0x89, 0xd1, 0x26, 0xd0, 0x55, 0x37, 0x50, 0xc5, 0x91, 0xfa, 0xfa, 0xd9, 0x87, 0x5b, 0x44, 0xd4, 0x1c, 0xf5, 0xb5, + 0x92, 0x99, 0x14, 0x53, 0xd0, 0xac, 0xcb, 0xc7, 0x97, 0xde, 0x18, 0xd6, 0xa2, 0xdb, 0x16, 0xe7, 0x00, 0x64, 0x97, + 0x48, 0xce, 0x21, 0x70, 0xd7, 0x48, 0x56, 0xad, 0xea, 0xec, 0x27, 0x33, 0x1b, 0xb2, 0x0b, 0xa9, 0xfd, 0x51, 0x3d, + 0x49, 0x35, 0x73, 0xdd, 0xd7, 0x8f, 0xe9, 0xb0, 0x63, 0x33, 0x2a, 0x9b, 0xa9, 0x94, 0xd3, 0x91, 0x9d, 0xbd, 0x98, + 0x89, 0xdb, 0xa1, 0x70, 0xaf, 0x2c, 0x32, 0xe4, 0x34, 0xec, 0x2b, 0x02, 0x64, 0xcb, 0x54, 0x0a, 0xd5, 0x9d, 0x48, + 0x47, 0x2f, 0xa1, 0xe7, 0xd9, 0xd4, 0xba, 0x80, 0x50, 0x3f, 0xcc, 0x6a, 0x83, 0x53, 0x20, 0x9d, 0x5f, 0x96, 0x22, + 0x7c, 0x2e, 0x66, 0x83, 0xac, 0x89, 0x00, 0xbc, 0x38, 0xc9, 0x4c, 0x72, 0x00, 0x70, 0x66, 0xa9, 0xf0, 0xb1, 0xde, + 0x1f, 0x3d, 0x72, 0xa8, 0x9b, 0xa0, 0x21, 0xf3, 0x14, 0x79, 0xa9, 0xcc, 0x27, 0x9a, 0xba, 0x6c, 0x1d, 0xc4, 0x8c, + 0x55, 0xfc, 0x5b, 0x2d, 0xc6, 0xa5, 0x9d, 0x77, 0xdd, 0xda, 0xa2, 0xd5, 0xc4, 0x93, 0xa1, 0x14, 0xb9, 0x49, 0x4e, + 0x9e, 0x1a, 0x6e, 0xbd, 0xbb, 0x50, 0x19, 0xc0, 0xae, 0x48, 0xea, 0xfe, 0x36, 0xff, 0xf7, 0x8c, 0xe1, 0x5e, 0x7c, + 0x57, 0x65, 0xa8, 0x80, 0x8b, 0x00, 0xe9, 0x6a, 0xd9, 0x9a, 0xeb, 0xad, 0x26, 0xf8, 0x51, 0x93, 0x3e, 0xac, 0xf2, + 0x3a, 0xed, 0xe9, 0xd5, 0x50, 0x06, 0x8b, 0x41, 0x4a, 0x48, 0xab, 0xaa, 0x71, 0x93, 0x66, 0x52, 0xda, 0xbf, 0xdf, + 0xec, 0x62, 0xdb, 0xea, 0x56, 0x0f, 0xc6, 0x4d, 0x5a, 0x64, 0xb1, 0xfe, 0x20, 0xed, 0xef, 0xbd, 0xdb, 0xb5, 0x20, + 0x16, 0x27, 0x15, 0xa6, 0xc2, 0x72, 0xfa, 0xcd, 0x52, 0x6e, 0xae, 0xa4, 0x3e, 0x62, 0x7a, 0xdb, 0x95, 0x1c, 0x9f, + 0x31, 0xef, 0x2b, 0x4f, 0x75, 0xef, 0x41, 0x0f, 0x20, 0x62, 0xc6, 0xd9, 0x3c, 0x8b, 0x84, 0x76, 0xfd, 0xd7, 0x14, + 0xaa, 0xbb, 0xfa, 0xd1, 0x50, 0xb1, 0xc4, 0x20, 0x81, 0x2c, 0xf6, 0xf1, 0xa3, 0xae, 0x1c, 0x0e, 0x1e, 0x46, 0x09, + 0x02, 0xd2, 0xdc, 0x4b, 0x46, 0xd1, 0xd8, 0xc0, 0x6f, 0xbd, 0x00, 0xda, 0xbd, 0xdf, 0xda, 0x67, 0x76, 0x27, 0x5e, + 0xe9, 0x1c, 0xf5, 0x8e, 0x84, 0x13, 0x02, 0xd5, 0xc8, 0x31, 0xae, 0x3f, 0x31, 0x6c, 0xea, 0x9f, 0xda, 0x72, 0xc5, + 0x1a, 0x16, 0xbf, 0x01, 0x19, 0x54, 0x11, 0x3f, 0x40, 0xc6, 0xc8, 0x69, 0x53, 0x6c, 0x46, 0xa7, 0x7a, 0xd8, 0xe5, + 0x11, 0x5b, 0xc0, 0x8a, 0x32, 0x86, 0x4f, 0x7f, 0xaa, 0x40, 0xc1, 0xbf, 0x71, 0x05, 0x5d, 0x6d, 0xd1, 0x6d, 0xb6, + 0xc6, 0x56, 0x54, 0x0c, 0x48, 0x0d, 0xfe, 0x24, 0x56, 0x13, 0xc8, 0x16, 0x2e, 0x35, 0x75, 0x60, 0x20, 0x53, 0x86, + 0xd9, 0x40, 0xa3, 0xeb, 0x13, 0x65, 0xfc, 0xe2, 0xf0, 0xde, 0xa0, 0x8f, 0xbe, 0x6f, 0x6a, 0x05, 0x9f, 0x76, 0xae, + 0xd8, 0x5c, 0x06, 0xdd, 0x81, 0x45, 0xb0, 0xa8, 0xed, 0xc9, 0xad, 0x3e, 0x7a, 0xe0, 0x5d, 0x90, 0xe9, 0x3d, 0xa3, + 0x14, 0xab, 0xea, 0x1d, 0xc0, 0x8d, 0x6b, 0xe4, 0xea, 0x3d, 0x77, 0x3d, 0xb6, 0x55, 0x2b, 0xd8, 0x42, 0x57, 0x05, + 0xd8, 0xfd, 0x5d, 0xe1, 0x1f, 0x69, 0x9c, 0x59, 0xc8, 0xe0, 0x99, 0xf5, 0x8a, 0x42, 0x66, 0x60, 0x46, 0xe2, 0xdb, + 0xda, 0xc1, 0x32, 0xc7, 0x7a, 0x81, 0x93, 0xc1, 0x75, 0x90, 0x05, 0x36, 0x61, 0x7c, 0x6a, 0xa8, 0x47, 0xa5, 0xc5, + 0x0f, 0xaa, 0x4f, 0x38, 0xfd, 0xf2, 0xd4, 0x0a, 0x8d, 0x05, 0x9f, 0x23, 0xf3, 0xf8, 0x63, 0xe4, 0x4d, 0xb8, 0xfd, + 0xec, 0x40, 0x87, 0x04, 0xe2, 0xad, 0x79, 0x74, 0x02, 0xb4, 0x54, 0x9a, 0x91, 0xe7, 0xaf, 0x2a, 0x10, 0xf4, 0x78, + 0x16, 0x50, 0x8d, 0xb0, 0x58, 0x79, 0xe7, 0xed, 0xbf, 0x9f, 0xa1, 0x74, 0x94, 0xc1, 0x5b, 0xa9, 0xdd, 0xaf, 0x18, + 0xe3, 0x7b, 0xa1, 0x12, 0xe1, 0x33, 0x94, 0x9f, 0x9b, 0x64, 0x24, 0x53, 0x9d, 0xcc, 0x45, 0x84, 0x9e, 0x6b, 0x6f, + 0x0c, 0xab, 0xa0, 0x40, 0x56, 0x43, 0xee, 0x25, 0xa3, 0x1e, 0x97, 0x2f, 0x1e, 0x0e, 0xad, 0xed, 0x43, 0xe0, 0x7a, + 0x56, 0xa9, 0x36, 0xea, 0xfb, 0x2b, 0x24, 0xb6, 0x3a, 0xce, 0x86, 0x9f, 0x26, 0x49, 0xda, 0x47, 0xea, 0x86, 0xf7, + 0x09, 0xe1, 0x02, 0xe1, 0x45, 0x5e, 0x9d, 0xd7, 0x3a, 0xbc, 0xed, 0x31, 0xe0, 0xb7, 0x6b, 0xe8, 0x93, 0x87, 0x07, + 0xcb, 0xef, 0xd3, 0xfd, 0xb2, 0x26, 0x4d, 0x36, 0x59, 0x42, 0xcc, 0xe7, 0x2d, 0x2b, 0x7d, 0x25, 0x79, 0xe6, 0xfe, + 0xa4, 0x3f, 0x74, 0xe9, 0xd9, 0xc7, 0x58, 0x69, 0x7f, 0xb3, 0x5b, 0x6f, 0x6e, 0x77, 0x67, 0xc4, 0x67, 0xf4, 0xb2, + 0x87, 0xa8, 0x65, 0xbf, 0x30, 0xa9, 0xea, 0x40, 0x0d, 0x21, 0x40, 0x81, 0x01, 0x1d, 0xce, 0x18, 0x8e, 0x25, 0x80, + 0xcb, 0xb5, 0x38, 0x02, 0x9f, 0xf5, 0x1f, 0xbf, 0x00, 0xa3, 0x55, 0x5c, 0xcd, 0x6c, 0xf5, 0x56, 0x20, 0x2c, 0x21, + 0xb2, 0xc1, 0x43, 0x85, 0x7f, 0xe6, 0xb7, 0x01, 0xdc, 0x04, 0x42, 0x4d, 0xa8, 0x85, 0x27, 0x5f, 0x84, 0x14, 0x48, + 0x0f, 0x7f, 0x73, 0x01, 0x67, 0x83, 0x8a, 0xf7, 0xb7, 0x82, 0xb2, 0xe5, 0xa0, 0xf1, 0xfc, 0x57, 0x5a, 0x99, 0xd3, + 0x5f, 0xc4, 0xf1, 0x36, 0xf2, 0xd5, 0x70, 0xde, 0x86, 0x33, 0x45, 0xbe, 0xc5, 0x55, 0x66, 0x05, 0x7e, 0x73, 0x63, + 0x5b, 0xb0, 0x47, 0xd4, 0x39, 0x20, 0xa9, 0x8b, 0x08, 0xa6, 0x1c, 0x43, 0xa2, 0xbf, 0x6c, 0x2f, 0x52, 0xfd, 0xfe, + 0x0c, 0x72, 0x2c, 0xe8, 0xbf, 0x18, 0x27, 0xce, 0x1c, 0x21, 0xca, 0x1e, 0xcf, 0x21, 0x19, 0x5b, 0xb9, 0x0d, 0xca, + 0xaf, 0x00, 0x21, 0x32, 0xe1, 0xb2, 0xc4, 0x48, 0xfd, 0xfb, 0xf7, 0x7a, 0x51, 0x16, 0x36, 0x3f, 0x67, 0xf8, 0xc9, + 0xaf, 0xa5, 0xfe, 0x51, 0xf1, 0x1c, 0x33, 0x53, 0x79, 0x31, 0xce, 0x54, 0x7f, 0xe4, 0x96, 0x7b, 0x6a, 0xac, 0x27, + 0x40, 0xc1, 0xc4, 0x39, 0x98, 0x6b, 0x70, 0xce, 0xff, 0xa3, 0x42, 0x6a, 0xcc, 0xbf, 0xe7, 0x8f, 0x2f, 0xec, 0x35, + 0xd4, 0xd5, 0x30, 0x17, 0x30, 0x02, 0x25, 0x87, 0xa1, 0x9f, 0x0b, 0x71, 0x20, 0x66, 0xff, 0x37, 0x05, 0x36, 0x5f, + 0x84, 0xc2, 0xa9, 0xfb, 0xd1, 0xa5, 0xc5, 0xd5, 0x9e, 0x4c, 0x12, 0xd0, 0xdb, 0x30, 0xce, 0x12, 0xdb, 0x1d, 0x63, + 0xf3, 0x9e, 0xc6, 0xe4, 0x59, 0x31, 0xf9, 0x0c, 0x7f, 0x7f, 0xd4, 0x8f, 0xf6, 0x94, 0x7f, 0x7e, 0xfa, 0x4b, 0xff, + 0xf6, 0xaf, 0x79, 0xee, 0x10, 0x67, 0xb1, 0xc3, 0x97, 0x43, 0xc6, 0xe2, 0xdd, 0x5e, 0x65, 0x67, 0xc5, 0xe4, 0xa3, + 0x6d, 0xf4, 0xe9, 0x77, 0xd7, 0x1f, 0x0f, 0xf1, 0xb6, 0xd0, 0xd2, 0xcc, 0x24, 0x75, 0x6e, 0x6e, 0x2a, 0x7d, 0xc9, + 0xed, 0x2e, 0x99, 0x04, 0xa4, 0x41, 0x46, 0x65, 0x31, 0xb5, 0x25, 0xd2, 0x58, 0x73, 0x05, 0xad, 0x82, 0xa1, 0x35, + 0xc7, 0x51, 0xf7, 0x25, 0x0a, 0xc0, 0x38, 0xaf, 0x10, 0x6d, 0x7a, 0x42, 0x65, 0x63, 0x83, 0x8d, 0x80, 0x33, 0xc8, + 0x30, 0x86, 0x8b, 0xbd, 0x96, 0xbc, 0x27, 0xd8, 0x46, 0xd2, 0x90, 0xe2, 0x4f, 0x65, 0xe3, 0x68, 0x8a, 0xeb, 0x6a, + 0x7c, 0xbd, 0xff, 0xa8, 0x1a, 0xa7, 0x18, 0x25, 0xbc, 0x29, 0xef, 0x48, 0x5a, 0x4d, 0x51, 0x82, 0x16, 0x22, 0x73, + 0x3b, 0x62, 0x16, 0xdc, 0x69, 0x5c, 0x8e, 0x03, 0x59, 0xa7, 0x6b, 0x3a, 0x1d, 0x65, 0x1c, 0x38, 0xb5, 0xca, 0xe8, + 0x9c, 0x20, 0xe9, 0xb5, 0xb8, 0xaa, 0x81, 0x72, 0xb2, 0x8c, 0x79, 0x0b, 0x3c, 0xfb, 0xd8, 0x39, 0xbe, 0x49, 0xa9, + 0xf3, 0x93, 0xd8, 0xf3, 0xf6, 0xe0, 0xfe, 0xe6, 0xf1, 0x6d, 0x7a, 0x34, 0x63, 0xd7, 0x84, 0x75, 0x3d, 0x5e, 0x53, + 0x42, 0xa8, 0x04, 0xea, 0x08, 0x10, 0xc6, 0xce, 0x1a, 0xd8, 0xd7, 0x21, 0xb3, 0x80, 0x96, 0xad, 0xff, 0xaf, 0x43, + 0x8e, 0x88, 0x40, 0xa3, 0x3f, 0x83, 0x42, 0xd2, 0x43, 0x99, 0xec, 0x0d, 0xf2, 0x5e, 0x0b, 0xfb, 0xcc, 0x1a, 0xd4, + 0x69, 0xc1, 0x8d, 0x40, 0x90, 0x74, 0xcb, 0xcc, 0x4d, 0x74, 0x3a, 0xcb, 0x4f, 0x2c, 0x3f, 0xc6, 0xb4, 0xe6, 0xbf, + 0x31, 0xa3, 0x9d, 0x93, 0xa7, 0xd3, 0xc2, 0x6a, 0x43, 0x81, 0x9b, 0x50, 0xaa, 0xd9, 0x32, 0x67, 0x5c, 0x83, 0xd2, + 0xa7, 0x69, 0x42, 0x37, 0x86, 0x4f, 0x43, 0xd6, 0x9f, 0xe4, 0x3d, 0xda, 0xba, 0x3e, 0x74, 0xcb, 0xb5, 0xa1, 0x6c, + 0xd6, 0x8d, 0xd8, 0xce, 0x0f, 0x6f, 0xa8, 0x8f, 0xc9, 0x22, 0xd0, 0x7d, 0x86, 0x5f, 0x3f, 0xea, 0x20, 0xa7, 0x7c, + 0xf9, 0xe9, 0x3f, 0xe5, 0x85, 0xee, 0x43, 0x28, 0x6a, 0x99, 0x82, 0x4f, 0x64, 0x3e, 0x1e, 0x1d, 0x65, 0xb3, 0x45, + 0xa6, 0x7e, 0xf5, 0xb9, 0x40, 0x9e, 0xbe, 0x5b, 0xb8, 0x17, 0xdf, 0xf2, 0x58, 0xdb, 0x8b, 0x03, 0x72, 0x91, 0x1b, + 0x09, 0xd0, 0x2b, 0x5b, 0x04, 0xf8, 0x83, 0x4a, 0x5d, 0x03, 0xfa, 0xca, 0xe7, 0xa0, 0x32, 0x80, 0xb2, 0xf2, 0x90, + 0x01, 0x0b, 0x9c, 0xc9, 0x01, 0x1f, 0x77, 0x94, 0xea, 0xa6, 0xf5, 0x9a, 0x85, 0x32, 0x01, 0x0b, 0x7e, 0xf1, 0xd4, + 0x91, 0x29, 0x4f, 0xd3, 0xa9, 0xfc, 0x0f, 0x97, 0x86, 0x69, 0x18, 0xe3, 0x52, 0x2b, 0xc5, 0x23, 0xcb, 0x17, 0x2d, + 0x99, 0x8b, 0xef, 0xfb, 0x38, 0xbb, 0xcc, 0x94, 0x3c, 0x5e, 0xd2, 0x6f, 0xb3, 0x33, 0xdd, 0x2b, 0x2d, 0xe5, 0x49, + 0x4b, 0xd1, 0x54, 0x7f, 0xf4, 0x17, 0x51, 0x89, 0x0f, 0xf8, 0x12, 0x90, 0x70, 0xce, 0x07, 0xd5, 0x1c, 0xcd, 0x14, + 0xf5, 0xf9, 0x01, 0x6e, 0x1b, 0x39, 0xdb, 0x37, 0xcb, 0x00, 0x1f, 0x0f, 0x45, 0xd9, 0xdb, 0x12, 0x52, 0x59, 0x76, + 0xf4, 0xc9, 0x79, 0xc9, 0x03, 0x65, 0x73, 0x05, 0x01, 0x8d, 0x08, 0xb1, 0xb7, 0x99, 0x87, 0xc4, 0x21, 0x50, 0xbd, + 0x43, 0x7f, 0x57, 0xf4, 0x51, 0x8a, 0x03, 0xe7, 0x7a, 0xbe, 0x65, 0xdd, 0x7e, 0xb6, 0x88, 0x3d, 0x27, 0xb6, 0x5a, + 0xab, 0xa4, 0x1c, 0x38, 0x74, 0xbb, 0x21, 0x5e, 0xba, 0x7f, 0xbf, 0x4e, 0x70, 0x72, 0x2e, 0xed, 0x97, 0x7b, 0x34, + 0x07, 0x04, 0x7d, 0xc0, 0xe2, 0xcf, 0x50, 0x92, 0xbe, 0xb5, 0xee, 0xee, 0xb4, 0xbb, 0x7b, 0xb4, 0x75, 0x5b, 0x1f, + 0x0e, 0xc1, 0x78, 0x8b, 0xd4, 0x3a, 0x33, 0x37, 0xfa, 0xb1, 0xec, 0x38, 0xe7, 0xe5, 0x75, 0xea, 0xa6, 0x76, 0x87, + 0xf2, 0x82, 0x44, 0xd7, 0x8a, 0x55, 0x94, 0x05, 0x0a, 0x46, 0xce, 0x74, 0xf6, 0x72, 0x86, 0x40, 0x82, 0x81, 0x5d, + 0x78, 0x88, 0x4f, 0xfc, 0x82, 0xee, 0x3f, 0x78, 0x6c, 0x77, 0xd3, 0x1d, 0x97, 0xc6, 0xb2, 0xc3, 0x77, 0x3b, 0xa5, + 0x2e, 0x08, 0x7a, 0xe5, 0x4e, 0x78, 0xdd, 0x63, 0xa8, 0xc9, 0x8d, 0x6b, 0x4b, 0xcd, 0x35, 0x82, 0x28, 0xde, 0x7d, + 0xce, 0x4b, 0x11, 0x4f, 0x3b, 0x50, 0x84, 0xe0, 0xf6, 0x15, 0x0d, 0xbe, 0x64, 0x34, 0x72, 0x85, 0x27, 0xe4, 0x3a, + 0xe9, 0xb3, 0xda, 0xaf, 0xb3, 0xd1, 0x03, 0x85, 0x31, 0xe1, 0x1d, 0xbd, 0x50, 0x73, 0x07, 0x5e, 0xc2, 0x36, 0x92, + 0x17, 0x70, 0xc6, 0x48, 0x54, 0xf1, 0x89, 0xc8, 0xa6, 0xd5, 0x95, 0x99, 0x81, 0x07, 0xc7, 0x4e, 0xc3, 0x30, 0x63, + 0x73, 0x9c, 0x20, 0x57, 0x1d, 0xc8, 0x8b, 0x19, 0xc1, 0x6f, 0x51, 0x19, 0x5a, 0x57, 0x93, 0xa2, 0xd0, 0xc0, 0x5a, + 0xa2, 0xff, 0x2e, 0xa8, 0xcc, 0xc3, 0x36, 0x74, 0x02, 0xb4, 0xf8, 0x4e, 0x44, 0x3f, 0xdd, 0x4d, 0xb2, 0xa8, 0xdf, + 0xb3, 0x8b, 0x86, 0x85, 0xcb, 0x80, 0x09, 0x63, 0x10, 0xd3, 0x61, 0x37, 0x5d, 0x51, 0x9b, 0x7b, 0x68, 0xbe, 0x39, + 0x41, 0x89, 0x47, 0x8b, 0x55, 0x64, 0x26, 0xb2, 0x31, 0xe3, 0x43, 0x15, 0x04, 0x45, 0x2d, 0x4d, 0x5c, 0x45, 0x88, + 0x00, 0xd6, 0x17, 0x8e, 0x4e, 0x07, 0x40, 0x80, 0x8b, 0xed, 0xb9, 0x64, 0x15, 0x2e, 0x2f, 0x2c, 0xe2, 0xe9, 0x11, + 0x1c, 0x5c, 0xff, 0xbf, 0x76, 0x17, 0xb5, 0xb4, 0xff, 0xca, 0x89, 0x5e, 0x3b, 0x7b, 0x52, 0x73, 0x9a, 0xe4, 0xa5, + 0x98, 0x9d, 0x9c, 0x1c, 0x64, 0x1e, 0xb3, 0xf8, 0xa4, 0x9e, 0x59, 0x74, 0x0c, 0xe5, 0xc1, 0x46, 0xcf, 0xd8, 0x47, + 0x97, 0x1c, 0x3b, 0xe4, 0x02, 0x8b, 0x53, 0x3e, 0x3b, 0xe1, 0x6b, 0x1e, 0x1a, 0x11, 0xcb, 0x20, 0xcc, 0x68, 0x35, + 0x81, 0xaf, 0x63, 0xec, 0xad, 0xb2, 0xe8, 0x39, 0x8a, 0x55, 0x14, 0xd0, 0x83, 0x9a, 0x1e, 0x9c, 0xba, 0x4b, 0x77, + 0xcf, 0x78, 0x09, 0xaa, 0xec, 0x28, 0x1d, 0xe2, 0xf9, 0xe3, 0xa2, 0x21, 0x82, 0x4e, 0x74, 0x66, 0x1d, 0xb3, 0xa9, + 0x94, 0xb9, 0xfe, 0x0b, 0x72, 0x1c, 0xb4, 0x7e, 0xe6, 0x2f, 0x1e, 0xe5, 0xfc, 0x74, 0x9e, 0xac, 0x5f, 0xbf, 0xe0, + 0xfc, 0xcc, 0xe5, 0xb7, 0xa3, 0x03, 0x97, 0x8f, 0x7f, 0x5d, 0x18, 0xfc, 0xc5, 0xc7, 0x33, 0x4e, 0x5f, 0xab, 0x25, + 0xbc, 0x3f, 0xe1, 0x9f, 0xe0, 0x2b, 0x9e, 0x98, 0xef, 0xa5, 0x3c, 0x4f, 0xf1, 0x45, 0x48, 0x20, 0x5f, 0x73, 0xbd, + 0x6c, 0x81, 0x28, 0x2e, 0xb5, 0x6e, 0x17, 0x82, 0x8d, 0x0a, 0x88, 0xdf, 0x4d, 0x03, 0x62, 0xb1, 0xea, 0x29, 0x6e, + 0xe2, 0x26, 0xcf, 0xd5, 0x60, 0xc6, 0x8d, 0x8e, 0x4a, 0xc6, 0x5c, 0x86, 0x06, 0x46, 0x1c, 0x93, 0x45, 0x7c, 0x26, + 0x83, 0xf0, 0x78, 0xd3, 0x95, 0x95, 0x4c, 0xa2, 0x31, 0xfa, 0xdb, 0xb4, 0xe3, 0xdb, 0x46, 0x9f, 0xde, 0x6b, 0xf4, + 0x65, 0xf3, 0x63, 0xaf, 0x1e, 0x2d, 0xdc, 0x1f, 0x5c, 0x0b, 0x36, 0x0e, 0xf2, 0xd4, 0xbf, 0xe8, 0xdc, 0x62, 0x7a, + 0x1c, 0x67, 0x1e, 0x5a, 0x23, 0xf7, 0xc1, 0x31, 0xae, 0xcc, 0xa6, 0x4d, 0x37, 0x9a, 0xd2, 0xbf, 0xb2, 0x58, 0x38, + 0xb0, 0xac, 0x73, 0x7b, 0xef, 0x3e, 0xf7, 0xa4, 0xb7, 0xee, 0x64, 0xad, 0x57, 0x39, 0x32, 0x1e, 0xbd, 0x00, 0xa2, + 0x16, 0x3b, 0xe9, 0x43, 0x5a, 0xe5, 0xce, 0xec, 0x75, 0x36, 0x00, 0xc9, 0xdf, 0x55, 0xdc, 0x39, 0x07, 0x24, 0xee, + 0x21, 0x31, 0xe9, 0x18, 0xfc, 0xba, 0x3c, 0x76, 0xef, 0x80, 0xfd, 0xf2, 0xff, 0x45, 0x46, 0xeb, 0x37, 0x2e, 0x91, + 0xc2, 0xa9, 0xe8, 0x18, 0x2b, 0x4a, 0xf8, 0x8e, 0xb4, 0x95, 0x12, 0xd5, 0x81, 0xf1, 0xf9, 0x0e, 0xc7, 0x60, 0x5f, + 0xd4, 0x50, 0x7c, 0x32, 0x0d, 0xc2, 0x87, 0xec, 0x80, 0xcf, 0x6c, 0x09, 0x11, 0x4a, 0x27, 0xdd, 0x91, 0x82, 0x02, + 0x64, 0x5c, 0x01, 0xc4, 0x0c, 0xd3, 0x86, 0x20, 0x07, 0x27, 0x0e, 0x7f, 0xe6, 0xcf, 0x1d, 0x92, 0x22, 0xdc, 0xc2, + 0xfb, 0xca, 0x04, 0xfe, 0x5a, 0xe2, 0xc7, 0x33, 0xf9, 0x63, 0x0c, 0x95, 0x57, 0x13, 0xb6, 0x73, 0xdf, 0xd7, 0xa4, + 0x74, 0x2a, 0xf3, 0x5d, 0x10, 0xb2, 0xca, 0x39, 0x5d, 0xde, 0x59, 0x9c, 0xec, 0xae, 0x6f, 0xad, 0x68, 0xb6, 0x13, + 0xf2, 0xe6, 0xb7, 0x95, 0xe8, 0x0f, 0x97, 0x2e, 0x7f, 0xfa, 0x7d, 0xff, 0x5a, 0x1c, 0xa0, 0x1a, 0x03, 0x7a, 0x1f, + 0xed, 0xc3, 0xd6, 0x7f, 0xdc, 0xbb, 0x69, 0x1f, 0xb6, 0xdb, 0x48, 0x5c, 0xc4, 0x7f, 0x42, 0x53, 0x3d, 0x0a, 0xde, + 0x6d, 0x85, 0x37, 0x0b, 0x17, 0x4e, 0x8d, 0x07, 0x7a, 0xe0, 0x32, 0x99, 0x30, 0x21, 0xf4, 0xee, 0x73, 0xb6, 0x7e, + 0xdf, 0x6c, 0x5d, 0x8c, 0x8d, 0x69, 0x6c, 0x9f, 0x8c, 0x25, 0x74, 0x23, 0x7b, 0xaf, 0x9a, 0x18, 0xba, 0xa6, 0x5b, + 0x83, 0x09, 0xfa, 0xb6, 0xe9, 0x60, 0x7f, 0x57, 0x34, 0x5d, 0x3b, 0x96, 0x19, 0xdd, 0xbc, 0x1c, 0x9b, 0x1d, 0xe9, + 0xac, 0x3d, 0xee, 0xd9, 0x78, 0xe8, 0x4d, 0x78, 0x09, 0x55, 0x3a, 0xf7, 0x12, 0xe4, 0x18, 0xee, 0x76, 0x06, 0xba, + 0x70, 0x1b, 0x47, 0x0a, 0x98, 0x12, 0x0c, 0x78, 0x08, 0x97, 0xfb, 0x22, 0xae, 0xd9, 0x9b, 0xd7, 0x3e, 0xbf, 0x9e, + 0x8c, 0x37, 0xda, 0x08, 0x17, 0x51, 0x66, 0xa0, 0x1e, 0x11, 0x75, 0x6a, 0x60, 0x85, 0xdc, 0x63, 0xc7, 0x43, 0x2d, + 0xd6, 0x5a, 0x32, 0x7b, 0xac, 0xa0, 0xc1, 0x2e, 0xa3, 0xfc, 0xd2, 0xcf, 0xcd, 0xf3, 0x6e, 0xfe, 0xd3, 0x1a, 0x4a, + 0x67, 0xf6, 0x88, 0x2b, 0x51, 0x28, 0x74, 0x9a, 0x1c, 0x68, 0x9b, 0x26, 0x3b, 0xd4, 0x9b, 0x21, 0x64, 0x83, 0xd7, + 0xba, 0xc7, 0x21, 0x4c, 0xa3, 0xa7, 0x3b, 0x7b, 0x8b, 0x7e, 0x81, 0xcd, 0x72, 0x76, 0x46, 0xda, 0xdd, 0xa8, 0x87, + 0x06, 0xc3, 0x6f, 0xc7, 0x03, 0x8e, 0x51, 0x6a, 0xad, 0x1e, 0x0a, 0x47, 0xe5, 0xab, 0x1d, 0xb9, 0x0d, 0xbc, 0x8a, + 0x7b, 0x6a, 0xda, 0x57, 0x53, 0x3d, 0x3d, 0xc8, 0x56, 0x17, 0xf7, 0x06, 0xd8, 0x48, 0x3b, 0x35, 0x1a, 0xfa, 0xc4, + 0x70, 0x88, 0xf3, 0xd8, 0xb9, 0xc8, 0xce, 0x92, 0x0e, 0x97, 0xb3, 0xda, 0xb3, 0xf1, 0xf8, 0x78, 0x22, 0x79, 0x19, + 0x77, 0x3b, 0x8f, 0xb5, 0x06, 0x06, 0x8e, 0xde, 0x37, 0x70, 0xe3, 0x4a, 0x08, 0x82, 0x1c, 0xdf, 0x97, 0xa1, 0x7c, + 0xc5, 0x82, 0xfb, 0x74, 0x77, 0xdb, 0x7d, 0x14, 0xda, 0x4e, 0x94, 0x8c, 0x13, 0xc1, 0xba, 0xa5, 0xbd, 0x6d, 0x29, + 0xd6, 0xf0, 0x9f, 0x3e, 0x64, 0xff, 0xc9, 0xdf, 0x10, 0xe5, 0xdd, 0xa0, 0xc0, 0xcc, 0xb5, 0xf0, 0x40, 0x23, 0x05, + 0x13, 0x77, 0x13, 0x14, 0x59, 0x72, 0x2f, 0x83, 0x23, 0x11, 0x51, 0xe7, 0x94, 0x78, 0x53, 0x3c, 0x67, 0x88, 0x44, + 0x36, 0xf6, 0x83, 0xe1, 0x40, 0xeb, 0x0d, 0xf4, 0x97, 0xd3, 0x4b, 0x4e, 0x77, 0x22, 0x7b, 0x8d, 0x37, 0x75, 0x20, + 0x85, 0xc0, 0x67, 0xd6, 0x3d, 0xc8, 0x78, 0x4b, 0xcb, 0x25, 0xea, 0x5b, 0x37, 0xee, 0xa4, 0x7a, 0xa8, 0xe7, 0xd1, + 0xda, 0x09, 0x2d, 0x13, 0xbc, 0x51, 0x89, 0x3f, 0x6f, 0x4d, 0xec, 0x8e, 0x6c, 0xdc, 0x28, 0x8a, 0x3b, 0xc2, 0xe9, + 0x86, 0x4c, 0x46, 0x39, 0x68, 0xfd, 0xec, 0x21, 0x21, 0x40, 0x1b, 0x8e, 0x8b, 0xac, 0x81, 0x45, 0x03, 0xdc, 0x83, + 0x1f, 0x01, 0x89, 0x45, 0x65, 0x9e, 0xa8, 0x43, 0x93, 0x3c, 0x2f, 0x99, 0xc1, 0xcb, 0x2e, 0x54, 0x72, 0xe1, 0x92, + 0x51, 0xc7, 0xb5, 0xb6, 0x94, 0xfc, 0xcc, 0xe9, 0xd9, 0x6d, 0xaa, 0x05, 0xa1, 0xe4, 0xdb, 0xbe, 0xed, 0x3a, 0x49, + 0x80, 0x0e, 0xee, 0x20, 0x09, 0x16, 0x42, 0x36, 0x6d, 0xe0, 0xda, 0xec, 0x27, 0x3e, 0x64, 0xd7, 0x29, 0x72, 0x4f, + 0x7c, 0xbc, 0x50, 0x31, 0xe2, 0x20, 0xd4, 0xbb, 0x4d, 0x80, 0x0a, 0xea, 0x7d, 0x28, 0x09, 0x82, 0xfc, 0x3f, 0x7e, + 0x50, 0xdc, 0x7b, 0x03, 0x6a, 0x7f, 0x62, 0x93, 0xbb, 0x0b, 0xdb, 0x19, 0xcf, 0xe8, 0xac, 0x67, 0xf8, 0x2f, 0xbd, + 0x13, 0xb7, 0x01}; + +// Backwards compatibility alias +#define INDEX_GZ INDEX_BR + +#endif // USE_WEBSERVER_GZIP + +} // namespace esphome::web_server #endif #endif diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 7c015adcf7..e5705d7b47 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -422,7 +422,11 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #else AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); #endif +#ifdef USE_WEBSERVER_GZIP response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip")); +#else + response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("br")); +#endif request->send(response); } #elif USE_WEBSERVER_VERSION >= 2 diff --git a/esphome/const.py b/esphome/const.py index 518247aa60..7a18428a61 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -247,6 +247,7 @@ CONF_COMPENSATION = "compensation" CONF_COMPILE_PROCESS_LIMIT = "compile_process_limit" CONF_COMPONENT_ID = "component_id" CONF_COMPONENTS = "components" +CONF_COMPRESSION = "compression" CONF_CONDITION = "condition" CONF_CONDITION_ID = "condition_id" CONF_CONDUCTIVITY = "conductivity" diff --git a/esphome/core/defines.h b/esphome/core/defines.h index f8f86e8c55..ae94f6ef5f 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -211,7 +211,9 @@ #define USE_WEBSERVER_AUTH #define USE_WEBSERVER_OTA #define USE_WEBSERVER_PORT 80 // NOLINT +#define USE_WEBSERVER_GZIP #define USE_WEBSERVER_SORTING +#define USE_CAPTIVE_PORTAL_GZIP #define USE_WIFI_11KV_SUPPORT #define USE_WIFI_FAST_CONNECT #define USE_WIFI_LISTENERS diff --git a/tests/components/captive_portal/common.yaml b/tests/components/captive_portal/common.yaml index 25bc4a887a..6180a77502 100644 --- a/tests/components/captive_portal/common.yaml +++ b/tests/components/captive_portal/common.yaml @@ -3,3 +3,4 @@ wifi: password: password1 captive_portal: + compression: br diff --git a/tests/components/web_server/common_v2.yaml b/tests/components/web_server/common_v2.yaml index 2af5ceca44..f2b15e484d 100644 --- a/tests/components/web_server/common_v2.yaml +++ b/tests/components/web_server/common_v2.yaml @@ -4,3 +4,4 @@ packages: web_server: port: 8080 version: 2 + compression: br From a66df9ab0f5895aea78b4c8c5d8bbfe492b7caa1 Mon Sep 17 00:00:00 2001 From: esphomebot Date: Thu, 8 Jan 2026 10:52:02 +1300 Subject: [PATCH 1143/1145] Update webserver local assets to 20260107-214817 (#13064) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston From afa4fe982084f834affa543938ee0e1886ce4e50 Mon Sep 17 00:00:00 2001 From: marcbodea Date: Thu, 8 Jan 2026 00:37:47 +0100 Subject: [PATCH 1144/1145] [esp32_touch] Disable hardware timeout to prevent continuous interrupts (#13059) Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- esphome/components/esp32_touch/esp32_touch.h | 10 ++++++++++ esphome/components/esp32_touch/esp32_touch_common.cpp | 11 ++++++++++- esphome/components/esp32_touch/esp32_touch_v2.cpp | 11 ++++++----- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index fb1973e26f..812c746301 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -243,6 +243,16 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { uint32_t get_wakeup_threshold() const { return this->wakeup_threshold_; } +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + /// Ensure benchmark value is read (v2 touch hardware only). + /// Called from multiple places - kept as helper to document shared usage. + void ensure_benchmark_read() { + if (this->benchmark_ == 0) { + touch_pad_read_benchmark(this->touch_pad_, &this->benchmark_); + } + } +#endif + protected: friend ESP32TouchComponent; diff --git a/esphome/components/esp32_touch/esp32_touch_common.cpp b/esphome/components/esp32_touch/esp32_touch_common.cpp index a0b1df38c1..429b5173be 100644 --- a/esphome/components/esp32_touch/esp32_touch_common.cpp +++ b/esphome/components/esp32_touch/esp32_touch_common.cpp @@ -102,7 +102,16 @@ void ESP32TouchComponent::process_setup_mode_logging_(uint32_t now) { uint32_t value = this->read_touch_value(child->get_touch_pad()); // Store the value for get_value() access in lambdas child->value_ = value; - ESP_LOGD(TAG, "Touch Pad '%s' (T%d): %d", child->get_name().c_str(), child->get_touch_pad(), value); + // Read benchmark if not already read + child->ensure_benchmark_read(); + // Calculate difference to help user set threshold + // For ESP32-S2/S3 v2: touch detected when value > benchmark + threshold + // So threshold should be < (value - benchmark) when touched + int32_t difference = static_cast(value) - static_cast(child->benchmark_); + ESP_LOGD(TAG, + "Touch Pad '%s' (T%d): value=%d, benchmark=%" PRIu32 ", difference=%" PRId32 " (set threshold < %" PRId32 + " to detect touch)", + child->get_name().c_str(), child->get_touch_pad(), value, child->benchmark_, difference, difference); #endif } this->setup_mode_last_log_print_ = now; diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 9662b009f6..b34ca1abd3 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -105,8 +105,10 @@ void ESP32TouchComponent::setup() { touch_pad_set_charge_discharge_times(this->meas_cycle_); touch_pad_set_measurement_interval(this->sleep_cycle_); - // Configure timeout if needed - touch_pad_timeout_set(true, TOUCH_PAD_THRESHOLD_MAX); + // Disable hardware timeout - it causes continuous interrupts with high-capacitance + // setups (e.g., pressure sensors under cushions). The periodic release check in + // loop() handles state detection reliably without needing hardware timeout. + touch_pad_timeout_set(false, TOUCH_PAD_THRESHOLD_MAX); // Register ISR handler with interrupt mask esp_err_t err = @@ -314,8 +316,7 @@ void ESP32TouchComponent::loop() { size_t pads_off = 0; for (auto *child : this->children_) { - if (child->benchmark_ == 0) - touch_pad_read_benchmark(child->touch_pad_, &child->benchmark_); + child->ensure_benchmark_read(); // Handle initial state publication after startup this->publish_initial_state_if_needed_(child, now); @@ -354,7 +355,7 @@ void ESP32TouchComponent::loop() { void ESP32TouchComponent::on_shutdown() { // Disable interrupts - touch_pad_intr_disable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_TIMEOUT)); + touch_pad_intr_disable(TOUCH_PAD_INTR_MASK_ACTIVE); touch_pad_isr_deregister(touch_isr_handler, this); this->cleanup_touch_queue_(); From 0ce3ac438b94e3e7e2a0acfae3f6c26e8f5e6097 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 13:40:15 -1000 Subject: [PATCH 1145/1145] [logger] Add thread-safe logging support for LibreTiny platform (#13062) --- esphome/components/logger/__init__.py | 13 +- esphome/components/logger/logger.cpp | 27 ++- esphome/components/logger/logger.h | 41 +++- .../logger/task_log_buffer_libretiny.cpp | 206 ++++++++++++++++++ .../logger/task_log_buffer_libretiny.h | 103 +++++++++ tests/components/logger/test.bk72xx-ard.yaml | 5 + tests/components/logger/test.ln882x-ard.yaml | 5 + tests/components/logger/test.rtl87xx-ard.yaml | 5 + 8 files changed, 396 insertions(+), 9 deletions(-) create mode 100644 esphome/components/logger/task_log_buffer_libretiny.cpp create mode 100644 esphome/components/logger/task_log_buffer_libretiny.h create mode 100644 tests/components/logger/test.bk72xx-ard.yaml create mode 100644 tests/components/logger/test.ln882x-ard.yaml create mode 100644 tests/components/logger/test.rtl87xx-ard.yaml diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 79a9a4208c..7691458df5 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -226,8 +226,13 @@ CONFIG_SCHEMA = cv.All( cv.SplitDefault( CONF_TASK_LOG_BUFFER_SIZE, esp32=768, # Default: 768 bytes (~5-6 messages with 70-byte text plus thread names) + bk72xx=768, + ln882x=768, + rtl87xx=768, ): cv.All( - cv.only_on_esp32, + cv.only_on( + [PLATFORM_ESP32, PLATFORM_BK72XX, PLATFORM_LN882X, PLATFORM_RTL87XX] + ), cv.validate_bytes, cv.Any( cv.int_(0), # Disabled @@ -306,6 +311,7 @@ async def to_code(config): ) if CORE.is_esp32: cg.add(log.create_pthread_key()) + if CORE.is_esp32 or CORE.is_libretiny: task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE] if task_log_buffer_size > 0: cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER") @@ -529,6 +535,11 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( PlatformFramework.ESP32_IDF, }, "task_log_buffer_host.cpp": {PlatformFramework.HOST_NATIVE}, + "task_log_buffer_libretiny.cpp": { + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, } ) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index e633f9fd7d..bb00a230ee 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -12,8 +12,8 @@ namespace esphome::logger { static const char *const TAG = "logger"; -#if defined(USE_ESP32) || defined(USE_HOST) -// Implementation for multi-threaded platforms (ESP32 with FreeRTOS, Host with pthreads) +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) +// Implementation for multi-threaded platforms (ESP32 with FreeRTOS, Host with pthreads, LibreTiny with FreeRTOS) // Main thread/task always uses direct buffer access for console output and callbacks // // For non-main threads/tasks: @@ -27,7 +27,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch if (level > this->level_for(tag)) return; -#ifdef USE_ESP32 +#if defined(USE_ESP32) || defined(USE_LIBRETINY) TaskHandle_t current_task = xTaskGetCurrentTaskHandle(); bool is_main_task = (current_task == main_task_); #else // USE_HOST @@ -50,7 +50,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch bool message_sent = false; #ifdef USE_ESPHOME_TASK_LOG_BUFFER // For non-main threads/tasks, queue the message for callbacks -#ifdef USE_ESP32 +#if defined(USE_ESP32) || defined(USE_LIBRETINY) message_sent = this->log_buffer_->send_message_thread_safe(level, tag, static_cast(line), current_task, format, args); #else // USE_HOST @@ -101,7 +101,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch global_recursion_guard_ = false; } -#endif // USE_ESP32 / USE_HOST +#endif // USE_ESP32 / USE_HOST / USE_LIBRETINY #ifdef USE_STORE_LOG_STR_IN_FLASH // Implementation for ESP8266 with flash string support. @@ -191,8 +191,10 @@ void Logger::init_log_buffer(size_t total_buffer_size) { #ifdef USE_HOST // Host uses slot count instead of byte size this->log_buffer_ = esphome::make_unique(total_buffer_size); -#else +#elif defined(USE_ESP32) this->log_buffer_ = esphome::make_unique(total_buffer_size); +#elif defined(USE_LIBRETINY) + this->log_buffer_ = esphome::make_unique(total_buffer_size); #endif #ifdef USE_ESP32 @@ -220,7 +222,7 @@ void Logger::process_messages_() { this->log_buffer_->release_message_main_loop(); this->write_tx_buffer_to_console_(); } -#else // USE_ESP32 +#elif defined(USE_ESP32) logger::TaskLogBuffer::LogMessage *message; const char *text; void *received_token; @@ -232,6 +234,17 @@ void Logger::process_messages_() { this->log_buffer_->release_message_main_loop(received_token); this->write_tx_buffer_to_console_(); } +#elif defined(USE_LIBRETINY) + logger::TaskLogBufferLibreTiny::LogMessage *message; + const char *text; + while (this->log_buffer_->borrow_message_main_loop(&message, &text)) { + const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; + this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, text, + message->text_length); + // Release the message to allow other tasks to use it as soon as possible + this->log_buffer_->release_message_main_loop(); + this->write_tx_buffer_to_console_(); + } #endif } #ifdef USE_ESP32 diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 86d2943135..79299c2b1c 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -16,6 +16,8 @@ #include "task_log_buffer_host.h" #elif defined(USE_ESP32) #include "task_log_buffer_esp32.h" +#elif defined(USE_LIBRETINY) +#include "task_log_buffer_libretiny.h" #endif #endif @@ -376,6 +378,8 @@ class Logger : public Component { std::unique_ptr log_buffer_; // Will be initialized with init_log_buffer #elif defined(USE_ESP32) std::unique_ptr log_buffer_; // Will be initialized with init_log_buffer +#elif defined(USE_LIBRETINY) + std::unique_ptr log_buffer_; // Will be initialized with init_log_buffer #endif #endif @@ -389,8 +393,11 @@ class Logger : public Component { #ifdef USE_LIBRETINY UARTSelection uart_{UART_SELECTION_DEFAULT}; #endif -#if defined(USE_ESP32) || defined(USE_HOST) +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) bool main_task_recursion_guard_{false}; +#ifdef USE_LIBRETINY + bool non_main_task_recursion_guard_{false}; // Shared guard for all non-main tasks on LibreTiny +#endif #else bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms #endif @@ -450,6 +457,38 @@ class Logger : public Component { pthread_setspecific(log_recursion_key_, (void *) 0); } +#elif defined(USE_LIBRETINY) + // LibreTiny doesn't have FreeRTOS TLS, so use a simple approach: + // - Main task uses dedicated boolean (same as ESP32) + // - Non-main tasks share a single recursion guard + // This is safe because: + // - Recursion from logging within logging is the main concern + // - Cross-task "recursion" is prevented by the buffer mutex anyway + // - Missing a recursive call from another task is acceptable (falls back to direct output) + + inline bool HOT check_and_set_task_log_recursion_(bool is_main_task) { + if (is_main_task) { + const bool was_recursive = main_task_recursion_guard_; + main_task_recursion_guard_ = true; + return was_recursive; + } + + // For non-main tasks, use a simple shared guard + // This may block legitimate concurrent logs from different tasks, + // but that's acceptable - they'll fall back to direct console output + const bool was_recursive = non_main_task_recursion_guard_; + non_main_task_recursion_guard_ = true; + return was_recursive; + } + + inline void HOT reset_task_log_recursion_(bool is_main_task) { + if (is_main_task) { + main_task_recursion_guard_ = false; + return; + } + + non_main_task_recursion_guard_ = false; + } #endif #ifdef USE_HOST diff --git a/esphome/components/logger/task_log_buffer_libretiny.cpp b/esphome/components/logger/task_log_buffer_libretiny.cpp new file mode 100644 index 0000000000..580066e621 --- /dev/null +++ b/esphome/components/logger/task_log_buffer_libretiny.cpp @@ -0,0 +1,206 @@ +#ifdef USE_LIBRETINY + +#include "task_log_buffer_libretiny.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + +namespace esphome::logger { + +TaskLogBufferLibreTiny::TaskLogBufferLibreTiny(size_t total_buffer_size) { + this->size_ = total_buffer_size; + // Allocate memory for the circular buffer using ESPHome's RAM allocator + RAMAllocator allocator; + this->storage_ = allocator.allocate(this->size_); + // Create mutex for thread-safe access + this->mutex_ = xSemaphoreCreateMutex(); +} + +TaskLogBufferLibreTiny::~TaskLogBufferLibreTiny() { + if (this->mutex_ != nullptr) { + vSemaphoreDelete(this->mutex_); + this->mutex_ = nullptr; + } + if (this->storage_ != nullptr) { + RAMAllocator allocator; + allocator.deallocate(this->storage_, this->size_); + this->storage_ = nullptr; + } +} + +size_t TaskLogBufferLibreTiny::available_contiguous_space() const { + if (this->head_ >= this->tail_) { + // head is ahead of or equal to tail + // Available space is from head to end, plus from start to tail + // But for contiguous, just from head to end (minus 1 to avoid head==tail ambiguity) + size_t space_to_end = this->size_ - this->head_; + if (this->tail_ == 0) { + // Can't use the last byte or head would equal tail + return space_to_end > 0 ? space_to_end - 1 : 0; + } + return space_to_end; + } else { + // tail is ahead of head + // Available contiguous space is from head to tail - 1 + return this->tail_ - this->head_ - 1; + } +} + +bool TaskLogBufferLibreTiny::borrow_message_main_loop(LogMessage **message, const char **text) { + if (message == nullptr || text == nullptr) { + return false; + } + + // Check if buffer was initialized successfully + if (this->mutex_ == nullptr || this->storage_ == nullptr) { + return false; + } + + // Try to take mutex without blocking - if busy, we'll get messages next loop iteration + if (xSemaphoreTake(this->mutex_, 0) != pdTRUE) { + return false; + } + + if (this->head_ == this->tail_) { + xSemaphoreGive(this->mutex_); + return false; + } + + // Read message header from tail + LogMessage *msg = reinterpret_cast(this->storage_ + this->tail_); + + // Check for padding marker (indicates wrap-around) + // We check the level field since valid levels are 0-7, and 0xFF indicates padding + if (msg->level == PADDING_MARKER_LEVEL) { + // Skip to start of buffer and re-read + this->tail_ = 0; + msg = reinterpret_cast(this->storage_); + } + *message = msg; + *text = msg->text_data(); + this->current_message_size_ = message_total_size(msg->text_length); + + // Keep mutex held until release_message_main_loop() + return true; +} + +void TaskLogBufferLibreTiny::release_message_main_loop() { + // Advance tail past the current message + this->tail_ += this->current_message_size_; + + // Handle wrap-around if we've reached the end + if (this->tail_ >= this->size_) { + this->tail_ = 0; + } + + this->message_count_--; + this->current_message_size_ = 0; + + xSemaphoreGive(this->mutex_); +} + +bool TaskLogBufferLibreTiny::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, + TaskHandle_t task_handle, const char *format, va_list args) { + // First, calculate the exact length needed using a null buffer (no actual writing) + va_list args_copy; + va_copy(args_copy, args); + int ret = vsnprintf(nullptr, 0, format, args_copy); + va_end(args_copy); + + if (ret <= 0) { + return false; // Formatting error or empty message + } + + // Calculate actual text length (capped to maximum size) + static constexpr size_t MAX_TEXT_SIZE = 255; + size_t text_length = (static_cast(ret) > MAX_TEXT_SIZE) ? MAX_TEXT_SIZE : ret; + + // Calculate total size needed (header + text length + null terminator) + size_t total_size = message_total_size(text_length); + + // Check if buffer was initialized successfully + if (this->mutex_ == nullptr || this->storage_ == nullptr) { + return false; // Buffer not initialized, fall back to direct output + } + + // Try to acquire mutex without blocking - don't block logging tasks + if (xSemaphoreTake(this->mutex_, 0) != pdTRUE) { + return false; // Mutex busy, fall back to direct output + } + + // Check if we have enough contiguous space + size_t contiguous = this->available_contiguous_space(); + + if (contiguous < total_size) { + // Not enough contiguous space at end + // Check if we can wrap around + size_t space_at_start = (this->head_ >= this->tail_) ? this->tail_ : 0; + if (space_at_start > 0) { + space_at_start--; // Leave 1 byte gap to distinguish full from empty + } + + // Need at least enough space to safely write padding marker (level field is at end of struct) + constexpr size_t PADDING_MARKER_MIN_SPACE = offsetof(LogMessage, level) + 1; + + if (space_at_start >= total_size && this->head_ > 0 && contiguous >= PADDING_MARKER_MIN_SPACE) { + // Add padding marker (set level field to indicate this is padding, not a real message) + LogMessage *padding = reinterpret_cast(this->storage_ + this->head_); + padding->level = PADDING_MARKER_LEVEL; + this->head_ = 0; + } else { + // Not enough space anywhere, or can't safely write padding marker + xSemaphoreGive(this->mutex_); + return false; + } + } + + // Write message header + LogMessage *msg = reinterpret_cast(this->storage_ + this->head_); + msg->level = level; + msg->tag = tag; + msg->line = line; + + // Store the thread name now to avoid crashes if task is deleted before processing + const char *thread_name = pcTaskGetTaskName(task_handle); + if (thread_name != nullptr) { + strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1); + msg->thread_name[sizeof(msg->thread_name) - 1] = '\0'; + } else { + msg->thread_name[0] = '\0'; + } + + // Format the message text directly into the buffer + char *text_area = msg->text_data(); + ret = vsnprintf(text_area, text_length + 1, format, args); + + if (ret <= 0) { + xSemaphoreGive(this->mutex_); + return false; + } + + // Remove trailing newlines + while (text_length > 0 && text_area[text_length - 1] == '\n') { + text_length--; + } + + msg->text_length = text_length; + + // Advance head + this->head_ += total_size; + + // Handle wrap-around (shouldn't happen due to contiguous space check, but be safe) + if (this->head_ >= this->size_) { + this->head_ = 0; + } + + this->message_count_++; + + xSemaphoreGive(this->mutex_); + return true; +} + +} // namespace esphome::logger + +#endif // USE_ESPHOME_TASK_LOG_BUFFER +#endif // USE_LIBRETINY diff --git a/esphome/components/logger/task_log_buffer_libretiny.h b/esphome/components/logger/task_log_buffer_libretiny.h new file mode 100644 index 0000000000..bf6b2d2fa4 --- /dev/null +++ b/esphome/components/logger/task_log_buffer_libretiny.h @@ -0,0 +1,103 @@ +#pragma once + +#ifdef USE_LIBRETINY + +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" + +#ifdef USE_ESPHOME_TASK_LOG_BUFFER +#include +#include +#include +#include +#include +#include + +namespace esphome::logger { + +/** + * @brief Task log buffer for LibreTiny platform using mutex-protected circular buffer. + * + * Why This Is Critical: + * Without thread-safe logging, when a non-main task logs a message, it would directly + * call the logger which builds a protobuf message in a shared buffer. If this happens + * while the main loop is also using that buffer (e.g., sending API responses), the + * buffer gets corrupted, sending garbage to all connected API clients and breaking + * their connections. This buffer ensures log messages from other tasks are queued + * safely and processed only from the main loop. + * + * Threading Model: Multi-Producer Single-Consumer (MPSC) + * - Multiple FreeRTOS tasks can safely call send_message_thread_safe() concurrently + * - Only the main loop task calls borrow_message_main_loop() and release_message_main_loop() + * + * This uses a simple circular buffer protected by a FreeRTOS mutex. Unlike ESP32, + * LibreTiny lacks hardware atomic support (ARM968E-S has no LDREX/STREX), so we use + * a volatile counter for fast has_messages() checks instead of atomics. + * + * Design: + * - Variable-size messages with header + text stored contiguously (NOSPLIT style) + * - FreeRTOS mutex protects all buffer operations + * - Volatile counter enables fast has_messages() without lock overhead + * - If message doesn't fit at end, padding is added and message wraps to start + */ +class TaskLogBufferLibreTiny { + public: + // Structure for a log message header (text data follows immediately after) + struct LogMessage { + const char *tag; // We store the pointer, assuming tags are static + char thread_name[16]; // Store thread name directly (only used for non-main threads) + uint16_t text_length; // Length of the message text (up to ~64KB) + uint16_t line; // Source code line number + uint8_t level; // Log level (0-7) + + // Methods for accessing message contents + inline char *text_data() { return reinterpret_cast(this) + sizeof(LogMessage); } + inline const char *text_data() const { return reinterpret_cast(this) + sizeof(LogMessage); } + }; + + // Padding marker level to indicate wrap-around point (stored in LogMessage.level field) + // Valid log levels are 0-7, so 0xFF cannot be a real message + static constexpr uint8_t PADDING_MARKER_LEVEL = 0xFF; + + // Constructor that takes a total buffer size + explicit TaskLogBufferLibreTiny(size_t total_buffer_size); + ~TaskLogBufferLibreTiny(); + + // NOT thread-safe - borrow a message from the buffer, only call from main loop + bool borrow_message_main_loop(LogMessage **message, const char **text); + + // NOT thread-safe - release a message buffer, only call from main loop + void release_message_main_loop(); + + // Thread-safe - send a message to the buffer from any thread + bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, TaskHandle_t task_handle, + const char *format, va_list args); + + // Fast check using volatile counter - no lock needed + // Worst case: miss a message for one loop iteration (~8ms at 7000 loops/min) + inline bool HOT has_messages() const { return this->message_count_ != 0; } + + // Get the total buffer size in bytes + inline size_t size() const { return this->size_; } + + private: + // Calculate total size needed for a message (header + text + null terminator) + static inline size_t message_total_size(size_t text_length) { return sizeof(LogMessage) + text_length + 1; } + + // Calculate available contiguous space at write position + size_t available_contiguous_space() const; + + uint8_t *storage_{nullptr}; // Pointer to allocated memory + size_t size_{0}; // Size of allocated memory + size_t head_{0}; // Write position + size_t tail_{0}; // Read position + + SemaphoreHandle_t mutex_{nullptr}; // FreeRTOS mutex for thread safety + volatile uint16_t message_count_{0}; // Fast check counter (dirty read OK) + size_t current_message_size_{0}; // Size of currently borrowed message +}; + +} // namespace esphome::logger + +#endif // USE_ESPHOME_TASK_LOG_BUFFER +#endif // USE_LIBRETINY diff --git a/tests/components/logger/test.bk72xx-ard.yaml b/tests/components/logger/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..c5bc9de6e8 --- /dev/null +++ b/tests/components/logger/test.bk72xx-ard.yaml @@ -0,0 +1,5 @@ +<<: !include common-default_uart.yaml + +logger: + id: logger_id + task_log_buffer_size: 1024B diff --git a/tests/components/logger/test.ln882x-ard.yaml b/tests/components/logger/test.ln882x-ard.yaml new file mode 100644 index 0000000000..c5bc9de6e8 --- /dev/null +++ b/tests/components/logger/test.ln882x-ard.yaml @@ -0,0 +1,5 @@ +<<: !include common-default_uart.yaml + +logger: + id: logger_id + task_log_buffer_size: 1024B diff --git a/tests/components/logger/test.rtl87xx-ard.yaml b/tests/components/logger/test.rtl87xx-ard.yaml new file mode 100644 index 0000000000..c5bc9de6e8 --- /dev/null +++ b/tests/components/logger/test.rtl87xx-ard.yaml @@ -0,0 +1,5 @@ +<<: !include common-default_uart.yaml + +logger: + id: logger_id + task_log_buffer_size: 1024B